]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - win/win32service.cpp
Some comments
[user/henk/code/inspircd.git] / win / win32service.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2008 InspIRCd Development Team
6  * See: http://www.inspircd.org/wiki/index.php/Credits
7  *
8  * This program is free but copyrighted software; see
9  *          the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  */
13 #include "inspircd_config.h"
14 #include "inspircd.h"
15 #include <windows.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <stdio.h>
19
20 static SERVICE_STATUS_HANDLE serviceStatusHandle;
21 static HANDLE hThreadEvent;
22 static HANDLE killServiceEvent;
23 static int serviceCurrentStatus;
24
25 /** This is used to define ChangeServiceConf2() as we can't link
26  * directly against this symbol (see below where it is used)
27  */
28 typedef BOOL (CALLBACK* SETSERVDESC)(SC_HANDLE,DWORD,LPVOID);
29
30 BOOL UpdateSCMStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwServiceSpecificExitCode, DWORD dwCheckPoint, DWORD dwWaitHint);
31 void terminateService(int code, int wincode);
32
33 /* A commandline parameter handler for service specific commandline parameters */
34 typedef void (*CommandlineParameterHandler)(void);
35
36 /* Represents a commandline and its handler */
37 struct Commandline
38 {
39         const char* Switch;
40         CommandlineParameterHandler Handler;
41 };
42
43 /* A function pointer for dynamic linking tricks */
44 SETSERVDESC ChangeServiceConf;
45
46 /* Returns true if this program is running as a service, false if it is running interactive */
47 bool IsAService()
48 {
49         USEROBJECTFLAGS uoflags;
50         HWINSTA winstation = GetProcessWindowStation();
51         if (GetUserObjectInformation(winstation, UOI_FLAGS, &uoflags, sizeof(uoflags), NULL))
52                 return ((uoflags.dwFlags & WSF_VISIBLE) == 0);
53         else
54                 return false;
55 }
56
57 /* Kills the service by setting an event which the other thread picks up and exits */
58 void KillService()
59 {
60         SetEvent(hThreadEvent);
61         Sleep(2000);
62         SetEvent(killServiceEvent);
63 }
64
65 /** The main part of inspircd runs within this thread function. This allows the service part to run
66  * seperately on its own and to be able to kill the worker thread when its time to quit.
67  */
68 DWORD WINAPI WorkerThread(LPDWORD param)
69 {
70         char modname[MAX_PATH];
71         GetModuleFileName(NULL, modname, sizeof(modname));
72         char* argv[] = { modname, "--nofork", "--debug" };
73         smain(3, argv);
74         KillService();
75         return 0;
76 }
77
78 /* This is called when all startup is done */
79 void SetServiceRunning()
80 {
81         if (!IsAService())
82                 return;
83
84         serviceCurrentStatus = SERVICE_RUNNING;
85         BOOL success = UpdateSCMStatus(SERVICE_RUNNING, NO_ERROR, 0, 0, 0);
86         if (!success)
87         {
88                 terminateService(EXIT_STATUS_UPDATESCM_FAILED, GetLastError());
89                 return;
90         }
91 }
92
93
94 /** Starts the worker thread above */
95 void StartServiceThread()
96 {
97         DWORD dwd;
98         CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)WorkerThread,NULL,0,&dwd);
99 }
100
101 /** This function updates the status of the service in the SCM
102  * (service control manager, the services.msc applet)
103  */
104 BOOL UpdateSCMStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwServiceSpecificExitCode, DWORD dwCheckPoint, DWORD dwWaitHint)
105 {
106         BOOL success;
107         SERVICE_STATUS serviceStatus;
108         serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
109         serviceStatus.dwCurrentState = dwCurrentState;
110
111         if (dwCurrentState == SERVICE_START_PENDING)
112         {
113                 serviceStatus.dwControlsAccepted = 0;
114         }
115         else
116         {
117                 serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
118         }
119
120         if (dwServiceSpecificExitCode == 0)
121         {
122                 serviceStatus.dwWin32ExitCode = dwWin32ExitCode;
123         }
124         else
125         {
126                 serviceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
127         }
128         serviceStatus.dwServiceSpecificExitCode =   dwServiceSpecificExitCode;
129         serviceStatus.dwCheckPoint = dwCheckPoint;
130         serviceStatus.dwWaitHint = dwWaitHint;
131
132         success = SetServiceStatus (serviceStatusHandle, &serviceStatus);
133         if (!success)
134         {
135                 KillService();
136         }
137         return success;
138 }
139
140 /** This function is called by us when the service is being shut down or when it can't be started */
141 void terminateService(int code, int wincode)
142 {
143         UpdateSCMStatus(SERVICE_STOPPED, wincode ? wincode : ERROR_SERVICE_SPECIFIC_ERROR, wincode ? 0 : code, 0, 0);
144         return;
145 }
146
147 /* In windows we hook this to InspIRCd::Exit() */
148 void SetServiceStopped(int status)
149 {
150         if (!IsAService())
151                 exit(status);
152
153         /* Are we running as a service? If so, trigger the service specific exit code */
154         terminateService(status, 0);
155         KillService();
156         exit(status);
157 }
158
159 /** This callback is called by windows when the state of the service has been changed */
160 VOID ServiceCtrlHandler(DWORD controlCode)
161 {
162         switch(controlCode)
163         {
164                 case SERVICE_CONTROL_INTERROGATE:
165                 break;
166                 case SERVICE_CONTROL_SHUTDOWN:
167                 case SERVICE_CONTROL_STOP:
168                         serviceCurrentStatus = SERVICE_STOP_PENDING;
169                         UpdateSCMStatus(SERVICE_STOP_PENDING, NO_ERROR, 0, 1, 5000);
170                         KillService();
171                         UpdateSCMStatus(SERVICE_STOPPED, NO_ERROR, 0, 0, 0);
172                         return;
173                 default:
174                 break;
175         }
176         UpdateSCMStatus(serviceCurrentStatus, NO_ERROR, 0, 0, 0);
177 }
178
179 /** This callback is called by windows when the service is started */
180 VOID ServiceMain(DWORD argc, LPTSTR *argv)
181 {
182         BOOL success;
183         DWORD type=0, size=0;
184
185         serviceStatusHandle = RegisterServiceCtrlHandler("InspIRCd", (LPHANDLER_FUNCTION)ServiceCtrlHandler);
186         if (!serviceStatusHandle)
187         {
188                 terminateService(EXIT_STATUS_RSCH_FAILED, GetLastError());
189                 return;
190         }
191
192         success = UpdateSCMStatus(SERVICE_START_PENDING, NO_ERROR, 0, 1, 1000);
193         if (!success)
194         {
195                 terminateService(EXIT_STATUS_UPDATESCM_FAILED, GetLastError());
196                 return;
197         }
198
199         killServiceEvent = CreateEvent(NULL, true, false, NULL);
200         hThreadEvent = CreateEvent(NULL, true, false, NULL);
201
202         if (!killServiceEvent || !hThreadEvent)
203         {
204                 terminateService(EXIT_STATUS_CREATE_EVENT_FAILED, GetLastError());
205                 return;
206         }
207
208         success = UpdateSCMStatus(SERVICE_START_PENDING, NO_ERROR, 0, 2, 1000);
209         if (!success)
210         {
211                 terminateService(EXIT_STATUS_UPDATESCM_FAILED, GetLastError());
212                 return;
213         }
214
215         StartServiceThread();
216         WaitForSingleObject (killServiceEvent, INFINITE);
217 }
218
219 /** Install the windows service. This requires administrator privileges. */
220 void InstallService()
221 {
222         SC_HANDLE myService, scm;
223         SERVICE_DESCRIPTION svDesc;
224         HINSTANCE advapi32;
225
226         char modname[MAX_PATH];
227         GetModuleFileName(NULL, modname, sizeof(modname));
228
229         scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE);
230         if (!scm)
231         {
232                 printf("Unable to open service control manager: %s\n", dlerror());
233                 return;
234         }
235
236         myService = CreateService(scm,"InspIRCd","Inspire IRC Daemon", SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
237                 SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, modname, 0, 0, 0, NULL, NULL);
238
239         if (!myService)
240         {
241                 printf("Unable to create service: %s\n", dlerror());
242                 CloseServiceHandle(scm);
243                 return;
244         }
245
246         // *** Set service description ***
247         // this is supported from 5.0 (win2k) onwards only, so we can't link to the definition of
248         // this function in advapi32.lib, otherwise the program will not run on windows NT 4. We
249         // must use LoadLibrary and GetProcAddress to export the function name from advapi32.dll
250         advapi32 = LoadLibrary("advapi32.dll");
251         if (advapi32)
252         {
253                 ChangeServiceConf = (SETSERVDESC)GetProcAddress(advapi32,"ChangeServiceConfig2A");
254                 if (ChangeServiceConf)
255                 {
256                         char desc[] = "The Inspire Internet Relay Chat Daemon hosts IRC channels and conversations.\
257  If this service is stopped, the IRC server will not run.";
258                         svDesc.lpDescription = desc;
259                         BOOL success = ChangeServiceConf(myService,SERVICE_CONFIG_DESCRIPTION, &svDesc);
260                         if (!success)
261                         {
262                                 printf("Unable to set service description: %s\n", dlerror());
263                                 CloseServiceHandle(myService);
264                                 CloseServiceHandle(scm);
265                                 return;
266                         }
267                 }
268                 FreeLibrary(advapi32);
269         }
270
271         printf("Service installed.\n");
272         CloseServiceHandle(myService);
273         CloseServiceHandle(scm);
274 }
275
276 /** Remove the windows service. This requires administrator privileges. */
277 void RemoveService()
278 {
279         SC_HANDLE myService, scm;
280
281         scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE);
282         if (!scm)
283         {
284                 printf("Unable to open service control manager: %s\n", dlerror());
285                 return;
286         }
287
288         myService = OpenService(scm,"InspIRCd",SERVICE_ALL_ACCESS);
289         if (!myService)
290         {
291                 printf("Unable to open service: %s\n", dlerror());
292                 CloseServiceHandle(scm);
293                 return;
294         }
295
296         if (!DeleteService(myService))
297         {
298                 printf("Unable to delete service: %s\n", dlerror());
299                 CloseServiceHandle(myService);
300                 CloseServiceHandle(scm);
301                 return;
302         }
303
304         printf("Service removed.\n");
305         CloseServiceHandle(myService);
306         CloseServiceHandle(scm);
307 }
308
309 /* In windows, our main() flows through here, before calling the 'real' main, smain() in inspircd.cpp */
310 int main(int argc, char** argv)
311 {
312         /* List of parameters and handlers */
313         Commandline params[] = {
314                 { "--installservice", InstallService },
315                 { "--removeservice", RemoveService },
316                 { NULL }
317         };
318
319         /* Check for parameters */
320         if (argc > 1)
321         {
322                 for (int z = 0; params[z].Switch; ++z)
323                 {
324                         if (!_stricmp(argv[1], params[z].Switch))
325                         {
326                                 params[z].Handler();
327                                 return 0;
328                         }
329                 }
330         }
331
332         /* First, check if the service is installed.
333          * if it is not, or we're starting as non-administrator,
334          * just call smain() and start as normal non-service
335          * process.
336          */
337         SC_HANDLE myService, scm;
338         scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE);
339         if (scm)
340         {
341                 myService = OpenService(scm,"InspIRCd",SERVICE_ALL_ACCESS);
342                 if (!myService)
343                 {
344                         /* Service not installed or no permission to modify it */
345                         CloseServiceHandle(scm);
346                         return smain(argc, argv);
347                 }
348         }
349         else
350         {
351                 /* Not enough privileges to open the SCM */
352                 return smain(argc, argv);
353         }
354
355         CloseServiceHandle(myService);
356         CloseServiceHandle(scm);
357
358         /* Check if the process is running interactively. InspIRCd does not run interactively
359          * as a service so if this is true, we just run the non-service inspircd.
360          */
361         if (!IsAService())
362                 return smain(argc, argv);
363
364         /* If we get here, we know the service is installed so we can start it */
365
366         SERVICE_TABLE_ENTRY serviceTable[] =
367         {
368                 {"InspIRCd", (LPSERVICE_MAIN_FUNCTION) ServiceMain },
369                 {NULL, NULL}
370         };
371
372         StartServiceCtrlDispatcher(serviceTable);
373         return 0;
374 }