diff options
Diffstat (limited to 'lib/command_parse.c')
-rw-r--r-- | lib/command_parse.c | 4881 |
1 files changed, 4881 insertions, 0 deletions
diff --git a/lib/command_parse.c b/lib/command_parse.c new file mode 100644 index 00000000..81e4ad74 --- /dev/null +++ b/lib/command_parse.c @@ -0,0 +1,4881 @@ +/* Quagga command line parsing + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Recast and extended: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "misc.h" +#include <ctype.h> +#include <stdio.h> +#include <arpa/inet.h> + +#include "command_local.h" +#include "command_parse.h" +#include "memory.h" +#include "list_util.h" +#include "elstring.h" + +/*============================================================================== + * Command Description objects. + * + */ +static void cmd_fail_item(cmd_command cmd, const char* msg) ; +static char* cmd_item_brackets(cmd_command cmd, char* cp) ; +static cmd_item cmd_make_item(cmd_command cmd, char* cp, char* dp) ; +static void cmd_make_item_inner(cmd_command cmd, cmd_item n, char* cp) ; +static char* cmd_make_item_numeric(cmd_command cmd, cmd_item n, char* cp) ; +static long cmd_make_item_number(cmd_command cmd, cmd_item n, char** p_cp) ; +static int cmd_cmp_item(const cmd_item* a, const cmd_item* b) ; +static int cmd_cmp_range_items(const cmd_item a, const cmd_item b) ; +static bool cmd_item_is_option(cmd_item_type_t it) ; +static bool cmd_item_is_vararg(cmd_item_type_t it) ; +static void cmd_set_str(cmd_item n, const char* str) ; + +/*------------------------------------------------------------------------------ + * Table of known "words" -- so that a word can be represented by its + * address in this table and its length. + * + * See: cmd_set_str() + */ +enum { word_lump_length = 500 * 8 } ; /* plenty ? */ +enum { command_specification_length = 500 } ; /* plenty !! */ + +typedef struct word_lump* word_lump ; + +struct word_lump +{ + word_lump next ; + + char* end ; /* points at the terminating '\0' */ + char words[word_lump_length] ; +} ; + +static struct dl_base_pair(word_lump) word_lumps = INIT_DL_BASE_PAIR ; + +/*------------------------------------------------------------------------------ + * Dummy eol_item -- completed in cmd_parse_init() + */ +static struct cmd_item eol_item = +{ + .str = ELSTRING_INIT, /* see cmd_parse_init() */ + .doc = "", + + .next = NULL, + + .type = item_eol, + .arg = false, + + .range_sign_allowed = false, + .range_sign_required = false, + .range_min = 0, + .range_max = 0 +} ; + +/*------------------------------------------------------------------------------ + * Parse cmd_command string and doc to create the items for the cmd_command, + * and fill in: + * + * cmd->items -- vector of cmd_item(s). + * + * Where a given item may have more than one possible + * value, thet are arranged as a list. + * + * cmd->nt_min -- count of items up to first [option] + * where (...) counts as 1 + * and .vararg counts as 1 + * + * Is the minimum number of tokens required to match to + * this command. + * + * cmd->nt -- count of all items + * where (...) counts as 1 + * and [option] counts as 1 + * and .vararg counts as 1 + * + * cmd->nt_max -- count of all items as nt_var, + * except .vararg forces to UINT_MAX + * + * Is the maximum number of tokens which can be matched + * to this command. + * + * cmd->r_string -- copy of cmd->string, chopped up and referred to by + * the cmd_items. + * + * cmd->d_string -- copy of the cmd->doc, chopped up and referred to by + * the cmd_items. + * + * Note that the cmd_items point into the r_string and the d_string, and + * do not have further copies of their fragments of the original. + * + * Note that the t_string and d_string have all extraneous spaces, tabs and + * control characters removed. + * + * Stops dead if not valid ! + * + * Accepts: items separated by one or more spaces. Early on in the process, + * will discard spaces within a bracketed item (and checks for balanced + * brackets). Rejects any control character other than '\t', which is + * converted to ' '. Multiple spaces are reduced to single spaces. + * + * - single item is one of: + * + * - keyword -- anything starting a-z or 0-9, followed by any + * alphanumeric, '-', '_' or ':'. + * or * + * - <0-9> -- decimal range + * - WORD -- anything at least starting A-Z, followed by any + * alphanumeric, '-', '_' or ':'. + * - A.B.C.D -- ipv4 address + * - A.B.C.D/M -- ipv4 prefix + * - X:X::X:X -- ipv6 address + * - X:X::X:X/M -- ipv6 prefix + * - .vararg -- anything starting '.', followed by any alphanumeric, + * '-', '_' or ':'. + * - [item] -- optional item any of the above TODO + * + * - multiple item is: '(' item '|' item .... ')' + * + * where spaces around items are discarded. The items may be any of the + * above, except: + * + * - must all be different types of item, or for keywords and ranges, + * different values. + * + * - cannot have [item] + * + * - cannot have .var + * + * may have a single item -- whose value is sent out as an argument. + * + * An [item] may only be followed by other [item](s). An [item] matches a + * token or end of line. + * + * A .vararg must be the last item. A .vararg matches one or more tokens. + * + * + * + */ +extern void +cmd_compile(cmd_command cmd) +{ + vector multvec ; + const char* p ; + char* cp ; + char* qp ; + char* dp ; + bool opt ; + bool vararg ; + + char spec[command_specification_length + 1] ; + + /* Initialise the compiled version of the command */ + + assert(cmd->r_doc == NULL) ; + + cmd->items = vector_init_new(NULL, 10) ; /* plenty ! */ + cmd->nt_min = 0 ; + cmd->nt = 0 ; + cmd->nt_max = 0 ; + cmd->vararg = NULL ; + cmd->r_doc = XSTRDUP(MTYPE_CMD_STRING, cmd->doc) ; /* NULL => "" */ + + if (strlen(cmd->string) > command_specification_length) + cmd_fail_item(cmd, "command specification *too* long") ; + + /* Simplify the command line string by replacing TABs by spaces, and barfing + * on control characters. Strip leading and trailing spaces and any spaces + * between brackets -- checking for matching brackets -- and squeeze out + * multiple spaces. + */ + qp = spec ; + p = cmd->string ; + + while ((*p == ' ') || (*p == '\t')) + ++p ; /* squeeze out leading spaces */ + + while (*p != '\0') /* starts with not ' ' and not '\t' */ + { + if (!iscntrl(*p)) + *qp = *p ; + else if (*p == '\t') + *qp = ' ' ; + else + cmd_fail_item(cmd, "improper control character in string") ; + + if ((*qp != ' ') || (*(qp - 1) != ' ')) + ++qp ; /* squeeze out multiple spaces */ + + ++p ; + } ; + + while ((qp > spec) && (*(qp - 1) == ' ')) + --qp ; /* squeeze out trailing spaces */ + + *qp = '\0' ; + + cp = spec ; + qp = spec ; + while (*cp != '\0') + { + if ((*cp == '(') || (*cp == '[') || (*cp == '<') || (*cp == '{')) + { + /* Check for balanced brackets and remove any spaces between. + * + * Checks for enclosed brackets being balanced as well. + * + * Leaves cp pointing at the trailing bracket. + */ + char* sp = cp ; + cp = cmd_item_brackets(cmd, cp) ; + while (sp < cp) + { + if (*sp != ' ') + *qp++ = *sp++ ; + else + ++sp ; + } ; + } ; + + *qp++ = *cp++ ; + } ; + + *qp = '\0' ; /* terminate reduced string */ + + /* Simplify the documentation string by replacing TABs by spaces, and barfing + * on control characters other than '\n'. + * + * Strips leading spaces and any spaces before or after '\n'. + */ + + qp = dp = cmd->r_doc ; + while (*dp != '\0') + { + /* Strip leading */ + while (*dp == ' ') + ++dp ; + + /* Eat documentation section. */ + while ((*dp != '\n') && (*dp != '\0')) + { + if (!iscntrl(*dp)) + *qp++ = *dp++ ; + else if (*dp == '\t') + { + *qp++ = ' ' ; + ++dp ; + } + else + cmd_fail_item(cmd, "improper control character in documentation") ; + } ; + + /* Get here with *dp == '\n' or '\0' + * + * Strip trailing spaces (any before '\n' or '\0' + */ + while ((qp != cmd->r_doc) && (*(qp - 1) == ' ')) + --qp ; + + /* copy '\n', if required. */ + if (*dp == '\n') + *qp++ = *dp++ ; + } ; + + *qp = '\0' ; /* terminate reduced string */ + + /* Processing loop */ + + cp = spec ; + dp = cmd->r_doc ; + + opt = false ; + vararg = false ; + + multvec = NULL ; + + while (*cp != '\0') + { + uint multiple ; + + /* Deal with single or multiple item. */ + multiple = 0 ; + do + { + cmd_item n ; + char* c_sp ; + char* d_sp ; + + /* step to the next documentation section */ + + d_sp = dp ; /* start of documentation */ + + while (*dp != '\0') + { + if (*dp == '\n') + { + *dp++ = '\0' ; + break ; + } ; + ++dp ; + } ; + + /* Deal with '(' if we have one. */ + + if (*cp == '(') /* change up to multiple */ + { + if (multiple != 0) + cmd_fail_item(cmd, "unexpected '('") ; + + multiple = 1 ; /* seen '(' */ + ++cp ; /* step past it */ + + multvec = vector_re_init(multvec, 10) ; /* plenty ! */ + } ; + + /* Find end of current item & '\0' terminate it. */ + c_sp = cp ; + while (1) + { + if (*cp == '|') /* eat '|' */ + { + if ((c_sp == cp) || (multiple < 1)) + cmd_fail_item(cmd, "unexpected '|'") ; + *cp++ = '\0' ; + break ; + } ; + + if (*cp == ')') /* eat ')' */ + { + if ((c_sp == cp) || (multiple < 1)) + cmd_fail_item(cmd, "unexpected ')'") ; + *cp++ = '\0' ; + multiple = 2 ; + + if ((*cp != ' ') && (*cp != '\0')) + cmd_fail_item(cmd, "expect ' ' or nothing after ')'") ; + } ; + + if (*cp == ' ') + { + *cp++ = '\0' ; + break ; + } ; + + if (*cp == '\0') + break ; + + ++cp ; + } ; + + /* Create the next item and push */ + + n = cmd_make_item(cmd, c_sp, d_sp) ; + + if (multiple == 0) + vector_push_item(cmd->items, n) ; + else + vector_push_item(multvec, n) ; + + /* Extra checks for multiple item. */ + if (multiple > 0) + { + n->arg = true ; /* always */ + + if (cmd_item_is_option(n->type)) + cmd_fail_item(cmd, "cannot have [option] inside (..)") ; + + /* could lift this restriction, but need to check that + * do not have a WORD|.VAR together, because that is tautologous. + */ + + if (cmd_item_is_vararg(n->type)) + cmd_fail_item(cmd, "cannot have .vararg inside (..)") ; + } ; + + /* Check optional item state -- can only be trailing */ + if (cmd_item_is_option(n->type)) + opt = true ; + else if (opt) + cmd_fail_item(cmd, "can only have [option] after [option]") ; + + /* Check vararg item state -- can only be trailing */ + if (vararg) + cmd_fail_item(cmd, "cannot have anything after .vararg") ; + else if (cmd_item_is_vararg(n->type)) + { + vararg = true ; + cmd->vararg = n ; /* remember for parsing */ + } ; + + } while (multiple == 1) ; + + /* count the item */ + if (!opt) + ++cmd->nt_min ; + ++cmd->nt ; + if (!vararg) + ++cmd->nt_max ; + else + cmd->nt_max = UINT_MAX ; + + /* Complete the multiple item. + * + * Sort the items so that are always used and presented in the same + * order. Check that the items are unique. + * + * We must have at least one item. + */ + if (multiple == 2) + { + cmd_item n, p ; + uint i ; + + assert(vector_length(multvec) >= 1) ; + + vector_sort(multvec, (vector_sort_cmp*)cmd_cmp_item) ; + + n = vector_get_item(multvec, 0) ; + vector_push_item(cmd->items, n) ; + + for (i = 1 ; i < vector_length(multvec) ; ++i) + { + p = n ; + n = vector_get_item(multvec, i) ; + + p->next = n ; + n->next = NULL ; + + if (p->type == n->type) + { + bool repeat ; + + if (n->type == item_keyword) + repeat = (els_body_nn(n->str) == els_body_nn(p->str)) + && (els_len_nn(n->str) == els_len_nn(p->str)) ; + else if (n->type == item_range) + repeat = cmd_cmp_range_items(n, p) == 0 ; + else + repeat = true ; + + if (repeat) + cmd_fail_item(cmd, "repeated items in (...)") ; + } ; + } ; + } + else + assert(multiple == 0) ; + } ; + + vector_reset(multvec, free_it) ; + + /* Reduce the vector to the minimum size required */ + vector_decant(cmd->items) ; +} ; + +/*------------------------------------------------------------------------------ + * Validate a compiled item + * + * Checks that the contents of the cmd_command are consistent with the + * contents of the srcvec. + * + */ +extern void +cmd_compile_check(cmd_command cmd) +{ + bool ok ; + + uint nt_min = 0 ; + uint nt = 0 ; + uint nt_max = 0 ; + cmd_item vararg = NULL ; + + ok = true ; + + /* Require the following to be set to something */ + if ( (cmd->string == NULL) + || (cmd->func == NULL) + || (cmd->items == NULL) + || (cmd->r_doc == NULL) ) + ok = false ; + + /* cmd->nt must match the vector_length and be non-zero. */ + ok = ok && ((nt = vector_length(cmd->items)) == cmd->nt) ; + ok = ok && (nt != 0) ; + + /* Walk the vector of items, and check that those are OK. */ + if (ok) + { + uint ii = 0 ; + bool opt = false ; + + for (ii = 0 ; ok && (ii < nt) ; ++ii) + { + cmd_item item ; + cmd_item first_item ; + + item = vector_get_item(cmd->items, ii) ; + if (item == NULL) + { + ok = false ; + break ; + } ; + + if (vararg != NULL) /* nothing after vararg */ + { + ok = false ; + break ; + } ; + + first_item = item ; + while (ok && (item != NULL)) + { + /* If this is an option, may only be a single item + * + * Otherwise, after an option must all be options. + */ + if (cmd_item_is_option(item->type)) + { + /* option must be a single item. */ + opt = true ; + if ((item != first_item) || (item->next != NULL)) + { + ok = false ; + break ; + } ; + } + else if (opt) + { + /* once we have an option, must all be options */ + ok = false ; + break ; + } ; + + /* If this is a vararg, must be the last of this item. + * + * Note that allow for [.varg] and (...., .varg) -- the second + * should be sorted to the back ! + */ + if (cmd_item_is_vararg(item->type)) + { + /* vararg must be last item & only vararg */ + if ((item->next != NULL) || (vararg != NULL)) + { + ok = false ; + break ; + } ; + + vararg = item ; + } ; + + + /* If there is a next, this and the next MUST be arg */ + if (item->next != NULL) + { + if (!((item->arg) && (item->next->arg))) + { + ok = false ; + break ; + } ; + } ; + + item = item->next ; + } ; + + /* Advance the nt_min and nt_max as required. */ + if (!opt) + ++nt_min ; + + if (vararg == NULL) + ++nt_max ; + else + nt_max = UINT_MAX ; + } ; + } ; + + /* Final checks */ + + ok = ok && (cmd->nt_min == nt_min) + && (cmd->nt == nt) + && (cmd->nt_max == nt_max) + && (cmd->vararg == vararg) ; + + if (!ok) + cmd_fail_item(cmd, "some compile error") ; +} ; + +/*------------------------------------------------------------------------------ + * Reject the cmd_item string or doc. + */ +static void +cmd_fail_item(cmd_command cmd, const char* msg) +{ + fprintf (stderr, "Command parse error!: %s\n", msg) ; + fprintf (stderr, " in command: '%s'\n", cmd->string) ; + exit(2) ; +} + +/*------------------------------------------------------------------------------ + * Advance to matching bracket -- fail if not found. Recurse as required. + * + * Returns: address of matching bracket. + */ +static char* +cmd_item_brackets(cmd_command cmd, char* cp) +{ + char seek ; + + switch (*cp) + { + case '(': + seek = ')' ; + break ; + + case '[': + seek = ']' ; + break ; + + case '<': + seek = '>' ; + break ; + + case '{': + seek = '}' ; + break ; + + default: + return cp ; + } ; + + do + { + ++cp ; + + if (*cp == seek) + return cp ; + else if ((*cp == '(') || (*cp == '[') || (*cp == '<') || (*cp == '{')) + cp = cmd_item_brackets(cmd, cp) ; + } + while (*cp != '\0') ; + + cmd_fail_item(cmd, "unbalanced brackets of some sort") ; + + return cp ; +} ; + +/*------------------------------------------------------------------------------ + * Make descriptor for current item. + * + * cp points at start of '\0' terminated item, which has no spaces and no + * control characters in or around it. Also, if there are brackets, they are + * balanced. + * + * Returns: new descriptor, filled in as required. + */ + +static cmd_item +cmd_make_item(cmd_command cmd, char* cp, char* dp) +{ + char* inner ; + cmd_item n ; + + n = XCALLOC(MTYPE_CMD_ITEM, sizeof(struct cmd_item)) ; + + /* Zeroising has set: + * + * * str = NULL -- set below + * * str_len = 0 -- set below + * * doc = NULL -- set below + * + * * next = NULL -- set elsewhere if multiple. + * + * * type = item_null + * * arg = false -- set if required, below + * + * * range_sign_allowed ) + * * range_sign_required ) -- set elsewhere if required + * * range_min ) + * * range_max ) + */ + confirm(item_null == 0) ; + + cmd_set_str(n, cp) ; + n->doc = dp ; + + /* Worry about option state */ + inner = NULL ; + if (*cp == '[') + { + n->arg = true ; /* always true for option */ + + inner = XSTRDUP(MTYPE_TMP, cp) ; + cp = inner + 1 ; /* strip leading '[' */ + *(cp + strlen(cp) - 1) = '\0' ; /* strip trailing ']' */ + + if (*cp == '\0') + cmd_fail_item(cmd, "empty [option]") ; + } ; + + /* Deal with the inner item */ + cmd_make_item_inner(cmd, n, cp) ; + + /* Worry about the option state, again. */ + if (inner != NULL) + { + XFREE(MTYPE_TMP, inner) ; + + if (n->type == item_vararg) + cmd_fail_item(cmd, "cannot have [.vararg]") ; + + n->type = item_option_word ; /* TODO other option types ? */ + } ; + + /* return newly minted cmd_item item */ + assert(n->type != item_null) ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Make inner part of cmd_item -- so can have [...] anything (in principle). + * + * Require '\0' terminated inner part. + */ +static void +cmd_make_item_inner(cmd_command cmd, cmd_item n, char* cp) +{ + bool eat_name_chars ; /* alphanumeric + '-', '_', '.' and ':' */ + + eat_name_chars = false ; + + if (islower(*cp) /* 'a'..'z' */ + || isdigit(*cp)) /* '0'..'9' */ + { + /* item_keyword -- lowercase alpha numeric + '_' and '-' */ + n->type = item_keyword ; + eat_name_chars = true ; + } + else if (*cp == '*') /* '*' */ + { + /* special item_keyword '*' */ + n->type = item_keyword ; + ++cp ; + } + else if (isupper(*cp)) /* 'A'..'Z' */ + { + n->arg = true ; + + /* WORD or other variable */ + if (strcmp(cp, "A.B.C.D") == 0) + n->type = item_ipv4_address ; + else if (strcmp(cp, "A.B.C.D/M") == 0) + n->type = item_ipv4_prefix ; + else if (strcmp(cp, "X:X::X:X") == 0) + n->type = item_ipv6_address ; + else if (strcmp(cp, "X:X::X:X/M") == 0) + n->type = item_ipv6_prefix ; + else + { + n->type = item_word ; + eat_name_chars = true ; + } ; + + if (n->type != item_word) + cp += strlen(cp) ; /* step past "A.B.C.D" et al */ + } + else if (*cp == '.') /* '.' */ + { + n->arg = true ; + n->type = item_vararg ; + eat_name_chars = true ; + } + else if (*cp == '<') /* '<' */ + { + n->arg = true ; + + cp = cmd_make_item_numeric(cmd, n, ++cp) ; + + if (*cp != '>') + cmd_fail_item(cmd, "badly formed <...>") ; + else + ++cp ; + } + else if (*cp == '\0') + cmd_fail_item(cmd, "cannot have an empty item") ; + + if (eat_name_chars) + { + do ++cp ; while ( isalnum(*cp) + || (*cp == '-') + || (*cp == '_') + || (*cp == ':') + || (*cp == '.') ) ; + + } ; + + if (*cp != '\0') + cmd_fail_item(cmd, "invalid item") ; +} ; + +/*------------------------------------------------------------------------------ + * Make <...> types + * + * Require '\0' terminated <...>, pointing after the '<'. + * + * Assumes the '>' is present. + * + * Returns: where processed up to -- pointing at '>' iff OK. + * + * Supports ranges: + * + * 9-10 => unsigned values in 32b range -- NO sign + * + * +9-10 => unsigned value, where '+' is optional + * 9-+10 => unsigned value, where '+' is *required* + * +9-+10 => same as above + * + * -9-10 => signed value, where '+' is optional + * -9-+10 => signed value, where '+' is *required* + * + * -9--8 => signed value, where '-' is required (!) + * + * + * +/-9 => -9-+9 -- sign is required. + * + * In place of decimal number can use 9b -- giving 2^9 - 1. + */ +static char* +cmd_make_item_numeric(cmd_command cmd, cmd_item n, char* cp) +{ + if (isdigit(*cp) || (*cp == '+') || (*cp == '-')) + { + long m ; + bool pm ; + + confirm((LONG_MAX > item_max_number) && (LONG_MIN < -item_max_number)) ; + + n->type = item_range ; + n->range_sign_allowed = false ; + n->range_sign_required = false ; + + if (strncmp(cp, "+/-", 3) == 0) + { + pm = true ; + n->range_sign_required = true ; + cp += 2 ; /* step to '-' to get -ve range_min. */ + } + else + { + pm = false ; + n->range_sign_allowed = (*cp == '+') ; + } ; + + m = cmd_make_item_number(cmd, n, &cp) ; + n->range_min = m ; + + if (pm) + m = -m ; /* for range_max */ + + else if (*cp == '-') + { + ++cp ; /* past the '-' */ + + n->range_sign_required = (*cp == '+') ; + + m = cmd_make_item_number(cmd, n, &cp) ; + } + + else + cmd_fail_item(cmd, "badly formed <0-1>") ; + + n->range_max = m ; + + if (n->range_min > n->range_max) + cmd_fail_item(cmd, "badly formed <0-1> min > max !") ; + + if ((n->range_sign_required) || (n->range_min < 0)) + n->range_sign_allowed = true ; /* allowed if required ! */ + } ; + + return cp ; +} ; + +/*------------------------------------------------------------------------------ + * Get signed or unsigned value -- process the '9b' form. + * + */ +static long +cmd_make_item_number(cmd_command cmd, cmd_item n, char** p_cp) +{ + long m ; + char* cp ; + + cp = *p_cp ; + m = strtol(cp, p_cp, 10) ; + + if ((*p_cp == cp) || (m > item_max_number)) + cmd_fail_item(cmd, "badly formed or out of range number in <...>") ; + + if (**p_cp == 'b') + { + long s ; + + ++(*p_cp) ; /* step past 'b' */ + s = m ; + m = labs(m) ; + if ((m == 0) || (m > 32)) + cmd_fail_item(cmd, "out of range number in 9b form in <...>") ; + + m = ((long)1 << m) - 1 ; + if (s < 0) + m = -m ; + } ; + + return m ; +} ; + +/*------------------------------------------------------------------------------ + * Compare cmd_item items + * + * Note that command types sort with the larger type value before the smaller. + */ +static int +cmd_cmp_item(const cmd_item* a, const cmd_item* b) +{ + if ((*a)->type != (*b)->type) + return ((*a)->type > (*b)->type) ? -1 : +1 ; /* descending order */ + else + { + if ((*a)->type == item_range) + return cmd_cmp_range_items(*a, *b) ; + else + return els_cmp((*a)->str, (*b)->str) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Compare cmd_item item_range items + */ +static int +cmd_cmp_range_items(const cmd_item a, const cmd_item b) +{ + int as, bs ; + + if (a->range_min != b->range_min) + return (a->range_min < b->range_min) ? -1 : +1 ; + + if (a->range_max != b->range_max) + return (a->range_max < b->range_max) ? -1 : +1 ; + + as = a->range_sign_required ? 2 : (a->range_sign_allowed ? 1 : 0) ; + bs = b->range_sign_required ? 2 : (b->range_sign_allowed ? 1 : 0) ; + + if (as != bs) + return (as < bs) ? -1 : +1 ; + + return 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Set the str and str_len entries for a given item. + * + * The str entry of a cmd_item is set to point at a pool of known values. + * This means that where two str entries are the same, they will have the + * same address ! + */ +static void +cmd_set_str(cmd_item n, const char* str) +{ + uint len ; + word_lump lump ; + char* word ; + + len = strlen(str) ; + + lump = dsl_head(word_lumps) ; + while (1) + { + if (lump == NULL) + { + lump = XCALLOC(MTYPE_CMD_STRING, sizeof(struct word_lump)) ; + lump->end = lump->words ; + + dsl_append(word_lumps, lump, next) ; + } ; + + word = strstr(lump->words, str) ; + if (word != NULL) + break ; + + if (dsl_next(lump, next) != NULL) + { + lump = dsl_next(lump, next) ; + continue ; + } ; + + if (len >= (&lump->words[word_lump_length] - lump->end)) + { + lump = NULL ; + continue ; + } ; + + word = lump->end ; + memcpy(word, str, len) ; + lump->end += len ; + *lump->end = '\0' ; + break ; + } ; + + els_set_n_nn(n->str, word, len) ; +} ; + +/*============================================================================== + * Token objects + */ + +/*------------------------------------------------------------------------------ + * Create a new, empty token_vector with room for a dozen arguments (initially). + */ +static token_vector +cmd_token_vector_new(void) +{ + token_vector tv ; + + tv = XCALLOC(MTYPE_CMD_PARSED, sizeof(token_vector_t)) ; + + vector_init_new(tv->body, 12) ; + + return tv ; +} ; + +/*------------------------------------------------------------------------------ + * Empty token_vector and release all memory if required. + */ +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) ; + + t->w_len = 0 ; /* no words matched to, yet */ + t->seen = 0 ; /* no matches attempted, yet */ +} ; + + +/*------------------------------------------------------------------------------ + * 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) +{ + 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) ; +} ; + +/*============================================================================== + * Parser object + */ + +/*------------------------------------------------------------------------------ + * Allocate and initialise a new cmd_parsed object + */ +extern cmd_parsed +cmd_parsed_new(void) +{ + cmd_parsed parsed ; + + parsed = XCALLOC(MTYPE_CMD_PARSED, sizeof(cmd_parsed_t)) ; + + /* Zeroising the structure has set: + * + * parts = 0 -- cleared by cmd_tokenize() + * tok_total = 0 -- set by cmd_tokenize() + * + * 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_tokenize() + * tokens = NULL -- see below + * + * args = NULL -- see below + * + * emess = NULL -- no error yet + * eloc = 0 -- no error location + * + * in_pipe = cmd_pipe_none + * out_pipe = cmd_pipe_none + * + * first_in_pipe ) + * num_in_pipe ) + * first_do ) + * num_do ) + * first_command ) none + * num_command ) + * first_out_pipe ) + * num_out_pipe ) + * first_comment ) + * num_comment ) + * + * cti ) set by cmd_token_position() + * rp ) + * + * cmd_v = NULL -- no vector of filtered commands + * item_v = NULL -- no vector of filtered items + * + * strongest ) + * best_complete ) set by cmd_filter_prepare() + * min_strength ) + * strict ) + */ + confirm(cmd_pipe_none == 0) ; + + parsed->tokens = cmd_token_vector_new() ; + parsed->args = cmd_arg_vector_new() ; + + return parsed ; +} ; + +/*------------------------------------------------------------------------------ + * Empty out and free a cmd_parsed object + */ +extern cmd_parsed +cmd_parsed_free(cmd_parsed parsed) +{ + if (parsed != NULL) + { + parsed->tokens = cmd_token_vector_free(parsed->tokens) ; + parsed->args = cmd_arg_vector_free(parsed->args) ; + + parsed->cmd_v = vector_reset(parsed->cmd_v, free_it) ; + parsed->item_v = vector_reset(parsed->item_v, free_it) ; + + parsed->emess = qs_reset(parsed->emess, free_it) ; + + XFREE(MTYPE_CMD_PARSED, parsed) ; + } ; + + return NULL ; +} ; + +/*============================================================================== + * Parsing error handling. + * + * + */ +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. + * + * 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 + */ +static cmd_return_code_t +cmd_set_parse_error(cmd_parsed parsed, cmd_token t, usize off, + const char* format, ...) +{ + 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 qstring and see if it is empty -- only whitespace and/or comment + */ +extern bool +cmd_is_empty(qstring line) +{ + cpp_t lp ; + + qs_cpp(lp, line) ; /* NULL -> NULL */ + + while (lp->p < lp->e) + { + if ((*lp->p == ' ') || (*lp->p == '\t')) + ++lp->p ; + else + return ((*lp->p == '!') || (*lp->p == '#')) ; + } ; + + return true ; +} ; + +/*------------------------------------------------------------------------------ + * Reserved characters in a pipe token. + * + * The characters reserved allow for a large number of possible options to + * be attached to the pipe token. + * + * Wish to allow the pipe token to be followed by file name or command name + * without requiring whitespace separation, also do not want to intrude into + * quoted or escaped stuff. So limit the characters that are reserved. + */ +static bool +cmd_pipe_reserved_char(char ch) +{ + return strchr("<|>%&*+-=?", ch) != NULL ; +} ; + +/*------------------------------------------------------------------------------ + * 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 (so any other control characters will + * end up as part of a token). + * + * Ignores leading and trailing ' ' or '\t' (whitespace). + * + * Have "full_lex" flag -- to distinguish old and new. + * + * If not full_lex, we have this simple tokenization: + * + * * tokens are separated by one or more whitespace characters. + * + * * if the first non-whitespace character is '!', this is a comment line. + * + * For the "full_lex" we more or less follow the usual shell conventions, + * except: + * + * * can have '!' as well as '#' to start comments + * + * * 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". + * + * Limiting '|' in this way removes problems with regular expressions. + * + * Also allows a <| shell to use pipes !! + * + * So the "full_lex" will: + * + * * ignore anything between '....' -- as per shell convention, '\' is + * ignored and there is no way to include "'" in a single quoted string. + * + * * ignore anything immediately preceded by '\' -- including space + * (or tab converted to a space) and double quote characters. + * + * * ignore anything between "...." -- including '\"' escapes. + * + * * unbalanced "'" or '"' are treated as if eol was a "'" or '"'. + * + * Except when ignored by the rules above, the "full lex" will recognise + * the following: + * + * * tokens are separated by whitespace -- one ' ' or '\t' characters. + * Whitespace before, after or between tokens is not part of the tokens. + * + * * 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 characters '!', '#' are significant only at the start of a token, + * and then from there to end of line is comment. + * + * Note that this means that comment after a token must be separated by + * at least one space from that token. + * + * * 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. + * + * 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. + * + * 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. + * returns cmd_tok_null if the line is empty (apart from ' ' and '\t') + * or if the elstring was or contained NULL. + * + * Initialises the parsed object, ready for further parsing: + * + * Sets: parsed->parts = cmd_parts_none + * parsed->num_tokens ) + * parsed->elen ) per the results + * parsed->tsp ) + * parsed->tokens ) + * + * Note that the num_tokens does not include the cmd_tok_eol on the end. + */ +extern void +cmd_tokenize(cmd_parsed parsed, qstring line, bool full_lex) +{ + cpp_t lp ; + const char *cp, *tp ; + cmd_token_type_t total ; + uint nt ; + + total = 0 ; /* nothing yet */ + nt = 0 ; + + qs_cpp(lp, line) ; /* NULL -> NULL */ + + cp = lp->p ; + tp = cp ; + while (cp < lp->e) /* process to end */ + { + const char* sp ; + bool end ; + cmd_token_type_t type ; + + if ((*cp == ' ') || (*cp == '\t')) + { + /* skip white-space */ + do + { + end = (++cp == lp->e) ; + } while (!end && ((*cp == ' ') || (*cp == '\t'))) ; + + if (end) + break ; + } ; + + end = false ; + sp = cp ; + type = cmd_tok_simple ; + do + { + switch (*cp) + { + case '\t': /* whitespace at end of token */ + case ' ': + end = true ; + break ; + + case '\'': /* proceed to matching ' or end */ + ++cp ; + if (full_lex) + { + type |= cmd_tok_sq ; + while (cp < lp->e) + { + if (*cp++ == '\'') + break ; + } ; + } ; + break ; + + case '\\': /* step past escaped character, if any */ + ++cp ; + if (full_lex) + { + type |= cmd_tok_esc ; + if (cp < lp->e) + ++cp ; + } ; + break ; + + case '"': /* proceed to matching " or end... */ + ++cp ; + if (full_lex) + { + type |= cmd_tok_dq ; + while (cp < lp->e) /* NB: do not register \ separately */ + { + if (*cp++ == '"') + if (*(cp - 2) != '\\') /* ignore escaped " */ + break ; + } ; + } ; + break ; + + case '|': /* only at start of line */ + if (full_lex && (nt == 0) && (cp == sp)) + { + type = cmd_tok_out_shell ; + do ++cp ; + while ((cp < lp->e) && cmd_pipe_reserved_char(*cp)) ; + end = true ; + } + else + ++cp ; + break ; + + case '>': /* '>' is a separator */ + if (full_lex) + { + 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 '<': /* '<' 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 ; + } + else + ++cp ; + break ; + + default: + ++cp ; + break ; + } ; + } while (!end && (cp < lp->e)) ; + + cmd_token_set(parsed->tokens, nt, type, sp, cp - sp, sp - lp->p) ; + ++nt ; + total |= type ; + + tp = cp ; + } ; + + /* When we get here, tp points just after last character of last token, + * or at start of line if the line is blank (other than whitespace). + * + * If line is empty (apart from whitespace): + * + * - the effective length is zero. + * - the trailing space count is the number of (leading) spaces. + * + * If line is not empty: + * + * - the start position of the first token is the number of leading spaces. + * - the trailing space count is the number of spaces after the last + * token. (If the last token is comment, this will be zero.) + */ + parsed->parts = cmd_parts_none ; + parsed->tok_total = total ; + parsed->num_tokens = nt ; + parsed->elen = tp - lp->p ; + parsed->tsp = lp->e - tp ; + + /* Append an empty end of line token. */ + cmd_token_set(parsed->tokens, parsed->num_tokens, cmd_tok_eol, + 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 options are: + + */ +static cmd_return_code_t +cmd_parse_in_pipe(cmd_parsed parsed, cmd_token t) +{ + cpp_t p ; + bool ok ; + + els_cpp(p, t->ot) ; + + ok = true ; + + switch (cpp_getch(p)) + { + case '<': + switch (cpp_getch(p)) + { + default: + --p->p ; + fall_through ; + + case '\0': + parsed->in_pipe = cmd_pipe_file ; + break ; + + case '|': + parsed->in_pipe = cmd_pipe_shell ; + break ; + } ; + break ; + + default: + 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_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 options are: none + */ +static cmd_return_code_t +cmd_parse_out_pipe(cmd_parsed parsed, cmd_token t) +{ + cpp_t p ; + bool ok ; + + els_cpp(p, t->ot) ; + + ok = true ; + + switch (cpp_getch(p)) + { + case '|': + parsed->out_pipe = cmd_pipe_shell | cmd_pipe_shell_only ; + break ; + + case '>': + switch (cpp_getch(p)) + { + default: + --p->p ; + fall_through ; + + case '\0': + parsed->out_pipe = cmd_pipe_file ; + break ; + + case '>': + parsed->out_pipe = cmd_pipe_file | cmd_pipe_append ; + break ; + + case '|': + parsed->out_pipe = cmd_pipe_shell ; + break ; + + 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_SUCCESS ; + + return cmd_set_parse_error(parsed, t, 0, "invalid 'pipe out'") ; +} ; + +/*------------------------------------------------------------------------------ + * If token is incomplete make a copy of it and '\0' terminate. + * + * If if contains quotes or escapes process those down. + * + * For Quagga purposes, the following is done: + * + * inside '...': all characters stand for themselves, except '\t' -> ' ', + * and, of course, the terminating '. + * + * This is just like the shell. + * + * inside "...": all characters stand for themselves, except '\t' -> ' ', + * and, of course, the terminating ", plus the following + * \x escapes are processed: + * + * \" -> " -- so can have " in "...." + * \\ -> \ -- so can have \ in "...." + * \$ -> $ -- so can have $ in "...." + * + * outside quotes: all characters stand for themselves, except: + * + * \sp -> sp -- so can escape the odd space (cf shell) + * \tab -> sp -- ditto of tab, but map to space + * \? -> ? ) + * \' -> ' ) + * \" -> " ) so can escape the odd meta character + * \< -> < ) where required or to taste. + * \> -> > ) + * \! -> ! ) + * \# -> \# ) + * + * 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. + * + * $ in "..." is reserved for future use as a variable or other + * substitution start. + * + * Returns: CMD_SUCCESS <=> OK + * CMD_ERR_PARSE <=> invalid escape or incomplete quotes + * + * NB: if fails, returns token completed as far as possible. + */ +static cmd_return_code_t +cmd_token_complete(cmd_parsed parsed, cmd_token t) +{ + cpp_t p ; + pp_t q ; + bool dq ; + cmd_return_code_t ret ; + + if ((t->type & cmd_tok_incomplete) == 0) + return CMD_SUCCESS ; /* Quick out if nothing to do */ + + /* To process quotes etc works from the elstring that points to + * the original line, to the qstring. + * + * For quotes and escapes, the result is always no longer than the + * original. + */ + els_cpp_nn(p, t->ot) ; /* original token */ + + qs_new_size(t->qs, p->e - p->p) ; /* discard alias & set qs to be + big enough for original */ + qs_pp_nn(q, t->qs) ; /* where to complete token to */ + + ret = CMD_SUCCESS ; + dq = false ; + while (p->p < p->e) + { + switch (*p->p) + { + case '\t': + *q->p++ = ' ' ; /* '\t' -> ' ' */ + ++p->p ; + break ; + + case '\'': + ++p->p ; /* skip leading ' */ + while (1) + { + if (p->p == p->e) + { + ret = cmd_set_parse_error(parsed, t, 0, "missing closing '") ; + break ; + } ; + + if (*p->p == '\'') + { + ++p->p ; /* skip trailing ' */ + break ; /* done '....' */ + } ; + + if (*p->p == '\t') + { + *q->p++ = ' ' ; /* '\t' -> ' ' */ + ++p->p ; + } + else + *q->p++ = *p->p++ ; /* rest as is */ + } ; + break ; + + case '"': + ++p->p ; /* skip " */ + dq = !dq ; /* switch state */ + break ; + + case '\\': + *q->p++ = *p->p++ ; /* copy the \ */ + if (p->p == p->e) + ret = cmd_set_parse_error(parsed, t, 0, "trailing \\") ; + else + { + if (dq) + { + /* inside "...": \", \$ and \\ only */ + if ((*p->p == '"') || (*p->p == '$') || (*p->p == '\\')) + --q->p ; /* strip the \ */ + } + else + { + /* outside quotes: \sp \tab \< \> \! \# */ + if ( (*p->p == '\t') || (*p->p == ' ') || + (*p->p == '\'') || (*p->p == '"') || + (*p->p == '<') || (*p->p == '>') || + (*p->p == '!') || (*p->p == '#') ) + --q->p ; /* strip the \ */ + } ; + + if (*p->p != '\t') + *q->p++ = *p->p++ ; + else + { + *q->p++ = ' ' ; + ++p->p ; + } ; + } ; + break ; + + default: + *q->p++ = *p->p++ ; + break ; + } ; + } ; + + if (dq) + ret = cmd_set_parse_error(parsed, t, 0, "missing closing \"") ; + + if (ret == CMD_SUCCESS) + { + *q->p = '\0' ; /* '\0' terminate */ + qs_set_len_nn(t->qs, q->p - qs_char_nn(t->qs)) ; + t->term = true ; + } + else + { + qs_set_alias_els(t->qs, t->ot) ; + } ; + + return ret ; +} + +/*------------------------------------------------------------------------------ + * 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: + * + * * where the cursor is just after the last character of a token, it is at + * the end of that token. + * + * * where there is more than one space between tokens, and the cursor is + * after the first space, then it is deemed to be "on" the second token. + * + * - if there are spaces at the start of the line and the cursor is on + * one of those spaces, the it is "on" the first token. + * + * - if the line is blank, with zero or more spaces, the cursor is "on" + * the eol token. + * + * Note that where a line ends in a comment, there are no trailing spaces. + * + * Returns: true <=> "in a special place" + * + * Is "in a special place" if the cursor is: + * + * a. in a quoted string of any type + * b. in an escape (so immediately after a '\') + * c. after the '!' or '#' on a line which consists only of a comment + * d. after the first '<' or '>' of the first pipe token on the line + * + * If NOT "in a special place", will set: + * + * * parsed->cti -- cursor token index + * * parsed->rp -- cursor position relative to the start of token + * + * Note that the cursor token may be a comment or pipe token, or the eol token + * at the end of the line. + */ +extern bool +cmd_token_position(cmd_parsed parsed, qstring line) +{ + cmd_token t ; + uint ti ; + uint cp, ep ; + + cpp_t p ; + const char* q ; + const char* e ; + bool sq, dq, bs ; + + /* Re-initialise parsed object and tokenize the given line */ + cmd_tokenize(parsed, line, true) ; + + /* Get the cursor position */ + cp = qs_cp_nn(line) ; + + /* Look for the last token whose end is <= cp + * + * Will position on last, "eol" token -- which is not counted in + * parsed->num_tokens -- if is beyond the last real token on the line. + */ + t = NULL ; + ti = 0 ; + while (1) + { + t = cmd_token_get(parsed->tokens, ti) ; + + if (cp > t->tp) + { + /* As soon as we are past '|', '<', '>', '!' or '#' + * return "special" + */ + if ((t->type & ( cmd_tok_in_pipe + | cmd_tok_out_pipe + | cmd_tok_out_shell + | cmd_tok_comment) ) != 0) + return true ; + } ; + + ep = t->tp + els_len_nn(t->ot) ; + + if ((cp <= ep) || (ti == parsed->num_tokens)) + break ; /* stop when found token (or at eol) */ + + ++ti ; + } ; + + parsed->cti = ti ; + parsed->ctl = els_len_nn(t->ot) ; + parsed->rp = (int)cp - (int)t->tp ; + + /* Arrive with t = token in which cp belongs + * + * If the token is incomplete, then need to check for "ins" -- unless is + * already "ins". + */ + if ((t->type & cmd_tok_incomplete) == 0) + return false ; + + /* Scan to see if in '...' or "..." or after '\'. */ + + els_cpp_nn(p, t->ot) ; /* original token */ + + q = p->p + (cp - t->tp) ; /* position interested in */ + assert(q > p->p) ; + + dq = false ; + sq = false ; + bs = false ; + + e = (q <= p->e) ? q : p->e ; /* stop at q or end of token */ + + while (p->p < e) + { + switch (*p->p) + { + case '\'': + if (!dq && !bs) /* ignore ' inside "..." & after \ */ + sq = !sq ; + break ; + + case '\"': /* ignore " inside '...' & after \ */ + if (!sq && !bs) + dq = !dq ; + break ; + + case '\\': + if (!sq) /* ignore \ inside '...' */ + bs = !bs ; /* cope with \\ */ + break ; + + default: + break ; + } ; + + ++p->p ; + } ; + + /* Have scanned to but excluding current character or end of token, whichever + * came first. + * + * If in '...' or "...", then is "ins" + * If is immediately after \, then is "ins". + */ + return sq || dq || (bs && (p->e <= q)) ; +} ; + +/*============================================================================== + * Match functions. + * + * Is the given string a, possibly incomplete, value of the required kind ? + */ + +static match_strength_t cmd_ipv4_match(const char* cp, uint prefix) ; +static match_strength_t cmd_prefix_match(const char* cp, uint prefix) ; + +/*------------------------------------------------------------------------------ + * Is this an IPv4 Address + * + * 999.999.999.999 -- each 0..255, no leading zeros, decimal only. + * + * Returns: mt_no_match -- improperly formed + * mt_ipv4_address_partial -- OK as far as it goes (or empty) + * mt_ipv4_address_complete -- syntactically complete + */ +static match_type_t +cmd_ipv4_address_match(cmd_token t) +{ + match_strength_t ms ; + match_type_t mt ; + + if ((t->seen & item_ipv4_address_bit) != 0) + return t->match[item_ipv4_address] ; + + ms = cmd_ipv4_match(cmd_token_make_string(t), 0) ; + + if (ms == ms_var_complete) + mt = mt_ipv4_address_complete ; + else if (ms == ms_partial) + mt = mt_ipv4_address_partial ; + else + mt = mt_no_match ; + + t->seen |= item_ipv4_address_bit ; + + return t->match[item_ipv4_address] = mt ; +} ; + +/*------------------------------------------------------------------------------ + * Is this an IPv4 Prefix + * + * 999.999.999.999/99 -- each 0..255, no leading zeros, decimal only. + * and prefix length must be <= 32 + * + * Returns: mt_no_match -- improperly formed + * mt_ipv4_prefix_partial -- OK as far as it goes (or empty) + * mt_ipv4_prefix_complete -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. + */ +static match_type_t +cmd_ipv4_prefix_match(cmd_token t) +{ + match_strength_t ms ; + match_type_t mt ; + + if ((t->seen & item_ipv4_prefix_bit) != 0) + return t->match[item_ipv4_prefix] ; + + ms = cmd_ipv4_match(cmd_token_make_string(t), 32) ; + + if (ms == ms_var_complete) + mt = mt_ipv4_prefix_complete ; + else if (ms == ms_partial) + mt = mt_ipv4_prefix_partial ; + else + mt = mt_no_match ; + + t->seen |= item_ipv4_prefix_bit ; + + return t->match[item_ipv4_prefix] = mt ; +} ; + +/*------------------------------------------------------------------------------ + * Is this an IPv4 Address or Prefix: + * + * 999.999.999.999[/99] -- each 0..255, no leading zeros, decimal only. + * and prefix length must be <= n + * + * Returns: ms_no_match -- improperly formed + * ms_partial -- OK as far as it goes (or empty) + * ms_var_complete -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. + */ +static match_strength_t +cmd_ipv4_match(const char* cp, uint prefix) +{ + uint nums ; + + for (nums = 0 ; nums < 4 ; ++nums) + { + if (*cp == '.') /* need a '.' except at start */ + { + if (nums == 0) + return ms_no_match ; + + ++cp ; /* step past '.' */ + } + else + { + if (nums != 0) + return (*cp == '\0') ? ms_partial : ms_no_match ; + } ; + + /* Collect a decimal number 0..255, no leading zeros. + * + * Rejects anything other than digits -- including '/' and '.'. + * + * Accepts '\0' as partial -- which accepts empty strings. + */ + if (*cp == '0') + { + ++cp ; + if (isdigit(*cp)) + return ms_no_match ; /* reject leading zeros */ + } + else + { + char* ep ; + + if (isdigit(*cp)) + { + if (strtoul(cp, &ep, 10) <= 255) + cp = ep ; + else + return ms_no_match ; /* reject invalid number */ + } + else + return (*cp == '\0') ? ms_partial : ms_no_match ; + } ; + } ; + + /* Arrive here with 4 numbers */ + + if (prefix == 0) + return (*cp == '\0') ? ms_var_complete : ms_no_match ; + else + return cmd_prefix_match(cp, prefix) ; +} ; + +/*------------------------------------------------------------------------------ + * Is this a Prefix: + * + * /99 no leading zeros, decimal only, value must be <= n. + * + * Arrives here with *cp pointing at where there should be a '/'. + * + * Returns: ms_no_match -- improperly formed + * ms_partial -- OK as far as it goes (or empty). + * ms_var_complete -- syntactically complete + */ +static match_strength_t +cmd_prefix_match(const char* cp, uint prefix) +{ + if (*cp != '/') + return (*cp == '\0') ? ms_partial : ms_no_match ; + + /* OK have '/' and a prefix is now expected. */ + + ++cp ; /* step past '/' */ + + if (*cp == '\0') + return ms_partial ; /* if nothing after '/' */ + + if (*cp == '0') + ++cp ; + else + { + char* ep ; + if (isdigit(*cp) && (strtoul(cp, &ep, 10) <= prefix)) + cp = ep ; + else + return ms_no_match ; /* reject invalid number */ + } ; + + if (*cp != '\0') + return ms_no_match ; /* something other than digits after the '/', + or leading zero, or number too big */ + + return ms_var_complete ; +} ; + +/*------------------------------------------------------------------------------ + * IPv6 Address and Prefix matching. + */ + +#ifdef HAVE_IPV6 + +static match_strength_t cmd_ipv6_match(const char* cp, uint prefix) ; + + + +/*------------------------------------------------------------------------------ + * Is this an IPv6 Address + * + * h:h:... -- RFC 4291 rules & prefix length must be <= n + * + * Returns: mt_no_match -- improperly formed + * mt_ipv6_address_partial -- OK as far as it goes (or empty) + * mt_ipv6_address_complete -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. + */ +static match_type_t +cmd_ipv6_address_match (cmd_token t) +{ + match_strength_t ms ; + match_type_t mt ; + + if ((t->seen & item_ipv6_address_bit) != 0) + return t->match[item_ipv6_address] ; + + ms = cmd_ipv6_match(cmd_token_make_string(t), 0) ; + + if (ms == ms_var_complete) + mt = mt_ipv6_address_complete ; + else if (ms == ms_partial) + mt = mt_ipv6_address_partial ; + else + mt = mt_no_match ; + + t->seen |= item_ipv6_address_bit ; + + return t->match[item_ipv6_address] = mt ; +} ; + +/*------------------------------------------------------------------------------ + * Is this an IPv6 Prefix + * + * h:h:...[/99] -- RFC 4291 rules & prefix length must be <= 128 + * + * Returns: mt_no_match -- improperly formed + * mt_ipv6_prefix_partial -- OK as far as it goes (or empty) + * mt_ipv6_prefix_complete -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. + */ +static match_type_t +cmd_ipv6_prefix_match(cmd_token t) +{ + match_strength_t ms ; + match_type_t mt ; + + if ((t->seen & item_ipv6_prefix_bit) != 0) + return t->match[item_ipv6_prefix] ; + + ms = cmd_ipv6_match(cmd_token_make_string(t), 128) ; + + if (ms == ms_var_complete) + mt = mt_ipv6_prefix_complete ; + else if (ms == ms_partial) + mt = mt_ipv6_prefix_partial ; + else + mt = mt_no_match ; + + t->seen |= item_ipv6_prefix_bit ; + + return t->match[item_ipv6_prefix] = mt ; +} ; + +/*------------------------------------------------------------------------------ + * Is this an IPv6 Address or Prefix: + * + * h:h:...[/99] -- RFC 4291 rules & prefix length must be <= n + * + * Returns: ms_no_match -- improperly formed + * ms_partial -- OK as far as it goes (or empty) + * ms_var_complete -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. + */ +static match_strength_t +cmd_ipv6_match(const char* cp, uint prefix) +{ + bool double_colon ; + uint nums ; + + double_colon = false ; + nums = 0 ; + + /* At start, the first time around the loop... */ + + for (nums = 0 ; nums < 8 ; ++nums) + { + const char* sp, * ep ; + + /* After number (nums != 0), or at start. + * + * Deal with (a) ':', '::' and '::/'. + * (b) '/' -- valid only if had '::'. + * (c) '\0' -- partial unless have had '::' + * (d) if not at start, must have one of the above. + */ + if (*cp == ':') + { + /* (a) ':', '::' and '::/'. + * + * At start can accept '::', but not ':' (unless '\0' follows). + * + * If not at start, accept ':' and accept '::' if not already seen. + * + * After '::' can have the full complement of numbers, or '/' or + * '\0' which bring the number part to an end. + * + * After ':' we accept '\0' but explicitly reject '/' (and we + * reject a ':' at the start if not followed by '\0'). + */ + ++cp ; /* step past ':' */ + + if (*cp == ':') + { + /* '::' -- counts as number, can be followed by '/' */ + + if (double_colon) + return ms_no_match ; /* at most one */ + + ++cp ; /* step past '::' */ + + double_colon = true ; + ++nums ; /* counts as a number */ + + if ((nums == 8) || (*cp == '/') || (*cp == '\0')) + break ; /* no more numbers */ + } + else if (*cp == '\0') + return ms_partial ; /* accepts bare ':', inter alia */ + + else if ((*cp == '/') || (nums == 0)) + return ms_no_match ; + } + else if (*cp == '/') + { + /* (b) '/' -- valid only if had '::'. */ + if (double_colon) + break ; + else + return ms_no_match ; + } + else if (*cp == '\0') + { + /* (c) '\0' -- partial unless have had '::' */ + if (double_colon) + break ; + else + return ms_partial ; /* accept empty string, inter alia */ + } + else if (nums != 0) + /* (d) if not at start, must have one of the above. */ + return ms_no_match ; + + assert(*cp != '\0') ; + + /* Is now at start, or after ':' and is not '\0'. + * + * Require 1..4 hex digits -- will also accept 1..3 decimals ! + * + * Rejects anything else, including '/' at this stage. + */ + sp = cp ; + ep = cp + 4 ; + do + { + if (((*cp >= '0') && (*cp <= '9')) || ((*cp >= 'A') && (*cp <= 'F')) + || ((*cp >= 'a') && (*cp <= 'f'))) + ++cp ; + else + { + if (cp > sp) + break ; + else + return ms_no_match ; /* no digits */ + } + } + while (cp < ep) ; + + /* Watch out for '.' ! */ + + if (*cp == '.') + { + /* Can have IPv4 trailing part, if that would account for the + * last two number parts of the IPv6. + * + * Note that a '.' after something which is not simple decimal + * 0..255 will be rejected by cmd_ipv4_match(). + * + * Note also that we pass through the prefix requirement. + */ + if ((nums == 6) || (double_colon && (nums < 6))) + return cmd_ipv4_match(sp, prefix) ; + else + return ms_no_match ; + } ; + } ; + + /* Arrives here either because nums == 8, or met '/' or '\0' after ':: + * + * So only get here if have a valid end of the digits part of the IPv6 + */ + + assert((nums == 8) || double_colon) ; + + if (prefix == 0) + return (*cp == '\0') ? ms_var_complete : ms_no_match ; + else + return cmd_prefix_match(cp, prefix) ; +} ; + + +#endif /* HAVE_IPV6 */ + +/*------------------------------------------------------------------------------ + * Is this a decimal number in the allowed range: + * + * Returns: mt_no_match -- improperly formed or empty + * mt_range_partial -- OK as far as it went (or empty string) + * mt_range_complete -- syntactically complete + */ +static match_type_t +cmd_range_match (cmd_item item, cmd_token t) +{ + const char* cp, * dp ; + char *ep ; + int base ; + long val; + + confirm((LONG_MAX > item_max_number) && (LONG_MIN < -item_max_number)) ; + + cp = cmd_token_make_string(t) ; + + /* Worry about any sign */ + + dp = cp ; + + if ((*cp == '-') || (*cp == '+')) + { + if (!item->range_sign_allowed) + return mt_no_match ; /* reject '-' or '+' */ + + ++dp ; /* step to digit */ + } + else + { + if (item->range_sign_required) + return mt_no_match ; + } ; + + /* Worry about leading digits and hex, and no digits at all */ + + base = 10 ; /* by default. */ + + if (*dp == '\0') + return mt_range_partial ; /* accepts empty string, inter alia */ + + else if (*dp == '0') + { + ++dp ; /* step past zero */ + + if (*dp != '\0') + { + /* No leading zeros and no stinking octal -- but allow hex */ + if ((*dp != 'x') && (*dp != 'X')) + return mt_no_match ; + + ++dp ; /* step past 'x' or 'X' */ + base = 16 ; + + if (*dp == '\0') + return mt_range_partial ; + } ; + } + + else if (!isdigit(*dp)) + return mt_no_match ; + + /* The string starts with digit, possibly preceded by sign, and possibly + * an 'x' or 'X' with at least 1 further character. + */ + val = strtol(cp, &ep, base) ; + if (*ep != '\0') + return mt_no_match ; + + /* Is the result in range ? */ + + if (val >= item->range_min && val <= item->range_max) + return mt_range_complete ; /* on the money */ + + /* Want to return mt_range_partial iff adding digits might make + * an in range value. + * + * If val is < 0, then adding digits makes it smaller. + * If val is == 0, not allowed to add digits. + * If val is > 0, then adding digits makes it bigger. + */ + if (val < item->range_min) + { + /* Is less than minimum, so partial match if can get bigger. */ + return (val > 0) ? mt_range_partial : mt_no_match ; + } + else + { + /* Is more than maximum, so partial match if can get smaller. */ + return (val < 0) ? mt_range_partial : mt_no_match ; + } ; +} ; + +/*============================================================================== + * Command "filtering". + * + * The command parsing process starts with a (shallow) copy of the cmd_vector + * entry for the current "node". + * + * So cmd_v contains pointers to struct cmd_command values. When match fails, + * the pointer is set NULL -- so parsing is a process of reducing the cmd_v + * down to just the entries that match. + * + * Each cmd_command has a vector "items", which contains an entry for each + * "token" position. That entry is a vector containing the possible values at + * that position. + */ + +static int cmd_item_filter(cmd_parsed parsed, cmd_item item, cmd_token t) ; + +/*------------------------------------------------------------------------------ + * Prepare to filter commands in the node being parsed in. + * + * The execute option turns off all partial matching -- so will not match, say, + * 222 as a possible IP address ! This means that the result of the filter + * operation will be executable command(s), only. + * + * The execute option also pre-filters the command vector to discard all + * commands which are too short or too long to match the current line. + * + * The strict option turns off partial matching of keywords, so only complete + * keywords will do. This is used for the configuration file, so that new + * commands can be added ! + * + * Returns: number of commands which may match to. + */ +static uint +cmd_filter_prepare(cmd_parsed parsed, cmd_context context) +{ + vector src_v ; + uint ci ; + uint nct ; + + /* get commands for the current node */ + 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 -- 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. + */ + parsed->cmd_v = vector_re_init(parsed->cmd_v, vector_length(src_v)) ; + + /* Filter out commands which are too short. + * + * For execution, can filter out commands which are too long. + */ + nct = parsed->num_command ; + + for (ci = 0 ; ci < vector_length(src_v) ; ++ci) + { + cmd_command cmd ; + + cmd = vector_get_item(src_v, ci) ; + + if ( cmd->nt_max < nct) + continue ; /* ignore if too short */ + + if ((cmd->nt_min > nct) && context->parse_execution) + continue ; /* ignore if too long */ + + vector_push_item(parsed->cmd_v, cmd) ; + } ; + + /* Other preparation for filtering + * + * The min_strength is set according to whether is for execution. The + * strongest match is set from that. This means that any match which does + * not meet the minimum criterion is automatically excluded. + */ + + 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 ; + + return vector_length(parsed->cmd_v) ; +} ; + +/*------------------------------------------------------------------------------ + * Filter set commands by matching items against the given token. + * + * Takes: parsed -- cmd_parsed object, previously prepared by + * cmd_prepare_fiter(). + * ii -- item index (0 == first) + * keep_items -- how to filter + * + * The item index is the index wrt the start of the commands being matched, + * not the index of the token in the command line. + * + * Keywords must match strictly or partly, depending on the filtering state + * in the parsed object. + * + * Variables must match completely if filtering for execution, otherwise may + * match partially. + * + * Returns: the number of items matched. + * + * NB: when filtering for execution, will not accept any partial matches for + * variables (but may accept partial matches for keywords). So, once the + * last token has been filtered against, the number of items matched gives + * the number of unambiguous commands, with complete values, that the line + * matches. + */ +static uint +cmd_filter(cmd_parsed parsed, uint ii, bool keep_items) +{ + uint ci ; /* command index -- in cmd_v */ + uint ti ; /* token index in command line */ + uint c_keep ; + uint i_keep ; + cmd_token t ; + + /* Reset the filtering state */ + + parsed->strongest = parsed->min_strength ; + parsed->best_complete = mt_no_match ; + + /* Decide which token we are trying to match. */ + if (ii < parsed->num_command) + ti = parsed->first_command + ii ; /* map to token index */ + else + { + /* special case of filtering against the empty token at the end of + * the command line -- this is for command line help stuff. + * + * Note that there may be out_pipe or even comment tokens in between, + * so we here set the ti to the trailing eol token. + */ + assert(ii == parsed->num_command) ; + ti = parsed->num_tokens ; + } + 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 ; + + for (ci = 0; ci < vector_length(parsed->cmd_v); ci++) + { + cmd_command cmd ; + cmd_item item ; + int best ; + + cmd = vector_get_item(parsed->cmd_v, ci) ; + + if (ii < cmd->nt) + item = vector_get_item(cmd->items, ii) ; + + else if (ii == cmd->nt_max) + item = &eol_item ; /* match at end of line */ + + else if (ii > cmd->nt_max) + continue ; /* discard commands we are beyond, now */ + + else /* cmd->nt < cmd->nt_max <=> vararg. */ + { + /* It is elsewhere arranged that a vararg is always the last + * item for a given command. + * + * We cope with all tokens from the first vararg onwards by matching + * them all against the vararg. Inter alia this allows for a + * vararg to check each argument in turn -- iff feel like doing + * that. + */ + item = cmd->vararg ; + + /* Must have something and must now only check against the varag. + * (Not anything else in the (...) if vararg was in one !) + */ + assert((item != NULL) && (item->next == NULL)) ; + } ; + + /* See if get any sort of match at current position */ + best = -1 ; + while (item != NULL) + { + int ret ; + ret = cmd_item_filter(parsed, item, t) ; + + if (ret >= 0) + { + if (ret > 0) + i_keep = 0 ; + + if (keep_items) + vector_set_item(parsed->item_v, i_keep++, item) ; + else + ++i_keep ; + } ; + + if (ret > best) + best = ret ; + + item = item->next ; + } ; + + /* Keep if had a match */ + if (best >= 0) + { + if (best > 0) + c_keep = 0 ; /* better than all the rest */ + + vector_set_item(parsed->cmd_v, c_keep++, cmd) ; + } ; + } ; + + if (keep_items) + vector_set_length(parsed->item_v, i_keep) ; /* discard what did not keep */ + + vector_set_length(parsed->cmd_v, c_keep) ; /* discard what did not keep */ + + return i_keep ; +} ; + +/*------------------------------------------------------------------------------ + * Filter given item, in given parsed state, attempting to match given token. + * + * Update the parsed state if get a better match. + * + * Returns: < 0 => is not as good ) + * == 0 => equally good ) compared to best match to date. + * > 0 => better than ) + * + * NB: the matching functions will partially match an empty string. + * + * The ms_anything types of item, will match an empty string. + */ +static int +cmd_item_filter(cmd_parsed parsed, cmd_item item, cmd_token t) +{ + match_strength_t ms ; + match_type_t mt ; + int cw ; + + /* Ignore item if the best we can hope for is not as strong as what we + * have already. + */ + mt = item_best_match(item->type) ; + ms = match_match_strength(mt) ; + + if ((mt < parsed->best_complete) || (ms < parsed->strongest)) + return -1 ; /* cannot even be as good */ + + /* Bang the rocks together to get match type + * + * TODO: do we need mt_xxx_partial etc ? + */ + if (t->type == cmd_tok_eol) + { + mt = (item->type == item_eol) ? mt_eol : mt_eol_partial ; + mt = mt_eol_partial ; + } + else + { + switch (item->type) + { + case item_null: + zabort("invalid item_null") ; + break ; + + case item_eol: + break ; + + case item_keyword: + if ((t->word == els_body_nn(item->str)) + && (t->w_len == els_len_nn(item->str))) + cw = t->cw ; + else + { + cw = els_cmp_word(t->ot, item->str) ; + t->word = els_body_nn(item->str) ; + t->w_len = els_len_nn(item->str) ; + t->cw = cw ; + } ; + + if (cw > 0) /* nope */ + mt = mt_no_match ; + else if (cw == 0) /* exact match */ + mt = mt_keyword_complete ; + else /* partial match */ + mt = parsed->strict ? mt_no_match + : mt_keyword_incomplete ; + break ; + + case item_range: + mt = cmd_range_match(item, t) ; + break ; + + case item_ipv4_address: + mt = cmd_ipv4_address_match(t) ; + break ; + + case item_ipv4_prefix: + mt = cmd_ipv4_prefix_match(t) ; + break ; + + case item_ipv6_address: + #ifdef HAVE_IPV6 + mt = cmd_ipv6_address_match(t) ; + #endif /* HAVE_IPV6 */ + break ; + + case item_ipv6_prefix: + #ifdef HAVE_IPV6 + mt = cmd_ipv6_prefix_match(t) ; + #endif /* HAVE_IPV6 */ + break ; + + case item_word: + mt = mt_word_match ; + break ; + + case item_vararg: + mt = mt_vararg_match ; + break ; + + case item_option_word: + mt = mt_option_word_match ; + break ; + + default: + zabort("unknown item type") ; + } ; + } ; + + /* Easy if did not match at all */ + if (mt == mt_no_match) + return -1 ; + + /* Is what we got worse, as good or better ? + * + * Update parsed to suit and return the news. + * + * Note that parsed->best_complete will be ms_no_match until parsed->strongest + * is set to ms_var_complete. + */ + ms = match_match_strength(mt) ; + + if (ms < parsed->strongest) + return -1 ; + + if (ms == parsed->strongest) + return 0 ; + + parsed->strongest = ms ; + return +1 ; +} ; + +/*============================================================================== + * Parsing of command lines + */ + +/*------------------------------------------------------------------------------ + * Parse a command in the given "node", if possible, ready for execution. + * + * If 'exact': use cmd_filter_by_string() + * otherwise: use cmd_filter_by_completion() + * + * If 'do': see if there is a 'do' at the front and proceed accordingly. + * + * If 'tree': move up the node tree to find command if not found in the + * current node. + */ + +static cmd_return_code_t cmd_parse_phase_one(cmd_parsed parsed, + 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_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 tokenized. + * + * Returns: CMD_SUCCESS => successfully parsed command, and the result is + * in the given parsed structure, ready for execution. + * + * - parsed->cnode may not be the incoming node. + * + * - parsed->nnode is set for when command succeeds. + * + * - parsed->parts is what was found + * + * - parsed->cmd->daemon => daemon + * + * CMD_EMPTY => line is empty, except perhaps for comment + * (iff parsing for execution) + * + * CMD_ERR_INCOMPLETE => "do" and nothing more + * (iff parsing for execution) + * + * CMD_SUCCESS_DAEMON => parsed successfully. Something for vtysh ?? + * + * CMD_ERR_NO_MATCH => could find nothing to match the command + * line. + * + * CMD_ERR_AMBIGUOUS => found 2 or more possible matches. + * Or, if not parsing for execution, there + * were no command tokens. + * + * CMD_ERR_PARSING => something wrong ! See parsed->emess. + * + * NB: unless cmd_parse_no_tree, may have tried any ancestor nodes. Returns + * with parsed->cnode with node last tried. + * + * NB: unless cmd_parse_no_do, will have taken a leading "do", and pushed the + * parsed->cnode to ENABLE_NODE (if in MIN_DO_SHORTCUT_NODE or higher). + * + * NB: the command line MUST be preserved until the parsed command is no + * longer required -- no copy is made. + * + * NB: expects to have free run of everything in the vty structure (except + * the contents of the vty_io sub-structure) until the command completes. + * + * See elsewhere for description of parsed structure. + */ +extern cmd_return_code_t +cmd_parse_command(cmd_parsed parsed, cmd_context context) +{ + cmd_return_code_t ret ; + + /* Level 1 parsing + * + * Break down the tokens into: + * + * 1) in-pipe or command (with or without "do") + * 2) possible out-pipe + * 3) possible comment + * + * Complete any tokens which contain quotes and/or escapes. + * + * 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, context) ; + if (ret != CMD_SUCCESS) + { + assert(ret == CMD_ERR_PARSING) ; /* no other error at this point */ + return ret ; + } ; + + /* 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) && context->parse_execution) + { + if ((parsed->parts & ~cmd_part_comment) == cmd_parts_none) + return CMD_EMPTY ; /* accept empty */ + + if ((parsed->parts & cmd_part_do) != 0) + return CMD_ERR_INCOMPLETE ; /* reject "do" alone */ + + return CMD_SUCCESS ; /* accept pipes */ + } ; + + /* Level 2 parsing + * + * Try in the current node and then in parent nodes, if allowed. + * + * 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 + * CMD_ERR_AMBIGUOUS. + * + * Note that cmd_parse_phase_two only returns CMD_SUCCESS, CMD_ERR_NO_MATCH + * or CMD_ERR_AMBIGUOUS. + */ + while (1) + { + node_type_t pnode ; + + ret = cmd_parse_phase_two(parsed, context) ; + + if (ret == CMD_SUCCESS) + break ; + + if ((ret != CMD_ERR_NO_MATCH) || context->parse_no_tree) + return ret ; + + pnode = cmd_node_parent(parsed->cnode) ; + + if (pnode == parsed->cnode) + return CMD_ERR_NO_MATCH ; /* done if no parent node. */ + + parsed->cnode = pnode ; + } ; + + /* 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: + * + * (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 (context->parse_execution) + { + uint ti ; + + cmd_arg_vector_empty(parsed) ; + + for (ti = 0; ti < parsed->num_command ; ti++) + { + bool take ; + + if (ti < parsed->cmd->nt_min) + { + cmd_item item = vector_get_item (parsed->cmd->items, ti); + + take = item->arg ; /* follow item */ + } + else + take = true ; /* option or vararg token */ + + if (take) + { + cmd_token t ; + t = cmd_token_get(parsed->tokens, parsed->first_command + ti) ; + cmd_arg_vector_push(parsed, cmd_token_make_string(t)) ; + } ; + } ; + } ; + + /* Return appropriate form of success */ + return parsed->cmd->daemon ? CMD_SUCCESS_DAEMON + : CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Phase 1 of command parsing -- get ready to search the command system. + * + * Scan the tokens to break them up into the sections: + * + * - in-pipe -- '<' etc up to '>' or comment + * - do -- leading 'do' on the command (empty if in-pipe) + * - command -- actual command (empty if in-pipe) + * - out-pipe -- '>' etc up to comment + * - comment -- '!' or '#' onwards + * + * 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_context context) +{ + uint nt = parsed->num_tokens ; + + /* Set command and parsing entries */ + parsed->nnode = parsed->cnode = context->node ; + parsed->cmd = NULL ; + + /* pick off any comment */ + if ((parsed->tok_total & cmd_tok_comment) != 0) + { + parsed->num_comment = 1 ; + parsed->first_comment = --nt ; /* implicitly the last */ + parsed->tok_total ^= cmd_tok_comment ; + parsed->parts |= cmd_part_comment ; + } ; + + /* If this is not a simple line need to do some extra work: + * + * - identify and check any pipe items + * - + */ + if (parsed->tok_total == cmd_tok_simple) + { + /* All tokens are simple and there is at least one */ + parsed->first_command = 0 ; + parsed->num_command = nt ; + parsed->parts |= cmd_part_command ; + } + else if (parsed->tok_total != 0) + { + /* There is at least one not-simple cmd_token. */ + cmd_return_code_t ret ; + ret = cmd_parse_phase_one_b(parsed, nt) ; + + if (ret != CMD_SUCCESS) + return ret ; + } ; + + /* If have a have a 'do' at the front, account for it */ + if ((parsed->parts & cmd_part_command) != 0) + { + /* Have a command -- worry about "do" if allowed */ + if (!context->parse_no_do && (context->node >= MIN_DO_SHORTCUT_NODE)) + { + cmd_token t ; + t = cmd_token_get(parsed->tokens, parsed->first_command) ; + if (els_len_nn(t->ot) == 2) + { + const char* p = els_body_nn(t->ot) ; + if ((*p == 'd') && (*(p+1) == 'o')) + { + parsed->cnode = ENABLE_NODE ; /* change to this node */ + + parsed->num_do = 1 ; + parsed->first_do = parsed->first_command ; + --parsed->num_command ; + ++parsed->first_command ; + parsed->parts |= cmd_part_do ; + /* If have *only* the "do", we don't have a command */ + if (parsed->num_command == 0) + parsed->parts ^= cmd_part_command ; + } ; + } ; + } ; + } ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Phase 1b of command parsing + * + * tokenizer found at least one of: + * + * - in pipe token + * - out pipe token + * - token with quotes or escapes + * + * Deal with all of those, verifying the syntax of any pipes and completing + * any tokens with quotes etc. + * + * 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 + */ +static cmd_return_code_t +cmd_parse_phase_one_b(cmd_parsed parsed, uint nt) +{ + cmd_return_code_t ret ; + cmd_token t ; + cmd_parts_t parts ; + uint i, n ; + uint* pn ; + + parts = cmd_parts_none ; /* no parts yet */ + 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) ; + + /* 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) + { + parts = cmd_part_command ; + parsed->first_command = i ; + pn = &parsed->num_command ; + n = 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_part_command | cmd_parts_pipe)) == cmd_part_command) + return cmd_set_parse_error(parsed, t, 0, "unexpected 'pipe in'") ; + + 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 ; + } + } + + /* '|' 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_part_command | cmd_parts_pipe)) == cmd_part_command) + return cmd_set_parse_error(parsed, t, 0, "unexpected 'shell pipe'") ; + + 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'") ; + + 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 ; + } ; + } ; + + 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) ; + if (ret != CMD_SUCCESS) + return ret ; + } ; + + ++n ; /* count up tokens */ + } ; + + if (pn != NULL) + *pn = n ; /* number in last phase */ + + /* If have an in-pipe or an out-pipe, worry about the number of + * arguments + */ + if ((parts & cmd_parts_pipe) != 0) + { + const char* msg = NULL ; + uint i ; + bool e ; + + /* If there is an in_pipe part, check number of arguments */ + if ((msg == NULL) && ((parts & cmd_part_in_pipe) != 0)) + { + assert(parsed->num_in_pipe > 0) ; + + if (((parsed->in_pipe & cmd_pipe_file) != 0) + && (parsed->num_in_pipe != 2)) + { + if (parsed->num_in_pipe == 1) + { + i = parsed->first_in_pipe ; + e = true ; + msg = "requires file" ; + } + else + { + i = parsed->first_in_pipe + 2 ; + e = false ; + msg = "expects file only" ; + } ; + } ; + + if (((parsed->in_pipe & cmd_pipe_shell) != 0) + && (parsed->num_in_pipe < 2)) + { + i = parsed->first_in_pipe ; + e = true ; + msg = "requires shell command" ; + } ; + } ; + + /* If there is an out_pipe part, check the number of arguments */ + if ((msg == NULL) && ((parts & cmd_part_out_pipe) != 0)) + { + assert(parsed->num_out_pipe > 0) ; + + if (((parsed->out_pipe & cmd_pipe_file) != 0) + && (parsed->num_out_pipe != 2)) + { + if (parsed->num_out_pipe == 1) + { + i = parsed->first_out_pipe ; + e = true ; + msg = "requires file" ; + } + else + { + i = parsed->first_out_pipe + 2 ; + e = false ; + msg = "expects file only" ; + } ; + } ; + + if (((parsed->out_pipe & cmd_pipe_shell) != 0) + && (parsed->num_out_pipe < 2)) + { + assert(parsed->num_out_pipe > 0) ; + + i = parsed->first_out_pipe ; + e = true ; + msg = "requires shell command" ; + } ; + } ; + + if (msg != NULL) + { + t = cmd_token_get(parsed->tokens, i) ; + return cmd_set_parse_error(parsed, t, e ? els_len_nn(t->ot) : 0, msg) ; + } ; + } ; + + /* It's OK -- so record the parts found and return success */ + parsed->parts |= parts ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Phase 2 of command parsing -- parsing for execution. + * + * Assumes phase 1 completed successfully. + * + * Note that if parsed->num_command == 0, will have constructed a cmd_v, with + * all possible commands in it (depending on cmd_parse_execution). + * + * Returns: CMD_SUCCESS -- parsed successfully + * CMD_ERR_NO_MATCH -- could find nothing that matches + * CMD_ERR_AMBIGUOUS -- found more than one match + * or parsed->num_command == 0 + */ +static cmd_return_code_t +cmd_parse_phase_two(cmd_parsed parsed, cmd_context context) +{ + uint ii ; + uint match ; + + /* Prepare to filter commands */ + + cmd_filter_prepare(parsed, context) ; + + match = 2 ; /* in case parsed->num_command == 0 ! */ + + for (ii = 0 ; ii < parsed->num_command ; ii++) + match = cmd_filter(parsed, ii, false) ; + + /* Should end up with one command to execute. */ + if (match == 0) + return CMD_ERR_NO_MATCH ; + + if (match > 1) + return CMD_ERR_AMBIGUOUS ; + + parsed->cmd = vector_get_item(parsed->cmd_v, 0) ; + + 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 + * + * Returns NULL if dst is an option, variable of vararg. + * + * NULL or empty src are deemed to match. + */ +static const char * +cmd_entry_function (const char *src, const char *dst) +{ + if (CMD_OPTION (dst) || CMD_VARIABLE (dst) || CMD_VARARG (dst)) + return NULL; + + if ((src == NULL) || (*src == '\0')) + return dst; + + if (strncmp (src, dst, strlen (src)) == 0) + return dst; + + return NULL; +} + +/* If src matches dst return dst string, otherwise return NULL */ +/* This version will return the dst string always if it is + CMD_VARIABLE for '?' key processing */ +static const char * +cmd_entry_function_item (const char *src, const char *dst) +{ + if (CMD_VARARG (dst)) + return dst; + + if (CMD_RANGE (dst)) + { + if (cmd_range_match (dst, src)) + return dst; + else + return NULL; + } + +#ifdef HAVE_IPV6 + if (CMD_IPV6 (dst)) + { + if (cmd_ipv6_match (src)) + return dst; + else + return NULL; + } + + if (CMD_IPV6_PREFIX (dst)) + { + if (cmd_ipv6_prefix_match (src)) + return dst; + else + return NULL; + } +#endif /* HAVE_IPV6 */ + + if (CMD_IPV4 (dst)) + { + if (cmd_ipv4_match (src)) + return dst; + else + return NULL; + } + + if (CMD_IPV4_PREFIX (dst)) + { + if (cmd_ipv4_prefix_match (src)) + return dst; + else + return NULL; + } + + /* Optional or variable commands always match on '?' */ + if (CMD_OPTION (dst) || CMD_VARIABLE (dst)) + return dst; + + /* In case of 'command \t', given src is NULL string. */ + if (src == NULL) + return dst; + + if (strncmp (src, dst, strlen (src)) == 0) + return dst; + else + return NULL; +} + +/*------------------------------------------------------------------------------ + * Check same string element existence. + * + * Returns: 0 => found same string in the vector + * 1 => NOT found same string in the vector + */ +static bool +cmd_unique_string (vector v, const char *str) +{ + unsigned int i; + char *match; + + for (i = 0; i < vector_length (v); i++) + if ((match = vector_get_item (v, i)) != NULL) + if (strcmp (match, str) == 0) + return 0; + return 1; +} + +/* Compare string to description vector. If there is same string + return 1 else return 0. */ +static bool +item_unique_string (vector v, const char *str) +{ + unsigned int i; + struct cmd_item *item; + + for (i = 0; i < vector_length (v); i++) + if ((item = vector_get_item (v, i)) != NULL) + if (strcmp (item->cmd, str) == 0) + return 1; + return 0; +} +#endif +/*============================================================================== + * '?' describe command support. + */ + +/*------------------------------------------------------------------------------ + * Get description of current (partial) command + * + * Returns: NULL => no description available + * + * status set to CMD_ERR_NO_MATCH or CMD_ERR_AMBIGUOUS + * + * or: address of vector of "struct cmd_item" values available. + * + * NB: when a vector is returned it is the caller's responsibility to + * vector_free() it. (The contents are all effectively const, so do not + * themselves need to be freed.) + */ +extern vector +cmd_describe_command (const char* line, node_type_t node, + cmd_return_code_t* status) +{ +#if 0 + vector ret ; + struct cmd_parsed parsed_s ; + cmd_parsed parsed ; + cmd_token_type_t tok_total ; + + /* Set up a parser object and tokenize the command line */ + parsed = cmd_parse_init_new(&parsed_s) ; + tok_total = cmd_tokenize(parsed, line, node) ; + + + + + + + + /* Level 1 parsing + * + * Strip quotes and escapes from all the tokens. + */ + if (tok_total != cmd_tok_simple) + { + ret = cmd_parse_phase_one(parsed) ; + if (ret != CMD_SUCCESS) + return ret ; + } ; + + /* If allowed to 'do', see if there. + * + * 'do' forces command to be parsed in ENABLE_NODE (if allowed) + */ + if ((type & cmd_parse_no_do) == 0) + cmd_try_do_shortcut(parsed) ; + + + + + return cmd_describe_command_real (tokens, node, status); + + + + static vector + cmd_describe_command_real (vector tokens, int node, int *status) + { + unsigned int i; + vector cmd_vector; + #define INIT_MATCHVEC_SIZE 10 + vector matchvec; + struct cmd_command *cmd_command; + unsigned int index; + int ret; + enum match_type match; + char *command; + + /* Set index. */ + if (vector_length (tokens) == 0) + { + *status = CMD_ERR_NO_MATCH; + return NULL; + } + else + index = vector_length (tokens) - 1; + + /* Make copy vector of current node's command vector. */ + cmd_vector = vector_copy (cmd_node_vector (cmdvec, node)); + + /* Prepare match vector */ + matchvec = vector_init (INIT_MATCHVEC_SIZE); + + /* Filter commands. */ + /* Only words precedes current word will be checked in this loop. */ + for (i = 0; i < index; i++) + if ((command = vector_get_item (tokens, i))) + { + match = cmd_filter(command, cmd_vector, i, any_match) ; + + if (match == vararg_match) + { + struct cmd_command *cmd_command; + vector descvec; + unsigned int j, k; + + for (j = 0; j < vector_length (cmd_vector); j++) + if ((cmd_command = vector_get_item (cmd_vector, j)) != NULL + && (vector_length (cmd_command->items))) + { + descvec = vector_get_item (cmd_command->items, + vector_length (cmd_command->items) - 1); + for (k = 0; k < vector_length (descvec); k++) + { + struct cmd_item *item = vector_get_item (descvec, k); + vector_set (matchvec, item); + } + } + + vector_set (matchvec, &item_cr); + vector_free (cmd_vector); + + return matchvec; + } ; + + ret = is_cmd_ambiguous (command, cmd_vector, i, match) ; + if (ret != 0) + { + vector_free (cmd_vector); + vector_free (matchvec); + *status = (ret == 1) ? CMD_ERR_AMBIGUOUS + : CMD_ERR_NO_MATCH ; + return NULL ; + } ; + } + + /* Prepare match vector */ + /* matchvec = vector_init (INIT_MATCHVEC_SIZE); */ + + /* Make sure that cmd_vector is filtered based on current word */ + command = vector_get_item (tokens, index); + if (command) + match = cmd_filter(command, cmd_vector, index, any_match); + + /* Make description vector. */ + for (i = 0; i < vector_length (cmd_vector); i++) + { + vector items ; + + cmd_command = vector_get_item (cmd_vector, i) ; + if (cmd_command == NULL) + continue ; + + /* Ignore cmd_command if no tokens at index position. + * + * Deal with special case of possible <cr> completion. + */ + items = cmd_command->items; + if (index >= vector_length (items)) + { + if (command == NULL && index == vector_length (items)) + { + if (!item_unique_string (matchvec, cr_string)) + vector_push_item(matchvec, &item_cr); + } + continue ; + } ; + + /* Check if command is completed. */ + unsigned int j; + vector descvec = vector_get_item (items, index); + struct cmd_item *item; + + for (j = 0; j < vector_length (descvec); j++) + if ((item = vector_get_item (descvec, j))) + { + const char *string; + + string = cmd_entry_function_desc (command, item->cmd); + if (string) + { + /* Uniqueness check */ + if (!item_unique_string (matchvec, string)) + vector_push_item(matchvec, item); + } + } ; + } ; + + vector_free (cmd_vector); + + if (vector_length(matchvec) == 0) + { + vector_free (matchvec); + *status = CMD_ERR_NO_MATCH; + return NULL; + } + + *status = CMD_SUCCESS; + return matchvec; + } + + cmd_parse_reset(parsed, false) ; + + return +#endif + + return NULL ; +} ; + +#if 0 +/*------------------------------------------------------------------------------ + * Check LCD of matched command. + * + * Scan list of matched keywords, and by comparing them pair-wise, find the + * longest common leading substring. + * + * Returns: 0 if zero or one matched keywords + * length of longest common leading substring, otherwise. + */ +static int +cmd_lcd (vector matchvec) +{ + int n ; + int i ; + int lcd ; + char *sp, *sq, *ss ; + + n = vector_end(matchvec) ; + if (n < 2) + return 0 ; + + ss = vector_get_item(matchvec, 0) ; + lcd = strlen(ss) ; + + for (i = 1 ; i < n ; i++) + { + sq = ss ; + ss = vector_get_item(matchvec, i) ; + sp = ss ; + + while ((*sp == *sq) && (*sp != '\0')) + { + ++sp ; + ++sq ; + } ; + + if (lcd > (sp - ss)) + lcd = (sp - ss) ; + } + return lcd; +} + +#endif + +/*============================================================================== + * Command line help support. + * + * + */ + +static bool cmd_post_last(cmd_parsed parsed) ; + + +/*------------------------------------------------------------------------------ + * Look out for the following special cases: + * + * a) before or on '#' or '!' + * + * '#' and '!' are only recognised as the start of a comment at the + * start of a line... So can insert something in front, and what + * currently looks like a comment, suddenly needs to be reconsidered + * as tokens... the first of which happens to start with '!' or '#'. + * + * To save work, and leave open the possibility of trailing comments, + * we treat this case as an "error". + * + * b) before or on '<' + * + * currently, pipe-in must be the first token on the command line. + * + * So if there is anything prior to the '<', treat that as an error. + * If is just spaces before the '<', say that nothing may be placed + * before the '<' (cf nothing may be placed before '!' or '#'). + * + * in these cases no help is available -- return message explaining. + * + * NB: assumes that have already done cmd_token_position(), and therefore that + * if there is a '#' or '<' that cannot be positioned *after* it, or + * indeed that can be positioned after a '>'. + */ +extern const char* +cmd_help_preflight(cmd_parsed parsed) +{ + if ((parsed->tok_total & cmd_tok_comment) != 0) + { + return "cannot have a command on a comment line"; + } ; + + if ((parsed->tok_total & cmd_tok_in_pipe) != 0) + { + return "cannot have a command and an '<' pipe together" ; + } ; + + return NULL ; /* OK ! */ +} ; + + + + +/*------------------------------------------------------------------------------ + * See if current command line can be completed, and if so, how. + * + * NB: must already have done cmd_token_position(). + * + * Must not be called if cmd_token_position() reported a "special". + * + * Returns: CMD_ERR_PARSING -- the parser could not make sense of the line. + * + */ +extern cmd_return_code_t +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. + */ + 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 */ + + /* Expect now to have a cmd_v set with the result of the filtering, + * with 0, 1 or more possible commands in it. + * + * Now establish what the alternatives are at the current token position. + * + * We do this by filtering again, on the current token position, and + * asking the filter to collect all the possible items. Note that if the + * current token position is beyond the last actual command token, then + * will be filtering on the empty eol token -- which we arrange to match + * anything, including eol -- which gives the dummy item_cr of type + * item_eol. + */ + assert(parsed->cti >= parsed->first_command) ; + assert(parsed->cti <= parsed->first_command + parsed->num_command) ; + + cmd_filter(parsed, parsed->cti - parsed->first_command, true) ; + + /* Reduce the list of items available at the current position to + * eliminate duplicates and have the possibilities sorted by type and + * by value. + */ + if (vector_length(parsed->item_v) > 1) + { + cmd_item prev ; + uint ii ; + uint i_keep ; + + vector_sort(parsed->item_v, (vector_sort_cmp*)cmd_cmp_item) ; + + i_keep = 1 ; + + prev = vector_get_item(parsed->item_v, 0) ; + for (ii = 1 ; ii < vector_length(parsed->item_v) ; ++ii) + { + cmd_item item ; + + item = vector_get_item(parsed->item_v, ii) ; + if (cmd_cmp_item(&item, &prev) != 0) + { + vector_set_item(parsed->item_v, i_keep++, item) ; + prev = item ; + } ; + } ; + + vector_set_length(parsed->item_v, i_keep) ; + /* discard what did not keep */ + } ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * How to insert a newly completed keyword ? + * + * There are a number of cases: + * + * 1) the cti is for the token to be completed. + * + * May be positioned one or more spaces in front of the keyword. + * + * Will replace any leading spaces and the keyword by the new keyword. + * + * 2) the cti is for the empty eol token + * + * Will replace any leading spaces to the eol keyword, by the new + * keyword. + * + * 3) the cti is for a '>' token, or any other !cmd_tok_simple. + * + * If cti is not exactly on the token, then insert the keyword exactly + * where it is -- no spaces need to be removed or added. + * + * If the cti is on the token, insert the keyword and one space. + * + * The second issue is where to leave the cursor... if the command is now + * complete, wish to leave the cursor at the end of the newly completed + * keyword. Otherwise: + * + * 1) if the next token is a cmd_tok_simple, move to it. + * + * 2) otherwise if there is already a space after the newly completed + * keyword, move past it. + * + * Otherwise, insert a space. + * + * 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 : 0..2 + * *mov = number of spaces to move afterwards (may be -ve) + */ +extern void +cmd_complete_keyword(cmd_parsed parsed, int* pre, int* rep, int* ins, int* mov) +{ + cmd_token t, nt ; + bool last ; + int gap ; + + /* Is this the last token possible on this command line ? */ + last = cmd_post_last(parsed) ; + + /* Get the token we are operating and the following one (if any). + * + * Calculate gap between end of token to be replaced and start of next, + * if any. + * + * gap == 0 => is at eol or on !cmd_tok_simple. + */ + t = cmd_token_get(parsed->tokens, parsed->cti) ; + if (parsed->cti < parsed->num_tokens) + { + nt = cmd_token_get(parsed->tokens, parsed->cti + 1) ; + gap = nt->tp - (t->tp + els_len_nn(t->ot)) ; + } + else + { + nt = NULL ; + gap = (parsed->rp < 0) ? -parsed->rp : 0 ; + } ; + + /* Now work out what we need to do. */ + + *pre = 0 ; /* preset values */ + *rep = 0 ; + *ins = 0 ; + *mov = 0 ; + + if ((t->type & cmd_tok_simple) != 0) + { + /* Replacing an existing simple token ------------------------------ + * + * Move to start of token and replace it. + */ + *pre = -parsed->rp ; + *rep = parsed->ctl ; + + /* now what do we do after the token ? */ + assert(nt != NULL) ; + + if ((nt->type & cmd_tok_simple) != 0) + { + /* Next token is simple -- step to it */ + assert(!last) ; + + *mov = gap ; + } + else if (nt->type == cmd_tok_eol) + { + /* Next token is eol + * + * gap is the number of spaces there will be after the + * newly replaced token. + */ + if (!last) + { + if (gap == 0) + *ins = 1 ; /* need a trailing space */ + else + *mov = 1 ; /* step over existing space */ + } ; + } + else + { + /* Next token is something special. + * + * gap is the number of spaces there will be after the + * newly replaced token. + */ + if (gap == 0) + { + *ins = last ? 1 : 2 ; + *mov = -1 ; + } + else if (gap == 1) + { + *ins = last ? 0 : 1 ; + } + else + { + *mov = last ? 0 : 1 ; + } + } ; + } + + else if (t->type == cmd_tok_eol) + { + /* Inserting at or before eol -------------------------------------- + * + * Insert exactly where we are -- *pre == 0 + */ + + /* If last, replace any spaces between cursor and end of line, + * otherwise, keep one of them. + */ + *rep = last ? gap : (gap > 0) ? gap - 1 : gap ; + + /* now what do we do after the token ? */ + assert(nt == NULL) ; + + if (!last) + { + if (gap == 0) + *ins = 1 ; /* need space after */ + else + *mov = 1 ; /* step over one */ + } ; + } + else + { + /* Inserting at or before something special ------------------------ + * + * Insert exactly where we are -- *pre == 0 + * replace nothing -- *rep == 0 + */ + if (gap == 0) + { + *ins = last ? 1 : 2 ; + *mov = -1 ; + } + else if (gap == 1) + { + *ins = last ? 0 : 1 ; + } + else + { + *mov = last ? 0 : 1 ; + } + } ; +} ; + +/*------------------------------------------------------------------------------ + * Do we have just one command left, and have we just filtered on the last + * possible command item & token ? + * + * If so, then there is no point filtering any further, and there is nothing + * more that could be added to this command line. + */ +static bool +cmd_post_last(cmd_parsed parsed) +{ + if (vector_length(parsed->cmd_v) == 1) + { + cmd_command cmd ; + cmd = vector_get_item(parsed->cmd_v, 0) ; + + return ((parsed->cti - parsed->first_command + 1) == cmd->nt_max) ; + } ; + + return false ; +} ; + + + + + + + + + + + + + + + + + +#if 0 +static vector +cmd_complete_command_real (vector tokens, int node, int *status) +{ + unsigned int i; + unsigned int ivl ; + unsigned int last_ivl ; + vector cmd_v ; +#define INIT_MATCHVEC_SIZE 10 + vector matchvec; + struct cmd_command *cmd_command; + unsigned int index; + struct cmd_item *cmd_item; + vector descvec; + char *token; + int n ; + + /* Stop immediately if the tokens is empty. */ + if (vector_length (tokens) == 0) + { + *status = CMD_ERR_NO_MATCH; + return NULL; + } + + /* Take (shallow) copy of cmdvec for given node. */ + cmd_v = vector_copy (cmd_node_vector (cmdvec, node)); + + /* First, filter upto, but excluding last token */ + last_ivl = vector_length (tokens) - 1; + + for (ivl = 0; ivl < last_ivl; ivl++) + { + enum match_type match; + int ret; + + /* TODO: does this test make any sense ? */ + if ((token = vector_get_item (tokens, ivl)) == NULL) + continue ; + + /* First try completion match, return best kind of match */ + index = ivl ; + match = cmd_filter_by_completion (token, cmd_v, index) ; + + /* Eliminate all but the selected kind of match */ + ret = is_cmd_ambiguous (token, cmd_v, index, match) ; + + if (ret == 1) + { + /* ret == 1 => either token matches more than one keyword + * or token matches more than one number range + */ + vector_free (cmd_v); + *status = CMD_ERR_AMBIGUOUS; + return NULL; + } +#if 0 + /* For command completion purposes do not appear to care about + * incomplete ipv4 or ipv6 prefixes (missing '/' or digits after). + */ + else if (ret == 2) + { + vector_free (cmd_v); + *status = CMD_ERR_NO_MATCH; + return NULL; + } +#endif + } + + /* Prepare match vector. */ + matchvec = vector_init (INIT_MATCHVEC_SIZE); + + /* Now we got into completion */ + index = last_ivl ; + token = vector_get_item(tokens, last_ivl) ; /* is now the last token */ + + for (i = 0; i < vector_length (cmd_v); i++) + { + unsigned int j; + const char *string; + + if ((cmd_command = vector_get_item (cmd_v, i)) == NULL) + continue ; + + descvec = vector_get_item (cmd_command->items, index); + if (descvec == NULL) + continue ; + + for (j = 0; j < vector_length (descvec); j++) + { + item = vector_get_item (descvec, j) ; + if (item == NULL) + continue ; + + string = cmd_entry_function(token, item->str) ; + if ((string != NULL) && cmd_unique_string(matchvec, string)) + cmd_add_to_strvec (matchvec, string) ; + } ; + } ; + + n = vector_length(matchvec) ; /* number of entries in the matchvec */ + + /* We don't need cmd_v any more. */ + vector_free (cmd_v); + + /* No matched command */ + if (n == 0) + { + vector_free (matchvec); + + /* In case of 'command \t' pattern. Do you need '?' command at + the end of the line. */ + if (*token == '\0') + *status = CMD_COMPLETE_ALREADY; + else + *status = CMD_ERR_NO_MATCH; + return NULL; + } + + /* Only one matched */ + if (n == 1) + { + *status = CMD_COMPLETE_FULL_MATCH; + return matchvec ; + } + + /* Check LCD of matched strings. */ + if (token != NULL) + { + unsigned lcd = cmd_lcd (matchvec) ; + + if (lcd != 0) + { + if (strlen(token) < lcd) + { + char *lcdstr; + + lcdstr = XMALLOC (MTYPE_STRVEC, lcd + 1); + memcpy (lcdstr, vector_get_item(matchvec, 0), lcd) ; + lcdstr[lcd] = '\0'; + + cmd_free_strvec(matchvec) ; /* discard the match vector */ + + matchvec = vector_init (1); + vector_push_item(matchvec, lcdstr) ; + + *status = CMD_COMPLETE_MATCH; + return matchvec ; + } + } + } + + *status = CMD_COMPLETE_LIST_MATCH; + return matchvec ; +} +#endif + +/*------------------------------------------------------------------------------ + * Can the current command be completed ? + */ +extern vector +cmd_complete_command (vector tokens, int node, int *status) +{ +#if 0 + vector ret; + + if ( cmd_try_do_shortcut(node, vector_get_item(tokens, 0) ) ) + { + vector shifted_tokens; + unsigned int index; + + /* We can try it on enable node, cos' the vty is authenticated */ + + shifted_tokens = vector_init (vector_count(tokens)); + /* use memcpy? */ + for (index = 1; index < vector_length (tokens); index++) + { + vector_set_index (shifted_tokens, index-1, + vector_lookup(tokens, index)) ; + } + + ret = cmd_complete_command_real (shifted_tokens, ENABLE_NODE, status); + + vector_free(shifted_tokens); + return ret; + } + + return cmd_complete_command_real (tokens, node, status); +#endif + + *status = CMD_ERR_NO_MATCH; + return NULL; +} ; + +/*============================================================================== + * Initialise command parsing -- done before first command is installed. + * + * Complete the (much used) eol_item. + */ +extern void +cmd_parser_init(void) +{ + dsl_init(word_lumps) ; + cmd_set_str(&eol_item, "<cr>") ; +} ; |