Try to throttle using a critical section.
[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 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_KILL_WINDOW_GRACE_PERIOD + NSSM_KILL_THREADS_GRACE_PERIOD + NSSM_WAITHINT_MARGIN;\r
449     SetServiceStatus(service_handle, &service_status);\r
450   }\r
451 \r
452   /* Nothing to do if service isn't running */\r
453   if (pid) {\r
454     /* Shut down service */\r
455     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);\r
456     kill_process(service_name, stop_method, process_handle, pid, 0);\r
457   }\r
458   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);\r
459 \r
460   end_service((void *) pid, true);\r
461 \r
462   /* Signal we stopped */\r
463   if (graceful) {\r
464     service_status.dwCurrentState = SERVICE_STOPPED;\r
465     if (exitcode) {\r
466       service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
467       service_status.dwServiceSpecificExitCode = exitcode;\r
468     }\r
469     else {\r
470       service_status.dwWin32ExitCode = NO_ERROR;\r
471       service_status.dwServiceSpecificExitCode = 0;\r
472     }\r
473     SetServiceStatus(service_handle, &service_status);\r
474   }\r
475 \r
476   return exitcode;\r
477 }\r
478 \r
479 /* Callback function triggered when the server exits */\r
480 void CALLBACK end_service(void *arg, unsigned char why) {\r
481   if (stopping) return;\r
482 \r
483   stopping = true;\r
484 \r
485   pid = (unsigned long) arg;\r
486 \r
487   /* Check exit code */\r
488   unsigned long exitcode = 0;\r
489   char code[16];\r
490   FILETIME exit_time;\r
491   GetExitCodeProcess(process_handle, &exitcode);\r
492   if (exitcode == STILL_ACTIVE || get_process_exit_time(process_handle, &exit_time)) GetSystemTimeAsFileTime(&exit_time);\r
493   CloseHandle(process_handle);\r
494 \r
495   /*\r
496     Log that the service ended BEFORE logging about killing the process\r
497     tree.  See below for the possible values of the why argument.\r
498   */\r
499   if (! why) {\r
500     _snprintf_s(code, sizeof(code), _TRUNCATE, "%d", exitcode);\r
501     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);\r
502   }\r
503 \r
504   /* Clean up. */\r
505   kill_process_tree(service_name, stop_method, pid, exitcode, pid, &creation_time, &exit_time);\r
506 \r
507   /*\r
508     The why argument is true if our wait timed out or false otherwise.\r
509     Our wait is infinite so why will never be true when called by the system.\r
510     If it is indeed true, assume we were called from stop_service() because\r
511     this is a controlled shutdown, and don't take any restart action.\r
512   */\r
513   if (why) return;\r
514   if (! allow_restart) return;\r
515 \r
516   /* What action should we take? */\r
517   int action = NSSM_EXIT_RESTART;\r
518   unsigned char action_string[ACTION_LEN];\r
519   bool default_action;\r
520   if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) {\r
521     for (int i = 0; exit_action_strings[i]; i++) {\r
522       if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
523         action = i;\r
524         break;\r
525       }\r
526     }\r
527   }\r
528 \r
529   process_handle = 0;\r
530   pid = 0;\r
531   switch (action) {\r
532     /* Try to restart the service or return failure code to service manager */\r
533     case NSSM_EXIT_RESTART:\r
534       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service_name, code, exit_action_strings[action], exe, 0);\r
535       while (monitor_service()) {\r
536         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, exe, service_name, 0);\r
537         Sleep(30000);\r
538       }\r
539     break;\r
540 \r
541     /* Do nothing, just like srvany would */\r
542     case NSSM_EXIT_IGNORE:\r
543       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service_name, code, exit_action_strings[action], exe, 0);\r
544       Sleep(INFINITE);\r
545     break;\r
546 \r
547     /* Tell the service manager we are finished */\r
548     case NSSM_EXIT_REALLY:\r
549       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);\r
550       stop_service(exitcode, true, default_action);\r
551     break;\r
552 \r
553     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
554     case NSSM_EXIT_UNCLEAN:\r
555       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);\r
556       stop_service(exitcode, false, default_action);\r
557       free_imports();\r
558       exit(exitcode);\r
559     break;\r
560   }\r
561 }\r
562 \r
563 void throttle_restart() {\r
564   /* This can't be a restart if the service is already running. */\r
565   if (! throttle++) return;\r
566 \r
567   int ms = throttle_milliseconds();\r
568 \r
569   if (throttle > 7) throttle = 8;\r
570 \r
571   char threshold[8], milliseconds[8];\r
572   _snprintf_s(threshold, sizeof(threshold), _TRUNCATE, "%d", throttle_delay);\r
573   _snprintf_s(milliseconds, sizeof(milliseconds), _TRUNCATE, "%d", ms);\r
574   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);\r
575 \r
576   if (use_critical_section) EnterCriticalSection(&throttle_section);\r
577   else if (throttle_timer) {\r
578     ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));\r
579     throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
580     SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);\r
581   }\r
582 \r
583   service_status.dwCurrentState = SERVICE_PAUSED;\r
584   SetServiceStatus(service_handle, &service_status);\r
585 \r
586   if (use_critical_section) {\r
587     imports.SleepConditionVariableCS(&throttle_condition, &throttle_section, ms);\r
588     LeaveCriticalSection(&throttle_section);\r
589   }\r
590   else {\r
591     if (throttle_timer) WaitForSingleObject(throttle_timer, INFINITE);\r
592     else Sleep(ms);\r
593   }\r
594 }\r