Blob Blame Raw


#include "service.h"
#include "tlog.h"
#include "tconvert.h"

#include "tfilepath.h"

#ifdef WIN32
#pragma warning(disable : 4996)
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <tchar.h>
#else
#include <string.h>
#include <errno.h>
#endif

#define SZDEPENDENCIES ""

#ifdef WIN32

//------------------------------------------------------------------------------

LPTSTR GetLastErrorText(LPTSTR lpszBuf, DWORD dwSize)
{
	DWORD dwRet;
	LPTSTR lpszTemp = NULL;

	dwRet = FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
		NULL,
		GetLastError(),
		LANG_NEUTRAL,
		(LPTSTR)&lpszTemp,
		0,
		NULL);

	// supplied buffer is not long enough
	if (!dwRet || ((long)dwSize < (long)dwRet + 14))
		lpszBuf[0] = TEXT('\0');
	else {
		lpszTemp[lstrlen(lpszTemp) - 2] = TEXT('\0'); //remove cr and newline character
		_stprintf(lpszBuf, TEXT("%s (0x%x)"), lpszTemp, GetLastError());
	}

	if (lpszTemp)
		LocalFree((HLOCAL)lpszTemp);

	return lpszBuf;
}

#endif // WIN32

//------------------------------------------------------------------------------

string getLastErrorText()
{
	string errText;
#ifdef WIN32
	char errBuff[256];
	errText = GetLastErrorText(errBuff, sizeof(errBuff));
#else
	errText = strerror(errno);
#endif

	return errText;
}

//==============================================================================
//==============================================================================
//==============================================================================
//==============================================================================

class TService::Imp
{
public:
	Imp() {}

#ifdef WIN32
	static void WINAPI serviceMain(DWORD dwArgc, LPTSTR *lpszArgv);
	static void WINAPI serviceCtrl(DWORD dwCtrlCode);

	static BOOL WINAPI controlHandler(DWORD dwCtrlType);

	static bool reportStatusToSCMgr(long currentState, long win32ExitCode, long waitHint);

#endif

	string m_name;
	string m_displayName;
	static bool m_console;

#ifdef WIN32
	static SERVICE_STATUS_HANDLE m_hService;
	static SERVICE_STATUS m_ssStatus; // current status of the service
	static DWORD m_dwErr;

#endif
};

#ifdef WIN32
SERVICE_STATUS_HANDLE TService::Imp::m_hService = 0;
SERVICE_STATUS TService::Imp::m_ssStatus;
DWORD TService::Imp::m_dwErr = 0;
#endif

bool TService::Imp::m_console = false;

//------------------------------------------------------------------------------

TService::TService(const string &name, const string &displayName)
	: m_imp(new Imp)
{
	m_imp->m_name = name;
	m_imp->m_displayName = displayName;
	m_instance = this;
}

//------------------------------------------------------------------------------

TService::~TService()
{
	delete m_imp;
}

//------------------------------------------------------------------------------

TService *TService::instance()
{
	return m_instance;
}

//------------------------------------------------------------------------------

TService *TService::m_instance = 0;

//------------------------------------------------------------------------------

void TService::setStatus(Status status, long exitCode, long waitHint)
{
#ifdef WIN32
	if (!isRunningAsConsoleApp())
		TService::Imp::reportStatusToSCMgr(status, exitCode, waitHint);
	else {
		if (status == Stopped)
			exit(1);
	}
#else
	if (status == Stopped)
		exit(1);
#endif
}

//------------------------------------------------------------------------------

string TService::getName() const
{
	return m_imp->m_name;
}

//------------------------------------------------------------------------------

string TService::getDisplayName() const
{
	return m_imp->m_displayName;
}

//------------------------------------------------------------------------------

#ifdef WIN32

void WINAPI TService::Imp::serviceCtrl(DWORD dwCtrlCode)
{
	// Handle the requested control code.
	//
	switch (dwCtrlCode) {
	// Stop the service.
	//
	// SERVICE_STOP_PENDING should be reported before
	// setting the Stop Event - hServerStopEvent - in
	// ServiceStop().  This avoids a race condition
	// which may result in a 1053 - The Service did not respond...
	// error.
	case SERVICE_CONTROL_STOP:
	case SERVICE_CONTROL_SHUTDOWN: {
		TService::Imp::reportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 3000 /*1*/ /*0*/);
		TService::instance()->onStop();

		TService::Imp::reportStatusToSCMgr(SERVICE_STOPPED, NO_ERROR, 3000);
		return;
	}

	// Update the service status.
	//
	case SERVICE_CONTROL_INTERROGATE:
		break;

	// invalid control code
	//
	default:
		break;
	}

	reportStatusToSCMgr(m_ssStatus.dwCurrentState, NO_ERROR, 0);
}

//------------------------------------------------------------------------------

void WINAPI TService::Imp::serviceMain(DWORD dwArgc, LPTSTR *lpszArgv)
{
	// register our service control handler:
	//
	m_hService = RegisterServiceCtrlHandler(
		TService::instance()->getName().c_str(), TService::Imp::serviceCtrl);

	if (m_hService == 0)
		goto cleanup;

	// SERVICE_STATUS members that don't change in example
	//
	m_ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
	m_ssStatus.dwServiceSpecificExitCode = 0;

	// report the status to the service control manager.
	//
	if (!reportStatusToSCMgr(
			SERVICE_START_PENDING, // service state
			NO_ERROR,			   // exit code
			3000))				   // wait hint
		goto cleanup;

	// AGGIUNGERE INIZIALIZZAZIONE QUI

	if (!reportStatusToSCMgr(
			SERVICE_RUNNING, // service state
			NO_ERROR,		 // exit code
			0))				 // wait hint
		goto cleanup;

	TService::instance()->onStart(dwArgc, lpszArgv);

cleanup:

	// try to report the stopped status to the service control manager.
	//
	if (m_hService)
		reportStatusToSCMgr(SERVICE_STOPPED, m_dwErr, 0);

	return;
}

//------------------------------------------------------------------------------
//
//  TService::Imp::controlHandler( DWORD dwCtrlType )
//
//  PURPOSE: Handled console control events
//
//  PARAMETERS:
//    dwCtrlType - type of control event
//
//  RETURN VALUE:
//    True - handled
//    False - unhandled
//
//  COMMENTS:
//

BOOL WINAPI TService::Imp::controlHandler(DWORD dwCtrlType)
{
	switch (dwCtrlType) {
	case CTRL_CLOSE_EVENT:
	case CTRL_LOGOFF_EVENT:
	case CTRL_SHUTDOWN_EVENT:

	case CTRL_BREAK_EVENT: // use Ctrl+C or Ctrl+Break to simulate
	case CTRL_C_EVENT:	 // SERVICE_CONTROL_STOP in debug mode
		_tprintf(
			TEXT("Stopping %s.\n"),
			TService::instance()->getDisplayName().c_str());

		TService::instance()->onStop();
		return TRUE;
		break;
	}

	return FALSE;
}

//------------------------------------------------------------------------------
//
//  FUNCTION: ReportStatusToSCMgr()
//
//  PURPOSE: Sets the current status of the service and
//           reports it to the Service Control Manager
//
//  PARAMETERS:
//    dwCurrentState - the state of the service
//    dwWin32ExitCode - error code to report
//    dwWaitHint - worst case estimate to next checkpoint
//
//  RETURN VALUE:
//    TRUE  - success
//    FALSE - failure
//
//  COMMENTS:
//
//------------------------------------------------------------------------------

bool TService::Imp::reportStatusToSCMgr(long currentState, long win32ExitCode, long waitHint)
{
	/*
	SERVICE_STATUS srvStatus;		// Declare a SERVICE_STATUS structure, and fill it in

	srvStatus.dwServiceType  = SERVICE_WIN32_OWN_PROCESS;	// We're a service running in our own process

	// Set the state of the service from the argument, and save it away
	// for future use
	_dwPrevState=_dwCurrState;
	srvStatus.dwCurrentState = _dwCurrState = dwState;

	// Which commands will we accept from the SCM? All the common ones...
	srvStatus.dwControlsAccepted =	SERVICE_ACCEPT_STOP | 
													SERVICE_ACCEPT_PAUSE_CONTINUE | 
													SERVICE_ACCEPT_SHUTDOWN;

	srvStatus.dwWin32ExitCode = dwExitCode;	// Set the Win32 exit code for the service
	srvStatus.dwServiceSpecificExitCode = 0;	// Set the service-specific exit code
	srvStatus.dwCheckPoint = dwProgress;	// Set the checkpoint value	
	srvStatus.dwWaitHint = __WAIT_TIME_FOR_SERVICE;	// 3 second timeout for waits

	return SetServiceStatus( _hService, &srvStatus); // pass the structure to the SCM
*/

	static DWORD dwCheckPoint = 1;
	BOOL fResult = true;

	if (!m_console) // when running as a console application we don't report to the SCM
	{
		if (currentState == SERVICE_START_PENDING)
			m_ssStatus.dwControlsAccepted = 0;
		else
			m_ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP |
											SERVICE_ACCEPT_PAUSE_CONTINUE |
											SERVICE_ACCEPT_SHUTDOWN;
		;

		m_ssStatus.dwCurrentState = currentState;
		m_ssStatus.dwWin32ExitCode = win32ExitCode;
		m_ssStatus.dwWaitHint = waitHint;

		if ((currentState == SERVICE_RUNNING) ||
			(currentState == SERVICE_STOPPED))
			m_ssStatus.dwCheckPoint = 0;
		else
			m_ssStatus.dwCheckPoint = dwCheckPoint++;

		// Report the status of the service to the service control manager.
		//
		if (!(fResult = SetServiceStatus(m_hService, &m_ssStatus))) {
			TService::addToMessageLog(QString("Failed to set the service status"));
		}
	}
	return !!fResult;
}

#endif

//------------------------------------------------------------------------------

void TService::run(int argc, char *argv[], bool console)
{
	m_imp->m_console = console;

/*
#ifdef _DEBUG
  DebugBreak();
#endif
*/

#ifdef WIN32
	if (console) {
		_tprintf(TEXT("Starting %s.\n"), TService::instance()->getDisplayName().c_str());

		SetConsoleCtrlHandler(TService::Imp::controlHandler, TRUE);
		TService::instance()->onStart(argc, argv);
	} else {
		SERVICE_TABLE_ENTRY dispatchTable[2];

		string name = TService::instance()->getName().c_str();

		dispatchTable[0].lpServiceName = (char *)name.c_str();
		dispatchTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)TService::Imp::serviceMain;

		dispatchTable[1].lpServiceName = NULL;
		dispatchTable[1].lpServiceProc = NULL;

		if (!StartServiceCtrlDispatcher(dispatchTable))
			TService::addToMessageLog(QString(TEXT("Service start failed.")));
	}
#else
	TService::instance()->onStart(argc, argv);
#endif
}

//------------------------------------------------------------------------------

void TService::start(const string &name)
{
}

//------------------------------------------------------------------------------

void TService::stop(const string &name)
{
}

//------------------------------------------------------------------------------

bool TService::isRunningAsConsoleApp() const
{
	return m_imp->m_console;
}

//------------------------------------------------------------------------------

void TService::install(const string &name, const string &displayName, const TFilePath &appPath)
{
#ifdef WIN32
	SC_HANDLE schService;
	SC_HANDLE schSCManager;

	schSCManager = OpenSCManager(
		NULL,					// machine (NULL == local)
		NULL,					// database (NULL == default)
		SC_MANAGER_ALL_ACCESS); // access required

	if (schSCManager) {
		schService = CreateService(
			schSCManager,							   // SCManager database
			name.c_str(),							   // name of service
			displayName.c_str(),					   // 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
			toString(appPath.getWideString()).c_str(), // service's binary
			NULL,									   // no load ordering group
			NULL,									   // no tag identifier
			TEXT(SZDEPENDENCIES),					   // dependencies
			NULL,									   // LocalSystem account
			NULL);									   // no password

		if (schService) {
			_tprintf(TEXT("%s installed.\n"), displayName.c_str());
			CloseServiceHandle(schService);
		} else {
			_tprintf(TEXT("CreateService failed - %s\n"), getLastErrorText().c_str());
		}

		CloseServiceHandle(schSCManager);
	} else
		_tprintf(TEXT("OpenSCManager failed - %s\n"), getLastErrorText().c_str());
#endif
}

//------------------------------------------------------------------------------

void TService::remove(const string &name)
{
#ifdef WIN32
	string displayName = name;

	SC_HANDLE schService;
	SC_HANDLE schSCManager;

	schSCManager = OpenSCManager(
		NULL,					// machine (NULL == local)
		NULL,					// database (NULL == default)
		SC_MANAGER_ALL_ACCESS); // access required

	if (schSCManager) {
		schService = OpenService(schSCManager, name.c_str(), SERVICE_ALL_ACCESS);

		if (schService) {

			SERVICE_STATUS ssStatus; // current status of the service

			// try to stop the service
			if (ControlService(schService, SERVICE_CONTROL_STOP, &ssStatus)) {
				_tprintf(TEXT("Stopping %s."), displayName.c_str());
				Sleep(1000);

				while (QueryServiceStatus(schService, &ssStatus)) {
					if (ssStatus.dwCurrentState == SERVICE_STOP_PENDING) {
						_tprintf(TEXT("."));
						Sleep(1000);
					} else
						break;
				}

				if (ssStatus.dwCurrentState == SERVICE_STOPPED)
					_tprintf(TEXT("\n%s stopped.\n"), displayName.c_str());
				else
					_tprintf(TEXT("\n%s failed to stop.\n"), displayName.c_str());
			}

			// now remove the service
			if (DeleteService(schService))
				_tprintf(TEXT("%s removed.\n"), displayName.c_str());
			else
				_tprintf(TEXT("DeleteService failed - %s\n"), getLastErrorText().c_str());

			CloseServiceHandle(schService);
		} else
			_tprintf(TEXT("OpenService failed - %s\n"), getLastErrorText().c_str());

		CloseServiceHandle(schSCManager);
	} else
		_tprintf(TEXT("OpenSCManager failed - %s\n"), getLastErrorText().c_str());
#endif
}

//------------------------------------------------------------------------------

void TService::addToMessageLog(const QString &msg)
{
	addToMessageLog(msg.toStdString());
}

void TService::addToMessageLog(const string &msg)
{
#ifdef WIN32
	TCHAR szMsg[256];
	HANDLE hEventSource;
	LPCTSTR lpszStrings[2];

	if (!TService::Imp::m_console) {
		TService::Imp::m_dwErr = GetLastError();

		// Use event logging to log the error.
		//
		hEventSource = RegisterEventSource(NULL, TService::instance()->getName().c_str());

		_stprintf(szMsg, TEXT("%s error: %d"), TService::instance()->getName().c_str(), TService::Imp::m_dwErr);
		lpszStrings[0] = szMsg;
		lpszStrings[1] = msg.c_str();

		if (hEventSource != NULL) {
			ReportEvent(hEventSource,		 // handle of event source
						EVENTLOG_ERROR_TYPE, // event type
						0,					 // event category
						0,					 // event ID
						NULL,				 // current user's SID
						2,					 // strings in lpszStrings
						0,					 // no bytes of raw data
						lpszStrings,		 // array of error strings
						NULL);				 // no raw data

			(VOID) DeregisterEventSource(hEventSource);
		}
	} else {
		cout << msg.c_str();
	}
#else
	if (!TService::Imp::m_console) {
		QString str(msg.c_str());
		TSysLog::error(str);
	} else {
		std::cout << msg.c_str();
	}
#endif
}