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