Coverage Report

Created: 2025-08-28 06:29

/src/frr/lib/zlog_targets.c
Line
Count
Source (jump to first uncovered line)
1
// SPDX-License-Identifier: ISC
2
/*
3
 * Copyright (c) 2015-19  David Lamparter, for NetDEF, Inc.
4
 */
5
6
#include "zebra.h"
7
8
#include <sys/un.h>
9
#include <syslog.h>
10
11
#include "memory.h"
12
#include "frrcu.h"
13
#include "frr_pthread.h"
14
#include "printfrr.h"
15
#include "zlog.h"
16
#include "zlog_targets.h"
17
18
/* these allocations are intentionally left active even when doing full exit
19
 * cleanup, in order to keep the logging subsystem fully functional until the
20
 * absolute end.
21
 */
22
23
DEFINE_MGROUP_ACTIVEATEXIT(LOG, "logging subsystem");
24
25
DEFINE_MTYPE_STATIC(LOG, LOG_FD,        "log file target");
26
DEFINE_MTYPE_STATIC(LOG, LOG_FD_NAME,   "log file name");
27
DEFINE_MTYPE_STATIC(LOG, LOG_FD_ROTATE, "log file rotate helper");
28
DEFINE_MTYPE_STATIC(LOG, LOG_SYSL,      "syslog target");
29
30
struct zlt_fd {
31
  struct zlog_target zt;
32
33
  atomic_uint_fast32_t fd;
34
35
  char ts_subsec;
36
  bool record_priority;
37
38
  struct rcu_head_close head_close;
39
};
40
41
static const char * const prionames[] = {
42
  [LOG_EMERG] = "emergencies: ",
43
  [LOG_ALERT] = "alerts: ",
44
  [LOG_CRIT] =  "critical: ",
45
  [LOG_ERR] = "errors: ",
46
  [LOG_WARNING] = "warnings: ",
47
  [LOG_NOTICE] =  "notifications: ",
48
  [LOG_INFO] =  "informational: ",
49
  [LOG_DEBUG] = "debugging: ",
50
};
51
52
void zlog_fd(struct zlog_target *zt, struct zlog_msg *msgs[], size_t nmsgs)
53
0
{
54
0
  struct zlt_fd *zte = container_of(zt, struct zlt_fd, zt);
55
0
  int fd;
56
0
  size_t i, textlen, iovpos = 0;
57
0
  size_t niov = MIN(4 * nmsgs + 1, IOV_MAX);
58
0
  struct iovec iov[niov];
59
  /* "\nYYYY-MM-DD HH:MM:SS.NNNNNNNNN+ZZ:ZZ " = 37 chars */
60
0
#define TS_LEN 40
61
0
  char ts_buf[TS_LEN * nmsgs], *ts_pos = ts_buf;
62
63
0
  fd = atomic_load_explicit(&zte->fd, memory_order_relaxed);
64
65
0
  for (i = 0; i < nmsgs; i++) {
66
0
    struct zlog_msg *msg = msgs[i];
67
0
    int prio = zlog_msg_prio(msg);
68
69
0
    if (prio <= zt->prio_min) {
70
0
      struct fbuf fbuf = {
71
0
        .buf = ts_buf,
72
0
        .pos = ts_pos,
73
0
        .len = sizeof(ts_buf),
74
0
      };
75
76
0
      iov[iovpos].iov_base = ts_pos;
77
0
      zlog_msg_ts(msg, &fbuf,
78
0
            ZLOG_TS_LEGACY | zte->ts_subsec);
79
0
      ts_pos = fbuf.pos;
80
81
0
      *ts_pos++ = ' ';
82
0
      iov[iovpos].iov_len =
83
0
        ts_pos - (char *)iov[iovpos].iov_base;
84
85
0
      iovpos++;
86
87
0
      if (zte->record_priority) {
88
0
        iov[iovpos].iov_base = (char *)prionames[prio];
89
0
        iov[iovpos].iov_len =
90
0
          strlen(iov[iovpos].iov_base);
91
92
0
        iovpos++;
93
0
      }
94
95
0
      iov[iovpos].iov_base = zlog_prefix;
96
0
      iov[iovpos].iov_len = zlog_prefixsz;
97
98
0
      iovpos++;
99
100
0
      iov[iovpos].iov_base =
101
0
        (char *)zlog_msg_text(msg, &textlen);
102
0
      iov[iovpos].iov_len = textlen + 1;
103
104
0
      iovpos++;
105
0
    }
106
107
    /* conditions that trigger writing:
108
     *  - out of space for more timestamps/headers
109
     *  - this being the last message in the batch
110
     *  - not enough remaining iov entries
111
     */
112
0
    if (iovpos > 0 && (ts_buf + sizeof(ts_buf) - ts_pos < TS_LEN
113
0
           || i + 1 == nmsgs
114
0
           || array_size(iov) - iovpos < 5)) {
115
0
      writev(fd, iov, iovpos);
116
117
0
      iovpos = 0;
118
0
      ts_pos = ts_buf;
119
0
    }
120
0
  }
121
122
0
  assert(iovpos == 0);
123
0
}
124
125
static void zlog_fd_sigsafe(struct zlog_target *zt, const char *text,
126
          size_t len)
127
0
{
128
0
  struct zlt_fd *zte = container_of(zt, struct zlt_fd, zt);
129
0
  struct iovec iov[4];
130
0
  int fd;
131
132
0
  iov[0].iov_base = (char *)prionames[LOG_CRIT];
133
0
  iov[0].iov_len = zte->record_priority ? strlen(iov[0].iov_base) : 0;
134
135
0
  iov[1].iov_base = zlog_prefix;
136
0
  iov[1].iov_len = zlog_prefixsz;
137
138
0
  iov[2].iov_base = (char *)text;
139
0
  iov[2].iov_len = len;
140
141
0
  iov[3].iov_base = (char *)"\n";
142
0
  iov[3].iov_len = 1;
143
144
0
  fd = atomic_load_explicit(&zte->fd, memory_order_relaxed);
145
146
0
  writev(fd, iov, array_size(iov));
147
0
}
148
149
/*
150
 * (re-)configuration
151
 */
152
153
void zlog_file_init(struct zlog_cfg_file *zcf)
154
2
{
155
2
  memset(zcf, 0, sizeof(*zcf));
156
2
  zcf->prio_min = ZLOG_DISABLED;
157
2
  zcf->fd = -1;
158
2
  pthread_mutex_init(&zcf->cfg_mtx, NULL);
159
2
}
160
161
static void zlog_file_target_free(struct zlt_fd *zlt)
162
2
{
163
2
  if (!zlt)
164
2
    return;
165
166
0
  rcu_close(&zlt->head_close, zlt->fd);
167
0
  rcu_free(MTYPE_LOG_FD, zlt, zt.rcu_head);
168
0
}
169
170
void zlog_file_fini(struct zlog_cfg_file *zcf)
171
0
{
172
0
  if (zcf->active) {
173
0
    struct zlt_fd *ztf;
174
0
    struct zlog_target *zt;
175
176
0
    zt = zlog_target_replace(&zcf->active->zt, NULL);
177
0
    ztf = container_of(zt, struct zlt_fd, zt);
178
0
    zlog_file_target_free(ztf);
179
0
  }
180
0
  XFREE(MTYPE_LOG_FD_NAME, zcf->filename);
181
0
  pthread_mutex_destroy(&zcf->cfg_mtx);
182
0
}
183
184
static bool zlog_file_cycle(struct zlog_cfg_file *zcf)
185
2
{
186
2
  struct zlog_target *zt, *old;
187
2
  struct zlt_fd *zlt = NULL;
188
2
  int fd;
189
2
  bool rv = true;
190
191
2
  do {
192
2
    if (zcf->prio_min == ZLOG_DISABLED)
193
2
      break;
194
195
0
    if (zcf->fd != -1)
196
0
      fd = dup(zcf->fd);
197
0
    else if (zcf->filename)
198
0
      fd = open(zcf->filename,
199
0
          O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC
200
0
          | O_NOCTTY,
201
0
          LOGFILE_MASK);
202
0
    else
203
0
      fd = -1;
204
205
0
    if (fd < 0) {
206
0
      rv = false;
207
0
      break;
208
0
    }
209
210
0
    zt = zlog_target_clone(MTYPE_LOG_FD, &zcf->active->zt,
211
0
               sizeof(*zlt));
212
0
    zlt = container_of(zt, struct zlt_fd, zt);
213
214
0
    zlt->fd = fd;
215
0
    zlt->record_priority = zcf->record_priority;
216
0
    zlt->ts_subsec = zcf->ts_subsec;
217
218
0
    zlt->zt.prio_min = zcf->prio_min;
219
0
    zlt->zt.logfn = zcf->zlog_wrap ? zcf->zlog_wrap : zlog_fd;
220
0
    zlt->zt.logfn_sigsafe = zlog_fd_sigsafe;
221
0
  } while (0);
222
223
2
  old = zlog_target_replace(zcf->active ? &zcf->active->zt : NULL,
224
2
          zlt ? &zlt->zt : NULL);
225
2
  zcf->active = zlt;
226
227
2
  zlog_file_target_free(container_of_null(old, struct zlt_fd, zt));
228
229
2
  return rv;
230
2
}
231
232
void zlog_file_set_other(struct zlog_cfg_file *zcf)
233
0
{
234
0
  frr_with_mutex (&zcf->cfg_mtx) {
235
0
    zlog_file_cycle(zcf);
236
0
  }
237
0
}
238
239
bool zlog_file_set_filename(struct zlog_cfg_file *zcf, const char *filename)
240
0
{
241
0
  frr_with_mutex (&zcf->cfg_mtx) {
242
0
    XFREE(MTYPE_LOG_FD_NAME, zcf->filename);
243
0
    zcf->filename = XSTRDUP(MTYPE_LOG_FD_NAME, filename);
244
0
    zcf->fd = -1;
245
246
0
    return zlog_file_cycle(zcf);
247
0
  }
248
0
  assert(0);
249
0
  return false;
250
0
}
251
252
bool zlog_file_set_fd(struct zlog_cfg_file *zcf, int fd)
253
2
{
254
2
  frr_with_mutex (&zcf->cfg_mtx) {
255
2
    if (zcf->fd == fd)
256
0
      return true;
257
258
2
    XFREE(MTYPE_LOG_FD_NAME, zcf->filename);
259
2
    zcf->fd = fd;
260
261
2
    return zlog_file_cycle(zcf);
262
2
  }
263
0
  assert(0);
264
0
  return false;
265
0
}
266
267
struct rcu_close_rotate {
268
  struct rcu_head_close head_close;
269
  struct rcu_head head_self;
270
};
271
272
bool zlog_file_rotate(struct zlog_cfg_file *zcf)
273
0
{
274
0
  struct rcu_close_rotate *rcr;
275
0
  int fd;
276
277
0
  frr_with_mutex (&zcf->cfg_mtx) {
278
0
    if (!zcf->active || !zcf->filename)
279
0
      return true;
280
281
0
    fd = open(zcf->filename,
282
0
        O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC | O_NOCTTY,
283
0
        LOGFILE_MASK);
284
0
    if (fd < 0)
285
0
      return false;
286
287
0
    fd = atomic_exchange_explicit(&zcf->active->fd,
288
0
                (uint_fast32_t)fd,
289
0
                memory_order_relaxed);
290
0
  }
291
292
0
  rcr = XCALLOC(MTYPE_LOG_FD_ROTATE, sizeof(*rcr));
293
0
  rcu_close(&rcr->head_close, fd);
294
0
  rcu_free(MTYPE_LOG_FD_ROTATE, rcr, head_self);
295
296
0
  return true;
297
0
}
298
299
/* fixed crash logging */
300
301
static struct zlt_fd zlog_crashlog;
302
303
static void zlog_crashlog_sigsafe(struct zlog_target *zt, const char *text,
304
          size_t len)
305
0
{
306
0
  static int crashlog_fd = -1;
307
308
0
  if (crashlog_fd == -1) {
309
0
#ifdef HAVE_OPENAT
310
0
    crashlog_fd = openat(zlog_tmpdirfd, "crashlog",
311
0
             O_WRONLY | O_APPEND | O_CREAT,
312
0
             LOGFILE_MASK);
313
0
#endif
314
0
    if (crashlog_fd < 0)
315
0
      crashlog_fd = -2;
316
0
  }
317
318
0
  if (crashlog_fd == -2)
319
0
    return;
320
321
0
  zlog_crashlog.fd = crashlog_fd;
322
0
  zlog_fd_sigsafe(&zlog_crashlog.zt, text, len);
323
0
}
324
325
/* this is used for assert failures (they don't need AS-Safe logging) */
326
static void zlog_crashlog_plain(struct zlog_target *zt, struct zlog_msg *msgs[],
327
        size_t nmsgs)
328
0
{
329
0
  size_t i, len;
330
0
  const char *text;
331
332
0
  for (i = 0; i < nmsgs; i++) {
333
0
    if (zlog_msg_prio(msgs[i]) > zt->prio_min)
334
0
      continue;
335
336
0
    text = zlog_msg_text(msgs[i], &len);
337
0
    zlog_crashlog_sigsafe(zt, text, len);
338
0
  }
339
0
}
340
341
static void zlog_crashlog_init(void)
342
0
{
343
0
  zlog_crashlog.zt.prio_min = LOG_CRIT;
344
0
  zlog_crashlog.zt.logfn = zlog_crashlog_plain;
345
0
  zlog_crashlog.zt.logfn_sigsafe = zlog_crashlog_sigsafe;
346
0
  zlog_crashlog.fd = -1;
347
348
0
  zlog_target_replace(NULL, &zlog_crashlog.zt);
349
0
}
350
351
/* fixed logging for test/auxiliary programs */
352
353
static struct zlt_fd zlog_aux_stdout;
354
static bool zlog_is_aux;
355
356
static int zlt_aux_init(const char *prefix, int prio_min)
357
0
{
358
0
  zlog_is_aux = true;
359
360
0
  zlog_aux_stdout.zt.prio_min = prio_min;
361
0
  zlog_aux_stdout.zt.logfn = zlog_fd;
362
0
  zlog_aux_stdout.zt.logfn_sigsafe = zlog_fd_sigsafe;
363
0
  zlog_aux_stdout.fd = STDOUT_FILENO;
364
365
0
  zlog_target_replace(NULL, &zlog_aux_stdout.zt);
366
0
  zlog_startup_end();
367
0
  return 0;
368
0
}
369
370
static int zlt_init(const char *progname, const char *protoname,
371
         unsigned short instance, uid_t uid, gid_t gid)
372
2
{
373
2
  openlog(progname, LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON);
374
2
  return 0;
375
2
}
376
377
static int zlt_fini(void)
378
0
{
379
0
  closelog();
380
0
  return 0;
381
0
}
382
383
/* fixed startup logging to stderr */
384
385
static struct zlt_fd zlog_startup_stderr;
386
387
__attribute__((_CONSTRUCTOR(450))) static void zlog_startup_init(void)
388
6
{
389
6
  zlog_startup_stderr.zt.prio_min = LOG_WARNING;
390
6
  zlog_startup_stderr.zt.logfn = zlog_fd;
391
6
  zlog_startup_stderr.zt.logfn_sigsafe = zlog_fd_sigsafe;
392
6
  zlog_startup_stderr.fd = STDERR_FILENO;
393
394
6
  zlog_target_replace(NULL, &zlog_startup_stderr.zt);
395
396
6
  hook_register(zlog_aux_init, zlt_aux_init);
397
6
  hook_register(zlog_init, zlt_init);
398
6
  hook_register(zlog_fini, zlt_fini);
399
6
}
400
401
void zlog_startup_end(void)
402
0
{
403
0
  static bool startup_ended = false;
404
405
0
  if (startup_ended)
406
0
    return;
407
0
  startup_ended = true;
408
409
0
  zlog_target_replace(&zlog_startup_stderr.zt, NULL);
410
411
0
  if (zlog_is_aux)
412
0
    return;
413
414
  /* until here, crashlogs go to stderr */
415
0
  zlog_crashlog_init();
416
0
}
417
418
/* syslog */
419
420
struct zlt_syslog {
421
  struct zlog_target zt;
422
423
  int syslog_facility;
424
};
425
426
static void zlog_syslog(struct zlog_target *zt, struct zlog_msg *msgs[],
427
      size_t nmsgs)
428
0
{
429
0
  size_t i;
430
0
  struct zlt_syslog *zte = container_of(zt, struct zlt_syslog, zt);
431
0
  const char *text;
432
0
  size_t text_len;
433
434
0
  for (i = 0; i < nmsgs; i++) {
435
0
    if (zlog_msg_prio(msgs[i]) > zt->prio_min)
436
0
      continue;
437
438
0
    text = zlog_msg_text(msgs[i], &text_len);
439
0
    syslog(zlog_msg_prio(msgs[i]) | zte->syslog_facility, "%.*s",
440
0
           (int)text_len, text);
441
0
  }
442
0
}
443
444
#ifndef _PATH_LOG
445
#define _PATH_LOG "/dev/log"
446
#endif
447
448
static void zlog_syslog_sigsafe(struct zlog_target *zt, const char *text,
449
        size_t len)
450
0
{
451
0
  static int syslog_fd = -1;
452
453
0
  char hdr[192];
454
0
  size_t hdrlen;
455
0
  struct iovec iov[2];
456
457
0
  if (syslog_fd == -1) {
458
0
    syslog_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
459
0
    if (syslog_fd >= 0) {
460
0
      struct sockaddr_un sa;
461
0
      socklen_t salen = sizeof(sa);
462
463
0
      sa.sun_family = AF_UNIX;
464
0
      strlcpy(sa.sun_path, _PATH_LOG, sizeof(sa.sun_path));
465
#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN
466
      salen = sa.sun_len = SUN_LEN(&sa);
467
#endif
468
0
      if (connect(syslog_fd, (struct sockaddr *)&sa, salen)) {
469
0
        close(syslog_fd);
470
0
        syslog_fd = -1;
471
0
      }
472
0
    }
473
474
    /* /dev/log could be a fifo instead of a socket */
475
0
    if (syslog_fd == -1) {
476
0
      syslog_fd = open(_PATH_LOG, O_WRONLY | O_NOCTTY);
477
0
      if (syslog_fd < 0)
478
        /* give up ... */
479
0
        syslog_fd = -2;
480
0
    }
481
0
  }
482
483
0
  if (syslog_fd == -2)
484
0
    return;
485
486
  /* note zlog_prefix includes trailing ": ", need to cut off 2 chars */
487
0
  hdrlen = snprintfrr(hdr, sizeof(hdr), "<%d>%.*s[%ld]: ", LOG_CRIT,
488
0
          zlog_prefixsz > 2 ? (int)(zlog_prefixsz - 2) : 0,
489
0
          zlog_prefix, (long)getpid());
490
491
0
  iov[0].iov_base = hdr;
492
0
  iov[0].iov_len = hdrlen;
493
494
0
  iov[1].iov_base = (char *)text;
495
0
  iov[1].iov_len = len;
496
497
0
  writev(syslog_fd, iov, array_size(iov));
498
0
}
499
500
501
static pthread_mutex_t syslog_cfg_mutex = PTHREAD_MUTEX_INITIALIZER;
502
static struct zlt_syslog *zlt_syslog;
503
static int syslog_facility = LOG_DAEMON;
504
static int syslog_prio_min = ZLOG_DISABLED;
505
506
void zlog_syslog_set_facility(int facility)
507
0
{
508
0
  struct zlog_target *newztc;
509
0
  struct zlt_syslog *newzt;
510
511
0
  frr_with_mutex (&syslog_cfg_mutex) {
512
0
    if (facility == syslog_facility)
513
0
      return;
514
0
    syslog_facility = facility;
515
516
0
    if (syslog_prio_min == ZLOG_DISABLED)
517
0
      return;
518
519
0
    newztc = zlog_target_clone(MTYPE_LOG_SYSL, &zlt_syslog->zt,
520
0
             sizeof(*newzt));
521
0
    newzt = container_of(newztc, struct zlt_syslog, zt);
522
0
    newzt->syslog_facility = syslog_facility;
523
524
0
    zlog_target_free(MTYPE_LOG_SYSL,
525
0
         zlog_target_replace(&zlt_syslog->zt,
526
0
                 &newzt->zt));
527
528
0
    zlt_syslog = newzt;
529
0
  }
530
0
}
531
532
int zlog_syslog_get_facility(void)
533
0
{
534
0
  frr_with_mutex (&syslog_cfg_mutex) {
535
0
    return syslog_facility;
536
0
  }
537
0
  assert(0);
538
0
  return 0;
539
0
}
540
541
void zlog_syslog_set_prio_min(int prio_min)
542
0
{
543
0
  struct zlog_target *newztc;
544
0
  struct zlt_syslog *newzt = NULL;
545
546
0
  frr_with_mutex (&syslog_cfg_mutex) {
547
0
    if (prio_min == syslog_prio_min)
548
0
      return;
549
0
    syslog_prio_min = prio_min;
550
551
0
    if (syslog_prio_min != ZLOG_DISABLED) {
552
0
      newztc = zlog_target_clone(MTYPE_LOG_SYSL,
553
0
               &zlt_syslog->zt,
554
0
               sizeof(*newzt));
555
0
      newzt = container_of(newztc, struct zlt_syslog, zt);
556
0
      newzt->zt.prio_min = prio_min;
557
0
      newzt->zt.logfn = zlog_syslog;
558
0
      newzt->zt.logfn_sigsafe = zlog_syslog_sigsafe;
559
0
      newzt->syslog_facility = syslog_facility;
560
0
    }
561
562
0
    zlog_target_free(MTYPE_LOG_SYSL,
563
0
         zlog_target_replace(&zlt_syslog->zt,
564
0
                 &newzt->zt));
565
566
0
    zlt_syslog = newzt;
567
0
  }
568
0
}
569
570
int zlog_syslog_get_prio_min(void)
571
0
{
572
0
  frr_with_mutex (&syslog_cfg_mutex) {
573
0
    return syslog_prio_min;
574
0
  }
575
0
  assert(0);
576
0
  return 0;
577
0
}