diff options
author | ChrisTX <chris@rev-crew.info> | 2013-03-23 23:52:51 +0100 |
---|---|---|
committer | attilamolnar <attilamolnar@hush.com> | 2013-03-25 14:57:05 +0100 |
commit | 25aa23067133fa80df009c2245742b3b7749c2e5 (patch) | |
tree | 50e9e6603d8a9322f28efb2a5115911d0d3e2b1d | |
parent | f2a5fa58edb13ad70764ecd8c5d52041b8b6eed0 (diff) |
Win32: Rewrite the service wrapper
Edited original commit to pass MAX_PATH as nSize to GetModuleFileNameA() instead of 101 after approval from author @ChrisTX (Attila Molnar)
Fixes #404 reported by @crchauffe
-rw-r--r-- | win/inspircd_win32wrapper.cpp | 22 | ||||
-rw-r--r-- | win/inspircd_win32wrapper.h | 15 | ||||
-rw-r--r-- | win/win32service.cpp | 442 | ||||
-rw-r--r-- | win/win32service.h | 7 |
4 files changed, 208 insertions, 278 deletions
diff --git a/win/inspircd_win32wrapper.cpp b/win/inspircd_win32wrapper.cpp index a451dd0df..ebc498529 100644 --- a/win/inspircd_win32wrapper.cpp +++ b/win/inspircd_win32wrapper.cpp @@ -180,6 +180,28 @@ int getopt_long(int ___argc, char *const *___argv, const char *__shortopts, cons return 1; } +CWin32Exception::CWin32Exception() : exception() +{ + dwErrorCode = GetLastError(); + if( FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dwErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)szErrorString, _countof(szErrorString), NULL) == 0 ) + sprintf_s(szErrorString, _countof(szErrorString), "Error code: %u", dwErrorCode); +} + +CWin32Exception::CWin32Exception(const CWin32Exception& other) +{ + strcpy_s(szErrorString, _countof(szErrorString), other.szErrorString); +} + +const char* CWin32Exception::what() const throw() +{ + return szErrorString; +} + +DWORD CWin32Exception::GetErrorCode() +{ + return dwErrorCode; +} + #include "../src/modules/m_spanningtree/link.h" #include "../src/modules/ssl.h" template class reference<Link>; diff --git a/win/inspircd_win32wrapper.h b/win/inspircd_win32wrapper.h index d60276b88..75404ef8b 100644 --- a/win/inspircd_win32wrapper.h +++ b/win/inspircd_win32wrapper.h @@ -186,5 +186,20 @@ void ::operator delete(void * ptr); #define DISABLE_WRITEV +#include <exception> + +class CWin32Exception : public std::exception +{ +public: + CWin32Exception(); + CWin32Exception(const CWin32Exception& other); + virtual const char* what() const throw(); + DWORD GetErrorCode(); + +private: + char szErrorString[500]; + DWORD dwErrorCode; +}; + #endif diff --git a/win/win32service.cpp b/win/win32service.cpp index 0a4b0c5b4..c34e9957d 100644 --- a/win/win32service.cpp +++ b/win/win32service.cpp @@ -21,159 +21,61 @@ #include "inspircd.h" #include "exitcodes.h" #include <windows.h> -#include <stdlib.h> -#include <string.h> -#include <stdio.h> +#include <cstdlib> +#include <cstring> +#include <cstdio> #include <iostream> -static SERVICE_STATUS_HANDLE serviceStatusHandle; -static HANDLE hThreadEvent; -static HANDLE killServiceEvent; -static int serviceCurrentStatus; +static SERVICE_STATUS_HANDLE g_ServiceStatusHandle; +static SERVICE_STATUS g_ServiceStatus; +static bool g_bRunningAsService; -/** This is used to define ChangeServiceConf2() as we can't link - * directly against this symbol (see below where it is used) - */ -typedef BOOL (CALLBACK* SETSERVDESC)(SC_HANDLE,DWORD,LPVOID); - -BOOL UpdateSCMStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwServiceSpecificExitCode, DWORD dwCheckPoint, DWORD dwWaitHint); -void terminateService(int code, int wincode); - -/* A commandline parameter handler for service specific commandline parameters */ -typedef void (*CommandlineParameterHandler)(void); - -/* Represents a commandline and its handler */ -struct Commandline -{ - const char* Switch; - CommandlineParameterHandler Handler; +struct Service_Data { + DWORD argc; + LPSTR *argv; }; -/* A function pointer for dynamic linking tricks */ -SETSERVDESC ChangeServiceConf; - -LPCSTR RetrieveLastError() -{ - static char err[100]; - DWORD LastError = GetLastError(); - if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, 0, LastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)err, sizeof(err), 0) == 0) - snprintf(err, sizeof(err), "Error code: %d", LastError); - SetLastError(ERROR_SUCCESS); - return err; -} - -/* Returns true if this program is running as a service, false if it is running interactive */ -bool IsAService() -{ - USEROBJECTFLAGS uoflags; - HWINSTA winstation = GetProcessWindowStation(); - if (GetUserObjectInformation(winstation, UOI_FLAGS, &uoflags, sizeof(uoflags), NULL)) - return ((uoflags.dwFlags & WSF_VISIBLE) == 0); - else - return false; -} - -/* Kills the service by setting an event which the other thread picks up and exits */ -void KillService() -{ - SetEvent(hThreadEvent); - Sleep(2000); - SetEvent(killServiceEvent); -} +static Service_Data g_ServiceData; /** The main part of inspircd runs within this thread function. This allows the service part to run * seperately on its own and to be able to kill the worker thread when its time to quit. */ -DWORD WINAPI WorkerThread(LPDWORD param) +DWORD WINAPI WorkerThread(LPVOID param) { - char modname[MAX_PATH]; - GetModuleFileNameA(NULL, modname, sizeof(modname)); - char* argv[] = { modname, "--nofork" }; - smain(2, argv); - KillService(); + smain(g_ServiceData.argc, g_ServiceData.argv); return 0; } /* This is called when all startup is done */ void SetServiceRunning() { - if (!IsAService()) + if (!g_bRunningAsService) return; - serviceCurrentStatus = SERVICE_RUNNING; - BOOL success = UpdateSCMStatus(SERVICE_RUNNING, NO_ERROR, 0, 0, 0); - if (!success) - { - terminateService(EXIT_STATUS_UPDATESCM_FAILED, GetLastError()); - return; - } -} + g_ServiceStatus.dwCurrentState = SERVICE_RUNNING; + g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; - -/** Starts the worker thread above */ -void StartServiceThread() -{ - HANDLE hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)WorkerThread,NULL,0,NULL); - if (hThread != NULL) - CloseHandle(hThread); + if( !SetServiceStatus( g_ServiceStatusHandle, &g_ServiceStatus ) ) + throw CWin32Exception(); } -/** This function updates the status of the service in the SCM - * (service control manager, the services.msc applet) - */ -BOOL UpdateSCMStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwServiceSpecificExitCode, DWORD dwCheckPoint, DWORD dwWaitHint) +/* In windows we hook this to InspIRCd::Exit() */ +void SetServiceStopped(DWORD dwStatus) { - BOOL success; - SERVICE_STATUS serviceStatus; - serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; - serviceStatus.dwCurrentState = dwCurrentState; - - if (dwCurrentState == SERVICE_START_PENDING) - { - serviceStatus.dwControlsAccepted = 0; - } - else - { - serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; - } + if (!g_bRunningAsService) + return; - if (dwServiceSpecificExitCode == 0) + g_ServiceStatus.dwCurrentState = SERVICE_STOPPED; + if(dwStatus != EXIT_STATUS_NOERROR) { - serviceStatus.dwWin32ExitCode = dwWin32ExitCode; + g_ServiceStatus.dwServiceSpecificExitCode = dwStatus; + g_ServiceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; } else { - serviceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; + g_ServiceStatus.dwWin32ExitCode = ERROR_SUCCESS; } - serviceStatus.dwServiceSpecificExitCode = dwServiceSpecificExitCode; - serviceStatus.dwCheckPoint = dwCheckPoint; - serviceStatus.dwWaitHint = dwWaitHint; - - success = SetServiceStatus (serviceStatusHandle, &serviceStatus); - if (!success) - { - KillService(); - } - return success; -} - -/** This function is called by us when the service is being shut down or when it can't be started */ -void terminateService(int code, int wincode) -{ - UpdateSCMStatus(SERVICE_STOPPED, wincode ? wincode : ERROR_SERVICE_SPECIFIC_ERROR, wincode ? 0 : code, 0, 0); - return; -} - -/* In windows we hook this to InspIRCd::Exit() */ -void SetServiceStopped(int status) -{ - if (!IsAService()) - exit(status); - - /* Are we running as a service? If so, trigger the service specific exit code */ - terminateService(status, 0); - KillService(); - exit(status); + SetServiceStatus( g_ServiceStatusHandle, &g_ServiceStatus ); } /** This callback is called by windows when the state of the service has been changed */ @@ -181,213 +83,205 @@ VOID ServiceCtrlHandler(DWORD controlCode) { switch(controlCode) { - case SERVICE_CONTROL_INTERROGATE: - break; case SERVICE_CONTROL_SHUTDOWN: case SERVICE_CONTROL_STOP: - serviceCurrentStatus = SERVICE_STOP_PENDING; - UpdateSCMStatus(SERVICE_STOP_PENDING, NO_ERROR, 0, 1, 5000); - KillService(); - UpdateSCMStatus(SERVICE_STOPPED, NO_ERROR, 0, 0, 0); - return; - default: - break; + g_ServiceStatus.dwCurrentState = SERVICE_STOPPED; + SetServiceStatus( g_ServiceStatusHandle, &g_ServiceStatus ); + break; } - UpdateSCMStatus(serviceCurrentStatus, NO_ERROR, 0, 0, 0); } /** This callback is called by windows when the service is started */ -VOID ServiceMain(DWORD argc, LPTSTR *argv) +VOID ServiceMain(DWORD argc, LPCSTR *argv) { - BOOL success; + g_ServiceStatusHandle = RegisterServiceCtrlHandler(TEXT("InspIRCd"), (LPHANDLER_FUNCTION)ServiceCtrlHandler); + if( !g_ServiceStatusHandle ) + return; - serviceStatusHandle = RegisterServiceCtrlHandler(TEXT("InspIRCd"), (LPHANDLER_FUNCTION)ServiceCtrlHandler); - if (!serviceStatusHandle) - { - terminateService(EXIT_STATUS_RSCH_FAILED, GetLastError()); + g_ServiceStatus.dwCheckPoint = 1; + g_ServiceStatus.dwControlsAccepted = 0; + g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + g_ServiceStatus.dwWaitHint = 5000; + g_ServiceStatus.dwWin32ExitCode = NO_ERROR; + g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING; + + if( !SetServiceStatus( g_ServiceStatusHandle, &g_ServiceStatus ) ) return; - } - success = UpdateSCMStatus(SERVICE_START_PENDING, NO_ERROR, 0, 1, 1000); - if (!success) + char szModuleName[MAX_PATH]; + if(GetModuleFileNameA(NULL, szModuleName, MAX_PATH)) { - terminateService(EXIT_STATUS_UPDATESCM_FAILED, GetLastError()); - return; - } + if(!argc) + argc = 1; - killServiceEvent = CreateEvent(NULL, true, false, NULL); - hThreadEvent = CreateEvent(NULL, true, false, NULL); + g_ServiceData.argc = argc; - if (!killServiceEvent || !hThreadEvent) - { - terminateService(EXIT_STATUS_CREATE_EVENT_FAILED, GetLastError()); - return; - } + // Note: since this memory is going to stay allocated for the rest of the execution, + // it doesn't make sense to free it, as it's going to be "freed" on process termination + try { + g_ServiceData.argv = new char*[argc]; - success = UpdateSCMStatus(SERVICE_START_PENDING, NO_ERROR, 0, 2, 1000); - if (!success) - { - terminateService(EXIT_STATUS_UPDATESCM_FAILED, GetLastError()); - return; + uint32_t allocsize = strnlen_s(szModuleName, MAX_PATH) + 1; + g_ServiceData.argv[0] = new char[allocsize]; + strcpy_s(g_ServiceData.argv[0], allocsize, szModuleName); + + for(uint32_t i = 1; i < argc; i++) + { + allocsize = strnlen_s(argv[i], MAX_PATH) + 1; + g_ServiceData.argv[i] = new char[allocsize]; + strcpy_s(g_ServiceData.argv[i], allocsize, argv[i]); + } + + *(strrchr(szModuleName, '\\') + 1) = NULL; + SetCurrentDirectoryA(szModuleName); + + HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)WorkerThread, NULL, 0, NULL); + if (hThread != NULL) + { + WaitForSingleObject(hThread, INFINITE); + CloseHandle(hThread); + } + } + catch(...) + { + g_ServiceStatus.dwCurrentState = SERVICE_STOPPED; + g_ServiceStatus.dwWin32ExitCode = ERROR_OUTOFMEMORY; + SetServiceStatus( g_ServiceStatusHandle, &g_ServiceStatus ); + } } + if(g_ServiceStatus.dwCurrentState == SERVICE_STOPPED) + return; - StartServiceThread(); - WaitForSingleObject (killServiceEvent, INFINITE); + g_ServiceStatus.dwCurrentState = SERVICE_STOPPED; + g_ServiceStatus.dwWin32ExitCode = GetLastError(); + SetServiceStatus( g_ServiceStatusHandle, &g_ServiceStatus ); } /** Install the windows service. This requires administrator privileges. */ void InstallService() { - SC_HANDLE myService, scm; - SERVICE_DESCRIPTION svDesc; - HINSTANCE advapi32; + SC_HANDLE InspServiceHandle = 0, SCMHandle = 0; - TCHAR modname[MAX_PATH]; - GetModuleFileName(NULL, modname, sizeof(modname)); + try { + TCHAR tszBinaryPath[MAX_PATH]; + if(!GetModuleFileName(NULL, tszBinaryPath, _countof(tszBinaryPath))) + { + throw CWin32Exception(); + } - scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE); - if (!scm) - { - std::cout << "Unable to open service control manager: " << RetrieveLastError() << std::endl; - return; - } + SCMHandle = OpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS); + if (!SCMHandle) + { + throw CWin32Exception(); + } - myService = CreateService(scm,TEXT("InspIRCd"),TEXT("Inspire IRC Daemon"), SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, - SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, modname, 0, 0, 0, NULL, NULL); + InspServiceHandle = CreateService(SCMHandle, TEXT("InspIRCd"),TEXT("InspIRCd Daemon"), SERVICE_CHANGE_CONFIG, SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, tszBinaryPath, 0, 0, 0, TEXT("NT AUTHORITY\\NetworkService"), NULL); - if (!myService) - { - std::cout << "Unable to create service: " << RetrieveLastError() << std::endl; - CloseServiceHandle(scm); - return; - } + if (!InspServiceHandle) + { + throw CWin32Exception(); + } - // *** Set service description *** - // this is supported from 5.0 (win2k) onwards only, so we can't link to the definition of - // this function in advapi32.lib, otherwise the program will not run on windows NT 4. We - // must use LoadLibrary and GetProcAddress to export the function name from advapi32.dll - advapi32 = LoadLibrary(TEXT("advapi32.dll")); - if (advapi32) - { - ChangeServiceConf = (SETSERVDESC)GetProcAddress(advapi32,"ChangeServiceConfig2A"); - if (ChangeServiceConf) + TCHAR tszDescription[] = TEXT("The InspIRCd service hosts IRC channels and conversations. If this service is stopped, the IRC server will be unavailable."); + SERVICE_DESCRIPTION svDescription = { tszDescription }; + if(!ChangeServiceConfig2(InspServiceHandle, SERVICE_CONFIG_DESCRIPTION, &svDescription)) { - TCHAR desc[] = TEXT("The Inspire Internet Relay Chat Daemon hosts IRC channels and conversations.\ - If this service is stopped, the IRC server will not run."); - svDesc.lpDescription = desc; - BOOL success = ChangeServiceConf(myService,SERVICE_CONFIG_DESCRIPTION, &svDesc); - if (!success) - { - std::cout << "Unable to set service description: " << RetrieveLastError() << std::endl; - CloseServiceHandle(myService); - CloseServiceHandle(scm); - return; - } + throw CWin32Exception(); } - FreeLibrary(advapi32); + + CloseServiceHandle(InspServiceHandle); + CloseServiceHandle(SCMHandle); + std::cout << "Service installed." << std::endl; } + catch(CWin32Exception e) + { + if(InspServiceHandle) + CloseServiceHandle(InspServiceHandle); + + if(SCMHandle) + CloseServiceHandle(SCMHandle); - std::cout << "Service installed." << std::endl; - CloseServiceHandle(myService); - CloseServiceHandle(scm); + std::cout << "Service installation failed: " << e.what() << std::endl; + } } /** Remove the windows service. This requires administrator privileges. */ -void RemoveService() +void UninstallService() { - SC_HANDLE myService, scm; + SC_HANDLE InspServiceHandle = 0, SCMHandle = 0; - scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE); - if (!scm) + try { - std::cout << "Unable to open service control manager: " << RetrieveLastError() << std::endl; - return; - } + SCMHandle = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, DELETE); + if (!SCMHandle) + throw CWin32Exception(); - myService = OpenService(scm,TEXT("InspIRCd"),SERVICE_ALL_ACCESS); - if (!myService) - { - std::cout << "Unable to open service: " << RetrieveLastError() << std::endl; - CloseServiceHandle(scm); - return; - } + InspServiceHandle = OpenService(SCMHandle, TEXT("InspIRCd"), DELETE); + if (!InspServiceHandle) + throw CWin32Exception(); - if (!DeleteService(myService)) - { - std::cout << "Unable to delete service: " << RetrieveLastError() << std::endl; - CloseServiceHandle(myService); - CloseServiceHandle(scm); - return; + if (!DeleteService(InspServiceHandle) && GetLastError() != ERROR_SERVICE_MARKED_FOR_DELETE) + { + throw CWin32Exception(); + } + + CloseServiceHandle(InspServiceHandle); + CloseServiceHandle(SCMHandle); + std::cout << "Service removed." << std::endl; } + catch(CWin32Exception e) + { + if(InspServiceHandle) + CloseServiceHandle(InspServiceHandle); + + if(SCMHandle) + CloseServiceHandle(SCMHandle); - std::cout << "Service removed." << std::endl; - CloseServiceHandle(myService); - CloseServiceHandle(scm); + std::cout << "Service deletion failed: " << e.what() << std::endl; + } } /* In windows, our main() flows through here, before calling the 'real' main, smain() in inspircd.cpp */ -int main(int argc, char** argv) +int main(int argc, char* argv[]) { - /* List of parameters and handlers */ - Commandline params[] = { - { "--installservice", InstallService }, - { "--removeservice", RemoveService }, - { NULL } - }; - /* Check for parameters */ if (argc > 1) { - for (int z = 0; params[z].Switch; ++z) + for (int i = 1; i < argc; i++) { - if (!_stricmp(argv[1], params[z].Switch)) + if(!_stricmp(argv[i], "--installservice")) + { + InstallService(); + return 0; + } + if(!_stricmp(argv[i], "--uninstallservice") || !_stricmp(argv[i], "--removeservice")) { - params[z].Handler(); + UninstallService(); return 0; } } } - /* First, check if the service is installed. - * if it is not, or we're starting as non-administrator, - * just call smain() and start as normal non-service - * process. - */ - SC_HANDLE myService, scm; - scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE); - if (scm) + SERVICE_TABLE_ENTRY serviceTable[] = { - myService = OpenService(scm,TEXT("InspIRCd"),SERVICE_ALL_ACCESS); - if (!myService) + { TEXT("InspIRCd"), (LPSERVICE_MAIN_FUNCTION)ServiceMain }, + { NULL, NULL } + }; + + g_bRunningAsService = true; + if( !StartServiceCtrlDispatcher(serviceTable) ) + { + // This error means that the program was not started as service. + if( GetLastError() == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT ) { - /* Service not installed or no permission to modify it */ - CloseServiceHandle(scm); + g_bRunningAsService = false; return smain(argc, argv); } + else + { + return EXIT_STATUS_INTERNAL; + } } - else - { - /* Not enough privileges to open the SCM */ - return smain(argc, argv); - } - - CloseServiceHandle(myService); - CloseServiceHandle(scm); - - /* Check if the process is running interactively. InspIRCd does not run interactively - * as a service so if this is true, we just run the non-service inspircd. - */ - if (!IsAService()) - return smain(argc, argv); - - /* If we get here, we know the service is installed so we can start it */ - - SERVICE_TABLE_ENTRY serviceTable[] = - { - {TEXT("InspIRCd"), (LPSERVICE_MAIN_FUNCTION) ServiceMain }, - {NULL, NULL} - }; - - StartServiceCtrlDispatcher(serviceTable); return 0; } diff --git a/win/win32service.h b/win/win32service.h index b33a53c28..e4500be13 100644 --- a/win/win32service.h +++ b/win/win32service.h @@ -15,12 +15,11 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef WIN32SERVICE_H -#define WIN32SERVICE_H +#pragma once +#ifdef _WIN32 /* Hook for win32service.cpp to exit properly with the service specific error code */ -void SetServiceStopped(int status); +void SetServiceStopped(unsigned long dwStatus); /* Marks the service as running, not called until the config is parsed */ void SetServiceRunning(); |