#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)( bool* is_running ); void service_run( char const* service_name, service_main_func_t main_func ); void service_sleep( void ); void service_cancel_sleep( void ); #endif /* service_h */ #ifdef SERVICE_IMPLEMENTATION #undef SERVICE_IMPLEMENTATION #include #include // 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 ) { SERVICE_LOG_INFO( "Starting service main thread" ); g_service.main_func( (bool*) param ); SERVICE_LOG_INFO( "Terminating service main thread" ); 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 ) { SERVICE_LOG_INFO( "Reporting service status, current_state=%u, exit_code=%u, wait_hint=%u", current_state, exit_code, 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_LOG_INFO( "Service statua reported" ); } // 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 ) { SERVICE_LOG_INFO( "Service control handler, ctrl=%u", ctrl ); switch( ctrl ) { case SERVICE_CONTROL_STOP: { SERVICE_LOG_INFO( "SERVICE_CONTROL_STOP" ); SERVICE_LOG_INFO( "Reporting service status SERVICE_STOP_PENDING" ); report_service_status( SERVICE_STOP_PENDING, NO_ERROR, 0 ); // Signal the service to stop. SERVICE_LOG_INFO( "Signaling service to stop" ); SetEvent( g_service.stop_event ); SERVICE_LOG_INFO( "Reporting service status %u", g_service.service_status.dwCurrentState ); report_service_status(g_service.service_status.dwCurrentState, NO_ERROR, 0); return; } break; case SERVICE_CONTROL_INTERROGATE: { SERVICE_LOG_INFO( "SERVICE_CONTROL_INTERROGATE" ); SERVICE_LOG_INFO( "Reporting service status SERVICE_STOP_PENDING" ); 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 ) { SERVICE_LOG_INFO( "Starting service proc" ); // Register the handler function for the service g_service.status_handle = RegisterServiceCtrlHandler( g_service.service_name, service_ctrl_handler ); if( !g_service.status_handle ) { SERVICE_LOG_ERROR( "Failed to register the service control handler" ); 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 ) { SERVICE_LOG_ERROR( "No event" ); report_service_status( SERVICE_STOPPED, NO_ERROR, 0 ); return; } // Start the thread which runs the user-provided main function SERVICE_LOG_INFO( "Creating service main thread" ); HANDLE thread = CreateThread( NULL, // no security attribute 0, // default stack size service_main_thread, // thread proc &g_service.is_running, // 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 ); SERVICE_LOG_INFO( "Service started" ); // Wait until the service is stopped WaitForSingleObject( g_service.stop_event, INFINITE ); SERVICE_LOG_INFO( "Service stop requested" ); // 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 SERVICE_LOG_INFO( "Wait for service main thread to exit" ); WaitForSingleObject( thread, INFINITE ); SERVICE_LOG_INFO( "Service main thread exited" ); // Report to the SCM that the service has stopped report_service_status( SERVICE_STOPPED, NO_ERROR, 0 ); SERVICE_LOG_INFO( "Service proc terminated" ); } // Run the service with the specified name, and invoke the main_func function // The main_func provided should exit if is_running becomes false void service_run( char const* service_name, service_main_func_t main_func ) { SetLastError( 0 ); SERVICE_LOG_INFO( "Starting service" ); // 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 ) ) { SERVICE_LOG_LAST_ERROR( "StartServiceCtrlDispatcher" ); } // Cleanup CloseHandle( g_service.sleep_event ); SERVICE_LOG_INFO( "Service stopped" ); } // 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 */