/*
 * NT Service interface Utility.
 *  Based on code written by
 *     Chas Woodfield, Fretwell Downing Informatics.
 * $Log: service.c,v $
 * Revision 1.5  2001/11/13 23:00:42  adam
 * Separate malloc debug library. Removal of ASN_COMPILED-#ifdefs.
 *
 * Revision 1.4  2000/12/05 19:05:10  adam
 * Service automatically starts in the directory from which it was installed.
 *
 * Revision 1.3  1999/06/10 11:45:30  adam
 * Added bend_start, bend_stop handlers and removed pre_init.
 * Handlers bend_start/bend_stop are called when service/daemon is
 * started/stopped.
 *
 * Revision 1.2  1999/02/02 13:57:36  adam
 * Uses preprocessor define WIN32 instead of WINDOWS to build code
 * for Microsoft WIN32.
 *
 * Revision 1.1  1997/11/07 13:31:52  adam
 * Added NT Service name part of statserv_options_block. Moved NT
 * service utility to server library.
 *
 * Revision 1.6  1997/09/18 08:49:14  adam
 * Option -runnormal no needed to run server in standalone mode.
 *
 * Revision 1.5  1997/09/17 12:10:43  adam
 * YAZ version 1.4.
 *
 * Revision 1.4  1997/09/09 10:10:20  adam
 * Another MSV5.0 port. Changed projects to include proper
 * library/include paths.
 * Server starts server in test-mode when no options are given.
 *
 * Revision 1.3  1997/09/04 13:50:30  adam
 * Bug fix in ztest.
 *
 */

/************************************************************/
/* Note this file is shared by all processes                */
/* Should really put it somewhere other than here           */
/* For some strange reason it won't work when part of a lib */
/************************************************************/

#ifdef WIN32

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <direct.h>

#include "service.h"

static AppService *pService = NULL;
static BOOL bRunAsService = TRUE;
static void *pAppHandle = NULL;

/* Private functions to this module */
void Service_Create(LPTSTR pAppName, LPTSTR pServiceName, LPTSTR pServiceDisplayName, LPTSTR pDependancies, int argc, char **argv);
void Service_Delete();
void Service_Initialize();
BOOL NotifyServiceController();
BOOL UpdateServiceStatus(DWORD Status);
void FailServiceStart(DWORD Win32Code, DWORD PrivateCode);
void CmdInstallService(int argc, char *argv[], BOOL bAutoStart);
void CmdRemoveService();
LPTSTR GetLastErrorText(LPTSTR lpszBuf, DWORD dwSize);
BOOL CheckServiceArguments(int argc, char *argv[]);

/* Callback functions for thee service manager */
void WINAPI ServiceMain(DWORD argc, LPTSTR argv[]);
void WINAPI ServiceControlHandler(DWORD fdwControl);

/* Function to handle Ctrl + C etc... */
BOOL EventHandlerRoutine(DWORD dwCtrlType);

void Service_Create(LPTSTR pAppName, LPTSTR pServiceName, LPTSTR pServiceDisplayName, LPTSTR pDependancies, int argc, char **argv)
{
    pService = malloc(sizeof(AppService));
    pService->pAppName = pAppName;
    pService->pServiceName = pServiceName;
    pService->pServiceDisplayName = pServiceDisplayName;
    pService->pDependancies = pDependancies;
    pService->hService = 0;
    pService->ServiceTable[0].lpServiceName = pServiceName; 
    pService->ServiceTable[0].lpServiceProc = ServiceMain; 
    pService->ServiceTable[1].lpServiceName = NULL; 
    pService->ServiceTable[1].lpServiceProc = NULL; 
    pService->argc = argc;
    pService->argv = argv;
}

void Service_Delete()
{
    if (pService != NULL)
    {
        /* Mark the service as stopping */
        UpdateServiceStatus(SERVICE_STOP_PENDING);

        /* Stop the service */
        StopAppService(pAppHandle);

        /* Service has now stopped */
        UpdateServiceStatus(SERVICE_STOPPED);

        /* Free the memory */
        free(pService);
        pService = NULL;
    }
}

void Service_Initialize()
{
    if (pService != NULL)
    {
        /* Register ourselves with the control dispatcher */
        StartServiceCtrlDispatcher(pService->ServiceTable);
    }
}

void WINAPI ServiceMain(DWORD argc, LPTSTR argv[])
{
    if (pService != NULL)
    {
        if (NotifyServiceController())
        {
            /* Set the status to pending */
            UpdateServiceStatus(SERVICE_START_PENDING);

            /* Lets attempt to start the service */
            if (StartAppService(pAppHandle, pService->argc, pService->argv))
            {
                /* Service is now up and running */
                UpdateServiceStatus(SERVICE_RUNNING);

                /* Lets wait for our clients */
                RunAppService(pAppHandle);
            }
            else
            {
                FailServiceStart(GetLastError(), 0);
                Service_Delete();
            }
        }
    }
}

BOOL NotifyServiceController()
{
    if (pService == NULL)
    {
        return(FALSE);
    }
    else
    {
        if (bRunAsService)
        {
            pService->ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
            pService->ServiceStatus.dwCurrentState = SERVICE_STOPPED;
            pService->ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
            pService->ServiceStatus.dwWin32ExitCode = 0;
            pService->ServiceStatus.dwServiceSpecificExitCode = 0;
            pService->ServiceStatus.dwCheckPoint = 0;
            pService->ServiceStatus.dwWaitHint = 0;
            pService->hService = RegisterServiceCtrlHandler(pService->pServiceName, ServiceControlHandler);

            if (pService->hService)
                UpdateServiceStatus(SERVICE_START_PENDING);
            else
                return(FALSE);
        }
        return(TRUE);
    }
}

void WINAPI ServiceControlHandler(DWORD fdwControl)
{
    if (pService != NULL)
    {
        switch (fdwControl)
        {
            case SERVICE_CONTROL_STOP:
                /* Update the service status to be pending */
                Service_Delete();
                break;

            case SERVICE_CONTROL_INTERROGATE:
                UpdateServiceStatus(pService->ServiceStatus.dwCurrentState);
                break;

            default:
                break;
        }
    }
}

BOOL UpdateServiceStatus(DWORD Status)
{
    if (pService != NULL)
    {
        if (pService->hService)
        {
            pService->ServiceStatus.dwCurrentState = Status;
            if ((Status == SERVICE_START_PENDING) || (Status == SERVICE_STOP_PENDING))
            {
                pService->ServiceStatus.dwCheckPoint ++;
                pService->ServiceStatus.dwWaitHint = 5000;    /* 5 sec.*/
            }
            else
            {
                pService->ServiceStatus.dwCheckPoint = 0;
                pService->ServiceStatus.dwWaitHint = 0;
            }

            return(SetServiceStatus(pService->hService, &pService->ServiceStatus));
        }
    }

    return(FALSE);
}

void FailServiceStart(DWORD Win32Code, DWORD PrivateCode)
{
    if (pService != NULL)
    {
        pService->ServiceStatus.dwWin32ExitCode = Win32Code;
        pService->ServiceStatus.dwServiceSpecificExitCode = PrivateCode;
        UpdateServiceStatus(SERVICE_STOPPED);
    }
}

void CmdInstallService(int argc, char *argv[], BOOL bAutoStart)
{
    if (pService != NULL)
    {
        SC_HANDLE   schService;
        SC_HANDLE   schSCManager;

        TCHAR szPath[2048];

        if (GetModuleFileName(NULL, szPath, 512) == 0)
        {
            _tprintf(TEXT("Unable to install %s - %s\n"), TEXT(pService->pServiceDisplayName), GetLastErrorText(pService->szErr, 256));
        }
        else
        {
            int i;
            char cwdstr[_MAX_PATH];

            if (!_getcwd(cwdstr, sizeof(cwdstr)))
                strcpy (cwdstr, ".");

            strcat (szPath, TEXT(" -runservice \""));
            strcat (szPath, cwdstr);
            strcat (szPath, "\"");

            for (i = 1; i < argc; i++)
            {
                /* We will add the given command line arguments to the command */
                /* We are not interested in the install and remove options */
                if ((stricmp("-install", argv[i]) != 0) &&
                    (stricmp("-installa", argv[i]) != 0) &&
                    (stricmp("-remove", argv[i]) != 0))
                {
                    strcat(szPath, TEXT(" "));
                    strcat(szPath, argv[i]);
                }
            }

            schSCManager = OpenSCManager(NULL,                   /* machine (NULL == local) */
                                         NULL,                   /* database (NULL == default) */
                                         SC_MANAGER_ALL_ACCESS); /* access required */
            if (schSCManager)
            {
                schService = CreateService(schSCManager,               /* SCManager database */
                                           TEXT(pService->pServiceName),        /* name of service */
                                           TEXT(pService->pServiceDisplayName), /* name to display */
                                           SERVICE_ALL_ACCESS,         /* desired access */
                                           SERVICE_WIN32_OWN_PROCESS,  /* service type */
                                           bAutoStart ? SERVICE_AUTO_START :
                                                        SERVICE_DEMAND_START, /* start type */
                                           SERVICE_ERROR_NORMAL,       /* error control type */
                                           szPath,                     /* service's binary */
                                           NULL,                       /* no load ordering group */
                                           NULL,                       /* no tag identifier */
                                           TEXT(pService->pDependancies),       /* dependencies */
                                           NULL,                       /* LocalSystem account */
                                           NULL);                      /* no password */

                if (schService)
                {
                    _tprintf(TEXT("%s installed.\n"), TEXT(pService->pServiceDisplayName));
                    CloseServiceHandle(schService);
                }
                else
                {
                    _tprintf(TEXT("CreateService failed - %s\n"), GetLastErrorText(pService->szErr, 256));
                }

                CloseServiceHandle(schSCManager);
            }
            else
                _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(pService->szErr,256));
        }
    }
}

void CmdRemoveService()
{
    if (pService != NULL)
    {
        SC_HANDLE   schService;
        SC_HANDLE   schSCManager;

        schSCManager = OpenSCManager(NULL,                   /* machine (NULL == local) */
                                     NULL,                   /* database (NULL == default) */
                                     SC_MANAGER_ALL_ACCESS); /* access required */
        if (schSCManager)
        {
            schService = OpenService(schSCManager, TEXT(pService->pServiceName), SERVICE_ALL_ACCESS);

            if (schService)
            {
                /* try to stop the service */
                if (ControlService(schService, SERVICE_CONTROL_STOP, &pService->ServiceStatus))
                {
                    _tprintf(TEXT("Stopping %s."), TEXT(pService->pServiceDisplayName));
                    Sleep(1000);

                    while (QueryServiceStatus(schService, &pService->ServiceStatus))
                    {
                        if (pService->ServiceStatus.dwCurrentState == SERVICE_STOP_PENDING)
                        {
                            _tprintf(TEXT("."));
                            Sleep( 1000 );
                        }
                        else
                            break;
                    }

                    if (pService->ServiceStatus.dwCurrentState == SERVICE_STOPPED)
                        _tprintf(TEXT("\n%s stopped.\n"), TEXT(pService->pServiceDisplayName));
                    else
                        _tprintf(TEXT("\n%s failed to stop.\n"), TEXT(pService->pServiceDisplayName));

                }

                /* now remove the service */
                if(DeleteService(schService))
                    _tprintf(TEXT("%s removed.\n"), TEXT(pService->pServiceDisplayName));
                else
                    _tprintf(TEXT("DeleteService failed - %s\n"), GetLastErrorText(pService->szErr,256));

                CloseServiceHandle(schService);
            }
            else
                _tprintf(TEXT("OpenService failed - %s\n"), GetLastErrorText(pService->szErr,256));

            CloseServiceHandle(schSCManager);
        }
        else
            _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(pService->szErr,256));
    }
}

LPTSTR GetLastErrorText(LPTSTR lpszBuf, DWORD dwSize)
{
    DWORD dwRet;
    LPTSTR lpszTemp = NULL;

    dwRet = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |FORMAT_MESSAGE_ARGUMENT_ARRAY,
                          NULL,
                          GetLastError(),
                          LANG_NEUTRAL,
                          (LPTSTR)&lpszTemp,
                          0,
                          NULL);

    /* supplied buffer is not long enough */
    if (!dwRet || ((long)dwSize < (long)dwRet + 14))
        lpszBuf[0] = TEXT('\0');
    else
    {
        lpszTemp[lstrlen(lpszTemp)-2] = TEXT('\0');  /* remove cr and newline character */
        _stprintf(lpszBuf, TEXT("%s (0x%x)"), lpszTemp, GetLastError());
    }

    if (lpszTemp)
        LocalFree((HLOCAL)lpszTemp);

    return(lpszBuf);
}

BOOL CheckServiceArguments(int argc, char *argv[])
{
    int i;

    /* Lets process the arguments */
    for (i = 1; i < argc; i++)
    {
        if (stricmp("-install", argv[i]) == 0)
        {
            /* They want to install the service */
            CmdInstallService(argc, argv, FALSE);

            /* We don't carry on, after we have installed the service */
            return(FALSE);
        }
        else if (stricmp("-installa", argv[i]) == 0)
        {
            /* They want to install the service */
            CmdInstallService(argc, argv, TRUE);

            /* We don't carry on, after we have installed the service */
            return(FALSE);
        }
        else if (stricmp("-remove", argv[i]) == 0)
        {
            /* Here they want to remove it */
            CmdRemoveService();

            /* We don't carry on, after we have removed the service */
            return(FALSE);
        }
        else if (stricmp ("-runservice", argv[i]) == 0)
        {
            /* We can carry on, if we reached here */
            chdir(argv[i+1]);
            argv[i] = "";
            argv[i+1] = "";
            return(TRUE);
        }
    }
    bRunAsService = FALSE;
    return(TRUE);
}

BOOL SetupService(int argc, char *argv[], void *pHandle, LPTSTR pAppName, LPTSTR pServiceName, LPTSTR pServiceDisplayName, LPTSTR pDependancies)
{
    BOOL bDeleteService = TRUE;
    BOOL bResult = FALSE;

    /* Save the handle for later use */
    pAppHandle = pHandle;

    /* Create our service class */
    Service_Create(pAppName, pServiceName, pServiceDisplayName, pDependancies, argc, argv);

    if (CheckServiceArguments(argc, argv))
    {
        if (bRunAsService)
        {
            /* No need to set the console control handler, as the service manager handles all this for us */
            Service_Initialize();
            bDeleteService = FALSE;
        }
        else
        {
            /* Set the console control handler for exiting the program */
            SetConsoleCtrlHandler((PHANDLER_ROUTINE)EventHandlerRoutine, TRUE);

            /* Now do the main work */
            ServiceMain(argc, argv);
        }

        /* We have been successful initializing, so let the caller know */
        bResult = TRUE;
    }

    if (bDeleteService)
    {
        /* Finished with the service now */
        Service_Delete();
    }
    return(bResult);
}

BOOL EventHandlerRoutine(DWORD dwCtrlType)
{
    /* This routine dosn't seem to get called all the time, Why ??? */
    switch (dwCtrlType)
    {
        case CTRL_C_EVENT:        /* A CTRL+C signal was received, either from keyboard input or from a signal generated by the GenerateConsoleCtrlEvent function.*/
        case CTRL_BREAK_EVENT:    /* A CTRL+BREAK signal was received, either from keyboard input or from a signal generated by GenerateConsoleCtrlEvent.*/
        case CTRL_CLOSE_EVENT:    /* A signal that the system sends to all processes attached to a console when the user closes the console (either by choosing the Close command from the console window's System menu, or by choosing the End Task command from the Task List).*/
        case CTRL_LOGOFF_EVENT:   /* A signal that the system sends to all console processes when a user is logging off. This signal does not indicate which user is logging off, so no assumptions can be made.*/
        case CTRL_SHUTDOWN_EVENT: /* A signal that the system sends to all console processes when the system */
            /* We are basically shutting down, so call Service_Delete */
            Service_Delete();
            return(FALSE);
            break;

        default:
            /* we are not handling this one, so return FALSE */
            return(FALSE);
    }
}
#endif
