diff options
-rw-r--r-- | include/osmocom/vty/command.h | 2 | ||||
-rw-r--r-- | include/osmocom/vty/vty.h | 23 | ||||
-rw-r--r-- | src/vty/command.c | 216 | ||||
-rw-r--r-- | src/vty/vty.c | 8 | ||||
-rw-r--r-- | tests/Makefile.am | 13 | ||||
-rw-r--r-- | tests/testsuite.at | 1 | ||||
-rw-r--r-- | tests/vty/fail_not_de-indented.cfg | 3 | ||||
-rw-r--r-- | tests/vty/fail_tabs_and_spaces.cfg | 4 | ||||
-rw-r--r-- | tests/vty/fail_too_much_indent.cfg | 3 | ||||
-rw-r--r-- | tests/vty/ok.cfg | 3 | ||||
-rw-r--r-- | tests/vty/ok_ignore_blank.cfg | 7 | ||||
-rw-r--r-- | tests/vty/ok_ignore_comment.cfg | 7 | ||||
-rw-r--r-- | tests/vty/ok_indented_root.cfg | 3 | ||||
-rw-r--r-- | tests/vty/ok_more_spaces.cfg | 4 | ||||
-rw-r--r-- | tests/vty/ok_tabs.cfg | 4 | ||||
-rw-r--r-- | tests/vty/ok_tabs_and_spaces.cfg | 4 | ||||
-rw-r--r-- | tests/vty/vty_test.c | 20 | ||||
-rw-r--r-- | tests/vty/vty_test.ok | 20 |
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 |