diff options
Diffstat (limited to 'src/vty')
| -rw-r--r-- | src/vty/command.c | 216 | ||||
| -rw-r--r-- | src/vty/vty.c | 8 | 
2 files changed, 200 insertions, 24 deletions
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);  | 
