]> git.proxmox.com Git - mirror_frr.git/blob - lib/grammar_sandbox.c
zebra, lib: fix the ZEBRA_INTERFACE_VRF_UPDATE zapi message
[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 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
29
30 #include "command.h"
31 #include "memory_vty.h"
32 #include "graph.h"
33 #include "linklist.h"
34 #include "command_match.h"
35
36 #define GRAMMAR_STR "CLI grammar sandbox\n"
37
38 DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command desc")
39
40 /** headers **/
41 void grammar_sandbox_init(void);
42 void pretty_print_graph(struct vty *vty, struct graph_node *, int, int,
43 struct graph_node **, size_t);
44 void init_cmdgraph(struct vty *, struct graph **);
45
46 /** shim interface commands **/
47 struct graph *nodegraph = NULL, *nodegraph_free = NULL;
48
49 #define check_nodegraph() \
50 do { \
51 if (!nodegraph) { \
52 vty_out(vty, "nodegraph not initialized\n"); \
53 return CMD_WARNING; \
54 } \
55 } while (0)
56
57 DEFUN (grammar_test,
58 grammar_test_cmd,
59 "grammar parse LINE...",
60 GRAMMAR_STR
61 "parse a command\n"
62 "command to pass to new parser\n")
63 {
64 check_nodegraph();
65
66 int idx_command = 2;
67 // make a string from tokenized command line
68 char *command = argv_concat(argv, argc, idx_command);
69
70 // create cmd_element for parser
71 struct cmd_element *cmd =
72 XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_element));
73 cmd->string = command;
74 cmd->doc =
75 "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 =
81 cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL);
82 graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del);
83
84 cmd_graph_parse(graph, cmd);
85 cmd_graph_merge(nodegraph, graph, +1);
86
87 return CMD_SUCCESS;
88 }
89
90 DEFUN (grammar_test_complete,
91 grammar_test_complete_cmd,
92 "grammar complete COMMAND...",
93 GRAMMAR_STR
94 "attempt to complete input on DFA\n"
95 "command to complete\n")
96 {
97 check_nodegraph();
98
99 int idx_command = 2;
100 char *cmdstr = argv_concat(argv, argc, idx_command);
101 if (!cmdstr)
102 return CMD_SUCCESS;
103
104 vector command = cmd_make_strvec(cmdstr);
105 if (!command) {
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 =
113 command_complete(nodegraph, command, &completions);
114
115 // print completions or relevant error message
116 if (!MATCHER_ERROR(result)) {
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_out(vty, " %-*s %s\n", width, tkn->text,
132 tkn->desc);
133 }
134
135 for (i = 0; i < vector_active(comps); i++)
136 cmd_token_del(
137 (struct cmd_token *)vector_slot(comps, i));
138 vector_free(comps);
139 } else
140 vty_out(vty, "%% No match\n");
141
142 // free resources
143 list_delete(&completions);
144 cmd_free_strvec(command);
145 XFREE(MTYPE_TMP, cmdstr);
146
147 return CMD_SUCCESS;
148 }
149
150 DEFUN (grammar_test_match,
151 grammar_test_match_cmd,
152 "grammar match COMMAND...",
153 GRAMMAR_STR
154 "attempt to match input on DFA\n"
155 "command to match\n")
156 {
157 check_nodegraph();
158
159 int idx_command = 2;
160 if (argv[2]->arg[0] == '#')
161 return CMD_SUCCESS;
162
163 char *cmdstr = argv_concat(argv, argc, idx_command);
164 if (!cmdstr)
165 return CMD_SUCCESS;
166 vector command = cmd_make_strvec(cmdstr);
167 if (!command) {
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 =
175 command_match(nodegraph, command, &argvv, &element);
176
177 // print completions or relevant error message
178 if (element) {
179 vty_out(vty, "Matched: %s\n", element->string);
180 struct listnode *ln;
181 struct cmd_token *token;
182 for (ALL_LIST_ELEMENTS_RO(argvv, ln, token))
183 vty_out(vty, "%s -- %s\n", token->text, token->arg);
184
185 vty_out(vty, "func: %p\n", element->func);
186
187 list_delete(&argvv);
188 } else {
189 assert(MATCHER_ERROR(result));
190 switch (result) {
191 case MATCHER_NO_MATCH:
192 vty_out(vty, "%% Unknown command\n");
193 break;
194 case MATCHER_INCOMPLETE:
195 vty_out(vty, "%% Incomplete command\n");
196 break;
197 case MATCHER_AMBIGUOUS:
198 vty_out(vty, "%% Ambiguous command\n");
199 break;
200 default:
201 vty_out(vty, "%% Unknown error\n");
202 break;
203 }
204 }
205
206 // free resources
207 cmd_free_strvec(command);
208 XFREE(MTYPE_TMP, cmdstr);
209
210 return CMD_SUCCESS;
211 }
212
213 /**
214 * Testing shim to test docstrings
215 */
216 DEFUN (grammar_test_doc,
217 grammar_test_doc_cmd,
218 "grammar test docstring",
219 GRAMMAR_STR
220 "Test function for docstring\n"
221 "Command end\n")
222 {
223 check_nodegraph();
224
225 // create cmd_element with docstring
226 struct cmd_element *cmd =
227 XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_element));
228 cmd->string = XSTRDUP(
229 MTYPE_CMD_TOKENS,
230 "test docstring <example|selector follow> (1-255) end VARIABLE [OPTION|set lol] . VARARG");
231 cmd->doc = XSTRDUP(MTYPE_CMD_TOKENS,
232 "Test stuff\n"
233 "docstring thing\n"
234 "first example\n"
235 "second example\n"
236 "follow\n"
237 "random range\n"
238 "end thingy\n"
239 "variable\n"
240 "optional variable\n"
241 "optional set\n"
242 "optional lol\n"
243 "vararg!\n");
244 cmd->func = NULL;
245
246 // parse element
247 cmd_graph_parse(nodegraph, cmd);
248
249 return CMD_SUCCESS;
250 }
251
252 /**
253 * Debugging command to print command graph
254 */
255 DEFUN (grammar_test_show,
256 grammar_test_show_cmd,
257 "grammar show [doc]",
258 GRAMMAR_STR
259 "print current accumulated DFA\n"
260 "include docstrings\n")
261 {
262 check_nodegraph();
263
264 struct graph_node *stack[CMD_ARGC_MAX];
265 pretty_print_graph(vty, vector_slot(nodegraph->nodes, 0), 0, argc >= 3,
266 stack, 0);
267 return CMD_SUCCESS;
268 }
269
270 DEFUN (grammar_test_dot,
271 grammar_test_dot_cmd,
272 "grammar dotfile OUTNAME",
273 GRAMMAR_STR
274 "print current graph for dot\n"
275 ".dot filename\n")
276 {
277 check_nodegraph();
278 FILE *ofd = fopen(argv[2]->arg, "w");
279
280 if (!ofd) {
281 vty_out(vty, "%s: %s\r\n", argv[2]->arg, strerror(errno));
282 return CMD_SUCCESS;
283 }
284
285 char *dot = cmd_graph_dump_dot(nodegraph);
286
287 fprintf(ofd, "%s", dot);
288 fclose(ofd);
289 XFREE(MTYPE_TMP, dot);
290
291 return CMD_SUCCESS;
292 }
293
294 struct cmd_permute_item {
295 char *cmd;
296 struct cmd_element *el;
297 };
298
299 static void cmd_permute_free(void *arg)
300 {
301 struct cmd_permute_item *i = arg;
302 XFREE(MTYPE_TMP, i->cmd);
303 XFREE(MTYPE_TMP, i);
304 }
305
306 static int cmd_permute_cmp(void *a, void *b)
307 {
308 struct cmd_permute_item *aa = a, *bb = b;
309 return strcmp(aa->cmd, bb->cmd);
310 }
311
312 static void cmd_graph_permute(struct list *out, struct graph_node **stack,
313 size_t stackpos, char *cmd)
314 {
315 struct graph_node *gn = stack[stackpos];
316 struct cmd_token *tok = gn->data;
317 char *appendp = cmd + strlen(cmd);
318 size_t j;
319
320 if (tok->type < SPECIAL_TKN) {
321 sprintf(appendp, "%s ", tok->text);
322 appendp += strlen(appendp);
323 } else if (tok->type == END_TKN) {
324 struct cmd_permute_item *i = XMALLOC(MTYPE_TMP, sizeof(*i));
325 i->el = ((struct graph_node *)vector_slot(gn->to, 0))->data;
326 i->cmd = XSTRDUP(MTYPE_TMP, cmd);
327 i->cmd[strlen(cmd) - 1] = '\0';
328 listnode_add_sort(out, i);
329 return;
330 }
331
332 if (++stackpos == CMD_ARGC_MAX)
333 return;
334
335 for (size_t i = 0; i < vector_active(gn->to); i++) {
336 struct graph_node *gnext = vector_slot(gn->to, i);
337 for (j = 0; j < stackpos; j++)
338 if (stack[j] == gnext)
339 break;
340 if (j != stackpos)
341 continue;
342
343 stack[stackpos] = gnext;
344 *appendp = '\0';
345 cmd_graph_permute(out, stack, stackpos, cmd);
346 }
347 }
348
349 static struct list *cmd_graph_permutations(struct graph *graph)
350 {
351 char accumulate[2048] = "";
352 struct graph_node *stack[CMD_ARGC_MAX];
353
354 struct list *rv = list_new();
355 rv->cmp = cmd_permute_cmp;
356 rv->del = cmd_permute_free;
357 stack[0] = vector_slot(graph->nodes, 0);
358 cmd_graph_permute(rv, stack, 0, accumulate);
359 return rv;
360 }
361
362 extern vector cmdvec;
363
364 DEFUN (grammar_findambig,
365 grammar_findambig_cmd,
366 "grammar find-ambiguous [{printall|nodescan}]",
367 GRAMMAR_STR
368 "Find ambiguous commands\n"
369 "Print all permutations\n"
370 "Scan all nodes\n")
371 {
372 struct list *commands;
373 struct cmd_permute_item *prev = NULL, *cur = NULL;
374 struct listnode *ln;
375 int i, printall, scan, scannode = 0;
376 int ambig = 0;
377
378 i = 0;
379 printall = argv_find(argv, argc, "printall", &i);
380 i = 0;
381 scan = argv_find(argv, argc, "nodescan", &i);
382
383 if (scan && nodegraph_free) {
384 graph_delete_graph(nodegraph_free);
385 nodegraph_free = NULL;
386 }
387
388 if (!scan && !nodegraph) {
389 vty_out(vty, "nodegraph uninitialized\r\n");
390 return CMD_WARNING_CONFIG_FAILED;
391 }
392
393 do {
394 if (scan) {
395 struct cmd_node *cnode =
396 vector_slot(cmdvec, scannode++);
397 if (!cnode)
398 continue;
399 nodegraph = cnode->cmdgraph;
400 if (!nodegraph)
401 continue;
402 vty_out(vty, "scanning node %d (%s)\n", scannode - 1,
403 node_names[scannode - 1]);
404 }
405
406 commands = cmd_graph_permutations(nodegraph);
407 prev = NULL;
408 for (ALL_LIST_ELEMENTS_RO(commands, ln, cur)) {
409 int same = prev && !strcmp(prev->cmd, cur->cmd);
410 if (printall && !same)
411 vty_out(vty, "'%s' [%x]\n", cur->cmd,
412 cur->el->daemon);
413 if (same) {
414 vty_out(vty, "'%s' AMBIGUOUS:\n", cur->cmd);
415 vty_out(vty, " %s\n '%s'\n", prev->el->name,
416 prev->el->string);
417 vty_out(vty, " %s\n '%s'\n", cur->el->name,
418 cur->el->string);
419 vty_out(vty, "\n");
420 ambig++;
421 }
422 prev = cur;
423 }
424 list_delete(&commands);
425
426 vty_out(vty, "\n");
427 } while (scan && scannode < LINK_PARAMS_NODE);
428
429 vty_out(vty, "%d ambiguous commands found.\n", ambig);
430
431 if (scan)
432 nodegraph = NULL;
433 return ambig == 0 ? CMD_SUCCESS : CMD_WARNING_CONFIG_FAILED;
434 }
435
436 DEFUN (grammar_init_graph,
437 grammar_init_graph_cmd,
438 "grammar init",
439 GRAMMAR_STR
440 "(re)initialize graph\n")
441 {
442 if (nodegraph_free)
443 graph_delete_graph(nodegraph_free);
444 nodegraph_free = NULL;
445
446 init_cmdgraph(vty, &nodegraph);
447 return CMD_SUCCESS;
448 }
449
450 DEFUN (grammar_access,
451 grammar_access_cmd,
452 "grammar access (0-65535)",
453 GRAMMAR_STR
454 "access node graph\n"
455 "node number\n")
456 {
457 if (nodegraph_free)
458 graph_delete_graph(nodegraph_free);
459 nodegraph_free = NULL;
460
461 struct cmd_node *cnode;
462
463 cnode = vector_slot(cmdvec, atoi(argv[2]->arg));
464 if (!cnode) {
465 vty_out(vty, "%% no such node\n");
466 return CMD_WARNING_CONFIG_FAILED;
467 }
468
469 vty_out(vty, "node %d\n", (int)cnode->node);
470 nodegraph = cnode->cmdgraph;
471 return CMD_SUCCESS;
472 }
473
474 /* this is called in vtysh.c to set up the testing shim */
475 void grammar_sandbox_init(void)
476 {
477 // install all enable elements
478 install_element(ENABLE_NODE, &grammar_test_cmd);
479 install_element(ENABLE_NODE, &grammar_test_show_cmd);
480 install_element(ENABLE_NODE, &grammar_test_dot_cmd);
481 install_element(ENABLE_NODE, &grammar_test_match_cmd);
482 install_element(ENABLE_NODE, &grammar_test_complete_cmd);
483 install_element(ENABLE_NODE, &grammar_test_doc_cmd);
484 install_element(ENABLE_NODE, &grammar_findambig_cmd);
485 install_element(ENABLE_NODE, &grammar_init_graph_cmd);
486 install_element(ENABLE_NODE, &grammar_access_cmd);
487 }
488
489 /**
490 * Pretty-prints a graph, assuming it is a tree.
491 *
492 * @param start the node to take as the root
493 * @param level indent level for recursive calls, always pass 0
494 */
495 void pretty_print_graph(struct vty *vty, struct graph_node *start, int level,
496 int desc, struct graph_node **stack, size_t stackpos)
497 {
498 // print this node
499 char tokennum[32];
500 struct cmd_token *tok = start->data;
501
502 snprintf(tokennum, sizeof(tokennum), "%d?", tok->type);
503 vty_out(vty, "%s", lookup_msg(tokennames, tok->type, NULL));
504 if (tok->text)
505 vty_out(vty, ":\"%s\"", tok->text);
506 if (tok->varname)
507 vty_out(vty, " => %s", tok->varname);
508 if (desc)
509 vty_out(vty, " ?'%s'", tok->desc);
510 vty_out(vty, " ");
511
512 if (stackpos == CMD_ARGC_MAX) {
513 vty_out(vty, " -aborting! (depth limit)\n");
514 return;
515 }
516 stack[stackpos++] = start;
517
518 int numto = desc ? 2 : vector_active(start->to);
519 if (numto) {
520 if (numto > 1)
521 vty_out(vty, "\n");
522 for (unsigned int i = 0; i < vector_active(start->to); i++) {
523 struct graph_node *adj = vector_slot(start->to, i);
524 // if we're listing multiple children, indent!
525 if (numto > 1)
526 for (int j = 0; j < level + 1; j++)
527 vty_out(vty, " ");
528 // if this node is a vararg, just print *
529 if (adj == start)
530 vty_out(vty, "*");
531 else if (((struct cmd_token *)adj->data)->type
532 == END_TKN)
533 vty_out(vty, "--END\n");
534 else {
535 size_t k;
536 for (k = 0; k < stackpos; k++)
537 if (stack[k] == adj) {
538 vty_out(vty, "<<loop@%zu \n",
539 k);
540 break;
541 }
542 if (k == stackpos)
543 pretty_print_graph(
544 vty, adj,
545 numto > 1 ? level + 1 : level,
546 desc, stack, stackpos);
547 }
548 }
549 } else
550 vty_out(vty, "\n");
551 }
552
553 /** stuff that should go in command.c + command.h */
554 void init_cmdgraph(struct vty *vty, struct graph **graph)
555 {
556 // initialize graph, add start noe
557 *graph = graph_new();
558 nodegraph_free = *graph;
559 struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL);
560 graph_new_node(*graph, token, (void (*)(void *)) & cmd_token_del);
561 if (vty)
562 vty_out(vty, "initialized graph\n");
563 }