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