NSSM 2.16.
[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_SHUTDOWN:\r
323     case SERVICE_CONTROL_STOP:\r
324       log_service_control(service_name, control, true);\r
325       stop_service(0, true, true);\r
326       return NO_ERROR;\r
327 \r
328     case SERVICE_CONTROL_CONTINUE:\r
329       log_service_control(service_name, control, true);\r
330       if (! throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
331       throttle = 0;\r
332       ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));\r
333       SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);\r
334       service_status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
335       service_status.dwWaitHint = throttle_milliseconds() + NSSM_WAITHINT_MARGIN;\r
336       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service_name, 0);\r
337       SetServiceStatus(service_handle, &service_status);\r
338       return NO_ERROR;\r
339 \r
340     case SERVICE_CONTROL_PAUSE:\r
341       /*\r
342         We don't accept pause messages but it isn't possible to register\r
343         only for continue messages so we have to handle this case.\r
344       */\r
345       log_service_control(service_name, control, false);\r
346       return ERROR_CALL_NOT_IMPLEMENTED;\r
347   }\r
348 \r
349   /* Unknown control */\r
350   log_service_control(service_name, control, false);\r
351   return ERROR_CALL_NOT_IMPLEMENTED;\r
352 }\r
353 \r
354 /* Start the service */\r
355 int start_service() {\r
356   stopping = false;\r
357 \r
358   if (process_handle) return 0;\r
359 \r
360   /* Allocate a STARTUPINFO structure for a new process */\r
361   STARTUPINFO si;\r
362   ZeroMemory(&si, sizeof(si));\r
363   si.cb = sizeof(si);\r
364 \r
365   /* Allocate a PROCESSINFO structure for the process */\r
366   PROCESS_INFORMATION pi;\r
367   ZeroMemory(&pi, sizeof(pi));\r
368 \r
369   /* Get startup parameters */\r
370   char *env = 0;\r
371   int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay);\r
372   if (ret) {\r
373     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service_name, 0);\r
374     return stop_service(2, true, true);\r
375   }\r
376 \r
377   /* Launch executable with arguments */\r
378   char cmd[CMD_LENGTH];\r
379   if (_snprintf(cmd, sizeof(cmd), "\"%s\" %s", exe, flags) < 0) {\r
380     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);\r
381     return stop_service(2, true, true);\r
382   }\r
383 \r
384   throttle_restart();\r
385 \r
386   if (! CreateProcess(0, cmd, 0, 0, false, 0, env, dir, &si, &pi)) {\r
387     unsigned long error = GetLastError();\r
388     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
389     else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, error_string(error), 0);\r
390     return stop_service(3, true, true);\r
391   }\r
392   process_handle = pi.hProcess;\r
393   pid = pi.dwProcessId;\r
394 \r
395   if (get_process_creation_time(process_handle, &creation_time)) ZeroMemory(&creation_time, sizeof(creation_time));\r
396 \r
397   /* Signal successful start */\r
398   service_status.dwCurrentState = SERVICE_RUNNING;\r
399   SetServiceStatus(service_handle, &service_status);\r
400 \r
401   /* Wait for a clean startup. */\r
402   if (WaitForSingleObject(process_handle, throttle_delay) == WAIT_TIMEOUT) throttle = 0;\r
403 \r
404   return 0;\r
405 }\r
406 \r
407 /* Stop the service */\r
408 int stop_service(unsigned long exitcode, bool graceful, bool default_action) {\r
409   if (default_action && ! exitcode && ! graceful) {\r
410     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
411     graceful = true;\r
412   }\r
413 \r
414   /* Signal we are stopping */\r
415   if (graceful) {\r
416     service_status.dwCurrentState = SERVICE_STOP_PENDING;\r
417     service_status.dwWaitHint = NSSM_KILL_WINDOW_GRACE_PERIOD + NSSM_KILL_THREADS_GRACE_PERIOD + NSSM_WAITHINT_MARGIN;\r
418     SetServiceStatus(service_handle, &service_status);\r
419   }\r
420 \r
421   /* Nothing to do if service isn't running */\r
422   if (pid) {\r
423     /* Shut down service */\r
424     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);\r
425     kill_process(service_name, process_handle, pid, 0);\r
426   }\r
427   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);\r
428 \r
429   end_service((void *) pid, true);\r
430 \r
431   /* Signal we stopped */\r
432   if (graceful) {\r
433     service_status.dwCurrentState = SERVICE_STOPPED;\r
434     if (exitcode) {\r
435       service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
436       service_status.dwServiceSpecificExitCode = exitcode;\r
437     }\r
438     else {\r
439       service_status.dwWin32ExitCode = NO_ERROR;\r
440       service_status.dwServiceSpecificExitCode = 0;\r
441     }\r
442     SetServiceStatus(service_handle, &service_status);\r
443   }\r
444 \r
445   return exitcode;\r
446 }\r
447 \r
448 /* Callback function triggered when the server exits */\r
449 void CALLBACK end_service(void *arg, unsigned char why) {\r
450   if (stopping) return;\r
451 \r
452   stopping = true;\r
453 \r
454   pid = (unsigned long) arg;\r
455 \r
456   /* Check exit code */\r
457   unsigned long exitcode = 0;\r
458   char code[16];\r
459   FILETIME exit_time;\r
460   GetExitCodeProcess(process_handle, &exitcode);\r
461   if (exitcode == STILL_ACTIVE || get_process_exit_time(process_handle, &exit_time)) GetSystemTimeAsFileTime(&exit_time);\r
462   CloseHandle(process_handle);\r
463 \r
464   /*\r
465     Log that the service ended BEFORE logging about killing the process\r
466     tree.  See below for the possible values of the why argument.\r
467   */\r
468   if (! why) {\r
469     _snprintf(code, sizeof(code), "%d", exitcode);\r
470     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);\r
471   }\r
472 \r
473   /* Clean up. */\r
474   kill_process_tree(service_name, pid, exitcode, pid, &creation_time, &exit_time);\r
475 \r
476   /*\r
477     The why argument is true if our wait timed out or false otherwise.\r
478     Our wait is infinite so why will never be true when called by the system.\r
479     If it is indeed true, assume we were called from stop_service() because\r
480     this is a controlled shutdown, and don't take any restart action.\r
481   */\r
482   if (why) return;\r
483 \r
484   /* What action should we take? */\r
485   int action = NSSM_EXIT_RESTART;\r
486   unsigned char action_string[ACTION_LEN];\r
487   bool default_action;\r
488   if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) {\r
489     for (int i = 0; exit_action_strings[i]; i++) {\r
490       if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
491         action = i;\r
492         break;\r
493       }\r
494     }\r
495   }\r
496 \r
497   process_handle = 0;\r
498   pid = 0;\r
499   switch (action) {\r
500     /* Try to restart the service or return failure code to service manager */\r
501     case NSSM_EXIT_RESTART:\r
502       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service_name, code, exit_action_strings[action], exe, 0);\r
503       while (monitor_service()) {\r
504         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, exe, service_name, 0);\r
505         Sleep(30000);\r
506       }\r
507     break;\r
508 \r
509     /* Do nothing, just like srvany would */\r
510     case NSSM_EXIT_IGNORE:\r
511       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service_name, code, exit_action_strings[action], exe, 0);\r
512       Sleep(INFINITE);\r
513     break;\r
514 \r
515     /* Tell the service manager we are finished */\r
516     case NSSM_EXIT_REALLY:\r
517       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);\r
518       stop_service(exitcode, true, default_action);\r
519     break;\r
520 \r
521     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
522     case NSSM_EXIT_UNCLEAN:\r
523       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);\r
524       exit(stop_service(exitcode, false, default_action));\r
525     break;\r
526   }\r
527 }\r
528 \r
529 void throttle_restart() {\r
530   /* This can't be a restart if the service is already running. */\r
531   if (! throttle++) return;\r
532 \r
533   int ms = throttle_milliseconds();\r
534 \r
535   if (throttle > 7) throttle = 8;\r
536 \r
537   char threshold[8], milliseconds[8];\r
538   _snprintf(threshold, sizeof(threshold), "%d", throttle_delay);\r
539   _snprintf(milliseconds, sizeof(milliseconds), "%d", ms);\r
540   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);\r
541 \r
542   if (throttle_timer) {\r
543     ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));\r
544     throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
545     SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);\r
546   }\r
547 \r
548   service_status.dwCurrentState = SERVICE_PAUSED;\r
549   SetServiceStatus(service_handle, &service_status);\r
550 \r
551   if (throttle_timer) WaitForSingleObject(throttle_timer, INFINITE);\r
552   else Sleep(ms);\r
553 }\r