suricata
util-logopenfile.c
Go to the documentation of this file.
1 /* Copyright (C) 2007-2026 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 #include "util-unittest.h"
37 #include "log-maintenance.h"
38 
39 #if defined(HAVE_SYS_UN_H) && defined(HAVE_SYS_SOCKET_H) && defined(HAVE_SYS_TYPES_H)
40 #define BUILD_WITH_UNIXSOCKET
41 #include <sys/types.h>
42 #include <sys/socket.h>
43 #include <sys/un.h>
44 #endif
45 
46 #ifdef HAVE_LIBHIREDIS
47 #include "util-log-redis.h"
48 #endif /* HAVE_LIBHIREDIS */
49 
50 #define LOGFILE_NAME_MAX 255
51 
52 static bool LogFileNewThreadedCtx(LogFileCtx *parent_ctx, const char *log_path, const char *append,
53  ThreadLogFileHashEntry *entry);
54 
55 // Threaded eve.json identifier
56 static SC_ATOMIC_DECL_AND_INIT_WITH_VAL(uint16_t, eve_file_id, 1);
57 
58 /* Log file list for heartbeat-triggered flushing and rotation */
59 static SCMutex log_file_list_mutex = SCMUTEX_INITIALIZER;
60 static TAILQ_HEAD(, LogFileEntry_) log_file_list = TAILQ_HEAD_INITIALIZER(log_file_list);
61 
62 #ifdef BUILD_WITH_UNIXSOCKET
63 /** \brief connect to the indicated local stream socket, logging any errors
64  * \param path filesystem path to connect to
65  * \param log_err, non-zero if connect failure should be logged.
66  * \retval FILE* on success (fdopen'd wrapper of underlying socket)
67  * \retval NULL on error
68  */
69 static FILE *
70 SCLogOpenUnixSocketFp(const char *path, int sock_type, int log_err)
71 {
72  struct sockaddr_un saun;
73  int s = -1;
74  FILE * ret = NULL;
75 
76  memset(&saun, 0x00, sizeof(saun));
77 
78  s = socket(PF_UNIX, sock_type, 0);
79  if (s < 0) goto err;
80 
81  saun.sun_family = AF_UNIX;
82  strlcpy(saun.sun_path, path, sizeof(saun.sun_path));
83 
84  if (connect(s, (const struct sockaddr *)&saun, sizeof(saun)) < 0)
85  goto err;
86 
87  ret = fdopen(s, "w");
88  if (ret == NULL)
89  goto err;
90 
91  return ret;
92 
93 err:
94  if (log_err)
96  "Error connecting to socket \"%s\": %s (will keep trying)", path, strerror(errno));
97 
98  if (s >= 0)
99  close(s);
100 
101  return NULL;
102 }
103 
104 /**
105  * \brief Attempt to reconnect a disconnected (or never-connected) Unix domain socket.
106  * \retval 1 if it is now connected; otherwise 0
107  */
108 static int SCLogUnixSocketReconnect(LogFileCtx *log_ctx)
109 {
110  int disconnected = 0;
111  if (log_ctx->fp) {
112  SCLogWarning("Write error on Unix socket \"%s\": %s; reconnecting...", log_ctx->filename,
113  strerror(errno));
114  fclose(log_ctx->fp);
115  log_ctx->fp = NULL;
116  log_ctx->reconn_timer = 0;
117  disconnected = 1;
118  }
119 
120  struct timeval tv;
121  uint64_t now;
122  gettimeofday(&tv, NULL);
123  now = (uint64_t)tv.tv_sec * 1000;
124  now += tv.tv_usec / 1000; /* msec resolution */
125  if (log_ctx->reconn_timer != 0 &&
126  (now - log_ctx->reconn_timer) < LOGFILE_RECONN_MIN_TIME) {
127  /* Don't bother to try reconnecting too often. */
128  return 0;
129  }
130  log_ctx->reconn_timer = now;
131 
132  log_ctx->fp = SCLogOpenUnixSocketFp(log_ctx->filename, log_ctx->sock_type, 0);
133  if (log_ctx->fp) {
134  /* Connected at last (or reconnected) */
135  SCLogDebug("Reconnected socket \"%s\"", log_ctx->filename);
136  } else if (disconnected) {
137  SCLogWarning("Reconnect failed: %s (will keep trying)", strerror(errno));
138  }
139 
140  return log_ctx->fp ? 1 : 0;
141 }
142 
143 static int SCLogFileWriteSocket(const char *buffer, int buffer_len,
144  LogFileCtx *ctx)
145 {
146  int tries = 0;
147  int ret = 0;
148  bool reopen = false;
149  if (ctx->fp == NULL && ctx->is_sock) {
150  SCLogUnixSocketReconnect(ctx);
151  }
152 tryagain:
153  ret = -1;
154  reopen = 0;
155  errno = 0;
156  if (ctx->fp != NULL) {
157  int fd = fileno(ctx->fp);
158  ssize_t size = send(fd, buffer, buffer_len, ctx->send_flags);
159  if (size > -1) {
160  ret = 0;
161  } else {
162  if (errno == EAGAIN || errno == EWOULDBLOCK) {
163  SCLogDebug("Socket would block, dropping event.");
164  } else if (errno == EINTR) {
165  if (tries++ == 0) {
166  SCLogDebug("Interrupted system call, trying again.");
167  goto tryagain;
168  }
169  SCLogDebug("Too many interrupted system calls, "
170  "dropping event.");
171  } else {
172  /* Some other error. Assume badness and reopen. */
173  SCLogDebug("Send failed: %s", strerror(errno));
174  reopen = true;
175  }
176  }
177  }
178 
179  if (reopen && tries++ == 0) {
180  if (SCLogUnixSocketReconnect(ctx)) {
181  goto tryagain;
182  }
183  }
184 
185  if (ret == -1) {
186  ctx->dropped++;
187  }
188 
189  return ret;
190 }
191 #endif /* BUILD_WITH_UNIXSOCKET */
192 static inline void OutputWriteLock(pthread_mutex_t *m)
193 {
194  SCMutexLock(m);
195 
196 }
197 
198 /**
199  * \brief Flush a log file.
200  */
201 static void SCLogFileFlushNoLock(LogFileCtx *log_ctx)
202 {
203  log_ctx->bytes_since_last_flush = 0;
204  SCFflushUnlocked(log_ctx->fp);
205 }
206 
207 static void SCLogFileFlush(LogFileCtx *log_ctx)
208 {
209  OutputWriteLock(&log_ctx->fp_mutex);
210  SCLogFileFlushNoLock(log_ctx);
211  SCMutexUnlock(&log_ctx->fp_mutex);
212 }
213 
214 /**
215  * \brief Get a number identifying the local day, hour or minute of tm
216  *
217  * The number is only compared for equality to detect that the local period
218  * changed, it is not a count of elapsed time. Counting 366 days per year keeps
219  * the number unique for every tm_yday (0-365), as with 365, Dec 31 of a leap
220  * year would get the same number as Jan 1 of the next year.
221  */
222 static int64_t LogFileRotatePeriod(LogFileRotateUnit unit, const struct tm *tm)
223 {
224  const int64_t day = (int64_t)tm->tm_year * 366 + tm->tm_yday;
225  const int64_t hour = day * 24 + tm->tm_hour;
226  const int64_t minute = hour * 60 + tm->tm_min;
227 
228  if (unit == LOGFILE_ROTATE_ON_NEW_DAY) {
229  return day;
230  }
231  if (unit == LOGFILE_ROTATE_ON_NEW_HOUR) {
232  return hour;
233  }
234  return minute;
235 }
236 
237 /**
238  * \brief Record the rotation period of now and schedule the next rotation check
239  *
240  * Calendar units (minute, hour, day) are checked at the start of every local
241  * minute, relative intervals when the interval has passed.
242  */
243 static void LogFileRotateArm(LogFileCtx *log_ctx, time_t now)
244 {
246  log_ctx->rotate_time = now + log_ctx->rotate_interval;
247  return;
248  }
249 
250  struct tm local_tm;
251  struct tm *tms = SCLocalTime(now, &local_tm);
252  if (tms == NULL) {
253  return;
254  }
255  log_ctx->rotate_time = now + 60 - tms->tm_sec;
256  log_ctx->rotate_period = LogFileRotatePeriod(log_ctx->rotate_unit, tms);
257 }
258 
259 /**
260  * \brief Update the rotation state to now and report if the log file must be rotated
261  *
262  * Calendar units rotate when the local time period changes, so rotation stays
263  * aligned with local midnight across DST transitions.
264  *
265  * \retval true if the log file must be rotated
266  */
267 static bool LogFileRotateUpdate(LogFileCtx *log_ctx, time_t now)
268 {
269  if (now < log_ctx->rotate_time) {
270  return false;
271  }
272 
274  log_ctx->rotate_time = now + log_ctx->rotate_interval;
275  return true;
276  }
277 
278  const int64_t prev_period = log_ctx->rotate_period;
279  LogFileRotateArm(log_ctx, now);
280  return log_ctx->rotate_period != prev_period;
281 }
282 
283 /**
284  * \brief Handle log file rotation checks and updates
285  * \param log_ctx Log file context
286  * \retval true if rotation occurred
287  */
288 static bool HandleLogRotation(LogFileCtx *log_ctx)
289 {
290  if (log_ctx->rotation_flag) {
291  log_ctx->rotation_flag = 0;
292  if (log_ctx->rotate_unit != LOGFILE_ROTATE_NONE) {
293  LogFileRotateArm(log_ctx, time(NULL));
294  }
295  SCConfLogReopen(log_ctx);
296  return true;
297  } else if (log_ctx->rotate_unit != LOGFILE_ROTATE_NONE) {
298  if (LogFileRotateUpdate(log_ctx, time(NULL))) {
299  SCConfLogReopen(log_ctx);
300  return true;
301  }
302  }
303 
304  return false;
305 }
306 
307 /**
308  * \brief Write buffer to log file.
309  * \retval 0 on failure; otherwise, the return value of fwrite_unlocked (number of
310  * characters successfully written).
311  */
312 static int SCLogFileWriteNoLock(const char *buffer, int buffer_len, LogFileCtx *log_ctx)
313 {
314  int ret = 0;
315 
316  DEBUG_VALIDATE_BUG_ON(log_ctx->is_sock);
317 
318  HandleLogRotation(log_ctx);
319 
320  if (log_ctx->fp) {
321  SCClearErrUnlocked(log_ctx->fp);
322  if (1 != SCFwriteUnlocked(buffer, buffer_len, 1, log_ctx->fp)) {
323  /* Only the first error is logged */
324  if (!log_ctx->output_errors) {
325  SCLogError("%s error while writing to %s",
326  SCFerrorUnlocked(log_ctx->fp) ? strerror(errno) : "unknown error",
327  log_ctx->filename);
328  }
329  log_ctx->output_errors++;
330  return ret;
331  }
332 
333  log_ctx->bytes_since_last_flush += buffer_len;
334 
335  if (log_ctx->buffer_size && log_ctx->bytes_since_last_flush >= log_ctx->buffer_size) {
336  SCLogDebug("%s: flushing %" PRIu64 " during write", log_ctx->filename,
337  log_ctx->bytes_since_last_flush);
338  SCLogFileFlushNoLock(log_ctx);
339  }
340  }
341 
342  return ret;
343 }
344 
345 /**
346  * \brief Check and perform log file rotation if needed.
347  *
348  * Called by the maintenance thread to drive rotation independently of
349  * traffic-driven writes. Takes the file lock so it is safe to run
350  * concurrently with worker threads writing to the same context.
351  */
352 static void SCLogFileRotate(LogFileCtx *log_ctx)
353 {
354  OutputWriteLock(&log_ctx->fp_mutex);
355  HandleLogRotation(log_ctx);
356  SCMutexUnlock(&log_ctx->fp_mutex);
357 }
358 
359 /**
360  * \brief Write buffer to log file.
361  * \retval 0 on failure; otherwise, the return value of fwrite (number of
362  * characters successfully written).
363  */
364 static int SCLogFileWrite(const char *buffer, int buffer_len, LogFileCtx *log_ctx)
365 {
366  OutputWriteLock(&log_ctx->fp_mutex);
367  int ret = 0;
368 
369 #ifdef BUILD_WITH_UNIXSOCKET
370  if (log_ctx->is_sock) {
371  ret = SCLogFileWriteSocket(buffer, buffer_len, log_ctx);
372  } else
373 #endif
374  {
375  ret = SCLogFileWriteNoLock(buffer, buffer_len, log_ctx);
376  }
377 
378  SCMutexUnlock(&log_ctx->fp_mutex);
379 
380  return ret;
381 }
382 
383 /** \brief generate filename based on pattern
384  * \param pattern pattern to use
385  * \retval char* on success
386  * \retval NULL on error
387  */
388 static char *SCLogFilenameFromPattern(const char *pattern)
389 {
390  char *filename = SCMalloc(PATH_MAX);
391  if (filename == NULL) {
392  return NULL;
393  }
394 
395  int rc = SCTimeToStringPattern(time(NULL), pattern, filename, PATH_MAX);
396  if (rc != 0) {
397  SCFree(filename);
398  return NULL;
399  }
400 
401  return filename;
402 }
403 
404 static void SCLogFileCloseNoLock(LogFileCtx *log_ctx)
405 {
406  SCLogDebug("Closing %s", log_ctx->filename);
407  if (log_ctx->fp) {
408  if (log_ctx->buffer_size)
409  SCFflushUnlocked(log_ctx->fp);
410  fclose(log_ctx->fp);
411  }
412 
413  if (log_ctx->output_errors) {
414  SCLogError("There were %" PRIu64 " output errors to %s", log_ctx->output_errors,
415  log_ctx->filename);
416  }
417 }
418 
419 static void SCLogFileClose(LogFileCtx *log_ctx)
420 {
421  SCMutexLock(&log_ctx->fp_mutex);
422  SCLogFileCloseNoLock(log_ctx);
423  SCMutexUnlock(&log_ctx->fp_mutex);
424 }
425 
426 static char ThreadLogFileHashCompareFunc(
427  void *data1, uint16_t datalen1, void *data2, uint16_t datalen2)
428 {
431 
432  if (p1 == NULL || p2 == NULL)
433  return 0;
434 
435  return p1->thread_id == p2->thread_id;
436 }
437 static uint32_t ThreadLogFileHashFunc(HashTable *ht, void *data, uint16_t datalen)
438 {
439  const ThreadLogFileHashEntry *ent = (ThreadLogFileHashEntry *)data;
440 
441  return ent->thread_id % ht->array_size;
442 }
443 
444 static void ThreadLogFileHashFreeFunc(void *data)
445 {
446  BUG_ON(data == NULL);
447  ThreadLogFileHashEntry *thread_ent = (ThreadLogFileHashEntry *)data;
448 
449  if (!thread_ent)
450  return;
451 
452  if (thread_ent->isopen) {
453  LogFileCtx *lf_ctx = thread_ent->ctx;
454  /* Free the leaf log file entries */
455  if (!lf_ctx->threaded) {
456  LogFileFreeCtx(lf_ctx);
457  }
458  }
459  SCFree(thread_ent);
460 }
461 
462 bool SCLogOpenThreadedFile(const char *log_path, const char *append, LogFileCtx *parent_ctx)
463 {
464  parent_ctx->threads = SCCalloc(1, sizeof(LogThreadedFileCtx));
465  if (!parent_ctx->threads) {
466  SCLogError("Unable to allocate threads container");
467  return false;
468  }
469 
470  parent_ctx->threads->ht = HashTableInit(255, ThreadLogFileHashFunc,
471  ThreadLogFileHashCompareFunc, ThreadLogFileHashFreeFunc);
472  if (!parent_ctx->threads->ht) {
473  FatalError("Unable to initialize thread/entry hash table");
474  }
475 
476  parent_ctx->threads->append = SCStrdup(append == NULL ? DEFAULT_LOG_MODE_APPEND : append);
477  if (!parent_ctx->threads->append) {
478  SCLogError("Unable to allocate threads append setting");
479  goto error_exit;
480  }
481 
482  SCMutexInit(&parent_ctx->threads->mutex, NULL);
483  return true;
484 
485 error_exit:
486 
487  if (parent_ctx->threads->append) {
488  SCFree(parent_ctx->threads->append);
489  }
490  if (parent_ctx->threads->ht) {
491  HashTableFree(parent_ctx->threads->ht);
492  }
493  SCFree(parent_ctx->threads);
494  parent_ctx->threads = NULL;
495  return false;
496 }
497 
498 /** \brief open the indicated file, logging any errors
499  * \param path filesystem path to open
500  * \param append_setting open file with O_APPEND: "yes" or "no"
501  * \param mode permissions to set on file
502  * \retval FILE* on success
503  * \retval NULL on error
504  */
505 static FILE *SCLogOpenFileFp(
506  const char *path, const char *append_setting, uint32_t mode, const uint32_t buffer_size)
507 {
508  FILE *ret = NULL;
509 
510  char *filename = SCLogFilenameFromPattern(path);
511  if (filename == NULL) {
512  return NULL;
513  }
514 
515  int rc = SCCreateDirectoryTree(filename, false);
516  if (rc < 0) {
517  SCFree(filename);
518  return NULL;
519  }
520 
521  const char *fmode = SCConfValIsTrue(append_setting) ? "a" : "w";
522  ret = SCFopenLogFile(filename, fmode);
523 
524  if (ret == NULL) {
525  SCLogError("Error opening file: \"%s\": %s", filename, strerror(errno));
526  goto error_exit;
527  } else {
528  if (mode != 0) {
529 #ifdef OS_WIN32
530  int r = _chmod(filename, (mode_t)mode);
531 #else
532  int r = fchmod(fileno(ret), (mode_t)mode);
533 #endif
534  if (r < 0) {
535  SCLogWarning("Could not chmod %s to %o: %s", filename, mode, strerror(errno));
536  }
537  }
538  }
539 
540  /* Set buffering behavior */
541  if (buffer_size == 0) {
542  setbuf(ret, NULL);
543  SCLogConfig("Setting output to %s non-buffered", filename);
544  } else {
545  if (setvbuf(ret, NULL, _IOFBF, buffer_size) < 0)
546  FatalError("unable to set %s to buffered: %d", filename, buffer_size);
547  SCLogConfig("Setting output to %s buffered [limit %d bytes]", filename, buffer_size);
548  }
549 
550 error_exit:
551  SCFree(filename);
552 
553  return ret;
554 }
555 
556 /** \brief open a generic output "log file", which may be a regular file or a socket
557  * \param conf ConfNode structure for the output section in question
558  * \param log_ctx Log file context allocated by caller
559  * \param default_filename Default name of file to open, if not specified in ConfNode
560  * \param rotate Register the file for rotation in HUP.
561  * \retval 0 on success
562  * \retval -1 on error
563  */
565  SCConfNode *conf, LogFileCtx *log_ctx, const char *default_filename, int rotate)
566 {
567  char log_path[PATH_MAX];
568  const char *log_dir;
569  const char *filename, *filetype;
570 
571  // Arg check
572  if (conf == NULL || log_ctx == NULL || default_filename == NULL) {
573  SCLogError("SCConfLogOpenGeneric(conf %p, ctx %p, default %p) "
574  "missing an argument",
575  conf, log_ctx, default_filename);
576  return -1;
577  }
578  if (log_ctx->fp != NULL) {
579  SCLogError("SCConfLogOpenGeneric: previously initialized Log CTX "
580  "encountered");
581  return -1;
582  }
583 
584  // Resolve the given config
585  filename = SCConfNodeLookupChildValue(conf, "filename");
586  if (filename == NULL)
587  filename = default_filename;
588 
589  log_dir = SCConfigGetLogDirectory();
590 
591  if (PathIsAbsolute(filename)) {
592  snprintf(log_path, PATH_MAX, "%s", filename);
593  } else {
594  snprintf(log_path, PATH_MAX, "%s/%s", log_dir, filename);
595  }
596 
597  /* Compress IPv6 addresses */
598  const char *compress = SCConfNodeLookupChildValue(conf, "ipv6-compress");
599  log_ctx->compress_ipv6 = false;
600  if (compress != NULL && SCConfValIsTrue(compress))
601  log_ctx->compress_ipv6 = true;
602 
603  /* Rotate log file based on time */
604  const char *rotate_int = SCConfNodeLookupChildValue(conf, "rotate-interval");
605  if (rotate_int != NULL) {
606  /* Use a specific time */
607  if (strcmp(rotate_int, "minute") == 0) {
609  } else if (strcmp(rotate_int, "hour") == 0) {
611  } else if (strcmp(rotate_int, "day") == 0) {
613  }
614 
615  /* Use a timer */
616  else {
618  log_ctx->rotate_interval = SCParseTimeSizeString(rotate_int);
619  if (log_ctx->rotate_interval == 0) {
620  FatalError("invalid rotate-interval value");
621  }
622  }
623  LogFileRotateArm(log_ctx, time(NULL));
624  }
625 
626  filetype = SCConfNodeLookupChildValue(conf, "filetype");
627  if (filetype == NULL)
628  filetype = DEFAULT_LOG_FILETYPE;
629 
630  /* Determine the buffering for this output device; a value of 0 means to not buffer;
631  * any other value must be a multiple of 4096
632  * The default value is 0 (no buffering)
633  */
634  uint32_t buffer_size = LOGFILE_EVE_BUFFER_SIZE;
635  const char *buffer_size_value = SCConfNodeLookupChildValue(conf, "buffer-size");
636  if (buffer_size_value != NULL) {
637  uint32_t value;
638  if (ParseSizeStringU32(buffer_size_value, &value) < 0) {
639  FatalError("Error parsing "
640  "buffer-size - %s. Killing engine",
641  buffer_size_value);
642  }
643  buffer_size = value;
644  }
645 
646  SCLogDebug("buffering: %s -> %d", buffer_size_value, buffer_size);
647  const char *filemode = SCConfNodeLookupChildValue(conf, "filemode");
648  uint32_t mode = 0;
649  if (filemode != NULL && StringParseUint32(&mode, 8, (uint16_t)strlen(filemode), filemode) > 0) {
650  log_ctx->filemode = mode;
651  }
652 
653  const char *append = SCConfNodeLookupChildValue(conf, "append");
654  if (append == NULL)
655  append = DEFAULT_LOG_MODE_APPEND;
656 
657  /* JSON flags */
658  log_ctx->json_flags = JSON_PRESERVE_ORDER|JSON_COMPACT|
659  JSON_ENSURE_ASCII|JSON_ESCAPE_SLASH;
660 
661  SCConfNode *json_flags = SCConfNodeLookupChild(conf, "json");
662 
663  if (json_flags != 0) {
664  const char *preserve_order = SCConfNodeLookupChildValue(json_flags, "preserve-order");
665  if (preserve_order != NULL && SCConfValIsFalse(preserve_order))
666  log_ctx->json_flags &= ~(JSON_PRESERVE_ORDER);
667 
668  const char *compact = SCConfNodeLookupChildValue(json_flags, "compact");
669  if (compact != NULL && SCConfValIsFalse(compact))
670  log_ctx->json_flags &= ~(JSON_COMPACT);
671 
672  const char *ensure_ascii = SCConfNodeLookupChildValue(json_flags, "ensure-ascii");
673  if (ensure_ascii != NULL && SCConfValIsFalse(ensure_ascii))
674  log_ctx->json_flags &= ~(JSON_ENSURE_ASCII);
675 
676  const char *escape_slash = SCConfNodeLookupChildValue(json_flags, "escape-slash");
677  if (escape_slash != NULL && SCConfValIsFalse(escape_slash))
678  log_ctx->json_flags &= ~(JSON_ESCAPE_SLASH);
679  }
680 
681 #ifdef BUILD_WITH_UNIXSOCKET
682  if (log_ctx->threaded) {
683  if (strcasecmp(filetype, "unix_stream") == 0 || strcasecmp(filetype, "unix_dgram") == 0) {
684  FatalError("Socket file types do not support threaded output");
685  }
686  }
687 #endif
688  if (!(strcasecmp(filetype, DEFAULT_LOG_FILETYPE) == 0 || strcasecmp(filetype, "file") == 0)) {
689  SCLogConfig("buffering setting ignored for %s output types", filetype);
690  }
691 
692  // Now, what have we been asked to open?
693  if (strcasecmp(filetype, "unix_stream") == 0) {
694 #ifdef BUILD_WITH_UNIXSOCKET
695  /* Don't bail. May be able to connect later. */
696  log_ctx->is_sock = 1;
697  log_ctx->sock_type = SOCK_STREAM;
698  log_ctx->fp = SCLogOpenUnixSocketFp(log_path, SOCK_STREAM, 1);
699 #else
700  return -1;
701 #endif
702  } else if (strcasecmp(filetype, "unix_dgram") == 0) {
703 #ifdef BUILD_WITH_UNIXSOCKET
704  /* Don't bail. May be able to connect later. */
705  log_ctx->is_sock = 1;
706  log_ctx->sock_type = SOCK_DGRAM;
707  log_ctx->fp = SCLogOpenUnixSocketFp(log_path, SOCK_DGRAM, 1);
708 #else
709  return -1;
710 #endif
711  } else if (strcasecmp(filetype, DEFAULT_LOG_FILETYPE) == 0 ||
712  strcasecmp(filetype, "file") == 0) {
713  log_ctx->is_regular = 1;
714  log_ctx->buffer_size = buffer_size;
715  if (!log_ctx->threaded) {
716  log_ctx->fp =
717  SCLogOpenFileFp(log_path, append, log_ctx->filemode, log_ctx->buffer_size);
718  if (log_ctx->fp == NULL)
719  return -1; // Error already logged by Open...Fp routine
720  } else {
721  if (!SCLogOpenThreadedFile(log_path, append, log_ctx)) {
722  return -1;
723  }
724  }
725  if (rotate) {
727  }
728  /* Register non-threaded regular files for heartbeat maintenance */
729  if (!log_ctx->threaded && log_ctx->is_regular) {
730  LogFileRegister(log_ctx);
731  }
732  } else {
733  SCLogError("Invalid entry for "
734  "%s.filetype. Expected \"regular\" (default), \"unix_stream\", "
735  "or \"unix_dgram\"",
736  conf->name);
737  }
738  log_ctx->filename = SCStrdup(log_path);
739  if (unlikely(log_ctx->filename == NULL)) {
740  SCLogError("Failed to allocate memory for filename");
741  return -1;
742  }
743 
744 #ifdef BUILD_WITH_UNIXSOCKET
745  /* If a socket and running live, do non-blocking writes. */
746  if (log_ctx->is_sock && !IsRunModeOffline(SCRunmodeGet())) {
747  SCLogInfo("Setting logging socket of non-blocking in live mode.");
748  log_ctx->send_flags |= MSG_DONTWAIT;
749  }
750 #endif
751  SCLogInfo("%s output device (%s) initialized: %s", conf->name, filetype,
752  filename);
753 
754  return 0;
755 }
756 
757 /**
758  * \brief Reopen a regular log file with the side-affect of truncating it.
759  *
760  * This is useful to clear the log file and start a new one, or to
761  * re-open the file after its been moved by something external
762  * (eg. logrotate).
763  */
765 {
766  if (!log_ctx->is_regular) {
767  /* Not supported and not needed on non-regular files. */
768  return 0;
769  }
770 
771  if (log_ctx->filename == NULL) {
772  SCLogWarning("Can't re-open LogFileCtx without a filename.");
773  return -1;
774  }
775 
776  if (log_ctx->fp != NULL) {
777  fclose(log_ctx->fp);
778  }
779 
780  /* Reopen the file. Append is forced in case the file was not
781  * moved as part of a rotation process. */
782  SCLogDebug("Reopening log file %s.", log_ctx->filename);
783  log_ctx->fp =
784  SCLogOpenFileFp(log_ctx->filename, "yes", log_ctx->filemode, log_ctx->buffer_size);
785  if (log_ctx->fp == NULL) {
786  return -1; // Already logged by Open..Fp routine.
787  }
788 
789  return 0;
790 }
791 
792 /** \brief LogFileNewCtx() Get a new LogFileCtx
793  * \retval LogFileCtx * pointer if successful, NULL if error
794  * */
796 {
797  LogFileCtx* lf_ctx;
798  lf_ctx = (LogFileCtx*)SCCalloc(1, sizeof(LogFileCtx));
799 
800  if (lf_ctx == NULL)
801  return NULL;
802 
803  lf_ctx->Write = SCLogFileWrite;
804  lf_ctx->Close = SCLogFileClose;
805  lf_ctx->Flush = SCLogFileFlush;
806  lf_ctx->Rotate = SCLogFileRotate;
807 
808  return lf_ctx;
809 }
810 
811 /** \brief LogFileThread2Slot() Return a file entry
812  * \retval ThreadLogFileHashEntry * file entry for caller
813  *
814  * This function returns the file entry for the calling thread.
815  * Each thread -- identified by its operating system thread-id -- has its
816  * own file entry that includes a file pointer.
817  */
818 static ThreadLogFileHashEntry *LogFileThread2Slot(LogThreadedFileCtx *parent, ThreadId thread_id)
819 {
820  ThreadLogFileHashEntry thread_hash_entry;
821 
822  /* Check hash table for thread id*/
823  thread_hash_entry.thread_id = SCGetThreadIdLong();
825  HashTableLookup(parent->ht, &thread_hash_entry, sizeof(thread_hash_entry));
826 
827  if (!ent) {
828  ent = SCCalloc(1, sizeof(*ent));
829  if (!ent) {
830  FatalError("Unable to allocate thread/hash-entry entry");
831  }
832  ent->thread_id = thread_hash_entry.thread_id;
833  ent->internal_thread_id = thread_id;
834  SCLogDebug(
835  "Trying to add thread %" PRIi64 " to entry %d", ent->thread_id, ent->slot_number);
836  if (0 != HashTableAdd(parent->ht, ent, 0)) {
837  FatalError("Unable to add thread/hash-entry mapping");
838  }
839  }
840  return ent;
841 }
842 
843 /** \brief LogFileEnsureExists() Ensure a log file context for the thread exists
844  * \param parent_ctx
845  * \retval LogFileCtx * pointer if successful; NULL otherwise
846  */
848 {
849  /* threaded output disabled */
850  if (!parent_ctx->threaded)
851  return parent_ctx;
852 
853  LogFileCtx *ret_ctx = NULL;
854  SCMutexLock(&parent_ctx->threads->mutex);
855  /* Find this thread's entry */
856  ThreadLogFileHashEntry *entry = LogFileThread2Slot(parent_ctx->threads, thread_id);
857  SCLogDebug("%s: Adding reference for thread %" PRIi64
858  " (local thread id %d) to file %s [ctx %p]",
859  t_thread_name, SCGetThreadIdLong(), thread_id, parent_ctx->filename, parent_ctx);
860 
861  bool new = entry->isopen;
862  /* has it been opened yet? */
863  if (!new) {
864  SCLogDebug("%s: Opening new file for thread/id %d to file %s [ctx %p]", t_thread_name,
865  thread_id, parent_ctx->filename, parent_ctx);
866  if (LogFileNewThreadedCtx(
867  parent_ctx, parent_ctx->filename, parent_ctx->threads->append, entry)) {
868  entry->isopen = true;
869  ret_ctx = entry->ctx;
870  } else {
871  SCLogDebug(
872  "Unable to open slot %d for file %s", entry->slot_number, parent_ctx->filename);
873  (void)HashTableRemove(parent_ctx->threads->ht, entry, 0);
874  }
875  } else {
876  ret_ctx = entry->ctx;
877  }
878  SCMutexUnlock(&parent_ctx->threads->mutex);
879 
881  if (new) {
882  SCLogDebug("Existing file for thread/entry %p reference to file %s [ctx %p]", entry,
883  parent_ctx->filename, parent_ctx);
884  }
885  }
886 
887  return ret_ctx;
888 }
889 
890 /** \brief LogFileThreadedName() Create file name for threaded EVE storage
891  *
892  */
893 static bool LogFileThreadedName(
894  const char *original_name, char *threaded_name, size_t len, uint32_t unique_id)
895 {
896  sc_errno = SC_OK;
897 
898  if (strcmp("/dev/null", original_name) == 0) {
899  strlcpy(threaded_name, original_name, len);
900  return true;
901  }
902 
903  const char *base = SCBasename(original_name);
904  if (!base) {
905  FatalError("Invalid filename for threaded mode \"%s\"; "
906  "no basename found.",
907  original_name);
908  }
909 
910  /* Check if basename has an extension */
911  const char *dot = strrchr(base, '.');
912  if (dot) {
913  char *tname = SCStrdup(original_name);
914  if (!tname) {
916  return false;
917  }
918 
919  /* Fetch extension location from original, not base
920  * for update
921  */
922  dot = strrchr(original_name, '.');
923  ptrdiff_t dotpos = dot - original_name;
924  tname[dotpos] = '\0';
925  char *ext = tname + dotpos + 1;
926  if (strlen(tname) && strlen(ext)) {
927  snprintf(threaded_name, len, "%s.%u.%s", tname, unique_id, ext);
928  } else {
929  FatalError("Invalid filename for threaded mode \"%s\"; "
930  "filenames must include an extension, e.g: \"name.ext\"",
931  original_name);
932  }
933  SCFree(tname);
934  } else {
935  snprintf(threaded_name, len, "%s.%u", original_name, unique_id);
936  }
937  return true;
938 }
939 
940 /** \brief LogFileNewThreadedCtx() Create file context for threaded output
941  * \param parent_ctx
942  * \param log_path
943  * \param append
944  * \param entry
945  */
946 static bool LogFileNewThreadedCtx(LogFileCtx *parent_ctx, const char *log_path, const char *append,
947  ThreadLogFileHashEntry *entry)
948 {
949  LogFileCtx *thread = SCCalloc(1, sizeof(LogFileCtx));
950  if (!thread) {
951  SCLogError("Unable to allocate thread file context entry %p", entry);
952  return false;
953  }
954 
955  *thread = *parent_ctx;
956  if (parent_ctx->type == LOGFILE_TYPE_FILE) {
957  char fname[LOGFILE_NAME_MAX];
958  entry->slot_number = SC_ATOMIC_ADD(eve_file_id, 1);
959  if (!LogFileThreadedName(log_path, fname, sizeof(fname), entry->slot_number)) {
960  SCLogError("Unable to create threaded filename for log");
961  goto error;
962  }
963  SCLogDebug("%s: thread open -- using name %s [replaces %s] - thread %d [slot %d]",
964  t_thread_name, fname, log_path, entry->internal_thread_id, entry->slot_number);
965  thread->fp = SCLogOpenFileFp(fname, append, thread->filemode, parent_ctx->buffer_size);
966  if (thread->fp == NULL) {
967  goto error;
968  }
969  thread->filename = SCStrdup(fname);
970  if (!thread->filename) {
971  SCLogError("Unable to duplicate filename for context entry %p", entry);
972  goto error;
973  }
974  thread->is_regular = true;
975  thread->Write = SCLogFileWrite;
976  thread->Close = SCLogFileClose;
977  thread->Rotate = SCLogFileRotate;
979  LogFileRegister(thread);
980  } else if (parent_ctx->type == LOGFILE_TYPE_FILETYPE) {
981  entry->slot_number = SC_ATOMIC_ADD(eve_file_id, 1);
982  SCLogDebug("%s - thread %d [slot %d]", log_path, entry->internal_thread_id,
983  entry->slot_number);
985  &thread->filetype.thread_data);
986  }
987  thread->threaded = false;
988  thread->parent = parent_ctx;
989  thread->entry = entry;
990  entry->ctx = thread;
991 
992  return true;
993 
994 error:
995  if (parent_ctx->type == LOGFILE_TYPE_FILE) {
996  SC_ATOMIC_SUB(eve_file_id, 1);
997  if (thread->fp) {
998  thread->Close(thread);
999  }
1000  }
1001 
1002  if (thread) {
1003  SCFree(thread);
1004  }
1005  return false;
1006 }
1007 
1008 /** \brief LogFileFreeCtx() Destroy a LogFileCtx (Close the file and free memory)
1009  * \param lf_ctx pointer to the OutputCtx
1010  * \retval int 1 if successful, 0 if error
1011  * */
1013 {
1014  if (lf_ctx == NULL) {
1015  SCReturnInt(0);
1016  }
1017 
1018  /* Unregister from flush list first, before closing files.
1019  * This ensures the heartbeat thread won't try to flush a context
1020  * that's being destroyed. */
1021  LogFileUnregister(lf_ctx);
1022 
1023  if (lf_ctx->type == LOGFILE_TYPE_FILETYPE && lf_ctx->filetype.filetype->ThreadDeinit) {
1024  lf_ctx->filetype.filetype->ThreadDeinit(
1025  lf_ctx->filetype.init_data, lf_ctx->filetype.thread_data);
1026  }
1027 
1028  if (lf_ctx->threaded) {
1029  BUG_ON(lf_ctx->threads == NULL);
1030  SCMutexDestroy(&lf_ctx->threads->mutex);
1031  if (lf_ctx->threads->append)
1032  SCFree(lf_ctx->threads->append);
1033  if (lf_ctx->threads->ht) {
1034  HashTableFree(lf_ctx->threads->ht);
1035  }
1036  SCFree(lf_ctx->threads);
1037  } else {
1038  if (lf_ctx->type != LOGFILE_TYPE_FILETYPE) {
1039  if (lf_ctx->fp != NULL) {
1040  lf_ctx->Close(lf_ctx);
1041  }
1042  }
1043  SCMutexDestroy(&lf_ctx->fp_mutex);
1044  }
1045 
1046  if (lf_ctx->parent == NULL) {
1047  if (lf_ctx->prefix != NULL) {
1048  SCFree(lf_ctx->prefix);
1049  lf_ctx->prefix_len = 0;
1050  }
1051 
1052  if (lf_ctx->sensor_name != NULL) {
1053  SCFree(lf_ctx->sensor_name);
1054  }
1055  }
1056 
1057  if(lf_ctx->filename != NULL)
1058  SCFree(lf_ctx->filename);
1059 
1060  if (!lf_ctx->threaded) {
1062  }
1063 
1064  /* Deinitialize output filetypes. We only want to call this for
1065  * the parent of threaded output, or always for non-threaded
1066  * output. */
1067  if (lf_ctx->type == LOGFILE_TYPE_FILETYPE && lf_ctx->parent == NULL) {
1068  lf_ctx->filetype.filetype->Deinit(lf_ctx->filetype.init_data);
1069  }
1070 
1071 #ifdef HAVE_LIBHIREDIS
1072  if (lf_ctx->type == LOGFILE_TYPE_REDIS) {
1073  if (lf_ctx->redis_setup.stream_format != NULL) {
1074  SCFree(lf_ctx->redis_setup.stream_format);
1075  }
1076  }
1077 #endif
1078 
1079  memset(lf_ctx, 0, sizeof(*lf_ctx));
1080  SCFree(lf_ctx);
1081 
1082  SCReturnInt(1);
1083 }
1084 
1085 void LogFileFlush(LogFileCtx *file_ctx)
1086 {
1087  SCLogDebug("%s: bytes-to-flush %ld", file_ctx->filename, file_ctx->bytes_since_last_flush);
1088  file_ctx->Flush(file_ctx);
1089 }
1090 
1091 /**
1092  * \brief Register a LogFileCtx for maintenance operations
1093  *
1094  * Adds a LogFileCtx to the global log file list so the heartbeat thread
1095  * can perform flush and rotation on it.
1096  *
1097  * \param ctx The LogFileCtx to register (must be LOGFILE_TYPE_FILE)
1098  */
1100 {
1101  if (ctx == NULL || ctx->type != LOGFILE_TYPE_FILE) {
1102  return;
1103  }
1104 
1105  LogFileEntry *entry = SCMalloc(sizeof(LogFileEntry));
1106  if (entry == NULL) {
1107  SCLogError("Unable to allocate memory for log file entry");
1108  return;
1109  }
1110 
1111  entry->ctx = ctx;
1112 
1113  SCMutexLock(&log_file_list_mutex);
1114  TAILQ_INSERT_TAIL(&log_file_list, entry, entries);
1115  SCMutexUnlock(&log_file_list_mutex);
1116 }
1117 
1118 /**
1119  * \brief Unregister a LogFileCtx from maintenance operations
1120  *
1121  * Removes a LogFileCtx from the global log file list.
1122  *
1123  * \param ctx The LogFileCtx to unregister
1124  */
1126 {
1127  if (ctx == NULL) {
1128  return;
1129  }
1130 
1131  SCMutexLock(&log_file_list_mutex);
1132  LogFileEntry *entry, *safe;
1133  TAILQ_FOREACH_SAFE (entry, &log_file_list, entries, safe) {
1134  if (entry->ctx == ctx) {
1135  TAILQ_REMOVE(&log_file_list, entry, entries);
1136  SCFree(entry);
1137  break;
1138  }
1139  }
1140  SCMutexUnlock(&log_file_list_mutex);
1141 }
1142 
1143 /**
1144  * \brief Flush all registered LogFileCtx instances
1145  *
1146  * Called by the maintenance thread to flush all active file-based loggers.
1147  */
1149 {
1150  SCMutexLock(&log_file_list_mutex);
1151  LogFileEntry *entry;
1152  TAILQ_FOREACH (entry, &log_file_list, entries) {
1153  if (entry->ctx != NULL) {
1154  LogFileFlush(entry->ctx);
1155  }
1156  }
1157  SCMutexUnlock(&log_file_list_mutex);
1158 }
1159 
1160 /**
1161  * \brief Check rotation for all registered LogFileCtx instances
1162  *
1163  * Called by the maintenance thread to trigger rotation checks on all
1164  * registered log contexts during zero-traffic periods.
1165  */
1167 {
1168  SCMutexLock(&log_file_list_mutex);
1169  LogFileEntry *entry;
1170  TAILQ_FOREACH (entry, &log_file_list, entries) {
1171  if (entry->ctx != NULL && entry->ctx->Rotate != NULL) {
1172  entry->ctx->Rotate(entry->ctx);
1173  }
1174  }
1175  SCMutexUnlock(&log_file_list_mutex);
1176 }
1177 
1178 int LogFileWrite(LogFileCtx *file_ctx, MemBuffer *buffer)
1179 {
1180  if (file_ctx->type == LOGFILE_TYPE_FILE || file_ctx->type == LOGFILE_TYPE_UNIX_DGRAM ||
1181  file_ctx->type == LOGFILE_TYPE_UNIX_STREAM) {
1182  /* append \n for files only */
1183  MemBufferWriteString(buffer, "\n");
1184  file_ctx->Write((const char *)MEMBUFFER_BUFFER(buffer),
1185  MEMBUFFER_OFFSET(buffer), file_ctx);
1186  } else if (file_ctx->type == LOGFILE_TYPE_FILETYPE) {
1187  file_ctx->filetype.filetype->Write((const char *)MEMBUFFER_BUFFER(buffer),
1188  MEMBUFFER_OFFSET(buffer), file_ctx->filetype.init_data,
1189  file_ctx->filetype.thread_data);
1190  }
1191 #ifdef HAVE_LIBHIREDIS
1192  else if (file_ctx->type == LOGFILE_TYPE_REDIS) {
1193  SCMutexLock(&file_ctx->fp_mutex);
1194  LogFileWriteRedis(file_ctx, (const char *)MEMBUFFER_BUFFER(buffer),
1195  MEMBUFFER_OFFSET(buffer));
1196  SCMutexUnlock(&file_ctx->fp_mutex);
1197  }
1198 #endif
1199 
1200  return 0;
1201 }
1202 
1203 /* the test needs POSIX TZ rules, which Windows does not support */
1204 #if defined(UNITTESTS) && !defined(OS_WIN32)
1205 /**
1206  * \brief Call the rotation check for the 10 seconds around every quarter hour
1207  * from 1 hour before to 26 hours after the DST switch, as if a log
1208  * record was written each of these seconds, and check that it rotates
1209  * exactly when the filename pattern expands to a new name.
1210  *
1211  * All timezones in use have UTC offsets in multiples of 15 minutes, so the
1212  * switch and every local hour and day start fall on a UTC quarter hour. The
1213  * 26 hours reach the first local midnight after the switch, where a day
1214  * rotation with a fixed length of 86400 seconds would be off by the DST shift.
1215  */
1216 static int LogFileRotateTestAroundSwitch(
1217  LogFileRotateUnit unit, const char *pattern, time_t dst_switch)
1218 {
1219  const time_t start = dst_switch - 3600;
1220  const time_t end = dst_switch + 26 * 3600;
1221 
1222  LogFileCtx ctx;
1223  memset(&ctx, 0, sizeof(ctx));
1224  ctx.rotate_unit = unit;
1225  LogFileRotateArm(&ctx, start);
1226 
1227  char file_name[32];
1228  FAIL_IF(SCTimeToStringPattern(start, pattern, file_name, sizeof(file_name)) != 0);
1229  int rotations = 0;
1230 
1231  for (time_t quarter = start + 900; quarter < end; quarter += 900) {
1232  for (time_t now = quarter - 5; now < quarter + 5; now++) {
1233  char expected_name[32];
1234  FAIL_IF(SCTimeToStringPattern(now, pattern, expected_name, sizeof(expected_name)) != 0);
1235  const bool name_changed = strcmp(expected_name, file_name) != 0;
1236 
1237  const bool rotated = LogFileRotateUpdate(&ctx, now);
1238  FAIL_IF(rotated != name_changed);
1239 
1240  if (rotated) {
1241  strlcpy(file_name, expected_name, sizeof(file_name));
1242  rotations++;
1243  }
1244  }
1245  }
1246  /* a window without any name change would prove nothing */
1247  FAIL_IF(rotations == 0);
1248  PASS;
1249 }
1250 
1251 /** \brief Set the timezone of the process, NULL for the system default */
1252 static void LogFileRotateTestSetTimezone(const char *tz)
1253 {
1254  if (tz != NULL) {
1255  setenv("TZ", tz, 1);
1256  } else {
1257  unsetenv("TZ");
1258  }
1259  tzset();
1261 }
1262 
1263 /** \brief Check the rotation of all units around both DST switches of tz */
1264 static int LogFileRotateTestTimezone(const char *tz, time_t dst_start, time_t dst_end)
1265 {
1266  const struct {
1267  LogFileRotateUnit unit;
1268  const char *pattern;
1269  } units[] = {
1270  { LOGFILE_ROTATE_ON_NEW_MINUTE, "%Y-%m-%d-%H:%M" },
1271  { LOGFILE_ROTATE_ON_NEW_HOUR, "%Y-%m-%d-%H" },
1272  { LOGFILE_ROTATE_ON_NEW_DAY, "%Y-%m-%d" },
1273  };
1274 
1275  LogFileRotateTestSetTimezone(tz);
1276  for (size_t u = 0; u < ARRAY_SIZE(units); u++) {
1277  FAIL_IF_NOT(LogFileRotateTestAroundSwitch(units[u].unit, units[u].pattern, dst_start) == 1);
1278  FAIL_IF_NOT(LogFileRotateTestAroundSwitch(units[u].unit, units[u].pattern, dst_end) == 1);
1279  }
1280  PASS;
1281 }
1282 
1283 /** \test minute, hour and day rotations follow the local time, also around
1284  * DST transitions of timezones with unusual rules */
1285 static int LogFileRotateTest01(void)
1286 {
1287  /* POSIX TZ rules of timezones with DST, and their DST switches in 2026 */
1288  const struct {
1289  const char *tz;
1290  time_t dst_start;
1291  time_t dst_end;
1292  } timezones[] = {
1293  /* America/New_York: 2026-03-08 07:00 and 2026-11-01 06:00 UTC */
1294  { "EST5EDT,M3.2.0,M11.1.0", 1772953200, 1793512800 },
1295  /* America/Havana, DST start skips midnight: 2026-03-08 and 2026-11-01 05:00 UTC */
1296  { "CST5CDT,M3.2.0/0,M11.1.0/1", 1772946000, 1793509200 },
1297  /* Europe/Prague: 2026-03-29 and 2026-10-25 01:00 UTC */
1298  { "CET-1CEST,M3.5.0,M10.5.0/3", 1774746000, 1792890000 },
1299  /* Australia/Sydney: 2026-10-03 and 2026-04-04 16:00 UTC */
1300  { "AEST-10AEDT,M10.1.0,M4.1.0/3", 1791043200, 1775318400 },
1301  /* Australia/Lord_Howe, 30 minute DST: 2026-10-03 15:30 and 2026-04-04 15:00 UTC */
1302  { "<+1030>-10:30<+11>-11,M10.1.0,M4.1.0", 1791041400, 1775314800 },
1303  /* Pacific/Chatham, switches at 02:45 and 03:45: 2026-09-26 and 2026-04-04 14:00 UTC */
1304  { "<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45", 1790431200, 1775311200 },
1305  };
1306 
1307  const char *env_tz = getenv("TZ");
1308  char *orig_tz = env_tz != NULL ? SCStrdup(env_tz) : NULL;
1309  FAIL_IF(env_tz != NULL && orig_tz == NULL);
1310 
1311  int result = 1;
1312  for (size_t i = 0; i < ARRAY_SIZE(timezones); i++) {
1313  if (LogFileRotateTestTimezone(
1314  timezones[i].tz, timezones[i].dst_start, timezones[i].dst_end) != 1) {
1315  result = 0;
1316  break;
1317  }
1318  }
1319 
1320  /* restore the timezone before reporting, also when a check failed */
1321  LogFileRotateTestSetTimezone(orig_tz);
1322  SCFree(orig_tz);
1323  FAIL_IF_NOT(result == 1);
1324  PASS;
1325 }
1326 #endif /* UNITTESTS && !OS_WIN32 */
1327 
1329 {
1330 #if defined(UNITTESTS) && !defined(OS_WIN32)
1331  UtRegisterTest("LogFileRotateTest01", LogFileRotateTest01);
1332 #endif
1333 }
LogFileRegisterTests
void LogFileRegisterTests(void)
Definition: util-logopenfile.c:1328
SCParseTimeSizeString
uint64_t SCParseTimeSizeString(const char *str)
Parse string containing time size (1m, 1h, etc).
Definition: util-time.c:588
LogFileCtx_::rotation_flag
int rotation_flag
Definition: util-logopenfile.h:172
util-byte.h
LogThreadedFileCtx_::append
char * append
Definition: util-logopenfile.h:62
SCFerrorUnlocked
#define SCFerrorUnlocked
Definition: suricata-common.h:552
len
uint8_t len
Definition: app-layer-dnp3.h:2
SCConfValIsTrue
int SCConfValIsTrue(const char *val)
Check if a value is true.
Definition: conf.c:577
LogFileEntry_::ctx
struct LogFileCtx_ * ctx
Definition: util-logopenfile.h:72
LOGFILE_TYPE_REDIS
@ LOGFILE_TYPE_REDIS
Definition: util-logopenfile.h:42
LogFileCtx_::sensor_name
char * sensor_name
Definition: util-logopenfile.h:129
SCEveFileType_::Deinit
SCEveFileTypeDeinitFunc Deinit
Final call to deinitialize this filetype.
Definition: output-eve.h:217
LogFileCtx_::reconn_timer
uint64_t reconn_timer
Definition: util-logopenfile.h:134
IsRunModeOffline
bool IsRunModeOffline(enum SCRunModes run_mode_to_check)
Definition: runmodes.c:556
SC_LOG_DEBUG
@ SC_LOG_DEBUG
Definition: util-debug.h:44
unsetenv
void unsetenv(const char *name)
LogFileCtx_::fp_mutex
SCMutex fp_mutex
Definition: util-logopenfile.h:109
unlikely
#define unlikely(expr)
Definition: util-optimize.h:35
LOGFILE_ROTATE_ON_FIXED_PERIOD
@ LOGFILE_ROTATE_ON_FIXED_PERIOD
Definition: util-logopenfile.h:78
SC_ATOMIC_DECL_AND_INIT_WITH_VAL
#define SC_ATOMIC_DECL_AND_INIT_WITH_VAL(type, name, val)
wrapper for declaring an atomic variable and initializing it to a specific value
Definition: util-atomic.h:303
UtRegisterTest
void UtRegisterTest(const char *name, int(*TestFn)(void))
Register unit test.
Definition: util-unittest.c:101
SCEveFileType_::Write
SCEveFileTypeWriteFunc Write
Called for each EVE log record.
Definition: output-eve.h:195
LogFileNewCtx
LogFileCtx * LogFileNewCtx(void)
LogFileNewCtx() Get a new LogFileCtx.
Definition: util-logopenfile.c:795
SCLogDebug
#define SCLogDebug(...)
Definition: util-debug.h:282
LogFileRotateAll
void LogFileRotateAll(void)
Check rotation for all registered LogFileCtx instances.
Definition: util-logopenfile.c:1166
LogFileCtx_::rotate_period
int64_t rotate_period
Definition: util-logopenfile.h:146
LogFileFlush
void LogFileFlush(LogFileCtx *file_ctx)
Definition: util-logopenfile.c:1085
LOGFILE_ROTATE_ON_NEW_DAY
@ LOGFILE_ROTATE_ON_NEW_DAY
Definition: util-logopenfile.h:81
LogFileCtx_::json_flags
size_t json_flags
Definition: util-logopenfile.h:175
ctx
struct Thresholds ctx
SC_ATOMIC_ADD
#define SC_ATOMIC_ADD(name, val)
add a value to our atomic variable
Definition: util-atomic.h:332
LogFileCtx_
Definition: util-logopenfile.h:85
LOGFILE_RECONN_MIN_TIME
#define LOGFILE_RECONN_MIN_TIME
Definition: util-logopenfile.h:191
TAILQ_FOREACH
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:252
DEFAULT_LOG_MODE_APPEND
#define DEFAULT_LOG_MODE_APPEND
Definition: output.h:30
SCMutexLock
#define SCMutexLock(mut)
Definition: threads-debug.h:117
HashTable_
Definition: util-hash.h:35
util-log-redis.h
SCConfValIsFalse
int SCConfValIsFalse(const char *val)
Check if a value is false.
Definition: conf.c:602
LogFileCtx_::Write
int(* Write)(const char *buffer, int buffer_len, struct LogFileCtx_ *fp)
Definition: util-logopenfile.h:100
SCMUTEX_INITIALIZER
#define SCMUTEX_INITIALIZER
Definition: threads-debug.h:122
LogFileCtx_::filename
char * filename
Definition: util-logopenfile.h:120
TAILQ_INSERT_TAIL
#define TAILQ_INSERT_TAIL(head, elm, field)
Definition: queue.h:294
LOGFILE_NAME_MAX
#define LOGFILE_NAME_MAX
Definition: util-logopenfile.c:50
JSON_ESCAPE_SLASH
#define JSON_ESCAPE_SLASH
Definition: suricata-common.h:295
m
SCMutex m
Definition: flow-hash.h:6
SCConfNodeLookupChildValue
const char * SCConfNodeLookupChildValue(const SCConfNode *node, const char *name)
Lookup the value of a child configuration node by name.
Definition: conf.c:877
ThreadLogFileHashEntry::ctx
struct LogFileCtx_ * ctx
Definition: util-logopenfile.h:49
LogFileCtx_::send_flags
uint8_t send_flags
Definition: util-logopenfile.h:162
SCConfLogReopen
int SCConfLogReopen(LogFileCtx *log_ctx)
Reopen a regular log file with the side-affect of truncating it.
Definition: util-logopenfile.c:764
ThreadLogFileHashEntry::slot_number
uint16_t slot_number
Definition: util-logopenfile.h:54
ThreadLogFileHashEntry::isopen
bool isopen
Definition: util-logopenfile.h:55
SCClearErrUnlocked
#define SCClearErrUnlocked
Definition: suricata-common.h:551
util-unittest.h
FAIL_IF_NOT
#define FAIL_IF_NOT(expr)
Fail a test if expression evaluates to false.
Definition: util-unittest.h:82
SC_ENOMEM
@ SC_ENOMEM
Definition: util-error.h:29
ThreadId
uint32_t ThreadId
Definition: output-eve.h:46
HashTableFree
void HashTableFree(HashTable *ht)
Free a HashTable and all its contents.
Definition: util-hash.c:111
t_thread_name
thread_local char t_thread_name[THREAD_NAME_LEN+1]
Definition: threads.c:32
HashTable_::array_size
uint32_t array_size
Definition: util-hash.h:37
strlcpy
size_t strlcpy(char *dst, const char *src, size_t siz)
Definition: util-strlcpyu.c:43
LogFileCtx_::sock_type
int sock_type
Definition: util-logopenfile.h:133
LOGFILE_ROTATE_ON_NEW_HOUR
@ LOGFILE_ROTATE_ON_NEW_HOUR
Definition: util-logopenfile.h:80
OutputRegisterFileRotationFlag
void OutputRegisterFileRotationFlag(int *flag)
Register a flag for file rotation notification.
Definition: output.c:698
LogFileCtx_::filetype
LogFileTypeCtx filetype
Definition: util-logopenfile.h:105
log-maintenance.h
LogFileCtx_::rotate_unit
LogFileRotateUnit rotate_unit
Definition: util-logopenfile.h:144
SCTimeToStringPattern
int SCTimeToStringPattern(time_t epoch, const char *pattern, char *str, size_t size)
Convert epoch time to string pattern.
Definition: util-time.c:559
TAILQ_HEAD_INITIALIZER
#define TAILQ_HEAD_INITIALIZER(head)
Definition: queue.h:236
TAILQ_REMOVE
#define TAILQ_REMOVE(head, elm, field)
Definition: queue.h:312
SCRunmodeGet
SCRunMode SCRunmodeGet(void)
Get the current run mode.
Definition: suricata.c:302
LogFileWrite
int LogFileWrite(LogFileCtx *file_ctx, MemBuffer *buffer)
Definition: util-logopenfile.c:1178
SCEveFileType_::ThreadDeinit
SCEveFileTypeThreadDeinitFunc ThreadDeinit
Called to deinitialize each thread.
Definition: output-eve.h:207
PASS
#define PASS
Pass the test.
Definition: util-unittest.h:105
LogFileCtx_::rotate_interval
uint64_t rotate_interval
Definition: util-logopenfile.h:142
DEFAULT_LOG_FILETYPE
#define DEFAULT_LOG_FILETYPE
Definition: output.h:31
LogFileRegister
void LogFileRegister(LogFileCtx *ctx)
Register a LogFileCtx for maintenance operations.
Definition: util-logopenfile.c:1099
LogFileCtx_::is_sock
int is_sock
Definition: util-logopenfile.h:132
SCFwriteUnlocked
#define SCFwriteUnlocked
Definition: suricata-common.h:549
LogFileCtx_::prefix_len
uint32_t prefix_len
Definition: util-logopenfile.h:151
SCEveFileType_::ThreadInit
SCEveFileTypeThreadInitFunc ThreadInit
Initialize thread specific data.
Definition: output-eve.h:176
SCFflushUnlocked
#define SCFflushUnlocked
Definition: suricata-common.h:550
SCMutexUnlock
#define SCMutexUnlock(mut)
Definition: threads-debug.h:120
LogFileRotateUnit
enum LogFileRotateUnit_ LogFileRotateUnit
HashTableLookup
void * HashTableLookup(HashTable *ht, void *data, uint16_t datalen)
Definition: util-hash.c:193
HashTableRemove
int HashTableRemove(HashTable *ht, void *data, uint16_t datalen)
Remove an item from the hash table.
Definition: util-hash.c:177
ThreadLogFileHashEntry::internal_thread_id
ThreadId internal_thread_id
Definition: util-logopenfile.h:52
SCLocalTimeCacheReset
void SCLocalTimeCacheReset(void)
Forget the local times cached by the calling thread, e.g. after the timezone was changed.
Definition: util-time.c:435
StringParseUint32
int StringParseUint32(uint32_t *res, int base, size_t len, const char *str)
Definition: util-byte.c:268
util-time.h
LOGFILE_EVE_BUFFER_SIZE
#define LOGFILE_EVE_BUFFER_SIZE
Definition: util-logopenfile.h:194
SCLogWarning
#define SCLogWarning(...)
Macro used to log WARNING messages.
Definition: util-debug.h:262
HashTableAdd
int HashTableAdd(HashTable *ht, void *data, uint16_t datalen)
Definition: util-hash.c:131
LogFileCtx_::type
enum LogFileType type
Definition: util-logopenfile.h:117
LOGFILE_TYPE_FILE
@ LOGFILE_TYPE_FILE
Definition: util-logopenfile.h:39
BUG_ON
#define BUG_ON(x)
Definition: suricata-common.h:322
LogFileCtx_::threads
LogThreadedFileCtx * threads
Definition: util-logopenfile.h:92
LogFileFlushAll
void LogFileFlushAll(void)
Flush all registered LogFileCtx instances.
Definition: util-logopenfile.c:1148
SC_ATOMIC_SUB
#define SC_ATOMIC_SUB(name, val)
sub a value from our atomic variable
Definition: util-atomic.h:341
ThreadLogFileHashEntry::thread_id
uint64_t thread_id
Definition: util-logopenfile.h:51
LogFileUnregister
void LogFileUnregister(LogFileCtx *ctx)
Unregister a LogFileCtx from maintenance operations.
Definition: util-logopenfile.c:1125
SCLocalTime
struct tm * SCLocalTime(time_t timep, struct tm *result)
Definition: util-time.c:267
SCConfigGetLogDirectory
const char * SCConfigGetLogDirectory(void)
Definition: util-conf.c:61
conf.h
SC_OK
@ SC_OK
Definition: util-error.h:27
SCBasename
const char * SCBasename(const char *path)
Definition: util-path.c:247
LOGFILE_TYPE_UNIX_DGRAM
@ LOGFILE_TYPE_UNIX_DGRAM
Definition: util-logopenfile.h:40
LogFileTypeCtx_::thread_data
void * thread_data
Definition: util-logopenfile.h:68
SCLogOpenThreadedFile
bool SCLogOpenThreadedFile(const char *log_path, const char *append, LogFileCtx *parent_ctx)
Definition: util-logopenfile.c:462
LogFileCtx_::compress_ipv6
bool compress_ipv6
Definition: util-logopenfile.h:169
LOGFILE_TYPE_UNIX_STREAM
@ LOGFILE_TYPE_UNIX_STREAM
Definition: util-logopenfile.h:41
MemBuffer_
Definition: util-buffer.h:27
setenv
void setenv(const char *name, const char *value, int overwrite)
LogFileCtx_::is_regular
uint8_t is_regular
Definition: util-logopenfile.h:166
SCLogInfo
#define SCLogInfo(...)
Macro used to log INFORMATIONAL messages.
Definition: util-debug.h:232
TAILQ_FOREACH_SAFE
#define TAILQ_FOREACH_SAFE(var, head, field, tvar)
Definition: queue.h:329
SCMutexInit
#define SCMutexInit(mut, mutattrs)
Definition: threads-debug.h:116
LogFileCtx_::buffer_size
uint32_t buffer_size
Definition: util-logopenfile.h:126
LogFileCtx_::bytes_since_last_flush
uint64_t bytes_since_last_flush
Definition: util-logopenfile.h:187
SCGetThreadIdLong
#define SCGetThreadIdLong(...)
Definition: threads.h:256
ARRAY_SIZE
#define ARRAY_SIZE(arr)
Definition: suricata-common.h:563
SCConfNodeLookupChild
SCConfNode * SCConfNodeLookupChild(const SCConfNode *node, const char *name)
Lookup a child configuration node by name.
Definition: conf.c:849
LogFileCtx_::parent
struct LogFileCtx_ * parent
Definition: util-logopenfile.h:113
LogFileEntry_
Definition: util-logopenfile.h:71
util-conf.h
FAIL_IF
#define FAIL_IF(expr)
Fail a test if expression evaluates to true.
Definition: util-unittest.h:71
LogThreadedFileCtx_
Definition: util-logopenfile.h:59
SCFopenLogFile
FILE * SCFopenLogFile(const char *path, const char *mode)
Open a text log file for append or truncate.
Definition: util-path.c:291
LogFileEnsureExists
LogFileCtx * LogFileEnsureExists(ThreadId thread_id, LogFileCtx *parent_ctx)
LogFileEnsureExists() Ensure a log file context for the thread exists.
Definition: util-logopenfile.c:847
suricata-common.h
LogFileTypeCtx_::init_data
void * init_data
Definition: util-logopenfile.h:67
util-path.h
LogFileCtx_::output_errors
uint64_t output_errors
Definition: util-logopenfile.h:184
ThreadLogFileHashEntry
Definition: util-logopenfile.h:48
LogFileCtx_::Flush
void(* Flush)(struct LogFileCtx_ *fp)
Definition: util-logopenfile.h:102
PathIsAbsolute
int PathIsAbsolute(const char *path)
Check if a path is absolute.
Definition: util-path.c:45
LogFileCtx_::entry
ThreadLogFileHashEntry * entry
Definition: util-logopenfile.h:114
LogFileFreeCtx
int LogFileFreeCtx(LogFileCtx *lf_ctx)
LogFileFreeCtx() Destroy a LogFileCtx (Close the file and free memory)
Definition: util-logopenfile.c:1012
SCStrdup
#define SCStrdup(s)
Definition: util-mem.h:56
LogFileCtx_::rotate_time
time_t rotate_time
Definition: util-logopenfile.h:139
FatalError
#define FatalError(...)
Definition: util-debug.h:517
LOGFILE_ROTATE_ON_NEW_MINUTE
@ LOGFILE_ROTATE_ON_NEW_MINUTE
Definition: util-logopenfile.h:79
tv
ThreadVars * tv
Definition: fuzz_decodepcapfile.c:34
ParseSizeStringU32
int ParseSizeStringU32(const char *size, uint32_t *res)
Definition: util-misc.c:173
SCMalloc
#define SCMalloc(sz)
Definition: util-mem.h:47
SCLogConfig
struct SCLogConfig_ SCLogConfig
Holds the config state used by the logging api.
SCLogError
#define SCLogError(...)
Macro used to log ERROR messages.
Definition: util-debug.h:274
LOGFILE_ROTATE_NONE
@ LOGFILE_ROTATE_NONE
Definition: util-logopenfile.h:77
SCConfLogOpenGeneric
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
Definition: util-logopenfile.c:564
SCFree
#define SCFree(p)
Definition: util-mem.h:61
util-logopenfile.h
LogThreadedFileCtx_::mutex
SCMutex mutex
Definition: util-logopenfile.h:60
sc_errno
thread_local SCError sc_errno
Definition: util-error.c:31
LogFileCtx_::prefix
char * prefix
Definition: util-logopenfile.h:150
TAILQ_HEAD
#define TAILQ_HEAD(name, type)
Definition: queue.h:230
HashTableInit
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:34
sc_log_global_log_level
SCLogLevel sc_log_global_log_level
Holds the global log level. Is the same as sc_log_config->log_level.
Definition: util-debug.c:104
suricata.h
MemBufferWriteString
void MemBufferWriteString(MemBuffer *dst, const char *fmt,...)
Definition: util-buffer.c:130
SCConfNode_::name
char * name
Definition: conf.h:38
LogFileCtx_::Close
void(* Close)(struct LogFileCtx_ *fp)
Definition: util-logopenfile.h:101
MEMBUFFER_BUFFER
#define MEMBUFFER_BUFFER(mem_buffer)
Get the MemBuffers underlying buffer.
Definition: util-buffer.h:51
LogFileTypeCtx_::filetype
SCEveFileType * filetype
Definition: util-logopenfile.h:66
util-misc.h
LogThreadedFileCtx_::ht
HashTable * ht
Definition: util-logopenfile.h:61
OutputUnregisterFileRotationFlag
void OutputUnregisterFileRotationFlag(int *flag)
Unregister a file rotation flag.
Definition: output.c:721
MEMBUFFER_OFFSET
#define MEMBUFFER_OFFSET(mem_buffer)
Get the MemBuffers current offset.
Definition: util-buffer.h:56
SCCalloc
#define SCCalloc(nm, sz)
Definition: util-mem.h:53
SCReturnInt
#define SCReturnInt(x)
Definition: util-debug.h:288
SCMutexDestroy
#define SCMutexDestroy
Definition: threads-debug.h:121
SCConfNode_
Definition: conf.h:37
LogFileCtx_::Rotate
void(* Rotate)(struct LogFileCtx_ *fp)
Definition: util-logopenfile.h:103
LogFileCtx_::fp
FILE * fp
Definition: util-logopenfile.h:87
SCMutex
#define SCMutex
Definition: threads-debug.h:114
DEBUG_VALIDATE_BUG_ON
#define DEBUG_VALIDATE_BUG_ON(exp)
Definition: util-validate.h:109
LOGFILE_TYPE_FILETYPE
@ LOGFILE_TYPE_FILETYPE
Definition: util-logopenfile.h:44
output.h
LogFileCtx_::threaded
bool threaded
Definition: util-logopenfile.h:112
SCCreateDirectoryTree
int SCCreateDirectoryTree(const char *path, const bool final)
Recursively create a directory.
Definition: util-path.c:138
LogFileCtx_::filemode
uint32_t filemode
Definition: util-logopenfile.h:123