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