94cca765d5e6239a992f636b61cd09c4d41d90c9
[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 exitcode = 3;\r
453     unsigned long error = GetLastError();\r
454     if (error == ERROR_INVALID_PARAMETER && service->env) {\r
455       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);\r
456       if (test_environment(service->env)) exitcode = 4;\r
457     }\r
458     else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\r
459     close_output_handles(&si);\r
460     return stop_service(service, exitcode, true, true);\r
461   }\r
462   service->process_handle = pi.hProcess;\r
463   service->pid = pi.dwProcessId;\r
464 \r
465   if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));\r
466 \r
467   close_output_handles(&si);\r
468 \r
469   /*\r
470     Wait for a clean startup before changing the service status to RUNNING\r
471     but be mindful of the fact that we are blocking the service control manager\r
472     so abandon the wait before too much time has elapsed.\r
473   */\r
474   unsigned long delay = service->throttle_delay;\r
475   if (delay > NSSM_SERVICE_STATUS_DEADLINE) {\r
476     TCHAR delay_milliseconds[16];\r
477     _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);\r
478     TCHAR deadline_milliseconds[16];\r
479     _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);\r
480     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);\r
481     delay = NSSM_SERVICE_STATUS_DEADLINE;\r
482   }\r
483   unsigned long deadline = WaitForSingleObject(service->process_handle, delay);\r
484 \r
485   /* Signal successful start */\r
486   service->status.dwCurrentState = SERVICE_RUNNING;\r
487   SetServiceStatus(service->status_handle, &service->status);\r
488 \r
489   /* Continue waiting for a clean startup. */\r
490   if (deadline == WAIT_TIMEOUT) {\r
491     if (service->throttle_delay > delay) {\r
492       if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;\r
493     }\r
494     else service->throttle = 0;\r
495   }\r
496 \r
497   return 0;\r
498 }\r
499 \r
500 /* Stop the service */\r
501 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {\r
502   service->allow_restart = false;\r
503   if (service->wait_handle) {\r
504     UnregisterWait(service->wait_handle);\r
505     service->wait_handle = 0;\r
506   }\r
507 \r
508   if (default_action && ! exitcode && ! graceful) {\r
509     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
510     graceful = true;\r
511   }\r
512 \r
513   /* Signal we are stopping */\r
514   if (graceful) {\r
515     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
516     service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
517     SetServiceStatus(service->status_handle, &service->status);\r
518   }\r
519 \r
520   /* Nothing to do if service isn't running */\r
521   if (service->pid) {\r
522     /* Shut down service */\r
523     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);\r
524     kill_process(service, service->process_handle, service->pid, 0);\r
525   }\r
526   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);\r
527 \r
528   end_service((void *) service, true);\r
529 \r
530   /* Signal we stopped */\r
531   if (graceful) {\r
532     service->status.dwCurrentState = SERVICE_STOPPED;\r
533     if (exitcode) {\r
534       service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
535       service->status.dwServiceSpecificExitCode = exitcode;\r
536     }\r
537     else {\r
538       service->status.dwWin32ExitCode = NO_ERROR;\r
539       service->status.dwServiceSpecificExitCode = 0;\r
540     }\r
541     SetServiceStatus(service->status_handle, &service->status);\r
542   }\r
543 \r
544   return exitcode;\r
545 }\r
546 \r
547 /* Callback function triggered when the server exits */\r
548 void CALLBACK end_service(void *arg, unsigned char why) {\r
549   nssm_service_t *service = (nssm_service_t *) arg;\r
550 \r
551   if (service->stopping) return;\r
552 \r
553   service->stopping = true;\r
554 \r
555   /* Check exit code */\r
556   unsigned long exitcode = 0;\r
557   TCHAR code[16];\r
558   if (service->process_handle) {\r
559     GetExitCodeProcess(service->process_handle, &exitcode);\r
560     if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);\r
561     CloseHandle(service->process_handle);\r
562   }\r
563   else GetSystemTimeAsFileTime(&service->exit_time);\r
564 \r
565   service->process_handle = 0;\r
566 \r
567   /*\r
568     Log that the service ended BEFORE logging about killing the process\r
569     tree.  See below for the possible values of the why argument.\r
570   */\r
571   if (! why) {\r
572     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);\r
573     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);\r
574   }\r
575 \r
576   /* Clean up. */\r
577   if (exitcode == STILL_ACTIVE) exitcode = 0;\r
578   if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);\r
579   service->pid = 0;\r
580 \r
581   /*\r
582     The why argument is true if our wait timed out or false otherwise.\r
583     Our wait is infinite so why will never be true when called by the system.\r
584     If it is indeed true, assume we were called from stop_service() because\r
585     this is a controlled shutdown, and don't take any restart action.\r
586   */\r
587   if (why) return;\r
588   if (! service->allow_restart) return;\r
589 \r
590   /* What action should we take? */\r
591   int action = NSSM_EXIT_RESTART;\r
592   TCHAR action_string[ACTION_LEN];\r
593   bool default_action;\r
594   if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {\r
595     for (int i = 0; exit_action_strings[i]; i++) {\r
596       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
597         action = i;\r
598         break;\r
599       }\r
600     }\r
601   }\r
602 \r
603   switch (action) {\r
604     /* Try to restart the service or return failure code to service manager */\r
605     case NSSM_EXIT_RESTART:\r
606       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);\r
607       while (monitor_service(service)) {\r
608         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);\r
609         Sleep(30000);\r
610       }\r
611     break;\r
612 \r
613     /* Do nothing, just like srvany would */\r
614     case NSSM_EXIT_IGNORE:\r
615       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);\r
616       Sleep(INFINITE);\r
617     break;\r
618 \r
619     /* Tell the service manager we are finished */\r
620     case NSSM_EXIT_REALLY:\r
621       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);\r
622       stop_service(service, exitcode, true, default_action);\r
623     break;\r
624 \r
625     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
626     case NSSM_EXIT_UNCLEAN:\r
627       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);\r
628       stop_service(service, exitcode, false, default_action);\r
629       free_imports();\r
630       exit(exitcode);\r
631     break;\r
632   }\r
633 }\r
634 \r
635 void throttle_restart(nssm_service_t *service) {\r
636   /* This can't be a restart if the service is already running. */\r
637   if (! service->throttle++) return;\r
638 \r
639   int ms = throttle_milliseconds(service->throttle);\r
640 \r
641   if (service->throttle > 7) service->throttle = 8;\r
642 \r
643   TCHAR threshold[8], milliseconds[8];\r
644   _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);\r
645   _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);\r
646   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);\r
647 \r
648   if (use_critical_section) EnterCriticalSection(&service->throttle_section);\r
649   else if (service->throttle_timer) {\r
650     ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
651     service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
652     SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
653   }\r
654 \r
655   service->status.dwCurrentState = SERVICE_PAUSED;\r
656   SetServiceStatus(service->status_handle, &service->status);\r
657 \r
658   if (use_critical_section) {\r
659     imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);\r
660     LeaveCriticalSection(&service->throttle_section);\r
661   }\r
662   else {\r
663     if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);\r
664     else Sleep(ms);\r
665   }\r
666 }\r
667 \r
668 /*\r
669   When responding to a stop (or any other) request we need to set dwWaitHint to\r
670   the number of milliseconds we expect the operation to take, and optionally\r
671   increase dwCheckPoint.  If dwWaitHint milliseconds elapses without the\r
672   operation completing or dwCheckPoint increasing, the system will consider the\r
673   service to be hung.\r
674 \r
675   However the system will consider the service to be hung after 30000\r
676   milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not\r
677   changed.  Therefore if we want to wait longer than that we must periodically\r
678   increase dwCheckPoint.\r
679 \r
680   Furthermore, it will consider the service to be hung after 60000 milliseconds\r
681   regardless of the value of dwCheckPoint unless dwWaitHint is increased every\r
682   time dwCheckPoint is also increased.\r
683 \r
684   Our strategy then is to retrieve the initial dwWaitHint and wait for\r
685   NSSM_SERVICE_STATUS_DEADLINE milliseconds.  If the process is still running\r
686   and we haven't finished waiting we increment dwCheckPoint and add whichever is\r
687   smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to\r
688   dwWaitHint.\r
689 \r
690   Only doing both these things will prevent the system from killing the service.\r
691 \r
692   Returns: 1 if the wait timed out.\r
693            0 if the wait completed.\r
694           -1 on error.\r
695 */\r
696 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {\r
697   unsigned long interval;\r
698   unsigned long waithint;\r
699   unsigned long ret;\r
700   unsigned long waited;\r
701   TCHAR interval_milliseconds[16];\r
702   TCHAR timeout_milliseconds[16];\r
703   TCHAR waited_milliseconds[16];\r
704   TCHAR *function = function_name;\r
705 \r
706   /* Add brackets to function name. */\r
707   size_t funclen = _tcslen(function_name) + 3;\r
708   TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));\r
709   if (func) {\r
710     if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;\r
711   }\r
712 \r
713   _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);\r
714 \r
715   waithint = service->status.dwWaitHint;\r
716   waited = 0;\r
717   while (waited < timeout) {\r
718     interval = timeout - waited;\r
719     if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;\r
720 \r
721     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
722     service->status.dwWaitHint += interval;\r
723     service->status.dwCheckPoint++;\r
724     SetServiceStatus(service->status_handle, &service->status);\r
725 \r
726     if (waited) {\r
727       _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);\r
728       _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);\r
729       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
730     }\r
731 \r
732     switch (WaitForSingleObject(service->process_handle, interval)) {\r
733       case WAIT_OBJECT_0:\r
734         ret = 0;\r
735         goto awaited;\r
736 \r
737       case WAIT_TIMEOUT:\r
738         ret = 1;\r
739       break;\r
740 \r
741       default:\r
742         ret = -1;\r
743         goto awaited;\r
744     }\r
745 \r
746     waited += interval;\r
747   }\r
748 \r
749 awaited:\r
750   if (func) HeapFree(GetProcessHeap(), 0, func);\r
751 \r
752   return ret;\r
753 }\r