4 bool use_critical_section;
\r
6 extern imports_t imports;
\r
8 const char *exit_action_strings[] = { "Restart", "Ignore", "Exit", "Suicide", 0 };
\r
10 static inline int throttle_milliseconds(unsigned long throttle) {
\r
11 /* pow() operates on doubles. */
\r
12 int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;
\r
17 Wrapper to be called in a new thread so that we can acknowledge a STOP
\r
18 control immediately.
\r
20 static unsigned long WINAPI shutdown_service(void *arg) {
\r
21 return stop_service((nssm_service_t *) arg, 0, true, true);
\r
24 /* Connect to the service manager */
\r
25 SC_HANDLE open_service_manager() {
\r
26 SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
\r
28 if (is_admin) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);
\r
35 /* Allocate and zero memory for a service. */
\r
36 nssm_service_t *alloc_nssm_service() {
\r
37 nssm_service_t *service = (nssm_service_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(nssm_service_t));
\r
38 if (! service) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service", "alloc_nssm_service()", 0);
\r
42 /* Free memory for a service. */
\r
43 void cleanup_nssm_service(nssm_service_t *service) {
\r
44 if (! service) return;
\r
45 if (service->env) HeapFree(GetProcessHeap(), 0, service->env);
\r
46 if (service->env_extra) HeapFree(GetProcessHeap(), 0, service->env_extra);
\r
47 if (service->handle) CloseServiceHandle(service->handle);
\r
48 if (service->process_handle) CloseHandle(service->process_handle);
\r
49 if (service->wait_handle) UnregisterWait(service->process_handle);
\r
50 if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);
\r
51 if (service->throttle_timer) CloseHandle(service->throttle_timer);
\r
52 HeapFree(GetProcessHeap(), 0, service);
\r
55 /* About to install the service */
\r
56 int pre_install_service(int argc, char **argv) {
\r
57 /* Show the dialogue box if we didn't give the service name and path */
\r
58 if (argc < 2) return nssm_gui(IDD_INSTALL, argv[0]);
\r
60 nssm_service_t *service = alloc_nssm_service();
\r
62 print_message(stderr, NSSM_EVENT_OUT_OF_MEMORY, "service", "pre_install_service()");
\r
66 memmove(service->name, argv[0], strlen(argv[0]));
\r
67 memmove(service->exe, argv[1], strlen(argv[1]));
\r
69 /* Arguments are optional */
\r
70 size_t flagslen = 0;
\r
73 for (i = 2; i < argc; i++) flagslen += strlen(argv[i]) + 1;
\r
74 if (! flagslen) flagslen = 1;
\r
77 This probably isn't UTF8-safe and should use std::string or something
\r
78 but it's been broken for the best part of a decade and due for a rewrite
\r
79 anyway so it'll do as a quick-'n'-dirty fix. Note that we don't free
\r
80 the flags buffer but as the program exits that isn't a big problem.
\r
82 for (i = 2; i < argc; i++) {
\r
83 size_t len = strlen(argv[i]);
\r
84 memmove(service->flags + s, argv[i], len);
\r
86 if (i < argc - 1) service->flags[s++] = ' ';
\r
89 /* Work out directory name */
\r
90 size_t len = strlen(service->exe);
\r
91 for (i = len; i && service->exe[i] != '\\' && service->exe[i] != '/'; i--);
\r
92 memmove(service->dir, service->exe, i);
\r
93 service->dir[i] = '\0';
\r
95 int ret = install_service(service);
\r
96 cleanup_nssm_service(service);
\r
100 /* About to remove the service */
\r
101 int pre_remove_service(int argc, char **argv) {
\r
102 /* Show dialogue box if we didn't pass service name and "confirm" */
\r
103 if (argc < 2) return nssm_gui(IDD_REMOVE, argv[0]);
\r
104 if (str_equiv(argv[1], "confirm")) {
\r
105 nssm_service_t *service = alloc_nssm_service();
\r
106 memmove(service->name, argv[0], strlen(argv[0]));
\r
107 int ret = remove_service(service);
\r
108 cleanup_nssm_service(service);
\r
111 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
115 /* Install the service */
\r
116 int install_service(nssm_service_t *service) {
\r
117 if (! service) return 1;
\r
119 /* Open service manager */
\r
120 SC_HANDLE services = open_service_manager();
\r
122 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
123 cleanup_nssm_service(service);
\r
127 /* Get path of this program */
\r
128 char path[MAX_PATH];
\r
129 GetModuleFileName(0, path, MAX_PATH);
\r
131 /* Construct command */
\r
132 char command[CMD_LENGTH];
\r
133 size_t pathlen = strlen(path);
\r
134 if (pathlen + 1 >= VALUE_LENGTH) {
\r
135 print_message(stderr, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);
\r
138 if (_snprintf_s(command, sizeof(command), _TRUNCATE, "\"%s\"", path) < 0) {
\r
139 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY_FOR_IMAGEPATH);
\r
143 /* Create the service */
\r
144 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
145 if (! service->handle) {
\r
146 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
147 CloseServiceHandle(services);
\r
151 /* Now we need to put the parameters into the registry */
\r
152 if (create_parameters(service)) {
\r
153 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
154 DeleteService(service->handle);
\r
155 CloseServiceHandle(services);
\r
159 set_service_recovery(service);
\r
161 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
\r
164 CloseServiceHandle(services);
\r
169 /* Remove the service */
\r
170 int remove_service(nssm_service_t *service) {
\r
171 if (! service) return 1;
\r
173 /* Open service manager */
\r
174 SC_HANDLE services = open_service_manager();
\r
176 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
180 /* Try to open the service */
\r
181 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
182 if (! service->handle) {
\r
183 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
184 CloseServiceHandle(services);
\r
188 /* Try to delete the service */
\r
189 if (! DeleteService(service->handle)) {
\r
190 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
191 CloseServiceHandle(services);
\r
196 CloseServiceHandle(services);
\r
198 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
\r
202 /* Service initialisation */
\r
203 void WINAPI service_main(unsigned long argc, char **argv) {
\r
204 nssm_service_t *service = alloc_nssm_service();
\r
205 if (! service) return;
\r
207 if (_snprintf_s(service->name, sizeof(service->name), _TRUNCATE, "%s", argv[0]) < 0) {
\r
208 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service->name", "service_main()", 0);
\r
212 /* We can use a condition variable in a critical section on Vista or later. */
\r
213 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
214 else use_critical_section = false;
\r
216 /* Initialise status */
\r
217 ZeroMemory(&service->status, sizeof(service->status));
\r
218 service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
219 service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
220 service->status.dwWin32ExitCode = NO_ERROR;
\r
221 service->status.dwServiceSpecificExitCode = 0;
\r
222 service->status.dwCheckPoint = 0;
\r
223 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
225 /* Signal we AREN'T running the server */
\r
226 service->process_handle = 0;
\r
229 /* Register control handler */
\r
230 service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);
\r
231 if (! service->status_handle) {
\r
232 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
236 log_service_control(service->name, 0, true);
\r
238 service->status.dwCurrentState = SERVICE_START_PENDING;
\r
239 service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;
\r
240 SetServiceStatus(service->status_handle, &service->status);
\r
243 /* Try to create the exit action parameters; we don't care if it fails */
\r
244 create_exit_action(service->name, exit_action_strings[0]);
\r
246 SC_HANDLE services = open_service_manager();
\r
248 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
249 set_service_recovery(service);
\r
250 CloseServiceHandle(services);
\r
254 /* Used for signalling a resume if the service pauses when throttled. */
\r
255 if (use_critical_section) {
\r
256 InitializeCriticalSection(&service->throttle_section);
\r
257 service->throttle_section_initialised = true;
\r
260 service->throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
261 if (! service->throttle_timer) {
\r
262 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);
\r
266 monitor_service(service);
\r
269 /* Make sure service recovery actions are taken where necessary */
\r
270 void set_service_recovery(nssm_service_t *service) {
\r
271 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
272 ZeroMemory(&flag, sizeof(flag));
\r
273 flag.fFailureActionsOnNonCrashFailures = true;
\r
275 /* This functionality was added in Vista so the call may fail */
\r
276 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
277 unsigned long error = GetLastError();
\r
278 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
279 if (error != ERROR_INVALID_LEVEL) {
\r
280 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CHANGESERVICECONFIG2_FAILED, service->name, error_string(error), 0);
\r
285 int monitor_service(nssm_service_t *service) {
\r
286 /* Set service status to started */
\r
287 int ret = start_service(service);
\r
290 _snprintf_s(code, sizeof(code), _TRUNCATE, "%d", ret);
\r
291 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);
\r
294 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);
\r
296 /* Monitor service */
\r
297 if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
298 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);
\r
304 char *service_control_text(unsigned long control) {
\r
306 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
307 case 0: return "START";
\r
308 case SERVICE_CONTROL_STOP: return "STOP";
\r
309 case SERVICE_CONTROL_SHUTDOWN: return "SHUTDOWN";
\r
310 case SERVICE_CONTROL_PAUSE: return "PAUSE";
\r
311 case SERVICE_CONTROL_CONTINUE: return "CONTINUE";
\r
312 case SERVICE_CONTROL_INTERROGATE: return "INTERROGATE";
\r
317 void log_service_control(char *service_name, unsigned long control, bool handled) {
\r
318 char *text = service_control_text(control);
\r
319 unsigned long event;
\r
322 /* "0x" + 8 x hex + NULL */
\r
323 text = (char *) HeapAlloc(GetProcessHeap(), 0, 11);
\r
325 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control()", 0);
\r
328 if (_snprintf_s(text, 11, _TRUNCATE, "0x%08x", control) < 0) {
\r
329 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control()", 0);
\r
330 HeapFree(GetProcessHeap(), 0, text);
\r
334 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
336 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
337 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
339 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
341 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
342 HeapFree(GetProcessHeap(), 0, text);
\r
346 /* Service control handler */
\r
347 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
348 nssm_service_t *service = (nssm_service_t *) context;
\r
351 case SERVICE_CONTROL_INTERROGATE:
\r
352 /* We always keep the service status up-to-date so this is a no-op. */
\r
355 case SERVICE_CONTROL_SHUTDOWN:
\r
356 case SERVICE_CONTROL_STOP:
\r
357 log_service_control(service->name, control, true);
\r
359 We MUST acknowledge the stop request promptly but we're committed to
\r
360 waiting for the application to exit. Spawn a new thread to wait
\r
361 while we acknowledge the request.
\r
363 if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {
\r
364 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
\r
367 We couldn't create a thread to tidy up so we'll have to force the tidyup
\r
368 to complete in time in this thread.
\r
370 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
371 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
372 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
374 stop_service(service, 0, true, true);
\r
378 case SERVICE_CONTROL_CONTINUE:
\r
379 log_service_control(service->name, control, true);
\r
380 service->throttle = 0;
\r
381 if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);
\r
383 if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
384 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
385 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
387 service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
388 service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;
\r
389 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);
\r
390 SetServiceStatus(service->status_handle, &service->status);
\r
393 case SERVICE_CONTROL_PAUSE:
\r
395 We don't accept pause messages but it isn't possible to register
\r
396 only for continue messages so we have to handle this case.
\r
398 log_service_control(service->name, control, false);
\r
399 return ERROR_CALL_NOT_IMPLEMENTED;
\r
402 /* Unknown control */
\r
403 log_service_control(service->name, control, false);
\r
404 return ERROR_CALL_NOT_IMPLEMENTED;
\r
407 /* Start the service */
\r
408 int start_service(nssm_service_t *service) {
\r
409 service->stopping = false;
\r
410 service->allow_restart = true;
\r
412 if (service->process_handle) return 0;
\r
414 /* Allocate a STARTUPINFO structure for a new process */
\r
416 ZeroMemory(&si, sizeof(si));
\r
417 si.cb = sizeof(si);
\r
419 /* Allocate a PROCESSINFO structure for the process */
\r
420 PROCESS_INFORMATION pi;
\r
421 ZeroMemory(&pi, sizeof(pi));
\r
423 /* Get startup parameters */
\r
424 int ret = get_parameters(service, &si);
\r
426 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);
\r
427 return stop_service(service, 2, true, true);
\r
430 /* Launch executable with arguments */
\r
431 char cmd[CMD_LENGTH];
\r
432 if (_snprintf_s(cmd, sizeof(cmd), _TRUNCATE, "\"%s\" %s", service->exe, service->flags) < 0) {
\r
433 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);
\r
434 close_output_handles(&si);
\r
435 return stop_service(service, 2, true, true);
\r
438 throttle_restart(service);
\r
440 bool inherit_handles = false;
\r
441 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
442 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, 0, service->env, service->dir, &si, &pi)) {
\r
443 unsigned long error = GetLastError();
\r
444 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
445 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);
\r
446 close_output_handles(&si);
\r
447 return stop_service(service, 3, true, true);
\r
449 service->process_handle = pi.hProcess;
\r
450 service->pid = pi.dwProcessId;
\r
452 if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
\r
454 close_output_handles(&si);
\r
457 Wait for a clean startup before changing the service status to RUNNING
\r
458 but be mindful of the fact that we are blocking the service control manager
\r
459 so abandon the wait before too much time has elapsed.
\r
461 unsigned long delay = service->throttle_delay;
\r
462 if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
\r
463 char delay_milliseconds[16];
\r
464 _snprintf_s(delay_milliseconds, sizeof(delay_milliseconds), _TRUNCATE, "%lu", delay);
\r
465 char deadline_milliseconds[16];
\r
466 _snprintf_s(deadline_milliseconds, sizeof(deadline_milliseconds), _TRUNCATE, "%lu", NSSM_SERVICE_STATUS_DEADLINE);
\r
467 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
\r
468 delay = NSSM_SERVICE_STATUS_DEADLINE;
\r
470 unsigned long deadline = WaitForSingleObject(service->process_handle, delay);
\r
472 /* Signal successful start */
\r
473 service->status.dwCurrentState = SERVICE_RUNNING;
\r
474 SetServiceStatus(service->status_handle, &service->status);
\r
476 /* Continue waiting for a clean startup. */
\r
477 if (deadline == WAIT_TIMEOUT) {
\r
478 if (service->throttle_delay > delay) {
\r
479 if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;
\r
481 else service->throttle = 0;
\r
487 /* Stop the service */
\r
488 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {
\r
489 service->allow_restart = false;
\r
490 if (service->wait_handle) {
\r
491 UnregisterWait(service->wait_handle);
\r
492 service->wait_handle = 0;
\r
495 if (default_action && ! exitcode && ! graceful) {
\r
496 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
500 /* Signal we are stopping */
\r
502 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
503 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
504 SetServiceStatus(service->status_handle, &service->status);
\r
507 /* Nothing to do if service isn't running */
\r
508 if (service->pid) {
\r
509 /* Shut down service */
\r
510 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);
\r
511 kill_process(service, service->process_handle, service->pid, 0);
\r
513 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
\r
515 end_service((void *) service, true);
\r
517 /* Signal we stopped */
\r
519 service->status.dwCurrentState = SERVICE_STOPPED;
\r
521 service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
522 service->status.dwServiceSpecificExitCode = exitcode;
\r
525 service->status.dwWin32ExitCode = NO_ERROR;
\r
526 service->status.dwServiceSpecificExitCode = 0;
\r
528 SetServiceStatus(service->status_handle, &service->status);
\r
534 /* Callback function triggered when the server exits */
\r
535 void CALLBACK end_service(void *arg, unsigned char why) {
\r
536 nssm_service_t *service = (nssm_service_t *) arg;
\r
538 if (service->stopping) return;
\r
540 service->stopping = true;
\r
542 /* Check exit code */
\r
543 unsigned long exitcode = 0;
\r
545 GetExitCodeProcess(service->process_handle, &exitcode);
\r
546 if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);
\r
547 CloseHandle(service->process_handle);
\r
549 service->process_handle = 0;
\r
553 Log that the service ended BEFORE logging about killing the process
\r
554 tree. See below for the possible values of the why argument.
\r
557 _snprintf_s(code, sizeof(code), _TRUNCATE, "%lu", exitcode);
\r
558 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);
\r
562 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
563 kill_process_tree(service, service->pid, exitcode, service->pid);
\r
566 The why argument is true if our wait timed out or false otherwise.
\r
567 Our wait is infinite so why will never be true when called by the system.
\r
568 If it is indeed true, assume we were called from stop_service() because
\r
569 this is a controlled shutdown, and don't take any restart action.
\r
572 if (! service->allow_restart) return;
\r
574 /* What action should we take? */
\r
575 int action = NSSM_EXIT_RESTART;
\r
576 unsigned char action_string[ACTION_LEN];
\r
577 bool default_action;
\r
578 if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {
\r
579 for (int i = 0; exit_action_strings[i]; i++) {
\r
580 if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
588 /* Try to restart the service or return failure code to service manager */
\r
589 case NSSM_EXIT_RESTART:
\r
590 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);
\r
591 while (monitor_service(service)) {
\r
592 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);
\r
597 /* Do nothing, just like srvany would */
\r
598 case NSSM_EXIT_IGNORE:
\r
599 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);
\r
603 /* Tell the service manager we are finished */
\r
604 case NSSM_EXIT_REALLY:
\r
605 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);
\r
606 stop_service(service, exitcode, true, default_action);
\r
609 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
610 case NSSM_EXIT_UNCLEAN:
\r
611 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);
\r
612 stop_service(service, exitcode, false, default_action);
\r
619 void throttle_restart(nssm_service_t *service) {
\r
620 /* This can't be a restart if the service is already running. */
\r
621 if (! service->throttle++) return;
\r
623 int ms = throttle_milliseconds(service->throttle);
\r
625 if (service->throttle > 7) service->throttle = 8;
\r
627 char threshold[8], milliseconds[8];
\r
628 _snprintf_s(threshold, sizeof(threshold), _TRUNCATE, "%lu", service->throttle_delay);
\r
629 _snprintf_s(milliseconds, sizeof(milliseconds), _TRUNCATE, "%lu", ms);
\r
630 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
\r
632 if (use_critical_section) EnterCriticalSection(&service->throttle_section);
\r
633 else if (service->throttle_timer) {
\r
634 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
635 service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
636 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
639 service->status.dwCurrentState = SERVICE_PAUSED;
\r
640 SetServiceStatus(service->status_handle, &service->status);
\r
642 if (use_critical_section) {
\r
643 imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);
\r
644 LeaveCriticalSection(&service->throttle_section);
\r
647 if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);
\r
653 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
654 the number of milliseconds we expect the operation to take, and optionally
\r
655 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
656 operation completing or dwCheckPoint increasing, the system will consider the
\r
657 service to be hung.
\r
659 However the system will consider the service to be hung after 30000
\r
660 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
661 changed. Therefore if we want to wait longer than that we must periodically
\r
662 increase dwCheckPoint.
\r
664 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
665 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
666 time dwCheckPoint is also increased.
\r
668 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
669 NSSM_SERVICE_STATUS_DEADLINE milliseconds. If the process is still running
\r
670 and we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
671 smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to
\r
674 Only doing both these things will prevent the system from killing the service.
\r
676 Returns: 1 if the wait timed out.
\r
677 0 if the wait completed.
\r
680 int await_shutdown(nssm_service_t *service, char *function_name, unsigned long timeout) {
\r
681 unsigned long interval;
\r
682 unsigned long waithint;
\r
684 unsigned long waited;
\r
685 char interval_milliseconds[16];
\r
686 char timeout_milliseconds[16];
\r
687 char waited_milliseconds[16];
\r
688 char *function = function_name;
\r
690 /* Add brackets to function name. */
\r
691 size_t funclen = strlen(function_name) + 3;
\r
692 char *func = (char *) HeapAlloc(GetProcessHeap(), 0, funclen);
\r
694 if (_snprintf_s(func, funclen, _TRUNCATE, "%s()", function_name) > -1) function = func;
\r
697 _snprintf_s(timeout_milliseconds, sizeof(timeout_milliseconds), _TRUNCATE, "%lu", timeout);
\r
699 waithint = service->status.dwWaitHint;
\r
701 while (waited < timeout) {
\r
702 interval = timeout - waited;
\r
703 if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
\r
705 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
706 service->status.dwWaitHint += interval;
\r
707 service->status.dwCheckPoint++;
\r
708 SetServiceStatus(service->status_handle, &service->status);
\r
711 _snprintf_s(waited_milliseconds, sizeof(waited_milliseconds), _TRUNCATE, "%lu", waited);
\r
712 _snprintf_s(interval_milliseconds, sizeof(interval_milliseconds), _TRUNCATE, "%lu", interval);
\r
713 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
716 switch (WaitForSingleObject(service->process_handle, interval)) {
\r
717 case WAIT_OBJECT_0:
\r
730 waited += interval;
\r
734 if (func) HeapFree(GetProcessHeap(), 0, func);
\r