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