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