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