/* Файл содержит функции для работы ltrd как сервиса под Windows */
#include "ltrsrv_daemon.h"

#include "config.h"
#include "ltrsrv_wait.h"
#include "ltrsrv_log.h"
#include "ltrsrv_defs.h"
#include "ltrsrv_opt_parse.h"
#include "ltimer.h"
#include <tchar.h>

#include <stdio.h>


#define LTRD_SERVICE_NAME  (TEXT(LTRD_PROGRAMM_NAME))
#define LTRD_SERVICE_DESCR (TEXT("Service for control ") TEXT(LTRD_CRATE_NAME) TEXT(" Crates"))
#define LTRD_EVENTLOG_KEY  (TEXT("SYSTEM\\CurrentControlSet\\services\\eventlog\\application\\") TEXT(LTRD_PROGRAMM_NAME))

static SERVICE_STATUS f_svc_status;
SERVICE_STATUS_HANDLE service_hnd;

#ifdef LTRD_USE_WIN_DEVICEEVENT
    extern DWORD ltrd_proc_devevent(DWORD dwEventType, LPVOID lpEventData);
#endif



static void f_svc_report_status(SERVICE_STATUS_HANDLE hnd, DWORD dwCurrentState,
                                DWORD dwWin32ExitCode, DWORD dwWaitHint) {
    static DWORD dwCheckPoint = 1;

    // Fill in the SERVICE_STATUS structure.

    f_svc_status.dwCurrentState = dwCurrentState;
    f_svc_status.dwWin32ExitCode = dwWin32ExitCode;
    f_svc_status.dwWaitHint = dwWaitHint;

    if ( (dwCurrentState == SERVICE_RUNNING) ||
           (dwCurrentState == SERVICE_STOPPED) ) {
        f_svc_status.dwCheckPoint = 0;
    } else {
        f_svc_status.dwCheckPoint = dwCheckPoint++;
    }

    // Report the status of the service to the SCM.
    SetServiceStatus( hnd, &f_svc_status );
}



static DWORD WINAPI f_svc_handler(DWORD dwControl,
                                  DWORD dwEventType,
                                  LPVOID lpEventData,
                                  LPVOID lpContext
                                  ) {
    DWORD ret_code = ERROR_CALL_NOT_IMPLEMENTED;


    if ((dwControl == SERVICE_CONTROL_STOP) || (dwControl == SERVICE_CONTROL_SHUTDOWN)) {
        f_svc_report_status(service_hnd, SERVICE_STOP_PENDING, NO_ERROR, 0);
        ltrsrv_req_server_close();
        ret_code = ERROR_SUCCESS;
    } else if (dwControl == SERVICE_CONTROL_INTERROGATE) {
        ret_code = ERROR_SUCCESS;
#ifdef LTRD_USE_WIN_DEVICEEVENT
    } else if (dwControl == SERVICE_CONTROL_DEVICEEVENT) {
        ret_code = ltrd_proc_devevent(dwEventType, lpEventData);
#endif
    }

    return ret_code;
}







static void f_svc_main(DWORD argc, LPTSTR *argv) {
    service_hnd = RegisterServiceCtrlHandlerEx(
                   LTRD_SERVICE_NAME, f_svc_handler, 0);

    if (service_hnd) {
        f_svc_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
        f_svc_status.dwServiceSpecificExitCode = 0;
        f_svc_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;

        f_svc_report_status(service_hnd, SERVICE_START_PENDING, NO_ERROR, 0);

        ltrsrv_log_init();
        f_svc_report_status(service_hnd, SERVICE_RUNNING, NO_ERROR, 0);
    }

    daemon_main();
}

int ltrsrv_daemon_start(void) {
    int err = 0;
    SERVICE_TABLE_ENTRY srv_tbl[] =
    {
        { LTRD_SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) f_svc_main },
        { NULL, NULL }
    };

    if (!StartServiceCtrlDispatcher( &srv_tbl[0] ))
    {
        err = -2;
    }
    return err;
}

void ltrsrv_daemon_close(void)
{
    f_svc_report_status(service_hnd, SERVICE_STOPPED, NO_ERROR, 0);
}



/************** Выполнение спец. сервисных операций ***************************/


static int f_register_log_event(const TCHAR* filename)
{
    HKEY hkey;
    LONG err = RegCreateKeyEx(HKEY_LOCAL_MACHINE,
                              LTRD_EVENTLOG_KEY,
                              0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
                              NULL, &hkey, NULL);
    if (err!=ERROR_SUCCESS)
    {
        fprintf(stderr, "Cannot create " LTRD_PROGRAMM_NAME " logevent key!\n");
    }

    if (err==ERROR_SUCCESS)
    {
        err = RegSetValueEx(hkey, TEXT("EventMessageFile"), 0, REG_SZ, (const BYTE*)filename,
                             (_tcslen(filename)+1)*sizeof(filename[0]));
        if (err!=ERROR_SUCCESS)
        {
            fprintf(stderr, "Cannot create EventMessageFile parameter!\n");
        }
    }

    if (err==ERROR_SUCCESS)
    {
        DWORD types = 0x7;
        err = RegSetValueEx(hkey, TEXT("TypesSupported"), REG_DWORD, 0,
                             (const BYTE*)&types, sizeof(types));
        if (err!=ERROR_SUCCESS)
        {
            fprintf(stderr, "Cannot create TypesSupported parameter!\n");
        }
    }

    return err;

}

static int f_svc_install(void)
{
    SC_HANDLE schSCManager = NULL;
    SC_HANDLE schService = NULL;
    int last_pos=0, pos;
    static TCHAR fileName[MAX_PATH];
    static TCHAR dirName[MAX_PATH];
    static TCHAR imageName[4*MAX_PATH];

    int err = 0;

    /* получаем полное имя исполняемого файла (с путем) */
    if( !GetModuleFileName(NULL, fileName, MAX_PATH ) )
    {
        fprintf(stderr, "Cannot install service (%d)\n", GetLastError());
        err = -1;
    }

    /* получаем только путь, удаляя имя файла */
    if (!err)
    {
        for (pos = 0; (pos < MAX_PATH) && (fileName[pos]!='\0'); pos++)
        {
            if (fileName[pos]=='\\')
                last_pos = pos;
            dirName[pos]=fileName[pos];
        }
        dirName[last_pos] = 0;

        /* полная строка запуска сервиса, включая все аргументы */
        _stprintf(imageName, TEXT("%s --daemon --config-file=\"%s\\config.xml\""),
                  fileName, dirName);
    }

    if (!err)
    {
        schSCManager = OpenSCManager(
                NULL,                    // local computer
                NULL,                    // ServicesActive database
                SC_MANAGER_ALL_ACCESS);  // full access rights

        if (NULL == schSCManager)
        {
            fprintf(stderr, "OpenSCManager failed (%d)\n", GetLastError());
            err = -2;
        }
    }

    if (!err)
    {
        // Create the service
        schService = CreateService(
            schSCManager,              // SCM database
            LTRD_SERVICE_NAME,         // name of service
            LTRD_SERVICE_NAME,         // service name to display
            SERVICE_ALL_ACCESS,        // desired access
            SERVICE_WIN32_OWN_PROCESS, // service type
            SERVICE_AUTO_START,        // start type
            SERVICE_ERROR_NORMAL,      // error control type
            imageName,                 // path to service's binary
            NULL,                      // no load ordering group
            NULL,                      // no tag identifier
            NULL,                      // no dependencies
            NULL,                      // LocalSystem account
            NULL);                     // no password

        if (schService == NULL)
        {
            fprintf(stderr, "CreateService failed (%d)\n", GetLastError());
            err = -3;
        }
    }

    if (!err)
    {
        SERVICE_DESCRIPTION descr;
        descr.lpDescription = LTRD_SERVICE_DESCR;
        if (!ChangeServiceConfig2(schService,
                                  SERVICE_CONFIG_DESCRIPTION,
                                  &descr))
        {
            fprintf(stderr, "Set service description failed (%d)\n", GetLastError());
            err = -3;
        }
    }

    if (!err)
    {
        err = f_register_log_event(fileName);
    }

    if (!err)
    {
        printf("Service installed successfully\n");
    }

    CloseServiceHandle(schService);
    CloseServiceHandle(schSCManager);


    return 0;
}

static int f_svc_remove(void)
{
    SC_HANDLE schSCManager=NULL;
    SC_HANDLE schService=NULL;
    int err = 0;

    schSCManager = OpenSCManager(
            NULL,                    // local computer
            NULL,                    // ServicesActive database
            SC_MANAGER_ALL_ACCESS);  // full access rights

    if (NULL == schSCManager)
    {
        fprintf(stderr, "OpenSCManager failed (%d)\n", GetLastError());
        err = -2;
    }

    if (!err)
    {
        schService = OpenService(schSCManager,
                                 LTRD_SERVICE_NAME,
                                 SERVICE_ALL_ACCESS);

        if (schService == NULL)
        {
            if (GetLastError()==ERROR_SERVICE_DOES_NOT_EXIST)
            {
                printf("Service already removed!\n");
            }
            else
            {
                fprintf(stderr, "OpenService failed (%d)\n", GetLastError());
                err = -3;
            }
        }
        else
        {
            if (!DeleteService(schService))
            {
                DWORD delErr = GetLastError();
                if (delErr==ERROR_SERVICE_MARKED_FOR_DELETE)
                {
                    printf("Service already mark to remove!\n");
                }
                else
                {
                    fprintf(stderr, "DeleteService failed (%d)\n", delErr);
                    err = -4;
                }
            }
        }
    }

    if (!err)
    {
        LONG reg_err = RegDeleteKey(HKEY_LOCAL_MACHINE, LTRD_EVENTLOG_KEY);
        if (reg_err == ERROR_FILE_NOT_FOUND)
        {
            printf("Event key already removed!\n");
        }
        else if (reg_err)
        {
            err = -5;
            fprintf(stderr, "Cannot remove " LTRD_PROGRAMM_NAME " eventlog key (%d)\n", reg_err);
        }
    }


    if (!err)
    {
        printf("Service removed successfully\n");
    }

    CloseServiceHandle(schService);
    CloseServiceHandle(schSCManager);
    return 0;
}



static int f_get_srv_status(SC_HANDLE schService, SERVICE_STATUS_PROCESS *ssp)
{
    DWORD dwBytesNeeded;
    if ( !QueryServiceStatusEx(
            schService,
            SC_STATUS_PROCESS_INFO,
            (LPBYTE)ssp,
            sizeof(SERVICE_STATUS_PROCESS),
             &dwBytesNeeded ))
    {
        printf( "QueryServiceStatusEx failed (%d)\n", GetLastError() );
        return -3;
    }
    return 0;
}


static DWORD f_get_sleep_time(SERVICE_STATUS_PROCESS *ssp)
{
    // Do not wait longer than the wait hint. A good interval is
    // one-tenth the wait hint, but no less than 1 second and no
    // more than 10 seconds.
    DWORD sleep_time = ssp->dwWaitHint / 10;
    if( sleep_time < 250 )
        sleep_time = 250;
    else if ( sleep_time > 10000 )
        sleep_time = 10000;
    return sleep_time;
}

static DWORD f_get_wait_time(SERVICE_STATUS_PROCESS *ssp)
{
    // Do not wait longer than the wait hint. A good interval is
    // one-tenth the wait hint, but no less than 1 second and no
    // more than 10 seconds.
    return ssp->dwWaitHint+1000;
}


static int f_svc_start(void) {
    SERVICE_STATUS_PROCESS ssp;
    DWORD sleep_time;
    DWORD wait_time;
    t_ltimer tmr;
    int err = 0;
    SC_HANDLE schSCManager=NULL;
    SC_HANDLE schService=NULL;

    // Get a handle to the SCM database.

    schSCManager = OpenSCManager(
        NULL,                    // local computer
        NULL,                    // servicesActive database
        SC_MANAGER_ALL_ACCESS);  // full access rights

    if (NULL == schSCManager) {
        fprintf(stderr, "OpenSCManager failed (%d)\n", GetLastError());
        err = -1;
    }

    if (!err) {
        // Get a handle to the service.
        schService = OpenService(
            schSCManager,         // SCM database
            LTRD_SERVICE_NAME,    // name of service
            SERVICE_ALL_ACCESS);  // full access

        if (schService == NULL) {
            fprintf(stderr, "OpenService failed (%d)\n", GetLastError());
            err = -2;
        }
    }

    if (!err) {
        err = f_get_srv_status(schService, &ssp);
    }

    if (!err) {
        // Check if the service is already running. It would be possible
        // to stop the service here, but for simplicity this example just returns.
        if(ssp.dwCurrentState != SERVICE_STOPPED && ssp.dwCurrentState != SERVICE_STOP_PENDING) {
            printf("Service already running\n");
        } else {
            sleep_time = f_get_sleep_time(&ssp);
            wait_time = f_get_wait_time(&ssp);

            // Save the tick count and initial checkpoint.
            ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(wait_time));

            // Wait for the service to stop before attempting to start it.
            while ((ssp.dwCurrentState == SERVICE_STOP_PENDING) && !ltimer_expired(&tmr) && !err) {
                Sleep(sleep_time);
                err = f_get_srv_status(schService, &ssp);
            }

            if (!err && (ssp.dwCurrentState == SERVICE_STOP_PENDING)) {
                fprintf(stderr, "Wait service stop timeout expired!\n");
                err = -4;
            }

            if (!err) {
                // Attempt to start the service.
                if (!StartService(schService, 0, NULL)) {
                    fprintf(stderr, "StartService failed (%d)\n", GetLastError());
                    err = -5;
                } else {
                    printf("Service start pending...\n");
                }
            }

            if (!err)
                err = f_get_srv_status(schService, &ssp);

            ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(wait_time));

            while ((ssp.dwCurrentState == SERVICE_START_PENDING) && !err
                                  && !ltimer_expired(&tmr)) {
                Sleep(sleep_time);
                err = f_get_srv_status(schService, &ssp);
            }

            if (!err) {
                if (ssp.dwCurrentState == SERVICE_RUNNING) {
                    printf("Service started successfully.\n");
                } else {
                    printf("Service not started. \n");
                    printf("  Current State: %d\n", ssp.dwCurrentState);
                    printf("  Exit Code: %d\n", ssp.dwWin32ExitCode);
                    printf("  Check Point: %d\n", ssp.dwCheckPoint);
                    printf("  Wait Hint: %d\n", ssp.dwWaitHint);
                    err = -6;
                }
            }
        }
    }

    CloseServiceHandle(schService);
    CloseServiceHandle(schSCManager);
    return err;
}



static int f_svc_stop(void) {
    int err = 0;
    SC_HANDLE schSCManager=NULL;
    SC_HANDLE schService=NULL;
    SERVICE_STATUS_PROCESS ssp;

    // Get a handle to the SCM database.

    schSCManager = OpenSCManager(
                NULL,                    // local computer
                NULL,                    // ServicesActive database
                SC_MANAGER_ALL_ACCESS);  // full access rights

    if (NULL == schSCManager) {
        fprintf(stderr, "OpenSCManager failed (%d)\n", GetLastError());
        err = -1;
    }

    if (!err) {
        // Get a handle to the service.

        schService = OpenService(
                    schSCManager,         // SCM database
                    LTRD_SERVICE_NAME,            // name of service
                    SERVICE_STOP |
                    SERVICE_QUERY_STATUS |
                    SERVICE_ENUMERATE_DEPENDENTS);

        if (schService == NULL) {
            fprintf(stderr, "OpenService failed (%d)\n", GetLastError());
            err = -2;
        }
    }

    if (!err) {
        // Make sure the service is not already stopped.
        err = f_get_srv_status(schService, &ssp);
    }

    if (!err) {
        if ( ssp.dwCurrentState == SERVICE_STOPPED ) {
            printf("Service is already stopped.\n");
        } else {
            // Send a stop code to the service.

            if ( !ControlService(schService,
                                 SERVICE_CONTROL_STOP,
                                 (LPSERVICE_STATUS) &ssp ) ) {
                printf( "ControlService failed (%d)\n", GetLastError() );
                err = -4;
            }

            if (!err) {
                printf("Service stop pending...\n");
                err = f_get_srv_status(schService, &ssp);
            }

            if (!err) {
                DWORD sleep_time;
                t_ltimer tmr;

                sleep_time = f_get_sleep_time(&ssp);
                ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(15000));

                // Wait for the service to stop.
                while ((ssp.dwCurrentState != SERVICE_STOPPED) && !err
                       && !ltimer_expired(&tmr)) {
                    Sleep(sleep_time);
                    err = f_get_srv_status(schService, &ssp);
                }

                if (!err && (ssp.dwCurrentState != SERVICE_STOPPED)) {
                    fprintf(stderr, "Wait service stop wait timeout!\n");
                    err = -6;
                }
            }

            if (!err) {
                printf("Service stopped successfully\n");
            }
        }
    }
    CloseServiceHandle(schService);
    CloseServiceHandle(schSCManager);


    return err;
}

static int f_svc_set_autostart(int en) {
    int err = 0;
    SC_HANDLE schSCManager=NULL;
    SC_HANDLE schService=NULL;

    // Get a handle to the SCM database.

    schSCManager = OpenSCManager(
                NULL,                    // local computer
                NULL,                    // ServicesActive database
                SC_MANAGER_ALL_ACCESS);  // full access rights

    if (NULL == schSCManager) {
        fprintf(stderr, "OpenSCManager failed (%d)\n", GetLastError());
        err = -1;
    }

    if (!err) {
        // Get a handle to the service.

        schService = OpenService(
                    schSCManager,         // SCM database
                    LTRD_SERVICE_NAME,            // name of service
                    SERVICE_ALL_ACCESS);

        if (schService == NULL) {
            fprintf(stderr, "OpenService failed (%d)\n", GetLastError());
            err = -2;
        }
    }

    if (!err) {
        LPQUERY_SERVICE_CONFIG pConfig = NULL;
        DWORD cfg_size;
        if (!QueryServiceConfig(schService, NULL, 0, &cfg_size) &&
                (GetLastError()==ERROR_INSUFFICIENT_BUFFER)) {
            pConfig =(LPQUERY_SERVICE_CONFIG) malloc(cfg_size);
            if (pConfig==NULL) {
                err = LTRSRV_ERR_MEMALLOC;
            } else if (!QueryServiceConfig(schService, pConfig, cfg_size, &cfg_size)) {
                fprintf(stderr, "QueryServiceConfig failed (%d)\n", GetLastError());
                err = -4;
            }
        } else {
            fprintf(stderr, "QueryServiceConfig for config size failed (%d)\n", GetLastError());
            err = -3;
        }

        if (!err) {
            if ((pConfig->dwStartType==SERVICE_AUTO_START) && en) {
                printf("Autostart already enabled\n");
            } else if ((pConfig->dwStartType==SERVICE_DEMAND_START) && !en) {
                printf("Autostart already disabled\n");
            } else {
                pConfig->dwStartType = en ? SERVICE_AUTO_START : SERVICE_DEMAND_START;
                if (!ChangeServiceConfig(schService,
                                         pConfig->dwServiceType,
                                         pConfig->dwStartType,
                                         pConfig->dwErrorControl,
                                         pConfig->lpBinaryPathName,
                                         pConfig->lpLoadOrderGroup,
                                         0,
                                         pConfig->lpDependencies,
                                         pConfig->lpServiceStartName,
                                         0,
                                         pConfig->lpDisplayName
                                         )) {
                    fprintf(stderr, "ChangeServiceConfig failed (%d)\n", GetLastError());
                    err = -5;
                } else {
                    if (en) {
                        printf("Autostart enabled successfully\n");
                    } else {
                        printf("Autostart disabled successfully\n");
                    }
                }
            }
        }

        free(pConfig);
    }
    CloseServiceHandle(schService);
    CloseServiceHandle(schSCManager);

    return err;
}


int ltrsrv_daemon_svc_op(t_ltrd_svc_op op) {
    switch (op) {
        case LTRD_SVC_OP_INSTALL:
            return f_svc_install();
        case LTRD_SVC_OP_REMOVE:
            return f_svc_remove();
        case LTRD_SVC_OP_START:
            return f_svc_start();
        case LTRD_SVC_OP_STOP:
            return f_svc_stop();
        case LTRD_SVC_OP_AUTOSTART_EN:
            return f_svc_set_autostart(1);
        case LTRD_SVC_OP_AUTOSTART_DIS:
            return f_svc_set_autostart(0);
        default:
            return LTRSRV_ERR_INVALID_SVC_OP;
    }
}






