/* 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 * * 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 #include #include #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; }