summaryrefslogtreecommitdiffstats
path: root/lib/command_parse.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/command_parse.c')
-rw-r--r--lib/command_parse.c1278
1 files changed, 965 insertions, 313 deletions
diff --git a/lib/command_parse.c b/lib/command_parse.c
index c64ee3ba..ba1b217e 100644
--- a/lib/command_parse.c
+++ b/lib/command_parse.c
@@ -963,35 +963,184 @@ cmd_cmp_range_items(const cmd_item a, const cmd_item b)
*/
/*------------------------------------------------------------------------------
- * Make a brand new token object
+ * Create a new, empty token_vector with room for a dozen arguments (initially).
*/
-Private cmd_token
-cmd_token_new(void)
+static token_vector
+cmd_token_vector_new(void)
{
- return XCALLOC(MTYPE_TOKEN, sizeof(struct cmd_token)) ;
+ token_vector tv ;
- /* Zeroising the new structure sets:
- *
- * type = 0 -- cmd_tok_eol
- * qs = zeroised qstring -- empty string
- * complete = 0 -- false
- *
- * tp = 0
- * lp = zeroised elstring -- empty string
- */
- confirm(cmd_tok_eol == 0) ;
- confirm(QSTRING_INIT_ALL_ZEROS) ;
- confirm(ELSTRING_INIT_ALL_ZEROS) ;
+ tv = XCALLOC(MTYPE_CMD_PARSED, sizeof(token_vector_t)) ;
+
+ vector_init_new(tv->body, 12) ;
+
+ return tv ;
} ;
/*------------------------------------------------------------------------------
- * Empty token object and free it.
+ * Empty token_vector and release all memory if required.
*/
-static void
-cmd_token_free(cmd_token t)
+static token_vector
+cmd_token_vector_free(token_vector tv)
+{
+ if (tv != NULL)
+ {
+ cmd_token t ;
+
+ /* Give back all the token objects and release vector body */
+ while ((t = vector_ream(tv->body, keep_it)) != NULL)
+ {
+ qs_reset(t->qs, keep_it) ; /* discard body of qstring */
+ XFREE(MTYPE_TOKEN, t) ;
+ } ;
+
+ XFREE(MTYPE_CMD_PARSED, tv) ;
+ } ;
+
+ return NULL ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get string value of given token.
+ *
+ * Returns an empty (not NULL) string if token NULL or no string.
+ */
+inline static char*
+cmd_token_make_string(cmd_token t)
+{
+ if (t->term)
+ return qs_char_nn(t->qs) ;
+ else
+ {
+ t->term = true ;
+ return qs_make_string(t->qs) ;
+ }
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get i'th token from given token vector -- zero origin
+ */
+Inline cmd_token
+cmd_token_get(token_vector tv, vector_index_t i)
+{
+ return vector_get_item(tv->body, i) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set i'th token from given token vector -- zero origin
+ */
+inline static void
+cmd_token_set(token_vector tv, vector_index_t i,
+ cmd_token_type_t type, const char* p, usize len, usize tp)
+{
+ cmd_token t = cmd_token_get(tv, i) ;
+
+ if (t == NULL)
+ {
+ /* Make a brand new token object */
+ t = XCALLOC(MTYPE_TOKEN, sizeof(struct cmd_token)) ;
+
+ /* Zeroising the new structure sets:
+ *
+ * type = 0 -- cmd_tok_eol
+ * qs = zeroised qstring -- empty string
+ * complete = 0 -- false
+ *
+ * tp = 0
+ * lp = zeroised elstring -- empty string
+ */
+ confirm(cmd_tok_eol == 0) ;
+ confirm(QSTRING_INIT_ALL_ZEROS) ;
+ confirm(ELSTRING_INIT_ALL_ZEROS) ;
+
+ vector_set_item(tv->body, i, t) ;
+ } ;
+
+ t->type = type ;
+ t->term = false ;
+ t->tp = tp ;
+
+ qs_set_alias_n(t->qs, p, len) ;
+ qs_els_copy_nn(t->ot, t->qs) ;
+} ;
+
+
+/*------------------------------------------------------------------------------
+ * Get one or more original token values, concatenated with space between each.
+ *
+ * Returns a brand new qstring that must be discarded after use.
+ */
+extern qstring
+cmd_tokens_concat(cmd_parsed parsed, uint ti, uint nt)
+{
+ cmd_token t ;
+ qstring qs ;
+
+ assert(nt >= 2) ;
+
+ t = cmd_token_get(parsed->tokens, ++ti) ;
+ qs = qs_set_els(NULL, t->ot) ;
+
+ while (--nt >= 2)
+ {
+ t = cmd_token_get(parsed->tokens, ++ti) ;
+ qs_append_str(qs, " ") ;
+ qs_append_els(qs, t->ot) ;
+ } ;
+
+ return qs ;
+} ;
+
+/*==============================================================================
+ * Argument vector
+ */
+
+/*------------------------------------------------------------------------------
+ * Create a new, empty arg_vector with room for a dozen arguments (initially).
+ */
+static arg_vector
+cmd_arg_vector_new(void)
{
- qs_reset(t->qs, keep_it) ; /* discard body of qstring */
- XFREE(MTYPE_TOKEN, t) ;
+ arg_vector args ;
+
+ args = XCALLOC(MTYPE_CMD_PARSED, sizeof(arg_vector_t)) ;
+
+ vector_init_new(args->body, 12) ;
+
+ return args ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Empty arg_vector and release all memory if required.
+ */
+static arg_vector
+cmd_arg_vector_free(arg_vector args)
+{
+ if (args != NULL)
+ {
+ vector_reset(args->body, keep_it) ;
+ XFREE(MTYPE_CMD_PARSED, args) ;
+ } ;
+
+ return NULL ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Empty the body of the arg_vector object in cmd_parsed.
+ */
+Inline void
+cmd_arg_vector_empty(cmd_parsed parsed)
+{
+ vector_set_length(parsed->args->body, 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Push argument to the argument vector.
+ */
+Inline void
+cmd_arg_vector_push(cmd_parsed parsed, char* arg)
+{
+ vector_push_item(parsed->args->body, arg) ;
} ;
/*==============================================================================
@@ -999,31 +1148,31 @@ cmd_token_free(cmd_token t)
*/
/*------------------------------------------------------------------------------
- * Initialise a new cmd_parsed object, allocating if required
+ * Allocate and initialise a new cmd_parsed object
*/
extern cmd_parsed
-cmd_parsed_init_new(cmd_parsed parsed)
+cmd_parsed_new(void)
{
- if (parsed == NULL)
- parsed = XCALLOC(MTYPE_CMD_PARSED, sizeof(*parsed)) ;
- else
- memset(parsed, 0, sizeof(*parsed)) ;
+ cmd_parsed parsed ;
+
+ parsed = XCALLOC(MTYPE_CMD_PARSED, sizeof(cmd_parsed_t)) ;
/* Zeroising the structure has set:
*
- * parts = 0 -- cleared by cmd_tokenise()
- * tok_total = 0 -- set by cmd_tokenise()
+ * parts = 0 -- cleared by cmd_tokenize()
+ * tok_total = 0 -- set by cmd_tokenize()
*
- * elen = 0 -- set by cmd_tokenise()
- * tsp = 0 -- set by cmd_tokenise()
+ * elen = 0 -- set by cmd_tokenize()
+ * tsp = 0 -- set by cmd_tokenize()
*
* cmd = NULL -- no command yet
* cnode = 0 -- not set
+ * nnode = 0 -- not set
*
- * num_tokens = 0 -- set by cmd_tokenise()
- * tokens = all zeros -- empty token vector
+ * num_tokens = 0 -- set by cmd_tokenize()
+ * tokens = NULL -- see below
*
- * args = all zeros -- empty vector of arguments
+ * args = NULL -- see below
*
* emess = NULL -- no error yet
* eloc = 0 -- no error location
@@ -1045,8 +1194,8 @@ cmd_parsed_init_new(cmd_parsed parsed)
* cti ) set by cmd_token_position()
* rp )
*
- * cmd_v = all zeros -- empty vector of filtered commands
- * item_v = all zeros -- empty vector of filtered items
+ * cmd_v = NULL -- no vector of filtered commands
+ * item_v = NULL -- no vector of filtered items
*
* strongest )
* best_complete ) set by cmd_filter_prepare()
@@ -1054,37 +1203,33 @@ cmd_parsed_init_new(cmd_parsed parsed)
* strict )
*/
confirm(cmd_pipe_none == 0) ;
- confirm(TOKEN_VECTOR_INIT_ALL_ZEROS) ;
- confirm(ARG_VECTOR_INIT_ALL_ZEROS) ;
+
+ parsed->tokens = cmd_token_vector_new() ;
+ parsed->args = cmd_arg_vector_new() ;
return parsed ;
} ;
/*------------------------------------------------------------------------------
- * Empty out and (if required) free a cmd_parsed object
+ * Empty out and free a cmd_parsed object
*/
extern cmd_parsed
-cmd_parsed_reset(cmd_parsed parsed, free_keep_b free_structure)
+cmd_parsed_free(cmd_parsed parsed)
{
if (parsed != NULL)
{
- cmd_token t ;
+ parsed->tokens = cmd_token_vector_free(parsed->tokens) ;
+ parsed->args = cmd_arg_vector_free(parsed->args) ;
- /* Give back all the token objects and release vector body */
- while ((t = vector_ream(parsed->tokens->body, keep_it)) != NULL)
- cmd_token_free(t) ;
+ parsed->cmd_v = vector_reset(parsed->cmd_v, free_it) ;
+ parsed->item_v = vector_reset(parsed->item_v, free_it) ;
- vector_reset(parsed->args->body, keep_it) ; /* embedded */
- vector_reset(parsed->cmd_v, keep_it) ; /* embedded */
- vector_reset(parsed->item_v, keep_it) ; /* embedded */
+ parsed->emess = qs_reset(parsed->emess, free_it) ;
- if (free_structure)
- XFREE(MTYPE_CMD_PARSED, parsed) ; /* sets parsed = NULL */
- else
- cmd_parsed_init_new(parsed) ;
+ XFREE(MTYPE_CMD_PARSED, parsed) ;
} ;
- return parsed ;
+ return NULL ;
} ;
/*==============================================================================
@@ -1092,6 +1237,9 @@ cmd_parsed_reset(cmd_parsed parsed, free_keep_b free_structure)
*
*
*/
+static cmd_return_code_t
+cmd_set_parse_error(cmd_parsed parsed, cmd_token t, usize off,
+ const char* format, ...) PRINTF_ATTRIBUTE(4, 5) ;
/*------------------------------------------------------------------------------
* Register a parsing error.
@@ -1113,27 +1261,72 @@ cmd_parsed_reset(cmd_parsed parsed, free_keep_b free_structure)
* Returns: CMD_ERR_PARSING -- which MUST only be returned if p
*/
static cmd_return_code_t
-cmd_parse_error(cmd_parsed parsed, cmd_token t, usize off, const char* mess)
+cmd_set_parse_error(cmd_parsed parsed, cmd_token t, usize off,
+ const char* format, ...)
{
- parsed->emess = mess ;
- parsed->eloc = t->tp + off ;
+ va_list args ;
+
+ qs_clear(parsed->emess) ;
+
+ va_start (args, format);
+ parsed->emess = qs_vprintf(parsed->emess, format, args);
+ va_end (args) ;
+
+ if (t != NULL)
+ parsed->eloc = t->tp + off ;
+ else
+ parsed->eloc = -1 ;
return CMD_ERR_PARSING ;
} ;
+/*------------------------------------------------------------------------------
+ * Register a parsing error.
+ *
+ * Takes token in which parsing error was detected, and an offset from the
+ * start of that, for the location of the error. If the offset is not zero,
+ * it must be an offset in the original token (!).
+ *
+ * The message is a simple constant string (!).
+ *
+ * The message will be output as: ..........^ pointing to the location
+ * followed by: % <mess>\n
+ *
+ * (The mess does not need to include the '%' or the '\n'.)
+ *
+ * Sets: parsed->emess
+ * parsed->eloc
+ *
+ * Returns: CMD_ERR_PARSING -- which MUST only be returned if p
+ */
+extern void
+cmd_get_parse_error(vio_fifo ebuf, cmd_parsed parsed, uint indent)
+{
+ if (parsed->eloc >= 0)
+ {
+ qstring here ;
+
+ here = qs_set_fill(NULL, indent + parsed->eloc, "....") ;
+ vio_fifo_put_bytes(ebuf, qs_body_nn(here), qs_len_nn(here)) ;
+ qs_reset(here, free_it) ;
+ } ;
+
+ vio_fifo_printf(ebuf, "^\n%% %s\n", qs_make_string(parsed->emess)) ;
+} ;
+
/*==============================================================================
* Lexical level stuff
*/
/*------------------------------------------------------------------------------
- * Take elstring and see if it is empty -- only whitespace and/or comment
+ * Take qstring and see if it is empty -- only whitespace and/or comment
*/
extern bool
-cmd_is_empty(elstring line)
+cmd_is_empty(qstring line)
{
cpp_t lp ;
- els_cpp(lp, line) ; /* NULL -> NULL */
+ qs_cpp(lp, line) ; /* NULL -> NULL */
while (lp->p < lp->e)
{
@@ -1156,87 +1349,82 @@ cmd_is_empty(elstring line)
* without requiring whitespace separation, also do not want to intrude into
* quoted or escaped stuff. So limit the characters that are reserved.
*/
-static inline bool
+static bool
cmd_pipe_reserved_char(char ch)
{
return strchr("<|>%&*+-=?", ch) != NULL ;
} ;
/*------------------------------------------------------------------------------
- * Take elstring and break it into tokens.
- *
- * Discards leading and trailing ' ' or '\t'.
+ * Take qstring and break it into tokens.
*
* Expects string to have been preprocessed, if required, to ensure that any
* unwanted control characters have been removed. This code only recognises
- * '\t' and treats it as whitespace.
+ * '\t' and treats it as whitespace (so any other control characters will
+ * end up as part of a token).
*
- * Anything between '....' is ignored by the tokenizer. NB: this follows the
- * shell convention, so '\' is also ignored and there is no way to include "'"
- * in a single quoted string.
+ * Ignores leading and trailing ' ' or '\t' (whitespace).
*
- * Anything immediately preceded by '\' is ignored by the tokenizer. This
- * includes blanks and quotes.
+ * Have "full_lex" flag -- to distinguish old and new.
*
- * Anything inside "...." is ignored by the tokenizer, including '\"' escapes.
+ * If not full_lex, we have this simple tokenization:
*
- * Unbalanced "'" or '"' are treated as if eol was a "'" or '"'.
+ * * tokens are separated by one or more whitespace characters.
*
- * Of the things which are not ignored by the tokenizer:
+ * * if the first non-whitespace character is '!', this is a comment line.
*
- * * tokens are separated by whitespace -- one ' ' or '\t' characters
- * The whitespace is discarded.
+ * For the "full_lex" we more or less follow the usual shell conventions,
+ * except:
*
- * * tokens which start with any of:
+ * * can have '!' as well as '#' to start comments
*
- * '!', '#', '<' and '>'
+ * * may only have '|' at the start of a line. '>|' must be used for
+ * piping command output to shell command. So '|' is, effectively, a
+ * "shell command prefix".
*
- * terminate themselves, as follows:
+ * Limiting '|' in this way removes problems with regular expressions.
*
- * - from '!' or '#' to end of line is a comment token.
+ * Also allows a <| shell to use pipes !!
*
- * - '<' followed by pipe_reserved_chars is a token (in_pipe)
+ * So the "full_lex" will:
*
- * - '>' followed by pipe_reserved_chars is a token (out_pipe).
+ * * ignore anything between '....' -- as per shell convention, '\' is
+ * ignored and there is no way to include "'" in a single quoted string.
*
- * See above for pipe_reserved_
+ * * ignore anything immediately preceded by '\' -- including space
+ * (or tab converted to a space) and double quote characters.
*
- * NB: this means that for '!', '#', '<' and '>' to be significant, they
- * MUST be preceeded by whitespace (or start of line). This ever so
- * slightly reduces the impact of the new lexical conventions.
+ * * ignore anything between "...." -- including '\"' escapes.
*
- * NB: the tokenization roughly mimics the (POSIX) standard shell. The
- * differences are:
+ * * unbalanced "'" or '"' are treated as if eol was a "'" or '"'.
*
- * '|' is *not* a pipe ('>|' is), because '|' is a character in the
- * regex repertoire.
+ * Except when ignored by the rules above, the "full lex" will recognise
+ * the following:
*
- * '<' and '>' do not terminate a token -- so are only significant
- * at the start of a token.
+ * * tokens are separated by whitespace -- one ' ' or '\t' characters.
+ * Whitespace before, after or between tokens is not part of the tokens.
*
- * '!' is not a comment for the shell.
+ * * the character '|' is significant at the start of a line (after any
+ * leading whitespace) only. It is then the start of an out_pipe
+ * token -- so self terminates after any pipe_reserved_chars.
*
- * The requirement for whitespace (or start of line) before '#' is
- * consistent with the shell.
+ * * the characters '!', '#' are significant only at the start of a token,
+ * and then from there to end of line is comment.
*
- * The handling of '...' follows the standard shell.
+ * Note that this means that comment after a token must be separated by
+ * at least one space from that token.
*
- * The tokenization does not remove any " ' or \ characters, that is left
- * for a later stage, where context may affect the handling.
+ * * the characters '<' and '>' are separators -- they terminate any
+ * preceding token. They are in_pipe or out_pipe tokens, and self
+ * terminate after any pipe_reserved_chars.
*
- * NB: any control characters other than '\t' are accepted as part of the
- * current token !
+ * The tokenization does not remove any " ' or \ characters, that is left for
+ * a later stage, where context may affect the handling.
*
* The tokens returned contain all the original characters of the line, except
* for the removal of ' ' and '\t' between tokens and at the end of the line.
*
- * Note: all the tokens in the vector have at least one character, and no
- * entries are NULL.
- *
- * NB: it is the callers responsibility to release the token objects in due
- * course.
- *
- * NB: the elstring containing the line to be tokenised MUST NOT change
+ * NB: the elstring containing the line to be tokenized MUST NOT change
* until the parsed object is finished with.
*
* Returns: the types of all tokens or'd together.
@@ -1254,7 +1442,7 @@ cmd_pipe_reserved_char(char ch)
* Note that the num_tokens does not include the cmd_tok_eol on the end.
*/
extern void
-cmd_tokenise(cmd_parsed parsed, qstring line)
+cmd_tokenize(cmd_parsed parsed, qstring line, bool full_lex)
{
cpp_t lp ;
const char *cp, *tp ;
@@ -1300,59 +1488,94 @@ cmd_tokenise(cmd_parsed parsed, qstring line)
case '\'': /* proceed to matching ' or end */
++cp ;
- type |= cmd_tok_sq ;
- while (cp < lp->e)
+ if (full_lex)
{
- if (*cp++ == '\'')
- break ;
+ type |= cmd_tok_sq ;
+ while (cp < lp->e)
+ {
+ if (*cp++ == '\'')
+ break ;
+ } ;
} ;
break ;
case '\\': /* step past escaped character, if any */
++cp ;
- type |= cmd_tok_esc ;
- if (cp < lp->e)
- ++cp ;
+ if (full_lex)
+ {
+ type |= cmd_tok_esc ;
+ if (cp < lp->e)
+ ++cp ;
+ } ;
break ;
case '"': /* proceed to matching " or end... */
++cp ;
- type |= cmd_tok_dq ;
- while (cp < lp->e) /* NB: do not register \ separately */
+ if (full_lex)
{
- if (*cp++ == '"')
- if (*(cp - 2) != '\\') /* ignore escaped " */
- break ;
+ type |= cmd_tok_dq ;
+ while (cp < lp->e) /* NB: do not register \ separately */
+ {
+ if (*cp++ == '"')
+ if (*(cp - 2) != '\\') /* ignore escaped " */
+ break ;
+ } ;
} ;
break ;
- case '>': /* '>' special at start */
- end = (cp == sp) ;
- ++cp ;
- if (end) /* if special */
+ case '|': /* only at start of line */
+ if (full_lex && (nt == 0) && (cp == sp))
{
- type = cmd_tok_out_pipe ;
- while ((cp < lp->e) && cmd_pipe_reserved_char(*cp))
- ++cp ;
- } ;
+ type = cmd_tok_out_shell ;
+ do ++cp ;
+ while ((cp < lp->e) && cmd_pipe_reserved_char(*cp)) ;
+ end = true ;
+ }
+ else
+ ++cp ;
break ;
- case '<': /* '<' special at start */
- end = (cp == sp) ;
- ++cp ;
- if (end) /* if special */
+ case '>': /* '>' is a separator */
+ if (full_lex)
{
- type = cmd_tok_in_pipe ;
- while ((cp < lp->e) && cmd_pipe_reserved_char(*cp))
- ++cp ;
- } ;
+ if (cp == sp)
+ {
+ type = cmd_tok_out_pipe ;
+ do ++cp ;
+ while ((cp < lp->e) && cmd_pipe_reserved_char(*cp)) ;
+ } ;
+ end = true ;
+ }
+ else
+ ++cp ;
break ;
- case '!': /* '!' and '#' special at start */
- case '#':
- if ((cp == sp) && (nt == 0))
+ case '<': /* '<' is a separator */
+ if (full_lex)
{
+ if (cp == sp)
+ {
+ type = cmd_tok_in_pipe ;
+ do ++cp ;
+ while ((cp < lp->e) && cmd_pipe_reserved_char(*cp)) ;
+ } ;
end = true ;
+ }
+ else
+ ++cp ;
+ break ;
+
+ case '#':
+ if (!full_lex)
+ {
+ ++cp ;
+ break ;
+ } ;
+ fall_through ; /* treat as '!'. */
+
+ case '!': /* '!' and '#' special at token start */
+ if ((cp == sp) && (full_lex || (nt == 0)))
+ {
type = cmd_tok_comment ;
cp = lp->e ;
}
@@ -1398,68 +1621,237 @@ cmd_tokenise(cmd_parsed parsed, qstring line)
lp->e, 0, lp->e - lp->p) ;
} ;
+
+
+
+
+/*==============================================================================
+ * VTY Command Line Input Pipe
+ *
+ * Here are the mechanics which support the:
+ *
+ * < file_name
+ *
+ * <| shell_command
+ *
+ * and the:
+ *
+ * > file_name
+ * >> file_name
+ * | shell_command
+ *
+ *==============================================================================
+ * The file_name handling
+ *
+ * Two directories are supported:
+ *
+ * "home" -- being the root for configuration files
+ *
+ * "cd" -- being the root for relative filenames, in the usual way
+ *
+ * "here" -- being the directory for the enclosing "< filename"
+ * see below for more detailed semantics
+ *
+ * There are the global values:
+ *
+ * config home -- defaults to directory for configuration file.
+ * may be set by command.
+ *
+ * config cd -- defaults to cwd at start up
+ *
+ * There are the local values, within a given CLI instance (ie VTY):
+ *
+ * cli home -- set to config home when CLI instance starts, or when
+ * config home is set within the CLI.
+ *
+ * cli cd -- similarly
+ *
+ * cli here -- outside < is same as cli home, unless explicitly set
+ * - set by cli here
+ * - pushed each time executes <, and set to directory for
+ * the < filename.
+ * - pushed each time executes <| and left unchanged
+ * - popped each time exits < or <|
+ * - set to parent by "no cli here" inside <, or to
+ * default state outside <
+ *
+ * And then the filename syntax:
+ *
+ * /path -- absolute path -- in the usual way
+ *
+ * path -- path wrt "cli cd" -- in the usual way
+ *
+ * ~/path -- path in "cli home" -- so "config" stuff
+ *
+ * ~./path -- path in "here" -- so wrt to enclosing < file
+ *
+ *==============================================================================
+ * The Input Pipe Commands
+ *
+ * These are "universal commands".
+ *
+ * They are:
+ *
+ * < filename -- filename as above
+ *
+ * treat contents of given file as command lines.
+ * (That may include further < commands.)
+ *
+ * See notes on cli here for pushing/popping the here
+ * directory.
+ *
+ * TODO: Filename and quotes ??
+ *
+ * <| shell_command -- the shell command is executed "as is" by system().
+ *
+ * the stdout and stderr are collected.
+ *
+ * treats stdout as command lines.
+ * (That may include further < commands.)
+ *
+ * anything from stderr is sent to the VTY output.
+ *
+ * As far as the top level CLI is concerned, these are discrete commands.
+ * That is to say:
+ *
+ * -- except where blocked while reading the "pipe", all commands are
+ * executed one after another, in one Routing Engine operation.
+ *
+ * -- in any event, all output is gathered in the VTY buffering, and will
+ * be sent to the console (or where ever) only when the outermost command
+ * completes.
+ *
+ * There are three options associated with the output from a < operation:
+ *
+ * -- suppress command line reflect
+ *
+ * whether to suppress reflect of commands to the VTY before they are
+ * dispatched.
+ *
+ * The default is not to suppress command line reflect.
+ *
+ * -- suppress command results
+ *
+ * whether to suppress any output generated by each command.
+ *
+ * The default is not to suppress command results.
+ *
+ * -- suppress "more"
+ *
+ * whether to do "--more--", if currently applies, when finally
+ * outputting all the command results.
+ *
+ * The default is not to suppress "more".
+ *
+ * This option can only be set for the outermost < operation.
+ *
+ * Remembering that all output occurs in one go when the outermost < operation
+ * completes.
+ *
+ * The default is to show everything and implement "--more--", pretty much as
+ * if the commands had been typed in. Except that "--more--" applies to
+ * everything (including the command lines) together, rather than to the output
+ * of each command individually.
+ *
+ * These options may be changed by flags attached to the <, as follows:
+ *
+ * ! suppress command line reflect
+ *
+ * ? suppress "--more--"
+ *
+ * * suppress command output
+ *
+ * TODO: cli xxx commands for reflect and output suppression and notes ....
+ *
+ * TODO: Error handling....
+ *
+ * TODO: Closing the CLI....
+ *
+ * TODO: Time out and the CLI....
+ *
+ *
+ */
+
+
+
+
+/*------------------------------------------------------------------------------
+ * Get next cpp char & step -- unless at end of cpp.
+ */
+inline static char
+cpp_getch(cpp p)
+{
+ if (p->p < p->e)
+ return *p->p++ ;
+ else
+ return '\0' ;
+}
+
/*------------------------------------------------------------------------------
* Process in-pipe token and set the required bits in the pipe type word
*
- * Known tokens are: < <| <+ <|+
+ * Known tokens are: < <|
+ * Known options are: +
*/
static cmd_return_code_t
cmd_parse_in_pipe(cmd_parsed parsed, cmd_token t)
{
cpp_t p ;
- bool ok ;
+ bool ok ;
els_cpp(p, t->ot) ;
- ok = ((p->p < p->e) && (*p->p++ == '<')) ;
+ ok = true ;
- if (ok)
- {
- /* First character after '<' may qualify the type of the pipe */
- parsed->in_pipe = cmd_pipe_file ;
+ switch (cpp_getch(p))
+ {
+ case '<':
+ switch (cpp_getch(p))
+ {
+ default:
+ --p->p ;
+ fall_through ;
- if (p->p < p->e)
- {
- switch (*p->p++)
- {
- case '|':
- parsed->in_pipe = cmd_pipe_shell ;
- break ;
+ case '\0':
+ parsed->in_pipe = cmd_pipe_file ;
+ break ;
- default:
- --p->p ; /* put back */
- break ;
- }
- } ;
+ case '|':
+ parsed->in_pipe = cmd_pipe_shell ;
+ break ;
+ } ;
+ break ;
- /* Deal with option characters */
- while (ok && (p->p < p->e))
- {
- /* Eat option character, if recognise it. */
- switch (*p->p++)
- {
- case '+': /* reflect command lines */
- parsed->in_pipe |= cmd_pipe_reflect ;
- break ;
+ default:
+ ok = false ;
+ break ;
+ } ;
- default:
- --p->p ;
- ok = false ;
- break ;
- } ;
- } ;
+ while (ok && (p->p < p->e))
+ {
+ switch (*p->p++)
+ {
+ case '+':
+ parsed->in_pipe |= cmd_pipe_reflect ;
+ break ;
+
+ default:
+ ok = false ;
+ break ;
+ } ;
} ;
- if (!ok)
- return cmd_parse_error(parsed, t, 0, "invalid 'pipe in'") ;
+ if (ok)
+ return CMD_SUCCESS ;
- return CMD_SUCCESS ;
+ return cmd_set_parse_error(parsed, t, 0, "invalid 'pipe in'") ;
} ;
/*------------------------------------------------------------------------------
* Process out-pipe token and set the required bits in the pipe type word
*
- * Known tokens are: > >> >| >*
+ * Known tokens are: | > >> >| >*
+ * Known options are: none
*/
static cmd_return_code_t
cmd_parse_out_pipe(cmd_parsed parsed, cmd_token t)
@@ -1469,46 +1861,58 @@ cmd_parse_out_pipe(cmd_parsed parsed, cmd_token t)
els_cpp(p, t->ot) ;
- ok = ((p->p < p->e) && (*p->p++ == '>')) ;
+ ok = true ;
- if (ok)
- {
- /* First character after '>' may qualify the type of the pipe */
- parsed->out_pipe = cmd_pipe_file ;
+ switch (cpp_getch(p))
+ {
+ case '|':
+ parsed->out_pipe = cmd_pipe_shell | cmd_pipe_shell_only ;
+ break ;
- if (p->p < p->e)
- {
- switch (*p->p++)
- {
- case '>':
- parsed->out_pipe |= cmd_pipe_append ;
- break ;
+ case '>':
+ switch (cpp_getch(p))
+ {
+ default:
+ --p->p ;
+ fall_through ;
- case '|':
- parsed->out_pipe = cmd_pipe_shell ;
- break ;
+ case '\0':
+ parsed->out_pipe = cmd_pipe_file ;
+ break ;
- case '*':
- parsed->out_pipe = cmd_pipe_dev_null ;
- break ;
+ case '>':
+ parsed->out_pipe = cmd_pipe_file | cmd_pipe_append ;
+ break ;
- default:
- --p->p ; /* put back */
- break ;
- }
- } ;
+ case '|':
+ parsed->out_pipe = cmd_pipe_shell ;
+ break ;
- /* Could now have options, but presently do not */
- if (p->p < p->e)
- {
+ case '*':
+ parsed->out_pipe = cmd_pipe_dev_null ;
+ break ;
+ } ;
+ break ;
+
+ default:
+ ok = false ;
+ break ;
+ } ;
+
+ while (ok && (p->p < p->e))
+ {
+ switch (*p->p++)
+ {
+ default:
ok = false ;
- } ;
+ break ;
+ } ;
} ;
- if (!ok)
- return cmd_parse_error(parsed, t, 0, "invalid 'pipe out'") ;
+ if (ok)
+ return CMD_SUCCESS ;
- return CMD_SUCCESS ;
+ return cmd_set_parse_error(parsed, t, 0, "invalid 'pipe out'") ;
} ;
/*------------------------------------------------------------------------------
@@ -1543,7 +1947,7 @@ cmd_parse_out_pipe(cmd_parsed parsed, cmd_token t)
* \! -> ! )
* \# -> \# )
*
- * NB: with the exception of $ inside of "..." the carefully avoids the
+ * NB: with the exception of $ inside of "..." this carefully avoids the
* regex meta characters: .*+?^$_ and (|)[-]
* and the regex escaped forms of those and the back reference \1 etc.
*
@@ -1595,7 +1999,7 @@ cmd_token_complete(cmd_parsed parsed, cmd_token t)
{
if (p->p == p->e)
{
- ret = cmd_parse_error(parsed, t, 0, "missing closing '") ;
+ ret = cmd_set_parse_error(parsed, t, 0, "missing closing '") ;
break ;
} ;
@@ -1623,7 +2027,7 @@ cmd_token_complete(cmd_parsed parsed, cmd_token t)
case '\\':
*q->p++ = *p->p++ ; /* copy the \ */
if (p->p == p->e)
- ret = cmd_parse_error(parsed, t, 0, "trailing \\") ;
+ ret = cmd_set_parse_error(parsed, t, 0, "trailing \\") ;
else
{
if (dq)
@@ -1659,7 +2063,7 @@ cmd_token_complete(cmd_parsed parsed, cmd_token t)
} ;
if (dq)
- ret = cmd_parse_error(parsed, t, 0, "missing closing \"") ;
+ ret = cmd_set_parse_error(parsed, t, 0, "missing closing \"") ;
if (ret == CMD_SUCCESS)
{
@@ -1676,7 +2080,9 @@ cmd_token_complete(cmd_parsed parsed, cmd_token t)
}
/*------------------------------------------------------------------------------
- * Tokenise the given line and work out where cursor is wrt tokens.
+ * Tokenize the given line and work out where cursor is wrt tokens.
+ *
+ * Note that this is implicitly "full lex".
*
* Looks for first token whose end is at or beyond the cursor position. Note
* that:
@@ -1724,8 +2130,8 @@ cmd_token_position(cmd_parsed parsed, qstring line)
const char* e ;
bool sq, dq, bs ;
- /* Re-initialise parsed object and tokenise the given line */
- cmd_tokenise(parsed, line) ;
+ /* Re-initialise parsed object and tokenize the given line */
+ cmd_tokenize(parsed, line, true) ;
/* Get the cursor position */
cp = qs_cp_nn(line) ;
@@ -1743,9 +2149,13 @@ cmd_token_position(cmd_parsed parsed, qstring line)
if (cp > t->tp)
{
- /* As soon as we are past '<', '>', '!' or '#' -- return "special" */
+ /* As soon as we are past '|', '<', '>', '!' or '#'
+ * return "special"
+ */
if ((t->type & ( cmd_tok_in_pipe
- | cmd_tok_out_pipe | cmd_tok_comment) ) != 0)
+ | cmd_tok_out_pipe
+ | cmd_tok_out_shell
+ | cmd_tok_comment) ) != 0)
return true ;
} ;
@@ -2333,28 +2743,24 @@ static int cmd_item_filter(cmd_parsed parsed, cmd_item item, cmd_token t) ;
* Returns: number of commands which may match to.
*/
static uint
-cmd_filter_prepare(cmd_parsed parsed, cmd_parse_type_t type)
+cmd_filter_prepare(cmd_parsed parsed, cmd_context context)
{
vector src_v ;
uint ci ;
uint nct ;
- bool execution = (type & cmd_parse_execution) != 0 ;
- bool strict = (type & cmd_parse_strict) != 0 ;
-
/* get commands for the current node */
- src_v = ((cmd_node)vector_get_item(node_vector, parsed->cnode))
- ->cmd_vector ;
+ src_v = ((cmd_node)vector_get_item(node_vector, parsed->cnode))->cmd_vector ;
assert(src_v != NULL) ; /* invalid parsed->cnode ?? */
- /* empty the working commands vector, making sure big enough for the current
- * node's commands.
+ /* Empty the working commands vector, making sure big enough for the current
+ * node's commands -- creates vector if required.
*
* Note that the cmd_v lives for as long as the parsed object, so will
* grow over time to accommodate what is required.
*/
- vector_re_init(parsed->cmd_v, vector_length(src_v)) ;
+ parsed->cmd_v = vector_re_init(parsed->cmd_v, vector_length(src_v)) ;
/* Filter out commands which are too short.
*
@@ -2371,7 +2777,7 @@ cmd_filter_prepare(cmd_parsed parsed, cmd_parse_type_t type)
if ( cmd->nt_max < nct)
continue ; /* ignore if too short */
- if ((cmd->nt_min > nct) && execution)
+ if ((cmd->nt_min > nct) && context->parse_execution)
continue ; /* ignore if too long */
vector_push_item(parsed->cmd_v, cmd) ;
@@ -2384,8 +2790,9 @@ cmd_filter_prepare(cmd_parsed parsed, cmd_parse_type_t type)
* not meet the minimum criterion is automatically excluded.
*/
- parsed->min_strength = execution ? ms_min_execute : ms_min_parse ;
- parsed->strict = strict ;
+ parsed->min_strength = context->parse_execution ? ms_min_execute
+ : ms_min_parse ;
+ parsed->strict = context->parse_strict ;
parsed->strongest = parsed->min_strength ;
parsed->best_complete = mt_no_match ;
@@ -2432,22 +2839,7 @@ cmd_filter(cmd_parsed parsed, uint ii, bool keep_items)
parsed->strongest = parsed->min_strength ;
parsed->best_complete = mt_no_match ;
- /* If we are keeping the items which are matched, set the item_v
- * empty and guess that may get one item per command.
- *
- * Note that the item_v lives for as long as the parsed object, so will
- * grow over time to accommodate what is required.
- */
- if (keep_items)
- vector_re_init(parsed->item_v, vector_length(parsed->cmd_v)) ;
-
- /* Work down the cmd_v, attempting to match cmd_items against cmd_tokens.
- *
- * Keep in the cmd_v the commands for which we get a match.
- *
- * At the same time, if required, keep in the item_v all the items which have
- * matched.
- */
+ /* Decide which token we are trying to match. */
if (ii < parsed->num_command)
ti = parsed->first_command + ii ; /* map to token index */
else
@@ -2463,12 +2855,27 @@ cmd_filter(cmd_parsed parsed, uint ii, bool keep_items)
}
t = cmd_token_get(parsed->tokens, ti) ;
+ /* If we are keeping the items which are matched, set the item_v
+ * empty and guess that may get one item per command -- creates the item_v
+ * vector if required..
+ *
+ * Note that the item_v lives for as long as the parsed object, so will
+ * grow over time to accommodate what is required.
+ */
+ if (keep_items)
+ parsed->item_v = vector_re_init(parsed->item_v,
+ vector_length(parsed->cmd_v)) ;
+
+ /* Work down the cmd_v, attempting to match cmd_items against the cmd_token.
+ *
+ * Keep in the cmd_v the commands for which we get a match.
+ *
+ * At the same time, if required, keep in the item_v all the items which have
+ * matched.
+ */
c_keep = 0 ;
i_keep = 0 ;
- if (keep_items)
- vector_re_init(parsed->item_v, vector_length(parsed->cmd_v)) ;
-
for (ci = 0; ci < vector_length(parsed->cmd_v); ci++)
{
cmd_command cmd ;
@@ -2688,24 +3095,29 @@ cmd_item_filter(cmd_parsed parsed, cmd_item item, cmd_token t)
*/
static cmd_return_code_t cmd_parse_phase_one(cmd_parsed parsed,
- cmd_parse_type_t type, node_type_t node) ;
+ cmd_context context) ;
static cmd_return_code_t cmd_parse_phase_one_b(cmd_parsed parsed, uint nt) ;
static cmd_return_code_t cmd_parse_phase_two(cmd_parsed parsed,
- cmd_parse_type_t type) ;
+ cmd_context context) ;
+static cmd_return_code_t cmd_parse_specials(cmd_parsed parsed,
+ cmd_context context) ;
+static node_type_t cmd_auth_specials(cmd_context context, node_type_t target) ;
/*------------------------------------------------------------------------------
* Parse a command in the given "node", or (if required) any of its ancestors.
*
- * Requires command line to have been tokenised.
+ * Requires command line to have been tokenized.
*
* Returns: CMD_SUCCESS => successfully parsed command, and the result is
* in the given parsed structure, ready for execution.
*
- * NB: parsed->cnode may not be the incoming node.
+ * - parsed->cnode may not be the incoming node.
*
- * NB: parsed->parts is what was found
+ * - parsed->nnode is set for when command succeeds.
*
- * NB: parsed->cmd->daemon => daemon
+ * - parsed->parts is what was found
+ *
+ * - parsed->cmd->daemon => daemon
*
* CMD_EMPTY => line is empty, except perhaps for comment
* (iff parsing for execution)
@@ -2739,7 +3151,7 @@ static cmd_return_code_t cmd_parse_phase_two(cmd_parsed parsed,
* See elsewhere for description of parsed structure.
*/
extern cmd_return_code_t
-cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type)
+cmd_parse_command(cmd_parsed parsed, cmd_context context)
{
cmd_return_code_t ret ;
@@ -2753,16 +3165,17 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type)
*
* Complete any tokens which contain quotes and/or escapes.
*
- * If there is a command then:
+ * Unless CMD_ERR_PARSING:
*
* - sort out any "do" and cut from start of command.
* - set parsed->cnode (from given node or according to "do")
+ * - set parsed->nnode (from given node)
* - set parsed->cmd = NULL
* - empty the argv vector
*
* Note that cmd_parse_phase_one only returns CMD_SUCCESS or CMD_ERR_PARSING.
*/
- ret = cmd_parse_phase_one(parsed, type, node) ;
+ ret = cmd_parse_phase_one(parsed, context) ;
if (ret != CMD_SUCCESS)
{
assert(ret == CMD_ERR_PARSING) ; /* no other error at this point */
@@ -2772,8 +3185,7 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type)
/* If no command tokens, and is parsing for execution, then we are done...
* but watch out for bare "do"
*/
- if (((parsed->parts & cmd_part_command) == 0) &&
- ((type & cmd_parse_execution) != 0))
+ if (((parsed->parts & cmd_part_command) == 0) && context->parse_execution)
{
if ((parsed->parts & ~cmd_part_comment) == cmd_parts_none)
return CMD_EMPTY ; /* accept empty */
@@ -2786,11 +3198,11 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type)
/* Level 2 parsing
*
- * Try in the current node and then in parent nodes, if can.
+ * Try in the current node and then in parent nodes, if allowed.
*
- * Cannot move up the node tree if is already at CONFIG_NODE or below.
- * Note that "do" pushes us to the ENABLE_NODE -- which is below the
- * CONFIG_NODE.
+ * Will stop moving up the tree when hits a node which is its own parent.
+ * All nodes from NODE_CONFIG up have a parent. All nodes from NODE_ENABLE
+ * down are their own parent.
*
* Note that when not parsing for execution, may get here with no command
* tokens at all -- in which case cmd_parse_phase_two() will return
@@ -2803,19 +3215,14 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type)
{
node_type_t pnode ;
- ret = cmd_parse_phase_two(parsed, type) ;
+ ret = cmd_parse_phase_two(parsed, context) ;
if (ret == CMD_SUCCESS)
break ;
- if (ret != CMD_ERR_NO_MATCH)
+ if ((ret != CMD_ERR_NO_MATCH) || context->parse_no_tree)
return ret ;
- confirm(ENABLE_NODE < CONFIG_NODE) ;
-
- if (((type & cmd_parse_no_tree) != 0) || (parsed->cnode <= CONFIG_NODE))
- return CMD_ERR_NO_MATCH ;
-
pnode = cmd_node_parent(parsed->cnode) ;
if (pnode == parsed->cnode)
@@ -2824,15 +3231,46 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type)
parsed->cnode = pnode ;
} ;
- /* Parsed successfully.
+ /* Parsed successfully -- worry about parsed->nnode.
+ *
+ * Currently parsed->nnode is set to the node we came in on. If the
+ * parsed->cnode is not the same, then we have two choices:
*
- * If for execution, fill the arg_vector
+ * (a) parsed->cnode is ENABLE_NODE -- either because have 'do' or by tree
+ * walk -- in which case we leave parsed->nnode;
+ *
+ * (b) parsed->cnode is not ENABLE_NODE -- so this command, if successful
+ * will change context, in which cas we need to set parsed->nnode.
+ */
+ if ((parsed->nnode != parsed->cnode) && (parsed->cnode != ENABLE_NODE))
+ parsed->nnode = parsed->cnode ;
+
+ /* Worry about "special" commands and those that set the next node. */
+ if ((parsed->cmd->attr & (CMD_ATTR_NODE | CMD_ATTR_MASK)) != 0)
+ {
+ if ((parsed->cmd->attr & CMD_ATTR_NODE) != 0)
+ parsed->nnode = parsed->cmd->attr & CMD_ATTR_MASK ;
+ else
+ {
+ /* This is a "special" command, which may have some (limited)
+ * semantic restrictions.
+ *
+ * The main thing we are interested in is commands which have
+ * special effects on parsed->nnode (in particular exit and end).
+ */
+ ret = cmd_parse_specials(parsed, context) ;
+
+ if (ret != CMD_SUCCESS)
+ return ret ;
+ } ;
+ } ;
+
+ /* If for execution, fill the arg_vector
*
* The arg_vector is an array of pointers to '\0' terminated strings, which
* are pointers to the relevant tokens' qstring bodies.
*/
-
- if ((type & cmd_parse_execution) != 0)
+ if (context->parse_execution)
{
uint ti ;
@@ -2866,7 +3304,7 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type)
} ;
/*------------------------------------------------------------------------------
- * Phase 1 of command parsing
+ * Phase 1 of command parsing -- get ready to search the command system.
*
* Scan the tokens to break them up into the sections:
*
@@ -2876,20 +3314,25 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type)
* - out-pipe -- '>' etc up to comment
* - comment -- '!' or '#' onwards
*
- * Requires line to have been tokenised -- cmd_tokenise().
+ * Sets parsed->cnode to the given node, or to ENABLE_NODE if have 'do' (and
+ * the original node allows it).
+ *
+ * Sets parsed->nnode to the given node.
+ *
+ * Requires line to have been tokenized -- cmd_tokenize().
*
* Returns: CMD_SUCCESS -- all is well
* CMD_ERR_PARSING -- parsing error -- malformed or misplaced pipe
* -- malformed quotes/escapes
*/
static cmd_return_code_t
-cmd_parse_phase_one(cmd_parsed parsed, cmd_parse_type_t type, node_type_t node)
+cmd_parse_phase_one(cmd_parsed parsed, cmd_context context)
{
uint nt = parsed->num_tokens ;
/* Set command and parsing entries */
- parsed->cnode = node ;
- parsed->cmd = NULL ;
+ parsed->nnode = parsed->cnode = context->node ;
+ parsed->cmd = NULL ;
/* pick off any comment */
if ((parsed->tok_total & cmd_tok_comment) != 0)
@@ -2926,7 +3369,7 @@ cmd_parse_phase_one(cmd_parsed parsed, cmd_parse_type_t type, node_type_t node)
if ((parsed->parts & cmd_part_command) != 0)
{
/* Have a command -- worry about "do" if allowed */
- if (((type & cmd_parse_no_do) == 0) && (node >= MIN_DO_SHORTCUT_NODE))
+ if (!context->parse_no_do && (context->node >= MIN_DO_SHORTCUT_NODE))
{
cmd_token t ;
t = cmd_token_get(parsed->tokens, parsed->first_command) ;
@@ -2935,7 +3378,7 @@ cmd_parse_phase_one(cmd_parsed parsed, cmd_parse_type_t type, node_type_t node)
const char* p = els_body_nn(t->ot) ;
if ((*p == 'd') && (*(p+1) == 'o'))
{
- node = ENABLE_NODE ; /* change to this node */
+ parsed->cnode = ENABLE_NODE ; /* change to this node */
parsed->num_do = 1 ;
parsed->first_do = parsed->first_command ;
@@ -2956,7 +3399,7 @@ cmd_parse_phase_one(cmd_parsed parsed, cmd_parse_type_t type, node_type_t node)
/*------------------------------------------------------------------------------
* Phase 1b of command parsing
*
- * Tokeniser found at least one of:
+ * tokenizer found at least one of:
*
* - in pipe token
* - out pipe token
@@ -2967,6 +3410,8 @@ cmd_parse_phase_one(cmd_parsed parsed, cmd_parse_type_t type, node_type_t node)
*
* Update the "parts" and the relevant first/num values.
*
+ * Note that the number of tokens (nt) *excludes* any comment token.
+ *
* Returns: CMD_SUCCESS -- all is well
* CMD_ERR_PARSING -- parsing error -- malformed or misplaced pipe
* -- malformed quotes/escapes
@@ -2984,11 +3429,24 @@ cmd_parse_phase_one_b(cmd_parsed parsed, uint nt)
n = 0 ; /* no tokens in current part */
pn = NULL ; /* no current part */
+ /* Walk the tokens, establishing the start and end of:
+ *
+ * - command part )
+ * - in pipe part ) exclusive
+ * - out shell part )
+ * - out pipe part which may only follow command or in pipe
+ *
+ *
+ */
for (i = 0 ; i < nt ; ++i)
{
t = cmd_token_get(parsed->tokens, i) ;
- if ((t->type & cmd_tok_simple) != 0)
+ /* Simple tokens can appear anywhere.
+ *
+ * If this is the first token, start a cmd_part_command.
+ */
+ if ((t->type & cmd_tok_simple) != 0)
{
if (parts == cmd_parts_none)
{
@@ -2997,42 +3455,80 @@ cmd_parse_phase_one_b(cmd_parsed parsed, uint nt)
pn = &parsed->num_command ;
n = 0 ;
} ;
- } ;
+ }
- if ((t->type & cmd_tok_in_pipe) != 0)
+ /* '<' types of token can appear anywhere, except in a cmd_part_command.
+ *
+ * If this is the first token, start a cmd_part_in_pipe.
+ */
+ else if ((t->type & cmd_tok_in_pipe) != 0)
{
- if (parts != cmd_parts_none)
- return cmd_parse_error(parsed, t, 0, "unexpected 'pipe in'") ;
+ if ((parts & (cmd_part_command | cmd_parts_pipe)) == cmd_part_command)
+ return cmd_set_parse_error(parsed, t, 0, "unexpected 'pipe in'") ;
- ret = cmd_parse_in_pipe(parsed, t) ;
- if (ret != CMD_SUCCESS)
- return ret ;
+ if (parts == cmd_parts_none)
+ {
+ ret = cmd_parse_in_pipe(parsed, t) ;
+ if (ret != CMD_SUCCESS)
+ return ret ;
- parts = cmd_part_in_pipe ;
- parsed->first_in_pipe = i ;
- pn = &parsed->num_in_pipe ;
- n = 0 ;
- } ;
+ parts = cmd_part_in_pipe ;
+ parsed->first_in_pipe = i ;
+ pn = &parsed->num_in_pipe ;
+ n = 0 ;
+ }
+ }
- if ((t->type & cmd_tok_out_pipe) != 0)
+ /* '|' tokens can appear anywhere.
+ *
+ * If this is the first token, start a cmd_part_out_pipe.
+ */
+ else if ((t->type & cmd_tok_out_shell) != 0)
{
- if ((parts == cmd_parts_none) || ((parts & cmd_part_out_pipe) != 0))
- return cmd_parse_error(parsed, t, 0, "unexpected 'pipe out'") ;
+ if ((parts & (cmd_part_command | cmd_parts_pipe)) == cmd_part_command)
+ return cmd_set_parse_error(parsed, t, 0, "unexpected 'shell pipe'") ;
- ret = cmd_parse_out_pipe(parsed, t) ;
- if (ret != CMD_SUCCESS)
- return ret ;
+ if (parts == cmd_parts_none)
+ {
+ ret = cmd_parse_out_pipe(parsed, t) ;
+ if (ret != CMD_SUCCESS)
+ return ret ;
+
+ parts = cmd_part_out_pipe ;
+ parsed->first_out_pipe = i ;
+ pn = &parsed->num_out_pipe ;
+ n = 0 ;
+ } ;
+ }
+
+ /* '>' types of token can appear anywhere, except on an empty line.
+ *
+ * If not already in cmd_part_out_pipe, start a cmd_part_out_pipe.
+ */
+ else if ((t->type & cmd_tok_out_pipe) != 0)
+ {
+ if (parts == cmd_parts_none)
+ return cmd_set_parse_error(parsed, t, 0, "unexpected 'pipe out'") ;
- *pn = n ; /* set number of in-pipe/cmd */
+ if ((parts & cmd_part_out_pipe) == 0)
+ {
+ ret = cmd_parse_out_pipe(parsed, t) ;
+ if (ret != CMD_SUCCESS)
+ return ret ;
+
+ *pn = n ; /* set number of in-pipe/cmd */
- parts |= cmd_part_out_pipe ;
- parsed->first_out_pipe = i ;
- pn = &parsed->num_out_pipe ;
- n = 0 ;
+ parts |= cmd_part_out_pipe ;
+ parsed->first_out_pipe = i ;
+ pn = &parsed->num_out_pipe ;
+ n = 0 ;
+ } ;
} ;
assert(parts != cmd_parts_none) ; /* dealt with all token types */
+ /* Now deal with any "incompleteness" */
+
if ((t->type & cmd_tok_incomplete) != 0)
{
ret = cmd_token_complete(parsed, t) ;
@@ -3122,7 +3618,7 @@ cmd_parse_phase_one_b(cmd_parsed parsed, uint nt)
if (msg != NULL)
{
t = cmd_token_get(parsed->tokens, i) ;
- return cmd_parse_error(parsed, t, e ? els_len_nn(t->ot) : 0, msg) ;
+ return cmd_set_parse_error(parsed, t, e ? els_len_nn(t->ot) : 0, msg) ;
} ;
} ;
@@ -3146,14 +3642,14 @@ cmd_parse_phase_one_b(cmd_parsed parsed, uint nt)
* or parsed->num_command == 0
*/
static cmd_return_code_t
-cmd_parse_phase_two(cmd_parsed parsed, cmd_parse_type_t type)
+cmd_parse_phase_two(cmd_parsed parsed, cmd_context context)
{
uint ii ;
uint match ;
/* Prepare to filter commands */
- cmd_filter_prepare(parsed, type) ;
+ cmd_filter_prepare(parsed, context) ;
match = 2 ; /* in case parsed->num_command == 0 ! */
@@ -3172,6 +3668,158 @@ cmd_parse_phase_two(cmd_parsed parsed, cmd_parse_type_t type)
return CMD_SUCCESS ;
} ;
+/*------------------------------------------------------------------------------
+ * Perform the special handling required for the command that has parsed
+ * successfully so far.
+ *
+ * This mechanism is used to deal with commands which have a context sensitive
+ * effect on parsed->nnode.
+ *
+ * Can be used to do any other semantic checks, subject to the information
+ * being available.
+ *
+ * Returns: CMD_SUCCESS -- OK
+ * CMD_ERR_PARSING -- failed some check, see parsed->emess
+ *
+ * Dealt with here are:
+ *
+ * * "exit" and "quit"
+ *
+ * The next node depends on the current node.
+ *
+ * * "end"
+ *
+ * The next node depends on the current node.
+ *
+ * * "enable"
+ *
+ * This command is really only intended for interactive use, but need to
+ * do something with it in other contexts.
+ *
+ * Can go to ENABLE_NODE directly if can_enable is set in the context.
+ *
+ * The can_enable is set when the vty starts if it does not require any
+ * authentication to enter ENABLE_NODE (eg VTY_CONFIG_READ) or because
+ * it started in ENABLE_NODE or greater (eg VTY_TERMINAL with no password
+ * and advanced mode !).
+ *
+ * Once can_enable is set it is not unset. So getting to enable once is
+ * sufficient for a given VTY.
+ *
+ * A pipe will inherit can_enable, provided that the parent is in
+ * ENABLE_NODE or better.
+ *
+ * A pipe cannot inherit can_auth_enable -- this means that a pipe
+ * can either immediately enable, or cannot enable at all.
+ *
+ * The effect of all this is that "enable" is straightforward, except for
+ * VTY_TERMINAL. For VTY_TERMINAL:
+ *
+ * - if the VTY starts in any node >= ENABLE_NODE, then can_enable
+ * is set from the beginning !
+ *
+ * If has ever reached ENABLE_NODE, then can_enable will be set.
+ *
+ * - otherwise: when enable command is seen, must authenticate.
+ *
+ * - if there is an enable password, then must get and accept the
+ * password, which can only happen at vin_depth == vout_depth == 0
+ * -- see vty_cmd_can_auth_enable().
+ *
+ * - if there is no enable password, then is implicitly authenticated
+ * if is in VIEW_NODE.
+ *
+ * Note that will not accept enable with no password if is in
+ * RESTRICTED_NODE. Can only be in RESTRICTED_NODE if started with
+ * no password, but host.restricted_mode is set. Doesn't seem much
+ * point having a restricted_mode if you can go straight to
+ * ENABLE_NODE just because a password has not been set !
+ */
+static cmd_return_code_t
+cmd_parse_specials(cmd_parsed parsed, cmd_context context)
+{
+ cmd_return_code_t ret ;
+
+ ret = CMD_SUCCESS ;
+
+ switch (parsed->cmd->attr & CMD_ATTR_MASK)
+ {
+ case cmd_sp_simple:
+ zabort("invalid cmd_sp_simple") ;
+ break ;
+
+ case cmd_sp_end:
+ parsed->nnode = cmd_node_end_to(parsed->cnode) ;
+ break ;
+
+ case cmd_sp_exit: /* NULL_NODE <=> CMD_EOF */
+ parsed->nnode = cmd_node_exit_to(parsed->cnode) ;
+ break ;
+
+ case cmd_sp_enable:
+ parsed->nnode = cmd_auth_specials(context, ENABLE_NODE) ;
+ break ;
+
+ case cmd_sp_configure:
+ parsed->nnode = cmd_auth_specials(context, CONFIG_NODE) ;
+ break ;
+
+ default:
+ zabort("unknown cmd_sp_xxx") ;
+ break ;
+ } ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Deal with commands which may require AUTH_ENABLE_NODE authentication.
+ *
+ * The rule is that the parser must set what node a given command *will* change
+ * to iff it succeeds.
+ *
+ * So if we can go directly to the target node, must establish that now.
+ *
+ * If cannot go to directly, we set that should go to AUTH_ENABLE_NODE. That
+ * may fail if not in a suitable state to do that -- and issue suitable
+ * message.
+ */
+static node_type_t
+cmd_auth_specials(cmd_context context, node_type_t target)
+{
+ if (!context->can_enable)
+ {
+ /* Can enable if is already ENABLE_NODE or better (this should
+ * not be required -- but does no harm).
+ *
+ * If we are allowed to authenticate, then the authentication
+ * is trivial if there is no password set and we are in
+ * VIEW_NODE !
+ *
+ * Note that if we are in RESTRICTED_NODE, then must authenticate,
+ * which will later be refused if no password is set at the time !
+ */
+ if (context->node >= ENABLE_NODE)
+ context->can_enable = true ;
+ else if (context->can_auth_enable)
+ {
+ /* Can do a*/
+ bool no_pw ;
+
+ VTY_LOCK() ;
+ no_pw = (host.enable == NULL) ;
+ VTY_UNLOCK() ;
+
+ context->can_enable = no_pw && (context->node == VIEW_NODE) ;
+ } ;
+ } ;
+
+ context->onode = context->node ; /* see vty_cmd_auth() */
+ context->tnode = target ; /* see vty_cmd_auth() */
+
+ return context->can_enable ? target : AUTH_ENABLE_NODE ;
+} ;
+
#if 0
/*------------------------------------------------------------------------------
* If src matches dst return dst string, otherwise return NULL
@@ -3321,9 +3969,9 @@ cmd_describe_command (const char* line, node_type_t node,
cmd_parsed parsed ;
cmd_token_type_t tok_total ;
- /* Set up a parser object and tokenise the command line */
+ /* Set up a parser object and tokenize the command line */
parsed = cmd_parse_init_new(&parsed_s) ;
- tok_total = cmd_tokenise(parsed, line, node) ;
+ tok_total = cmd_tokenize(parsed, line, node) ;
@@ -3611,14 +4259,21 @@ cmd_help_preflight(cmd_parsed parsed)
*
*/
extern cmd_return_code_t
-cmd_completion(cmd_parsed parsed, node_type_t node)
+cmd_completion(cmd_parsed parsed, cmd_context context)
{
cmd_return_code_t ret ;
/* Parse the line -- allow completion, allow do, allow backing up the tree,
* but do not parse for execution.
*/
- ret = cmd_parse_command(parsed, node, cmd_parse_standard) ;
+ context->can_enable = true ;
+ context->full_lex = true ;
+ context->parse_execution = false ;
+ context->parse_no_do = false ;
+ context->parse_no_tree = false ;
+ context->parse_strict = false ;
+
+ ret = cmd_parse_command(parsed, context) ;
if (ret == CMD_ERR_PARSING)
return ret ; /* nothing more possible */
@@ -3674,9 +4329,6 @@ cmd_completion(cmd_parsed parsed, node_type_t node)
return CMD_SUCCESS ;
} ;
-
-
-
/*------------------------------------------------------------------------------
* How to insert a newly completed keyword ?
*
@@ -3714,7 +4366,7 @@ cmd_completion(cmd_parsed parsed, node_type_t node)
* Returns: n = number of characters to move the cursor before
* starting to replace characters (may be -ve)
* *rep = number of characters to replace
- * *ins = number of spaces to insert
+ * *ins = number of spaces to insert : 0..2
* *mov = number of spaces to move afterwards (may be -ve)
*/
extern void