Browse Source

Copy/truncate file rotation.

Some processes, notably Logstash, open files for reading without setting
the FILE_SHARE_READ share mode.  If NSSM is using such a file for I/O
redirection and it tries to rotate the file, its MoveFile() call will
fail.

If the new (REG_DWORD) value(s) AppStdoutCopyAndTruncate and/or
AppStderrCopyAndTruncate are non-zero, NSSM will instead try to rotate
the file(s) by calling CopyFile() to create a copy of the file then
calling SetEndOfFile() to truncate the original file to zero size.
Doing so allows the rotation to succeed at the cost of time and disk
space to create a full copy of the file.

If the new (REG_DWORD) value AppRotateDelay is non-zero, NSSM will sleep
for the given number of milliseconds after rotation, regardless of the
rotation method used.  Testing with Logstash specifically has shown that
introducing a delay of 1000ms may be sufficient to avoid an issue
whereby the reading application loses the final line of input from the
old file due to not noticing that it has been truncated.

Thanks Miguel Angel Terrón.
Iain Patterson 7 years ago
parent
commit
f7f20a0b3e
9 changed files with 111 additions and 16 deletions
  1. 7 0
      ChangeLog.txt
  2. 14 0
      README.txt
  3. 65 11
      io.cpp
  4. 4 2
      io.h
  5. 3 0
      nssm.h
  6. 10 3
      registry.cpp
  7. 2 0
      registry.h
  8. 3 0
      service.h
  9. 3 0
      settings.cpp

+ 7 - 0
ChangeLog.txt

@@ -2,6 +2,13 @@ Changes since 2.24
 ------------------
   * Allow skipping kill_process_tree().
 
+  * NSSM can now sleep a configurable amount of time after
+    rotating output files.
+
+  * NSSM can now rotate log files by calling CopyFile()
+    followed by SetEndOfFile(), allowing it to rotate files
+    which other processes hold open.
+
 Changes since 2.23
 ------------------
   * NSSM once again calls TerminateProcess() correctly.

+ 14 - 0
README.txt

@@ -326,6 +326,19 @@ If AppRotateBytes is non-zero, a file will not be rotated if it is smaller
 than the given number of bytes.  64-bit file sizes can be handled by setting
 a non-zero value of AppRotateBytesHigh.
 
+If AppRotateDelay is non-zero, NSSM will pause for the given number of
+milliseconds after rotation.
+
+If AppStdoutCopyAndTruncate or AppStdErrCopyAndTruncate are non-zero, the
+stdout (or stderr respectively) file will be rotated by first taking a copy
+of the file then truncating the original file to zero size.  This allows
+NSSM to rotate files which are held open by other processes, preventing the
+usual MoveFile() from succeeding.  Note that the copy process may take some
+time if the file is large, and will temporarily consume twice as much disk
+space as the original file.  Note also that applications reading the log file
+may not notice that the file size changed.  Using this option in conjunction
+with AppRotateDelay may help in that case.
+
 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.
@@ -692,6 +705,7 @@ run under a local user account.
 Thanks to Sam Townsend for noticing a regression with TerminateProcess().
 Thanks to Barrett Lewis for suggesting the option to skip terminating the
 application's child processes.
+Thanks to Miguel Angel Terrón for suggesting copy/truncate rotation.
 
 Licence
 -------

+ 65 - 11
io.cpp

@@ -4,7 +4,7 @@
 #define COMPLAINED_WRITE (1 << 1)
 #define COMPLAINED_ROTATE (1 << 2)
 
-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 *tid_ptr, unsigned long *rotate_online) {
+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;
 
   /* Pipe between application's stdout/stderr and our logging handle. */
@@ -40,6 +40,8 @@ static HANDLE create_logging_thread(TCHAR *service_name, TCHAR *path, unsigned l
   logger->size = (__int64) size.QuadPart;
   logger->tid_ptr = tid_ptr;
   logger->rotate_online = rotate_online;
+  logger->rotate_delay = rotate_delay;
+  logger->copy_and_truncate = copy_and_truncate;
 
   HANDLE thread_handle = CreateThread(NULL, 0, log_and_rotate, (void *) logger, 0, logger->tid_ptr);
   if (! thread_handle) {
@@ -63,7 +65,7 @@ static inline void write_bom(logger_t *logger, unsigned long *out) {
 }
 
 /* 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) {
+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, bool *copy_and_truncate) {
   TCHAR value[NSSM_STDIO_LENGTH];
 
   /* Path. */
@@ -109,6 +111,23 @@ int get_createfile_parameters(HKEY key, TCHAR *prefix, TCHAR *path, unsigned lon
     case -2: return 8; break; /* Error. */
   }
 
+  /* Rotate with CopyFile() and SetEndOfFile(). */
+  if (copy_and_truncate) {
+    unsigned long data;
+    if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, NSSM_REG_STDIO_COPY_AND_TRUNCATE) < 0) {
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, NSSM_REG_STDIO_COPY_AND_TRUNCATE, _T("get_createfile_parameters()"), 0);
+      return 9;
+    }
+    switch (get_number(key, value, &data, false)) {
+      case 0: *copy_and_truncate = false; break; /* Missing. */
+      case 1: /* Found. */
+        if (data) *copy_and_truncate = true;
+        else *copy_and_truncate = false;
+        break;
+      case -2: return 9; break; /* Error. */
+    }
+  }
+
   return 0;
 }
 
@@ -162,7 +181,7 @@ static void rotated_filename(TCHAR *path, TCHAR *rotated, unsigned long rotated_
   _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) {
+void rotate_file(TCHAR *service_name, TCHAR *path, unsigned long seconds, unsigned long delay, unsigned long low, unsigned long high, bool copy_and_truncate) {
   unsigned long error;
 
   /* Now. */
@@ -219,14 +238,31 @@ void rotate_file(TCHAR *service_name, TCHAR *path, unsigned long seconds, unsign
   rotated_filename(path, rotated, _countof(rotated), &st);
 
   /* Rotate. */
-  if (MoveFile(path, rotated)) {
+  bool ok = true;
+  TCHAR *function;
+  if (copy_and_truncate) {
+    function = _T("CopyFile()");
+    if (CopyFile(path, rotated, TRUE)) {
+      file = write_to_file(path, NSSM_STDOUT_SHARING, 0, NSSM_STDOUT_DISPOSITION, NSSM_STDOUT_FLAGS);
+      Sleep(delay);
+      SetFilePointer(file, 0, 0, FILE_BEGIN);
+      SetEndOfFile(file);
+      CloseHandle(file);
+    }
+    else ok = false;
+  }
+  else {
+    function = _T("MoveFile()");
+    if (! MoveFile(path, rotated)) ok = false;
+  }
+  if (ok) {
     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ROTATED, service_name, path, rotated, 0);
     return;
   }
   error = GetLastError();
 
   if (error == ERROR_FILE_NOT_FOUND) return;
-  log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, service_name, path, _T("MoveFile()"), rotated, error_string(error), 0);
+  log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, service_name, path, function, rotated, error_string(error), 0);
   return;
 }
 
@@ -247,13 +283,13 @@ int get_output_handles(nssm_service_t *service, STARTUPINFO *si) {
 
   /* stdout */
   if (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);
+    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) return 4;
 
     if (service->rotate_files && service->rotate_stdout_online) {
       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->stdout_tid, &service->rotate_stdout_online);
+      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);
       if (! service->stdout_thread) {
         CloseHandle(service->stdout_pipe);
         CloseHandle(si->hStdOutput);
@@ -286,13 +322,13 @@ int get_output_handles(nssm_service_t *service, STARTUPINFO *si) {
       }
     }
     else {
-      if (service->rotate_files) rotate_file(service->name, service->stderr_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high);
+      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) return 7;
 
       if (service->rotate_files && service->rotate_stderr_online) {
         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->stderr_tid, &service->rotate_stderr_online);
+        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);
         if (! service->stderr_thread) {
           CloseHandle(service->stderr_pipe);
           CloseHandle(si->hStdError);
@@ -499,15 +535,33 @@ unsigned long WINAPI log_and_rotate(void *arg) {
             MoveFile() will fail if the handle is still open so we must
             risk losing everything.
           */
+          if (logger->copy_and_truncate) FlushFileBuffers(logger->write_handle);
           CloseHandle(logger->write_handle);
-          if (MoveFile(logger->path, rotated)) {
+          bool ok = true;
+          TCHAR *function;
+          if (logger->copy_and_truncate) {
+            function = _T("CopyFile()");
+            if (CopyFile(logger->path, rotated, TRUE)) {
+              HANDLE file = write_to_file(logger->path, NSSM_STDOUT_SHARING, 0, NSSM_STDOUT_DISPOSITION, NSSM_STDOUT_FLAGS);
+              Sleep(logger->rotate_delay);
+              SetFilePointer(file, 0, 0, FILE_BEGIN);
+              SetEndOfFile(file);
+              CloseHandle(file);
+            }
+            else ok = false;
+          }
+          else {
+            function = _T("MoveFile()");
+            if (! MoveFile(logger->path, rotated)) ok = false;
+          }
+          if (ok) {
             log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ROTATED, logger->service_name, logger->path, rotated, 0);
             size = 0LL;
           }
           else {
             error = GetLastError();
             if (error != ERROR_FILE_NOT_FOUND) {
-              if (! (complained & COMPLAINED_ROTATE)) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, logger->service_name, logger->path, _T("MoveFile()"), rotated, error_string(error), 0);
+              if (! (complained & COMPLAINED_ROTATE)) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, logger->service_name, logger->path, function, rotated, error_string(error), 0);
               complained |= COMPLAINED_ROTATE;
               /* We can at least try to re-open the existing file. */
               logger->disposition = OPEN_ALWAYS;

+ 4 - 2
io.h

@@ -22,13 +22,15 @@ typedef struct {
   __int64 size;
   unsigned long *tid_ptr;
   unsigned long *rotate_online;
+  bool copy_and_truncate;
+  unsigned long rotate_delay;
 } logger_t;
 
-int get_createfile_parameters(HKEY, TCHAR *, TCHAR *, unsigned long *, unsigned long, unsigned long *, unsigned long, unsigned long *, unsigned long);
+int get_createfile_parameters(HKEY, TCHAR *, TCHAR *, unsigned long *, unsigned long, unsigned long *, unsigned long, unsigned long *, unsigned long, bool *);
 int set_createfile_parameter(HKEY, TCHAR *, TCHAR *, unsigned long);
 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);
+void rotate_file(TCHAR *, TCHAR *, unsigned long, unsigned long, unsigned long, unsigned long, bool);
 int get_output_handles(nssm_service_t *, STARTUPINFO *);
 void close_output_handles(STARTUPINFO *);
 unsigned long WINAPI log_and_rotate(void *);

+ 3 - 0
nssm.h

@@ -96,6 +96,9 @@ int usage(int);
 */
 #define NSSM_KILL_THREADS_GRACE_PERIOD 1500
 
+/* How many milliseconds to pause after rotating logs. */
+#define NSSM_ROTATE_DELAY 0
+
 /* Margin of error for service status wait hints in milliseconds. */
 #define NSSM_WAITHINT_MARGIN 2000
 

+ 10 - 3
registry.cpp

@@ -100,6 +100,8 @@ int create_parameters(nssm_service_t *service, bool editing) {
     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_DISPOSITION);
     if (service->stdout_flags != NSSM_STDOUT_FLAGS) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_FLAGS, service->stdout_flags);
     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_FLAGS);
+    if (service->stdout_copy_and_truncate) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_COPY_AND_TRUNCATE, 1);
+    else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_COPY_AND_TRUNCATE);
   }
   if (service->stderr_path[0] || editing) {
     if (service->stderr_path[0]) set_expand_string(key, NSSM_REG_STDERR, service->stderr_path);
@@ -110,6 +112,8 @@ int create_parameters(nssm_service_t *service, bool editing) {
     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_DISPOSITION);
     if (service->stderr_flags != NSSM_STDERR_FLAGS) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_FLAGS, service->stderr_flags);
     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_FLAGS);
+    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->rotate_files) set_number(key, NSSM_REG_ROTATE, 1);
   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE);
@@ -121,6 +125,8 @@ int create_parameters(nssm_service_t *service, bool editing) {
   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_BYTES_LOW);
   if (service->rotate_bytes_high) set_number(key, NSSM_REG_ROTATE_BYTES_HIGH, service->rotate_bytes_high);
   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_BYTES_HIGH);
+  if (service->rotate_delay != NSSM_ROTATE_DELAY) set_number(key, NSSM_REG_ROTATE_DELAY, service->rotate_delay);
+  else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_DELAY);
   if (service->no_console) set_number(key, NSSM_REG_NO_CONSOLE, 1);
   else if (editing) RegDeleteValue(key, NSSM_REG_NO_CONSOLE);
 
@@ -486,21 +492,21 @@ HKEY open_registry(const TCHAR *service_name, REGSAM sam) {
 
 int get_io_parameters(nssm_service_t *service, HKEY key) {
   /* stdin */
-  if (get_createfile_parameters(key, NSSM_REG_STDIN, service->stdin_path, &service->stdin_sharing, NSSM_STDIN_SHARING, &service->stdin_disposition, NSSM_STDIN_DISPOSITION, &service->stdin_flags, NSSM_STDIN_FLAGS)) {
+  if (get_createfile_parameters(key, NSSM_REG_STDIN, service->stdin_path, &service->stdin_sharing, NSSM_STDIN_SHARING, &service->stdin_disposition, NSSM_STDIN_DISPOSITION, &service->stdin_flags, NSSM_STDIN_FLAGS, 0)) {
     service->stdin_sharing = service->stdin_disposition = service->stdin_flags = 0;
     ZeroMemory(service->stdin_path, _countof(service->stdin_path) * sizeof(TCHAR));
     return 1;
   }
 
   /* 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)) {
+  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_copy_and_truncate)) {
     service->stdout_sharing = service->stdout_disposition = service->stdout_flags = 0;
     ZeroMemory(service->stdout_path, _countof(service->stdout_path) * sizeof(TCHAR));
     return 2;
   }
 
   /* stderr */
-  if (get_createfile_parameters(key, NSSM_REG_STDERR, service->stderr_path, &service->stderr_sharing, NSSM_STDERR_SHARING, &service->stderr_disposition, NSSM_STDERR_DISPOSITION, &service->stderr_flags, NSSM_STDERR_FLAGS)) {
+  if (get_createfile_parameters(key, NSSM_REG_STDERR, service->stderr_path, &service->stderr_sharing, NSSM_STDERR_SHARING, &service->stderr_disposition, NSSM_STDERR_DISPOSITION, &service->stderr_flags, NSSM_STDERR_FLAGS, &service->stderr_copy_and_truncate)) {
     service->stderr_sharing = service->stderr_disposition = service->stderr_flags = 0;
     ZeroMemory(service->stderr_path, _countof(service->stderr_path) * sizeof(TCHAR));
     return 3;
@@ -600,6 +606,7 @@ int get_parameters(nssm_service_t *service, STARTUPINFO *si) {
   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;
+  override_milliseconds(service->name, key, NSSM_REG_ROTATE_DELAY, &service->rotate_delay, NSSM_ROTATE_DELAY, NSSM_EVENT_BOGUS_THROTTLE);
 
   /* Try to get force new console setting - may fail. */
   if (get_number(key, NSSM_REG_NO_CONSOLE, &service->no_console, false) != 1) service->no_console = 0;

+ 2 - 0
registry.h

@@ -23,11 +23,13 @@
 #define NSSM_REG_STDIO_SHARING _T("ShareMode")
 #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_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")
+#define NSSM_REG_ROTATE_DELAY _T("AppRotateDelay")
 #define NSSM_REG_PRIORITY _T("AppPriority")
 #define NSSM_REG_AFFINITY _T("AppAffinity")
 #define NSSM_REG_NO_CONSOLE _T("AppNoConsole")

+ 3 - 0
service.h

@@ -72,11 +72,14 @@ typedef struct {
   HANDLE stderr_thread;
   unsigned long stderr_tid;
   bool rotate_files;
+  bool stdout_copy_and_truncate;
+  bool stderr_copy_and_truncate;
   unsigned long rotate_stdout_online;
   unsigned long rotate_stderr_online;
   unsigned long rotate_seconds;
   unsigned long rotate_bytes_low;
   unsigned long rotate_bytes_high;
+  unsigned long rotate_delay;
   unsigned long default_exit_action;
   unsigned long restart_delay;
   unsigned long throttle_delay;

+ 3 - 0
settings.cpp

@@ -1034,10 +1034,12 @@ settings_t settings[] = {
   { NSSM_REG_STDOUT NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDOUT_SHARING, false, 0, setting_set_number, setting_get_number },
   { NSSM_REG_STDOUT NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDOUT_DISPOSITION, false, 0, setting_set_number, setting_get_number },
   { NSSM_REG_STDOUT NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDOUT_FLAGS, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDOUT NSSM_REG_STDIO_COPY_AND_TRUNCATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
   { NSSM_REG_STDERR, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string },
   { NSSM_REG_STDERR NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDERR_SHARING, false, 0, setting_set_number, setting_get_number },
   { NSSM_REG_STDERR NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDERR_DISPOSITION, false, 0, setting_set_number, setting_get_number },
   { NSSM_REG_STDERR NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDERR_FLAGS, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDERR NSSM_REG_STDIO_COPY_AND_TRUNCATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
   { NSSM_REG_STOP_METHOD_SKIP, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
   { NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_CONSOLE_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number },
   { NSSM_REG_KILL_WINDOW_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_WINDOW_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number },
@@ -1049,6 +1051,7 @@ settings_t settings[] = {
   { 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 },
+  { NSSM_REG_ROTATE_DELAY, REG_DWORD, (void *) NSSM_ROTATE_DELAY, false, 0, setting_set_number, setting_get_number },
   { NSSM_NATIVE_DEPENDONGROUP, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_dependongroup, native_get_dependongroup },
   { NSSM_NATIVE_DEPENDONSERVICE, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_dependonservice, native_get_dependonservice },
   { NSSM_NATIVE_DESCRIPTION, REG_SZ, _T(""), true, 0, native_set_description, native_get_description },