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