diff options
Diffstat (limited to 'lib/command.c')
| -rw-r--r-- | lib/command.c | 835 |
1 files changed, 366 insertions, 469 deletions
diff --git a/lib/command.c b/lib/command.c index cc52224523..09ffa6ce56 100644 --- a/lib/command.c +++ b/lib/command.c @@ -19,10 +19,9 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with GNU Zebra; see the file COPYING. If not, write to the Free - * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include <zebra.h> @@ -40,15 +39,82 @@ #include "workqueue.h" #include "vrf.h" #include "command_match.h" +#include "command_graph.h" #include "qobj.h" #include "defaults.h" DEFINE_MTYPE(LIB, HOST, "Host config") DEFINE_MTYPE(LIB, STRVEC, "String vector") -DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command Tokens") -DEFINE_MTYPE_STATIC(LIB, CMD_DESC, "Command Token Text") -DEFINE_MTYPE_STATIC(LIB, CMD_TEXT, "Command Token Help") -DEFINE_MTYPE(LIB, CMD_ARG, "Command Argument") +DEFINE_MTYPE(LIB, COMPLETION, "Completion item") + +const char *node_names[] = { + "auth", // AUTH_NODE, + "view", // VIEW_NODE, + "auth enable", // AUTH_ENABLE_NODE, + "enable", // ENABLE_NODE, + "config", // CONFIG_NODE, + "service", // SERVICE_NODE, + "debug", // DEBUG_NODE, + "vrf debug", // VRF_DEBUG_NODE, + "vnc debug", // DEBUG_VNC_NODE, + "aaa", // AAA_NODE, + "keychain", // KEYCHAIN_NODE, + "keychain key", // KEYCHAIN_KEY_NODE, + "logical-router", // NS_NODE, + "vrf", // VRF_NODE, + "interface", // INTERFACE_NODE, + "zebra", // ZEBRA_NODE, + "table", // TABLE_NODE, + "rip", // RIP_NODE, + "ripng", // RIPNG_NODE, + "babel", // BABEL_NODE, + "eigrp", // EIGRP_NODE, + "bgp", // BGP_NODE, + "bgp vpnv4", // BGP_VPNV4_NODE, + "bgp vpnv6", // BGP_VPNV6_NODE, + "bgp ipv4 unicast", // BGP_IPV4_NODE, + "bgp ipv4 multicast", // BGP_IPV4M_NODE, + "bgp ipv4 labeled unicast", // BGP_IPV4L_NODE, + "bgp ipv6", // BGP_IPV6_NODE, + "bgp ipv6 multicast", // BGP_IPV6M_NODE, + "bgp ipv6 labeled unicast", // BGP_IPV6L_NODE, + "bgp vrf policy", // BGP_VRF_POLICY_NODE, + "bgp vnc defaults", // BGP_VNC_DEFAULTS_NODE, + "bgp vnc nve", // BGP_VNC_NVE_GROUP_NODE, + "bgp vnc l2", // BGP_VNC_L2_GROUP_NODE, + "rfp defaults", // RFP_DEFAULTS_NODE, + "bgp evpn", // BGP_EVPN_NODE, + "ospf", // OSPF_NODE, + "ospf6", // OSPF6_NODE, + "ldp", // LDP_NODE, + "ldp ipv4", // LDP_IPV4_NODE, + "ldp ipv6", // LDP_IPV6_NODE, + "ldp ipv4 interface", // LDP_IPV4_IFACE_NODE, + "ldp ipv6 interface", // LDP_IPV6_IFACE_NODE, + "ldp l2vpn", // LDP_L2VPN_NODE, + "ldp", // LDP_PSEUDOWIRE_NODE, + "isis", // ISIS_NODE, + "pim", // PIM_NODE, + "masc", // MASC_NODE, + "irdp", // IRDP_NODE, + "static ip", // IP_NODE, + "ipv4 access list", // ACCESS_NODE, + "ipv4 prefix list", // PREFIX_NODE, + "ipv6 access list", // ACCESS_IPV6_NODE, + "ipv6 prefix list", // PREFIX_IPV6_NODE, + "as list", // AS_LIST_NODE, + "community list", // COMMUNITY_LIST_NODE, + "routemap", // RMAP_NODE, + "smux", // SMUX_NODE, + "dump", // DUMP_NODE, + "forwarding", // FORWARDING_NODE, + "protocol", // PROTOCOL_NODE, + "mpls", // MPLS_NODE, + "pw", // PW_NODE, + "vty", // VTY_NODE, + "link-params", // LINK_PARAMS_NODE, + "bgp evpn vni", // BGP_EVPN_VNI_NODE, +}; /* Command vector which includes some level of command lists. Normally each daemon maintains each own cmdvec. */ @@ -210,10 +276,10 @@ void install_node(struct cmd_node *node, int (*func)(struct vty *)) node->cmd_vector = vector_init(VECTOR_MIN_SIZE); // add start node struct cmd_token *token = - new_cmd_token(START_TKN, CMD_ATTR_NORMAL, NULL, NULL); + cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL); graph_new_node(node->cmdgraph, token, - (void (*)(void *)) & del_cmd_token); - node->cmd_hash = hash_create(cmd_hash_key, cmd_hash_cmp); + (void (*)(void *)) & cmd_token_del); + node->cmd_hash = hash_create(cmd_hash_key, cmd_hash_cmp, NULL); } /** @@ -280,261 +346,6 @@ const char *cmd_prompt(enum node_type node) return cnode->prompt; } -static bool cmd_nodes_link(struct graph_node *from, struct graph_node *to) -{ - for (size_t i = 0; i < vector_active(from->to); i++) - if (vector_slot(from->to, i) == to) - return true; - return false; -} - -static bool cmd_nodes_equal(struct graph_node *ga, struct graph_node *gb); - -/* returns a single node to be excluded as "next" from iteration - * - for JOIN_TKN, never continue back to the FORK_TKN - * - in all other cases, don't try the node itself (in case of "...") - */ -static inline struct graph_node *cmd_loopstop(struct graph_node *gn) -{ - struct cmd_token *tok = gn->data; - if (tok->type == JOIN_TKN) - return tok->forkjoin; - else - return gn; -} - -static bool cmd_subgraph_equal(struct graph_node *ga, struct graph_node *gb, - struct graph_node *a_join) -{ - size_t i, j; - struct graph_node *a_fork, *b_fork; - a_fork = cmd_loopstop(ga); - b_fork = cmd_loopstop(gb); - - if (vector_active(ga->to) != vector_active(gb->to)) - return false; - for (i = 0; i < vector_active(ga->to); i++) { - struct graph_node *cga = vector_slot(ga->to, i); - - for (j = 0; j < vector_active(gb->to); j++) { - struct graph_node *cgb = vector_slot(gb->to, i); - - if (cga == a_fork && cgb != b_fork) - continue; - if (cga == a_fork && cgb == b_fork) - break; - - if (cmd_nodes_equal(cga, cgb)) { - if (cga == a_join) - break; - if (cmd_subgraph_equal(cga, cgb, a_join)) - break; - } - } - if (j == vector_active(gb->to)) - return false; - } - return true; -} - -/* deep compare -- for FORK_TKN, the entire subgraph is compared. - * this is what's needed since we're not currently trying to partially - * merge subgraphs */ -static bool cmd_nodes_equal(struct graph_node *ga, struct graph_node *gb) -{ - struct cmd_token *a = ga->data, *b = gb->data; - - if (a->type != b->type || a->allowrepeat != b->allowrepeat) - return false; - if (a->type < SPECIAL_TKN && strcmp(a->text, b->text)) - return false; - /* one a ..., the other not. */ - if (cmd_nodes_link(ga, ga) != cmd_nodes_link(gb, gb)) - return false; - - switch (a->type) { - case RANGE_TKN: - return a->min == b->min && a->max == b->max; - - case FORK_TKN: - /* one is keywords, the other just option or selector ... */ - if (cmd_nodes_link(a->forkjoin, ga) - != cmd_nodes_link(b->forkjoin, gb)) - return false; - if (cmd_nodes_link(ga, a->forkjoin) - != cmd_nodes_link(gb, b->forkjoin)) - return false; - return cmd_subgraph_equal(ga, gb, a->forkjoin); - - default: - return true; - } -} - -static void cmd_fork_bump_attr(struct graph_node *gn, struct graph_node *join, - u_char attr) -{ - size_t i; - struct cmd_token *tok = gn->data; - struct graph_node *stop = cmd_loopstop(gn); - - tok->attr = attr; - for (i = 0; i < vector_active(gn->to); i++) { - struct graph_node *next = vector_slot(gn->to, i); - if (next == stop || next == join) - continue; - cmd_fork_bump_attr(next, join, attr); - } -} - -/* move an entire subtree from the temporary graph resulting from - * parse() into the permanent graph for the command node. - * - * this touches rather deeply into the graph code unfortunately. - */ -static void cmd_reparent_tree(struct graph *fromgraph, struct graph *tograph, - struct graph_node *node) -{ - struct graph_node *stop = cmd_loopstop(node); - size_t i; - - for (i = 0; i < vector_active(fromgraph->nodes); i++) - if (vector_slot(fromgraph->nodes, i) == node) { - /* agressive iteration punching through subgraphs - may - * hit some - * nodes twice. reparent only if found on old graph */ - vector_unset(fromgraph->nodes, i); - vector_set(tograph->nodes, node); - break; - } - - for (i = 0; i < vector_active(node->to); i++) { - struct graph_node *next = vector_slot(node->to, i); - if (next != stop) - cmd_reparent_tree(fromgraph, tograph, next); - } -} - -static void cmd_free_recur(struct graph *graph, struct graph_node *node, - struct graph_node *stop) -{ - struct graph_node *next, *nstop; - - for (size_t i = vector_active(node->to); i; i--) { - next = vector_slot(node->to, i - 1); - if (next == stop) - continue; - nstop = cmd_loopstop(next); - if (nstop != next) - cmd_free_recur(graph, next, nstop); - cmd_free_recur(graph, nstop, stop); - } - graph_delete_node(graph, node); -} - -static void cmd_free_node(struct graph *graph, struct graph_node *node) -{ - struct cmd_token *tok = node->data; - if (tok->type == JOIN_TKN) - cmd_free_recur(graph, tok->forkjoin, node); - graph_delete_node(graph, node); -} - -/* recursive graph merge. call with - * old ~= new - * (which holds true for old == START_TKN, new == START_TKN) - */ -static void cmd_merge_nodes(struct graph *oldgraph, struct graph *newgraph, - struct graph_node *old, struct graph_node *new, - int direction) -{ - struct cmd_token *tok; - struct graph_node *old_skip, *new_skip; - old_skip = cmd_loopstop(old); - new_skip = cmd_loopstop(new); - - assert(direction == 1 || direction == -1); - - tok = old->data; - tok->refcnt += direction; - - size_t j, i; - for (j = 0; j < vector_active(new->to); j++) { - struct graph_node *cnew = vector_slot(new->to, j); - if (cnew == new_skip) - continue; - - for (i = 0; i < vector_active(old->to); i++) { - struct graph_node *cold = vector_slot(old->to, i); - if (cold == old_skip) - continue; - - if (cmd_nodes_equal(cold, cnew)) { - struct cmd_token *told = cold->data, - *tnew = cnew->data; - - if (told->type == END_TKN) { - if (direction < 0) { - graph_delete_node( - oldgraph, - vector_slot(cold->to, - 0)); - graph_delete_node(oldgraph, - cold); - } else - /* force no-match handling to - * install END_TKN */ - i = vector_active(old->to); - break; - } - - /* the entire fork compared as equal, we - * continue after it. */ - if (told->type == FORK_TKN) { - if (tnew->attr < told->attr - && direction > 0) - cmd_fork_bump_attr( - cold, told->forkjoin, - tnew->attr); - /* XXX: no reverse bump on uninstall */ - told = (cold = told->forkjoin)->data; - tnew = (cnew = tnew->forkjoin)->data; - } - if (tnew->attr < told->attr) - told->attr = tnew->attr; - - cmd_merge_nodes(oldgraph, newgraph, cold, cnew, - direction); - break; - } - } - /* nothing found => add new to old */ - if (i == vector_active(old->to) && direction > 0) { - assert(vector_count(cnew->from) - == cmd_nodes_link(cnew, cnew) - ? 2 - : 1); - graph_remove_edge(new, cnew); - - cmd_reparent_tree(newgraph, oldgraph, cnew); - - graph_add_edge(old, cnew); - } - } - - if (!tok->refcnt) - cmd_free_node(oldgraph, old); -} - -void cmd_merge_graphs(struct graph *old, struct graph *new, int direction) -{ - assert(vector_active(old->nodes) >= 1); - assert(vector_active(new->nodes) >= 1); - - cmd_merge_nodes(old, new, vector_slot(old->nodes, 0), - vector_slot(new->nodes, 0), direction); -} - /* Install a command into a node. */ void install_element(enum node_type ntype, struct cmd_element *cmd) { @@ -553,6 +364,8 @@ void install_element(enum node_type ntype, struct cmd_element *cmd) fprintf(stderr, "Command node %d doesn't exist, please check it\n", ntype); + fprintf(stderr, + "Have you called install_node before this install_element?\n"); exit(EXIT_FAILURE); } @@ -567,11 +380,12 @@ void install_element(enum node_type ntype, struct cmd_element *cmd) struct graph *graph = graph_new(); struct cmd_token *token = - new_cmd_token(START_TKN, CMD_ATTR_NORMAL, NULL, NULL); - graph_new_node(graph, token, (void (*)(void *)) & del_cmd_token); + cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL); + graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del); - command_parse_format(graph, cmd); - cmd_merge_graphs(cnode->cmdgraph, graph, +1); + cmd_graph_parse(graph, cmd); + cmd_graph_names(graph); + cmd_graph_merge(cnode->cmdgraph, graph, +1); graph_delete_graph(graph); vector_set(cnode->cmd_vector, cmd); @@ -597,6 +411,8 @@ void uninstall_element(enum node_type ntype, struct cmd_element *cmd) fprintf(stderr, "Command node %d doesn't exist, please check it\n", ntype); + fprintf(stderr, + "Have you called install_node before this install_element?\n"); exit(EXIT_FAILURE); } @@ -611,11 +427,12 @@ void uninstall_element(enum node_type ntype, struct cmd_element *cmd) struct graph *graph = graph_new(); struct cmd_token *token = - new_cmd_token(START_TKN, CMD_ATTR_NORMAL, NULL, NULL); - graph_new_node(graph, token, (void (*)(void *)) & del_cmd_token); + cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL); + graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del); - command_parse_format(graph, cmd); - cmd_merge_graphs(cnode->cmdgraph, graph, -1); + cmd_graph_parse(graph, cmd); + cmd_graph_names(graph); + cmd_graph_merge(cnode->cmdgraph, graph, -1); graph_delete_graph(graph); if (ntype == VIEW_NODE) @@ -653,29 +470,25 @@ static char *zencrypt(const char *passwd) static int config_write_host(struct vty *vty) { if (host.name) - vty_out(vty, "hostname %s%s", host.name, VTY_NEWLINE); + vty_out(vty, "hostname %s\n", host.name); if (host.encrypt) { if (host.password_encrypt) - vty_out(vty, "password 8 %s%s", host.password_encrypt, - VTY_NEWLINE); + vty_out(vty, "password 8 %s\n", host.password_encrypt); if (host.enable_encrypt) - vty_out(vty, "enable password 8 %s%s", - host.enable_encrypt, VTY_NEWLINE); + vty_out(vty, "enable password 8 %s\n", + host.enable_encrypt); } else { if (host.password) - vty_out(vty, "password %s%s", host.password, - VTY_NEWLINE); + vty_out(vty, "password %s\n", host.password); if (host.enable) - vty_out(vty, "enable password %s%s", host.enable, - VTY_NEWLINE); + vty_out(vty, "enable password %s\n", host.enable); } if (zlog_default->default_lvl != LOG_DEBUG) { - vty_out(vty, "! N.B. The 'log trap' command is deprecated.%s", - VTY_NEWLINE); - vty_out(vty, "log trap %s%s", - zlog_priority[zlog_default->default_lvl], VTY_NEWLINE); + vty_out(vty, "! N.B. The 'log trap' command is deprecated.\n"); + vty_out(vty, "log trap %s\n", + zlog_priority[zlog_default->default_lvl]); } if (host.logfile @@ -686,7 +499,7 @@ static int config_write_host(struct vty *vty) vty_out(vty, " %s", zlog_priority [zlog_default->maxlvl[ZLOG_DEST_FILE]]); - vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, "\n"); } if (zlog_default->maxlvl[ZLOG_DEST_STDOUT] != ZLOG_DISABLED) { @@ -696,16 +509,15 @@ static int config_write_host(struct vty *vty) vty_out(vty, " %s", zlog_priority[zlog_default->maxlvl [ZLOG_DEST_STDOUT]]); - vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, "\n"); } if (zlog_default->maxlvl[ZLOG_DEST_MONITOR] == ZLOG_DISABLED) - vty_out(vty, "no log monitor%s", VTY_NEWLINE); + vty_out(vty, "no log monitor\n"); else if (zlog_default->maxlvl[ZLOG_DEST_MONITOR] != zlog_default->default_lvl) - vty_out(vty, "log monitor %s%s", - zlog_priority[zlog_default->maxlvl[ZLOG_DEST_MONITOR]], - VTY_NEWLINE); + vty_out(vty, "log monitor %s\n", + zlog_priority[zlog_default->maxlvl[ZLOG_DEST_MONITOR]]); if (zlog_default->maxlvl[ZLOG_DEST_SYSLOG] != ZLOG_DISABLED) { vty_out(vty, "log syslog"); @@ -714,35 +526,33 @@ static int config_write_host(struct vty *vty) vty_out(vty, " %s", zlog_priority[zlog_default->maxlvl [ZLOG_DEST_SYSLOG]]); - vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, "\n"); } if (zlog_default->facility != LOG_DAEMON) - vty_out(vty, "log facility %s%s", - facility_name(zlog_default->facility), VTY_NEWLINE); + vty_out(vty, "log facility %s\n", + facility_name(zlog_default->facility)); if (zlog_default->record_priority == 1) - vty_out(vty, "log record-priority%s", VTY_NEWLINE); + vty_out(vty, "log record-priority\n"); if (zlog_default->timestamp_precision > 0) - vty_out(vty, "log timestamp precision %d%s", - zlog_default->timestamp_precision, VTY_NEWLINE); + vty_out(vty, "log timestamp precision %d\n", + zlog_default->timestamp_precision); if (host.advanced) - vty_out(vty, "service advanced-vty%s", VTY_NEWLINE); + vty_out(vty, "service advanced-vty\n"); if (host.encrypt) - vty_out(vty, "service password-encryption%s", VTY_NEWLINE); + vty_out(vty, "service password-encryption\n"); if (host.lines >= 0) - vty_out(vty, "service terminal-length %d%s", host.lines, - VTY_NEWLINE); + vty_out(vty, "service terminal-length %d\n", host.lines); if (host.motdfile) - vty_out(vty, "banner motd file %s%s", host.motdfile, - VTY_NEWLINE); + vty_out(vty, "banner motd file %s\n", host.motdfile); else if (!host.motd) - vty_out(vty, "no banner motd%s", VTY_NEWLINE); + vty_out(vty, "no banner motd\n"); return 1; } @@ -757,8 +567,7 @@ static struct graph *cmd_node_graph(vector v, enum node_type ntype) static int cmd_try_do_shortcut(enum node_type node, char *first_word) { if (first_word != NULL && node != AUTH_NODE && node != VIEW_NODE - && node != AUTH_ENABLE_NODE && node != ENABLE_NODE - && 0 == strcmp("do", first_word)) + && node != AUTH_ENABLE_NODE && 0 == strcmp("do", first_word)) return 1; return 0; } @@ -894,6 +703,114 @@ vector cmd_describe_command(vector vline, struct vty *vty, int *status) return cmd_complete_command_real(vline, vty, status); } +static struct list *varhandlers = NULL; + +void cmd_variable_complete(struct cmd_token *token, const char *arg, + vector comps) +{ + struct listnode *ln; + const struct cmd_variable_handler *cvh; + size_t i, argsz; + vector tmpcomps; + + tmpcomps = arg ? vector_init(VECTOR_MIN_SIZE) : comps; + + for (ALL_LIST_ELEMENTS_RO(varhandlers, ln, cvh)) { + if (cvh->tokenname && strcmp(cvh->tokenname, token->text)) + continue; + if (cvh->varname && (!token->varname + || strcmp(cvh->varname, token->varname))) + continue; + cvh->completions(tmpcomps, token); + break; + } + + if (!arg) + return; + + argsz = strlen(arg); + for (i = vector_active(tmpcomps); i; i--) { + char *item = vector_slot(tmpcomps, i - 1); + if (strlen(item) >= argsz && !strncmp(item, arg, argsz)) + vector_set(comps, item); + else + XFREE(MTYPE_COMPLETION, item); + } + vector_free(tmpcomps); +} + +#define AUTOCOMP_INDENT 5 + +char *cmd_variable_comp2str(vector comps, unsigned short cols) +{ + size_t bsz = 16; + char *buf = XCALLOC(MTYPE_TMP, bsz); + int lc = AUTOCOMP_INDENT; + size_t cs = AUTOCOMP_INDENT; + size_t itemlen; + snprintf(buf, bsz, "%*s", AUTOCOMP_INDENT, ""); + for (size_t j = 0; j < vector_active(comps); j++) { + char *item = vector_slot(comps, j); + itemlen = strlen(item); + + if (cs + itemlen + AUTOCOMP_INDENT + 3 >= bsz) + buf = XREALLOC(MTYPE_TMP, buf, (bsz *= 2)); + + if (lc + itemlen + 1 >= cols) { + cs += snprintf(&buf[cs], bsz - cs, "\n%*s", + AUTOCOMP_INDENT, ""); + lc = AUTOCOMP_INDENT; + } + + size_t written = snprintf(&buf[cs], bsz - cs, "%s ", item); + lc += written; + cs += written; + XFREE(MTYPE_COMPLETION, item); + vector_set_index(comps, j, NULL); + } + return buf; +} + +void cmd_variable_handler_register(const struct cmd_variable_handler *cvh) +{ + if (!varhandlers) + return; + + for (; cvh->completions; cvh++) + listnode_add(varhandlers, (void *)cvh); +} + +DEFUN_HIDDEN (autocomplete, + autocomplete_cmd, + "autocomplete TYPE TEXT VARNAME", + "Autocompletion handler (internal, for vtysh)\n" + "cmd_token->type\n" + "cmd_token->text\n" + "cmd_token->varname\n") +{ + struct cmd_token tok; + vector comps = vector_init(32); + size_t i; + + memset(&tok, 0, sizeof(tok)); + tok.type = atoi(argv[1]->arg); + tok.text = argv[2]->arg; + tok.varname = argv[3]->arg; + if (!strcmp(tok.varname, "-")) + tok.varname = NULL; + + cmd_variable_complete(&tok, NULL, comps); + + for (i = 0; i < vector_active(comps); i++) { + char *text = vector_slot(comps, i); + vty_out(vty, "%s\n", text); + XFREE(MTYPE_COMPLETION, text); + } + + vector_free(comps); + return CMD_SUCCESS; +} + /** * Generate possible tab-completions for the given input. This function only * returns results that would result in a valid command if used as Readline @@ -936,7 +853,13 @@ char **cmd_complete_command(vector vline, struct vty *vty, int *status) i++) { struct cmd_token *token = vector_slot(initial_comps, i); if (token->type == WORD_TKN) - vector_set(comps, token); + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, + token->text)); + else if (IS_VARYING_TOKEN(token->type)) { + const char *ref = vector_lookup( + vline, vector_active(vline) - 1); + cmd_variable_complete(token, ref, comps); + } } vector_free(initial_comps); @@ -957,9 +880,7 @@ char **cmd_complete_command(vector vline, struct vty *vty, int *status) (vector_active(comps) + 1) * sizeof(char *)); unsigned int i; for (i = 0; i < vector_active(comps); i++) { - struct cmd_token *token = vector_slot(comps, i); - ret[i] = XSTRDUP(MTYPE_TMP, token->text); - vector_unset(comps, i); + ret[i] = vector_slot(comps, i); } // set the last element to NULL, because this array is used in // a Readline completion_generator function which expects NULL @@ -999,11 +920,16 @@ enum node_type node_parent(enum node_type node) case BGP_VNC_L2_GROUP_NODE: case BGP_IPV4_NODE: case BGP_IPV4M_NODE: + case BGP_IPV4L_NODE: case BGP_IPV6_NODE: case BGP_IPV6M_NODE: case BGP_EVPN_NODE: + case BGP_IPV6L_NODE: ret = BGP_NODE; break; + case BGP_EVPN_VNI_NODE: + ret = BGP_EVPN_NODE; + break; case KEYCHAIN_KEY_NODE: ret = KEYCHAIN_NODE; break; @@ -1270,9 +1196,8 @@ DEFUN (config_terminal, if (vty_config_lock(vty)) vty->node = CONFIG_NODE; else { - vty_out(vty, "VTY configuration is locked by other VTY%s", - VTY_NEWLINE); - return CMD_WARNING; + vty_out(vty, "VTY configuration is locked by other VTY\n"); + return CMD_WARNING_CONFIG_FAILED; } return CMD_SUCCESS; } @@ -1335,6 +1260,8 @@ void cmd_exit(struct vty *vty) case ZEBRA_NODE: case BGP_NODE: case RIP_NODE: + case EIGRP_NODE: + case BABEL_NODE: case RIPNG_NODE: case OSPF_NODE: case OSPF6_NODE: @@ -1350,6 +1277,7 @@ void cmd_exit(struct vty *vty) break; case BGP_IPV4_NODE: case BGP_IPV4M_NODE: + case BGP_IPV4L_NODE: case BGP_VPNV4_NODE: case BGP_VPNV6_NODE: case BGP_VRF_POLICY_NODE: @@ -1359,8 +1287,12 @@ void cmd_exit(struct vty *vty) case BGP_IPV6_NODE: case BGP_IPV6M_NODE: case BGP_EVPN_NODE: + case BGP_IPV6L_NODE: vty->node = BGP_NODE; break; + case BGP_EVPN_VNI_NODE: + vty->node = BGP_EVPN_NODE; + break; case LDP_IPV4_NODE: case LDP_IPV6_NODE: vty->node = LDP_NODE; @@ -1414,6 +1346,8 @@ DEFUN (config_end, case ZEBRA_NODE: case RIP_NODE: case RIPNG_NODE: + case EIGRP_NODE: + case BABEL_NODE: case BGP_NODE: case BGP_VRF_POLICY_NODE: case BGP_VNC_DEFAULTS_NODE: @@ -1423,9 +1357,12 @@ DEFUN (config_end, case BGP_VPNV6_NODE: case BGP_IPV4_NODE: case BGP_IPV4M_NODE: + case BGP_IPV4L_NODE: case BGP_IPV6_NODE: case BGP_IPV6M_NODE: case BGP_EVPN_NODE: + case BGP_EVPN_VNI_NODE: + case BGP_IPV6L_NODE: case RMAP_NODE: case OSPF_NODE: case OSPF6_NODE: @@ -1459,11 +1396,10 @@ DEFUN (show_version, SHOW_STR "Displays zebra version\n") { - vty_out(vty, "%s %s (%s).%s", FRR_FULL_NAME, FRR_VERSION, - host.name ? host.name : "", VTY_NEWLINE); - vty_out(vty, "%s%s%s", FRR_COPYRIGHT, GIT_INFO, VTY_NEWLINE); - vty_out(vty, "configured with:%s %s%s", VTY_NEWLINE, FRR_CONFIG_ARGS, - VTY_NEWLINE); + vty_out(vty, "%s %s (%s).\n", FRR_FULL_NAME, FRR_VERSION, + host.name ? host.name : ""); + vty_out(vty, "%s%s\n", FRR_COPYRIGHT, GIT_INFO); + vty_out(vty, "configured with:\n %s\n", FRR_CONFIG_ARGS); return CMD_SUCCESS; } @@ -1477,12 +1413,6 @@ DEFUN (frr_version_defaults, "set of configuration defaults used\n" "version string\n") { - if (vty->type == VTY_TERM || vty->type == VTY_SHELL) - /* only print this when the user tries to do run it */ - vty_out(vty, - "%% NOTE: This command currently does nothing.%s" - "%% It is written to the configuration for future reference.%s", - VTY_NEWLINE, VTY_NEWLINE); return CMD_SUCCESS; } @@ -1493,21 +1423,18 @@ DEFUN (config_help, "Description of the interactive help system\n") { vty_out(vty, - "Quagga VTY provides advanced help feature. When you need help,%s\ -anytime at the command line please press '?'.%s\ -%s\ -If nothing matches, the help list will be empty and you must backup%s\ - until entering a '?' shows the available options.%s\ -Two styles of help are provided:%s\ -1. Full help is available when you are ready to enter a%s\ -command argument (e.g. 'show ?') and describes each possible%s\ -argument.%s\ -2. Partial help is provided when an abbreviated argument is entered%s\ - and you want to know what arguments match the input%s\ - (e.g. 'show me?'.)%s%s", - VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, - VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, - VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE); + "Quagga VTY provides advanced help feature. When you need help,\n\ +anytime at the command line please press '?'.\n\ +\n\ +If nothing matches, the help list will be empty and you must backup\n\ + until entering a '?' shows the available options.\n\ +Two styles of help are provided:\n\ +1. Full help is available when you are ready to enter a\n\ +command argument (e.g. 'show ?') and describes each possible\n\ +argument.\n\ +2. Partial help is provided when an abbreviated argument is entered\n\ + and you want to know what arguments match the input\n\ + (e.g. 'show me?'.)\n\n"); return CMD_SUCCESS; } @@ -1538,7 +1465,7 @@ static void permute(struct graph_node *start, struct vty *vty) } if (gn == start) vty_out(vty, "..."); - vty_out(vty, VTY_NEWLINE); + vty_out(vty, "\n"); } else { bool skip = false; if (stok->type == FORK_TKN && tok->type != FORK_TKN) @@ -1568,8 +1495,7 @@ int cmd_list_cmds(struct vty *vty, int do_permute) if ((element = vector_slot(node->cmd_vector, i)) && element->attr != CMD_ATTR_DEPRECATED && element->attr != CMD_ATTR_HIDDEN) - vty_out(vty, " %s%s", element->string, - VTY_NEWLINE); + vty_out(vty, " %s\n", element->string); } return CMD_SUCCESS; } @@ -1600,24 +1526,23 @@ static void vty_write_config(struct vty *vty) struct cmd_node *node; if (vty->type == VTY_TERM) { - vty_out(vty, "%sCurrent configuration:%s", VTY_NEWLINE, - VTY_NEWLINE); - vty_out(vty, "!%s", VTY_NEWLINE); + vty_out(vty, "\nCurrent configuration:\n"); + vty_out(vty, "!\n"); } - vty_out(vty, "frr version %s%s", FRR_VER_SHORT, VTY_NEWLINE); - vty_out(vty, "frr defaults %s%s", DFLT_NAME, VTY_NEWLINE); - vty_out(vty, "!%s", VTY_NEWLINE); + vty_out(vty, "frr version %s\n", FRR_VER_SHORT); + vty_out(vty, "frr defaults %s\n", DFLT_NAME); + vty_out(vty, "!\n"); for (i = 0; i < vector_active(cmdvec); i++) if ((node = vector_slot(cmdvec, i)) && node->func && (node->vtysh || vty->type != VTY_SHELL)) { if ((*node->func)(vty)) - vty_out(vty, "!%s", VTY_NEWLINE); + vty_out(vty, "!\n"); } if (vty->type == VTY_TERM) { - vty_out(vty, "end%s", VTY_NEWLINE); + vty_out(vty, "end\n"); } } @@ -1641,8 +1566,8 @@ DEFUN (config_write, struct stat conf_stat; // if command was 'write terminal' or 'show running-config' - if (argc == 2 && (!strcmp(argv[idx_type]->text, "terminal") - || !strcmp(argv[0]->text, "show"))) { + if (argc == 2 && (strmatch(argv[idx_type]->text, "terminal") + || strmatch(argv[0]->text, "show"))) { vty_write_config(vty); return CMD_SUCCESS; } @@ -1652,8 +1577,8 @@ DEFUN (config_write, /* Check and see if we are operating under vtysh configuration */ if (host.config == NULL) { - vty_out(vty, "Can't save to configuration file, using vtysh.%s", - VTY_NEWLINE); + vty_out(vty, + "Can't save to configuration file, using vtysh.\n"); return CMD_WARNING; } @@ -1685,14 +1610,13 @@ DEFUN (config_write, /* Open file to configuration write. */ fd = mkstemp(config_file_tmp); if (fd < 0) { - vty_out(vty, "Can't open configuration file %s.%s", - config_file_tmp, VTY_NEWLINE); + vty_out(vty, "Can't open configuration file %s.\n", + config_file_tmp); goto finished; } if (fchmod(fd, CONFIGFILE_MASK) != 0) { - vty_out(vty, "Can't chmod configuration file %s: %s (%d).%s", - config_file_tmp, safe_strerror(errno), errno, - VTY_NEWLINE); + vty_out(vty, "Can't chmod configuration file %s: %s (%d).\n", + config_file_tmp, safe_strerror(errno), errno); goto finished; } @@ -1712,28 +1636,28 @@ DEFUN (config_write, if (unlink(config_file_sav) != 0) if (errno != ENOENT) { vty_out(vty, - "Can't unlink backup configuration file %s.%s", - config_file_sav, VTY_NEWLINE); + "Can't unlink backup configuration file %s.\n", + config_file_sav); goto finished; } if (link(config_file, config_file_sav) != 0) { vty_out(vty, - "Can't backup old configuration file %s.%s", - config_file_sav, VTY_NEWLINE); + "Can't backup old configuration file %s.\n", + config_file_sav); goto finished; } if (dirfd >= 0) fsync(dirfd); } if (rename(config_file_tmp, config_file) != 0) { - vty_out(vty, "Can't save configuration file %s.%s", config_file, - VTY_NEWLINE); + vty_out(vty, "Can't save configuration file %s.\n", + config_file); goto finished; } if (dirfd >= 0) fsync(dirfd); - vty_out(vty, "Configuration saved to %s%s", config_file, VTY_NEWLINE); + vty_out(vty, "Configuration saved to %s\n", config_file); ret = CMD_SUCCESS; finished: @@ -1787,8 +1711,8 @@ DEFUN (show_startup_config, confp = fopen(host.config, "r"); if (confp == NULL) { - vty_out(vty, "Can't open configuration file [%s] due to '%s'%s", - host.config, safe_strerror(errno), VTY_NEWLINE); + vty_out(vty, "Can't open configuration file [%s] due to '%s'\n", + host.config, safe_strerror(errno)); return CMD_WARNING; } @@ -1799,7 +1723,7 @@ DEFUN (show_startup_config, cp++; *cp = '\0'; - vty_out(vty, "%s%s", buf, VTY_NEWLINE); + vty_out(vty, "%s\n", buf); } fclose(confp); @@ -1824,9 +1748,8 @@ DEFUN (config_hostname, struct cmd_token *word = argv[1]; if (!isalpha((int)word->arg[0])) { - vty_out(vty, "Please specify string starting with alphabet%s", - VTY_NEWLINE); - return CMD_WARNING; + vty_out(vty, "Please specify string starting with alphabet\n"); + return CMD_WARNING_CONFIG_FAILED; } return cmd_hostname_set(word->arg); @@ -1866,9 +1789,8 @@ DEFUN (config_password, if (!isalnum(argv[idx_8]->arg[0])) { vty_out(vty, - "Please specify string starting with alphanumeric%s", - VTY_NEWLINE); - return CMD_WARNING; + "Please specify string starting with alphanumeric\n"); + return CMD_WARNING_CONFIG_FAILED; } if (host.password) @@ -1912,16 +1834,15 @@ DEFUN (config_enable_password, return CMD_SUCCESS; } else { - vty_out(vty, "Unknown encryption type.%s", VTY_NEWLINE); - return CMD_WARNING; + vty_out(vty, "Unknown encryption type.\n"); + return CMD_WARNING_CONFIG_FAILED; } } if (!isalnum(argv[idx_8]->arg[0])) { vty_out(vty, - "Please specify string starting with alphanumeric%s", - VTY_NEWLINE); - return CMD_WARNING; + "Please specify string starting with alphanumeric\n"); + return CMD_WARNING_CONFIG_FAILED; } if (host.enable) @@ -2017,15 +1938,8 @@ DEFUN (config_terminal_length, "Number of lines on screen (0 for no pausing)\n") { int idx_number = 2; - int lines; - char *endptr = NULL; - lines = strtol(argv[idx_number]->arg, &endptr, 10); - if (lines < 0 || lines > 512 || *endptr != '\0') { - vty_out(vty, "length is malformed%s", VTY_NEWLINE); - return CMD_WARNING; - } - vty->lines = lines; + vty->lines = atoi(argv[idx_number]->arg); return CMD_SUCCESS; } @@ -2049,15 +1963,8 @@ DEFUN (service_terminal_length, "Number of lines of VTY (0 means no line control)\n") { int idx_number = 2; - int lines; - char *endptr = NULL; - lines = strtol(argv[idx_number]->arg, &endptr, 10); - if (lines < 0 || lines > 512 || *endptr != '\0') { - vty_out(vty, "length is malformed%s", VTY_NEWLINE); - return CMD_WARNING; - } - host.lines = lines; + host.lines = atoi(argv[idx_number]->arg); return CMD_SUCCESS; } @@ -2082,9 +1989,8 @@ DEFUN_HIDDEN (do_echo, { char *message; - vty_out(vty, "%s%s", - ((message = argv_concat(argv, argc, 1)) ? message : ""), - VTY_NEWLINE); + vty_out(vty, "%s\n", + ((message = argv_concat(argv, argc, 1)) ? message : "")); if (message) XFREE(MTYPE_TMP, message); return CMD_SUCCESS; @@ -2128,7 +2034,7 @@ DEFUN (show_logging, vty_out(vty, "level %s, facility %s, ident %s", zlog_priority[zl->maxlvl[ZLOG_DEST_SYSLOG]], facility_name(zl->facility), zl->ident); - vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, "\n"); vty_out(vty, "Stdout logging: "); if (zl->maxlvl[ZLOG_DEST_STDOUT] == ZLOG_DISABLED) @@ -2136,7 +2042,7 @@ DEFUN (show_logging, else vty_out(vty, "level %s", zlog_priority[zl->maxlvl[ZLOG_DEST_STDOUT]]); - vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, "\n"); vty_out(vty, "Monitor logging: "); if (zl->maxlvl[ZLOG_DEST_MONITOR] == ZLOG_DISABLED) @@ -2144,7 +2050,7 @@ DEFUN (show_logging, else vty_out(vty, "level %s", zlog_priority[zl->maxlvl[ZLOG_DEST_MONITOR]]); - vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, "\n"); vty_out(vty, "File logging: "); if ((zl->maxlvl[ZLOG_DEST_FILE] == ZLOG_DISABLED) || !zl->fp) @@ -2153,13 +2059,12 @@ DEFUN (show_logging, vty_out(vty, "level %s, filename %s", zlog_priority[zl->maxlvl[ZLOG_DEST_FILE]], zl->filename); - vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, "\n"); - vty_out(vty, "Protocol name: %s%s", zl->protoname, VTY_NEWLINE); - vty_out(vty, "Record priority: %s%s", - (zl->record_priority ? "enabled" : "disabled"), VTY_NEWLINE); - vty_out(vty, "Timestamp precision: %d%s", zl->timestamp_precision, - VTY_NEWLINE); + vty_out(vty, "Protocol name: %s\n", zl->protoname); + vty_out(vty, "Record priority: %s\n", + (zl->record_priority ? "enabled" : "disabled")); + vty_out(vty, "Timestamp precision: %d\n", zl->timestamp_precision); return CMD_SUCCESS; } @@ -2243,13 +2148,13 @@ static int set_log_file(struct vty *vty, const char *fname, int loglevel) if (getcwd(cwd, MAXPATHLEN) == NULL) { zlog_err("config_log_file: Unable to alloc mem!"); - return CMD_WARNING; + return CMD_WARNING_CONFIG_FAILED; } if ((p = XMALLOC(MTYPE_TMP, strlen(cwd) + strlen(fname) + 2)) == NULL) { zlog_err("config_log_file: Unable to alloc mem!"); - return CMD_WARNING; + return CMD_WARNING_CONFIG_FAILED; } sprintf(p, "%s/%s", cwd, fname); fullpath = p; @@ -2263,7 +2168,7 @@ static int set_log_file(struct vty *vty, const char *fname, int loglevel) if (!ret) { vty_out(vty, "can't open logfile %s\n", fname); - return CMD_WARNING; + return CMD_WARNING_CONFIG_FAILED; } if (host.logfile) @@ -2306,7 +2211,6 @@ DEFUN (no_config_log_file, "Logging control\n" "Cancel logging to file\n" "Logging file name\n" - "Logging file name\n" "Logging level\n") { zlog_reset_file(); @@ -2439,9 +2343,8 @@ DEFUN (config_log_timestamp_precision, "Number of subsecond digits\n") { int idx_number = 3; - VTY_GET_INTEGER_RANGE("Timestamp Precision", - zlog_default->timestamp_precision, - argv[idx_number]->arg, 0, 6); + zlog_default->timestamp_precision = + strtoul(argv[idx_number]->arg, NULL, 10); return CMD_SUCCESS; } @@ -2473,7 +2376,7 @@ int cmd_banner_motd_file(const char *file) XFREE(MTYPE_HOST, host.motdfile); host.motdfile = XSTRDUP(MTYPE_HOST, file); } else - success = CMD_WARNING; + success = CMD_WARNING_CONFIG_FAILED; return success; } @@ -2492,7 +2395,7 @@ DEFUN (banner_motd_file, if (cmd == CMD_ERR_NO_FILE) vty_out(vty, "%s does not exist", filename); - else if (cmd == CMD_WARNING) + else if (cmd == CMD_WARNING_CONFIG_FAILED) vty_out(vty, "%s must be in %s", filename, SYSCONFDIR); return cmd; @@ -2523,6 +2426,35 @@ DEFUN (no_banner_motd, return CMD_SUCCESS; } +DEFUN(find, + find_cmd, + "find COMMAND...", + "Find CLI command containing text\n" + "Text to search for\n") +{ + char *text = argv_concat(argv, argc, 1); + const struct cmd_node *node; + const struct cmd_element *cli; + vector clis; + + for (unsigned int i = 0; i < vector_active(cmdvec); i++) { + node = vector_slot(cmdvec, i); + if (!node) + continue; + clis = node->cmd_vector; + for (unsigned int j = 0; j < vector_active(clis); j++) { + cli = vector_slot(clis, j); + if (strcasestr(cli->string, text)) + vty_out(vty, " (%s) %s\n", + node_names[node->node], cli->string); + } + } + + XFREE(MTYPE_TMP, text); + + return CMD_SUCCESS; +} + /* Set config filename. Called from vty.c */ void host_config_set(const char *filename) { @@ -2543,9 +2475,12 @@ void install_default(enum node_type node) install_element(node, &config_end_cmd); install_element(node, &config_help_cmd); install_element(node, &config_list_cmd); + install_element(node, &find_cmd); install_element(node, &config_write_cmd); install_element(node, &show_running_config_cmd); + + install_element(node, &autocomplete_cmd); } /* Initialize command interface. Install basic nodes and commands. @@ -2555,8 +2490,13 @@ void install_default(enum node_type node) * terminal = -1 -- watchfrr / no logging, but minimal config control */ void cmd_init(int terminal) { + if (array_size(node_names) != NODE_TYPE_MAX) + assert(!"Update the CLI node description array!"); + qobj_init(); + varhandlers = list_new(); + /* Allocate initial top vector of commands. */ cmdvec = vector_init(VECTOR_MIN_SIZE); @@ -2580,6 +2520,8 @@ void cmd_init(int terminal) /* Each node's basic commands. */ install_element(VIEW_NODE, &show_version_cmd); + install_element(ENABLE_NODE, &show_startup_config_cmd); + if (terminal) { install_element(VIEW_NODE, &config_list_cmd); install_element(VIEW_NODE, &config_exit_cmd); @@ -2591,24 +2533,22 @@ void cmd_init(int terminal) install_element(VIEW_NODE, &show_logging_cmd); install_element(VIEW_NODE, &show_commandtree_cmd); install_element(VIEW_NODE, &echo_cmd); - } + install_element(VIEW_NODE, &autocomplete_cmd); + install_element(VIEW_NODE, &find_cmd); - if (terminal) { install_element(ENABLE_NODE, &config_end_cmd); install_element(ENABLE_NODE, &config_disable_cmd); install_element(ENABLE_NODE, &config_terminal_cmd); install_element(ENABLE_NODE, ©_runningconf_startupconf_cmd); install_element(ENABLE_NODE, &config_write_cmd); install_element(ENABLE_NODE, &show_running_config_cmd); - } - install_element(ENABLE_NODE, &show_startup_config_cmd); - - if (terminal) { install_element(ENABLE_NODE, &config_logmsg_cmd); + install_default(CONFIG_NODE); thread_cmd_init(); workqueue_cmd_init(); + hash_cmd_init(); } install_element(CONFIG_NODE, &hostname_cmd); @@ -2655,50 +2595,6 @@ void cmd_init(int terminal) #endif } -struct cmd_token *new_cmd_token(enum cmd_token_type type, u_char attr, - const char *text, const char *desc) -{ - struct cmd_token *token = - XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_token)); - token->type = type; - token->attr = attr; - token->text = text ? XSTRDUP(MTYPE_CMD_TEXT, text) : NULL; - token->desc = desc ? XSTRDUP(MTYPE_CMD_DESC, desc) : NULL; - token->refcnt = 1; - token->arg = NULL; - token->allowrepeat = false; - - return token; -} - -void del_cmd_token(struct cmd_token *token) -{ - if (!token) - return; - - if (token->text) - XFREE(MTYPE_CMD_TEXT, token->text); - if (token->desc) - XFREE(MTYPE_CMD_DESC, token->desc); - if (token->arg) - XFREE(MTYPE_CMD_ARG, token->arg); - - XFREE(MTYPE_CMD_TOKENS, token); -} - -struct cmd_token *copy_cmd_token(struct cmd_token *token) -{ - struct cmd_token *copy = - new_cmd_token(token->type, token->attr, NULL, NULL); - copy->max = token->max; - copy->min = token->min; - copy->text = token->text ? XSTRDUP(MTYPE_CMD_TEXT, token->text) : NULL; - copy->desc = token->desc ? XSTRDUP(MTYPE_CMD_DESC, token->desc) : NULL; - copy->arg = token->arg ? XSTRDUP(MTYPE_CMD_ARG, token->arg) : NULL; - - return copy; -} - void cmd_terminate() { struct cmd_node *cmd_node; @@ -2736,5 +2632,6 @@ void cmd_terminate() if (host.config) XFREE(MTYPE_HOST, host.config); + list_delete(varhandlers); qobj_finish(); } |
