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