Exit/Post\r
\r
\r
+If NSSM is redirecting stdout or stderr it can be configured to redirect\r
+the output of any hooks it runs. Set AppRedirectHooks to 1 to enable\r
+that functionality. A hook can of course redirect its own I/O independently\r
+of NSSM.\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
Thanks to Paul Baxter for help with Visual Studio 2015.\r
Thanks to Mathias Breiner for help with Visual Studio and some registry fixes.\r
Thanks to David Bremner for general tidyups.\r
+Thanks to Nabil Redmann for suggesting redirecting hooks' output.\r
\r
Licence\r
-------\r
SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS, service->rotate_seconds, 0);\r
if (! service->rotate_bytes_high) SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, service->rotate_bytes_low, 0);\r
\r
+ /* Hooks tab. */\r
+ if (service->hook_share_output_handles) SendDlgItemMessage(tablist[NSSM_TAB_HOOKS], IDC_REDIRECT_HOOK, BM_SETCHECK, BST_CHECKED, 0);\r
+\r
/* Check if advanced settings are in use. */\r
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);\r
if (service->rotate_bytes_high) popup_message(dlg, MB_OK | MB_ICONWARNING, NSSM_GUI_WARN_ROTATE_BYTES);\r
check_number(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, &service->rotate_bytes_low);\r
}\r
\r
+ /* Get hook stuff. */\r
+ if (SendDlgItemMessage(tablist[NSSM_TAB_HOOKS], IDC_REDIRECT_HOOK, BM_GETCHECK, 0, 0) & BST_CHECKED) service->hook_share_output_handles = true;\r
+\r
/* Get environment. */\r
unsigned long envlen = (unsigned long) SendMessage(GetDlgItem(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT), WM_GETTEXTLENGTH, 0, 0);\r
if (envlen) {\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
+ SendDlgItemMessage(tablist[NSSM_TAB_HOOKS], IDC_REDIRECT_HOOK, BM_SETCHECK, BST_UNCHECKED, 0);\r
if (_tcslen(service->name)) {\r
TCHAR hook_name[HOOK_NAME_LENGTH];\r
TCHAR cmd[CMD_LENGTH];\r
si.cb = sizeof(si);\r
PROCESS_INFORMATION pi;\r
ZeroMemory(&pi, sizeof(pi));\r
+ if (service->hook_share_output_handles) (void) use_output_handles(service, &si);\r
+ bool inherit_handles = false;\r
+ if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
unsigned long flags = 0;\r
#ifdef UNICODE\r
flags |= CREATE_UNICODE_ENVIRONMENT;\r
#endif\r
ret = NSSM_HOOK_STATUS_NOTRUN;\r
- if (CreateProcess(0, cmd, 0, 0, false, flags, 0, service->dir, &si, &pi)) {\r
+ if (CreateProcess(0, cmd, 0, 0, inherit_handles, flags, 0, service->dir, &si, &pi)) {\r
hook->name = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, HOOK_NAME_LENGTH * sizeof(TCHAR));\r
if (hook->name) _sntprintf_s(hook->name, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s (%s/%s)"), service->name, hook_event, hook_action);\r
hook->process_handle = pi.hProcess;\r
else {\r
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_HOOK_CREATEPROCESS_FAILED, hook_event, hook_action, service->name, cmd, error_string(GetLastError()), 0);\r
HeapFree(GetProcessHeap(), 0, hook);\r
+ close_output_handles(&si);\r
}\r
\r
/* Restore our environment. */\r
return dup_handle(source_handle, dest_handle_ptr, source_description, dest_description, DUPLICATE_SAME_ACCESS);\r
}\r
\r
+/*\r
+ read_handle: read from application\r
+ pipe_handle: stdout of application\r
+ write_handle: to file\r
+*/\r
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) {\r
*tid_ptr = 0;\r
\r
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);\r
HANDLE stdout_handle = write_to_file(service->stdout_path, service->stdout_sharing, 0, service->stdout_disposition, service->stdout_flags);\r
if (stdout_handle == INVALID_HANDLE_VALUE) return 4;\r
+ service->stdout_si = 0;\r
\r
- if (service->rotate_files && service->rotate_stdout_online) {\r
+ if (service->use_stdout_pipe) {\r
service->stdout_pipe = si->hStdOutput = 0;\r
- 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);\r
+ 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);\r
if (! service->stdout_thread) {\r
CloseHandle(service->stdout_pipe);\r
- CloseHandle(si->hStdOutput);\r
+ CloseHandle(service->stdout_si);\r
}\r
}\r
else service->stdout_thread = 0;\r
\r
if (! service->stdout_thread) {\r
- if (dup_handle(stdout_handle, &si->hStdOutput, NSSM_REG_STDOUT, _T("stdout"), DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) return 4;\r
+ if (dup_handle(stdout_handle, &service->stdout_si, NSSM_REG_STDOUT, _T("stdout"), DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) return 4;\r
service->rotate_stdout_online = NSSM_ROTATE_OFFLINE;\r
}\r
+\r
+ if (dup_handle(service->stdout_si, &si->hStdOutput, _T("stdout_si"), _T("stdout"))) {\r
+ if (service->stdout_thread) {\r
+ CloseHandle(service->stdout_thread);\r
+ service->stdout_thread = 0;\r
+ }\r
+ }\r
}\r
\r
/* stderr */\r
service->rotate_stderr_online = NSSM_ROTATE_OFFLINE;\r
\r
/* Two handles to the same file will create a race. */\r
- if (dup_handle(si->hStdOutput, &si->hStdError, _T("stdout"), _T("stderr"))) return 6;\r
+ /* XXX: Here we assume that either both or neither handle must be a pipe. */\r
+ if (dup_handle(service->stdout_si, &service->stderr_si, _T("stdout"), _T("stderr"))) return 6;\r
}\r
else {\r
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);\r
HANDLE stderr_handle = write_to_file(service->stderr_path, service->stderr_sharing, 0, service->stderr_disposition, service->stderr_flags);\r
if (stderr_handle == INVALID_HANDLE_VALUE) return 7;\r
+ service->stderr_si = 0;\r
\r
- if (service->rotate_files && service->rotate_stderr_online) {\r
+ if (service->use_stderr_pipe) {\r
service->stderr_pipe = si->hStdError = 0;\r
- 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);\r
+ 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);\r
if (! service->stderr_thread) {\r
CloseHandle(service->stderr_pipe);\r
- CloseHandle(si->hStdError);\r
+ CloseHandle(service->stderr_si);\r
}\r
}\r
else service->stderr_thread = 0;\r
\r
if (! service->stderr_thread) {\r
- if (dup_handle(stderr_handle, &si->hStdError, NSSM_REG_STDERR, _T("stderr"), DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) return 7;\r
+ if (dup_handle(stderr_handle, &service->stderr_si, NSSM_REG_STDERR, _T("stderr"), DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) return 7;\r
service->rotate_stderr_online = NSSM_ROTATE_OFFLINE;\r
}\r
}\r
+\r
+ if (dup_handle(service->stderr_si, &si->hStdError, _T("stderr_si"), _T("stderr"))) {\r
+ if (service->stderr_thread) {\r
+ CloseHandle(service->stderr_thread);\r
+ service->stderr_thread = 0;\r
+ }\r
+ }\r
}\r
\r
/*\r
}\r
if (! si->hStdError) {\r
if (dup_handle(GetStdHandle(STD_ERROR_HANDLE), &si->hStdError, _T("STD_ERROR_HANDLE"), _T("stderr"))) return 10;\r
+ }\r
+\r
+ return 0;\r
+}\r
+\r
+/* Reuse output handles for a hook. */\r
+int use_output_handles(nssm_service_t *service, STARTUPINFO *si) {\r
+ si->dwFlags &= ~STARTF_USESTDHANDLES;\r
+\r
+ if (service->stdout_si) {\r
+ if (dup_handle(service->stdout_si, &si->hStdOutput, _T("stdout_pipe"), _T("hStdOutput"))) return 1;\r
+ si->dwFlags |= STARTF_USESTDHANDLES;\r
+ }\r
+\r
+ if (service->stderr_si) {\r
+ if (dup_handle(service->stderr_si, &si->hStdError, _T("stderr_pipe"), _T("hStdError"))) {\r
+ if (si->hStdOutput) {\r
+ si->dwFlags &= ~STARTF_USESTDHANDLES;\r
+ CloseHandle(si->hStdOutput);\r
+ }\r
+ return 2;\r
}\r
+ si->dwFlags |= STARTF_USESTDHANDLES;\r
}\r
\r
return 0;\r
HANDLE write_to_file(TCHAR *, unsigned long, SECURITY_ATTRIBUTES *, unsigned long, unsigned long);\r
void rotate_file(TCHAR *, TCHAR *, unsigned long, unsigned long, unsigned long, unsigned long, bool);\r
int get_output_handles(nssm_service_t *, STARTUPINFO *);\r
+int use_output_handles(nssm_service_t *, STARTUPINFO *);\r
void close_output_handles(STARTUPINFO *);\r
unsigned long WINAPI log_and_rotate(void *);\r
\r
if (service->stderr_copy_and_truncate) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_COPY_AND_TRUNCATE, 1);\r
else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_COPY_AND_TRUNCATE);\r
}\r
+ if (service->hook_share_output_handles) set_number(key, NSSM_REG_HOOK_SHARE_OUTPUT_HANDLES, 1);\r
+ else if (editing) RegDeleteValue(key, NSSM_REG_HOOK_SHARE_OUTPUT_HANDLES);\r
if (service->rotate_files) set_number(key, NSSM_REG_ROTATE, 1);\r
else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE);\r
if (service->rotate_stdout_online) set_number(key, NSSM_REG_ROTATE_ONLINE, 1);\r
else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_PRIORITY, service->name, NSSM_REG_PRIORITY, 0);\r
}\r
\r
+ /* Try to get hook I/O sharing - may fail. */\r
+ unsigned long hook_share_output_handles;\r
+ if (get_number(key, NSSM_REG_HOOK_SHARE_OUTPUT_HANDLES, &hook_share_output_handles, false) == 1) {\r
+ if (hook_share_output_handles) service->hook_share_output_handles = true;\r
+ else service->hook_share_output_handles = false;\r
+ }\r
+ else hook_share_output_handles = false;\r
/* Try to get file rotation settings - may fail. */\r
unsigned long rotate_files;\r
if (get_number(key, NSSM_REG_ROTATE, &rotate_files, false) == 1) {\r
else service->rotate_stdout_online = service->rotate_stderr_online = false;\r
}\r
else service->rotate_stdout_online = service->rotate_stderr_online = false;\r
+ /* Hook I/O sharing and online rotation need a pipe. */\r
+ service->use_stdout_pipe = service->rotate_stdout_online || hook_share_output_handles;\r
+ service->use_stderr_pipe = service->rotate_stderr_online || hook_share_output_handles;\r
if (get_number(key, NSSM_REG_ROTATE_SECONDS, &service->rotate_seconds, false) != 1) service->rotate_seconds = 0;\r
if (get_number(key, NSSM_REG_ROTATE_BYTES_LOW, &service->rotate_bytes_low, false) != 1) service->rotate_bytes_low = 0;\r
if (get_number(key, NSSM_REG_ROTATE_BYTES_HIGH, &service->rotate_bytes_high, false) != 1) service->rotate_bytes_high = 0;\r
#define NSSM_REG_STDIO_DISPOSITION _T("CreationDisposition")\r
#define NSSM_REG_STDIO_FLAGS _T("FlagsAndAttributes")\r
#define NSSM_REG_STDIO_COPY_AND_TRUNCATE _T("CopyAndTruncate")\r
+#define NSSM_REG_HOOK_SHARE_OUTPUT_HANDLES _T("AppRedirectHook")\r
#define NSSM_REG_ROTATE _T("AppRotateFiles")\r
#define NSSM_REG_ROTATE_ONLINE _T("AppRotateOnline")\r
#define NSSM_REG_ROTATE_SECONDS _T("AppRotateSeconds")\r
#define IDC_HOOK_ACTION 1049\r
#define IDC_HOOK 1050\r
#define IDC_BROWSE_HOOK 1051\r
+#define IDC_REDIRECT_HOOK 1052\r
\r
// Next default values for new objects\r
// \r
#ifndef APSTUDIO_READONLY_SYMBOLS\r
#define _APS_NEXT_RESOURCE_VALUE 117\r
#define _APS_NEXT_COMMAND_VALUE 40001\r
-#define _APS_NEXT_CONTROL_VALUE 1052\r
+#define _APS_NEXT_CONTROL_VALUE 1053\r
#define _APS_NEXT_SYMED_VALUE 101\r
#endif\r
#endif\r
service->status.dwControlsAccepted = SERVICE_ACCEPT_POWEREVENT | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;\r
SetServiceStatus(service->status_handle, &service->status);\r
\r
- /* Pre-start hook. */\r
unsigned long control = NSSM_SERVICE_CONTROL_START;\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
- unset_service_environment(service);\r
- return stop_service(service, 5, true, true);\r
- }\r
\r
/* Did another thread receive a stop control? */\r
if (service->allow_restart) {\r
return stop_service(service, 4, true, true);\r
}\r
\r
+ /* Pre-start hook. May need I/O to have been redirected already. */\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
+ unset_service_environment(service);\r
+ return stop_service(service, 5, true, true);\r
+ }\r
+\r
/* The pre-start hook will have cleaned the environment. */\r
set_service_environment(service);\r
\r
unsigned long stdout_sharing;\r
unsigned long stdout_disposition;\r
unsigned long stdout_flags;\r
+ bool use_stdout_pipe;\r
+ HANDLE stdout_si;\r
HANDLE stdout_pipe;\r
HANDLE stdout_thread;\r
unsigned long stdout_tid;\r
unsigned long stderr_sharing;\r
unsigned long stderr_disposition;\r
unsigned long stderr_flags;\r
+ bool use_stderr_pipe;\r
+ HANDLE stderr_si;\r
HANDLE stderr_pipe;\r
HANDLE stderr_thread;\r
unsigned long stderr_tid;\r
+ bool hook_share_output_handles;\r
bool rotate_files;\r
bool stdout_copy_and_truncate;\r
bool stderr_copy_and_truncate;\r
{ NSSM_REG_KILL_THREADS_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_THREADS_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number },\r
{ NSSM_REG_KILL_PROCESS_TREE, REG_DWORD, (void *) 1, false, 0, setting_set_number, setting_get_number },\r
{ NSSM_REG_THROTTLE, REG_DWORD, (void *) NSSM_RESET_THROTTLE_RESTART, false, 0, setting_set_number, setting_get_number },\r
+ { NSSM_REG_HOOK_SHARE_OUTPUT_HANDLES, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },\r
{ NSSM_REG_ROTATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },\r
{ NSSM_REG_ROTATE_ONLINE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },\r
{ NSSM_REG_ROTATE_SECONDS, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },\r