d4d023bf77d0569eb162e377b54d46b99150b489
[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);\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     return stop_service(2, true, true);\r
386   }\r
387 \r
388   throttle_restart();\r
389 \r
390   if (! CreateProcess(0, cmd, 0, 0, false, 0, env, dir, &si, &pi)) {\r
391     unsigned long error = GetLastError();\r
392     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
393     else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, error_string(error), 0);\r
394     return stop_service(3, true, true);\r
395   }\r
396   process_handle = pi.hProcess;\r
397   pid = pi.dwProcessId;\r
398 \r
399   if (get_process_creation_time(process_handle, &creation_time)) ZeroMemory(&creation_time, sizeof(creation_time));\r
400 \r
401   /* Signal successful start */\r
402   service_status.dwCurrentState = SERVICE_RUNNING;\r
403   SetServiceStatus(service_handle, &service_status);\r
404 \r
405   /* Wait for a clean startup. */\r
406   if (WaitForSingleObject(process_handle, throttle_delay) == WAIT_TIMEOUT) throttle = 0;\r
407 \r
408   return 0;\r
409 }\r
410 \r
411 /* Stop the service */\r
412 int stop_service(unsigned long exitcode, bool graceful, bool default_action) {\r
413   if (default_action && ! exitcode && ! graceful) {\r
414     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
415     graceful = true;\r
416   }\r
417 \r
418   /* Signal we are stopping */\r
419   if (graceful) {\r
420     service_status.dwCurrentState = SERVICE_STOP_PENDING;\r
421     service_status.dwWaitHint = NSSM_KILL_WINDOW_GRACE_PERIOD + NSSM_KILL_THREADS_GRACE_PERIOD + NSSM_WAITHINT_MARGIN;\r
422     SetServiceStatus(service_handle, &service_status);\r
423   }\r
424 \r
425   /* Nothing to do if service isn't running */\r
426   if (pid) {\r
427     /* Shut down service */\r
428     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);\r
429     kill_process(service_name, process_handle, pid, 0);\r
430   }\r
431   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);\r
432 \r
433   end_service((void *) pid, true);\r
434 \r
435   /* Signal we stopped */\r
436   if (graceful) {\r
437     service_status.dwCurrentState = SERVICE_STOPPED;\r
438     if (exitcode) {\r
439       service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
440       service_status.dwServiceSpecificExitCode = exitcode;\r
441     }\r
442     else {\r
443       service_status.dwWin32ExitCode = NO_ERROR;\r
444       service_status.dwServiceSpecificExitCode = 0;\r
445     }\r
446     SetServiceStatus(service_handle, &service_status);\r
447   }\r
448 \r
449   return exitcode;\r
450 }\r
451 \r
452 /* Callback function triggered when the server exits */\r
453 void CALLBACK end_service(void *arg, unsigned char why) {\r
454   if (stopping) return;\r
455 \r
456   stopping = true;\r
457 \r
458   pid = (unsigned long) arg;\r
459 \r
460   /* Check exit code */\r
461   unsigned long exitcode = 0;\r
462   char code[16];\r
463   FILETIME exit_time;\r
464   GetExitCodeProcess(process_handle, &exitcode);\r
465   if (exitcode == STILL_ACTIVE || get_process_exit_time(process_handle, &exit_time)) GetSystemTimeAsFileTime(&exit_time);\r
466   CloseHandle(process_handle);\r
467 \r
468   /*\r
469     Log that the service ended BEFORE logging about killing the process\r
470     tree.  See below for the possible values of the why argument.\r
471   */\r
472   if (! why) {\r
473     _snprintf(code, sizeof(code), "%d", exitcode);\r
474     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);\r
475   }\r
476 \r
477   /* Clean up. */\r
478   kill_process_tree(service_name, pid, exitcode, pid, &creation_time, &exit_time);\r
479 \r
480   /*\r
481     The why argument is true if our wait timed out or false otherwise.\r
482     Our wait is infinite so why will never be true when called by the system.\r
483     If it is indeed true, assume we were called from stop_service() because\r
484     this is a controlled shutdown, and don't take any restart action.\r
485   */\r
486   if (why) return;\r
487 \r
488   /* What action should we take? */\r
489   int action = NSSM_EXIT_RESTART;\r
490   unsigned char action_string[ACTION_LEN];\r
491   bool default_action;\r
492   if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) {\r
493     for (int i = 0; exit_action_strings[i]; i++) {\r
494       if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
495         action = i;\r
496         break;\r
497       }\r
498     }\r
499   }\r
500 \r
501   process_handle = 0;\r
502   pid = 0;\r
503   switch (action) {\r
504     /* Try to restart the service or return failure code to service manager */\r
505     case NSSM_EXIT_RESTART:\r
506       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service_name, code, exit_action_strings[action], exe, 0);\r
507       while (monitor_service()) {\r
508         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, exe, service_name, 0);\r
509         Sleep(30000);\r
510       }\r
511     break;\r
512 \r
513     /* Do nothing, just like srvany would */\r
514     case NSSM_EXIT_IGNORE:\r
515       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service_name, code, exit_action_strings[action], exe, 0);\r
516       Sleep(INFINITE);\r
517     break;\r
518 \r
519     /* Tell the service manager we are finished */\r
520     case NSSM_EXIT_REALLY:\r
521       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);\r
522       stop_service(exitcode, true, default_action);\r
523     break;\r
524 \r
525     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
526     case NSSM_EXIT_UNCLEAN:\r
527       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);\r
528       exit(stop_service(exitcode, false, default_action));\r
529     break;\r
530   }\r
531 }\r
532 \r
533 void throttle_restart() {\r
534   /* This can't be a restart if the service is already running. */\r
535   if (! throttle++) return;\r
536 \r
537   int ms = throttle_milliseconds();\r
538 \r
539   if (throttle > 7) throttle = 8;\r
540 \r
541   char threshold[8], milliseconds[8];\r
542   _snprintf(threshold, sizeof(threshold), "%d", throttle_delay);\r
543   _snprintf(milliseconds, sizeof(milliseconds), "%d", ms);\r
544   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);\r
545 \r
546   if (throttle_timer) {\r
547     ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));\r
548     throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
549     SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);\r
550   }\r
551 \r
552   service_status.dwCurrentState = SERVICE_PAUSED;\r
553   SetServiceStatus(service_handle, &service_status);\r
554 \r
555   if (throttle_timer) WaitForSingleObject(throttle_timer, INFINITE);\r
556   else Sleep(ms);\r
557 }\r