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