]> git.proxmox.com Git - mirror_frr.git/blame - lib/zlog.c
Merge pull request #6261 from ton31337/fix/allow_nhlen_32
[mirror_frr.git] / lib / zlog.c
CommitLineData
0bdeb5e5
DL
1/*
2 * Copyright (c) 2015-19 David Lamparter, for NetDEF, Inc.
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17#include "zebra.h"
18
19#include <unistd.h>
20#include <sys/time.h>
21#include <sys/mman.h>
22#include <sys/types.h>
23#include <time.h>
24#include <stdlib.h>
25#include <stdio.h>
26#include <string.h>
27#include <stdarg.h>
28#include <pthread.h>
29
30/* gettid() & co. */
31#ifdef HAVE_PTHREAD_NP_H
32#include <pthread_np.h>
33#endif
34#ifdef linux
35#include <sys/syscall.h>
36#endif
37#ifdef __FreeBSD__
38#include <sys/thr.h>
39#endif
40#ifdef __NetBSD__
41#include <lwp.h>
42#endif
43#ifdef __DragonFly__
44#include <sys/lwp.h>
45#endif
46#ifdef __APPLE__
47#include <mach/mach_traps.h>
48#endif
49
50#include "memory.h"
51#include "atomlist.h"
52#include "printfrr.h"
53#include "frrcu.h"
54#include "zlog.h"
55
56DEFINE_MTYPE_STATIC(LIB, LOG_MESSAGE, "log message")
57DEFINE_MTYPE_STATIC(LIB, LOG_TLSBUF, "log thread-local buffer")
58
59DEFINE_HOOK(zlog_init, (const char *progname, const char *protoname,
60 unsigned short instance, uid_t uid, gid_t gid),
61 (progname, protoname, instance, uid, gid))
62DEFINE_KOOH(zlog_fini, (), ())
63DEFINE_HOOK(zlog_aux_init, (const char *prefix, int prio_min),
64 (prefix, prio_min))
65
66char zlog_prefix[128];
67size_t zlog_prefixsz;
68int zlog_tmpdirfd = -1;
69
70/* these are kept around because logging is initialized (and directories
71 * & files created) before zprivs code switches to the FRR user; therefore
72 * we need to chown() things so we don't get permission errors later when
73 * trying to delete things on shutdown
74 */
75static uid_t zlog_uid = -1;
76static gid_t zlog_gid = -1;
77
78DECLARE_ATOMLIST(zlog_targets, struct zlog_target, head);
79static struct zlog_targets_head zlog_targets;
80
81/* cf. zlog.h for additional comments on this struct.
82 *
83 * Note: you MUST NOT pass the format string + va_list to non-FRR format
84 * string functions (e.g. vsyslog, sd_journal_printv, ...) since FRR uses an
85 * extended prinf() with additional formats (%pI4 and the like).
86 *
87 * Also remember to use va_copy() on args.
88 */
89
90struct zlog_msg {
91 struct timespec ts;
92 int prio;
93
94 const char *fmt;
95 va_list args;
96
97 char *stackbuf;
98 size_t stackbufsz;
99 char *text;
100 size_t textlen;
101
102 /* This is always ISO8601 with sub-second precision 9 here, it's
103 * converted for callers as needed. ts_dot points to the "."
104 * separating sub-seconds. ts_zonetail is "Z" or "+00:00" for the
105 * local time offset.
106 *
107 * Valid if ZLOG_TS_ISO8601 is set.
108 * (0 if timestamp has not been formatted yet)
109 */
110 uint32_t ts_flags;
111 char ts_str[32], *ts_dot, ts_zonetail[8];
112};
113
114/* thread-local log message buffering
115 *
116 * This is strictly optional and set up by calling zlog_tls_buffer_init()
117 * on a particular thread.
118 *
119 * If in use, this will create a temporary file in /var/tmp which is used as
120 * memory-mapped MAP_SHARED log message buffer. The idea there is that buffer
121 * access doesn't require any syscalls, but in case of a crash the kernel
122 * knows to sync the memory back to disk. This way the user can still get the
123 * last log messages if there were any left unwritten in the buffer.
124 *
125 * Sizing this dynamically isn't particularly useful, so here's an 8k buffer
126 * with a message limit of 64 messages. Message metadata (e.g. priority,
127 * timestamp) aren't in the mmap region, so they're lost on crash, but we can
128 * live with that.
129 */
130
131#if defined(HAVE_OPENAT) && defined(HAVE_UNLINKAT)
132#define CAN_DO_TLS 1
133#endif
134
135#define TLS_LOG_BUF_SIZE 8192
136#define TLS_LOG_MAXMSG 64
137
138struct zlog_tls {
139 char *mmbuf;
140 size_t bufpos;
141
142 size_t nmsgs;
143 struct zlog_msg msgs[TLS_LOG_MAXMSG];
144 struct zlog_msg *msgp[TLS_LOG_MAXMSG];
145};
146
147static inline void zlog_tls_free(void *arg);
148
149/* proper ELF TLS is a bit faster than pthread_[gs]etspecific, so if it's
150 * available we'll use it here
151 */
152
153#ifdef __OpenBSD__
154static pthread_key_t zlog_tls_key;
155
156static void zlog_tls_key_init(void) __attribute__((_CONSTRUCTOR(500)));
157static void zlog_tls_key_init(void)
158{
159 pthread_key_create(&zlog_tls_key, zlog_tls_free);
160}
161
162static void zlog_tls_key_fini(void) __attribute__((_DESTRUCTOR(500)));
163static void zlog_tls_key_fini(void)
164{
165 pthread_key_delete(zlog_tls_key);
166}
167
168static inline struct zlog_tls *zlog_tls_get(void)
169{
170 return pthread_getspecific(zlog_tls_key);
171}
172
173static inline void zlog_tls_set(struct zlog_tls *val)
174{
175 pthread_setspecific(zlog_tls_key, val);
176}
177#else
178# ifndef thread_local
179# define thread_local __thread
180# endif
181
182static thread_local struct zlog_tls *zlog_tls_var
183 __attribute__((tls_model("initial-exec")));
184
185static inline struct zlog_tls *zlog_tls_get(void)
186{
187 return zlog_tls_var;
188}
189
190static inline void zlog_tls_set(struct zlog_tls *val)
191{
192 zlog_tls_var = val;
193}
194#endif
195
196#ifdef CAN_DO_TLS
197static long zlog_gettid(void)
198{
199 long rv = -1;
200#ifdef HAVE_PTHREAD_GETTHREADID_NP
201 rv = pthread_getthreadid_np();
202#elif defined(linux)
203 rv = syscall(__NR_gettid);
204#elif defined(__NetBSD__)
205 rv = _lwp_self();
206#elif defined(__FreeBSD__)
207 thr_self(&rv);
208#elif defined(__DragonFly__)
209 rv = lwp_gettid();
210#elif defined(__OpenBSD__)
211 rv = getthrid();
212#elif defined(__sun)
213 rv = pthread_self();
214#elif defined(__APPLE__)
215 rv = mach_thread_self();
216 mach_port_deallocate(mach_task_self(), rv);
217#endif
218 return rv;
219}
220
221void zlog_tls_buffer_init(void)
222{
223 struct zlog_tls *zlog_tls;
224 char mmpath[MAXPATHLEN];
225 int mmfd;
226 size_t i;
227
228 zlog_tls = zlog_tls_get();
229
230 if (zlog_tls || zlog_tmpdirfd < 0)
231 return;
232
233 zlog_tls = XCALLOC(MTYPE_LOG_TLSBUF, sizeof(*zlog_tls));
234 for (i = 0; i < array_size(zlog_tls->msgp); i++)
235 zlog_tls->msgp[i] = &zlog_tls->msgs[i];
236
237 snprintfrr(mmpath, sizeof(mmpath), "logbuf.%ld", zlog_gettid());
238
239 mmfd = openat(zlog_tmpdirfd, mmpath,
240 O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
241 fchown(mmfd, zlog_uid, zlog_gid);
242
243 if (mmfd < 0) {
244 zlog_err("failed to open thread log buffer \"%s\": %s",
245 mmpath, strerror(errno));
246 goto out_anon;
247 }
248
249#ifdef HAVE_POSIX_FALLOCATE
250 if (posix_fallocate(mmfd, 0, TLS_LOG_BUF_SIZE) < 0) {
251#else
252 if (ftruncate(mmfd, TLS_LOG_BUF_SIZE) < 0) {
253#endif
254 zlog_err("failed to allocate thread log buffer \"%s\": %s",
255 mmpath, strerror(errno));
256 goto out_anon_unlink;
257 }
258
259 zlog_tls->mmbuf = mmap(NULL, TLS_LOG_BUF_SIZE, PROT_READ | PROT_WRITE,
260 MAP_SHARED, mmfd, 0);
261 if (zlog_tls->mmbuf == MAP_FAILED) {
262 zlog_err("failed to mmap thread log buffer \"%s\": %s",
263 mmpath, strerror(errno));
264 goto out_anon_unlink;
265 }
266
267 close(mmfd);
268 zlog_tls_set(zlog_tls);
269 return;
270
271out_anon_unlink:
272 unlink(mmpath);
273 close(mmfd);
274out_anon:
275
276#ifndef MAP_ANONYMOUS
277#define MAP_ANONYMOUS MAP_ANON
278#endif
279 zlog_tls->mmbuf = mmap(NULL, TLS_LOG_BUF_SIZE, PROT_READ | PROT_WRITE,
280 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
281
282 if (!zlog_tls->mmbuf) {
283 zlog_err("failed to anonymous-mmap thread log buffer: %s",
284 strerror(errno));
285 XFREE(MTYPE_LOG_TLSBUF, zlog_tls);
286 zlog_tls_set(NULL);
287 return;
288 }
289
290 zlog_tls_set(zlog_tls);
291}
292
293void zlog_tls_buffer_fini(void)
294{
295 char mmpath[MAXPATHLEN];
296
297 zlog_tls_buffer_flush();
298
299 zlog_tls_free(zlog_tls_get());
300 zlog_tls_set(NULL);
301
302 snprintfrr(mmpath, sizeof(mmpath), "logbuf.%ld", zlog_gettid());
303 if (unlinkat(zlog_tmpdirfd, mmpath, 0))
304 zlog_err("unlink logbuf: %s (%d)", strerror(errno), errno);
305}
306
307#else /* !CAN_DO_TLS */
308void zlog_tls_buffer_init(void)
309{
310}
311
312void zlog_tls_buffer_fini(void)
313{
314}
315#endif
316
317static inline void zlog_tls_free(void *arg)
318{
319 struct zlog_tls *zlog_tls = arg;
320
321 if (!zlog_tls)
322 return;
323
324 munmap(zlog_tls->mmbuf, TLS_LOG_BUF_SIZE);
325 XFREE(MTYPE_LOG_TLSBUF, zlog_tls);
326}
327
328void zlog_tls_buffer_flush(void)
329{
330 struct zlog_target *zt;
331 struct zlog_tls *zlog_tls = zlog_tls_get();
332
333 if (!zlog_tls)
334 return;
335 if (!zlog_tls->nmsgs)
336 return;
337
338 rcu_read_lock();
339 frr_each (zlog_targets, &zlog_targets, zt) {
340 if (!zt->logfn)
341 continue;
342
343 zt->logfn(zt, zlog_tls->msgp, zlog_tls->nmsgs);
344 }
345 rcu_read_unlock();
346
347 zlog_tls->bufpos = 0;
348 zlog_tls->nmsgs = 0;
349}
350
351
352static void vzlog_notls(int prio, const char *fmt, va_list ap)
353{
354 struct zlog_target *zt;
355 struct zlog_msg stackmsg = {
356 .prio = prio & LOG_PRIMASK,
357 .fmt = fmt,
358 }, *msg = &stackmsg;
359 char stackbuf[512];
360
361 clock_gettime(CLOCK_REALTIME, &msg->ts);
362 va_copy(msg->args, ap);
363 msg->stackbuf = stackbuf;
364 msg->stackbufsz = sizeof(stackbuf);
365
366 rcu_read_lock();
367 frr_each (zlog_targets, &zlog_targets, zt) {
368 if (prio > zt->prio_min)
369 continue;
370 if (!zt->logfn)
371 continue;
372
373 zt->logfn(zt, &msg, 1);
374 }
375 rcu_read_unlock();
376
377 va_end(msg->args);
378 if (msg->text && msg->text != stackbuf)
379 XFREE(MTYPE_LOG_MESSAGE, msg->text);
380}
381
382static void vzlog_tls(struct zlog_tls *zlog_tls, int prio,
383 const char *fmt, va_list ap)
384{
385 struct zlog_target *zt;
386 struct zlog_msg *msg;
387 char *buf;
388 bool ignoremsg = true;
389 bool immediate = false;
390
391 /* avoid further processing cost if no target wants this message */
392 rcu_read_lock();
393 frr_each (zlog_targets, &zlog_targets, zt) {
394 if (prio > zt->prio_min)
395 continue;
396 ignoremsg = false;
397 break;
398 }
399 rcu_read_unlock();
400
401 if (ignoremsg)
402 return;
403
404 msg = &zlog_tls->msgs[zlog_tls->nmsgs];
405 zlog_tls->nmsgs++;
406 if (zlog_tls->nmsgs == array_size(zlog_tls->msgs))
407 immediate = true;
408
409 memset(msg, 0, sizeof(*msg));
410 clock_gettime(CLOCK_REALTIME, &msg->ts);
411 va_copy(msg->args, ap);
412 msg->stackbuf = buf = zlog_tls->mmbuf + zlog_tls->bufpos;
413 msg->stackbufsz = TLS_LOG_BUF_SIZE - zlog_tls->bufpos - 1;
414 msg->fmt = fmt;
415 msg->prio = prio & LOG_PRIMASK;
416 if (msg->prio < LOG_INFO)
417 immediate = true;
418
419 if (!immediate) {
420 /* messages written later need to take the formatting cost
421 * immediately since we can't hold a reference on varargs
422 */
423 zlog_msg_text(msg, NULL);
424
425 if (msg->text != buf)
426 /* zlog_msg_text called malloc() on us :( */
427 immediate = true;
428 else {
429 zlog_tls->bufpos += msg->textlen + 1;
430 /* write a second \0 to mark current end position
431 * (in case of crash this signals end of unwritten log
432 * messages in mmap'd logbuf file)
433 */
434 zlog_tls->mmbuf[zlog_tls->bufpos] = '\0';
435
436 /* avoid malloc() for next message */
437 if (TLS_LOG_BUF_SIZE - zlog_tls->bufpos < 256)
438 immediate = true;
439 }
440 }
441
442 if (immediate)
443 zlog_tls_buffer_flush();
444
445 va_end(msg->args);
446 if (msg->text && msg->text != buf)
447 XFREE(MTYPE_LOG_MESSAGE, msg->text);
448}
449
450void vzlog(int prio, const char *fmt, va_list ap)
451{
452 struct zlog_tls *zlog_tls = zlog_tls_get();
453
454 if (zlog_tls)
455 vzlog_tls(zlog_tls, prio, fmt, ap);
456 else
457 vzlog_notls(prio, fmt, ap);
458}
459
460void zlog_sigsafe(const char *text, size_t len)
461{
462 struct zlog_target *zt;
463 const char *end = text + len, *nlpos;
464
465 while (text < end) {
466 nlpos = memchr(text, '\n', end - text);
467 if (!nlpos)
468 nlpos = end;
469
470 frr_each (zlog_targets, &zlog_targets, zt) {
471 if (LOG_CRIT > zt->prio_min)
472 continue;
473 if (!zt->logfn_sigsafe)
474 continue;
475
476 zt->logfn_sigsafe(zt, text, nlpos - text);
477 }
478
479 if (nlpos == end)
480 break;
481 text = nlpos + 1;
482 }
483}
484
485
486int zlog_msg_prio(struct zlog_msg *msg)
487{
488 return msg->prio;
489}
490
491const char *zlog_msg_text(struct zlog_msg *msg, size_t *textlen)
492{
493 if (!msg->text) {
494 va_list args;
495
496 va_copy(args, msg->args);
497 msg->text = vasnprintfrr(MTYPE_LOG_MESSAGE, msg->stackbuf,
498 msg->stackbufsz, msg->fmt, args);
499 msg->textlen = strlen(msg->text);
500 va_end(args);
501 }
502 if (textlen)
503 *textlen = msg->textlen;
504 return msg->text;
505}
506
507#define ZLOG_TS_FORMAT (ZLOG_TS_ISO8601 | ZLOG_TS_LEGACY)
508#define ZLOG_TS_FLAGS ~ZLOG_TS_PREC
509
510size_t zlog_msg_ts(struct zlog_msg *msg, char *out, size_t outsz,
511 uint32_t flags)
512{
513 size_t len1;
514
515 if (!(flags & ZLOG_TS_FORMAT))
516 return 0;
517
518 if (!(msg->ts_flags & ZLOG_TS_FORMAT) ||
519 ((msg->ts_flags ^ flags) & ZLOG_TS_UTC)) {
520 struct tm tm;
521
522 if (flags & ZLOG_TS_UTC)
523 gmtime_r(&msg->ts.tv_sec, &tm);
524 else
525 localtime_r(&msg->ts.tv_sec, &tm);
526
527 strftime(msg->ts_str, sizeof(msg->ts_str),
528 "%Y-%m-%dT%H:%M:%S", &tm);
529
530 if (flags & ZLOG_TS_UTC) {
531 msg->ts_zonetail[0] = 'Z';
532 msg->ts_zonetail[1] = '\0';
533 } else
534 snprintfrr(msg->ts_zonetail, sizeof(msg->ts_zonetail),
535 "%+03d:%02d",
536 (int)(tm.tm_gmtoff / 3600),
537 (int)(labs(tm.tm_gmtoff) / 60) % 60);
538
539 msg->ts_dot = msg->ts_str + strlen(msg->ts_str);
540 snprintfrr(msg->ts_dot,
541 msg->ts_str + sizeof(msg->ts_str) - msg->ts_dot,
542 ".%09lu", (unsigned long)msg->ts.tv_nsec);
543
544 msg->ts_flags = ZLOG_TS_ISO8601 | (flags & ZLOG_TS_UTC);
545 }
546
547 len1 = flags & ZLOG_TS_PREC;
548 len1 = (msg->ts_dot - msg->ts_str) + (len1 ? len1 + 1 : 0);
549
550 if (len1 > strlen(msg->ts_str))
551 len1 = strlen(msg->ts_str);
552
553 if (flags & ZLOG_TS_LEGACY) {
554 if (len1 + 1 > outsz)
555 return 0;
556
557 /* just swap out the formatting, faster than redoing it */
558 for (char *p = msg->ts_str; p < msg->ts_str + len1; p++) {
559 switch (*p) {
560 case '-':
561 *out++ = '/';
562 break;
563 case 'T':
564 *out++ = ' ';
565 break;
566 default:
567 *out++ = *p;
568 }
569 }
570 *out = '\0';
571 return len1;
572 } else {
573 size_t len2 = strlen(msg->ts_zonetail);
574
575 if (len1 + len2 + 1 > outsz)
576 return 0;
577 memcpy(out, msg->ts_str, len1);
578 memcpy(out + len1, msg->ts_zonetail, len2);
579 out[len1 + len2] = '\0';
580 return len1 + len2;
581 }
582}
583
584/* setup functions */
585
586struct zlog_target *zlog_target_clone(struct memtype *mt,
587 struct zlog_target *oldzt, size_t size)
588{
589 struct zlog_target *newzt;
590
591 newzt = XCALLOC(mt, size);
592 if (oldzt) {
593 newzt->prio_min = oldzt->prio_min;
594 newzt->logfn = oldzt->logfn;
595 newzt->logfn_sigsafe = oldzt->logfn_sigsafe;
596 }
597
598 return newzt;
599}
600
601struct zlog_target *zlog_target_replace(struct zlog_target *oldzt,
602 struct zlog_target *newzt)
603{
604 if (newzt)
605 zlog_targets_add_tail(&zlog_targets, newzt);
606 if (oldzt)
607 zlog_targets_del(&zlog_targets, oldzt);
608 return oldzt;
609}
610
611
612/* common init */
613
614#define TMPBASEDIR "/var/tmp/frr"
615
616static char zlog_tmpdir[MAXPATHLEN];
617
618void zlog_aux_init(const char *prefix, int prio_min)
619{
620 if (prefix)
621 strlcpy(zlog_prefix, prefix, sizeof(zlog_prefix));
622
623 hook_call(zlog_aux_init, prefix, prio_min);
624}
625
626void zlog_init(const char *progname, const char *protoname,
627 unsigned short instance, uid_t uid, gid_t gid)
628{
629 zlog_uid = uid;
630 zlog_gid = gid;
631
632 if (instance) {
633 snprintfrr(zlog_tmpdir, sizeof(zlog_tmpdir),
634 "/var/tmp/frr/%s-%d.%ld",
635 progname, instance, (long)getpid());
636
637 zlog_prefixsz = snprintfrr(zlog_prefix, sizeof(zlog_prefix),
638 "%s[%d]: ", protoname, instance);
639 } else {
640 snprintfrr(zlog_tmpdir, sizeof(zlog_tmpdir),
641 "/var/tmp/frr/%s.%ld",
642 progname, (long)getpid());
643
644 zlog_prefixsz = snprintfrr(zlog_prefix, sizeof(zlog_prefix),
645 "%s: ", protoname);
646 }
647
648 if (mkdir(TMPBASEDIR, 0700) != 0) {
649 if (errno != EEXIST) {
650 zlog_err("failed to mkdir \"%s\": %s",
651 TMPBASEDIR, strerror(errno));
652 goto out_warn;
653 }
654 }
655 chown(TMPBASEDIR, zlog_uid, zlog_gid);
656
657 if (mkdir(zlog_tmpdir, 0700) != 0) {
658 zlog_err("failed to mkdir \"%s\": %s",
659 zlog_tmpdir, strerror(errno));
660 goto out_warn;
661 }
662
663#ifdef O_PATH
664 zlog_tmpdirfd = open(zlog_tmpdir,
665 O_PATH | O_RDONLY | O_CLOEXEC);
666#else
667 zlog_tmpdirfd = open(zlog_tmpdir,
668 O_DIRECTORY | O_RDONLY | O_CLOEXEC);
669#endif
670 if (zlog_tmpdirfd < 0) {
671 zlog_err("failed to open \"%s\": %s",
672 zlog_tmpdir, strerror(errno));
673 goto out_warn;
674 }
675
676#ifdef AT_EMPTY_PATH
677 fchownat(zlog_tmpdirfd, "", zlog_uid, zlog_gid, AT_EMPTY_PATH);
678#else
679 chown(zlog_tmpdir, zlog_uid, zlog_gid);
680#endif
681
682 hook_call(zlog_init, progname, protoname, instance, uid, gid);
683 return;
684
685out_warn:
686 zlog_err("crashlog and per-thread log buffering unavailable!");
687 hook_call(zlog_init, progname, protoname, instance, uid, gid);
688}
689
690void zlog_fini(void)
691{
692 hook_call(zlog_fini);
693
694 if (zlog_tmpdirfd >= 0) {
695 close(zlog_tmpdirfd);
696 zlog_tmpdirfd = -1;
697
698 if (rmdir(zlog_tmpdir))
699 zlog_err("failed to rmdir \"%s\": %s",
700 zlog_tmpdir, strerror(errno));
701 }
702}