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