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