Spawn a separate thread for stop_service().
[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   /* Wait for a clean startup. */\r
454   if (WaitForSingleObject(process_handle, throttle_delay) == WAIT_TIMEOUT) throttle = 0;\r
455 \r
456   /* Signal successful start */\r
457   service_status.dwCurrentState = SERVICE_RUNNING;\r
458   SetServiceStatus(service_handle, &service_status);\r
459 \r
460   return 0;\r
461 }\r
462 \r
463 /* Stop the service */\r
464 int stop_service(unsigned long exitcode, bool graceful, bool default_action) {\r
465   allow_restart = false;\r
466   if (wait_handle) UnregisterWait(wait_handle);\r
467 \r
468   if (default_action && ! exitcode && ! graceful) {\r
469     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
470     graceful = true;\r
471   }\r
472 \r
473   /* Signal we are stopping */\r
474   if (graceful) {\r
475     service_status.dwCurrentState = SERVICE_STOP_PENDING;\r
476     service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
477     SetServiceStatus(service_handle, &service_status);\r
478   }\r
479 \r
480   /* Nothing to do if service isn't running */\r
481   if (pid) {\r
482     /* Shut down service */\r
483     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);\r
484     kill_process(service_name, service_handle, &service_status, stop_method, process_handle, pid, 0);\r
485   }\r
486   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);\r
487 \r
488   end_service((void *) pid, true);\r
489 \r
490   /* Signal we stopped */\r
491   if (graceful) {\r
492     service_status.dwCurrentState = SERVICE_STOPPED;\r
493     if (exitcode) {\r
494       service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
495       service_status.dwServiceSpecificExitCode = exitcode;\r
496     }\r
497     else {\r
498       service_status.dwWin32ExitCode = NO_ERROR;\r
499       service_status.dwServiceSpecificExitCode = 0;\r
500     }\r
501     SetServiceStatus(service_handle, &service_status);\r
502   }\r
503 \r
504   return exitcode;\r
505 }\r
506 \r
507 /* Callback function triggered when the server exits */\r
508 void CALLBACK end_service(void *arg, unsigned char why) {\r
509   if (stopping) return;\r
510 \r
511   stopping = true;\r
512 \r
513   pid = (unsigned long) arg;\r
514 \r
515   /* Check exit code */\r
516   unsigned long exitcode = 0;\r
517   char code[16];\r
518   FILETIME exit_time;\r
519   GetExitCodeProcess(process_handle, &exitcode);\r
520   if (exitcode == STILL_ACTIVE || get_process_exit_time(process_handle, &exit_time)) GetSystemTimeAsFileTime(&exit_time);\r
521   CloseHandle(process_handle);\r
522 \r
523   /*\r
524     Log that the service ended BEFORE logging about killing the process\r
525     tree.  See below for the possible values of the why argument.\r
526   */\r
527   if (! why) {\r
528     _snprintf_s(code, sizeof(code), _TRUNCATE, "%lu", exitcode);\r
529     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);\r
530   }\r
531 \r
532   /* Clean up. */\r
533   if (exitcode == STILL_ACTIVE) exitcode = 0;\r
534   kill_process_tree(service_name, service_handle, &service_status, stop_method, pid, exitcode, pid, &creation_time, &exit_time);\r
535 \r
536   /*\r
537     The why argument is true if our wait timed out or false otherwise.\r
538     Our wait is infinite so why will never be true when called by the system.\r
539     If it is indeed true, assume we were called from stop_service() because\r
540     this is a controlled shutdown, and don't take any restart action.\r
541   */\r
542   if (why) return;\r
543   if (! allow_restart) return;\r
544 \r
545   /* What action should we take? */\r
546   int action = NSSM_EXIT_RESTART;\r
547   unsigned char action_string[ACTION_LEN];\r
548   bool default_action;\r
549   if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) {\r
550     for (int i = 0; exit_action_strings[i]; i++) {\r
551       if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
552         action = i;\r
553         break;\r
554       }\r
555     }\r
556   }\r
557 \r
558   process_handle = 0;\r
559   pid = 0;\r
560   switch (action) {\r
561     /* Try to restart the service or return failure code to service manager */\r
562     case NSSM_EXIT_RESTART:\r
563       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service_name, code, exit_action_strings[action], exe, 0);\r
564       while (monitor_service()) {\r
565         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, exe, service_name, 0);\r
566         Sleep(30000);\r
567       }\r
568     break;\r
569 \r
570     /* Do nothing, just like srvany would */\r
571     case NSSM_EXIT_IGNORE:\r
572       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service_name, code, exit_action_strings[action], exe, 0);\r
573       Sleep(INFINITE);\r
574     break;\r
575 \r
576     /* Tell the service manager we are finished */\r
577     case NSSM_EXIT_REALLY:\r
578       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);\r
579       stop_service(exitcode, true, default_action);\r
580     break;\r
581 \r
582     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
583     case NSSM_EXIT_UNCLEAN:\r
584       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);\r
585       stop_service(exitcode, false, default_action);\r
586       free_imports();\r
587       exit(exitcode);\r
588     break;\r
589   }\r
590 }\r
591 \r
592 void throttle_restart() {\r
593   /* This can't be a restart if the service is already running. */\r
594   if (! throttle++) return;\r
595 \r
596   int ms = throttle_milliseconds();\r
597 \r
598   if (throttle > 7) throttle = 8;\r
599 \r
600   char threshold[8], milliseconds[8];\r
601   _snprintf_s(threshold, sizeof(threshold), _TRUNCATE, "%lu", throttle_delay);\r
602   _snprintf_s(milliseconds, sizeof(milliseconds), _TRUNCATE, "%lu", ms);\r
603   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);\r
604 \r
605   if (use_critical_section) EnterCriticalSection(&throttle_section);\r
606   else if (throttle_timer) {\r
607     ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));\r
608     throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
609     SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);\r
610   }\r
611 \r
612   service_status.dwCurrentState = SERVICE_PAUSED;\r
613   SetServiceStatus(service_handle, &service_status);\r
614 \r
615   if (use_critical_section) {\r
616     imports.SleepConditionVariableCS(&throttle_condition, &throttle_section, ms);\r
617     LeaveCriticalSection(&throttle_section);\r
618   }\r
619   else {\r
620     if (throttle_timer) WaitForSingleObject(throttle_timer, INFINITE);\r
621     else Sleep(ms);\r
622   }\r
623 }\r
624 \r
625 /*\r
626   When responding to a stop (or any other) request we need to set dwWaitHint to\r
627   the number of milliseconds we expect the operation to take, and optionally\r
628   increase dwCheckPoint.  If dwWaitHint milliseconds elapses without the\r
629   operation completing or dwCheckPoint increasing, the system will consider the\r
630   service to be hung.\r
631 \r
632   However the system will consider the service to be hung after 30000\r
633   milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not\r
634   changed.  Therefore if we want to wait longer than that we must periodically\r
635   increase dwCheckPoint.\r
636 \r
637   Furthermore, it will consider the service to be hung after 60000 milliseconds\r
638   regardless of the value of dwCheckPoint unless dwWaitHint is increased every\r
639   time dwCheckPoint is also increased.\r
640 \r
641   Our strategy then is to retrieve the initial dwWaitHint and wait for\r
642   NSSM_SHUTDOWN_CHECKPOINT milliseconds.  If the process is still running and\r
643   we haven't finished waiting we increment dwCheckPoint and add whichever is\r
644   smaller of NSSM_SHUTDOWN_CHECKPOINT or the remaining timeout to dwWaitHint.\r
645 \r
646   Only doing both these things will prevent the system from killing the service.\r
647 \r
648   Returns: 1 if the wait timed out.\r
649            0 if the wait completed.\r
650           -1 on error.\r
651 */\r
652 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
653   unsigned long interval;\r
654   unsigned long waithint;\r
655   unsigned long ret;\r
656   unsigned long waited;\r
657   char interval_milliseconds[16];\r
658   char timeout_milliseconds[16];\r
659   char waited_milliseconds[16];\r
660   char *function = function_name;\r
661 \r
662   /* Add brackets to function name. */\r
663   size_t funclen = strlen(function_name) + 3;\r
664   char *func = (char *) HeapAlloc(GetProcessHeap(), 0, funclen);\r
665   if (func) {\r
666     if (_snprintf_s(func, funclen, _TRUNCATE, "%s()", function_name) > -1) function = func;\r
667   }\r
668 \r
669   _snprintf_s(timeout_milliseconds, sizeof(timeout_milliseconds), _TRUNCATE, "%lu", timeout);\r
670 \r
671   waithint = service_status->dwWaitHint;\r
672   waited = 0;\r
673   while (waited < timeout) {\r
674     interval = timeout - waited;\r
675     if (interval > NSSM_SHUTDOWN_CHECKPOINT) interval = NSSM_SHUTDOWN_CHECKPOINT;\r
676 \r
677     service_status->dwCurrentState = SERVICE_STOP_PENDING;\r
678     service_status->dwWaitHint += interval;\r
679     service_status->dwCheckPoint++;\r
680     SetServiceStatus(service_handle, service_status);\r
681 \r
682     if (waited) {\r
683       _snprintf_s(waited_milliseconds, sizeof(waited_milliseconds), _TRUNCATE, "%lu", waited);\r
684       _snprintf_s(interval_milliseconds, sizeof(interval_milliseconds), _TRUNCATE, "%lu", interval);\r
685       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service_name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
686     }\r
687 \r
688     switch (WaitForSingleObject(process_handle, interval)) {\r
689       case WAIT_OBJECT_0:\r
690         ret = 0;\r
691         goto awaited;\r
692 \r
693       case WAIT_TIMEOUT:\r
694         ret = 1;\r
695       break;\r
696 \r
697       default:\r
698         ret = -1;\r
699         goto awaited;\r
700     }\r
701 \r
702     waited += interval;\r
703   }\r
704 \r
705 awaited:\r
706   if (func) HeapFree(GetProcessHeap(), 0, func);\r
707 \r
708   return ret;\r
709 }\r