diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 3 | ||||
-rw-r--r-- | src/fsm.c | 4 | ||||
-rw-r--r-- | src/tdef.c | 282 | ||||
-rw-r--r-- | src/vty/Makefile.am | 3 | ||||
-rw-r--r-- | src/vty/tdef_vty.c | 372 |
5 files changed, 662 insertions, 2 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 6840f798..27ab7026 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -23,7 +23,8 @@ libosmocore_la_SOURCES = timer.c timer_gettimeofday.c timer_clockgettime.c \ loggingrb.c crc8gen.c crc16gen.c crc32gen.c crc64gen.c \ macaddr.c stat_item.c stats.c stats_statsd.c prim.c \ conv_acc.c conv_acc_generic.c sercomm.c prbs.c \ - isdnhdlc.c + isdnhdlc.c \ + tdef.c if HAVE_SSSE3 libosmocore_la_SOURCES += conv_acc_sse.c @@ -498,6 +498,10 @@ static int state_chg(struct osmo_fsm_inst *fi, uint32_t new_state, * timer_cb. If passing timeout_secs == 0, it is recommended to also pass T == * 0, so that fi->T is reset to 0 when no timeout is invoked. * + * See also osmo_tdef_fsm_inst_state_chg() from the osmo_tdef API, which + * provides a unified way to configure and apply GSM style Tnnnn timers to FSM + * state transitions. + * * Range: since time_t's maximum value is not well defined in a cross platform * way, clamp timeout_secs to the maximum of the signed 32bit range, or roughly * 68 years (float(0x7fffffff) / (60. * 60 * 24 * 365.25) = 68.0497). Thus diff --git a/src/tdef.c b/src/tdef.c new file mode 100644 index 00000000..7e79d680 --- /dev/null +++ b/src/tdef.c @@ -0,0 +1,282 @@ +/*! \file tdef.c + * Implementation to define Tnnn timers globally and use for FSM state changes. + */ +/* + * (C) 2018-2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * Author: Neels Hofmeyr <neels@hofmeyr.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <limits.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/tdef.h> + +/*! \addtogroup Tdef + * + * Implementation to define Tnnn timers globally and use for FSM state changes. + * + * See also \ref Tdef_VTY + * + * osmo_tdef provides: + * + * - a list of Tnnnn (GSM) timers with description, unit and default value. + * - vty UI to allow users to configure non-default timeouts. + * - API to tie T timers to osmo_fsm states and set them on state transitions. + * + * - a few standard units (minute, second, millisecond) as well as a custom unit + * (which relies on the timer's human readable description to indicate the + * meaning of the value). + * - conversion for standard units: for example, some GSM timers are defined in + * minutes, while our FSM definitions need timeouts in seconds. Conversion is + * for convenience only and can be easily avoided via the custom unit. + * + * By keeping separate osmo_tdef arrays, several groups of timers can be kept + * separately. The VTY tests in tests/tdef/ showcase different schemes: + * + * - \ref tests/vty/tdef_vty_test_config_root.c: + * Keep several timer definitions in separately named groups: showcase the + * osmo_tdef_vty_groups*() API. Each timer group exists exactly once. + * + * - \ref tests/vty/tdef_vty_test_config_subnode.c: + * Keep a single list of timers without separate grouping. + * Put this list on a specific subnode below the CONFIG_NODE. + * There could be several separate subnodes with timers like this, i.e. + * continuing from this example, sets of timers could be separated by placing + * timers in specific config subnodes instead of using the global group name. + * + * - \ref tests/vty/tdef_vty_test_dynamic.c: + * Dynamically allocate timer definitions per each new created object. + * Thus there can be an arbitrary number of independent timer definitions, one + * per allocated object. + * + * osmo_tdef was introduced because: + * + * - without osmo_tdef, each invocation of osmo_fsm_inst_state_chg() needs to be + * programmed with the right timeout value, for all code paths that invoke this + * state change. It is a likely source of errors to get one of them wrong. By + * defining a T timer exactly for an FSM state, the caller can merely invoke the + * state change and trust on the original state definition to apply the correct + * timeout. + * + * - it is helpful to have a standardized config file UI to provide user + * configurable timeouts, instead of inventing new VTY commands for each + * separate application of T timer numbers. See \ref tdef_vty.h. + * + * @{ + * \file tdef.c + */ + +/*! a = return_val * b. \return 0 if factor is below 1. */ +static unsigned long osmo_tdef_factor(enum osmo_tdef_unit a, enum osmo_tdef_unit b) +{ + if (b == a + || b == OSMO_TDEF_CUSTOM || a == OSMO_TDEF_CUSTOM) + return 1; + + switch (b) { + case OSMO_TDEF_MS: + switch (a) { + case OSMO_TDEF_S: + return 1000; + case OSMO_TDEF_M: + return 60*1000; + default: + return 0; + } + case OSMO_TDEF_S: + switch (a) { + case OSMO_TDEF_M: + return 60; + default: + return 0; + } + default: + return 0; + } +} + +/*! \return val in unit to_unit, rounded up to the next integer value and clamped to ULONG_MAX, or 0 if val == 0. */ +static unsigned long osmo_tdef_round(unsigned long val, enum osmo_tdef_unit from_unit, enum osmo_tdef_unit to_unit) +{ + unsigned long f; + if (!val) + return 0; + + f = osmo_tdef_factor(from_unit, to_unit); + if (f == 1) + return val; + if (f < 1) { + f = osmo_tdef_factor(to_unit, from_unit); + return (val / f) + (val % f? 1 : 0); + } + /* range checking */ + if (f > (ULONG_MAX / val)) + return ULONG_MAX; + return val * f; +} + +/*! Set all osmo_tdef values to the default_val. + * It is convenient to define a tdefs array by setting only the default_val, and calling osmo_tdefs_reset() once for + * program startup. (See also osmo_tdef_vty_init()) + * \param[in] tdefs Array of timer definitions, last entry being fully zero. + */ +void osmo_tdefs_reset(struct osmo_tdef *tdefs) +{ + struct osmo_tdef *t; + osmo_tdef_for_each(t, tdefs) + t->val = t->default_val; +} + +/*! Return the value of a T timer from a list of osmo_tdef, in the given unit. + * If no such timer is defined, return the default value passed, or abort the program if default < 0. + * + * Round up any value match as_unit: 1100 ms as OSMO_TDEF_S becomes 2 seconds, as OSMO_TDEF_M becomes one minute. + * However, always return a value of zero as zero (0 ms as OSMO_TDEF_M still is 0 m). + * + * Range: even though the value range is unsigned long here, in practice, using ULONG_MAX as value for a timeout in + * seconds may actually wrap to negative or low timeout values (e.g. in struct timeval). It is recommended to stay below + * INT_MAX seconds. See also osmo_fsm_inst_state_chg(). + * + * Usage example: + * + * struct osmo_tdef global_T_defs[] = { + * { .T=7, .default_val=50, .desc="Water Boiling Timeout" }, // default is .unit=OSMO_TDEF_S == 0 + * { .T=8, .default_val=300, .desc="Tea brewing" }, + * { .T=9, .default_val=5, .unit=OSMO_TDEF_M, .desc="Let tea cool down before drinking" }, + * { .T=10, .default_val=20, .unit=OSMO_TDEF_M, .desc="Forgot to drink tea while it's warm" }, + * {} // <-- important! last entry shall be zero + * }; + * osmo_tdefs_reset(global_T_defs); // make all values the default + * osmo_tdef_vty_init(global_T_defs, CONFIG_NODE); + * + * val = osmo_tdef_get(global_T_defs, 7, OSMO_TDEF_S, -1); // -> 50 + * sleep(val); + * + * val = osmo_tdef_get(global_T_defs, 7, OSMO_TDEF_M, -1); // 50 seconds becomes 1 minute -> 1 + * sleep_minutes(val); + * + * val = osmo_tdef_get(global_T_defs, 99, OSMO_TDEF_S, 3); // not defined, returns 3 + * + * val = osmo_tdef_get(global_T_defs, 99, OSMO_TDEF_S, -1); // not defined, program aborts! + * + * \param[in] tdefs Array of timer definitions, last entry must be fully zero initialized. + * \param[in] T Timer number to get the value for. + * \param[in] as_unit Return timeout value in this unit. + * \param[in] val_if_not_present Fallback value to return if no timeout is defined. + * \return Timeout value in the unit given by as_unit, rounded up if necessary, or val_if_not_present. + */ +unsigned long osmo_tdef_get(const struct osmo_tdef *tdefs, int T, enum osmo_tdef_unit as_unit, unsigned long val_if_not_present) +{ + const struct osmo_tdef *t = osmo_tdef_get_entry((struct osmo_tdef*)tdefs, T); + if (!t) { + OSMO_ASSERT(val_if_not_present >= 0); + return val_if_not_present; + } + return osmo_tdef_round(t->val, t->unit, as_unit); +} + +/*! Find tdef entry matching T. + * This is useful for manipulation, which is usually limited to the VTY configuration. To retrieve a timeout value, + * most callers probably should use osmo_tdef_get() instead. + * \param[in] tdefs Array of timer definitions, last entry being fully zero. + * \param[in] T Timer number to get the entry for. + * \return osmo_tdef entry matching T in given array, or NULL if no match is found. + */ +struct osmo_tdef *osmo_tdef_get_entry(struct osmo_tdef *tdefs, int T) +{ + struct osmo_tdef *t; + osmo_tdef_for_each(t, tdefs) { + if (t->T == T) + return t; + } + return NULL; +} + +/*! Using osmo_tdef for osmo_fsm_inst: find a given state's osmo_tdef_state_timeout entry. + * + * The timeouts_array shall contain exactly 32 elements, regardless whether only some of them are actually populated + * with nonzero values. 32 corresponds to the number of states allowed by the osmo_fsm_* API. Lookup is by array index. + * Not populated entries imply a state change invocation without timeout. + * + * For example: + * + * struct osmo_tdef_state_timeout my_fsm_timeouts[32] = { + * [MY_FSM_STATE_3] = { .T = 423 }, // look up timeout configured for T423 + * [MY_FSM_STATE_7] = { .T = 235 }, + * [MY_FSM_STATE_8] = { .keep_timer = true }, // keep previous state's T number, continue timeout. + * // any state that is omitted will remain zero == no timeout + * }; + * osmo_tdef_get_state_timeout(MY_FSM_STATE_0, &my_fsm_timeouts) -> NULL, + * osmo_tdef_get_state_timeout(MY_FSM_STATE_7, &my_fsm_timeouts) -> { .T = 235 } + * + * The intention is then to obtain the timer like osmo_tdef_get(global_T_defs, T=235); see also + * fsm_inst_state_chg_T() below. + * + * \param[in] state State constant to look up. + * \param[in] timeouts_array Array[32] of struct osmo_tdef_state_timeout defining which timer number to use per state. + * \return A struct osmo_tdef_state_timeout entry, or NULL if that entry is zero initialized. + */ +const struct osmo_tdef_state_timeout *osmo_tdef_get_state_timeout(uint32_t state, const struct osmo_tdef_state_timeout *timeouts_array) +{ + const struct osmo_tdef_state_timeout *t; + OSMO_ASSERT(state < 32); + t = &timeouts_array[state]; + if (!t->keep_timer && !t->T) + return NULL; + return t; +} + +/*! See invocation macro osmo_tdef_fsm_inst_state_chg() instead. + * \param[in] file Source file name, like __FILE__. + * \param[in] line Source file line number, like __LINE__. + */ +int _osmo_tdef_fsm_inst_state_chg(struct osmo_fsm_inst *fi, uint32_t state, + const struct osmo_tdef_state_timeout *timeouts_array, + const struct osmo_tdef *tdefs, unsigned long default_timeout, + const char *file, int line) +{ + const struct osmo_tdef_state_timeout *t = osmo_tdef_get_state_timeout(state, timeouts_array); + unsigned long val; + + /* No timeout defined for this state? */ + if (!t) + return _osmo_fsm_inst_state_chg(fi, state, 0, 0, file, line); + + if (t->keep_timer) { + int rc = _osmo_fsm_inst_state_chg_keep_timer(fi, state, file, line); + if (t->T && !rc) + fi->T = t->T; + return rc; + } + + val = osmo_tdef_get(tdefs, t->T, OSMO_TDEF_S, default_timeout); + return _osmo_fsm_inst_state_chg(fi, state, val, t->T, file, line); +} + +const struct value_string osmo_tdef_unit_names[] = { + { OSMO_TDEF_S, "s" }, + { OSMO_TDEF_MS, "ms" }, + { OSMO_TDEF_M, "m" }, + { OSMO_TDEF_CUSTOM, "custom-unit" }, + {} +}; + +/*! @} */ diff --git a/src/vty/Makefile.am b/src/vty/Makefile.am index 2e494982..cdde0fa5 100644 --- a/src/vty/Makefile.am +++ b/src/vty/Makefile.am @@ -11,7 +11,8 @@ lib_LTLIBRARIES = libosmovty.la libosmovty_la_SOURCES = buffer.c command.c vty.c vector.c utils.c \ telnet_interface.c logging_vty.c stats_vty.c \ - fsm_vty.c talloc_ctx_vty.c + fsm_vty.c talloc_ctx_vty.c \ + tdef_vty.c libosmovty_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined libosmovty_la_LIBADD = $(top_builddir)/src/libosmocore.la $(TALLOC_LIBS) endif diff --git a/src/vty/tdef_vty.c b/src/vty/tdef_vty.c new file mode 100644 index 00000000..1c6af70a --- /dev/null +++ b/src/vty/tdef_vty.c @@ -0,0 +1,372 @@ +/*! \file tdef_vty.c + * Implementation to configure osmo_tdef Tnnn timers from VTY configuration. + */ +/* (C) 2018-2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * Author: Neels Hofmeyr <neels@hofmeyr.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <limits.h> + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/tdef_vty.h> +#include <osmocom/core/tdef.h> + +/*! \addtogroup Tdef_VTY + * + * VTY API for \ref Tdef. + * + * @{ + * \file tdef_vty.c + */ + +/*! Parse an argument like "T1234", "t1234" or "1234", 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) +{ + long l; + int T; + struct osmo_tdef *t; + char *endptr; + const char *T_nr_str; + + 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') + T_nr_str++; + + errno = 0; + l = strtol(T_nr_str, &endptr, 10); + if (errno || *endptr || l > INT_MAX) { + vty_out(vty, "%% No such timer: '%s'%s", T_str, VTY_NEWLINE); + return NULL; + } + T = l; + + t = osmo_tdef_get_entry(tdefs, T); + if (!t) + vty_out(vty, "%% No such timer: T%d%s", 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) +{ + 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; + t->val = osmo_tdef_vty_parse_val_arg(val_arg, t->default_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) +{ + 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, "T%d = %lu%s%s\t%s (default: %lu%s%s)%s", + t->T, t->val, + t->unit == OSMO_TDEF_CUSTOM ? "" : " ", t->unit == OSMO_TDEF_CUSTOM ? "" : osmo_tdef_unit_name(t->unit), + t->desc, t->default_val, + t->unit == OSMO_TDEF_CUSTOM ? "" : " ", t->unit == OSMO_TDEF_CUSTOM ? "" : osmo_tdef_unit_name(t->unit), + 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_test_config_subnode.c and tdef_vty_test_dynamic.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, "T%d %lu%s", 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; + +/*! \return true iff the first characters of str fully match startswith_str or both are empty. */ +static bool startswith(const char *str, const char *startswith_str) +{ + if (!startswith_str) + return true; + if (!str) + return false; + return strncmp(str, startswith_str, strlen(startswith_str)) == 0; +} + +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 || 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 = argc > 0 ? argv[0] : NULL; + 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] [(<val>|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_node VTY node id at which to add the timer group commands, e.g. CONFIG_NODE. + * \param[in] groups Global timer groups definition. + */ +void osmo_tdef_vty_groups_init(enum node_type parent_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_element_ve(&show_timer_cmd); + install_element(parent_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); +} + +/*! @} */ |