From: Iain Patterson Date: Sat, 21 Dec 2013 12:00:53 +0000 (+0000) Subject: Rotate output files. X-Git-Tag: v2.22~129 X-Git-Url: http://git.iain.cx/?p=nssm.git;a=commitdiff_plain;h=2f219930f488b2551326900df5c201de2e9304f2 Rotate output files. The files specified by AppStdout and AppStderr can now be rotated prior to launching the application. An existing file will be renamed with a new suffix based on its last write time, to millisecond precision. Rotation is controlled independently of the CreateFile() arguments used when opening the files for writing. It is possible to configure rotation regardless of whether existing files would be appended or replaced. Four new REG_DWORD entries control how rotation happens. If AppRotateFiles is 0 or missing, rotation is disabled. If AppRotateSeconds is non-zero, a file will not be rotated if its last write time is less than the given number of seconds in the past. 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 specifying a high order part in AppRotateBytesHigh. Thanks Doug Watson. --- diff --git a/ChangeLog.txt b/ChangeLog.txt index e50c841..b26ab1b 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,11 @@ +Changes since 2.21 +----------------- + * NSSM can now optionally rotate existing files when + redirecting I/O. + + * Unqualified path names are now relative to the + application startup directory when redirecting I/O. + Changes since 2.20 ----------------- * Services installed from the GUI no longer have incorrect diff --git a/README.txt b/README.txt index 3dacf18..366e6f3 100644 --- a/README.txt +++ b/README.txt @@ -53,6 +53,8 @@ Since version 2.19, NSSM can add to the service's environment by setting AppEnvironmentExtra in place of or in addition to the srvany-compatible AppEnvironment. +Since version 2.22, NSSM can rotate existing output files when redirecting I/O. + Usage ----- @@ -234,6 +236,32 @@ work. Remember, however, that the path must be accessible to the user running the service. +File rotation +------------- +When using I/O redirection, NSSM can rotate existing output files prior to +opening stdout and/or stderr. An existing file will be renamed with a +suffix based on the file's last write time, to millisecond precision. For +example, the file nssm.log might be rotated to nssm-20131221T113939.457.log. + +NSSM will look in the registry under +HKLM\SYSTEM\CurrentControlSet\Services\\Parameters for REG_DWORD +entries which control how rotation happens. + +If AppRotateFiles is missing or set to 0, rotation is disabled. Any non-zero +value enables rotation. + +If AppRotateSeconds is non-zero, a file will not be rotated if its last write +time is less than the given number of seconds in the past. + +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. + +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. + + Environment variables --------------------- NSSM can replace or append to the managed application's environment. Two @@ -328,6 +356,7 @@ Thanks to Brian Baxter for suggesting how to escape quotes from the command prom Thanks to Russ Holmann for suggesting that the shutdown timeout be configurable. Thanks to Paul Spause for spotting a bug with default registry entries. Thanks to BUGHUNTER for spotting more GUI bugs. +Thanks to Doug Watson for suggesting file rotation. Licence ------- diff --git a/gui.cpp b/gui.cpp index 67458af..1459d97 100644 --- a/gui.cpp +++ b/gui.cpp @@ -1,6 +1,6 @@ #include "nssm.h" -static enum { NSSM_TAB_APPLICATION, NSSM_TAB_SHUTDOWN, NSSM_TAB_EXIT, NSSM_TAB_IO, NSSM_TAB_ENVIRONMENT, NSSM_NUM_TABS }; +static enum { NSSM_TAB_APPLICATION, NSSM_TAB_SHUTDOWN, NSSM_TAB_EXIT, NSSM_TAB_IO, NSSM_TAB_ROTATION, NSSM_TAB_ENVIRONMENT, NSSM_NUM_TABS }; static HWND tablist[NSSM_NUM_TABS]; static int selected_tab; @@ -135,12 +135,24 @@ int install(HWND window) { check_io(_T("stdin"), service->stdin_path, sizeof(service->stdin_path), IDC_STDIN); check_io(_T("stdout"), service->stdout_path, sizeof(service->stdout_path), IDC_STDOUT); check_io(_T("stderr"), service->stderr_path, sizeof(service->stderr_path), IDC_STDERR); + /* Override stdout and/or stderr. */ - if (SendDlgItemMessage(tablist[NSSM_TAB_IO], IDC_TRUNCATE, BM_GETCHECK, 0, 0) & BST_CHECKED) { + if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_TRUNCATE, BM_GETCHECK, 0, 0) & BST_CHECKED) { if (service->stdout_path[0]) service->stdout_disposition = CREATE_ALWAYS; if (service->stderr_path[0]) service->stderr_disposition = CREATE_ALWAYS; } + /* 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_SECONDS_ENABLED, BM_GETCHECK, 0, 0) & BST_CHECKED) { + check_method_timeout(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS, &service->rotate_seconds); + } + if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW_ENABLED, BM_GETCHECK, 0, 0) & BST_CHECKED) { + check_method_timeout(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, &service->rotate_bytes_low); + } + /* Get environment. */ unsigned long envlen = (unsigned long) SendMessage(GetDlgItem(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT), WM_GETTEXTLENGTH, 0, 0); if (envlen) { @@ -367,6 +379,7 @@ INT_PTR CALLBACK tab_dlg(HWND tab, UINT message, WPARAM w, LPARAM l) { case WM_COMMAND: HWND dlg; TCHAR buffer[MAX_PATH]; + unsigned long state; switch (LOWORD(w)) { /* Browse for application. */ @@ -416,6 +429,17 @@ INT_PTR CALLBACK tab_dlg(HWND tab, UINT message, WPARAM w, LPARAM l) { GetDlgItemText(tab, IDC_STDERR, buffer, sizeof(buffer)); browse(dlg, buffer, 0, NSSM_GUI_BROWSE_FILTER_ALL_FILES, 0); break; + + /* Rotation. */ + case IDC_ROTATE: + case IDC_ROTATE_SECONDS_ENABLED: + case IDC_ROTATE_BYTES_LOW_ENABLED: + if (SendDlgItemMessage(tab, LOWORD(w), BM_GETCHECK, 0, 0) & BST_CHECKED) state = BST_CHECKED; + else state = BST_UNCHECKED; + SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE, BM_SETCHECK, state, 0); + SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS_ENABLED, BM_SETCHECK, state, 0); + SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW_ENABLED, BM_SETCHECK, state, 0); + break; } return 1; } @@ -486,6 +510,17 @@ INT_PTR CALLBACK install_dlg(HWND window, UINT message, WPARAM w, LPARAM l) { tablist[NSSM_TAB_IO] = CreateDialog(0, MAKEINTRESOURCE(IDD_IO), window, tab_dlg); ShowWindow(tablist[NSSM_TAB_IO], SW_HIDE); + /* Rotation tab. */ + tab.pszText = message_string(NSSM_GUI_TAB_ROTATION); + tab.cchTextMax = (int) _tcslen(tab.pszText) + 1; + SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_ROTATION, (LPARAM) &tab); + tablist[NSSM_TAB_ROTATION] = CreateDialog(0, MAKEINTRESOURCE(IDD_ROTATION), window, tab_dlg); + ShowWindow(tablist[NSSM_TAB_ROTATION], SW_HIDE); + + /* Set defaults. */ + SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS, 0, 0); + SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, 0, 0); + /* Environment tab. */ tab.pszText = message_string(NSSM_GUI_TAB_ENVIRONMENT); tab.cchTextMax = (int) _tcslen(tab.pszText) + 1; diff --git a/io.cpp b/io.cpp index 5331188..30deb72 100644 --- a/io.cpp +++ b/io.cpp @@ -81,7 +81,77 @@ HANDLE append_to_file(TCHAR *path, unsigned long sharing, SECURITY_ATTRIBUTES *a return CreateFile(path, FILE_WRITE_DATA, sharing, attributes, disposition, flags, 0); } -int get_output_handles(HKEY key, STARTUPINFO *si) { +void rotate_file(TCHAR *service_name, TCHAR *path, unsigned long seconds, unsigned long low, unsigned long high) { + unsigned long error; + + /* Now. */ + SYSTEMTIME st; + GetSystemTime(&st); + + BY_HANDLE_FILE_INFORMATION info; + + /* Try to open the file to check if it exists and to get attributes. */ + HANDLE file = CreateFile(path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + if (file) { + /* Get file attributes. */ + if (! GetFileInformationByHandle(file, &info)) { + /* Reuse current time for rotation timestamp. */ + seconds = low = high = 0; + SystemTimeToFileTime(&st, &info.ftLastWriteTime); + } + + CloseHandle(file); + } + else { + error = GetLastError(); + if (error == ERROR_FILE_NOT_FOUND) return; + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, service_name, path, _T("CreateFile()"), path, error_string(error), 0); + /* Reuse current time for rotation timestamp. */ + seconds = low = high = 0; + SystemTimeToFileTime(&st, &info.ftLastWriteTime); + } + + /* Check file age. */ + if (seconds) { + FILETIME ft; + SystemTimeToFileTime(&st, &ft); + + ULARGE_INTEGER s; + s.LowPart = ft.dwLowDateTime; + s.HighPart = ft.dwHighDateTime; + s.QuadPart -= seconds * 10000000LL; + ft.dwLowDateTime = s.LowPart; + ft.dwHighDateTime = s.HighPart; + if (CompareFileTime(&info.ftLastWriteTime, &ft) > 0) return; + } + + /* Check file size. */ + if (low || high) { + if (info.nFileSizeHigh < high) return; + if (info.nFileSizeHigh == high && info.nFileSizeLow < low) return; + } + + /* 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); + + /* Rotate. */ + if (MoveFile(path, rotated)) return; + error = GetLastError(); + + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, service_name, path, _T("MoveFile()"), rotated, error_string(error), 0); + return; +} + +int get_output_handles(nssm_service_t *service, HKEY key, STARTUPINFO *si) { TCHAR path[MAX_PATH]; TCHAR stdout_path[MAX_PATH]; unsigned long sharing, disposition, flags; @@ -112,6 +182,7 @@ int get_output_handles(HKEY key, STARTUPINFO *si) { return 4; } + if (service->rotate_files) rotate_file(service->name, path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high); si->hStdOutput = append_to_file(path, sharing, &attributes, disposition, flags); if (! si->hStdOutput) return 5; set_flags = true; @@ -130,6 +201,7 @@ int get_output_handles(HKEY key, STARTUPINFO *si) { } } else { + if (service->rotate_files) rotate_file(service->name, path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high); si->hStdError = append_to_file(path, sharing, &attributes, disposition, flags); if (! si->hStdError) { log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, path, error_string(GetLastError())); diff --git a/io.h b/io.h index 5b9e8ca..41cb976 100644 --- a/io.h +++ b/io.h @@ -14,7 +14,8 @@ 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); HANDLE append_to_file(TCHAR *, unsigned long, SECURITY_ATTRIBUTES *, unsigned long, unsigned long); -int get_output_handles(HKEY, STARTUPINFO *); +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 *); #endif diff --git a/messages.mc b/messages.mc index 150d6be..62fb9cd 100644 --- a/messages.mc +++ b/messages.mc @@ -510,6 +510,19 @@ Language = Italian I/O%0 . +MessageId = +1 +SymbolicName = NSSM_GUI_TAB_ROTATION +Severity = Informational +Language = English +File rotation%0 +. +Language = French +File rotation%0 +. +Language = Italian +File rotation%0 +. + MessageId = +1 SymbolicName = NSSM_GUI_TAB_ENVIRONMENT Severity = Informational @@ -1546,3 +1559,22 @@ SetEnvironmentVariable(%1=%2) a Language = Italian Chiamata a SetEnvironmentVariable(%1=%2) fallita: . + +MessageId = +1 +SymbolicName = NSSM_EVENT_ROTATE_FILE_FAILED +Severity = Error +Language = English +Failed to rotate output file %2 for service %1. +%3 failed for file %4: +%5 +. +Language = French +Failed to rotate output file %2 for service %1. +%3 failed for file %4: +%5 +. +Language = Italian +Failed to rotate output file %2 for service %1. +%3 failed for file %4: +%5 +. diff --git a/nssm.rc b/nssm.rc index 9596674..3b8667a 100644 --- a/nssm.rc +++ b/nssm.rc @@ -154,7 +154,22 @@ FONT 8, "MS Sans Serif" LTEXT "Error (stderr):", IDC_STATIC, 13, 50, 53, 8, SS_LEFT EDITTEXT IDC_STDERR, 70, 48, 167, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES DEFPUSHBUTTON "...", IDC_BROWSE_STDERR, 239, 47, 15, 14 - AUTOCHECKBOX "Replace files", IDC_TRUNCATE, 13, 60, 74, 8 +} + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_ROTATION DIALOG 9, 20, 261, 75 +STYLE DS_SHELLFONT | WS_VISIBLE | WS_CHILD | DS_CONTROL +FONT 8, "MS Sans Serif" +{ + GROUPBOX "File rotation", IDC_STATIC, 7, 7, 251, 68 + AUTOCHECKBOX "Replace existing Output and/or Error files", IDC_TRUNCATE, 13, 18, 145, 8 + AUTOCHECKBOX "Rotate files", IDC_ROTATE, 13, 32, 51, 8 + AUTOCHECKBOX "Restrict rotation to files older than", IDC_ROTATE_SECONDS_ENABLED, 13, 46, 121, 8 + EDITTEXT IDC_ROTATE_SECONDS, 140, 44, 29, 12, ES_AUTOHSCROLL | ES_NUMBER + LTEXT "s", IDC_STATIC, 171, 46, 8, 8, SS_LEFT + AUTOCHECKBOX "Restrict rotation to files bigger than", IDC_ROTATE_BYTES_LOW_ENABLED, 13, 60, 125, 8 + EDITTEXT IDC_ROTATE_BYTES_LOW, 140, 58, 49, 12, ES_AUTOHSCROLL | ES_NUMBER + LTEXT "kB", IDC_STATIC, 191, 60, 8, 8, SS_LEFT } LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL diff --git a/registry.cpp b/registry.cpp index e192529..048c07d 100644 --- a/registry.cpp +++ b/registry.cpp @@ -86,6 +86,10 @@ int create_parameters(nssm_service_t *service) { if (service->stderr_disposition != NSSM_STDERR_DISPOSITION) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_DISPOSITION, service->stderr_disposition); if (service->stderr_flags != NSSM_STDERR_FLAGS) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_FLAGS, service->stderr_flags); } + if (service->rotate_files) set_number(key, NSSM_REG_ROTATE, 1); + if (service->rotate_seconds) set_number(key, NSSM_REG_ROTATE_SECONDS, service->rotate_seconds); + if (service->rotate_bytes_low) set_number(key, NSSM_REG_ROTATE_BYTES_LOW, service->rotate_bytes_low); + if (service->rotate_bytes_high) set_number(key, NSSM_REG_ROTATE_BYTES_HIGH, service->rotate_bytes_high); /* Environment */ if (service->env) { @@ -387,13 +391,24 @@ int get_parameters(nssm_service_t *service, STARTUPINFO *si) { } } + /* Try to get file rotation settings - may fail. */ + unsigned long rotate_files; + if (get_number(key, NSSM_REG_ROTATE, &rotate_files, false) == 1) { + if (rotate_files) service->rotate_files = true; + else service->rotate_files = false; + } + else service->rotate_files = 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; + /* Change to startup directory in case stdout/stderr are relative paths. */ TCHAR cwd[MAX_PATH]; GetCurrentDirectory(_countof(cwd), cwd); SetCurrentDirectory(service->dir); /* Try to get stdout and stderr */ - if (get_output_handles(key, si)) { + if (get_output_handles(service, key, si)) { log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0); RegCloseKey(key); SetCurrentDirectory(cwd); diff --git a/registry.h b/registry.h index ffdf3d6..600c188 100644 --- a/registry.h +++ b/registry.h @@ -19,6 +19,10 @@ #define NSSM_REG_STDIO_SHARING _T("ShareMode") #define NSSM_REG_STDIO_DISPOSITION _T("CreationDisposition") #define NSSM_REG_STDIO_FLAGS _T("FlagsAndAttributes") +#define NSSM_REG_ROTATE _T("AppRotateFiles") +#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_STDIO_LENGTH 29 int create_messages(); diff --git a/resource.h b/resource.h index d6f2792..ec82a78 100644 --- a/resource.h +++ b/resource.h @@ -8,9 +8,10 @@ #define IDD_REMOVE 103 #define IDD_APPLICATION 104 #define IDD_IO 105 -#define IDD_APPEXIT 106 -#define IDD_SHUTDOWN 107 -#define IDD_ENVIRONMENT 108 +#define IDD_ROTATION 106 +#define IDD_APPEXIT 107 +#define IDD_SHUTDOWN 108 +#define IDD_ENVIRONMENT 109 #define IDC_PATH 1000 #define IDC_TAB1 1001 #define IDC_CANCEL 1002 @@ -38,14 +39,19 @@ #define IDC_ENVIRONMENT 1025 #define IDC_ENVIRONMENT_REPLACE 1026 #define IDC_TRUNCATE 1027 +#define IDC_ROTATE 1028 +#define IDC_ROTATE_SECONDS_ENABLED 1029 +#define IDC_ROTATE_SECONDS 1030 +#define IDC_ROTATE_BYTES_LOW_ENABLED 1031 +#define IDC_ROTATE_BYTES_LOW 1032 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 109 +#define _APS_NEXT_RESOURCE_VALUE 110 #define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1028 +#define _APS_NEXT_CONTROL_VALUE 1033 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif diff --git a/service.h b/service.h index c9824d4..04327c3 100644 --- a/service.h +++ b/service.h @@ -37,6 +37,10 @@ typedef struct { unsigned long stderr_sharing; unsigned long stderr_disposition; unsigned long stderr_flags; + bool rotate_files; + unsigned long rotate_seconds; + unsigned long rotate_bytes_low; + unsigned long rotate_bytes_high; unsigned long default_exit_action; unsigned long throttle_delay; unsigned long stop_method;