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