]> git.proxmox.com Git - mirror_frr.git/blob - lib/command_match.c
lib: Check match level when calculating completions
[mirror_frr.git] / lib / command_match.c
1 /*
2 * Input matching routines for CLI backend.
3 *
4 * --
5 * Copyright (C) 2016 Cumulus Networks, Inc.
6 *
7 * This file is part of GNU Zebra.
8 *
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
12 * later version.
13 *
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.
18 *
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
22 * 02111-1307, USA.
23 */
24
25 #include <zebra.h>
26
27 #include "command_match.h"
28 #include "command_parse.h"
29 #include "memory.h"
30
31 DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command Tokens")
32
33 /* matcher helper prototypes */
34 static int
35 add_nexthops (struct list *, struct graph_node *);
36
37 static struct list *
38 command_match_r (struct graph_node *, vector, unsigned int);
39
40 static int
41 score_precedence (enum cmd_token_type);
42
43 static enum match_type
44 min_match_level (enum cmd_token_type);
45
46 static void
47 del_arglist (struct list *);
48
49 static struct cmd_token *
50 disambiguate_tokens (struct cmd_token *, struct cmd_token *, char *);
51
52 static struct list *
53 disambiguate (struct list *, struct list *, vector, unsigned int);
54
55 int
56 compare_completions (const void *, const void *);
57
58 /* token matcher prototypes */
59 static enum match_type
60 match_token (struct cmd_token *, char *);
61
62 static enum match_type
63 match_ipv4 (const char *);
64
65 static enum match_type
66 match_ipv4_prefix (const char *);
67
68 static enum match_type
69 match_ipv6 (const char *);
70
71 static enum match_type
72 match_ipv6_prefix (const char *);
73
74 static enum match_type
75 match_range (struct cmd_token *, const char *);
76
77 static enum match_type
78 match_word (struct cmd_token *, const char *);
79
80 static enum match_type
81 match_variable (struct cmd_token *, const char *);
82
83 /* matching functions */
84 static enum matcher_rv matcher_rv;
85
86 enum matcher_rv
87 command_match (struct graph *cmdgraph,
88 vector vline,
89 struct list **argv,
90 struct cmd_element **el)
91 {
92 matcher_rv = MATCHER_NO_MATCH;
93
94 // prepend a dummy token to match that pesky start node
95 vector vvline = vector_init (vline->alloced + 1);
96 vector_set_index (vvline, 0, (void *) XSTRDUP (MTYPE_TMP, "dummy"));
97 memcpy (vvline->index + 1, vline->index, sizeof (void *) * vline->alloced);
98 vvline->active = vline->active + 1;
99
100 struct graph_node *start = vector_slot (cmdgraph->nodes, 0);
101 if ((*argv = command_match_r (start, vvline, 0))) // successful match
102 {
103 struct listnode *head = listhead (*argv);
104 struct listnode *tail = listtail (*argv);
105
106 // delete dummy start node
107 del_cmd_token ((struct cmd_token *) head->data);
108 list_delete_node (*argv, head);
109
110 // get cmd_element out of list tail
111 *el = listgetdata (tail);
112 list_delete_node (*argv, tail);
113
114 // now argv is an ordered list of cmd_token matching the user
115 // input, with each cmd_token->arg holding the corresponding input
116 assert (*el);
117 }
118
119 #ifdef TRACE_MATCHER
120 if (!*el)
121 fprintf (stdout, "No match\n");
122 else
123 fprintf (stdout, "Matched command\n->string %s\n->desc %s\n", (*el)->string, (*el)->doc);
124 #endif
125
126 // free the leader token we alloc'd
127 XFREE (MTYPE_TMP, vector_slot (vvline, 0));
128 // free vector
129 vector_free (vvline);
130
131 return matcher_rv;
132 }
133
134 /**
135 * Builds an argument list given a DFA and a matching input line.
136 *
137 * First the function determines if the node it is passed matches the first
138 * token of input. If it does not, it returns NULL (MATCHER_NO_MATCH). If it
139 * does match, then it saves the input token as the head of an argument list.
140 *
141 * The next step is to see if there is further input in the input line. If
142 * there is not, the current node's children are searched to see if any of them
143 * are leaves (type END_TKN). If this is the case, then the bottom of the
144 * recursion stack has been reached, the leaf is pushed onto the argument list,
145 * the current node is pushed, and the resulting argument list is
146 * returned (MATCHER_OK). If it is not the case, NULL is returned, indicating
147 * that there is no match for the input along this path (MATCHER_INCOMPLETE).
148 *
149 * If there is further input, then the function recurses on each of the current
150 * node's children, passing them the input line minus the token that was just
151 * matched. For each child, the return value of the recursive call is
152 * inspected. If it is null, then there is no match for the input along the
153 * subgraph headed by that child. If it is not null, then there is at least one
154 * input match in that subgraph (more on this in a moment).
155 *
156 * If a recursive call on a child returns a non-null value, then it has matched
157 * the input given it on the subgraph that starts with that child. However, due
158 * to the flexibility of the grammar, it is sometimes the case that two or more
159 * child graphs match the same input (two or more of the recursive calls have
160 * non-NULL return values). This is not a valid state, since only one true
161 * match is possible. In order to resolve this conflict, the function keeps a
162 * reference to the child node that most specifically matches the input. This
163 * is done by assigning each node type a precedence. If a child is found to
164 * match the remaining input, then the precedence values of the current
165 * best-matching child and this new match are compared. The node with higher
166 * precedence is kept, and the other match is discarded. Due to the recursive
167 * nature of this function, it is only necessary to compare the precedence of
168 * immediate children, since all subsequent children will already have been
169 * disambiguated in this way.
170 *
171 * In the event that two children are found to match with the same precedence,
172 * then the input is ambiguous for the passed cmd_element and NULL is returned.
173 *
174 * The ultimate return value is an ordered linked list of nodes that comprise
175 * the best match for the command, each with their `arg` fields pointing to the
176 * matching token string.
177 *
178 * @param[in] start the start node.
179 * @param[in] vline the vectorized input line.
180 * @param[in] n the index of the first input token.
181 */
182 static struct list *
183 command_match_r (struct graph_node *start, vector vline, unsigned int n)
184 {
185 assert (n < vector_active (vline));
186
187 // get the minimum match level that can count as a full match
188 struct cmd_token *token = start->data;
189 enum match_type minmatch = min_match_level (token->type);
190
191 // get the current operating input token
192 char *input_token = vector_slot (vline, n);
193
194 #ifdef TRACE_MATCHER
195 fprintf (stdout, "\"%s\" matches \"%s\" (%d) ? ", input_token, token->text, token->type);
196 switch (match_token (token, input_token))
197 {
198 case trivial_match:
199 fprintf (stdout, "trivial_match ");
200 break;
201 case no_match:
202 fprintf (stdout, "no_match ");
203 break;
204 case partly_match:
205 fprintf (stdout, "partly_match ");
206 break;
207 case exact_match:
208 fprintf (stdout, "exact_match ");
209 break;
210 }
211 fprintf (stdout, "(minimum: %d)\n", minmatch);
212 #endif
213
214 // if we don't match this node, die
215 if (match_token (token, input_token) < minmatch)
216 return NULL;
217
218 // pointers for iterating linklist
219 struct listnode *ln;
220 struct graph_node *gn;
221
222 // get all possible nexthops
223 struct list *next = list_new();
224 add_nexthops (next, start);
225
226 // determine the best match
227 int ambiguous = 0;
228 struct list *currbest = NULL;
229 for (ALL_LIST_ELEMENTS_RO (next,ln,gn))
230 {
231 // if we've matched all input we're looking for END_TKN
232 if (n+1 == vector_active (vline))
233 {
234 struct cmd_token *tok = gn->data;
235 if (tok->type == END_TKN)
236 {
237 currbest = list_new();
238 // node should have one child node with the element
239 struct graph_node *leaf = vector_slot (gn->to, 0);
240 // last node in the list will hold the cmd_element;
241 // this is important because list_delete() expects
242 // that all nodes have the same data type, so when
243 // deleting this list the last node must be
244 // manually deleted
245 struct cmd_element *el = leaf->data;
246 listnode_add (currbest, copy_cmd_element (el));
247 currbest->del = (void (*)(void *)) &del_cmd_token;
248 break;
249 }
250 else continue;
251 }
252
253 // else recurse on candidate child node
254 struct list *result = command_match_r (gn, vline, n+1);
255
256 // save the best match
257 if (result && currbest)
258 {
259 // pick the best of two matches
260 struct list *newbest = disambiguate (currbest, result, vline, n+1);
261 // set ambiguity flag
262 ambiguous = !newbest || (ambiguous && newbest == currbest);
263 // delete the unnecessary result
264 struct list *todelete = ((newbest && newbest == result) ? currbest : result);
265 del_arglist (todelete);
266
267 currbest = newbest ? newbest : currbest;
268 }
269 else if (result)
270 currbest = result;
271 }
272
273 if (currbest)
274 {
275 if (ambiguous)
276 {
277 del_arglist (currbest);
278 currbest = NULL;
279 matcher_rv = MATCHER_AMBIGUOUS;
280 }
281 else
282 {
283 // copy token, set arg and prepend to currbest
284 struct cmd_token *token = start->data;
285 struct cmd_token *copy = copy_cmd_token (token);
286 copy->arg = XSTRDUP (MTYPE_CMD_TOKENS, input_token);
287 listnode_add_before (currbest, currbest->head, copy);
288 matcher_rv = MATCHER_OK;
289 }
290 }
291 else if (n+1 == vector_active (vline) && matcher_rv == MATCHER_NO_MATCH)
292 matcher_rv = MATCHER_INCOMPLETE;
293
294 // cleanup
295 list_delete (next);
296
297 return currbest;
298 }
299
300 enum matcher_rv
301 command_complete (struct graph *graph,
302 vector vline,
303 struct list **completions)
304 {
305 // pointer to next input token to match
306 char *input_token;
307
308 struct list *current = list_new(), // current nodes to match input token against
309 *next = list_new(); // possible next hops after current input token
310
311 // pointers used for iterating lists
312 struct graph_node *gn;
313 struct listnode *node;
314
315 // add all children of start node to list
316 struct graph_node *start = vector_slot (graph->nodes, 0);
317 add_nexthops (next, start);
318
319 unsigned int idx;
320 for (idx = 0; idx < vector_active (vline) && next->count > 0; idx++)
321 {
322 list_delete (current);
323 current = next;
324 next = list_new();
325
326 input_token = vector_slot (vline, idx);
327
328 for (ALL_LIST_ELEMENTS_RO (current,node,gn))
329 {
330 struct cmd_token *token = gn->data;
331 enum match_type minmatch = min_match_level (token->type);
332 #ifdef TRACE_MATCHER
333 fprintf (stdout, "\"%s\" matches \"%s\" (%d) ? ", input_token, token->text, token->type);
334 #endif
335
336 switch (match_token (token, input_token))
337 {
338 case trivial_match:
339 #ifdef TRACE_MATCHER
340 fprintf (stdout, "trivial_match\n");
341 #endif
342 case partly_match:
343 #ifdef TRACE_MATCHER
344 fprintf (stdout, "partly_match\n");
345 #endif
346 if (idx == vector_active (vline) - 1)
347 {
348 listnode_add (next, gn);
349 break;
350 }
351 if (minmatch > partly_match)
352 break;
353 case exact_match:
354 #ifdef TRACE_MATCHER
355 fprintf (stdout, "exact_match\n");
356 #endif
357 add_nexthops (next, gn);
358 break;
359 default:
360 #ifdef TRACE_MATCHER
361 fprintf (stdout, "no_match\n");
362 #endif
363 break;
364 }
365 }
366 }
367
368 /* Variable summary
369 * -----------------------------------------------------------------
370 * token = last input token processed
371 * idx = index in `command` of last token processed
372 * current = set of all transitions from the previous input token
373 * next = set of all nodes reachable from all nodes in `matched`
374 */
375
376 matcher_rv =
377 idx == vector_active(vline) && next->count ?
378 MATCHER_OK :
379 MATCHER_NO_MATCH;
380
381 // extract cmd_token into list
382 *completions = list_new ();
383 for (ALL_LIST_ELEMENTS_RO (next,node,gn))
384 listnode_add (*completions, gn->data);
385
386 list_delete (current);
387 list_delete (next);
388
389 return matcher_rv;
390 }
391
392 /**
393 * Adds all children that are reachable by one parser hop to the given list.
394 * NUL_TKN, SELECTOR_TKN, and OPTION_TKN nodes are treated as transparent.
395 *
396 * @param[in] list to add the nexthops to
397 * @param[in] node to start calculating nexthops from
398 * @return the number of children added to the list
399 */
400 static int
401 add_nexthops (struct list *list, struct graph_node *node)
402 {
403 int added = 0;
404 struct graph_node *child;
405 for (unsigned int i = 0; i < vector_active (node->to); i++)
406 {
407 child = vector_slot (node->to, i);
408 struct cmd_token *token = child->data;
409 switch (token->type)
410 {
411 case OPTION_TKN:
412 case SELECTOR_TKN:
413 case NUL_TKN:
414 added += add_nexthops (list, child);
415 break;
416 default:
417 listnode_add (list, child);
418 added++;
419 }
420 }
421
422 return added;
423 }
424
425 /**
426 * Determines the node types for which a partial match may count as a full
427 * match. Enables command abbrevations.
428 *
429 * @param[in] type node type
430 * @return minimum match level needed to for a token to fully match
431 */
432 static enum match_type
433 min_match_level (enum cmd_token_type type)
434 {
435 switch (type)
436 {
437 // anything matches a start node, for the sake of recursion
438 case START_TKN:
439 return no_match;
440 // allowing words to partly match enables command abbreviation
441 case WORD_TKN:
442 return partly_match;
443 default:
444 return exact_match;
445 }
446 }
447
448 /**
449 * Assigns precedence scores to node types.
450 *
451 * @param[in] type node type to score
452 * @return precedence score
453 */
454 static int
455 score_precedence (enum cmd_token_type type)
456 {
457 switch (type)
458 {
459 // some of these are mutually exclusive, so they share
460 // the same precedence value
461 case IPV4_TKN:
462 case IPV4_PREFIX_TKN:
463 case IPV6_TKN:
464 case IPV6_PREFIX_TKN:
465 case RANGE_TKN:
466 return 2;
467 case WORD_TKN:
468 return 3;
469 case VARIABLE_TKN:
470 return 4;
471 default:
472 return 10;
473 }
474 }
475
476 /**
477 * Picks the better of two possible matches for a token.
478 *
479 * @param[in] first candidate node matching token
480 * @param[in] second candidate node matching token
481 * @param[in] token the token being matched
482 * @return the best-matching node, or NULL if the two are entirely ambiguous
483 */
484 static struct cmd_token *
485 disambiguate_tokens (struct cmd_token *first,
486 struct cmd_token *second,
487 char *input_token)
488 {
489 // if the types are different, simply go off of type precedence
490 if (first->type != second->type)
491 {
492 int firstprec = score_precedence (first->type);
493 int secndprec = score_precedence (second->type);
494 if (firstprec != secndprec)
495 return firstprec < secndprec ? first : second;
496 else
497 return NULL;
498 }
499
500 // if they're the same, return the more exact match
501 enum match_type fmtype = match_token (first, input_token);
502 enum match_type smtype = match_token (second, input_token);
503 if (fmtype != smtype)
504 return fmtype > smtype ? first : second;
505
506 return NULL;
507 }
508
509 /**
510 * Picks the better of two possible matches for an input line.
511 *
512 * @param[in] first candidate list of cmd_token matching vline
513 * @param[in] second candidate list of cmd_token matching vline
514 * @param[in] vline the input line being matched
515 * @param[in] n index into vline to start comparing at
516 * @return the best-matching list, or NULL if the two are entirely ambiguous
517 */
518 static struct list *
519 disambiguate (struct list *first,
520 struct list *second,
521 vector vline,
522 unsigned int n)
523 {
524 // doesn't make sense for these to be inequal length
525 assert (first->count == second->count);
526 assert (first->count == vector_active (vline) - n+1);
527
528 struct listnode *fnode = listhead (first),
529 *snode = listhead (second);
530 struct cmd_token *ftok = listgetdata (fnode),
531 *stok = listgetdata (snode),
532 *best = NULL;
533
534 // compare each token, if one matches better use that one
535 for (unsigned int i = n; i < vector_active (vline); i++)
536 {
537 char *token = vector_slot(vline, i);
538 if ((best = disambiguate_tokens (ftok, stok, token)))
539 return best == ftok ? first : second;
540 fnode = listnextnode (fnode);
541 snode = listnextnode (snode);
542 ftok = listgetdata (fnode);
543 stok = listgetdata (snode);
544 }
545
546 return NULL;
547 }
548
549 /*
550 * Deletion function for arglist.
551 *
552 * Since list->del for arglists expects all listnode->data to hold cmd_token,
553 * but arglists have cmd_element as the data for the tail, this function
554 * manually deletes the tail before deleting the rest of the list as usual.
555 *
556 * @param list the arglist to delete
557 */
558 static void
559 del_arglist (struct list *list)
560 {
561 // manually delete last node
562 struct listnode *tail = listtail (list);
563 del_cmd_element (tail->data);
564 tail->data = NULL;
565 list_delete_node (list, tail);
566
567 // delete the rest of the list as usual
568 list_delete (list);
569 }
570
571 /*---------- token level matching functions ----------*/
572
573 static enum match_type
574 match_token (struct cmd_token *token, char *input_token)
575 {
576 // nothing trivially matches everything
577 if (!input_token)
578 return trivial_match;
579
580 switch (token->type) {
581 case WORD_TKN:
582 return match_word (token, input_token);
583 case IPV4_TKN:
584 return match_ipv4 (input_token);
585 case IPV4_PREFIX_TKN:
586 return match_ipv4_prefix (input_token);
587 case IPV6_TKN:
588 return match_ipv6 (input_token);
589 case IPV6_PREFIX_TKN:
590 return match_ipv6_prefix (input_token);
591 case RANGE_TKN:
592 return match_range (token, input_token);
593 case VARIABLE_TKN:
594 return match_variable (token, input_token);
595 case END_TKN:
596 default:
597 return no_match;
598 }
599 }
600
601 #define IPV4_ADDR_STR "0123456789."
602 #define IPV4_PREFIX_STR "0123456789./"
603
604 static enum match_type
605 match_ipv4 (const char *str)
606 {
607 const char *sp;
608 int dots = 0, nums = 0;
609 char buf[4];
610
611 for (;;)
612 {
613 memset (buf, 0, sizeof (buf));
614 sp = str;
615 while (*str != '\0')
616 {
617 if (*str == '.')
618 {
619 if (dots >= 3)
620 return no_match;
621
622 if (*(str + 1) == '.')
623 return no_match;
624
625 if (*(str + 1) == '\0')
626 return partly_match;
627
628 dots++;
629 break;
630 }
631 if (!isdigit ((int) *str))
632 return no_match;
633
634 str++;
635 }
636
637 if (str - sp > 3)
638 return no_match;
639
640 strncpy (buf, sp, str - sp);
641 if (atoi (buf) > 255)
642 return no_match;
643
644 nums++;
645
646 if (*str == '\0')
647 break;
648
649 str++;
650 }
651
652 if (nums < 4)
653 return partly_match;
654
655 return exact_match;
656 }
657
658 static enum match_type
659 match_ipv4_prefix (const char *str)
660 {
661 const char *sp;
662 int dots = 0;
663 char buf[4];
664
665 for (;;)
666 {
667 memset (buf, 0, sizeof (buf));
668 sp = str;
669 while (*str != '\0' && *str != '/')
670 {
671 if (*str == '.')
672 {
673 if (dots == 3)
674 return no_match;
675
676 if (*(str + 1) == '.' || *(str + 1) == '/')
677 return no_match;
678
679 if (*(str + 1) == '\0')
680 return partly_match;
681
682 dots++;
683 break;
684 }
685
686 if (!isdigit ((int) *str))
687 return no_match;
688
689 str++;
690 }
691
692 if (str - sp > 3)
693 return no_match;
694
695 strncpy (buf, sp, str - sp);
696 if (atoi (buf) > 255)
697 return no_match;
698
699 if (dots == 3)
700 {
701 if (*str == '/')
702 {
703 if (*(str + 1) == '\0')
704 return partly_match;
705
706 str++;
707 break;
708 }
709 else if (*str == '\0')
710 return partly_match;
711 }
712
713 if (*str == '\0')
714 return partly_match;
715
716 str++;
717 }
718
719 sp = str;
720 while (*str != '\0')
721 {
722 if (!isdigit ((int) *str))
723 return no_match;
724
725 str++;
726 }
727
728 if (atoi (sp) > 32)
729 return no_match;
730
731 return exact_match;
732 }
733
734 #ifdef HAVE_IPV6
735 #define IPV6_ADDR_STR "0123456789abcdefABCDEF:."
736 #define IPV6_PREFIX_STR "0123456789abcdefABCDEF:./"
737
738 static enum match_type
739 match_ipv6 (const char *str)
740 {
741 struct sockaddr_in6 sin6_dummy;
742 int ret;
743
744 if (strspn (str, IPV6_ADDR_STR) != strlen (str))
745 return no_match;
746
747 ret = inet_pton(AF_INET6, str, &sin6_dummy.sin6_addr);
748
749 if (ret == 1)
750 return exact_match;
751
752 return no_match;
753 }
754
755 static enum match_type
756 match_ipv6_prefix (const char *str)
757 {
758 struct sockaddr_in6 sin6_dummy;
759 const char *delim = "/\0";
760 char *tofree, *dupe, *prefix, *mask, *endptr;
761 int nmask = -1;
762
763 if (strspn (str, IPV6_PREFIX_STR) != strlen (str))
764 return no_match;
765
766 /* tokenize to prefix + mask */
767 tofree = dupe = XSTRDUP (MTYPE_TMP, str);
768 prefix = strsep (&dupe, delim);
769 mask = dupe;
770
771 /* validate prefix */
772 if (inet_pton (AF_INET6, prefix, &sin6_dummy.sin6_addr) != 1)
773 {
774 XFREE (MTYPE_TMP, tofree);
775 return no_match;
776 }
777
778 /* validate mask */
779 if (!mask)
780 {
781 XFREE (MTYPE_TMP, tofree);
782 return partly_match;
783 }
784
785 nmask = strtoimax (mask, &endptr, 10);
786 if (*endptr != '\0' || nmask < 0 || nmask > 128)
787 {
788 XFREE (MTYPE_TMP, tofree);
789 return no_match;
790 }
791
792 XFREE (MTYPE_TMP, tofree);
793 return exact_match;
794 }
795 #endif
796
797 static enum match_type
798 match_range (struct cmd_token *token, const char *str)
799 {
800 assert (token->type == RANGE_TKN);
801
802 char *endptr = NULL;
803 long long val;
804
805 val = strtoll (str, &endptr, 10);
806 if (*endptr != '\0')
807 return no_match;
808
809 if (val < token->min || val > token->max)
810 return no_match;
811 else
812 return exact_match;
813 }
814
815 static enum match_type
816 match_word (struct cmd_token *token, const char *word)
817 {
818 assert (token->type == WORD_TKN);
819
820 // if the passed token is 0 length, partly match
821 if (!strlen(word))
822 return partly_match;
823
824 // if the passed token is strictly a prefix of the full word, partly match
825 if (strlen (word) < strlen (token->text))
826 return !strncmp (token->text, word, strlen (word)) ?
827 partly_match :
828 no_match;
829
830 // if they are the same length and exactly equal, exact match
831 else if (strlen (word) == strlen (token->text))
832 return !strncmp (token->text, word, strlen (word)) ? exact_match : no_match;
833
834 return no_match;
835 }
836
837 #define VARIABLE_ALPHABET \
838 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890:/."
839
840 static enum match_type
841 match_variable (struct cmd_token *token, const char *word)
842 {
843 assert (token->type == VARIABLE_TKN);
844
845 return strlen (word) == strspn(word, VARIABLE_ALPHABET) ?
846 exact_match : no_match;
847 }