diff options
Diffstat (limited to 'lib/zlog_5424_cli.c')
-rw-r--r-- | lib/zlog_5424_cli.c | 1006 |
1 files changed, 1006 insertions, 0 deletions
diff --git a/lib/zlog_5424_cli.c b/lib/zlog_5424_cli.c new file mode 100644 index 0000000..dd8dbfa --- /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; +} |