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