#include "nssm.h" typedef struct { TCHAR *name; HANDLE process_handle; unsigned long pid; unsigned long deadline; FILETIME creation_time; kill_t k; } hook_t; const TCHAR *hook_event_strings[] = { NSSM_HOOK_EVENT_START, NSSM_HOOK_EVENT_STOP, NSSM_HOOK_EVENT_EXIT, NSSM_HOOK_EVENT_POWER, NSSM_HOOK_EVENT_ROTATE, NULL }; const TCHAR *hook_action_strings[] = { NSSM_HOOK_ACTION_PRE, NSSM_HOOK_ACTION_POST, NSSM_HOOK_ACTION_CHANGE, NSSM_HOOK_ACTION_RESUME, NULL }; static unsigned long WINAPI await_hook(void *arg) { hook_t *hook = (hook_t *) arg; if (! hook) return NSSM_HOOK_STATUS_ERROR; int ret = 0; if (WaitForSingleObject(hook->process_handle, hook->deadline) == WAIT_TIMEOUT) ret = NSSM_HOOK_STATUS_TIMEOUT; /* Tidy up hook process tree. */ if (hook->name) hook->k.name = hook->name; else hook->k.name = _T("hook"); hook->k.process_handle = hook->process_handle; hook->k.pid = hook->pid; hook->k.stop_method = ~0; hook->k.kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD; hook->k.kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD; hook->k.kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD; hook->k.creation_time = hook->creation_time; GetSystemTimeAsFileTime(&hook->k.exit_time); kill_process_tree(&hook->k, hook->pid); if (ret) { CloseHandle(hook->process_handle); if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name); HeapFree(GetProcessHeap(), 0, hook); return ret; } unsigned long exitcode; GetExitCodeProcess(hook->process_handle, &exitcode); CloseHandle(hook->process_handle); if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name); HeapFree(GetProcessHeap(), 0, hook); if (exitcode == NSSM_HOOK_STATUS_ABORT) return NSSM_HOOK_STATUS_ABORT; if (exitcode) return NSSM_HOOK_STATUS_FAILED; return NSSM_HOOK_STATUS_SUCCESS; } static void set_hook_runtime(TCHAR *v, FILETIME *start, FILETIME *now) { if (start && now) { ULARGE_INTEGER s; s.LowPart = start->dwLowDateTime; s.HighPart = start->dwHighDateTime; if (s.QuadPart) { ULARGE_INTEGER t; t.LowPart = now->dwLowDateTime; t.HighPart = now->dwHighDateTime; if (t.QuadPart && t.QuadPart >= s.QuadPart) { t.QuadPart -= s.QuadPart; t.QuadPart /= 10000LL; TCHAR number[16]; _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%llu"), t.QuadPart); SetEnvironmentVariable(v, number); return; } } } SetEnvironmentVariable(v, _T("")); } static void add_thread_handle(hook_thread_t *hook_threads, HANDLE thread_handle, TCHAR *name) { if (! hook_threads) return; int num_threads = hook_threads->num_threads + 1; hook_thread_data_t *data = (hook_thread_data_t *) HeapAlloc(GetProcessHeap(), 0, num_threads * sizeof(hook_thread_data_t)); if (! data) { log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook_thread_t"), _T("add_thread_handle()"), 0); return; } int i; for (i = 0; i < hook_threads->num_threads; i++) memmove(&data[i], &hook_threads->data[i], sizeof(data[i])); memmove(data[i].name, name, sizeof(data[i].name)); data[i].thread_handle = thread_handle; if (hook_threads->data) HeapFree(GetProcessHeap(), 0, hook_threads->data); hook_threads->data = data; hook_threads->num_threads = num_threads; } bool valid_hook_name(const TCHAR *hook_event, const TCHAR *hook_action, bool quiet) { bool valid_event = false; bool valid_action = false; /* Exit/Post */ if (str_equiv(hook_event, NSSM_HOOK_EVENT_EXIT)) { if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true; if (quiet) return false; print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST); return false; } /* Power/{Change,Resume} */ if (str_equiv(hook_event, NSSM_HOOK_EVENT_POWER)) { if (str_equiv(hook_action, NSSM_HOOK_ACTION_CHANGE)) return true; if (str_equiv(hook_action, NSSM_HOOK_ACTION_RESUME)) return true; if (quiet) return false; print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_CHANGE); _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_RESUME); return false; } /* Rotate/{Pre,Post} */ if (str_equiv(hook_event, NSSM_HOOK_EVENT_ROTATE)) { if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true; if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true; if (quiet) return false; print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE); _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST); return false; } /* Start/{Pre,Post} */ if (str_equiv(hook_event, NSSM_HOOK_EVENT_START)) { if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true; if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true; if (quiet) return false; print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE); _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST); return false; } /* Stop/Pre */ if (str_equiv(hook_event, NSSM_HOOK_EVENT_STOP)) { if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true; if (quiet) return false; print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE); return false; } if (quiet) return false; print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_EVENT); _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_EXIT); _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_POWER); _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_ROTATE); _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_START); _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_STOP); return false; } void await_hook_threads(hook_thread_t *hook_threads, SERVICE_STATUS_HANDLE status_handle, SERVICE_STATUS *status, unsigned long deadline) { if (! hook_threads) return; if (! hook_threads->num_threads) return; int *retain = (int *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, hook_threads->num_threads * sizeof(int)); if (! retain) { log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("retain"), _T("await_hook_threads()"), 0); return; } /* We could use WaitForMultipleObjects() but await_single_object() can update the service status as well. */ int num_threads = 0; int i; for (i = 0; i < hook_threads->num_threads; i++) { if (deadline) { if (await_single_handle(status_handle, status, hook_threads->data[i].thread_handle, hook_threads->data[i].name, _T(__FUNCTION__), deadline) != 1) { CloseHandle(hook_threads->data[i].thread_handle); continue; } } else if (WaitForSingleObject(hook_threads->data[i].thread_handle, 0) != WAIT_TIMEOUT) { CloseHandle(hook_threads->data[i].thread_handle); continue; } retain[num_threads++]= i; } if (num_threads) { hook_thread_data_t *data = (hook_thread_data_t *) HeapAlloc(GetProcessHeap(), 0, num_threads * sizeof(hook_thread_data_t)); if (! data) { log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("data"), _T("await_hook_threads()"), 0); HeapFree(GetProcessHeap(), 0, retain); return; } for (i = 0; i < num_threads; i++) memmove(&data[i], &hook_threads->data[retain[i]], sizeof(data[i])); HeapFree(GetProcessHeap(), 0, hook_threads->data); hook_threads->data = data; hook_threads->num_threads = num_threads; } else { HeapFree(GetProcessHeap(), 0, hook_threads->data); ZeroMemory(hook_threads, sizeof(*hook_threads)); } HeapFree(GetProcessHeap(), 0, retain); } /* Returns: NSSM_HOOK_STATUS_SUCCESS if the hook ran successfully. NSSM_HOOK_STATUS_NOTFOUND if no hook was found. NSSM_HOOK_STATUS_ABORT if the hook failed and we should cancel service start. NSSM_HOOK_STATUS_ERROR on error. NSSM_HOOK_STATUS_NOTRUN if the hook didn't run. NSSM_HOOK_STATUS_TIMEOUT if the hook timed out. NSSM_HOOK_STATUS_FAILED if the hook failed. */ int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control, unsigned long deadline, bool async) { int ret = 0; hook_t *hook = (hook_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(hook_t)); if (! hook) { log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook"), _T("nssm_hook()"), 0); return NSSM_HOOK_STATUS_ERROR; } FILETIME now; GetSystemTimeAsFileTime(&now); EnterCriticalSection(&service->hook_section); /* Set the environment. */ set_service_environment(service); /* ABI version. */ TCHAR number[16]; _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), NSSM_HOOK_VERSION); SetEnvironmentVariable(NSSM_HOOK_ENV_VERSION, number); /* Event triggering this action. */ SetEnvironmentVariable(NSSM_HOOK_ENV_EVENT, hook_event); /* Hook action. */ SetEnvironmentVariable(NSSM_HOOK_ENV_ACTION, hook_action); /* Control triggering this action. May be empty. */ if (hook_control) SetEnvironmentVariable(NSSM_HOOK_ENV_TRIGGER, service_control_text(*hook_control)); else SetEnvironmentVariable(NSSM_HOOK_ENV_TRIGGER, _T("")); /* Last control handled. */ SetEnvironmentVariable(NSSM_HOOK_ENV_LAST_CONTROL, service_control_text(service->last_control)); /* Path to NSSM, unquoted for the environment. */ SetEnvironmentVariable(NSSM_HOOK_ENV_IMAGE_PATH, nssm_unquoted_imagepath()); /* NSSM version. */ SetEnvironmentVariable(NSSM_HOOK_ENV_NSSM_CONFIGURATION, NSSM_CONFIGURATION); SetEnvironmentVariable(NSSM_HOOK_ENV_NSSM_VERSION, NSSM_VERSION); SetEnvironmentVariable(NSSM_HOOK_ENV_BUILD_DATE, NSSM_DATE); /* NSSM PID. */ _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), GetCurrentProcessId()); SetEnvironmentVariable(NSSM_HOOK_ENV_PID, number); /* NSSM runtime. */ set_hook_runtime(NSSM_HOOK_ENV_RUNTIME, &service->nssm_creation_time, &now); /* Application PID. */ if (service->pid) { _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->pid); SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_PID, number); /* Application runtime. */ set_hook_runtime(NSSM_HOOK_ENV_APPLICATION_RUNTIME, &service->creation_time, &now); /* Exit code. */ SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, _T("")); } else { SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_PID, _T("")); if (str_equiv(hook_event, NSSM_HOOK_EVENT_START) && str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) { SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_RUNTIME, _T("")); SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, _T("")); } else { set_hook_runtime(NSSM_HOOK_ENV_APPLICATION_RUNTIME, &service->creation_time, &service->exit_time); /* Exit code. */ _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->exitcode); SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, number); } } /* Deadline for this script. */ _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), deadline); SetEnvironmentVariable(NSSM_HOOK_ENV_DEADLINE, number); /* Service name. */ SetEnvironmentVariable(NSSM_HOOK_ENV_SERVICE_NAME, service->name); SetEnvironmentVariable(NSSM_HOOK_ENV_SERVICE_DISPLAYNAME, service->displayname); /* Times the service was asked to start. */ _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->start_requested_count); SetEnvironmentVariable(NSSM_HOOK_ENV_START_REQUESTED_COUNT, number); /* Times the service actually did start. */ _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->start_count); SetEnvironmentVariable(NSSM_HOOK_ENV_START_COUNT, number); /* Times the service exited. */ _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->exit_count); SetEnvironmentVariable(NSSM_HOOK_ENV_EXIT_COUNT, number); /* Throttled count. */ _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->throttle); SetEnvironmentVariable(NSSM_HOOK_ENV_THROTTLE_COUNT, number); /* Command line. */ TCHAR app[CMD_LENGTH]; _sntprintf_s(app, _countof(app), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags); SetEnvironmentVariable(NSSM_HOOK_ENV_COMMAND_LINE, app); TCHAR cmd[CMD_LENGTH]; if (get_hook(service->name, hook_event, hook_action, cmd, sizeof(cmd))) { log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_HOOK_FAILED, hook_event, hook_action, service->name, 0); unset_service_environment(service); LeaveCriticalSection(&service->hook_section); HeapFree(GetProcessHeap(), 0, hook); return NSSM_HOOK_STATUS_ERROR; } /* No hook. */ if (! _tcslen(cmd)) { unset_service_environment(service); LeaveCriticalSection(&service->hook_section); HeapFree(GetProcessHeap(), 0, hook); return NSSM_HOOK_STATUS_NOTFOUND; } /* Run the command. */ STARTUPINFO si; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); PROCESS_INFORMATION pi; ZeroMemory(&pi, sizeof(pi)); if (service->hook_share_output_handles) (void) use_output_handles(service, &si); bool inherit_handles = false; if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true; unsigned long flags = 0; #ifdef UNICODE flags |= CREATE_UNICODE_ENVIRONMENT; #endif ret = NSSM_HOOK_STATUS_NOTRUN; if (CreateProcess(0, cmd, 0, 0, inherit_handles, flags, 0, service->dir, &si, &pi)) { hook->name = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, HOOK_NAME_LENGTH * sizeof(TCHAR)); if (hook->name) _sntprintf_s(hook->name, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s (%s/%s)"), service->name, hook_event, hook_action); hook->process_handle = pi.hProcess; hook->pid = pi.dwProcessId; hook->deadline = deadline; if (get_process_creation_time(hook->process_handle, &hook->creation_time)) GetSystemTimeAsFileTime(&hook->creation_time); unsigned long tid; HANDLE thread_handle = CreateThread(NULL, 0, await_hook, (void *) hook, 0, &tid); if (thread_handle) { if (async) { ret = 0; await_hook_threads(hook_threads, service->status_handle, &service->status, 0); add_thread_handle(hook_threads, thread_handle, hook->name); } else { await_single_handle(service->status_handle, &service->status, thread_handle, hook->name, _T(__FUNCTION__), deadline + NSSM_SERVICE_STATUS_DEADLINE); unsigned long exitcode; GetExitCodeThread(thread_handle, &exitcode); ret = (int) exitcode; CloseHandle(thread_handle); } } else { log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0); await_hook(hook); if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name); HeapFree(GetProcessHeap(), 0, hook); } } else { log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_HOOK_CREATEPROCESS_FAILED, hook_event, hook_action, service->name, cmd, error_string(GetLastError()), 0); HeapFree(GetProcessHeap(), 0, hook); close_output_handles(&si); } /* Restore our environment. */ unset_service_environment(service); LeaveCriticalSection(&service->hook_section); return ret; } int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control, unsigned long deadline) { return nssm_hook(hook_threads, service, hook_event, hook_action, hook_control, deadline, true); } int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control) { return nssm_hook(hook_threads, service, hook_event, hook_action, hook_control, NSSM_HOOK_DEADLINE); }