/* Sets the string for the exit action corresponding to the exit code. ret is a pointer to an unsigned long containing the exit code. If ret is NULL, we retrieve the default exit action unconditionally. action is a buffer which receives the string. default_action is a pointer to a bool which is set to false if there was an explicit string for the given exit code, or true if we are returning the default action. Returns: 0 on success. 1 on error. */ int get_exit_action(const TCHAR *service_name, unsigned long *ret, TCHAR *action, bool *default_action) { /* Are we returning the default action or a status-specific one? */ *default_action = ! ret; /* Try to open the registry */ HKEY key = open_registry(service_name, NSSM_REG_EXIT, KEY_READ); if (! key) return 1; unsigned long type = REG_SZ; unsigned long action_len = ACTION_LEN; TCHAR code[16]; if (! ret) code[0] = _T('\0'); else if (_sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), *ret) < 0) { RegCloseKey(key); return get_exit_action(service_name, 0, action, default_action); } if (RegQueryValueEx(key, code, 0, &type, (unsigned char *) action, &action_len) != ERROR_SUCCESS) { RegCloseKey(key); /* Try again with * as the key if an exit code was defined */ if (ret) return get_exit_action(service_name, 0, action, default_action); return 0; } /* Close registry */ RegCloseKey(key); return 0; }
static int setting_get_exit_action(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) { unsigned long exitcode = 0; unsigned long *code = 0; if (additional) { if (! is_default(additional)) { if (str_number(additional, &exitcode)) return -1; code = &exitcode; } } TCHAR action_string[ACTION_LEN]; bool default_action; if (get_exit_action(service_name, code, action_string, &default_action)) return -1; value_from_string(name, value, action_string); if (default_action && ! _tcsnicmp((const TCHAR *) action_string, (TCHAR *) default_value, ACTION_LEN)) return 0; return 1; }
int get_parameters(nssm_service_t *service, STARTUPINFO *si) { unsigned long ret; /* Try to open the registry */ HKEY key = open_registry(service->name, KEY_READ); if (! key) return 1; /* Don't expand parameters when retrieving for the GUI. */ bool expand = si ? true : false; /* Try to get environment variables - may fail */ get_environment(service->name, key, NSSM_REG_ENV, &service->env, &service->envlen); /* Environment variables to add to existing rather than replace - may fail. */ get_environment(service->name, key, NSSM_REG_ENV_EXTRA, &service->env_extra, &service->env_extralen); /* Set environment if we are starting the service. */ if (si) set_service_environment(service); /* Try to get executable file - MUST succeed */ if (get_string(key, NSSM_REG_EXE, service->exe, sizeof(service->exe), expand, false, true)) { RegCloseKey(key); return 3; } /* Try to get flags - may fail and we don't care */ if (get_string(key, NSSM_REG_FLAGS, service->flags, sizeof(service->flags), expand, false, true)) { log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_NO_FLAGS, NSSM_REG_FLAGS, service->name, service->exe, 0); ZeroMemory(service->flags, sizeof(service->flags)); } /* Try to get startup directory - may fail and we fall back to a default */ if (get_string(key, NSSM_REG_DIR, service->dir, sizeof(service->dir), expand, true, true) || ! service->dir[0]) { _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe); strip_basename(service->dir); if (service->dir[0] == _T('\0')) { /* Help! */ ret = GetWindowsDirectory(service->dir, sizeof(service->dir)); if (! ret || ret > sizeof(service->dir)) { log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_NO_DIR_AND_NO_FALLBACK, NSSM_REG_DIR, service->name, 0); RegCloseKey(key); return 4; } } log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_NO_DIR, NSSM_REG_DIR, service->name, service->dir, 0); } /* Try to get processor affinity - may fail. */ TCHAR buffer[512]; if (get_string(key, NSSM_REG_AFFINITY, buffer, sizeof(buffer), false, false, false) || ! buffer[0]) service->affinity = 0LL; else if (affinity_string_to_mask(buffer, &service->affinity)) { log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_AFFINITY_MASK, service->name, buffer); service->affinity = 0LL; } else { DWORD_PTR affinity, system_affinity; if (GetProcessAffinityMask(GetCurrentProcess(), &affinity, &system_affinity)) { _int64 effective_affinity = service->affinity & system_affinity; if (effective_affinity != service->affinity) { TCHAR *system = 0; if (! affinity_mask_to_string(system_affinity, &system)) { TCHAR *effective = 0; if (! affinity_mask_to_string(effective_affinity, &effective)) { log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_EFFECTIVE_AFFINITY_MASK, service->name, buffer, system, effective, 0); } HeapFree(GetProcessHeap(), 0, effective); } HeapFree(GetProcessHeap(), 0, system); } } } /* Try to get priority - may fail. */ unsigned long priority; if (get_number(key, NSSM_REG_PRIORITY, &priority, false) == 1) { if (priority == (priority & priority_mask())) service->priority = priority; else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_PRIORITY, service->name, NSSM_REG_PRIORITY, 0); } /* Try to get hook I/O sharing - may fail. */ unsigned long hook_share_output_handles; if (get_number(key, NSSM_REG_HOOK_SHARE_OUTPUT_HANDLES, &hook_share_output_handles, false) == 1) { if (hook_share_output_handles) service->hook_share_output_handles = true; else service->hook_share_output_handles = false; } else hook_share_output_handles = false; /* Try to get file rotation settings - may fail. */ unsigned long rotate_files; if (get_number(key, NSSM_REG_ROTATE, &rotate_files, false) == 1) { if (rotate_files) service->rotate_files = true; else service->rotate_files = false; } else service->rotate_files = false; if (get_number(key, NSSM_REG_ROTATE_ONLINE, &rotate_files, false) == 1) { if (rotate_files) service->rotate_stdout_online = service->rotate_stderr_online = true; else service->rotate_stdout_online = service->rotate_stderr_online = false; } else service->rotate_stdout_online = service->rotate_stderr_online = false; /* Log timestamping requires a logging thread.*/ unsigned long timestamp_log; if (get_number(key, NSSM_REG_TIMESTAMP_LOG, ×tamp_log, false) == 1) { if (timestamp_log) service->timestamp_log = true; else service->timestamp_log = false; } else service->timestamp_log = false; /* Hook I/O sharing and online rotation need a pipe. */ service->use_stdout_pipe = service->rotate_stdout_online || service->timestamp_log || hook_share_output_handles; service->use_stderr_pipe = service->rotate_stderr_online || service->timestamp_log || hook_share_output_handles; if (get_number(key, NSSM_REG_ROTATE_SECONDS, &service->rotate_seconds, false) != 1) service->rotate_seconds = 0; if (get_number(key, NSSM_REG_ROTATE_BYTES_LOW, &service->rotate_bytes_low, false) != 1) service->rotate_bytes_low = 0; if (get_number(key, NSSM_REG_ROTATE_BYTES_HIGH, &service->rotate_bytes_high, false) != 1) service->rotate_bytes_high = 0; override_milliseconds(service->name, key, NSSM_REG_ROTATE_DELAY, &service->rotate_delay, NSSM_ROTATE_DELAY, NSSM_EVENT_BOGUS_THROTTLE); /* Try to get force new console setting - may fail. */ if (get_number(key, NSSM_REG_NO_CONSOLE, &service->no_console, false) != 1) service->no_console = 0; /* Change to startup directory in case stdout/stderr are relative paths. */ TCHAR cwd[PATH_LENGTH]; GetCurrentDirectory(_countof(cwd), cwd); SetCurrentDirectory(service->dir); /* Try to get stdout and stderr */ if (get_io_parameters(service, key)) { log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0); RegCloseKey(key); SetCurrentDirectory(cwd); return 5; } /* Change back in case the startup directory needs to be deleted. */ SetCurrentDirectory(cwd); /* Try to get mandatory restart delay */ override_milliseconds(service->name, key, NSSM_REG_RESTART_DELAY, &service->restart_delay, 0, NSSM_EVENT_BOGUS_RESTART_DELAY); /* Try to get throttle restart delay */ override_milliseconds(service->name, key, NSSM_REG_THROTTLE, &service->throttle_delay, NSSM_RESET_THROTTLE_RESTART, NSSM_EVENT_BOGUS_THROTTLE); /* Try to get service stop flags. */ unsigned long type = REG_DWORD; unsigned long stop_method_skip; unsigned long buflen = sizeof(stop_method_skip); bool stop_ok = false; ret = RegQueryValueEx(key, NSSM_REG_STOP_METHOD_SKIP, 0, &type, (unsigned char *) &stop_method_skip, &buflen); if (ret != ERROR_SUCCESS) { if (ret != ERROR_FILE_NOT_FOUND) { if (type != REG_DWORD) { log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_STOP_METHOD_SKIP, service->name, NSSM_REG_STOP_METHOD_SKIP, NSSM, 0); } else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, NSSM_REG_STOP_METHOD_SKIP, error_string(ret), 0); } } else stop_ok = true; /* Try all methods except those requested to be skipped. */ service->stop_method = ~0; if (stop_ok) service->stop_method &= ~stop_method_skip; /* Try to get kill delays - may fail. */ override_milliseconds(service->name, key, NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, &service->kill_console_delay, NSSM_KILL_CONSOLE_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_CONSOLE_GRACE_PERIOD); override_milliseconds(service->name, key, NSSM_REG_KILL_WINDOW_GRACE_PERIOD, &service->kill_window_delay, NSSM_KILL_WINDOW_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_WINDOW_GRACE_PERIOD); override_milliseconds(service->name, key, NSSM_REG_KILL_THREADS_GRACE_PERIOD, &service->kill_threads_delay, NSSM_KILL_THREADS_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_THREADS_GRACE_PERIOD); /* Try to get process tree settings - may fail. */ unsigned long kill_process_tree; if (get_number(key, NSSM_REG_KILL_PROCESS_TREE, &kill_process_tree, false) == 1) { if (kill_process_tree) service->kill_process_tree = true; else service->kill_process_tree = false; } else service->kill_process_tree = true; /* Try to get default exit action. */ bool default_action; service->default_exit_action = NSSM_EXIT_RESTART; TCHAR action_string[ACTION_LEN]; if (! get_exit_action(service->name, 0, action_string, &default_action)) { for (int i = 0; exit_action_strings[i]; i++) { if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) { service->default_exit_action = i; break; } } } /* Close registry */ RegCloseKey(key); return 0; }
/* Callback function triggered when the server exits */ void CALLBACK end_service(void *arg, unsigned char why) { if (stopping) return; stopping = true; pid = (unsigned long) arg; /* Check exit code */ unsigned long exitcode = 0; char code[16]; FILETIME exit_time; GetExitCodeProcess(process_handle, &exitcode); if (exitcode == STILL_ACTIVE || get_process_exit_time(process_handle, &exit_time)) GetSystemTimeAsFileTime(&exit_time); CloseHandle(process_handle); /* Log that the service ended BEFORE logging about killing the process tree. See below for the possible values of the why argument. */ if (! why) { _snprintf(code, sizeof(code), "%d", exitcode); log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0); } /* Clean up. */ kill_process_tree(service_name, pid, exitcode, pid, &creation_time, &exit_time); /* The why argument is true if our wait timed out or false otherwise. Our wait is infinite so why will never be true when called by the system. If it is indeed true, assume we were called from stop_service() because this is a controlled shutdown, and don't take any restart action. */ if (why) return; /* What action should we take? */ int action = NSSM_EXIT_RESTART; unsigned char action_string[ACTION_LEN]; bool default_action; if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) { for (int i = 0; exit_action_strings[i]; i++) { if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) { action = i; break; } } } process_handle = 0; pid = 0; switch (action) { /* Try to restart the service or return failure code to service manager */ case NSSM_EXIT_RESTART: log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service_name, code, exit_action_strings[action], exe, 0); while (monitor_service()) { log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, exe, service_name, 0); Sleep(30000); } break; /* Do nothing, just like srvany would */ case NSSM_EXIT_IGNORE: log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service_name, code, exit_action_strings[action], exe, 0); Sleep(INFINITE); break; /* Tell the service manager we are finished */ case NSSM_EXIT_REALLY: log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0); stop_service(exitcode, true, default_action); break; /* Fake a crash so pre-Vista service managers will run recovery actions. */ case NSSM_EXIT_UNCLEAN: log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0); exit(stop_service(exitcode, false, default_action)); break; } }