/*! \file tdef_vty.c * Implementation to configure osmo_tdef Tnnn timers from VTY configuration. */ /* (C) 2018-2019 by sysmocom - s.f.m.c. GmbH * * Author: Neels Hofmeyr * * All Rights Reserved * * 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. If not, see . * * SPDX-License-Identifier: GPLv2+ */ #include #include #include #include #include #include #include #include #include /*! \addtogroup Tdef_VTY * * VTY API for \ref Tdef. * * @{ * \file tdef_vty.c */ /*! Parse an argument like "1234", "T1234", "t1234", or "X1234", "x1234", as from OSMO_TDEF_VTY_ARG_T. * \param[in] vty VTY context for vty_out() of error messages. * \param[in] tdefs Array of timer definitions to look up T timer. * \param[in] T_str Argument string. It is not validated, expected to be checked by VTY input. * \return the corresponding osmo_tdef entry from the tdefs array, or NULL if no such entry exists. */ struct osmo_tdef *osmo_tdef_vty_parse_T_arg(struct vty *vty, struct osmo_tdef *tdefs, const char *T_str) { int l; int T; struct osmo_tdef *t; const char *T_nr_str; int sign = 1; if (!tdefs) { vty_out(vty, "%% Error: no timers found%s", VTY_NEWLINE); return NULL; } T_nr_str = T_str; if (T_nr_str[0] == 't' || T_nr_str[0] == 'T') { sign = 1; T_nr_str++; } else if (T_nr_str[0] == 'x' || T_nr_str[0] == 'X') { T_nr_str++; sign = -1; } /* Make sure to disallow any characters changing the signedness of the parsed int */ if (T_nr_str[0] < '0' || T_nr_str[0] > '9') { vty_out(vty, "%% Invalid T timer argument (should be 'T1234' or 'X1234'): '%s'%s", T_str, VTY_NEWLINE); return NULL; } if (osmo_str_to_int(&l, T_nr_str, 10, 0, INT_MAX)) { vty_out(vty, "%% Invalid T timer argument (should be 'T1234' or 'X1234'): '%s'%s", T_str, VTY_NEWLINE); return NULL; } T = l * sign; t = osmo_tdef_get_entry(tdefs, T); if (!t) vty_out(vty, "%% No such timer: " OSMO_T_FMT "%s", OSMO_T_FMT_ARGS(T), VTY_NEWLINE); return t; } /*! Parse an argument of the form "(0-2147483647|default)", as from OSMO_TDEF_VTY_ARG_VAL. * \param[in] val_arg Argument string (not format checked). * \param[in] default_val Value to return in case of val_arg being "default". * \return Parsed value or default_val. */ unsigned long osmo_tdef_vty_parse_val_arg(const char *val_arg, unsigned long default_val) { if (!strcmp(val_arg, "default")) return default_val; return atoll(val_arg); } /*! Apply a timer configuration from VTY argument strings. * Employ both osmo_tdef_vty_parse_T_arg() and osmo_tdef_vty_parse_val_arg() to configure a T timer in an array of * tdefs. Evaluate two arguments, a "T1234" argument and a "(0-2147483647|default)" argument, as from * OSMO_TDEF_VTY_ARGS. If the T timer given in the first argument is found in tdefs, set it to the value given in the * second argument. * \param[in] vty VTY context for vty_out() of error messages. * \param[in] tdefs Array of timer definitions to look up T timer. * \param[in] args Array of string arguments like { "T1234", "23" }. * \return CMD_SUCCESS, or CMD_WARNING if no such timer is found in tdefs. */ int osmo_tdef_vty_set_cmd(struct vty *vty, struct osmo_tdef *tdefs, const char **args) { unsigned long new_val; const char *T_arg = args[0]; const char *val_arg = args[1]; struct osmo_tdef *t = osmo_tdef_vty_parse_T_arg(vty, tdefs, T_arg); if (!t) return CMD_WARNING; new_val = osmo_tdef_vty_parse_val_arg(val_arg, t->default_val); if (!osmo_tdef_val_in_range(t, new_val)) { char range_str[64]; osmo_tdef_range_str_buf(range_str, sizeof(range_str), t); vty_out(vty, "%% Timer " OSMO_T_FMT " value %lu is out of range %s%s", OSMO_T_FMT_ARGS(t->T), new_val, range_str, VTY_NEWLINE); return CMD_WARNING; } t->val = new_val; return CMD_SUCCESS; } /*! Output one or all timers to the VTY, as for a VTY command like 'show timer [TNNNN]'. * If T_arg is NULL, print all timers in tdefs to the VTY. * If T_arg is not NULL, employ osmo_tdef_vty_parse_T_arg() to select one timer from tdefs and print only that to the * VTY. * \param[in] vty VTY context for vty_out() of error messages. * \param[in] tdefs Array of timer definitions. * \param[in] T_arg Argument string like "T1234", or NULL. * \param[in] prefix_fmt Arbitrary string to start each line with, with variable printf like arguments. * \return CMD_SUCCESS, or CMD_WARNING if no such timer is found in tdefs. */ int osmo_tdef_vty_show_cmd(struct vty *vty, struct osmo_tdef *tdefs, const char *T_arg, const char *prefix_fmt, ...) { va_list va; if (T_arg) { struct osmo_tdef *t = osmo_tdef_vty_parse_T_arg(vty, tdefs, T_arg); if (!t) return CMD_WARNING; va_start(va, prefix_fmt); osmo_tdef_vty_out_one_va(vty, t, prefix_fmt, va); va_end(va); } else { va_start(va, prefix_fmt); osmo_tdef_vty_out_all_va(vty, tdefs, prefix_fmt, va); va_end(va); } return CMD_SUCCESS; } /*! Write to VTY the current status of one timer. * \param[in] vty VTY context for vty_out(). * \param[in] t The timer to print. * \param[in] prefix_fmt Arbitrary string to start each line with, with variable vprintf like arguments. * \param[in] va va_list instance. As always, call va_start() before, and va_end() after this call. */ void osmo_tdef_vty_out_one_va(struct vty *vty, struct osmo_tdef *t, const char *prefix_fmt, va_list va) { char range_str[64]; if (!t) { vty_out(vty, "%% Error: no such timer%s", VTY_NEWLINE); return; } if (prefix_fmt) vty_out_va(vty, prefix_fmt, va); vty_out(vty, OSMO_T_FMT " = %lu", OSMO_T_FMT_ARGS(t->T), t->val); if (t->unit != OSMO_TDEF_CUSTOM) vty_out(vty, " %s", osmo_tdef_unit_name(t->unit)); vty_out(vty, "\t%s (default: %lu", t->desc, t->default_val); if (t->unit != OSMO_TDEF_CUSTOM) vty_out(vty, " %s", osmo_tdef_unit_name(t->unit)); if (t->min_val || t->max_val) { osmo_tdef_range_str_buf(range_str, sizeof(range_str), t); vty_out(vty, ", range: %s", range_str); } vty_out(vty, ")%s", VTY_NEWLINE); } /*! Write to VTY the current status of one timer. * \param[in] vty VTY context for vty_out(). * \param[in] t The timer to print. * \param[in] prefix_fmt Arbitrary string to start each line with, with variable printf like arguments. */ void osmo_tdef_vty_out_one(struct vty *vty, struct osmo_tdef *t, const char *prefix_fmt, ...) { va_list va; va_start(va, prefix_fmt); osmo_tdef_vty_out_one_va(vty, t, prefix_fmt, va); va_end(va); } /*! Write to VTY the current status of all given timers. * \param[in] vty VTY context for vty_out(). * \param[in] tdefs Array of timers to print, ended with a fully zero-initialized entry. * \param[in] prefix_fmt Arbitrary string to start each line with, with variable vprintf like arguments. * \param[in] va va_list instance. As always, call va_start() before, and va_end() after this call. */ void osmo_tdef_vty_out_all_va(struct vty *vty, struct osmo_tdef *tdefs, const char *prefix_fmt, va_list va) { struct osmo_tdef *t; if (!tdefs) { vty_out(vty, "%% Error: no such timers%s", VTY_NEWLINE); return; } osmo_tdef_for_each(t, tdefs) { va_list va2; va_copy(va2, va); osmo_tdef_vty_out_one_va(vty, t, prefix_fmt, va); va_end(va2); } } /*! Write to VTY the current status of all given timers. * \param[in] vty VTY context for vty_out(). * \param[in] tdefs Array of timers to print, ended with a fully zero-initialized entry. * \param[in] prefix_fmt Arbitrary string to start each line with, with variable printf like arguments. */ void osmo_tdef_vty_out_all(struct vty *vty, struct osmo_tdef *tdefs, const char *prefix_fmt, ...) { va_list va; va_start(va, prefix_fmt); osmo_tdef_vty_out_all_va(vty, tdefs, prefix_fmt, va); va_end(va); } /*! Write current timer configuration arguments to the vty. Skip all entries that reflect their default value. * The passed prefix string must contain both necessary indent and the VTY command the specific implementation is using. * See tdef_vty_config_subnode_test.c and tdef_vty_dynamic_test.c for examples. * \param[in] vty VTY context. * \param[in] tdefs Array of timers to print, ended with a fully zero-initialized entry. * \param[in] prefix_fmt Arbitrary string to start each line with, with variable printf like arguments. */ void osmo_tdef_vty_write(struct vty *vty, struct osmo_tdef *tdefs, const char *prefix_fmt, ...) { va_list va; struct osmo_tdef *t; osmo_tdef_for_each(t, tdefs) { if (t->val == t->default_val) continue; if (prefix_fmt && *prefix_fmt) { va_start(va, prefix_fmt); vty_out_va(vty, prefix_fmt, va); va_end(va); } vty_out(vty, OSMO_T_FMT " %lu%s", OSMO_T_FMT_ARGS(t->T), t->val, VTY_NEWLINE); } } /*! Singleton Tnnn groups definition as set by osmo_tdef_vty_groups_init(). */ static struct osmo_tdef_group *global_tdef_groups; DEFUN(show_timer, show_timer_cmd, "DYNAMIC", "DYNAMIC") /* show timer [(alpha|beta|gamma)] [TNNNN] */ { const char *group_arg = argc > 0 ? argv[0] : NULL; const char *T_arg = argc > 1 ? argv[1] : NULL; struct osmo_tdef_group *g; /* The argument should be either "tea" or "software", but the VTY also allows partial arguments * like "softw" or "t" (which can also be ambiguous). */ osmo_tdef_groups_for_each(g, global_tdef_groups) { if (!group_arg || osmo_str_startswith(g->name, group_arg)) osmo_tdef_vty_show_cmd(vty, g->tdefs, T_arg, "%s: ", g->name); } return CMD_SUCCESS; } DEFUN(cfg_timer, cfg_timer_cmd, "DYNAMIC", "DYNAMIC") /* show timer [(alpha|beta|gamma)] [TNNNN] [(<0-2147483647>|default)] */ { const char *group_arg; const char **timer_args; struct osmo_tdef *tdefs = NULL; struct osmo_tdef_group *g = NULL; /* If any arguments are missing, redirect to 'show' */ if (argc < 3) return show_timer(self, vty, argc, argv); /* If all arguments are passed, this is configuring a timer. */ group_arg = argv[0]; timer_args = argv + 1; osmo_tdef_groups_for_each(g, global_tdef_groups) { if (strcmp(g->name, group_arg)) continue; if (tdefs) { vty_out(vty, "%% Error: ambiguous timer group match%s", VTY_NEWLINE); return CMD_WARNING; } tdefs = g->tdefs; } return osmo_tdef_vty_set_cmd(vty, tdefs, timer_args); } static char *add_group_args(void *talloc_ctx, char *dest) { struct osmo_tdef_group *g; osmo_talloc_asprintf(talloc_ctx, dest, "[("); osmo_tdef_groups_for_each(g, global_tdef_groups) { osmo_talloc_asprintf(talloc_ctx, dest, "%s%s", (g == global_tdef_groups) ? "" : "|", g->name); } osmo_talloc_asprintf(talloc_ctx, dest, ")]"); return dest; } static char *add_group_docs(void *talloc_ctx, char *dest) { struct osmo_tdef_group *g; osmo_tdef_groups_for_each(g, global_tdef_groups) { osmo_talloc_asprintf(talloc_ctx, dest, "%s\n", g->desc); } return dest; } static char *timer_command_string(const char *prefix, const char *suffix) { char *dest = NULL; osmo_talloc_asprintf(tall_vty_cmd_ctx, dest, "%s ", prefix); dest = add_group_args(tall_vty_cmd_ctx, dest); osmo_talloc_asprintf(tall_vty_cmd_ctx, dest, " %s", suffix); return dest; } static char *timer_doc_string(const char *prefix, const char *suffix) { char *dest = NULL; osmo_talloc_asprintf(tall_vty_cmd_ctx, dest, "%s ", prefix); dest = add_group_docs(tall_vty_cmd_ctx, dest); osmo_talloc_asprintf(tall_vty_cmd_ctx, dest, " %s", suffix); return dest; } /*! Convenience implementation for keeping a fixed set of timer groups in a program. * Install a 'timer [(group|names|...)] [TNNN] [(|default)]' command under the given parent_node, * and install a 'show timer...' command on VIEW_NODE and ENABLE_NODE. * For a usage example, see \ref tdef_test_config_root.c. * The given timer definitions group is stored in a global pointer, so this can be done only once per main() scope. * It would also be possible to have distinct timer groups on separate VTY subnodes, with a "manual" implementation, but * not with this API. * \param[in] parent_cfg_node VTY node at which to add the timer configuration commands, e.g. CONFIG_NODE. * \param[in] groups Global timer groups definition. */ void osmo_tdef_vty_groups_init(unsigned int parent_cfg_node, struct osmo_tdef_group *groups) { struct osmo_tdef_group *g; OSMO_ASSERT(!global_tdef_groups); global_tdef_groups = groups; osmo_tdef_groups_for_each(g, global_tdef_groups) osmo_tdefs_reset(g->tdefs); show_timer_cmd.string = timer_command_string("show timer", OSMO_TDEF_VTY_ARG_T_OPTIONAL); show_timer_cmd.doc = timer_doc_string(SHOW_STR "Show timers\n", OSMO_TDEF_VTY_DOC_T); cfg_timer_cmd.string = timer_command_string("timer", OSMO_TDEF_VTY_ARG_SET_OPTIONAL); cfg_timer_cmd.doc = timer_doc_string("Configure or show timers\n", OSMO_TDEF_VTY_DOC_SET); install_lib_element_ve(&show_timer_cmd); install_lib_element(parent_cfg_node, &cfg_timer_cmd); } /*! Write the global osmo_tdef_group configuration to VTY, as previously passed to osmo_tdef_vty_groups_init(). * \param[in] vty VTY context. * \param[in] indent String to print before each line. */ void osmo_tdef_vty_groups_write(struct vty *vty, const char *indent) { struct osmo_tdef_group *g; osmo_tdef_groups_for_each(g, global_tdef_groups) osmo_tdef_vty_write(vty, g->tdefs, "%stimer %s ", indent ? : "", g->name); } /*! @} */