blob: 0dac2bf5860c0dfe0510e66c8e61a50a9158b4e4 [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
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 Hofmeyr5734bff2019-02-21 02:27:48 +010034#include <osmocom/core/fsm.h>
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +010035
36/*! \addtogroup Tdef_VTY
37 *
38 * VTY API for \ref Tdef.
39 *
40 * @{
41 * \file tdef_vty.c
42 */
43
Neels Hofmeyr5734bff2019-02-21 02:27:48 +010044/*! Parse an argument like "1234", "T1234", "t1234", or "X1234", "x1234", as from OSMO_TDEF_VTY_ARG_T.
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +010045 * \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 */
50struct 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 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
79 errno = 0;
80 l = strtol(T_nr_str, &endptr, 10);
Neels Hofmeyr5734bff2019-02-21 02:27:48 +010081 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 Hofmeyr0fd615f2019-01-26 20:36:12 +010083 return NULL;
84 }
Neels Hofmeyr5734bff2019-02-21 02:27:48 +010085 T = l * sign;
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +010086
87 t = osmo_tdef_get_entry(tdefs, T);
88 if (!t)
Neels Hofmeyr5734bff2019-02-21 02:27:48 +010089 vty_out(vty, "%% No such timer: " OSMO_T_FMT "%s", OSMO_T_FMT_ARGS(T), VTY_NEWLINE);
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +010090 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 */
98unsigned 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 */
115int 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 */
136int 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 */
161void 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 Hofmeyr5734bff2019-02-21 02:27:48 +0100169 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 Hofmeyr0fd615f2019-01-26 20:36:12 +0100171 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 */
182void 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 */
196void 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 */
216void 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 */
231void 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 Hofmeyr5734bff2019-02-21 02:27:48 +0100243 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 +0100244 }
245}
246
247/*! Singleton Tnnn groups definition as set by osmo_tdef_vty_groups_init(). */
248static struct osmo_tdef_group *global_tdef_groups;
249
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +0100250DEFUN(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 Hofmeyrd79ccc62019-03-07 23:08:40 +0100261 if (!group_arg || osmo_str_startswith(g->name, group_arg))
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +0100262 osmo_tdef_vty_show_cmd(vty, g->tdefs, T_arg, "%s: ", g->name);
263 }
264 return CMD_SUCCESS;
265}
266
267DEFUN(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 Yanitskiy54f5f4d2019-02-23 17:19:35 +0700280 group_arg = argv[0];
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +0100281 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
295static 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
308static 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
317static 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
326static 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 */
345void 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 */
368void 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/*! @} */