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 /* Startup type. */
\r
149 unsigned long startup;
\r
150 switch (service->startup) {
\r
151 case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;
\r
152 case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;
\r
153 default: startup = SERVICE_AUTO_START;
\r
156 /* Display name. */
\r
157 if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);
\r
159 /* Create the service */
\r
160 service->handle = CreateService(services, service->name, service->displayname, SC_MANAGER_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, startup, SERVICE_ERROR_NORMAL, command, 0, 0, 0, 0, 0);
\r
161 if (! service->handle) {
\r
162 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
163 CloseServiceHandle(services);
\r
167 if (service->description[0]) {
\r
168 SERVICE_DESCRIPTION description;
\r
169 ZeroMemory(&description, sizeof(description));
\r
170 description.lpDescription = service->description;
\r
171 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, &description)) {
\r
172 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service->name, error_string(GetLastError()), 0);
\r
176 if (service->startup == NSSM_STARTUP_DELAYED) {
\r
177 SERVICE_DELAYED_AUTO_START_INFO delayed;
\r
178 ZeroMemory(&delayed, sizeof(delayed));
\r
179 delayed.fDelayedAutostart = 1;
\r
180 /* Delayed startup isn't supported until Vista. */
\r
181 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {
\r
182 unsigned long error = GetLastError();
\r
183 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
184 if (error != ERROR_INVALID_LEVEL) {
\r
185 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);
\r
190 /* Now we need to put the parameters into the registry */
\r
191 if (create_parameters(service)) {
\r
192 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
193 DeleteService(service->handle);
\r
194 CloseServiceHandle(services);
\r
198 set_service_recovery(service);
\r
200 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
\r
203 CloseServiceHandle(services);
\r
208 /* Remove the service */
\r
209 int remove_service(nssm_service_t *service) {
\r
210 if (! service) return 1;
\r
212 /* Open service manager */
\r
213 SC_HANDLE services = open_service_manager();
\r
215 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
219 /* Try to open the service */
\r
220 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
221 if (! service->handle) {
\r
222 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
223 CloseServiceHandle(services);
\r
227 /* Try to delete the service */
\r
228 if (! DeleteService(service->handle)) {
\r
229 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
230 CloseServiceHandle(services);
\r
235 CloseServiceHandle(services);
\r
237 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
\r
241 /* Service initialisation */
\r
242 void WINAPI service_main(unsigned long argc, TCHAR **argv) {
\r
243 nssm_service_t *service = alloc_nssm_service();
\r
244 if (! service) return;
\r
246 if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {
\r
247 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);
\r
251 /* We can use a condition variable in a critical section on Vista or later. */
\r
252 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
253 else use_critical_section = false;
\r
255 /* Initialise status */
\r
256 ZeroMemory(&service->status, sizeof(service->status));
\r
257 service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
258 service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
259 service->status.dwWin32ExitCode = NO_ERROR;
\r
260 service->status.dwServiceSpecificExitCode = 0;
\r
261 service->status.dwCheckPoint = 0;
\r
262 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
264 /* Signal we AREN'T running the server */
\r
265 service->process_handle = 0;
\r
268 /* Register control handler */
\r
269 service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);
\r
270 if (! service->status_handle) {
\r
271 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
275 log_service_control(service->name, 0, true);
\r
277 service->status.dwCurrentState = SERVICE_START_PENDING;
\r
278 service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;
\r
279 SetServiceStatus(service->status_handle, &service->status);
\r
282 /* Try to create the exit action parameters; we don't care if it fails */
\r
283 create_exit_action(service->name, exit_action_strings[0]);
\r
285 SC_HANDLE services = open_service_manager();
\r
287 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
288 set_service_recovery(service);
\r
289 CloseServiceHandle(services);
\r
293 /* Used for signalling a resume if the service pauses when throttled. */
\r
294 if (use_critical_section) {
\r
295 InitializeCriticalSection(&service->throttle_section);
\r
296 service->throttle_section_initialised = true;
\r
299 service->throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
300 if (! service->throttle_timer) {
\r
301 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);
\r
305 monitor_service(service);
\r
308 /* Make sure service recovery actions are taken where necessary */
\r
309 void set_service_recovery(nssm_service_t *service) {
\r
310 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
311 ZeroMemory(&flag, sizeof(flag));
\r
312 flag.fFailureActionsOnNonCrashFailures = true;
\r
314 /* This functionality was added in Vista so the call may fail */
\r
315 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
316 unsigned long error = GetLastError();
\r
317 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
318 if (error != ERROR_INVALID_LEVEL) {
\r
319 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);
\r
324 int monitor_service(nssm_service_t *service) {
\r
325 /* Set service status to started */
\r
326 int ret = start_service(service);
\r
329 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);
\r
330 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);
\r
333 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);
\r
335 /* Monitor service */
\r
336 if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
337 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);
\r
343 TCHAR *service_control_text(unsigned long control) {
\r
345 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
346 case 0: return _T("START");
\r
347 case SERVICE_CONTROL_STOP: return _T("STOP");
\r
348 case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");
\r
349 case SERVICE_CONTROL_PAUSE: return _T("PAUSE");
\r
350 case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");
\r
351 case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");
\r
356 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {
\r
357 TCHAR *text = service_control_text(control);
\r
358 unsigned long event;
\r
361 /* "0x" + 8 x hex + NULL */
\r
362 text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));
\r
364 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
367 if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {
\r
368 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
369 HeapFree(GetProcessHeap(), 0, text);
\r
373 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
375 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
376 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
378 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
380 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
381 HeapFree(GetProcessHeap(), 0, text);
\r
385 /* Service control handler */
\r
386 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
387 nssm_service_t *service = (nssm_service_t *) context;
\r
390 case SERVICE_CONTROL_INTERROGATE:
\r
391 /* We always keep the service status up-to-date so this is a no-op. */
\r
394 case SERVICE_CONTROL_SHUTDOWN:
\r
395 case SERVICE_CONTROL_STOP:
\r
396 log_service_control(service->name, control, true);
\r
398 We MUST acknowledge the stop request promptly but we're committed to
\r
399 waiting for the application to exit. Spawn a new thread to wait
\r
400 while we acknowledge the request.
\r
402 if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {
\r
403 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
\r
406 We couldn't create a thread to tidy up so we'll have to force the tidyup
\r
407 to complete in time in this thread.
\r
409 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
410 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
411 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
413 stop_service(service, 0, true, true);
\r
417 case SERVICE_CONTROL_CONTINUE:
\r
418 log_service_control(service->name, control, true);
\r
419 service->throttle = 0;
\r
420 if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);
\r
422 if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
423 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
424 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
426 service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
427 service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;
\r
428 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);
\r
429 SetServiceStatus(service->status_handle, &service->status);
\r
432 case SERVICE_CONTROL_PAUSE:
\r
434 We don't accept pause messages but it isn't possible to register
\r
435 only for continue messages so we have to handle this case.
\r
437 log_service_control(service->name, control, false);
\r
438 return ERROR_CALL_NOT_IMPLEMENTED;
\r
441 /* Unknown control */
\r
442 log_service_control(service->name, control, false);
\r
443 return ERROR_CALL_NOT_IMPLEMENTED;
\r
446 /* Start the service */
\r
447 int start_service(nssm_service_t *service) {
\r
448 service->stopping = false;
\r
449 service->allow_restart = true;
\r
451 if (service->process_handle) return 0;
\r
453 /* Allocate a STARTUPINFO structure for a new process */
\r
455 ZeroMemory(&si, sizeof(si));
\r
456 si.cb = sizeof(si);
\r
458 /* Allocate a PROCESSINFO structure for the process */
\r
459 PROCESS_INFORMATION pi;
\r
460 ZeroMemory(&pi, sizeof(pi));
\r
462 /* Get startup parameters */
\r
463 int ret = get_parameters(service, &si);
\r
465 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);
\r
466 return stop_service(service, 2, true, true);
\r
469 /* Launch executable with arguments */
\r
470 TCHAR cmd[CMD_LENGTH];
\r
471 if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {
\r
472 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);
\r
473 close_output_handles(&si);
\r
474 return stop_service(service, 2, true, true);
\r
477 throttle_restart(service);
\r
479 bool inherit_handles = false;
\r
480 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
481 unsigned long flags = 0;
\r
483 flags |= CREATE_UNICODE_ENVIRONMENT;
\r
485 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {
\r
486 unsigned long exitcode = 3;
\r
487 unsigned long error = GetLastError();
\r
488 if (error == ERROR_INVALID_PARAMETER && service->env) {
\r
489 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);
\r
490 if (test_environment(service->env)) exitcode = 4;
\r
492 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);
\r
493 close_output_handles(&si);
\r
494 return stop_service(service, exitcode, true, true);
\r
496 service->process_handle = pi.hProcess;
\r
497 service->pid = pi.dwProcessId;
\r
499 if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
\r
501 close_output_handles(&si);
\r
504 Wait for a clean startup before changing the service status to RUNNING
\r
505 but be mindful of the fact that we are blocking the service control manager
\r
506 so abandon the wait before too much time has elapsed.
\r
508 unsigned long delay = service->throttle_delay;
\r
509 if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
\r
510 TCHAR delay_milliseconds[16];
\r
511 _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);
\r
512 TCHAR deadline_milliseconds[16];
\r
513 _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);
\r
514 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
\r
515 delay = NSSM_SERVICE_STATUS_DEADLINE;
\r
517 unsigned long deadline = WaitForSingleObject(service->process_handle, delay);
\r
519 /* Signal successful start */
\r
520 service->status.dwCurrentState = SERVICE_RUNNING;
\r
521 SetServiceStatus(service->status_handle, &service->status);
\r
523 /* Continue waiting for a clean startup. */
\r
524 if (deadline == WAIT_TIMEOUT) {
\r
525 if (service->throttle_delay > delay) {
\r
526 if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;
\r
528 else service->throttle = 0;
\r
534 /* Stop the service */
\r
535 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {
\r
536 service->allow_restart = false;
\r
537 if (service->wait_handle) {
\r
538 UnregisterWait(service->wait_handle);
\r
539 service->wait_handle = 0;
\r
542 if (default_action && ! exitcode && ! graceful) {
\r
543 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_GRACEFUL_SUICIDE, service->name, service->exe, exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_REALLY], 0);
\r
547 /* Signal we are stopping */
\r
549 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
550 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
551 SetServiceStatus(service->status_handle, &service->status);
\r
554 /* Nothing to do if service isn't running */
\r
555 if (service->pid) {
\r
556 /* Shut down service */
\r
557 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);
\r
558 kill_process(service, service->process_handle, service->pid, 0);
\r
560 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
\r
562 end_service((void *) service, true);
\r
564 /* Signal we stopped */
\r
566 service->status.dwCurrentState = SERVICE_STOPPED;
\r
568 service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
569 service->status.dwServiceSpecificExitCode = exitcode;
\r
572 service->status.dwWin32ExitCode = NO_ERROR;
\r
573 service->status.dwServiceSpecificExitCode = 0;
\r
575 SetServiceStatus(service->status_handle, &service->status);
\r
581 /* Callback function triggered when the server exits */
\r
582 void CALLBACK end_service(void *arg, unsigned char why) {
\r
583 nssm_service_t *service = (nssm_service_t *) arg;
\r
585 if (service->stopping) return;
\r
587 service->stopping = true;
\r
589 /* Check exit code */
\r
590 unsigned long exitcode = 0;
\r
592 if (service->process_handle) {
\r
593 GetExitCodeProcess(service->process_handle, &exitcode);
\r
594 if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);
\r
595 CloseHandle(service->process_handle);
\r
597 else GetSystemTimeAsFileTime(&service->exit_time);
\r
599 service->process_handle = 0;
\r
602 Log that the service ended BEFORE logging about killing the process
\r
603 tree. See below for the possible values of the why argument.
\r
606 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);
\r
607 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);
\r
611 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
612 if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);
\r
616 The why argument is true if our wait timed out or false otherwise.
\r
617 Our wait is infinite so why will never be true when called by the system.
\r
618 If it is indeed true, assume we were called from stop_service() because
\r
619 this is a controlled shutdown, and don't take any restart action.
\r
622 if (! service->allow_restart) return;
\r
624 /* What action should we take? */
\r
625 int action = NSSM_EXIT_RESTART;
\r
626 TCHAR action_string[ACTION_LEN];
\r
627 bool default_action;
\r
628 if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {
\r
629 for (int i = 0; exit_action_strings[i]; i++) {
\r
630 if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
638 /* Try to restart the service or return failure code to service manager */
\r
639 case NSSM_EXIT_RESTART:
\r
640 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);
\r
641 while (monitor_service(service)) {
\r
642 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);
\r
647 /* Do nothing, just like srvany would */
\r
648 case NSSM_EXIT_IGNORE:
\r
649 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);
\r
653 /* Tell the service manager we are finished */
\r
654 case NSSM_EXIT_REALLY:
\r
655 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);
\r
656 stop_service(service, exitcode, true, default_action);
\r
659 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
660 case NSSM_EXIT_UNCLEAN:
\r
661 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);
\r
662 stop_service(service, exitcode, false, default_action);
\r
669 void throttle_restart(nssm_service_t *service) {
\r
670 /* This can't be a restart if the service is already running. */
\r
671 if (! service->throttle++) return;
\r
673 int ms = throttle_milliseconds(service->throttle);
\r
675 if (service->throttle > 7) service->throttle = 8;
\r
677 TCHAR threshold[8], milliseconds[8];
\r
678 _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);
\r
679 _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);
\r
680 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
\r
682 if (use_critical_section) EnterCriticalSection(&service->throttle_section);
\r
683 else if (service->throttle_timer) {
\r
684 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
685 service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
686 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
689 service->status.dwCurrentState = SERVICE_PAUSED;
\r
690 SetServiceStatus(service->status_handle, &service->status);
\r
692 if (use_critical_section) {
\r
693 imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);
\r
694 LeaveCriticalSection(&service->throttle_section);
\r
697 if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);
\r
703 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
704 the number of milliseconds we expect the operation to take, and optionally
\r
705 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
706 operation completing or dwCheckPoint increasing, the system will consider the
\r
707 service to be hung.
\r
709 However the system will consider the service to be hung after 30000
\r
710 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
711 changed. Therefore if we want to wait longer than that we must periodically
\r
712 increase dwCheckPoint.
\r
714 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
715 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
716 time dwCheckPoint is also increased.
\r
718 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
719 NSSM_SERVICE_STATUS_DEADLINE milliseconds. If the process is still running
\r
720 and we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
721 smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to
\r
724 Only doing both these things will prevent the system from killing the service.
\r
726 Returns: 1 if the wait timed out.
\r
727 0 if the wait completed.
\r
730 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {
\r
731 unsigned long interval;
\r
732 unsigned long waithint;
\r
734 unsigned long waited;
\r
735 TCHAR interval_milliseconds[16];
\r
736 TCHAR timeout_milliseconds[16];
\r
737 TCHAR waited_milliseconds[16];
\r
738 TCHAR *function = function_name;
\r
740 /* Add brackets to function name. */
\r
741 size_t funclen = _tcslen(function_name) + 3;
\r
742 TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));
\r
744 if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;
\r
747 _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);
\r
749 waithint = service->status.dwWaitHint;
\r
751 while (waited < timeout) {
\r
752 interval = timeout - waited;
\r
753 if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
\r
755 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
756 service->status.dwWaitHint += interval;
\r
757 service->status.dwCheckPoint++;
\r
758 SetServiceStatus(service->status_handle, &service->status);
\r
761 _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);
\r
762 _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);
\r
763 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
766 switch (WaitForSingleObject(service->process_handle, interval)) {
\r
767 case WAIT_OBJECT_0:
\r
780 waited += interval;
\r
784 if (func) HeapFree(GetProcessHeap(), 0, func);
\r