]> git.proxmox.com Git - mirror_frr.git/blob - lib/grammar_sandbox.c
*: remove the checking returned value for hash_get()
[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 "graph.h"
32 #include "linklist.h"
33 #include "command_match.h"
34
35 #define GRAMMAR_STR "CLI grammar sandbox\n"
36
37 DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command desc");
38
39 /** headers **/
40 void grammar_sandbox_init(void);
41 static void pretty_print_graph(struct vty *vty, struct graph_node *, int, int,
42 struct graph_node **, size_t);
43 static void init_cmdgraph(struct vty *, struct graph **);
44
45 /** shim interface commands **/
46 static struct graph *nodegraph = NULL, *nodegraph_free = NULL;
47
48 #define check_nodegraph() \
49 do { \
50 if (!nodegraph) { \
51 vty_out(vty, "nodegraph not initialized\n"); \
52 return CMD_WARNING; \
53 } \
54 } while (0)
55
56 DEFUN (grammar_test,
57 grammar_test_cmd,
58 "grammar parse LINE...",
59 GRAMMAR_STR
60 "parse a command\n"
61 "command to pass to new parser\n")
62 {
63 check_nodegraph();
64
65 int idx_command = 2;
66 // make a string from tokenized command line
67 char *command = argv_concat(argv, argc, idx_command);
68
69 // create cmd_element for parser
70 struct cmd_element *cmd =
71 XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_element));
72 cmd->string = command;
73 cmd->doc =
74 "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n";
75 cmd->func = NULL;
76
77 // parse the command and install it into the command graph
78 struct graph *graph = graph_new();
79 struct cmd_token *token =
80 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 XFREE(MTYPE_TMP, cmdstr);
106 return CMD_SUCCESS;
107 }
108
109 // generate completions of user input
110 struct list *completions;
111 enum matcher_rv result =
112 command_complete(nodegraph, command, &completions);
113
114 // print completions or relevant error message
115 if (!MATCHER_ERROR(result)) {
116 vector comps = completions_to_vec(completions);
117 struct cmd_token *tkn;
118
119 // calculate length of longest tkn->text in completions
120 unsigned int width = 0, i = 0;
121 for (i = 0; i < vector_active(comps); i++) {
122 tkn = vector_slot(comps, i);
123 unsigned int len = strlen(tkn->text);
124 width = len > width ? len : width;
125 }
126
127 // print completions
128 for (i = 0; i < vector_active(comps); i++) {
129 tkn = vector_slot(comps, i);
130 vty_out(vty, " %-*s %s\n", width, tkn->text,
131 tkn->desc);
132 }
133
134 for (i = 0; i < vector_active(comps); i++)
135 cmd_token_del(
136 (struct cmd_token *)vector_slot(comps, i));
137 vector_free(comps);
138 } else
139 vty_out(vty, "%% No match\n");
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 XFREE(MTYPE_TMP, cmdstr);
168 return CMD_SUCCESS;
169 }
170
171 struct list *argvv = NULL;
172 const struct cmd_element *element = NULL;
173 enum matcher_rv result =
174 command_match(nodegraph, command, &argvv, &element);
175
176 // print completions or relevant error message
177 if (element) {
178 vty_out(vty, "Matched: %s\n", element->string);
179 struct listnode *ln;
180 struct cmd_token *token;
181 for (ALL_LIST_ELEMENTS_RO(argvv, ln, token))
182 vty_out(vty, "%s -- %s\n", token->text, token->arg);
183
184 vty_out(vty, "func: %p\n", element->func);
185
186 list_delete(&argvv);
187 } else {
188 assert(MATCHER_ERROR(result));
189 switch (result) {
190 case MATCHER_NO_MATCH:
191 vty_out(vty, "%% Unknown command\n");
192 break;
193 case MATCHER_INCOMPLETE:
194 vty_out(vty, "%% Incomplete command\n");
195 break;
196 case MATCHER_AMBIGUOUS:
197 vty_out(vty, "%% Ambiguous command\n");
198 break;
199 default:
200 vty_out(vty, "%% Unknown error\n");
201 break;
202 }
203 }
204
205 // free resources
206 cmd_free_strvec(command);
207 XFREE(MTYPE_TMP, cmdstr);
208
209 return CMD_SUCCESS;
210 }
211
212 /**
213 * Testing shim to test docstrings
214 */
215 DEFUN (grammar_test_doc,
216 grammar_test_doc_cmd,
217 "grammar test docstring",
218 GRAMMAR_STR
219 "Test function for docstring\n"
220 "Command end\n")
221 {
222 check_nodegraph();
223
224 // create cmd_element with docstring
225 struct cmd_element *cmd =
226 XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_element));
227 cmd->string = XSTRDUP(
228 MTYPE_CMD_TOKENS,
229 "test docstring <example|selector follow> (1-255) end VARIABLE [OPTION|set lol] . VARARG");
230 cmd->doc = XSTRDUP(MTYPE_CMD_TOKENS,
231 "Test stuff\n"
232 "docstring thing\n"
233 "first example\n"
234 "second example\n"
235 "follow\n"
236 "random range\n"
237 "end thingy\n"
238 "variable\n"
239 "optional variable\n"
240 "optional set\n"
241 "optional lol\n"
242 "vararg!\n");
243 cmd->func = NULL;
244
245 // parse element
246 cmd_graph_parse(nodegraph, cmd);
247
248 return CMD_SUCCESS;
249 }
250
251 /**
252 * Debugging command to print command graph
253 */
254 DEFUN (grammar_test_show,
255 grammar_test_show_cmd,
256 "grammar show [doc]",
257 GRAMMAR_STR
258 "print current accumulated DFA\n"
259 "include docstrings\n")
260 {
261 check_nodegraph();
262
263 struct graph_node *stack[CMD_ARGC_MAX];
264 pretty_print_graph(vty, vector_slot(nodegraph->nodes, 0), 0, argc >= 3,
265 stack, 0);
266 return CMD_SUCCESS;
267 }
268
269 DEFUN (grammar_test_dot,
270 grammar_test_dot_cmd,
271 "grammar dotfile OUTNAME",
272 GRAMMAR_STR
273 "print current graph for dot\n"
274 ".dot filename\n")
275 {
276 check_nodegraph();
277 FILE *ofd = fopen(argv[2]->arg, "w");
278
279 if (!ofd) {
280 vty_out(vty, "%s: %s\r\n", argv[2]->arg, strerror(errno));
281 return CMD_SUCCESS;
282 }
283
284 char *dot = cmd_graph_dump_dot(nodegraph);
285
286 fprintf(ofd, "%s", dot);
287 fclose(ofd);
288 XFREE(MTYPE_TMP, dot);
289
290 return CMD_SUCCESS;
291 }
292
293 struct cmd_permute_item {
294 char *cmd;
295 struct cmd_element *el;
296 };
297
298 static void cmd_permute_free(void *arg)
299 {
300 struct cmd_permute_item *i = arg;
301 XFREE(MTYPE_TMP, i->cmd);
302 XFREE(MTYPE_TMP, i);
303 }
304
305 static int cmd_permute_cmp(void *a, void *b)
306 {
307 struct cmd_permute_item *aa = a, *bb = b;
308 return strcmp(aa->cmd, bb->cmd);
309 }
310
311 static void cmd_graph_permute(struct list *out, struct graph_node **stack,
312 size_t stackpos, char *cmd)
313 {
314 struct graph_node *gn = stack[stackpos];
315 struct cmd_token *tok = gn->data;
316 char *appendp = cmd + strlen(cmd);
317 size_t j;
318
319 if (tok->type < SPECIAL_TKN) {
320 sprintf(appendp, "%s ", tok->text);
321 appendp += strlen(appendp);
322 } else if (tok->type == END_TKN) {
323 struct cmd_permute_item *i = XMALLOC(MTYPE_TMP, sizeof(*i));
324 i->el = ((struct graph_node *)vector_slot(gn->to, 0))->data;
325 i->cmd = XSTRDUP(MTYPE_TMP, cmd);
326 i->cmd[strlen(cmd) - 1] = '\0';
327 listnode_add_sort(out, i);
328 return;
329 }
330
331 if (++stackpos == CMD_ARGC_MAX)
332 return;
333
334 for (size_t i = 0; i < vector_active(gn->to); i++) {
335 struct graph_node *gnext = vector_slot(gn->to, i);
336 for (j = 0; j < stackpos; j++)
337 if (stack[j] == gnext)
338 break;
339 if (j != stackpos)
340 continue;
341
342 stack[stackpos] = gnext;
343 *appendp = '\0';
344 cmd_graph_permute(out, stack, stackpos, cmd);
345 }
346 }
347
348 static struct list *cmd_graph_permutations(struct graph *graph)
349 {
350 char accumulate[2048] = "";
351 struct graph_node *stack[CMD_ARGC_MAX];
352
353 struct list *rv = list_new();
354 rv->cmp = cmd_permute_cmp;
355 rv->del = cmd_permute_free;
356 stack[0] = vector_slot(graph->nodes, 0);
357 cmd_graph_permute(rv, stack, 0, accumulate);
358 return rv;
359 }
360
361 extern vector cmdvec;
362
363 DEFUN (grammar_findambig,
364 grammar_findambig_cmd,
365 "grammar find-ambiguous [{printall|nodescan}]",
366 GRAMMAR_STR
367 "Find ambiguous commands\n"
368 "Print all permutations\n"
369 "Scan all nodes\n")
370 {
371 struct list *commands;
372 struct cmd_permute_item *prev = NULL, *cur = NULL;
373 struct listnode *ln;
374 int i, printall, scan, scannode = 0;
375 int ambig = 0;
376
377 i = 0;
378 printall = argv_find(argv, argc, "printall", &i);
379 i = 0;
380 scan = argv_find(argv, argc, "nodescan", &i);
381
382 if (scan && nodegraph_free) {
383 graph_delete_graph(nodegraph_free);
384 nodegraph_free = NULL;
385 }
386
387 if (!scan && !nodegraph) {
388 vty_out(vty, "nodegraph uninitialized\r\n");
389 return CMD_WARNING_CONFIG_FAILED;
390 }
391
392 do {
393 if (scan) {
394 struct cmd_node *cnode =
395 vector_slot(cmdvec, scannode++);
396 if (!cnode)
397 continue;
398 cmd_finalize_node(cnode);
399 nodegraph = cnode->cmdgraph;
400 if (!nodegraph)
401 continue;
402 vty_out(vty, "scanning node %d (%s)\n", scannode - 1,
403 cnode->name);
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 cmd_finalize_node(cnode);
471 nodegraph = cnode->cmdgraph;
472 return CMD_SUCCESS;
473 }
474
475 /* this is called in vtysh.c to set up the testing shim */
476 void grammar_sandbox_init(void)
477 {
478 // install all enable elements
479 install_element(ENABLE_NODE, &grammar_test_cmd);
480 install_element(ENABLE_NODE, &grammar_test_show_cmd);
481 install_element(ENABLE_NODE, &grammar_test_dot_cmd);
482 install_element(ENABLE_NODE, &grammar_test_match_cmd);
483 install_element(ENABLE_NODE, &grammar_test_complete_cmd);
484 install_element(ENABLE_NODE, &grammar_test_doc_cmd);
485 install_element(ENABLE_NODE, &grammar_findambig_cmd);
486 install_element(ENABLE_NODE, &grammar_init_graph_cmd);
487 install_element(ENABLE_NODE, &grammar_access_cmd);
488 }
489
490 /**
491 * Pretty-prints a graph, assuming it is a tree.
492 *
493 * @param start the node to take as the root
494 * @param level indent level for recursive calls, always pass 0
495 */
496 static void pretty_print_graph(struct vty *vty, struct graph_node *start,
497 int level, int desc, struct graph_node **stack,
498 size_t stackpos)
499 {
500 // print this node
501 char tokennum[32];
502 struct cmd_token *tok = start->data;
503
504 snprintf(tokennum, sizeof(tokennum), "%d?", tok->type);
505 vty_out(vty, "%s", lookup_msg(tokennames, tok->type, NULL));
506 if (tok->text)
507 vty_out(vty, ":\"%s\"", tok->text);
508 if (tok->varname)
509 vty_out(vty, " => %s", tok->varname);
510 if (desc)
511 vty_out(vty, " ?'%s'", tok->desc);
512 vty_out(vty, " ");
513
514 if (stackpos == CMD_ARGC_MAX) {
515 vty_out(vty, " -aborting! (depth limit)\n");
516 return;
517 }
518 stack[stackpos++] = start;
519
520 int numto = desc ? 2 : vector_active(start->to);
521 if (numto) {
522 if (numto > 1)
523 vty_out(vty, "\n");
524 for (unsigned int i = 0; i < vector_active(start->to); i++) {
525 struct graph_node *adj = vector_slot(start->to, i);
526 // if we're listing multiple children, indent!
527 if (numto > 1)
528 for (int j = 0; j < level + 1; j++)
529 vty_out(vty, " ");
530 // if this node is a vararg, just print *
531 if (adj == start)
532 vty_out(vty, "*");
533 else if (((struct cmd_token *)adj->data)->type
534 == END_TKN)
535 vty_out(vty, "--END\n");
536 else {
537 size_t k;
538 for (k = 0; k < stackpos; k++)
539 if (stack[k] == adj) {
540 vty_out(vty, "<<loop@%zu \n",
541 k);
542 break;
543 }
544 if (k == stackpos)
545 pretty_print_graph(
546 vty, adj,
547 numto > 1 ? level + 1 : level,
548 desc, stack, stackpos);
549 }
550 }
551 } else
552 vty_out(vty, "\n");
553 }
554
555 /** stuff that should go in command.c + command.h */
556 static void init_cmdgraph(struct vty *vty, struct graph **graph)
557 {
558 // initialize graph, add start noe
559 *graph = graph_new();
560 nodegraph_free = *graph;
561 struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL);
562 graph_new_node(*graph, token, (void (*)(void *)) & cmd_token_del);
563 if (vty)
564 vty_out(vty, "initialized graph\n");
565 }