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