Fix event logging.
[nssm.git] / service.cpp
1 #include "nssm.h"\r
2 \r
3 SERVICE_STATUS service_status;\r
4 SERVICE_STATUS_HANDLE service_handle;\r
5 HANDLE process_handle;\r
6 HANDLE wait_handle;\r
7 unsigned long pid;\r
8 static char service_name[SERVICE_NAME_LENGTH];\r
9 char exe[EXE_LENGTH];\r
10 char flags[CMD_LENGTH];\r
11 char dir[MAX_PATH];\r
12 bool stopping;\r
13 CRITICAL_SECTION throttle_section;\r
14 CONDITION_VARIABLE throttle_condition;\r
15 \r
16 static enum { NSSM_EXIT_RESTART, NSSM_EXIT_IGNORE, NSSM_EXIT_REALLY, NSSM_EXIT_UNCLEAN } exit_actions;\r
17 static const char *exit_action_strings[] = { "Restart", "Ignore", "Exit", "Suicide", 0 };\r
18 \r
19 static unsigned long throttle;\r
20 \r
21 static inline int throttle_milliseconds() {\r
22   /* pow() operates on doubles. */\r
23   int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;\r
24   return ret * 1000;\r
25 }\r
26 \r
27 /* Connect to the service manager */\r
28 SC_HANDLE open_service_manager() {\r
29   SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);\r
30   if (! ret) {\r
31     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);\r
32     return 0;\r
33   }\r
34 \r
35   return ret;\r
36 }\r
37 \r
38 /* About to install the service */\r
39 int pre_install_service(int argc, char **argv) {\r
40   /* Show the dialogue box if we didn't give the */\r
41   if (argc < 2) return nssm_gui(IDD_INSTALL, argv[0]);\r
42 \r
43   /* Arguments are optional */\r
44   char *flags;\r
45   size_t flagslen = 0;\r
46   size_t s = 0;\r
47   int i;\r
48   for (i = 2; i < argc; i++) flagslen += strlen(argv[i]) + 1;\r
49   if (! flagslen) flagslen = 1;\r
50 \r
51   flags = (char *) HeapAlloc(GetProcessHeap(), 0, flagslen);\r
52   if (! flags) {\r
53     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "flags", "pre_install_service()", 0);\r
54     return 2;\r
55   }\r
56   ZeroMemory(flags, flagslen);\r
57 \r
58   /*\r
59     This probably isn't UTF8-safe and should use std::string or something\r
60     but it's been broken for the best part of a decade and due for a rewrite\r
61     anyway so it'll do as a quick-'n'-dirty fix.  Note that we don't free\r
62     the flags buffer but as the program exits that isn't a big problem.\r
63   */\r
64   for (i = 2; i < argc; i++) {\r
65     size_t len = strlen(argv[i]);\r
66     memmove(flags + s, argv[i], len);\r
67     s += len;\r
68     if (i < argc - 1) flags[s++] = ' ';\r
69   }\r
70 \r
71   return install_service(argv[0], argv[1], flags);\r
72 }\r
73 \r
74 /* About to remove the service */\r
75 int pre_remove_service(int argc, char **argv) {\r
76   /* Show dialogue box if we didn't pass service name and "confirm" */\r
77   if (argc < 2) return nssm_gui(IDD_REMOVE, argv[0]);\r
78   if (str_equiv(argv[1], "confirm")) return remove_service(argv[0]);\r
79   fprintf(stderr, "To remove a service without confirmation: nssm remove <servicename> confirm\n");\r
80   return 100;\r
81 }\r
82 \r
83 /* Install the service */\r
84 int install_service(char *name, char *exe, char *flags) {\r
85   /* Open service manager */\r
86   SC_HANDLE services = open_service_manager();\r
87   if (! services) {\r
88     fprintf(stderr, "Error opening service manager!\n");\r
89     return 2;\r
90   }\r
91   \r
92   /* Get path of this program */\r
93   char path[MAX_PATH];\r
94   GetModuleFileName(0, path, MAX_PATH);\r
95 \r
96   /* Construct command */\r
97   char command[CMD_LENGTH];\r
98   size_t runlen = strlen(NSSM_RUN);\r
99   size_t pathlen = strlen(path);\r
100   if (pathlen + runlen + 2 >= VALUE_LENGTH) {\r
101     fprintf(stderr, "The full path to " NSSM " is too long!\n");\r
102     return 3;\r
103   }\r
104   if (snprintf(command, sizeof(command), "\"%s\" %s", path, NSSM_RUN) < 0) {\r
105     fprintf(stderr, "Out of memory for ImagePath!\n");\r
106     return 4;\r
107   }\r
108 \r
109   /* Work out directory name */\r
110   size_t len = strlen(exe);\r
111   size_t i;\r
112   for (i = len; i && exe[i] != '\\' && exe[i] != '/'; i--);\r
113   char dir[MAX_PATH];\r
114   memmove(dir, exe, i);\r
115   dir[i] = '\0';\r
116 \r
117   /* Create the service */\r
118   SC_HANDLE service = CreateService(services, name, name, SC_MANAGER_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, command, 0, 0, 0, 0, 0);\r
119   if (! service) {\r
120     fprintf(stderr, "Error creating service!\n");\r
121     CloseServiceHandle(services);\r
122     return 5;\r
123   }\r
124 \r
125   /* Now we need to put the parameters into the registry */\r
126   if (create_parameters(name, exe, flags, dir)) {\r
127     fprintf(stderr, "Error setting startup parameters for the service!\n");\r
128     DeleteService(service);\r
129     CloseServiceHandle(services);\r
130     return 6;\r
131   }\r
132 \r
133   /* Cleanup */\r
134   CloseServiceHandle(service);\r
135   CloseServiceHandle(services);\r
136 \r
137   printf("Service \"%s\" installed successfully!\n", name);\r
138   return 0;\r
139 }\r
140 \r
141 /* Remove the service */\r
142 int remove_service(char *name) {\r
143   /* Open service manager */\r
144   SC_HANDLE services = open_service_manager();\r
145   if (! services) {\r
146     fprintf(stderr, "Error opening service manager!\n");\r
147     return 2;\r
148   }\r
149   \r
150   /* Try to open the service */\r
151   SC_HANDLE service = OpenService(services, name, SC_MANAGER_ALL_ACCESS);\r
152   if (! service) {\r
153     fprintf(stderr, "Can't open service!");\r
154     CloseServiceHandle(services);\r
155     return 3;\r
156   }\r
157 \r
158   /* Try to delete the service */\r
159   if (! DeleteService(service)) {\r
160     fprintf(stderr, "Error deleting service!\n");\r
161     CloseServiceHandle(service);\r
162     CloseServiceHandle(services);\r
163     return 4;\r
164   }\r
165 \r
166   /* Cleanup */\r
167   CloseServiceHandle(service);\r
168   CloseServiceHandle(services);\r
169 \r
170   printf("Service \"%s\" removed successfully!\n", name);\r
171   return 0;\r
172 }\r
173 \r
174 /* Service initialisation */\r
175 void WINAPI service_main(unsigned long argc, char **argv) {\r
176   if (_snprintf(service_name, sizeof(service_name), "%s", argv[0]) < 0) {\r
177     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service_name", "service_main()", 0);\r
178     return;\r
179   }\r
180 \r
181   /* Initialise status */\r
182   ZeroMemory(&service_status, sizeof(service_status));\r
183   service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
184   service_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;\r
185   service_status.dwWin32ExitCode = NO_ERROR;\r
186   service_status.dwServiceSpecificExitCode = 0;\r
187   service_status.dwCheckPoint = 0;\r
188   service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
189 \r
190   /* Signal we AREN'T running the server */\r
191   process_handle = 0;\r
192   pid = 0;\r
193 \r
194   /* Register control handler */\r
195   service_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, 0);\r
196   if (! service_handle) {\r
197     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);\r
198     return;\r
199   }\r
200 \r
201   /* Get startup parameters */\r
202   int ret = get_parameters(argv[0], exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir));\r
203   if (ret) {\r
204     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, argv[0], 0);\r
205     service_status.dwCurrentState = SERVICE_STOPPED;\r
206     /* An accurate, if not particularly helpful, status */\r
207     service_status.dwWin32ExitCode = ERROR_SERVICE_NOT_ACTIVE;\r
208     SetServiceStatus(service_handle, &service_status);\r
209     return;\r
210   }\r
211 \r
212   service_status.dwCurrentState = SERVICE_START_PENDING;\r
213   service_status.dwWaitHint = NSSM_RESET_THROTTLE_RESTART + NSSM_WAITHINT_MARGIN;\r
214   SetServiceStatus(service_handle, &service_status);\r
215 \r
216   /* Try to create the exit action parameters; we don't care if it fails */\r
217   create_exit_action(argv[0], exit_action_strings[0]);\r
218 \r
219   set_service_recovery(service_name);\r
220 \r
221   /* Used for signalling a resume if the service pauses when throttled. */\r
222   InitializeCriticalSection(&throttle_section);\r
223 \r
224   monitor_service();\r
225 }\r
226 \r
227 /* Make sure service recovery actions are taken where necessary */\r
228 void set_service_recovery(char *service_name) {\r
229   SC_HANDLE services = open_service_manager();\r
230   if (! services) return;\r
231 \r
232   SC_HANDLE service = OpenService(services, service_name, SC_MANAGER_ALL_ACCESS);\r
233   if (! service) return;\r
234   return;\r
235 \r
236   SERVICE_FAILURE_ACTIONS_FLAG flag;\r
237   ZeroMemory(&flag, sizeof(flag));\r
238   flag.fFailureActionsOnNonCrashFailures = true;\r
239 \r
240   /* This functionality was added in Vista so the call may fail */\r
241   ChangeServiceConfig2(service, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag);\r
242 }\r
243 \r
244 int monitor_service() {\r
245   /* Set service status to started */\r
246   int ret = start_service();\r
247   if (ret) {\r
248     char code[16];\r
249     snprintf(code, sizeof(code), "%d", ret);\r
250     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, exe, service_name, ret, 0);\r
251     return ret;\r
252   }\r
253   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, exe, flags, service_name, dir, 0);\r
254 \r
255   /* Monitor service service */\r
256   if (! RegisterWaitForSingleObject(&wait_handle, process_handle, end_service, (void *) pid, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
257     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service_name, exe, error_string(GetLastError()), 0);\r
258   }\r
259 \r
260   return 0;\r
261 }\r
262 \r
263 /* Service control handler */\r
264 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
265   switch (control) {\r
266     case SERVICE_CONTROL_SHUTDOWN:\r
267     case SERVICE_CONTROL_STOP:\r
268       stop_service(0, true, true);\r
269       return NO_ERROR;\r
270 \r
271     case SERVICE_CONTROL_CONTINUE:\r
272       throttle = 0;\r
273       WakeConditionVariable(&throttle_condition);\r
274       service_status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
275       service_status.dwWaitHint = throttle_milliseconds() + NSSM_WAITHINT_MARGIN;\r
276       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service_name, 0);\r
277       SetServiceStatus(service_handle, &service_status);\r
278       return NO_ERROR;\r
279 \r
280     case SERVICE_CONTROL_PAUSE:\r
281       /*\r
282         We don't accept pause messages but it isn't possible to register\r
283         only for continue messages so we have to handle this case.\r
284       */\r
285       return ERROR_CALL_NOT_IMPLEMENTED;\r
286   }\r
287 \r
288   /* Unknown control */\r
289   return ERROR_CALL_NOT_IMPLEMENTED;\r
290 }\r
291 \r
292 /* Start the service */\r
293 int start_service() {\r
294   stopping = false;\r
295 \r
296   if (process_handle) return 0;\r
297 \r
298   /* Allocate a STARTUPINFO structure for a new process */\r
299   STARTUPINFO si;\r
300   ZeroMemory(&si, sizeof(si));\r
301   si.cb = sizeof(si);\r
302 \r
303   /* Allocate a PROCESSINFO structure for the process */\r
304   PROCESS_INFORMATION pi;\r
305   ZeroMemory(&pi, sizeof(pi));\r
306 \r
307   /* Launch executable with arguments */\r
308   char cmd[CMD_LENGTH];\r
309   if (_snprintf(cmd, sizeof(cmd), "%s %s", exe, flags) < 0) {\r
310     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);\r
311     return stop_service(2, true, true);\r
312   }\r
313 \r
314   throttle_restart();\r
315 \r
316   if (! CreateProcess(0, cmd, 0, 0, false, 0, 0, dir, &si, &pi)) {\r
317     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, error_string(GetLastError()), 0);\r
318     return stop_service(3, true, true);\r
319   }\r
320   process_handle = pi.hProcess;\r
321   pid = pi.dwProcessId;\r
322 \r
323   /* Signal successful start */\r
324   service_status.dwCurrentState = SERVICE_RUNNING;\r
325   SetServiceStatus(service_handle, &service_status);\r
326 \r
327   /* Wait for a clean startup. */\r
328   if (WaitForSingleObject(process_handle, NSSM_RESET_THROTTLE_RESTART) == WAIT_TIMEOUT) throttle = 0;\r
329 \r
330   return 0;\r
331 }\r
332 \r
333 /* Stop the service */\r
334 int stop_service(unsigned long exitcode, bool graceful, bool default_action) {\r
335   if (default_action && ! exitcode && ! graceful) {\r
336     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_GRACEFUL_SUICIDE, service_name, exe, exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_REALLY] ,0);\r
337     graceful = true;\r
338   }\r
339 \r
340   /* Signal we are stopping */\r
341   if (graceful) {\r
342     service_status.dwCurrentState = SERVICE_STOP_PENDING;\r
343     service_status.dwWaitHint = NSSM_KILL_WINDOW_GRACE_PERIOD + NSSM_KILL_THREADS_GRACE_PERIOD + NSSM_WAITHINT_MARGIN;\r
344     SetServiceStatus(service_handle, &service_status);\r
345   }\r
346 \r
347   /* Nothing to do if server isn't running */\r
348   if (pid) {\r
349     /* Shut down server */\r
350     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);\r
351     kill_process(service_name, process_handle, pid, 0);\r
352     process_handle = 0;\r
353   }\r
354   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);\r
355 \r
356   end_service((void *) pid, true);\r
357 \r
358   /* Signal we stopped */\r
359   if (graceful) {\r
360     service_status.dwCurrentState = SERVICE_STOPPED;\r
361     if (exitcode) {\r
362       service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
363       service_status.dwServiceSpecificExitCode = exitcode;\r
364     }\r
365     else {\r
366       service_status.dwWin32ExitCode = NO_ERROR;\r
367       service_status.dwServiceSpecificExitCode = 0;\r
368     }\r
369     SetServiceStatus(service_handle, &service_status);\r
370   }\r
371 \r
372   return exitcode;\r
373 }\r
374 \r
375 /* Callback function triggered when the server exits */\r
376 void CALLBACK end_service(void *arg, unsigned char why) {\r
377   if (stopping) return;\r
378 \r
379   stopping = true;\r
380 \r
381   pid = (unsigned long) arg;\r
382 \r
383   /* Check exit code */\r
384   unsigned long exitcode = 0;\r
385   GetExitCodeProcess(process_handle, &exitcode);\r
386 \r
387   /* Clean up. */\r
388   kill_process_tree(service_name, pid, exitcode, pid);\r
389 \r
390   /*\r
391     The why argument is true if our wait timed out or false otherwise.\r
392     Our wait is infinite so why will never be true when called by the system.\r
393     If it is indeed true, assume we were called from stop_service() because\r
394     this is a controlled shutdown, and don't take any restart action.\r
395   */\r
396   if (why) return;\r
397 \r
398   char code[16];\r
399   _snprintf(code, sizeof(code), "%d", exitcode);\r
400   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);\r
401 \r
402   /* What action should we take? */\r
403   int action = NSSM_EXIT_RESTART;\r
404   unsigned char action_string[ACTION_LEN];\r
405   bool default_action;\r
406   if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) {\r
407     for (int i = 0; exit_action_strings[i]; i++) {\r
408       if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
409         action = i;\r
410         break;\r
411       }\r
412     }\r
413   }\r
414 \r
415   process_handle = 0;\r
416   pid = 0;\r
417   switch (action) {\r
418     /* Try to restart the service or return failure code to service manager */\r
419     case NSSM_EXIT_RESTART:\r
420       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service_name, code, exit_action_strings[action], exe, 0);\r
421       while (monitor_service()) {\r
422         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, exe, service_name, 0);\r
423         Sleep(30000);\r
424       }\r
425     break;\r
426 \r
427     /* Do nothing, just like srvany would */\r
428     case NSSM_EXIT_IGNORE:\r
429       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service_name, code, exit_action_strings[action], exe, 0);\r
430       Sleep(INFINITE);\r
431     break;\r
432 \r
433     /* Tell the service manager we are finished */\r
434     case NSSM_EXIT_REALLY:\r
435       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);\r
436       stop_service(exitcode, true, default_action);\r
437     break;\r
438 \r
439     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
440     case NSSM_EXIT_UNCLEAN:\r
441       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);\r
442       exit(stop_service(exitcode, false, default_action));\r
443     break;\r
444   }\r
445 }\r
446 \r
447 void throttle_restart() {\r
448   /* This can't be a restart if the service is already running. */\r
449   if (! throttle++) return;\r
450 \r
451   int ms = throttle_milliseconds();\r
452 \r
453   if (throttle > 7) throttle = 8;\r
454 \r
455   char threshold[8], milliseconds[8];\r
456   _snprintf(threshold, sizeof(threshold), "%d", NSSM_RESET_THROTTLE_RESTART);\r
457   _snprintf(milliseconds, sizeof(milliseconds), "%d", ms);\r
458   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);\r
459 \r
460   EnterCriticalSection(&throttle_section);\r
461 \r
462   service_status.dwCurrentState = SERVICE_PAUSED;\r
463   SetServiceStatus(service_handle, &service_status);\r
464 \r
465   SleepConditionVariableCS(&throttle_condition, &throttle_section, ms);\r
466 \r
467   LeaveCriticalSection(&throttle_section);\r
468 }\r