]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - win/win32service.cpp
Update copyrights for 2009.
[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://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 "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         DWORD type=0, size=0;
185
186         serviceStatusHandle = RegisterServiceCtrlHandler("InspIRCd", (LPHANDLER_FUNCTION)ServiceCtrlHandler);
187         if (!serviceStatusHandle)
188         {
189                 terminateService(EXIT_STATUS_RSCH_FAILED, GetLastError());
190                 return;
191         }
192
193         success = UpdateSCMStatus(SERVICE_START_PENDING, NO_ERROR, 0, 1, 1000);
194         if (!success)
195         {
196                 terminateService(EXIT_STATUS_UPDATESCM_FAILED, GetLastError());
197                 return;
198         }
199
200         killServiceEvent = CreateEvent(NULL, true, false, NULL);
201         hThreadEvent = CreateEvent(NULL, true, false, NULL);
202
203         if (!killServiceEvent || !hThreadEvent)
204         {
205                 terminateService(EXIT_STATUS_CREATE_EVENT_FAILED, GetLastError());
206                 return;
207         }
208
209         success = UpdateSCMStatus(SERVICE_START_PENDING, NO_ERROR, 0, 2, 1000);
210         if (!success)
211         {
212                 terminateService(EXIT_STATUS_UPDATESCM_FAILED, GetLastError());
213                 return;
214         }
215
216         StartServiceThread();
217         WaitForSingleObject (killServiceEvent, INFINITE);
218 }
219
220 /** Install the windows service. This requires administrator privileges. */
221 void InstallService()
222 {
223         SC_HANDLE myService, scm;
224         SERVICE_DESCRIPTION svDesc;
225         HINSTANCE advapi32;
226
227         char modname[MAX_PATH];
228         GetModuleFileName(NULL, modname, sizeof(modname));
229
230         scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE);
231         if (!scm)
232         {
233                 printf("Unable to open service control manager: %s\n", dlerror());
234                 return;
235         }
236
237         myService = CreateService(scm,"InspIRCd","Inspire IRC Daemon", SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
238                 SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, modname, 0, 0, 0, NULL, NULL);
239
240         if (!myService)
241         {
242                 printf("Unable to create service: %s\n", dlerror());
243                 CloseServiceHandle(scm);
244                 return;
245         }
246
247         // *** Set service description ***
248         // this is supported from 5.0 (win2k) onwards only, so we can't link to the definition of
249         // this function in advapi32.lib, otherwise the program will not run on windows NT 4. We
250         // must use LoadLibrary and GetProcAddress to export the function name from advapi32.dll
251         advapi32 = LoadLibrary("advapi32.dll");
252         if (advapi32)
253         {
254                 ChangeServiceConf = (SETSERVDESC)GetProcAddress(advapi32,"ChangeServiceConfig2A");
255                 if (ChangeServiceConf)
256                 {
257                         char desc[] = "The Inspire Internet Relay Chat Daemon hosts IRC channels and conversations.\
258  If this service is stopped, the IRC server will not run.";
259                         svDesc.lpDescription = desc;
260                         BOOL success = ChangeServiceConf(myService,SERVICE_CONFIG_DESCRIPTION, &svDesc);
261                         if (!success)
262                         {
263                                 printf("Unable to set service description: %s\n", dlerror());
264                                 CloseServiceHandle(myService);
265                                 CloseServiceHandle(scm);
266                                 return;
267                         }
268                 }
269                 FreeLibrary(advapi32);
270         }
271
272         printf("Service installed.\n");
273         CloseServiceHandle(myService);
274         CloseServiceHandle(scm);
275 }
276
277 /** Remove the windows service. This requires administrator privileges. */
278 void RemoveService()
279 {
280         SC_HANDLE myService, scm;
281
282         scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE);
283         if (!scm)
284         {
285                 printf("Unable to open service control manager: %s\n", dlerror());
286                 return;
287         }
288
289         myService = OpenService(scm,"InspIRCd",SERVICE_ALL_ACCESS);
290         if (!myService)
291         {
292                 printf("Unable to open service: %s\n", dlerror());
293                 CloseServiceHandle(scm);
294                 return;
295         }
296
297         if (!DeleteService(myService))
298         {
299                 printf("Unable to delete service: %s\n", dlerror());
300                 CloseServiceHandle(myService);
301                 CloseServiceHandle(scm);
302                 return;
303         }
304
305         printf("Service removed.\n");
306         CloseServiceHandle(myService);
307         CloseServiceHandle(scm);
308 }
309
310 /* In windows, our main() flows through here, before calling the 'real' main, smain() in inspircd.cpp */
311 int main(int argc, char** argv)
312 {
313         /* List of parameters and handlers */
314         Commandline params[] = {
315                 { "--installservice", InstallService },
316                 { "--removeservice", RemoveService },
317                 { NULL }
318         };
319
320         /* Check for parameters */
321         if (argc > 1)
322         {
323                 for (int z = 0; params[z].Switch; ++z)
324                 {
325                         if (!_stricmp(argv[1], params[z].Switch))
326                         {
327                                 params[z].Handler();
328                                 return 0;
329                         }
330                 }
331         }
332
333         /* First, check if the service is installed.
334          * if it is not, or we're starting as non-administrator,
335          * just call smain() and start as normal non-service
336          * process.
337          */
338         SC_HANDLE myService, scm;
339         scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE);
340         if (scm)
341         {
342                 myService = OpenService(scm,"InspIRCd",SERVICE_ALL_ACCESS);
343                 if (!myService)
344                 {
345                         /* Service not installed or no permission to modify it */
346                         CloseServiceHandle(scm);
347                         return smain(argc, argv);
348                 }
349         }
350         else
351         {
352                 /* Not enough privileges to open the SCM */
353                 return smain(argc, argv);
354         }
355
356         CloseServiceHandle(myService);
357         CloseServiceHandle(scm);
358
359         /* Check if the process is running interactively. InspIRCd does not run interactively
360          * as a service so if this is true, we just run the non-service inspircd.
361          */
362         if (!IsAService())
363                 return smain(argc, argv);
364
365         /* If we get here, we know the service is installed so we can start it */
366
367         SERVICE_TABLE_ENTRY serviceTable[] =
368         {
369                 {"InspIRCd", (LPSERVICE_MAIN_FUNCTION) ServiceMain },
370                 {NULL, NULL}
371         };
372
373         StartServiceCtrlDispatcher(serviceTable);
374         return 0;
375 }