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 path[MAX_PATH];
\r
146 GetModuleFileName(0, path, MAX_PATH);
\r
148 /* Construct command */
\r
149 TCHAR command[CMD_LENGTH];
\r
150 size_t pathlen = _tcslen(path);
\r
151 if (pathlen + 1 >= VALUE_LENGTH) {
\r
152 print_message(stderr, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);
\r
155 if (_sntprintf_s(command, sizeof(command), _TRUNCATE, _T("\"%s\""), path) < 0) {
\r
156 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY_FOR_IMAGEPATH);
\r
160 /* Create the service */
\r
161 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
162 if (! service->handle) {
\r
163 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
164 CloseServiceHandle(services);
\r
168 /* Now we need to put the parameters into the registry */
\r
169 if (create_parameters(service)) {
\r
170 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
171 DeleteService(service->handle);
\r
172 CloseServiceHandle(services);
\r
176 set_service_recovery(service);
\r
178 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
\r
181 CloseServiceHandle(services);
\r
186 /* Remove the service */
\r
187 int remove_service(nssm_service_t *service) {
\r
188 if (! service) return 1;
\r
190 /* Open service manager */
\r
191 SC_HANDLE services = open_service_manager();
\r
193 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
197 /* Try to open the service */
\r
198 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
199 if (! service->handle) {
\r
200 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
201 CloseServiceHandle(services);
\r
205 /* Try to delete the service */
\r
206 if (! DeleteService(service->handle)) {
\r
207 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
208 CloseServiceHandle(services);
\r
213 CloseServiceHandle(services);
\r
215 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
\r
219 /* Service initialisation */
\r
220 void WINAPI service_main(unsigned long argc, TCHAR **argv) {
\r
221 nssm_service_t *service = alloc_nssm_service();
\r
222 if (! service) return;
\r
224 if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {
\r
225 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);
\r
229 /* We can use a condition variable in a critical section on Vista or later. */
\r
230 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
231 else use_critical_section = false;
\r
233 /* Initialise status */
\r
234 ZeroMemory(&service->status, sizeof(service->status));
\r
235 service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
236 service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
237 service->status.dwWin32ExitCode = NO_ERROR;
\r
238 service->status.dwServiceSpecificExitCode = 0;
\r
239 service->status.dwCheckPoint = 0;
\r
240 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
242 /* Signal we AREN'T running the server */
\r
243 service->process_handle = 0;
\r
246 /* Register control handler */
\r
247 service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);
\r
248 if (! service->status_handle) {
\r
249 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
253 log_service_control(service->name, 0, true);
\r
255 service->status.dwCurrentState = SERVICE_START_PENDING;
\r
256 service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;
\r
257 SetServiceStatus(service->status_handle, &service->status);
\r
260 /* Try to create the exit action parameters; we don't care if it fails */
\r
261 create_exit_action(service->name, exit_action_strings[0]);
\r
263 SC_HANDLE services = open_service_manager();
\r
265 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
266 set_service_recovery(service);
\r
267 CloseServiceHandle(services);
\r
271 /* Used for signalling a resume if the service pauses when throttled. */
\r
272 if (use_critical_section) {
\r
273 InitializeCriticalSection(&service->throttle_section);
\r
274 service->throttle_section_initialised = true;
\r
277 service->throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
278 if (! service->throttle_timer) {
\r
279 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);
\r
283 monitor_service(service);
\r
286 /* Make sure service recovery actions are taken where necessary */
\r
287 void set_service_recovery(nssm_service_t *service) {
\r
288 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
289 ZeroMemory(&flag, sizeof(flag));
\r
290 flag.fFailureActionsOnNonCrashFailures = true;
\r
292 /* This functionality was added in Vista so the call may fail */
\r
293 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
294 unsigned long error = GetLastError();
\r
295 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
296 if (error != ERROR_INVALID_LEVEL) {
\r
297 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CHANGESERVICECONFIG2_FAILED, service->name, error_string(error), 0);
\r
302 int monitor_service(nssm_service_t *service) {
\r
303 /* Set service status to started */
\r
304 int ret = start_service(service);
\r
307 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);
\r
308 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);
\r
311 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);
\r
313 /* Monitor service */
\r
314 if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
315 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);
\r
321 TCHAR *service_control_text(unsigned long control) {
\r
323 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
324 case 0: return _T("START");
\r
325 case SERVICE_CONTROL_STOP: return _T("STOP");
\r
326 case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");
\r
327 case SERVICE_CONTROL_PAUSE: return _T("PAUSE");
\r
328 case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");
\r
329 case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");
\r
334 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {
\r
335 TCHAR *text = service_control_text(control);
\r
336 unsigned long event;
\r
339 /* "0x" + 8 x hex + NULL */
\r
340 text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));
\r
342 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
345 if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {
\r
346 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
347 HeapFree(GetProcessHeap(), 0, text);
\r
351 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
353 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
354 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
356 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
358 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
359 HeapFree(GetProcessHeap(), 0, text);
\r
363 /* Service control handler */
\r
364 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
365 nssm_service_t *service = (nssm_service_t *) context;
\r
368 case SERVICE_CONTROL_INTERROGATE:
\r
369 /* We always keep the service status up-to-date so this is a no-op. */
\r
372 case SERVICE_CONTROL_SHUTDOWN:
\r
373 case SERVICE_CONTROL_STOP:
\r
374 log_service_control(service->name, control, true);
\r
376 We MUST acknowledge the stop request promptly but we're committed to
\r
377 waiting for the application to exit. Spawn a new thread to wait
\r
378 while we acknowledge the request.
\r
380 if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {
\r
381 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
\r
384 We couldn't create a thread to tidy up so we'll have to force the tidyup
\r
385 to complete in time in this thread.
\r
387 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
388 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
389 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
391 stop_service(service, 0, true, true);
\r
395 case SERVICE_CONTROL_CONTINUE:
\r
396 log_service_control(service->name, control, true);
\r
397 service->throttle = 0;
\r
398 if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);
\r
400 if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
401 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
402 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
404 service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
405 service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;
\r
406 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);
\r
407 SetServiceStatus(service->status_handle, &service->status);
\r
410 case SERVICE_CONTROL_PAUSE:
\r
412 We don't accept pause messages but it isn't possible to register
\r
413 only for continue messages so we have to handle this case.
\r
415 log_service_control(service->name, control, false);
\r
416 return ERROR_CALL_NOT_IMPLEMENTED;
\r
419 /* Unknown control */
\r
420 log_service_control(service->name, control, false);
\r
421 return ERROR_CALL_NOT_IMPLEMENTED;
\r
424 /* Start the service */
\r
425 int start_service(nssm_service_t *service) {
\r
426 service->stopping = false;
\r
427 service->allow_restart = true;
\r
429 if (service->process_handle) return 0;
\r
431 /* Allocate a STARTUPINFO structure for a new process */
\r
433 ZeroMemory(&si, sizeof(si));
\r
434 si.cb = sizeof(si);
\r
436 /* Allocate a PROCESSINFO structure for the process */
\r
437 PROCESS_INFORMATION pi;
\r
438 ZeroMemory(&pi, sizeof(pi));
\r
440 /* Get startup parameters */
\r
441 int ret = get_parameters(service, &si);
\r
443 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);
\r
444 return stop_service(service, 2, true, true);
\r
447 /* Launch executable with arguments */
\r
448 TCHAR cmd[CMD_LENGTH];
\r
449 if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {
\r
450 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);
\r
451 close_output_handles(&si);
\r
452 return stop_service(service, 2, true, true);
\r
455 throttle_restart(service);
\r
457 bool inherit_handles = false;
\r
458 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
459 unsigned long flags = 0;
\r
461 flags |= CREATE_UNICODE_ENVIRONMENT;
\r
463 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {
\r
464 unsigned long error = GetLastError();
\r
465 if (error == ERROR_INVALID_PARAMETER && service->env) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);
\r
466 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);
\r
467 close_output_handles(&si);
\r
468 return stop_service(service, 3, true, true);
\r
470 service->process_handle = pi.hProcess;
\r
471 service->pid = pi.dwProcessId;
\r
473 if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
\r
475 close_output_handles(&si);
\r
478 Wait for a clean startup before changing the service status to RUNNING
\r
479 but be mindful of the fact that we are blocking the service control manager
\r
480 so abandon the wait before too much time has elapsed.
\r
482 unsigned long delay = service->throttle_delay;
\r
483 if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
\r
484 TCHAR delay_milliseconds[16];
\r
485 _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);
\r
486 TCHAR deadline_milliseconds[16];
\r
487 _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);
\r
488 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
\r
489 delay = NSSM_SERVICE_STATUS_DEADLINE;
\r
491 unsigned long deadline = WaitForSingleObject(service->process_handle, delay);
\r
493 /* Signal successful start */
\r
494 service->status.dwCurrentState = SERVICE_RUNNING;
\r
495 SetServiceStatus(service->status_handle, &service->status);
\r
497 /* Continue waiting for a clean startup. */
\r
498 if (deadline == WAIT_TIMEOUT) {
\r
499 if (service->throttle_delay > delay) {
\r
500 if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;
\r
502 else service->throttle = 0;
\r
508 /* Stop the service */
\r
509 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {
\r
510 service->allow_restart = false;
\r
511 if (service->wait_handle) {
\r
512 UnregisterWait(service->wait_handle);
\r
513 service->wait_handle = 0;
\r
516 if (default_action && ! exitcode && ! graceful) {
\r
517 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
521 /* Signal we are stopping */
\r
523 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
524 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
525 SetServiceStatus(service->status_handle, &service->status);
\r
528 /* Nothing to do if service isn't running */
\r
529 if (service->pid) {
\r
530 /* Shut down service */
\r
531 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);
\r
532 kill_process(service, service->process_handle, service->pid, 0);
\r
534 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
\r
536 end_service((void *) service, true);
\r
538 /* Signal we stopped */
\r
540 service->status.dwCurrentState = SERVICE_STOPPED;
\r
542 service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
543 service->status.dwServiceSpecificExitCode = exitcode;
\r
546 service->status.dwWin32ExitCode = NO_ERROR;
\r
547 service->status.dwServiceSpecificExitCode = 0;
\r
549 SetServiceStatus(service->status_handle, &service->status);
\r
555 /* Callback function triggered when the server exits */
\r
556 void CALLBACK end_service(void *arg, unsigned char why) {
\r
557 nssm_service_t *service = (nssm_service_t *) arg;
\r
559 if (service->stopping) return;
\r
561 service->stopping = true;
\r
563 /* Check exit code */
\r
564 unsigned long exitcode = 0;
\r
566 GetExitCodeProcess(service->process_handle, &exitcode);
\r
567 if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);
\r
568 CloseHandle(service->process_handle);
\r
570 service->process_handle = 0;
\r
574 Log that the service ended BEFORE logging about killing the process
\r
575 tree. See below for the possible values of the why argument.
\r
578 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);
\r
579 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);
\r
583 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
584 kill_process_tree(service, service->pid, exitcode, service->pid);
\r
587 The why argument is true if our wait timed out or false otherwise.
\r
588 Our wait is infinite so why will never be true when called by the system.
\r
589 If it is indeed true, assume we were called from stop_service() because
\r
590 this is a controlled shutdown, and don't take any restart action.
\r
593 if (! service->allow_restart) return;
\r
595 /* What action should we take? */
\r
596 int action = NSSM_EXIT_RESTART;
\r
597 TCHAR action_string[ACTION_LEN];
\r
598 bool default_action;
\r
599 if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {
\r
600 for (int i = 0; exit_action_strings[i]; i++) {
\r
601 if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
609 /* Try to restart the service or return failure code to service manager */
\r
610 case NSSM_EXIT_RESTART:
\r
611 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);
\r
612 while (monitor_service(service)) {
\r
613 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);
\r
618 /* Do nothing, just like srvany would */
\r
619 case NSSM_EXIT_IGNORE:
\r
620 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);
\r
624 /* Tell the service manager we are finished */
\r
625 case NSSM_EXIT_REALLY:
\r
626 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);
\r
627 stop_service(service, exitcode, true, default_action);
\r
630 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
631 case NSSM_EXIT_UNCLEAN:
\r
632 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);
\r
633 stop_service(service, exitcode, false, default_action);
\r
640 void throttle_restart(nssm_service_t *service) {
\r
641 /* This can't be a restart if the service is already running. */
\r
642 if (! service->throttle++) return;
\r
644 int ms = throttle_milliseconds(service->throttle);
\r
646 if (service->throttle > 7) service->throttle = 8;
\r
648 TCHAR threshold[8], milliseconds[8];
\r
649 _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);
\r
650 _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);
\r
651 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
\r
653 if (use_critical_section) EnterCriticalSection(&service->throttle_section);
\r
654 else if (service->throttle_timer) {
\r
655 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
656 service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
657 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
660 service->status.dwCurrentState = SERVICE_PAUSED;
\r
661 SetServiceStatus(service->status_handle, &service->status);
\r
663 if (use_critical_section) {
\r
664 imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);
\r
665 LeaveCriticalSection(&service->throttle_section);
\r
668 if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);
\r
674 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
675 the number of milliseconds we expect the operation to take, and optionally
\r
676 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
677 operation completing or dwCheckPoint increasing, the system will consider the
\r
678 service to be hung.
\r
680 However the system will consider the service to be hung after 30000
\r
681 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
682 changed. Therefore if we want to wait longer than that we must periodically
\r
683 increase dwCheckPoint.
\r
685 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
686 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
687 time dwCheckPoint is also increased.
\r
689 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
690 NSSM_SERVICE_STATUS_DEADLINE milliseconds. If the process is still running
\r
691 and we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
692 smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to
\r
695 Only doing both these things will prevent the system from killing the service.
\r
697 Returns: 1 if the wait timed out.
\r
698 0 if the wait completed.
\r
701 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {
\r
702 unsigned long interval;
\r
703 unsigned long waithint;
\r
705 unsigned long waited;
\r
706 TCHAR interval_milliseconds[16];
\r
707 TCHAR timeout_milliseconds[16];
\r
708 TCHAR waited_milliseconds[16];
\r
709 TCHAR *function = function_name;
\r
711 /* Add brackets to function name. */
\r
712 size_t funclen = _tcslen(function_name) + 3;
\r
713 TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));
\r
715 if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;
\r
718 _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);
\r
720 waithint = service->status.dwWaitHint;
\r
722 while (waited < timeout) {
\r
723 interval = timeout - waited;
\r
724 if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
\r
726 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
727 service->status.dwWaitHint += interval;
\r
728 service->status.dwCheckPoint++;
\r
729 SetServiceStatus(service->status_handle, &service->status);
\r
732 _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);
\r
733 _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);
\r
734 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
737 switch (WaitForSingleObject(service->process_handle, interval)) {
\r
738 case WAIT_OBJECT_0:
\r
751 waited += interval;
\r
755 if (func) HeapFree(GetProcessHeap(), 0, func);
\r