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->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
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
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
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
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
86 nssm_service_t *service = alloc_nssm_service();
\r
88 print_message(stderr, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("pre_install_service()"));
\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
96 /* Arguments are optional */
\r
97 size_t flagslen = 0;
\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
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
111 if (i < argc - 1) service->flags[s++] = _T(' ');
\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
118 int ret = install_service(service);
\r
119 cleanup_nssm_service(service);
\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
134 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
138 /* Install the service */
\r
139 int install_service(nssm_service_t *service) {
\r
140 if (! service) return 1;
\r
142 /* Open service manager */
\r
143 SC_HANDLE services = open_service_manager();
\r
145 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
146 cleanup_nssm_service(service);
\r
150 /* Get path of this program */
\r
151 TCHAR command[MAX_PATH];
\r
152 GetModuleFileName(0, command, _countof(command));
\r
155 The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS
\r
156 and SERVICE_INTERACTIVE_PROCESS.
\r
158 service->type &= SERVICE_INTERACTIVE_PROCESS;
\r
159 service->type |= SERVICE_WIN32_OWN_PROCESS;
\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
169 /* Display name. */
\r
170 if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);
\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
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
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
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
211 set_service_recovery(service);
\r
213 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
\r
216 CloseServiceHandle(services);
\r
221 /* Remove the service */
\r
222 int remove_service(nssm_service_t *service) {
\r
223 if (! service) return 1;
\r
225 /* Open service manager */
\r
226 SC_HANDLE services = open_service_manager();
\r
228 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\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
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
248 CloseServiceHandle(services);
\r
250 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
\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
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
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
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
277 /* Signal we AREN'T running the server */
\r
278 service->process_handle = 0;
\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
288 log_service_control(service->name, 0, true);
\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
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
298 SC_HANDLE services = open_service_manager();
\r
300 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
301 set_service_recovery(service);
\r
302 CloseServiceHandle(services);
\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
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
318 monitor_service(service);
\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
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
337 int monitor_service(nssm_service_t *service) {
\r
338 /* Set service status to started */
\r
339 int ret = start_service(service);
\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
346 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);
\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
356 TCHAR *service_control_text(unsigned long 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
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
374 /* "0x" + 8 x hex + NULL */
\r
375 text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));
\r
377 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\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
386 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
388 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
389 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
391 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
393 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
394 HeapFree(GetProcessHeap(), 0, text);
\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
403 case SERVICE_CONTROL_INTERROGATE:
\r
404 /* We always keep the service status up-to-date so this is a no-op. */
\r
407 case SERVICE_CONTROL_SHUTDOWN:
\r
408 case SERVICE_CONTROL_STOP:
\r
409 log_service_control(service->name, control, true);
\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
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
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
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
426 stop_service(service, 0, true, true);
\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
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
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
445 case SERVICE_CONTROL_PAUSE:
\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
450 log_service_control(service->name, control, false);
\r
451 return ERROR_CALL_NOT_IMPLEMENTED;
\r
454 /* Unknown control */
\r
455 log_service_control(service->name, control, false);
\r
456 return ERROR_CALL_NOT_IMPLEMENTED;
\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
464 if (service->process_handle) return 0;
\r
466 /* Allocate a STARTUPINFO structure for a new process */
\r
468 ZeroMemory(&si, sizeof(si));
\r
469 si.cb = sizeof(si);
\r
471 /* Allocate a PROCESSINFO structure for the process */
\r
472 PROCESS_INFORMATION pi;
\r
473 ZeroMemory(&pi, sizeof(pi));
\r
475 /* Get startup parameters */
\r
476 int ret = get_parameters(service, &si);
\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
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
490 throttle_restart(service);
\r
492 bool inherit_handles = false;
\r
493 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
494 unsigned long flags = 0;
\r
496 flags |= CREATE_UNICODE_ENVIRONMENT;
\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
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
509 service->process_handle = pi.hProcess;
\r
510 service->pid = pi.dwProcessId;
\r
512 if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
\r
514 close_output_handles(&si);
\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
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
530 unsigned long deadline = WaitForSingleObject(service->process_handle, delay);
\r
532 /* Signal successful start */
\r
533 service->status.dwCurrentState = SERVICE_RUNNING;
\r
534 SetServiceStatus(service->status_handle, &service->status);
\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
541 else service->throttle = 0;
\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
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
560 /* Signal we are stopping */
\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
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
573 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
\r
575 end_service((void *) service, true);
\r
577 /* Signal we stopped */
\r
579 service->status.dwCurrentState = SERVICE_STOPPED;
\r
581 service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
582 service->status.dwServiceSpecificExitCode = exitcode;
\r
585 service->status.dwWin32ExitCode = NO_ERROR;
\r
586 service->status.dwServiceSpecificExitCode = 0;
\r
588 SetServiceStatus(service->status_handle, &service->status);
\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
598 if (service->stopping) return;
\r
600 service->stopping = true;
\r
602 /* Check exit code */
\r
603 unsigned long exitcode = 0;
\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
610 else GetSystemTimeAsFileTime(&service->exit_time);
\r
612 service->process_handle = 0;
\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
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
624 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
625 if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);
\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
635 if (! service->allow_restart) return;
\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
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
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
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
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
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
686 int ms = throttle_milliseconds(service->throttle);
\r
688 if (service->throttle > 7) service->throttle = 8;
\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
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
702 service->status.dwCurrentState = SERVICE_PAUSED;
\r
703 SetServiceStatus(service->status_handle, &service->status);
\r
705 if (use_critical_section) {
\r
706 imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);
\r
707 LeaveCriticalSection(&service->throttle_section);
\r
710 if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);
\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
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
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
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
737 Only doing both these things will prevent the system from killing the service.
\r
739 Returns: 1 if the wait timed out.
\r
740 0 if the wait completed.
\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
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
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
757 if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;
\r
760 _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);
\r
762 waithint = service->status.dwWaitHint;
\r
764 while (waited < timeout) {
\r
765 interval = timeout - waited;
\r
766 if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
\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
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
779 switch (WaitForSingleObject(service->process_handle, interval)) {
\r
780 case WAIT_OBJECT_0:
\r
793 waited += interval;
\r
797 if (func) HeapFree(GetProcessHeap(), 0, func);
\r