]> git.proxmox.com Git - mirror_frr.git/blob - lib/grammar_sandbox.c
vtysh: return non-zero for configuration failures
[mirror_frr.git] / lib / grammar_sandbox.c
1 /*
2 * Testing shim and API examples for the new CLI backend.
3 *
4 * This unit defines a number of commands in the old engine that can
5 * be used to test and interact with the new engine.
6 * --
7 * Copyright (C) 2016 Cumulus Networks, Inc.
8 *
9 * This file is part of GNU Zebra.
10 *
11 * GNU Zebra is free software; you can redistribute it and/or modify it
12 * under the terms of the GNU General Public License as published by the
13 * Free Software Foundation; either version 2, or (at your option) any
14 * later version.
15 *
16 * GNU Zebra is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License along
22 * with this program; see the file COPYING; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24 */
25
26 #include "command.h"
27 #include "memory_vty.h"
28 #include "graph.h"
29 #include "linklist.h"
30 #include "command_match.h"
31
32 #define GRAMMAR_STR "CLI grammar sandbox\n"
33
34 DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command desc")
35
36 #define MAXDEPTH 64
37
38 /** headers **/
39 void
40 grammar_sandbox_init (void);
41 void
42 pretty_print_graph (struct vty *vty, struct graph_node *, int, int, struct graph_node **, size_t);
43 static void
44 pretty_print_dot (FILE *ofd, unsigned opts, struct graph_node *start,
45 struct graph_node **stack, size_t stackpos,
46 struct graph_node **visited, size_t *visitpos);
47 void
48 init_cmdgraph (struct vty *, struct graph **);
49
50 /** shim interface commands **/
51 struct graph *nodegraph = NULL, *nodegraph_free = NULL;
52
53 #define check_nodegraph() \
54 do { if (!nodegraph) { \
55 vty_outln(vty, "nodegraph not initialized"); \
56 return CMD_WARNING; \
57 } } while (0)
58
59 DEFUN (grammar_test,
60 grammar_test_cmd,
61 "grammar parse LINE...",
62 GRAMMAR_STR
63 "parse a command\n"
64 "command to pass to new parser\n")
65 {
66 check_nodegraph();
67
68 int idx_command = 2;
69 // make a string from tokenized command line
70 char *command = argv_concat (argv, argc, idx_command);
71
72 // create cmd_element for parser
73 struct cmd_element *cmd = XCALLOC (MTYPE_CMD_TOKENS, sizeof (struct cmd_element));
74 cmd->string = command;
75 cmd->doc = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n";
76 cmd->func = NULL;
77
78 // parse the command and install it into the command graph
79 struct graph *graph = graph_new();
80 struct cmd_token *token = cmd_token_new (START_TKN, CMD_ATTR_NORMAL, NULL, NULL);
81 graph_new_node (graph, token, (void (*)(void *)) &cmd_token_del);
82
83 cmd_graph_parse (graph, cmd);
84 cmd_graph_merge (nodegraph, graph, +1);
85
86 return CMD_SUCCESS;
87 }
88
89 DEFUN (grammar_test_complete,
90 grammar_test_complete_cmd,
91 "grammar complete COMMAND...",
92 GRAMMAR_STR
93 "attempt to complete input on DFA\n"
94 "command to complete\n")
95 {
96 check_nodegraph();
97
98 int idx_command = 2;
99 char *cmdstr = argv_concat (argv, argc, idx_command);
100 if (!cmdstr)
101 return CMD_SUCCESS;
102
103 vector command = cmd_make_strvec (cmdstr);
104 if (!command)
105 {
106 XFREE (MTYPE_TMP, cmdstr);
107 return CMD_SUCCESS;
108 }
109
110 // generate completions of user input
111 struct list *completions;
112 enum matcher_rv result = command_complete (nodegraph, command, &completions);
113
114 // print completions or relevant error message
115 if (!MATCHER_ERROR(result))
116 {
117 vector comps = completions_to_vec (completions);
118 struct cmd_token *tkn;
119
120 // calculate length of longest tkn->text in completions
121 unsigned int width = 0, i = 0;
122 for (i = 0; i < vector_active (comps); i++) {
123 tkn = vector_slot (comps, i);
124 unsigned int len = strlen (tkn->text);
125 width = len > width ? len : width;
126 }
127
128 // print completions
129 for (i = 0; i < vector_active (comps); i++) {
130 tkn = vector_slot (comps, i);
131 vty_outln (vty, " %-*s %s", width, tkn->text, tkn->desc);
132 }
133
134 for (i = 0; i < vector_active (comps); i++)
135 cmd_token_del ((struct cmd_token *) vector_slot (comps, i));
136 vector_free (comps);
137 }
138 else
139 vty_outln (vty, "%% No match");
140
141 // free resources
142 list_delete (completions);
143 cmd_free_strvec (command);
144 XFREE (MTYPE_TMP, cmdstr);
145
146 return CMD_SUCCESS;
147 }
148
149 DEFUN (grammar_test_match,
150 grammar_test_match_cmd,
151 "grammar match COMMAND...",
152 GRAMMAR_STR
153 "attempt to match input on DFA\n"
154 "command to match\n")
155 {
156 check_nodegraph();
157
158 int idx_command = 2;
159 if (argv[2]->arg[0] == '#')
160 return CMD_SUCCESS;
161
162 char *cmdstr = argv_concat(argv, argc, idx_command);
163 if (!cmdstr)
164 return CMD_SUCCESS;
165 vector command = cmd_make_strvec (cmdstr);
166 if (!command)
167 {
168 XFREE (MTYPE_TMP, cmdstr);
169 return CMD_SUCCESS;
170 }
171
172 struct list *argvv = NULL;
173 const struct cmd_element *element = NULL;
174 enum matcher_rv result = command_match (nodegraph, command, &argvv, &element);
175
176 // print completions or relevant error message
177 if (element)
178 {
179 vty_outln (vty, "Matched: %s", element->string);
180 struct listnode *ln;
181 struct cmd_token *token;
182 for (ALL_LIST_ELEMENTS_RO(argvv,ln,token))
183 vty_outln (vty, "%s -- %s", token->text, token->arg);
184
185 vty_outln (vty, "func: %p", element->func);
186
187 list_delete (argvv);
188 }
189 else {
190 assert(MATCHER_ERROR(result));
191 switch (result) {
192 case MATCHER_NO_MATCH:
193 vty_outln (vty, "%% Unknown command");
194 break;
195 case MATCHER_INCOMPLETE:
196 vty_outln (vty, "%% Incomplete command");
197 break;
198 case MATCHER_AMBIGUOUS:
199 vty_outln (vty, "%% Ambiguous command");
200 break;
201 default:
202 vty_outln (vty, "%% Unknown error");
203 break;
204 }
205 }
206
207 // free resources
208 cmd_free_strvec (command);
209 XFREE (MTYPE_TMP, cmdstr);
210
211 return CMD_SUCCESS;
212 }
213
214 /**
215 * Testing shim to test docstrings
216 */
217 DEFUN (grammar_test_doc,
218 grammar_test_doc_cmd,
219 "grammar test docstring",
220 GRAMMAR_STR
221 "Test function for docstring\n"
222 "Command end\n")
223 {
224 check_nodegraph();
225
226 // create cmd_element with docstring
227 struct cmd_element *cmd = XCALLOC (MTYPE_CMD_TOKENS, sizeof (struct cmd_element));
228 cmd->string = XSTRDUP (MTYPE_CMD_TOKENS, "test docstring <example|selector follow> (1-255) end VARIABLE [OPTION|set lol] . VARARG");
229 cmd->doc = XSTRDUP (MTYPE_CMD_TOKENS,
230 "Test stuff\n"
231 "docstring thing\n"
232 "first example\n"
233 "second example\n"
234 "follow\n"
235 "random range\n"
236 "end thingy\n"
237 "variable\n"
238 "optional variable\n"
239 "optional set\n"
240 "optional lol\n"
241 "vararg!\n");
242 cmd->func = NULL;
243
244 // parse element
245 cmd_graph_parse (nodegraph, cmd);
246
247 return CMD_SUCCESS;
248 }
249
250 /**
251 * Debugging command to print command graph
252 */
253 DEFUN (grammar_test_show,
254 grammar_test_show_cmd,
255 "grammar show [doc]",
256 GRAMMAR_STR
257 "print current accumulated DFA\n"
258 "include docstrings\n")
259 {
260 check_nodegraph();
261
262 struct graph_node *stack[MAXDEPTH];
263 pretty_print_graph (vty, vector_slot (nodegraph->nodes, 0), 0, argc >= 3, stack, 0);
264 return CMD_SUCCESS;
265 }
266
267 DEFUN (grammar_test_dot,
268 grammar_test_dot_cmd,
269 "grammar dotfile OUTNAME",
270 GRAMMAR_STR
271 "print current graph for dot\n"
272 ".dot filename\n")
273 {
274 check_nodegraph();
275
276 struct graph_node *stack[MAXDEPTH];
277 struct graph_node *visited[MAXDEPTH*MAXDEPTH];
278 size_t vpos = 0;
279
280 FILE *ofd = fopen(argv[2]->arg, "w");
281 if (!ofd) {
282 vty_out(vty, "%s: %s\r\n", argv[2]->arg, strerror(errno));
283 return CMD_SUCCESS;
284 }
285
286 fprintf(ofd, "digraph {\n graph [ rankdir = LR ];\n node [ fontname = \"Fira Mono\", fontsize = 9 ];\n\n");
287 pretty_print_dot (ofd, 0,
288 vector_slot (nodegraph->nodes, 0),
289 stack, 0, visited, &vpos);
290 fprintf(ofd, "}\n");
291 fclose(ofd);
292 return CMD_SUCCESS;
293 }
294
295 struct cmd_permute_item
296 {
297 char *cmd;
298 struct cmd_element *el;
299 };
300
301 static void
302 cmd_permute_free (void *arg)
303 {
304 struct cmd_permute_item *i = arg;
305 XFREE (MTYPE_TMP, i->cmd);
306 XFREE (MTYPE_TMP, i);
307 }
308
309 static int
310 cmd_permute_cmp (void *a, void *b)
311 {
312 struct cmd_permute_item *aa = a, *bb = b;
313 return strcmp (aa->cmd, bb->cmd);
314 }
315
316 static void
317 cmd_graph_permute (struct list *out, struct graph_node **stack,
318 size_t stackpos, char *cmd)
319 {
320 struct graph_node *gn = stack[stackpos];
321 struct cmd_token *tok = gn->data;
322 char *appendp = cmd + strlen(cmd);
323 size_t i, j;
324
325 if (tok->type < SPECIAL_TKN)
326 {
327 sprintf (appendp, "%s ", tok->text);
328 appendp += strlen (appendp);
329 }
330 else if (tok->type == END_TKN)
331 {
332 struct cmd_permute_item *i = XMALLOC (MTYPE_TMP, sizeof (*i));
333 i->el = ((struct graph_node *)vector_slot (gn->to, 0))->data;
334 i->cmd = XSTRDUP (MTYPE_TMP, cmd);
335 i->cmd[strlen(cmd) - 1] = '\0';
336 listnode_add_sort (out, i);
337 return;
338 }
339
340 if (++stackpos == MAXDEPTH)
341 return;
342
343 for (i = 0; i < vector_active (gn->to); i++)
344 {
345 struct graph_node *gnext = vector_slot (gn->to, i);
346 for (j = 0; j < stackpos; j++)
347 if (stack[j] == gnext)
348 break;
349 if (j != stackpos)
350 continue;
351
352 stack[stackpos] = gnext;
353 *appendp = '\0';
354 cmd_graph_permute (out, stack, stackpos, cmd);
355 }
356 }
357
358 static struct list *
359 cmd_graph_permutations (struct graph *graph)
360 {
361 char accumulate[2048] = "";
362 struct graph_node *stack[MAXDEPTH];
363
364 struct list *rv = list_new ();
365 rv->cmp = cmd_permute_cmp;
366 rv->del = cmd_permute_free;
367 stack[0] = vector_slot (graph->nodes, 0);
368 cmd_graph_permute (rv, stack, 0, accumulate);
369 return rv;
370 }
371
372 extern vector cmdvec;
373
374 DEFUN (grammar_findambig,
375 grammar_findambig_cmd,
376 "grammar find-ambiguous [{printall|nodescan}]",
377 GRAMMAR_STR
378 "Find ambiguous commands\n"
379 "Print all permutations\n"
380 "Scan all nodes\n")
381 {
382 struct list *commands;
383 struct cmd_permute_item *prev = NULL, *cur = NULL;
384 struct listnode *ln;
385 int i, printall, scan, scannode = 0;
386 int ambig = 0;
387
388 i = 0;
389 printall = argv_find (argv, argc, "printall", &i);
390 i = 0;
391 scan = argv_find (argv, argc, "nodescan", &i);
392
393 if (scan && nodegraph_free)
394 {
395 graph_delete_graph (nodegraph_free);
396 nodegraph_free = NULL;
397 }
398
399 if (!scan && !nodegraph)
400 {
401 vty_out(vty, "nodegraph uninitialized\r\n");
402 return CMD_WARNING_CONFIG_FAILED;
403 }
404
405 do {
406 if (scan)
407 {
408 struct cmd_node *cnode = vector_slot (cmdvec, scannode++);
409 if (!cnode)
410 continue;
411 nodegraph = cnode->cmdgraph;
412 if (!nodegraph)
413 continue;
414 vty_outln (vty, "scanning node %d", scannode - 1);
415 }
416
417 commands = cmd_graph_permutations (nodegraph);
418 prev = NULL;
419 for (ALL_LIST_ELEMENTS_RO (commands, ln, cur))
420 {
421 int same = prev && !strcmp (prev->cmd, cur->cmd);
422 if (printall && !same)
423 vty_outln (vty, "'%s' [%x]", cur->cmd, cur->el->daemon);
424 if (same)
425 {
426 vty_outln (vty, "'%s' AMBIGUOUS:", cur->cmd);
427 vty_outln (vty, " %s%s '%s'", prev->el->name, VTYNL,
428 prev->el->string);
429 vty_outln (vty, " %s%s '%s'", cur->el->name, VTYNL,
430 cur->el->string);
431 vty_out (vty, VTYNL);
432 ambig++;
433 }
434 prev = cur;
435 }
436 list_delete (commands);
437
438 vty_out (vty, VTYNL);
439 } while (scan && scannode < LINK_PARAMS_NODE);
440
441 vty_outln (vty, "%d ambiguous commands found.", ambig);
442
443 if (scan)
444 nodegraph = NULL;
445 return ambig == 0 ? CMD_SUCCESS : CMD_WARNING_CONFIG_FAILED;
446 }
447
448 DEFUN (grammar_init_graph,
449 grammar_init_graph_cmd,
450 "grammar init",
451 GRAMMAR_STR
452 "(re)initialize graph\n")
453 {
454 if (nodegraph_free)
455 graph_delete_graph (nodegraph_free);
456 nodegraph_free = NULL;
457
458 init_cmdgraph (vty, &nodegraph);
459 return CMD_SUCCESS;
460 }
461
462 DEFUN (grammar_access,
463 grammar_access_cmd,
464 "grammar access (0-65535)",
465 GRAMMAR_STR
466 "access node graph\n"
467 "node number\n")
468 {
469 if (nodegraph_free)
470 graph_delete_graph (nodegraph_free);
471 nodegraph_free = NULL;
472
473 struct cmd_node *cnode;
474
475 cnode = vector_slot (cmdvec, atoi (argv[2]->arg));
476 if (!cnode)
477 {
478 vty_outln (vty, "%% no such node");
479 return CMD_WARNING_CONFIG_FAILED;
480 }
481
482 vty_outln (vty, "node %d", (int)cnode->node);
483 nodegraph = cnode->cmdgraph;
484 return CMD_SUCCESS;
485 }
486
487 /* this is called in vtysh.c to set up the testing shim */
488 void grammar_sandbox_init(void) {
489 // install all enable elements
490 install_element (ENABLE_NODE, &grammar_test_cmd);
491 install_element (ENABLE_NODE, &grammar_test_show_cmd);
492 install_element (ENABLE_NODE, &grammar_test_dot_cmd);
493 install_element (ENABLE_NODE, &grammar_test_match_cmd);
494 install_element (ENABLE_NODE, &grammar_test_complete_cmd);
495 install_element (ENABLE_NODE, &grammar_test_doc_cmd);
496 install_element (ENABLE_NODE, &grammar_findambig_cmd);
497 install_element (ENABLE_NODE, &grammar_init_graph_cmd);
498 install_element (ENABLE_NODE, &grammar_access_cmd);
499 }
500
501 #define item(x) { x, #x }
502 struct message tokennames[] = {
503 item(WORD_TKN), // words
504 item(VARIABLE_TKN), // almost anything
505 item(RANGE_TKN), // integer range
506 item(IPV4_TKN), // IPV4 addresses
507 item(IPV4_PREFIX_TKN), // IPV4 network prefixes
508 item(IPV6_TKN), // IPV6 prefixes
509 item(IPV6_PREFIX_TKN), // IPV6 network prefixes
510
511 /* plumbing types */
512 item(FORK_TKN),
513 item(JOIN_TKN),
514 item(START_TKN), // first token in line
515 item(END_TKN), // last token in line
516 { 0 }
517 };
518
519 /**
520 * Pretty-prints a graph, assuming it is a tree.
521 *
522 * @param start the node to take as the root
523 * @param level indent level for recursive calls, always pass 0
524 */
525 void
526 pretty_print_graph (struct vty *vty, struct graph_node *start, int level,
527 int desc, struct graph_node **stack, size_t stackpos)
528 {
529 // print this node
530 char tokennum[32];
531 struct cmd_token *tok = start->data;
532
533 snprintf(tokennum, sizeof(tokennum), "%d?", tok->type);
534 vty_out(vty, "%s", lookup_msg(tokennames, tok->type, NULL));
535 if (tok->text)
536 vty_out(vty, ":\"%s\"", tok->text);
537 if (tok->varname)
538 vty_out(vty, " => %s", tok->varname);
539 if (desc)
540 vty_out(vty, " ?'%s'", tok->desc);
541 vty_out(vty, " ");
542
543 if (stackpos == MAXDEPTH)
544 {
545 vty_outln (vty, " -aborting! (depth limit)");
546 return;
547 }
548 stack[stackpos++] = start;
549
550 int numto = desc ? 2 : vector_active (start->to);
551 if (numto)
552 {
553 if (numto > 1)
554 vty_out (vty, VTYNL);
555 for (unsigned int i = 0; i < vector_active (start->to); i++)
556 {
557 struct graph_node *adj = vector_slot (start->to, i);
558 // if we're listing multiple children, indent!
559 if (numto > 1)
560 for (int j = 0; j < level+1; j++)
561 vty_out(vty, " ");
562 // if this node is a vararg, just print *
563 if (adj == start)
564 vty_out(vty, "*");
565 else if (((struct cmd_token *)adj->data)->type == END_TKN)
566 vty_outln (vty, "--END");
567 else {
568 size_t k;
569 for (k = 0; k < stackpos; k++)
570 if (stack[k] == adj) {
571 vty_outln (vty, "<<loop@%zu ", k);
572 break;
573 }
574 if (k == stackpos)
575 pretty_print_graph (vty, adj, numto > 1 ? level+1 : level, desc, stack, stackpos);
576 }
577 }
578 }
579 else
580 vty_out (vty, VTYNL);
581 }
582
583 static void
584 pretty_print_dot (FILE *ofd, unsigned opts, struct graph_node *start,
585 struct graph_node **stack, size_t stackpos,
586 struct graph_node **visited, size_t *visitpos)
587 {
588 // print this node
589 char tokennum[32];
590 struct cmd_token *tok = start->data;
591 const char *color;
592
593 for (size_t i = 0; i < (*visitpos); i++)
594 if (visited[i] == start)
595 return;
596 visited[(*visitpos)++] = start;
597 if ((*visitpos) == MAXDEPTH*MAXDEPTH)
598 return;
599
600 snprintf(tokennum, sizeof(tokennum), "%d?", tok->type);
601 fprintf(ofd, " n%p [ shape=box, label=<", start);
602
603 fprintf(ofd, "<b>%s</b>", lookup_msg(tokennames, tok->type, NULL));
604 if (tok->attr == CMD_ATTR_DEPRECATED)
605 fprintf(ofd, " (d)");
606 else if (tok->attr == CMD_ATTR_HIDDEN)
607 fprintf(ofd, " (h)");
608 if (tok->text) {
609 if (tok->type == WORD_TKN)
610 fprintf(ofd, "<br/>\"<font color=\"#0055ff\" point-size=\"11\"><b>%s</b></font>\"", tok->text);
611 else
612 fprintf(ofd, "<br/>%s", tok->text);
613 }
614 /* if (desc)
615 fprintf(ofd, " ?'%s'", tok->desc); */
616 switch (tok->type) {
617 case START_TKN: color = "#ccffcc"; break;
618 case FORK_TKN: color = "#aaddff"; break;
619 case JOIN_TKN: color = "#ddaaff"; break;
620 case WORD_TKN: color = "#ffffff"; break;
621 default: color = "#ffffff"; break;
622 }
623 fprintf(ofd, ">, style = filled, fillcolor = \"%s\" ];\n", color);
624
625 if (stackpos == MAXDEPTH)
626 return;
627 stack[stackpos++] = start;
628
629 for (unsigned int i = 0; i < vector_active (start->to); i++)
630 {
631 struct graph_node *adj = vector_slot (start->to, i);
632 // if this node is a vararg, just print *
633 if (adj == start) {
634 fprintf(ofd, " n%p -> n%p;\n", start, start);
635 } else if (((struct cmd_token *)adj->data)->type == END_TKN) {
636 //struct cmd_token *et = adj->data;
637 fprintf(ofd, " n%p -> end%p;\n", start, adj);
638 fprintf(ofd, " end%p [ shape=box, label=<end>, style = filled, fillcolor = \"#ffddaa\" ];\n", adj);
639 } else {
640 fprintf(ofd, " n%p -> n%p;\n", start, adj);
641 size_t k;
642 for (k = 0; k < stackpos; k++)
643 if (stack[k] == adj)
644 break;
645 if (k == stackpos) {
646 pretty_print_dot (ofd, opts, adj, stack, stackpos, visited, visitpos);
647 }
648 }
649 }
650 }
651
652
653 /** stuff that should go in command.c + command.h */
654 void
655 init_cmdgraph (struct vty *vty, struct graph **graph)
656 {
657 // initialize graph, add start noe
658 *graph = graph_new ();
659 nodegraph_free = *graph;
660 struct cmd_token *token = cmd_token_new (START_TKN, 0, NULL, NULL);
661 graph_new_node (*graph, token, (void (*)(void *)) &cmd_token_del);
662 if (vty)
663 vty_outln (vty, "initialized graph");
664 }