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->handle) CloseServiceHandle(service->handle);
\r
47 if (service->process_handle) CloseHandle(service->process_handle);
\r
48 if (service->wait_handle) UnregisterWait(service->process_handle);
\r
49 if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);
\r
50 if (service->throttle_timer) CloseHandle(service->throttle_timer);
\r
51 HeapFree(GetProcessHeap(), 0, service);
\r
54 /* About to install the service */
\r
55 int pre_install_service(int argc, char **argv) {
\r
56 /* Show the dialogue box if we didn't give the service name and path */
\r
57 if (argc < 2) return nssm_gui(IDD_INSTALL, argv[0]);
\r
59 nssm_service_t *service = alloc_nssm_service();
\r
61 print_message(stderr, NSSM_EVENT_OUT_OF_MEMORY, "service", "pre_install_service()");
\r
65 memmove(service->name, argv[0], strlen(argv[0]));
\r
66 memmove(service->exe, argv[1], strlen(argv[1]));
\r
68 /* Arguments are optional */
\r
69 size_t flagslen = 0;
\r
72 for (i = 2; i < argc; i++) flagslen += strlen(argv[i]) + 1;
\r
73 if (! flagslen) flagslen = 1;
\r
76 This probably isn't UTF8-safe and should use std::string or something
\r
77 but it's been broken for the best part of a decade and due for a rewrite
\r
78 anyway so it'll do as a quick-'n'-dirty fix. Note that we don't free
\r
79 the flags buffer but as the program exits that isn't a big problem.
\r
81 for (i = 2; i < argc; i++) {
\r
82 size_t len = strlen(argv[i]);
\r
83 memmove(service->flags + s, argv[i], len);
\r
85 if (i < argc - 1) service->flags[s++] = ' ';
\r
88 /* Work out directory name */
\r
89 size_t len = strlen(service->exe);
\r
90 for (i = len; i && service->exe[i] != '\\' && service->exe[i] != '/'; i--);
\r
91 memmove(service->dir, service->exe, i);
\r
92 service->dir[i] = '\0';
\r
94 int ret = install_service(service);
\r
95 cleanup_nssm_service(service);
\r
99 /* About to remove the service */
\r
100 int pre_remove_service(int argc, char **argv) {
\r
101 /* Show dialogue box if we didn't pass service name and "confirm" */
\r
102 if (argc < 2) return nssm_gui(IDD_REMOVE, argv[0]);
\r
103 if (str_equiv(argv[1], "confirm")) {
\r
104 nssm_service_t *service = alloc_nssm_service();
\r
105 memmove(service->name, argv[0], strlen(argv[0]));
\r
106 int ret = remove_service(service);
\r
107 cleanup_nssm_service(service);
\r
110 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
114 /* Install the service */
\r
115 int install_service(nssm_service_t *service) {
\r
116 if (! service) return 1;
\r
118 /* Open service manager */
\r
119 SC_HANDLE services = open_service_manager();
\r
121 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
122 cleanup_nssm_service(service);
\r
126 /* Get path of this program */
\r
127 char path[MAX_PATH];
\r
128 GetModuleFileName(0, path, MAX_PATH);
\r
130 /* Construct command */
\r
131 char command[CMD_LENGTH];
\r
132 size_t pathlen = strlen(path);
\r
133 if (pathlen + 1 >= VALUE_LENGTH) {
\r
134 print_message(stderr, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);
\r
137 if (_snprintf_s(command, sizeof(command), _TRUNCATE, "\"%s\"", path) < 0) {
\r
138 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY_FOR_IMAGEPATH);
\r
142 /* Create the service */
\r
143 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
144 if (! service->handle) {
\r
145 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
146 CloseServiceHandle(services);
\r
150 /* Now we need to put the parameters into the registry */
\r
151 if (create_parameters(service)) {
\r
152 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
153 DeleteService(service->handle);
\r
154 CloseServiceHandle(services);
\r
158 set_service_recovery(service);
\r
160 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
\r
163 CloseServiceHandle(services);
\r
168 /* Remove the service */
\r
169 int remove_service(nssm_service_t *service) {
\r
170 if (! service) return 1;
\r
172 /* Open service manager */
\r
173 SC_HANDLE services = open_service_manager();
\r
175 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
179 /* Try to open the service */
\r
180 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
181 if (! service->handle) {
\r
182 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
183 CloseServiceHandle(services);
\r
187 /* Try to delete the service */
\r
188 if (! DeleteService(service->handle)) {
\r
189 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
190 CloseServiceHandle(services);
\r
195 CloseServiceHandle(services);
\r
197 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
\r
201 /* Service initialisation */
\r
202 void WINAPI service_main(unsigned long argc, char **argv) {
\r
203 nssm_service_t *service = alloc_nssm_service();
\r
204 if (! service) return;
\r
206 if (_snprintf_s(service->name, sizeof(service->name), _TRUNCATE, "%s", argv[0]) < 0) {
\r
207 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service->name", "service_main()", 0);
\r
211 /* We can use a condition variable in a critical section on Vista or later. */
\r
212 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
213 else use_critical_section = false;
\r
215 /* Initialise status */
\r
216 ZeroMemory(&service->status, sizeof(service->status));
\r
217 service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
218 service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
219 service->status.dwWin32ExitCode = NO_ERROR;
\r
220 service->status.dwServiceSpecificExitCode = 0;
\r
221 service->status.dwCheckPoint = 0;
\r
222 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
224 /* Signal we AREN'T running the server */
\r
225 service->process_handle = 0;
\r
228 /* Register control handler */
\r
229 service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);
\r
230 if (! service->status_handle) {
\r
231 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
235 log_service_control(service->name, 0, true);
\r
237 service->status.dwCurrentState = SERVICE_START_PENDING;
\r
238 service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;
\r
239 SetServiceStatus(service->status_handle, &service->status);
\r
242 /* Try to create the exit action parameters; we don't care if it fails */
\r
243 create_exit_action(service->name, exit_action_strings[0]);
\r
245 SC_HANDLE services = open_service_manager();
\r
247 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
248 set_service_recovery(service);
\r
249 CloseServiceHandle(services);
\r
253 /* Used for signalling a resume if the service pauses when throttled. */
\r
254 if (use_critical_section) {
\r
255 InitializeCriticalSection(&service->throttle_section);
\r
256 service->throttle_section_initialised = true;
\r
259 service->throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
260 if (! service->throttle_timer) {
\r
261 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);
\r
265 monitor_service(service);
\r
268 /* Make sure service recovery actions are taken where necessary */
\r
269 void set_service_recovery(nssm_service_t *service) {
\r
270 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
271 ZeroMemory(&flag, sizeof(flag));
\r
272 flag.fFailureActionsOnNonCrashFailures = true;
\r
274 /* This functionality was added in Vista so the call may fail */
\r
275 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
276 unsigned long error = GetLastError();
\r
277 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
278 if (error != ERROR_INVALID_LEVEL) {
\r
279 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CHANGESERVICECONFIG2_FAILED, service->name, error_string(error), 0);
\r
284 int monitor_service(nssm_service_t *service) {
\r
285 /* Set service status to started */
\r
286 int ret = start_service(service);
\r
289 _snprintf_s(code, sizeof(code), _TRUNCATE, "%d", ret);
\r
290 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);
\r
293 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);
\r
295 /* Monitor service */
\r
296 if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
297 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);
\r
303 char *service_control_text(unsigned long control) {
\r
305 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
306 case 0: return "START";
\r
307 case SERVICE_CONTROL_STOP: return "STOP";
\r
308 case SERVICE_CONTROL_SHUTDOWN: return "SHUTDOWN";
\r
309 case SERVICE_CONTROL_PAUSE: return "PAUSE";
\r
310 case SERVICE_CONTROL_CONTINUE: return "CONTINUE";
\r
311 case SERVICE_CONTROL_INTERROGATE: return "INTERROGATE";
\r
316 void log_service_control(char *service_name, unsigned long control, bool handled) {
\r
317 char *text = service_control_text(control);
\r
318 unsigned long event;
\r
321 /* "0x" + 8 x hex + NULL */
\r
322 text = (char *) HeapAlloc(GetProcessHeap(), 0, 11);
\r
324 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control()", 0);
\r
327 if (_snprintf_s(text, 11, _TRUNCATE, "0x%08x", control) < 0) {
\r
328 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control()", 0);
\r
329 HeapFree(GetProcessHeap(), 0, text);
\r
333 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
335 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
336 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
338 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
340 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
341 HeapFree(GetProcessHeap(), 0, text);
\r
345 /* Service control handler */
\r
346 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
347 nssm_service_t *service = (nssm_service_t *) context;
\r
350 case SERVICE_CONTROL_INTERROGATE:
\r
351 /* We always keep the service status up-to-date so this is a no-op. */
\r
354 case SERVICE_CONTROL_SHUTDOWN:
\r
355 case SERVICE_CONTROL_STOP:
\r
356 log_service_control(service->name, control, true);
\r
358 We MUST acknowledge the stop request promptly but we're committed to
\r
359 waiting for the application to exit. Spawn a new thread to wait
\r
360 while we acknowledge the request.
\r
362 if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {
\r
363 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
\r
366 We couldn't create a thread to tidy up so we'll have to force the tidyup
\r
367 to complete in time in this thread.
\r
369 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
370 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
371 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
373 stop_service(service, 0, true, true);
\r
377 case SERVICE_CONTROL_CONTINUE:
\r
378 log_service_control(service->name, control, true);
\r
379 service->throttle = 0;
\r
380 if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);
\r
382 if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
383 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
384 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
386 service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
387 service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;
\r
388 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);
\r
389 SetServiceStatus(service->status_handle, &service->status);
\r
392 case SERVICE_CONTROL_PAUSE:
\r
394 We don't accept pause messages but it isn't possible to register
\r
395 only for continue messages so we have to handle this case.
\r
397 log_service_control(service->name, control, false);
\r
398 return ERROR_CALL_NOT_IMPLEMENTED;
\r
401 /* Unknown control */
\r
402 log_service_control(service->name, control, false);
\r
403 return ERROR_CALL_NOT_IMPLEMENTED;
\r
406 /* Start the service */
\r
407 int start_service(nssm_service_t *service) {
\r
408 service->stopping = false;
\r
409 service->allow_restart = true;
\r
411 if (service->process_handle) return 0;
\r
413 /* Allocate a STARTUPINFO structure for a new process */
\r
415 ZeroMemory(&si, sizeof(si));
\r
416 si.cb = sizeof(si);
\r
418 /* Allocate a PROCESSINFO structure for the process */
\r
419 PROCESS_INFORMATION pi;
\r
420 ZeroMemory(&pi, sizeof(pi));
\r
422 /* Get startup parameters */
\r
423 int ret = get_parameters(service, &si);
\r
425 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);
\r
426 return stop_service(service, 2, true, true);
\r
429 /* Launch executable with arguments */
\r
430 char cmd[CMD_LENGTH];
\r
431 if (_snprintf_s(cmd, sizeof(cmd), _TRUNCATE, "\"%s\" %s", service->exe, service->flags) < 0) {
\r
432 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);
\r
433 close_output_handles(&si);
\r
434 return stop_service(service, 2, true, true);
\r
437 throttle_restart(service);
\r
439 bool inherit_handles = false;
\r
440 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
441 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, 0, service->env, service->dir, &si, &pi)) {
\r
442 unsigned long error = GetLastError();
\r
443 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
444 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);
\r
445 close_output_handles(&si);
\r
446 return stop_service(service, 3, true, true);
\r
448 service->process_handle = pi.hProcess;
\r
449 service->pid = pi.dwProcessId;
\r
451 if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
\r
453 close_output_handles(&si);
\r
456 Wait for a clean startup before changing the service status to RUNNING
\r
457 but be mindful of the fact that we are blocking the service control manager
\r
458 so abandon the wait before too much time has elapsed.
\r
460 unsigned long delay = service->throttle_delay;
\r
461 if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
\r
462 char delay_milliseconds[16];
\r
463 _snprintf_s(delay_milliseconds, sizeof(delay_milliseconds), _TRUNCATE, "%lu", delay);
\r
464 char deadline_milliseconds[16];
\r
465 _snprintf_s(deadline_milliseconds, sizeof(deadline_milliseconds), _TRUNCATE, "%lu", NSSM_SERVICE_STATUS_DEADLINE);
\r
466 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
\r
467 delay = NSSM_SERVICE_STATUS_DEADLINE;
\r
469 unsigned long deadline = WaitForSingleObject(service->process_handle, delay);
\r
471 /* Signal successful start */
\r
472 service->status.dwCurrentState = SERVICE_RUNNING;
\r
473 SetServiceStatus(service->status_handle, &service->status);
\r
475 /* Continue waiting for a clean startup. */
\r
476 if (deadline == WAIT_TIMEOUT) {
\r
477 if (service->throttle_delay > delay) {
\r
478 if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;
\r
480 else service->throttle = 0;
\r
486 /* Stop the service */
\r
487 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {
\r
488 service->allow_restart = false;
\r
489 if (service->wait_handle) {
\r
490 UnregisterWait(service->wait_handle);
\r
491 service->wait_handle = 0;
\r
494 if (default_action && ! exitcode && ! graceful) {
\r
495 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
499 /* Signal we are stopping */
\r
501 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
502 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
503 SetServiceStatus(service->status_handle, &service->status);
\r
506 /* Nothing to do if service isn't running */
\r
507 if (service->pid) {
\r
508 /* Shut down service */
\r
509 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);
\r
510 kill_process(service, service->process_handle, service->pid, 0);
\r
512 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
\r
514 end_service((void *) service, true);
\r
516 /* Signal we stopped */
\r
518 service->status.dwCurrentState = SERVICE_STOPPED;
\r
520 service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
521 service->status.dwServiceSpecificExitCode = exitcode;
\r
524 service->status.dwWin32ExitCode = NO_ERROR;
\r
525 service->status.dwServiceSpecificExitCode = 0;
\r
527 SetServiceStatus(service->status_handle, &service->status);
\r
533 /* Callback function triggered when the server exits */
\r
534 void CALLBACK end_service(void *arg, unsigned char why) {
\r
535 nssm_service_t *service = (nssm_service_t *) arg;
\r
537 if (service->stopping) return;
\r
539 service->stopping = true;
\r
541 /* Check exit code */
\r
542 unsigned long exitcode = 0;
\r
544 GetExitCodeProcess(service->process_handle, &exitcode);
\r
545 if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);
\r
546 CloseHandle(service->process_handle);
\r
548 service->process_handle = 0;
\r
552 Log that the service ended BEFORE logging about killing the process
\r
553 tree. See below for the possible values of the why argument.
\r
556 _snprintf_s(code, sizeof(code), _TRUNCATE, "%lu", exitcode);
\r
557 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);
\r
561 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
562 kill_process_tree(service, service->pid, exitcode, service->pid);
\r
565 The why argument is true if our wait timed out or false otherwise.
\r
566 Our wait is infinite so why will never be true when called by the system.
\r
567 If it is indeed true, assume we were called from stop_service() because
\r
568 this is a controlled shutdown, and don't take any restart action.
\r
571 if (! service->allow_restart) return;
\r
573 /* What action should we take? */
\r
574 int action = NSSM_EXIT_RESTART;
\r
575 unsigned char action_string[ACTION_LEN];
\r
576 bool default_action;
\r
577 if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {
\r
578 for (int i = 0; exit_action_strings[i]; i++) {
\r
579 if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
587 /* Try to restart the service or return failure code to service manager */
\r
588 case NSSM_EXIT_RESTART:
\r
589 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);
\r
590 while (monitor_service(service)) {
\r
591 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);
\r
596 /* Do nothing, just like srvany would */
\r
597 case NSSM_EXIT_IGNORE:
\r
598 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);
\r
602 /* Tell the service manager we are finished */
\r
603 case NSSM_EXIT_REALLY:
\r
604 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);
\r
605 stop_service(service, exitcode, true, default_action);
\r
608 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
609 case NSSM_EXIT_UNCLEAN:
\r
610 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);
\r
611 stop_service(service, exitcode, false, default_action);
\r
618 void throttle_restart(nssm_service_t *service) {
\r
619 /* This can't be a restart if the service is already running. */
\r
620 if (! service->throttle++) return;
\r
622 int ms = throttle_milliseconds(service->throttle);
\r
624 if (service->throttle > 7) service->throttle = 8;
\r
626 char threshold[8], milliseconds[8];
\r
627 _snprintf_s(threshold, sizeof(threshold), _TRUNCATE, "%lu", service->throttle_delay);
\r
628 _snprintf_s(milliseconds, sizeof(milliseconds), _TRUNCATE, "%lu", ms);
\r
629 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
\r
631 if (use_critical_section) EnterCriticalSection(&service->throttle_section);
\r
632 else if (service->throttle_timer) {
\r
633 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
634 service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
635 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
638 service->status.dwCurrentState = SERVICE_PAUSED;
\r
639 SetServiceStatus(service->status_handle, &service->status);
\r
641 if (use_critical_section) {
\r
642 imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);
\r
643 LeaveCriticalSection(&service->throttle_section);
\r
646 if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);
\r
652 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
653 the number of milliseconds we expect the operation to take, and optionally
\r
654 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
655 operation completing or dwCheckPoint increasing, the system will consider the
\r
656 service to be hung.
\r
658 However the system will consider the service to be hung after 30000
\r
659 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
660 changed. Therefore if we want to wait longer than that we must periodically
\r
661 increase dwCheckPoint.
\r
663 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
664 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
665 time dwCheckPoint is also increased.
\r
667 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
668 NSSM_SERVICE_STATUS_DEADLINE milliseconds. If the process is still running
\r
669 and we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
670 smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to
\r
673 Only doing both these things will prevent the system from killing the service.
\r
675 Returns: 1 if the wait timed out.
\r
676 0 if the wait completed.
\r
679 int await_shutdown(nssm_service_t *service, char *function_name, unsigned long timeout) {
\r
680 unsigned long interval;
\r
681 unsigned long waithint;
\r
683 unsigned long waited;
\r
684 char interval_milliseconds[16];
\r
685 char timeout_milliseconds[16];
\r
686 char waited_milliseconds[16];
\r
687 char *function = function_name;
\r
689 /* Add brackets to function name. */
\r
690 size_t funclen = strlen(function_name) + 3;
\r
691 char *func = (char *) HeapAlloc(GetProcessHeap(), 0, funclen);
\r
693 if (_snprintf_s(func, funclen, _TRUNCATE, "%s()", function_name) > -1) function = func;
\r
696 _snprintf_s(timeout_milliseconds, sizeof(timeout_milliseconds), _TRUNCATE, "%lu", timeout);
\r
698 waithint = service->status.dwWaitHint;
\r
700 while (waited < timeout) {
\r
701 interval = timeout - waited;
\r
702 if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
\r
704 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
705 service->status.dwWaitHint += interval;
\r
706 service->status.dwCheckPoint++;
\r
707 SetServiceStatus(service->status_handle, &service->status);
\r
710 _snprintf_s(waited_milliseconds, sizeof(waited_milliseconds), _TRUNCATE, "%lu", waited);
\r
711 _snprintf_s(interval_milliseconds, sizeof(interval_milliseconds), _TRUNCATE, "%lu", interval);
\r
712 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
715 switch (WaitForSingleObject(service->process_handle, interval)) {
\r
716 case WAIT_OBJECT_0:
\r
729 waited += interval;
\r
733 if (func) HeapFree(GetProcessHeap(), 0, func);
\r