.arch-ids
*~
*.loT
- memtypes.h
route_types.h
+command_lex.c
+command_parse.c
+command_parse.h
refix
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@
#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;
#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
--- /dev/null
+/*
+ * 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 <zebra.h>
++
+#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;
+}
--- /dev/null
- #include "memory.h"
+/*
+ * 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"
- docstr = element->doc ? XSTRDUP(MTYPE_TMP, element->doc) : NULL;
+
+ 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 <string> WORD
+%token <string> IPV4
+%token <string> IPV4_PREFIX
+%token <string> IPV6
+%token <string> IPV6_PREFIX
+%token <string> VARIABLE
+%token <string> RANGE
+
+/* union types for parsed rules */
+%type <node> start
+%type <node> sentence_root
+%type <node> literal_token
+%type <node> placeholder_token
+%type <node> simple_token
+%type <subgraph> option
+%type <subgraph> option_token
+%type <subgraph> option_token_seq
+%type <subgraph> selector
+%type <subgraph> selector_token
+%type <subgraph> selector_token_seq
+%type <subgraph> selector_seq_seq
+%type <subgraph> 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 */
- new_token_node (graph, WORD_TKN, XSTRDUP (MTYPE_CMD_TOKENS, $1), doc_next());
++ 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 =
- XFREE (MTYPE_TMP, $1);
++ 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;
- $$ = new_token_node (graph, WORD_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next());
++ free ($1);
+}
+;
+
+simple_token:
+ literal_token
+| placeholder_token
+;
+
+compound_token:
+ selector
+| option
+;
+
+literal_token: WORD
+{
- $$ = new_token_node (graph, IPV4_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_PREFIX_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, IPV6_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_PREFIX_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, VARIABLE_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, RANGE_TKN, XSTRDUP(MTYPE_CMD_TOKENS, $1), doc_next());
++ $$ = new_token_node (graph, VARIABLE_TKN, strdup($1), doc_next());
+ free ($1);
+}
+| RANGE
+{
- $$ = XMALLOC (MTYPE_TMP, sizeof (struct subgraph));
++ $$ = 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);
+}
+
+/* <selector|set> productions */
+selector: '<' selector_seq_seq '>'
+{
- XFREE (MTYPE_TMP, $2);
++ $$ = 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);
- $$ = XMALLOC (MTYPE_TMP, sizeof (struct subgraph));
++ free ($2);
+};
+
+selector_seq_seq:
+ selector_seq_seq '|' selector_token_seq
+{
- XFREE (MTYPE_TMP, $3);
++ $$ = 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, $1);
- 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);
- $$ = XMALLOC (MTYPE_TMP, sizeof (struct subgraph));
++ free ($1);
++ free ($3);
+}
+| selector_token_seq '|' selector_token_seq
+{
- XFREE (MTYPE_TMP, $1);
- XFREE (MTYPE_TMP, $3);
++ $$ = 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);
- $$ = XMALLOC (MTYPE_TMP, sizeof (struct subgraph));
++ 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
+{
- XFREE (MTYPE_TMP, $1);
- XFREE (MTYPE_TMP, $2);
++ $$ = malloc (sizeof (struct subgraph));
+ graph_add_edge ($1->end, $2->start);
+ $$->start = $1->start;
+ $$->end = $2->end;
- $$ = XMALLOC (MTYPE_TMP, sizeof (struct subgraph));
++ 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
- XFREE (MTYPE_TMP, $2);
++ $$ = 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);
- $$ = XMALLOC (MTYPE_TMP, sizeof (struct subgraph));
++ free ($2);
+}
+;
+
+option_token_seq:
+ option_token
+| option_token_seq option_token
+{
- XFREE (MTYPE_TMP, $1);
++ $$ = malloc (sizeof (struct subgraph));
+ graph_add_edge ($1->end, $2->start);
+ $$->start = $1->start;
+ $$->end = $2->end;
- $$ = XMALLOC (MTYPE_TMP, sizeof (struct subgraph));
++ free ($1);
+}
+;
+
+option_token:
+ simple_token
+{
- XSTRDUP (MTYPE_CMD_TOKENS, CMD_CR_TEXT),
- XSTRDUP (MTYPE_CMD_TOKENS, ""));
++ $$ = 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,
- return XSTRDUP (MTYPE_CMD_TOKENS, "");
- return XSTRDUP (MTYPE_CMD_TOKENS, piece);
++ 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 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;
+}
--- /dev/null
+/*
+ * 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 <zebra.h>
+#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);
+}
#include <arpa/telnet.h>
#include <termios.h>
+ 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,
#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;