]> git.puffer.fish Git - mirror/frr.git/commitdiff
lib: implement `terminal monitor` for vtysh
authorDavid Lamparter <equinox@diac24.net>
Wed, 4 Dec 2019 07:10:42 +0000 (08:10 +0100)
committerDavid Lamparter <equinox@opensourcerouting.org>
Mon, 28 Feb 2022 12:28:43 +0000 (13:28 +0100)
Adds a new logging target that sends log messages to vtysh.

Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
lib/subdir.am
lib/vty.c
lib/vty.h
lib/zlog_live.c [new file with mode: 0644]
lib/zlog_live.h [new file with mode: 0644]

index 648ab7f14a1fa00c2de454d25cc465a98c4517ee..64f86b670ae12ca7a81f8c4b3ffb8b4fddfd8030 100644 (file)
@@ -113,6 +113,7 @@ lib_libfrr_la_SOURCES = \
        lib/zlog.c \
        lib/zlog_5424.c \
        lib/zlog_5424_cli.c \
+       lib/zlog_live.c \
        lib/zlog_targets.c \
        lib/printf/printf-pos.c \
        lib/printf/vfprintf.c \
@@ -287,6 +288,7 @@ pkginclude_HEADERS += \
        lib/zebra.h \
        lib/zlog.h \
        lib/zlog_5424.h \
+       lib/zlog_live.h \
        lib/zlog_targets.h \
        lib/pbr.h \
        lib/routing_nb.h \
index ad9cd719e067430d082d8375c02fc56e172eb830..6aa8a0bbb506b95ed6154c39d51d07ca5340ccdb 100644 (file)
--- a/lib/vty.c
+++ b/lib/vty.c
@@ -1314,8 +1314,6 @@ static void vty_read(struct thread *thread)
                                vty_event(VTY_READ, vty);
                                return;
                        }
-                       vty->monitor = 0; /* disable monitoring to avoid
-                                            infinite recursion */
                        flog_err(
                                EC_LIB_SOCKET,
                                "%s: read error on vty client fd %d, closing: %s",
@@ -1529,8 +1527,6 @@ static void vty_flush(struct thread *thread)
                        vty->lines >= 0 ? vty->lines : vty->height, erase, 0);
        switch (flushrc) {
        case BUFFER_ERROR:
-               vty->monitor =
-                       0; /* disable monitoring to avoid infinite recursion */
                zlog_info("buffer_flush failed on vty client fd %d/%d, closing",
                          vty->fd, vty->wfd);
                buffer_reset(vty->lbuf);
@@ -2078,8 +2074,6 @@ static int vtysh_flush(struct vty *vty)
                vty_event(VTYSH_WRITE, vty);
                break;
        case BUFFER_ERROR:
-               vty->monitor =
-                       0; /* disable monitoring to avoid infinite recursion */
                flog_err(EC_LIB_SOCKET, "%s: write error to fd %d, closing",
                         __func__, vty->fd);
                buffer_reset(vty->lbuf);
@@ -2119,8 +2113,6 @@ static void vtysh_read(struct thread *thread)
                                vty_event(VTYSH_READ, vty);
                                return;
                        }
-                       vty->monitor = 0; /* disable monitoring to avoid
-                                            infinite recursion */
                        flog_err(
                                EC_LIB_SOCKET,
                                "%s: read failed on vtysh client fd %d, closing: %s",
@@ -2254,6 +2246,7 @@ void vty_close(struct vty *vty)
                close(vty->pass_fd);
                vty->pass_fd = -1;
        }
+       zlog_live_close(&vty->live_log);
 
        /* Flush buffer. */
        buffer_flush_all(vty->obuf, vty->wfd);
@@ -2755,8 +2748,9 @@ DEFUN_NOSH (config_who,
        struct vty *v;
 
        frr_each (vtys, vty_sessions, v)
-               vty_out(vty, "%svty[%d] connected from %s.\n",
-                       v->config ? "*" : " ", v->fd, v->address);
+               vty_out(vty, "%svty[%d] connected from %s%s.\n",
+                       v->config ? "*" : " ", v->fd, v->address,
+                       zlog_live_is_null(&v->live_log) ? "" : ", live log");
        return CMD_SUCCESS;
 }
 
@@ -2949,35 +2943,56 @@ DEFUN (no_service_advanced_vty,
        return CMD_SUCCESS;
 }
 
-DEFUN_NOSH (terminal_monitor,
-       terminal_monitor_cmd,
-       "terminal monitor",
-       "Set terminal line parameters\n"
-       "Copy debug output to the current terminal line\n")
+DEFUN_NOSH(terminal_monitor,
+          terminal_monitor_cmd,
+          "terminal monitor [detach]",
+          "Set terminal line parameters\n"
+          "Copy debug output to the current terminal line\n"
+          "Keep logging feed open independent of VTY session\n")
 {
-       vty->monitor = 1;
+       int fd_ret = -1;
+
+       if (vty->type != VTY_SHELL_SERV) {
+               vty_out(vty, "%% not supported\n");
+               return CMD_WARNING;
+       }
+
+       if (argc == 3) {
+               struct zlog_live_cfg detach_log = {};
+
+               zlog_live_open(&detach_log, LOG_DEBUG, &fd_ret);
+               zlog_live_disown(&detach_log);
+       } else
+               zlog_live_open(&vty->live_log, LOG_DEBUG, &fd_ret);
+
+       if (fd_ret == -1) {
+               vty_out(vty, "%% error opening live log: %m\n");
+               return CMD_WARNING;
+       }
+
+       vty_pass_fd(vty, fd_ret);
        return CMD_SUCCESS;
 }
 
-DEFUN_NOSH (terminal_no_monitor,
-       terminal_no_monitor_cmd,
-       "terminal no monitor",
-       "Set terminal line parameters\n"
-       NO_STR
-       "Copy debug output to the current terminal line\n")
+DEFUN_NOSH(no_terminal_monitor,
+          no_terminal_monitor_cmd,
+          "no terminal monitor",
+          NO_STR
+          "Set terminal line parameters\n"
+          "Copy debug output to the current terminal line\n")
 {
-       vty->monitor = 0;
+       zlog_live_close(&vty->live_log);
        return CMD_SUCCESS;
 }
 
-DEFUN_NOSH (no_terminal_monitor,
-       no_terminal_monitor_cmd,
-       "no terminal monitor",
-       NO_STR
-       "Set terminal line parameters\n"
-       "Copy debug output to the current terminal line\n")
+DEFUN_NOSH(terminal_no_monitor,
+          terminal_no_monitor_cmd,
+          "terminal no monitor",
+          "Set terminal line parameters\n"
+          NO_STR
+          "Copy debug output to the current terminal line\n")
 {
-       return terminal_no_monitor(self, vty, argc, argv);
+       return no_terminal_monitor(self, vty, argc, argv);
 }
 
 
index 92fbb468a975538c421c462f1dd19f339d905f4b..e42a3b210fef4ea650621edd06cd49c82ed15a03 100644 (file)
--- a/lib/vty.h
+++ b/lib/vty.h
@@ -34,6 +34,7 @@
 #include "qobj.h"
 #include "compiler.h"
 #include "northbound.h"
+#include "zlog_live.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -175,6 +176,9 @@ struct vty {
        /* CLI command return value (likely CMD_SUCCESS) when pass_fd != -1 */
        uint8_t pass_fd_status[4];
 
+       /* live logging target / terminal monitor */
+       struct zlog_live_cfg live_log;
+
        /* IAC handling: was the last character received the
           IAC (interpret-as-command) escape character (and therefore the next
           character will be the command code)?  Refer to Telnet RFC 854. */
@@ -198,9 +202,6 @@ struct vty {
        /* Configure lines. */
        int lines;
 
-       /* Terminal monitor. */
-       int monitor;
-
        /* Read and write thread. */
        struct thread *t_read;
        struct thread *t_write;
diff --git a/lib/zlog_live.c b/lib/zlog_live.c
new file mode 100644 (file)
index 0000000..fbe0e5e
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 2019-22  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.
+ */
+
+#include "zebra.h"
+
+#include "zlog_live.h"
+
+#include "memory.h"
+#include "frrcu.h"
+#include "zlog.h"
+#include "printfrr.h"
+
+DEFINE_MTYPE_STATIC(LOG, LOG_LIVE, "log vtysh live target");
+
+enum {
+       STATE_NORMAL = 0,
+       STATE_FD_DEAD,
+       STATE_DISOWNED,
+};
+
+struct zlt_live {
+       struct zlog_target zt;
+
+       atomic_uint_fast32_t fd;
+       struct rcu_head_close head_close;
+       struct rcu_head head_self;
+
+       atomic_uint_fast32_t state;
+};
+
+static void zlog_live(struct zlog_target *zt, struct zlog_msg *msgs[],
+                     size_t nmsgs)
+{
+       struct zlt_live *zte = container_of(zt, struct zlt_live, zt);
+       struct zlog_live_hdr hdrs[nmsgs], *hdr = hdrs;
+       struct mmsghdr mmhs[nmsgs], *mmh = mmhs;
+       struct iovec iovs[nmsgs * 3], *iov = iovs;
+       struct timespec ts;
+       size_t i, textlen;
+       int fd;
+       uint_fast32_t state;
+
+       fd = atomic_load_explicit(&zte->fd, memory_order_relaxed);
+
+       if (fd < 0)
+               return;
+
+       memset(mmhs, 0, sizeof(mmhs));
+       memset(hdrs, 0, sizeof(hdrs));
+
+       for (i = 0; i < nmsgs; i++) {
+               const struct fmt_outpos *argpos;
+               size_t n_argpos, arghdrlen;
+               struct zlog_msg *msg = msgs[i];
+               int prio = zlog_msg_prio(msg);
+
+               if (prio > zt->prio_min)
+                       continue;
+
+               zlog_msg_args(msg, &arghdrlen, &n_argpos, &argpos);
+
+               mmh->msg_hdr.msg_iov = iov;
+
+               iov->iov_base = hdr;
+               iov->iov_len = sizeof(*hdr);
+               iov++;
+
+               if (n_argpos) {
+                       iov->iov_base = (char *)argpos;
+                       iov->iov_len = sizeof(*argpos) * n_argpos;
+                       iov++;
+               }
+
+               iov->iov_base = (char *)zlog_msg_text(msg, &textlen);
+               iov->iov_len = textlen;
+               iov++;
+
+               zlog_msg_tsraw(msg, &ts);
+
+               hdr->ts_sec = ts.tv_sec;
+               hdr->ts_nsec = ts.tv_nsec;
+               hdr->prio = zlog_msg_prio(msg);
+               hdr->flags = 0;
+               hdr->textlen = textlen;
+               hdr->arghdrlen = arghdrlen;
+               hdr->n_argpos = n_argpos;
+
+               mmh->msg_hdr.msg_iovlen = iov - mmh->msg_hdr.msg_iov;
+               mmh++;
+               hdr++;
+       }
+
+       size_t msgtotal = mmh - mmhs;
+       ssize_t sent;
+
+       for (size_t msgpos = 0; msgpos < msgtotal; msgpos += sent) {
+               sent = sendmmsg(fd, mmhs + msgpos, msgtotal - msgpos, 0);
+
+               if (sent <= 0)
+                       goto out_err;
+       }
+       return;
+
+out_err:
+       fd = atomic_exchange_explicit(&zte->fd, -1, memory_order_relaxed);
+       if (fd < 0)
+               return;
+
+       rcu_close(&zte->head_close, fd);
+       zlog_target_replace(zt, NULL);
+
+       state = STATE_NORMAL;
+       atomic_compare_exchange_strong_explicit(
+               &zte->state, &state, STATE_FD_DEAD, memory_order_relaxed,
+               memory_order_relaxed);
+       if (state == STATE_DISOWNED)
+               rcu_free(MTYPE_LOG_LIVE, zte, head_self);
+}
+
+static void zlog_live_sigsafe(struct zlog_target *zt, const char *text,
+                             size_t len)
+{
+       struct zlt_live *zte = container_of(zt, struct zlt_live, zt);
+       struct zlog_live_hdr hdr[1];
+       struct iovec iovs[2], *iov = iovs;
+       struct timespec ts;
+       int fd;
+
+       fd = atomic_load_explicit(&zte->fd, memory_order_relaxed);
+       if (fd < 0)
+               return;
+
+       clock_gettime(CLOCK_MONOTONIC, &ts);
+
+       hdr->ts_sec = ts.tv_sec;
+       hdr->ts_nsec = ts.tv_nsec;
+       hdr->prio = LOG_CRIT;
+       hdr->flags = 0;
+       hdr->textlen = len;
+       hdr->n_argpos = 0;
+
+       iov->iov_base = (char *)hdr;
+       iov->iov_len = sizeof(hdr);
+       iov++;
+
+       iov->iov_base = (char *)text;
+       iov->iov_len = len;
+       iov++;
+
+       writev(fd, iovs, iov - iovs);
+}
+
+void zlog_live_open(struct zlog_live_cfg *cfg, int prio_min, int *other_fd)
+{
+       int sockets[2];
+       struct zlt_live *zte;
+       struct zlog_target *zt;
+
+       if (cfg->target)
+               zlog_live_close(cfg);
+
+       *other_fd = -1;
+       if (prio_min == ZLOG_DISABLED)
+               return;
+
+       /* the only reason for SEQPACKET here is getting close notifications.
+        * otherwise if you open a bunch of vtysh connections with live logs
+        * and close them all, the fds will stick around until we get an error
+        * when trying to log something to them at some later point -- which
+        * eats up fds and might be *much* later for some daemons.
+        */
+       if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets) < 0) {
+               if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets) < 0) {
+                       zlog_warn("%% could not open socket pair: %m");
+                       return;
+               }
+       } else
+               /* SEQPACKET only: try to zap read direction */
+               shutdown(sockets[0], SHUT_RD);
+
+       *other_fd = sockets[1];
+
+       zt = zlog_target_clone(MTYPE_LOG_LIVE, NULL, sizeof(*zte));
+       zte = container_of(zt, struct zlt_live, zt);
+       cfg->target = zte;
+
+       zte->fd = sockets[0];
+       zte->zt.prio_min = prio_min;
+       zte->zt.logfn = zlog_live;
+       zte->zt.logfn_sigsafe = zlog_live_sigsafe;
+
+       zlog_target_replace(NULL, zt);
+}
+
+void zlog_live_close(struct zlog_live_cfg *cfg)
+{
+       struct zlt_live *zte;
+       int fd;
+
+       if (!cfg->target)
+               return;
+
+       zte = cfg->target;
+       cfg->target = NULL;
+
+       fd = atomic_exchange_explicit(&zte->fd, -1, memory_order_relaxed);
+
+       if (fd >= 0) {
+               rcu_close(&zte->head_close, fd);
+               zlog_target_replace(&zte->zt, NULL);
+       }
+       rcu_free(MTYPE_LOG_LIVE, zte, head_self);
+}
+
+void zlog_live_disown(struct zlog_live_cfg *cfg)
+{
+       struct zlt_live *zte;
+       uint_fast32_t state;
+
+       if (!cfg->target)
+               return;
+
+       zte = cfg->target;
+       cfg->target = NULL;
+
+       state = STATE_NORMAL;
+       atomic_compare_exchange_strong_explicit(
+               &zte->state, &state, STATE_DISOWNED, memory_order_relaxed,
+               memory_order_relaxed);
+       if (state == STATE_FD_DEAD)
+               rcu_free(MTYPE_LOG_LIVE, zte, head_self);
+}
diff --git a/lib/zlog_live.h b/lib/zlog_live.h
new file mode 100644 (file)
index 0000000..c948bae
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2019-22  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_LIVE_H
+#define _FRR_ZLOG_LIVE_H
+
+#include "printfrr.h"
+
+struct zlog_live_hdr {
+       uint64_t ts_sec;
+       uint32_t ts_nsec;
+       uint32_t prio;
+       uint32_t flags;
+       uint32_t textlen;
+
+       uint32_t arghdrlen;
+       uint32_t n_argpos;
+       struct fmt_outpos argpos[0];
+};
+
+struct zlt_live;
+
+struct zlog_live_cfg {
+       struct zlt_live *target;
+
+       /* nothing else here */
+};
+
+extern void zlog_live_open(struct zlog_live_cfg *cfg, int prio_min,
+                          int *other_fd);
+
+static inline bool zlog_live_is_null(struct zlog_live_cfg *cfg)
+{
+       return cfg->target == NULL;
+}
+
+extern void zlog_live_close(struct zlog_live_cfg *cfg);
+extern void zlog_live_disown(struct zlog_live_cfg *cfg);
+
+#endif /* _FRR_ZLOG_5424_H */