/* Editor Settings: expandtabs and use 4 spaces for indentation
 * ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: *
 * -*- mode: c, c-basic-offset: 4 -*- */

/*
 * Copyright Likewise Software    2004-2008
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.  You should have received a copy of the GNU General
 * Public License along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 * LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING
 * TERMS AS WELL.  IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT
 * WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE
 * TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU
 * GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE.  IF YOU
 * HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING
 * TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT
 * license@likewisesoftware.com
 */

/*
 * Copyright (C) Likewise Software. All rights reserved.
 *
 * Module Name:
 *
 *        main.c
 *
 * Abstract:
 *
 *        Likewise Security and Authentication Subsystem (SMBSS)
 *
 *        Service Entry API
 *
 * Authors: Krishna Ganugapati (krishnag@likewisesoftware.com)
 *          Sriram Nambakam (snambakam@likewisesoftware.com)
 *          Kyle Stemen (kstemen@likewisesoftware.com)
 */
#include "includes.h"
#include "ioinit.h"
#include "ioipc.h"

typedef struct _SVC_HANDLE
{
    PWSTR pName;
} SVC_HANDLE, *PSVC_HANDLE;

static
NTSTATUS
LwIoSvcmStartIpc(
    VOID
    );

static
VOID
LwIoSvcmStopLwio(
    VOID
    );

#ifdef ENABLE_STATIC_DRIVERS

extern NTSTATUS IO_DRIVER_ENTRY(rdr)(IO_DRIVER_HANDLE, ULONG);
extern NTSTATUS IO_DRIVER_ENTRY(srv)(IO_DRIVER_HANDLE, ULONG);
extern NTSTATUS IO_DRIVER_ENTRY(npfs)(IO_DRIVER_HANDLE, ULONG);
extern NTSTATUS IO_DRIVER_ENTRY(pvfs)(IO_DRIVER_HANDLE, ULONG);

static IO_STATIC_DRIVER gStaticDrivers[] =
{
#ifdef ENABLE_RDR
    IO_STATIC_DRIVER_ENTRY(rdr),
#endif
#ifdef ENABLE_SRV
    IO_STATIC_DRIVER_ENTRY(srv),
#endif
#ifdef ENABLE_PVFS
    IO_STATIC_DRIVER_ENTRY(pvfs),
#endif
#ifdef ENABLE_NPFS
    IO_STATIC_DRIVER_ENTRY(npfs),
#endif
#ifdef ENABLE_DFS
    IO_STATIC_DRIVER_ENTRY(dfs),
#endif

    IO_STATIC_DRIVER_END
};

#endif

static LWMsgContext* pContext = NULL;
static LWMsgProtocol* pProtocol = NULL;
static LWMsgPeer* pServer = NULL;
static LONG volatile IomgrState = 0;
static LONG volatile LwioState = 0;
static LONG volatile RefCount = 0;
static pthread_mutex_t SvcMutex = PTHREAD_MUTEX_INITIALIZER;

#define STATE_STOPPED 0
#define STATE_STARTING 1
#define STATE_STOPPING 2
#define STATE_STARTED 4

NTSTATUS
LwIoSvcmInit(
    PCWSTR pServiceName,
    PLW_SVCM_INSTANCE pInstance
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    PSVC_HANDLE pHandle = NULL;

    status = LW_RTL_ALLOCATE_AUTO(&pHandle);
    BAIL_ON_NT_STATUS(status);

    status = LwRtlWC16StringDuplicate(&pHandle->pName, pServiceName);
    BAIL_ON_NT_STATUS(status);

    LwRtlSvcmSetData(pInstance, pHandle);

error:

    if (!NT_SUCCESS(status))
    {
        RTL_FREE(&pHandle);
    }

    return status;
}

VOID
LwIoSvcmDestroy(
    PLW_SVCM_INSTANCE pInstance
    )
{
    PSVC_HANDLE pHandle = LwRtlSvcmGetData(pInstance);

    if (pHandle)
    {
        RTL_FREE(&pHandle->pName);
        RTL_FREE(&pHandle);
    }
}

NTSTATUS
LwIoSvcmAcquireIomgr(
    VOID
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    BOOLEAN bLocked = FALSE;

    LWIO_LOCK_MUTEX(bLocked, &SvcMutex);

    if (RefCount++ == 0)
    {
        if (IomgrState != STATE_STOPPED)
        {
            ntStatus = STATUS_DEVICE_BUSY;
            BAIL_ON_NT_STATUS(ntStatus);
        }
        IomgrState = STATE_STARTING;

        LWIO_UNLOCK_MUTEX(bLocked, &SvcMutex);

#ifdef ENABLE_STATIC_DRIVERS
        ntStatus = IoInitialize(gStaticDrivers);
#else
        ntStatus = IoInitialize(NULL);
#endif
        BAIL_ON_NT_STATUS(ntStatus);

        LWIO_LOCK_MUTEX(bLocked, &SvcMutex);
        IomgrState = STATE_STARTED;
    }

error:

    LWIO_UNLOCK_MUTEX(bLocked, &SvcMutex);

    if (ntStatus)
    {
        LwIoSvcmReleaseIomgr();
    }

    return ntStatus;
}

static
NTSTATUS
LwIoSvcmStartLwio(
    VOID
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    BOOLEAN bLocked = FALSE;

    LWIO_LOCK_MUTEX(bLocked, &SvcMutex);

    if (LwioState != STATE_STOPPED)
    {
        ntStatus = STATUS_DEVICE_BUSY;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    LwioState = STATE_STARTING;

    LWIO_UNLOCK_MUTEX(bLocked, &SvcMutex);

    ntStatus = LwIoSvcmStartIpc();
    BAIL_ON_NT_STATUS(ntStatus);

    LWIO_LOG_INFO("lwio started");

    LWIO_LOCK_MUTEX(bLocked, &SvcMutex);

    LwioState = STATE_STARTED;

error:

    LWIO_UNLOCK_MUTEX(bLocked, &SvcMutex);

    if (ntStatus)
    {
        LwIoSvcmStopLwio();
    }

    return ntStatus;
}

NTSTATUS
LwIoSvcmStart(
    PLW_SVCM_INSTANCE pInstance,
    ULONG ArgCount,
    PWSTR* ppArgs,
    ULONG FdCount,
    int* pFds
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PSVC_HANDLE pHandle = LwRtlSvcmGetData(pInstance);
    static const WCHAR nameLwio[] = {'l', 'w', 'i', 'o', '\0'};
    BOOLEAN bStopOnError = FALSE;

    ntStatus = LwIoSvcmAcquireIomgr();
    BAIL_ON_NT_STATUS(ntStatus);
    bStopOnError = TRUE;

    if (LwRtlWC16StringIsEqual(pHandle->pName, nameLwio, TRUE))
    {
        ntStatus = LwIoSvcmStartLwio();
        BAIL_ON_NT_STATUS(ntStatus);
    }
    else
    {
        ntStatus = IoMgrLoadDriver(pHandle->pName);
        BAIL_ON_NT_STATUS(ntStatus);
    }

error:

    if (ntStatus && bStopOnError)
    {
        LwIoSvcmReleaseIomgr();
    }

    return ntStatus;
}

VOID
LwIoSvcmReleaseIomgr(
    VOID
    )
{
    BOOLEAN bLocked = FALSE;

    LWIO_LOCK_MUTEX(bLocked, &SvcMutex);

    if (--RefCount == 0 && IomgrState != STATE_STOPPED)
    {
        IomgrState = STATE_STOPPING;
        LWIO_UNLOCK_MUTEX(bLocked, &SvcMutex);

        IoCleanup();

        LWIO_LOCK_MUTEX(bLocked, &SvcMutex);
        IomgrState = STATE_STOPPED;
    }

    LWIO_UNLOCK_MUTEX(bLocked, &SvcMutex);
}

static
VOID
LwIoSvcmStopLwio(
    VOID
    )
{
    BOOLEAN bLocked = FALSE;

    LWIO_LOCK_MUTEX(bLocked, &SvcMutex);

    if (LwioState != STATE_STOPPED)
    {
        LwioState = STATE_STOPPING;
        LWIO_UNLOCK_MUTEX(bLocked, &SvcMutex);

        if (pServer)
        {
            lwmsg_peer_stop_listen(pServer);
            lwmsg_peer_delete(pServer);
        }

        if (pProtocol)
        {
            lwmsg_protocol_delete(pProtocol);
        }

        if (pContext)
        {
            lwmsg_context_delete(pContext);
        }

        LWIO_LOG_INFO("lwio stopped");

        LWIO_LOCK_MUTEX(bLocked, &SvcMutex);
        LwioState = STATE_STOPPED;
    }

    LWIO_UNLOCK_MUTEX(bLocked, &SvcMutex);
}

NTSTATUS
LwIoSvcmStop(
    PLW_SVCM_INSTANCE pInstance
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PSVC_HANDLE pHandle = LwRtlSvcmGetData(pInstance);
    static const WCHAR nameLwio[] = {'l', 'w', 'i', 'o', '\0'};

    if (LwRtlWC16StringIsEqual(pHandle->pName, nameLwio, TRUE))
    {
        LwIoSvcmStopLwio();
    }
    else
    {
        ntStatus = IoMgrUnloadDriver(pHandle->pName);
        BAIL_ON_NT_STATUS(ntStatus);
    }

    LwIoSvcmReleaseIomgr();

error:

    return ntStatus;
}

NTSTATUS
LwIoSvcmRefresh(
    PLW_SVCM_INSTANCE pInstance
    )
{
    return IoMgrRefreshConfig();
}

static LW_SVCM_MODULE gService =
{
    .Size = sizeof(gService),
    .Init = LwIoSvcmInit,
    .Destroy = LwIoSvcmDestroy,
    .Start = LwIoSvcmStart,
    .Stop = LwIoSvcmStop,
    .Refresh = LwIoSvcmRefresh
};

#define SVCM_ENTRY_POINT LW_RTL_SVCM_ENTRY_POINT_NAME(lwio)

PLW_SVCM_MODULE
SVCM_ENTRY_POINT(
    VOID
    )
{
    return &gService;
}

#ifdef ENABLE_STATIC_DRIVERS
#ifdef ENABLE_RDR
#define RDR_ENTRY_POINT LW_RTL_SVCM_ENTRY_POINT_NAME(rdr)
PLW_SVCM_MODULE
RDR_ENTRY_POINT(
    VOID
    )
{
    return &gService;
}
#endif
#ifdef ENABLE_SRV
#define SRV_ENTRY_POINT LW_RTL_SVCM_ENTRY_POINT_NAME(srv)
PLW_SVCM_MODULE
SRV_ENTRY_POINT(
    VOID
    )
{
    return &gService;
}
#endif
#ifdef ENABLE_PVFS
#define PVFS_ENTRY_POINT LW_RTL_SVCM_ENTRY_POINT_NAME(pvfs)
PLW_SVCM_MODULE
PVFS_ENTRY_POINT(
    VOID
    )
{
    return &gService;
}
#endif
#ifdef ENABLE_NPFS
#define NPFS_ENTRY_POINT LW_RTL_SVCM_ENTRY_POINT_NAME(npfs)
PLW_SVCM_MODULE
NPFS_ENTRY_POINT(
    VOID
    )
{
    return &gService;
}
#endif
#ifdef ENABLE_DFS
#define DFS_ENTRY_POINT LW_RTL_SVCM_ENTRY_POINT_NAME(dfs)
PLW_SVCM_MODULE
DFS_ENTRY_POINT(
    VOID
    )
{
    return &gService;
}
#endif
#endif

LWMsgBool
LwIoDaemonLogIpc (
    LWMsgLogLevel level,
    const char* pszMessage,
    const char* pszFunction,
    const char* pszFilename,
    unsigned int line,
    void* pData
    )
{
    LWIO_LOG_LEVEL ioLevel = LWIO_LOG_LEVEL_DEBUG;
    LWMsgBool result = LWMSG_FALSE;

    switch (level)
    {
    case LWMSG_LOGLEVEL_ALWAYS:
        ioLevel = LWIO_LOG_LEVEL_ALWAYS;
        break;
    case LWMSG_LOGLEVEL_ERROR:
        ioLevel = LWIO_LOG_LEVEL_ERROR;
        break;
    case LWMSG_LOGLEVEL_WARNING:
        ioLevel = LWIO_LOG_LEVEL_WARNING;
        break;
    case LWMSG_LOGLEVEL_INFO:
        ioLevel = LWIO_LOG_LEVEL_INFO;
        break;
    case LWMSG_LOGLEVEL_VERBOSE:
        ioLevel = LWIO_LOG_LEVEL_VERBOSE;
        break;
    case LWMSG_LOGLEVEL_DEBUG:
        ioLevel = LWIO_LOG_LEVEL_DEBUG;
        break;
    case LWMSG_LOGLEVEL_TRACE:
        ioLevel = LWIO_LOG_LEVEL_TRACE;
        break;
    }

    result = LwRtlLogGetLevel() >= ioLevel;

    if (pszMessage && result)
    {
        LW_RTL_LOG_RAW(ioLevel, "lwio-ipc", pszFunction, pszFilename, line, "%s", pszMessage);
    }

    return result;
}

static
NTSTATUS
LwIoSvcmStartIpc(
    VOID
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    LWMsgTime timeout = { 30, 0 }; /* 30 seconds */

    ntStatus = MAP_LWMSG_STATUS(lwmsg_context_new(NULL, &pContext));
    BAIL_ON_NT_STATUS(ntStatus);

    lwmsg_context_set_log_function(pContext, LwIoDaemonLogIpc, NULL);

    ntStatus = MAP_LWMSG_STATUS(lwmsg_protocol_new(pContext, &pProtocol));
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = LwIoDaemonIpcAddProtocolSpec(pProtocol);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = IoIpcAddProtocolSpec(pProtocol);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = MAP_LWMSG_STATUS(lwmsg_peer_new(pContext, pProtocol, &pServer));
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = LwIoDaemonIpcAddDispatch(pServer);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = IoIpcAddDispatch(pServer);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = MAP_LWMSG_STATUS(lwmsg_peer_add_listen_endpoint(
                    pServer,
                    LWMSG_ENDPOINT_DIRECT,
                    "lwio",
                    0));
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = MAP_LWMSG_STATUS(lwmsg_peer_add_listen_endpoint(
                    pServer,
                    LWMSG_ENDPOINT_LOCAL,
                    LWIO_SERVER_FILENAME,
                    (S_IRWXU | S_IRWXG | S_IRWXO)));
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = MAP_LWMSG_STATUS(lwmsg_peer_set_max_listen_clients(
                    pServer,
                    512));
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = MAP_LWMSG_STATUS(lwmsg_peer_set_timeout(
                    pServer,
                    LWMSG_TIMEOUT_IDLE,
                    &timeout));
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = MAP_LWMSG_STATUS(lwmsg_peer_start_listen(pServer));
    BAIL_ON_NT_STATUS(ntStatus);

cleanup:

    return ntStatus;

error:

    LWIO_LOG_ERROR("SMB Server stopping due to error [code: %d]", ntStatus);

    if (pServer)
    {
        LWMsgStatus status2 = lwmsg_peer_stop_listen(pServer);

        if (status2)
        {
            LWIO_LOG_ERROR("Error stopping server. [Error code:%d]", status2);
        }

        lwmsg_peer_delete(pServer);
    }

    if (pContext)
    {
        lwmsg_context_delete(pContext);
    }

    goto cleanup;
}

/*
local variables:
mode: c
c-basic-offset: 4
indent-tabs-mode: nil
tab-width: 4
end:
*/
