From: Quentin Young Date: Tue, 19 Jul 2016 21:14:27 +0000 (+0000) Subject: lib: Break up functions, begin matcher X-Git-Tag: frr-3.0-branchpoint~129^2~284 X-Git-Url: https://git.puffer.fish/?a=commitdiff_plain;h=9d0662e009c0cf4532b88c9a1fb0f7c0dc174584;p=matthieu%2Ffrr.git lib: Break up functions, begin matcher Moved test hook out of command.c into vtysh.c, renamed graph modules, added matching code Signed-off-by: Quentin Young --- diff --git a/lib/Makefile.am b/lib/Makefile.am index 126303622f..2df5ed61f4 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -11,7 +11,7 @@ libzebra_la_LDFLAGS = -version-info 0:0:0 libzebra_la_SOURCES = \ network.c pid_output.c getopt.c getopt1.c daemon.c \ checksum.c vector.c linklist.c vty.c \ - cmdtree.c command_parse.y command_lex.l \ + command_graph.c command_parse.y command_lex.l command_match.c grammar_sandbox.c \ command.c \ sockunion.c prefix.c thread.c if.c memory.c buffer.c table.c hash.c \ filter.c routemap.c distribute.c stream.c str.c log.c plist.c \ @@ -28,7 +28,7 @@ libzebra_la_LIBADD = @LIB_REGEX@ @LIBCAP@ pkginclude_HEADERS = \ buffer.h checksum.h filter.h getopt.h hash.h \ if.h linklist.h log.h \ - cmdtree.h \ + command_graph.h command_match.h grammar_sandbox.h \ command.h \ memory.h network.h prefix.h routemap.h distribute.h sockunion.h \ str.h stream.h table.h thread.h vector.h version.h vty.h zebra.h \ diff --git a/lib/cmdtree.c b/lib/cmdtree.c deleted file mode 100644 index 38632dca40..0000000000 --- a/lib/cmdtree.c +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Command DFA module. - * Provides a DFA data structure and associated functions for manipulating it. - * Used to match user command line input. - * - * @author Quentin Young - */ - -#include -#include "cmdtree.h" -#include "memory.h" - -struct graph_node * -add_node(struct graph_node *parent, struct graph_node *child) -{ - struct graph_node *p_child; - - for (unsigned int i = 0; i < vector_active(parent->children); i++) - { - p_child = vector_slot(parent->children, i); - if (cmp_node(child, p_child)) - return p_child; - } - vector_set(parent->children, child); - return child; -} - -int -cmp_node(struct graph_node *first, struct graph_node *second) -{ - // compare types - if (first->type != second->type) return 0; - - switch (first->type) { - case WORD_GN: // words and variables are equal if their - case VARIABLE_GN: // text value is equal - if (first->text && second->text) { - if (strcmp(first->text, second->text)) return 0; - } - else if (first->text != second->text) return 0; - break; - case RANGE_GN: // ranges are equal if their bounds are equal - if (first->min != second->min || first->max != second->max) - return 0; - break; - case NUMBER_GN: // numbers are equal if their values are equal - if (first->value != second->value) return 0; - break; - /* selectors and options should be equal if all paths are equal, - * but the graph isomorphism problem is not solvable in polynomial - * time so we consider selectors and options inequal in all cases - */ - case SELECTOR_GN: - case OPTION_GN: - return 0; - default: - break; - } - - return 1; -} - -struct graph_node * -new_node(enum graph_node_type type) -{ - struct graph_node *node = malloc(sizeof(struct graph_node)); - node->type = type; - node->children = vector_init(VECTOR_MIN_SIZE); - node->is_leaf = 0; - node->is_root = 0; - node->end = NULL; - node->text = NULL; - node->value = 0; - node->min = 0; - node->max = 0; - node->func = NULL; - - return node; -} - -void -walk_graph(struct graph_node *start, int level) -{ - // print this node - switch (start->type) { - case WORD_GN: - case IPV4_GN: - case IPV4_PREFIX_GN: - case IPV6_GN: - case IPV6_PREFIX_GN: - case VARIABLE_GN: - case RANGE_GN: - fprintf(stderr, "%s", start->text); - break; - case NUMBER_GN: - fprintf(stderr, "%d", start->value); - break; - case SELECTOR_GN: - fprintf(stderr, "<>"); - break; - case OPTION_GN: - fprintf(stderr, "[]"); - break; - case NUL_GN: - fprintf(stderr, "NUL"); - break; - default: - fprintf(stderr, "ERROR"); - } - fprintf(stderr, "[%d] ", vector_active(start->children)); - - if (vector_active(start->children)) - for (unsigned int i = 0; i < vector_active(start->children); i++) - { - struct graph_node *r = vector_slot(start->children, i); - if (!r) { - fprintf(stderr, "Child seems null?\n"); - break; - } - else { - if (vector_active(start->children) > 1) { - fprintf(stderr, "\n"); - for (int i = 0; i < level+1; i++) - fprintf(stderr, " "); - walk_graph(r, level+1); - } - else - walk_graph(r, level); - } - } - if (level == 0) - fprintf(stderr, "\n"); -} diff --git a/lib/cmdtree.h b/lib/cmdtree.h deleted file mode 100644 index 3d2366b008..0000000000 --- a/lib/cmdtree.h +++ /dev/null @@ -1,80 +0,0 @@ -#include "vty.h" -#include "vector.h" - -enum graph_node_type -{ - WORD_GN, - IPV4_GN, - IPV4_PREFIX_GN, - IPV6_GN, - IPV6_PREFIX_GN, - VARIABLE_GN, - RANGE_GN, - NUMBER_GN, - SELECTOR_GN, - OPTION_GN, - NUL_GN -}; - -struct graph_node -{ - enum graph_node_type type; - vector children; - int is_root; // true if first token in command - int is_leaf; // true if last token in command - struct graph_node * end; // pointer to end for selector & option - - int (*func)(struct vty *, int, const char *[]); - - /* various data fields for nodes */ - char* text; // for words and variables - int value; // for numbers - int min, max; // for ranges -}; - -/* - * Adds a child to a node. - * If the node already has the exact same child, nothing is done. This is - * decided with cmp_node. - * - * @param[in] parent node - * @param[in] child node - * @return the new child, or the existing child if the parent already has the - * new child - */ -extern struct graph_node * -add_node(struct graph_node *, struct graph_node *); - -/* - * Compares two nodes for parsing equivalence. - * Equivalence in this case means that a single user input token - * should be able to unambiguously match one of the two nodes. - * For example, two nodes which have all fields equal except their - * function pointers would be considered equal. - * - * @param[in] first node to compare - * @param[in] second node to compare - * @return 1 if equal, zero otherwise. - */ -extern int -cmp_node(struct graph_node *, struct graph_node *); - -/* - * Create a new node. - * Initializes all fields to default values and sets the node type. - * - * @param[in] node type - * @return pointer to the newly allocated node - */ -extern struct graph_node * -new_node(enum graph_node_type); - -/** - * Walks a command DFA, printing structure to stdout. - * For debugging. - * - * @param[in] start node of graph to walk - * @param[in] graph depth for recursion, caller passes 0 - */ -extern void -walk_graph(struct graph_node *, int); diff --git a/lib/command.c b/lib/command.c index 4b571ba75d..490c2a0690 100644 --- a/lib/command.c +++ b/lib/command.c @@ -34,8 +34,6 @@ Boston, MA 02111-1307, USA. */ #include "workqueue.h" #include "vrf.h" -#include "grammar_sandbox.c" - /* Command vector which includes some level of command lists. Normally each daemon maintains each own cmdvec. */ vector cmdvec = NULL; @@ -4077,10 +4075,6 @@ cmd_init (int terminal) install_element (ENABLE_NODE, &show_version_cmd); install_element (ENABLE_NODE, &show_commandtree_cmd); - /**/ - grammar_sandbox_init(); - /**/ - if (terminal) { install_element (ENABLE_NODE, &config_terminal_length_cmd); diff --git a/lib/command_graph.c b/lib/command_graph.c new file mode 100644 index 0000000000..9b91eaf242 --- /dev/null +++ b/lib/command_graph.c @@ -0,0 +1,133 @@ +/* + * Command DFA module. + * Provides a DFA data structure and associated functions for manipulating it. + * Used to match user command line input. + * + * @author Quentin Young + */ + +#include +#include "command_graph.h" +#include "memory.h" + +struct graph_node * +add_node(struct graph_node *parent, struct graph_node *child) +{ + struct graph_node *p_child; + + for (unsigned int i = 0; i < vector_active(parent->children); i++) + { + p_child = vector_slot(parent->children, i); + if (cmp_node(child, p_child)) + return p_child; + } + vector_set(parent->children, child); + return child; +} + +int +cmp_node(struct graph_node *first, struct graph_node *second) +{ + // compare types + if (first->type != second->type) return 0; + + switch (first->type) { + case WORD_GN: // words and variables are equal if their + case VARIABLE_GN: // text value is equal + if (first->text && second->text) { + if (strcmp(first->text, second->text)) return 0; + } + else if (first->text != second->text) return 0; + break; + case RANGE_GN: // ranges are equal if their bounds are equal + if (first->min != second->min || first->max != second->max) + return 0; + break; + case NUMBER_GN: // numbers are equal if their values are equal + if (first->value != second->value) return 0; + break; + /* selectors and options should be equal if all paths are equal, + * but the graph isomorphism problem is not solvable in polynomial + * time so we consider selectors and options inequal in all cases + */ + case SELECTOR_GN: + case OPTION_GN: + return 0; + default: + break; + } + + return 1; +} + +struct graph_node * +new_node(enum graph_node_type type) +{ + struct graph_node *node = malloc(sizeof(struct graph_node)); + node->type = type; + node->children = vector_init(VECTOR_MIN_SIZE); + node->is_leaf = 0; + node->is_root = 0; + node->end = NULL; + node->text = NULL; + node->value = 0; + node->min = 0; + node->max = 0; + node->func = NULL; + + return node; +} + +void +walk_graph(struct graph_node *start, int level) +{ + // print this node + switch (start->type) { + case WORD_GN: + case IPV4_GN: + case IPV4_PREFIX_GN: + case IPV6_GN: + case IPV6_PREFIX_GN: + case VARIABLE_GN: + case RANGE_GN: + fprintf(stderr, "%s", start->text); + break; + case NUMBER_GN: + fprintf(stderr, "%d", start->value); + break; + case SELECTOR_GN: + fprintf(stderr, "<>"); + break; + case OPTION_GN: + fprintf(stderr, "[]"); + break; + case NUL_GN: + fprintf(stderr, "NUL"); + break; + default: + fprintf(stderr, "ERROR"); + } + fprintf(stderr, "[%d] ", vector_active(start->children)); + + if (vector_active(start->children)) + for (unsigned int i = 0; i < vector_active(start->children); i++) + { + struct graph_node *r = vector_slot(start->children, i); + if (!r) { + fprintf(stderr, "Child seems null?\n"); + break; + } + else { + if (vector_active(start->children) > 1) { + fprintf(stderr, "\n"); + for (int i = 0; i < level+1; i++) + fprintf(stderr, " "); + walk_graph(r, level+1); + } + else + walk_graph(r, level); + } + } + if (level == 0) + fprintf(stderr, "\n"); +} diff --git a/lib/command_graph.h b/lib/command_graph.h new file mode 100644 index 0000000000..8d23577d37 --- /dev/null +++ b/lib/command_graph.h @@ -0,0 +1,85 @@ +#ifndef COMMAND_GRAPH_H +#define COMMAND_GRAPH_H + +#include "vty.h" +#include "vector.h" + +enum graph_node_type +{ + WORD_GN, + IPV4_GN, + IPV4_PREFIX_GN, + IPV6_GN, + IPV6_PREFIX_GN, + VARIABLE_GN, + RANGE_GN, + NUMBER_GN, + SELECTOR_GN, + OPTION_GN, + NUL_GN +}; + +struct graph_node +{ + enum graph_node_type type; + vector children; + int is_root; // true if first token in command + int is_leaf; // true if last token in command + struct graph_node * end; // pointer to end for selector & option + + int (*func)(struct vty *, int, const char *[]); + + /* various data fields for nodes */ + char* text; // for words and variables + int value; // for numbers + int min, max; // for ranges +}; + +/* + * Adds a child to a node. + * If the node already has the exact same child, nothing is done. This is + * decided with cmp_node. + * + * @param[in] parent node + * @param[in] child node + * @return the new child, or the existing child if the parent already has the + * new child + */ +extern struct graph_node * +add_node(struct graph_node *, struct graph_node *); + +/* + * Compares two nodes for parsing equivalence. + * Equivalence in this case means that a single user input token + * should be able to unambiguously match one of the two nodes. + * For example, two nodes which have all fields equal except their + * function pointers would be considered equal. + * + * @param[in] first node to compare + * @param[in] second node to compare + * @return 1 if equal, zero otherwise. + */ +extern int +cmp_node(struct graph_node *, struct graph_node *); + +/* + * Create a new node. + * Initializes all fields to default values and sets the node type. + * + * @param[in] node type + * @return pointer to the newly allocated node + */ +extern struct graph_node * +new_node(enum graph_node_type); + +/** + * Walks a command DFA, printing structure to stdout. + * For debugging. + * + * @param[in] start node of graph to walk + * @param[in] graph depth for recursion, caller passes 0 + */ +extern void +walk_graph(struct graph_node *, int); + +#endif diff --git a/lib/command_match.c b/lib/command_match.c new file mode 100644 index 0000000000..bcc28bdc5b --- /dev/null +++ b/lib/command_match.c @@ -0,0 +1,174 @@ +#include +#include "memory.h" +#include "command_match.h" + +enum match_type +match_command (struct graph_node *start, enum filter_type filter, const char *input) +{ + // match input on DFA + return exact_match; +} + + +#define IPV4_ADDR_STR "0123456789." +#define IPV4_PREFIX_STR "0123456789./" + +enum match_type +cmd_ipv4_match (const char *str) +{ + struct sockaddr_in sin_dummy; + + if (str == NULL) + return partly_match; + + if (strspn (str, IPV4_ADDR_STR) != strlen (str)) + return no_match; + + if (inet_pton(AF_INET, str, &sin_dummy.sin_addr) != 1) + return no_match; + + return exact_match; +} + +enum match_type +cmd_ipv4_prefix_match (const char *str) +{ + struct sockaddr_in sin_dummy; + const char *delim = "/\0"; + char *dupe, *prefix, *mask, *context, *endptr; + int nmask = -1; + + if (str == NULL) + return partly_match; + + if (strspn (str, IPV4_PREFIX_STR) != strlen (str)) + return no_match; + + /* tokenize to address + mask */ + dupe = XMALLOC(MTYPE_TMP, strlen(str)+1); + strncpy(dupe, str, strlen(str)+1); + prefix = strtok_r(dupe, delim, &context); + mask = strtok_r(NULL, delim, &context); + + if (!mask) + return partly_match; + + /* validate prefix */ + if (inet_pton(AF_INET, prefix, &sin_dummy.sin_addr) != 1) + return no_match; + + /* validate mask */ + nmask = strtol (mask, &endptr, 10); + if (*endptr != '\0' || nmask < 0 || nmask > 32) + return no_match; + + XFREE(MTYPE_TMP, dupe); + + return exact_match; +} + +#define IPV6_ADDR_STR "0123456789abcdefABCDEF:." +#define IPV6_PREFIX_STR "0123456789abcdefABCDEF:./" + +#ifdef HAVE_IPV6 +enum match_type +cmd_ipv6_match (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; +} + +enum match_type +cmd_ipv6_prefix_match (const char *str) +{ + struct sockaddr_in6 sin6_dummy; + const char *delim = "/\0"; + char *dupe, *prefix, *mask, *context, *endptr; + int nmask = -1; + + if (str == NULL) + return partly_match; + + if (strspn (str, IPV6_PREFIX_STR) != strlen (str)) + return no_match; + + /* tokenize to address + mask */ + dupe = XMALLOC(MTYPE_TMP, strlen(str)+1); + strncpy(dupe, str, strlen(str)+1); + prefix = strtok_r(dupe, delim, &context); + mask = strtok_r(NULL, delim, &context); + + if (!mask) + return partly_match; + + /* validate prefix */ + if (inet_pton(AF_INET6, prefix, &sin6_dummy.sin6_addr) != 1) + return no_match; + + /* validate mask */ + nmask = strtol (mask, &endptr, 10); + if (*endptr != '\0' || nmask < 0 || nmask > 128) + return no_match; + + XFREE(MTYPE_TMP, dupe); + + return exact_match; +} +#endif + +enum match_type +cmd_range_match (struct graph_node *rangenode, const char *str) +{ + char *endptr = NULL; + signed long long val; + + if (str == NULL) + return 1; + + val = strtoll (str, &endptr, 10); + if (*endptr != '\0') + return 0; + val = llabs(val); + + if (val < rangenode->min || val > rangenode->max) + return no_match; + else + return exact_match; +} + +enum match_type +cmd_word_match(struct graph_node *wordnode, + enum filter_type filter, + const char *word) +{ + if (filter == FILTER_RELAXED) + if (!word || !strlen(word)) + return partly_match; + + if (!word) + return no_match; + + if (filter == FILTER_RELAXED && !strncmp(wordnode->text, word, strlen(word))) + { + if (!strcmp(wordnode->text, word)) + return exact_match; + return partly_match; + } + if (filter == FILTER_STRICT && !strcmp(wordnode->text, word)) + return exact_match; + + return no_match; +} diff --git a/lib/command_match.h b/lib/command_match.h new file mode 100644 index 0000000000..cddeb08af7 --- /dev/null +++ b/lib/command_match.h @@ -0,0 +1,69 @@ +#ifndef COMMAND_MATCH_H +#define COMMAND_MATCH_H + +#include "command_graph.h" + +/** + * Filter types. These tell the parser whether to allow + * partial matching on tokens. + */ +enum filter_type +{ + FILTER_RELAXED, + FILTER_STRICT +}; + +/** + * Command matcher result value. + */ +enum matcher_rv +{ + MATCHER_OK, + MATCHER_COMPLETE, + MATCHER_INCOMPLETE, + MATCHER_NO_MATCH, + MATCHER_AMBIGUOUS, + MATCHER_EXCEED_ARGC_MAX +}; + +/* Completion match types. */ +enum match_type +{ + no_match, + partly_match, + exact_match +}; +/** + * Defines which matcher_rv values constitute + * an error. Should be used against matcher_rv + * return values to do basic error checking. + */ +#define MATCHER_ERROR(matcher_rv) \ + ( (matcher_rv) == MATCHER_INCOMPLETE \ + || (matcher_rv) == MATCHER_NO_MATCH \ + || (matcher_rv) == MATCHER_AMBIGUOUS \ + || (matcher_rv) == MATCHER_EXCEED_ARGC_MAX \ + ) + +enum match_type +cmd_ipv4_match (const char *); + +enum match_type +cmd_ipv4_prefix_match (const char *); + +enum match_type +cmd_ipv6_match (const char *); + +enum match_type +cmd_ipv6_prefix_match (const char *); + +enum match_type +cmd_range_match (struct graph_node *, const char *str); + +enum match_type +cmd_word_match (struct graph_node *, enum filter_type, const char *); + +enum match_type +match_command (struct graph_node *, enum filter_type, const char *); + +#endif diff --git a/lib/command_parse.y b/lib/command_parse.y index cf2cd95bdd..80487af7cd 100644 --- a/lib/command_parse.y +++ b/lib/command_parse.y @@ -1,5 +1,15 @@ +/* + * Command format string parser. + * + * Turns a command definition into a DFA that together with the functions + * provided in command_match.c may be used to map command line input to a + * function. + * + * @author Quentin Young + */ + %{ -#include "cmdtree.h" +#include "command_graph.h" extern int yylex(void); extern void yyerror(const char *); @@ -8,16 +18,22 @@ extern void yyerror(const char *); #define YYDEBUG 1 %} %code provides { -extern struct graph_node *cmd_parse_format_new(const char *, const char *, struct graph_node *); -extern void set_buffer_string(const char *); +extern struct +graph_node *cmd_parse_format(const char *, + const char *, + struct graph_node *); +extern void +set_buffer_string(const char *); } +/* valid types for tokens */ %union{ int integer; char *string; struct graph_node *node; } +/* some helpful state variables */ %{ struct graph_node *startnode, // command root node *currnode, // current node @@ -66,7 +82,7 @@ sentence_root: WORD $$ = new_node(WORD_GN); $$->text = strdup(yylval.string); fprintf(stderr, ">>>>>>>> YYLVAL.STRING: %s\n", yylval.string); - fprintf(stderr, ">>>>>>>> TEXT: %s\n", $$->text); + fprintf(stderr, ">>>>>>>> TEXT: %s\n", $$->text); currnode = $$; currnode->is_root = 1; @@ -270,7 +286,7 @@ void yyerror(char const *message) { } struct graph_node * -cmd_parse_format_new(const char *string, const char *desc, struct graph_node *start) +cmd_parse_format(const char *string, const char *desc, struct graph_node *start) { fprintf(stderr, "parsing: %s\n", string); diff --git a/lib/grammar_sandbox.c b/lib/grammar_sandbox.c index 807ada9d98..1e6a76c69c 100644 --- a/lib/grammar_sandbox.c +++ b/lib/grammar_sandbox.c @@ -1,17 +1,14 @@ #include "command.h" +#include "command_graph.h" #include "command_parse.h" -#include "cmdtree.h" +#include "command_match.h" #define GRAMMAR_STR "CLI grammar sandbox\n" struct graph_node * nodegraph; -DEFUN (grammar_test, - grammar_test_cmd, - "grammar parse .COMMAND", - GRAMMAR_STR - "command to pass to new parser\n") -{ +/* +char* combine_vararg(char* argv, int argc) { size_t linesize = 0; for (int i = 0; i < argc; i++) linesize += strlen(argv[i]) + 1; @@ -24,8 +21,19 @@ DEFUN (grammar_test, strcat(cat, " "); } - //struct graph_node *result = new_node(NUL_GN); - cmd_parse_format_new((const char*) cat, "lol", nodegraph); + return cat; +} +*/ + +DEFUN (grammar_test, + grammar_test_cmd, + "grammar parse .COMMAND", + GRAMMAR_STR + "command to pass to new parser\n") +{ + + const char* command = argv_concat(argv, argc, 0); + cmd_parse_format(command, "lol", nodegraph); walk_graph(nodegraph, 0); return CMD_SUCCESS; @@ -37,8 +45,20 @@ DEFUN (grammar_test_show, GRAMMAR_STR "print current accumulated DFA\n") { - walk_graph(nodegraph, 0); - return CMD_SUCCESS; + walk_graph(nodegraph, 0); + return CMD_SUCCESS; +} + +DEFUN (grammar_test_match, + grammar_test_match_cmd, + "grammar match .COMMAND", + GRAMMAR_STR + "attempt to match input on DFA\n" + "command to match") +{ + const char* command = argv_concat(argv, argc, 0); + match_command(nodegraph, FILTER_STRICT, command); + return CMD_SUCCESS; } @@ -48,4 +68,5 @@ void grammar_sandbox_init() { nodegraph = new_node(NUL_GN); install_element (ENABLE_NODE, &grammar_test_cmd); install_element (ENABLE_NODE, &grammar_test_show_cmd); + install_element (ENABLE_NODE, &grammar_test_match_cmd); } diff --git a/lib/grammar_sandbox.h b/lib/grammar_sandbox.h new file mode 100644 index 0000000000..2b2c742461 --- /dev/null +++ b/lib/grammar_sandbox.h @@ -0,0 +1,2 @@ +void +grammar_sandbox_init(void); diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c index ecb6c5c6ac..9d4061cfcf 100644 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@ -42,6 +42,8 @@ #include "bgpd/bgp_vty.h" #include "vrf.h" +#include "lib/grammar_sandbox.h" + /* Struct VTY. */ struct vty *vty; @@ -3076,4 +3078,6 @@ vtysh_init_vty (void) install_element (CONFIG_NODE, &vtysh_enable_password_text_cmd); install_element (CONFIG_NODE, &no_vtysh_enable_password_cmd); + /* grammar sandbox */ + grammar_sandbox_init(); }