+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
AppEnvironmentExtra in place of or in addition to the srvany-compatible\r
AppEnvironment.\r
\r
+Since version 2.22, NSSM can rotate existing output files when redirecting I/O.\r
+\r
\r
Usage\r
-----\r
running the service.\r
\r
\r
+File rotation\r
+-------------\r
+When using I/O redirection, NSSM can rotate existing output files prior to\r
+opening stdout and/or stderr. An existing file will be renamed with a\r
+suffix based on the file's last write time, to millisecond precision. For\r
+example, the file nssm.log might be rotated to nssm-20131221T113939.457.log.\r
+\r
+NSSM will look in the registry under\r
+HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters for REG_DWORD\r
+entries which control how rotation happens.\r
+\r
+If AppRotateFiles is missing or set to 0, rotation is disabled. Any non-zero\r
+value enables rotation.\r
+\r
+If AppRotateSeconds is non-zero, a file will not be rotated if its last write\r
+time is less than the given number of seconds in the past.\r
+\r
+If AppRotateBytes is non-zero, a file will not be rotated if it is smaller\r
+than the given number of bytes. 64-bit file sizes can be handled by setting\r
+a non-zero value of AppRotateBytesHigh.\r
+\r
+Rotation is independent of the CreateFile() parameters used to open the files.\r
+They will be rotated regardless of whether NSSM would otherwise have appended\r
+or replaced them.\r
+\r
+\r
Environment variables\r
---------------------\r
NSSM can replace or append to the managed application's environment. Two\r
Thanks to Russ Holmann for suggesting that the shutdown timeout be configurable.\r
Thanks to Paul Spause for spotting a bug with default registry entries.\r
Thanks to BUGHUNTER for spotting more GUI bugs.\r
+Thanks to Doug Watson for suggesting file rotation.\r
\r
Licence\r
-------\r
#include "nssm.h"\r
\r
-static enum { NSSM_TAB_APPLICATION, NSSM_TAB_SHUTDOWN, NSSM_TAB_EXIT, NSSM_TAB_IO, NSSM_TAB_ENVIRONMENT, NSSM_NUM_TABS };\r
+static enum { NSSM_TAB_APPLICATION, NSSM_TAB_SHUTDOWN, NSSM_TAB_EXIT, NSSM_TAB_IO, NSSM_TAB_ROTATION, NSSM_TAB_ENVIRONMENT, NSSM_NUM_TABS };\r
static HWND tablist[NSSM_NUM_TABS];\r
static int selected_tab;\r
\r
check_io(_T("stdin"), service->stdin_path, sizeof(service->stdin_path), IDC_STDIN);\r
check_io(_T("stdout"), service->stdout_path, sizeof(service->stdout_path), IDC_STDOUT);\r
check_io(_T("stderr"), service->stderr_path, sizeof(service->stderr_path), IDC_STDERR);\r
+\r
/* Override stdout and/or stderr. */\r
- if (SendDlgItemMessage(tablist[NSSM_TAB_IO], IDC_TRUNCATE, BM_GETCHECK, 0, 0) & BST_CHECKED) {\r
+ if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_TRUNCATE, BM_GETCHECK, 0, 0) & BST_CHECKED) {\r
if (service->stdout_path[0]) service->stdout_disposition = CREATE_ALWAYS;\r
if (service->stderr_path[0]) service->stderr_disposition = CREATE_ALWAYS;\r
}\r
\r
+ /* Get rotation stuff. */\r
+ if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE, BM_GETCHECK, 0, 0) & BST_CHECKED) {\r
+ service->rotate_files = true;\r
+ }\r
+ if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS_ENABLED, BM_GETCHECK, 0, 0) & BST_CHECKED) {\r
+ check_method_timeout(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS, &service->rotate_seconds);\r
+ }\r
+ if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW_ENABLED, BM_GETCHECK, 0, 0) & BST_CHECKED) {\r
+ check_method_timeout(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, &service->rotate_bytes_low);\r
+ }\r
+\r
/* Get environment. */\r
unsigned long envlen = (unsigned long) SendMessage(GetDlgItem(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT), WM_GETTEXTLENGTH, 0, 0);\r
if (envlen) {\r
case WM_COMMAND:\r
HWND dlg;\r
TCHAR buffer[MAX_PATH];\r
+ unsigned long state;\r
\r
switch (LOWORD(w)) {\r
/* Browse for application. */\r
GetDlgItemText(tab, IDC_STDERR, buffer, sizeof(buffer));\r
browse(dlg, buffer, 0, NSSM_GUI_BROWSE_FILTER_ALL_FILES, 0);\r
break;\r
+\r
+ /* Rotation. */\r
+ case IDC_ROTATE:\r
+ case IDC_ROTATE_SECONDS_ENABLED:\r
+ case IDC_ROTATE_BYTES_LOW_ENABLED:\r
+ if (SendDlgItemMessage(tab, LOWORD(w), BM_GETCHECK, 0, 0) & BST_CHECKED) state = BST_CHECKED;\r
+ else state = BST_UNCHECKED;\r
+ SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE, BM_SETCHECK, state, 0);\r
+ SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS_ENABLED, BM_SETCHECK, state, 0);\r
+ SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW_ENABLED, BM_SETCHECK, state, 0);\r
+ break;\r
}\r
return 1;\r
}\r
tablist[NSSM_TAB_IO] = CreateDialog(0, MAKEINTRESOURCE(IDD_IO), window, tab_dlg);\r
ShowWindow(tablist[NSSM_TAB_IO], SW_HIDE);\r
\r
+ /* Rotation tab. */\r
+ tab.pszText = message_string(NSSM_GUI_TAB_ROTATION);\r
+ tab.cchTextMax = (int) _tcslen(tab.pszText) + 1;\r
+ SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_ROTATION, (LPARAM) &tab);\r
+ tablist[NSSM_TAB_ROTATION] = CreateDialog(0, MAKEINTRESOURCE(IDD_ROTATION), window, tab_dlg);\r
+ ShowWindow(tablist[NSSM_TAB_ROTATION], SW_HIDE);\r
+\r
+ /* Set defaults. */\r
+ SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS, 0, 0);\r
+ SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, 0, 0);\r
+\r
/* Environment tab. */\r
tab.pszText = message_string(NSSM_GUI_TAB_ENVIRONMENT);\r
tab.cchTextMax = (int) _tcslen(tab.pszText) + 1;\r
return CreateFile(path, FILE_WRITE_DATA, sharing, attributes, disposition, flags, 0);\r
}\r
\r
-int get_output_handles(HKEY key, STARTUPINFO *si) {\r
+void rotate_file(TCHAR *service_name, TCHAR *path, unsigned long seconds, unsigned long low, unsigned long high) {\r
+ unsigned long error;\r
+\r
+ /* Now. */\r
+ SYSTEMTIME st;\r
+ GetSystemTime(&st);\r
+\r
+ BY_HANDLE_FILE_INFORMATION info;\r
+\r
+ /* Try to open the file to check if it exists and to get attributes. */\r
+ HANDLE file = CreateFile(path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);\r
+ if (file) {\r
+ /* Get file attributes. */\r
+ if (! GetFileInformationByHandle(file, &info)) {\r
+ /* Reuse current time for rotation timestamp. */\r
+ seconds = low = high = 0;\r
+ SystemTimeToFileTime(&st, &info.ftLastWriteTime);\r
+ }\r
+\r
+ CloseHandle(file);\r
+ }\r
+ else {\r
+ error = GetLastError();\r
+ if (error == ERROR_FILE_NOT_FOUND) return;\r
+ log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, service_name, path, _T("CreateFile()"), path, error_string(error), 0);\r
+ /* Reuse current time for rotation timestamp. */\r
+ seconds = low = high = 0;\r
+ SystemTimeToFileTime(&st, &info.ftLastWriteTime);\r
+ }\r
+\r
+ /* Check file age. */\r
+ if (seconds) {\r
+ FILETIME ft;\r
+ SystemTimeToFileTime(&st, &ft);\r
+\r
+ ULARGE_INTEGER s;\r
+ s.LowPart = ft.dwLowDateTime;\r
+ s.HighPart = ft.dwHighDateTime;\r
+ s.QuadPart -= seconds * 10000000LL;\r
+ ft.dwLowDateTime = s.LowPart;\r
+ ft.dwHighDateTime = s.HighPart;\r
+ if (CompareFileTime(&info.ftLastWriteTime, &ft) > 0) return;\r
+ }\r
+\r
+ /* Check file size. */\r
+ if (low || high) {\r
+ if (info.nFileSizeHigh < high) return;\r
+ if (info.nFileSizeHigh == high && info.nFileSizeLow < low) return;\r
+ }\r
+\r
+ /* Get new filename. */\r
+ FileTimeToSystemTime(&info.ftLastWriteTime, &st);\r
+\r
+ TCHAR buffer[MAX_PATH];\r
+ memmove(buffer, path, sizeof(buffer));\r
+ TCHAR *ext = PathFindExtension(buffer);\r
+ TCHAR extension[MAX_PATH];\r
+ _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);\r
+ *ext = _T('\0');\r
+ TCHAR rotated[MAX_PATH];\r
+ _sntprintf_s(rotated, _countof(rotated), _TRUNCATE, _T("%s%s"), buffer, extension);\r
+\r
+ /* Rotate. */\r
+ if (MoveFile(path, rotated)) return;\r
+ error = GetLastError();\r
+\r
+ log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, service_name, path, _T("MoveFile()"), rotated, error_string(error), 0);\r
+ return;\r
+}\r
+\r
+int get_output_handles(nssm_service_t *service, HKEY key, STARTUPINFO *si) {\r
TCHAR path[MAX_PATH];\r
TCHAR stdout_path[MAX_PATH];\r
unsigned long sharing, disposition, flags;\r
return 4;\r
}\r
\r
+ if (service->rotate_files) rotate_file(service->name, path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high);\r
si->hStdOutput = append_to_file(path, sharing, &attributes, disposition, flags);\r
if (! si->hStdOutput) return 5;\r
set_flags = true;\r
}\r
}\r
else {\r
+ if (service->rotate_files) rotate_file(service->name, path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high);\r
si->hStdError = append_to_file(path, sharing, &attributes, disposition, flags);\r
if (! si->hStdError) {\r
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, path, error_string(GetLastError()));\r
int get_createfile_parameters(HKEY, TCHAR *, TCHAR *, unsigned long *, unsigned long, unsigned long *, unsigned long, unsigned long *, unsigned long);\r
int set_createfile_parameter(HKEY, TCHAR *, TCHAR *, unsigned long);\r
HANDLE append_to_file(TCHAR *, unsigned long, SECURITY_ATTRIBUTES *, unsigned long, unsigned long);\r
-int get_output_handles(HKEY, STARTUPINFO *);\r
+void rotate_file(TCHAR *, TCHAR *, unsigned long, unsigned long, unsigned long);\r
+int get_output_handles(nssm_service_t *, HKEY, STARTUPINFO *);\r
void close_output_handles(STARTUPINFO *);\r
\r
#endif\r
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
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
+.
LTEXT "Error (stderr):", IDC_STATIC, 13, 50, 53, 8, SS_LEFT\r
EDITTEXT IDC_STDERR, 70, 48, 167, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES\r
DEFPUSHBUTTON "...", IDC_BROWSE_STDERR, 239, 47, 15, 14\r
- AUTOCHECKBOX "Replace files", IDC_TRUNCATE, 13, 60, 74, 8\r
+}\r
+\r
+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL\r
+IDD_ROTATION DIALOG 9, 20, 261, 75\r
+STYLE DS_SHELLFONT | WS_VISIBLE | WS_CHILD | DS_CONTROL\r
+FONT 8, "MS Sans Serif"\r
+{\r
+ GROUPBOX "File rotation", IDC_STATIC, 7, 7, 251, 68\r
+ AUTOCHECKBOX "Replace existing Output and/or Error files", IDC_TRUNCATE, 13, 18, 145, 8\r
+ AUTOCHECKBOX "Rotate files", IDC_ROTATE, 13, 32, 51, 8\r
+ AUTOCHECKBOX "Restrict rotation to files older than", IDC_ROTATE_SECONDS_ENABLED, 13, 46, 121, 8\r
+ EDITTEXT IDC_ROTATE_SECONDS, 140, 44, 29, 12, ES_AUTOHSCROLL | ES_NUMBER\r
+ LTEXT "s", IDC_STATIC, 171, 46, 8, 8, SS_LEFT\r
+ AUTOCHECKBOX "Restrict rotation to files bigger than", IDC_ROTATE_BYTES_LOW_ENABLED, 13, 60, 125, 8\r
+ EDITTEXT IDC_ROTATE_BYTES_LOW, 140, 58, 49, 12, ES_AUTOHSCROLL | ES_NUMBER\r
+ LTEXT "kB", IDC_STATIC, 191, 60, 8, 8, SS_LEFT\r
}\r
\r
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL\r
if (service->stderr_disposition != NSSM_STDERR_DISPOSITION) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_DISPOSITION, service->stderr_disposition);\r
if (service->stderr_flags != NSSM_STDERR_FLAGS) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_FLAGS, service->stderr_flags);\r
}\r
+ if (service->rotate_files) set_number(key, NSSM_REG_ROTATE, 1);\r
+ if (service->rotate_seconds) set_number(key, NSSM_REG_ROTATE_SECONDS, service->rotate_seconds);\r
+ if (service->rotate_bytes_low) set_number(key, NSSM_REG_ROTATE_BYTES_LOW, service->rotate_bytes_low);\r
+ if (service->rotate_bytes_high) set_number(key, NSSM_REG_ROTATE_BYTES_HIGH, service->rotate_bytes_high);\r
\r
/* Environment */\r
if (service->env) {\r
}\r
}\r
\r
+ /* Try to get file rotation settings - may fail. */\r
+ unsigned long rotate_files;\r
+ if (get_number(key, NSSM_REG_ROTATE, &rotate_files, false) == 1) {\r
+ if (rotate_files) service->rotate_files = true;\r
+ else service->rotate_files = false;\r
+ }\r
+ else service->rotate_files = false;\r
+ if (get_number(key, NSSM_REG_ROTATE_SECONDS, &service->rotate_seconds, false) != 1) service->rotate_seconds = 0;\r
+ if (get_number(key, NSSM_REG_ROTATE_BYTES_LOW, &service->rotate_bytes_low, false) != 1) service->rotate_bytes_low = 0;\r
+ if (get_number(key, NSSM_REG_ROTATE_BYTES_HIGH, &service->rotate_bytes_high, false) != 1) service->rotate_bytes_high = 0;\r
+\r
/* Change to startup directory in case stdout/stderr are relative paths. */\r
TCHAR cwd[MAX_PATH];\r
GetCurrentDirectory(_countof(cwd), cwd);\r
SetCurrentDirectory(service->dir);\r
\r
/* Try to get stdout and stderr */\r
- if (get_output_handles(key, si)) {\r
+ if (get_output_handles(service, key, si)) {\r
log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0);\r
RegCloseKey(key);\r
SetCurrentDirectory(cwd);\r
#define NSSM_REG_STDIO_SHARING _T("ShareMode")\r
#define NSSM_REG_STDIO_DISPOSITION _T("CreationDisposition")\r
#define NSSM_REG_STDIO_FLAGS _T("FlagsAndAttributes")\r
+#define NSSM_REG_ROTATE _T("AppRotateFiles")\r
+#define NSSM_REG_ROTATE_SECONDS _T("AppRotateSeconds")\r
+#define NSSM_REG_ROTATE_BYTES_LOW _T("AppRotateBytes")\r
+#define NSSM_REG_ROTATE_BYTES_HIGH _T("AppRotateBytesHigh")\r
#define NSSM_STDIO_LENGTH 29\r
\r
int create_messages();\r
#define IDD_REMOVE 103\r
#define IDD_APPLICATION 104\r
#define IDD_IO 105\r
-#define IDD_APPEXIT 106\r
-#define IDD_SHUTDOWN 107\r
-#define IDD_ENVIRONMENT 108\r
+#define IDD_ROTATION 106\r
+#define IDD_APPEXIT 107\r
+#define IDD_SHUTDOWN 108\r
+#define IDD_ENVIRONMENT 109\r
#define IDC_PATH 1000\r
#define IDC_TAB1 1001\r
#define IDC_CANCEL 1002\r
#define IDC_ENVIRONMENT 1025\r
#define IDC_ENVIRONMENT_REPLACE 1026\r
#define IDC_TRUNCATE 1027\r
+#define IDC_ROTATE 1028\r
+#define IDC_ROTATE_SECONDS_ENABLED 1029\r
+#define IDC_ROTATE_SECONDS 1030\r
+#define IDC_ROTATE_BYTES_LOW_ENABLED 1031\r
+#define IDC_ROTATE_BYTES_LOW 1032\r
\r
// Next default values for new objects\r
// \r
#ifdef APSTUDIO_INVOKED\r
#ifndef APSTUDIO_READONLY_SYMBOLS\r
-#define _APS_NEXT_RESOURCE_VALUE 109\r
+#define _APS_NEXT_RESOURCE_VALUE 110\r
#define _APS_NEXT_COMMAND_VALUE 40001\r
-#define _APS_NEXT_CONTROL_VALUE 1028\r
+#define _APS_NEXT_CONTROL_VALUE 1033\r
#define _APS_NEXT_SYMED_VALUE 101\r
#endif\r
#endif\r
unsigned long stderr_sharing;\r
unsigned long stderr_disposition;\r
unsigned long stderr_flags;\r
+ bool rotate_files;\r
+ unsigned long rotate_seconds;\r
+ unsigned long rotate_bytes_low;\r
+ unsigned long rotate_bytes_high;\r
unsigned long default_exit_action;\r
unsigned long throttle_delay;\r
unsigned long stop_method;\r