diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/command.h | 1 | ||||
| -rw-r--r-- | lib/log_vty.c | 3 | ||||
| -rw-r--r-- | lib/subdir.am | 4 | ||||
| -rw-r--r-- | lib/zlog_5424.c | 1146 | ||||
| -rw-r--r-- | lib/zlog_5424.h | 152 | ||||
| -rw-r--r-- | lib/zlog_5424_cli.c | 1006 |
6 files changed, 2312 insertions, 0 deletions
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 <sys/un.h> +#include <syslog.h> + +#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 <sys/stat.h> + +#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 <sys/types.h> +#include <pwd.h> +#include <grp.h> + +#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 <rfc3164|rfc5424|local-syslogd|journald>$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 <rfc3164|rfc5424|local-syslogd|journald>$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 <rfc3164|rfc5424|local-syslogd|journald>$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 <rfc3164|rfc5424|local-syslogd|journald>$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 <udp|tcp>$proto <A.B.C.D|X:X::X:X> (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 <fd <(0-63)$fd|envvar WORD>|stdout$fd1|stderr$fd2>" + "[format <rfc3164|rfc5424|local-syslogd|journald>$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 <emergencies|alerts|critical|errors|warnings|notifications|informational|debugging>$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 <kern|user|mail|daemon|auth|syslog|lpr|news|uucp|cron|local0|local1|local2|local3|local4|local5|local6|local7>$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 <code-location|version|unique-id|error-category|format-args>$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; +} |
