Neels Hofmeyr | 0fd615f | 2019-01-26 20:36:12 +0100 | [diff] [blame] | 1 | /*! \file tdef_vty.c |
| 2 | * Implementation to configure osmo_tdef Tnnn timers from VTY configuration. |
| 3 | */ |
| 4 | /* (C) 2018-2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> |
| 5 | * |
| 6 | * Author: Neels Hofmeyr <neels@hofmeyr.de> |
| 7 | * |
| 8 | * All Rights Reserved |
| 9 | * |
| 10 | * This program is free software; you can redistribute it and/or modify |
| 11 | * it under the terms of the GNU Affero General Public License as published by |
| 12 | * the Free Software Foundation; either version 3 of the License, or |
| 13 | * (at your option) any later version. |
| 14 | * |
| 15 | * This program is distributed in the hope that it will be useful, |
| 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 18 | * GNU Affero General Public License for more details. |
| 19 | * |
| 20 | * You should have received a copy of the GNU Affero General Public License |
| 21 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 22 | * |
| 23 | */ |
| 24 | |
| 25 | #include <string.h> |
| 26 | #include <stdlib.h> |
| 27 | #include <errno.h> |
| 28 | #include <limits.h> |
| 29 | |
| 30 | #include <osmocom/vty/vty.h> |
| 31 | #include <osmocom/vty/command.h> |
| 32 | #include <osmocom/vty/tdef_vty.h> |
| 33 | #include <osmocom/core/tdef.h> |
Neels Hofmeyr | 5734bff | 2019-02-21 02:27:48 +0100 | [diff] [blame] | 34 | #include <osmocom/core/fsm.h> |
Neels Hofmeyr | 0fd615f | 2019-01-26 20:36:12 +0100 | [diff] [blame] | 35 | |
| 36 | /*! \addtogroup Tdef_VTY |
| 37 | * |
| 38 | * VTY API for \ref Tdef. |
| 39 | * |
| 40 | * @{ |
| 41 | * \file tdef_vty.c |
| 42 | */ |
| 43 | |
Neels Hofmeyr | 5734bff | 2019-02-21 02:27:48 +0100 | [diff] [blame] | 44 | /*! Parse an argument like "1234", "T1234", "t1234", or "X1234", "x1234", as from OSMO_TDEF_VTY_ARG_T. |
Neels Hofmeyr | 0fd615f | 2019-01-26 20:36:12 +0100 | [diff] [blame] | 45 | * \param[in] vty VTY context for vty_out() of error messages. |
| 46 | * \param[in] tdefs Array of timer definitions to look up T timer. |
| 47 | * \param[in] T_str Argument string. It is not validated, expected to be checked by VTY input. |
| 48 | * \return the corresponding osmo_tdef entry from the tdefs array, or NULL if no such entry exists. |
| 49 | */ |
| 50 | struct osmo_tdef *osmo_tdef_vty_parse_T_arg(struct vty *vty, struct osmo_tdef *tdefs, const char *T_str) |
| 51 | { |
| 52 | long l; |
| 53 | int T; |
| 54 | struct osmo_tdef *t; |
| 55 | char *endptr; |
| 56 | const char *T_nr_str; |
Neels Hofmeyr | 5734bff | 2019-02-21 02:27:48 +0100 | [diff] [blame] | 57 | int sign = 1; |
Neels Hofmeyr | 0fd615f | 2019-01-26 20:36:12 +0100 | [diff] [blame] | 58 | |
| 59 | if (!tdefs) { |
| 60 | vty_out(vty, "%% Error: no timers found%s", VTY_NEWLINE); |
| 61 | return NULL; |
| 62 | } |
| 63 | |
| 64 | T_nr_str = T_str; |
Neels Hofmeyr | 5734bff | 2019-02-21 02:27:48 +0100 | [diff] [blame] | 65 | if (T_nr_str[0] == 't' || T_nr_str[0] == 'T') { |
| 66 | sign = 1; |
Neels Hofmeyr | 0fd615f | 2019-01-26 20:36:12 +0100 | [diff] [blame] | 67 | T_nr_str++; |
Neels Hofmeyr | 5734bff | 2019-02-21 02:27:48 +0100 | [diff] [blame] | 68 | } else if (T_nr_str[0] == 'x' || T_nr_str[0] == 'X') { |
| 69 | T_nr_str++; |
| 70 | sign = -1; |
| 71 | } |
| 72 | |
| 73 | /* Make sure to disallow any characters changing the signedness of the parsed int */ |
| 74 | if (T_nr_str[0] < '0' || T_nr_str[0] > '9') { |
| 75 | vty_out(vty, "%% Invalid T timer argument (should be 'T1234' or 'X1234'): '%s'%s", T_str, VTY_NEWLINE); |
| 76 | return NULL; |
| 77 | } |
Neels Hofmeyr | 0fd615f | 2019-01-26 20:36:12 +0100 | [diff] [blame] | 78 | |
| 79 | errno = 0; |
| 80 | l = strtol(T_nr_str, &endptr, 10); |
Neels Hofmeyr | 5734bff | 2019-02-21 02:27:48 +0100 | [diff] [blame] | 81 | if (errno || *endptr || l > INT_MAX || l < 0) { |
| 82 | vty_out(vty, "%% Invalid T timer argument (should be 'T1234' or 'X1234'): '%s'%s", T_str, VTY_NEWLINE); |
Neels Hofmeyr | 0fd615f | 2019-01-26 20:36:12 +0100 | [diff] [blame] | 83 | return NULL; |
| 84 | } |
Neels Hofmeyr | 5734bff | 2019-02-21 02:27:48 +0100 | [diff] [blame] | 85 | T = l * sign; |
Neels Hofmeyr | 0fd615f | 2019-01-26 20:36:12 +0100 | [diff] [blame] | 86 | |
| 87 | t = osmo_tdef_get_entry(tdefs, T); |
| 88 | if (!t) |
Neels Hofmeyr | 5734bff | 2019-02-21 02:27:48 +0100 | [diff] [blame] | 89 | vty_out(vty, "%% No such timer: " OSMO_T_FMT "%s", OSMO_T_FMT_ARGS(T), VTY_NEWLINE); |
Neels Hofmeyr | 0fd615f | 2019-01-26 20:36:12 +0100 | [diff] [blame] | 90 | return t; |
| 91 | } |
| 92 | |
| 93 | /*! Parse an argument of the form "(0-2147483647|default)", as from OSMO_TDEF_VTY_ARG_VAL. |
| 94 | * \param[in] val_arg Argument string (not format checked). |
| 95 | * \param[in] default_val Value to return in case of val_arg being "default". |
| 96 | * \return Parsed value or default_val. |
| 97 | */ |
| 98 | unsigned long osmo_tdef_vty_parse_val_arg(const char *val_arg, unsigned long default_val) |
| 99 | { |
| 100 | if (!strcmp(val_arg, "default")) |
| 101 | return default_val; |
| 102 | return atoll(val_arg); |
| 103 | } |
| 104 | |
| 105 | /*! Apply a timer configuration from VTY argument strings. |
| 106 | * Employ both osmo_tdef_vty_parse_T_arg() and osmo_tdef_vty_parse_val_arg() to configure a T timer in an array of |
| 107 | * tdefs. Evaluate two arguments, a "T1234" argument and a "(0-2147483647|default)" argument, as from |
| 108 | * 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 |
| 109 | * second argument. |
| 110 | * \param[in] vty VTY context for vty_out() of error messages. |
| 111 | * \param[in] tdefs Array of timer definitions to look up T timer. |
| 112 | * \param[in] args Array of string arguments like { "T1234", "23" }. |
| 113 | * \return CMD_SUCCESS, or CMD_WARNING if no such timer is found in tdefs. |
| 114 | */ |
| 115 | int osmo_tdef_vty_set_cmd(struct vty *vty, struct osmo_tdef *tdefs, const char **args) |
| 116 | { |
| 117 | const char *T_arg = args[0]; |
| 118 | const char *val_arg = args[1]; |
| 119 | struct osmo_tdef *t = osmo_tdef_vty_parse_T_arg(vty, tdefs, T_arg); |
| 120 | if (!t) |
| 121 | return CMD_WARNING; |
| 122 | t->val = osmo_tdef_vty_parse_val_arg(val_arg, t->default_val); |
| 123 | return CMD_SUCCESS; |
| 124 | } |
| 125 | |
| 126 | /*! Output one or all timers to the VTY, as for a VTY command like 'show timer [TNNNN]'. |
| 127 | * If T_arg is NULL, print all timers in tdefs to the VTY. |
| 128 | * 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 |
| 129 | * VTY. |
| 130 | * \param[in] vty VTY context for vty_out() of error messages. |
| 131 | * \param[in] tdefs Array of timer definitions. |
| 132 | * \param[in] T_arg Argument string like "T1234", or NULL. |
| 133 | * \param[in] prefix_fmt Arbitrary string to start each line with, with variable printf like arguments. |
| 134 | * \return CMD_SUCCESS, or CMD_WARNING if no such timer is found in tdefs. |
| 135 | */ |
| 136 | int osmo_tdef_vty_show_cmd(struct vty *vty, struct osmo_tdef *tdefs, const char *T_arg, |
| 137 | const char *prefix_fmt, ...) |
| 138 | { |
| 139 | va_list va; |
| 140 | if (T_arg) { |
| 141 | struct osmo_tdef *t = osmo_tdef_vty_parse_T_arg(vty, tdefs, T_arg); |
| 142 | if (!t) |
| 143 | return CMD_WARNING; |
| 144 | va_start(va, prefix_fmt); |
| 145 | osmo_tdef_vty_out_one_va(vty, t, prefix_fmt, va); |
| 146 | va_end(va); |
| 147 | } else { |
| 148 | va_start(va, prefix_fmt); |
| 149 | osmo_tdef_vty_out_all_va(vty, tdefs, prefix_fmt, va); |
| 150 | va_end(va); |
| 151 | } |
| 152 | return CMD_SUCCESS; |
| 153 | } |
| 154 | |
| 155 | /*! Write to VTY the current status of one timer. |
| 156 | * \param[in] vty VTY context for vty_out(). |
| 157 | * \param[in] t The timer to print. |
| 158 | * \param[in] prefix_fmt Arbitrary string to start each line with, with variable vprintf like arguments. |
| 159 | * \param[in] va va_list instance. As always, call va_start() before, and va_end() after this call. |
| 160 | */ |
| 161 | void osmo_tdef_vty_out_one_va(struct vty *vty, struct osmo_tdef *t, const char *prefix_fmt, va_list va) |
| 162 | { |
| 163 | if (!t) { |
| 164 | vty_out(vty, "%% Error: no such timer%s", VTY_NEWLINE); |
| 165 | return; |
| 166 | } |
| 167 | if (prefix_fmt) |
| 168 | vty_out_va(vty, prefix_fmt, va); |
Neels Hofmeyr | 5734bff | 2019-02-21 02:27:48 +0100 | [diff] [blame] | 169 | vty_out(vty, OSMO_T_FMT " = %lu%s%s\t%s (default: %lu%s%s)%s", |
| 170 | OSMO_T_FMT_ARGS(t->T), t->val, |
Neels Hofmeyr | 0fd615f | 2019-01-26 20:36:12 +0100 | [diff] [blame] | 171 | t->unit == OSMO_TDEF_CUSTOM ? "" : " ", t->unit == OSMO_TDEF_CUSTOM ? "" : osmo_tdef_unit_name(t->unit), |
| 172 | t->desc, t->default_val, |
| 173 | t->unit == OSMO_TDEF_CUSTOM ? "" : " ", t->unit == OSMO_TDEF_CUSTOM ? "" : osmo_tdef_unit_name(t->unit), |
| 174 | VTY_NEWLINE); |
| 175 | } |
| 176 | |
| 177 | /*! Write to VTY the current status of one timer. |
| 178 | * \param[in] vty VTY context for vty_out(). |
| 179 | * \param[in] t The timer to print. |
| 180 | * \param[in] prefix_fmt Arbitrary string to start each line with, with variable printf like arguments. |
| 181 | */ |
| 182 | void osmo_tdef_vty_out_one(struct vty *vty, struct osmo_tdef *t, const char *prefix_fmt, ...) |
| 183 | { |
| 184 | va_list va; |
| 185 | va_start(va, prefix_fmt); |
| 186 | osmo_tdef_vty_out_one_va(vty, t, prefix_fmt, va); |
| 187 | va_end(va); |
| 188 | } |
| 189 | |
| 190 | /*! Write to VTY the current status of all given timers. |
| 191 | * \param[in] vty VTY context for vty_out(). |
| 192 | * \param[in] tdefs Array of timers to print, ended with a fully zero-initialized entry. |
| 193 | * \param[in] prefix_fmt Arbitrary string to start each line with, with variable vprintf like arguments. |
| 194 | * \param[in] va va_list instance. As always, call va_start() before, and va_end() after this call. |
| 195 | */ |
| 196 | void osmo_tdef_vty_out_all_va(struct vty *vty, struct osmo_tdef *tdefs, const char *prefix_fmt, va_list va) |
| 197 | { |
| 198 | struct osmo_tdef *t; |
| 199 | if (!tdefs) { |
| 200 | vty_out(vty, "%% Error: no such timers%s", VTY_NEWLINE); |
| 201 | return; |
| 202 | } |
| 203 | osmo_tdef_for_each(t, tdefs) { |
| 204 | va_list va2; |
| 205 | va_copy(va2, va); |
| 206 | osmo_tdef_vty_out_one_va(vty, t, prefix_fmt, va); |
| 207 | va_end(va2); |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | /*! Write to VTY the current status of all given timers. |
| 212 | * \param[in] vty VTY context for vty_out(). |
| 213 | * \param[in] tdefs Array of timers to print, ended with a fully zero-initialized entry. |
| 214 | * \param[in] prefix_fmt Arbitrary string to start each line with, with variable printf like arguments. |
| 215 | */ |
| 216 | void osmo_tdef_vty_out_all(struct vty *vty, struct osmo_tdef *tdefs, const char *prefix_fmt, ...) |
| 217 | { |
| 218 | va_list va; |
| 219 | va_start(va, prefix_fmt); |
| 220 | osmo_tdef_vty_out_all_va(vty, tdefs, prefix_fmt, va); |
| 221 | va_end(va); |
| 222 | } |
| 223 | |
| 224 | /*! Write current timer configuration arguments to the vty. Skip all entries that reflect their default value. |
| 225 | * The passed prefix string must contain both necessary indent and the VTY command the specific implementation is using. |
| 226 | * See tdef_vty_test_config_subnode.c and tdef_vty_test_dynamic.c for examples. |
| 227 | * \param[in] vty VTY context. |
| 228 | * \param[in] tdefs Array of timers to print, ended with a fully zero-initialized entry. |
| 229 | * \param[in] prefix_fmt Arbitrary string to start each line with, with variable printf like arguments. |
| 230 | */ |
| 231 | void osmo_tdef_vty_write(struct vty *vty, struct osmo_tdef *tdefs, const char *prefix_fmt, ...) |
| 232 | { |
| 233 | va_list va; |
| 234 | struct osmo_tdef *t; |
| 235 | osmo_tdef_for_each(t, tdefs) { |
| 236 | if (t->val == t->default_val) |
| 237 | continue; |
| 238 | if (prefix_fmt && *prefix_fmt) { |
| 239 | va_start(va, prefix_fmt); |
| 240 | vty_out_va(vty, prefix_fmt, va); |
| 241 | va_end(va); |
| 242 | } |
Neels Hofmeyr | 5734bff | 2019-02-21 02:27:48 +0100 | [diff] [blame] | 243 | vty_out(vty, OSMO_T_FMT " %lu%s", OSMO_T_FMT_ARGS(t->T), t->val, VTY_NEWLINE); |
Neels Hofmeyr | 0fd615f | 2019-01-26 20:36:12 +0100 | [diff] [blame] | 244 | } |
| 245 | } |
| 246 | |
| 247 | /*! Singleton Tnnn groups definition as set by osmo_tdef_vty_groups_init(). */ |
| 248 | static struct osmo_tdef_group *global_tdef_groups; |
| 249 | |
Neels Hofmeyr | 0fd615f | 2019-01-26 20:36:12 +0100 | [diff] [blame] | 250 | DEFUN(show_timer, show_timer_cmd, "DYNAMIC", "DYNAMIC") |
| 251 | /* show timer [(alpha|beta|gamma)] [TNNNN] */ |
| 252 | { |
| 253 | const char *group_arg = argc > 0 ? argv[0] : NULL; |
| 254 | const char *T_arg = argc > 1 ? argv[1] : NULL; |
| 255 | struct osmo_tdef_group *g; |
| 256 | |
| 257 | /* The argument should be either "tea" or "software", but the VTY also allows partial arguments |
| 258 | * like "softw" or "t" (which can also be ambiguous). */ |
| 259 | |
| 260 | osmo_tdef_groups_for_each(g, global_tdef_groups) { |
Neels Hofmeyr | d79ccc6 | 2019-03-07 23:08:40 +0100 | [diff] [blame] | 261 | if (!group_arg || osmo_str_startswith(g->name, group_arg)) |
Neels Hofmeyr | 0fd615f | 2019-01-26 20:36:12 +0100 | [diff] [blame] | 262 | osmo_tdef_vty_show_cmd(vty, g->tdefs, T_arg, "%s: ", g->name); |
| 263 | } |
| 264 | return CMD_SUCCESS; |
| 265 | } |
| 266 | |
| 267 | DEFUN(cfg_timer, cfg_timer_cmd, "DYNAMIC", "DYNAMIC") |
| 268 | /* show timer [(alpha|beta|gamma)] [TNNNN] [(<0-2147483647>|default)] */ |
| 269 | { |
| 270 | const char *group_arg; |
| 271 | const char **timer_args; |
| 272 | struct osmo_tdef *tdefs = NULL; |
| 273 | struct osmo_tdef_group *g = NULL; |
| 274 | |
| 275 | /* If any arguments are missing, redirect to 'show' */ |
| 276 | if (argc < 3) |
| 277 | return show_timer(self, vty, argc, argv); |
| 278 | |
| 279 | /* If all arguments are passed, this is configuring a timer. */ |
Vadim Yanitskiy | 54f5f4d | 2019-02-23 17:19:35 +0700 | [diff] [blame] | 280 | group_arg = argv[0]; |
Neels Hofmeyr | 0fd615f | 2019-01-26 20:36:12 +0100 | [diff] [blame] | 281 | timer_args = argv + 1; |
| 282 | osmo_tdef_groups_for_each(g, global_tdef_groups) { |
| 283 | if (strcmp(g->name, group_arg)) |
| 284 | continue; |
| 285 | if (tdefs) { |
| 286 | vty_out(vty, "%% Error: ambiguous timer group match%s", VTY_NEWLINE); |
| 287 | return CMD_WARNING; |
| 288 | } |
| 289 | tdefs = g->tdefs; |
| 290 | } |
| 291 | |
| 292 | return osmo_tdef_vty_set_cmd(vty, tdefs, timer_args); |
| 293 | } |
| 294 | |
| 295 | static char *add_group_args(void *talloc_ctx, char *dest) |
| 296 | { |
| 297 | struct osmo_tdef_group *g; |
| 298 | osmo_talloc_asprintf(talloc_ctx, dest, "[("); |
| 299 | osmo_tdef_groups_for_each(g, global_tdef_groups) { |
| 300 | osmo_talloc_asprintf(talloc_ctx, dest, "%s%s", |
| 301 | (g == global_tdef_groups) ? "" : "|", |
| 302 | g->name); |
| 303 | } |
| 304 | osmo_talloc_asprintf(talloc_ctx, dest, ")]"); |
| 305 | return dest; |
| 306 | } |
| 307 | |
| 308 | static char *add_group_docs(void *talloc_ctx, char *dest) |
| 309 | { |
| 310 | struct osmo_tdef_group *g; |
| 311 | osmo_tdef_groups_for_each(g, global_tdef_groups) { |
| 312 | osmo_talloc_asprintf(talloc_ctx, dest, "%s\n", g->desc); |
| 313 | } |
| 314 | return dest; |
| 315 | } |
| 316 | |
| 317 | static char *timer_command_string(const char *prefix, const char *suffix) |
| 318 | { |
| 319 | char *dest = NULL; |
| 320 | osmo_talloc_asprintf(tall_vty_cmd_ctx, dest, "%s ", prefix); |
| 321 | dest = add_group_args(tall_vty_cmd_ctx, dest); |
| 322 | osmo_talloc_asprintf(tall_vty_cmd_ctx, dest, " %s", suffix); |
| 323 | return dest; |
| 324 | } |
| 325 | |
| 326 | static char *timer_doc_string(const char *prefix, const char *suffix) |
| 327 | { |
| 328 | char *dest = NULL; |
| 329 | osmo_talloc_asprintf(tall_vty_cmd_ctx, dest, "%s ", prefix); |
| 330 | dest = add_group_docs(tall_vty_cmd_ctx, dest); |
| 331 | osmo_talloc_asprintf(tall_vty_cmd_ctx, dest, " %s", suffix); |
| 332 | return dest; |
| 333 | } |
| 334 | |
| 335 | /*! Convenience implementation for keeping a fixed set of timer groups in a program. |
| 336 | * Install a 'timer [(group|names|...)] [TNNN] [(<val>|default)]' command under the given parent_node, |
| 337 | * and install a 'show timer...' command on VIEW_NODE and ENABLE_NODE. |
| 338 | * For a usage example, see \ref tdef_test_config_root.c. |
| 339 | * The given timer definitions group is stored in a global pointer, so this can be done only once per main() scope. |
| 340 | * It would also be possible to have distinct timer groups on separate VTY subnodes, with a "manual" implementation, but |
| 341 | * not with this API. |
| 342 | * \param[in] parent_node VTY node id at which to add the timer group commands, e.g. CONFIG_NODE. |
| 343 | * \param[in] groups Global timer groups definition. |
| 344 | */ |
| 345 | void osmo_tdef_vty_groups_init(enum node_type parent_node, struct osmo_tdef_group *groups) |
| 346 | { |
| 347 | struct osmo_tdef_group *g; |
| 348 | OSMO_ASSERT(!global_tdef_groups); |
| 349 | global_tdef_groups = groups; |
| 350 | |
| 351 | osmo_tdef_groups_for_each(g, global_tdef_groups) |
| 352 | osmo_tdefs_reset(g->tdefs); |
| 353 | |
| 354 | show_timer_cmd.string = timer_command_string("show timer", OSMO_TDEF_VTY_ARG_T_OPTIONAL); |
| 355 | show_timer_cmd.doc = timer_doc_string(SHOW_STR "Show timers\n", OSMO_TDEF_VTY_DOC_T); |
| 356 | |
| 357 | cfg_timer_cmd.string = timer_command_string("timer", OSMO_TDEF_VTY_ARG_SET_OPTIONAL); |
| 358 | cfg_timer_cmd.doc = timer_doc_string("Configure or show timers\n", OSMO_TDEF_VTY_DOC_SET); |
| 359 | |
| 360 | install_element_ve(&show_timer_cmd); |
| 361 | install_element(parent_node, &cfg_timer_cmd); |
| 362 | } |
| 363 | |
| 364 | /*! Write the global osmo_tdef_group configuration to VTY, as previously passed to osmo_tdef_vty_groups_init(). |
| 365 | * \param[in] vty VTY context. |
| 366 | * \param[in] indent String to print before each line. |
| 367 | */ |
| 368 | void osmo_tdef_vty_groups_write(struct vty *vty, const char *indent) |
| 369 | { |
| 370 | struct osmo_tdef_group *g; |
| 371 | osmo_tdef_groups_for_each(g, global_tdef_groups) |
| 372 | osmo_tdef_vty_write(vty, g->tdefs, "%stimer %s ", indent ? : "", g->name); |
| 373 | } |
| 374 | |
| 375 | /*! @} */ |