Be less paranoid.
[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   GetExitCodeProcess(service->process_handle, &exitcode);\r
555   if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);\r
556   CloseHandle(service->process_handle);\r
557 \r
558   service->process_handle = 0;\r
559   service->pid = 0;\r
560 \r
561   /*\r
562     Log that the service ended BEFORE logging about killing the process\r
563     tree.  See below for the possible values of the why argument.\r
564   */\r
565   if (! why) {\r
566     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);\r
567     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);\r
568   }\r
569 \r
570   /* Clean up. */\r
571   if (exitcode == STILL_ACTIVE) exitcode = 0;\r
572   kill_process_tree(service, service->pid, exitcode, service->pid);\r
573 \r
574   /*\r
575     The why argument is true if our wait timed out or false otherwise.\r
576     Our wait is infinite so why will never be true when called by the system.\r
577     If it is indeed true, assume we were called from stop_service() because\r
578     this is a controlled shutdown, and don't take any restart action.\r
579   */\r
580   if (why) return;\r
581   if (! service->allow_restart) return;\r
582 \r
583   /* What action should we take? */\r
584   int action = NSSM_EXIT_RESTART;\r
585   TCHAR action_string[ACTION_LEN];\r
586   bool default_action;\r
587   if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {\r
588     for (int i = 0; exit_action_strings[i]; i++) {\r
589       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
590         action = i;\r
591         break;\r
592       }\r
593     }\r
594   }\r
595 \r
596   switch (action) {\r
597     /* Try to restart the service or return failure code to service manager */\r
598     case NSSM_EXIT_RESTART:\r
599       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);\r
600       while (monitor_service(service)) {\r
601         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);\r
602         Sleep(30000);\r
603       }\r
604     break;\r
605 \r
606     /* Do nothing, just like srvany would */\r
607     case NSSM_EXIT_IGNORE:\r
608       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);\r
609       Sleep(INFINITE);\r
610     break;\r
611 \r
612     /* Tell the service manager we are finished */\r
613     case NSSM_EXIT_REALLY:\r
614       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);\r
615       stop_service(service, exitcode, true, default_action);\r
616     break;\r
617 \r
618     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
619     case NSSM_EXIT_UNCLEAN:\r
620       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);\r
621       stop_service(service, exitcode, false, default_action);\r
622       free_imports();\r
623       exit(exitcode);\r
624     break;\r
625   }\r
626 }\r
627 \r
628 void throttle_restart(nssm_service_t *service) {\r
629   /* This can't be a restart if the service is already running. */\r
630   if (! service->throttle++) return;\r
631 \r
632   int ms = throttle_milliseconds(service->throttle);\r
633 \r
634   if (service->throttle > 7) service->throttle = 8;\r
635 \r
636   TCHAR threshold[8], milliseconds[8];\r
637   _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);\r
638   _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);\r
639   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);\r
640 \r
641   if (use_critical_section) EnterCriticalSection(&service->throttle_section);\r
642   else if (service->throttle_timer) {\r
643     ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
644     service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
645     SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
646   }\r
647 \r
648   service->status.dwCurrentState = SERVICE_PAUSED;\r
649   SetServiceStatus(service->status_handle, &service->status);\r
650 \r
651   if (use_critical_section) {\r
652     imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);\r
653     LeaveCriticalSection(&service->throttle_section);\r
654   }\r
655   else {\r
656     if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);\r
657     else Sleep(ms);\r
658   }\r
659 }\r
660 \r
661 /*\r
662   When responding to a stop (or any other) request we need to set dwWaitHint to\r
663   the number of milliseconds we expect the operation to take, and optionally\r
664   increase dwCheckPoint.  If dwWaitHint milliseconds elapses without the\r
665   operation completing or dwCheckPoint increasing, the system will consider the\r
666   service to be hung.\r
667 \r
668   However the system will consider the service to be hung after 30000\r
669   milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not\r
670   changed.  Therefore if we want to wait longer than that we must periodically\r
671   increase dwCheckPoint.\r
672 \r
673   Furthermore, it will consider the service to be hung after 60000 milliseconds\r
674   regardless of the value of dwCheckPoint unless dwWaitHint is increased every\r
675   time dwCheckPoint is also increased.\r
676 \r
677   Our strategy then is to retrieve the initial dwWaitHint and wait for\r
678   NSSM_SERVICE_STATUS_DEADLINE milliseconds.  If the process is still running\r
679   and we haven't finished waiting we increment dwCheckPoint and add whichever is\r
680   smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to\r
681   dwWaitHint.\r
682 \r
683   Only doing both these things will prevent the system from killing the service.\r
684 \r
685   Returns: 1 if the wait timed out.\r
686            0 if the wait completed.\r
687           -1 on error.\r
688 */\r
689 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {\r
690   unsigned long interval;\r
691   unsigned long waithint;\r
692   unsigned long ret;\r
693   unsigned long waited;\r
694   TCHAR interval_milliseconds[16];\r
695   TCHAR timeout_milliseconds[16];\r
696   TCHAR waited_milliseconds[16];\r
697   TCHAR *function = function_name;\r
698 \r
699   /* Add brackets to function name. */\r
700   size_t funclen = _tcslen(function_name) + 3;\r
701   TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));\r
702   if (func) {\r
703     if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;\r
704   }\r
705 \r
706   _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);\r
707 \r
708   waithint = service->status.dwWaitHint;\r
709   waited = 0;\r
710   while (waited < timeout) {\r
711     interval = timeout - waited;\r
712     if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;\r
713 \r
714     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
715     service->status.dwWaitHint += interval;\r
716     service->status.dwCheckPoint++;\r
717     SetServiceStatus(service->status_handle, &service->status);\r
718 \r
719     if (waited) {\r
720       _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);\r
721       _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);\r
722       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
723     }\r
724 \r
725     switch (WaitForSingleObject(service->process_handle, interval)) {\r
726       case WAIT_OBJECT_0:\r
727         ret = 0;\r
728         goto awaited;\r
729 \r
730       case WAIT_TIMEOUT:\r
731         ret = 1;\r
732       break;\r
733 \r
734       default:\r
735         ret = -1;\r
736         goto awaited;\r
737     }\r
738 \r
739     waited += interval;\r
740   }\r
741 \r
742 awaited:\r
743   if (func) HeapFree(GetProcessHeap(), 0, func);\r
744 \r
745   return ret;\r
746 }\r