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 /* Set default values which aren't zero. */
\r
36 void set_nssm_service_defaults(nssm_service_t *service) {
\r
37 if (! service) return;
\r
39 service->stdin_sharing = NSSM_STDIN_SHARING;
\r
40 service->stdin_disposition = NSSM_STDIN_DISPOSITION;
\r
41 service->stdin_flags = NSSM_STDIN_FLAGS;
\r
42 service->stdout_sharing = NSSM_STDOUT_SHARING;
\r
43 service->stdout_disposition = NSSM_STDOUT_DISPOSITION;
\r
44 service->stdout_flags = NSSM_STDOUT_FLAGS;
\r
45 service->stderr_sharing = NSSM_STDERR_SHARING;
\r
46 service->stderr_disposition = NSSM_STDERR_DISPOSITION;
\r
47 service->stderr_flags = NSSM_STDERR_FLAGS;
\r
48 service->throttle_delay = NSSM_RESET_THROTTLE_RESTART;
\r
49 service->stop_method = ~0;
\r
50 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
51 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
52 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
55 /* Allocate and zero memory for a service. */
\r
56 nssm_service_t *alloc_nssm_service() {
\r
57 nssm_service_t *service = (nssm_service_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(nssm_service_t));
\r
58 if (! service) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service", "alloc_nssm_service()", 0);
\r
62 /* Free memory for a service. */
\r
63 void cleanup_nssm_service(nssm_service_t *service) {
\r
64 if (! service) return;
\r
65 if (service->env) HeapFree(GetProcessHeap(), 0, service->env);
\r
66 if (service->env_extra) HeapFree(GetProcessHeap(), 0, service->env_extra);
\r
67 if (service->handle) CloseServiceHandle(service->handle);
\r
68 if (service->process_handle) CloseHandle(service->process_handle);
\r
69 if (service->wait_handle) UnregisterWait(service->process_handle);
\r
70 if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);
\r
71 if (service->throttle_timer) CloseHandle(service->throttle_timer);
\r
72 HeapFree(GetProcessHeap(), 0, service);
\r
75 /* About to install the service */
\r
76 int pre_install_service(int argc, char **argv) {
\r
77 /* Show the dialogue box if we didn't give the service name and path */
\r
78 if (argc < 2) return nssm_gui(IDD_INSTALL, argv[0]);
\r
80 nssm_service_t *service = alloc_nssm_service();
\r
82 print_message(stderr, NSSM_EVENT_OUT_OF_MEMORY, "service", "pre_install_service()");
\r
86 set_nssm_service_defaults(service);
\r
87 memmove(service->name, argv[0], strlen(argv[0]));
\r
88 memmove(service->exe, argv[1], strlen(argv[1]));
\r
90 /* Arguments are optional */
\r
91 size_t flagslen = 0;
\r
94 for (i = 2; i < argc; i++) flagslen += strlen(argv[i]) + 1;
\r
95 if (! flagslen) flagslen = 1;
\r
98 This probably isn't UTF8-safe and should use std::string or something
\r
99 but it's been broken for the best part of a decade and due for a rewrite
\r
100 anyway so it'll do as a quick-'n'-dirty fix. Note that we don't free
\r
101 the flags buffer but as the program exits that isn't a big problem.
\r
103 for (i = 2; i < argc; i++) {
\r
104 size_t len = strlen(argv[i]);
\r
105 memmove(service->flags + s, argv[i], len);
\r
107 if (i < argc - 1) service->flags[s++] = ' ';
\r
110 /* Work out directory name */
\r
111 memmove(service->dir, service->exe, sizeof(service->dir));
\r
112 strip_basename(service->dir);
\r
114 int ret = install_service(service);
\r
115 cleanup_nssm_service(service);
\r
119 /* About to remove the service */
\r
120 int pre_remove_service(int argc, char **argv) {
\r
121 /* Show dialogue box if we didn't pass service name and "confirm" */
\r
122 if (argc < 2) return nssm_gui(IDD_REMOVE, argv[0]);
\r
123 if (str_equiv(argv[1], "confirm")) {
\r
124 nssm_service_t *service = alloc_nssm_service();
\r
125 memmove(service->name, argv[0], strlen(argv[0]));
\r
126 int ret = remove_service(service);
\r
127 cleanup_nssm_service(service);
\r
130 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
134 /* Install the service */
\r
135 int install_service(nssm_service_t *service) {
\r
136 if (! service) return 1;
\r
138 /* Open service manager */
\r
139 SC_HANDLE services = open_service_manager();
\r
141 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
142 cleanup_nssm_service(service);
\r
146 /* Get path of this program */
\r
147 char path[MAX_PATH];
\r
148 GetModuleFileName(0, path, MAX_PATH);
\r
150 /* Construct command */
\r
151 char command[CMD_LENGTH];
\r
152 size_t pathlen = strlen(path);
\r
153 if (pathlen + 1 >= VALUE_LENGTH) {
\r
154 print_message(stderr, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);
\r
157 if (_snprintf_s(command, sizeof(command), _TRUNCATE, "\"%s\"", path) < 0) {
\r
158 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY_FOR_IMAGEPATH);
\r
162 /* Create the service */
\r
163 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
164 if (! service->handle) {
\r
165 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
166 CloseServiceHandle(services);
\r
170 /* Now we need to put the parameters into the registry */
\r
171 if (create_parameters(service)) {
\r
172 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
173 DeleteService(service->handle);
\r
174 CloseServiceHandle(services);
\r
178 set_service_recovery(service);
\r
180 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
\r
183 CloseServiceHandle(services);
\r
188 /* Remove the service */
\r
189 int remove_service(nssm_service_t *service) {
\r
190 if (! service) return 1;
\r
192 /* Open service manager */
\r
193 SC_HANDLE services = open_service_manager();
\r
195 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
199 /* Try to open the service */
\r
200 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
201 if (! service->handle) {
\r
202 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
203 CloseServiceHandle(services);
\r
207 /* Try to delete the service */
\r
208 if (! DeleteService(service->handle)) {
\r
209 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
210 CloseServiceHandle(services);
\r
215 CloseServiceHandle(services);
\r
217 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
\r
221 /* Service initialisation */
\r
222 void WINAPI service_main(unsigned long argc, char **argv) {
\r
223 nssm_service_t *service = alloc_nssm_service();
\r
224 if (! service) return;
\r
226 if (_snprintf_s(service->name, sizeof(service->name), _TRUNCATE, "%s", argv[0]) < 0) {
\r
227 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service->name", "service_main()", 0);
\r
231 /* We can use a condition variable in a critical section on Vista or later. */
\r
232 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
233 else use_critical_section = false;
\r
235 /* Initialise status */
\r
236 ZeroMemory(&service->status, sizeof(service->status));
\r
237 service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
238 service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
239 service->status.dwWin32ExitCode = NO_ERROR;
\r
240 service->status.dwServiceSpecificExitCode = 0;
\r
241 service->status.dwCheckPoint = 0;
\r
242 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
244 /* Signal we AREN'T running the server */
\r
245 service->process_handle = 0;
\r
248 /* Register control handler */
\r
249 service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);
\r
250 if (! service->status_handle) {
\r
251 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
255 log_service_control(service->name, 0, true);
\r
257 service->status.dwCurrentState = SERVICE_START_PENDING;
\r
258 service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;
\r
259 SetServiceStatus(service->status_handle, &service->status);
\r
262 /* Try to create the exit action parameters; we don't care if it fails */
\r
263 create_exit_action(service->name, exit_action_strings[0]);
\r
265 SC_HANDLE services = open_service_manager();
\r
267 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
268 set_service_recovery(service);
\r
269 CloseServiceHandle(services);
\r
273 /* Used for signalling a resume if the service pauses when throttled. */
\r
274 if (use_critical_section) {
\r
275 InitializeCriticalSection(&service->throttle_section);
\r
276 service->throttle_section_initialised = true;
\r
279 service->throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
280 if (! service->throttle_timer) {
\r
281 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);
\r
285 monitor_service(service);
\r
288 /* Make sure service recovery actions are taken where necessary */
\r
289 void set_service_recovery(nssm_service_t *service) {
\r
290 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
291 ZeroMemory(&flag, sizeof(flag));
\r
292 flag.fFailureActionsOnNonCrashFailures = true;
\r
294 /* This functionality was added in Vista so the call may fail */
\r
295 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
296 unsigned long error = GetLastError();
\r
297 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
298 if (error != ERROR_INVALID_LEVEL) {
\r
299 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CHANGESERVICECONFIG2_FAILED, service->name, error_string(error), 0);
\r
304 int monitor_service(nssm_service_t *service) {
\r
305 /* Set service status to started */
\r
306 int ret = start_service(service);
\r
309 _snprintf_s(code, sizeof(code), _TRUNCATE, "%d", ret);
\r
310 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);
\r
313 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);
\r
315 /* Monitor service */
\r
316 if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
317 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);
\r
323 char *service_control_text(unsigned long control) {
\r
325 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
326 case 0: return "START";
\r
327 case SERVICE_CONTROL_STOP: return "STOP";
\r
328 case SERVICE_CONTROL_SHUTDOWN: return "SHUTDOWN";
\r
329 case SERVICE_CONTROL_PAUSE: return "PAUSE";
\r
330 case SERVICE_CONTROL_CONTINUE: return "CONTINUE";
\r
331 case SERVICE_CONTROL_INTERROGATE: return "INTERROGATE";
\r
336 void log_service_control(char *service_name, unsigned long control, bool handled) {
\r
337 char *text = service_control_text(control);
\r
338 unsigned long event;
\r
341 /* "0x" + 8 x hex + NULL */
\r
342 text = (char *) HeapAlloc(GetProcessHeap(), 0, 11);
\r
344 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control()", 0);
\r
347 if (_snprintf_s(text, 11, _TRUNCATE, "0x%08x", control) < 0) {
\r
348 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control()", 0);
\r
349 HeapFree(GetProcessHeap(), 0, text);
\r
353 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
355 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
356 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
358 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
360 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
361 HeapFree(GetProcessHeap(), 0, text);
\r
365 /* Service control handler */
\r
366 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
367 nssm_service_t *service = (nssm_service_t *) context;
\r
370 case SERVICE_CONTROL_INTERROGATE:
\r
371 /* We always keep the service status up-to-date so this is a no-op. */
\r
374 case SERVICE_CONTROL_SHUTDOWN:
\r
375 case SERVICE_CONTROL_STOP:
\r
376 log_service_control(service->name, control, true);
\r
378 We MUST acknowledge the stop request promptly but we're committed to
\r
379 waiting for the application to exit. Spawn a new thread to wait
\r
380 while we acknowledge the request.
\r
382 if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {
\r
383 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
\r
386 We couldn't create a thread to tidy up so we'll have to force the tidyup
\r
387 to complete in time in this thread.
\r
389 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
390 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
391 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
393 stop_service(service, 0, true, true);
\r
397 case SERVICE_CONTROL_CONTINUE:
\r
398 log_service_control(service->name, control, true);
\r
399 service->throttle = 0;
\r
400 if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);
\r
402 if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
403 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
404 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
406 service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
407 service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;
\r
408 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);
\r
409 SetServiceStatus(service->status_handle, &service->status);
\r
412 case SERVICE_CONTROL_PAUSE:
\r
414 We don't accept pause messages but it isn't possible to register
\r
415 only for continue messages so we have to handle this case.
\r
417 log_service_control(service->name, control, false);
\r
418 return ERROR_CALL_NOT_IMPLEMENTED;
\r
421 /* Unknown control */
\r
422 log_service_control(service->name, control, false);
\r
423 return ERROR_CALL_NOT_IMPLEMENTED;
\r
426 /* Start the service */
\r
427 int start_service(nssm_service_t *service) {
\r
428 service->stopping = false;
\r
429 service->allow_restart = true;
\r
431 if (service->process_handle) return 0;
\r
433 /* Allocate a STARTUPINFO structure for a new process */
\r
435 ZeroMemory(&si, sizeof(si));
\r
436 si.cb = sizeof(si);
\r
438 /* Allocate a PROCESSINFO structure for the process */
\r
439 PROCESS_INFORMATION pi;
\r
440 ZeroMemory(&pi, sizeof(pi));
\r
442 /* Get startup parameters */
\r
443 int ret = get_parameters(service, &si);
\r
445 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);
\r
446 return stop_service(service, 2, true, true);
\r
449 /* Launch executable with arguments */
\r
450 char cmd[CMD_LENGTH];
\r
451 if (_snprintf_s(cmd, sizeof(cmd), _TRUNCATE, "\"%s\" %s", service->exe, service->flags) < 0) {
\r
452 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);
\r
453 close_output_handles(&si);
\r
454 return stop_service(service, 2, true, true);
\r
457 throttle_restart(service);
\r
459 bool inherit_handles = false;
\r
460 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
461 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, 0, service->env, service->dir, &si, &pi)) {
\r
462 unsigned long error = GetLastError();
\r
463 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
464 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);
\r
465 close_output_handles(&si);
\r
466 return stop_service(service, 3, true, true);
\r
468 service->process_handle = pi.hProcess;
\r
469 service->pid = pi.dwProcessId;
\r
471 if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
\r
473 close_output_handles(&si);
\r
476 Wait for a clean startup before changing the service status to RUNNING
\r
477 but be mindful of the fact that we are blocking the service control manager
\r
478 so abandon the wait before too much time has elapsed.
\r
480 unsigned long delay = service->throttle_delay;
\r
481 if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
\r
482 char delay_milliseconds[16];
\r
483 _snprintf_s(delay_milliseconds, sizeof(delay_milliseconds), _TRUNCATE, "%lu", delay);
\r
484 char deadline_milliseconds[16];
\r
485 _snprintf_s(deadline_milliseconds, sizeof(deadline_milliseconds), _TRUNCATE, "%lu", NSSM_SERVICE_STATUS_DEADLINE);
\r
486 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
\r
487 delay = NSSM_SERVICE_STATUS_DEADLINE;
\r
489 unsigned long deadline = WaitForSingleObject(service->process_handle, delay);
\r
491 /* Signal successful start */
\r
492 service->status.dwCurrentState = SERVICE_RUNNING;
\r
493 SetServiceStatus(service->status_handle, &service->status);
\r
495 /* Continue waiting for a clean startup. */
\r
496 if (deadline == WAIT_TIMEOUT) {
\r
497 if (service->throttle_delay > delay) {
\r
498 if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;
\r
500 else service->throttle = 0;
\r
506 /* Stop the service */
\r
507 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {
\r
508 service->allow_restart = false;
\r
509 if (service->wait_handle) {
\r
510 UnregisterWait(service->wait_handle);
\r
511 service->wait_handle = 0;
\r
514 if (default_action && ! exitcode && ! graceful) {
\r
515 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
519 /* Signal we are stopping */
\r
521 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
522 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
523 SetServiceStatus(service->status_handle, &service->status);
\r
526 /* Nothing to do if service isn't running */
\r
527 if (service->pid) {
\r
528 /* Shut down service */
\r
529 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);
\r
530 kill_process(service, service->process_handle, service->pid, 0);
\r
532 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
\r
534 end_service((void *) service, true);
\r
536 /* Signal we stopped */
\r
538 service->status.dwCurrentState = SERVICE_STOPPED;
\r
540 service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
541 service->status.dwServiceSpecificExitCode = exitcode;
\r
544 service->status.dwWin32ExitCode = NO_ERROR;
\r
545 service->status.dwServiceSpecificExitCode = 0;
\r
547 SetServiceStatus(service->status_handle, &service->status);
\r
553 /* Callback function triggered when the server exits */
\r
554 void CALLBACK end_service(void *arg, unsigned char why) {
\r
555 nssm_service_t *service = (nssm_service_t *) arg;
\r
557 if (service->stopping) return;
\r
559 service->stopping = true;
\r
561 /* Check exit code */
\r
562 unsigned long exitcode = 0;
\r
564 GetExitCodeProcess(service->process_handle, &exitcode);
\r
565 if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);
\r
566 CloseHandle(service->process_handle);
\r
568 service->process_handle = 0;
\r
572 Log that the service ended BEFORE logging about killing the process
\r
573 tree. See below for the possible values of the why argument.
\r
576 _snprintf_s(code, sizeof(code), _TRUNCATE, "%lu", exitcode);
\r
577 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);
\r
581 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
582 kill_process_tree(service, service->pid, exitcode, service->pid);
\r
585 The why argument is true if our wait timed out or false otherwise.
\r
586 Our wait is infinite so why will never be true when called by the system.
\r
587 If it is indeed true, assume we were called from stop_service() because
\r
588 this is a controlled shutdown, and don't take any restart action.
\r
591 if (! service->allow_restart) return;
\r
593 /* What action should we take? */
\r
594 int action = NSSM_EXIT_RESTART;
\r
595 unsigned char action_string[ACTION_LEN];
\r
596 bool default_action;
\r
597 if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {
\r
598 for (int i = 0; exit_action_strings[i]; i++) {
\r
599 if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
607 /* Try to restart the service or return failure code to service manager */
\r
608 case NSSM_EXIT_RESTART:
\r
609 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);
\r
610 while (monitor_service(service)) {
\r
611 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);
\r
616 /* Do nothing, just like srvany would */
\r
617 case NSSM_EXIT_IGNORE:
\r
618 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);
\r
622 /* Tell the service manager we are finished */
\r
623 case NSSM_EXIT_REALLY:
\r
624 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);
\r
625 stop_service(service, exitcode, true, default_action);
\r
628 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
629 case NSSM_EXIT_UNCLEAN:
\r
630 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);
\r
631 stop_service(service, exitcode, false, default_action);
\r
638 void throttle_restart(nssm_service_t *service) {
\r
639 /* This can't be a restart if the service is already running. */
\r
640 if (! service->throttle++) return;
\r
642 int ms = throttle_milliseconds(service->throttle);
\r
644 if (service->throttle > 7) service->throttle = 8;
\r
646 char threshold[8], milliseconds[8];
\r
647 _snprintf_s(threshold, sizeof(threshold), _TRUNCATE, "%lu", service->throttle_delay);
\r
648 _snprintf_s(milliseconds, sizeof(milliseconds), _TRUNCATE, "%lu", ms);
\r
649 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
\r
651 if (use_critical_section) EnterCriticalSection(&service->throttle_section);
\r
652 else if (service->throttle_timer) {
\r
653 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
654 service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
655 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
658 service->status.dwCurrentState = SERVICE_PAUSED;
\r
659 SetServiceStatus(service->status_handle, &service->status);
\r
661 if (use_critical_section) {
\r
662 imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);
\r
663 LeaveCriticalSection(&service->throttle_section);
\r
666 if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);
\r
672 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
673 the number of milliseconds we expect the operation to take, and optionally
\r
674 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
675 operation completing or dwCheckPoint increasing, the system will consider the
\r
676 service to be hung.
\r
678 However the system will consider the service to be hung after 30000
\r
679 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
680 changed. Therefore if we want to wait longer than that we must periodically
\r
681 increase dwCheckPoint.
\r
683 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
684 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
685 time dwCheckPoint is also increased.
\r
687 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
688 NSSM_SERVICE_STATUS_DEADLINE milliseconds. If the process is still running
\r
689 and we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
690 smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to
\r
693 Only doing both these things will prevent the system from killing the service.
\r
695 Returns: 1 if the wait timed out.
\r
696 0 if the wait completed.
\r
699 int await_shutdown(nssm_service_t *service, char *function_name, unsigned long timeout) {
\r
700 unsigned long interval;
\r
701 unsigned long waithint;
\r
703 unsigned long waited;
\r
704 char interval_milliseconds[16];
\r
705 char timeout_milliseconds[16];
\r
706 char waited_milliseconds[16];
\r
707 char *function = function_name;
\r
709 /* Add brackets to function name. */
\r
710 size_t funclen = strlen(function_name) + 3;
\r
711 char *func = (char *) HeapAlloc(GetProcessHeap(), 0, funclen);
\r
713 if (_snprintf_s(func, funclen, _TRUNCATE, "%s()", function_name) > -1) function = func;
\r
716 _snprintf_s(timeout_milliseconds, sizeof(timeout_milliseconds), _TRUNCATE, "%lu", timeout);
\r
718 waithint = service->status.dwWaitHint;
\r
720 while (waited < timeout) {
\r
721 interval = timeout - waited;
\r
722 if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
\r
724 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
725 service->status.dwWaitHint += interval;
\r
726 service->status.dwCheckPoint++;
\r
727 SetServiceStatus(service->status_handle, &service->status);
\r
730 _snprintf_s(waited_milliseconds, sizeof(waited_milliseconds), _TRUNCATE, "%lu", waited);
\r
731 _snprintf_s(interval_milliseconds, sizeof(interval_milliseconds), _TRUNCATE, "%lu", interval);
\r
732 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
735 switch (WaitForSingleObject(service->process_handle, interval)) {
\r
736 case WAIT_OBJECT_0:
\r
749 waited += interval;
\r
753 if (func) HeapFree(GetProcessHeap(), 0, func);
\r