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