]>
git.proxmox.com Git - mirror_frr.git/blob - lib/command_match.c
2 * Input matching routines for CLI backend.
5 * Copyright (C) 2016 Cumulus Networks, Inc.
7 * This file is part of GNU Zebra.
9 * GNU Zebra is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU General Public License as published by the
11 * Free Software Foundation; either version 2, or (at your option) any
14 * GNU Zebra is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with GNU Zebra; see the file COPYING. If not, write to the Free
21 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
26 #include "command_match.h"
27 #include "command_parse.h"
30 /* matcher helper prototypes */
32 add_nexthops (struct list
*, struct graph_node
*);
35 match_command_r (struct graph_node
*, vector
, unsigned int);
38 score_precedence (enum graph_node_type
);
40 static enum match_type
41 min_match_level (enum node_type
);
43 static struct graph_node
*
44 copy_node (struct graph_node
*);
47 delete_nodelist (void *);
49 static struct graph_node
*
50 disambiguate_nodes (struct graph_node
*, struct graph_node
*, char *);
53 disambiguate (struct list
*, struct list
*, vector
, unsigned int);
55 /* token matcher prototypes */
56 static enum match_type
57 match_token (struct graph_node
*, char *);
59 static enum match_type
60 match_ipv4 (const char *);
62 static enum match_type
63 match_ipv4_prefix (const char *);
65 static enum match_type
66 match_ipv6 (const char *);
68 static enum match_type
69 match_ipv6_prefix (const char *);
71 static enum match_type
72 match_range (struct graph_node
*, const char *);
74 static enum match_type
75 match_word (struct graph_node
*, const char *);
77 static enum match_type
78 match_number (struct graph_node
*, const char *);
80 static enum match_type
81 match_variable (struct graph_node
*node
, const char *word
);
83 /* matching functions */
84 static enum matcher_rv matcher_rv
;
87 match_command (struct graph_node
*start
,
90 struct cmd_element
**el
)
92 matcher_rv
= MATCHER_NO_MATCH
;
94 // call recursive matcher on each starting child
95 for (unsigned int i
= 0; i
< vector_active (start
->children
); i
++)
97 *argv
= match_command_r (vector_slot (start
->children
, i
), vline
, 0);
98 if (*argv
) // successful match
100 struct graph_node
*end
= listgetdata (listtail (*argv
));
111 * Builds an argument list given a DFA and a matching input line.
113 * First the function determines if the node it is passed matches the first
114 * token of input. If it does not, it returns NULL (MATCHER_NO_MATCH). If it
115 * does match, then it saves the input token as the head of an argument list.
117 * The next step is to see if there is further input in the input line. If
118 * there is not, the current node's children are searched to see if any of them
119 * are leaves (type END_GN). If this is the case, then the bottom of the
120 * recursion stack has been reached, the leaf is pushed onto the argument list,
121 * the current node is pushed, and the resulting argument list is
122 * returned (MATCHER_OK). If it is not the case, NULL is returned, indicating
123 * that there is no match for the input along this path (MATCHER_INCOMPLETE).
125 * If there is further input, then the function recurses on each of the current
126 * node's children, passing them the input line minus the token that was just
127 * matched. For each child, the return value of the recursive call is
128 * inspected. If it is null, then there is no match for the input along the
129 * subgraph headed by that child. If it is not null, then there is at least one
130 * input match in that subgraph (more on this in a moment).
132 * If a recursive call on a child returns a non-null value, then it has matched
133 * the input given it on the subgraph that starts with that child. However, due
134 * to the flexibility of the grammar, it is sometimes the case that two or more
135 * child graphs match the same input (two or more of the recursive calls have
136 * non-NULL return values). This is not a valid state, since only one true
137 * match is possible. In order to resolve this conflict, the function keeps a
138 * reference to the child node that most specifically matches the input. This
139 * is done by assigning each node type a precedence. If a child is found to
140 * match the remaining input, then the precedence values of the current
141 * best-matching child and this new match are compared. The node with higher
142 * precedence is kept, and the other match is discarded. Due to the recursive
143 * nature of this function, it is only necessary to compare the precedence of
144 * immediate children, since all subsequent children will already have been
145 * disambiguated in this way.
147 * In the event that two children are found to match with the same precedence,
148 * then the input is ambiguous for the passed cmd_element and NULL is returned.
150 * The ultimate return value is an ordered linked list of nodes that comprise
151 * the best match for the command, each with their `arg` fields pointing to the
152 * matching token string.
154 * @param[in] start the start node.
155 * @param[in] vline the vectorized input line.
156 * @param[in] n the index of the first input token.
159 match_command_r (struct graph_node
*start
, vector vline
, unsigned int n
)
161 // get the minimum match level that can count as a full match
162 enum match_type minmatch
= min_match_level (start
->type
);
164 // get the current operating token
165 char *token
= vector_slot (vline
, n
);
167 // if we don't match this node, die
168 if (match_token (start
, token
) < minmatch
)
171 // pointers for iterating linklist
173 struct graph_node
*gn
;
175 // get all possible nexthops
176 struct list
*next
= list_new();
177 add_nexthops (next
, start
);
179 // determine the best match
181 struct list
*currbest
= NULL
;
182 for (ALL_LIST_ELEMENTS_RO (next
,ln
,gn
))
184 // if we've matched all input we're looking for END_GN
185 if (n
+1 == vector_active (vline
))
187 if (gn
->type
== END_GN
)
189 currbest
= list_new();
190 listnode_add (currbest
, copy_node(gn
));
191 currbest
->del
= &delete_nodelist
;
197 // else recurse on candidate child node
198 struct list
*result
= match_command_r (gn
, vline
, n
+1);
200 // save the best match
201 if (result
&& currbest
)
203 struct list
*newbest
= disambiguate (currbest
, result
, vline
, n
+1);
204 ambiguous
= !newbest
|| (ambiguous
&& newbest
== currbest
);
205 list_delete ((newbest
&& newbest
== result
) ? currbest
: result
);
206 currbest
= newbest
? newbest
: currbest
;
216 list_delete (currbest
);
218 matcher_rv
= MATCHER_AMBIGUOUS
;
222 // copy current node, set arg and prepend to currbest
223 struct graph_node
*curr
= copy_node (start
);
224 curr
->arg
= XSTRDUP(MTYPE_CMD_TOKENS
, token
);
225 list_add_node_prev (currbest
, currbest
->head
, curr
);
226 matcher_rv
= MATCHER_OK
;
229 else if (n
+1 == vector_active (vline
) && matcher_rv
== MATCHER_NO_MATCH
)
230 matcher_rv
= MATCHER_INCOMPLETE
;
239 match_command_complete (struct graph_node
*start
, vector vline
, struct list
**completions
)
241 // pointer to next input token to match
244 struct list
*current
= list_new(), // current nodes to match input token against
245 *next
= list_new(); // possible next hops after current input token
247 // pointers used for iterating lists
248 struct graph_node
*gn
;
249 struct listnode
*node
;
251 // add all children of start node to list
252 add_nexthops (next
, start
);
255 for (idx
= 0; idx
< vector_active (vline
) && next
->count
> 0; idx
++)
261 token
= vector_slot (vline
, idx
);
263 for (ALL_LIST_ELEMENTS_RO (current
,node
,gn
))
265 switch (match_token (gn
, token
))
268 if (idx
== vector_active (vline
) - 1)
270 listnode_add (next
, gn
);
274 add_nexthops (next
, gn
);
283 * -----------------------------------------------------------------
284 * token = last input token processed
285 * idx = index in `command` of last token processed
286 * current = set of all transitions from the previous input token
287 * next = set of all nodes reachable from all nodes in `matched`
291 idx
+ 1 == vector_active(vline
) && next
->count
?
302 * Adds all children that are reachable by one parser hop to the given list.
303 * NUL_GN, SELECTOR_GN, and OPTION_GN nodes are treated as transparent.
305 * @param[in] list to add the nexthops to
306 * @param[in] node to start calculating nexthops from
307 * @return the number of children added to the list
310 add_nexthops (struct list
*list
, struct graph_node
*node
)
313 struct graph_node
*child
;
314 for (unsigned int i
= 0; i
< vector_active (node
->children
); i
++)
316 child
= vector_slot (node
->children
, i
);
322 added
+= add_nexthops (list
, child
);
325 listnode_add (list
, child
);
334 * Determines the node types for which a partial match may count as a full
335 * match. Enables command abbrevations.
337 * @param[in] type node type
338 * @return minimum match level needed to for a token to fully match
340 static enum match_type
341 min_match_level (enum node_type type
)
345 // allowing words to partly match enables command abbreviation
354 * Assigns precedence scores to node types.
356 * @param[in] type node type to score
357 * @return precedence score
360 score_precedence (enum graph_node_type type
)
364 // some of these are mutually exclusive, so they share
365 // the same precedence value
384 * Picks the better of two possible matches for a token.
386 * @param[in] first candidate node matching token
387 * @param[in] second candidate node matching token
388 * @param[in] token the token being matched
389 * @return the best-matching node, or NULL if the two are entirely ambiguous
391 static struct graph_node
*
392 disambiguate_nodes (struct graph_node
*first
,
393 struct graph_node
*second
,
396 // if the types are different, simply go off of type precedence
397 if (first
->type
!= second
->type
)
399 int firstprec
= score_precedence (first
->type
);
400 int secndprec
= score_precedence (second
->type
);
401 if (firstprec
!= secndprec
)
402 return firstprec
< secndprec
? first
: second
;
407 // if they're the same, return the more exact match
408 enum match_type fmtype
= match_token (first
, token
);
409 enum match_type smtype
= match_token (second
, token
);
410 if (fmtype
!= smtype
)
411 return fmtype
> smtype
? first
: second
;
417 * Picks the better of two possible matches for an input line.
419 * @param[in] first candidate list of graph_node matching vline
420 * @param[in] second candidate list of graph_node matching vline
421 * @param[in] vline the input line being matched
422 * @param[in] n index into vline to start comparing at
423 * @return the best-matching list, or NULL if the two are entirely ambiguous
426 disambiguate (struct list
*first
,
431 // doesn't make sense for these to be inequal length
432 assert (first
->count
== second
->count
);
433 assert (first
->count
== vector_active (vline
) - n
+1);
435 struct listnode
*fnode
= listhead (first
),
436 *snode
= listhead (second
);
437 struct graph_node
*fgn
= listgetdata (fnode
),
438 *sgn
= listgetdata (snode
),
441 // compare each node, if one matches better use that one
442 for (unsigned int i
= n
; i
< vector_active (vline
); i
++)
444 char *token
= vector_slot(vline
, i
);
445 if ((best
= disambiguate_nodes (fgn
, sgn
, token
)))
446 return best
== fgn
? first
: second
;
447 fnode
= listnextnode (fnode
);
448 snode
= listnextnode (snode
);
449 fgn
= (struct graph_node
*) listgetdata (fnode
);
450 sgn
= (struct graph_node
*) listgetdata (snode
);
457 * Performs a deep copy on a node.
458 * Used to build argv node lists that can be safely deleted or modified by
459 * endpoint functions. Everything is copied except the children vector,
460 * subgraph end pointer and reference count.
462 * @param[in] node to copy
465 static struct graph_node
*
466 copy_node (struct graph_node
*node
)
468 struct graph_node
*new = new_node(node
->type
);
469 new->children
= NULL
;
471 new->text
= node
->text
? XSTRDUP(MTYPE_CMD_TOKENS
, node
->text
) : NULL
;
472 new->value
= node
->value
;
473 new->min
= node
->min
;
474 new->max
= node
->max
;
475 new->element
= node
->element
? copy_cmd_element(node
->element
) : NULL
;
476 new->arg
= node
->arg
? XSTRDUP(MTYPE_CMD_TOKENS
, node
->arg
) : NULL
;
482 * List deletion callback for argv lists.
485 delete_nodelist (void *node
)
487 delete_node ((struct graph_node
*) node
);
491 /* token level matching functions */
493 static enum match_type
494 match_token (struct graph_node
*node
, char *token
)
496 switch (node
->type
) {
498 return match_word (node
, token
);
500 return match_ipv4 (token
);
502 return match_ipv4_prefix (token
);
504 return match_ipv6 (token
);
506 return match_ipv6_prefix (token
);
508 return match_range (node
, token
);
510 return match_number (node
, token
);
512 return match_variable (node
, token
);
519 #define IPV4_ADDR_STR "0123456789."
520 #define IPV4_PREFIX_STR "0123456789./"
522 static enum match_type
523 match_ipv4 (const char *str
)
526 int dots
= 0, nums
= 0;
534 memset (buf
, 0, sizeof (buf
));
543 if (*(str
+ 1) == '.')
546 if (*(str
+ 1) == '\0')
552 if (!isdigit ((int) *str
))
561 strncpy (buf
, sp
, str
- sp
);
562 if (atoi (buf
) > 255)
579 static enum match_type
580 match_ipv4_prefix (const char *str
)
591 memset (buf
, 0, sizeof (buf
));
593 while (*str
!= '\0' && *str
!= '/')
600 if (*(str
+ 1) == '.' || *(str
+ 1) == '/')
603 if (*(str
+ 1) == '\0')
610 if (!isdigit ((int) *str
))
619 strncpy (buf
, sp
, str
- sp
);
620 if (atoi (buf
) > 255)
627 if (*(str
+ 1) == '\0')
633 else if (*str
== '\0')
646 if (!isdigit ((int) *str
))
659 #define IPV6_ADDR_STR "0123456789abcdefABCDEF:."
660 #define IPV6_PREFIX_STR "0123456789abcdefABCDEF:./"
662 static enum match_type
663 match_ipv6 (const char *str
)
665 struct sockaddr_in6 sin6_dummy
;
671 if (strspn (str
, IPV6_ADDR_STR
) != strlen (str
))
674 ret
= inet_pton(AF_INET6
, str
, &sin6_dummy
.sin6_addr
);
682 static enum match_type
683 match_ipv6_prefix (const char *str
)
685 struct sockaddr_in6 sin6_dummy
;
686 const char *delim
= "/\0";
687 char *dupe
, *prefix
, *mask
, *context
, *endptr
;
693 if (strspn (str
, IPV6_PREFIX_STR
) != strlen (str
))
696 /* tokenize to address + mask */
697 dupe
= XCALLOC(MTYPE_TMP
, strlen(str
)+1);
698 strncpy(dupe
, str
, strlen(str
)+1);
699 prefix
= strtok_r(dupe
, delim
, &context
);
700 mask
= strtok_r(NULL
, delim
, &context
);
705 /* validate prefix */
706 if (inet_pton(AF_INET6
, prefix
, &sin6_dummy
.sin6_addr
) != 1)
710 nmask
= strtoimax (mask
, &endptr
, 10);
711 if (*endptr
!= '\0' || nmask
< 0 || nmask
> 128)
714 XFREE(MTYPE_TMP
, dupe
);
720 static enum match_type
721 match_range (struct graph_node
*node
, const char *str
)
723 assert (node
->type
== RANGE_GN
);
731 val
= strtoll (str
, &endptr
, 10);
735 if (val
< node
->min
|| val
> node
->max
)
741 static enum match_type
742 match_word (struct graph_node
*node
, const char *word
)
744 assert (node
->type
== WORD_GN
);
746 // if the passed token is null or 0 length, partly match
747 if (!word
|| !strlen(word
))
750 // if the passed token is strictly a prefix of the full word, partly match
751 if (strlen (word
) < strlen (node
->text
))
752 return !strncmp (node
->text
, word
, strlen (word
)) ?
756 // if they are the same length and exactly equal, exact match
757 else if (strlen (word
) == strlen (node
->text
))
758 return !strncmp (node
->text
, word
, strlen (word
)) ? exact_match
: no_match
;
763 static enum match_type
764 match_number (struct graph_node
*node
, const char *word
)
766 assert (node
->type
== NUMBER_GN
);
768 if (!strcmp ("\0", word
)) return no_match
;
770 long long num
= strtoll (word
, &endptr
, 10);
771 if (endptr
!= '\0') return no_match
;
772 return num
== node
->value
? exact_match
: no_match
;
775 #define VARIABLE_ALPHABET \
776 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890:"
778 static enum match_type
779 match_variable (struct graph_node
*node
, const char *word
)
781 assert (node
->type
== VARIABLE_GN
);
783 return strlen (word
) == strspn(word
, VARIABLE_ALPHABET
) ?
784 exact_match
: no_match
;