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