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