\r
Since version 2.22, NSSM can manage existing services.\r
\r
+Since version 2.25, NSSM can execute commands in response to service events.\r
+\r
\r
Usage\r
-----\r
supports AppEnvironment.\r
\r
\r
+Event hooks\r
+-----------\r
+NSSM can run user-configurable commands in response to application events.\r
+These commands are referred to as "hooks" below.\r
+\r
+All hooks are optional. Any hooks which are run will be launched with the\r
+environment configured for the service. NSSM will place additional\r
+variables into the environment which hooks can query to learn how and why\r
+they were called.\r
+\r
+Hooks are categorised by Event and Action. Some hooks are run synchronously\r
+and some are run asynchronously. Hooks prefixed with an *asterisk are run\r
+synchronously. NSSM will wait for these hooks to complete before continuing\r
+its work. Note, however, that ALL hooks are subject to a deadline after which\r
+they will be killed, regardless of whether they are run asynchronously\r
+or not.\r
+\r
+ Event: Start - Triggered when the service is requested to start.\r
+ *Action: Pre - Called before NSSM attempts to launch the application.\r
+ Action: Post - Called after the application successfully starts.\r
+\r
+ Event: Stop - Triggered when the service is requested to stop.\r
+ *Action: Pre - Called before NSSM attempts to kill the application.\r
+\r
+ Event: Exit - Triggered when the application exits.\r
+ *Action: Post - Called after NSSM has cleaned up the application.\r
+\r
+ Event: Rotate - Triggered when online log rotation is requested.\r
+ *Action: Pre - Called before NSSM rotates logs.\r
+ Action: Post - Called after NSSM rotates logs.\r
+\r
+ Event: Power\r
+ Action: Change - Called when the system power status has changed.\r
+ Action: Resume - Called when the system has resumed from standby.\r
+\r
+Note that there is no Stop/Post hook. This is because Exit/Post is called\r
+when the application exits, regardless of whether it did so in response to\r
+a service shutdown request. Stop/Pre is only called before a graceful\r
+shutdown attempt.\r
+\r
+NSSM sets the environment variable NSSM_HOOK_VERSION to a positive number.\r
+Hooks can check the value of the number to determine which other environment\r
+variables are available to them.\r
+\r
+If NSSM_HOOK_VERSION is 1 or greater, these variables are provided:\r
+\r
+ NSSM_EXE - Path to NSSM itself.\r
+ NSSM_CONFIGURATION - Build information for the NSSM executable,\r
+ eg 64-bit debug.\r
+ NSSM_VERSION - Version of the NSSM executable.\r
+ NSSM_BUILD_DATE - Build date of NSSM.\r
+ NSSM_PID - Process ID of the running NSSM executable.\r
+ NSSM_DEADLINE - Deadline number of milliseconds after which NSSM will\r
+ kill the hook if it is still running.\r
+ NSSM_SERVICE_NAME - Name of the service controlled by NSSM.\r
+ NSSM_SERVICE_DISPLAYNAME - Display name of the service.\r
+ NSSM_COMMAND_LINE - Command line used to launch the application.\r
+ NSSM_APPLICATION_PID - Process ID of the primary application process.\r
+ May be blank if the process is not running.\r
+ NSSM_EVENT - Event class triggering the hook.\r
+ NSSM_ACTION - Event action triggering the hook.\r
+ NSSM_TRIGGER - Service control triggering the hook. May be blank if\r
+ the hook was not triggered by a service control, eg Exit/Post.\r
+ NSSM_LAST_CONTROL - Last service control handled by NSSM.\r
+ NSSM_START_REQUESTED_COUNT - Number of times the application was\r
+ requested to start.\r
+ NSSM_START_COUNT - Number of times the application successfully started.\r
+ NSSM_THROTTLE_COUNT - Number of times the application ran for less than\r
+ the throttle period. Reset to zero on successful start or when the\r
+ service is explicitly unpaused.\r
+ NSSM_EXIT_COUNT - Number of times the application exited.\r
+ NSSM_EXITCODE - Exit code of the application. May be blank if the\r
+ application is still running or has not started yet.\r
+ NSSM_RUNTIME - Number of milliseconds for which the NSSM executable has\r
+ been running.\r
+ NSSM_APPLICATION_RUNTIME - Number of milliseconds for which the\r
+ application has been running since it was last started. May be blank\r
+ if the application has not been started yet.\r
+\r
+Future versions of NSSM may provide more environment variables, in which\r
+case NSSM_HOOK_VERSION will be set to a higher number.\r
+\r
+Hooks are configured by creating string (REG_EXPAND_SZ) values in the\r
+registry named after the hook action and placed under\r
+HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters\AppEvents\<event>.\r
+\r
+For example the service could be configured to restart when the system\r
+resumes from standby by setting AppEvents\Power\Resume to:\r
+\r
+ %NSSM_EXE% restart %NSSM_SERVICE_NAME%\r
+\r
+Note that NSSM will abort the startup of the application if a Start/Pre hook\r
+returns exit code of 99.\r
+\r
+A service will normally run hooks in the following order:\r
+\r
+ Start/Pre\r
+ Start/Post\r
+ Stop/Pre\r
+ Exit/Post\r
+\r
+If the application crashes and is restarted by NSSM, the order might be:\r
+\r
+ Start/Pre\r
+ Start/Post\r
+ Exit/Post\r
+ Start/Pre\r
+ Start/Post\r
+ Stop/Pre\r
+ Exit/Post\r
+\r
+\r
Managing services using the GUI\r
-------------------------------\r
NSSM can edit the settings of existing services with the same GUI that is\r
#include "nssm.h"\r
\r
-static enum { NSSM_TAB_APPLICATION, NSSM_TAB_DETAILS, NSSM_TAB_LOGON, NSSM_TAB_DEPENDENCIES, NSSM_TAB_PROCESS, NSSM_TAB_SHUTDOWN, NSSM_TAB_EXIT, NSSM_TAB_IO, NSSM_TAB_ROTATION, NSSM_TAB_ENVIRONMENT, NSSM_NUM_TABS };\r
+static enum { NSSM_TAB_APPLICATION, NSSM_TAB_DETAILS, NSSM_TAB_LOGON, NSSM_TAB_DEPENDENCIES, NSSM_TAB_PROCESS, NSSM_TAB_SHUTDOWN, NSSM_TAB_EXIT, NSSM_TAB_IO, NSSM_TAB_ROTATION, NSSM_TAB_ENVIRONMENT, NSSM_TAB_HOOKS, NSSM_NUM_TABS };\r
static HWND tablist[NSSM_NUM_TABS];\r
+static 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 };\r
+static const TCHAR *hook_action_strings[] = { NSSM_HOOK_ACTION_PRE, NSSM_HOOK_ACTION_POST, NSSM_HOOK_ACTION_CHANGE, NSSM_HOOK_ACTION_RESUME, NULL };\r
static int selected_tab;\r
\r
static HWND dialog(const TCHAR *templ, HWND parent, DLGPROC function, LPARAM l) {\r
EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW), enabled);\r
}\r
\r
+static inline int hook_env(const TCHAR *hook_event, const TCHAR *hook_action, TCHAR *buffer, unsigned long buflen) {\r
+ return _sntprintf_s(buffer, buflen, _TRUNCATE, _T("NSSM_HOOK_%s_%s"), hook_event, hook_action);\r
+}\r
+\r
+static inline void set_hook_tab(int event_index, int action_index, bool changed) {\r
+ int first_event = NSSM_GUI_HOOK_EVENT_START;\r
+ HWND combo;\r
+ combo = GetDlgItem(tablist[NSSM_TAB_HOOKS], IDC_HOOK_EVENT);\r
+ SendMessage(combo, CB_SETCURSEL, event_index, 0);\r
+ combo = GetDlgItem(tablist[NSSM_TAB_HOOKS], IDC_HOOK_ACTION);\r
+ SendMessage(combo, CB_RESETCONTENT, 0, 0);\r
+\r
+ const TCHAR *hook_event = hook_event_strings[event_index];\r
+ TCHAR *hook_action;\r
+ int i;\r
+ switch (event_index + first_event) {\r
+ case NSSM_GUI_HOOK_EVENT_ROTATE:\r
+ i = 0;\r
+ SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(NSSM_GUI_HOOK_ACTION_ROTATE_PRE));\r
+ if (action_index == i++) hook_action = NSSM_HOOK_ACTION_PRE;\r
+ SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(NSSM_GUI_HOOK_ACTION_ROTATE_POST));\r
+ if (action_index == i++) hook_action = NSSM_HOOK_ACTION_POST;\r
+ break;\r
+\r
+ case NSSM_GUI_HOOK_EVENT_START:\r
+ i = 0;\r
+ SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(NSSM_GUI_HOOK_ACTION_START_PRE));\r
+ if (action_index == i++) hook_action = NSSM_HOOK_ACTION_PRE;\r
+ SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(NSSM_GUI_HOOK_ACTION_START_POST));\r
+ if (action_index == i++) hook_action = NSSM_HOOK_ACTION_POST;\r
+ break;\r
+\r
+ case NSSM_GUI_HOOK_EVENT_STOP:\r
+ i = 0;\r
+ SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(NSSM_GUI_HOOK_ACTION_STOP_PRE));\r
+ if (action_index == i++) hook_action = NSSM_HOOK_ACTION_PRE;\r
+ break;\r
+\r
+ case NSSM_GUI_HOOK_EVENT_EXIT:\r
+ i = 0;\r
+ SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(NSSM_GUI_HOOK_ACTION_EXIT_POST));\r
+ if (action_index == i++) hook_action = NSSM_HOOK_ACTION_POST;\r
+ break;\r
+\r
+ case NSSM_GUI_HOOK_EVENT_POWER:\r
+ i = 0;\r
+ SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(NSSM_GUI_HOOK_ACTION_POWER_CHANGE));\r
+ if (action_index == i++) hook_action = NSSM_HOOK_ACTION_CHANGE;\r
+ SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(NSSM_GUI_HOOK_ACTION_POWER_RESUME));\r
+ if (action_index == i++) hook_action = NSSM_HOOK_ACTION_RESUME;\r
+ break;\r
+ }\r
+\r
+ SendMessage(combo, CB_SETCURSEL, action_index, 0);\r
+\r
+ TCHAR hook_name[HOOK_NAME_LENGTH];\r
+ hook_env(hook_event, hook_action, hook_name, _countof(hook_name));\r
+\r
+ if (! *hook_name) return;\r
+\r
+ TCHAR cmd[CMD_LENGTH];\r
+ if (changed) {\r
+ GetDlgItemText(tablist[NSSM_TAB_HOOKS], IDC_HOOK, cmd, _countof(cmd));\r
+ SetEnvironmentVariable(hook_name, cmd);\r
+ }\r
+ else {\r
+ GetEnvironmentVariable(hook_name, cmd, _countof(cmd));\r
+ SetDlgItemText(tablist[NSSM_TAB_HOOKS], IDC_HOOK, cmd);\r
+ }\r
+}\r
+\r
+static inline int update_hook(TCHAR *service_name, const TCHAR *hook_event, const TCHAR *hook_action) {\r
+ TCHAR hook_name[HOOK_NAME_LENGTH];\r
+ if (hook_env(hook_event, hook_action, hook_name, _countof(hook_name)) < 0) return 1;\r
+ TCHAR cmd[CMD_LENGTH];\r
+ ZeroMemory(cmd, sizeof(cmd));\r
+ GetEnvironmentVariable(hook_name, cmd, _countof(cmd));\r
+ if (set_hook(service_name, hook_event, hook_action, cmd)) return 2;\r
+ return 0;\r
+}\r
+\r
+static inline int update_hooks(TCHAR *service_name) {\r
+ int ret = 0;\r
+ ret += update_hook(service_name, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_PRE);\r
+ ret += update_hook(service_name, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_POST);\r
+ ret += update_hook(service_name, NSSM_HOOK_EVENT_STOP, NSSM_HOOK_ACTION_PRE);\r
+ ret += update_hook(service_name, NSSM_HOOK_EVENT_EXIT, NSSM_HOOK_ACTION_POST);\r
+ ret += update_hook(service_name, NSSM_HOOK_EVENT_POWER, NSSM_HOOK_ACTION_CHANGE);\r
+ ret += update_hook(service_name, NSSM_HOOK_EVENT_POWER, NSSM_HOOK_ACTION_RESUME);\r
+ ret += update_hook(service_name, NSSM_HOOK_EVENT_ROTATE, NSSM_HOOK_ACTION_PRE);\r
+ ret += update_hook(service_name, NSSM_HOOK_EVENT_ROTATE, NSSM_HOOK_ACTION_POST);\r
+ return ret;\r
+}\r
+\r
static inline void check_io(HWND owner, TCHAR *name, TCHAR *buffer, unsigned long len, unsigned long control) {\r
if (! SendMessage(GetDlgItem(tablist[NSSM_TAB_IO], control), WM_GETTEXTLENGTH, 0, 0)) return;\r
if (GetDlgItemText(tablist[NSSM_TAB_IO], control, buffer, (int) len)) return;\r
return 6;\r
}\r
\r
+ update_hooks(service->name);\r
+\r
popup_message(window, MB_OK, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);\r
cleanup_nssm_service(service);\r
return 0;\r
return 6;\r
}\r
\r
+ update_hooks(service->name);\r
+\r
popup_message(window, MB_OK, NSSM_MESSAGE_SERVICE_EDITED, service->name);\r
cleanup_nssm_service(service);\r
return 0;\r
else enabled = 0;\r
set_rotation_enabled(enabled);\r
break;\r
+\r
+ /* Hook event. */\r
+ case IDC_HOOK_EVENT:\r
+ if (HIWORD(w) == CBN_SELCHANGE) set_hook_tab((int) SendMessage(GetDlgItem(tab, IDC_HOOK_EVENT), CB_GETCURSEL, 0, 0), 0, false);\r
+ break;\r
+\r
+ /* Hook action. */\r
+ case IDC_HOOK_ACTION:\r
+ if (HIWORD(w) == CBN_SELCHANGE) set_hook_tab((int) SendMessage(GetDlgItem(tab, IDC_HOOK_EVENT), CB_GETCURSEL, 0, 0), (int) SendMessage(GetDlgItem(tab, IDC_HOOK_ACTION), CB_GETCURSEL, 0, 0), false);\r
+ break;\r
+\r
+ /* Browse for hook. */\r
+ case IDC_BROWSE_HOOK:\r
+ dlg = GetDlgItem(tab, IDC_HOOK);\r
+ GetDlgItemText(tab, IDC_HOOK, buffer, _countof(buffer));\r
+ browse(dlg, _T(""), OFN_FILEMUSTEXIST, NSSM_GUI_BROWSE_FILTER_ALL_FILES, 0);\r
+ break;\r
+\r
+ /* Hook. */\r
+ case IDC_HOOK:\r
+ set_hook_tab((int) SendMessage(GetDlgItem(tab, IDC_HOOK_EVENT), CB_GETCURSEL, 0, 0), (int) SendMessage(GetDlgItem(tab, IDC_HOOK_ACTION), CB_GETCURSEL, 0, 0), true);\r
+ break;\r
}\r
return 1;\r
}\r
tablist[NSSM_TAB_ENVIRONMENT] = dialog(MAKEINTRESOURCE(IDD_ENVIRONMENT), window, tab_dlg);\r
ShowWindow(tablist[NSSM_TAB_ENVIRONMENT], SW_HIDE);\r
\r
+ /* Hooks tab. */\r
+ tab.pszText = message_string(NSSM_GUI_TAB_HOOKS);\r
+ tab.cchTextMax = (int) _tcslen(tab.pszText) + 1;\r
+ SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_HOOKS, (LPARAM) &tab);\r
+ tablist[NSSM_TAB_HOOKS] = dialog(MAKEINTRESOURCE(IDD_HOOKS), window, tab_dlg);\r
+ ShowWindow(tablist[NSSM_TAB_HOOKS], SW_HIDE);\r
+\r
+ /* Set defaults. */\r
+ combo = GetDlgItem(tablist[NSSM_TAB_HOOKS], IDC_HOOK_EVENT);\r
+ SendMessage(combo, CB_INSERTSTRING, -1, (LPARAM) message_string(NSSM_GUI_HOOK_EVENT_START));\r
+ SendMessage(combo, CB_INSERTSTRING, -1, (LPARAM) message_string(NSSM_GUI_HOOK_EVENT_STOP));\r
+ SendMessage(combo, CB_INSERTSTRING, -1, (LPARAM) message_string(NSSM_GUI_HOOK_EVENT_EXIT));\r
+ SendMessage(combo, CB_INSERTSTRING, -1, (LPARAM) message_string(NSSM_GUI_HOOK_EVENT_POWER));\r
+ SendMessage(combo, CB_INSERTSTRING, -1, (LPARAM) message_string(NSSM_GUI_HOOK_EVENT_ROTATE));\r
+ if (_tcslen(service->name)) {\r
+ TCHAR hook_name[HOOK_NAME_LENGTH];\r
+ TCHAR cmd[CMD_LENGTH];\r
+ for (i = 0; hook_event_strings[i]; i++) {\r
+ const TCHAR *hook_event = hook_event_strings[i];\r
+ int j;\r
+ for (j = 0; hook_action_strings[j]; j++) {\r
+ const TCHAR *hook_action = hook_action_strings[j];\r
+ if (! valid_hook_name(hook_event, hook_action, true)) continue;\r
+ if (get_hook(service->name, hook_event, hook_action, cmd, sizeof(cmd))) continue;\r
+ if (hook_env(hook_event, hook_action, hook_name, _countof(hook_name)) < 0) continue;\r
+ SetEnvironmentVariable(hook_name, cmd);\r
+ }\r
+ }\r
+ }\r
+ set_hook_tab(0, 0, false);\r
+\r
return 1;\r
\r
/* Tab change. */\r
--- /dev/null
+#include "nssm.h"
+
+typedef struct {
+ TCHAR *name;
+ HANDLE process_handle;
+ unsigned long pid;
+ unsigned long deadline;
+ FILETIME creation_time;
+ kill_t k;
+} hook_t;
+
+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. */
+ if (service->env) duplicate_environment(service->env);
+ if (service->env_extra) set_environment_block(service->env_extra);
+
+ /* 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. */
+ TCHAR path[PATH_LENGTH];
+ GetModuleFileName(0, path, _countof(path));
+ SetEnvironmentVariable(NSSM_HOOK_ENV_IMAGE_PATH, path);
+
+ /* 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);
+ duplicate_environment_strings(service->initial_env);
+ LeaveCriticalSection(&service->hook_section);
+ HeapFree(GetProcessHeap(), 0, hook);
+ return NSSM_HOOK_STATUS_ERROR;
+ }
+
+ /* No hook. */
+ if (! _tcslen(cmd)) {
+ duplicate_environment_strings(service->initial_env);
+ 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));
+ unsigned long flags = 0;
+#ifdef UNICODE
+ flags |= CREATE_UNICODE_ENVIRONMENT;
+#endif
+ ret = NSSM_HOOK_STATUS_NOTRUN;
+ if (CreateProcess(0, cmd, 0, 0, false, 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);
+ }
+
+ /* Restore our environment. */
+ duplicate_environment_strings(service->initial_env);
+
+ 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);
+}
--- /dev/null
+#ifndef HOOK_H
+#define HOOK_H
+
+#define NSSM_HOOK_EVENT_START _T("Start")
+#define NSSM_HOOK_EVENT_STOP _T("Stop")
+#define NSSM_HOOK_EVENT_EXIT _T("Exit")
+#define NSSM_HOOK_EVENT_POWER _T("Power")
+#define NSSM_HOOK_EVENT_ROTATE _T("Rotate")
+
+#define NSSM_HOOK_ACTION_PRE _T("Pre")
+#define NSSM_HOOK_ACTION_POST _T("Post")
+#define NSSM_HOOK_ACTION_CHANGE _T("Change")
+#define NSSM_HOOK_ACTION_RESUME _T("Resume")
+
+/* Hook name will be "<service> (<event>/<action>)" */
+#define HOOK_NAME_LENGTH SERVICE_NAME_LENGTH * 2
+
+#define NSSM_HOOK_VERSION 1
+
+/* Hook ran successfully. */
+#define NSSM_HOOK_STATUS_SUCCESS 0
+/* No hook configured. */
+#define NSSM_HOOK_STATUS_NOTFOUND 1
+/* Hook requested abort. */
+#define NSSM_HOOK_STATUS_ABORT 99
+/* Internal error launching hook. */
+#define NSSM_HOOK_STATUS_ERROR 100
+/* Hook was not run. */
+#define NSSM_HOOK_STATUS_NOTRUN 101
+/* Hook timed out. */
+#define NSSM_HOOK_STATUS_TIMEOUT 102
+/* Hook returned non-zero. */
+#define NSSM_HOOK_STATUS_FAILED 111
+
+/* Version 1. */
+#define NSSM_HOOK_ENV_VERSION _T("NSSM_HOOK_VERSION")
+#define NSSM_HOOK_ENV_IMAGE_PATH _T("NSSM_EXE")
+#define NSSM_HOOK_ENV_NSSM_CONFIGURATION _T("NSSM_CONFIGURATION")
+#define NSSM_HOOK_ENV_NSSM_VERSION _T("NSSM_VERSION")
+#define NSSM_HOOK_ENV_BUILD_DATE _T("NSSM_BUILD_DATE")
+#define NSSM_HOOK_ENV_PID _T("NSSM_PID")
+#define NSSM_HOOK_ENV_DEADLINE _T("NSSM_DEADLINE")
+#define NSSM_HOOK_ENV_SERVICE_NAME _T("NSSM_SERVICE_NAME")
+#define NSSM_HOOK_ENV_SERVICE_DISPLAYNAME _T("NSSM_SERVICE_DISPLAYNAME")
+#define NSSM_HOOK_ENV_COMMAND_LINE _T("NSSM_COMMAND_LINE")
+#define NSSM_HOOK_ENV_APPLICATION_PID _T("NSSM_APPLICATION_PID")
+#define NSSM_HOOK_ENV_EVENT _T("NSSM_EVENT")
+#define NSSM_HOOK_ENV_ACTION _T("NSSM_ACTION")
+#define NSSM_HOOK_ENV_TRIGGER _T("NSSM_TRIGGER")
+#define NSSM_HOOK_ENV_LAST_CONTROL _T("NSSM_LAST_CONTROL")
+#define NSSM_HOOK_ENV_START_REQUESTED_COUNT _T("NSSM_START_REQUESTED_COUNT")
+#define NSSM_HOOK_ENV_START_COUNT _T("NSSM_START_COUNT")
+#define NSSM_HOOK_ENV_THROTTLE_COUNT _T("NSSM_THROTTLE_COUNT")
+#define NSSM_HOOK_ENV_EXIT_COUNT _T("NSSM_EXIT_COUNT")
+#define NSSM_HOOK_ENV_EXITCODE _T("NSSM_EXITCODE")
+#define NSSM_HOOK_ENV_RUNTIME _T("NSSM_RUNTIME")
+#define NSSM_HOOK_ENV_APPLICATION_RUNTIME _T("NSSM_APPLICATION_RUNTIME")
+
+typedef struct {
+ TCHAR name[HOOK_NAME_LENGTH];
+ HANDLE thread_handle;
+} hook_thread_data_t;
+
+typedef struct {
+ hook_thread_data_t *data;
+ int num_threads;
+} hook_thread_t;
+
+bool valid_hook_name(const TCHAR *, const TCHAR *, bool);
+void await_hook_threads(hook_thread_t *, SERVICE_STATUS_HANDLE, SERVICE_STATUS *, unsigned long);
+int nssm_hook(hook_thread_t *, nssm_service_t *, TCHAR *, TCHAR *, unsigned long *, unsigned long, bool);
+int nssm_hook(hook_thread_t *, nssm_service_t *, TCHAR *, TCHAR *, unsigned long *, unsigned long);
+int nssm_hook(hook_thread_t *, nssm_service_t *, TCHAR *, TCHAR *, unsigned long *);
+
+#endif
#include "console.h"\r
#include "env.h"\r
#include "event.h"\r
+#include "hook.h"\r
#include "imports.h"\r
#include "messages.h"\r
#include "process.h"\r
#define NSSM_SERVICE_CONTROL_START 0\r
#define NSSM_SERVICE_CONTROL_ROTATE 128\r
\r
+/* How many milliseconds to wait for a hook. */\r
+#define NSSM_HOOK_DEADLINE 60000\r
+\r
+/* How many milliseconds to wait for outstanding hooks. */\r
+#define NSSM_HOOK_THREAD_DEADLINE 80000\r
+\r
#endif\r
</FileConfiguration>\r
</File>\r
<File\r
+ RelativePath="hook.cpp"\r
+ >\r
+ </File>\r
+ <File\r
RelativePath="imports.cpp"\r
>\r
</File>\r
>\r
</File>\r
<File\r
+ RelativePath="hook.h"\r
+ >\r
+ </File>\r
+ <File\r
RelativePath="imports.h"\r
>\r
</File>\r
\r
return 0;\r
}\r
+\r
+int set_hook(const TCHAR *service_name, const TCHAR *hook_event, const TCHAR *hook_action, TCHAR *cmd) {\r
+ /* Try to open the registry */\r
+ TCHAR registry[KEY_LENGTH];\r
+ if (_sntprintf_s(registry, _countof(registry), _TRUNCATE, _T("%s\\%s"), NSSM_REG_HOOK, hook_event) < 0) {\r
+ log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook registry"), _T("set_hook()"), 0);\r
+ return 1;\r
+ }\r
+\r
+ HKEY key;\r
+ long error;\r
+\r
+ /* Don't create keys needlessly. */\r
+ if (! _tcslen(cmd)) {\r
+ key = open_registry(service_name, registry, KEY_READ, false);\r
+ if (! key) return 0;\r
+ error = RegQueryValueEx(key, hook_action, 0, 0, 0, 0);\r
+ RegCloseKey(key);\r
+ if (error == ERROR_FILE_NOT_FOUND) return 0;\r
+ }\r
+\r
+ key = open_registry(service_name, registry, KEY_WRITE);\r
+ if (! key) return 1;\r
+\r
+ int ret = 1;\r
+ if (_tcslen(cmd)) ret = set_string(key, (TCHAR *) hook_action, cmd, true);\r
+ else {\r
+ error = RegDeleteValue(key, hook_action);\r
+ if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) ret = 0;\r
+ }\r
+\r
+ /* Close registry */\r
+ RegCloseKey(key);\r
+\r
+ return ret;\r
+}\r
+\r
+int get_hook(const TCHAR *service_name, const TCHAR *hook_event, const TCHAR *hook_action, TCHAR *buffer, unsigned long buflen) {\r
+ /* Try to open the registry */\r
+ TCHAR registry[KEY_LENGTH];\r
+ if (_sntprintf_s(registry, _countof(registry), _TRUNCATE, _T("%s\\%s"), NSSM_REG_HOOK, hook_event) < 0) {\r
+ log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook registry"), _T("get_hook()"), 0);\r
+ return 1;\r
+ }\r
+ HKEY key = open_registry(service_name, registry, KEY_READ, false);\r
+ if (! key) return 1;\r
+\r
+ int ret = expand_parameter(key, (TCHAR *) hook_action, buffer, buflen, true, false);\r
+\r
+ /* Close registry */\r
+ RegCloseKey(key);\r
+\r
+ return ret;\r
+}\r
#define NSSM_REG_PRIORITY _T("AppPriority")\r
#define NSSM_REG_AFFINITY _T("AppAffinity")\r
#define NSSM_REG_NO_CONSOLE _T("AppNoConsole")\r
+#define NSSM_REG_HOOK _T("AppEvents")\r
#define NSSM_STDIO_LENGTH 29\r
\r
HKEY open_registry(const TCHAR *, const TCHAR *, REGSAM sam, bool);\r
int get_io_parameters(nssm_service_t *, HKEY);\r
int get_parameters(nssm_service_t *, STARTUPINFO *);\r
int get_exit_action(const TCHAR *, unsigned long *, TCHAR *, bool *);\r
+int set_hook(const TCHAR *, const TCHAR *, const TCHAR *, TCHAR *);\r
+int get_hook(const TCHAR *, const TCHAR *, const TCHAR *, TCHAR *, unsigned long);\r
\r
#endif\r
#define IDD_NATIVE 113\r
#define IDD_PROCESS 114\r
#define IDD_DEPENDENCIES 115\r
+#define IDD_HOOKS 116\r
#define IDC_PATH 1000\r
#define IDC_TAB1 1001\r
#define IDC_CANCEL 1002\r
#define IDC_CONSOLE 1045\r
#define IDC_DEPENDENCIES 1046\r
#define IDC_KILL_PROCESS_TREE 1047\r
+#define IDC_HOOK_EVENT 1048\r
+#define IDC_HOOK_ACTION 1049\r
+#define IDC_HOOK 1050\r
+#define IDC_BROWSE_HOOK 1051\r
\r
// Next default values for new objects\r
// \r
#ifdef APSTUDIO_INVOKED\r
#ifndef APSTUDIO_READONLY_SYMBOLS\r
-#define _APS_NEXT_RESOURCE_VALUE 115\r
+#define _APS_NEXT_RESOURCE_VALUE 117\r
#define _APS_NEXT_COMMAND_VALUE 40001\r
-#define _APS_NEXT_CONTROL_VALUE 1048\r
+#define _APS_NEXT_CONTROL_VALUE 1052\r
#define _APS_NEXT_SYMED_VALUE 101\r
#endif\r
#endif\r
const TCHAR *startup_strings[] = { _T("SERVICE_AUTO_START"), _T("SERVICE_DELAYED_AUTO_START"), _T("SERVICE_DEMAND_START"), _T("SERVICE_DISABLED"), 0 };\r
const TCHAR *priority_strings[] = { _T("REALTIME_PRIORITY_CLASS"), _T("HIGH_PRIORITY_CLASS"), _T("ABOVE_NORMAL_PRIORITY_CLASS"), _T("NORMAL_PRIORITY_CLASS"), _T("BELOW_NORMAL_PRIORITY_CLASS"), _T("IDLE_PRIORITY_CLASS"), 0 };\r
\r
+static hook_thread_t hook_threads = { NULL, 0 };\r
+\r
typedef struct {\r
int first;\r
int last;\r
return -1;\r
}\r
\r
+static inline void wait_for_hooks(nssm_service_t *service, bool notify) {\r
+ SERVICE_STATUS_HANDLE status_handle;\r
+ SERVICE_STATUS *status;\r
+\r
+ /* On a clean shutdown we need to keep the service's status up-to-date. */\r
+ if (notify) {\r
+ status_handle = service->status_handle;\r
+ status = &service->status;\r
+ }\r
+ else {\r
+ status_handle = NULL;\r
+ status = NULL;\r
+ }\r
+\r
+ EnterCriticalSection(&service->hook_section);\r
+ await_hook_threads(&hook_threads, status_handle, status, NSSM_HOOK_THREAD_DEADLINE);\r
+ LeaveCriticalSection(&service->hook_section);\r
+}\r
+\r
int affinity_mask_to_string(__int64 mask, TCHAR **string) {\r
if (! string) return 1;\r
if (! mask) {\r
}\r
\r
static inline unsigned long throttle_milliseconds(unsigned long throttle) {\r
+ if (throttle > 7) throttle = 8;\r
/* pow() operates on doubles. */\r
unsigned long ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;\r
return ret * 1000;\r
if (service->wait_handle) UnregisterWait(service->wait_handle);\r
if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);\r
if (service->throttle_timer) CloseHandle(service->throttle_timer);\r
+ if (service->hook_section_initialised) DeleteCriticalSection(&service->hook_section);\r
if (service->initial_env) FreeEnvironmentStrings(service->initial_env);\r
HeapFree(GetProcessHeap(), 0, service);\r
}\r
service->handle = open_service(services, service->name, SERVICE_CHANGE_CONFIG, 0, 0);\r
set_service_recovery(service);\r
\r
+ /* Remember our display name. */\r
+ unsigned long displayname_len = _countof(service->displayname);\r
+ GetServiceDisplayName(services, service->name, service->displayname, &displayname_len);\r
+\r
CloseServiceHandle(services);\r
}\r
}\r
}\r
}\r
\r
+ /* Critical section for hooks. */\r
+ InitializeCriticalSection(&service->hook_section);\r
+ service->hook_section_initialised = true;\r
+\r
/* Remember our initial environment. */\r
service->initial_env = GetEnvironmentStrings();\r
\r
+ /* Remember our creation time. */\r
+ if (get_process_creation_time(GetCurrentProcess(), &service->nssm_creation_time)) ZeroMemory(&service->nssm_creation_time, sizeof(service->nssm_creation_time));\r
+\r
monitor_service(service);\r
}\r
\r
\r
case SERVICE_CONTROL_SHUTDOWN:\r
case SERVICE_CONTROL_STOP:\r
+ service->last_control = control;\r
log_service_control(service->name, control, true);\r
+\r
+ /* Pre-stop hook. */\r
+ service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
+ SetServiceStatus(service->status_handle, &service->status);\r
+ nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_STOP, NSSM_HOOK_ACTION_PRE, &control, NSSM_SERVICE_STATUS_DEADLINE, false);\r
+\r
/*\r
We MUST acknowledge the stop request promptly but we're committed to\r
waiting for the application to exit. Spawn a new thread to wait\r
return NO_ERROR;\r
\r
case SERVICE_CONTROL_CONTINUE:\r
+ service->last_control = control;\r
log_service_control(service->name, control, true);\r
service->throttle = 0;\r
if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);\r
return ERROR_CALL_NOT_IMPLEMENTED;\r
\r
case NSSM_SERVICE_CONTROL_ROTATE:\r
+ service->last_control = control;\r
log_service_control(service->name, control, true);\r
+ (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_ROTATE, NSSM_HOOK_ACTION_PRE, &control, NSSM_HOOK_DEADLINE, false);\r
if (service->rotate_stdout_online == NSSM_ROTATE_ONLINE) service->rotate_stdout_online = NSSM_ROTATE_ONLINE_ASAP;\r
if (service->rotate_stderr_online == NSSM_ROTATE_ONLINE) service->rotate_stderr_online = NSSM_ROTATE_ONLINE_ASAP;\r
+ (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_ROTATE, NSSM_HOOK_ACTION_POST, &control);\r
return NO_ERROR;\r
\r
case SERVICE_CONTROL_POWEREVENT:\r
- if (event != PBT_APMRESUMEAUTOMATIC) {\r
- log_service_control(service->name, control, false);\r
+ /* Resume from suspend. */\r
+ if (event == PBT_APMRESUMEAUTOMATIC) {\r
+ service->last_control = control;\r
+ log_service_control(service->name, control, true);\r
+ (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_POWER, NSSM_HOOK_ACTION_RESUME, &control);\r
return NO_ERROR;\r
}\r
- log_service_control(service->name, control, true);\r
- end_service((void *) service, false);\r
+\r
+ /* Battery low or changed to A/C power or something. */\r
+ if (event == PBT_APMPOWERSTATUSCHANGE) {\r
+ service->last_control = control;\r
+ log_service_control(service->name, control, true);\r
+ (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_POWER, NSSM_HOOK_ACTION_CHANGE, &control);\r
+ return NO_ERROR;\r
+ }\r
+ log_service_control(service->name, control, false);\r
return NO_ERROR;\r
}\r
\r
service->allow_restart = true;\r
\r
if (service->process_handle) return 0;\r
+ service->start_requested_count++;\r
\r
/* Allocate a STARTUPINFO structure for a new process */\r
STARTUPINFO si;\r
if (service->env) duplicate_environment(service->env);\r
if (service->env_extra) set_environment_block(service->env_extra);\r
\r
+ /* Pre-start hook. */\r
+ unsigned long control = NSSM_SERVICE_CONTROL_START;\r
+ service->status.dwCurrentState = SERVICE_START_PENDING;\r
+ service->status.dwControlsAccepted = SERVICE_ACCEPT_POWEREVENT | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;\r
+ if (nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_PRE, &control, NSSM_SERVICE_STATUS_DEADLINE, false) == NSSM_HOOK_STATUS_ABORT) {\r
+ TCHAR code[16];\r
+ _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), NSSM_HOOK_STATUS_ABORT);\r
+ log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PRESTART_HOOK_ABORT, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_PRE, service->name, code, 0);\r
+ return stop_service(service, 5, true, true);\r
+ }\r
+\r
/* Set up I/O redirection. */\r
if (get_output_handles(service, &si)) {\r
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0);\r
duplicate_environment_strings(service->initial_env);\r
return stop_service(service, exitcode, true, true);\r
}\r
+ service->start_count++;\r
service->process_handle = pi.hProcess;\r
service->pid = pi.dwProcessId;\r
\r
so abandon the wait before too much time has elapsed.\r
*/\r
service->status.dwCurrentState = SERVICE_START_PENDING;\r
- service->status.dwControlsAccepted = SERVICE_ACCEPT_POWEREVENT | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;\r
if (await_single_handle(service->status_handle, &service->status, service->process_handle, service->name, _T("start_service"), service->throttle_delay) == 1) service->throttle = 0;\r
\r
/* Signal successful start */\r
service->status.dwControlsAccepted &= ~SERVICE_ACCEPT_PAUSE_CONTINUE;\r
SetServiceStatus(service->status_handle, &service->status);\r
\r
- /* Continue waiting for a clean startup. */\r
- if (deadline == WAIT_TIMEOUT) {\r
- if (service->throttle_delay > delay) {\r
- if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;\r
- }\r
- else service->throttle = 0;\r
+ /* Post-start hook. */\r
+ if (! service->throttle) {\r
+ (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_POST, &control);\r
}\r
\r
/* Ensure the restart delay is always applied. */\r
\r
/* Signal we stopped */\r
if (graceful) {\r
+ service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
+ wait_for_hooks(service, true);\r
service->status.dwCurrentState = SERVICE_STOPPED;\r
if (exitcode) {\r
service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
TCHAR code[16];\r
if (service->process_handle) {\r
GetExitCodeProcess(service->process_handle, &exitcode);\r
+ service->exitcode = exitcode;\r
/* Check real exit time. */\r
if (exitcode != STILL_ACTIVE) get_process_exit_time(service->process_handle, &service->exit_time);\r
CloseHandle(service->process_handle);\r
}\r
service->pid = 0;\r
\r
+ /* Exit hook. */\r
+ service->exit_count++;\r
+ (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_EXIT, NSSM_HOOK_ACTION_POST, NULL, NSSM_HOOK_DEADLINE, true);\r
+\r
/*\r
The why argument is true if our wait timed out or false otherwise.\r
Our wait is infinite so why will never be true when called by the system.\r
/* Do nothing, just like srvany would */\r
case NSSM_EXIT_IGNORE:\r
log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);\r
+ wait_for_hooks(service, false);\r
Sleep(INFINITE);\r
break;\r
\r
case NSSM_EXIT_UNCLEAN:\r
log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);\r
stop_service(service, exitcode, false, default_action);\r
+ wait_for_hooks(service, false);\r
free_imports();\r
exit(exitcode);\r
break;\r
if (service->restart_delay > throttle_ms) ms = service->restart_delay;\r
else ms = throttle_ms;\r
\r
- if (service->throttle > 7) service->throttle = 8;\r
-\r
_sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);\r
\r
if (service->throttle == 1 && service->restart_delay > throttle_ms) log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESTART_DELAY, service->name, milliseconds, 0);\r
HANDLE process_handle;\r
unsigned long pid;\r
HANDLE wait_handle;\r
+ unsigned long exitcode;\r
bool stopping;\r
bool allow_restart;\r
unsigned long throttle;\r
CRITICAL_SECTION throttle_section;\r
bool throttle_section_initialised;\r
+ CRITICAL_SECTION hook_section;\r
+ bool hook_section_initialised;\r
CONDITION_VARIABLE throttle_condition;\r
HANDLE throttle_timer;\r
LARGE_INTEGER throttle_duetime;\r
+ FILETIME nssm_creation_time;\r
FILETIME creation_time;\r
FILETIME exit_time;\r
TCHAR *initial_env;\r
+ unsigned long last_control;\r
+ unsigned long start_requested_count;\r
+ unsigned long start_count;\r
+ unsigned long exit_count;\r
} nssm_service_t;\r
\r
void WINAPI service_main(unsigned long, TCHAR **);\r
return 1;
}
+static inline bool split_hook_name(const TCHAR *hook_name, TCHAR *hook_event, TCHAR *hook_action) {
+ TCHAR *s;
+
+ for (s = (TCHAR *) hook_name; *s; s++) {
+ if (*s == _T('/')) {
+ *s = _T('\0');
+ _sntprintf_s(hook_event, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s"), hook_name);
+ _sntprintf_s(hook_action, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s"), ++s);
+ return valid_hook_name(hook_event, hook_action, false);
+ }
+ }
+
+ print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_NAME, hook_name);
+ return false;
+}
+
+static int setting_set_hook(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+ TCHAR hook_event[HOOK_NAME_LENGTH];
+ TCHAR hook_action[HOOK_NAME_LENGTH];
+ if (! split_hook_name(additional, hook_event, hook_action)) return -1;
+
+ TCHAR *cmd;
+ if (value && value->string) cmd = value->string;
+ else cmd = _T("");
+
+ if (set_hook(service_name, hook_event, hook_action, cmd)) return -1;
+ if (! _tcslen(cmd)) return 0;
+ return 1;
+}
+
+static int setting_get_hook(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+ TCHAR hook_event[HOOK_NAME_LENGTH];
+ TCHAR hook_action[HOOK_NAME_LENGTH];
+ if (! split_hook_name(additional, hook_event, hook_action)) return -1;
+
+ TCHAR cmd[CMD_LENGTH];
+ if (get_hook(service_name, hook_event, hook_action, cmd, sizeof(cmd))) return -1;
+
+ value_from_string(name, value, cmd);
+
+ if (! _tcslen(cmd)) return 0;
+ return 1;
+}
+
static int setting_set_affinity(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
HKEY key = (HKEY) param;
if (! key) return -1;
{ NSSM_REG_FLAGS, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string },
{ NSSM_REG_DIR, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string },
{ NSSM_REG_EXIT, REG_SZ, (void *) exit_action_strings[NSSM_EXIT_RESTART], false, ADDITIONAL_MANDATORY, setting_set_exit_action, setting_get_exit_action },
+ { NSSM_REG_HOOK, REG_SZ, (void *) _T(""), false, ADDITIONAL_MANDATORY, setting_set_hook, setting_get_hook },
{ NSSM_REG_AFFINITY, REG_SZ, 0, false, 0, setting_set_affinity, setting_get_affinity },
{ NSSM_REG_ENV, REG_MULTI_SZ, NULL, false, ADDITIONAL_CRLF, setting_set_environment, setting_get_environment },
{ NSSM_REG_ENV_EXTRA, REG_MULTI_SZ, NULL, false, ADDITIONAL_CRLF, setting_set_environment, setting_get_environment },