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