diff options
Diffstat (limited to 'lib')
69 files changed, 12109 insertions, 11537 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am index 39b6b03e..cc8fbd3a 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -14,9 +14,10 @@ libzebra_la_SOURCES = \ zclient.c sockopt.c smux.c md5.c if_rmap.c keychain.c privs.c \ sigevent.c pqueue.c jhash.c memtypes.c workqueue.c symtab.c heap.c \ qtime.c qpthreads.c mqueue.c qpselect.c qtimers.c qpnexus.c \ - command_parse.c command_queue.c qlib_init.c pthread_safe.c list_util.c \ - vty_io.c vty_io_file.c vty_io_shell.c vty_io_term.c vty_cli.c \ - vty_io_basic.c keystroke.c qstring.c vio_fifo.c vio_lines.c \ + command_parse.c command_execute.c command_queue.c vty_command.c \ + vty_io.c vty_io_file.c vty_io_shell.c vty_io_vsh.c vty_io_term.c vty_cli.c \ + vty_io_basic.c keystroke.c qstring.c elstring.c vio_fifo.c vio_lines.c \ + qlib_init.c pthread_safe.c list_util.c \ qiovec.c qfstring.c errno_names.c BUILT_SOURCES = memtypes.h route_types.h @@ -26,19 +27,19 @@ libzebra_la_DEPENDENCIES = @LIB_REGEX@ libzebra_la_LIBADD = @LIB_REGEX@ pkginclude_HEADERS = \ - buffer.h checksum.h command.h filter.h getopt.h hash.h \ + zebra.h zconfig.h buffer.h checksum.h command.h filter.h getopt.h hash.h \ if.h linklist.h log.h \ memory.h network.h prefix.h routemap.h distribute.h sockunion.h \ - str.h stream.h table.h thread.h vector.h version.h vty.h zebra.h \ + str.h stream.h table.h thread.h vector.h version.h vty.h \ plist.h zclient.h sockopt.h smux.h md5.h if_rmap.h keychain.h \ privs.h sigevent.h pqueue.h jhash.h zassert.h memtypes.h \ workqueue.h route_types.h symtab.h heap.h \ qtime.h qpthreads.h mqueue.h qpselect.h qtimers.h qpnexus.h \ - command_parse.h command_queue.h qlib_init.h qafi_safi.h \ + command_parse.h command_queue.h vty_command.h qlib_init.h qafi_safi.h \ confirm.h misc.h vargs.h miyagi.h pthread_safe.h list_util.h \ - tstring.h node_type.h uty.h \ - vty_io.h vty_io_file.h vty_io_shell.h vty_io_term.h vty_cli.h \ - vty_io_basic.h keystroke.h qstring.h vio_fifo.h vio_lines.h \ + tstring.h command_common.h command_local.h vty_common.h vty_local.h \ + vty_io.h vty_io_file.h vty_io_shell.h vty_io_vsh.h vty_io_term.h vty_cli.h \ + vty_io_basic.h keystroke.h qstring.h elstring.h vio_fifo.h vio_lines.h \ qiovec.h qfstring.h errno_names.h \ route_types.h command_execute.h diff --git a/lib/command.c b/lib/command.c index 3c4f056a..adea80e4 100644 --- a/lib/command.c +++ b/lib/command.c @@ -21,86 +21,279 @@ 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. */ -#include <zebra.h> +#include "zconfig.h" +#include "version.h" +#include "misc.h" + +#include <ctype.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/utsname.h> #include "memory.h" #include "log.h" -#include <lib/version.h> #include "thread.h" #include "vector.h" -#include "vty.h" -#include "uty.h" #include "qstring.h" +#include "qtime.h" +#include "workqueue.h" #include "command.h" +#include "command_local.h" +#include "command_parse.h" #include "command_execute.h" -#include "workqueue.h" #include "command_queue.h" -#include "command_parse.h" +#include "vty_local.h" +#include "vty_command.h" +#include "vty_io.h" + +/* Vector of cmd_node, one for each known node, built during daemon + * initialisation. + * + * Declared extern in command_local.h, so it can get at it. + */ +vector node_vector = NULL ; + +/*============================================================================== + * Default motd string. + */ +#define DEFAULT_MOTD \ +"\n" \ +"Hello, this is " QUAGGA_PROGNAME " (version " QUAGGA_VERSION ")\n" \ + QUAGGA_COPYRIGHT "\n" \ +"\n" + +#ifdef QDEBUG +const char *debug_banner = + QUAGGA_PROGNAME " version " QUAGGA_VERSION " QDEBUG=" QDEBUG " " + __DATE__ " " __TIME__; +#endif + +/*============================================================================== + * Host information structure -- shared across command/vty + */ +struct host host = +{ + /* Host name of this router. */ + .name_set = false, + .name = NULL, /* set by cmd_init */ + .name_gen = 0, /* set by cmd_init */ + + /* Password for vty interface. */ + .password = NULL, + .password_encrypted = false, + + /* Enable password */ + .enable = NULL, + .enable_encrypted = false, + + /* System wide terminal lines. */ + .lines = -1, /* unset */ + + /* Log filename. */ + .logfile = NULL, + + /* config file name of this host */ + .config_file = NULL, + + /* Flags for services */ + .advanced = false, + .encrypt = false, + + /* Banner configuration. */ + .motd = DEFAULT_MOTD, + .motdfile = NULL, + + /* allow VTY to start without password */ + .no_password_check = false, + + /* if VTY starts without password, use RESTRICTED_NODE */ + .restricted_mode = false, + + /* Vty timeout value -- see "exec timeout" command */ + .vty_timeout_val = VTY_TIMEOUT_DEFAULT, + + /* Vty access-class command */ + .vty_accesslist_name = NULL, + + /* Vty access-class for IPv6. */ + .vty_ipv6_accesslist_name = NULL, + + /* Current directory -- initialised in vty_init() */ + .vty_cwd = NULL, +} ; + +/*============================================================================== + * host.name handling. + * + * If the host.name is set explicitly by command then host.name_set is true, + * and things are simple. + * + * Otherwise, need to ask the system. Does that once at start up, and if the + * host.name is unset by command -- so there should always be a valid host.name. + * + * However, it is conceivable that the host name changes (!). So, when asking + * for cmd_host_name(), can ask for the system to be asked afresh (if the name + * is not explicitly set). + * + * The VTY watch-dog timer refreshes the host.name on a regular basis, + * cmd_check_host_name(), so need not ask for a fresh host.name, unless wish + * to guarantee to be absolutely up to date. + * + * The VTY prompts need the current host name, but that is debounced using the + * host.name_gen value. host.name_gen is incremented each time the host.name + * actually changes. It is thought unlikely that this will ever wrap round, + * but it is guaranteed not to be zero. + * + * The fact that the host.name can change is reflected in the need to hold + * the VTY_LOCK while have the host.name in hand. + */ + +static void cmd_get_sys_host_name(void) ; +static void cmd_new_host_name(const char* name) ; + +/*------------------------------------------------------------------------------ + * Get the host name: (a) from an explicit host name command + * or: (b) from the last time the system was asked. + * + * Note that the system is asked regularly by the watch dog. + * + * NB: needs to be VTY_LOCK() *or* not running qpthreads. + */ +extern const char* +cmd_host_name(bool fresh) +{ + VTY_ASSERT_LOCKED() ; -/* Command vector which includes some level of command lists. Normally - each daemon maintains each own cmdvec. */ -vector cmdvec = NULL; + if (!host.name_set && fresh) + cmd_get_sys_host_name() ; + + return host.name ; +} ; + +/*------------------------------------------------------------------------------ + * Set (or unset) the host name from an explicit host name command. + * + * If unsets, immediately asks the system for the system host name (which may + * be the same !). + */ +static cmd_return_code_t +cmd_set_host_name(const char* name) +{ + VTY_LOCK() ; + + host.name_set = (name != NULL) ; + if (host.name_set) + cmd_new_host_name(name) ; + else + cmd_get_sys_host_name() ; + + VTY_UNLOCK() ; + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Get the host name from the system and set host.name to that. + * + * NB: result will not be NULL, whatever happens will get at least an empty + * '\0' terminated string. + * + * NB: called early in the morning to initialise host.name and to set + * host.name_gen != 0. + */ +static void +cmd_get_sys_host_name(void) +{ + struct utsname info ; -struct desc desc_cr; -char *command_cr = NULL; + VTY_ASSERT_LOCKED() ; -/* Host information structure. */ -struct host host; + uname (&info) ; + cmd_new_host_name(info.nodename) ; +} ; -/* Store of qstrings, used for parsing. */ -token_vector_t spare_token_strings ; +/*------------------------------------------------------------------------------ + * Set host.name to (possibly) new value. + * + * Does nothing if new name == old name, otherwise increments name_gen. + */ +static void +cmd_new_host_name(const char* name) +{ + if ((host.name == NULL) || (strcmp(host.name, name) != 0)) + { + XFREE(MTYPE_HOST, host.name) ; + host.name = XSTRDUP(MTYPE_HOST, name) ; + do ++host.name_gen ; while (host.name_gen == 0) ; + } ; +} ; -/* Standard command node structures. */ +/*============================================================================== + * node structures for the basic nodes. + * + * Covers everything up to and including the CONFIG_NODE. + */ static struct cmd_node auth_node = { - .node = AUTH_NODE, - "Password: ", + .node = AUTH_NODE, + .prompt = "Password: ", + + .parent = AUTH_NODE, /* self => no parent */ + .exit_to = AUTH_NODE, /* self => no exit */ + .end_to = AUTH_NODE, /* self => no end */ }; static struct cmd_node view_node = { - VIEW_NODE, - "%s> ", + .node = VIEW_NODE, + .prompt = "%s> ", + + .parent = AUTH_NODE, /* self => no parent */ + .exit_to = NULL_NODE, /* close ! */ + .end_to = VIEW_NODE, /* self => no end */ }; static struct cmd_node restricted_node = { - RESTRICTED_NODE, - "%s$ ", + .node = RESTRICTED_NODE, + .prompt = "%s$ ", + + .parent = AUTH_NODE, /* self => no parent */ + .exit_to = NULL_NODE, /* close ! */ + .end_to = RESTRICTED_NODE, /* self => no end */ }; static struct cmd_node auth_enable_node = { - AUTH_ENABLE_NODE, - "Password: ", + .node = AUTH_ENABLE_NODE, + .prompt = "Password: ", + + .parent = AUTH_ENABLE_NODE, /* self => no parent */ + .exit_to = NULL_NODE, /* close ! */ + .end_to = AUTH_ENABLE_NODE, /* self => no end */ }; static struct cmd_node enable_node = { - ENABLE_NODE, - "%s# ", + .node = ENABLE_NODE, + .prompt = "%s# ", + + .parent = ENABLE_NODE, /* self => no parent */ + .exit_to = NULL_NODE, /* close ! */ + .end_to = ENABLE_NODE, /* self => no end */ }; static struct cmd_node config_node = { - CONFIG_NODE, - "%s(config)# ", - 1 -}; + .node = CONFIG_NODE, + .prompt = "%s(config)# ", -/* Default motd string. */ -static const char *default_motd = -"\r\n\ -Hello, this is " QUAGGA_PROGNAME " (version " QUAGGA_VERSION ").\r\n\ -" QUAGGA_COPYRIGHT "\r\n\ -\r\n"; + .parent = CONFIG_NODE, /* self => no parent */ + .exit_to = ENABLE_NODE, /* exit == end for CONFIG_NODE */ + .end_to = ENABLE_NODE, /* standard end action */ -#ifdef QDEBUG -const char *debug_banner = - QUAGGA_PROGNAME " version " QUAGGA_VERSION " QDEBUG=" QDEBUG " " - __DATE__ " " __TIME__; -#endif + .config_to_vtysh = true +}; static const struct facility_map { int facility; @@ -132,18 +325,29 @@ static const struct facility_map { { 0, NULL, 0 }, }; -static struct cmd_element cmd_pipe = +#if 0 +static enum cmd_return_code +cmd_pipe_func(cmd_command self DEFUN_CMD_ARG_UNUSED, + struct vty* vty DEFUN_CMD_ARG_UNUSED, + int argc DEFUN_CMD_ARG_UNUSED, + argv_t argv DEFUN_CMD_ARG_UNUSED) +{ + return CMD_SUCCESS ; +} ; + +static struct cmd_command cmd_pipe_element = { .string = "< or <|", /* Dummy */ .func = cmd_pipe_func, .doc = "Pipe input to command processor", .daemon = 0, - .strvec = NULL, + .items = NULL, .cmdsize = 0, .config = NULL, .subconfig = NULL, .attr = CMD_ATTR_SIMPLE, } ; +#endif static const char * @@ -188,9 +392,12 @@ print_version (const char *progname) } -/* Utility function to concatenate argv argument into a single string - with inserting ' ' character between each argument. */ -char * +/*------------------------------------------------------------------------------ + * Concatenate argv argument into a single string, inserting ' ' between each + * argument. + * + */ +extern char * argv_concat (const char* const* argv, int argc, int shift) { int i; @@ -201,8 +408,10 @@ argv_concat (const char* const* argv, int argc, int shift) len = 0; for (i = shift; i < argc; i++) len += strlen(argv[i])+1; + if (!len) return NULL; + p = str = XMALLOC(MTYPE_TMP, len); for (i = shift; i < argc; i++) { @@ -220,8 +429,8 @@ argv_concat (const char* const* argv, int argc, int shift) static int cmp_node (const void *p, const void *q) { - const struct cmd_element *a = *(struct cmd_element * const *)p; - const struct cmd_element *b = *(struct cmd_element * const *)q; + const struct cmd_command *a = *(struct cmd_command * const *)p; + const struct cmd_command *b = *(struct cmd_command * const *)q; return strcmp (a->string, b->string); } @@ -237,42 +446,146 @@ cmp_desc (const void *p, const void *q) #endif //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -/* Install top node of command vector. */ -void +/*------------------------------------------------------------------------------ + * Install top node of command vector. + * + * Initialised as follows: + * + * .node -- must be set before node is installed + * .prompt -- must be set before node is installed + * .config_to_vtysh -- must be set true/false before node is installed + * + * .parent -- may be set, or if 0 (node_null) will be set to default + * .exit_to -- may be set, or if 0 (node_null) will be set to default + * .end_to -- may be set, or if 0 (node_null) will be set to default + * + * .config_write -- is set from parameter + * + * .cmd_vector -- initialised empty + * + * Default parent node: + * + * * all nodes >= NODE_CONFIG have NODE_CONFIG as a parent + * * all nodes < NODE_CONFIG are their own parents + * + * Default exit_to: + * + * * all nodes > NODE_CONFIG exit_to their parent + * * node == NODE_CONFIG exit_to ENABLE_NODE (same as end_to !) + * * all nodes < NODE_CONFIG exit_to close + * + * Default end_to: + * + * * all nodes >= NODE_CONFIG end_to ENABLE_NODE + * * all nodes < NODE_CONFIG end_to themselves + * + */ +extern void install_node (struct cmd_node *node, - int (*func) (struct vty *)) + int (*config_write) (struct vty *)) { - vector_set_index (cmdvec, node->node, node); - node->func = func; - node->cmd_vector = vector_init (0); -} + confirm(NULL_NODE == 0) ; /* unset value for .parent, .end_to & .exit_to */ -/* Compare two command's string. Used in sort_node (). */ -static int -cmp_node (const struct cmd_element **a, const struct cmd_element **b) + if (node->parent == NULL_NODE) + { + if (node->node >= CONFIG_NODE) + node->parent = CONFIG_NODE ; + else + node->parent = node->node ; + } ; + + if (node->end_to == NULL_NODE) + { + if (node->node >= CONFIG_NODE) + node->end_to = ENABLE_NODE ; + else + node->end_to = node->node ; + } ; + + if (node->exit_to == NULL_NODE) + { + if (node->node > CONFIG_NODE) + node->exit_to = node->parent ; + else if (node->node == CONFIG_NODE) + node->exit_to = ENABLE_NODE ; + else + node->exit_to = NULL_NODE ; + } ; + + node->config_write = config_write ; + + vector_init_new(node->cmd_vector, 0) ; /* embedded */ + + vector_set_index (node_vector, node->node, node); +} ; + +/*------------------------------------------------------------------------------ + * Return address of cmd_node -- asserts is not NULL ! + */ +static cmd_node +cmd_cmd_node(node_type_t node) { - return strcmp ((*a)->string, (*b)->string); -} + cmd_node cn ; + + cn = vector_get_item(node_vector, node) ; + if (cn != NULL) + return cn ; + + zabort("invalid node") ; +} ; + +/*------------------------------------------------------------------------------ + * Return parent node + */ +extern node_type_t +cmd_node_parent(node_type_t node) +{ + return (cmd_cmd_node(node))->parent ; +} ; + +/*------------------------------------------------------------------------------ + * Return exit_to node + */ +extern node_type_t +cmd_node_exit_to(node_type_t node) +{ + return (cmd_cmd_node(node))->exit_to ; +} ; + +/*------------------------------------------------------------------------------ + * Return parent node + */ +extern node_type_t +cmd_node_end_to(node_type_t node) +{ + return (cmd_cmd_node(node))->end_to ; +} ; + + +/*------------------------------------------------------------------------------ + * Sorting of all node cmd_vectors. + */ + +/* Compare two command's string. Used in sort_node (). */ static int -cmp_desc (const struct desc **a, const struct desc **b) +cmp_node (const struct cmd_command **a, const struct cmd_command **b) { - return strcmp ((*a)->cmd, (*b)->cmd); + return strcmp ((*a)->string, (*b)->string); } /* Sort each node's command element according to command string. */ -void +extern void sort_node () { unsigned int i ; - for (i = 0; i < vector_length(cmdvec); i++) + for (i = 0; i < vector_length(node_vector); i++) { struct cmd_node *cnode; vector cmd_vector ; - unsigned int j; - cnode = vector_get_item(cmdvec, i) ; + cnode = vector_get_item(node_vector, i) ; if (cnode == NULL) continue ; @@ -282,25 +595,10 @@ sort_node () continue ; vector_sort(cmd_vector, (vector_sort_cmp*)cmp_node) ; - - for (j = 0; j < vector_length(cmd_vector); j++) - { - struct cmd_element *cmd_element ; - vector descvec ; - - cmd_element = vector_get_item (cmd_vector, j); - if (cmd_element == NULL) - continue ; - - descvec = vector_get_last_item(cmd_element->strvec) ; - if (descvec == NULL) - continue ; - - vector_sort(descvec, (vector_sort_cmp*)cmp_desc) ; - } ; } ; } ; +#if 0 /*------------------------------------------------------------------------------ * Take string and break it into tokens -- see cmd_make_tokens(). * @@ -310,8 +608,11 @@ sort_node () extern vector cmd_make_strvec (const char *string) { +#if 0 return cmd_tokenise(NULL, string) ; #error sort this one out +#endif + return NULL ; } ; /*------------------------------------------------------------------------------ @@ -320,204 +621,47 @@ cmd_make_strvec (const char *string) * Create vector if required. */ extern vector -cmd_add_to_strvec (vector strvec, const char* str) +cmd_add_to_strvec (vector items, const char* str) { - if (strvec == NULL) - strvec = vector_init(1) ; + if (items == NULL) + items = vector_init(1) ; - vector_push_item(strvec, XSTRDUP(MTYPE_STRVEC, str)); + vector_push_item(items, XSTRDUP(MTYPE_STRVEC, str)); - return strvec ; + return items ; } ; /*------------------------------------------------------------------------------ * Free allocated string vector (if any) and all its contents. * - * Note that this is perfectly happy with strvec == NULL. + * Note that this is perfectly happy with items == NULL. */ extern void -cmd_free_strvec (vector strvec) +cmd_free_strvec (vector items) { char *cp; - /* Note that vector_ream_free() returns NULL if strvec == NULL */ - while((cp = vector_ream(strvec, free_it)) != NULL) + /* Note that vector_ream_free() returns NULL if items == NULL */ + while((cp = vector_ream(items, free_it)) != NULL) XFREE (MTYPE_STRVEC, cp); } ; -/*----------------------------------------------------------------------------*/ - -/* Fetch next description. Used in cmd_make_descvec(). */ -static char * -cmd_desc_str (const char **string) -{ - const char *cp, *start; - char *token; - int strlen; - - cp = *string; - - if (cp == NULL) - return NULL; - - /* Skip white spaces. */ - while (isspace ((int) *cp) && *cp != '\0') - cp++; - - /* Return if there is only white spaces */ - if (*cp == '\0') - return NULL; - - start = cp; - - while (!(*cp == '\r' || *cp == '\n') && *cp != '\0') - cp++; - - strlen = cp - start; - token = XMALLOC (MTYPE_STRVEC, strlen + 1); - memcpy (token, start, strlen); - *(token + strlen) = '\0'; - - *string = cp; - - return token; -} - -/* New string vector. */ -static vector -cmd_make_descvec (const char *string, const char *descstr) -{ - int multiple = 0; - const char *sp; - char *token; - int len; - const char *cp; - const char *dp; - vector allvec; - vector strvec = NULL; - struct desc *desc; - - cp = string; - dp = descstr; - - if (cp == NULL) - return NULL; - - allvec = vector_init (0); - - while (1) - { - while (isspace ((int) *cp) && *cp != '\0') - cp++; - - if (*cp == '(') - { - multiple = 1; - cp++; - } - if (*cp == ')') - { - multiple = 0; - cp++; - } - if (*cp == '|') - { - if (! multiple) - { - fprintf (stderr, "Command parse error!: %s\n", string); - exit (1); - } - cp++; - } - - while (isspace ((int) *cp) && *cp != '\0') - cp++; - - if (*cp == '(') - { - multiple = 1; - cp++; - } - - if (*cp == '\0') - return allvec; - - sp = cp; - - while (! (isspace ((int) *cp) || *cp == ')' || *cp == '|') && *cp != '\0') - cp++; - - len = cp - sp; - - token = XMALLOC (MTYPE_STRVEC, len + 1); - memcpy (token, sp, len); - *(token + len) = '\0'; - - desc = XCALLOC (MTYPE_DESC, sizeof (struct desc)); - desc->cmd = token; - desc->str = cmd_desc_str (&dp); - - if (multiple) - { - if (multiple == 1) - { - strvec = vector_init (0); - vector_set (allvec, strvec); - } - multiple++; - } - else - { - strvec = vector_init (0); - vector_set (allvec, strvec); - } - vector_set (strvec, desc); - } -} - -/* Count mandantory string vector size. This is to determine inputed - command has enough command length. */ -static int -cmd_cmdsize (vector strvec) -{ - unsigned int i; - int size = 0; - - for (i = 0; i < vector_length(strvec); i++) - { - vector descvec; - - descvec = vector_get_item (strvec, i) ; - if (descvec == NULL) - continue ; - - if (vector_length(descvec) == 1) - { - struct desc *desc; - - desc = vector_get_item(descvec, 0) ; - if (desc != NULL) - if (desc->cmd == NULL || CMD_OPTION (desc->cmd)) - break ; - } - size++; - } ; - - return size; -} +#endif -/* Return prompt character of specified node. */ -const char * -cmd_prompt (enum node_type node) +/*------------------------------------------------------------------------------ + * Return prompt string for the specified node. + */ +extern const char * +cmd_prompt(node_type_t node) { struct cmd_node *cnode; - assert(cmdvec != NULL) ; - assert(cmdvec->p_items != NULL) ; + assert(node_vector != NULL) ; + assert(node_vector->p_items != NULL) ; cnode = NULL ; - if (node < cmdvec->limit) - cnode = vector_get_item (cmdvec, node); + if (node < node_vector->limit) + cnode = vector_get_item (node_vector, node); if (cnode == NULL) { @@ -528,17 +672,16 @@ cmd_prompt (enum node_type node) return cnode->prompt; } -/* Install a command into a node. */ -void -install_element (enum node_type ntype, struct cmd_element *cmd) +/*------------------------------------------------------------------------------ + * Install a command into a node. + * + */ +extern void +install_element(node_type_t ntype, cmd_command cmd) { - struct cmd_node *cnode; + cmd_node cnode; - /* cmd_init hasn't been called */ - if (!cmdvec) - return; - - cnode = vector_get_item (cmdvec, ntype); + cnode = vector_get_item (node_vector, ntype); if (cnode == NULL) { @@ -549,65 +692,75 @@ install_element (enum node_type ntype, struct cmd_element *cmd) vector_set (cnode->cmd_vector, cmd); - if (cmd->strvec == NULL) - cmd->strvec = cmd_make_descvec (cmd->string, cmd->doc); + /* A cmd_command may appear in a number of cmd_vectors, but the cmd->items + * etc. need only be set up once. + * + * It is assumed that once a cmd_command has been installed it will never be + * changed ! + * + * Need now to "compile" the command if not already compiled. + */ + if (cmd->items == NULL) + cmd_compile(cmd); - cmd->cmdsize = cmd_cmdsize (cmd->strvec); -} + /* Post compilation check for reasonable cmd_command ! */ + cmd_compile_check(cmd) ; +} ; +/*============================================================================== + * Password encryption + */ static const unsigned char itoa64[] = -"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; -static void -to64(char *s, long v, int n) -{ - while (--n >= 0) - { - *s++ = itoa64[v&0x3f]; - v >>= 6; - } -} - -static char * +/* Uses the usual crypt() function. + * + * Note that crypt() is not thread safe ! + */ +static const char * zencrypt (const char *passwd) { - char salt[6]; - struct timeval tv; - char *crypt (const char *, const char *); + uint32_t r ; + char salt[3]; + + extern char *crypt (const char *, const char *) ; - gettimeofday(&tv,0); + r = qt_random(*passwd) ; - to64(&salt[0], random(), 3); - to64(&salt[3], tv.tv_usec, 3); - salt[5] = '\0'; + salt[0] = itoa64[(r >> (32 - 5)) & 0x3F] ; /* ms 5 */ + salt[1] = itoa64[(r >> (32 - 10)) & 0x3F] ; /* next ms 5 */ + salt[2] = '\0'; - return crypt (passwd, salt); + return crypt(passwd, salt) ; } /* This function write configuration of this host. */ static int config_write_host (struct vty *vty) { + VTY_LOCK() ; + if (qpthreads_enabled) vty_out (vty, "threaded%s", VTY_NEWLINE); - if (host.name) + if (host.name_set) vty_out (vty, "hostname %s%s", host.name, VTY_NEWLINE); - if (host.encrypt) + if (host.password != NULL) { - if (host.password_encrypt) - vty_out (vty, "password 8 %s%s", host.password_encrypt, VTY_NEWLINE); - if (host.enable_encrypt) - vty_out (vty, "enable password 8 %s%s", host.enable_encrypt, VTY_NEWLINE); - } - else + if (host.password_encrypted) + vty_out (vty, "password 8 %s\n", host.password); + else + vty_out (vty, "password %s\n", host.password); + } ; + + if (host.enable != NULL) { - if (host.password) - vty_out (vty, "password %s%s", host.password, VTY_NEWLINE); - if (host.enable) - vty_out (vty, "enable password %s%s", host.enable, VTY_NEWLINE); - } + if (host.enable_encrypted) + vty_out (vty, "enable password 8 %s\n", host.enable); + else + vty_out (vty, "enable password %s\n", host.enable); + } ; if (zlog_get_default_lvl(NULL) != LOG_DEBUG) { @@ -671,1674 +824,16 @@ config_write_host (struct vty *vty) vty_out (vty, "service terminal-length %d%s", host.lines, VTY_NEWLINE); - if (host.motdfile) + if (host.motdfile) vty_out (vty, "banner motd file %s%s", host.motdfile, VTY_NEWLINE); else if (! host.motd) vty_out (vty, "no banner motd%s", VTY_NEWLINE); - return 1; -} - -/* Utility function for getting command vector. */ -static vector -cmd_node_vector (vector v, enum node_type ntype) -{ - struct cmd_node *cnode = vector_get_item (v, ntype); - return cnode->cmd_vector; -} - -#if 0 -/* Filter command vector by symbol. This function is not actually used; - * should it be deleted? */ -static int -cmd_filter_by_symbol (char *command, char *symbol) -{ - int i, lim; - - if (strcmp (symbol, "IPV4_ADDRESS") == 0) - { - i = 0; - lim = strlen (command); - while (i < lim) - { - if (! (isdigit ((int) command[i]) || command[i] == '.' || command[i] == '/')) - return 1; - i++; - } - return 0; - } - if (strcmp (symbol, "STRING") == 0) - { - i = 0; - lim = strlen (command); - while (i < lim) - { - if (! (isalpha ((int) command[i]) || command[i] == '_' || command[i] == '-')) - return 1; - i++; - } - return 0; - } - if (strcmp (symbol, "IFNAME") == 0) - { - i = 0; - lim = strlen (command); - while (i < lim) - { - if (! isalnum ((int) command[i])) - return 1; - i++; - } - return 0; - } - return 0; -} -#endif - -/*============================================================================== - * Command "filtering". - * - * The command parsing process starts with a (shallow) copy of the cmd_vector - * entry for the current "node". - * - * So cmd_v contains pointers to struct cmd_element values. When match fails, - * the pointer is set NULL -- so parsing is a process of reducing the cmd_v - * down to just the entries that match. - * - * Each cmd_element has a vector "strvec", which contains an entry for each - * "token" position. That entry is a vector containing the possible values at - * that position. - * - * - */ - -/*------------------------------------------------------------------------------ - * Make strict or completion match and return match type flag. - * - * Takes: command -- address of candidate token - * cmd_v -- vector of commands that is being reduced/filtered - * index -- index of token (position in line -- 0 == first) - * min_match -- any_match => allow partial matching - * exact_match => must match completely - * - * Returns: any of the enum match_type values: - * - * no_match => no match of any kind - * - * extend_match => saw an optional token - * ipv4_prefix_match ) - * ipv4_match ) - * ipv6_prefix_match ) saw full or partial match for this - * ipv6_match ) - * range_match ) - * vararg_match ) - * - * partly_match => saw partial match for a keyword - * exact_match => saw exact match for a keyword - * - * Note that these return values are in ascending order of preference. So, - * if there are multiple possibilities at this position, will return the one - * furthest down this list. - */ -static enum match_type -cmd_filter(const char *command, vector cmd_v, unsigned int index, - match_type_t min_match) -{ - unsigned int i; - unsigned int k; - enum match_type best_match; - size_t c_len ; - - best_match = no_match ; - c_len = strlen(command) ; - - /* If command and cmd_element string do match, keep in vector */ - k = 0 ; - for (i = 0; i < vector_length (cmd_v); i++) - { - const char *str; - struct cmd_element *cmd_element; - vector descvec; - struct desc *desc; - unsigned int j; - bool matched ; - - cmd_element = vector_get_item(cmd_v, i) ; - - /* Skip past NULL cmd_v entries (just in case) */ - if (cmd_element == NULL) - continue ; - - /* Discard cmd_v entry that has no token at the current position */ - descvec = vector_get_item(cmd_element->strvec, index) ; - if (descvec == NULL) - continue ; - - /* See if get any sort of match at current position */ - matched = 0 ; - for (j = 0; j < vector_length (descvec); j++) - { - desc = vector_get_item(descvec, j) ; - if (desc == NULL) - continue ; - - str = desc->cmd; - - if (CMD_VARARG (str)) - { - if (best_match < vararg_match) - best_match = vararg_match; - matched = true ; - } - else if (CMD_RANGE (str)) - { - if (cmd_range_match (str, command)) - { - if (best_match < range_match) - best_match = range_match; - matched = true ; - } - } -#ifdef HAVE_IPV6 - else if (CMD_IPV6 (str)) - { - if (cmd_ipv6_match (command) >= min_match) - { - if (best_match < ipv6_match) - best_match = ipv6_match; - matched = true ; - } - } - else if (CMD_IPV6_PREFIX (str)) - { - if (cmd_ipv6_prefix_match (command) >= min_match) - { - if (best_match < ipv6_prefix_match) - best_match = ipv6_prefix_match; - matched = true ; - } - } -#endif /* HAVE_IPV6 */ - else if (CMD_IPV4 (str)) - { - if (cmd_ipv4_match (command) >= min_match) - { - if (best_match < ipv4_match) - best_match = ipv4_match; - matched = true ; - } - } - else if (CMD_IPV4_PREFIX (str)) - { - if (cmd_ipv4_prefix_match (command) >= min_match) - { - if (best_match < ipv4_prefix_match) - best_match = ipv4_prefix_match; - matched = true ; - } - } - else if (CMD_OPTION (str) || CMD_VARIABLE (str)) - { - if (best_match < extend_match) - best_match = extend_match; - matched = true ; - } - else - { - if (strcmp (command, str) == 0) - { - best_match = exact_match ; - matched = true ; - } - else if (min_match <= partly_match) - { - if (strncmp (command, str, c_len) == 0) - { - if (best_match < partly_match) - best_match = partly_match ; - matched = true ; - } ; - } ; - } ; - } ; - - /* Keep cmd_element if have a match */ - if (matched) - vector_set_item(cmd_v, k++, cmd_element) ; - } ; - - vector_set_length(cmd_v, k) ; /* discard what did not keep */ - - return best_match; -} ; - -/*------------------------------------------------------------------------------ - * Check for ambiguous match - * - * Given the best that cmd_filter_by_completion() or cmd_filter_by_string() - * found, check as follows: - * - * 1. discard all commands for which do not have the type of match selected. - * - * See above for the ranking of matches. - * - * 2. for "partial match", look out for matching more than one keyword, and - * return 1 if finds that. - * - * 3. for "range match", look out for matching more than one range, and - * return 1 if finds that. - * - * 4. for ipv4_prefix_match and ipv6_prefix_match, if get a "partial match", - * return 2. - * - * This appears to catch things which are supposed to be prefixes, but - * do not have a '/' or do not have any digits after the '/'. - * - * Takes: command -- address of candidate token - * cmd_v -- vector of commands that is being reduced/filtered - * index -- index of token (position in line -- 0 == first) - * type -- as returned by cmd_filter_by_completion() - * or cmd_filter_by_string() - * - * Returns: 0 => not ambiguous - * 1 => ambiguous -- the candidate token matches more than one - * keyword, or the candidate number matches more - * than one number range. - * 2 => partial match for ipv4_prefix or ipv6_prefix - * (missing '/' or no digits after '/'). - * - * NB: it is assumed that cannot find both 1 and 2 states. But in any case, - * returns 1 in preference. - */ -static int -is_cmd_ambiguous (const char *command, vector cmd_v, int index, - enum match_type type) -{ - unsigned int i; - unsigned int k; - int ret ; - - ret = 0 ; /* all's well so far */ - k = 0 ; /* nothing kept, yet */ - - for (i = 0; i < vector_length (cmd_v); i++) - { - unsigned int j; - struct cmd_element *cmd_element; - const char *str_matched ; - vector descvec; - struct desc *desc; - bool matched ; - enum match_type mt ; - - cmd_element = vector_get_item (cmd_v, i) ; - - /* Skip past NULL cmd_v entries (just in case) */ - if (cmd_element == NULL) - continue ; - - /* The cmd_v entry MUST have a token at the current position */ - descvec = vector_get_item (cmd_element->strvec, index) ; - assert(descvec != NULL) ; - - /* See if have a match against any of the current possibilities - * - * str_matched is set the first time get a partial string match, - * or the first time get a number range match. - * - * If get a second partial string match or number range match, then - * unless - */ - str_matched = NULL ; - matched = 0; - for (j = 0; j < vector_length (descvec); j++) - { - enum match_type ret; - const char *str ; - - desc = vector_get_item (descvec, j) ; - if (desc == NULL) - continue ; - - str = desc->cmd; - - switch (type) - { - case exact_match: - if (!(CMD_OPTION (str) || CMD_VARIABLE (str)) - && strcmp (command, str) == 0) - matched = true ; - break; - - case partly_match: - if (!(CMD_OPTION (str) || CMD_VARIABLE (str)) - && strncmp (command, str, strlen (command)) == 0) - { - if (str_matched && (strcmp (str_matched, str) != 0)) - ret = 1; /* There is ambiguous match. */ - else - str_matched = str; - matched = true ; - } - break; - - case range_match: - if (cmd_range_match (str, command)) - { - if (str_matched && strcmp (str_matched, str) != 0) - ret = 1; - else - str_matched = str; - matched = true ; - } - break; - -#ifdef HAVE_IPV6 - case ipv6_match: - if (CMD_IPV6 (str)) - matched = true ; - break; - - case ipv6_prefix_match: - if ((mt = cmd_ipv6_prefix_match (command)) != no_match) - { - if ((mt == partly_match) && (ret != 1)) - ret = 2; /* There is incomplete match. */ - matched = true ; - } - break; -#endif /* HAVE_IPV6 */ - - case ipv4_match: - if (CMD_IPV4 (str)) - matched = true ; - break; - - case ipv4_prefix_match: - if ((mt = cmd_ipv4_prefix_match (command)) != no_match) - { - if ((mt == partly_match) && (ret != 1)) - ret = 2; /* There is incomplete match. */ - matched = true ; - } - break; - - case extend_match: - if (CMD_OPTION (str) || CMD_VARIABLE (str)) - matched = true ; - break; - - case no_match: - default: - break; - } ; - } ; - - /* Keep cmd_element if have a match */ - if (matched) - vector_set_item(cmd_v, k++, cmd_element) ; - } ; - - vector_set_length(cmd_v, k) ; /* discard what did not keep */ - - return ret ; -} ; - -/*------------------------------------------------------------------------------ - * If src matches dst return dst string, otherwise return NULL - * - * Returns NULL if dst is an option, variable of vararg. - * - * NULL or empty src are deemed to match. - */ -static const char * -cmd_entry_function (const char *src, const char *dst) -{ - if (CMD_OPTION (dst) || CMD_VARIABLE (dst) || CMD_VARARG (dst)) - return NULL; - - if ((src == NULL) || (*src == '\0')) - return dst; - - if (strncmp (src, dst, strlen (src)) == 0) - return dst; - - return NULL; -} - -/* If src matches dst return dst string, otherwise return NULL */ -/* This version will return the dst string always if it is - CMD_VARIABLE for '?' key processing */ -static const char * -cmd_entry_function_desc (const char *src, const char *dst) -{ - if (CMD_VARARG (dst)) - return dst; - - if (CMD_RANGE (dst)) - { - if (cmd_range_match (dst, src)) - return dst; - else - return NULL; - } - -#ifdef HAVE_IPV6 - if (CMD_IPV6 (dst)) - { - if (cmd_ipv6_match (src)) - return dst; - else - return NULL; - } - - if (CMD_IPV6_PREFIX (dst)) - { - if (cmd_ipv6_prefix_match (src)) - return dst; - else - return NULL; - } -#endif /* HAVE_IPV6 */ - - if (CMD_IPV4 (dst)) - { - if (cmd_ipv4_match (src)) - return dst; - else - return NULL; - } - - if (CMD_IPV4_PREFIX (dst)) - { - if (cmd_ipv4_prefix_match (src)) - return dst; - else - return NULL; - } - - /* Optional or variable commands always match on '?' */ - if (CMD_OPTION (dst) || CMD_VARIABLE (dst)) - return dst; - - /* In case of 'command \t', given src is NULL string. */ - if (src == NULL) - return dst; - - if (strncmp (src, dst, strlen (src)) == 0) - return dst; - else - return NULL; -} - -/*------------------------------------------------------------------------------ - * Check same string element existence. - * - * Returns: 0 => found same string in the vector - * 1 => NOT found same string in the vector - */ -static bool -cmd_unique_string (vector v, const char *str) -{ - unsigned int i; - char *match; + VTY_UNLOCK() ; - for (i = 0; i < vector_length (v); i++) - if ((match = vector_get_item (v, i)) != NULL) - if (strcmp (match, str) == 0) - return 0; return 1; } -/* Compare string to description vector. If there is same string - return 1 else return 0. */ -static bool -desc_unique_string (vector v, const char *str) -{ - unsigned int i; - struct desc *desc; - - for (i = 0; i < vector_length (v); i++) - if ((desc = vector_get_item (v, i)) != NULL) - if (strcmp (desc->cmd, str) == 0) - return 1; - return 0; -} - -/*------------------------------------------------------------------------------ - * Special parsing for leading 'do', if current mode allows it. - * - * If finds a valid "do", sets current node and do_shortcut flag, and discards - * the "do" token. - * - * Returns: true <=> dealt with the "do" - * false => no do, or no do allowed. - */ -static bool -cmd_try_do_shortcut(cmd_parsed parsed) -{ - const char* ts ; - - if (parsed->cnode < MIN_DO_SHORTCUT_NODE) - return false ; - - ts = cmd_token_string(cmd_token_get(&parsed->tokens, 0)) ; - if (strcmp("do", ts) != 0) - return false ; - - parsed->cnode = ENABLE_NODE ; - parsed->do_shortcut = true ; - cmd_token_discard(cmd_token_shift(&parsed->tokens)) ; - - return true ; -} ; - -/*============================================================================== - * '?' describe command support. - */ - -/*------------------------------------------------------------------------------ - * Get description of current (partial) command - * - * Returns: NULL => no description available - * - * status set to CMD_ERR_NO_MATCH or CMD_ERR_AMBIGUOUS - * - * or: address of vector of "struct desc" values available. - * - * NB: when a vector is returned it is the caller's responsibility to - * vector_free() it. (The contents are all effectively const, so do not - * themselves need to be freed.) - */ -extern vector -cmd_describe_command (const char* line, node_type_t node, - cmd_return_code_t* status) -{ - vector ret ; - struct cmd_parsed parsed_s ; - cmd_parsed parsed ; - cmd_token_type_t tok_total ; - - /* Set up a parser object and tokenise the command line */ - parsed = cmd_parse_init_new(&parsed_s) ; - tok_total = cmd_tokenise(parsed, line, node) ; - - - - - - - - /* Stop immediately if line is empty apart from comment */ - if ((tok_total & ~cmd_tok_comment) == cmd_tok_null) - return CMD_EMPTY ; /* NB: parsed->cmd == NULL */ - - /* Level 1 parsing - * - * Strip quotes and escapes from all the tokens. - */ - if (tok_total != cmd_tok_simple) - { - ret = cmd_parse_phase_one(parsed) ; - if (ret != CMD_SUCCESS) - return ret ; - } ; - - /* If allowed to 'do', see if there. - * - * 'do' forces command to be parsed in ENABLE_NODE (if allowed) - */ - if (type & cmd_parse_do) - cmd_try_do_shortcut(parsed) ; - - - - - return cmd_describe_command_real (tokens, node, status); - - - - static vector - cmd_describe_command_real (vector tokens, int node, int *status) - { - unsigned int i; - vector cmd_vector; - #define INIT_MATCHVEC_SIZE 10 - vector matchvec; - struct cmd_element *cmd_element; - unsigned int index; - int ret; - enum match_type match; - char *command; - - /* Set index. */ - if (vector_length (tokens) == 0) - { - *status = CMD_ERR_NO_MATCH; - return NULL; - } - else - index = vector_length (tokens) - 1; - - /* Make copy vector of current node's command vector. */ - cmd_vector = vector_copy (cmd_node_vector (cmdvec, node)); - - /* Prepare match vector */ - matchvec = vector_init (INIT_MATCHVEC_SIZE); - - /* Filter commands. */ - /* Only words precedes current word will be checked in this loop. */ - for (i = 0; i < index; i++) - if ((command = vector_get_item (tokens, i))) - { - match = cmd_filter(command, cmd_vector, i, any_match) ; - - if (match == vararg_match) - { - struct cmd_element *cmd_element; - vector descvec; - unsigned int j, k; - - for (j = 0; j < vector_length (cmd_vector); j++) - if ((cmd_element = vector_get_item (cmd_vector, j)) != NULL - && (vector_length (cmd_element->strvec))) - { - descvec = vector_get_item (cmd_element->strvec, - vector_length (cmd_element->strvec) - 1); - for (k = 0; k < vector_length (descvec); k++) - { - struct desc *desc = vector_get_item (descvec, k); - vector_set (matchvec, desc); - } - } - - vector_set (matchvec, &desc_cr); - vector_free (cmd_vector); - - return matchvec; - } ; - - ret = is_cmd_ambiguous (command, cmd_vector, i, match) ; - if (ret != 0) - { - vector_free (cmd_vector); - vector_free (matchvec); - *status = (ret == 1) ? CMD_ERR_AMBIGUOUS - : CMD_ERR_NO_MATCH ; - return NULL ; - } ; - } - - /* Prepare match vector */ - /* matchvec = vector_init (INIT_MATCHVEC_SIZE); */ - - /* Make sure that cmd_vector is filtered based on current word */ - command = vector_get_item (tokens, index); - if (command) - match = cmd_filter(command, cmd_vector, index, any_match); - - /* Make description vector. */ - for (i = 0; i < vector_length (cmd_vector); i++) - { - vector strvec ; - - cmd_element = vector_get_item (cmd_vector, i) ; - if (cmd_element == NULL) - continue ; - - /* Ignore cmd_element if no tokens at index position. - * - * Deal with special case of possible <cr> completion. - */ - strvec = cmd_element->strvec; - if (index >= vector_length (strvec)) - { - if (command == NULL && index == vector_length (strvec)) - { - if (!desc_unique_string (matchvec, command_cr)) - vector_push_item(matchvec, &desc_cr); - } - continue ; - } ; - - /* Check if command is completed. */ - unsigned int j; - vector descvec = vector_get_item (strvec, index); - struct desc *desc; - - for (j = 0; j < vector_length (descvec); j++) - if ((desc = vector_get_item (descvec, j))) - { - const char *string; - - string = cmd_entry_function_desc (command, desc->cmd); - if (string) - { - /* Uniqueness check */ - if (!desc_unique_string (matchvec, string)) - vector_push_item(matchvec, desc); - } - } ; - } ; - - vector_free (cmd_vector); - - if (vector_length(matchvec) == 0) - { - vector_free (matchvec); - *status = CMD_ERR_NO_MATCH; - return NULL; - } - - *status = CMD_SUCCESS; - return matchvec; - } - - - - - - - - - - - - - - - - - - - cmd_parse_reset(parsed, false) ; - - return -} ; - -/*------------------------------------------------------------------------------ - * Check LCD of matched command. - * - * Scan list of matched keywords, and by comparing them pair-wise, find the - * longest common leading substring. - * - * Returns: 0 if zero or one matched keywords - * length of longest common leading substring, otherwise. - */ -static int -cmd_lcd (vector matchvec) -{ - int n ; - int i ; - int lcd ; - char *sp, *sq, *ss ; - - n = vector_end(matchvec) ; - if (n < 2) - return 0 ; - - ss = vector_get_item(matchvec, 0) ; - lcd = strlen(ss) ; - - for (i = 1 ; i < n ; i++) - { - sq = ss ; - ss = vector_get_item(matchvec, i) ; - sp = ss ; - - while ((*sp == *sq) && (*sp != '\0')) - { - ++sp ; - ++sq ; - } ; - - if (lcd > (sp - ss)) - lcd = (sp - ss) ; - } - return lcd; -} - -/*------------------------------------------------------------------------------ - * Command line completion support. - */ -static vector -cmd_complete_command_real (vector tokens, int node, int *status) -{ - unsigned int i; - unsigned int ivl ; - unsigned int last_ivl ; - vector cmd_v ; -#define INIT_MATCHVEC_SIZE 10 - vector matchvec; - struct cmd_element *cmd_element; - unsigned int index; - struct desc *desc; - vector descvec; - char *token; - int n ; - - /* Stop immediately if the tokens is empty. */ - if (vector_length (tokens) == 0) - { - *status = CMD_ERR_NO_MATCH; - return NULL; - } - - /* Take (shallow) copy of cmdvec for given node. */ - cmd_v = vector_copy (cmd_node_vector (cmdvec, node)); - - /* First, filter upto, but excluding last token */ - last_ivl = vector_length (tokens) - 1; - - for (ivl = 0; ivl < last_ivl; ivl++) - { - enum match_type match; - int ret; - - /* TODO: does this test make any sense ? */ - if ((token = vector_get_item (tokens, ivl)) == NULL) - continue ; - - /* First try completion match, return best kind of match */ - index = ivl ; - match = cmd_filter_by_completion (token, cmd_v, index) ; - - /* Eliminate all but the selected kind of match */ - ret = is_cmd_ambiguous (token, cmd_v, index, match) ; - - if (ret == 1) - { - /* ret == 1 => either token matches more than one keyword - * or token matches more than one number range - */ - vector_free (cmd_v); - *status = CMD_ERR_AMBIGUOUS; - return NULL; - } -#if 0 - /* For command completion purposes do not appear to care about - * incomplete ipv4 or ipv6 prefixes (missing '/' or digits after). - */ - else if (ret == 2) - { - vector_free (cmd_v); - *status = CMD_ERR_NO_MATCH; - return NULL; - } -#endif - } - - /* Prepare match vector. */ - matchvec = vector_init (INIT_MATCHVEC_SIZE); - - /* Now we got into completion */ - index = last_ivl ; - token = vector_get_item(tokens, last_ivl) ; /* is now the last token */ - - for (i = 0; i < vector_length (cmd_v); i++) - { - unsigned int j; - const char *string; - - if ((cmd_element = vector_get_item (cmd_v, i)) == NULL) - continue ; - - descvec = vector_get_item (cmd_element->strvec, index); - if (descvec == NULL) - continue ; - - for (j = 0; j < vector_length (descvec); j++) - { - desc = vector_get_item (descvec, j) ; - if (desc == NULL) - continue ; - - string = cmd_entry_function(token, desc->cmd) ; - if ((string != NULL) && cmd_unique_string(matchvec, string)) - cmd_add_to_strvec (matchvec, string) ; - } ; - } ; - - n = vector_length(matchvec) ; /* number of entries in the matchvec */ - - /* We don't need cmd_v any more. */ - vector_free (cmd_v); - - /* No matched command */ - if (n == 0) - { - vector_free (matchvec); - - /* In case of 'command \t' pattern. Do you need '?' command at - the end of the line. */ - if (*token == '\0') - *status = CMD_COMPLETE_ALREADY; - else - *status = CMD_ERR_NO_MATCH; - return NULL; - } - - /* Only one matched */ - if (n == 1) - { - *status = CMD_COMPLETE_FULL_MATCH; - return matchvec ; - } - - /* Check LCD of matched strings. */ - if (token != NULL) - { - unsigned lcd = cmd_lcd (matchvec) ; - - if (lcd != 0) - { - if (strlen(token) < lcd) - { - char *lcdstr; - - lcdstr = XMALLOC (MTYPE_STRVEC, lcd + 1); - memcpy (lcdstr, vector_get_item(matchvec, 0), lcd) ; - lcdstr[lcd] = '\0'; - - cmd_free_strvec(matchvec) ; /* discard the match vector */ - - matchvec = vector_init (1); - vector_push_item(matchvec, lcdstr) ; - - *status = CMD_COMPLETE_MATCH; - return matchvec ; - } - } - } - - *status = CMD_COMPLETE_LIST_MATCH; - return matchvec ; -} - -/*------------------------------------------------------------------------------ - * Can the current command be completed ? - */ -extern vector -cmd_complete_command (vector tokens, int node, int *status) -{ - vector ret; - - if ( cmd_try_do_shortcut(node, vector_get_item(tokens, 0) ) ) - { - vector shifted_tokens; - unsigned int index; - - /* We can try it on enable node, cos' the vty is authenticated */ - - shifted_tokens = vector_init (vector_count(tokens)); - /* use memcpy? */ - for (index = 1; index < vector_length (tokens); index++) - { - vector_set_index (shifted_tokens, index-1, - vector_lookup(tokens, index)) ; - } - - ret = cmd_complete_command_real (shifted_tokens, ENABLE_NODE, status); - - vector_free(shifted_tokens); - return ret; - } - - return cmd_complete_command_real (tokens, node, status); -} - -/*------------------------------------------------------------------------------ - * Return parent node - * - * All nodes > CONFIG_NODE are descended from CONFIG_NODE - */ -enum node_type -node_parent ( enum node_type node ) -{ - assert (node > CONFIG_NODE); - - switch (node) - { - case BGP_VPNV4_NODE: - case BGP_IPV4_NODE: - case BGP_IPV4M_NODE: - case BGP_IPV6_NODE: - case BGP_IPV6M_NODE: - return BGP_NODE; - - case KEYCHAIN_KEY_NODE: - return KEYCHAIN_NODE; - - default: - return CONFIG_NODE; - } ; -} ; - -/*============================================================================== - * Parsing of command lines - */ - -/*------------------------------------------------------------------------------ - * Parse a command in the given "node", if possible, ready for execution. - * - * If 'strict': use cmd_filter_by_string() - * otherwise: use cmd_filter_by_completion() - * - * If 'do': see if there is a 'do' at the front and proceed accordingly. - * - * If 'tree': move up the node tree to find command if not found in the - * current node. - */ - -static enum cmd_return_code cmd_parse_phase_one(cmd_parsed parsed) ; -static enum cmd_return_code cmd_parse_phase_two(struct cmd_parsed* parsed, - bool strict) ; - -/*------------------------------------------------------------------------------ - * Parse a command in the given "node", or (if required) any of its ancestors. - * - * Returns: CMD_SUCCESS => successfully parsed command, and the result is - * in the given parsed structure, ready for execution. - * - * NB: parsed->cnode may have changed. - * - * NB: parsed->cmd->daemon => daemon - * - * CMD_EMPTY => empty or comment line - * - * NB: parsed->cmd == NULL - * - * CMD_SUCCESS_DAEMON => parsed successfully. Something for vtysh ?? - * - * CMD_ERR_NO_MATCH ) - * CMD_ERR_AMBIGUOUS ) failed to parse - * CMD_ERR_INCOMPLETE ) - * - * NB: if has failed to parse in the current node - * and in any ancestor nodes, returns the error - * from the attempt to parse in the current node - * (parsed->cnode which is returned unchanged). - * - * NB: the command line MUST be preserved until the parsed command is no - * longer required -- no copy is made. - * - * NB: expects to have free run of everything in the vty structure (except - * the contents of the vty_io sub-structure) until the command completes. - * - * See elsewhere for description of parsed structure. - */ -extern enum cmd_return_code -cmd_parse_command(struct vty* vty, cmd_parse_type_t type) -{ - enum cmd_return_code ret ; - enum cmd_return_code first_ret ; - cmd_parsed parsed ; - cmd_token_type_t tok_total ; - bool varflag ; - unsigned int i, ivl ; - - /* Initialise the parsed structure -- assuming no 'do' */ - if (vty->parsed == NULL) - parsed = vty->parsed = cmd_parse_init_new(NULL) ; - else - { - parsed = vty->parsed ; - - parsed->cmd = NULL ; - parsed->do_shortcut = false ; - - if (parsed->pipes != cmd_pipe_none) - { - parsed->pipes = cmd_pipe_none ; - cmd_empty_parsed_tokens(parsed) ; - } ; - } ; - - /* Parse the line into tokens, set parsed->line, ->cnode & ->onode */ - tok_total = cmd_tokenise(parsed, vty->buf, vty->node) ; - - /* Stop immediately if line is empty apart from comment */ - if ((tok_total & ~cmd_tok_comment) == cmd_tok_null) - return CMD_EMPTY ; /* NB: parsed->cmd == NULL */ - - /* Level 1 parsing - * - * Strip quotes and escapes from all the tokens. - */ - if (tok_total != cmd_tok_simple) - { - ret = cmd_parse_phase_one(parsed) ; - if (ret != CMD_SUCCESS) - return ret ; - } ; - - /* If allowed to 'do', see if there. - * - * 'do' forces command to be parsed in ENABLE_NODE (if allowed) - */ - if (type & cmd_parse_do) - cmd_try_do_shortcut(parsed) ; - - /* Level 2 parsing - * Try in the current node - */ - ret = cmd_parse_phase_two(parsed, type) ; - - if (ret != CMD_SUCCESS) - { - if (((type & cmd_parse_tree) == 0) || parsed->do_shortcut) - return ret ; /* done if not allowed to walk tree - or just tried to parse a 'do' */ - - /* Try in parent node(s) */ - first_ret = ret ; - - while (ret != CMD_SUCCESS) - { - if (parsed->cnode <= CONFIG_NODE) - { - parsed->cnode = parsed->onode ; /* restore node state */ - return first_ret ; /* return original result */ - } ; - - parsed->cnode = node_parent(parsed->cnode) ; - ret = cmd_parse_phase_two(parsed, type) ; - } ; - } ; - - /* Parsed successfully -- construct the arg_vector */ - - varflag = false ; - ivl = vector_length(parsed->cmd->strvec) ; - - cmd_arg_vector_empty(parsed) ; - for (i = 0; i < ivl ; i++) - { - bool take = varflag ; - - if (!varflag) - { - vector descvec = vector_get_item (parsed->cmd->strvec, i); - - if (vector_length (descvec) == 1) - { - struct desc *desc = vector_get_item (descvec, 0); - - if (CMD_VARARG (desc->cmd)) - take = varflag = true ; - else - take = (CMD_VARIABLE (desc->cmd) || CMD_OPTION (desc->cmd)) ; - } - else - take = true ; - } - - if (take) - cmd_arg_vector_push(parsed, - cmd_token_value(cmd_token_get(&parsed->tokens, i))) ; - } ; - - /* Return appropriate form of success */ - return parsed->cmd->daemon ? CMD_SUCCESS_DAEMON - : CMD_SUCCESS ; -} ; - -/*------------------------------------------------------------------------------ - * Phase 1 of command parsing - * - * * At start of line look for: - * - * * '<' -- pipe-in command of some sort - * - * Sets the pipe type and the read_pipe_tokens -- all tokens up to - * '>', '|', '!' or '#'.. - * - * Scan for '>', '|', '!' or '#' - * - * * '>' or '|' -- pipe-out command of some sort - * - * Collect type of pipe, and then all tokens up to '!' or '#' - * - * - * - * * '!', '#', comment -- discards - * - * Returns: CMD_SUCCESS -- parsed successfully - * CMD_ERR_NO_MATCH ) - * CMD_ERR_AMBIGUOUS ) failed to parse - * CMD_ERR_INCOMPLETE ) - */ -static enum cmd_return_code -cmd_parse_phase_one(cmd_parsed parsed) -{ - - - - - - -} ; - -/*------------------------------------------------------------------------------ - * Phase 2 of command parsing - * - * Takes a parsed structure, with the: - * - * cnode -- node to parse in - * tokens -- the line broken into words - * do_shortcut -- true if first word is 'do' (to be ignored) - * - * and parses either strictly or with command completion. - * - * Returns: CMD_SUCCESS -- parsed successfully - * CMD_ERR_NO_MATCH ) - * CMD_ERR_AMBIGUOUS ) failed to parse - * CMD_ERR_INCOMPLETE ) - */ -static enum cmd_return_code -cmd_parse_phase_two(cmd_parsed parsed, cmd_parse_type_t type) -{ - unsigned int i ; - unsigned int ivl ; - unsigned index ; - vector cmd_v; - struct cmd_element *cmd_element; - struct cmd_element *matched_element; - unsigned int matched_count, incomplete_count; - enum match_type match ; - enum match_type filter_level ; - const char *command; - - /* Need number of tokens */ - ivl = cmd_token_count(&parsed->tokens) ; - - /* Make copy of command elements. */ - cmd_v = vector_copy (cmd_node_vector (cmdvec, parsed->cnode)); - - /* Look for an unambiguous result */ - filter_level = (type & cmd_parse_strict) ? exact_match : any_match ; - match = no_match ; /* in case of emptiness */ - for (index = 0 ; index < ivl; index++) - { - int ret ; - - command = cmd_token_string(cmd_token_get(&parsed->tokens, index)) ; - - match = cmd_filter(command, cmd_v, index, filter_level) ; - - if (match == vararg_match) - break; - - ret = is_cmd_ambiguous (command, cmd_v, index, match); - - if (ret != 0) - { - assert((ret == 1) || (ret == 2)) ; - vector_free (cmd_v); - return (ret == 1) ? CMD_ERR_AMBIGUOUS : CMD_ERR_NO_MATCH ; - } - } ; - - /* Check matched count. */ - matched_element = NULL; - matched_count = 0; - incomplete_count = 0; - - for (i = 0; i < vector_length(cmd_v); i++) - { - cmd_element = vector_get_item(cmd_v, i) ; - if (cmd_element == NULL) - continue ; - - if (match == vararg_match || index >= cmd_element->cmdsize) - { - matched_element = cmd_element; -#if 0 - printf ("DEBUG: %s\n", cmd_element->string); -#endif - matched_count++; - } - else - { - incomplete_count++; - } - } ; - - /* Finished with cmd_v. */ - vector_free (cmd_v); - - /* To execute command, matched_count must be 1. */ - if (matched_count != 1) - { - if (matched_count == 0) - return (incomplete_count) ? CMD_ERR_INCOMPLETE : CMD_ERR_NO_MATCH ; - else - return CMD_ERR_AMBIGUOUS ; - } ; - - /* Everything checks out... ready to execute command */ - parsed->cmd = matched_element ; - - return CMD_SUCCESS ; -} ; - -/*------------------------------------------------------------------------------ - * Dispatch a parsed command. - * - * Returns: command return code. NB: may be CMD_QUEUED (unless no_queue). - * - * NB: expects to have free run of everything in the vty structure (except - * the contents of the vty_io sub-structure) until the command completes. - */ -extern enum cmd_return_code -cmd_dispatch(struct vty* vty, bool no_queue) -{ - cmd_parsed parsed = vty->parsed ; - enum cmd_return_code ret ; - - if (parsed->cmd == NULL) - return CMD_SUCCESS ; /* NULL commands are easy */ - - vty->node = parsed->cnode ; - - if (no_queue || !vty_cli_nexus) - { - ret = cmd_dispatch_call(vty) ; - cmd_post_command(vty, ret) ; - } - else - { - /* Don't do it now, but send to bgp qpthread */ - if (parsed->cmd->attr & CMD_ATTR_CALL) - cq_enqueue(vty, vty_cli_nexus) ; - else - cq_enqueue(vty, vty_cmd_nexus) ; - - ret = CMD_QUEUED ; - } ; - - return ret ; -} ; - -/*------------------------------------------------------------------------------ - * Tidy up after executing command. - * - * This is separated out so that can be called when queued command completes. - * - * If have just processed a "do" shortcut command, and it has not set the - * vty->node to something other than ENABLE_NODE, then restore to the original - * state. - * - * Arguments: ret = CMD_XXXX -- NB: CMD_QUEUED => command revoked - */ -extern void -cmd_post_command(struct vty* vty, int ret) -{ - if (vty->parsed->do_shortcut) - { - if (vty->node == ENABLE_NODE) - vty->node = vty->parsed->onode ; - } ; -} ; - -/*------------------------------------------------------------------------------ - * Parse and execute a command. - * - * The command is given by vty->buf and vty->node. - * - * Uses vty->parsed. - * - * -- use strict/completion parsing, as required. - * - * -- parse in current node and in ancestors, as required - * - * If does not find in any ancestor, return error from current node. - * - * -- implement the "do" shortcut, as required - * - * If qpthreads_enabled, then may queue the command rather than execute it - * here. - * - * The vty->node may be changed during the execution of the command, and may - * be returned changed once the command has completed. - * - * NB: expects to have free run of everything in the vty structure (except - * the contents of the vty_io sub-structure) until the command completes. - */ -extern enum cmd_return_code -cmd_execute_command(struct vty *vty, - enum cmd_parse_type type, struct cmd_element **cmd) -{ - enum cmd_return_code ret ; - - /* Try to parse in vty->node or, if required, ancestors thereof. */ - ret = cmd_parse_command(vty, type) ; - - if (cmd != NULL) - *cmd = vty->parsed->cmd ; /* for vtysh */ - - if (ret == CMD_SUCCESS) - ret = cmd_dispatch(vty, cmd_may_queue) ; - else if (ret == CMD_EMPTY) - ret = CMD_SUCCESS ; - - return ret ; -} ; - -/*------------------------------------------------------------------------------ - * Read configuration from file. - * - * In the qpthreads world this assumes that it is running with the vty - * locked, and that all commands are to be executed directly. - * - * If the 'first_cmd' argument is not NULL it is the address of the first - * command that is expected to appear. If the first command is not this, then - * the 'first_cmd' is called with argv == NULL (and argc == 0) to signal the - * command is being invoked by default. - * - * Command processing continues while CMD_SUCCESS is returned by the command - * parser and command execution. - * - * If 'ignore_warning' is set, then any CMD_WARNING returned by command - * execution is converted to CMD_SUCCESS. Note that any CMD_WARNING returned - * by command parsing (or in execution of any default 'first_cmd'). - * - * Returns: cmd_return_code for last command - * vty->buf is last line processed - * vty->lineno is number of last line processed (1 is first) - * - * If the file is empty, will return CMD_SUCCESS. - * - * Never returns CMD_EMPTY -- that counts as CMD_SUCCESS. - * - * If - * - * If return code is not CMD_SUCCESS, the the output buffering contains the - * output from the last command attempted. - */ -extern enum cmd_return_code -config_from_file (struct vty *vty, FILE *fp, struct cmd_element* first_cmd, - qstring buf, bool ignore_warning) -{ - enum cmd_return_code ret; - - vty->buf = buf->body ; - vty->lineno = 0 ; - - ret = CMD_SUCCESS ; /* in case file is empty */ - vty_out_clear(vty) ; - - while (fgets (buf->body, buf->size, fp)) - { - ++vty->lineno ; - - /* Execute configuration command : this is strict match */ - ret = cmd_parse_command(vty, cmd_parse_strict + cmd_parse_tree) ; - - if (ret == CMD_EMPTY) - continue ; /* skip empty/comment */ - - if (ret != CMD_SUCCESS) - break ; /* stop on *any* parsing issue */ - - /* special handling before of first command */ - if (first_cmd != NULL) - { - if (first_cmd != vty->parsed->cmd) - { - ret = (*first_cmd->func)(first_cmd, vty, 0, NULL) ; - if (ret != CMD_SUCCESS) - break ; /* stop on *any* issue with "default" */ - } ; - first_cmd = NULL ; - } - - /* Standard command handling */ - ret = cmd_dispatch(vty, cmd_no_queue) ; - - if (ret != CMD_SUCCESS) - { - /* Ignore CMD_WARNING if required - * - * Ignore CMD_CLOSE at all times. - */ - if ( ((ret == CMD_WARNING) && ignore_warning) - || (ret == CMD_CLOSE) ) - ret = CMD_SUCCESS ; /* in case at EOF */ - else - break ; /* stop */ - } ; - - vty_out_clear(vty) ; - } ; - - if (ret == CMD_EMPTY) - ret = CMD_SUCCESS ; /* OK if end on empty line */ - - return ret ; -} ; - -/*============================================================================== - */ - -static cmd_return_code_t cmd_fetch_command(struct vty* vty) ; - -/*------------------------------------------------------------------------------ - * Command Loop - * - * Read and dispatch commands until can no longer do so for whatever reason: - * - * - reached end of command stream -- CMD_CLOSE - * - encounter error of some kind -- CMD_WARNING, CMD_ERROR, etc - * - waiting for input to arrive -- CMD_WAIT_INPUT - * - waiting for command to complete -- CMD_QUEUED - * - waiting for output to complete -- ?? - * - */ -extern cmd_return_code_t -cmd_command_loop(struct vty *vty, struct cmd_element* first_cmd, - qstring buf, bool ignore_warning) -{ - cmd_return_code_t ret ; - - vty->buf = buf->body ; - vty->lineno = 0 ; - - - /* - * - */ - - vty_out_clear(vty) ; - - while (1) { - - /* Fetch a command line */ - - ret = cmd_fetch_command(vty) ; - - if (ret != CMD_SUCCESS) - break ; - - ++vty->lineno ; - - /* Parse the command line we now have */ - - ret = cmd_parse_command(vty, cmd_parse_strict + cmd_parse_tree) ; - - if (ret == CMD_EMPTY) - continue ; /* skip empty/comment */ - - if (ret != CMD_SUCCESS) - break ; /* stop on *any* parsing issue */ - - /* special handling before of first command */ - if (first_cmd != NULL) - { - if (first_cmd != vty->parsed->cmd) - { - ret = (*first_cmd->func)(first_cmd, vty, 0, NULL) ; - if (ret != CMD_SUCCESS) - break ; /* stop on *any* issue with "default" */ - } ; - first_cmd = NULL ; - } ; - - /* Reflect command line if required */ - - /* Standard command handling */ - - ret = cmd_dispatch(vty, cmd_no_queue) ; - - if (ret == CMD_QUEUED) - break ; - - /* Output Handling..... */ - - - /* Return code handling.... */ - - if (ret != CMD_SUCCESS) - { - if ((ret == CMD_WARNING) && !ignore_warning) - break ; - if (ret != CMD_CLOSE) - break ; - } ; - - vty_out_clear(vty) ; - } ; - - return ret ; -} ; - - -/*------------------------------------------------------------------------------ - * Fetch the next command. - * - * - * - * - */ -static cmd_return_code_t -cmd_fetch_command(struct vty* vty) -{ - - - - -} ; - - - - - - - - - - - /*============================================================================*/ /*----------------------------------------------------------------------------*/ @@ -2364,11 +859,10 @@ DEFUN_CALL (enable, "Turn on privileged mode command\n") { /* If enable password is NULL, change to ENABLE_NODE */ - if ((host.enable == NULL && host.enable_encrypt == NULL) || - vty_shell_server(vty)) - vty_set_node(vty, ENABLE_NODE); + if ((host.enable == NULL) || (vty->type == VTY_SHELL_SERVER)) + vty->node = ENABLE_NODE ; else - vty_set_node(vty, AUTH_ENABLE_NODE); + vty->node = AUTH_ENABLE_NODE ; return CMD_SUCCESS; } @@ -2379,8 +873,8 @@ DEFUN_CALL (disable, "disable", "Turn off privileged mode command\n") { - if (vty_get_node(vty) == ENABLE_NODE) - vty_set_node(vty, VIEW_NODE); + if (vty->node == ENABLE_NODE) + vty->node = VIEW_NODE; return CMD_SUCCESS; } @@ -2390,7 +884,7 @@ DEFUN_CALL (config_exit, "exit", "Exit current mode and down to previous mode\n") { - return vty_cmd_exit(vty) ; + return cmd_exit(vty) ; } /* quit is alias of exit. */ @@ -2403,9 +897,9 @@ ALIAS_CALL (config_exit, DEFUN_CALL (config_end, config_end_cmd, "end", - "End current mode and change to enable mode.") + "End current mode and change to enable mode\n") { - return vty_cmd_end(vty) ; + return cmd_end(vty) ; } /* Show version. */ @@ -2453,8 +947,8 @@ DEFUN_CALL (config_list, "Print command list\n") { unsigned int i; - struct cmd_node *cnode = vector_get_item (cmdvec, vty_get_node(vty)); - struct cmd_element *cmd; + struct cmd_node *cnode = vector_get_item (node_vector, vty->node); + struct cmd_command *cmd; for (i = 0; i < vector_length (cnode->cmd_vector); i++) if ((cmd = vector_get_item (cnode->cmd_vector, i)) != NULL @@ -2481,7 +975,7 @@ DEFUN (config_write_file, int ret = CMD_WARNING; /* Check and see if we are operating under vtysh configuration */ - if (host.config == NULL) + if (host.config_file == NULL) { vty_out (vty, "Can't save to configuration file, using vtysh.%s", VTY_NEWLINE); @@ -2489,7 +983,7 @@ DEFUN (config_write_file, } /* Get filename. */ - config_file = host.config; + config_file = host.config_file; config_file_sav = XMALLOC (MTYPE_TMP, strlen (config_file) + strlen (CONF_BACKUP_EXT) + 1); @@ -2504,8 +998,7 @@ DEFUN (config_write_file, 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; } @@ -2517,15 +1010,18 @@ DEFUN (config_write_file, vty_time_print (vty, 1); vty_out (vty, "!\n"); - for (i = 0; i < vector_length (cmdvec); i++) - if ((node = vector_get_item (cmdvec, i)) && node->func) - { - if ((*node->func) (vty)) - vty_out (vty, "!\n"); - } + for (i = 0; i < vector_length (node_vector); i++) + { + if ((node = vector_get_item (node_vector, i)) && node->config_write) + { + if ((*node->config_write) (vty)) + vty_out (vty, "!\n"); + + vty_cmd_out_push(vty) ; /* Push stuff so far */ + } + } ; err = vty_close_config_write(vty) ; - close(fd) ; if (err != 0) { @@ -2611,31 +1107,34 @@ DEFUN (config_write_terminal, "Write to terminal\n") { unsigned int i; - struct cmd_node *node; + bool vtysh_config ; - if (vty_shell_serv(vty)) - { - for (i = 0; i < vector_length (cmdvec); i++) - if ((node = vector_get_item (cmdvec, i)) && node->func && node->vtysh) - { - if ((*node->func) (vty)) - vty_out (vty, "!%s", VTY_NEWLINE); - } - } - else + vtysh_config = (vty->type == VTY_SHELL_SERVER) ; + + if (!vtysh_config) + vty_out (vty, "\n" + "Current configuration:\n" + "!\n") ; + + for (i = 0 ; i < vector_length(node_vector) ; i++) { - vty_out (vty, "%sCurrent configuration:%s", VTY_NEWLINE, - VTY_NEWLINE); - vty_out (vty, "!%s", VTY_NEWLINE); - - for (i = 0; i < vector_length (cmdvec); i++) - if ((node = vector_get_item (cmdvec, i)) && node->func) - { - if ((*node->func) (vty)) - vty_out (vty, "!%s", VTY_NEWLINE); - } - vty_out (vty, "end%s",VTY_NEWLINE); - } + cmd_node node ; + + node = vector_get_item(node_vector, i) ; + + if (node == NULL) + continue ; + + if (vtysh_config && !node->config_to_vtysh) + continue ; + + if ((*node->config_write != NULL) && ((*node->config_write)(vty) != 0)) + vty_out (vty, "!\n") ; + } ; + + if (!vtysh_config) + vty_out (vty, "end\n") ; + return CMD_SUCCESS; } @@ -2656,11 +1155,11 @@ DEFUN (show_startup_config, char buf[BUFSIZ]; FILE *confp; - confp = fopen (host.config, "r"); + confp = fopen (host.config_file, "r"); if (confp == NULL) { vty_out (vty, "Can't open configuration file [%s]%s", - host.config, VTY_NEWLINE); + host.config_file, VTY_NEWLINE); return CMD_WARNING; } @@ -2689,21 +1188,11 @@ DEFUN_CALL (config_hostname, { if (!isalpha((int) *argv[0])) { - vty_out (vty, "Please specify string starting with alphabet%s", VTY_NEWLINE); + vty_out (vty, "Please specify string starting with alphabet\n"); return CMD_WARNING; } - VTY_LOCK() ; - - if (host.name) - XFREE (MTYPE_HOST, host.name); - - host.name = XSTRDUP (MTYPE_HOST, argv[0]); - uty_set_host_name(host.name) ; - - VTY_UNLOCK() ; - - return CMD_SUCCESS; + return cmd_set_host_name(argv[0]) ; } DEFUN_CALL (config_no_hostname, @@ -2713,26 +1202,18 @@ DEFUN_CALL (config_no_hostname, "Reset system's network name\n" "Host name of this router\n") { - VTY_LOCK() ; - - if (host.name) - XFREE (MTYPE_HOST, host.name); - host.name = NULL; - uty_set_host_name(host.name) ; - - VTY_UNLOCK() ; - - return CMD_SUCCESS; + return cmd_set_host_name(NULL) ; } -/* VTY interface password set. */ -DEFUN_CALL (config_password, password_cmd, - "password (8|) WORD", - "Assign the terminal connection password\n" - "Specifies a HIDDEN password will follow\n" - "dummy string \n" - "The HIDDEN line password string\n") +/*------------------------------------------------------------------------------ + * Password setting function -- common for password and enable password. + */ +static cmd_return_code_t +do_set_password(vty vty, int argc, argv_t argv, char** p_password, + bool* p_encrypted) { + cmd_return_code_t ret ; + /* Argument check. */ if (argc == 0) { @@ -2740,47 +1221,63 @@ DEFUN_CALL (config_password, password_cmd, return CMD_WARNING; } + VTY_LOCK() ; + ret = CMD_SUCCESS ; + if (argc == 2) { + /* Encrypted password argument */ + if (*argv[0] == '8') - { - if (host.password) - XFREE (MTYPE_HOST, host.password); - host.password = NULL; - if (host.password_encrypt) - XFREE (MTYPE_HOST, host.password_encrypt); - host.password_encrypt = XSTRDUP (MTYPE_HOST, argv[1]); - return CMD_SUCCESS; - } + { + XFREE(MTYPE_HOST, *p_password); + *p_encrypted = true ; + *p_password = XSTRDUP(MTYPE_HOST, argv[1]); + } else - { - vty_out (vty, "Unknown encryption type.%s", VTY_NEWLINE); - return CMD_WARNING; - } + { + vty_out(vty, "Unknown encryption type.\n"); + ret = CMD_WARNING; + } } - - if (!isalnum ((int) *argv[0])) + else { - vty_out (vty, - "Please specify string starting with alphanumeric%s", VTY_NEWLINE); - return CMD_WARNING; - } + /* Plaintext password argument */ + + if (!isalnum ((int) *argv[0])) + { + vty_out(vty, "Please specify string starting with alphanumeric\n"); + ret = CMD_WARNING ; + } + else + { + /* If host.encrypt, only keeps the encrypted password. */ - if (host.password) - XFREE (MTYPE_HOST, host.password); - host.password = NULL; + XFREE (MTYPE_HOST, *p_password); - if (host.encrypt) - { - if (host.password_encrypt) - XFREE (MTYPE_HOST, host.password_encrypt); - host.password_encrypt = XSTRDUP (MTYPE_HOST, zencrypt (argv[0])); - } - else - host.password = XSTRDUP (MTYPE_HOST, argv[0]); + *p_encrypted = host.encrypt ; + if (*p_encrypted) + *p_password = XSTRDUP (MTYPE_HOST, zencrypt (argv[0])); + else + *p_password = XSTRDUP (MTYPE_HOST, argv[0]); + } ; + } ; - return CMD_SUCCESS; -} + VTY_UNLOCK() ; + return ret ; +} ; + +/* VTY interface password set. */ +DEFUN_CALL (config_password, password_cmd, + "password (8) WORD", + "Assign the terminal connection password\n" + "Specifies a HIDDEN password will follow\n" + "dummy string \n" + "The HIDDEN line password string\n") +{ + return do_set_password(vty, argc, argv, &host.password, + &host.password_encrypted) ; +} ; ALIAS_CALL (config_password, password_text_cmd, "password LINE", @@ -2789,65 +1286,16 @@ ALIAS_CALL (config_password, password_text_cmd, /* VTY enable password set. */ DEFUN_CALL (config_enable_password, enable_password_cmd, - "enable password (8|) WORD", + "enable password (8) WORD", "Modify enable password parameters\n" "Assign the privileged level password\n" "Specifies a HIDDEN password will follow\n" "dummy string \n" "The HIDDEN 'enable' password string\n") { - /* Argument check. */ - if (argc == 0) - { - vty_out (vty, "Please specify password.%s", VTY_NEWLINE); - return CMD_WARNING; - } - - /* Crypt type is specified. */ - if (argc == 2) - { - if (*argv[0] == '8') - { - if (host.enable) - XFREE (MTYPE_HOST, host.enable); - host.enable = NULL; - - if (host.enable_encrypt) - XFREE (MTYPE_HOST, host.enable_encrypt); - host.enable_encrypt = XSTRDUP (MTYPE_HOST, argv[1]); - - return CMD_SUCCESS; - } - else - { - vty_out (vty, "Unknown encryption type.%s", VTY_NEWLINE); - return CMD_WARNING; - } - } - - if (!isalnum ((int) *argv[0])) - { - vty_out (vty, - "Please specify string starting with alphanumeric%s", VTY_NEWLINE); - return CMD_WARNING; - } - - if (host.enable) - XFREE (MTYPE_HOST, host.enable); - host.enable = NULL; - - /* Plain password input. */ - if (host.encrypt) - { - if (host.enable_encrypt) - XFREE (MTYPE_HOST, host.enable_encrypt); - host.enable_encrypt = XSTRDUP (MTYPE_HOST, zencrypt (argv[0])); - } - else - host.enable = XSTRDUP (MTYPE_HOST, argv[0]); - - return CMD_SUCCESS; -} + return do_set_password(vty, argc, argv, &host.enable, + &host.enable_encrypted) ; +} ; ALIAS_CALL (config_enable_password, enable_password_text_cmd, @@ -2863,14 +1311,12 @@ DEFUN_CALL (no_config_enable_password, no_enable_password_cmd, "Modify enable password parameters\n" "Assign the privileged level password\n") { - if (host.enable) - XFREE (MTYPE_HOST, host.enable); - host.enable = NULL; + VTY_LOCK() ; - if (host.enable_encrypt) - XFREE (MTYPE_HOST, host.enable_encrypt); - host.enable_encrypt = NULL; + host.enable_encrypted = false ; + XFREE (MTYPE_HOST, host.enable); + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -2880,24 +1326,30 @@ DEFUN_CALL (service_password_encrypt, "Set up miscellaneous service\n" "Enable encrypted passwords\n") { - if (host.encrypt) - return CMD_SUCCESS; + VTY_LOCK() ; - host.encrypt = 1; + host.encrypt = true ; - if (host.password) + /* If we have an unencrypted password in hand, convert that now. + * If not, retain any already encrypted password. + */ + if (!host.password_encrypted && (host.password != NULL)) { - if (host.password_encrypt) - XFREE (MTYPE_HOST, host.password_encrypt); - host.password_encrypt = XSTRDUP (MTYPE_HOST, zencrypt (host.password)); - } - if (host.enable) + char* plain = host.password ; + host.password = XSTRDUP(MTYPE_HOST, zencrypt (plain)) ; + XFREE(MTYPE_HOST, plain) ; + host.password_encrypted = true ; + } ; + + if (!host.enable_encrypted && (host.enable != NULL)) { - if (host.enable_encrypt) - XFREE (MTYPE_HOST, host.enable_encrypt); - host.enable_encrypt = XSTRDUP (MTYPE_HOST, zencrypt (host.enable)); - } + char* plain = host.enable ; + host.enable = XSTRDUP(MTYPE_HOST, zencrypt (plain)) ; + XFREE(MTYPE_HOST, plain) ; + host.enable_encrypted = true ; + } ; + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -2908,19 +1360,13 @@ DEFUN_CALL (no_service_password_encrypt, "Set up miscellaneous service\n" "Enable encrypted passwords\n") { - if (! host.encrypt) - return CMD_SUCCESS; - - host.encrypt = 0; + VTY_LOCK() ; - if (host.password_encrypt) - XFREE (MTYPE_HOST, host.password_encrypt); - host.password_encrypt = NULL; + /* Keep any existing passwords, encrypted or not. */ - if (host.enable_encrypt) - XFREE (MTYPE_HOST, host.enable_encrypt); - host.enable_encrypt = NULL; + host.encrypt = false ; + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -2954,6 +1400,15 @@ DEFUN_CALL (config_terminal_no_length, config_terminal_no_length_cmd, return CMD_SUCCESS; } +static cmd_return_code_t +set_host_lines(int lines) +{ + VTY_LOCK() ; + host.lines = lines ; + VTY_UNLOCK() ; + return CMD_SUCCESS ; +} ; + DEFUN_CALL (service_terminal_length, service_terminal_length_cmd, "service terminal-length <0-512>", "Set up miscellaneous service\n" @@ -2969,10 +1424,9 @@ DEFUN_CALL (service_terminal_length, service_terminal_length_cmd, vty_out (vty, "length is malformed%s", VTY_NEWLINE); return CMD_WARNING; } - host.lines = lines; - return CMD_SUCCESS; -} + return set_host_lines(lines) ; +} ; DEFUN_CALL (no_service_terminal_length, no_service_terminal_length_cmd, "no service terminal-length [<0-512>]", @@ -2981,8 +1435,7 @@ DEFUN_CALL (no_service_terminal_length, no_service_terminal_length_cmd, "System wide terminal length configuration\n" "Number of lines of VTY (0 means no line control)\n") { - host.lines = -1; - return CMD_SUCCESS; + return set_host_lines(-1) ; } DEFUN_HID_CALL (do_echo, @@ -2993,10 +1446,10 @@ DEFUN_HID_CALL (do_echo, { char *message; - vty_out (vty, "%s%s", ((message = argv_concat(argv, argc, 0)) ? message : ""), - VTY_NEWLINE); - if (message) - XFREE(MTYPE_TMP, message); + message = argv_concat(argv, argc, 0) ; + vty_out (vty, "%s\n", message ? message : "") ; + XFREE(MTYPE_TMP, message); + return CMD_SUCCESS; } @@ -3015,8 +1468,7 @@ DEFUN_CALL (config_logmsg, message = argv_concat(argv, argc, 1); zlog(NULL, level, "%s", (message ? message : "")); - if (message) - XFREE(MTYPE_TMP, message); + XFREE(MTYPE_TMP, message); return CMD_SUCCESS; } @@ -3436,6 +1888,7 @@ DEFUN_CALL (banner_motd_file, { if (host.motdfile) XFREE (MTYPE_HOST, host.motdfile); + host.motdfile = XSTRDUP (MTYPE_HOST, argv[0]); return CMD_SUCCESS; @@ -3448,7 +1901,7 @@ DEFUN_CALL (banner_motd_default, "Strings for motd\n" "Default string\n") { - host.motd = default_motd; + host.motd = DEFAULT_MOTD ; return CMD_SUCCESS; } @@ -3467,12 +1920,12 @@ DEFUN_CALL (no_banner_motd, } /* Set config filename. Called from vty.c */ -void -host_config_set (char *filename) +extern void +host_config_set (const char* file_name) { - if (host.config) - XFREE (MTYPE_HOST, host.config); - host.config = XSTRDUP (MTYPE_HOST, filename); + if (host.config_file) + XFREE (MTYPE_HOST, host.config_file); + host.config_file = XSTRDUP (MTYPE_HOST, file_name); } void @@ -3495,25 +1948,12 @@ install_default (enum node_type node) void cmd_init (int terminal) { - command_cr = XSTRDUP(MTYPE_STRVEC, "<cr>"); - desc_cr.cmd = command_cr; - desc_cr.str = XSTRDUP(MTYPE_STRVEC, ""); + /* Allocate initial top vector of commands. */ + node_vector = vector_init(0); - /* Allocate initial top vector of commands. */ - cmdvec = vector_init(0); + /* Default host value settings are already set, see above */ - /* Allocate vector of spare qstrings for tokens */ - cmd_spare_tokens_init() ; - - /* Default host value settings. */ - host.name = NULL; - host.password = NULL; - host.enable = NULL; - host.logfile = NULL; - host.config = NULL; - host.lines = -1; - host.motd = default_motd; - host.motdfile = NULL; + cmd_get_sys_host_name() ; /* start with system name & name_gen == 1 */ /* Install top nodes. */ install_node (&view_node, NULL); @@ -3622,85 +2062,46 @@ cmd_init (int terminal) void cmd_terminate () { - unsigned int i, j, k, l; - struct cmd_node *cmd_node; - struct cmd_element *cmd_element; - struct desc *desc; - vector cmd_node_v, cmd_element_v, desc_v; + cmd_node cmd_node; + cmd_command cmd ; - cmd_spare_tokens_free() ; - - if (cmdvec) + while ((cmd_node = vector_ream(node_vector, free_it)) != NULL) { - for (i = 0; i < vector_length (cmdvec); i++) + while ((cmd = vector_ream(cmd_node->cmd_vector, free_it)) != NULL) { - cmd_node = vector_get_item (cmdvec, i) ; - if (cmd_node == NULL) - continue ; - - cmd_node_v = cmd_node->cmd_vector; + /* Note that each cmd is a static structure, which may appear in + * more than one cmd_vector. + */ + cmd_item next_item ; - for (j = 0; j < vector_length (cmd_node_v); j++) + while ((next_item = vector_ream(cmd->items, free_it)) != NULL) { - cmd_element = vector_get_item (cmd_node_v, j) ; - if (cmd_element == NULL) - continue ; - - cmd_element_v = cmd_element->strvec ; - if (cmd_element_v == NULL) - continue ; - - for (k = 0; k < vector_length (cmd_element_v); k++) + do { - desc_v = vector_get_item (cmd_element_v, k) ; - if (desc_v == NULL) - continue ; - - for (l = 0; l < vector_length (desc_v); l++) - { - desc = vector_get_item (desc_v, l) ; - if (desc == NULL) - continue ; - - if (desc->cmd) - XFREE (MTYPE_STRVEC, desc->cmd); - if (desc->str) - XFREE (MTYPE_STRVEC, desc->str); - - XFREE (MTYPE_DESC, desc); - } ; - vector_free (desc_v); - } ; - - cmd_element->strvec = NULL; - vector_free (cmd_element_v); + cmd_item item ; + item = next_item ; + next_item = item->next ; + XFREE(MTYPE_CMD_ITEM, item); + } + while (next_item != NULL) ; } ; - vector_free (cmd_node_v); - } ; + cmd->items = NULL ; /* gone */ - vector_free (cmdvec); - cmdvec = NULL; + XFREE (MTYPE_CMD_STRING, cmd->r_string) ; /* sets NULL */ + XFREE (MTYPE_CMD_STRING, cmd->r_doc) ; + } ; } - if (command_cr) - XFREE(MTYPE_STRVEC, command_cr); - if (desc_cr.str) - XFREE(MTYPE_STRVEC, desc_cr.str); - if (host.name) - XFREE (MTYPE_HOST, host.name); - if (host.password) - XFREE (MTYPE_HOST, host.password); - if (host.password_encrypt) - XFREE (MTYPE_HOST, host.password_encrypt); - if (host.enable) - XFREE (MTYPE_HOST, host.enable); - if (host.enable_encrypt) - XFREE (MTYPE_HOST, host.enable_encrypt); - if (host.logfile) - XFREE (MTYPE_HOST, host.logfile); - if (host.motdfile) - XFREE (MTYPE_HOST, host.motdfile); - if (host.config) - XFREE (MTYPE_HOST, host.config); -} + node_vector = NULL ; + + XFREE(MTYPE_HOST, host.name); + XFREE(MTYPE_HOST, host.password); + XFREE(MTYPE_HOST, host.enable); + XFREE(MTYPE_HOST, host.logfile); + XFREE(MTYPE_HOST, host.motdfile); + XFREE(MTYPE_HOST, host.config_file); + XFREE(MTYPE_HOST, host.vty_accesslist_name); + XFREE(MTYPE_HOST, host.vty_ipv6_accesslist_name); + XFREE(MTYPE_HOST, host.vty_cwd); +} ; diff --git a/lib/command.h b/lib/command.h index fe4f0a56..89644ce7 100644 --- a/lib/command.h +++ b/lib/command.h @@ -25,168 +25,39 @@ #include "misc.h" -#include "node_type.h" +#include "command_common.h" +#include "vty.h" + #include "vector.h" #include "qstring.h" -struct vty ; /* in case command.h expanded first */ - -/* Host configuration variable */ -struct host -{ - /* Host name of this router. */ - char *name; - - /* Password for vty interface. */ - char *password; - char *password_encrypt; - - /* Enable password */ - char *enable; - char *enable_encrypt; - - /* System wide terminal lines. */ - int lines; - - /* Log filename. */ - char *logfile; - - /* config file name of this host */ - char *config; - - /* Flags for services */ - int advanced; - int encrypt; - - /* Banner configuration. */ - const char *motd; - char *motdfile; -}; - -/* Node which has some commands and prompt string and configuration - function pointer . */ -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 *); - - /* Vector of this node's command list. */ - vector cmd_vector; -}; - -enum -{ - CMD_ATTR_SIMPLE = 0x00, - /* bit significant */ - CMD_ATTR_DEPRECATED = 0x01, - CMD_ATTR_HIDDEN = 0x02, - CMD_ATTR_CALL = 0x04, -}; - -/* Return values for command handling. - * - * NB: when a command is executed it may return CMD_SUCCESS or CMD_WARNING. - * - * In both cases any output required (including any warning or error - * messages) must already have been output. - * - * All other return codes are for use within the command handler. +/*============================================================================== + * This contains everything required for command handling from outside the + * library. */ -enum cmd_return_code -{ - CMD_SUCCESS = 0, - CMD_WARNING = 1, - CMD_ERROR, - - CMD_EMPTY, - CMD_SUCCESS_DAEMON, - - CMD_WAIT_INPUT, - CMD_CLOSE, - CMD_QUEUED, - - CMD_ERR_NO_MATCH, - CMD_ERR_AMBIGUOUS, - CMD_ERR_INCOMPLETE, - - CMD_COMPLETE_FULL_MATCH, - CMD_COMPLETE_MATCH, - CMD_COMPLETE_LIST_MATCH, - CMD_COMPLETE_ALREADY -} ; - -typedef enum cmd_return_code cmd_return_code_t ; - -#define MSG_CMD_ERR_AMBIGUOUS "Ambiguous command" -#define MSG_CMD_ERR_NO_MATCH "Unrecognised command" -#define MSG_CMD_ERR_NO_MATCH_old "There is no matched command" - -/* Structure of command element. */ - -struct cmd_element ; -typedef struct cmd_element* cmd_element ; - -typedef const char* const argv_t[] ; - -#define DEFUN_CMD_ARG_UNUSED __attribute__ ((unused)) -#define DEFUN_CMD_FUNCTION(name) \ - enum cmd_return_code name (cmd_element self DEFUN_CMD_ARG_UNUSED, \ - struct vty* vty DEFUN_CMD_ARG_UNUSED, \ - int argc DEFUN_CMD_ARG_UNUSED, \ - argv_t argv DEFUN_CMD_ARG_UNUSED) - -typedef DEFUN_CMD_FUNCTION((cmd_function)) ; - -struct cmd_element -{ - const char* string ; /* Command specification by string. */ - cmd_function* func ; - const char* doc ; /* Documentation of this command. */ - int daemon ; /* Daemon to which this command belong. */ - vector strvec ; /* Vector of vectors of struct desc */ - unsigned int cmdsize ; /* Command index count. */ - char* config ; /* Configuration string */ - vector subconfig ; /* Sub configuration string */ - u_char attr ; /* Command attributes */ -}; - -/* Command description structure. */ -struct desc -{ - char* cmd; /* Command string. */ - char* str; /* Command's description. */ -}; - /*------------------------------------------------------------------------------ - * Can now include these + * Turn off these macros when using cpp with extract.pl */ - -#include "vty.h" -#include "uty.h" - -/*----------------------------------------------------------------------------*/ -/* Turn off these macros when uisng cpp with extract.pl */ #ifndef VTYSH_EXTRACT_PL -/* helper defines for end-user DEFUN* macros */ -#define DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attrs, dnum) \ - struct cmd_element cmdname = \ +/* helper defines for end-user DEFUN* macros */ +#define DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, attrs, dnum) \ + struct cmd_command cmdname = \ { \ - .string = cmdstr, \ - .func = funcname, \ - .doc = helpstr, \ - .attr = attrs, \ - .daemon = dnum, \ - }; + .string = cmdstr, \ + .func = funcname, \ + .doc = helpstr, \ + .attr = attrs, \ + .daemon = dnum, \ + \ + .items = NULL, \ + .nt_min = 0, \ + .nt = 0, \ + .nt_max = 0, \ + .vararg = NULL, \ + .r_string = NULL, \ + .r_doc = NULL, \ + } ; #define DEFUN_CMD_FUNC_DECL(funcname) \ static cmd_function funcname; @@ -194,50 +65,52 @@ struct desc #define DEFUN_CMD_FUNC_TEXT(funcname) \ static DEFUN_CMD_FUNCTION(funcname) -/* DEFUN for vty command interafce. Little bit hacky ;-). */ +/* DEFUN for vty command interface. Little bit hacky ;-). */ #define DEFUN(funcname, cmdname, cmdstr, helpstr) \ DEFUN_CMD_FUNC_DECL(funcname) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \ + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, 0, 0) \ DEFUN_CMD_FUNC_TEXT(funcname) #define DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \ DEFUN_CMD_FUNC_DECL(funcname) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0) \ + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, attr, 0) \ DEFUN_CMD_FUNC_TEXT(funcname) +#define DEFUN_CALL(funcname, cmdname, cmdstr, helpstr) \ + DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DIRECT) + #define DEFUN_HIDDEN(funcname, cmdname, cmdstr, helpstr) \ DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN) #define DEFUN_HID_CALL(funcname, cmdname, cmdstr, helpstr) \ - DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, (CMD_ATTR_CALL | CMD_ATTR_HIDDEN)) + DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, \ + (CMD_ATTR_DIRECT | CMD_ATTR_HIDDEN)) #define DEFUN_DEPRECATED(funcname, cmdname, cmdstr, helpstr) \ DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED) #define DEFUN_DEP_CALL(funcname, cmdname, cmdstr, helpstr) \ - DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, (CMD_ATTR_CALL | CMD_ATTR_DEPRECATED)) - -#define DEFUN_CALL(funcname, cmdname, cmdstr, helpstr) \ - DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_CALL) + DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, \ + (CMD_ATTR_DIRECT | CMD_ATTR_DEPRECATED)) -/* DEFUN_NOSH for commands that vtysh should ignore */ +/* DEFUN_NOSH for commands that vtysh should ignore */ #define DEFUN_NOSH(funcname, cmdname, cmdstr, helpstr) \ DEFUN(funcname, cmdname, cmdstr, helpstr) -/* DEFSH for vtysh. */ +/* DEFSH for vtysh. */ #define DEFSH(daemon, cmdname, cmdstr, helpstr) \ - DEFUN_CMD_ELEMENT(NULL, cmdname, cmdstr, helpstr, 0, daemon) + DEFUN_CMD_COMMAND(NULL, cmdname, cmdstr, helpstr, 0, daemon) -/* DEFUN + DEFSH */ +/* DEFUN + DEFSH */ #define DEFUNSH(daemon, funcname, cmdname, cmdstr, helpstr) \ DEFUN_CMD_FUNC_DECL(funcname) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, daemon) \ + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, 0, daemon) \ DEFUN_CMD_FUNC_TEXT(funcname) -/* DEFUN + DEFSH with attributes */ +/* DEFUN + DEFSH with attributes */ #define DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr, attr) \ DEFUN_CMD_FUNC_DECL(funcname) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, daemon) \ + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, attr, daemon) \ DEFUN_CMD_FUNC_TEXT(funcname) #define DEFUNSH_HIDDEN(daemon, funcname, cmdname, cmdstr, helpstr) \ @@ -246,44 +119,34 @@ struct desc #define DEFUNSH_DEPRECATED(daemon, funcname, cmdname, cmdstr, helpstr) \ DEFUNSH_ATTR (daemon, funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED) -/* ALIAS macro which define existing command's alias. */ +/* ALIAS macro which define existing command's alias. */ #define ALIAS(funcname, cmdname, cmdstr, helpstr) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, 0, 0) #define ALIAS_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0) + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, attr, 0) + +#define ALIAS_CALL(funcname, cmdname, cmdstr, helpstr) \ + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DIRECT, 0) #define ALIAS_HIDDEN(funcname, cmdname, cmdstr, helpstr) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, 0) + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, 0) #define ALIAS_DEPRECATED(funcname, cmdname, cmdstr, helpstr) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED, 0) - -#define ALIAS_CALL(funcname, cmdname, cmdstr, helpstr) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_CALL, 0) + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED, 0) #define ALIAS_SH(daemon, funcname, cmdname, cmdstr, helpstr) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, daemon) + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, 0, daemon) #define ALIAS_SH_HIDDEN(daemon, funcname, cmdname, cmdstr, helpstr) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, daemon) + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, daemon) #define ALIAS_SH_DEPRECATED(daemon, funcname, cmdname, cmdstr, helpstr) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED, daemon) + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED,\ + daemon) #endif /* VTYSH_EXTRACT_PL */ -/* Some macroes */ -#define CMD_OPTION(S) ((S[0]) == '[') -#define CMD_VARIABLE(S) (((S[0]) >= 'A' && (S[0]) <= 'Z') || ((S[0]) == '<')) -#define CMD_VARARG(S) ((S[0]) == '.') -#define CMD_RANGE(S) ((S[0] == '<')) - -#define CMD_IPV4(S) ((strcmp ((S), "A.B.C.D") == 0)) -#define CMD_IPV4_PREFIX(S) ((strcmp ((S), "A.B.C.D/M") == 0)) -#define CMD_IPV6(S) ((strcmp ((S), "X:X::X:X") == 0)) -#define CMD_IPV6_PREFIX(S) ((strcmp ((S), "X:X::X:X/M") == 0)) - /* Common descriptions. */ #define SHOW_STR "Show running system information\n" #define IP_STR "IP information\n" @@ -347,17 +210,29 @@ extern void cmd_terminate (void); extern void print_version (const char *); extern void install_node (struct cmd_node *, int (*) (struct vty *)); -extern void install_default (enum node_type); -extern void install_element (enum node_type, struct cmd_element *); +extern void install_default (node_type_t); +extern void install_element (node_type_t, struct cmd_command *); extern void sort_node (void); +extern const char* cmd_host_name(bool fresh) ; + +extern node_type_t cmd_node_parent(node_type_t node) ; +extern node_type_t cmd_node_exit_to(node_type_t node) ; +extern node_type_t cmd_node_end_to(node_type_t node) ; + /* Concatenates argv[shift] through argv[argc-1] into a single NUL-terminated string with a space between each element (allocated using XMALLOC(MTYPE_TMP)). Returns NULL if shift >= argc. */ extern char *argv_concat (const char* const* argv, int argc, int shift); -/* struct host global, ick */ -extern struct host host; +/* Export typical functions. */ +extern struct cmd_command config_end_cmd; +extern struct cmd_command config_exit_cmd; +extern struct cmd_command config_quit_cmd; +extern struct cmd_command config_help_cmd; +extern struct cmd_command config_list_cmd; +extern char *host_config_file (void); +extern void host_config_set (const char *); #ifdef QDEBUG extern const char *debug_banner ; diff --git a/lib/command_execute.h b/lib/command_execute.h index 6a7e2e4b..c1a2bdd0 100644 --- a/lib/command_execute.h +++ b/lib/command_execute.h @@ -23,59 +23,104 @@ #ifndef _ZEBRA_COMMAND_EXECUTE_H #define _ZEBRA_COMMAND_EXECUTE_H -#include "command.h" +#include "command_local.h" #include "command_parse.h" -#include "node_type.h" - -extern vector cmd_make_strvec (const char *); -extern vector cmd_add_to_strvec (vector v, const char* str) ; -extern void cmd_free_strvec (vector); -extern vector cmd_describe_command (const char* line, node_type_t node, - cmd_return_code_t* status) ; -extern vector cmd_complete_command (vector, int, int *status); -extern const char *cmd_prompt (enum node_type); -extern enum cmd_return_code -config_from_file (struct vty* vty, FILE *fp, struct cmd_element* first_cmd, - qstring buf, bool stop_on_warning) ; -extern enum node_type node_parent (enum node_type); -extern enum cmd_return_code cmd_execute_command (struct vty *vty, - enum cmd_parse_type type, struct cmd_element **cmd) ; -extern enum cmd_return_code cmd_execute_command_strict (struct vty *vty, - enum cmd_parse_type type, struct cmd_element **cmd) ; - -extern cmd_parsed cmd_parse_init_new(cmd_parsed parsed) ; -extern cmd_parsed cmd_parse_reset(cmd_parsed parsed, bool free_structure) ; -extern enum cmd_return_code cmd_parse_command(struct vty* vty, - enum cmd_parse_type type) ; -extern enum cmd_return_code cmd_dispatch(struct vty* vty, bool no_queue) ; - -Inline enum cmd_return_code -cmd_dispatch_call(struct vty* vty) +#include "vty_common.h" +#include "qstring.h" +#include "mqueue.h" +#include "qpnexus.h" +#include "thread.h" + +/*============================================================================== + * This is stuff which is used to parse and then execute commands. + */ + +/* State of the execution loop + */ +enum cmd_exec_state { - cmd_parsed parsed = vty->parsed ; - return (*(parsed->cmd->func))(parsed->cmd, vty, cmd_arg_vector_argc(parsed), - cmd_arg_vector_argv(parsed)) ; + exec_null = 0, + + exec_special, /* not a simple command */ + + exec_fetch, + exec_parse, + exec_open_pipes, + exec_execute, + exec_success, + exec_complete, } ; +typedef enum cmd_exec_state cmd_exec_state_t ; + +typedef struct cmd_exec* cmd_exec ; + +struct cmd_exec +{ + vty vty ; /* parent */ -#define cmd_parse_reset_keep(parsed) cmd_parse_reset(parsed, 0) -#define cmd_parse_reset_free(parsed) cmd_parse_reset(parsed, 1) + qstring line ; /* pointer to qstring in vf */ + cmd_do_t to_do ; /* for cli driven stuff */ -extern void config_replace_string (struct cmd_element *, char *, ...); + cmd_parse_type_t parse_type ; /* how should parse */ + bool out_enabled ; /* as required */ + bool reflect_enabled ; /* as required */ + + cmd_parsed_t parsed ; /* embedded */ + + cmd_exec_state_t state ; /* for cq_process */ + qpn_nexus locus ; /* for cq_process */ + + cmd_return_code_t ret ; /* for cq_process */ + + union + { + mqueue_block mqb ; /* for cq_process */ + struct thread* thread ; + } cq ; +} ; + +/*============================================================================== + * Functions + * + */ -/* Export typical functions. */ -extern struct cmd_element config_end_cmd; -extern struct cmd_element config_exit_cmd; -extern struct cmd_element config_quit_cmd; -extern struct cmd_element config_help_cmd; -extern struct cmd_element config_list_cmd; -extern char *host_config_file (void); -extern void host_config_set (char *); +extern cmd_exec cmd_exec_new(vty vty) ; +extern cmd_exec cmd_exec_free(cmd_exec exec) ; -/* "<cr>" global */ -extern char *command_cr; +extern cmd_return_code_t cmd_read_config(vty vty, cmd_command first_cmd, + bool ignore_warning) ; +extern cmd_return_code_t cmd_end(vty vty) ; +extern cmd_return_code_t cmd_exit(vty vty) ; + +extern cmd_return_code_t cmd_open_pipes(vty vty) ; + +extern cmd_return_code_t cmd_execute(vty vty) ; + + + + + + +#if 0 + +extern enum cmd_return_code cmd_execute_command (vty vty, + cmd_parse_type_t type, struct cmd_command **cmd) ; +extern enum cmd_return_code cmd_execute_command_strict (vty vty, + enum cmd_parse_type type, struct cmd_command **cmd) ; + + +extern void config_replace_string (cmd_command, char *, ...); -#ifdef QDEBUG -extern const char *debug_banner; #endif +/*============================================================================== + * Inlines + */ + +Inline bool +cmd_is_direct(cmd_parsed parsed) +{ + return ((parsed->cmd->attr & CMD_ATTR_DIRECT) != 0) ; +} ; + #endif /* _ZEBRA_COMMAND_EXECUTE_H */ diff --git a/lib/command_parse.c b/lib/command_parse.c index d8960b8d..c64ee3ba 100644 --- a/lib/command_parse.c +++ b/lib/command_parse.c @@ -1,4 +1,4 @@ -/* Quagga command line parsing -- header +/* Quagga command line parsing * Copyright (C) 1997, 98 Kunihiro Ishiguro * * Recast and extended: Copyright (C) 2010 Chris Hall (GMCH), Highwayman @@ -21,58 +21,1155 @@ * Boston, MA 02111-1307, USA. */ -#include <zebra.h> +#include "misc.h" +#include <ctype.h> +#include <stdio.h> +#include <arpa/inet.h> +#include "command_local.h" #include "command_parse.h" #include "memory.h" /*============================================================================== - * Token handling + * Command Description objects. + * + */ +static void cmd_fail_item(cmd_command cmd, const char* msg) ; +static char* cmd_item_brackets(cmd_command cmd, char* cp) ; +static cmd_item cmd_make_item(cmd_command cmd, char* cp, char* dp) ; +static void cmd_make_item_inner(cmd_command cmd, cmd_item n, char* cp) ; +static char* cmd_make_item_numeric(cmd_command cmd, cmd_item n, char* cp) ; +static long cmd_make_item_number(cmd_command cmd, cmd_item n, char** p_cp) ; +static int cmd_cmp_item(const cmd_item* a, const cmd_item* b) ; +static int cmd_cmp_range_items(const cmd_item a, const cmd_item b) ; +static bool cmd_item_is_option(cmd_item_type_t it) ; +static bool cmd_item_is_vararg(cmd_item_type_t it) ; + +/*------------------------------------------------------------------------------ + * Dummy eol_item */ +static struct cmd_item eol_item = +{ + .str = "<cr>", + .doc = "", + + .next = NULL, -/* Store of qstrings, used for parsing. */ -token_vector_t spare_tokens ; + .type = item_eol, + .arg = false, -static char cmd_token_escape(char e) ; + .range_sign_allowed = false, + .range_sign_required = false, + .range_min = 0, + .range_max = 0 +} ; /*------------------------------------------------------------------------------ - * Initialise a brand new token vector -- empty. + * Parse cmd_command string and doc to create the items for the cmd_command, + * and fill in: + * + * cmd->items -- vector of cmd_item(s). + * + * Where a given item may have more than one possible + * value, thet are arranged as a list. + * + * cmd->nt_min -- count of items up to first [option] + * where (...) counts as 1 + * and .vararg counts as 1 + * + * Is the minimum number of tokens required to match to + * this command. + * + * cmd->nt -- count of all items + * where (...) counts as 1 + * and [option] counts as 1 + * and .vararg counts as 1 + * + * cmd->nt_max -- count of all items as nt_var, + * except .vararg forces to UINT_MAX + * + * Is the maximum number of tokens which can be matched + * to this command. + * + * cmd->r_string -- copy of cmd->string, chopped up and referred to by + * the cmd_items. + * + * cmd->d_string -- copy of the cmd->doc, chopped up and referred to by + * the cmd_items. + * + * Note that the cmd_items point into the r_string and the d_string, and + * do not have further copies of their fragments of the original. + * + * Note that the t_string and d_string have all extraneous spaces, tabs and + * control characters removed. + * + * Stops dead if not valid ! + * + * Accepts: items separated by one or more spaces. Early on in the process, + * will discard spaces within a bracketed item (and checks for balanced + * brackets). Rejects any control character other than '\t', which is + * converted to ' '. Multiple spaces are reduced to single spaces. + * + * - single item is one of: + * + * - keyword -- anything starting a-z or 0-9, followed by any + * alphanumeric, '-', '_' or ':'. + * or * + * - <0-9> -- decimal range + * - WORD -- anything at least starting A-Z, followed by any + * alphanumeric, '-', '_' or ':'. + * - A.B.C.D -- ipv4 address + * - A.B.C.D/M -- ipv4 prefix + * - X:X::X:X -- ipv6 address + * - X:X::X:X/M -- ipv6 prefix + * - .vararg -- anything starting '.', followed by any alphanumeric, + * '-', '_' or ':'. + * - [item] -- optional item any of the above TODO + * + * - multiple item is: '(' item '|' item .... ')' + * + * where spaces around items are discarded. The items may be any of the + * above, except: + * + * - must all be different types of item, or for keywords and ranges, + * different values. + * + * - cannot have [item] + * + * - cannot have .var + * + * may have a single item -- whose value is sent out as an argument. + * + * An [item] may only be followed by other [item](s). An [item] matches a + * token or end of line. + * + * A .vararg must be the last item. A .vararg matches one or more tokens. + * + * + * */ -static inline void -cmd_token_vector_init(token_vector tokens) +extern void +cmd_compile(cmd_command cmd) { - vector_init_new(tokens->body, 0) ; + vector multvec ; + char* cp ; + char* qp ; + char* dp ; + bool opt ; + bool vararg ; + + /* Initialise the compiled version of the command */ + + assert((cmd->r_doc == NULL) && (cmd->r_string == NULL)) ; + + cmd->items = vector_init_new(NULL, 10) ; /* plenty ! */ + cmd->nt_min = 0 ; + cmd->nt = 0 ; + cmd->nt_max = 0 ; + cmd->vararg = NULL ; + cmd->r_doc = XSTRDUP(MTYPE_CMD_STRING, cmd->doc) ; /* NULL => "" */ + cmd->r_string = XSTRDUP(MTYPE_CMD_STRING, cmd->string) ; + + /* Simplify the command line string by replacing TABs by spaces, and barfing + * on control characters. Strip leading and trailing spaces and any spaces + * between brackets... checking for matching brackets. + */ + cp = cmd->r_string ; + while (*cp != '\0') + { + if (!iscntrl(*cp)) + ++cp ; + else if (*cp == '\t') + *cp++ = ' ' ; + else + cmd_fail_item(cmd, "improper control character in string") ; + } ; + + cp = cmd->r_string ; + while (*cp == ' ') + ++cp ; + + qp = cmd->r_string ; + while (*cp != '\0') + { + if (*cp != ' ') + { + if ((*cp == '(') || (*cp == '[') || (*cp == '<') || (*cp == '{')) + { + /* Check for balanced brackets and remove any spaces between. + * + * Checks for enclosed brackets being balanced as well. + * + * Leaves cp pointing at the trailing bracket. + */ + char* sp = cp ; + cp = cmd_item_brackets(cmd, cp) ; + while (sp < cp) + { + if (*sp != ' ') + *qp++ = *sp++ ; + else + ++sp ; + } ; + } ; + } + else + { + while (*(cp + 1) == ' ') + ++cp ; + if (*(cp + 1) == '\0') + break ; + } ; + + *qp++ = *cp++ ; + } ; + + *qp++ = '\0' ; /* terminate reduced string */ + + /* Simplify the documentation string by replacing TABs by spaces, and barfing + * on control characters other than '\n'. + * + * Strips leading spaces and any spaces before or after '\n'. + */ + + qp = dp = cmd->r_doc ; + while (*dp != '\0') + { + /* Strip leading */ + while (*dp == ' ') + ++dp ; + + /* Eat documentation section. */ + while ((*dp != '\n') && (*dp != '\0')) + { + if (!iscntrl(*dp)) + *qp++ = *dp++ ; + else if (*dp == '\t') + { + *qp++ = ' ' ; + ++dp ; + } + else + cmd_fail_item(cmd, "improper control character in documentation") ; + } ; + + /* Get here with *dp == '\n' or '\0' + * + * Strip trailing spaces (any before '\n' or '\0' + */ + while ((qp != cmd->r_doc) && (*(qp - 1) == ' ')) + --qp ; + + /* copy '\n', if required. */ + if (*dp == '\n') + *qp++ = *dp++ ; + } ; + + *qp++ = '\0' ; /* terminate reduced string */ + + /* Processing loop */ + + cp = cmd->r_string ; + dp = cmd->r_doc ; + + opt = false ; + vararg = false ; + + multvec = NULL ; + + while (*cp != '\0') + { + uint multiple ; + + /* Deal with single or multiple item. */ + multiple = 0 ; + do + { + cmd_item n ; + char* c_sp ; + char* d_sp ; + + /* step to the next documentation section */ + + d_sp = dp ; /* start of documentation */ + + while (*dp != '\0') + { + if (*dp == '\n') + { + *dp++ = '\0' ; + break ; + } ; + ++dp ; + } ; + + /* Deal with '(' if we have one. */ + + if (*cp == '(') /* change up to multiple */ + { + if (multiple != 0) + cmd_fail_item(cmd, "unexpected '('") ; + + multiple = 1 ; /* seen '(' */ + ++cp ; /* step past it */ + + multvec = vector_re_init(multvec, 10) ; /* plenty ! */ + } ; + + /* Find end of current item & '\0' terminate it. */ + c_sp = cp ; + while (1) + { + if (*cp == '|') /* eat '|' */ + { + if ((c_sp == cp) || (multiple < 1)) + cmd_fail_item(cmd, "unexpected '|'") ; + *cp++ = '\0' ; + break ; + } ; + + if (*cp == ')') /* eat ')' */ + { + if ((c_sp == cp) || (multiple < 1)) + cmd_fail_item(cmd, "unexpected ')'") ; + *cp++ = '\0' ; + multiple = 2 ; + + if ((*cp != ' ') && (*cp != '\0')) + cmd_fail_item(cmd, "expect ' ' or nothing after ')'") ; + } ; + + if (*cp == ' ') + { + *cp++ = '\0' ; + break ; + } ; + + if (*cp == '\0') + break ; + + ++cp ; + } ; + + /* Create the next item and push */ + + n = cmd_make_item(cmd, c_sp, d_sp) ; + + if (multiple == 0) + vector_push_item(cmd->items, n) ; + else + vector_push_item(multvec, n) ; + + /* Extra checks for multiple item. */ + if (multiple > 0) + { + n->arg = true ; /* always */ + + if (cmd_item_is_option(n->type)) + cmd_fail_item(cmd, "cannot have [option] inside (..)") ; + + /* could lift this restriction, but need to check that + * do not have a WORD|.VAR together, because that is tautologous. + */ + + if (cmd_item_is_vararg(n->type)) + cmd_fail_item(cmd, "cannot have .vararg inside (..)") ; + } ; + + /* Check optional item state -- can only be trailing */ + if (cmd_item_is_option(n->type)) + opt = true ; + else if (opt) + cmd_fail_item(cmd, "can only have [option] after [option]") ; + + /* Check vararg item state -- can only be trailing */ + if (vararg) + cmd_fail_item(cmd, "cannot have anything after .vararg") ; + else if (cmd_item_is_vararg(n->type)) + { + vararg = true ; + cmd->vararg = n ; /* remember for parsing */ + } ; + + } while (multiple == 1) ; + + /* count the item */ + if (!opt) + ++cmd->nt_min ; + ++cmd->nt ; + if (!vararg) + ++cmd->nt_max ; + else + cmd->nt_max = UINT_MAX ; + + /* Complete the multiple item. + * + * Sort the items so that are always used and presented in the same + * order. Check that the items are unique. + * + * We must have at least one item. + */ + if (multiple == 2) + { + cmd_item n, p ; + uint i ; + + assert(vector_length(multvec) >= 1) ; + + vector_sort(multvec, (vector_sort_cmp*)cmd_cmp_item) ; + + n = vector_get_item(multvec, 0) ; + vector_push_item(cmd->items, n) ; + + for (i = 1 ; i < vector_length(multvec) ; ++i) + { + p = n ; + n = vector_get_item(multvec, i) ; + + p->next = n ; + n->next = NULL ; + + if (p->type == n->type) + { + bool repeat ; + + if (n->type == item_keyword) + repeat = strcmp(n->str, p->str) == 0 ; + else if (n->type == item_range) + repeat = cmd_cmp_range_items(n, p) == 0 ; + else + repeat = true ; + + if (repeat) + cmd_fail_item(cmd, "repeated items in (...)") ; + } ; + } ; + } + else + assert(multiple == 0) ; + } ; + + vector_reset(multvec, free_it) ; + + /* Reduce the vector to the minimum size required */ + vector_decant(cmd->items) ; } ; /*------------------------------------------------------------------------------ - * Initialise empty spare tokens vector + * Validate a compiled item + * + * Checks that the contents of the cmd_command are consistent with the + * contents of the srcvec. + * */ extern void -cmd_spare_tokens_init(void) +cmd_compile_check(cmd_command cmd) { - cmd_token_vector_init(spare_tokens) ; + bool ok ; + + uint nt_min = 0 ; + uint nt = 0 ; + uint nt_max = 0 ; + cmd_item vararg = NULL ; + + ok = true ; + + /* Require the following to be set to something */ + if ( (cmd->string == NULL) + || (cmd->func == NULL) + || (cmd->items == NULL) + || (cmd->r_string == NULL) + || (cmd->r_doc == NULL) ) + ok = false ; + + /* cmd->nt must match the vector_length and be non-zero. */ + ok = ok && ((nt = vector_length(cmd->items)) == cmd->nt) ; + ok = ok && (nt != 0) ; + + /* Walk the vector of items, and check that those are OK. */ + if (ok) + { + uint ii = 0 ; + bool opt = false ; + + for (ii = 0 ; ok && (ii < nt) ; ++ii) + { + cmd_item item ; + cmd_item first_item ; + + item = vector_get_item(cmd->items, ii) ; + if (item == NULL) + { + ok = false ; + break ; + } ; + + if (vararg != NULL) /* nothing after vararg */ + { + ok = false ; + break ; + } ; + + first_item = item ; + while (ok && (item != NULL)) + { + /* If this is an option, may only be a single item + * + * Otherwise, after an option must all be options. + */ + if (cmd_item_is_option(item->type)) + { + /* option must be a single item. */ + opt = true ; + if ((item != first_item) || (item->next != NULL)) + { + ok = false ; + break ; + } ; + } + else if (opt) + { + /* once we have an option, must all be options */ + ok = false ; + break ; + } ; + + /* If this is a vararg, must be the last of this item. + * + * Note that allow for [.varg] and (...., .varg) -- the second + * should be sorted to the back ! + */ + if (cmd_item_is_vararg(item->type)) + { + /* vararg must be last item & only vararg */ + if ((item->next != NULL) || (vararg != NULL)) + { + ok = false ; + break ; + } ; + + vararg = item ; + } ; + + + /* If there is a next, this and the next MUST be arg */ + if (item->next != NULL) + { + if (!((item->arg) && (item->next->arg))) + { + ok = false ; + break ; + } ; + } ; + + item = item->next ; + } ; + + /* Advance the nt_min and nt_max as required. */ + if (!opt) + ++nt_min ; + + if (vararg == NULL) + ++nt_max ; + else + nt_max = UINT_MAX ; + } ; + } ; + + /* Final checks */ + + ok = ok && (cmd->nt_min == nt_min) + && (cmd->nt == nt) + && (cmd->nt_max == nt_max) + && (cmd->vararg == vararg) ; + + if (!ok) + cmd_fail_item(cmd, "some compile error") ; } ; /*------------------------------------------------------------------------------ - * Empty out the spare_tokens vector and release all memory + * Reject the cmd_item string or doc. */ -extern void -cmd_spare_tokens_free(void) +static void +cmd_fail_item(cmd_command cmd, const char* msg) +{ + fprintf (stderr, "Command parse error!: %s\n", msg) ; + fprintf (stderr, " in command: '%s'\n", cmd->string) ; + exit(2) ; +} + +/*------------------------------------------------------------------------------ + * Advance to matching bracket -- fail if not found. Recurse as required. + * + * Returns: address of matching bracket. + */ +static char* +cmd_item_brackets(cmd_command cmd, char* cp) +{ + char seek ; + + switch (*cp) + { + case '(': + seek = ')' ; + break ; + + case '[': + seek = ']' ; + break ; + + case '<': + seek = '>' ; + break ; + + case '{': + seek = '}' ; + break ; + + default: + return cp ; + } ; + + do + { + ++cp ; + + if (*cp == seek) + return cp ; + else if ((*cp == '(') || (*cp == '[') || (*cp == '<') || (*cp == '{')) + cp = cmd_item_brackets(cmd, cp) ; + } + while (*cp != '\0') ; + + cmd_fail_item(cmd, "unbalanced brackets of some sort") ; + + return cp ; +} ; + +/*------------------------------------------------------------------------------ + * Make descriptor for current item. + * + * cp points at start of '\0' terminated item, which has no spaces and no + * control characters in or around it. Also, if there are brackets, they are + * balanced. + * + * Returns: new descriptor, filled in as required. + */ + +static cmd_item +cmd_make_item(cmd_command cmd, char* cp, char* dp) +{ + char* inner ; + cmd_item n ; + + n = XCALLOC(MTYPE_CMD_ITEM, sizeof(struct cmd_item)) ; + + /* Zeroising has set: + * + * * cmd = NULL -- set below + * * doc = NULL -- set below + * + * * next = NULL -- set elsewhere if multiple. + * + * * type = item_null + * * arg = false -- set if required, below + * + * * range_sign_allowed ) + * * range_sign_required ) -- set elsewhere if required + * * range_min ) + * * range_max ) + */ + confirm(item_null == 0) ; + + n->str = cp ; + n->doc = dp ; + + /* Worry about option state */ + inner = NULL ; + if (*cp == '[') + { + n->arg = true ; /* always true for option */ + + inner = XSTRDUP(MTYPE_TMP, cp) ; + cp = inner + 1 ; /* strip leading '[' */ + *(cp + strlen(cp) - 1) = '\0' ; /* strip trailing ']' */ + + if (*cp == '\0') + cmd_fail_item(cmd, "empty [option]") ; + } ; + + /* Deal with the inner item */ + cmd_make_item_inner(cmd, n, cp) ; + + /* Worry about the option state, again. */ + if (inner != NULL) + { + XFREE(MTYPE_TMP, inner) ; + + if (n->type == item_vararg) + cmd_fail_item(cmd, "cannot have [.vararg]") ; + + n->type = item_option_word ; /* TODO other option types ? */ + } ; + + /* return newly minted cmd_item item */ + assert(n->type != item_null) ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Make inner part of cmd_item -- so can have [...] anything (in principle). + * + * Require '\0' terminated inner part. + */ +static void +cmd_make_item_inner(cmd_command cmd, cmd_item n, char* cp) +{ + bool eat_name_chars ; /* alphanumeric + '-', '_', '.' and ':' */ + + eat_name_chars = false ; + + if (islower(*cp) /* 'a'..'z' */ + || isdigit(*cp)) /* '0'..'9' */ + { + /* item_keyword -- lowercase alpha numeric + '_' and '-' */ + n->type = item_keyword ; + eat_name_chars = true ; + } + else if (*cp == '*') /* '*' */ + { + /* special item_keyword '*' */ + n->type = item_keyword ; + ++cp ; + } + else if (isupper(*cp)) /* 'A'..'Z' */ + { + n->arg = true ; + + /* WORD or other variable */ + if (strcmp(cp, "A.B.C.D") == 0) + n->type = item_ipv4_address ; + else if (strcmp(cp, "A.B.C.D/M") == 0) + n->type = item_ipv4_prefix ; + else if (strcmp(cp, "X:X::X:X") == 0) + n->type = item_ipv6_address ; + else if (strcmp(cp, "X:X::X:X/M") == 0) + n->type = item_ipv6_prefix ; + else + { + n->type = item_word ; + eat_name_chars = true ; + } ; + + if (n->type != item_word) + cp += strlen(cp) ; /* step past "A.B.C.D" et al */ + } + else if (*cp == '.') /* '.' */ + { + n->arg = true ; + n->type = item_vararg ; + eat_name_chars = true ; + } + else if (*cp == '<') /* '<' */ + { + n->arg = true ; + + cp = cmd_make_item_numeric(cmd, n, ++cp) ; + + if (*cp != '>') + cmd_fail_item(cmd, "badly formed <...>") ; + else + ++cp ; + } + else if (*cp == '\0') + cmd_fail_item(cmd, "cannot have an empty item") ; + + if (eat_name_chars) + { + do ++cp ; while ( isalnum(*cp) + || (*cp == '-') + || (*cp == '_') + || (*cp == ':') + || (*cp == '.') ) ; + + } ; + + if (*cp != '\0') + cmd_fail_item(cmd, "invalid item") ; +} ; + +/*------------------------------------------------------------------------------ + * Make <...> types + * + * Require '\0' terminated <...>, pointing after the '<'. + * + * Assumes the '>' is present. + * + * Returns: where processed up to -- pointing at '>' iff OK. + * + * Supports ranges: + * + * 9-10 => unsigned values in 32b range -- NO sign + * + * +9-10 => unsigned value, where '+' is optional + * 9-+10 => unsigned value, where '+' is *required* + * +9-+10 => same as above + * + * -9-10 => signed value, where '+' is optional + * -9-+10 => signed value, where '+' is *required* + * + * -9--8 => signed value, where '-' is required (!) + * + * + * +/-9 => -9-+9 -- sign is required. + * + * In place of decimal number can use 9b -- giving 2^9 - 1. + */ +static char* +cmd_make_item_numeric(cmd_command cmd, cmd_item n, char* cp) +{ + if (isdigit(*cp) || (*cp == '+') || (*cp == '-')) + { + long m ; + bool pm ; + + confirm((LONG_MAX > item_max_number) && (LONG_MIN < -item_max_number)) ; + + n->type = item_range ; + n->range_sign_allowed = false ; + n->range_sign_required = false ; + + if (strncmp(cp, "+/-", 3) == 0) + { + pm = true ; + n->range_sign_required = true ; + cp += 2 ; /* step to '-' to get -ve range_min. */ + } + else + { + pm = false ; + n->range_sign_allowed = (*cp == '+') ; + } ; + + m = cmd_make_item_number(cmd, n, &cp) ; + n->range_min = m ; + + if (pm) + m = -m ; /* for range_max */ + + else if (*cp == '-') + { + ++cp ; /* past the '-' */ + + n->range_sign_required = (*cp == '+') ; + + m = cmd_make_item_number(cmd, n, &cp) ; + } + + else + cmd_fail_item(cmd, "badly formed <0-1>") ; + + n->range_max = m ; + + if (n->range_min > n->range_max) + cmd_fail_item(cmd, "badly formed <0-1> min > max !") ; + + if ((n->range_sign_required) || (n->range_min < 0)) + n->range_sign_allowed = true ; /* allowed if required ! */ + } ; + + return cp ; +} ; + +/*------------------------------------------------------------------------------ + * Get signed or unsigned value -- process the '9b' form. + * + */ +static long +cmd_make_item_number(cmd_command cmd, cmd_item n, char** p_cp) +{ + long m ; + char* cp ; + + cp = *p_cp ; + m = strtol(cp, p_cp, 10) ; + + if ((*p_cp == cp) || (m > item_max_number)) + cmd_fail_item(cmd, "badly formed or out of range number in <...>") ; + + if (**p_cp == 'b') + { + long s ; + + ++(*p_cp) ; /* step past 'b' */ + s = m ; + m = labs(m) ; + if ((m == 0) || (m > 32)) + cmd_fail_item(cmd, "out of range number in 9b form in <...>") ; + + m = ((long)1 << m) - 1 ; + if (s < 0) + m = -m ; + } ; + + return m ; +} ; + +/*------------------------------------------------------------------------------ + * Compare cmd_item items + * + * Note that command types sort with the larger type value before the smaller. + */ +static int +cmd_cmp_item(const cmd_item* a, const cmd_item* b) +{ + if ((*a)->type != (*b)->type) + return ((*a)->type > (*b)->type) ? -1 : +1 ; /* descending order */ + else + { + if ((*a)->type == item_range) + return cmd_cmp_range_items(*a, *b) ; + else + return strcmp ((*a)->str, (*b)->str); + } ; +} ; + +/*------------------------------------------------------------------------------ + * Compare cmd_item item_range items + */ +static int +cmd_cmp_range_items(const cmd_item a, const cmd_item b) +{ + int as, bs ; + + if (a->range_min != b->range_min) + return (a->range_min < b->range_min) ? -1 : +1 ; + + if (a->range_max != b->range_max) + return (a->range_max < b->range_max) ? -1 : +1 ; + + as = a->range_sign_required ? 2 : (a->range_sign_allowed ? 1 : 0) ; + bs = b->range_sign_required ? 2 : (b->range_sign_allowed ? 1 : 0) ; + + if (as != bs) + return (as < bs) ? -1 : +1 ; + + return 0 ; +} ; + +/*============================================================================== + * Token objects + */ + +/*------------------------------------------------------------------------------ + * Make a brand new token object + */ +Private cmd_token +cmd_token_new(void) +{ + return XCALLOC(MTYPE_TOKEN, sizeof(struct cmd_token)) ; + + /* Zeroising the new structure sets: + * + * type = 0 -- cmd_tok_eol + * qs = zeroised qstring -- empty string + * complete = 0 -- false + * + * tp = 0 + * lp = zeroised elstring -- empty string + */ + confirm(cmd_tok_eol == 0) ; + confirm(QSTRING_INIT_ALL_ZEROS) ; + confirm(ELSTRING_INIT_ALL_ZEROS) ; +} ; + +/*------------------------------------------------------------------------------ + * Empty token object and free it. + */ +static void +cmd_token_free(cmd_token t) +{ + qs_reset(t->qs, keep_it) ; /* discard body of qstring */ + XFREE(MTYPE_TOKEN, t) ; +} ; + +/*============================================================================== + * Parser object + */ + +/*------------------------------------------------------------------------------ + * Initialise a new cmd_parsed object, allocating if required + */ +extern cmd_parsed +cmd_parsed_init_new(cmd_parsed parsed) { - token tok ; + if (parsed == NULL) + parsed = XCALLOC(MTYPE_CMD_PARSED, sizeof(*parsed)) ; + else + memset(parsed, 0, sizeof(*parsed)) ; + + /* Zeroising the structure has set: + * + * parts = 0 -- cleared by cmd_tokenise() + * tok_total = 0 -- set by cmd_tokenise() + * + * elen = 0 -- set by cmd_tokenise() + * tsp = 0 -- set by cmd_tokenise() + * + * cmd = NULL -- no command yet + * cnode = 0 -- not set + * + * num_tokens = 0 -- set by cmd_tokenise() + * tokens = all zeros -- empty token vector + * + * args = all zeros -- empty vector of arguments + * + * emess = NULL -- no error yet + * eloc = 0 -- no error location + * + * in_pipe = cmd_pipe_none + * out_pipe = cmd_pipe_none + * + * first_in_pipe ) + * num_in_pipe ) + * first_do ) + * num_do ) + * first_command ) none + * num_command ) + * first_out_pipe ) + * num_out_pipe ) + * first_comment ) + * num_comment ) + * + * cti ) set by cmd_token_position() + * rp ) + * + * cmd_v = all zeros -- empty vector of filtered commands + * item_v = all zeros -- empty vector of filtered items + * + * strongest ) + * best_complete ) set by cmd_filter_prepare() + * min_strength ) + * strict ) + */ + confirm(cmd_pipe_none == 0) ; + confirm(TOKEN_VECTOR_INIT_ALL_ZEROS) ; + confirm(ARG_VECTOR_INIT_ALL_ZEROS) ; - while ((tok = vector_ream(spare_tokens->body, keep_it)) != NULL) - qs_reset(tok->qs, keep_it) ; + return parsed ; } ; /*------------------------------------------------------------------------------ - * Take string and break it into tokens. + * Empty out and (if required) free a cmd_parsed object + */ +extern cmd_parsed +cmd_parsed_reset(cmd_parsed parsed, free_keep_b free_structure) +{ + if (parsed != NULL) + { + cmd_token t ; + + /* Give back all the token objects and release vector body */ + while ((t = vector_ream(parsed->tokens->body, keep_it)) != NULL) + cmd_token_free(t) ; + + vector_reset(parsed->args->body, keep_it) ; /* embedded */ + vector_reset(parsed->cmd_v, keep_it) ; /* embedded */ + vector_reset(parsed->item_v, keep_it) ; /* embedded */ + + if (free_structure) + XFREE(MTYPE_CMD_PARSED, parsed) ; /* sets parsed = NULL */ + else + cmd_parsed_init_new(parsed) ; + } ; + + return parsed ; +} ; + +/*============================================================================== + * Parsing error handling. + * + * + */ + +/*------------------------------------------------------------------------------ + * Register a parsing error. + * + * Takes token in which parsing error was detected, and an offset from the + * start of that, for the location of the error. If the offset is not zero, + * it must be an offset in the original token (!). + * + * The message is a simple constant string (!). + * + * The message will be output as: ..........^ pointing to the location + * followed by: % <mess>\n + * + * (The mess does not need to include the '%' or the '\n'.) + * + * Sets: parsed->emess + * parsed->eloc + * + * Returns: CMD_ERR_PARSING -- which MUST only be returned if p + */ +static cmd_return_code_t +cmd_parse_error(cmd_parsed parsed, cmd_token t, usize off, const char* mess) +{ + parsed->emess = mess ; + parsed->eloc = t->tp + off ; + + return CMD_ERR_PARSING ; +} ; + +/*============================================================================== + * Lexical level stuff + */ + +/*------------------------------------------------------------------------------ + * Take elstring and see if it is empty -- only whitespace and/or comment + */ +extern bool +cmd_is_empty(elstring line) +{ + cpp_t lp ; + + els_cpp(lp, line) ; /* NULL -> NULL */ + + while (lp->p < lp->e) + { + if ((*lp->p == ' ') || (*lp->p == '\t')) + ++lp->p ; + else + return ((*lp->p == '!') || (*lp->p == '#')) ; + } ; + + return true ; +} ; + +/*------------------------------------------------------------------------------ + * Reserved characters in a pipe token. + * + * The characters reserved allow for a large number of possible options to + * be attached to the pipe token. + * + * Wish to allow the pipe token to be followed by file name or command name + * without requiring whitespace separation, also do not want to intrude into + * quoted or escaped stuff. So limit the characters that are reserved. + */ +static inline bool +cmd_pipe_reserved_char(char ch) +{ + return strchr("<|>%&*+-=?", ch) != NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Take elstring and break it into tokens. * * Discards leading and trailing ' ' or '\t'. * * Expects string to have been preprocessed, if required, to ensure that any * unwanted control characters have been removed. This code only recognises - * '\t'. + * '\t' and treats it as whitespace. * * Anything between '....' is ignored by the tokenizer. NB: this follows the * shell convention, so '\' is also ignored and there is no way to include "'" @@ -90,60 +1187,88 @@ cmd_spare_tokens_free(void) * * tokens are separated by whitespace -- one ' ' or '\t' characters * The whitespace is discarded. * - * * tokens are separated by "separators", which start with any of: + * * tokens which start with any of: * - * '!', '#', '<', '>' and '|' + * '!', '#', '<' and '>' * - * which may be followed by one or more characters to form a separator - * token. + * terminate themselves, as follows: * * - from '!' or '#' to end of line is a comment token. * - * - '<' opt and '<|' opt are separators, where opt is any combination - * of '+', '*' and '-'. + * - '<' followed by pipe_reserved_chars is a token (in_pipe) * - * - '>', '>>' and '|' are separators. + * - '>' followed by pipe_reserved_chars is a token (out_pipe). * - * NB: the tokenization mimics the standard shell which makes the piping stuff - * straightforward. It's also well known. Apart from the "'" rule, it - * also seems fine ! + * See above for pipe_reserved_ * - * NB: any control characters other than those spotted by isspace() are accepted - * as part of the current token ! + * NB: this means that for '!', '#', '<' and '>' to be significant, they + * MUST be preceeded by whitespace (or start of line). This ever so + * slightly reduces the impact of the new lexical conventions. * - * The tokens returned contain all the original characters of the line, except - * for the removal of '\t' between tokens. + * NB: the tokenization roughly mimics the (POSIX) standard shell. The + * differences are: * - * Returns: the types of all tokens or'd together. - * returns cmd_tok_null if the line is empty (apart from ' ' and '\t') - * or if the pointer was NULL. + * '|' is *not* a pipe ('>|' is), because '|' is a character in the + * regex repertoire. + * + * '<' and '>' do not terminate a token -- so are only significant + * at the start of a token. + * + * '!' is not a comment for the shell. + * + * The requirement for whitespace (or start of line) before '#' is + * consistent with the shell. + * + * The handling of '...' follows the standard shell. + * + * The tokenization does not remove any " ' or \ characters, that is left + * for a later stage, where context may affect the handling. + * + * NB: any control characters other than '\t' are accepted as part of the + * current token ! + * + * The tokens returned contain all the original characters of the line, except + * for the removal of ' ' and '\t' between tokens and at the end of the line. * * Note: all the tokens in the vector have at least one character, and no * entries are NULL. * * NB: it is the callers responsibility to release the token objects in due * course. + * + * NB: the elstring containing the line to be tokenised MUST NOT change + * until the parsed object is finished with. + * + * Returns: the types of all tokens or'd together. + * returns cmd_tok_null if the line is empty (apart from ' ' and '\t') + * or if the elstring was or contained NULL. + * + * Initialises the parsed object, ready for further parsing: + * + * Sets: parsed->parts = cmd_parts_none + * parsed->num_tokens ) + * parsed->elen ) per the results + * parsed->tsp ) + * parsed->tokens ) + * + * Note that the num_tokens does not include the cmd_tok_eol on the end. */ -extern cmd_token_type_t -cmd_tokenise(cmd_parsed parsed, const char *line, node_type_t node) +extern void +cmd_tokenise(cmd_parsed parsed, qstring line) { - const char *cp, *ep ; + cpp_t lp ; + const char *cp, *tp ; cmd_token_type_t total ; + uint nt ; - cmd_empty_token_vector(parsed->tokens) ; /* Empty the token vector */ - - parsed->line = line ; - parsed->onode = parsed->cnode = node ; - - total = cmd_tok_null ; /* nothing yet */ + total = 0 ; /* nothing yet */ + nt = 0 ; - if (line == NULL) /* tolerate NULL */ - return total ; + qs_cpp(lp, line) ; /* NULL -> NULL */ - cp = line ; - ep = cp + strlen(cp) ; - - while (cp < ep) /* process to end */ + cp = lp->p ; + tp = cp ; + while (cp < lp->e) /* process to end */ { const char* sp ; bool end ; @@ -152,18 +1277,17 @@ cmd_tokenise(cmd_parsed parsed, const char *line, node_type_t node) if ((*cp == ' ') || (*cp == '\t')) { /* skip white-space */ - do { ++cp ; } while ((*cp == ' ') || (*cp == '\t')) ; - - if (cp == ep) + do { - if (total != cmd_tok_null) - total |= cmd_tok_trailing ; - break ; - } ; + end = (++cp == lp->e) ; + } while (!end && ((*cp == ' ') || (*cp == '\t'))) ; + + if (end) + break ; } ; - sp = cp ; end = false ; + sp = cp ; type = cmd_tok_simple ; do { @@ -174,10 +1298,10 @@ cmd_tokenise(cmd_parsed parsed, const char *line, node_type_t node) end = true ; break ; - case '\'': /* proceed to matching '\'' or end */ - type |= cmd_tok_sq ; + case '\'': /* proceed to matching ' or end */ ++cp ; - while (cp < ep) + type |= cmd_tok_sq ; + while (cp < lp->e) { if (*cp++ == '\'') break ; @@ -185,719 +1309,2785 @@ cmd_tokenise(cmd_parsed parsed, const char *line, node_type_t node) break ; case '\\': /* step past escaped character, if any */ - type |= cmd_tok_esc ; ++cp ; - if (cp < ep) + type |= cmd_tok_esc ; + if (cp < lp->e) ++cp ; break ; - case '"': /* proceed to matching '"' or end... */ - type |= cmd_tok_dq ; + case '"': /* proceed to matching " or end... */ ++cp ; - while (cp < ep) /* NB: do not register '\\' separately */ + type |= cmd_tok_dq ; + while (cp < lp->e) /* NB: do not register \ separately */ { if (*cp++ == '"') - if (*(cp - 2) != '\\') /* ignore escaped '"' */ + if (*(cp - 2) != '\\') /* ignore escaped " */ break ; } ; break ; - case '>': /* '>' or '>>' separators. */ - end = true ; - if (cp == sp) /* if at start of token */ + case '>': /* '>' special at start */ + end = (cp == sp) ; + ++cp ; + if (end) /* if special */ { - type = cmd_tok_pipe_out ; - ++cp ; - if ((cp < ep) && (*cp == '>')) + type = cmd_tok_out_pipe ; + while ((cp < lp->e) && cmd_pipe_reserved_char(*cp)) ++cp ; } ; break ; - case '|': /* '|' separator. */ - end = true ; - if (cp == sp) - type = cmd_tok_pipe_out ; - ++cp ; - break ; - - case '<': /* '<' or '<|' separators. */ - end = true ; - if (cp == sp) + case '<': /* '<' special at start */ + end = (cp == sp) ; + ++cp ; + if (end) /* if special */ { - type = cmd_tok_pipe_in ; - ++cp ; - if ((cp < ep) && (*cp == '|')) - ++cp ; - if ( (cp < ep) && - ((*cp == '+') || (*cp == '-') || (*cp == '*')) ) + type = cmd_tok_in_pipe ; + while ((cp < lp->e) && cmd_pipe_reserved_char(*cp)) ++cp ; } ; break ; - case '!': /* '!' and '#' separators. */ + case '!': /* '!' and '#' special at start */ case '#': - end = true ; - if (cp == sp) + if ((cp == sp) && (nt == 0)) { + end = true ; type = cmd_tok_comment ; - cp = ep ; - } ; + cp = lp->e ; + } + else + ++cp ; break ; default: ++cp ; break ; } ; - } while (!end && (cp < ep)) ; + } while (!end && (cp < lp->e)) ; - cmd_token_push(parsed->tokens, - cmd_token_new(type, sp, cp - sp, sp - line)) ; + cmd_token_set(parsed->tokens, nt, type, sp, cp - sp, sp - lp->p) ; + ++nt ; + total |= type ; + + tp = cp ; } ; - return total ; + /* When we get here, tp points just after last character of last token, + * or at start of line if the line is blank (other than whitespace). + * + * If line is empty (apart from whitespace): + * + * - the effective length is zero. + * - the trailing space count is the number of (leading) spaces. + * + * If line is not empty: + * + * - the start position of the first token is the number of leading spaces. + * - the trailing space count is the number of spaces after the last + * token. (If the last token is comment, this will be zero.) + */ + parsed->parts = cmd_parts_none ; + parsed->tok_total = total ; + parsed->num_tokens = nt ; + parsed->elen = tp - lp->p ; + parsed->tsp = lp->e - tp ; + + /* Append an empty end of line token. */ + cmd_token_set(parsed->tokens, parsed->num_tokens, cmd_tok_eol, + lp->e, 0, lp->e - lp->p) ; } ; /*------------------------------------------------------------------------------ - * Process token to remove quotes and escapes (if any). + * Process in-pipe token and set the required bits in the pipe type word * - * Returns: true <=> OK - * false => invalid escape or incomplete quotes + * Known tokens are: < <| <+ <|+ + */ +static cmd_return_code_t +cmd_parse_in_pipe(cmd_parsed parsed, cmd_token t) +{ + cpp_t p ; + bool ok ; + + els_cpp(p, t->ot) ; + + ok = ((p->p < p->e) && (*p->p++ == '<')) ; + + if (ok) + { + /* First character after '<' may qualify the type of the pipe */ + parsed->in_pipe = cmd_pipe_file ; + + if (p->p < p->e) + { + switch (*p->p++) + { + case '|': + parsed->in_pipe = cmd_pipe_shell ; + break ; + + default: + --p->p ; /* put back */ + break ; + } + } ; + + /* Deal with option characters */ + while (ok && (p->p < p->e)) + { + /* Eat option character, if recognise it. */ + switch (*p->p++) + { + case '+': /* reflect command lines */ + parsed->in_pipe |= cmd_pipe_reflect ; + break ; + + default: + --p->p ; + ok = false ; + break ; + } ; + } ; + } ; + + if (!ok) + return cmd_parse_error(parsed, t, 0, "invalid 'pipe in'") ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Process out-pipe token and set the required bits in the pipe type word + * + * Known tokens are: > >> >| >* + */ +static cmd_return_code_t +cmd_parse_out_pipe(cmd_parsed parsed, cmd_token t) +{ + cpp_t p ; + bool ok ; + + els_cpp(p, t->ot) ; + + ok = ((p->p < p->e) && (*p->p++ == '>')) ; + + if (ok) + { + /* First character after '>' may qualify the type of the pipe */ + parsed->out_pipe = cmd_pipe_file ; + + if (p->p < p->e) + { + switch (*p->p++) + { + case '>': + parsed->out_pipe |= cmd_pipe_append ; + break ; + + case '|': + parsed->out_pipe = cmd_pipe_shell ; + break ; + + case '*': + parsed->out_pipe = cmd_pipe_dev_null ; + break ; + + default: + --p->p ; /* put back */ + break ; + } + } ; + + /* Could now have options, but presently do not */ + if (p->p < p->e) + { + ok = false ; + } ; + } ; + + if (!ok) + return cmd_parse_error(parsed, t, 0, "invalid 'pipe out'") ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * If token is incomplete make a copy of it and '\0' terminate. + * + * If if contains quotes or escapes process those down. + * + * For Quagga purposes, the following is done: + * + * inside '...': all characters stand for themselves, except '\t' -> ' ', + * and, of course, the terminating '. + * + * This is just like the shell. + * + * inside "...": all characters stand for themselves, except '\t' -> ' ', + * and, of course, the terminating ", plus the following + * \x escapes are processed: + * + * \" -> " -- so can have " in "...." + * \\ -> \ -- so can have \ in "...." + * \$ -> $ -- so can have $ in "...." + * + * outside quotes: all characters stand for themselves, except: + * + * \sp -> sp -- so can escape the odd space (cf shell) + * \tab -> sp -- ditto of tab, but map to space + * \? -> ? ) + * \' -> ' ) + * \" -> " ) so can escape the odd meta character + * \< -> < ) where required or to taste. + * \> -> > ) + * \! -> ! ) + * \# -> \# ) + * + * NB: with the exception of $ inside of "..." the carefully avoids the + * regex meta characters: .*+?^$_ and (|)[-] + * and the regex escaped forms of those and the back reference \1 etc. + * + * $ in "..." is reserved for future use as a variable or other + * substitution start. + * + * Returns: CMD_SUCCESS <=> OK + * CMD_ERR_PARSE <=> invalid escape or incomplete quotes * * NB: if fails, returns token completed as far as possible. */ -extern bool -cmd_token_do_complete(token t) +static cmd_return_code_t +cmd_token_complete(cmd_parsed parsed, cmd_token t) { - char *s, *p, *q ; - char ch ; - bool ok = true ; - bool dq = false ; + cpp_t p ; + pp_t q ; + bool dq ; + cmd_return_code_t ret ; + + if ((t->type & cmd_tok_incomplete) == 0) + return CMD_SUCCESS ; /* Quick out if nothing to do */ - p = s = cmd_token_value(t) ; - q = p ; - while (*p != '\0') + /* To process quotes etc works from the elstring that points to + * the original line, to the qstring. + * + * For quotes and escapes, the result is always no longer than the + * original. + */ + els_cpp_nn(p, t->ot) ; /* original token */ + + qs_new_size(t->qs, p->e - p->p) ; /* discard alias & set qs to be + big enough for original */ + qs_pp_nn(q, t->qs) ; /* where to complete token to */ + + ret = CMD_SUCCESS ; + dq = false ; + while (p->p < p->e) { - switch (*p) + switch (*p->p) { + case '\t': + *q->p++ = ' ' ; /* '\t' -> ' ' */ + ++p->p ; + break ; + case '\'': - ++p ; /* skip leading '\'' */ + ++p->p ; /* skip leading ' */ while (1) { - if (*p == '\0') + if (p->p == p->e) { - ok = false ; /* broken '...' */ + ret = cmd_parse_error(parsed, t, 0, "missing closing '") ; break ; } ; - if (*p == '\'') + if (*p->p == '\'') { - ++p ; /* skip trailing '\'' */ + ++p->p ; /* skip trailing ' */ break ; /* done '....' */ } ; - *q++ = *p++ ; + if (*p->p == '\t') + { + *q->p++ = ' ' ; /* '\t' -> ' ' */ + ++p->p ; + } + else + *q->p++ = *p->p++ ; /* rest as is */ } ; break ; case '"': - ++p ; /* skip '"' */ - dq = !dq ; + ++p->p ; /* skip " */ + dq = !dq ; /* switch state */ break ; case '\\': - ++p ; /* step past '\\' */ - ch = cmd_token_escape(*p) ; - if (ch == '\0') + *q->p++ = *p->p++ ; /* copy the \ */ + if (p->p == p->e) + ret = cmd_parse_error(parsed, t, 0, "trailing \\") ; + else { - ok = false ; - ch = *p ; - if (ch == '\0') - ch = '\\' ; /* \ at end is kept */ + if (dq) + { + /* inside "...": \", \$ and \\ only */ + if ((*p->p == '"') || (*p->p == '$') || (*p->p == '\\')) + --q->p ; /* strip the \ */ + } + else + { + /* outside quotes: \sp \tab \< \> \! \# */ + if ( (*p->p == '\t') || (*p->p == ' ') || + (*p->p == '\'') || (*p->p == '"') || + (*p->p == '<') || (*p->p == '>') || + (*p->p == '!') || (*p->p == '#') ) + --q->p ; /* strip the \ */ + } ; + + if (*p->p != '\t') + *q->p++ = *p->p++ ; else - *q++ = '\\' ; /* otherwise keep \x */ + { + *q->p++ = ' ' ; + ++p->p ; + } ; } ; - *q++ = ch ; - break ; + break ; default: - *q++ = *p++ ; + *q->p++ = *p->p++ ; + break ; } ; } ; - qs_term_here(t->qs, q) ; + if (dq) + ret = cmd_parse_error(parsed, t, 0, "missing closing \"") ; - return ok && !dq ; + if (ret == CMD_SUCCESS) + { + *q->p = '\0' ; /* '\0' terminate */ + qs_set_len_nn(t->qs, q->p - qs_char_nn(t->qs)) ; + t->term = true ; + } + else + { + qs_set_alias_els(t->qs, t->ot) ; + } ; + + return ret ; } /*------------------------------------------------------------------------------ - * Return escaped value of \e. + * Tokenise the given line and work out where cursor is wrt tokens. + * + * Looks for first token whose end is at or beyond the cursor position. Note + * that: + * + * * where the cursor is just after the last character of a token, it is at + * the end of that token. + * + * * where there is more than one space between tokens, and the cursor is + * after the first space, then it is deemed to be "on" the second token. + * + * - if there are spaces at the start of the line and the cursor is on + * one of those spaces, the it is "on" the first token. * - * Everything except '0'..'9', 'A'..'Z', 'a'..'z' can be escaped -- these are - * reserved for future actual escapes ! + * - if the line is blank, with zero or more spaces, the cursor is "on" + * the eol token. * - * Returns '\0' if e == '\0' or if \e is an invalid escape. + * Note that where a line ends in a comment, there are no trailing spaces. + * + * Returns: true <=> "in a special place" + * + * Is "in a special place" if the cursor is: + * + * a. in a quoted string of any type + * b. in an escape (so immediately after a '\') + * c. after the '!' or '#' on a line which consists only of a comment + * d. after the first '<' or '>' of the first pipe token on the line + * + * If NOT "in a special place", will set: + * + * * parsed->cti -- cursor token index + * * parsed->rp -- cursor position relative to the start of token + * + * Note that the cursor token may be a comment or pipe token, or the eol token + * at the end of the line. */ -static char -cmd_token_escape(char e) +extern bool +cmd_token_position(cmd_parsed parsed, qstring line) { - if ((e < '0') || (e > 'z')) - return e ; - return isalpha(e) ? '\0' : e ; + cmd_token t ; + uint ti ; + uint cp, ep ; + + cpp_t p ; + const char* q ; + const char* e ; + bool sq, dq, bs ; + + /* Re-initialise parsed object and tokenise the given line */ + cmd_tokenise(parsed, line) ; + + /* Get the cursor position */ + cp = qs_cp_nn(line) ; + + /* Look for the last token whose end is <= cp + * + * Will position on last, "eol" token -- which is not counted in + * parsed->num_tokens -- if is beyond the last real token on the line. + */ + t = NULL ; + ti = 0 ; + while (1) + { + t = cmd_token_get(parsed->tokens, ti) ; + + if (cp > t->tp) + { + /* As soon as we are past '<', '>', '!' or '#' -- return "special" */ + if ((t->type & ( cmd_tok_in_pipe + | cmd_tok_out_pipe | cmd_tok_comment) ) != 0) + return true ; + } ; + + ep = t->tp + els_len_nn(t->ot) ; + + if ((cp <= ep) || (ti == parsed->num_tokens)) + break ; /* stop when found token (or at eol) */ + + ++ti ; + } ; + + parsed->cti = ti ; + parsed->ctl = els_len_nn(t->ot) ; + parsed->rp = (int)cp - (int)t->tp ; + + /* Arrive with t = token in which cp belongs + * + * If the token is incomplete, then need to check for "ins" -- unless is + * already "ins". + */ + if ((t->type & cmd_tok_incomplete) == 0) + return false ; + + /* Scan to see if in '...' or "..." or after '\'. */ + + els_cpp_nn(p, t->ot) ; /* original token */ + + q = p->p + (cp - t->tp) ; /* position interested in */ + assert(q > p->p) ; + + dq = false ; + sq = false ; + bs = false ; + + e = (q <= p->e) ? q : p->e ; /* stop at q or end of token */ + + while (p->p < e) + { + switch (*p->p) + { + case '\'': + if (!dq && !bs) /* ignore ' inside "..." & after \ */ + sq = !sq ; + break ; + + case '\"': /* ignore " inside '...' & after \ */ + if (!sq && !bs) + dq = !dq ; + break ; + + case '\\': + if (!sq) /* ignore \ inside '...' */ + bs = !bs ; /* cope with \\ */ + break ; + + default: + break ; + } ; + + ++p->p ; + } ; + + /* Have scanned to but excluding current character or end of token, whichever + * came first. + * + * If in '...' or "...", then is "ins" + * If is immediately after \, then is "ins". + */ + return sq || dq || (bs && (p->e <= q)) ; } ; /*============================================================================== - * Parser object + * Match functions. + * + * Is the given string a, possibly incomplete, value of the required kind ? */ +static match_strength_t cmd_ipv4_match(const char* cp, uint prefix) ; +static match_strength_t cmd_prefix_match(const char* cp, uint prefix) ; + /*------------------------------------------------------------------------------ - * Initialise a new cmd_parsed object, allocating if required + * Is this an IPv4 Address + * + * 999.999.999.999 -- each 0..255, no leading zeros, decimal only. + * + * Returns: mt_no_match -- improperly formed + * mt_ipv4_address_partial -- OK as far as it goes (or empty) + * mt_ipv4_address_complete -- syntactically complete */ -extern cmd_parsed -cmd_parse_init_new(cmd_parsed parsed) +static match_type_t +cmd_ipv4_address_match(cmd_token t) { - if (parsed == NULL) - parsed = XCALLOC(MTYPE_CMD_PARSED, sizeof(*parsed)) ; - else - memset(parsed, 0, sizeof(*parsed)) ; + match_strength_t ms ; - /* Zeroising the structure has set: - * - * cmd = NULL -- no command parsed, yet - * cnode -- no node set, yet - * - * do_shortcut -- false - * onode -- not material (do_shortcut is false) - * - * pipes = 0 -- cmd_pipe_none - */ - confirm(cmd_pipe_none == 0) ; + ms = cmd_ipv4_match(cmd_token_make_string(t), 0) ; - cmd_token_vector_init(parsed->tokens) ; - cmd_token_vector_init(parsed->read_pipe_tokens) ; - cmd_token_vector_init(parsed->write_pipe_tokens) ; - cmd_arg_vector_init(parsed) ; + if (ms == ms_var_complete) + return mt_ipv4_address_complete ; + if (ms == ms_partial) + return mt_ipv4_address_partial ; - return parsed ; + return mt_no_match ; } ; /*------------------------------------------------------------------------------ - * Empty out and (if required) free a cmd_parsed object + * Is this an IPv4 Prefix + * + * 999.999.999.999/99 -- each 0..255, no leading zeros, decimal only. + * and prefix length must be <= 32 + * + * Returns: mt_no_match -- improperly formed + * mt_ipv4_prefix_partial -- OK as far as it goes (or empty) + * mt_ipv4_prefix_complete -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. */ -extern cmd_parsed -cmd_parse_reset(cmd_parsed parsed, bool free_structure) +static match_type_t +cmd_ipv4_prefix_match(cmd_token t) { - if (parsed != NULL) + match_strength_t ms ; + + ms = cmd_ipv4_match(cmd_token_make_string(t), 32) ; + + if (ms == ms_var_complete) + return mt_ipv4_prefix_complete ; + if (ms == ms_partial) + return mt_ipv4_prefix_partial ; + + return mt_no_match ; +} ; + +/*------------------------------------------------------------------------------ + * Is this an IPv4 Address or Prefix: + * + * 999.999.999.999[/99] -- each 0..255, no leading zeros, decimal only. + * and prefix length must be <= n + * + * Returns: ms_no_match -- improperly formed + * ms_partial -- OK as far as it goes (or empty) + * ms_var_complete -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. + */ +static match_strength_t +cmd_ipv4_match(const char* cp, uint prefix) +{ + uint nums ; + + for (nums = 0 ; nums < 4 ; ++nums) { - cmd_empty_parsed_tokens(parsed) ; /* give back tokens */ - cmd_arg_vector_free(parsed) ; /* give back vector body */ + if (*cp == '.') /* need a '.' except at start */ + { + if (nums == 0) + return ms_no_match ; - if (free_structure) - XFREE(MTYPE_CMD_PARSED, parsed) ; /* sets parsed = NULL */ + ++cp ; /* step past '.' */ + } + else + { + if (nums != 0) + return (*cp == '\0') ? ms_partial : ms_no_match ; + } ; + + /* Collect a decimal number 0..255, no leading zeros. + * + * Rejects anything other than digits -- including '/' and '.'. + * + * Accepts '\0' as partial -- which accepts empty strings. + */ + if (*cp == '0') + { + ++cp ; + if (isdigit(*cp)) + return ms_no_match ; /* reject leading zeros */ + } + else + { + char* ep ; + + if (isdigit(*cp)) + { + if (strtoul(cp, &ep, 10) <= 255) + cp = ep ; + else + return ms_no_match ; /* reject invalid number */ + } + else + return (*cp == '\0') ? ms_partial : ms_no_match ; + } ; + } ; + + /* Arrive here with 4 numbers */ + + if (prefix == 0) + return (*cp == '\0') ? ms_var_complete : ms_no_match ; + else + return cmd_prefix_match(cp, prefix) ; +} ; + +/*------------------------------------------------------------------------------ + * Is this a Prefix: + * + * /99 no leading zeros, decimal only, value must be <= n. + * + * Arrives here with *cp pointing at where there should be a '/'. + * + * Returns: ms_no_match -- improperly formed + * ms_partial -- OK as far as it goes (or empty). + * ms_var_complete -- syntactically complete + */ +static match_strength_t +cmd_prefix_match(const char* cp, uint prefix) +{ + if (*cp != '/') + return (*cp == '\0') ? ms_partial : ms_no_match ; + + /* OK have '/' and a prefix is now expected. */ + + ++cp ; /* step past '/' */ + + if (*cp == '\0') + return ms_partial ; /* if nothing after '/' */ + + if (*cp == '0') + ++cp ; + else + { + char* ep ; + if (isdigit(*cp) && (strtoul(cp, &ep, 10) <= prefix)) + cp = ep ; else - cmd_parse_init_new(parsed) ; + return ms_no_match ; /* reject invalid number */ } ; - return parsed ; + if (*cp != '\0') + return ms_no_match ; /* something other than digits after the '/', + or leading zero, or number too big */ + + return ms_var_complete ; } ; -/*============================================================================== - * Match functions. +/*------------------------------------------------------------------------------ + * IPv6 Address and Prefix matching. + */ + +#ifdef HAVE_IPV6 + +static match_strength_t cmd_ipv6_match(const char* cp, uint prefix) ; + + + +/*------------------------------------------------------------------------------ + * Is this an IPv6 Address * - * Is the given string a, possibly incomplete, value of the required kind ? + * h:h:... -- RFC 4291 rules & prefix length must be <= n + * + * Returns: mt_no_match -- improperly formed + * mt_ipv6_address_partial -- OK as far as it goes (or empty) + * mt_ipv6_address_complete -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. */ +static match_type_t +cmd_ipv6_address_match (cmd_token t) +{ + match_strength_t ms ; + + ms = cmd_ipv6_match(cmd_token_make_string(t), 0) ; + if (ms == ms_var_complete) + return mt_ipv6_address_complete ; + if (ms == ms_partial) + return mt_ipv6_address_partial ; + + return mt_no_match ; +} ; /*------------------------------------------------------------------------------ - * Is this an IPv4 Address: + * Is this an IPv6 Prefix * - * 999.999.999.999 -- where no part may be > 255 + * h:h:...[/99] -- RFC 4291 rules & prefix length must be <= 128 * - * TODO: cmd_ipv4_match() seems to accept leading '.' ? - * TODO: cmd_ipv4_match() seems to accept leading zeros ? + * Returns: mt_no_match -- improperly formed + * mt_ipv6_prefix_partial -- OK as far as it goes (or empty) + * mt_ipv6_prefix_complete -- syntactically complete * - * Returns: no_match -- improperly formed - * partly_match -- accepts empty string - * exact_match -- syntactically complete + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. */ -extern match_type_t -cmd_ipv4_match (const char *str) +static match_type_t +cmd_ipv6_prefix_match(cmd_token t) { - const char *sp; - int dots = 0, nums = 0; - char buf[4]; + match_strength_t ms ; + + ms = cmd_ipv6_match(cmd_token_make_string(t), 128) ; + + if (ms == ms_var_complete) + return mt_ipv6_prefix_complete ; + if (ms == ms_partial) + return mt_ipv6_prefix_partial ; - if (str == NULL) - return partly_match; + return mt_no_match ; +} ; - for (;;) +/*------------------------------------------------------------------------------ + * Is this an IPv6 Address or Prefix: + * + * h:h:...[/99] -- RFC 4291 rules & prefix length must be <= n + * + * Returns: ms_no_match -- improperly formed + * ms_partial -- OK as far as it goes (or empty) + * ms_var_complete -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. + */ +static match_strength_t +cmd_ipv6_match(const char* cp, uint prefix) +{ + bool double_colon ; + uint nums ; + + double_colon = false ; + nums = 0 ; + + /* At start, the first time around the loop... */ + + for (nums = 0 ; nums < 8 ; ++nums) { - memset (buf, 0, sizeof (buf)); - sp = str; - while (*str != '\0') + const char* sp, * ep ; + + /* After number (nums != 0), or at start. + * + * Deal with (a) ':', '::' and '::/'. + * (b) '/' -- valid only if had '::'. + * (c) '\0' -- partial unless have had '::' + * (d) if not at start, must have one of the above. + */ + if (*cp == ':') { - if (*str == '.') + /* (a) ':', '::' and '::/'. + * + * At start can accept '::', but not ':' (unless '\0' follows). + * + * If not at start, accept ':' and accept '::' if not already seen. + * + * After '::' can have the full complement of numbers, or '/' or + * '\0' which bring the number part to an end. + * + * After ':' we accept '\0' but explicitly reject '/' (and we + * reject a ':' at the start if not followed by '\0'). + */ + ++cp ; /* step past ':' */ + + if (*cp == ':') { - if (dots >= 3) - return no_match; + /* '::' -- counts as number, can be followed by '/' */ - if (*(str + 1) == '.') - return no_match; + if (double_colon) + return ms_no_match ; /* at most one */ - if (*(str + 1) == '\0') - return partly_match; + ++cp ; /* step past '::' */ - dots++; - break; + double_colon = true ; + ++nums ; /* counts as a number */ + + if ((nums == 8) || (*cp == '/') || (*cp == '\0')) + break ; /* no more numbers */ } - if (!isdigit ((int) *str)) - return no_match; + else if (*cp == '\0') + return ms_partial ; /* accepts bare ':', inter alia */ - str++; + else if ((*cp == '/') || (nums == 0)) + return ms_no_match ; + } + else if (*cp == '/') + { + /* (b) '/' -- valid only if had '::'. */ + if (double_colon) + break ; + else + return ms_no_match ; + } + else if (*cp == '\0') + { + /* (c) '\0' -- partial unless have had '::' */ + if (double_colon) + break ; + else + return ms_partial ; /* accept empty string, inter alia */ } + else if (nums != 0) + /* (d) if not at start, must have one of the above. */ + return ms_no_match ; + + assert(*cp != '\0') ; + + /* Is now at start, or after ':' and is not '\0'. + * + * Require 1..4 hex digits -- will also accept 1..3 decimals ! + * + * Rejects anything else, including '/' at this stage. + */ + sp = cp ; + ep = cp + 4 ; + do + { + if (((*cp >= '0') && (*cp <= '9')) || ((*cp >= 'A') && (*cp <= 'F')) + || ((*cp >= 'a') && (*cp <= 'f'))) + ++cp ; + else + { + if (cp > sp) + break ; + else + return ms_no_match ; /* no digits */ + } + } + while (cp < ep) ; - if (str - sp > 3) - return no_match; + /* Watch out for '.' ! */ - strncpy (buf, sp, str - sp); - if (atoi (buf) > 255) - return no_match; + if (*cp == '.') + { + /* Can have IPv4 trailing part, if that would account for the + * last two number parts of the IPv6. + * + * Note that a '.' after something which is not simple decimal + * 0..255 will be rejected by cmd_ipv4_match(). + * + * Note also that we pass through the prefix requirement. + */ + if ((nums == 6) || (double_colon && (nums < 6))) + return cmd_ipv4_match(sp, prefix) ; + else + return ms_no_match ; + } ; + } ; - nums++; + /* Arrives here either because nums == 8, or met '/' or '\0' after ':: + * + * So only get here if have a valid end of the digits part of the IPv6 + */ + + assert((nums == 8) || double_colon) ; + + if (prefix == 0) + return (*cp == '\0') ? ms_var_complete : ms_no_match ; + else + return cmd_prefix_match(cp, prefix) ; +} ; + + +#endif /* HAVE_IPV6 */ + +/*------------------------------------------------------------------------------ + * Is this a decimal number in the allowed range: + * + * Returns: mt_no_match -- improperly formed or empty + * mt_range_partial -- OK as far as it went (or empty string) + * mt_range_complete -- syntactically complete + */ +static match_type_t +cmd_range_match (cmd_item item, cmd_token t) +{ + const char* cp, * dp ; + char *ep ; + int base ; + long val; + + confirm((LONG_MAX > item_max_number) && (LONG_MIN < -item_max_number)) ; + + cp = cmd_token_make_string(t) ; + + /* Worry about any sign */ + + dp = cp ; + + if ((*cp == '-') || (*cp == '+')) + { + if (!item->range_sign_allowed) + return mt_no_match ; /* reject '-' or '+' */ + + ++dp ; /* step to digit */ + } + else + { + if (item->range_sign_required) + return mt_no_match ; + } ; + + /* Worry about leading digits and hex, and no digits at all */ + + base = 10 ; /* by default. */ + + if (*dp == '\0') + return mt_range_partial ; /* accepts empty string, inter alia */ - if (*str == '\0') - break; + else if (*dp == '0') + { + ++dp ; /* step past zero */ + + if (*dp != '\0') + { + /* No leading zeros and no stinking octal -- but allow hex */ + if ((*dp != 'x') && (*dp != 'X')) + return mt_no_match ; - str++; + ++dp ; /* step past 'x' or 'X' */ + base = 16 ; + + if (*dp == '\0') + return mt_range_partial ; + } ; } - if (nums < 4) - return partly_match; + else if (!isdigit(*dp)) + return mt_no_match ; - return exact_match; -} + /* The string starts with digit, possibly preceded by sign, and possibly + * an 'x' or 'X' with at least 1 further character. + */ + val = strtol(cp, &ep, base) ; + if (*ep != '\0') + return mt_no_match ; + + /* Is the result in range ? */ + + if (val >= item->range_min && val <= item->range_max) + return mt_range_complete ; /* on the money */ + + /* Want to return mt_range_partial iff adding digits might make + * an in range value. + * + * If val is < 0, then adding digits makes it smaller. + * If val is == 0, not allowed to add digits. + * If val is > 0, then adding digits makes it bigger. + */ + if (val < item->range_min) + { + /* Is less than minimum, so partial match if can get bigger. */ + return (val > 0) ? mt_range_partial : mt_no_match ; + } + else + { + /* Is more than maximum, so partial match if can get smaller. */ + return (val < 0) ? mt_range_partial : mt_no_match ; + } ; +} ; + +/*============================================================================== + * Command "filtering". + * + * The command parsing process starts with a (shallow) copy of the cmd_vector + * entry for the current "node". + * + * So cmd_v contains pointers to struct cmd_command values. When match fails, + * the pointer is set NULL -- so parsing is a process of reducing the cmd_v + * down to just the entries that match. + * + * Each cmd_command has a vector "items", which contains an entry for each + * "token" position. That entry is a vector containing the possible values at + * that position. + */ + +static int cmd_item_filter(cmd_parsed parsed, cmd_item item, cmd_token t) ; /*------------------------------------------------------------------------------ - * Is this an IPv4 Prefix: + * Prepare to filter commands in the node being parsed in. * - * 999.999.999.999/99 -- where no part may be > 255, - * and prefix length may not be > 32 + * The execute option turns off all partial matching -- so will not match, say, + * 222 as a possible IP address ! This means that the result of the filter + * operation will be executable command(s), only. * - * TODO: cmd_ipv4_prefix_match() seems to accept leading '.' ? - * TODO: cmd_ipv4_prefix_match() seems to accept leading zeros ? + * The execute option also pre-filters the command vector to discard all + * commands which are too short or too long to match the current line. * - * Returns: no_match -- improperly formed - * partly_match -- accepts empty string - * exact_match -- syntactically complete + * The strict option turns off partial matching of keywords, so only complete + * keywords will do. This is used for the configuration file, so that new + * commands can be added ! * - * NB: partly_match is returned for anything valid before the '/', but which - * has no '/' or no number after the '/'. + * Returns: number of commands which may match to. + */ +static uint +cmd_filter_prepare(cmd_parsed parsed, cmd_parse_type_t type) +{ + vector src_v ; + uint ci ; + uint nct ; + + bool execution = (type & cmd_parse_execution) != 0 ; + bool strict = (type & cmd_parse_strict) != 0 ; + + /* get commands for the current node */ + src_v = ((cmd_node)vector_get_item(node_vector, parsed->cnode)) + ->cmd_vector ; + + assert(src_v != NULL) ; /* invalid parsed->cnode ?? */ + + /* empty the working commands vector, making sure big enough for the current + * node's commands. + * + * Note that the cmd_v lives for as long as the parsed object, so will + * grow over time to accommodate what is required. + */ + vector_re_init(parsed->cmd_v, vector_length(src_v)) ; + + /* Filter out commands which are too short. + * + * For execution, can filter out commands which are too long. + */ + nct = parsed->num_command ; + + for (ci = 0 ; ci < vector_length(src_v) ; ++ci) + { + cmd_command cmd ; + + cmd = vector_get_item(src_v, ci) ; + + if ( cmd->nt_max < nct) + continue ; /* ignore if too short */ + + if ((cmd->nt_min > nct) && execution) + continue ; /* ignore if too long */ + + vector_push_item(parsed->cmd_v, cmd) ; + } ; + + /* Other preparation for filtering + * + * The min_strength is set according to whether is for execution. The + * strongest match is set from that. This means that any match which does + * not meet the minimum criterion is automatically excluded. + */ + + parsed->min_strength = execution ? ms_min_execute : ms_min_parse ; + parsed->strict = strict ; + + parsed->strongest = parsed->min_strength ; + parsed->best_complete = mt_no_match ; + + return vector_length(parsed->cmd_v) ; +} ; + +/*------------------------------------------------------------------------------ + * Filter set commands by matching items against the given token. + * + * Takes: parsed -- cmd_parsed object, previously prepared by + * cmd_prepare_fiter(). + * ii -- item index (0 == first) + * keep_items -- how to filter + * + * The item index is the index wrt the start of the commands being matched, + * not the index of the token in the command line. + * + * Keywords must match strictly or partly, depending on the filtering state + * in the parsed object. + * + * Variables must match completely if filtering for execution, otherwise may + * match partially. + * + * Returns: the number of items matched. + * + * NB: when filtering for execution, will not accept any partial matches for + * variables (but may accept partial matches for keywords). So, once the + * last token has been filtered against, the number of items matched gives + * the number of unambiguous commands, with complete values, that the line + * matches. */ -extern match_type_t -cmd_ipv4_prefix_match (const char *str) +static uint +cmd_filter(cmd_parsed parsed, uint ii, bool keep_items) { - const char *sp; - int dots = 0; - char buf[4]; + uint ci ; /* command index -- in cmd_v */ + uint ti ; /* token index in command line */ + uint c_keep ; + uint i_keep ; + cmd_token t ; + + /* Reset the filtering state */ + + parsed->strongest = parsed->min_strength ; + parsed->best_complete = mt_no_match ; + + /* If we are keeping the items which are matched, set the item_v + * empty and guess that may get one item per command. + * + * Note that the item_v lives for as long as the parsed object, so will + * grow over time to accommodate what is required. + */ + if (keep_items) + vector_re_init(parsed->item_v, vector_length(parsed->cmd_v)) ; - if (str == NULL) - return partly_match; + /* Work down the cmd_v, attempting to match cmd_items against cmd_tokens. + * + * Keep in the cmd_v the commands for which we get a match. + * + * At the same time, if required, keep in the item_v all the items which have + * matched. + */ + if (ii < parsed->num_command) + ti = parsed->first_command + ii ; /* map to token index */ + else + { + /* special case of filtering against the empty token at the end of + * the command line -- this is for command line help stuff. + * + * Note that there may be out_pipe or even comment tokens in between, + * so we here set the ti to the trailing eol token. + */ + assert(ii == parsed->num_command) ; + ti = parsed->num_tokens ; + } + t = cmd_token_get(parsed->tokens, ti) ; - for (;;) + c_keep = 0 ; + i_keep = 0 ; + + if (keep_items) + vector_re_init(parsed->item_v, vector_length(parsed->cmd_v)) ; + + for (ci = 0; ci < vector_length(parsed->cmd_v); ci++) { - memset (buf, 0, sizeof (buf)); - sp = str; - while (*str != '\0' && *str != '/') + cmd_command cmd ; + cmd_item item ; + int best ; + + cmd = vector_get_item(parsed->cmd_v, ci) ; + + if (ii < cmd->nt) + item = vector_get_item(cmd->items, ii) ; + + else if (ii == cmd->nt_max) + item = &eol_item ; /* match at end of line */ + + else if (ii > cmd->nt_max) + continue ; /* discard commands we are beyond, now */ + + else /* cmd->nt < cmd->nt_max <=> vararg. */ + { + /* It is elsewhere arranged that a vararg is always the last + * item for a given command. + * + * We cope with all tokens from the first vararg onwards by matching + * them all against the vararg. Inter alia this allows for a + * vararg to check each argument in turn -- iff feel like doing + * that. + */ + item = cmd->vararg ; + + /* Must have something and must now only check against the varag. + * (Not anything else in the (...) if vararg was in one !) + */ + assert((item != NULL) && (item->next == NULL)) ; + } ; + + /* See if get any sort of match at current position */ + best = -1 ; + while (item != NULL) { - if (*str == '.') + int ret ; + ret = cmd_item_filter(parsed, item, t) ; + + if (ret >= 0) { - if (dots == 3) - return no_match; + if (ret > 0) + i_keep = 0 ; - if (*(str + 1) == '.' || *(str + 1) == '/') - return no_match; + if (keep_items) + vector_set_item(parsed->item_v, i_keep++, item) ; + else + ++i_keep ; + } ; - if (*(str + 1) == '\0') - return partly_match; + if (ret > best) + best = ret ; - dots++; - break; - } + item = item->next ; + } ; - if (!isdigit ((int) *str)) - return no_match; + /* Keep if had a match */ + if (best >= 0) + { + if (best > 0) + c_keep = 0 ; /* better than all the rest */ - str++; - } + vector_set_item(parsed->cmd_v, c_keep++, cmd) ; + } ; + } ; + + if (keep_items) + vector_set_length(parsed->item_v, i_keep) ; /* discard what did not keep */ - if (str - sp > 3) - return no_match; + vector_set_length(parsed->cmd_v, c_keep) ; /* discard what did not keep */ - strncpy (buf, sp, str - sp); - if (atoi (buf) > 255) - return no_match; + return i_keep ; +} ; + +/*------------------------------------------------------------------------------ + * Filter given item, in given parsed state, attempting to match given token. + * + * Update the parsed state if get a better match. + * + * Returns: < 0 => is not as good ) + * == 0 => equally good ) compared to best match to date. + * > 0 => better than ) + * + * NB: the matching functions will partially match an empty string. + * + * The ms_anything types of item, will match an empty string. + */ +static int +cmd_item_filter(cmd_parsed parsed, cmd_item item, cmd_token t) +{ + match_strength_t ms ; + match_type_t mt ; + int cw ; - if (dots == 3) + /* Ignore item if the best we can hope for is not as strong as what we + * have already. + */ + mt = item_best_match(item->type) ; + ms = match_match_strength(mt) ; + + if ((mt < parsed->best_complete) || (ms < parsed->strongest)) + return -1 ; /* cannot even be as good */ + + /* Bang the rocks together to get match type + * + * TODO: do we need mt_xxx_partial etc ? + */ + if (t->type == cmd_tok_eol) + { +// mt = (item->type == item_eol) ? mt_eol : mt_eol_partial ; + mt = mt_eol_partial ; + } + else + { + switch (item->type) + { + case item_null: + zabort("invalid item_null") ; + break ; + + case item_eol: + break ; + + case item_keyword: + cw = els_cmp_word(t->ot, item->str) ; + + if (cw > 0) /* nope */ + mt = mt_no_match ; + else if (cw == 0) /* exact match */ + mt = mt_keyword_complete ; + else /* partial match */ + mt = parsed->strict ? mt_no_match + : mt_keyword_incomplete ; + break ; + + case item_range: + mt = cmd_range_match(item, t) ; + break ; + + case item_ipv4_address: + mt = cmd_ipv4_address_match(t) ; + break ; + + case item_ipv4_prefix: + mt = cmd_ipv4_prefix_match(t) ; + break ; + + case item_ipv6_address: + #ifdef HAVE_IPV6 + mt = cmd_ipv6_address_match(t) ; + #endif /* HAVE_IPV6 */ + break ; + + case item_ipv6_prefix: + #ifdef HAVE_IPV6 + mt = cmd_ipv6_prefix_match(t) ; + #endif /* HAVE_IPV6 */ + break ; + + case item_word: + mt = mt_word_match ; + break ; + + case item_vararg: + mt = mt_vararg_match ; + break ; + + case item_option_word: + mt = mt_option_word_match ; + break ; + + default: + zabort("unknown item type") ; + } ; + } ; + + /* Easy if did not match at all */ + if (mt == mt_no_match) + return -1 ; + + /* Is what we got worse, as good or better ? + * + * Update parsed to suit and return the news. + * + * Note that parsed->best_complete will be ms_no_match until parsed->strongest + * is set to ms_var_complete. + */ + ms = match_match_strength(mt) ; + + if (ms < parsed->strongest) + return -1 ; + + if (ms == parsed->strongest) + return 0 ; + + parsed->strongest = ms ; + return +1 ; +} ; + +/*============================================================================== + * Parsing of command lines + */ + +/*------------------------------------------------------------------------------ + * Parse a command in the given "node", if possible, ready for execution. + * + * If 'exact': use cmd_filter_by_string() + * otherwise: use cmd_filter_by_completion() + * + * If 'do': see if there is a 'do' at the front and proceed accordingly. + * + * If 'tree': move up the node tree to find command if not found in the + * current node. + */ + +static cmd_return_code_t cmd_parse_phase_one(cmd_parsed parsed, + cmd_parse_type_t type, node_type_t node) ; +static cmd_return_code_t cmd_parse_phase_one_b(cmd_parsed parsed, uint nt) ; +static cmd_return_code_t cmd_parse_phase_two(cmd_parsed parsed, + cmd_parse_type_t type) ; + +/*------------------------------------------------------------------------------ + * Parse a command in the given "node", or (if required) any of its ancestors. + * + * Requires command line to have been tokenised. + * + * Returns: CMD_SUCCESS => successfully parsed command, and the result is + * in the given parsed structure, ready for execution. + * + * NB: parsed->cnode may not be the incoming node. + * + * NB: parsed->parts is what was found + * + * NB: parsed->cmd->daemon => daemon + * + * CMD_EMPTY => line is empty, except perhaps for comment + * (iff parsing for execution) + * + * CMD_ERR_INCOMPLETE => "do" and nothing more + * (iff parsing for execution) + * + * CMD_SUCCESS_DAEMON => parsed successfully. Something for vtysh ?? + * + * CMD_ERR_NO_MATCH => could find nothing to match the command + * line. + * + * CMD_ERR_AMBIGUOUS => found 2 or more possible matches. + * Or, if not parsing for execution, there + * were no command tokens. + * + * CMD_ERR_PARSING => something wrong ! See parsed->emess. + * + * NB: unless cmd_parse_no_tree, may have tried any ancestor nodes. Returns + * with parsed->cnode with node last tried. + * + * NB: unless cmd_parse_no_do, will have taken a leading "do", and pushed the + * parsed->cnode to ENABLE_NODE (if in MIN_DO_SHORTCUT_NODE or higher). + * + * NB: the command line MUST be preserved until the parsed command is no + * longer required -- no copy is made. + * + * NB: expects to have free run of everything in the vty structure (except + * the contents of the vty_io sub-structure) until the command completes. + * + * See elsewhere for description of parsed structure. + */ +extern cmd_return_code_t +cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type) +{ + cmd_return_code_t ret ; + + /* Level 1 parsing + * + * Break down the tokens into: + * + * 1) in-pipe or command (with or without "do") + * 2) possible out-pipe + * 3) possible comment + * + * Complete any tokens which contain quotes and/or escapes. + * + * If there is a command then: + * + * - sort out any "do" and cut from start of command. + * - set parsed->cnode (from given node or according to "do") + * - set parsed->cmd = NULL + * - empty the argv vector + * + * Note that cmd_parse_phase_one only returns CMD_SUCCESS or CMD_ERR_PARSING. + */ + ret = cmd_parse_phase_one(parsed, type, node) ; + if (ret != CMD_SUCCESS) + { + assert(ret == CMD_ERR_PARSING) ; /* no other error at this point */ + return ret ; + } ; + + /* If no command tokens, and is parsing for execution, then we are done... + * but watch out for bare "do" + */ + if (((parsed->parts & cmd_part_command) == 0) && + ((type & cmd_parse_execution) != 0)) + { + if ((parsed->parts & ~cmd_part_comment) == cmd_parts_none) + return CMD_EMPTY ; /* accept empty */ + + if ((parsed->parts & cmd_part_do) != 0) + return CMD_ERR_INCOMPLETE ; /* reject "do" alone */ + + return CMD_SUCCESS ; /* accept pipes */ + } ; + + /* Level 2 parsing + * + * Try in the current node and then in parent nodes, if can. + * + * Cannot move up the node tree if is already at CONFIG_NODE or below. + * Note that "do" pushes us to the ENABLE_NODE -- which is below the + * CONFIG_NODE. + * + * Note that when not parsing for execution, may get here with no command + * tokens at all -- in which case cmd_parse_phase_two() will return + * CMD_ERR_AMBIGUOUS. + * + * Note that cmd_parse_phase_two only returns CMD_SUCCESS, CMD_ERR_NO_MATCH + * or CMD_ERR_AMBIGUOUS. + */ + while (1) + { + node_type_t pnode ; + + ret = cmd_parse_phase_two(parsed, type) ; + + if (ret == CMD_SUCCESS) + break ; + + if (ret != CMD_ERR_NO_MATCH) + return ret ; + + confirm(ENABLE_NODE < CONFIG_NODE) ; + + if (((type & cmd_parse_no_tree) != 0) || (parsed->cnode <= CONFIG_NODE)) + return CMD_ERR_NO_MATCH ; + + pnode = cmd_node_parent(parsed->cnode) ; + + if (pnode == parsed->cnode) + return CMD_ERR_NO_MATCH ; /* done if no parent node. */ + + parsed->cnode = pnode ; + } ; + + /* Parsed successfully. + * + * If for execution, fill the arg_vector + * + * The arg_vector is an array of pointers to '\0' terminated strings, which + * are pointers to the relevant tokens' qstring bodies. + */ + + if ((type & cmd_parse_execution) != 0) + { + uint ti ; + + cmd_arg_vector_empty(parsed) ; + + for (ti = 0; ti < parsed->num_command ; ti++) { - if (*str == '/') + bool take ; + + if (ti < parsed->cmd->nt_min) { - if (*(str + 1) == '\0') - return partly_match; + cmd_item item = vector_get_item (parsed->cmd->items, ti); - str++; - break; + take = item->arg ; /* follow item */ } - else if (*str == '\0') - return partly_match; - } + else + take = true ; /* option or vararg token */ - if (*str == '\0') - return partly_match; + if (take) + { + cmd_token t ; + t = cmd_token_get(parsed->tokens, parsed->first_command + ti) ; + cmd_arg_vector_push(parsed, cmd_token_make_string(t)) ; + } ; + } ; + } ; - str++; - } + /* Return appropriate form of success */ + return parsed->cmd->daemon ? CMD_SUCCESS_DAEMON + : CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Phase 1 of command parsing + * + * Scan the tokens to break them up into the sections: + * + * - in-pipe -- '<' etc up to '>' or comment + * - do -- leading 'do' on the command (empty if in-pipe) + * - command -- actual command (empty if in-pipe) + * - out-pipe -- '>' etc up to comment + * - comment -- '!' or '#' onwards + * + * Requires line to have been tokenised -- cmd_tokenise(). + * + * Returns: CMD_SUCCESS -- all is well + * CMD_ERR_PARSING -- parsing error -- malformed or misplaced pipe + * -- malformed quotes/escapes + */ +static cmd_return_code_t +cmd_parse_phase_one(cmd_parsed parsed, cmd_parse_type_t type, node_type_t node) +{ + uint nt = parsed->num_tokens ; - sp = str; - while (*str != '\0') + /* Set command and parsing entries */ + parsed->cnode = node ; + parsed->cmd = NULL ; + + /* pick off any comment */ + if ((parsed->tok_total & cmd_tok_comment) != 0) { - if (!isdigit ((int) *str)) - return no_match; + parsed->num_comment = 1 ; + parsed->first_comment = --nt ; /* implicitly the last */ + parsed->tok_total ^= cmd_tok_comment ; + parsed->parts |= cmd_part_comment ; + } ; - str++; + /* If this is not a simple line need to do some extra work: + * + * - identify and check any pipe items + * - + */ + if (parsed->tok_total == cmd_tok_simple) + { + /* All tokens are simple and there is at least one */ + parsed->first_command = 0 ; + parsed->num_command = nt ; + parsed->parts |= cmd_part_command ; } + else if (parsed->tok_total != 0) + { + /* There is at least one not-simple cmd_token. */ + cmd_return_code_t ret ; + ret = cmd_parse_phase_one_b(parsed, nt) ; - if (atoi (sp) > 32) - return no_match; + if (ret != CMD_SUCCESS) + return ret ; + } ; - return exact_match; -} + /* If have a have a 'do' at the front, account for it */ + if ((parsed->parts & cmd_part_command) != 0) + { + /* Have a command -- worry about "do" if allowed */ + if (((type & cmd_parse_no_do) == 0) && (node >= MIN_DO_SHORTCUT_NODE)) + { + cmd_token t ; + t = cmd_token_get(parsed->tokens, parsed->first_command) ; + if (els_len_nn(t->ot) == 2) + { + const char* p = els_body_nn(t->ot) ; + if ((*p == 'd') && (*(p+1) == 'o')) + { + node = ENABLE_NODE ; /* change to this node */ + + parsed->num_do = 1 ; + parsed->first_do = parsed->first_command ; + --parsed->num_command ; + ++parsed->first_command ; + parsed->parts |= cmd_part_do ; + /* If have *only* the "do", we don't have a command */ + if (parsed->num_command == 0) + parsed->parts ^= cmd_part_command ; + } ; + } ; + } ; + } ; + + return CMD_SUCCESS ; +} ; /*------------------------------------------------------------------------------ - * Is this an IPv6 Address: + * Phase 1b of command parsing + * + * Tokeniser found at least one of: + * + * - in pipe token + * - out pipe token + * - token with quotes or escapes * - * TODO: cmd_ipv6_match() only returns "partly_match" for empty string ? + * Deal with all of those, verifying the syntax of any pipes and completing + * any tokens with quotes etc. * - * Returns: no_match -- improperly formed - * partly_match -- accepts empty string - * exact_match -- syntactically complete + * Update the "parts" and the relevant first/num values. + * + * Returns: CMD_SUCCESS -- all is well + * CMD_ERR_PARSING -- parsing error -- malformed or misplaced pipe + * -- malformed quotes/escapes */ +static cmd_return_code_t +cmd_parse_phase_one_b(cmd_parsed parsed, uint nt) +{ + cmd_return_code_t ret ; + cmd_token t ; + cmd_parts_t parts ; + uint i, n ; + uint* pn ; -#define IPV6_ADDR_STR "0123456789abcdefABCDEF:.%" -#define IPV6_PREFIX_STR "0123456789abcdefABCDEF:.%/" -#define STATE_START 1 -#define STATE_COLON 2 -#define STATE_DOUBLE 3 -#define STATE_ADDR 4 -#define STATE_DOT 5 -#define STATE_SLASH 6 -#define STATE_MASK 7 + parts = cmd_parts_none ; /* no parts yet */ + n = 0 ; /* no tokens in current part */ + pn = NULL ; /* no current part */ -#ifdef HAVE_IPV6 + for (i = 0 ; i < nt ; ++i) + { + t = cmd_token_get(parsed->tokens, i) ; -extern match_type_t -cmd_ipv6_match (const char *str) -{ - int state = STATE_START; - int colons = 0, nums = 0, double_colon = 0; - const char *sp = NULL; - struct sockaddr_in6 sin6_dummy; - int ret; + if ((t->type & cmd_tok_simple) != 0) + { + if (parts == cmd_parts_none) + { + parts = cmd_part_command ; + parsed->first_command = i ; + pn = &parsed->num_command ; + n = 0 ; + } ; + } ; - if (str == NULL) - return partly_match; + if ((t->type & cmd_tok_in_pipe) != 0) + { + if (parts != cmd_parts_none) + return cmd_parse_error(parsed, t, 0, "unexpected 'pipe in'") ; - if (strspn (str, IPV6_ADDR_STR) != strlen (str)) - return no_match; + ret = cmd_parse_in_pipe(parsed, t) ; + if (ret != CMD_SUCCESS) + return ret ; - /* use inet_pton that has a better support, - * for example inet_pton can support the automatic addresses: - * ::1.2.3.4 - */ - ret = inet_pton(AF_INET6, str, &sin6_dummy.sin6_addr); + parts = cmd_part_in_pipe ; + parsed->first_in_pipe = i ; + pn = &parsed->num_in_pipe ; + n = 0 ; + } ; + + if ((t->type & cmd_tok_out_pipe) != 0) + { + if ((parts == cmd_parts_none) || ((parts & cmd_part_out_pipe) != 0)) + return cmd_parse_error(parsed, t, 0, "unexpected 'pipe out'") ; + + ret = cmd_parse_out_pipe(parsed, t) ; + if (ret != CMD_SUCCESS) + return ret ; + + *pn = n ; /* set number of in-pipe/cmd */ + + parts |= cmd_part_out_pipe ; + parsed->first_out_pipe = i ; + pn = &parsed->num_out_pipe ; + n = 0 ; + } ; + + assert(parts != cmd_parts_none) ; /* dealt with all token types */ + + if ((t->type & cmd_tok_incomplete) != 0) + { + ret = cmd_token_complete(parsed, t) ; + if (ret != CMD_SUCCESS) + return ret ; + } ; + + ++n ; /* count up tokens */ + } ; - if (ret == 1) - return exact_match; + if (pn != NULL) + *pn = n ; /* number in last phase */ - while (*str != '\0') + /* If have an in-pipe or an out-pipe, worry about the number of + * arguments + */ + if ((parts & cmd_parts_pipe) != 0) { - switch (state) + const char* msg = NULL ; + uint i ; + bool e ; + + /* If there is an in_pipe part, check number of arguments */ + if ((msg == NULL) && ((parts & cmd_part_in_pipe) != 0)) { - case STATE_START: - if (*str == ':') + assert(parsed->num_in_pipe > 0) ; + + if (((parsed->in_pipe & cmd_pipe_file) != 0) + && (parsed->num_in_pipe != 2)) { - if (*(str + 1) != ':' && *(str + 1) != '\0') - return no_match; - colons--; - state = STATE_COLON; - } - else + if (parsed->num_in_pipe == 1) + { + i = parsed->first_in_pipe ; + e = true ; + msg = "requires file" ; + } + else + { + i = parsed->first_in_pipe + 2 ; + e = false ; + msg = "expects file only" ; + } ; + } ; + + if (((parsed->in_pipe & cmd_pipe_shell) != 0) + && (parsed->num_in_pipe < 2)) { - sp = str; - state = STATE_ADDR; - } + i = parsed->first_in_pipe ; + e = true ; + msg = "requires shell command" ; + } ; + } ; - continue; - case STATE_COLON: - colons++; - if (*(str + 1) == ':') - state = STATE_DOUBLE; - else + /* If there is an out_pipe part, check the number of arguments */ + if ((msg == NULL) && ((parts & cmd_part_out_pipe) != 0)) + { + assert(parsed->num_out_pipe > 0) ; + + if (((parsed->out_pipe & cmd_pipe_file) != 0) + && (parsed->num_out_pipe != 2)) { - sp = str + 1; - state = STATE_ADDR; - } - break; - case STATE_DOUBLE: - if (double_colon) - return no_match; + if (parsed->num_out_pipe == 1) + { + i = parsed->first_out_pipe ; + e = true ; + msg = "requires file" ; + } + else + { + i = parsed->first_out_pipe + 2 ; + e = false ; + msg = "expects file only" ; + } ; + } ; - if (*(str + 1) == ':') - return no_match; - else + if (((parsed->out_pipe & cmd_pipe_shell) != 0) + && (parsed->num_out_pipe < 2)) { - if (*(str + 1) != '\0') - colons++; - sp = str + 1; - state = STATE_ADDR; - } + assert(parsed->num_out_pipe > 0) ; + + i = parsed->first_out_pipe ; + e = true ; + msg = "requires shell command" ; + } ; + } ; + + if (msg != NULL) + { + t = cmd_token_get(parsed->tokens, i) ; + return cmd_parse_error(parsed, t, e ? els_len_nn(t->ot) : 0, msg) ; + } ; + } ; + + /* It's OK -- so record the parts found and return success */ + parsed->parts |= parts ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Phase 2 of command parsing -- parsing for execution. + * + * Assumes phase 1 completed successfully. + * + * Note that if parsed->num_command == 0, will have constructed a cmd_v, with + * all possible commands in it (depending on cmd_parse_execution). + * + * Returns: CMD_SUCCESS -- parsed successfully + * CMD_ERR_NO_MATCH -- could find nothing that matches + * CMD_ERR_AMBIGUOUS -- found more than one match + * or parsed->num_command == 0 + */ +static cmd_return_code_t +cmd_parse_phase_two(cmd_parsed parsed, cmd_parse_type_t type) +{ + uint ii ; + uint match ; + + /* Prepare to filter commands */ + + cmd_filter_prepare(parsed, type) ; + + match = 2 ; /* in case parsed->num_command == 0 ! */ + + for (ii = 0 ; ii < parsed->num_command ; ii++) + match = cmd_filter(parsed, ii, false) ; + + /* Should end up with one command to execute. */ + if (match == 0) + return CMD_ERR_NO_MATCH ; + + if (match > 1) + return CMD_ERR_AMBIGUOUS ; + + parsed->cmd = vector_get_item(parsed->cmd_v, 0) ; + + return CMD_SUCCESS ; +} ; + +#if 0 +/*------------------------------------------------------------------------------ + * If src matches dst return dst string, otherwise return NULL + * + * Returns NULL if dst is an option, variable of vararg. + * + * NULL or empty src are deemed to match. + */ +static const char * +cmd_entry_function (const char *src, const char *dst) +{ + if (CMD_OPTION (dst) || CMD_VARIABLE (dst) || CMD_VARARG (dst)) + return NULL; + + if ((src == NULL) || (*src == '\0')) + return dst; + + if (strncmp (src, dst, strlen (src)) == 0) + return dst; + + return NULL; +} + +/* If src matches dst return dst string, otherwise return NULL */ +/* This version will return the dst string always if it is + CMD_VARIABLE for '?' key processing */ +static const char * +cmd_entry_function_item (const char *src, const char *dst) +{ + if (CMD_VARARG (dst)) + return dst; + + if (CMD_RANGE (dst)) + { + if (cmd_range_match (dst, src)) + return dst; + else + return NULL; + } + +#ifdef HAVE_IPV6 + if (CMD_IPV6 (dst)) + { + if (cmd_ipv6_match (src)) + return dst; + else + return NULL; + } + + if (CMD_IPV6_PREFIX (dst)) + { + if (cmd_ipv6_prefix_match (src)) + return dst; + else + return NULL; + } +#endif /* HAVE_IPV6 */ + + if (CMD_IPV4 (dst)) + { + if (cmd_ipv4_match (src)) + return dst; + else + return NULL; + } + + if (CMD_IPV4_PREFIX (dst)) + { + if (cmd_ipv4_prefix_match (src)) + return dst; + else + return NULL; + } + + /* Optional or variable commands always match on '?' */ + if (CMD_OPTION (dst) || CMD_VARIABLE (dst)) + return dst; + + /* In case of 'command \t', given src is NULL string. */ + if (src == NULL) + return dst; + + if (strncmp (src, dst, strlen (src)) == 0) + return dst; + else + return NULL; +} + +/*------------------------------------------------------------------------------ + * Check same string element existence. + * + * Returns: 0 => found same string in the vector + * 1 => NOT found same string in the vector + */ +static bool +cmd_unique_string (vector v, const char *str) +{ + unsigned int i; + char *match; + + for (i = 0; i < vector_length (v); i++) + if ((match = vector_get_item (v, i)) != NULL) + if (strcmp (match, str) == 0) + return 0; + return 1; +} + +/* Compare string to description vector. If there is same string + return 1 else return 0. */ +static bool +item_unique_string (vector v, const char *str) +{ + unsigned int i; + struct cmd_item *item; + + for (i = 0; i < vector_length (v); i++) + if ((item = vector_get_item (v, i)) != NULL) + if (strcmp (item->cmd, str) == 0) + return 1; + return 0; +} +#endif +/*============================================================================== + * '?' describe command support. + */ + +/*------------------------------------------------------------------------------ + * Get description of current (partial) command + * + * Returns: NULL => no description available + * + * status set to CMD_ERR_NO_MATCH or CMD_ERR_AMBIGUOUS + * + * or: address of vector of "struct cmd_item" values available. + * + * NB: when a vector is returned it is the caller's responsibility to + * vector_free() it. (The contents are all effectively const, so do not + * themselves need to be freed.) + */ +extern vector +cmd_describe_command (const char* line, node_type_t node, + cmd_return_code_t* status) +{ +#if 0 + vector ret ; + struct cmd_parsed parsed_s ; + cmd_parsed parsed ; + cmd_token_type_t tok_total ; + + /* Set up a parser object and tokenise the command line */ + parsed = cmd_parse_init_new(&parsed_s) ; + tok_total = cmd_tokenise(parsed, line, node) ; + - double_colon++; - nums++; - break; - case STATE_ADDR: - if (*(str + 1) == ':' || *(str + 1) == '\0') + + + + + + /* Level 1 parsing + * + * Strip quotes and escapes from all the tokens. + */ + if (tok_total != cmd_tok_simple) + { + ret = cmd_parse_phase_one(parsed) ; + if (ret != CMD_SUCCESS) + return ret ; + } ; + + /* If allowed to 'do', see if there. + * + * 'do' forces command to be parsed in ENABLE_NODE (if allowed) + */ + if ((type & cmd_parse_no_do) == 0) + cmd_try_do_shortcut(parsed) ; + + + + + return cmd_describe_command_real (tokens, node, status); + + + + static vector + cmd_describe_command_real (vector tokens, int node, int *status) + { + unsigned int i; + vector cmd_vector; + #define INIT_MATCHVEC_SIZE 10 + vector matchvec; + struct cmd_command *cmd_command; + unsigned int index; + int ret; + enum match_type match; + char *command; + + /* Set index. */ + if (vector_length (tokens) == 0) + { + *status = CMD_ERR_NO_MATCH; + return NULL; + } + else + index = vector_length (tokens) - 1; + + /* Make copy vector of current node's command vector. */ + cmd_vector = vector_copy (cmd_node_vector (cmdvec, node)); + + /* Prepare match vector */ + matchvec = vector_init (INIT_MATCHVEC_SIZE); + + /* Filter commands. */ + /* Only words precedes current word will be checked in this loop. */ + for (i = 0; i < index; i++) + if ((command = vector_get_item (tokens, i))) + { + match = cmd_filter(command, cmd_vector, i, any_match) ; + + if (match == vararg_match) { - if (str - sp > 3) - return no_match; + struct cmd_command *cmd_command; + vector descvec; + unsigned int j, k; + + for (j = 0; j < vector_length (cmd_vector); j++) + if ((cmd_command = vector_get_item (cmd_vector, j)) != NULL + && (vector_length (cmd_command->items))) + { + descvec = vector_get_item (cmd_command->items, + vector_length (cmd_command->items) - 1); + for (k = 0; k < vector_length (descvec); k++) + { + struct cmd_item *item = vector_get_item (descvec, k); + vector_set (matchvec, item); + } + } + + vector_set (matchvec, &item_cr); + vector_free (cmd_vector); + + return matchvec; + } ; - nums++; - state = STATE_COLON; - } - if (*(str + 1) == '.') - state = STATE_DOT; - break; - case STATE_DOT: - state = STATE_ADDR; - break; - default: - break; + ret = is_cmd_ambiguous (command, cmd_vector, i, match) ; + if (ret != 0) + { + vector_free (cmd_vector); + vector_free (matchvec); + *status = (ret == 1) ? CMD_ERR_AMBIGUOUS + : CMD_ERR_NO_MATCH ; + return NULL ; + } ; } - if (nums > 8) - return no_match; + /* Prepare match vector */ + /* matchvec = vector_init (INIT_MATCHVEC_SIZE); */ - if (colons > 7) - return no_match; + /* Make sure that cmd_vector is filtered based on current word */ + command = vector_get_item (tokens, index); + if (command) + match = cmd_filter(command, cmd_vector, index, any_match); - str++; - } + /* Make description vector. */ + for (i = 0; i < vector_length (cmd_vector); i++) + { + vector items ; + + cmd_command = vector_get_item (cmd_vector, i) ; + if (cmd_command == NULL) + continue ; + + /* Ignore cmd_command if no tokens at index position. + * + * Deal with special case of possible <cr> completion. + */ + items = cmd_command->items; + if (index >= vector_length (items)) + { + if (command == NULL && index == vector_length (items)) + { + if (!item_unique_string (matchvec, cr_string)) + vector_push_item(matchvec, &item_cr); + } + continue ; + } ; + + /* Check if command is completed. */ + unsigned int j; + vector descvec = vector_get_item (items, index); + struct cmd_item *item; + + for (j = 0; j < vector_length (descvec); j++) + if ((item = vector_get_item (descvec, j))) + { + const char *string; + + string = cmd_entry_function_desc (command, item->cmd); + if (string) + { + /* Uniqueness check */ + if (!item_unique_string (matchvec, string)) + vector_push_item(matchvec, item); + } + } ; + } ; + + vector_free (cmd_vector); + + if (vector_length(matchvec) == 0) + { + vector_free (matchvec); + *status = CMD_ERR_NO_MATCH; + return NULL; + } + + *status = CMD_SUCCESS; + return matchvec; + } + + cmd_parse_reset(parsed, false) ; + + return +#endif + + return NULL ; +} ; #if 0 - if (nums < 11) - return partly_match; -#endif /* 0 */ +/*------------------------------------------------------------------------------ + * Check LCD of matched command. + * + * Scan list of matched keywords, and by comparing them pair-wise, find the + * longest common leading substring. + * + * Returns: 0 if zero or one matched keywords + * length of longest common leading substring, otherwise. + */ +static int +cmd_lcd (vector matchvec) +{ + int n ; + int i ; + int lcd ; + char *sp, *sq, *ss ; - return exact_match; + n = vector_end(matchvec) ; + if (n < 2) + return 0 ; + + ss = vector_get_item(matchvec, 0) ; + lcd = strlen(ss) ; + + for (i = 1 ; i < n ; i++) + { + sq = ss ; + ss = vector_get_item(matchvec, i) ; + sp = ss ; + + while ((*sp == *sq) && (*sp != '\0')) + { + ++sp ; + ++sq ; + } ; + + if (lcd > (sp - ss)) + lcd = (sp - ss) ; + } + return lcd; } +#endif + +/*============================================================================== + * Command line help support. + * + * + */ + +static bool cmd_post_last(cmd_parsed parsed) ; + + /*------------------------------------------------------------------------------ - * Is this an IPv6 Prefix: + * Look out for the following special cases: * - * TODO: cmd_ipv6_prefix_match() hardly returns "partly_match" ? - * TODO: cmd_ipv6_prefix_match() possibly accepts invalid address before '/' ? + * a) before or on '#' or '!' * - * Returns: no_match -- improperly formed - * partly_match -- accepts empty string - * exact_match -- syntactically complete + * '#' and '!' are only recognised as the start of a comment at the + * start of a line... So can insert something in front, and what + * currently looks like a comment, suddenly needs to be reconsidered + * as tokens... the first of which happens to start with '!' or '#'. * - * NB: partly_match is returned for anything valid before the '/', but which - * has no '/' or no number after the '/'. + * To save work, and leave open the possibility of trailing comments, + * we treat this case as an "error". + * + * b) before or on '<' + * + * currently, pipe-in must be the first token on the command line. + * + * So if there is anything prior to the '<', treat that as an error. + * If is just spaces before the '<', say that nothing may be placed + * before the '<' (cf nothing may be placed before '!' or '#'). + * + * in these cases no help is available -- return message explaining. + * + * NB: assumes that have already done cmd_token_position(), and therefore that + * if there is a '#' or '<' that cannot be positioned *after* it, or + * indeed that can be positioned after a '>'. */ -extern match_type_t -cmd_ipv6_prefix_match (const char *str) +extern const char* +cmd_help_preflight(cmd_parsed parsed) { - int state = STATE_START; - int colons = 0, nums = 0, double_colon = 0; - int mask; - const char *sp = NULL; - char *endptr = NULL; + if ((parsed->tok_total & cmd_tok_comment) != 0) + { + return "cannot have a command on a comment line"; + } ; + + if ((parsed->tok_total & cmd_tok_in_pipe) != 0) + { + return "cannot have a command and an '<' pipe together" ; + } ; - if (str == NULL) - return partly_match; + return NULL ; /* OK ! */ +} ; - if (strspn (str, IPV6_PREFIX_STR) != strlen (str)) - return no_match; - while (*str != '\0' && state != STATE_MASK) + + +/*------------------------------------------------------------------------------ + * See if current command line can be completed, and if so, how. + * + * NB: must already have done cmd_token_position(). + * + * Must not be called if cmd_token_position() reported a "special". + * + * Returns: CMD_ERR_PARSING -- the parser could not make sense of the line. + * + */ +extern cmd_return_code_t +cmd_completion(cmd_parsed parsed, node_type_t node) +{ + cmd_return_code_t ret ; + + /* Parse the line -- allow completion, allow do, allow backing up the tree, + * but do not parse for execution. + */ + ret = cmd_parse_command(parsed, node, cmd_parse_standard) ; + + if (ret == CMD_ERR_PARSING) + return ret ; /* nothing more possible */ + + /* Expect now to have a cmd_v set with the result of the filtering, + * with 0, 1 or more possible commands in it. + * + * Now establish what the alternatives are at the current token position. + * + * We do this by filtering again, on the current token position, and + * asking the filter to collect all the possible items. Note that if the + * current token position is beyond the last actual command token, then + * will be filtering on the empty eol token -- which we arrange to match + * anything, including eol -- which gives the dummy item_cr of type + * item_eol. + */ + assert(parsed->cti >= parsed->first_command) ; + assert(parsed->cti <= parsed->first_command + parsed->num_command) ; + + cmd_filter(parsed, parsed->cti - parsed->first_command, true) ; + + /* Reduce the list of items available at the current position to + * eliminate duplicates and have the possibilities sorted by type and + * by value. + */ + if (vector_length(parsed->item_v) > 1) + { + cmd_item prev ; + uint ii ; + uint i_keep ; + + vector_sort(parsed->item_v, (vector_sort_cmp*)cmd_cmp_item) ; + + i_keep = 1 ; + + prev = vector_get_item(parsed->item_v, 0) ; + for (ii = 1 ; ii < vector_length(parsed->item_v) ; ++ii) + { + cmd_item item ; + + item = vector_get_item(parsed->item_v, ii) ; + if (cmd_cmp_item(&item, &prev) != 0) + { + vector_set_item(parsed->item_v, i_keep++, item) ; + prev = item ; + } ; + } ; + + vector_set_length(parsed->item_v, i_keep) ; + /* discard what did not keep */ + } ; + + return CMD_SUCCESS ; +} ; + + + + +/*------------------------------------------------------------------------------ + * How to insert a newly completed keyword ? + * + * There are a number of cases: + * + * 1) the cti is for the token to be completed. + * + * May be positioned one or more spaces in front of the keyword. + * + * Will replace any leading spaces and the keyword by the new keyword. + * + * 2) the cti is for the empty eol token + * + * Will replace any leading spaces to the eol keyword, by the new + * keyword. + * + * 3) the cti is for a '>' token, or any other !cmd_tok_simple. + * + * If cti is not exactly on the token, then insert the keyword exactly + * where it is -- no spaces need to be removed or added. + * + * If the cti is on the token, insert the keyword and one space. + * + * The second issue is where to leave the cursor... if the command is now + * complete, wish to leave the cursor at the end of the newly completed + * keyword. Otherwise: + * + * 1) if the next token is a cmd_tok_simple, move to it. + * + * 2) otherwise if there is already a space after the newly completed + * keyword, move past it. + * + * Otherwise, insert a space. + * + * Returns: n = number of characters to move the cursor before + * starting to replace characters (may be -ve) + * *rep = number of characters to replace + * *ins = number of spaces to insert + * *mov = number of spaces to move afterwards (may be -ve) + */ +extern void +cmd_complete_keyword(cmd_parsed parsed, int* pre, int* rep, int* ins, int* mov) +{ + cmd_token t, nt ; + bool last ; + int gap ; + + /* Is this the last token possible on this command line ? */ + last = cmd_post_last(parsed) ; + + /* Get the token we are operating and the following one (if any). + * + * Calculate gap between end of token to be replaced and start of next, + * if any. + * + * gap == 0 => is at eol or on !cmd_tok_simple. + */ + t = cmd_token_get(parsed->tokens, parsed->cti) ; + if (parsed->cti < parsed->num_tokens) + { + nt = cmd_token_get(parsed->tokens, parsed->cti + 1) ; + gap = nt->tp - (t->tp + els_len_nn(t->ot)) ; + } + else + { + nt = NULL ; + gap = (parsed->rp < 0) ? -parsed->rp : 0 ; + } ; + + /* Now work out what we need to do. */ + + *pre = 0 ; /* preset values */ + *rep = 0 ; + *ins = 0 ; + *mov = 0 ; + + if ((t->type & cmd_tok_simple) != 0) { - switch (state) + /* Replacing an existing simple token ------------------------------ + * + * Move to start of token and replace it. + */ + *pre = -parsed->rp ; + *rep = parsed->ctl ; + + /* now what do we do after the token ? */ + assert(nt != NULL) ; + + if ((nt->type & cmd_tok_simple) != 0) { - case STATE_START: - if (*str == ':') + /* Next token is simple -- step to it */ + assert(!last) ; + + *mov = gap ; + } + else if (nt->type == cmd_tok_eol) + { + /* Next token is eol + * + * gap is the number of spaces there will be after the + * newly replaced token. + */ + if (!last) { - if (*(str + 1) != ':' && *(str + 1) != '\0') - return no_match; - colons--; - state = STATE_COLON; + if (gap == 0) + *ins = 1 ; /* need a trailing space */ + else + *mov = 1 ; /* step over existing space */ + } ; + } + else + { + /* Next token is something special. + * + * gap is the number of spaces there will be after the + * newly replaced token. + */ + if (gap == 0) + { + *ins = last ? 1 : 2 ; + *mov = -1 ; } - else + else if (gap == 1) { - sp = str; - state = STATE_ADDR; + *ins = last ? 0 : 1 ; } - - continue; - case STATE_COLON: - colons++; - if (*(str + 1) == '/') - return no_match; - else if (*(str + 1) == ':') - state = STATE_DOUBLE; else { - sp = str + 1; - state = STATE_ADDR; + *mov = last ? 0 : 1 ; } - break; - case STATE_DOUBLE: - if (double_colon) - return no_match; + } ; + } + + else if (t->type == cmd_tok_eol) + { + /* Inserting at or before eol -------------------------------------- + * + * Insert exactly where we are -- *pre == 0 + */ + + /* If last, replace any spaces between cursor and end of line, + * otherwise, keep one of them. + */ + *rep = last ? gap : (gap > 0) ? gap - 1 : gap ; - if (*(str + 1) == ':') - return no_match; + /* now what do we do after the token ? */ + assert(nt == NULL) ; + + if (!last) + { + if (gap == 0) + *ins = 1 ; /* need space after */ else - { - if (*(str + 1) != '\0' && *(str + 1) != '/') - colons++; - sp = str + 1; + *mov = 1 ; /* step over one */ + } ; + } + else + { + /* Inserting at or before something special ------------------------ + * + * Insert exactly where we are -- *pre == 0 + * replace nothing -- *rep == 0 + */ + if (gap == 0) + { + *ins = last ? 1 : 2 ; + *mov = -1 ; + } + else if (gap == 1) + { + *ins = last ? 0 : 1 ; + } + else + { + *mov = last ? 0 : 1 ; + } + } ; +} ; - if (*(str + 1) == '/') - state = STATE_SLASH; - else - state = STATE_ADDR; - } +/*------------------------------------------------------------------------------ + * Do we have just one command left, and have we just filtered on the last + * possible command item & token ? + * + * If so, then there is no point filtering any further, and there is nothing + * more that could be added to this command line. + */ +static bool +cmd_post_last(cmd_parsed parsed) +{ + if (vector_length(parsed->cmd_v) == 1) + { + cmd_command cmd ; + cmd = vector_get_item(parsed->cmd_v, 0) ; - double_colon++; - nums += 1; - break; - case STATE_ADDR: - if (*(str + 1) == ':' || *(str + 1) == '.' - || *(str + 1) == '\0' || *(str + 1) == '/') - { - if (str - sp > 3) - return no_match; + return ((parsed->cti - parsed->first_command + 1) == cmd->nt_max) ; + } ; + + return false ; +} ; - for (; sp <= str; sp++) - if (*sp == '/') - return no_match; - nums++; - if (*(str + 1) == ':') - state = STATE_COLON; - else if (*(str + 1) == '.') - state = STATE_DOT; - else if (*(str + 1) == '/') - state = STATE_SLASH; - } - break; - case STATE_DOT: - state = STATE_ADDR; - break; - case STATE_SLASH: - if (*(str + 1) == '\0') - return partly_match; - - state = STATE_MASK; - break; - default: - break; + + + + + + + + + + + + + + +#if 0 +static vector +cmd_complete_command_real (vector tokens, int node, int *status) +{ + unsigned int i; + unsigned int ivl ; + unsigned int last_ivl ; + vector cmd_v ; +#define INIT_MATCHVEC_SIZE 10 + vector matchvec; + struct cmd_command *cmd_command; + unsigned int index; + struct cmd_item *cmd_item; + vector descvec; + char *token; + int n ; + + /* Stop immediately if the tokens is empty. */ + if (vector_length (tokens) == 0) + { + *status = CMD_ERR_NO_MATCH; + return NULL; + } + + /* Take (shallow) copy of cmdvec for given node. */ + cmd_v = vector_copy (cmd_node_vector (cmdvec, node)); + + /* First, filter upto, but excluding last token */ + last_ivl = vector_length (tokens) - 1; + + for (ivl = 0; ivl < last_ivl; ivl++) + { + enum match_type match; + int ret; + + /* TODO: does this test make any sense ? */ + if ((token = vector_get_item (tokens, ivl)) == NULL) + continue ; + + /* First try completion match, return best kind of match */ + index = ivl ; + match = cmd_filter_by_completion (token, cmd_v, index) ; + + /* Eliminate all but the selected kind of match */ + ret = is_cmd_ambiguous (token, cmd_v, index, match) ; + + if (ret == 1) + { + /* ret == 1 => either token matches more than one keyword + * or token matches more than one number range + */ + vector_free (cmd_v); + *status = CMD_ERR_AMBIGUOUS; + return NULL; } +#if 0 + /* For command completion purposes do not appear to care about + * incomplete ipv4 or ipv6 prefixes (missing '/' or digits after). + */ + else if (ret == 2) + { + vector_free (cmd_v); + *status = CMD_ERR_NO_MATCH; + return NULL; + } +#endif + } + + /* Prepare match vector. */ + matchvec = vector_init (INIT_MATCHVEC_SIZE); + + /* Now we got into completion */ + index = last_ivl ; + token = vector_get_item(tokens, last_ivl) ; /* is now the last token */ + + for (i = 0; i < vector_length (cmd_v); i++) + { + unsigned int j; + const char *string; - if (nums > 11) - return no_match; + if ((cmd_command = vector_get_item (cmd_v, i)) == NULL) + continue ; - if (colons > 7) - return no_match; + descvec = vector_get_item (cmd_command->items, index); + if (descvec == NULL) + continue ; - str++; + for (j = 0; j < vector_length (descvec); j++) + { + item = vector_get_item (descvec, j) ; + if (item == NULL) + continue ; + + string = cmd_entry_function(token, item->str) ; + if ((string != NULL) && cmd_unique_string(matchvec, string)) + cmd_add_to_strvec (matchvec, string) ; + } ; + } ; + + n = vector_length(matchvec) ; /* number of entries in the matchvec */ + + /* We don't need cmd_v any more. */ + vector_free (cmd_v); + + /* No matched command */ + if (n == 0) + { + vector_free (matchvec); + + /* In case of 'command \t' pattern. Do you need '?' command at + the end of the line. */ + if (*token == '\0') + *status = CMD_COMPLETE_ALREADY; + else + *status = CMD_ERR_NO_MATCH; + return NULL; } - if (state < STATE_MASK) - return partly_match; + /* Only one matched */ + if (n == 1) + { + *status = CMD_COMPLETE_FULL_MATCH; + return matchvec ; + } - mask = strtol (str, &endptr, 10); - if (*endptr != '\0') - return no_match; + /* Check LCD of matched strings. */ + if (token != NULL) + { + unsigned lcd = cmd_lcd (matchvec) ; + + if (lcd != 0) + { + if (strlen(token) < lcd) + { + char *lcdstr; - if (mask < 0 || mask > 128) - return no_match; + lcdstr = XMALLOC (MTYPE_STRVEC, lcd + 1); + memcpy (lcdstr, vector_get_item(matchvec, 0), lcd) ; + lcdstr[lcd] = '\0'; -/* I don't know why mask < 13 makes command match partly. - Forgive me to make this comments. I Want to set static default route - because of lack of function to originate default in ospf6d; sorry - yasu - if (mask < 13) - return partly_match; -*/ + cmd_free_strvec(matchvec) ; /* discard the match vector */ - return exact_match; -} + matchvec = vector_init (1); + vector_push_item(matchvec, lcdstr) ; -#endif /* HAVE_IPV6 */ + *status = CMD_COMPLETE_MATCH; + return matchvec ; + } + } + } + + *status = CMD_COMPLETE_LIST_MATCH; + return matchvec ; +} +#endif /*------------------------------------------------------------------------------ - * Is this a decimal number in the allowed range: - * - * Returns: true <=> OK -- *including* empty string - * false => not a valid number, or not in required range - * (or invalid range !!) + * Can the current command be completed ? */ +extern vector +cmd_complete_command (vector tokens, int node, int *status) +{ +#if 0 + vector ret; -#define DECIMAL_STRLEN_MAX 10 + if ( cmd_try_do_shortcut(node, vector_get_item(tokens, 0) ) ) + { + vector shifted_tokens; + unsigned int index; -extern bool -cmd_range_match (const char *range, const char *str) -{ - char *p; - char buf[DECIMAL_STRLEN_MAX + 1]; - char *endptr = NULL; - unsigned long min, max, val; + /* We can try it on enable node, cos' the vty is authenticated */ - if (str == NULL) - return true ; + shifted_tokens = vector_init (vector_count(tokens)); + /* use memcpy? */ + for (index = 1; index < vector_length (tokens); index++) + { + vector_set_index (shifted_tokens, index-1, + vector_lookup(tokens, index)) ; + } - val = strtoul (str, &endptr, 10); - if (*endptr != '\0') - return false ; + ret = cmd_complete_command_real (shifted_tokens, ENABLE_NODE, status); + + vector_free(shifted_tokens); + return ret; + } + + return cmd_complete_command_real (tokens, node, status); +#endif + + *status = CMD_ERR_NO_MATCH; + return NULL; +} ; - range++; - p = strchr (range, '-'); - if (p == NULL) - return false ; - if (p - range > DECIMAL_STRLEN_MAX) - return false ; - strncpy (buf, range, p - range); - buf[p - range] = '\0'; - min = strtoul (buf, &endptr, 10); - if (*endptr != '\0') - return false ; - range = p + 1; - p = strchr (range, '>'); - if (p == NULL) - return false ; - if (p - range > DECIMAL_STRLEN_MAX) - return false ; - strncpy (buf, range, p - range); - buf[p - range] = '\0'; - max = strtoul (buf, &endptr, 10); - if (*endptr != '\0') - return false ; - if (val < min || val > max) - return false ; - return true ; -} diff --git a/lib/command_parse.h b/lib/command_parse.h index ab91e7f4..789fe601 100644 --- a/lib/command_parse.h +++ b/lib/command_parse.h @@ -24,13 +24,15 @@ #ifndef _ZEBRA_COMMAND_PARSE_H #define _ZEBRA_COMMAND_PARSE_H -#include <zebra.h> +#include "zconfig.h" /* HAVE_IPV6 */ #include "misc.h" -#include "node_type.h" +#include "command_common.h" #include "vector.h" #include "qstring.h" +#include "elstring.h" +#if 0 /*============================================================================== * Parsing of tokens */ @@ -51,42 +53,397 @@ enum cmd_token_spec cmd_ts_ipv6_prefix, cmd_ts_vararg, +} ; +typedef enum cmd_token_spec cmd_token_spec_t ; +#endif + +/*============================================================================== + * Sexing of token types in command descriptions + */ +#define CMD_OPTION(S) ((S[0]) == '[') +#define CMD_VARIABLE(S) (((S[0]) >= 'A' && (S[0]) <= 'Z') || ((S[0]) == '<')) +#define CMD_VARARG(S) ((S[0]) == '.') +#define CMD_RANGE(S) ((S[0] == '<')) + +#define CMD_IPV4(S) ((strcmp ((S), "A.B.C.D") == 0)) +#define CMD_IPV4_PREFIX(S) ((strcmp ((S), "A.B.C.D/M") == 0)) +#define CMD_IPV6(S) ((strcmp ((S), "X:X::X:X") == 0)) +#define CMD_IPV6_PREFIX(S) ((strcmp ((S), "X:X::X:X/M") == 0)) + +/*============================================================================== + * Command Items. + * + * A command is compiled into a vector of lists of command items -- the + * cmd_command->items (so called). + * + */ + +/* Command item types + * + * NB: the command items sort according to this -- highest first. + * + * NB: this is in the same order as the match_type -- and should remain so. + * See match_type below for further discussion of the hierarchy. + */ +enum cmd_item_type +{ + item_null, + + item_eol, + item_option_word, + item_vararg, /* rest of the line */ + item_word, + item_ipv6_prefix, + item_ipv6_address, + item_ipv4_prefix, + item_ipv4_address, + + item_range, + + item_keyword, + + item_type_count, /* number of types */ +} ; +typedef enum cmd_item_type cmd_item_type_t ; + +enum { + item_max_number = 0xFFFFFFFF /* can be +/- this */ +} ; + +CONFIRM(LONG_MAX >= item_max_number) ; + +/*------------------------------------------------------------------------------ + * Sex cmd_item_type and return whether it is an "option" type, or not. + * + * NB: tolerates item_null -- others may well not. + */ +Inline bool +cmd_item_is_option(cmd_item_type_t itt) +{ + const static bool is_option[item_type_count] = + { + [item_null] = false, + + [item_eol] = false, + + [item_option_word] = true, + + [item_vararg] = false, + [item_word] = false, + + [item_ipv6_prefix] = false, + [item_ipv6_address] = false, + [item_ipv4_prefix] = false, + [item_ipv4_address] = false, + + [item_range] = false, + + [item_keyword] = false, + } ; + + assert((itt >= 0) && (itt < item_type_count)) ; + + return is_option[itt] ; +} ; + +/*------------------------------------------------------------------------------ + * Sex cmd_item_type and return whether it is an "vararg" type, or not. + * + * NB: tolerates item_null -- others may well not. + */ +Inline bool +cmd_item_is_vararg(cmd_item_type_t itt) +{ + const static bool is_vararg[item_type_count] = + { + [item_null] = false, + + [item_eol] = false, + + [item_option_word] = false, + + [item_vararg] = true, + [item_word] = false, + + [item_ipv6_prefix] = false, + [item_ipv6_address] = false, + [item_ipv4_prefix] = false, + [item_ipv4_address] = false, + + [item_range] = false, + + [item_keyword] = false, + } ; + + assert((itt >= 0) && (itt < item_type_count)) ; + + return is_vararg[itt] ; +} ; + +/*------------------------------------------------------------------------------ + * The command item structure. + */ +typedef struct cmd_item* cmd_item ; +struct cmd_item +{ + const char* str ; /* in r_string -- original string form */ + const char* doc ; /* in r_doc -- description text */ + + cmd_item next ; /* Next possibility (if any) */ + + cmd_item_type_t type ; + bool arg ; /* include in argv */ + + /* For item_range values */ + bool range_sign_allowed ; + bool range_sign_required ; + long range_min ; + long range_max ; } ; -typedef enum cmd_token_spec cmd_token_spec_t ; /*============================================================================== - * Completion match types. + * Match strengths types and filter settings. + * + * When matching a token, may have a number of competing items, possibly of + * different types. + * + * For execution a token must match completely -- or, for keyword items, + * match partially at most one possible keyword. + */ + +/* Match strength + * + * When matching a token against an item, the following are the possible + * results. + */ +enum match_strength +{ + ms_no_match = 0, /* match failed: token is definitely NOT + * the item in question. + */ + + ms_min_parse = ms_no_match + 1, + /* for parsing must match somehow ! */ + + ms_partial, /* match OK up to the end of the token, but + * more is required to complete it. + * This is used by the variable matches. + */ + + ms_min_execute = ms_partial + 1, + /* for execution must be at least this */ + + ms_anything, /* matches because will match anything. + * This is used for WORD and such like items. + */ + + ms_kwd_incomplete, /* keyword match OK, but not complete. + */ + + ms_var_complete, /* match succeeded: token is a complete and + * valid instance of the item in question + */ + + ms_kwd_complete /* match succeeded: token is a complete and + * valid instance of the item in question + */ +} ; +typedef enum match_strength match_strength_t ; + + +/* The match type indicates what has been matched and the strength of the + * match. + * + * NB: the order of these is significant, higher numbered match types are + * preferred over lower numbered ones. + * + * NB: this is in the same order as the cmd_item_type -- and should remain so. + * + * During the command filtering, when a token and an item match, and the + * match type is better than the match type to date, then all previous matches + * are discarded. + * + * The hierarchy means that, for example, '22' will match an IPv4 prefix + * partially, but that will be forgotten if it later matches a WORD, and that + * will be forgotten if it later matches a number range. + * + * The IPv6 items have a lower priority so that a simple decimal will prefer + * the simpler IPv4 address form. As soon as there is an 'a'..'f' or a ':', + * the IPv4 will no longer match. + * + * The partial keyword match is preferred over a partial anything else, but + * cannot mask a complete value ! + * + * The relative ranking of: mt_option_word_match, mt_vararg_match, and + * mt_word_match, is more or less arbitrary -- if these are ever candidates + * at the same time, the commands are ambiguous. * - * NB: the order of these is significant -- in particular as confirmed below. */ enum match_type { - no_match = 0, /* nope */ - any_match = 1, + mt_no_match, - extend_match = any_match, + mt_eol_partial, /* a partial match ! */ - ipv4_prefix_match, - ipv4_match, - ipv6_prefix_match, - ipv6_match, - range_match, - vararg_match, + mt_ipv6_address_partial, + mt_ipv6_prefix_partial, + mt_ipv4_address_partial, + mt_ipv4_prefix_partial, - partly_match, /* OK as far as it went */ - exact_match, /* Syntactically complete -- greatest match */ + mt_range_partial, + + mt_eol, /* an ms_anything match */ + + mt_option_word_match, /* anything can match a [WORD] */ + + mt_vararg_match, /* anything can match a .vararg */ + mt_word_match, /* anything can match a WORD */ + + mt_keyword_incomplete, + + mt_ipv6_prefix_complete, + mt_ipv6_address_complete, + mt_ipv4_prefix_complete, + mt_ipv4_address_complete, + + mt_range_complete, + + mt_keyword_complete, match_type_count /* Number of match types */ } ; typedef enum match_type match_type_t ; -CONFIRM(no_match == false) ; -CONFIRM(extend_match == (no_match + 1)) ; -CONFIRM(partly_match == (exact_match - 1)) ; -CONFIRM(exact_match == (match_type_count - 1)) ; +/*------------------------------------------------------------------------------ + * Map match_type -> cmd_item_type + * + * From a match type extract the type of item which has been match, partially + * or completely. + */ +Inline cmd_item_type_t +match_item_type(match_type_t mt) +{ + static cmd_item_type_t match_item_type[match_type_count] = + { + [mt_no_match] = item_null, + + [mt_eol_partial] = item_eol, + + [mt_ipv4_prefix_partial] = item_ipv4_prefix, + [mt_ipv4_address_partial] = item_ipv4_address, + [mt_ipv6_prefix_partial] = item_ipv6_prefix, + [mt_ipv6_address_partial] = item_ipv6_address, + + [mt_range_partial] = item_range, + + [mt_eol] = item_eol, + + [mt_option_word_match] = item_option_word, + + [mt_vararg_match] = item_vararg, + [mt_word_match] = item_word, + + [mt_keyword_incomplete] = item_keyword, + + [mt_ipv6_prefix_complete] = item_ipv6_prefix, + [mt_ipv6_address_complete] = item_ipv6_address, + [mt_ipv4_prefix_complete] = item_ipv4_prefix, + [mt_ipv4_address_complete] = item_ipv4_address, + + [mt_range_complete] = item_range, + + [mt_keyword_complete] = item_keyword, + } ; + + assert((mt >= 0) && (mt < match_type_count)) ; + + return match_item_type[mt] ; +} ; + +/*------------------------------------------------------------------------------ + * Map match_type -> match_strength. + * + * From a match type extract the strength of the match. + */ +Inline match_strength_t +match_match_strength(match_type_t mt) +{ + const static match_strength_t match_match_strength[match_type_count] = + { + [mt_no_match] = ms_no_match, + + [mt_eol_partial] = ms_partial, + + [mt_ipv6_prefix_partial] = ms_partial, + [mt_ipv6_address_partial] = ms_partial, + [mt_ipv4_prefix_partial] = ms_partial, + [mt_ipv4_address_partial] = ms_partial, + + [mt_range_partial] = ms_partial, + + [mt_eol] = ms_anything, + + [mt_option_word_match] = ms_anything, + + [mt_vararg_match] = ms_anything, + [mt_word_match] = ms_anything, + + [mt_keyword_incomplete] = ms_kwd_incomplete, + + [mt_ipv6_prefix_complete] = ms_var_complete, + [mt_ipv6_address_complete] = ms_var_complete, + [mt_ipv4_prefix_complete] = ms_var_complete, + [mt_ipv4_address_complete] = ms_var_complete, + + [mt_range_complete] = ms_var_complete, + + [mt_keyword_complete] = ms_kwd_complete, + } ; + + assert((mt >= 0) && (mt < match_type_count)) ; + + return match_match_strength[mt] ; +} ; + +/*------------------------------------------------------------------------------ + * Map cmd_item_type -> best possible match type. + * + * This gives the most optimistic outcome of an attempt to match to the given + * type of item. + * + * NB: tolerates item_null -- others may not + */ +Inline match_type_t +item_best_match(cmd_item_type_t it) +{ + const static match_type_t item_best_match[item_type_count] = + { + [item_null] = mt_no_match, + + [item_eol] = mt_eol, + + [item_option_word] = mt_option_word_match, + + [item_vararg] = mt_vararg_match, + [item_word] = mt_word_match, + + [item_ipv6_prefix] = mt_ipv6_prefix_complete, + [item_ipv6_address] = mt_ipv6_address_complete, + [item_ipv4_prefix] = mt_ipv4_prefix_complete, + [item_ipv4_address] = mt_ipv4_address_complete, + + [item_range] = mt_range_complete, + + [item_keyword] = mt_keyword_complete, + } ; + + assert((it >= 0) && (it < item_type_count)) ; + + return item_best_match[it] ; +} ; /*============================================================================== * @@ -95,11 +452,13 @@ CONFIRM(exact_match == (match_type_count - 1)) ; /* Command parsing options */ enum cmd_parse_type /* bit significant */ { - cmd_parse_completion = 0, + cmd_parse_standard = 0, /* accept short command parts */ + cmd_parse_strict = BIT(0), + cmd_parse_no_do = BIT(1), + cmd_parse_no_tree = BIT(2), - cmd_parse_do = BIT(1), - cmd_parse_tree = BIT(2), + cmd_parse_execution = BIT(3), /* wish to execute command */ } ; typedef enum cmd_parse_type cmd_parse_type_t ; @@ -108,28 +467,44 @@ enum cmd_pipe_type /* bit significant */ { cmd_pipe_none = 0, - cmd_pipe_in_file = BIT(0), - cmd_pipe_in_shell = BIT(1), + cmd_pipe_file = BIT(0), + cmd_pipe_shell = BIT(1), + cmd_pipe_dev_null = BIT(2), /* out pipe only -- black hole */ - cmd_pipe_reflect = BIT(4), - cmd_pipe_output = BIT(5), - cmd_pipe_more = BIT(6), + /* For in pipes */ + cmd_pipe_reflect = BIT(4), /* + option */ - cmd_pipe_out_file = BIT( 8), - cmd_pipe_out_file_append = BIT( 9), - cmd_pipe_out_shell = BIT(10), + /* For out file pipes */ + cmd_pipe_append = BIT(4), /* >> */ } ; typedef enum cmd_pipe_type cmd_pipe_type_t ; +/* Parsed parts */ +enum cmd_parts /* bit significant */ +{ + cmd_parts_none = 0, + + cmd_part_do = BIT(0), + cmd_part_command = BIT(1), + + cmd_part_in_pipe = BIT(2), + cmd_part_out_pipe = BIT(3), + + cmd_parts_pipe = (cmd_part_in_pipe | cmd_part_out_pipe), + + cmd_part_comment = BIT(4), +} ; +typedef enum cmd_parts cmd_parts_t ; + + /*------------------------------------------------------------------------------ * Token object -- a qstring and some other properties. */ enum cmd_token_type /* *bit* significant */ { - cmd_tok_null = 0, /* used for empty lines */ + cmd_tok_eol = 0, /* all lines have one */ cmd_tok_simple = BIT( 0), - cmd_tok_trailing = BIT( 1), cmd_tok_sq = BIT( 8), cmd_tok_dq = BIT( 9), /* '\\' within "..." are not @@ -138,20 +513,24 @@ enum cmd_token_type /* *bit* significant */ cmd_tok_incomplete = (cmd_tok_sq | cmd_tok_dq | cmd_tok_esc), - cmd_tok_pipe_in = BIT(12), - cmd_tok_pipe_out = BIT(13), - cmd_tok_comment = BIT(14), + cmd_tok_in_pipe = BIT(12), /* token starting '<' */ + cmd_tok_out_pipe = BIT(13), /* token starting '>' */ + cmd_tok_comment = BIT(14), /* token starting '!' or '#" */ } ; typedef enum cmd_token_type cmd_token_type_t ; -struct token +struct cmd_token { cmd_token_type_t type ; - qstring_t qs ; - size_t tp ; + qstring_t qs ; /* token string */ + + bool term ; /* token has been '\0' terminated */ + + usize tp ; /* location of token in the line */ + elstring_t ot ; /* original token in the line */ } ; -typedef struct token token_t[1] ; -typedef struct token* token ; + +typedef struct cmd_token* cmd_token ; /*------------------------------------------------------------------------------ * Token vector -- a vector of token objects @@ -164,6 +543,10 @@ struct token_vector typedef struct token_vector token_vector_t[1] ; typedef struct token_vector* token_vector ; +enum { + TOKEN_VECTOR_INIT_ALL_ZEROS = VECTOR_INIT_ALL_ZEROS +} ; + /*------------------------------------------------------------------------------ * Argument vector -- a vector of const char* */ @@ -174,6 +557,10 @@ struct arg_vector typedef struct arg_vector arg_vector_t[1] ; typedef struct arg_vector* arg_vector ; +enum { + ARG_VECTOR_INIT_ALL_ZEROS = VECTOR_INIT_ALL_ZEROS +} ; + /*------------------------------------------------------------------------------ * Parsed command line * @@ -191,252 +578,209 @@ typedef struct arg_vector* arg_vector ; * emptied -- see cmd_empty_parsed_tokens() -- but the next command to be * parsed will tidy up before proceeding. */ -typedef struct cmd_parsed* cmd_parsed ; struct cmd_parsed { - struct cmd_element *cmd ; /* NULL if empty command - or fails to parse */ + cmd_parts_t parts ; /* What parts are present */ - const char* line ; /* the current line */ + cmd_token_type_t tok_total ; /* What token types are present */ - enum node_type cnode ; /* node command is in */ - enum node_type onode ; /* node the parser started in */ + usize elen ; /* effective length (less trailing spaces) */ + usize tsp ; /* number of trailing spaces */ - bool do_shortcut ; /* true => is "do" command */ + uint num_tokens ; /* number of tokens parsed */ - token_vector_t tokens ; /* vector of token objects */ - arg_vector_t args ; /* vector of arguments */ + token_vector_t tokens ; /* vector of token objects */ - cmd_pipe_type_t pipes ; /* if any */ + /* NB: the following are significant only if there is a command part + * or a do part + */ + struct cmd_command *cmd ; /* NULL if empty command + or fails to parse */ + node_type_t cnode ; /* node command is in */ - token_vector_t read_pipe_tokens ; - token_vector_t write_pipe_tokens ; -} ; + arg_vector_t args ; /* vector of arguments -- embedded */ -/* Command dispatch options */ -enum { - cmd_no_queue = true, - cmd_may_queue = false, -} ; + /* NB: the following are significant only if an error is returned */ -/*------------------------------------------------------------------------------ - * Vector of spare token objects -- declared here to allow inlines, defined - * in command_parse.c - */ -extern token_vector_t spare_tokens ; + const char* emess ; /* parse error */ + usize eloc ; /* error location */ -/*============================================================================== - * Prototypes - */ -extern void cmd_spare_tokens_init(void) ; -extern void cmd_spare_tokens_free(void) ; - -extern cmd_parsed cmd_parse_init_new(cmd_parsed parsed) ; -extern cmd_parsed cmd_parse_reset(cmd_parsed parsed, bool free_structure) ; - -extern cmd_token_type_t cmd_tokenise(cmd_parsed parsed, const char *line, - node_type_t node) ; -Inline const char* cmd_token_string(token t) ; -Inline int cmd_token_count(token_vector tv) ; -Inline token cmd_token_get(token_vector tv, vector_index_t i) ; -Inline token cmd_token_pop(token_vector tv) ; -Inline void cmd_token_push(token_vector tv, token t) ; -Inline token cmd_token_shift(token_vector tv) ; -Inline void cmd_token_unshift(token_vector tv, token t) ; -Inline token cmd_token_make(void) ; -Inline token cmd_token_new(cmd_token_type_t type, const char* p, - size_t len, size_t tp) ; - -Inline void cmd_empty_token_vector(token_vector tv) ; -Inline void cmd_empty_parsed_tokens(cmd_parsed parsed) ; - -Inline bool cmd_token_complete(token t) ; -extern bool cmd_token_do_complete(token t) ; - -extern match_type_t cmd_ipv4_match (const char *str) ; -extern match_type_t cmd_ipv4_prefix_match (const char *str) ; -#if HAVE_IPV6 -extern match_type_t cmd_ipv6_match (const char *str) ; -extern match_type_t cmd_ipv6_prefix_match (const char *str) ; -#endif -extern bool cmd_range_match (const char *range, const char *str) ; + /* NB: the following are significant only if respective part is + * present. + */ + cmd_pipe_type_t in_pipe ; /* if any */ + cmd_pipe_type_t out_pipe ; /* if any */ -/*============================================================================== - * Inline Functions - */ + uint first_in_pipe ; + uint num_in_pipe ; -/*------------------------------------------------------------------------------ - * Get pointer to token value. - * - * Returns NULL if token NULL or no string. - */ -Inline char* -cmd_token_value(token t) -{ - return (t == NULL) ? NULL : qs_chars(t->qs) ; -} ; + uint first_do ; + uint num_do ; -/*------------------------------------------------------------------------------ - * Get string value of given token. - * - * Returns an empty (not NULL) string if token NULL or no string. - */ -Inline const char* -cmd_token_string(token t) -{ - const char* s = cmd_token_value(t) ; - return (s == NULL) ? "" : s ; -} ; + uint first_command ; + uint num_command ; -/*------------------------------------------------------------------------------ - * Get number of tokens in the given token vector - */ -Inline int -cmd_token_count(token_vector tv) -{ - return vector_length(tv->body) ; -} ; + uint first_out_pipe ; + uint num_out_pipe ; -/*------------------------------------------------------------------------------ - * Get i'th token from given token vector -- zero origin - */ -Inline token -cmd_token_get(token_vector tv, vector_index_t i) -{ - return vector_get_item(tv->body, i) ; -} ; + uint first_comment ; + uint num_comment ; -/*------------------------------------------------------------------------------ - * Pop token from end of given token vector -- if any. - */ -Inline token -cmd_token_pop(token_vector tv) -{ - return vector_pop_item(tv->body) ; -} ; + /* The following are significant after cmd_token_position() */ -/*------------------------------------------------------------------------------ - * Push token onto end of given token vector. - */ -Inline void -cmd_token_push(token_vector tv, token t) -{ - vector_push_item(tv->body, t) ; + uint cti ; /* cursor token index -- may be eol token */ + uint ctl ; /* cursor token length -- 0 <=> eol token */ + int rp ; /* cursor relative to start of cursor token */ + + /* The following are used while filtering commands */ + + vector_t cmd_v ; /* working vector -- embedded */ + vector_t item_v ; /* working vector -- embedded */ + + match_strength_t strongest ; + match_type_t best_complete ; + + match_strength_t min_strength ; /* for execution */ + bool strict ; /* for strict keyword match */ } ; -/*------------------------------------------------------------------------------ - * Shift first token off front of given token vector -- if any. - */ -Inline token -cmd_token_shift(token_vector tv) +enum { - return vector_shift_item(tv->body) ; + CMD_PARSED_INIT_ALL_ZEROS = (cmd_pipe_none == 0) + && TOKEN_VECTOR_INIT_ALL_ZEROS + && ARG_VECTOR_INIT_ALL_ZEROS } ; -/*------------------------------------------------------------------------------ - * Unshift token onto front of given token vector. - */ -Inline void -cmd_token_unshift(token_vector tv, token t) +typedef struct cmd_parsed cmd_parsed_t[1] ; +typedef struct cmd_parsed* cmd_parsed ; + +/* Command dispatch options */ +enum cmd_queue_b { - vector_unshift_item(tv->body, t) ; + cmd_no_queue = false, + cmd_queue_it = true, } ; +typedef enum cmd_queue_b cmd_queue_b ; -/*------------------------------------------------------------------------------ - * Make a brand new token object +/*============================================================================== + * Prototypes */ -Inline token -cmd_token_make(void) -{ - return XCALLOC(MTYPE_TOKEN, sizeof(struct token)) ; +extern void cmd_compile(cmd_command cmd) ; +extern void cmd_compile_check(cmd_command cmd) ; - /* Zeroising the new structure sets: - * - * type = 0 -- cmd_tok_null - * qs = zeroised qstring -- empty string - */ - confirm(cmd_tok_null == 0) ; -} ; +extern cmd_parsed cmd_parsed_init_new(cmd_parsed parsed) ; +extern cmd_parsed cmd_parsed_reset(cmd_parsed parsed, + free_keep_b free_structure) ; +extern bool cmd_is_empty(elstring line) ; -/*------------------------------------------------------------------------------ - * Create new 'cmd_tok_simple' token from given characters + length - */ -Inline token -cmd_token_new(cmd_token_type_t type, const char* p, size_t len, size_t tp) -{ - token t ; +extern cmd_return_code_t cmd_parse_command(cmd_parsed parsed, + node_type_t node, cmd_parse_type_t type) ; - t = cmd_token_pop(spare_tokens) ; - if (t == NULL) - t = cmd_token_make() ; +extern bool cmd_token_position(cmd_parsed parsed, qstring line) ; +extern const char* cmd_help_preflight(cmd_parsed parsed) ; +extern cmd_return_code_t cmd_completion(cmd_parsed parsed, node_type_t node) ; - t->type = type ; - qs_set_n(&t->qs, p, len) ; - t->tp = tp ; +extern void cmd_complete_keyword(cmd_parsed parsed, + int* pre, int* rep, int* ins, int* mov) ; - return t ; -} ; -/*------------------------------------------------------------------------------ - * Discard given token -- give back to spare tokens list - */ -Inline void -cmd_token_discard(token t) -{ - if (t != NULL) - vector_push_item(spare_tokens->body, t) ; -} ; -/*------------------------------------------------------------------------------ - * Release contents of token vector -- move to the spare_token_strings. + + + +//extern vector cmd_make_strvec (const char *); +//extern vector cmd_add_to_strvec (vector v, const char* str) ; +//extern void cmd_free_strvec (vector); +extern vector cmd_describe_command (const char* line, node_type_t node, + cmd_return_code_t* status) ; +extern vector cmd_complete_command (vector, int, int *status); + + + +extern void cmd_tokenise(cmd_parsed parsed, qstring line) ; +//extern cmd_return_code_t cmd_parse_error(cmd_parsed parsed, cmd_token t, +// usize off, const char* mess) ; + +//Inline const char* cmd_token_string(cmd_token t) ; +//Inline char* cmd_token_make_string(cmd_token t) ; +//Inline int cmd_token_count(token_vector tv) ; +//Inline cmd_token cmd_token_get(token_vector tv, vector_index_t i) ; +//Inline void cmd_token_set(token_vector tv, vector_index_t i, +// cmd_token_type_t type, const char* p, usize len, usize tp) ; + +//extern cmd_return_code_t cmd_token_complete(cmd_parsed parsed, cmd_token t) ; +//Inline const char* cmd_token_string(cmd_token t) ; +//extern cmd_return_code_t cmd_parse_in_pipe(cmd_parsed parsed, cmd_token t) ; +//extern cmd_return_code_t cmd_parse_out_pipe(cmd_parsed parsed, cmd_token t) ; + +//extern match_type_t cmd_ipv4_match (const char *str) ; +//extern match_type_t cmd_ipv4_prefix_match (const char *str) ; +#if HAVE_IPV6 +//extern match_type_t cmd_ipv6_match (const char *str) ; +//extern match_type_t cmd_ipv6_prefix_match (const char *str) ; +#endif +//extern bool cmd_range_match (const char *range, const char *str) ; + +/*============================================================================== + * Inline Functions */ -Inline void -cmd_empty_token_vector(token_vector tv) -{ - if (cmd_token_count(tv) != 0) - vector_move_append(spare_tokens->body, tv->body) ; -} ; + +Private cmd_token cmd_token_new(void) ; /*------------------------------------------------------------------------------ - * Release contents of all token vectors in given parsed object. + * Get pointer to token value. + * + * Returns NULL if token NULL or no string. */ -Inline void -cmd_empty_parsed_tokens(cmd_parsed parsed) +Inline char* +cmd_token_value(cmd_token t) { - cmd_empty_token_vector(parsed->tokens) ; - cmd_empty_token_vector(parsed->read_pipe_tokens) ; - cmd_empty_token_vector(parsed->write_pipe_tokens) ; + return (t == NULL) ? NULL : qs_char_nn(t->qs) ; } ; /*------------------------------------------------------------------------------ - * If token is incomplete (contains quotes or escapes) process those down. + * Get string value of given token. * - * Returns: true <=> OK - * false => invalid escape + * Returns an empty (not NULL) string if token NULL or no string. */ -Inline bool -cmd_token_complete(token t) +Inline char* +cmd_token_make_string(cmd_token t) { - return ((t->type & cmd_tok_incomplete) == 0) ? true - : cmd_token_do_complete(t) ; + if (t->term) + return qs_char_nn(t->qs) ; + else + { + t->term = true ; + return qs_make_string(t->qs) ; + } } ; /*------------------------------------------------------------------------------ - * Initialise arg_vector object in cmd_parsed. + * Get i'th token from given token vector -- zero origin */ -Inline void -cmd_arg_vector_init(cmd_parsed parsed) +Inline cmd_token +cmd_token_get(token_vector tv, vector_index_t i) { - vector_init_new(parsed->args->body, 0) ; + return vector_get_item(tv->body, i) ; } ; /*------------------------------------------------------------------------------ - * Free the body of the arg_vector object in cmd_parsed. + * Set i'th token from given token vector -- zero origin */ Inline void -cmd_arg_vector_free(cmd_parsed parsed) +cmd_token_set(token_vector tv, vector_index_t i, + cmd_token_type_t type, const char* p, usize len, usize tp) { - vector_reset(parsed->args->body, keep_it) ; + cmd_token t = cmd_token_get(tv, i) ; + + if (t == NULL) + vector_set_item(tv->body, i, (t = cmd_token_new())) ; + + t->type = type ; + t->term = false ; + t->tp = tp ; + qs_set_alias_n(t->qs, p, len) ; + qs_els_copy_nn(t->ot, t->qs) ; } ; /*------------------------------------------------------------------------------ diff --git a/lib/command_queue.c b/lib/command_queue.c index 5f14abae..0a09d950 100644 --- a/lib/command_queue.c +++ b/lib/command_queue.c @@ -19,136 +19,564 @@ * Boston, MA 02111-1307, USA. */ -#include <zebra.h> +#include "zconfig.h" /* for CONSUMED_TIME_CHECK */ +#include "misc.h" -#include "mqueue.h" #include "qpnexus.h" -#include "memory.h" +#include "mqueue.h" +#include "thread.h" + #include "command_queue.h" #include "command_execute.h" -#include "vty.h" -#include "uty.h" -#include "vector.h" -#include "qstring.h" +#include "vty_command.h" +#include "vty_io.h" +#include "log.h" + +/*============================================================================== + * This command loop processes commands for VTY_TERMINAL and VTY_SHELL_SERVER. + * + * Commands appear from those sources, driven by socket I/O. Once a command + * line is ready it is passed out of the I/O pselect/select driven code + * as a message, and enters the command loop. + * + * If pipes are involved that is all dealt with in the command loop, which + * runs until the vin/vout stacks return to 0 -- the VTY_TERMINAL and + * VTY_SHELL_SERVER. + * + * There are further issues: + * + * 1) in the qpthreads world, commands are parsed in the CLI thread, but most + * are executed in the Routing thread. So parsed commands are passed, by + * message between threads. + * + * 2) input is expected to be non-blocking -- so the command loop will + * exit if a command line cannot be delivered immediately, and will be + * returned to later. + * + * 3) while a VTY is in the command loop it is marked vio->cmd_running. + * + * While that is true, the vty and the vty->exec are in the hands + * of the command loop. The vty->vio is always accessed under VTY_LOCK(). + * + * 4) opening pipes is done in the CLI thread, in case of any possible + * blocking. + * + * 5) all output is to fifo buffers -- when output is pushed the CLI side + * is kicked to manage all output via pselect/select. + * + * In vty_io_basic() it is possible to set read/write ready and associated + * timeouts while running in the CMD thread, but that too depends on the + * passing of messages to the CLI thread. + * + * The smooth running of the command handling depends on the continued + * running of the CLI thread. + * + * To close a VTY must (eventually) arrange for vio->cmd_running to be cleared. + * While a vty is vio->cmd_running, it must be in one of these states: + * + * - on the vty_cli_nexus queue (or the combined queue) + * - executing in the vty_cli_nexus (or the single "both" nexus) + * - on the vty_cmd_nexus queue (if different) + * - executing in the vty_cmd_nexus (if different) + * + * Or, in the legacy threads world: + * + * - on the event queue + * - executing + * + * Where there is only one pthread (and in the legacy threads world) things are + * easy. + * + * + * This revoke runs in the CLI thread, and will catch the message if it is + * on either queue, and vty_revoke() will deal with it -- still in the CLI + * thread. + * + * The command cannot be running in the CLI thread, since that is where we + * are ! + * + * That leaves the command running in the CMD thread. That will run to + * completion... the VTY may be closed in the meantime, which will shut down + * the reading side, so the command loop will come to a halt quite quickly. + * Note, however, that the smooth running of this process requires the CLI + * thread and its messages to be + * + * + */ + + + /*------------------------------------------------------------------------------ - * Form of message passed with command to be executed + * Prototypes */ +static void cq_enqueue(struct vty *vty, qpn_nexus dst, cmd_exec_state_t state, + cmd_return_code_t ret) ; +static void cq_action(mqueue_block mqb, mqb_flag_t flag); +static int cq_thread(struct thread* thread) ; +static void cq_process(vty vty) ; -struct cq_command_args +/*------------------------------------------------------------------------------ + * Enqueue vty for parse and execution of a command. + * + * Sets the vio->cmd_running flag, which will be cleared only when the + * command is completed (including any nested pipes etc.) or when the vty + * is blocked on input or it is revoked. + * + * Note that from now on, exec->ret reflects the state of the return + * code when the vty was last enqueued. + * + * While vio_cmd_running is set, the CLI side MUST NOT fiddle with the main + * part of the VTY or with the vty->exec state. + */ +extern void +cq_dispatch(vty vty, cmd_do_t to_do, qstring line) { - enum cmd_return_code ret ; /* return code from command */ + VTY_ASSERT_CLI_THREAD() ; + + vty->exec->to_do = to_do ; + vty->exec->line = line ; + vty->vio->cmd_running = true ; + cq_enqueue(vty, vty_cli_nexus, + (to_do == cmd_do_command) ? exec_parse + : exec_special, CMD_SUCCESS) ; } ; -MQB_ARGS_SIZE_OK(cq_command_args) ; /*------------------------------------------------------------------------------ - * Prototypes + * Enqueue vty for fetching a command line from an in_pipe which has just + * received more input. + * + * Sets the vio->cmd_running flag, which will be cleared only when the + * command is completed (including any nested pipes etc.) or when the vty + * is blocked on input or it is revoked. + * + * While vio_cmd_running is set, the CLI side MUST NOT fiddle with the main + * part of the VTY or with the vty->exec state. */ -static void cq_action(mqueue_block mqb, mqb_flag_t flag); -static void cq_return(mqueue_block mqb, mqb_flag_t flag); +extern void +cq_go_fetch(vty vty) +{ + VTY_ASSERT_CLI_THREAD() ; + + vty->vio->cmd_running = true ; + cq_enqueue(vty, vty_cli_nexus, exec_fetch, CMD_SUCCESS) ; +} ; /*------------------------------------------------------------------------------ - * Enqueue vty and argv[] for execution in given nexus. + * Enqueue vty for execution in given nexus or issue thread event. + * + * Note that preserves the return code state. */ -void -cq_enqueue(struct vty *vty, qpn_nexus dst) +static void +cq_enqueue(vty vty, qpn_nexus dst, cmd_exec_state_t state, + cmd_return_code_t ret) { - struct cq_command_args* args ; - mqueue_block mqb ; + cmd_exec exec = vty->exec ; + + assert(vty->vio->cmd_running) ; + assert((dst == vty_cli_nexus) || (dst == vty_cmd_nexus)) ; - assert(vty_cli_nexus) ; /* must be running qnexus-wise */ + exec->locus = dst ; + exec->state = state ; + exec->ret = ret ; - mqb = mqb_init_new(NULL, cq_action, vty) ; - args = mqb_get_args(mqb) ; + if (vty_nexus) + { + mqueue_block mqb ; - args->ret = CMD_QUEUED ; + if ((mqb = exec->cq.mqb) == NULL) + mqb = exec->cq.mqb = mqb_init_new(NULL, cq_action, vty) ; - mqueue_enqueue(dst->queue, mqb, 0) ; -} + mqueue_enqueue(dst->queue, mqb, (dst == vty_cmd_nexus) ? mqb_priority + : mqb_ordinary) ; + } + else + { + assert(vty_cli_nexus == vty_cmd_nexus) ; + exec->cq.thread = thread_add_event(vty_master, cq_thread, vty, 0) ; + } ; +} ; /*------------------------------------------------------------------------------ - * Dispatch a command from the message queue block + * Deal with command message -- in the qpthreads world. * - * When done (or revoked/deleted) return the message, so that the sender knows - * that the command has been dealt with (one way or another). - * - * Note that if the command is revoked the return is set to CMD_QUEUED. + * Note that if the command is revoked.... */ static void cq_action(mqueue_block mqb, mqb_flag_t flag) { - struct vty *vty; - struct cq_command_args* args ; + vty vty; - assert(vty_cli_nexus) ; /* must be running qnexus-wise */ + assert(vty_nexus) ; /* must be running qnexus-wise */ vty = mqb_get_arg0(mqb); - args = mqb_get_args(mqb) ; + + assert(vty->exec->cq.mqb == mqb) ; + assert(vty->vio->cmd_running) ; if (flag == mqb_action) - { - args->ret = cmd_dispatch_call(vty) ; - assert(args->ret != CMD_QUEUED) ; /* avoid confusion ! */ - } - else - args->ret = CMD_QUEUED ; + return cq_process(vty) ; /* do not touch vty on way out */ + + mqb_free(mqb) ; + vty->exec->cq.mqb = NULL ; +} ; - mqb_set_action(mqb, cq_return) ; - mqueue_enqueue(vty_cli_nexus->queue, mqb, 0) ; +/*------------------------------------------------------------------------------ + * Deal with command message -- in the legacy threads world. + * + * Note that if the command is revoked.... + */ +static int +cq_thread(struct thread* thread) +{ + vty vty = THREAD_ARG(thread) ; + + assert(vty->exec->cq.thread == thread) ; + assert(vty->vio->cmd_running) ; + + vty->exec->cq.thread = NULL ; + + cq_process(vty) ; /* do not touch vty on way out */ + + return 0 ; } ; /*------------------------------------------------------------------------------ - * Accept return from command which has completed. + * Process command(s) queued from VTY_TERMINAL or from VTY_SHELL_SERVER. * - * The command line processing for the vty may be stalled (with read mode - * disabled) waiting for the return from the command. + * To get into the process loop, or to get back to it when more input has + * arrived a message is sent: * - * Do not care whether the message is being revoked or not... the command - * has completed and that must be signalled to the CLI. Any pending output - * is released. + * cli -> cli -- exec_parse -- when a command line has been gathered + * from input and is ready to be processed. * - * The command itself may have been revoked before it was executed. That - * makes no difference either... the output buffers will simply be empty. - * However, the return code is CMD_QUEUED, to signal the fact that the command - * was never executed. + * cli -> cli -- exec_fetch -- when was waiting for more input from an + * in_pipe of some sort. + * + * In the single-pthread world, where the vty_cli_nexus is the same as the + * vty_cmd_nexus (either because not running qpthreaded, or because are + * running in the legacy threads world), things are reasonably straightforward. + * The process runs to completion or until an in_pipe would block, and the + * above are the only messages in the system. + * + * In the multi-pthread world, things are more complicated... The above + * messages are sent, and in addition the following are sent back and forth to + * transfer between threads in the following states: + * + * cmd -> cli -- exec_open_pipes + * cli -> cmd -- exec_execute + * cmd -> cli -- exec_complete + * + * Note that this means that in the multi-pthread world, only one sort of + * message is sent to the vty_cmd_nexus. + * + * The vty_io_basic stuff allows the multi-pthread vty_cmd_nexus to set read + * and/or write ready state -- which may generate messages to the vty_cli_nexus. + * + * NB: if blocks in exec_fetch, then vty_cmd_fetch_line() will have cleared + * vio->cmd_running -- so on return from cq_process the vty MAY HAVE BEEN + * DELETED. */ static void -cq_return(mqueue_block mqb, mqb_flag_t flag) +cq_process(vty vty) { - struct vty *vty ; - struct cq_command_args* args ; + cmd_exec exec = vty->exec ; + cmd_parsed parsed = exec->parsed ; + cmd_return_code_t ret = exec->ret ; + + /* Have switch wrapped in a while(1) so that can change state by setting + * exec->state and doing "continue". + * + * Breaking out of the switch forces the exec state to exec_complete, + * with the current ret, in the CLI thread. + * + * The exec locus is either vty_cli_nexus or vty_cmd_nexus. If these + * are equal (including both NULL, in the legacy threads world), then is + * running single threaded -- otherwise is running multi-threaded. + */ + while (1) + { + switch(exec->state) + { + /*-------------------------------------------------------------------- + * Should not get here in exec_null state. + */ + case exec_null: + zabort("exec->state == exec_null") ; + break ; + + /*-------------------------------------------------------------------- + * Deal with the "spacial" commands + */ + case exec_special: + ret = vty_cmd_special(vty) ; + if (ret != CMD_SUCCESS) + break ; + + exec->state = exec_fetch ; + /* continue by falling through */ + + /*-------------------------------------------------------------------- + * Need another command to execute => in_pipe ! + * + * Note that at vin_depth == 0 this will return CMD_EOF, and will + * drop out of the loop exec_complete. + * + * Will also receive CMD_EOF if the VTY has been closed. + * + * If multi-threaded: may be in either thread: + * + * vty_cmd_fetch_line() may set read and/or write ready -- so in + * vty_cmd_nexus may generate message to vty_cli_nexus. + */ + case exec_fetch: + ret = vty_cmd_fetch_line(vty) ; + + if (ret != CMD_SUCCESS) + { + /* If is CMD_WAITING, then the vty_cmd_fetch_line() will + * have prepared for command to be re-queued when there is more + * to be read. NB: vio->cmd_running has been cleared, so + * vty MAY HAVE BEEN DELETED ! + * + * If is CMD_EOF then is "closing" or reached EOF on top-most + * pipe. + */ + if (ret == CMD_WAITING) + return ; /* <<<< DONE, pro tem */ + + break ; /* => exec_complete */ + } ; + + if (exec->to_do != cmd_do_command) + { + exec->state = exec_special ; + continue ; + } ; + + exec->state = exec_parse ; + /* continue by falling through */ + + /*-------------------------------------------------------------------- + * Parse command in hand + * + * If multi-threaded: may be in either thread: + * + * vty_cmd_reflect_line() may set read and/or write ready -- so in + * vty_cmd_nexus may generate message to vty_cli_nexus. + */ + case exec_parse: + cmd_tokenise(parsed, exec->line) ; + ret = cmd_parse_command(parsed, vty->node, + exec->parse_type | cmd_parse_execution) ; + if (ret != CMD_SUCCESS) + { + if (ret != CMD_EMPTY) + break ; /* stop on *any* parsing issue */ + + /* Empty lines from in_pipes we simply ignore. + * + * Don't expect to see them otherwise, but if we do then need + * to complete the command execution process. + */ + ret = CMD_SUCCESS ; + + exec->state = exec_success ; + continue ; + } ; + + /* reflection now -- output always succeeds */ + if (exec->reflect_enabled) + vty_cmd_reflect_line(vty) ; + + /* continue by falling through */ + + /*-------------------------------------------------------------------- + * Pipe work if any + * + * Will receive CMD_EOF if the VTY has been closed. + * + * If multi-threaded: must be in vty_cli_nexus to proceed -- so may + * generate message to transfer to vty_cli_nexus. + */ + case exec_open_pipes: + if ((parsed->parts & cmd_parts_pipe) != 0) + { + exec->state = exec_open_pipes ; + + /* If running pthreaded, do open pipes in vty_cli_nexus */ + if (exec->locus != vty_cli_nexus) + return cq_enqueue(vty, vty_cli_nexus, exec_open_pipes, ret) ; + + /* Now in vty_cli_nexus */ + ret = cmd_open_pipes(vty) ; + if (ret != CMD_SUCCESS) + break ; /* quit if open fails */ + } ; - assert(vty_cli_nexus) ; /* must be running qnexus-wise */ + exec->state = exec_execute ; + /* continue by falling through */ - vty = mqb_get_arg0(mqb) ; - args = mqb_get_args(mqb) ; + /*-------------------------------------------------------------------- + * Execute command in hand + * + * If multi-threaded: some commands can run in either thread, most must + * run in the vty_cmd_nexus -- so may generate message to transfer to + * the vty_cmd_nexus. + */ + case exec_execute: + if ((parsed->parts & cmd_part_command) != 0) + { + /* If running pthreaded, do most commands in vty_cmd_nexus */ + if ((exec->locus != vty_cmd_nexus) && + (!cmd_is_direct(parsed))) + return cq_enqueue(vty, vty_cmd_nexus, exec_execute, ret) ; - /* signal end of command */ - cmd_post_command(vty, args->ret) ; - vty_queued_result(vty, args->ret) ; + /* Standard command handling */ +#ifdef CONSUMED_TIME_CHECK + { + RUSAGE_T before; + RUSAGE_T after; + unsigned long realtime, cputime; -//if (qpthreads_enabled) -// qpt_thread_signal(vty_cli_nexus->thread_id, SIGMQUEUE); + GETRUSAGE(&before); +#endif /* CONSUMED_TIME_CHECK */ - mqb_free(mqb); -} + ret = cmd_execute(vty) ; + +#ifdef CONSUMED_TIME_CHECK + GETRUSAGE(&after); + realtime = thread_consumed_time(&after, &before, &cputime) ; + if (realtime > CONSUMED_TIME_CHECK) + /* Warn about CPU hog that must be fixed. */ + uzlog(NULL, LOG_WARNING, + "SLOW COMMAND: command took %lums (cpu time %lums): %s", + realtime/1000, cputime/1000, + qs_make_string(exec->line)) ; + } ; +#endif /* CONSUMED_TIME_CHECK */ + + if (ret != CMD_SUCCESS) + break ; /* stop */ + } ; + + exec->state = exec_success ; + /* continue by falling through */ + + /*-------------------------------------------------------------------- + * Command has completed successfully -- so push the output. + * + * If the vout_depth > vin_depth, pops the vout's -- deals with single + * command lines with a pipe output. + * + * Output cannot block, so this always succeeds. + * + * Then loop back to fetch another command line, if can. + * + * If multi-threaded: may be in either thread: + * + * vty_cmd_success() may set write ready -- so in vty_cmd_nexus may + * generate message to vty_cli_nexus. + */ + case exec_success: + assert(ret == CMD_SUCCESS) ; + + vty_cmd_success(vty) ; + + exec->state = exec_fetch ; + continue ; + + /*-------------------------------------------------------------------- + * End of the command loop ! + * + * If multi-threaded: must return to the vty_cli_nexus. + */ + case exec_complete: + if (exec->locus != vty_cli_nexus) + break ; /* Will send back to the cli */ + + /* Now in the vty_cli_nexus */ + + exec->state = exec_null ; /* all done ! */ + + vty_cmd_loop_exit(vty, ret) ; /* clears vio->cmd_running */ + + return ; /* <<<< Finally finished ! */ + + /*---------------------------------------------------------------------- + * Unknown exec->state ! + */ + default: + zabort("unknown exec->state") ; + break ; + } ; + + /* Have broken out of the switch(). This means that for good or ill, + * the command is complete. If we are not in the vty_cli_nexus, need to + * send back to the vty_cli_nexus for handling. + * + * At all times we treat CMD_EOF and CMD_SUCCESS. + * + * Otherwise, can continue in exec_complete state. + */ + if (ret == CMD_EOF) + ret = CMD_SUCCESS ; + + if (exec->locus != vty_cli_nexus) + return cq_enqueue(vty, vty_cli_nexus, exec_complete, ret) ; + + exec->state = exec_complete ; + } ; +} ; /*------------------------------------------------------------------------------ - * Revoke any messages related to the given VTY -- if running qnexus-wise. + * Revoke any message associated with the given vty from the command queue. + * + * This is used when a VTY_TERMINAL or a VTY_SHELL_SERVER is being closed. + * + * See cq_process above for discussion of what messages there may be. At any + * time there is at most one message in flight. + * + * If we find a message in flight, then we vty_cmd_loop_exit() to bring things + * to a stop tidily. * - * Revokes in vty_cmd_nexus -- so before command is started - * and in vty_cli_nexus -- so after command has completed + * In the single-threaded world, expect that a command, once started will run + * to conclusion, or until blocked on an in_pipe. So after this revoke the + * vty should not be vio->cmd_running. * - * Can do nothing about any command actually being executed in the - * vty_cmd_nexus. + * In the multi-threaded world, this revoke will catch any vty which is on + * either the vty_cli_nexus or vty_cmd_nexus queues, and force completion. + * After this revoke, vio->cmd_running will be true iff the command is + * currently being executed in the vty_cmd_nexus -- we expect that to run to + * conclusion or block on an in_pipe, shortly, which will be collected when + * the vty_cli_nexus message queue is next processed. + * + * Note that the revoke does not affect any vty_cli_nexus messages associated + * with the vty_io_basic operations. */ -void -cq_revoke(struct vty *vty) +extern void +cq_revoke(vty vty) { - if (vty_cli_nexus) + int ret ; + + VTY_ASSERT_CLI_THREAD() ; + + if (vty_nexus) { - mqueue_revoke(vty_cmd_nexus->queue, vty) ; - mqueue_revoke(vty_cli_nexus->queue, vty) ; + ret = mqueue_revoke(vty_cmd_nexus->queue, vty, 1) ; + + if ((ret == 0) && (vty_cli_nexus != vty_cmd_nexus)) + ret = mqueue_revoke(vty_cli_nexus->queue, vty, 1) ; + } + else + ret = thread_cancel_event(vty_master, vty) ; + + if (ret != 0) + { + vty->exec->state = exec_null ; + vty_cmd_loop_exit(vty, vty->exec->ret) ; } ; -} +} ; + + diff --git a/lib/command_queue.h b/lib/command_queue.h index 804dfa1f..0966bd68 100644 --- a/lib/command_queue.h +++ b/lib/command_queue.h @@ -22,10 +22,12 @@ #ifndef COMMAND_QUEUE_H_ #define COMMAND_QUEUE_H_ -#include "command.h" +#include "vty_local.h" +#include "command_execute.h" #include "qpnexus.h" -extern void cq_enqueue(struct vty *vty, qpn_nexus dst) ; -extern void cq_revoke(struct vty* vty) ; +extern void cq_dispatch(vty vty, cmd_do_t to_do, qstring line) ; +extern void cq_go_fetch(vty vty) ; +extern void cq_revoke(vty vty) ; #endif /* COMMAND_QUEUE_H_ */ diff --git a/lib/elstring.h b/lib/elstring.h index 051e5962..8ab1c58d 100644 --- a/lib/elstring.h +++ b/lib/elstring.h @@ -23,8 +23,6 @@ #define _ZEBRA_ELSTRING_H #include "misc.h" -#include "zassert.h" -#include "memory.h" /*============================================================================== * This is some very simple support for strings which are Length/Body @@ -34,8 +32,8 @@ * is the simplest possible encapsulation of strings which are NOT '\0' * terminated. * - * - * + * NB: this object knows NOTHING about whether there is a '\0' beyond the + * 'len' of the string. */ struct elstring { @@ -46,7 +44,6 @@ struct elstring } body ; ulen len ; - bool term ; /* true <=> body is '\0' terminated */ } ; typedef struct elstring elstring_t[1] ; @@ -60,15 +57,48 @@ enum ELSTRING_INIT_ALL_ZEROS = true } ; -/*------------------------------------------------------------------------------ - * Various forms of body -- NB: +/*============================================================================== + * Pointer pair and unsigned pointer pair and const versions. */ +struct pp +{ + char* p ; + char* e ; +} ; +typedef struct pp pp_t[1] ; +typedef struct pp* pp ; -Inline void* -els_body_nn(elstring els) +struct cpp { - return els->body.v ; + const char* p ; + const char* e ; } ; +typedef struct cpp cpp_t[1] ; +typedef struct cpp* cpp ; + +/*============================================================================== + * NULLs for all types of pp and for els + */ +Inline void pp_null(pp p) { p->p = p->e = NULL ; } ; +Inline void cpp_null(cpp p) { p->p = p->e = NULL ; } ; + +Inline void els_null(elstring els) { els->body.v = NULL ; els->len = 0 ; } ; + +/*============================================================================== + * Access functions. + */ + +Inline void* els_body(elstring els) ; +Inline void* els_body_nn(elstring els) ; +Inline ulen els_len(elstring els) ; +Inline ulen els_len_nn(elstring els) ; +Inline void* els_end(elstring els) ; +Inline void* els_end_nn(elstring els) ; + +Inline void els_pp(pp p, elstring els) ; +Inline void els_pp_nn(pp p, elstring els) ; +Inline void els_cpp(cpp p, elstring els) ; +Inline void els_cpp_nn(cpp p, elstring els) ; Inline void* els_body(elstring els) @@ -76,10 +106,10 @@ els_body(elstring els) return (els != NULL) ? els_body_nn(els) : NULL ; } ; -Inline ulen -els_len_nn(elstring els) +Inline void* +els_body_nn(elstring els) { - return els->len ; + return els->body.v ; } ; Inline ulen @@ -88,38 +118,70 @@ els_len(elstring els) return (els != NULL) ? els_len_nn(els) : 0 ; } ; -Inline bool -els_term_nn(elstring els) +Inline ulen +els_len_nn(elstring els) { - return els->term ; + return els->len ; } ; -Inline bool -els_term(elstring els) +Inline void* +els_end(elstring els) { - return (els != NULL) ? els_term_nn(els) : false ; + return (els != NULL) ? els_end_nn(els) : NULL ; } ; -/*============================================================================== - * All so simple that everything is implemented as Inline - */ +Inline void* +els_end_nn(elstring els) +{ + return (void*)((char*)els->body.v + els->len); +} ; -/*------------------------------------------------------------------------------ - * Initialise or create a new elstring - */ -Inline elstring -els_init_new(elstring els) +Inline void +els_pp(pp p, elstring els) { - if (els == NULL) - els = XCALLOC(MTYPE_TMP, sizeof(elstring_t)) ; + if (els != NULL) + els_pp_nn(p, els) ; else - memset(els, 0, sizeof(elstring_t)) ; + pp_null(p) ; +} ; - confirm(ELSTRING_INIT_ALL_ZEROS) ; +Inline void +els_pp_nn(pp p, elstring els) +{ + p->p = els->body.v ; + p->e = p->p + els->len ; +} ; - return els ; +Inline void +els_cpp(cpp p, elstring els) +{ + if (els != NULL) + els_cpp_nn(p, els) ; + else + cpp_null(p) ; +} ; + +Inline void +els_cpp_nn(cpp p, elstring els) +{ + p->p = els->body.cv ; + p->e = p->p + els->len ; } ; +/*============================================================================== + * All so simple that most is implemented as Inline + */ + +extern elstring els_init_new(elstring els) ; +extern elstring els_new(void) ; +extern elstring els_free(elstring els) ; + +extern int els_cmp(elstring a, elstring b) ; +extern int els_cmp_word(elstring a, const char* w) ; +extern int els_cmp_sig(elstring a, elstring b) ; +extern bool els_equal(elstring a, elstring b) ; +extern bool els_substring(elstring a, elstring b) ; + /*------------------------------------------------------------------------------ * Set elstring value from ordinary string. * @@ -134,7 +196,6 @@ els_set_nn(elstring els, const void* str) { els->body.cv = str ; els->len = (str != NULL) ? strlen(str) : 0 ; - els->term = (str != NULL) ; } ; /*------------------------------------------------------------------------------ @@ -148,7 +209,7 @@ Inline elstring els_set(elstring els, const void* str) { if (els == NULL) - els = XCALLOC(MTYPE_TMP, sizeof(elstring_t)) ; + els = els_new() ; els_set_nn(els, str) ; @@ -169,7 +230,6 @@ els_set_n_nn(elstring els, const void* body, ulen len) { els->body.cv = body ; els->len = len ; - els->term = false ; } ; /*------------------------------------------------------------------------------ @@ -183,7 +243,7 @@ Inline elstring els_set_n(elstring els, const void* body, ulen len) { if (els == NULL) - els = XCALLOC(MTYPE_TMP, sizeof(elstring_t)) ; + els = els_new() ; els_set_n_nn(els, body, len) ; @@ -203,31 +263,13 @@ els_clear(elstring els) { els->body.v = NULL ; els->len = 0 ; - els->term = false ; } ; } ; /*------------------------------------------------------------------------------ - * Release dynamically allocated elstring. - * - * Returns NULL. - * - * NB: it is the callers responsibility to free the contents of the elstring. - * if that is required, before freeing the elstring itself. - */ -Inline elstring -els_free(elstring els) -{ - if (els != NULL) - XFREE(MTYPE_TMP, els) ; - - return NULL ; -} ; - -/*------------------------------------------------------------------------------ - * Set elstring length. And set term false. + * Set elstring 'len'. * - * NB: it is the caller's responsibility to set a valid length !! + * NB: it is the caller's responsibility to set a valid body !! * * NB: elstring MUST NOT be NULL. */ @@ -235,120 +277,21 @@ Inline void els_set_len_nn(elstring els, ulen len) { els->len = len ; - els->term = false ; } ; /*------------------------------------------------------------------------------ - * Set elstring body. And set term false. + * Set elstring body. * * NB: it is the caller's responsibility to set a valid body !! * + * NB: it is the caller's responsibility to set a valid 'len'. + * * NB: elstring MUST NOT be NULL. */ Inline void els_set_body_nn(elstring els, const void* body) { els->body.cv = body ; - els->term = false ; -} ; - -/*------------------------------------------------------------------------------ - * Set elstring terminated. - * - * NB: it is the caller's responsibility to set a valid body !! - * - * NB: elstring MUST NOT be NULL. - */ -Inline void -els_set_term_nn(elstring els, bool term) -{ - els->term = term ; -} ; - -/*------------------------------------------------------------------------------ - * Compare two elstrings -- returns the usual -ve, 0, +ve cmp result. - */ -Inline int -els_cmp(elstring a, elstring b) -{ - const uchar* ap ; - const uchar* bp ; - ulen al, bl ; - ulen n ; - - ap = els_body(a) ; - bp = els_body(b) ; - al = els_len(a) ; - bl = els_len(b) ; - - n = (al <= bl) ? al : bl ; - - while (n) - { - int d = *ap++ - *bp++ ; - if (d != 0) - return d ; - --n ; - } ; - - return al < bl ? -1 : (al == bl) ? 0 : +1 ; -} ; - -/*------------------------------------------------------------------------------ - * Are two elstrings equal ? -- returns true if strings equal. - */ -Inline bool -els_equal(elstring a, elstring b) -{ - const uchar* ap ; - const uchar* bp ; - ulen n ; - - n = els_len(b) ; - if (n != els_len(a)) - return false ; - - ap = els_body(a) ; - bp = els_body(b) ; - - while (n) - { - if (*ap++ != *bp++) - return false ; - --n ; - } ; - - return true ; -} ; - -/*------------------------------------------------------------------------------ - * Is 'b' a leading substring of 'a' ? -- returns true if it is. - * - * If 'b' is empty it is always a leading substring. - */ -Inline int -els_substring(elstring a, elstring b) -{ - const uchar* ap ; - const uchar* bp ; - ulen n ; - - n = els_len(b) ; - if (n > els_len(a)) - return false ; - - ap = els_body(a) ; - bp = els_body(b) ; - - while (n) - { - if (*ap++ != *bp++) - return false ; - --n ; - } ; - - return true ; } ; #endif /* _ZEBRA_ELSTRING_H */ - @@ -19,9 +19,7 @@ * 02111-1307, USA. */ -#include <zebra.h> - -#include "zassert.h" +#include "misc.h" #include "heap.h" #include "memory.h" @@ -556,7 +556,7 @@ DEFUN (interface, #endif /* SUNOS_5 */ vty->index = ifp; - vty_set_node(vty, INTERFACE_NODE) ; + vty->node = INTERFACE_NODE ; return CMD_SUCCESS; } @@ -300,12 +300,12 @@ extern char *if_indextoname (unsigned int, char *); /* Exported variables. */ extern struct list *iflist; -extern struct cmd_element interface_desc_cmd; -extern struct cmd_element no_interface_desc_cmd; -extern struct cmd_element interface_cmd; -extern struct cmd_element no_interface_cmd; -extern struct cmd_element interface_pseudo_cmd; -extern struct cmd_element no_interface_pseudo_cmd; -extern struct cmd_element show_address_cmd; +extern struct cmd_command interface_desc_cmd; +extern struct cmd_command no_interface_desc_cmd; +extern struct cmd_command interface_cmd; +extern struct cmd_command no_interface_cmd; +extern struct cmd_command interface_pseudo_cmd; +extern struct cmd_command no_interface_pseudo_cmd; +extern struct cmd_command show_address_cmd; #endif /* _ZEBRA_IF_H */ diff --git a/lib/keychain.c b/lib/keychain.c index 2f8a0b77..57ccf7cc 100644 --- a/lib/keychain.c +++ b/lib/keychain.c @@ -238,7 +238,7 @@ DEFUN (key_chain, keychain = keychain_get (argv[0]); vty->index = keychain; - vty_set_node(vty, KEYCHAIN_NODE) ; + vty->node = KEYCHAIN_NODE ; return CMD_SUCCESS; } @@ -281,7 +281,7 @@ DEFUN (key, VTY_GET_INTEGER ("key identifier", index, argv[0]); key = key_get (keychain, index); vty->index_sub = key; - vty_set_node(vty, KEYCHAIN_KEY_NODE) ; + vty->node = KEYCHAIN_KEY_NODE ; return CMD_SUCCESS; } @@ -309,7 +309,7 @@ DEFUN (no_key, key_delete (keychain, key); - vty_set_node(vty, KEYCHAIN_NODE) ; + vty->node = KEYCHAIN_NODE ; return CMD_SUCCESS; } @@ -850,16 +850,20 @@ DEFUN (send_lifetime_duration_month_day, static struct cmd_node keychain_node = { - KEYCHAIN_NODE, - "%s(config-keychain)# ", - 1 + .node = KEYCHAIN_NODE, + .prompt ="%s(config-keychain)# ", + + .config_to_vtysh = true }; static struct cmd_node keychain_key_node = { - KEYCHAIN_KEY_NODE, - "%s(config-keychain-key)# ", - 1 + .node = KEYCHAIN_KEY_NODE, + .prompt = "%s(config-keychain-key)# ", + + .parent = KEYCHAIN_NODE, + + .config_to_vtysh = true }; static int diff --git a/lib/keystroke.c b/lib/keystroke.c index c6eb811e..13b79e39 100644 --- a/lib/keystroke.c +++ b/lib/keystroke.c @@ -272,7 +272,7 @@ struct keystroke_state struct keystroke_stream { - vio_fifo_t fifo ; /* the keystrokes */ + vio_fifo_t fifo ; /* the keystrokes -- embedded */ keystroke_callback* iac_callback ; void* iac_callback_context ; @@ -301,7 +301,8 @@ enum { keystroke_buffer_len = 2000 } ; /* should be plenty ! */ static void keystroke_in_push(keystroke_stream stream, uint8_t u) ; static void keystroke_in_pop(keystroke_stream stream) ; -inline static int keystroke_set_null(keystroke_stream stream, keystroke stroke); +inline static bool keystroke_set_null(keystroke_stream stream, + keystroke stroke) ; inline static uint8_t keystroke_get_byte(keystroke_stream stream) ; inline static void keystroke_add_raw(keystroke_stream stream, uint8_t u) ; static void keystroke_put_char(keystroke_stream stream, uint32_t u) ; @@ -368,7 +369,7 @@ keystroke_stream_new(uint8_t csi_char, keystroke_callback* iac_callback, stream->iac_callback = iac_callback ; stream->iac_callback_context = iac_callback_context ; - vio_fifo_init_new(&stream->fifo, keystroke_buffer_len) ; + vio_fifo_init_new(stream->fifo, keystroke_buffer_len) ; stream->CSI = (csi_char != '\0') ? csi_char : 0x1B ; @@ -385,7 +386,7 @@ keystroke_stream_free(keystroke_stream stream) { if (stream != NULL) { - vio_fifo_reset_keep(&stream->fifo) ; + vio_fifo_reset(stream->fifo, keep_it) ; XFREE(MTYPE_KEY_STREAM, stream) ; } ; @@ -407,7 +408,7 @@ keystroke_stream_free(keystroke_stream stream) extern bool keystroke_stream_empty(keystroke_stream stream) { - return (stream == NULL) || vio_fifo_empty(&stream->fifo) ; + return (stream == NULL) || vio_fifo_empty(stream->fifo) ; } ; /*------------------------------------------------------------------------------ @@ -428,7 +429,7 @@ keystroke_stream_eof(keystroke_stream stream) * is converted to a broken keystroke and placed in the stream. * (So eof_met => no partial keystroke.) */ - return (stream == NULL) || (vio_fifo_empty(&stream->fifo) && stream->eof_met); + return (stream == NULL) || (vio_fifo_empty(stream->fifo) && stream->eof_met); } ; /*------------------------------------------------------------------------------ @@ -441,7 +442,7 @@ keystroke_stream_eof(keystroke_stream stream) extern void keystroke_stream_set_eof(keystroke_stream stream) { - vio_fifo_reset_keep(&stream->fifo) ; + vio_fifo_reset(stream->fifo, keep_it) ; stream->eof_met = true ; /* essential information */ @@ -858,10 +859,10 @@ keystroke_in_pop(keystroke_stream stream) /*============================================================================== * Fetch next keystroke from keystroke stream * - * Returns: 1 => have a stroke type != ks_null - * 0 => stroke type is ks_null (may be EOF). + * Returns: true => have a stroke type != ks_null + * false => stroke type is ks_null (may be EOF). */ -extern int +extern bool keystroke_get(keystroke_stream stream, keystroke stroke) { int b ; @@ -869,7 +870,7 @@ keystroke_get(keystroke_stream stream, keystroke stroke) uint8_t* e ; /* Get first byte and deal with FIFO empty response */ - b = vio_fifo_get_byte(&stream->fifo) ; + b = vio_fifo_get_byte(stream->fifo) ; if (b < 0) return keystroke_set_null(stream, stroke) ; @@ -884,7 +885,7 @@ keystroke_get(keystroke_stream stream, keystroke stroke) stroke->len = 1 ; stroke->buf[0] = b ; - return 1 ; + return true ; } ; /* Sex the compound keystroke */ @@ -954,7 +955,7 @@ keystroke_get(keystroke_stream stream, keystroke stroke) zabort("unknown keystroke type") ; } ; - return 1 ; + return true ; } ; /*------------------------------------------------------------------------------ @@ -962,7 +963,7 @@ keystroke_get(keystroke_stream stream, keystroke stroke) * * Returns: 0 */ -inline static int +inline static bool keystroke_set_null(keystroke_stream stream, keystroke stroke) { stroke->type = ks_null ; @@ -971,7 +972,7 @@ keystroke_set_null(keystroke_stream stream, keystroke stroke) stroke->flags = 0 ; stroke->len = 0 ; - return 0 ; + return false ; } ; /*------------------------------------------------------------------------------ @@ -983,7 +984,7 @@ keystroke_set_null(keystroke_stream stream, keystroke stroke) inline static uint8_t keystroke_get_byte(keystroke_stream stream) { - int b = vio_fifo_get_byte(&stream->fifo) ; + int b = vio_fifo_get_byte(stream->fifo) ; passert(b >= 0) ; @@ -1013,7 +1014,7 @@ static void keystroke_put_char(keystroke_stream stream, uint32_t u) { if (u < 0x80) - vio_fifo_put_byte(&stream->fifo, (uint8_t)u) ; + vio_fifo_put_byte(stream->fifo, (uint8_t)u) ; else { uint8_t buf[4] ; @@ -1140,12 +1141,12 @@ keystroke_put(keystroke_stream stream, enum keystroke_type type, bool broken, type |= kf_truncated ; } ; - vio_fifo_put_byte(&stream->fifo, + vio_fifo_put_byte(stream->fifo, kf_compound | (broken ? kf_broken : 0) | type) ; - vio_fifo_put_byte(&stream->fifo, len) ; + vio_fifo_put_byte(stream->fifo, len) ; if (len > 0) - vio_fifo_put(&stream->fifo, (void*)bytes, len) ; + vio_fifo_put_bytes(stream->fifo, (void*)bytes, len) ; } ; /*------------------------------------------------------------------------------ diff --git a/lib/keystroke.h b/lib/keystroke.h index 10494df0..67c6cd0f 100644 --- a/lib/keystroke.h +++ b/lib/keystroke.h @@ -47,6 +47,7 @@ enum keystroke_type ks_type_reserved = 0x0F, } ; +typedef enum keystroke_type keystroke_type ; CONFIRM(ks_type_count <= ks_type_reserved) ; enum keystroke_null @@ -68,8 +69,9 @@ enum keystroke_flags kf_type_mask = 0x0F, /* extraction of type */ } ; +typedef enum keystroke_type keystroke_flags ; -CONFIRM(ks_type_reserved == kf_type_mask) ; +CONFIRM(ks_type_reserved == (keystroke_type)kf_type_mask) ; typedef struct keystroke* keystroke ; typedef struct keystroke_stream* keystroke_stream ; @@ -182,7 +184,7 @@ keystroke_stream_eof(keystroke_stream stream) ; extern void keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len, keystroke steal) ; -extern int +extern bool keystroke_get(keystroke_stream stream, keystroke stroke) ; #endif /* _ZEBRA_KEYSTROKE_H */ diff --git a/lib/list_util.c b/lib/list_util.c index 8ce01c03..f73244a4 100644 --- a/lib/list_util.c +++ b/lib/list_util.c @@ -49,28 +49,28 @@ * * Note again the cast to (void**). * - * Returns: 0 => OK -- removed item from list (OR item == NULL) - * -1 => item not found on list + * Returns: true => removed item from list + * false => item not found on list (or item == NULL) */ -extern int +extern bool ssl_del_func(void* p_this, void* item, size_t link_offset) { void* this ; if (item == NULL) - return 0 ; + return false ; while ((this = *(void**)p_this) != item) { if (this == NULL) - return -1 ; + return false ; p_this = _sl_p_next(this, link_offset) ; } ; *(void**)p_this = _sl_next(item, link_offset) ; - return 0 ; + return true ; } ; /*============================================================================== diff --git a/lib/list_util.h b/lib/list_util.h index bd796779..f6a01d93 100644 --- a/lib/list_util.h +++ b/lib/list_util.h @@ -133,10 +133,10 @@ struct dl_void_base_pair base_pair(void*) ; * * ssl_del(base, item, next) -- delete from list * - * Treat as function returning int. Does nothing if the item is NULL. + * Treat as function returning bool. Does nothing if the item is NULL. * - * Returns: 0 => OK -- removed item from list (OR item == NULL) - * -1 => item not found on list + * Returns: true => removed item from list + * false => item not found on list (or item was NULL) * * ssl_del_head(base, next) -- delete head of list * @@ -237,7 +237,7 @@ struct dl_void_base_pair base_pair(void*) ; (base) = item ; \ } while (0) -extern int ssl_del_func(void* p_this, void* obj, size_t link_offset) +extern bool ssl_del_func(void* p_this, void* obj, size_t link_offset) __attribute__((noinline)) ; #define ssl_del(base, item, next) \ @@ -506,7 +506,7 @@ extern int ssl_del_func(void* p_this, void* obj, size_t link_offset) * * Treat as function returning void*. * - * Returns old head in dst and as return from "function". + * Returns old tail in dst and as return from "function". * * Returns NULL and sets dst == NULL if list is empty. * @@ -26,9 +26,8 @@ #include "log.h" #include "vty.h" -#include "uty.h" #include "memory.h" -#include "command.h" +#include "command_local.h" #ifndef SUNOS_5 #include <sys/un.h> #endif @@ -26,6 +26,7 @@ #define _ZEBRA_LOG_H #include <syslog.h> +#include <stdio.h> #include "vargs.h" #include "pthread_safe.h" @@ -109,13 +110,6 @@ extern struct zlog *openzlog (const char *progname, zlog_proto_t protocol, /* Close zlog function. */ extern void closezlog (struct zlog *zl); -/* GCC have printf type attribute check. */ -#ifdef __GNUC__ -#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) -#else -#define PRINTF_ATTRIBUTE(a,b) -#endif /* __GNUC__ */ - /* Generic function for zlog. */ extern void zlog (struct zlog *zl, int priority, const char *format, ...) PRINTF_ATTRIBUTE(3, 4); diff --git a/lib/memory.c b/lib/memory.c index c13404d2..e15492c9 100644 --- a/lib/memory.c +++ b/lib/memory.c @@ -72,7 +72,7 @@ mem_tracker_zeroise(struct mem_tracker* mem) memset(mem, 0, sizeof(struct mem_tracker)) ; } ; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER #include "mem_tracker.c" #endif @@ -130,7 +130,7 @@ zmalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME) else { mstat.mt[mtype].alloc++; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER mem_md_malloc(mtype, memory, size, name) ; #endif UNLOCK ; @@ -159,7 +159,7 @@ zcalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME) else { mstat.mt[mtype].alloc++; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER mem_md_malloc(mtype, memory, size, name) ; #endif UNLOCK ; @@ -188,7 +188,7 @@ zrealloc (enum MTYPE mtype, void *ptr, size_t size MEMORY_TRACKER_NAME) { if (ptr == NULL) mstat.mt[mtype].alloc++; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER mem_md_realloc(mtype, ptr, memory, size, name) ; #endif UNLOCK ; @@ -210,7 +210,7 @@ zfree (enum MTYPE mtype, void *ptr) assert(mstat.mt[mtype].alloc > 0) ; mstat.mt[mtype].alloc--; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER mem_md_free(mtype, ptr) ; #endif @@ -230,7 +230,7 @@ zstrdup (enum MTYPE mtype, const char *str MEMORY_TRACKER_NAME) LOCK ; - dup = strdup (str); + dup = strdup (str ? str : ""); if (dup == NULL) { UNLOCK ; @@ -239,7 +239,7 @@ zstrdup (enum MTYPE mtype, const char *str MEMORY_TRACKER_NAME) else { mstat.mt[mtype].alloc++; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER mem_md_malloc(mtype, dup, strlen(str)+1, name) ; #endif UNLOCK ; @@ -443,7 +443,7 @@ show_memory_type_vty (struct vty *vty, const char* name, vty_out (vty, "-----------------------------%s", VTY_NEWLINE) ; vty_out (vty, "%-30s:", name) ; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER show_memory_tracker_detail(vty, mt, alloc) ; #else vty_out (vty, " %10ld", alloc) ; @@ -464,13 +464,13 @@ show_memory_vty (struct vty *vty, struct memory_list *m, struct mlist* ml, struct mem_tracker mem_one ; struct mem_tracker* mt ; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER struct mem_type_tracker mem_tt ; #endif LOCK ; mst = mstat ; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER mem_tt = mem_type_tracker ; #endif UNLOCK ; @@ -499,7 +499,7 @@ show_memory_vty (struct vty *vty, struct memory_list *m, struct mlist* ml, else { alloc = mst.mt[m->index].alloc ; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER mt = &(mem_tt.mt[m->index]) ; #else mt = &mem_one ; @@ -580,7 +580,7 @@ DEFUN_CALL (show_memory_summary, "Memory statistics\n" "Summary memory statistics\n") { -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER show_memory_tracker_summary(vty) ; #else long alloc = 0 ; @@ -613,7 +613,7 @@ DEFUN_CALL (show_memory_all, #ifdef HAVE_MALLINFO needsep |= show_memory_mallinfo (vty); #endif /* HAVE_MALLINFO */ -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER needsep |= show_memory_tracker_summary(vty) ; #endif diff --git a/lib/memory.h b/lib/memory.h index 6cc95a5b..4fccb079 100644 --- a/lib/memory.h +++ b/lib/memory.h @@ -58,11 +58,21 @@ typedef enum MTYPE mtype_t ; mtype_zstrdup (__FILE__, __LINE__, (mtype), (str)) #else -#ifdef QDEBUG -#define MEMORY_TRACKER 1 +#ifdef MEMORY_TRACKER /* Can be forced from outside */ +# if MEMORY_TRACKER +# define MEMORY_TRACKER 1 /* Force 1 or 0 */ +#else +# define MEMORY_TRACKER 0 +# endif +#else +# ifdef QDEBUG +# define MEMORY_TRACKER 1 /* Follow QDEBUG */ +#else +# define MEMORY_TRACKER 0 +# endif #endif -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER #define MEMORY_TRACKER_NAME , const char* name #define MEMORY_TRACKER_FUNC , __func__ #else diff --git a/lib/memtypes.c b/lib/memtypes.c index e5a591d6..7e73e4c1 100644 --- a/lib/memtypes.c +++ b/lib/memtypes.c @@ -16,7 +16,8 @@ struct memory_list memory_list_lib[] = { { MTYPE_TMP, "Temporary memory" }, { MTYPE_STRING, "String (general)" }, - { MTYPE_STRVEC, "String vector" }, + { MTYPE_CMD_ITEM, "Command item" }, + { MTYPE_CMD_STRING, "Command string" }, { MTYPE_VECTOR, "Vector structure" }, { MTYPE_VECTOR_BODY, "Vector body" }, { MTYPE_SYMBOL_TABLE, "Symbol Table structure" }, @@ -44,13 +45,14 @@ struct memory_list memory_list_lib[] = { MTYPE_QPN_NEXUS, "qtn nexus" }, { MTYPE_TSD, "Thread specific data" }, { MTYPE_VTY, "VTY" }, + { MTYPE_CMD_EXEC, "Command exec context" }, { MTYPE_CMD_PARSED, "Parsed command" }, { MTYPE_TOKEN, "Command token" }, - { MTYPE_MARSHAL, "marshalled commands" }, { MTYPE_VTY_OUT_BUF, "VTY output buffer" }, { MTYPE_VTY_HIST, "VTY history" }, { MTYPE_VTY_NAME, "VTY name" }, { MTYPE_KEY_STREAM, "Keystroke Stream" }, + { MTYPE_VTY_CLI, "VTY CLI" }, { MTYPE_VIO_FIFO, "VTY IO FIFO" }, { MTYPE_VIO_FIFO_LUMP, "VTY IO FIFO Lump" }, { MTYPE_VIO_LC, "VTY IO Line Control" }, @@ -87,7 +89,6 @@ struct memory_list memory_list_lib[] = { MTYPE_ROUTE_MAP_RULE, "Route map rule" }, { MTYPE_ROUTE_MAP_RULE_STR, "Route map rule str" }, { MTYPE_ROUTE_MAP_COMPILED, "Route map compiled" }, - { MTYPE_DESC, "Command desc" }, { MTYPE_KEY, "Key" }, { MTYPE_KEYCHAIN, "Key chain" }, { MTYPE_IF_RMAP, "Interface route map" }, @@ -22,13 +22,16 @@ #ifndef _ZEBRA_MISC_H #define _ZEBRA_MISC_H +#include "zconfig.h" + /* Stuff which we generally expect to have */ -#include <stddef.h> -#include <stdint.h> #include <limits.h> +#include <string.h> + #include <stdbool.h> +#include <stddef.h> +#include <stdint.h> #include <stdlib.h> -#include <string.h> #include "zassert.h" @@ -43,6 +46,9 @@ */ #define Private extern +/* For use in switch/case */ +#define fall_through + /* Other names of true/false */ enum on_off { @@ -67,14 +73,26 @@ enum free_keep } ; typedef enum free_keep free_keep_b ; -/* We really want to be able to assume that an int is at least 32 bits */ -CONFIRM(UINT_MAX >= 0xFFFFFFFF) ; +/* We really want to be able to assume that an int is at least 32 bits + * and that a long is at least 64 bits ! + */ +CONFIRM(UINT_MAX >= 0xFFFFFFFF) ; +CONFIRM(ULONG_MAX >= 0xFFFFFFFFFFFFFFFF) ; /* Some useful shorthand */ typedef unsigned char byte ; typedef unsigned char uchar ; + typedef unsigned int uint ; typedef unsigned int usize ; typedef unsigned int ulen ; +typedef unsigned long ulong ; + +/* cvp == const void* -- ptr to constant void + * cvp* == void * const* -- ptr to ptr to constant void + * const cvp* == void const* const* -- ptr to constant ptr to constant void + */ +typedef const void* cvp ; + #endif /* _ZEBRA_MISC_H */ diff --git a/lib/mqueue.c b/lib/mqueue.c index 8fa9fbd5..c7037a64 100644 --- a/lib/mqueue.c +++ b/lib/mqueue.c @@ -254,7 +254,7 @@ mqueue_init_new(mqueue_queue mq, enum mqueue_queue_type type) extern void mqueue_empty(mqueue_queue mq) { - mqueue_revoke(mq, NULL) ; + mqueue_revoke(mq, NULL, 0) ; assert((mq->head == NULL) && (mq->count == 0)) ; } ; @@ -268,8 +268,11 @@ mqueue_empty(mqueue_queue mq) * NB: there MUST NOT be ANY waiters ! */ extern mqueue_queue -mqueue_reset(mqueue_queue mq, int free_structure) +mqueue_reset(mqueue_queue mq, free_keep_b free_structure) { + if (mq == NULL) + return NULL ; + mqueue_empty(mq) ; passert(mq->waiters == 0) ; @@ -327,13 +330,10 @@ mqueue_local_init_new(mqueue_local_queue lmq) * * Dequeues entries and dispatches them "mqb_destroy", to empty the queue. * - * See: mqueue_local_reset_keep(lmq) - * mqueue_local_reset_free(lmq) - * * Returns address of Local Message Queue */ extern mqueue_local_queue -mqueue_local_reset(mqueue_local_queue lmq, int free_structure) +mqueue_local_reset(mqueue_local_queue lmq, free_keep_b free_structure) { mqueue_block mqb ; @@ -533,7 +533,7 @@ static void mqueue_dequeue_signal(mqueue_queue mq, mqueue_thread_signal mtsig) ; * never be any waiters... so no kicking is ever done. */ extern void -mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, enum mqb_rank priority) +mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, mqb_rank_b priority) { if (mq == NULL) return mqb_dispatch_destroy(mqb) ; @@ -755,35 +755,41 @@ done: * * Revokes by calling mqb_dispatch_destroy(). * - * During a revoke() operation more items may be enqueued, but no other mqueue - * operations may be performed. Enqueued items may promptly be revoked, except - * for priority items if the revoke operation has already moved past the last - * priority item. + * NB: for safety, holds the queue locked for the duration of the revoke + * operation. + * + * If the destroy code can handle it, this means that can revoke stuff + * from one thread even though it is usually only dequeued by another. + * + * The danger is that if queues get very long, and many revokes happen, + * may (a) spend a lot of time scanning the message queue, which stops + * other threads as soon as they try to enqueue anything, and (b) if this + * happens a lot, could end up in an O(n^2) thing scanning the message + * queue once for each revoked object type. * * If mq is NULL, does nothing. + * + * If num > 0, stops after revoking that many messages. + * + * Returns: number of messages revoked. */ -extern void -mqueue_revoke(mqueue_queue mq, void* arg0) +extern int +mqueue_revoke(mqueue_queue mq, void* arg0, int num) { mqueue_block mqb ; mqueue_block prev ; + int did ; if (mq == NULL) - return ; + return 0 ; qpt_mutex_lock(&mq->mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ + did = 0 ; prev = NULL ; - while (1) + mqb = mq->head ; + while (mqb != NULL) { - if (prev == NULL) - mqb = mq->head ; - else - mqb = prev->next ; - - if (mqb == NULL) - break ; - if ((arg0 == NULL) || (arg0 == mqb->arg0)) { assert(mq->count > 0) ; @@ -800,16 +806,30 @@ mqueue_revoke(mqueue_queue mq, void* arg0) mq->tail_priority = prev ; --mq->count ; + ++did ; + + mqb_dispatch_destroy(mqb) ; - qpt_mutex_unlock(&mq->mutex) ; - mqb_dispatch_destroy(mqb) ; - qpt_mutex_lock(&mq->mutex) ; + if (num == 1) + break ; + else if (num > 1) + --num ; + + if (prev == NULL) + mqb = mq->head ; + else + mqb = prev->next ; } else - prev = mqb ; + { + prev = mqb ; + mqb = mqb->next ; + } ; } ; qpt_mutex_unlock(&mq->mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ + + return did ; } ; /*------------------------------------------------------------------------------ @@ -930,8 +950,11 @@ mqueue_thread_signal_init(mqueue_thread_signal mqt, qpt_thread_t thread, * Otherwise zeroises the structure, and returns address of same. */ extern mqueue_thread_signal -mqueue_thread_signal_reset(mqueue_thread_signal mqt, int free_structure) +mqueue_thread_signal_reset(mqueue_thread_signal mqt, free_keep_b free_structure) { + if (mqt == NULL) + return NULL ; + passert(mqt->prev == NULL) ; if (free_structure) diff --git a/lib/mqueue.h b/lib/mqueue.h index 68dceb15..f33af564 100644 --- a/lib/mqueue.h +++ b/lib/mqueue.h @@ -191,83 +191,48 @@ struct mqueue_local_queue * Functions */ -extern void -mqueue_initialise(void) ; - -extern void -mqueue_finish(void) ; - -extern mqueue_queue -mqueue_init_new(mqueue_queue mq, enum mqueue_queue_type type) ; - -extern void -mqueue_empty(mqueue_queue mq) ; - -extern mqueue_queue -mqueue_reset(mqueue_queue mq, int free_structure) ; - -#define mqueue_reset_keep(mq) mqueue_reset(mq, 0) -#define mqueue_reset_free(mq) mqueue_reset(mq, 1) - -extern mqueue_local_queue -mqueue_local_init_new(mqueue_local_queue lmq) ; - -extern mqueue_local_queue -mqueue_local_reset(mqueue_local_queue lmq, int free_structure) ; - -#define mqueue_local_reset_keep(lmq) mqueue_local_reset(lmq, 0) -#define mqueue_local_reset_free(lmq) mqueue_local_reset(lmq, 1) - -extern void -mqueue_set_timeout_interval(mqueue_queue mq, qtime_t interval) ; - -extern mqueue_thread_signal -mqueue_thread_signal_init(mqueue_thread_signal mqt, qpt_thread_t thread, - int signum) ; -mqueue_thread_signal -mqueue_thread_signal_reset(mqueue_thread_signal mqt, int free_structure) ; - -#define mqueue_thread_signal_reset_keep(mqt) mqueue_thread_signal_reset(mqt, 0) -#define mqueue_thread_signal_reset_free(mqt) mqueue_thread_signal_reset(mqt, 1) - -extern mqueue_block -mqb_init_new(mqueue_block mqb, mqueue_action action, void* arg0) ; - -extern mqueue_block -mqb_re_init(mqueue_block mqb, mqueue_action action, void* arg0) ; - -extern void -mqb_free(mqueue_block mqb) ; +extern void mqueue_initialise(void) ; +extern void mqueue_finish(void) ; + +extern mqueue_queue mqueue_init_new(mqueue_queue mq, + enum mqueue_queue_type type) ; +extern void mqueue_empty(mqueue_queue mq) ; +extern mqueue_queue mqueue_reset(mqueue_queue mq, free_keep_b free_structure) ; + +extern mqueue_local_queue mqueue_local_init_new(mqueue_local_queue lmq) ; +extern mqueue_local_queue mqueue_local_reset(mqueue_local_queue lmq, + free_keep_b free_structure) ; + +extern void mqueue_set_timeout_interval(mqueue_queue mq, qtime_t interval) ; +extern mqueue_thread_signal mqueue_thread_signal_init(mqueue_thread_signal mqt, + qpt_thread_t thread, int signum) ; +mqueue_thread_signal mqueue_thread_signal_reset(mqueue_thread_signal mqt, + free_keep_b free_structure) ; + +extern mqueue_block mqb_init_new(mqueue_block mqb, mqueue_action action, + void* arg0) ; +extern mqueue_block mqb_re_init(mqueue_block mqb, mqueue_action action, + void* arg0) ; +extern void mqb_free(mqueue_block mqb) ; enum mqb_rank { mqb_priority = true, mqb_ordinary = false } ; +typedef enum mqb_rank mqb_rank_b ; -extern void -mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, enum mqb_rank priority) ; - -extern mqueue_block -mqueue_dequeue(mqueue_queue mq, int wait, void* arg) ; +extern void mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, + mqb_rank_b priority) ; +extern mqueue_block mqueue_dequeue(mqueue_queue mq, int wait, void* arg) ; +extern int mqueue_revoke(mqueue_queue mq, void* arg0, int num) ; -extern void -mqueue_revoke(mqueue_queue mq, void* arg0) ; - -extern int -mqueue_done_waiting(mqueue_queue mq, mqueue_thread_signal mtsig) ; - -extern void -mqueue_local_enqueue(mqueue_local_queue lmq, mqueue_block mqb) ; - -extern void -mqueue_local_enqueue_head(mqueue_local_queue lmq, mqueue_block mqb) ; - -Inline mqueue_block -mqueue_local_head(mqueue_local_queue lmq) ; +extern int mqueue_done_waiting(mqueue_queue mq, mqueue_thread_signal mtsig) ; -extern mqueue_block -mqueue_local_dequeue(mqueue_local_queue lmq) ; +extern void mqueue_local_enqueue(mqueue_local_queue lmq, mqueue_block mqb) ; +extern void mqueue_local_enqueue_head(mqueue_local_queue lmq, mqueue_block mqb) ; +Inline mqueue_block mqueue_local_head(mqueue_local_queue lmq) ; +extern mqueue_block mqueue_local_dequeue(mqueue_local_queue lmq) ; /*============================================================================== * Access functions for mqueue_block fields -- mqb_set_xxx/mqb_get_xxx diff --git a/lib/node_type.h b/lib/node_type.h deleted file mode 100644 index b1f26187..00000000 --- a/lib/node_type.h +++ /dev/null @@ -1,82 +0,0 @@ -/* Command handler node_type stuff -- header - * Copyright (C) 1997, 98 Kunihiro Ishiguro - * - * This file is part of GNU Zebra. - * - * GNU Zebra is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published - * by the Free Software Foundation; either version 2, or (at your - * option) any later version. - * - * GNU Zebra is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * 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. - */ - -#ifndef _ZEBRA_NODE_TYPE_H -#define _ZEBRA_NODE_TYPE_H - -/* There are some command levels which called from command node. */ -enum node_type -{ - AUTH_NODE, /* Authentication mode of vty interface. */ - RESTRICTED_NODE, /* Restricted view mode */ - VIEW_NODE, /* View node. Default mode of vty interface. */ - AUTH_ENABLE_NODE, /* Authentication mode for change enable. */ - ENABLE_NODE, /* Enable node. */ - - MIN_DO_SHORTCUT_NODE = ENABLE_NODE, - /* May not "do xxx" at any node lower */ - MAX_NON_CONFIG_NODE = ENABLE_NODE, - /* May not be higher than this without owning - * the configuration symbol of power */ - - CONFIG_NODE, /* Config node. Default mode of config file. */ - - MIN_CONTEXT_NODE = CONFIG_NODE, - /* May not change context to any node lower */ - - SERVICE_NODE, /* Service node. */ - DEBUG_NODE, /* Debug node. */ - AAA_NODE, /* AAA node. */ - KEYCHAIN_NODE, /* Key-chain node. */ - KEYCHAIN_KEY_NODE, /* Key-chain key node. */ - INTERFACE_NODE, /* Interface mode node. */ - ZEBRA_NODE, /* zebra connection node. */ - TABLE_NODE, /* rtm_table selection node. */ - RIP_NODE, /* RIP protocol mode node. */ - RIPNG_NODE, /* RIPng protocol mode node. */ - BGP_NODE, /* BGP protocol mode which includes BGP4+ */ - BGP_VPNV4_NODE, /* BGP MPLS-VPN PE exchange. */ - BGP_IPV4_NODE, /* BGP IPv4 unicast address family. */ - BGP_IPV4M_NODE, /* BGP IPv4 multicast address family. */ - BGP_IPV6_NODE, /* BGP IPv6 address family */ - BGP_IPV6M_NODE, /* BGP IPv6 multicast address family. */ - OSPF_NODE, /* OSPF protocol mode */ - OSPF6_NODE, /* OSPF protocol for IPv6 mode */ - ISIS_NODE, /* ISIS protocol mode */ - MASC_NODE, /* MASC for multicast. */ - IRDP_NODE, /* ICMP Router Discovery Protocol mode. */ - IP_NODE, /* Static ip route node. */ - ACCESS_NODE, /* Access list node. */ - PREFIX_NODE, /* Prefix list node. */ - ACCESS_IPV6_NODE, /* Access list node. */ - PREFIX_IPV6_NODE, /* Prefix list node. */ - AS_LIST_NODE, /* AS list node. */ - COMMUNITY_LIST_NODE, /* Community list node. */ - RMAP_NODE, /* Route map node. */ - SMUX_NODE, /* SNMP configuration node. */ - DUMP_NODE, /* Packet dump node. */ - FORWARDING_NODE, /* IP forwarding node. */ - PROTOCOL_NODE, /* protocol filtering node */ - VTY_NODE, /* Vty node. */ -} ; -typedef enum node_type node_type_t ; - -#endif /* _ZEBRA_NODE_TYPE_H */ diff --git a/lib/pthread_safe.c b/lib/pthread_safe.c index c4ab9679..e2c7cdc0 100644 --- a/lib/pthread_safe.c +++ b/lib/pthread_safe.c @@ -299,7 +299,8 @@ errtox(strerror_t* st, int err, int len, int want) if ((len + ql) <= qfs_left(&qfs)) /* accounting for "quotes" */ qfs_printf(&qfs, "%s%s%s", q, errm, q) ; else - qfs_printf(&qfs, "%s%.*s...%s", q, qfs_left(&qfs) - ql - 3, errm, q) ; + qfs_printf(&qfs, "%s%.*s...%s", + q, (int)(qfs_left(&qfs) - ql - 3), errm, q) ; /* -ve precision is ignored ! */ } ; diff --git a/lib/qfstring.c b/lib/qfstring.c index 3db3fedf..f40f1417 100644 --- a/lib/qfstring.c +++ b/lib/qfstring.c @@ -26,10 +26,10 @@ */ /*------------------------------------------------------------------------------ - * Initialise qf_str -- to given size (which includes the '\n') + * Initialise qf_str -- to given size (which includes the '\0') * * Sets pointers and terminates an empty string with one byte reserved for the - * terminating '\n'. + * terminating '\0'. * * This operation is async-signal-safe. */ @@ -47,12 +47,12 @@ qfs_init(qf_str qfs, char* str, size_t size) /*------------------------------------------------------------------------------ * Initialise qf_str which already contains string -- to given size (which - * includes the '\n') + * includes the '\0') * * This may be used to prepare for appending to a buffer which already contains * something. * - * Sets pointers, setting the write pointer to the existing terminating '\n'. + * Sets pointers, setting the write pointer to the existing terminating '\0'. * * This operation is async-signal-safe. */ @@ -331,7 +331,7 @@ qfs_pointer(qf_str qfs, void* p_val, enum pf_flags flags, * * If the precision is < 0 it is ignored (unless pf_hex, see below). * - * If the precision is 0 it is ignored unless bf_precision is set. + * If the precision is 0 it is ignored unless pf_precision is set. * * Precedence issues: * @@ -776,7 +776,7 @@ qfs_vprintf(qf_str qfs, const char *format, va_list args) if (precision < 0) { precision = 0 ; - flags &= ~pf_precision ; + flags &= ~pf_precision ; /* completely ignore */ } ; } else @@ -885,7 +885,7 @@ qfs_vprintf(qf_str qfs, const char *format, va_list args) * %s handler -- tolerates NULL pointer * * Accepts: width - * precision + * precision -- ignored if < 0 * pf_precision -- explicit precision * * Rejects: pf_commas -- "'" seen @@ -913,6 +913,12 @@ qfs_arg_string(qf_str qfs, va_list args, enum pf_flags flags, if (flags != (flags & pf_precision)) return pfp_failed ; + if (precision < 0) /* make sure */ + { + precision = 0 ; + flags &= ~pf_precision ; + } ; + len = (src != NULL) ? strlen(src) : 0 ; if (((precision > 0) || (flags & pf_precision)) && (len > precision)) len = precision ; diff --git a/lib/qiovec.c b/lib/qiovec.c index 546dfcb0..38ea0dbb 100644 --- a/lib/qiovec.c +++ b/lib/qiovec.c @@ -18,16 +18,30 @@ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ +#include "zconfig.h" /* Otherwise IOV_MAX is not defined ! */ -#include "zebra.h" +#include <errno.h> +#include "misc.h" #include "memory.h" -#include "zassert.h" #include "miyagi.h" #include "qiovec.h" /*============================================================================== + * IOV_MAX is defined by POSIX to appear in <limits.h>. + * + * This does not appear unless "zconfig.h" is included, first... + * ...but although it compiles OK, Eclipse cannot see where the value has + * come from. + */ +#ifdef IOV_MAX /* Stops Eclipse whinging */ +#if IOV_MAX < 64 /* check for a reasonable value */ +#error IOV_MAX < 64 +#endif +#endif + +/*============================================================================== * Initialise, allocate and reset qiovec */ diff --git a/lib/qiovec.h b/lib/qiovec.h index f1498146..230198f3 100644 --- a/lib/qiovec.h +++ b/lib/qiovec.h @@ -22,9 +22,8 @@ #ifndef _ZEBRA_QIOVEC_H #define _ZEBRA_QIOVEC_H -#include "zebra.h" - #include "misc.h" +#include <sys/uio.h> /* iovec stuff */ /*============================================================================== * Flexible size "struct iovec" diff --git a/lib/qpath.c b/lib/qpath.c index f6157b89..07c294de 100644 --- a/lib/qpath.c +++ b/lib/qpath.c @@ -68,7 +68,7 @@ qpath_init_new(qpath qp, const char* path) /* Worry about fields other than the path */ - qs_init_new(&qp->path, 0) ; + qs_init_new(qp->path, 0) ; if (path != NULL) qpath_set(qp, path) ; @@ -119,7 +119,7 @@ qpath_set(qpath qp, const char* path) if (path != NULL) qs_set(&qp->path, path) ; else - qs_clear(&qp->path) ; + qs_clear(qp->path) ; /* Worry about fields other than the path */ } ; @@ -521,7 +521,7 @@ qpath_push_str(qpath qp, const char* path) len = 0 ; /* Worry about whether need to add a '/' to the path before pushing */ - sp = qs_chars(qs) ; + sp = qs_char(qs) ; ep = qs_ep_char(qs) ; /* points at trailing '\0' */ if (sp == NULL) diff --git a/lib/qpnexus.c b/lib/qpnexus.c index 3b014be2..001d5049 100644 --- a/lib/qpnexus.c +++ b/lib/qpnexus.c @@ -18,9 +18,8 @@ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ - -#include <zebra.h> -#include <stdbool.h> +#include "misc.h" +#include <signal.h> #include "qpnexus.h" #include "memory.h" @@ -91,7 +90,7 @@ qpn_add_hook_function(qpn_hook_list list, void* hook) * object is empty or otherwise out of action. */ extern qpn_nexus -qpn_reset(qpn_nexus qpn, bool free_structure) +qpn_reset(qpn_nexus qpn, free_keep_b free_structure) { qps_file qf; qtimer qtr; @@ -115,11 +114,8 @@ qpn_reset(qpn_nexus qpn, bool free_structure) qpn->selection = NULL ; } - if (qpn->queue != NULL) - qpn->queue = mqueue_reset(qpn->queue, 1); - - if (qpn->mts != NULL) - qpn->mts = mqueue_thread_signal_reset(qpn->mts, 1); + qpn->queue = mqueue_reset(qpn->queue, free_it); + qpn->mts = mqueue_thread_signal_reset(qpn->mts, free_it); if (free_structure) XFREE(MTYPE_QPN_NEXUS, qpn) ; /* sets qpn = NULL */ diff --git a/lib/qpnexus.h b/lib/qpnexus.h index 03b52876..a6c8bf44 100644 --- a/lib/qpnexus.h +++ b/lib/qpnexus.h @@ -146,9 +146,6 @@ extern qpn_nexus qpn_init_new(qpn_nexus qpn, bool main_thread); extern void qpn_add_hook_function(qpn_hook_list list, void* hook) ; extern void qpn_exec(qpn_nexus qpn); extern void qpn_terminate(qpn_nexus qpn); -extern qpn_nexus qpn_reset(qpn_nexus qpn, bool free_structure); - -#define qpn_reset_free(qpn) qpn_reset(qpn, 1) -#define qpn_reset_keep(qpn) qpn_reset(qpn, 0) +extern qpn_nexus qpn_reset(qpn_nexus qpn, free_keep_b free_structure); #endif /* _ZEBRA_QPNEXUS_H */ diff --git a/lib/qpselect.c b/lib/qpselect.c index c15f112e..bcd2e2c9 100644 --- a/lib/qpselect.c +++ b/lib/qpselect.c @@ -29,13 +29,24 @@ #include "memory.h" #include "vector.h" -enum { qdebug = -#ifdef QDEBUG - 1 +/*------------------------------------------------------------------------------ + * Sort out any debug setting + */ +#ifdef QPSELECT_DEBUG /* Can be forced from outside */ +# if QPSELECT_DEBUG +# define QPSELECT_DEBUG 1 /* Force 1 or 0 */ +#else +# define QPSELECT_DEBUG 0 +# endif #else - 0 +# ifdef QDEBUG +# define QPSELECT_DEBUG 1 /* Follow QDEBUG */ +#else +# define QPSELECT_DEBUG 0 +# endif #endif -}; + +enum { qpselect_debug = QPSELECT_DEBUG } ; /*============================================================================== * Quagga pselect -- qps_xxxx @@ -319,7 +330,7 @@ qps_pselect(qps_selection qps, qtime_t max_wait) fd_set* p_fds[qps_mnum_count] ; int n ; - if (qdebug) + if (qpselect_debug) qps_selection_validate(qps) ; /* If there is stuff still pending, tidy up by zeroising the result */ @@ -415,7 +426,7 @@ qps_dispatch_next(qps_selection qps) qps_file qf ; qps_mnum_t mnum ; - if (qdebug) + if (qpselect_debug) qps_selection_validate(qps) ; if (qps->pend_count == 0) diff --git a/lib/qpthreads.h b/lib/qpthreads.h index 959194ad..da6b2319 100644 --- a/lib/qpthreads.h +++ b/lib/qpthreads.h @@ -52,6 +52,22 @@ #error Require _POSIX_THREADS #endif +#ifdef QPTHREADS_DEBUG /* Can be forced from outside */ +# if QPTHREADS_DEBUG +# define QPTHREADS_DEBUG 1 /* Force 1 or 0 */ +#else +# define QPTHREADS_DEBUG 0 +# endif +#else +# ifdef QDEBUG +# define QPTHREADS_DEBUG 1 /* Follow QDEBUG */ +#else +# define QPTHREADS_DEBUG 0 +# endif +#endif + +enum { qpthreads_debug = QPTHREADS_DEBUG } ; + /*============================================================================== * Global Switch -- this allows the library to be run WITHOUT pthreads ! * @@ -165,7 +181,7 @@ Inline bool qpt_thread_is_self(qpt_thread_t id) * * Quagga's default mutex type is: * - * * PTHREAD_MUTEX_ERRORCHECK unless NDEBUG && NDEBUG_QPTHREADS + * * PTHREAD_MUTEX_ERRORCHECK if QPTHREADS_DEBUG * * QPT_MUTEX_TYPE_DEFAULT * * QPT_MUTEX_TYPE_DEFAULT may be set elsewhere. If it is not set then it is @@ -200,10 +216,10 @@ enum qpt_mutex_options # define QPT_MUTEX_TYPE_DEFAULT PTHREAD_MUTEX_NORMAL #endif -#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS) -# define QPT_MUTEX_TYPE QPT_MUTEX_TYPE_DEFAULT -#else +#if QPTHREADS_DEBUG # define QPT_MUTEX_TYPE PTHREAD_MUTEX_ERRORCHECK +#else +# define QPT_MUTEX_TYPE QPT_MUTEX_TYPE_DEFAULT #endif extern qpt_mutex /* freezes qpthreads_enabled */ diff --git a/lib/qstring.c b/lib/qstring.c index c7e5b81c..19f74e8c 100644 --- a/lib/qstring.c +++ b/lib/qstring.c @@ -19,13 +19,10 @@ * Boston, MA 02111-1307, USA. */ -#include <stdio.h> -#include <ctype.h> +#include "stdio.h" #include "qstring.h" - #include "memory.h" -#include "zassert.h" /*============================================================================== */ @@ -38,15 +35,53 @@ qs_new(void) { /* zeroising sets a completely empty qstring -- see qs_init_new() */ return XCALLOC(MTYPE_QSTRING, sizeof(qstring_t)) ; + + confirm(QSTRING_INIT_ALL_ZEROS) ; +} ; + +/*------------------------------------------------------------------------------ + * Create a new body or extend existing one to accommodate at least slen + 1. + * + * Sets size to multiple of 8 (minimum 16), with at least 9 bytes free beyond + * the requested slen. + * + * Does not affect 'len' or 'cp'. + * + * If 'keep_alias', ONLY sets 'b_size' & 'b_body'. + * + * If !'keep_alias', sets the elstring body & size & clears 'alias'. + */ +static inline void +qs_new_body(qstring qs, usize slen, bool keep_alias) +{ + qs->b_size = (slen + 0x10) & ~(usize)(0x08 - 1) ; + qs->b_body = XREALLOC(MTYPE_STRING, qs->b_body, qs->b_size) ; + + if (!keep_alias) + qs_set_real_body_nn(qs) ; +} ; + +/*------------------------------------------------------------------------------ + * Create a new, empty qs with body + */ +extern qstring +qs_new_with_body(usize slen) +{ + qstring qs ; + + qs = qs_new() ; + qs_new_body(qs, slen, false) ; + + return qs ; } ; /*------------------------------------------------------------------------------ * Initialise qstring -- allocate if required. * - * If non-zero slen is given, a body is allocated (for at least slen + 1). + * If non-zero slen is given, a body is allocated (size = slen + 1). * If zero slen is given, no body is allocated. * - * Sets qs->len = qs->cp = 0. '\0' terminates body if allocates one. + * Sets 'len' = 'cp' = 0. * * Returns: address of qstring * @@ -65,18 +100,20 @@ qs_init_new(qstring qs, usize slen) /* Zeroising has set: * - * body = NULL -- no body - * size = 0 -- no body + * empty elstring -- no body, 'len' = 0 + * + * size = 0 -- no elstring body * - * len = 0 * cp = 0 * - * b_body = NULL -- no body buffer - * b_size = 0 -- no body buffer + * b_body = NULL -- no body buffer + * b_size = 0 -- no body buffer + * + * alias = false -- not an alias qstring */ if (slen != 0) - qs_make_to_size(qs, slen, false) ; + qs_new_body(qs, slen, false) ; return qs ; } ; @@ -104,90 +141,75 @@ qs_reset(qstring qs, free_keep_b free_structure) XFREE(MTYPE_QSTRING, qs) ; /* sets qs = NULL */ else memset(qs, 0, sizeof(qstring_t)) ; /* see qs_init_new */ + + confirm(QSTRING_INIT_ALL_ZEROS) ; } ; return qs ; } ; /*------------------------------------------------------------------------------ - * Allocate or reallocate body so that is big enough for the given "slen". + * Allocate or reallocate body so that is big enough for the given 'slen'. * - * If the qstring is currently an alias, copies all of the alias to a new - * body -- so always returns a non-alias qstring. + * qstring may NOT be NULL. * - * Returns with a body with size > 0. Allocates to 16 byte boundaries with - * space for '\0' beyond given length. + * Returns with a body with size > 0 (even if 'slen' == 0). * - * Does NOT affect qs->len or qs->cp. Does NOT re-terminate. + * Expects, but does not require, that 'slen' > 'size'. * - * NB: will allocate a new body even if the slen == 0. + * Does not change 'cp' or 'len' (even if 'len' > 'slen'). * - * NB: always copies all of any aliased string (even if the slen == 0). - * - * NB: sets terminated false + * If is an alias, copies min('len', 'alen', 'slen') characters to the body. */ Private void -qs_make_to_size(qstring qs, usize slen, bool keep) +qs_make_to_size(qstring qs, usize slen, usize alen) { - usize size ; - usize alen ; + /* If body needs to be extended, do that now */ + if (slen >= qs->b_size) + qs_new_body(qs, slen, (qs->alias && (alen != 0))) ; + /* keeps alias if required */ /* Worry about alias. If we keep it, we keep all of it. */ - if (keep && (qs->size == 0)) + if (qs->alias) { - alen = qs_len_nn(qs) ; /* alias stuff to keep */ - if (slen <= alen) - slen = alen + 1 ; /* making sure can do that. */ - } - else - alen = 0 ; /* no alias stuff to keep */ + /* alias or empty */ + usize len = qs_len_nn(qs) ; - /* Calculate the new size -- multiple of 16, >= 16. */ - size = (slen + 0x10) & ~(usize)(0x10 - 1) ; - dassert(size != 0) ; + if (alen >= len) + alen = len ; /* keep min(alen, len) */ - /* If that requires the body to be extended, do that now */ - if (size > qs->b_size) - { - /* Need to allocate or extend the buffer. - * - * If current size is not zero, extend by doubling size or making at - * least the multiple of 16 calculated for new len. - */ - qs->b_size *= 2 ; - if (qs->b_size < size) - qs->b_size = size ; - qs->b_body = XREALLOC(MTYPE_STRING, qs->b_body, qs->b_size) ; - } ; - - /* If this is a non-empty alias, copy all or part of it. */ - if (alen != 0) - memcpy(qs->b_body, qs_body_nn(qs), alen) ; - - /* Update body and size, and no longer known to be terminated */ - qs_set_body_nn(qs, qs->b_body) ; - qs->size = qs->b_size ; + if (alen > slen) + alen = slen ; /* keep only to new len. */ + if (alen != 0) + memcpy(qs->b_body, qs_body_nn(qs), alen) ; + qs_set_real_body_nn(qs) ; + } ; } ; /*============================================================================== * Setting value of qstring + * + * Copy the given string to the qstring, allocating qstring and/or extending + * it as required. + * + * Any alias is simply discarded. + * + * Sets 'len' to new length + * Sets 'cp' = 0 + * + * Returns: address of the qstring (allocated if required). */ /*------------------------------------------------------------------------------ * Set qstring to be copy of the given string. * - * Allocate qstring if required (setting qs->len = qs->cp = 0). - * - * Allocates a body and copies src to it, adding '\0'. Treats src == NULL as - * an empty string. - * - * Sets qs->len to the length of the string (excluding trailing '\0'). - * Sets qs->cp == 0. + * Treats src == NULL as an empty string. Otherwise src must be a '\0' + * terminated string. * - * Returns: address of the qstring copied to. + * Does not copy or count the '\0' terminator. * - * NB: if copying to a dummy qstring, the old body is simply discarded. + * See notes above. */ extern qstring qs_set(qstring qs, const char* src) @@ -196,157 +218,202 @@ qs_set(qstring qs, const char* src) } ; /*------------------------------------------------------------------------------ - * Set qstring to be leading 'n' bytes of given string. - * - * Allocate qstring if required (setting qs->len = qs->cp = 0). - * - * Allocates a body and copies 'n' bytes from src to it, adding '\0'. The - * src pointer is ignored if n == 0. + * Set qstring to be copy of leading 'n' bytes of given string. * - * Sets qs->len to the length of the string (excluding trailing '\0'). - * Sets qs->cp == 0. + * If n == 0, src is ignored (and may be NULL) + * If n > 0, src string MUST be at least 'n' bytes long. * - * Returns: address of the qstring copied to. - * - * NB: if n == 0, src may be NULL - * - * NB: if n > 0, src string MUST be at least 'n' bytes long. - * - * NB: if copying to a dummy qstring, the old body is simply discarded. + * See notes above. */ extern qstring qs_set_n(qstring qs, const char* src, usize len) { - char* p ; - - qs = qs_new_len(qs, len) ; /* ensures have body > n */ - - p = qs_char_nn(qs) ; + qs = qs_new_size(qs, len) ; /* ensures have body > len */ if (len != 0) - memcpy(p, src, len) ; - - *(p + len) = '\0' ; + memcpy(qs_char_nn(qs), src, len) ; - qs_set_term_nn(qs, true) ; - qs->cp = 0 ; + qs_set_len_nn(qs, len) ; + qs_set_cp_nn(qs, 0) ; return qs ; } ; /*------------------------------------------------------------------------------ - * Append given string to a qstring -- adding at qs->len position. - * - * Allocate qstring if required (setting qs->len = qs->cp = 0). - * - * Allocates or extends the body and copies bytes from src to it, adding '\0'. - * Treats src == NULL as an empty string. - * - * Sets qs->len to the length of the result (excluding trailing '\0') - * Does not change qs->cp. + * Set qstring to be copy of given qstring contents. * - * Returns: address of the qstring appended to. + * If the given qstring is an alias, then the contents of the alias are copied + * (so the result is not an alias). See qs_copy() for the alternative. * - * NB: if appending to a dummy qstring, the old body is copied first. + * See notes above -- and note that 'cp' is set to 0. */ -extern qstring qs_append(qstring qs, const char* src) +extern qstring +qs_set_qs(qstring qs, qstring src) { - return qs_append_n(qs, src, (src != NULL) ? strlen(src) : 0) ; + return qs_set_n(qs, qs_body(src), qs_len(src)) ; } ; /*------------------------------------------------------------------------------ - * Append leading 'n' bytes of given string to a qstring -- adding at qs->len - * position. - * - * Allocate qstring if required (setting qs->len = qs->cp = 0). + * Set qstring to be copy of given elstring contents. * - * Allocates or extends the body and copies 'n' bytes from src to it, - * adding '\0'. The src pointer is ignored if n == 0. + * See notes above. + */ +extern qstring +qs_set_els(qstring qs, elstring src) +{ + return qs_set_n(qs, els_body(src), els_len(src)) ; +} ; + +/*------------------------------------------------------------------------------ + * Set qstring with given pattern to given length. * - * Sets qs->len to the length of the result (excluding trailing '\0') - * Does not change qs->cp. + * Repeats the given pattern as many times as necessary to get to the given + * length -- using a final partial piece of the pattern as required. * - * Returns: address of the qstring appended to. + * If the pattern is zero length, fills with spaces ! * - * NB: if n == 0, src may be NULL + * See notes above. + */ +extern qstring +qs_set_fill(qstring qs, usize len, const char* src) +{ + return qs_set_fill_n(qs, len, src, (src != NULL ? strlen(src) : 0)) ; +} ; + +/*------------------------------------------------------------------------------ + * Set qstring with given pattern to given length. * - * NB: if n > 0, src string MUST be at least 'n' bytes long. + * Repeats the given pattern as many times as necessary to get to the given + * length -- using a final partial piece of the pattern as required. * - * NB: if appending to a dummy qstring, the old body is copied first. + * See notes above. */ extern qstring -qs_append_n(qstring qs, const char* src, usize n) +qs_set_fill_n(qstring qs, usize len, const char* src, usize flen) { - char* ep ; + char* p ; + char* q ; + usize left ; - qs = qs_add_len(qs, n, &ep) ; + qs = qs_new_size(qs, len) ; /* ensures have body > len */ - if (n != 0) - memcpy(ep, src, n) ; + if (len != 0) + { + if (flen == 0) + { + src = " " ; + flen = strlen(src) ; + } ; + + if (len < flen) + flen = len ; + + q = p = qs_char_nn(qs) ; + memcpy(p, src, flen) ; + p += flen ; + left = len - flen ; + + while (left > 0) + { + if (left < flen) + flen = left ; + + memcpy(p, q, flen) ; + p += flen ; + left -= flen ; + + flen += flen ; + } ; + } ; + + qs_set_len_nn(qs, len) ; + qs_set_cp_nn(qs, 0) ; return qs ; } ; -/*------------------------------------------------------------------------------ - * Copy one qstring to another +/*============================================================================== + * Appending to a qstring * - * If both are NULL, returns NULL. + * Copy the given string to the end of the given qstring (at 'len'), + * allocating qstring and/or extending it as required. * - * Otherwise if dst is NULL, creates a new qstring. + * If this is an alias, it is copied to before being appended to (even if + * appending nothing). * - * Sets dst: body = copy of src->len bytes of src->body -- '\0' terminated. - * cp = src->cp - * len = src->len + * Can append to NULL or empty qstring. * - * Where a NULL src has zero cp and len. + * Sets 'len' to new length. + * Does not affect 'cp'. + * + * Returns: address of the qstring (allocated if required). + */ + +/*------------------------------------------------------------------------------ + * Append given string to a qstring. * - * If not NULL, the destination is guaranteed to have a body, and that will be - * '\0' terminated. + * Treats src == NULL as an empty string. Otherwise src must be a '\0' + * terminated string. * - * Returns: the destination qstring + * Does not copy or count the '\0' terminator. * - * NB: if copying to a dummy qstring, the old body is simply discarded. + * See notes above. + */ +extern qstring qs_append(qstring qs, const char* src) +{ + return qs_append_n(qs, src, (src != NULL) ? strlen(src) : 0) ; +} ; + +/*------------------------------------------------------------------------------ + * Append leading 'n' bytes of given string to a qstring. + * + * If n == 0, src may be NULL + * If n > 0, src string MUST be at least 'n' bytes long. + * + * See notes above. */ extern qstring -qs_copy(qstring dst, qstring src) +qs_append_n(qstring qs, const char* src, usize n) { - usize n ; + qs = qs_extend(qs, n) ; /* allocate, copy any alias, extend body, + set new length, etc */ - if (src == NULL) - { - if (dst == NULL) - return dst ; + if (n != 0) + memcpy(qs_ep_char_nn(qs) - n, src, n) ; - n = 0 ; - dst->cp = 0 ; - } - else - { - if (dst == NULL) - dst = qs_new() ; + return qs ; +} ; - n = src->len ; - dst->cp = src->cp ; - } ; +/*------------------------------------------------------------------------------ + * Append given qstring to a qstring. + * + * See notes above. + */ +extern qstring +qs_append_qs(qstring qs, qstring src) +{ + return qs_append_n(qs, qs_body(src), qs_len(src)) ; +} ; - qs_set_len(dst, n) ; /* TODO: Copies alias !! */ +/*------------------------------------------------------------------------------ + * Append given elstring to a qstring. + * + * See notes above. + */ +extern qstring +qs_append_els(qstring qs, elstring src) +{ + return qs_append_n(qs, els_body(src), els_len(src)) ; +} ; - if (n > 0) - memcpy(dst->s.body, src->s.body, n) ; - *(dst->s.char_body + n) = '\0' ; - return dst ; -} ; -/*------------------------------------------------------------------------------ - * Construct a qstring which is an alias for the given string. - * - * Allocates a qstring if required. - * - * Given string must be '\0' terminated. +/*============================================================================== + * Setting of alias. * * Does NOT copy the given string, but sets the qstring to be a pointer to it. + * This means that: * * NB: it is the caller's responsibility to ensure that the original string * stays put for however long the qstring is an alias for it. @@ -361,150 +428,147 @@ qs_copy(qstring dst, qstring src) * use, the qstring must not be released, so that the alias is not * released. * - * Returns: the address of the qstring. + * Preserves any existing qstring body. + * + * Returns: address of the qstring (allocated if required). + */ + +/*------------------------------------------------------------------------------ + * Set qstring to be an alias for the given string. + * + * Treats src == NULL as an empty string. Otherwise src must be a '\0' + * terminated string. + * + * Does not count the '\0' terminator. + * + * See notes above. */ extern qstring qs_set_alias(qstring qs, const char* src) { + return qs_set_alias_n(qs, src, (src != NULL) ? strlen(src) : 0) ; +} ; + +/*------------------------------------------------------------------------------ + * Set qstring to be an alias for the leading 'n' bytes of given string. + * + * If n == 0, src may be NULL + * If n > 0, src string MUST be at least 'n' bytes long. + * + * See notes above. + */ +extern qstring +qs_set_alias_n(qstring qs, const char* src, usize n) +{ if (qs == NULL) - qs = qs_init_new(NULL, 0) ; + qs = qs_new() ; /* Make the alias. Note that any existing b_body and b_size are preserved, * so that any current body can be reused at a later date. */ - qs->s.const_body = (src != NULL) ? src : "" ; - qs->len = strlen(src) ; - qs->cp = 0 ; - qs->size = 0 ; /* <=> this is an alias ! */ - qs->terminated = true ; + qs_set_body_nn(qs, (n != 0) ? src : "") ; + qs_set_len_nn(qs, n) ; + qs_set_cp_nn(qs, 0) ; + qs->alias = true ; + qs->size = 0 ; /* => empty or alias */ return qs ; } ; /*------------------------------------------------------------------------------ - * Construct a qstring which is an alias for the 'n' given characters. - * - * Allocates a qstring if required. - * - * Given characters are assumed not to be '\0' terminated. + * Set qstring to be an alias for the given qstring. * - * Does NOT copy the given characters, but sets the qstring to be a pointer to - * them. + * If the src is not an alias, then the qstring is an alias for the body of + * src -- so must be careful not to disturb that ! * - * NB: it is the caller's responsibility to ensure that the original characters - * stays put for however long the qstring is an alias for them. + * If the src is an alias, then the qstring is another alias. * - * It is also the caller's responsibility to see that the original - * characters are discarded as required (once the alias is no longer - * required.) - * - * NB: if the qstring is changed in any way, a copy of the aliased characters - * will be made first. + * See notes above. + */ +extern qstring +qs_set_alias_qs(qstring qs, qstring src) +{ + return qs_set_alias_n(qs, qs_body(src), qs_len(src)) ; +} ; + +/*------------------------------------------------------------------------------ + * Construct a qstring which is an alias for the given elstring. * - * NB: if a pointer to the body of the qstring is taken, then while that is in - * use, the qstring must not be released, so that the alias is not - * released. + * If n == 0, src may be NULL + * If n > 0, src string MUST be at least 'n' bytes long. * - * Returns: the address of the qstring. + * See notes above. */ extern qstring -qs_set_alias_n(qstring qs, const char* src, usize len) +qs_set_alias_els(qstring qs, elstring src) { - if (qs == NULL) - qs = qs_init_new(NULL, 0) ; - - if (len == 0) - src = "" ; - else - assert(src != NULL) ; - - /* Make the alias. Note that any existing b_body and b_size are preserved, - * so that any current body can be reused at a later date. - */ - qs->s.const_body = src ; - qs->len = len ; - qs->cp = 0 ; - qs->size = 0 ; /* <=> this is an alias ! */ - qs->terminated = false ; - - return qs ; + return qs_set_alias_n(qs, els_body(src), els_len(src)) ; } ; - - - - - - - - - - - - - +/*============================================================================== + * Copying of qstring + */ /*------------------------------------------------------------------------------ - * Add 'n' to the current string length, allocating or extending the body. + * Copy one qstring to another -- allocating/extending as required. * - * Allocate qstring if required (setting qs->len = qs->cp = 0). + * If both are NULL, returns NULL. + * Otherwise if dst is NULL, creates a new qstring. * - * Returns with a body with size > 0. Allocates to 16 byte boundaries with - * space for '\0' beyond given length. + * If src is NULL it is treated as zero length, with 'cp' == 0. * - * Does NOT affect qs->cp. - * Does set the new qs->len -- qs->len += n - * Does NOT reterminate. + * If src is not an alias, a copy is made to dst. + * If src is an alias, dst becomes another alias for the same thing. * - * Returns: address of qstring + * If dst is an alias, that is discarded. * - * also: sets char** p_ep to point at the *end* of the old len. + * Copies the src 'cp' to the dst. * - * NB: will allocate a new body even if the new len == 0. - * - * NB: always copies all of any aliased string (even if the slen == 0). + * Returns: the destination qstring (allocated if required). */ extern qstring -qs_add_len(qstring qs, usize n, char** p_ep) +qs_copy(qstring dst, qstring src) { - usize slen ; - usize len ; - - len = qs_len(qs) ; - slen = len + n ; + if (src == NULL) + { + qs_clear(dst) ; /* if dst not NULL, clear it */ + return dst ; + } ; - /* Set the new length -- creating if required. - * - * Will always return with a body and no longer an alias (if was one). - */ - qs = qs_set_len(qs, slen) ; + if (src->alias) + dst = qs_set_alias_qs(dst, src) ; + else + dst = qs_set_qs(dst, src) ; - /* Set pointer to old end (len) position. */ - *p_ep = ((char*)qs_body_nn(qs)) + len ; + qs_set_cp_nn(dst, qs_cp_nn(src)) ; /* copy in the src cp. */ - return qs ; + return dst ; } ; /*============================================================================== * printf() and vprintf() type functions - */ - -/*------------------------------------------------------------------------------ - * Formatted print to qstring -- cf printf() * - * Allocate qstring if required (setting qs->len = qs->cp = 0). + * Allocate and/or extend qstring as required. * - * If OK: + * Any alias is discarded. * - * Sets qs->len to the length of the null terminated result. - * Does NOT affect qs->cp. + * Sets 'len' = length of the '\0' terminated result (less the '\0'). + * Sets 'cp' = 0 * - * If fails: + * If fails and qs != NULL: * - * Sets qs->len = qs->cp = 0 and terminates to zero length. + * Sets 'len' = 0 + * Sets 'cp' = 0 + * But retains any existing body. * * Returns: address of qstring if OK - * NULL if failed (unlikely though that is) -- qstring set empty. + * NULL if failed (unlikely though that is) + */ + +/*------------------------------------------------------------------------------ + * Formatted print to qstring -- cf printf() + * + * See notes above. */ extern qstring qs_printf(qstring qs, const char* format, ...) @@ -521,19 +585,7 @@ qs_printf(qstring qs, const char* format, ...) /*------------------------------------------------------------------------------ * Formatted print to qstring -- cf vprintf() * - * Allocate qstring if required (setting qs->len = qs->cp = 0). - * - * If OK: - * - * Sets qs->len to the length of the null terminated result. - * Does NOT affect qs->cp. - * - * If fails: - * - * Sets qs->len = qs->cp = 0 and terminates to zero length. - * - * Returns: address of qstring if OK - * NULL if failed (unlikely though that is) + * See notes above. */ extern qstring qs_vprintf(qstring qs, const char *format, va_list args) @@ -542,11 +594,12 @@ qs_vprintf(qstring qs, const char *format, va_list args) int slen ; qstring qqs ; - qqs = qs ; + qqs = qs ; /* NULL => need to make qs */ if (qs == NULL) - qs = qs_new() ; /* sets size == 0 */ + qqs = qs_new() ; /* Sets size, cp & len = 0 */ else - qs_set_len_nn(qs, 0) ; /* Forget current contents */ + qs_clear(qqs) ; /* Sets cp & len = 0, discard any alias, but + keep existing body */ while (1) { @@ -558,25 +611,24 @@ qs_vprintf(qstring qs, const char *format, va_list args) * the result is still the length required. */ va_copy(ac, args); - slen = vsnprintf (qs_body_nn(qs), qs->size, format, ac) ; + slen = vsnprintf(qs_body_nn(qqs), qqs->size, format, ac) ; va_end(ac); if (slen < 0) - break ; /* Quit if failed */ + break ; /* failed */ - if ((usize)slen < qs->size) + if ((usize)slen < qqs->size) { - qs_set_len_nn(qs, slen) ; - return qs ; /* Exit if succeeded */ + qs_set_len_nn(qqs, slen) ; + return qqs ; /* succeeded */ } ; - qs_make_to_size(qs, slen) ; /* Extend body to required len */ + qs_make_to_size(qqs, slen, 0) ; /* need space for slen */ } ; - if (qqs == NULL) - qs_reset(qs, free_it) ; /* discard what was allocated */ - else - qs_clear(qs) ; + /* Failed... discard anything that has been allocated. */ + if (qs == NULL) + qs_reset(qqs, free_it) ; return NULL ; } ; @@ -586,233 +638,64 @@ qs_vprintf(qstring qs, const char *format, va_list args) */ /*------------------------------------------------------------------------------ - * Compare significant parts of two qstrings. + * Replace 'r' bytes at 'cp' by 'n' bytes -- extending if required. * - * By significant, mean excluding leading/trailing isspace() and treating - * multiple isspace() as single isspace(). + * May increase or decrease 'len'. but does not affect 'cp'. * - * Compares the 'len' portions of the strings. + * Returns: number of bytes beyond 'cp' that now exist. * - * If either is NULL, it is deemed to be an empty string. + * qstring MUST NOT be NULL * - * Returns: -1 => a < b - * 0 => a == b - * +1 => a > b - */ -extern int -qs_cmp_sig(qstring a, qstring b) -{ - const unsigned char* p_a ; - const unsigned char* e_a ; - const unsigned char* p_b ; - const unsigned char* e_b ; - - /* Set up pointers and dispense with leading and trailing isspace() - * - * Dummy up if NULL - */ - if (a != NULL) - { - p_a = a->s.uchar_body ; - e_a = p_a + a->len ; - - while ((p_a < e_a) && isspace(*p_a)) - ++p_a ; - while ((p_a < e_a) && isspace(*(e_a - 1))) - --e_a ; - } - else - { - p_a = NULL ; - e_a = NULL ; - } - - if (b != NULL) - { - p_b = b->s.uchar_body ; - e_b = p_b + b->len ; - - while ((p_b < e_b) && isspace(*p_b)) - ++p_b ; - while ((p_b < e_b) && isspace(*(e_b - 1))) - --e_b ; - } - else - { - p_b = NULL ; - e_b = NULL ; - } ; - - /* Now set about finding the first difference */ - while ((p_a != e_a) && (p_b != e_b)) - { - if (isspace(*p_a) && isspace(*p_b)) - { - do { ++p_a ; } while (isspace(*p_a)) ; - do { ++p_b ; } while (isspace(*p_b)) ; - } ; - - if (*p_a != *p_b) - return (*p_a < *p_b) ? -1 : +1 ; - - ++p_a ; - ++p_b ; - } ; - - /* No difference before ran out of one or both */ - if (p_a != e_a) - return +1 ; - else if (p_b != e_b) - return -1 ; - else - return 0 ; -} ; - -/*------------------------------------------------------------------------------ - * Insert 'n' bytes at 'cp' -- moves anything cp..len up. - * - * Increases 'len'. but does not affect 'cp'. - * - * Returns: number of bytes beyond 'cp' that were moved before insert. - * - * NB: qstring MUST NOT be NULL - * - * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce - * one or more undefined bytes. - * - * NB: the string is NOT re-terminated. - * - * NB: if this is a aliased qstring, a copy is made of the original body. - */ -extern usize -qs_insert(qstring qs, const void* src, usize n) -{ - usize after ; - usize len ; - char* p ; - - qs->terminated = false ; /* NB: require qs != NULL ! */ - - len = qs_len_nn(qs) ; - if (len < qs->cp) /* make len = max(len, cp) ! */ - len = qs->cp ; - - after = len - qs->cp ; - - qs_set_len(qs, len + n) ; /* set len and ensure have space - Makes copy of any aliased string. */ - p = qs_cp_char(qs) ; - if (after > 0) - memmove (p + n, p, after) ; - - if (n > 0) - memmove(p, src, n) ; - - return after ; -} ; - -/*------------------------------------------------------------------------------ - * Replace 'n' bytes at 'cp' -- extending if required. - * - * May increase 'len'. but does not affect 'cp'. + * If 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce + * one or more undefined bytes. * - * NB: qstring MUST NOT be NULL - * - * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce - * one or more undefined bytes. - * - * NB: the string is NOT re-terminated. - * - * NB: if this is a aliased qstring, a copy is made of the original body. - */ -extern void -qs_replace(qstring qs, const void* src, usize n) -{ - usize len ; - - qs->terminated = false ; /* NB: require qs != NULL ! */ - - len = qs_len_nn(qs) ; - if (len < (qs->cp + n)) /* make len = max(len, cp + n) */ - len = qs->cp + n ; - - qs_set_len(qs, len) ; /* set len and ensure have space. - Makes copy of any aliased string. */ - - if (n > 0) - memmove(qs_cp_char(qs), src, n) ; -} ; - -/*------------------------------------------------------------------------------ - * Remove 'n' bytes at 'cp' -- extending if required. - * - * May change 'len'. but does not affect 'cp'. - * - * Returns: number of bytes beyond 'cp' that were moved before insert. - * - * NB: qstring MUST NOT be NULL - * - * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce - * one or more undefined bytes. - * - * NB: the string is NOT re-terminated. - * - * NB: if this is a aliased qstring, a copy is made of the original body. + * If this is a aliased qstring, a copy is made, so is no longer an alias. */ extern usize -qs_delete(qstring qs, usize n) +qs_replace(qstring qs, usize r, const void* src, usize n) { - usize after ; - char* p ; - usize len ; + usize cp, len, nlen, after ; + const char* ap ; + char* np ; - qs->terminated = false ; /* NB: require qs != NULL ! */ + len = qs_len_nn(qs) ; + cp = qs_cp_nn(qs) ; - len = qs_len_nn(qs) ; - - /* If deleting to or beyond 'len', force len to cp */ - if ((qs->cp + n) >= len) - { - len = qs->cp ; - qs_set_len_nn(qs, len) ; /* truncate now, so that if this is an - aliased string, only copy what is - going to be kept. */ - after = 0 ; /* nothing to move */ - } + if ((cp + r) >= len) + /* Replacing up to or beyond the end of the string */ + after = 0 ; else - after = len - (qs->cp + n) ; + /* Replacing section short of the end of the string */ + after = len - (cp + r) ; - qs_set_len(qs, len) ; /* set len and ensure have space. - Makes copy of any aliased string. */ + nlen = cp + n + after ; + if (nlen >= qs->b_size) + qs_new_body(qs, nlen, qs->alias) ; /* keeping any alias */ + ap = np = qs->b_body ; + if (qs->alias) + { + ap = qs_body_nn(qs) ; /* copy from the alias */ - /* Watch out for "dummy" */ - if (qs->size == 0) - qs_make_to_size(qs, len) ; + uint before = cp ; + if (before > len) + before = len ; - /* If deleting up to or beyond len, then simply set len == cp - * note that this may reduce or increase len ! - */ - if ((qs->cp + n) >= len) - { - if (qs->cp < len) - qs_set_len_nn(qs, qs->cp) ; /* discard stuff after qs->cp */ + if (before > 0) + memmove(np, ap, before) ; - qs_set_len(qs, qs->cp) ; /* set len */ - return 0 ; /* nothing after */ - } + qs_set_real_body_nn(qs) ; + } ; - /* There is at least one byte after cp (so body must exist) */ - after = len - (qs->cp + n) ; + if (after > 0) /* move the after part before inserting */ + memmove(np + cp + n, ap + cp + r, after) ; - if (n > 0) - { - p = qs_cp_char(qs) ; - memmove (p, p + n, after) ; + if (n > 0) /* insert */ + memmove(np + cp, src, n) ; - qs_set_len_nn(qs, len - n) ; - } ; + /* Set new 'len' */ + qs_set_len_nn(qs, nlen) ; - return after ; + return n + after ; } ; diff --git a/lib/qstring.h b/lib/qstring.h index 5b1d4932..48124a2d 100644 --- a/lib/qstring.h +++ b/lib/qstring.h @@ -38,8 +38,21 @@ * The caller does, however, have to explicitly release the contents of a * qstring when it is done with. * - * - * + * The mechanics of a qstring work on a length/pointer basis. The body of a + * qstring is not guaranteed to be '\0' terminated, but when memory is + * allocated provision is always made for a terminator beyond the 'len'. + * The qs_string() function will add a '\0' at the 'len' position. + * + * The body of a qstring is allocated and extended automatically as the length + * or the size is set. The address of the body can, therefore, change -- so + * should be careful when handling pointers to the body. The qstring handling + * tends to hold on to any body that has been allocated -- only qs_reset() will + * free the body. + * + * The qstring supports an "alias" state. A qstring can be set to be an + * "alias" for another length/pointer string, and at some later date that + * can be copied to the qstring body -- to be changed or for any other + * reason. */ struct qstring { @@ -47,10 +60,12 @@ struct qstring usize size ; /* of the els body */ - usize cp ; + ulen cp ; - usize b_size ; void* b_body ; + usize b_size ; + + bool alias ; } ; typedef struct qstring qstring_t[1] ; @@ -68,111 +83,295 @@ enum /*------------------------------------------------------------------------------ * Access functions for body of qstring -- to take care of casting pointers * - * NB: if the body has not yet been allocated, these functions will return - * NULL or NULL + the offset. + * There are generally two versions of each function: + * + * xxxx_nn -- where the argument may NOT be NULL + * + * xxxx -- where a NULL or zero value is returned if the argument + * is NULL + * + * NB: the various 'cp', 'len' and 'at' functions do not guarantee that these + * positions exist within the current body of the string. + * */ + +Inline elstring qs_els(qstring qs) ; +Inline elstring qs_els_nn(qstring qs) ; +Inline void qs_els_copy(elstring els, qstring qs) ; +Inline void qs_els_copy_nn(elstring els, qstring qs) ; + +Inline char* qs_char(qstring qs) ; +Inline char* qs_char_nn(qstring qs) ; + +Inline char* qs_cp_char(qstring qs) ; +Inline char* qs_cp_char_nn(qstring qs) ; + +Inline char* qs_ep_char(qstring qs) ; +Inline char* qs_ep_char_nn(qstring qs) ; + +Inline char* qs_char_at(qstring qs, usize off) ; +Inline char* qs_char_at_nn(qstring qs, usize off) ; + +Inline void* qs_body(qstring qs) ; +Inline void* qs_body_nn(qstring qs) ; +Inline void qs_set_body_nn(qstring qs, const void* body) ; + +Inline ulen qs_len(qstring qs) ; +Inline ulen qs_len_nn(qstring qs) ; +Inline void qs_set_len_nn(qstring qs, ulen len) ; + +Inline ulen qs_cp(qstring qs) ; +Inline ulen qs_cp_nn(qstring qs) ; +Inline void qs_set_cp_nn(qstring qs, usize cp) ; +Inline void qs_move_cp_nn(qstring qs, int delta) ; + +Inline ulen qs_after_cp(qstring qs) ; +Inline ulen qs_after_cp_nn(qstring qs) ; + +Inline void qs_pp(pp p, qstring qs) ; +Inline void qs_pp_nn(pp p, qstring qs) ; + +Inline void qs_cpp(cpp p, qstring qs) ; +Inline void qs_cpp_nn(cpp p, qstring qs) ; + +/*------------------------------------------------------------------------------ + * Functions to fetch the elstring body of the qstring. + */ + +/* Pointer to elstring of qstring -- returns NULL if qstring is NULL */ Inline elstring -qs_els_nn(qstring qs) +qs_els(qstring qs) { - return qs->els ; + return (qs != NULL) ? qs->els : NULL ; } ; +/* Pointer to elstring of qstring (not NULL) */ Inline elstring -qs_els(qstring qs) +qs_els_nn(qstring qs) { return qs->els ; } ; -Inline void* -qs_body_nn(qstring qs) /* pointer to body of qstring (not NULL) */ +/* Copy elstring of qstring to another elstring (elstring not NULL) */ +Inline void +qs_els_copy(elstring els, qstring qs) { - return els_body_nn(qs->els) ; + if (qs != NULL) + qs_els_copy_nn(els, qs) ; + else + els_null(els) ; +} ; + +/* Copy elstring of qstring to another elstring (neither NULL) */ +Inline void +qs_els_copy_nn(elstring els, qstring qs) +{ + *els = *(qs->els) ; +} ; + +/*------------------------------------------------------------------------------ + * Functions to fetch pointers to or into the string body. + * + * NB: these pointers must be treated with care -- and change to the string + * may invalidate the pointer. Where the qstring is an alias, the pointer + * returned is the alias -- which could disappear ! + */ + +/* Start of qstring -- returns NULL if qstring is NULL, or body is. */ +Inline char* +qs_char(qstring qs) +{ + return qs_body(qs) ; } ; -Inline void* /* pointer to body of qstring */ +/* Start of qstring (not NULL)-- returns NULL if body is NULL. */ +Inline char* +qs_char_nn(qstring qs) +{ + return qs_body_nn(qs) ; +} ; + +/* Offset in qstring -- returns NULL if qstring is NULL + * returns *nonsense if body is NULL */ +Inline char* +qs_char_at(qstring qs, usize off) +{ + return (qs != NULL) ? qs_char_at_nn(qs, off) : NULL ; +} ; + +/* Offset in qstring (not NULL) -- returns *nonsense if body is NULL */ +Inline char* +qs_char_at_nn(qstring qs, usize off) +{ + return qs_char_nn(qs) + off ; +} ; + +/* 'cp' in qstring -- returns NULL if qstring is NULL + * returns *nonsense if body is NULL */ +Inline char* +qs_cp_char(qstring qs) +{ + return (qs != NULL) ? qs_cp_char_nn(qs) : NULL ; +} ; + +/* 'cp' in qstring (not NULL) -- returns *nonsense if body is NULL */ +Inline char* +qs_cp_char_nn(qstring qs) +{ + return qs_char_at_nn(qs, qs_cp_nn(qs)) ; +} ; + +/* 'len' in qstring -- returns NULL if qstring is NULL + * returns *nonsense if body is NULL */ +Inline char* +qs_ep_char(qstring qs) +{ + return (qs != NULL) ? qs_ep_char_nn(qs) : NULL ; +} ; + +/* 'len' in qstring (not NULL) -- returns *nonsense if body is NULL */ +Inline char* +qs_ep_char_nn(qstring qs) +{ + return qs_char_at_nn(qs, qs_len_nn(qs)) ; +} ; + +/* Start of qstring -- returns NULL if qstring is NULL, or body is. */ +Inline void* qs_body(qstring qs) { return (qs != NULL) ? qs_body_nn(qs) : NULL ; } ; -Inline void /* set pointer to body of qstring (not NULL) */ -qs_set_body_nn(qstring qs, const void* body) +/* Start of qstring (not NULL)-- returns NULL if body is NULL. */ +Inline void* +qs_body_nn(qstring qs) { - els_set_body_nn(qs->els, body) ; /* sets term = fase */ + return els_body_nn(qs->els) ; } ; -Inline ulen /* length of qstring (not NULL) */ -qs_len_nn(qstring qs) +/* Set new body of qstring (not NULL). + * + * Caller must ensure that 'size', 'len' & 'cp' are all valid ! */ +Inline void +qs_set_body_nn(qstring qs, const void* body) { - return els_len_nn(qs->els) ; + els_set_body_nn(qs->els, body) ; } ; -Inline ulen /* length of qstring */ +/*----------------------------------------------------------------------------*/ + +/* 'len' of qstring -- returns 0 if qstring is NULL */ +Inline ulen qs_len(qstring qs) { return (qs != NULL) ? qs_len_nn(qs) : 0 ; } ; -Inline void /* set length of qstring (not NULL) */ -qs_do_set_len_nn(qstring qs, ulen len) +/* 'len' of qstring (not NULL) */ +Inline ulen +qs_len_nn(qstring qs) { - els_set_len_nn(qs->els, len) ; /* sets term = false */ + return els_len_nn(qs->els) ; } ; -Inline ulen /* cp of qstring (not NULL) */ -qs_cp_nn(qstring qs) +/* set 'len' of qstring (not NULL) -- caller responsible for validity */ +Inline void +qs_set_len_nn(qstring qs, ulen len) { - return qs->cp ; + els_set_len_nn(qs->els, len) ; } ; -Inline ulen /* cp of qstring */ +/*----------------------------------------------------------------------------*/ + +/* 'cp' of qstring -- returns 0 if qstring is NULL */ +Inline ulen qs_cp(qstring qs) { return (qs != NULL) ? qs_cp_nn(qs) : 0 ; } ; -Inline void /* set cp of qstring (not NULL) */ -qs_do_set_cp_nn(qstring qs, ulen cp) +/* 'cp' of qstring (not NULL) */ +Inline ulen +qs_cp_nn(qstring qs) +{ + return qs->cp ; +} ; + +/* set 'cp' of qstring (not NULL) -- caller responsible for validity */ +Inline void +qs_set_cp_nn(qstring qs, usize cp) { qs->cp = cp ; } ; -Inline char* /* pointer to given offset in qstring */ -qs_char_at_nn(qstring qs, usize off) +/* move 'cp' of qstring (not NULL) -- caller responsible for validity */ +Inline void +qs_move_cp_nn(qstring qs, int delta) { - char* p ; - p = qs_body_nn(qs) ; - return (p != NULL) ? p + off : NULL ; + qs->cp += delta ; } ; -Inline char* /* pointer to given offset in qstring */ -qs_char_at(qstring qs, usize off) +/*----------------------------------------------------------------------------*/ + +/* 'len' - 'cp' of qstring (not NULL) -- zero if 'len' < 'cp' */ +Inline ulen +qs_after_cp_nn(qstring qs) { - return (qs != NULL) ? qs_char_at_nn(qs, off) : NULL ; + return (qs_len_nn(qs) > qs_cp_nn(qs)) ? qs_len_nn(qs) - qs_cp_nn(qs) : 0 ; } ; -Inline char* /* pointer to 'cp' offset in qstring */ -qs_cp_char(qstring qs) +/* 'len' - 'cp' of qstring -- zero if NULL or 'len' < 'cp' */ +Inline ulen +qs_after_cp(qstring qs) { - return (qs != NULL) ? qs_char_at_nn(qs, qs_cp_nn(qs)) : NULL ; + return (qs != NULL) ? qs_after_cp_nn(qs) : 0 ; } ; -Inline char* /* pointer to 'len' offset in qstring */ -qs_ep_char(qstring qs) +/*------------------------------------------------------------------------------ + * Functions to fetch various pointer pairs. + */ + +Inline void +qs_pp(pp p, qstring qs) +{ + if (qs != NULL) + qs_pp_nn(p, qs) ; + else + pp_null(p) ; +} ; + +Inline void +qs_pp_nn(pp p, qstring qs) { - return (qs != NULL) ? qs_char_at_nn(qs, qs_len_nn(qs)) : NULL ; + els_pp_nn(p, qs->els) ; +} ; + +Inline void +qs_cpp(cpp p, qstring qs) +{ + if (qs != NULL) + qs_cpp_nn(p, qs) ; + else + cpp_null(p) ; } ; -Inline bool /* whether qstring is known to be terminated */ -qs_term_nn(qstring qs) +Inline void +qs_cpp_nn(cpp p, qstring qs) { - return els_term_nn(qs->els) ; + els_cpp_nn(p, qs->els) ; } ; -Inline void /* set qstring is known to be terminated */ -qs_set_term_nn(qstring qs, bool how) +/*------------------------------------------------------------------------------ + * Set real body -- discarding any alias. + * + * NB: does not affect 'len' or 'cp' + */ +Inline void qs_set_real_body_nn(qstring qs) { - return els_set_term_nn(qs->els, how) ; + qs->size = qs->b_size ; + qs_set_body_nn(qs, qs->b_body) ; + qs->alias = false ; } ; /*============================================================================== @@ -180,42 +379,140 @@ qs_set_term_nn(qstring qs, bool how) */ extern qstring qs_new(void) ; +extern qstring qs_new_with_body(usize slen) ; extern qstring qs_init_new(qstring qs, usize len) ; extern qstring qs_reset(qstring qs, free_keep_b free_structure) ; -Private void qs_make_to_size(qstring qs, usize len, free_keep_b free) ; +Inline char* qs_make_string(qstring qs) ; +Inline const char* qs_string(qstring qs) ; +Inline void qs_clear(qstring qs) ; +Inline qstring qs_new_size(qstring qs, usize slen) ; +Inline qstring qs_extend(qstring qs, usize elen) ; +Inline void qs_chop(qstring qs, usize clen) ; + +Private void qs_make_to_size(qstring qs, usize len, usize alen) ; extern qstring qs_set(qstring qs, const char* src) ; extern qstring qs_set_n(qstring qs, const char* src, usize n) ; +extern qstring qs_set_qs(qstring qs, qstring src) ; +extern qstring qs_set_els(qstring qs, elstring src) ; +extern qstring qs_set_fill(qstring qs, usize len, const char* src) ; +extern qstring qs_set_fill_n(qstring qs, usize len, const char* src, + usize flen) ; extern qstring qs_append(qstring qs, const char* src) ; extern qstring qs_append_n(qstring qs, const char* src, usize n) ; - -extern qstring qs_copy(qstring dst, qstring src) ; +extern qstring qs_append_qs(qstring qs, qstring src) ; +extern qstring qs_append_els(qstring qs, elstring src) ; extern qstring qs_set_alias(qstring qs, const char* src) ; extern qstring qs_set_alias_n(qstring qs, const char* src, usize len) ; +extern qstring qs_set_alias_qs(qstring qs, qstring src) ; +extern qstring qs_set_alias_els(qstring qs, elstring src) ; + +extern qstring qs_copy(qstring dst, qstring src) ; extern qstring qs_printf(qstring qs, const char* format, ...) PRINTF_ATTRIBUTE(2, 3) ; extern qstring qs_vprintf(qstring qs, const char *format, va_list args) ; -extern usize qs_insert(qstring qs, const void* src, usize n) ; -extern void qs_replace(qstring qs, const void* src, usize n) ; -extern usize qs_delete(qstring qs, usize n) ; +extern usize qs_replace(qstring qs, usize r, const void* src, usize n) ; +Inline usize qs_insert(qstring qs, const void* src, usize n) +{ + return qs_replace(qs, 0, src, n) ; +} ; +Inline usize qs_delete(qstring qs, usize n) +{ + return qs_replace(qs, n, NULL, 0) ; +} ; -extern int qs_cmp_sig(qstring a, qstring b) ; +Inline int qs_cmp(qstring a, qstring b) ; +Inline int qs_cmp_word(qstring a, const char* w) ; +Inline int qs_cmp_sig(qstring a, qstring b) ; +Inline bool qs_equal(qstring a, qstring b) ; +Inline bool qs_substring(qstring a, qstring b) ; /*============================================================================== * The Inline functions. */ /*------------------------------------------------------------------------------ + * Return pointer to string value -- ensure not alias and '\0' terminated. + * + * If is alias, copies that before adding '\0' terminator. + * + * Sets the '\0' terminator at the 'len' position, extending string if that + * is required. + * + * If qs == NULL or 'len' == 0 returns pointer to constant empty '\0' + * terminated string (ie ""). + * + * NB: The qstring should not be changed or reset until this pointer has been + * discarded ! + */ +Inline char* +qs_make_string(qstring qs) +{ + usize len ; + char* p ; + + if (qs == NULL) + { + len = 0 ; + qs = qs_new_with_body(len) ; + } + else + { + len = qs_len_nn(qs) ; + if (len >= qs->size) /* for alias, qs_size == 0 */ + qs_make_to_size(qs, len, len) ; /* extend and/or copy any alias */ + } ; + + p = qs_char_nn(qs) ; + *(p + len) = '\0' ; + + return p ; +} ; + +/*------------------------------------------------------------------------------ + * Return pointer to string value. + * + * If possible, writes '\0' at 'len' in order to return a terminated string. + * + * If qs == NULL or body == NULL, or 'len' == 0 returns pointer to constant + * empty '\0' terminated string (ie ""). + * + * NB: if 'len' is beyond the current 'size' of the of the qstring, then + * does NOT write '\0' (does NOT extend the string). + * + * NB: if string is an alias, this returns its address, whether it is + * terminated or not. + * + * NB: In any event, the string should not be changed or reset until this + * pointer has been discarded ! + */ +Inline const char* +qs_string(qstring qs) +{ + char* p ; + usize len ; + + if ( (qs == NULL) || ((len = qs_len_nn(qs) ) == 0) + || ((p = qs_char_nn(qs)) == NULL) ) + return "" ; + + if (len < qs->size) /* for alias, qs_size == 0 */ + *(p + len) = '\0' ; + + return p ; +} ; + +/*------------------------------------------------------------------------------ * Clear contents of qstring -- preserves any qstring body, but sets len = 0. * * Does nothing if qstring is NULL * - * Sets 'cp' = 'len' = 0. + * Sets 'cp' = 'len' = 0 * * If is an alias qstring, discard the alias. * @@ -226,68 +523,64 @@ qs_clear(qstring qs) { if (qs != NULL) { - qs_do_set_len_nn(qs, 0) ; /* sets term == false */ - if (qs->size == 0) - { - qs_set_body_nn(qs, qs->b_body) ; - qs->size = qs->b_size ; - } ; - qs->cp = 0 ; + if (qs->alias) + qs_set_real_body_nn(qs) ; + qs_set_len_nn(qs, 0) ; + qs_set_cp_nn(qs, 0) ; } ; } ; /*------------------------------------------------------------------------------ - * Need space for a string of 'slen' characters (plus possible '\0'). + * Ensure have space for a string of 'slen' characters (plus possible '\0'), + * and discard any alias. * - * Allocate qstring if required (setting qs->len = qs->cp = 0). + * Allocate qstring if required (setting 'len' = 'cp' = 0). * * Returns: address of qstring -- with body that can be written upto and * including 'slen' + 1. * - * NB: has no effect on 'len' -- even if 'len' > 'slen'. + * Has no effect on 'len' -- even if 'len' > 'slen'. * - * NB: has no effect on 'cp' -- even if 'cp' > 'len' or 'cp' > 'slen'. + * Has no effect on 'cp' -- even if 'cp' > 'len' or 'cp' > 'slen'. * - * NB: if this is a aliased qstring, the alias is discarded and term = false. + * If this is a aliased qstring, the alias is discarded. */ Inline qstring -qs_need(qstring qs, usize slen) +qs_new_size(qstring qs, usize slen) { - if (qs == NULL) - qs = qs_init_new(NULL, slen) ; /* Make the qstring if required */ - else - if (slen >= qs->size) /* for alias qs->size == 0 ! */ - qs_make_to_size(qs, slen, free_it) ; + if (qs == NULL) + qs = qs_new_with_body(slen) ; + else if (slen >= qs->size) /* for alias qs->size == 0 ! */ + qs_make_to_size(qs, slen, 0) ; /* extend and/or make body */ return qs ; } ; /*------------------------------------------------------------------------------ - * Set 'len' -- allocate or extend body as required. + * Extend to 'len' + 'elen' -- allocate or extend body as required. * - * Allocate qstring if required (setting qs->len = qs->cp = 0). + * Allocate qstring if required (setting 'cp' = 0). * - * Returns: address of qstring -- with body that can be written upto and - * including 'len' + 1. + * Returns: address of qstring -- with body that can be written up to and + * including 'len' + 'elen' + 1. * - * Sets 'cp' to the (new) 'len' if 'cp' > 'len'. + * Has no effect on 'cp' -- even if 'cp' > 'len'. * - * NB: if this is a aliased qstring, a copy is made of all of the original body, - * even if that is longer than the required 'slen'. (And term = false.) + * If this is a aliased qstring, a copy is made. */ Inline qstring -qs_set_len(qstring qs, usize len) +qs_extend(qstring qs, usize elen) { if (qs == NULL) - qs = qs_init_new(NULL, len) ; /* Make the qstring if required */ + qs = qs_new_with_body(elen) ; else - if (len >= qs->size) /* for alias qs->size == 0 ! */ - qs_make_to_size(qs, len, keep_it) ; - - qs_do_set_len_nn(qs, len) ; + { + elen = qs_len_nn(qs) + elen ; + if (elen >= qs->size) /* for alias qs->size == 0 ! */ + qs_make_to_size(qs, elen, elen) ; /* extend and/or copy any alias */ + } ; - if (qs->cp > len) - qs->cp = len ; + qs_set_len_nn(qs, elen) ; return qs ; } ; @@ -299,125 +592,74 @@ qs_set_len(qstring qs, usize len) * * Does not change the 'len' if it is <= length to chop to. * - * Sets 'cp' to the (new) 'len' if 'cp' > 'len'. + * NB: has no effect on 'cp' -- even if 'cp' > (new) 'len'. * * NB: if this is a aliased qstring, then it remains an aliased string, but - * shorter and term = false (unless no change made to the length). + * shorter (unless no change made to the length). */ Inline void qs_chop(qstring qs, usize clen) { if (qs != NULL) { - usize len = qs_len_nn(qs) ; - if (len > clen) - qs_do_set_len_nn(qs, (len = clen)) ; /* sets term = false */ - if (qs->cp > len) - qs->cp = len ; + if (clen < qs_len_nn(qs)) + qs_set_len_nn(qs, clen) ; } ; } ; /*------------------------------------------------------------------------------ - * Set 'cp' -- allocate or extend body as required. - * - * Allocates the qstring, if required. - * - * Returns: address of qstring + * Compare two qstrings -- returns the usual -ve, 0, +ve cmp result. * - * NB: if there was no body, allocates a body for the string, even if 'cp' == 0. - * - * NB: if new 'cp' > 'len', extends body (or allocates one), and sets 'len' to - * 'cp'. If this is an alias qstring, a copy of the string is made. + * NULL qstring is treated as empty. */ -Inline qstring -qs_set_cp(qstring qs, usize cp) +Inline int +qs_cmp(qstring a, qstring b) { - if (qs == NULL) - qs = qs_new(qs) ; - - if (qs->size <= cp) - - - if ((qs == NULL) || (cp >(cp > qs_len_nn(qs))) - qs = qs_set_len(qs, cp) ; - qs->cp = cp ; - return qs ; + return els_cmp(qs_els(a), qs_els(b)) ; } ; /*------------------------------------------------------------------------------ - * If not "term": set '\0' at qs->len -- extending body as required. - * - * Does NOT affect qs->cp or qs->len. - * - * Returns address of body -- NULL if the qstring is NULL - * - * NB: if this is an alias, and it is not terminated, make a copy before adding - * terminating '\0'. + * Compare qstrings to given word -- see els_cmp_word */ -Inline void -qs_terminate_nn(qstring qs) +Inline int +qs_cmp_word(qstring a, const char* w) { - if (!qs_term_nn(qs)) - { - usize len ; - - len = qs_len_nn(qs) ; - - if (len >= qs->size) /* alias has size == 0 */ - qs_make_to_size(qs, len) ; /* make sure can insert '\0' */ - - *qs_char_at_nn(qs, len) = '\0' ; - - qs_set_term_nn(qs, true) ; - } ; + return els_cmp_word(qs_els(a), w) ; } ; /*------------------------------------------------------------------------------ - * Return pointer to '\0' terminated string value. + * Compare significant parts of two qstrings -- returns the usual -ve, 0, +ve + * cmp result. * - * If qs is NULL or body is NULL returns pointer to constant empty '\0' - * terminated string. + * By significant, mean excluding leading/trailing isspace() and treating + * multiple isspace() as single isspace(). * - * If string is terminated, return address of string, which may be an alias - * address. - * - * Otherwise, makes sure that there is a '\0' at the qs->len position, making - * a copy of any aliased string if required, and returns address of result. - * - * NB: value returned may be the address of the qstring body, or the address of - * an aliased string. In any event, the string should not be changed or - * reset until this pointer has been discarded ! + * NULL qstring is treated as empty. */ -Inline const char* -qs_string(qstring qs) +Inline int +qs_cmp_sig(qstring a, qstring b) { - if ((qs == NULL) || (qs_len_nn(qs) == 0)) - return "" ; - - qs_terminate_nn(qs) ; + return els_cmp_sig(qs_els(a), qs_els(b)) ; +} ; - return qs_body_nn(qs) ; +/*------------------------------------------------------------------------------ + * Are two qstrings equal ? -- returns true if they are. + */ +Inline bool +qs_equal(qstring a, qstring b) +{ + return els_equal(qs_els(a), qs_els(b)) ; } ; /*------------------------------------------------------------------------------ - * Assuming the given address is within the size of the given qstring, - * set qs->len and insert '\0' terminator there. - * - * Does NOT affect qs->cp. - * - * NB: must NOT be a NULL qs. + * Is 'b' a leading substring of 'a' ? -- returns true if it is. * - * NB: must NOT be an aliased qstring. - * - * NB: must NOT have a NULL body. + * If 'b' is empty it is always a leading substring. */ -Inline void -qs_term_here(qstring qs, char* here) +Inline bool +qs_substring(qstring a, qstring b) { - assert((here >= qs->s.char_body) && (here < (qs->s.char_body + qs->size))) ; - - qs->len = (here - qs->s.char_body) ; - *here = '\0' ; + return els_substring(qs_els(a), qs_els(b)) ; } ; #endif /* _ZEBRA_QSTRING_H */ diff --git a/lib/qtime.c b/lib/qtime.c index 3ec34a72..92e67e27 100644 --- a/lib/qtime.c +++ b/lib/qtime.c @@ -192,3 +192,51 @@ qt_craft_monotonic(void) { return (monotonic * times_scale_q) + ((monotonic * times_scale_r) / times_clk_tcks) ; } ; + +/*============================================================================== + * A simple minded random number generator. + * + * Designed to be reasonably unpredictable... particularly the ms bits ! + */ + +static inline uint32_t +qt_rand(uint64_t q, uint64_t s) +{ + /* Takes q ^ s and reduces to 32 bits by xoring ms and ls halves + * then uses Knuth recommended linear congruent to randomise that, so that + * most of the original bits affect the result. + * + * Note that linear congruent tends to be "more random" in the ms bits. + */ + q ^= s ; + q = (q ^ (q >> 32)) & 0xFFFFFFFF ; + return ((q * 2650845021) + 5) & 0xFFFFFFFF ; +} ; + +extern uint32_t +qt_random(uint32_t seed) +{ + uint32_t x, y ; + uint64_t t ; + + t = qt_get_realtime() ; + + /* Set x by munging the time, the address of x, the current contents of x, + * and the "seed". (Munge the seed a bit for good measure.) + */ + x = qt_rand(t ^ (uint64_t)x ^ (uint64_t)&x, seed ^ 3141592653) ; + /* munge the address and the contents with the seed */ + + /* Set y by munging the time, the address of y, the current contents of y, + * and the "seed". (Munge the seed a bit for good measure.) + */ + y = qt_rand(t ^ (uint64_t)y ^ (uint64_t)&y, seed ^ 3562951413) ; + /* munge the current real time with the seed */ + + /* Return x and y munged together. + * + * Note that we swap the halves of y before munging, in order to spread + * the "more random" part of y down to the ls end of the result. + */ + return x ^ ((y >> 16) & 0xFFFF) ^ ((y & 0xFFFF) << 16) ; +} ; diff --git a/lib/qtime.h b/lib/qtime.h index 38e9ac1a..94d468a8 100644 --- a/lib/qtime.h +++ b/lib/qtime.h @@ -23,14 +23,11 @@ #define _ZEBRA_QTIME_H #include "misc.h" -#include <stdlib.h> + #include <time.h> #include <sys/time.h> #include <unistd.h> -#include "zassert.h" -#include "config.h" - /*============================================================================== * qtime_t -- signed 64-bit integer. * @@ -64,17 +61,13 @@ typedef qtime_t qtime_tod_t ; /* qtime_t value, timeofday time-base... */ /* Note that the time to convert may be a float. */ #define QTIME(s) ((qtime_t)((s) * (qtime_t)QTIME_SECOND)) -Inline qtime_t -timespec2qtime(struct timespec* p_ts) ; - -Inline qtime_t -timeval2qtime(struct timeval* p_tv) ; - -Inline struct timespec* -qtime2timespec(struct timespec* p_ts, qtime_t qt) ; - -Inline struct timeval* -qtime2timeval(struct timeval* p_tv, qtime_t qt) ; +/*============================================================================== + * Conversion functions + */ +Inline qtime_t timespec2qtime(struct timespec* p_ts) ; +Inline qtime_t timeval2qtime(struct timeval* p_tv) ; +Inline struct timespec* qtime2timespec(struct timespec* p_ts, qtime_t qt) ; +Inline struct timeval* qtime2timeval(struct timeval* p_tv, qtime_t qt) ; /*============================================================================== * Clocks. @@ -93,44 +86,37 @@ qtime2timeval(struct timeval* p_tv, qtime_t qt) ; * a manufactured equivalent using times() -- see qt_craft_monotonic(). */ -Inline qtime_real_t -qt_get_realtime(void) ; /* clock_gettime(CLOCK_REALTIME, ...) */ +Inline qtime_real_t qt_get_realtime(void) ; + /* clock_gettime(CLOCK_REALTIME, ...) */ +Inline qtime_mono_t qt_add_realtime(qtime_t interval) ; + /* qt_get_realtime() + interval */ -Inline qtime_mono_t -qt_add_realtime(qtime_t interval) ; /* qt_get_realtime() + interval */ - -Inline qtime_mono_t -qt_get_monotonic(void) ; /* clock_gettime(CLOCK_MONOTONIC, ...) */ +Inline qtime_mono_t qt_get_monotonic(void) ; + /* clock_gettime(CLOCK_MONOTONIC, ...) */ /* OR equivalent using times() */ -Inline qtime_mono_t -qt_add_monotonic(qtime_t interval) ; /* qt_get_monotonic() + interval */ - -Inline qtime_mono_t /* monotonic time from CLOCK_REALTIME */ -qt_realtime2monotonic(qtime_real_t realtime) ; -Inline qtime_real_t /* CLOCK_REALTIME from monotonic time */ -qt_monotonic2realtime(qtime_mono_t monotonic) ; - -/* Function to manufacture a monotonic clock. */ -extern qtime_mono_t -qt_craft_monotonic(void) ; +Inline qtime_mono_t qt_add_monotonic(qtime_t interval) ; + /* qt_get_monotonic() + interval */ /* These are provided just in case gettimeofday() != CLOCK_REALTIME */ -Inline qtime_tod_t -qt_get_timeofday(void) ; /* gettimeofday(&tv, NULL) */ +Inline qtime_tod_t qt_get_timeofday(void) ; + /* gettimeofday(&tv, NULL) */ +Inline qtime_tod_t qt_add_timeofday(qtime_t interval) ; + /* qt_get_timeofday() + interval */ -Inline qtime_tod_t -qt_add_timeofday(qtime_t interval) ; /* qt_get_timeofday() + interval */ - -Inline qtime_mono_t /* monotonic time from timeofday */ -qt_timeofday2monotonic(qtime_tod_t timeofday) ; -Inline qtime_tod_t /* timeofday from monotonic time */ -qt_monotonic2timeofday(qtime_mono_t monotonic) ; +/*============================================================================== + * Primitive random number generation. + * + * Uses time and other stuff to produce something which is not particularly + * predictable. + */ +extern uint32_t qt_random(uint32_t seed) ; /*============================================================================== * Inline conversion functions */ -/* Convert timespec to qtime_t +/*------------------------------------------------------------------------------ + * Convert timespec to qtime_t * * Returns qtime_t value. */ @@ -141,7 +127,8 @@ timespec2qtime(struct timespec* p_ts) confirm(QTIME_SECOND == TIMESPEC_SECOND) ; } ; -/* Convert timeval to qtime_t +/*------------------------------------------------------------------------------ + * Convert timeval to qtime_t * * Returns qtime_t value. */ @@ -152,7 +139,8 @@ timeval2qtime(struct timeval* p_tv) confirm(QTIME_SECOND == TIMEVAL_SECOND * 1000) ; } ; -/* Convert qtime_t to timespec +/*------------------------------------------------------------------------------ + * Convert qtime_t to timespec * * Takes address of struct timespec and returns that address. */ @@ -169,7 +157,8 @@ qtime2timespec(struct timespec* p_ts, qtime_t qt) return p_ts ; } ; -/* Convert timespec to qtime_t +/*------------------------------------------------------------------------------ + * Convert timespec to qtime_t * * Takes address of struct timespec and returns that address. */ @@ -190,7 +179,11 @@ qtime2timeval(struct timeval* p_tv, qtime_t qt) * Inline Clock Functions. */ -/* Read given clock & return a qtime_t value. +/* Function to manufacture a monotonic clock. */ +Private qtime_mono_t qt_craft_monotonic(void) ; + +/*------------------------------------------------------------------------------ + * Read given clock & return a qtime_t value. * * While possibility of error is essentially theoretical, must treat it as a * FATAL error -- cannot continue with broken time value ! @@ -207,7 +200,8 @@ qt_clock_gettime(clockid_t clock_id) return timespec2qtime(&ts) ; } ; -/* clock_gettime(CLOCK_REALTIME, ...) -- returning qtime_t value +/*------------------------------------------------------------------------------ + * clock_gettime(CLOCK_REALTIME, ...) -- returning qtime_t value * * While possibility of error is essentially theoretical, must treat it as a * FATAL error -- cannot continue with broken time value ! @@ -218,7 +212,8 @@ qt_get_realtime(void) return qt_clock_gettime(CLOCK_REALTIME) ; } ; -/* qt_get_realtime() + interval +/*------------------------------------------------------------------------------ + * qt_get_realtime() + interval */ Inline qtime_real_t qt_add_realtime(qtime_t interval) @@ -226,7 +221,8 @@ qt_add_realtime(qtime_t interval) return qt_get_realtime() + interval; } ; -/* clock_gettime(CLOCK_MONOTONIC, ...) OR qt_craft_monotonic() +/*------------------------------------------------------------------------------ + * clock_gettime(CLOCK_MONOTONIC, ...) OR qt_craft_monotonic() * -- returning qtime_t value * * While possibility of error is essentially theoretical, must treat it as a @@ -242,7 +238,8 @@ qt_get_monotonic(void) #endif } ; -/* qt_get_monotonic() + interval +/*------------------------------------------------------------------------------ + * qt_get_monotonic() + interval */ Inline qtime_mono_t qt_add_monotonic(qtime_t interval) @@ -250,7 +247,8 @@ qt_add_monotonic(qtime_t interval) return qt_get_monotonic() + interval; } ; -/* gettimeofday(&tv, NULL) -- returning qtime_t value +/*------------------------------------------------------------------------------ + * gettimeofday(&tv, NULL) -- returning qtime_t value */ Inline qtime_tod_t qt_get_timeofday(void) @@ -260,7 +258,8 @@ qt_get_timeofday(void) return timeval2qtime(&tv) ; } -/* qt_get_timeofday() + interval +/*------------------------------------------------------------------------------ + * qt_get_timeofday() + interval */ Inline qtime_tod_t qt_add_timeofday(qtime_t interval) @@ -268,36 +267,4 @@ qt_add_timeofday(qtime_t interval) return qt_get_timeofday() + interval; } ; -/*============================================================================== - * Conversion between realtime/timeofday and monotonic - */ - -/* Convert a CLOCK_REALTIME time to our local monotonic time. */ -Inline qtime_mono_t -qt_realtime2monotonic(qtime_real_t realtime) -{ - return qt_get_monotonic() + (realtime - qt_get_realtime()) ; -} ; - -/* Convert a local monotonic time to CLOCK_REALTIME time. */ -Inline qtime_real_t -qt_monotonic2realtime(qtime_mono_t monotonic) -{ - return qt_get_realtime() + (monotonic - qt_get_monotonic()) ; -} ; - -/* Convert a gettimeofday() time to our local monotonic time. */ -Inline qtime_mono_t -qt_timeofday2monotonic(qtime_tod_t timeofday) -{ - return qt_get_monotonic() + (timeofday - qt_get_timeofday()) ; -} ; - -/* Convert a local monotonic time to gettimeofday() time. */ -Inline qtime_tod_t -qt_monotonic2timeofday(qtime_mono_t monotonic) -{ - return qt_get_timeofday() + (monotonic - qt_get_monotonic()) ; -} ; - #endif /* _ZEBRA_QTIME_H */ diff --git a/lib/qtimers.c b/lib/qtimers.c index 5c0f1518..6ccb7f7d 100644 --- a/lib/qtimers.c +++ b/lib/qtimers.c @@ -27,14 +27,6 @@ #include "memory.h" #include "heap.h" -enum { qdebug = -#ifdef QDEBUG - 1 -#else - 0 -#endif -}; - /*============================================================================== * Quagga Timers -- qtimer_xxxx * @@ -170,7 +162,7 @@ qtimer_pile_dispatch_next(qtimer_pile qtp, qtime_mono_t upto) { qtimer qtr ; - if (qdebug) + if (qtimers_debug) qtimer_pile_verify(qtp) ; qtr = heap_top_item(&qtp->timers) ; @@ -343,7 +335,7 @@ qtimer_set(qtimer qtr, qtime_mono_t when, qtimer_action* action) qtp = qtr->pile ; assert(qtp != NULL) ; - if (qdebug) + if (qtimers_debug) qtimer_pile_verify(qtp) ; qtr->time = when ; @@ -371,7 +363,7 @@ qtimer_set(qtimer qtr, qtime_mono_t when, qtimer_action* action) else assert(qtr->action != NULL) ; - if (qdebug) + if (qtimers_debug) qtimer_pile_verify(qtp) ; } ; @@ -387,7 +379,7 @@ qtimer_unset(qtimer qtr) assert(qtp != NULL) ; - if (qdebug) + if (qtimers_debug) qtimer_pile_verify(qtp) ; if (qtr->active) @@ -397,7 +389,7 @@ qtimer_unset(qtimer qtr) heap_delete_item(&qtp->timers, qtr) ; - if (qdebug) + if (qtimers_debug) qtimer_pile_verify(qtp) ; qtr->active = false ; diff --git a/lib/qtimers.h b/lib/qtimers.h index 8534a789..2af81819 100644 --- a/lib/qtimers.h +++ b/lib/qtimers.h @@ -35,6 +35,22 @@ * each with an action to be executed when the timer expires. */ +#ifdef QTIMERS_DEBUG /* Can be forced from outside */ +# if QTIMERS_DEBUG +# define QTIMERS_DEBUG 1 /* Force 1 or 0 */ +#else +# define QTIMERS_DEBUG 0 +# endif +#else +# ifdef QDEBUG +# define QTIMERS_DEBUG 1 /* Follow QDEBUG */ +#else +# define QTIMERS_DEBUG 0 +# endif +#endif + +enum { qtimers_debug = QTIMERS_DEBUG } ; + /*============================================================================== * Data Structures. */ diff --git a/lib/routemap.c b/lib/routemap.c index b452530d..d716e0b5 100644 --- a/lib/routemap.c +++ b/lib/routemap.c @@ -959,7 +959,7 @@ DEFUN (route_map, index = route_map_index_get (map, permit, seq); vty->index = index; - vty_set_node(vty, RMAP_NODE) ; + vty->node = RMAP_NODE ; return CMD_SUCCESS; } diff --git a/lib/symtab.h b/lib/symtab.h index 4e33390e..008b7853 100644 --- a/lib/symtab.h +++ b/lib/symtab.h @@ -168,7 +168,7 @@ extern void* symbol_set_value(symbol sym, void* new_value) ; Inline void* symbol_unset_value(symbol sym) { - symbol_set_value(sym, NULL) ; + return symbol_set_value(sym, NULL) ; } ; extern void symbol_ref_walk_start(symbol sym, symbol_ref walk) ; diff --git a/lib/thread.h b/lib/thread.h index fa021486..6f74876d 100644 --- a/lib/thread.h +++ b/lib/thread.h @@ -206,7 +206,7 @@ extern int thread_should_yield (struct thread *); /* Internal libzebra exports */ extern void thread_getrusage (RUSAGE_T *); -extern struct cmd_element show_thread_cpu_cmd; +extern struct cmd_command show_thread_cpu_cmd; /* replacements for the system gettimeofday(), clock_gettime() and * time() functions, providing support for non-decrementing clock on diff --git a/lib/uty.h b/lib/uty.h deleted file mode 100644 index cc759673..00000000 --- a/lib/uty.h +++ /dev/null @@ -1,232 +0,0 @@ -/* VTY internal stuff -- header - * Copyright (C) 1997 Kunihiro Ishiguro - * - * Copyright (C) 2009 Chris Hall (GMCH), Highwayman - * - * This file is part of GNU Zebra. - * - * GNU Zebra is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published - * by the Free Software Foundation; either version 2, or (at your - * option) any later version. - * - * GNU Zebra is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * 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. - */ - -#ifndef _ZEBRA_UTY_H -#define _ZEBRA_UTY_H - -#include "misc.h" -#include "vargs.h" - -#include "qpthreads.h" -#include "qpnexus.h" -#include "thread.h" -#include "list_util.h" -#include "vty.h" -#include "vty_io_basic.h" -#include "node_type.h" - -/*============================================================================== - * This is stuff which is used by the close family of: - * - * vty - * command - * command_queue - * log - * - * and which is not for use elsewhere. - * - * There are also: - * - * vty_io - * vty_cli - * - * Which are the "immediate family" of vty: - * - * * *nothing* in their ".h" is for use anywhere except the immediate family. - * - * * things for use within the rest of the family are published here. - */ - -/*============================================================================== - * Variables in vty.c -- used in any of the family - */ -extern vty_io vio_list_base ; -extern vty_io vio_monitors_base ; -extern vty_io vio_death_watch ; - -extern struct thread_master* vty_master ; - -extern unsigned long vty_timeout_val ; - -extern bool vty_config ; - -extern bool no_password_check ; -extern const bool restricted_mode_default ; -extern bool restricted_mode ; - -char *vty_accesslist_name ; -char *vty_ipv6_accesslist_name ; - -extern qpn_nexus vty_cli_nexus ; -extern qpn_nexus vty_cmd_nexus ; - -/*============================================================================== - * To make vty qpthread safe we use a single mutex. - * - * vty and log recurse through each other, so the same mutex is used - * for both, i.e. they are treated as being part of the same monitor. - * - * A recursive mutex is used. This simplifies the calling from log to vty and - * back again. It also allows for the vty internals to call each other. - * - * There are some "uty" functions which assume the mutex is locked. - * - * vty is closely bound to the command handling -- the main vty structure - * contains the context in which commands are parsed and executed. - */ - -extern qpt_mutex_t vty_mutex ; - -#ifdef NDEBUG -# define VTY_DEBUG 0 /* NDEBUG override */ -#else -# ifndef VTY_DEBUG -# define VTY_DEBUG 1 /* Set to 1 to turn on debug checks */ -# endif -#endif - -#if VTY_DEBUG - -extern int vty_lock_count ; -extern int vty_assert_fail ; - -#endif - -Inline void -VTY_LOCK(void) /* if is qpthreads_enabled, lock vty_mutex */ -{ - qpt_mutex_lock(&vty_mutex) ; - if (VTY_DEBUG) - ++vty_lock_count ; -} ; - -Inline void -VTY_UNLOCK(void) /* if is qpthreads_enabled, unlock vty_mutex */ -{ - if (VTY_DEBUG) - --vty_lock_count ; - qpt_mutex_unlock(&vty_mutex) ; -} ; - -Inline bool /* true => is (effectively) cli thread */ -vty_is_cli_thread(void) -{ - return !qpthreads_enabled || qpt_thread_is_self(vty_cli_nexus->thread_id) ; -} ; - -/* For debug (and documentation) purposes, will VTY_ASSERT_LOCKED where that - * is required. - * - * In some cases, need also to be running in the CLI thread as well. - */ -#if VTY_DEBUG - -Inline void -VTY_ASSERT_FAILED(void) -{ - if (vty_assert_fail == 0) ; - { - vty_assert_fail = 1 ; - assert(0) ; - } ; -} ; - -Inline void -VTY_ASSERT_LOCKED(void) -{ - if (vty_lock_count == 0) - VTY_ASSERT_FAILED() ; -} ; - -Inline void -VTY_ASSERT_CLI_THREAD(void) -{ - if (!vty_is_cli_thread()) - VTY_ASSERT_FAILED() ; -} ; - -#else - -#define VTY_ASSERT_LOCKED() -#define VTY_ASSERT_CLI_THREAD() - -#endif - -/*============================================================================== - * Shared definitions - */ - -enum cli_do -{ - cli_do_nothing = 0, /* no action required */ - - cli_do_command, /* dispatch the current command line */ - cli_do_ctrl_c, /* received ^c */ - cli_do_ctrl_d, /* received ^d on empty line */ - cli_do_ctrl_z, /* received ^z */ - - cli_do_eof, /* hit "EOF" */ - - cli_do_count /* number of different cli_do_xxx */ -} ; - -/*============================================================================== - * Functions in vty.c -- used in any of the family - */ -extern enum cmd_return_code uty_command(struct vty *vty) ; -extern enum cmd_return_code uty_auth (struct vty *vty, const char *buf, - enum cli_do cli_do) ; -extern enum cmd_return_code vty_cmd_exit(struct vty* vty) ; -extern enum cmd_return_code vty_cmd_end(struct vty* vty) ; -extern enum cmd_return_code uty_cmd_close(struct vty *vty, const char* reason) ; -extern enum cmd_return_code uty_stop_input(struct vty *vty) ; -extern enum cmd_return_code uty_end_config (struct vty *vty) ; -extern enum cmd_return_code uty_down_level (struct vty *vty) ; - -extern bool vty_config_lock (struct vty *, enum node_type node); -extern void vty_config_unlock (struct vty *, enum node_type node); -extern void uty_config_unlock (struct vty *vty, enum node_type node) ; - -/*============================================================================== - * Functions in vty_cli -- used outside the immediate vty family - */ -extern void vty_queued_result(struct vty* vty, enum cmd_return_code ret); -extern void uty_set_host_name(const char* name) ; - -/*============================================================================== - * Functions in vty_io -- used outside the immediate vty family - */ -extern void vty_open_config_write(struct vty* vty, int fd) ; -extern int vty_close_config_write(struct vty*) ; - -extern void vty_log_fixed (const char *buf, size_t len); - -extern void uty_log (struct logline* ll, struct zlog *zl, int priority, - const char *format, va_list va); - -/*============================================================================== - * Functions in command.c - */ -extern void cmd_post_command(struct vty* vty, int ret) ; - -#endif /* _ZEBRA_UTY_H */ diff --git a/lib/vector.c b/lib/vector.c index 6df8d409..15371930 100644 --- a/lib/vector.c +++ b/lib/vector.c @@ -22,7 +22,7 @@ * 02111-1307, USA. */ -#include <zebra.h> +//#include <zebra.h> #include "vector.h" #include "memory.h" @@ -1055,7 +1055,7 @@ vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val, if (v->end <= 1) { - *result = (v->end == 0) ? -1 : cmp(&p_val, (const void**)&v->p_items[0]) ; + *result = (v->end == 0) ? -1 : cmp(&p_val, (const cvp*)&v->p_items[0]) ; return 0 ; /* Stop dead if 0 or 1 items */ } ; @@ -1064,12 +1064,12 @@ vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val, ih = v->end - 1 ; /* Pick off the edge cases: >= last and <= first. */ - if ((c = cmp(&p_val, (const void**)&v->p_items[ih])) >= 0) + if ((c = cmp(&p_val, (const cvp*)&v->p_items[ih])) >= 0) { *result = c ; /* 0 => found. +1 => val > last */ return ih ; /* return high index. */ } ; - if ((c = cmp(&p_val, (const void**)&v->p_items[il])) <= 0) + if ((c = cmp(&p_val, (const cvp*)&v->p_items[il])) <= 0) { *result = c ; /* 0 => found. -1 => val < first */ return il ; /* return low index. */ @@ -1086,7 +1086,7 @@ vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val, return il ; /* return il: item[il] < val < item[il+1] */ } ; /* We now know that il < iv < ih */ - c = cmp(&p_val, (const void**)&v->p_items[iv]) ; + c = cmp(&p_val, (const cvp*)&v->p_items[iv]) ; if (c == 0) { *result = 0 ; diff --git a/lib/vector.h b/lib/vector.h index 58b9e415..bae2a2f8 100644 --- a/lib/vector.h +++ b/lib/vector.h @@ -50,6 +50,16 @@ struct vector typedef struct vector vector_t[1] ; /* embedded vector structure */ typedef struct vector* vector ; /* pointer to vector structure */ +/* Setting a vector object to all zeros is enough to initialise it to + * an empty vector. + */ +enum +{ + VECTOR_INIT_ALL_ZEROS = true +} ; + +#define VECTOR_INIT_EMPTY { .p_items = NULL, .end = 0, .limit = 0 } + /* Under very controlled circumstances, may access the vector body */ typedef p_vector_item const* vector_body_t ; @@ -162,10 +172,10 @@ Inline p_vector_item vector_shift_item(vector v) ; extern void vector_insert(vector v, vector_index_t i, vector_length_t n) ; extern void vector_delete(vector v, vector_index_t i, vector_length_t n) ; -typedef int vector_bsearch_cmp(const void** pp_val, const void** item) ; +typedef int vector_bsearch_cmp(const cvp* pp_val, const cvp* item) ; vector_index_t vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val, int* result) ; -typedef int vector_sort_cmp(const void** a, const void** b) ; +typedef int vector_sort_cmp(const cvp* a, const cvp* b) ; void vector_sort(vector v, vector_sort_cmp* cmp) ; extern vector vector_copy_here(vector dst, vector src) ; diff --git a/lib/version.h.in b/lib/version.h.in index 429474d1..ded43944 100644 --- a/lib/version.h.in +++ b/lib/version.h.in @@ -34,6 +34,8 @@ #define QUAGGA_COPYRIGHT "Copyright 1996-2005 Kunihiro Ishiguro, et al." +#include <sys/types.h> + pid_t pid_output (const char *); #ifndef HAVE_DAEMON diff --git a/lib/vio_fifo.c b/lib/vio_fifo.c index 10c4a343..e6365861 100644 --- a/lib/vio_fifo.c +++ b/lib/vio_fifo.c @@ -20,14 +20,13 @@ */ #include "misc.h" +#include <stdio.h> #include <string.h> #include "vio_fifo.h" #include "network.h" -#include "list_util.h" #include "memory.h" -#include "zassert.h" /*============================================================================== * VTY I/O FIFO manages an arbitrary length byte-wise FIFO buffer. @@ -39,7 +38,6 @@ * ever needed. * * When releasing lumps, keeps one lump "spare", to be reused as necessary. - * This is used in ... TODO <<<< And is released... * *------------------------------------------------------------------------------ * Implementation notes: @@ -48,97 +46,218 @@ * * Once a lump has been allocated there is always one lump in the FIFO. * + * The hold_mark allows the get_ptr to move forward, but retaining the data in + * the FIFO until the hold_mark is cleared. Can move the get_ptr back to the + * hold_mark to reread the data. + * + * The end_mark allows put_ptr to move forward, but the new data cannot be got + * from the FIFO until the end_mark is cleared. Can discard the new data + * and move the put_ptr back to the end_mark. + * + * There are four lumps of interest: + * + * * head -- where the hold_mark is, if there is one. + * + * * get_lump -- where the get_ptr is. + * Same as head when no hold_mark. + * + * * end_lump -- where the end_mark is, if there is one. + * Same as tail when no end mark. + * + * * tail -- where the put_ptr is. + * + * Some or all of those may be the same, depending on how big the FIFO is. + * * The following are expected to be true: * - * * put_ptr == get_ptr => FIFO empty + * * set <=> at least one lump in the FIFO + * + * * as_one <=> set && get_lump == tail && !end_mark + * => get_end moves with put_ptr * - * * put_ptr == tail->end -- at all times (NULL when no lumps) + * * hold_mark => there is a hold mark & hold_ptr is valid + * & head lump contains mark + * !hold_mark => get_lump == head & hold_ptr == NULL + * + * * end_mark => there is an end_mark & end_end is valid + * & end_lump contains mark + * !end_mark => end_lump == tail & end_ptr == NULL + * + * * put_ptr == get_ptr => FIFO empty -- unless hold_mark and + * hold_ptr != get_ptr. + * + * * put_end == tail->end -- at all times (NULL when no lumps) * * put_ptr >= tail->data ) otherwise something is broken * put_ptr <= tail->end ) * - * * get_ptr == head->end -- when there is more than one lump - * get_ptr <= put_ptr -- when there is only one lump + * * get_end == get_lump->end -- when get_lump != end_lump + * == end_end -- when get_lump == end_lump & end_mark set + * <= put_ptr -- when get_lump == end_lump & no end_mark + * + * See note below on get_end and as_one flag. * - * get_ptr >= head->data ) otherwise something is broken - * get_ptr <= head->end ) + * get_ptr >= get_lump->data ) otherwise something is broken + * get_ptr <= get_lump->end ) * * * put_ptr == put_end => tail lump is full * put_ptr < put_end => space exists in the tail lump * put_ptr > put_end => broken * - * * get_ptr == get_end => head lump is empty - * BUT if there is only one lump, make sure that - * get_end == put_ptr. - * get_ptr < get_end => data exists in the head lump + * * get_ptr == get_end => nothing to get from current get_lump + * BUT if as_one, make sure that get_end == put_ptr + * get_ptr < get_end => data exists in the current get_lump * get_ptr > get_end => broken * * Note that: * - * * when the get_ptr reaches the put_ptr the pointers are reset to the - * start of the one and only lump. + * * while get_ptr <= get_end can get stuff without worrying about other + * pointers or moving between lumps etc. It is permissible to leave + * get_ptr == get_end -- this will be tidied up on the next get operation, + * or any other time vio_fifo_sync_get() is called. Leaving get_ptr in + * that state delays the discard of the now empty lump, but has no other + * real downside. + * + * Similarly, while put_ptr <= put_end, can put stuff without worrying + * about other pointers or moving between lumps etc. + * + * When getting, if get_ptr == get_end, or require more data than is + * immediately available, need to use vio_fifo_sync_get(), to do that. + * + * * the value of get_end depends on whether get_lump == end_lump, and then + * whether there is an end_mark. + * + * When get_lump == end_lump && !end_mark, then get_end may be out of date + * because get_ptr has been advanced. This is dealt with by + * vio_fifo_sync_get(), which uses the as_one flag to signal that it should + * set get_end = put_ptr and then (re)check for anything to get. * - * Everywhere that the get_ptr is moved, must check for meeting the - * put_ptr and reset pointers. At the same time, when reaches the end of - * a lump, gets rid of it. + * The as_one flag is there to save a little work. Making sure that the + * get_end is up to date can be done often when getting from the FIFO. So + * the maintenance of the flag should be worth the effort. * - * * when advancing the put_ptr does not check for advancing the get_end. + * * some care must be taken to ensure that are not fooled by the + * ambiguity of a pointer to the end of one lump and a pointer to the + * start of the next -- these are really equal, but they don't look as if + * they are ! * - * The one exception to this, is that when the put_ptr advances to a new - * block, if there was one lump, sets the get_end to the end of that block. + * - put_ptr -- if at the end of the last lump there is no next lump ! * - * Everywhere that the get_end is used, must check for there being one - * lump and the possibility that put_ptr has changed. + * When a new lump is added, the put_ptr advances to the + * start of the new last lump. + * + * - end_end -- when set from the put_ptr, this can be at the end of + * the last lump, but as above, there is no next lump. + * + * When a new lump is added, if the end_end is at the + * end of the last lump, it is moved to the start of the + * new last lump (along with the out_ptr). + * + * So... end_end will never be ambiguous. + * + * - get_ptr -- this can be ambiguous. + * + * When getting bytes, if the segment get_ptr..get_end + * is sufficient, then nothing else is required. + * + * Otherwise, vio_fifo_sync_get() will sort things out, + * including resetting all pointers if the FIFO has been + * emptied. + * + * - hold_ptr -- when set from get_ptr this could be at the end of the + * first lump -- but when vio_fifo_sync_get() is called, + * that will be spotted and sorted out. + * + * If get_ptr is set from an ambiguous hold_ptr, that is + * also taken care of by the next vio_fifo_sync_get(). + * + * When doing things with hold_ptr, does a number of + * vio_fifo_sync_get() operations, so the hold_ptr should + * not, in practice, be ambiguous. + * + * * Before the first lump is allocated the FIFO appears empty (of course) + * but may have hold_mark and/or end_mark set, and these work as expected. */ /*============================================================================== * Initialisation, allocation and freeing of FIFO and lumps thereof. */ -/* Return default size, or given size rounded up to 16 byte boundary */ +/*------------------------------------------------------------------------------ + * Return default size, or given size rounded up to 128 byte boundary + */ static size_t vio_fifo_size(size_t size) { +#if VIO_FIFO_DEBUG +#warning VIO_FIFO_DEBUG and 29 byte lumps ! + return 29 ; +#else if (size == 0) return 4096 ; else - return ((size + 16 - 1) / 16) * 16 ; + return ((size + 128 - 1) / 128) * 128 ; +#endif +} ; + +/*------------------------------------------------------------------------------ + * Set and return true end of lump. + * + * End of lump can be set short of true end when putting stuff, if wish to + * move to next lump early (eg if not enough room left in current lump). + * + * When reusing a lump, need to restore the true lump end. + */ +static inline char* +vio_fifo_true_lump_size(vio_fifo_lump lump) +{ + return lump->end = lump->data + lump->size ; } ; /*============================================================================== * Initialise VTY I/O FIFO -- allocating if required. */ extern vio_fifo -vio_fifo_init_new(vio_fifo vf, size_t size) +vio_fifo_init_new(vio_fifo vff, size_t size) { - if (vf == NULL) - vf = XCALLOC(MTYPE_VIO_FIFO, sizeof(vio_fifo_t)) ; + if (vff == NULL) + vff = XCALLOC(MTYPE_VIO_FIFO, sizeof(vio_fifo_t)) ; else - memset(vf, 0, sizeof(vio_fifo_t)) ; + memset(vff, 0, sizeof(vio_fifo_t)) ; /* Zeroising the the vio_fifo_t has set: * - * lump -- base pair, both pointers NULL => list is empty + * base -- base pair, both pointers NULL => list is empty * - * put_ptr -- NULL ) no lump to put anything into - * put_end -- NULL ) put_ptr == put_end => no room in current lump + * set -- false -- nothing set, yet. + * as_one -- false -- get_lump != tail or end_mark + * hold_mark -- false -- no hold mark + * end_mark -- false -- no end mark + * + * hold_ptr -- NULL no hold_mark * + * get_lump -- NULL ) * get_ptr -- NULL ) no lump to get anything from * get_end -- NULL ) get_ptr -- get_end => nothing left in current lump * - * rdr_lump -- NULL ) no rdr_lump - * rdr_ptr -- NULL + * end_lump -- NULL no lump at end of what can get + * end_end -- NULL no end_mark * - * spare -- NULL no spare lump + * put_ptr -- NULL ) no lump to put anything into + * put_end -- NULL ) put_ptr == put_end => no room in current lump * - * ALSO put_ptr == get_ptr => FIFO is empty ! + * size -- 0 no size set for lumps (yet) + * + * spare -- NULL no spare lump */ + confirm(VIO_FIFO_INIT_ALL_ZEROS) ; - vf->size = vio_fifo_size(size) ; + if (size != 0) + vff->size = vio_fifo_size(size) ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; - return vf ; + return vff ; } /*------------------------------------------------------------------------------ @@ -146,7 +265,8 @@ vio_fifo_init_new(vio_fifo vf, size_t size) * * Does nothing if given a NULL pointer -- must already have been freed ! * - * If does not free the FIFO structure, resets it all empty. + * If does not free the FIFO structure, resets it all empty, keeping the + * current size setting. * * Frees *all* FIFO lumps. * @@ -154,104 +274,289 @@ vio_fifo_init_new(vio_fifo vf, size_t size) * vio_fifo_reset_free(vio_fifo) */ extern vio_fifo -vio_fifo_reset(vio_fifo vf, int free_structure) +vio_fifo_reset(vio_fifo vff, free_keep_b free_structure) { vio_fifo_lump lump ; - if (vf == NULL) + if (vff == NULL) return NULL ; - while (ddl_pop(&lump, vf->base, list) != NULL) + while (ddl_pop(&lump, vff->base, list) != NULL) XFREE(MTYPE_VIO_FIFO_LUMP, lump) ; - if (vf->spare != NULL) - XFREE(MTYPE_VIO_FIFO_LUMP, vf->spare) ; + if (vff->spare != NULL) + XFREE(MTYPE_VIO_FIFO_LUMP, vff->spare) ; + + confirm(free_it == true) ; if (free_structure) - XFREE(MTYPE_VIO_FIFO, vf) ; /* sets vf = NULL */ + XFREE(MTYPE_VIO_FIFO, vff) ; /* sets vff = NULL */ else - vio_fifo_init_new(vf, vf->size) ; + vio_fifo_init_new(vff, vff->size) ; - return vf ; + return vff ; +} ; + +/*------------------------------------------------------------------------------ + * The FIFO has one lump -- set all pointers. + * + * Preserves end_mark -- setting to new put_ptr position + * Preserves hold_mark -- setting to new put_ptr position + * + * Sets as_one if no end_mark. + */ +inline static void +vio_fifo_ptr_set(vio_fifo vff, vio_fifo_lump lump) +{ + vff->as_one = !vff->end_mark ; + + vff->put_ptr = lump->data ; + vff->put_end = vio_fifo_true_lump_size(lump) ; + + vff->hold_ptr = vff->hold_mark ? vff->put_ptr : NULL ; + + vff->get_lump = lump ; + vff->get_ptr = vff->put_ptr ; + vff->get_end = vff->put_ptr ; + + vff->end_lump = lump ; + vff->end_end = vff->end_mark ? vff->put_ptr : NULL ; } ; /*------------------------------------------------------------------------------ * The FIFO is empty, with one lump -- reset all pointers. + * + * Preserves end_mark -- setting to new put_ptr position + * Preserves hold_mark -- setting to new put_ptr position + * + * Sets as_one if no end_mark. */ inline static void -vio_fifo_ptr_reset(vio_fifo vf, vio_fifo_lump lump) +vio_fifo_ptr_reset(vio_fifo vff, vio_fifo_lump lump) { - if (vf->rdr_lump != NULL) - { - assert((lump == vf->rdr_lump) && (vf->rdr_ptr == vf->get_ptr)) ; - vf->rdr_ptr = lump->data ; - } ; + assert(vff->set) ; + assert(lump == ddl_tail(vff->base)) ; /* must be tail */ + assert(lump == ddl_head(vff->base)) ; /* and must be head */ + + vio_fifo_ptr_set(vff, lump) ; +} ; + +/*------------------------------------------------------------------------------ + * Synchronise get_end and put_ptr (if required) + * + * When in the same lump, the get_end and the put_ptr should be the same. But + * maintaining that every time something is put into the buffer is a pain, so + * we allow the put_ptr to get ahead, and re-synchronise when get_ptr hits + * get_end, or any other time we need the pointers to be straight. + * + * The as_one flag takes a little maintenance... it means: + * + * (get_lump == tail) && set && !end_mark + * + * If get_ptr is at the end of what can be got from the current lump, advances + * to the next lump if possible, releasing anything that can be released. + * + * If the FIFO is empty (with one lump), will crash all pointers down to start + * of current lump. The FIFO is empty if get_ptr == put_ptr, and there are no + * bytes held before the get_ptr. + * + * NB: object of the hold_mark is to allow users of the FIFO to store pointers + * to sections of the FIFO returned by vio_fifo_get_lump() and stepped + * over by vio_fifo_got_upto(). Note that where there is nothing to + * read vio_fifo_get_lump() returns NULL -- so do not have to preserve + * the hold_ptr when hold_ptr == get_ptr (== end_end) == put_ptr -- that + * is, if hold_ptr == get_ptr, we can move the hold_ptr around with the + * get_ptr. + * + * Can reach empty state without realising it, because the "get" stuff is not + * required to check every time a byte is read -- in fact, it is not necessary + * to check until cannot get anything because get_ptr == get_end. + * + * NB: with an end_mark there is the ambiguous position at the end of a + * lump -- which is the same as the start of the next lump, if any. + * + * Elsewhere we advance the end_end if it was at the very end of the FIFO + * and we advance the put_ptr to a new lump. But we cope here in any + * case. + * + * Returns: true <=> there is something in the (now) current get_lump. + * false <=> there is nothing else to be got (though if there is an + * end_mark, there may be stuff beyond that). + */ +static bool vio_fifo_sync_get_next(vio_fifo vff) ; +static void vio_fifo_release_head(vio_fifo vff, vio_fifo_lump lump) ; + +inline static bool +vio_fifo_sync_get(vio_fifo vff) +{ + if (vff->as_one) /* false if !vff->set */ + vff->get_end = vff->put_ptr ; - /* Note that sets the lump->end to the true lump->end */ - vf->get_ptr = vf->get_end = vf->put_ptr = lump->data ; - vf->put_end = lump->end = lump->data + lump->size ; + if (vff->get_ptr < vff->get_end) /* both NULL if !vff->set */ + return true ; /* have at least one byte */ + + return vio_fifo_sync_get_next(vff) ; +} ; + +/*------------------------------------------------------------------------------ + * See if fifo really is or is not empty (get_ptr == end_end or == put_ptr). + * + * Used by vio_fifo_empty(). Called iff get_ptr >= get_end ! + * + * So need to vio_fifo_sync_get() and test again against end_end & put_ptr. + */ +Private bool +vio_fifo_do_empty(vio_fifo vff) +{ + return ! vio_fifo_sync_get(vff) ; } ; /*------------------------------------------------------------------------------ - * The FIFO is utterly empty, with ZERO lumps -- unset all pointers. + * Set a new get_lump/get_ptr/get_end set. And set as_one to suit. */ inline static void -vio_fifo_ptr_unset(vio_fifo vf) +vio_fifo_set_get_ptr(vio_fifo vff, void* ptr, vio_fifo_lump lump) { - assert((ddl_head(vf->base) == NULL) && (ddl_tail(vf->base) == NULL)) ; + vff->get_lump = lump ; + vff->get_ptr = ptr ; - vf->one = false ; + if (lump != vff->end_lump) + { + vff->get_end = lump->end ; + vff->as_one = false ; + } + else if (vff->end_mark) + { + vff->get_end = vff->end_end ; + vff->as_one = false ; + } + else + { + vff->get_end = vff->put_ptr ; + vff->as_one = true ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Move on to next lump to get stuff from. + * + * For use by vio_fifo_sync_get() *ONLY*. + * + * Asserts that (vff->get_ptr == vff->get_end) and assumes that if as_one, then + * get_end == put_ptr ! + * + * Returns: true <=> at least one byte in FIFO available to get. + */ +static bool +vio_fifo_sync_get_next(vio_fifo vff) +{ + bool hold_empty ; - vf->put_ptr = NULL ; - vf->put_end = NULL ; - vf->get_ptr = NULL ; - vf->get_end = NULL ; + assert(vff->get_ptr == vff->get_end) ; - vf->rdr_lump = NULL ; - vf->rdr_ptr = NULL ; + VIO_FIFO_DEBUG_VERIFY(vff) ; + + if (!vff->set) + return false ; /* quit before get into any trouble */ + + /* Worry about whether there is anything held. + * + * NB: we know that at this point the get_ptr == get_end, so is at the end + * of the current get_lump. The hold_ptr cannot be ahead of get_ptr, + * so the test hold_ptr == get_ptr is sufficient to detect that there + * is nothing, in fact, held. + */ + hold_empty = !vff->hold_mark || (vff->hold_ptr == vff->get_ptr) ; + + /* If we are not at the end_lump, step forward, discarding current + * lump unless hold_mark. + */ + if (vff->get_lump != vff->end_lump) + { + vio_fifo_lump next ; + next = ddl_next(vff->get_lump, list) ; + + vio_fifo_set_get_ptr(vff, next->data, next) ; + + if (hold_empty) + { + vio_fifo_release_head(vff, next) ; + + /* If there is a hold_mark, then had hold_ptr == get_ptr + * and this is where we keep hold_ptr & get_ptr in sync. + */ + if (vff->hold_mark) + vff->hold_ptr = vff->get_ptr ; + } ; + + /* Return the get state now */ + if (vff->get_ptr < vff->get_end) + return true ; + } ; + + /* Still have get_ptr == get_end => nothing more to get. + * + * Check now for empty FIFO, and reset all pointers if that is the case. + */ + if ((vff->get_ptr == vff->put_ptr) && hold_empty) + { + if (vff->hold_mark) + assert(vff->hold_ptr == vff->get_ptr) ; + + if (vff->end_mark) + assert(vff->end_end == vff->put_ptr) ; + + vio_fifo_ptr_reset(vff, vff->end_lump) ; + } + + return false ; /* nothing to get */ } ; /*------------------------------------------------------------------------------ * Clear out contents of FIFO -- will continue to use the FIFO. * + * If required, clears any hold mark and/or end mark. + * * Keeps one FIFO lump. (Frees everything else, including any spare.) * * Does nothing if there is no FIFO ! */ extern void -vio_fifo_clear(vio_fifo vf) +vio_fifo_clear(vio_fifo vff, bool clear_marks) { - vio_fifo_lump tail ; - - if (vf == NULL) + if (vff == NULL) return ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; - tail = ddl_tail(vf->base) ; + vff->as_one = vff->set ; + if (clear_marks) + { + vff->hold_mark = false ; /* Discard marks */ + vff->end_mark = false ; + } ; - if (tail != NULL) + if (vff->set) { - while (ddl_head(vf->base) != tail) + vio_fifo_lump lump ; + + while (1) { - vio_fifo_lump lump ; - ddl_pop(&lump, vf->base, list) ; + lump = ddl_head(vff->base) ; + if (lump == ddl_tail(vff->base)) + break ; + + ddl_pop(&lump, vff->base, list) ; XFREE(MTYPE_VIO_FIFO_LUMP, lump) ; } ; - vf->rdr_lump = NULL ; /* clear rdr */ - vf->rdr_ptr = NULL ; - - vf->one = true ; - vio_fifo_ptr_reset(vf, tail) ; - } - else - vio_fifo_ptr_unset(vf) ; + vio_fifo_ptr_reset(vff, lump) ; + } ; - if (vf->spare != NULL) - XFREE(MTYPE_VIO_FIFO_LUMP, vf->spare) ; /* sets vf->spare = NULL */ + if (vff->spare != NULL) + XFREE(MTYPE_VIO_FIFO_LUMP, vff->spare) ; /* sets vff->spare = NULL */ - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; } ; /*------------------------------------------------------------------------------ @@ -266,279 +571,226 @@ vio_fifo_clear(vio_fifo vf) * Returns: room available as described */ extern size_t -vio_fifo_room(vio_fifo vf) +vio_fifo_room(vio_fifo vff) { - if (vf->put_ptr != NULL) - return vf->put_end - vf->put_ptr ; + if (vff->set) + return vff->put_end - vff->put_ptr ; else - return vf->size ; + return vff->size ; } ; /*------------------------------------------------------------------------------ - * Allocate another lump for putting into. + * Need a new lump to put stuff into. * - * Call when (vf->put_ptr >= vf->put_end) -- asserts that they are equal. + * Call when (vff->put_ptr >= vff->put_end) -- asserts that they are equal. * - * Set the put_ptr/put_end pointers to point at the new lump. + * If the FIFO is, in fact, empty but with at least one lump, then does not + * allocate anything more, but releases all lumps but the last lump and then + * resets all pointers to the start of that lump. * - * If this is the first lump allocated, set the get_ptr/get_end pointers too. + * Otherwise, allocates a new lump (or reuses the spare) to the requested size, + * and updates all pointers as required. (Allocates to vff->size if + * requested size is zero.) * - * If have just filled the first lump on the list, update the get_end pointer - * to reflect the fact that the out lump is now full. + * NB: if there is an end_mark, makes sure that it advances with the put_ptr + * if currently end_end == the put_ptr. */ -extern void -vio_fifo_lump_new(vio_fifo vf, size_t size) +Private void +vio_fifo_lump_new(vio_fifo vff, size_t size) { vio_fifo_lump lump ; - int first_alloc ; + size_t std_size ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + passert(vff->put_ptr == vff->put_end) ; /* must be end of tail lump + (or both NULL) */ + VIO_FIFO_DEBUG_VERIFY(vff) ; - passert(vf->put_ptr == vf->put_end) ; /* must be end of tail lump */ + /* First, make sure that the get side is synchronised, which may advance + * various pointers, release lumps and possibly reset now empty FIFO. + * + * If synchronising the get side does not yield space, then the FIFO is + * either not set or it has something in it (including held stuff). + */ + vio_fifo_sync_get(vff) ; - if (vf->one) - vf->get_end = vf->put_ptr ; /* update get_end */ + if (vff->put_ptr < vff->put_end) + return ; /* Done if have created space */ - lump = ddl_tail(vf->base) ; + /* If we can use the spare, do so, otherwise make it to size */ + lump = vff->spare ; + vff->spare = NULL ; - first_alloc = (lump == NULL) ; /* extra initialisation needed */ + std_size = vio_fifo_size(vff->size) ; /* normalised standard */ - if (first_alloc) - assert(vf->put_ptr == NULL) ; /* must all be NULL together */ + if (size <= std_size) + size = std_size ; /* use standard as a minimum */ else - assert(vf->put_ptr == lump->end) ; /* must be end of tail lump */ +#if VIO_FIFO_DEBUG + size |= 3 ; /* most of the time a little bigger */ +#else + size = vio_fifo_size(size) ; /* normalise requested size */ +#endif - size = vio_fifo_size(size) ; - - if ((vf->spare != NULL) && (vf->spare->size >= size)) - { - lump = vf->spare ; - vf->spare = NULL ; - } - else + if ((lump == NULL) || (lump->size < size)) { - lump = XMALLOC(MTYPE_VIO_FIFO_LUMP, - offsetof(vio_fifo_lump_t, data[size])) ; + /* If there was no spare, lump == NULL and XREALLOC == XMALLOC. + * If there was a spare that was too small, better extend that than + * keep a sub-standard spare. + */ + lump = XREALLOC(MTYPE_VIO_FIFO_LUMP, lump, + offsetof(vio_fifo_lump_t, data[size])) ; lump->size = size ; + lump->end = lump->data + size ; } ; - lump->end = lump->data + lump->size ; - - ddl_append(vf->base, lump, list) ; - - vf->one = first_alloc ; - - vf->put_ptr = lump->data ; - vf->put_end = lump->end ; - - if (first_alloc) - { - vf->get_ptr = vf->put_ptr ; /* get_ptr == put_ptr => empty */ - vf->get_end = vf->put_ptr ; - } ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; -} ; + ddl_append(vff->base, lump, list) ; -/*------------------------------------------------------------------------------ - * Release lump, head or tail (or both) and update pointers. - * - * Note that this does release the lump if it is the only lump. - * - * Do nothing if nothing is yet allocated. - * - * If releasing the only lump in the FIFO, resets all pointers to NULL. - * - * If releasing the head lump: - * - * * the lump MUST be finished with -- so vf->get_ptr must be at the end - * - * If releasing the only lump, the FIFO MUST be empty. - * - * * if the lump is the current vf->rdr_lump, the reader must be at the - * end too -- ie it must be the same as the vf->get_ptr ! - * - * If releasing the tail lump: - * - * * the lump MUST be empty - * - * If releasing the only lump, the FIFO MUST be empty. - * - * * if the lump is the current vf->rdr_lump, the reader must be at the - * end too -- ie it must be the same as the vf->get_ptr ! - */ -static void -vio_fifo_lump_release(vio_fifo vf, vio_fifo_lump lump) -{ - vio_fifo_lump head ; - vio_fifo_lump tail ; - vio_fifo_lump free ; - bool release_head ; - bool release_tail ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; - - /* Prepare and check whether removing head or tail (or both) */ - head = ddl_head(vf->base) ; - tail = ddl_tail(vf->base) ; - - release_head = (lump == head) ; - release_tail = (lump == tail) ; - - assert(release_head || release_tail) ; - - /* Unless nothing ever allocated -- release the lump. */ - free = lump ; /* expect to free the lump */ - if (lump != NULL) + /* If not just allocated the first lump, set the put_ptr and normalise any + * end_mark or update end_lump. + * + * If is first block to be allocated, set all pointers, taking into account + * any end_mark and/or hold_mark. + */ + if (vff->set) { - vio_fifo_lump keep ; + /* Allocated new lump on the end of a not-empty fifo. + * + * Have to watch out for cases where the get_ptr and/or end_end are + * equal to the put_ptr, which is about to move to the start of a new + * lump, so as to avoid ambiguity ! + */ - /* Consistency checks */ - if (release_head) + if (vff->get_ptr == vff->put_ptr) { - if (release_tail) - assert(vf->get_ptr == vf->put_ptr) ; - else - assert(vf->get_ptr == lump->end) ; + /* If the fifo is empty, the vio_fifo_sync_get() above will have + * spotted it. So can only get here iff there is something held in + * the fifo behind the get_ptr. + * + * If was as_one, is still as_one. + * + * If was not as_one then must have end_mark with end_end == put_ptr, + * which will be dealt with below. + */ + assert(vff->hold_mark && (vff->get_ptr != vff->hold_ptr)) ; - if (vf->rdr_lump == lump) - assert(vf->rdr_ptr == vf->get_ptr) ; + vff->get_ptr = lump->data ; + vff->get_end = lump->data ; + vff->get_lump = lump ; } - else if (release_tail) + else { - assert(vf->put_ptr == lump->data) ; - - if (vf->rdr_lump == lump) - assert(vf->rdr_ptr == vf->put_ptr) ; + /* If were as_one, then will no longer be, because put_ptr is about + * to advance to the start of the new lump. + */ + vff->as_one = false ; } ; - /* Remove lump from FIFO and decide whether to keep as spare, or - * which of spare and this to free. - */ - ddl_del(vf->base, lump, list) ; - - keep = vf->spare ; /* expect to keep current spare */ + if (vff->end_mark) + { + assert(!vff->as_one) ; - if ((keep == NULL) || (keep->size < lump->size)) + /* The end_end follows the put_ptr iff they are equal + * + * If the get_ptr also equals the put_ptr, it has already advanced. + */ + if (vff->end_end == vff->put_ptr) + { + vff->end_end = lump->data ; + vff->end_lump = lump ; + } ; + } + else { - keep = lump ; - free = vf->spare ; + /* No end_mark => end_lump simply follows the put_ptr. */ + vff->end_lump = lump ; } ; - vf->spare = keep ; - - head = ddl_head(vf->base) ; /* changed if released head */ - tail = ddl_tail(vf->base) ; /* changed if released tail */ + vff->put_ptr = lump->data ; + vff->put_end = vio_fifo_true_lump_size(lump) ; + } + else + { + /* Allocated lump for previously empty fifo -- set all pointers to the + * start of the lump, except for put_end. + */ + vff->set = true ; + vio_fifo_ptr_set(vff, lump) ; } ; - /* Now update pointers... depending on what was released and what have - * left. - */ - if (head == NULL) - { - /* Deal with FIFO that now has no lumps or had none to start with */ - if (lump != NULL) - assert(vf->one) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; +} ; - vio_fifo_ptr_unset(vf) ; - } - else +/*------------------------------------------------------------------------------ + * Release the given lump, provided it is neither get_lump nor end_lump. + * + * If don't have a spare lump, keep this one. Otherwise, keep larger of + * this and current spare. + */ +static void +vio_fifo_release_lump(vio_fifo vff, vio_fifo_lump lump) +{ + assert(lump != NULL) ; + assert(lump != vff->get_lump) ; + assert(lump != vff->end_lump) ; + + if (vff->spare != NULL) { - /* Have at least one lump left -- so must have had at least two ! */ - assert(!vf->one) ; + vio_fifo_lump free ; - vf->one = (head == tail) ; /* update */ + free = vff->spare ; - if (release_head) + if (free->size > lump->size) { - /* Released the head. - * - * Update the vf->get_ptr and the vf->get_end. - */ - vf->get_ptr = head->data ; - if (vf->one) - vf->get_end = vf->put_ptr ; - else - vf->get_end = head->end ; - - /* Update vf->rdr_ptr and vf->rdr_lump. */ - if (vf->rdr_lump == lump) - { - vf->rdr_lump = head ; - vf->rdr_ptr = head->data ; - } ; - } - else - { - /* Released the tail. - * Update the vf->put_ptr and vf->put_end - */ - vf->put_ptr = vf->put_end = tail->end ; - - /* Update vf->rdr_ptr and vf->rdr_lump. */ - if (vf->rdr_lump == lump) - { - vf->rdr_lump = tail ; - vf->rdr_ptr = tail->end ; - } ; + free = lump ; + lump = vff->spare ; } ; - } ; - - /* Finally, free any lump that is actually to be freed */ - if (free != NULL) - XFREE(MTYPE_VIO_FIFO_LUMP, free) ; + XFREE(MTYPE_VIO_FIFO_LUMP, free) ; + } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + vff->spare = lump ; } ; /*------------------------------------------------------------------------------ - * Re-allocate lump for putting into. - * - * Call when vf->put_ptr == start of last lump, and that lump is not big - * enough ! + * Release lumps from head up to, but not including, given lump. * - * There must be at least one lump. + * NB: must be "set" and must not attempt to release the get_lump or the + * end_lump. * - * Updates put_ptr/put_end pointers to point at the new lump. + * So MUST advance at least the get_lump before calling this. * - * Updates get_ptr/get_end pointers if required. - * - * Updates rdr_ptr if required. + * It is the caller's responsibility to update get and/or hold pointers. */ static void -vio_fifo_lump_renew(vio_fifo vf, vio_fifo_lump lump, size_t size) +vio_fifo_release_head(vio_fifo vff, vio_fifo_lump upto) { - bool rdr_set ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; - - /* FIFO may not be completely empty. - * This must be the last lump. - * The last lump must be empty. - */ - assert((lump != NULL) && (lump == ddl_tail(vf->base))) ; - - /* Remove the last, *empty* lump, and update all pointers to suit. */ - rdr_set = (vf->rdr_lump == lump) ; - - vio_fifo_lump_release(vf, lump) ; + assert(vff->set) ; - /* Now allocate a new lump with the required size */ - vio_fifo_lump_new(vf, size) ; - - /* Restore the rdr_ptr, if required */ - if (rdr_set) + while (upto != ddl_head(vff->base)) { - vio_fifo_lump tail ; + vio_fifo_lump lump ; + vio_fifo_release_lump(vff, ddl_pop(&lump, vff->base, list)) ; + } ; +} ; - tail = ddl_tail(vf->base) ; +/*------------------------------------------------------------------------------ + * Release lumps from tail back to, but not including, given lump. + * + * NB: must be "set" and must not attempt to release the get_lump or the + * end_lump. + * + * It is the caller's responsibility to update end and/or put pointers. + */ +static void +vio_fifo_release_tail(vio_fifo vff, vio_fifo_lump backto) +{ + assert(vff->set) ; - vf->rdr_lump = tail ; - vf->rdr_ptr = tail->data ; + while (backto != ddl_tail(vff->base)) + { + vio_fifo_lump lump ; + vio_fifo_release_lump(vff, ddl_crop(&lump, vff->base, list)) ; } ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; } ; /*============================================================================== @@ -546,196 +798,461 @@ vio_fifo_lump_renew(vio_fifo vf, vio_fifo_lump lump, size_t size) */ /*------------------------------------------------------------------------------ - * Store 'n' bytes -- allocate new lump if current is exhausted. + * Put 'n' bytes -- allocating as required. */ extern void -vio_fifo_put(vio_fifo vf, const char* src, size_t n) +vio_fifo_put_bytes(vio_fifo vff, const char* src, size_t n) { - size_t take ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; while (n > 0) { - if (vf->put_ptr >= vf->put_end) - vio_fifo_lump_new(vf, vf->size) ; /* traps put_ptr > put_end */ + size_t take ; - take = (vf->put_end - vf->put_ptr) ; + if (vff->put_ptr >= vff->put_end) + vio_fifo_lump_new(vff, 0) ; /* traps put_ptr > put_end */ + + take = (vff->put_end - vff->put_ptr) ; if (take > n) take = n ; - memcpy(vf->put_ptr, src, take) ; - vf->put_ptr += take ; + memcpy(vff->put_ptr, src, take) ; + vff->put_ptr += take ; src += take ; n -= take ; } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; } ; /*------------------------------------------------------------------------------ - * Formatted print to fifo -- cf printf() + * Formatted print to FIFO -- cf printf() * * Returns: >= 0 -- number of bytes written * < 0 -- failed (unlikely though that is) */ extern int -vio_fifo_printf(vio_fifo vf, const char* format, ...) +vio_fifo_printf(vio_fifo vff, const char* format, ...) { va_list args; int len ; va_start (args, format); - len = vio_fifo_vprintf(vf, format, args); + len = vio_fifo_vprintf(vff, format, args); va_end (args); return len; } ; /*------------------------------------------------------------------------------ - * Formatted print to fifo -- cf vprintf() + * Formatted print to FIFO -- cf vprintf() + * + * Does nothing if vff is NULL ! * * Returns: >= 0 -- number of bytes written * < 0 -- failed (unlikely though that is) + * + * NB: does not extend an existing lump in order to make things fit, but + * splits the result across two lumps. This ensures that at all times + * pointers into existing lumps are stable -- so pointer returned by + * vio_fifo_get_lump(), for example, cannot be upset ! */ extern int -vio_fifo_vprintf(vio_fifo vf, const char *format, va_list args) +vio_fifo_vprintf(vio_fifo vff, const char *format, va_list args) { va_list ac ; int len ; int have ; - size_t size ; - vio_fifo_lump lump ; + int had ; + int need ; + char* last ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + if (vff == NULL) + return 0 ; - size = vf->size ; /* standard allocation size */ - while (1) + VIO_FIFO_DEBUG_VERIFY(vff) ; + + /* First the simple way. + * + * Note that vsnprintf() returns the length of what it would like to + * have produced, if it had the space. That length does not include + * the trailing '\0'. In the meantime, it has output as much as it + * can, with a trailing '\0'. + */ + assert(vff->put_ptr <= vff->put_end) ; + + have = vff->put_end - vff->put_ptr ; /* what can do in current lump + if any. */ + va_copy(ac, args) ; + len = vsnprintf(vff->put_ptr, have, format, ac) ; + va_end(ac) ; + + if ((len < have) || (len == 0)) /* OK, or failed ! */ { - /* Find what space is left in the tail lump, and allocate a new, - * empty lump if required. - */ - if (vf->put_ptr >= vf->put_end) - vio_fifo_lump_new(vf, size) ; /* traps put_ptr > put_end */ + if (len > 0) + vff->put_ptr += len ; /* advance put_ptr as required */ - have = vf->put_end - vf->put_ptr ; - assert(have > 0) ; + return len ; + } ; - /* Note that vsnprintf() returns the length of what it would like to - * have produced, if it had the space. That length does not include - * the trailing '\0'. - */ - va_copy(ac, args) ; - len = vsnprintf(vf->put_ptr, have, format, ac) ; - va_end(ac) ; + /* Now know that we need len + 1 bytes in all to complete the task, and + * that it has written have - 1 bytes to the existing lump (if any). + * + * Also, len > 0. + * + * Allocate a new lump in which we can write the entire result, even if + * that is a non-standard size. + */ + need = len + 1 ; /* need includes the '\0' */ + had = have ; - if (len < have) - { - if (len < 0) - break ; /* quit if failed */ + if (had > 0) + vff->put_ptr += had ; /* step to the end */ + last = vff->put_ptr ; /* point at end of lump (NULL if none) */ - vf->put_ptr += len ; - break ; /* done */ - } ; + vio_fifo_lump_new(vff, need) ;/* new lump to do it all */ - /* Not able to complete the operation in the current buffer. - * - * If the required space (len + 1) is greater than the standard - * allocation, then need to increase the allocation for the next lump. - * - * If the current lump is empty, need to renew it with a fresh lump of - * the now known required size. - * - * If the current lump is not empty, need to cut the end off and then - * allocate a fresh lump (of the standard or now known required size). - */ - if (len >= (int)size) - size = len + 1 ; /* need a non-standard size */ + have = vff->put_end - vff->put_ptr ; + assert(have >= need) ; /* have >= 2 */ - lump = ddl_tail(vf->base) ; + /* We really expect to get the same result a second time ! */ + va_copy(ac, args) ; + len = vsnprintf(vff->put_ptr, have, format, ac) ; + va_end(ac) ; - if (vf->put_ptr == lump->data) - /* Need to replace the last, empty, lump with another empty lump, but - * big enough. - */ - vio_fifo_lump_renew(vf, lump, size) ; - else - /* Need to cut this lump short, and allocate new lump at top of loop. - */ - lump->end = vf->put_end = vf->put_ptr ; + /* Since have >= what previously said it needed, things have gone + * badly wrong if the new len is >= have. + * + * Also, things have gone badly wrong if new len is < what previously + * had, which was then not enough ! + * + * Also, things have gone badly wrong if new len == 0, because previously + * it was > 0 ! + */ + if ((len >= have) || (len < had) || (len == 0)) + return (len < 0) ? len : -1 ; + + /* Move result around if required -- len >= had */ + have = len ; + if (had > 0) + { + char* frag ; + frag = vff->put_ptr + had ; /* first character to keep */ + *(last - 1) = *(frag - 1) ; /* replace the '\0' */ + + have -= had ; /* amount to keep */ + if (have > 0) + memmove(vff->put_ptr, frag, have) ; } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + /* Advance the put_ptr past what we have in the new lump. */ + vff->put_ptr += have ; + + VIO_FIFO_DEBUG_VERIFY(vff) ; return len ; } ; +/*------------------------------------------------------------------------------ + * Read part of file into FIFO -- assuming non-blocking file + * + * Will read up to the end of the lump which meets, or exceeds the number of + * bytes requested, or until would block. + * + * Returns: 0..n -- number of bytes read + * -1 => failed -- see errno + * -2 => EOF met immediately + * + * Note: will work perfectly well for a non-blocking file -- which should + * never return EAGAIN/EWOULDBLOCK, so will return from here with + * something, error or EOF. + */ +extern int +vio_fifo_read_nb(vio_fifo vff, int fd, size_t request) +{ + size_t total ; + + total = 0 ; + + do + { + int got ; + + if (vff->put_ptr >= vff->put_end) + vio_fifo_lump_new(vff, 0) ; /* traps put_ptr > put_end */ + + got = read_nb(fd, vff->put_ptr, vff->put_end - vff->put_ptr) ; + + if (got <= 0) + { + if (got == -2) /* EOF met */ + return (total > 0) ? (int)total : got ; + else + return (got == 0) ? (int)total : got ; + } ; + + vff->put_ptr += got ; + total += got ; + + } while (total < request) ; + + return total ; +} ; + /*============================================================================== - * Get data from the FIFO. + * Copy operations -- from one fifo to another. */ -static bool vio_fifo_get_next_lump(vio_fifo vf) ; +/*------------------------------------------------------------------------------ + * Copy src fifo (everything from get_ptr to end_mark or put_ptr) to dst fifo. + * + * Create a dst fifo if there isn't one. + * + * Appends to the dst fifo. + * + * Does not change the src fifo in any way. + */ +extern vio_fifo +vio_fifo_copy(vio_fifo dst, vio_fifo src) +{ + if (dst == NULL) + dst = vio_fifo_init_new(dst, 0) ; + + if ((src != 0) && (src->set)) + { + vio_fifo_lump src_lump ; + char* src_ptr ; + + if (src->get_ptr >= src->get_end) + vio_fifo_sync_get(src) ; + + src_lump = src->get_lump ; + src_ptr = src->get_ptr ; + + while (1) + { + char* src_end ; + + if (src_lump != src->end_lump) + src_end = src_lump->end ; + else + src_end = (src->end_mark) ? src->end_end : src->put_ptr ; + + vio_fifo_put_bytes(dst, src_ptr, src_end - src_ptr) ; + + if (src_lump == src->end_lump) + break ; + + src_lump = ddl_next(src_lump, list) ; + src_ptr = src_lump->data ; + } ; + } ; + + return dst ; +} ; /*------------------------------------------------------------------------------ - * Get ready to read something out of the FIFO. + * Copy tail of src fifo (everything from end_mark to put_ptr) to dst fifo. + * + * Create a dst fifo if there isn't one. * - * Makes sure vf->get_end is up to date (if required) and if the FIFO is not - * empty, makes sure vf->get_ptr points at the next byte to be read. + * Appends to the dst fifo. * - * Returns: true <=> there is something in the FIFO. + * Does not change the src fifo in any way. */ -static inline bool -vio_fifo_get_ready(vio_fifo vf) +extern vio_fifo +vio_fifo_copy_tail(vio_fifo dst, vio_fifo src) { - assert(vf->rdr_lump == NULL) ; + if (dst == NULL) + dst = vio_fifo_init_new(dst, 0) ; - if (vf->one) - vf->get_end = vf->put_ptr ; /* make sure have everything */ + if ((src != 0) && (src->end_mark) && (src->set)) + { + vio_fifo_lump src_lump ; + char* src_ptr ; + vio_fifo_lump tail ; - if (vf->get_ptr >= vf->get_end) - if (!vio_fifo_get_next_lump(vf)) - return 0 ; /* quit now if nothing there */ + src_lump = src->end_lump ; + src_ptr = src->end_end ; + tail = ddl_tail(src->base) ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + while (1) + { + char* src_end ; - return 1 ; + if (src_lump != tail) + src_end = src_lump->end ; + else + src_end = src->put_ptr ; + + vio_fifo_put_bytes(dst, src_ptr, src_end - src_ptr) ; + + if (src_lump == tail) + break ; + + src_lump = ddl_next(src_lump, list) ; + src_ptr = src_lump->data ; + } ; + } ; + + return dst ; } ; +/*============================================================================== + * End Mark Operations. + */ + +/*------------------------------------------------------------------------------ + * Set end_mark at the current put position. + * + * If there was an end_mark before, move it (forward) to the current put_ptr, + * which keeps everything in between in the FIFO. + * + * Set the get_end to the new reality. + */ +extern void +vio_fifo_set_end_mark(vio_fifo vff) +{ + if (vff->set) + { + vio_fifo_sync_get(vff) ; /* in case is currently empty */ + + vff->end_lump = ddl_tail(vff->base) ; + vff->end_end = vff->put_ptr ; + + vff->get_end = (vff->get_lump == vff->end_lump) ? vff->end_end + : vff->get_lump->end ; + } ; + + vff->as_one = false ; /* not as_one with end_mark */ + vff->end_mark = true ; + + VIO_FIFO_DEBUG_VERIFY(vff) ; +} ; + +/*------------------------------------------------------------------------------ + * If there is an end mark, advance it to the put_ptr. + * + * If there was an end_mark before, move it (forward) to the current put_ptr, + * which keeps everything in between in the FIFO. + * + * If there was no end_mark before, do nothing. + * + * Set the get_end to the new reality. + */ +extern void +vio_fifo_step_end_mark(vio_fifo vff) +{ + if (vff->end_mark) + vio_fifo_set_end_mark(vff) ; +} ; + +/*------------------------------------------------------------------------------ + * If there is an end_mark, clear it -- everything between end mark and + * current put_ptr is kept in the FIFO. + * + * Set the get_end to the new reality. + */ +extern void +vio_fifo_clear_end_mark(vio_fifo vff) +{ + if (vff->end_mark) + { + vff->end_mark = false ; + + if (vff->set) + { + vff->end_lump = ddl_tail(vff->base) ; + vff->end_end = NULL ; + + vff->as_one = (vff->get_lump == vff->end_lump) ; + /* since now no end_mark */ + if (!vff->as_one) + vff->get_end = vff->get_lump->end ; + /* would have been end_end */ + + vio_fifo_sync_get(vff) ; /* sets get_end if as_one and + tidies up if now empty (!) */ + } ; + } ; + + VIO_FIFO_DEBUG_VERIFY(vff) ; +} ; + +/*------------------------------------------------------------------------------ + * Move put_ptr back to the end mark, if any, and discard data. + * + * If there is an end_mark, keep it if required. + * + * If there is no end mark, do nothing. + */ +extern void +vio_fifo_back_to_end_mark(vio_fifo vff, bool keep) +{ + if (vff->end_mark) + { + if (vff->set) + { + vio_fifo_release_tail(vff, vff->end_lump) ; + + vff->put_ptr = vff->end_end ; + vff->put_end = vio_fifo_true_lump_size(vff->end_lump) ; + + /* If retaining the existing end_mark, we retain the end_end and + * the current as_one (false). + * + * Otherwise... + */ + if (!keep) + { + vff->end_mark = false ; + vff->end_end = NULL ; + vff->as_one = (vff->get_lump == vff->end_lump) ; + } ; + + vio_fifo_sync_get(vff) ; /* in case now empty */ + + VIO_FIFO_DEBUG_VERIFY(vff) ; + } + else + vff->end_mark = keep ; + } ; +} ; + +/*============================================================================== + * Get data from the FIFO. + */ + /*------------------------------------------------------------------------------ * Get upto 'n' bytes. * * Returns: number of bytes got -- may be zero. */ extern size_t -vio_fifo_get(vio_fifo vf, void* dst, size_t n) +vio_fifo_get_bytes(vio_fifo vff, void* dst, size_t n) { size_t have ; void* dst_in ; - if (!vio_fifo_get_ready(vf)) - return 0 ; /* quit now if nothing there */ + VIO_FIFO_DEBUG_VERIFY(vff) ; dst_in = dst ; - while (n > 0) + while (vio_fifo_sync_get(vff) && (n > 0)) { - have = vf->get_end - vf->get_ptr ; + have = vff->get_end - vff->get_ptr ; if (have > n) have = n ; - memcpy(dst, vf->get_ptr, have) ; - vf->get_ptr += have ; + memcpy(dst, vff->get_ptr, have) ; + vff->get_ptr += have ; dst = (char*)dst + have ; - if (vf->get_ptr >= vf->get_end) /* deal with exhausted lump */ - if (!vio_fifo_get_next_lump(vf)) - break ; /* quit if nothing more to come */ - n -= have ; } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; return (char*)dst - (char*)dst_in ; } ; @@ -752,76 +1269,84 @@ vio_fifo_get(vio_fifo vf, void* dst, size_t n) * Returns: 0x00..0xFF -- byte value (as an int) * -1 => FIFO is empty. */ - -extern int -vio_fifo_get_next_byte(vio_fifo vf) +Private int +vio_fifo_get_next_byte(vio_fifo vff) { - unsigned char u ; - - if (!vio_fifo_get_ready(vf)) - return -1 ; /* quit now if nothing there */ - - u = *vf->get_ptr++ ; - - /* As soon as reach the end want either to discard empty lump, or reset - * the pointers. - */ - if (vf->get_ptr >= vf->get_end) /* deal with exhausted lump */ - vio_fifo_get_next_lump(vf) ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; + if (vio_fifo_sync_get(vff)) + return (uchar)*vff->get_ptr++ ; - return u ; + return -1 ; } ; /*------------------------------------------------------------------------------ - * Get pointer to a lump of bytes. + * Get pointer to as many bytes as are available in the current lump (or next + * lump if nothing available in the current). * * Returns: address of next byte to get, *p_have = number of bytes available * or: NULL => FIFO is empty, *p_have = 0 * - * If the FIFO is not empty, will return pointer to at least one byte. - * - * Returns number of bytes to the end of the current lump. There may be - * further lumps beyond the current one. + * If the FIFO is not empty and not at the end_mark, will return pointer to at + * least one byte. There may be more bytes to get in further lumps. */ extern void* -vio_fifo_get_lump(vio_fifo vf, size_t* p_have) +vio_fifo_get(vio_fifo vff, size_t* p_have) { - if (!vio_fifo_get_ready(vf)) + if (vio_fifo_sync_get(vff)) { - *p_have = 0 ; - return NULL ; + *p_have = (vff->get_end - vff->get_ptr) ; + return vff->get_ptr ; } ; - *p_have = (vf->get_end - vf->get_ptr) ; - return vf->get_ptr ; + *p_have = 0 ; + return NULL ; } ; /*------------------------------------------------------------------------------ - * Advance FIFO to position reached. + * Step FIFO past bytes used. * - * Having done vio_fifo_get_lump(), can take any number of bytes (up to the - * number that "have"), then call this function to advance the pointers. + * Can be called after a vio_fifo_get() or vio_fifo_step_get(). * - * The "here" argument must the the address returned by vio_fifo_get_lump() - * plus the number of bytes taken. + * NB: the "step" argument MUST not exceed the "have" previously returned. */ extern void -vio_fifo_got_upto(vio_fifo vf, void* here) +vio_fifo_step(vio_fifo vff, size_t step) +{ + vff->get_ptr += step ; + vio_fifo_sync_get(vff) ; /* ensure up to date with that */ + + VIO_FIFO_DEBUG_VERIFY(vff) ; +} ; + +/*------------------------------------------------------------------------------ + * Step FIFO past bytes used, the get pointer to as many bytes as are available + * in the current lump (or next lump if nothing available in the current). + * + * Same as vio_fifo_step() followed by vio_fifo_get(). + * + * Can be called after a vio_fifo_get() or vio_fifo_step_get(). + * + * NB: the "step" argument MUST not exceed the "have" previously returned. + */ +extern void* +vio_fifo_step_get(vio_fifo vff, size_t* p_have, size_t step) { - vf->get_ptr = here ; + vff->get_ptr += step ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + if (vio_fifo_sync_get(vff)) + { + *p_have = (vff->get_end - vff->get_ptr) ; + return vff->get_ptr ; + } ; - if (vf->get_ptr >= vf->get_end) - vio_fifo_get_next_lump(vf) ; + *p_have = 0 ; + return NULL ; } ; /*------------------------------------------------------------------------------ * Write contents of FIFO -- assuming non-blocking file * - * Will write all of FIFO, or upto but excluding the last lump. + * Will write all of FIFO up to end mark or put_ptr, or upto but excluding + * the last lump. * * Returns: > 0 => blocked * 0 => all gone (up to last lump if !all) @@ -831,338 +1356,314 @@ vio_fifo_got_upto(vio_fifo vf, void* here) * never return EAGAIN/EWOULDBLOCK, so will return from here "all gone". */ extern int -vio_fifo_write_nb(vio_fifo vf, int fd, bool all) +vio_fifo_write_nb(vio_fifo vff, int fd, bool all) { char* src ; size_t have ; - int done ; - while ((src = vio_fifo_get_lump(vf, &have)) != NULL) + while ((src = vio_fifo_get(vff, &have)) != NULL) { - if (!all && vf->one) - break ; /* don't write last lump */ + int done ; + + if ((vff->get_lump == vff->end_lump) && !all) + break ; /* don't write last lump */ done = write_nb(fd, src, have) ; if (done < 0) - return -1 ; /* failed */ + return -1 ; /* failed */ - vio_fifo_got_upto(vf, src + done) ; + vio_fifo_step(vff, done) ; if (done < (int)have) - return 1 ; /* blocked */ + return 1 ; /* blocked */ } ; - return 0 ; /* all gone */ -} ; - -/*------------------------------------------------------------------------------ - * Get the current rdr_end value. - * - * Unlike get_end, do not have a field for this, but find it each time. - */ -inline static char* -vio_fifo_rdr_end(vio_fifo vf) -{ - if (vf->rdr_lump == ddl_tail(vf->base)) - return vf->put_ptr ; - else - return vf->rdr_lump->end ; + return 0 ; /* all gone */ } ; /*------------------------------------------------------------------------------ - * Get the current rdr position -- sets it up if not currently set. - * - * Returns: address of next byte to get, *p_have = number of bytes available - * or: NULL => FIFO is empty, *p_have = 0 + * Write contents of FIFO -- assuming blocking file * - * If the FIFO is not empty, will return pointer to at least one byte. + * Will write all of FIFO up to end mark or put_ptr. * - * Returns number of bytes to the end of the current lump. There may be - * further lumps beyond the current one. + * Returns: 0 => all gone + * < 0 => failed -- see errno * - * NB: unless returns FIFO is empty, it is a mistake to now do any "get" - * operation other than vio_fifo_step_rdr(), until do vio_fifo_sync_rdr() - * or vio_fifo_drop_rdr. + * Note: will work perfectly well for a non-blocking file -- which should + * never return EAGAIN/EWOULDBLOCK, so will return from here "all gone". */ -extern void* -vio_fifo_get_rdr(vio_fifo vf, size_t* p_have) +extern int +vio_fifo_fwrite(vio_fifo vff, FILE* file) { - if (!vio_fifo_get_ready(vf)) - { - *p_have = 0 ; - return NULL ; - } ; + char* src ; + size_t have ; - if (vf->rdr_lump == NULL) /* set up new rdr if required */ + while ((src = vio_fifo_get(vff, &have)) != NULL) { - vf->rdr_lump = ddl_head(vf->base) ; - vf->rdr_ptr = vf->get_ptr ; - } ; + size_t done ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + done = fwrite(src, have, 1, file) ; - *p_have = vio_fifo_rdr_end(vf) - vf->rdr_ptr ; - return vf->rdr_ptr ; + if (done < 1) + return -1 ; /* failed */ + + vio_fifo_step(vff, have) ; + } ; + + return 0 ; /* all gone */ } ; /*------------------------------------------------------------------------------ - * Step the rdr forward by the given number of bytes. - * - * Returns: address of next byte to get, *p_have = number of bytes available - * or: NULL => FIFO is empty, *p_have = 0 - * - * If the FIFO is not empty, will return pointer to at least one byte. + * Skip get_ptr to the current end -- which may be the current end_mark. * - * Returns number of bytes to the end of the current lump. There may be - * further lumps beyond the current one. - * - * NB: this does not change the get pointers, so all the data being stepped - * over is preserved in the FIFO, until vio_fifo_sync_rdr(). - * - * NB: the step may NOT exceed the last reported "have". + * Does not clear any hold_mark or end_mark. */ -extern void* -vio_fifo_step_rdr(vio_fifo vf, size_t* p_have, size_t step) +extern void +vio_fifo_skip_to_end(vio_fifo vff) { - char* rdr_end ; + vio_fifo_sync_get(vff) ; /* ensure all straight */ - assert(vf->rdr_lump != NULL) ; + /* Setting the get_ptr to the start of the end_lump does the bulk + * of the work -- then just skip to the get_end which that sets. + */ + vio_fifo_set_get_ptr(vff, vff->end_lump->data, vff->end_lump) ; + vff->get_ptr = vff->get_end ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + vio_fifo_sync_get(vff) ; /* crunch */ +} ; - rdr_end = vio_fifo_rdr_end(vf) ; - vf->rdr_ptr += step ; +/*============================================================================== + * Hold Mark Operations. + */ - if (vf->rdr_ptr >= rdr_end) +/*------------------------------------------------------------------------------ + * If there is a hold_mark, clear it -- discard all contents up to the + * current get_ptr. + * + * Set hold_mark at the current get_ptr. + */ +extern void +vio_fifo_set_hold_mark(vio_fifo vff) +{ + if (vff->set) { - assert(vf->rdr_ptr == rdr_end) ; - - if (vf->rdr_lump != ddl_tail(vf->base)) - { - vf->rdr_lump = ddl_next(vf->rdr_lump, list) ; - vf->rdr_ptr = vf->rdr_lump->data ; + if (vff->hold_mark) + vio_fifo_clear_hold_mark(vff) ; /* clear existing mark & sync */ + else + vio_fifo_sync_get(vff) ; /* ensure all straight */ - rdr_end = vio_fifo_rdr_end(vf) ; - } ; + vff->hold_ptr = vff->get_ptr ; } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + vff->hold_mark = true ; - *p_have = (rdr_end - vf->rdr_ptr) ; - return (*p_have > 0) ? vf->rdr_ptr : NULL ; + VIO_FIFO_DEBUG_VERIFY(vff) ; } ; /*------------------------------------------------------------------------------ - * Move FIFO get position to the rdr position, if any. + * If there is a hold_mark, clear it -- discard all contents up to the + * current get_ptr. * - * This clears the rdr position, and removes all data between the current and - * new get positions from the FIFO. + * The get_ptr is synchronised. */ extern void -vio_fifo_sync_rdr(vio_fifo vf) +vio_fifo_clear_hold_mark(vio_fifo vff) { - vio_fifo_lump head ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; - - if (vf->rdr_lump == NULL) - return ; + /* Make sure all is up to date, and in particular that the get_ptr + * is not sitting at the end of a lump when there is a following lump. + */ + vio_fifo_sync_get(vff) ; - while ((head = ddl_head(vf->base)) != vf->rdr_lump) + if ((vff->hold_mark) && (vff->set)) { - vf->get_ptr = vf->get_end ; /* jump to end of lump */ - vio_fifo_lump_release(vf, head) ; - } ; + /* Release everything upto but not including the current get_lump. + * + * This has no effect on the get_ptr etc. so they remain straight. + */ + vio_fifo_release_head(vff, vff->get_lump) ; - vf->get_ptr = vf->rdr_ptr ; /* jump to rdr_ptr */ + vff->hold_ptr = NULL ; + } ; - vf->rdr_lump = NULL ; /* clear the rdr */ - vf->rdr_ptr = NULL ; + vff->hold_mark = false ; - if (vf->one) - { - if (vf->put_ptr == vf->get_ptr) /* reset pointers if FIFO empty */ - vio_fifo_ptr_reset(vf, head) ; - else - vf->get_end = vf->put_ptr ; - } - else - vf->get_end = head->end ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; } ; /*------------------------------------------------------------------------------ - * Drop the rdr position (if any). + * If there is an hold_mark, reset get_ptr *back* to it. * - * This clears the rdr position leaving the get position and FIFO unchanged. + * Leave hold mark set or clear. */ extern void -vio_fifo_drop_rdr(vio_fifo vf) -{ - VIO_FIFO_DEBUG_VERIFY(vf) ; - - vf->rdr_lump = NULL ; - vf->rdr_ptr = NULL ; -} ; - -/*------------------------------------------------------------------------------ - * Move on to next lump to get stuff from. - * - * Advance pointers etc. so that have at least one byte available, unless - * the FIFO is entirely empty. - * - * This should be called if (vf->get_ptr >= vf->get_end) -- asserts that - * these are equal ! - * - * NB: when there is only one block, it may be that get_end is out of date, - * and should be advanced to the current put_ptr position. - * - * That is done here, but may be worth updating get_end before testing - * against get_ptr. - * - * Returns: true <=> at least one byte in FIFO. - * - * NB: if finds that the FIFO is empty, resets the pointers to the start - * of the last lump -- does not release the last lump. - */ -static bool -vio_fifo_get_next_lump(vio_fifo vf) +vio_fifo_back_to_hold_mark(vio_fifo vff, bool mark) { - vio_fifo_lump head ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; - assert(vf->get_ptr == vf->get_end) ; - - head = ddl_head(vf->base) ; /* current lump for get */ - - /* Deal with the simple case of one lump, first. - * - * To save work when putting data into the FIFO (particularly when putting - * a byte at a time) does not keep the vf->get_end up to date (when there is - * only one lump). - * - * If the FIFO is empty, reset pointers and return empty. - */ - if (vf->one) + if (vff->hold_mark) { - assert( (head != NULL) && (head == ddl_tail(vf->base)) ) ; - - if (vf->get_ptr < vf->put_ptr) + if (vff->set) { - /* Had an out of date vf->get_end */ - vf->get_end = vf->put_ptr ; + vio_fifo_set_get_ptr(vff, vff->hold_ptr, ddl_head(vff->base)) ; + /* Set back to hold position */ - return true ; /* FIFO not empty */ - } ; - - assert(vf->get_ptr == vf->put_ptr) ; - - /* FIFO is empty -- reset pointers and exit */ - vio_fifo_ptr_reset(vf, head) ; - - return false ; /* FIFO empty */ - } ; + vff->end_mark = mark ; /* new state */ + if (!mark) + vff->hold_ptr = NULL ; /* clear if required */ - /* Release the head and update pointers - * - * Deals with possibility that nothing has yet been allocated - */ - vio_fifo_lump_release(vf, head) ; + vio_fifo_sync_get(vff) ; /* to be absolutely sure ! */ + } ; - return (vf->get_ptr < vf->get_end) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; + } + else if (mark) + vio_fifo_set_end_mark(vff) ; } ; /*============================================================================== * For debug purposes -- verify the state of the given FIFO */ Private void -vio_fifo_verify(vio_fifo vf) +vio_fifo_verify(vio_fifo vff) { vio_fifo_lump head ; vio_fifo_lump lump ; vio_fifo_lump tail ; - head = ddl_head(vf->base) ; - tail = ddl_tail(vf->base) ; + head = ddl_head(vff->base) ; + tail = ddl_tail(vff->base) ; - /* If nothing allocated, should all be NULL & !vf->one */ + /* If nothing allocated, should all be NULL & !vff->set */ /* If something allocated, tail must not be NULL */ if (head == NULL) { if ( (tail != NULL) - || (vf->put_ptr != NULL) - || (vf->put_end != NULL) - || (vf->get_ptr != NULL) - || (vf->rdr_lump != NULL) - || (vf->rdr_ptr != NULL) - || (vf->one) ) + || (vff->set) + || (vff->as_one) + || (vff->hold_ptr != NULL) + || (vff->get_lump != NULL) + || (vff->get_ptr != NULL) + || (vff->get_end != NULL) + || (vff->end_lump != NULL) + || (vff->end_end != NULL) + || (vff->put_ptr != NULL) + || (vff->put_end != NULL) ) zabort("nothing allocated, but not all NULL") ; return ; } else { if (tail == NULL) - zabort("head pointer not NULL, but tail pointer is") ; + zabort("head not NULL, but tail is") ; } ; - /* Check that all the pointers are within respective lumps + /* Must now be set ! */ + if (!vff->set) + zabort("head not NULL, but set is false") ; + + /* Make sure that the lump pointers all work * - * Know that put_end is always tail->end, but get_end need not be. + * When finished, know that head <= get_lump <= end_lump <= tail. */ - if ( (tail->data > vf->put_ptr) - || (vf->put_ptr > vf->put_end) - || (vf->put_end != tail->end) ) - zabort("put pointers outside the tail lump") ; + lump = head ; + while (lump != vff->get_lump) + { + lump = ddl_next(lump, list) ; + if (lump == NULL) + zabort("ran out of lumps looking for get_lump") ; + } ; - if ( (head->data > vf->get_ptr) - || (vf->get_ptr > vf->get_end) - || (vf->get_end > head->end) ) - zabort("get pointers outside the head lump") ; + while (lump != vff->end_lump) + { + lump = ddl_next(lump, list) ; + if (lump == NULL) + zabort("ran out of lumps looking for end_lump") ; + } ; - /* If head == tail, should be vf->one, etc. */ - if (head == tail) + while (lump != tail) { - if (!vf->one) - zabort("have one lump, but !vf->one") ; + lump = ddl_next(lump, list) ; + if (lump == NULL) + zabort("ran out of lumps looking for tail") ; + } ; - if (vf->get_end > vf->put_ptr) - zabort("get_end is greater than put_ptr when vf->one") ; + /* Check that all the pointers are within respective lumps + * + * Know that put_end is always tail->end, but get_end need not be. + * + * When finished, know that: + * + * - get_lump == head if !hold_mark + * - end_lump == tail if !end_mark + * - that all pointers are within their respective lumps + * - all ptr are <= their respective ends + * - if hold_mark: hold_ptr <= get_ptr or head != get_lump + * - if end_mark: end_end <= put_ptr or tail != end_lump + */ + if (vff->hold_mark) + { + if ( (head->data > vff->hold_ptr) + || (vff->hold_ptr > head->end) ) + zabort("hold pointer outside the head lump") ; + + if ((vff->get_lump == head) && (vff->hold_ptr > vff->get_ptr)) + zabort("hold pointer greater than get pointer") ; } else { - if (vf->one) - zabort("have two or more lumps, but vf->one is true") ; - - if (vf->get_end != head->end) - zabort("get_end is not head->end when !vf->one") ; + if (vff->hold_ptr != NULL) + zabort("no hold_mark, but hold pointer not NULL") ; + if (vff->get_lump != head) + zabort("no hold_mark, but get_lump is not head") ; } ; - /* If have an rdr_lump -- make sure everything else is valid */ - if (vf->rdr_lump != NULL) + if ( (vff->get_lump->data > vff->get_ptr) + || (vff->get_ptr > vff->get_end) + || (vff->get_end > vff->get_lump->end)) + zabort("get pointers outside the get lump") ; + + if (vff->end_mark) { - lump = head ; - while (lump != vf->rdr_lump) - { - if (lump == tail) - zabort("rdr_lump is not part of FIFO") ; - lump = ddl_next(lump, list) ; - } ; + if ( (vff->end_lump->data > vff->end_end) + || (vff->end_end > vff->end_lump->end) ) + zabort("end pointer outside the end lump") ; - if ( (lump->data > vf->rdr_ptr) - || (vf->rdr_ptr > lump->end) ) - zabort("rdr_ptr outside its lump") ; + if ((vff->end_lump == tail) && (vff->end_end > vff->put_ptr)) + zabort("end pointer greater than put pointer") ; + } + else + { + if (vff->end_end != NULL) + zabort("no end_mark, but end end not NULL") ; + if (vff->end_lump != tail) + zabort("no end_mark, but end_lump is not tail") ; + } ; - if ( (lump == head) && (vf->rdr_ptr < vf->get_ptr)) - zabort("rdr_ptr is less than get_ptr in first lump") ; + if ( (tail->data > vff->put_ptr) + || (vff->put_ptr > vff->put_end) + || (vff->put_end != tail->end) ) + zabort("put pointers outside the tail lump") ; - if ( (lump == tail) && (vf->rdr_ptr > vf->put_ptr)) - zabort("rdr_ptr is greater than put_ptr in last lump") ; + /* The as_one state & get_end + */ + if (vff->get_lump != vff->end_lump) + { + if (vff->as_one) + zabort("get_lump != end_lump, but as_one true") ; + if (vff->get_end != vff->get_lump->end) + zabort("get_lump != end_lump, but get_end != get_lump->end") ; } - else + else if (vff->end_mark) { - if (vf->rdr_ptr != NULL) - zabort("rdr_ptr not NULL when rdr_lump is") ; + if (vff->as_one) + zabort("end_mark true, but as_one also true") ; + if (vff->get_end != vff->end_end) + zabort("get_lump == end_lump and end_mark, but get_end != end_end") ; } + else + { + if (!vff->as_one) + zabort("get_lump == end_lump and !end_mark, but as_one not true") ; + if (vff->get_end > vff->put_ptr) + zabort("is as_one, but get_end > put_ptr") ; + } ; } ; diff --git a/lib/vio_fifo.h b/lib/vio_fifo.h index 08af4590..1b7b05d3 100644 --- a/lib/vio_fifo.h +++ b/lib/vio_fifo.h @@ -22,37 +22,35 @@ #ifndef _ZEBRA_VIO_FIFO_H #define _ZEBRA_VIO_FIFO_H -#include "zebra.h" #include "misc.h" +#include "vargs.h" +#include <stdio.h> #include "list_util.h" -#include "zassert.h" - -/* GCC have printf type attribute check. */ -#ifdef __GNUC__ -#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) -#else -#define PRINTF_ATTRIBUTE(a,b) -#endif /* __GNUC__ */ /*============================================================================== * VTY I/O FIFO -- buffering of arbitrary amounts of I/O. */ -#ifdef NDEBUG -# define VIO_FIFO_DEBUG 0 /* NDEBUG override */ +#ifdef VIO_FIFO_DEBUG /* Can be forced from outside */ +# if VIO_FIFO_DEBUG +# define VIO_FIFO_DEBUG 1 /* Force 1 or 0 */ +#else +# define VIO_FIFO_DEBUG 0 +# endif +#else +# ifdef QDEBUG +# define VIO_FIFO_DEBUG 1 /* Follow QDEBUG */ #else -# ifndef VIO_FIFO_DEBUG -# define VIO_FIFO_DEBUG 1 /* Set to 1 to turn on debug checks */ +# define VIO_FIFO_DEBUG 0 # endif #endif +enum { vio_fifo_debug = VIO_FIFO_DEBUG } ; + /*============================================================================== * Data Structures */ -typedef struct vio_fifo vio_fifo_t ; -typedef struct vio_fifo* vio_fifo ; - typedef struct vio_fifo_lump vio_fifo_lump_t ; typedef struct vio_fifo_lump* vio_fifo_lump ; @@ -60,22 +58,46 @@ struct vio_fifo { struct dl_base_pair(vio_fifo_lump) base ; - bool one ; + bool set ; /* have at least one lump */ - char* put_ptr ; - char* put_end ; + bool as_one ; /* get_lump == tail && !end_mark + => get_end may not be up to date */ + bool hold_mark ; /* hold stuff while getting */ + bool end_mark ; /* do not get beyond end */ + + char* hold_ptr ; /* implicitly in the head lump */ + /* used only if "hold_mark" */ + + vio_fifo_lump get_lump ; /* head lump unless "hold_mark" */ char* get_ptr ; char* get_end ; - vio_fifo_lump rdr_lump ; - char* rdr_ptr ; + vio_fifo_lump end_lump ; /* tail lump unless "end_mark" */ + char* end_end ; /* used only if "end_mark" */ + + char* put_ptr ; /* implicitly in the tail lump */ + char* put_end ; size_t size ; vio_fifo_lump spare ; } ; +typedef struct vio_fifo vio_fifo_t[1] ; /* embedded */ +typedef struct vio_fifo* vio_fifo ; + +/* Setting a FIFO object to all zeros is enough to initialise it to an + * empty FIFO (with default lump sizes). + */ +enum +{ + VIO_FIFO_INIT_ALL_ZEROS = true, + VIO_FIFO_DEFAULT_LUMP_SIZE = 4 * 1024 +} ; + +#define VIO_FIFO_INIT_EMPTY { 0 } + struct vio_fifo_lump { struct dl_list_pair(vio_fifo_lump) list ; @@ -89,76 +111,114 @@ struct vio_fifo_lump * Functions */ -extern vio_fifo vio_fifo_init_new(vio_fifo vf, size_t size) ; -extern vio_fifo vio_fifo_reset(vio_fifo vf, int free_structure) ; +extern vio_fifo vio_fifo_init_new(vio_fifo vff, size_t size) ; +extern vio_fifo vio_fifo_reset(vio_fifo vff, free_keep_b free_structure) ; -#define vio_fifo_reset_keep(vf) vio_fifo_reset(vf, 0) -#define vio_fifo_reset_free(vf) vio_fifo_reset(vf, 1) +extern void vio_fifo_clear(vio_fifo vff, bool clear_marks) ; +Inline bool vio_fifo_empty(vio_fifo vff) ; +extern size_t vio_fifo_room(vio_fifo vff) ; -extern void vio_fifo_clear(vio_fifo vf) ; -Inline bool vio_fifo_empty(vio_fifo vf) ; -extern size_t vio_fifo_room(vio_fifo vf) ; +extern void vio_fifo_put_bytes(vio_fifo vff, const char* src, size_t n) ; +Inline void vio_fifo_put_byte(vio_fifo vff, char b) ; -extern void vio_fifo_put(vio_fifo vf, const char* src, size_t n) ; -Inline void vio_fifo_put_byte(vio_fifo vf, char b) ; - -extern int vio_fifo_printf(vio_fifo vf, const char* format, ...) +extern int vio_fifo_printf(vio_fifo vff, const char* format, ...) PRINTF_ATTRIBUTE(2, 3) ; -extern int vio_fifo_vprintf(vio_fifo vf, const char *format, va_list args) ; - -extern size_t vio_fifo_get(vio_fifo vf, void* dst, size_t n) ; -Inline int vio_fifo_get_byte(vio_fifo vf) ; -extern void* vio_fifo_get_lump(vio_fifo vf, size_t* have) ; -extern void vio_fifo_got_upto(vio_fifo vf, void* here) ; - -Inline bool vio_fifo_full_lump(vio_fifo vf) ; -extern int vio_fifo_write_nb(vio_fifo vf, int fd, bool all) ; - -extern void* vio_fifo_get_rdr(vio_fifo vf, size_t* have) ; -extern void* vio_fifo_step_rdr(vio_fifo vf, size_t* have, size_t step) ; -extern void vio_fifo_sync_rdr(vio_fifo vf) ; -extern void vio_fifo_drop_rdr(vio_fifo vf) ; - -Private void vio_fifo_lump_new(vio_fifo vf, size_t size) ; -Private int vio_fifo_get_next_byte(vio_fifo vf) ; +extern int vio_fifo_vprintf(vio_fifo vff, const char *format, va_list args) ; +extern int vio_fifo_read_nb(vio_fifo vff, int fd, size_t request) ; + +extern size_t vio_fifo_get_bytes(vio_fifo vff, void* dst, size_t n) ; +Inline int vio_fifo_get_byte(vio_fifo vff) ; +extern void* vio_fifo_get(vio_fifo vff, size_t* have) ; +extern void vio_fifo_step(vio_fifo vff, size_t step) ; +extern void* vio_fifo_step_get(vio_fifo vff, size_t* p_have, size_t step) ; + +extern vio_fifo vio_fifo_copy(vio_fifo dst, vio_fifo src) ; +extern vio_fifo vio_fifo_copy_tail(vio_fifo dst, vio_fifo src) ; + +Inline bool vio_fifo_full_lump(vio_fifo vff) ; +extern int vio_fifo_write_nb(vio_fifo vff, int fd, bool all) ; +extern int vio_fifo_fwrite(vio_fifo vff, FILE* file) ; + +extern void vio_fifo_skip_to_end(vio_fifo vff) ; +extern void vio_fifo_set_end_mark(vio_fifo vff) ; +extern void vio_fifo_step_end_mark(vio_fifo vff) ; +extern void vio_fifo_clear_end_mark(vio_fifo vff) ; +extern void vio_fifo_back_to_end_mark(vio_fifo vff, bool keep) ; +extern void vio_fifo_set_hold_mark(vio_fifo vff) ; +extern void vio_fifo_clear_hold_mark(vio_fifo vff) ; +extern void vio_fifo_back_to_hold_mark(vio_fifo vff, bool keep) ; /*============================================================================== * Debug -- verification function */ -Private void vio_fifo_verify(vio_fifo vf) ; +Private void vio_fifo_verify(vio_fifo vff) ; #if VIO_FIFO_DEBUG -# define VIO_FIFO_DEBUG_VERIFY(vf) vio_fifo_verify(vf) +# define VIO_FIFO_DEBUG_VERIFY(vff) vio_fifo_verify(vff) #else -# define VIO_FIFO_DEBUG_VERIFY(vf) +# define VIO_FIFO_DEBUG_VERIFY(vff) #endif /*============================================================================== * Inline Functions */ +Private void vio_fifo_lump_new(vio_fifo vff, size_t size) ; +Private int vio_fifo_get_next_byte(vio_fifo vff) ; +Private bool vio_fifo_do_empty(vio_fifo vff) ; + /*------------------------------------------------------------------------------ - * Returns true <=> FIFO is empty + * Returns true <=> FIFO is empty -- at least: get_ptr == end_end (if any) + * or: get_ptr == put_ptr. */ Inline bool -vio_fifo_empty(vio_fifo vf) +vio_fifo_empty(vio_fifo vff) { - return (vf->get_ptr == vf->put_ptr) ; -} + /* if vff is NULL, treat as empty ! + * if !set, then all pointers should be NULL (so get_ptr == put_ptr) + * if !end_mark, then vff->end_end will be NULL (so get_ptr != end_end, + * unless !set) + * Is definitely empty if any of the following is true. + */ + if ((vff == NULL) || (vff->get_ptr == vff->put_ptr) + || (vff->get_ptr == vff->end_end)) + return true ; + + /* If get_ptr < get_end is NOT empty */ + if (vff->get_ptr < vff->get_end) + return false ; + + /* See if can advance get_ptr, and if so whether is then empty. */ + return vio_fifo_do_empty(vff) ; +} ; + +/*------------------------------------------------------------------------------ + * Returns true <=> FIFO is empty beyond end_end (if any). + */ +Inline bool +vio_fifo_empty_tail(vio_fifo vff) +{ + /* if vff is NULL, treat as empty ! + * if !set, then all pointers should be NULL (so end_end == put_ptr) + * if !end_mark, then tail is empty ! + * else tail is empty iff end_end == put_ptr. + */ + return (vff == NULL) || !vff->end_mark || (vff->end_end == vff->put_ptr) ; +} ; /*------------------------------------------------------------------------------ * Put one byte to the FIFO */ Inline void -vio_fifo_put_byte(vio_fifo vf, char b) +vio_fifo_put_byte(vio_fifo vff, char b) { - if (vf->put_ptr >= vf->put_end) - vio_fifo_lump_new(vf, vf->size) ; /* traps put_ptr > put_end */ + if (vff->put_ptr >= vff->put_end) + vio_fifo_lump_new(vff, 0) ; /* traps put_ptr > put_end */ - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; - *vf->put_ptr++ = b ; + *vff->put_ptr++ = b ; } ; /*------------------------------------------------------------------------------ @@ -168,14 +228,12 @@ vio_fifo_put_byte(vio_fifo vf, char b) * -1 => FIFO is empty. */ Inline int -vio_fifo_get_byte(vio_fifo vf) +vio_fifo_get_byte(vio_fifo vff) { - if (vf->get_end <= (vf->get_ptr + 1)) - return vio_fifo_get_next_byte(vf) ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; + if (vff->get_ptr < vff->get_end) + return (uchar)*vff->get_ptr++ ; - return (unsigned char)*vf->get_ptr++ ; + return vio_fifo_get_next_byte(vff) ; } ; /*------------------------------------------------------------------------------ @@ -188,9 +246,9 @@ vio_fifo_get_byte(vio_fifo vf) * (excluding the last lump if it happens to be full) */ Inline bool -vio_fifo_full_lump(vio_fifo vf) +vio_fifo_full_lump(vio_fifo vff) { - return (!vf->one && (vf->put_ptr != NULL)) ; + return (ddl_head(vff->base) != ddl_tail(vff->base)) ; } ; #endif /* _ZEBRA_VIO_FIFO_H */ diff --git a/lib/vio_lines.c b/lib/vio_lines.c index 2ac874d1..a9268fd5 100644 --- a/lib/vio_lines.c +++ b/lib/vio_lines.c @@ -119,7 +119,7 @@ vio_lc_init_new(vio_line_control lc, int width, int height) * Returns: address of vio_line_control (if any) -- NULL if structure released */ extern vio_line_control -vio_lc_reset(vio_line_control lc, bool free_structure) +vio_lc_reset(vio_line_control lc, free_keep_b free_structure) { if (lc != NULL) { diff --git a/lib/vio_lines.h b/lib/vio_lines.h index 7097fe9c..dd5545cc 100644 --- a/lib/vio_lines.h +++ b/lib/vio_lines.h @@ -22,7 +22,6 @@ #ifndef _ZEBRA_VIO_LINES_H #define _ZEBRA_VIO_LINES_H -#include "zebra.h" #include "misc.h" #include "qiovec.h" @@ -35,9 +34,6 @@ * * NB: a completely zero structure is a valid, clear vio_line_control. */ - -typedef struct vio_line_control* vio_line_control ; -typedef struct vio_line_control vio_line_control_t ; struct vio_line_control { unsigned width ; /* console width -- 0 => HUGE */ @@ -54,15 +50,21 @@ struct vio_line_control bool writing ; /* write started, but not completed */ } ; +typedef struct vio_line_control* vio_line_control ; +typedef struct vio_line_control vio_line_control_t[1] ; + +enum +{ + VIO_LINE_CONTROL_INIT_ALL_ZEROS = true +} ; + /*============================================================================== * Functions */ extern vio_line_control vio_lc_init_new(vio_line_control lc, int width, int height) ; -extern vio_line_control vio_lc_reset(vio_line_control lc, bool free_structure) ; - -#define vio_lc_reset_keep(lc) vio_lc_reset(lc, 0) -#define vio_lc_reset_free(lc) vio_lc_reset(lc, 1) +extern vio_line_control vio_lc_reset(vio_line_control lc, + free_keep_b free_structure) ; Inline bool vio_lc_empty(vio_line_control lc) ; extern void vio_lc_clear(vio_line_control lc) ; @@ -1,4 +1,4 @@ -/* VTY top level +/* VTY external interface * Copyright (C) 1997, 98 Kunihiro Ishiguro * * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman @@ -21,36 +21,74 @@ * 02111-1307, USA. */ -#include "zebra.h" +#include "zconfig.h" #include "misc.h" #include "lib/version.h" -#include "vty_io.h" +#include <sys/param.h> +#include <sys/stat.h> +#include <ctype.h> + #include "vty.h" -#include "uty.h" +#include "vty_local.h" +#include "vty_io.h" +#include "vty_command.h" #include "vty_cli.h" +#include "vio_fifo.h" #include "list_util.h" #include "command.h" -#include "command_queue.h" +#include "command_local.h" #include "command_execute.h" +#include "command_parse.h" #include "memory.h" #include "log.h" #include "mqueue.h" +#include "qstring.h" + +/*============================================================================== + * The vty family comprises: + * + * vty -- level visible from outside the vty/command/log family + * and within those families. + * + * vty_common.h -- definitions ... + * vty_local.h + * + * vty_io -- top level of the vio handling + * + * vty_command -- functions called by the command family + * vty_log -- functions called by the log family + * + * vty_cli -- terminal command line handling + * vty_io_term -- terminal (telnet) I/O + * vty_io_vsh -- vtysh I/O + * vty_io_file -- file I/O + * vty_io_shell -- system shell I/O + * + * vty_io_basic -- common low level I/O handling + * encapsulates the differences between qpselect and legacy + * thread/select worlds. + * + * vio_lines -- for terminal: handles width, CRLF, line counting etc. + * vio_fifo -- + * qiovec + * + */ + /*============================================================================== - * Variables etc. (see uty.h) + * Variables etc. (see vty_local.h) */ /* The mutex and related debug counters */ qpt_mutex_t vty_mutex ; -#if VTY_DEBUG - int vty_lock_count = 0 ; -int vty_assert_fail = 0 ; +#if VTY_DEBUG +int vty_assert_fail = 0 ; #endif /* For thread handling -- initialised in vty_init */ @@ -59,7 +97,14 @@ struct thread_master* vty_master = NULL ; /* In the qpthreads world, have nexus for the CLI and one for the Routeing * Engine. Some commands are processed directly in the CLI, most have to * be sent to the Routeing Engine. + * + * If not in the qpthreads world, vty_cli_nexus == vty_cmd_nexus == NULL. + * + * If in the qpthreads world these vty_cli_nexus == vty_cmd_nexus if not + * actually running pthreaded. */ +bool vty_nexus ; /* true <=> in the qpthreads world */ + qpn_nexus vty_cli_nexus = NULL ; qpn_nexus vty_cmd_nexus = NULL ; @@ -72,28 +117,6 @@ vty_io vio_monitors_base = NULL ; /* List of all vty which are on death watch */ vty_io vio_death_watch = NULL ; -/* Vty timeout value -- see "exec timeout" command */ -unsigned long vty_timeout_val = VTY_TIMEOUT_DEFAULT; - -/* Vty access-class command */ -char *vty_accesslist_name = NULL; - -/* Vty access-class for IPv6. */ -char *vty_ipv6_accesslist_name = NULL; - -/* Current directory -- initialised in vty_init() */ -static char *vty_cwd = NULL; - -/* Configure lock -- only one vty may be in CONFIG_NODE or above ! */ -bool vty_config = 0 ; - -/* Login password check override. */ -bool no_password_check = 0; - -/* Restrict unauthenticated logins? */ -const bool restricted_mode_default = 0 ; - bool restricted_mode = 0 ; - /*------------------------------------------------------------------------------ * VTYSH stuff */ @@ -107,9 +130,10 @@ char integrate_default[] = SYSCONFDIR INTEGRATE_DEFAULT_CONFIG ; static void uty_reset (bool final, const char* why) ; static void uty_init_commands (void) ; static void vty_save_cwd (void) ; -static bool vty_terminal (struct vty *); -static bool vty_shell_server (struct vty *); -static bool vty_shell_client (struct vty *); + +//static bool vty_terminal (struct vty *); +//static bool vty_shell_server (struct vty *); +//static bool vty_shell_client (struct vty *); /*------------------------------------------------------------------------------ * Tracking the initialisation state. @@ -143,8 +167,8 @@ static enum vty_init_state vty_init_state ; extern void vty_init (struct thread_master *master_thread) { - VTY_LOCK() ; /* Does nothing if !qpthreads_enabled */ VTY_ASSERT_CLI_THREAD() ; /* True if !qpthreads_enabled */ + VTY_LOCK() ; /* Does nothing if !qpthreads_enabled */ assert(vty_init_state == vty_init_pending) ; @@ -156,7 +180,8 @@ vty_init (struct thread_master *master_thread) vio_monitors_base = NULL ; vio_death_watch = NULL ; - vty_cli_nexus = NULL ; /* not running qnexus-wise */ + vty_nexus = false ; /* not running qnexus-wise */ + vty_cli_nexus = NULL ; vty_cmd_nexus = NULL ; uty_watch_dog_init() ; /* empty watch dog */ @@ -190,6 +215,7 @@ vty_init_r (qpn_nexus cli, qpn_nexus cmd) { assert(vty_init_state == vty_init_1st_stage) ; + vty_nexus = true ; vty_cli_nexus = cli ; vty_cmd_nexus = cmd ; @@ -214,7 +240,7 @@ vty_init_vtysh (void) /*------------------------------------------------------------------------------ * Start the VTY going. * - * This starts the listeners for VTY_TERM and VTY_SHELL_SERV. + * This starts the listeners for VTY_TERMINAL and VTY_SHELL_SERVER. * * Also starts the watch dog. * @@ -222,14 +248,12 @@ vty_init_vtysh (void) * any threads are started -- so is, implicitly, in the CLI thread. * * NB: may be called once and once only. - * - * NB: MUST be in the CLI thread (if any). */ extern void vty_start(const char *addr, unsigned short port, const char *path) { - VTY_LOCK() ; VTY_ASSERT_CLI_THREAD() ; + VTY_LOCK() ; assert( (vty_init_state == vty_init_1st_stage) || (vty_init_state == vty_init_2nd_stage) ) ; @@ -254,10 +278,11 @@ vty_reset() /*------------------------------------------------------------------------------ * Reset all VTY status * - * This is done in response to SIGHUP -- and runs in the CLI thread. + * This is done in response to SIGHUP/SIGINT/SIGTERM -- and runs in the + * CLI thread (if there is one). * - * Half closes all VTY, leaving the death watch to tidy up once all output - * and any command in progress have completed. + * Closes all VTY, leaving the death watch to tidy up once all output and any + * command in progress have completed. * * Closes all listening sockets. * @@ -268,21 +293,24 @@ vty_reset() extern void vty_reset_because(const char* why) { - VTY_LOCK() ; VTY_ASSERT_CLI_THREAD() ; + VTY_LOCK() ; - assert(vty_init_state == vty_init_started) ; + if (vty_init_state != vty_init_reset) + { + assert(vty_init_state == vty_init_started) ; - uty_reset(0, why) ; /* not final ! */ + uty_reset(false, why) ; /* not final ! */ - vty_init_state = vty_init_reset ; + vty_init_state = vty_init_reset ; + } ; VTY_UNLOCK() ; } /*------------------------------------------------------------------------------ * Restart the VTY, following a vty_reset(). * - * This starts the listeners for VTY_TERM and VTY_SHELL_SERV, again. + * This starts the listeners for VTY_TERMINAL and VTY_SHELL_SERVER, again. * * NB: may be called once, and once only, *after* a vty_reset(). * @@ -296,7 +324,7 @@ struct vty_restart_args } ; MQB_ARGS_SIZE_OK(vty_restart_args) ; -static void uty_restart_action(mqueue_block mqb, mqb_flag_t flag) ; +static void vty_restart_action(mqueue_block mqb, mqb_flag_t flag) ; static void uty_restart(const char *addr, unsigned short port, const char *path) ; extern void @@ -308,14 +336,14 @@ vty_restart(const char *addr, unsigned short port, const char *path) * * Otherwise, construct and dispatch message to do a uty_restart. */ - if (!vty_cli_nexus) + if (!vty_nexus) uty_restart(addr, port, path) ; else { mqueue_block mqb ; struct vty_restart_args* args ; - mqb = mqb_init_new(NULL, uty_restart_action, vty_cli_nexus) ; + mqb = mqb_init_new(NULL, vty_restart_action, vty_cli_nexus) ; args = mqb_get_args(mqb) ; if (addr != NULL) @@ -330,7 +358,7 @@ vty_restart(const char *addr, unsigned short port, const char *path) else args->path = NULL ; - mqueue_enqueue(vty_cli_nexus->queue, mqb, 0) ; + mqueue_enqueue(vty_cli_nexus->queue, mqb, mqb_priority) ; } ; VTY_UNLOCK() ; @@ -338,7 +366,7 @@ vty_restart(const char *addr, unsigned short port, const char *path) /* Deal with the uty_restart message */ static void -uty_restart_action(mqueue_block mqb, mqb_flag_t flag) +vty_restart_action(mqueue_block mqb, mqb_flag_t flag) { struct vty_restart_args* args ; args = mqb_get_args(mqb) ; @@ -388,17 +416,18 @@ uty_restart(const char *addr, unsigned short port, const char *path) extern void vty_terminate (void) { + VTY_ASSERT_CLI_THREAD() ; + if ( (vty_init_state == vty_init_pending) || (vty_init_state == vty_init_terminated) ) - return ; /* nothing to do ! */ + return ; /* nothing to do ! */ VTY_LOCK() ; - VTY_ASSERT_CLI_THREAD() ; assert( (vty_init_state > vty_init_pending) && (vty_init_state < vty_init_terminated) ) ; - uty_reset(1, "Shut down") ; /* final reset */ + uty_reset(true, "Shut down") ; /* final reset */ VTY_UNLOCK() ; @@ -408,12 +437,14 @@ vty_terminate (void) } /*------------------------------------------------------------------------------ - * Reset -- final or for SIGHUP + * Reset -- final curtain or for SIGHUP * * Closes listeners. * - * Closes (final) or half closes (SIGHUP) all VTY, and revokes any outstanding - * commands. + * Revokes any outstanding commands and close (SIGHUP) or close_final + * (curtains) all VTY. + * + * * * Resets the vty timeout and access lists. * @@ -440,34 +471,22 @@ uty_reset (bool curtains, const char* why) vio = next ; next = sdl_next(vio, vio_list) ; - if (uty_is_terminal(vio->vty)) - cq_revoke(vio->vty) ; - - if (curtains) - uty_close_final(vio, why) ; - else - uty_close(vio, why) ; + uty_close(vio, curtains, qs_set(NULL, why)) ; } ; - vty_timeout_val = VTY_TIMEOUT_DEFAULT; - - if (vty_accesslist_name) - { - XFREE(MTYPE_VTY, vty_accesslist_name); - vty_accesslist_name = NULL; - } + host.vty_timeout_val = VTY_TIMEOUT_DEFAULT; - if (vty_ipv6_accesslist_name) - { - XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); - vty_ipv6_accesslist_name = NULL; - } + XFREE(MTYPE_HOST, host.vty_accesslist_name) ; + /* sets host.vty_accesslist_name = NULL */ - if (curtains && vty_cwd) - XFREE (MTYPE_TMP, vty_cwd); + XFREE(MTYPE_HOST, host.vty_ipv6_accesslist_name); + /* sets host.vty_ipv6_accesslist_name = NULL */ if (curtains) - uty_watch_dog_stop() ; /* and final death watch run */ + { + XFREE (MTYPE_HOST, host.vty_cwd); + uty_watch_dog_stop() ; /* and final death watch run */ + } ; } ; /*============================================================================== @@ -481,15 +500,17 @@ uty_reset (bool curtains, const char* why) /*------------------------------------------------------------------------------ * Create a new VTY of the given type * - * The type may NOT be: VTY_TERM or VTY_SHELL_SERV + * The type may NOT be: VTY_TERMINAL or VTY_SHELL_SERVER + * + * Appears only to be used by vtysh !! TODO ???? */ -extern struct vty * -vty_open(enum vty_type type) +extern vty +vty_open(vty_type_t type) { struct vty* vty ; VTY_LOCK() ; - vty = uty_new(type, -1) ; /* fails for VTY_TERM or VTY_SHELL_SERV */ + vty = uty_new(type) ; VTY_UNLOCK() ; return vty ; @@ -498,12 +519,16 @@ vty_open(enum vty_type type) /*------------------------------------------------------------------------------ * Close the given VTY */ -extern void -vty_close (struct vty *vty) +extern bool +vty_close(vty vty, bool final, qstring reason) { + bool closed ; + VTY_LOCK() ; - uty_close(vty->vio) ; + closed = uty_close(vty->vio, final, reason) ; VTY_UNLOCK() ; + + return closed ; } /*============================================================================== @@ -517,59 +542,22 @@ vty_close (struct vty *vty) /*------------------------------------------------------------------------------ * VTY output -- cf fprintf ! * - * This is for command output, which may be suppressed - * - * Returns: >= 0 => OK - * < 0 => failed (see errno) - */ -extern int -vty_out (struct vty *vty, const char *format, ...) -{ - int ret ; - va_list args ; - - VTY_LOCK() ; - - if (vty->output_enabled) - { - va_start (args, format) ; - ret = uty_vprintf(vty, format, args) ; - va_end (args) ; - } - else - ret = 0 ; - - VTY_UNLOCK() ; - return ret ; -} - -/*------------------------------------------------------------------------------ - * VTY output error message -- cf fprintf ! - * - * If command has not yet been reflected, do that first. + * This is for command output, which may later be suppressed * * Returns: >= 0 => OK * < 0 => failed (see errno) */ extern int -vty_out_error (struct vty *vty, const char *format, ...) +vty_out(struct vty *vty, const char *format, ...) { int ret ; va_list args ; VTY_LOCK() ; - if (!vty->reflected) - ret = uty_reflect(vty) ; - else - ret = 0 ; - - if (ret == 0) - { - va_start (args, format) ; - ret = uty_vprintf(vty, format, args) ; - va_end (args) ; - } ; + va_start (args, format) ; + ret = vio_fifo_vprintf(vty->vio->obuf, format, args) ; + va_end (args) ; VTY_UNLOCK() ; return ret ; @@ -631,7 +619,7 @@ vty_hello (struct vty *vty) VTY_LOCK() ; #ifdef QDEBUG - uty_out (vty, "%s\n", debug_banner); + vty_out (vty, "%s\n", debug_banner); #endif if (host.motdfile) { @@ -648,15 +636,15 @@ vty_hello (struct vty *vty) for (s = buf + strlen (buf); (s > buf) && isspace ((int)*(s - 1)); s--); *s = '\0'; - uty_output (vty, "%s\n", buf); + vty_out(vty, "%s\n", buf); } fclose (f); } else - uty_output (vty, "MOTD file %s not found\n", host.motdfile); + vty_out(vty, "MOTD file %s not found\n", host.motdfile); } else if (host.motd) - uty_output (vty, "%s", host.motd); + vty_out(vty, "%s", host.motd); VTY_UNLOCK() ; } @@ -665,7 +653,7 @@ vty_hello (struct vty *vty) * Clear the contents of the command output FIFO etc. */ extern void -vty_out_clear(struct vty* vty) +vty_out_clear(vty vty) { VTY_LOCK() ; uty_out_clear(vty->vio) ; @@ -673,429 +661,6 @@ vty_out_clear(struct vty* vty) } ; /*============================================================================== - * Command Execution - */ - -/*------------------------------------------------------------------------------ - * Execute command -- adding to history is not empty or just comment - * - * This is for VTY_TERM type VTY. - * - * Outputs diagnostics if fails to parse. - * - * Returns: command return code - */ -extern enum cmd_return_code -uty_command(struct vty *vty) -{ - enum cmd_return_code ret; - - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - assert(uty_is_terminal(vty)) ; - - /* Parse the command and add to history (if not empty) */ - ret = cmd_parse_command(vty, - cmd_parse_completion + cmd_parse_do + cmd_parse_tree) ; - if (ret != CMD_EMPTY) - uty_cli_hist_add (vty->vio, vty->buf) ; - - /* If parsed and not empty, dispatch */ - if (ret == CMD_SUCCESS) - { -#ifdef CONSUMED_TIME_CHECK - RUSAGE_T before; - RUSAGE_T after; - unsigned long realtime, cputime; - - GETRUSAGE(&before); -#endif /* CONSUMED_TIME_CHECK */ - - ret = cmd_dispatch(vty, cmd_may_queue) ; - -#ifdef CONSUMED_TIME_CHECK - GETRUSAGE(&after); - if ((realtime = thread_consumed_time(&after, &before, &cputime)) > - CONSUMED_TIME_CHECK) - /* Warn about CPU hog that must be fixed. */ - uzlog(NULL, LOG_WARNING, - "SLOW COMMAND: command took %lums (cpu time %lums): %s", - realtime/1000, cputime/1000, vty->buf) ; -#endif /* CONSUMED_TIME_CHECK */ - } ; - - /* Deal with the return code */ - switch (ret) - { - case CMD_ERR_AMBIGUOUS: - vty_out_error(vty, "%% Ambiguous command.\n"); - break; - - case CMD_ERR_NO_MATCH: - vty_out_error(vty, "%% Unknown command.\n") ; - break; - - case CMD_ERR_INCOMPLETE: - vty_out_error(vty, "%% Command incomplete.\n"); - break; - - default: - break ; - } ; - - return ret ; -} ; - -/*------------------------------------------------------------------------------ - * Authentication of vty - * - * During AUTH_NODE and AUTH_ENABLE_NODE, when a command line is dispatched by - * any means this function is called. - * - * Note that if the AUTH_NODE password fails too many times, the terminal is - * closed. - * - * Returns: command return code - */ -extern enum cmd_return_code -uty_auth (struct vty *vty, const char *buf, enum cli_do cli_do) -{ - char *passwd = NULL; - enum node_type next_node = 0; - int fail; - char *crypt (const char *, const char *); - enum cmd_return_code ret ; - - vty_io vio = vty->vio ; - - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - /* What to do ? - * - * In fact, all the exotic command terminators simply discard any input - * and return. - */ - switch (cli_do) - { - case cli_do_nothing: - case cli_do_ctrl_c: - case cli_do_ctrl_z: - return CMD_SUCCESS ; - - case cli_do_command: - break ; - - case cli_do_ctrl_d: - case cli_do_eof: - return uty_cmd_close(vty, "End") ; - - default: - zabort("unknown or invalid cli_do") ; - } ; - - /* Ordinary command dispatch -- see if password is OK. */ - switch (vty->node) - { - case AUTH_NODE: - if (host.encrypt) - passwd = host.password_encrypt; - else - passwd = host.password; - if (host.advanced) - next_node = host.enable ? VIEW_NODE : ENABLE_NODE; - else - next_node = VIEW_NODE; - break; - - case AUTH_ENABLE_NODE: - if (host.encrypt) - passwd = host.enable_encrypt; - else - passwd = host.enable; - next_node = ENABLE_NODE; - break; - - default: - zabort("unknown node type") ; - } - - if (passwd) - { - if (host.encrypt) - fail = strcmp (crypt(buf, passwd), passwd); - else - fail = strcmp (buf, passwd); - } - else - fail = 1; - - ret = CMD_SUCCESS ; - - if (! fail) - { - vio->fail = 0; - vty->node = next_node; /* Success ! */ - } - else - { - vio->fail++; - if (vio->fail >= 3) - { - if (vty->node == AUTH_NODE) - { - ret = uty_cmd_close(vty, "Bad passwords, too many failures!") ; - } - else - { - /* AUTH_ENABLE_NODE */ - vio->fail = 0; - vty_out_error(vty, - "%% Bad enable passwords, too many failures!\n") ; - vty->node = restricted_mode ? RESTRICTED_NODE : VIEW_NODE; - - ret = CMD_WARNING ; - } - } - } - - return ret ; -} ; - -/*------------------------------------------------------------------------------ - * Command line "exit" command -- aka "quit" - * - * Falls back one NODE level. - * - * Returns: command return code - */ -extern enum cmd_return_code -vty_cmd_exit(struct vty* vty) -{ - enum cmd_return_code ret ; - - VTY_LOCK() ; - - ret = CMD_SUCCESS ; - switch (vty->node) - { - case VIEW_NODE: - case ENABLE_NODE: - case RESTRICTED_NODE: - if (uty_shell_client(vty)) - exit (0); - else - ret = uty_cmd_close(vty, "Exit") ; - break; - case CONFIG_NODE: - uty_config_unlock (vty, ENABLE_NODE); - break; - case INTERFACE_NODE: - case ZEBRA_NODE: - case BGP_NODE: - case RIP_NODE: - case RIPNG_NODE: - case OSPF_NODE: - case OSPF6_NODE: - case ISIS_NODE: - case KEYCHAIN_NODE: - case MASC_NODE: - case RMAP_NODE: - case VTY_NODE: - vty->node = CONFIG_NODE ; - break; - case BGP_VPNV4_NODE: - case BGP_IPV4_NODE: - case BGP_IPV4M_NODE: - case BGP_IPV6_NODE: - case BGP_IPV6M_NODE: - vty->node = BGP_NODE ; - break; - case KEYCHAIN_KEY_NODE: - vty->node = KEYCHAIN_NODE ; - break; - default: - break; - } - - VTY_UNLOCK() ; - return ret ; -} - -/*------------------------------------------------------------------------------ - * Command line "end" command - * - * Falls back to ENABLE_NODE. - * - * Returns: command return code - */ -extern enum cmd_return_code -vty_cmd_end(struct vty* vty) -{ - VTY_LOCK() ; - - switch (vty->node) - { - case VIEW_NODE: - case ENABLE_NODE: - case RESTRICTED_NODE: - /* Nothing to do. */ - break; - case CONFIG_NODE: - case INTERFACE_NODE: - case ZEBRA_NODE: - case RIP_NODE: - case RIPNG_NODE: - case BGP_NODE: - case BGP_VPNV4_NODE: - case BGP_IPV4_NODE: - case BGP_IPV4M_NODE: - case BGP_IPV6_NODE: - case BGP_IPV6M_NODE: - case RMAP_NODE: - case OSPF_NODE: - case OSPF6_NODE: - case ISIS_NODE: - case KEYCHAIN_NODE: - case KEYCHAIN_KEY_NODE: - case MASC_NODE: - case VTY_NODE: - uty_config_unlock (vty, ENABLE_NODE); - break; - default: - break; - } - - VTY_UNLOCK() ; - return CMD_SUCCESS ; -} ; - -/*------------------------------------------------------------------------------ - * Result of command is to close the input. - * - * Posts the reason for the close. - * - * Returns: CMD_CLOSE - */ -extern enum cmd_return_code -uty_cmd_close(struct vty *vty, const char* reason) -{ - vty->vio->close_reason = reason ; - return CMD_CLOSE ; -} ; - -/*------------------------------------------------------------------------------ - * Command line ^C action. - * - * Ignores contents of command line (including not adding to history). - * - * Fall back to ENABLE_NODE if in any one of a number of nodes. - * - * Resets the history pointer. - * - * Returns: command return code - */ -extern enum cmd_return_code -uty_stop_input(struct vty *vty) -{ - vty_io vio = vty->vio ; - - VTY_ASSERT_LOCKED() ; - - switch (vty->node) - { - case CONFIG_NODE: - case INTERFACE_NODE: - case ZEBRA_NODE: - case RIP_NODE: - case RIPNG_NODE: - case BGP_NODE: - case RMAP_NODE: - case OSPF_NODE: - case OSPF6_NODE: - case ISIS_NODE: - case KEYCHAIN_NODE: - case KEYCHAIN_KEY_NODE: - case MASC_NODE: - case VTY_NODE: - uty_config_unlock (vty, ENABLE_NODE) ; - break; - default: - /* Unknown node, we have to ignore it. */ - break; - } - - /* Set history pointer to the latest one. */ - vio->hp = vio->hindex; - - return CMD_SUCCESS ; -} ; - -/*------------------------------------------------------------------------------ - * Command ^Z action. - * - * Ignores contents of command line (including not adding to history). - * - * Fall back to ENABLE_NODE if in any one of a number of nodes. - * - * Returns: command return code - */ -extern enum cmd_return_code -uty_end_config (struct vty *vty) -{ - VTY_ASSERT_LOCKED() ; - - switch (vty->node) - { - case VIEW_NODE: - case ENABLE_NODE: - case RESTRICTED_NODE: - /* Nothing to do. */ - break; - case CONFIG_NODE: - case INTERFACE_NODE: - case ZEBRA_NODE: - case RIP_NODE: - case RIPNG_NODE: - case BGP_NODE: - case BGP_VPNV4_NODE: - case BGP_IPV4_NODE: - case BGP_IPV4M_NODE: - case BGP_IPV6_NODE: - case BGP_IPV6M_NODE: - case RMAP_NODE: - case OSPF_NODE: - case OSPF6_NODE: - case ISIS_NODE: - case KEYCHAIN_NODE: - case KEYCHAIN_KEY_NODE: - case MASC_NODE: - case VTY_NODE: - uty_config_unlock (vty, ENABLE_NODE) ; - break; - default: - /* Unknown node, we have to ignore it. */ - break; - } - - return CMD_SUCCESS ; -} - -/*------------------------------------------------------------------------------ - * Command ^D action -- when nothing else on command line. - * - * Same as "exit" command. - * - * Returns: command return code - */ -extern enum cmd_return_code -uty_down_level (struct vty *vty) -{ - return vty_cmd_exit(vty) ; -} ; - -/*============================================================================== * Reading of configuration file * * The reading of the configuration file occurs at two times: @@ -1116,18 +681,18 @@ uty_down_level (struct vty *vty) * run directly in the thread -- no commands are queued. */ -static FILE * vty_use_backup_config (char *fullpath) ; -static void vty_read_file (FILE *confp, struct cmd_element* first_cmd, - bool ignore_warnings) ; +static int vty_use_backup_config (const char *fullpath) ; +static void vty_read_file (int conf_fd, const char* name, + cmd_command first_cmd, bool ignore_warnings) ; /*------------------------------------------------------------------------------ * Read the given configuration file. */ extern void -vty_read_config (char *config_file, - char *config_default) +vty_read_config (const char *config_file, + const char *config_default) { - vty_read_config_first_cmd_special(config_file, config_default, NULL, 1); + vty_read_config_first_cmd_special(config_file, config_default, NULL, true); } /*------------------------------------------------------------------------------ @@ -1148,15 +713,15 @@ vty_read_config (char *config_file, * command. */ extern void -vty_read_config_first_cmd_special(char *config_file, - char *config_default, - struct cmd_element* first_cmd, +vty_read_config_first_cmd_special(const char *config_file, + const char *config_default, + cmd_command first_cmd, bool ignore_warnings) { - char cwd[MAXPATHLEN]; - FILE *confp = NULL; - char *fullpath; - char *tmp = NULL; + char cwd[MAXPATHLEN] ; + int conf_fd ; + const char *fullpath ; + char *tmp = NULL ; /* Deal with VTYSH_ENABLED magic */ if (VTYSH_ENABLED && (config_file == NULL)) @@ -1204,15 +769,15 @@ vty_read_config_first_cmd_special(char *config_file, } ; /* try to open the configuration file */ - confp = fopen (fullpath, "r"); + conf_fd = uty_vfd_file_open(fullpath, vfd_io_read | vfd_io_blocking) ; - if (confp == NULL) + if (conf_fd < 0) { fprintf (stderr, "%s: failed to open configuration file %s: %s\n", __func__, fullpath, errtostr(errno, 0).str); - confp = vty_use_backup_config (fullpath); - if (confp) + conf_fd = vty_use_backup_config (fullpath); + if (conf_fd >= 0) fprintf (stderr, "WARNING: using backup configuration file!\n"); else { @@ -1226,8 +791,7 @@ vty_read_config_first_cmd_special(char *config_file, fprintf(stderr, "Reading config file: %s\n", fullpath); #endif - vty_read_file (confp, first_cmd, ignore_warnings); - fclose (confp); + vty_read_file(conf_fd, fullpath, first_cmd, ignore_warnings); host_config_set (fullpath); @@ -1249,17 +813,17 @@ vty_read_config_first_cmd_special(char *config_file, * - call it "<fullpath>" * - return an open FILE * - * Returns: NULL => no "<fullpath>.sav", or faild doing any of the above - * otherwise, returns FILE* for open file. + * Returns: < 0 => no "<fullpath>.sav", or failed doing any of the above + * >= 0 otherwise, fd file. */ -static FILE * -vty_use_backup_config (char *fullpath) +static int +vty_use_backup_config (const char *fullpath) { - char *tmp_path ; + char *tmp_path ; struct stat buf; - int ret, tmp, sav; - int c; - char buffer[4096] ; + int ret, tmp, sav; + int c, err; + char* buffer ; enum { xl = 32 } ; tmp_path = malloc(strlen(fullpath) + xl) ; @@ -1268,14 +832,17 @@ vty_use_backup_config (char *fullpath) confirm(xl > sizeof(CONF_BACKUP_EXT)) ; sprintf (tmp_path, "%s%s", fullpath, CONF_BACKUP_EXT) ; - sav = -1 ; if (stat (tmp_path, &buf) != -1) - sav = open (tmp_path, O_RDONLY); + sav = uty_vfd_file_open(tmp_path, vfd_io_read | vfd_io_blocking) ; + else + sav = -1 ; if (sav < 0) { - free (tmp_path); - return NULL; + err = errno ; /* making sure */ + free (tmp_path) ; + errno = err ; + return sav ; } ; /* construct a temporary file and copy "<fullpath.sav>" to it. */ @@ -1286,34 +853,60 @@ vty_use_backup_config (char *fullpath) tmp = mkstemp (tmp_path); if (tmp < 0) { + err = errno ; free (tmp_path); close(sav); - return NULL; + errno = err ; + return tmp; } - while((c = read (sav, buffer, sizeof(buffer))) > 0) - write (tmp, buffer, c); + enum { buffer_size = 64 * 1024 } ; + buffer = malloc(buffer_size) ; + + c = 1 ; + while (c > 0) + { + c = read(sav, buffer, buffer_size) ; + if (c > 0) + { + if (write(tmp, buffer, c) < c) + c = -1 ; + } ; + } ; + err = errno ; + + free(buffer) ; + close(sav) ; + close(tmp) ; - close (sav); - close (tmp); + if (c < 0) + { + errno = err ; + return c ; + } ; /* Make sure that have the required file status */ if (chmod(tmp_path, CONFIGFILE_MASK) != 0) { + err = errno ; unlink (tmp_path); free (tmp_path); - return NULL; - } + errno = err ; + return -1 ; + } ; /* Make <fullpath> be a name for the new file just created. */ ret = link (tmp_path, fullpath) ; /* Discard the temporary, now */ + err = errno ; unlink (tmp_path) ; free (tmp_path) ; + errno = err ; /* If link was successful, try to open -- otherwise, failed. */ - return (ret == 0) ? fopen (fullpath, "r") : NULL ; + return (ret == 0) ? uty_vfd_file_open(fullpath, vfd_io_read | vfd_io_blocking) + : -1 ; } ; /*------------------------------------------------------------------------------ @@ -1337,68 +930,49 @@ vty_use_backup_config (char *fullpath) * so all commands are executed directly. */ static void -vty_read_file (FILE *confp, struct cmd_element* first_cmd, bool ignore_warnings) +vty_read_file (int conf_fd, const char* name, + cmd_command first_cmd, bool ignore_warnings) { - enum cmd_return_code ret ; - struct vty *vty ; + cmd_return_code_t ret ; + vty vty ; + vty_io vio ; + vio_vf vf ; + + VTY_LOCK() ; /* Set up configuration file reader VTY -- which buffers all output */ vty = vty_open(VTY_CONFIG_READ); vty->node = CONFIG_NODE; - /* Make sure we have a suitable buffer, and set vty->buf to point at - * it -- same like other command execution. - */ - qs_need(&vty->vio->clx, VTY_BUFSIZ) ; - vty->buf = qs_chars(&vty->vio->clx) ; + vio = vty->vio ; - /* Execute configuration file */ - ret = config_from_file (vty, confp, first_cmd, &vty->vio->clx, - ignore_warnings) ; - - VTY_LOCK() ; - - if (ret != CMD_SUCCESS) - { - fprintf (stderr, "%% while processing line %u of the configuration:\n" - "%s", vty->lineno, vty->buf) ; - - switch (ret) - { - case CMD_WARNING: - fprintf (stderr, "%% Warning...\n"); - break; + vf = uty_vf_new(vio, name, conf_fd, vfd_file, vfd_io_read | vfd_io_blocking) ; - case CMD_ERROR: - fprintf (stderr, "%% Error...\n"); - break; + uty_vin_open( vio, vf, VIN_CONFIG, NULL, NULL, 64 * 1024) ; + uty_vout_open(vio, vf, VOUT_STDERR, NULL, NULL, 4 * 1024) ; - case CMD_ERR_AMBIGUOUS: - fprintf (stderr, "%% Ambiguous command.\n"); - break; + vio->vin->parse_type = cmd_parse_strict | cmd_parse_no_do ; - case CMD_ERR_NO_MATCH: - fprintf (stderr, "%% There is no such command.\n"); - break; + /* When we get here the VTY is set up and all ready to go. */ + uty_cmd_prepare(vio) ; - case CMD_ERR_INCOMPLETE: - fprintf (stderr, "%% Incomplete command.\n"); - break; + VTY_UNLOCK() ; - default: - fprintf(stderr, "%% (unknown cause %d)\n", ret) ; - break ; - } ; + /* Execute configuration file */ + ret = cmd_read_config(vty, first_cmd, ignore_warnings) ; - uty_out_fflush(vty->vio, stderr) ; /* flush command output buffer */ + ret = vty_cmd_loop_exit(vty, ret) ; + if (ret != CMD_SUCCESS) + { + vty_close(vty, true, qs_set(NULL, "Error in configuration file.")) ; exit (1); } ; - uty_close(vty->vio) ; - VTY_UNLOCK() ; + vty_close(vty, false, NULL) ; } ; +#if 0 /*------------------------------------------------------------------------------ * Flush the contents of the command output FIFO to the given file. * @@ -1414,84 +988,59 @@ uty_out_fflush(vty_io vio, FILE* file) fflush(file) ; - while ((src = vio_fifo_get_lump(&vio->cmd_obuf, &have)) != NULL) + src = vio_fifo_get_lump(&vio->cmd_obuf, &have) ; + while (src != NULL) { fwrite(src, 1, have, file) ; - vio_fifo_got_upto(&vio->cmd_obuf, src + have) ; + src = vio_fifo_step_get_lump(&vio->cmd_obuf, &have, have) ; } ; fflush(file) ; } ; - - - +#endif /*============================================================================== - * Configuration node/state handling - * - * At most one VTY may hold the configuration symbol of power at any time. + * For writing configuration file by command, temporarily redirect output to + * an actual file. */ /*------------------------------------------------------------------------------ - * Attempt to gain the configuration symbol of power - * - * If succeeds, set the given node. - * - * Returns: true <=> now own the symbol of power. + * Set the given fd as the VTY_FILE output. */ -extern bool -vty_config_lock (struct vty *vty, enum node_type node) +extern void +vty_open_config_write(vty vty, int fd) { - bool result; + vty_io vio ; + vio_vf vf ; VTY_LOCK() ; - if (!vty_config) - { - vty->config = true ; - vty_config = true ; - } ; + vio = vty->vio ; - result = vty->config; + vf = uty_vf_new(vio, "config write", fd, vfd_file, vfd_io_read) ; - if (result) - vty->node = node ; + uty_vout_open(vio, vf, VOUT_FILE, NULL, NULL, 16 * 1024) ; VTY_UNLOCK() ; - - return result; -} +} ; /*------------------------------------------------------------------------------ - * Give back the configuration symbol of power -- if own it. - * - * Set the given node -- which must be <= MAX_NON_CONFIG_NODE + * Write away any pending stuff, and return the VTY to normal. */ -extern void -vty_config_unlock (struct vty *vty, enum node_type node) +extern int +vty_close_config_write(struct vty* vty) { +// vty_io vio ; +// int err ; + VTY_LOCK() ; - uty_config_unlock(vty, node); - VTY_UNLOCK() ; -} -/*------------------------------------------------------------------------------ - * Give back the configuration symbol of power -- if own it. - * - * Set the given node -- which must be <= MAX_NON_CONFIG_NODE - */ -extern void -uty_config_unlock (struct vty *vty, enum node_type node) -{ - VTY_ASSERT_LOCKED() ; - if (vty_config && vty->config) - { - vty->config = false ; - vty_config = false ; - } + uty_vout_close(vty->vio, false) ; + + VTY_UNLOCK() ; - assert(node <= MAX_NON_CONFIG_NODE) ; - vty->node = node ; +// return err ; + return 0 ; } ; /*============================================================================== @@ -1508,13 +1057,15 @@ DEFUN_CALL (config_who, VTY_LOCK() ; - vio = vio_list_base ; + vio = vio_list_base ; /* once locked */ + while (vio != NULL) /* TODO: show only VTY_TERM ??? */ { - uty_output (vty, "%svty[%d] connected from %s.\n", + vty_out(vty, "%svty[%d] connected from %s.\n", vio->vty->config ? "*" : " ", i, uty_get_name(vio)); vio = sdl_next(vio, vio_list) ; } ; + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -1532,7 +1083,11 @@ DEFUN_CALL (line_vty, return CMD_SUCCESS; } -/* Set time out value. */ +/*------------------------------------------------------------------------------ + * Set time out value. + * + * Affects this and any future VTY_TERMINAL or VTY_SHELL_SERVER type VTY. + */ static int exec_timeout (struct vty *vty, const char *min_str, const char *sec_str) { @@ -1550,10 +1105,9 @@ exec_timeout (struct vty *vty, const char *min_str, const char *sec_str) if (sec_str) timeout += strtol (sec_str, NULL, 10); - vty_timeout_val = timeout; + host.vty_timeout_val = timeout; - if (uty_is_terminal(vty) || uty_is_shell_server(vty)) - uty_sock_set_timer(&vty->vio->sock, timeout) ; + uty_set_timeout(vty->vio, timeout) ; /* update own timeout, if required */ VTY_UNLOCK() ; return CMD_SUCCESS; @@ -1596,10 +1150,9 @@ DEFUN_CALL (vty_access_class, { VTY_LOCK() ; - if (vty_accesslist_name) - XFREE(MTYPE_VTY, vty_accesslist_name); + XFREE(MTYPE_HOST, host.vty_accesslist_name); - vty_accesslist_name = XSTRDUP(MTYPE_VTY, argv[0]); + host.vty_accesslist_name = XSTRDUP(MTYPE_HOST, argv[0]); VTY_UNLOCK() ; return CMD_SUCCESS; @@ -1613,22 +1166,25 @@ DEFUN_CALL (no_vty_access_class, "Filter connections based on an IP access list\n" "IP access list\n") { - int result = CMD_SUCCESS; + cmd_return_code_t ret ; VTY_LOCK() ; - if (! vty_accesslist_name || (argc && strcmp(vty_accesslist_name, argv[0]))) + + if ((argc == 0) || ( (host.vty_accesslist_name != NULL) && + (strcmp(host.vty_accesslist_name, argv[0]) == 0) )) { - vty_out_error(vty, "Access-class is not currently applied to vty\n"); - result = CMD_WARNING; + XFREE(MTYPE_HOST, host.vty_accesslist_name); + /* sets host.vty_accesslist_name = NULL */ + ret = CMD_SUCCESS ; } else { - XFREE(MTYPE_VTY, vty_accesslist_name); - vty_accesslist_name = NULL; - } + vty_out(vty, "Access-class is not currently applied to vty\n") ; + ret = CMD_WARNING; + } ; VTY_UNLOCK() ; - return result; + return ret; } #ifdef HAVE_IPV6 @@ -1641,10 +1197,10 @@ DEFUN_CALL (vty_ipv6_access_class, "IPv6 access list\n") { VTY_LOCK() ; - if (vty_ipv6_accesslist_name) - XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); - vty_ipv6_accesslist_name = XSTRDUP(MTYPE_VTY, argv[0]); + XFREE(MTYPE_HOST, host.vty_ipv6_accesslist_name); + + host.vty_ipv6_accesslist_name = XSTRDUP(MTYPE_HOST, argv[0]); VTY_UNLOCK() ; return CMD_SUCCESS; @@ -1659,25 +1215,25 @@ DEFUN_CALL (no_vty_ipv6_access_class, "Filter connections based on an IP access list\n" "IPv6 access list\n") { - int result = CMD_SUCCESS; + cmd_return_code_t ret ; VTY_LOCK() ; - if (! vty_ipv6_accesslist_name || - (argc && strcmp(vty_ipv6_accesslist_name, argv[0]))) + if ((argc == 0) || ( (host.vty_ipv6_accesslist_name != NULL) && + (strcmp(host.vty_ipv6_accesslist_name, argv[0]) == 0) )) { - vty_out_error(vty, "IPv6 access-class is not currently applied to vty\n") ; - result = CMD_WARNING; + XFREE(MTYPE_HOST, host.vty_ipv6_accesslist_name); + /* sets host.vty_ipv6_accesslist_name = NULL */ + ret = CMD_SUCCESS ; } else { - XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); - - vty_ipv6_accesslist_name = NULL; - } + vty_out(vty, "IPv6 access-class is not currently applied to vty\n") ; + ret = CMD_WARNING; + } ; VTY_UNLOCK() ; - return CMD_SUCCESS; + return ret; } #endif /* HAVE_IPV6 */ @@ -1688,7 +1244,7 @@ DEFUN_CALL (vty_login, "Enable password checking\n") { VTY_LOCK() ; - no_password_check = 0; + host.no_password_check = false ; VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -1700,7 +1256,7 @@ DEFUN_CALL (no_vty_login, "Enable password checking\n") { VTY_LOCK() ; - no_password_check = 1; + host.no_password_check = true ; VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -1712,7 +1268,7 @@ DEFUN_CALL (vty_restricted_mode, "Restrict view commands available in anonymous, unauthenticated vty\n") { VTY_LOCK() ; - restricted_mode = 1; + host.restricted_mode = true; VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -1724,7 +1280,7 @@ DEFUN_CALL (vty_no_restricted_mode, "Enable password checking\n") { VTY_LOCK() ; - restricted_mode = 0; + host.restricted_mode = false; VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -1736,7 +1292,7 @@ DEFUN_CALL (service_advanced_vty, "Enable advanced mode vty interface\n") { VTY_LOCK() ; - host.advanced = 1; + host.advanced = true; VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -1749,7 +1305,7 @@ DEFUN_CALL (no_service_advanced_vty, "Enable advanced mode vty interface\n") { VTY_LOCK() ; - host.advanced = 0; + host.advanced = false; VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -1792,26 +1348,10 @@ DEFUN_CALL (show_history, SHOW_STR "Display the session command history\n") { - int index; - VTY_LOCK() ; - for (index = vty->vio->hindex + 1; index != vty->vio->hindex;) - { - qstring line ; - - if (index == VTY_MAXHIST) - { - index = 0; - continue; - } - - line = vector_get_item(vty->vio->hist, index) ; - if (line != NULL) - uty_output (vty, " %s\n", line->char_body); - - index++; - } + if (vty->type == VTY_TERMINAL) + uty_cli_hist_show(vty->vio->vin->cli) ; VTY_UNLOCK() ; return CMD_SUCCESS; @@ -1827,22 +1367,22 @@ vty_config_write (struct vty *vty) { vty_out (vty, "line vty\n"); - if (vty_accesslist_name) - vty_out (vty, " access-class %s\n", vty_accesslist_name); + if (host.vty_accesslist_name) + vty_out (vty, " access-class %s\n", host.vty_accesslist_name); - if (vty_ipv6_accesslist_name) - vty_out (vty, " ipv6 access-class %s\n", vty_ipv6_accesslist_name); + if (host.vty_ipv6_accesslist_name) + vty_out (vty, " ipv6 access-class %s\n", host.vty_ipv6_accesslist_name); /* exec-timeout */ - if (vty_timeout_val != VTY_TIMEOUT_DEFAULT) - vty_out (vty, " exec-timeout %ld %ld\n", vty_timeout_val / 60, - vty_timeout_val % 60); + if (host.vty_timeout_val != VTY_TIMEOUT_DEFAULT) + vty_out (vty, " exec-timeout %ld %ld\n", host.vty_timeout_val / 60, + host.vty_timeout_val % 60); /* login */ - if (no_password_check) + if (host.no_password_check) vty_out (vty, " no login\n"); - if (restricted_mode != restricted_mode_default) + if (host.restricted_mode != restricted_mode_default) { if (restricted_mode_default) vty_out (vty, " no anonymous restricted\n"); @@ -1879,7 +1419,7 @@ vty_save_cwd (void) getcwd (cwd, MAXPATHLEN); } - vty_cwd = XSTRDUP(MTYPE_TMP, cwd) ; + host.vty_cwd = XSTRDUP(MTYPE_HOST, cwd) ; } ; /*------------------------------------------------------------------------------ @@ -1888,12 +1428,13 @@ vty_save_cwd (void) char * vty_get_cwd () { - return vty_cwd; + return host.vty_cwd; } /*============================================================================== * Access functions for VTY values, where locking is or might be required. */ +#if 0 static bool vty_is_terminal(struct vty *vty) @@ -1925,31 +1466,16 @@ vty_is_shell_client (struct vty *vty) return result; } -enum node_type -vty_get_node(struct vty *vty) -{ - int result; - VTY_LOCK() ; - result = vty->node; - VTY_UNLOCK() ; - return result; -} - -void -vty_set_node(struct vty *vty, enum node_type node) -{ - VTY_LOCK() ; - vty->node = node; - VTY_UNLOCK() ; -} +#endif void vty_set_lines(struct vty *vty, int lines) { VTY_LOCK() ; - vty->vio->lines = lines; - vty->vio->lines_set = 1 ; - uty_set_height(vty->vio) ; + + if (vty->type == VTY_TERMINAL) + uty_cli_set_lines(vty->vio->vin->cli, lines, true) ; + VTY_UNLOCK() ; } @@ -1,4 +1,4 @@ -/* VTY top level +/* VTY external interface -- header * Copyright (C) 1997, 98 Kunihiro Ishiguro * * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman @@ -25,6 +25,10 @@ #define _ZEBRA_VTY_H #include "misc.h" +#include "vargs.h" + +#include "command_common.h" /* NB: *not* command.h */ +#include "vty_common.h" /* struct vty & VTY types */ #include "thread.h" #include "log.h" @@ -35,128 +39,12 @@ #include "list_util.h" #include "vector.h" #include "qstring.h" -#include "node_type.h" - -/*============================================================================== - * The VTYSH uses a unix socket to talk to the daemon. - * - * The ability to respond to a connection from VTYSH appears to be a *compile* - * time option. In the interests of keeping the code up to date, the VTYSH - * option is turned into a testable constant. - */ -#ifdef VTYSH - enum { VTYSH_ENABLED = true } ; -#else - enum { VTYSH_ENABLED = false } ; -#endif - -/*============================================================================== - * VTY Types - */ -enum vty_type /* Command output */ -{ - VTY_TERMINAL, /* a telnet terminal server */ - VTY_SHELL_SERVER, /* a vty_shell server */ - - VTY_SHELL_CLIENT, /* a vty_shell client */ - - VTY_CONFIG_READ, /* configuration file reader */ - - VTY_STDOUT, /* stdout */ - VTY_STDERR, /* stderr */ -} ; -typedef enum vty_type vty_type_t ; - -/*============================================================================== - * - * - * - * - */ - -typedef unsigned long vty_timer_time ; /* Time out time in seconds */ - -enum -{ - VTY_WATCH_DOG_INTERVAL = 5, /* interval between barks */ - - VTY_HALF_CLOSE_TIMEOUT = 120, /* timeout after half_close */ - - VTY_TIMEOUT_DEFAULT = 600, /* terminal timeout value */ -} ; /*============================================================================== - * VTY struct. - */ - -typedef struct vty_io* vty_io ; /* private to vty.c */ - -struct cmd_parsed ; /* in case vty.h expanded before command.h */ - -typedef struct vty* vty ; -struct vty -{ - vty_type_t type ; - - /*---------------------------------------------------------------------- - * The following are the context in which commands are executed. - */ - - /* Node status of this vty - * - * NB: when using qpthreads should lock VTY to access this -- so use - * vty_get_node() and vty_set_node(). - */ - enum node_type node ; - - /* For current referencing point of interface, route-map, access-list - * etc... - * - * NB: this value is private to the command execution, which is assumed - * to all be in the one thread... so no lock required. - */ - void *index ; - - /* For multiple level index treatment such as key chain and key. - * - * NB: this value is private to the command execution, which is assumed - * to all be in the one thread... so no lock required. - */ - void *index_sub ; - - /* In configure mode. */ - bool config; - - /*---------------------------------------------------------------------------- - * The current command line. - * - * These are set when a command is parsed and dispatched. - * - * They are not touched until the command completes -- so may be read while - * the command is being parsed and executed. - */ - const char* buf ; - struct cmd_parsed* parsed ; - unsigned lineno ; - - bool output_enabled ; - bool reflect_enabled ; - bool more_enabled ; - - bool reflected ; - - /*---------------------------------------------------------------------- - * The following are used inside vty.c only. - */ - vty_io vio ; /* one vio object per vty */ -} ; - -/*------------------------------------------------------------------------------ - * Can now include this + * These are definitions and functions for things which are required outside + * the vty and command family. */ -#include "command.h" - /*------------------------------------------------------------------------------ * */ @@ -187,13 +75,6 @@ enum { VTY_MAX_SPACES = 40 } ; #define IS_DIRECTORY_SEP(c) ((c) == DIRECTORY_SEP) #endif -/* GCC have printf type attribute check. */ -#ifdef __GNUC__ -#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) -#else -#define PRINTF_ATTRIBUTE(a,b) -#endif /* __GNUC__ */ - /* Utility macros to convert VTY argument to unsigned long or integer. */ #define VTY_GET_LONG(NAME,V,STR) \ do { \ @@ -267,22 +148,23 @@ extern void vty_reset_because(const char* why) ; extern int vty_out (struct vty *, const char *, ...) PRINTF_ATTRIBUTE(2, 3); extern int vty_out_indent(struct vty *vty, int indent) ; -extern int vty_out_error (struct vty *vty, const char *format, ...) - PRINTF_ATTRIBUTE(2, 3); extern void vty_out_clear(struct vty *vty) ; -extern void vty_read_config (char *config_file, char *config_default); -extern void vty_read_config_first_cmd_special( - char *config_file, char *config_default, - struct cmd_element* first_cmd, bool ignore_warnings) ; + + + + +extern void vty_read_config (const char* config_file, + const char* config_default); +extern void vty_read_config_first_cmd_special(const char* config_file, + const char* config_default, + cmd_command first_cmd, + bool ignore_warnings) ; extern void vty_time_print (struct vty *, int); extern char *vty_get_cwd (void); extern void vty_hello (struct vty *); -extern enum node_type vty_get_node(struct vty *); -extern void vty_set_node(struct vty *, enum node_type); -extern void vty_set_lines(struct vty *, int); #endif /* _ZEBRA_VTY_H */ diff --git a/lib/vty_cli.c b/lib/vty_cli.c index 17351f58..42d78349 100644 --- a/lib/vty_cli.c +++ b/lib/vty_cli.c @@ -20,17 +20,20 @@ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ +#include "misc.h" +#include <ctype.h> -#include <zebra.h> - -#include "keystroke.h" -#include "vty.h" -#include "uty.h" +#include "vty_local.h" +#include "vty_command.h" #include "vty_cli.h" +#include "vty_io_term.h" #include "vty_io.h" #include "vio_lines.h" +#include "vio_fifo.h" -#include "command.h" +#include "keystroke.h" + +#include "command_common.h" #include "command_parse.h" #include "command_execute.h" #include "command_queue.h" @@ -38,86 +41,323 @@ #include "memory.h" /*============================================================================== - * Host name handling + * Construct and destroy CLI object + * + * + * + */ +static bool uty_cli_iac_callback(keystroke_iac_callback_args) ; +static void uty_cli_update_more(vty_cli cli) ; + +/*------------------------------------------------------------------------------ + * Construct and initialise a new CLI object -- never embedded. * - * The host name is used in the command line prompt. The name used is either - * the name set by "hostname" command, or the current machine host name. + * The CLI is started up in the state it is in waiting for a command to + * complete. This means that all start-up messages etc. are handled as the + * output from an implied start command. uty_cli_start() is called when the + * start-up messages etc. are complete. * - * Static variables -- under the VTY_LOCK ! + * Note that the vty_io_term stuff sends out a bunch of telnet commands, which + * will be buffered in the CLI buffer. That will be output, before the + * start-up messages etc. on uty_cli_start(). */ +extern vty_cli +uty_cli_new(vio_vf vf) +{ + vty_cli cli ; + + cli = XCALLOC(MTYPE_VTY_CLI, sizeof(struct vty_cli)) ; + + /* Zeroising has initialised: + * + * vf = NULL -- set below + * + * hist = empty vector (embedded) -- set up when first used. + * hp = 0 -- hp == h_now => in the present ... + * h_now = 0 -- ... see uty_cli_hist_add() + * + * width = 0 -- unknown width ) Telnet window size + * height = 0 -- unknown height ) + * + * lines = 0 -- set below + * lines_set = false -- set below + * + * monitor = false -- not a "monitor" + * monitor_busy = false -- so not busy either + * + * v_timeout = ??? + * + * key_stream = X -- set below + * + * drawn = false + * dirty = false + * + * prompt_len = 0 -- not drawn, in any case + * extra_len = 0 -- not drawn, in any case + * + * echo_suppress = false + * + * prompt_node = NULL_NODE -- so not set ! + * prompt_gen = 0 -- not a generation number + * prompt_for_node = empty qstring (embedded) + * + * password_fail = 0 -- so far, so good. + * + * blocked = false -- see below + * in_progress = false -- see below + * out_active = false + * + * more_wait = false + * more_active = false + * + * out_done = false -- not that it matters + * + * more_enabled = false -- not in "--More--" state + * + * node = NULL_NODE -- set in vty_cli_start() + * to_do = cmd_do_nothing + * + * cl = NULL qstring -- set below + * clx = NULL qstring -- set below + * + * parsed = all zeros -- empty parsed object + * cbuf = empty fifo (embedded) + * + * olc = empty line control (embedded) + */ + confirm(VECTOR_INIT_ALL_ZEROS) ; /* hist */ + confirm(QSTRING_INIT_ALL_ZEROS) ; /* prompt_for_node */ + confirm(NULL_NODE == 0) ; /* prompt_node & node */ + confirm(cmd_do_nothing == 0) ; /* to_do */ + confirm(VIO_FIFO_INIT_ALL_ZEROS) ; /* cbuf */ + confirm(VIO_LINE_CONTROL_INIT_ALL_ZEROS) ; /* olc */ + confirm(CMD_PARSED_INIT_ALL_ZEROS) ; /* parsed */ + + cli->vf = vf ; + + /* Allocate and initialise a keystroke stream TODO: CSI ?? */ + cli->key_stream = keystroke_stream_new('\0', uty_cli_iac_callback, cli) ; -static char* vty_host_name = NULL ; -int vty_host_name_set = 0 ; + /* Set up cl and clx qstrings and the command line output fifo */ + cli->cl = qs_init_new(NULL, 120) ; /* reasonable line length */ + cli->clx = qs_init_new(NULL, 120) ; -static void uty_new_host_name(const char* name) ; + vio_fifo_init_new(cli->cbuf, 500) ; /* just for the CLI stuff */ + + /* Use global 'lines' setting, as default -- but 'lines_set' false. */ + uty_cli_set_lines(cli, host.lines, false) ; + uty_cli_update_more(cli) ; /* and update the olc etc to suit. */ + + /* Ready to be started -- out_active & more_wait are false. */ + cli->blocked = true ; + cli->in_progress = true ; + + return cli ; +} ; /*------------------------------------------------------------------------------ - * Update vty_host_name as per "hostname" or "no hostname" command + * Start the CLI. + * + * The implied start "command" is complete and everything is ready to start + * the CLI. + * + * Note that before releasing any pending output, calls + * + * Returns: write_ready -- so the first event is a write event, to flush + * any output to date. */ extern void -uty_set_host_name(const char* name) +uty_cli_start(vty_cli cli, node_type_t node) +{ + uty_cli_done_command(cli, node) ; /* implied push output */ +} ; + +/*------------------------------------------------------------------------------ + * Close CLI object, if any -- may be called more than once. + * + * Shuts down and discards anything to do with the input. + * + * Revokes anything that can be revoked, which may allow output to proceed + * and the vty to clear itself down. + * + * If in_progress and not final, keeps the cbuf, the olc and clx if in_progress. + * If not final, keeps the cbuf and the olc. + * + * Destroys self if final. + * + * Expects other code to: + * + * - close down command loop and empty the vin/vout stacks. + * + * - turn of any monitor status. + * + * - half close the telnet connection. + */ +extern vty_cli +uty_cli_close(vty_cli cli, bool final) { + if (cli == NULL) + return NULL ; + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; - vty_host_name_set = (name != NULL) ; + /* Revoke any command that is in flight. */ + cq_revoke(cli->vf->vio->vty) ; - if (vty_host_name_set) - uty_new_host_name(name) ; - else - uty_check_host_name() ; + /* Bring as much of the command handling to a stop as possible, and + * turn off "---more--" etc. so that output can proceed. + */ + cli->more_enabled = false ; + cli->lines = 0 ; /* so will not be re-enabled */ + + uty_cli_wipe(cli, 0) ; /* wipe anything the CLI has on screen */ + + cli->more_wait = false ; /* exit more_wait (if was in it) */ + cli->more_active = false ; /* and so cannot be this */ + + cli->out_active = true ; /* if there is any output, it can go */ + + /* Ream out the history. */ + { + qstring line ; + while ((line = vector_ream(cli->hist, keep_it)) != NULL) + qs_reset(line, free_it) ; + } ; + + /* Empty the keystroke handling. */ + cli->key_stream = keystroke_stream_free(cli->key_stream) ; + + /* Can discard active command line if not in_progress. */ + if (!cli->in_progress || final) + cli->clx = qs_reset(cli->clx, free_it) ; + + /* Can discard parsed object. */ + cmd_parsed_reset(cli->parsed, keep_it) ; + + /* If final, free the CLI object. */ + if (final) + { + qs_reset(cli->prompt_for_node, keep_it) ; + cli->cl = qs_reset(cli->cl, free_it) ; + + vio_fifo_reset(cli->cbuf, keep_it) ; /* embedded fifo */ + vio_lc_reset(cli->olc, keep_it) ; /* embedded line control */ + + XFREE(MTYPE_VTY_CLI, cli) ; /* sets cli = NULL */ + } ; + + return cli ; } ; /*------------------------------------------------------------------------------ - * If vty_host_name is set, free it. + * The keystroke iac callback function. + * + * This deals with IAC sequences that should be dealt with as soon as they + * are read -- not stored in the keystroke stream for later processing. */ -extern void -uty_free_host_name(void) +static bool +uty_cli_iac_callback(keystroke_iac_callback_args) { - if (vty_host_name != NULL) - XFREE (MTYPE_HOST, vty_host_name) ; /* sets vty_host_name = NULL */ + return uty_telnet_command(((vty_cli)context)->vf, stroke, true) ; } ; /*------------------------------------------------------------------------------ - * If the host name is not set by command, see if the actual host name has - * changed, and if so change it. + * Set the cli->lines, explicitly or implicitly, and update the "--more--" + * state to suit. * - * This is done periodically in case the actual host name changes ! + * lines < 0 => use whatever telnet has said, or no "--more--" + * lines = 0 => no "--more--" + * lines > 0 => use */ extern void -uty_check_host_name(void) +uty_cli_set_lines(vty_cli cli, int lines, bool explicit) { - struct utsname names ; - - VTY_ASSERT_LOCKED() ; + cli->lines = lines ; + cli->lines_set = explicit ; - if (vty_host_name_set) - return ; /* nothing to do if set by command */ + uty_cli_update_more(cli) ; +} ; - uname (&names) ; +/*------------------------------------------------------------------------------ + * Set the cli->lines, explicitly or implicitly, and update the "--more--" + * state to suit. + * + * lines < 0 => use whatever telnet has said, or no "--more--" + * lines = 0 => no "--more--" + * lines > 0 => + */ +extern void +uty_cli_set_window(vty_cli cli, int width, int height) +{ + cli->width = width ; + cli->height = height ; - if ((vty_host_name == NULL) || (strcmp(vty_host_name, names.nodename) != 0)) - uty_new_host_name(names.nodename) ; + uty_cli_update_more(cli) ; } ; /*------------------------------------------------------------------------------ - * Set new vty_host_name and run along list of VTYs to mark the change. + * Update the "--more--" state, after changing one or more of: + * + * cli->lines + * cli->lines_set + * cli->width + * cli->height + * + * Set the effective height for line control, if required, which may enable the + * "--more--" output handling. + * + * If not, want some limit on the amount of stuff output at a time. + * + * Sets the line control window width and height. + * Sets cli_more_enabled if "--more--" is enabled. */ static void -uty_new_host_name(const char* name) +uty_cli_update_more(vty_cli cli) { - vty_io vio ; - - VTY_ASSERT_LOCKED() ; + bool on ; - uty_free_host_name() ; - vty_host_name = XSTRDUP(MTYPE_HOST, name) ; + on = false ; /* default state */ - vio = vio_list_base ; - while (vio != NULL) + if (cli->vf->vout_state == vf_open) { - vio->cli_prompt_set = 0 ; - vio = sdl_next(vio, vio_list) ; + int height ; + + height = 0 ; /* default state: no "--more--" */ + + if ((cli->width) != 0) + { + /* If window size is known, use lines or given height */ + if (cli->lines >= 0) + height = cli->lines ; + else + { + /* Window height, leaving one line from previous "page" + * and one line for the "--more--" -- if at all possible + */ + height = cli->height - 2 ; + if (height < 1) + height = 1 ; + } ; + } + else + { + /* If window size not known, use lines if that has been set + * explicitly for this terminal. + */ + if (cli->lines_set) + height = cli->lines ; + } ; + + if (height > 0) + on = true ; /* have a defined height */ + else + height = 200 ; /* but no "--more--" */ + + vio_lc_set_window(cli->olc, cli->width, height) ; } ; + + cli->more_enabled = on ; } ; /*============================================================================== @@ -132,31 +372,49 @@ uty_new_host_name(const char* name) * -- setting read or write on, means enabling the file for select/pselect to * consider it for read_ready or write_ready, respectively. * + * All command actions are dispatched via the command queue -- commands are + * not executed in the cli. [For legacy threads the event queue is used. If + * that is too slow, a priority event queue will have to be invented.] + * * State of the CLI: * - * cli_blocked -- the CLI is unable to process any further keystrokes. + * in_progress -- a command has been dispatched and has not yet completed. + * + * The command may be a in_pipe, so there may be many commands + * to be completed before the CLI level command is. + * + * or: the CLI has been closed. + * + * blocked -- is in_progress and a further command is now ready to be + * dispatched. + * + * or: the CLI has been closed. + * + * out_active -- the command output FIFO is being emptied. * - * cmd_in_progress -- a command has been dispatched and has not yet - * completed (may have been queued). + * This is set when a command completes, and cleared when + * everything is written away. * - * cmd_out_enabled -- the command FIFO is may be emptied. + * Note that in this context a command is an individual + * command -- where in_progress may cover any number of + * command if the CLI level command is an in_pipe. * - * This is set when a command completes, and cleared when - * everything is written away. + * more_wait -- is in "--more--" wait state * - * cli_more_wait -- is in "--more--" wait state + * more_active -- is in "--more--" wait state, and the "--more--" prompt + * has not been written away, yet. * * The following are the valid combinations: * - * blkd : cip : o_en : m_wt : - * -----:------:------:------:-------------------------------------------- - * 0 : 0 : 0 : 0 : collecting a new command - * 0 : 1 : 0 : 0 : command dispatched - * 1 : 1 : 0 : 0 : waiting for (queued) command to complete - * 1 : 0 : 1 : 0 : waiting for command output to complete - * 1 : 0 : 0 : 1 : waiting for "--more--" to be written away - * 0 : 0 : 0 : 1 : waiting for "--more--" response - * 1 : 1 : 1 : 0 : waiting for command to complete, after the + * in_p: blkd: o_ac: m_wt: m_ac: + * ----:-----:-----:-----:-----:----------------------------------------- + * 0 : 0 : 0 : 0 : 0 : collecting a new command + * 0 : 0 : 1 : 0 : 0 : waiting for command output to finish + * 1 : 0 : X : 0 : 0 : command dispatched + * 1 : 1 : X : 0 : 0 : waiting for command to complete + * X : X : 0 : 1 : 1 : waiting for "--more--" to be written away + * X : X : 0 : 1 : 0 : waiting for "--more--" response + * 1 : 1 : 1 : 0 : 0 : waiting for command to complete, after the * CLI has been closed * * There are two output FIFOs: @@ -166,9 +424,9 @@ uty_new_host_name(const char* name) * 2. for output generated by commands -- vty_out and friends. * * The CLI FIFO is emptied whenever possible, in preference to the command - * FIFO. The command FIFO is emptied when cmd_out_enabled. While - * cmd_in_progress is also !cmd_out_enabled -- so that all the output from a - * given command is collected together before being sent to the file. + * FIFO. The command FIFO is emptied when out_active. While a command is + * in_progress all its output is collected in the command output buffer, + * to be written away when the command completes. * * Note that only sets read on when the keystroke stream is empty and has not * yet hit eof. The CLI process is driven mostly by write_ready -- which @@ -188,32 +446,20 @@ uty_new_host_name(const char* name) * * This is largely buried in the output handling. * - * While cmd_in_progress is true cmd_out_enabled will be false. When the - * command completes: - * - * * cmd_in_progress is cleared - * - * * cmd_out_enabled is set - * - * * cli_blocked will be set - * - * * the line_control structure is reset - * - * * the output process is kicked off by setting write on + * When a command completes and its output is pushed to written away, + * out_active will be set (and any command line will be wiped). The output + * process is then kicked. * * The output process used the line_control structure to manage the output, and * occasionally enter the trivial "--more--" CLI. This is invisible to the - * main CLI. (See the cli_more_wait flag and its handling.) + * main CLI. (See the more_wait and more_active flags and their handling.) * - * When all the output has completed the CLI will be kicked, which will see - * that the output buffer is now empty, and it can proceed. + * When all the output has completed the out_active flag is cleared and the CLI + * will be kicked. * * It is expected that the output will end with a newline -- so that when the * CLI is kicked, the cursor will be at the start of an empty line. * - * This mechanism means that the command output FIFO only ever contains the - * output from the last command executed. - * * If the user decides to abandon output at the "--more--" prompt, then the * contents of the command output FIFO are discarded. * @@ -265,102 +511,15 @@ uty_new_host_name(const char* name) #define CONTROL(X) ((X) & 0x1F) -static void uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret) ; -static enum vty_readiness uty_cli_standard(vty_io vio) ; -static enum vty_readiness uty_cli_more_wait(vty_io vio) ; -static void uty_cli_draw(vty_io vio) ; -static void uty_cli_draw_this(vty_io vio, enum node_type node) ; -static void uty_cli_wipe(vty_io vio, int len) ; - -static void uty_will_echo (vty_io vio) ; -static void uty_will_suppress_go_ahead (vty_io vio) ; -static void uty_dont_linemode (vty_io vio) ; -static void uty_do_window_size (vty_io vio) ; -static void uty_dont_lflow_ahead (vty_io vio) ; - -/*------------------------------------------------------------------------------ - * Initialise CLI. - * - * It is assumed that the following have been initialised, empty or zero: - * - * cli_prompt_for_node - * cl - * clx - * cli_vbuf - * cli_obuf - * - * cli_drawn - * cli_dirty - * - * cli_prompt_set - * - * cli_blocked - * cmd_in_progress - * cmd_out_enabled - * cli_wait_more - * - * cli_more_enabled - * - * Sets the CLI such that there is apparently a command in progress, so that - * further initialisation (in particular hello messages and the like) is - * treated as a "start up command". - * - * Sends a suitable set of Telnet commands to start the process. - */ -extern void -uty_cli_init(vty_io vio) -{ - assert(vio->vty->type == VTY_TERMINAL) ; - - vio->cmd_in_progress = 1 ; - vio->cli_blocked = 1 ; - - vio->cli_do = cli_do_nothing ; - - /* Setting up terminal. */ - uty_will_echo (vio); - uty_will_suppress_go_ahead (vio); - uty_dont_linemode (vio); - uty_do_window_size (vio); - if (0) - uty_dont_lflow_ahead (vio) ; -} ; - -/*------------------------------------------------------------------------------ - * Start the CLI. - * - * All start-up operations are complete -- so the "command" is now complete. - * - * Returns: write_ready -- so the first event is a write event, to flush - * any output to date. - */ -extern enum vty_readiness -uty_cli_start(vty_io vio) -{ - uty_cli_cmd_complete(vio, CMD_SUCCESS) ; - return write_ready ; -} ; +static enum vty_readiness uty_cli_standard(vty_cli cli) ; +static enum vty_readiness uty_cli_more_wait(vty_cli cli) ; +static void uty_cli_draw(vty_cli cli) ; +static void uty_cli_draw_this(vty_cli cli, node_type_t node) ; /*------------------------------------------------------------------------------ - * Close the CLI + * CLI for VTY_TERMINAL * - * Note that if any command is revoked, then will clear cmd_in_progress and - * set cmd_out_enabled -- so any output can now clear. - */ -extern void -uty_cli_close(vty_io vio) -{ - VTY_ASSERT_LOCKED() ; - assert(vio->vty->type == VTY_TERMINAL) ; - - cq_revoke(vio->vty) ; - - vio->cli_blocked = 1 ; /* don't attempt any more */ - vio->cmd_out_enabled = 1 ; /* allow output to clear */ -} ; - -/*------------------------------------------------------------------------------ - * CLI for VTY_TERM + * Called by uty_term_ready(), so driven by read/write ready. * * Do nothing at all if half closed. * @@ -370,29 +529,33 @@ uty_cli_close(vty_io vio) * NB: on return, requires that an attempt is made to write away anything that * may be ready for that. */ -extern enum vty_readiness -uty_cli(vty_io vio) +extern vty_readiness_t +uty_cli(vty_cli cli) { VTY_ASSERT_LOCKED() ; - assert(vio->vty_type == VTY_TERM) ; - if (vio->half_closed) - return not_ready ; /* Nothing more if half closed */ + if (cli->vf->vin_state != vf_open) + return not_ready ; /* Nothing more from CLI if closing */ /* Standard or "--more--" CLI ? */ - if (vio->cli_more_wait) - return uty_cli_more_wait(vio) ; + if (cli->more_wait) + return uty_cli_more_wait(cli) ; else - return uty_cli_standard(vio) ; + return uty_cli_standard(cli) ; } ; /*============================================================================== * The Standard CLI */ -static enum cli_do uty_cli_process(vty_io vio, enum node_type node) ; -static void uty_cli_response(vty_io vio, enum cli_do cli_do) ; -static bool uty_cli_dispatch(vty_io vio) ; +static cmd_do_t uty_cli_process(vty_cli cli, node_type_t node) ; +static void uty_cli_response(vty_cli cli, cmd_do_t cmd_do) ; + + +static void uty_cli_dispatch(vty_cli cli) ; +static cmd_do_t uty_cli_command(vty_cli cli) ; +static void uty_cli_hist_add (vty_cli cli, qstring clx) ; + /*------------------------------------------------------------------------------ * Standard CLI for VTY_TERM -- if not blocked, runs until: @@ -408,34 +571,21 @@ static bool uty_cli_dispatch(vty_io vio) ; * write_ready if there is anything in the keystroke stream * read_ready otherwise */ -static enum vty_readiness -uty_cli_standard(vty_io vio) +static vty_readiness_t +uty_cli_standard(vty_cli cli) { VTY_ASSERT_LOCKED() ; - assert(vio->vty_type == VTY_TERM) ; - /* cli_blocked is set when is waiting for a command, or its output to - * complete -- unless either of those has happened, is still blocked. + assert(!cli->more_wait) ; /* cannot be here in more_wait state ! */ + + /* cli_blocked is set when is waiting for a command, or some output to + * complete. * * NB: in both these cases, assumes that other forces are at work to * keep things moving. */ - if (vio->cli_blocked) - { - assert(vio->cmd_in_progress || vio->cmd_out_enabled) ; - - if (vio->cmd_in_progress) - { - assert(!vio->cmd_out_enabled) ; - return not_ready ; - } ; - - if (!vio_fifo_empty(&vio->cmd_obuf)) - return not_ready ; - - vio->cli_blocked = 0 ; - vio->cmd_out_enabled = 0 ; - } ; + if (cli->blocked || cli->out_active) + return not_ready ; /* If there is nothing pending, then can run the CLI until there is * something to do, or runs out of input. @@ -444,26 +594,26 @@ uty_cli_standard(vty_io vio) * now completed, which may have wiped the pending command or changed * the required prompt. */ - if (vio->cli_do == cli_do_nothing) - vio->cli_do = uty_cli_process(vio, vio->vty->node) ; + if (cli->to_do == cmd_do_nothing) + cli->to_do = uty_cli_process(cli, cli->node) ; else - uty_cli_draw_this(vio, vio->vty->node) ; + uty_cli_draw_this(cli, cli->node) ; - /* If have something to do, do it. */ - if (vio->cli_do != cli_do_nothing) + /* If have something to do, do it if we can. */ + if (cli->to_do != cmd_do_nothing) { - /* Reflect immediate response */ - uty_cli_response(vio, vio->cli_do) ; + /* Reflect immediate response */ + uty_cli_response(cli, cli->to_do) ; /* If command not already in progress, dispatch this one, which may * set the CLI blocked. * * Otherwise is now blocked until queued command completes. */ - if (!vio->cmd_in_progress) - vio->cli_blocked = uty_cli_dispatch(vio) ; + if (cli->in_progress) + cli->blocked = true ; /* blocked waiting for previous */ else - vio->cli_blocked = 1 ; + uty_cli_dispatch(cli) ; /* can dispatch the latest */ } ; /* Use write_ready as a proxy for read_ready on the keystroke stream. @@ -475,166 +625,164 @@ uty_cli_standard(vty_io vio) * defensive: at worst will generate one unnecessary read_ready/write_ready * event. */ - if (keystroke_stream_empty(vio->key_stream)) + if (keystroke_stream_empty(cli->key_stream)) return read_ready ; else return write_ready ; } ; /*------------------------------------------------------------------------------ - * Dispatch the current vio->cli_do -- queueing it if necessary. + * Dispatch the current cli->to_do -- queueing it if necessary. * * Requires that are NOT blocked and NO command is queued. * * Expects to be on new blank line, and when returns will be on new, blank * line. * - * Returns: true <=> command completed and output is pending - * false => command has been queued and is now in progress + * Generally sets cli->to_do = cmd_do_nothing and clears cli->cl to empty. * - * Generally sets vio->cl_do = cli_do_nothing and clears vio->cl to empty. - * - * Can set vio->cl_do = and vio->cl to be a follow-on command. + * Can set cli->cl_do = and cli->cl to be a follow-on command. */ -static bool -uty_cli_dispatch(vty_io vio) +static void +uty_cli_dispatch(vty_cli cli) { - qstring_t tmp ; - enum cli_do cli_do ; - enum cmd_return_code ret ; + qstring tmp ; + cmd_do_t to_do_now ; - struct vty* vty = vio->vty ; + vty_io vio = cli->vf->vio ; VTY_ASSERT_LOCKED() ; - assert(!vio->cli_blocked && !vio->cmd_in_progress) ; - /* Set vio->clx to the command about to execute. + /* About to dispatch a command, so must be in the following state. */ + assert(!cli->in_progress && !cli->out_active && !cli->blocked) ; + assert(cli->node == vio->vty->node) ; + + /* Set cli->clx to the command about to execute & pick up cli->to_do. * - * Clear vio->cl and vio->cl_do. + * Clear cli->cl and cli->to_do. */ - vio->cmd_in_progress = 1 ; /* => vty->buf is valid */ - vio->cmd_out_enabled = 0 ; /* => collect all output */ - - tmp = vio->clx ; /* swap clx and cl */ - vio->clx = vio->cl ; - vio->cl = tmp ; + tmp = cli->clx ; /* swap clx and cl */ + cli->clx = cli->cl ; + cli->cl = tmp ; - qs_term(&vio->clx) ; /* ensure string is terminated */ - vty->buf = qs_chars(&vio->clx) ; /* terminated command line */ - cli_do = vio->cli_do ; /* current operation */ + to_do_now = cli->to_do ; /* current operation */ - vio->cli_do = cli_do_nothing ; /* clear */ - qs_clear(&vio->cl) ; /* set cl empty (with '\0') */ + cli->to_do = cmd_do_nothing ; /* clear */ + qs_clear(cli->cl) ; /* set cl empty */ - /* Reset the command output FIFO and line_control */ - assert(vio_fifo_empty(&vio->cmd_obuf)) ; - uty_out_clear(vio) ; /* clears FIFO and line control */ + /* Reset the command output FIFO and line_control TODO */ +//uty_out_clear(cli->vio) ; /* clears FIFO and line control */ /* Dispatch command */ - if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE)) - { - /* AUTH_NODE and AUTH_ENABLE_NODE are unique */ - ret = uty_auth(vty, vty->buf, cli_do) ; - } + if ((cli->node == AUTH_NODE) && (to_do_now != cmd_do_nothing)) + to_do_now |= cmd_do_auth ; + else if ((cli->node == AUTH_ENABLE_NODE) && (to_do_now != cmd_do_nothing)) + to_do_now |= cmd_do_auth_enable ; else { /* All other nodes... */ - switch (cli_do) + switch (to_do_now) { - case cli_do_nothing: - ret = CMD_SUCCESS ; + case cmd_do_nothing: + case cmd_do_ctrl_c: + case cmd_do_eof: break ; - case cli_do_command: - ret = uty_command(vty) ; + case cmd_do_command: + to_do_now = uty_cli_command(cli) ; break ; - case cli_do_ctrl_c: - ret = uty_stop_input(vty) ; + case cmd_do_ctrl_d: + zabort("invalid cmd_do_ctrl_d") ; break ; - case cli_do_ctrl_d: - ret = uty_down_level(vty) ; - break ; + case cmd_do_ctrl_z: + to_do_now = uty_cli_command(cli) ; - case cli_do_ctrl_z: - ret = uty_command(vty) ; - if (ret == CMD_QUEUED) - vio->cli_do = cli_do_ctrl_z ; /* defer the ^Z action */ + if (to_do_now == cmd_do_nothing) + to_do_now = cmd_do_ctrl_z ; else - ret = uty_end_config(vty) ; /* do the ^Z now */ - break ; + cli->to_do = cmd_do_ctrl_z ; /* defer the ^Z */ - case cli_do_eof: - ret = uty_cmd_close(vio->vty, "End") ; break ; default: - zabort("unknown cli_do_xxx value") ; + zabort("unknown cmd_do_xxx value") ; } ; } ; - if (ret == CMD_QUEUED) + cli->hp = cli->h_now ; /* in any event, back to the present */ + + if (to_do_now != cmd_do_nothing) { - uty_cli_draw(vio) ; /* draw the prompt */ - return false ; /* command not complete */ + cli->in_progress = true ; + uty_cmd_dispatch(vio, to_do_now, cli->clx) ; } else - { - uty_cli_cmd_complete(vio, ret) ; - return true ; /* command complete */ - } ; + uty_cli_draw(cli) ; } ; /*------------------------------------------------------------------------------ - * Queued command has completed. + * Enqueue command -- adding to history -- if is not empty or just comment * - * Note that sets write on whether there is anything in the output buffer - * or not... write_ready will kick read_ready. + * This is for VTY_TERM type VTY. + * + * Returns: CMD_SUCCESS => empty command line (or just comment) + * CMD_WAITING => enqueued for parse and execute */ -extern void -vty_queued_result(struct vty *vty, enum cmd_return_code ret) +static cmd_do_t +uty_cli_command(vty_cli cli) { - vty_io vio ; + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; - VTY_LOCK() ; + if (cmd_is_empty(qs_els_nn(cli->clx))) + return cmd_do_nothing ; /* easy when nothing to do ! */ - vio = vty->tos ; + /* Add not empty command to history */ + uty_cli_hist_add(cli, cli->clx) ; - if (!vio->closed) - { - uty_cli_wipe(vio, 0) ; /* wipe any partly constructed line */ + return cmd_do_command ; +} ; - /* Do the command completion actions that were deferred because the - * command was queued. - * - * Return of CMD_QUEUED => command was revoked before being executed. - * However interesting that might be... frankly don't care. - */ - uty_cli_cmd_complete(vio, ret) ; +/*------------------------------------------------------------------------------ + * A command has completed, and there is (or may be) some output to now + * write away. + */ +extern void +uty_cli_out_push(vty_cli cli) +{ + VTY_ASSERT_LOCKED() ; - /* Kick the socket -- to write away any outstanding output, and - * re-enter the CLI when that's done. - */ - uty_sock_set_readiness(&vio->sock, write_ready) ; - } - else + if (cli->more_wait) + return ; /* can do nothing */ + + if (vio_fifo_empty(cli->vf->obuf)) { - /* If the VTY is closed, the only reason it still exists is because - * there was cmd_in_progress. - */ - vio->cmd_in_progress = 0 ; /* death watch will apply coup de grace */ + assert(!cli->out_active ) ; + return ; /* need do nothing if is empty */ } ; - VTY_UNLOCK() ; -} + uty_cli_wipe(cli, 0) ; /* wipe any partly constructed line */ + + cli->out_active = true ; /* enable the output */ + + uty_term_set_readiness(cli->vf, write_ready) ; + /* kick the write side into action */ +} ; /*------------------------------------------------------------------------------ + * Queued command has completed. + * + * Note that sets write on whether there is anything in the output buffer + * or not... write_ready will kick read_ready. + + * Command has completed, so: * * * clear cmd_in_progress - * * set cmd_out_enabled -- so any output can now proceed - * * set cli_blocked -- waiting for output to complete + * * set cmd_out_active -- so any output can now proceed + * * set cli_blocked -- waiting for output to complete * * and prepare the line control for output * * If the return is CMD_CLOSE, then also now does the required half close. @@ -650,33 +798,153 @@ vty_queued_result(struct vty *vty, enum cmd_return_code ret) * * It also means that the decision about whether there is anything to output * is left to the output code. + + + */ -static void -uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret) +extern void +uty_cli_done_command(vty_cli cli, node_type_t node) +{ + VTY_ASSERT_LOCKED() ; + + uty_cli_out_push(cli) ; /* just in case */ + + cli->in_progress = false ; /* command complete */ + cli->blocked = false ; /* releases possibly blocked command */ + cli->node = node ; /* and now in this node */ + + /* Reach all the way back, and get the now current node. */ + cli->node = cli->vf->vio->vty->node ; + + uty_term_set_readiness(cli->vf, write_ready) ; + /* kick the write side into action */ +} ; + +/*============================================================================== + * All commands are dispatched to command_queue. + * + * Some commands are VLI specific... and end up back here. + */ + +/*------------------------------------------------------------------------------ + * Authentication of vty + * + * Note that if the AUTH_NODE password fails too many times, the terminal is + * closed. + * + * Returns: CMD_SUCCESS -- OK, one way or another + * CMD_WARNING -- with error message sent to output + * CMD_CLOSE -- too many password failures + */ +extern cmd_return_code_t +uty_cli_auth(vty_cli cli) { + char *crypt (const char *, const char *); + + char* passwd = NULL ; + bool encrypted = false ; + node_type_t next_node = 0 ; + cmd_return_code_t ret ; + vty_io vio ; + cmd_exec exec ; + VTY_ASSERT_LOCKED() ; - assert(vio->cmd_in_progress && !vio->cmd_out_enabled) ; + vio = cli->vf->vio ; + exec = vio->vty->exec ; + + /* Deal with the exotic terminators. */ + switch (exec->to_do & cmd_do_mask) + { + case cmd_do_nothing: + case cmd_do_ctrl_c: + case cmd_do_ctrl_z: + return CMD_SUCCESS ; + + case cmd_do_command: + break ; + + case cmd_do_ctrl_d: + case cmd_do_eof: + return uty_cmd_close(vio, "End") ; + + default: + zabort("unknown or invalid cmd_do") ; + } ; + + /* Ordinary command dispatch -- see if password is OK. + * + * Select the password we need to check against. + */ + if ((exec->to_do & ~cmd_do_mask) == cmd_do_auth) + { + passwd = host.password ; + encrypted = host.password_encrypted ; + + if (host.advanced) + next_node = host.enable ? VIEW_NODE : ENABLE_NODE; + else + next_node = VIEW_NODE; + } + else if ((exec->to_do & ~cmd_do_mask) == cmd_do_auth_enable) + { + passwd = host.enable ; + encrypted = host.enable_encrypted ; + + next_node = ENABLE_NODE; + } + else + zabort("unknown to_do_value") ; + + /* Check against selected password (if any) */ + if (passwd != NULL) + { + char* candidate = qs_make_string(exec->line) ; + + if (encrypted) + candidate = crypt(candidate, passwd) ; + + if (strcmp(candidate, passwd) == 0) + { + cli->password_fail = 0 ; /* forgive any recent failures */ + vio->vty->node = next_node; + + return CMD_SUCCESS ; /* <<< SUCCESS <<<<<<<< */ + } ; + } ; - if (ret == CMD_CLOSE) - uty_close(vio, NULL) ; + /* Password failed -- or none set ! */ + cli->password_fail++ ; - vio->cmd_in_progress = 0 ; /* command complete */ - vio->cmd_out_enabled = 1 ; /* enable the output */ - vio->cli_blocked = 1 ; /* now blocked waiting for output */ + ret = CMD_SUCCESS ; /* OK so far */ - vio->vty->buf = NULL ; /* finished with command line */ + if (cli->password_fail >= 3) + { + if ((exec->to_do & ~cmd_do_mask) == cmd_do_auth) + { + ret = uty_cmd_close(vio, "Bad passwords, too many failures!") ; + } + else + { + /* AUTH_ENABLE_NODE */ + cli->password_fail = 0 ; /* allow further attempts */ + uty_out(vio, "%% Bad enable passwords, too many failures!\n") ; + vio->vty->node = host.restricted_mode ? RESTRICTED_NODE + : VIEW_NODE ; + ret = CMD_WARNING ; + } ; + } ; - uty_cmd_output_start(vio) ; /* reset line control (belt & braces) */ + return ret ; } ; /*============================================================================== * The "--more--" CLI * - * While command output is being cleared from its FIFO, the CLI is cli_blocked. + * While command output is being cleared from its FIFO, the CLI is blocked. * * When the output side signals that "--more--" is required, it sets the - * cli_more_wait flag and clears the cmd_out_enabled flag. + * more_wait flag and clears the out_active flag. * * The first stage of handling "--more--" is to suck the input dry, so that * (as far as is reasonably possible) does not steal a keystroke as the @@ -691,19 +959,20 @@ uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret) * Outputs the new prompt line. */ extern void -uty_cli_enter_more_wait(vty_io vio) +uty_cli_enter_more_wait(vty_cli cli) { VTY_ASSERT_LOCKED() ; - assert(vio->cli_blocked && vio->cmd_out_enabled && !vio->cli_more_wait) ; + assert(cli->out_active && !cli->more_wait) ; - uty_cli_wipe(vio, 0) ; /* make absolutely sure that command line is + uty_cli_wipe(cli, 0) ; /* make absolutely sure that command line is wiped before change the CLI state */ - vio->cmd_out_enabled = 0 ; /* stop output pro tem */ - vio->cli_more_wait = 1 ; /* new state */ + cli->out_active = false ; /* stop output pro tem */ + cli->more_wait = true ; /* new state */ + cli->more_active = true ; /* drawing the "--more--" */ - uty_cli_draw(vio) ; /* draw the "--more--" */ + uty_cli_draw(cli) ; /* draw the "--more--" */ } ; /*------------------------------------------------------------------------------ @@ -717,27 +986,26 @@ uty_cli_enter_more_wait(vty_io vio) * possible that the '--more--' prompt is yet to be completely written away, * so: * - * * assert that is either: !vio->cli_blocked (most of the time it will) - * or: !vio_fifo_empty(&vio->cli_obuf) + * * assert that is either: !cli->blocked (most of the time it will) + * or: !vio_fifo_empty(cli->cbuf) * * * note that can wipe the prompt even though it hasn't been fully * written away yet. (The effect is to append the wipe action to the * cli_obuf !) */ extern void -uty_cli_exit_more_wait(vty_io vio) +uty_cli_exit_more_wait(vty_cli cli) { VTY_ASSERT_LOCKED() ; - assert( (!vio->cli_blocked || !vio_fifo_empty(&vio->cli_obuf)) - && !vio->cmd_out_enabled && vio->cli_more_wait) ; + assert(cli->more_wait) ; - uty_cli_wipe(vio, 0) ; /* wipe the prompt ('--more--') + uty_cli_wipe(cli, 0) ; /* wipe the prompt ('--more--') before changing the CLI state */ - vio->cli_blocked = 1 ; /* back to blocked waiting for output */ - vio->cli_more_wait = 0 ; /* exit more_wait */ - vio->cmd_out_enabled = 1 ; /* re-enable output */ + cli->more_wait = false ; /* exit more_wait */ + cli->more_active = false ; /* tidy */ + cli->out_active = true ; /* re-enable output */ } ; /*------------------------------------------------------------------------------ @@ -754,35 +1022,37 @@ uty_cli_exit_more_wait(vty_io vio) * now_ready -- just left cli_more_wait * not_ready -- otherwise */ -static enum vty_readiness -uty_cli_more_wait(vty_io vio) +static vty_readiness_t +uty_cli_more_wait(vty_cli cli) { struct keystroke steal ; VTY_ASSERT_LOCKED() ; + assert(cli->more_wait) ; /* must be in more_wait state ! */ + /* Deal with the first stage of "--more--" */ - if (vio->cli_blocked) + if (cli->more_active) { int get ; /* If the CLI buffer is not yet empty, then is waiting for the * initial prompt to clear, so nothing to be done here. */ - if (!vio_fifo_empty(&vio->cli_obuf)) - return not_ready ; + if (!vio_fifo_empty(cli->cbuf)) + return write_ready ; - vio->cli_blocked = 0 ; + cli->more_active = false ; /* empty the input buffer into the keystroke stream */ do { - get = uty_read(vio, NULL) ; + get = uty_term_read(cli->vf, NULL) ; } while (get > 0) ; } ; - /* Go through the "--more--" process, unless no longer write_open (!) */ - if (vio->sock.write_open) + /* Go through the "--more--" process, unless closing */ + if (cli->vf->vout_state == vf_open) { /* The read fetches a reasonable lump from the I/O -- so if there * is a complete keystroke available, expect to get it. @@ -791,15 +1061,18 @@ uty_cli_more_wait(vty_io vio) * * If has hit EOF (or error etc), returns knull_eof. */ - uty_read(vio, &steal) ; + uty_term_read(cli->vf, &steal) ; /* If nothing stolen, make sure prompt is drawn and wait for more * input. */ if ((steal.type == ks_null) && (steal.value != knull_eof)) { - if (uty_cli_draw_if_required(vio)) /* "--more--" if req. */ - return write_ready ; + if (uty_cli_draw_if_required(cli)) /* "--more--" if req. */ + { + cli->more_active = true ; /* written "--more--" again */ + return write_ready ; + } else return read_ready ; } ; @@ -812,7 +1085,7 @@ uty_cli_more_wait(vty_io vio) case CONTROL('C'): case 'q': case 'Q': - uty_out_clear(vio) ; + uty_out_clear(cli->vf->vio) ; break; default: /* everything else, thrown away */ @@ -828,7 +1101,7 @@ uty_cli_more_wait(vty_io vio) * Return write_ready to tidy up the screen and, unless cleared, write * some more. */ - uty_cli_exit_more_wait(vio) ; + uty_cli_exit_more_wait(cli) ; return now_ready ; } ; @@ -838,18 +1111,17 @@ uty_cli_more_wait(vty_io vio) * * This is buffered separately from the general (command) VTY output above. * - * Has a dedicated buffer in the struct vty, which is flushed regularly during - * command processing. + * Has a dedicated buffer in the struct vty_cli, which is flushed regularly + * during command processing. * * It is expected that can flush straight to the file, since this is running at * CLI speed. However, if the CLI is being driven by something other than a * keyboard, or "monitor" output has filled the buffers, then may need to * have intermediate buffering. * - * No actual I/O takes place here-- all "output" is to vio->cli_obuf - * and/or vio->cli_ex_obuf + * No actual I/O takes place here-- all "output" is to cli->cbuf * - * The "cli_echo" functions discard the output if vio->cli_echo_suppress != 0. + * The "cli_echo" functions discard the output if cli->echo_suppress. * This is used while passwords are entered and to allow command line changes * to be made while the line is not visible. */ @@ -857,8 +1129,9 @@ uty_cli_more_wait(vty_io vio) enum { cli_rep_count = 32 } ; typedef const char cli_rep_char[cli_rep_count] ; +typedef const char cli_rep[] ; -static const char telnet_backspaces[] = +static cli_rep telnet_backspaces = { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, @@ -866,35 +1139,39 @@ static const char telnet_backspaces[] = } ; CONFIRM(sizeof(telnet_backspaces) == sizeof(cli_rep_char)) ; -static const char telnet_spaces[] = - { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', - ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', - ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', - ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' - } ; -CONFIRM(sizeof(telnet_spaces) == sizeof(cli_rep_char)) ; +/* 12345678901234567890123456789012 */ +static cli_rep telnet_spaces = " " ; +static cli_rep telnet_dots = "................................" ; + +CONFIRM(sizeof(telnet_spaces) == (sizeof(cli_rep_char) + 1)) ; +CONFIRM(sizeof(telnet_dots) == (sizeof(cli_rep_char) + 1)) ; static const char* telnet_newline = "\r\n" ; -static void uty_cli_write(vty_io vio, const char *this, int len) ; -static void uty_cli_write_n(vty_io vio, cli_rep_char chars, int n) ; +static void uty_cli_write_n(vty_cli cli, cli_rep_char chars, int n) ; /*------------------------------------------------------------------------------ * CLI VTY output -- cf fprintf() */ -static void -uty_cli_out(vty_io vio, const char *format, ...) +extern void +uty_cli_out(vty_cli cli, const char *format, ...) { + va_list args ; + VTY_ASSERT_LOCKED() ; - if (vio->sock.write_open) - { - va_list args ; + va_start (args, format); + vio_fifo_vprintf(cli->cbuf, format, args) ; + va_end(args); +} ; - va_start (args, format); - vio_fifo_vprintf(&vio->cli_obuf, format, args) ; - va_end(args); - } ; +/*------------------------------------------------------------------------------ + * Completely empty the cli command buffer + */ +extern void +uty_cli_out_clear(vty_cli cli) +{ + vio_fifo_clear(cli->cbuf, true) ; } ; /*------------------------------------------------------------------------------ @@ -903,14 +1180,12 @@ uty_cli_out(vty_io vio, const char *format, ...) * Do nothing if echo suppressed (eg in AUTH_NODE) or not write_open */ static void -uty_cli_echo(vty_io vio, const char *this, size_t len) +uty_cli_echo(vty_cli cli, const char *this, size_t len) { VTY_ASSERT_LOCKED() ; - if (vio->cli_echo_suppress || !vio->sock.write_open) - return ; - - uty_cli_write(vio, this, len) ; + if (!cli->echo_suppress) + uty_cli_write(cli, this, len) ; } /*------------------------------------------------------------------------------ @@ -919,33 +1194,30 @@ uty_cli_echo(vty_io vio, const char *this, size_t len) * Do nothing if echo suppressed (eg in AUTH_NODE) */ static void -uty_cli_echo_n(vty_io vio, cli_rep_char chars, int n) +uty_cli_echo_n(vty_cli cli, cli_rep_char chars, int n) { VTY_ASSERT_LOCKED() ; - if (vio->cli_echo_suppress || !vio->sock.write_open) - return ; - - uty_cli_write_n(vio, chars, n) ; + if (!cli->echo_suppress) + uty_cli_write_n(cli, chars, n) ; } /*------------------------------------------------------------------------------ * CLI VTY output -- cf write() */ -inline static void -uty_cli_write(vty_io vio, const char *this, int len) +extern void +uty_cli_write(vty_cli cli, const char *this, int len) { VTY_ASSERT_LOCKED() ; - if (vio->sock.write_open) - vio_fifo_put(&vio->cli_obuf, this, len) ; + vio_fifo_put_bytes(cli->cbuf, this, len) ; } ; /*------------------------------------------------------------------------------ * CLI VTY output -- write 'n' characters using a cli_rep_char string */ static void -uty_cli_write_n(vty_io vio, cli_rep_char chars, int n) +uty_cli_write_n(vty_cli cli, cli_rep_char chars, int n) { int len ; @@ -954,7 +1226,7 @@ uty_cli_write_n(vty_io vio, cli_rep_char chars, int n) { if (n < len) len = n ; - uty_cli_write(vio, chars, len) ; + uty_cli_write(cli, chars, len) ; n -= len ; } ; } ; @@ -965,13 +1237,13 @@ uty_cli_write_n(vty_io vio, cli_rep_char chars, int n) * Returns length of string. */ static int -uty_cli_write_s(vty_io vio, const char *str) +uty_cli_write_s(vty_cli cli, const char *str) { int len ; len = strlen(str) ; if (len != 0) - uty_cli_write(vio, str, len) ; + uty_cli_write(cli, str, len) ; return len ; } ; @@ -985,12 +1257,12 @@ uty_cli_write_s(vty_io vio, const char *str) * * Clears the cli_drawn and the cli_dirty flags. */ -static void -uty_cli_out_newline(vty_io vio) +extern void +uty_cli_out_newline(vty_cli cli) { - uty_cli_write(vio, telnet_newline, 2) ; - vio->cli_drawn = 0 ; - vio->cli_dirty = 0 ; + uty_cli_write(cli, telnet_newline, 2) ; + cli->drawn = false ; + cli->dirty = false ; } ; /*------------------------------------------------------------------------------ @@ -1000,23 +1272,23 @@ uty_cli_out_newline(vty_io vio) * 'n' > 0, wipes characters forwards, leaving cursor where it is */ static void -uty_cli_out_wipe_n(vty_io vio, int n) +uty_cli_out_wipe_n(vty_cli cli, int n) { if (n < 0) { n = abs(n) ; - uty_cli_write_n(vio, telnet_backspaces, n); + uty_cli_write_n(cli, telnet_backspaces, n); } ; if (n > 0) { - uty_cli_write_n(vio, telnet_spaces, n) ; - uty_cli_write_n(vio, telnet_backspaces, n) ; + uty_cli_write_n(cli, telnet_spaces, n) ; + uty_cli_write_n(cli, telnet_backspaces, n) ; } ; } ; /*------------------------------------------------------------------------------ - * Send response to the given cli_do + * Send response to the given cmd_do * * If no command is in progress, then will send newline to signal that the * command is about to be dispatched. @@ -1024,113 +1296,126 @@ uty_cli_out_wipe_n(vty_io vio, int n) * If command is in progress, then leaves cursor on '^' to signal that is now * waiting for previous command to complete. */ -static const char* cli_response [2][cli_do_count] = +static const char* cli_response [2][cmd_do_count] = { { /* when not waiting for previous command to complete */ - [cli_do_command] = "", - [cli_do_ctrl_c] = "^C", - [cli_do_ctrl_d] = "^D", - [cli_do_ctrl_z] = "^Z", - [cli_do_eof] = "^*" + [cmd_do_command] = "", + [cmd_do_ctrl_c] = "^C", + [cmd_do_ctrl_d] = "^D", + [cmd_do_ctrl_z] = "^Z", + [cmd_do_eof] = "^*" }, { /* when waiting for a previous command to complete */ - [cli_do_command] = "^", - [cli_do_ctrl_c] = "^C", - [cli_do_ctrl_d] = "^D", - [cli_do_ctrl_z] = "^Z", - [cli_do_eof] = "^*" + [cmd_do_command] = "^", + [cmd_do_ctrl_c] = "^C", + [cmd_do_ctrl_d] = "^D", + [cmd_do_ctrl_z] = "^Z", + [cmd_do_eof] = "^*" } } ; static void -uty_cli_response(vty_io vio, enum cli_do cli_do) +uty_cli_response(vty_cli cli, cmd_do_t to_do) { const char* str ; int len ; - if ((cli_do == cli_do_nothing) || (vio->half_closed)) + if ((to_do == cmd_do_nothing) || (cli->vf->vin_state != vf_open)) return ; - str = (cli_do < cli_do_count) - ? cli_response[vio->cmd_in_progress ? 1 : 0][cli_do] : NULL ; + str = (to_do < cmd_do_count) + ? cli_response[cli->in_progress ? 1 : 0][to_do] : NULL ; assert(str != NULL) ; - len = uty_cli_write_s(vio, str) ; + len = uty_cli_write_s(cli, str) ; - if (vio->cmd_in_progress) + if (cli->in_progress) { - vio->cli_extra_len = len ; - uty_cli_write_n(vio, telnet_backspaces, len) ; + cli->extra_len = len ; + uty_cli_write_n(cli, telnet_backspaces, len) ; } else { - uty_cli_out_newline(vio) ; + uty_cli_out_newline(cli) ; } ; } ; /*------------------------------------------------------------------------------ * Send various messages with trailing newline. */ - +#if 0 static void -uty_cli_out_CMD_ERR_AMBIGUOUS(vty_io vio) +uty_cli_out_CMD_ERR_AMBIGUOUS(vty_cli cli) { - uty_cli_write_s(vio, "% " MSG_CMD_ERR_AMBIGUOUS ".") ; - uty_cli_out_newline(vio) ; + uty_cli_write_s(cli, "% " MSG_CMD_ERR_AMBIGUOUS ".") ; + uty_cli_out_newline(cli) ; } ; static void -uty_cli_out_CMD_ERR_NO_MATCH(vty_io vio) +uty_cli_out_CMD_ERR_NO_MATCH(vty_cli cli) { - uty_cli_write_s(vio, "% " MSG_CMD_ERR_NO_MATCH ".") ; - uty_cli_out_newline(vio) ; + uty_cli_write_s(cli, "% " MSG_CMD_ERR_NO_MATCH ".") ; + uty_cli_out_newline(cli) ; } ; - +#endif /*============================================================================== * Command line draw and wipe */ /*------------------------------------------------------------------------------ + * Current prompt length + */ +extern ulen +uty_cli_prompt_len(vty_cli cli) +{ + return cli->prompt_len ; +} ; + +/*------------------------------------------------------------------------------ * Wipe the current console line -- if any. + * + * If it is known that some characters will immediately be written, then + * passing the number of them as "len" will reduce the number of characters + * that have to be sent. */ -static void -uty_cli_wipe(vty_io vio, int len) +extern void +uty_cli_wipe(vty_cli cli, int len) { int a ; int b ; - if (!vio->cli_drawn) + if (!cli->drawn) return ; /* quit if already wiped */ - assert(vio->cl.cp <= vio->cl.len) ; + assert(qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) ; /* Establish how much ahead and how much behind the cursor */ - a = vio->cli_extra_len ; - b = vio->cli_prompt_len ; + a = cli->extra_len ; + b = cli->prompt_len ; - if (!vio->cli_echo_suppress && !vio->cli_more_wait) + if (!cli->echo_suppress && !cli->more_wait) { - a += vio->cl.len - vio->cl.cp ; - b += vio->cl.cp ; + a += qs_len_nn(cli->cl) - qs_cp_nn(cli->cl) ; + b += qs_cp_nn(cli->cl) ; } ; /* Wipe anything ahead of the current position and ahead of new len */ if ((a + b) > len) - uty_cli_out_wipe_n(vio, +a) ; + uty_cli_out_wipe_n(cli, +a) ; /* Wipe anything behind current position, but ahead of new len */ if (b > len) { - uty_cli_out_wipe_n(vio, -(b - len)) ; + uty_cli_out_wipe_n(cli, -(b - len)) ; b = len ; /* moved the cursor back */ } ; /* Back to the beginning of the line */ - uty_cli_write_n(vio, telnet_backspaces, b) ; + uty_cli_write_n(cli, telnet_backspaces, b) ; /* Nothing there any more */ - vio->cli_drawn = 0 ; - vio->cli_dirty = 0 ; + cli->drawn = false ; + cli->dirty = false ; } ; /*------------------------------------------------------------------------------ @@ -1140,12 +1425,12 @@ uty_cli_wipe(vty_io vio, int len) * See uty_cli_draw(). */ extern bool -uty_cli_draw_if_required(vty_io vio) +uty_cli_draw_if_required(vty_cli cli) { - if (vio->cli_drawn) + if (cli->drawn) return false ; - uty_cli_draw(vio) ; + uty_cli_draw(cli) ; return true ; } ; @@ -1156,9 +1441,9 @@ uty_cli_draw_if_required(vty_io vio) * See uty_cli_draw_this() */ static void -uty_cli_draw(vty_io vio) +uty_cli_draw(vty_cli cli) { - uty_cli_draw_this(vio, vio->vty->node) ; + uty_cli_draw_this(cli, cli->node) ; } ; /*------------------------------------------------------------------------------ @@ -1171,7 +1456,7 @@ uty_cli_draw(vty_io vio) * * Draws prompt according to the given 'node', except: * - * * if is half_closed, draw nothing -- wipes the current line + * * if is closing, draw nothing -- wipes the current line * * * if is cli_more_wait, draw the "--more--" prompt * @@ -1187,34 +1472,34 @@ uty_cli_draw(vty_io vio) * cli_echo_suppress = (AUTH_NODE or AUTH_ENABLE_NODE) */ static void -uty_cli_draw_this(vty_io vio, enum node_type node) +uty_cli_draw_this(vty_cli cli, enum node_type node) { const char* prompt ; size_t l_len ; int p_len ; - if (vio->cli_dirty) - uty_cli_out_newline(vio) ; /* clears cli_dirty and cli_drawn */ + if (cli->dirty) + uty_cli_out_newline(cli) ; /* clears cli_dirty and cli_drawn */ /* Sort out what the prompt is. */ - if (vio->half_closed) + if (cli->vf->vin_state != vf_open) { prompt = "" ; p_len = 0 ; l_len = 0 ; } - else if (vio->cli_more_wait) + else if (cli->more_wait) { prompt = "--more--" ; p_len = strlen(prompt) ; l_len = 0 ; } - else if (vio->cmd_in_progress) + else if (cli->in_progress) { /* If there is a queued command, the prompt is a minimal affair. */ prompt = "~ " ; p_len = strlen(prompt) ; - l_len = vio->cl.len ; + l_len = qs_len_nn(cli->cl) ; } else { @@ -1225,49 +1510,46 @@ uty_cli_draw_this(vty_io vio, enum node_type node) * * If the host name changes, the cli_prompt_set flag is cleared. */ - if (!vio->cli_prompt_set || (node != vio->cli_prompt_node)) + if ((node != cli->prompt_node) || (host.name_gen != cli->prompt_gen)) { const char* prompt ; - if (vty_host_name == NULL) - uty_check_host_name() ; /* should never be required */ - prompt = cmd_prompt(node) ; if (prompt == NULL) { - zlog_err("vty %s has node %d", uty_get_name(vio), node) ; + zlog_err("vty %s has node %d", uty_get_name(cli->vf->vio), node) ; prompt = "%s ???: " ; } ; - qs_printf(&vio->cli_prompt_for_node, prompt, vty_host_name); + qs_printf(cli->prompt_for_node, prompt, host.name); - vio->cli_prompt_node = node ; - vio->cli_prompt_set = 1 ; + cli->prompt_node = node ; + cli->prompt_gen = host.name_gen ; } ; - prompt = vio->cli_prompt_for_node.body ; - p_len = vio->cli_prompt_for_node.len ; - l_len = vio->cl.len ; + prompt = qs_char(cli->prompt_for_node) ; + p_len = qs_len(cli->prompt_for_node) ; + l_len = qs_len_nn(cli->cl) ; } ; /* Now, if line is currently drawn, time to wipe it */ - if (vio->cli_drawn) - uty_cli_wipe(vio, p_len + l_len) ; + if (cli->drawn) + uty_cli_wipe(cli, p_len + l_len) ; /* Set about writing the prompt and the line */ - vio->cli_drawn = 1 ; - vio->cli_extra_len = 0 ; - vio->cli_echo_suppress = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ; + cli->drawn = true ; + cli->extra_len = false ; + cli->echo_suppress = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ; - vio->cli_prompt_len = p_len ; + cli->prompt_len = p_len ; - uty_cli_write(vio, prompt, p_len) ; + uty_cli_write(cli, prompt, p_len) ; if (l_len != 0) { - uty_cli_write(vio, qs_chars(&vio->cl), l_len) ; - if (vio->cl.cp < l_len) - uty_cli_write_n(vio, telnet_backspaces, l_len - vio->cl.cp) ; + uty_cli_write(cli, qs_char(cli->cl), l_len) ; + if (qs_cp_nn(cli->cl) < l_len) + uty_cli_write_n(cli, telnet_backspaces, l_len - qs_cp_nn(cli->cl)) ; } ; } ; @@ -1298,11 +1580,11 @@ uty_cli_draw_this(vty_io vio, enum node_type node) * Wipe any existing command line. */ extern void -uty_cli_pre_monitor(vty_io vio, size_t len) +uty_cli_pre_monitor(vty_cli cli, size_t len) { VTY_ASSERT_LOCKED() ; - uty_cli_wipe(vio, len) ; + uty_cli_wipe(cli, len) ; } ; /*------------------------------------------------------------------------------ @@ -1317,16 +1599,16 @@ uty_cli_pre_monitor(vty_io vio, size_t len) * > 0 => rump not empty or some command line stuff written */ extern int -uty_cli_post_monitor(vty_io vio, const char* buf, size_t len) +uty_cli_post_monitor(vty_cli cli, const char* buf, size_t len) { VTY_ASSERT_LOCKED() ; if (len != 0) - uty_cli_write(vio, buf, len) ; + uty_cli_write(cli, buf, len) ; - if (vio->cli_more_wait || (vio->cl.len != 0)) + if (cli->more_wait || (qs_len_nn(cli->cl) != 0)) { - uty_cli_draw(vio) ; + uty_cli_draw(cli) ; ++len ; } ; @@ -1337,48 +1619,52 @@ uty_cli_post_monitor(vty_io vio, const char* buf, size_t len) * Command line processing loop */ -static bool uty_telnet_command(vty_io vio, keystroke stroke, bool callback) ; -static int uty_cli_insert (vty_io vio, const char* chars, int n) ; -static int uty_cli_overwrite (vty_io vio, char* chars, int n) ; -static int uty_cli_word_overwrite (vty_io vio, char *str) ; -static int uty_cli_forwards(vty_io vio, int n) ; -static int uty_cli_backwards(vty_io vio, int n) ; -static int uty_cli_del_forwards(vty_io vio, int n) ; -static int uty_cli_del_backwards(vty_io vio, int n) ; -static int uty_cli_bol (vty_io vio) ; -static int uty_cli_eol (vty_io vio) ; -static int uty_cli_word_forwards_delta(vty_io vio) ; -static int uty_cli_word_forwards(vty_io vio) ; -static int uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) ; -static int uty_cli_word_backwards_pure (vty_io vio) ; -static int uty_cli_word_backwards (vty_io vio) ; -static int uty_cli_del_word_forwards(vty_io vio) ; -static int uty_cli_del_word_backwards(vty_io vio) ; -static int uty_cli_del_to_eol (vty_io vio) ; -static int uty_cli_clear_line(vty_io vio) ; -static int uty_cli_transpose_chars(vty_io vio) ; -static void uty_cli_history_use(vty_io vio, int step) ; -static void uty_cli_next_line(vty_io vio) ; -static void uty_cli_previous_line (vty_io vio) ; -static void uty_cli_complete_command (vty_io vio, enum node_type node) ; -static void uty_cli_describe_command (vty_io vio, enum node_type node) ; +static int uty_cli_insert (vty_cli cli, const char* chars, int n) ; +static int uty_cli_overwrite (vty_cli cli, char* chars, int n) ; +//static int uty_cli_word_overwrite (vty_cli cli, char *str) ; +static int uty_cli_forwards(vty_cli cli, int n) ; +static int uty_cli_backwards(vty_cli cli, int n) ; +static int uty_cli_del_forwards(vty_cli cli, int n) ; +static int uty_cli_del_backwards(vty_cli cli, int n) ; +static int uty_cli_bol (vty_cli cli) ; +static int uty_cli_eol (vty_cli cli) ; +static int uty_cli_word_forwards_delta(vty_cli cli) ; +static int uty_cli_word_forwards(vty_cli cli) ; +static int uty_cli_word_backwards_delta(vty_cli cli, int eat_spaces) ; +//static int uty_cli_word_backwards_pure (vty_cli cli) ; +static int uty_cli_word_backwards (vty_cli cli) ; +static int uty_cli_del_word_forwards(vty_cli cli) ; +static int uty_cli_del_word_backwards(vty_cli cli) ; +static int uty_cli_del_to_eol (vty_cli cli) ; +static int uty_cli_clear_line(vty_cli cli) ; +static int uty_cli_transpose_chars(vty_cli cli) ; +static void uty_cli_hist_use(vty_cli cli, int step) ; +static void uty_cli_hist_next(vty_cli cli) ; +static void uty_cli_hist_previous(vty_cli cli) ; +static void uty_cli_complete_command (vty_cli cli, node_type_t node) ; +static void uty_cli_describe_command (vty_cli cli, node_type_t node) ; /*------------------------------------------------------------------------------ * Fetch next keystroke, reading from the file if required. */ -static inline bool -uty_cli_get_keystroke(vty_io vio, keystroke stroke) +static bool +uty_cli_get_keystroke(vty_cli cli, keystroke stroke) { - if (keystroke_get(vio->key_stream, stroke)) - return 1 ; + int got ; + + do + { + if (keystroke_get(cli->key_stream, stroke)) + return true ; - uty_read(vio, NULL) ; /* not stealing */ + got = uty_term_read(cli->vf, NULL) ; /* not stealing */ + } while (got > 0) ; - return keystroke_get(vio->key_stream, stroke) ; + return false ; } ; /*------------------------------------------------------------------------------ - * Process keystrokes until run out of input, or get something to cli_do. + * Process keystrokes until run out of input, or get something to cmd_do. * * If required, draw the prompt and command line. * @@ -1388,30 +1674,30 @@ uty_cli_get_keystroke(vty_io vio, keystroke stroke) * Processes the contents of the keystroke stream. If exhausts that, will set * ready to read and return. (To give some "sharing".) * - * Returns: cli_do_xxxx + * Returns: cmd_do_xxxx * * When returns the cl is '\0' terminated. */ -static enum cli_do -uty_cli_process(vty_io vio, enum node_type node) +static cmd_do_t +uty_cli_process(vty_cli cli, node_type_t node) { - struct keystroke stroke ; - uint8_t u ; - int auth ; - enum cli_do ret ; + struct keystroke stroke ; + uint8_t u ; + bool auth ; + cmd_do_t to_do ; auth = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ; /* Now process as much as possible of what there is */ - ret = cli_do_nothing ; - while (ret == cli_do_nothing) + to_do = cmd_do_nothing ; + while (to_do == cmd_do_nothing) { - if (!vio->cli_drawn) - uty_cli_draw_this(vio, node) ; + if (!cli->drawn) + uty_cli_draw_this(cli, node) ; - if (!uty_cli_get_keystroke(vio, &stroke)) + if (!uty_cli_get_keystroke(cli, &stroke)) { - ret = (stroke.value == knull_eof) ? cli_do_eof : cli_do_nothing ; + to_do = (stroke.value == knull_eof) ? cmd_do_eof : cmd_do_nothing ; break ; } ; @@ -1431,87 +1717,91 @@ uty_cli_process(vty_io vio, enum node_type node) switch (stroke.value) { case CONTROL('A'): - uty_cli_bol (vio); + uty_cli_bol (cli); break; case CONTROL('B'): - uty_cli_backwards(vio, 1); + uty_cli_backwards(cli, 1); break; case CONTROL('C'): - ret = cli_do_ctrl_c ; /* Exit on ^C ..................*/ + to_do = cmd_do_ctrl_c ; /* Exit on ^C ..................*/ break ; case CONTROL('D'): - if (vio->cl.len == 0) /* if at start of empty line */ - ret = cli_do_ctrl_d ; /* Exit on ^D ..................*/ + if (auth) + to_do = cmd_do_ctrl_d ; /* Exit on ^D ..................*/ else - uty_cli_del_forwards(vio, 1); + uty_cli_del_forwards(cli, 1); break; case CONTROL('E'): - uty_cli_eol (vio); + uty_cli_eol (cli); break; case CONTROL('F'): - uty_cli_forwards(vio, 1); + uty_cli_forwards(cli, 1); break; case CONTROL('H'): case 0x7f: - uty_cli_del_backwards(vio, 1); + uty_cli_del_backwards(cli, 1); break; case CONTROL('K'): - uty_cli_del_to_eol (vio); + uty_cli_del_to_eol (cli); break; case CONTROL('N'): - uty_cli_next_line (vio); + uty_cli_hist_next (cli); break; case CONTROL('P'): - uty_cli_previous_line (vio); + uty_cli_hist_previous(cli); break; case CONTROL('T'): - uty_cli_transpose_chars (vio); + uty_cli_transpose_chars (cli); break; case CONTROL('U'): - uty_cli_clear_line(vio); + uty_cli_clear_line(cli); break; case CONTROL('W'): - uty_cli_del_word_backwards (vio); + uty_cli_del_word_backwards (cli); break; case CONTROL('Z'): - ret = cli_do_ctrl_z ; /* Exit on ^Z ..................*/ + to_do = cmd_do_ctrl_z ; /* Exit on ^Z ..................*/ break; case '\n': case '\r': - ret = cli_do_command ; /* Exit on CR or LF.............*/ + to_do = cmd_do_command ; /* Exit on CR or LF.............*/ break ; case '\t': if (auth) - break ; + uty_cli_insert (cli, " ", 1) ; + else if (cmd_token_position(cli->parsed, cli->cl)) + uty_cli_insert (cli, " ", 1) ; else - uty_cli_complete_command (vio, node); + uty_cli_complete_command (cli, node); break; case '?': - if (auth) - uty_cli_insert (vio, (char*)&u, 1); + if (auth) + uty_cli_insert (cli, (char*)&u, 1) ; + else if (cmd_token_position(cli->parsed, cli->cl)) + uty_cli_insert (cli, (char*)&u, 1) ; else - uty_cli_describe_command (vio, node); + uty_cli_describe_command (cli, node); break; default: if ((stroke.value >= 0x20) && (stroke.value < 0x7F)) - uty_cli_insert (vio, (char*)&u, 1) ; + uty_cli_insert (cli, (char*)&u, 1) ; break; } break ; @@ -1521,20 +1811,20 @@ uty_cli_process(vty_io vio, enum node_type node) switch (stroke.value) { case 'b': - uty_cli_word_backwards (vio); + uty_cli_word_backwards (cli); break; case 'f': - uty_cli_word_forwards (vio); + uty_cli_word_forwards (cli); break; case 'd': - uty_cli_del_word_forwards (vio); + uty_cli_del_word_forwards (cli); break; case CONTROL('H'): case 0x7f: - uty_cli_del_word_backwards (vio); + uty_cli_del_word_backwards (cli); break; default: @@ -1549,20 +1839,20 @@ uty_cli_process(vty_io vio, enum node_type node) switch (stroke.value) { - case ('A'): - uty_cli_previous_line (vio); + case ('A'): /* up arrow */ + uty_cli_hist_previous(cli); break; - case ('B'): - uty_cli_next_line (vio); + case ('B'): /* down arrow */ + uty_cli_hist_next (cli); break; - case ('C'): - uty_cli_forwards(vio, 1); + case ('C'): /* right arrow */ + uty_cli_forwards(cli, 1); break; - case ('D'): - uty_cli_backwards(vio, 1); + case ('D'): /* left arrow */ + uty_cli_backwards(cli, 1); break; default: @@ -1572,7 +1862,7 @@ uty_cli_process(vty_io vio, enum node_type node) /* Telnet Command ----------------------------------------------------*/ case ks_iac: - uty_telnet_command(vio, &stroke, false) ; + uty_telnet_command(cli->vf, &stroke, false) ; break ; /* Unknown -----------------------------------------------------------*/ @@ -1584,12 +1874,10 @@ uty_cli_process(vty_io vio, enum node_type node) /* Tidy up and return where got to. */ - if (ret != cli_do_nothing) - uty_cli_eol (vio) ; /* go to the end of the line */ - - qs_term(&vio->cl) ; /* add '\0' */ + if (to_do != cmd_do_nothing) + uty_cli_eol (cli) ; /* go to the end of the line */ - return ret ; + return to_do ; } ; /*============================================================================== @@ -1602,25 +1890,25 @@ uty_cli_process(vty_io vio, enum node_type node) * Returns number of characters inserted -- ie 'n' */ static int -uty_cli_insert (vty_io vio, const char* chars, int n) +uty_cli_insert (vty_cli cli, const char* chars, int n) { int after ; VTY_ASSERT_LOCKED() ; - assert((vio->cl.cp <= vio->cl.len) && (n >= 0)) ; + assert((qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) && (n >= 0)) ; if (n <= 0) return n ; /* avoid trouble */ - after = qs_insert(&vio->cl, chars, n) ; + after = qs_insert(cli->cl, chars, n) ; - uty_cli_echo(vio, qs_cp_char(&vio->cl), after + n) ; + uty_cli_echo(cli, qs_cp_char(cli->cl), after) ; - if (after != 0) - uty_cli_echo_n(vio, telnet_backspaces, after) ; + if ((after - n) != 0) + uty_cli_echo_n(cli, telnet_backspaces, after - n) ; - vio->cl.cp += n ; + qs_move_cp_nn(cli->cl, n) ; return n ; } ; @@ -1633,24 +1921,58 @@ uty_cli_insert (vty_io vio, const char* chars, int n) * Returns number of characters inserted -- ie 'n' */ static int -uty_cli_overwrite (vty_io vio, char* chars, int n) +uty_cli_overwrite (vty_cli cli, char* chars, int n) { VTY_ASSERT_LOCKED() ; - assert((vio->cl.cp <= vio->cl.len) && (n >= 0)) ; + assert((qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) && (n >= 0)) ; if (n > 0) { - qs_replace(&vio->cl, chars, n) ; - uty_cli_echo(vio, chars, n) ; + qs_replace(cli->cl, n, chars, n) ; + uty_cli_echo(cli, chars, n) ; - vio->cl.cp += n ; + qs_move_cp_nn(cli->cl, n) ; } ; return n ; } /*------------------------------------------------------------------------------ + * Replace 'm' characters at the current position, by 'n' characters and leave + * cursor at the end of the inserted characters. + * + * Returns number of characters inserted -- ie 'n' + */ +static int +uty_cli_replace(vty_cli cli, int m, const char* chars, int n) +{ + int a, b ; + + VTY_ASSERT_LOCKED() ; + + assert((qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) && (n >= 0) && (m >= 0)) ; + + b = qs_len_nn(cli->cl) - qs_cp_nn(cli->cl) ; + a = qs_replace(cli->cl, m, chars, n) ; + + uty_cli_echo(cli, qs_cp_char(cli->cl), a) ; + + if (a < b) + uty_cli_echo_n(cli, telnet_spaces, b - a) ; + else + b = a ; + + if (b > n) + uty_cli_echo_n(cli, telnet_backspaces, b - n) ; + + qs_move_cp_nn(cli->cl, n) ; + + return n ; +} ; + +#if 0 +/*------------------------------------------------------------------------------ * Insert a word into vty interface with overwrite mode. * * NB: Assumes result will then be the end of the line. @@ -1658,17 +1980,18 @@ uty_cli_overwrite (vty_io vio, char* chars, int n) * Returns number of characters inserted -- ie length of string */ static int -uty_cli_word_overwrite (vty_io vio, char *str) +uty_cli_word_overwrite (vty_cli cli, char *str) { int n ; VTY_ASSERT_LOCKED() ; - n = uty_cli_overwrite(vio, str, strlen(str)) ; + n = uty_cli_overwrite(cli, str, strlen(str)) ; - vio->cl.len = vio->cl.cp ; + qs_set_len_nn(cli->cl, qs_cp_nn(cli->cl)) ; return n ; } +#endif /*------------------------------------------------------------------------------ * Forward 'n' characters -- stop at end of line. @@ -1676,12 +1999,12 @@ uty_cli_word_overwrite (vty_io vio, char *str) * Returns number of characters actually moved */ static int -uty_cli_forwards(vty_io vio, int n) +uty_cli_forwards(vty_cli cli, int n) { int have ; VTY_ASSERT_LOCKED() ; - have = vio->cl.len - vio->cl.cp ; + have = qs_after_cp_nn(cli->cl) ; if (have < n) n = have ; @@ -1689,12 +2012,12 @@ uty_cli_forwards(vty_io vio, int n) if (n > 0) { - uty_cli_echo(vio, qs_cp_char(&vio->cl), n) ; - vio->cl.cp += n ; + uty_cli_echo(cli, qs_cp_char(cli->cl), n) ; + qs_move_cp_nn(cli->cl, n) ; } ; return n ; -} +} ; /*------------------------------------------------------------------------------ * Backwards 'n' characters -- stop at start of line. @@ -1702,23 +2025,43 @@ uty_cli_forwards(vty_io vio, int n) * Returns number of characters actually moved */ static int -uty_cli_backwards(vty_io vio, int n) +uty_cli_backwards(vty_cli cli, int n) { VTY_ASSERT_LOCKED() ; - if ((int)vio->cl.cp < n) - n = vio->cl.cp ; + if ((int)qs_cp_nn(cli->cl) < n) + n = qs_cp_nn(cli->cl) ; assert(n >= 0) ; if (n > 0) { - uty_cli_echo_n(vio, telnet_backspaces, n) ; - vio->cl.cp -= n ; + uty_cli_echo_n(cli, telnet_backspaces, n) ; + qs_move_cp_nn(cli->cl, -n) ; } ; return n ; -} +} ; + +/*------------------------------------------------------------------------------ + * Move forwards (if n > 0) or backwards (if n < 0) -- stop at start or end of + * line. + * + * Returns number of characters actually moved -- signed + */ +static int +uty_cli_move(vty_cli cli, int n) +{ + VTY_ASSERT_LOCKED() ; + + if (n < 0) + return - uty_cli_backwards(cli, -n) ; + + if (n > 0) + return + uty_cli_forwards(cli, +n) ; + + return 0 ; +} ; /*------------------------------------------------------------------------------ * Delete 'n' characters -- forwards -- stop at end of line. @@ -1726,14 +2069,14 @@ uty_cli_backwards(vty_io vio, int n) * Returns number of characters actually deleted. */ static int -uty_cli_del_forwards(vty_io vio, int n) +uty_cli_del_forwards(vty_cli cli, int n) { int after ; int have ; VTY_ASSERT_LOCKED() ; - have = vio->cl.len - vio->cl.cp ; + have = qs_after_cp_nn(cli->cl) ; if (have < n) n = have ; /* cannot delete more than have */ @@ -1742,13 +2085,13 @@ uty_cli_del_forwards(vty_io vio, int n) if (n <= 0) return 0 ; - after = qs_delete(&vio->cl, n) ; + after = qs_delete(cli->cl, n) ; if (after > 0) - uty_cli_echo(vio, qs_cp_char(&vio->cl), after) ; + uty_cli_echo(cli, qs_cp_char(cli->cl), after) ; - uty_cli_echo_n(vio, telnet_spaces, n) ; - uty_cli_echo_n(vio, telnet_backspaces, after + n) ; + uty_cli_echo_n(cli, telnet_spaces, n) ; + uty_cli_echo_n(cli, telnet_backspaces, after + n) ; return n ; } @@ -1759,9 +2102,9 @@ uty_cli_del_forwards(vty_io vio, int n) * Returns number of characters actually deleted. */ static int -uty_cli_del_backwards(vty_io vio, int n) +uty_cli_del_backwards(vty_cli cli, int n) { - return uty_cli_del_forwards(vio, uty_cli_backwards(vio, n)) ; + return uty_cli_del_forwards(cli, uty_cli_backwards(cli, n)) ; } /*------------------------------------------------------------------------------ @@ -1770,9 +2113,9 @@ uty_cli_del_backwards(vty_io vio, int n) * Returns number of characters moved over. */ static int -uty_cli_bol (vty_io vio) +uty_cli_bol (vty_cli cli) { - return uty_cli_backwards(vio, vio->cl.cp) ; + return uty_cli_backwards(cli, qs_cp_nn(cli->cl)) ; } ; /*------------------------------------------------------------------------------ @@ -1781,9 +2124,9 @@ uty_cli_bol (vty_io vio) * Returns number of characters moved over */ static int -uty_cli_eol (vty_io vio) +uty_cli_eol (vty_cli cli) { - return uty_cli_forwards(vio, vio->cl.len - vio->cl.cp) ; + return uty_cli_forwards(cli, qs_after_cp_nn(cli->cl)) ; } ; /*------------------------------------------------------------------------------ @@ -1794,7 +2137,7 @@ uty_cli_eol (vty_io vio) * Steps over non-space characters and then any spaces. */ static int -uty_cli_word_forwards_delta(vty_io vio) +uty_cli_word_forwards_delta(vty_cli cli) { char* cp ; char* tp ; @@ -1802,10 +2145,10 @@ uty_cli_word_forwards_delta(vty_io vio) VTY_ASSERT_LOCKED() ; ; - assert(vio->cl.cp <= vio->cl.len) ; + assert(qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) ; - cp = qs_cp_char(&vio->cl) ; - ep = qs_ep_char(&vio->cl) ; + cp = qs_cp_char(cli->cl) ; + ep = qs_ep_char(cli->cl) ; tp = cp ; @@ -1824,9 +2167,9 @@ uty_cli_word_forwards_delta(vty_io vio) * Moves past any non-spaces, then past any spaces. */ static int -uty_cli_word_forwards(vty_io vio) +uty_cli_word_forwards(vty_cli cli) { - return uty_cli_forwards(vio, uty_cli_word_forwards_delta(vio)) ; + return uty_cli_forwards(cli, uty_cli_word_forwards_delta(cli)) ; } ; /*------------------------------------------------------------------------------ @@ -1838,7 +2181,7 @@ uty_cli_word_forwards(vty_io vio) * Steps back until next (backwards) character is space, or hits start of line. */ static int -uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) +uty_cli_word_backwards_delta(vty_cli cli, int eat_spaces) { char* cp ; char* tp ; @@ -1846,10 +2189,10 @@ uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) VTY_ASSERT_LOCKED() ; ; - assert(vio->cl.cp <= vio->cl.len) ; + assert(qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) ; - cp = qs_cp_char(&vio->cl) ; - sp = qs_chars(&vio->cl) ; + cp = qs_cp_char(cli->cl) ; + sp = qs_char(cli->cl) ; tp = cp ; @@ -1863,6 +2206,7 @@ uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) return cp - tp ; } ; +#if 0 /*------------------------------------------------------------------------------ * Backward word, but not trailing spaces. * @@ -1871,10 +2215,11 @@ uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) * Returns number of characters stepped over. */ static int -uty_cli_word_backwards_pure (vty_io vio) +uty_cli_word_backwards_pure (vty_cli cli) { - return uty_cli_backwards(vio, uty_cli_word_backwards_delta(vio, 0)) ; + return uty_cli_backwards(cli, uty_cli_word_backwards_delta(cli, 0)) ; } ; +#endif /*------------------------------------------------------------------------------ * Backward word -- move to start of previous word. @@ -1885,9 +2230,9 @@ uty_cli_word_backwards_pure (vty_io vio) * Returns number of characters stepped over. */ static int -uty_cli_word_backwards (vty_io vio) +uty_cli_word_backwards (vty_cli cli) { - return uty_cli_backwards(vio, uty_cli_word_backwards_delta(vio, 1)) ; + return uty_cli_backwards(cli, uty_cli_word_backwards_delta(cli, 1)) ; } ; /*------------------------------------------------------------------------------ @@ -1898,9 +2243,9 @@ uty_cli_word_backwards (vty_io vio) * Returns number of characters deleted. */ static int -uty_cli_del_word_forwards(vty_io vio) +uty_cli_del_word_forwards(vty_cli cli) { - return uty_cli_del_forwards(vio, uty_cli_word_forwards_delta(vio)) ; + return uty_cli_del_forwards(cli, uty_cli_word_forwards_delta(cli)) ; } /*------------------------------------------------------------------------------ @@ -1911,9 +2256,9 @@ uty_cli_del_word_forwards(vty_io vio) * Returns number of characters deleted. */ static int -uty_cli_del_word_backwards(vty_io vio) +uty_cli_del_word_backwards(vty_cli cli) { - return uty_cli_del_backwards(vio, uty_cli_word_backwards_delta(vio, 1)) ; + return uty_cli_del_backwards(cli, uty_cli_word_backwards_delta(cli, 1)) ; } ; /*------------------------------------------------------------------------------ @@ -1922,9 +2267,9 @@ uty_cli_del_word_backwards(vty_io vio) * Returns number of characters deleted. */ static int -uty_cli_del_to_eol (vty_io vio) +uty_cli_del_to_eol (vty_cli cli) { - return uty_cli_del_forwards(vio, vio->cl.len - vio->cl.cp) ; + return uty_cli_del_forwards(cli, qs_after_cp_nn(cli->cl)) ; } ; /*------------------------------------------------------------------------------ @@ -1933,10 +2278,10 @@ uty_cli_del_to_eol (vty_io vio) * Returns number of characters deleted. */ static int -uty_cli_clear_line(vty_io vio) +uty_cli_clear_line(vty_cli cli) { - uty_cli_bol(vio) ; - return uty_cli_del_to_eol(vio) ; + uty_cli_bol(cli) ; + return uty_cli_del_to_eol(cli) ; } ; /*------------------------------------------------------------------------------ @@ -1945,7 +2290,7 @@ uty_cli_clear_line(vty_io vio) * Return number of characters affected. */ static int -uty_cli_transpose_chars(vty_io vio) +uty_cli_transpose_chars(vty_cli cli) { char chars[2] ; char* cp ; @@ -1953,54 +2298,79 @@ uty_cli_transpose_chars(vty_io vio) VTY_ASSERT_LOCKED() ; /* Give up if < 2 characters or at start of line. */ - if ((vio->cl.len < 2) || (vio->cl.cp < 1)) + if ((qs_len_nn(cli->cl) < 2) || (qs_cp_nn(cli->cl) < 1)) return 0 ; /* Move back to first of characters to exchange */ - if (vio->cl.cp == vio->cl.len) - uty_cli_backwards(vio, 2) ; + if (qs_cp_nn(cli->cl) == qs_len_nn(cli->cl)) + uty_cli_backwards(cli, 2) ; else - uty_cli_backwards(vio, 1) ; + uty_cli_backwards(cli, 1) ; /* Pick up in the new order */ - cp = qs_cp_char(&vio->cl) ; + cp = qs_cp_char(cli->cl) ; chars[1] = *cp++ ; chars[0] = *cp ; /* And overwrite */ - return uty_cli_overwrite(vio, chars, 2) ; + return uty_cli_overwrite(cli, chars, 2) ; } ; /*============================================================================== * Command line history handling + * + * cli->hist is vector of qstrings + * cli->h_now is index of the present time + * cli->hp is index of most recent line read back + * + * cli->hist is initialised empty, with h_now == hp == 0. + * + * On first use it is set to VTY_MAX_HIST entries, and its size never changes. + * Before VTY_MAX_HIST lines have been inserted, a NULL entry signals the end + * of history (to date). + * + * h_now is incremented after a line is inserted (and wraps round). So + * stepping +1 moves towards the present (down) and -1 moves towards the past + * (up). + * + * hp == h_now means we are in the present. + * + * Cannot step forwards from hp == h_now (into the future !). + * + * Before stepping backwards from hp == hp_now, sets hp_now to be a copy of + * the current line (complete with cp), so can return to the present. + * + * Cannot step backwards to hp == hp_now -- that would be to wrap round from + * the ancient past to the now time. + * + * When storing a line in the history, replaces the last line stored if that + * is the same as the new line, apart from whitespace. */ /*------------------------------------------------------------------------------ * Add given command line to the history buffer. * * This is inserting the vty->buf line into the history. + * + * Resets hp == h_now. */ -extern void -uty_cli_hist_add (vty_io vio, const char* cmd_line) +static void +uty_cli_hist_add (vty_cli cli, qstring clx) { - qstring prev_line ; - qstring_t line ; - int prev_index ; + qstring hist_line ; + int prev ; VTY_ASSERT_LOCKED() ; - /* Construct a dummy qstring for the given command line */ - qs_dummy(&line, cmd_line, 1) ; /* set cursor to the end */ - /* make sure have a suitable history vector */ - vector_set_min_length(vio->hist, VTY_MAXHIST) ; + vector_set_min_length(cli->hist, VTY_MAXHIST) ; - /* find the previous command line in the history */ - prev_index = vio->hindex - 1 ; - if (prev_index < 0) - prev_index = VTY_MAXHIST - 1 ; + /* get the previous command line */ + prev = cli->h_now - 1 ; + if (prev < 0) + prev = VTY_MAXHIST - 1 ; - prev_line = vector_get_item(vio->hist, prev_index) ; + hist_line = vector_get_item(cli->hist, prev) ; /* If the previous line is NULL, that means the history is empty. * @@ -2008,23 +2378,32 @@ uty_cli_hist_add (vty_io vio, const char* cmd_line) * replace it with the current line -- so that the latest whitespace * version is saved. * - * Either way, replace the the previous line entry by moving hindex - * back ! + * In both those cases, replace the the previous line entry by moving + * h_now back to it -- leaving hist_line pointing at it. + * + * Otherwise, leave cli->h_now and point hist_line at the most ancient + * line in history. */ - if ((prev_line == NULL) || (qs_cmp_sig(prev_line, &line) == 0)) - vio->hindex = prev_index ; + if ((hist_line == NULL) || (qs_cmp_sig(hist_line, clx) == 0)) + cli->h_now = prev ; else - prev_line = vector_get_item(vio->hist, vio->hindex) ; + hist_line = vector_get_item(cli->hist, cli->h_now) ; - /* Now replace the hindex entry */ - vector_set_item(vio->hist, vio->hindex, qs_copy(prev_line, &line)) ; + /* Now replace the h_now entry + * + * Note that the line inserted in the history has it's 'cp' set to the end of + * the line -- so that it is there when it comes back out again. + */ + hist_line = qs_copy(hist_line, clx) ; + qs_set_cp_nn(hist_line, qs_len_nn(hist_line)) ; + vector_set_item(cli->hist, cli->h_now, hist_line) ; /* Advance to the near future and reset the history pointer */ - vio->hindex++; - if (vio->hindex == VTY_MAXHIST) - vio->hindex = 0; + cli->h_now++; + if (cli->h_now == VTY_MAXHIST) + cli->h_now = 0; - vio->hp = vio->hindex; + cli->hp = cli->h_now; } ; /*------------------------------------------------------------------------------ @@ -2032,668 +2411,503 @@ uty_cli_hist_add (vty_io vio, const char* cmd_line) * * This function is called from vty_next_line and vty_previous_line. * - * Step +1 is towards the present - * -1 is into the past + * Step -1 is into the past (up) + * +1 is towards the present (down) */ static void -uty_cli_history_use(vty_io vio, int step) +uty_cli_hist_use(vty_cli cli, int step) { - int index ; - unsigned old_len ; - unsigned after ; - unsigned back ; - qstring hist ; + int hp ; + ulen old_len ; + ulen new_len ; + ulen after ; + ulen back ; + qstring hist_line ; VTY_ASSERT_LOCKED() ; assert((step == +1) || (step == -1)) ; - index = vio->hp ; + hp = cli->hp ; /* Special case of being at the insertion point */ - if (index == vio->hindex) + if (hp == cli->h_now) { if (step > 0) return ; /* already in the present */ /* before stepping back from the present, take a copy of the * current command line -- so can get back to it. + * + * Note that the 'cp' is stored with the line. If return to here + * and later enter the line it will replace this. */ - hist = vector_get_item(vio->hist, vio->hindex) ; - vector_set_item(vio->hist, vio->hindex, qs_copy(hist, &vio->cl)) ; + hist_line = vector_get_item(cli->hist, cli->h_now) ; + vector_set_item(cli->hist, cli->h_now, qs_copy(hist_line, cli->cl)) ; } ; /* Advance or retreat */ - index += step ; - if (index < 0) - index = VTY_MAXHIST - 1 ; - else if (index >= VTY_MAXHIST) - index = 0 ; + hp += step ; + if (hp < 0) + hp = VTY_MAXHIST - 1 ; + else if (hp >= VTY_MAXHIST) + hp = 0 ; - hist = vector_get_item(vio->hist, index) ; + hist_line = vector_get_item(cli->hist, hp) ; - /* If moving backwards in time, may not move back to the insertion + /* If moving backwards in time, may not move back to the h_now * point (that would be wrapping round to the present) and may not * move back to a NULL entry (that would be going back before '.'). + * + * If moving forwards in time, may return to the present, with + * hp == cli->h_now. */ if (step < 0) - if ((hist == NULL) || (index == vio->hindex)) + if ((hist_line == NULL) || (hp == cli->h_now)) return ; - /* Now, if arrived at the insertion point, this is returning to the - * present, which is fine. - */ - vio->hp = index; + cli->hp = hp ; /* Move back to the start of the current line */ - uty_cli_bol(vio) ; + uty_cli_bol(cli) ; /* Get previous line from history buffer and echo that */ - old_len = vio->cl.len ; - qs_copy(&vio->cl, hist) ; + old_len = qs_len_nn(cli->cl) ; + qs_copy(cli->cl, hist_line) ; + new_len = qs_len_nn(cli->cl) ; /* Sort out wiping out any excess and setting the cursor position */ - if (old_len > vio->cl.len) - after = old_len - vio->cl.len ; + if (old_len > new_len) + after = old_len - new_len ; else after = 0 ; - back = after ; - if (vio->cl.len > vio->cl.cp) - back += (vio->cl.len - vio->cl.cp) ; + /* Return cursor to stored 'cp' -- which will be end of line unless + * this is the copy of the original current line stored above. + */ + back = after + qs_after_cp_nn(cli->cl) ; - if (vio->cl.len > 0) - uty_cli_echo(vio, vio->cl.body, vio->cl.len) ; + if (new_len > 0) + uty_cli_echo(cli, qs_char_nn(cli->cl), new_len) ; if (after > 0) - uty_cli_echo_n(vio, telnet_spaces, after) ; + uty_cli_echo_n(cli, telnet_spaces, after) ; if (back > 0) - uty_cli_echo_n(vio, telnet_backspaces, back) ; + uty_cli_echo_n(cli, telnet_backspaces, back) ; return ; } ; /*------------------------------------------------------------------------------ - * Use next history line, if any. + * Use previous history line, if any (up arrow). */ static void -uty_cli_next_line(vty_io vio) +uty_cli_hist_previous(vty_cli cli) { - uty_cli_history_use(vio, +1) ; + uty_cli_hist_use(cli, -1) ; } /*------------------------------------------------------------------------------ - * Use previous history line, if any. + * Use next history line, if any (down arrow). */ static void -uty_cli_previous_line (vty_io vio) +uty_cli_hist_next(vty_cli cli) { - uty_cli_history_use(vio, -1) ; + uty_cli_hist_use(cli, +1) ; } -/*============================================================================== - * Command Completion and Command Description - * - */ -static void uty_cli_describe_show(vty_io vio, vector describe) ; -static void uty_cli_describe_fold (vty_io vio, int cmd_width, - unsigned int desc_width, struct desc *desc) ; -static void uty_cli_describe_line(vty_io vio, int cmd_width, const char* cmd, - const char* str) ; - -static vector uty_cli_cmd_prepare(vty_io vio, int help) ; - /*------------------------------------------------------------------------------ - * Command completion + * Show the contents of the history */ -static void -uty_cli_complete_command (vty_io vio, enum node_type node) +extern void +uty_cli_hist_show(vty_cli cli) { - unsigned i ; - int ret ; - int len ; - int n ; - vector matched ; - vector vline ; + int hp ; VTY_ASSERT_LOCKED() ; - /* Try and match the tokenised command line */ - vline = uty_cli_cmd_prepare(vio, 1) ; - matched = cmd_complete_command (vline, node, &ret); - cmd_free_strvec (vline); + hp = cli->h_now ; - /* Show the result. */ - switch (ret) + while(1) { - case CMD_ERR_AMBIGUOUS: - uty_cli_out_newline(vio) ; /* clears cli_drawn */ - uty_cli_out_CMD_ERR_AMBIGUOUS(vio) ; - break ; - - case CMD_ERR_NO_MATCH: - uty_cli_out_newline(vio) ; /* clears cli_drawn */ - uty_cli_out_CMD_ERR_NO_MATCH(vio) ; - break ; - - case CMD_COMPLETE_FULL_MATCH: - uty_cli_eol (vio) ; - uty_cli_word_backwards_pure (vio); - uty_cli_word_overwrite (vio, vector_get_item(matched, 0)); - uty_cli_insert(vio, " ", 1); - break ; - - case CMD_COMPLETE_MATCH: - uty_cli_eol (vio) ; - uty_cli_word_backwards_pure (vio); - uty_cli_word_overwrite (vio, vector_get_item(matched, 0)); - break ; - - case CMD_COMPLETE_LIST_MATCH: - len = 6 ; - for (i = 0; i < vector_end(matched); i++) - { - int sl = strlen((char*)vector_get_item(matched, i)) ; - if (len < sl) - len = sl ; - } ; + qstring line ; - n = vio->width ; - if (n == 0) - n = 60 ; - n = n / (len + 2) ; - if (n == 0) - n = 1 ; + ++hp ; + if (hp == VTY_MAXHIST) + hp = 0 ; - for (i = 0; i < vector_end(matched); i++) - { - if ((i % n) == 0) - uty_cli_out_newline(vio) ; /* clears cli_drawn */ - uty_cli_out(vio, "%-*s ", len, (char*)vector_get_item(matched, i)); - } - uty_cli_out_newline(vio) ; + if (hp == cli->h_now) + break ; /* wrapped round to "now" */ - break; + line = vector_get_item(cli->hist, hp) ; - case CMD_COMPLETE_ALREADY: - default: - break; - } ; + if (line == NULL) + break ; /* reached limit of history so far */ - cmd_free_strvec(matched); + uty_out(cli->vf->vio, " %s\n", qs_string(line)); + } } ; -/*------------------------------------------------------------------------------ - * Command Description +/*============================================================================== + * Command Completion and Command Description + * */ -static void -uty_cli_describe_command (vty_io vio, node_type_t node) -{ - cmd_return_code_t ret ; - vector describe ; - - VTY_ASSERT_LOCKED() ; - - /* Try and match the tokenised command line */ - describe = cmd_describe_command (qs_term(&vio->cl), node, &ret); - - uty_cli_out_newline(vio); /* clears cli_drawn */ - - /* Deal with result. */ - switch (ret) - { - case CMD_ERR_AMBIGUOUS: - uty_cli_out_CMD_ERR_AMBIGUOUS(vio) ; - break ; +static uint uty_cli_help_parse(vty_cli cli, node_type_t node) ; +static void uty_cli_out_message(vty_cli cli, const char* msg) ; - case CMD_ERR_NO_MATCH: - uty_cli_out_CMD_ERR_NO_MATCH(vio) ; - break ; +static void uty_cli_complete_keyword(vty_cli cli, const char* keyword) ; +static void uty_cli_complete_list(vty_cli cli, vector item_v) ; - default: - uty_cli_describe_show(vio, describe) ; - break ; - } ; +static void uty_cli_describe_list(vty_cli cli, vector item_v) ; +static void uty_cli_describe_line(vty_cli cli, uint str_width, const char* str, + const char* doc, uint len) ; - if (describe != NULL) - vector_free (describe); -} +static uint uty_cli_width_to_use(vty_cli cli) ; /*------------------------------------------------------------------------------ - * Show the command description. - * - * Generates lines of the form: - * - * word description text - * - * Where the word field is adjusted to suit the longest word, and the - * description text is wrapped if required (if the width of the console is - * known) so that get: + * Command completion * - * word description .................................. - * .............text + * Requires that cmd_token_position() has been called to tokenise the line and + * establish which token the cursor is in. Must NOT call this if the cursor + * is in a "special" place. * - * If one of the options is '<cr>', that is always shown last. */ static void -uty_cli_describe_show(vty_io vio, vector describe) +uty_cli_complete_command (vty_cli cli, node_type_t node) { - unsigned int i, cmd_width, desc_width; - struct desc *desc, *desc_cr ; + uint n_items ; + cmd_parsed parsed ; + cmd_item item ; - /* Get width of the longest "word" */ - cmd_width = 0; - for (i = 0; i < vector_active (describe); i++) - if ((desc = vector_slot (describe, i)) != NULL) - { - unsigned int len; + VTY_ASSERT_LOCKED() ; + + parsed = cli->parsed ; - if (desc->cmd[0] == '\0') - continue; + /* Establish what items may be present at the current token position. */ + n_items = uty_cli_help_parse(cli, node) ; - len = strlen (desc->cmd); - if (desc->cmd[0] == '.') - len--; + if (n_items == 0) /* quit if nothing to consider */ + return ; - if (cmd_width < len) - cmd_width = len; - } + if (n_items > 1) /* render list of alternatives */ + return uty_cli_complete_list(cli, parsed->item_v) ; - /* Set width of description string. */ - desc_width = vio->width - (cmd_width + 6); + /* One possible item -- one or more possible commands */ + item = vector_get_item(parsed->item_v, 0) ; - /* Print out description. */ - desc_cr = NULL ; /* put <cr> last if it appears */ + switch (item->type) + { + case item_null: + zabort("invalid item_null") ; - for (i = 0; i < vector_active (describe); i++) - if ((desc = vector_slot (describe, i)) != NULL) - { - if (desc->cmd[0] == '\0') - continue; + case item_eol: - if (strcmp (desc->cmd, command_cr) == 0) - { - desc_cr = desc; - continue; - } + case item_option_word: + + case item_vararg: + + case item_word: + + case item_ipv6_prefix: + case item_ipv6_address: + case item_ipv4_prefix: + case item_ipv4_address: + + case item_range: + return uty_cli_describe_list(cli, parsed->item_v) ; - uty_cli_describe_fold (vio, cmd_width, desc_width, desc); - } + case item_keyword: + return uty_cli_complete_keyword(cli, item->str) ; - if (desc_cr != NULL) - uty_cli_describe_fold (vio, cmd_width, desc_width, desc_cr); + default: + zabort("unknown item type") ; + } ; } ; /*------------------------------------------------------------------------------ - * Show one word and the description, folding the description as required. + * Command Description */ static void -uty_cli_describe_fold (vty_io vio, int cmd_width, - unsigned int desc_width, struct desc *desc) +uty_cli_describe_command (vty_cli cli, node_type_t node) { - char *buf; - const char *cmd, *p; - int pos; + uint n_items ; VTY_ASSERT_LOCKED() ; - cmd = desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd; - p = desc->str ; + /* Establish what items may be present at the current token position. */ + n_items = uty_cli_help_parse(cli, node) ; - /* If have a sensible description width */ - if (desc_width > 20) - { - buf = XCALLOC (MTYPE_TMP, strlen (desc->str) + 1); + if (n_items > 0) /* render list of possibilities */ + uty_cli_describe_list(cli, cli->parsed->item_v) ; +} ; - while (strlen (p) > desc_width) - { - /* move back to first space */ - for (pos = desc_width; pos > 0; pos--) - if (*(p + pos) == ' ') - break; +/*------------------------------------------------------------------------------ + * Parse for command completion and command description. + * + * Requires that cmd_token_position() has been called to tokenise the line and + * establish which token the cursor is in. Must NOT call this if the cursor + * is in a "special" place. + * + * Deal with all cases which yield no items at all. + * + * Returns: number of items to consider. + */ +static uint +uty_cli_help_parse(vty_cli cli, node_type_t node) +{ + const char* msg ; + cmd_return_code_t ret ; + uint n_items ; - /* if did not find a space, break at width */ - if (pos == 0) - pos = desc_width ; + /* The preflight checks avoid getting into trouble doing command completion + * on a line with comment + */ + msg = cmd_help_preflight(cli->parsed) ; + if (msg != NULL) + { + uty_cli_out_message(cli, msg) ; + return 0 ; + } ; - strncpy (buf, p, pos); - buf[pos] = '\0'; - uty_cli_describe_line(vio, cmd_width, cmd, buf) ; + /* Now see what the cmd_completion can come up with. */ + ret = cmd_completion(cli->parsed, node) ; - cmd = ""; /* for 2nd and subsequent lines */ + if (ret == CMD_ERR_PARSING) + { + uint eloc = cli->prompt_len + cli->parsed->eloc ; - p += pos ; /* step past what just wrote */ - while (*p == ' ') - ++p ; /* skip spaces */ - } ; + uty_cli_out_newline(cli) ; /* clears cli_drawn */ + uty_cli_write_n(cli, telnet_dots, eloc) ; + uty_cli_write_s(cli, "^") ; - XFREE (MTYPE_TMP, buf); + uty_cli_out_message(cli, cli->parsed->emess) ; + + return 0 ; } ; - uty_cli_describe_line(vio, cmd_width, cmd, p) ; -} ; + /* Will now have 0, 1 or more items which match at the current + * cursor token. + */ + n_items = vector_length(cli->parsed->item_v) ; -/*------------------------------------------------------------------------------ - * Show one description line. - */ -static void -uty_cli_describe_line(vty_io vio, int cmd_width, const char* cmd, - const char* str) -{ - if (str != NULL) - uty_cli_out (vio, " %-*s %s", cmd_width, cmd, str) ; - else - uty_cli_out (vio, " %-s", cmd) ; - uty_cli_out_newline(vio) ; + if (n_items == 0) + uty_cli_out_message(cli, "command not recognised") ; + + return n_items ; } ; -/*------------------------------------------------------------------------------ - * Prepare "vline" token array for command handler. - * - * For "help" (command completion/description), if the command line is empty, - * or ends in ' ', adds an empty token to the end of the token array. - */ -static vector -uty_cli_cmd_prepare(vty_io vio, int help) -{ - vector vline ; - vline = cmd_make_strvec(qs_term(&vio->cl)) ; - /* Note that if there is a vector of tokens, then there is at least one - * token, so can guarantee that vio->cl.len >= 1 ! - */ - if (help) - if ((vline == NULL) || isspace(*qs_chars_at(&vio->cl, vio->cl.len - 1))) - vline = cmd_add_to_strvec(vline, "") ; - return vline ; -} ; -/*============================================================================== - * VTY telnet stuff - */ - -#define TELNET_OPTION_DEBUG 0 /* 0 to turn off */ - -static const char* telnet_commands[256] = -{ - [tn_IAC ] = "IAC", - [tn_DONT ] = "DONT", - [tn_DO ] = "DO", - [tn_WONT ] = "WONT", - [tn_WILL ] = "WILL", - [tn_SB ] = "SB", - [tn_GA ] = "GA", - [tn_EL ] = "EL", - [tn_EC ] = "EC", - [tn_AYT ] = "AYT", - [tn_AO ] = "AO", - [tn_IP ] = "IP", - [tn_BREAK] = "BREAK", - [tn_DM ] = "DM", - [tn_NOP ] = "NOP", - [tn_SE ] = "SE", - [tn_EOR ] = "EOR", - [tn_ABORT] = "ABORT", - [tn_SUSP ] = "SUSP", - [tn_EOF ] = "EOF", -} ; - -static const char* telnet_options[256] = -{ - [to_BINARY] = "BINARY", /* 8-bit data path */ - [to_ECHO] = "ECHO", /* echo */ - [to_RCP] = "RCP", /* prepare to reconnect */ - [to_SGA] = "SGA", /* suppress go ahead */ - [to_NAMS] = "NAMS", /* approximate message size */ - [to_STATUS] = "STATUS", /* give status */ - [to_TM] = "TM", /* timing mark */ - [to_RCTE] = "RCTE", /* remote controlled tx and echo */ - [to_NAOL] = "NAOL", /* neg. about output line width */ - [to_NAOP] = "NAOP", /* neg. about output page size */ - [to_NAOCRD] = "NAOCRD", /* neg. about CR disposition */ - [to_NAOHTS] = "NAOHTS", /* neg. about horizontal tabstops */ - [to_NAOHTD] = "NAOHTD", /* neg. about horizontal tab disp. */ - [to_NAOFFD] = "NAOFFD", /* neg. about formfeed disposition */ - [to_NAOVTS] = "NAOVTS", /* neg. about vertical tab stops */ - [to_NAOVTD] = "NAOVTD", /* neg. about vertical tab disp. */ - [to_NAOLFD] = "NAOLFD", /* neg. about output LF disposition */ - [to_XASCII] = "XASCII", /* extended ascii character set */ - [to_LOGOUT] = "LOGOUT", /* force logout */ - [to_BM] = "BM", /* byte macro */ - [to_DET] = "DET", /* data entry terminal */ - [to_SUPDUP] = "SUPDUP", /* supdup protocol */ - [to_SUPDUPOUTPUT] = "SUPDUPOUTPUT",/* supdup output */ - [to_SNDLOC] = "SNDLOC", /* send location */ - [to_TTYPE] = "TTYPE", /* terminal type */ - [to_EOR] = "EOR", /* end or record */ - [to_TUID] = "TUID", /* TACACS user identification */ - [to_OUTMRK] = "OUTMRK", /* output marking */ - [to_TTYLOC] = "TTYLOC", /* terminal location number */ - [to_3270REGIME] = "3270REGIME", /* 3270 regime */ - [to_X3PAD] = "X3PAD", /* X.3 PAD */ - [to_NAWS] = "NAWS", /* window size */ - [to_TSPEED] = "TSPEED", /* terminal speed */ - [to_LFLOW] = "LFLOW", /* remote flow control */ - [to_LINEMODE] = "LINEMODE", /* Linemode option */ - [to_XDISPLOC] = "XDISPLOC", /* X Display Location */ - [to_OLD_ENVIRON] = "OLD_ENVIRON", /* Old - Environment variables */ - [to_AUTHENTICATION] = "AUTHENTICATION", /* Authenticate */ - [to_ENCRYPT] = "ENCRYPT", /* Encryption option */ - [to_NEW_ENVIRON] = "NEW_ENVIRON", /* New - Environment variables */ - [to_EXOPL] = "EXOPL", /* extended-options-list */ -} ; - -/*------------------------------------------------------------------------------ - * For debug. Put string or value as decimal. - */ + + + static void -uty_cli_out_dec(vty_io vio, const char* str, unsigned char u) +uty_cli_out_message(vty_cli cli, const char* msg) { - if (str != NULL) - uty_cli_out(vio, "%s ", str) ; - else - uty_cli_out(vio, "%d ", (int)u) ; + uty_cli_out_newline(cli) ; /* clears cli_drawn */ + uty_cli_write_s(cli, "% ") ; + uty_cli_write_s(cli, msg) ; + uty_cli_out_newline(cli) ; } ; -/*------------------------------------------------------------------------------ - * For debug. Put string or value as hex. - */ + + static void -uty_cli_out_hex(vty_io vio, const char* str, unsigned char u) +uty_cli_complete_keyword(vty_cli cli, const char* keyword) { - if (str != NULL) - uty_cli_out(vio, "%s ", str) ; - else - uty_cli_out(vio, "0x%02x ", (unsigned)u) ; + int pre, rep, ins, mov ; + + cmd_complete_keyword(cli->parsed, &pre, &rep, &ins, &mov) ; + + uty_cli_move(cli, pre) ; /* move to start of token */ + uty_cli_replace(cli, rep, keyword, strlen(keyword)) ; + + if (ins > 0) + uty_cli_insert(cli, " ", ins) ; + + uty_cli_move(cli, mov) ; + + return ; } ; /*------------------------------------------------------------------------------ - * Send telnet: "WILL TELOPT_ECHO" + * Show the command completions -- usually more than one. + * + * Generates a table of possible items, with fixed width entries, depending + * on the longest option. + * + * NB: the items will have been sorted before we get here. Inter alia, that + * ensures that any <cr> is shown last. */ static void -uty_will_echo (vty_io vio) +uty_cli_complete_list(vty_cli cli, vector item_v) { - unsigned char cmd[] = { tn_IAC, tn_WILL, to_ECHO }; - VTY_ASSERT_LOCKED() ; - uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); -} + uint i, len, n ; -/*------------------------------------------------------------------------------ - * Send telnet: "suppress Go-Ahead" - */ -static void -uty_will_suppress_go_ahead (vty_io vio) -{ - unsigned char cmd[] = { tn_IAC, tn_WILL, to_SGA }; - VTY_ASSERT_LOCKED() ; - uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); -} + len = 6 ; + for (i = 0 ; i < vector_length(item_v) ; ++i) + { + cmd_item item ; + uint sl ; -/*------------------------------------------------------------------------------ - * Send telnet: "don't use linemode" - */ -static void -uty_dont_linemode (vty_io vio) -{ - unsigned char cmd[] = { tn_IAC, tn_DONT, to_LINEMODE }; - VTY_ASSERT_LOCKED() ; - uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); -} + item = vector_get_item(item_v, i) ; -/*------------------------------------------------------------------------------ - * Send telnet: "Use window size" - */ -static void -uty_do_window_size (vty_io vio) -{ - unsigned char cmd[] = { tn_IAC, tn_DO, to_NAWS }; - VTY_ASSERT_LOCKED() ; - uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); -} + sl = strlen(item->str) ; + if (len < sl) + len = sl ; + } ; -/*------------------------------------------------------------------------------ - * Send telnet: "don't use lflow" -- not currently used - */ -static void -uty_dont_lflow_ahead (vty_io vio) -{ - unsigned char cmd[] = { tn_IAC, tn_DONT, to_LFLOW }; - VTY_ASSERT_LOCKED() ; - uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); -} + n = uty_cli_width_to_use(cli) / (len + 2) ; -/*------------------------------------------------------------------------------ - * The keystroke iac callback function. - * - * This deals with IAC sequences that should be dealt with as soon as they - * are read -- not stored in the keystroke stream for later processing. - */ -extern bool -uty_cli_iac_callback(keystroke_iac_callback_args) -{ - return uty_telnet_command((vty_io)context, stroke, true) ; + if (n == 0) + n = 1 ; + + for (i = 0 ; i < vector_length(item_v) ; ++i) + { + cmd_item item ; + item = vector_get_item(item_v, i) ; + + if ((i % n) == 0) + uty_cli_out_newline(cli) ; /* clears cli_drawn */ + + uty_cli_out(cli, "%-*s ", len, item->str) ; + } + uty_cli_out_newline(cli) ; } ; /*------------------------------------------------------------------------------ - * Process incoming Telnet Option(s) + * Show the command description. + * + * Generates lines of the form: * - * May be called during keystroke iac callback, or when processing CLI - * keystrokes. + * word description text * - * In particular: get telnet window size. + * Where the word field is adjusted to suit the longest word, and the + * description text is wrapped if required (if the width of the console is + * known) so that get: * - * Returns: true <=> dealt with, for: + * word description .................................. + * .............text * - * * telnet window size. + * NB: the items will have been sorted before we get here. Inter alia, that + * ensures that any <cr> is shown last. */ -static bool -uty_telnet_command(vty_io vio, keystroke stroke, bool callback) +static void +uty_cli_describe_list(vty_cli cli, vector item_v) { - uint8_t* p ; - uint8_t o ; - int left ; - bool dealt_with ; + uint i, str_width, doc_width, width ; - /* Echo to the other end if required */ - if (TELNET_OPTION_DEBUG) + /* Get width of the longest "word" */ + str_width = 0; + for (i = 0 ; i < vector_length(item_v) ; ++i) { - uty_cli_wipe(vio, 0) ; + cmd_item item ; + uint len ; + + item = vector_get_item(item_v, i) ; - p = stroke->buf ; - left = stroke->len ; + len = strlen(item->str) ; + if (item->str[0] == '.') + len--; - uty_cli_out_hex(vio, telnet_commands[tn_IAC], tn_IAC) ; + if (len > str_width) + str_width = len ; + } ; - if (left-- > 0) - uty_cli_out_dec(vio, telnet_commands[*p], *p) ; - ++p ; + /* Set width of description string. + * + * Format is: + * + * __wo.....rd__description... + * ...continues + * + * The width of the word part has been established above as the width of the + * widest word. + * + * There are two spaces on either side of the word, so we here calculate the + * width of the description part + */ + width = uty_cli_width_to_use(cli) ; - if (left-- > 0) - uty_cli_out_dec(vio, telnet_options[*p], *p) ; - ++p ; + if (width > ((str_width + 6) + 20)) + doc_width = width - (str_width + 6) ; + else + doc_width = 0 ; - if (left > 0) - { - while(left-- > 0) - uty_cli_out_hex(vio, NULL, *p++) ; + /* Print out description. */ + for (i = 0 ; i < vector_length(item_v) ; ++i) + { + cmd_item item ; + const char* str, * dp, * ep ; + + item = vector_get_item(item_v, i) ; - if (stroke->flags & kf_truncated) - uty_cli_out(vio, "... ") ; + str = item->str[0] == '.' ? item->str + 1 : item->str; + dp = item->doc ; + ep = dp + strlen(dp) ; - if (!(stroke->flags & kf_broken)) + /* If have a sensible description width */ + if (doc_width > 20) + { + while ((ep - dp) > doc_width) { - uty_cli_out_hex(vio, telnet_commands[tn_IAC], tn_IAC) ; - uty_cli_out_hex(vio, telnet_commands[tn_SE], tn_SE) ; - } - } ; + const char* np ; - if (stroke->flags & kf_broken) - uty_cli_out (vio, "BROKEN") ; + np = dp + doc_width ; /* target next position */ - uty_cli_out (vio, "\r\n") ; - } ; + while ((np > dp) && (*np != ' ')) + --np ; /* seek back to ' ' */ - /* Process the telnet command */ - dealt_with = false ; + if (np == dp) /* if no space... */ + np = dp + doc_width ; /* ...force break */ - if (stroke->flags != 0) - return dealt_with ; /* go no further if broken */ + uty_cli_describe_line(cli, str_width, str, dp, np - dp) ; - p = stroke->buf ; - left = stroke->len ; + str = ""; /* for 2nd and subsequent lines */ - passert(left >= 1) ; /* must be if not broken ! */ - passert(stroke->value == *p) ; /* or something is wrong */ + dp = np ; /* step past what just wrote */ + while (*dp == ' ') + ++dp ; /* skip spaces */ + } ; + } ; - ++p ; /* step past X of IAC X */ - --left ; + uty_cli_describe_line(cli, str_width, str, dp, ep - dp) ; + } ; - /* Decode the one command that is interesting -- "NAWS" */ - switch (stroke->value) - { - case tn_SB: - passert(left > 0) ; /* or parser failed */ + uty_cli_out_newline(cli) ; +} ; - o = *p++ ; /* the option byte */ - --left ; - switch(o) - { - case to_NAWS: - if (left != 4) - { - uzlog(NULL, LOG_WARNING, - "RFC 1073 violation detected: telnet NAWS option " - "should send %d characters, but we received %d", - (3 + 4 + 2), (3 + left + 2)) ; - } - else - { - vio->width = *p++ << 8 ; - vio->width += *p++ ; - vio->height = *p++ << 8 ; - vio->height += *p ; - - if (TELNET_OPTION_DEBUG) - uty_cli_out(vio, "TELNET NAWS window size received: " - "width %d, height %d%s", - vio->width, vio->height, telnet_newline) ; - uty_set_height(vio) ; - - dealt_with = true ; - } ; - break ; +/*------------------------------------------------------------------------------ + * Show one description line. + */ +static void +uty_cli_describe_line(vty_cli cli, uint str_width, const char* str, + const char* doc, uint len) +{ + if ((*str == '\0') && (len == 0)) + return ; /* quit if nothing to say */ - default: /* no other IAC SB <option> */ - break ; - } ; - break ; + uty_cli_out_newline(cli) ; - default: /* no other IAC X */ - break ; - } ; + if (len == 0) + uty_cli_out(cli, " %s", str) ; /* left justify */ + else + { + uty_cli_out(cli, " %-*s ", str_width, str) ; + uty_cli_write(cli, doc, len) ; + } ; +} ; - return dealt_with ; +/*------------------------------------------------------------------------------ + * Return the actual or assumed console width. + * + * If we know the width we use it. Otherwise just assume something reasonable. + */ +static uint +uty_cli_width_to_use(vty_cli cli) +{ + return (cli->width == 0) ? 60 : cli->width ; } ; diff --git a/lib/vty_cli.h b/lib/vty_cli.h index f532616a..94e502f0 100644 --- a/lib/vty_cli.h +++ b/lib/vty_cli.h @@ -24,26 +24,166 @@ #ifndef _ZEBRA_VTY_CLI_H #define _ZEBRA_VTY_CLI_H +#include "misc.h" +#include "vargs.h" + +#include "command_local.h" #include "vty_io.h" +#include "vty_io_basic.h" +#include "vio_fifo.h" +#include "vio_lines.h" +#include "qstring.h" #include "keystroke.h" -extern void uty_cli_init(vty_io vio) ; -extern enum vty_readiness uty_cli_start(vty_io vio) ; -extern void uty_cli_close(vty_io vio) ; +/*------------------------------------------------------------------------------ + * The vty_cli structure pointed to by the vty_io structure. + */ +typedef struct vty_cli* vty_cli ; + +struct vty_cli +{ + vio_vf vf ; /* parent */ + + /* History of commands */ + vector_t hist ; /* embedded */ + int hp ; /* current place in history */ + int h_now ; /* the present moment */ + + /* Window width/height as reported by Telnet. 0 => unknown */ + int width; + int height; + + /* Configure lines. */ + int lines; + bool lines_set ; /* true <=> explicitly set */ + + /* Terminal monitor. */ + bool monitor ; + bool monitor_busy ; + + /* Terminal timeout in seconds -- 0 => none */ + vty_timer_time v_timeout ; + + /* The incoming stuff */ + keystroke_stream key_stream ; + + /* drawn <=> the current prompt and user input occupy the current + * line on the screen. + * + * dirty <=> the last command output did not end with a newline. + * + * If drawn is true, the following are valid: + * + * prompt_len -- the length of the prompt part. + * (will be the "--more--" prompt in cli_more_wait) + * + * extra_len -- the length of any ^X at the cursor position + * (for when blocked waiting for queued command) + * + * echo_suppress -- the user part of the command line is suppressed + * + * NB: echo_suppress is only used for password entry. + */ + bool drawn ; + bool dirty ; + + int prompt_len ; + int extra_len ; + + bool echo_suppress ; + + /* "cache" for prompt -- when node or host name changes, prompt does */ + node_type_t prompt_node ; + name_gen_t prompt_gen ; + qstring_t prompt_for_node ; + + /* password failure count -- main login or enable login. */ + int password_fail ; + + /* State of the CLI + * + * in_progress -- command dispatched + * blocked -- blocked until current command completes + * out_active -- contents of the command FIFO are being written away + * + * more_wait -- is in "--more--" wait state + * more_active -- more_wait and waiting for "--more--" prompt to be + * written away. + */ + bool in_progress ; + bool blocked ; + bool out_active ; + + bool more_wait ; + bool more_active ; + + /* This is used to control command output, so that each write_ready event + * generates at most one tranche of output. + */ + bool out_done ; + + /* This is set only if the "--more--" handling is enabled */ + bool more_enabled ; + + /* Command Line(s) + * + * node -- the node that the CLI is in. This may be some way behind + * the VTY, but is updated when the CLI level command completes. + * + * to_do -- when current command being prepared is completed (by + * CR/LF or otherwise) this says what there now is to be done. + * + * cl -- current command line being prepared. + * + * clx -- current command line being executed. + * + * NB: during command execution vty->buf is set to point at the '\0' + * terminated current command line being executed. + */ + node_type_t node ; + + cmd_do_t to_do ; + + qstring cl ; + qstring clx ; + + cmd_parsed_t parsed ; /* embedded */ + + /* CLI line buffering */ + vio_fifo_t cbuf ; /* embedded */ + + /* CLI line control for command output & "--more--" stuff */ + vio_line_control_t olc ; /* embedded */ +} ; + +extern vty_cli uty_cli_new(vio_vf vf) ; +extern void uty_cli_start(vty_cli cli, node_type_t node) ; + +extern vty_cli uty_cli_close(vty_cli cli, bool final) ; + +extern cmd_return_code_t uty_cli_auth(vty_cli) ; +extern void uty_cli_hist_show(vty_cli cli) ; +extern ulen uty_cli_prompt_len(vty_cli cli) ; -extern enum vty_readiness uty_cli(vty_io vio) ; -extern keystroke_callback uty_cli_iac_callback ; +extern vty_readiness_t uty_cli(vty_cli cli) ; +extern void uty_cli_out_push(vty_cli cli) ; +extern void uty_cli_done_command(vty_cli cli, node_type_t node) ; -extern void uty_cli_hist_add (vty_io vio, const char* cmd_line) ; -extern void uty_cli_enter_more_wait(vty_io vio) ; -extern void uty_cli_exit_more_wait(vty_io vio) ; +extern void uty_cli_out(vty_cli cli, const char *format, ...) + PRINTF_ATTRIBUTE(2, 3) ; +extern void uty_cli_out_newline(vty_cli cli) ; +extern void uty_cli_out_clear(vty_cli cli) ; +extern void uty_cli_write(vty_cli cli, const char *this, int len) ; +extern void uty_cli_wipe(vty_cli cli, int len) ; -extern void uty_free_host_name(void) ; -extern void uty_check_host_name(void) ; +extern void uty_cli_set_lines(vty_cli cli, int lines, bool explicit) ; +extern void uty_cli_set_window(vty_cli cli, int width, int height) ; +extern void uty_cli_enter_more_wait(vty_cli cli) ; +extern void uty_cli_exit_more_wait(vty_cli cli) ; -extern bool uty_cli_draw_if_required(vty_io vio) ; +extern bool uty_cli_draw_if_required(vty_cli cli) ; -extern void uty_cli_pre_monitor(vty_io vio, size_t len) ; -extern int uty_cli_post_monitor(vty_io vio, const char* buf, size_t len) ; +extern void uty_cli_pre_monitor(vty_cli cli, size_t len) ; +extern int uty_cli_post_monitor(vty_cli cli, const char* buf, size_t len) ; #endif /* _ZEBRA_VTY_CLI_H */ diff --git a/lib/vty_io.c b/lib/vty_io.c index c116e6b9..4b50c526 100644 --- a/lib/vty_io.c +++ b/lib/vty_io.c @@ -24,10 +24,14 @@ #include "vty.h" #include "vty_io.h" #include "vty_io_term.h" +#include "vty_io_file.h" #include "vty_cli.h" +#include "vty_command.h" #include "qstring.h" #include "keystroke.h" #include "list_util.h" +#include "command_parse.h" +#include "command_execute.h" #include "memory.h" @@ -45,20 +49,18 @@ /*============================================================================== * Basic output to VTY. - * - * */ /*------------------------------------------------------------------------------ - * UTY output function -- cf fprintf + * VTY output -- cf fprintf ! Same as vty_out, less the VTY_LOCK(). * - * NB: this is NOT suppressed by ! vty->output_enabled + * This is for command output, which may later be suppressed * * Returns: >= 0 => OK * < 0 => failed (see errno) */ extern int -uty_output(struct vty *vty, const char *format, ...) +uty_out(vty_io vio, const char *format, ...) { int ret ; va_list args ; @@ -66,122 +68,10 @@ uty_output(struct vty *vty, const char *format, ...) VTY_ASSERT_LOCKED() ; va_start (args, format) ; - ret = uty_vprintf(vty, format, args) ; + ret = uty_vprintf(vio, format, args) ; va_end (args) ; return ret ; -} - -/*------------------------------------------------------------------------------ - * UTY reflect command line, if not already reflected - * - * NB: this is NOT suppressed by ! vty->output_enabled - * - * Returns: >= 0 => OK - * < 0 => failed (see errno) - */ -extern int -uty_reflect(struct vty *vty) -{ - int ret ; - - if (!vty->reflected) - ret = uty_output(vty, "%s\n", vty->buf) ; - else - ret = 0 ; - - vty->reflected = true ; - - return ret ; -} ; - -/*------------------------------------------------------------------------------ - * VTY output function -- cf vfprintf - * - * Returns: >= 0 => OK - * < 0 => failed (see errno) - * - * NB: for VTY_TERM and for VTY_SHELL_SERV -- this is command output: - * - * * MAY NOT do any command output if !cmd_enabled - * - * * first, the life of a vty is not guaranteed unless cmd_in_progress, - * so should not attempt to use a vty anywhere other than command - * execution. - * - * * second, cmd_out_enabled is false most of the time, and is only - * set true when a command completes, and it is time to write away - * the results. - * - * * all output is placed in the vio->cmd_obuf. When the command completes, - * the contents of the cmd_obuf will be written away -- subject to line - * control. - * - * * output is discarded if the vty is no longer write_open - */ -extern int -uty_vprintf(struct vty *vty, const char *format, va_list args) -{ - vio_vf vf ; - int ret ; - - VTY_ASSERT_LOCKED() ; - - vf = vty->vio->vout ; - - switch (vf->vout_type) - { - case VOUT_NONE: - ret = 0 ; - break ; - - case VOUT_TERM: - ret = uty_term_vprintf(vf, format, args) ; - break ; - - case VOUT_SHELL: - ret = uty_shell_vprintf(vf, format, args) ; - break ; - - case VOUT_FILE: - ret = uty_file_vprintf(vf, format, args) ; - break ; - - case VOUT_PIPE: - ret = uty_pipe_vprintf(vf, format, args) ; - break ; - - case VOUT_STDOUT: - ret = uty_stdout_vprintf(vf, format, args) ; - break ; - - case VOUT_STDERR: - ret = uty_stderr_vprintf(vf, format, args) ; - break ; - - default: - zabort("impossible VTY Output type") ; - } ; - - return ret ; -} ; - -/*------------------------------------------------------------------------------ - * Clear the contents of the command output FIFO etc. - * - * NB: does not change any of the cli_blocked/cmd_in_progress/cli_wait_more/etc - * flags -- competent parties must deal with those - */ -extern void -uty_out_clear(vty_io vio) -{ - VTY_ASSERT_LOCKED() ; - - if (vio->vout != NULL) - { - vio_fifo_clear(vio->vout->obuf) ; - vio_lc_clear(vio->vout->olc) ; - } ; } ; /*============================================================================== @@ -238,7 +128,7 @@ uty_watch_dog_stop(void) static vty_timer_time uty_watch_dog_bark(vio_timer_t* timer, void* info) { - uty_check_host_name() ; /* check for host name change */ + cmd_host_name(true) ; /* check for host name change */ uty_death_watch_scan(false) ; /* scan the death-watch list */ @@ -248,11 +138,12 @@ uty_watch_dog_bark(vio_timer_t* timer, void* info) /*------------------------------------------------------------------------------ * Scan the death watch list. * - * A vty may finally be freed if it is closed and there is no command in - * progress. + * A vty can finally be freed if it is closed and there is no command running. + * + * At curtains the command running state is forced off. */ static bool -uty_death_watch_scan(bool final) +uty_death_watch_scan(bool curtains) { vty_io vio ; vty_io next ; @@ -263,21 +154,19 @@ uty_death_watch_scan(bool final) vio = next ; next = sdl_next(vio, vio_list) ; - if (final && !vio->closed) - - - if (vio->closed) + /* If this is curtains, override cmd_running ! */ + if (curtains) + vio->cmd_running = false ; - - if (vio->closed && !vio->cmd_in_progress) + if (uty_close(vio, curtains, NULL)) { - uty_close(vio) ; /* closes again to ensure that all buffers - are released. */ + vty vty = vio->vty ; sdl_del(vio_death_watch, vio, vio_list) ; - XFREE(MTYPE_VTY, vio->vty) ; - XFREE(MTYPE_VTY, vio) ; + cmd_exec_free(vty->exec) ; + XFREE(MTYPE_VTY, vty->vio) ; + XFREE(MTYPE_VTY, vty) ; } ; } ; @@ -288,189 +177,128 @@ uty_death_watch_scan(bool final) * Prototypes. */ -static void uty_vf_half_close(vio_vf vf) ; -static void uty_vf_close(vio_vf vf) ; +static void uty_vf_read_close(vio_vf vf) ; +static bool uty_vf_write_close(vio_vf vf, bool final) ; +static vio_vf uty_vf_free(vio_vf vf) ; /*============================================================================== * Creation and destruction of VTY objects */ /*------------------------------------------------------------------------------ - * Allocate new vty struct - * - * VTY_TERMINAL a telnet terminal server - * - * Must be in cli thread. - * - * Requires fd = socket for telnet terminal. - * - * VTY_SHELL_SERVER a vty_shell server - * - * Must be in cli thread. + * Allocate new vty structure, including empty vty_io and empty execution + * structures. * - * Requires fd = socket for talking to the VTY_SHELL_CLIENT + * Caller must complete the initialisation of the vty_io, which means: * - * VTY_SHELL_CLIENT a vty_shell client + * * constructing a suitable vio_vf and doing uty_vin_open() to set the + * vin_base. * - * VTY_CONFIG_READ configuration file reader + * All vty_io MUST have a vin_base, even if it is /dev/null. * - * Requires fd = file descriptor for reading config file + * * constructing a suitable vio_vf and doing uty_vout_open() to set the + * vout_base. * - * VTY_STDOUT stdout - * VTY_STDERR stderr + * All vty_io MUST have a vout_base, even if it is /dev/null. * + * * setting the vio->obuf to the vout_base obuf... TODO ??? * + * * setting vio->cli, if required. * - * - * - * - * - * - * Allocates and initialises basic vty and vty_io structures, setting the - * given type. - * - * Note that where is not setting up a vty_file, this *may* be called from - * any thread. - * - * Returns: new vty + * Caller must also set the initial vty->node, if any. */ -extern struct vty * -uty_new(enum vty_type type, int fd) +extern vty +uty_new(vty_type_t type) { vty vty ; vty_io vio ; + bool execution ; + VTY_ASSERT_LOCKED() ; /* Basic allocation */ - vty = XCALLOC (MTYPE_VTY, sizeof (struct vty)) ; - vio = XCALLOC (MTYPE_VTY, sizeof (struct vty_io)) ; - - vty->vio = vio ; - vio->vty = vty ; - - /* Zeroising the vty_io structure has set: - * - * name = NULL -- no name, yet - * - * vio_list both pointers NULL - * mon_list both pointers NULL - * - * half_closed = 0 -- NOT half closed (important !) - * closed = 0 -- NOT closed (important !) - * close_reason = NULL -- no reason, yet + vty = XCALLOC(MTYPE_VTY, sizeof(struct vty)) ; + /* Zeroising the vty structure has set: * - * real_type = 0 -- not material - * file_fd = 0 -- not material - * file_error = 0 -- not material + * type = X -- set to actual type, below * - * key_stream = NULL -- no key stream (always empty, at EOF) + * node = NULL_NODE -- set to something sensible elsewhere * - * cli_drawn = 0 -- not drawn - * cli_dirty = 0 -- not dirty - * cli_prompt_len = 0 ) - * cli_extra_len = 0 ) not material - * cli_echo_suppress = 0 ) + * index = NULL -- nothing, yet + * index_sub = NULL -- nothing, yet * - * cli_prompt_node = 0 -- not material - * cli_prompt_set = 0 -- so prompt needs to be constructed + * config = false -- not in configure mode * - * cli_blocked = 0 -- not blocked - * cmd_in_progress = 0 -- no command in progress - * cmd_out_enabled = 0 -- command output is disabled - * cli_wait_more = 0 -- not waiting for response to "--more--" + * execution = X -- set below + * vio = X -- set below + */ + confirm(NULL_NODE == 0) ; + confirm(QSTRING_INIT_ALL_ZEROS) ; + + vty->type = type ; + + vio = XCALLOC(MTYPE_VTY, sizeof(struct vty_io)) ; + + /* Zeroising the vty_io structure has set: * - * cli_more_enabled = 0 -- not enabled for "--more--" + * vty = X -- set to point to parent vty, below * - * cmd_out_done = 0 -- not material + * name = NULL -- no name, yet TODO ??? * - * cli_do = 0 == cli_do_nothing + * vin = NULL -- empty input stack + * vin_base = NULL -- empty input stack + * vin_depth = 0 -- no stacked vin's, yet * - * cmd_lc = NULL -- no line control + * vout = NULL -- empty output stack + * vout_base = NULL -- empty output stack + * vout_depth = 0 -- no stacked vout's, yet * - * fail = 0 -- no login failures yet + * vout_closing = NULL -- nothing closing yet * - * hist = empty vector - * hp = 0 -- at the beginning - * hindex = 0 -- the beginning + * vio_list = NULLs -- not on the vio_list, yet + * mon_list = NULLs -- not on the monitors list * - * width = 0 -- unknown console width - * height = 0 -- unknown console height + * blocking = X -- set below: false unless VTY_CONFIG_READ + * cmd_running = false -- no commands running, yet * - * lines = 0 -- no limit - * lines_set = 0 -- no explicit setting + * state = X -- set vf_open, below. * - * monitor = 0 -- not a monitor - * monitor_busy = 0 -- not a busy monitor + * close_reason = NULL -- not closed, yet * - * config = 0 -- not holder of "config" mode + * obuf = NULL -- no output buffer, yet */ - confirm(cli_do_nothing == 0) ; - confirm(AUTH_NODE == 0) ; /* default node type */ - - vty->type = type ; - + vty->vio = vio ; + vio->vty = vty ; + vio->blocking = (type == VTY_CONFIG_READ) ; + vio->state = vf_open ; + /* Create and initialise the command execution environment (if any) */ + execution = true ; - /* Zeroising the vty structure has set: - * - * node = 0 TODO: something better for node value ???? - * buf = NULL -- no command line, yet - * parsed = NULL -- no parsed command, yet - * lineno = 0 -- nothing read, yet - * index = NULL -- nothing, yet - * index_sub = NULL -- nothing, yet - */ - - /* If this is a VTY_TERM or a VTY_SHELL, place */ - switch (type) + switch(type) { - case VTY_TERMINAL: /* Require fd -- Telnet session */ - VTY_ASSERT_CLI_THREAD() ; - assert(fd >= 0) ; - - uty_term_new(vio, fd) ; - break ; - - case VTY_SHELL_SERVER: /* Require fd -- Unix socket */ - VTY_ASSERT_CLI_THREAD() ; - assert(fd >= 0) ; - - - break ; - - case VTY_CONFIG_READ: /* Require fd -- file to read */ - assert(fd >= 0) ; + case VTY_TERMINAL: + case VTY_SHELL_SERVER: + case VTY_SHELL_CLIENT: + case VTY_CONFIG_READ: + execution = true ; break ; case VTY_STDOUT: case VTY_STDERR: - case VTY_SHELL_CLIENT: - fd = -1 ; /* No fd -- output to stdout/stderr */ + execution = false ; break ; default: - zabort("unknown VTY type") ; + zabort("unknown vty type") ; } ; - - - - /* Make sure all buffers etc. are initialised clean and empty. - * - * Note that no buffers are actually allocated at this stage. - */ - qs_init_new(&vio->cli_prompt_for_node, 0) ; - - qs_init_new(&vio->cl, 0) ; - qs_init_new(&vio->clx, 0) ; - - vio_fifo_init_new(&vio->cli_obuf, 2 * 1024) ; /* allocate in 2K lumps */ - - vio_fifo_init_new(&vio->cmd_obuf, 8 * 1024) ; + if (execution) + vty->exec = cmd_exec_new(vty) ; /* Place on list of known vio/vty */ sdl_push(vio_list_base, vio, vio_list) ; @@ -483,73 +311,127 @@ uty_new(enum vty_type type, int fd) * * Sets the vf->vin_type and set vf->read_open. * - * Sets the read ready action and the read timer timeout action. + * Initialises an input buffer if required, and sets line_complete and + * line_step so that first attempt to fetch a line will give line 1. * - * NB: may add a VIN_NONE *only* as the first vin item. + * Sets the read ready action and the read timer timeout action. * - * Can have a write only VTY_XXX object, which requires a vin entry so that - * do not have to everywhere deal with NULL vio_vf pointers. + * NB: a uty_cmd_prepare() is required before command processing can continue. * - * But for subsequent write only vio_vf objects, must not add to the vin - * stack. + * That is not done here because the vin state may not be complete (in + * particular the vin->parse_type and vin->reflect_enabled !). */ extern void -uty_vin_add(vty_io vio, vio_vf vf, vio_in_type_t type, - vio_fd_action* read_action, vio_timer_action* read_timer_action) +uty_vin_open(vty_io vio, vio_vf vf, vio_in_type_t type, + vio_vfd_action* read_action, + vio_timer_action* read_timer_action, + usize ibuf_size) { vf->vin_type = type ; - vf->read_open = true ; + vf->vin_state = vf_open ; - vio_fd_set_read_action(vf->vfd, read_action) ; - vio_fd_set_read_timeout_action(vf->vfd, read_timer_action) ; + if (type < VIN_SPECIALS) + { + vio_vfd_set_read_action(vf->vfd, read_action) ; + vio_vfd_set_read_timeout_action(vf->vfd, read_timer_action) ; + } ; ssl_push(vio->vin, vf, vin_next) ; if (vio->vin_base == NULL) - vio->vin_base = vf ; + { + assert(vio->vin_depth == 0) ; + vio->vin_base = vf ; + } else - assert(type != VIN_NONE) ; + { + assert(type != VIN_NONE) ; + ++vio->vin_depth ; + } ; + + if (ibuf_size != 0) + { + vf->ibuf = vio_fifo_init_new(NULL, ibuf_size) ; + + vf->line_complete = true ; + vf->line_step = 1 ; + } ; } ; /*------------------------------------------------------------------------------ - * Add a new vf to the vio->vout stack, and set write stuff. + * Push a new vf to the vio->vout stack, and set write stuff. * * Sets the vf->vout_type and set vf->write_open. * * Sets the write ready action and the write timer timeout action. * - * NB: may add a VOUT_NONE *only* as the first vout item. + * Initialises an output buffer and sets an end_mark. + * + * NB: VOUT_DEV_NULL, VOUT_STDOUT and VOUT_STDERR are special. + * + * The write_action and the write_timer_action are ignored. * - * Can have a read only VTY_XXX object, which requires a vout entry so that - * do not have to everywhere deal with NULL vio_vf pointers. + * All actual I/O to these outputs is direct, blocking and via standard + * I/O -- except VOUT_DEV_NULL where all I/O is discarded. * - * But for subsequent read only vio_vf objects, must not add to the vout - * stack. + * NB: all outputs are set up with an obuf, so all output is collected, even + * if it is later to be discarded. + * + * NB: a uty_cmd_prepare() is required before command processing can continue. + * + * That is not done here because the vout state may not be complete (in + * particular the vout->out_enabled !). */ extern void -uty_vout_add(vty_io vio, vio_vf vf, vio_out_type_t type, - vio_fd_action* write_action, vio_timer_action* write_timer_action) +uty_vout_open(vty_io vio, vio_vf vf, vio_out_type_t type, + vio_vfd_action* write_action, + vio_timer_action* write_timer_action, + usize obuf_size) { vf->vout_type = type ; - vf->write_open = true ; + vf->vout_state = vf_open ; - vio_fd_set_write_action(vf->vfd, write_action) ; - vio_fd_set_write_timeout_action(vf->vfd, write_timer_action) ; + if (type < VOUT_SPECIALS) + { + vio_vfd_set_write_action(vf->vfd, write_action) ; + vio_vfd_set_write_timeout_action(vf->vfd, write_timer_action) ; + } ; ssl_push(vio->vout, vf, vout_next) ; if (vio->vout_base == NULL) - vio->vout_base = vf ; + { + assert(vio->vout_depth == 0) ; + vio->vout_base = vf ; + } else - assert(type != VOUT_NONE) ; -} ; - - - + { + assert(type != VOUT_NONE) ; + ++vio->vout_depth ; + } ; + vf->obuf = vio_fifo_init_new(NULL, obuf_size) ; + vio_fifo_set_end_mark(vf->obuf) ; + vio->obuf = vio->vout->obuf ; +} ; +/*------------------------------------------------------------------------------ + * Set timeout value. + * + * This is only ever called when a command (eg exec-timeout) sets a new + * time out value -- which applies only to VIN_TERM and VTY_VTYSH. + */ +extern void +uty_set_timeout(vty_io vio, vty_timer_time timeout) +{ + vio_in_type_t vt ; + VTY_ASSERT_LOCKED() ; + vt = vio->vin_base->vin_type ; + if ((vt == VIN_TERM) || (vt == VIN_VTYSH)) + uty_vf_set_read_timeout(vio->vin_base, timeout) ; +} ; /*------------------------------------------------------------------------------ * Set/Clear "monitor" state: @@ -561,10 +443,10 @@ extern void uty_set_monitor(vty_io vio, bool on) { VTY_ASSERT_LOCKED() ; - +#if 0 if (on && !vio->monitor) { - if ((vio->vty->type == VTY_TERMINAL) && !vio->half_closed) + if ((vio->vty->type == VTY_TERMINAL) && !vio->closing) { vio->monitor = 1 ; sdl_push(vio_monitors_base, vio, mon_list) ; @@ -575,267 +457,278 @@ uty_set_monitor(vty_io vio, bool on) vio->monitor = 0 ; sdl_del(vio_monitors_base, vio, mon_list) ; } +#endif } ; /*------------------------------------------------------------------------------ - * Return "name" of VTY + * Return "name" of VTY. * - * For VTY_TERM this is the IP address of the far end of the telnet connection. + * The name of the base vin, or (failing that) the base vout. */ extern const char* uty_get_name(vty_io vio) { - return (vio->name != NULL) ? vio->name : "?" ; + const char* name ; + + name = vio->vin_base->name ; + if (name == NULL) + name = vio->vout_base->name ; + + return (name != NULL) ? name : "?" ; } ; /*------------------------------------------------------------------------------ - * Close all the readers. + * Close all vin excluding the vin_base. * - * Half-close everything on the vin stack. Empties the vin stack down to the - * base entry. Discards any read-only vio_vf (except for last vin entry). + * This is done on close and when there is a command error. + */ +extern void +uty_vin_close_stack(vty_io vio) +{ + while (vio->vin != vio->vin_base) + uty_vin_close(vio) ; +} ; + +/*------------------------------------------------------------------------------ + * Close all vout except for the vout_base. * - * VIO is placed on death watch, and will stay there until: + * This is done on close and when there is a command error. * - * * outstanding commands complete. + * Should only be "final" on a call from the watch-dog ! + */ +extern void +uty_vout_close_stack(vty_io vio, bool final) +{ + while (vio->vout != vio->vout_base) + uty_vout_close(vio, final) ; +} ; + +/*------------------------------------------------------------------------------ + * Close VTY -- may be called any number of times ! * - * * outstanding output completes, or times out, or program terminates. + * If no reason for the close has already been set, sets the given reason. + * The close reason is reported at the end of the output to the vout_base. * - * For VTY_TERMINAL (must be in CLI thread): + * Once a VTY has been closed, it will provide no further input. However, until + * "final" close, it will continue to output anything which is pending at the + * time of the close, which includes: (a) anything in the output buffer, + * (b) any further output from a then running command and (c) anything that an + * out_pipe may be in the process of returning. * - * * shut the socket for reading - * * discard all buffered input, setting it to "EOF" - * * turns off any monitor status ! - * * drop down to RESTRICTED_NODE + * The main complication is that in a multi-threaded world there may be a + * vio->cmd_running. The close shuts down everything on the input stack, so + * any active command loop will come to a halt as soon as the current command + * does complete. The output stack is preserved, but will be closed on + * completion of the command. * - * For VTY_SHELL_SERVER (must be in CLI thread): + * Closing a VTY places it on the death-watch list. Once any pending output + * has been dealt with and any cmd_running has completed, then the death-watch + * will apply the coup de grace. * - * * shut the socket for reading - * * discard all buffered input - * * drop down to RESTRICTED_NODE + * Getting the death-watch to finally close the VTY also allows the vty to be + * closed from somewhere deep (e.g. when there is an I/O error) without + * destroying the VTY and upsetting code that might not realise the VTY has + * vanished. * - * If no reason for the close has already been set, sets the given reason. + * The close sequence is: + * + * 1. if a close reason has not already been set, set any given one. + * + * 2. if not already closing -- ie this is the first call of uty_close() + * + * a. transfer to death-watch list & set closing. + * + * b. turn off any monitor state + * + * c. close down the input/read side completely. + * + * Empties the vin stack down to vin_base, closing all input. + * + * Close the vin_base. The vin_base is left !read_open. + * + * All read-only vio_vf (except the vin_base) are freed. + * + * Note that everything is read closed before anything is write + * closed. + * + * A vio_vf is read closed once and only once. + * + * The vin_base is closed only by this function. Until the final + * uty_close, there is always at least the vin_base, even if it is + * read_closed. + * + * 3. try to close everything on the vout_closing list. + * + * even if cmd_running * - * If already half-closed, does nothing else. * - * Returns true <=> first half close. */ -static bool -uty_do_half_close(vty_io vio, const char* reason) +extern bool +uty_close(vty_io vio, bool final, qstring reason) { - vio_vf vf ; + vio_vf vf_next ; VTY_ASSERT_LOCKED() ; - if ((vio->close_reason == NULL) && (reason != NULL)) - vio->close_reason = XSTRDUP(MTYPE_TMP, reason) ; + /* quit if already closed */ + if (vio->state == vf_closed) + return true ; - if (vio->half_closed) - return false ; + /* Set the close reason, if not already set. */ + if (reason != NULL) + { + if (vio->close_reason == NULL) + vio->close_reason = reason ; + else + qs_reset(reason, free_it) ; + } ; - /* Half close everything on the vin stack. - * - * Leave stack with just the base entry (closed for read). + /* If not already closing, set closing and transfer to the death watch + * list -- turn off any "monitor" status immediately. */ - while (1) + if (vio->state == vf_open) { - vf = vio->vin ; /* Current first on list */ + vio->state = vf_closing ; - uty_vf_half_close(vf) ; /* fd level half close etc. */ + /* Transfer to the death-watch */ + sdl_del(vio_list_base, vio, vio_list) ; + sdl_push(vio_death_watch, vio, vio_list) ; - switch(vf->vin_type) /* tidy up each type */ - { - case VIN_NONE: - break ; + /* TODO turn off any "monitor" IMMEDIATELY. */ - case VIN_TERM: - uty_term_half_close(vf) ; - uty_cli_close(vio) ; /* tell the CLI to stop */ - break ; + /* Flush the vin stack. + * + * Where possible this will revoke commands bring command processing to + * as sudden a halt as possible -- though may still be processing + * commands. + */ + uty_vin_close_stack(vio) ; + assert(vio->vin == vio->vin_base) ; + uty_vf_read_close(vio->vin) ; + } ; + + /* If there is anything on the vout_closing list, give it a shove in case + * that manages to close anything -- which it will do if "final". + */ + vf_next = vio->vout_closing ; + while (vf_next != NULL) + { + vio_vf vf = vf_next ; + vf_next = ssl_next(vf, vout_next) ; - case VIN_SHELL: - break ; + uty_vf_write_close(vf, final) ; /* removes from vout_closing if + is now completely closed. */ + } ; - case VIN_FILE: - break ; + /* If is vio->cmd_running, this is as far as we can go this time. */ + if (vio->cmd_running) + return false ; - case VIN_PIPE: - break ; + /* Make sure no longer holding the config symbol of power */ + uty_config_unlock(vio->vty, NULL_NODE) ; - case VIN_CONFIG: - break ; + /* Flush the vout stack. + * + * If cannot completely close a vf, places it on the vout_closing list, + * except for the vout_base. + */ + uty_vout_close_stack(vio, final) ; + assert(vio->vout == vio->vout_base) ; + uty_vf_write_close(vio->vout, final) ; - default: - zabort("unknown VIN type") ; - } ; + /* See if have now successfully closed everything. */ + if ((vio->vout_closing != NULL) || (vio->vout_base->vout_state != vf_closed)) + return false ; /* something is still open */ - /* Finished if half closed the last on the list */ - if (vf == vio->vin_base) - break ; + /* Empty everything out of the vio and return the good news. + * + * NB: retains vio->vty & the vio->vio_list for death-watch. + */ + assert((vio->vin == vio->vin_base) && (vio->vin_depth == 0)) ; - /* Hack off head of list. If it is read-only, close & free. */ - ssl_del_head(vio->vin, vin_next) ; + if (vio->vin != vio->vout) + vio->vin = uty_vf_free(vio->vin) ; + else + vio->vin = NULL ; + vio->vin_base = NULL ; - if (vf->vout_type == VOUT_NONE) - uty_vf_close(vf) ; - } ; + assert((vio->vout == vio->vout_base) && (vio->vout_depth == 0)) ; - /* Make sure no longer holding the config symbol of power */ - uty_config_unlock(vio->vty, RESTRICTED_NODE) ; + vio->vout = uty_vf_free(vio->vout) ; - /* Move to the death watch list */ - sdl_del(vio_list_base, vio, vio_list) ; - sdl_push(vio_death_watch, vio, vio_list) ; + vio->close_reason = qs_reset(vio->close_reason, free_it) ; - vio->half_closed = true ; + vio->obuf = NULL ; + vio->state = vf_closed ; return true ; } ; /*------------------------------------------------------------------------------ - * Close VTY. - * + * Pop and close the top of the vin stack -- MUST NOT be vin_base. * + * Read closes the vio_vfd -- if the vio_vfd was read-only, this will fully + * close it, and the vio_vfd will have been freed. * - * If no reason for the close has already been set, sets the given reason. - * - * If not already half-closed, close all readers as described above, and then - * kick everything on the vout stack and apply VTY_HALF_CLOSE_TIMEOUT. + * If this is read-only will free the vio_vf and all its contents. * + * NB: a uty_cmd_prepare() is required before command processing can continue. * + * That is not done here because this may be called from outside the + * command loop -- in particular by uty_close(). */ extern void -uty_close (vty_io vio, const char* reason) +uty_vin_close(vty_io vio) { - if (uty_do_half_close(vio, reason)) - { - vio_vf vf ; + vio_vf vf ; - /* Run down the vout stack, and write-ready enable everything with - * the half close timeout set. - * - * This will have the effect of kicking all output. - */ - vf = vio->vout ; - while (vf != NULL) - { - vf->write_timeout = 0 ; - uty_vf_set_write(vf, on) ; + vf = vio->vin ; - vf = ssl_next(vf, vout_next) ; - } ; - } ; + assert(vf != vio->vin_base) ; + assert(vio->vin_depth > 0) ; + + ssl_del_head(vio->vin, vin_next) ; + --vio->vin_depth ; + + uty_vf_read_close(vf) ; } ; /*------------------------------------------------------------------------------ - * Closing down VTY. - * - * Shuts down everything and discards all buffers etc. etc. + * Pop and close the top of the vout stack -- MUST NOT be vout_base. * - * If cmd_in_progress, cannot complete the process -- but sets the closed - * flag. + * Moves the vio_vf to the vout_closing list. * - * Can call vty_close() any number of times. + * Before closing, push any outstanding output. * - * The vty structure is placed on death watch, which will finally free the - * structure once no longer cmd_in_progress. + * Unless "final", the close is soft, that is, if there is any output still + * outstanding does not actually close the vout. * + * If there is no outstanding output (or if final) will completely close the + * vf and free it. * - * If no reason for the close has already been set, sets the given reason. + * NB: a uty_cmd_prepare() is required before command processing can continue. * + * That is not done here because this may be called from outside the + * command loop -- in particular by uty_close(). */ extern void -uty_close_final(vty_io vio, const char* reason) +uty_vout_close(vty_io vio, bool final) { - VTY_ASSERT_LOCKED() ; - - /* If not already closed, close. */ - uty_do_half_close(vio, reason) ; /* set reason if required, and - make sure is half closed. */ - if (!vio->closed) - { - vio_vf vf ; - - /* Empty the vin stack */ - assert(vio->vin != NULL) ; - assert(vio->vin == vio->vin_base) ; - if (vio->vin != vio->vout_base) - uty_vf_close(vio->vin) ; - vio->vin_base = vio->vin = NULL ; - - /* Now kick everything in the vout stack, in case can get stuff - * written away. And then close. - */ - while (ssl_pop(&vf, vio->vout, vout_next) != NULL) - { - uty_vf_set_write(vf, off) ; /* stop any write ready... */ - vf->closing = true ; /* ...permanently. */ - - switch(vf->vout_type) /* tidy up each type */ - { - case VOUT_NONE: - break ; - - case VOUT_TERM: - break ; - - case VOUT_SHELL: - break ; - - case VOUT_FILE: - break ; - - case VOUT_PIPE: - break ; - - case VOUT_STDOUT: - break ; - - case VOUT_STDERR: - break ; - - default: - zabort("unknown VOUT type") ; - } ; - - uty_vf_close(vf) ; /* close and free */ - } ; + vio_vf vf ; - vio->vout_base = NULL ; /* stack is empty */ - vio->closed = true ; /* now closed */ - } ; + vf = vio->vout ; - /* Nothing more should happen, so can now release almost everything, - * the exceptions being the things that are related to a cmd_in_progress. - * - * All writing to buffers is suppressed, and as the sock has been closed, - * there will be no more read_ready or write_ready events. - */ - if (vio->name != NULL) - XFREE(MTYPE_VTY_NAME, vio->name) ; + assert(vf != vio->vout_base) ; + assert(vio->vout_depth > 0) ; - vio->key_stream = keystroke_stream_free(vio->key_stream) ; + ssl_del_head(vio->vout, vout_next) ; + --vio->vout_depth ; - qs_reset(&vio->cli_prompt_for_node, keep_it) ; - qs_reset(&vio->cl, keep_it) ; + ssl_push(vio->vout_closing, vf, vout_next) ; - { - qstring line ; - while ((line = vector_ream(vio->hist, keep_it)) != NULL) - qs_reset_free(line) ; - } ; + vio->obuf = vio->vout->obuf ; - /* The final stage cannot be completed if cmd_in_progress. - * - * The clx is pointed at by vty->buf -- containing the current command. - * - * Once everything is released, can take the vty off death watch, and - * release the vio and the vty. - */ - if (!vio->cmd_in_progress) - { - qs_reset(&vio->clx, keep_it) ; - vio->vty->buf = NULL ; - } ; + uty_vf_write_close(vf, final) ; } ; /*============================================================================== @@ -847,96 +740,294 @@ uty_close_final(vty_io vio, const char* reason) * * There are no errors, yet. * - * This leaves most things unset/NULL/false. Caller will want to set: + * This leaves most things unset/NULL/false. Caller will need to: + * + * - uty_vin_open() and/or uty_vout_open() + * + * - once those are done, the following optional items remain to be set + * if they are required: + * + * read_timeout -- default = 0 => no timeout + * write_timeout -- default = 0 => no timeout + * + * parse_type -- default = cmd_parse_standard + * reflect_enabled -- default = false + * out_enabled -- default = true iff vfd_io_write + * + * If vio->blocking, adds vfd_io_blocking to the io_type. * + * NB: if there is no fd for this vio_vf, it should be set to -1, and the + * type (recommend vfd_none) and io_type are ignored -- except for + * vfd_io_write and the out_enabled state. * + * A VTY_STDOUT or a VTY_STDERR (output only, to the standard I/O) can + * be set up without an fd, and with/without out_enabled. * - * Sets timeout to no timeout at all -- timeout is optional. + * A VTY_CONFIG_READ can be set up with the fd of the input file, but + * MUST be type = vfd_file and io_type = vfd_io_read -- because the fd + * is for input only. This will set out_enabled false (as required). + * The required VOUT_STDERR will be set by uty_vout_open(). */ extern vio_vf -uty_vf_new(vty_io vio, int fd, vfd_type_t type, vfd_io_type_t io_type) +uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type, + vfd_io_type_t io_type) { vio_vf vf ; VTY_ASSERT_LOCKED() ; + if (vio->blocking) + io_type |= vfd_io_blocking ; + vf = XCALLOC (MTYPE_VTY, sizeof(struct vio_vf)) ; /* Zeroising the structure has set: * - * vin_type = 0 -- VIN_NONE - * vin_next = NULL -- not on a vin list, yet + * vio = X -- set below + * name = X -- set below + * + * vin_type = VIN_NONE -- see uty_vin_open() + * vin_state = vf_closed -- see uty_vin_open() + * vin_next = NULL -- see uty_vin_open() + * + * cli = NULL -- no CLI, yet + * + * ibuf = NULL -- none, yet -- see uty_vin_open() + * + * cl = zeros -- empty qstring (embedded) + * line_complete = false -- see uty_vout_open() + * line_number = 0 -- nothing yet + * line_step = 0 -- see uty_vout_open() + * + * parse_type = cmd_parse_standard + * reflect_enabled = false * - * vout_type = NULL -- VOUT_NONE - * vout_next = NULL -- not on a vout list, yet + * vout_type = VOUT_NONE -- see uty_vout_open() + * vout_state = vf_closed -- see uty_vout_open() + * vout_next = NULL -- see uty_vout_open() * - * obuf = NULL -- none, yet - * olc = NULL -- none, yet + * obuf = NULL -- none, yet -- see uty_vout_open() * - * blocking = false - * closing = false + * out_enabled = X -- set below: true iff vfd_io_write * - * read_open = false - * write_open = false - * error_seen = 0 -- no error seen, yet + * vfd = NULL -- no vfd, yet * - * read_timeout = 0 -- none - * write_timeout = 0 -- none + * blocking = false -- default is non-blocking I/O + * closing = false -- definitely not + * + * error_seen = 0 -- no error seen, yet + * + * read_on = false + * write_on = false + * + * read_timeout = 0 -- none + * write_timeout = 0 -- none */ confirm((VIN_NONE == 0) && (VOUT_NONE == 0)) ; + confirm(vf_closed == 0) ; + confirm(QSTRING_INIT_ALL_ZEROS) ; + confirm(cmd_parse_standard == 0) ; vf->vio = vio ; - vf->vfd = vio_fd_new(fd, type, io_type, vf) ; + + if (name != NULL) + vf->name = XSTRDUP(MTYPE_VTY_NAME, name) ; + + if (fd >= 0) + vf->vfd = vio_vfd_new(fd, type, io_type, vf) ; + + vf->out_enabled = ((io_type & vfd_io_write) != 0) ; return vf ; } ; /*------------------------------------------------------------------------------ - * Half close a vio_vf. + * Close the read side of the given vio_vf. + * + * Read closes the vio_vfd -- if the vio_vfd was read-only, this will fully + * close it, and the vio_vfd will have been freed. + * + * If this is read-only (ie vout_type == VOUT_NONE) will free the vio_vf as + * well as the vio_vfd -- unless this is the vin_base. * - * Half closes the vio_fd and shuts down all reading. If the vio_fd was - * read-only, the half-close will fully close it, and the vio_fd will have - * been freed. + * Note that read_close is effective immediately, there is no delay as there is + * with write_close -- so no need for a "final" option. */ static void -uty_vf_half_close(vio_vf vf) +uty_vf_read_close(vio_vf vf) { - /* the vfd level half close will close completely if is read only. */ - vf->vfd = vio_fd_half_close(vf->vfd) ; + assert(vf->vin_state != vf_closed) ; + + /* Do the vfd level read close and mark the vf no longer read_open */ + if (vf->vin_type < VIN_SPECIALS) + vf->vfd = vio_vfd_read_close(vf->vfd) ; + + vf->vin_state = vf_closed ; + vf->read_on = off ; + + /* Now the vin_type specific clean up. */ + switch(vf->vin_type) + { + case VIN_NONE: + zabort("invalid VIN_NONE") ; + break ; + + case VIN_TERM: + uty_term_read_close(vf) ; + break ; - vf->read_open = false ; /* no more read operations */ - vf->read_on = off ; /* of course */ + case VIN_VTYSH: + break ; - if (vf->vfd == NULL) /* check really was read-only */ - assert(!vf->write_open && !vf->read_on) ; + case VIN_FILE: + uty_file_read_close(vf) ; + break ; + + case VIN_PIPE: + break ; + + case VIN_CONFIG: + break ; + + case VIN_DEV_NULL: + break ; + + default: + zabort("unknown VIN type") ; + } ; + + if ((vf->vout_type == VOUT_NONE) && (vf != vf->vio->vin_base)) + uty_vf_free(vf) ; } ; /*------------------------------------------------------------------------------ - * Close given vio_vf and free the vio_vf structure and all its contents. + * Close the write side of the given vio_vf, if can. + * + * Pushes any outstanding output -- non-blocking if not a blocking vty. * - * Assumes has been removed from vio->vin and vio->vout ! + * The vio_vf MUST either be on the vout_closing list or be vout_base. + * The vio_vf MUST be write_open and MUST NOT be read_open. + * + * If output is empty, or if final, close the vf, then if it is on the + * vout_closing list, remove and free it. + * + * Returns whether output was empty or not. */ -static void -uty_vf_close(vio_vf vf) +static bool +uty_vf_write_close(vio_vf vf, bool final) +{ + bool empty ; + + assert((vf->vout_state != vf_closed) && (vf->vin_state == vf_closed)) ; + + /* Worry about remaining return input from vout pipe TODO */ + + /* Worry about appending the close reason to the vout_base TODO */ + + /* Now the vout_type specific clean up. */ + empty = false ; + + switch(vf->vout_type) + { + case VOUT_NONE: + zabort("invalid VOUT_NONE") ; + break ; + + case VOUT_TERM: + empty = uty_term_write_close(vf, final) ; + break ; + + case VOUT_VTYSH: + break ; + + case VOUT_FILE: + empty = uty_file_write_close(vf, final) ; + break ; + + case VOUT_PIPE: + break ; + + case VOUT_CONFIG: + break ; + + case VOUT_DEV_NULL: + case VOUT_STDOUT: + case VOUT_STDERR: + empty = true ; + break ; + + default: + zabort("unknown VOUT type") ; + } ; + + if (empty || final) + { + /* Do the vfd level close and mark the vf no longer write_open */ + if (vf->vout_type < VOUT_SPECIALS) + vf->vfd = vio_vfd_close(vf->vfd) ; + else + assert(vf->vfd == NULL) ; + + vf->vout_state = vf_closed ; + vf->write_on = off ; + + if (ssl_del(vf->vio->vout_closing, vf, vout_next)) + uty_vf_free(vf) ; + else + assert(vf == vf->vio->vout_base) ; + } ; + + return empty ; +} ; + +/*------------------------------------------------------------------------------ + * Free the given vio_vf structure and all its contents. + * + * Expects the vfd to already have been closed, but will close it if not. + * + * Expects any cli to be closed, but will close it if not. + * + * Assumes has been removed from any and all lists ! + */ +static vio_vf +uty_vf_free(vio_vf vf) { - vf->vfd = vio_fd_close(vf->vfd) ; + XFREE(MTYPE_VTY_NAME, vf->name) ; + + vf->ibuf = vio_fifo_reset(vf->ibuf, free_it) ; + vf->obuf = vio_fifo_reset(vf->obuf, free_it) ; + + qs_reset(vf->cl, keep_it) ; - vf->obuf = vio_fifo_reset_free(vf->obuf) ; - vf->olc = vio_lc_reset_free(vf->olc) ; + vf->cli = uty_cli_close(vf->cli, true) ; + vf->vfd = vio_vfd_close(vf->vfd) ; /* for completeness */ XFREE(MTYPE_VTY, vf) ; + + return NULL ; } ; /*------------------------------------------------------------------------------ + * + * + */ + + + + + +/*------------------------------------------------------------------------------ * Dealing with an I/O error on VTY socket * * If this is the first error for this VTY, produce suitable log message. * * If is a "monitor", turn that off, *before* issuing log message. */ -static int -uty_vf_error(vty_io vio, const char* what) +extern int +uty_vf_error(vio_vf vf, const char* what, int err) { + vty_io vio = vf->vio ; + VTY_ASSERT_LOCKED() ; VTY_ASSERT_CLI_THREAD() ; @@ -944,24 +1035,14 @@ uty_vf_error(vty_io vio, const char* what) uty_set_monitor(vio, 0) ; /* if this is the first error, log it */ - if (vio->sock.error_seen == 0) + if (vf->error_seen == 0) { - const char* type ; - switch (vio->vty_type) - { - case VTY_TERM: - type = "VTY Terminal" ; - break ; - case VTY_SHELL: - type = "VTY Shell Server" ; - break ; - default: - zabort("unknown VTY type for uty_file_error()") ; - } ; - - vio->sock.error_seen = errno ; + const char* type = "?" ; /* TODO */ + + + vf->error_seen = err ; uzlog(NULL, LOG_WARNING, "%s: %s failed on fd %d: %s", - type, what, vio->sock.fd, errtoa(vio->sock.error_seen, 0).str) ; + type, what, vio_vfd_fd(vf->vfd), errtoa(err, 0).str) ; } ; return -1 ; @@ -970,33 +1051,34 @@ uty_vf_error(vty_io vio, const char* what) /*------------------------------------------------------------------------------ * Set required read ready state. Applies the current read timeout. * - * Forces off if: vf->closing + * Forces off if: vf->vin_state != vf_open * - * Does nothing if: !vf->read_open + * Does nothing if: vf->vin_state == vf_closed * or: vf->vfd == NULL + * + * Returns new state of vf->read_on */ -extern on_off_t -uty_vf_set_read(vio_vf vf, on_off_t how) +extern on_off_b +uty_vf_set_read(vio_vf vf, on_off_b how) { - if (vf->closing) + if (vf->vin_state != vf_open) how = off ; - return vf->read_on = vf->read_open - ? vio_fd_set_read(vf->vfd, how, vf->read_timeout) + + return vf->read_on = (vf->vin_state != vf_closed) + ? vio_vfd_set_read(vf->vfd, how, vf->read_timeout) : off ; } ; /*------------------------------------------------------------------------------ * Set required read ready timeout -- if already read_on, restart it. * - * Forces read ready off if: vf->closing - * - * Does nothing if: !vf->read_open - * or: vf->vfd == NULL + * Returns new state of vf->read_on */ -extern on_off_t +extern on_off_b uty_vf_set_read_timeout(vio_vf vf, vty_timer_time read_timeout) { vf->read_timeout = read_timeout ; + return vf->read_on ? uty_vf_set_read(vf, on) : off ; } ; @@ -1004,34 +1086,34 @@ uty_vf_set_read_timeout(vio_vf vf, vty_timer_time read_timeout) /*------------------------------------------------------------------------------ * Set required write ready state. Applies the current write timeout. * - * Forces off if: vf->closing + * Forces off if: vf->vout_state != vf_open * - * Does nothing if: !vf->write_open + * Does nothing if: vf->vout_state == vf_closed * or: vf->vfd == NULL + * + * Returns new state of vf->write_on */ -extern on_off_t -uty_vf_set_write(vio_vf vf, on_off_t how) +extern on_off_b +uty_vf_set_write(vio_vf vf, on_off_b how) { - if (vf->closing) + if (vf->vout_state != vf_open) how = off ; - return vf->write_on = vf->write_open - ? vio_fd_set_write(vf->vfd, how, vf->write_timeout) + return vf->write_on = (vf->vout_state != vf_closed) + ? vio_vfd_set_write(vf->vfd, how, vf->write_timeout) : off ; } ; /*------------------------------------------------------------------------------ * Set required write ready timeout -- if already write_on, restart it. * - * Forces write ready off if: vf->closing - * - * Does nothing if: !vf->write_open - * or: vf->vfd == NULL + * Returns new state of vf->write_on */ -extern on_off_t +extern on_off_b uty_vf_set_write_timeout(vio_vf vf, vty_timer_time write_timeout) { vf->write_timeout = write_timeout ; + return vf->write_on ? uty_vf_set_write(vf, on) : off ; } ; @@ -1061,9 +1143,11 @@ uty_open_listeners(const char *addr, unsigned short port, const char *path) if (port) uty_term_open_listeners(addr, port) ; +#if 0 /* If want to listen for vtysh, set up listener now */ if (VTYSH_ENABLED && (path != NULL)) uty_serv_vtysh(path) ; +#endif } ; /*------------------------------------------------------------------------------ @@ -1072,7 +1156,7 @@ uty_open_listeners(const char *addr, unsigned short port, const char *path) * Adds to list of listeners for close down. */ extern void -uty_add_listener(int fd, vio_fd_accept* accept_action) +uty_add_listener(int fd, vio_vfd_accept* accept_action) { vio_listener vl ; diff --git a/lib/vty_io.h b/lib/vty_io.h index ecd7a451..5db12ca2 100644 --- a/lib/vty_io.h +++ b/lib/vty_io.h @@ -25,145 +25,251 @@ #ifndef _ZEBRA_VTY_IO_H #define _ZEBRA_VTY_IO_H -#include "zebra.h" +//#include "zebra.h" #include "misc.h" +//#include <errno.h> -#include <errno.h> - +#include "vty_local.h" +#include "command_local.h" #include "vty_io_basic.h" -#include "uty.h" -#include "vty.h" #include "vio_fifo.h" -#include "vio_lines.h" -#include "keystroke.h" #include "thread.h" -#include "command.h" +#include "command_execute.h" #include "qstring.h" /*============================================================================== - * Here are structures and other definitions which are shared by: - * - * vty.c -- the main vty handler - * vty_cli.c -- which handles the command line stuff - * vty_io.c -- .... - * - * The "struct vty" is used extensively across the Quagga daemons, where it - * has two functions relating to command handling as: - * - * 1) a "file handle" for output produced by commands - * - * 2) the holder of some context -- notably the current command "node" -- for - * command execution to use + * Structures and other definitions for the top level VTY I/O. * - * The bulk of "struct vty" is, therefore, private to vty.c and is factored - * out into the "struct vty_io". - * - * To reduce the size of vty.c, some groups of functions are separated into: - * - * vty_cli.c -- which looks after the keystroke by keystroke handling - * of the command line. + * There is one struct vty_io per VTY, which contains, inter alia, the vin + * and vout stacks. * + * The vin and vout stacks contain one or more struct vty_vf -- one per + * input and/or output associated with the VTY. */ -/*============================================================================== - * VTY CLI and OUT types +enum +{ + VTY_WATCH_DOG_INTERVAL = 5, /* interval between barks */ + + VTY_HALF_CLOSE_TIMEOUT = 120, /* timeout after half_close */ + + VTY_TIMEOUT_DEFAULT = 600, /* terminal timeout value */ +} ; + +/*------------------------------------------------------------------------------ + * VTY VIN and OUT types */ enum vio_in_type /* Command input */ { - VIN_NONE = 0, /* no input at all */ + VIN_NONE = 0, /* not a valid input type */ VIN_TERM, /* telnet terminal */ - VIN_SHELL, /* vty_shell input */ + VIN_VTYSH, /* vty_shell input */ VIN_FILE, /* ordinary file input */ VIN_PIPE, /* pipe (from child process) */ - VIN_CONFIG, /* config file ?? */ + VIN_CONFIG, /* config file */ + + VIN_PIPE_RETURN, /* */ + + /* The VIN types >= VIN_SPECIALS do not have an associated fd. + * + * These can coexist with a VOUT which does have an associated fd. + */ + VIN_SPECIALS, /* all special from now on */ + + VIN_DEV_NULL = VIN_SPECIALS, + /* black hole input */ } ; typedef enum vio_in_type vio_in_type_t ; enum vio_out_type /* Command output */ { - VOUT_NONE = 0, /* no output at all */ + VOUT_NONE = 0, /* not a valid output type */ VOUT_TERM, /* a telnet terminal */ - VOUT_SHELL, /* a vty_shell output pipe */ + VOUT_VTYSH, /* a vty_shell output pipe */ VOUT_FILE, /* ordinary file */ VOUT_PIPE, /* pipe (to child process) */ + VOUT_CONFIG, /* config file ?? */ + + /* The VOUT types >= VOUT_SPECIALS do not have an associated fd. + * + * These can coexist with a VIN which does have an associated fd. + */ + VOUT_SPECIALS, /* all special from now on */ + + VOUT_DEV_NULL = VOUT_SPECIALS, + /* black hole output */ + VOUT_STDOUT, /* stdout */ VOUT_STDERR, /* stderr */ }; typedef enum vio_out_type vio_out_type_t ; /*------------------------------------------------------------------------------ - * VIO file structure + * State of a vf or of the entire vio. + * + * For a vf: I/O is possible iff vf_open -- has separate state for vin/vout. + * + * For a vio: used to manage the closing process. + */ +enum vf_state +{ + vf_closed = 0, /* not active -- may be discarded */ + + vf_open = 1, /* open for business */ + + vf_eof = 2, /* open, but at eof */ + vf_error = 3, /* open, but error reported */ + + vf_closing = 4, /* open, but in process of closing */ +} ; +typedef enum vf_state vf_state_t ; + +/*------------------------------------------------------------------------------ + * vty_vf -- "vty file" structure * - * All I/O is non-blocking for all sources and sinks of VIO stuff. + * A vio_vf may be a read, write or read/write object. + * + * All I/O is via vio_vfd objects, except for VOUT_STDOUT and VOUT_STDERR. + * The vio_vfd layer hides the differences between the qpthreads an legacy + * thread environments. + * + * The VOUT_STDOUT and VOUT_STDERR are handled as direct output to the standard + * i/o file handles. In the case of a VTY_CONFIG_READ, the vin is VIN_CONFIG + * and the vout is VOUT_STDOUT, and these can share a single vty_vf. * * Also used for the associated listeners. */ +struct vty_io ; /* Forward reference */ +typedef struct vty_io* vty_io ; + typedef struct vio_vf* vio_vf ; struct vio_vf { - vty_io vio ; /* parent */ + vty_io vio ; /* parent */ + + char* name ; /* MTYPE_VTY_NAME (if any) */ + + /* Input side. */ + + vio_in_type_t vin_type ; + vf_state_t vin_state ; + vio_vf vin_next ; /* list of inputs */ + + + struct vty_cli* cli ; /* NULL if not a VTY_TERMINAL ! */ + + vio_fifo ibuf ; /* input fifo (if required) */ - vio_in_type_t vin_type ; - vio_vf vin_next ; /* list of inputs */ + qstring_t cl ; /* command line buffer */ + bool line_complete ; /* false => line in construction */ + uint line_number ; /* number of first line in cl */ + uint line_step ; /* number of real lines in cl */ - vio_out_type_t vout_type ; - vio_vf vout_next ; /* list of outputs */ + cmd_parse_type_t parse_type ; /* iff vin_type != VIN_NONE */ + bool reflect_enabled ; /* false if vin_type == VIN_NONE */ - vio_fifo obuf ; /* pointer to fifo */ - vio_line_control olc ; /* pointer to lc */ + /* Output side. */ - vio_fd vfd ; + vio_out_type_t vout_type ; + vf_state_t vout_state ; + vio_vf vout_next ; /* list of outputs */ - bool blocking ; /* using blocking reads */ - bool closing ; /* suppress read/write ready */ + vio_fifo obuf ; /* output fifo (if required) */ - bool read_open ; /* reads returns 0 if not */ - bool write_open ; /* writes complete instantly if not */ - int error_seen ; /* non-zero => failed */ + bool out_enabled ; /* false if vout_type == VOUT_NONE */ - on_off_b read_on ; - on_off_b write_on ; + /* I/O */ + + vio_vfd vfd ; /* vty_io_basic "file descriptor" */ + + bool blocking ; /* using blocking I/O (config read) */ + + int error_seen ; /* non-zero => failed */ + + on_off_b read_on ; + on_off_b write_on ; vty_timer_time read_timeout ; vty_timer_time write_timeout ; + } ; -enum vty_readiness /* bit significant */ +enum vty_readiness /* bit significant */ { not_ready = 0, - read_ready = 1, - write_ready = 2, /* takes precedence */ - now_ready = 4 + read_ready = BIT(0), + write_ready = BIT(1), /* may take precedence */ + now_ready = BIT(2) } ; +typedef enum vty_readiness vty_readiness_t ; /*------------------------------------------------------------------------------ * The vty_io structure * + * The main elements of the vty_io object are the vin and vout stacks. + * + * One of the challenges is managing the closing of VTY objects. First, + * cannot close and free a VTY object which is in the hands of a command + * function, or which is queued to or from a command function. Second, + * do not wish to completely close an output until have given it a chance + * to clear any buffered output. + * + * * + * "cmd_running" means that the VTY is in hands of (or has been passed to) + * a command loop -- the VTY cannot be fully closed until that is no + * longer the case. * + * "blocking" is set for configuration reading VTY, so that everything is + * done with blocking I/O. * + * "closing" means that the vty has been closed (!), but a command + * and or output may still be active: * + * - if is a socket, will have shut_down the read side (half-closed) + * + * - any further attempts to read will give "eof" + * + * - there may be a command in execution -- see "cmd_running". + * + * - further writing will be honoured. + * + * - the write side may still be active, attempting to empty out any + * pending output. + * + * "closed" means the vty has been fully and finally closed. + * + * - any further attempts to write will be ignored, but return instant + * success. + * + * - the file/socket has been fully closed. + * + * - the VTY and all attached structures can be reaped by the death_watch. */ +struct vty_cli ; /* forward reference -- vty_cli.h is + *not* included, because that refers + back to the vty_io ! */ -struct vty_io +struct vty_io /* typedef appears above */ { - struct vty* vty ; /* the related vty */ - char *name ; /* for VTY_TERM is IP address) */ + vty vty ; /* the related vty */ - /* vin stack */ - vio_vf vin ; + vio_vf vin ; /* vin stack */ vio_vf vin_base ; + uint vin_depth ; - /* vout stack */ - vio_vf vout ; + vio_vf vout ; /* vout stack */ vio_vf vout_base ; + uint vout_depth ; + + vio_vf vout_closing ; /* vout closing list */ /* List of all vty_io objects */ struct dl_list_pair(vty_io) vio_list ; @@ -171,193 +277,102 @@ struct vty_io /* List of all vty_io that are in monitor state */ struct dl_list_pair(vty_io) mon_list ; - /* VTY state */ - - bool half_closed ; /* => on death watch list until closed */ - bool closed ; /* => all I/O terminated - will also be half_closed */ - - char* close_reason ; /* message to be sent, once all other - output has completed, giving reason - for closing the VTY. */ - - - - - - /* When writing configuration file */ - enum vty_type real_type ; - - int file_fd ; - int file_error ; - - /* Failure count for login attempts */ - int fail; - - /* History of commands */ - vector_t hist ; - int hp ; /* History lookup current point */ - int hindex; /* History insert end point */ - - /* Window width/height as reported by Telnet. 0 => unknown */ - int width; - int height; - - /* Configure lines. */ - int lines; - bool lines_set ; /* true <=> explicitly set */ - - /* Terminal monitor. */ - bool monitor ; - bool monitor_busy ; - - /* Terminal timeout in seconds -- 0 => none */ - vty_timer_time v_timeout ; - - /*------------------------------------------------------------------------- - * CLI_TERM stuff. - */ - - keystroke_stream key_stream ; - - /* cli_drawn <=> the current prompt and user input occupy the current - * line on the screen. + /* VTY state * - * cli_dirty <=> the last command output did not end with a newline. + * "blocking" is set for configuration reading VTY, so that everything is + * done with blocking I/O. * - * If cli_drawn is true, the following are valid: + * "half_closed" means that the vty has been closed (!), but a command + * and or output may still be active: * - * cli_prompt_len -- the length of the prompt part. - * (will be the "--more--" prompt in cli_more_wait) + * - if is a socket, will have shut_down the read side (half-closed) * - * cli_extra_len -- the length of any ^X at the cursor position - * (for when blocked waiting for queued command) + * - any further attempts to read will give "eof" * - * cli_echo_suppress -- the user part of the command line is suppressed + * - there may be a command in execution -- see "cmd_running". * - * NB: cli_echo_suppress is only used for password entry. - */ - bool cli_drawn ; - bool cli_dirty ; - - int cli_prompt_len ; - int cli_extra_len ; - - bool cli_echo_suppress ; - - /* "cache" for prompt -- when node or host name changes, prompt does */ - enum node_type cli_prompt_node ; - bool cli_prompt_set ; - qstring_t cli_prompt_for_node ; - - /* State of the CLI + * - further writing will be honoured. * - * cli_blocked -- blocked from processing keystrokes - * cmd_in_progress -- command dispatched (may be queued) - * cmd_out_enabled -- contents of the command FIFO may be written away - * cli_more_wait -- is in "--more--" wait state - */ - bool cli_blocked ; - bool cmd_in_progress ; - bool cmd_out_enabled ; - bool cli_more_wait ; - - /* This is used to control command output, so that each write_ready event - * generates at most one tranche of output. - */ - bool cmd_out_done ; - - /* This is set only if the "--more--" handling is enabled */ - bool cli_more_enabled ; - - /* Command Line(s) + * - the write side may still be active, attempting to empty out any + * pending output. + * + * "cmd_running" means that the VTY is in hands of (or has been passed to) + * a command loop -- the VTY cannot be fully closed until that is no + * longer the case. * - * cli_do -- when current command being prepared is completed (by - * CR/LF or otherwise) this says what there now is to be done. + * "closed" means the vty has been fully and finally closed. * - * cl -- current command line being prepared. + * - any further attempts to write will be ignored, but return instant + * success. * - * clx -- current command line being executed. + * - the file/socket has been fully closed. * - * NB: during command execution vty->buf is set to point at the '\0' - * terminated current command line being executed. + * - the VTY and all attached structures can be reaped by the death_watch. */ - enum cli_do cli_do ; + bool blocking ; /* => all I/O is blocking. */ - qstring_t cl ; - qstring_t clx ; + bool cmd_running ; /* => cannot be fully closed */ - /* CLI output buffering */ - vio_fifo_t cli_obuf ; + vf_state_t state ; -} ; + qstring close_reason ; /* message to be sent, once all other + output has completed, giving reason + for closing the VTY. */ -/*============================================================================== - * If possible, will use getaddrinfo() to find all the things to listen on. - */ -enum { -#if defined(HAVE_IPV6) && !defined(NRL) - VTY_USE_ADDRINFO = 1, -#else - VTY_USE_ADDRINFO = 0, -#endif + /* For ease of output, pointer to current vout->obuf */ + vio_fifo obuf ; /* NULL => no vout ! */ } ; /*============================================================================== * Functions */ -extern vty uty_new (vty_type_t type, int sock_fd) ; -extern void uty_close (vty_io vio, const char* reason) ; -extern void uty_close_final(vty_io vio, const char* reason) ; +extern int uty_out (vty_io vio, const char* format, ...) PRINTF_ATTRIBUTE(2, 3); +Inline int uty_vprintf(vty_io vio, const char *format, va_list args) ; + +Inline void uty_out_clear(vty_io vio) ; +Inline void uty_out_accept(vty_io vio) ; +Inline void uty_out_reject(vty_io vio) ; +extern vty uty_new (vty_type_t type) ; +extern bool uty_close(vty_io vio, bool final, qstring reason) ; -extern void uty_vin_add(vty_io vio, vio_vf vf, vio_in_type_t type, - vio_fd_action* read_action, vio_timer_action* read_timer_action) ; -extern void uty_vout_add(vty_io vio, vio_vf vf, vio_out_type_t type, - vio_fd_action* write_action, vio_timer_action* write_timer_action) ; +extern void uty_vin_close(vty_io vio) ; +extern void uty_vout_close(vty_io vio, bool final) ; +extern void uty_vin_close_stack(vty_io vio) ; +extern void uty_vout_close_stack(vty_io vio, bool final) ; +extern void uty_set_timeout(vty_io vio, vty_timer_time timeout) ; +extern void uty_vin_open(vty_io vio, vio_vf vf, vio_in_type_t type, + vio_vfd_action* read_action, + vio_timer_action* read_timer_action, + usize ibuf_size) ; +extern void uty_vout_open(vty_io vio, vio_vf vf, vio_out_type_t type, + vio_vfd_action* write_action, + vio_timer_action* write_timer_action, + usize obuf_size) ; -extern vio_vf uty_vf_new(vty_io vio, int fd, vfd_type_t type, +extern vio_vf uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type, vfd_io_type_t io_type) ; -Inline int uty_vf_fd(vio_vf vf) ; -extern on_off_t uty_vf_set_read(vio_vf vf, on_off_t on) ; -extern on_off_t uty_vf_set_read_timeout(vio_vf vf, +extern on_off_b uty_vf_set_read(vio_vf vf, on_off_b on) ; +extern on_off_b uty_vf_set_read_timeout(vio_vf vf, vty_timer_time read_timeout) ; -extern on_off_t uty_vf_set_write(vio_vf vf, on_off_t on) ; -extern on_off_t uty_vf_set_write_timeout(vio_vf vf, +extern on_off_b uty_vf_set_write(vio_vf vf, on_off_b on) ; +extern on_off_b uty_vf_set_write_timeout(vio_vf vf, vty_timer_time write_timeout) ; - +extern int uty_vf_error(vio_vf vf, const char* what, int err) ; extern void uty_open_listeners(const char *addr, unsigned short port, const char *path) ; -extern void uty_add_listener(int fd, vio_fd_accept* accept) ; +extern void uty_add_listener(int fd, vio_vfd_accept* accept) ; extern void uty_close_listeners(void) ; extern void uty_watch_dog_init(void) ; extern void uty_watch_dog_start(void) ; extern void uty_watch_dog_stop(void) ; - - -extern int uty_output (struct vty *vty, const char *format, ...) - PRINTF_ATTRIBUTE(2, 3) ; -extern int uty_vprintf(struct vty *vty, const char *format, va_list args) ; -extern int uty_reflect(struct vty *vty) ; -extern void uty_out_clear(vty_io vio) ; -extern void uty_out_fflush(vty_io vio, FILE* file) ; - -extern void uty_set_height(vty_io vio) ; -extern void uty_cmd_output_start(vty_io vio) ; - -extern void uty_file_set_readiness(vio_vf vf, enum vty_readiness ready) ; -extern void uty_file_set_timer(vio_vf vf, unsigned long timeout) ; - -extern int uty_read (vty_io vio, keystroke steal) ; -extern int utysh_read (vty_io vio, qstring cl, qstring buf) ; - extern const char* uty_get_name(vty_io vio) ; extern void uty_set_monitor(vty_io vio, bool on) ; @@ -366,19 +381,6 @@ extern void uty_set_monitor(vty_io vio, bool on) ; * Inline Functions */ -/*------------------------------------------------------------------------------ - * Return the fd from a vio_fd structure - */ -Inline int -uty_vf_fd(vio_vf vf) -{ - return vio_fd_fd(vf->vfd) ; -} ; - -/*------------------------------------------------------------------------------ - * Return the fd from a vio_fd structure - */ - Inline bool uty_is_terminal(struct vty *vty) { @@ -397,4 +399,41 @@ uty_is_shell_client(struct vty *vty) return vty->type == VTY_SHELL_CLIENT ; } +/*------------------------------------------------------------------------------ + * Command output -- append to output buffer. + */ +Inline int +uty_vprintf(vty_io vio, const char *format, va_list args) +{ + return vio_fifo_vprintf(vio->obuf, format, args) ; +} ; + +/*------------------------------------------------------------------------------ + * Clear command output -- discard anything in the buffer, but keep markers. + */ +Inline void +uty_out_clear(vty_io vio) +{ + vio_fifo_clear(vio->obuf, false) ; +} ; + +/*------------------------------------------------------------------------------ + * Accept command output -- advance any end_mark to current put position. + */ +Inline void +uty_out_accept(vty_io vio) +{ + vio_fifo_step_end_mark(vio->obuf) ; +} ; + +/*------------------------------------------------------------------------------ + * Reject command output -- discard anything after the end_mark in the buffer, + * but keep markers. + */ +Inline void +uty_out_reject(vty_io vio) +{ + vio_fifo_back_to_end_mark(vio->obuf, true) ; +} ; + #endif /* _ZEBRA_VTY_IO_H */ diff --git a/lib/vty_io_basic.c b/lib/vty_io_basic.c index 3bead51f..85ea2587 100644 --- a/lib/vty_io_basic.c +++ b/lib/vty_io_basic.c @@ -21,9 +21,67 @@ * 02111-1307, USA. */ -#include "zebra.h" - #include "vty_io_basic.h" +#include "vty_local.h" + +#include "memory.h" + +#include <sys/socket.h> +#include <fcntl.h> + +/*============================================================================== + * Base level open operations -- files and pipes + * + */ + +/*============================================================================== + * Try to open the given file for the given type of I/O. + * + * vfd_io_write => create if does not exist (mode 0600) + * + * vfd_io_append => set O_APPEND + * otherwise => truncate file if it does exist + * + * vfd_io_read => fail if does not exist + * + * vfd_io_read_write => create if does not exist (mode 0600) + * + * vfd_io_append => set O_APPEND + * otherwise => leave file as is + * + * vfd_io_blocking => do not open O_NONBLOCK + * + * (if none of the above, treat as vfd_io_read) + * + * Returns: if >= 0 -- the fd of the open file + * < 0 -- failed to open file -- see errno + */ +extern int +uty_vfd_file_open(const char* name, vfd_io_type_t io_type) +{ + int oflag ; + + oflag = 0 ; + if ((io_type & vfd_io_read_write) == vfd_io_read_write) + oflag = O_RDWR | O_CREAT ; + else if ((io_type & vfd_io_write) != 0) + oflag = O_WRONLY | O_CREAT ; + else + oflag = O_RDONLY ; + + if ((io_type & vfd_io_write) != 0) + { + if ((io_type & vfd_io_append) != 0) + oflag |= O_APPEND ; + else if ((io_type & vfd_io_read) == 0) + oflag |= O_TRUNC ; + } ; + + if ((io_type & vfd_io_blocking) == 0) + oflag |= O_NONBLOCK ; + + return open(name, oflag, S_IRUSR | S_IWUSR) ; +} ; /*============================================================================== * Base level I/O and Timer handling.... @@ -34,25 +92,23 @@ struct vio_io_set_args /* to CLI thread */ { - bool active ; /* set when queued, cleared when dequeued */ - bool die ; /* set when is queued and vio_fd is closed */ - bool close ; /* close and free the vio_fd and mqb */ + bool active ; /* set when queued, cleared when dequeued */ + bool close ; /* close and free the vio_vfd and mqb */ bool readable ; /* set when read state to be changed */ - on_off_t read_on ; /* what to change read to */ + on_off_b read_on ; /* what to change read to */ vty_timer_time read_timeout ; /* what to set the timeout to, if any */ bool writable ; /* set when write state to be changed */ - on_off_t write_on ; /* what to change write to */ + on_off_b write_on ; /* what to change write to */ vty_timer_time write_timeout ; /* what to set the timeout to, if any */ } ; MQB_ARGS_SIZE_OK(vio_io_set_args) ; -static void vio_fd_mqb_dispatch(vio_fd vfd) ; -static void vio_fd_mqb_free(vio_fd vfd) ; -static struct vio_io_set_args* vio_fd_mqb_args(vio_fd vfd) ; +static void vio_vfd_mqb_dispatch(vio_vfd vfd) ; +static struct vio_io_set_args* vio_vfd_mqb_args(vio_vfd vfd) ; /*============================================================================== * File Descriptor handling @@ -69,22 +125,22 @@ static struct vio_io_set_args* vio_fd_mqb_args(vio_fd vfd) ; * see above. */ -static void vio_fd_qps_read_action(qps_file qf, void* file_info) ; -static void vio_fd_qps_write_action(qps_file qf, void* file_info) ; -static int vio_fd_thread_read_action(struct thread *thread) ; -static int vio_fd_thread_write_action(struct thread *thread) ; +static void vio_vfd_qps_read_action(qps_file qf, void* file_info) ; +static void vio_vfd_qps_write_action(qps_file qf, void* file_info) ; +static int vio_vfd_thread_read_action(struct thread *thread) ; +static int vio_vfd_thread_write_action(struct thread *thread) ; static void vio_timer_squelch(vio_timer_t* timer) ; Inline void -vio_fd_do_read_action(vio_fd vfd) +vio_vfd_do_read_action(vio_vfd vfd) { if (vfd->active) vfd->read_action(vfd, vfd->action_info) ; } Inline void -vio_fd_do_write_action(vio_fd vfd) +vio_vfd_do_write_action(vio_vfd vfd) { if (vfd->active) vfd->write_action(vfd, vfd->action_info) ; @@ -93,12 +149,12 @@ vio_fd_do_write_action(vio_fd vfd) /*------------------------------------------------------------------------------ * Create a new vfd structure. */ -extern vio_fd -vio_fd_new(int fd, vfd_type_t type, vfd_io_type_t io_type, void* action_info) +extern vio_vfd +vio_vfd_new(int fd, vfd_type_t type, vfd_io_type_t io_type, void* action_info) { - vio_fd vfd ; + vio_vfd vfd ; - vfd = XCALLOC(MTYPE_VTY, sizeof(struct vio_fd)) ; + vfd = XCALLOC(MTYPE_VTY, sizeof(struct vio_vfd)) ; /* Has set: * @@ -114,12 +170,12 @@ vio_fd_new(int fd, vfd_type_t type, vfd_io_type_t io_type, void* action_info) * mqb -- NULL */ - vio_fd_set_fd(vfd, fd, type, io_type) ; + vio_vfd_set_fd(vfd, fd, type, io_type) ; vio_timer_init(&vfd->read_timer, NULL, NULL) ; vio_timer_init(&vfd->write_timer, NULL, NULL) ; - vio_fd_set_action_info(vfd, action_info) ; + vio_vfd_set_action_info(vfd, action_info) ; return vfd ; } ; @@ -127,13 +183,13 @@ vio_fd_new(int fd, vfd_type_t type, vfd_io_type_t io_type, void* action_info) /*------------------------------------------------------------------------------ * If vfd was not fully set up when created, set it up now. * - * To close an active vfd, use vio_fd_close() ! + * To close an active vfd, use vio_vfd_close() ! * * NB: for use when vfd has been created, but the fd was not known at that * time -- ie the vfd is NOT active. */ extern void -vio_fd_set_fd(vio_fd vfd, int fd, vfd_type_t type, vfd_io_type_t io_type) +vio_vfd_set_fd(vio_vfd vfd, int fd, vfd_type_t type, vfd_io_type_t io_type) { assert(!vfd->active) ; @@ -144,74 +200,76 @@ vio_fd_set_fd(vio_fd vfd, int fd, vfd_type_t type, vfd_io_type_t io_type) } ; /*------------------------------------------------------------------------------ - * Set the read action field for the given vio_fd. + * Set the read action field for the given vio_vfd. */ extern void -vio_fd_set_read_action(vio_fd vfd, vio_fd_action* action) +vio_vfd_set_read_action(vio_vfd vfd, vio_vfd_action* action) { vfd->read_action = action ; } ; /*------------------------------------------------------------------------------ - * Set the write action field for the given vio_fd. + * Set the write action field for the given vio_vfd. */ extern void -vio_fd_set_write_action(vio_fd vfd, vio_fd_action* action) +vio_vfd_set_write_action(vio_vfd vfd, vio_vfd_action* action) { vfd->write_action = action ; } ; /*------------------------------------------------------------------------------ - * Set the read action field for the given vio_fd. + * Set the read action field for the given vio_vfd. */ extern void -vio_fd_set_read_timeout_action(vio_fd vfd, vio_timer_action* action) +vio_vfd_set_read_timeout_action(vio_vfd vfd, vio_timer_action* action) { vio_timer_set_action(&vfd->read_timer, action) ; } ; /*------------------------------------------------------------------------------ - * Set the write action field for the given vio_fd. + * Set the write action field for the given vio_vfd. */ extern void -vio_fd_set_write_timeout_action(vio_fd vfd, vio_timer_action* action) +vio_vfd_set_write_timeout_action(vio_vfd vfd, vio_timer_action* action) { vio_timer_set_action(&vfd->write_timer, action) ; } ; /*------------------------------------------------------------------------------ - * Set the action_info field for the given vio_fd read/write action. + * Set the action_info field for the given vio_vfd read/write action. */ extern void -vio_fd_set_action_info(vio_fd vfd, void* action_info) +vio_vfd_set_action_info(vio_vfd vfd, void* action_info) { vfd->action_info = action_info ; vio_timer_set_info(&vfd->read_timer, action_info) ; vio_timer_set_info(&vfd->write_timer, action_info) ; } ; +#if 0 /*------------------------------------------------------------------------------ - * If there is a read action set for the give vio_fd (if any), then kick it. + * If there is a read action set for the give vio_vfd (if any), then kick it. */ extern void -vio_fd_do_read_action(vio_fd vfd) +vio_vfd_kick_read_action(vio_vfd vfd) { if ((vfd != NULL) && (vfd->read_action != NULL)) - vio_fd_do_read_action(vfd) ; + vio_vfd_do_read_action(vfd) ; } ; /*------------------------------------------------------------------------------ - * If there is a write action set for the give vio_fd (if any), then kick it. + * If there is a write action set for the give vio_vfd (if any), then kick it. */ extern void -vio_fd_do_write_action(vio_fd vfd) +vio_vfd_kick_write_action(vio_vfd vfd) { if ((vfd != NULL) && (vfd->write_action != NULL)) - vio_fd_do_read_action(vfd) ; + vio_vfd_do_read_action(vfd) ; } ; +#endif /*------------------------------------------------------------------------------ - * Half close the given vfd (if any). + * Close the read side of the given vfd (if any). * * If the vfd is a socket, then does a shutdown of the read side. * @@ -221,8 +279,8 @@ vio_fd_do_write_action(vio_fd vfd) * * Returns original vfd, or NULL if it has been closed. */ -extern vio_fd -vio_fd_half_close(vio_fd vfd) +extern vio_vfd +vio_vfd_read_close(vio_vfd vfd) { VTY_ASSERT_LOCKED() ; @@ -239,14 +297,14 @@ vio_fd_half_close(vio_fd vfd) { /* read & write, so really half-close if can */ if (vfd->type == vfd_socket) - shutdown(vfd->fd, SHUT_RD) ; - vio_fd_set_read(vfd, off, 0) ; + shutdown(vfd->fd, SHUT_RD) ; /* ignore errors TODO */ + vio_vfd_set_read(vfd, off, 0) ; vfd->io_type ^= vfd_io_read ; /* now write only ! */ } else { /* read only, so fully close */ - vfd = vio_fd_close(vfd) ; + vfd = vio_vfd_close(vfd) ; } ; } ; } @@ -259,32 +317,31 @@ vio_fd_half_close(vio_fd vfd) /*------------------------------------------------------------------------------ * If there is an fd, close it. * - * Stops any read/write waiting and releases all memory. - * - * NB: this can done from any thread, but if not done from the CLI thread, - * and there is a qf, a message must be sent to the CLI thread to actually - * implement: which passes the vio_fd to the CLI thread for later - * close and destruction. - * - * The actual close has to be delayed, so that cannot open another fd - * and bang into a still active qps_file ! - * - * The message looks after freeing the vio_fd, the qps_file and the mqb. + * Stops any read/write waiting and releases all memory, including the vfd + * itself if required.. */ static void -vio_fd_do_close(vio_fd vfd) +vio_vfd_do_close(vio_vfd vfd, free_keep_b free) { + if (vfd == NULL) + return ; + VTY_ASSERT_LOCKED() ; VTY_ASSERT_CLI_THREAD() ; - if (vty_cli_nexus) + if (vty_nexus) { if (vfd->f.qf != NULL) { assert(vfd->fd == qps_file_fd(vfd->f.qf)) ; vfd->f.qf = qps_file_free(vfd->f.qf) ; } ; - vio_fd_mqb_free(vfd) ; + + if (vfd->mqb != NULL) + { + mqb_free(vfd->mqb) ; + vfd->mqb = NULL ; + } ; } else { @@ -306,12 +363,13 @@ vio_fd_do_close(vio_fd vfd) } ; if (vfd->fd >= 0) - close(vfd->fd) ; + close(vfd->fd) ; /* ignores errors TODO */ vio_timer_reset(&vfd->read_timer) ; vio_timer_reset(&vfd->write_timer) ; - XFREE(MTYPE_VTY, vfd) ; + if (free == free_it) + XFREE(MTYPE_VTY, vfd) ; } ; /*------------------------------------------------------------------------------ @@ -322,16 +380,16 @@ vio_fd_do_close(vio_fd vfd) * * NB: this can done from any thread, but if not done from the CLI thread, * and there is a qf, a message must be sent to the CLI thread to actually - * implement: which passes the vio_fd to the CLI thread for later + * implement: which passes the vio_vfd to the CLI thread for later * close and destruction. * * The actual close has to be delayed, so that cannot open another fd * and bang into a still active qps_file ! * - * The message looks after freeing the vio_fd, the qps_file and the mqb. + * The message looks after freeing the vio_vfd, the qps_file and the mqb. */ -extern vio_fd -vio_fd_close(vio_fd vfd) +extern vio_vfd +vio_vfd_close(vio_vfd vfd) { VTY_ASSERT_LOCKED() ; @@ -340,9 +398,9 @@ vio_fd_close(vio_fd vfd) if (vfd->fd < 0) { - /* closing an inactive vio_fd -- make sure all is quiet */ + /* closing an inactive vio_vfd -- make sure all is quiet */ assert(!vfd->active) ; - if (vty_cli_nexus) + if (vty_nexus) { assert(vfd->f.qf == NULL) ; } @@ -355,19 +413,18 @@ vio_fd_close(vio_fd vfd) } else { - /* closing an active vio_fd */ + /* closing an active vio_vfd */ if (vty_is_cli_thread()) { /* In cli thread, so close directly */ - vio_fd_do_close(vfd) ; + vio_vfd_do_close(vfd, free_it) ; } else { /* Rats... have to send message to cli thread to close */ - struct vio_io_set_args* args = vio_fd_mqb_args(vfd) ; + struct vio_io_set_args* args = vio_vfd_mqb_args(vfd) ; args->close = true ; - args->die = true ; /* in case something goes ready before the close message * is processed, squelch. @@ -381,7 +438,7 @@ vio_fd_close(vio_fd vfd) vio_timer_squelch(&vfd->write_timer) ; assert(vfd == mqb_get_arg0(vfd->mqb)) ; - vio_fd_mqb_dispatch(vfd) ; + vio_vfd_mqb_dispatch(vfd) ; } ; } ; @@ -389,16 +446,16 @@ vio_fd_close(vio_fd vfd) } ; /*------------------------------------------------------------------------------ - * Set or unset read ready state on given vio_fd (if any) if it is active. + * Set or unset read ready state on given vio_vfd (if any) if it is active. * - * If setting read_on, starts any read timeout timer. + * If setting read_on, starts any read timeout timer (or stops it if 0). * If setting read off, stops any read timeout timer. * * NB: this can done from any thread, but if not done from the CLI thread, * a message must be sent to the CLI thread to actually implement. */ -extern on_off_t -vio_fd_set_read(vio_fd vfd, on_off_t on, vty_timer_time timeout) +extern on_off_b +vio_vfd_set_read(vio_vfd vfd, on_off_b on, vty_timer_time timeout) { struct vio_io_set_args* args ; @@ -422,7 +479,7 @@ vio_fd_set_read(vio_fd vfd, on_off_t on, vty_timer_time timeout) { assert(vfd->read_action != NULL) ; - if (vty_cli_nexus) + if (vty_nexus) { if (vfd->f.qf == NULL) { @@ -431,20 +488,20 @@ vio_fd_set_read(vio_fd vfd, on_off_t on, vty_timer_time timeout) vfd->fd, vfd) ; } ; qps_enable_mode(vfd->f.qf, qps_read_mnum, - vio_fd_qps_read_action) ; + vio_vfd_qps_read_action) ; } else { if (vfd->f.thread.read == NULL) vfd->f.thread.read = thread_add_read(vty_master, - vio_fd_thread_read_action, vfd, vfd->fd) ; + vio_vfd_thread_read_action, vfd, vfd->fd) ; } ; vio_timer_set(&vfd->read_timer, timeout) ; } else { - if (vty_cli_nexus) + if (vty_nexus) { if (vfd->f.qf != NULL) qps_disable_modes(vfd->f.qf, qps_read_mbit) ; @@ -462,19 +519,19 @@ vio_fd_set_read(vio_fd vfd, on_off_t on, vty_timer_time timeout) { /* In other threads, must send message to cli thread */ - args = vio_fd_mqb_args(vfd) ; + args = vio_vfd_mqb_args(vfd) ; args->readable = true ; args->read_on = on ; args->read_timeout = timeout ; vio_timer_squelch(&vfd->read_timer) ; - vio_fd_mqb_dispatch(vfd) ; + vio_vfd_mqb_dispatch(vfd) ; } ; return on ; } ; /*------------------------------------------------------------------------------ - * Set or unset write ready state on given vio_fd (if any) if it is active. + * Set or unset write ready state on given vio_vfd (if any) if it is active. * * If setting write_on, starts any write timeout timer. * If setting write off, stops any write timeout timer. @@ -482,8 +539,8 @@ vio_fd_set_read(vio_fd vfd, on_off_t on, vty_timer_time timeout) * NB: this can done from any thread, but if not done from the CLI thread, * a message must be sent to the CLI thread to actually implement. */ -extern on_off_t -vio_fd_set_write(vio_fd vfd, on_off_t on, vty_timer_time timeout) +extern on_off_b +vio_vfd_set_write(vio_vfd vfd, on_off_b on, vty_timer_time timeout) { struct vio_io_set_args* args ; @@ -507,7 +564,7 @@ vio_fd_set_write(vio_fd vfd, on_off_t on, vty_timer_time timeout) { assert(vfd->write_action != NULL) ; - if (vty_cli_nexus) + if (vty_nexus) { if (vfd->f.qf == NULL) { @@ -516,20 +573,20 @@ vio_fd_set_write(vio_fd vfd, on_off_t on, vty_timer_time timeout) vfd->fd, vfd) ; } ; qps_enable_mode(vfd->f.qf, qps_write_mnum, - vio_fd_qps_write_action) ; + vio_vfd_qps_write_action) ; } else { if (vfd->f.thread.write == NULL) vfd->f.thread.write = thread_add_write(vty_master, - vio_fd_thread_write_action, vfd, vfd->fd) ; + vio_vfd_thread_write_action, vfd, vfd->fd) ; } ; vio_timer_set(&vfd->write_timer, timeout) ; } else { - if (vty_cli_nexus) + if (vty_nexus) { if (vfd->f.qf != NULL) qps_disable_modes(vfd->f.qf, qps_write_mbit) ; @@ -547,12 +604,12 @@ vio_fd_set_write(vio_fd vfd, on_off_t on, vty_timer_time timeout) { /* In other threads, must send message to cli thread */ - args = vio_fd_mqb_args(vfd) ; + args = vio_vfd_mqb_args(vfd) ; args->writable = true ; args->write_on = on ; args->write_timeout = timeout ; vio_timer_squelch(&vfd->write_timer) ; - vio_fd_mqb_dispatch(vfd) ; + vio_vfd_mqb_dispatch(vfd) ; } ; return on ; @@ -567,19 +624,21 @@ vio_fd_set_write(vio_fd vfd, on_off_t on, vty_timer_time timeout) * message is yet to be procesed. */ static void -vio_fd_qps_read_action(qps_file qf, void* file_info) +vio_vfd_qps_read_action(qps_file qf, void* file_info) { - vio_fd vfd = file_info ; + vio_vfd vfd ; - VTY_LOCK() ; VTY_ASSERT_CLI_THREAD() ; + VTY_LOCK() ; + + vfd = file_info ; assert((vfd->fd == qf->fd) && (vfd->f.qf == qf)) ; qps_disable_modes(vfd->f.qf, qps_read_mbit) ; vio_timer_unset(&vfd->read_timer) ; - vio_fd_do_read_action(vfd) ; + vio_vfd_do_read_action(vfd) ; VTY_UNLOCK() ; } ; @@ -593,19 +652,21 @@ vio_fd_qps_read_action(qps_file qf, void* file_info) * message is yet to be procesed. */ static int -vio_fd_thread_read_action(struct thread *thread) +vio_vfd_thread_read_action(struct thread *thread) { - vio_fd vfd = THREAD_ARG (thread); + vio_vfd vfd ; - VTY_LOCK() ; VTY_ASSERT_CLI_THREAD() ; + VTY_LOCK() ; + + vfd = THREAD_ARG(thread); assert(vfd->fd == THREAD_FD(thread)) ; vfd->f.thread.read = NULL ; /* implicitly */ vio_timer_unset(&vfd->read_timer) ; - vio_fd_do_read_action(vfd) ; + vio_vfd_do_read_action(vfd) ; VTY_UNLOCK() ; return 0 ; @@ -620,19 +681,19 @@ vio_fd_thread_read_action(struct thread *thread) * message is yet to be procesed. */ static void -vio_fd_qps_write_action(qps_file qf, void* file_info) +vio_vfd_qps_write_action(qps_file qf, void* file_info) { - vio_fd vfd = file_info ; + vio_vfd vfd = file_info ; - VTY_LOCK() ; VTY_ASSERT_CLI_THREAD() ; + VTY_LOCK() ; assert((vfd->fd == qf->fd) && (vfd->f.qf == qf)) ; qps_disable_modes(vfd->f.qf, qps_write_mbit) ; vio_timer_unset(&vfd->write_timer) ; - vio_fd_do_write_action(vfd) ; + vio_vfd_do_write_action(vfd) ; VTY_UNLOCK() ; } ; @@ -646,19 +707,19 @@ vio_fd_qps_write_action(qps_file qf, void* file_info) * message is yet to be procesed. */ static int -vio_fd_thread_write_action(struct thread *thread) +vio_vfd_thread_write_action(struct thread *thread) { - vio_fd vfd = THREAD_ARG (thread); + vio_vfd vfd = THREAD_ARG (thread); - VTY_LOCK() ; VTY_ASSERT_CLI_THREAD() ; + VTY_LOCK() ; assert(vfd->fd == THREAD_FD(thread)) ; vio_timer_unset(&vfd->write_timer) ; vfd->f.thread.write = NULL ; /* implicitly */ - vio_fd_do_write_action(vfd) ; + vio_vfd_do_write_action(vfd) ; VTY_UNLOCK() ; return 0 ; @@ -675,57 +736,31 @@ vio_fd_thread_write_action(struct thread *thread) * further dispatch is required. When it has been dequeued and processed, * it is marked inactive. * - * If the vfd is closed while the message is active, it is marked to die, + * If the vfd is closed while the message is active, it is marked to close, * which it will do when it is dequeued and actioned. */ -static void vio_fd_set_action(mqueue_block mqb, mqb_flag_t flag) ; +static void vio_vfd_set_action(mqueue_block mqb, mqb_flag_t flag) ; /*------------------------------------------------------------------------------ * Get mqb for the given vfd -- make one if required. */ static struct vio_io_set_args* -vio_fd_mqb_args(vio_fd vfd) +vio_vfd_mqb_args(vio_vfd vfd) { VTY_ASSERT_LOCKED() ; if (vfd->mqb == NULL) - vfd->mqb = mqb_init_new(NULL, vio_fd_set_action, vfd) ; + vfd->mqb = mqb_init_new(NULL, vio_vfd_set_action, vfd) ; return mqb_get_args(vfd->mqb) ; } ; /*------------------------------------------------------------------------------ - * Free mqb for the given vfd -- if any. - */ -static void -vio_fd_mqb_free(vio_fd vfd) -{ - VTY_ASSERT_LOCKED() ; - - if (vfd->mqb != NULL) - { - struct vio_io_set_args* args = mqb_get_args(vfd->mqb) ; - - if (args->active) - { - args->die = true ; - mqb_set_arg0(vfd->mqb, NULL) ; - } - else - { - mqb_free(vfd->mqb) ; - } ; - - vfd->mqb = NULL ; - } ; -} ; - -/*------------------------------------------------------------------------------ * Dispatch mqb, if not already active */ static void -vio_fd_mqb_dispatch(vio_fd vfd) +vio_vfd_mqb_dispatch(vio_vfd vfd) { struct vio_io_set_args* args = mqb_get_args(vfd->mqb) ; @@ -741,33 +776,43 @@ vio_fd_mqb_dispatch(vio_fd vfd) /*------------------------------------------------------------------------------ * Action routine for the read/write on/off setting message. * - * If the mqb is marked to die, then it and any qps_file it points to have been - * cut loose, and now is the time to close the fd and release the qps_file, - * along with releasing the mqb. + * If the mqb is marked to close, then it and any qps_file it points to have + * been cut loose, and now is the time to close the fd and release the + * qps_file, along with releasing the mqb. + * + * If the mqb is being revoked */ static void -vio_fd_set_action(mqueue_block mqb, mqb_flag_t flag) +vio_vfd_set_action(mqueue_block mqb, mqb_flag_t flag) { - struct vio_io_set_args* args = mqb_get_args(mqb) ; - vio_fd vfd = mqb_get_arg0(mqb) ; + struct vio_io_set_args* args ; + vio_vfd vfd ; - VTY_LOCK() ; VTY_ASSERT_CLI_THREAD() ; + VTY_LOCK() ; + + args = mqb_get_args(mqb) ; + vfd = mqb_get_arg0(mqb) ; args->active = false ; - if ((flag != mqb_destroy) && (!args->die)) + if ((flag != mqb_destroy) && (!args->close)) { if (args->readable) - vio_fd_set_read(vfd, args->read_on, args->read_timeout) ; + vio_vfd_set_read(vfd, args->read_on, args->read_timeout) ; if (args->writable) - vio_fd_set_write(vfd, args->write_on, args->write_timeout) ; + vio_vfd_set_write(vfd, args->write_on, args->write_timeout) ; } else { - if (args->close) - vio_fd_do_close(vfd) ; - mqb_free(mqb) ; + /* Revoke and/or close. + * + * If close can free the vfd. + * + * If just revoke (should not happen), then cannot free the vfd, because + * somewhere there can be a dangling reference. + */ + vio_vfd_do_close(vfd, args->close) ; /* frees the mqb */ } ; VTY_UNLOCK() ; @@ -779,7 +824,7 @@ vio_fd_set_action(mqueue_block mqb, mqb_flag_t flag) * */ -static void vio_accept(vio_fd vfd, void* info) ; +static void vio_accept(vio_vfd vfd, void* info) ; /*------------------------------------------------------------------------------ * Create a new listener object for the newly opened listener socket. @@ -790,7 +835,7 @@ static void vio_accept(vio_fd vfd, void* info) ; * Returns address of newly created listener structure. */ extern vio_listener -vio_listener_new(int fd, vio_fd_accept* accept_action) +vio_listener_new(int fd, vio_vfd_accept* accept_action) { vio_listener listener ; @@ -800,12 +845,12 @@ vio_listener_new(int fd, vio_fd_accept* accept_action) listener = XCALLOC(MTYPE_VTY, sizeof(struct vio_listener)) ; /* sets the next pointer to NULL */ - listener->vfd = vio_fd_new(fd, vfd_listener, vfd_io_read, listener) ; + listener->vfd = vio_vfd_new(fd, vfd_listener, vfd_io_read, listener) ; listener->accept_action = accept_action ; - vio_fd_set_read_action(listener->vfd, vio_accept) ; - vio_fd_set_read(listener->vfd, on, 0) ; + vio_vfd_set_read_action(listener->vfd, vio_accept) ; + vio_vfd_set_read(listener->vfd, on, 0) ; return listener ; } ; @@ -822,7 +867,7 @@ vio_listener_close(vio_listener listener) VTY_ASSERT_LOCKED() ; VTY_ASSERT_CLI_THREAD() ; - vio_fd_close(listener->vfd) ; + vio_vfd_close(listener->vfd) ; XFREE(MTYPE_VTY, listener) ; } ; @@ -832,7 +877,7 @@ vio_listener_close(vio_listener listener) * info points at the listener object. */ static void -vio_accept(vio_fd vfd, void* info) +vio_accept(vio_vfd vfd, void* info) { vio_listener listener ; @@ -842,6 +887,8 @@ vio_accept(vio_fd vfd, void* info) listener = info ; assert(vfd == listener->vfd) ; + vio_vfd_set_read(listener->vfd, on, 0) ; + listener->accept_action(vfd->fd) ; } ; @@ -902,7 +949,6 @@ static void vio_timer_squelch(vio_timer_t* timer) { VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; timer->squelch = true ; } ; @@ -920,7 +966,7 @@ vio_timer_reset(vio_timer_t* timer) if (timer->t.anon != NULL) { - if (vty_cli_nexus) + if (vty_nexus) qtimer_free(timer->t.qtr) ; else thread_cancel(timer->t.thread) ; @@ -953,7 +999,7 @@ vio_timer_set(vio_timer_t* timer, vty_timer_time time) assert(timer->action != NULL) ; - if (vty_cli_nexus) + if (vty_nexus) { if (timer->t.qtr == NULL) /* allocate qtr if required */ timer->t.qtr = qtimer_init_new(NULL, vty_cli_nexus->pile, @@ -983,7 +1029,7 @@ vio_timer_unset(vio_timer_t* timer) if (timer->active) { - if (vty_cli_nexus) + if (vty_nexus) { assert(timer->t.qtr != NULL) ; qtimer_unset(timer->t.qtr) ; @@ -1007,13 +1053,15 @@ vio_timer_unset(vio_timer_t* timer) static void vio_timer_qtr_action(qtimer qtr, void* timer_info, qtime_t when) { - vio_timer_t* timer = timer_info ; + vio_timer_t* timer ; vty_timer_time time ; - VTY_LOCK() ; VTY_ASSERT_CLI_THREAD() ; + VTY_LOCK() ; + + timer = timer_info ; - if (!timer->squelch) /* do nothing if squelched */ + if ((timer->action != NULL) && (!timer->squelch)) { time = timer->action(timer, timer->action_info) ; if (time != 0) @@ -1036,15 +1084,16 @@ vio_timer_qtr_action(qtimer qtr, void* timer_info, qtime_t when) static int vio_timer_thread_action(struct thread *thread) { - vio_timer_t* timer = THREAD_ARG (thread); + vio_timer_t* timer ; vty_timer_time time ; - VTY_LOCK() ; VTY_ASSERT_CLI_THREAD() ; + VTY_LOCK() ; + timer = THREAD_ARG(thread) ; timer->t.thread = NULL ; /* implicitly */ - if (!timer->squelch) /* do nothing if squelched */ + if ((timer->action != NULL) && (!timer->squelch)) { time = timer->action(timer, timer->action_info) ; if (time != 0) diff --git a/lib/vty_io_basic.h b/lib/vty_io_basic.h index a2b1cbb3..00ff923b 100644 --- a/lib/vty_io_basic.h +++ b/lib/vty_io_basic.h @@ -27,7 +27,7 @@ #include "misc.h" -#include "vty.h" +//#include "vty_local.h" #include "qpselect.h" #include "thread.h" @@ -55,15 +55,23 @@ typedef enum vfd_type vfd_type_t ; enum vfd_io_type /* NB: *bit*significant* */ { vfd_io_none = 0, - vfd_io_read = 1, - vfd_io_write = 2, - vfd_io_read_write = 3, + + vfd_io_read = BIT(0), + vfd_io_write = BIT(1), + vfd_io_read_write = vfd_io_read | vfd_io_write, + + vfd_io_append = BIT(2), + vfd_io_blocking = BIT(3), } ; typedef enum vfd_io_type vfd_io_type_t ; /*------------------------------------------------------------------------------ * Timers -- implemented as qtimer or thread timer, depending on environment. + * + * Timer action function returns a new value for the timer; 0 => off. */ +typedef unsigned long vty_timer_time ; /* Time out time in seconds */ + typedef struct vio_timer vio_timer_t ; typedef vty_timer_time vio_timer_action(vio_timer_t* timer, void* action_info) ; @@ -88,10 +96,11 @@ struct vio_timer * Implemented as qps_file or as read/write thread, depending on the * environment. */ -typedef struct vio_fd* vio_fd ; -typedef void vio_fd_action(vio_fd vfd, void* action_info) ; +typedef struct vio_vfd* vio_vfd ; + +typedef void vio_vfd_action(vio_vfd vfd, void* action_info) ; -struct vio_fd +struct vio_vfd { int fd ; bool active ; /* used for close message */ @@ -99,8 +108,8 @@ struct vio_fd vfd_type_t type ; /* used for half-close */ vfd_io_type_t io_type ; /* read, write, read/write */ - vio_fd_action* read_action ; - vio_fd_action* write_action ; + vio_vfd_action* read_action ; + vio_vfd_action* write_action ; vio_timer_t read_timer ; vio_timer_t write_timer ; @@ -127,39 +136,41 @@ struct vio_fd typedef struct vio_listener* vio_listener ; -typedef void vio_fd_accept(int fd) ; +typedef void vio_vfd_accept(int fd) ; struct vio_listener { vio_listener next ; /* ssl type list */ - vio_fd vfd ; - vio_fd_accept* accept_action ; + vio_vfd vfd ; + vio_vfd_accept* accept_action ; }; /*============================================================================== * Functions */ -extern vio_fd vio_fd_new(int fd, vfd_type_t type, +extern int uty_vfd_file_open(const char* name, vfd_io_type_t io_type) ; + +extern vio_vfd vio_vfd_new(int fd, vfd_type_t type, vfd_io_type_t io_type, void* action_info) ; -extern void vio_fd_set_fd(vio_fd vfd, int fd, vfd_type_t type, +extern void vio_vfd_set_fd(vio_vfd vfd, int fd, vfd_type_t type, vfd_io_type_t io_type) ; -extern void vio_fd_set_read_action(vio_fd vfd, vio_fd_action* action) ; -extern void vio_fd_set_write_action(vio_fd vfd, vio_fd_action* action) ; -extern void vio_fd_set_read_timeout_action(vio_fd vfd, +extern void vio_vfd_set_read_action(vio_vfd vfd, vio_vfd_action* action) ; +extern void vio_vfd_set_write_action(vio_vfd vfd, vio_vfd_action* action) ; +extern void vio_vfd_set_read_timeout_action(vio_vfd vfd, vio_timer_action* action) ; -extern void vio_fd_set_write_timeout_action(vio_fd vfd, +extern void vio_vfd_set_write_timeout_action(vio_vfd vfd, vio_timer_action* action) ; -extern void vio_fd_set_action_info(vio_fd vfd, void* action_info) ; -extern vio_fd vio_fd_half_close(vio_fd vfd) ; -extern vio_fd vio_fd_close(vio_fd vfd) ; -extern on_off_t vio_fd_set_read(vio_fd vfd, on_off_t on, +extern void vio_vfd_set_action_info(vio_vfd vfd, void* action_info) ; +extern vio_vfd vio_vfd_read_close(vio_vfd vfd) ; +extern vio_vfd vio_vfd_close(vio_vfd vfd) ; +extern on_off_b vio_vfd_set_read(vio_vfd vfd, on_off_b on, vty_timer_time timeout) ; -extern on_off_t vio_fd_set_write(vio_fd vfd, on_off_t on, +extern on_off_b vio_vfd_set_write(vio_vfd vfd, on_off_b on, vty_timer_time timeout) ; -Inline int vio_fd_fd(vio_fd vfd) ; +Inline int vio_vfd_fd(vio_vfd vfd) ; -extern vio_listener vio_listener_new(int fd, vio_fd_accept* accept) ; +extern vio_listener vio_listener_new(int fd, vio_vfd_accept* accept) ; extern void vio_listener_close(vio_listener listener) ; extern void vio_timer_init(vio_timer_t* timer, vio_timer_action* action, @@ -175,10 +186,10 @@ extern void vio_timer_unset(vio_timer_t* timer) ; */ /*------------------------------------------------------------------------------ - * Return the fd from a vio_fd structure + * Return the fd from a vio_vfd structure */ Inline int -vio_fd_fd(vio_fd vfd) +vio_vfd_fd(vio_vfd vfd) { return vfd->fd ; } ; diff --git a/lib/vty_io_file.c b/lib/vty_io_file.c index ed2c35e8..2d17276a 100644 --- a/lib/vty_io_file.c +++ b/lib/vty_io_file.c @@ -1,7 +1,6 @@ -/* VTY Log Functions - * Copyright (C) 1997, 98 Kunihiro Ishiguro +/* VTY I/O for Files * - * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman * * This file is part of GNU Zebra. * @@ -20,2589 +19,304 @@ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ +#include "misc.h" -#include "zebra.h" +#include "command_local.h" -#include "vty.h" -#include "vty_io.h" -#include "vty_cli.h" -#include "qstring.h" -#include "keystroke.h" - -#include "memory.h" - -#include "prefix.h" -#include "filter.h" -#include "privs.h" -#include "sockunion.h" -#include "network.h" - -#include <arpa/telnet.h> -#include <sys/un.h> /* for VTYSH */ -#include <sys/socket.h> - -#define VTYSH_DEBUG 0 +#include "vty_io_file.h" +#include "vty_io_basic.h" /*============================================================================== - * VTY Log Output -- provides a "vty" interface to logging. + * VTY File Output * - * Once a VTY_LOG object is created, can be used as an (output only) vty. + * This is for input and output of configuration files and piped stuff. * - * All output is sent to the logging system, - * - * - * During command processing the output sent here is held until the command - * completes. + * When reading the configuration (and piped stuff in the configuration) I/O + * is blocking... nothing else can run while this is going on. Otherwise, + * all I/O is non-blocking. */ -static int uty_config_write(vty_io vio, bool all) ; - -/*------------------------------------------------------------------------------ - * VTY output function -- cf fprintf - * - * Returns: >= 0 => OK - * < 0 => failed (see errno) - */ -extern int -uty_out (struct vty *vty, const char *format, ...) -{ - int result; - VTY_ASSERT_LOCKED() ; - va_list args; - va_start (args, format); - result = uty_vout(vty, format, args); - va_end (args); - return result; -} - -/*------------------------------------------------------------------------------ - * VTY output function -- cf vfprintf - * - * Returns: >= 0 => OK - * < 0 => failed (see errno) - * - * NB: for VTY_TERM and for VTY_SHELL_SERV -- this is command output: - * - * * MAY NOT do any command output if !cmd_enabled - * - * * first, the life of a vty is not guaranteed unless cmd_in_progress, - * so should not attempt to use a vty anywhere other than command - * execution. - * - * * second, cmd_out_enabled is false most of the time, and is only - * set true when a command completes, and it is time to write away - * the results. - * - * * all output is placed in the vio->cmd_obuf. When the command completes, - * the contents of the cmd_obuf will be written away -- subject to line - * control. - * - * * output is discarded if the vty is no longer write_open - */ -extern int -uty_vout(struct vty *vty, const char *format, va_list args) -{ - vty_io vio ; - int ret ; - - VTY_ASSERT_LOCKED() ; - - vio = vty->vio ; - - switch (vio->type) - { - case VTY_STDOUT: - case VTY_SHELL: - ret = vprintf (format, args) ; - break ; - - case VTY_STDERR: - ret = vfprintf (stderr, format, args) ; - break ; - - case VTY_CONFIG_WRITE: - ret = vio_fifo_vprintf(&vio->cmd_obuf, format, args) ; - if ((ret > 0) && vio_fifo_full_lump(&vio->cmd_obuf)) - ret = uty_config_write(vio, false) ; - break ; - - case VTY_TERM: - case VTY_SHELL_SERV: - assert(vio->cmd_in_progress) ; - - if (!vio->sock.write_open) - return 0 ; /* discard output if not open ! */ - /* fall through.... */ - - case VTY_CONFIG_READ: - ret = vio_fifo_vprintf(&vio->cmd_obuf, format, args) ; - break ; - - default: - zabort("impossible VTY type") ; - } ; - - return ret ; -} ; - -/*------------------------------------------------------------------------------ - * Clear the contents of the command output FIFO etc. - * - * NB: does not change any of the cli_blocked/cmd_in_progress/cli_wait_more/etc - * flags -- competent parties must deal with those - */ -extern void -uty_out_clear(vty_io vio) -{ - VTY_ASSERT_LOCKED() ; - - vio_fifo_clear(&vio->cmd_obuf) ; - - if (vio->cmd_lc != NULL) - vio_lc_clear(vio->cmd_lc) ; -} ; - -/*------------------------------------------------------------------------------ - * Flush the contents of the command output FIFO to the given file. - * - * Takes no notice of any errors ! - */ -extern void -uty_out_fflush(vty_io vio, FILE* file) -{ - char* src ; - size_t have ; - - VTY_ASSERT_LOCKED() ; - - fflush(file) ; - - while ((src = vio_fifo_get_lump(&vio->cmd_obuf, &have)) != NULL) - { - fwrite(src, 1, have, file) ; - vio_fifo_got_upto(&vio->cmd_obuf, src + have) ; - } ; - - fflush(file) ; -} ; /*============================================================================== * Prototypes. */ -static void uty_sock_init_new(vio_sock sock, int fd, void* info) ; -static void uty_sock_half_close(vio_sock sock) ; -static void uty_sock_close(vio_sock sock) ; - -static void vty_read_qnexus (qps_file qf, void* file_info) ; -static void vty_write_qnexus (qps_file qf, void* file_info) ; -static void vty_timer_qnexus (qtimer qtr, void* timer_info, qtime_t when) ; - -static int vty_read_thread (struct thread *thread) ; -static int vty_write_thread (struct thread *thread) ; -static int vty_timer_thread (struct thread *thread) ; - -static void vtysh_read_qnexus (qps_file qf, void* file_info) ; -static int vtysh_read_thread (struct thread *thread) ; - -static enum vty_readiness uty_write(vty_io vio) ; - -/*============================================================================== - * Creation and destruction of VTY objects - */ /*------------------------------------------------------------------------------ - * Allocate new vty struct - * - * Allocates and initialises basic vty and vty_io structures, setting the - * given type. - * - * Note that where is not setting up a vty_sock, this *may* be called from - * any thread. * - * NB: may not create a VTY_CONFIG_WRITE type vty directly - * - * see: vty_open_config_write() and vty_close_config_write() - * - * NB: the sock_fd *must* be valid for VTY_TERM and VTY_SHELL_SERV. - * (So MUST be in the CLI thread to set those up !) - * - * the sock_fd is ignored for everything else. - * - * Returns: new vty - */ -extern struct vty * -uty_new(enum vty_type type, int sock_fd) -{ - struct vty *vty ; - struct vty_io* vio ; - - VTY_ASSERT_LOCKED() ; - - /* If this is a VTY_TERM or a VTY_SHELL, place */ - switch (type) - { - case VTY_TERM: /* Require fd -- Telnet session */ - case VTY_SHELL_SERV: /* Require fd -- Unix socket */ - assert(sock_fd >= 0) ; - break ; - - case VTY_CONFIG_WRITE: - zabort("may not make a new VTY_CONFIG_WRITE VTY") ; - break ; - - case VTY_CONFIG_READ: - case VTY_STDOUT: - case VTY_STDERR: - case VTY_SHELL: - sock_fd = -1 ; /* No fd -- output to stdout/stderr */ - break ; - - default: - zabort("unknown VTY type") ; - } ; - - /* Basic allocation */ - vty = XCALLOC (MTYPE_VTY, sizeof (struct vty)); - vio = XCALLOC (MTYPE_VTY, sizeof (struct vty_io)) ; - - vty->vio = vio ; - vio->vty = vty ; - - /* Zeroising the vty_io structure has set: - * - * name = NULL -- no name, yet - * - * vio_list both pointers NULL - * mon_list both pointers NULL - * - * half_closed = 0 -- NOT half closed (important !) - * closed = 0 -- NOT closed (important !) - * close_reason = NULL -- no reason, yet - * - * real_type = 0 -- not material - * file_fd = 0 -- not material - * file_error = 0 -- not material - * - * key_stream = NULL -- no key stream (always empty, at EOF) - * - * cli_drawn = 0 -- not drawn - * cli_dirty = 0 -- not dirty - * cli_prompt_len = 0 ) - * cli_extra_len = 0 ) not material - * cli_echo_suppress = 0 ) - * - * cli_prompt_node = 0 -- not material - * cli_prompt_set = 0 -- so prompt needs to be constructed - * - * cli_blocked = 0 -- not blocked - * cmd_in_progress = 0 -- no command in progress - * cmd_out_enabled = 0 -- command output is disabled - * cli_wait_more = 0 -- not waiting for response to "--more--" - * - * cli_more_enabled = 0 -- not enabled for "--more--" - * - * cmd_out_done = 0 -- not material - * - * cli_do = 0 == cli_do_nothing - * - * cmd_lc = NULL -- no line control - * - * fail = 0 -- no login failures yet - * - * hist = empty vector - * hp = 0 -- at the beginning - * hindex = 0 -- the beginning - * - * width = 0 -- unknown console width - * height = 0 -- unknown console height - * - * lines = 0 -- no limit - * lines_set = 0 -- no explicit setting - * - * monitor = 0 -- not a monitor - * monitor_busy = 0 -- not a busy monitor - * - * config = 0 -- not holder of "config" mode - */ - confirm(cli_do_nothing == 0) ; - confirm(AUTH_NODE == 0) ; /* default node type */ - - vio->type = type ; - - /* Zeroising the vty structure has set: - * - * node = 0 TODO: something better for node value ???? - * buf = NULL -- no command line, yet - * parsed = NULL -- no parsed command, yet - * lineno = 0 -- nothing read, yet - * index = NULL -- nothing, yet - * index_sub = NULL -- nothing, yet - */ - - /* Initialise the vio_sock, */ - uty_sock_init_new(&vio->sock, sock_fd, vio) ; - - /* Make sure all buffers etc. are initialised clean and empty. - * - * Note that no buffers are actually allocated at this stage. - */ - qs_init_new(&vio->cli_prompt_for_node, 0) ; - - qs_init_new(&vio->cl, 0) ; - qs_init_new(&vio->clx, 0) ; - - vio_fifo_init_new(&vio->cli_obuf, 2 * 1024) ; /* allocate in 2K lumps */ - - vio_fifo_init_new(&vio->cmd_obuf, 8 * 1024) ; - - /* Place on list of known vio/vty */ - sdl_push(vio_list_base, vio, vio_list) ; - - return vty; -} ; - -/*------------------------------------------------------------------------------ - * Create new vty of type VTY_TERM -- ie attached to a telnet session. * - * Returns: new vty */ -static struct vty * -uty_new_term(int sock_fd, union sockunion *su) +extern cmd_return_code_t +uty_file_read_open(vty_io vio, qstring name, bool reflect) { - struct vty *vty ; - vty_io vio ; - enum vty_readiness ready ; - - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - /* Allocate new vty structure and set up default values. */ - vty = uty_new (VTY_TERM, sock_fd) ; - vio = vty->vio ; - - /* Allocate and initialise a keystroke stream TODO: CSI ?? */ - vio->key_stream = keystroke_stream_new('\0', uty_cli_iac_callback, vio) ; - - /* Set the socket action functions */ - if (vty_cli_nexus) - { - vio->sock.action.read.qnexus = vty_read_qnexus ; - vio->sock.action.write.qnexus = vty_write_qnexus ; - vio->sock.action.timer.qnexus = vty_timer_qnexus ; - } - else - { - vio->sock.action.read.thread = vty_read_thread ; - vio->sock.action.write.thread = vty_write_thread ; - vio->sock.action.timer.thread = vty_timer_thread ; - } ; - - /* The text form of the address identifies the VTY */ - vio->name = sockunion_su2str (su, MTYPE_VTY_NAME); - - /* Set the initial node */ - if (no_password_check) - { - if (restricted_mode) - vty->node = RESTRICTED_NODE; - else if (host.advanced) - vty->node = ENABLE_NODE; - else - vty->node = VIEW_NODE; - } - else - vty->node = AUTH_NODE; - - /* Pick up current timeout setting */ - vio->sock.v_timeout = vty_timeout_val; - - /* Use global 'lines' setting, as default. May be -1 => unset */ - vio->lines = host.lines ; - - /* For VTY_TERM use vio_line_control for '\n' and "--more--" */ - vio->cmd_lc = vio_lc_init_new(NULL, 0, 0) ; - uty_set_height(vio) ; /* set initial state */ - - /* Initialise the CLI, ready for start-up messages etc. */ - uty_cli_init(vio) ; - - /* Reject connection if password isn't set, and not "no password" */ - if ((host.password == NULL) && (host.password_encrypt == NULL) - && ! no_password_check) - { - uty_half_close (vio, "Vty password is not set."); - vty = NULL; - } - else - { - /* Say hello to the world. */ - vty_hello (vty); - - if (! no_password_check) - uty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE, - VTY_NEWLINE, VTY_NEWLINE); - } ; + const char* name_str ; + int fd ; + vio_vf vf ; + vfd_io_type_t iot ; - /* Now start the CLI and set a suitable state of readiness */ - ready = uty_cli_start(vio) ; - uty_sock_set_readiness(&vio->sock, ready) ; + name_str = qs_make_string(name) ; - return vty; -} ; + iot = vfd_io_read | vfd_io_blocking ; /* TODO blocking */ -/*------------------------------------------------------------------------------ - * Create new vty of type VTY_SHELL_SERV -- ie attached to a vtysh session. - * - * Returns: new vty - */ -static struct vty * -uty_new_shell_serv(int sock_fd) -{ - struct vty *vty ; - vty_io vio ; + /* Do the basic file open. */ + fd = uty_vfd_file_open(name_str, iot) ; - VTY_ASSERT_LOCKED() ; - - /* Allocate new vty structure and set up default values. */ - vty = uty_new (VTY_SHELL_SERV, sock_fd) ; - vio = vty->vio ; - - /* Set the action functions */ - if (vty_cli_nexus) + if (fd < 0) { - vio->sock.action.read.qnexus = vtysh_read_qnexus ; - vio->sock.action.write.qnexus = vty_write_qnexus ; - vio->sock.action.timer.qnexus = NULL ; - } - else - { - vio->sock.action.read.thread = vtysh_read_thread ; - vio->sock.action.write.thread = vty_write_thread ; - vio->sock.action.timer.thread = NULL ; - } ; - - vty->node = VIEW_NODE; + uty_out(vio, "%% Could not open input file %s\n", name_str) ; - /* Kick start the CLI etc. */ - uty_sock_set_readiness(&vio->sock, write_ready) ; - - return vty; -} ; - -/*------------------------------------------------------------------------------ - * Set/Clear "monitor" state: - * - * set: if VTY_TERM and not already "monitor" (and write_open !) - * clear: if is "monitor" - */ -extern void -uty_set_monitor(vty_io vio, bool on) -{ - VTY_ASSERT_LOCKED() ; - - if (on && !vio->monitor) - { - if ((vio->type == VTY_TERM) && vio->sock.write_open) - { - vio->monitor = 1 ; - sdl_push(vio_monitors_base, vio, mon_list) ; - } ; - } - else if (!on && vio->monitor) - { - vio->monitor = 0 ; - sdl_del(vio_monitors_base, vio, mon_list) ; + return CMD_WARNING ; } -} ; - -/*------------------------------------------------------------------------------ - * Return "name" of VTY - * - * For VTY_TERM this is the IP address of the far end of the telnet connection. - */ -extern const char* -uty_get_name(vty_io vio) -{ - return (vio->name != NULL) ? vio->name : "?" ; -} ; - -/*------------------------------------------------------------------------------ - * Closing down VTY for reading. - * - * For VTY_TERM (must be in CLI thread): - * - * * shut the socket for reading - * * discard all buffered input, setting it to "EOF" - * * turns off any monitor status ! - * * drop down to RESTRICTED_NODE - * - * For VTY_SHELL_SERV (must be in CLI thread): - * - * * shut the socket for reading - * * discard all buffered input - * * drop down to RESTRICTED_NODE - * - * In all cases: - * - * * place on death watch - * * set the vty half_closed - * * sets the reason for closing (if any given) - * - * For VTY_TERM and VTY_SHELL_SERV, when the output side has emptied out all - * the buffers, the VTY is closed. - * - * May already have set the vio->close_reason, or can set it now. (Passing a - * NULL reason has no effect on any existing posted reason.) - */ -extern void -uty_half_close (vty_io vio, const char* reason) -{ - VTY_ASSERT_LOCKED() ; - - if (vio->half_closed) - return ; - - if (reason != NULL) - vio->close_reason = reason ; - - /* Do the file side of things - * - * Note that half closing the file sets a new timeout, sets read off - * and write on. - */ - uty_sock_half_close(&vio->sock) ; - uty_set_monitor(vio, 0) ; - - /* Discard everything in the keystroke stream and force it to EOF */ - if (vio->key_stream != NULL) - keystroke_stream_set_eof(vio->key_stream) ; - - /* Turn off "--more--" so that all output clears without interruption. - * - * If is sitting on a "--more--" prompt, then exit the wait_more CLI. - */ - vio->cli_more_enabled = 0 ; - - if (vio->cli_more_wait) - uty_cli_exit_more_wait(vio) ; - - /* If a command is not in progress, enable output, which will clear - * the output buffer if there is anything there, plus any close reason, - * and then close. - * - * If command is in progress, then this process will start when it - * completes. - */ - if (!vio->cmd_in_progress) - vio->cmd_out_enabled = 1 ; - - /* Make sure no longer holding the config symbol of power */ - uty_config_unlock(vio->vty, RESTRICTED_NODE) ; - /* Log closing of VTY_TERM */ - if (vio->type == VTY_TERM) - uzlog (NULL, LOG_INFO, "Vty connection (fd %d) close", vio->sock.fd) ; + /* OK -- now push the new input onto the vin_stack. */ + vf = uty_vf_new(vio, name_str, fd, vfd_file, iot) ; + uty_vin_open(vio, vf, VIN_FILE, NULL, NULL, 16 * 1024) ; - /* Move to the death watch list */ - sdl_del(vio_list_base, vio, vio_list) ; - sdl_push(vio_death_watch, vio, vio_list) ; + vf->parse_type = cmd_parse_strict ; + vf->reflect_enabled = reflect ; - vio->half_closed = 1 ; + return CMD_SUCCESS ; } ; /*------------------------------------------------------------------------------ - * Closing down VTY. * - * Shuts down everything and discards all buffers etc. etc. * - * If cmd_in_progress, cannot complete the process -- but sets the closed - * flag. - * - * Can call vty_close() any number of times. - * - * The vty structure is placed on death watch, which will finally free the - * structure once no longer cmd_in_progress. - */ -extern void -uty_close (vty_io vio) -{ - VTY_ASSERT_LOCKED() ; - - /* Empty all the output buffers */ - vio_fifo_reset_keep(&vio->cli_obuf) ; - vio_fifo_reset_keep(&vio->cmd_obuf) ; - vio->cmd_lc = vio_lc_reset_free(vio->cmd_lc) ; - - /* If not already closed, close. */ - if (!vio->closed) - { - uty_half_close(vio, NULL) ; /* place on death watch -- if not - already done */ - if (vio->type == VTY_TERM) - uty_cli_close(vio) ; /* tell the CLI to stop */ - - vio->closed = 1 ; /* now closed (stop uty_write() - from recursing) */ - - if (vio->sock.write_open) - uty_write(vio) ; /* last gasp attempt */ - - uty_sock_close(&vio->sock) ; - - } ; - - /* Nothing more should happen, so can now release almost everything, - * the exceptions being the things that are related to a cmd_in_progress. - * - * All writing to buffers is suppressed, and as the sock has been closed, - * there will be no more read_ready or write_ready events. - */ - if (vio->name != NULL) - XFREE(MTYPE_VTY_NAME, vio->name) ; - - vio->key_stream = keystroke_stream_free(vio->key_stream) ; - - qs_reset(&vio->cli_prompt_for_node, keep_it) ; - qs_reset(&vio->cl, keep_it) ; - - { - qstring line ; - while ((line = vector_ream(vio->hist, keep_it)) != NULL) - qs_reset_free(line) ; - } ; - - /* The final stage cannot be completed if cmd_in_progress. - * - * The clx is pointed at by vty->buf -- containing the current command. - * - * Once everything is released, can take the vty off death watch, and - * release the vio and the vty. - */ - if (!vio->cmd_in_progress) - { - qs_reset(&vio->clx, keep_it) ; - vio->vty->buf = NULL ; - } ; -} ; - -/*============================================================================== - * For writing configuration file by command, temporarily redirect output to - * an actual file. */ - -/*------------------------------------------------------------------------------ - * Set the given fd as the VTY_FILE output. - */ -extern void -vty_open_config_write(struct vty* vty, int fd) +extern cmd_return_code_t +uty_file_write_open(vty_io vio, qstring name, bool append) { - vty_io vio ; - - VTY_LOCK() ; + const char* name_str ; + int fd ; + vio_vf vf ; + vfd_io_type_t iot ; - vio = vty->vio ; + iot = vfd_io_write | vfd_io_blocking ; /* TODO blocking */ - assert((vio->type != VTY_CONFIG_WRITE) && (vio->type != VTY_NONE)) ; + if (append) + iot |= vfd_io_append ; - vio->real_type = vio->type ; + name_str = qs_make_string(name) ; - vio->type = VTY_CONFIG_WRITE ; - vio->file_fd = fd ; - vio->file_error = 0 ; + /* Do the basic file open. */ + fd = uty_vfd_file_open(name_str, iot) ; - VTY_UNLOCK() ; -} ; - -/*------------------------------------------------------------------------------ - * Write away configuration file stuff -- all or just the full lump(s). - * - * Returns: > 0 => blocked - * 0 => all gone (up to last lump if !all) - * < 0 => failed -- see vio->file_error - */ -static int -uty_config_write(vty_io vio, bool all) -{ - int ret ; - - VTY_ASSERT_LOCKED() ; - - if (vio->file_error == 0) + if (fd < 0) { - ret = vio_fifo_write_nb(&vio->cmd_obuf, vio->file_fd, all) ; + uty_out(vio, "%% Could not open output file %s\n", name_str) ; - if (ret < 0) - vio->file_error = errno ; + return CMD_WARNING ; } - else - ret = -1 ; - - return ret ; -} ; - -/*------------------------------------------------------------------------------ - * Write away any pending stuff, and return the VTY to normal. - */ -extern int -vty_close_config_write(struct vty* vty) -{ - vty_io vio ; - int err ; - - VTY_LOCK() ; - - vio = vty->vio ; - - assert((vio->type == VTY_CONFIG_WRITE) && (vio->real_type != VTY_NONE)) ; - uty_config_write(vio, true) ; /* write all that is left */ + /* OK -- now push the new input onto the vin_stack. */ + vf = uty_vf_new(vio, name_str, fd, vfd_file, iot) ; + uty_vout_open(vio, vf, VOUT_FILE, NULL, NULL, 16 * 1024) ; - err = vio->file_error ; + vf->out_enabled = true ; - vio->type = vio->real_type ; - vio->file_fd = -1 ; - vio->file_error = 0 ; - - VTY_UNLOCK() ; - - return err ; + return CMD_SUCCESS ; } ; /*============================================================================== - * vio_sock level operations - */ - -/*------------------------------------------------------------------------------ - * Initialise a new vio_sock structure. - * - * Requires that: the vio_sock structure is not currently in use. - * - * if fd >= 0 then: sock is open and ready read and write - * otherwise: sock is not open - * - * there are no errors, yet. - * - * Sets timeout to no timeout at all -- timeout is optional. - * - * NB: MUST be in the CLI thread if the fd is >= 0 ! - */ -static void -uty_sock_init_new(vio_sock sock, int fd, void* info) -{ - VTY_ASSERT_LOCKED() ; - - if (fd >= 0) - VTY_ASSERT_CLI_THREAD() ; - - memset(sock, 0, sizeof(struct vio_sock)) ; - - /* Zeroising the structure has set: - * - * action = all the actions set NULL - * - * error_seen = 0 -- no error, yet - * - * qf = NULL -- no qfile, yet - * t_read = NULL ) no threads, yet - * t_write = NULL ) - * - * v_timeout = 0 -- no timeout set - * timer_runing = 0 -- not running, yet - * t_timer = NULL -- no timer thread, yet - * qtr = NULL -- no qtimer, yet - */ - sock->fd = fd ; - sock->info = info ; - - sock->read_open = (fd >= 0) ; - sock->write_open = (fd >= 0) ; - - if ((fd >= 0) && vty_cli_nexus) - { - sock->qf = qps_file_init_new(NULL, NULL); - qps_add_file(vty_cli_nexus->selection, sock->qf, sock->fd, sock->info); - } ; -} ; - -/*------------------------------------------------------------------------------ - * Restart the timer. + * Command line fetch from a file or pipe. * - * If a timeout time is set, then start or restart the timer with that value. - * - * If no timeout time is set, and the timer is running, unset it. + * Returns: CMD_SUCCESS -- have another command line ready to go + * CMD_WAITING -- do not have a command line at the moment + * CMD_EOF -- ran into EOF + * CMD_IO_ERROR -- ran into an I/O error */ -static void -uty_sock_restart_timer(vio_sock sock) +extern cmd_return_code_t +uty_file_fetch_command_line(vio_vf vf, qstring* line) { - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; + assert(vf->vin_state == vf_open) ; - if (sock->v_timeout != 0) + if (vf->line_complete) { - assert(sock->action.timer.anon != NULL) ; + vio_fifo_set_hold_mark(vf->ibuf) ; /* advance hold */ - if (vty_cli_nexus) - { - if (sock->qtr == NULL) /* allocate qtr if required */ - sock->qtr = qtimer_init_new(NULL, vty_cli_nexus->pile, - NULL, sock->info) ; - qtimer_set(sock->qtr, qt_add_monotonic(QTIME(sock->v_timeout)), - sock->action.timer.qnexus) ; - } - else - { - if (sock->t_timer != NULL) - thread_cancel (sock->t_timer); - sock->t_timer = thread_add_timer (vty_master, - sock->action.timer.thread, sock->info, sock->v_timeout) ; - } ; - - sock->timer_running = 1 ; - } - else if (sock->timer_running) - { - if (vty_cli_nexus) - { - if (sock->qtr != NULL) - qtimer_unset(sock->qtr) ; - } - else - { - if (sock->t_timer != NULL) - thread_cancel (sock->t_timer) ; - } ; + vf->line_complete = false ; + vf->line_number += vf->line_step ; - sock->timer_running = 0 ; + qs_set_len_nn(vf->cl, 0) ; + vf->line_step = 0 ; } ; -} ; -/*------------------------------------------------------------------------------ - * Set read on/off - * - * Returns: the on/off state set - */ -static bool -uty_sock_set_read(vio_sock sock, bool on) -{ - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - if (sock->fd < 0) - return 0 ; - - if (on) + while (1) { - assert(sock->action.read.anon != NULL) ; + char* s, * p, * q, * e ; + size_t have ; + ulen len ; - if (vty_cli_nexus) - qps_enable_mode(sock->qf, qps_read_mnum, sock->action.read.qnexus) ; - else - { - if (sock->t_read != NULL) - thread_cancel(sock->t_read) ; + s = vio_fifo_get(vf->ibuf, &have) ; - sock->t_read = thread_add_read(vty_master, - sock->action.read.thread, sock->info, sock->fd) ; - } ; - } - else - { - if (vty_cli_nexus) - qps_disable_modes(sock->qf, qps_read_mbit) ; - else + /* If nothing in hand, try and get some more. + * + * Either exits or loops back to set s & have. + */ + if (have == 0) { - if (sock->t_read != NULL) - thread_cancel (sock->t_read) ; - } ; - } ; - - return on ; -} ; + int get ; -/*------------------------------------------------------------------------------ - * Set write on/off - * - * Returns: the on/off state set - */ -static bool -uty_sock_set_write(vio_sock sock, bool on) -{ - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; + get = vio_fifo_read_nb(vf->ibuf, vio_vfd_fd(vf->vfd), 100) ; - if (sock->fd < 0) - return 0 ; + if (get > 0) + continue ; /* loop back */ - if (on) - { - assert(sock->action.write.anon != NULL) ; + if (get == 0) + return CMD_WAITING ; /* need to set read ready ! */ - if (vty_cli_nexus) - qps_enable_mode(sock->qf, qps_write_mnum, sock->action.write.qnexus) ; - else - { - if (sock->t_write != NULL) - thread_cancel(sock->t_write) ; + if (get == -1) + ; /* register error */ - sock->t_write = thread_add_write(vty_master, - sock->action.write.thread, sock->info, sock->fd) ; + if (get == -2) + return (qs_len_nn(vf->cl) > 0) ? CMD_SUCCESS : CMD_EOF ; } ; - } - else - { - if (vty_cli_nexus) - qps_disable_modes(sock->qf, qps_write_mbit) ; - else - { - if (sock->t_write != NULL) - thread_cancel (sock->t_write) ; - } ; - } ; - - return on ; -} ; - -/*------------------------------------------------------------------------------ - * Set read/write readiness -- for VTY_TERM - * - * Note that for VTY_TERM, set only one of read or write, and sets write for - * preference. - */ -extern void -uty_sock_set_readiness(vio_sock sock, enum vty_readiness ready) -{ - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - uty_sock_set_read(sock, (ready == read_ready)) ; - uty_sock_set_write(sock, (ready >= write_ready)) ; -} ; - -/*------------------------------------------------------------------------------ - * Set a new timer value. - */ -extern void -uty_sock_set_timer(vio_sock sock, unsigned long timeout) -{ - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - sock->v_timeout = timeout ; - if (sock->timer_running) - uty_sock_restart_timer(sock) ; -} ; - -/*------------------------------------------------------------------------------ - * Close given vty sock for reading. - * - * Sets timer to timeout for clearing any pending output. - * - * NB: if there is a socket, MUST be in the CLI thread - */ -static void -uty_sock_half_close(vio_sock sock) -{ - VTY_ASSERT_LOCKED() ; - - sock->read_open = 0 ; /* make sure */ - - if (sock->fd < 0) - return ; /* nothing more if no socket */ - - VTY_ASSERT_CLI_THREAD() ; - - shutdown(sock->fd, SHUT_RD) ; /* actual half close */ - - uty_sock_set_read(sock, off) ; - uty_sock_set_write(sock, on) ; - sock->v_timeout = 30 ; /* for output to clear */ - uty_sock_restart_timer(sock) ; -} ; - -/*------------------------------------------------------------------------------ - * Close given vio_sock, completely -- shut down any timer. - * - * Structure is cleared of everything except the last error ! - * - * NB: if there is a socket, MUST be in the CLI thread - */ -static void -uty_sock_close(vio_sock sock) -{ - VTY_ASSERT_LOCKED() ; - - sock->read_open = 0 ; /* make sure */ - sock->write_open = 0 ; - - if (sock->fd < 0) - { - assert( (sock->qf == NULL) - && (sock->qtr == NULL) - && (sock->t_read == NULL) - && (sock->t_write == NULL) - && (sock->t_timer == NULL) ) ; - return ; /* no more to be done here */ - } ; - - VTY_ASSERT_CLI_THREAD() ; - close(sock->fd) ; - - if (vty_cli_nexus) - { - assert((sock->qf != NULL) && (sock->fd == qps_file_fd(sock->qf))) ; - qps_remove_file(sock->qf) ; - qps_file_free(sock->qf) ; - sock->qf = NULL ; - } ; - - sock->fd = -1 ; - - if (sock->t_read != NULL) - thread_cancel(sock->t_write) ; - if (sock->t_write != NULL) - thread_cancel(sock->t_write) ; - - sock->t_read = NULL ; - sock->t_write = NULL ; - - sock->info = NULL ; - sock->action.read.anon = NULL ; - sock->action.write.anon = NULL ; - sock->action.timer.anon = NULL ; - - if (sock->qtr != NULL) - qtimer_free(sock->qtr) ; - if (sock->t_timer != NULL) - thread_cancel(sock->t_timer) ; - - sock->v_timeout = 0 ; - sock->qtr = NULL ; - sock->t_timer = NULL ; -} ; - -/*------------------------------------------------------------------------------ - * Dealing with an I/O error on VTY socket - * - * If this is the first error for this VTY, produce suitable log message. - * - * If is a "monitor", turn that off, *before* issuing log message. - */ -static int -uty_sock_error(vty_io vio, const char* what) -{ - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - /* can no longer be a monitor ! *before* any logging ! */ - uty_set_monitor(vio, 0) ; - - /* if this is the first error, log it */ - if (vio->sock.error_seen == 0) - { - const char* type ; - switch (vio->type) - { - case VTY_TERM: - type = "VTY Terminal" ; - break ; - case VTY_SHELL_SERV: - type = "VTY Shell Server" ; - break ; - default: - zabort("unknown VTY type for uty_sock_error()") ; - } ; - - vio->sock.error_seen = errno ; - uzlog(NULL, LOG_WARNING, "%s: %s failed on fd %d: %s", - type, what, vio->sock.fd, errtoa(vio->sock.error_seen, 0).str) ; - } ; - - return -1 ; -} ; - -/*============================================================================== - * Readiness and the VTY_TERM type VTY. - * - * For VTY_TERM the driving force is write ready. This is used to prompt the - * VTY_TERM when there is outstanding output (obviously), but also if there - * is buffered input in the keystroke stream. - * - * The VTY_TERM uses read ready only when it doesn't set write ready. Does - * not set both at once. - * - * So there is only one, common, uty_ready function, which: - * - * 1. attempts to clear any output it can. - * - * The state of the output affects the CLI, so must always do this before - * before invoking the CLI. - * - * If this write enters the "--more--" state, then will have tried to - * write away the prompt. - * - * 2. invokes the CLI - * - * Which will do either the standard CLI stuff or the special "--more--" - * stuff. - * - * 3. attempts to write any output there now is. - * - * If the CLI generated new output, as much as possible is written away - * now. - * - * If this write enters the "--more--" state, then it returns now_ready, - * if the prompt was written away, which loops back to the CLI. - * - * Note that this is arranging: - * - * a. to write away the "--more--" prompt as soon as the tranche of output to - * which it refers, completes - * - * b. to enter the cli_more_wait CLI for the first time immediately after the - * "--more--" prompt is written away. - * - * The loop limits itself to one trache of command output each time. - * - * Resets the timer because something happened. - */ -static void -uty_ready(vty_io vio) -{ - enum vty_readiness ready ; - - VTY_ASSERT_LOCKED() ; - - vio->cmd_out_done = 0 ; /* not done any command output yet */ - - uty_write(vio) ; /* try to clear outstanding stuff */ - do - { - ready = uty_cli(vio) ; /* do any CLI work... */ - ready |= uty_write(vio) ; /* ...and any output that generates */ - } while (ready >= now_ready) ; - - uty_sock_set_readiness(&vio->sock, ready) ; - uty_sock_restart_timer(&vio->sock) ; -} ; - -/*============================================================================== - * Reading from VTY_TERM. - * - * The select/pselect call-back ends up in uty_read_ready(). - * - * Note that uty_write_ready() also calls uty_read_ready, in order to kick the - * current CLI. - */ - -/*------------------------------------------------------------------------------ - * Callback -- qnexus: ready to read -> kicking CLI - */ -static void -vty_read_qnexus(qps_file qf, void* file_info) -{ - vty_io vio = file_info; - - VTY_LOCK() ; - - assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; - - uty_ready(vio) ; - - VTY_UNLOCK() ; -} - -/*------------------------------------------------------------------------------ - * Callback -- threads: ready to read -> kicking CLI - */ -static int -vty_read_thread(struct thread *thread) -{ - vty_io vio = THREAD_ARG (thread); - - VTY_LOCK() ; - - assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ; - vio->sock.t_read = NULL ; /* implicitly */ - uty_ready(vio); - - VTY_UNLOCK() ; - return 0 ; -} - -/*------------------------------------------------------------------------------ - * Read a lump of bytes and shovel into the keystroke stream - * - * Steal keystroke if required -- see keystroke_input() - * - * Returns: 0 => nothing available - * > 0 => read at least one byte - * -1 => EOF (or not open, or failed) - */ -extern int -uty_read (vty_io vio, keystroke steal) -{ - unsigned char buf[500] ; - int get ; - - if (!vio->sock.read_open) - return -1 ; /* at EOF if not open */ - - get = read_nb(vio->sock.fd, buf, sizeof(buf)) ; - if (get >= 0) - keystroke_input(vio->key_stream, buf, get, steal) ; - else if (get < 0) - { - if (get == -1) - uty_sock_error(vio, "read") ; - - vio->sock.read_open = 0 ; - keystroke_input(vio->key_stream, NULL, 0, steal) ; - - get = -1 ; - } ; - - return get ; -} ; - -/*============================================================================== - * The write sock action for VTY_TERM type VTY - * - * There are two sets of buffering: - * - * cli -- command line -- which reflects the status of the command line - * - * cmd -- command output -- which is written to the file only while - * cmd_out_enabled. - * - * The cli output takes precedence. - * - * Output of command stuff is subject to line_control, and may go through the - * "--more--" mechanism. - */ - -static int uty_write_lc(vty_io vio, vio_fifo vf, vio_line_control lc) ; -static int uty_write_fifo_lc(vty_io vio, vio_fifo vf, vio_line_control lc) ; - -/*------------------------------------------------------------------------------ - * Callback -- qnexus: ready to write -> try to empty buffers - */ -static void -vty_write_qnexus(qps_file qf, void* file_info) -{ - vty_io vio = file_info ; - - VTY_LOCK() ; - - assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; - - uty_ready(vio) ; - - VTY_UNLOCK() ; -} - -/*------------------------------------------------------------------------------ - * Callback -- thread: ready to write -> try to empty buffers - */ -static int -vty_write_thread(struct thread *thread) -{ - vty_io vio = THREAD_ARG (thread); - - VTY_LOCK() ; - - assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ; - - vio->sock.t_write = NULL; /* implicitly */ - uty_ready(vio) ; - - VTY_UNLOCK() ; - return 0 ; -} - -/*------------------------------------------------------------------------------ - * Write as much as possible of what there is. - * - * If not cmd_in_progress, clears cli_blocked if both FIFOs are, or become, - * empty. - * - * Note that if !write_open, or becomes !write_open, then the FIFOs are empty - * and all output instantly successful. - * - * Sets write on if prevented from writing everything available for output - * by write() threatening to block. - * - * Returns: write_ready if should now set write on - * now_ready if should loop back and try again - * not_ready otherwise - */ -static enum vty_readiness -uty_write(vty_io vio) -{ - int ret ; - - VTY_ASSERT_LOCKED() ; - - ret = -1 ; - while (vio->sock.write_open) - { - /* Any outstanding line control output takes precedence */ - if (vio->cmd_lc != NULL) - { - ret = uty_write_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ; - if (ret != 0) - break ; - } - - /* Next: empty out the cli output */ - ret = vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ; - if (ret != 0) - break ; - - /* Finished now if not allowed to progress the command stuff */ - if (!vio->cmd_out_enabled) - return not_ready ; /* done all can do */ + /* Try to find a '\n' -- converting all other control chars to ' ' + * + * When we find '\n' step back across any trailing ' ' (which includes + * any control chars before the '\n'). + * + * This means that we cope with "\r\n" line terminators. But not anything + * more exotic. + */ + p = s ; + e = s + have ; /* have != 0 */ + q = NULL ; - /* Last: if there is something in the command buffer, do that */ - if (!vio_fifo_empty(&vio->cmd_obuf)) + while (p < e) { - if (vio->cmd_out_done) - break ; /* ...but not if done once */ - - vio->cmd_out_done = 1 ; /* done this once */ - - assert(!vio->cli_more_wait) ; + if (*p++ < 0x20) + { + if (*(p-1) != '\n') + { + *(p-1) = ' ' ; /* everything other than '\n' */ + continue ; + } ; - if (vio->cmd_lc != NULL) - ret = uty_write_fifo_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ; - else - ret = vio_fifo_write_nb(&vio->cmd_obuf, vio->sock.fd, true) ; + ++vf->line_step ; /* got a '\n' */ - /* If moved into "--more--" state@ - * - * * the "--more--" prompt is ready to be written, so do that now - * - * * if that completes, then want to run the CLI *now* to perform the - * first stage of the "--more--" process. - */ - if (vio->cli_more_wait) - { - ret = vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ; - if (ret == 0) - return now_ready ; + q = p ; /* point just past '\n' */ + do --q ; while ((q > s) && (*(q-1) == ' ')) ; + /* discard trailing "spaces" */ + break ; } ; + } ; - if (ret != 0) - break ; - } + /* Step past what have just consumed -- we have a hold_mark, so + * stuff is still in the fifo. + */ + vio_fifo_step(vf->ibuf, p - s) ; - /* Exciting stuff: there is nothing left to output... + /* If not found '\n', then we have a line fragment that needs to be + * appended to any previous line fragments. * - * ... watch out for half closed state. + * Loops back to try to get some more form the fifo. */ - if (vio->half_closed) + if (q == NULL) { - if (vio->close_reason != NULL) - { - vio->cmd_in_progress = 1 ; /* TODO: not use vty_out ? */ - - struct vty* vty = vio->vty ; - if (vio->cli_drawn || vio->cli_dirty) - vty_out(vty, VTY_NEWLINE) ; - vty_out(vty, "%% %s%s", vio->close_reason, VTY_NEWLINE) ; - - vio->cmd_in_progress = 0 ; - - vio->close_reason = NULL ; /* MUST discard now... */ - continue ; /* ... and write away */ - } ; - - if (!vio->closed) /* avoid recursion */ - uty_close(vio) ; - - return not_ready ; /* it's all over */ + qs_append_n(vf->cl, s, p - s) ; + continue ; } ; - /* For VTY_TERM: if the command line is not drawn, now is a good - * time to do that. + /* If we have nothing so far, set alias to point at what we have in + * the fifo. Otherwise, append to what we have. + * + * End up with: s = start of entire line, so far. + * p = end of entire line so far. */ - if (vio->type == VTY_TERM) - if (uty_cli_draw_if_required(vio)) - continue ; /* do that now. */ - - /* There really is nothing left to output */ - return not_ready ; - } ; - - /* Arrives here if there is more to do, or failed (or was !write_open) */ - - if (ret >= 0) - return write_ready ; - - /* If is write_open, then report the error - * - * If still read_open, let the reader pick up and report the error, when it - * has finished anything it has buffered. - */ - if (vio->sock.write_open) - { - if (!vio->sock.read_open) - uty_sock_error(vio, "write") ; - - vio->sock.write_open = 0 ; /* crash close write */ - } ; - - /* For whatever reason, is no longer write_open -- clear all buffers. - */ - vio_fifo_clear(&vio->cli_obuf) ; /* throw away cli stuff */ - uty_out_clear(vio) ; /* throw away cmd stuff */ - - vio->close_reason = NULL ; /* too late for this */ - - return not_ready ; /* NB: NOT blocked by I/O */ -} ; - -/*------------------------------------------------------------------------------ - * Write as much as possible -- for "monitor" output. - * - * Outputs only: - * - * a. outstanding line control stuff. - * - * b. contents of CLI buffer - * - * And: - * - * a. does not report any errors. - * - * b. does not change anything except the state of the buffers. - * - * In particular, for the qpthreaded world, does not attempt to change - * the state of the qfile or any other "thread private" structures. - * - * Returns: > 0 => blocked - * 0 => all gone - * < 0 => failed (or !write_open) - */ -static int -uty_write_monitor(vty_io vio) -{ - VTY_ASSERT_LOCKED() ; - - if (!vio->sock.write_open) - return -1 ; - - if (vio->cmd_lc != NULL) - { - int ret ; - ret = uty_write_lc(vio, &vio->cmd_obuf, vio->cmd_lc) ; - - if (ret != 0) - return ret ; - } ; - - return vio_fifo_write_nb(&vio->cli_obuf, vio->sock.fd, true) ; -} ; - -/*------------------------------------------------------------------------------ - * Write the given FIFO to output -- subject to possible line control. - * - * Note that even if no "--more--" is set, will have set some height, so - * that does not attempt to empty the FIFO completely all in one go. - * - * If the line control becomes "paused", it is time to enter "--more--" state - * -- unless the FIFO is empty (or "--more--" is not enabled). - * - * NB: expects that the sock is write_open - * - * Returns: > 0 => blocked or completed one tranche - * 0 => all gone - * < 0 => failed - */ -static int -uty_write_fifo_lc(vty_io vio, vio_fifo vf, vio_line_control lc) -{ - int ret ; - char* src ; - size_t have ; - - /* Collect another line_control height's worth of output. - * - * Expect the line control to be empty at this point, but it does not have - * to be. - */ - vio_lc_set_pause(lc) ; /* clears lc->paused */ - - src = vio_fifo_get_rdr(vf, &have) ; - - while ((src != NULL) && (!lc->paused)) - { - size_t take ; - take = vio_lc_append(lc, src, have) ; - src = vio_fifo_step_rdr(vf, &have, take) ; - } ; - - vio->cli_dirty = (lc->col != 0) ; - - /* Write the contents of the line control */ - ret = uty_write_lc(vio, vf, lc) ; - - if (ret < 0) - return ret ; /* give up now if failed. */ - - if ((ret == 0) && vio_fifo_empty(vf)) - return 0 ; /* FIFO and line control empty */ - - /* If should now do "--more--", now is the time to prepare for that. - * - * Entering more state issues a new prompt in the CLI buffer, which can - * be written once line control write completes. - * - * The "--more--" cli will not do anything until the CLI buffer has - * cleared. - */ - if (lc->paused && vio->cli_more_enabled) - uty_cli_enter_more_wait(vio) ; - - return 1 ; /* FIFO or line control, not empty */ -} ; - -/*------------------------------------------------------------------------------ - * Write contents of line control (if any). - * - * NB: expects that the sock is write_open - * - * NB: does nothing other than write() and buffer management. - * - * Returns: > 0 => blocked - * 0 => all gone - * < 0 => failed - */ -static int -uty_write_lc(vty_io vio, vio_fifo vf, vio_line_control lc) -{ - int ret ; - - ret = vio_lc_write_nb(vio->sock.fd, lc) ; - - if (ret <= 0) - vio_fifo_sync_rdr(vf) ; /* finished with FIFO contents */ - - return ret ; -} ; - -/*------------------------------------------------------------------------------ - * Start command output -- clears down the line control. - * - * Requires that that current line is empty -- restarts the line control - * on the basis that is at column 0. - */ -extern void -uty_cmd_output_start(vty_io vio) -{ - if (vio->cmd_lc != NULL) - vio_lc_clear(vio->cmd_lc) ; -} ; - -/*------------------------------------------------------------------------------ - * Set the effective height for line control (if any) - * - * If using line_control, may enable the "--more--" output handling. - * - * If not, want some limit on the amount of stuff output at a time. - * - * Sets the line control window width and height. - * Sets cli_more_enabled if "--more--" is enabled. - */ -extern void -uty_set_height(vty_io vio) -{ - bool on ; - - on = 0 ; /* default state */ - - if ((vio->cmd_lc != NULL) && !vio->half_closed) - { - int height ; - - height = 0 ; /* default state */ - - if ((vio->width) != 0) + len = q - s ; /* length to add */ + if (qs_len_nn(vf->cl) == 0) { - /* If window size is known, use lines or given height */ - if (vio->lines >= 0) - height = vio->lines ; - else - { - /* Window height, leaving one line from previous "page" - * and one line for the "--more--" -- if at all possible - */ - height = vio->height - 2 ; - if (height < 1) - height = 1 ; - } ; + qs_set_alias_n(vf->cl, s, len) ; + p = q ; } else { - /* If window size not known, use lines if that has been set - * explicitly for this terminal. - */ - if (vio->lines_set) - height = vio->lines ; - } ; - - if (height > 0) - on = 1 ; /* have a defined height */ - else - height = 200 ; /* but no "--more--" */ - - vio_lc_set_window(vio->cmd_lc, vio->width, height) ; - } ; - - vio->cli_more_enabled = on ; -} ; - -/*============================================================================== - * Timer for VTY_TERM (and VTY_SHELL_SERV). - */ - -/*------------------------------------------------------------------------------ - * Timer has expired. - * - * If half_closed, then this is curtains -- have waited long enough ! - * - * Otherwise, half close the VTY and leave it to the death-watch to sweep up. - */ -static void -uty_timer_expired (vty_io vio) -{ - VTY_ASSERT_LOCKED() ; - - if (vio->half_closed) - return uty_close(vio) ; /* curtains */ - - uty_half_close(vio, "Timed out") ; /* bring input side to a halt */ - } ; - -/*------------------------------------------------------------------------------ - * Callback -- qnexus: deal with timer timeout. - */ -static void -vty_timer_qnexus (qtimer qtr, void* timer_info, qtime_t when) -{ - vty_io vio = timer_info ; - - VTY_LOCK() ; - - uty_timer_expired(vio); - - VTY_UNLOCK() ; -} - -/*------------------------------------------------------------------------------ - * Callback -- thread: deal with timer timeout. - */ -static int -vty_timer_thread (struct thread *thread) -{ - vty_io vio = THREAD_ARG (thread); - - VTY_LOCK() ; - - vio->sock.t_timer = NULL ; /* implicitly */ - - uty_timer_expired(vio) ; - - VTY_UNLOCK() ; - return 0; -} - -/*============================================================================== - * VTY Listener(s) - * - * Have listeners for VTY_TERM and VTY_SHELL_SERV types of VTY. - */ - -typedef struct vty_listener* vty_listener ; - -struct vty_listener -{ - vty_listener next ; /* ssl type list */ - - enum vty_type type ; - - struct vio_sock sock ; -}; - -/* List of listeners so can tidy up. */ -static vty_listener vty_listeners_list = NULL ; - -/* Prototypes for listener stuff */ -static int uty_serv_sock_addrinfo (const char *hostname, unsigned short port) ; -static int uty_serv_sock(const char* addr, unsigned short port) ; -static int uty_serv_sock_open(sa_family_t family, int type, int protocol, - struct sockaddr* sa, unsigned short port) ; -static int uty_serv_vtysh(const char *path) ; -static int vty_accept_thread(struct thread *thread) ; -static void vty_accept_qnexus(qps_file qf, void* listener) ; -static int uty_accept(vty_listener listener, int listen_sock) ; -static int uty_accept_term(vty_listener listener) ; -static int uty_accept_shell_serv (vty_listener listener) ; - -static void uty_serv_start_listener(int fd, enum vty_type type) ; - -/*------------------------------------------------------------------------------ - * If possible, will use getaddrinfo() to find all the things to listen on. - */ - -#if defined(HAVE_IPV6) && !defined(NRL) -# define VTY_USE_ADDRINFO 1 -#else -# define VTY_USE_ADDRINFO 0 -#endif - -/*------------------------------------------------------------------------------ - * Open VTY listener(s) - * - * addr -- address ) to listen for VTY_TERM connections - * port -- port ) - * path -- path for VTYSH connections -- if VTYSH_ENABLED - */ -extern void -uty_open_listeners(const char *addr, unsigned short port, const char *path) -{ - VTY_ASSERT_LOCKED() ; - - /* If port is set to 0, do not listen on TCP/IP at all! */ - if (port) - { - int n ; - - if (VTY_USE_ADDRINFO) - n = uty_serv_sock_addrinfo(addr, port); - else - n = uty_serv_sock(addr, port); - - if (n == 0) - uzlog(NULL, LOG_ERR, "could not open any VTY listeners") ; - } - - /* If want to listen for vtysh, set up listener now */ - if (VTYSH_ENABLED && (path != NULL)) - uty_serv_vtysh(path) ; -} ; - -/*------------------------------------------------------------------------------ - * Close VTY listener - * - * addr -- address ) to listen for VTY_TERM connections - * port -- port ) - * path -- path for VTYSH connections -- if VTYSH_ENABLED - */ -extern void -uty_close_listeners(void) -{ - vty_listener listener ; - - VTY_ASSERT_LOCKED() ; - - while ((listener = ssl_pop(&listener, vty_listeners_list, next)) != NULL) - { - uty_sock_close(&listener->sock) ; /* no ceremony, no flowers */ - XFREE(MTYPE_VTY, listener) ; - } ; -} ; - -/*------------------------------------------------------------------------------ - * Open listener(s) for VTY_TERM -- using getaddrinfo(). - * - * Returns: number of listeners successfully opened. - */ -static int -uty_serv_sock_addrinfo (const char *hostname, unsigned short port) -{ -#if VTY_USE_ADDRINFO - -# ifndef HAVE_IPV6 -# error Using getaddrinfo() but HAVE_IPV6 is not defined ?? -# endif - - int ret; - int n ; - struct addrinfo req; - struct addrinfo *ainfo; - struct addrinfo *ainfo_save; - char port_str[16]; - - VTY_ASSERT_LOCKED() ; - - /* Want to listen, TCP-wise, on all available address families, on the - * given port. - */ - memset (&req, 0, sizeof (struct addrinfo)); - req.ai_flags = AI_PASSIVE; - req.ai_family = AF_UNSPEC; - req.ai_socktype = SOCK_STREAM; - snprintf(port_str, sizeof(port_str), "%d", port); - - ret = getaddrinfo (hostname, port_str, &req, &ainfo); - - if (ret != 0) - { - fprintf (stderr, "getaddrinfo failed: %s\n", eaitoa(ret, errno, 0).str); - exit (1); - } - - /* Open up sockets on all AF_INET and AF_INET6 addresses */ - ainfo_save = ainfo; - - n = 0 ; - do - { - if ((ainfo->ai_family != AF_INET) && (ainfo->ai_family != AF_INET6)) - continue; - - assert(ainfo->ai_family == ainfo->ai_addr->sa_family) ; - - ret = uty_serv_sock_open(ainfo->ai_family, ainfo->ai_socktype, - ainfo->ai_protocol, ainfo->ai_addr, port) ; - if (ret >= 0) - ++n ; - } - while ((ainfo = ainfo->ai_next) != NULL); - - freeaddrinfo (ainfo_save); - - return n ; - -#else - zabort("uty_serv_sock_addrinfo not implemented") ; -#endif /* VTY_USE_ADDRINFO */ -} - -/*------------------------------------------------------------------------------ - * Open listener(s) for VTY_TERM -- not using getaddrinfo() ! - * - * Returns: number of listeners successfully opened. - */ -static int -uty_serv_sock(const char* addr, unsigned short port) -{ - int ret; - int n ; - union sockunion su_addr ; - struct sockaddr* sa ; - - VTY_ASSERT_LOCKED() ; - - n = 0 ; /* nothing opened yet */ - - /* If have an address, see what kind and whether valid */ - sa = NULL ; - - if (addr != NULL) - { - ret = str2sockunion (addr, &su_addr) ; - if (ret == 0) - sa = &su_addr.sa ; - else - uzlog(NULL, LOG_ERR, "bad address %s, cannot listen for VTY", addr); - } ; - - /* Try for AF_INET */ - ret = uty_serv_sock_open(AF_INET, SOCK_STREAM, 0, sa, port) ; - if (ret >= 0) - ++n ; /* opened socket */ - if (ret == 1) - sa = NULL ; /* used the address */ - -#if HAVE_IPV6 - /* Try for AF_INET6 */ - ret = uty_serv_sock_open(AF_INET6, SOCK_STREAM, 0, sa, port) ; - if (ret >= 0) - ++n ; /* opened socket */ - if (ret == 1) - sa = NULL ; /* used the address */ -#endif - - /* If not used the address... something wrong */ - if (sa != NULL) - uzlog(NULL, LOG_ERR, "could not use address %s, to listen for VTY", addr); - - /* Done */ - return n ; -} - -/*------------------------------------------------------------------------------ - * Open a VTY_TERM listener socket. - * - * The sockaddr 'sa' may be NULL or of a different address family, in which - * case "any" address is used. - * - * If the sockaddr 'sa' is used, only the address portion is used. - * - * Returns: < 0 => failed - * == 0 => OK -- did not use the sockaddr 'sa'. - * > 1 => OK -- and did use the sockaddr 'sa' - */ -static int -uty_serv_sock_open(sa_family_t family, int type, int protocol, - struct sockaddr* sa, unsigned short port) -{ - union sockunion su ; - int sock ; - int ret ; - - VTY_ASSERT_LOCKED() ; - - /* Is there an address and is it for this family ? */ - if ((sa != NULL) || (sa->sa_family == family)) - /* Set up sockunion containing required family and address */ - sockunion_new_sockaddr(&su, sa) ; - else - { - /* no address or wrong family -- set up empty sockunion of - * required family */ - sockunion_init_new(&su, family) ; - sa = NULL ; - } ; - - /* Open the socket and set its properties */ - sock = sockunion_socket(family, type, protocol) ; - if (sock < 0) - return -1 ; - - ret = sockopt_reuseaddr (sock); - - if (ret >= 0) - ret = sockopt_reuseport (sock); - - if (ret >= 0) - ret = set_nonblocking(sock); - -#if defined(HAVE_IPV6) && defined(IPV6_V6ONLY) - /* Want only IPV6 on ipv6 socket (not mapped addresses) - * - * This distinguishes 0.0.0.0 from :: -- without this, bind() will reject the - * attempt to bind to :: after binding to 0.0.0.0. - */ - if ((ret >= 0) && (sa->sa_family == AF_INET6)) - { - int on = 1; - ret = setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on)); - } -#endif - - if (ret >= 0) - ret = sockunion_bind (sock, &su, port, sa) ; + if (len != 0) + qs_append_n(vf->cl, s, len) ; - if (ret >= 0) - ret = sockunion_listen (sock, 3); + s = qs_char_nn(vf->cl) ; + p = s + qs_len_nn(vf->cl) ; - if (ret < 0) - { - close (sock); - return -1 ; - } - - /* Socket is open -- set VTY Term listener going */ - uty_serv_start_listener(sock, VTY_TERM) ; - - /* Return OK and signal whether used address or not */ - return (sa != NULL) ? 1 : 0 ; -} ; - -/*------------------------------------------------------------------------------ - * Open a VTY_SHEL_SERV listener socket (UNIX domain). - * - * Returns: < 0 => failed - * >= 0 => OK - */ -static int -uty_serv_vtysh(const char *path) -{ - int ret; - int sock, sa_len, path_len ; - struct sockaddr_un sa_un ; - mode_t old_mask; - struct zprivs_ids_t ids; - - VTY_ASSERT_LOCKED() ; - - /* worry about the path length */ - path_len = strlen(path) + 1 ; - if (path_len >= (int)sizeof(sa_un.sun_path)) - { - uzlog(NULL, LOG_ERR, "path too long for unix stream socket: '%s'", path); - return -1 ; - } ; - - /* First of all, unlink existing socket */ - unlink (path); - - /* Make UNIX domain socket. */ - sock = socket (AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) - { - uzlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s", - errtoa(errno, 0).str) ; - return -1 ; - } + if ((len == 0) && (p > s) && (*(p-1) == ' ')) + { + /* Have an empty end of line section, and the last character + * of what we have so far is ' ', so need now to trim trailing + * spaces off the stored stuff. + */ + do --p ; while ((p > s) && (*(p-1) == ' ')) ; - /* Bind to the required path */ - memset (&sa_un, 0, sizeof(sa_un)); - sa_un.sun_family = AF_UNIX; - strncpy (sa_un.sun_path, path, sizeof(sa_un.sun_path) - 1); + qs_set_len_nn(vf->cl, p - s) ; + } ; + } ; - sa_len = SUN_LEN(&sa_un) ; + /* Now worry about we have a trailing '\'. */ -#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN - sa_un.sun_len = sa_len ; -#endif + if ((p == s) || (*(p-1) != '\\')) + break ; /* no \ => no continuation => success */ - old_mask = umask (0007); + /* Have a trailing '\'. + * + * If there are an odd number of '\', strip the last one and loop + * round to collect the continuation. + * + * If there are an even number of '\', then this is not a continuation. + * + * Note that this rule deals with the case of the continuation line + * being empty... e.g. ....\\\ n n -- where n is '\n' + */ + q = p ; + do --q ; while ((q > s) && (*(q-1) == '\\')) ; - ret = bind (sock, (struct sockaddr *) &sa_un, sa_len) ; - if (ret < 0) - uzlog(NULL, LOG_ERR, "Cannot bind path %s: %s", path, errtoa(errno, 0).str); + if (((p - q) & 1) == 0) + break ; /* even => no continuation => success */ - if (ret >= 0) - ret = set_nonblocking(sock); + qs_set_len_nn(vf->cl, p - s - 1) ; /* strip odd '\' */ - if (ret >= 0) - { - ret = listen (sock, 5); - if (ret < 0) - uzlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock, - errtoa(errno, 0).str) ; + continue ; /* loop back to fetch more */ } ; - zprivs_get_ids(&ids); - - if (ids.gid_vty > 0) - { - /* set group of socket */ - if ( chown (path, -1, ids.gid_vty) ) - uzlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s", - errtoa(errno, 0).str) ; - } + /* Success have a line in hand */ - umask (old_mask); + vf->line_complete = true ; + *line = vf->cl ; - /* Give up now if failed along the way */ - if (ret < 0) - { - close (sock) ; - return -1 ; - } ; - - /* Socket is open -- set VTY Term listener going */ - uty_serv_start_listener(sock, VTY_SHELL_SERV) ; - - return 0 ; + return CMD_SUCCESS ; } ; /*------------------------------------------------------------------------------ - * Socket is open -- set a VTY listener going + * Command output push to a file or pipe. * - * Note that the vyt_listener structure is passed to the accept action function. + * Returns: CMD_SUCCESS -- done + * CMD_IO_ERROR -- ran into an I/O error */ -static void -uty_serv_start_listener(int fd, enum vty_type type) +extern cmd_return_code_t +uty_file_out_push(vio_vf vf) { - vty_listener listener ; - - listener = XCALLOC(MTYPE_VTY, sizeof (struct vty_listener)); + assert(vf->vout_state == vf_open) ; - ssl_push(vty_listeners_list, listener, next) ; - uty_sock_init_new(&listener->sock, fd, listener) ; - - listener->type = type ; - - if (vty_cli_nexus) - listener->sock.action.read.qnexus = vty_accept_qnexus ; + if (vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), false) >= 0) + return CMD_SUCCESS ; else - listener->sock.action.read.thread = vty_accept_thread ; - - uty_sock_set_read(&listener->sock, on) ; -} ; - -/*------------------------------------------------------------------------------ - * Accept action for the thread world -- create and dispatch VTY - */ -static int -vty_accept_thread(struct thread *thread) -{ - vty_listener listener = THREAD_ARG(thread) ; - int result ; - - VTY_LOCK() ; - - result = uty_accept(listener, THREAD_FD(thread)); - - uty_sock_set_read(&listener->sock, on) ; - - VTY_UNLOCK() ; - return result ; -} ; - -/*------------------------------------------------------------------------------ - * Accept action for the qnexus world -- create and dispatch VTY - */ -static void -vty_accept_qnexus(qps_file qf, void* listener) -{ - VTY_LOCK() ; - - uty_accept(listener, qf->fd); - - VTY_UNLOCK() ; -} - -/*------------------------------------------------------------------------------ - * Accept action -- create and dispatch VTY_TERM or VTY_SHELL_SERV - */ -static int -uty_accept(vty_listener listener, int listen_sock) -{ - VTY_ASSERT_LOCKED() ; - - assert(listener->sock.fd == listen_sock) ; - - switch (listener->type) - { - case VTY_TERM: - return uty_accept_term(listener) ; - - case VTY_SHELL_SERV: - return uty_accept_shell_serv(listener) ; - - default: - zabort("unknown vty type") ; - } ; -} ; - -/*------------------------------------------------------------------------------ - * Accept action -- create and dispatch VTY_TERM - */ -static int -uty_accept_term(vty_listener listener) -{ - int sock_fd; - union sockunion su; - int ret; - unsigned int on; - struct prefix *p ; - - VTY_ASSERT_LOCKED() ; - - /* We can handle IPv4 or IPv6 socket. */ - sockunion_init_new(&su, AF_UNSPEC) ; - - sock_fd = sockunion_accept (listener->sock.fd, &su); - - if (sock_fd < 0) - { - if (sock_fd == -1) - uzlog (NULL, LOG_WARNING, "can't accept vty socket : %s", - errtoa(errno, 0).str) ; - return -1; - } - - /* Really MUST have non-blocking */ - ret = set_nonblocking(sock_fd) ; /* issues WARNING if fails */ - if (ret < 0) - { - close(sock_fd) ; - return -1 ; - } ; - - /* New socket is open... worry about access lists */ - p = sockunion2hostprefix (&su); - ret = 0 ; /* so far, so good */ - - if ((p->family == AF_INET) && vty_accesslist_name) - { - /* VTY's accesslist apply. */ - struct access_list* acl ; - - if ((acl = access_list_lookup (AFI_IP, vty_accesslist_name)) && - (access_list_apply (acl, p) == FILTER_DENY)) - ret = -1 ; - } - -#ifdef HAVE_IPV6 - if ((p->family == AF_INET6) && vty_ipv6_accesslist_name) - { - /* VTY's ipv6 accesslist apply. */ - struct access_list* acl ; - - if ((acl = access_list_lookup (AFI_IP6, vty_ipv6_accesslist_name)) && - (access_list_apply (acl, p) == FILTER_DENY)) - ret = -1 ; - } -#endif /* HAVE_IPV6 */ - - prefix_free (p); - - if (ret != 0) - { - uzlog (NULL, LOG_INFO, "Vty connection refused from %s", sutoa(&su).str) ; - close (sock_fd); - return 0; - } ; - - /* Final options (optional) */ - on = 1 ; - ret = setsockopt (sock_fd, IPPROTO_TCP, TCP_NODELAY, - (void*)&on, sizeof (on)); - if (ret < 0) - uzlog (NULL, LOG_INFO, "can't set sockopt to socket %d: %s", - sock_fd, errtoa(errno, 0).str) ; - - /* All set -- create the VTY_TERM */ - uty_new_term(sock_fd, &su); - - /* Log new VTY */ - uzlog (NULL, LOG_INFO, "Vty connection from %s (fd %d)", sutoa(&su).str, - sock_fd) ; - - return 0; -} - -/*------------------------------------------------------------------------------ - * Accept action -- create and dispatch VTY_SHELL_SERV - */ -static int -uty_accept_shell_serv (vty_listener listener) -{ - int sock_fd ; - int ret ; - int client_len ; - struct sockaddr_un client ; - - VTY_ASSERT_LOCKED() ; - - client_len = sizeof(client); - memset (&client, 0, client_len); - - sock_fd = accept(listener->sock.fd, (struct sockaddr *) &client, - (socklen_t *) &client_len) ; - - if (sock_fd < 0) - { - uzlog (NULL, LOG_WARNING, "can't accept vty shell socket : %s", - errtoa(errno, 0).str) ; - return -1; - } - - /* Really MUST have non-blocking */ - ret = set_nonblocking(sock_fd) ; /* issues WARNING if fails */ - if (ret < 0) - { - close(sock_fd) ; - return -1 ; - } ; - - /* All set -- create the VTY_SHELL_SERV */ - if (VTYSH_DEBUG) - printf ("VTY shell accept\n"); - - uty_new_shell_serv(sock_fd) ; - - /* Log new VTY */ - uzlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock_fd); - return 0; -} - -/*============================================================================== - * Reading from the VTY_SHELL_SERV type sock. - * - * The select/pselect call-back ends up in utysh_read_ready(). - */ - -/*------------------------------------------------------------------------------ - * Ready to read -> kicking the "SHELL_SERV CLI" - * - * End up here when there is something ready to be read. - * - * Will also end up here if an error has occurred, the other end has closed, - * this end has half closed, etc. This fact is used to kick the CLI even when - * there is no data to be read. - * - * Note that nothing is actually read here -- reading is done in the CLI itself, - * if required. - * - * The CLI decides whether to re-enable read, or enable write, or both. - */ -static void -utysh_read_ready(vty_io vio) -{ - uty_sock_set_read(&vio->sock, off) ; - - /* TODO: need minimal "CLI" for VTY_SHELL_SERV - * NB: when output from command is flushed out, must append the - * following four bytes: '\0' '\0' '\0' <ret> - * Where <ret> is the command return code. - */ + return CMD_IO_ERROR ; } ; /*------------------------------------------------------------------------------ - * Callback -- qnexus: ready to read -> kicking the "SHELL_SERV CLI" - */ -static void -vtysh_read_qnexus(qps_file qf, void* file_info) -{ - vty_io vio = file_info; - - VTY_LOCK() ; - - assert((vio->sock.fd == qf->fd) && (vio == vio->sock.info)) ; - - utysh_read_ready(vio) ; - - VTY_UNLOCK() ; -} - -/*------------------------------------------------------------------------------ - * Callback -- threads: ready to read -> kicking the "SHELL_SERV CLI" - */ -static int -vtysh_read_thread(struct thread *thread) -{ - vty_io vio = THREAD_ARG (thread); - - VTY_LOCK() ; - - assert(vio->sock.fd == THREAD_FD (thread) && (vio == vio->sock.info)) ; - - vio->sock.t_read = NULL ; /* implicitly */ - utysh_read_ready(vio); - - VTY_UNLOCK() ; - return 0 ; -} - -/*------------------------------------------------------------------------------ - * Read a lump of bytes and shovel into the command line buffer - * - * Lines coming in are terminated by '\0'. - * - * Assumes that the incoming command line is empty or otherwise incomplete. - * - * Moves stuff from the "buf" qstring and appends to "cl" qstring, stopping - * when get '\0' or empties the "buf". - * - * When empties "buf", reads a lump from the sock. - * - * Returns: 0 => command line is incomplete - * 1 => have a complete command line - * -1 => EOF (or not open, or failed) - */ -extern int -utysh_read (vty_io vio, qstring cl, qstring buf) -{ - int get ; - char* cp ; - char* ep ; - size_t have ; - - while (1) - { - /* process what there is in the buffer */ - if (buf->len > buf->cp) - { - cp = qs_cp_char(buf) ; - ep = qs_ep_char(buf) ; - have = ep - cp ; - - ep = memchr(cp, '\0', have) ; - if (ep != NULL) - have = ep - cp ; /* have upto, but excluding '\0' */ - - if (have > 0) /* take what have */ - { - qs_insert(cl, cp, have) ; - cl->cp += have ; - buf->cp += have ; - } ; - - if (ep != NULL) /* if found '\0' */ - { - qs_term(cl) ; /* '\0' terminate */ - ++buf->cp ; /* step past it */ - return 1 ; /* have a complete line <<<<<<<<<<<<< */ - } - } ; - - /* buffer is empty -- try and get some more stuff */ - assert(buf->len == buf->cp) ; - - if (!vio->sock.read_open) - return -1 ; /* at EOF if not open <<<<<<<<<<<<< */ - - qs_need(buf, 500) ; /* need a reasonable lump */ - qs_clear(buf) ; /* set cp = len = 0 */ - - get = read_nb(vio->sock.fd, buf->body, buf->size) ; - if (get > 0) - buf->len = get ; - else if (get == 0) - return 0 ; /* have an incomplete line <<<<<<<<<<<< */ - else - { - if (get == -1) - uty_sock_error(vio, "read") ; - - vio->sock.read_open = 0 ; - - return -1 ; /* at EOF or failed <<<<<<<<<<<<< */ - } ; - } ; -} ; - -/*============================================================================== - * Output to vty which are set to "monitor". - * - * This is VERY TRICKY. - * - * If not running qpthreaded, then the objective is to get the message away - * immediately -- do not wish it to be delayed in any way by the thread - * system. - * - * So proceed as follows: - * - * a. wipe command line -- which adds output to the CLI buffer - * - * b. write the CLI buffer to the sock and any outstanding line control. - * - * c. write the monitor output. - * - * If that does not complete, put the tail end to the CLI buffer. - * - * d. restore any command line -- which adds output to the CLI buffer - * - * e. write the CLI buffer to the sock - * - * If that all succeeds, nothing has changed as far as the VTY stuff is - * concerned -- except that possibly some CLI output was sent before it got - * round to it. - * - * Note that step (b) will deal with any output hanging around from an - * earlier step (e). If cannot complete that, then does not add fuel to the - * fire -- but the message will be discarded. - * - * If that fails, or does not complete, then can set write on, to signal that - * there is some output in the CLI buffer that needs to be sent, or some - * error to be dealt with. - * - * The output should be tidy. - * - * To cut down the clutter, step (d) is performed only if the command line - * is not empty (or if in cli_more_wait). Once a the user has started to enter - * a command, the prompt and the command will remain visible. - * - * When logging an I/O error for a vty that happens to be a monitor, the - * monitor-ness has already been turned off. The monitor output code does not - * attempt to log any errors, sets write on so that the error will be picked - * up that way. - * - * However, in the event of an assertion failure, it is possible that an - * assertion will fail inside the monitor output. The monitor_busy flag - * prevents disaster. It is also left set if I/O fails in monitor output, so - * will not try to use the monitor again. - * - * Note that an assertion which is false for all vty monitors will recurse - * through all the monitors, setting each one busy, in turn ! - * - - - * TODO: sort out write on in the qpthreads world ?? - * - * The problem is that the qpselect structure is designed to be accessed ONLY - * within the thread to which it belongs. This makes it impossible for the - * monitor output to set/clear read/write on the vty sock... so some way - * around this is required. - */ - -/*------------------------------------------------------------------------------ - * Output logging information to all vty which are set to "monitor". + * Tidy up after input file has been closed */ extern void -uty_log(struct logline* ll, struct zlog *zl, int priority, - const char *format, va_list va) +uty_file_read_close(vio_vf vf) { - vty_io vio ; - - VTY_ASSERT_LOCKED() ; - - vio = sdl_head(vio_monitors_base) ; - - if (vio == NULL) - return ; /* go no further if no "monitor" vtys */ - - /* Prepare line for output. */ - uvzlog_line(ll, zl, priority, format, va, llt_crlf) ; /* with crlf */ - - /* write to all known "monitor" vty - * - */ - while (vio != NULL) - { - if (!vio->monitor_busy) - { - int ret ; - - vio->monitor_busy = 1 ; /* close the door */ - - uty_cli_pre_monitor(vio, ll->len - 2) ; /* claim the console */ - - ret = uty_write_monitor(vio) ; - if (ret == 0) - { - ret = write_nb(vio->sock.fd, ll->line, ll->len) ; - - if (ret >= 0) - { - ret = uty_cli_post_monitor(vio, ll->line + ret, - ll->len - ret) ; - if (ret > 0) - ret = uty_write_monitor(vio) ; - } ; - } ; - - if (ret != 0) - /* need to prod */ ; - - if (ret >= 0) - vio->monitor_busy = 0 ; - } ; - - vio = sdl_next(vio, mon_list) ; - } ; + return ; } ; /*------------------------------------------------------------------------------ - * Async-signal-safe version of vty_log for fixed strings. + * Flush output buffer and close. * - * This is last gasp operation. + * Returns: true <=> buffer (now) empty */ -void -vty_log_fixed (const char *buf, size_t len) +extern bool +uty_file_write_close(vio_vf vf, bool final) { - vty_io vio ; - - /* Write to all known "monitor" vty - * - * Forget all the niceties -- about to die in any case. - */ - vio = sdl_head(vio_monitors_base) ; - while (vio != NULL) - { - write(vio->sock.fd, buf, len) ; - write(vio->sock.fd, "\r\n", 2) ; - - vio = sdl_next(vio, mon_list) ; - } ; + return vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), true) == 0 ; } ; diff --git a/lib/vty_io_file.h b/lib/vty_io_file.h index b4a79f52..ab852e08 100644 --- a/lib/vty_io_file.h +++ b/lib/vty_io_file.h @@ -1,8 +1,6 @@ -/* VTY IO FILE -- File I/O -- header - * Virtual terminal [aka TeletYpe] interface routine. - * Copyright (C) 1997, 98 Kunihiro Ishiguro +/* VTY I/O for Files -- Header * - * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * Copyright (C) 2010 Chris Hall (GMCH), Highwayman * * This file is part of GNU Zebra. * @@ -26,17 +24,8 @@ #define _ZEBRA_VTY_IO_FILE_H #include "misc.h" -#include <errno.h> -#include "uty.h" -#include "vty.h" #include "vty_io.h" -#include "vio_fifo.h" -#include "vio_lines.h" -#include "keystroke.h" -#include "thread.h" -#include "command.h" -#include "qstring.h" /*============================================================================== * Here are structures and other definitions which are shared by: @@ -50,7 +39,15 @@ * Functions */ -extern int uty_vprintf_file(vty_io vio, const char *format, va_list args) ; +extern cmd_return_code_t uty_file_read_open(vty_io vio, qstring name, + bool reflect) ; +extern cmd_return_code_t uty_file_write_open(vty_io vio, qstring name, + bool append) ; +extern cmd_return_code_t uty_file_fetch_command_line(vio_vf vf, qstring* line) ; +extern cmd_return_code_t uty_file_out_push(vio_vf vf) ; + +extern void uty_file_read_close(vio_vf vf) ; +extern bool uty_file_write_close(vio_vf vf, bool final) ; #endif /* _ZEBRA_VTY_IO_FILE_H */ diff --git a/lib/vty_io_shell.c b/lib/vty_io_shell.c index c56cc2d2..7b24b871 100644 --- a/lib/vty_io_shell.c +++ b/lib/vty_io_shell.c @@ -43,6 +43,7 @@ #define VTYSH_DEBUG 0 +#if 0 /*------------------------------------------------------------------------------ * Create new vty of type VTY_SHELL_SERV -- ie attached to a vtysh session. @@ -62,7 +63,7 @@ uty_new_shell_serv(int sock_fd) vio = vty->vio ; /* Set the action functions */ - if (vty_cli_nexus) + if (vty_nexus) { vio->sock.action.read.qnexus = vtysh_read_qnexus ; vio->sock.action.write.qnexus = vty_write_qnexus ; @@ -364,3 +365,5 @@ utysh_read (vty_io vio, qstring cl, qstring buf) } ; } ; } ; + +#endif diff --git a/lib/vty_io_term.c b/lib/vty_io_term.c index 2053dcc5..0f8e834d 100644 --- a/lib/vty_io_term.c +++ b/lib/vty_io_term.c @@ -20,11 +20,15 @@ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ +#include "zconfig.h" +#include "misc.h" -#include "zebra.h" - +#include "vty_local.h" +#include "vty_io.h" #include "vty_io_term.h" #include "vty_cli.h" +#include "vty_command.h" +#include "vio_fifo.h" #include "qstring.h" #include "keystroke.h" @@ -38,149 +42,159 @@ #include "network.h" #include <arpa/telnet.h> -#include <sys/un.h> /* for VTYSH */ #include <sys/socket.h> +#include <errno.h> -#define VTYSH_DEBUG 0 - -/*------------------------------------------------------------------------------ - * Create new vty of type VTY_TERMINAL -- ie attached to a telnet session. +/*============================================================================== + * The I/O side of Telnet VTY_TERMINAL. The CLI side is vty_cli.c. + * + * A VTY_TERMINAL comes into being when a telnet connection is accepted, and + * is closed either on command, or on timeout, or when the daemon is reset + * or terminated. + * + * + * * - * This is called by the accept action for the VTY_TERMINAL listener. */ -static void -uty_term_accept_new(int sock_fd, union sockunion *su) -{ - struct vty *vty ; - vty_io vio ; - enum vty_readiness ready ; - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - /* Allocate new vty structure and set up default values. */ - vty = uty_new(VTY_TERMINAL, sock_fd) ; - vio = vty->vio ; - /* The text form of the address identifies the VTY */ - vty->vio->name = sockunion_su2str (su, MTYPE_VTY_NAME); - /* Set the initial node */ - if (no_password_check) - { - if (restricted_mode) - vty->node = RESTRICTED_NODE; - else if (host.advanced) - vty->node = ENABLE_NODE; - else - vty->node = VIEW_NODE; - } - else - vty->node = AUTH_NODE; - /* Pick up current timeout setting */ - vio->sock.v_timeout = vty_timeout_val; - /* Use global 'lines' setting, as default. May be -1 => unset */ - vio->lines = host.lines ; - - /* For VTY_TERM use vio_line_control for '\n' and "--more--" */ - vio->cmd_lc = vio_lc_init_new(NULL, 0, 0) ; - uty_set_height(vio) ; /* set initial state */ - - /* Initialise the CLI, ready for start-up messages etc. */ - uty_cli_init(vio) ; +/*============================================================================== + * If possible, will use getaddrinfo() to find all the things to listen on. + */ +#if defined(HAVE_IPV6) && !defined(NRL) +# define VTY_USE_ADDRINFO 1 +#else +# define VTY_USE_ADDRINFO 0 +#endif - /* Reject connection if password isn't set, and not "no password" */ - if ((host.password == NULL) && (host.password_encrypt == NULL) - && ! no_password_check) - { - uty_close(vio, "Vty password is not set."); - vty = NULL; - } - else - { - /* Say hello to the world. */ - vty_hello (vty); +/*============================================================================== + * Opening and closing VTY_TERMINAL type + */ - if (! no_password_check) - uty_output (vty, "\nUser Access Verification\n\n"); - } ; +static void uty_term_ready(vio_vfd vfd, void* action_info) ; +static vty_timer_time uty_term_read_timeout(vio_timer_t* timer, + void* action_info) ; +static vty_timer_time uty_term_write_timeout(vio_timer_t* timer, + void* action_info) ; +static vty_readiness_t uty_term_write(vio_vf vf) ; - /* Now start the CLI and set a suitable state of readiness */ - ready = uty_cli_start(vio) ; - uty_sock_set_readiness(&vio->sock, ready) ; -} ; +static void uty_term_will_echo(vty_cli cli) ; +static void uty_term_will_suppress_go_ahead(vty_cli cli) ; +static void uty_term_dont_linemode(vty_cli cli) ; +static void uty_term_do_window_size(vty_cli cli) ; +static void uty_term_dont_lflow_ahead(vty_cli cli) ; /*------------------------------------------------------------------------------ - * Construct vio_vf structure for new VTY_TERMINAL type VTY, and add same. - * + * Create new vty of type VTY_TERMINAL -- ie attached to a telnet session. * + * This is called by the accept action for the VTY_TERMINAL listener. */ - static void -uty_term_new(vty_io vio, int sock_fd) +uty_term_open(int sock_fd, union sockunion *su) { - vio_vf vf ; - - enum vty_readiness ready ; + vty vty ; + vty_io vio ; + vio_vf vf ; VTY_ASSERT_LOCKED() ; VTY_ASSERT_CLI_THREAD() ; - /* May only be the first vio_vf ! */ - assert((vio->vin_base == NULL) && (vio->vout_base == NULL)) ; - - /* Construct and add to the vio */ - vf = uty_vf_new(vio, sock_fd, vfd_socket, vfd_io_read_write) ; + /* Allocate new vty structure and set up default values. */ + vty = uty_new(VTY_TERMINAL) ; + vio = vty->vio ; - uty_vin_add(vio, vf, VIN_TERM, uty_term_ready, uty_term_read_timeout) ; - uty_vout_add(vio, vf, VIN_TERM, uty_term_ready, uty_term_write_timeout) ; + /* The initial vty->node depends on a number of vty type things, so + * we set that now. + * + * This completes the initialisation of the vty object, except that the + * execution and vio objects are largely empty. + */ + if (host.no_password_check) + { + if (host.restricted_mode) + vio->vty->node = RESTRICTED_NODE; + else if (host.advanced) + vio->vty->node = ENABLE_NODE; + else + vio->vty->node = VIEW_NODE; + } + else + vio->vty->node = AUTH_NODE; - /* Allocate and initialise a keystroke stream TODO: CSI ?? */ - vio->key_stream = keystroke_stream_new('\0', uty_cli_iac_callback, vio) ; + /* Complete the initialisation of the vty_io object. + * + * Note that the defaults for: + * + * - read_timeout -- default = 0 => no timeout + * - write_timeout -- default = 0 => no timeout + * + * - parse_type -- default = cmd_parse_standard + * - reflect_enabled -- default = false + * - out_enabled -- default = true iff vfd_io_write + * + * Are OK, except that we want the read_timeout set to the current EXEC + * timeout value. + * + * The text form of the address identifies the VTY. + */ + vf = uty_vf_new(vio, sutoa(su).str, sock_fd, vfd_socket, vfd_io_read_write) ; - /* Pick up current timeout setting */ - vf->read_timeout = vty_timeout_val; + uty_vin_open( vio, vf, VIN_TERM, uty_term_ready, + uty_term_read_timeout, + 0) ; /* no ibuf required */ + uty_vout_open(vio, vf, VOUT_TERM, uty_term_ready, + uty_term_write_timeout, + 4096) ; /* obuf is required */ - /* Use global 'lines' setting, as default. May be -1 => unset */ - vio->lines = host.lines ; + vf->read_timeout = host.vty_timeout_val ; /* current EXEC timeout */ - /* For VTY_TERM use vio_line_control for '\n' and "--more--" */ - vf->olc = vio_lc_init_new(NULL, 0, 0) ; - uty_set_height(vio) ; /* set initial state */ + /* Set up the CLI object & initialise */ + vf->cli = uty_cli_new(vf) ; - /* Initialise the CLI, ready for start-up messages etc. */ - uty_cli_init(vio) ; + /* When we get here the VTY is set up and all ready to go. */ + uty_cmd_prepare(vio) ; - /* Reject connection if password isn't set, and not "no password" */ - if ((host.password == NULL) && (host.password_encrypt == NULL) - && ! no_password_check) - { - uty_close(vio, "Vty password is not set."); - vty = NULL; - } - else + /* Issue Telnet commands/escapes to be a good telnet citizen -- not much + * real negotiating going on -- just a statement of intentions ! + */ + uty_term_will_echo (vf->cli); + uty_term_will_suppress_go_ahead (vf->cli); + uty_term_dont_linemode (vf->cli); + uty_term_do_window_size (vf->cli); + if (0) + uty_term_dont_lflow_ahead (vf->cli) ; + + /* Say hello */ + vty_hello(vty); + + /* If need password, issue prompt or give up if no password to check + * against ! + */ + if (vty->node == AUTH_NODE) { - /* Say hello to the world. */ - vty_hello (vty); - - if (! no_password_check) - uty_output (vty, "\nUser Access Verification\n\n"); + if (host.password != NULL) + vty_out(vty, "\nUser Access Verification\n\n"); + else + uty_close(vio, false, qs_set(NULL, "vty password is not set.")); } ; - /* Now start the CLI and set a suitable state of readiness */ - ready = uty_cli_start(vio) ; - uty_sock_set_readiness(&vio->sock, ready) ; + /* Push the output to date and start the CLI */ + uty_cmd_out_push(vio) ; + uty_cli_start(vf->cli, vty->node) ; } ; - - /*------------------------------------------------------------------------------ + * Close the reading side of VTY_TERMINAL, and close down CLI as far as + * possible, given that output may be continuing. * + * Expects to be called once only for the VTY_TERMINAL. */ extern void -uty_term_half_close(vio_vf vf) +uty_term_read_close(vio_vf vf) { vty_io vio ; @@ -193,93 +207,60 @@ uty_term_half_close(vio_vf vf) * Note that half closing the file sets a new timeout, sets read off * and write on. */ - - - uty_set_monitor(vio, 0) ; - /* Discard everything in the keystroke stream and force it to EOF */ - if (vio->key_stream != NULL) - keystroke_stream_set_eof(vio->key_stream) ; - - /* Turn off "--more--" so that all output clears without interruption. - * - * If is sitting on a "--more--" prompt, then exit the wait_more CLI. - */ - vio->cli_more_enabled = 0 ; - - if (vio->cli_more_wait) - uty_cli_exit_more_wait(vio) ; - - /* If a command is not in progress, enable output, which will clear - * the output buffer if there is anything there, plus any close reason, - * and then close. - * - * If command is in progress, then this process will start when it - * completes. - */ - if (!vio->cmd_in_progress) - vio->cmd_out_enabled = 1 ; + uty_cli_close(vf->cli, false) ; /* Log closing of VTY_TERM */ assert(vio->vty->type == VTY_TERMINAL) ; - uzlog (NULL, LOG_INFO, "Vty connection (fd %d) close", uty_vf_fd(vf)) ; + uzlog (NULL, LOG_INFO, "Vty connection (fd %d) close", vio_vfd_fd(vf->vfd)) ; } ; /*------------------------------------------------------------------------------ - * vprintf to VTY_TERM - * - * All output goes to output fifo until command completes. + * Close the writing side of VTY_TERMINAL. * - * NB: MUST be cmd_in_progress + * Pushes any buffered stuff to output and * - * Discards output if the socket is not open for whatever reason. */ -extern int -uty_term_vprintf(vio_vf vf, const char *format, va_list args) +extern bool +uty_term_write_close(vio_vf vf, bool final) { - VTY_ASSERT_LOCKED() ; - - if (!vf->write_open) - return 0 ; /* discard output if not open ! */ + vty_io vio ; + vty_readiness_t ready ; - assert(vf->vio->cmd_in_progress) ; + /* Get the vio and ensure that we are all straight */ + vio = vf->vio ; + assert((vio->vin == vio->vin_base) && (vio->vin == vf)) ; - return vio_fifo_vprintf(vf->obuf, format, args) ; -} ; + /* Do the file side of things + * + * Note that half closing the file sets a new timeout, sets read off + * and write on. + */ + uty_set_monitor(vio, 0) ; -/*------------------------------------------------------------------------------ - * Set/Clear "monitor" state: - * - * set: if VTY_TERM and not already "monitor" (and write_open !) - * clear: if is "monitor" - */ -extern void -uty_set_monitor(vty_io vio, bool on) -{ - VTY_ASSERT_LOCKED() ; + vf->cli->out_active = true ; /* force the issue */ - if (on && !vio->monitor) - { - if ((vio->type == VTY_TERM) && vio->sock.write_open) - { - vio->monitor = 1 ; - sdl_push(vio_monitors_base, vio, mon_list) ; - } ; - } - else if (!on && vio->monitor) + do { - vio->monitor = 0 ; - sdl_del(vio_monitors_base, vio, mon_list) ; - } + vf->cli->out_done = false ; + ready = uty_term_write(vf) ; + } while ((ready != write_ready) && vf->cli->out_active) ; + + final = final || !vf->cli->out_active ; + + if (!final) + uty_term_set_readiness(vf, ready) ; + + vf->cli = uty_cli_close(vf->cli, final) ; + + return final ; } ; /*============================================================================== * Action routines for VIN_TERM/VOUT_TERM type vin/vout objects */ -static enum vty_readiness uty_term_write(vio_vf vf) ; - /*============================================================================== * Readiness and the VIN_TERM type vin. * @@ -289,7 +270,23 @@ static enum vty_readiness uty_term_write(vio_vf vf) ; * * The VIN_TERM uses read ready only when it doesn't set write ready. Does * not set both at once. + */ + +/*------------------------------------------------------------------------------ + * Set read/write readiness -- for VIN_TERM/VOUT_TERM * + * Note that sets only one of read or write, and sets write for preference. + */ +extern void +uty_term_set_readiness(vio_vf vf, vty_readiness_t ready) +{ + VTY_ASSERT_LOCKED() ; + + uty_vf_set_read(vf, (ready == read_ready)) ; + uty_vf_set_write(vf, (ready >= write_ready)) ; +} ; + +/*------------------------------------------------------------------------------ * So there is only one, common, uty_term_ready function, which: * * 1. attempts to clear any output it can. @@ -326,39 +323,41 @@ static enum vty_readiness uty_term_write(vio_vf vf) ; * Resets the timer because something happened. */ static void -uty_term_ready(vio_fd vfd, void* action_info) +uty_term_ready(vio_vfd vfd, void* action_info) { - enum vty_readiness ready ; + vty_readiness_t ready ; vio_vf vf = action_info ; - vty_io vio = vf->vio ; - VTY_ASSERT_LOCKED() ; + assert(vfd == vf->vfd) ; - vio->cmd_out_done = 0 ; /* not done any command output yet */ + VTY_ASSERT_LOCKED() ; uty_term_write(vf) ; /* try to clear outstanding stuff */ do { - ready = uty_cli(vio) ; /* do any CLI work... */ + ready = uty_cli(vf->cli) ; /* do any CLI work... */ ready |= uty_term_write(vf) ; /* ...and any output that generates */ } while (ready >= now_ready) ; - uty_file_set_readiness(vf, ready) ; + uty_term_set_readiness(vf, ready) ; } ; /*============================================================================== - * Reading from VTY_TERM. - * - * The select/pselect call-back ends up in uty_read_ready(). + * Reading from VTY_TERMINAL. * - * Note that uty_write_ready() also calls uty_read_ready, in order to kick the - * current CLI. + * The select/pselect call-back ends up in uty_term_ready(). */ /*------------------------------------------------------------------------------ * Read a lump of bytes and shovel into the keystroke stream * + * This function is called from the vty_cli to top up the keystroke buffer, + * or in the stealing of a keystroke to end "--more--" state. + * + * NB: need not be in the term_ready path. Indeed, when the VTY_TERMINAL is + * initialised, this is called to suck up any telnet preamble. + * * Steal keystroke if required -- see keystroke_input() * * Returns: 0 => nothing available @@ -366,25 +365,24 @@ uty_term_ready(vio_fd vfd, void* action_info) * -1 => EOF (or not open, or failed) */ extern int -uty_read (vty_io vio, keystroke steal) +uty_term_read(vio_vf vf, keystroke steal) { unsigned char buf[500] ; int get ; - if (!vio->sock.read_open) - return -1 ; /* at EOF if not open */ + if (vf->vin_state != vf_open) + return -1 ; /* at EOF if not open */ - get = read_nb(vio->sock.fd, buf, sizeof(buf)) ; + get = read_nb(vio_vfd_fd(vf->vfd), buf, sizeof(buf)) ; if (get >= 0) - keystroke_input(vio->key_stream, buf, get, steal) ; + keystroke_input(vf->cli->key_stream, buf, get, steal) ; else if (get < 0) { if (get == -1) - uty_file_error(vio, "read") ; - - vio->sock.read_open = 0 ; - keystroke_input(vio->key_stream, NULL, 0, steal) ; + uty_vf_error(vf, "read", errno) ; + keystroke_input(vf->cli->key_stream, NULL, 0, steal) ; + /* Tell keystroke stream that EOF met */ get = -1 ; } ; @@ -396,10 +394,10 @@ uty_read (vty_io vio, keystroke steal) * * There are two sets of buffering: * - * cli -- command line -- which reflects the status of the command line + * cli->cbuf -- command line -- reflects the status of the command line * - * cmd -- command output -- which is written to the file only while - * cmd_out_enabled. + * vf->obuf -- command output -- which is written to the file only while + * out_active. * * The cli output takes precedence. * @@ -407,8 +405,8 @@ uty_read (vty_io vio, keystroke steal) * "--more--" mechanism. */ -static int uty_write_lc(vio_vf vf, vio_fifo vfifo, vio_line_control lc) ; -static int uty_write_fifo_lc(vio_vf vf, vio_fifo vfifo, vio_line_control lc) ; +static int uty_write_lc(vio_vf vf, vio_fifo vff, vio_line_control lc) ; +static int uty_write_fifo_lc(vio_vf vf, vio_fifo vff, vio_line_control lc) ; /*------------------------------------------------------------------------------ * Write as much as possible of what there is. @@ -426,83 +424,79 @@ static int uty_write_fifo_lc(vio_vf vf, vio_fifo vfifo, vio_line_control lc) ; * now_ready if should loop back and try again * not_ready otherwise */ -static enum vty_readiness +static vty_readiness_t uty_term_write(vio_vf vf) { - vty_io vio = vf->vio ; + vty_cli cli = vf->cli ; int ret ; VTY_ASSERT_LOCKED() ; ret = -1 ; - while (vf->write_open) + while (vf->vout_state == vf_open) { /* Any outstanding line control output takes precedence */ - if (vf->olc != NULL) - { - ret = uty_write_lc(vf, vf->obuf, vf->olc) ; - if (ret != 0) - break ; - } + ret = uty_write_lc(vf, vf->obuf, cli->olc) ; + if (ret != 0) + break ; /* Next: empty out the cli output */ - ret = vio_fifo_write_nb(&vf->cli_obuf, vio->sock.fd, true) ; + ret = vio_fifo_write_nb(cli->cbuf, vio_vfd_fd(vf->vfd), true) ; if (ret != 0) break ; /* Finished now if not allowed to progress the command stuff */ - if (!vio->cmd_out_enabled) + if (!cli->out_active) return not_ready ; /* done all can do */ - /* Last: if there is something in the command buffer, do that */ + /* If there is something in the command buffer, do that */ if (!vio_fifo_empty(vf->obuf)) { - if (vio->cmd_out_done) +#if 0 + if (cli->out_done) break ; /* ...but not if done once */ - vio->cmd_out_done = 1 ; /* done this once */ - - assert(!vio->cli_more_wait) ; + cli->out_done = true ; /* done this once */ +#endif + assert(!cli->more_wait) ; - if (vio->cmd_lc != NULL) - ret = uty_write_fifo_lc(vf, vf->obuf, vf->olc) ; - else - ret = vio_fifo_write_nb(vf->obuf, vio->sock.fd, true) ; - - /* If moved into "--more--" state@ - * - * * the "--more--" prompt is ready to be written, so do that now - * - * * if that completes, then want to run the CLI *now* to perform the - * first stage of the "--more--" process. - */ - if (vio->cli_more_wait) + ret = uty_write_fifo_lc(vf, vf->obuf, cli->olc) ; + if (ret != 0) { - ret = vio_fifo_write_nb(vf->obuf, vio->sock.fd, true) ; - if (ret == 0) - return now_ready ; - } ; + if (ret < 0) + break ; /* failed */ + + if (!cli->more_wait) + return write_ready ; /* done a tranche */ + + /* Moved into "--more--" state + * + * * the "--more--" prompt is ready to be written, so do that + * now + * + * * if that completes, then want to run the CLI *now* to + * perform the first stage of the "--more--" process. + */ + ret = vio_fifo_write_nb(cli->cbuf, vio_vfd_fd(vf->vfd), true) ; + if (ret != 0) + break ; - if (ret != 0) - break ; - } + return now_ready ; + } ; + } ; /* Exciting stuff: there is nothing left to output... * * ... watch out for half closed state. */ - if (vio->half_closed) +#if 0 + if (vio->closing) { if (vio->close_reason != NULL) { - vio->cmd_in_progress = 1 ; /* TODO: not use vty_out ? */ - - struct vty* vty = vio->vty ; - if (vio->cli_drawn || vio->cli_dirty) - vty_out(vty, VTY_NEWLINE) ; - vty_out(vty, "%% %s%s", vio->close_reason, VTY_NEWLINE) ; - - vio->cmd_in_progress = 0 ; + if (cli->drawn || cli->dirty) + uty_out(vio, "\n") ; + uty_out(vio, "%% %s\n", vio->close_reason) ; vio->close_reason = NULL ; /* MUST discard now... */ continue ; /* ... and write away */ @@ -513,42 +507,39 @@ uty_term_write(vio_vf vf) return not_ready ; /* it's all over */ } ; +#endif - /* For VTY_TERM: if the command line is not drawn, now is a good - * time to do that. - */ - if (vio->vty_type == VTY_TERM) - if (uty_cli_draw_if_required(vio)) - continue ; /* do that now. */ + if (uty_cli_draw_if_required(cli)) + continue ; /* do that now. */ /* There really is nothing left to output */ + cli->out_active = false ; + return not_ready ; } ; /* Arrives here if there is more to do, or failed (or was !write_open) */ - if (ret >= 0) + if (ret > 0) return write_ready ; + if (ret == 0) /* just in case */ + return not_ready ; + /* If is write_open, then report the error * * If still read_open, let the reader pick up and report the error, when it * has finished anything it has buffered. */ - if (vf->write_open) - { - if (!vf->read_open) - uty_file_error(vio, "write") ; - - vf->write_open = false ; /* crash close write */ - } ; + if (vf->vout_state == vf_open) + uty_vf_error(vf, "write", errno) ; /* For whatever reason, is no longer write_open -- clear all buffers. */ - vio_fifo_clear(vf->obuf) ; /* throw away cli stuff */ - uty_out_clear(vio) ; /* throw away cmd stuff */ + vio_fifo_clear(vf->obuf, true) ; /* throw away cli stuff */ + uty_cli_out_clear(cli) ; /* throw away cmd stuff */ - vio->close_reason = NULL ; /* too late for this */ + cli->out_active = false ; return not_ready ; /* NB: NOT blocked by I/O */ } ; @@ -575,6 +566,7 @@ uty_term_write(vio_vf vf) * 0 => all gone * < 0 => failed (or !write_open) */ +#if 0 static int uty_write_monitor(vio_vf vf) { @@ -592,11 +584,12 @@ uty_write_monitor(vio_vf vf) return ret ; } ; - return vio_fifo_write_nb(vf->obuf, uty_vf_fd(vf), true) ; + return vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf), true) ; } ; +#endif /*------------------------------------------------------------------------------ - * Write the given FIFO to output -- subject to possible line control. + * Write the given FIFO to output -- subject to line control. * * Note that even if no "--more--" is set, will have set some height, so * that does not attempt to empty the FIFO completely all in one go. @@ -606,16 +599,19 @@ uty_write_monitor(vio_vf vf) * * NB: expects that the sock is write_open * - * Returns: > 0 => blocked or completed one tranche + * Returns: > 0 => blocked or completed one tranche (more to go) * 0 => all gone * < 0 => failed */ static int -uty_write_fifo_lc(vio_vf vf, vio_fifo vfifo, vio_line_control lc) +uty_write_fifo_lc(vio_vf vf, vio_fifo vff, vio_line_control lc) { int ret ; char* src ; size_t have ; + vty_cli cli ; + + cli = vf->cli ; /* Collect another line_control height's worth of output. * @@ -624,24 +620,29 @@ uty_write_fifo_lc(vio_vf vf, vio_fifo vfifo, vio_line_control lc) */ vio_lc_set_pause(lc) ; /* clears lc->paused */ - src = vio_fifo_get_rdr(vfifo, &have) ; + vio_fifo_set_hold_mark(vff) ; + src = vio_fifo_get(vff, &have) ; while ((src != NULL) && (!lc->paused)) { size_t take ; + + if (src == NULL) + break ; + take = vio_lc_append(lc, src, have) ; - src = vio_fifo_step_rdr(vfifo, &have, take) ; + src = vio_fifo_step_get(vff, &have, take) ; } ; - vf->vio->cli_dirty = (lc->col != 0) ; + cli->dirty = (lc->col != 0) ; /* Write the contents of the line control */ - ret = uty_write_lc(vf, vfifo, lc) ; + ret = uty_write_lc(vf, vff, lc) ; if (ret < 0) return ret ; /* give up now if failed. */ - if ((ret == 0) && vio_fifo_empty(vfifo)) + if ((ret == 0) && vio_fifo_empty(vff)) return 0 ; /* FIFO and line control empty */ /* If should now do "--more--", now is the time to prepare for that. @@ -652,8 +653,8 @@ uty_write_fifo_lc(vio_vf vf, vio_fifo vfifo, vio_line_control lc) * The "--more--" cli will not do anything until the CLI buffer has * cleared. */ - if (lc->paused && vf->vio->cli_more_enabled) - uty_cli_enter_more_wait(vf->vio) ; + if (lc->paused && cli->more_enabled) + uty_cli_enter_more_wait(cli) ; return 1 ; /* FIFO or line control, not empty */ } ; @@ -670,18 +671,21 @@ uty_write_fifo_lc(vio_vf vf, vio_fifo vfifo, vio_line_control lc) * < 0 => failed */ static int -uty_write_lc(vio_vf vf, vio_fifo vfifo, vio_line_control lc) +uty_write_lc(vio_vf vf, vio_fifo vff, vio_line_control lc) { int ret ; - ret = vio_lc_write_nb(uty_vf_fd(vf), lc) ; + ret = vio_lc_write_nb(vio_vfd_fd(vf->vfd), lc) ; if (ret <= 0) - vio_fifo_sync_rdr(vfifo) ; /* finished with FIFO contents */ + vio_fifo_clear_hold_mark(vff) ; /* finished with FIFO contents */ return ret ; } ; + +#if 0 + /*------------------------------------------------------------------------------ * Start command output -- clears down the line control. * @@ -693,137 +697,60 @@ uty_cmd_output_start(vio_vf vf) { if (vf->olc != NULL) vio_lc_clear(vf->olc) ; -} ; - -/*------------------------------------------------------------------------------ - * Set the effective height for line control (if any) - * - * If using line_control, may enable the "--more--" output handling. - * - * If not, want some limit on the amount of stuff output at a time. - * - * Sets the line control window width and height. - * Sets cli_more_enabled if "--more--" is enabled. - */ -extern void -uty_set_height(vio_vf vf) -{ - vty_io vio = vf->vio ; - bool on ; - - on = 0 ; /* default state */ - if ((vf->olc != NULL) && !vio->half_closed) - { - int height ; - - height = 0 ; /* default state */ + vio_fifo_set_hold_mark(vf->obuf) ; /* mark to keep until all gone */ +} ; - if ((vio->width) != 0) - { - /* If window size is known, use lines or given height */ - if (vio->lines >= 0) - height = vio->lines ; - else - { - /* Window height, leaving one line from previous "page" - * and one line for the "--more--" -- if at all possible - */ - height = vio->height - 2 ; - if (height < 1) - height = 1 ; - } ; - } - else - { - /* If window size not known, use lines if that has been set - * explicitly for this terminal. - */ - if (vio->lines_set) - height = vio->lines ; - } ; +#endif - if (height > 0) - on = 1 ; /* have a defined height */ - else - height = 200 ; /* but no "--more--" */ - vio_lc_set_window(vio->cmd_lc, vio->width, height) ; - } ; - vio->cli_more_enabled = on ; -} ; /*============================================================================== - * Timer for VTY_TERM (and VTY_SHELL_SERV). + * Timer actions for VTY_TERMINAL */ /*------------------------------------------------------------------------------ - * Timer has expired. + * Read timer has expired. + * + * If closing, then this is curtains -- have waited long enough ! * - * If half_closed, then this is curtains -- have waited long enough ! + * TODO .... sort out the VTY_TERMINAL time-out & death-watch timeout * * Otherwise, half close the VTY and leave it to the death-watch to sweep up. */ -static void -uty_timer_expired (vty_io vio) +static vty_timer_time +uty_term_read_timeout(vio_timer_t* timer, void* action_info) { + vty_io vio = action_info ; + VTY_ASSERT_LOCKED() ; - if (vio->half_closed) - return uty_close(vio) ; /* curtains */ + uty_close(vio, true, qs_set(NULL, "Timed out")) ; - uty_close(vio, "Timed out") ; /* bring input side to a halt */ + return 0 ; } ; - - - - - - - - - - - - - - - - /*------------------------------------------------------------------------------ - * Set read/write readiness -- for VTY_TERM + * Write timer has expired. + * + * If closing, then this is curtains -- have waited long enough ! + * + * TODO .... sort out the VTY_TERMINAL time-out & death-watch timeout * - * Note that for VTY_TERM, set only one of read or write, and sets write for - * preference. + * Otherwise, half close the VTY and leave it to the death-watch to sweep up. */ -extern void -uty_file_set_readiness(vio_vf vf, enum vty_readiness ready) +static vty_timer_time +uty_term_write_timeout(vio_timer_t* timer, void* action_info) { - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - vio_fd_set_read(vf, (ready == read_ready)) ; - vio_fd_set_write(vf, (ready >= write_ready)) ; -} ; + vty_io vio = action_info ; -/*------------------------------------------------------------------------------ - * Set a new timer value. - */ -extern void -uty_file_set_timer(vio_vf vf, unsigned long timeout) -{ VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - vf->v_timeout = timeout ; -} ; - - - + uty_close(vio, true, qs_set(NULL, "Timed out")) ; + return 0 ; +} ; /*============================================================================== * VTY Listener(s) for VTY_TERMINAL @@ -1055,7 +982,7 @@ uty_term_listen_open(sa_family_t family, int type, int protocol, } ; /*------------------------------------------------------------------------------ - * Accept action -- create and dispatch VTY_TERM + * Accept action -- create and dispatch VTY_TERMINAL */ static void uty_term_accept(int sock_listen) @@ -1093,24 +1020,24 @@ uty_term_accept(int sock_listen) p = sockunion2hostprefix (&su); ret = 0 ; /* so far, so good */ - if ((p->family == AF_INET) && vty_accesslist_name) + if ((p->family == AF_INET) && host.vty_accesslist_name) { /* VTY's accesslist apply. */ struct access_list* acl ; - if ((acl = access_list_lookup (AFI_IP, vty_accesslist_name)) && - (access_list_apply (acl, p) == FILTER_DENY)) + if ((acl = access_list_lookup (AFI_IP, host.vty_accesslist_name)) + && (access_list_apply (acl, p) == FILTER_DENY)) ret = -1 ; } #ifdef HAVE_IPV6 - if ((p->family == AF_INET6) && vty_ipv6_accesslist_name) + if ((p->family == AF_INET6) && host.vty_ipv6_accesslist_name) { /* VTY's ipv6 accesslist apply. */ struct access_list* acl ; - if ((acl = access_list_lookup (AFI_IP6, vty_ipv6_accesslist_name)) && - (access_list_apply (acl, p) == FILTER_DENY)) + if ((acl = access_list_lookup (AFI_IP6, host.vty_ipv6_accesslist_name)) + && (access_list_apply (acl, p) == FILTER_DENY)) ret = -1 ; } #endif /* HAVE_IPV6 */ @@ -1132,8 +1059,8 @@ uty_term_accept(int sock_listen) uzlog (NULL, LOG_INFO, "can't set sockopt to socket %d: %s", sock_fd, errtoa(errno, 0).str) ; - /* All set -- create the VTY_TERM */ - uty_term_accept_new(sock_fd, &su); + /* All set -- create the VTY_TERMINAL and set it going */ + uty_term_open(sock_fd, &su); /* Log new VTY */ uzlog (NULL, LOG_INFO, "Vty connection from %s (fd %d)", sutoa(&su).str, @@ -1143,6 +1070,289 @@ uty_term_accept(int sock_listen) } ; /*============================================================================== + * VTY telnet stuff + * + * Note that all telnet commands (escapes) and any debug stuff is treated as + * CLI output. + */ + +#define TELNET_OPTION_DEBUG 0 /* 0 to turn off */ + +static const char* telnet_commands[256] = +{ + [tn_IAC ] = "IAC", + [tn_DONT ] = "DONT", + [tn_DO ] = "DO", + [tn_WONT ] = "WONT", + [tn_WILL ] = "WILL", + [tn_SB ] = "SB", + [tn_GA ] = "GA", + [tn_EL ] = "EL", + [tn_EC ] = "EC", + [tn_AYT ] = "AYT", + [tn_AO ] = "AO", + [tn_IP ] = "IP", + [tn_BREAK] = "BREAK", + [tn_DM ] = "DM", + [tn_NOP ] = "NOP", + [tn_SE ] = "SE", + [tn_EOR ] = "EOR", + [tn_ABORT] = "ABORT", + [tn_SUSP ] = "SUSP", + [tn_EOF ] = "EOF", +} ; + +static const char* telnet_options[256] = +{ + [to_BINARY] = "BINARY", /* 8-bit data path */ + [to_ECHO] = "ECHO", /* echo */ + [to_RCP] = "RCP", /* prepare to reconnect */ + [to_SGA] = "SGA", /* suppress go ahead */ + [to_NAMS] = "NAMS", /* approximate message size */ + [to_STATUS] = "STATUS", /* give status */ + [to_TM] = "TM", /* timing mark */ + [to_RCTE] = "RCTE", /* remote controlled tx and echo */ + [to_NAOL] = "NAOL", /* neg. about output line width */ + [to_NAOP] = "NAOP", /* neg. about output page size */ + [to_NAOCRD] = "NAOCRD", /* neg. about CR disposition */ + [to_NAOHTS] = "NAOHTS", /* neg. about horizontal tabstops */ + [to_NAOHTD] = "NAOHTD", /* neg. about horizontal tab disp. */ + [to_NAOFFD] = "NAOFFD", /* neg. about formfeed disposition */ + [to_NAOVTS] = "NAOVTS", /* neg. about vertical tab stops */ + [to_NAOVTD] = "NAOVTD", /* neg. about vertical tab disp. */ + [to_NAOLFD] = "NAOLFD", /* neg. about output LF disposition */ + [to_XASCII] = "XASCII", /* extended ascii character set */ + [to_LOGOUT] = "LOGOUT", /* force logout */ + [to_BM] = "BM", /* byte macro */ + [to_DET] = "DET", /* data entry terminal */ + [to_SUPDUP] = "SUPDUP", /* supdup protocol */ + [to_SUPDUPOUTPUT] = "SUPDUPOUTPUT",/* supdup output */ + [to_SNDLOC] = "SNDLOC", /* send location */ + [to_TTYPE] = "TTYPE", /* terminal type */ + [to_EOR] = "EOR", /* end or record */ + [to_TUID] = "TUID", /* TACACS user identification */ + [to_OUTMRK] = "OUTMRK", /* output marking */ + [to_TTYLOC] = "TTYLOC", /* terminal location number */ + [to_3270REGIME] = "3270REGIME", /* 3270 regime */ + [to_X3PAD] = "X3PAD", /* X.3 PAD */ + [to_NAWS] = "NAWS", /* window size */ + [to_TSPEED] = "TSPEED", /* terminal speed */ + [to_LFLOW] = "LFLOW", /* remote flow control */ + [to_LINEMODE] = "LINEMODE", /* Linemode option */ + [to_XDISPLOC] = "XDISPLOC", /* X Display Location */ + [to_OLD_ENVIRON] = "OLD_ENVIRON", /* Old - Environment variables */ + [to_AUTHENTICATION] = "AUTHENTICATION", /* Authenticate */ + [to_ENCRYPT] = "ENCRYPT", /* Encryption option */ + [to_NEW_ENVIRON] = "NEW_ENVIRON", /* New - Environment variables */ + [to_EXOPL] = "EXOPL", /* extended-options-list */ +} ; + +/*------------------------------------------------------------------------------ + * For debug. Put string or value as decimal. + */ +static void +uty_cli_out_dec(vty_cli cli, const char* str, unsigned char u) +{ + if (str != NULL) + uty_cli_out(cli, "%s ", str) ; + else + uty_cli_out(cli, "%d ", (int)u) ; +} ; + +/*------------------------------------------------------------------------------ + * For debug. Put string or value as hex. + */ +static void +uty_cli_out_hex(vty_cli cli, const char* str, unsigned char u) +{ + if (str != NULL) + uty_cli_out(cli, "%s ", str) ; + else + uty_cli_out(cli, "0x%02x ", (unsigned)u) ; +} ; + +/*------------------------------------------------------------------------------ + * Send telnet: "WILL TELOPT_ECHO" + */ +static void +uty_term_will_echo (vty_cli cli) +{ + unsigned char cmd[] = { tn_IAC, tn_WILL, to_ECHO }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (cli, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "suppress Go-Ahead" + */ +static void +uty_term_will_suppress_go_ahead (vty_cli cli) +{ + unsigned char cmd[] = { tn_IAC, tn_WILL, to_SGA }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (cli, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "don't use linemode" + */ +static void +uty_term_dont_linemode (vty_cli cli) +{ + unsigned char cmd[] = { tn_IAC, tn_DONT, to_LINEMODE }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (cli, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "Use window size" + */ +static void +uty_term_do_window_size (vty_cli cli) +{ + unsigned char cmd[] = { tn_IAC, tn_DO, to_NAWS }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (cli, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "don't use lflow" -- not currently used + */ +static void +uty_term_dont_lflow_ahead (vty_cli cli) +{ + unsigned char cmd[] = { tn_IAC, tn_DONT, to_LFLOW }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (cli, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Process incoming Telnet Option(s) + * + * May be called during keystroke iac callback, or when processing CLI + * keystrokes. + * + * In particular: get telnet window size. + * + * Returns: true <=> dealt with, for: + * + * * telnet window size. + */ +extern bool +uty_telnet_command(vio_vf vf, keystroke stroke, bool callback) +{ + uint8_t* p ; + uint8_t o ; + int left ; + bool dealt_with ; + + vty_cli cli = vf->cli ; + + /* Echo to the other end if required */ + if (TELNET_OPTION_DEBUG) + { + uty_cli_wipe(cli, 0) ; + + p = stroke->buf ; + left = stroke->len ; + + uty_cli_out_hex(cli, telnet_commands[tn_IAC], tn_IAC) ; + + if (left-- > 0) + uty_cli_out_dec(cli, telnet_commands[*p], *p) ; + ++p ; + + if (left-- > 0) + uty_cli_out_dec(cli, telnet_options[*p], *p) ; + ++p ; + + if (left > 0) + { + while(left-- > 0) + uty_cli_out_hex(cli, NULL, *p++) ; + + if (stroke->flags & kf_truncated) + uty_cli_out(cli, "... ") ; + + if (!(stroke->flags & kf_broken)) + { + uty_cli_out_hex(cli, telnet_commands[tn_IAC], tn_IAC) ; + uty_cli_out_hex(cli, telnet_commands[tn_SE], tn_SE) ; + } + } ; + + if (stroke->flags & kf_broken) + uty_cli_out (cli, "BROKEN") ; + + uty_cli_out_newline(cli) ; + } ; + + /* Process the telnet command */ + dealt_with = false ; + + if (stroke->flags != 0) + return dealt_with ; /* go no further if broken */ + + p = stroke->buf ; + left = stroke->len ; + + passert(left >= 1) ; /* must be if not broken ! */ + passert(stroke->value == *p) ; /* or something is wrong */ + + ++p ; /* step past X of IAC X */ + --left ; + + /* Decode the one command that is interesting -- "NAWS" */ + switch (stroke->value) + { + case tn_SB: + passert(left > 0) ; /* or parser failed */ + + o = *p++ ; /* the option byte */ + --left ; + switch(o) + { + case to_NAWS: + if (left != 4) + { + uzlog(NULL, LOG_WARNING, + "RFC 1073 violation detected: telnet NAWS option " + "should send %d characters, but we received %d", + (3 + 4 + 2), (3 + left + 2)) ; + } + else + { + int width, height ; + + width = *p++ << 8 ; width += *p++ ; + height = *p++ << 8 ; height += *p ; + + if (TELNET_OPTION_DEBUG) + { + uty_cli_out(cli, "TELNET NAWS window size received: " + "width %d, height %d", width, height) ; + uty_cli_out_newline(cli) ; + } ; + + uty_cli_set_window(cli, width, height) ; + + dealt_with = true ; + } ; + break ; + + default: /* no other IAC SB <option> */ + break ; + } ; + break ; + + default: /* no other IAC X */ + break ; + } ; + + return dealt_with ; +} ; + +/*============================================================================== * Output to vty which are set to "monitor". * * This is VERY TRICKY. @@ -1230,6 +1440,7 @@ uty_log(struct logline* ll, struct zlog *zl, int priority, */ while (vio != NULL) { +#if 0 if (!vio->monitor_busy) { int ret ; @@ -1241,7 +1452,7 @@ uty_log(struct logline* ll, struct zlog *zl, int priority, ret = uty_write_monitor(vio) ; if (ret == 0) { - ret = write_nb(uty_vf_fd(vf), ll->line, ll->len) ; + ret = write_nb(vio_vfd_fd(vf->vfd), ll->line, ll->len) ; if (ret >= 0) { @@ -1258,7 +1469,7 @@ uty_log(struct logline* ll, struct zlog *zl, int priority, if (ret >= 0) vio->monitor_busy = 0 ; } ; - +#endif vio = sdl_next(vio, mon_list) ; } ; } ; @@ -1280,8 +1491,8 @@ vty_log_fixed (const char *buf, size_t len) vio = sdl_head(vio_monitors_base) ; while (vio != NULL) { - write(uty_vf_fd(vf), buf, len) ; - write(uty_vf_fd(vf), "\r\n", 2) ; + write(vio_vfd_fd(vio->vout_base->vfd), buf, len) ; + write(vio_vfd_fd(vio->vout_base->vfd), "\r\n", 2) ; vio = sdl_next(vio, mon_list) ; } ; diff --git a/lib/vty_io_term.h b/lib/vty_io_term.h index ef975a71..f1823a92 100644 --- a/lib/vty_io_term.h +++ b/lib/vty_io_term.h @@ -28,16 +28,16 @@ #include "misc.h" #include <errno.h> -#include "uty.h" #include "vty.h" #include "vty_io_basic.h" #include "vty_io.h" +#include "vty_cli.h" #include "vio_fifo.h" #include "vio_lines.h" #include "keystroke.h" #include "thread.h" -#include "command.h" +#include "command_local.h" #include "qstring.h" /*============================================================================== @@ -54,8 +54,15 @@ extern void uty_term_new(vty_io vio, int sock_fd) ; -extern void uty_term_half_close(vio_vf vf) ; -extern int uty_term_vprintf(vio_vf vf, const char *format, va_list args) ; +extern void uty_term_read_close(vio_vf vf) ; +extern bool uty_term_write_close(vio_vf vf, bool final) ; + +extern int uty_term_read(vio_vf vf, keystroke steal) ; +extern void uty_term_set_readiness(vio_vf vf, vty_readiness_t ready) ; + + + +extern bool uty_telnet_command(vio_vf, keystroke stroke, bool callback) ; extern void uty_term_open_listeners(const char *addr, unsigned short port) ; diff --git a/lib/vty_local.h b/lib/vty_local.h index 2767f9d3..97e5786c 100644 --- a/lib/vty_local.h +++ b/lib/vty_local.h @@ -89,33 +89,36 @@ extern qpn_nexus vty_cmd_nexus ; extern qpt_mutex_t vty_mutex ; -#ifdef NDEBUG -# define VTY_DEBUG 0 /* NDEBUG override */ +#ifdef VTY_DEBUG /* Can be forced from outside */ +# if VTY_DEBUG +# define VTY_DEBUG 1 /* Force 1 or 0 */ #else -# ifndef VTY_DEBUG -# define VTY_DEBUG 1 /* Set to 1 to turn on debug checks */ +# define VTY_DEBUG 0 +# endif +#else +# ifdef QDEBUG +# define VTY_DEBUG 1 /* Follow QDEBUG */ +#else +# define VTY_DEBUG 0 # endif #endif -#if VTY_DEBUG +enum { vty_debug = VTY_DEBUG } ; extern int vty_lock_count ; -extern int vty_assert_fail ; - -#endif Inline void VTY_LOCK(void) /* if is qpthreads_enabled, lock vty_mutex */ { qpt_mutex_lock(&vty_mutex) ; - if (VTY_DEBUG) + if (vty_debug) ++vty_lock_count ; } ; Inline void VTY_UNLOCK(void) /* if is qpthreads_enabled, unlock vty_mutex */ { - if (VTY_DEBUG) + if (vty_debug) --vty_lock_count ; qpt_mutex_unlock(&vty_mutex) ; } ; @@ -137,6 +140,8 @@ vty_is_cli_thread(void) */ #if VTY_DEBUG +extern int vty_assert_fail ; + Inline void VTY_ASSERT_FAILED(void) { diff --git a/lib/vty_pipe.c b/lib/vty_pipe.c index d4f1c9ba..3276085c 100644 --- a/lib/vty_pipe.c +++ b/lib/vty_pipe.c @@ -24,12 +24,11 @@ #include <zebra.h> #include "vty.h" -#include "uty.h" #include "vty_cli.h" +#include "vty_command.h" #include "vty_io.h" #include "vio_lines.h" -#include "command.h" #include "command_execute.h" #include "command_queue.h" @@ -194,7 +193,7 @@ * */ - +#if 0 @@ -684,7 +683,7 @@ uty_cli_dispatch(vty_io vio) cli_do = vio->cli_do ; /* current operation */ vio->cli_do = cli_do_nothing ; /* clear */ - qs_clear(&vio->cl) ; /* set cl empty (with '\0') */ + qs_clear(vio->cl) ; /* set cl empty (with '\0') */ /* Reset the command output FIFO and line_control */ assert(vio_fifo_empty(&vio->cmd_obuf)) ; @@ -706,7 +705,7 @@ uty_cli_dispatch(vty_io vio) break ; case cli_do_command: - ret = uty_command(vty) ; + ret = uty_dispatch_command(vty) ; break ; case cli_do_ctrl_c: @@ -1401,7 +1400,7 @@ uty_cli_draw_this(vty_io vio, enum node_type node) prompt = "%s ???: " ; } ; - qs_printf(&vio->cli_prompt_for_node, prompt, vty_host_name); + qs_printf(vio->cli_prompt_for_node, prompt, vty_host_name); vio->cli_prompt_node = node ; vio->cli_prompt_set = 1 ; @@ -1778,12 +1777,12 @@ uty_cli_insert (vty_io vio, const char* chars, int n) if (n <= 0) return n ; /* avoid trouble */ - after = qs_insert(&vio->cl, chars, n) ; + after = qs_insert(vio->cl, chars, n) ; - uty_cli_echo(vio, qs_cp_char(&vio->cl), after + n) ; + uty_cli_echo(vio, qs_cp_char(vio->cl), after) ; - if (after != 0) - uty_cli_echo_n(vio, telnet_backspaces, after) ; + if ((after - n) != 0) + uty_cli_echo_n(vio, telnet_backspaces, after - n) ; vio->cl.cp += n ; @@ -1806,7 +1805,7 @@ uty_cli_overwrite (vty_io vio, char* chars, int n) if (n > 0) { - qs_replace(&vio->cl, chars, n) ; + qs_replace(vio->cl, n, chars, n) ; uty_cli_echo(vio, chars, n) ; vio->cl.cp += n ; @@ -1854,7 +1853,7 @@ uty_cli_forwards(vty_io vio, int n) if (n > 0) { - uty_cli_echo(vio, qs_cp_char(&vio->cl), n) ; + uty_cli_echo(vio, qs_cp_char(vio->cl), n) ; vio->cl.cp += n ; } ; @@ -1907,10 +1906,10 @@ uty_cli_del_forwards(vty_io vio, int n) if (n <= 0) return 0 ; - after = qs_delete(&vio->cl, n) ; + after = qs_delete(vio->cl, n) ; if (after > 0) - uty_cli_echo(vio, qs_cp_char(&vio->cl), after) ; + uty_cli_echo(vio, qs_cp_char(vio->cl), after) ; uty_cli_echo_n(vio, telnet_spaces, n) ; uty_cli_echo_n(vio, telnet_backspaces, after + n) ; @@ -1969,8 +1968,8 @@ uty_cli_word_forwards_delta(vty_io vio) assert(vio->cl.cp <= vio->cl.len) ; - cp = qs_cp_char(&vio->cl) ; - ep = qs_ep_char(&vio->cl) ; + cp = qs_cp_char(vio->cl) ; + ep = qs_ep_char(vio->cl) ; tp = cp ; @@ -2013,8 +2012,8 @@ uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) assert(vio->cl.cp <= vio->cl.len) ; - cp = qs_cp_char(&vio->cl) ; - sp = qs_chars(&vio->cl) ; + cp = qs_cp_char(vio->cl) ; + sp = qs_chars(vio->cl) ; tp = cp ; @@ -2128,7 +2127,7 @@ uty_cli_transpose_chars(vty_io vio) uty_cli_backwards(vio, 1) ; /* Pick up in the new order */ - cp = qs_cp_char(&vio->cl) ; + cp = qs_cp_char(vio->cl) ; chars[1] = *cp++ ; chars[0] = *cp ; @@ -2176,13 +2175,13 @@ uty_cli_hist_add (vty_io vio, const char* cmd_line) * Either way, replace the the previous line entry by moving hindex * back ! */ - if ((prev_line == NULL) || (qs_cmp_sig(prev_line, &line) == 0)) + if ((prev_line == NULL) || (qs_cmp_sig(prev_line, line) == 0)) vio->hindex = prev_index ; else prev_line = vector_get_item(vio->hist, vio->hindex) ; /* Now replace the hindex entry */ - vector_set_item(vio->hist, vio->hindex, qs_copy(prev_line, &line)) ; + vector_set_item(vio->hist, vio->hindex, qs_copy(prev_line, line)) ; /* Advance to the near future and reset the history pointer */ vio->hindex++; @@ -2225,7 +2224,7 @@ uty_cli_history_use(vty_io vio, int step) * current command line -- so can get back to it. */ hist = vector_get_item(&vio->hist, vio->hindex) ; - vector_set_item(vio->hist, vio->hindex, qs_copy(hist, &vio->cl)) ; + vector_set_item(vio->hist, vio->hindex, qs_copy(hist, vio->cl)) ; } ; /* Advance or retreat */ @@ -2255,7 +2254,7 @@ uty_cli_history_use(vty_io vio, int step) /* Get previous line from history buffer and echo that */ old_len = vio->cl.len ; - qs_copy(&vio->cl, hist) ; + qs_copy(vio->cl, hist) ; /* Sort out wiping out any excess and setting the cursor position */ if (old_len > vio->cl.len) @@ -2448,7 +2447,7 @@ static void uty_cli_describe_show(vty_io vio, vector describe) { unsigned int i, cmd_width, desc_width; - struct desc *desc, *desc_cr ; + struct desc *desc, *cr_item ; /* Get width of the longest "word" */ cmd_width = 0; @@ -2472,7 +2471,7 @@ uty_cli_describe_show(vty_io vio, vector describe) desc_width = vio->width - (cmd_width + 6); /* Print out description. */ - desc_cr = NULL ; /* put <cr> last if it appears */ + cr_item = NULL ; /* put <cr> last if it appears */ for (i = 0; i < vector_active (describe); i++) if ((desc = vector_slot (describe, i)) != NULL) @@ -2480,17 +2479,17 @@ uty_cli_describe_show(vty_io vio, vector describe) if (desc->cmd[0] == '\0') continue; - if (strcmp (desc->cmd, command_cr) == 0) + if (strcmp (desc->cmd, cr_string) == 0) { - desc_cr = desc; + cr_item = desc; continue; } uty_cli_describe_fold (vio, cmd_width, desc_width, desc); } - if (desc_cr != NULL) - uty_cli_describe_fold (vio, cmd_width, desc_width, desc_cr); + if (cr_item != NULL) + uty_cli_describe_fold (vio, cmd_width, desc_width, cr_item); } ; /*------------------------------------------------------------------------------ @@ -2865,3 +2864,5 @@ uty_telnet_command(vty_io vio, keystroke stroke, bool callback) return dealt_with ; } ; + +#endif diff --git a/lib/workqueue.h b/lib/workqueue.h index 34e68cd8..d9f32a04 100644 --- a/lib/workqueue.h +++ b/lib/workqueue.h @@ -161,7 +161,7 @@ extern void work_queue_unplug (struct work_queue *wq); /* Helpers, exported for thread.c and command.c */ extern int work_queue_run (struct thread *); -extern struct cmd_element show_work_queues_cmd; +extern struct cmd_command show_work_queues_cmd; /*============================================================================== * The Inline functions diff --git a/lib/zebra.h b/lib/zebra.h index 799cfc3d..52e5d571 100644 --- a/lib/zebra.h +++ b/lib/zebra.h @@ -21,9 +21,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA #ifndef _ZEBRA_H #define _ZEBRA_H -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif /* HAVE_CONFIG_H */ +#include "zconfig.h" #ifdef SUNOS_5 #define _XPG4_2 |