]>
git.proxmox.com Git - mirror_frr.git/blob - lib/command_match.c
7b9c5f15d89c71f52b66dad79d5fded2d41cdb89
1 #include "command_match.h"
2 #include "command_parse.h"
6 /* matcher helper prototypes */
8 add_nexthops(struct list
*, struct graph_node
*);
11 match_command_r (struct graph_node
*, vector
, unsigned int);
14 score_precedence (struct graph_node
*);
16 static enum match_type
17 min_match_level(enum node_type type
);
19 /* token matcher prototypes */
20 static enum match_type
21 match_ipv4 (const char *);
23 static enum match_type
24 match_ipv4_prefix (const char *);
26 static enum match_type
27 match_ipv6 (const char *);
29 static enum match_type
30 match_ipv6_prefix (const char *);
32 static enum match_type
33 match_range (struct graph_node
*, const char *str
);
35 static enum match_type
36 match_word (struct graph_node
*, const char *, enum filter_type
);
38 static enum match_type
39 match_number (struct graph_node
*, const char *);
41 static enum match_type
42 match_variable (struct graph_node
*, const char *);
44 static enum match_type
45 match_token (struct graph_node
*, char *, enum filter_type
);
47 /* matching functions */
49 /* Linked list data deletion callback */
51 free_nodelist (void *node
) {
52 free_node ((struct graph_node
*) node
);
56 match_command (struct graph_node
*start
, const char *line
, struct list
**argv
)
59 vector vline
= cmd_make_strvec (line
);
61 for (unsigned int i
= 0; i
< vector_active(start
->children
); i
++)
63 // call recursive builder on each starting child
64 *argv
= match_command_r(vector_slot(start
->children
, i
), vline
, 0);
65 // if any of them succeed, return their argv
66 // since all command DFA's must begin with a word, there can only be
67 // one valid return value
72 // copy the nodes we need
74 struct graph_node
*gn
;
76 for (ALL_LIST_ELEMENTS_RO(*argv
,ln
,gn
)) {
77 describe_node(gn
, buf
, 50);
78 fprintf(stderr
, "%s[%d]\n", buf
, gn
->type
);
79 if (gn
->type
== END_GN
)
89 * Matches a given input line against a DFA.
91 * Builds an argument list given a DFA and a matching input line. This function
92 * should be passed the start node of the DFA, a matching input line, and the
93 * index of the first token in the input line.
95 * First the function determines if the node it is passed matches the first
96 * token of input. If it does not, it returns NULL. If it does match, then it
97 * saves the input token as the head of an argument list.
99 * The next step is to see if there is further input in the input line. If
100 * there is not, the current node's children are searched to see if any of them
101 * are leaves (type END_GN). If this is the case, then the bottom of the
102 * recursion stack has been reached, and the argument list (with one node) is
103 * returned. If it is not the case, NULL is returned, indicating that there is
104 * no match for the input along this path.
106 * If there is further input, then the function recurses on each of the current
107 * node's children, passing them the input line minus the token that was just
108 * matched. For each child, the return value of the recursive call is
109 * inspected. If it is null, then there is no match for the input along the
110 * subgraph headed by that child. If it is not null, then there is at least one
111 * input match in that subgraph (more on this in a moment).
113 * If a recursive call on a child returns a non-null value, then it has matched
114 * the input given it on the subgraph that starts with that child. However, due
115 * to the flexibility of the grammar, it is sometimes the case that two or more
116 * child graphs match the same input (two or more of the recursive calls have
117 * non-NULL return values). This is not a valid state, since only one true
118 * match is possible. In order to resolve this conflict, the function keeps a
119 * reference to the child node that most specifically matches the input. This
120 * is done by assigning each node type a precedence. If a child is found to
121 * match the remaining input, then the precedence values of the current
122 * best-matching child and this new match are compared. The node with higher
123 * precedence is kept, and the other match is discarded. Due to the recursive
124 * nature of this function, it is only necessary to compare the precedence of
125 * immediate children, since all subsequent children will already have been
126 * disambiguated in this way.
128 * In the event that two children are found to match with the same precedence,
129 * then the input is ambiguous for the passed cmd_element and NULL is returned.
131 * The ultimate return value is an ordered linked list of nodes that comprise
132 * the best match for the command, each with their `arg` fields pointing to the
133 * matching token string.
135 * @param[out] start the start node.
136 * @param[in] vline the vectorized input line.
137 * @param[in] n the index of the first input token. Should be 0 for external
141 match_command_r (struct graph_node
*start
, vector vline
, unsigned int n
)
143 // get the minimum match level that can count as a full match
144 enum match_type minmatch
= min_match_level(start
->type
);
146 // if we don't match this node, die
147 if (match_token(start
, vector_slot(vline
, n
), FILTER_RELAXED
) < minmatch
)
150 // arg list for this subgraph
153 // pointers for iterating linklist
154 struct graph_node
*gn
;
157 // get all possible nexthops
158 struct list
*next
= list_new();
159 add_nexthops(next
, start
);
161 // if we're at the end of input, need END_GN or no match
162 if (n
+1 == vector_active (vline
)) {
163 for (ALL_LIST_ELEMENTS_RO(next
,ln
,gn
)) {
164 if (gn
->type
== END_GN
) {
165 struct graph_node
*curr
= copy_node(start
);
166 curr
->arg
= XSTRDUP(MTYPE_CMD_TOKENS
, vector_slot(vline
, n
));
167 // initialize a new argument list
169 argv
->del
= &free_nodelist
;
171 listnode_add(argv
, curr
);
173 listnode_add(argv
, copy_node(gn
));
179 // no END_GN found, free resources and return null
184 // otherwise recurse on all nexthops
185 struct list
*bestmatch
= NULL
;
186 for (ALL_LIST_ELEMENTS_RO(next
,ln
,gn
))
188 if (gn
->type
== END_GN
) // skip END_GN since we aren't at end of input
191 // get the result of the next node
192 struct list
*result
= match_command_r (gn
, vline
, n
+1);
194 // compare to our current best match, and save if it's better
197 int currprec
= score_precedence (listgetdata(listhead(bestmatch
)));
198 int rsltprec
= score_precedence (gn
);
199 if (currprec
< rsltprec
)
200 list_delete (result
);
201 if (currprec
> rsltprec
) {
202 list_delete (bestmatch
);
205 if (currprec
== rsltprec
) {
206 list_delete (bestmatch
);
207 list_delete (result
);
219 listnode_add(argv
, start
);
220 list_add_list(argv
, bestmatch
);
221 list_free (bestmatch
);
223 start
->arg
= XSTRDUP(MTYPE_CMD_TOKENS
, vector_slot(vline
, n
));
231 match_command_complete (struct graph_node
*start
, const char *line
, enum filter_type filter
)
233 enum match_type minmatch
= filter
+ 1;
235 // vectorize command line
236 vector vline
= cmd_make_strvec (line
);
238 // pointer to next input token to match
241 struct list
*current
= list_new(), // current nodes to match input token against
242 *matched
= list_new(), // current nodes that match the input token
243 *next
= list_new(); // possible next hops to current input token
245 // pointers used for iterating lists
246 struct graph_node
*gn
;
247 struct listnode
*node
;
249 // add all children of start node to list
250 add_nexthops(next
, start
);
253 for (idx
= 0; idx
< vector_active(vline
) && next
->count
> 0; idx
++)
259 token
= vector_slot(vline
, idx
);
261 list_delete_all_node(matched
);
263 for (ALL_LIST_ELEMENTS_RO(current
,node
,gn
))
265 if (match_token(gn
, token
, filter
) >= minmatch
) {
266 listnode_add(matched
, gn
);
267 add_nexthops(next
, gn
);
273 * -----------------------------------------------------------------
274 * token = last input token processed
275 * idx = index in `command` of last token processed
276 * current = set of all transitions from the previous input token
277 * matched = set of all nodes reachable with current input
278 * next = set of all nodes reachable from all nodes in `matched`
283 cmd_free_strvec(vline
);
289 * Adds all children that are reachable by one parser hop
290 * to the given list. NUL_GN, SELECTOR_GN, and OPTION_GN
291 * nodes are treated as transparent.
293 * @param[out] l the list to add the children to
294 * @param[in] node the node to get the children of
295 * @return the number of children added to the list
298 add_nexthops(struct list
*l
, struct graph_node
*node
)
301 struct graph_node
*child
;
302 for (unsigned int i
= 0; i
< vector_active(node
->children
); i
++)
304 child
= vector_slot(node
->children
, i
);
305 switch (child
->type
) {
309 added
+= add_nexthops(l
, child
);
312 listnode_add(l
, child
);
320 /* matching utility functions */
323 * Determines the minimum acceptable matching level
324 * for a given node type that can be accepted as a
325 * full match. Used for things like abbreviating
326 * commands, e.g. `conf t`.
328 static enum match_type
329 min_match_level(enum node_type type
)
340 score_precedence (struct graph_node
*node
)
344 // these should be mutually exclusive,
362 static enum match_type
363 match_token (struct graph_node
*node
, char *token
, enum filter_type filter
)
365 switch (node
->type
) {
367 return match_word (node
, token
, filter
);
369 return match_ipv4 (token
);
371 return match_ipv4_prefix (token
);
373 return match_ipv6 (token
);
375 return match_ipv6_prefix (token
);
377 return match_range (node
, token
);
379 return match_number (node
, token
);
381 return match_variable (node
, token
);
388 #define IPV4_ADDR_STR "0123456789."
389 #define IPV4_PREFIX_STR "0123456789./"
391 static enum match_type
392 match_ipv4 (const char *str
)
395 int dots
= 0, nums
= 0;
403 memset (buf
, 0, sizeof (buf
));
412 if (*(str
+ 1) == '.')
415 if (*(str
+ 1) == '\0')
421 if (!isdigit ((int) *str
))
430 strncpy (buf
, sp
, str
- sp
);
431 if (atoi (buf
) > 255)
448 static enum match_type
449 match_ipv4_prefix (const char *str
)
460 memset (buf
, 0, sizeof (buf
));
462 while (*str
!= '\0' && *str
!= '/')
469 if (*(str
+ 1) == '.' || *(str
+ 1) == '/')
472 if (*(str
+ 1) == '\0')
479 if (!isdigit ((int) *str
))
488 strncpy (buf
, sp
, str
- sp
);
489 if (atoi (buf
) > 255)
496 if (*(str
+ 1) == '\0')
502 else if (*str
== '\0')
515 if (!isdigit ((int) *str
))
528 #define IPV6_ADDR_STR "0123456789abcdefABCDEF:."
529 #define IPV6_PREFIX_STR "0123456789abcdefABCDEF:./"
531 static enum match_type
532 match_ipv6 (const char *str
)
534 struct sockaddr_in6 sin6_dummy
;
540 if (strspn (str
, IPV6_ADDR_STR
) != strlen (str
))
543 ret
= inet_pton(AF_INET6
, str
, &sin6_dummy
.sin6_addr
);
551 static enum match_type
552 match_ipv6_prefix (const char *str
)
554 struct sockaddr_in6 sin6_dummy
;
555 const char *delim
= "/\0";
556 char *dupe
, *prefix
, *mask
, *context
, *endptr
;
562 if (strspn (str
, IPV6_PREFIX_STR
) != strlen (str
))
565 /* tokenize to address + mask */
566 dupe
= XMALLOC(MTYPE_TMP
, strlen(str
)+1);
567 strncpy(dupe
, str
, strlen(str
)+1);
568 prefix
= strtok_r(dupe
, delim
, &context
);
569 mask
= strtok_r(NULL
, delim
, &context
);
574 /* validate prefix */
575 if (inet_pton(AF_INET6
, prefix
, &sin6_dummy
.sin6_addr
) != 1)
579 nmask
= strtol (mask
, &endptr
, 10);
580 if (*endptr
!= '\0' || nmask
< 0 || nmask
> 128)
583 XFREE(MTYPE_TMP
, dupe
);
589 static enum match_type
590 match_range (struct graph_node
*rangenode
, const char *str
)
598 val
= strtoll (str
, &endptr
, 10);
603 if (val
< rangenode
->min
|| val
> rangenode
->max
)
609 static enum match_type
610 match_word(struct graph_node
*wordnode
,
612 enum filter_type filter
)
614 if (filter
== FILTER_RELAXED
)
616 if (!word
|| !strlen(word
))
618 else if (!strncmp(wordnode
->text
, word
, strlen(word
)))
619 return !strcmp(wordnode
->text
, word
) ? exact_match
: partly_match
;
628 return !strcmp(wordnode
->text
, word
) ? exact_match
: no_match
;
632 static enum match_type
633 match_number(struct graph_node
*numnode
, const char *word
)
635 if (!strcmp("\0", word
)) return no_match
;
637 long num
= strtol(word
, &endptr
, 10);
638 if (endptr
!= '\0') return no_match
;
639 return num
== numnode
->value
? exact_match
: no_match
;
642 #define VARIABLE_ALPHABET "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"
644 static enum match_type
645 match_variable(struct graph_node
*varnode
, const char *word
)
647 return strlen(word
) == strspn(word
, VARIABLE_ALPHABET
) && isalpha(word
[0]) ?
648 exact_match
: no_match
;