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