diff options
Diffstat (limited to 'src/util-logopenfile.c')
-rw-r--r-- | src/util-logopenfile.c | 940 |
1 files changed, 940 insertions, 0 deletions
diff --git a/src/util-logopenfile.c b/src/util-logopenfile.c new file mode 100644 index 0000000..feca63f --- /dev/null +++ b/src/util-logopenfile.c @@ -0,0 +1,940 @@ +/* vi: set et ts=4: */ +/* Copyright (C) 2007-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Mike Pomraning <mpomraning@qualys.com> + * + * File-like output for logging: regular files and sockets. + */ + +#include "suricata-common.h" /* errno.h, string.h, etc. */ +#include "util-logopenfile.h" +#include "suricata.h" +#include "conf.h" /* ConfNode, etc. */ +#include "output.h" /* DEFAULT_LOG_* */ +#include "util-byte.h" +#include "util-conf.h" +#include "util-path.h" +#include "util-time.h" + +#if defined(HAVE_SYS_UN_H) && defined(HAVE_SYS_SOCKET_H) && defined(HAVE_SYS_TYPES_H) +#define BUILD_WITH_UNIXSOCKET +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#endif + +#ifdef HAVE_LIBHIREDIS +#include "util-log-redis.h" +#endif /* HAVE_LIBHIREDIS */ + +#define LOGFILE_NAME_MAX 255 + +static bool LogFileNewThreadedCtx(LogFileCtx *parent_ctx, const char *log_path, const char *append, + ThreadLogFileHashEntry *entry); + +// Threaded eve.json identifier +static SC_ATOMIC_DECL_AND_INIT_WITH_VAL(uint32_t, eve_file_id, 1); + +#ifdef BUILD_WITH_UNIXSOCKET +/** \brief connect to the indicated local stream socket, logging any errors + * \param path filesystem path to connect to + * \param log_err, non-zero if connect failure should be logged. + * \retval FILE* on success (fdopen'd wrapper of underlying socket) + * \retval NULL on error + */ +static FILE * +SCLogOpenUnixSocketFp(const char *path, int sock_type, int log_err) +{ + struct sockaddr_un saun; + int s = -1; + FILE * ret = NULL; + + memset(&saun, 0x00, sizeof(saun)); + + s = socket(PF_UNIX, sock_type, 0); + if (s < 0) goto err; + + saun.sun_family = AF_UNIX; + strlcpy(saun.sun_path, path, sizeof(saun.sun_path)); + + if (connect(s, (const struct sockaddr *)&saun, sizeof(saun)) < 0) + goto err; + + ret = fdopen(s, "w"); + if (ret == NULL) + goto err; + + return ret; + +err: + if (log_err) + SCLogWarning( + "Error connecting to socket \"%s\": %s (will keep trying)", path, strerror(errno)); + + if (s >= 0) + close(s); + + return NULL; +} + +/** + * \brief Attempt to reconnect a disconnected (or never-connected) Unix domain socket. + * \retval 1 if it is now connected; otherwise 0 + */ +static int SCLogUnixSocketReconnect(LogFileCtx *log_ctx) +{ + int disconnected = 0; + if (log_ctx->fp) { + SCLogWarning("Write error on Unix socket \"%s\": %s; reconnecting...", log_ctx->filename, + strerror(errno)); + fclose(log_ctx->fp); + log_ctx->fp = NULL; + log_ctx->reconn_timer = 0; + disconnected = 1; + } + + struct timeval tv; + uint64_t now; + gettimeofday(&tv, NULL); + now = (uint64_t)tv.tv_sec * 1000; + now += tv.tv_usec / 1000; /* msec resolution */ + if (log_ctx->reconn_timer != 0 && + (now - log_ctx->reconn_timer) < LOGFILE_RECONN_MIN_TIME) { + /* Don't bother to try reconnecting too often. */ + return 0; + } + log_ctx->reconn_timer = now; + + log_ctx->fp = SCLogOpenUnixSocketFp(log_ctx->filename, log_ctx->sock_type, 0); + if (log_ctx->fp) { + /* Connected at last (or reconnected) */ + SCLogNotice("Reconnected socket \"%s\"", log_ctx->filename); + } else if (disconnected) { + SCLogWarning("Reconnect failed: %s (will keep trying)", strerror(errno)); + } + + return log_ctx->fp ? 1 : 0; +} + +static int SCLogFileWriteSocket(const char *buffer, int buffer_len, + LogFileCtx *ctx) +{ + int tries = 0; + int ret = 0; + bool reopen = false; + if (ctx->fp == NULL && ctx->is_sock) { + SCLogUnixSocketReconnect(ctx); + } +tryagain: + ret = -1; + reopen = 0; + errno = 0; + if (ctx->fp != NULL) { + int fd = fileno(ctx->fp); + ssize_t size = send(fd, buffer, buffer_len, ctx->send_flags); + if (size > -1) { + ret = 0; + } else { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + SCLogDebug("Socket would block, dropping event."); + } else if (errno == EINTR) { + if (tries++ == 0) { + SCLogDebug("Interrupted system call, trying again."); + goto tryagain; + } + SCLogDebug("Too many interrupted system calls, " + "dropping event."); + } else { + /* Some other error. Assume badness and reopen. */ + SCLogDebug("Send failed: %s", strerror(errno)); + reopen = true; + } + } + } + + if (reopen && tries++ == 0) { + if (SCLogUnixSocketReconnect(ctx)) { + goto tryagain; + } + } + + if (ret == -1) { + ctx->dropped++; + } + + return ret; +} +#endif /* BUILD_WITH_UNIXSOCKET */ +static inline void OutputWriteLock(pthread_mutex_t *m) +{ + SCMutexLock(m); + +} + +/** + * \brief Write buffer to log file. + * \retval 0 on failure; otherwise, the return value of fwrite_unlocked (number of + * characters successfully written). + */ +static int SCLogFileWriteNoLock(const char *buffer, int buffer_len, LogFileCtx *log_ctx) +{ + int ret = 0; + + BUG_ON(log_ctx->is_sock); + + /* Check for rotation. */ + if (log_ctx->rotation_flag) { + log_ctx->rotation_flag = 0; + SCConfLogReopen(log_ctx); + } + + if (log_ctx->flags & LOGFILE_ROTATE_INTERVAL) { + time_t now = time(NULL); + if (now >= log_ctx->rotate_time) { + SCConfLogReopen(log_ctx); + log_ctx->rotate_time = now + log_ctx->rotate_interval; + } + } + + if (log_ctx->fp) { + SCClearErrUnlocked(log_ctx->fp); + if (1 != SCFwriteUnlocked(buffer, buffer_len, 1, log_ctx->fp)) { + /* Only the first error is logged */ + if (!log_ctx->output_errors) { + SCLogError("%s error while writing to %s", + SCFerrorUnlocked(log_ctx->fp) ? strerror(errno) : "unknown error", + log_ctx->filename); + } + log_ctx->output_errors++; + } else { + SCFflushUnlocked(log_ctx->fp); + } + } + + return ret; +} + +/** + * \brief Write buffer to log file. + * \retval 0 on failure; otherwise, the return value of fwrite (number of + * characters successfully written). + */ +static int SCLogFileWrite(const char *buffer, int buffer_len, LogFileCtx *log_ctx) +{ + OutputWriteLock(&log_ctx->fp_mutex); + int ret = 0; + +#ifdef BUILD_WITH_UNIXSOCKET + if (log_ctx->is_sock) { + ret = SCLogFileWriteSocket(buffer, buffer_len, log_ctx); + } else +#endif + { + + /* Check for rotation. */ + if (log_ctx->rotation_flag) { + log_ctx->rotation_flag = 0; + SCConfLogReopen(log_ctx); + } + + if (log_ctx->flags & LOGFILE_ROTATE_INTERVAL) { + time_t now = time(NULL); + if (now >= log_ctx->rotate_time) { + SCConfLogReopen(log_ctx); + log_ctx->rotate_time = now + log_ctx->rotate_interval; + } + } + + if (log_ctx->fp) { + clearerr(log_ctx->fp); + if (1 != fwrite(buffer, buffer_len, 1, log_ctx->fp)) { + /* Only the first error is logged */ + if (!log_ctx->output_errors) { + SCLogError("%s error while writing to %s", + ferror(log_ctx->fp) ? strerror(errno) : "unknown error", + log_ctx->filename); + } + log_ctx->output_errors++; + } else { + fflush(log_ctx->fp); + } + } + } + + SCMutexUnlock(&log_ctx->fp_mutex); + + return ret; +} + +/** \brief generate filename based on pattern + * \param pattern pattern to use + * \retval char* on success + * \retval NULL on error + */ +static char *SCLogFilenameFromPattern(const char *pattern) +{ + char *filename = SCMalloc(PATH_MAX); + if (filename == NULL) { + return NULL; + } + + int rc = SCTimeToStringPattern(time(NULL), pattern, filename, PATH_MAX); + if (rc != 0) { + SCFree(filename); + return NULL; + } + + return filename; +} + +static void SCLogFileCloseNoLock(LogFileCtx *log_ctx) +{ + SCLogDebug("Closing %s", log_ctx->filename); + if (log_ctx->fp) + fclose(log_ctx->fp); + + if (log_ctx->output_errors) { + SCLogError("There were %" PRIu64 " output errors to %s", log_ctx->output_errors, + log_ctx->filename); + } +} + +static void SCLogFileClose(LogFileCtx *log_ctx) +{ + SCMutexLock(&log_ctx->fp_mutex); + SCLogFileCloseNoLock(log_ctx); + SCMutexUnlock(&log_ctx->fp_mutex); +} + +static char ThreadLogFileHashCompareFunc( + void *data1, uint16_t datalen1, void *data2, uint16_t datalen2) +{ + ThreadLogFileHashEntry *p1 = (ThreadLogFileHashEntry *)data1; + ThreadLogFileHashEntry *p2 = (ThreadLogFileHashEntry *)data2; + + if (p1 == NULL || p2 == NULL) + return 0; + + return p1->thread_id == p2->thread_id; +} +static uint32_t ThreadLogFileHashFunc(HashTable *ht, void *data, uint16_t datalen) +{ + const ThreadLogFileHashEntry *ent = (ThreadLogFileHashEntry *)data; + + return ent->thread_id % ht->array_size; +} + +static void ThreadLogFileHashFreeFunc(void *data) +{ + BUG_ON(data == NULL); + ThreadLogFileHashEntry *thread_ent = (ThreadLogFileHashEntry *)data; + + if (thread_ent) { + LogFileCtx *lf_ctx = thread_ent->ctx; + /* Free the leaf log file entries */ + if (!lf_ctx->threaded) { + LogFileFreeCtx(lf_ctx); + } + SCFree(thread_ent); + } +} + +bool SCLogOpenThreadedFile(const char *log_path, const char *append, LogFileCtx *parent_ctx) +{ + parent_ctx->threads = SCCalloc(1, sizeof(LogThreadedFileCtx)); + if (!parent_ctx->threads) { + SCLogError("Unable to allocate threads container"); + return false; + } + + parent_ctx->threads->ht = HashTableInit(255, ThreadLogFileHashFunc, + ThreadLogFileHashCompareFunc, ThreadLogFileHashFreeFunc); + if (!parent_ctx->threads->ht) { + FatalError("Unable to initialize thread/entry hash table"); + } + + parent_ctx->threads->append = SCStrdup(append == NULL ? DEFAULT_LOG_MODE_APPEND : append); + if (!parent_ctx->threads->append) { + SCLogError("Unable to allocate threads append setting"); + goto error_exit; + } + + SCMutexInit(&parent_ctx->threads->mutex, NULL); + return true; + +error_exit: + + if (parent_ctx->threads->append) { + SCFree(parent_ctx->threads->append); + } + if (parent_ctx->threads->ht) { + HashTableFree(parent_ctx->threads->ht); + } + SCFree(parent_ctx->threads); + parent_ctx->threads = NULL; + return false; +} + +/** \brief open the indicated file, logging any errors + * \param path filesystem path to open + * \param append_setting open file with O_APPEND: "yes" or "no" + * \param mode permissions to set on file + * \retval FILE* on success + * \retval NULL on error + */ +static FILE * +SCLogOpenFileFp(const char *path, const char *append_setting, uint32_t mode) +{ + FILE *ret = NULL; + + char *filename = SCLogFilenameFromPattern(path); + if (filename == NULL) { + return NULL; + } + + int rc = SCCreateDirectoryTree(filename, false); + if (rc < 0) { + SCFree(filename); + return NULL; + } + + if (ConfValIsTrue(append_setting)) { + ret = fopen(filename, "a"); + } else { + ret = fopen(filename, "w"); + } + + if (ret == NULL) { + SCLogError("Error opening file: \"%s\": %s", filename, strerror(errno)); + } else { + if (mode != 0) { +#ifdef OS_WIN32 + int r = _chmod(filename, (mode_t)mode); +#else + int r = fchmod(fileno(ret), (mode_t)mode); +#endif + if (r < 0) { + SCLogWarning("Could not chmod %s to %o: %s", filename, mode, strerror(errno)); + } + } + } + + SCFree(filename); + return ret; +} + +/** \brief open a generic output "log file", which may be a regular file or a socket + * \param conf ConfNode structure for the output section in question + * \param log_ctx Log file context allocated by caller + * \param default_filename Default name of file to open, if not specified in ConfNode + * \param rotate Register the file for rotation in HUP. + * \retval 0 on success + * \retval -1 on error + */ +int +SCConfLogOpenGeneric(ConfNode *conf, + LogFileCtx *log_ctx, + const char *default_filename, + int rotate) +{ + char log_path[PATH_MAX]; + const char *log_dir; + const char *filename, *filetype; + + // Arg check + if (conf == NULL || log_ctx == NULL || default_filename == NULL) { + SCLogError("SCConfLogOpenGeneric(conf %p, ctx %p, default %p) " + "missing an argument", + conf, log_ctx, default_filename); + return -1; + } + if (log_ctx->fp != NULL) { + SCLogError("SCConfLogOpenGeneric: previously initialized Log CTX " + "encountered"); + return -1; + } + + // Resolve the given config + filename = ConfNodeLookupChildValue(conf, "filename"); + if (filename == NULL) + filename = default_filename; + + log_dir = ConfigGetLogDirectory(); + + if (PathIsAbsolute(filename)) { + snprintf(log_path, PATH_MAX, "%s", filename); + } else { + snprintf(log_path, PATH_MAX, "%s/%s", log_dir, filename); + } + + /* Rotate log file based on time */ + const char *rotate_int = ConfNodeLookupChildValue(conf, "rotate-interval"); + if (rotate_int != NULL) { + time_t now = time(NULL); + log_ctx->flags |= LOGFILE_ROTATE_INTERVAL; + + /* Use a specific time */ + if (strcmp(rotate_int, "minute") == 0) { + log_ctx->rotate_time = now + SCGetSecondsUntil(rotate_int, now); + log_ctx->rotate_interval = 60; + } else if (strcmp(rotate_int, "hour") == 0) { + log_ctx->rotate_time = now + SCGetSecondsUntil(rotate_int, now); + log_ctx->rotate_interval = 3600; + } else if (strcmp(rotate_int, "day") == 0) { + log_ctx->rotate_time = now + SCGetSecondsUntil(rotate_int, now); + log_ctx->rotate_interval = 86400; + } + + /* Use a timer */ + else { + log_ctx->rotate_interval = SCParseTimeSizeString(rotate_int); + if (log_ctx->rotate_interval == 0) { + FatalError("invalid rotate-interval value"); + } + log_ctx->rotate_time = now + log_ctx->rotate_interval; + } + } + + filetype = ConfNodeLookupChildValue(conf, "filetype"); + if (filetype == NULL) + filetype = DEFAULT_LOG_FILETYPE; + + const char *filemode = ConfNodeLookupChildValue(conf, "filemode"); + uint32_t mode = 0; + if (filemode != NULL && StringParseUint32(&mode, 8, (uint16_t)strlen(filemode), filemode) > 0) { + log_ctx->filemode = mode; + } + + const char *append = ConfNodeLookupChildValue(conf, "append"); + if (append == NULL) + append = DEFAULT_LOG_MODE_APPEND; + + /* JSON flags */ + log_ctx->json_flags = JSON_PRESERVE_ORDER|JSON_COMPACT| + JSON_ENSURE_ASCII|JSON_ESCAPE_SLASH; + + ConfNode *json_flags = ConfNodeLookupChild(conf, "json"); + + if (json_flags != 0) { + const char *preserve_order = ConfNodeLookupChildValue(json_flags, + "preserve-order"); + if (preserve_order != NULL && ConfValIsFalse(preserve_order)) + log_ctx->json_flags &= ~(JSON_PRESERVE_ORDER); + + const char *compact = ConfNodeLookupChildValue(json_flags, "compact"); + if (compact != NULL && ConfValIsFalse(compact)) + log_ctx->json_flags &= ~(JSON_COMPACT); + + const char *ensure_ascii = ConfNodeLookupChildValue(json_flags, + "ensure-ascii"); + if (ensure_ascii != NULL && ConfValIsFalse(ensure_ascii)) + log_ctx->json_flags &= ~(JSON_ENSURE_ASCII); + + const char *escape_slash = ConfNodeLookupChildValue(json_flags, + "escape-slash"); + if (escape_slash != NULL && ConfValIsFalse(escape_slash)) + log_ctx->json_flags &= ~(JSON_ESCAPE_SLASH); + } + +#ifdef BUILD_WITH_UNIXSOCKET + if (log_ctx->threaded) { + if (strcasecmp(filetype, "unix_stream") == 0 || strcasecmp(filetype, "unix_dgram") == 0) { + FatalError("Socket file types do not support threaded output"); + } + } +#endif + // Now, what have we been asked to open? + if (strcasecmp(filetype, "unix_stream") == 0) { +#ifdef BUILD_WITH_UNIXSOCKET + /* Don't bail. May be able to connect later. */ + log_ctx->is_sock = 1; + log_ctx->sock_type = SOCK_STREAM; + log_ctx->fp = SCLogOpenUnixSocketFp(log_path, SOCK_STREAM, 1); +#else + return -1; +#endif + } else if (strcasecmp(filetype, "unix_dgram") == 0) { +#ifdef BUILD_WITH_UNIXSOCKET + /* Don't bail. May be able to connect later. */ + log_ctx->is_sock = 1; + log_ctx->sock_type = SOCK_DGRAM; + log_ctx->fp = SCLogOpenUnixSocketFp(log_path, SOCK_DGRAM, 1); +#else + return -1; +#endif + } else if (strcasecmp(filetype, DEFAULT_LOG_FILETYPE) == 0 || + strcasecmp(filetype, "file") == 0) { + log_ctx->is_regular = 1; + if (!log_ctx->threaded) { + log_ctx->fp = SCLogOpenFileFp(log_path, append, log_ctx->filemode); + if (log_ctx->fp == NULL) + return -1; // Error already logged by Open...Fp routine + } else { + if (!SCLogOpenThreadedFile(log_path, append, log_ctx)) { + return -1; + } + } + if (rotate) { + OutputRegisterFileRotationFlag(&log_ctx->rotation_flag); + } + } else { + SCLogError("Invalid entry for " + "%s.filetype. Expected \"regular\" (default), \"unix_stream\", " + "or \"unix_dgram\"", + conf->name); + } + log_ctx->filename = SCStrdup(log_path); + if (unlikely(log_ctx->filename == NULL)) { + SCLogError("Failed to allocate memory for filename"); + return -1; + } + +#ifdef BUILD_WITH_UNIXSOCKET + /* If a socket and running live, do non-blocking writes. */ + if (log_ctx->is_sock && !IsRunModeOffline(RunmodeGetCurrent())) { + SCLogInfo("Setting logging socket of non-blocking in live mode."); + log_ctx->send_flags |= MSG_DONTWAIT; + } +#endif + SCLogInfo("%s output device (%s) initialized: %s", conf->name, filetype, + filename); + + return 0; +} + +/** + * \brief Reopen a regular log file with the side-affect of truncating it. + * + * This is useful to clear the log file and start a new one, or to + * re-open the file after its been moved by something external + * (eg. logrotate). + */ +int SCConfLogReopen(LogFileCtx *log_ctx) +{ + if (!log_ctx->is_regular) { + /* Not supported and not needed on non-regular files. */ + return 0; + } + + if (log_ctx->filename == NULL) { + SCLogWarning("Can't re-open LogFileCtx without a filename."); + return -1; + } + + if (log_ctx->fp != NULL) { + fclose(log_ctx->fp); + } + + /* Reopen the file. Append is forced in case the file was not + * moved as part of a rotation process. */ + SCLogDebug("Reopening log file %s.", log_ctx->filename); + log_ctx->fp = SCLogOpenFileFp(log_ctx->filename, "yes", log_ctx->filemode); + if (log_ctx->fp == NULL) { + return -1; // Already logged by Open..Fp routine. + } + + return 0; +} + +/** \brief LogFileNewCtx() Get a new LogFileCtx + * \retval LogFileCtx * pointer if successful, NULL if error + * */ +LogFileCtx *LogFileNewCtx(void) +{ + LogFileCtx* lf_ctx; + lf_ctx = (LogFileCtx*)SCCalloc(1, sizeof(LogFileCtx)); + + if (lf_ctx == NULL) + return NULL; + + lf_ctx->Write = SCLogFileWrite; + lf_ctx->Close = SCLogFileClose; + + return lf_ctx; +} + +/** \brief LogFileThread2Slot() Return a file entry + * \retval ThreadLogFileHashEntry * file entry for caller + * + * This function returns the file entry for the calling thread. + * Each thread -- identified by its operating system thread-id -- has its + * own file entry that includes a file pointer. + */ +static ThreadLogFileHashEntry *LogFileThread2Slot(LogThreadedFileCtx *parent) +{ + ThreadLogFileHashEntry thread_hash_entry; + + /* Check hash table for thread id*/ + thread_hash_entry.thread_id = SCGetThreadIdLong(); + ThreadLogFileHashEntry *ent = + HashTableLookup(parent->ht, &thread_hash_entry, sizeof(thread_hash_entry)); + + if (!ent) { + ent = SCCalloc(1, sizeof(*ent)); + if (!ent) { + FatalError("Unable to allocate thread/entry entry"); + } + ent->thread_id = thread_hash_entry.thread_id; + SCLogDebug("Trying to add thread %ld to entry %d", ent->thread_id, ent->slot_number); + if (0 != HashTableAdd(parent->ht, ent, 0)) { + FatalError("Unable to add thread/entry mapping"); + } + } + return ent; +} + +/** \brief LogFileEnsureExists() Ensure a log file context for the thread exists + * \param parent_ctx + * \retval LogFileCtx * pointer if successful; NULL otherwise + */ +LogFileCtx *LogFileEnsureExists(LogFileCtx *parent_ctx) +{ + /* threaded output disabled */ + if (!parent_ctx->threaded) + return parent_ctx; + + SCMutexLock(&parent_ctx->threads->mutex); + /* Find this thread's entry */ + ThreadLogFileHashEntry *entry = LogFileThread2Slot(parent_ctx->threads); + SCLogDebug("Adding reference for thread %ld [slot %d] to file %s [ctx %p]", SCGetThreadIdLong(), + entry->slot_number, parent_ctx->filename, parent_ctx); + + bool new = entry->isopen; + /* has it been opened yet? */ + if (!entry->isopen) { + SCLogDebug("Opening new file for thread/slot %d to file %s [ctx %p]", entry->slot_number, + parent_ctx->filename, parent_ctx); + if (LogFileNewThreadedCtx( + parent_ctx, parent_ctx->filename, parent_ctx->threads->append, entry)) { + entry->isopen = true; + } else { + SCLogError( + "Unable to open slot %d for file %s", entry->slot_number, parent_ctx->filename); + (void)HashTableRemove(parent_ctx->threads->ht, entry, 0); + } + } + SCMutexUnlock(&parent_ctx->threads->mutex); + + if (sc_log_global_log_level >= SC_LOG_DEBUG) { + if (new) { + SCLogDebug("Existing file for thread/entry %p reference to file %s [ctx %p]", entry, + parent_ctx->filename, parent_ctx); + } + } + + return entry->ctx; +} + +/** \brief LogFileThreadedName() Create file name for threaded EVE storage + * + */ +static bool LogFileThreadedName( + const char *original_name, char *threaded_name, size_t len, uint32_t unique_id) +{ + sc_errno = SC_OK; + + if (strcmp("/dev/null", original_name) == 0) { + strlcpy(threaded_name, original_name, len); + return true; + } + + const char *base = SCBasename(original_name); + if (!base) { + FatalError("Invalid filename for threaded mode \"%s\"; " + "no basename found.", + original_name); + } + + /* Check if basename has an extension */ + char *dot = strrchr(base, '.'); + if (dot) { + char *tname = SCStrdup(original_name); + if (!tname) { + sc_errno = SC_ENOMEM; + return false; + } + + /* Fetch extension location from original, not base + * for update + */ + dot = strrchr(original_name, '.'); + int dotpos = dot - original_name; + tname[dotpos] = '\0'; + char *ext = tname + dotpos + 1; + if (strlen(tname) && strlen(ext)) { + snprintf(threaded_name, len, "%s.%u.%s", tname, unique_id, ext); + } else { + FatalError("Invalid filename for threaded mode \"%s\"; " + "filenames must include an extension, e.g: \"name.ext\"", + original_name); + } + SCFree(tname); + } else { + snprintf(threaded_name, len, "%s.%u", original_name, unique_id); + } + return true; +} + +/** \brief LogFileNewThreadedCtx() Create file context for threaded output + * \param parent_ctx + * \param log_path + * \param append + * \param entry + */ +static bool LogFileNewThreadedCtx(LogFileCtx *parent_ctx, const char *log_path, const char *append, + ThreadLogFileHashEntry *entry) +{ + LogFileCtx *thread = SCCalloc(1, sizeof(LogFileCtx)); + if (!thread) { + SCLogError("Unable to allocate thread file context entry %p", entry); + return false; + } + + *thread = *parent_ctx; + if (parent_ctx->type == LOGFILE_TYPE_FILE) { + char fname[LOGFILE_NAME_MAX]; + if (!LogFileThreadedName(log_path, fname, sizeof(fname), SC_ATOMIC_ADD(eve_file_id, 1))) { + SCLogError("Unable to create threaded filename for log"); + goto error; + } + SCLogDebug("Thread open -- using name %s [replaces %s]", fname, log_path); + thread->fp = SCLogOpenFileFp(fname, append, thread->filemode); + if (thread->fp == NULL) { + goto error; + } + thread->filename = SCStrdup(fname); + if (!thread->filename) { + SCLogError("Unable to duplicate filename for context entry %p", entry); + goto error; + } + thread->is_regular = true; + thread->Write = SCLogFileWriteNoLock; + thread->Close = SCLogFileCloseNoLock; + OutputRegisterFileRotationFlag(&thread->rotation_flag); + } else if (parent_ctx->type == LOGFILE_TYPE_PLUGIN) { + entry->slot_number = SC_ATOMIC_ADD(eve_file_id, 1); + thread->plugin.plugin->ThreadInit( + thread->plugin.init_data, entry->slot_number, &thread->plugin.thread_data); + } + thread->threaded = false; + thread->parent = parent_ctx; + thread->entry = entry; + entry->ctx = thread; + + return true; + +error: + if (parent_ctx->type == LOGFILE_TYPE_FILE) { + SC_ATOMIC_SUB(eve_file_id, 1); + if (thread->fp) { + thread->Close(thread); + } + } + + if (thread) { + SCFree(thread); + } + return false; +} + +/** \brief LogFileFreeCtx() Destroy a LogFileCtx (Close the file and free memory) + * \param lf_ctx pointer to the OutputCtx + * \retval int 1 if successful, 0 if error + * */ +int LogFileFreeCtx(LogFileCtx *lf_ctx) +{ + if (lf_ctx == NULL) { + SCReturnInt(0); + } + + if (lf_ctx->type == LOGFILE_TYPE_PLUGIN && lf_ctx->parent != NULL) { + lf_ctx->plugin.plugin->ThreadDeinit(lf_ctx->plugin.init_data, lf_ctx->plugin.thread_data); + } + + if (lf_ctx->threaded) { + BUG_ON(lf_ctx->threads == NULL); + SCMutexDestroy(&lf_ctx->threads->mutex); + if (lf_ctx->threads->append) + SCFree(lf_ctx->threads->append); + if (lf_ctx->threads->ht) { + HashTableFree(lf_ctx->threads->ht); + } + SCFree(lf_ctx->threads); + } else { + if (lf_ctx->type != LOGFILE_TYPE_PLUGIN) { + if (lf_ctx->fp != NULL) { + lf_ctx->Close(lf_ctx); + } + } + SCMutexDestroy(&lf_ctx->fp_mutex); + } + + if (lf_ctx->prefix != NULL) { + SCFree(lf_ctx->prefix); + lf_ctx->prefix_len = 0; + } + + if(lf_ctx->filename != NULL) + SCFree(lf_ctx->filename); + + if (lf_ctx->sensor_name) + SCFree(lf_ctx->sensor_name); + + if (!lf_ctx->threaded) { + OutputUnregisterFileRotationFlag(&lf_ctx->rotation_flag); + } + + /* Deinitialize output plugins. We only want to call this for the + * parent of threaded output, or always for non-threaded + * output. */ + if (lf_ctx->type == LOGFILE_TYPE_PLUGIN && lf_ctx->parent == NULL) { + lf_ctx->plugin.plugin->Deinit(lf_ctx->plugin.init_data); + } + + memset(lf_ctx, 0, sizeof(*lf_ctx)); + SCFree(lf_ctx); + + SCReturnInt(1); +} + +int LogFileWrite(LogFileCtx *file_ctx, MemBuffer *buffer) +{ + if (file_ctx->type == LOGFILE_TYPE_FILE || file_ctx->type == LOGFILE_TYPE_UNIX_DGRAM || + file_ctx->type == LOGFILE_TYPE_UNIX_STREAM) { + /* append \n for files only */ + MemBufferWriteString(buffer, "\n"); + file_ctx->Write((const char *)MEMBUFFER_BUFFER(buffer), + MEMBUFFER_OFFSET(buffer), file_ctx); + } else if (file_ctx->type == LOGFILE_TYPE_PLUGIN) { + file_ctx->plugin.plugin->Write((const char *)MEMBUFFER_BUFFER(buffer), + MEMBUFFER_OFFSET(buffer), file_ctx->plugin.init_data, file_ctx->plugin.thread_data); + } +#ifdef HAVE_LIBHIREDIS + else if (file_ctx->type == LOGFILE_TYPE_REDIS) { + SCMutexLock(&file_ctx->fp_mutex); + LogFileWriteRedis(file_ctx, (const char *)MEMBUFFER_BUFFER(buffer), + MEMBUFFER_OFFSET(buffer)); + SCMutexUnlock(&file_ctx->fp_mutex); + } +#endif + + return 0; +} |