suricata
util-logopenfile.c
Go to the documentation of this file.
1/* Copyright (C) 2007-2022 Open Information Security Foundation
2 *
3 * You can copy, redistribute or modify this Program under the terms of
4 * the GNU General Public License version 2 as published by the Free
5 * Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * version 2 along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15 * 02110-1301, USA.
16 */
17
18/**
19 * \file
20 *
21 * \author Mike Pomraning <mpomraning@qualys.com>
22 *
23 * File-like output for logging: regular files and sockets.
24 */
25
26#include "suricata-common.h" /* errno.h, string.h, etc. */
27#include "util-logopenfile.h"
28#include "suricata.h"
29#include "conf.h" /* ConfNode, etc. */
30#include "output.h" /* DEFAULT_LOG_* */
31#include "util-byte.h"
32#include "util-conf.h"
33#include "util-path.h"
34#include "util-misc.h"
35#include "util-time.h"
36
37#if defined(HAVE_SYS_UN_H) && defined(HAVE_SYS_SOCKET_H) && defined(HAVE_SYS_TYPES_H)
38#define BUILD_WITH_UNIXSOCKET
39#include <sys/types.h>
40#include <sys/socket.h>
41#include <sys/un.h>
42#endif
43
44#ifdef HAVE_LIBHIREDIS
45#include "util-log-redis.h"
46#endif /* HAVE_LIBHIREDIS */
47
48#define LOGFILE_NAME_MAX 255
49
50static bool LogFileNewThreadedCtx(LogFileCtx *parent_ctx, const char *log_path, const char *append,
52
53// Threaded eve.json identifier
54static SC_ATOMIC_DECL_AND_INIT_WITH_VAL(uint16_t, eve_file_id, 1);
55
56#ifdef BUILD_WITH_UNIXSOCKET
57/** \brief connect to the indicated local stream socket, logging any errors
58 * \param path filesystem path to connect to
59 * \param log_err, non-zero if connect failure should be logged.
60 * \retval FILE* on success (fdopen'd wrapper of underlying socket)
61 * \retval NULL on error
62 */
63static FILE *
64SCLogOpenUnixSocketFp(const char *path, int sock_type, int log_err)
65{
66 struct sockaddr_un saun;
67 int s = -1;
68 FILE * ret = NULL;
69
70 memset(&saun, 0x00, sizeof(saun));
71
72 s = socket(PF_UNIX, sock_type, 0);
73 if (s < 0) goto err;
74
75 saun.sun_family = AF_UNIX;
76 strlcpy(saun.sun_path, path, sizeof(saun.sun_path));
77
78 if (connect(s, (const struct sockaddr *)&saun, sizeof(saun)) < 0)
79 goto err;
80
81 ret = fdopen(s, "w");
82 if (ret == NULL)
83 goto err;
84
85 return ret;
86
87err:
88 if (log_err)
90 "Error connecting to socket \"%s\": %s (will keep trying)", path, strerror(errno));
91
92 if (s >= 0)
93 close(s);
94
95 return NULL;
96}
97
98/**
99 * \brief Attempt to reconnect a disconnected (or never-connected) Unix domain socket.
100 * \retval 1 if it is now connected; otherwise 0
101 */
102static int SCLogUnixSocketReconnect(LogFileCtx *log_ctx)
103{
104 int disconnected = 0;
105 if (log_ctx->fp) {
106 SCLogWarning("Write error on Unix socket \"%s\": %s; reconnecting...", log_ctx->filename,
107 strerror(errno));
108 fclose(log_ctx->fp);
109 log_ctx->fp = NULL;
110 log_ctx->reconn_timer = 0;
111 disconnected = 1;
112 }
113
114 struct timeval tv;
115 uint64_t now;
116 gettimeofday(&tv, NULL);
117 now = (uint64_t)tv.tv_sec * 1000;
118 now += tv.tv_usec / 1000; /* msec resolution */
119 if (log_ctx->reconn_timer != 0 &&
120 (now - log_ctx->reconn_timer) < LOGFILE_RECONN_MIN_TIME) {
121 /* Don't bother to try reconnecting too often. */
122 return 0;
123 }
124 log_ctx->reconn_timer = now;
125
126 log_ctx->fp = SCLogOpenUnixSocketFp(log_ctx->filename, log_ctx->sock_type, 0);
127 if (log_ctx->fp) {
128 /* Connected at last (or reconnected) */
129 SCLogDebug("Reconnected socket \"%s\"", log_ctx->filename);
130 } else if (disconnected) {
131 SCLogWarning("Reconnect failed: %s (will keep trying)", strerror(errno));
132 }
133
134 return log_ctx->fp ? 1 : 0;
135}
136
137static int SCLogFileWriteSocket(const char *buffer, int buffer_len,
139{
140 int tries = 0;
141 int ret = 0;
142 bool reopen = false;
143 if (ctx->fp == NULL && ctx->is_sock) {
144 SCLogUnixSocketReconnect(ctx);
145 }
146tryagain:
147 ret = -1;
148 reopen = 0;
149 errno = 0;
150 if (ctx->fp != NULL) {
151 int fd = fileno(ctx->fp);
152 ssize_t size = send(fd, buffer, buffer_len, ctx->send_flags);
153 if (size > -1) {
154 ret = 0;
155 } else {
156 if (errno == EAGAIN || errno == EWOULDBLOCK) {
157 SCLogDebug("Socket would block, dropping event.");
158 } else if (errno == EINTR) {
159 if (tries++ == 0) {
160 SCLogDebug("Interrupted system call, trying again.");
161 goto tryagain;
162 }
163 SCLogDebug("Too many interrupted system calls, "
164 "dropping event.");
165 } else {
166 /* Some other error. Assume badness and reopen. */
167 SCLogDebug("Send failed: %s", strerror(errno));
168 reopen = true;
169 }
170 }
171 }
172
173 if (reopen && tries++ == 0) {
174 if (SCLogUnixSocketReconnect(ctx)) {
175 goto tryagain;
176 }
177 }
178
179 if (ret == -1) {
180 ctx->dropped++;
181 }
182
183 return ret;
184}
185#endif /* BUILD_WITH_UNIXSOCKET */
186static inline void OutputWriteLock(pthread_mutex_t *m)
187{
188 SCMutexLock(m);
189
190}
191
192/**
193 * \brief Flush a log file.
194 */
195static void SCLogFileFlushNoLock(LogFileCtx *log_ctx)
196{
197 log_ctx->bytes_since_last_flush = 0;
198 SCFflushUnlocked(log_ctx->fp);
199}
200
201static void SCLogFileFlush(LogFileCtx *log_ctx)
202{
203 OutputWriteLock(&log_ctx->fp_mutex);
204 SCLogFileFlushNoLock(log_ctx);
205 SCMutexUnlock(&log_ctx->fp_mutex);
206}
207
208/**
209 * \brief Write buffer to log file.
210 * \retval 0 on failure; otherwise, the return value of fwrite_unlocked (number of
211 * characters successfully written).
212 */
213static int SCLogFileWriteNoLock(const char *buffer, int buffer_len, LogFileCtx *log_ctx)
214{
215 int ret = 0;
216
218
219 /* Check for rotation. */
220 if (log_ctx->rotation_flag) {
221 log_ctx->rotation_flag = 0;
222 SCConfLogReopen(log_ctx);
223 }
224
225 if (log_ctx->flags & LOGFILE_ROTATE_INTERVAL) {
226 time_t now = time(NULL);
227 if (now >= log_ctx->rotate_time) {
228 SCConfLogReopen(log_ctx);
229 log_ctx->rotate_time = now + log_ctx->rotate_interval;
230 }
231 }
232
233 if (log_ctx->fp) {
234 SCClearErrUnlocked(log_ctx->fp);
235 if (1 != SCFwriteUnlocked(buffer, buffer_len, 1, log_ctx->fp)) {
236 /* Only the first error is logged */
237 if (!log_ctx->output_errors) {
238 SCLogError("%s error while writing to %s",
239 SCFerrorUnlocked(log_ctx->fp) ? strerror(errno) : "unknown error",
240 log_ctx->filename);
241 }
242 log_ctx->output_errors++;
243 return ret;
244 }
245
246 log_ctx->bytes_since_last_flush += buffer_len;
247
248 if (log_ctx->buffer_size && log_ctx->bytes_since_last_flush >= log_ctx->buffer_size) {
249 SCLogDebug("%s: flushing %" PRIu64 " during write", log_ctx->filename,
250 log_ctx->bytes_since_last_flush);
251 SCLogFileFlushNoLock(log_ctx);
252 }
253 }
254
255 return ret;
256}
257
258/**
259 * \brief Write buffer to log file.
260 * \retval 0 on failure; otherwise, the return value of fwrite (number of
261 * characters successfully written).
262 */
263static int SCLogFileWrite(const char *buffer, int buffer_len, LogFileCtx *log_ctx)
264{
265 OutputWriteLock(&log_ctx->fp_mutex);
266 int ret = 0;
267
268#ifdef BUILD_WITH_UNIXSOCKET
269 if (log_ctx->is_sock) {
270 ret = SCLogFileWriteSocket(buffer, buffer_len, log_ctx);
271 } else
272#endif
273 {
274 ret = SCLogFileWriteNoLock(buffer, buffer_len, log_ctx);
275 }
276
277 SCMutexUnlock(&log_ctx->fp_mutex);
278
279 return ret;
280}
281
282/** \brief generate filename based on pattern
283 * \param pattern pattern to use
284 * \retval char* on success
285 * \retval NULL on error
286 */
287static char *SCLogFilenameFromPattern(const char *pattern)
288{
289 char *filename = SCMalloc(PATH_MAX);
290 if (filename == NULL) {
291 return NULL;
292 }
293
294 int rc = SCTimeToStringPattern(time(NULL), pattern, filename, PATH_MAX);
295 if (rc != 0) {
296 SCFree(filename);
297 return NULL;
298 }
299
300 return filename;
301}
302
303static void SCLogFileCloseNoLock(LogFileCtx *log_ctx)
304{
305 SCLogDebug("Closing %s", log_ctx->filename);
306 if (log_ctx->fp) {
307 if (log_ctx->buffer_size)
308 SCFflushUnlocked(log_ctx->fp);
309 fclose(log_ctx->fp);
310 }
311
312 if (log_ctx->output_errors) {
313 SCLogError("There were %" PRIu64 " output errors to %s", log_ctx->output_errors,
314 log_ctx->filename);
315 }
316}
317
318static void SCLogFileClose(LogFileCtx *log_ctx)
319{
320 SCMutexLock(&log_ctx->fp_mutex);
321 SCLogFileCloseNoLock(log_ctx);
322 SCMutexUnlock(&log_ctx->fp_mutex);
323}
324
325static char ThreadLogFileHashCompareFunc(
326 void *data1, uint16_t datalen1, void *data2, uint16_t datalen2)
327{
330
331 if (p1 == NULL || p2 == NULL)
332 return 0;
333
334 return p1->thread_id == p2->thread_id;
335}
336static uint32_t ThreadLogFileHashFunc(HashTable *ht, void *data, uint16_t datalen)
337{
339
340 return ent->thread_id % ht->array_size;
341}
342
343static void ThreadLogFileHashFreeFunc(void *data)
344{
345 BUG_ON(data == NULL);
347
348 if (!thread_ent)
349 return;
350
351 if (thread_ent->isopen) {
352 LogFileCtx *lf_ctx = thread_ent->ctx;
353 /* Free the leaf log file entries */
354 if (!lf_ctx->threaded) {
355 LogFileFreeCtx(lf_ctx);
356 }
357 }
358 SCFree(thread_ent);
359}
360
361bool SCLogOpenThreadedFile(const char *log_path, const char *append, LogFileCtx *parent_ctx)
362{
363 parent_ctx->threads = SCCalloc(1, sizeof(LogThreadedFileCtx));
364 if (!parent_ctx->threads) {
365 SCLogError("Unable to allocate threads container");
366 return false;
367 }
368
369 parent_ctx->threads->ht = HashTableInit(255, ThreadLogFileHashFunc,
370 ThreadLogFileHashCompareFunc, ThreadLogFileHashFreeFunc);
371 if (!parent_ctx->threads->ht) {
372 FatalError("Unable to initialize thread/entry hash table");
373 }
374
375 parent_ctx->threads->append = SCStrdup(append == NULL ? DEFAULT_LOG_MODE_APPEND : append);
376 if (!parent_ctx->threads->append) {
377 SCLogError("Unable to allocate threads append setting");
378 goto error_exit;
379 }
380
381 SCMutexInit(&parent_ctx->threads->mutex, NULL);
382 return true;
383
384error_exit:
385
386 if (parent_ctx->threads->append) {
387 SCFree(parent_ctx->threads->append);
388 }
389 if (parent_ctx->threads->ht) {
390 HashTableFree(parent_ctx->threads->ht);
391 }
392 SCFree(parent_ctx->threads);
393 parent_ctx->threads = NULL;
394 return false;
395}
396
397/** \brief open the indicated file, logging any errors
398 * \param path filesystem path to open
399 * \param append_setting open file with O_APPEND: "yes" or "no"
400 * \param mode permissions to set on file
401 * \retval FILE* on success
402 * \retval NULL on error
403 */
404static FILE *SCLogOpenFileFp(
405 const char *path, const char *append_setting, uint32_t mode, const uint32_t buffer_size)
406{
407 FILE *ret = NULL;
408
409 char *filename = SCLogFilenameFromPattern(path);
410 if (filename == NULL) {
411 return NULL;
412 }
413
414 int rc = SCCreateDirectoryTree(filename, false);
415 if (rc < 0) {
416 SCFree(filename);
417 return NULL;
418 }
419
420 if (SCConfValIsTrue(append_setting)) {
421 ret = fopen(filename, "a");
422 } else {
423 ret = fopen(filename, "w");
424 }
425
426 if (ret == NULL) {
427 SCLogError("Error opening file: \"%s\": %s", filename, strerror(errno));
428 goto error_exit;
429 } else {
430 if (mode != 0) {
431#ifdef OS_WIN32
432 int r = _chmod(filename, (mode_t)mode);
433#else
434 int r = fchmod(fileno(ret), (mode_t)mode);
435#endif
436 if (r < 0) {
437 SCLogWarning("Could not chmod %s to %o: %s", filename, mode, strerror(errno));
438 }
439 }
440 }
441
442 /* Set buffering behavior */
443 if (buffer_size == 0) {
444 setbuf(ret, NULL);
445 SCLogConfig("Setting output to %s non-buffered", filename);
446 } else {
447 if (setvbuf(ret, NULL, _IOFBF, buffer_size) < 0)
448 FatalError("unable to set %s to buffered: %d", filename, buffer_size);
449 SCLogConfig("Setting output to %s buffered [limit %d bytes]", filename, buffer_size);
450 }
451
452error_exit:
453 SCFree(filename);
454
455 return ret;
456}
457
458/** \brief open a generic output "log file", which may be a regular file or a socket
459 * \param conf ConfNode structure for the output section in question
460 * \param log_ctx Log file context allocated by caller
461 * \param default_filename Default name of file to open, if not specified in ConfNode
462 * \param rotate Register the file for rotation in HUP.
463 * \retval 0 on success
464 * \retval -1 on error
465 */
467 SCConfNode *conf, LogFileCtx *log_ctx, const char *default_filename, int rotate)
468{
469 char log_path[PATH_MAX];
470 const char *log_dir;
471 const char *filename, *filetype;
472
473 // Arg check
474 if (conf == NULL || log_ctx == NULL || default_filename == NULL) {
475 SCLogError("SCConfLogOpenGeneric(conf %p, ctx %p, default %p) "
476 "missing an argument",
477 conf, log_ctx, default_filename);
478 return -1;
479 }
480 if (log_ctx->fp != NULL) {
481 SCLogError("SCConfLogOpenGeneric: previously initialized Log CTX "
482 "encountered");
483 return -1;
484 }
485
486 // Resolve the given config
487 filename = SCConfNodeLookupChildValue(conf, "filename");
488 if (filename == NULL)
489 filename = default_filename;
490
491 log_dir = SCConfigGetLogDirectory();
492
493 if (PathIsAbsolute(filename)) {
494 snprintf(log_path, PATH_MAX, "%s", filename);
495 } else {
496 snprintf(log_path, PATH_MAX, "%s/%s", log_dir, filename);
497 }
498
499 /* Rotate log file based on time */
500 const char *rotate_int = SCConfNodeLookupChildValue(conf, "rotate-interval");
501 if (rotate_int != NULL) {
502 time_t now = time(NULL);
503 log_ctx->flags |= LOGFILE_ROTATE_INTERVAL;
504
505 /* Use a specific time */
506 if (strcmp(rotate_int, "minute") == 0) {
507 log_ctx->rotate_time = now + SCGetSecondsUntil(rotate_int, now);
508 log_ctx->rotate_interval = 60;
509 } else if (strcmp(rotate_int, "hour") == 0) {
510 log_ctx->rotate_time = now + SCGetSecondsUntil(rotate_int, now);
511 log_ctx->rotate_interval = 3600;
512 } else if (strcmp(rotate_int, "day") == 0) {
513 log_ctx->rotate_time = now + SCGetSecondsUntil(rotate_int, now);
514 log_ctx->rotate_interval = 86400;
515 }
516
517 /* Use a timer */
518 else {
519 log_ctx->rotate_interval = SCParseTimeSizeString(rotate_int);
520 if (log_ctx->rotate_interval == 0) {
521 FatalError("invalid rotate-interval value");
522 }
523 log_ctx->rotate_time = now + log_ctx->rotate_interval;
524 }
525 }
526
527 filetype = SCConfNodeLookupChildValue(conf, "filetype");
528 if (filetype == NULL)
529 filetype = DEFAULT_LOG_FILETYPE;
530
531 /* Determine the buffering for this output device; a value of 0 means to not buffer;
532 * any other value must be a multiple of 4096
533 * The default value is 0 (no buffering)
534 */
535 uint32_t buffer_size = LOGFILE_EVE_BUFFER_SIZE;
536 const char *buffer_size_value = SCConfNodeLookupChildValue(conf, "buffer-size");
537 if (buffer_size_value != NULL) {
538 uint32_t value;
539 if (ParseSizeStringU32(buffer_size_value, &value) < 0) {
540 FatalError("Error parsing "
541 "buffer-size - %s. Killing engine",
542 buffer_size_value);
543 }
544 buffer_size = value;
545 }
546
547 SCLogDebug("buffering: %s -> %d", buffer_size_value, buffer_size);
548 const char *filemode = SCConfNodeLookupChildValue(conf, "filemode");
549 uint32_t mode = 0;
550 if (filemode != NULL && StringParseUint32(&mode, 8, (uint16_t)strlen(filemode), filemode) > 0) {
551 log_ctx->filemode = mode;
552 }
553
554 const char *append = SCConfNodeLookupChildValue(conf, "append");
555 if (append == NULL)
557
558 /* JSON flags */
559 log_ctx->json_flags = JSON_PRESERVE_ORDER|JSON_COMPACT|
560 JSON_ENSURE_ASCII|JSON_ESCAPE_SLASH;
561
562 SCConfNode *json_flags = SCConfNodeLookupChild(conf, "json");
563
564 if (json_flags != 0) {
565 const char *preserve_order = SCConfNodeLookupChildValue(json_flags, "preserve-order");
566 if (preserve_order != NULL && SCConfValIsFalse(preserve_order))
567 log_ctx->json_flags &= ~(JSON_PRESERVE_ORDER);
568
569 const char *compact = SCConfNodeLookupChildValue(json_flags, "compact");
570 if (compact != NULL && SCConfValIsFalse(compact))
571 log_ctx->json_flags &= ~(JSON_COMPACT);
572
573 const char *ensure_ascii = SCConfNodeLookupChildValue(json_flags, "ensure-ascii");
574 if (ensure_ascii != NULL && SCConfValIsFalse(ensure_ascii))
575 log_ctx->json_flags &= ~(JSON_ENSURE_ASCII);
576
577 const char *escape_slash = SCConfNodeLookupChildValue(json_flags, "escape-slash");
578 if (escape_slash != NULL && SCConfValIsFalse(escape_slash))
579 log_ctx->json_flags &= ~(JSON_ESCAPE_SLASH);
580 }
581
582#ifdef BUILD_WITH_UNIXSOCKET
583 if (log_ctx->threaded) {
584 if (strcasecmp(filetype, "unix_stream") == 0 || strcasecmp(filetype, "unix_dgram") == 0) {
585 FatalError("Socket file types do not support threaded output");
586 }
587 }
588#endif
589 if (!(strcasecmp(filetype, DEFAULT_LOG_FILETYPE) == 0 || strcasecmp(filetype, "file") == 0)) {
590 SCLogConfig("buffering setting ignored for %s output types", filetype);
591 }
592
593 // Now, what have we been asked to open?
594 if (strcasecmp(filetype, "unix_stream") == 0) {
595#ifdef BUILD_WITH_UNIXSOCKET
596 /* Don't bail. May be able to connect later. */
597 log_ctx->is_sock = 1;
598 log_ctx->sock_type = SOCK_STREAM;
599 log_ctx->fp = SCLogOpenUnixSocketFp(log_path, SOCK_STREAM, 1);
600#else
601 return -1;
602#endif
603 } else if (strcasecmp(filetype, "unix_dgram") == 0) {
604#ifdef BUILD_WITH_UNIXSOCKET
605 /* Don't bail. May be able to connect later. */
606 log_ctx->is_sock = 1;
607 log_ctx->sock_type = SOCK_DGRAM;
608 log_ctx->fp = SCLogOpenUnixSocketFp(log_path, SOCK_DGRAM, 1);
609#else
610 return -1;
611#endif
612 } else if (strcasecmp(filetype, DEFAULT_LOG_FILETYPE) == 0 ||
613 strcasecmp(filetype, "file") == 0) {
614 log_ctx->is_regular = 1;
615 log_ctx->buffer_size = buffer_size;
616 if (!log_ctx->threaded) {
617 log_ctx->fp =
618 SCLogOpenFileFp(log_path, append, log_ctx->filemode, log_ctx->buffer_size);
619 if (log_ctx->fp == NULL)
620 return -1; // Error already logged by Open...Fp routine
621 } else {
622 if (!SCLogOpenThreadedFile(log_path, append, log_ctx)) {
623 return -1;
624 }
625 }
626 if (rotate) {
628 }
629 } else {
630 SCLogError("Invalid entry for "
631 "%s.filetype. Expected \"regular\" (default), \"unix_stream\", "
632 "or \"unix_dgram\"",
633 conf->name);
634 }
635 log_ctx->filename = SCStrdup(log_path);
636 if (unlikely(log_ctx->filename == NULL)) {
637 SCLogError("Failed to allocate memory for filename");
638 return -1;
639 }
640
641#ifdef BUILD_WITH_UNIXSOCKET
642 /* If a socket and running live, do non-blocking writes. */
643 if (log_ctx->is_sock && !IsRunModeOffline(SCRunmodeGet())) {
644 SCLogInfo("Setting logging socket of non-blocking in live mode.");
645 log_ctx->send_flags |= MSG_DONTWAIT;
646 }
647#endif
648 SCLogInfo("%s output device (%s) initialized: %s", conf->name, filetype,
649 filename);
650
651 return 0;
652}
653
654/**
655 * \brief Reopen a regular log file with the side-affect of truncating it.
656 *
657 * This is useful to clear the log file and start a new one, or to
658 * re-open the file after its been moved by something external
659 * (eg. logrotate).
660 */
662{
663 if (!log_ctx->is_regular) {
664 /* Not supported and not needed on non-regular files. */
665 return 0;
666 }
667
668 if (log_ctx->filename == NULL) {
669 SCLogWarning("Can't re-open LogFileCtx without a filename.");
670 return -1;
671 }
672
673 if (log_ctx->fp != NULL) {
674 fclose(log_ctx->fp);
675 }
676
677 /* Reopen the file. Append is forced in case the file was not
678 * moved as part of a rotation process. */
679 SCLogDebug("Reopening log file %s.", log_ctx->filename);
680 log_ctx->fp =
681 SCLogOpenFileFp(log_ctx->filename, "yes", log_ctx->filemode, log_ctx->buffer_size);
682 if (log_ctx->fp == NULL) {
683 return -1; // Already logged by Open..Fp routine.
684 }
685
686 return 0;
687}
688
689/** \brief LogFileNewCtx() Get a new LogFileCtx
690 * \retval LogFileCtx * pointer if successful, NULL if error
691 * */
693{
694 LogFileCtx* lf_ctx;
695 lf_ctx = (LogFileCtx*)SCCalloc(1, sizeof(LogFileCtx));
696
697 if (lf_ctx == NULL)
698 return NULL;
699
700 lf_ctx->Write = SCLogFileWrite;
701 lf_ctx->Close = SCLogFileClose;
702 lf_ctx->Flush = SCLogFileFlush;
703
704 return lf_ctx;
705}
706
707/** \brief LogFileThread2Slot() Return a file entry
708 * \retval ThreadLogFileHashEntry * file entry for caller
709 *
710 * This function returns the file entry for the calling thread.
711 * Each thread -- identified by its operating system thread-id -- has its
712 * own file entry that includes a file pointer.
713 */
714static ThreadLogFileHashEntry *LogFileThread2Slot(LogThreadedFileCtx *parent, ThreadId thread_id)
715{
716 ThreadLogFileHashEntry thread_hash_entry;
717
718 /* Check hash table for thread id*/
719 thread_hash_entry.thread_id = SCGetThreadIdLong();
721 HashTableLookup(parent->ht, &thread_hash_entry, sizeof(thread_hash_entry));
722
723 if (!ent) {
724 ent = SCCalloc(1, sizeof(*ent));
725 if (!ent) {
726 FatalError("Unable to allocate thread/hash-entry entry");
727 }
728 ent->thread_id = thread_hash_entry.thread_id;
729 ent->internal_thread_id = thread_id;
731 "Trying to add thread %" PRIi64 " to entry %d", ent->thread_id, ent->slot_number);
732 if (0 != HashTableAdd(parent->ht, ent, 0)) {
733 FatalError("Unable to add thread/hash-entry mapping");
734 }
735 }
736 return ent;
737}
738
739/** \brief LogFileEnsureExists() Ensure a log file context for the thread exists
740 * \param parent_ctx
741 * \retval LogFileCtx * pointer if successful; NULL otherwise
742 */
744{
745 /* threaded output disabled */
746 if (!parent_ctx->threaded)
747 return parent_ctx;
748
749 LogFileCtx *ret_ctx = NULL;
750 SCMutexLock(&parent_ctx->threads->mutex);
751 /* Find this thread's entry */
752 ThreadLogFileHashEntry *entry = LogFileThread2Slot(parent_ctx->threads, thread_id);
753 SCLogDebug("%s: Adding reference for thread %" PRIi64
754 " (local thread id %d) to file %s [ctx %p]",
755 t_thread_name, SCGetThreadIdLong(), thread_id, parent_ctx->filename, parent_ctx);
756
757 bool new = entry->isopen;
758 /* has it been opened yet? */
759 if (!new) {
760 SCLogDebug("%s: Opening new file for thread/id %d to file %s [ctx %p]", t_thread_name,
761 thread_id, parent_ctx->filename, parent_ctx);
762 if (LogFileNewThreadedCtx(
763 parent_ctx, parent_ctx->filename, parent_ctx->threads->append, entry)) {
764 entry->isopen = true;
765 ret_ctx = entry->ctx;
766 } else {
768 "Unable to open slot %d for file %s", entry->slot_number, parent_ctx->filename);
769 (void)HashTableRemove(parent_ctx->threads->ht, entry, 0);
770 }
771 } else {
772 ret_ctx = entry->ctx;
773 }
774 SCMutexUnlock(&parent_ctx->threads->mutex);
775
777 if (new) {
778 SCLogDebug("Existing file for thread/entry %p reference to file %s [ctx %p]", entry,
779 parent_ctx->filename, parent_ctx);
780 }
781 }
782
783 return ret_ctx;
784}
785
786/** \brief LogFileThreadedName() Create file name for threaded EVE storage
787 *
788 */
789static bool LogFileThreadedName(
790 const char *original_name, char *threaded_name, size_t len, uint32_t unique_id)
791{
792 sc_errno = SC_OK;
793
794 if (strcmp("/dev/null", original_name) == 0) {
795 strlcpy(threaded_name, original_name, len);
796 return true;
797 }
798
799 const char *base = SCBasename(original_name);
800 if (!base) {
801 FatalError("Invalid filename for threaded mode \"%s\"; "
802 "no basename found.",
803 original_name);
804 }
805
806 /* Check if basename has an extension */
807 char *dot = strrchr(base, '.');
808 if (dot) {
809 char *tname = SCStrdup(original_name);
810 if (!tname) {
812 return false;
813 }
814
815 /* Fetch extension location from original, not base
816 * for update
817 */
818 dot = strrchr(original_name, '.');
819 ptrdiff_t dotpos = dot - original_name;
820 tname[dotpos] = '\0';
821 char *ext = tname + dotpos + 1;
822 if (strlen(tname) && strlen(ext)) {
823 snprintf(threaded_name, len, "%s.%u.%s", tname, unique_id, ext);
824 } else {
825 FatalError("Invalid filename for threaded mode \"%s\"; "
826 "filenames must include an extension, e.g: \"name.ext\"",
827 original_name);
828 }
829 SCFree(tname);
830 } else {
831 snprintf(threaded_name, len, "%s.%u", original_name, unique_id);
832 }
833 return true;
834}
835
836/** \brief LogFileNewThreadedCtx() Create file context for threaded output
837 * \param parent_ctx
838 * \param log_path
839 * \param append
840 * \param entry
841 */
842static bool LogFileNewThreadedCtx(LogFileCtx *parent_ctx, const char *log_path, const char *append,
844{
845 LogFileCtx *thread = SCCalloc(1, sizeof(LogFileCtx));
846 if (!thread) {
847 SCLogError("Unable to allocate thread file context entry %p", entry);
848 return false;
849 }
850
851 *thread = *parent_ctx;
852 if (parent_ctx->type == LOGFILE_TYPE_FILE) {
853 char fname[LOGFILE_NAME_MAX];
854 entry->slot_number = SC_ATOMIC_ADD(eve_file_id, 1);
855 if (!LogFileThreadedName(log_path, fname, sizeof(fname), entry->slot_number)) {
856 SCLogError("Unable to create threaded filename for log");
857 goto error;
858 }
859 SCLogDebug("%s: thread open -- using name %s [replaces %s] - thread %d [slot %d]",
860 t_thread_name, fname, log_path, entry->internal_thread_id, entry->slot_number);
861 thread->fp = SCLogOpenFileFp(fname, append, thread->filemode, parent_ctx->buffer_size);
862 if (thread->fp == NULL) {
863 goto error;
864 }
865 thread->filename = SCStrdup(fname);
866 if (!thread->filename) {
867 SCLogError("Unable to duplicate filename for context entry %p", entry);
868 goto error;
869 }
870 thread->is_regular = true;
871 thread->Write = SCLogFileWriteNoLock;
872 thread->Close = SCLogFileCloseNoLock;
874 } else if (parent_ctx->type == LOGFILE_TYPE_FILETYPE) {
875 entry->slot_number = SC_ATOMIC_ADD(eve_file_id, 1);
876 SCLogDebug("%s - thread %d [slot %d]", log_path, entry->internal_thread_id,
877 entry->slot_number);
879 &thread->filetype.thread_data);
880 }
881 thread->threaded = false;
882 thread->parent = parent_ctx;
883 thread->entry = entry;
884 entry->ctx = thread;
885
886 return true;
887
888error:
889 if (parent_ctx->type == LOGFILE_TYPE_FILE) {
890 SC_ATOMIC_SUB(eve_file_id, 1);
891 if (thread->fp) {
892 thread->Close(thread);
893 }
894 }
895
896 if (thread) {
897 SCFree(thread);
898 }
899 return false;
900}
901
902/** \brief LogFileFreeCtx() Destroy a LogFileCtx (Close the file and free memory)
903 * \param lf_ctx pointer to the OutputCtx
904 * \retval int 1 if successful, 0 if error
905 * */
907{
908 if (lf_ctx == NULL) {
909 SCReturnInt(0);
910 }
911
912 if (lf_ctx->type == LOGFILE_TYPE_FILETYPE && lf_ctx->filetype.filetype->ThreadDeinit) {
914 lf_ctx->filetype.init_data, lf_ctx->filetype.thread_data);
915 }
916
917 if (lf_ctx->threaded) {
918 BUG_ON(lf_ctx->threads == NULL);
919 SCMutexDestroy(&lf_ctx->threads->mutex);
920 if (lf_ctx->threads->append)
921 SCFree(lf_ctx->threads->append);
922 if (lf_ctx->threads->ht) {
923 HashTableFree(lf_ctx->threads->ht);
924 }
925 SCFree(lf_ctx->threads);
926 } else {
927 if (lf_ctx->type != LOGFILE_TYPE_FILETYPE) {
928 if (lf_ctx->fp != NULL) {
929 lf_ctx->Close(lf_ctx);
930 }
931 }
932 SCMutexDestroy(&lf_ctx->fp_mutex);
933 }
934
935 if (lf_ctx->prefix != NULL) {
936 SCFree(lf_ctx->prefix);
937 lf_ctx->prefix_len = 0;
938 }
939
940 if(lf_ctx->filename != NULL)
941 SCFree(lf_ctx->filename);
942
943 if (lf_ctx->sensor_name)
944 SCFree(lf_ctx->sensor_name);
945
946 if (!lf_ctx->threaded) {
948 }
949
950 /* Deinitialize output filetypes. We only want to call this for
951 * the parent of threaded output, or always for non-threaded
952 * output. */
953 if (lf_ctx->type == LOGFILE_TYPE_FILETYPE && lf_ctx->parent == NULL) {
954 lf_ctx->filetype.filetype->Deinit(lf_ctx->filetype.init_data);
955 }
956
957#ifdef HAVE_LIBHIREDIS
958 if (lf_ctx->type == LOGFILE_TYPE_REDIS) {
959 if (lf_ctx->redis_setup.stream_format != NULL) {
960 SCFree(lf_ctx->redis_setup.stream_format);
961 }
962 }
963#endif
964
965 memset(lf_ctx, 0, sizeof(*lf_ctx));
966 SCFree(lf_ctx);
967
968 SCReturnInt(1);
969}
970
972{
973 SCLogDebug("%s: bytes-to-flush %ld", file_ctx->filename, file_ctx->bytes_since_last_flush);
974 file_ctx->Flush(file_ctx);
975}
976
977int LogFileWrite(LogFileCtx *file_ctx, MemBuffer *buffer)
978{
979 if (file_ctx->type == LOGFILE_TYPE_FILE || file_ctx->type == LOGFILE_TYPE_UNIX_DGRAM ||
980 file_ctx->type == LOGFILE_TYPE_UNIX_STREAM) {
981 /* append \n for files only */
982 MemBufferWriteString(buffer, "\n");
983 file_ctx->Write((const char *)MEMBUFFER_BUFFER(buffer),
984 MEMBUFFER_OFFSET(buffer), file_ctx);
985 } else if (file_ctx->type == LOGFILE_TYPE_FILETYPE) {
986 file_ctx->filetype.filetype->Write((const char *)MEMBUFFER_BUFFER(buffer),
987 MEMBUFFER_OFFSET(buffer), file_ctx->filetype.init_data,
988 file_ctx->filetype.thread_data);
989 }
990#ifdef HAVE_LIBHIREDIS
991 else if (file_ctx->type == LOGFILE_TYPE_REDIS) {
992 SCMutexLock(&file_ctx->fp_mutex);
993 LogFileWriteRedis(file_ctx, (const char *)MEMBUFFER_BUFFER(buffer),
994 MEMBUFFER_OFFSET(buffer));
995 SCMutexUnlock(&file_ctx->fp_mutex);
996 }
997#endif
998
999 return 0;
1000}
uint8_t len
SCConfNode * SCConfNodeLookupChild(const SCConfNode *node, const char *name)
Lookup a child configuration node by name.
Definition conf.c:796
int SCConfValIsTrue(const char *val)
Check if a value is true.
Definition conf.c:551
const char * SCConfNodeLookupChildValue(const SCConfNode *node, const char *name)
Lookup the value of a child configuration node by name.
Definition conf.c:824
int SCConfValIsFalse(const char *val)
Check if a value is false.
Definition conf.c:576
SCMutex m
Definition flow-hash.h:6
ThreadVars * tv
struct Thresholds ctx
uint32_t ThreadId
Definition output-eve.h:37
void OutputRegisterFileRotationFlag(int *flag)
Register a flag for file rotation notification.
Definition output.c:692
void OutputUnregisterFileRotationFlag(int *flag)
Unregister a file rotation flag.
Definition output.c:715
#define DEFAULT_LOG_FILETYPE
Definition output.h:31
#define DEFAULT_LOG_MODE_APPEND
Definition output.h:30
bool IsRunModeOffline(enum SCRunModes run_mode_to_check)
Definition runmodes.c:561
uint32_t array_size
Definition util-hash.h:37
uint64_t reconn_timer
uint64_t rotate_interval
uint32_t prefix_len
uint64_t bytes_since_last_flush
uint64_t output_errors
LogFileTypeCtx filetype
int(* Write)(const char *buffer, int buffer_len, struct LogFileCtx_ *fp)
enum LogFileType type
struct LogFileCtx_ * parent
void(* Flush)(struct LogFileCtx_ *fp)
uint32_t buffer_size
LogThreadedFileCtx * threads
void(* Close)(struct LogFileCtx_ *fp)
ThreadLogFileHashEntry * entry
SCEveFileType * filetype
char * name
Definition conf.h:38
void(* Deinit)(void *init_data)
Final call to deinitialize this filetype.
Definition output-eve.h:167
int(* Write)(const char *buffer, const int buffer_len, const void *init_data, void *thread_data)
Called for each EVE log record.
Definition output-eve.h:144
void(* ThreadDeinit)(const void *init_data, void *thread_data)
Called to deinitialize each thread.
Definition output-eve.h:157
int(* ThreadInit)(const void *init_data, const ThreadId thread_id, void **thread_data)
Initialize thread specific data.
Definition output-eve.h:125
struct LogFileCtx_ * ctx
#define BUG_ON(x)
#define JSON_ESCAPE_SLASH
#define SCClearErrUnlocked
#define SCFflushUnlocked
#define SCFerrorUnlocked
#define SCFwriteUnlocked
size_t strlcpy(char *dst, const char *src, size_t siz)
SCRunMode SCRunmodeGet(void)
Get the current run mode.
Definition suricata.c:279
#define SCMutexDestroy
#define SCMutexUnlock(mut)
#define SCMutexInit(mut, mutattrs)
#define SCMutexLock(mut)
thread_local char t_thread_name[THREAD_NAME_LEN+1]
Definition threads.c:33
#define SCGetThreadIdLong(...)
Definition threads.h:255
#define SC_ATOMIC_ADD(name, val)
add a value to our atomic variable
#define SC_ATOMIC_DECL_AND_INIT_WITH_VAL(type, name, val)
wrapper for declaring an atomic variable and initializing it to a specific value
#define SC_ATOMIC_SUB(name, val)
sub a value from our atomic variable
void MemBufferWriteString(MemBuffer *dst, const char *fmt,...)
#define MEMBUFFER_BUFFER(mem_buffer)
Get the MemBuffers underlying buffer.
Definition util-buffer.h:51
#define MEMBUFFER_OFFSET(mem_buffer)
Get the MemBuffers current offset.
Definition util-buffer.h:56
int StringParseUint32(uint32_t *res, int base, size_t len, const char *str)
Definition util-byte.c:313
const char * SCConfigGetLogDirectory(void)
Definition util-conf.c:38
SCLogLevel sc_log_global_log_level
Holds the global log level. Is the same as sc_log_config->log_level.
Definition util-debug.c:101
#define FatalError(...)
Definition util-debug.h:510
@ SC_LOG_DEBUG
Definition util-debug.h:41
#define SCLogDebug(...)
Definition util-debug.h:275
#define SCReturnInt(x)
Definition util-debug.h:281
#define SCLogWarning(...)
Macro used to log WARNING messages.
Definition util-debug.h:255
#define SCLogInfo(...)
Macro used to log INFORMATIONAL messages.
Definition util-debug.h:225
#define SCLogError(...)
Macro used to log ERROR messages.
Definition util-debug.h:267
#define SCLogConfig(...)
Definition util-debug.h:229
thread_local SCError sc_errno
Definition util-error.c:31
@ SC_ENOMEM
Definition util-error.h:29
@ SC_OK
Definition util-error.h:27
int HashTableRemove(HashTable *ht, void *data, uint16_t datalen)
Definition util-hash.c:142
int HashTableAdd(HashTable *ht, void *data, uint16_t datalen)
Definition util-hash.c:104
HashTable * HashTableInit(uint32_t size, uint32_t(*Hash)(struct HashTable_ *, void *, uint16_t), char(*Compare)(void *, uint16_t, void *, uint16_t), void(*Free)(void *))
Definition util-hash.c:35
void HashTableFree(HashTable *ht)
Definition util-hash.c:78
void * HashTableLookup(HashTable *ht, void *data, uint16_t datalen)
Definition util-hash.c:183
int LogFileWrite(LogFileCtx *file_ctx, MemBuffer *buffer)
bool SCLogOpenThreadedFile(const char *log_path, const char *append, LogFileCtx *parent_ctx)
int LogFileFreeCtx(LogFileCtx *lf_ctx)
LogFileFreeCtx() Destroy a LogFileCtx (Close the file and free memory)
LogFileCtx * LogFileEnsureExists(ThreadId thread_id, LogFileCtx *parent_ctx)
LogFileEnsureExists() Ensure a log file context for the thread exists.
LogFileCtx * LogFileNewCtx(void)
LogFileNewCtx() Get a new LogFileCtx.
int SCConfLogOpenGeneric(SCConfNode *conf, LogFileCtx *log_ctx, const char *default_filename, int rotate)
open a generic output "log file", which may be a regular file or a socket
void LogFileFlush(LogFileCtx *file_ctx)
int SCConfLogReopen(LogFileCtx *log_ctx)
Reopen a regular log file with the side-affect of truncating it.
#define LOGFILE_NAME_MAX
#define LOGFILE_RECONN_MIN_TIME
@ LOGFILE_TYPE_REDIS
@ LOGFILE_TYPE_FILETYPE
@ LOGFILE_TYPE_FILE
@ LOGFILE_TYPE_UNIX_STREAM
@ LOGFILE_TYPE_UNIX_DGRAM
#define LOGFILE_ROTATE_INTERVAL
#define LOGFILE_EVE_BUFFER_SIZE
#define SCMalloc(sz)
Definition util-mem.h:47
#define SCFree(p)
Definition util-mem.h:61
#define SCCalloc(nm, sz)
Definition util-mem.h:53
#define SCStrdup(s)
Definition util-mem.h:56
int ParseSizeStringU32(const char *size, uint32_t *res)
Definition util-misc.c:173
#define unlikely(expr)
int PathIsAbsolute(const char *path)
Check if a path is absolute.
Definition util-path.c:44
int SCCreateDirectoryTree(const char *path, const bool final)
Recursively create a directory.
Definition util-path.c:137
const char * SCBasename(const char *path)
Definition util-path.c:249
uint64_t SCParseTimeSizeString(const char *str)
Parse string containing time size (1m, 1h, etc).
Definition util-time.c:570
int SCTimeToStringPattern(time_t epoch, const char *pattern, char *str, size_t size)
Convert epoch time to string pattern.
Definition util-time.c:541
uint64_t SCGetSecondsUntil(const char *str, time_t epoch)
Get seconds until a time unit changes.
Definition util-time.c:621
#define DEBUG_VALIDATE_BUG_ON(exp)