From 1c6261a99e9a1447422869a116f704097a19854a Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Wed, 29 Apr 2020 10:26:05 +0200 Subject: [PATCH] lib: RFC5424 & journald extended syslog target Not much to say here, user docs are coming up in a separate commit. RFC5424 and (systemd's) journald allow passing structured key-value data. This stuffs the metadata we have available into there. The "does the system syslogd support RFC5424" question is unfortunately not easily answered, so we can only give an affirmative answer on NetBSD 5.0+ or FreeBSD 12+. Signed-off-by: David Lamparter --- lib/command.h | 1 + lib/log_vty.c | 3 + lib/subdir.am | 4 + lib/zlog_5424.c | 1146 +++++++++++++++++++++++++++++++++++++++++++ lib/zlog_5424.h | 152 ++++++ lib/zlog_5424_cli.c | 1006 +++++++++++++++++++++++++++++++++++++ 6 files changed, 2312 insertions(+) create mode 100644 lib/zlog_5424.c create mode 100644 lib/zlog_5424.h create mode 100644 lib/zlog_5424_cli.c diff --git a/lib/command.h b/lib/command.h index c888356d61..902d39ebe2 100644 --- a/lib/command.h +++ b/lib/command.h @@ -93,6 +93,7 @@ enum node_type { RMAP_DEBUG_NODE, /* Route-map debug node */ RESOLVER_DEBUG_NODE, /* Resolver debug node */ AAA_NODE, /* AAA node. */ + EXTLOG_NODE, /* RFC5424 & co. extended syslog */ KEYCHAIN_NODE, /* Key-chain node. */ KEYCHAIN_KEY_NODE, /* Key-chain key node. */ IP_NODE, /* Static ip route node. */ diff --git a/lib/log_vty.c b/lib/log_vty.c index 621949ab57..d50e2788ef 100644 --- a/lib/log_vty.c +++ b/lib/log_vty.c @@ -24,6 +24,7 @@ #include "command.h" #include "lib/log.h" #include "lib/zlog_targets.h" +#include "lib/zlog_5424.h" #include "lib/lib_errors.h" #include "lib/printfrr.h" @@ -861,4 +862,6 @@ void log_cmd_init(void) install_element(ENABLE_NODE, &debug_uid_backtrace_cmd); install_element(CONFIG_NODE, &debug_uid_backtrace_cmd); + + log_5424_cmd_init(); } diff --git a/lib/subdir.am b/lib/subdir.am index bb10d71ed1..a56bbb0c8d 100644 --- a/lib/subdir.am +++ b/lib/subdir.am @@ -110,6 +110,8 @@ lib_libfrr_la_SOURCES = \ lib/yang_wrappers.c \ lib/zclient.c \ lib/zlog.c \ + lib/zlog_5424.c \ + lib/zlog_5424_cli.c \ lib/zlog_targets.c \ lib/printf/printf-pos.c \ lib/printf/vfprintf.c \ @@ -168,6 +170,7 @@ clippy_scan += \ lib/routemap_cli.c \ lib/thread.c \ lib/vty.c \ + lib/zlog_5424_cli.c \ # end pkginclude_HEADERS += \ @@ -281,6 +284,7 @@ pkginclude_HEADERS += \ lib/zclient.h \ lib/zebra.h \ lib/zlog.h \ + lib/zlog_5424.h \ lib/zlog_targets.h \ lib/pbr.h \ lib/routing_nb.h \ diff --git a/lib/zlog_5424.c b/lib/zlog_5424.c new file mode 100644 index 0000000000..740d6bfba8 --- /dev/null +++ b/lib/zlog_5424.c @@ -0,0 +1,1146 @@ +/* + * Copyright (c) 2015-21 David Lamparter, for NetDEF, Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* when you work on this code, please install a fuzzer (e.g. AFL) and run + * tests/lib/fuzz_zlog.c + * + * The most likely type of bug in this code is an off-by-one error in the + * buffer management pieces, and this isn't easily covered by an unit test + * or topotests. Fuzzing is the best tool here, but the CI can't do that + * since it's quite resource intensive. + */ + +#include "zebra.h" + +#include "zlog_5424.h" + +#include +#include + +#include "memory.h" +#include "frrcu.h" +#include "printfrr.h" +#include "typerb.h" +#include "frr_pthread.h" +#include "command.h" +#include "monotime.h" +#include "thread.h" + +#include "lib/version.h" +#include "lib/lib_errors.h" + +DEFINE_MTYPE_STATIC(LOG, LOG_5424, "extended log target"); +DEFINE_MTYPE_STATIC(LOG, LOG_5424_ROTATE, "extended log rotate helper"); + +/* the actual log target data structure + * + * remember this is RCU'd by the core zlog functions. Changing anything + * works by allocating a new struct, filling it, adding it, and removing the + * old one. + */ +struct zlt_5424 { + struct zlog_target zt; + + atomic_uint_fast32_t fd; + + enum zlog_5424_format fmt; + uint32_t ts_flags; + int facility; + + /* the various extra pieces to add... */ + bool kw_version : 1; + bool kw_location : 1; + bool kw_uid : 1; + bool kw_ec : 1; + bool kw_args : 1; + + /* some formats may or may not include the trailing \n */ + bool use_nl : 1; + + /* for DGRAM & SEQPACKET sockets, send 1 log message per packet, since + * the socket preserves packet boundaries. On Linux, this uses + * sendmmsg() for efficiency, on other systems we need a syscall each. + */ + bool packets : 1; + + /* for DGRAM, in order to not have to reconnect, we need to use + * sendto()/sendmsg() with the destination given; otherwise we'll get + * ENOTCONN. (We do a connect(), which serves to verify the type of + * socket, but if the receiver goes away, the kernel disconnects the + * socket so writev() no longer works since the destination is now + * unspecified.) + */ + struct sockaddr_storage sa; + socklen_t sa_len; + + /* these are both getting set, but current_err is cleared on success, + * so we know whether the error is current or past. + */ + int last_err, current_err; + atomic_size_t lost_msgs; + struct timeval last_err_ts; + + struct rcu_head_close head_close; +}; + +static int zlog_5424_open(struct zlog_cfg_5424 *zcf, int sock_type); + +/* rough header length estimate + * ============================ + * + * ^ = might grow + * + * 49^ longest filename (pceplib/test/pcep_utils_double_linked_list_test.h) + * 5^ highest line number (48530, bgpd/bgp_nb_config.c) + * 65^ longest function name + * (lib_prefix_list_entry_ipv6_prefix_length_greater_or_equal_destroy) + * 11 unique id ("XXXXX-XXXXX") + * 10 EC ("4294967295" or "0xffffffff") + * 35 ISO8601 TS at full length ("YYYY-MM-DD HH:MM:SS.NNNNNNNNN+ZZ:ZZ") + * --- + * 175 + * + * rarely used (hopefully...): + * 26^ FRR_VERSION ("10.10.10-dev-gffffffffffff") + * --- + * 201 + * + * x16 highest number of format parameters currently + * 40 estimate for hostname + 2*daemon + pid + * + * specific format overhead: + * + * RFC3164 - shorter than the others + * RFC5424 - 175 + "<999>1 "=7 + 52 (location@50145) + 40 (host/...) + * rarely: + 65 + 26 (for [origin]) + * args: 16 * (8 + per-arg (20?)) = ~448 + * + * so without "args@", origin or (future) keywords, around 256 seems OK + * with args@ and/or origin and/or keywords, 512 seems more reasonable + * + * but - note the code allocates this amount multiplied by the number of + * messages in the incoming batch (minimum 3), this means short messages and + * long messages smooth each other out. + * + * Since the code handles space-exceeded by grabbing a bunch of stack memory, + * a reasonable middle ground estimate is desirable here, so ... + * *drumroll* + * let's go with 128 + args?128. (remember the minimum 3 multiplier) + * + * low_space is the point where we don't try to fit another message in & just + * submit what we have to the kernel. + * + * The zlog code only buffers debug & informational messages, so in production + * usage most of the calls will be writing out only 1 message. This makes + * the min *3 multiplier quite useful. + */ + +static inline size_t zlog_5424_bufsz(struct zlt_5424 *zte, size_t nmsgs, + size_t *low_space) +{ + size_t ret = 128; + + if (zte->kw_args) + ret += 128; + *low_space = ret; + return ret * MAX(nmsgs, 3); +} + +struct state { + struct fbuf *fbuf; + struct iovec *iov; +}; + +/* stack-based keyword support is likely to bump this to 3 or 4 */ +#define IOV_PER_MSG 2 +_Static_assert(IOV_MAX >= IOV_PER_MSG, + "this code won't work with IOV_MAX < IOV_PER_MSG"); + +/* the following functions are quite similar, but trying to merge them just + * makes a big mess. check the others when touching one. + * + * timestamp keywords hostname + * RFC5424 ISO8601 yes yes + * RFC3164 RFC3164 no yes + * local RFC3164 no no + * journald ISO8601(unused) yes (unused) + */ + +static size_t zlog_5424_one(struct zlt_5424 *zte, struct zlog_msg *msg, + struct state *state) +{ + size_t textlen; + struct fbuf *fbuf = state->fbuf; + char *orig_pos = fbuf->pos; + size_t need = 0; + int prio = zlog_msg_prio(msg); + intmax_t pid, tid; + + zlog_msg_pid(msg, &pid, &tid); + + need += bprintfrr(fbuf, "<%d>1 ", prio | zte->facility); + need += zlog_msg_ts(msg, fbuf, zte->ts_flags); + need += bprintfrr(fbuf, " %s %s %jd %.*s ", cmd_hostname_get() ?: "-", + zlog_progname, pid, (int)(zlog_prefixsz - 2), + zlog_prefix); + + if (zte->kw_version) + need += bprintfrr( + fbuf, + "[origin enterpriseId=\"50145\" software=\"FRRouting\" swVersion=\"%s\"]", + FRR_VERSION); + + const struct xref_logmsg *xref; + struct xrefdata *xrefdata; + + need += bprintfrr(fbuf, "[location@50145 tid=\"%jd\"", tid); + if (zlog_instance > 0) + need += bprintfrr(fbuf, " instance=\"%d\"", zlog_instance); + + xref = zlog_msg_xref(msg); + xrefdata = xref ? xref->xref.xrefdata : NULL; + if (xrefdata) { + if (zte->kw_uid) + need += bprintfrr(fbuf, " id=\"%s\"", xrefdata->uid); + if (zte->kw_ec && prio <= LOG_WARNING) + need += bprintfrr(fbuf, " ec=\"%u\"", xref->ec); + if (zte->kw_location) + need += bprintfrr( + fbuf, " file=\"%s\" line=\"%d\" func=\"%s\"", + xref->xref.file, xref->xref.line, + xref->xref.func); + } + need += bputch(fbuf, ']'); + + size_t hdrlen, n_argpos; + const struct fmt_outpos *argpos; + const char *text; + + text = zlog_msg_text(msg, &textlen); + zlog_msg_args(msg, &hdrlen, &n_argpos, &argpos); + + if (zte->kw_args && n_argpos) { + need += bputs(fbuf, "[args@50145"); + + for (size_t i = 0; i < n_argpos; i++) { + int len = argpos[i].off_end - argpos[i].off_start; + + need += bprintfrr(fbuf, " arg%zu=%*pSQsq", i + 1, len, + text + argpos[i].off_start); + } + + need += bputch(fbuf, ']'); + } + + need += bputch(fbuf, ' '); + + if (orig_pos + need > fbuf->buf + fbuf->len) { + /* not enough space in the buffer for headers. the loop in + * zlog_5424() will flush other messages that are already in + * the buffer, grab a bigger buffer if needed, and try again. + */ + fbuf->pos = orig_pos; + return need; + } + + /* NB: zlog_5424 below assumes we use max. IOV_PER_MSG iovs here */ + state->iov->iov_base = orig_pos; + state->iov->iov_len = fbuf->pos - orig_pos; + state->iov++; + + state->iov->iov_base = (char *)text + hdrlen; + state->iov->iov_len = textlen - hdrlen + zte->use_nl; + state->iov++; + return 0; +} + +static size_t zlog_3164_one(struct zlt_5424 *zte, struct zlog_msg *msg, + struct state *state) +{ + size_t textlen; + struct fbuf *fbuf = state->fbuf; + char *orig_pos = fbuf->pos; + size_t need = 0; + int prio = zlog_msg_prio(msg); + intmax_t pid, tid; + + zlog_msg_pid(msg, &pid, &tid); + + need += bprintfrr(fbuf, "<%d>", prio | zte->facility); + need += zlog_msg_ts_3164(msg, fbuf, zte->ts_flags); + if (zte->fmt != ZLOG_FMT_LOCAL) { + need += bputch(fbuf, ' '); + need += bputs(fbuf, cmd_hostname_get() ?: "-"); + } + need += bprintfrr(fbuf, " %s[%jd]: ", zlog_progname, pid); + + if (orig_pos + need > fbuf->buf + fbuf->len) { + /* not enough space in the buffer for headers. loop in + * zlog_5424() will flush other messages that are already in + * the buffer, grab a bigger buffer if needed, and try again. + */ + fbuf->pos = orig_pos; + return need; + } + + /* NB: zlog_5424 below assumes we use max. IOV_PER_MSG iovs here */ + state->iov->iov_base = orig_pos; + state->iov->iov_len = fbuf->pos - orig_pos; + state->iov++; + + state->iov->iov_base = (char *)zlog_msg_text(msg, &textlen); + state->iov->iov_len = textlen + zte->use_nl; + state->iov++; + return 0; +} + +static size_t zlog_journald_one(struct zlt_5424 *zte, struct zlog_msg *msg, + struct state *state) +{ + size_t textlen; + struct fbuf *fbuf = state->fbuf; + char *orig_pos = fbuf->pos; + size_t need = 0; + int prio = zlog_msg_prio(msg); + intmax_t pid, tid; + + zlog_msg_pid(msg, &pid, &tid); + + need += bprintfrr(fbuf, + "PRIORITY=%d\n" + "SYSLOG_FACILITY=%d\n" + "TID=%jd\n" + "FRR_DAEMON=%s\n" + "SYSLOG_TIMESTAMP=", + prio, zte->facility, tid, zlog_progname); + need += zlog_msg_ts(msg, fbuf, zte->ts_flags); + need += bputch(fbuf, '\n'); + if (zlog_instance > 0) + need += bprintfrr(fbuf, "FRR_INSTANCE=%d\n", zlog_instance); + + const struct xref_logmsg *xref; + struct xrefdata *xrefdata; + + xref = zlog_msg_xref(msg); + xrefdata = xref ? xref->xref.xrefdata : NULL; + if (xrefdata) { + if (zte->kw_uid && xrefdata->uid[0]) + need += bprintfrr(fbuf, "FRR_ID=%s\n", xrefdata->uid); + if (zte->kw_ec && prio <= LOG_WARNING) + need += bprintfrr(fbuf, "FRR_EC=%d\n", xref->ec); + if (zte->kw_location) + need += bprintfrr(fbuf, + "CODE_FILE=%s\n" + "CODE_LINE=%d\n" + "CODE_FUNC=%s\n", + xref->xref.file, xref->xref.line, + xref->xref.func); + } + + size_t hdrlen, n_argpos; + const struct fmt_outpos *argpos; + const char *text; + + text = zlog_msg_text(msg, &textlen); + zlog_msg_args(msg, &hdrlen, &n_argpos, &argpos); + + if (zte->kw_args && n_argpos) { + for (size_t i = 0; i < n_argpos; i++) { + int len = argpos[i].off_end - argpos[i].off_start; + + /* rather than escape the value, we could use + * journald's binary encoding, but that seems a bit + * excessive/unnecessary. 99% of things we print here + * will just output 1:1 with %pSE. + */ + need += bprintfrr(fbuf, "FRR_ARG%zu=%*pSE\n", i + 1, + len, text + argpos[i].off_start); + } + } + + need += bputs(fbuf, "MESSAGE="); + + if (orig_pos + need > fbuf->buf + fbuf->len) { + /* not enough space in the buffer for headers. loop in + * zlog_5424() will flush other messages that are already in + * the buffer, grab a bigger buffer if needed, and try again. + */ + fbuf->pos = orig_pos; + return need; + } + + /* NB: zlog_5424 below assumes we use max. IOV_PER_MSG iovs here */ + state->iov->iov_base = orig_pos; + state->iov->iov_len = fbuf->pos - orig_pos; + state->iov++; + + state->iov->iov_base = (char *)text + hdrlen; + state->iov->iov_len = textlen - hdrlen + 1; + state->iov++; + return 0; +} + +static size_t zlog_one(struct zlt_5424 *zte, struct zlog_msg *msg, + struct state *state) +{ + switch (zte->fmt) { + case ZLOG_FMT_5424: + return zlog_5424_one(zte, msg, state); + case ZLOG_FMT_3164: + case ZLOG_FMT_LOCAL: + return zlog_3164_one(zte, msg, state); + case ZLOG_FMT_JOURNALD: + return zlog_journald_one(zte, msg, state); + } + return 0; +} + +static void zlog_5424_err(struct zlt_5424 *zte, size_t count) +{ + if (!count) { + zte->current_err = 0; + return; + } + + /* only the counter is atomic because otherwise it'd be meaningless */ + atomic_fetch_add_explicit(&zte->lost_msgs, count, memory_order_relaxed); + + /* these are non-atomic and can provide wrong results when read, but + * since they're only for debugging / display, that's OK. + */ + zte->current_err = zte->last_err = errno; + monotime(&zte->last_err_ts); +} + +static void zlog_5424(struct zlog_target *zt, struct zlog_msg *msgs[], + size_t nmsgs) +{ + size_t i; + struct zlt_5424 *zte = container_of(zt, struct zlt_5424, zt); + int fd, ret; + size_t niov = MIN(IOV_PER_MSG * nmsgs, IOV_MAX); + struct iovec iov[niov], *iov_last = iov + niov; + struct mmsghdr mmsg[zte->packets ? nmsgs : 1], *mpos = mmsg; + size_t count = 0; + + /* refer to size estimate at top of file */ + size_t low_space; + char hdr_buf[zlog_5424_bufsz(zte, nmsgs, &low_space)]; + struct fbuf hdr_pos = { + .buf = hdr_buf, + .pos = hdr_buf, + .len = sizeof(hdr_buf), + }; + struct state state = { + .fbuf = &hdr_pos, + .iov = iov, + }; + + fd = atomic_load_explicit(&zte->fd, memory_order_relaxed); + + memset(mmsg, 0, sizeof(mmsg)); + if (zte->sa_len) { + for (i = 0; i < array_size(mmsg); i++) { + mmsg[i].msg_hdr.msg_name = (struct sockaddr *)&zte->sa; + mmsg[i].msg_hdr.msg_namelen = zte->sa_len; + } + } + mmsg[0].msg_hdr.msg_iov = iov; + + for (i = 0; i < nmsgs; i++) { + int prio = zlog_msg_prio(msgs[i]); + size_t need = 0; + + if (prio <= zte->zt.prio_min) { + if (zte->packets) + mpos->msg_hdr.msg_iov = state.iov; + + need = zlog_one(zte, msgs[i], &state); + + if (zte->packets) { + mpos->msg_hdr.msg_iovlen = + state.iov - mpos->msg_hdr.msg_iov; + mpos++; + } + count++; + } + + /* clang-format off */ + if ((need + || (size_t)(hdr_pos.buf + hdr_pos.len - hdr_pos.pos) + < low_space + || i + 1 == nmsgs + || state.iov + IOV_PER_MSG > iov_last) + && state.iov > iov) { + /* clang-format on */ + + if (zte->packets) { + struct mmsghdr *sendpos; + + for (sendpos = mmsg; sendpos < mpos;) { + ret = sendmmsg(fd, sendpos, + mpos - sendpos, 0); + if (ret <= 0) + break; + sendpos += ret; + } + zlog_5424_err(zte, mpos - sendpos); + mpos = mmsg; + } else { + if (!zte->sa_len) + ret = writev(fd, iov, state.iov - iov); + else { + mpos->msg_hdr.msg_iovlen = + state.iov - iov; + ret = sendmsg(fd, &mpos->msg_hdr, 0); + } + + if (ret < 0) + zlog_5424_err(zte, count); + else + zlog_5424_err(zte, 0); + } + + count = 0; + hdr_pos.pos = hdr_buf; + state.iov = iov; + } + + /* if need == 0, we just put a message (or nothing) in the + * buffer and are continuing for more to batch in a single + * writev() + */ + if (need == 0) + continue; + + if (need && need <= sizeof(hdr_buf)) { + /* don't need to allocate, just try this msg + * again without other msgs already using up + * buffer space + */ + i--; + continue; + } + + /* need > sizeof(hdr_buf), need to grab some memory. Taking + * it off the stack is fine here. + */ + char buf2[need]; + struct fbuf fbuf2 = { + .buf = buf2, + .pos = buf2, + .len = sizeof(buf2), + }; + + state.fbuf = &fbuf2; + need = zlog_one(zte, msgs[i], &state); + assert(need == 0); + + if (!zte->sa_len) + ret = writev(fd, iov, state.iov - iov); + else { + mpos->msg_hdr.msg_iovlen = state.iov - iov; + ret = sendmsg(fd, &mpos->msg_hdr, 0); + } + + if (ret < 0) + zlog_5424_err(zte, 1); + else + zlog_5424_err(zte, 0); + + count = 0; + state.fbuf = &hdr_pos; + state.iov = iov; + mpos = mmsg; + } + + assert(state.iov == iov); +} + +/* strftime(), gmtime_r() and localtime_r() aren't AS-Safe (they access locale + * data), but we need an AS-Safe timestamp below :( + */ +static void gmtime_assafe(time_t ts, struct tm *res) +{ + res->tm_sec = ts % 60; + ts /= 60; + res->tm_min = ts % 60; + ts /= 60; + res->tm_hour = ts % 24; + ts /= 24; + + ts -= 11017; /* start on 2020-03-01, 11017 days since 1970-01-01 */ + + /* 1461 days = 3 regular years + 1 leap year + * this works until 2100, which isn't a leap year + * + * struct tm.tm_year starts at 1900. + */ + res->tm_year = 2000 - 1900 + 4 * (ts / 1461); + ts = ts % 1461; + + if (ts == 1460) { + res->tm_year += 4; + res->tm_mon = 1; + res->tm_mday = 29; + return; + } + res->tm_year += ts / 365; + ts %= 365; + + /* note we're starting in march like the romans did... */ + if (ts >= 306) /* Jan 1 of next year */ + res->tm_year++; + + static unsigned int months[13] = { + 0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337, 365, + }; + + for (size_t i = 0; i < array_size(months); i++) { + if ((unsigned int)ts < months[i + 1]) { + res->tm_mon = ((i + 2) % 12); + res->tm_mday = 1 + ts - months[i]; + break; + } + } +} + +/* one of the greatest advantages of this logging target: unlike syslog(), + * which is not AS-Safe, we can send crashlogs to syslog here. + */ +static void zlog_5424_sigsafe(struct zlog_target *zt, const char *text, + size_t len) +{ + static const char *const months_3164[12] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + }; + + struct zlt_5424 *zte = container_of(zt, struct zlt_5424, zt); + struct iovec iov[3], *iovp = iov; + char buf[256]; + struct fbuf fbuf = { + .buf = buf, + .pos = buf, + .len = sizeof(buf), + }; + int fd; + intmax_t pid = (intmax_t)getpid(); + struct tm tm; + + switch (zte->fmt) { + case ZLOG_FMT_5424: + gmtime_assafe(time(NULL), &tm); + bprintfrr( + &fbuf, + "<%d>1 %04u-%02u-%02uT%02u:%02u:%02uZ - %s %jd %.*s ", + zte->facility | LOG_CRIT, tm.tm_year + 1900, + tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, + tm.tm_sec, zlog_progname, pid, (int)(zlog_prefixsz - 2), + zlog_prefix); + break; + + case ZLOG_FMT_3164: + case ZLOG_FMT_LOCAL: + /* this will unfortuantely be wrong by the timezone offset + * if the user selected non-UTC. But not much we can do + * about that... + */ + gmtime_assafe(time(NULL), &tm); + bprintfrr(&fbuf, "<%d>%3s %2u %02u:%02u:%02u %s%s[%jd]: ", + zte->facility | LOG_CRIT, months_3164[tm.tm_mon], + tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, + (zte->fmt == ZLOG_FMT_LOCAL) ? "" : "- ", + zlog_progname, pid); + break; + + case ZLOG_FMT_JOURNALD: + bprintfrr(&fbuf, + "PRIORITY=%d\n" + "SYSLOG_FACILITY=%d\n" + "FRR_DAEMON=%s\n" + "MESSAGE=", + LOG_CRIT, zte->facility, zlog_progname); + break; + } + + iovp->iov_base = fbuf.buf; + iovp->iov_len = fbuf.pos - fbuf.buf; + iovp++; + + iovp->iov_base = (char *)text; + iovp->iov_len = len; + iovp++; + + if (zte->use_nl) { + iovp->iov_base = (char *)"\n"; + iovp->iov_len = 1; + iovp++; + } + + fd = atomic_load_explicit(&zte->fd, memory_order_relaxed); + + if (!zte->sa_len) + writev(fd, iov, iovp - iov); + else { + struct msghdr mh = {}; + + mh.msg_name = (struct sockaddr *)&zte->sa; + mh.msg_namelen = zte->sa_len; + mh.msg_iov = iov; + mh.msg_iovlen = iovp - iov; + sendmsg(fd, &mh, 0); + } +} + +/* housekeeping & configuration */ + +void zlog_5424_init(struct zlog_cfg_5424 *zcf) +{ + pthread_mutex_init(&zcf->cfg_mtx, NULL); +} + +static void zlog_5424_target_free(struct zlt_5424 *zlt) +{ + if (!zlt) + return; + + rcu_close(&zlt->head_close, zlt->fd); + rcu_free(MTYPE_LOG_5424, zlt, zt.rcu_head); +} + +void zlog_5424_fini(struct zlog_cfg_5424 *zcf, bool keepopen) +{ + if (keepopen) + zcf->active = NULL; + + if (zcf->active) { + struct zlt_5424 *ztf; + struct zlog_target *zt; + + zt = zlog_target_replace(&zcf->active->zt, NULL); + ztf = container_of(zt, struct zlt_5424, zt); + zlog_5424_target_free(ztf); + } + pthread_mutex_destroy(&zcf->cfg_mtx); +} + +static void zlog_5424_cycle(struct zlog_cfg_5424 *zcf, int fd) +{ + struct zlog_target *old; + struct zlt_5424 *zlt = NULL, *oldt; + + if (fd >= 0) { + struct zlog_target *zt; + + /* all of this is swapped in by zlog_target_replace() below, + * the old target is RCU-freed afterwards. + */ + zt = zlog_target_clone(MTYPE_LOG_5424, &zcf->active->zt, + sizeof(*zlt)); + zlt = container_of(zt, struct zlt_5424, zt); + + zlt->fd = fd; + zlt->kw_version = zcf->kw_version; + zlt->kw_location = zcf->kw_location; + zlt->kw_uid = zcf->kw_uid; + zlt->kw_ec = zcf->kw_ec; + zlt->kw_args = zcf->kw_args; + zlt->use_nl = true; + zlt->facility = zcf->facility; + + /* DGRAM & SEQPACKET = 1 log message per packet */ + zlt->packets = (zcf->sock_type == SOCK_DGRAM) || + (zcf->sock_type == SOCK_SEQPACKET); + zlt->sa = zcf->sa; + zlt->sa_len = zcf->sa_len; + zlt->fmt = zcf->fmt; + zlt->zt.prio_min = zcf->prio_min; + zlt->zt.logfn = zlog_5424; + zlt->zt.logfn_sigsafe = zlog_5424_sigsafe; + + switch (zcf->fmt) { + case ZLOG_FMT_5424: + case ZLOG_FMT_JOURNALD: + zlt->ts_flags = zcf->ts_flags; + zlt->ts_flags &= ZLOG_TS_PREC | ZLOG_TS_UTC; + zlt->ts_flags |= ZLOG_TS_ISO8601; + break; + case ZLOG_FMT_3164: + case ZLOG_FMT_LOCAL: + zlt->ts_flags = zcf->ts_flags & ZLOG_TS_UTC; + if (zlt->packets) + zlt->use_nl = false; + break; + } + } + + old = zcf->active ? &zcf->active->zt : NULL; + old = zlog_target_replace(old, &zlt->zt); + zcf->active = zlt; + + /* oldt->fd == fd happens for zlog_5424_apply_meta() */ + oldt = container_of(old, struct zlt_5424, zt); + if (oldt && oldt->fd != (unsigned int)fd) + rcu_close(&oldt->head_close, oldt->fd); + rcu_free(MTYPE_LOG_5424, oldt, zt.rcu_head); +} + +static int zlog_5424_reconnect(struct thread *t) +{ + struct zlog_cfg_5424 *zcf = THREAD_ARG(t); + int fd = THREAD_FD(t); + char dummy[256]; + ssize_t ret; + + if (zcf->active) { + ret = read(fd, dummy, sizeof(dummy)); + if (ret > 0) { + /* logger is sending us something?!?! */ + thread_add_read(t->master, zlog_5424_reconnect, zcf, fd, + &zcf->t_reconnect); + return 0; + } + + if (ret == 0) + zlog_warn("logging socket %pSE closed by peer", + zcf->filename); + else + zlog_warn("logging socket %pSE error: %m", + zcf->filename); + } + + /* do NOT close() anything here; other threads may still be writing + * and their messages need to be lost rather than end up on a random + * other fd that got reassigned the same number, like a BGP session! + */ + fd = zlog_5424_open(zcf, -1); + + frr_with_mutex (&zcf->cfg_mtx) { + zlog_5424_cycle(zcf, fd); + } + return 0; +} + +static int zlog_5424_unix(struct sockaddr_un *suna, int sock_type) +{ + int fd; + int size = 1 * 1024 * 1024, prev_size; + socklen_t opt_size; + int save_errno; + + fd = socket(AF_UNIX, sock_type, 0); + if (fd < 0) + return -1; + + if (connect(fd, (struct sockaddr *)suna, sizeof(*suna))) { + /* zlog_5424_open() will print the error for connect() */ + save_errno = errno; + close(fd); + errno = save_errno; + return -1; + } + + opt_size = sizeof(prev_size); + if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &prev_size, &opt_size)) + return fd; + + /* setsockopt_so_sendbuf() logs on error; we don't really care that + * much here. Also, never shrink the buffer below the initial size. + */ + while (size > prev_size && + setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)) == -1) + size /= 2; + + return fd; +} + +static int zlog_5424_open(struct zlog_cfg_5424 *zcf, int sock_type) +{ + int fd = -1; + int flags = 0; + int err; + socklen_t optlen; + bool do_chown = false; + bool need_reconnect = false; + static const int unix_types[] = { + SOCK_STREAM, + SOCK_SEQPACKET, + SOCK_DGRAM, + }; + struct sockaddr_un sa; + + zcf->sock_type = -1; + zcf->sa_len = 0; + + switch (zcf->dst) { + case ZLOG_5424_DST_NONE: + break; + + case ZLOG_5424_DST_FD: + fd = dup(zcf->fd); + + optlen = sizeof(sock_type); + if (!getsockopt(fd, SOL_SOCKET, SO_TYPE, &sock_type, &optlen)) { + zcf->sock_type = sock_type; + need_reconnect = (zcf->sock_type != SOCK_DGRAM); + } + break; + + case ZLOG_5424_DST_FIFO: + if (!zcf->filename) + break; + + if (!zcf->file_nocreate) { + frr_with_privs (lib_privs) { + mode_t prevmask; + + prevmask = umask(0777 ^ zcf->file_mode); + err = mkfifo(zcf->filename, 0666); + umask(prevmask); + } + if (err == 0) + do_chown = true; + else if (errno != EEXIST) + break; + } + + flags = O_NONBLOCK; + /* fallthru */ + + case ZLOG_5424_DST_FILE: + if (!zcf->filename) + break; + + frr_with_privs (lib_privs) { + fd = open(zcf->filename, flags | O_WRONLY | O_APPEND | + O_CLOEXEC | O_NOCTTY); + } + if (fd >= 0) + break; + if (zcf->file_nocreate || flags) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "could not open log file %pSE: %m", + zcf->filename); + break; + } + + frr_with_privs (lib_privs) { + mode_t prevmask; + + prevmask = umask(0777 ^ zcf->file_mode); + fd = open(zcf->filename, + O_WRONLY | O_APPEND | O_CLOEXEC | O_NOCTTY | + O_CREAT | O_EXCL, + zcf->file_mode); + umask(prevmask); + } + if (fd >= 0) { + do_chown = true; + break; + } + + frr_with_privs (lib_privs) { + fd = open(zcf->filename, + O_WRONLY | O_APPEND | O_CLOEXEC | O_NOCTTY); + } + if (fd >= 0) + break; + + flog_err_sys(EC_LIB_SYSTEM_CALL, + "could not open or create log file %pSE: %m", + zcf->filename); + break; + + case ZLOG_5424_DST_UNIX: + if (!zcf->filename) + break; + + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + strlcpy(sa.sun_path, zcf->filename, sizeof(sa.sun_path)); + + /* check if ZLOG_5424_DST_FD needs a touch when changing + * something here. the user can pass in a pre-opened unix + * socket through a fd at startup. + */ + frr_with_privs (lib_privs) { + if (sock_type != -1) + fd = zlog_5424_unix(&sa, sock_type); + else { + for (size_t i = 0; i < array_size(unix_types); + i++) { + fd = zlog_5424_unix(&sa, unix_types[i]); + if (fd != -1) { + zcf->sock_type = unix_types[i]; + break; + } + } + } + } + if (fd == -1) { + zcf->sock_type = -1; + + flog_err_sys( + EC_LIB_SYSTEM_CALL, + "could not connect to log unix path %pSE: %m", + zcf->filename); + need_reconnect = true; + } else { + /* datagram sockets are connectionless, restarting + * the receiver may lose some packets but will resume + * working afterwards without any action from us. + */ + need_reconnect = (zcf->sock_type != SOCK_DGRAM); + } + break; + } + + /* viable on both DST_FD and DST_UNIX path */ + if (zcf->sock_type == SOCK_DGRAM) { + zcf->sa_len = sizeof(zcf->sa); + if (getpeername(fd, (struct sockaddr *)&zcf->sa, + &zcf->sa_len)) { + flog_err_sys( + EC_LIB_SYSTEM_CALL, + "could not get remote address for log socket. logging may break if log receiver restarts."); + zcf->sa_len = 0; + } + } + + if (do_chown) { + uid_t uid = zcf->file_uid; + gid_t gid = zcf->file_gid; + + if (uid != (uid_t)-1 || gid != (gid_t)-1) { + frr_with_privs (lib_privs) { + err = fchown(fd, uid, gid); + } + if (err) + flog_err_sys( + EC_LIB_SYSTEM_CALL, + "failed to chown() log file %pSE: %m", + zcf->filename); + } + } + + if (need_reconnect) { + assert(zcf->master); + + if (fd != -1) { + thread_add_read(zcf->master, zlog_5424_reconnect, zcf, + fd, &zcf->t_reconnect); + zcf->reconn_backoff_cur = zcf->reconn_backoff; + + } else { + thread_add_timer_msec(zcf->master, zlog_5424_reconnect, + zcf, zcf->reconn_backoff_cur, + &zcf->t_reconnect); + + zcf->reconn_backoff_cur += zcf->reconn_backoff_cur / 2; + if (zcf->reconn_backoff_cur > zcf->reconn_backoff_max) + zcf->reconn_backoff_cur = + zcf->reconn_backoff_max; + } + } + + return fd; +} + +bool zlog_5424_apply_dst(struct zlog_cfg_5424 *zcf) +{ + int fd = -1; + + thread_cancel(&zcf->t_reconnect); + + if (zcf->prio_min != ZLOG_DISABLED) + fd = zlog_5424_open(zcf, -1); + + frr_with_mutex (&zcf->cfg_mtx) { + zlog_5424_cycle(zcf, fd); + } + return fd != -1; +} + + +bool zlog_5424_apply_meta(struct zlog_cfg_5424 *zcf) +{ + frr_with_mutex (&zcf->cfg_mtx) { + if (zcf->active) + zlog_5424_cycle(zcf, zcf->active->fd); + } + + return true; +} + +void zlog_5424_state(struct zlog_cfg_5424 *zcf, size_t *lost_msgs, + int *last_errno, bool *stale_errno, struct timeval *err_ts) +{ + if (lost_msgs) + *lost_msgs = + zcf->active + ? atomic_load_explicit(&zcf->active->lost_msgs, + memory_order_relaxed) + : 0; + if (last_errno) + *last_errno = zcf->active ? zcf->active->last_err : 0; + if (stale_errno) + *stale_errno = zcf->active ? !zcf->active->current_err : 0; + if (err_ts && zcf->active) + *err_ts = zcf->active->last_err_ts; +} + +struct rcu_close_rotate { + struct rcu_head_close head_close; + struct rcu_head head_self; +}; + +bool zlog_5424_rotate(struct zlog_cfg_5424 *zcf) +{ + struct rcu_close_rotate *rcr; + int fd; + + frr_with_mutex (&zcf->cfg_mtx) { + if (!zcf->active) + return true; + + thread_cancel(&zcf->t_reconnect); + + /* need to retain the socket type because it also influences + * other fields (packets) and we can't atomically swap these + * out. But we really want the atomic swap so we neither lose + * nor duplicate log messages, particularly for file targets. + * + * (zlog_5424_apply_dst / zlog_target_replace will cause + * duplicate log messages if another thread logs something + * while we're right in the middle of swapping out the log + * target) + */ + fd = zlog_5424_open(zcf, zcf->sock_type); + if (fd < 0) + return false; + + fd = atomic_exchange_explicit(&zcf->active->fd, + (uint_fast32_t)fd, + memory_order_relaxed); + } + + rcr = XCALLOC(MTYPE_LOG_5424_ROTATE, sizeof(*rcr)); + rcu_close(&rcr->head_close, fd); + rcu_free(MTYPE_LOG_5424_ROTATE, rcr, head_self); + + return true; +} diff --git a/lib/zlog_5424.h b/lib/zlog_5424.h new file mode 100644 index 0000000000..b4a12ccbff --- /dev/null +++ b/lib/zlog_5424.h @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2021 David Lamparter, for NetDEF, Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _FRR_ZLOG_5424_H +#define _FRR_ZLOG_5424_H + +#include + +#include "typerb.h" +#include "zlog.h" +#include "zlog_targets.h" +#include "qobj.h" + +struct thread; +struct thread_master; + +enum zlog_5424_dst { + /* can be used to disable a target temporarily */ + ZLOG_5424_DST_NONE = 0, + + ZLOG_5424_DST_FD, + ZLOG_5424_DST_FILE, + ZLOG_5424_DST_FIFO, + ZLOG_5424_DST_UNIX, + +#define ZLOG_5424_DST_LAST ZLOG_5424_DST_UNIX +}; + +enum zlog_5424_format { + ZLOG_FMT_5424 = 0, + ZLOG_FMT_3164, + ZLOG_FMT_LOCAL, + ZLOG_FMT_JOURNALD, + +#define ZLOG_FMT_LAST ZLOG_FMT_JOURNALD +}; + +/* actual RCU'd logging backend */ +struct zlt_5424; + +struct zlog_cfg_5424 { + struct zlt_5424 *active; + + pthread_mutex_t cfg_mtx; + + /* general settings for all dsts */ + int facility; + int prio_min; + bool kw_version; + bool kw_location; + bool kw_uid; + bool kw_ec; + bool kw_args; + + uint32_t ts_flags; + + enum zlog_5424_format fmt; + + /* destination specifics */ + enum zlog_5424_dst dst; + + /* pre-opened FD. not the actual fd we log to */ + int fd; + + /* file, fifo, unix */ + bool file_nocreate; + + const char *filename; + mode_t file_mode; + /* -1 = no change */ + uid_t file_uid; + gid_t file_gid; + + /* remaining fields are internally used & updated by the 5424 + * code - *not* config. don't set these. + */ + + /* sockets only - read handler to reconnect on errors */ + struct thread_master *master; + struct thread *t_reconnect; + unsigned int reconn_backoff, reconn_backoff_cur, reconn_backoff_max; + int sock_type; + struct sockaddr_storage sa; + socklen_t sa_len; +}; + +/* these don't do malloc/free to allow using a static global */ +extern void zlog_5424_init(struct zlog_cfg_5424 *zcf); + +/* keepopen = true => for shutdown, just zap the config, keep logging */ +extern void zlog_5424_fini(struct zlog_cfg_5424 *zcf, bool keepopen); + +/* apply metadata/config changes */ +extern bool zlog_5424_apply_meta(struct zlog_cfg_5424 *zcf); + +/* apply changes requiring (re-)opening the destination + * + * also does log cycling/rotate & applies _meta at the same time + */ +extern bool zlog_5424_apply_dst(struct zlog_cfg_5424 *zcf); + +/* SIGHUP log rotation */ +extern bool zlog_5424_rotate(struct zlog_cfg_5424 *zcf); + +extern void zlog_5424_state(struct zlog_cfg_5424 *zcf, size_t *lost_msgs, + int *last_errno, bool *stale_errno, + struct timeval *err_ts); + +/* this is the dynamically allocated "variant" */ +PREDECL_RBTREE_UNIQ(targets); + +struct zlog_cfg_5424_user { + struct targets_item targets_item; + char *name; + + struct zlog_cfg_5424 cfg; + + char *envvar; + + /* non-const, always same as cfg.filename */ + char *filename; + + /* uid/gid strings to write back out in show config */ + char *file_user; + char *file_group; + + bool reconf_dst; + bool reconf_meta; + + int unix_special; + + QOBJ_FIELDS; +}; + +DECLARE_QOBJ_TYPE(zlog_cfg_5424_user); + +extern void log_5424_cmd_init(void); + +#endif /* _FRR_ZLOG_5424_H */ diff --git a/lib/zlog_5424_cli.c b/lib/zlog_5424_cli.c new file mode 100644 index 0000000000..dd8dbfaffd --- /dev/null +++ b/lib/zlog_5424_cli.c @@ -0,0 +1,1006 @@ +/* + * Copyright (C) 2021 David Lamparter for NetDEF, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "zebra.h" +#include "zlog_5424.h" + +#include +#include +#include + +#include "lib/command.h" +#include "lib/libfrr.h" +#include "lib/log_vty.h" + +DEFINE_MTYPE_STATIC(LOG, LOG_5424_CONFIG, "extended syslog config"); +DEFINE_MTYPE_STATIC(LOG, LOG_5424_DATA, "extended syslog config items"); + +static int target_cmp(const struct zlog_cfg_5424_user *a, + const struct zlog_cfg_5424_user *b) +{ + return strcmp(a->name, b->name); +} + +DECLARE_RBTREE_UNIQ(targets, struct zlog_cfg_5424_user, targets_item, + target_cmp); +DEFINE_QOBJ_TYPE(zlog_cfg_5424_user); + +static struct targets_head targets = INIT_RBTREE_UNIQ(targets); +static struct thread_master *log_5424_master; + +static void clear_dst(struct zlog_cfg_5424_user *cfg); + +struct log_option { + const char *name; + ptrdiff_t offs; + bool dflt; +}; + +/* clang-format off */ +static struct log_option log_opts[] = { + { "code-location", offsetof(struct zlog_cfg_5424, kw_location) }, + { "version", offsetof(struct zlog_cfg_5424, kw_version) }, + { "unique-id", offsetof(struct zlog_cfg_5424, kw_uid), true }, + { "error-category", offsetof(struct zlog_cfg_5424, kw_ec), true }, + { "format-args", offsetof(struct zlog_cfg_5424, kw_args) }, + {}, +}; + +#define DFLT_TS_FLAGS (6 | ZLOG_TS_UTC) +#define DFLT_FACILITY LOG_DAEMON +#define DFLT_PRIO_MIN LOG_DEBUG +/* clang-format on */ + +enum unix_special { + SPECIAL_NONE = 0, + SPECIAL_SYSLOG, + SPECIAL_JOURNALD, +}; + +static struct zlog_cfg_5424_user *log_5424_alloc(const char *name) +{ + struct zlog_cfg_5424_user *cfg; + + cfg = XCALLOC(MTYPE_LOG_5424_CONFIG, sizeof(*cfg)); + cfg->name = XSTRDUP(MTYPE_LOG_5424_DATA, name); + + cfg->cfg.master = log_5424_master; + cfg->cfg.kw_location = true; + cfg->cfg.kw_version = false; + cfg->cfg.facility = DFLT_FACILITY; + cfg->cfg.prio_min = DFLT_PRIO_MIN; + cfg->cfg.ts_flags = DFLT_TS_FLAGS; + clear_dst(cfg); + + for (struct log_option *opt = log_opts; opt->name; opt++) { + bool *ptr = (bool *)(((char *)&cfg->cfg) + opt->offs); + *ptr = opt->dflt; + } + + zlog_5424_init(&cfg->cfg); + + QOBJ_REG(cfg, zlog_cfg_5424_user); + targets_add(&targets, cfg); + return cfg; +} + +static void log_5424_free(struct zlog_cfg_5424_user *cfg, bool keepopen) +{ + targets_del(&targets, cfg); + QOBJ_UNREG(cfg); + + zlog_5424_fini(&cfg->cfg, keepopen); + clear_dst(cfg); + + XFREE(MTYPE_LOG_5424_DATA, cfg->filename); + XFREE(MTYPE_LOG_5424_DATA, cfg->name); + XFREE(MTYPE_LOG_5424_CONFIG, cfg); +} + +static void clear_dst(struct zlog_cfg_5424_user *cfg) +{ + XFREE(MTYPE_LOG_5424_DATA, cfg->filename); + cfg->cfg.filename = cfg->filename; + + XFREE(MTYPE_LOG_5424_DATA, cfg->file_user); + XFREE(MTYPE_LOG_5424_DATA, cfg->file_group); + XFREE(MTYPE_LOG_5424_DATA, cfg->envvar); + + cfg->cfg.fd = -1; + cfg->cfg.file_uid = -1; + cfg->cfg.file_gid = -1; + cfg->cfg.file_mode = LOGFILE_MASK & 0666; + cfg->cfg.file_nocreate = false; + cfg->cfg.dst = ZLOG_5424_DST_NONE; +} + +static int reconf_dst(struct zlog_cfg_5424_user *cfg, struct vty *vty) +{ + if (!cfg->reconf_dst && !cfg->reconf_meta && vty->type != VTY_FILE) + vty_out(vty, + "%% Changes will be applied when exiting this config block\n"); + + cfg->reconf_dst = true; + return CMD_SUCCESS; +} + +static int reconf_meta(struct zlog_cfg_5424_user *cfg, struct vty *vty) +{ + if (!cfg->reconf_dst && !cfg->reconf_meta && vty->type != VTY_FILE) + vty_out(vty, + "%% Changes will be applied when exiting this config block\n"); + + cfg->reconf_meta = true; + return CMD_SUCCESS; +} + +static int reconf_clear_dst(struct zlog_cfg_5424_user *cfg, struct vty *vty) +{ + if (cfg->cfg.dst == ZLOG_5424_DST_NONE) + return CMD_SUCCESS; + + clear_dst(cfg); + return reconf_dst(cfg, vty); +} + +#ifndef VTYSH_EXTRACT_PL +#include "lib/zlog_5424_cli_clippy.c" +#endif + +DEFPY_NOSH(log_5424_target, + log_5424_target_cmd, + "log extended-syslog EXTLOGNAME", + "Logging control\n" + "Extended RFC5424 syslog (including file targets)\n" + "Name identifying this syslog target\n") +{ + struct zlog_cfg_5424_user *cfg, ref; + + ref.name = (char *)extlogname; + cfg = targets_find(&targets, &ref); + + if (!cfg) + cfg = log_5424_alloc(extlogname); + + VTY_PUSH_CONTEXT(EXTLOG_NODE, cfg); + return CMD_SUCCESS; +} + +DEFPY(no_log_5424_target, + no_log_5424_target_cmd, + "no log extended-syslog EXTLOGNAME", + NO_STR + "Logging control\n" + "Extended RFC5424 syslog (including file targets)\n" + "Name identifying this syslog target\n") +{ + struct zlog_cfg_5424_user *cfg, ref; + + ref.name = (char *)extlogname; + cfg = targets_find(&targets, &ref); + + if (!cfg) { + vty_out(vty, "%% No extended syslog target named \"%s\"\n", + extlogname); + return CMD_WARNING; + } + + log_5424_free(cfg, false); + return CMD_SUCCESS; +} + +/* "format $fmt" */ +#define FORMAT_HELP \ + "Select log message formatting\n" \ + "RFC3164 (legacy) syslog\n" \ + "RFC5424 (modern) syslog, supports structured data (default)\n" \ + "modified RFC3164 without hostname for local syslogd (/dev/log)\n" \ + "journald (systemd log) native format\n" \ + /* end */ + +static enum zlog_5424_format log_5424_fmt(const char *fmt, + enum zlog_5424_format dflt) +{ + if (!fmt) + return dflt; + else if (!strcmp(fmt, "rfc5424")) + return ZLOG_FMT_5424; + else if (!strcmp(fmt, "rfc3164")) + return ZLOG_FMT_3164; + else if (!strcmp(fmt, "local-syslogd")) + return ZLOG_FMT_LOCAL; + else if (!strcmp(fmt, "journald")) + return ZLOG_FMT_JOURNALD; + + return dflt; +} + +DEFPY(log_5424_destination_file, + log_5424_destination_file_cmd, + "[no] destination file$type PATH " + "[create$create [{user WORD|group WORD|mode PERMS}]" + "|no-create$nocreate] " + "[format $fmt]", + NO_STR + "Log destination setup\n" + "Log to file\n" + "Path to destination\n" + "Create file if it does not exist\n" + "Set file owner\n" + "User name\n" + "Set file group\n" + "Group name\n" + "Set permissions\n" + "File permissions (octal)\n" + "Do not create file if it does not exist\n" + FORMAT_HELP) +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + enum zlog_5424_dst dst; + bool reconf = true, warn_perm = false; + char *prev_user, *prev_group; + mode_t perm_val = LOGFILE_MASK & 0666; + enum zlog_5424_format fmtv; + + if (no) + return reconf_clear_dst(cfg, vty); + + fmtv = log_5424_fmt(fmt, ZLOG_FMT_5424); + + if (perms) { + char *errp = (char *)perms; + + perm_val = strtoul(perms, &errp, 8); + if (*errp || errp == perms || perm_val == 0 || + (perm_val & ~0666)) { + vty_out(vty, "%% Invalid permissions value \"%s\"\n", + perms); + return CMD_WARNING; + } + } + + dst = (strcmp(type, "fifo") == 0) ? ZLOG_5424_DST_FIFO + : ZLOG_5424_DST_FILE; + + if (cfg->filename && !strcmp(path, cfg->filename) && + dst == cfg->cfg.dst && cfg->cfg.active && cfg->cfg.fmt == fmtv) + reconf = false; + + /* keep for compare below */ + prev_user = cfg->file_user; + prev_group = cfg->file_group; + cfg->file_user = NULL; + cfg->file_group = NULL; + + clear_dst(cfg); + + cfg->filename = XSTRDUP(MTYPE_LOG_5424_DATA, path); + cfg->cfg.dst = dst; + cfg->cfg.filename = cfg->filename; + cfg->cfg.fmt = fmtv; + + if (nocreate) + cfg->cfg.file_nocreate = true; + else { + if (user) { + struct passwd *pwent; + + warn_perm |= (prev_user && strcmp(user, prev_user)); + cfg->file_user = XSTRDUP(MTYPE_LOG_5424_DATA, user); + + errno = 0; + pwent = getpwnam(user); + if (!pwent) + vty_out(vty, + "%% Could not look up user \"%s\" (%s), file owner will be left untouched!\n", + user, + errno ? safe_strerror(errno) + : "No entry by this user name"); + else + cfg->cfg.file_uid = pwent->pw_uid; + } + if (group) { + struct group *grent; + + warn_perm |= (prev_group && strcmp(group, prev_group)); + cfg->file_group = XSTRDUP(MTYPE_LOG_5424_DATA, group); + + errno = 0; + grent = getgrnam(group); + if (!grent) + vty_out(vty, + "%% Could not look up group \"%s\" (%s), file group will be left untouched!\n", + group, + errno ? safe_strerror(errno) + : "No entry by this group name"); + else + cfg->cfg.file_gid = grent->gr_gid; + } + } + XFREE(MTYPE_LOG_5424_DATA, prev_user); + XFREE(MTYPE_LOG_5424_DATA, prev_group); + + if (cfg->cfg.file_uid != (uid_t)-1 || cfg->cfg.file_gid != (gid_t)-1) { + struct stat st; + + if (stat(cfg->filename, &st) == 0) { + warn_perm |= (st.st_uid != cfg->cfg.file_uid); + warn_perm |= (st.st_gid != cfg->cfg.file_gid); + } + } + if (warn_perm) + vty_out(vty, + "%% Warning: ownership and permission bits are only applied when creating\n" + "%% log files. Use system tools to change existing files.\n" + "%% FRR may also be missing necessary privileges to set these.\n"); + + if (reconf) + return reconf_dst(cfg, vty); + + return CMD_SUCCESS; +} + +/* FIFOs are for legacy /dev/log implementations; using this is very much not + * recommended since it can unexpectedly block in logging calls. Also the fd + * would need to be reopened when the process at the other end restarts. None + * of this is handled - use at your own caution. It's _HIDDEN for a purpose. + */ +ALIAS_HIDDEN(log_5424_destination_file, + log_5424_destination_fifo_cmd, + "[no] destination fifo$type PATH " + "[create$create [{owner WORD|group WORD|permissions PERMS}]" + "|no-create$nocreate] " + "[format $fmt]", + NO_STR + "Log destination setup\n" + "Log to filesystem FIFO\n" + "Path to destination\n" + "Create file if it does not exist\n" + "Set file owner\n" + "User name\n" + "Set file group\n" + "Group name\n" + "Set permissions\n" + "File permissions (octal)\n" + "Do not create file if it does not exist\n" + FORMAT_HELP) + +static int dst_unix(struct vty *vty, const char *no, const char *path, + enum zlog_5424_format fmt, enum unix_special special) +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + + if (no) + return reconf_clear_dst(cfg, vty); + + cfg->unix_special = special; + + if (cfg->cfg.dst == ZLOG_5424_DST_UNIX && cfg->filename && + !strcmp(path, cfg->filename) && cfg->cfg.active && + cfg->cfg.fmt == fmt) + return CMD_SUCCESS; + + clear_dst(cfg); + + cfg->filename = XSTRDUP(MTYPE_LOG_5424_DATA, path); + cfg->cfg.dst = ZLOG_5424_DST_UNIX; + cfg->cfg.filename = cfg->filename; + cfg->cfg.fmt = fmt; + + cfg->cfg.reconn_backoff = 25; + cfg->cfg.reconn_backoff_cur = 25; + cfg->cfg.reconn_backoff_max = 10000; + return reconf_dst(cfg, vty); +} + +DEFPY(log_5424_destination_unix, + log_5424_destination_unix_cmd, + "[no] destination unix PATH " + "[format $fmt]", + NO_STR + "Log destination setup\n" + "Log to unix socket\n" + "Unix socket path\n" + FORMAT_HELP) +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + enum zlog_5424_format fmtv = log_5424_fmt(fmt, ZLOG_FMT_5424); + + return dst_unix(vty, no, path, fmtv, SPECIAL_NONE); +} + +DEFPY(log_5424_destination_journald, + log_5424_destination_journald_cmd, + "[no] destination journald", + NO_STR + "Log destination setup\n" + "Log directly to systemd's journald\n") +{ + return dst_unix(vty, no, "/run/systemd/journal/socket", + ZLOG_FMT_JOURNALD, SPECIAL_JOURNALD); +} + +#if defined(__FreeBSD_version) && (__FreeBSD_version >= 1200061) +#define ZLOG_FMT_DEV_LOG ZLOG_FMT_5424 +#elif defined(__NetBSD_Version__) && (__NetBSD_Version__ >= 500000000) +#define ZLOG_FMT_DEV_LOG ZLOG_FMT_5424 +#else +#define ZLOG_FMT_DEV_LOG ZLOG_FMT_LOCAL +#endif + +DEFPY(log_5424_destination_syslog, + log_5424_destination_syslog_cmd, + "[no] destination syslog [supports-rfc5424]$supp5424", + NO_STR + "Log destination setup\n" + "Log directly to syslog\n" + "Use RFC5424 format (please refer to documentation)\n") +{ + int format = supp5424 ? ZLOG_FMT_5424 : ZLOG_FMT_DEV_LOG; + + /* unfortunately, there is no way to detect 5424 support */ + return dst_unix(vty, no, "/dev/log", format, SPECIAL_SYSLOG); +} + +/* could add something like + * "destination $proto (1-65535)$port" + * here, but there are 2 reasons not to do that: + * + * - each FRR daemon would open its own connection, there's no system level + * aggregation. That's the system's syslogd's job. It likely also + * supports directing & filtering log messages with configurable rules. + * - we're likely not going to support DTLS or TLS for more secure logging; + * adding this would require a considerable amount of additional config + * and an entire TLS library to begin with. A proper syslogd implements + * all of this, why reinvent the wheel? + */ + +DEFPY(log_5424_destination_fd, + log_5424_destination_fd_cmd, + "[no] destination |stdout$fd1|stderr$fd2>" + "[format $fmt]", + NO_STR + "Log destination setup\n" + "Log to pre-opened file descriptor\n" + "File descriptor number (must be open at startup)\n" + "Read file descriptor number from environment variable\n" + "Environment variable name\n" + "Log to standard output\n" + "Log to standard error output\n" + FORMAT_HELP) +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + bool envvar_problem = false; + enum zlog_5424_format fmtv; + + if (no) + return reconf_clear_dst(cfg, vty); + + fmtv = log_5424_fmt(fmt, ZLOG_FMT_5424); + + if (envvar) { + char *envval; + + envval = getenv(envvar); + if (!envval) + envvar_problem = true; + else { + char *errp = envval; + + fd = strtoul(envval, &errp, 0); + if (errp == envval || *errp) + envvar_problem = true; + } + + if (envvar_problem) + fd = -1; + } else if (fd1) + fd = 1; + else if (fd2) + fd = 2; + + if (cfg->cfg.dst == ZLOG_5424_DST_FD && cfg->cfg.fd == fd && + cfg->cfg.active && cfg->cfg.fmt == fmtv) + return CMD_SUCCESS; + + clear_dst(cfg); + + cfg->cfg.dst = ZLOG_5424_DST_FD; + cfg->cfg.fd = fd; + cfg->cfg.fmt = fmtv; + if (envvar) + cfg->envvar = XSTRDUP(MTYPE_LOG_5424_DATA, envvar); + + if (envvar_problem) + vty_out(vty, + "%% environment variable \"%s\" not present or invalid.\n", + envvar); + if (!frr_is_startup_fd(fd)) + vty_out(vty, + "%% file descriptor %d was not open when this process was started\n", + (int)fd); + if (envvar_problem || !frr_is_startup_fd(fd)) + vty_out(vty, + "%% configuration will be saved but has no effect currently\n"); + + return reconf_dst(cfg, vty); +} + +DEFPY(log_5424_destination_none, + log_5424_destination_none_cmd, + "[no] destination [none]", + NO_STR + "Log destination setup\n" + "Deconfigure destination\n") +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + + return reconf_clear_dst(cfg, vty); +} + +/* end of destinations */ + +DEFPY(log_5424_prio, + log_5424_prio_cmd, + "[no] priority $levelarg", + NO_STR + "Set minimum message priority to include for this target\n" + LOG_LEVEL_DESC) +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + int prio_min = log_level_match(levelarg); + + if (prio_min == cfg->cfg.prio_min) + return CMD_SUCCESS; + + cfg->cfg.prio_min = prio_min; + return reconf_meta(cfg, vty); +} + +DEFPY(log_5424_facility, + log_5424_facility_cmd, + "[no] facility $facilityarg", + NO_STR + "Set syslog facility to use\n" + LOG_FACILITY_DESC) +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + int facility = facility_match(facilityarg); + + if (cfg->cfg.facility == facility) + return CMD_SUCCESS; + + cfg->cfg.facility = facility; + return reconf_meta(cfg, vty); +} + +DEFPY(log_5424_meta, + log_5424_meta_cmd, + "[no] structured-data $option", + NO_STR + "Select structured data (key/value pairs) to include in each message\n" + "FRR source code location\n" + "FRR version\n" + "Unique message identifier (XXXXX-XXXXX)\n" + "Error category (EC numeric)\n" + "Individual formatted log message arguments\n") +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + bool val = !no, *ptr; + struct log_option *opt = log_opts; + + while (opt->name && strcmp(opt->name, option)) + opt++; + if (!opt->name) + return CMD_WARNING; + + ptr = (bool *)(((char *)&cfg->cfg) + opt->offs); + if (*ptr == val) + return CMD_SUCCESS; + + *ptr = val; + return reconf_meta(cfg, vty); +} + +DEFPY(log_5424_ts_prec, + log_5424_ts_prec_cmd, + "[no] timestamp precision (0-9)", + NO_STR + "Timestamp options\n" + "Number of sub-second digits to include\n" + "Number of sub-second digits to include\n") +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + uint32_t ts_flags = cfg->cfg.ts_flags; + + ts_flags &= ~ZLOG_TS_PREC; + if (no) + ts_flags |= DFLT_TS_FLAGS & ZLOG_TS_PREC; + else + ts_flags |= precision; + + if (ts_flags == cfg->cfg.ts_flags) + return CMD_SUCCESS; + + cfg->cfg.ts_flags = ts_flags; + return reconf_meta(cfg, vty); +} + +DEFPY(log_5424_ts_local, + log_5424_ts_local_cmd, + "[no] timestamp local-time", + NO_STR + "Timestamp options\n" + "Use local system time zone rather than UTC\n") +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + uint32_t ts_flags = cfg->cfg.ts_flags; + + ts_flags &= ~ZLOG_TS_UTC; + if (no) + ts_flags |= DFLT_TS_FLAGS & ZLOG_TS_UTC; + else + ts_flags |= (~DFLT_TS_FLAGS) & ZLOG_TS_UTC; + + if (ts_flags == cfg->cfg.ts_flags) + return CMD_SUCCESS; + + cfg->cfg.ts_flags = ts_flags; + return reconf_meta(cfg, vty); +} + +static int log_5424_node_exit(struct vty *vty) +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + + if ((cfg->reconf_dst || cfg->reconf_meta) && vty->type != VTY_FILE) + vty_out(vty, "%% applying changes.\n"); + + if (cfg->reconf_dst) + zlog_5424_apply_dst(&cfg->cfg); + else if (cfg->reconf_meta) + zlog_5424_apply_meta(&cfg->cfg); + + cfg->reconf_dst = cfg->reconf_meta = false; + return 1; +} + +static int log_5424_config_write(struct vty *vty) +{ + struct zlog_cfg_5424_user *cfg; + + frr_each (targets, &targets, cfg) { + const char *fmt_str = ""; + + vty_out(vty, "log extended %s\n", cfg->name); + + switch (cfg->cfg.fmt) { + case ZLOG_FMT_5424: + fmt_str = " format rfc5424"; + break; + case ZLOG_FMT_3164: + fmt_str = " format rfc3164"; + break; + case ZLOG_FMT_LOCAL: + fmt_str = " format local-syslogd"; + break; + case ZLOG_FMT_JOURNALD: + fmt_str = " format journald"; + break; + } + + switch (cfg->cfg.dst) { + case ZLOG_5424_DST_NONE: + vty_out(vty, " ! no destination configured\n"); + break; + + case ZLOG_5424_DST_FD: + if (cfg->cfg.fmt == ZLOG_FMT_5424) + fmt_str = ""; + + if (cfg->envvar) + vty_out(vty, " destination fd envvar %s%s\n", + cfg->envvar, fmt_str); + else if (cfg->cfg.fd == 1) + vty_out(vty, " destination stdout%s\n", + fmt_str); + else if (cfg->cfg.fd == 2) + vty_out(vty, " destination stderr%s\n", + fmt_str); + else + vty_out(vty, " destination fd %d%s\n", + cfg->cfg.fd, fmt_str); + break; + + case ZLOG_5424_DST_FILE: + case ZLOG_5424_DST_FIFO: + if (cfg->cfg.fmt == ZLOG_FMT_5424) + fmt_str = ""; + + vty_out(vty, " destination %s %s", + (cfg->cfg.dst == ZLOG_5424_DST_FIFO) ? "fifo" + : "file", + cfg->filename); + + if (cfg->cfg.file_nocreate) + vty_out(vty, " no-create"); + else if (cfg->file_user || cfg->file_group || + cfg->cfg.file_mode != (LOGFILE_MASK & 0666)) { + vty_out(vty, " create"); + + if (cfg->file_user) + vty_out(vty, " user %s", + cfg->file_user); + if (cfg->file_group) + vty_out(vty, " group %s", + cfg->file_group); + if (cfg->cfg.file_mode != (LOGFILE_MASK & 0666)) + vty_out(vty, " mode %04o", + cfg->cfg.file_mode); + } + vty_out(vty, "%s\n", fmt_str); + break; + + case ZLOG_5424_DST_UNIX: + switch (cfg->unix_special) { + case SPECIAL_NONE: + vty_out(vty, " destination unix %s%s\n", + cfg->filename, fmt_str); + break; + case SPECIAL_SYSLOG: + if (cfg->cfg.fmt == ZLOG_FMT_DEV_LOG) + vty_out(vty, " destination syslog\n"); + else + vty_out(vty, + " destination syslog supports-rfc5424\n"); + break; + case SPECIAL_JOURNALD: + vty_out(vty, " destination journald\n"); + break; + } + break; + } + + if (cfg->cfg.prio_min != LOG_DEBUG) + vty_out(vty, " priority %s\n", + zlog_priority_str(cfg->cfg.prio_min)); + if (cfg->cfg.facility != DFLT_FACILITY) + vty_out(vty, " facility %s\n", + facility_name(cfg->cfg.facility)); + + for (struct log_option *opt = log_opts; opt->name; opt++) { + bool *ptr = (bool *)(((char *)&cfg->cfg) + opt->offs); + + if (*ptr != opt->dflt) + vty_out(vty, " %sstructured-data %s\n", + *ptr ? "" : "no ", opt->name); + } + + if ((cfg->cfg.ts_flags ^ DFLT_TS_FLAGS) & ZLOG_TS_PREC) + vty_out(vty, " timestamp precision %u\n", + cfg->cfg.ts_flags & ZLOG_TS_PREC); + + if ((cfg->cfg.ts_flags ^ DFLT_TS_FLAGS) & ZLOG_TS_UTC) { + if (cfg->cfg.ts_flags & ZLOG_TS_UTC) + vty_out(vty, " no timestamp local-time\n"); + else + vty_out(vty, " timestamp local-time\n"); + } + + vty_out(vty, "!\n"); + } + return 0; +} + +static int log_5424_show(struct vty *vty) +{ + struct zlog_cfg_5424_user *cfg; + + frr_each (targets, &targets, cfg) { + vty_out(vty, "\nExtended log target %pSQq\n", cfg->name); + + switch (cfg->cfg.dst) { + case ZLOG_5424_DST_NONE: + vty_out(vty, + " Inactive (no destination configured)\n"); + break; + + case ZLOG_5424_DST_FD: + if (cfg->envvar) + vty_out(vty, + " logging to fd %d from environment variable %pSE\n", + cfg->cfg.fd, cfg->envvar); + else if (cfg->cfg.fd == 1) + vty_out(vty, " logging to stdout\n"); + else if (cfg->cfg.fd == 2) + vty_out(vty, " logging to stderr\n"); + else + vty_out(vty, " logging to fd %d\n", + cfg->cfg.fd); + break; + + case ZLOG_5424_DST_FILE: + case ZLOG_5424_DST_FIFO: + case ZLOG_5424_DST_UNIX: + vty_out(vty, " logging to %s: %pSE\n", + (cfg->cfg.dst == ZLOG_5424_DST_FIFO) ? "fifo" + : (cfg->cfg.dst == ZLOG_5424_DST_UNIX) + ? "unix socket" + : "file", + cfg->filename); + break; + } + + vty_out(vty, " log level: %s, facility: %s\n", + zlog_priority_str(cfg->cfg.prio_min), + facility_name(cfg->cfg.facility)); + + bool any_meta = false, first = true; + + for (struct log_option *opt = log_opts; opt->name; opt++) { + bool *ptr = (bool *)(((char *)&cfg->cfg) + opt->offs); + + any_meta |= *ptr; + } + + if (!any_meta) + continue; + + switch (cfg->cfg.fmt) { + case ZLOG_FMT_5424: + case ZLOG_FMT_JOURNALD: + vty_out(vty, " structured data: "); + + for (struct log_option *opt = log_opts; opt->name; + opt++) { + bool *ptr = (bool *)(((char *)&cfg->cfg) + + opt->offs); + + if (*ptr) { + vty_out(vty, "%s%s", first ? "" : ", ", + opt->name); + first = false; + } + } + break; + + default: + vty_out(vty, + " structured data is not supported by the selected format\n"); + break; + } + + vty_out(vty, "\n"); + + size_t lost_msgs; + int last_errno; + bool stale_errno; + struct timeval err_ts; + int64_t since; + + zlog_5424_state(&cfg->cfg, &lost_msgs, &last_errno, + &stale_errno, &err_ts); + vty_out(vty, " number of lost messages: %zu\n", lost_msgs); + + if (last_errno == 0) + since = 0; + else + since = monotime_since(&err_ts, NULL); + vty_out(vty, + " last error: %s (%lld.%06llds ago, currently %s)\n", + last_errno ? safe_strerror(last_errno) : "none", + since / 1000000LL, since % 1000000LL, + stale_errno ? "OK" : "erroring"); + } + return 0; +} + +static struct cmd_node extlog_node = { + .name = "extended", + .node = EXTLOG_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-ext-log)# ", + + .config_write = log_5424_config_write, + .node_exit = log_5424_node_exit, +}; + +static void log_5424_autocomplete(vector comps, struct cmd_token *token) +{ + struct zlog_cfg_5424_user *cfg; + + frr_each (targets, &targets, cfg) + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, cfg->name)); +} + +static const struct cmd_variable_handler log_5424_var_handlers[] = { + {.tokenname = "EXTLOGNAME", .completions = log_5424_autocomplete}, + {.completions = NULL}, +}; + +void log_5424_cmd_init(void) +{ + hook_register(zlog_cli_show, log_5424_show); + + cmd_variable_handler_register(log_5424_var_handlers); + + /* CLI commands. */ + install_node(&extlog_node); + install_default(EXTLOG_NODE); + + install_element(CONFIG_NODE, &log_5424_target_cmd); + install_element(CONFIG_NODE, &no_log_5424_target_cmd); + + install_element(EXTLOG_NODE, &log_5424_destination_file_cmd); + install_element(EXTLOG_NODE, &log_5424_destination_fifo_cmd); + install_element(EXTLOG_NODE, &log_5424_destination_unix_cmd); + install_element(EXTLOG_NODE, &log_5424_destination_journald_cmd); + install_element(EXTLOG_NODE, &log_5424_destination_syslog_cmd); + install_element(EXTLOG_NODE, &log_5424_destination_fd_cmd); + + install_element(EXTLOG_NODE, &log_5424_meta_cmd); + install_element(EXTLOG_NODE, &log_5424_prio_cmd); + install_element(EXTLOG_NODE, &log_5424_facility_cmd); + install_element(EXTLOG_NODE, &log_5424_ts_prec_cmd); + install_element(EXTLOG_NODE, &log_5424_ts_local_cmd); +} + +/* hooks */ + +static int log_5424_early_init(struct thread_master *master); +static int log_5424_rotate(void); +static int log_5424_fini(void); + +__attribute__((_CONSTRUCTOR(475))) static void zlog_5424_startup_init(void) +{ + hook_register(frr_early_init, log_5424_early_init); + hook_register(zlog_rotate, log_5424_rotate); + hook_register(frr_fini, log_5424_fini); +} + +static int log_5424_early_init(struct thread_master *master) +{ + log_5424_master = master; + + return 0; +} + +static int log_5424_rotate(void) +{ + struct zlog_cfg_5424_user *cfg; + + frr_each (targets, &targets, cfg) + if (!zlog_5424_rotate(&cfg->cfg)) + zlog_err( + "log rotation on extended log target %s failed", + cfg->name); + + return 0; +} + +static int log_5424_fini(void) +{ + struct zlog_cfg_5424_user *cfg; + + while ((cfg = targets_pop(&targets))) + log_5424_free(cfg, true); + + log_5424_master = NULL; + + return 0; +} -- 2.39.5