From: Donald Sharp Date: Wed, 21 Sep 2016 01:17:34 +0000 (-0400) Subject: Merge remote-tracking branch 'origin/cmaster-next' into vtysh-grammar X-Git-Tag: frr-3.0-branchpoint~129^2~224 X-Git-Url: https://git.puffer.fish/?a=commitdiff_plain;h=460a768914822df062bc1282296e6bf6a3c6f725;p=matthieu%2Ffrr.git Merge remote-tracking branch 'origin/cmaster-next' into vtysh-grammar --- 460a768914822df062bc1282296e6bf6a3c6f725 diff --cc lib/.gitignore index 8174bda7d8,fe75137bca..e45fec1786 --- a/lib/.gitignore +++ b/lib/.gitignore @@@ -14,9 -14,5 +14,8 @@@ gitversion.h.tm .arch-ids *~ *.loT - memtypes.h route_types.h +command_lex.c +command_parse.c +command_parse.h refix diff --cc lib/Makefile.am index 3dfd09e2e6,dbb80076c5..acaf7e6744 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@@ -10,16 -9,14 +10,16 @@@ libzebra_la_LDFLAGS = -version-info 0:0 libzebra_la_SOURCES = \ network.c pid_output.c getopt.c getopt1.c daemon.c \ - checksum.c vector.c linklist.c vty.c command.c \ + checksum.c vector.c linklist.c vty.c \ + graph.c command_parse.y command_lex.l command_match.c \ + command.c \ - sockunion.c prefix.c thread.c if.c memory.c buffer.c table.c hash.c \ + sockunion.c prefix.c thread.c if.c buffer.c table.c hash.c \ filter.c routemap.c distribute.c stream.c str.c log.c plist.c \ zclient.c sockopt.c smux.c agentx.c snmp.c md5.c if_rmap.c keychain.c privs.c \ - sigevent.c pqueue.c jhash.c memtypes.c workqueue.c nexthop.c json.c \ - ptm_lib.c csv.c bfd.c vrf.c systemd.c ns.c + sigevent.c pqueue.c jhash.c workqueue.c nexthop.c json.c \ + ptm_lib.c csv.c bfd.c vrf.c systemd.c ns.c memory.c memory_vty.c - BUILT_SOURCES = memtypes.h route_types.h gitversion.h command_parse.h -BUILT_SOURCES = route_types.h gitversion.h ++BUILT_SOURCES = route_types.h gitversion.h command_parse.h libzebra_la_DEPENDENCIES = @LIB_REGEX@ diff --cc lib/command.c index b4ef30a300,cfdb91a5b9..b6f97c317e --- a/lib/command.c +++ b/lib/command.c @@@ -34,10 -33,11 +34,13 @@@ Boston, MA 02111-1307, USA. * #include "command.h" #include "workqueue.h" #include "vrf.h" - +#include "command_match.h" +#include "command_parse.h" + DEFINE_MTYPE( LIB, HOST, "Host config") + DEFINE_MTYPE( LIB, STRVEC, "String vector") + DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command desc") + /* Command vector which includes some level of command lists. Normally each daemon maintains each own cmdvec. */ vector cmdvec = NULL; diff --cc lib/command.h index 5e1b48c68f,9ee4f2db3d..ddb61c686f --- a/lib/command.h +++ b/lib/command.h @@@ -26,7 -26,12 +26,13 @@@ #include "vector.h" #include "vty.h" #include "lib/route_types.h" +#include "graph.h" + #include "memory.h" + + DECLARE_MTYPE(HOST) + + /* for test-commands.c */ + DECLARE_MTYPE(STRVEC) /* Host configuration variable */ struct host diff --cc lib/command_match.c index d7c2222ce6,0000000000..dd68ca477e mode 100644,000000..100644 --- a/lib/command_match.c +++ b/lib/command_match.c @@@ -1,827 -1,0 +1,830 @@@ +/* + * Input matching routines for CLI backend. + * + * -- + * Copyright (C) 2016 Cumulus Networks, Inc. + * + * 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 ++ +#include "command_match.h" +#include "command_parse.h" +#include "memory.h" + ++DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command Tokens") ++ +/* matcher helper prototypes */ +static int +add_nexthops (struct list *, struct graph_node *); + +static struct list * +command_match_r (struct graph_node *, vector, unsigned int); + +static int +score_precedence (enum cmd_token_type); + +static enum match_type +min_match_level (enum cmd_token_type); + +static void +del_arglist (struct list *); + +static struct cmd_token * +disambiguate_tokens (struct cmd_token *, struct cmd_token *, char *); + +static struct list * +disambiguate (struct list *, struct list *, vector, unsigned int); + +int +compare_completions (const void *, const void *); + +/* token matcher prototypes */ +static enum match_type +match_token (struct cmd_token *, char *); + +static enum match_type +match_ipv4 (const char *); + +static enum match_type +match_ipv4_prefix (const char *); + +static enum match_type +match_ipv6 (const char *); + +static enum match_type +match_ipv6_prefix (const char *); + +static enum match_type +match_range (struct cmd_token *, const char *); + +static enum match_type +match_word (struct cmd_token *, const char *); + +static enum match_type +match_number (struct cmd_token *, const char *); + +static enum match_type +match_variable (struct cmd_token *, const char *); + +/* matching functions */ +static enum matcher_rv matcher_rv; + +enum matcher_rv +command_match (struct graph *cmdgraph, + vector vline, + struct list **argv, + struct cmd_element **el) +{ + matcher_rv = MATCHER_NO_MATCH; + + // prepend a dummy token to match that pesky start node + vector vvline = vector_init (vline->alloced + 1); + vector_set_index (vvline, 0, (void *) XSTRDUP (MTYPE_TMP, "dummy")); + memcpy (vvline->index + 1, vline->index, sizeof (void *) * vline->alloced); + vvline->active = vline->active + 1; + + struct graph_node *start = vector_slot (cmdgraph->nodes, 0); + if ((*argv = command_match_r (start, vvline, 0))) // successful match + { + struct listnode *head = listhead (*argv); + struct listnode *tail = listtail (*argv); + + // delete dummy start node + del_cmd_token ((struct cmd_token *) head->data); + list_delete_node (*argv, head); + + // get cmd_element out of list tail + *el = listgetdata (tail); + list_delete_node (*argv, tail); + + // now argv is an ordered list of cmd_token matching the user + // input, with each cmd_token->arg holding the corresponding input + assert (*el); + } + + // free the leader token we alloc'd + XFREE (MTYPE_TMP, vector_slot (vvline, 0)); + // free vector + vector_free (vvline); + + return matcher_rv; +} + +/** + * Builds an argument list given a DFA and a matching input line. + * + * First the function determines if the node it is passed matches the first + * token of input. If it does not, it returns NULL (MATCHER_NO_MATCH). If it + * does match, then it saves the input token as the head of an argument list. + * + * The next step is to see if there is further input in the input line. If + * there is not, the current node's children are searched to see if any of them + * are leaves (type END_TKN). If this is the case, then the bottom of the + * recursion stack has been reached, the leaf is pushed onto the argument list, + * the current node is pushed, and the resulting argument list is + * returned (MATCHER_OK). If it is not the case, NULL is returned, indicating + * that there is no match for the input along this path (MATCHER_INCOMPLETE). + * + * If there is further input, then the function recurses on each of the current + * node's children, passing them the input line minus the token that was just + * matched. For each child, the return value of the recursive call is + * inspected. If it is null, then there is no match for the input along the + * subgraph headed by that child. If it is not null, then there is at least one + * input match in that subgraph (more on this in a moment). + * + * If a recursive call on a child returns a non-null value, then it has matched + * the input given it on the subgraph that starts with that child. However, due + * to the flexibility of the grammar, it is sometimes the case that two or more + * child graphs match the same input (two or more of the recursive calls have + * non-NULL return values). This is not a valid state, since only one true + * match is possible. In order to resolve this conflict, the function keeps a + * reference to the child node that most specifically matches the input. This + * is done by assigning each node type a precedence. If a child is found to + * match the remaining input, then the precedence values of the current + * best-matching child and this new match are compared. The node with higher + * precedence is kept, and the other match is discarded. Due to the recursive + * nature of this function, it is only necessary to compare the precedence of + * immediate children, since all subsequent children will already have been + * disambiguated in this way. + * + * In the event that two children are found to match with the same precedence, + * then the input is ambiguous for the passed cmd_element and NULL is returned. + * + * The ultimate return value is an ordered linked list of nodes that comprise + * the best match for the command, each with their `arg` fields pointing to the + * matching token string. + * + * @param[in] start the start node. + * @param[in] vline the vectorized input line. + * @param[in] n the index of the first input token. + */ +static struct list * +command_match_r (struct graph_node *start, vector vline, unsigned int n) +{ + assert (n < vector_active (vline)); + + // get the minimum match level that can count as a full match + struct cmd_token *token = start->data; + enum match_type minmatch = min_match_level (token->type); + + // get the current operating input token + char *input_token = vector_slot (vline, n); + + // if we don't match this node, die + if (match_token (token, input_token) < minmatch) + return NULL; + + // pointers for iterating linklist + struct listnode *ln; + struct graph_node *gn; + + // get all possible nexthops + struct list *next = list_new(); + add_nexthops (next, start); + + // determine the best match + int ambiguous = 0; + struct list *currbest = NULL; + for (ALL_LIST_ELEMENTS_RO (next,ln,gn)) + { + // if we've matched all input we're looking for END_TKN + if (n+1 == vector_active (vline)) + { + struct cmd_token *tok = gn->data; + if (tok->type == END_TKN) + { + currbest = list_new(); + // node should have one child node with the element + struct graph_node *leaf = vector_slot (gn->to, 0); + // last node in the list will hold the cmd_element; + // this is important because list_delete() expects + // that all nodes have the same data type, so when + // deleting this list the last node must be + // manually deleted + struct cmd_element *el = leaf->data; + listnode_add (currbest, copy_cmd_element (el)); + currbest->del = (void (*)(void *)) &del_cmd_token; + break; + } + else continue; + } + + // else recurse on candidate child node + struct list *result = command_match_r (gn, vline, n+1); + + // save the best match + if (result && currbest) + { + // pick the best of two matches + struct list *newbest = disambiguate (currbest, result, vline, n+1); + // set ambiguity flag + ambiguous = !newbest || (ambiguous && newbest == currbest); + // delete the unnecessary result + struct list *todelete = ((newbest && newbest == result) ? currbest : result); + del_arglist (todelete); + + currbest = newbest ? newbest : currbest; + } + else if (result) + currbest = result; + } + + if (currbest) + { + if (ambiguous) + { + del_arglist (currbest); + currbest = NULL; + matcher_rv = MATCHER_AMBIGUOUS; + } + else + { + // copy token, set arg and prepend to currbest + struct cmd_token *token = start->data; + struct cmd_token *copy = copy_cmd_token (token); + copy->arg = XSTRDUP (MTYPE_CMD_TOKENS, input_token); + list_add_node_prev (currbest, currbest->head, copy); + matcher_rv = MATCHER_OK; + } + } + else if (n+1 == vector_active (vline) && matcher_rv == MATCHER_NO_MATCH) + matcher_rv = MATCHER_INCOMPLETE; + + // cleanup + list_delete (next); + + return currbest; +} + +enum matcher_rv +command_complete (struct graph *graph, + vector vline, + struct list **completions) +{ + // pointer to next input token to match + char *input_token; + + struct list *current = list_new(), // current nodes to match input token against + *next = list_new(); // possible next hops after current input token + + // pointers used for iterating lists + struct graph_node *gn; + struct listnode *node; + + // add all children of start node to list + struct graph_node *start = vector_slot (graph->nodes, 0); + add_nexthops (next, start); + + unsigned int idx; + for (idx = 0; idx < vector_active (vline) && next->count > 0; idx++) + { + list_delete (current); + current = next; + next = list_new(); + + input_token = vector_slot (vline, idx); + + for (ALL_LIST_ELEMENTS_RO (current,node,gn)) + { + struct cmd_token *token = gn->data; + switch (match_token (token, input_token)) + { + case partly_match: + if (idx == vector_active (vline) - 1) + { + listnode_add (next, gn); + break; + } + case exact_match: + add_nexthops (next, gn); + break; + default: + break; + } + } + } + + /* Variable summary + * ----------------------------------------------------------------- + * token = last input token processed + * idx = index in `command` of last token processed + * current = set of all transitions from the previous input token + * next = set of all nodes reachable from all nodes in `matched` + */ + + matcher_rv = + idx == vector_active(vline) && next->count ? + MATCHER_OK : + MATCHER_NO_MATCH; + + // extract cmd_token into list + *completions = list_new (); + for (ALL_LIST_ELEMENTS_RO (next,node,gn)) + listnode_add (*completions, gn->data); + + list_delete (current); + list_delete (next); + + return matcher_rv; +} + +/** + * Adds all children that are reachable by one parser hop to the given list. + * NUL_TKN, SELECTOR_TKN, and OPTION_TKN nodes are treated as transparent. + * + * @param[in] list to add the nexthops to + * @param[in] node to start calculating nexthops from + * @return the number of children added to the list + */ +static int +add_nexthops (struct list *list, struct graph_node *node) +{ + int added = 0; + struct graph_node *child; + for (unsigned int i = 0; i < vector_active (node->to); i++) + { + child = vector_slot (node->to, i); + struct cmd_token *token = child->data; + switch (token->type) + { + case OPTION_TKN: + case SELECTOR_TKN: + case NUL_TKN: + added += add_nexthops (list, child); + break; + default: + listnode_add (list, child); + added++; + } + } + + return added; +} + +/** + * Determines the node types for which a partial match may count as a full + * match. Enables command abbrevations. + * + * @param[in] type node type + * @return minimum match level needed to for a token to fully match + */ +static enum match_type +min_match_level (enum cmd_token_type type) +{ + switch (type) + { + // anything matches a start node, for the sake of recursion + case START_TKN: + return no_match; + // allowing words to partly match enables command abbreviation + case WORD_TKN: + return partly_match; + default: + return exact_match; + } +} + +/** + * Assigns precedence scores to node types. + * + * @param[in] type node type to score + * @return precedence score + */ +static int +score_precedence (enum cmd_token_type type) +{ + switch (type) + { + // some of these are mutually exclusive, so they share + // the same precedence value + case IPV4_TKN: + case IPV4_PREFIX_TKN: + case IPV6_TKN: + case IPV6_PREFIX_TKN: + case NUMBER_TKN: + return 1; + case RANGE_TKN: + return 2; + case WORD_TKN: + return 3; + case VARIABLE_TKN: + return 4; + default: + return 10; + } +} + +/** + * Picks the better of two possible matches for a token. + * + * @param[in] first candidate node matching token + * @param[in] second candidate node matching token + * @param[in] token the token being matched + * @return the best-matching node, or NULL if the two are entirely ambiguous + */ +static struct cmd_token * +disambiguate_tokens (struct cmd_token *first, + struct cmd_token *second, + char *input_token) +{ + // if the types are different, simply go off of type precedence + if (first->type != second->type) + { + int firstprec = score_precedence (first->type); + int secndprec = score_precedence (second->type); + if (firstprec != secndprec) + return firstprec < secndprec ? first : second; + else + return NULL; + } + + // if they're the same, return the more exact match + enum match_type fmtype = match_token (first, input_token); + enum match_type smtype = match_token (second, input_token); + if (fmtype != smtype) + return fmtype > smtype ? first : second; + + return NULL; +} + +/** + * Picks the better of two possible matches for an input line. + * + * @param[in] first candidate list of cmd_token matching vline + * @param[in] second candidate list of cmd_token matching vline + * @param[in] vline the input line being matched + * @param[in] n index into vline to start comparing at + * @return the best-matching list, or NULL if the two are entirely ambiguous + */ +static struct list * +disambiguate (struct list *first, + struct list *second, + vector vline, + unsigned int n) +{ + // doesn't make sense for these to be inequal length + assert (first->count == second->count); + assert (first->count == vector_active (vline) - n+1); + + struct listnode *fnode = listhead (first), + *snode = listhead (second); + struct cmd_token *ftok = listgetdata (fnode), + *stok = listgetdata (snode), + *best = NULL; + + // compare each token, if one matches better use that one + for (unsigned int i = n; i < vector_active (vline); i++) + { + char *token = vector_slot(vline, i); + if ((best = disambiguate_tokens (ftok, stok, token))) + return best == ftok ? first : second; + fnode = listnextnode (fnode); + snode = listnextnode (snode); + ftok = listgetdata (fnode); + stok = listgetdata (snode); + } + + return NULL; +} + +/* + * Deletion function for arglist. + * + * Since list->del for arglists expects all listnode->data to hold cmd_token, + * but arglists have cmd_element as the data for the tail, this function + * manually deletes the tail before deleting the rest of the list as usual. + * + * @param list the arglist to delete + */ +static void +del_arglist (struct list *list) +{ + // manually delete last node + struct listnode *tail = listtail (list); + del_cmd_element (tail->data); + tail->data = NULL; + list_delete_node (list, tail); + + // delete the rest of the list as usual + list_delete (list); +} + +/*---------- token level matching functions ----------*/ + +static enum match_type +match_token (struct cmd_token *token, char *input_token) +{ + switch (token->type) { + case WORD_TKN: + return match_word (token, input_token); + case IPV4_TKN: + return match_ipv4 (input_token); + case IPV4_PREFIX_TKN: + return match_ipv4_prefix (input_token); + case IPV6_TKN: + return match_ipv6 (input_token); + case IPV6_PREFIX_TKN: + return match_ipv6_prefix (input_token); + case RANGE_TKN: + return match_range (token, input_token); + case NUMBER_TKN: + return match_number (token, input_token); + case VARIABLE_TKN: + return match_variable (token, input_token); + case END_TKN: + default: + return no_match; + } +} + +#define IPV4_ADDR_STR "0123456789." +#define IPV4_PREFIX_STR "0123456789./" + +static enum match_type +match_ipv4 (const char *str) +{ + const char *sp; + int dots = 0, nums = 0; + char buf[4]; + + if (str == NULL) + return partly_match; + + for (;;) + { + memset (buf, 0, sizeof (buf)); + sp = str; + while (*str != '\0') + { + if (*str == '.') + { + if (dots >= 3) + return no_match; + + if (*(str + 1) == '.') + return no_match; + + if (*(str + 1) == '\0') + return partly_match; + + dots++; + break; + } + if (!isdigit ((int) *str)) + return no_match; + + str++; + } + + if (str - sp > 3) + return no_match; + + strncpy (buf, sp, str - sp); + if (atoi (buf) > 255) + return no_match; + + nums++; + + if (*str == '\0') + break; + + str++; + } + + if (nums < 4) + return partly_match; + + return exact_match; +} + +static enum match_type +match_ipv4_prefix (const char *str) +{ + const char *sp; + int dots = 0; + char buf[4]; + + if (str == NULL) + return partly_match; + + for (;;) + { + memset (buf, 0, sizeof (buf)); + sp = str; + while (*str != '\0' && *str != '/') + { + if (*str == '.') + { + if (dots == 3) + return no_match; + + if (*(str + 1) == '.' || *(str + 1) == '/') + return no_match; + + if (*(str + 1) == '\0') + return partly_match; + + dots++; + break; + } + + if (!isdigit ((int) *str)) + return no_match; + + str++; + } + + if (str - sp > 3) + return no_match; + + strncpy (buf, sp, str - sp); + if (atoi (buf) > 255) + return no_match; + + if (dots == 3) + { + if (*str == '/') + { + if (*(str + 1) == '\0') + return partly_match; + + str++; + break; + } + else if (*str == '\0') + return partly_match; + } + + if (*str == '\0') + return partly_match; + + str++; + } + + sp = str; + while (*str != '\0') + { + if (!isdigit ((int) *str)) + return no_match; + + str++; + } + + if (atoi (sp) > 32) + return no_match; + + return exact_match; +} + +#ifdef HAVE_IPV6 +#define IPV6_ADDR_STR "0123456789abcdefABCDEF:." +#define IPV6_PREFIX_STR "0123456789abcdefABCDEF:./" + +static enum match_type +match_ipv6 (const char *str) +{ + struct sockaddr_in6 sin6_dummy; + int ret; + + if (str == NULL) + return partly_match; + + if (strspn (str, IPV6_ADDR_STR) != strlen (str)) + return no_match; + + ret = inet_pton(AF_INET6, str, &sin6_dummy.sin6_addr); + + if (ret == 1) + return exact_match; + + return no_match; +} + +static enum match_type +match_ipv6_prefix (const char *str) +{ + struct sockaddr_in6 sin6_dummy; + const char *delim = "/\0"; + char *tofree, *dupe, *prefix, *mask, *endptr; + int nmask = -1; + + if (str == NULL) + return partly_match; + + if (strspn (str, IPV6_PREFIX_STR) != strlen (str)) + return no_match; + + /* tokenize to prefix + mask */ + tofree = dupe = XSTRDUP (MTYPE_TMP, str); + prefix = strsep (&dupe, delim); + mask = dupe; + + /* validate prefix */ + if (inet_pton (AF_INET6, prefix, &sin6_dummy.sin6_addr) != 1) + { + XFREE (MTYPE_TMP, tofree); + return no_match; + } + + /* validate mask */ + if (!mask) + { + XFREE (MTYPE_TMP, tofree); + return partly_match; + } + + nmask = strtoimax (mask, &endptr, 10); + if (*endptr != '\0' || nmask < 0 || nmask > 128) + { + XFREE (MTYPE_TMP, tofree); + return no_match; + } + + XFREE (MTYPE_TMP, tofree); + return exact_match; +} +#endif + +static enum match_type +match_range (struct cmd_token *token, const char *str) +{ + assert (token->type == RANGE_TKN); + + char *endptr = NULL; + long long val; + + if (str == NULL) + return 1; + + val = strtoll (str, &endptr, 10); + if (*endptr != '\0') + return 0; + + if (val < token->min || val > token->max) + return no_match; + else + return exact_match; +} + +static enum match_type +match_word (struct cmd_token *token, const char *word) +{ + assert (token->type == WORD_TKN); + + // if the passed token is null or 0 length, partly match + if (!word || !strlen(word)) + return partly_match; + + // if the passed token is strictly a prefix of the full word, partly match + if (strlen (word) < strlen (token->text)) + return !strncmp (token->text, word, strlen (word)) ? + partly_match : + no_match; + + // if they are the same length and exactly equal, exact match + else if (strlen (word) == strlen (token->text)) + return !strncmp (token->text, word, strlen (word)) ? exact_match : no_match; + + return no_match; +} + +static enum match_type +match_number (struct cmd_token *token, const char *word) +{ + assert (token->type == NUMBER_TKN); + + if (!strcmp ("\0", word)) return no_match; + char *endptr; + long long num = strtoll (word, &endptr, 10); + if (endptr != '\0') return no_match; + return num == token->value ? exact_match : no_match; +} + +#define VARIABLE_ALPHABET \ +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890:" + +static enum match_type +match_variable (struct cmd_token *token, const char *word) +{ + assert (token->type == VARIABLE_TKN); + + return strlen (word) == strspn(word, VARIABLE_ALPHABET) ? + exact_match : no_match; +} diff --cc lib/command_parse.y index 089b506b03,0000000000..6a5d75aabf mode 100644,000000..100644 --- a/lib/command_parse.y +++ b/lib/command_parse.y @@@ -1,547 -1,0 +1,548 @@@ +/* + * Command format string parser for CLI backend. + * + * -- + * Copyright (C) 2015 Cumulus Networks, Inc. + * + * 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. + */ + +%{ +// compile with debugging facilities +#define YYDEBUG 1 +%} + +/* names for generated header and parser files */ +%defines "command_parse.h" +%output "command_parse.c" + +/* required external units */ +%code requires { ++ #include "stdlib.h" ++ #include "string.h" + #include "command.h" + #include "graph.h" - #include "memory.h" + + extern int + yylex (void); + + extern void + set_lexer_string (const char *); + + extern void + cleanup_lexer (void); +} + +/* functionality this unit exports */ +%code provides { + void + command_parse_format (struct graph *, struct cmd_element *); + + /* maximum length of a number, lexer will not match anything longer */ + #define DECIMAL_STRLEN_MAX 20 +} + +/* valid semantic types for tokens and rules */ +%union { + long long number; + char *string; + struct graph_node *node; + struct subgraph *subgraph; +} + +/* union types for lexed tokens */ +%token WORD +%token IPV4 +%token IPV4_PREFIX +%token IPV6 +%token IPV6_PREFIX +%token VARIABLE +%token RANGE + +/* union types for parsed rules */ +%type start +%type sentence_root +%type literal_token +%type placeholder_token +%type simple_token +%type option +%type option_token +%type option_token_seq +%type selector +%type selector_token +%type selector_token_seq +%type selector_seq_seq +%type compound_token + +%code { + /* bison declarations */ + void + yyerror (struct graph *, struct cmd_element *el, char const *msg); + + /* subgraph semantic value */ + struct subgraph { + struct graph_node *start, *end; + }; + + struct graph_node *currnode, *startnode; + + /* pointers to copy of command docstring */ + char *docstr_start, *docstr; + + /* helper functions for parser */ + static char * + doc_next (void); + + static struct graph_node * + node_adjacent (struct graph_node *, struct graph_node *); + + static struct graph_node * + add_edge_dedup (struct graph_node *, struct graph_node *); + + static int + cmp_token (struct cmd_token *, struct cmd_token *); + + static struct graph_node * + new_token_node (struct graph *, + enum cmd_token_type type, + char *text, char *doc); + + static void + terminate_graph (struct graph *, + struct graph_node *, + struct cmd_element *); + + static void + cleanup (void); +} + +/* yyparse parameters */ +%parse-param { struct graph *graph } +%parse-param { struct cmd_element *element } + +/* called automatically before yyparse */ +%initial-action { + /* clear state pointers */ + currnode = startnode = NULL; + + startnode = vector_slot (graph->nodes, 0); + + /* set string to parse */ + set_lexer_string (element->string); + + /* copy docstring and keep a pointer to the copy */ - docstr = element->doc ? XSTRDUP(MTYPE_TMP, element->doc) : NULL; ++ docstr = element->doc ? strdup(element->doc) : NULL; + docstr_start = docstr; +} + +%% + +start: + sentence_root cmd_token_seq +{ + // tack on the command element + terminate_graph (graph, currnode, element); +} +| sentence_root cmd_token_seq placeholder_token '.' '.' '.' +{ + if ((currnode = add_edge_dedup (currnode, $3)) != $3) + graph_delete_node (graph, $3); + + // adding a node as a child of itself accepts any number + // of the same token, which is what we want for varags + add_edge_dedup (currnode, currnode); + + // tack on the command element + terminate_graph (graph, currnode, element); +} +; + +sentence_root: WORD +{ + struct graph_node *root = - new_token_node (graph, WORD_TKN, XSTRDUP (MTYPE_CMD_TOKENS, $1), doc_next()); ++ new_token_node (graph, WORD_TKN, strdup ($1), doc_next()); + + if ((currnode = add_edge_dedup (startnode, root)) != root) + graph_delete_node (graph, root); + + free ($1); + $$ = currnode; +} +; + +cmd_token_seq: + %empty +| cmd_token_seq cmd_token +; + +cmd_token: + simple_token +{ + if ((currnode = add_edge_dedup (currnode, $1)) != $1) + graph_delete_node (graph, $1); +} +| compound_token +{ + graph_add_edge (currnode, $1->start); + currnode = $1->end; - XFREE (MTYPE_TMP, $1); ++ free ($1); +} +; + +simple_token: + literal_token +| placeholder_token +; + +compound_token: + selector +| option +; + +literal_token: WORD +{ - $$ = new_token_node (graph, WORD_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next()); ++ $$ = new_token_node (graph, WORD_TKN, strdup($1), doc_next()); + free ($1); +} +; + +placeholder_token: + IPV4 +{ - $$ = new_token_node (graph, IPV4_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next()); ++ $$ = new_token_node (graph, IPV4_TKN, strdup($1), doc_next()); + free ($1); +} +| IPV4_PREFIX +{ - $$ = new_token_node (graph, IPV4_PREFIX_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next()); ++ $$ = new_token_node (graph, IPV4_PREFIX_TKN, strdup($1), doc_next()); + free ($1); +} +| IPV6 +{ - $$ = new_token_node (graph, IPV6_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next()); ++ $$ = new_token_node (graph, IPV6_TKN, strdup($1), doc_next()); + free ($1); +} +| IPV6_PREFIX +{ - $$ = new_token_node (graph, IPV6_PREFIX_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next()); ++ $$ = new_token_node (graph, IPV6_PREFIX_TKN, strdup($1), doc_next()); + free ($1); +} +| VARIABLE +{ - $$ = new_token_node (graph, VARIABLE_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next()); ++ $$ = new_token_node (graph, VARIABLE_TKN, strdup($1), doc_next()); + free ($1); +} +| RANGE +{ - $$ = new_token_node (graph, RANGE_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next()); ++ $$ = new_token_node (graph, RANGE_TKN, strdup($1), doc_next()); + struct cmd_token *token = $$->data; + + // get the numbers out + yylval.string++; + token->min = strtoll (yylval.string, &yylval.string, 10); + strsep (&yylval.string, "-"); + token->max = strtoll (yylval.string, &yylval.string, 10); + + // validate range + if (token->min > token->max) yyerror (graph, element, "Invalid range."); + + free ($1); +} + +/* productions */ +selector: '<' selector_seq_seq '>' +{ - $$ = XMALLOC (MTYPE_TMP, sizeof (struct subgraph)); ++ $$ = malloc (sizeof (struct subgraph)); + $$->start = new_token_node (graph, SELECTOR_TKN, NULL, NULL); + $$->end = new_token_node (graph, NUL_TKN, NULL, NULL); + for (unsigned int i = 0; i < vector_active ($2->start->to); i++) + { + struct graph_node *sn = vector_slot ($2->start->to, i), + *en = vector_slot ($2->end->from, i); + graph_add_edge ($$->start, sn); + graph_add_edge (en, $$->end); + } + graph_delete_node (graph, $2->start); + graph_delete_node (graph, $2->end); - XFREE (MTYPE_TMP, $2); ++ free ($2); +}; + +selector_seq_seq: + selector_seq_seq '|' selector_token_seq +{ - $$ = XMALLOC (MTYPE_TMP, sizeof (struct subgraph)); ++ $$ = malloc (sizeof (struct subgraph)); + $$->start = graph_new_node (graph, NULL, NULL); + $$->end = graph_new_node (graph, NULL, NULL); + + // link in last sequence + graph_add_edge ($$->start, $3->start); + graph_add_edge ($3->end, $$->end); - XFREE (MTYPE_TMP, $3); ++ free ($3); + + for (unsigned int i = 0; i < vector_active ($1->start->to); i++) + { + struct graph_node *sn = vector_slot ($1->start->to, i), + *en = vector_slot ($1->end->from, i); + graph_add_edge ($$->start, sn); + graph_add_edge (en, $$->end); + } + graph_delete_node (graph, $1->start); + graph_delete_node (graph, $1->end); - XFREE (MTYPE_TMP, $1); - XFREE (MTYPE_TMP, $3); ++ free ($1); ++ free ($3); +} +| selector_token_seq '|' selector_token_seq +{ - $$ = XMALLOC (MTYPE_TMP, sizeof (struct subgraph)); ++ $$ = malloc (sizeof (struct subgraph)); + $$->start = graph_new_node (graph, NULL, NULL); + $$->end = graph_new_node (graph, NULL, NULL); + graph_add_edge ($$->start, $1->start); + graph_add_edge ($1->end, $$->end); + graph_add_edge ($$->start, $3->start); + graph_add_edge ($3->end, $$->end); - XFREE (MTYPE_TMP, $1); - XFREE (MTYPE_TMP, $3); ++ free ($1); ++ free ($3); +} +; + +selector_token_seq: + simple_token +{ - $$ = XMALLOC (MTYPE_TMP, sizeof (struct subgraph)); ++ $$ = malloc (sizeof (struct subgraph)); + $$->start = $$->end = $1; +} +| selector_token_seq selector_token +{ - $$ = XMALLOC (MTYPE_TMP, sizeof (struct subgraph)); ++ $$ = malloc (sizeof (struct subgraph)); + graph_add_edge ($1->end, $2->start); + $$->start = $1->start; + $$->end = $2->end; - XFREE (MTYPE_TMP, $1); - XFREE (MTYPE_TMP, $2); ++ free ($1); ++ free ($2); +} +; + +selector_token: + simple_token +{ - $$ = XMALLOC (MTYPE_TMP, sizeof (struct subgraph)); ++ $$ = malloc (sizeof (struct subgraph)); + $$->start = $$->end = $1; +} +| option +; + +/* [option] productions */ +option: '[' option_token_seq ']' +{ + // make a new option - $$ = XMALLOC (MTYPE_TMP, sizeof (struct subgraph)); ++ $$ = malloc (sizeof (struct subgraph)); + $$->start = new_token_node (graph, OPTION_TKN, NULL, NULL); + $$->end = new_token_node (graph, NUL_TKN, NULL, NULL); + // add a path through the sequence to the end + graph_add_edge ($$->start, $2->start); + graph_add_edge ($2->end, $$->end); + // add a path directly from the start to the end + graph_add_edge ($$->start, $$->end); - XFREE (MTYPE_TMP, $2); ++ free ($2); +} +; + +option_token_seq: + option_token +| option_token_seq option_token +{ - $$ = XMALLOC (MTYPE_TMP, sizeof (struct subgraph)); ++ $$ = malloc (sizeof (struct subgraph)); + graph_add_edge ($1->end, $2->start); + $$->start = $1->start; + $$->end = $2->end; - XFREE (MTYPE_TMP, $1); ++ free ($1); +} +; + +option_token: + simple_token +{ - $$ = XMALLOC (MTYPE_TMP, sizeof (struct subgraph)); ++ $$ = malloc (sizeof (struct subgraph)); + $$->start = $$->end = $1; +} +| compound_token +; + +%% + +void +command_parse_format (struct graph *graph, struct cmd_element *cmd) +{ + // set to 1 to enable parser traces + yydebug = 0; + + // parse command into DFA + yyparse (graph, cmd); + + // cleanup + cleanup (); +} + +/* parser helper functions */ + +void +yyerror (struct graph *graph, struct cmd_element *el, char const *msg) +{ + zlog_err ("%s: FATAL parse error: %s", __func__, msg); + zlog_err ("while parsing this command definition: \n\t%s\n", el->string); + exit(EXIT_FAILURE); +} + +static void +cleanup() +{ + /* free resources */ + free (docstr_start); + + /* cleanup lexer */ + cleanup_lexer (); + + /* clear state pointers */ + currnode = NULL; + docstr_start = docstr = NULL; +} + +static void +terminate_graph (struct graph *graph, struct graph_node *finalnode, struct cmd_element *element) +{ + // end of graph should look like this + // * -> finalnode -> END_TKN -> cmd_element + struct graph_node *end_token_node = + new_token_node (graph, + END_TKN, - XSTRDUP (MTYPE_CMD_TOKENS, CMD_CR_TEXT), - XSTRDUP (MTYPE_CMD_TOKENS, "")); ++ strdup (CMD_CR_TEXT), ++ strdup ("")); + struct graph_node *end_element_node = + graph_new_node (graph, element, (void (*)(void *)) &del_cmd_element); + + if (node_adjacent (finalnode, end_token_node)) + yyerror (graph, element, "Duplicate command."); + + graph_add_edge (finalnode, end_token_node); + graph_add_edge (end_token_node, end_element_node); +} + +static char * +doc_next() +{ + char *piece = NULL; + if (!docstr || !(piece = strsep (&docstr, "\n"))) - return XSTRDUP (MTYPE_CMD_TOKENS, ""); - return XSTRDUP (MTYPE_CMD_TOKENS, piece); ++ return strdup (""); ++ return strdup (piece); +} + +static struct graph_node * +new_token_node (struct graph *graph, enum cmd_token_type type, char *text, char *doc) +{ + struct cmd_token *token = new_cmd_token (type, text, doc); + return graph_new_node (graph, token, (void (*)(void *)) &del_cmd_token); +} + +/** + * Determines if there is an out edge from the first node to the second + */ +static struct graph_node * +node_adjacent (struct graph_node *first, struct graph_node *second) +{ + struct graph_node *adj; + for (unsigned int i = 0; i < vector_active (first->to); i++) + { + adj = vector_slot (first->to, i); + struct cmd_token *ftok = adj->data, + *stok = second->data; + if (cmp_token (ftok, stok)) + return adj; + } + return NULL; +} + +/** + * Creates an edge betwen two nodes, unless there is already an edge to an + * equivalent node. + * + * The first node's out edges are searched to see if any of them point to a + * node that is equivalent to the second node. If such a node exists, it is + * returned. Otherwise an edge is created from the first node to the second. + * + * @param from start node for edge + * @param to end node for edge + * @return the node which the new edge points to + */ +static struct graph_node * +add_edge_dedup (struct graph_node *from, struct graph_node *to) +{ + struct graph_node *existing = node_adjacent (from, to); + return existing ? existing : graph_add_edge (from, to); +} + +/** + * Compares two cmd_token's for equality, + * + * As such, this function is the working definition of token equality + * for parsing purposes and determines overall graph structure. + */ +static int +cmp_token (struct cmd_token *first, struct cmd_token *second) +{ + // compare types + if (first->type != second->type) return 0; + + switch (first->type) { + case WORD_TKN: + case VARIABLE_TKN: + if (first->text && second->text) + { + if (strcmp (first->text, second->text)) + return 0; + } + else if (first->text != second->text) return 0; + break; + case RANGE_TKN: + if (first->min != second->min || first->max != second->max) + return 0; + break; + case NUMBER_TKN: + if (first->value != second->value) return 0; + break; + + /* selectors and options should be equal if their subgraphs are equal, + * but the graph isomorphism problem is not known to be solvable in + * polynomial time so we consider selectors and options inequal in all + * cases; ultimately this forks the graph, but the matcher can handle + * this regardless + */ + case SELECTOR_TKN: + case OPTION_TKN: + return 0; + + /* end nodes are always considered equal, since each node may only + * have one END_TKN child at a time + */ + case START_TKN: + case END_TKN: + case NUL_TKN: + default: + break; + } + return 1; +} diff --cc lib/graph.c index 7c59fe95b5,0000000000..891ecc33c0 mode 100644,000000..100644 --- a/lib/graph.c +++ b/lib/graph.c @@@ -1,136 -1,0 +1,138 @@@ +/* + * Graph data structure. + * + * -- + * Copyright (C) 2016 Cumulus Networks, Inc. + * + * 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 +#include "graph.h" +#include "memory.h" + ++DEFINE_MTYPE_STATIC(LIB, GRAPH, "Graph") ++DEFINE_MTYPE_STATIC(LIB, GRAPH_NODE, "Graph Node") +struct graph * +graph_new () +{ + struct graph *graph = XCALLOC (MTYPE_GRAPH, sizeof(struct graph)); + graph->nodes = vector_init (VECTOR_MIN_SIZE); + + return graph; +} + +struct graph_node * +graph_new_node (struct graph *graph, void *data, void (*del) (void*)) +{ + struct graph_node *node = + XCALLOC(MTYPE_GRAPH_NODE, sizeof(struct graph_node)); + + node->from = vector_init (VECTOR_MIN_SIZE); + node->to = vector_init (VECTOR_MIN_SIZE); + node->data = data; + node->del = del; + + vector_set (graph->nodes, node); + + return node; +} + +static void +vector_remove (vector v, unsigned int ix) +{ + vector_unset (v, ix); + if (ix == vector_active (v)) return; + for (; ix < vector_active (v) - 1; ix++) + v->index[ix] = v->index[ix+1]; + v->active--; +} + +void +graph_delete_node (struct graph *graph, struct graph_node *node) +{ + if (!node) return; + + // an adjacent node + struct graph_node *adj; + + // remove all edges from other nodes to us + vector edges = vector_copy (node->from); + for (unsigned int i = 0; i < vector_active (edges); i++) + { + adj = vector_slot (edges, i); + graph_remove_edge (adj, node); + } + vector_free (edges); + + // remove all edges from us to other nodes + edges = vector_copy (node->to); + for (unsigned int i = 0; i < vector_active (edges); i++) + { + adj = vector_slot (edges, i); + graph_remove_edge (node, adj); + } + vector_free (edges); + + // if there is a deletion callback, call it + if (node->del && node->data) + (*node->del) (node->data); + + // free adjacency lists + vector_free (node->to); + vector_free (node->from); + + // remove node from graph->nodes + for (unsigned int i = 0; i < vector_active (graph->nodes); i++) + if (vector_slot (graph->nodes, i) == node) + vector_remove (graph->nodes, i); + + // free the node itself + XFREE (MTYPE_GRAPH_NODE, node); +} + +struct graph_node * +graph_add_edge (struct graph_node *from, struct graph_node *to) +{ + vector_set (from->to, to); + vector_set (to->from, from); + return to; +} + +void +graph_remove_edge (struct graph_node *from, struct graph_node *to) +{ + // remove from from to->from + for (unsigned int i = 0; i < vector_active (to->from); i++) + if (vector_slot (to->from, i) == from) + vector_remove (to->from, i); + // remove to from from->to + for (unsigned int i = 0; i < vector_active (from->to); i++) + if (vector_slot (from->to, i) == to) + vector_remove (from->to, i); +} + +void +graph_delete_graph (struct graph *graph) +{ + // delete each node in the graph + for (unsigned int i = 0; i < vector_active (graph->nodes); i++) + graph_delete_node (graph, vector_slot (graph->nodes, i)); + + vector_free (graph->nodes); + XFREE (MTYPE_GRAPH, graph); +} diff --cc lib/vty.c index 61b0c3b4be,48b64a95c4..ff559d1705 --- a/lib/vty.c +++ b/lib/vty.c @@@ -40,8 -40,12 +40,12 @@@ #include #include + DEFINE_MTYPE_STATIC(LIB, VTY, "VTY") + DEFINE_MTYPE_STATIC(LIB, VTY_OUT_BUF, "VTY output buffer") + DEFINE_MTYPE_STATIC(LIB, VTY_HIST, "VTY history") + /* Vty events */ -enum event +enum event { VTY_SERV, VTY_READ, diff --cc vtysh/vtysh.c index 18ca0dca29,10bdef8cc8..f767c64d35 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@@ -44,8 -44,8 +44,10 @@@ #include "ns.h" #include "vrf.h" +#include "lib/grammar_sandbox.h" + + DEFINE_MTYPE_STATIC(MVTYSH, VTYSH_CMD, "Vtysh cmd copy") + /* Struct VTY. */ struct vty *vty;