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