From: Iain Patterson Date: Thu, 18 Sep 2014 07:28:21 +0000 (+0100) Subject: Copy/truncate file rotation. X-Git-Url: http://git.iain.cx/?a=commitdiff_plain;h=f7f20a0b3ecbb0e1a2eb59f95b50f0625b37c671;p=nssm.git 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. --- diff --git a/ChangeLog.txt b/ChangeLog.txt index 6d127fc..62fd6b8 100644 --- a/ChangeLog.txt +++ b/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. diff --git a/README.txt b/README.txt index 7aca940..e944340 100644 --- a/README.txt +++ b/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 ------- diff --git a/io.cpp b/io.cpp index cf30cd4..90cc0d1 100644 --- a/io.cpp +++ b/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; diff --git a/io.h b/io.h index b262db1..41c0cdb 100644 --- a/io.h +++ b/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 *); diff --git a/nssm.h b/nssm.h index f34346b..1c78d18 100644 --- a/nssm.h +++ b/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 diff --git a/registry.cpp b/registry.cpp index bf2ddc5..42c5425 100644 --- a/registry.cpp +++ b/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; diff --git a/registry.h b/registry.h index f831f89..bd8ee41 100644 --- a/registry.h +++ b/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") diff --git a/service.h b/service.h index dd96f7c..c5a5d33 100644 --- a/service.h +++ b/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; diff --git a/settings.cpp b/settings.cpp index 0ed75ee..5ca2de9 100644 --- a/settings.cpp +++ b/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 },