lib: implement `terminal monitor` for vtysh

Adds a new logging target that sends log messages to vtysh.

Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
This commit is contained in:
David Lamparter 2019-12-04 08:10:42 +01:00 committed by David Lamparter
parent b2dde56b2c
commit 0798d2760d
5 changed files with 349 additions and 33 deletions

View 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 \

View File

@ -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);
}

View File

@ -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;

245
lib/zlog_live.c Normal file
View File

@ -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);
}

53
lib/zlog_live.h Normal file
View File

@ -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 */