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