]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - win/win32service.cpp
Hooray unused variables. Thanks MSVC.
[user/henk/code/inspircd.git] / win / win32service.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2009 InspIRCd Development Team
6  * See: http://wiki.inspircd.org/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 "exitcodes.h"
16 #include <windows.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <stdio.h>
20
21 static SERVICE_STATUS_HANDLE serviceStatusHandle;
22 static HANDLE hThreadEvent;
23 static HANDLE killServiceEvent;
24 static int serviceCurrentStatus;
25
26 /** This is used to define ChangeServiceConf2() as we can't link
27  * directly against this symbol (see below where it is used)
28  */
29 typedef BOOL (CALLBACK* SETSERVDESC)(SC_HANDLE,DWORD,LPVOID);
30
31 BOOL UpdateSCMStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwServiceSpecificExitCode, DWORD dwCheckPoint, DWORD dwWaitHint);
32 void terminateService(int code, int wincode);
33
34 /* A commandline parameter handler for service specific commandline parameters */
35 typedef void (*CommandlineParameterHandler)(void);
36
37 /* Represents a commandline and its handler */
38 struct Commandline
39 {
40         const char* Switch;
41         CommandlineParameterHandler Handler;
42 };
43
44 /* A function pointer for dynamic linking tricks */
45 SETSERVDESC ChangeServiceConf;
46
47 /* Returns true if this program is running as a service, false if it is running interactive */
48 bool IsAService()
49 {
50         USEROBJECTFLAGS uoflags;
51         HWINSTA winstation = GetProcessWindowStation();
52         if (GetUserObjectInformation(winstation, UOI_FLAGS, &uoflags, sizeof(uoflags), NULL))
53                 return ((uoflags.dwFlags & WSF_VISIBLE) == 0);
54         else
55                 return false;
56 }
57
58 /* Kills the service by setting an event which the other thread picks up and exits */
59 void KillService()
60 {
61         SetEvent(hThreadEvent);
62         Sleep(2000);
63         SetEvent(killServiceEvent);
64 }
65
66 /** The main part of inspircd runs within this thread function. This allows the service part to run
67  * seperately on its own and to be able to kill the worker thread when its time to quit.
68  */
69 DWORD WINAPI WorkerThread(LPDWORD param)
70 {
71         char modname[MAX_PATH];
72         GetModuleFileName(NULL, modname, sizeof(modname));
73         char* argv[] = { modname, "--nofork" };
74         smain(2, argv);
75         KillService();
76         return 0;
77 }
78
79 /* This is called when all startup is done */
80 void SetServiceRunning()
81 {
82         if (!IsAService())
83                 return;
84
85         serviceCurrentStatus = SERVICE_RUNNING;
86         BOOL success = UpdateSCMStatus(SERVICE_RUNNING, NO_ERROR, 0, 0, 0);
87         if (!success)
88         {
89                 terminateService(EXIT_STATUS_UPDATESCM_FAILED, GetLastError());
90                 return;
91         }
92 }
93
94
95 /** Starts the worker thread above */
96 void StartServiceThread()
97 {
98         DWORD dwd;
99         CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)WorkerThread,NULL,0,&dwd);
100 }
101
102 /** This function updates the status of the service in the SCM
103  * (service control manager, the services.msc applet)
104  */
105 BOOL UpdateSCMStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwServiceSpecificExitCode, DWORD dwCheckPoint, DWORD dwWaitHint)
106 {
107         BOOL success;
108         SERVICE_STATUS serviceStatus;
109         serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
110         serviceStatus.dwCurrentState = dwCurrentState;
111
112         if (dwCurrentState == SERVICE_START_PENDING)
113         {
114                 serviceStatus.dwControlsAccepted = 0;
115         }
116         else
117         {
118                 serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
119         }
120
121         if (dwServiceSpecificExitCode == 0)
122         {
123                 serviceStatus.dwWin32ExitCode = dwWin32ExitCode;
124         }
125         else
126         {
127                 serviceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
128         }
129         serviceStatus.dwServiceSpecificExitCode =   dwServiceSpecificExitCode;
130         serviceStatus.dwCheckPoint = dwCheckPoint;
131         serviceStatus.dwWaitHint = dwWaitHint;
132
133         success = SetServiceStatus (serviceStatusHandle, &serviceStatus);
134         if (!success)
135         {
136                 KillService();
137         }
138         return success;
139 }
140
141 /** This function is called by us when the service is being shut down or when it can't be started */
142 void terminateService(int code, int wincode)
143 {
144         UpdateSCMStatus(SERVICE_STOPPED, wincode ? wincode : ERROR_SERVICE_SPECIFIC_ERROR, wincode ? 0 : code, 0, 0);
145         return;
146 }
147
148 /* In windows we hook this to InspIRCd::Exit() */
149 void SetServiceStopped(int status)
150 {
151         if (!IsAService())
152                 exit(status);
153
154         /* Are we running as a service? If so, trigger the service specific exit code */
155         terminateService(status, 0);
156         KillService();
157         exit(status);
158 }
159
160 /** This callback is called by windows when the state of the service has been changed */
161 VOID ServiceCtrlHandler(DWORD controlCode)
162 {
163         switch(controlCode)
164         {
165                 case SERVICE_CONTROL_INTERROGATE:
166                 break;
167                 case SERVICE_CONTROL_SHUTDOWN:
168                 case SERVICE_CONTROL_STOP:
169                         serviceCurrentStatus = SERVICE_STOP_PENDING;
170                         UpdateSCMStatus(SERVICE_STOP_PENDING, NO_ERROR, 0, 1, 5000);
171                         KillService();
172                         UpdateSCMStatus(SERVICE_STOPPED, NO_ERROR, 0, 0, 0);
173                         return;
174                 default:
175                 break;
176         }
177         UpdateSCMStatus(serviceCurrentStatus, NO_ERROR, 0, 0, 0);
178 }
179
180 /** This callback is called by windows when the service is started */
181 VOID ServiceMain(DWORD argc, LPTSTR *argv)
182 {
183         BOOL success;
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 }