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