blob: bd209ae025bf7445fb85adb7f7d4db880cb4d3ca [file] [log] [blame]
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +01001/*! \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
Harald Welte581a34d2019-05-27 23:15:28 +020011 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +010013 * (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
Harald Welte581a34d2019-05-27 23:15:28 +020018 * GNU General Public License for more details.
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +010019 *
Harald Welte581a34d2019-05-27 23:15:28 +020020 * You should have received a copy of the GNU General Public License
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +010021 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 *
Harald Welte581a34d2019-05-27 23:15:28 +020023 * SPDX-License-Identifier: GPLv2+
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +010024 */
25
26#include <string.h>
27#include <stdlib.h>
28#include <errno.h>
29#include <limits.h>
30
31#include <osmocom/vty/vty.h>
32#include <osmocom/vty/command.h>
33#include <osmocom/vty/tdef_vty.h>
34#include <osmocom/core/tdef.h>
Neels Hofmeyr5734bff2019-02-21 02:27:48 +010035#include <osmocom/core/fsm.h>
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +010036
37/*! \addtogroup Tdef_VTY
38 *
39 * VTY API for \ref Tdef.
40 *
41 * @{
42 * \file tdef_vty.c
43 */
44
Neels Hofmeyr5734bff2019-02-21 02:27:48 +010045/*! Parse an argument like "1234", "T1234", "t1234", or "X1234", "x1234", as from OSMO_TDEF_VTY_ARG_T.
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +010046 * \param[in] vty VTY context for vty_out() of error messages.
47 * \param[in] tdefs Array of timer definitions to look up T timer.
48 * \param[in] T_str Argument string. It is not validated, expected to be checked by VTY input.
49 * \return the corresponding osmo_tdef entry from the tdefs array, or NULL if no such entry exists.
50 */
51struct osmo_tdef *osmo_tdef_vty_parse_T_arg(struct vty *vty, struct osmo_tdef *tdefs, const char *T_str)
52{
Neels Hofmeyr34907fe2021-09-05 19:50:34 +020053 int l;
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +010054 int T;
55 struct osmo_tdef *t;
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +010056 const char *T_nr_str;
Neels Hofmeyr5734bff2019-02-21 02:27:48 +010057 int sign = 1;
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +010058
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 Hofmeyr5734bff2019-02-21 02:27:48 +010065 if (T_nr_str[0] == 't' || T_nr_str[0] == 'T') {
66 sign = 1;
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +010067 T_nr_str++;
Neels Hofmeyr5734bff2019-02-21 02:27:48 +010068 } 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 Hofmeyr0fd615f2019-01-26 20:36:12 +010078
Neels Hofmeyr34907fe2021-09-05 19:50:34 +020079 if (osmo_str_to_int(&l, T_nr_str, 10, 0, INT_MAX)) {
Neels Hofmeyr5734bff2019-02-21 02:27:48 +010080 vty_out(vty, "%% Invalid T timer argument (should be 'T1234' or 'X1234'): '%s'%s", T_str, VTY_NEWLINE);
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +010081 return NULL;
82 }
Neels Hofmeyr5734bff2019-02-21 02:27:48 +010083 T = l * sign;
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +010084
85 t = osmo_tdef_get_entry(tdefs, T);
86 if (!t)
Neels Hofmeyr5734bff2019-02-21 02:27:48 +010087 vty_out(vty, "%% No such timer: " OSMO_T_FMT "%s", OSMO_T_FMT_ARGS(T), VTY_NEWLINE);
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +010088 return t;
89}
90
91/*! Parse an argument of the form "(0-2147483647|default)", as from OSMO_TDEF_VTY_ARG_VAL.
92 * \param[in] val_arg Argument string (not format checked).
93 * \param[in] default_val Value to return in case of val_arg being "default".
94 * \return Parsed value or default_val.
95 */
96unsigned long osmo_tdef_vty_parse_val_arg(const char *val_arg, unsigned long default_val)
97{
98 if (!strcmp(val_arg, "default"))
99 return default_val;
100 return atoll(val_arg);
101}
102
103/*! Apply a timer configuration from VTY argument strings.
104 * Employ both osmo_tdef_vty_parse_T_arg() and osmo_tdef_vty_parse_val_arg() to configure a T timer in an array of
105 * tdefs. Evaluate two arguments, a "T1234" argument and a "(0-2147483647|default)" argument, as from
106 * 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
107 * second argument.
108 * \param[in] vty VTY context for vty_out() of error messages.
109 * \param[in] tdefs Array of timer definitions to look up T timer.
110 * \param[in] args Array of string arguments like { "T1234", "23" }.
111 * \return CMD_SUCCESS, or CMD_WARNING if no such timer is found in tdefs.
112 */
113int osmo_tdef_vty_set_cmd(struct vty *vty, struct osmo_tdef *tdefs, const char **args)
114{
Pau Espin Pedrol0cbe8f02019-09-17 13:13:52 +0200115 unsigned long new_val;
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +0100116 const char *T_arg = args[0];
117 const char *val_arg = args[1];
118 struct osmo_tdef *t = osmo_tdef_vty_parse_T_arg(vty, tdefs, T_arg);
119 if (!t)
120 return CMD_WARNING;
Pau Espin Pedrol0cbe8f02019-09-17 13:13:52 +0200121 new_val = osmo_tdef_vty_parse_val_arg(val_arg, t->default_val);
122
123 if (!osmo_tdef_val_in_range(t, new_val)) {
124 char range_str[64];
125 osmo_tdef_range_str_buf(range_str, sizeof(range_str), t);
126 vty_out(vty, "%% Timer " OSMO_T_FMT " value %lu is out of range %s%s",
127 OSMO_T_FMT_ARGS(t->T), new_val, range_str, VTY_NEWLINE);
128 return CMD_WARNING;
129 }
130 t->val = new_val;
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +0100131 return CMD_SUCCESS;
132}
133
134/*! Output one or all timers to the VTY, as for a VTY command like 'show timer [TNNNN]'.
135 * If T_arg is NULL, print all timers in tdefs to the VTY.
136 * 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
137 * VTY.
138 * \param[in] vty VTY context for vty_out() of error messages.
139 * \param[in] tdefs Array of timer definitions.
140 * \param[in] T_arg Argument string like "T1234", or NULL.
141 * \param[in] prefix_fmt Arbitrary string to start each line with, with variable printf like arguments.
142 * \return CMD_SUCCESS, or CMD_WARNING if no such timer is found in tdefs.
143 */
144int osmo_tdef_vty_show_cmd(struct vty *vty, struct osmo_tdef *tdefs, const char *T_arg,
145 const char *prefix_fmt, ...)
146{
147 va_list va;
148 if (T_arg) {
149 struct osmo_tdef *t = osmo_tdef_vty_parse_T_arg(vty, tdefs, T_arg);
150 if (!t)
151 return CMD_WARNING;
152 va_start(va, prefix_fmt);
153 osmo_tdef_vty_out_one_va(vty, t, prefix_fmt, va);
154 va_end(va);
155 } else {
156 va_start(va, prefix_fmt);
157 osmo_tdef_vty_out_all_va(vty, tdefs, prefix_fmt, va);
158 va_end(va);
159 }
160 return CMD_SUCCESS;
161}
162
163/*! Write to VTY the current status of one timer.
164 * \param[in] vty VTY context for vty_out().
165 * \param[in] t The timer to print.
166 * \param[in] prefix_fmt Arbitrary string to start each line with, with variable vprintf like arguments.
167 * \param[in] va va_list instance. As always, call va_start() before, and va_end() after this call.
168 */
169void osmo_tdef_vty_out_one_va(struct vty *vty, struct osmo_tdef *t, const char *prefix_fmt, va_list va)
170{
Pau Espin Pedrol0cbe8f02019-09-17 13:13:52 +0200171 char range_str[64];
172
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +0100173 if (!t) {
174 vty_out(vty, "%% Error: no such timer%s", VTY_NEWLINE);
175 return;
176 }
177 if (prefix_fmt)
178 vty_out_va(vty, prefix_fmt, va);
Pau Espin Pedrol0cbe8f02019-09-17 13:13:52 +0200179
180 vty_out(vty, OSMO_T_FMT " = %lu", OSMO_T_FMT_ARGS(t->T), t->val);
181 if (t->unit != OSMO_TDEF_CUSTOM)
182 vty_out(vty, " %s", osmo_tdef_unit_name(t->unit));
183
184 vty_out(vty, "\t%s (default: %lu", t->desc, t->default_val);
185 if (t->unit != OSMO_TDEF_CUSTOM)
186 vty_out(vty, " %s", osmo_tdef_unit_name(t->unit));
187
188 if (t->min_val || t->max_val) {
189 osmo_tdef_range_str_buf(range_str, sizeof(range_str), t);
190 vty_out(vty, ", range: %s", range_str);
191 }
192
193 vty_out(vty, ")%s", VTY_NEWLINE);
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +0100194}
195
196/*! Write to VTY the current status of one timer.
197 * \param[in] vty VTY context for vty_out().
198 * \param[in] t The timer to print.
199 * \param[in] prefix_fmt Arbitrary string to start each line with, with variable printf like arguments.
200 */
201void osmo_tdef_vty_out_one(struct vty *vty, struct osmo_tdef *t, const char *prefix_fmt, ...)
202{
203 va_list va;
204 va_start(va, prefix_fmt);
205 osmo_tdef_vty_out_one_va(vty, t, prefix_fmt, va);
206 va_end(va);
207}
208
209/*! Write to VTY the current status of all given timers.
210 * \param[in] vty VTY context for vty_out().
211 * \param[in] tdefs Array of timers to print, ended with a fully zero-initialized entry.
212 * \param[in] prefix_fmt Arbitrary string to start each line with, with variable vprintf like arguments.
213 * \param[in] va va_list instance. As always, call va_start() before, and va_end() after this call.
214 */
215void osmo_tdef_vty_out_all_va(struct vty *vty, struct osmo_tdef *tdefs, const char *prefix_fmt, va_list va)
216{
217 struct osmo_tdef *t;
218 if (!tdefs) {
219 vty_out(vty, "%% Error: no such timers%s", VTY_NEWLINE);
220 return;
221 }
222 osmo_tdef_for_each(t, tdefs) {
223 va_list va2;
224 va_copy(va2, va);
225 osmo_tdef_vty_out_one_va(vty, t, prefix_fmt, va);
226 va_end(va2);
227 }
228}
229
230/*! Write to VTY the current status of all given timers.
231 * \param[in] vty VTY context for vty_out().
232 * \param[in] tdefs Array of timers to print, ended with a fully zero-initialized entry.
233 * \param[in] prefix_fmt Arbitrary string to start each line with, with variable printf like arguments.
234 */
235void osmo_tdef_vty_out_all(struct vty *vty, struct osmo_tdef *tdefs, const char *prefix_fmt, ...)
236{
237 va_list va;
238 va_start(va, prefix_fmt);
239 osmo_tdef_vty_out_all_va(vty, tdefs, prefix_fmt, va);
240 va_end(va);
241}
242
243/*! Write current timer configuration arguments to the vty. Skip all entries that reflect their default value.
244 * The passed prefix string must contain both necessary indent and the VTY command the specific implementation is using.
Vadim Yanitskiy52a38b42021-11-17 01:58:03 +0300245 * See tdef_vty_config_subnode_test.c and tdef_vty_dynamic_test.c for examples.
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +0100246 * \param[in] vty VTY context.
247 * \param[in] tdefs Array of timers to print, ended with a fully zero-initialized entry.
248 * \param[in] prefix_fmt Arbitrary string to start each line with, with variable printf like arguments.
249 */
250void osmo_tdef_vty_write(struct vty *vty, struct osmo_tdef *tdefs, const char *prefix_fmt, ...)
251{
252 va_list va;
253 struct osmo_tdef *t;
254 osmo_tdef_for_each(t, tdefs) {
255 if (t->val == t->default_val)
256 continue;
257 if (prefix_fmt && *prefix_fmt) {
258 va_start(va, prefix_fmt);
259 vty_out_va(vty, prefix_fmt, va);
260 va_end(va);
261 }
Neels Hofmeyr5734bff2019-02-21 02:27:48 +0100262 vty_out(vty, OSMO_T_FMT " %lu%s", OSMO_T_FMT_ARGS(t->T), t->val, VTY_NEWLINE);
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +0100263 }
264}
265
266/*! Singleton Tnnn groups definition as set by osmo_tdef_vty_groups_init(). */
267static struct osmo_tdef_group *global_tdef_groups;
268
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +0100269DEFUN(show_timer, show_timer_cmd, "DYNAMIC", "DYNAMIC")
270 /* show timer [(alpha|beta|gamma)] [TNNNN] */
271{
272 const char *group_arg = argc > 0 ? argv[0] : NULL;
273 const char *T_arg = argc > 1 ? argv[1] : NULL;
274 struct osmo_tdef_group *g;
275
276 /* The argument should be either "tea" or "software", but the VTY also allows partial arguments
277 * like "softw" or "t" (which can also be ambiguous). */
278
279 osmo_tdef_groups_for_each(g, global_tdef_groups) {
Neels Hofmeyrd79ccc62019-03-07 23:08:40 +0100280 if (!group_arg || osmo_str_startswith(g->name, group_arg))
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +0100281 osmo_tdef_vty_show_cmd(vty, g->tdefs, T_arg, "%s: ", g->name);
282 }
283 return CMD_SUCCESS;
284}
285
286DEFUN(cfg_timer, cfg_timer_cmd, "DYNAMIC", "DYNAMIC")
287 /* show timer [(alpha|beta|gamma)] [TNNNN] [(<0-2147483647>|default)] */
288{
289 const char *group_arg;
290 const char **timer_args;
291 struct osmo_tdef *tdefs = NULL;
292 struct osmo_tdef_group *g = NULL;
293
294 /* If any arguments are missing, redirect to 'show' */
295 if (argc < 3)
296 return show_timer(self, vty, argc, argv);
297
298 /* If all arguments are passed, this is configuring a timer. */
Vadim Yanitskiy54f5f4d2019-02-23 17:19:35 +0700299 group_arg = argv[0];
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +0100300 timer_args = argv + 1;
301 osmo_tdef_groups_for_each(g, global_tdef_groups) {
302 if (strcmp(g->name, group_arg))
303 continue;
304 if (tdefs) {
305 vty_out(vty, "%% Error: ambiguous timer group match%s", VTY_NEWLINE);
306 return CMD_WARNING;
307 }
308 tdefs = g->tdefs;
309 }
310
311 return osmo_tdef_vty_set_cmd(vty, tdefs, timer_args);
312}
313
314static char *add_group_args(void *talloc_ctx, char *dest)
315{
316 struct osmo_tdef_group *g;
317 osmo_talloc_asprintf(talloc_ctx, dest, "[(");
318 osmo_tdef_groups_for_each(g, global_tdef_groups) {
319 osmo_talloc_asprintf(talloc_ctx, dest, "%s%s",
320 (g == global_tdef_groups) ? "" : "|",
321 g->name);
322 }
323 osmo_talloc_asprintf(talloc_ctx, dest, ")]");
324 return dest;
325}
326
327static char *add_group_docs(void *talloc_ctx, char *dest)
328{
329 struct osmo_tdef_group *g;
330 osmo_tdef_groups_for_each(g, global_tdef_groups) {
331 osmo_talloc_asprintf(talloc_ctx, dest, "%s\n", g->desc);
332 }
333 return dest;
334}
335
336static char *timer_command_string(const char *prefix, const char *suffix)
337{
338 char *dest = NULL;
339 osmo_talloc_asprintf(tall_vty_cmd_ctx, dest, "%s ", prefix);
340 dest = add_group_args(tall_vty_cmd_ctx, dest);
341 osmo_talloc_asprintf(tall_vty_cmd_ctx, dest, " %s", suffix);
342 return dest;
343}
344
345static char *timer_doc_string(const char *prefix, const char *suffix)
346{
347 char *dest = NULL;
348 osmo_talloc_asprintf(tall_vty_cmd_ctx, dest, "%s ", prefix);
349 dest = add_group_docs(tall_vty_cmd_ctx, dest);
350 osmo_talloc_asprintf(tall_vty_cmd_ctx, dest, " %s", suffix);
351 return dest;
352}
353
354/*! Convenience implementation for keeping a fixed set of timer groups in a program.
355 * Install a 'timer [(group|names|...)] [TNNN] [(<val>|default)]' command under the given parent_node,
356 * and install a 'show timer...' command on VIEW_NODE and ENABLE_NODE.
357 * For a usage example, see \ref tdef_test_config_root.c.
358 * The given timer definitions group is stored in a global pointer, so this can be done only once per main() scope.
359 * It would also be possible to have distinct timer groups on separate VTY subnodes, with a "manual" implementation, but
360 * not with this API.
Vadim Yanitskiydf4f6082020-01-25 09:49:22 +0700361 * \param[in] parent_cfg_node VTY node at which to add the timer configuration commands, e.g. CONFIG_NODE.
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +0100362 * \param[in] groups Global timer groups definition.
363 */
Vadim Yanitskiydf4f6082020-01-25 09:49:22 +0700364void osmo_tdef_vty_groups_init(unsigned int parent_cfg_node, struct osmo_tdef_group *groups)
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +0100365{
366 struct osmo_tdef_group *g;
367 OSMO_ASSERT(!global_tdef_groups);
368 global_tdef_groups = groups;
369
370 osmo_tdef_groups_for_each(g, global_tdef_groups)
371 osmo_tdefs_reset(g->tdefs);
372
373 show_timer_cmd.string = timer_command_string("show timer", OSMO_TDEF_VTY_ARG_T_OPTIONAL);
374 show_timer_cmd.doc = timer_doc_string(SHOW_STR "Show timers\n", OSMO_TDEF_VTY_DOC_T);
375
376 cfg_timer_cmd.string = timer_command_string("timer", OSMO_TDEF_VTY_ARG_SET_OPTIONAL);
377 cfg_timer_cmd.doc = timer_doc_string("Configure or show timers\n", OSMO_TDEF_VTY_DOC_SET);
378
Vadim Yanitskiy8e7c4962020-10-04 15:37:31 +0700379 install_lib_element_ve(&show_timer_cmd);
380 install_lib_element(parent_cfg_node, &cfg_timer_cmd);
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +0100381}
382
383/*! Write the global osmo_tdef_group configuration to VTY, as previously passed to osmo_tdef_vty_groups_init().
384 * \param[in] vty VTY context.
385 * \param[in] indent String to print before each line.
386 */
387void osmo_tdef_vty_groups_write(struct vty *vty, const char *indent)
388{
389 struct osmo_tdef_group *g;
390 osmo_tdef_groups_for_each(g, global_tdef_groups)
391 osmo_tdef_vty_write(vty, g->tdefs, "%stimer %s ", indent ? : "", g->name);
392}
393
394/*! @} */