lib/command.c: rewrite command matching/parsing

Add support for keyword commands.

Includes new documentation for DEFUN() in lib/command.h, for preexisting
features as well as new keyword specification.

Signed-off-by: Christian Franke <chris@opensourcerouting.org>
Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
This commit is contained in:
Christian Franke 2013-09-30 12:27:51 +00:00 committed by David Lamparter
parent e712d0e366
commit cd40b329a2
17 changed files with 1499 additions and 792 deletions

View File

@ -277,9 +277,6 @@ babel_init(int argc, char **argv)
/* this replace kernel_setup && kernel_setup_socket */ /* this replace kernel_setup && kernel_setup_socket */
babelz_zebra_init (); babelz_zebra_init ();
/* Sort all installed commands. */
sort_node ();
/* Get zebra configuration file. */ /* Get zebra configuration file. */
zlog_set_level (NULL, ZLOG_DEST_STDOUT, ZLOG_DISABLED); zlog_set_level (NULL, ZLOG_DEST_STDOUT, ZLOG_DISABLED);
vty_read_config (babel_config_file, babel_config_default); vty_read_config (babel_config_file, babel_config_default);

View File

@ -431,9 +431,6 @@ main (int argc, char **argv)
/* BGP related initialization. */ /* BGP related initialization. */
bgp_init (); bgp_init ();
/* Sort CLI commands. */
sort_node ();
/* Parse config file. */ /* Parse config file. */
vty_read_config (config_file, config_default); vty_read_config (config_file, config_default);

View File

@ -339,8 +339,6 @@ main (int argc, char **argv, char **envp)
isis_zebra_init (); isis_zebra_init ();
sort_node ();
/* parse config file */ /* parse config file */
/* this is needed three times! because we have interfaces before the areas */ /* this is needed three times! because we have interfaces before the areas */
vty_read_config (config_file, config_default); vty_read_config (config_file, config_default);

File diff suppressed because it is too large Load Diff

View File

@ -138,18 +138,32 @@ struct cmd_element
int (*func) (struct cmd_element *, struct vty *, int, const char *[]); int (*func) (struct cmd_element *, struct vty *, int, const char *[]);
const char *doc; /* Documentation of this command. */ const char *doc; /* Documentation of this command. */
int daemon; /* Daemon to which this command belong. */ int daemon; /* Daemon to which this command belong. */
vector strvec; /* Pointing out each description vector. */ vector tokens; /* Vector of cmd_tokens */
unsigned int cmdsize; /* Command index count. */
char *config; /* Configuration string */
vector subconfig; /* Sub configuration string */
u_char attr; /* Command attributes */ u_char attr; /* Command attributes */
}; };
/* Command description structure. */
struct desc enum cmd_token_type
{ {
TOKEN_TERMINAL = 0,
TOKEN_MULTIPLE,
TOKEN_KEYWORD,
};
/* Command description structure. */
struct cmd_token
{
enum cmd_token_type type;
/* Used for type == MULTIPLE */
vector multiple; /* vector of cmd_token, type == FINAL */
/* Used for type == KEYWORD */
vector keyword; /* vector of vector of cmd_tokens */
/* Used for type == TERMINAL */
char *cmd; /* Command string. */ char *cmd; /* Command string. */
char *str; /* Command's description. */ char *desc; /* Command's description. */
}; };
/* Return value of the commands. */ /* Return value of the commands. */
@ -192,7 +206,170 @@ struct desc
int argc __attribute__ ((unused)), \ int argc __attribute__ ((unused)), \
const char *argv[] __attribute__ ((unused)) ) const char *argv[] __attribute__ ((unused)) )
/* DEFUN for vty command interafce. Little bit hacky ;-). */ /* DEFUN for vty command interafce. Little bit hacky ;-).
*
* DEFUN(funcname, cmdname, cmdstr, helpstr)
*
* funcname
* ========
*
* Name of the function that will be defined.
*
* cmdname
* =======
*
* Name of the struct that will be defined for the command.
*
* cmdstr
* ======
*
* The cmdstr defines the command syntax. It is used by the vty subsystem
* and vtysh to perform matching and completion in the cli. So you have to take
* care to construct it adhering to the following grammar. The names used
* for the production rules losely represent the names used in lib/command.c
*
* cmdstr = cmd_token , { " " , cmd_token } ;
*
* cmd_token = cmd_terminal
* | cmd_multiple
* | cmd_keyword ;
*
* cmd_terminal_fixed = fixed_string
* | variable
* | range
* | ipv4
* | ipv4_prefix
* | ipv6
* | ipv6_prefix ;
*
* cmd_terminal = cmd_terminal_fixed
* | option
* | vararg ;
*
* multiple_part = cmd_terminal_fixed ;
* cmd_multiple = "(" , multiple_part , ( "|" | { "|" , multiple_part } ) , ")" ;
*
* keyword_part = fixed_string , { " " , ( cmd_terminal_fixed | cmd_multiple ) } ;
* cmd_keyword = "{" , keyword_part , { "|" , keyword_part } , "}" ;
*
* lowercase = "a" | ... | "z" ;
* uppercase = "A" | ... | "Z" ;
* digit = "0" | ... | "9" ;
* number = digit , { digit } ;
*
* fixed_string = (lowercase | digit) , { lowercase | digit | uppercase | "-" | "_" } ;
* variable = uppercase , { uppercase | "_" } ;
* range = "<" , number , "-" , number , ">" ;
* ipv4 = "A.B.C.D" ;
* ipv4_prefix = "A.B.C.D/M" ;
* ipv6 = "X:X::X:X" ;
* ipv6_prefix = "X:X::X:X/M" ;
* option = "[" , variable , "]" ;
* vararg = "." , variable ;
*
* To put that all in a textual description: A cmdstr is a sequence of tokens,
* separated by spaces.
*
* Terminal Tokens:
*
* A very simple cmdstring would be something like: "show ip bgp". It consists
* of three Terminal Tokens, each containing a fixed string. When this command
* is called, no arguments will be passed down to the function implementing it,
* as it only consists of fixed strings.
*
* Apart from fixed strings, Terminal Tokens can also contain variables:
* An example would be "show ip bgp A.B.C.D". This command expects an IPv4
* as argument. As this is a variable, the IP address entered by the user will
* be passed down as an argument. Apart from two exceptions, the other options
* for Terminal Tokens behave exactly as we just discussed and only make a
* difference for the CLI. The two exceptions will be discussed in the next
* paragraphs.
*
* A Terminal Token can contain a so called option match. This is a simple
* string variable that the user may omit. An example would be:
* "show interface [IFNAME]". If the user calls this without an interface as
* argument, no arguments will be passed down to the function implementing
* this command. Otherwise, the interface name will be provided to the function
* as a regular argument.
* Also, a Terminal Token can contain a so called vararg. This is used e.g. in
* "show ip bgp regexp .LINE". The last token is a vararg match and will
* consume all the arguments the user inputs on the command line and append
* those to the list of arguments passed down to the function implementing this
* command. (Therefore, it doesn't make much sense to have any tokens after a
* vararg because the vararg will already consume all the words the user entered
* in the CLI)
*
* Multiple Tokens:
*
* The Multiple Token type can be used if there are multiple possibilities what
* arguments may be used for a command, but it should map to the same function
* nonetheless. An example would be "ip route A.B.C.D/M (reject|blackhole)"
* In that case both "reject" and "blackhole" would be acceptable as last
* arguments. The words matched by Multiple Tokens are always added to the
* argument list, even if they are matched by fixed strings. Such a Multiple
* Token can contain almost any type of token that would also be acceptable
* for a Terminal Token, the exception are optional variables and varag.
*
* There is one special case that is used in some places of Quagga that should be
* pointed out here shortly. An example would be "password (8|) WORD". This
* construct is used to have fixed strings communicated as arguments. (The "8"
* will be passed down as an argument in this case) It does not mean that
* the "8" is optional. Another historic and possibly surprising property of
* this construct is that it consumes two parts of helpstr. (Help
* strings will be explained later)
*
* Keyword Tokens:
*
* There are commands that take a lot of different and possibly optional arguments.
* An example from ospf would be the "default-information originate" command. This
* command takes a lot of optional arguments that may be provided in any order.
* To accomodate such commands, the Keyword Token has been implemented.
* Using the keyword token, the "default-information originate" command and all
* its possible options can be represented using this single cmdstr:
* "default-information originate \
* {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}"
*
* Keywords always start with a fixed string and may be followed by arguments.
* Except optional variables and vararg, everything is permitted here.
*
* For the special case of a keyword without arguments, either NULL or the
* keyword itself will be pushed as an argument, depending on whether the
* keyword is present.
* For the other keywords, arguments will be only pushed for
* variables/Multiple Tokens. If the keyword is not present, the arguments that
* would have been pushed will be substituted by NULL.
*
* A few examples:
* "default information originate metric-type 1 metric 1000"
* would yield the following arguments:
* { NULL, "1000", "1", NULL }
*
* "default information originate always route-map RMAP-DEFAULT"
* would yield the following arguments:
* { "always", NULL, NULL, "RMAP-DEFAULT" }
*
* helpstr
* =======
*
* The helpstr is used to show a short explantion for the commands that
* are available when the user presses '?' on the CLI. It is the concatenation
* of the helpstrings for all the tokens that make up the command.
*
* There should be one helpstring for each token in the cmdstr except those
* containing other tokens, like Multiple or Keyword Tokens. For those, there
* will only be the helpstrings of the contained tokens.
*
* The individual helpstrings are expected to be in the same order as their
* respective Tokens appear in the cmdstr. They should each be terminated with
* a linefeed. The last helpstring should be terminated with a linefeed as well.
*
* Care should also be taken to avoid having similar tokens with different
* helpstrings. Imagine e.g. the commands "show ip ospf" and "show ip bgp".
* they both contain a helpstring for "show", but only one will be displayed
* when the user enters "sh?". If those two helpstrings differ, it is not
* defined which one will be shown and the behavior is therefore unpredictable.
*/
#define DEFUN(funcname, cmdname, cmdstr, helpstr) \ #define DEFUN(funcname, cmdname, cmdstr, helpstr) \
DEFUN_CMD_FUNC_DECL(funcname) \ DEFUN_CMD_FUNC_DECL(funcname) \
DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \ DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \
@ -330,7 +507,6 @@ struct desc
extern void install_node (struct cmd_node *, int (*) (struct vty *)); extern void install_node (struct cmd_node *, int (*) (struct vty *));
extern void install_default (enum node_type); extern void install_default (enum node_type);
extern void install_element (enum node_type, struct cmd_element *); extern void install_element (enum node_type, struct cmd_element *);
extern void sort_node (void);
/* Concatenates argv[shift] through argv[argc-1] into a single NUL-terminated /* Concatenates argv[shift] through argv[argc-1] into a single NUL-terminated
string with a space between each element (allocated using string with a space between each element (allocated using
@ -346,7 +522,6 @@ extern int config_from_file (struct vty *, FILE *);
extern enum node_type node_parent (enum node_type); extern enum node_type node_parent (enum node_type);
extern int cmd_execute_command (vector, struct vty *, struct cmd_element **, int); extern int cmd_execute_command (vector, struct vty *, struct cmd_element **, int);
extern int cmd_execute_command_strict (vector, struct vty *, struct cmd_element **); extern int cmd_execute_command_strict (vector, struct vty *, struct cmd_element **);
extern void config_replace_string (struct cmd_element *, char *, ...);
extern void cmd_init (int); extern void cmd_init (int);
extern void cmd_terminate (void); extern void cmd_terminate (void);

View File

@ -54,7 +54,7 @@ struct memory_list memory_list_lib[] =
{ MTYPE_ROUTE_MAP_RULE, "Route map rule" }, { MTYPE_ROUTE_MAP_RULE, "Route map rule" },
{ MTYPE_ROUTE_MAP_RULE_STR, "Route map rule str" }, { MTYPE_ROUTE_MAP_RULE_STR, "Route map rule str" },
{ MTYPE_ROUTE_MAP_COMPILED, "Route map compiled" }, { MTYPE_ROUTE_MAP_COMPILED, "Route map compiled" },
{ MTYPE_DESC, "Command desc" }, { MTYPE_CMD_TOKENS, "Command desc" },
{ MTYPE_KEY, "Key" }, { MTYPE_KEY, "Key" },
{ MTYPE_KEYCHAIN, "Key chain" }, { MTYPE_KEYCHAIN, "Key chain" },
{ MTYPE_IF_RMAP, "Interface route map" }, { MTYPE_IF_RMAP, "Interface route map" },

View File

@ -931,23 +931,23 @@ vty_complete_command (struct vty *vty)
static void static void
vty_describe_fold (struct vty *vty, int cmd_width, vty_describe_fold (struct vty *vty, int cmd_width,
unsigned int desc_width, struct desc *desc) unsigned int desc_width, struct cmd_token *token)
{ {
char *buf; char *buf;
const char *cmd, *p; const char *cmd, *p;
int pos; int pos;
cmd = desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd; cmd = token->cmd[0] == '.' ? token->cmd + 1 : token->cmd;
if (desc_width <= 0) if (desc_width <= 0)
{ {
vty_out (vty, " %-*s %s%s", cmd_width, cmd, desc->str, VTY_NEWLINE); vty_out (vty, " %-*s %s%s", cmd_width, cmd, token->desc, VTY_NEWLINE);
return; return;
} }
buf = XCALLOC (MTYPE_TMP, strlen (desc->str) + 1); buf = XCALLOC (MTYPE_TMP, strlen (token->desc) + 1);
for (p = desc->str; strlen (p) > desc_width; p += pos + 1) for (p = token->desc; strlen (p) > desc_width; p += pos + 1)
{ {
for (pos = desc_width; pos > 0; pos--) for (pos = desc_width; pos > 0; pos--)
if (*(p + pos) == ' ') if (*(p + pos) == ' ')
@ -976,7 +976,7 @@ vty_describe_command (struct vty *vty)
vector vline; vector vline;
vector describe; vector describe;
unsigned int i, width, desc_width; unsigned int i, width, desc_width;
struct desc *desc, *desc_cr = NULL; struct cmd_token *token, *token_cr = NULL;
vline = cmd_make_strvec (vty->buf); vline = cmd_make_strvec (vty->buf);
@ -1010,15 +1010,15 @@ vty_describe_command (struct vty *vty)
/* Get width of command string. */ /* Get width of command string. */
width = 0; width = 0;
for (i = 0; i < vector_active (describe); i++) for (i = 0; i < vector_active (describe); i++)
if ((desc = vector_slot (describe, i)) != NULL) if ((token = vector_slot (describe, i)) != NULL)
{ {
unsigned int len; unsigned int len;
if (desc->cmd[0] == '\0') if (token->cmd[0] == '\0')
continue; continue;
len = strlen (desc->cmd); len = strlen (token->cmd);
if (desc->cmd[0] == '.') if (token->cmd[0] == '.')
len--; len--;
if (width < len) if (width < len)
@ -1030,27 +1030,27 @@ vty_describe_command (struct vty *vty)
/* Print out description. */ /* Print out description. */
for (i = 0; i < vector_active (describe); i++) for (i = 0; i < vector_active (describe); i++)
if ((desc = vector_slot (describe, i)) != NULL) if ((token = vector_slot (describe, i)) != NULL)
{ {
if (desc->cmd[0] == '\0') if (token->cmd[0] == '\0')
continue; continue;
if (strcmp (desc->cmd, command_cr) == 0) if (strcmp (token->cmd, command_cr) == 0)
{ {
desc_cr = desc; token_cr = token;
continue; continue;
} }
if (!desc->str) if (!token->desc)
vty_out (vty, " %-s%s", vty_out (vty, " %-s%s",
desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, token->cmd[0] == '.' ? token->cmd + 1 : token->cmd,
VTY_NEWLINE); VTY_NEWLINE);
else if (desc_width >= strlen (desc->str)) else if (desc_width >= strlen (token->desc))
vty_out (vty, " %-*s %s%s", width, vty_out (vty, " %-*s %s%s", width,
desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, token->cmd[0] == '.' ? token->cmd + 1 : token->cmd,
desc->str, VTY_NEWLINE); token->desc, VTY_NEWLINE);
else else
vty_describe_fold (vty, width, desc_width, desc); vty_describe_fold (vty, width, desc_width, token);
#if 0 #if 0
vty_out (vty, " %-*s %s%s", width vty_out (vty, " %-*s %s%s", width
@ -1059,18 +1059,18 @@ vty_describe_command (struct vty *vty)
#endif /* 0 */ #endif /* 0 */
} }
if ((desc = desc_cr)) if ((token = token_cr))
{ {
if (!desc->str) if (!token->desc)
vty_out (vty, " %-s%s", vty_out (vty, " %-s%s",
desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, token->cmd[0] == '.' ? token->cmd + 1 : token->cmd,
VTY_NEWLINE); VTY_NEWLINE);
else if (desc_width >= strlen (desc->str)) else if (desc_width >= strlen (token->desc))
vty_out (vty, " %-*s %s%s", width, vty_out (vty, " %-*s %s%s", width,
desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, token->cmd[0] == '.' ? token->cmd + 1 : token->cmd,
desc->str, VTY_NEWLINE); token->desc, VTY_NEWLINE);
else else
vty_describe_fold (vty, width, desc_width, desc); vty_describe_fold (vty, width, desc_width, token);
} }
out: out:

View File

@ -325,9 +325,6 @@ main (int argc, char *argv[], char *envp[])
/* initialize ospf6 */ /* initialize ospf6 */
ospf6_init (); ospf6_init ();
/* sort command vector */
sort_node ();
/* parse config file */ /* parse config file */
vty_read_config (config_file, config_default); vty_read_config (config_file, config_default);

View File

@ -310,8 +310,6 @@ main (int argc, char **argv)
ospf_opaque_init (); ospf_opaque_init ();
#endif /* HAVE_OPAQUE_LSA */ #endif /* HAVE_OPAQUE_LSA */
sort_node ();
/* Get configuration file. */ /* Get configuration file. */
vty_read_config (config_file, config_default); vty_read_config (config_file, config_default);

View File

@ -287,9 +287,6 @@ main (int argc, char **argv)
rip_zclient_init (); rip_zclient_init ();
rip_peer_init (); rip_peer_init ();
/* Sort all installed commands. */
sort_node ();
/* Get configuration file. */ /* Get configuration file. */
vty_read_config (config_file, config_default); vty_read_config (config_file, config_default);

View File

@ -282,9 +282,6 @@ main (int argc, char **argv)
zebra_init (); zebra_init ();
ripng_peer_init (); ripng_peer_init ();
/* Sort all installed commands. */
sort_node ();
/* Get configuration file. */ /* Get configuration file. */
vty_read_config (config_file, config_default); vty_read_config (config_file, config_default);

View File

@ -171,8 +171,6 @@ main (int argc, char **argv)
/* OSPF vty inits. */ /* OSPF vty inits. */
test_vty_init (); test_vty_init ();
sort_node ();
/* Change to the daemon program. */ /* Change to the daemon program. */
if (daemon_mode && daemon (0, 0) < 0) if (daemon_mode && daemon (0, 0) < 0)
{ {

View File

@ -233,8 +233,6 @@ test_init(void)
cmd->daemon = 0; cmd->daemon = 0;
cmd->func = test_callback; cmd->func = test_callback;
} }
sort_node();
test_load(); test_load();
vty_init_vtysh(); vty_init_vtysh();
} }
@ -340,8 +338,8 @@ test_run(struct prng *prng, struct vty *vty, const char *cmd, unsigned int edit_
{ {
for (j = 0; j < vector_active(descriptions); j++) for (j = 0; j < vector_active(descriptions); j++)
{ {
struct desc *cmd = vector_slot(descriptions, j); struct cmd_token *cmd = vector_slot(descriptions, j);
printf(" '%s' '%s'\n", cmd->cmd, cmd->str); printf(" '%s' '%s'\n", cmd->cmd, cmd->desc);
} }
vector_free(descriptions); vector_free(descriptions);
} }

View File

@ -554,7 +554,7 @@ vtysh_rl_describe (void)
vector vline; vector vline;
vector describe; vector describe;
int width; int width;
struct desc *desc; struct cmd_token *token;
vline = cmd_make_strvec (rl_line_buffer); vline = cmd_make_strvec (rl_line_buffer);
@ -592,15 +592,15 @@ vtysh_rl_describe (void)
/* Get width of command string. */ /* Get width of command string. */
width = 0; width = 0;
for (i = 0; i < vector_active (describe); i++) for (i = 0; i < vector_active (describe); i++)
if ((desc = vector_slot (describe, i)) != NULL) if ((token = vector_slot (describe, i)) != NULL)
{ {
int len; int len;
if (desc->cmd[0] == '\0') if (token->cmd[0] == '\0')
continue; continue;
len = strlen (desc->cmd); len = strlen (token->cmd);
if (desc->cmd[0] == '.') if (token->cmd[0] == '.')
len--; len--;
if (width < len) if (width < len)
@ -608,19 +608,19 @@ vtysh_rl_describe (void)
} }
for (i = 0; i < vector_active (describe); i++) for (i = 0; i < vector_active (describe); i++)
if ((desc = vector_slot (describe, i)) != NULL) if ((token = vector_slot (describe, i)) != NULL)
{ {
if (desc->cmd[0] == '\0') if (token->cmd[0] == '\0')
continue; continue;
if (! desc->str) if (! token->desc)
fprintf (stdout," %-s\n", fprintf (stdout," %-s\n",
desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd); token->cmd[0] == '.' ? token->cmd + 1 : token->cmd);
else else
fprintf (stdout," %-*s %s\n", fprintf (stdout," %-*s %s\n",
width, width,
desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, token->cmd[0] == '.' ? token->cmd + 1 : token->cmd,
desc->str); token->desc);
} }
cmd_free_strvec (vline); cmd_free_strvec (vline);

View File

@ -299,8 +299,6 @@ main (int argc, char **argv, char **env)
vty_init_vtysh (); vty_init_vtysh ();
sort_node ();
/* Read vtysh configuration file before connecting to daemons. */ /* Read vtysh configuration file before connecting to daemons. */
vtysh_read_config (config_default); vtysh_read_config (config_default);

View File

@ -343,9 +343,6 @@ main (int argc, char **argv)
interface_list (); interface_list ();
route_read (); route_read ();
/* Sort VTY commands. */
sort_node ();
#ifdef HAVE_SNMP #ifdef HAVE_SNMP
zebra_snmp_init (); zebra_snmp_init ();
#endif /* HAVE_SNMP */ #endif /* HAVE_SNMP */

View File

@ -298,9 +298,6 @@ main (int argc, char **argv)
route_read (); route_read ();
zebra_vty_init(); zebra_vty_init();
/* Sort VTY commands. */
sort_node ();
/* Configuration file read*/ /* Configuration file read*/
vty_read_config (config_file, config_default); vty_read_config (config_file, config_default);