Browse Source

Rotate files while the service is running.

If AppRotateOnline is set, set up a pipe between the application's
stdout/stderr and a new thread which reads all input and writes to the
configured output file(s).

If a file breaches the configured AppRotateBytes threshold while the
service is running, close and rotate the file then resume logging to
a new file.

This behaviour is not the default due to the potential for things to go
wrong.  We have to juggle file handles and might hit a read or write
error which causes logging to fail thus losing all output until the
service is restarted, etc etc.

Thanks Doug Watson.
Iain Patterson 10 years ago
parent
commit
143238dfe3
12 changed files with 305 additions and 31 deletions
  1. 9 0
      README.txt
  2. 5 0
      gui.cpp
  3. 243 15
      io.cpp
  4. 14 0
      io.h
  5. BIN
      messages.mc
  6. BIN
      nssm.rc
  7. 7 0
      registry.cpp
  8. 1 0
      registry.h
  9. 16 15
      resource.h
  10. 1 1
      service.cpp
  11. 8 0
      service.h
  12. 1 0
      settings.cpp

+ 9 - 0
README.txt

@@ -314,6 +314,15 @@ Rotation is independent of the CreateFile() parameters used to open the files.
 They will be rotated regardless of whether NSSM would otherwise have appended
 or replaced them.
 
+NSSM can also rotate files which hit the configured size threshold while the
+service is running.  To enable this feature, set AppRotateOnline to a non-zero
+value.
+
+Note that online rotation requires NSSM to intercept the application's I/O
+and create the output files on its behalf.  This is more complex and
+error-prone than simply redirecting the I/O streams before launching the
+application.  Therefore online rotation is not enabled by default.
+
 
 Environment variables
 ---------------------

+ 5 - 0
gui.cpp

@@ -152,9 +152,11 @@ int nssm_gui(int resource, nssm_service_t *service) {
     if (service->stdout_disposition == CREATE_ALWAYS) SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_TRUNCATE, BM_SETCHECK, BST_CHECKED, 0);
     if (service->rotate_files) {
       SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE, BM_SETCHECK, BST_CHECKED, 0);
+      EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_ONLINE), 1);
       EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS), 1);
       EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW), 1);
     }
+    if (service->rotate_stdout_online || service->rotate_stderr_online) SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_ONLINE, BM_SETCHECK, BST_CHECKED, 0);
     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);
 
@@ -252,6 +254,7 @@ static inline void set_affinity_enabled(unsigned char enabled) {
 }
 
 static inline void set_rotation_enabled(unsigned char enabled) {
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_ONLINE), enabled);
   EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS), enabled);
   EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW), enabled);
 }
@@ -505,6 +508,7 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service
   /* Get rotation stuff. */
   if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE, BM_GETCHECK, 0, 0) & BST_CHECKED) {
     service->rotate_files = true;
+    if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_ONLINE, BM_GETCHECK, 0, 0) & BST_CHECKED) service->rotate_stdout_online = service->rotate_stderr_online = true;
     check_number(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS, &service->rotate_seconds);
     check_number(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, &service->rotate_bytes_low);
   }
@@ -1027,6 +1031,7 @@ INT_PTR CALLBACK nssm_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {
       ShowWindow(tablist[NSSM_TAB_ROTATION], SW_HIDE);
 
       /* Set defaults. */
+      SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_ONLINE, BM_SETCHECK, BST_UNCHECKED, 0);
       SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS, 0, 0);
       SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, 0, 0);
       set_rotation_enabled(0);

+ 243 - 15
io.cpp

@@ -1,5 +1,19 @@
 #include "nssm.h"
 
+static HANDLE create_logging_thread(logger_t *logger) {
+  HANDLE thread_handle = CreateThread(NULL, 0, log_and_rotate, (void *) logger, 0, logger->tid_ptr);
+  if (! thread_handle) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
+  return thread_handle;
+}
+
+static inline void write_bom(logger_t *logger) {
+  wchar_t bom = L'\ufeff';
+  unsigned long out;
+  if (! WriteFile(logger->write_handle, (void *) &bom, sizeof(bom), &out, 0)) {
+    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_SOMEBODY_SET_UP_US_THE_BOM, logger->service_name, logger->path, error_string(GetLastError()), 0);
+  }
+}
+
 /* Get path, share mode, creation disposition and flags for a stream. */
 int get_createfile_parameters(HKEY key, TCHAR *prefix, TCHAR *path, unsigned long *sharing, unsigned long default_sharing, unsigned long *disposition, unsigned long default_disposition, unsigned long *flags, unsigned long default_flags) {
   TCHAR value[NSSM_STDIO_LENGTH];
@@ -93,6 +107,22 @@ HANDLE append_to_file(TCHAR *path, unsigned long sharing, SECURITY_ATTRIBUTES *a
   return CreateFile(path, FILE_WRITE_DATA, sharing, attributes, disposition, flags, 0);
 }
 
+static void rotated_filename(TCHAR *path, TCHAR *rotated, unsigned long rotated_len, SYSTEMTIME *st) {
+  if (! st) {
+    SYSTEMTIME now;
+    st = &now;
+    GetSystemTime(st);
+  }
+
+  TCHAR buffer[MAX_PATH];
+  memmove(buffer, path, sizeof(buffer));
+  TCHAR *ext = PathFindExtension(buffer);
+  TCHAR extension[MAX_PATH];
+  _sntprintf_s(extension, _countof(extension), _TRUNCATE, _T("-%04u%02u%02uT%02u%02u%02u.%03u%s"), st->wYear, st->wMonth, st->wDay, st->wHour, st->wMinute, st->wSecond, st->wMilliseconds, ext);
+  *ext = _T('\0');
+  _sntprintf_s(rotated, rotated_len, _TRUNCATE, _T("%s%s"), buffer, extension);
+}
+
 void rotate_file(TCHAR *service_name, TCHAR *path, unsigned long seconds, unsigned long low, unsigned long high) {
   unsigned long error;
 
@@ -146,14 +176,8 @@ void rotate_file(TCHAR *service_name, TCHAR *path, unsigned long seconds, unsign
   /* Get new filename. */
   FileTimeToSystemTime(&info.ftLastWriteTime, &st);
 
-  TCHAR buffer[MAX_PATH];
-  memmove(buffer, path, sizeof(buffer));
-  TCHAR *ext = PathFindExtension(buffer);
-  TCHAR extension[MAX_PATH];
-  _sntprintf_s(extension, _countof(extension), _TRUNCATE, _T("-%04u%02u%02uT%02u%02u%02u.%03u%s"), st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, ext);
-  *ext = _T('\0');
   TCHAR rotated[MAX_PATH];
-  _sntprintf_s(rotated, _countof(rotated), _TRUNCATE, _T("%s%s"), buffer, extension);
+  rotated_filename(path, rotated, _countof(rotated), &st);
 
   /* Rotate. */
   if (MoveFile(path, rotated)) return;
@@ -186,6 +210,10 @@ int get_output_handles(nssm_service_t *service, HKEY key, STARTUPINFO *si) {
     set_flags = true;
   }
 
+  ULARGE_INTEGER size;
+  size.LowPart = service->rotate_bytes_low;
+  size.HighPart = service->rotate_bytes_high;
+
   /* stdout */
   if (get_createfile_parameters(key, NSSM_REG_STDOUT, service->stdout_path, &service->stdout_sharing, NSSM_STDOUT_SHARING, &service->stdout_disposition, NSSM_STDOUT_DISPOSITION, &service->stdout_flags, NSSM_STDOUT_FLAGS)) {
     service->stdout_sharing = service->stdout_disposition = service->stdout_flags = 0;
@@ -194,8 +222,58 @@ int get_output_handles(nssm_service_t *service, HKEY key, STARTUPINFO *si) {
   }
   if (si && service->stdout_path[0]) {
     if (service->rotate_files) rotate_file(service->name, service->stdout_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high);
-    si->hStdOutput = append_to_file(service->stdout_path, service->stdout_sharing, &attributes, service->stdout_disposition, service->stdout_flags);
-    if (! si->hStdOutput) return 4;
+    HANDLE stdout_handle = append_to_file(service->stdout_path, service->stdout_sharing, 0, service->stdout_disposition, service->stdout_flags);
+    if (! stdout_handle) {
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, service->stdout_path, error_string(GetLastError()), 0);
+      return 4;
+    }
+
+    /* Try online rotation only if a size threshold is set. */
+    logger_t *stdout_logger = 0;
+    if (service->rotate_files && service->rotate_stdout_online && size.QuadPart) {
+      stdout_logger = (logger_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(logger_t));
+      if (stdout_logger) {
+        /* Pipe between application's stdout and our logging handle. */
+        if (CreatePipe(&service->stdout_pipe, &si->hStdOutput, &attributes, 0)) {
+          stdout_logger->service_name = service->name;
+          stdout_logger->path = service->stdout_path;
+          stdout_logger->sharing = service->stdout_sharing;
+          stdout_logger->disposition = service->stdout_disposition;
+          stdout_logger->flags = service->stdout_flags;
+          stdout_logger->read_handle = service->stdout_pipe;
+          stdout_logger->write_handle = stdout_handle;
+          stdout_logger->size = (__int64) size.QuadPart;
+          stdout_logger->tid_ptr = &service->stdout_tid;
+
+          /* Logging thread. */
+          service->stdout_thread = create_logging_thread(stdout_logger);
+          if (! service->stdout_thread) {
+            log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPIPE_FAILED, service->name, service->stdout_path, error_string(GetLastError()));
+            CloseHandle(service->stdout_pipe);
+            CloseHandle(si->hStdOutput);
+            service->stdout_tid = 0;
+          }
+        }
+        else service->stdout_tid = 0;
+      }
+      else {
+        log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("stdout_logger"), _T("get_output_handles()"), 0);
+        service->stdout_tid = 0;
+      }
+
+      /* Fall through to try direct I/O. */
+      if (! service->stdout_tid) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPIPE_FAILED, service->name, service->stdout_path, error_string(GetLastError()));
+    }
+
+    if (! service->stdout_tid) {
+      if (stdout_logger) HeapFree(GetProcessHeap(), 0, stdout_logger);
+      if (! DuplicateHandle(GetCurrentProcess(), stdout_handle, GetCurrentProcess(), &si->hStdOutput, 0, true, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
+        log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DUPLICATEHANDLE_FAILED, NSSM_REG_STDOUT, error_string(GetLastError()), 0);
+        return 4;
+      }
+      service->rotate_stdout_online = false;
+    }
+
     set_flags = true;
   }
 
@@ -211,6 +289,7 @@ int get_output_handles(nssm_service_t *service, HKEY key, STARTUPINFO *si) {
       service->stderr_sharing = service->stdout_sharing;
       service->stderr_disposition = service->stdout_disposition;
       service->stderr_flags = service->stdout_flags;
+      service->rotate_stderr_online = false;
 
       if (si) {
         /* Two handles to the same file will create a race. */
@@ -222,13 +301,59 @@ int get_output_handles(nssm_service_t *service, HKEY key, STARTUPINFO *si) {
     }
     else if (si) {
       if (service->rotate_files) rotate_file(service->name, service->stderr_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high);
-      si->hStdError = append_to_file(service->stderr_path, service->stdout_sharing, &attributes, service->stdout_disposition, service->stdout_flags);
-      if (! si->hStdError) {
+      HANDLE stderr_handle = append_to_file(service->stderr_path, service->stdout_sharing, 0, service->stdout_disposition, service->stdout_flags);
+      if (! stderr_handle) {
         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, service->stderr_path, error_string(GetLastError()), 0);
         return 7;
       }
-      SetEndOfFile(si->hStdError);
+
+      /* Try online rotation only if a size threshold is set. */
+      logger_t *stderr_logger = 0;
+      if (service->rotate_files && service->rotate_stderr_online && size.QuadPart) {
+        stderr_logger = (logger_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(logger_t));
+        if (stderr_logger) {
+          /* Pipe between application's stderr and our logging handle. */
+          if (CreatePipe(&service->stderr_pipe, &si->hStdError, &attributes, 0)) {
+            stderr_logger->service_name = service->name;
+            stderr_logger->path = service->stderr_path;
+            stderr_logger->sharing = service->stderr_sharing;
+            stderr_logger->disposition = service->stderr_disposition;
+            stderr_logger->flags = service->stderr_flags;
+            stderr_logger->read_handle = service->stderr_pipe;
+            stderr_logger->write_handle = stderr_handle;
+            stderr_logger->size = (__int64) size.QuadPart;
+            stderr_logger->tid_ptr = &service->stderr_tid;
+
+            /* Logging thread. */
+            service->stderr_thread = create_logging_thread(stderr_logger);
+            if (! service->stderr_thread) {
+              log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPIPE_FAILED, service->name, service->stderr_path, error_string(GetLastError()));
+              CloseHandle(service->stderr_pipe);
+              CloseHandle(si->hStdError);
+              service->stderr_tid = 0;
+            }
+          }
+          else service->stderr_tid = 0;
+        }
+        else {
+          log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("stderr_logger"), _T("get_output_handles()"), 0);
+          service->stderr_tid = 0;
+        }
+
+        /* Fall through to try direct I/O. */
+        if (! service->stderr_tid) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPIPE_FAILED, service->name, service->stderr_path, error_string(GetLastError()));
+      }
+
+      if (! service->stderr_tid) {
+        if (stderr_logger) HeapFree(GetProcessHeap(), 0, stderr_logger);
+        if (! DuplicateHandle(GetCurrentProcess(), stderr_handle, GetCurrentProcess(), &si->hStdError, 0, true, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
+          log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DUPLICATEHANDLE_FAILED, NSSM_REG_STDERR, error_string(GetLastError()), 0);
+          return 7;
+        }
+        service->rotate_stderr_online = false;
+      }
     }
+
     set_flags = true;
   }
 
@@ -243,8 +368,111 @@ int get_output_handles(nssm_service_t *service, HKEY key, STARTUPINFO *si) {
   return 0;
 }
 
-void close_output_handles(STARTUPINFO *si) {
+void close_output_handles(STARTUPINFO *si, bool close_stdout, bool close_stderr) {
   if (si->hStdInput) CloseHandle(si->hStdInput);
-  if (si->hStdOutput) CloseHandle(si->hStdOutput);
-  if (si->hStdError) CloseHandle(si->hStdError);
+  if (si->hStdOutput && close_stdout) CloseHandle(si->hStdOutput);
+  if (si->hStdError && close_stderr) CloseHandle(si->hStdError);
+}
+
+void close_output_handles(STARTUPINFO *si) {
+  return close_output_handles(si, true, true);
+}
+
+/* Wrapper to be called in a new thread for logging. */
+unsigned long WINAPI log_and_rotate(void *arg) {
+  logger_t *logger = (logger_t *) arg;
+  if (! logger) return 1;
+
+  __int64 size;
+  BY_HANDLE_FILE_INFORMATION info;
+
+  /* Find initial file size. */
+  if (! GetFileInformationByHandle(logger->write_handle, &info)) logger->size = 0LL;
+  else {
+    ULARGE_INTEGER l;
+    l.HighPart = info.nFileSizeHigh;
+    l.LowPart = info.nFileSizeLow;
+    size = l.QuadPart;
+  }
+
+  char buffer[80];
+  void *address;
+  unsigned long in, out;
+  while (true) {
+    /* Read data from the pipe. */
+    address = &buffer;
+    if (! ReadFile(logger->read_handle, address, sizeof(buffer), &in, 0)) {
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_READFILE_FAILED, logger->service_name, logger->path, error_string(GetLastError()), 0);
+      return 2;
+    }
+
+    if (size + (__int64) in >= logger->size) {
+      /* Look for newline. */
+      unsigned long i;
+      for (i = 0; i < in; i++) {
+        if (buffer[i] == '\n') {
+          unsigned char unicode = IsTextUnicode(address, sizeof(buffer), 0);
+          if (unicode) i += sizeof(wchar_t);
+          else i += sizeof(char);
+
+          /* Write up to the newline. */
+          if (! WriteFile(logger->write_handle, address, i, &out, 0)) {
+            log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_WRITEFILE_FAILED, logger->service_name, logger->path, error_string(GetLastError()), 0);
+            return 3;
+          }
+
+          /* Rotate. */
+          TCHAR rotated[MAX_PATH];
+          rotated_filename(logger->path, rotated, _countof(rotated), 0);
+
+          /*
+            Ideally we'd try the rename first then close the handle but
+            MoveFile() will fail if the handle is still open so we must
+            risk losing everything.
+          */
+          CloseHandle(logger->write_handle);
+          if (! MoveFile(logger->path, rotated)) {
+            unsigned long error = GetLastError();
+            if (error != ERROR_FILE_NOT_FOUND) {
+              log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, logger->service_name, logger->path, _T("MoveFile()"), rotated, error_string(error), 0);
+              /* We can at least try to re-open the existing file. */
+              logger->disposition = OPEN_ALWAYS;
+            }
+          }
+
+          /* Reopen. */
+          logger->write_handle = append_to_file(logger->path, logger->sharing, 0, logger->disposition, logger->flags);
+          if (! logger->write_handle) {
+            log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, logger->path, error_string(GetLastError()), 0);
+            /* Oh dear.  Now we can't log anything further. */
+            return 4;
+          }
+
+          /* Unicode files need a new BOM. */
+          if (unicode) write_bom(logger);
+
+          /* Resume writing after the newline. */
+          size = 0LL;
+          address = (void *) ((char *) address + i);
+          in -= i;
+
+          break;
+        }
+      }
+    }
+    else if (! size) {
+      /* Write a BOM to the new file. */
+      if (IsTextUnicode(address, sizeof(buffer), 0)) write_bom(logger);
+    }
+
+    /* Write the data. */
+    if (! WriteFile(logger->write_handle, address, in, &out, 0)) {
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_WRITEFILE_FAILED, logger->service_name, logger->path, error_string(GetLastError()), 0);
+      return 3;
+    }
+
+    size += (__int64) out;
+  }
+
+  return 0;
 }

+ 14 - 0
io.h

@@ -11,12 +11,26 @@
 #define NSSM_STDERR_DISPOSITION OPEN_ALWAYS
 #define NSSM_STDERR_FLAGS FILE_ATTRIBUTE_NORMAL
 
+typedef struct {
+  TCHAR *service_name;
+  TCHAR *path;
+  unsigned long sharing;
+  unsigned long disposition;
+  unsigned long flags;
+  HANDLE read_handle;
+  HANDLE write_handle;
+  __int64 size;
+  unsigned long *tid_ptr;
+} logger_t;
+
 int get_createfile_parameters(HKEY, TCHAR *, TCHAR *, unsigned long *, unsigned long, unsigned long *, unsigned long, unsigned long *, unsigned long);
 int set_createfile_parameter(HKEY, TCHAR *, TCHAR *, unsigned long);
 int delete_createfile_parameter(HKEY, TCHAR *, TCHAR *);
 HANDLE append_to_file(TCHAR *, unsigned long, SECURITY_ATTRIBUTES *, unsigned long, unsigned long);
 void rotate_file(TCHAR *, TCHAR *, unsigned long, unsigned long, unsigned long);
 int get_output_handles(nssm_service_t *, HKEY, STARTUPINFO *);
+void close_output_handles(STARTUPINFO *, bool, bool);
 void close_output_handles(STARTUPINFO *);
+unsigned long WINAPI log_and_rotate(void *);
 
 #endif

BIN
messages.mc


BIN
nssm.rc


+ 7 - 0
registry.cpp

@@ -111,6 +111,8 @@ int create_parameters(nssm_service_t *service, bool editing) {
   }
   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);
+  else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_ONLINE);
   if (service->rotate_seconds) set_number(key, NSSM_REG_ROTATE_SECONDS, service->rotate_seconds);
   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_SECONDS);
   if (service->rotate_bytes_low) set_number(key, NSSM_REG_ROTATE_BYTES_LOW, service->rotate_bytes_low);
@@ -573,6 +575,11 @@ int get_parameters(nssm_service_t *service, STARTUPINFO *si) {
     else service->rotate_files = false;
   }
   else service->rotate_files = false;
+  if (get_number(key, NSSM_REG_ROTATE_ONLINE, &rotate_files, false) == 1) {
+    if (rotate_files) service->rotate_stdout_online = service->rotate_stderr_online = true;
+    else service->rotate_stdout_online = service->rotate_stderr_online = false;
+  }
+  else service->rotate_stdout_online = service->rotate_stderr_online = false;
   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

@@ -21,6 +21,7 @@
 #define NSSM_REG_STDIO_DISPOSITION _T("CreationDisposition")
 #define NSSM_REG_STDIO_FLAGS _T("FlagsAndAttributes")
 #define NSSM_REG_ROTATE _T("AppRotateFiles")
+#define NSSM_REG_ROTATE_ONLINE _T("AppRotateOnline")
 #define NSSM_REG_ROTATE_SECONDS _T("AppRotateSeconds")
 #define NSSM_REG_ROTATE_BYTES_LOW _T("AppRotateBytes")
 #define NSSM_REG_ROTATE_BYTES_HIGH _T("AppRotateBytesHigh")

+ 16 - 15
resource.h

@@ -46,20 +46,21 @@
 #define IDC_ENVIRONMENT_REPLACE         1027
 #define IDC_TRUNCATE                    1028
 #define IDC_ROTATE                      1029
-#define IDC_ROTATE_SECONDS              1030
-#define IDC_ROTATE_BYTES_LOW            1031
-#define IDC_DISPLAYNAME                 1032
-#define IDC_DESCRIPTION                 1033
-#define IDC_STARTUP                     1034
-#define IDC_LOCALSYSTEM                 1035
-#define IDC_INTERACT                    1036
-#define IDC_ACCOUNT                     1037
-#define IDC_USERNAME                    1038
-#define IDC_PASSWORD1                   1039
-#define IDC_PASSWORD2                   1040
-#define IDC_PRIORITY                    1041
-#define IDC_AFFINITY_ALL                1042
-#define IDC_AFFINITY                    1043
+#define IDC_ROTATE_ONLINE               1030
+#define IDC_ROTATE_SECONDS              1031
+#define IDC_ROTATE_BYTES_LOW            1032
+#define IDC_DISPLAYNAME                 1033
+#define IDC_DESCRIPTION                 1034
+#define IDC_STARTUP                     1035
+#define IDC_LOCALSYSTEM                 1036
+#define IDC_INTERACT                    1037
+#define IDC_ACCOUNT                     1038
+#define IDC_USERNAME                    1039
+#define IDC_PASSWORD1                   1040
+#define IDC_PASSWORD2                   1041
+#define IDC_PRIORITY                    1042
+#define IDC_AFFINITY_ALL                1043
+#define IDC_AFFINITY                    1044
 
 // Next default values for new objects
 // 
@@ -67,7 +68,7 @@
 #ifndef APSTUDIO_READONLY_SYMBOLS
 #define _APS_NEXT_RESOURCE_VALUE        115
 #define _APS_NEXT_COMMAND_VALUE         40001
-#define _APS_NEXT_CONTROL_VALUE         1044
+#define _APS_NEXT_CONTROL_VALUE         1045
 #define _APS_NEXT_SYMED_VALUE           101
 #endif
 #endif

+ 1 - 1
service.cpp

@@ -1431,7 +1431,7 @@ int start_service(nssm_service_t *service) {
 
   if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
 
-  close_output_handles(&si);
+  close_output_handles(&si, ! service->rotate_stdout_online, ! service->rotate_stderr_online);
 
   if (service->affinity) {
     /*

+ 8 - 0
service.h

@@ -57,11 +57,19 @@ typedef struct {
   unsigned long stdout_sharing;
   unsigned long stdout_disposition;
   unsigned long stdout_flags;
+  HANDLE stdout_pipe;
+  HANDLE stdout_thread;
+  unsigned long stdout_tid;
   TCHAR stderr_path[MAX_PATH];
   unsigned long stderr_sharing;
   unsigned long stderr_disposition;
   unsigned long stderr_flags;
+  HANDLE stderr_pipe;
+  HANDLE stderr_thread;
+  unsigned long stderr_tid;
   bool rotate_files;
+  bool rotate_stdout_online;
+  bool rotate_stderr_online;
   unsigned long rotate_seconds;
   unsigned long rotate_bytes_low;
   unsigned long rotate_bytes_high;

+ 1 - 0
settings.cpp

@@ -830,6 +830,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_THROTTLE, REG_DWORD, (void *) NSSM_RESET_THROTTLE_RESTART, 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 },
   { NSSM_REG_ROTATE_BYTES_LOW, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
   { NSSM_REG_ROTATE_BYTES_HIGH, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },