mirror of
https://github.com/finos/SymphonyElectron.git
synced 2024-12-28 09:51:06 -06:00
Merge pull request #1226 from mattias-symphony/SDA-3155
SDA-3155 Implementation of auto update service
This commit is contained in:
commit
4bffbbfb17
103
auto_update/auto_update_service.c
Normal file
103
auto_update/auto_update_service.c
Normal file
@ -0,0 +1,103 @@
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#define _CRT_NONSTDC_NO_WARNINGS
|
||||
#include "service.h"
|
||||
#include "ipc.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
|
||||
#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"
|
@ -1,332 +0,0 @@
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#include <windows.h>
|
||||
#include <aclapi.h>
|
||||
#include <stdio.h>
|
||||
#include <tchar.h>
|
||||
#include <strsafe.h>
|
||||
|
||||
#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"
|
||||
|
@ -134,10 +134,11 @@
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="auto_update_service.cpp" />
|
||||
<ClCompile Include="auto_update_service.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ipc.h" />
|
||||
<ClInclude Include="service.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
|
@ -1,6 +1,9 @@
|
||||
#ifndef ipc_h
|
||||
#define ipc_h
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
342
auto_update/service.h
Normal file
342
auto_update/service.h
Normal file
@ -0,0 +1,342 @@
|
||||
#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)( 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 <windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// 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 */
|
Loading…
Reference in New Issue
Block a user