// SPDX-License-Identifier: GPL-2.0-or-later /* * Testing shim and API examples for the new CLI backend. * * This unit defines a number of commands in the old engine that can * be used to test and interact with the new engine. * -- * Copyright (C) 2016 Cumulus Networks, Inc. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "command.h" #include "graph.h" #include "linklist.h" #include "command_match.h" #define GRAMMAR_STR "CLI grammar sandbox\n" DEFINE_MTYPE_STATIC(LIB, CMD_DESCRIPTIONS, "Command desc"); /** headers **/ void grammar_sandbox_init(void); static void pretty_print_graph(struct vty *vty, struct graph_node *, int, int, struct graph_node **, size_t); static void init_cmdgraph(struct vty *, struct graph **); /** shim interface commands **/ static struct graph *nodegraph = NULL, *nodegraph_free = NULL; #define check_nodegraph() \ do { \ if (!nodegraph) { \ vty_out(vty, "nodegraph not initialized\n"); \ return CMD_WARNING; \ } \ } while (0) DEFUN (grammar_test, grammar_test_cmd, "grammar parse LINE...", GRAMMAR_STR "parse a command\n" "command to pass to new parser\n") { check_nodegraph(); int idx_command = 2; // make a string from tokenized command line char *command = argv_concat(argv, argc, idx_command); // create cmd_element for parser struct cmd_element *cmd = XCALLOC(MTYPE_CMD_DESCRIPTIONS, sizeof(struct cmd_element)); cmd->string = command; cmd->doc = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n"; cmd->func = NULL; // parse the command and install it into the command graph struct graph *graph = graph_new(); struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL); graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del); cmd_graph_parse(graph, cmd); cmd_graph_merge(nodegraph, graph, +1); return CMD_SUCCESS; } DEFUN (grammar_test_complete, grammar_test_complete_cmd, "grammar complete COMMAND...", GRAMMAR_STR "attempt to complete input on DFA\n" "command to complete\n") { check_nodegraph(); int idx_command = 2; char *cmdstr = argv_concat(argv, argc, idx_command); if (!cmdstr) return CMD_SUCCESS; vector command = cmd_make_strvec(cmdstr); if (!command) { XFREE(MTYPE_TMP, cmdstr); return CMD_SUCCESS; } // generate completions of user input struct list *completions; enum matcher_rv result = command_complete(nodegraph, command, &completions); // print completions or relevant error message if (!MATCHER_ERROR(result)) { vector comps = completions_to_vec(completions); struct cmd_token *tkn; // calculate length of longest tkn->text in completions unsigned int width = 0, i = 0; for (i = 0; i < vector_active(comps); i++) { tkn = vector_slot(comps, i); unsigned int len = strlen(tkn->text); width = len > width ? len : width; } // print completions for (i = 0; i < vector_active(comps); i++) { tkn = vector_slot(comps, i); vty_out(vty, " %-*s %s\n", width, tkn->text, tkn->desc); } for (i = 0; i < vector_active(comps); i++) cmd_token_del( (struct cmd_token *)vector_slot(comps, i)); vector_free(comps); } else vty_out(vty, "%% No match\n"); // free resources list_delete(&completions); cmd_free_strvec(command); XFREE(MTYPE_TMP, cmdstr); return CMD_SUCCESS; } DEFUN (grammar_test_match, grammar_test_match_cmd, "grammar match COMMAND...", GRAMMAR_STR "attempt to match input on DFA\n" "command to match\n") { check_nodegraph(); int idx_command = 2; if (argv[2]->arg[0] == '#') return CMD_SUCCESS; char *cmdstr = argv_concat(argv, argc, idx_command); if (!cmdstr) return CMD_SUCCESS; vector command = cmd_make_strvec(cmdstr); if (!command) { XFREE(MTYPE_TMP, cmdstr); return CMD_SUCCESS; } struct list *argvv = NULL; const struct cmd_element *element = NULL; enum matcher_rv result = command_match(nodegraph, command, &argvv, &element); // print completions or relevant error message if (element) { vty_out(vty, "Matched: %s\n", element->string); struct listnode *ln; struct cmd_token *token; for (ALL_LIST_ELEMENTS_RO(argvv, ln, token)) vty_out(vty, "%s -- %s\n", token->text, token->arg); vty_out(vty, "func: %p\n", element->func); list_delete(&argvv); } else { assert(MATCHER_ERROR(result)); switch (result) { case MATCHER_NO_MATCH: vty_out(vty, "%% Unknown command\n"); break; case MATCHER_INCOMPLETE: vty_out(vty, "%% Incomplete command\n"); break; case MATCHER_AMBIGUOUS: vty_out(vty, "%% Ambiguous command\n"); break; case MATCHER_OK: vty_out(vty, "%% Unknown error\n"); break; } } // free resources cmd_free_strvec(command); XFREE(MTYPE_TMP, cmdstr); return CMD_SUCCESS; } /** * Testing shim to test docstrings */ DEFUN (grammar_test_doc, grammar_test_doc_cmd, "grammar test docstring", GRAMMAR_STR "Test function for docstring\n" "Command end\n") { check_nodegraph(); // create cmd_element with docstring struct cmd_element *cmd = XCALLOC(MTYPE_CMD_DESCRIPTIONS, sizeof(struct cmd_element)); cmd->string = XSTRDUP( MTYPE_CMD_DESCRIPTIONS, "test docstring (1-255) end VARIABLE [OPTION|set lol] . VARARG"); cmd->doc = XSTRDUP(MTYPE_CMD_DESCRIPTIONS, "Test stuff\n" "docstring thing\n" "first example\n" "second example\n" "follow\n" "random range\n" "end thingy\n" "variable\n" "optional variable\n" "optional set\n" "optional lol\n" "vararg!\n"); cmd->func = NULL; // parse element cmd_graph_parse(nodegraph, cmd); return CMD_SUCCESS; } /** * Debugging command to print command graph */ DEFUN (grammar_test_show, grammar_test_show_cmd, "grammar show [doc]", GRAMMAR_STR "print current accumulated DFA\n" "include docstrings\n") { check_nodegraph(); struct graph_node *stack[CMD_ARGC_MAX]; pretty_print_graph(vty, vector_slot(nodegraph->nodes, 0), 0, argc >= 3, stack, 0); return CMD_SUCCESS; } DEFUN (grammar_test_dot, grammar_test_dot_cmd, "grammar dotfile OUTNAME", GRAMMAR_STR "print current graph for dot\n" ".dot filename\n") { check_nodegraph(); FILE *ofd = fopen(argv[2]->arg, "w"); if (!ofd) { vty_out(vty, "%s: %s\r\n", argv[2]->arg, strerror(errno)); return CMD_SUCCESS; } char *dot = cmd_graph_dump_dot(nodegraph); fprintf(ofd, "%s", dot); fclose(ofd); XFREE(MTYPE_TMP, dot); return CMD_SUCCESS; } struct cmd_permute_item { char *cmd; struct cmd_element *el; }; static void cmd_permute_free(void *arg) { struct cmd_permute_item *i = arg; XFREE(MTYPE_TMP, i->cmd); XFREE(MTYPE_TMP, i); } static int cmd_permute_cmp(void *a, void *b) { struct cmd_permute_item *aa = a, *bb = b; return strcmp(aa->cmd, bb->cmd); } static void cmd_graph_permute(struct list *out, struct graph_node **stack, size_t stackpos, char *cmd) { struct graph_node *gn = stack[stackpos]; struct cmd_token *tok = gn->data; char *appendp = cmd + strlen(cmd); size_t j; if (tok->type < SPECIAL_TKN) { sprintf(appendp, "%s ", tok->text); appendp += strlen(appendp); } else if (tok->type == END_TKN) { struct cmd_permute_item *i = XMALLOC(MTYPE_TMP, sizeof(*i)); i->el = ((struct graph_node *)vector_slot(gn->to, 0))->data; i->cmd = XSTRDUP(MTYPE_TMP, cmd); i->cmd[strlen(cmd) - 1] = '\0'; listnode_add_sort(out, i); return; } if (++stackpos == CMD_ARGC_MAX) return; for (size_t i = 0; i < vector_active(gn->to); i++) { struct graph_node *gnext = vector_slot(gn->to, i); for (j = 0; j < stackpos; j++) if (stack[j] == gnext) break; if (j != stackpos) continue; stack[stackpos] = gnext; *appendp = '\0'; cmd_graph_permute(out, stack, stackpos, cmd); } } static struct list *cmd_graph_permutations(struct graph *graph) { char accumulate[2048] = ""; struct graph_node *stack[CMD_ARGC_MAX]; struct list *rv = list_new(); rv->cmp = cmd_permute_cmp; rv->del = cmd_permute_free; stack[0] = vector_slot(graph->nodes, 0); cmd_graph_permute(rv, stack, 0, accumulate); return rv; } extern vector cmdvec; DEFUN (grammar_findambig, grammar_findambig_cmd, "grammar find-ambiguous [{printall|nodescan}]", GRAMMAR_STR "Find ambiguous commands\n" "Print all permutations\n" "Scan all nodes\n") { struct list *commands; struct cmd_permute_item *prev = NULL, *cur = NULL; struct listnode *ln; int i, printall, scan, scannode = 0; int ambig = 0; i = 0; printall = argv_find(argv, argc, "printall", &i); i = 0; scan = argv_find(argv, argc, "nodescan", &i); if (scan && nodegraph_free) { graph_delete_graph(nodegraph_free); nodegraph_free = NULL; } if (!scan && !nodegraph) { vty_out(vty, "nodegraph uninitialized\r\n"); return CMD_WARNING_CONFIG_FAILED; } do { if (scan) { struct cmd_node *cnode = vector_slot(cmdvec, scannode++); if (!cnode) continue; cmd_finalize_node(cnode); nodegraph = cnode->cmdgraph; if (!nodegraph) continue; vty_out(vty, "scanning node %d (%s)\n", scannode - 1, cnode->name); } commands = cmd_graph_permutations(nodegraph); prev = NULL; for (ALL_LIST_ELEMENTS_RO(commands, ln, cur)) { int same = prev && !strcmp(prev->cmd, cur->cmd); if (printall && !same) vty_out(vty, "'%s' [%x]\n", cur->cmd, cur->el->daemon); if (same) { vty_out(vty, "'%s' AMBIGUOUS:\n", cur->cmd); vty_out(vty, " %s\n '%s'\n", prev->el->name, prev->el->string); vty_out(vty, " %s\n '%s'\n", cur->el->name, cur->el->string); vty_out(vty, "\n"); ambig++; } prev = cur; } list_delete(&commands); vty_out(vty, "\n"); } while (scan && scannode < LINK_PARAMS_NODE); vty_out(vty, "%d ambiguous commands found.\n", ambig); if (scan) nodegraph = NULL; return ambig == 0 ? CMD_SUCCESS : CMD_WARNING_CONFIG_FAILED; } DEFUN (grammar_init_graph, grammar_init_graph_cmd, "grammar init", GRAMMAR_STR "(re)initialize graph\n") { if (nodegraph_free) graph_delete_graph(nodegraph_free); nodegraph_free = NULL; init_cmdgraph(vty, &nodegraph); return CMD_SUCCESS; } DEFUN (grammar_access, grammar_access_cmd, "grammar access (0-65535)", GRAMMAR_STR "access node graph\n" "node number\n") { if (nodegraph_free) graph_delete_graph(nodegraph_free); nodegraph_free = NULL; struct cmd_node *cnode; cnode = vector_slot(cmdvec, atoi(argv[2]->arg)); if (!cnode) { vty_out(vty, "%% no such node\n"); return CMD_WARNING_CONFIG_FAILED; } vty_out(vty, "node %d\n", (int)cnode->node); cmd_finalize_node(cnode); nodegraph = cnode->cmdgraph; return CMD_SUCCESS; } /* this is called in vtysh.c to set up the testing shim */ void grammar_sandbox_init(void) { // install all enable elements install_element(ENABLE_NODE, &grammar_test_cmd); install_element(ENABLE_NODE, &grammar_test_show_cmd); install_element(ENABLE_NODE, &grammar_test_dot_cmd); install_element(ENABLE_NODE, &grammar_test_match_cmd); install_element(ENABLE_NODE, &grammar_test_complete_cmd); install_element(ENABLE_NODE, &grammar_test_doc_cmd); install_element(ENABLE_NODE, &grammar_findambig_cmd); install_element(ENABLE_NODE, &grammar_init_graph_cmd); install_element(ENABLE_NODE, &grammar_access_cmd); } /** * Pretty-prints a graph, assuming it is a tree. * * @param start the node to take as the root * @param level indent level for recursive calls, always pass 0 */ static void pretty_print_graph(struct vty *vty, struct graph_node *start, int level, int desc, struct graph_node **stack, size_t stackpos) { // print this node char tokennum[32]; struct cmd_token *tok = start->data; snprintf(tokennum, sizeof(tokennum), "%d?", tok->type); vty_out(vty, "%s", lookup_msg(tokennames, tok->type, NULL)); if (tok->text) vty_out(vty, ":\"%s\"", tok->text); if (tok->varname) vty_out(vty, " => %s", tok->varname); if (desc) vty_out(vty, " ?'%s'", tok->desc); vty_out(vty, " "); if (stackpos == CMD_ARGC_MAX) { vty_out(vty, " -aborting! (depth limit)\n"); return; } stack[stackpos++] = start; int numto = desc ? 2 : vector_active(start->to); if (numto) { if (numto > 1) vty_out(vty, "\n"); for (unsigned int i = 0; i < vector_active(start->to); i++) { struct graph_node *adj = vector_slot(start->to, i); // if we're listing multiple children, indent! if (numto > 1) for (int j = 0; j < level + 1; j++) vty_out(vty, " "); // if this node is a vararg, just print * if (adj == start) vty_out(vty, "*"); else if (((struct cmd_token *)adj->data)->type == END_TKN) vty_out(vty, "--END\n"); else { size_t k; for (k = 0; k < stackpos; k++) if (stack[k] == adj) { vty_out(vty, "< 1 ? level + 1 : level, desc, stack, stackpos); } } } else vty_out(vty, "\n"); } /** stuff that should go in command.c + command.h */ static void init_cmdgraph(struct vty *vty, struct graph **graph) { // initialize graph, add start noe *graph = graph_new(); nodegraph_free = *graph; struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL); graph_new_node(*graph, token, (void (*)(void *)) & cmd_token_del); if (vty) vty_out(vty, "initialized graph\n"); }