summaryrefslogtreecommitdiffstats
path: root/lib/zlog_targets.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/zlog_targets.c')
-rw-r--r--lib/zlog_targets.c577
1 files changed, 577 insertions, 0 deletions
diff --git a/lib/zlog_targets.c b/lib/zlog_targets.c
new file mode 100644
index 0000000..b0f7571
--- /dev/null
+++ b/lib/zlog_targets.c
@@ -0,0 +1,577 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2015-19 David Lamparter, for NetDEF, Inc.
+ */
+
+#include "zebra.h"
+
+#include <sys/un.h>
+#include <syslog.h>
+
+#include "memory.h"
+#include "frrcu.h"
+#include "frr_pthread.h"
+#include "printfrr.h"
+#include "zlog.h"
+#include "zlog_targets.h"
+
+/* these allocations are intentionally left active even when doing full exit
+ * cleanup, in order to keep the logging subsystem fully functional until the
+ * absolute end.
+ */
+
+DEFINE_MGROUP_ACTIVEATEXIT(LOG, "logging subsystem");
+
+DEFINE_MTYPE_STATIC(LOG, LOG_FD, "log file target");
+DEFINE_MTYPE_STATIC(LOG, LOG_FD_NAME, "log file name");
+DEFINE_MTYPE_STATIC(LOG, LOG_FD_ROTATE, "log file rotate helper");
+DEFINE_MTYPE_STATIC(LOG, LOG_SYSL, "syslog target");
+
+struct zlt_fd {
+ struct zlog_target zt;
+
+ atomic_uint_fast32_t fd;
+
+ char ts_subsec;
+ bool record_priority;
+
+ struct rcu_head_close head_close;
+};
+
+static const char * const prionames[] = {
+ [LOG_EMERG] = "emergencies: ",
+ [LOG_ALERT] = "alerts: ",
+ [LOG_CRIT] = "critical: ",
+ [LOG_ERR] = "errors: ",
+ [LOG_WARNING] = "warnings: ",
+ [LOG_NOTICE] = "notifications: ",
+ [LOG_INFO] = "informational: ",
+ [LOG_DEBUG] = "debugging: ",
+};
+
+void zlog_fd(struct zlog_target *zt, struct zlog_msg *msgs[], size_t nmsgs)
+{
+ struct zlt_fd *zte = container_of(zt, struct zlt_fd, zt);
+ int fd;
+ size_t i, textlen, iovpos = 0;
+ size_t niov = MIN(4 * nmsgs + 1, IOV_MAX);
+ struct iovec iov[niov];
+ /* "\nYYYY-MM-DD HH:MM:SS.NNNNNNNNN+ZZ:ZZ " = 37 chars */
+#define TS_LEN 40
+ char ts_buf[TS_LEN * nmsgs], *ts_pos = ts_buf;
+
+ fd = atomic_load_explicit(&zte->fd, memory_order_relaxed);
+
+ for (i = 0; i < nmsgs; i++) {
+ struct zlog_msg *msg = msgs[i];
+ int prio = zlog_msg_prio(msg);
+
+ if (prio <= zt->prio_min) {
+ struct fbuf fbuf = {
+ .buf = ts_buf,
+ .pos = ts_pos,
+ .len = sizeof(ts_buf),
+ };
+
+ iov[iovpos].iov_base = ts_pos;
+ zlog_msg_ts(msg, &fbuf,
+ ZLOG_TS_LEGACY | zte->ts_subsec);
+ ts_pos = fbuf.pos;
+
+ *ts_pos++ = ' ';
+ iov[iovpos].iov_len =
+ ts_pos - (char *)iov[iovpos].iov_base;
+
+ iovpos++;
+
+ if (zte->record_priority) {
+ iov[iovpos].iov_base = (char *)prionames[prio];
+ iov[iovpos].iov_len =
+ strlen(iov[iovpos].iov_base);
+
+ iovpos++;
+ }
+
+ iov[iovpos].iov_base = zlog_prefix;
+ iov[iovpos].iov_len = zlog_prefixsz;
+
+ iovpos++;
+
+ iov[iovpos].iov_base =
+ (char *)zlog_msg_text(msg, &textlen);
+ iov[iovpos].iov_len = textlen + 1;
+
+ iovpos++;
+ }
+
+ /* conditions that trigger writing:
+ * - out of space for more timestamps/headers
+ * - this being the last message in the batch
+ * - not enough remaining iov entries
+ */
+ if (iovpos > 0 && (ts_buf + sizeof(ts_buf) - ts_pos < TS_LEN
+ || i + 1 == nmsgs
+ || array_size(iov) - iovpos < 5)) {
+ writev(fd, iov, iovpos);
+
+ iovpos = 0;
+ ts_pos = ts_buf;
+ }
+ }
+
+ assert(iovpos == 0);
+}
+
+static void zlog_fd_sigsafe(struct zlog_target *zt, const char *text,
+ size_t len)
+{
+ struct zlt_fd *zte = container_of(zt, struct zlt_fd, zt);
+ struct iovec iov[4];
+ int fd;
+
+ iov[0].iov_base = (char *)prionames[LOG_CRIT];
+ iov[0].iov_len = zte->record_priority ? strlen(iov[0].iov_base) : 0;
+
+ iov[1].iov_base = zlog_prefix;
+ iov[1].iov_len = zlog_prefixsz;
+
+ iov[2].iov_base = (char *)text;
+ iov[2].iov_len = len;
+
+ iov[3].iov_base = (char *)"\n";
+ iov[3].iov_len = 1;
+
+ fd = atomic_load_explicit(&zte->fd, memory_order_relaxed);
+
+ writev(fd, iov, array_size(iov));
+}
+
+/*
+ * (re-)configuration
+ */
+
+void zlog_file_init(struct zlog_cfg_file *zcf)
+{
+ memset(zcf, 0, sizeof(*zcf));
+ zcf->prio_min = ZLOG_DISABLED;
+ zcf->fd = -1;
+ pthread_mutex_init(&zcf->cfg_mtx, NULL);
+}
+
+static void zlog_file_target_free(struct zlt_fd *zlt)
+{
+ if (!zlt)
+ return;
+
+ rcu_close(&zlt->head_close, zlt->fd);
+ rcu_free(MTYPE_LOG_FD, zlt, zt.rcu_head);
+}
+
+void zlog_file_fini(struct zlog_cfg_file *zcf)
+{
+ if (zcf->active) {
+ struct zlt_fd *ztf;
+ struct zlog_target *zt;
+
+ zt = zlog_target_replace(&zcf->active->zt, NULL);
+ ztf = container_of(zt, struct zlt_fd, zt);
+ zlog_file_target_free(ztf);
+ }
+ XFREE(MTYPE_LOG_FD_NAME, zcf->filename);
+ pthread_mutex_destroy(&zcf->cfg_mtx);
+}
+
+static bool zlog_file_cycle(struct zlog_cfg_file *zcf)
+{
+ struct zlog_target *zt, *old;
+ struct zlt_fd *zlt = NULL;
+ int fd;
+ bool rv = true;
+
+ do {
+ if (zcf->prio_min == ZLOG_DISABLED)
+ break;
+
+ if (zcf->fd != -1)
+ fd = dup(zcf->fd);
+ else if (zcf->filename)
+ fd = open(zcf->filename,
+ O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC
+ | O_NOCTTY,
+ LOGFILE_MASK);
+ else
+ fd = -1;
+
+ if (fd < 0) {
+ rv = false;
+ break;
+ }
+
+ zt = zlog_target_clone(MTYPE_LOG_FD, &zcf->active->zt,
+ sizeof(*zlt));
+ zlt = container_of(zt, struct zlt_fd, zt);
+
+ zlt->fd = fd;
+ zlt->record_priority = zcf->record_priority;
+ zlt->ts_subsec = zcf->ts_subsec;
+
+ zlt->zt.prio_min = zcf->prio_min;
+ zlt->zt.logfn = zcf->zlog_wrap ? zcf->zlog_wrap : zlog_fd;
+ zlt->zt.logfn_sigsafe = zlog_fd_sigsafe;
+ } while (0);
+
+ old = zlog_target_replace(zcf->active ? &zcf->active->zt : NULL,
+ zlt ? &zlt->zt : NULL);
+ zcf->active = zlt;
+
+ zlog_file_target_free(container_of_null(old, struct zlt_fd, zt));
+
+ return rv;
+}
+
+void zlog_file_set_other(struct zlog_cfg_file *zcf)
+{
+ frr_with_mutex (&zcf->cfg_mtx) {
+ zlog_file_cycle(zcf);
+ }
+}
+
+bool zlog_file_set_filename(struct zlog_cfg_file *zcf, const char *filename)
+{
+ frr_with_mutex (&zcf->cfg_mtx) {
+ XFREE(MTYPE_LOG_FD_NAME, zcf->filename);
+ zcf->filename = XSTRDUP(MTYPE_LOG_FD_NAME, filename);
+ zcf->fd = -1;
+
+ return zlog_file_cycle(zcf);
+ }
+ assert(0);
+ return false;
+}
+
+bool zlog_file_set_fd(struct zlog_cfg_file *zcf, int fd)
+{
+ frr_with_mutex (&zcf->cfg_mtx) {
+ if (zcf->fd == fd)
+ return true;
+
+ XFREE(MTYPE_LOG_FD_NAME, zcf->filename);
+ zcf->fd = fd;
+
+ return zlog_file_cycle(zcf);
+ }
+ assert(0);
+ return false;
+}
+
+struct rcu_close_rotate {
+ struct rcu_head_close head_close;
+ struct rcu_head head_self;
+};
+
+bool zlog_file_rotate(struct zlog_cfg_file *zcf)
+{
+ struct rcu_close_rotate *rcr;
+ int fd;
+
+ frr_with_mutex (&zcf->cfg_mtx) {
+ if (!zcf->active || !zcf->filename)
+ return true;
+
+ fd = open(zcf->filename,
+ O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC | O_NOCTTY,
+ LOGFILE_MASK);
+ if (fd < 0)
+ return false;
+
+ fd = atomic_exchange_explicit(&zcf->active->fd,
+ (uint_fast32_t)fd,
+ memory_order_relaxed);
+ }
+
+ rcr = XCALLOC(MTYPE_LOG_FD_ROTATE, sizeof(*rcr));
+ rcu_close(&rcr->head_close, fd);
+ rcu_free(MTYPE_LOG_FD_ROTATE, rcr, head_self);
+
+ return true;
+}
+
+/* fixed crash logging */
+
+static struct zlt_fd zlog_crashlog;
+
+static void zlog_crashlog_sigsafe(struct zlog_target *zt, const char *text,
+ size_t len)
+{
+ static int crashlog_fd = -1;
+
+ if (crashlog_fd == -1) {
+#ifdef HAVE_OPENAT
+ crashlog_fd = openat(zlog_tmpdirfd, "crashlog",
+ O_WRONLY | O_APPEND | O_CREAT,
+ LOGFILE_MASK);
+#endif
+ if (crashlog_fd < 0)
+ crashlog_fd = -2;
+ }
+
+ if (crashlog_fd == -2)
+ return;
+
+ zlog_crashlog.fd = crashlog_fd;
+ zlog_fd_sigsafe(&zlog_crashlog.zt, text, len);
+}
+
+/* this is used for assert failures (they don't need AS-Safe logging) */
+static void zlog_crashlog_plain(struct zlog_target *zt, struct zlog_msg *msgs[],
+ size_t nmsgs)
+{
+ size_t i, len;
+ const char *text;
+
+ for (i = 0; i < nmsgs; i++) {
+ if (zlog_msg_prio(msgs[i]) > zt->prio_min)
+ continue;
+
+ text = zlog_msg_text(msgs[i], &len);
+ zlog_crashlog_sigsafe(zt, text, len);
+ }
+}
+
+static void zlog_crashlog_init(void)
+{
+ zlog_crashlog.zt.prio_min = LOG_CRIT;
+ zlog_crashlog.zt.logfn = zlog_crashlog_plain;
+ zlog_crashlog.zt.logfn_sigsafe = zlog_crashlog_sigsafe;
+ zlog_crashlog.fd = -1;
+
+ zlog_target_replace(NULL, &zlog_crashlog.zt);
+}
+
+/* fixed logging for test/auxiliary programs */
+
+static struct zlt_fd zlog_aux_stdout;
+static bool zlog_is_aux;
+
+static int zlt_aux_init(const char *prefix, int prio_min)
+{
+ zlog_is_aux = true;
+
+ zlog_aux_stdout.zt.prio_min = prio_min;
+ zlog_aux_stdout.zt.logfn = zlog_fd;
+ zlog_aux_stdout.zt.logfn_sigsafe = zlog_fd_sigsafe;
+ zlog_aux_stdout.fd = STDOUT_FILENO;
+
+ zlog_target_replace(NULL, &zlog_aux_stdout.zt);
+ zlog_startup_end();
+ return 0;
+}
+
+static int zlt_init(const char *progname, const char *protoname,
+ unsigned short instance, uid_t uid, gid_t gid)
+{
+ openlog(progname, LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON);
+ return 0;
+}
+
+static int zlt_fini(void)
+{
+ closelog();
+ return 0;
+}
+
+/* fixed startup logging to stderr */
+
+static struct zlt_fd zlog_startup_stderr;
+
+__attribute__((_CONSTRUCTOR(450))) static void zlog_startup_init(void)
+{
+ zlog_startup_stderr.zt.prio_min = LOG_WARNING;
+ zlog_startup_stderr.zt.logfn = zlog_fd;
+ zlog_startup_stderr.zt.logfn_sigsafe = zlog_fd_sigsafe;
+ zlog_startup_stderr.fd = STDERR_FILENO;
+
+ zlog_target_replace(NULL, &zlog_startup_stderr.zt);
+
+ hook_register(zlog_aux_init, zlt_aux_init);
+ hook_register(zlog_init, zlt_init);
+ hook_register(zlog_fini, zlt_fini);
+}
+
+void zlog_startup_end(void)
+{
+ static bool startup_ended = false;
+
+ if (startup_ended)
+ return;
+ startup_ended = true;
+
+ zlog_target_replace(&zlog_startup_stderr.zt, NULL);
+
+ if (zlog_is_aux)
+ return;
+
+ /* until here, crashlogs go to stderr */
+ zlog_crashlog_init();
+}
+
+/* syslog */
+
+struct zlt_syslog {
+ struct zlog_target zt;
+
+ int syslog_facility;
+};
+
+static void zlog_syslog(struct zlog_target *zt, struct zlog_msg *msgs[],
+ size_t nmsgs)
+{
+ size_t i;
+ struct zlt_syslog *zte = container_of(zt, struct zlt_syslog, zt);
+ const char *text;
+ size_t text_len;
+
+ for (i = 0; i < nmsgs; i++) {
+ if (zlog_msg_prio(msgs[i]) > zt->prio_min)
+ continue;
+
+ text = zlog_msg_text(msgs[i], &text_len);
+ syslog(zlog_msg_prio(msgs[i]) | zte->syslog_facility, "%.*s",
+ (int)text_len, text);
+ }
+}
+
+#ifndef _PATH_LOG
+#define _PATH_LOG "/dev/log"
+#endif
+
+static void zlog_syslog_sigsafe(struct zlog_target *zt, const char *text,
+ size_t len)
+{
+ static int syslog_fd = -1;
+
+ char hdr[192];
+ size_t hdrlen;
+ struct iovec iov[2];
+
+ if (syslog_fd == -1) {
+ syslog_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
+ if (syslog_fd >= 0) {
+ struct sockaddr_un sa;
+ socklen_t salen = sizeof(sa);
+
+ sa.sun_family = AF_UNIX;
+ strlcpy(sa.sun_path, _PATH_LOG, sizeof(sa.sun_path));
+#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN
+ salen = sa.sun_len = SUN_LEN(&sa);
+#endif
+ if (connect(syslog_fd, (struct sockaddr *)&sa, salen)) {
+ close(syslog_fd);
+ syslog_fd = -1;
+ }
+ }
+
+ /* /dev/log could be a fifo instead of a socket */
+ if (syslog_fd == -1) {
+ syslog_fd = open(_PATH_LOG, O_WRONLY | O_NOCTTY);
+ if (syslog_fd < 0)
+ /* give up ... */
+ syslog_fd = -2;
+ }
+ }
+
+ if (syslog_fd == -2)
+ return;
+
+ /* note zlog_prefix includes trailing ": ", need to cut off 2 chars */
+ hdrlen = snprintfrr(hdr, sizeof(hdr), "<%d>%.*s[%ld]: ", LOG_CRIT,
+ zlog_prefixsz > 2 ? (int)(zlog_prefixsz - 2) : 0,
+ zlog_prefix, (long)getpid());
+
+ iov[0].iov_base = hdr;
+ iov[0].iov_len = hdrlen;
+
+ iov[1].iov_base = (char *)text;
+ iov[1].iov_len = len;
+
+ writev(syslog_fd, iov, array_size(iov));
+}
+
+
+static pthread_mutex_t syslog_cfg_mutex = PTHREAD_MUTEX_INITIALIZER;
+static struct zlt_syslog *zlt_syslog;
+static int syslog_facility = LOG_DAEMON;
+static int syslog_prio_min = ZLOG_DISABLED;
+
+void zlog_syslog_set_facility(int facility)
+{
+ struct zlog_target *newztc;
+ struct zlt_syslog *newzt;
+
+ frr_with_mutex (&syslog_cfg_mutex) {
+ if (facility == syslog_facility)
+ return;
+ syslog_facility = facility;
+
+ if (syslog_prio_min == ZLOG_DISABLED)
+ return;
+
+ newztc = zlog_target_clone(MTYPE_LOG_SYSL, &zlt_syslog->zt,
+ sizeof(*newzt));
+ newzt = container_of(newztc, struct zlt_syslog, zt);
+ newzt->syslog_facility = syslog_facility;
+
+ zlog_target_free(MTYPE_LOG_SYSL,
+ zlog_target_replace(&zlt_syslog->zt,
+ &newzt->zt));
+
+ zlt_syslog = newzt;
+ }
+}
+
+int zlog_syslog_get_facility(void)
+{
+ frr_with_mutex (&syslog_cfg_mutex) {
+ return syslog_facility;
+ }
+ assert(0);
+ return 0;
+}
+
+void zlog_syslog_set_prio_min(int prio_min)
+{
+ struct zlog_target *newztc;
+ struct zlt_syslog *newzt = NULL;
+
+ frr_with_mutex (&syslog_cfg_mutex) {
+ if (prio_min == syslog_prio_min)
+ return;
+ syslog_prio_min = prio_min;
+
+ if (syslog_prio_min != ZLOG_DISABLED) {
+ newztc = zlog_target_clone(MTYPE_LOG_SYSL,
+ &zlt_syslog->zt,
+ sizeof(*newzt));
+ newzt = container_of(newztc, struct zlt_syslog, zt);
+ newzt->zt.prio_min = prio_min;
+ newzt->zt.logfn = zlog_syslog;
+ newzt->zt.logfn_sigsafe = zlog_syslog_sigsafe;
+ newzt->syslog_facility = syslog_facility;
+ }
+
+ zlog_target_free(MTYPE_LOG_SYSL,
+ zlog_target_replace(&zlt_syslog->zt,
+ &newzt->zt));
+
+ zlt_syslog = newzt;
+ }
+}
+
+int zlog_syslog_get_prio_min(void)
+{
+ frr_with_mutex (&syslog_cfg_mutex) {
+ return syslog_prio_min;
+ }
+ assert(0);
+ return 0;
+}