summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/osmocom/vty/command.h2
-rw-r--r--include/osmocom/vty/vty.h23
-rw-r--r--src/vty/command.c216
-rw-r--r--src/vty/vty.c8
-rw-r--r--tests/Makefile.am13
-rw-r--r--tests/testsuite.at1
-rw-r--r--tests/vty/fail_not_de-indented.cfg3
-rw-r--r--tests/vty/fail_tabs_and_spaces.cfg4
-rw-r--r--tests/vty/fail_too_much_indent.cfg3
-rw-r--r--tests/vty/ok.cfg3
-rw-r--r--tests/vty/ok_ignore_blank.cfg7
-rw-r--r--tests/vty/ok_ignore_comment.cfg7
-rw-r--r--tests/vty/ok_indented_root.cfg3
-rw-r--r--tests/vty/ok_more_spaces.cfg4
-rw-r--r--tests/vty/ok_tabs.cfg4
-rw-r--r--tests/vty/ok_tabs_and_spaces.cfg4
-rw-r--r--tests/vty/vty_test.c20
-rw-r--r--tests/vty/vty_test.ok20
18 files changed, 320 insertions, 25 deletions
diff --git a/include/osmocom/vty/command.h b/include/osmocom/vty/command.h
index 0fa5175a..cb2edaaf 100644
--- a/include/osmocom/vty/command.h
+++ b/include/osmocom/vty/command.h
@@ -161,6 +161,7 @@ struct desc {
#define CMD_COMPLETE_MATCH 8
#define CMD_COMPLETE_LIST_MATCH 9
#define CMD_SUCCESS_DAEMON 10
+#define CMD_ERR_INVALID_INDENT 11
/* Argc max counts. */
#define CMD_ARGC_MAX 256
@@ -368,6 +369,7 @@ void vty_install_default(int node_type);
char *argv_concat(const char **argv, int argc, int shift);
vector cmd_make_strvec(const char *);
+int cmd_make_strvec2(const char *string, char **indent, vector *strvec_p);
void cmd_free_strvec(vector);
vector cmd_describe_command();
char **cmd_complete_command();
diff --git a/include/osmocom/vty/vty.h b/include/osmocom/vty/vty.h
index 544e1fa0..02ba03ee 100644
--- a/include/osmocom/vty/vty.h
+++ b/include/osmocom/vty/vty.h
@@ -3,6 +3,8 @@
#include <stdio.h>
#include <stdarg.h>
+#include <osmocom/core/linuxlist.h>
+
/*! \defgroup vty VTY (Virtual TTY) interface
* @{
* \file vty.h */
@@ -45,6 +47,20 @@ enum vty_type {
VTY_SHELL_SERV
};
+struct vty_parent_node {
+ struct llist_head entry;
+
+ /*! private data, specified by creator */
+ void *priv;
+
+ /*! Node status of this vty */
+ int node;
+
+ /*! When reading from a config file, these are the indenting characters expected for children of
+ * this VTY node. */
+ char *indent;
+};
+
/*! Internal representation of a single VTY */
struct vty {
/*! underlying file (if any) */
@@ -134,6 +150,13 @@ struct vty {
/*! In configure mode. */
int config;
+
+ /*! List of parent nodes, last item is the outermost parent. */
+ struct llist_head parent_nodes;
+
+ /*! When reading from a config file, these are the indenting characters expected for children of
+ * the current VTY node. */
+ char *indent;
};
/* Small macro to determine newline is newline only or linefeed needed. */
diff --git a/src/vty/command.c b/src/vty/command.c
index 52c71913..a65b4de5 100644
--- a/src/vty/command.c
+++ b/src/vty/command.c
@@ -190,31 +190,56 @@ void sort_node(void)
}
}
-/*! Breaking up string into each command piece. I assume given
- character is separated by a space character. Return value is a
- vector which includes char ** data element. */
-vector cmd_make_strvec(const char *string)
+/*! Break up string in command tokens. Return leading indents.
+ * \param[in] string String to split.
+ * \param[out] indent If not NULL, return a talloc_strdup of indent characters.
+ * \param[out] strvec_p Returns vector of split tokens, must not be NULL.
+ * \returns CMD_SUCCESS or CMD_ERR_INVALID_INDENT
+ *
+ * If \a indent is passed non-NULL, only simple space ' ' indents are allowed,
+ * so that \a indent can simply return the count of leading spaces.
+ * Otherwise any isspace() characters are allowed for indenting (backwards compat).
+ */
+int cmd_make_strvec2(const char *string, char **indent, vector *strvec_p)
{
const char *cp, *start;
char *token;
int strlen;
vector strvec;
+ *strvec_p = NULL;
+ if (indent)
+ *indent = 0;
+
if (string == NULL)
- return NULL;
+ return CMD_SUCCESS;
cp = string;
/* Skip white spaces. */
- while (isspace((int)*cp) && *cp != '\0')
+ while (isspace((int)*cp) && *cp != '\0') {
+ /* if we're counting indents, we need to be strict about them */
+ if (indent && (*cp != ' ') && (*cp != '\t')) {
+ /* Ignore blank lines, they appear as leading whitespace with line breaks. */
+ if (*cp == '\n' || *cp == '\r') {
+ cp++;
+ string = cp;
+ continue;
+ }
+ return CMD_ERR_INVALID_INDENT;
+ }
cp++;
+ }
+
+ if (indent)
+ *indent = talloc_strndup(tall_vty_cmd_ctx, string, cp - string);
/* Return if there is only white spaces */
if (*cp == '\0')
- return NULL;
+ return CMD_SUCCESS;
if (*cp == '!' || *cp == '#')
- return NULL;
+ return CMD_SUCCESS;
/* Prepare return vector. */
strvec = vector_init(VECTOR_MIN_SIZE);
@@ -236,8 +261,21 @@ vector cmd_make_strvec(const char *string)
cp++;
if (*cp == '\0')
- return strvec;
+ break;
}
+
+ *strvec_p = strvec;
+ return CMD_SUCCESS;
+}
+
+/*! Breaking up string into each command piece. I assume given
+ character is separated by a space character. Return value is a
+ vector which includes char ** data element. */
+vector cmd_make_strvec(const char *string)
+{
+ vector strvec;
+ cmd_make_strvec2(string, NULL, &strvec);
+ return strvec;
}
/*! Free allocated string vector. */
@@ -1947,6 +1985,33 @@ char **cmd_complete_command(vector vline, struct vty *vty, int *status)
return cmd_complete_command_real(vline, vty, status);
}
+static struct vty_parent_node *vty_parent(struct vty *vty)
+{
+ return llist_first_entry_or_null(&vty->parent_nodes,
+ struct vty_parent_node,
+ entry);
+}
+
+static bool vty_pop_parent(struct vty *vty)
+{
+ struct vty_parent_node *parent = vty_parent(vty);
+ if (!parent)
+ return false;
+ llist_del(&parent->entry);
+ vty->node = parent->node;
+ vty->priv = parent->priv;
+ if (vty->indent)
+ talloc_free(vty->indent);
+ vty->indent = parent->indent;
+ talloc_free(parent);
+ return true;
+}
+
+static void vty_clear_parents(struct vty *vty)
+{
+ while (vty_pop_parent(vty));
+}
+
/* return parent node */
/*
* This function MUST eventually converge on a node when called repeatedly,
@@ -1969,24 +2034,33 @@ int vty_go_parent(struct vty *vty)
case VIEW_NODE:
case ENABLE_NODE:
case CONFIG_NODE:
+ vty_clear_parents(vty);
break;
case AUTH_ENABLE_NODE:
vty->node = VIEW_NODE;
+ vty_clear_parents(vty);
break;
case CFG_LOG_NODE:
case VTY_NODE:
vty->node = CONFIG_NODE;
+ vty_clear_parents(vty);
break;
default:
- if (host.app_info->go_parent_cb)
+ if (host.app_info->go_parent_cb) {
host.app_info->go_parent_cb(vty);
- else if (is_config_child(vty))
+ vty_pop_parent(vty);
+ }
+ else if (is_config_child(vty)) {
vty->node = CONFIG_NODE;
- else
+ vty_clear_parents(vty);
+ }
+ else {
vty->node = VIEW_NODE;
+ vty_clear_parents(vty);
+ }
break;
}
@@ -2252,36 +2326,130 @@ cmd_execute_command_strict(vector vline, struct vty *vty,
return (*matched_element->func) (matched_element, vty, argc, argv);
}
+static inline size_t len(const char *str)
+{
+ return str? strlen(str) : 0;
+}
+
+static int indent_cmp(const char *a, const char *b)
+{
+ size_t al, bl;
+ al = len(a);
+ bl = len(b);
+ if (al > bl) {
+ if (bl && strncmp(a, b, bl) != 0)
+ return EINVAL;
+ return 1;
+ }
+ /* al <= bl */
+ if (al && strncmp(a, b, al) != 0)
+ return EINVAL;
+ return (al < bl)? -1 : 0;
+}
+
/* Configration make from file. */
int config_from_file(struct vty *vty, FILE * fp)
{
int ret;
vector vline;
+ char *indent;
+ int cmp;
+ struct vty_parent_node this_node;
+ struct vty_parent_node *parent;
while (fgets(vty->buf, VTY_BUFSIZ, fp)) {
- vline = cmd_make_strvec(vty->buf);
-
- /* In case of comment line */
- if (vline == NULL)
+ indent = NULL;
+ vline = NULL;
+ ret = cmd_make_strvec2(vty->buf, &indent, &vline);
+
+ if (ret != CMD_SUCCESS)
+ goto return_invalid_indent;
+
+ /* In case of comment or empty line */
+ if (vline == NULL) {
+ if (indent) {
+ talloc_free(indent);
+ indent = NULL;
+ }
continue;
- /* Execute configuration command : this is strict match */
- ret = cmd_execute_command_strict(vline, vty, NULL);
+ }
+
+ /* We have a nonempty line. This might be the first on a deeper indenting level, so let's
+ * remember this indent if we don't have one yet. */
+ if (!vty->indent)
+ vty->indent = talloc_strdup(vty, indent);
+
+ cmp = indent_cmp(indent, vty->indent);
+ if (cmp == EINVAL)
+ goto return_invalid_indent;
- /* Try again with setting node to CONFIG_NODE */
- while (ret != CMD_SUCCESS && ret != CMD_WARNING
- && ret != CMD_ERR_NOTHING_TODO
- && is_config_child(vty)) {
+ /* Less indent: go up the parent nodes to find matching amount of less indent. When this
+ * loop exits, we want to have found an exact match, i.e. cmp == 0. */
+ while (cmp < 0) {
vty_go_parent(vty);
- ret = cmd_execute_command_strict(vline, vty, NULL);
+ cmp = indent_cmp(indent, vty->indent);
+ if (cmp == EINVAL)
+ goto return_invalid_indent;
}
+ /* More indent without having entered a child node level? Either the parent node's indent
+ * wasn't hit exactly (e.g. there's a space more than the parent level had further above)
+ * or the indentation increased even though the vty command didn't enter a child. */
+ if (cmp > 0)
+ goto return_invalid_indent;
+
+ /* Remember the current node before the command possibly changes it. */
+ this_node = (struct vty_parent_node){
+ .node = vty->node,
+ .priv = vty->priv,
+ .indent = vty->indent,
+ };
+
+ parent = vty_parent(vty);
+ ret = cmd_execute_command_strict(vline, vty, NULL);
cmd_free_strvec(vline);
if (ret != CMD_SUCCESS && ret != CMD_WARNING
- && ret != CMD_ERR_NOTHING_TODO)
+ && ret != CMD_ERR_NOTHING_TODO) {
+ if (indent) {
+ talloc_free(indent);
+ indent = NULL;
+ }
return ret;
+ }
+
+ /* If we have stepped down into a child node, push a parent frame.
+ * The causality is such: we don't expect every single node entry implementation to push
+ * a parent node entry onto vty->parent_nodes. Instead we expect vty_go_parent() to *pop*
+ * a parent node. Hence if the node changed without the parent node changing, we must
+ * have stepped into a child node (and now expect a deeper indent). */
+ if (vty->node != this_node.node && parent == vty_parent(vty)) {
+ /* Push the parent node. */
+ parent = talloc_zero(vty, struct vty_parent_node);
+ *parent = this_node;
+ llist_add(&parent->entry, &vty->parent_nodes);
+
+ /* The current talloc'ed vty->indent string will now be owned by this parent
+ * struct. Indicate that we don't know what deeper indent characters the user
+ * will choose. */
+ vty->indent = NULL;
+ }
+
+ if (indent) {
+ talloc_free(indent);
+ indent = NULL;
+ }
}
return CMD_SUCCESS;
+
+return_invalid_indent:
+ if (vline)
+ cmd_free_strvec(vline);
+ if (indent) {
+ talloc_free(indent);
+ indent = NULL;
+ }
+ return CMD_ERR_INVALID_INDENT;
}
/* Configration from terminal */
diff --git a/src/vty/vty.c b/src/vty/vty.c
index 113a781c..bd0d2c37 100644
--- a/src/vty/vty.c
+++ b/src/vty/vty.c
@@ -110,6 +110,8 @@ struct vty *vty_new(void)
if (!new)
goto out;
+ INIT_LLIST_HEAD(&new->parent_nodes);
+
new->obuf = buffer_new(new, 0); /* Use default buffer size. */
if (!new->obuf)
goto out_new;
@@ -1480,6 +1482,12 @@ vty_read_file(FILE *confp, void *priv)
case CMD_ERR_NO_MATCH:
fprintf(stderr, "There is no such command.\n");
break;
+ case CMD_ERR_INVALID_INDENT:
+ fprintf(stderr,
+ "Inconsistent indentation -- leading whitespace must match adjacent lines, and\n"
+ "indentation must reflect child node levels. A mix of tabs and spaces is\n"
+ "allowed, but their sequence must not change within a child block.\n");
+ break;
}
fprintf(stderr, "Error occurred during reading the below "
"line:\n%s\n", vty->buf);
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 37378fbc..8935bf72 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -217,7 +217,18 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \
logging/logging_test.ok logging/logging_test.err \
fr/fr_test.ok loggingrb/logging_test.ok \
loggingrb/logging_test.err strrb/strrb_test.ok \
- vty/vty_test.ok comp128/comp128_test.ok \
+ vty/vty_test.ok \
+ vty/fail_not_de-indented.cfg \
+ vty/fail_tabs_and_spaces.cfg \
+ vty/fail_too_much_indent.cfg \
+ vty/ok.cfg \
+ vty/ok_ignore_blank.cfg \
+ vty/ok_ignore_comment.cfg \
+ vty/ok_indented_root.cfg \
+ vty/ok_more_spaces.cfg \
+ vty/ok_tabs_and_spaces.cfg \
+ vty/ok_tabs.cfg \
+ comp128/comp128_test.ok \
utils/utils_test.ok stats/stats_test.ok \
bitvec/bitvec_test.ok msgb/msgb_test.ok bits/bitcomp_test.ok \
sim/sim_test.ok tlv/tlv_test.ok abis/abis_test.ok \
diff --git a/tests/testsuite.at b/tests/testsuite.at
index f148cf5a..1954e66b 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -174,6 +174,7 @@ AT_CLEANUP
AT_SETUP([vty])
AT_KEYWORDS([vty])
cat $abs_srcdir/vty/vty_test.ok > expout
+cp $abs_srcdir/vty/*.cfg .
AT_CHECK([$abs_top_builddir/tests/vty/vty_test], [0], [expout], [ignore])
AT_CLEANUP
diff --git a/tests/vty/fail_not_de-indented.cfg b/tests/vty/fail_not_de-indented.cfg
new file mode 100644
index 00000000..5af833da
--- /dev/null
+++ b/tests/vty/fail_not_de-indented.cfg
@@ -0,0 +1,3 @@
+line vty
+ no login
+ log stderr
diff --git a/tests/vty/fail_tabs_and_spaces.cfg b/tests/vty/fail_tabs_and_spaces.cfg
new file mode 100644
index 00000000..fa6ce059
--- /dev/null
+++ b/tests/vty/fail_tabs_and_spaces.cfg
@@ -0,0 +1,4 @@
+line vty
+ no login
+ no login
+log stderr
diff --git a/tests/vty/fail_too_much_indent.cfg b/tests/vty/fail_too_much_indent.cfg
new file mode 100644
index 00000000..bacb3e1e
--- /dev/null
+++ b/tests/vty/fail_too_much_indent.cfg
@@ -0,0 +1,3 @@
+line vty
+ no login
+ log stderr
diff --git a/tests/vty/ok.cfg b/tests/vty/ok.cfg
new file mode 100644
index 00000000..d5ef23e4
--- /dev/null
+++ b/tests/vty/ok.cfg
@@ -0,0 +1,3 @@
+line vty
+ no login
+log stderr
diff --git a/tests/vty/ok_ignore_blank.cfg b/tests/vty/ok_ignore_blank.cfg
new file mode 100644
index 00000000..d16ff64e
--- /dev/null
+++ b/tests/vty/ok_ignore_blank.cfg
@@ -0,0 +1,7 @@
+line vty
+
+ no login
+
+ no login
+
+log stderr
diff --git a/tests/vty/ok_ignore_comment.cfg b/tests/vty/ok_ignore_comment.cfg
new file mode 100644
index 00000000..5813fc7c
--- /dev/null
+++ b/tests/vty/ok_ignore_comment.cfg
@@ -0,0 +1,7 @@
+line vty
+ ! comment
+ no login
+! comment
+ no login
+ ! comment
+log stderr
diff --git a/tests/vty/ok_indented_root.cfg b/tests/vty/ok_indented_root.cfg
new file mode 100644
index 00000000..313c6742
--- /dev/null
+++ b/tests/vty/ok_indented_root.cfg
@@ -0,0 +1,3 @@
+ line vty
+ no login
+ log stderr
diff --git a/tests/vty/ok_more_spaces.cfg b/tests/vty/ok_more_spaces.cfg
new file mode 100644
index 00000000..b66a9c21
--- /dev/null
+++ b/tests/vty/ok_more_spaces.cfg
@@ -0,0 +1,4 @@
+line vty
+ no login
+ no login
+log stderr
diff --git a/tests/vty/ok_tabs.cfg b/tests/vty/ok_tabs.cfg
new file mode 100644
index 00000000..e94609b7
--- /dev/null
+++ b/tests/vty/ok_tabs.cfg
@@ -0,0 +1,4 @@
+line vty
+ no login
+ no login
+log stderr
diff --git a/tests/vty/ok_tabs_and_spaces.cfg b/tests/vty/ok_tabs_and_spaces.cfg
new file mode 100644
index 00000000..2049b732
--- /dev/null
+++ b/tests/vty/ok_tabs_and_spaces.cfg
@@ -0,0 +1,4 @@
+line vty
+ no login
+ no login
+log stderr
diff --git a/tests/vty/vty_test.c b/tests/vty/vty_test.c
index d84bf419..eba9995c 100644
--- a/tests/vty/vty_test.c
+++ b/tests/vty/vty_test.c
@@ -19,6 +19,7 @@
#include <stdio.h>
#include <string.h>
+#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
@@ -288,6 +289,15 @@ static void test_stats_vty(void)
destroy_test_vty(&test, vty);
}
+void test_exit_by_indent(const char *fname, int expect_rc)
+{
+ int rc;
+ printf("reading file %s, expecting rc=%d\n", fname, expect_rc);
+ rc = vty_read_config_file(fname, NULL);
+ printf("got rc=%d\n", rc);
+ OSMO_ASSERT(rc == expect_rc);
+}
+
int main(int argc, char **argv)
{
struct vty_app_info vty_info = {
@@ -322,6 +332,16 @@ int main(int argc, char **argv)
test_cmd_string_from_valstr();
test_node_tree_structure();
test_stats_vty();
+ test_exit_by_indent("ok.cfg", 0);
+ test_exit_by_indent("ok_more_spaces.cfg", 0);
+ test_exit_by_indent("ok_tabs.cfg", 0);
+ test_exit_by_indent("ok_tabs_and_spaces.cfg", 0);
+ test_exit_by_indent("ok_ignore_comment.cfg", 0);
+ test_exit_by_indent("ok_ignore_blank.cfg", 0);
+ test_exit_by_indent("fail_not_de-indented.cfg", -EINVAL);
+ test_exit_by_indent("fail_too_much_indent.cfg", -EINVAL);
+ test_exit_by_indent("fail_tabs_and_spaces.cfg", -EINVAL);
+ test_exit_by_indent("ok_indented_root.cfg", 0);
/* Leak check */
OSMO_ASSERT(talloc_total_blocks(stats_ctx) == 1);
diff --git a/tests/vty/vty_test.ok b/tests/vty/vty_test.ok
index 2b6d5a67..b2df1a11 100644
--- a/tests/vty/vty_test.ok
+++ b/tests/vty/vty_test.ok
@@ -108,4 +108,24 @@ Going to execute 'no stats reporter log'
Returned: 0, Current node: 4 '%s(config)# '
Going to execute 'no stats reporter statsd'
Returned: 0, Current node: 4 '%s(config)# '
+reading file ok.cfg, expecting rc=0
+got rc=0
+reading file ok_more_spaces.cfg, expecting rc=0
+got rc=0
+reading file ok_tabs.cfg, expecting rc=0
+got rc=0
+reading file ok_tabs_and_spaces.cfg, expecting rc=0
+got rc=0
+reading file ok_ignore_comment.cfg, expecting rc=0
+got rc=0
+reading file ok_ignore_blank.cfg, expecting rc=0
+got rc=0
+reading file fail_not_de-indented.cfg, expecting rc=-22
+got rc=-22
+reading file fail_too_much_indent.cfg, expecting rc=-22
+got rc=-22
+reading file fail_tabs_and_spaces.cfg, expecting rc=-22
+got rc=-22
+reading file ok_indented_root.cfg, expecting rc=0
+got rc=0
All tests passed