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