4 bool use_critical_section;
\r
6 extern imports_t imports;
\r
8 const char *exit_action_strings[] = { "Restart", "Ignore", "Exit", "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 /* Allocate and zero memory for a service. */
\r
36 nssm_service_t *alloc_nssm_service() {
\r
37 nssm_service_t *service = (nssm_service_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(nssm_service_t));
\r
38 if (! service) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service", "alloc_nssm_service()", 0);
\r
42 /* Free memory for a service. */
\r
43 void cleanup_nssm_service(nssm_service_t *service) {
\r
44 if (! service) return;
\r
45 if (service->env) HeapFree(GetProcessHeap(), 0, service->env);
\r
46 if (service->env_extra) HeapFree(GetProcessHeap(), 0, service->env_extra);
\r
47 if (service->handle) CloseServiceHandle(service->handle);
\r
48 if (service->process_handle) CloseHandle(service->process_handle);
\r
49 if (service->wait_handle) UnregisterWait(service->process_handle);
\r
50 if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);
\r
51 if (service->throttle_timer) CloseHandle(service->throttle_timer);
\r
52 HeapFree(GetProcessHeap(), 0, service);
\r
55 /* About to install the service */
\r
56 int pre_install_service(int argc, char **argv) {
\r
57 /* Show the dialogue box if we didn't give the service name and path */
\r
58 if (argc < 2) return nssm_gui(IDD_INSTALL, argv[0]);
\r
60 nssm_service_t *service = alloc_nssm_service();
\r
62 print_message(stderr, NSSM_EVENT_OUT_OF_MEMORY, "service", "pre_install_service()");
\r
66 memmove(service->name, argv[0], strlen(argv[0]));
\r
67 memmove(service->exe, argv[1], strlen(argv[1]));
\r
69 /* Arguments are optional */
\r
70 size_t flagslen = 0;
\r
73 for (i = 2; i < argc; i++) flagslen += strlen(argv[i]) + 1;
\r
74 if (! flagslen) flagslen = 1;
\r
77 This probably isn't UTF8-safe and should use std::string or something
\r
78 but it's been broken for the best part of a decade and due for a rewrite
\r
79 anyway so it'll do as a quick-'n'-dirty fix. Note that we don't free
\r
80 the flags buffer but as the program exits that isn't a big problem.
\r
82 for (i = 2; i < argc; i++) {
\r
83 size_t len = strlen(argv[i]);
\r
84 memmove(service->flags + s, argv[i], len);
\r
86 if (i < argc - 1) service->flags[s++] = ' ';
\r
89 /* Work out directory name */
\r
90 memmove(service->dir, service->exe, sizeof(service->dir));
\r
91 strip_basename(service->dir);
\r
93 int ret = install_service(service);
\r
94 cleanup_nssm_service(service);
\r
98 /* About to remove the service */
\r
99 int pre_remove_service(int argc, char **argv) {
\r
100 /* Show dialogue box if we didn't pass service name and "confirm" */
\r
101 if (argc < 2) return nssm_gui(IDD_REMOVE, argv[0]);
\r
102 if (str_equiv(argv[1], "confirm")) {
\r
103 nssm_service_t *service = alloc_nssm_service();
\r
104 memmove(service->name, argv[0], strlen(argv[0]));
\r
105 int ret = remove_service(service);
\r
106 cleanup_nssm_service(service);
\r
109 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
113 /* Install the service */
\r
114 int install_service(nssm_service_t *service) {
\r
115 if (! service) return 1;
\r
117 /* Open service manager */
\r
118 SC_HANDLE services = open_service_manager();
\r
120 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
121 cleanup_nssm_service(service);
\r
125 /* Get path of this program */
\r
126 char path[MAX_PATH];
\r
127 GetModuleFileName(0, path, MAX_PATH);
\r
129 /* Construct command */
\r
130 char command[CMD_LENGTH];
\r
131 size_t pathlen = strlen(path);
\r
132 if (pathlen + 1 >= VALUE_LENGTH) {
\r
133 print_message(stderr, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);
\r
136 if (_snprintf_s(command, sizeof(command), _TRUNCATE, "\"%s\"", path) < 0) {
\r
137 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY_FOR_IMAGEPATH);
\r
141 /* Create the service */
\r
142 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
143 if (! service->handle) {
\r
144 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
145 CloseServiceHandle(services);
\r
149 /* Now we need to put the parameters into the registry */
\r
150 if (create_parameters(service)) {
\r
151 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
152 DeleteService(service->handle);
\r
153 CloseServiceHandle(services);
\r
157 set_service_recovery(service);
\r
159 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
\r
162 CloseServiceHandle(services);
\r
167 /* Remove the service */
\r
168 int remove_service(nssm_service_t *service) {
\r
169 if (! service) return 1;
\r
171 /* Open service manager */
\r
172 SC_HANDLE services = open_service_manager();
\r
174 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
178 /* Try to open the service */
\r
179 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
180 if (! service->handle) {
\r
181 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
182 CloseServiceHandle(services);
\r
186 /* Try to delete the service */
\r
187 if (! DeleteService(service->handle)) {
\r
188 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
189 CloseServiceHandle(services);
\r
194 CloseServiceHandle(services);
\r
196 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
\r
200 /* Service initialisation */
\r
201 void WINAPI service_main(unsigned long argc, char **argv) {
\r
202 nssm_service_t *service = alloc_nssm_service();
\r
203 if (! service) return;
\r
205 if (_snprintf_s(service->name, sizeof(service->name), _TRUNCATE, "%s", argv[0]) < 0) {
\r
206 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service->name", "service_main()", 0);
\r
210 /* We can use a condition variable in a critical section on Vista or later. */
\r
211 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
212 else use_critical_section = false;
\r
214 /* Initialise status */
\r
215 ZeroMemory(&service->status, sizeof(service->status));
\r
216 service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
217 service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
218 service->status.dwWin32ExitCode = NO_ERROR;
\r
219 service->status.dwServiceSpecificExitCode = 0;
\r
220 service->status.dwCheckPoint = 0;
\r
221 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
223 /* Signal we AREN'T running the server */
\r
224 service->process_handle = 0;
\r
227 /* Register control handler */
\r
228 service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);
\r
229 if (! service->status_handle) {
\r
230 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
234 log_service_control(service->name, 0, true);
\r
236 service->status.dwCurrentState = SERVICE_START_PENDING;
\r
237 service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;
\r
238 SetServiceStatus(service->status_handle, &service->status);
\r
241 /* Try to create the exit action parameters; we don't care if it fails */
\r
242 create_exit_action(service->name, exit_action_strings[0]);
\r
244 SC_HANDLE services = open_service_manager();
\r
246 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
247 set_service_recovery(service);
\r
248 CloseServiceHandle(services);
\r
252 /* Used for signalling a resume if the service pauses when throttled. */
\r
253 if (use_critical_section) {
\r
254 InitializeCriticalSection(&service->throttle_section);
\r
255 service->throttle_section_initialised = true;
\r
258 service->throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
259 if (! service->throttle_timer) {
\r
260 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);
\r
264 monitor_service(service);
\r
267 /* Make sure service recovery actions are taken where necessary */
\r
268 void set_service_recovery(nssm_service_t *service) {
\r
269 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
270 ZeroMemory(&flag, sizeof(flag));
\r
271 flag.fFailureActionsOnNonCrashFailures = true;
\r
273 /* This functionality was added in Vista so the call may fail */
\r
274 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
275 unsigned long error = GetLastError();
\r
276 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
277 if (error != ERROR_INVALID_LEVEL) {
\r
278 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CHANGESERVICECONFIG2_FAILED, service->name, error_string(error), 0);
\r
283 int monitor_service(nssm_service_t *service) {
\r
284 /* Set service status to started */
\r
285 int ret = start_service(service);
\r
288 _snprintf_s(code, sizeof(code), _TRUNCATE, "%d", ret);
\r
289 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);
\r
292 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);
\r
294 /* Monitor service */
\r
295 if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
296 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);
\r
302 char *service_control_text(unsigned long control) {
\r
304 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
305 case 0: return "START";
\r
306 case SERVICE_CONTROL_STOP: return "STOP";
\r
307 case SERVICE_CONTROL_SHUTDOWN: return "SHUTDOWN";
\r
308 case SERVICE_CONTROL_PAUSE: return "PAUSE";
\r
309 case SERVICE_CONTROL_CONTINUE: return "CONTINUE";
\r
310 case SERVICE_CONTROL_INTERROGATE: return "INTERROGATE";
\r
315 void log_service_control(char *service_name, unsigned long control, bool handled) {
\r
316 char *text = service_control_text(control);
\r
317 unsigned long event;
\r
320 /* "0x" + 8 x hex + NULL */
\r
321 text = (char *) HeapAlloc(GetProcessHeap(), 0, 11);
\r
323 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control()", 0);
\r
326 if (_snprintf_s(text, 11, _TRUNCATE, "0x%08x", control) < 0) {
\r
327 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control()", 0);
\r
328 HeapFree(GetProcessHeap(), 0, text);
\r
332 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
334 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
335 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
337 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
339 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
340 HeapFree(GetProcessHeap(), 0, text);
\r
344 /* Service control handler */
\r
345 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
346 nssm_service_t *service = (nssm_service_t *) context;
\r
349 case SERVICE_CONTROL_INTERROGATE:
\r
350 /* We always keep the service status up-to-date so this is a no-op. */
\r
353 case SERVICE_CONTROL_SHUTDOWN:
\r
354 case SERVICE_CONTROL_STOP:
\r
355 log_service_control(service->name, control, true);
\r
357 We MUST acknowledge the stop request promptly but we're committed to
\r
358 waiting for the application to exit. Spawn a new thread to wait
\r
359 while we acknowledge the request.
\r
361 if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {
\r
362 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
\r
365 We couldn't create a thread to tidy up so we'll have to force the tidyup
\r
366 to complete in time in this thread.
\r
368 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
369 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
370 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
372 stop_service(service, 0, true, true);
\r
376 case SERVICE_CONTROL_CONTINUE:
\r
377 log_service_control(service->name, control, true);
\r
378 service->throttle = 0;
\r
379 if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);
\r
381 if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
382 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
383 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
385 service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
386 service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;
\r
387 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);
\r
388 SetServiceStatus(service->status_handle, &service->status);
\r
391 case SERVICE_CONTROL_PAUSE:
\r
393 We don't accept pause messages but it isn't possible to register
\r
394 only for continue messages so we have to handle this case.
\r
396 log_service_control(service->name, control, false);
\r
397 return ERROR_CALL_NOT_IMPLEMENTED;
\r
400 /* Unknown control */
\r
401 log_service_control(service->name, control, false);
\r
402 return ERROR_CALL_NOT_IMPLEMENTED;
\r
405 /* Start the service */
\r
406 int start_service(nssm_service_t *service) {
\r
407 service->stopping = false;
\r
408 service->allow_restart = true;
\r
410 if (service->process_handle) return 0;
\r
412 /* Allocate a STARTUPINFO structure for a new process */
\r
414 ZeroMemory(&si, sizeof(si));
\r
415 si.cb = sizeof(si);
\r
417 /* Allocate a PROCESSINFO structure for the process */
\r
418 PROCESS_INFORMATION pi;
\r
419 ZeroMemory(&pi, sizeof(pi));
\r
421 /* Get startup parameters */
\r
422 int ret = get_parameters(service, &si);
\r
424 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);
\r
425 return stop_service(service, 2, true, true);
\r
428 /* Launch executable with arguments */
\r
429 char cmd[CMD_LENGTH];
\r
430 if (_snprintf_s(cmd, sizeof(cmd), _TRUNCATE, "\"%s\" %s", service->exe, service->flags) < 0) {
\r
431 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);
\r
432 close_output_handles(&si);
\r
433 return stop_service(service, 2, true, true);
\r
436 throttle_restart(service);
\r
438 bool inherit_handles = false;
\r
439 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
440 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, 0, service->env, service->dir, &si, &pi)) {
\r
441 unsigned long error = GetLastError();
\r
442 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
443 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);
\r
444 close_output_handles(&si);
\r
445 return stop_service(service, 3, true, true);
\r
447 service->process_handle = pi.hProcess;
\r
448 service->pid = pi.dwProcessId;
\r
450 if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
\r
452 close_output_handles(&si);
\r
455 Wait for a clean startup before changing the service status to RUNNING
\r
456 but be mindful of the fact that we are blocking the service control manager
\r
457 so abandon the wait before too much time has elapsed.
\r
459 unsigned long delay = service->throttle_delay;
\r
460 if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
\r
461 char delay_milliseconds[16];
\r
462 _snprintf_s(delay_milliseconds, sizeof(delay_milliseconds), _TRUNCATE, "%lu", delay);
\r
463 char deadline_milliseconds[16];
\r
464 _snprintf_s(deadline_milliseconds, sizeof(deadline_milliseconds), _TRUNCATE, "%lu", NSSM_SERVICE_STATUS_DEADLINE);
\r
465 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
\r
466 delay = NSSM_SERVICE_STATUS_DEADLINE;
\r
468 unsigned long deadline = WaitForSingleObject(service->process_handle, delay);
\r
470 /* Signal successful start */
\r
471 service->status.dwCurrentState = SERVICE_RUNNING;
\r
472 SetServiceStatus(service->status_handle, &service->status);
\r
474 /* Continue waiting for a clean startup. */
\r
475 if (deadline == WAIT_TIMEOUT) {
\r
476 if (service->throttle_delay > delay) {
\r
477 if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;
\r
479 else service->throttle = 0;
\r
485 /* Stop the service */
\r
486 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {
\r
487 service->allow_restart = false;
\r
488 if (service->wait_handle) {
\r
489 UnregisterWait(service->wait_handle);
\r
490 service->wait_handle = 0;
\r
493 if (default_action && ! exitcode && ! graceful) {
\r
494 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
498 /* Signal we are stopping */
\r
500 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
501 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
502 SetServiceStatus(service->status_handle, &service->status);
\r
505 /* Nothing to do if service isn't running */
\r
506 if (service->pid) {
\r
507 /* Shut down service */
\r
508 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);
\r
509 kill_process(service, service->process_handle, service->pid, 0);
\r
511 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
\r
513 end_service((void *) service, true);
\r
515 /* Signal we stopped */
\r
517 service->status.dwCurrentState = SERVICE_STOPPED;
\r
519 service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
520 service->status.dwServiceSpecificExitCode = exitcode;
\r
523 service->status.dwWin32ExitCode = NO_ERROR;
\r
524 service->status.dwServiceSpecificExitCode = 0;
\r
526 SetServiceStatus(service->status_handle, &service->status);
\r
532 /* Callback function triggered when the server exits */
\r
533 void CALLBACK end_service(void *arg, unsigned char why) {
\r
534 nssm_service_t *service = (nssm_service_t *) arg;
\r
536 if (service->stopping) return;
\r
538 service->stopping = true;
\r
540 /* Check exit code */
\r
541 unsigned long exitcode = 0;
\r
543 GetExitCodeProcess(service->process_handle, &exitcode);
\r
544 if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);
\r
545 CloseHandle(service->process_handle);
\r
547 service->process_handle = 0;
\r
551 Log that the service ended BEFORE logging about killing the process
\r
552 tree. See below for the possible values of the why argument.
\r
555 _snprintf_s(code, sizeof(code), _TRUNCATE, "%lu", exitcode);
\r
556 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);
\r
560 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
561 kill_process_tree(service, service->pid, exitcode, service->pid);
\r
564 The why argument is true if our wait timed out or false otherwise.
\r
565 Our wait is infinite so why will never be true when called by the system.
\r
566 If it is indeed true, assume we were called from stop_service() because
\r
567 this is a controlled shutdown, and don't take any restart action.
\r
570 if (! service->allow_restart) return;
\r
572 /* What action should we take? */
\r
573 int action = NSSM_EXIT_RESTART;
\r
574 unsigned char action_string[ACTION_LEN];
\r
575 bool default_action;
\r
576 if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {
\r
577 for (int i = 0; exit_action_strings[i]; i++) {
\r
578 if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
586 /* Try to restart the service or return failure code to service manager */
\r
587 case NSSM_EXIT_RESTART:
\r
588 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);
\r
589 while (monitor_service(service)) {
\r
590 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);
\r
595 /* Do nothing, just like srvany would */
\r
596 case NSSM_EXIT_IGNORE:
\r
597 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);
\r
601 /* Tell the service manager we are finished */
\r
602 case NSSM_EXIT_REALLY:
\r
603 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);
\r
604 stop_service(service, exitcode, true, default_action);
\r
607 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
608 case NSSM_EXIT_UNCLEAN:
\r
609 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);
\r
610 stop_service(service, exitcode, false, default_action);
\r
617 void throttle_restart(nssm_service_t *service) {
\r
618 /* This can't be a restart if the service is already running. */
\r
619 if (! service->throttle++) return;
\r
621 int ms = throttle_milliseconds(service->throttle);
\r
623 if (service->throttle > 7) service->throttle = 8;
\r
625 char threshold[8], milliseconds[8];
\r
626 _snprintf_s(threshold, sizeof(threshold), _TRUNCATE, "%lu", service->throttle_delay);
\r
627 _snprintf_s(milliseconds, sizeof(milliseconds), _TRUNCATE, "%lu", ms);
\r
628 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
\r
630 if (use_critical_section) EnterCriticalSection(&service->throttle_section);
\r
631 else if (service->throttle_timer) {
\r
632 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
633 service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
634 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
637 service->status.dwCurrentState = SERVICE_PAUSED;
\r
638 SetServiceStatus(service->status_handle, &service->status);
\r
640 if (use_critical_section) {
\r
641 imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);
\r
642 LeaveCriticalSection(&service->throttle_section);
\r
645 if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);
\r
651 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
652 the number of milliseconds we expect the operation to take, and optionally
\r
653 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
654 operation completing or dwCheckPoint increasing, the system will consider the
\r
655 service to be hung.
\r
657 However the system will consider the service to be hung after 30000
\r
658 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
659 changed. Therefore if we want to wait longer than that we must periodically
\r
660 increase dwCheckPoint.
\r
662 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
663 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
664 time dwCheckPoint is also increased.
\r
666 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
667 NSSM_SERVICE_STATUS_DEADLINE milliseconds. If the process is still running
\r
668 and we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
669 smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to
\r
672 Only doing both these things will prevent the system from killing the service.
\r
674 Returns: 1 if the wait timed out.
\r
675 0 if the wait completed.
\r
678 int await_shutdown(nssm_service_t *service, char *function_name, unsigned long timeout) {
\r
679 unsigned long interval;
\r
680 unsigned long waithint;
\r
682 unsigned long waited;
\r
683 char interval_milliseconds[16];
\r
684 char timeout_milliseconds[16];
\r
685 char waited_milliseconds[16];
\r
686 char *function = function_name;
\r
688 /* Add brackets to function name. */
\r
689 size_t funclen = strlen(function_name) + 3;
\r
690 char *func = (char *) HeapAlloc(GetProcessHeap(), 0, funclen);
\r
692 if (_snprintf_s(func, funclen, _TRUNCATE, "%s()", function_name) > -1) function = func;
\r
695 _snprintf_s(timeout_milliseconds, sizeof(timeout_milliseconds), _TRUNCATE, "%lu", timeout);
\r
697 waithint = service->status.dwWaitHint;
\r
699 while (waited < timeout) {
\r
700 interval = timeout - waited;
\r
701 if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
\r
703 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
704 service->status.dwWaitHint += interval;
\r
705 service->status.dwCheckPoint++;
\r
706 SetServiceStatus(service->status_handle, &service->status);
\r
709 _snprintf_s(waited_milliseconds, sizeof(waited_milliseconds), _TRUNCATE, "%lu", waited);
\r
710 _snprintf_s(interval_milliseconds, sizeof(interval_milliseconds), _TRUNCATE, "%lu", interval);
\r
711 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
714 switch (WaitForSingleObject(service->process_handle, interval)) {
\r
715 case WAIT_OBJECT_0:
\r
728 waited += interval;
\r
732 if (func) HeapFree(GetProcessHeap(), 0, func);
\r