diff --git a/auto_update/auto_update_service.c b/auto_update/auto_update_service.c new file mode 100644 index 00000000..10794b07 --- /dev/null +++ b/auto_update/auto_update_service.c @@ -0,0 +1,103 @@ +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_NONSTDC_NO_WARNINGS +#include "service.h" +#include "ipc.h" +#include +#include +#include +#include + +#pragma comment(lib, "Shell32.lib") + +#define SERVICE_NAME "symphony_sda_auto_update_service" +#define PIPE_NAME "symphony_sda_auto_update_ipc" + +// Runs msiexec with the supplied filename +// TODO: logging/error handling +bool run_installer( char const* filename ) { + char command[ 512 ]; + sprintf( command, "/i %s /q", filename ); + + SHELLEXECUTEINFO ShExecInfo = { 0 }; + ShExecInfo.cbSize = sizeof( SHELLEXECUTEINFO ); + ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS; + ShExecInfo.hwnd = NULL; + ShExecInfo.lpVerb = "open"; + ShExecInfo.lpFile = "msiexec"; + ShExecInfo.lpParameters = command; + ShExecInfo.lpDirectory = NULL; + ShExecInfo.nShow = SW_SHOW; + ShExecInfo.hInstApp = NULL; + ShellExecuteEx( &ShExecInfo ); + WaitForSingleObject( ShExecInfo.hProcess, INFINITE ); + CloseHandle( ShExecInfo.hProcess ); + + return true; +} + + +// Called by IPC server when a request comes in. Installs the package and returns a resul +// Also detects disconnects +void ipc_handler( char const* request, void* user_data, char* response, size_t capacity ) { + bool* is_connected = (bool*) user_data; + if( !request ) { + *is_connected = false; + service_cancel_sleep(); + return; + } + + if( run_installer( request ) ) { + strcpy( response, "OK" ); + } else { + strcpy( response, "ERROR" ); + } + +} + + +// Service main function. Keeps an IPC server running - if it gets disconnected it starts it +// up again. Install requests are handled by ipc_handler above +void service_main( void ) { + while( service_is_running() ) { + bool is_connected = true; + ipc_server_t* server = ipc_server_start( PIPE_NAME, ipc_handler, &is_connected ); + while( is_connected && service_is_running() ) { + service_sleep(); + } + ipc_server_stop( server ); + } +} + + +int main( int argc, char** argv ) { + // Debug helpers for install/uninstall + if( argc >= 2 && stricmp( argv[ 1 ], "install" ) == 0 ) { + if( service_install( SERVICE_NAME ) ) { + printf("Service installed successfully\n"); + return EXIT_SUCCESS; + } else { + printf("Service failed to install\n"); + return EXIT_FAILURE; + } + } + if( argc >= 2 && stricmp( argv[ 1 ], "uninstall" ) == 0 ) { + if( service_uninstall( SERVICE_NAME ) ) { + printf("Service uninstalled successfully\n"); + return EXIT_SUCCESS; + } else { + printf("Service failed to uninstall\n"); + return EXIT_FAILURE; + } + } + + // Run the service - called by the Windows Services system + service_run( SERVICE_NAME, service_main ); + return EXIT_SUCCESS; +} + + +#define SERVICE_IMPLEMENTATION +#include "service.h" + +#define IPC_IMPLEMENTATION +#include "ipc.h" \ No newline at end of file diff --git a/auto_update/auto_update_service.cpp b/auto_update/auto_update_service.cpp deleted file mode 100644 index 17ff0892..00000000 --- a/auto_update/auto_update_service.cpp +++ /dev/null @@ -1,332 +0,0 @@ -#define _CRT_SECURE_NO_WARNINGS -#include -#include -#include -#include -#include - -#include "ipc.h" - -#pragma comment(lib, "advapi32.lib") -#pragma comment(lib, "Shell32.lib") - -#define PIPE_NAME "symphony_sda_auto_update_ipc" - -#define SVCNAME TEXT("SDA Auto Update") - -SERVICE_STATUS gSvcStatus; -SERVICE_STATUS_HANDLE gSvcStatusHandle; -HANDLE ghSvcStopEvent = NULL; - -VOID SvcInstall(void); -VOID WINAPI SvcCtrlHandler( DWORD ); -VOID WINAPI SvcMain( DWORD, LPTSTR * ); - -VOID ReportSvcStatus( DWORD, DWORD, DWORD ); -VOID SvcInit( DWORD, LPTSTR * ); - - -// -// Purpose: -// Entry point for the process -// -// Parameters: -// None -// -// Return value: -// None, defaults to 0 (zero) -// -int __cdecl _tmain(int argc, TCHAR *argv[]) -{ - // If command-line parameter is "install", install the service. - // Otherwise, the service is probably being started by the SCM. - - if( lstrcmpi( argv[1], TEXT("install")) == 0 ) - { - SvcInstall(); - return 0; - } - - // TO_DO: Add any additional services for the process to this table. - SERVICE_TABLE_ENTRY DispatchTable[] = - { - { (LPSTR)SVCNAME, (LPSERVICE_MAIN_FUNCTION) SvcMain }, - { NULL, NULL } - }; - - // This call returns when the service has stopped. - // The process should simply terminate when the call returns. - - if (!StartServiceCtrlDispatcher( DispatchTable )) - { - //SvcReportEvent(TEXT("StartServiceCtrlDispatcher")); - } - return 0; -} - -// -// Purpose: -// Installs a service in the SCM database -// -// Parameters: -// None -// -// Return value: -// None -// -VOID SvcInstall() -{ - SC_HANDLE schSCManager; - SC_HANDLE schService; - TCHAR szPath[MAX_PATH]; - - if( !GetModuleFileName( NULL, szPath, MAX_PATH ) ) - { - printf("Cannot install service (%d)\n", GetLastError()); - return; - } - - // 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) - { - printf("OpenSCManager failed (%d)\n", GetLastError()); - return; - } - - // Create the service - - schService = CreateService( - schSCManager, // SCM database - SVCNAME, // name of service - SVCNAME, // service name to display - SERVICE_ALL_ACCESS, // desired access - SERVICE_WIN32_OWN_PROCESS, // service type - SERVICE_DEMAND_START, // start type - SERVICE_ERROR_NORMAL, // error control type - szPath, // 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) - { - printf("CreateService failed (%d)\n", GetLastError()); - CloseServiceHandle(schSCManager); - return; - } - else printf("Service installed successfully\n"); - - CloseServiceHandle(schService); - CloseServiceHandle(schSCManager); -} - -// -// Purpose: -// Entry point for the service -// -// Parameters: -// dwArgc - Number of arguments in the lpszArgv array -// lpszArgv - Array of strings. The first string is the name of -// the service and subsequent strings are passed by the process -// that called the StartService function to start the service. -// -// Return value: -// None. -// -VOID WINAPI SvcMain( DWORD dwArgc, LPTSTR *lpszArgv ) -{ - // Register the handler function for the service - - gSvcStatusHandle = RegisterServiceCtrlHandler( - SVCNAME, - SvcCtrlHandler); - - if( !gSvcStatusHandle ) - { -// SvcReportEvent(TEXT("RegisterServiceCtrlHandler")); - return; - } - - // These SERVICE_STATUS members remain as set here - - gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; - gSvcStatus.dwServiceSpecificExitCode = 0; - - // Report initial status to the SCM - - ReportSvcStatus( SERVICE_START_PENDING, NO_ERROR, 3000 ); - - // Perform service-specific initialization and work. - - SvcInit( dwArgc, lpszArgv ); -} - - -// This routine is a simple function to print the client request to the console -// and populate the reply buffer with a default data string. This is where you -// would put the actual client request processing code that runs in the context -// of an instance thread. Keep in mind the main thread will continue to wait for -// and receive other client connections while the instance thread is working. -void run_installer( char const* request, void* user_data, char* response, size_t capacity ) { - printf( "Client Request String:\"%s\"\n", request ); - - char command[ 512 ]; - sprintf( command, "/i %s /q", request ); - - SHELLEXECUTEINFO ShExecInfo = { 0 }; - ShExecInfo.cbSize = sizeof( SHELLEXECUTEINFO ); - ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS; - ShExecInfo.hwnd = NULL; - ShExecInfo.lpVerb = "open"; - ShExecInfo.lpFile = "msiexec"; - ShExecInfo.lpParameters = command; - ShExecInfo.lpDirectory = NULL; - ShExecInfo.nShow = SW_SHOW; - ShExecInfo.hInstApp = NULL; - ShellExecuteEx( &ShExecInfo ); - WaitForSingleObject( ShExecInfo.hProcess, INFINITE ); - CloseHandle( ShExecInfo.hProcess ); - - strcpy( response, "OK" ); -} - - -// -// Purpose: -// The service code -// -// Parameters: -// dwArgc - Number of arguments in the lpszArgv array -// lpszArgv - Array of strings. The first string is the name of -// the service and subsequent strings are passed by the process -// that called the StartService function to start the service. -// -// Return value: -// None -// -VOID SvcInit( DWORD dwArgc, LPTSTR *lpszArgv) -{ - ipc_server_t* server = ipc_server_start( PIPE_NAME, run_installer, NULL ); - - // TO_DO: Declare and set any required variables. - // Be sure to periodically call ReportSvcStatus() with - // SERVICE_START_PENDING. If initialization fails, call - // ReportSvcStatus with SERVICE_STOPPED. - - // Create an event. The control handler function, SvcCtrlHandler, - // signals this event when it receives the stop control code. - - ghSvcStopEvent = CreateEvent( - NULL, // default security attributes - TRUE, // manual reset event - FALSE, // not signaled - NULL); // no name - - if ( ghSvcStopEvent == NULL) - { - ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 ); - return; - } - - // Report running status when initialization is complete. - - ReportSvcStatus( SERVICE_RUNNING, NO_ERROR, 0 ); - - // TO_DO: Perform work until service stops. - - while(1) - { - // Check whether to stop the service. - - WaitForSingleObject(ghSvcStopEvent, INFINITE); - - ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 ); - return; - } -} - -// -// Purpose: -// Sets the current service status and reports it to the SCM. -// -// Parameters: -// dwCurrentState - The current state (see SERVICE_STATUS) -// dwWin32ExitCode - The system error code -// dwWaitHint - Estimated time for pending operation, -// in milliseconds -// -// Return value: -// None -// -VOID ReportSvcStatus( DWORD dwCurrentState, - DWORD dwWin32ExitCode, - DWORD dwWaitHint) -{ - static DWORD dwCheckPoint = 1; - - // Fill in the SERVICE_STATUS structure. - - gSvcStatus.dwCurrentState = dwCurrentState; - gSvcStatus.dwWin32ExitCode = dwWin32ExitCode; - gSvcStatus.dwWaitHint = dwWaitHint; - - if (dwCurrentState == SERVICE_START_PENDING) - gSvcStatus.dwControlsAccepted = 0; - else gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; - - if ( (dwCurrentState == SERVICE_RUNNING) || - (dwCurrentState == SERVICE_STOPPED) ) - gSvcStatus.dwCheckPoint = 0; - else gSvcStatus.dwCheckPoint = dwCheckPoint++; - - // Report the status of the service to the SCM. - SetServiceStatus( gSvcStatusHandle, &gSvcStatus ); -} - -// -// Purpose: -// Called by SCM whenever a control code is sent to the service -// using the ControlService function. -// -// Parameters: -// dwCtrl - control code -// -// Return value: -// None -// -VOID WINAPI SvcCtrlHandler( DWORD dwCtrl ) -{ - // Handle the requested control code. - - switch(dwCtrl) - { - case SERVICE_CONTROL_STOP: - ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0); - - // Signal the service to stop. - - SetEvent(ghSvcStopEvent); - ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0); - - return; - - case SERVICE_CONTROL_INTERROGATE: - break; - - default: - break; - } - -} - -#define IPC_IMPLEMENTATION -#include "ipc.h" - diff --git a/auto_update/auto_update_service.vcxproj b/auto_update/auto_update_service.vcxproj index a1060d6f..7698937e 100644 --- a/auto_update/auto_update_service.vcxproj +++ b/auto_update/auto_update_service.vcxproj @@ -134,10 +134,11 @@ - + + diff --git a/auto_update/ipc.h b/auto_update/ipc.h index caa63b21..277ddd83 100644 --- a/auto_update/ipc.h +++ b/auto_update/ipc.h @@ -1,6 +1,9 @@ #ifndef ipc_h #define ipc_h +#include +#include + #define IPC_MESSAGE_MAX_LENGTH 512 // client @@ -345,7 +348,7 @@ DWORD WINAPI ipc_client_thread( LPVOID param ) { // TODO: consider if there are better ways to deal with the error. There might not be, // but then the user-code calling client send/receive might need some robust retry code if( !success || bytes_read == 0 ) { - if (GetLastError() == ERROR_BROKEN_PIPE) { + if( GetLastError() == ERROR_BROKEN_PIPE ) { //IPC_LOG( "ipc_client_thread: client disconnected.\n" ); } else { IPC_LOG( "ipc_client_thread ReadFile failed, LastError=%d.\n", GetLastError() ); @@ -391,6 +394,8 @@ DWORD WINAPI ipc_client_thread( LPVOID param ) { } } + // Signal that a disconnect has happened + context->request_handler( NULL, context->user_data, NULL, 0 ); // Flush the pipe to allow the client to read the pipe's contents // before disconnecting. Then disconnect the pipe, and close the diff --git a/auto_update/package.json b/auto_update/package.json index 092b7c6b..a8adbbe4 100644 --- a/auto_update/package.json +++ b/auto_update/package.json @@ -5,6 +5,6 @@ "scripts": { "test": "cl tests.cpp /O2 /MTd /D_DEBUG /D_CRTDBG_MAP_ALLOC /nologo /link /SUBSYSTEM:CONSOLE && tests.exe", "preinstall": "npm run test && npm run build", - "build": "cl auto_update_helper.cpp /O2 /MT /nologo /link /SUBSYSTEM:CONSOLE && cl auto_update_service.cpp /O2 /MT /nologo /link /SUBSYSTEM:CONSOLE" + "build": "cl auto_update_helper.cpp /O2 /MT /nologo /link /SUBSYSTEM:CONSOLE && cl auto_update_service.c /O2 /MT /nologo /link /SUBSYSTEM:CONSOLE" } } diff --git a/auto_update/service.h b/auto_update/service.h new file mode 100644 index 00000000..3d0ff485 --- /dev/null +++ b/auto_update/service.h @@ -0,0 +1,342 @@ +#ifndef service_h +#define service_h + +#include + +bool service_install( char const* service_name ); +bool service_uninstall( char const* service_name ); + +typedef void (*service_main_func_t)( void ); + +void service_run( char const* service_name, service_main_func_t main_func ); + +bool service_is_running( void ); + +void service_sleep( void ); +void service_cancel_sleep( void ); + +#endif /* service_h */ + + +#ifdef SERVICE_IMPLEMENTATION +#undef SERVICE_IMPLEMENTATION + +#include +#include + +// Temporary logging for debugging, will be replaced by something better +static struct { + FILE* fp; + CRITICAL_SECTION mutex; +} g_log = { NULL }; + +void logf( char const* format, ... ) { + if( !g_log.fp ) { + char path[ MAX_PATH ]; + ExpandEnvironmentStringsA( "%LOCALAPPDATA%\\SdaAutoUpdate", path, MAX_PATH ); + CreateDirectory( path, NULL ); + strcat( path, "\\log.txt" ); + g_log.fp = fopen( path, "w" ); + InitializeCriticalSection( &g_log.mutex ); + } + EnterCriticalSection( &g_log.mutex ); + va_list args; + va_start( args, format ); + vfprintf( g_log.fp, format, args ); + vprintf( format, args ); + va_end( args ); + fflush( g_log.fp ); + LeaveCriticalSection( &g_log.mutex ); +} + + +// Get an errror description for the last error from Windows, for logging purposes +// TODO: thread safe return value +char const* error_message( void ) { + DWORD error = GetLastError(); + if( error == 0 ) { + return ""; + } + + LPSTR buffer = NULL; + size_t size = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), (LPSTR)&buffer, 0, NULL); + + static char message[ 1024 ] = ""; + if( buffer ) { + strncpy( message, buffer, sizeof( message ) ); + message[ sizeof( message ) - 1 ] = '\0'; + LocalFree(buffer); + } + + return message; +} + + +// Helper function to install the service, used for debugging (for production, service is set up by installer). +bool service_install( char const* service_name ) { + // Get the path to the running executable + TCHAR path[ MAX_PATH ]; + if( !GetModuleFileName( NULL, path, MAX_PATH ) ) { + printf("Cannot install service: LastError = %d %s\n", GetLastError(), error_message() ); + return false; + } + + // Get a handle to the SCM database. + SC_HANDLE scm = OpenSCManager( + NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_ALL_ACCESS ); // full access rights + if( !scm ) { + printf( "OpenSCManager failed: LastError = %d %s\n", GetLastError(), error_message() ); + return false; + } + + // Create the service + SC_HANDLE service = CreateService( + scm, // SCM database + service_name, // name of service + service_name, // service name to display + SERVICE_ALL_ACCESS, // desired access + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_DEMAND_START, // start type + SERVICE_ERROR_NORMAL, // error control type + path, // path to service's binary + NULL, // no load ordering group + NULL, // no tag identifier + NULL, // no dependencies + NULL, // LocalSystem account + NULL); // no password + if( !service ) { + printf( "CreateService failed: LastError = %d %s\n", GetLastError(), error_message() ); + CloseServiceHandle( scm ); + return false; + } + + CloseServiceHandle( service ); + CloseServiceHandle( scm ); + return true; +} + + +// Helper function to uninstall the service, used for debugging (for production, service is set up by installer). +bool service_uninstall( char const* service_name ) { + // Get a handle to the SCM database. + SC_HANDLE scm = OpenSCManager( + NULL, // local computer + NULL, // ServicesActive database + SC_MANAGER_ALL_ACCESS ); // full access rights + if( !scm ) { + printf( "OpenSCManager failed: LastError = %d %s\n", GetLastError(), error_message() ); + return false; + } + + // Get a handle to the service. + SC_HANDLE service = OpenService( + scm, // SCM database + service_name, // name of service + DELETE); // need delete access + if( !service ) { + printf( "OpenService failed: LastError = %d %s\n", GetLastError(), error_message() ); + CloseServiceHandle( scm ); + return false; + } + + // Delete the service. + if( !DeleteService( service ) ) { + printf( "DeleteService failed: LastError = %d %s\n", GetLastError(), error_message() ); + CloseServiceHandle( service ); + CloseServiceHandle( scm ); + return false; + } + + CloseServiceHandle( service ); + CloseServiceHandle( scm ); + return true; +} + + +// Global state for the service +struct { + char const* service_name; + service_main_func_t main_func; + bool is_running; + HANDLE stop_event; + SERVICE_STATUS service_status; + SERVICE_STATUS_HANDLE status_handle; + HANDLE sleep_event; +} g_service = { FALSE }; + + +// Thread proc for service. Just calls the user-provided main func +DWORD WINAPI service_main_thread( LPVOID param ) { + g_service.main_func(); + return EXIT_SUCCESS; +} + + +// Helper function to report the status of the service. The service needs to +// notify the windows service manager of its status correctly, otherwise it +// will be considerered unresponsive and closed +VOID report_service_status( DWORD current_state, DWORD exit_code, DWORD wait_hint ) { + static DWORD check_point = 1; + + g_service.service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + g_service.service_status.dwServiceSpecificExitCode = 0; + g_service.service_status.dwCurrentState = current_state; + g_service.service_status.dwWin32ExitCode = exit_code; + g_service.service_status.dwWaitHint = wait_hint; + + if( current_state == SERVICE_START_PENDING ) { + g_service.service_status.dwControlsAccepted = 0; + } else { + g_service.service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP; + } + + if( current_state == SERVICE_RUNNING || current_state == SERVICE_STOPPED ) { + g_service.service_status.dwCheckPoint = 0; + } else { + g_service.service_status.dwCheckPoint = check_point++; + } + + // Report the status of the service to the SCM. + SetServiceStatus( g_service.status_handle, &g_service.service_status ); +} + + +// Service control handler, used to signal the service to stop (if the user stops it +// in the Services control panel) +VOID WINAPI service_ctrl_handler( DWORD ctrl ) { + switch( ctrl ) { + case SERVICE_CONTROL_STOP: { + report_service_status( SERVICE_STOP_PENDING, NO_ERROR, 0 ); + + // Signal the service to stop. + SetEvent( g_service.stop_event ); + + report_service_status(g_service.service_status.dwCurrentState, NO_ERROR, 0); + return; + } break; + + case SERVICE_CONTROL_INTERROGATE: { + report_service_status( g_service.service_status.dwCurrentState, NO_ERROR, 0 ); + } break; + + default: + break; + } +} + + +// TODO: better logging once we have a final solution for logging (another jira ticket) + +// Main function of the service. Starts a thread to run the user-provided service function +VOID WINAPI service_proc( DWORD argc, LPSTR *argv ) { + logf( "Service proc\n" ); + + // Register the handler function for the service + g_service.status_handle = RegisterServiceCtrlHandler( g_service.service_name, service_ctrl_handler ); + if( !g_service.status_handle ) { + logf( "Failed to register the service control handler\n" ); + return; + } + + // Report initial status to the SCM + report_service_status( SERVICE_START_PENDING, NO_ERROR, 3000 ); + + // Create the event used to signal the service to stop + g_service.stop_event = CreateEvent( + NULL, // default security attributes + TRUE, // manual reset event + FALSE, // not signaled + NULL); // no name + if ( g_service.stop_event == NULL ) { + logf( "No event\n" ); + report_service_status( SERVICE_STOPPED, NO_ERROR, 0 ); + return; + } + + // Start the thread which runs the user-provided main function + HANDLE thread = CreateThread( + NULL, // no security attribute + 0, // default stack size + service_main_thread, // thread proc + NULL, // thread parameter + 0, // not suspended + NULL ); // returns thread ID + + // Report to the SCM that the service is now up and running + report_service_status( SERVICE_RUNNING, NO_ERROR, 0 ); + logf( "Service started\n" ); + + // Wait until the service is stopped + WaitForSingleObject( g_service.stop_event, INFINITE ); + + // Flag the user-provided main func to exit + g_service.is_running = false; + SetEvent( g_service.sleep_event ); + + // Wait until user-provided main func has exited + WaitForSingleObject( thread, INFINITE ); + + // Report to the SCM that the service has stopped + report_service_status( SERVICE_STOPPED, NO_ERROR, 0 ); + + logf( "Exit service proc\n" ); +} + + +// Run the service with the specified name, and invoke the main_func function +// The main_func provided should exit if service_is_running returns false +void service_run( char const* service_name, service_main_func_t main_func ) { + SetLastError( 0 ); + logf( "Starting service\n" ); + + // Initialize global state + g_service.service_name = service_name; + g_service.main_func = main_func; + g_service.is_running = true; + g_service.sleep_event = CreateEvent( + NULL, // default security attributes + TRUE, // manual reset event + FALSE, // not signaled + NULL); // no name + + // Launch service + SERVICE_TABLE_ENTRY dispatch_table[] = { + { (LPSTR) service_name, (LPSERVICE_MAIN_FUNCTION) service_proc }, + { NULL, NULL } + }; + if( !StartServiceCtrlDispatcher( dispatch_table ) ) { + logf( "Error: LastError = %d %s\n", GetLastError(), error_message() ); + } + + // Cleanup + CloseHandle( g_service.sleep_event ); + logf( "Service stopped\n" ); +} + + +// Returns true while service is running, false if it has been asked to stop +bool service_is_running() { + return g_service.is_running; +} + + +// Sleeps until service_cancel_sleep is called, either manually by the user or +// because the serice received a stop request +void service_sleep( void ) { + ResetEvent( g_service.sleep_event ); + WaitForSingleObject( g_service.sleep_event, INFINITE ); +} + + +// Can be used to cancel a pending service sleep +void service_cancel_sleep( void ) { + SetEvent( g_service.sleep_event ); +} + + + +#endif /* SERVICE_IMPLEMENTATION */ \ No newline at end of file