Allow specifying output streams.
[nssm.git] / service.cpp
1 #include "nssm.h"\r
2 \r
3 bool is_admin;\r
4 SERVICE_STATUS service_status;\r
5 SERVICE_STATUS_HANDLE service_handle;\r
6 HANDLE process_handle;\r
7 HANDLE wait_handle;\r
8 unsigned long pid;\r
9 static char service_name[SERVICE_NAME_LENGTH];\r
10 char exe[EXE_LENGTH];\r
11 char flags[CMD_LENGTH];\r
12 char dir[MAX_PATH];\r
13 bool stopping;\r
14 unsigned long throttle_delay;\r
15 HANDLE throttle_timer;\r
16 LARGE_INTEGER throttle_duetime;\r
17 FILETIME creation_time;\r
18 \r
19 static enum { NSSM_EXIT_RESTART, NSSM_EXIT_IGNORE, NSSM_EXIT_REALLY, NSSM_EXIT_UNCLEAN } exit_actions;\r
20 static const char *exit_action_strings[] = { "Restart", "Ignore", "Exit", "Suicide", 0 };\r
21 \r
22 static unsigned long throttle;\r
23 \r
24 static inline int throttle_milliseconds() {\r
25   /* pow() operates on doubles. */\r
26   int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;\r
27   return ret * 1000;\r
28 }\r
29 \r
30 /* Connect to the service manager */\r
31 SC_HANDLE open_service_manager() {\r
32   SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);\r
33   if (! ret) {\r
34     if (is_admin) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);\r
35     return 0;\r
36   }\r
37 \r
38   return ret;\r
39 }\r
40 \r
41 /* About to install the service */\r
42 int pre_install_service(int argc, char **argv) {\r
43   /* Show the dialogue box if we didn't give the service name and path */\r
44   if (argc < 2) return nssm_gui(IDD_INSTALL, argv[0]);\r
45 \r
46   /* Arguments are optional */\r
47   char *flags;\r
48   size_t flagslen = 0;\r
49   size_t s = 0;\r
50   int i;\r
51   for (i = 2; i < argc; i++) flagslen += strlen(argv[i]) + 1;\r
52   if (! flagslen) flagslen = 1;\r
53 \r
54   flags = (char *) HeapAlloc(GetProcessHeap(), 0, flagslen);\r
55   if (! flags) {\r
56     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "flags", "pre_install_service()", 0);\r
57     return 2;\r
58   }\r
59   ZeroMemory(flags, flagslen);\r
60 \r
61   /*\r
62     This probably isn't UTF8-safe and should use std::string or something\r
63     but it's been broken for the best part of a decade and due for a rewrite\r
64     anyway so it'll do as a quick-'n'-dirty fix.  Note that we don't free\r
65     the flags buffer but as the program exits that isn't a big problem.\r
66   */\r
67   for (i = 2; i < argc; i++) {\r
68     size_t len = strlen(argv[i]);\r
69     memmove(flags + s, argv[i], len);\r
70     s += len;\r
71     if (i < argc - 1) flags[s++] = ' ';\r
72   }\r
73 \r
74   return install_service(argv[0], argv[1], flags);\r
75 }\r
76 \r
77 /* About to remove the service */\r
78 int pre_remove_service(int argc, char **argv) {\r
79   /* Show dialogue box if we didn't pass service name and "confirm" */\r
80   if (argc < 2) return nssm_gui(IDD_REMOVE, argv[0]);\r
81   if (str_equiv(argv[1], "confirm")) return remove_service(argv[0]);\r
82   print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);\r
83   return 100;\r
84 }\r
85 \r
86 /* Install the service */\r
87 int install_service(char *name, char *exe, char *flags) {\r
88   /* Open service manager */\r
89   SC_HANDLE services = open_service_manager();\r
90   if (! services) {\r
91     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
92     return 2;\r
93   }\r
94 \r
95   /* Get path of this program */\r
96   char path[MAX_PATH];\r
97   GetModuleFileName(0, path, MAX_PATH);\r
98 \r
99   /* Construct command */\r
100   char command[CMD_LENGTH];\r
101   size_t pathlen = strlen(path);\r
102   if (pathlen + 1 >= VALUE_LENGTH) {\r
103     print_message(stderr, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);\r
104     return 3;\r
105   }\r
106   if (_snprintf(command, sizeof(command), "\"%s\"", path) < 0) {\r
107     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY_FOR_IMAGEPATH);\r
108     return 4;\r
109   }\r
110 \r
111   /* Work out directory name */\r
112   size_t len = strlen(exe);\r
113   size_t i;\r
114   for (i = len; i && exe[i] != '\\' && exe[i] != '/'; i--);\r
115   char dir[MAX_PATH];\r
116   memmove(dir, exe, i);\r
117   dir[i] = '\0';\r
118 \r
119   /* Create the service */\r
120   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
121   if (! service) {\r
122     print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);\r
123     CloseServiceHandle(services);\r
124     return 5;\r
125   }\r
126 \r
127   /* Now we need to put the parameters into the registry */\r
128   if (create_parameters(name, exe, flags, dir)) {\r
129     print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);\r
130     DeleteService(service);\r
131     CloseServiceHandle(services);\r
132     return 6;\r
133   }\r
134 \r
135   set_service_recovery(service, name);\r
136 \r
137   /* Cleanup */\r
138   CloseServiceHandle(service);\r
139   CloseServiceHandle(services);\r
140 \r
141   print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, name);\r
142   return 0;\r
143 }\r
144 \r
145 /* Remove the service */\r
146 int remove_service(char *name) {\r
147   /* Open service manager */\r
148   SC_HANDLE services = open_service_manager();\r
149   if (! services) {\r
150     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
151     return 2;\r
152   }\r
153 \r
154   /* Try to open the service */\r
155   SC_HANDLE service = OpenService(services, name, SC_MANAGER_ALL_ACCESS);\r
156   if (! service) {\r
157     print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);\r
158     CloseServiceHandle(services);\r
159     return 3;\r
160   }\r
161 \r
162   /* Try to delete the service */\r
163   if (! DeleteService(service)) {\r
164     print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);\r
165     CloseServiceHandle(service);\r
166     CloseServiceHandle(services);\r
167     return 4;\r
168   }\r
169 \r
170   /* Cleanup */\r
171   CloseServiceHandle(service);\r
172   CloseServiceHandle(services);\r
173 \r
174   print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, name);\r
175   return 0;\r
176 }\r
177 \r
178 /* Service initialisation */\r
179 void WINAPI service_main(unsigned long argc, char **argv) {\r
180   if (_snprintf(service_name, sizeof(service_name), "%s", argv[0]) < 0) {\r
181     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service_name", "service_main()", 0);\r
182     return;\r
183   }\r
184 \r
185   /* Initialise status */\r
186   ZeroMemory(&service_status, sizeof(service_status));\r
187   service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
188   service_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;\r
189   service_status.dwWin32ExitCode = NO_ERROR;\r
190   service_status.dwServiceSpecificExitCode = 0;\r
191   service_status.dwCheckPoint = 0;\r
192   service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
193 \r
194   /* Signal we AREN'T running the server */\r
195   process_handle = 0;\r
196   pid = 0;\r
197 \r
198   /* Register control handler */\r
199   service_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, 0);\r
200   if (! service_handle) {\r
201     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);\r
202     return;\r
203   }\r
204 \r
205   log_service_control(service_name, 0, true);\r
206 \r
207   service_status.dwCurrentState = SERVICE_START_PENDING;\r
208   service_status.dwWaitHint = throttle_delay + NSSM_WAITHINT_MARGIN;\r
209   SetServiceStatus(service_handle, &service_status);\r
210 \r
211   if (is_admin) {\r
212     /* Try to create the exit action parameters; we don't care if it fails */\r
213     create_exit_action(argv[0], exit_action_strings[0]);\r
214 \r
215     set_service_recovery(0, service_name);\r
216   }\r
217 \r
218   /* Used for signalling a resume if the service pauses when throttled. */\r
219   throttle_timer = CreateWaitableTimer(0, 1, 0);\r
220   if (! throttle_timer) {\r
221     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service_name, error_string(GetLastError()), 0);\r
222   }\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(SC_HANDLE service, char *service_name) {\r
229   SC_HANDLE services = 0;\r
230 \r
231   if (! service) {\r
232     services = open_service_manager();\r
233     if (! services) return;\r
234 \r
235     service = OpenService(services, service_name, SC_MANAGER_ALL_ACCESS);\r
236     if (! service) return;\r
237   }\r
238 \r
239   SERVICE_FAILURE_ACTIONS_FLAG flag;\r
240   ZeroMemory(&flag, sizeof(flag));\r
241   flag.fFailureActionsOnNonCrashFailures = true;\r
242 \r
243   /* This functionality was added in Vista so the call may fail */\r
244   if (! ChangeServiceConfig2(service, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {\r
245     unsigned long error = GetLastError();\r
246     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
247     if (error != ERROR_INVALID_LEVEL) {\r
248       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CHANGESERVICECONFIG2_FAILED, service_name, error_string(error), 0);\r
249     }\r
250   }\r
251 \r
252   if (services) {\r
253     CloseServiceHandle(service);\r
254     CloseServiceHandle(services);\r
255   }\r
256 }\r
257 \r
258 int monitor_service() {\r
259   /* Set service status to started */\r
260   int ret = start_service();\r
261   if (ret) {\r
262     char code[16];\r
263     _snprintf(code, sizeof(code), "%d", ret);\r
264     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, exe, service_name, ret, 0);\r
265     return ret;\r
266   }\r
267   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, exe, flags, service_name, dir, 0);\r
268 \r
269   /* Monitor service service */\r
270   if (! RegisterWaitForSingleObject(&wait_handle, process_handle, end_service, (void *) pid, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
271     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service_name, exe, error_string(GetLastError()), 0);\r
272   }\r
273 \r
274   return 0;\r
275 }\r
276 \r
277 char *service_control_text(unsigned long control) {\r
278   switch (control) {\r
279     /* HACK: there is no SERVICE_CONTROL_START constant */\r
280     case 0: return "START";\r
281     case SERVICE_CONTROL_STOP: return "STOP";\r
282     case SERVICE_CONTROL_SHUTDOWN: return "SHUTDOWN";\r
283     case SERVICE_CONTROL_PAUSE: return "PAUSE";\r
284     case SERVICE_CONTROL_CONTINUE: return "CONTINUE";\r
285     case SERVICE_CONTROL_INTERROGATE: return "INTERROGATE";\r
286     default: return 0;\r
287   }\r
288 }\r
289 \r
290 void log_service_control(char *service_name, unsigned long control, bool handled) {\r
291   char *text = service_control_text(control);\r
292   unsigned long event;\r
293 \r
294   if (! text) {\r
295     /* "0x" + 8 x hex + NULL */\r
296     text = (char *) HeapAlloc(GetProcessHeap(), 0, 11);\r
297     if (! text) {\r
298       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control", 0);\r
299       return;\r
300     }\r
301     if (_snprintf(text, 11, "0x%08x", control) < 0) {\r
302       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control", 0);\r
303       HeapFree(GetProcessHeap(), 0, text);\r
304       return;\r
305     }\r
306 \r
307     event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;\r
308   }\r
309   else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;\r
310   else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;\r
311 \r
312   log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);\r
313 \r
314   if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {\r
315     HeapFree(GetProcessHeap(), 0, text);\r
316   }\r
317 }\r
318 \r
319 /* Service control handler */\r
320 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
321   switch (control) {\r
322     case SERVICE_CONTROL_INTERROGATE:\r
323       /* We always keep the service status up-to-date so this is a no-op. */\r
324       return NO_ERROR;\r
325 \r
326     case SERVICE_CONTROL_SHUTDOWN:\r
327     case SERVICE_CONTROL_STOP:\r
328       log_service_control(service_name, control, true);\r
329       stop_service(0, true, true);\r
330       return NO_ERROR;\r
331 \r
332     case SERVICE_CONTROL_CONTINUE:\r
333       log_service_control(service_name, control, true);\r
334       if (! throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
335       throttle = 0;\r
336       ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));\r
337       SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);\r
338       service_status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
339       service_status.dwWaitHint = throttle_milliseconds() + NSSM_WAITHINT_MARGIN;\r
340       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service_name, 0);\r
341       SetServiceStatus(service_handle, &service_status);\r
342       return NO_ERROR;\r
343 \r
344     case SERVICE_CONTROL_PAUSE:\r
345       /*\r
346         We don't accept pause messages but it isn't possible to register\r
347         only for continue messages so we have to handle this case.\r
348       */\r
349       log_service_control(service_name, control, false);\r
350       return ERROR_CALL_NOT_IMPLEMENTED;\r
351   }\r
352 \r
353   /* Unknown control */\r
354   log_service_control(service_name, control, false);\r
355   return ERROR_CALL_NOT_IMPLEMENTED;\r
356 }\r
357 \r
358 /* Start the service */\r
359 int start_service() {\r
360   stopping = false;\r
361 \r
362   if (process_handle) return 0;\r
363 \r
364   /* Allocate a STARTUPINFO structure for a new process */\r
365   STARTUPINFO si;\r
366   ZeroMemory(&si, sizeof(si));\r
367   si.cb = sizeof(si);\r
368 \r
369   /* Allocate a PROCESSINFO structure for the process */\r
370   PROCESS_INFORMATION pi;\r
371   ZeroMemory(&pi, sizeof(pi));\r
372 \r
373   /* Get startup parameters */\r
374   char *env = 0;\r
375   int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay, &si);\r
376   if (ret) {\r
377     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service_name, 0);\r
378     return stop_service(2, true, true);\r
379   }\r
380 \r
381   /* Launch executable with arguments */\r
382   char cmd[CMD_LENGTH];\r
383   if (_snprintf(cmd, sizeof(cmd), "\"%s\" %s", exe, flags) < 0) {\r
384     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);\r
385     close_output_handles(&si);\r
386     return stop_service(2, true, true);\r
387   }\r
388 \r
389   throttle_restart();\r
390 \r
391   bool inherit_handles = (si.dwFlags & STARTF_USESTDHANDLES);\r
392   if (! CreateProcess(0, cmd, 0, 0, inherit_handles, 0, env, dir, &si, &pi)) {\r
393     unsigned long error = GetLastError();\r
394     if (error == ERROR_INVALID_PARAMETER && env) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service_name, exe, NSSM_REG_ENV, 0);\r
395     else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, error_string(error), 0);\r
396     close_output_handles(&si);\r
397     return stop_service(3, true, true);\r
398   }\r
399   process_handle = pi.hProcess;\r
400   pid = pi.dwProcessId;\r
401 \r
402   if (get_process_creation_time(process_handle, &creation_time)) ZeroMemory(&creation_time, sizeof(creation_time));\r
403 \r
404   close_output_handles(&si);\r
405 \r
406   /* Signal successful start */\r
407   service_status.dwCurrentState = SERVICE_RUNNING;\r
408   SetServiceStatus(service_handle, &service_status);\r
409 \r
410   /* Wait for a clean startup. */\r
411   if (WaitForSingleObject(process_handle, throttle_delay) == WAIT_TIMEOUT) throttle = 0;\r
412 \r
413   return 0;\r
414 }\r
415 \r
416 /* Stop the service */\r
417 int stop_service(unsigned long exitcode, bool graceful, bool default_action) {\r
418   if (default_action && ! exitcode && ! graceful) {\r
419     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
420     graceful = true;\r
421   }\r
422 \r
423   /* Signal we are stopping */\r
424   if (graceful) {\r
425     service_status.dwCurrentState = SERVICE_STOP_PENDING;\r
426     service_status.dwWaitHint = NSSM_KILL_WINDOW_GRACE_PERIOD + NSSM_KILL_THREADS_GRACE_PERIOD + NSSM_WAITHINT_MARGIN;\r
427     SetServiceStatus(service_handle, &service_status);\r
428   }\r
429 \r
430   /* Nothing to do if service isn't running */\r
431   if (pid) {\r
432     /* Shut down service */\r
433     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);\r
434     kill_process(service_name, process_handle, pid, 0);\r
435   }\r
436   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);\r
437 \r
438   end_service((void *) pid, true);\r
439 \r
440   /* Signal we stopped */\r
441   if (graceful) {\r
442     service_status.dwCurrentState = SERVICE_STOPPED;\r
443     if (exitcode) {\r
444       service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
445       service_status.dwServiceSpecificExitCode = exitcode;\r
446     }\r
447     else {\r
448       service_status.dwWin32ExitCode = NO_ERROR;\r
449       service_status.dwServiceSpecificExitCode = 0;\r
450     }\r
451     SetServiceStatus(service_handle, &service_status);\r
452   }\r
453 \r
454   return exitcode;\r
455 }\r
456 \r
457 /* Callback function triggered when the server exits */\r
458 void CALLBACK end_service(void *arg, unsigned char why) {\r
459   if (stopping) return;\r
460 \r
461   stopping = true;\r
462 \r
463   pid = (unsigned long) arg;\r
464 \r
465   /* Check exit code */\r
466   unsigned long exitcode = 0;\r
467   char code[16];\r
468   FILETIME exit_time;\r
469   GetExitCodeProcess(process_handle, &exitcode);\r
470   if (exitcode == STILL_ACTIVE || get_process_exit_time(process_handle, &exit_time)) GetSystemTimeAsFileTime(&exit_time);\r
471   CloseHandle(process_handle);\r
472 \r
473   /*\r
474     Log that the service ended BEFORE logging about killing the process\r
475     tree.  See below for the possible values of the why argument.\r
476   */\r
477   if (! why) {\r
478     _snprintf(code, sizeof(code), "%d", exitcode);\r
479     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);\r
480   }\r
481 \r
482   /* Clean up. */\r
483   kill_process_tree(service_name, pid, exitcode, pid, &creation_time, &exit_time);\r
484 \r
485   /*\r
486     The why argument is true if our wait timed out or false otherwise.\r
487     Our wait is infinite so why will never be true when called by the system.\r
488     If it is indeed true, assume we were called from stop_service() because\r
489     this is a controlled shutdown, and don't take any restart action.\r
490   */\r
491   if (why) return;\r
492 \r
493   /* What action should we take? */\r
494   int action = NSSM_EXIT_RESTART;\r
495   unsigned char action_string[ACTION_LEN];\r
496   bool default_action;\r
497   if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) {\r
498     for (int i = 0; exit_action_strings[i]; i++) {\r
499       if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
500         action = i;\r
501         break;\r
502       }\r
503     }\r
504   }\r
505 \r
506   process_handle = 0;\r
507   pid = 0;\r
508   switch (action) {\r
509     /* Try to restart the service or return failure code to service manager */\r
510     case NSSM_EXIT_RESTART:\r
511       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service_name, code, exit_action_strings[action], exe, 0);\r
512       while (monitor_service()) {\r
513         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, exe, service_name, 0);\r
514         Sleep(30000);\r
515       }\r
516     break;\r
517 \r
518     /* Do nothing, just like srvany would */\r
519     case NSSM_EXIT_IGNORE:\r
520       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service_name, code, exit_action_strings[action], exe, 0);\r
521       Sleep(INFINITE);\r
522     break;\r
523 \r
524     /* Tell the service manager we are finished */\r
525     case NSSM_EXIT_REALLY:\r
526       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);\r
527       stop_service(exitcode, true, default_action);\r
528     break;\r
529 \r
530     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
531     case NSSM_EXIT_UNCLEAN:\r
532       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);\r
533       exit(stop_service(exitcode, false, default_action));\r
534     break;\r
535   }\r
536 }\r
537 \r
538 void throttle_restart() {\r
539   /* This can't be a restart if the service is already running. */\r
540   if (! throttle++) return;\r
541 \r
542   int ms = throttle_milliseconds();\r
543 \r
544   if (throttle > 7) throttle = 8;\r
545 \r
546   char threshold[8], milliseconds[8];\r
547   _snprintf(threshold, sizeof(threshold), "%d", throttle_delay);\r
548   _snprintf(milliseconds, sizeof(milliseconds), "%d", ms);\r
549   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);\r
550 \r
551   if (throttle_timer) {\r
552     ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));\r
553     throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
554     SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);\r
555   }\r
556 \r
557   service_status.dwCurrentState = SERVICE_PAUSED;\r
558   SetServiceStatus(service_handle, &service_status);\r
559 \r
560   if (throttle_timer) WaitForSingleObject(throttle_timer, INFINITE);\r
561   else Sleep(ms);\r
562 }\r