2 * InspIRCd -- Internet Relay Chat Daemon
4 * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc>
6 * This file is part of InspIRCd. InspIRCd is free software: you can
7 * redistribute it and/or modify it under the terms of the GNU General Public
8 * License as published by the Free Software Foundation, version 2.
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "inspircd_config.h"
22 #include "exitcodes.h"
29 static SERVICE_STATUS_HANDLE serviceStatusHandle;
30 static HANDLE hThreadEvent;
31 static HANDLE killServiceEvent;
32 static int serviceCurrentStatus;
34 /** This is used to define ChangeServiceConf2() as we can't link
35 * directly against this symbol (see below where it is used)
37 typedef BOOL (CALLBACK* SETSERVDESC)(SC_HANDLE,DWORD,LPVOID);
39 BOOL UpdateSCMStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwServiceSpecificExitCode, DWORD dwCheckPoint, DWORD dwWaitHint);
40 void terminateService(int code, int wincode);
42 /* A commandline parameter handler for service specific commandline parameters */
43 typedef void (*CommandlineParameterHandler)(void);
45 /* Represents a commandline and its handler */
49 CommandlineParameterHandler Handler;
52 /* A function pointer for dynamic linking tricks */
53 SETSERVDESC ChangeServiceConf;
55 LPCSTR RetrieveLastError()
58 DWORD LastError = GetLastError();
59 if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, 0, LastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)err, sizeof(err), 0) == 0)
60 snprintf(err, sizeof(err), "Error code: %d", LastError);
61 SetLastError(ERROR_SUCCESS);
65 /* Returns true if this program is running as a service, false if it is running interactive */
68 USEROBJECTFLAGS uoflags;
69 HWINSTA winstation = GetProcessWindowStation();
70 if (GetUserObjectInformation(winstation, UOI_FLAGS, &uoflags, sizeof(uoflags), NULL))
71 return ((uoflags.dwFlags & WSF_VISIBLE) == 0);
76 /* Kills the service by setting an event which the other thread picks up and exits */
79 SetEvent(hThreadEvent);
81 SetEvent(killServiceEvent);
84 /** The main part of inspircd runs within this thread function. This allows the service part to run
85 * seperately on its own and to be able to kill the worker thread when its time to quit.
87 DWORD WINAPI WorkerThread(LPDWORD param)
89 char modname[MAX_PATH];
90 GetModuleFileNameA(NULL, modname, sizeof(modname));
91 char* argv[] = { modname, "--nofork" };
97 /* This is called when all startup is done */
98 void SetServiceRunning()
103 serviceCurrentStatus = SERVICE_RUNNING;
104 BOOL success = UpdateSCMStatus(SERVICE_RUNNING, NO_ERROR, 0, 0, 0);
107 terminateService(EXIT_STATUS_UPDATESCM_FAILED, GetLastError());
113 /** Starts the worker thread above */
114 void StartServiceThread()
116 HANDLE hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)WorkerThread,NULL,0,NULL);
118 CloseHandle(hThread);
121 /** This function updates the status of the service in the SCM
122 * (service control manager, the services.msc applet)
124 BOOL UpdateSCMStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwServiceSpecificExitCode, DWORD dwCheckPoint, DWORD dwWaitHint)
127 SERVICE_STATUS serviceStatus;
128 serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
129 serviceStatus.dwCurrentState = dwCurrentState;
131 if (dwCurrentState == SERVICE_START_PENDING)
133 serviceStatus.dwControlsAccepted = 0;
137 serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
140 if (dwServiceSpecificExitCode == 0)
142 serviceStatus.dwWin32ExitCode = dwWin32ExitCode;
146 serviceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
148 serviceStatus.dwServiceSpecificExitCode = dwServiceSpecificExitCode;
149 serviceStatus.dwCheckPoint = dwCheckPoint;
150 serviceStatus.dwWaitHint = dwWaitHint;
152 success = SetServiceStatus (serviceStatusHandle, &serviceStatus);
160 /** This function is called by us when the service is being shut down or when it can't be started */
161 void terminateService(int code, int wincode)
163 UpdateSCMStatus(SERVICE_STOPPED, wincode ? wincode : ERROR_SERVICE_SPECIFIC_ERROR, wincode ? 0 : code, 0, 0);
167 /* In windows we hook this to InspIRCd::Exit() */
168 void SetServiceStopped(int status)
173 /* Are we running as a service? If so, trigger the service specific exit code */
174 terminateService(status, 0);
179 /** This callback is called by windows when the state of the service has been changed */
180 VOID ServiceCtrlHandler(DWORD controlCode)
184 case SERVICE_CONTROL_INTERROGATE:
186 case SERVICE_CONTROL_SHUTDOWN:
187 case SERVICE_CONTROL_STOP:
188 serviceCurrentStatus = SERVICE_STOP_PENDING;
189 UpdateSCMStatus(SERVICE_STOP_PENDING, NO_ERROR, 0, 1, 5000);
191 UpdateSCMStatus(SERVICE_STOPPED, NO_ERROR, 0, 0, 0);
196 UpdateSCMStatus(serviceCurrentStatus, NO_ERROR, 0, 0, 0);
199 /** This callback is called by windows when the service is started */
200 VOID ServiceMain(DWORD argc, LPTSTR *argv)
204 serviceStatusHandle = RegisterServiceCtrlHandler(TEXT("InspIRCd"), (LPHANDLER_FUNCTION)ServiceCtrlHandler);
205 if (!serviceStatusHandle)
207 terminateService(EXIT_STATUS_RSCH_FAILED, GetLastError());
211 success = UpdateSCMStatus(SERVICE_START_PENDING, NO_ERROR, 0, 1, 1000);
214 terminateService(EXIT_STATUS_UPDATESCM_FAILED, GetLastError());
218 killServiceEvent = CreateEvent(NULL, true, false, NULL);
219 hThreadEvent = CreateEvent(NULL, true, false, NULL);
221 if (!killServiceEvent || !hThreadEvent)
223 terminateService(EXIT_STATUS_CREATE_EVENT_FAILED, GetLastError());
227 success = UpdateSCMStatus(SERVICE_START_PENDING, NO_ERROR, 0, 2, 1000);
230 terminateService(EXIT_STATUS_UPDATESCM_FAILED, GetLastError());
234 StartServiceThread();
235 WaitForSingleObject (killServiceEvent, INFINITE);
238 /** Install the windows service. This requires administrator privileges. */
239 void InstallService()
241 SC_HANDLE myService, scm;
242 SERVICE_DESCRIPTION svDesc;
245 TCHAR modname[MAX_PATH];
246 GetModuleFileName(NULL, modname, sizeof(modname));
248 scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE);
251 std::cout << "Unable to open service control manager: " << RetrieveLastError() << std::endl;
255 myService = CreateService(scm,TEXT("InspIRCd"),TEXT("Inspire IRC Daemon"), SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
256 SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, modname, 0, 0, 0, NULL, NULL);
260 std::cout << "Unable to create service: " << RetrieveLastError() << std::endl;
261 CloseServiceHandle(scm);
265 // *** Set service description ***
266 // this is supported from 5.0 (win2k) onwards only, so we can't link to the definition of
267 // this function in advapi32.lib, otherwise the program will not run on windows NT 4. We
268 // must use LoadLibrary and GetProcAddress to export the function name from advapi32.dll
269 advapi32 = LoadLibrary(TEXT("advapi32.dll"));
272 ChangeServiceConf = (SETSERVDESC)GetProcAddress(advapi32,"ChangeServiceConfig2A");
273 if (ChangeServiceConf)
275 TCHAR desc[] = TEXT("The Inspire Internet Relay Chat Daemon hosts IRC channels and conversations.\
276 If this service is stopped, the IRC server will not run.");
277 svDesc.lpDescription = desc;
278 BOOL success = ChangeServiceConf(myService,SERVICE_CONFIG_DESCRIPTION, &svDesc);
281 std::cout << "Unable to set service description: " << RetrieveLastError() << std::endl;
282 CloseServiceHandle(myService);
283 CloseServiceHandle(scm);
287 FreeLibrary(advapi32);
290 std::cout << "Service installed." << std::endl;
291 CloseServiceHandle(myService);
292 CloseServiceHandle(scm);
295 /** Remove the windows service. This requires administrator privileges. */
298 SC_HANDLE myService, scm;
300 scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE);
303 std::cout << "Unable to open service control manager: " << RetrieveLastError() << std::endl;
307 myService = OpenService(scm,TEXT("InspIRCd"),SERVICE_ALL_ACCESS);
310 std::cout << "Unable to open service: " << RetrieveLastError() << std::endl;
311 CloseServiceHandle(scm);
315 if (!DeleteService(myService))
317 std::cout << "Unable to delete service: " << RetrieveLastError() << std::endl;
318 CloseServiceHandle(myService);
319 CloseServiceHandle(scm);
323 std::cout << "Service removed." << std::endl;
324 CloseServiceHandle(myService);
325 CloseServiceHandle(scm);
328 /* In windows, our main() flows through here, before calling the 'real' main, smain() in inspircd.cpp */
329 int main(int argc, char** argv)
331 /* List of parameters and handlers */
332 Commandline params[] = {
333 { "--installservice", InstallService },
334 { "--removeservice", RemoveService },
338 /* Check for parameters */
341 for (int z = 0; params[z].Switch; ++z)
343 if (!_stricmp(argv[1], params[z].Switch))
351 /* First, check if the service is installed.
352 * if it is not, or we're starting as non-administrator,
353 * just call smain() and start as normal non-service
356 SC_HANDLE myService, scm;
357 scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE);
360 myService = OpenService(scm,TEXT("InspIRCd"),SERVICE_ALL_ACCESS);
363 /* Service not installed or no permission to modify it */
364 CloseServiceHandle(scm);
365 return smain(argc, argv);
370 /* Not enough privileges to open the SCM */
371 return smain(argc, argv);
374 CloseServiceHandle(myService);
375 CloseServiceHandle(scm);
377 /* Check if the process is running interactively. InspIRCd does not run interactively
378 * as a service so if this is true, we just run the non-service inspircd.
381 return smain(argc, argv);
383 /* If we get here, we know the service is installed so we can start it */
385 SERVICE_TABLE_ENTRY serviceTable[] =
387 {TEXT("InspIRCd"), (LPSERVICE_MAIN_FUNCTION) ServiceMain },
391 StartServiceCtrlDispatcher(serviceTable);