4 bool use_critical_section;
\r
6 extern imports_t imports;
\r
8 const TCHAR *exit_action_strings[] = { _T("Restart"), _T("Ignore"), _T("Exit"), _T("Suicide"), 0 };
\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
17 Wrapper to be called in a new thread so that we can acknowledge a STOP
\r
18 control immediately.
\r
20 static unsigned long WINAPI shutdown_service(void *arg) {
\r
21 return stop_service((nssm_service_t *) arg, 0, true, true);
\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
28 if (is_admin) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);
\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
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
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
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
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
80 nssm_service_t *service = alloc_nssm_service();
\r
82 print_message(stderr, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("pre_install_service()"));
\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
90 /* Arguments are optional */
\r
91 size_t flagslen = 0;
\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
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
105 if (i < argc - 1) service->flags[s++] = _T(' ');
\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
112 int ret = install_service(service);
\r
113 cleanup_nssm_service(service);
\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
128 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
132 /* Install the service */
\r
133 int install_service(nssm_service_t *service) {
\r
134 if (! service) return 1;
\r
136 /* Open service manager */
\r
137 SC_HANDLE services = open_service_manager();
\r
139 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
140 cleanup_nssm_service(service);
\r
144 /* Get path of this program */
\r
145 TCHAR command[MAX_PATH];
\r
146 GetModuleFileName(0, command, _countof(command));
\r
148 /* Create the service */
\r
149 service->handle = CreateService(services, service->name, service->name, SC_MANAGER_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, command, 0, 0, 0, 0, 0);
\r
150 if (! service->handle) {
\r
151 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
152 CloseServiceHandle(services);
\r
156 /* Now we need to put the parameters into the registry */
\r
157 if (create_parameters(service)) {
\r
158 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
159 DeleteService(service->handle);
\r
160 CloseServiceHandle(services);
\r
164 set_service_recovery(service);
\r
166 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
\r
169 CloseServiceHandle(services);
\r
174 /* Remove the service */
\r
175 int remove_service(nssm_service_t *service) {
\r
176 if (! service) return 1;
\r
178 /* Open service manager */
\r
179 SC_HANDLE services = open_service_manager();
\r
181 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
185 /* Try to open the service */
\r
186 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
187 if (! service->handle) {
\r
188 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
189 CloseServiceHandle(services);
\r
193 /* Try to delete the service */
\r
194 if (! DeleteService(service->handle)) {
\r
195 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
196 CloseServiceHandle(services);
\r
201 CloseServiceHandle(services);
\r
203 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
\r
207 /* Service initialisation */
\r
208 void WINAPI service_main(unsigned long argc, TCHAR **argv) {
\r
209 nssm_service_t *service = alloc_nssm_service();
\r
210 if (! service) return;
\r
212 if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {
\r
213 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);
\r
217 /* We can use a condition variable in a critical section on Vista or later. */
\r
218 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
219 else use_critical_section = false;
\r
221 /* Initialise status */
\r
222 ZeroMemory(&service->status, sizeof(service->status));
\r
223 service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
224 service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
225 service->status.dwWin32ExitCode = NO_ERROR;
\r
226 service->status.dwServiceSpecificExitCode = 0;
\r
227 service->status.dwCheckPoint = 0;
\r
228 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
230 /* Signal we AREN'T running the server */
\r
231 service->process_handle = 0;
\r
234 /* Register control handler */
\r
235 service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);
\r
236 if (! service->status_handle) {
\r
237 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
241 log_service_control(service->name, 0, true);
\r
243 service->status.dwCurrentState = SERVICE_START_PENDING;
\r
244 service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;
\r
245 SetServiceStatus(service->status_handle, &service->status);
\r
248 /* Try to create the exit action parameters; we don't care if it fails */
\r
249 create_exit_action(service->name, exit_action_strings[0]);
\r
251 SC_HANDLE services = open_service_manager();
\r
253 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
254 set_service_recovery(service);
\r
255 CloseServiceHandle(services);
\r
259 /* Used for signalling a resume if the service pauses when throttled. */
\r
260 if (use_critical_section) {
\r
261 InitializeCriticalSection(&service->throttle_section);
\r
262 service->throttle_section_initialised = true;
\r
265 service->throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
266 if (! service->throttle_timer) {
\r
267 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);
\r
271 monitor_service(service);
\r
274 /* Make sure service recovery actions are taken where necessary */
\r
275 void set_service_recovery(nssm_service_t *service) {
\r
276 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
277 ZeroMemory(&flag, sizeof(flag));
\r
278 flag.fFailureActionsOnNonCrashFailures = true;
\r
280 /* This functionality was added in Vista so the call may fail */
\r
281 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
282 unsigned long error = GetLastError();
\r
283 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
284 if (error != ERROR_INVALID_LEVEL) {
\r
285 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CHANGESERVICECONFIG2_FAILED, service->name, error_string(error), 0);
\r
290 int monitor_service(nssm_service_t *service) {
\r
291 /* Set service status to started */
\r
292 int ret = start_service(service);
\r
295 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);
\r
296 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);
\r
299 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);
\r
301 /* Monitor service */
\r
302 if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
303 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);
\r
309 TCHAR *service_control_text(unsigned long control) {
\r
311 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
312 case 0: return _T("START");
\r
313 case SERVICE_CONTROL_STOP: return _T("STOP");
\r
314 case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");
\r
315 case SERVICE_CONTROL_PAUSE: return _T("PAUSE");
\r
316 case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");
\r
317 case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");
\r
322 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {
\r
323 TCHAR *text = service_control_text(control);
\r
324 unsigned long event;
\r
327 /* "0x" + 8 x hex + NULL */
\r
328 text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));
\r
330 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
333 if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {
\r
334 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
335 HeapFree(GetProcessHeap(), 0, text);
\r
339 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
341 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
342 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
344 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
346 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
347 HeapFree(GetProcessHeap(), 0, text);
\r
351 /* Service control handler */
\r
352 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
353 nssm_service_t *service = (nssm_service_t *) context;
\r
356 case SERVICE_CONTROL_INTERROGATE:
\r
357 /* We always keep the service status up-to-date so this is a no-op. */
\r
360 case SERVICE_CONTROL_SHUTDOWN:
\r
361 case SERVICE_CONTROL_STOP:
\r
362 log_service_control(service->name, control, true);
\r
364 We MUST acknowledge the stop request promptly but we're committed to
\r
365 waiting for the application to exit. Spawn a new thread to wait
\r
366 while we acknowledge the request.
\r
368 if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {
\r
369 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
\r
372 We couldn't create a thread to tidy up so we'll have to force the tidyup
\r
373 to complete in time in this thread.
\r
375 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
376 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
377 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
379 stop_service(service, 0, true, true);
\r
383 case SERVICE_CONTROL_CONTINUE:
\r
384 log_service_control(service->name, control, true);
\r
385 service->throttle = 0;
\r
386 if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);
\r
388 if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
389 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
390 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
392 service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
393 service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;
\r
394 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);
\r
395 SetServiceStatus(service->status_handle, &service->status);
\r
398 case SERVICE_CONTROL_PAUSE:
\r
400 We don't accept pause messages but it isn't possible to register
\r
401 only for continue messages so we have to handle this case.
\r
403 log_service_control(service->name, control, false);
\r
404 return ERROR_CALL_NOT_IMPLEMENTED;
\r
407 /* Unknown control */
\r
408 log_service_control(service->name, control, false);
\r
409 return ERROR_CALL_NOT_IMPLEMENTED;
\r
412 /* Start the service */
\r
413 int start_service(nssm_service_t *service) {
\r
414 service->stopping = false;
\r
415 service->allow_restart = true;
\r
417 if (service->process_handle) return 0;
\r
419 /* Allocate a STARTUPINFO structure for a new process */
\r
421 ZeroMemory(&si, sizeof(si));
\r
422 si.cb = sizeof(si);
\r
424 /* Allocate a PROCESSINFO structure for the process */
\r
425 PROCESS_INFORMATION pi;
\r
426 ZeroMemory(&pi, sizeof(pi));
\r
428 /* Get startup parameters */
\r
429 int ret = get_parameters(service, &si);
\r
431 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);
\r
432 return stop_service(service, 2, true, true);
\r
435 /* Launch executable with arguments */
\r
436 TCHAR cmd[CMD_LENGTH];
\r
437 if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {
\r
438 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);
\r
439 close_output_handles(&si);
\r
440 return stop_service(service, 2, true, true);
\r
443 throttle_restart(service);
\r
445 bool inherit_handles = false;
\r
446 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
447 unsigned long flags = 0;
\r
449 flags |= CREATE_UNICODE_ENVIRONMENT;
\r
451 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {
\r
452 unsigned long exitcode = 3;
\r
453 unsigned long error = GetLastError();
\r
454 if (error == ERROR_INVALID_PARAMETER && service->env) {
\r
455 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);
\r
456 if (test_environment(service->env)) exitcode = 4;
\r
458 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);
\r
459 close_output_handles(&si);
\r
460 return stop_service(service, exitcode, true, true);
\r
462 service->process_handle = pi.hProcess;
\r
463 service->pid = pi.dwProcessId;
\r
465 if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
\r
467 close_output_handles(&si);
\r
470 Wait for a clean startup before changing the service status to RUNNING
\r
471 but be mindful of the fact that we are blocking the service control manager
\r
472 so abandon the wait before too much time has elapsed.
\r
474 unsigned long delay = service->throttle_delay;
\r
475 if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
\r
476 TCHAR delay_milliseconds[16];
\r
477 _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);
\r
478 TCHAR deadline_milliseconds[16];
\r
479 _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);
\r
480 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
\r
481 delay = NSSM_SERVICE_STATUS_DEADLINE;
\r
483 unsigned long deadline = WaitForSingleObject(service->process_handle, delay);
\r
485 /* Signal successful start */
\r
486 service->status.dwCurrentState = SERVICE_RUNNING;
\r
487 SetServiceStatus(service->status_handle, &service->status);
\r
489 /* Continue waiting for a clean startup. */
\r
490 if (deadline == WAIT_TIMEOUT) {
\r
491 if (service->throttle_delay > delay) {
\r
492 if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;
\r
494 else service->throttle = 0;
\r
500 /* Stop the service */
\r
501 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {
\r
502 service->allow_restart = false;
\r
503 if (service->wait_handle) {
\r
504 UnregisterWait(service->wait_handle);
\r
505 service->wait_handle = 0;
\r
508 if (default_action && ! exitcode && ! graceful) {
\r
509 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_GRACEFUL_SUICIDE, service->name, service->exe, exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_REALLY], 0);
\r
513 /* Signal we are stopping */
\r
515 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
516 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
517 SetServiceStatus(service->status_handle, &service->status);
\r
520 /* Nothing to do if service isn't running */
\r
521 if (service->pid) {
\r
522 /* Shut down service */
\r
523 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);
\r
524 kill_process(service, service->process_handle, service->pid, 0);
\r
526 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
\r
528 end_service((void *) service, true);
\r
530 /* Signal we stopped */
\r
532 service->status.dwCurrentState = SERVICE_STOPPED;
\r
534 service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
535 service->status.dwServiceSpecificExitCode = exitcode;
\r
538 service->status.dwWin32ExitCode = NO_ERROR;
\r
539 service->status.dwServiceSpecificExitCode = 0;
\r
541 SetServiceStatus(service->status_handle, &service->status);
\r
547 /* Callback function triggered when the server exits */
\r
548 void CALLBACK end_service(void *arg, unsigned char why) {
\r
549 nssm_service_t *service = (nssm_service_t *) arg;
\r
551 if (service->stopping) return;
\r
553 service->stopping = true;
\r
555 /* Check exit code */
\r
556 unsigned long exitcode = 0;
\r
558 if (service->process_handle) {
\r
559 GetExitCodeProcess(service->process_handle, &exitcode);
\r
560 if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);
\r
561 CloseHandle(service->process_handle);
\r
563 else GetSystemTimeAsFileTime(&service->exit_time);
\r
565 service->process_handle = 0;
\r
568 Log that the service ended BEFORE logging about killing the process
\r
569 tree. See below for the possible values of the why argument.
\r
572 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);
\r
573 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);
\r
577 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
578 if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);
\r
582 The why argument is true if our wait timed out or false otherwise.
\r
583 Our wait is infinite so why will never be true when called by the system.
\r
584 If it is indeed true, assume we were called from stop_service() because
\r
585 this is a controlled shutdown, and don't take any restart action.
\r
588 if (! service->allow_restart) return;
\r
590 /* What action should we take? */
\r
591 int action = NSSM_EXIT_RESTART;
\r
592 TCHAR action_string[ACTION_LEN];
\r
593 bool default_action;
\r
594 if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {
\r
595 for (int i = 0; exit_action_strings[i]; i++) {
\r
596 if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
604 /* Try to restart the service or return failure code to service manager */
\r
605 case NSSM_EXIT_RESTART:
\r
606 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);
\r
607 while (monitor_service(service)) {
\r
608 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);
\r
613 /* Do nothing, just like srvany would */
\r
614 case NSSM_EXIT_IGNORE:
\r
615 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);
\r
619 /* Tell the service manager we are finished */
\r
620 case NSSM_EXIT_REALLY:
\r
621 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);
\r
622 stop_service(service, exitcode, true, default_action);
\r
625 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
626 case NSSM_EXIT_UNCLEAN:
\r
627 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);
\r
628 stop_service(service, exitcode, false, default_action);
\r
635 void throttle_restart(nssm_service_t *service) {
\r
636 /* This can't be a restart if the service is already running. */
\r
637 if (! service->throttle++) return;
\r
639 int ms = throttle_milliseconds(service->throttle);
\r
641 if (service->throttle > 7) service->throttle = 8;
\r
643 TCHAR threshold[8], milliseconds[8];
\r
644 _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);
\r
645 _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);
\r
646 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
\r
648 if (use_critical_section) EnterCriticalSection(&service->throttle_section);
\r
649 else if (service->throttle_timer) {
\r
650 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
651 service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
652 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
655 service->status.dwCurrentState = SERVICE_PAUSED;
\r
656 SetServiceStatus(service->status_handle, &service->status);
\r
658 if (use_critical_section) {
\r
659 imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);
\r
660 LeaveCriticalSection(&service->throttle_section);
\r
663 if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);
\r
669 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
670 the number of milliseconds we expect the operation to take, and optionally
\r
671 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
672 operation completing or dwCheckPoint increasing, the system will consider the
\r
673 service to be hung.
\r
675 However the system will consider the service to be hung after 30000
\r
676 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
677 changed. Therefore if we want to wait longer than that we must periodically
\r
678 increase dwCheckPoint.
\r
680 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
681 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
682 time dwCheckPoint is also increased.
\r
684 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
685 NSSM_SERVICE_STATUS_DEADLINE milliseconds. If the process is still running
\r
686 and we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
687 smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to
\r
690 Only doing both these things will prevent the system from killing the service.
\r
692 Returns: 1 if the wait timed out.
\r
693 0 if the wait completed.
\r
696 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {
\r
697 unsigned long interval;
\r
698 unsigned long waithint;
\r
700 unsigned long waited;
\r
701 TCHAR interval_milliseconds[16];
\r
702 TCHAR timeout_milliseconds[16];
\r
703 TCHAR waited_milliseconds[16];
\r
704 TCHAR *function = function_name;
\r
706 /* Add brackets to function name. */
\r
707 size_t funclen = _tcslen(function_name) + 3;
\r
708 TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));
\r
710 if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;
\r
713 _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);
\r
715 waithint = service->status.dwWaitHint;
\r
717 while (waited < timeout) {
\r
718 interval = timeout - waited;
\r
719 if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
\r
721 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
722 service->status.dwWaitHint += interval;
\r
723 service->status.dwCheckPoint++;
\r
724 SetServiceStatus(service->status_handle, &service->status);
\r
727 _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);
\r
728 _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);
\r
729 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
732 switch (WaitForSingleObject(service->process_handle, interval)) {
\r
733 case WAIT_OBJECT_0:
\r
746 waited += interval;
\r
750 if (func) HeapFree(GetProcessHeap(), 0, func);
\r