diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:53:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:53:30 +0000 |
commit | 2c7cac91ed6e7db0f6937923d2b57f97dbdbc337 (patch) | |
tree | c05dc0f8e6aa3accc84e3e5cffc933ed94941383 /doc/developer/cli.rst | |
parent | Initial commit. (diff) | |
download | frr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.tar.xz frr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.zip |
Adding upstream version 8.4.4.upstream/8.4.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'doc/developer/cli.rst')
-rw-r--r-- | doc/developer/cli.rst | 999 |
1 files changed, 999 insertions, 0 deletions
diff --git a/doc/developer/cli.rst b/doc/developer/cli.rst new file mode 100644 index 0000000..ff6c4f6 --- /dev/null +++ b/doc/developer/cli.rst @@ -0,0 +1,999 @@ +.. _command-line-interface: + +Command Line Interface +====================== + +FRR features a flexible modal command line interface. Often when adding new +features or modifying existing code it is necessary to create or modify CLI +commands. FRR has a powerful internal CLI system that does most of the heavy +lifting for you. + +Modes +----- +FRR's CLI is organized by modes. Each mode is associated with some set of +functionality, e.g. EVPN, or some underlying object such as an interface. Each +mode contains a set of commands that control the associated functionality or +object. Users move between the modes by entering a command, which is usually +different for each source and destination mode. + +A summary of the modes is given in the following figure. + +.. graphviz:: ../figures/nodes.dot + +.. seealso:: :ref:`cli-data-structures` + +Walkup +^^^^^^ +FRR exhibits, for historical reasons, a peculiar behavior called 'walkup'. +Suppose a user is in ``OSPF_NODE``, which contains only OSPF-specific commands, +and enters the following command: :: + + ip route 192.168.100.0/24 10.0.2.2 + +This command is not defined in ``OSPF_NODE``, so the matcher will fail to match +the command in that node. The matcher will then check "parent" nodes of +``OSPF_NODE``. In this case the direct parent of ``OSPF_NODE`` is +``CONFIG_NODE``, so the current node switches to ``CONFIG_NODE`` and the command +is tried in that node. Since static route commands are defined in +``CONFIG_NODE`` the command succeeds. The procedure of attempting to execute +unmatched commands by sequentially "walking up" to parent nodes only happens in +children (direct and indirect) below ``CONFIG_NODE`` and stops at +``CONFIG_NODE``. + +Unfortunately, the internal representation of the various modes is not actually +a graph. Instead, there is an array. The parent-child relationships are not +explicitly defined in any datastructure but instead are hard-coded into the +specific commands that switch nodes. For walkup, there is a function that takes +a node and returns the parent of the node. This interface causes all manner of +insidious problems, even for experienced developers, and needs to be fixed at +some point in the future. + +Deprecation of old style of commands +------------------------------------ + +There are currently 2 styles of defining commands within a FRR source file. +``DEFUN`` and ``DEFPY``. ``DEFPY`` should be used for all new commands that +a developer is writing. This is because it allows for much better handling +of command line arguments as well as ensuring that input is correct. ``DEFUN`` +is listed here for historical reasons as well as for ensuring that existing +code can be understood by new developers. + +Defining Commands +----------------- +All definitions for the CLI system are exposed in ``lib/command.h``. In this +header there are a set of macros used to define commands. These macros are +collectively referred to as "DEFUNs", because of their syntax: + +:: + + DEFUN(command_name, + command_name_cmd, + "example command FOO...", + "Examples\n" + "CLI command\n" + "Argument\n") + { + // ...command handler... + } + +DEFUNs generally take four arguments which are expanded into the appropriate +constructs for hooking into the CLI. In order these are: + +- **Function name** - the name of the handler function for the command +- **Command name** - the identifier of the ``struct cmd_element`` for the + command. By convention this should be the function name with ``_cmd`` + appended. +- **Command definition** - an expression in FRR's CLI grammar that defines the + form of the command and its arguments, if any +- **Doc string** - a newline-delimited string that documents each element in + the command definition + +In the above example, ``command_name`` is the function name, +``command_name_cmd`` is the command name, ``"example..."`` is the definition and +the last argument is the doc string. The block following the macro is the body +of the handler function, details on which are presented later in this section. + +In order to make the command show up to the user it must be installed into the +CLI graph. To do this, call: + +``install_element(NODE, &command_name_cmd);`` + +This will install the command into the specified CLI node. Usually these calls +are grouped together in a CLI initialization function for a set of commands, and +the DEFUNs themselves are grouped into the same source file to avoid cluttering +the codebase. The names of these files follow the form ``*_vty.[ch]`` by +convention. Please do not scatter individual CLI commands in the middle of +source files; instead expose the necessary functions in a header and place the +command definition in a ``*_vty.[ch]`` file. + +Definition Grammar +^^^^^^^^^^^^^^^^^^ +FRR uses its own grammar for defining CLI commands. The grammar draws from +syntax commonly seen in \*nix manpages and should be fairly intuitive. The +parser is implemented in Bison and the lexer in Flex. These may be found in +``lib/command_parse.y`` and ``lib/command_lex.l``, respectively. + + **ProTip**: if you define a new command and find that the parser is + throwing syntax or other errors, the parser is the last place you want + to look. Bison is very stable and if it detects a syntax error, 99% of + the time it will be a syntax error in your definition. + +The formal grammar in BNF is given below. This is the grammar implemented in the +Bison parser. At runtime, the Bison parser reads all of the CLI strings and +builds a combined directed graph that is used to match and interpret user input. + +Human-friendly explanations of how to use this grammar are given a bit later in +this section alongside information on the :ref:`cli-data-structures` constructed +by the parser. + +.. productionlist:: + command: `cmd_token_seq` + : `cmd_token_seq` `placeholder_token` "..." + cmd_token_seq: *empty* + : `cmd_token_seq` `cmd_token` + cmd_token: `simple_token` + : `selector` + simple_token: `literal_token` + : `placeholder_token` + literal_token: WORD `varname_token` + varname_token: "$" WORD + placeholder_token: `placeholder_token_real` `varname_token` + placeholder_token_real: IPV4 + : IPV4_PREFIX + : IPV6 + : IPV6_PREFIX + : VARIABLE + : RANGE + : MAC + : MAC_PREFIX + selector: "<" `selector_seq_seq` ">" `varname_token` + : "{" `selector_seq_seq` "}" `varname_token` + : "[" `selector_seq_seq` "]" `varname_token` + : "![" `selector_seq_seq` "]" `varname_token` + selector_seq_seq: `selector_seq_seq` "|" `selector_token_seq` + : `selector_token_seq` + selector_token_seq: `selector_token_seq` `selector_token` + : `selector_token` + selector_token: `selector` + : `simple_token` + +Tokens +^^^^^^ +The various capitalized tokens in the BNF above are in fact themselves +placeholders, but not defined as such in the formal grammar; the grammar +provides the structure, and the tokens are actually more like a type system for +the strings you write in your CLI definitions. A CLI definition string is broken +apart and each piece is assigned a type by the lexer based on a set of regular +expressions. The parser uses the type information to verify the string and +determine the structure of the CLI graph; additional metadata (such as the raw +text of each token) is encoded into the graph as it is constructed by the +parser, but this is merely a dumb copy job. + +Here is a brief summary of the various token types along with examples. + ++-----------------+-------------------+-------------------------------------------------------------+ +| Token type | Syntax | Description | ++=================+===================+=============================================================+ +| ``WORD`` | ``show ip bgp`` | Matches itself. In the given example every token is a WORD. | ++-----------------+-------------------+-------------------------------------------------------------+ +| ``IPV4`` | ``A.B.C.D`` | Matches an IPv4 address. | ++-----------------+-------------------+-------------------------------------------------------------+ +| ``IPV6`` | ``X:X::X:X`` | Matches an IPv6 address. | ++-----------------+-------------------+-------------------------------------------------------------+ +| ``IPV4_PREFIX`` | ``A.B.C.D/M`` | Matches an IPv4 prefix in CIDR notation. | ++-----------------+-------------------+-------------------------------------------------------------+ +| ``IPV6_PREFIX`` | ``X:X::X:X/M`` | Matches an IPv6 prefix in CIDR notation. | ++-----------------+-------------------+-------------------------------------------------------------+ +| ``MAC`` | ``X:X:X:X:X:X`` | Matches a 48-bit mac address. | ++-----------------+-------------------+-------------------------------------------------------------+ +| ``MAC_PREFIX`` | ``X:X:X:X:X:X/M`` | Matches a 48-bit mac address with a mask. | ++-----------------+-------------------+-------------------------------------------------------------+ +| ``VARIABLE`` | ``FOOBAR`` | Matches anything. | ++-----------------+-------------------+-------------------------------------------------------------+ +| ``RANGE`` | ``(X-Y)`` | Matches numbers in the range X..Y inclusive. | ++-----------------+-------------------+-------------------------------------------------------------+ + +When presented with user input, the parser will search over all defined +commands in the current context to find a match. It is aware of the various +types of user input and has a ranking system to help disambiguate commands. For +instance, suppose the following commands are defined in the user's current +context: + +:: + + example command FOO + example command (22-49) + example command A.B.C.D/X + +The following table demonstrates the matcher's choice for a selection of +possible user input. + ++---------------------------------+---------------------------+--------------------------------------------------------------------------------------------------------------+ +| Input | Matched command | Reason | ++=================================+===========================+==============================================================================================================+ +| ``example command eLi7eH4xx0r`` | example command FOO | ``eLi7eH4xx0r`` is not an integer or IPv4 prefix, | +| | | but FOO is a variable and matches all input. | ++---------------------------------+---------------------------+--------------------------------------------------------------------------------------------------------------+ +| ``example command 42`` | example command (22-49) | ``42`` is not an IPv4 prefix. It does match both | +| | | ``(22-49)`` and ``FOO``, but RANGE tokens are more specific and have a higher priority than VARIABLE tokens. | ++---------------------------------+---------------------------+--------------------------------------------------------------------------------------------------------------+ +| ``example command 10.3.3.0/24`` | example command A.B.C.D/X | The user entered an IPv4 prefix, which is best matched by the last command. | ++---------------------------------+---------------------------+--------------------------------------------------------------------------------------------------------------+ + +Rules +^^^^^ +There are also constructs which allow optional tokens, mutual exclusion, +one-or-more selection and repetition. + +- ``<angle|brackets>`` -- Contain sequences of tokens separated by pipes and + provide mutual exclusion. User input matches at most one option. +- ``[square brackets]`` -- Contains sequences of tokens that can be omitted. + ``[<a|b>]`` can be shortened to ``[a|b]``. +- ``![exclamation square brackets]`` -- same as ``[square brackets]``, but + only allow skipping the contents if the command input starts with ``no``. + (For cases where the positive command needs a parameter, but the parameter + is optional for the negative case.) +- ``{curly|braces}`` -- similar to angle brackets, but instead of mutual + exclusion, curly braces indicate that one or more of the pipe-separated + sequences may be provided in any order. +- ``VARIADICS...`` -- Any token which accepts input (anything except WORD) + which occurs as the last token of a line may be followed by an ellipsis, + which indicates that input matching the token may be repeated an unlimited + number of times. +- ``$name`` -- Specify a variable name for the preceding token. See + "Variable Names" below. + +Some general notes: + +- Options are allowed at the beginning of the command. The developer is + entreated to use these extremely sparingly. They are most useful for + implementing the 'no' form of configuration commands. Please think carefully + before using them for anything else. There is usually a better solution, even + if it is just separating out the command definition into separate ones. +- The developer should judiciously apply separation of concerns when defining + commands. CLI definitions for two unrelated or vaguely related commands or + configuration items should be defined in separate commands. Clarity is + preferred over LOC (within reason). +- The maximum number of space-separated tokens that can be entered is + presently limited to 256. Please keep this limit in mind when + implementing new CLI. + +Variable Names +^^^^^^^^^^^^^^ +The parser tries to fill the "varname" field on each token. This can happen +either manually or automatically. Manual specifications work by appending +``$name`` after the input specifier: + +:: + + foo bar$cmd WORD$name A.B.C.D$ip + +Note that you can also assign variable names to fixed input tokens, this can be +useful if multiple commands share code. You can also use "$name" after a +multiple-choice option: + +:: + + foo bar <A.B.C.D|X:X::X:X>$addr [optionA|optionB]$mode + +The variable name is in this case assigned to the last token in each of the +branches. + +Automatic assignment of variable names works by applying the following rules: + +- manual names always have priority +- a ``[no]`` at the beginning receives ``no`` as varname on the ``no`` token +- ``VARIABLE`` tokens whose text is not ``WORD`` or ``NAME`` receive a cleaned + lowercase version of the token text as varname, e.g. ``ROUTE-MAP`` becomes + ``route_map``. +- other variable tokens (i.e. everything except "fixed") receive the text of + the preceding fixed token as varname, if one can be found. E.g. + ``ip route A.B.C.D/M INTERFACE`` assigns "route" to the ``A.B.C.D/M`` token. + +These rules should make it possible to avoid manual varname assignment in 90% of +the cases. + +Doc Strings +^^^^^^^^^^^ +Each token in a command definition should be documented with a brief doc string +that informs a user of the meaning and/or purpose of the subsequent command +tree. These strings are provided as the last parameter to DEFUN macros, +concatenated together and separated by an escaped newline (``\n``). These are +best explained by example. + +:: + + DEFUN (config_terminal, + config_terminal_cmd, + "configure terminal", + "Configuration from vty interface\n" + "Configuration terminal\n") + +The last parameter is split into two lines for readability. Two newline +delimited doc strings are present, one for each token in the command. The second +string documents the functionality of the ``terminal`` command in the +``configure`` subtree. + +Note that the first string, for ``configure`` does not contain documentation for +'terminal'. This is because the CLI is best envisioned as a tree, with tokens +defining branches. An imaginary ``start`` token is the root of every command in +a CLI node. Each subsequent written token descends into a subtree, so the +documentation for that token ideally summarizes all the functionality contained +in the subtree. + +A consequence of this structure is that the developer must be careful to use the +same doc strings when defining multiple commands that are part of the same tree. +Commands which share prefixes must share the same doc strings for those +prefixes. On startup the parser will generate warnings if it notices +inconsistent doc strings. Behavior is undefined; the same token may show up +twice in completions, with different doc strings, or it may show up once with a +random doc string. Parser warnings should be heeded and fixed to avoid confusing +users. + +The number of doc strings provided must be equal to the amount of tokens present +in the command definition, read left to right, ignoring any special constructs. + +In the examples below, each arrowed token needs a doc string. + +:: + + "show ip bgp" + ^ ^ ^ + + "command <foo|bar> [example]" + ^ ^ ^ ^ + +DEFPY +^^^^^ +``DEFPY(...)`` is an enhanced version of ``DEFUN()`` which is preprocessed by +:file:`python/clidef.py`. The python script parses the command definition +string, extracts variable names and types, and generates a C wrapper function +that parses the variables and passes them on. This means that in the CLI +function body, you will receive additional parameters with appropriate types. + +This is best explained by an example. Invoking ``DEFPY`` like this: + +.. code-block:: c + + DEFPY(func, func_cmd, "[no] foo bar A.B.C.D (0-99)$num", "...help...") + +defines the handler function like this: + +.. code-block:: c + + func(self, vty, argc, argv, /* standard CLI arguments */ + const char *no, /* unparsed "no" */ + struct in_addr bar, /* parsed IP address */ + const char *bar_str, /* unparsed IP address */ + long num, /* parsed num */ + const char *num_str) /* unparsed num */ + +Note that as documented in the previous section, ``bar`` is automatically +applied as variable name for ``A.B.C.D``. The Python script then detects this as +an IP address argument and generates code to parse it into a ``struct in_addr``, +passing it in ``bar``. The raw value is passed in ``bar_str``. The range/number +argument works in the same way with the explicitly given variable name. + +Type rules +"""""""""" + ++----------------------------+--------------------------------+--------------------------+ +| Token(s) | Type | Value if omitted by user | ++============================+================================+==========================+ +| ``A.B.C.D`` | ``struct in_addr`` | ``0.0.0.0`` | ++----------------------------+--------------------------------+--------------------------+ +| ``X:X::X:X`` | ``struct in6_addr`` | ``::`` | ++----------------------------+--------------------------------+--------------------------+ +| ``A.B.C.D + X:X::X:X`` | ``const union sockunion *`` | ``NULL`` | ++----------------------------+--------------------------------+--------------------------+ +| ``A.B.C.D/M`` | ``const struct prefix_ipv4 *`` | ``all-zeroes struct`` | ++----------------------------+--------------------------------+--------------------------+ +| ``X:X::X:X/M`` | ``const struct prefix_ipv6 *`` | ``all-zeroes struct`` | ++----------------------------+--------------------------------+--------------------------+ +| ``A.B.C.D/M + X:X::X:X/M`` | ``const struct prefix *`` | ``all-zeroes struct`` | ++----------------------------+--------------------------------+--------------------------+ +| ``(0-9)`` | ``long`` | ``0`` | ++----------------------------+--------------------------------+--------------------------+ +| ``VARIABLE`` | ``const char *`` | ``NULL`` | ++----------------------------+--------------------------------+--------------------------+ +| ``word`` | ``const char *`` | ``NULL`` | ++----------------------------+--------------------------------+--------------------------+ +| *all other* | ``const char *`` | ``NULL`` | ++----------------------------+--------------------------------+--------------------------+ + +Note the following details: + +- Not all parameters are pointers, some are passed as values. +- When the type is not ``const char *``, there will be an extra ``_str`` + argument with type ``const char *``. +- You can give a variable name not only to ``VARIABLE`` tokens but also to + ``word`` tokens (e.g. constant words). This is useful if some parts of a + command are optional. The type will be ``const char *``. +- ``[no]`` will be passed as ``const char *no``. +- Most pointers will be ``NULL`` when the argument is optional and the + user did not supply it. As noted in the table above, some prefix + struct type arguments are passed as pointers to all-zeroes structs, + not as ``NULL`` pointers. +- If a parameter is not a pointer, but is optional and the user didn't use it, + the default value will be passed. Check the ``_str`` argument if you need to + determine whether the parameter was omitted. +- If the definition contains multiple parameters with the same variable name, + they will be collapsed into a single function parameter. The python code will + detect if the types are compatible (i.e. IPv4 + IPv6 variants) and choose a + corresponding C type. +- The standard DEFUN parameters (``self, vty, argc, argv``) are still present + and can be used. A DEFUN can simply be **edited into a DEFPY without further + changes and it will still work**; this allows easy forward migration. +- A file may contain both ``DEFUN`` and ``DEFPY`` statements. + +Getting a parameter dump +"""""""""""""""""""""""" +The clidef.py script can be called to get a list of DEFUNs/DEFPYs with the +parameter name/type list: + +:: + + lib/clippy python/clidef.py --all-defun --show lib/plist.c > /dev/null + +The generated code is printed to stdout, the info dump to stderr. The +``--all-defun`` argument will make it process DEFUN blocks as well as DEFPYs, +which is useful prior to converting some DEFUNs. **The dump does not list the +``_str`` arguments** to keep the output shorter. + +Note that the ``clidef.py`` script cannot be run with python directly, it needs +to be run with *clippy* since the latter makes the CLI parser available. + +Include & Makefile requirements +""""""""""""""""""""""""""""""" +A source file that uses DEFPY needs to include the ``*_clippy.c`` file **before +all DEFPY statements**: + +.. code-block:: c + + /* GPL header */ + #include ... + ... + #ifndef VTYSH_EXTRACT_PL + #include "daemon/filename_clippy.c" + #endif + + DEFPY(...) + DEFPY(...) + + install_element(...) + +This dependency needs to be marked in ``Makefile.am`` or ``subdir.am``: (there +is no ordering requirement) + +.. code-block:: make + + # ... + + # if linked into a LTLIBRARY (.la/.so): + filename.lo: filename_clippy.c + + # if linked into an executable or static library (.a): + filename.o: filename_clippy.c + +Handlers +^^^^^^^^ +The block that follows a CLI definition is executed when a user enters input +that matches the definition. Its function signature looks like this: + +.. code-block:: c + + int (*func) (const struct cmd_element *, struct vty *, int, struct cmd_token *[]); + +The first argument is the command definition struct. The last argument is an +ordered array of tokens that correspond to the path taken through the graph, and +the argument just prior to that is the length of the array. + +The arrangement of the token array has changed from Quagga's CLI implementation. +In the old system, missing arguments were padded with ``NULL`` so that the same +parts of a command would show up at the same indices regardless of what was +entered. The new system does not perform such padding and therefore it is +generally *incorrect* to assume consistent indices in this array. As a simple +example: + +Command definition: + +:: + + command [foo] <bar|baz> + +User enters: + +:: + + command foo bar + +Array: + +:: + + [0] -> command + [1] -> foo + [2] -> bar + +User enters: + +:: + + command baz + +Array: + +:: + + [0] -> command + [1] -> baz + + +.. _cli-data-structures: + +Data Structures +--------------- +On startup, the CLI parser sequentially parses each command string definition +and constructs a directed graph with each token forming a node. This graph is +the basis of the entire CLI system. It is used to match user input in order to +generate command completions and match commands to functions. + +There is one graph per CLI node (not the same as a graph node in the CLI graph). +The CLI node struct keeps a reference to its graph (see :file:`lib/command.h`). + +While most of the graph maintains the form of a tree, special constructs +outlined in the Rules section introduce some quirks. ``<>``, ``[]`` and ``{}`` +form self-contained 'subgraphs'. Each subgraph is a tree except that all of the +'leaves' actually share a child node. This helps with minimizing graph size and +debugging. + +As a working example, here is the graph of the following command: :: + + show [ip] bgp neighbors [<A.B.C.D|X:X::X:X|WORD>] [json] + +.. figure:: ../figures/cligraph.png + :align: center + + Graph of example CLI command + + +``FORK`` and ``JOIN`` nodes are plumbing nodes that don't correspond to user +input. They're necessary in order to deduplicate these constructs where +applicable. + +Options follow the same form, except that there is an edge from the ``FORK`` +node to the ``JOIN`` node. Since all of the subgraphs in the example command are +optional, all of them have this edge. + +Keywords follow the same form, except that there is an edge from ``JOIN`` to +``FORK``. Because of this the CLI graph cannot be called acyclic. There is +special logic in the input matching code that keeps a stack of paths already +taken through the node in order to disallow following the same path more than +once. + +Variadics are a bit special; they have an edge back to themselves, which allows +repeating the same input indefinitely. + +The leaves of the graph are nodes that have no out edges. These nodes are +special; their data section does not contain a token, as most nodes do, or +``NULL``, as in ``FORK``/``JOIN`` nodes, but instead has a pointer to a +``cmd_element``. All paths through the graph that terminate on a leaf are +guaranteed to be defined by that command. When a user enters a complete command, +the command matcher tokenizes the input and executes a DFS on the CLI graph. If +it is simultaneously able to exhaust all input (one input token per graph node), +and then find exactly one leaf connected to the last node it reaches, then the +input has matched the corresponding command and the command is executed. If it +finds more than one node, then the command is ambiguous (more on this in +deduplication). If it cannot exhaust all input, the command is unknown. If it +exhausts all input but does not find an edge node, the command is incomplete. + +The parser uses an incremental strategy to build the CLI graph for a node. Each +command is parsed into its own graph, and then this graph is merged into the +overall graph. During this merge step, the parser makes a best-effort attempt to +remove duplicate nodes. If it finds a node in the overall graph that is equal to +a node in the corresponding position in the command graph, it will intelligently +merge the properties from the node in the command graph into the +already-existing node. Subgraphs are also checked for isomorphism and merged +where possible. The definition of whether two nodes are 'equal' is based on the +equality of some set of token properties; read the parser source for the most +up-to-date definition of equality. + +When the parser is unable to deduplicate some complicated constructs, this can +result in two identical paths through separate parts of the graph. If this +occurs and the user enters input that matches these paths, they will receive an +'ambiguous command' error and will be unable to execute the command. Most of the +time the parser can detect and warn about duplicate commands, but it will not +always be able to do this. Hence care should be taken before defining a new +command to ensure it is not defined elsewhere. + +struct cmd\_token +^^^^^^^^^^^^^^^^^ + +.. code-block:: c + + /* Command token struct. */ + struct cmd_token + { + enum cmd_token_type type; // token type + uint8_t attr; // token attributes + bool allowrepeat; // matcher can match token repetitively? + + char *text; // token text + char *desc; // token description + long long min, max; // for ranges + char *arg; // user input that matches this token + char *varname; // variable name + }; + +This struct is used in the CLI graph to match input against. It is also used to +pass user input to command handler functions, as it is frequently useful for +handlers to have access to that information. When a command is matched, the +sequence of ``cmd_tokens`` that form the matching path are duplicated and placed +in order into ``*argv[]``. Before this happens the ``->arg`` field is set to +point at the snippet of user input that matched it. + +For most nontrivial commands the handler function will need to determine which +of the possible matching inputs was entered. Previously this was done by looking +at the first few characters of input. This is now considered an anti-pattern and +should be avoided. Instead, the ``->type`` or ``->text`` fields for this logic. +The ``->type`` field can be used when the possible inputs differ in type. When +the possible types are the same, use the ``->text`` field. This field has the +full text of the corresponding token in the definition string and using it makes +for much more readable code. An example is helpful. + +Command definition: + +:: + + command <(1-10)|foo|BAR> + +In this example, the user may enter any one of: +- an integer between 1 and 10 +- "foo" +- anything at all + +If the user enters "command f", then: + +:: + + argv[1]->type == WORD_TKN + argv[1]->arg == "f" + argv[1]->text == "foo" + +Range tokens have some special treatment; a token with ``->type == RANGE_TKN`` +will have the ``->min`` and ``->max`` fields set to the bounding values of the +range. + +struct cmd\_element +^^^^^^^^^^^^^^^^^^^ + +.. code-block:: c + + struct cmd_node { + /* Node index. */ + enum node_type node; + + /* Prompt character at vty interface. */ + const char *prompt; + + /* Is this node's configuration goes to vtysh ? */ + int vtysh; + + /* Node's configuration write function */ + int (*func)(struct vty *); + + /* Node's command graph */ + struct graph *cmdgraph; + + /* Vector of this node's command list. */ + vector cmd_vector; + + /* Hashed index of command node list, for de-dupping primarily */ + struct hash *cmd_hash; + }; + +This struct corresponds to a CLI mode. The last three fields are most relevant +here. + +cmdgraph + This is a pointer to the command graph that was described in the first part + of this section. It is the datastructure used for matching user input to + commands. + +cmd_vector + This is a list of all the ``struct cmd_element`` defined in the mode. + +cmd_hash + This is a hash table of all the ``struct cmd_element`` defined in the mode. + When ``install_element`` is called, it checks that the element it is given is + not already present in the hash table as a safeguard against duplicate calls + resulting in a command being defined twice, which renders the command + ambiguous. + +All ``struct cmd_node`` are themselves held in a static vector defined in +:file:`lib/command.c` that defines the global CLI space. + +Command Abbreviation & Matching Priority +---------------------------------------- +It is possible for users to elide parts of tokens when the CLI matcher does not +need them to make an unambiguous match. This is best explained by example. + +Command definitions: + +:: + + command dog cow + command dog crow + +User input: + +:: + + c d c -> ambiguous command + c d co -> match "command dog cow" + + +The parser will look ahead and attempt to disambiguate the input based on tokens +later on in the input string. + +Command definitions: + +:: + + show ip bgp A.B.C.D + show ipv6 bgp X:X::X:X + +User enters: + +:: + + s i b 4.3.2.1 -> match "show ip bgp A.B.C.D" + s i b ::e0 -> match "show ipv6 bgp X:X::X:X" + +Reading left to right, both of these commands would be ambiguous since 'i' does +not explicitly select either 'ip' or 'ipv6'. However, since the user later +provides a token that matches only one of the commands (an IPv4 or IPv6 address) +the parser is able to look ahead and select the appropriate command. This has +some implications for parsing the ``*argv[]`` that is passed to the command +handler. + +Now consider a command definition such as: + +:: + + command <foo|VAR> + +'foo' only matches the string 'foo', but 'VAR' matches any input, including +'foo'. Who wins? In situations like this the matcher will always choose the +'better' match, so 'foo' will win. + +Consider also: + +:: + + show <ip|ipv6> foo + +User input: + +:: + + show ip foo + +``ip`` partially matches ``ipv6`` but exactly matches ``ip``, so ``ip`` will +win. + +Adding a CLI Node +----------------- + +To add a new CLI node, you should: + +- define a new numerical node constant +- define a node structure in the relevant daemon +- call ``install_node()`` in the relevant daemon +- define and install the new node in vtysh +- define corresponding node entry commands in daemon and vtysh +- add a new entry to the ``ctx_keywords`` dictionary in ``tools/frr-reload.py`` + +Defining the numerical node constant +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Add your new node value to the enum before ``NODE_TYPE_MAX`` in +``lib/command.h``: + +.. code-block:: c + + enum node_type { + AUTH_NODE, // Authentication mode of vty interface. + VIEW_NODE, // View node. Default mode of vty interface. + [...] + MY_NEW_NODE, + NODE_TYPE_MAX, // maximum + }; + +Defining a node structure +^^^^^^^^^^^^^^^^^^^^^^^^^ +In your daemon-specific code where you define your new commands that +attach to the new node, add a node definition: + +.. code-block:: c + + static struct cmd_node my_new_node = { + .name = "my new node name", + .node = MY_NEW_NODE, // enum node_type lib/command.h + .parent_node = CONFIG_NODE, + .prompt = "%s(my-new-node-prompt)# ", + .config_write = my_new_node_config_write, + }; + +You will need to define ``my_new_node_config_write(struct vty \*vty)`` +(or omit this field if you have no relevant configuration to save). + +Calling ``install_node()`` +^^^^^^^^^^^^^^^^^^^^^^^^^^ +In the daemon's initialization function, before installing your new commands +with ``install_element()``, add a call ``install_node(&my_new_node)``. + +Defining and installing the new node in vtysh +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The build tools automatically collect command definitions for vtysh. +However, new nodes must be coded in vtysh specifically. + +In ``vtysh/vtysh.c``, define a stripped-down node structure and +call ``install_node()``: + +.. code-block:: c + + static struct cmd_node my_new_node = { + .name = "my new node name", + .node = MY_NEW_NODE, /* enum node_type lib/command.h */ + .parent_node = CONFIG_NODE, + .prompt = "%s(my-new-node-prompt)# ", + }; + [...] + void vtysh_init_vty(void) + { + [...] + install_node(&my_new_node) + [...] + } + +Defining corresponding node entry commands in daemon and vtysh +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The command that descends into the new node is typically programmed +with ``VTY_PUSH_CONTEXT`` or equivalent in the daemon's CLI handler function. +(If the CLI has been updated to use the new northbound architecture, +``VTY_PUSH_XPATH`` is used instead.) + +In vtysh, you must implement a corresponding node change so that vtysh +tracks the daemon's movement through the node tree. + +Although the build tools typically scan daemon code for CLI definitions +to replicate their parsing in vtysh, the node-descent function in the +daemon must be blocked from this replication so that a hand-coded +skeleton can be written in ``vtysh.c``. + +Accordingly, use one of the ``*_NOSH`` macros such as ``DEFUN_NOSH``, +``DEFPY_NOSH``, or ``DEFUN_YANG_NOSH`` for the daemon's node-descent +CLI definition, and use ``DEFUNSH`` in ``vtysh.c`` for the vtysh equivalent. + +.. seealso:: :ref:`vtysh-special-defuns` + +Examples: + +``zebra_whatever.c`` + +.. code-block:: c + + DEFPY_NOSH(my_new_node, + my_new_node_cmd, + "my-new-node foo", + "New Thing\n" + "A foo\n") + { + [...] + VTY_PUSH_CONTEXT(MY_NEW_NODE, bar); + [...] + } + + +``ripd_whatever.c`` + +.. code-block:: c + + DEFPY_YANG_NOSH(my_new_node, + my_new_node_cmd, + "my-new-node foo", + "New Thing\n" + "A foo\n") + { + [...] + VTY_PUSH_XPATH(MY_NEW_NODE, xbar); + [...] + } + + +``vtysh.c`` + +.. code-block:: c + + DEFUNSH(VTYSH_ZEBRA, my_new_node, + my_new_node_cmd, + "my-new-node foo", + "New Thing\n" + "A foo\n") + { + vty->node = MY_NEW_NODE; + return CMD_SUCCESS; + } + [...] + install_element(CONFIG_NODE, &my_new_node_cmd); + + +Adding a new entry to the ``ctx_keywords`` dictionary +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +In file ``tools/frr-reload.py``, the ``ctx_keywords`` dictionary +describes the various node relationships. +Add a new node entry at the appropriate level in this dictionary. + +.. code-block:: python + + ctx_keywords = { + [...] + "key chain ": { + "key ": {} + }, + [...] + "my-new-node": {}, + [...] + } + + + +Inspection & Debugging +---------------------- + +Permutations +^^^^^^^^^^^^ +It is sometimes useful to check all the possible combinations of input that +would match an arbitrary definition string. There is a tool in +:file:`tools/permutations` that reads CLI definition strings on ``stdin`` and +prints out all matching input permutations. It also dumps a text representation +of the graph, which is more useful for debugging than anything else. It looks +like this: + +.. code-block:: shell + + $ ./permutations "show [ip] bgp [<view|vrf> WORD]" + + show ip bgp view WORD + show ip bgp vrf WORD + show ip bgp + show bgp view WORD + show bgp vrf WORD + show bgp + +This functionality is also built into VTY/VTYSH; :clicmd:`list permutations` +will list all possible matching input permutations in the current CLI node. + +Graph Inspection +^^^^^^^^^^^^^^^^ +When in the Telnet or VTYSH console, :clicmd:`show cli graph` will dump the +entire command space of the current mode in the DOT graph language. This can be +fed into one of the various GraphViz layout engines, such as ``dot``, +``neato``, etc. + +For example, to generate an image of the entire command space for the top-level +mode (``ENABLE_NODE``): + +.. code-block:: shell + + sudo vtysh -c 'show cli graph' | dot -Tjpg -Grankdir=LR > graph.jpg + +To do the same for the BGP mode: + +.. code-block:: shell + + sudo vtysh -c 'conf t' -c 'router bgp' -c 'show cli graph' | dot -Tjpg -Grankdir=LR > bgpgraph.jpg + +This information is very helpful when debugging command resolution, tracking +down duplicate / ambiguous commands, and debugging patches to the CLI graph +builder. |