4 SERVICE_STATUS service_status;
\r
5 SERVICE_STATUS_HANDLE service_handle;
\r
6 HANDLE process_handle;
\r
9 static char service_name[SERVICE_NAME_LENGTH];
\r
10 char exe[EXE_LENGTH];
\r
11 char flags[CMD_LENGTH];
\r
15 unsigned long throttle_delay;
\r
16 unsigned long stop_method;
\r
17 unsigned long kill_console_delay;
\r
18 unsigned long kill_window_delay;
\r
19 unsigned long kill_threads_delay;
\r
20 CRITICAL_SECTION throttle_section;
\r
21 CONDITION_VARIABLE throttle_condition;
\r
22 HANDLE throttle_timer;
\r
23 LARGE_INTEGER throttle_duetime;
\r
24 bool use_critical_section;
\r
25 FILETIME creation_time;
\r
27 extern imports_t imports;
\r
29 static enum { NSSM_EXIT_RESTART, NSSM_EXIT_IGNORE, NSSM_EXIT_REALLY, NSSM_EXIT_UNCLEAN } exit_actions;
\r
30 static const char *exit_action_strings[] = { "Restart", "Ignore", "Exit", "Suicide", 0 };
\r
32 static unsigned long throttle;
\r
34 static inline int throttle_milliseconds() {
\r
35 /* pow() operates on doubles. */
\r
36 int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;
\r
41 Wrapper to be called in a new thread so that we can acknowledge a STOP
\r
42 control immediately.
\r
44 static unsigned long WINAPI shutdown_service(void *arg) {
\r
45 return stop_service(0, true, true);
\r
48 /* Connect to the service manager */
\r
49 SC_HANDLE open_service_manager() {
\r
50 SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
\r
52 if (is_admin) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);
\r
59 /* About to install the service */
\r
60 int pre_install_service(int argc, char **argv) {
\r
61 /* Show the dialogue box if we didn't give the service name and path */
\r
62 if (argc < 2) return nssm_gui(IDD_INSTALL, argv[0]);
\r
64 /* Arguments are optional */
\r
66 size_t flagslen = 0;
\r
69 for (i = 2; i < argc; i++) flagslen += strlen(argv[i]) + 1;
\r
70 if (! flagslen) flagslen = 1;
\r
72 flags = (char *) HeapAlloc(GetProcessHeap(), 0, flagslen);
\r
74 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "flags", "pre_install_service()", 0);
\r
77 ZeroMemory(flags, flagslen);
\r
80 This probably isn't UTF8-safe and should use std::string or something
\r
81 but it's been broken for the best part of a decade and due for a rewrite
\r
82 anyway so it'll do as a quick-'n'-dirty fix. Note that we don't free
\r
83 the flags buffer but as the program exits that isn't a big problem.
\r
85 for (i = 2; i < argc; i++) {
\r
86 size_t len = strlen(argv[i]);
\r
87 memmove(flags + s, argv[i], len);
\r
89 if (i < argc - 1) flags[s++] = ' ';
\r
92 return install_service(argv[0], argv[1], flags);
\r
95 /* About to remove the service */
\r
96 int pre_remove_service(int argc, char **argv) {
\r
97 /* Show dialogue box if we didn't pass service name and "confirm" */
\r
98 if (argc < 2) return nssm_gui(IDD_REMOVE, argv[0]);
\r
99 if (str_equiv(argv[1], "confirm")) return remove_service(argv[0]);
\r
100 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
104 /* Install the service */
\r
105 int install_service(char *name, char *exe, char *flags) {
\r
106 /* Open service manager */
\r
107 SC_HANDLE services = open_service_manager();
\r
109 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
113 /* Get path of this program */
\r
114 char path[MAX_PATH];
\r
115 GetModuleFileName(0, path, MAX_PATH);
\r
117 /* Construct command */
\r
118 char command[CMD_LENGTH];
\r
119 size_t pathlen = strlen(path);
\r
120 if (pathlen + 1 >= VALUE_LENGTH) {
\r
121 print_message(stderr, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);
\r
124 if (_snprintf_s(command, sizeof(command), _TRUNCATE, "\"%s\"", path) < 0) {
\r
125 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY_FOR_IMAGEPATH);
\r
129 /* Work out directory name */
\r
130 size_t len = strlen(exe);
\r
132 for (i = len; i && exe[i] != '\\' && exe[i] != '/'; i--);
\r
133 char dir[MAX_PATH];
\r
134 memmove(dir, exe, i);
\r
137 /* Create the service */
\r
138 SC_HANDLE service = CreateService(services, name, name, SC_MANAGER_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, command, 0, 0, 0, 0, 0);
\r
140 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
141 CloseServiceHandle(services);
\r
145 /* Now we need to put the parameters into the registry */
\r
146 if (create_parameters(name, exe, flags, dir)) {
\r
147 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
148 DeleteService(service);
\r
149 CloseServiceHandle(services);
\r
153 set_service_recovery(service, name);
\r
156 CloseServiceHandle(service);
\r
157 CloseServiceHandle(services);
\r
159 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, name);
\r
163 /* Remove the service */
\r
164 int remove_service(char *name) {
\r
165 /* Open service manager */
\r
166 SC_HANDLE services = open_service_manager();
\r
168 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
172 /* Try to open the service */
\r
173 SC_HANDLE service = OpenService(services, name, SC_MANAGER_ALL_ACCESS);
\r
175 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
176 CloseServiceHandle(services);
\r
180 /* Try to delete the service */
\r
181 if (! DeleteService(service)) {
\r
182 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
183 CloseServiceHandle(service);
\r
184 CloseServiceHandle(services);
\r
189 CloseServiceHandle(service);
\r
190 CloseServiceHandle(services);
\r
192 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, name);
\r
196 /* Service initialisation */
\r
197 void WINAPI service_main(unsigned long argc, char **argv) {
\r
198 if (_snprintf_s(service_name, sizeof(service_name), _TRUNCATE, "%s", argv[0]) < 0) {
\r
199 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service_name", "service_main()", 0);
\r
203 /* We can use a condition variable in a critical section on Vista or later. */
\r
204 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
205 else use_critical_section = false;
\r
207 /* Initialise status */
\r
208 ZeroMemory(&service_status, sizeof(service_status));
\r
209 service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
210 service_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
211 service_status.dwWin32ExitCode = NO_ERROR;
\r
212 service_status.dwServiceSpecificExitCode = 0;
\r
213 service_status.dwCheckPoint = 0;
\r
214 service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
216 /* Signal we AREN'T running the server */
\r
217 process_handle = 0;
\r
220 /* Register control handler */
\r
221 service_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, 0);
\r
222 if (! service_handle) {
\r
223 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
227 log_service_control(service_name, 0, true);
\r
229 service_status.dwCurrentState = SERVICE_START_PENDING;
\r
230 service_status.dwWaitHint = throttle_delay + NSSM_WAITHINT_MARGIN;
\r
231 SetServiceStatus(service_handle, &service_status);
\r
234 /* Try to create the exit action parameters; we don't care if it fails */
\r
235 create_exit_action(argv[0], exit_action_strings[0]);
\r
237 set_service_recovery(0, service_name);
\r
240 /* Used for signalling a resume if the service pauses when throttled. */
\r
241 if (use_critical_section) InitializeCriticalSection(&throttle_section);
\r
243 throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
244 if (! throttle_timer) {
\r
245 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service_name, error_string(GetLastError()), 0);
\r
252 /* Make sure service recovery actions are taken where necessary */
\r
253 void set_service_recovery(SC_HANDLE service, char *service_name) {
\r
254 SC_HANDLE services = 0;
\r
257 services = open_service_manager();
\r
258 if (! services) return;
\r
260 service = OpenService(services, service_name, SC_MANAGER_ALL_ACCESS);
\r
261 if (! service) return;
\r
264 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
265 ZeroMemory(&flag, sizeof(flag));
\r
266 flag.fFailureActionsOnNonCrashFailures = true;
\r
268 /* This functionality was added in Vista so the call may fail */
\r
269 if (! ChangeServiceConfig2(service, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
270 unsigned long error = GetLastError();
\r
271 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
272 if (error != ERROR_INVALID_LEVEL) {
\r
273 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CHANGESERVICECONFIG2_FAILED, service_name, error_string(error), 0);
\r
278 CloseServiceHandle(service);
\r
279 CloseServiceHandle(services);
\r
283 int monitor_service() {
\r
284 /* Set service status to started */
\r
285 int ret = start_service();
\r
288 _snprintf_s(code, sizeof(code), _TRUNCATE, "%d", ret);
\r
289 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, exe, service_name, ret, 0);
\r
292 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, exe, flags, service_name, dir, 0);
\r
294 /* Monitor service */
\r
295 if (! RegisterWaitForSingleObject(&wait_handle, process_handle, end_service, (void *) pid, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
296 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service_name, 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
347 case SERVICE_CONTROL_INTERROGATE:
\r
348 /* We always keep the service status up-to-date so this is a no-op. */
\r
351 case SERVICE_CONTROL_SHUTDOWN:
\r
352 case SERVICE_CONTROL_STOP:
\r
353 log_service_control(service_name, control, true);
\r
355 We MUST acknowledge the stop request promptly but we're committed to
\r
356 waiting for the application to exit. Spawn a new thread to wait
\r
357 while we acknowledge the request.
\r
359 if (! CreateThread(NULL, 0, shutdown_service, (void *) service_name, 0, NULL)) {
\r
360 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
\r
363 We couldn't create a thread to tidy up so we'll have to force the tidyup
\r
364 to complete in time in this thread.
\r
366 kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
367 kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
368 kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
370 stop_service(0, true, true);
\r
374 case SERVICE_CONTROL_CONTINUE:
\r
375 log_service_control(service_name, control, true);
\r
377 if (use_critical_section) imports.WakeConditionVariable(&throttle_condition);
\r
379 if (! throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
380 ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));
\r
381 SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);
\r
383 service_status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
384 service_status.dwWaitHint = throttle_milliseconds() + NSSM_WAITHINT_MARGIN;
\r
385 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service_name, 0);
\r
386 SetServiceStatus(service_handle, &service_status);
\r
389 case SERVICE_CONTROL_PAUSE:
\r
391 We don't accept pause messages but it isn't possible to register
\r
392 only for continue messages so we have to handle this case.
\r
394 log_service_control(service_name, control, false);
\r
395 return ERROR_CALL_NOT_IMPLEMENTED;
\r
398 /* Unknown control */
\r
399 log_service_control(service_name, control, false);
\r
400 return ERROR_CALL_NOT_IMPLEMENTED;
\r
403 /* Start the service */
\r
404 int start_service() {
\r
406 allow_restart = true;
\r
408 if (process_handle) return 0;
\r
410 /* Allocate a STARTUPINFO structure for a new process */
\r
412 ZeroMemory(&si, sizeof(si));
\r
413 si.cb = sizeof(si);
\r
415 /* Allocate a PROCESSINFO structure for the process */
\r
416 PROCESS_INFORMATION pi;
\r
417 ZeroMemory(&pi, sizeof(pi));
\r
419 /* Get startup parameters */
\r
421 int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay, &stop_method, &kill_console_delay, &kill_window_delay, &kill_threads_delay, &si);
\r
423 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service_name, 0);
\r
424 return stop_service(2, true, true);
\r
427 /* Launch executable with arguments */
\r
428 char cmd[CMD_LENGTH];
\r
429 if (_snprintf_s(cmd, sizeof(cmd), _TRUNCATE, "\"%s\" %s", exe, flags) < 0) {
\r
430 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);
\r
431 close_output_handles(&si);
\r
432 return stop_service(2, true, true);
\r
435 throttle_restart();
\r
437 bool inherit_handles = false;
\r
438 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
439 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, 0, env, dir, &si, &pi)) {
\r
440 unsigned long error = GetLastError();
\r
441 if (error == ERROR_INVALID_PARAMETER && env) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service_name, exe, NSSM_REG_ENV, 0);
\r
442 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, error_string(error), 0);
\r
443 close_output_handles(&si);
\r
444 return stop_service(3, true, true);
\r
446 process_handle = pi.hProcess;
\r
447 pid = pi.dwProcessId;
\r
449 if (get_process_creation_time(process_handle, &creation_time)) ZeroMemory(&creation_time, sizeof(creation_time));
\r
451 close_output_handles(&si);
\r
453 /* Wait for a clean startup. */
\r
454 if (WaitForSingleObject(process_handle, throttle_delay) == WAIT_TIMEOUT) throttle = 0;
\r
456 /* Signal successful start */
\r
457 service_status.dwCurrentState = SERVICE_RUNNING;
\r
458 SetServiceStatus(service_handle, &service_status);
\r
463 /* Stop the service */
\r
464 int stop_service(unsigned long exitcode, bool graceful, bool default_action) {
\r
465 allow_restart = false;
\r
466 if (wait_handle) UnregisterWait(wait_handle);
\r
468 if (default_action && ! exitcode && ! graceful) {
\r
469 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_GRACEFUL_SUICIDE, service_name, 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
473 /* Signal we are stopping */
\r
475 service_status.dwCurrentState = SERVICE_STOP_PENDING;
\r
476 service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
477 SetServiceStatus(service_handle, &service_status);
\r
480 /* Nothing to do if service isn't running */
\r
482 /* Shut down service */
\r
483 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);
\r
484 kill_process(service_name, service_handle, &service_status, stop_method, process_handle, pid, 0);
\r
486 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);
\r
488 end_service((void *) pid, true);
\r
490 /* Signal we stopped */
\r
492 service_status.dwCurrentState = SERVICE_STOPPED;
\r
494 service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
495 service_status.dwServiceSpecificExitCode = exitcode;
\r
498 service_status.dwWin32ExitCode = NO_ERROR;
\r
499 service_status.dwServiceSpecificExitCode = 0;
\r
501 SetServiceStatus(service_handle, &service_status);
\r
507 /* Callback function triggered when the server exits */
\r
508 void CALLBACK end_service(void *arg, unsigned char why) {
\r
509 if (stopping) return;
\r
513 pid = (unsigned long) arg;
\r
515 /* Check exit code */
\r
516 unsigned long exitcode = 0;
\r
518 FILETIME exit_time;
\r
519 GetExitCodeProcess(process_handle, &exitcode);
\r
520 if (exitcode == STILL_ACTIVE || get_process_exit_time(process_handle, &exit_time)) GetSystemTimeAsFileTime(&exit_time);
\r
521 CloseHandle(process_handle);
\r
524 Log that the service ended BEFORE logging about killing the process
\r
525 tree. See below for the possible values of the why argument.
\r
528 _snprintf_s(code, sizeof(code), _TRUNCATE, "%lu", exitcode);
\r
529 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);
\r
533 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
534 kill_process_tree(service_name, service_handle, &service_status, stop_method, pid, exitcode, pid, &creation_time, &exit_time);
\r
537 The why argument is true if our wait timed out or false otherwise.
\r
538 Our wait is infinite so why will never be true when called by the system.
\r
539 If it is indeed true, assume we were called from stop_service() because
\r
540 this is a controlled shutdown, and don't take any restart action.
\r
543 if (! allow_restart) return;
\r
545 /* What action should we take? */
\r
546 int action = NSSM_EXIT_RESTART;
\r
547 unsigned char action_string[ACTION_LEN];
\r
548 bool default_action;
\r
549 if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) {
\r
550 for (int i = 0; exit_action_strings[i]; i++) {
\r
551 if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
558 process_handle = 0;
\r
561 /* Try to restart the service or return failure code to service manager */
\r
562 case NSSM_EXIT_RESTART:
\r
563 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service_name, code, exit_action_strings[action], exe, 0);
\r
564 while (monitor_service()) {
\r
565 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, exe, service_name, 0);
\r
570 /* Do nothing, just like srvany would */
\r
571 case NSSM_EXIT_IGNORE:
\r
572 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service_name, code, exit_action_strings[action], exe, 0);
\r
576 /* Tell the service manager we are finished */
\r
577 case NSSM_EXIT_REALLY:
\r
578 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);
\r
579 stop_service(exitcode, true, default_action);
\r
582 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
583 case NSSM_EXIT_UNCLEAN:
\r
584 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);
\r
585 stop_service(exitcode, false, default_action);
\r
592 void throttle_restart() {
\r
593 /* This can't be a restart if the service is already running. */
\r
594 if (! throttle++) return;
\r
596 int ms = throttle_milliseconds();
\r
598 if (throttle > 7) throttle = 8;
\r
600 char threshold[8], milliseconds[8];
\r
601 _snprintf_s(threshold, sizeof(threshold), _TRUNCATE, "%lu", throttle_delay);
\r
602 _snprintf_s(milliseconds, sizeof(milliseconds), _TRUNCATE, "%lu", ms);
\r
603 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);
\r
605 if (use_critical_section) EnterCriticalSection(&throttle_section);
\r
606 else if (throttle_timer) {
\r
607 ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));
\r
608 throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
609 SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);
\r
612 service_status.dwCurrentState = SERVICE_PAUSED;
\r
613 SetServiceStatus(service_handle, &service_status);
\r
615 if (use_critical_section) {
\r
616 imports.SleepConditionVariableCS(&throttle_condition, &throttle_section, ms);
\r
617 LeaveCriticalSection(&throttle_section);
\r
620 if (throttle_timer) WaitForSingleObject(throttle_timer, INFINITE);
\r
626 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
627 the number of milliseconds we expect the operation to take, and optionally
\r
628 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
629 operation completing or dwCheckPoint increasing, the system will consider the
\r
630 service to be hung.
\r
632 However the system will consider the service to be hung after 30000
\r
633 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
634 changed. Therefore if we want to wait longer than that we must periodically
\r
635 increase dwCheckPoint.
\r
637 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
638 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
639 time dwCheckPoint is also increased.
\r
641 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
642 NSSM_SHUTDOWN_CHECKPOINT milliseconds. If the process is still running and
\r
643 we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
644 smaller of NSSM_SHUTDOWN_CHECKPOINT or the remaining timeout to dwWaitHint.
\r
646 Only doing both these things will prevent the system from killing the service.
\r
648 Returns: 1 if the wait timed out.
\r
649 0 if the wait completed.
\r
652 int await_shutdown(char *function_name, char *service_name, SERVICE_STATUS_HANDLE service_handle, SERVICE_STATUS *service_status, HANDLE process_handle, unsigned long timeout) {
\r
653 unsigned long interval;
\r
654 unsigned long waithint;
\r
656 unsigned long waited;
\r
657 char interval_milliseconds[16];
\r
658 char timeout_milliseconds[16];
\r
659 char waited_milliseconds[16];
\r
660 char *function = function_name;
\r
662 /* Add brackets to function name. */
\r
663 size_t funclen = strlen(function_name) + 3;
\r
664 char *func = (char *) HeapAlloc(GetProcessHeap(), 0, funclen);
\r
666 if (_snprintf_s(func, funclen, _TRUNCATE, "%s()", function_name) > -1) function = func;
\r
669 _snprintf_s(timeout_milliseconds, sizeof(timeout_milliseconds), _TRUNCATE, "%lu", timeout);
\r
671 waithint = service_status->dwWaitHint;
\r
673 while (waited < timeout) {
\r
674 interval = timeout - waited;
\r
675 if (interval > NSSM_SHUTDOWN_CHECKPOINT) interval = NSSM_SHUTDOWN_CHECKPOINT;
\r
677 service_status->dwCurrentState = SERVICE_STOP_PENDING;
\r
678 service_status->dwWaitHint += interval;
\r
679 service_status->dwCheckPoint++;
\r
680 SetServiceStatus(service_handle, service_status);
\r
683 _snprintf_s(waited_milliseconds, sizeof(waited_milliseconds), _TRUNCATE, "%lu", waited);
\r
684 _snprintf_s(interval_milliseconds, sizeof(interval_milliseconds), _TRUNCATE, "%lu", interval);
\r
685 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service_name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
688 switch (WaitForSingleObject(process_handle, interval)) {
\r
689 case WAIT_OBJECT_0:
\r
702 waited += interval;
\r
706 if (func) HeapFree(GetProcessHeap(), 0, func);
\r