polserver/pol-core/clib/NTService.cpp
2018-10-09 19:07:53 +02:00

538 lines
15 KiB
C++

/** @file
*
* @par History
*
* @note Implementation of CNTService
*/
#include "NTService.h"
#include "ntservmsg.h" // Event message ids
#include <stdio.h>
// Custom Controls Below Here
#define SERVICE_CONTROL_BEEP 0x000004B0
#define SERVICE_CONTROL_STOP_POL 0x000004B1
namespace Pol
{
namespace Clib
{
// static variables
CNTService* CNTService::m_pThis = nullptr;
CNTService::CNTService( const char* szServiceName )
{
// copy the address of the current object so we can access it from
// the static member callback functions.
// WARNING: This limits the application to only one CNTService object.
m_pThis = this;
// Set the default service name and version
strncpy( m_szServiceName, szServiceName, sizeof( m_szServiceName ) - 1 );
m_iMajorVersion = 1;
m_iMinorVersion = 0;
m_hEventSource = nullptr;
// set up the initial service status
m_hServiceStatus = nullptr;
m_Status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
m_Status.dwCurrentState = SERVICE_STOPPED;
m_Status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
m_Status.dwWin32ExitCode = 0;
m_Status.dwServiceSpecificExitCode = 0;
m_Status.dwCheckPoint = 0;
m_Status.dwWaitHint = 0;
m_bIsRunning = FALSE;
}
CNTService::~CNTService()
{
DebugMsg( "CNTService::~CNTService()" );
if ( m_hEventSource )
{
::DeregisterEventSource( m_hEventSource );
}
}
////////////////////////////////////////////////////////////////////////////////////////
// Default command line argument parsing
// Returns TRUE if it found an arg it recognised, FALSE if not
// Note: processing some arguments causes output to stdout to be generated.
BOOL CNTService::ParseStandardArgs( int argc, char* argv[] )
{
// See if we have any command line args we recognise
if ( argc <= 1 )
return FALSE;
if ( _stricmp( argv[1], "-v" ) == 0 )
{
// Spit out version info
printf( "%s Version %d.%d\n", m_szServiceName, m_iMajorVersion, m_iMinorVersion );
printf( "The service is %s installed\n", IsInstalled() ? "currently" : "not" );
return TRUE; // say we processed the argument
}
else if ( _stricmp( argv[1], "-i" ) == 0 )
{
// Request to install.
if ( IsInstalled() )
{
printf( "%s is already installed\n", m_szServiceName );
}
else
{
// Try and install the copy that's running
if ( Install() )
{
printf( "%s installed\n", m_szServiceName );
}
else
{
printf( "%s failed to install. Error %u\n", m_szServiceName, GetLastError() );
}
}
return TRUE; // say we processed the argument
}
else if ( _stricmp( argv[1], "-u" ) == 0 )
{
// Request to uninstall.
if ( !IsInstalled() )
{
printf( "%s is not installed\n", m_szServiceName );
}
else
{
// Try and remove the copy that's installed
if ( Uninstall() )
{
// Get the executable file path
char szFilePath[MAX_PATH];
::GetModuleFileName( nullptr, szFilePath, sizeof( szFilePath ) );
printf( "%s removed. (You must delete the file (%s) yourself.)\n", m_szServiceName,
szFilePath );
}
else
{
printf( "Could not remove %s. Error %u\n", m_szServiceName, GetLastError() );
}
}
return TRUE; // say we processed the argument
}
else if ( _stricmp( argv[1], "-s" ) == 0 )
{
// run as a service
printf( "Running as a service.\n" );
if ( !StartService() )
{
printf( "Unable to run as a service.\n" );
}
return TRUE;
}
// Don't recognise the args
return FALSE;
}
////////////////////////////////////////////////////////////////////////////////////////
// Install/uninstall routines
// Test if the service is currently installed
BOOL CNTService::IsInstalled()
{
BOOL bResult = FALSE;
// Open the Service Control Manager
SC_HANDLE hSCM = ::OpenSCManager( nullptr, // local machine
nullptr, // ServicesActive database
SC_MANAGER_ALL_ACCESS ); // full access
if ( hSCM )
{
// Try to open the service
SC_HANDLE hService = ::OpenService( hSCM, m_szServiceName, SERVICE_QUERY_CONFIG );
if ( hService )
{
bResult = TRUE;
::CloseServiceHandle( hService );
}
::CloseServiceHandle( hSCM );
}
return bResult;
}
BOOL CNTService::Install()
{
// Open the Service Control Manager
SC_HANDLE hSCM = ::OpenSCManager( nullptr, // local machine
nullptr, // ServicesActive database
SC_MANAGER_ALL_ACCESS ); // full access
if ( !hSCM )
return FALSE;
// Get the executable file path
char szFilePath[MAX_PATH];
::GetModuleFileName( nullptr, szFilePath, sizeof( szFilePath ) );
strcat( szFilePath, " -s" );
// Create the service
SC_HANDLE hService = ::CreateService( hSCM, m_szServiceName, m_szServiceName, SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
SERVICE_DEMAND_START, // start condition
SERVICE_ERROR_NORMAL, szFilePath, nullptr, nullptr, nullptr,
nullptr, nullptr );
if ( !hService )
{
::CloseServiceHandle( hSCM );
return FALSE;
}
// make registry entries to support logging messages
// Add the source name as a subkey under the Application
// key in the EventLog service portion of the registry.
char szKey[256];
HKEY hKey = nullptr;
strcpy( szKey, "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\" );
strcat( szKey, m_szServiceName );
if (::RegCreateKey( HKEY_LOCAL_MACHINE, szKey, &hKey ) != ERROR_SUCCESS )
{
::CloseServiceHandle( hService );
::CloseServiceHandle( hSCM );
return FALSE;
}
// Add the Event ID message-file name to the 'EventMessageFile' subkey.
::RegSetValueEx( hKey, "EventMessageFile", 0, REG_EXPAND_SZ, (CONST BYTE*)szFilePath,
static_cast<DWORD>( strlen( szFilePath ) + 1 ) );
// Set the supported types flags.
DWORD dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;
::RegSetValueEx( hKey, "TypesSupported", 0, REG_DWORD, (CONST BYTE*)&dwData, sizeof( DWORD ) );
::RegCloseKey( hKey );
LogEvent( EVENTLOG_INFORMATION_TYPE, EVMSG_INSTALLED, m_szServiceName );
// tidy up
::CloseServiceHandle( hService );
::CloseServiceHandle( hSCM );
return TRUE;
}
BOOL CNTService::Uninstall()
{
// Open the Service Control Manager
SC_HANDLE hSCM = ::OpenSCManager( nullptr, // local machine
nullptr, // ServicesActive database
SC_MANAGER_ALL_ACCESS ); // full access
if ( !hSCM )
return FALSE;
BOOL bResult = FALSE;
SC_HANDLE hService = ::OpenService( hSCM, m_szServiceName, DELETE );
if ( hService )
{
if (::DeleteService( hService ) )
{
LogEvent( EVENTLOG_INFORMATION_TYPE, EVMSG_REMOVED, m_szServiceName );
bResult = TRUE;
}
else
{
LogEvent( EVENTLOG_ERROR_TYPE, EVMSG_NOTREMOVED, m_szServiceName );
}
::CloseServiceHandle( hService );
}
::CloseServiceHandle( hSCM );
return bResult;
}
///////////////////////////////////////////////////////////////////////////////////////
// Logging functions
// This function makes an entry into the application event log
void CNTService::LogEvent( WORD wType, DWORD dwID, const char* pszS1, const char* pszS2,
const char* pszS3 )
{
const char* ps[3];
ps[0] = pszS1;
ps[1] = pszS2;
ps[2] = pszS3;
WORD iStr = 0;
for ( WORD i = 0; i < 3; i++ )
{
if ( ps[i] != nullptr )
iStr++;
}
// Check the event source has been registered and if
// not then register it now
if ( !m_hEventSource )
{
m_hEventSource = ::RegisterEventSource( nullptr, // local machine
m_szServiceName ); // source name
}
if ( m_hEventSource )
{
::ReportEvent( m_hEventSource, wType, 0, dwID,
nullptr, // sid
iStr, 0, ps, nullptr );
}
}
//////////////////////////////////////////////////////////////////////////////////////////////
// Service startup and registration
BOOL CNTService::StartService()
{
SERVICE_TABLE_ENTRY st[] = {{m_szServiceName, ServiceMain}, {nullptr, nullptr}};
DebugMsg( "Calling StartServiceCtrlDispatcher()" );
BOOL b = ::StartServiceCtrlDispatcher( st );
if ( !b )
{
DWORD rc = GetLastError();
DebugMsg( "StartServiceCtrlDispatcher returned false: %d", rc );
}
DebugMsg( "Returned from StartServiceCtrlDispatcher()" );
return b;
}
// static member function (callback)
void CNTService::ServiceMain( DWORD /*dwArgc*/, LPTSTR* /*lpszArgv*/ )
{
// Get a pointer to the C++ object
CNTService* pService = m_pThis;
pService->DebugMsg( "Entering CNTService::ServiceMain()" );
// Register the control request handler
pService->m_Status.dwCurrentState = SERVICE_START_PENDING;
pService->m_hServiceStatus = RegisterServiceCtrlHandler( pService->m_szServiceName, Handler );
if ( pService->m_hServiceStatus == nullptr )
{
pService->LogEvent( EVENTLOG_ERROR_TYPE, EVMSG_CTRLHANDLERNOTINSTALLED );
return;
}
// Start the initialisation
if ( pService->Initialize() )
{
// Do the real work.
// When the Run function returns, the service has stopped.
pService->m_bIsRunning = TRUE;
pService->m_Status.dwWin32ExitCode = 0;
pService->m_Status.dwCheckPoint = 0;
pService->m_Status.dwWaitHint = 0;
pService->Run();
}
// Tell the service manager we are stopped
pService->SetStatus( SERVICE_STOPPED );
pService->DebugMsg( "Leaving CNTService::ServiceMain()" );
}
///////////////////////////////////////////////////////////////////////////////////////////
// status functions
void CNTService::SetStatus( DWORD dwState )
{
DebugMsg( "CNTService::SetStatus(%lu, %lu)", m_hServiceStatus, dwState );
m_Status.dwCurrentState = dwState;
::SetServiceStatus( m_hServiceStatus, &m_Status );
}
///////////////////////////////////////////////////////////////////////////////////////////
// Service initialization
BOOL CNTService::Initialize()
{
DebugMsg( "Entering CNTService::Initialize()" );
// Start the initialization
SetStatus( SERVICE_START_PENDING );
// Perform the actual initialization
BOOL bResult = OnInit();
// Set final state
m_Status.dwWin32ExitCode = GetLastError();
m_Status.dwCheckPoint = 0;
m_Status.dwWaitHint = 0;
if ( !bResult )
{
LogEvent( EVENTLOG_ERROR_TYPE, EVMSG_FAILEDINIT );
SetStatus( SERVICE_STOPPED );
return FALSE;
}
LogEvent( EVENTLOG_INFORMATION_TYPE, EVMSG_STARTED );
SetStatus( SERVICE_RUNNING );
DebugMsg( "Leaving CNTService::Initialize()" );
return TRUE;
}
///////////////////////////////////////////////////////////////////////////////////////////////
// main function to do the real work of the service
// This function performs the main work of the service.
// When this function returns the service has stopped.
void CNTService::Run()
{
DebugMsg( "Entering CNTService::Run()" );
while ( m_bIsRunning )
{
DebugMsg( "Sleeping..." );
Sleep( 5000 );
}
// nothing more to do
DebugMsg( "Leaving CNTService::Run()" );
}
//////////////////////////////////////////////////////////////////////////////////////
// Control request handlers
// static member function (callback) to handle commands from the
// service control manager
void CNTService::Handler( DWORD dwOpcode )
{
// Get a pointer to the object
CNTService* pService = m_pThis;
pService->DebugMsg( "CNTService::Handler(%lu)", dwOpcode );
switch ( dwOpcode )
{
case SERVICE_CONTROL_STOP: // 1
pService->SetStatus( SERVICE_STOP_PENDING );
pService->OnStop();
pService->m_bIsRunning = FALSE;
pService->LogEvent( EVENTLOG_INFORMATION_TYPE, EVMSG_STOPPED );
break;
case SERVICE_CONTROL_PAUSE: // 2
pService->OnPause();
break;
case SERVICE_CONTROL_CONTINUE: // 3
pService->OnContinue();
break;
case SERVICE_CONTROL_INTERROGATE: // 4
pService->OnInterrogate();
break;
case SERVICE_CONTROL_SHUTDOWN: // 5
pService->OnShutdown();
break;
default:
if ( dwOpcode >= SERVICE_CONTROL_USER )
{
if ( !pService->OnUserControl( dwOpcode ) )
{
pService->LogEvent( EVENTLOG_ERROR_TYPE, EVMSG_BADREQUEST );
}
}
else
{
pService->LogEvent( EVENTLOG_ERROR_TYPE, EVMSG_BADREQUEST );
}
break;
}
// Report current status
pService->DebugMsg( "Updating status (%lu, %lu)", pService->m_hServiceStatus,
pService->m_Status.dwCurrentState );
::SetServiceStatus( pService->m_hServiceStatus, &pService->m_Status );
}
// Called when the service is first initialized
BOOL CNTService::OnInit()
{
DebugMsg( "CNTService::OnInit()" );
return TRUE;
}
// Called when the service control manager wants to stop the service
void CNTService::OnStop()
{
DebugMsg( "CNTService::OnStop()" );
}
// called when the service is interrogated
void CNTService::OnInterrogate()
{
DebugMsg( "CNTService::OnInterrogate()" );
}
// called when the service is paused
void CNTService::OnPause()
{
DebugMsg( "CNTService::OnPause()" );
}
// called when the service is continued
void CNTService::OnContinue()
{
DebugMsg( "CNTService::OnContinue()" );
}
// called when the service is shut down
void CNTService::OnShutdown()
{
DebugMsg( "CNTService::OnShutdown()" );
}
// called when the service gets a user control message
BOOL CNTService::OnUserControl( DWORD dwOpcode )
{
CNTService* pService = m_pThis;
switch ( dwOpcode )
{
case SERVICE_CONTROL_BEEP: // 1200
Beep( 1200, 200 );
break;
case SERVICE_CONTROL_STOP_POL: // 1201
pService->SetStatus( SERVICE_STOP_PENDING );
pService->OnStop();
pService->m_bIsRunning = FALSE;
pService->LogEvent( EVENTLOG_INFORMATION_TYPE, EVMSG_STOPPED );
break;
default:
DebugMsg( "CNTService::OnUserControl(%8.8lXH)", dwOpcode );
return FALSE; // say not handled
break;
}
// if it makes it this far, something's wrong anyway.
// left in for contrivance
DebugMsg( "CNTService::OnUserControl(%8.8lXH)", dwOpcode );
return FALSE; // say not handled
}
////////////////////////////////////////////////////////////////////////////////////////////
// Debugging support
void CNTService::DebugMsg( const char* pszFormat, ... )
{
char buf[1024];
sprintf( buf, "[%s](%lu): ", m_szServiceName, GetCurrentThreadId() );
va_list arglist;
va_start( arglist, pszFormat );
vsprintf( &buf[strlen( buf )], pszFormat, arglist );
va_end( arglist );
strcat( buf, "\n" );
OutputDebugString( buf );
}
}
}