From: Iain Patterson Date: Thu, 14 Jul 2016 16:44:14 +0000 (+0100) Subject: Redirect hooks' output. X-Git-Url: http://git.iain.cx/?a=commitdiff_plain;h=6adc886e1fa296f67aacef0c01994e302e8caf86;p=nssm.git Redirect hooks' output. If AppRedirectHook is 1 and stdout and/or stderr are being redirected, also redirect the output of hooks. Doing so requires using a logging thread and so implies online rotation if log rotation is used. Thanks Nabil Redmann. --- diff --git a/README.txt b/README.txt index 182f885..2158741 100644 --- a/README.txt +++ b/README.txt @@ -574,6 +574,12 @@ If the application crashes and is restarted by NSSM, the order might be: Exit/Post +If NSSM is redirecting stdout or stderr it can be configured to redirect +the output of any hooks it runs. Set AppRedirectHooks to 1 to enable +that functionality. A hook can of course redirect its own I/O independently +of NSSM. + + Managing services using the GUI ------------------------------- NSSM can edit the settings of existing services with the same GUI that is @@ -892,6 +898,7 @@ Thanks to Stefan and Michael Scherer for reporting a bug writing the event messa Thanks to Paul Baxter for help with Visual Studio 2015. Thanks to Mathias Breiner for help with Visual Studio and some registry fixes. Thanks to David Bremner for general tidyups. +Thanks to Nabil Redmann for suggesting redirecting hooks' output. Licence ------- diff --git a/gui.cpp b/gui.cpp index 79000a1..148314c 100644 --- a/gui.cpp +++ b/gui.cpp @@ -182,6 +182,9 @@ int nssm_gui(int resource, nssm_service_t *service) { SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS, service->rotate_seconds, 0); if (! service->rotate_bytes_high) SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, service->rotate_bytes_low, 0); + /* Hooks tab. */ + if (service->hook_share_output_handles) SendDlgItemMessage(tablist[NSSM_TAB_HOOKS], IDC_REDIRECT_HOOK, BM_SETCHECK, BST_CHECKED, 0); + /* Check if advanced settings are in use. */ if (service->stdout_disposition != service->stderr_disposition || (service->stdout_disposition && service->stdout_disposition != NSSM_STDOUT_DISPOSITION && service->stdout_disposition != CREATE_ALWAYS) || (service->stderr_disposition && service->stderr_disposition != NSSM_STDERR_DISPOSITION && service->stderr_disposition != CREATE_ALWAYS)) popup_message(dlg, MB_OK | MB_ICONWARNING, NSSM_GUI_WARN_STDIO); if (service->rotate_bytes_high) popup_message(dlg, MB_OK | MB_ICONWARNING, NSSM_GUI_WARN_ROTATE_BYTES); @@ -673,6 +676,9 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service check_number(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, &service->rotate_bytes_low); } + /* Get hook stuff. */ + if (SendDlgItemMessage(tablist[NSSM_TAB_HOOKS], IDC_REDIRECT_HOOK, BM_GETCHECK, 0, 0) & BST_CHECKED) service->hook_share_output_handles = true; + /* Get environment. */ unsigned long envlen = (unsigned long) SendMessage(GetDlgItem(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT), WM_GETTEXTLENGTH, 0, 0); if (envlen) { @@ -1257,6 +1263,7 @@ INT_PTR CALLBACK nssm_dlg(HWND window, UINT message, WPARAM w, LPARAM l) { SendMessage(combo, CB_INSERTSTRING, -1, (LPARAM) message_string(NSSM_GUI_HOOK_EVENT_EXIT)); SendMessage(combo, CB_INSERTSTRING, -1, (LPARAM) message_string(NSSM_GUI_HOOK_EVENT_POWER)); SendMessage(combo, CB_INSERTSTRING, -1, (LPARAM) message_string(NSSM_GUI_HOOK_EVENT_ROTATE)); + SendDlgItemMessage(tablist[NSSM_TAB_HOOKS], IDC_REDIRECT_HOOK, BM_SETCHECK, BST_UNCHECKED, 0); if (_tcslen(service->name)) { TCHAR hook_name[HOOK_NAME_LENGTH]; TCHAR cmd[CMD_LENGTH]; diff --git a/hook.cpp b/hook.cpp index 01e3a50..dc9e02f 100644 --- a/hook.cpp +++ b/hook.cpp @@ -344,12 +344,15 @@ int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_ 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, false, flags, 0, service->dir, &si, &pi)) { + 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; @@ -383,6 +386,7 @@ int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *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. */ diff --git a/io.cpp b/io.cpp index 9d3795c..b6f9d37 100644 --- a/io.cpp +++ b/io.cpp @@ -18,6 +18,11 @@ static int dup_handle(HANDLE source_handle, HANDLE *dest_handle_ptr, TCHAR *sour return dup_handle(source_handle, dest_handle_ptr, source_description, dest_description, DUPLICATE_SAME_ACCESS); } +/* + read_handle: read from application + pipe_handle: stdout of application + write_handle: to file +*/ static HANDLE create_logging_thread(TCHAR *service_name, TCHAR *path, unsigned long sharing, unsigned long disposition, unsigned long flags, HANDLE *read_handle_ptr, HANDLE *pipe_handle_ptr, HANDLE *write_handle_ptr, unsigned long rotate_bytes_low, unsigned long rotate_bytes_high, unsigned long rotate_delay, unsigned long *tid_ptr, unsigned long *rotate_online, bool copy_and_truncate) { *tid_ptr = 0; @@ -300,21 +305,29 @@ int get_output_handles(nssm_service_t *service, STARTUPINFO *si) { if (service->rotate_files) rotate_file(service->name, service->stdout_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, service->stdout_copy_and_truncate); HANDLE stdout_handle = write_to_file(service->stdout_path, service->stdout_sharing, 0, service->stdout_disposition, service->stdout_flags); if (stdout_handle == INVALID_HANDLE_VALUE) return 4; + service->stdout_si = 0; - if (service->rotate_files && service->rotate_stdout_online) { + if (service->use_stdout_pipe) { service->stdout_pipe = si->hStdOutput = 0; - service->stdout_thread = create_logging_thread(service->name, service->stdout_path, service->stdout_sharing, service->stdout_disposition, service->stdout_flags, &service->stdout_pipe, &si->hStdOutput, &stdout_handle, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, &service->stdout_tid, &service->rotate_stdout_online, service->stdout_copy_and_truncate); + service->stdout_thread = create_logging_thread(service->name, service->stdout_path, service->stdout_sharing, service->stdout_disposition, service->stdout_flags, &service->stdout_pipe, &service->stdout_si, &stdout_handle, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, &service->stdout_tid, &service->rotate_stdout_online, service->stdout_copy_and_truncate); if (! service->stdout_thread) { CloseHandle(service->stdout_pipe); - CloseHandle(si->hStdOutput); + CloseHandle(service->stdout_si); } } else service->stdout_thread = 0; if (! service->stdout_thread) { - if (dup_handle(stdout_handle, &si->hStdOutput, NSSM_REG_STDOUT, _T("stdout"), DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) return 4; + if (dup_handle(stdout_handle, &service->stdout_si, NSSM_REG_STDOUT, _T("stdout"), DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) return 4; service->rotate_stdout_online = NSSM_ROTATE_OFFLINE; } + + if (dup_handle(service->stdout_si, &si->hStdOutput, _T("stdout_si"), _T("stdout"))) { + if (service->stdout_thread) { + CloseHandle(service->stdout_thread); + service->stdout_thread = 0; + } + } } /* stderr */ @@ -327,28 +340,37 @@ int get_output_handles(nssm_service_t *service, STARTUPINFO *si) { service->rotate_stderr_online = NSSM_ROTATE_OFFLINE; /* Two handles to the same file will create a race. */ - if (dup_handle(si->hStdOutput, &si->hStdError, _T("stdout"), _T("stderr"))) return 6; + /* XXX: Here we assume that either both or neither handle must be a pipe. */ + if (dup_handle(service->stdout_si, &service->stderr_si, _T("stdout"), _T("stderr"))) return 6; } else { if (service->rotate_files) rotate_file(service->name, service->stderr_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, service->stderr_copy_and_truncate); HANDLE stderr_handle = write_to_file(service->stderr_path, service->stderr_sharing, 0, service->stderr_disposition, service->stderr_flags); if (stderr_handle == INVALID_HANDLE_VALUE) return 7; + service->stderr_si = 0; - if (service->rotate_files && service->rotate_stderr_online) { + if (service->use_stderr_pipe) { service->stderr_pipe = si->hStdError = 0; - service->stderr_thread = create_logging_thread(service->name, service->stderr_path, service->stderr_sharing, service->stderr_disposition, service->stderr_flags, &service->stderr_pipe, &si->hStdError, &stderr_handle, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, &service->stderr_tid, &service->rotate_stderr_online, service->stderr_copy_and_truncate); + service->stderr_thread = create_logging_thread(service->name, service->stderr_path, service->stderr_sharing, service->stderr_disposition, service->stderr_flags, &service->stderr_pipe, &service->stderr_si, &stderr_handle, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, &service->stderr_tid, &service->rotate_stderr_online, service->stderr_copy_and_truncate); if (! service->stderr_thread) { CloseHandle(service->stderr_pipe); - CloseHandle(si->hStdError); + CloseHandle(service->stderr_si); } } else service->stderr_thread = 0; if (! service->stderr_thread) { - if (dup_handle(stderr_handle, &si->hStdError, NSSM_REG_STDERR, _T("stderr"), DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) return 7; + if (dup_handle(stderr_handle, &service->stderr_si, NSSM_REG_STDERR, _T("stderr"), DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) return 7; service->rotate_stderr_online = NSSM_ROTATE_OFFLINE; } } + + if (dup_handle(service->stderr_si, &si->hStdError, _T("stderr_si"), _T("stderr"))) { + if (service->stderr_thread) { + CloseHandle(service->stderr_thread); + service->stderr_thread = 0; + } + } } /* @@ -368,7 +390,29 @@ int get_output_handles(nssm_service_t *service, STARTUPINFO *si) { } if (! si->hStdError) { if (dup_handle(GetStdHandle(STD_ERROR_HANDLE), &si->hStdError, _T("STD_ERROR_HANDLE"), _T("stderr"))) return 10; + } + + return 0; +} + +/* Reuse output handles for a hook. */ +int use_output_handles(nssm_service_t *service, STARTUPINFO *si) { + si->dwFlags &= ~STARTF_USESTDHANDLES; + + if (service->stdout_si) { + if (dup_handle(service->stdout_si, &si->hStdOutput, _T("stdout_pipe"), _T("hStdOutput"))) return 1; + si->dwFlags |= STARTF_USESTDHANDLES; + } + + if (service->stderr_si) { + if (dup_handle(service->stderr_si, &si->hStdError, _T("stderr_pipe"), _T("hStdError"))) { + if (si->hStdOutput) { + si->dwFlags &= ~STARTF_USESTDHANDLES; + CloseHandle(si->hStdOutput); + } + return 2; } + si->dwFlags |= STARTF_USESTDHANDLES; } return 0; diff --git a/io.h b/io.h index 41c0cdb..2bb6782 100644 --- a/io.h +++ b/io.h @@ -32,6 +32,7 @@ int delete_createfile_parameter(HKEY, TCHAR *, TCHAR *); HANDLE write_to_file(TCHAR *, unsigned long, SECURITY_ATTRIBUTES *, unsigned long, unsigned long); void rotate_file(TCHAR *, TCHAR *, unsigned long, unsigned long, unsigned long, unsigned long, bool); int get_output_handles(nssm_service_t *, STARTUPINFO *); +int use_output_handles(nssm_service_t *, STARTUPINFO *); void close_output_handles(STARTUPINFO *); unsigned long WINAPI log_and_rotate(void *); diff --git a/nssm.rc b/nssm.rc index 7104cca..d9e4351 100644 Binary files a/nssm.rc and b/nssm.rc differ diff --git a/registry.cpp b/registry.cpp index 258b96a..d20b2c2 100644 --- a/registry.cpp +++ b/registry.cpp @@ -158,6 +158,8 @@ int create_parameters(nssm_service_t *service, bool editing) { if (service->stderr_copy_and_truncate) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_COPY_AND_TRUNCATE, 1); else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_COPY_AND_TRUNCATE); } + if (service->hook_share_output_handles) set_number(key, NSSM_REG_HOOK_SHARE_OUTPUT_HANDLES, 1); + else if (editing) RegDeleteValue(key, NSSM_REG_HOOK_SHARE_OUTPUT_HANDLES); if (service->rotate_files) set_number(key, NSSM_REG_ROTATE, 1); else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE); if (service->rotate_stdout_online) set_number(key, NSSM_REG_ROTATE_ONLINE, 1); @@ -638,6 +640,13 @@ int get_parameters(nssm_service_t *service, STARTUPINFO *si) { 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) { @@ -650,6 +659,9 @@ int get_parameters(nssm_service_t *service, STARTUPINFO *si) { else service->rotate_stdout_online = service->rotate_stderr_online = false; } else service->rotate_stdout_online = service->rotate_stderr_online = false; + /* Hook I/O sharing and online rotation need a pipe. */ + service->use_stdout_pipe = service->rotate_stdout_online || hook_share_output_handles; + service->use_stderr_pipe = service->rotate_stderr_online || 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; diff --git a/registry.h b/registry.h index 0580aff..d8018ac 100644 --- a/registry.h +++ b/registry.h @@ -25,6 +25,7 @@ #define NSSM_REG_STDIO_DISPOSITION _T("CreationDisposition") #define NSSM_REG_STDIO_FLAGS _T("FlagsAndAttributes") #define NSSM_REG_STDIO_COPY_AND_TRUNCATE _T("CopyAndTruncate") +#define NSSM_REG_HOOK_SHARE_OUTPUT_HANDLES _T("AppRedirectHook") #define NSSM_REG_ROTATE _T("AppRotateFiles") #define NSSM_REG_ROTATE_ONLINE _T("AppRotateOnline") #define NSSM_REG_ROTATE_SECONDS _T("AppRotateSeconds") diff --git a/resource.h b/resource.h index 441ad3a..df5c6a3 100644 --- a/resource.h +++ b/resource.h @@ -70,6 +70,7 @@ #define IDC_HOOK_ACTION 1049 #define IDC_HOOK 1050 #define IDC_BROWSE_HOOK 1051 +#define IDC_REDIRECT_HOOK 1052 // Next default values for new objects // @@ -77,7 +78,7 @@ #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 117 #define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1052 +#define _APS_NEXT_CONTROL_VALUE 1053 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif diff --git a/service.cpp b/service.cpp index bc3a471..164fbc0 100644 --- a/service.cpp +++ b/service.cpp @@ -1721,15 +1721,7 @@ int start_service(nssm_service_t *service) { service->status.dwControlsAccepted = SERVICE_ACCEPT_POWEREVENT | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP; SetServiceStatus(service->status_handle, &service->status); - /* Pre-start hook. */ unsigned long control = NSSM_SERVICE_CONTROL_START; - if (nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_PRE, &control, NSSM_SERVICE_STATUS_DEADLINE, false) == NSSM_HOOK_STATUS_ABORT) { - TCHAR code[16]; - _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), NSSM_HOOK_STATUS_ABORT); - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PRESTART_HOOK_ABORT, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_PRE, service->name, code, 0); - unset_service_environment(service); - return stop_service(service, 5, true, true); - } /* Did another thread receive a stop control? */ if (service->allow_restart) { @@ -1742,6 +1734,15 @@ int start_service(nssm_service_t *service) { return stop_service(service, 4, true, true); } + /* Pre-start hook. May need I/O to have been redirected already. */ + if (nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_PRE, &control, NSSM_SERVICE_STATUS_DEADLINE, false) == NSSM_HOOK_STATUS_ABORT) { + TCHAR code[16]; + _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), NSSM_HOOK_STATUS_ABORT); + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PRESTART_HOOK_ABORT, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_PRE, service->name, code, 0); + unset_service_environment(service); + return stop_service(service, 5, true, true); + } + /* The pre-start hook will have cleaned the environment. */ set_service_environment(service); diff --git a/service.h b/service.h index 3a80751..1a1b263 100644 --- a/service.h +++ b/service.h @@ -61,6 +61,8 @@ typedef struct { unsigned long stdout_sharing; unsigned long stdout_disposition; unsigned long stdout_flags; + bool use_stdout_pipe; + HANDLE stdout_si; HANDLE stdout_pipe; HANDLE stdout_thread; unsigned long stdout_tid; @@ -68,9 +70,12 @@ typedef struct { unsigned long stderr_sharing; unsigned long stderr_disposition; unsigned long stderr_flags; + bool use_stderr_pipe; + HANDLE stderr_si; HANDLE stderr_pipe; HANDLE stderr_thread; unsigned long stderr_tid; + bool hook_share_output_handles; bool rotate_files; bool stdout_copy_and_truncate; bool stderr_copy_and_truncate; diff --git a/settings.cpp b/settings.cpp index 45e0a58..7d08e13 100644 --- a/settings.cpp +++ b/settings.cpp @@ -1110,6 +1110,7 @@ settings_t settings[] = { { NSSM_REG_KILL_THREADS_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_THREADS_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number }, { NSSM_REG_KILL_PROCESS_TREE, REG_DWORD, (void *) 1, false, 0, setting_set_number, setting_get_number }, { NSSM_REG_THROTTLE, REG_DWORD, (void *) NSSM_RESET_THROTTLE_RESTART, false, 0, setting_set_number, setting_get_number }, + { NSSM_REG_HOOK_SHARE_OUTPUT_HANDLES, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, { NSSM_REG_ROTATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, { NSSM_REG_ROTATE_ONLINE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number }, { NSSM_REG_ROTATE_SECONDS, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },