summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am3
-rw-r--r--src/fsm.c4
-rw-r--r--src/tdef.c282
-rw-r--r--src/vty/Makefile.am3
-rw-r--r--src/vty/tdef_vty.c372
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
diff --git a/src/fsm.c b/src/fsm.c
index 0d31f872..6e15ab70 100644
--- a/src/fsm.c
+++ b/src/fsm.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);
+}
+
+/*! @} */