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
454 Wait for a clean startup before changing the service status to RUNNING
\r
455 but be mindful of the fact that we are blocking the service control manager
\r
456 so abandon the wait before too much time has elapsed.
\r
458 unsigned long delay = throttle_delay;
\r
459 if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
\r
460 char delay_milliseconds[16];
\r
461 _snprintf_s(delay_milliseconds, sizeof(delay_milliseconds), _TRUNCATE, "%lu", delay);
\r
462 char deadline_milliseconds[16];
\r
463 _snprintf_s(deadline_milliseconds, sizeof(deadline_milliseconds), _TRUNCATE, "%lu", NSSM_SERVICE_STATUS_DEADLINE);
\r
464 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service_name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
\r
465 delay = NSSM_SERVICE_STATUS_DEADLINE;
\r
467 unsigned long deadline = WaitForSingleObject(process_handle, delay);
\r
469 /* Signal successful start */
\r
470 service_status.dwCurrentState = SERVICE_RUNNING;
\r
471 SetServiceStatus(service_handle, &service_status);
\r
473 /* Continue waiting for a clean startup. */
\r
474 if (deadline == WAIT_TIMEOUT) {
\r
475 if (throttle_delay > delay) {
\r
476 if (WaitForSingleObject(process_handle, throttle_delay - delay) == WAIT_TIMEOUT) throttle = 0;
\r
484 /* Stop the service */
\r
485 int stop_service(unsigned long exitcode, bool graceful, bool default_action) {
\r
486 allow_restart = false;
\r
487 if (wait_handle) UnregisterWait(wait_handle);
\r
489 if (default_action && ! exitcode && ! graceful) {
\r
490 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
494 /* Signal we are stopping */
\r
496 service_status.dwCurrentState = SERVICE_STOP_PENDING;
\r
497 service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
498 SetServiceStatus(service_handle, &service_status);
\r
501 /* Nothing to do if service isn't running */
\r
503 /* Shut down service */
\r
504 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);
\r
505 kill_process(service_name, service_handle, &service_status, stop_method, process_handle, pid, 0);
\r
507 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);
\r
509 end_service((void *) pid, true);
\r
511 /* Signal we stopped */
\r
513 service_status.dwCurrentState = SERVICE_STOPPED;
\r
515 service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
516 service_status.dwServiceSpecificExitCode = exitcode;
\r
519 service_status.dwWin32ExitCode = NO_ERROR;
\r
520 service_status.dwServiceSpecificExitCode = 0;
\r
522 SetServiceStatus(service_handle, &service_status);
\r
528 /* Callback function triggered when the server exits */
\r
529 void CALLBACK end_service(void *arg, unsigned char why) {
\r
530 if (stopping) return;
\r
534 pid = (unsigned long) arg;
\r
536 /* Check exit code */
\r
537 unsigned long exitcode = 0;
\r
539 FILETIME exit_time;
\r
540 GetExitCodeProcess(process_handle, &exitcode);
\r
541 if (exitcode == STILL_ACTIVE || get_process_exit_time(process_handle, &exit_time)) GetSystemTimeAsFileTime(&exit_time);
\r
542 CloseHandle(process_handle);
\r
545 Log that the service ended BEFORE logging about killing the process
\r
546 tree. See below for the possible values of the why argument.
\r
549 _snprintf_s(code, sizeof(code), _TRUNCATE, "%lu", exitcode);
\r
550 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);
\r
554 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
555 kill_process_tree(service_name, service_handle, &service_status, stop_method, pid, exitcode, pid, &creation_time, &exit_time);
\r
558 The why argument is true if our wait timed out or false otherwise.
\r
559 Our wait is infinite so why will never be true when called by the system.
\r
560 If it is indeed true, assume we were called from stop_service() because
\r
561 this is a controlled shutdown, and don't take any restart action.
\r
564 if (! allow_restart) return;
\r
566 /* What action should we take? */
\r
567 int action = NSSM_EXIT_RESTART;
\r
568 unsigned char action_string[ACTION_LEN];
\r
569 bool default_action;
\r
570 if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) {
\r
571 for (int i = 0; exit_action_strings[i]; i++) {
\r
572 if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
579 process_handle = 0;
\r
582 /* Try to restart the service or return failure code to service manager */
\r
583 case NSSM_EXIT_RESTART:
\r
584 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service_name, code, exit_action_strings[action], exe, 0);
\r
585 while (monitor_service()) {
\r
586 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, exe, service_name, 0);
\r
591 /* Do nothing, just like srvany would */
\r
592 case NSSM_EXIT_IGNORE:
\r
593 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service_name, code, exit_action_strings[action], exe, 0);
\r
597 /* Tell the service manager we are finished */
\r
598 case NSSM_EXIT_REALLY:
\r
599 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);
\r
600 stop_service(exitcode, true, default_action);
\r
603 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
604 case NSSM_EXIT_UNCLEAN:
\r
605 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);
\r
606 stop_service(exitcode, false, default_action);
\r
613 void throttle_restart() {
\r
614 /* This can't be a restart if the service is already running. */
\r
615 if (! throttle++) return;
\r
617 int ms = throttle_milliseconds();
\r
619 if (throttle > 7) throttle = 8;
\r
621 char threshold[8], milliseconds[8];
\r
622 _snprintf_s(threshold, sizeof(threshold), _TRUNCATE, "%lu", throttle_delay);
\r
623 _snprintf_s(milliseconds, sizeof(milliseconds), _TRUNCATE, "%lu", ms);
\r
624 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);
\r
626 if (use_critical_section) EnterCriticalSection(&throttle_section);
\r
627 else if (throttle_timer) {
\r
628 ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));
\r
629 throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
630 SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);
\r
633 service_status.dwCurrentState = SERVICE_PAUSED;
\r
634 SetServiceStatus(service_handle, &service_status);
\r
636 if (use_critical_section) {
\r
637 imports.SleepConditionVariableCS(&throttle_condition, &throttle_section, ms);
\r
638 LeaveCriticalSection(&throttle_section);
\r
641 if (throttle_timer) WaitForSingleObject(throttle_timer, INFINITE);
\r
647 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
648 the number of milliseconds we expect the operation to take, and optionally
\r
649 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
650 operation completing or dwCheckPoint increasing, the system will consider the
\r
651 service to be hung.
\r
653 However the system will consider the service to be hung after 30000
\r
654 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
655 changed. Therefore if we want to wait longer than that we must periodically
\r
656 increase dwCheckPoint.
\r
658 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
659 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
660 time dwCheckPoint is also increased.
\r
662 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
663 NSSM_SERVICE_STATUS_DEADLINE milliseconds. If the process is still running
\r
664 and we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
665 smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to
\r
668 Only doing both these things will prevent the system from killing the service.
\r
670 Returns: 1 if the wait timed out.
\r
671 0 if the wait completed.
\r
674 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
675 unsigned long interval;
\r
676 unsigned long waithint;
\r
678 unsigned long waited;
\r
679 char interval_milliseconds[16];
\r
680 char timeout_milliseconds[16];
\r
681 char waited_milliseconds[16];
\r
682 char *function = function_name;
\r
684 /* Add brackets to function name. */
\r
685 size_t funclen = strlen(function_name) + 3;
\r
686 char *func = (char *) HeapAlloc(GetProcessHeap(), 0, funclen);
\r
688 if (_snprintf_s(func, funclen, _TRUNCATE, "%s()", function_name) > -1) function = func;
\r
691 _snprintf_s(timeout_milliseconds, sizeof(timeout_milliseconds), _TRUNCATE, "%lu", timeout);
\r
693 waithint = service_status->dwWaitHint;
\r
695 while (waited < timeout) {
\r
696 interval = timeout - waited;
\r
697 if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
\r
699 service_status->dwCurrentState = SERVICE_STOP_PENDING;
\r
700 service_status->dwWaitHint += interval;
\r
701 service_status->dwCheckPoint++;
\r
702 SetServiceStatus(service_handle, service_status);
\r
705 _snprintf_s(waited_milliseconds, sizeof(waited_milliseconds), _TRUNCATE, "%lu", waited);
\r
706 _snprintf_s(interval_milliseconds, sizeof(interval_milliseconds), _TRUNCATE, "%lu", interval);
\r
707 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service_name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
710 switch (WaitForSingleObject(process_handle, interval)) {
\r
711 case WAIT_OBJECT_0:
\r
724 waited += interval;
\r
728 if (func) HeapFree(GetProcessHeap(), 0, func);
\r