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  | 
