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