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