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
40 /* Connect to the service manager */
\r
41 SC_HANDLE open_service_manager() {
\r
42 SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
\r
44 if (is_admin) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);
\r
51 /* About to install the service */
\r
52 int pre_install_service(int argc, char **argv) {
\r
53 /* Show the dialogue box if we didn't give the service name and path */
\r
54 if (argc < 2) return nssm_gui(IDD_INSTALL, argv[0]);
\r
56 /* Arguments are optional */
\r
58 size_t flagslen = 0;
\r
61 for (i = 2; i < argc; i++) flagslen += strlen(argv[i]) + 1;
\r
62 if (! flagslen) flagslen = 1;
\r
64 flags = (char *) HeapAlloc(GetProcessHeap(), 0, flagslen);
\r
66 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "flags", "pre_install_service()", 0);
\r
69 ZeroMemory(flags, flagslen);
\r
72 This probably isn't UTF8-safe and should use std::string or something
\r
73 but it's been broken for the best part of a decade and due for a rewrite
\r
74 anyway so it'll do as a quick-'n'-dirty fix. Note that we don't free
\r
75 the flags buffer but as the program exits that isn't a big problem.
\r
77 for (i = 2; i < argc; i++) {
\r
78 size_t len = strlen(argv[i]);
\r
79 memmove(flags + s, argv[i], len);
\r
81 if (i < argc - 1) flags[s++] = ' ';
\r
84 return install_service(argv[0], argv[1], flags);
\r
87 /* About to remove the service */
\r
88 int pre_remove_service(int argc, char **argv) {
\r
89 /* Show dialogue box if we didn't pass service name and "confirm" */
\r
90 if (argc < 2) return nssm_gui(IDD_REMOVE, argv[0]);
\r
91 if (str_equiv(argv[1], "confirm")) return remove_service(argv[0]);
\r
92 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
96 /* Install the service */
\r
97 int install_service(char *name, char *exe, char *flags) {
\r
98 /* Open service manager */
\r
99 SC_HANDLE services = open_service_manager();
\r
101 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
105 /* Get path of this program */
\r
106 char path[MAX_PATH];
\r
107 GetModuleFileName(0, path, MAX_PATH);
\r
109 /* Construct command */
\r
110 char command[CMD_LENGTH];
\r
111 size_t pathlen = strlen(path);
\r
112 if (pathlen + 1 >= VALUE_LENGTH) {
\r
113 print_message(stderr, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);
\r
116 if (_snprintf_s(command, sizeof(command), _TRUNCATE, "\"%s\"", path) < 0) {
\r
117 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY_FOR_IMAGEPATH);
\r
121 /* Work out directory name */
\r
122 size_t len = strlen(exe);
\r
124 for (i = len; i && exe[i] != '\\' && exe[i] != '/'; i--);
\r
125 char dir[MAX_PATH];
\r
126 memmove(dir, exe, i);
\r
129 /* Create the service */
\r
130 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
132 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
133 CloseServiceHandle(services);
\r
137 /* Now we need to put the parameters into the registry */
\r
138 if (create_parameters(name, exe, flags, dir)) {
\r
139 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
140 DeleteService(service);
\r
141 CloseServiceHandle(services);
\r
145 set_service_recovery(service, name);
\r
148 CloseServiceHandle(service);
\r
149 CloseServiceHandle(services);
\r
151 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, name);
\r
155 /* Remove the service */
\r
156 int remove_service(char *name) {
\r
157 /* Open service manager */
\r
158 SC_HANDLE services = open_service_manager();
\r
160 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
164 /* Try to open the service */
\r
165 SC_HANDLE service = OpenService(services, name, SC_MANAGER_ALL_ACCESS);
\r
167 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
168 CloseServiceHandle(services);
\r
172 /* Try to delete the service */
\r
173 if (! DeleteService(service)) {
\r
174 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
175 CloseServiceHandle(service);
\r
176 CloseServiceHandle(services);
\r
181 CloseServiceHandle(service);
\r
182 CloseServiceHandle(services);
\r
184 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, name);
\r
188 /* Service initialisation */
\r
189 void WINAPI service_main(unsigned long argc, char **argv) {
\r
190 if (_snprintf_s(service_name, sizeof(service_name), _TRUNCATE, "%s", argv[0]) < 0) {
\r
191 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service_name", "service_main()", 0);
\r
195 /* We can use a condition variable in a critical section on Vista or later. */
\r
196 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
197 else use_critical_section = false;
\r
199 /* Initialise status */
\r
200 ZeroMemory(&service_status, sizeof(service_status));
\r
201 service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
202 service_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
203 service_status.dwWin32ExitCode = NO_ERROR;
\r
204 service_status.dwServiceSpecificExitCode = 0;
\r
205 service_status.dwCheckPoint = 0;
\r
206 service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
208 /* Signal we AREN'T running the server */
\r
209 process_handle = 0;
\r
212 /* Register control handler */
\r
213 service_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, 0);
\r
214 if (! service_handle) {
\r
215 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
219 log_service_control(service_name, 0, true);
\r
221 service_status.dwCurrentState = SERVICE_START_PENDING;
\r
222 service_status.dwWaitHint = throttle_delay + NSSM_WAITHINT_MARGIN;
\r
223 SetServiceStatus(service_handle, &service_status);
\r
226 /* Try to create the exit action parameters; we don't care if it fails */
\r
227 create_exit_action(argv[0], exit_action_strings[0]);
\r
229 set_service_recovery(0, service_name);
\r
232 /* Used for signalling a resume if the service pauses when throttled. */
\r
233 if (use_critical_section) InitializeCriticalSection(&throttle_section);
\r
235 throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
236 if (! throttle_timer) {
\r
237 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service_name, error_string(GetLastError()), 0);
\r
244 /* Make sure service recovery actions are taken where necessary */
\r
245 void set_service_recovery(SC_HANDLE service, char *service_name) {
\r
246 SC_HANDLE services = 0;
\r
249 services = open_service_manager();
\r
250 if (! services) return;
\r
252 service = OpenService(services, service_name, SC_MANAGER_ALL_ACCESS);
\r
253 if (! service) return;
\r
256 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
257 ZeroMemory(&flag, sizeof(flag));
\r
258 flag.fFailureActionsOnNonCrashFailures = true;
\r
260 /* This functionality was added in Vista so the call may fail */
\r
261 if (! ChangeServiceConfig2(service, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
262 unsigned long error = GetLastError();
\r
263 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
264 if (error != ERROR_INVALID_LEVEL) {
\r
265 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CHANGESERVICECONFIG2_FAILED, service_name, error_string(error), 0);
\r
270 CloseServiceHandle(service);
\r
271 CloseServiceHandle(services);
\r
275 int monitor_service() {
\r
276 /* Set service status to started */
\r
277 int ret = start_service();
\r
280 _snprintf_s(code, sizeof(code), _TRUNCATE, "%d", ret);
\r
281 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, exe, service_name, ret, 0);
\r
284 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, exe, flags, service_name, dir, 0);
\r
286 /* Monitor service */
\r
287 if (! RegisterWaitForSingleObject(&wait_handle, process_handle, end_service, (void *) pid, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
288 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service_name, exe, error_string(GetLastError()), 0);
\r
294 char *service_control_text(unsigned long control) {
\r
296 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
297 case 0: return "START";
\r
298 case SERVICE_CONTROL_STOP: return "STOP";
\r
299 case SERVICE_CONTROL_SHUTDOWN: return "SHUTDOWN";
\r
300 case SERVICE_CONTROL_PAUSE: return "PAUSE";
\r
301 case SERVICE_CONTROL_CONTINUE: return "CONTINUE";
\r
302 case SERVICE_CONTROL_INTERROGATE: return "INTERROGATE";
\r
307 void log_service_control(char *service_name, unsigned long control, bool handled) {
\r
308 char *text = service_control_text(control);
\r
309 unsigned long event;
\r
312 /* "0x" + 8 x hex + NULL */
\r
313 text = (char *) HeapAlloc(GetProcessHeap(), 0, 11);
\r
315 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control()", 0);
\r
318 if (_snprintf_s(text, 11, _TRUNCATE, "0x%08x", control) < 0) {
\r
319 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control()", 0);
\r
320 HeapFree(GetProcessHeap(), 0, text);
\r
324 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
326 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
327 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
329 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
331 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
332 HeapFree(GetProcessHeap(), 0, text);
\r
336 /* Service control handler */
\r
337 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
339 case SERVICE_CONTROL_INTERROGATE:
\r
340 /* We always keep the service status up-to-date so this is a no-op. */
\r
343 case SERVICE_CONTROL_SHUTDOWN:
\r
344 case SERVICE_CONTROL_STOP:
\r
345 log_service_control(service_name, control, true);
\r
346 stop_service(0, true, true);
\r
349 case SERVICE_CONTROL_CONTINUE:
\r
350 log_service_control(service_name, control, true);
\r
352 if (use_critical_section) imports.WakeConditionVariable(&throttle_condition);
\r
354 if (! throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
355 ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));
\r
356 SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);
\r
358 service_status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
359 service_status.dwWaitHint = throttle_milliseconds() + NSSM_WAITHINT_MARGIN;
\r
360 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service_name, 0);
\r
361 SetServiceStatus(service_handle, &service_status);
\r
364 case SERVICE_CONTROL_PAUSE:
\r
366 We don't accept pause messages but it isn't possible to register
\r
367 only for continue messages so we have to handle this case.
\r
369 log_service_control(service_name, control, false);
\r
370 return ERROR_CALL_NOT_IMPLEMENTED;
\r
373 /* Unknown control */
\r
374 log_service_control(service_name, control, false);
\r
375 return ERROR_CALL_NOT_IMPLEMENTED;
\r
378 /* Start the service */
\r
379 int start_service() {
\r
381 allow_restart = true;
\r
383 if (process_handle) return 0;
\r
385 /* Allocate a STARTUPINFO structure for a new process */
\r
387 ZeroMemory(&si, sizeof(si));
\r
388 si.cb = sizeof(si);
\r
390 /* Allocate a PROCESSINFO structure for the process */
\r
391 PROCESS_INFORMATION pi;
\r
392 ZeroMemory(&pi, sizeof(pi));
\r
394 /* Get startup parameters */
\r
396 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
398 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service_name, 0);
\r
399 return stop_service(2, true, true);
\r
402 /* Launch executable with arguments */
\r
403 char cmd[CMD_LENGTH];
\r
404 if (_snprintf_s(cmd, sizeof(cmd), _TRUNCATE, "\"%s\" %s", exe, flags) < 0) {
\r
405 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);
\r
406 close_output_handles(&si);
\r
407 return stop_service(2, true, true);
\r
410 throttle_restart();
\r
412 bool inherit_handles = false;
\r
413 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
414 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, 0, env, dir, &si, &pi)) {
\r
415 unsigned long error = GetLastError();
\r
416 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
417 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, error_string(error), 0);
\r
418 close_output_handles(&si);
\r
419 return stop_service(3, true, true);
\r
421 process_handle = pi.hProcess;
\r
422 pid = pi.dwProcessId;
\r
424 if (get_process_creation_time(process_handle, &creation_time)) ZeroMemory(&creation_time, sizeof(creation_time));
\r
426 close_output_handles(&si);
\r
428 /* Wait for a clean startup. */
\r
429 if (WaitForSingleObject(process_handle, throttle_delay) == WAIT_TIMEOUT) throttle = 0;
\r
431 /* Signal successful start */
\r
432 service_status.dwCurrentState = SERVICE_RUNNING;
\r
433 SetServiceStatus(service_handle, &service_status);
\r
438 /* Stop the service */
\r
439 int stop_service(unsigned long exitcode, bool graceful, bool default_action) {
\r
440 allow_restart = false;
\r
441 if (wait_handle) UnregisterWait(wait_handle);
\r
443 if (default_action && ! exitcode && ! graceful) {
\r
444 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
448 /* Signal we are stopping */
\r
450 service_status.dwCurrentState = SERVICE_STOP_PENDING;
\r
451 service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
452 if (stop_method & NSSM_STOP_METHOD_CONSOLE && imports.AttachConsole) service_status.dwWaitHint += kill_console_delay;
\r
453 if (stop_method & NSSM_STOP_METHOD_WINDOW) service_status.dwWaitHint += kill_window_delay;
\r
454 if (stop_method & NSSM_STOP_METHOD_THREADS) service_status.dwWaitHint += kill_threads_delay;
\r
455 SetServiceStatus(service_handle, &service_status);
\r
458 /* Nothing to do if service isn't running */
\r
460 /* Shut down service */
\r
461 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);
\r
462 kill_process(service_name, service_handle, &service_status, stop_method, process_handle, pid, 0);
\r
464 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);
\r
466 end_service((void *) pid, true);
\r
468 /* Signal we stopped */
\r
470 service_status.dwCurrentState = SERVICE_STOPPED;
\r
472 service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
473 service_status.dwServiceSpecificExitCode = exitcode;
\r
476 service_status.dwWin32ExitCode = NO_ERROR;
\r
477 service_status.dwServiceSpecificExitCode = 0;
\r
479 SetServiceStatus(service_handle, &service_status);
\r
485 /* Callback function triggered when the server exits */
\r
486 void CALLBACK end_service(void *arg, unsigned char why) {
\r
487 if (stopping) return;
\r
491 pid = (unsigned long) arg;
\r
493 /* Check exit code */
\r
494 unsigned long exitcode = 0;
\r
496 FILETIME exit_time;
\r
497 GetExitCodeProcess(process_handle, &exitcode);
\r
498 if (exitcode == STILL_ACTIVE || get_process_exit_time(process_handle, &exit_time)) GetSystemTimeAsFileTime(&exit_time);
\r
499 CloseHandle(process_handle);
\r
502 Log that the service ended BEFORE logging about killing the process
\r
503 tree. See below for the possible values of the why argument.
\r
506 _snprintf_s(code, sizeof(code), _TRUNCATE, "%lu", exitcode);
\r
507 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);
\r
511 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
512 kill_process_tree(service_name, service_handle, &service_status, stop_method, pid, exitcode, pid, &creation_time, &exit_time);
\r
515 The why argument is true if our wait timed out or false otherwise.
\r
516 Our wait is infinite so why will never be true when called by the system.
\r
517 If it is indeed true, assume we were called from stop_service() because
\r
518 this is a controlled shutdown, and don't take any restart action.
\r
521 if (! allow_restart) return;
\r
523 /* What action should we take? */
\r
524 int action = NSSM_EXIT_RESTART;
\r
525 unsigned char action_string[ACTION_LEN];
\r
526 bool default_action;
\r
527 if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) {
\r
528 for (int i = 0; exit_action_strings[i]; i++) {
\r
529 if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
536 process_handle = 0;
\r
539 /* Try to restart the service or return failure code to service manager */
\r
540 case NSSM_EXIT_RESTART:
\r
541 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service_name, code, exit_action_strings[action], exe, 0);
\r
542 while (monitor_service()) {
\r
543 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, exe, service_name, 0);
\r
548 /* Do nothing, just like srvany would */
\r
549 case NSSM_EXIT_IGNORE:
\r
550 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service_name, code, exit_action_strings[action], exe, 0);
\r
554 /* Tell the service manager we are finished */
\r
555 case NSSM_EXIT_REALLY:
\r
556 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);
\r
557 stop_service(exitcode, true, default_action);
\r
560 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
561 case NSSM_EXIT_UNCLEAN:
\r
562 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);
\r
563 stop_service(exitcode, false, default_action);
\r
570 void throttle_restart() {
\r
571 /* This can't be a restart if the service is already running. */
\r
572 if (! throttle++) return;
\r
574 int ms = throttle_milliseconds();
\r
576 if (throttle > 7) throttle = 8;
\r
578 char threshold[8], milliseconds[8];
\r
579 _snprintf_s(threshold, sizeof(threshold), _TRUNCATE, "%lu", throttle_delay);
\r
580 _snprintf_s(milliseconds, sizeof(milliseconds), _TRUNCATE, "%lu", ms);
\r
581 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);
\r
583 if (use_critical_section) EnterCriticalSection(&throttle_section);
\r
584 else if (throttle_timer) {
\r
585 ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));
\r
586 throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
587 SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);
\r
590 service_status.dwCurrentState = SERVICE_PAUSED;
\r
591 SetServiceStatus(service_handle, &service_status);
\r
593 if (use_critical_section) {
\r
594 imports.SleepConditionVariableCS(&throttle_condition, &throttle_section, ms);
\r
595 LeaveCriticalSection(&throttle_section);
\r
598 if (throttle_timer) WaitForSingleObject(throttle_timer, INFINITE);
\r
604 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
605 the number of milliseconds we expect the operation to take, and optionally
\r
606 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
607 operation completing or dwCheckPoint increasing, the system will consider the
\r
608 service to be hung.
\r
610 However the system will consider the service to be hung after 30000
\r
611 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
612 changed. Therefore if we want to wait longer than that we must periodically
\r
613 increase dwCheckPoint.
\r
615 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
616 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
617 time dwCheckPoint is also increased.
\r
619 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
620 NSSM_SHUTDOWN_CHECKPOINT milliseconds. If the process is still running and
\r
621 we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
622 smaller of NSSM_SHUTDOWN_CHECKPOINT or the remaining timeout to dwWaitHint.
\r
624 Only doing both these things will prevent the system from killing the service.
\r
626 Returns: 1 if the wait timed out.
\r
627 0 if the wait completed.
\r
630 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
631 unsigned long interval;
\r
632 unsigned long waithint;
\r
634 unsigned long waited;
\r
635 char interval_milliseconds[16];
\r
636 char timeout_milliseconds[16];
\r
637 char waited_milliseconds[16];
\r
638 char *function = function_name;
\r
640 /* Add brackets to function name. */
\r
641 size_t funclen = strlen(function_name) + 3;
\r
642 char *func = (char *) HeapAlloc(GetProcessHeap(), 0, funclen);
\r
644 if (_snprintf_s(func, funclen, _TRUNCATE, "%s()", function_name) > -1) function = func;
\r
647 _snprintf_s(timeout_milliseconds, sizeof(timeout_milliseconds), _TRUNCATE, "%lu", timeout);
\r
649 waithint = service_status->dwWaitHint;
\r
651 while (waited < timeout) {
\r
652 interval = timeout - waited;
\r
653 if (interval > NSSM_SHUTDOWN_CHECKPOINT) interval = NSSM_SHUTDOWN_CHECKPOINT;
\r
655 service_status->dwCurrentState = SERVICE_STOP_PENDING;
\r
656 service_status->dwWaitHint += interval;
\r
657 service_status->dwCheckPoint++;
\r
658 SetServiceStatus(service_handle, service_status);
\r
661 _snprintf_s(waited_milliseconds, sizeof(waited_milliseconds), _TRUNCATE, "%lu", waited);
\r
662 _snprintf_s(interval_milliseconds, sizeof(interval_milliseconds), _TRUNCATE, "%lu", interval);
\r
663 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service_name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
666 switch (WaitForSingleObject(process_handle, interval)) {
\r
667 case WAIT_OBJECT_0:
\r
680 waited += interval;
\r
684 if (func) HeapFree(GetProcessHeap(), 0, func);
\r