Browse Source

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.
Iain Patterson 5 years ago
parent
commit
6adc886e1f
12 changed files with 103 additions and 19 deletions
  1. 7 0
      README.txt
  2. 7 0
      gui.cpp
  3. 5 1
      hook.cpp
  4. 53 9
      io.cpp
  5. 1 0
      io.h
  6. BIN
      nssm.rc
  7. 12 0
      registry.cpp
  8. 1 0
      registry.h
  9. 2 1
      resource.h
  10. 9 8
      service.cpp
  11. 5 0
      service.h
  12. 1 0
      settings.cpp

+ 7 - 0
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
 -------

+ 7 - 0
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];

+ 5 - 1
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. */

+ 53 - 9
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;

+ 1 - 0
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 *);
 

BIN
nssm.rc


+ 12 - 0
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;

+ 1 - 0
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")

+ 2 - 1
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

+ 9 - 8
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);
 

+ 5 - 0
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;

+ 1 - 0
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 },