From ca93c76ca1193c3d613870a6bca244b3c43080c3 Mon Sep 17 00:00:00 2001 From: Jason Ish Date: Tue, 13 Oct 2026 00:23:41 -0600 Subject: [PATCH] windows: open log files with shared delete access On Windows, fopen() does not open files with FILE_SHARE_DELETE, so a log file can not be renamed or deleted while Suricata has it open. This prevents external tools from rotating log files. Add SCWin32Fopen(), an fopen() replacement for the "a" and "w" modes that opens the file with CreateFile() and FILE_SHARE_DELETE, and use it for log files, including suricata.log. Ticket: 7757 --- src/util-debug.c | 9 ++++- src/util-logopenfile.c | 7 +--- src/util-path.c | 27 ++++++++++++++ src/util-path.h | 1 + src/win32-misc.c | 83 ++++++++++++++++++++++++++++++++++++++++++ src/win32-misc.h | 2 + 6 files changed, 122 insertions(+), 7 deletions(-) diff --git a/src/util-debug.c b/src/util-debug.c index eeab37b2ce..3009fa97d3 100644 --- a/src/util-debug.c +++ b/src/util-debug.c @@ -35,6 +35,10 @@ #include "util-path.h" #include "util-syslog.h" +#ifdef OS_WIN32 +#include "win32-misc.h" +#endif /* OS_WIN32 */ + // clang-format off /* holds the string-enum mapping for the enums held in the table SCLogLevel */ SCEnumCharMap sc_log_level_map[] = { @@ -631,7 +635,7 @@ static int SCLogReopen(SCLogOPIfaceCtx *op_iface_ctx) if (op_iface_ctx->file_d != NULL) { fclose(op_iface_ctx->file_d); } - op_iface_ctx->file_d = fopen(op_iface_ctx->file, "a"); + op_iface_ctx->file_d = SCFopenLogFile(op_iface_ctx->file, "a"); if (op_iface_ctx->file_d == NULL) { return -1; } @@ -846,7 +850,8 @@ static inline SCLogOPIfaceCtx *SCLogInitFileOPIface(const char *file, uint32_t u iface_ctx->iface = SC_LOG_OP_IFACE_FILE; iface_ctx->type = type; - if ( (iface_ctx->file_d = fopen(file, "a")) == NULL) { + iface_ctx->file_d = SCFopenLogFile(file, "a"); + if (iface_ctx->file_d == NULL) { SCLogWarning("error opening file %s: %s", file, strerror(errno)); goto error; } diff --git a/src/util-logopenfile.c b/src/util-logopenfile.c index b9a3929244..35d6877684 100644 --- a/src/util-logopenfile.c +++ b/src/util-logopenfile.c @@ -518,11 +518,8 @@ static FILE *SCLogOpenFileFp( return NULL; } - if (SCConfValIsTrue(append_setting)) { - ret = fopen(filename, "a"); - } else { - ret = fopen(filename, "w"); - } + const char *fmode = SCConfValIsTrue(append_setting) ? "a" : "w"; + ret = SCFopenLogFile(filename, fmode); if (ret == NULL) { SCLogError("Error opening file: \"%s\": %s", filename, strerror(errno)); diff --git a/src/util-path.c b/src/util-path.c index 8226704b0d..2e0cbc41df 100644 --- a/src/util-path.c +++ b/src/util-path.c @@ -28,6 +28,7 @@ #include "util-path.h" #ifdef OS_WIN32 +#include "win32-misc.h" #define DIRECTORY_SEPARATOR '\\' #else #define DIRECTORY_SEPARATOR '/' @@ -275,6 +276,32 @@ bool SCPathContainsTraversal(const char *path) #endif } +/** + * \brief Open a text log file for append or truncate. + * + * On Windows, allows the file to be renamed or deleted while open so that + * external tools can rotate it. Only modes "a" and "w" are supported on + * all platforms; other modes fail with errno set to EINVAL. + * + * \param path The file path to open + * \param mode "a" to append or "w" to truncate + * \retval FILE* on success + * \retval NULL on error with errno set + */ +FILE *SCFopenLogFile(const char *path, const char *mode) +{ + if (strcmp(mode, "a") != 0 && strcmp(mode, "w") != 0) { + errno = EINVAL; + return NULL; + } + +#ifdef OS_WIN32 + return SCWin32Fopen(path, mode); +#else + return fopen(path, mode); +#endif +} + /** * \brief Update access and modification time of an existing file to 'now'. * \param path The file path to touch diff --git a/src/util-path.h b/src/util-path.h index e835d847dd..18c96b1afd 100644 --- a/src/util-path.h +++ b/src/util-path.h @@ -60,5 +60,6 @@ char *SCRealPath(const char *path, char *resolved_path); const char *SCBasename(const char *path); bool SCPathContainsTraversal(const char *path); int SCTouchFile(const char *path); +FILE *SCFopenLogFile(const char *path, const char *mode); #endif /* SURICATA_UTIL_PATH_H */ diff --git a/src/win32-misc.c b/src/win32-misc.c index 1dc28a6926..1d67fcfba3 100644 --- a/src/win32-misc.c +++ b/src/win32-misc.c @@ -30,6 +30,9 @@ #include "direct.h" #include "util-ip.h" +#include +#include + void setenv(const char *name, const char *value, int overwrite) { if (overwrite || NULL == getenv(name)) { @@ -120,4 +123,84 @@ int inet_pton(int af, const char *src, void *dst) } #endif +/** + * \brief Open a file for writing. + * + * Like fopen() with a mode of "a" or "w", but also opens the file with + * FILE_SHARE_DELETE, which fopen() does not. This allows the file to be + * renamed or deleted while open, for example so log files can be rotated by + * external tools, then reopened by Suricata. + * + * \param path The file path to open + * \param mode "a" to append or "w" to truncate + * \retval FILE* on success + * \retval NULL on error with errno set + */ +FILE *SCWin32Fopen(const char *path, const char *mode) +{ + bool append; + if (strcmp(mode, "a") == 0) { + append = true; + } else if (strcmp(mode, "w") == 0) { + append = false; + } else { + errno = EINVAL; + return NULL; + } + + HANDLE h = + CreateFileA(path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, append ? OPEN_ALWAYS : CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (h == INVALID_HANDLE_VALUE) { + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + errno = ENOENT; + break; + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + case ERROR_LOCK_VIOLATION: + errno = EACCES; + break; + case ERROR_INVALID_NAME: + case ERROR_BAD_PATHNAME: + case ERROR_DIRECTORY: + errno = EINVAL; + break; + case ERROR_FILENAME_EXCED_RANGE: + errno = ENAMETOOLONG; + break; + case ERROR_TOO_MANY_OPEN_FILES: + errno = EMFILE; + break; + case ERROR_DISK_FULL: + case ERROR_HANDLE_DISK_FULL: + errno = ENOSPC; + break; + default: + errno = EIO; + break; + } + return NULL; + } + + /* Text mode, to match fopen() without a 'b' mode. */ + int fd = _open_osfhandle((intptr_t)h, _O_TEXT | (append ? _O_APPEND : 0)); + if (fd == -1) { + CloseHandle(h); + return NULL; + } + + FILE *fp = _fdopen(fd, mode); + if (fp == NULL) { + /* Also closes the underlying handle. */ + int err = errno; + _close(fd); + errno = err; + return NULL; + } + + return fp; +} + #endif /* OS_WIN32 */ diff --git a/src/win32-misc.h b/src/win32-misc.h index 4baf46f39d..35fe8a62cb 100644 --- a/src/win32-misc.h +++ b/src/win32-misc.h @@ -40,4 +40,6 @@ int inet_pton(int af, const char *src, void *dst); #define geteuid() (0) +FILE *SCWin32Fopen(const char *path, const char *mode); + #endif