/*
 * Copyright (c) Likewise Software.  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
 */

/*
 * Module Name:
 *
 *        main.c
 *
 * Abstract:
 *
 *        Server main logic
 *
 * Authors: Brian Koropoff (bkoropoff@likewise.com)
 *
 */

#include "includes.h"

SM_GLOBAL_STATE gLwsmState =
{
    .pPool = NULL,
    .pIpcContext = NULL,
    .pContolProtocol = NULL,
    .pControlServer = NULL,
    .bStartAsDaemon = FALSE,
    .notifyPipe = {-1, -1},
    .logLevel = 0,
    .pszLogFilePath = NULL,
    .bSyslog = FALSE,
    .bWatchdog = TRUE,
    .StuckTimeout = 15000,
    .RestartLimit = 2,
    .RestartPeriod = 30,
    .ControlLock = -1
};

static
DWORD
LwSmParseArguments(
    int argc,
    char** ppszArgv
    );

static
DWORD
LwSmDaemonize(
    VOID
    );

static
DWORD
LwSmMain(
    VOID
    );

static
DWORD
LwSmConfigureLogging(
    PCSTR pszProgramName
    );

static
DWORD
LwSmIpcInit(
    VOID
    );

static
DWORD
LwSmStartIpcServer(
    VOID
    );

static
DWORD
LwSmStopDirectServer(
    VOID
    );

static
DWORD
LwSmStopIpcServer(
    VOID
    );

static
DWORD
LwSmShutdownServices(
    VOID
    );

static
DWORD
LwSmShutdownService(
    PSM_TABLE_ENTRY pEntry
    );

static
DWORD
LwSmNotify(
    DWORD Error
    );

static
DWORD
LwSmControlLock(
    VOID
    );

int
main(
    int argc,
    char** ppszArgv
    )
{
    DWORD dwError = 0;

    /* Parse command line */
    dwError = LwSmParseArguments(argc, ppszArgv);
    BAIL_ON_ERROR(dwError);

    /* Block all signals */
    dwError = LwNtStatusToWin32Error(LwRtlBlockSignals());
    BAIL_ON_ERROR(dwError);

    /* Fork into background if running as a daemon */
    if (gLwsmState.bStartAsDaemon)
    {
        dwError = LwSmDaemonize();
        BAIL_ON_ERROR(dwError);
    }

    /* If we're starting as the control server, acquire lock */
    if (!gLwsmState.bContainer)
    {
        dwError = LwSmControlLock();
        BAIL_ON_ERROR(dwError);
    }

    /* Create thread pool */
    dwError = LwNtStatusToWin32Error(LwRtlCreateThreadPool(&gLwsmState.pPool, NULL));
    BAIL_ON_ERROR(dwError);

    dwError = LWNetExtendEnvironmentForKrb5Affinity(FALSE);
    BAIL_ON_ERROR(dwError);

    /* Mac OS X - avoid potential circular calls into directory services */
    dwError = LwDsCacheAddPidException(getpid());
    BAIL_ON_ERROR(dwError);

    /* Initialize i18n */
    setlocale(LC_ALL, "");

    /* Initialize logging subsystem */
    LwSmLogInit();

    /* Set up logging */
    dwError = LwSmConfigureLogging(gLwsmState.pName);
    BAIL_ON_ERROR(dwError);

    /* Set up eventing */
    dwError = LwSmEventlogInit();
    BAIL_ON_ERROR(dwError);

    /* Initialize the container subsystem */
    dwError = LwSmContainerInit();
    BAIL_ON_ERROR(dwError);

    /* Initialize the service table subsystem */
    dwError = LwSmTableInit();
    BAIL_ON_ERROR(dwError);

    /* Initialize watchdog subsystem */
    dwError = LwSmWatchdogInit();
    BAIL_ON_ERROR(dwError);

    /* Enter main loop */
    dwError = LwSmMain();
    BAIL_ON_ERROR(dwError);

error:

    /* If we are starting as a daemon and have not
       notified the parent process yet, notify it
       of an error now */
    if (gLwsmState.bStartAsDaemon && !gLwsmState.bNotified)
    {
        LwSmNotify(dwError);
    }

    /* Shut down watchdog table */
    LwSmWatchdogShutdown();

    /* Shut down service table */
    LwSmTableShutdown();

    /* Shut down containers */
    LwSmContainerShutdown();

    /* Shut down eventing */
    LwSmEventlogShutdown();

    /* Shut down logging */
    LwSmLoggingShutdown();

    /* Remove DS cache exception */
    LwDsCacheRemovePidException(getpid());

    /* Free thread pool */
    LwRtlFreeThreadPool(&gLwsmState.pPool);

    /* Close control file if it is open */
    if (gLwsmState.ControlLock >= 0)
    {
        unlink(CONTROL_LOCK);
        close(gLwsmState.ControlLock);
        gLwsmState.ControlLock = -1;
    }

    /* Free misc. memory */
    LW_SAFE_FREE_MEMORY(gLwsmState.pGroup);

    if (dwError)
    {
        fprintf(stderr, "Error: %s (%d)\n", LwWin32ExtErrorToName(dwError), (int) dwError);
    }

    return dwError ? 1 : 0;
}

static
DWORD
Usage(
    int argc,
    char** pArgv
    )
{
    DWORD dwError = 0;

    printf("Usage: %s [ options ... ]\n\n", pArgv[0]);
    printf("Options:\n"
           "    --start-as-daemon      Start as a background process\n"
           "    --syslog               Log to syslog (default when starting as daemon)\n"
           "    --logfile              Log to file\n"
           "    --loglevel <level>     Set log level to <level>\n"
           "                           (error, warning, info, verbose, debug, trace)\n"
           "    --container <group>    Start as a container for service group <group>\n"
           "    --help                 Show usage information\n");

    return dwError;
}

static
DWORD
LwSmParseArguments(
    int argc,
    char** ppszArgv
    )
{
    DWORD dwError = 0;
    int i = 0;

    if (!strcmp(ppszArgv[0], CONTAINER_PROCESS_NAME))
    {
        if (argc != 2)
        {
            dwError = ERROR_INVALID_PARAMETER;
            BAIL_ON_ERROR(dwError);
        }
        gLwsmState.bContainer = TRUE;
        gLwsmState.pName = ppszArgv[1];
        gLwsmState.bSyslog = TRUE;
    }
    else
    {
        gLwsmState.pName = ppszArgv[0];

        for (i = 1; i < argc; i++)
        {
            if (!strcmp(ppszArgv[i], "--start-as-daemon"))
            {
                gLwsmState.bStartAsDaemon = TRUE;
                gLwsmState.bSyslog = TRUE;
                gLwsmState.logLevel = LW_SM_LOG_LEVEL_INFO;
            }
            else if (!strcmp(ppszArgv[i], "--syslog"))
            {
                gLwsmState.bSyslog = TRUE;
                gLwsmState.logLevel = LW_SM_LOG_LEVEL_INFO;
            }
            else if (!strcmp(ppszArgv[i], "--loglevel"))
            {
                if (++i >= argc)
                {
                    dwError = LW_ERROR_INVALID_PARAMETER;
                    BAIL_ON_ERROR(dwError);
                }

                dwError = LwSmLogLevelNameToLogLevel(
                    ppszArgv[i],
                    &gLwsmState.logLevel);
                BAIL_ON_ERROR(dwError);
            }
            else if (!strcmp(ppszArgv[i], "--logfile"))
            {
                if (++i >= argc)
                {
                    dwError = LW_ERROR_INVALID_PARAMETER;
                    BAIL_ON_ERROR(dwError);
                }

                gLwsmState.pszLogFilePath = ppszArgv[i];
            }
            else if (!strcmp(ppszArgv[i], "--container"))
            {
                if (++i >= argc)
                {
                    dwError = LW_ERROR_INVALID_PARAMETER;
                    BAIL_ON_ERROR(dwError);
                }

                dwError = LwMbsToWc16s(ppszArgv[i], &gLwsmState.pGroup);
                BAIL_ON_ERROR(dwError);

                gLwsmState.bContainer = TRUE;
            }
            else if (!strcmp(ppszArgv[i], "--help"))
            {
                Usage(argc, ppszArgv);
                exit(0);
            }
        }
    }

error:

    return dwError;
}

static
DWORD
LwSmControlLock(
    VOID
    )
{
    DWORD dwError = ERROR_SUCCESS;
    struct flock lock = {0};
    char buf[20];
    int len = 0;

    lock.l_type = F_WRLCK;
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;
    lock.l_pid = getpid();

    if ((gLwsmState.ControlLock = open(CONTROL_LOCK, O_WRONLY | O_CREAT, 0644)) < 0)
    {
        dwError = LwErrnoToWin32Error(errno);
        BAIL_ON_ERROR(dwError);
    }

    if (fcntl(gLwsmState.ControlLock, F_SETFD, FD_CLOEXEC) < 0)
    {
        dwError = LwErrnoToWin32Error(errno);
        BAIL_ON_ERROR(dwError);
    }

    if (fcntl(gLwsmState.ControlLock, F_SETLK, &lock) < 0)
    {
        switch(errno)
        {
        case EACCES:
        case EAGAIN:
            dwError = ERROR_SERVICE_ALREADY_RUNNING;
            break;
        default:
            dwError = LwErrnoToWin32Error(errno);
            break;
        }

        BAIL_ON_ERROR(dwError);
    }

    /* We're the only lwsmd process running. Set pid in lock file. */
    if (ftruncate(gLwsmState.ControlLock, 0) < 0)
    {
        dwError = LwErrnoToWin32Error(errno);
        BAIL_ON_ERROR(dwError);
    }

    len = snprintf(buf, sizeof(buf)-1, "%d\n", lock.l_pid);
    if (write(gLwsmState.ControlLock, buf, len) != len)
    {
        dwError = LwErrnoToWin32Error(errno);
        BAIL_ON_ERROR(dwError);
    }

cleanup:

    return dwError;

error:

    if (gLwsmState.ControlLock >= 0)
    {
        close(gLwsmState.ControlLock);
        gLwsmState.ControlLock = -1;
    }

    goto cleanup;
}

static
DWORD
LwSmDaemonize(
    VOID
    )
{
    DWORD dwError = 0;
    pid_t pid = -1;
    int ret = 0;
    int devNull = -1;
    int i = 0;

    /* Open a pipe so the daemon process can notify us
       when it is ready to accept connections.  This means
       the foreground process will not exit until the daemon
       is fully usable */
    if (pipe(gLwsmState.notifyPipe) != 0)
    {
        dwError = LwMapErrnoToLwError(errno);
        BAIL_ON_ERROR(dwError);
    }

    pid = fork();

    if (pid < 0)
    {
        dwError = LwMapErrnoToLwError(errno);
        BAIL_ON_ERROR(dwError);
    }
    else if (pid > 0)
    {
        /* We are the foreground process */

        /* Close the write end of the pipe since we don't need it */
        close(gLwsmState.notifyPipe[1]);

        /* Wait until daemon process indicates it is fully started by sending error code */
        do
        {
            ret = read(gLwsmState.notifyPipe[0], &dwError, sizeof(dwError));
        } while (ret < 0 && errno == EINTR);

        if (dwError)
        {
            fprintf(stderr, "Error: %s (%d)\n", LwWin32ExtErrorToName(dwError), (int) dwError);
        }

        exit(dwError ? 1 : 0);
    }
    
    /* We are the intermediate background process.
       Isolate ourselves from the state of the foreground process
       by changing directory, becoming a session leader,
       redirecting stdout/stderr/stdin to /dev/null, and setting
       a reasonable umask */
    if (chdir("/") != 0)
    {
        dwError = LwMapErrnoToLwError(errno);
        BAIL_ON_ERROR(dwError);
    }

    if (setsid() < 0)
    {
        dwError = LwMapErrnoToLwError(errno);
        BAIL_ON_ERROR(dwError);
    }

    if ((devNull = open("/dev/null", O_RDWR)) < 0)
    {
        dwError = LwMapErrnoToLwError(errno);
        BAIL_ON_ERROR(dwError);
    }

    for (i = 0; i <= 2; i++)
    {
        if (dup2(devNull, i) < 0)
        {
            dwError = LwMapErrnoToLwError(errno);
            BAIL_ON_ERROR(dwError);
        }
    }

    close(devNull);
    umask(0022);

    /* Now that we are isolated, fork the actual daemon process and exit */
    pid = fork();

    if (pid < 0)
    {
        dwError = LwMapErrnoToLwError(errno);
        BAIL_ON_ERROR(dwError);
    }
    else if (pid > 0)
    {
        /* The intermediate background process exits here */
        exit(0);
    }

    /* We are the actual daemon process, continue with startup */

    /* Close the read end of the notification pipe now since we don't need it */
    close(gLwsmState.notifyPipe[0]);

    /* Prevent the write end of the notification pipe from being inherited by children */
    if (fcntl(gLwsmState.notifyPipe[1], F_SETFD, FD_CLOEXEC) < 0)
    {
        dwError = LwMapErrnoToLwError(errno);
        BAIL_ON_ERROR(dwError);
    }

error:

    return dwError;
}

static
DWORD
LwSmNotify(
    DWORD Error
    )
{
    DWORD dwError = 0;
    int ret = 0;

    do 
    {
        ret = write(gLwsmState.notifyPipe[1], &Error, sizeof(Error));
    } while (ret < 0 && errno == EINTR);

    if (ret < 0)
    {
        dwError = LwMapErrnoToLwError(errno);
        BAIL_ON_ERROR(dwError);
    }

cleanup:

    close(gLwsmState.notifyPipe[1]);

    return dwError;

error:

    goto cleanup;
}

static
VOID
Startup(
    PVOID pUnused
    )
{
    DWORD dwError = 0;

    SM_LOG_INFO("Likewise Service Manager starting up");

    /* Initialize IPC system and direct container endpoint */
    dwError = LwSmIpcInit();
    BAIL_ON_ERROR(dwError);

    if (!gLwsmState.bContainer)
    {
        /* Bootstrap ourselves by adding and starting any
           services we need to load configuration (e.g. registry) */
        dwError = LwSmBootstrap();
        BAIL_ON_ERROR(dwError);

        /* Read configuration and populate service table */
        dwError = LwSmPopulateTable();
        BAIL_ON_ERROR(dwError);

        /* Shut down bootstrap services now that we are bootstrapped */
        dwError = LwSmBootstrapDone();
        BAIL_ON_ERROR(dwError);
    }

    /* Start IPC servers */
    dwError = LwSmStartIpcServer();
    BAIL_ON_ERROR(dwError);

    /* If we are starting as a daemon, indicate that we
       are ready to the parent process.  This ensures that
       the parent does not exit until we are actually accepting
       IPC connections */
    if (gLwsmState.bStartAsDaemon)
    {
        dwError = LwSmNotify(0);
        BAIL_ON_ERROR(dwError);
        gLwsmState.bNotified = TRUE;
    }

    SM_LOG_INFO("Likewise Service Manager startup complete");

    return;

error:

    LwRtlExitMain(STATUS_UNSUCCESSFUL);
}

static
VOID
RefreshConfig(
    PVOID pUnused
    )
{
    DWORD dwError = 0;

    dwError = LwSmPopulateTable();
    BAIL_ON_ERROR(dwError);

    return;

error:

    LwRtlExitMain(STATUS_UNSUCCESSFUL);
}

static
VOID
Shutdown(
    PVOID pUnused
    )
{
    DWORD dwError = 0;

    /* Stop IPC servers */
    dwError = LwSmStopIpcServer();
    BAIL_ON_ERROR(dwError);

    if (!gLwsmState.bContainer)
    {
        /* Shut down all running services  */
        dwError = LwSmShutdownServices();
        BAIL_ON_ERROR(dwError);
    }

    /* Stop direct container */
    dwError  = LwSmStopDirectServer();
    BAIL_ON_ERROR(dwError);

    /* Exit from main loop */
    LwRtlExitMain(STATUS_SUCCESS);

    return;

error:

    LwRtlExitMain(STATUS_UNSUCCESSFUL);
}


static
VOID
MainTask(
    PLW_TASK pTask,
    PVOID pContext,
    LW_TASK_EVENT_MASK WakeMask,
    PLW_TASK_EVENT_MASK pWaitMask,
    PLONG64 pllTime
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    siginfo_t info;

    if (WakeMask & LW_TASK_EVENT_CANCEL)
    {
        *pWaitMask = LW_TASK_EVENT_COMPLETE;
        goto cleanup;
    }
    else if (WakeMask & LW_TASK_EVENT_INIT)
    {
        status = LwRtlSetTaskUnixSignal(pTask, SIGTERM, TRUE);
        BAIL_ON_ERROR(status);

        status = LwRtlSetTaskUnixSignal(pTask, SIGINT, TRUE);
        BAIL_ON_ERROR(status);

        status = LwRtlSetTaskUnixSignal(pTask, SIGHUP, TRUE);
        BAIL_ON_ERROR(status);

        status = LwRtlQueueWorkItem(gLwsmState.pPool, Startup, NULL, 0);
        BAIL_ON_ERROR(status);

        *pWaitMask = LW_TASK_EVENT_UNIX_SIGNAL;
    }
    else if (WakeMask & LW_TASK_EVENT_UNIX_SIGNAL)
    {
        while (LwRtlNextTaskUnixSignal(pTask, &info))
        {
            switch(info.si_signo)
            {
            case SIGTERM:
            case SIGINT:
                SM_LOG_VERBOSE(info.si_signo == SIGINT ?
                    "Shutting down on SIGINT" :
                    "Shutting down on SIGTERM");
                /* Shutting down stops all running services, which is a blocking operation */
                status = LwRtlQueueWorkItem(gLwsmState.pPool, Shutdown, NULL, 0);
                BAIL_ON_ERROR(status);
                *pWaitMask = LW_TASK_EVENT_COMPLETE;
                goto cleanup;
            case SIGHUP:
                SM_LOG_VERBOSE("Refreshing configuration on SIGHUP");
                /* Refreshing config reads from the registry, which is a blocking operation */
                status = LwRtlQueueWorkItem(gLwsmState.pPool, RefreshConfig, NULL, 0);
                BAIL_ON_ERROR(status);
                break;
            default:
                break;
            }
        }

        *pWaitMask = LW_TASK_EVENT_UNIX_SIGNAL;
    }

cleanup:

    return;

error:

    if (status)
    {
        LwRtlExitMain(status);
        *pWaitMask = LW_TASK_EVENT_COMPLETE;
    }

    goto cleanup;
}

static
DWORD
LwSmMain(
    VOID
    )
{
    DWORD dwError = 0;
    PLW_TASK pTask = NULL;

    dwError = LwNtStatusToWin32Error(LwRtlCreateTask(
                                         gLwsmState.pPool,
                                         &pTask,
                                         NULL,
                                         MainTask,
                                         NULL));
    BAIL_ON_ERROR(dwError);

    LwRtlWakeTask(pTask);

    dwError = LwNtStatusToWin32Error(LwRtlMain());
    BAIL_ON_ERROR(dwError);

cleanup:

     if (pTask)
     {
         LwRtlCancelTask(pTask);
         LwRtlWaitTask(pTask);
         LwRtlReleaseTask(&pTask);
     }

     return dwError;

error:

    goto cleanup;
}

static
DWORD
LwSmConfigureLogging(
    PCSTR pszProgramName
    )
{
    DWORD dwError = 0;

    LwSmLoggingInit(pszProgramName);

    if (gLwsmState.logLevel == 0)
    {
        if (gLwsmState.bStartAsDaemon || (gLwsmState.bContainer && !gLwsmState.pGroup))
        {
            gLwsmState.logLevel = LW_SM_LOG_LEVEL_WARNING;
        }
        else
        {
            gLwsmState.logLevel = LW_SM_LOG_LEVEL_VERBOSE;
        }
    }

    LwSmSetMaxLogLevel(NULL, gLwsmState.logLevel);

    if (gLwsmState.pszLogFilePath)
    {
        dwError = LwSmSetLoggerToPath(NULL, gLwsmState.pszLogFilePath);
        BAIL_ON_ERROR(dwError);
    }
    else if (gLwsmState.bSyslog)
    {
        dwError = LwSmSetLoggerToSyslog(NULL);
        BAIL_ON_ERROR(dwError);
    }
    else
    {
        dwError = LwSmSetLoggerToFile(NULL, stderr);
        BAIL_ON_ERROR(dwError);
    }

error:

    return dwError;
}

static
LWMsgBool
LwSmLogIpc (
    LWMsgLogLevel level,
    const char* pszMessage,
    const char* pszFunction,
    const char* pszFilename,
    unsigned int line,
    void* pData
    )
{
    LW_SM_LOG_LEVEL smLevel = LW_SM_LOG_LEVEL_DEBUG;
    LWMsgBool result = LWMSG_FALSE;

    switch (level)
    {
    case LWMSG_LOGLEVEL_ALWAYS:
        smLevel = LW_SM_LOG_LEVEL_ALWAYS;
        break;
    case LWMSG_LOGLEVEL_ERROR:
        smLevel = LW_SM_LOG_LEVEL_ERROR;
        break;
    case LWMSG_LOGLEVEL_WARNING:
        smLevel = LW_SM_LOG_LEVEL_WARNING;
        break;
    case LWMSG_LOGLEVEL_INFO:
        smLevel = LW_SM_LOG_LEVEL_INFO;
        break;
    case LWMSG_LOGLEVEL_VERBOSE:
        smLevel = LW_SM_LOG_LEVEL_VERBOSE;
        break;
    case LWMSG_LOGLEVEL_DEBUG:
        smLevel = LW_SM_LOG_LEVEL_DEBUG;
        break;
    case LWMSG_LOGLEVEL_TRACE:
        smLevel = LW_SM_LOG_LEVEL_TRACE;
        break;
    }

    if (LwRtlLogGetLevel() >= smLevel)
    {
        if (pszMessage)
        {
            LwSmLogMessage(
                smLevel,
                "lwsm-ipc",
                pszFunction,
                pszFilename,
                line,
                pszMessage);
        }
        result = LWMSG_TRUE;
    }
    else
    {
        result = LWMSG_FALSE;
    }

    return result;
}

static
DWORD
LwSmIpcInit(
    VOID
    )
{
    DWORD dwError = ERROR_SUCCESS;

    dwError = MAP_LWMSG_STATUS(lwmsg_context_new(NULL, &gLwsmState.pIpcContext));
    BAIL_ON_ERROR(dwError);

    lwmsg_context_set_log_function(
        gLwsmState.pIpcContext,
        LwSmLogIpc,
        NULL);

    dwError = MAP_LWMSG_STATUS(lwmsg_protocol_new(gLwsmState.pIpcContext, &gLwsmState.pContainerProtocol));
    BAIL_ON_ERROR(dwError);

    dwError = MAP_LWMSG_STATUS(lwmsg_protocol_add_protocol_spec(
        gLwsmState.pContainerProtocol,
        LwSmGetContainerProtocolSpec()));
    BAIL_ON_ERROR(dwError);

    dwError = MAP_LWMSG_STATUS(lwmsg_peer_new(
        gLwsmState.pIpcContext,
        gLwsmState.pContainerProtocol,
        &gLwsmState.pDirectServer));
    BAIL_ON_ERROR(dwError);

    dwError = MAP_LWMSG_STATUS(lwmsg_peer_add_listen_endpoint(
        gLwsmState.pDirectServer,
        LWMSG_ENDPOINT_DIRECT,
        "Container",
        0));
    BAIL_ON_ERROR(dwError);

    dwError = MAP_LWMSG_STATUS(lwmsg_peer_add_dispatch_spec(
        gLwsmState.pDirectServer,
        LwSmGetContainerDispatchSpec()));
    BAIL_ON_ERROR(dwError);

    dwError = MAP_LWMSG_STATUS(lwmsg_peer_start_listen(gLwsmState.pDirectServer));
    BAIL_ON_ERROR(dwError);

error:

    return dwError;
}

static
LWMsgStatus
ConstructContainerSession(
    LWMsgSecurityToken* poken,
    void* pData,
    void** ppSessionData
    )
{
    if (gLwsmState.bContainer)
    {
        *ppSessionData = pData;
    }
    else
    {
        *ppSessionData = NULL;
    }

    return LWMSG_STATUS_SUCCESS;
}

static
void
DestructContainerSession(
    LWMsgSecurityToken* pToken,
    void* pSessionData
    )
{
    // We lost connection to controller, so terminate process
    kill(getpid(), SIGTERM);
}

static
DWORD
LwSmStartIpcServer(
    VOID
    )
{
    DWORD dwError = 0;

    SM_LOG_VERBOSE("Starting IPC server");

    if (!gLwsmState.bContainer)
    {
        dwError = MAP_LWMSG_STATUS(lwmsg_protocol_new(gLwsmState.pIpcContext, &gLwsmState.pContolProtocol));
        BAIL_ON_ERROR(dwError);

        dwError = MAP_LWMSG_STATUS(lwmsg_protocol_add_protocol_spec(
            gLwsmState.pContolProtocol,
            LwSmIpcGetProtocolSpec()));
        BAIL_ON_ERROR(dwError);

        dwError = MAP_LWMSG_STATUS(lwmsg_peer_new(
            gLwsmState.pIpcContext,
            gLwsmState.pContolProtocol,
            &gLwsmState.pControlServer));
        BAIL_ON_ERROR(dwError);

        dwError = MAP_LWMSG_STATUS(lwmsg_peer_add_dispatch_spec(
            gLwsmState.pControlServer,
            LwSmGetDispatchSpec()));
        BAIL_ON_ERROR(dwError);

        dwError = MAP_LWMSG_STATUS(lwmsg_peer_add_listen_endpoint(
            gLwsmState.pControlServer,
            LWMSG_ENDPOINT_LOCAL,
            SM_ENDPOINT,
            0666));
        BAIL_ON_ERROR(dwError);

        dwError = MAP_LWMSG_STATUS(lwmsg_peer_start_listen(gLwsmState.pControlServer));
        BAIL_ON_ERROR(dwError);
    }

    dwError = MAP_LWMSG_STATUS(lwmsg_peer_new(
        gLwsmState.pIpcContext,
        gLwsmState.pContainerProtocol,
        &gLwsmState.pContainerServer));
    BAIL_ON_ERROR(dwError);

    dwError = MAP_LWMSG_STATUS(lwmsg_peer_set_listen_session_functions(
            gLwsmState.pContainerServer,
            ConstructContainerSession,
            DestructContainerSession,
            &gLwsmState));
    BAIL_ON_ERROR(dwError);

    if (gLwsmState.bContainer)
    {
        dwError = MAP_LWMSG_STATUS(lwmsg_peer_add_dispatch_spec(
            gLwsmState.pContainerServer,
            LwSmGetContainerDispatchSpec()));
        BAIL_ON_ERROR(dwError);

        if (!gLwsmState.pGroup)
        {
            dwError = MAP_LWMSG_STATUS(lwmsg_peer_accept_fd(
                gLwsmState.pContainerServer,
                LWMSG_ENDPOINT_PAIR,
                4));
            BAIL_ON_ERROR(dwError);
        }
        else
        {
            dwError = MAP_LWMSG_STATUS(lwmsg_peer_add_connect_endpoint(
                gLwsmState.pContainerServer,
                LWMSG_ENDPOINT_LOCAL,
                SC_ENDPOINT));
            BAIL_ON_ERROR(dwError);

            dwError = LwSmContainerRegister(gLwsmState.pContainerServer, gLwsmState.pGroup);
            BAIL_ON_ERROR(dwError);
        }
    }
    else
    {
        dwError = MAP_LWMSG_STATUS(lwmsg_peer_add_listen_endpoint(
            gLwsmState.pContainerServer,
            LWMSG_ENDPOINT_LOCAL,
            SC_ENDPOINT,
            0666));
        BAIL_ON_ERROR(dwError);

        dwError = MAP_LWMSG_STATUS(lwmsg_peer_add_dispatch_spec(
            gLwsmState.pContainerServer,
            LwSmGetContainerRegisterDispatchSpec()));
        BAIL_ON_ERROR(dwError);

        dwError = MAP_LWMSG_STATUS(lwmsg_peer_start_listen(gLwsmState.pContainerServer));
        BAIL_ON_ERROR(dwError);
    }

cleanup:

    return dwError;

error:

    goto cleanup;
}

static
DWORD
LwSmStopDirectServer(
    VOID
    )
{
    DWORD dwError = ERROR_SUCCESS;

    if (gLwsmState.pDirectServer)
    {
        dwError = MAP_LWMSG_STATUS(lwmsg_peer_stop_listen(gLwsmState.pDirectServer));
        BAIL_ON_ERROR(dwError);
    }

error:

    if (gLwsmState.pDirectServer)
    {
        lwmsg_peer_delete(gLwsmState.pDirectServer);
    }

    if (gLwsmState.pContainerProtocol)
    {
        lwmsg_protocol_delete(gLwsmState.pContainerProtocol);
    }

    if (gLwsmState.pIpcContext)
    {
        lwmsg_context_delete(gLwsmState.pIpcContext);
    }

    return dwError;
}

static
DWORD
LwSmStopIpcServer(
    VOID
    )
{
    DWORD dwError = 0;

    if (gLwsmState.pControlServer)
    {
        dwError = MAP_LWMSG_STATUS(lwmsg_peer_stop_listen(gLwsmState.pControlServer));
        BAIL_ON_ERROR(dwError);
    }

    if (gLwsmState.pContainerServer)
    {
        if (gLwsmState.pGroup)
        {
            dwError = MAP_LWMSG_STATUS(lwmsg_peer_disconnect(gLwsmState.pContainerServer));
            BAIL_ON_ERROR(dwError);
        }
        else
        {
            dwError = MAP_LWMSG_STATUS(lwmsg_peer_stop_listen(gLwsmState.pContainerServer));
            BAIL_ON_ERROR(dwError);
        }
    }

cleanup:

    if (gLwsmState.pControlServer)
    {
        lwmsg_peer_delete(gLwsmState.pControlServer);
    }

    if (gLwsmState.pContainerServer)
    {
        lwmsg_peer_delete(gLwsmState.pContainerServer);
    }

    if (gLwsmState.pContolProtocol)
    {
        lwmsg_protocol_delete(gLwsmState.pContolProtocol);
    }

    return dwError;

error:

    goto cleanup;
}

static
DWORD
LwSmShutdownServices(
    VOID
    )
{
    DWORD dwError = 0;
    PWSTR* ppwszServiceNames = NULL;
    size_t i = 0;
    PSM_TABLE_ENTRY pEntry = NULL;

    SM_LOG_INFO("Shutting down running services");

    dwError = LwSmTableEnumerateEntries(&ppwszServiceNames);
    BAIL_ON_ERROR(dwError);
    
    for (i = 0; ppwszServiceNames[i]; i++)
    {
        dwError = LwSmTableGetEntry(ppwszServiceNames[i], &pEntry);
        BAIL_ON_ERROR(dwError);

        dwError = LwSmShutdownService(pEntry);
        if (dwError)
        {
            /* Ignore errors and try to shut down everything we can */
            SM_LOG_WARNING("Could not shut down service: %u\n", (unsigned int) dwError);
            dwError = 0;
        }

        LwSmTableReleaseEntry(pEntry);
        pEntry = NULL;
    }

cleanup:

    if (ppwszServiceNames)
    {
        LwSmFreeStringList(ppwszServiceNames);
    }

    if (pEntry)
    {
        LwSmTableReleaseEntry(pEntry);
    }

    return dwError;

error:

    goto cleanup;
}

static
DWORD
LwSmShutdownService(
    PSM_TABLE_ENTRY pEntry
    )
{
    DWORD dwError = 0;
    PSM_TABLE_ENTRY pDep = NULL;
    PWSTR* ppwszDeps = NULL;
    size_t i = 0;

    dwError = LwSmTableGetEntryReverseDependencyClosure(pEntry, &ppwszDeps);
    BAIL_ON_ERROR(dwError);

    for (i = 0; ppwszDeps[i]; i++)
    {
        dwError = LwSmTableGetEntry(ppwszDeps[i], &pDep);
        BAIL_ON_ERROR(dwError);

        dwError = LwSmTableStopEntry(pDep);
        if (dwError)
        {
            dwError = LwSmTableKillEntry(pDep);
        }
        BAIL_ON_ERROR(dwError);

        LwSmTableReleaseEntry(pDep);
        pDep = NULL;
    }

    dwError = LwSmTableStopEntry(pEntry);
    if (dwError)
    {
        dwError = LwSmTableKillEntry(pEntry);
    }
    BAIL_ON_ERROR(dwError);

cleanup:

    if (pDep)
    {
        LwSmTableReleaseEntry(pDep);
    }

    if (ppwszDeps)
    {
        LwSmFreeStringList(ppwszDeps);
    }

    return dwError;

error:

    goto cleanup;
}
