SymphonyElectron/auto_update/service.h

323 lines
11 KiB
C

#ifndef service_h
#define service_h
#include <stdbool.h>
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 <windows.h>
#include <stdio.h>
// 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 */