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