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