diff options
author | Chris Hall <chris.hall@highwayman.com> | 2011-03-21 01:16:05 +0000 |
---|---|---|
committer | Chris Hall <chris.hall@highwayman.com> | 2011-03-21 01:16:05 +0000 |
commit | 9470cb2c32eab220f796b1438b787528272cbe84 (patch) | |
tree | b9b2cc12446173436d2bc7a32e82cc3378ec721e | |
parent | 5cae7eea451f2b7d65b5892e2c1dafc70f8b836e (diff) | |
download | quagga-ex11p.tar.bz2 quagga-ex11p.tar.xz |
Upgrade of "pipework" -- including piping to/from shell commandsex11p
Version 0.99.15ex11p
A major overhaul.
89 files changed, 14860 insertions, 9354 deletions
diff --git a/bgpd/bgp_connection.c b/bgpd/bgp_connection.c index 151d3309..560bac4d 100644 --- a/bgpd/bgp_connection.c +++ b/bgpd/bgp_connection.c @@ -155,7 +155,7 @@ bgp_connection_init_new(bgp_connection connection, bgp_session session, /* Link back to session, point at its mutex and point session here */ connection->session = session ; - connection->p_mutex = &session->mutex ; + connection->p_mutex = session->mutex ; connection->lock_count = 0 ; /* no question about it */ connection->paf = AF_UNSPEC ; diff --git a/bgpd/bgp_dump.c b/bgpd/bgp_dump.c index 3bc318a5..112cfd34 100644 --- a/bgpd/bgp_dump.c +++ b/bgpd/bgp_dump.c @@ -21,6 +21,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA #include <zebra.h> #include "log.h" +#include "vty.h" #include "stream.h" #include "sockunion.h" #include "command.h" @@ -28,6 +29,8 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA #include "thread.h" #include "linklist.h" #include "bgpd/bgp_table.h" +#include "qpath.h" +#include "qstring.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_route.h" @@ -97,8 +100,8 @@ bgp_dump_open_file (struct bgp_dump *bgp_dump) int ret; time_t clock; struct tm tm; - char fullpath[MAXPATHLEN]; - char realpath[MAXPATHLEN]; + qpath path ; + qstring name ; mode_t oldumask; time (&clock); @@ -106,11 +109,17 @@ bgp_dump_open_file (struct bgp_dump *bgp_dump) if (bgp_dump->filename[0] != DIRECTORY_SEP) { - sprintf (fullpath, "%s/%s", vty_get_cwd (), bgp_dump->filename); - ret = strftime (realpath, MAXPATHLEN, fullpath, &tm); + path = vty_getcwd(NULL) ; + qpath_append_str(path, bgp_dump->filename) ; } else - ret = strftime (realpath, MAXPATHLEN, bgp_dump->filename, &tm); + path = qpath_set(NULL, bgp_dump->filename) ; + + name = qs_new_size(NULL, qpath_len(path) + 60) ; + + ret = strftime (qs_char_nn(name), qs_len_nn(name), qpath_string(path), &tm); + + qpath_free(path) ; if (ret == 0) { @@ -123,11 +132,12 @@ bgp_dump_open_file (struct bgp_dump *bgp_dump) oldumask = umask(0777 & ~LOGFILE_MASK); - bgp_dump->fp = fopen (realpath, "w"); + bgp_dump->fp = fopen (qs_char_nn(name), "w"); if (bgp_dump->fp == NULL) { - zlog_warn("bgp_dump_open_file: %s: %s", realpath, errtoa(errno, 0).str); + zlog_warn("bgp_dump_open_file: %s: %s", qs_char_nn(name), + errtoa(errno, 0).str); umask(oldumask); return NULL; } diff --git a/bgpd/bgp_engine.h b/bgpd/bgp_engine.h index bedfc18e..821d8127 100644 --- a/bgpd/bgp_engine.h +++ b/bgpd/bgp_engine.h @@ -31,19 +31,31 @@ #include "lib/log.h" /*============================================================================== - * DEBUG setting + * BGP_ENGINE_DEBUG setting + * + * Set to 1 if defined, but blank. + * Set to QDEBUG if not defined. + * + * Force to 0 if BGP_ENGINE_NO_DEBUG is defined and not zero. + * + * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to: + * + * * can set BGP_ENGINE_DEBUG == 0 to turn off debug + * * or set BGP_ENGINE_DEBUG != 0 to turn on debug + * * or set BGP_ENGINE_NO_DEBUG != to force debug off */ - -#ifdef BGP_ENGINE_DEBUG /* Can be forced from outside */ -# if BGP_ENGINE_DEBUG -# define BGP_ENGINE_DEBUG 1 /* Force 1 or 0 */ -#else -# define BGP_ENGINE_DEBUG 0 +#ifdef BGP_ENGINE_DEBUG /* If defined, make it 1 or 0 */ +# if IS_BLANK_OPTION(BGP_ENGINE_DEBUG) +# undef BGP_ENGINE_DEBUG +# define BGP_ENGINE_DEBUG 1 # endif -#else -# ifdef QDEBUG -# define BGP_ENGINE_DEBUG 1 /* Follow QDEBUG */ -#else +#else /* If not defined, follow QDEBUG */ +# define BGP_ENGINE_DEBUG QDEBUG +#endif + +#ifdef BGP_ENGINE_NO_DEBUG /* Override, if defined */ +# if IS_NOT_ZERO_OPTION(BGP_ENGINE_NO_DEBUG) +# undef BGP_ENGINE_DEBUG # define BGP_ENGINE_DEBUG 0 # endif #endif @@ -80,7 +92,7 @@ bgp_queue_logging(const char* name, mqueue_queue mq, struct queue_stats* stats) ++stats->count ; - qpt_mutex_lock(&mq->mutex) ; + qpt_mutex_lock(mq->mutex) ; if (mq->count > stats->max) stats->max = mq->count ; @@ -91,7 +103,7 @@ bgp_queue_logging(const char* name, mqueue_queue mq, struct queue_stats* stats) if (stats->count < 1000) { - qpt_mutex_unlock(&mq->mutex) ; + qpt_mutex_unlock(mq->mutex) ; return ; } ; @@ -106,7 +118,7 @@ bgp_queue_logging(const char* name, mqueue_queue mq, struct queue_stats* stats) assert(my_count == mq->count) ; - qpt_mutex_unlock(&mq->mutex) ; + qpt_mutex_unlock(mq->mutex) ; average = stats->total * 1000 ; average = (average / stats->count) + 5 ; diff --git a/bgpd/bgp_main.c b/bgpd/bgp_main.c index 6bbc8197..82db783a 100644 --- a/bgpd/bgp_main.c +++ b/bgpd/bgp_main.c @@ -18,15 +18,15 @@ 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 <stdbool.h> +#include "zebra.h" +#include "misc.h" #include "vector.h" #include "vty.h" #include "command.h" #include "getopt.h" #include "thread.h" -#include <lib/version.h> +#include "lib/version.h" #include "memory.h" #include "prefix.h" #include "log.h" @@ -118,13 +118,13 @@ static zebra_capabilities_t _caps_p [] = struct zebra_privs_t bgpd_privs = { #if defined(QUAGGA_USER) && defined(QUAGGA_GROUP) - .user = QUAGGA_USER, - .group = QUAGGA_GROUP, + .user = QUAGGA_USER, + .group = QUAGGA_GROUP, #endif #ifdef VTY_GROUP .vty_group = VTY_GROUP, #endif - .caps_p = _caps_p, + .caps_p = _caps_p, .cap_num_p = sizeof(_caps_p)/sizeof(_caps_p[0]), .cap_num_i = 0, }; @@ -184,7 +184,7 @@ void sigusr2 (void); /* prototypes */ static void bgp_exit (int); -static void init_second_stage(int pthreads); +static void init_second_stage(bool pthreads); static void bgp_in_thread_init(void); static void routing_start(void) ; static void routing_finish(void) ; @@ -206,10 +206,6 @@ static struct quagga_signal_t bgp_signals[] = .handler = &sigusr1, }, { - .signal = SIGUSR2, - .handler = &sigusr2, - }, - { .signal = SIGINT, .handler = &sigint, }, @@ -261,17 +257,6 @@ sigusr1 (void) zlog_rotate (NULL); } -/* SIGUSR2 handler. */ -void -sigusr2 (void) -{ - /* Used to signal message queues */ - if (qpthreads_enabled) - return; - else - exit(1); -} - /*------------------------------------------------------------------------------ * Final exit code... * @@ -392,8 +377,8 @@ bgp_exit (int status) * * 1. if it's there, invoke the command in the usual way * - * 2. if it's not there, invoke the command but with a NULL set of arguments, - * which signals the "default" nature of the call. + * 2. if it's not there, invoke the command but with a *negative* count of + * arguments, which signals the "default" nature of the call. * * This mechanism is used so that the "threaded_cmd" is the time at which * second stage initialisation is done. (But only once -- not on rereading @@ -408,8 +393,8 @@ DEFUN_HID_CALL (threaded, "threaded", "Use pthreads\n") { - if (argv != NULL) - config_threaded = 1 ; /* Explicit command => turn on threading */ + if (argc == 0) + config_threaded = true ; /* Explicit command => turn on threading */ if (!done_2nd_stage_init) init_second_stage(config_threaded) ; @@ -425,7 +410,7 @@ DEFUN_HID_CALL (threaded, * the message queues are available for the configuration data. */ static void -init_second_stage(int pthreads) +init_second_stage(bool pthreads) { assert(!done_2nd_stage_init) ; @@ -435,13 +420,13 @@ init_second_stage(int pthreads) bgp_peer_index_mutex_init(); /* Make nexus for main thread, always needed */ - cli_nexus = qpn_init_new(cli_nexus, 1); /* main thread */ + cli_nexus = qpn_init_new(cli_nexus, true); /* main thread */ /* if using pthreads create additional nexus */ if (qpthreads_enabled) { - bgp_nexus = qpn_init_new(bgp_nexus, 0); - routing_nexus = qpn_init_new(routing_nexus, 0); + bgp_nexus = qpn_init_new(bgp_nexus, false); + routing_nexus = qpn_init_new(routing_nexus, false); } else { @@ -499,9 +484,8 @@ main (int argc, char **argv) /* Set umask before anything for security */ umask (0027); -#ifdef QDEBUG - fprintf(stderr, "%s\n", debug_banner); -#endif + if (qdebug) + fprintf(stderr, "%s\n", debug_banner); qlib_init_first_stage(); @@ -603,7 +587,11 @@ main (int argc, char **argv) /* Initializations. */ srand (time (NULL)); signal_init (master, Q_SIGC(bgp_signals), bgp_signals); - zprivs_init (&bgpd_privs); + + cmd_getcwd() ; /* while have privilege */ + + zprivs_init (&bgpd_privs); /* lowers privileges */ + cmd_init (1); install_element (CONFIG_NODE, &threaded_cmd); vty_init (master); @@ -652,9 +640,9 @@ main (int argc, char **argv) vty_start(vty_addr, vty_port, BGP_VTYSH_PATH); /* Print banner. */ -#ifdef QDEBUG - zlog_notice("%s", debug_banner); -#endif + if (qdebug) + zlog_notice("%s", debug_banner); + zlog_notice ("BGPd %s%s starting: vty@%d, bgp@%s:%d", QUAGGA_VERSION, (qpthreads_enabled ? " pthreaded" : ""), diff --git a/bgpd/bgp_session.c b/bgpd/bgp_session.c index 681075da..0c021b0f 100644 --- a/bgpd/bgp_session.c +++ b/bgpd/bgp_session.c @@ -124,7 +124,7 @@ bgp_session_init_new(bgp_peer peer) session = XCALLOC(MTYPE_BGP_SESSION, sizeof(struct bgp_session)) ; - qpt_mutex_init_new(&session->mutex, qpt_mutex_recursive) ; + qpt_mutex_init_new(session->mutex, qpt_mutex_recursive) ; session->peer = peer ; bgp_peer_lock(peer) ; /* Account for the session->peer pointer */ @@ -240,7 +240,7 @@ bgp_session_delete(bgp_peer peer) BGP_SESSION_UNLOCK(session) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ - qpt_mutex_destroy(&session->mutex, 0) ; + qpt_mutex_destroy(session->mutex, 0) ; /* Proceed to dismantle the session. */ diff --git a/bgpd/bgp_session.h b/bgpd/bgp_session.h index e1a4c51b..cc8acc19 100644 --- a/bgpd/bgp_session.h +++ b/bgpd/bgp_session.h @@ -304,12 +304,12 @@ MQB_ARGS_SIZE_OK(bgp_session_ttl_args) ; inline static void BGP_SESSION_LOCK(bgp_session session) { - qpt_mutex_lock(&session->mutex) ; + qpt_mutex_lock(session->mutex) ; } ; inline static void BGP_SESSION_UNLOCK(bgp_session session) { - qpt_mutex_unlock(&session->mutex) ; + qpt_mutex_unlock(session->mutex) ; } ; /*============================================================================== diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c index 9195b9f3..dc72f6e7 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c @@ -314,12 +314,13 @@ DEFUN_DEPRECATED (neighbor_version, } /* "router bgp" commands. */ -DEFUN (router_bgp, - router_bgp_cmd, - "router bgp " CMD_AS_RANGE, - ROUTER_STR - BGP_STR - AS_STR) +DEFUN_ATTR (router_bgp, + router_bgp_cmd, + "router bgp " CMD_AS_RANGE, + ROUTER_STR + BGP_STR + AS_STR, + CMD_ATTR_NODE + BGP_NODE) { int ret; as_t as; @@ -338,9 +339,11 @@ DEFUN (router_bgp, vty_out (vty, "Please specify 'bgp multiple-instance' first%s", VTY_NEWLINE); return CMD_WARNING; + case BGP_ERR_AS_MISMATCH: vty_out (vty, "BGP is already running; AS is %u%s", as, VTY_NEWLINE); return CMD_WARNING; + case BGP_ERR_INSTANCE_MISMATCH: vty_out (vty, "BGP view name and AS number mismatch%s", VTY_NEWLINE); vty_out (vty, "BGP instance is already running; AS is %u%s", @@ -354,14 +357,15 @@ DEFUN (router_bgp, return CMD_SUCCESS; } -ALIAS (router_bgp, - router_bgp_view_cmd, - "router bgp " CMD_AS_RANGE " view WORD", - ROUTER_STR - BGP_STR - AS_STR - "BGP view\n" - "view name\n") +ALIAS_ATTR (router_bgp, + router_bgp_view_cmd, + "router bgp " CMD_AS_RANGE " view WORD", + ROUTER_STR + BGP_STR + AS_STR + "BGP view\n" + "view name\n", + CMD_ATTR_NODE + BGP_NODE) /* "no router bgp" commands. */ DEFUN (no_router_bgp, @@ -4008,90 +4012,118 @@ DEFUN (no_neighbor_allowas_in, } /* Address family configuration. */ -DEFUN (address_family_ipv4, - address_family_ipv4_cmd, - "address-family ipv4", - "Enter Address Family command mode\n" - "Address family\n") +DEFUN_ATTR (address_family_ipv4, + address_family_ipv4_cmd, + "address-family ipv4", + "Enter Address Family command mode\n" + "Address family\n", + CMD_ATTR_NODE + BGP_IPV4_NODE) { vty->node = BGP_IPV4_NODE ; return CMD_SUCCESS; } -DEFUN (address_family_ipv4_safi, - address_family_ipv4_safi_cmd, - "address-family ipv4 (unicast|multicast)", - "Enter Address Family command mode\n" - "Address family\n" - "Address Family modifier\n" - "Address Family modifier\n") +DEFUN_ATTR (address_family_ipv4_safi_unicast, + address_family_ipv4_safi_unicast_cmd, + "address-family ipv4 unicast", + "Enter Address Family command mode\n" + "Address family\n" + "Address Family modifier\n", + CMD_ATTR_NODE + BGP_IPV4_NODE) { - if (strncmp (argv[0], "m", 1) == 0) - vty->node = BGP_IPV4M_NODE ; - else - vty->node = BGP_IPV4_NODE ; + vty->node = BGP_IPV4_NODE ; + return CMD_SUCCESS; +} +DEFUN_ATTR (address_family_ipv4_safi_multicast, + address_family_ipv4_safi_multicast_cmd, + "address-family ipv4 multicast", + "Enter Address Family command mode\n" + "Address family\n" + "Address Family modifier\n", + CMD_ATTR_NODE + BGP_IPV4M_NODE) +{ + vty->node = BGP_IPV4M_NODE ; return CMD_SUCCESS; } -DEFUN (address_family_ipv6, - address_family_ipv6_cmd, - "address-family ipv6", - "Enter Address Family command mode\n" - "Address family\n") +DEFUN_ATTR (address_family_ipv6, + address_family_ipv6_cmd, + "address-family ipv6", + "Enter Address Family command mode\n" + "Address family\n", + CMD_ATTR_NODE + BGP_IPV6_NODE) { vty->node = BGP_IPV6_NODE ; return CMD_SUCCESS; } -DEFUN (address_family_ipv6_safi, - address_family_ipv6_safi_cmd, - "address-family ipv6 (unicast|multicast)", - "Enter Address Family command mode\n" - "Address family\n" - "Address Family modifier\n" - "Address Family modifier\n") +DEFUN_ATTR (address_family_ipv6_safi_unicast, + address_family_ipv6_safi_unicast_cmd, + "address-family ipv6 unicast", + "Enter Address Family command mode\n" + "Address family\n" + "Address Family modifier\n", + CMD_ATTR_NODE + BGP_IPV6_NODE) { - if (strncmp (argv[0], "m", 1) == 0) - vty->node = BGP_IPV6M_NODE ; - else - vty->node = BGP_IPV6_NODE ; + vty->node = BGP_IPV6_NODE ; + return CMD_SUCCESS; +} +DEFUN_ATTR (address_family_ipv6_safi_multicast, + address_family_ipv6_safi_multicast_cmd, + "address-family ipv6 multicast", + "Enter Address Family command mode\n" + "Address family\n" + "Address Family modifier\n", + CMD_ATTR_NODE + BGP_IPV6M_NODE) +{ + vty->node = BGP_IPV6M_NODE ; return CMD_SUCCESS; } -DEFUN (address_family_vpnv4, - address_family_vpnv4_cmd, - "address-family vpnv4", - "Enter Address Family command mode\n" - "Address family\n") +DEFUN_ATTR (address_family_vpnv4, + address_family_vpnv4_cmd, + "address-family vpnv4", + "Enter Address Family command mode\n" + "Address family\n", + CMD_ATTR_NODE + BGP_VPNV4_NODE) { vty->node = BGP_VPNV4_NODE ; return CMD_SUCCESS; } -ALIAS (address_family_vpnv4, +ALIAS_ATTR (address_family_vpnv4, address_family_vpnv4_unicast_cmd, "address-family vpnv4 unicast", "Enter Address Family command mode\n" "Address family\n" - "Address Family Modifier\n") + "Address Family Modifier\n", + CMD_ATTR_NODE + BGP_VPNV4_NODE) -DEFUN (exit_address_family, - exit_address_family_cmd, - "exit-address-family", - "Exit from Address Family configuration mode\n") +DEFUN_ATTR (exit_address_family, + exit_address_family_cmd, + "exit-address-family", + "Exit from Address Family configuration mode\n", + CMD_ATTR_NODE + BGP_NODE) { node_type_t node = vty->node ; - if (node == BGP_IPV4_NODE + if ( node == BGP_IPV4_NODE || node == BGP_IPV4M_NODE || node == BGP_VPNV4_NODE || node == BGP_IPV6_NODE || node == BGP_IPV6M_NODE) - vty->node = BGP_NODE ; - return CMD_SUCCESS; -} + { + vty->node = BGP_NODE ; + return CMD_SUCCESS ; + } + else + { + vty_out(vty, "%% No address family to leave\n") ; + return CMD_WARNING ; + } ; +} ; /* BGP clear sort. */ enum clear_sort @@ -9626,10 +9658,12 @@ bgp_vty_init (void) /* address-family commands. */ install_element (BGP_NODE, &address_family_ipv4_cmd); - install_element (BGP_NODE, &address_family_ipv4_safi_cmd); + install_element (BGP_NODE, &address_family_ipv4_safi_unicast_cmd); + install_element (BGP_NODE, &address_family_ipv4_safi_multicast_cmd); #ifdef HAVE_IPV6 install_element (BGP_NODE, &address_family_ipv6_cmd); - install_element (BGP_NODE, &address_family_ipv6_safi_cmd); + install_element (BGP_NODE, &address_family_ipv6_safi_unicast_cmd); + install_element (BGP_NODE, &address_family_ipv6_safi_multicast_cmd); #endif /* HAVE_IPV6 */ install_element (BGP_NODE, &address_family_vpnv4_cmd); install_element (BGP_NODE, &address_family_vpnv4_unicast_cmd); diff --git a/configure.ac b/configure.ac index 4f100d94..462ee76d 100755 --- a/configure.ac +++ b/configure.ac @@ -8,7 +8,7 @@ ## $Id$ AC_PREREQ(2.53) -AC_INIT(Quagga, 0.99.15ex10p, [http://bugzilla.quagga.net]) +AC_INIT(Quagga, 0.99.15ex11p, [http://bugzilla.quagga.net]) AC_CONFIG_SRCDIR(lib/zebra.h) AC_CONFIG_MACRO_DIR([m4]) diff --git a/lib/Makefile.am b/lib/Makefile.am index cc8fbd3a..2d41399c 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -4,22 +4,24 @@ INCLUDES = @INCLUDES@ -I.. -I$(top_srcdir) -I$(top_srcdir)/lib @SNMP_INCLUDES@ DEFS = @DEFS@ -DSYSCONFDIR=\"$(sysconfdir)/\" lib_LTLIBRARIES = libzebra.la -libzebra_la_LDFLAGS = -version-info 0:0:0 +libzebra_la_LDFLAGS = -version-info 0:0:0 libzebra_la_SOURCES = \ - network.c pid_output.c getopt.c getopt1.c daemon.c \ - checksum.c vector.c linklist.c vty.c command.c \ - sockunion.c prefix.c thread.c if.c memory.c buffer.c table.c hash.c \ - filter.c routemap.c distribute.c stream.c str.c log.c plist.c \ - 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_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 - + buffer.c checksum.c command.c command_execute.c \ + command_parse.c command_queue.c daemon.c distribute.c \ + elstring.c errno_names.c filter.c getopt.c getopt1.c \ + hash.c heap.c if.c if_rmap.c jhash.c keychain.c keystroke.c \ + linklist.c list_util.c log.c md5.c memory.c memtypes.c mqueue.c \ + network.c pid_output.c plist.c pqueue.c prefix.c privs.c \ + pthread_safe.c qfstring.c qiovec.c qlib_init.c qpath.c \ + qpnexus.c qpselect.c qpthreads.c qrand.c qstring.c \ + qtime.c qtimers.c routemap.c sigevent.c smux.c \ + sockopt.c sockunion.c str.c stream.c symtab.c \ + table.c thread.c vector.c vio_fifo.c vio_lines.c \ + vty.c vty_cli.c vty_command.c vty_io.c vty_io_basic.c \ + vty_io_file.c vty_io_shell.c vty_io_term.c vty_io_vsh.c \ + vty_log.c workqueue.c zclient.c + BUILT_SOURCES = memtypes.h route_types.h libzebra_la_DEPENDENCIES = @LIB_REGEX@ @@ -27,22 +29,24 @@ libzebra_la_DEPENDENCIES = @LIB_REGEX@ libzebra_la_LIBADD = @LIB_REGEX@ pkginclude_HEADERS = \ - 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 \ - 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 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 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 - + command.h command_common.h command_execute.h \ + command_local.h command_parse.h command_queue.h confirm.h \ + distribute.h elstring.h errno_names.h filter.h getopt.h \ + hash.h heap.h if.h if_rmap.h jhash.h keychain.h \ + keystroke.h linklist.h list_util.h log.h log_local.h \ + md5.h memory.h memtypes.h misc.h miyagi.h mqueue.h \ + network.h plist.h pqueue.h prefix.h privs.h \ + pthread_safe.h qafi_safi.h qdebug_nb.h qfstring.h qiovec.h \ + qlib_init.h qpath.h qpnexus.h qpselect.h qpthreads.h \ + qrand.h qstring.h qtime.h qtimers.h route_types.h \ + routemap.h sigevent.h smux.h sockopt.h sockunion.h str.h \ + stream.h symtab.h table.h thread.h tstring.h vargs.h \ + vector.h version.h vio_fifo.h vio_lines.h vty.h vty_cli.h \ + vty_command.h vty_common.h vty_io.h vty_io_basic.h \ + vty_io_file.h vty_io_shell.h vty_io_term.h vty_io_vsh.h \ + vty_local.h vty_log.h workqueue.h zassert.h zclient.h \ + zconfig.h zebra.h + EXTRA_DIST = regex.c regex-gnu.h memtypes.awk route_types.awk route_types.txt memtypes.h: $(srcdir)/memtypes.c $(srcdir)/memtypes.awk diff --git a/lib/command.c b/lib/command.c index adea80e4..61f554bb 100644 --- a/lib/command.c +++ b/lib/command.c @@ -46,6 +46,7 @@ Boston, MA 02111-1307, USA. */ #include "vty_local.h" #include "vty_command.h" #include "vty_io.h" +#include "network.h" /* Vector of cmd_node, one for each known node, built during daemon * initialisation. @@ -57,52 +58,57 @@ vector node_vector = NULL ; /*============================================================================== * Default motd string. */ -#define DEFAULT_MOTD \ -"\n" \ -"Hello, this is " QUAGGA_PROGNAME " (version " QUAGGA_VERSION ")\n" \ - QUAGGA_COPYRIGHT "\n" \ +const char* 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 +const char* debug_banner = + QUAGGA_PROGNAME " version " QUAGGA_VERSION " QDEBUG=" QDEBUG_NAME " " + __DATE__ " " __TIME__ ; /*============================================================================== * Host information structure -- shared across command/vty + * + * Must have VTY_LOCK() or not be running multiple pthreads to access this ! */ struct host host = { /* Host name of this router. */ - .name_set = false, - .name = NULL, /* set by cmd_init */ - .name_gen = 0, /* set by cmd_init */ + .name = NULL, /* set by cmd_init */ + .name_set = false, + .name_gen = 0, /* set by cmd_init */ /* Password for vty interface. */ - .password = NULL, + .password = NULL, .password_encrypted = false, /* Enable password */ - .enable = NULL, - .enable_encrypted = false, + .enable = NULL, + .enable_encrypted = false, /* System wide terminal lines. */ - .lines = -1, /* unset */ + .lines = -1, /* unset */ /* Log filename. */ - .logfile = NULL, + .logfile = NULL, /* config file name of this host */ - .config_file = NULL, + .config_file = NULL, + .config_dir = NULL, /* Flags for services */ - .advanced = false, - .encrypt = false, + .advanced = false, + .encrypt = false, /* Banner configuration. */ - .motd = DEFAULT_MOTD, - .motdfile = NULL, + .motd = NULL, + .motdfile = NULL, + + /* Nobody has the config symbol of power */ + .config = false, + .config_brand = 0, /* allow VTY to start without password */ .no_password_check = false, @@ -119,8 +125,8 @@ struct host host = /* Vty access-class for IPv6. */ .vty_ipv6_accesslist_name = NULL, - /* Current directory -- initialised in vty_init() */ - .vty_cwd = NULL, + /* Current directory -- initialised in cmd_getcwd() */ + .cwd = NULL, } ; /*============================================================================== @@ -220,6 +226,8 @@ cmd_get_sys_host_name(void) static void cmd_new_host_name(const char* name) { + VTY_ASSERT_LOCKED() ; + if ((host.name == NULL) || (strcmp(host.name, name) != 0)) { XFREE(MTYPE_HOST, host.name) ; @@ -239,7 +247,7 @@ static struct cmd_node auth_node = .prompt = "Password: ", .parent = AUTH_NODE, /* self => no parent */ - .exit_to = AUTH_NODE, /* self => no exit */ + .exit_to = NULL_NODE, /* close ! */ .end_to = AUTH_NODE, /* self => no end */ }; @@ -248,7 +256,7 @@ static struct cmd_node view_node = .node = VIEW_NODE, .prompt = "%s> ", - .parent = AUTH_NODE, /* self => no parent */ + .parent = VIEW_NODE, /* self => no parent */ .exit_to = NULL_NODE, /* close ! */ .end_to = VIEW_NODE, /* self => no end */ }; @@ -258,7 +266,7 @@ static struct cmd_node restricted_node = .node = RESTRICTED_NODE, .prompt = "%s$ ", - .parent = AUTH_NODE, /* self => no parent */ + .parent = RESTRICTED_NODE, /* self => no parent */ .exit_to = NULL_NODE, /* close ! */ .end_to = RESTRICTED_NODE, /* self => no end */ }; @@ -269,7 +277,7 @@ static struct cmd_node auth_enable_node = .prompt = "Password: ", .parent = AUTH_ENABLE_NODE, /* self => no parent */ - .exit_to = NULL_NODE, /* close ! */ + .exit_to = VIEW_NODE, /* fall back */ .end_to = AUTH_ENABLE_NODE, /* self => no end */ }; @@ -288,7 +296,7 @@ static struct cmd_node config_node = .node = CONFIG_NODE, .prompt = "%s(config)# ", - .parent = CONFIG_NODE, /* self => no parent */ + .parent = ENABLE_NODE, /* more or less */ .exit_to = ENABLE_NODE, /* exit == end for CONFIG_NODE */ .end_to = ENABLE_NODE, /* standard end action */ @@ -465,13 +473,14 @@ cmp_desc (const void *p, const void *q) * * Default parent node: * - * * all nodes >= NODE_CONFIG have NODE_CONFIG as a parent - * * all nodes < NODE_CONFIG are their own parents + * * all nodes > NODE_CONFIG have NODE_CONFIG as parent + * * node == NODE_CONFIG has ENABLE_NODE as parent + * * all nodes < NODE_CONFIG are their own parents (including ENABLE_NODE) * * Default exit_to: * * * all nodes > NODE_CONFIG exit_to their parent - * * node == NODE_CONFIG exit_to ENABLE_NODE (same as end_to !) + * * node == NODE_CONFIG exit_to ENABLE_NODE (its parent) * * all nodes < NODE_CONFIG exit_to close * * Default end_to: @@ -488,8 +497,10 @@ install_node (struct cmd_node *node, if (node->parent == NULL_NODE) { - if (node->node >= CONFIG_NODE) + if (node->node > CONFIG_NODE) node->parent = CONFIG_NODE ; + else if (node->node == CONFIG_NODE) + node->parent = ENABLE_NODE ; else node->parent = node->node ; } ; @@ -504,10 +515,8 @@ install_node (struct cmd_node *node, if (node->exit_to == NULL_NODE) { - if (node->node > CONFIG_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 ; } ; @@ -553,7 +562,7 @@ cmd_node_exit_to(node_type_t node) } ; /*------------------------------------------------------------------------------ - * Return parent node + * Return end_to node */ extern node_type_t cmd_node_end_to(node_type_t node) @@ -561,8 +570,6 @@ cmd_node_end_to(node_type_t node) return (cmd_cmd_node(node))->end_to ; } ; - - /*------------------------------------------------------------------------------ * Sorting of all node cmd_vectors. */ @@ -582,15 +589,15 @@ sort_node () for (i = 0; i < vector_length(node_vector); i++) { - struct cmd_node *cnode; + struct cmd_node *cn; vector cmd_vector ; - cnode = vector_get_item(node_vector, i) ; + cn = vector_get_item(node_vector, i) ; - if (cnode == NULL) + if (cn == NULL) continue ; - cmd_vector = cnode->cmd_vector; + cmd_vector = cn->cmd_vector; if (cmd_vector == NULL) continue ; @@ -609,7 +616,7 @@ extern vector cmd_make_strvec (const char *string) { #if 0 - return cmd_tokenise(NULL, string) ; + return cmd_tokenize(NULL, string) ; #error sort this one out #endif return NULL ; @@ -654,22 +661,22 @@ cmd_free_strvec (vector items) extern const char * cmd_prompt(node_type_t node) { - struct cmd_node *cnode; + struct cmd_node *cn ; assert(node_vector != NULL) ; assert(node_vector->p_items != NULL) ; - cnode = NULL ; + cn = NULL ; if (node < node_vector->limit) - cnode = vector_get_item (node_vector, node); + cn = vector_get_item (node_vector, node); - if (cnode == NULL) + if (cn == NULL) { zlog_err("Could not find prompt for node %d for", node) ; return NULL ; } ; - return cnode->prompt; + return cn->prompt; } /*------------------------------------------------------------------------------ @@ -679,18 +686,18 @@ cmd_prompt(node_type_t node) extern void install_element(node_type_t ntype, cmd_command cmd) { - cmd_node cnode; + cmd_node cn ; - cnode = vector_get_item (node_vector, ntype); + cn = vector_get_item (node_vector, ntype); - if (cnode == NULL) + if (cn == NULL) { fprintf (stderr, "Command node %d doesn't exist, please check it\n", ntype); exit (1); } - vector_set (cnode->cmd_vector, cmd); + vector_set (cn->cmd_vector, cmd); /* A cmd_command may appear in a number of cmd_vectors, but the cmd->items * etc. need only be set up once. @@ -736,350 +743,454 @@ zencrypt (const char *passwd) /* This function write configuration of this host. */ static int -config_write_host (struct vty *vty) +config_write_host (vty vty) { + vty_io vio ; + VTY_LOCK() ; + vio = vty->vio ; + if (qpthreads_enabled) - vty_out (vty, "threaded%s", VTY_NEWLINE); + uty_out (vio, "threaded\n"); if (host.name_set) - vty_out (vty, "hostname %s%s", host.name, VTY_NEWLINE); + uty_out (vio, "hostname %s\n", host.name); if (host.password != NULL) { if (host.password_encrypted) - vty_out (vty, "password 8 %s\n", host.password); + uty_out (vio, "password 8 %s\n", host.password); else - vty_out (vty, "password %s\n", host.password); + uty_out (vio, "password %s\n", host.password); } ; if (host.enable != NULL) { if (host.enable_encrypted) - vty_out (vty, "enable password 8 %s\n", host.enable); + uty_out (vio, "enable password 8 %s\n", host.enable); else - vty_out (vty, "enable password %s\n", host.enable); + uty_out (vio, "enable password %s\n", host.enable); } ; if (zlog_get_default_lvl(NULL) != LOG_DEBUG) { - vty_out (vty, "! N.B. The 'log trap' command is deprecated.%s", - VTY_NEWLINE); - vty_out (vty, "log trap %s%s", - zlog_priority[zlog_get_default_lvl(NULL)], VTY_NEWLINE); + uty_out (vio, "! N.B. The 'log trap' command is deprecated.\n"); + uty_out (vio, "log trap %s\n", zlog_priority[zlog_get_default_lvl(NULL)]); } if (host.logfile && (zlog_get_maxlvl(NULL, ZLOG_DEST_FILE) != ZLOG_DISABLED)) { - vty_out (vty, "log file %s", host.logfile); + uty_out (vio, "log file %s", qpath_string(host.logfile)); if (zlog_get_maxlvl(NULL, ZLOG_DEST_FILE) != zlog_get_default_lvl(NULL)) - vty_out (vty, " %s", + uty_out (vio, " %s", zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_FILE)]); - vty_out (vty, "%s", VTY_NEWLINE); + uty_out (vio, "\n"); } if (zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT) != ZLOG_DISABLED) { - vty_out (vty, "log stdout"); + uty_out (vio, "log stdout"); if (zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT) != zlog_get_default_lvl(NULL)) - vty_out (vty, " %s", + uty_out (vio, " %s", zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT)]); - vty_out (vty, "%s", VTY_NEWLINE); + uty_out (vio, "\n"); } if (zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR) == ZLOG_DISABLED) - vty_out(vty,"no log monitor%s",VTY_NEWLINE); + uty_out(vio,"no log monitor%s",VTY_NEWLINE); else if (zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR) != zlog_get_default_lvl(NULL)) - vty_out(vty,"log monitor %s%s", - zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR)],VTY_NEWLINE); + uty_out(vio,"log monitor %s\n", + zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR)]); if (zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG) != ZLOG_DISABLED) { - vty_out (vty, "log syslog"); + uty_out (vio, "log syslog"); if (zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG) != zlog_get_default_lvl(NULL)) - vty_out (vty, " %s", + uty_out (vio, " %s", zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG)]); - vty_out (vty, "%s", VTY_NEWLINE); + uty_out (vio, "\n"); } if (zlog_get_facility(NULL) != LOG_DAEMON) - vty_out (vty, "log facility %s%s", - facility_name(zlog_get_facility(NULL)), VTY_NEWLINE); + uty_out (vio, "log facility %s\n", facility_name(zlog_get_facility(NULL))); if (zlog_get_record_priority(NULL) == 1) - vty_out (vty, "log record-priority%s", VTY_NEWLINE); + uty_out (vio, "log record-priority\n"); if (zlog_get_timestamp_precision(NULL) > 0) - vty_out (vty, "log timestamp precision %d%s", - zlog_get_timestamp_precision(NULL), VTY_NEWLINE); + uty_out (vio, "log timestamp precision %d\n", + zlog_get_timestamp_precision(NULL)); if (host.advanced) - vty_out (vty, "service advanced-vty%s", VTY_NEWLINE); + uty_out (vio, "service advanced-vty\n"); if (host.encrypt) - vty_out (vty, "service password-encryption%s", VTY_NEWLINE); + uty_out (vio, "service password-encryption\n"); if (host.lines >= 0) - vty_out (vty, "service terminal-length %d%s", host.lines, - VTY_NEWLINE); + uty_out (vio, "service terminal-length %d\n", host.lines); if (host.motdfile) - vty_out (vty, "banner motd file %s%s", host.motdfile, VTY_NEWLINE); + uty_out (vio, "banner motd file %s\n", qpath_string(host.motdfile)); else if (! host.motd) - vty_out (vty, "no banner motd%s", VTY_NEWLINE); + uty_out (vio, "no banner motd\n"); VTY_UNLOCK() ; return 1; } -/*============================================================================*/ - -/*----------------------------------------------------------------------------*/ +/*============================================================================== + * Commands and other stuff related to: + * + * * end (and ^Z) -- go to ENABLE_NODE (aka Privileged Exec) if above that, + * otherwise do nothing. + * + * This is installed in all nodes. + * + * * exit -- go to parent node, if in CONFIG_NODE or any of its + * sub-nodes. + * + * Parent of CONFIG_NODE is ENABLE_NODE. + * + * For all other nodes, this is EOF (which for the + * terminal is "close"). + * + * This is installed in all nodes. + * + * * enable -- go to ENABLE_NODE, if can. + * + * This is installed in VIEW_NODE and RESTRICTED_NODE. + * + * It is also installed in ENABLE_NODE (and hence is + * available anywhere), where it is a synonym for 'end' ! + * + * For configuration reading (VTY_CONFIG_READ) and for + * VTY_SHELL_SERVER, no password is required. + * + * For VTY_TERMINAL, must already have authenticated + * once, or must be able to enter AUTH_ENABLE_NODE. + * + * * disable -- go to VIEW_NODE (aka User Exec). + * + * This is installed in ENABLE_NODE *only*. + * + * Note, however, that all ENABLE_NODE commands are + * available at ENABLE_NODE and above ! + */ -/* Configration from terminal */ -DEFUN_CALL (config_terminal, - config_terminal_cmd, - "configure terminal", - "Configuration from vty interface\n" - "Configuration terminal\n") -{ - if (vty_config_lock (vty, CONFIG_NODE)) - return CMD_SUCCESS; +/*------------------------------------------------------------------------------ + * Enter CONFIG_NODE, possibly via password check. + * + * If the parser established that can enter CONFIG_NODE directly, that's what + * happens. + * + * If the parser established that must authenticate, then may fail here if + * we are not in the right state to run the authentication. + * + * The authentication itself may fail... + * + * NB: installed in VIEW_NODE, RESTRICTED_NODE and ENABLE_NODE. + */ +DEFUN_ATTR (config_terminal, + config_terminal_cmd, + "configure terminal", + "Configuration from vty interface\n" + "Configuration terminal\n", + CMD_ATTR_DIRECT + cmd_sp_configure) +{ + if (vty->exec->parsed->nnode == CONFIG_NODE) + return vty_cmd_config_lock(vty) ; ; + + /* Otherwise, must authenticate to enter CONFIG_NODE. */ + return vty_cmd_can_auth_enable(vty) ; +} ; - vty_out (vty, "VTY configuration is locked by other VTY%s", VTY_NEWLINE); - return CMD_WARNING; -} +ALIAS_ATTR (config_terminal, + config_enable_configure_cmd, + "enable configure", + "Turn on privileged mode command\n" + "Configuration terminal\n", + CMD_ATTR_DIRECT + cmd_sp_configure) -/* Enable command */ -DEFUN_CALL (enable, - config_enable_cmd, - "enable", - "Turn on privileged mode command\n") +/*------------------------------------------------------------------------------ + * Enter ENABLE_NODE, possibly via password check. + * + * If the parser established that can enter ENABLE_NODE directly, that's what + * happens. + * + * If the parser established that must authenticate, then may fail here if + * we are not in the right state to run the authentication. + * + * The authentication itself may fail... + * + * NB: installed in VIEW_NODE, RESTRICTED_NODE and ENABLE_NODE. + */ +DEFUN_ATTR (enable, + config_enable_cmd, + "enable", + "Turn on privileged mode command\n", + CMD_ATTR_DIRECT + cmd_sp_enable) { - /* If enable password is NULL, change to ENABLE_NODE */ - if ((host.enable == NULL) || (vty->type == VTY_SHELL_SERVER)) - vty->node = ENABLE_NODE ; - else - vty->node = AUTH_ENABLE_NODE ; + if (vty->exec->parsed->nnode == ENABLE_NODE) + return CMD_SUCCESS ; - return CMD_SUCCESS; -} + /* Otherwise, must authenticate to enter ENABLE_NODE. */ + return vty_cmd_can_auth_enable(vty) ; +} ; -/* Disable command */ -DEFUN_CALL (disable, - config_disable_cmd, - "disable", - "Turn off privileged mode command\n") +/*------------------------------------------------------------------------------ + * disable command: end enabled state -> VIEW_NODE. + * + * NB: although only installed in ENABLE_NODE, it will be implicitly available + * in all higher nodes -- as a quick way of crashing out to VIEW_NODE ! + */ +DEFUN_ATTR(disable, + config_disable_cmd, + "disable", + "Turn off privileged mode command\n", + CMD_ATTR_DIRECT + CMD_ATTR_NODE + VIEW_NODE) { - if (vty->node == ENABLE_NODE) - vty->node = VIEW_NODE; - return CMD_SUCCESS; + return CMD_SUCCESS ; /* will disable to parsed->nnode */ } -/* Down vty node level. */ -DEFUN_CALL (config_exit, - config_exit_cmd, - "exit", - "Exit current mode and down to previous mode\n") +/*------------------------------------------------------------------------------ + * exit command: down one node level, including exit command processor. + */ +DEFUN_ATTR(config_exit, + config_exit_cmd, + "exit", + "Exit current mode and down to previous mode\n", + CMD_ATTR_DIRECT + cmd_sp_exit) { - return cmd_exit(vty) ; + return CMD_SUCCESS ; /* will exit to parsed->nnode */ } -/* quit is alias of exit. */ -ALIAS_CALL (config_exit, - config_quit_cmd, - "quit", - "Exit current mode and down to previous mode\n") +/* quit is alias of exit. */ +ALIAS_ATTR (config_exit, + config_quit_cmd, + "quit", + "Exit current mode and down to previous mode\n", + CMD_ATTR_DIRECT + cmd_sp_exit) ; -/* End of configuration. */ -DEFUN_CALL (config_end, - config_end_cmd, - "end", - "End current mode and change to enable mode\n") +/*------------------------------------------------------------------------------ + * end command: down to enable mode. + */ +DEFUN_ATTR (config_end, + config_end_cmd, + "end", + "End current mode and change to enable mode\n", + CMD_ATTR_DIRECT + cmd_sp_end) { - return cmd_end(vty) ; + return CMD_SUCCESS ; /* will end to parsed->nnode */ } -/* Show version. */ +/* Show version. */ DEFUN_CALL (show_version, - show_version_cmd, - "show version", - SHOW_STR - "Displays zebra version\n") + show_version_cmd, + "show version", + SHOW_STR + "Displays zebra version\n") { - vty_out (vty, "Quagga %s (%s).%s", QUAGGA_VERSION, host.name?host.name:"", - VTY_NEWLINE); - vty_out (vty, "%s%s", QUAGGA_COPYRIGHT, VTY_NEWLINE); + VTY_LOCK() ; + + uty_out (vty->vio, "Quagga %s (%s).\n", QUAGGA_VERSION, + (host.name != NULL) ? host.name : "") ; + uty_out (vty->vio, "%s\n", QUAGGA_COPYRIGHT); + + VTY_UNLOCK() ; return CMD_SUCCESS; } -/* Help display function for all node. */ +/* Help display function for all node. */ DEFUN_CALL (config_help, config_help_cmd, "help", "Description of the interactive help system\n") { vty_out (vty, - "Quagga VTY provides advanced help feature. When you need help,%s\ -anytime at the command line please press '?'.%s\ -%s\ -If nothing matches, the help list will be empty and you must backup%s\ - until entering a '?' shows the available options.%s\ -Two styles of help are provided:%s\ -1. Full help is available when you are ready to enter a%s\ -command argument (e.g. 'show ?') and describes each possible%s\ -argument.%s\ -2. Partial help is provided when an abbreviated argument is entered%s\ - and you want to know what arguments match the input%s\ - (e.g. 'show me?'.)%s%s", VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, - VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, - VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE); + "Quagga VTY provides advanced help feature. When you need help,\n" + "anytime at the command line please press '?'.\n" + "\n" + "If nothing matches, the help list will be empty and you must backup\n" + "until entering a '?' shows the available options.\n" + "Two styles of help are provided:\n" + " 1. Full help is available when you are ready to enter a\n" + " command argument (e.g. 'show ?') and describes each possible\n" + " argument.\n" + " 2. Partial help is provided when an abbreviated argument is entered\n" + " and you want to know what arguments match the input\n" + " (e.g. 'show me?'.)\n" + "\n") ; return CMD_SUCCESS; } -/* Help display function for all node. */ +/* Help display function for all node. */ DEFUN_CALL (config_list, - config_list_cmd, - "list", - "Print command list\n") + config_list_cmd, + "list", + "Print command list\n") { unsigned int i; - struct cmd_node *cnode = vector_get_item (node_vector, vty->node); + struct cmd_node *cn ; struct cmd_command *cmd; - for (i = 0; i < vector_length (cnode->cmd_vector); i++) - if ((cmd = vector_get_item (cnode->cmd_vector, i)) != NULL - && !(cmd->attr & (CMD_ATTR_DEPRECATED | CMD_ATTR_HIDDEN))) - vty_out (vty, " %s%s", cmd->string, - VTY_NEWLINE); + cn = vector_get_item (node_vector, vty->node); + + for (i = 0; i < vector_length (cn->cmd_vector); i++) + if ( ((cmd = vector_get_item (cn->cmd_vector, i)) != NULL) + && ((cmd->attr & (CMD_ATTR_DEPRECATED | CMD_ATTR_HIDDEN)) == 0) ) + vty_out (vty, " %s\n", cmd->string); + return CMD_SUCCESS; } -/* Write current configuration into file. */ +/* Write current configuration into file. */ DEFUN (config_write_file, config_write_file_cmd, "write file", "Write running configuration to memory, network, or terminal\n" "Write to configuration file\n") { - unsigned int i; - int fd; - int err; - struct cmd_node *node; - char *config_file; - char *config_file_tmp = NULL; - char *config_file_sav = NULL; - int ret = CMD_WARNING; - - /* Check and see if we are operating under vtysh configuration */ - if (host.config_file == NULL) + qpath path ; + qpath temp ; + qpath save ; + cmd_return_code_t ret, retw ; + unsigned int i ; + int fd, err ; + struct cmd_node *cn; + const char *config_name ; + const char *save_name ; + char* temp_name ; + + err = 0 ; /* so far, so good */ + + VTY_LOCK() ; + path = (host.config_file != NULL) ? qpath_dup(host.config_file) : NULL ; + VTY_UNLOCK() ; + + /* Check and see if we are operating under vtysh configuration */ + if (path == NULL) { - vty_out (vty, "Can't save to configuration file, using vtysh.%s", - VTY_NEWLINE); + vty_out (vty, "%% Cannot save to configuration file, using vtysh.\n"); return CMD_WARNING; } - /* Get filename. */ - config_file = host.config_file; - - config_file_sav = - XMALLOC (MTYPE_TMP, strlen (config_file) + strlen (CONF_BACKUP_EXT) + 1); - strcpy (config_file_sav, config_file); - strcat (config_file_sav, CONF_BACKUP_EXT); + /* Set up the file names. */ + config_name = qpath_string(path) ; + save = qpath_dup(path) ; + qpath_extend_str(save, CONF_BACKUP_EXT) ; + save_name = qpath_string(save) ; - config_file_tmp = XMALLOC (MTYPE_TMP, strlen (config_file) + 8); - sprintf (config_file_tmp, "%s.XXXXXX", config_file); + temp = qpath_dup(path) ; + qpath_extend_str(temp, ".XXXXXX") ; + temp_name = qpath_char_string(temp) ; - /* Open file to configuration write. */ - fd = mkstemp (config_file_tmp); + /* Open file to configuration write. */ + fd = mkstemp (temp_name); if (fd < 0) { - vty_out (vty, "Can't open configuration file %s.\n", config_file_tmp) ; + err = errno ; + vty_out (vty, "%% Can't open configuration file %s", temp_name) ; goto finished; } - /* Make vty for configuration file. */ + /* Make vty for configuration file. */ vty_open_config_write(vty, fd) ; - /* Config file header print. */ vty_out (vty, "!\n! Zebra configuration saved from vty\n! "); vty_time_print (vty, 1); vty_out (vty, "!\n"); + retw = CMD_SUCCESS ; + for (i = 0; i < vector_length (node_vector); i++) { - if ((node = vector_get_item (node_vector, i)) && node->config_write) + if ((cn = vector_get_item (node_vector, i)) && cn->config_write) { - if ((*node->config_write) (vty)) + if ((*cn->config_write) (vty)) vty_out (vty, "!\n"); - vty_cmd_out_push(vty) ; /* Push stuff so far */ - } + retw = vty_cmd_out_push(vty) ; /* Push stuff so far */ + + if (retw != CMD_SUCCESS) + break ; + } ; } ; - err = vty_close_config_write(vty) ; + ret = vty_close_config_write(vty, (retw != CMD_SUCCESS)) ; - if (err != 0) + if ((ret != CMD_SUCCESS) || (retw != CMD_SUCCESS)) { - vty_out (vty, "Failed while writing configuration file %s.%s", - config_file_tmp, VTY_NEWLINE); + vty_out (vty, "%% Failed while writing configuration file %s.\n", + temp_name) ; goto finished; } - if (unlink (config_file_sav) != 0) + /* Now move files around to make .sav and the real file */ + ret = CMD_WARNING ; + + if (unlink (save_name) != 0) if (errno != ENOENT) { - vty_out (vty, "Can't unlink backup configuration file %s.%s", - config_file_sav, VTY_NEWLINE); + err = errno ; + vty_out (vty, "%% Can't unlink backup configuration file %s", + save_name) ; goto finished; } ; - if (link (config_file, config_file_sav) != 0) + if (link (config_name, save_name) != 0) { - vty_out (vty, "Can't backup old configuration file %s.%s", - config_file_sav, VTY_NEWLINE); + err = errno ; + vty_out (vty, "%% Can't backup old configuration file %s", config_name) ; goto finished; } ; sync () ; - if (unlink (config_file) != 0) + if (unlink (config_name) != 0) { - vty_out (vty, "Can't unlink configuration file %s.%s", - config_file, VTY_NEWLINE); + err = errno ; + vty_out (vty, "%% Can't unlink configuration file %s", config_name); goto finished; } ; - if (link (config_file_tmp, config_file) != 0) + if (link (temp_name, config_name) != 0) { - vty_out (vty, "Can't save configuration file %s.%s", - config_file, VTY_NEWLINE); + err = errno ; + vty_out (vty, "%% Can't save configuration file %s", config_name); goto finished; } ; sync (); - if (chmod (config_file, CONFIGFILE_MASK) != 0) + if (chmod (config_name, CONFIGFILE_MASK) != 0) { - vty_out (vty, "Can't chmod configuration file %s: %s (%s).\n", - config_file, errtostr(errno, 0).str, errtoname(errno, 0).str); + err = errno ; + vty_out (vty, "%% Can't chmod configuration file %s", config_name) ; goto finished; } - vty_out (vty, "Configuration saved to %s\n", config_file); + vty_out (vty, "Configuration saved to %s\n", config_name) ; ret = CMD_SUCCESS; finished: - unlink (config_file_tmp); - XFREE (MTYPE_TMP, config_file_tmp); - XFREE (MTYPE_TMP, config_file_sav); + if (err != 0) + vty_out(vty, ": %s (%s).\n", errtostr(errno, 0).str, + errtoname(errno, 0).str) ; + if (fd >= 0) + unlink (temp_name); + + qpath_free(temp) ; + qpath_free(save) ; + qpath_free(path) ; + return ret; -} +} ; ALIAS (config_write_file, config_write_cmd, @@ -1099,7 +1210,7 @@ ALIAS (config_write_file, "Copy running config to... \n" "Copy running config to startup config (same as write file)\n") -/* Write current configuration into the terminal. */ +/* Write current configuration into the terminal. */ DEFUN (config_write_terminal, config_write_terminal_cmd, "write terminal", @@ -1138,48 +1249,40 @@ DEFUN (config_write_terminal, return CMD_SUCCESS; } -/* Write current configuration into the terminal. */ +/* Write current configuration into the terminal. */ ALIAS (config_write_terminal, show_running_config_cmd, "show running-config", SHOW_STR "running configuration\n") -/* Write startup configuration into the terminal. */ +/* Write startup configuration into the terminal. */ DEFUN (show_startup_config, show_startup_config_cmd, "show startup-config", SHOW_STR - "Contentes of startup configuration\n") + "Contents of startup configuration\n") { - char buf[BUFSIZ]; - FILE *confp; + cmd_return_code_t ret ; + qpath path ; - confp = fopen (host.config_file, "r"); - if (confp == NULL) - { - vty_out (vty, "Can't open configuration file [%s]%s", - host.config_file, VTY_NEWLINE); - return CMD_WARNING; - } + VTY_LOCK() ; + path = (host.config_file != NULL) ? qpath_dup(host.config_file) : NULL ; + VTY_UNLOCK() ; - while (fgets (buf, BUFSIZ, confp)) + if (path == NULL) { - char *cp = buf; - - while (*cp != '\r' && *cp != '\n' && *cp != '\0') - cp++; - *cp = '\0'; - - vty_out (vty, "%s%s", buf, VTY_NEWLINE); - } + vty_out (vty, "%% Cannot show configuration file, using vtysh.\n"); + return CMD_WARNING; + } ; - fclose (confp); + ret = vty_cat_file(vty, path, "configuration file") ; + qpath_free(path) ; - return CMD_SUCCESS; + return ret ; } -/* Hostname configuration */ +/* Hostname configuration */ DEFUN_CALL (config_hostname, hostname_cmd, "hostname WORD", @@ -1453,6 +1556,10 @@ DEFUN_HID_CALL (do_echo, return CMD_SUCCESS; } +/*============================================================================== + * Logging configuration. + */ + DEFUN_CALL (config_logmsg, config_logmsg_cmd, "logmsg "LOG_LEVELS" .MESSAGE", @@ -1601,54 +1708,34 @@ DEFUN_CALL (no_config_log_monitor, return CMD_SUCCESS; } +/*------------------------------------------------------------------------------ + * Set new logging file and level -- "log file FILENAME [LEVEL]" + * + * Note that even if fail to open the new log file, will set host.logfile. + * + * Failure here is an error. + */ static int set_log_file(struct vty *vty, const char *fname, int loglevel) { - int ret; - char *p = NULL; - const char *fullpath; - - /* Path detection. */ - if (! IS_DIRECTORY_SEP (*fname)) - { - char cwd[MAXPATHLEN+1]; - cwd[MAXPATHLEN] = '\0'; - - if (getcwd (cwd, MAXPATHLEN) == NULL) - { - zlog_err ("config_log_file: Unable to alloc mem!"); - return CMD_WARNING; - } + int err ; - if ( (p = XMALLOC (MTYPE_TMP, strlen (cwd) + strlen (fname) + 2)) - == NULL) - { - zlog_err ("config_log_file: Unable to alloc mem!"); - return CMD_WARNING; - } - sprintf (p, "%s/%s", cwd, fname); - fullpath = p; - } - else - fullpath = fname; - - ret = zlog_set_file (NULL, fullpath, loglevel); + VTY_LOCK() ; - if (p) - XFREE (MTYPE_TMP, p); + host.logfile = uty_cmd_path_name_complete(host.logfile, fname, + vty->exec->context) ; + err = zlog_set_file (NULL, qpath_string(host.logfile), loglevel) ; - if (!ret) - { - vty_out (vty, "can't open logfile %s\n", fname); - return CMD_WARNING; - } - - if (host.logfile) - XFREE (MTYPE_HOST, host.logfile); + VTY_UNLOCK() ; - host.logfile = XSTRDUP (MTYPE_HOST, fname); + if (err == 0) + return CMD_SUCCESS ; - return CMD_SUCCESS; + vty_out(vty, "%% failed to open log file %s: %s (%s)\n", + qpath_string(host.logfile), + errtostr(err, 0).str, + errtoname(err, 0).str) ; + return CMD_WARNING ; } DEFUN_CALL (config_log_file, @@ -1663,7 +1750,7 @@ DEFUN_CALL (config_log_file, DEFUN_CALL (config_log_file_level, config_log_file_level_cmd, - "log file FILENAME "LOG_LEVELS, + "log file FILENAME " LOG_LEVELS, "Logging control\n" "Logging to file\n" "Logging filename\n" @@ -1673,6 +1760,7 @@ DEFUN_CALL (config_log_file_level, if ((level = level_match(argv[1])) == ZLOG_DISABLED) return CMD_ERR_NO_MATCH; + return set_log_file(vty, argv[0], level); } @@ -1684,13 +1772,13 @@ DEFUN_CALL (no_config_log_file, "Cancel logging to file\n" "Logging file name\n") { - zlog_reset_file (NULL); + VTY_LOCK() ; - if (host.logfile) - XFREE (MTYPE_HOST, host.logfile); + zlog_reset_file (NULL); - host.logfile = NULL; + host.logfile = qpath_free(host.logfile) ; + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -1878,21 +1966,43 @@ DEFUN_CALL (no_config_log_timestamp_precision, return CMD_SUCCESS; } +/*============================================================================== + * MOTD commands and set up. + * + * Note that can set a MOTD file that does not exist at the time. A friendly + * message warns about this, but it is not an error. The message will not be + * seen while reading the configuration file -- but it is not worth stopping + * the configuration file reader for this ! + */ DEFUN_CALL (banner_motd_file, banner_motd_file_cmd, - "banner motd file [FILE]", + "banner motd file FILE", "Set banner\n" "Banner for motd\n" "Banner from a file\n" "Filename\n") { - if (host.motdfile) - XFREE (MTYPE_HOST, host.motdfile); + int err ; - host.motdfile = XSTRDUP (MTYPE_HOST, argv[0]); + VTY_LOCK() ; - return CMD_SUCCESS; -} + host.motdfile = uty_cmd_path_name_complete(host.motdfile, + argv[0], vty->exec->context) ; + err = qpath_stat_is_file(host.motdfile) ; + + if (err != 0) + { + vty_out(vty, "NB: '%s': ", qpath_string(host.motdfile)) ; + if (err < 0) + vty_out(vty, "is not a file\n") ; + else + vty_out(vty, "%s (%s)\n", errtostr(err, 0).str, + errtoname(err, 0).str) ; + } ; + + VTY_UNLOCK() ; + return CMD_SUCCESS ; +} ; DEFUN_CALL (banner_motd_default, banner_motd_default_cmd, @@ -1901,7 +2011,11 @@ DEFUN_CALL (banner_motd_default, "Strings for motd\n" "Default string\n") { - host.motd = DEFAULT_MOTD ; + VTY_LOCK() ; + + host.motd = default_motd ; + + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -1912,23 +2026,114 @@ DEFUN_CALL (no_banner_motd, "Set banner string\n" "Strings for motd\n") { - host.motd = NULL; - if (host.motdfile) - XFREE (MTYPE_HOST, host.motdfile); - host.motdfile = NULL; + VTY_LOCK() ; + + host.motd = NULL ; + host.motdfile = qpath_free(host.motdfile) ; + + VTY_UNLOCK() ; return CMD_SUCCESS; } -/* Set config filename. Called from vty.c */ +/*============================================================================== + * Current directory handling + */ +DEFUN_CALL (do_chdir, + chdir_cmd, + "chdir DIR", + "Set current directory\n" + "Directory to set\n") +{ + cmd_return_code_t ret ; + qpath path ; + int err ; + + ret = CMD_SUCCESS ; + + path = uty_cmd_path_name_complete(NULL, argv[0], vty->exec->context) ; + err = qpath_stat_is_directory(path) ; + + if (err == 0) + qpath_copy(vty->exec->context->dir_cd, path) ; + else + { + vty_out(vty, "%% chdir %s: ", qpath_string(path)) ; + if (err < 0) + vty_out(vty, "is not a directory\n") ; + else + vty_out(vty, "%s (%s)\n", errtostr(err, 0).str, + errtoname(err, 0).str) ; + ret = CMD_WARNING ; + } ; + + qpath_free(path) ; + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Get cwd. + * + * This is done very early in the morning, before lowering privileges, to + * minimise chance of not being able to get the cwd. If cwd itself is not + * accessible in lowered privilege state, that will later become clear. + * + * Sets host.cwd, which is torn down in cmd_terminate(). + * + */ +extern void +cmd_getcwd(void) +{ + host.cwd = qpath_getcwd(NULL) ; + + if (host.cwd == NULL) + { + fprintf(stderr, "Cannot getcwd()\n") ; + exit(1) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Set host.config_file and host.config_dir. + */ extern void -host_config_set (const char* file_name) +cmd_host_config_set (qpath config_file) { - if (host.config_file) - XFREE (MTYPE_HOST, host.config_file); - host.config_file = XSTRDUP (MTYPE_HOST, file_name); + VTY_LOCK() ; + + host.config_file = qpath_copy(host.config_file, config_file) ; + host.config_dir = qpath_copy(host.config_dir, config_file) ; + + qpath_shave(host.config_dir) ; + + VTY_UNLOCK() ; } -void +/*------------------------------------------------------------------------------ + * Set the lexical level for further command processing. + */ +DEFUN_CALL (lexical_level, + lexical_level_cmd, + "lexical-level <0-1>", + "Set lexical level\n" + "The required lexical level\n") +{ + int level ; + + level = strtol(argv[0], NULL, 0) ; + + vty_cmd_set_full_lex(vty, (level != 0)) ; + + return CMD_SUCCESS; +} + +/*============================================================================== + * Command handling initialisation and termination. + */ + +/*------------------------------------------------------------------------------ + * Install copy of the default commands in the given node. + */ +extern void install_default (enum node_type node) { install_element (node, &config_exit_cmd); @@ -1944,13 +2149,28 @@ install_default (enum node_type node) install_element (node, &show_running_config_cmd); } -/* Initialize command interface. Install basic nodes and commands. */ -void -cmd_init (int terminal) +/*------------------------------------------------------------------------------ + * Initialise command handling. + * + * Install basic nodes and commands. Initialise the host structure. + * + * Sets srand(time(NULL)) + */ +extern void +cmd_init (bool terminal) { + srand(time(NULL)) ; + + if (host.cwd == NULL) /* in case cmd_cwd() not called, yet */ + cmd_getcwd() ; + /* Allocate initial top vector of commands. */ node_vector = vector_init(0); + /* Set default motd */ + host.motd = default_motd ; + host.config_brand = rand() ; + /* Default host value settings are already set, see above */ cmd_get_sys_host_name() ; /* start with system name & name_gen == 1 */ @@ -1972,30 +2192,41 @@ cmd_init (int terminal) install_element (VIEW_NODE, &config_quit_cmd); install_element (VIEW_NODE, &config_help_cmd); install_element (VIEW_NODE, &config_enable_cmd); + install_element (VIEW_NODE, &config_enable_configure_cmd); + install_element (VIEW_NODE, &config_terminal_cmd); install_element (VIEW_NODE, &config_terminal_length_cmd); install_element (VIEW_NODE, &config_terminal_no_length_cmd); install_element (VIEW_NODE, &show_logging_cmd); install_element (VIEW_NODE, &echo_cmd); + install_element (VIEW_NODE, &chdir_cmd); install_element (RESTRICTED_NODE, &config_list_cmd); install_element (RESTRICTED_NODE, &config_exit_cmd); install_element (RESTRICTED_NODE, &config_quit_cmd); install_element (RESTRICTED_NODE, &config_help_cmd); install_element (RESTRICTED_NODE, &config_enable_cmd); + install_element (RESTRICTED_NODE, &config_enable_configure_cmd); + install_element (RESTRICTED_NODE, &config_terminal_cmd); install_element (RESTRICTED_NODE, &config_terminal_length_cmd); install_element (RESTRICTED_NODE, &config_terminal_no_length_cmd); install_element (RESTRICTED_NODE, &echo_cmd); + install_element (RESTRICTED_NODE, &chdir_cmd); } if (terminal) { install_default (ENABLE_NODE); install_element (ENABLE_NODE, &config_disable_cmd); + install_element (ENABLE_NODE, &config_enable_cmd); + install_element (ENABLE_NODE, &config_enable_configure_cmd); install_element (ENABLE_NODE, &config_terminal_cmd); install_element (ENABLE_NODE, ©_runningconfig_startupconfig_cmd); } + install_element (ENABLE_NODE, &show_startup_config_cmd); install_element (ENABLE_NODE, &show_version_cmd); + install_element (ENABLE_NODE, &lexical_level_cmd); + install_element (ENABLE_NODE, &chdir_cmd); if (terminal) { @@ -2010,9 +2241,12 @@ cmd_init (int terminal) install_element (CONFIG_NODE, &hostname_cmd); install_element (CONFIG_NODE, &no_hostname_cmd); + install_element (CONFIG_NODE, &lexical_level_cmd); if (terminal) { + install_element (CONFIG_NODE, &echo_cmd); + install_element (CONFIG_NODE, &password_cmd); install_element (CONFIG_NODE, &password_text_cmd); install_element (CONFIG_NODE, &enable_password_cmd); @@ -2050,27 +2284,38 @@ cmd_init (int terminal) install_element (CONFIG_NODE, &service_terminal_length_cmd); install_element (CONFIG_NODE, &no_service_terminal_length_cmd); + install_element (RESTRICTED_NODE, &show_thread_cpu_cmd); install_element (VIEW_NODE, &show_thread_cpu_cmd); install_element (ENABLE_NODE, &show_thread_cpu_cmd); - install_element (RESTRICTED_NODE, &show_thread_cpu_cmd); install_element (VIEW_NODE, &show_work_queues_cmd); install_element (ENABLE_NODE, &show_work_queues_cmd); - } - srand(time(NULL)); -} + } ; +} ; +/*------------------------------------------------------------------------------ + * Close down command interface. + * + * Dismantle the node_vector and all commands. + * + * Clear out the host structure. + */ void cmd_terminate () { cmd_node cmd_node; cmd_command cmd ; + /* Ream out the vector of command nodes. */ while ((cmd_node = vector_ream(node_vector, free_it)) != NULL) { - while ((cmd = vector_ream(cmd_node->cmd_vector, free_it)) != NULL) + /* Ream out the (embedded) vector of commands per node. */ + while ((cmd = vector_ream(cmd_node->cmd_vector, keep_it)) != NULL) { - /* Note that each cmd is a static structure, which may appear in - * more than one cmd_vector. + /* Ream out the vector of items for each command. + * + * Note that each cmd is a static structure, which may appear in + * more than one cmd_vector -- but the "compiled" portions are + * dynamically allocated. */ cmd_item next_item ; @@ -2098,10 +2343,11 @@ cmd_terminate () 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); + host.logfile = qpath_free(host.logfile) ; + host.motdfile = qpath_free(host.motdfile) ; + host.config_file = qpath_free(host.config_file) ; + host.config_dir = qpath_free(host.config_dir) ; XFREE(MTYPE_HOST, host.vty_accesslist_name); XFREE(MTYPE_HOST, host.vty_ipv6_accesslist_name); - XFREE(MTYPE_HOST, host.vty_cwd); + host.cwd = qpath_free(host.cwd) ; } ; diff --git a/lib/command.h b/lib/command.h index 89644ce7..2b835cc8 100644 --- a/lib/command.h +++ b/lib/command.h @@ -204,7 +204,8 @@ #endif /* HAVE_IPV6 */ /* Prototypes. */ -extern void cmd_init (int); +extern void cmd_getcwd(void) ; +extern void cmd_init (bool); extern void cmd_terminate (void); extern void print_version (const char *); @@ -231,11 +232,7 @@ 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 ; -#endif #endif /* _ZEBRA_COMMAND_H */ diff --git a/lib/command_common.h b/lib/command_common.h index 0f7c4123..26f2909f 100644 --- a/lib/command_common.h +++ b/lib/command_common.h @@ -38,7 +38,7 @@ enum node_type AUTH_NODE, /* VTY login -> VIEW_NODE */ RESTRICTED_NODE, /* if no login required, may use this node */ VIEW_NODE, /* aka user EXEC */ - AUTH_ENABLE_NODE, /* enable login -> ENABLE_NODE */ + AUTH_ENABLE_NODE, /* enable login -> ENABLE_NODE/CONFIG_NODE */ ENABLE_NODE, /* aka privileged EXEC */ MIN_DO_SHORTCUT_NODE = ENABLE_NODE, @@ -86,6 +86,9 @@ enum node_type FORWARDING_NODE, /* forwarding config write -- see zserv.c */ PROTOCOL_NODE, /* protocol config write -- see zebra_vty.c */ VTY_NODE, /* line vty commands */ + + MAX_PLUS_1_NODE, + MAX_NODE = MAX_PLUS_1_NODE - 1 } ; typedef enum node_type node_type_t ; @@ -110,30 +113,44 @@ typedef enum node_type node_type_t ; */ enum cmd_return_code { - CMD_SUCCESS = 0, - CMD_WARNING = 1, - CMD_ERROR, + CMD_SUCCESS = 0, /* used generally */ - CMD_IO_ERROR, /* I/O -- failed :-( */ + /* Return codes suitable for command execution functions */ - CMD_SUCCESS_DAEMON, /* parser: success & command is for vtysh ? */ + CMD_WARNING = 1, + CMD_ERROR, - CMD_CLOSE, /* command: used by "exit" */ + /* Return codes from the command parser */ CMD_EMPTY, /* parser: nothing to execute */ - CMD_WAITING, /* I/O: waiting for more input */ - CMD_EOF, /* I/O: nothing more to come */ CMD_ERR_PARSING, /* parser: general parser error */ CMD_ERR_NO_MATCH, /* parser: command/argument not recognised */ CMD_ERR_AMBIGUOUS, /* parser: more than on command matches */ CMD_ERR_INCOMPLETE, + CMD_CLOSE, /* command: used by "exit" */ + + + + CMD_WAITING, /* I/O: waiting for more input */ + CMD_EOF, /* I/O: nothing more to come */ + + CMD_HIATUS, /* Something requires attention */ + + CMD_IO_ERROR, /* I/O -- failed :-( */ + CMD_IO_TIMEOUT, /* I/O -- timed out :-( */ + + /* For the chop ???? */ + CMD_COMPLETE_FULL_MATCH, /* cmd_completion returns */ CMD_COMPLETE_MATCH, CMD_COMPLETE_LIST_MATCH, - CMD_COMPLETE_ALREADY + CMD_COMPLETE_ALREADY, + + + CMD_SUCCESS_DAEMON, /* parser: success & command is for vtysh ? */ } ; typedef enum cmd_return_code cmd_return_code_t ; @@ -166,18 +183,66 @@ typedef struct cmd_node cmd_node_t ; typedef struct cmd_node* cmd_node ; /*------------------------------------------------------------------------------ - * Command elements -- contents of the node's cmd_vector + * Commands -- contents of the nodes' cmd_vector(s). + * + * A cmd_command is a static structure, which contains dynamic elements + * which are set when a command is installed. Note that is not uncommon for + * one cmd_command to appear in more than one node. + * + * The command attributes affect: + * + * * the parsing of the command -- in particular how the next_node is + * established. + * + * * whether the command is shown in help (or some forms of help if + * deprecated. + * + * * whether the command can be executed directly in the cli thread + * (avoiding having to wait for the cmd thread's attention -- this may be + * less useful now that all commands are treated a "priority" messages + * going into the cmd thread). + * + * If the command is marked CMD_ATTR_NODE, then the CMD_ATTR_MASK will + * extract the node_type_t that the command will set, if CMD_SUCCESS. This + * means that can parse commands without executing them. + * + * If the command is not marked CMD_ATTR_NODE, then the CMD_ATTR_MASK will + * extract the cmd_special_t value for the command -- which will be + * interesting if it isn't cmd_sp_simple. */ enum cmd_attr { - CMD_ATTR_SIMPLE = 0, /* bit significant */ - CMD_ATTR_DEPRECATED = BIT(0), - CMD_ATTR_HIDDEN = BIT(1), - CMD_ATTR_DIRECT = BIT(2), + CMD_ATTR_SIMPLE = 0, /* bit significant */ + + CMD_ATTR_NODE = BIT(7), /* sets given node */ + CMD_ATTR_MASK = CMD_ATTR_NODE - 1, + + CMD_ATTR_DEPRECATED = BIT(12), + CMD_ATTR_HIDDEN = BIT(13), /* not shown in help */ + CMD_ATTR_DIRECT = BIT(14), /* can run in cli thread */ }; typedef enum cmd_attr cmd_attr_t ; -/* Structure of command element. */ +CONFIRM(CMD_ATTR_MASK >= (cmd_attr_t)MAX_NODE) ; + +/* Special commands, which require extra processing at parse time. */ +enum cmd_special +{ + cmd_sp_simple = 0, + + cmd_sp_end, + cmd_sp_exit, + cmd_sp_enable, + cmd_sp_configure, + + cmd_sp_max_plus_1, + cmd_sp_max = cmd_sp_max_plus_1 - 1 +} ; +typedef enum cmd_special cmd_special_t ; + +CONFIRM(CMD_ATTR_MASK >= (cmd_attr_t)cmd_sp_max) ; + +/* Command functions and macros to define same */ struct cmd_command ; typedef struct cmd_command* cmd_command ; @@ -193,6 +258,8 @@ typedef const char* const argv_t[] ; typedef DEFUN_CMD_FUNCTION((cmd_function)) ; +/* The cmd_command structure itself */ + struct cmd_item ; /* Defined in command_parse.h */ struct cmd_command diff --git a/lib/command_execute.c b/lib/command_execute.c index cbf260b9..32698d94 100644 --- a/lib/command_execute.c +++ b/lib/command_execute.c @@ -40,135 +40,226 @@ /*------------------------------------------------------------------------------ * Construct cmd_exec object and initialise. * - * The following are set by uty_cmd_prepare() which is called after something - * has been pushed/popped on the vin/vout stacks, and before any command - * execution starts: + * This is done when a command loop is entered. * - * - parse_type - * - reflect_enabled - * - out_enabled - * - * to reflect the then current VTY state. + * The initial cmd_context reflects the vty->type and initial vty->node. */ extern cmd_exec cmd_exec_new(vty vty) { - cmd_exec exec ; + cmd_exec exec ; + cmd_context context ; exec = XCALLOC(MTYPE_CMD_EXEC, sizeof(struct cmd_exec)) ; /* Zeroising has set: * - * vty = X -- set below + * vty = X -- set below * - * line = NULL -- no command line, yet - * to_do = cmd_do_nothing + * action = all zeros * - * parse_type = cmd_parse_standard + * context = NULL -- see below * - * reflect_enabled = false -- not enabled - * out_enabled = false -- not enabled + * parsed = NULL -- see below * - * parsed all zeros -- empty parsed object (embedded) + * password_failures = 0 -- none, yet * - * state = exec_null - * locus = NULL -- not significant in exec_null - * ret = CMD_SUCCESS + * state = exec_null + * locus = NULL -- not significant in exec_null + * ret = CMD_SUCCESS * - * cq = NULL -- no mqb (qpthreads) - * -- no thread (legacy thread) + * cq = NULL -- no mqb (qpthreads) + * -- no thread (legacy thread) */ - confirm(cmd_do_nothing == 0) ; - confirm(cmd_parse_standard == 0) ; - confirm(CMD_PARSED_INIT_ALL_ZEROS) ; - confirm(exec_null == 0) ; - confirm(CMD_SUCCESS == 0) ; + confirm(CMD_ACTION_ALL_ZEROS) ; /* action */ + confirm(exec_null == 0) ; /* state */ + confirm(CMD_SUCCESS == 0) ; /* ret */ + + exec->vty = vty ; + + exec->parsed = cmd_parsed_new() ; + + /* Initialise the context */ + VTY_ASSERT_LOCKED() ; + + exec->context = context = cmd_context_new() ; + + context->node = vty->node ; + + context->parse_execution = true ; + context->parse_only = false ; + context->reflect_enabled = false ; + + context->onode = NULL_NODE ; + + context->dir_cd = qpath_dup(host.cwd) ; + context->dir_home = qpath_dup(host.config_dir) ; + + switch (vty->type) + { + case VTY_TERMINAL: + context->full_lex = true ; + + context->parse_strict = false ; + context->parse_no_do = false ; + context->parse_no_tree = false ; + + context->can_enable = context->node >= ENABLE_NODE ; + context->can_auth_enable = true ; + + context->dir_here = qpath_dup(context->dir_cd) ; + + break ; + + case VTY_SHELL_SERVER: + zabort("missing VTY_SHELL_SERVER context") ; + + context->full_lex = true ; + + context->parse_strict = false ; + context->parse_no_do = false ; + context->parse_no_tree = false ; + + context->can_enable = true ; + context->can_auth_enable = false ; - exec->vty = vty ; + context->dir_here = qpath_dup(context->dir_cd) ; + + break ; + + case VTY_CONFIG_READ: + context->full_lex = false ; + + context->parse_strict = true ; + context->parse_no_do = true ; + context->parse_no_tree = false ; + + context->can_enable = true ; + context->can_auth_enable = false ; + + context->dir_here = qpath_dup(context->dir_home) ; + + break ; + + default: + zabort("vty type unknown to cmd_exec_new()") ; + } ; return exec ; } ; /*------------------------------------------------------------------------------ - * Destroy cmd_exec object. + * Make a new context -- returns a completely empty context object. */ -extern cmd_exec -cmd_exec_free(cmd_exec exec) +extern cmd_context +cmd_context_new(void) { - cmd_parsed_reset(exec->parsed, keep_it) ; + return XCALLOC(MTYPE_CMD_EXEC, sizeof(struct cmd_context)) ; +} ; +/*------------------------------------------------------------------------------ + * Save current context and update for new, child vin. + * + * - updates the dir_here if required + * + * - cannot inherit can_enable unless is ENABLE_NODE or better + * + * - cannot inherit can_auth_enable, no how + */ +extern cmd_context +cmd_context_new_save(cmd_context context, qpath file_here) +{ + cmd_context saved ; + saved = cmd_context_new() ; - XFREE(MTYPE_CMD_EXEC, exec) ; + *saved = *context ; /* copy as is */ - return NULL ; -} ; + /* The saved copy of the context now owns the current paths, so now need + * to duplicate (or set new) paths. + */ + context->dir_cd = qpath_dup(saved->dir_cd) ; + context->dir_home = qpath_dup(saved->dir_home) ; + + if (file_here == NULL) + context->dir_here = qpath_dup(saved->dir_here) ; + else + { + context->dir_here = qpath_dup(file_here) ; + qpath_shave(context->dir_here) ; + } ; + /* The inheritance of can_enable is tricky. Will not bequeath can_enable + * is is not already in ENABLE_NODE or better ! + */ + if (context->node <= ENABLE_NODE) + context->can_enable = false ; -/*============================================================================== - * - */ + context->can_auth_enable = false ; + + return saved ; +} ; /*------------------------------------------------------------------------------ - * Set new node. + * Restore given context -- frees the copy restored from. * - * If old node >= CONFIG_NODE, and new node < CONFIG_NODE, give up the config - * symbol of power. + * Has to free the directories in the context being restored to. * - * Returns: CMD_SUCCESS -- OK - * CMD_CLOSE -- if new node == NODE_NULL + * Returns NULL. */ -static cmd_return_code_t -cmd_set_node(vty vty, node_type_t node) +extern cmd_context +cmd_context_restore(cmd_context dst, cmd_context src) { - if ((vty->node >= MIN_CONFIG_NODE) && (node < MIN_CONFIG_NODE)) - vty_config_unlock(vty, node) ; - else - vty->node = node ; + assert(src != NULL) ; + + qpath_free(dst->dir_cd) ; + qpath_free(dst->dir_home) ; + qpath_free(dst->dir_here) ; + + *dst = *src ; /* copy as is */ - return (vty->node != NULL_NODE) ? CMD_SUCCESS : CMD_CLOSE ; + return cmd_context_free(src, true) ; } ; /*------------------------------------------------------------------------------ - * Command line "end" command + * Free the given context. * - * Falls back to the current node's end_to node. If leaves configuration - * mode, give away the configuration symbol of power. - * - * Generally, for all configuration nodes end -> NODE_ENABLE (releasing the - * configuration lock), and all other nodes end does nothing. - * - * Returns: CMD_SUCCESS -- OK - * CMD_CLOSE -- if new node == NODE_NULL + * If the context is a copy of an existing context, then must not free the + * directories -- they will be freed when that existing context is freed. */ -extern cmd_return_code_t -cmd_end(vty vty) +extern cmd_context +cmd_context_free(cmd_context context, bool copy) { - return cmd_set_node(vty, cmd_node_end_to(vty->node)) ; + if (context != NULL) + { + if (!copy) + { + qpath_free(context->dir_cd) ; + qpath_free(context->dir_home) ; + qpath_free(context->dir_here) ; + } ; + + XFREE(MTYPE_CMD_EXEC, context) ; /* sets context = NULL */ + } ; + + return context ; } ; /*------------------------------------------------------------------------------ - * Command line "exit" command -- aka "quit" - * - * Falls back to the current node's exit_to node. If leaves configuration - * mode, give away the configuration symbol of power. - * - * Generally: - * - * - for all configuration nodes > NODE_CONFIG exit -> parent node. - * - * - for NODE_CONFIG exit -> ENABLE_NODE (and release configuration symbol - * of power) - * - * - for all nodes < NODE_CONFIG -> close the VTY - * - * Returns: CMD_SUCCESS -- OK - * CMD_CLOSE -- if new node == NODE_NULL + * Destroy cmd_exec object. */ -extern cmd_return_code_t -cmd_exit(vty vty) +extern cmd_exec +cmd_exec_free(cmd_exec exec) { - return cmd_set_node(vty, cmd_node_exit_to(vty->node)) ; + if (exec != NULL) + { + exec->parsed = cmd_parsed_free(exec->parsed) ; + exec->context = cmd_context_free(exec->context, false) ; /* not a copy */ + + XFREE(MTYPE_CMD_EXEC, exec) ; + } ; + + return NULL ; } ; /*============================================================================== @@ -236,16 +327,12 @@ cmd_execute_command(struct vty *vty, * 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. + * Returns: cmd_return_code for last command or I/O operation * - * If + * when returns, the entire vin/vout stack will have been closed. * - * If return code is not CMD_SUCCESS, the the output buffering contains the - * output from the last command attempted. + * If reaches EOF on the config file, returns CMD_SUCCESS. If anything else + * happens, will generate an error message and exits the command loop. */ extern cmd_return_code_t cmd_read_config(struct vty *vty, cmd_command first_cmd, bool ignore_warning) @@ -254,89 +341,122 @@ cmd_read_config(struct vty *vty, cmd_command first_cmd, bool ignore_warning) cmd_parsed parsed = exec->parsed ; cmd_return_code_t ret; + ret = CMD_SUCCESS ; /* so far, so good */ + while (1) { - /* Need a command line, pops pipes as required */ - ret = vty_cmd_fetch_line(vty) ; /* sets exec->line */ + /* Deal with anything which is not success ! + */ + if ((ret != CMD_SUCCESS) && (ret != CMD_EMPTY)) + { + /* Will drop straight out of the loop if have anything other + * than CMD_HIATUS, CMD_EOF or CMD_CLOSE, which are all signals + * that some adjustment to the vin/vout stacks is required, + * or that we are all done here. + * + * Everything else is deemed to be an error that stops the + * command loop. + */ + if ((ret != CMD_HIATUS) && (ret != CMD_EOF) && (ret != CMD_CLOSE)) + break ; + + ret = vty_cmd_hiatus(vty, ret) ; + /* for CMD_EOF & CMD_HIATUS only */ + if (ret == CMD_EOF) + return CMD_SUCCESS ; /* eof on the config file is + the expected outcome ! */ + if (ret != CMD_SUCCESS) + break ; + } ; + + /* If all is well, need another command line */ + + ret = vty_cmd_fetch_line(vty) ; /* sets exec->action */ if (ret != CMD_SUCCESS) - break ; /* stop on any and all problems */ + continue ; /* Parse the command line we now have */ - cmd_tokenise(parsed, exec->line) ; - ret = cmd_parse_command(parsed, vty->node, - exec->parse_type | cmd_parse_execution) ; + assert(exec->action->to_do == cmd_do_command) ; - if (ret == CMD_EMPTY) - continue ; /* easy if empty line */ + cmd_tokenize(parsed, exec->action->line, exec->context->full_lex) ; + ret = cmd_parse_command(parsed, exec->context) ; if (ret != CMD_SUCCESS) - break ; /* stop on *any* parsing issue */ + continue ; /* Special handling before first active line. */ if (first_cmd != NULL) { if (first_cmd != parsed->cmd) { - ret = (*first_cmd->func)(first_cmd, vty, 0, NULL) ; + ret = (*first_cmd->func)(first_cmd, vty, -1, NULL) ; if (ret != CMD_SUCCESS) - break ; /* stop on *any* issue with "default" */ + continue ; } ; first_cmd = NULL ; } ; /* reflection now..... */ - if (exec->reflect_enabled) - vty_cmd_reflect_line(vty) ; + if (exec->reflect) + { + ret = vty_cmd_reflect_line(vty) ; + if (ret != CMD_SUCCESS) + continue ; + } ; /* Pipe work, if any */ if ((parsed->parts & cmd_parts_pipe) != 0) { ret = cmd_open_pipes(vty) ; if (ret != CMD_SUCCESS) - break ; + continue ; } ; /* Command execution, if any */ if ((parsed->parts & cmd_part_command) != 0) - { - ret = cmd_execute(vty) ; + ret = cmd_execute(vty) ; - if (ret != CMD_SUCCESS) - { - /* If ignoring warnings, treat CMD_WARNING as CMD_SUCCESS */ - if (ignore_warning && (ret == CMD_WARNING)) - ret = CMD_SUCCESS ; - - /* Treat CMD_CLOSE as CMD_SUCCESS */ - else if (ret == CMD_CLOSE) - ret = CMD_SUCCESS ; - - /* Everything else -> stop */ - else - break ; - } ; - } ; - - /* When we get here the last command was CMD_SUCCESS ! - * (Or CMD_WARNING and ignore_warning.) - * - * Deals with closing out pipe(s) if required. - */ - vty_cmd_success(vty) ; + /* Deal with success (or suppressed warning). */ + if ((ret == CMD_SUCCESS) || ((ret == CMD_WARNING) && ignore_warning)) + ret = vty_cmd_success(vty) ; } ; - /* Deal with any errors */ - if (ret == CMD_EOF) - return CMD_SUCCESS ; + /* Arrives here if: + * + * - vty_cmd_fetch_line() returns anything except CMD_SUCCESS, CMD_EOF or + * CMD_HIATUS -- which are not errors. + * + * - any other operation returns anything except CMD_SUCCESS + * (or CMD_WARNING, if they are being ignored), CMD_EOF or CMD_CLOSE. + * + * Deal with any errors -- generate suitable error messages and close back + * to (but excluding) vout_base. + * + * CMD_SUCCESS and CMD_EMPTY are impossible at this point -- they should + * have been dealt with in the loop. + * + * CMD_EOF is also impossible -- vty_cmd_fetch_line() or vty_cmd_hiatus() + * can return that, but that will have been dealt with. + * + * CMD_CLOSE is also impossible -- commands can return that, but that will + * have been dealt with. + * + * CMD_WAITING is not valid for blocking vio ! + */ + assert(ret != CMD_SUCCESS) ; + assert(ret != CMD_EMPTY) ; + assert(ret != CMD_EOF) ; + assert(ret != CMD_CLOSE) ; + assert(ret != CMD_WAITING) ; + + vty_cmd_hiatus(vty, ret) ; return ret ; } ; /*============================================================================== */ -static qstring cmd_get_pipe_file_name(cmd_parsed parsed, uint ti, uint nt) ; - /*------------------------------------------------------------------------------ * Open in and/or out pipes @@ -352,109 +472,127 @@ cmd_open_pipes(vty vty) cmd_exec exec = vty->exec ; cmd_parsed parsed = exec->parsed ; cmd_return_code_t ret ; + vty_io vio ; + + VTY_LOCK() ; + vio = vty->vio ; ret = CMD_SUCCESS ; + uty_cmd_depth_mark(vio) ; /* about to push vin and/or vout */ + /* Deal with any in pipe stuff */ if ((parsed->parts & cmd_part_in_pipe) != 0) { - if ((parsed->in_pipe & cmd_pipe_file) != 0) - { - qstring name ; - name = cmd_get_pipe_file_name(parsed, parsed->first_in_pipe, - parsed->num_in_pipe) ; + qstring args ; - ret = vty_cmd_open_in_pipe_file(vty, name, - (parsed->in_pipe & cmd_pipe_reflect) != 0) ; + args = cmd_tokens_concat(parsed, parsed->first_in_pipe, + parsed->num_in_pipe) ; - qs_reset(name, free_it) ; - } + if ((parsed->in_pipe & cmd_pipe_file) != 0) + ret = uty_cmd_open_in_pipe_file(vio, exec->context, args, + parsed->in_pipe) ; else if ((parsed->in_pipe & cmd_pipe_shell) != 0) - { - } + ret = uty_cmd_open_in_pipe_shell(vio, exec->context, args, + parsed->in_pipe) ; else zabort("invalid in pipe state") ; - if (ret != CMD_SUCCESS) - return ret ; + qs_free(args) ; } ; /* Deal with any out pipe stuff */ - if ((parsed->parts & cmd_part_out_pipe) != 0) + if (((parsed->parts & cmd_part_out_pipe) != 0) && (ret == CMD_SUCCESS)) { - if ((parsed->out_pipe & cmd_pipe_file) != 0) - { - qstring name ; - name = cmd_get_pipe_file_name(parsed, parsed->first_out_pipe, - parsed->num_out_pipe) ; + qstring args ; - ret = vty_cmd_open_out_pipe_file(vty, name, - ((parsed->out_pipe & cmd_pipe_append) != 0)) ; + args = cmd_tokens_concat(parsed, parsed->first_out_pipe, + parsed->num_out_pipe) ; - qs_reset(name, free_it) ; - } + if ((parsed->out_pipe & cmd_pipe_file) != 0) + ret = uty_cmd_open_out_pipe_file(vio, exec->context, args, + parsed->out_pipe) ; else if ((parsed->out_pipe & cmd_pipe_shell) != 0) - { - ret = vty_cmd_open_out_dev_null(vty) ; - } + ret = uty_cmd_open_out_pipe_shell(vio, exec->context, args, + parsed->out_pipe) ; else if ((parsed->out_pipe & cmd_pipe_dev_null) != 0) - { - ret = vty_cmd_open_out_dev_null(vty) ; - } + ret = uty_cmd_open_out_dev_null(vio) ; else zabort("invalid out pipe state") ; - if (ret != CMD_SUCCESS) - return ret ; + qs_free(args) ; } ; - return CMD_SUCCESS ; + VTY_UNLOCK() ; + return ret ; } ; /*------------------------------------------------------------------------------ - * Get pipe file name + * Command Execution. * - * Returns a brand new qstring that must be discarded after use. + * If !parse_only, set vty->node and dispatch the command. * - * Pro tem this just gets the token value !! TODO - */ -static qstring -cmd_get_pipe_file_name(cmd_parsed parsed, uint ti, uint nt) -{ - cmd_token t ; - - assert(nt == 2) ; - - t = cmd_token_get(parsed->tokens, ti + 1) ; - - return qs_copy(NULL, t->qs) ; -} ; - -/*------------------------------------------------------------------------------ - * Command Execution + * Returns: CMD_SUCCESS -- it all want very well + * CMD_WARNING -- not so good: warning message sent by vty_out() + * CMD_ERROR -- not so good: warning message sent by vty_out() + * CMD_CLOSE -- close the current input * - * Returns: + * NB: the distinction between CMD_WARNING and CMD_ERROR is that CMD_WARNING + * may be ignored when reading a configuration file. * - * - have command ready for execution -- CMD_SUCCESS - * - reached end of command stream -- CMD_CLOSE - * - encounter error of some kind -- CMD_WARNING, CMD_ERROR, etc + * NB: no other returns are acceptable ! */ extern cmd_return_code_t cmd_execute(vty vty) { - cmd_parsed parsed = vty->exec->parsed ; - cmd_command cmd = parsed->cmd ; + cmd_parsed parsed = vty->exec->parsed ; + cmd_context context = vty->exec->context ; + cmd_command cmd = parsed->cmd ; + cmd_return_code_t ret ; - node_type_t onode ; - onode = vty->node ; vty->node = parsed->cnode ; - ret = (*(cmd->func))(cmd, vty, cmd_arg_vector_argc(parsed), - cmd_arg_vector_argv(parsed)) ; + if (context->parse_only) + ret = CMD_SUCCESS ; + else + ret = (*(cmd->func))(cmd, vty, cmd_arg_vector_argc(parsed), + cmd_arg_vector_argv(parsed)) ; + + if (ret == CMD_SUCCESS) + { + /* If the node is changed by the command, do that now and make sure + * that the configuration symbol of power is straight. + * + * If the new node is >= CONFIG_NODE, then MUST already have acquired + * the symbol of power (otherwise the command would have failed !) + * + * If the new node is < CONFIG_NODE, then we will here release the + * symbol of power iff we are at the vin_base ! + * + * If the new node is NULL_NODE, then treat as CMD_CLOSE. + */ + if (context->node != parsed->nnode) + { + context->node = parsed->nnode ; + vty_cmd_config_lock_check(vty, context->node) ; + + if (context->node == NULL_NODE) + ret = CMD_CLOSE ; + } ; - if (((parsed->parts & cmd_part_do) != 0) && (vty->node == ENABLE_NODE)) - vty->node = onode ; + /* The command should (no longer) change the vty->node, but if it does, + * it had better be to the same as what the parser expected -- for if + * not, that will break "parse_only" and generally cause confusion. + */ + qassert((vty->node == parsed->cnode) || (vty->node == parsed->nnode)) ; + } + else + { + /* Enforce restrictions on return codes. */ + assert((ret == CMD_WARNING) || (ret == CMD_ERROR) + || (ret == CMD_CLOSE)) ; + } ; return ret ; } ; diff --git a/lib/command_execute.h b/lib/command_execute.h index c1a2bdd0..97033b6e 100644 --- a/lib/command_execute.h +++ b/lib/command_execute.h @@ -39,16 +39,14 @@ */ enum cmd_exec_state { - exec_null = 0, - - exec_special, /* not a simple command */ - - exec_fetch, - exec_parse, - exec_open_pipes, - exec_execute, - exec_success, - exec_complete, + exec_null = 0, /* not started, yet */ + exec_fetch, /* fetch command line */ + exec_open_pipes, /* open pipes on command line */ + exec_execute, /* execute standard command */ + exec_special, /* execute special command */ + exec_done_cmd, /* command has completed */ + exec_hiatus, /* while issues are dealt with */ + exec_stopped, /* command loop has stopped */ } ; typedef enum cmd_exec_state cmd_exec_state_t ; @@ -56,25 +54,27 @@ typedef struct cmd_exec* cmd_exec ; struct cmd_exec { - vty vty ; /* parent */ + vty vty ; /* parent */ + + cmd_action_t action ; /* to do + line */ + + cmd_context context ; /* how to parse/execute */ - qstring line ; /* pointer to qstring in vf */ - cmd_do_t to_do ; /* for cli driven stuff */ + bool out_suppress ; /* for configuration reading */ + bool reflect ; /* actually reflect */ - cmd_parse_type_t parse_type ; /* how should parse */ - bool out_enabled ; /* as required */ - bool reflect_enabled ; /* as required */ + cmd_parsed parsed ; /* parsing and its result */ - cmd_parsed_t parsed ; /* embedded */ + uint password_failures ; /* AUTH_NODE & AUTH_ENABLE_NODE */ - cmd_exec_state_t state ; /* for cq_process */ - qpn_nexus locus ; /* for cq_process */ + cmd_exec_state_t state ; /* for cq_process */ + qpn_nexus locus ; /* for cq_process */ - cmd_return_code_t ret ; /* for cq_process */ + cmd_return_code_t ret ; /* for cq_process */ union { - mqueue_block mqb ; /* for cq_process */ + mqueue_block mqb ; /* for cq_process */ struct thread* thread ; } cq ; } ; @@ -89,16 +89,14 @@ extern cmd_exec cmd_exec_free(cmd_exec exec) ; 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) ; - - - +extern cmd_context cmd_context_new(void) ; +extern cmd_context cmd_context_new_save(cmd_context src, qpath file_here) ; +extern cmd_context cmd_context_restore(cmd_context dst, cmd_context src) ; +extern cmd_context cmd_context_free(cmd_context context, bool copy) ; #if 0 diff --git a/lib/command_local.h b/lib/command_local.h index 321ed33e..0925930d 100644 --- a/lib/command_local.h +++ b/lib/command_local.h @@ -26,6 +26,7 @@ #include "vty_local.h" #include "vector.h" +#include "qpath.h" /*============================================================================== * This is for access to some things in command.c which are not required @@ -63,39 +64,41 @@ struct host int lines; /* Log filename. */ - char* logfile; + qpath logfile; /* config file name of this host */ - char* config_file ; + qpath config_file ; + qpath config_dir ; /* Flags for services */ bool advanced; bool encrypt; /* Banner configuration. */ - const char* motd; - char* motdfile; + const char* motd ; + qpath motdfile; /* Someone has the config symbol of power */ bool config ; + ulong config_brand ; - /* Allow VTY to start without password */ + /* Allow vty to start without password */ bool no_password_check ; /* Restrict unauthenticated logins? */ bool restricted_mode ; - /* Vty timeout value -- see "exec timeout" command */ + /* vty timeout value -- see "exec timeout" command */ unsigned long vty_timeout_val ; - /* Vty access-class command */ + /* vty access-class command */ char* vty_accesslist_name ; - /* Vty access-class for IPv6. */ + /* vty access-class for IPv6. */ char* vty_ipv6_accesslist_name ; - /* Current directory -- initialised in vty_init() */ - char* vty_cwd ; + /* Current directory -- initialised cmd_cwd() */ + qpath cwd ; } ; enum @@ -115,17 +118,20 @@ enum cmd_do cmd_do_command = 1, /* dispatch the current command line */ - cmd_do_ctrl_c, /* received ^c */ - cmd_do_ctrl_d, /* received ^d */ - cmd_do_ctrl_z, /* received ^z */ + cmd_do_ctrl_c, /* received ^C */ + cmd_do_ctrl_d, /* received ^D */ + cmd_do_ctrl_z, /* received ^Z */ cmd_do_eof, /* hit "EOF" */ + cmd_do_timed_out, /* terminal timed out */ cmd_do_count, /* number of different cli_do_xxx */ cmd_do_mask = 0x0F, + cmd_do_auth = 0x10, - cmd_do_auth_enable = 0x20, + + cmd_do_keystroke = 0xFF, /* special for keystroke reader */ } ; CONFIRM(cmd_do_count <= (cmd_do_mask + 1)) ; @@ -133,6 +139,19 @@ CONFIRM(cmd_do_count <= (cmd_do_mask + 1)) ; typedef enum cmd_do cmd_do_t ; /*------------------------------------------------------------------------------ + * Command action -- qualifier + line + */ +struct cmd_action +{ + cmd_do_t to_do ; + qstring line ; +} ; +typedef struct cmd_action cmd_action_t[1] ; +typedef struct cmd_action* cmd_action ; + +enum { CMD_ACTION_ALL_ZEROS = (cmd_do_nothing == 0) } ; + +/*------------------------------------------------------------------------------ * Vector of nodes -- defined in command.c, declared here so the parser can * reach it. */ @@ -149,8 +168,7 @@ extern vector node_vector ; */ extern const char* cmd_host_name(bool fresh) ; -extern char *host_config_file(void); -extern void host_config_set(const char* file_name); +extern void cmd_host_config_set(qpath config_file); extern const char* cmd_prompt(node_type_t node) ; @@ -158,4 +176,28 @@ 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) ; +/*============================================================================== + * + */ +Inline void +cmd_action_clear(cmd_action act) +{ + act->to_do = cmd_do_nothing ; + act->line = NULL ; /* not essential, but tidy */ +} ; + +Inline void +cmd_action_set(cmd_action act, cmd_do_t to_do, qstring line) +{ + act->to_do = to_do ; + act->line = line ; +} ; + +Inline void +cmd_action_take(cmd_action dst, cmd_action src) +{ + *dst = *src ; + cmd_action_clear(src) ; +} ; + #endif /* _ZEBRA_COMMAND_LOCAL_H */ diff --git a/lib/command_parse.c b/lib/command_parse.c index c64ee3ba..ba1b217e 100644 --- a/lib/command_parse.c +++ b/lib/command_parse.c @@ -963,35 +963,184 @@ cmd_cmp_range_items(const cmd_item a, const cmd_item b) */ /*------------------------------------------------------------------------------ - * Make a brand new token object + * Create a new, empty token_vector with room for a dozen arguments (initially). */ -Private cmd_token -cmd_token_new(void) +static token_vector +cmd_token_vector_new(void) { - return XCALLOC(MTYPE_TOKEN, sizeof(struct cmd_token)) ; + token_vector tv ; - /* 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) ; + tv = XCALLOC(MTYPE_CMD_PARSED, sizeof(token_vector_t)) ; + + vector_init_new(tv->body, 12) ; + + return tv ; } ; /*------------------------------------------------------------------------------ - * Empty token object and free it. + * Empty token_vector and release all memory if required. */ -static void -cmd_token_free(cmd_token t) +static token_vector +cmd_token_vector_free(token_vector tv) +{ + if (tv != NULL) + { + cmd_token t ; + + /* Give back all the token objects and release vector body */ + while ((t = vector_ream(tv->body, keep_it)) != NULL) + { + qs_reset(t->qs, keep_it) ; /* discard body of qstring */ + XFREE(MTYPE_TOKEN, t) ; + } ; + + XFREE(MTYPE_CMD_PARSED, tv) ; + } ; + + return NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Get string value of given token. + * + * Returns an empty (not NULL) string if token NULL or no string. + */ +inline static char* +cmd_token_make_string(cmd_token t) +{ + if (t->term) + return qs_char_nn(t->qs) ; + else + { + t->term = true ; + return qs_make_string(t->qs) ; + } +} ; + +/*------------------------------------------------------------------------------ + * Get i'th token from given token vector -- zero origin + */ +Inline cmd_token +cmd_token_get(token_vector tv, vector_index_t i) +{ + return vector_get_item(tv->body, i) ; +} ; + +/*------------------------------------------------------------------------------ + * Set i'th token from given token vector -- zero origin + */ +inline static void +cmd_token_set(token_vector tv, vector_index_t i, + cmd_token_type_t type, const char* p, usize len, usize tp) +{ + cmd_token t = cmd_token_get(tv, i) ; + + if (t == NULL) + { + /* Make a brand new token object */ + t = 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) ; + + vector_set_item(tv->body, i, t) ; + } ; + + 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) ; +} ; + + +/*------------------------------------------------------------------------------ + * Get one or more original token values, concatenated with space between each. + * + * Returns a brand new qstring that must be discarded after use. + */ +extern qstring +cmd_tokens_concat(cmd_parsed parsed, uint ti, uint nt) +{ + cmd_token t ; + qstring qs ; + + assert(nt >= 2) ; + + t = cmd_token_get(parsed->tokens, ++ti) ; + qs = qs_set_els(NULL, t->ot) ; + + while (--nt >= 2) + { + t = cmd_token_get(parsed->tokens, ++ti) ; + qs_append_str(qs, " ") ; + qs_append_els(qs, t->ot) ; + } ; + + return qs ; +} ; + +/*============================================================================== + * Argument vector + */ + +/*------------------------------------------------------------------------------ + * Create a new, empty arg_vector with room for a dozen arguments (initially). + */ +static arg_vector +cmd_arg_vector_new(void) { - qs_reset(t->qs, keep_it) ; /* discard body of qstring */ - XFREE(MTYPE_TOKEN, t) ; + arg_vector args ; + + args = XCALLOC(MTYPE_CMD_PARSED, sizeof(arg_vector_t)) ; + + vector_init_new(args->body, 12) ; + + return args ; +} ; + +/*------------------------------------------------------------------------------ + * Empty arg_vector and release all memory if required. + */ +static arg_vector +cmd_arg_vector_free(arg_vector args) +{ + if (args != NULL) + { + vector_reset(args->body, keep_it) ; + XFREE(MTYPE_CMD_PARSED, args) ; + } ; + + return NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Empty the body of the arg_vector object in cmd_parsed. + */ +Inline void +cmd_arg_vector_empty(cmd_parsed parsed) +{ + vector_set_length(parsed->args->body, 0) ; +} ; + +/*------------------------------------------------------------------------------ + * Push argument to the argument vector. + */ +Inline void +cmd_arg_vector_push(cmd_parsed parsed, char* arg) +{ + vector_push_item(parsed->args->body, arg) ; } ; /*============================================================================== @@ -999,31 +1148,31 @@ cmd_token_free(cmd_token t) */ /*------------------------------------------------------------------------------ - * Initialise a new cmd_parsed object, allocating if required + * Allocate and initialise a new cmd_parsed object */ extern cmd_parsed -cmd_parsed_init_new(cmd_parsed parsed) +cmd_parsed_new(void) { - if (parsed == NULL) - parsed = XCALLOC(MTYPE_CMD_PARSED, sizeof(*parsed)) ; - else - memset(parsed, 0, sizeof(*parsed)) ; + cmd_parsed parsed ; + + parsed = XCALLOC(MTYPE_CMD_PARSED, sizeof(cmd_parsed_t)) ; /* Zeroising the structure has set: * - * parts = 0 -- cleared by cmd_tokenise() - * tok_total = 0 -- set by cmd_tokenise() + * parts = 0 -- cleared by cmd_tokenize() + * tok_total = 0 -- set by cmd_tokenize() * - * elen = 0 -- set by cmd_tokenise() - * tsp = 0 -- set by cmd_tokenise() + * elen = 0 -- set by cmd_tokenize() + * tsp = 0 -- set by cmd_tokenize() * * cmd = NULL -- no command yet * cnode = 0 -- not set + * nnode = 0 -- not set * - * num_tokens = 0 -- set by cmd_tokenise() - * tokens = all zeros -- empty token vector + * num_tokens = 0 -- set by cmd_tokenize() + * tokens = NULL -- see below * - * args = all zeros -- empty vector of arguments + * args = NULL -- see below * * emess = NULL -- no error yet * eloc = 0 -- no error location @@ -1045,8 +1194,8 @@ cmd_parsed_init_new(cmd_parsed parsed) * 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 + * cmd_v = NULL -- no vector of filtered commands + * item_v = NULL -- no vector of filtered items * * strongest ) * best_complete ) set by cmd_filter_prepare() @@ -1054,37 +1203,33 @@ cmd_parsed_init_new(cmd_parsed parsed) * strict ) */ confirm(cmd_pipe_none == 0) ; - confirm(TOKEN_VECTOR_INIT_ALL_ZEROS) ; - confirm(ARG_VECTOR_INIT_ALL_ZEROS) ; + + parsed->tokens = cmd_token_vector_new() ; + parsed->args = cmd_arg_vector_new() ; return parsed ; } ; /*------------------------------------------------------------------------------ - * Empty out and (if required) free a cmd_parsed object + * Empty out and free a cmd_parsed object */ extern cmd_parsed -cmd_parsed_reset(cmd_parsed parsed, free_keep_b free_structure) +cmd_parsed_free(cmd_parsed parsed) { if (parsed != NULL) { - cmd_token t ; + parsed->tokens = cmd_token_vector_free(parsed->tokens) ; + parsed->args = cmd_arg_vector_free(parsed->args) ; - /* Give back all the token objects and release vector body */ - while ((t = vector_ream(parsed->tokens->body, keep_it)) != NULL) - cmd_token_free(t) ; + parsed->cmd_v = vector_reset(parsed->cmd_v, free_it) ; + parsed->item_v = vector_reset(parsed->item_v, free_it) ; - vector_reset(parsed->args->body, keep_it) ; /* embedded */ - vector_reset(parsed->cmd_v, keep_it) ; /* embedded */ - vector_reset(parsed->item_v, keep_it) ; /* embedded */ + parsed->emess = qs_reset(parsed->emess, free_it) ; - if (free_structure) - XFREE(MTYPE_CMD_PARSED, parsed) ; /* sets parsed = NULL */ - else - cmd_parsed_init_new(parsed) ; + XFREE(MTYPE_CMD_PARSED, parsed) ; } ; - return parsed ; + return NULL ; } ; /*============================================================================== @@ -1092,6 +1237,9 @@ cmd_parsed_reset(cmd_parsed parsed, free_keep_b free_structure) * * */ +static cmd_return_code_t +cmd_set_parse_error(cmd_parsed parsed, cmd_token t, usize off, + const char* format, ...) PRINTF_ATTRIBUTE(4, 5) ; /*------------------------------------------------------------------------------ * Register a parsing error. @@ -1113,27 +1261,72 @@ cmd_parsed_reset(cmd_parsed parsed, free_keep_b free_structure) * 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) +cmd_set_parse_error(cmd_parsed parsed, cmd_token t, usize off, + const char* format, ...) { - parsed->emess = mess ; - parsed->eloc = t->tp + off ; + va_list args ; + + qs_clear(parsed->emess) ; + + va_start (args, format); + parsed->emess = qs_vprintf(parsed->emess, format, args); + va_end (args) ; + + if (t != NULL) + parsed->eloc = t->tp + off ; + else + parsed->eloc = -1 ; return CMD_ERR_PARSING ; } ; +/*------------------------------------------------------------------------------ + * 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 + */ +extern void +cmd_get_parse_error(vio_fifo ebuf, cmd_parsed parsed, uint indent) +{ + if (parsed->eloc >= 0) + { + qstring here ; + + here = qs_set_fill(NULL, indent + parsed->eloc, "....") ; + vio_fifo_put_bytes(ebuf, qs_body_nn(here), qs_len_nn(here)) ; + qs_reset(here, free_it) ; + } ; + + vio_fifo_printf(ebuf, "^\n%% %s\n", qs_make_string(parsed->emess)) ; +} ; + /*============================================================================== * Lexical level stuff */ /*------------------------------------------------------------------------------ - * Take elstring and see if it is empty -- only whitespace and/or comment + * Take qstring and see if it is empty -- only whitespace and/or comment */ extern bool -cmd_is_empty(elstring line) +cmd_is_empty(qstring line) { cpp_t lp ; - els_cpp(lp, line) ; /* NULL -> NULL */ + qs_cpp(lp, line) ; /* NULL -> NULL */ while (lp->p < lp->e) { @@ -1156,87 +1349,82 @@ cmd_is_empty(elstring line) * 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 +static bool cmd_pipe_reserved_char(char ch) { return strchr("<|>%&*+-=?", ch) != NULL ; } ; /*------------------------------------------------------------------------------ - * Take elstring and break it into tokens. - * - * Discards leading and trailing ' ' or '\t'. + * Take qstring and break it into tokens. * * Expects string to have been preprocessed, if required, to ensure that any * unwanted control characters have been removed. This code only recognises - * '\t' and treats it as whitespace. + * '\t' and treats it as whitespace (so any other control characters will + * end up as part of a token). * - * Anything between '....' is ignored by the tokenizer. NB: this follows the - * shell convention, so '\' is also ignored and there is no way to include "'" - * in a single quoted string. + * Ignores leading and trailing ' ' or '\t' (whitespace). * - * Anything immediately preceded by '\' is ignored by the tokenizer. This - * includes blanks and quotes. + * Have "full_lex" flag -- to distinguish old and new. * - * Anything inside "...." is ignored by the tokenizer, including '\"' escapes. + * If not full_lex, we have this simple tokenization: * - * Unbalanced "'" or '"' are treated as if eol was a "'" or '"'. + * * tokens are separated by one or more whitespace characters. * - * Of the things which are not ignored by the tokenizer: + * * if the first non-whitespace character is '!', this is a comment line. * - * * tokens are separated by whitespace -- one ' ' or '\t' characters - * The whitespace is discarded. + * For the "full_lex" we more or less follow the usual shell conventions, + * except: * - * * tokens which start with any of: + * * can have '!' as well as '#' to start comments * - * '!', '#', '<' and '>' + * * may only have '|' at the start of a line. '>|' must be used for + * piping command output to shell command. So '|' is, effectively, a + * "shell command prefix". * - * terminate themselves, as follows: + * Limiting '|' in this way removes problems with regular expressions. * - * - from '!' or '#' to end of line is a comment token. + * Also allows a <| shell to use pipes !! * - * - '<' followed by pipe_reserved_chars is a token (in_pipe) + * So the "full_lex" will: * - * - '>' followed by pipe_reserved_chars is a token (out_pipe). + * * ignore anything between '....' -- as per shell convention, '\' is + * ignored and there is no way to include "'" in a single quoted string. * - * See above for pipe_reserved_ + * * ignore anything immediately preceded by '\' -- including space + * (or tab converted to a space) and double quote characters. * - * 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. + * * ignore anything between "...." -- including '\"' escapes. * - * NB: the tokenization roughly mimics the (POSIX) standard shell. The - * differences are: + * * unbalanced "'" or '"' are treated as if eol was a "'" or '"'. * - * '|' is *not* a pipe ('>|' is), because '|' is a character in the - * regex repertoire. + * Except when ignored by the rules above, the "full lex" will recognise + * the following: * - * '<' and '>' do not terminate a token -- so are only significant - * at the start of a token. + * * tokens are separated by whitespace -- one ' ' or '\t' characters. + * Whitespace before, after or between tokens is not part of the tokens. * - * '!' is not a comment for the shell. + * * the character '|' is significant at the start of a line (after any + * leading whitespace) only. It is then the start of an out_pipe + * token -- so self terminates after any pipe_reserved_chars. * - * The requirement for whitespace (or start of line) before '#' is - * consistent with the shell. + * * the characters '!', '#' are significant only at the start of a token, + * and then from there to end of line is comment. * - * The handling of '...' follows the standard shell. + * Note that this means that comment after a token must be separated by + * at least one space from that token. * - * The tokenization does not remove any " ' or \ characters, that is left - * for a later stage, where context may affect the handling. + * * the characters '<' and '>' are separators -- they terminate any + * preceding token. They are in_pipe or out_pipe tokens, and self + * terminate after any pipe_reserved_chars. * - * NB: any control characters other than '\t' are accepted as part of the - * current token ! + * The tokenization does not remove any " ' or \ characters, that is left for + * a later stage, where context may affect the handling. * * 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 + * NB: the elstring containing the line to be tokenized MUST NOT change * until the parsed object is finished with. * * Returns: the types of all tokens or'd together. @@ -1254,7 +1442,7 @@ cmd_pipe_reserved_char(char ch) * Note that the num_tokens does not include the cmd_tok_eol on the end. */ extern void -cmd_tokenise(cmd_parsed parsed, qstring line) +cmd_tokenize(cmd_parsed parsed, qstring line, bool full_lex) { cpp_t lp ; const char *cp, *tp ; @@ -1300,59 +1488,94 @@ cmd_tokenise(cmd_parsed parsed, qstring line) case '\'': /* proceed to matching ' or end */ ++cp ; - type |= cmd_tok_sq ; - while (cp < lp->e) + if (full_lex) { - if (*cp++ == '\'') - break ; + type |= cmd_tok_sq ; + while (cp < lp->e) + { + if (*cp++ == '\'') + break ; + } ; } ; break ; case '\\': /* step past escaped character, if any */ ++cp ; - type |= cmd_tok_esc ; - if (cp < lp->e) - ++cp ; + if (full_lex) + { + type |= cmd_tok_esc ; + if (cp < lp->e) + ++cp ; + } ; break ; case '"': /* proceed to matching " or end... */ ++cp ; - type |= cmd_tok_dq ; - while (cp < lp->e) /* NB: do not register \ separately */ + if (full_lex) { - if (*cp++ == '"') - if (*(cp - 2) != '\\') /* ignore escaped " */ - break ; + type |= cmd_tok_dq ; + while (cp < lp->e) /* NB: do not register \ separately */ + { + if (*cp++ == '"') + if (*(cp - 2) != '\\') /* ignore escaped " */ + break ; + } ; } ; break ; - case '>': /* '>' special at start */ - end = (cp == sp) ; - ++cp ; - if (end) /* if special */ + case '|': /* only at start of line */ + if (full_lex && (nt == 0) && (cp == sp)) { - type = cmd_tok_out_pipe ; - while ((cp < lp->e) && cmd_pipe_reserved_char(*cp)) - ++cp ; - } ; + type = cmd_tok_out_shell ; + do ++cp ; + while ((cp < lp->e) && cmd_pipe_reserved_char(*cp)) ; + end = true ; + } + else + ++cp ; break ; - case '<': /* '<' special at start */ - end = (cp == sp) ; - ++cp ; - if (end) /* if special */ + case '>': /* '>' is a separator */ + if (full_lex) { - type = cmd_tok_in_pipe ; - while ((cp < lp->e) && cmd_pipe_reserved_char(*cp)) - ++cp ; - } ; + if (cp == sp) + { + type = cmd_tok_out_pipe ; + do ++cp ; + while ((cp < lp->e) && cmd_pipe_reserved_char(*cp)) ; + } ; + end = true ; + } + else + ++cp ; break ; - case '!': /* '!' and '#' special at start */ - case '#': - if ((cp == sp) && (nt == 0)) + case '<': /* '<' is a separator */ + if (full_lex) { + if (cp == sp) + { + type = cmd_tok_in_pipe ; + do ++cp ; + while ((cp < lp->e) && cmd_pipe_reserved_char(*cp)) ; + } ; end = true ; + } + else + ++cp ; + break ; + + case '#': + if (!full_lex) + { + ++cp ; + break ; + } ; + fall_through ; /* treat as '!'. */ + + case '!': /* '!' and '#' special at token start */ + if ((cp == sp) && (full_lex || (nt == 0))) + { type = cmd_tok_comment ; cp = lp->e ; } @@ -1398,68 +1621,237 @@ cmd_tokenise(cmd_parsed parsed, qstring line) lp->e, 0, lp->e - lp->p) ; } ; + + + + +/*============================================================================== + * VTY Command Line Input Pipe + * + * Here are the mechanics which support the: + * + * < file_name + * + * <| shell_command + * + * and the: + * + * > file_name + * >> file_name + * | shell_command + * + *============================================================================== + * The file_name handling + * + * Two directories are supported: + * + * "home" -- being the root for configuration files + * + * "cd" -- being the root for relative filenames, in the usual way + * + * "here" -- being the directory for the enclosing "< filename" + * see below for more detailed semantics + * + * There are the global values: + * + * config home -- defaults to directory for configuration file. + * may be set by command. + * + * config cd -- defaults to cwd at start up + * + * There are the local values, within a given CLI instance (ie VTY): + * + * cli home -- set to config home when CLI instance starts, or when + * config home is set within the CLI. + * + * cli cd -- similarly + * + * cli here -- outside < is same as cli home, unless explicitly set + * - set by cli here + * - pushed each time executes <, and set to directory for + * the < filename. + * - pushed each time executes <| and left unchanged + * - popped each time exits < or <| + * - set to parent by "no cli here" inside <, or to + * default state outside < + * + * And then the filename syntax: + * + * /path -- absolute path -- in the usual way + * + * path -- path wrt "cli cd" -- in the usual way + * + * ~/path -- path in "cli home" -- so "config" stuff + * + * ~./path -- path in "here" -- so wrt to enclosing < file + * + *============================================================================== + * The Input Pipe Commands + * + * These are "universal commands". + * + * They are: + * + * < filename -- filename as above + * + * treat contents of given file as command lines. + * (That may include further < commands.) + * + * See notes on cli here for pushing/popping the here + * directory. + * + * TODO: Filename and quotes ?? + * + * <| shell_command -- the shell command is executed "as is" by system(). + * + * the stdout and stderr are collected. + * + * treats stdout as command lines. + * (That may include further < commands.) + * + * anything from stderr is sent to the VTY output. + * + * As far as the top level CLI is concerned, these are discrete commands. + * That is to say: + * + * -- except where blocked while reading the "pipe", all commands are + * executed one after another, in one Routing Engine operation. + * + * -- in any event, all output is gathered in the VTY buffering, and will + * be sent to the console (or where ever) only when the outermost command + * completes. + * + * There are three options associated with the output from a < operation: + * + * -- suppress command line reflect + * + * whether to suppress reflect of commands to the VTY before they are + * dispatched. + * + * The default is not to suppress command line reflect. + * + * -- suppress command results + * + * whether to suppress any output generated by each command. + * + * The default is not to suppress command results. + * + * -- suppress "more" + * + * whether to do "--more--", if currently applies, when finally + * outputting all the command results. + * + * The default is not to suppress "more". + * + * This option can only be set for the outermost < operation. + * + * Remembering that all output occurs in one go when the outermost < operation + * completes. + * + * The default is to show everything and implement "--more--", pretty much as + * if the commands had been typed in. Except that "--more--" applies to + * everything (including the command lines) together, rather than to the output + * of each command individually. + * + * These options may be changed by flags attached to the <, as follows: + * + * ! suppress command line reflect + * + * ? suppress "--more--" + * + * * suppress command output + * + * TODO: cli xxx commands for reflect and output suppression and notes .... + * + * TODO: Error handling.... + * + * TODO: Closing the CLI.... + * + * TODO: Time out and the CLI.... + * + * + */ + + + + +/*------------------------------------------------------------------------------ + * Get next cpp char & step -- unless at end of cpp. + */ +inline static char +cpp_getch(cpp p) +{ + if (p->p < p->e) + return *p->p++ ; + else + return '\0' ; +} + /*------------------------------------------------------------------------------ * Process in-pipe token and set the required bits in the pipe type word * - * Known tokens are: < <| <+ <|+ + * Known tokens are: < <| + * Known options are: + */ static cmd_return_code_t cmd_parse_in_pipe(cmd_parsed parsed, cmd_token t) { cpp_t p ; - bool ok ; + bool ok ; els_cpp(p, t->ot) ; - ok = ((p->p < p->e) && (*p->p++ == '<')) ; + ok = true ; - if (ok) - { - /* First character after '<' may qualify the type of the pipe */ - parsed->in_pipe = cmd_pipe_file ; + switch (cpp_getch(p)) + { + case '<': + switch (cpp_getch(p)) + { + default: + --p->p ; + fall_through ; - if (p->p < p->e) - { - switch (*p->p++) - { - case '|': - parsed->in_pipe = cmd_pipe_shell ; - break ; + case '\0': + parsed->in_pipe = cmd_pipe_file ; + break ; - default: - --p->p ; /* put back */ - break ; - } - } ; + case '|': + parsed->in_pipe = cmd_pipe_shell ; + break ; + } ; + 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: + ok = false ; + break ; + } ; - default: - --p->p ; - ok = false ; - break ; - } ; - } ; + while (ok && (p->p < p->e)) + { + switch (*p->p++) + { + case '+': + parsed->in_pipe |= cmd_pipe_reflect ; + break ; + + default: + ok = false ; + break ; + } ; } ; - if (!ok) - return cmd_parse_error(parsed, t, 0, "invalid 'pipe in'") ; + if (ok) + return CMD_SUCCESS ; - return CMD_SUCCESS ; + return cmd_set_parse_error(parsed, t, 0, "invalid 'pipe in'") ; } ; /*------------------------------------------------------------------------------ * Process out-pipe token and set the required bits in the pipe type word * - * Known tokens are: > >> >| >* + * Known tokens are: | > >> >| >* + * Known options are: none */ static cmd_return_code_t cmd_parse_out_pipe(cmd_parsed parsed, cmd_token t) @@ -1469,46 +1861,58 @@ cmd_parse_out_pipe(cmd_parsed parsed, cmd_token t) els_cpp(p, t->ot) ; - ok = ((p->p < p->e) && (*p->p++ == '>')) ; + ok = true ; - if (ok) - { - /* First character after '>' may qualify the type of the pipe */ - parsed->out_pipe = cmd_pipe_file ; + switch (cpp_getch(p)) + { + case '|': + parsed->out_pipe = cmd_pipe_shell | cmd_pipe_shell_only ; + break ; - if (p->p < p->e) - { - switch (*p->p++) - { - case '>': - parsed->out_pipe |= cmd_pipe_append ; - break ; + case '>': + switch (cpp_getch(p)) + { + default: + --p->p ; + fall_through ; - case '|': - parsed->out_pipe = cmd_pipe_shell ; - break ; + case '\0': + parsed->out_pipe = cmd_pipe_file ; + break ; - case '*': - parsed->out_pipe = cmd_pipe_dev_null ; - break ; + case '>': + parsed->out_pipe = cmd_pipe_file | cmd_pipe_append ; + break ; - default: - --p->p ; /* put back */ - break ; - } - } ; + case '|': + parsed->out_pipe = cmd_pipe_shell ; + break ; - /* Could now have options, but presently do not */ - if (p->p < p->e) - { + case '*': + parsed->out_pipe = cmd_pipe_dev_null ; + break ; + } ; + break ; + + default: + ok = false ; + break ; + } ; + + while (ok && (p->p < p->e)) + { + switch (*p->p++) + { + default: ok = false ; - } ; + break ; + } ; } ; - if (!ok) - return cmd_parse_error(parsed, t, 0, "invalid 'pipe out'") ; + if (ok) + return CMD_SUCCESS ; - return CMD_SUCCESS ; + return cmd_set_parse_error(parsed, t, 0, "invalid 'pipe out'") ; } ; /*------------------------------------------------------------------------------ @@ -1543,7 +1947,7 @@ cmd_parse_out_pipe(cmd_parsed parsed, cmd_token t) * \! -> ! ) * \# -> \# ) * - * NB: with the exception of $ inside of "..." the carefully avoids the + * NB: with the exception of $ inside of "..." this carefully avoids the * regex meta characters: .*+?^$_ and (|)[-] * and the regex escaped forms of those and the back reference \1 etc. * @@ -1595,7 +1999,7 @@ cmd_token_complete(cmd_parsed parsed, cmd_token t) { if (p->p == p->e) { - ret = cmd_parse_error(parsed, t, 0, "missing closing '") ; + ret = cmd_set_parse_error(parsed, t, 0, "missing closing '") ; break ; } ; @@ -1623,7 +2027,7 @@ cmd_token_complete(cmd_parsed parsed, cmd_token t) case '\\': *q->p++ = *p->p++ ; /* copy the \ */ if (p->p == p->e) - ret = cmd_parse_error(parsed, t, 0, "trailing \\") ; + ret = cmd_set_parse_error(parsed, t, 0, "trailing \\") ; else { if (dq) @@ -1659,7 +2063,7 @@ cmd_token_complete(cmd_parsed parsed, cmd_token t) } ; if (dq) - ret = cmd_parse_error(parsed, t, 0, "missing closing \"") ; + ret = cmd_set_parse_error(parsed, t, 0, "missing closing \"") ; if (ret == CMD_SUCCESS) { @@ -1676,7 +2080,9 @@ cmd_token_complete(cmd_parsed parsed, cmd_token t) } /*------------------------------------------------------------------------------ - * Tokenise the given line and work out where cursor is wrt tokens. + * Tokenize the given line and work out where cursor is wrt tokens. + * + * Note that this is implicitly "full lex". * * Looks for first token whose end is at or beyond the cursor position. Note * that: @@ -1724,8 +2130,8 @@ cmd_token_position(cmd_parsed parsed, qstring line) const char* e ; bool sq, dq, bs ; - /* Re-initialise parsed object and tokenise the given line */ - cmd_tokenise(parsed, line) ; + /* Re-initialise parsed object and tokenize the given line */ + cmd_tokenize(parsed, line, true) ; /* Get the cursor position */ cp = qs_cp_nn(line) ; @@ -1743,9 +2149,13 @@ cmd_token_position(cmd_parsed parsed, qstring line) if (cp > t->tp) { - /* As soon as we are past '<', '>', '!' or '#' -- return "special" */ + /* As soon as we are past '|', '<', '>', '!' or '#' + * return "special" + */ if ((t->type & ( cmd_tok_in_pipe - | cmd_tok_out_pipe | cmd_tok_comment) ) != 0) + | cmd_tok_out_pipe + | cmd_tok_out_shell + | cmd_tok_comment) ) != 0) return true ; } ; @@ -2333,28 +2743,24 @@ static int cmd_item_filter(cmd_parsed parsed, cmd_item item, cmd_token t) ; * Returns: number of commands which may match to. */ static uint -cmd_filter_prepare(cmd_parsed parsed, cmd_parse_type_t type) +cmd_filter_prepare(cmd_parsed parsed, cmd_context context) { 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 ; + 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. + /* Empty the working commands vector, making sure big enough for the current + * node's commands -- creates vector if required. * * 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)) ; + parsed->cmd_v = vector_re_init(parsed->cmd_v, vector_length(src_v)) ; /* Filter out commands which are too short. * @@ -2371,7 +2777,7 @@ cmd_filter_prepare(cmd_parsed parsed, cmd_parse_type_t type) if ( cmd->nt_max < nct) continue ; /* ignore if too short */ - if ((cmd->nt_min > nct) && execution) + if ((cmd->nt_min > nct) && context->parse_execution) continue ; /* ignore if too long */ vector_push_item(parsed->cmd_v, cmd) ; @@ -2384,8 +2790,9 @@ cmd_filter_prepare(cmd_parsed parsed, cmd_parse_type_t type) * not meet the minimum criterion is automatically excluded. */ - parsed->min_strength = execution ? ms_min_execute : ms_min_parse ; - parsed->strict = strict ; + parsed->min_strength = context->parse_execution ? ms_min_execute + : ms_min_parse ; + parsed->strict = context->parse_strict ; parsed->strongest = parsed->min_strength ; parsed->best_complete = mt_no_match ; @@ -2432,22 +2839,7 @@ cmd_filter(cmd_parsed parsed, uint ii, bool keep_items) 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)) ; - - /* 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. - */ + /* Decide which token we are trying to match. */ if (ii < parsed->num_command) ti = parsed->first_command + ii ; /* map to token index */ else @@ -2463,12 +2855,27 @@ cmd_filter(cmd_parsed parsed, uint ii, bool keep_items) } t = cmd_token_get(parsed->tokens, ti) ; + /* If we are keeping the items which are matched, set the item_v + * empty and guess that may get one item per command -- creates the item_v + * vector if required.. + * + * 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) + parsed->item_v = vector_re_init(parsed->item_v, + vector_length(parsed->cmd_v)) ; + + /* Work down the cmd_v, attempting to match cmd_items against the cmd_token. + * + * 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. + */ 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++) { cmd_command cmd ; @@ -2688,24 +3095,29 @@ cmd_item_filter(cmd_parsed parsed, cmd_item item, cmd_token t) */ static cmd_return_code_t cmd_parse_phase_one(cmd_parsed parsed, - cmd_parse_type_t type, node_type_t node) ; + cmd_context context) ; 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) ; + cmd_context context) ; +static cmd_return_code_t cmd_parse_specials(cmd_parsed parsed, + cmd_context context) ; +static node_type_t cmd_auth_specials(cmd_context context, node_type_t target) ; /*------------------------------------------------------------------------------ * Parse a command in the given "node", or (if required) any of its ancestors. * - * Requires command line to have been tokenised. + * Requires command line to have been tokenized. * * 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. + * - parsed->cnode may not be the incoming node. * - * NB: parsed->parts is what was found + * - parsed->nnode is set for when command succeeds. * - * NB: parsed->cmd->daemon => daemon + * - parsed->parts is what was found + * + * - parsed->cmd->daemon => daemon * * CMD_EMPTY => line is empty, except perhaps for comment * (iff parsing for execution) @@ -2739,7 +3151,7 @@ static cmd_return_code_t cmd_parse_phase_two(cmd_parsed parsed, * 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_parse_command(cmd_parsed parsed, cmd_context context) { cmd_return_code_t ret ; @@ -2753,16 +3165,17 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type) * * Complete any tokens which contain quotes and/or escapes. * - * If there is a command then: + * Unless CMD_ERR_PARSING: * * - sort out any "do" and cut from start of command. * - set parsed->cnode (from given node or according to "do") + * - set parsed->nnode (from given node) * - 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) ; + ret = cmd_parse_phase_one(parsed, context) ; if (ret != CMD_SUCCESS) { assert(ret == CMD_ERR_PARSING) ; /* no other error at this point */ @@ -2772,8 +3185,7 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type) /* 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_command) == 0) && context->parse_execution) { if ((parsed->parts & ~cmd_part_comment) == cmd_parts_none) return CMD_EMPTY ; /* accept empty */ @@ -2786,11 +3198,11 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type) /* Level 2 parsing * - * Try in the current node and then in parent nodes, if can. + * Try in the current node and then in parent nodes, if allowed. * - * 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. + * Will stop moving up the tree when hits a node which is its own parent. + * All nodes from NODE_CONFIG up have a parent. All nodes from NODE_ENABLE + * down are their own parent. * * 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 @@ -2803,19 +3215,14 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type) { node_type_t pnode ; - ret = cmd_parse_phase_two(parsed, type) ; + ret = cmd_parse_phase_two(parsed, context) ; if (ret == CMD_SUCCESS) break ; - if (ret != CMD_ERR_NO_MATCH) + if ((ret != CMD_ERR_NO_MATCH) || context->parse_no_tree) 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) @@ -2824,15 +3231,46 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type) parsed->cnode = pnode ; } ; - /* Parsed successfully. + /* Parsed successfully -- worry about parsed->nnode. + * + * Currently parsed->nnode is set to the node we came in on. If the + * parsed->cnode is not the same, then we have two choices: * - * If for execution, fill the arg_vector + * (a) parsed->cnode is ENABLE_NODE -- either because have 'do' or by tree + * walk -- in which case we leave parsed->nnode; + * + * (b) parsed->cnode is not ENABLE_NODE -- so this command, if successful + * will change context, in which cas we need to set parsed->nnode. + */ + if ((parsed->nnode != parsed->cnode) && (parsed->cnode != ENABLE_NODE)) + parsed->nnode = parsed->cnode ; + + /* Worry about "special" commands and those that set the next node. */ + if ((parsed->cmd->attr & (CMD_ATTR_NODE | CMD_ATTR_MASK)) != 0) + { + if ((parsed->cmd->attr & CMD_ATTR_NODE) != 0) + parsed->nnode = parsed->cmd->attr & CMD_ATTR_MASK ; + else + { + /* This is a "special" command, which may have some (limited) + * semantic restrictions. + * + * The main thing we are interested in is commands which have + * special effects on parsed->nnode (in particular exit and end). + */ + ret = cmd_parse_specials(parsed, context) ; + + if (ret != CMD_SUCCESS) + return ret ; + } ; + } ; + + /* 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) + if (context->parse_execution) { uint ti ; @@ -2866,7 +3304,7 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type) } ; /*------------------------------------------------------------------------------ - * Phase 1 of command parsing + * Phase 1 of command parsing -- get ready to search the command system. * * Scan the tokens to break them up into the sections: * @@ -2876,20 +3314,25 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type) * - out-pipe -- '>' etc up to comment * - comment -- '!' or '#' onwards * - * Requires line to have been tokenised -- cmd_tokenise(). + * Sets parsed->cnode to the given node, or to ENABLE_NODE if have 'do' (and + * the original node allows it). + * + * Sets parsed->nnode to the given node. + * + * Requires line to have been tokenized -- cmd_tokenize(). * * 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) +cmd_parse_phase_one(cmd_parsed parsed, cmd_context context) { uint nt = parsed->num_tokens ; /* Set command and parsing entries */ - parsed->cnode = node ; - parsed->cmd = NULL ; + parsed->nnode = parsed->cnode = context->node ; + parsed->cmd = NULL ; /* pick off any comment */ if ((parsed->tok_total & cmd_tok_comment) != 0) @@ -2926,7 +3369,7 @@ cmd_parse_phase_one(cmd_parsed parsed, cmd_parse_type_t type, node_type_t node) 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)) + if (!context->parse_no_do && (context->node >= MIN_DO_SHORTCUT_NODE)) { cmd_token t ; t = cmd_token_get(parsed->tokens, parsed->first_command) ; @@ -2935,7 +3378,7 @@ cmd_parse_phase_one(cmd_parsed parsed, cmd_parse_type_t type, node_type_t node) const char* p = els_body_nn(t->ot) ; if ((*p == 'd') && (*(p+1) == 'o')) { - node = ENABLE_NODE ; /* change to this node */ + parsed->cnode = ENABLE_NODE ; /* change to this node */ parsed->num_do = 1 ; parsed->first_do = parsed->first_command ; @@ -2956,7 +3399,7 @@ cmd_parse_phase_one(cmd_parsed parsed, cmd_parse_type_t type, node_type_t node) /*------------------------------------------------------------------------------ * Phase 1b of command parsing * - * Tokeniser found at least one of: + * tokenizer found at least one of: * * - in pipe token * - out pipe token @@ -2967,6 +3410,8 @@ cmd_parse_phase_one(cmd_parsed parsed, cmd_parse_type_t type, node_type_t node) * * Update the "parts" and the relevant first/num values. * + * Note that the number of tokens (nt) *excludes* any comment token. + * * Returns: CMD_SUCCESS -- all is well * CMD_ERR_PARSING -- parsing error -- malformed or misplaced pipe * -- malformed quotes/escapes @@ -2984,11 +3429,24 @@ cmd_parse_phase_one_b(cmd_parsed parsed, uint nt) n = 0 ; /* no tokens in current part */ pn = NULL ; /* no current part */ + /* Walk the tokens, establishing the start and end of: + * + * - command part ) + * - in pipe part ) exclusive + * - out shell part ) + * - out pipe part which may only follow command or in pipe + * + * + */ for (i = 0 ; i < nt ; ++i) { t = cmd_token_get(parsed->tokens, i) ; - if ((t->type & cmd_tok_simple) != 0) + /* Simple tokens can appear anywhere. + * + * If this is the first token, start a cmd_part_command. + */ + if ((t->type & cmd_tok_simple) != 0) { if (parts == cmd_parts_none) { @@ -2997,42 +3455,80 @@ cmd_parse_phase_one_b(cmd_parsed parsed, uint nt) pn = &parsed->num_command ; n = 0 ; } ; - } ; + } - if ((t->type & cmd_tok_in_pipe) != 0) + /* '<' types of token can appear anywhere, except in a cmd_part_command. + * + * If this is the first token, start a cmd_part_in_pipe. + */ + else if ((t->type & cmd_tok_in_pipe) != 0) { - if (parts != cmd_parts_none) - return cmd_parse_error(parsed, t, 0, "unexpected 'pipe in'") ; + if ((parts & (cmd_part_command | cmd_parts_pipe)) == cmd_part_command) + return cmd_set_parse_error(parsed, t, 0, "unexpected 'pipe in'") ; - ret = cmd_parse_in_pipe(parsed, t) ; - if (ret != CMD_SUCCESS) - return ret ; + if (parts == cmd_parts_none) + { + ret = cmd_parse_in_pipe(parsed, t) ; + if (ret != CMD_SUCCESS) + return ret ; - parts = cmd_part_in_pipe ; - parsed->first_in_pipe = i ; - pn = &parsed->num_in_pipe ; - n = 0 ; - } ; + 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) + /* '|' tokens can appear anywhere. + * + * If this is the first token, start a cmd_part_out_pipe. + */ + else if ((t->type & cmd_tok_out_shell) != 0) { - if ((parts == cmd_parts_none) || ((parts & cmd_part_out_pipe) != 0)) - return cmd_parse_error(parsed, t, 0, "unexpected 'pipe out'") ; + if ((parts & (cmd_part_command | cmd_parts_pipe)) == cmd_part_command) + return cmd_set_parse_error(parsed, t, 0, "unexpected 'shell pipe'") ; - ret = cmd_parse_out_pipe(parsed, t) ; - if (ret != CMD_SUCCESS) - return ret ; + if (parts == cmd_parts_none) + { + ret = cmd_parse_out_pipe(parsed, t) ; + if (ret != CMD_SUCCESS) + return ret ; + + parts = cmd_part_out_pipe ; + parsed->first_out_pipe = i ; + pn = &parsed->num_out_pipe ; + n = 0 ; + } ; + } + + /* '>' types of token can appear anywhere, except on an empty line. + * + * If not already in cmd_part_out_pipe, start a cmd_part_out_pipe. + */ + else if ((t->type & cmd_tok_out_pipe) != 0) + { + if (parts == cmd_parts_none) + return cmd_set_parse_error(parsed, t, 0, "unexpected 'pipe out'") ; - *pn = n ; /* set number of in-pipe/cmd */ + if ((parts & cmd_part_out_pipe) == 0) + { + 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 ; + 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 */ + /* Now deal with any "incompleteness" */ + if ((t->type & cmd_tok_incomplete) != 0) { ret = cmd_token_complete(parsed, t) ; @@ -3122,7 +3618,7 @@ cmd_parse_phase_one_b(cmd_parsed parsed, uint nt) if (msg != NULL) { t = cmd_token_get(parsed->tokens, i) ; - return cmd_parse_error(parsed, t, e ? els_len_nn(t->ot) : 0, msg) ; + return cmd_set_parse_error(parsed, t, e ? els_len_nn(t->ot) : 0, msg) ; } ; } ; @@ -3146,14 +3642,14 @@ cmd_parse_phase_one_b(cmd_parsed parsed, uint nt) * or parsed->num_command == 0 */ static cmd_return_code_t -cmd_parse_phase_two(cmd_parsed parsed, cmd_parse_type_t type) +cmd_parse_phase_two(cmd_parsed parsed, cmd_context context) { uint ii ; uint match ; /* Prepare to filter commands */ - cmd_filter_prepare(parsed, type) ; + cmd_filter_prepare(parsed, context) ; match = 2 ; /* in case parsed->num_command == 0 ! */ @@ -3172,6 +3668,158 @@ cmd_parse_phase_two(cmd_parsed parsed, cmd_parse_type_t type) return CMD_SUCCESS ; } ; +/*------------------------------------------------------------------------------ + * Perform the special handling required for the command that has parsed + * successfully so far. + * + * This mechanism is used to deal with commands which have a context sensitive + * effect on parsed->nnode. + * + * Can be used to do any other semantic checks, subject to the information + * being available. + * + * Returns: CMD_SUCCESS -- OK + * CMD_ERR_PARSING -- failed some check, see parsed->emess + * + * Dealt with here are: + * + * * "exit" and "quit" + * + * The next node depends on the current node. + * + * * "end" + * + * The next node depends on the current node. + * + * * "enable" + * + * This command is really only intended for interactive use, but need to + * do something with it in other contexts. + * + * Can go to ENABLE_NODE directly if can_enable is set in the context. + * + * The can_enable is set when the vty starts if it does not require any + * authentication to enter ENABLE_NODE (eg VTY_CONFIG_READ) or because + * it started in ENABLE_NODE or greater (eg VTY_TERMINAL with no password + * and advanced mode !). + * + * Once can_enable is set it is not unset. So getting to enable once is + * sufficient for a given VTY. + * + * A pipe will inherit can_enable, provided that the parent is in + * ENABLE_NODE or better. + * + * A pipe cannot inherit can_auth_enable -- this means that a pipe + * can either immediately enable, or cannot enable at all. + * + * The effect of all this is that "enable" is straightforward, except for + * VTY_TERMINAL. For VTY_TERMINAL: + * + * - if the VTY starts in any node >= ENABLE_NODE, then can_enable + * is set from the beginning ! + * + * If has ever reached ENABLE_NODE, then can_enable will be set. + * + * - otherwise: when enable command is seen, must authenticate. + * + * - if there is an enable password, then must get and accept the + * password, which can only happen at vin_depth == vout_depth == 0 + * -- see vty_cmd_can_auth_enable(). + * + * - if there is no enable password, then is implicitly authenticated + * if is in VIEW_NODE. + * + * Note that will not accept enable with no password if is in + * RESTRICTED_NODE. Can only be in RESTRICTED_NODE if started with + * no password, but host.restricted_mode is set. Doesn't seem much + * point having a restricted_mode if you can go straight to + * ENABLE_NODE just because a password has not been set ! + */ +static cmd_return_code_t +cmd_parse_specials(cmd_parsed parsed, cmd_context context) +{ + cmd_return_code_t ret ; + + ret = CMD_SUCCESS ; + + switch (parsed->cmd->attr & CMD_ATTR_MASK) + { + case cmd_sp_simple: + zabort("invalid cmd_sp_simple") ; + break ; + + case cmd_sp_end: + parsed->nnode = cmd_node_end_to(parsed->cnode) ; + break ; + + case cmd_sp_exit: /* NULL_NODE <=> CMD_EOF */ + parsed->nnode = cmd_node_exit_to(parsed->cnode) ; + break ; + + case cmd_sp_enable: + parsed->nnode = cmd_auth_specials(context, ENABLE_NODE) ; + break ; + + case cmd_sp_configure: + parsed->nnode = cmd_auth_specials(context, CONFIG_NODE) ; + break ; + + default: + zabort("unknown cmd_sp_xxx") ; + break ; + } ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Deal with commands which may require AUTH_ENABLE_NODE authentication. + * + * The rule is that the parser must set what node a given command *will* change + * to iff it succeeds. + * + * So if we can go directly to the target node, must establish that now. + * + * If cannot go to directly, we set that should go to AUTH_ENABLE_NODE. That + * may fail if not in a suitable state to do that -- and issue suitable + * message. + */ +static node_type_t +cmd_auth_specials(cmd_context context, node_type_t target) +{ + if (!context->can_enable) + { + /* Can enable if is already ENABLE_NODE or better (this should + * not be required -- but does no harm). + * + * If we are allowed to authenticate, then the authentication + * is trivial if there is no password set and we are in + * VIEW_NODE ! + * + * Note that if we are in RESTRICTED_NODE, then must authenticate, + * which will later be refused if no password is set at the time ! + */ + if (context->node >= ENABLE_NODE) + context->can_enable = true ; + else if (context->can_auth_enable) + { + /* Can do a*/ + bool no_pw ; + + VTY_LOCK() ; + no_pw = (host.enable == NULL) ; + VTY_UNLOCK() ; + + context->can_enable = no_pw && (context->node == VIEW_NODE) ; + } ; + } ; + + context->onode = context->node ; /* see vty_cmd_auth() */ + context->tnode = target ; /* see vty_cmd_auth() */ + + return context->can_enable ? target : AUTH_ENABLE_NODE ; +} ; + #if 0 /*------------------------------------------------------------------------------ * If src matches dst return dst string, otherwise return NULL @@ -3321,9 +3969,9 @@ cmd_describe_command (const char* line, node_type_t node, cmd_parsed parsed ; cmd_token_type_t tok_total ; - /* Set up a parser object and tokenise the command line */ + /* Set up a parser object and tokenize the command line */ parsed = cmd_parse_init_new(&parsed_s) ; - tok_total = cmd_tokenise(parsed, line, node) ; + tok_total = cmd_tokenize(parsed, line, node) ; @@ -3611,14 +4259,21 @@ cmd_help_preflight(cmd_parsed parsed) * */ extern cmd_return_code_t -cmd_completion(cmd_parsed parsed, node_type_t node) +cmd_completion(cmd_parsed parsed, cmd_context context) { 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) ; + context->can_enable = true ; + context->full_lex = true ; + context->parse_execution = false ; + context->parse_no_do = false ; + context->parse_no_tree = false ; + context->parse_strict = false ; + + ret = cmd_parse_command(parsed, context) ; if (ret == CMD_ERR_PARSING) return ret ; /* nothing more possible */ @@ -3674,9 +4329,6 @@ cmd_completion(cmd_parsed parsed, node_type_t node) return CMD_SUCCESS ; } ; - - - /*------------------------------------------------------------------------------ * How to insert a newly completed keyword ? * @@ -3714,7 +4366,7 @@ cmd_completion(cmd_parsed parsed, node_type_t node) * 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 + * *ins = number of spaces to insert : 0..2 * *mov = number of spaces to move afterwards (may be -ve) */ extern void diff --git a/lib/command_parse.h b/lib/command_parse.h index 789fe601..a1e801ce 100644 --- a/lib/command_parse.h +++ b/lib/command_parse.h @@ -29,8 +29,11 @@ #include "command_common.h" #include "vector.h" +#include "vio_fifo.h" #include "qstring.h" +#include "qpath.h" #include "elstring.h" +#include "memory.h" #if 0 /*============================================================================== @@ -449,19 +452,6 @@ item_best_match(cmd_item_type_t it) * */ -/* Command parsing options */ -enum cmd_parse_type /* bit significant */ -{ - 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_execution = BIT(3), /* wish to execute command */ -} ; -typedef enum cmd_parse_type cmd_parse_type_t ; - /* Pipe types */ enum cmd_pipe_type /* bit significant */ { @@ -476,6 +466,9 @@ enum cmd_pipe_type /* bit significant */ /* For out file pipes */ cmd_pipe_append = BIT(4), /* >> */ + + /* For out shell pipes */ + cmd_pipe_shell_only = BIT(4), /* | at start of line */ } ; typedef enum cmd_pipe_type cmd_pipe_type_t ; @@ -506,15 +499,17 @@ enum cmd_token_type /* *bit* significant */ cmd_tok_simple = BIT( 0), - cmd_tok_sq = BIT( 8), - cmd_tok_dq = BIT( 9), /* '\\' within "..." are not + cmd_tok_sq = BIT( 4), + cmd_tok_dq = BIT( 5), /* '\\' within "..." are not registered separately. */ - cmd_tok_esc = BIT(10), + cmd_tok_esc = BIT( 6), cmd_tok_incomplete = (cmd_tok_sq | cmd_tok_dq | cmd_tok_esc), - cmd_tok_in_pipe = BIT(12), /* token starting '<' */ - cmd_tok_out_pipe = BIT(13), /* token starting '>' */ + cmd_tok_in_pipe = BIT( 8), /* token starting '<' */ + cmd_tok_out_pipe = BIT( 9), /* token starting '>' */ + cmd_tok_out_shell = BIT(10), /* token starting '|' */ + cmd_tok_comment = BIT(14), /* token starting '!' or '#" */ } ; typedef enum cmd_token_type cmd_token_type_t ; @@ -522,11 +517,11 @@ typedef enum cmd_token_type cmd_token_type_t ; struct cmd_token { cmd_token_type_t type ; - qstring_t qs ; /* token string */ + usize tp ; /* location of token in the line */ bool term ; /* token has been '\0' terminated */ - usize tp ; /* location of token in the line */ + qstring_t qs ; /* token string */ elstring_t ot ; /* original token in the line */ } ; @@ -587,9 +582,9 @@ struct cmd_parsed usize elen ; /* effective length (less trailing spaces) */ usize tsp ; /* number of trailing spaces */ - uint num_tokens ; /* number of tokens parsed */ + uint num_tokens ; /* number of tokens parsed */ - token_vector_t tokens ; /* vector of token objects */ + token_vector tokens ; /* vector of token objects */ /* NB: the following are significant only if there is a command part * or a do part @@ -597,13 +592,14 @@ struct cmd_parsed struct cmd_command *cmd ; /* NULL if empty command or fails to parse */ node_type_t cnode ; /* node command is in */ + node_type_t nnode ; /* node to set if command succeeds */ - arg_vector_t args ; /* vector of arguments -- embedded */ + arg_vector args ; /* vector of arguments */ /* NB: the following are significant only if an error is returned */ - const char* emess ; /* parse error */ - usize eloc ; /* error location */ + qstring emess ; /* parse error */ + ssize eloc ; /* error location */ /* NB: the following are significant only if respective part is * present. @@ -634,8 +630,8 @@ struct cmd_parsed /* The following are used while filtering commands */ - vector_t cmd_v ; /* working vector -- embedded */ - vector_t item_v ; /* working vector -- embedded */ + vector cmd_v ; /* working vector */ + vector item_v ; /* working vector */ match_strength_t strongest ; match_type_t best_complete ; @@ -647,20 +643,65 @@ struct cmd_parsed enum { CMD_PARSED_INIT_ALL_ZEROS = (cmd_pipe_none == 0) - && TOKEN_VECTOR_INIT_ALL_ZEROS - && ARG_VECTOR_INIT_ALL_ZEROS } ; typedef struct cmd_parsed cmd_parsed_t[1] ; typedef struct cmd_parsed* cmd_parsed ; -/* Command dispatch options */ -enum cmd_queue_b +/*============================================================================== + * This is the stuff that defines the context in which commands are parsed, + * and then executed. + * + * The context lives in the cmd_exec. The CLI has a copy of the context, + * which it uses for the prompt and for command line help handling. + * + * Each time a new vin is pushed, the current context is copied to the current + * TOS (before the push). + * + * Easch time a vin is popped, the context is restored. + * + * Each time a vin or vout is pushed or popped the context the cmd_exec + * out_suppress and reflect flags must be updated. + */ +struct cmd_context { - cmd_no_queue = false, - cmd_queue_it = true, + /* The node between commands. */ + + node_type_t node ; /* updated on CMD_SUCCESS */ + + /* These properties affect the parsing of command lines. */ + + bool full_lex ; /* as required */ + + bool parse_execution ; /* parsing to execute */ + + bool parse_only ; /* do not execute */ + + bool parse_strict ; /* no incomplete keywords */ + bool parse_no_do ; /* no 'do' commands */ + bool parse_no_tree ; /* no tree walking */ + + bool can_auth_enable ; /* if required */ + bool can_enable ; /* no (further) password needed */ + + /* These properties affect the execution of parsed commands. */ + + bool reflect_enabled ; /* per the pipe */ + + /* Special for AUTH_ENABLE_NODE -- going from/to */ + + node_type_t onode ; /* VIEW_NODE or RESTRICTED_NODE */ + node_type_t tnode ; /* ENABLE_NODE or CONFIG_NODE */ + + /* The current directories. */ + + qpath dir_cd ; /* chdir directory */ + qpath dir_home ; /* "~/" directory */ + qpath dir_here ; /* "~./" directory */ } ; -typedef enum cmd_queue_b cmd_queue_b ; + +typedef struct cmd_context cmd_context_t[1] ; +typedef struct cmd_context* cmd_context ; /*============================================================================== * Prototypes @@ -668,21 +709,24 @@ typedef enum cmd_queue_b cmd_queue_b ; extern void cmd_compile(cmd_command cmd) ; extern void cmd_compile_check(cmd_command cmd) ; -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) ; +extern cmd_parsed cmd_parsed_new(void) ; +extern cmd_parsed cmd_parsed_free(cmd_parsed parsed) ; + +extern bool cmd_is_empty(qstring line) ; +extern void cmd_tokenize(cmd_parsed parsed, qstring line, bool full_lex) ; +extern qstring cmd_tokens_concat(cmd_parsed parsed, uint ti, uint nt) ; extern cmd_return_code_t cmd_parse_command(cmd_parsed parsed, - node_type_t node, cmd_parse_type_t type) ; + cmd_context context) ; 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) ; +extern cmd_return_code_t cmd_completion(cmd_parsed parsed, cmd_context context); extern void cmd_complete_keyword(cmd_parsed parsed, int* pre, int* rep, int* ins, int* mov) ; +extern void cmd_get_parse_error(vio_fifo ebuf, cmd_parsed parsed, uint indent) ; @@ -697,7 +741,6 @@ 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) ; @@ -722,84 +765,8 @@ extern void cmd_tokenise(cmd_parsed parsed, qstring line) ; //extern bool cmd_range_match (const char *range, const char *str) ; /*============================================================================== - * Inline Functions - */ - -Private cmd_token cmd_token_new(void) ; - -/*------------------------------------------------------------------------------ - * Get pointer to token value. - * - * Returns NULL if token NULL or no string. - */ -Inline char* -cmd_token_value(cmd_token t) -{ - return (t == NULL) ? NULL : qs_char_nn(t->qs) ; -} ; - -/*------------------------------------------------------------------------------ - * Get string value of given token. - * - * Returns an empty (not NULL) string if token NULL or no string. - */ -Inline char* -cmd_token_make_string(cmd_token t) -{ - if (t->term) - return qs_char_nn(t->qs) ; - else - { - t->term = true ; - return qs_make_string(t->qs) ; - } -} ; - -/*------------------------------------------------------------------------------ - * Get i'th token from given token vector -- zero origin - */ -Inline cmd_token -cmd_token_get(token_vector tv, vector_index_t i) -{ - return vector_get_item(tv->body, i) ; -} ; - -/*------------------------------------------------------------------------------ - * Set i'th token from given token vector -- zero origin + * Inlines */ -Inline void -cmd_token_set(token_vector tv, vector_index_t i, - cmd_token_type_t type, const char* p, usize len, usize tp) -{ - 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) ; -} ; - -/*------------------------------------------------------------------------------ - * Empty the body of the arg_vector object in cmd_parsed. - */ -Inline void -cmd_arg_vector_empty(cmd_parsed parsed) -{ - vector_set_length(parsed->args->body, 0) ; -} ; - -/*------------------------------------------------------------------------------ - * Empty the body of the arg_vector object in cmd_parsed. - */ -Inline void -cmd_arg_vector_push(cmd_parsed parsed, char* arg) -{ - vector_push_item(parsed->args->body, arg) ; -} ; /*------------------------------------------------------------------------------ * Get the body of the argument vector. diff --git a/lib/command_queue.c b/lib/command_queue.c index 0a09d950..f83e3291 100644 --- a/lib/command_queue.c +++ b/lib/command_queue.c @@ -43,7 +43,7 @@ * runs until the vin/vout stacks return to 0 -- the VTY_TERMINAL and * VTY_SHELL_SERVER. * - * There are further issues: + * There are further issues: fix to current state TODO * * 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 @@ -53,7 +53,7 @@ * 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. + * 3) while a VTY is in the command loop it is marked vio->cmd_running. TODO * * 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(). @@ -72,7 +72,7 @@ * 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: + * While a vty is vio->cmd_running, it must be in one of these states: TODO * * - on the vty_cli_nexus queue (or the combined queue) * - executing in the vty_cli_nexus (or the single "both" nexus) @@ -111,69 +111,54 @@ * Prototypes */ static void cq_enqueue(struct vty *vty, qpn_nexus dst, cmd_exec_state_t state, - cmd_return_code_t ret) ; + 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) ; +static void cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret) ; /*------------------------------------------------------------------------------ - * Enqueue vty for parse and execution of a command. + * Enqueue vty to enter the command loop -- must be exec_null * - * 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. + * Expects the vty start up process to have output some cheerful messages, + * which is treated as a dummy "login" command. So the loop is entered at + * "exec_done_cmd", which will push out the output, deal with the return code, + * and loop round to fetch the first command line (if required). */ extern void -cq_dispatch(vty vty, cmd_do_t to_do, qstring line) +cq_loop_enter(vty vty, cmd_return_code_t ret) { 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) ; + assert(vty->exec->state == exec_null) ; + + cq_enqueue(vty, vty_cli_nexus, exec_done_cmd, ret) ; } ; /*------------------------------------------------------------------------------ - * 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. + * Continue (resume) at hiatus -- must be exec_hiatus */ extern void -cq_go_fetch(vty vty) +cq_continue(vty vty, cmd_return_code_t ret) { VTY_ASSERT_CLI_THREAD() ; - vty->vio->cmd_running = true ; - cq_enqueue(vty, vty_cli_nexus, exec_fetch, CMD_SUCCESS) ; + assert(vty->exec->state == exec_hiatus) ; + + cq_enqueue(vty, vty_cli_nexus, exec_hiatus, ret) ; } ; /*------------------------------------------------------------------------------ * Enqueue vty for execution in given nexus or issue thread event. * - * Note that preserves the return code state. + * Will execute in the current exec->state, passing in the given return + * code. */ static void cq_enqueue(vty vty, qpn_nexus dst, cmd_exec_state_t state, - cmd_return_code_t ret) + cmd_return_code_t ret) { cmd_exec exec = vty->exec ; - assert(vty->vio->cmd_running) ; assert((dst == vty_cli_nexus) || (dst == vty_cmd_nexus)) ; exec->locus = dst ; @@ -200,7 +185,7 @@ cq_enqueue(vty vty, qpn_nexus dst, cmd_exec_state_t state, /*------------------------------------------------------------------------------ * Deal with command message -- in the qpthreads world. * - * Note that if the command is revoked.... + * Note that if the command is revoked.... TODO */ static void cq_action(mqueue_block mqb, mqb_flag_t flag) @@ -212,11 +197,12 @@ cq_action(mqueue_block mqb, mqb_flag_t flag) vty = mqb_get_arg0(mqb); assert(vty->exec->cq.mqb == mqb) ; - assert(vty->vio->cmd_running) ; if (flag == mqb_action) - return cq_process(vty) ; /* do not touch vty on way out */ + return cq_process(vty, vty->exec->state, vty->exec->ret) ; + /* do not touch vty on way out */ + /* Revoke action. */ mqb_free(mqb) ; vty->exec->cq.mqb = NULL ; } ; @@ -224,7 +210,7 @@ cq_action(mqueue_block mqb, mqb_flag_t flag) /*------------------------------------------------------------------------------ * Deal with command message -- in the legacy threads world. * - * Note that if the command is revoked.... + * Note that if the command is revoked.... TODO */ static int cq_thread(struct thread* thread) @@ -232,63 +218,64 @@ 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 */ - + cq_process(vty, vty->exec->state, vty->exec->ret) ; + /* do not touch vty on way out */ return 0 ; } ; /*------------------------------------------------------------------------------ * Process command(s) queued from VTY_TERMINAL or from VTY_SHELL_SERVER. * - * To get into the process loop, or to get back to it when more input has - * arrived a message is sent: + * This is essentially a coroutine, where the state is in the vty, noting that: + * + * vty->exec->state is the "return address" + * vty->exec->ret is the "return code" + * + * The command loop runs at the mqueue level in the qpnexus world, or is + * driven by "events" in the legacy threads world. The two ways into the + * loop are: * - * cli -> cli -- exec_parse -- when a command line has been gathered - * from input and is ready to be processed. + * cq_loop_enter() -- called once and once only to start the loop. * - * cli -> cli -- exec_fetch -- when was waiting for more input from an - * in_pipe of some sort. + * cq_continue() -- called when some I/O process has completed and + * the loop is in hiatus, waiting for I/O. * - * 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. + * The one way out of the loop is when it hits a hiatus, which is triggered + * by any operation not returning CMD_SUCCESS. In hiatus, vty_cmd_hiatus() + * is called and given the current return code to deal with. It will + * return: * - * 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_SUCCESS => can proceed to fetch the next command + * CMD_WAITING => must leave the command loop, waiting for I/O + * CMD_EOF => close the vty and leave command loop + * CMD_IO_ERROR => loop back and deal with the error * - * cmd -> cli -- exec_open_pipes - * cli -> cmd -- exec_execute - * cmd -> cli -- exec_complete + * The cq_loop_enter() and cq_continue() operations send a message (to the + * cli thread, in the mult-pthreaded world), whose action routine is to call + * cq_process(). * - * Note that this means that in the multi-pthread world, only one sort of - * message is sent to the vty_cmd_nexus. + * In the multi-pthreaded world, opening and closing files MUST be done in the + * cli thread. Most commands MUST be executed in the cmd thread. So the + * command loop is passed between the threads in a number of places. (To keep + * life simple, switches back to the cli thread if ends up waiting for I/O.) * * 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_process(vty vty) +cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret) { - cmd_exec exec = vty->exec ; - cmd_parsed parsed = exec->parsed ; - cmd_return_code_t ret = exec->ret ; + cmd_exec exec = vty->exec ; + cmd_parsed parsed = exec->parsed ; /* 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. + * Breaking out of the switch forces the exec state to exec_hiatus, + * with the current ret (which will switch to 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 @@ -296,79 +283,44 @@ cq_process(vty vty) */ while (1) { - switch(exec->state) + switch(state) { /*-------------------------------------------------------------------- * Should not get here in exec_null state. */ case exec_null: - zabort("exec->state == 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. + * If multi-threaded: may be in either thread. If vty_cmd_fetch_line() + * cannot, for any reason, return a command line, will return something + * other than CMD_SUCCESS -- which enters the exec_hiatus. */ 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 */ - } ; + break ; - if (exec->to_do != cmd_do_command) + if (exec->action->to_do != cmd_do_command) { - exec->state = exec_special ; + state = exec_special ; continue ; } ; - exec->state = exec_parse ; - /* continue by falling through */ + fall_through ; /* with ret == CMD_SUCCESS */ /*-------------------------------------------------------------------- * 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. + * If multi-threaded: may be in either thread. */ - case exec_parse: - cmd_tokenise(parsed, exec->line) ; - ret = cmd_parse_command(parsed, vty->node, - exec->parse_type | cmd_parse_execution) ; + cmd_tokenize(parsed, exec->action->line, exec->context->full_lex) ; + + ret = cmd_parse_command(parsed, exec->context) ; if (ret != CMD_SUCCESS) { if (ret != CMD_EMPTY) @@ -381,15 +333,20 @@ cq_process(vty vty) */ ret = CMD_SUCCESS ; - exec->state = exec_success ; + state = exec_done_cmd ; continue ; } ; - /* reflection now -- output always succeeds */ - if (exec->reflect_enabled) - vty_cmd_reflect_line(vty) ; + /* reflection now */ + if (exec->reflect) + { + ret = vty_cmd_reflect_line(vty) ; + + if ((ret != CMD_SUCCESS) && (ret != CMD_WAITING)) + break ; + } ; - /* continue by falling through */ + fall_through ; /*-------------------------------------------------------------------- * Pipe work if any @@ -402,8 +359,6 @@ cq_process(vty vty) 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) ; @@ -414,11 +369,10 @@ cq_process(vty vty) break ; /* quit if open fails */ } ; - exec->state = exec_execute ; - /* continue by falling through */ + fall_through ; /* with ret == CMD_SUCCESS */ /*-------------------------------------------------------------------- - * Execute command in hand + * Execute command in hand (if any) * * 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 @@ -428,8 +382,7 @@ cq_process(vty vty) 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))) + if ((exec->locus != vty_cmd_nexus) && (!cmd_is_direct(parsed))) return cq_enqueue(vty, vty_cmd_nexus, exec_execute, ret) ; /* Standard command handling */ @@ -449,59 +402,97 @@ cq_process(vty vty) realtime = thread_consumed_time(&after, &before, &cputime) ; if (realtime > CONSUMED_TIME_CHECK) /* Warn about CPU hog that must be fixed. */ - uzlog(NULL, LOG_WARNING, + zlog(NULL, LOG_WARNING, "SLOW COMMAND: command took %lums (cpu time %lums): %s", realtime/1000, cputime/1000, - qs_make_string(exec->line)) ; + qs_make_string(exec->action->line)) ; } ; #endif /* CONSUMED_TIME_CHECK */ - - if (ret != CMD_SUCCESS) - break ; /* stop */ } ; - exec->state = exec_success ; - /* continue by falling through */ + fall_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. + * Command has completed -- if successful, push output and loop back + * to fetch another command. * - * Output cannot block, so this always succeeds. - * - * Then loop back to fetch another command line, if can. + * Break if not successful, or push fails or must wait. * * 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) ; + case exec_done_cmd: + if (ret == CMD_SUCCESS) + ret = vty_cmd_success(vty) ; - vty_cmd_success(vty) ; + if ((ret != CMD_SUCCESS) && (ret != CMD_WAITING)) + break ; /* stop */ - exec->state = exec_fetch ; + state = exec_fetch ; continue ; /*-------------------------------------------------------------------- - * End of the command loop ! - * - * If multi-threaded: must return to the vty_cli_nexus. + * Deal with the "special" commands + */ + case exec_special: + if (exec->locus != vty_cli_nexus) + return cq_enqueue(vty, vty_cli_nexus, exec_special, ret) ; + + ret = vty_cmd_special(vty) ; + if (ret != CMD_SUCCESS) + break ; + + state = exec_done_cmd ; + continue ; + + /*-------------------------------------------------------------------- + * Hiatus state -- some return code to be dealt with ! */ - case exec_complete: + case exec_hiatus: if (exec->locus != vty_cli_nexus) - break ; /* Will send back to the cli */ + return cq_enqueue(vty, vty_cli_nexus, exec_hiatus, ret) ; + + while (1) + { + /* Let vty_cmd_hiatus() deal with the return code, and adjust + * the stack as required. + */ + ret = vty_cmd_hiatus(vty, ret) ; + + if (ret == CMD_SUCCESS) + break ; - /* Now in the vty_cli_nexus */ + if (ret == CMD_WAITING) + { + exec->state = exec_hiatus ; + return ; /* <<< DONE, pro tem */ + } ; - exec->state = exec_null ; /* all done ! */ + if (ret == CMD_EOF) + { + exec->state = exec_stopped ; + vty_cmd_loop_exit(vty) ; - vty_cmd_loop_exit(vty, ret) ; /* clears vio->cmd_running */ + return ; /* <<< DONE, permanently */ + } ; - return ; /* <<<< Finally finished ! */ + if (ret == CMD_IO_ERROR) + continue ; + + zabort("invalid return from vty_cmd_hiatus()") ; + } ; + + state = exec_fetch ; + continue ; /* can fetch, again */ + + /*-------------------------------------------------------------------- + * Should not get here in exec_stopped state. + */ + case exec_stopped: + zabort("exec state == exec_exit") ; + break ; /*---------------------------------------------------------------------- * Unknown exec->state ! @@ -511,22 +502,9 @@ cq_process(vty vty) 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 ; - } ; + /* Have broken out of the switch() => exec_hiatus */ + state = exec_hiatus ; + } ; } ; /*------------------------------------------------------------------------------ @@ -537,45 +515,41 @@ cq_process(vty vty) * 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. - * - * 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. + * In the single-threaded world (including legacy threads), messages or events + * are used to enter or resume the command loop. If a message or event can + * be revoked, then the command loop is effectively stopped. * - * 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. + * In the multi-threaded world, messages are used to enter or resume the + * command loop, and to transfer it to and from the cli and cmd threads. If + * a message can be revoked, the command loop is effectively stopped. * * Note that the revoke does not affect any vty_cli_nexus messages associated * with the vty_io_basic operations. + * + * Returns: true <=> have revoked a pending message/event */ -extern void +extern bool cq_revoke(vty vty) { int ret ; - VTY_ASSERT_CLI_THREAD() ; + VTY_ASSERT_CLI_THREAD_LOCKED() ; if (vty_nexus) { - ret = mqueue_revoke(vty_cmd_nexus->queue, vty, 1) ; + ret = mqueue_revoke(vty_cli_nexus->queue, vty, 1) ; - if ((ret == 0) && (vty_cli_nexus != vty_cmd_nexus)) - ret = mqueue_revoke(vty_cli_nexus->queue, vty, 1) ; + if ((ret == 0) && vty_multi_nexus) + ret = mqueue_revoke(vty_cmd_nexus->queue, vty, 1) ; } else ret = thread_cancel_event(vty_master, vty) ; + /* If revoked message/event, the command loop is stopped. */ if (ret != 0) - { - vty->exec->state = exec_null ; - vty_cmd_loop_exit(vty, vty->exec->ret) ; - } ; + vty->exec->state = exec_stopped ; + + return (ret != 0) ; } ; diff --git a/lib/command_queue.h b/lib/command_queue.h index 0966bd68..0f4863d6 100644 --- a/lib/command_queue.h +++ b/lib/command_queue.h @@ -26,8 +26,8 @@ #include "command_execute.h" #include "qpnexus.h" -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) ; +extern void cq_loop_enter(vty vty, cmd_return_code_t ret) ; +extern void cq_continue(vty vty, cmd_return_code_t ret) ; +extern bool cq_revoke(vty vty) ; #endif /* COMMAND_QUEUE_H_ */ @@ -532,11 +532,12 @@ if_sunwzebra_get (const char *name, size_t nlen) } #endif /* SUNOS_5 */ -DEFUN (interface, - interface_cmd, - "interface IFNAME", - "Select an interface to configure\n" - "Interface's name\n") +DEFUN_ATTR (interface, + interface_cmd, + "interface IFNAME", + "Select an interface to configure\n" + "Interface's name\n", + CMD_ATTR_NODE + INTERFACE_NODE) { struct interface *ifp; size_t sl; diff --git a/lib/keychain.c b/lib/keychain.c index 57ccf7cc..a7929751 100644 --- a/lib/keychain.c +++ b/lib/keychain.c @@ -227,12 +227,13 @@ key_delete (struct keychain *keychain, struct key *key) key_free (key); } -DEFUN (key_chain, - key_chain_cmd, - "key chain WORD", - "Authentication key management\n" - "Key-chain management\n" - "Key-chain name\n") +DEFUN_ATTR (key_chain, + key_chain_cmd, + "key chain WORD", + "Authentication key management\n" + "Key-chain management\n" + "Key-chain name\n", + CMD_ATTR_NODE + KEYCHAIN_NODE) { struct keychain *keychain; @@ -266,11 +267,12 @@ DEFUN (no_key_chain, return CMD_SUCCESS; } -DEFUN (key, - key_cmd, - "key <0-2147483647>", - "Configure a key\n" - "Key identifier number\n") +DEFUN_ATTR (key, + key_cmd, + "key <0-2147483647>", + "Configure a key\n" + "Key identifier number\n", + CMD_ATTR_NODE + KEYCHAIN_KEY_NODE) { struct keychain *keychain; struct key *key; @@ -286,12 +288,13 @@ DEFUN (key, return CMD_SUCCESS; } -DEFUN (no_key, - no_key_cmd, - "no key <0-2147483647>", - NO_STR - "Delete a key\n" - "Key identifier number\n") +DEFUN_ATTR (no_key, + no_key_cmd, + "no key <0-2147483647>", + NO_STR + "Delete a key\n" + "Key identifier number\n", + CMD_ATTR_NODE + KEYCHAIN_NODE) { struct keychain *keychain; struct key *key; diff --git a/lib/keystroke.c b/lib/keystroke.c index 13b79e39..8325b8f8 100644 --- a/lib/keystroke.c +++ b/lib/keystroke.c @@ -262,13 +262,15 @@ enum stream_state kst_iac_option, /* waiting for option (just seen IAC X) */ kst_iac_sub, /* waiting for IAC SE */ } ; +typedef enum stream_state stream_state_t ; struct keystroke_state { - enum stream_state state ; - unsigned len ; - uint8_t raw[keystroke_max_len] ; + stream_state_t state ; + uint len ; + uint8_t raw[keystroke_max_len] ; } ; +typedef struct keystroke_state keystroke_state_t ; struct keystroke_stream { @@ -280,14 +282,15 @@ struct keystroke_stream uint8_t CSI ; /* CSI character value (if any) */ bool eof_met ; /* nothing more to come */ + bool timed_out ; /* eof because timed out */ bool steal_this ; /* steal current keystroke when complete */ bool iac ; /* last character was an IAC */ - struct keystroke_state in ; /* current keystroke being collected */ + keystroke_state_t in ; /* current keystroke being collected */ - struct keystroke_state pushed_in ; + keystroke_state_t pushed_in ; /* keystroke interrupted by IAC */ } ; @@ -318,7 +321,7 @@ static void keystroke_steal_esc(keystroke steal, keystroke_stream stream, uint8_t u) ; static void keystroke_steal_csi(keystroke steal, keystroke_stream stream, uint8_t u) ; -static void keystroke_put(keystroke_stream stream, enum keystroke_type type, +static void keystroke_put(keystroke_stream stream, keystroke_compound_t type, bool broken, uint8_t* bytes, int len) ; /*============================================================================== @@ -354,12 +357,13 @@ keystroke_stream_new(uint8_t csi_char, keystroke_callback* iac_callback, * iac_callback = NULL -- none * iac_callback_context = NULL -- none * - * eof_met = false -- no EOF yet - * steal = false -- no stealing set - * iac = false -- last character was not an IAC + * eof_met = false -- no EOF yet + * timed_out = false -- not timed out yet + * steal = false -- no stealing set + * iac = false -- last character was not an IAC * - * in.state = kst_null - * in.len = 0 -- nothing in the buffer + * in.state = kst_null + * in.len = 0 -- nothing in the buffer * * pushed_in.state ) ditto * pushed_in.len ) @@ -429,7 +433,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) ; } ; /*------------------------------------------------------------------------------ @@ -437,14 +441,15 @@ keystroke_stream_eof(keystroke_stream stream) * * * discard contents of the stream, including any partial keystroke. * - * * set the stream "eof_met". + * * set the stream "eof_met" with or without timed_out */ extern void -keystroke_stream_set_eof(keystroke_stream stream) +keystroke_stream_set_eof(keystroke_stream stream, bool timed_out) { vio_fifo_reset(stream->fifo, keep_it) ; stream->eof_met = true ; /* essential information */ + stream->timed_out = timed_out ; /* variant of eof */ stream->steal_this = false ; /* keep tidy */ stream->iac = false ; @@ -473,7 +478,7 @@ keystroke_stream_set_eof(keystroke_stream stream) * If steal != NULL the keystroke will be set to the stolen keystroke. That * will be type ks_null if nothing was available, and may be knull_eof. * - * Note that never steals broken or truncated keystrokes. + * Note that never steals broken or truncated keystrokes, or IAC. * * Passing len == 0 and ptr == NULL signals EOF to the keystroke_stream. * @@ -496,6 +501,7 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len, if ((len == 0) && (ptr == NULL)) { stream->eof_met = true ; + stream->timed_out = false ; stream->steal_this = false ; /* Loop to deal with any pending IAC and partial keystroke. @@ -967,8 +973,9 @@ inline static bool keystroke_set_null(keystroke_stream stream, keystroke stroke) { stroke->type = ks_null ; - stroke->value = stream->eof_met ? knull_eof : knull_not_eof ; - + stroke->value = stream->eof_met ? (stream->timed_out ? knull_timed_out + : knull_eof) + : knull_not_eof ; stroke->flags = 0 ; stroke->len = 0 ; @@ -1013,7 +1020,7 @@ keystroke_add_raw(keystroke_stream stream, uint8_t u) static void keystroke_put_char(keystroke_stream stream, uint32_t u) { - if (u < 0x80) + if (u < kf_compound) vio_fifo_put_byte(stream->fifo, (uint8_t)u) ; else { @@ -1029,7 +1036,7 @@ keystroke_put_char(keystroke_stream stream, uint32_t u) } while (u != 0) ; - keystroke_put(stream, ks_char, 0, p, (buf + 4) - p) ; + keystroke_put(stream, ks_char, false, p, (buf + 4) - p) ; } ; } ; @@ -1127,12 +1134,12 @@ keystroke_clear_iac(keystroke_stream stream) } ; /*------------------------------------------------------------------------------ - * Store <first> <len> [<bytes>] + * Store <first> <len> [<bytes>] -- compound keystroke. * * If len == 0, bytes may be NULL */ static void -keystroke_put(keystroke_stream stream, enum keystroke_type type, bool broken, +keystroke_put(keystroke_stream stream, keystroke_compound_t type, bool broken, uint8_t* bytes, int len) { if (len > keystroke_max_len) diff --git a/lib/keystroke.h b/lib/keystroke.h index 67c6cd0f..70aa120b 100644 --- a/lib/keystroke.h +++ b/lib/keystroke.h @@ -47,13 +47,14 @@ enum keystroke_type ks_type_reserved = 0x0F, } ; -typedef enum keystroke_type keystroke_type ; +typedef enum keystroke_type keystroke_type_t ; CONFIRM(ks_type_count <= ks_type_reserved) ; enum keystroke_null { knull_not_eof, - knull_eof + knull_eof, + knull_timed_out, }; enum keystroke_flags @@ -69,23 +70,30 @@ enum keystroke_flags kf_type_mask = 0x0F, /* extraction of type */ } ; -typedef enum keystroke_type keystroke_flags ; +typedef enum keystroke_type keystroke_flags_t ; -CONFIRM(ks_type_reserved == (keystroke_type)kf_type_mask) ; +/* The keystroke type and flags are designed so that they can be packed + * together as a single byte, the first of a "compound character". + */ +typedef uint8_t keystroke_compound_t ; + +CONFIRM(ks_type_reserved == (keystroke_type_t)kf_type_mask) ; +CONFIRM((kf_compound | kf_flag_mask | kf_type_mask) <= 0xFF) ; typedef struct keystroke* keystroke ; typedef struct keystroke_stream* keystroke_stream ; struct keystroke { - enum keystroke_type type ; - uint8_t flags ; /* the kf_flag_mask flags */ + keystroke_type_t type ; + keystroke_flags_t flags ; /* just the kf_flag_mask bits */ uint32_t value ; unsigned len ; uint8_t buf[keystroke_max_len] ; } ; +typedef struct keystroke keystroke_t[1] ; #define keystroke_iac_callback_args void* context, keystroke stroke typedef bool (keystroke_callback)(keystroke_iac_callback_args) ; @@ -171,7 +179,7 @@ keystroke_stream_new(uint8_t csi_char, keystroke_callback* iac_callback, void* iac_callback_context) ; extern void -keystroke_stream_set_eof(keystroke_stream stream) ; +keystroke_stream_set_eof(keystroke_stream stream, bool timed_out) ; extern keystroke_stream keystroke_stream_free(keystroke_stream stream) ; @@ -25,13 +25,14 @@ #include <zebra.h> #include "log.h" -#include "vty.h" +#include "log_local.h" +#include "vty_log.h" #include "memory.h" -#include "command_local.h" + #ifndef SUNOS_5 #include <sys/un.h> #endif -/* for printstack on solaris */ +/* for printstack on solaris */ #ifdef HAVE_UCONTEXT_H #include <ucontext.h> #endif @@ -39,18 +40,11 @@ #include "qfstring.h" #include "sigevent.h" -/* log is protected by the same mutext as vty, see comments in vty.c */ - -/* prototypes */ -static int uzlog_reset_file (struct zlog *zl); -static void zlog_abort (const char *mess) __attribute__ ((noreturn)); -static void vzlog (struct zlog *zl, int priority, const char *format, va_list args); +/*------------------------------------------------------------------------------ + * Prototypes + */ +static void zlog_abort (const char *mess) __attribute__ ((noreturn)) ; static void uzlog_backtrace(int priority); -static void uvzlog (struct zlog *zl, int priority, const char *format, va_list args); - -static int logfile_fd = -1; /* Used in signal handler. */ - -struct zlog *zlog_default = NULL; const char *zlog_proto_names[] = { @@ -80,23 +74,310 @@ const char *zlog_priority[] = NULL, }; +/*------------------------------------------------------------------------------ + * Static variables + */ +struct zlog* zlog_default = NULL; + +struct zlog* zlog_list = NULL ; + +static volatile sig_atomic_t max_maxlvl = INT_MAX ; + +qpt_mutex_t log_mutex ; + +int log_lock_count = 0 ; +int log_assert_fail = 0 ; + /*============================================================================== - * Time stamp handling -- gettimeofday(), localtime() and strftime(). + * Log locking and relationship with VTY. * - * Maintains a cached form of the current time (under the vty/log mutex), so - * that can avoid multiple calls of localtime() and strftime() per second. + * For pthreads purposes there is a LOG_LOCK() mutex. * - * The value from gettimeofday() is in micro-seconds. Can provide timestamp - * with any number of decimal digits, but at most 6 will be significant. + * To support interaction with the VTY: + * + * a. setting logging configuration. + * + * b. issuing log messages within the VTY + * + * the LOG_LOCK() may be collected while VTY_LOCK() -- BUT NOT vice versa ! + * This is not too difficult, because the logging has no need to call VTY + * functions... except for "vty monitor" VTY, so, for that purpose: + * + * a. for each monitor VTY there is a "monitor_fifo", into which log messages + * are place if required. This buffer and some related flags must be + * handled under the LOG_LOCK(). + * + * The vty_log() function takes care of this, and takes care of prompting + * the vty(s) to output the contents of the fifo. + * + * This will be called under LOG_LOCK(). It will NOT VTY_LOCK(). It + * will return promptly. + * + * b. once any logging has been handed to the VTY, the logging can forget + * about it. + * + * c. the interface from logging to VTY is in vty_log.h + * + * The logging system supports some logging once a signal (in particular, + * the hard exceptions, such as SIGSEGV) has gone off and the system is being + * brought to a dead stop. A separate mechanism is provided for outputting + * directly to any vty monitor VTY -- at a file descriptor level ! + * + * The VTY may call logging functions in the same was as any other part of the + * system. When interacting with configuration etc, may need to acquire the + * LOG_LOCK, etc. The interface from VTY to logging is in log_local.h. */ +/*------------------------------------------------------------------------------ + * Further initialisation for qpthreads. + * + * This is done during "second stage" initialisation, when all nexuses have + * been set up and the qpthread_enabled state established. + * + * Initialise mutex. + * + * NB: may be called once and once only. + */ +extern void +log_init_r(void) +{ + qpt_mutex_init(log_mutex, qpt_mutex_recursive); +} ; + +/*------------------------------------------------------------------------------ + * Close down for qpthreads. + */ +extern void +log_finish(void) +{ + qpt_mutex_destroy(log_mutex, 0); +} ; + +/*============================================================================== + * The actual logging function and creation of the logging lines. + * + */ +#define TIMESTAMP_FORM "%Y/%m/%d %H:%M:%S" + +/* Structure used to hold line for log output -- so that need be generated + * just once even if output to multiple destinations. + * + * Note that the buffer length is a hard limit (including terminating "\n") + * Do not wish to malloc any larger buffer while logging. + */ +enum { logline_buffer_len = 1008 } ; + +struct logline { + size_t len ; /* length including either '\r''\n' or '\n' */ + + char line[logline_buffer_len]; /* buffer */ +} ; + +typedef struct logline logline_t[1] ; +typedef struct logline* logline ; + +static void uvzlog_line(logline ll, struct zlog *zl, int priority, + const char *format, va_list va) ; static void uquagga_timestamp(qf_str qfs, int timestamp_precision) ; +/*============================================================================== + * The main logging function. + */ +extern void +zlog (struct zlog *zl, int priority, const char *format, ...) +{ + logline_t ll ; + + /* Decide whether any logging at all is required. + * + * NB: the max_maxlvl is established every time any change is made to + * logging facilities. Those changes are made under LOG_LOCK(), so + * only one thread will write to max_max_lvl at any time, and that + * will be consistent with the state of all known logging at the + * time. + * + * The max_maxlvl is sig_atomic_t and volatile. So, we assume that: + * + * a) writing to max_maxlvl is atomic wrt all forms of interrupt. + * So the variable cannot exist in a partly written state (with, + * say, some bytes of the old value and some of the new). + * + * b) reading max_maxlvl will either collect the state before some + * change to the logging levels, or after. + * + * If passes the initial test, immediately acquires the LOG_LOCK(). + * + * So, if the logging facilities are being changed, then: + * + * a) if the level is about to be increased, so the current + * priority would pass, then that change is just too late + * for this particular logging operation. + * + * b) if the level is about to be reduced, will get past the + * initial test, but will fail the later tests, under the + * LOG_LOCK(). + * + * NB: max_maxlvl is statically initialised to INT_MAX ! + */ + if (priority > max_maxlvl) + return ; + + /* Decide where we are logging to. */ + + LOG_LOCK() ; + + if (zl == NULL) + { + zl = zlog_default ; + + if (zl == NULL) + { + /* Have to get up very early in the morning to get to here, because + * zlog_default should be initialised -- by openzlog() -- very early + * indeed. + * + * So... "log" to stderr. + */ + va_list va; + va_start(va, format); + uvzlog_line(ll, zl, priority, format, va) ; + va_end (va); + + write(fileno(stderr), ll->line, ll->len) ; + + return ; + } ; + } ; + + /* Step through the logging destinations and log as required. */ + + ll->len = 0 ; /* Nothing generated, yet */ + + /* Syslog output */ + if (priority <= zl->emaxlvl[ZLOG_DEST_SYSLOG]) + { + va_list va; + va_start(va, format); + vsyslog (priority|zlog_default->facility, format, va); + va_end(va); + } + + /* File output. */ + if (priority <= zl->emaxlvl[ZLOG_DEST_FILE]) + { + if (ll->len == 0) + { + va_list va; + va_start(va, format); + uvzlog_line(ll, zl, priority, format, va) ; + va_end (va); + } ; + write(zl->file_fd, ll->line, ll->len) ; + } + + /* stdout output. */ + if (priority <= zl->emaxlvl[ZLOG_DEST_STDOUT]) + { + if (ll->len == 0) + { + va_list va; + va_start(va, format); + uvzlog_line(ll, zl, priority, format, va) ; + va_end (va); + } ; + write(zl->stdout_fd, ll->line, ll->len) ; + } + + /* Terminal monitor. */ + if (priority <= zl->emaxlvl[ZLOG_DEST_MONITOR]) + { + if (ll->len == 0) + { + va_list va; + va_start(va, format); + uvzlog_line(ll, zl, priority, format, va) ; + va_end (va); + } ; + vty_log(priority, ll->line, ll->len - 1) ; /* less the '\n' */ + } ; + + LOG_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * Preparation of line to send to logging: file, stdout or "monitor" terminals. + */ +static void +uvzlog_line(logline ll, struct zlog *zl, int priority, + const char *format, va_list va) +{ + const char* q ; + qf_str_t qfs ; + + qfs_init(qfs, ll->line, sizeof(ll->line) - 1) ; /* leave space for '\n' */ + /* "<time stamp>" */ + uquagga_timestamp(qfs, (zl != NULL) ? zl->timestamp_precision : 0) ; + + qfs_append_n(qfs, " ", 1) ; + + /* "<priority>: " if required */ + if ((zl != NULL) && zl->record_priority) + { + qfs_append(qfs, zlog_priority[priority]) ; + qfs_append(qfs, ": ") ; + } ; + + /* "<protocol>: " or "unknown: " */ + if (zl != NULL) + q = zlog_proto_names[zl->protocol] ; + else + q = "unknown" ; + + qfs_append(qfs, q) ; + qfs_append(qfs, ": ") ; + + /* Now the log line itself (uses a *copy* of the va_list) */ + qfs_vprintf(qfs, format, va) ; + + /* Set pointer to where the '\n' is going */ + qfs_append_n(qfs, "\n", 1) ; + ll->len = qfs_len(qfs) ; +} ; + /*------------------------------------------------------------------------------ * Fill buffer with current time, to given number of decimal digits. * * If given buffer is too small, provides as many characters as possible. + + * Time stamp handling -- gettimeofday(), localtime() and strftime(). + * + * Maintains a cached form of the current time (under the vty/log mutex), so + * that can avoid multiple calls of localtime() and strftime() per second. + * + * The value from gettimeofday() is in micro-seconds. Can provide timestamp + * with any number of decimal digits, but at most 6 will be significant. + * + * Puts a current timestamp in buf and returns the number of characters + * written (not including the terminating NUL). The purpose of + * this function is to avoid calls to localtime appearing all over the code. + * It caches the most recent localtime result and can therefore + * avoid multiple calls within the same second. * + * The buflen MUST be > 1 and the buffer address MUST NOT be NULL. + * + * If buflen is too small, writes buflen-1 characters followed by '\0'. + * + * Time stamp is rendered in the form: %Y/%m/%d %H:%M:%S + * + * This has a fixed length (leading zeros are included) of 19 characters + * (unless this code is still in use beyond the year 9999 !) + * + * Which may be followed by "." and a number of decimal digits, usually 1..6. + * + * So the maximum time stamp is 19 + 1 + 6 = 26. Adding the trailing '\n', and + * rounding up for good measure -- buffer size = 32. + + * Returns: number of characters in buffer, not including trailing '\0'. * * NB: does no rounding. @@ -108,18 +389,21 @@ quagga_timestamp(int timestamp_precision, char *buf, size_t buflen) { qf_str_t qfs ; - VTY_LOCK() ; + LOG_LOCK() ; - qfs_init(&qfs, buf, buflen) ; - uquagga_timestamp(&qfs, timestamp_precision) ; + qfs_init(qfs, buf, buflen) ; + uquagga_timestamp(qfs, timestamp_precision) ; - VTY_UNLOCK() ; - return qfs_len(&qfs) ; + LOG_UNLOCK() ; + return qfs_len(qfs) ; } /*------------------------------------------------------------------------------ * unprotected version for when mutex already held */ + +// used in uvzlog_line + static void uquagga_timestamp(qf_str qfs, int timestamp_precision) { @@ -134,7 +418,7 @@ uquagga_timestamp(qf_str qfs, int timestamp_precision) /* would it be sufficient to use global 'recent_time' here? I fear not... */ gettimeofday(&clock, NULL); - /* first, we update the cache if the time has changed */ + /* first, we update the cache if the seconds time has changed */ if (cache.last != clock.tv_sec) { struct tm tm; @@ -173,137 +457,54 @@ uquagga_timestamp(qf_str qfs, int timestamp_precision) } ; /*============================================================================== - * va_list version of zlog + * */ -static void -vzlog (struct zlog *zl, int priority, const char *format, va_list args) + +void +_zlog_assert_failed (const char *assertion, const char *file, + unsigned int line, const char *function) { - VTY_LOCK() ; - uvzlog(zl, priority, format, args); - VTY_UNLOCK() ; + const static size_t buff_size = 1024; + char buff[buff_size]; + snprintf(buff, buff_size, + "Assertion `%s' failed in file %s, line %u, function %s", + assertion, file, line, (function ? function : "?")); + zlog_abort(buff); } -/* va_list version of zlog. Unprotected assumes mutex already held*/ -static void -uvzlog (struct zlog *zl, int priority, const char *format, va_list va) +/* Abort with message */ +void +_zlog_abort_mess (const char *mess, const char *file, + unsigned int line, const char *function) { - struct logline ll ; /* prepares line for output, here */ - - VTY_ASSERT_LOCKED() ; - - ll.p_nl = NULL ; /* Nothing generated, yet */ - - /* If zlog is not specified, use default one. */ - if (zl == NULL) - zl = zlog_default ; - - /* When zlog_default is also NULL, use stderr for logging. */ - if (zl == NULL) - { - uvzlog_line(&ll, zl, priority, format, va, llt_lf) ; - write(fileno(stderr), ll.line, ll.len) ; - } - else - { - /* Syslog output */ - if (priority <= zl->maxlvl[ZLOG_DEST_SYSLOG]) - { - va_list ac; - va_copy(ac, va); - vsyslog (priority|zlog_default->facility, format, ac); - va_end(ac); - } - - /* File output. */ - if ((priority <= zl->maxlvl[ZLOG_DEST_FILE]) && zl->fp) - { - uvzlog_line(&ll, zl, priority, format, va, llt_lf) ; - write(fileno(zl->fp), ll.line, ll.len) ; - } - - /* stdout output. */ - if (priority <= zl->maxlvl[ZLOG_DEST_STDOUT]) - { - uvzlog_line(&ll, zl, priority, format, va, llt_lf) ; - write(fileno(zl->fp), ll.line, ll.len) ; - } - - /* Terminal monitor. */ - if (priority <= zl->maxlvl[ZLOG_DEST_MONITOR]) - uty_log(&ll, zl, priority, format, va) ; - } + const static size_t buff_size = 1024; + char buff[buff_size]; + snprintf(buff, buff_size, "%s, in file %s, line %u, function %s", + mess, file, line, (function ? function : "?")); + zlog_abort(buff); } -/*------------------------------------------------------------------------------ - * Preparation of line to send to logging: file, stdout or "monitor" terminals. - * - * Takes copy of va_list before using it, so the va_list is unchanged. - */ -extern void -uvzlog_line(struct logline* ll, struct zlog *zl, int priority, - const char *format, va_list va, enum ll_term term) +/* Abort with message and errno and strerror() thereof */ +void +_zlog_abort_errno (const char *mess, const char *file, + unsigned int line, const char *function) { - char* p ; - const char* q ; - - p = ll->p_nl ; - - if (p != NULL) - { - /* we have the line -- just need to worry about the crlf state */ - if (term == ll->term) - return ; /* exit here if all set */ - } - else - { - /* must construct the line */ - qf_str_t qfs ; - va_list vac ; - - qfs_init(&qfs, ll->line, sizeof(ll->line) - 2) ; - /* leave space for '\n' or '\r''\n' */ - /* "<time stamp>" */ - uquagga_timestamp(&qfs, (zl != NULL) ? zl->timestamp_precision : 0) ; - - qfs_append_n(&qfs, " ", 1) ; - - /* "<priority>: " if required */ - if ((zl != NULL) && (zl->record_priority)) - { - qfs_append(&qfs, zlog_priority[priority]) ; - qfs_append(&qfs, ": ") ; - } ; - - /* "<protocol>: " or "unknown: " */ - if (zl != NULL) - q = zlog_proto_names[zl->protocol] ; - else - q = "unknown" ; - - qfs_append(&qfs, q) ; - qfs_append(&qfs, ": ") ; - - /* Now the log line itself */ - va_copy(vac, va); - qfs_vprintf(&qfs, format, vac) ; - va_end(vac); - - /* Set pointer to where the '\0' is. */ - p = ll->p_nl = qfs_ptr(&qfs) ; - } ; - - /* finish off with '\r''\n''\0' or '\n''\0' as required */ - if (term == llt_crlf) - *p++ = '\r' ; - - if (term != llt_nul) - *p++ = '\n' ; - - *p = '\0' ; + _zlog_abort_err(mess, errno, file, line, function); +} - ll->len = p - ll->line ; - ll->term = term ; -} ; +/* Abort with message and given error and strerror() thereof */ +void +_zlog_abort_err (const char *mess, int err, const char *file, + unsigned int line, const char *function) +{ + const static size_t buff_size = 1024; + char buff[buff_size]; + snprintf(buff, buff_size, + "%s, in file %s, line %u, function %s, %s", + mess, file, line, (function ? function : "?"), + errtoa(err, 0).str); + zlog_abort(buff); +} /*============================================================================*/ @@ -444,18 +645,18 @@ open_crashlog(void) #undef CRASHLOG_PREFIX } -/* Note: the goal here is to use only async-signal-safe functions. */ -void -zlog_signal(int signo, const char *action -#ifdef SA_SIGINFO - , siginfo_t *siginfo, void *program_counter -#endif - ) +/*------------------------------------------------------------------------------ + * Note: the goal here is to use only async-signal-safe functions. + */ +extern void +zlog_signal(int signo, const char *action, siginfo_t *siginfo, + void *program_counter) { time_t now; char buf[sizeof("DEFAULT: Received signal S at T (si_addr 0xP, PC 0xP); aborting...")+100]; char *s = buf; char *msgstart = buf; + int file_fd ; #define LOC s,buf+sizeof(buf)-s time(&now); @@ -489,10 +690,16 @@ zlog_signal(int signo, const char *action /* N.B. implicit priority is most severe */ #define PRI LOG_CRIT -#define DUMP(FD) write(FD, buf, s-buf); +#define DUMP(FD) write(FD, buf, s - buf); + /* If no file logging configured, try to write to fallback log file. */ - if ((logfile_fd >= 0) || ((logfile_fd = open_crashlog()) >= 0)) - DUMP(logfile_fd) + file_fd = (zlog_default != NULL) ? zlog_default->file_fd : -1 ; + if (file_fd < 0) + file_fd = open_crashlog() ; + + if (file_fd >= 0) + DUMP(file_fd) + if (!zlog_default) DUMP(STDERR_FILENO) else @@ -502,7 +709,7 @@ zlog_signal(int signo, const char *action /* Remove trailing '\n' for monitor and syslog */ *--s = '\0'; if (PRI <= zlog_default->maxlvl[ZLOG_DEST_MONITOR]) - vty_log_fixed(buf,s-buf); + vty_log_fixed(buf, s - buf); if (PRI <= zlog_default->maxlvl[ZLOG_DEST_SYSLOG]) syslog_sigsafe(PRI|zlog_default->facility,msgstart,s-msgstart); } @@ -537,6 +744,7 @@ zlog_backtrace_sigsafe(int priority, void *program_counter) int size; char buf[100]; char *s, **bt = NULL; + int file_fd ; #define LOC s,buf+sizeof(buf)-s #ifdef HAVE_GLIBC_BACKTRACE @@ -567,8 +775,13 @@ zlog_backtrace_sigsafe(int priority, void *program_counter) s = num_append(LOC,size); s = str_append(LOC," stack frames:\n"); - if ((logfile_fd >= 0) || ((logfile_fd = open_crashlog()) >= 0)) - DUMP(logfile_fd) + file_fd = (zlog_default != NULL) ? zlog_default->file_fd : -1 ; + if (file_fd < 0) + file_fd = open_crashlog() ; + + if (file_fd >= 0) + DUMP(file_fd) + if (!zlog_default) DUMP(STDERR_FILENO) else @@ -616,16 +829,16 @@ zlog_backtrace_sigsafe(int priority, void *program_counter) void zlog_backtrace(int priority) { - VTY_LOCK() ; + LOG_LOCK() ; uzlog_backtrace(priority); - VTY_UNLOCK() ; + LOG_UNLOCK() ; } static void uzlog_backtrace(int priority) { #ifndef HAVE_GLIBC_BACKTRACE - uzlog(NULL, priority, "No backtrace available on this platform."); + zlog(NULL, priority, "No backtrace available on this platform."); #else void *array[20]; int size, i; @@ -634,161 +847,79 @@ uzlog_backtrace(int priority) if (((size = backtrace(array,sizeof(array)/sizeof(array[0]))) <= 0) || ((size_t)size > sizeof(array)/sizeof(array[0]))) { - uzlog(NULL, LOG_ERR, "Cannot get backtrace, returned invalid # of frames %d " + zlog(NULL, LOG_ERR, "Cannot get backtrace, returned invalid # of frames %d " "(valid range is between 1 and %lu)", size, (unsigned long)(sizeof(array)/sizeof(array[0]))); return; } - uzlog(NULL, priority, "Backtrace for %d stack frames:", size); + zlog(NULL, priority, "Backtrace for %d stack frames:", size); if (!(strings = backtrace_symbols(array, size))) { - uzlog(NULL, LOG_ERR, "Cannot get backtrace symbols (out of memory?)"); + zlog(NULL, LOG_ERR, "Cannot get backtrace symbols (out of memory?)"); for (i = 0; i < size; i++) - uzlog(NULL, priority, "[bt %d] %p",i,array[i]); + zlog(NULL, priority, "[bt %d] %p",i,array[i]); } else { for (i = 0; i < size; i++) - uzlog(NULL, priority, "[bt %d] %s",i,strings[i]); + zlog(NULL, priority, "[bt %d] %s",i,strings[i]); free(strings); } #endif /* HAVE_GLIBC_BACKTRACE */ } -/* unlocked version */ -void -uzlog (struct zlog *zl, int priority, const char *format, ...) -{ - va_list args; - - va_start(args, format); - uvzlog (zl, priority, format, args); - va_end (args); -} - -void -zlog (struct zlog *zl, int priority, const char *format, ...) -{ - va_list args; - - va_start(args, format); - vzlog (zl, priority, format, args); - va_end (args); -} - -#define ZLOG_FUNC(FUNCNAME,PRIORITY) \ -void \ -FUNCNAME(const char *format, ...) \ -{ \ - va_list args; \ - va_start(args, format); \ - vzlog (NULL, PRIORITY, format, args); \ - va_end(args); \ -} - -ZLOG_FUNC(zlog_err, LOG_ERR) - -ZLOG_FUNC(zlog_warn, LOG_WARNING) - -ZLOG_FUNC(zlog_info, LOG_INFO) - -ZLOG_FUNC(zlog_notice, LOG_NOTICE) - -ZLOG_FUNC(zlog_debug, LOG_DEBUG) - -#undef ZLOG_FUNC - -#define PLOG_FUNC(FUNCNAME,PRIORITY) \ -void \ -FUNCNAME(struct zlog *zl, const char *format, ...) \ -{ \ - va_list args; \ - va_start(args, format); \ - vzlog (zl, PRIORITY, format, args); \ - va_end(args); \ -} - -PLOG_FUNC(plog_err, LOG_ERR) - -PLOG_FUNC(plog_warn, LOG_WARNING) - -PLOG_FUNC(plog_info, LOG_INFO) - -PLOG_FUNC(plog_notice, LOG_NOTICE) - -PLOG_FUNC(plog_debug, LOG_DEBUG) - -#undef PLOG_FUNC - -void -_zlog_assert_failed (const char *assertion, const char *file, - unsigned int line, const char *function) -{ - const static size_t buff_size = 1024; - char buff[buff_size]; - snprintf(buff, buff_size, - "Assertion `%s' failed in file %s, line %u, function %s", - assertion, file, line, (function ? function : "?")); - zlog_abort(buff); -} - -/* Abort with message */ -void -_zlog_abort_mess (const char *mess, const char *file, - unsigned int line, const char *function) -{ - const static size_t buff_size = 1024; - char buff[buff_size]; - snprintf(buff, buff_size, "%s, in file %s, line %u, function %s", - mess, file, line, (function ? function : "?")); - zlog_abort(buff); -} - -/* Abort with message and errno and strerror() thereof */ -void -_zlog_abort_errno (const char *mess, const char *file, - unsigned int line, const char *function) -{ - _zlog_abort_err(mess, errno, file, line, function); -} - -/* Abort with message and given error and strerror() thereof */ -void -_zlog_abort_err (const char *mess, int err, const char *file, - unsigned int line, const char *function) -{ - const static size_t buff_size = 1024; - char buff[buff_size]; - snprintf(buff, buff_size, - "%s, in file %s, line %u, function %s, %s", - mess, file, line, (function ? function : "?"), - errtoa(err, 0).str); - zlog_abort(buff); -} +static void uzlog_set_effective_level(struct zlog* zl) ; static void zlog_abort (const char *mess) { -#if VTY_DEBUG +#if LOG_DEBUG /* May not be locked -- but that doesn't matter any more */ - ++vty_lock_count ; + ++log_lock_count ; #endif - /* Force fallback file logging? */ - if (zlog_default && !zlog_default->fp && - ((logfile_fd = open_crashlog()) >= 0) && - ((zlog_default->fp = fdopen(logfile_fd, "w")) != NULL)) - zlog_default->maxlvl[ZLOG_DEST_FILE] = LOG_ERR; + if (zlog_default != NULL) + { + if (zlog_default->file_fd < 0) + zlog_default->file_fd = open_crashlog() ; + + if (zlog_default->file_fd >= 0) + { + zlog_default->maxlvl[ZLOG_DEST_FILE] = LOG_ERR; + uzlog_set_effective_level(zlog_default) ; + } ; + } ; - uzlog(NULL, LOG_CRIT, "%s", mess); + zlog(NULL, LOG_CRIT, "%s", mess); uzlog_backtrace(LOG_CRIT); zabort_abort(); } +/*============================================================================== + * Opening, closing, setting log levels etc. + * + */ +static int uzlog_set_file(struct zlog *zl, const char *filename, int level) ; +static int uzlog_reset_file(struct zlog *zl) ; +static void uzlog_set_effective_level(struct zlog* zl) ; + +/*------------------------------------------------------------------------------ + * Get the effective zlog stream. + */ +static inline struct zlog* zlog_actual(struct zlog* zl) +{ + return zl != NULL ? zl : zlog_default ; +} ; -/* Open log stream */ -struct zlog * +/*------------------------------------------------------------------------------ + * Open logging -- create and initialise a struct zlog object. + * + * Opens a connection to syslog. + * + * This must be done very early in the morning, before any pthreading starts. + */ +extern struct zlog * openzlog (const char *progname, zlog_proto_t protocol, int syslog_flags, int syslog_facility) { @@ -797,438 +928,515 @@ openzlog (const char *progname, zlog_proto_t protocol, zl = XCALLOC(MTYPE_ZLOG, sizeof (struct zlog)); - zl->ident = progname; - zl->protocol = protocol; - zl->facility = syslog_facility; + zl->ident = progname; + zl->protocol = protocol; + zl->facility = syslog_facility; zl->syslog_options = syslog_flags; - /* Set default logging levels. */ - for (i = 0; i < sizeof(zl->maxlvl)/sizeof(zl->maxlvl[0]); i++) - zl->maxlvl[i] = ZLOG_DISABLED; - zl->maxlvl[ZLOG_DEST_MONITOR] = LOG_DEBUG; - zl->default_lvl = LOG_DEBUG; + /* Set default logging levels. */ + for (i = 0 ; i < ZLOG_DEST_COUNT ; ++i) + zl->maxlvl[i] = ZLOG_DISABLED ; + + zl->default_lvl = LOG_DEBUG ; openlog (progname, syslog_flags, zl->facility); + zl->syslog = true ; /* have syslog */ + zl->stdout_fd = fileno(stdout) ; /* assume have stdout */ + zl->file_fd = -1 ; /* no file, yet */ + zl->monitors = 0 ; /* no monitors, yet */ + + assert(zlog_list == NULL) ; /* can do this once ! */ + zlog_list = zl ; + + uzlog_set_effective_level(zl) ; + return zl; -} +} ; -void +/*------------------------------------------------------------------------------ + * Close logging -- destroy struct zlog object. + * + * Closes connection to syslog and any log file. + */ +extern void closezlog (struct zlog *zl) { + assert((zl == zlog_list) && (zl->next == NULL)) ; /* pro tem */ + closelog(); + if (zl->file_fd >= 0) + close (zl->file_fd) ; - if (zl->fp != NULL) - fclose (zl->fp); + if (zl->filename != NULL) + free (zl->filename) ; + + zl->syslog = false ; + zl->file_fd = -1 ; + zl->stdout_fd = -1 ; + zl->monitors = 0 ; /* TODO... state of VTY ?? */ + + uzlog_set_effective_level(zl) ; XFREE (MTYPE_ZLOG, zl); } -/* Called from command.c. */ -void -zlog_set_level (struct zlog *zl, zlog_dest_t dest, int log_level) +/*------------------------------------------------------------------------------ + * Set new logging level for the given destination. + * + * Update the effective maxlvl for this zlog, and the max_maxlvl for all zlog. + */ +extern void +zlog_set_level (struct zlog *zl, zlog_dest_t dest, int level) { - VTY_LOCK() ; - - if (zl == NULL) - zl = zlog_default; + LOG_LOCK() ; - if (zl != NULL) + if ((zl = zlog_actual(zl)) != NULL) { - zl->maxlvl[dest] = log_level; + zl->maxlvl[dest] = level ; + uzlog_set_effective_level(zl) ; } - VTY_UNLOCK() ; + LOG_UNLOCK() ; } -int -zlog_set_file (struct zlog *zl, const char *filename, int log_level) +/*------------------------------------------------------------------------------ + * Set new log file: name and level. + * + * Note that this closes any existing file + * + * Returns: 0 => OK + * errno otherwise. + */ +extern int +zlog_set_file(struct zlog *zl, const char *filename, int level) { - FILE *fp; - mode_t oldumask; - int result = 1; + int err ; - VTY_LOCK() ; + LOG_LOCK() ; - /* There is opend file. */ - uzlog_reset_file (zl); + err = uzlog_set_file(zl, filename, level) ; - /* Set default zl. */ - if (zl == NULL) - zl = zlog_default; + LOG_UNLOCK() ; + return err ; +} - if (zl != NULL) +static int +uzlog_set_file(struct zlog *zl, const char *filename, int level) +{ + int err ; + + LOG_ASSERT_LOCKED() ; + + err = 0 ; + if ((zl = zlog_actual(zl)) != NULL) { - /* Open file. */ + mode_t oldumask; + int fd ; + + /* Close any existing file */ + uzlog_reset_file(zl); + + /* Open file making damn sure we get the mode we want ! */ oldumask = umask (0777 & ~LOGFILE_MASK); - fp = fopen (filename, "a"); - umask(oldumask); - if (fp == NULL) - result = 0; + fd = open(filename, O_WRONLY | O_APPEND | O_CREAT, LOGFILE_MASK); + if (fd < 0) + err = errno ; else { - /* Set flags. */ - zl->filename = strdup (filename); - zl->maxlvl[ZLOG_DEST_FILE] = log_level; - zl->fp = fp; - logfile_fd = fileno(fp); - } - } + /* Set flags. */ + zl->filename = strdup (filename) ; + zl->file_fd = fd ; + zl->maxlvl[ZLOG_DEST_FILE] = level; + + uzlog_set_effective_level(zl) ; + } ; + + umask(oldumask); + } ; - VTY_UNLOCK() ; - return result; + return err ; } -/* Reset opend file. */ -int -zlog_reset_file (struct zlog *zl) +/*------------------------------------------------------------------------------ + * Close any existing log file. Set level to ZLOG_DISABLED. + * + * Note that this closes any existing file + * + * Returns: 1 + */ +extern int +zlog_reset_file(struct zlog *zl) { - int result; - VTY_LOCK() ; - result = uzlog_reset_file(zl); - VTY_UNLOCK() ; - return result; + int ret ; + LOG_LOCK() ; + + ret = uzlog_reset_file(zl) ; + + LOG_UNLOCK() ; + return ret ; } static int -uzlog_reset_file (struct zlog *zl) - { - if (zl == NULL) - zl = zlog_default; +uzlog_reset_file(struct zlog *zl) +{ + LOG_ASSERT_LOCKED() ; - if (zl != NULL) + if ((zl = zlog_actual(zl)) != NULL) { - if (zl->fp) - fclose (zl->fp); - zl->fp = NULL; - logfile_fd = -1; + if (zl->file_fd >= 0) + close (zl->file_fd) ; + + zl->file_fd = -1 ; zl->maxlvl[ZLOG_DEST_FILE] = ZLOG_DISABLED; - if (zl->filename) - free (zl->filename); - zl->filename = NULL; - } + if (zl->filename != NULL) + free (zl->filename) ; + + zl->filename = NULL ; + + uzlog_set_effective_level(zl) ; + } ; return 1; } -/* Reopen log file. */ -int +/*------------------------------------------------------------------------------ + * Close and reopen log file -- TODO and the point ?? + */ +extern int zlog_rotate (struct zlog *zl) { - int level; - int result = 1; + int err ; + char* filename ; - VTY_LOCK() ; + filename = NULL ; + err = 0 ; - if (zl == NULL) - zl = zlog_default; + LOG_LOCK() ; - if (zl != NULL) + if ((zl = zlog_actual(zl)) != NULL) { - if (zl->fp) - fclose (zl->fp); - zl->fp = NULL; - logfile_fd = -1; - level = zl->maxlvl[ZLOG_DEST_FILE]; - zl->maxlvl[ZLOG_DEST_FILE] = ZLOG_DISABLED; - - if (zl->filename) + if (zl->file_fd >= 0) { - mode_t oldumask; - int save_errno; - - oldumask = umask (0777 & ~LOGFILE_MASK); - zl->fp = fopen (zl->filename, "a"); - save_errno = errno; - umask(oldumask); - if (zl->fp == NULL) - { - /* can't call logging while locked */ - char *fname = strdup(zl->filename); - uzlog(NULL, LOG_ERR, - "Log rotate failed: cannot open file %s for append: %s", - fname, errtoa(save_errno, 0).str); - free(fname); - result = -1; - } - else - { - logfile_fd = fileno(zl->fp); - zl->maxlvl[ZLOG_DEST_FILE] = level; - } - } - } - VTY_UNLOCK() ; - return result; + filename = zl->filename ; + zl->filename = NULL ; /* co-opt the name */ + + err = uzlog_set_file(zl, filename, zl->maxlvl[ZLOG_DEST_FILE]) ; + } ; + } ; + + LOG_UNLOCK() ; + + if (err != 0) + zlog(NULL, LOG_ERR, + "Log rotate failed: cannot open file %s for append: %s", + filename, errtoa(err, 0).str) ; + if (filename != NULL) + free(filename) ; /* discard old copy */ + + return (err == 0) ? 1 : -1 ; } -int -zlog_get_default_lvl (struct zlog *zl) +/*------------------------------------------------------------------------------ + * Increment or decrement the number of monitor terminals. + */ +extern void +uzlog_add_monitor(struct zlog *zl, int count) { - int result = LOG_DEBUG; + if ((zl = zlog_actual(zl)) != NULL) + { + zl->monitors += count ; + uzlog_set_effective_level(zl) ; + } ; +} - VTY_LOCK() ; - if (zl == NULL) - zl = zlog_default; +/*------------------------------------------------------------------------------ + * get the current default level + */ +extern int +zlog_get_default_lvl (struct zlog *zl) +{ + int level ; - if (zl != NULL) - { - result = zl->default_lvl; - } + LOG_LOCK() ; + + zl = zlog_actual(zl) ; + level = (zl != NULL) ? zl->default_lvl : LOG_DEBUG ; - VTY_UNLOCK() ; - return result; + LOG_UNLOCK() ; + return level ; } -void +/*------------------------------------------------------------------------------ + * set the default level + */ +extern void zlog_set_default_lvl (struct zlog *zl, int level) { - VTY_LOCK() ; + LOG_LOCK() ; - if (zl == NULL) - zl = zlog_default; + if ((zl = zlog_actual(zl)) != NULL) + zl->default_lvl = level; - if (zl != NULL) - { - zl->default_lvl = level; - } - - VTY_UNLOCK() ; + LOG_UNLOCK() ; } -/* Set logging level and default for all destinations */ -void +/*------------------------------------------------------------------------------ + * Set default logging level and the level for any destination not ZLOG_DISABLED + * + * Note that on openzlog(), all destinations are set ZLOG_DISABLED. + */ +extern void zlog_set_default_lvl_dest (struct zlog *zl, int level) { - int i; + LOG_LOCK() ; - VTY_LOCK() ; - - if (zl == NULL) - zl = zlog_default; - - if (zl != NULL) + if ((zl = zlog_actual(zl)) != NULL) { + int i; + zl->default_lvl = level; - for (i = 0; i < ZLOG_NUM_DESTS; i++) + for (i = 0; i < ZLOG_DEST_COUNT; i++) if (zl->maxlvl[i] != ZLOG_DISABLED) - zl->maxlvl[i] = level; - } + zl->maxlvl[i] = level ; - VTY_UNLOCK() ; -} + uzlog_set_effective_level(zl) ; + } ; -int + LOG_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * Get the current level for the given destination. + */ +extern int zlog_get_maxlvl (struct zlog *zl, zlog_dest_t dest) { - int result = ZLOG_DISABLED; + int level ; - VTY_LOCK() ; + LOG_LOCK() ; - if (zl == NULL) - zl = zlog_default; - - if (zl != NULL) - { - result = zl->maxlvl[dest]; - } + zl = zlog_actual(zl) ; + level = (zl != NULL) ? zl->maxlvl[dest] : ZLOG_DISABLED ; - VTY_UNLOCK() ; - return result; + LOG_UNLOCK() ; + return level; } -int +/*------------------------------------------------------------------------------ + * Get the current facility setting for syslog + */ +extern int zlog_get_facility (struct zlog *zl) { - int result = LOG_DAEMON; + int facility ; - VTY_LOCK() ; + LOG_LOCK() ; - if (zl == NULL) - zl = zlog_default; - - if (zl != NULL) - { - result = zl->facility; - } + zl = zlog_actual(zl) ; + facility = (zl != NULL) ? zl->facility : LOG_DAEMON ; - VTY_UNLOCK() ; - return result; -} + LOG_UNLOCK() ; + return facility ; +} ; -void +/*------------------------------------------------------------------------------ + * Set the current facility setting for syslog + */ +extern void zlog_set_facility (struct zlog *zl, int facility) { - VTY_LOCK() ; - - if (zl == NULL) - zl = zlog_default; + LOG_LOCK() ; - if (zl != NULL) - { - zl->facility = facility; - } + if ((zl = zlog_actual(zl)) != NULL) + zl->facility = facility ; - VTY_UNLOCK() ; + LOG_UNLOCK() ; } -int +/*------------------------------------------------------------------------------ + * Get the record priority setting + */ +extern bool zlog_get_record_priority (struct zlog *zl) { - int result = 0; + bool priority ; - VTY_LOCK() ; + LOG_LOCK() ; - if (zl == NULL) - zl = zlog_default; + zl = zlog_actual(zl) ; + priority = (zl != NULL) ? zl->record_priority : false ; - if (zl != NULL) - { - result = zl->record_priority; - } - - VTY_UNLOCK() ; - return result; + LOG_UNLOCK() ; + return priority ; } -void -zlog_set_record_priority (struct zlog *zl, int record_priority) +/*------------------------------------------------------------------------------ + * Set the record priority setting + */ +extern void +zlog_set_record_priority (struct zlog *zl, bool record_priority) { - VTY_LOCK() ; + LOG_LOCK() ; - if (zl == NULL) - zl = zlog_default; + if ((zl = zlog_actual(zl)) != NULL) + zl->record_priority = record_priority ; - if (zl != NULL) - { - zl->record_priority = record_priority; - } - VTY_UNLOCK() ; + LOG_UNLOCK() ; } -int +/*------------------------------------------------------------------------------ + * Get the timestamp precision setting + */ +extern int zlog_get_timestamp_precision (struct zlog *zl) { - int result = 0; + int precision = 0; - VTY_LOCK() ; + LOG_LOCK() ; - if (zl == NULL) - zl = zlog_default; + zl = zlog_actual(zl) ; + precision = (zl != NULL) ? zl->timestamp_precision : 0 ; - if (zl != NULL) - { - result = zl->timestamp_precision; - } - VTY_UNLOCK() ; - return result; + LOG_UNLOCK() ; + return precision ; } -void +/*------------------------------------------------------------------------------ + * Get the timestamp precision setting + */ +extern void zlog_set_timestamp_precision (struct zlog *zl, int timestamp_precision) { - VTY_LOCK() ; + LOG_LOCK() ; - if (zl == NULL) - zl = zlog_default; - - if (zl != NULL) - { - zl->timestamp_precision = timestamp_precision; - } + if ((zl = zlog_actual(zl)) != NULL) + zl->timestamp_precision = timestamp_precision; - VTY_UNLOCK() ; + LOG_UNLOCK() ; } -/* returns name of ZLOG_NONE if no zlog given and no default set */ -const char * +/*------------------------------------------------------------------------------ + * Get name of the protocol -- name of ZLOG_NONE if no zlog set up yet. + */ +extern const char * zlog_get_proto_name (struct zlog *zl) { - const char * result; - VTY_LOCK() ; - result = uzlog_get_proto_name(zl); - VTY_UNLOCK() ; - return result; + const char* name ; + LOG_LOCK() ; + + zl = zlog_actual(zl) ; + name = zlog_proto_names[(zl != NULL) ? zl->protocol : ZLOG_NONE] ; + + LOG_UNLOCK() ; + return name ; } -/* unprotected version, assumes mutex held */ -const char * -uzlog_get_proto_name (struct zlog *zl) +/*------------------------------------------------------------------------------ + * caller must free result + */ +extern char * +zlog_get_filename (struct zlog *zl) { - zlog_proto_t protocol = ZLOG_NONE; + char* name = NULL; - if (zl == NULL) - zl = zlog_default; + LOG_LOCK() ; - if (zl != NULL) - { - protocol = zl->protocol; - } - - return zlog_proto_names[protocol]; + zl = zlog_actual(zl) ; + name = ((zl != NULL) && (zl->filename != NULL)) ? strdup(zl->filename) + : NULL ; + LOG_UNLOCK() ; + return name ; } -/* caller must free result */ -char * -zlog_get_filename (struct zlog *zl) +/*------------------------------------------------------------------------------ + * Get address of ident string + */ +extern const char * +zlog_get_ident (struct zlog *zl) { - char * result = NULL; + const char* ident ; - VTY_LOCK() ; - - if (zl == NULL) - zl = zlog_default; + LOG_LOCK() ; - if (zl != NULL && zl->filename != NULL) - { - result = strdup(zl->filename); - } + zl = zlog_actual(zl) ; + ident = (zl != NULL) ? zl->ident : NULL ; - VTY_UNLOCK() ; - return result; + LOG_UNLOCK() ; + return ident ; } -const char * -zlog_get_ident (struct zlog *zl) +/*------------------------------------------------------------------------------ + * logging to a file? + */ +extern bool +zlog_is_file (struct zlog *zl) { - const char * result = NULL; + bool is_file ; - VTY_LOCK() ; + LOG_LOCK() ; - if (zl == NULL) - zl = zlog_default; + zl = zlog_actual(zl) ; + is_file = (zl != NULL) ? (zl->file_fd >= 0) : false ; - if (zl != NULL) - { - result = zl->ident; - } - - VTY_UNLOCK() ; - return result; + LOG_UNLOCK() ;; + return is_file ; } -/* logging to a file? */ -int -zlog_is_file (struct zlog *zl) +/*------------------------------------------------------------------------------ + * Setting of emaxlvl for given destination. + */ +inline static void +uzlog_set_emaxlvl(struct zlog *zl, zlog_dest_t dest, bool test) +{ + zl->emaxlvl[dest] = test ? zl->maxlvl[dest] : ZLOG_DISABLED ; +}; + +/*------------------------------------------------------------------------------ + * Update effective logging level for the given destination, and max_maxlvl. + * + * The effective logging level takes into account whether the destination is + * enabled or not. + */ +static void +uzlog_set_effective_level(struct zlog *zl) { - int result = 0; + int emaxlvl ; - VTY_LOCK() ; + LOG_ASSERT_LOCKED() ; - if (zl == NULL) - zl = zlog_default; + /* Re-establish the emaxlvl for this logging stream. */ + uzlog_set_emaxlvl(zl, ZLOG_DEST_SYSLOG, zl->syslog ) ; + uzlog_set_emaxlvl(zl, ZLOG_DEST_FILE, zl->file_fd >= 0) ; + uzlog_set_emaxlvl(zl, ZLOG_DEST_STDOUT, zl->stdout_fd >= 0) ; + uzlog_set_emaxlvl(zl, ZLOG_DEST_MONITOR, zl->monitors > 0) ; + confirm(ZLOG_DEST_COUNT == 4) ; - if (zl != NULL) + /* Scan all known logging streams, and re-establish max_maxlvl. + * + * Do not expect there to be many of these, or that we will change them very + * often -- so nothing clever, here. + */ + emaxlvl = ZLOG_DISABLED ; + zl = zlog_list ; + while (zl != NULL) { - result = (zl->fp != NULL); - } + uint i ; + for (i = 0 ; i < ZLOG_DEST_COUNT ; ++i) + if (emaxlvl < zl->emaxlvl[i]) + emaxlvl = zl->emaxlvl[i] ; + zl = zl->next ; + } ; - VTY_UNLOCK() ;; - return result; -} + max_maxlvl = emaxlvl ; +} ; + +/*============================================================================== + * + */ /* Message lookup function. */ const char * @@ -25,6 +25,9 @@ #ifndef _ZEBRA_LOG_H #define _ZEBRA_LOG_H +#include "zconfig.h" +#include "misc.h" +#include <signal.h> #include <syslog.h> #include <stdio.h> #include "vargs.h" @@ -61,111 +64,117 @@ typedef enum } zlog_proto_t; /* If maxlvl is set to ZLOG_DISABLED, then no messages will be sent - to that logging destination. */ -#define ZLOG_DISABLED (LOG_EMERG-1) + * to that logging destination. + */ +enum { ZLOG_DISABLED = LOG_EMERG - 1 } ; typedef enum { ZLOG_DEST_SYSLOG = 0, + ZLOG_DEST_FILE, ZLOG_DEST_STDOUT, ZLOG_DEST_MONITOR, - ZLOG_DEST_FILE + + ZLOG_DEST_COUNT /* Number of destinations */ } zlog_dest_t; -#define ZLOG_NUM_DESTS (ZLOG_DEST_FILE+1) struct zlog { - const char *ident; /* daemon name (first arg to openlog) */ - zlog_proto_t protocol; - int maxlvl[ZLOG_NUM_DESTS]; /* maximum priority to send to associated - logging destination */ - int default_lvl; /* maxlvl to use if none is specified */ - FILE *fp; - char *filename; - int facility; /* as per syslog facility */ - int record_priority; /* should messages logged through stdio include the - priority of the message? */ - int syslog_options; /* 2nd arg to openlog */ - int timestamp_precision; /* # of digits of subsecond precision */ -}; + struct zlog* next ; /* To support multiple logging streams */ + + const char *ident; /* daemon name (first arg to openlog) */ + zlog_proto_t protocol ; + + int maxlvl[ZLOG_DEST_COUNT]; /* maximum priority set */ + int default_lvl; /* maxlvl to use if none is specified */ + + int emaxlvl[ZLOG_DEST_COUNT]; /* effective maximum priority */ + + bool syslog ; /* have active syslog */ + int file_fd ; /* fd for ZLOG_DEST_FILE (if any) */ + int stdout_fd ; /* fd for ZLOG_DEST_STDOUT */ + int monitors ; /* count of monitors */ + + char *filename; /* for ZLOG_DEST_FILE */ + + int facility; /* as per syslog facility */ + int syslog_options; /* 2nd arg to openlog */ -/* Message structure. */ + int timestamp_precision; /* # of digits of subsecond precision */ + bool record_priority; /* should messages logged through stdio + include the priority of the message? */ +} ; + +/* Message structure. */ struct message { int key; const char *str; }; -/* module initialization */ +/* module initialization */ extern void zlog_init_r(void); extern void zlog_destroy_r(void); -/* Default logging structure. */ +/* Default logging structure. */ extern struct zlog *zlog_default; -/* Open zlog function */ +/* Open zlog function */ extern struct zlog *openzlog (const char *progname, zlog_proto_t protocol, int syslog_options, int syslog_facility); -/* Close zlog function. */ +/* Close zlog function. */ extern void closezlog (struct zlog *zl); -/* Generic function for zlog. */ +/* Generic function for zlog. */ extern void zlog (struct zlog *zl, int priority, const char *format, ...) - PRINTF_ATTRIBUTE(3, 4); -/* assumed locked version for close friends */ -extern void uzlog (struct zlog *zl, int priority, const char *format, ...) - PRINTF_ATTRIBUTE(3, 4); - -/* Handy zlog functions. */ -extern void zlog_err (const char *format, ...) PRINTF_ATTRIBUTE(1, 2); -extern void zlog_warn (const char *format, ...) PRINTF_ATTRIBUTE(1, 2); -extern void zlog_info (const char *format, ...) PRINTF_ATTRIBUTE(1, 2); -extern void zlog_notice (const char *format, ...) PRINTF_ATTRIBUTE(1, 2); -extern void zlog_debug (const char *format, ...) PRINTF_ATTRIBUTE(1, 2); + PRINTF_ATTRIBUTE(3, 4); + +/* Handy zlog functions. */ +#define zlog_thus(thus, ...) zlog(zlog_default, thus, __VA_ARGS__) +#define zlog_err(...) zlog(zlog_default, LOG_ERR, __VA_ARGS__) +#define zlog_warn(...) zlog(zlog_default, LOG_WARNING, __VA_ARGS__) +#define zlog_info(...) zlog(zlog_default, LOG_INFO, __VA_ARGS__) +#define zlog_notice(...) zlog(zlog_default, LOG_NOTICE, __VA_ARGS__) +#define zlog_debug(...) zlog(zlog_default, LOG_DEBUG, __VA_ARGS__) /* For bgpd's peer oriented log. */ -extern void plog_err (struct zlog *, const char *format, ...) - PRINTF_ATTRIBUTE(2, 3); -extern void plog_warn (struct zlog *, const char *format, ...) - PRINTF_ATTRIBUTE(2, 3); -extern void plog_info (struct zlog *, const char *format, ...) - PRINTF_ATTRIBUTE(2, 3); -extern void plog_notice (struct zlog *, const char *format, ...) - PRINTF_ATTRIBUTE(2, 3); -extern void plog_debug (struct zlog *, const char *format, ...) - PRINTF_ATTRIBUTE(2, 3); +#define plog_thus(zl, thus, ...) zlog(zl, thus, __VA_ARGS__) +#define plog_err(zl, ...) zlog(zl, LOG_ERR, __VA_ARGS__) +#define plog_warn(zl, ...) zlog(zl, LOG_WARNING, __VA_ARGS__) +#define plog_info(zl, ...) zlog(zl, LOG_INFO, __VA_ARGS__) +#define plog_notice(zl, ...) zlog(zl, LOG_NOTICE, __VA_ARGS__) +#define plog_debug(zl, ...) zlog(zl, LOG_DEBUG, __VA_ARGS__) /* Set logging level for the given destination. If the log_level argument is ZLOG_DISABLED, then the destination is disabled. - This function should not be used for file logging (use zlog_set_file + This function should not be used for file logging (use uzlog_set_file or zlog_reset_file instead). */ -extern void zlog_set_level (struct zlog *zl, zlog_dest_t, int log_level); +extern void zlog_set_level(struct zlog *zl, zlog_dest_t, int log_level); /* Set logging to the given filename at the specified level. */ -extern int zlog_set_file (struct zlog *zl, const char *filename, int log_level); +extern int zlog_set_file(struct zlog *zl, const char *filename, int log_level); /* Disable file logging. */ -extern int zlog_reset_file (struct zlog *zl); +extern int zlog_reset_file(struct zlog *zl); /* Rotate log. */ extern int zlog_rotate (struct zlog *); /* getters & setters */ -extern int zlog_get_default_lvl (struct zlog *zl); +extern int zlog_get_default_lvl (struct zlog *zl); extern void zlog_set_default_lvl (struct zlog *zl, int level); extern void zlog_set_default_lvl_dest (struct zlog *zl, int level); -extern int zlog_get_maxlvl (struct zlog *zl, zlog_dest_t dest); -extern int zlog_get_facility (struct zlog *zl); +extern int zlog_get_maxlvl (struct zlog *zl, zlog_dest_t dest); +extern int zlog_get_facility (struct zlog *zl); extern void zlog_set_facility (struct zlog *zl, int facility); -extern int zlog_get_record_priority (struct zlog *zl); -extern void zlog_set_record_priority (struct zlog *zl, int record_priority); -extern int zlog_get_timestamp_precision (struct zlog *zl); +extern bool zlog_get_record_priority (struct zlog *zl); +extern void zlog_set_record_priority (struct zlog *zl, bool record_priority); +extern int zlog_get_timestamp_precision (struct zlog *zl); extern void zlog_set_timestamp_precision (struct zlog *zl, int timestamp_precision); extern const char * zlog_get_ident (struct zlog *zl); extern char * zlog_get_filename (struct zlog *zl); -extern int zlog_is_file (struct zlog *zl); +extern bool zlog_is_file (struct zlog *zl); extern const char * zlog_get_proto_name (struct zlog *zl); -extern const char * uzlog_get_proto_name (struct zlog *zl); /* For hackey massage lookup and check */ #define LOOKUP(x, y) mes_lookup(x, x ## _max, y, "(no item found)") @@ -182,11 +191,8 @@ extern const char *zlog_proto_names[]; extern const char *safe_strerror(int errnum); /* To be called when a fatal signal is caught. */ -extern void zlog_signal(int signo, const char *action -#ifdef SA_SIGINFO - , siginfo_t *siginfo, void *program_counter -#endif - ); +extern void zlog_signal(int signo, const char *action, siginfo_t *siginfo, + void *program_counter) ; /* Ring down the curtain -- turn of SIGABRT handler and abort() */ extern void zabort_abort(void) __attribute__ ((noreturn)) ; @@ -200,63 +206,6 @@ extern void zlog_backtrace(int priority); that is logged in addition to the current backtrace. */ extern void zlog_backtrace_sigsafe(int priority, void *program_counter); -/* Puts a current timestamp in buf and returns the number of characters - * written (not including the terminating NUL). The purpose of - * this function is to avoid calls to localtime appearing all over the code. - * It caches the most recent localtime result and can therefore - * avoid multiple calls within the same second. - * - * The buflen MUST be > 1 and the buffer address MUST NOT be NULL. - * - * If buflen is too small, writes buflen-1 characters followed by '\0'. - * - * Time stamp is rendered in the form: %Y/%m/%d %H:%M:%S - * - * This has a fixed length (leading zeros are included) of 19 characters - * (unless this code is still in use beyond the year 9999 !) - * - * Which may be followed by "." and a number of decimal digits, usually 1..6. - * - * So the maximum time stamp is 19 + 1 + 6 = 26. Adding the trailing '\n', and - * rounding up for good measure -- buffer size = 32. - */ -#define TIMESTAMP_FORM "%Y/%m/%d %H:%M:%S" - -enum { timestamp_buffer_len = 32 } ; - -extern size_t quagga_timestamp(int timestamp_precision /* # subsecond digits */, - char *buf, size_t buflen); - -/* Generate line to be logged - * - * Structure used to hold line for log output -- so that need be generated - * just once even if output to multiple destinations. - * - * Note that the buffer length is a hard limit (including terminating '\n''\0' - * or '\r''\n''\0'). Do not wish to malloc any larger buffer while logging. - */ -enum { logline_buffer_len = 1008 } ; -enum ll_term -{ - llt_nul = 0, /* NB: also length of the terminator */ - llt_lf = 1, - llt_crlf = 2, -} ; - -struct logline { - char* p_nl ; /* address of the terminator */ - - enum ll_term term ; /* how line is terminated */ - - size_t len ; /* length including either '\r''\n' or '\n' */ - - char line[logline_buffer_len]; /* buffer */ -} ; - -extern void -uvzlog_line(struct logline* ll, struct zlog *zl, int priority, - const char *format, va_list va, enum ll_term term) ; - /* Defines for use in command construction: */ #define LOG_LEVELS "(emergencies|alerts|critical|errors|" \ diff --git a/lib/log_local.h b/lib/log_local.h new file mode 100644 index 00000000..8e38bf48 --- /dev/null +++ b/lib/log_local.h @@ -0,0 +1,155 @@ +/* Logging locking and interface presented to vty et al + * Copyright (C) 2011 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_LOG_LOCAL_H +#define _ZEBRA_LOG_LOCAL_H + +#include "misc.h" + +#include "qpthreads.h" + +/*============================================================================== + * This is for access to some things in log.c which are not required + * by external users, who use log.h. + * + * This is for use within the log/command/vty family. + * + * Should not be used with log.h ! (Except in log.c itself.) + * + * This may duplicate things published in log.h, but also includes things + * which are not intended for "external" use. + */ + +/*============================================================================== + * To make logging qpthread safe we use a single mutex. + * + * A normal mutex is used -- recursion is not expected or required. + * + * There are some "ulog" functions which assume the mutex is locked. + * + * It is assumed that while the log_mutex is owned, NO OTHER SIGNIFICANT lock + * will be acquired -- in particular, NOT the VTY_LOCK() !! + */ + +extern qpt_mutex_t log_mutex ; + +/*------------------------------------------------------------------------------ + * Sort out LOG_DEBUG. + * + * Set to 1 if defined, but blank. + * Set to QDEBUG if not defined. + * + * Force to 0 if LOG_NO_DEBUG is defined and not zero. + * + * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to: + * + * * can set LOG_DEBUG == 0 to turn off debug + * * or set LOG_DEBUG != 0 to turn on debug + * * or set LOG_NO_DEBUG != 0 to force debug off + */ + +#ifdef LOG_DEBUG /* If defined, make it 1 or 0 */ +# if IS_BLANK_OPTION(LOG_DEBUG) +# undef LOG_DEBUG +# define LOG_DEBUG 1 +# endif +#else /* If not defined, follow QDEBUG */ +# define LOG_DEBUG QDEBUG +#endif + +#ifdef LOG_NO_DEBUG /* Override, if defined */ +# if IS_NOT_ZERO_OPTION(LOG_NO_DEBUG) +# undef LOG_DEBUG +# define LOG_DEBUG 0 +# endif +#endif + +enum { log_debug = LOG_DEBUG } ; + +/*------------------------------------------------------------------------------ + * Locking and related functions. + */ +extern int log_lock_count ; + +Inline void +LOG_LOCK(void) /* if is qpthreads_enabled, lock log_mutex */ +{ + qpt_mutex_lock(log_mutex) ; + if (log_debug) + ++log_lock_count ; +} ; + +Inline void +LOG_UNLOCK(void) /* if is qpthreads_enabled, unlock log_mutex */ +{ + if (log_debug) + --log_lock_count ; + qpt_mutex_unlock(log_mutex) ; +} ; + +/* For debug (and documentation) purposes, will LOG_ASSERT_LOCKED where that + * is required. + * + * Note that both these checks will pass if !qpthreads_enabled. So can have + * code which is called before qpthreads are started up, or which will never + * run qpthreaded ! + */ + +extern int log_assert_fail ; + +Inline void +LOG_ASSERT_FAILED(void) +{ + if (log_assert_fail == 0) ; + { + log_assert_fail = 1 ; + assert(0) ; + } ; +} ; + +Inline void +LOG_ASSERT_LOCKED(void) +{ + if (log_debug) + if ((log_lock_count == 0) && (qpthreads_enabled)) + LOG_ASSERT_FAILED() ; +} ; + + + +enum { timestamp_buffer_len = 32 } ; + +extern size_t quagga_timestamp(int timestamp_precision /* # subsecond digits */, + char *buf, size_t buflen); + + +/*============================================================================== + * Functions in log.c -- used in any of the log/command/vty + */ +struct zlog ; + +extern void uzlog_add_monitor(struct zlog *zl, int count) ; + +extern void log_init_r(void) ; +extern void log_finish(void); + + +#endif /* _ZEBRA_LOG_LOCAL_H */ diff --git a/lib/memory.c b/lib/memory.c index e15492c9..d96ff21d 100644 --- a/lib/memory.c +++ b/lib/memory.c @@ -35,8 +35,8 @@ */ static qpt_mutex_t memory_mutex; -#define LOCK qpt_mutex_lock(&memory_mutex); -#define UNLOCK qpt_mutex_unlock(&memory_mutex); +#define LOCK qpt_mutex_lock(memory_mutex); +#define UNLOCK qpt_mutex_unlock(memory_mutex); static void log_memstats(int log_priority); @@ -50,8 +50,13 @@ static const struct message mstr [] = { 0, NULL }, }; -/* If using the mem_tracker, include it now. */ - +/*------------------------------------------------------------------------------ + * Include the memory tracker, if required. + * + * Makes sure that the tracker is always part of the source -- so kept up to + * date -- depending on dead code removal to eliminate overhead when not + * used. + */ typedef struct mem_tracker* mem_tracker ; struct mem_tracker { @@ -72,9 +77,7 @@ mem_tracker_zeroise(struct mem_tracker* mem) memset(mem, 0, sizeof(struct mem_tracker)) ; } ; -#if MEMORY_TRACKER #include "mem_tracker.c" -#endif /*============================================================================== * Keeping track of number of allocated objects of given type @@ -130,9 +133,9 @@ zmalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME) else { mstat.mt[mtype].alloc++; -#if MEMORY_TRACKER - mem_md_malloc(mtype, memory, size, name) ; -#endif + if (memory_tracker) + mem_md_malloc(mtype, memory, size, MEMORY_TRACKER_NAME_ARG) ; + UNLOCK ; } ; @@ -159,9 +162,8 @@ zcalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME) else { mstat.mt[mtype].alloc++; -#if MEMORY_TRACKER - mem_md_malloc(mtype, memory, size, name) ; -#endif + if (memory_tracker) + mem_md_malloc(mtype, memory, size, MEMORY_TRACKER_NAME_ARG) ; UNLOCK ; } ; @@ -170,6 +172,8 @@ zcalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME) /*------------------------------------------------------------------------------ * Memory reallocation. + * + * NB: just like real realloc(), is same as malloc() if ptr == NULL. */ void * zrealloc (enum MTYPE mtype, void *ptr, size_t size MEMORY_TRACKER_NAME) @@ -188,9 +192,8 @@ zrealloc (enum MTYPE mtype, void *ptr, size_t size MEMORY_TRACKER_NAME) { if (ptr == NULL) mstat.mt[mtype].alloc++; -#if MEMORY_TRACKER - mem_md_realloc(mtype, ptr, memory, size, name) ; -#endif + if (memory_tracker) + mem_md_realloc(mtype, ptr, memory, size, MEMORY_TRACKER_NAME_ARG) ; UNLOCK ; } ; @@ -199,6 +202,8 @@ zrealloc (enum MTYPE mtype, void *ptr, size_t size MEMORY_TRACKER_NAME) /*------------------------------------------------------------------------------ * Memory free. + * + * NB: just like real free(), does nothing if ptr == NULL. */ void zfree (enum MTYPE mtype, void *ptr) @@ -210,9 +215,8 @@ zfree (enum MTYPE mtype, void *ptr) assert(mstat.mt[mtype].alloc > 0) ; mstat.mt[mtype].alloc--; -#if MEMORY_TRACKER - mem_md_free(mtype, ptr) ; -#endif + if (memory_tracker) + mem_md_free(mtype, ptr) ; free (ptr); @@ -239,9 +243,9 @@ zstrdup (enum MTYPE mtype, const char *str MEMORY_TRACKER_NAME) else { mstat.mt[mtype].alloc++; -#if MEMORY_TRACKER - mem_md_malloc(mtype, dup, strlen(str)+1, name) ; -#endif + if (memory_tracker) + mem_md_malloc(mtype, dup, strlen(str)+1, MEMORY_TRACKER_NAME_ARG) ; + UNLOCK ; } ; @@ -443,11 +447,12 @@ show_memory_type_vty (struct vty *vty, const char* name, vty_out (vty, "-----------------------------%s", VTY_NEWLINE) ; vty_out (vty, "%-30s:", name) ; -#if MEMORY_TRACKER - show_memory_tracker_detail(vty, mt, alloc) ; -#else - vty_out (vty, " %10ld", alloc) ; -#endif + + if (memory_tracker) + show_memory_tracker_detail(vty, mt, alloc) ; + else + vty_out (vty, " %10ld", alloc) ; + vty_out (vty, "%s", VTY_NEWLINE); } ; @@ -464,15 +469,13 @@ show_memory_vty (struct vty *vty, struct memory_list *m, struct mlist* ml, struct mem_tracker mem_one ; struct mem_tracker* mt ; -#if MEMORY_TRACKER struct mem_type_tracker mem_tt ; -#endif LOCK ; + mst = mstat ; -#if MEMORY_TRACKER mem_tt = mem_type_tracker ; -#endif + UNLOCK ; mem_tracker_zeroise(&mem_tot) ; @@ -499,12 +502,11 @@ show_memory_vty (struct vty *vty, struct memory_list *m, struct mlist* ml, else { alloc = mst.mt[m->index].alloc ; -#if MEMORY_TRACKER - mt = &(mem_tt.mt[m->index]) ; -#else - mt = &mem_one ; + if (memory_tracker) + mt = &(mem_tt.mt[m->index]) ; + else + mt = &mem_one ; mt->tracked_count = alloc ; -#endif mem_tot.malloc_count += mt->malloc_count ; mem_tot.free_count += mt->free_count ; @@ -580,23 +582,23 @@ DEFUN_CALL (show_memory_summary, "Memory statistics\n" "Summary memory statistics\n") { -#if MEMORY_TRACKER - show_memory_tracker_summary(vty) ; -#else - long alloc = 0 ; - int mtype ; + if (memory_tracker) + show_memory_tracker_summary(vty) ; + else + { + long alloc = 0 ; + int mtype ; # ifdef HAVE_MALLINFO - show_memory_mallinfo (vty); + show_memory_mallinfo (vty); # endif /* HAVE_MALLINFO */ - LOCK ; - for (mtype = 1 ; mtype < MTYPE_MAX ; ++mtype) - alloc += mstat.mt[mtype].alloc ; - UNLOCK - vty_out(vty, "%ld items allocated%s", alloc, VTY_NEWLINE) ; - -#endif /* MEMORY_TRACKER */ + LOCK ; + for (mtype = 1 ; mtype < MTYPE_MAX ; ++mtype) + alloc += mstat.mt[mtype].alloc ; + UNLOCK + vty_out(vty, "%ld items allocated%s", alloc, VTY_NEWLINE) ; + } ; return CMD_SUCCESS; } @@ -613,9 +615,9 @@ DEFUN_CALL (show_memory_all, #ifdef HAVE_MALLINFO needsep |= show_memory_mallinfo (vty); #endif /* HAVE_MALLINFO */ -#if MEMORY_TRACKER - needsep |= show_memory_tracker_summary(vty) ; -#endif + + if (memory_tracker) + needsep |= show_memory_tracker_summary(vty) ; show_memory_vty (vty, NULL, mlists, needsep); @@ -720,14 +722,14 @@ DEFUN_CALL (show_memory_isis, void memory_init_r (void) { - qpt_mutex_init(&memory_mutex, qpt_mutex_quagga); + qpt_mutex_init(memory_mutex, qpt_mutex_quagga); } /* Finished with module */ void memory_finish (void) { - qpt_mutex_destroy(&memory_mutex, 0); + qpt_mutex_destroy(memory_mutex, 0); } void diff --git a/lib/memory.h b/lib/memory.h index 4fccb079..0585307b 100644 --- a/lib/memory.h +++ b/lib/memory.h @@ -21,27 +21,37 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA #ifndef _ZEBRA_MEMORY_H #define _ZEBRA_MEMORY_H -#include <stddef.h> - -/* For pretty printing of memory allocate information. */ -struct memory_list +#include "misc.h" + +/*------------------------------------------------------------------------------ + * The file "lib/memtypes.h is created automatically (in the make) from + * memtypes.c, and contains a large enum which defines all the memory type + * names. + * + * The file memtypes.c contains a number of arrays of struct memory_list, which + * map memory type names to a string, for printing. Those arrays are + * collected together in an array of struvt mlist. + */ +struct memory_list /* one per memory type */ { int index; const char *format; }; -struct mlist { +struct mlist { /* one per class of memory types */ struct memory_list *list; const char *name; }; -extern struct mlist mlists[]; +extern struct mlist mlists[]; /* all classes of memory */ #include "lib/memtypes.h" typedef enum MTYPE mtype_t ; -/* #define MEMORY_LOG */ +/*------------------------------------------------------------------------------ + * Option for logging memory operations. + */ #ifdef MEMORY_LOG #define XMALLOC(mtype, size) \ mtype_zmalloc (__FILE__, __LINE__, (mtype), (size)) @@ -58,38 +68,61 @@ typedef enum MTYPE mtype_t ; mtype_zstrdup (__FILE__, __LINE__, (mtype), (str)) #else -#ifdef MEMORY_TRACKER /* Can be forced from outside */ -# if MEMORY_TRACKER -# define MEMORY_TRACKER 1 /* Force 1 or 0 */ -#else -# define MEMORY_TRACKER 0 +/*------------------------------------------------------------------------------ + * Sort out MEMORY_TRACKER -- option to keep track of memory allocation. + * + * Set to 1 if defined, but blank. + * Set to QDEBUG if not defined. + * + * Force to 0 if NO_MEMORY_TRACKER is defined and not zero. + * + * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to: + * + * * can set MEMORY_TRACKER == 0 to turn off debug + * * or set MEMORY_TRACKER != 0 to turn on debug + * * or set VTY_NO_DEBUG != to force debug off + */ + +#ifdef MEMORY_TRACKER /* If defined, make it 1 or 0 */ +# if IS_BLANK_OPTION(MEMORY_TRACKER) +# undef MEMORY_TRACKER +# define MEMORY_TRACKER 1 # endif -#else -# ifdef QDEBUG -# define MEMORY_TRACKER 1 /* Follow QDEBUG */ -#else +#else /* If not defined, follow QDEBUG */ +# define MEMORY_TRACKER QDEBUG +#endif + +#ifdef NO_MEMORY_TRACKER /* Override, if defined. */ +# if IS_NOT_ZERO_OPTION(VTY_NO_DEBUG) +# undef MEMORY_TRACKER # define MEMORY_TRACKER 0 # endif #endif +enum { memory_tracker = MEMORY_TRACKER } ; + #if MEMORY_TRACKER -#define MEMORY_TRACKER_NAME , const char* name -#define MEMORY_TRACKER_FUNC , __func__ +#define MEMORY_TRACKER_NAME_ARG name +#define MEMORY_TRACKER_NAME , const char* MEMORY_TRACKER_NAME_ARG +#define MEMORY_TRACKER_FUNC , __func__ #else +#define MEMORY_TRACKER_NAME_ARG "*dummy*" #define MEMORY_TRACKER_NAME #define MEMORY_TRACKER_FUNC #endif +/*------------------------------------------------------------------------------ + * The macros used for all Quagga dynamic memory. + */ + #define XMALLOC(mtype, size) zmalloc ((mtype), (size) \ MEMORY_TRACKER_FUNC) #define XCALLOC(mtype, size) zcalloc ((mtype), (size) \ MEMORY_TRACKER_FUNC) #define XREALLOC(mtype, ptr, size) zrealloc ((mtype), (ptr), (size) \ MEMORY_TRACKER_FUNC) -#define XFREE(mtype, ptr) do { \ - zfree ((mtype), (ptr)); \ - ptr = NULL; } \ - while (0) +#define XFREE(mtype, ptr) do { zfree ((mtype), (ptr)); \ + ptr = NULL; } while (0) #define XSTRDUP(mtype, str) zstrdup ((mtype), (str) \ MEMORY_TRACKER_FUNC) diff --git a/lib/memtypes.c b/lib/memtypes.c index 7e73e4c1..66d65328 100644 --- a/lib/memtypes.c +++ b/lib/memtypes.c @@ -57,6 +57,7 @@ struct memory_list memory_list_lib[] = { MTYPE_VIO_FIFO_LUMP, "VTY IO FIFO Lump" }, { MTYPE_VIO_LC, "VTY IO Line Control" }, { MTYPE_QSTRING, "qstring structure" }, + { MTYPE_QPATH, "qpath structure" }, { MTYPE_QIOVEC, "qiovec structure" }, { MTYPE_QIOVEC_VEC, "qiovec iovec vector" }, { MTYPE_IF, "Interface" }, @@ -87,6 +87,9 @@ typedef unsigned int uint ; typedef unsigned int usize ; typedef unsigned int ulen ; +typedef int ssize ; +typedef int slen ; + typedef unsigned long ulong ; /* cvp == const void* -- ptr to constant void @@ -95,4 +98,46 @@ typedef unsigned long ulong ; */ typedef const void* cvp ; +/* Macros for sexing value of compilation options. + * + * In particular allow a blank option to be treated as true, and a zero option + * to be treated as false. + * + * NB: the option MUST be defined, and must be decimal numeric !! + */ +#define STRING_VALUE_INNER(x) #x +#define STRING_VALUE(x) STRING_VALUE_INNER(x) + +#define IS_BLANK_OPTION(x) IS_BLANK_OPTION_INNER(x) +#define IS_ZERO_OPTION(x) IS_ZERO_OPTION_INNER(x) +#define IS_NOT_ZERO_OPTION(x) IS_NOT_OPTION_ZERO_INNER(x) + +#define IS_BLANK_OPTION_INNER(x) (1##x##1 == 11) +#define IS_ZERO_OPTION_INNER(x) (1##x##1 == 101) +#define IS_NOT_ZERO_OPTION_INNER(x) (1##x##1 != 101) + +/* If QDEBUG is defined, make QDEBUG_NAME and set QDEBUG + * + * Numeric value for QDEBUG: undefined => 0 + * defined, blank => 1 + * defined, 0 => 0 + * defined, other => other + * + * Template for turning compilation option into a value. + */ +#ifdef QDEBUG +# if IS_BLANK_OPTION(QDEBUG) +# undef QDEBUG +# define QDEBUG 1 +# endif +#else +# define QDEBUG 0 +#endif + +enum { qdebug = QDEBUG } ; + +#ifndef QDEBUG_NAME +# define QDEBUG_NAME STRING_VALUE(QDEBUG) +#endif + #endif /* _ZEBRA_MISC_H */ diff --git a/lib/mqueue.c b/lib/mqueue.c index c7037a64..8cef1ad9 100644 --- a/lib/mqueue.c +++ b/lib/mqueue.c @@ -211,7 +211,7 @@ mqueue_init_new(mqueue_queue mq, enum mqueue_queue_type type) memset(mq, 0, sizeof(struct mqueue_queue)) ; if (qpt_freeze_qpthreads_enabled()) - qpt_mutex_init_new(&mq->mutex, qpt_mutex_quagga) ; + qpt_mutex_init_new(mq->mutex, qpt_mutex_quagga) ; /* head, tail and tail_priority set NULL already */ /* count set zero already */ @@ -222,7 +222,7 @@ mqueue_init_new(mqueue_queue mq, enum mqueue_queue_type type) { case mqt_cond_unicast: case mqt_cond_broadcast: - qpt_cond_init_new(&mq->kick.cond.wait_here, qpt_cond_quagga) ; + qpt_cond_init_new(mq->kick.cond.wait_here, qpt_cond_quagga) ; if (MQUEUE_DEFAULT_INTERVAL != 0) { @@ -277,13 +277,13 @@ mqueue_reset(mqueue_queue mq, free_keep_b free_structure) passert(mq->waiters == 0) ; - qpt_mutex_destroy_keep(&mq->mutex) ; + qpt_mutex_destroy_keep(mq->mutex) ; switch (mq->type) { case mqt_cond_unicast: case mqt_cond_broadcast: - qpt_cond_destroy_keep(&mq->kick.cond.wait_here) ; + qpt_cond_destroy_keep(mq->kick.cond.wait_here) ; break; case mqt_signal_unicast: @@ -362,7 +362,7 @@ mqueue_local_reset(mqueue_local_queue lmq, free_keep_b free_structure) extern void mqueue_set_timeout_interval(mqueue_queue mq, qtime_t interval) { - qpt_mutex_lock(&mq->mutex) ; + qpt_mutex_lock(mq->mutex) ; dassert( (mq->type == mqt_cond_unicast) || (mq->type == mqt_cond_broadcast) ) ; @@ -370,7 +370,7 @@ mqueue_set_timeout_interval(mqueue_queue mq, qtime_t interval) mq->kick.cond.interval = interval ; mq->kick.cond.timeout = (interval > 0) ? qt_add_monotonic(interval) : 0 ; - qpt_mutex_unlock(&mq->mutex) ; + qpt_mutex_unlock(mq->mutex) ; } ; /*============================================================================== @@ -484,9 +484,12 @@ mqb_re_init(mqueue_block mqb, mqueue_action action, void* arg0) * NB: it is the caller's responsibility to free the value of any argument that * requires it. */ -extern void +extern mqueue_block mqb_free(mqueue_block mqb) { + if (mqb == NULL) + return NULL ; + if (mqb->argv != NULL) XFREE(MTYPE_MQUEUE_BLOCK_ARGV, mqb->argv) ; @@ -497,6 +500,8 @@ mqb_free(mqueue_block mqb) ++mqb_free_count ; qpt_mutex_unlock(&mqb_mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ + + return NULL ; } ; /*============================================================================== @@ -538,7 +543,7 @@ mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, mqb_rank_b priority) if (mq == NULL) return mqb_dispatch_destroy(mqb) ; - qpt_mutex_lock(&mq->mutex) ; + qpt_mutex_lock(mq->mutex) ; if (mq->head == NULL) { @@ -588,12 +593,12 @@ mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, mqb_rank_b priority) switch (mq->type) { case mqt_cond_unicast: - qpt_cond_signal(&mq->kick.cond.wait_here) ; + qpt_cond_signal(mq->kick.cond.wait_here) ; --mq->waiters ; break ; case mqt_cond_broadcast: - qpt_cond_broadcast(&mq->kick.cond.wait_here) ; + qpt_cond_broadcast(mq->kick.cond.wait_here) ; mq->waiters = 0 ; break ; @@ -612,7 +617,7 @@ mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, mqb_rank_b priority) } ; } ; - qpt_mutex_unlock(&mq->mutex) ; + qpt_mutex_unlock(mq->mutex) ; } ; /*------------------------------------------------------------------------------ @@ -655,7 +660,7 @@ mqueue_dequeue(mqueue_queue mq, int wait, void* arg) if (mq == NULL) return NULL ; - qpt_mutex_lock(&mq->mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ + qpt_mutex_lock(mq->mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ while (1) { @@ -676,13 +681,13 @@ mqueue_dequeue(mqueue_queue mq, int wait, void* arg) case mqt_cond_unicast: /* Now wait here */ case mqt_cond_broadcast: if ((arg == NULL) && (mq->kick.cond.interval <= 0)) - qpt_cond_wait(&mq->kick.cond.wait_here, &mq->mutex) ; + qpt_cond_wait(mq->kick.cond.wait_here, mq->mutex) ; else { timeout_time = (arg != NULL) ? *(qtime_mono_t*)arg : mq->kick.cond.timeout ; - if (qpt_cond_timedwait(&mq->kick.cond.wait_here, &mq->mutex, + if (qpt_cond_timedwait(mq->kick.cond.wait_here, mq->mutex, timeout_time) == 0) { /* Timed out -- update timeout time, if required */ @@ -742,7 +747,7 @@ mqueue_dequeue(mqueue_queue mq, int wait, void* arg) mq->tail_priority = NULL ; done: - qpt_mutex_unlock(&mq->mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ + qpt_mutex_unlock(mq->mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ return mqb ; } ; @@ -783,7 +788,7 @@ mqueue_revoke(mqueue_queue mq, void* arg0, int num) if (mq == NULL) return 0 ; - qpt_mutex_lock(&mq->mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ + qpt_mutex_lock(mq->mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ did = 0 ; prev = NULL ; @@ -827,7 +832,7 @@ mqueue_revoke(mqueue_queue mq, void* arg0, int num) } ; } ; - qpt_mutex_unlock(&mq->mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ + qpt_mutex_unlock(mq->mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ return did ; } ; @@ -847,7 +852,7 @@ mqueue_done_waiting(mqueue_queue mq, mqueue_thread_signal mtsig) if (!qpthreads_enabled) return 0 ; - qpt_mutex_lock(&mq->mutex) ; + qpt_mutex_lock(mq->mutex) ; dassert( (mq->type == mqt_signal_unicast) || (mq->type == mqt_signal_broadcast) ) ; @@ -864,7 +869,7 @@ mqueue_done_waiting(mqueue_queue mq, mqueue_thread_signal mtsig) if (!kicked) mqueue_dequeue_signal(mq, mtsig) ; - qpt_mutex_unlock(&mq->mutex) ; + qpt_mutex_unlock(mq->mutex) ; return kicked ; } ; diff --git a/lib/mqueue.h b/lib/mqueue.h index f33af564..c787e19b 100644 --- a/lib/mqueue.h +++ b/lib/mqueue.h @@ -213,7 +213,7 @@ 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 mqueue_block mqb_free(mqueue_block mqb) ; enum mqb_rank { diff --git a/lib/network.c b/lib/network.c index ae67a402..57cca0d4 100644 --- a/lib/network.c +++ b/lib/network.c @@ -20,9 +20,29 @@ * 02111-1307, USA. */ -#include <zebra.h> +//#include <zebra.h> + +#include "misc.h" +#include "qdebug_nb.h" + +#include <unistd.h> +#include <stdio.h> +#include <fcntl.h> +#include <errno.h> + #include "log.h" #include "network.h" +#include "memory.h" + +/*============================================================================== + * Read and write loops -- assuming blocking or not-blocking. + * + * + * + */ + +static ssize_t read_qdebug_nb(int fd, void* buf, size_t nbyte) ; +static ssize_t write_qdebug_nb(int fd, const void* buf, size_t nbyte) ; /*------------------------------------------------------------------------------ * Read nbytes from fd and store into ptr -- BLOCKING @@ -34,8 +54,8 @@ * * NB: if applied to a NON-BLOCKING fd, may return EAGAIN or EWOULDBLOCK */ -int -readn (int fd, u_char *ptr, int nbytes) +extern int +readn (int fd, void* buf, int nbytes) { int nleft; int nread; @@ -44,15 +64,17 @@ readn (int fd, u_char *ptr, int nbytes) while (nleft > 0) { - nread = read (fd, ptr, nleft); + nread = read (fd, buf, nleft); if (nread > 0) { nleft -= nread; - ptr += nread; + buf = (char*)buf + nread; } + else if (nread == 0) break; + else { if (errno != EINTR) @@ -73,57 +95,122 @@ readn (int fd, u_char *ptr, int nbytes) * -1 => failed -- see errno * -2 => EOF met immediately * - * NB: if asked to write zero bytes, does nothing and will return 0. + * NB: if asked to read zero bytes, does nothing and will return 0. * * Reading zero bytes is defined for all types of files, and may be used * to probe for error state. */ -int +extern int read_nb(int fd, void* buf, size_t nbyte) { - size_t nleft = nbyte ; + size_t nleft ; + +// char* p = buf ; + + nleft = nbyte ; do { - int ret = read(fd, buf, nleft); + ssize_t ret ; + + if (qdebug_nb) + ret = read_qdebug_nb(fd, buf, nleft) ; + else + ret = read(fd, buf, nleft) ; if (ret > 0) { buf = (char*)buf + ret ; nleft -= ret ; } + else if (ret == 0) { if (nleft < nbyte) break ; /* if read something before EOF */ - return -2 ; /* hit EOF immediately */ +// fprintf(stderr, "[read (%d) %s]\n", fd, (nbyte == 0) ? "OK" : "EOF") ; + + return (nbyte == 0) ? 0 : -2 ; /* OK or EOF */ } + else { int err = errno ; if ((err == EAGAIN) || (err == EWOULDBLOCK)) break ; if (err != EINTR) +// assert(0) ; // PRO TEM return -1 ; /* failed */ } ; } while (nleft > 0) ; +#if 0 +{ + int n ; + char buffer[100] ; + char* q, * e ; + e = buffer + 95 ; + + n = nbyte - nleft ; + + fprintf(stderr, "[read (%d) %d: '", fd, n) ; + while (n > 0) + { + q = buffer ; + while ((q < e) && (n > 0)) + { + char ch = *p++ ; + --n ; + + if (ch < 0x20) + { + *q++ = '\\' ; + switch (ch) + { + case '\n': + ch = 'n' ; + break ; + + case '\r': + ch = 'r' ; + break ; + + case '\t': + ch = 't' ; + break ; + + default: + ch += '@' ; + break ; + } ; + } ; + + *q++ = ch ; + } ; + *q++ = '\0' ; + + fprintf(stderr, "%s", buffer) ; + } + fprintf(stderr, "']\n") ; +} ; +#endif + return (nbyte - nleft) ; } ; /*------------------------------------------------------------------------------ - * Write nbytes to fd from ptr -- BLOCKING + * Write nbytes to fd from buf -- BLOCKING * * Loops internally if gets EINTR. * * Returns: >= 0 -- number of bytes written - * < 0 => error + * -1 => error * * NB: if applied to a NON-BLOCKING fd, may return EAGAIN or EWOULDBLOCK */ -int -writen(int fd, const u_char *ptr, int nbytes) +extern int +writen(int fd, const void* buf, int nbytes) { int nleft; int nwritten; @@ -132,21 +219,23 @@ writen(int fd, const u_char *ptr, int nbytes) while (nleft > 0) { - nwritten = write(fd, ptr, nleft); + nwritten = write(fd, buf, nleft); - if (nwritten > 0) + if (nwritten >= 0) { + /* write() is not expected to return 0 unless the request is 0, + * which in this case it isn't. So cannot happen -- and if it did, + * wouldn't know what else to do with it ! + */ nleft -= nwritten; - ptr += nwritten; + buf = (const char*)buf + nwritten; } - else if (nwritten == 0) - break ; else { if (errno != EINTR) - return (nwritten); - } - } + return -1 ; + } ; + } ; return nbytes - nleft; } @@ -163,29 +252,39 @@ writen(int fd, const u_char *ptr, int nbytes) * Writing zero bytes is defined for "regular files", but not for anything * else. */ -int -write_nb(int fd, void* buf, size_t nbyte) +extern int +write_nb(int fd, const void* buf, size_t nbyte) { - size_t nleft = nbyte ; + size_t nleft ; + + nleft = nbyte ; while (nleft > 0) { - int ret = write(fd, buf, nleft); + ssize_t ret ; - if (ret > 0) + if (qdebug_nb) + ret = write_qdebug_nb(fd, buf, nleft) ; + else + ret = write(fd, buf, nleft) ; + + if (ret > 0) { - buf = (char*)buf + ret ; + buf = (const char*)buf + ret ; nleft -= ret ; } else if (ret == 0) - break ; /* not sure can happen... but - cannot assume will go away */ + /* write() is not expected to return 0 unless the request is 0, + * which in this case it isn't. So cannot happen -- but if it were + * to happen, this treats it as another form of "EAGAIN" ! + */ + break ; else { - int err = errno ; - if ((err == EAGAIN) || (err == EWOULDBLOCK)) + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) break ; - if (err != EINTR) + if (errno != EINTR) +// assert(0) ; // PRO TEM return -1 ; /* failed */ } ; } ; @@ -194,12 +293,46 @@ write_nb(int fd, void* buf, size_t nbyte) } ; /*------------------------------------------------------------------------------ + * Copy from one fd to another -- blocking. + * + * NB: if applied to a NON-BLOCKING fd, may return EAGAIN or EWOULDBLOCK + */ +extern int +copyn (int dst_fd, int src_fd) +{ + void* buffer ; + int r, err ; + + enum { buffer_size = 64 * 1024 } ; + buffer = XMALLOC(MTYPE_TMP, buffer_size) ; + + do + { + r = readn(src_fd, buffer, buffer_size) ; + if (r > 0) + r = writen(dst_fd, buffer, r) ; + } + while (r > 0) ; + + err = errno ; + + XFREE(MTYPE_TMP, buffer) ; + + errno = err ; + return r ; +} ; + +/*============================================================================== + * + */ + +/*------------------------------------------------------------------------------ * Set fd to non-blocking * * Returns: 0 => OK * -1 => failed */ -int +extern int set_nonblocking(int fd) { int flags; @@ -220,3 +353,52 @@ set_nonblocking(int fd) } return 0; } + +/*============================================================================== + * Simulate read/write with tiny buffers and a lot of blocking. + */ + +static qrand_seq_t rseq = QRAND_SEQ_INIT(2001) ; +static qrand_seq_t wseq = QRAND_SEQ_INIT(3001) ; + +static const int blocking_errs[] = { EAGAIN, EWOULDBLOCK, EINTR } ; + +/*------------------------------------------------------------------------------ + * Simulate read(), with tiny input buffer and lots of blocking. + */ +static ssize_t +read_qdebug_nb(int fd, void* buf, size_t nbyte) +{ + if (nbyte > 0) + { + if (qrand(rseq, 3) == 0) /* 1/3 chance of blocking */ + { + errno = blocking_errs[qrand(rseq, 3)] ; + return -1 ; + } ; + + nbyte = qrand(rseq, (nbyte < 200) ? nbyte : 200) + 1 ; + } ; + + + return read(fd, buf, nbyte) ; +} ; + +/*------------------------------------------------------------------------------ + * Simulate write(), with tiny input buffer and lots of blocking. + */ +static ssize_t +write_qdebug_nb(int fd, const void* buf, size_t nbyte) +{ + assert(nbyte > 0) ; + + if (qrand(wseq, 3) == 0) /* 1/3 chance of blocking */ + { + errno = blocking_errs[qrand(wseq, 3)] ; + return -1 ; + } ; + + nbyte = qrand(wseq, (nbyte < 200) ? nbyte : 200) + 1 ; + + return write(fd, buf, nbyte) ; +} ; diff --git a/lib/network.h b/lib/network.h index 72d38b52..0e626e72 100644 --- a/lib/network.h +++ b/lib/network.h @@ -26,8 +26,9 @@ /* Both readn and writen are deprecated and will be removed. They are not suitable for use with non-blocking file descriptors. */ -extern int readn (int, u_char *, int); -extern int writen (int, const u_char *, int); +extern int readn (int, void*, int); +extern int writen (int, const void*, int); +extern int copyn (int dst_fd, int src_fd) ; /* Set the file descriptor to use non-blocking I/O. Returns 0 for success, -1 on error. */ @@ -35,7 +36,7 @@ extern int set_nonblocking(int fd); /* Non-Blocking versions of read/write */ int read_nb(int fd, void* buf, size_t nbyte) ; -int write_nb(int fd, void* buf, size_t nbyte) ; +int write_nb(int fd, const void* buf, size_t nbyte) ; /* Does the I/O error indicate that the operation should be retried later? */ #define ERRNO_IO_RETRY(EN) \ diff --git a/lib/privs.c b/lib/privs.c index be3265ed..bcee6228 100644 --- a/lib/privs.c +++ b/lib/privs.c @@ -21,7 +21,8 @@ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ -#include <zebra.h> +#include "zebra.h" +#include "zconfig.h" #include "log.h" #include "privs.h" #include "memory.h" @@ -29,8 +30,8 @@ /* Needs to be qpthread safe */ static qpt_mutex_t privs_mutex; -#define LOCK qpt_mutex_lock(&privs_mutex); -#define UNLOCK qpt_mutex_unlock(&privs_mutex); +#define LOCK qpt_mutex_lock(privs_mutex); +#define UNLOCK qpt_mutex_unlock(privs_mutex); #ifdef HAVE_CAPABILITIES /* sort out some generic internal types for: @@ -675,20 +676,28 @@ zprivs_state_null (void) void zprivs_init_r() { - qpt_mutex_init(&privs_mutex, qpt_mutex_quagga); + qpt_mutex_init(privs_mutex, qpt_mutex_quagga); } void zprivs_finish(void) { - qpt_mutex_destroy(&privs_mutex, 0); + qpt_mutex_destroy(privs_mutex, 0); } +/*------------------------------------------------------------------------------ + * Initialise privilege handling and set any running group. + * + * Returns with lowered privileges. + * + * Must be initialised before vty. Will be initialised very early in the + * morning, before daemonisation and therefore before any pthreads. + */ void zprivs_init(struct zebra_privs_t *zprivs) { struct passwd *pwentry = NULL; - struct group *grentry = NULL; + struct group *grentry = NULL; if (!zprivs) { @@ -696,18 +705,18 @@ zprivs_init(struct zebra_privs_t *zprivs) exit (1); } - LOCK - - /* NULL privs */ - if (! (zprivs->user || zprivs->group - || zprivs->cap_num_p || zprivs->cap_num_i) ) + /* NULL privs */ + if ( (zprivs->user == NULL) && (zprivs->group == NULL) + && (zprivs->cap_num_p == 0) + && (zprivs->cap_num_i == 0) ) { - zprivs->change = zprivs_change_null; + zprivs->change = zprivs_change_null; zprivs->current_state = zprivs_state_null; - UNLOCK + return; } + /* Get uid for configured user (if any) */ if (zprivs->user) { if ( (pwentry = getpwnam (zprivs->user)) ) @@ -719,11 +728,11 @@ zprivs_init(struct zebra_privs_t *zprivs) /* cant use log.h here as it depends on vty */ fprintf (stderr, "privs_init: could not lookup user %s\n", zprivs->user); - UNLOCK exit (1); } } + /* Get gid for vty_group and add to our groups */ grentry = NULL; if (zprivs->vty_group) @@ -736,7 +745,6 @@ zprivs_init(struct zebra_privs_t *zprivs) { fprintf (stderr, "privs_init: could not setgroups, %s\n", errtostr(errno, 0).str) ; - UNLOCK exit (1); } } @@ -744,11 +752,11 @@ zprivs_init(struct zebra_privs_t *zprivs) { fprintf (stderr, "privs_init: could not lookup vty group %s\n", zprivs->vty_group); - UNLOCK exit (1); } } + /* Get gid for configured group and switch to same, now. */ if (zprivs->group) { if ( (grentry = getgrnam (zprivs->group)) ) @@ -759,15 +767,13 @@ zprivs_init(struct zebra_privs_t *zprivs) { fprintf (stderr, "privs_init: could not lookup group %s\n", zprivs->group); - UNLOCK exit (1); } - /* change group now, forever. uid we do later */ + /* change group now, forever. uid we do later */ if ( setregid (zprivs_state.zgid, zprivs_state.zgid) ) { fprintf (stderr, "zprivs_init: could not setregid, %s\n", errtostr(errno, 0).str) ; - UNLOCK exit (1); } } @@ -775,7 +781,8 @@ zprivs_init(struct zebra_privs_t *zprivs) #ifdef HAVE_CAPABILITIES zprivs_caps_init (zprivs); #else /* !HAVE_CAPABILITIES */ - /* we dont have caps. we'll need to maintain rid and saved uid + + /* We don't have caps. we'll need to maintain rid and saved uid * and change euid back to saved uid (who we presume has all necessary * privileges) whenever we are asked to raise our privileges. * @@ -784,20 +791,23 @@ zprivs_init(struct zebra_privs_t *zprivs) zprivs_state.zsuid = geteuid(); if ( zprivs_state.zuid ) { + /* If the zuid != real uid, will set the saved uid == zuid, leaving + * just the two zuid to choose from -- though if the real uid is root, + * this makes little difference. + */ if ( setreuid (-1, zprivs_state.zuid) ) { fprintf (stderr, "privs_init (uid): could not setreuid, %s\n", errtoa(errno, 0).str); - UNLOCK exit (1); } } - zprivs->change = zprivs_change_uid; - zprivs->current_state = zprivs_state_uid; + zprivs->change = zprivs_change_uid ; + zprivs->current_state = zprivs_state_uid ; + #endif /* HAVE_CAPABILITIES */ - UNLOCK } void @@ -826,9 +836,9 @@ zprivs_terminate (struct zebra_privs_t *zprivs) } #endif /* HAVE_LCAPS */ - zprivs->change = zprivs_change_null; + zprivs->change = zprivs_change_null; zprivs->current_state = zprivs_state_null; - zprivs_null_state = ZPRIVS_LOWERED; + zprivs_null_state = ZPRIVS_LOWERED; raise_count = 0; UNLOCK @@ -841,12 +851,12 @@ zprivs_get_ids(struct zprivs_ids_t *ids) LOCK ids->uid_priv = getuid(); - (zprivs_state.zuid) ? (ids->uid_normal = zprivs_state.zuid) - : (ids->uid_normal = -1); - (zprivs_state.zgid) ? (ids->gid_normal = zprivs_state.zgid) - : (ids->gid_normal = -1); + (zprivs_state.zuid) ? (ids->uid_normal = zprivs_state.zuid) + : (ids->uid_normal = -1); + (zprivs_state.zgid) ? (ids->gid_normal = zprivs_state.zgid) + : (ids->gid_normal = -1); (zprivs_state.vtygrp) ? (ids->gid_vty = zprivs_state.vtygrp) - : (ids->gid_vty = -1); + : (ids->gid_vty = -1); UNLOCK return; diff --git a/lib/pthread_safe.c b/lib/pthread_safe.c index e2c7cdc0..828b8e29 100644 --- a/lib/pthread_safe.c +++ b/lib/pthread_safe.c @@ -203,7 +203,7 @@ errtox(strerror_t* st, int err, int len, int want) if ((len <= 0) || (len >= (int)sizeof(st->str))) len = sizeof(st->str) - 1 ; - qfs_init(&qfs, st->str, len + 1) ; + qfs_init(qfs, st->str, len + 1) ; q = "" ; ql = 0 ; @@ -214,15 +214,15 @@ errtox(strerror_t* st, int err, int len, int want) const char* name = errno_name_lookup(err) ; if (name != NULL) - qfs_append(&qfs, name) ; + qfs_append(qfs, name) ; else - qfs_printf(&qfs, "ERRNO=%d", err) ; + qfs_printf(qfs, "ERRNO=%d", err) ; } ; /* name and string ? */ if (want == 3) { - qfs_append(&qfs, " ") ; + qfs_append(qfs, " ") ; q = "'" ; ql = 2 ; } ; @@ -285,8 +285,8 @@ errtox(strerror_t* st, int err, int len, int want) else { qf_str_t qfs_b ; - qfs_init(&qfs_b, buf, sizeof(buf)) ; - qfs_printf(&qfs_b, "strerror%s(%d) returned error %d", + qfs_init(qfs_b, buf, sizeof(buf)) ; + qfs_printf(qfs_b, "strerror%s(%d) returned error %d", qpthreads_enabled ? "_r" : "", err, ret) ; errm = buf ; } ; @@ -296,11 +296,11 @@ errtox(strerror_t* st, int err, int len, int want) /* Add strerror to the result... looking out for overflow. */ len = strlen(errm) ; - if ((len + ql) <= qfs_left(&qfs)) /* accounting for "quotes" */ - qfs_printf(&qfs, "%s%s%s", q, errm, q) ; + 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, (int)(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 ! */ } ; @@ -421,7 +421,7 @@ eaitox(strerror_t* st, int eai, int err, int len, int want) if ((len <= 0) || (len >= (int)sizeof(st->str))) len = sizeof(st->str) - 1 ; - qfs_init(&qfs, st->str, len + 1) ; + qfs_init(qfs, st->str, len + 1) ; q = "" ; ql = 0 ; @@ -432,15 +432,15 @@ eaitox(strerror_t* st, int eai, int err, int len, int want) const char* name = eaino_name_lookup(eai) ; if (name != NULL) - qfs_append(&qfs, name) ; + qfs_append(qfs, name) ; else - qfs_printf(&qfs, "EAI=%d", eai) ; + qfs_printf(qfs, "EAI=%d", eai) ; } ; /* name and string ? */ if (want == 3) { - qfs_append(&qfs, " ") ; + qfs_append(qfs, " ") ; q = "'" ; ql = 2 ; } ; @@ -458,10 +458,10 @@ eaitox(strerror_t* st, int eai, int err, int len, int want) /* Add strerror to the result... looking out for overflow. */ len = strlen(eaim) ; - if ((len + ql) <= qfs_left(&qfs)) /* accounting for "quotes" */ - qfs_printf(&qfs, "%s%s%s", q, eaim, q) ; + if ((len + ql) <= qfs_left(qfs)) /* accounting for "quotes" */ + qfs_printf(qfs, "%s%s%s", q, eaim, q) ; else - qfs_printf(&qfs, "%s%.*s...%s", q, qfs_left(&qfs) - ql - 3, eaim, q) ; + qfs_printf(qfs, "%s%.*s...%s", q, qfs_left(qfs) - ql - 3, eaim, q) ; /* -ve precision is ignored ! */ } ; diff --git a/lib/qdebug_nb.h b/lib/qdebug_nb.h new file mode 100644 index 00000000..6da2a120 --- /dev/null +++ b/lib/qdebug_nb.h @@ -0,0 +1,61 @@ +/* Debug definitions for non-blocking I/O testing + * Copyright (C) 2010 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_QDEBUG_NB_H +#define _ZEBRA_QDEBUG_NB_H + +#include "misc.h" +#include "qrand.h" + +/*------------------------------------------------------------------------------ + * Sort out QDEBUG_NB_DEBUG. + * + * Set to 1 if defined, but blank. + * Set to QDEBUG if not defined. + * + * Force to 0 if VTY_NO_DEBUG is defined and not zero. + * + * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to: + * + * * can set QDEBUG_NB_DEBUG == 0 to turn off debug + * * or set QDEBUG_NB_DEBUG != 0 to turn on debug + * * or set QDEBUG_NB_NO_DEBUG != to force debug off + */ + +#ifdef QDEBUG_NB /* If defined, make it 1 or 0 */ +# if IS_BLANK_OPTION(QDEBUG_NB) +# undef QDEBUG_NB +# define QDEBUG_NB 1 +# endif +#else /* If not defined, follow QDEBUG */ +# define QDEBUG_NB QDEBUG +#endif + +#ifdef QDEBUG_NB_NO /* Override, if defined */ +# if IS_NOT_ZERO_OPTION(QDEBUG_NB_NO) +# undef QDEBUG_NB +# define QDEBUG_NB 0 +# endif +#endif + +enum { qdebug_nb = QDEBUG_NB } ; + +#endif /* _ZEBRA_QDEBUG_NB_H */ diff --git a/lib/qfstring.c b/lib/qfstring.c index f40f1417..3639bccb 100644 --- a/lib/qfstring.c +++ b/lib/qfstring.c @@ -651,11 +651,11 @@ enum arg_num_type ant_default = ant_int, }; -static enum pf_phase qfs_arg_string(qf_str qfs, va_list args, +static enum pf_phase qfs_arg_string(qf_str qfs, const char* src, enum pf_flags flags, int width, int precision) ; -static enum pf_phase qfs_arg_char(qf_str qfs, va_list args, +static enum pf_phase qfs_arg_char(qf_str qfs, char ch, enum pf_flags flags, int width, int precision) ; -static enum pf_phase qfs_arg_number(qf_str qfs, va_list args, +static enum pf_phase qfs_arg_number(qf_str qfs, va_list* p_va, enum pf_flags flags, int width, int precision, enum arg_num_type ant) ; /*------------------------------------------------------------------------------ @@ -666,24 +666,30 @@ static enum pf_phase qfs_arg_number(qf_str qfs, va_list args, extern void qfs_printf(qf_str qfs, const char* format, ...) { - va_list args; + va_list va ; - va_start (args, format); - qfs_vprintf(qfs, format, args); - va_end (args); + va_start (va, format); + qfs_vprintf(qfs, format, va); + va_end (va); } ; /*------------------------------------------------------------------------------ * Formatted print to qf_str -- cf vprintf() * * This operation is async-signal-safe. + * + * Operates on a copy of the va_list -- so the original is *unchanged*. */ extern void -qfs_vprintf(qf_str qfs, const char *format, va_list args) +qfs_vprintf(qf_str qfs, const char *format, va_list va) { + va_list vac ; + if (format == NULL) return ; + va_copy(vac, va) ; + while ((qfs->ptr < qfs->end) && (*format != '\0')) { /* Have space for one byte and current format byte is not '\0' */ @@ -768,11 +774,11 @@ qfs_vprintf(qf_str qfs, const char *format, va_list args) if (!star && !digit && (phase <= pfp_width)) { phase = pfp_width ; - width = va_arg(args, int) ; + width = va_arg(vac, int) ; } else if (!star && !digit && (phase == pfp_precision)) { - precision = va_arg(args, int) ; + precision = va_arg(vac, int) ; if (precision < 0) { precision = 0 ; @@ -825,34 +831,36 @@ qfs_vprintf(qf_str qfs, const char *format, va_list args) if (phase == pfp_num_type) phase = pfp_failed ; /* don't do 'l' etc. */ else - phase = qfs_arg_string(qfs, args, flags, width, precision) ; + phase = qfs_arg_string(qfs, va_arg(vac, char*), + flags, width, precision) ; break ; case 'c': if (phase == pfp_num_type) phase = pfp_failed ; /* don't do 'l' etc. */ else - phase = qfs_arg_char(qfs, args, flags, width, precision) ; + phase = qfs_arg_char(qfs, (char)va_arg(vac, int), + flags, width, precision) ; break ; case 'd': case 'i': - phase = qfs_arg_number(qfs, args, flags, width, precision, + phase = qfs_arg_number(qfs, &vac, flags, width, precision, ant) ; break ; case 'u': - phase = qfs_arg_number(qfs, args, flags | pf_unsigned, width, + phase = qfs_arg_number(qfs, &vac, flags | pf_unsigned, width, precision, ant) ; break ; case 'x': - phase = qfs_arg_number(qfs, args, flags | pf_hex_x, width, + phase = qfs_arg_number(qfs, &vac, flags | pf_hex_x, width, precision, ant) ; break ; case 'X': - phase = qfs_arg_number(qfs, args, flags | pf_hex_X, width, + phase = qfs_arg_number(qfs, &vac, flags | pf_hex_X, width, precision, ant) ; break ; @@ -860,7 +868,7 @@ qfs_vprintf(qf_str qfs, const char *format, va_list args) if (phase == pfp_num_type) phase = pfp_failed ; else - phase = qfs_arg_number(qfs, args, flags | pf_void_p, width, + phase = qfs_arg_number(qfs, &vac, flags | pf_void_p, width, precision, ant_ptr_t) ; break ; @@ -879,6 +887,8 @@ qfs_vprintf(qf_str qfs, const char *format, va_list args) } ; *qfs->ptr = '\0' ; + + va_end(vac) ; } ; /*------------------------------------------------------------------------------ @@ -902,14 +912,11 @@ qfs_vprintf(qf_str qfs, const char *format, va_list args) * This operation is async-signal-safe. */ static enum pf_phase -qfs_arg_string(qf_str qfs, va_list args, enum pf_flags flags, +qfs_arg_string(qf_str qfs, const char* src, enum pf_flags flags, int width, int precision) { - const char* src ; int len ; - src = va_arg(args, char*) ; - if (flags != (flags & pf_precision)) return pfp_failed ; @@ -949,13 +956,8 @@ qfs_arg_string(qf_str qfs, va_list args, enum pf_flags flags, * This operation is async-signal-safe. */ static enum pf_phase -qfs_arg_char(qf_str qfs, va_list args, enum pf_flags flags, - int width, int precision) +qfs_arg_char(qf_str qfs, char ch, enum pf_flags flags, int width, int precision) { - unsigned char ch ; - - ch = va_arg(args, int) ; - if ((flags != 0) || (precision != 0)) return pfp_failed ; @@ -986,7 +988,7 @@ qfs_arg_char(qf_str qfs, va_list args, enum pf_flags flags, * This operation is async-signal-safe. */ static enum pf_phase -qfs_arg_number(qf_str qfs, va_list args, enum pf_flags flags, +qfs_arg_number(qf_str qfs, va_list* p_va, enum pf_flags flags, int width, int precision, enum arg_num_type ant) { uintmax_t u_val ; @@ -995,7 +997,7 @@ qfs_arg_number(qf_str qfs, va_list args, enum pf_flags flags, /* Special for hex with '0... if no explicit precision, set -1 for byte * and -2 for everything else -- see qfs_number(). */ - if (((flags & pf_precision) == 0) && (flags & pf_hex)) + if ((flags & (pf_hex | pf_precision)) == pf_hex) { if ((flags & (pf_commas | pf_zeros)) == (pf_commas | pf_zeros)) { @@ -1013,31 +1015,28 @@ qfs_arg_number(qf_str qfs, va_list args, enum pf_flags flags, { case ant_char: case ant_short: - u_val = va_arg(args, int) ; - break ; - case ant_int: - u_val = va_arg(args, unsigned int) ; + u_val = va_arg(*p_va, unsigned int) ; break ; case ant_long: - u_val = va_arg(args, unsigned long) ; + u_val = va_arg(*p_va, unsigned long) ; break ; case ant_long_long: - u_val = va_arg(args, unsigned long long) ; + u_val = va_arg(*p_va, unsigned long long) ; break ; case ant_intmax_t: - u_val = va_arg(args, uintmax_t) ; + u_val = va_arg(*p_va, uintmax_t) ; break ; case ant_size_t: - u_val = va_arg(args, size_t) ; + u_val = va_arg(*p_va, size_t) ; break ; case ant_ptr_t: - u_val = va_arg(args, uintptr_t) ; + u_val = va_arg(*p_va, uintptr_t) ; break ; default: @@ -1052,31 +1051,28 @@ qfs_arg_number(qf_str qfs, va_list args, enum pf_flags flags, { case ant_char: case ant_short: - s_val = va_arg(args, int) ; - break ; - case ant_int: - s_val = va_arg(args, signed int) ; + s_val = va_arg(*p_va, signed int) ; break ; case ant_long: - s_val = va_arg(args, signed long) ; + s_val = va_arg(*p_va, signed long) ; break ; case ant_long_long: - s_val = va_arg(args, signed long long) ; + s_val = va_arg(*p_va, signed long long) ; break ; case ant_intmax_t: - s_val = va_arg(args, intmax_t) ; + s_val = va_arg(*p_va, intmax_t) ; break ; case ant_size_t: - s_val = va_arg(args, ssize_t) ; + s_val = va_arg(*p_va, ssize_t) ; break ; case ant_ptr_t: - s_val = va_arg(args, intptr_t) ; + s_val = va_arg(*p_va, intptr_t) ; break ; default: @@ -1086,8 +1082,6 @@ qfs_arg_number(qf_str qfs, va_list args, enum pf_flags flags, qfs_signed(qfs, s_val, flags, width, precision) ; } ; - /* construct a digit string, the hard way */ - return pfp_done ; } ; diff --git a/lib/qfstring.h b/lib/qfstring.h index 990b4ef4..c57bd4ea 100644 --- a/lib/qfstring.h +++ b/lib/qfstring.h @@ -30,9 +30,6 @@ * strings, particularly where the string handling must be async-signal-safe. */ -typedef struct qf_str qf_str_t ; -typedef struct qf_str* qf_str ; - /* When initialised a qf_string is set: * * str = start of string -- and this is never changed @@ -47,6 +44,9 @@ struct qf_str char* end ; /* end of string */ } ; +typedef struct qf_str qf_str_t[1] ; +typedef struct qf_str* qf_str ; + /*------------------------------------------------------------------------------ * Print format flags for number printing */ diff --git a/lib/qiovec.c b/lib/qiovec.c index 38ea0dbb..2946efaa 100644 --- a/lib/qiovec.c +++ b/lib/qiovec.c @@ -22,6 +22,7 @@ #include <errno.h> #include "misc.h" +#include "qdebug_nb.h" #include "memory.h" #include "miyagi.h" @@ -54,12 +55,12 @@ * Returns: address of qiovec */ extern qiovec -qiovec_init_new(qiovec viov) +qiovec_init_new(qiovec qiov) { - if (viov == NULL) - viov = XCALLOC(MTYPE_QIOVEC, sizeof(struct qiovec)) ; + if (qiov == NULL) + qiov = XCALLOC(MTYPE_QIOVEC, sizeof(struct qiovec)) ; else - memset(viov, 0, sizeof(struct qiovec)) ; + memset(qiov, 0, sizeof(struct qiovec)) ; /* Zeroising has set: * @@ -74,7 +75,7 @@ qiovec_init_new(qiovec viov) * Nothing more is required. */ - return viov ; + return qiov ; } ; /*------------------------------------------------------------------------------ @@ -83,81 +84,188 @@ qiovec_init_new(qiovec viov) * Returns: address of qiovec (if any) -- NULL if structure released */ extern qiovec -qiovec_reset(qiovec viov, bool free_structure) +qiovec_reset(qiovec qiov, bool free_structure) { - if (viov != NULL) + if (qiov != NULL) { - if (viov->vec != NULL) - XFREE(MTYPE_QIOVEC_VEC, viov->vec) ; + if (qiov->vec != NULL) + XFREE(MTYPE_QIOVEC_VEC, qiov->vec) ; if (free_structure) - XFREE(MTYPE_QIOVEC, viov) ; /* sets viov = NULL */ + XFREE(MTYPE_QIOVEC, qiov) ; /* sets qiov = NULL */ else - qiovec_init_new(viov) ; /* re-initialise */ + qiovec_init_new(qiov) ; /* re-initialise */ } ; - return viov ; + return qiov ; } ; /*------------------------------------------------------------------------------ * Clear given qiovec. */ extern void -qiovec_clear(qiovec viov) +qiovec_clear(qiovec qiov) { - viov->i_get = 0 ; - viov->i_put = 0 ; - viov->writing = 0 ; + qiov->i_get = 0 ; + qiov->i_put = 0 ; + qiov->writing = 0 ; } ; /*------------------------------------------------------------------------------ - * Push item to given qiovec - * - * NB: avoids pushing zero length items. + * Establish total length of the qiov -- add up all the item lengths. */ -extern void -qiovec_push(qiovec viov, const void* base, size_t len) +extern size_t +qiovec_length(qiovec qiov) { - struct iovec* p_iov ; + if (qiov->i_put > qiov->i_get) + { + size_t length ; + uint n ; + qiov_item item ; - if (len == 0) - return ; + n = qiov->i_put - qiov->i_get ; + item = &qiov->vec[qiov->i_get] ; - if (viov->i_put >= viov->i_alloc) + length = item->len ; + while (--n) + length += (++item)->len ; + + return length ; + } + else { - size_t size ; - assert(viov->i_put == viov->i_alloc) ; + assert(qiov->i_get == qiov->i_put) ; + qiov->i_get = qiov->i_put = 0 ; - assert( ((viov->i_alloc == 0) && (viov->vec == NULL)) - || ((viov->i_alloc != 0) && (viov->vec != NULL)) ) ; + return 0 ; + } ; +} ; - if (viov->i_get > 200) /* keep in check */ - { - size = (viov->i_put - viov->i_get) * sizeof(struct iovec) ; - if (size != 0) - memmove(viov->vec, &viov->vec[viov->i_get], size) ; - viov->i_put -= viov->i_get ; - viov->i_get = 0 ; - } - else - { - viov->i_alloc += 100 ; /* a sizable chunk */ - size = viov->i_alloc * sizeof(struct iovec) ; - if (viov->vec == NULL) - viov->vec = XMALLOC(MTYPE_QIOVEC_VEC, size) ; - else - viov->vec = XREALLOC(MTYPE_QIOVEC_VEC, viov->vec, size) ; - } ; +/*============================================================================== + * Extending and shuffling qiovec in order to support push and unshift + * operations. + * + * Note that if unshift becomes common, then should adjust the following. + * Also address the tendency to reset i_get and i_put to zero whenever they + * are found to be equal. + */ +enum +{ + i_get_limit = 200, /* in qiovec_extend, shuffle vector down if + i_get is >= i_get_limit */ + + i_alloc_unit = 100, /* allocate in lots of this much */ + + i_get_margin = 20, /* in qiovec_shuffle, if have to reorganise, + allow for this many unshifts. */ + + i_put_margin = 40, /* in qiovec_shuffle, if have to reorganise, + allow for this many pushes. */ +} ; + +static void qiovec_move_vector(qiovec qiov, uint new_i_get) ; +static void qiovec_new_vector(qiovec qiov, uint items) ; + +/*------------------------------------------------------------------------------ + * Extend -- see qiovec_push. + * + * NB: underlying vector may be reorganised, reallocated, etc. All pointers + * to items are now INVALID. + */ +Private void +qiovec_extend(qiovec qiov) +{ + assert(qiov->i_put <= qiov->i_alloc) ; + assert(qiov->i_get <= qiov->i_put) ; + + assert( ((qiov->i_alloc == 0) && (qiov->vec == NULL)) + || ((qiov->i_alloc != 0) && (qiov->vec != NULL)) ) ; + + /* Short circuit if can make space by resetting indexes */ + if ((qiov->i_put == qiov->i_get) && (qiov->i_alloc != 0)) + { + qiov->i_put = 0 ; + qiov->i_get = 0 ; + + return ; } ; - p_iov = &viov->vec[viov->i_put++] ; + if (qiov->i_get > i_get_limit) /* keep in check */ + qiovec_move_vector(qiov, 0) ; + else + qiovec_new_vector(qiov, i_alloc_unit) ; +} ; + +/*------------------------------------------------------------------------------ + * Shuffle to allow for qiovec_unshift. + * + * Don't expect this to happen often -- but will do it if required. + * + * On exit the i_get will be > 0. + * + * NB: underlying vector may be reorganised, reallocated, etc. All pointers + * to items are now INVALID. + */ +Private void +qiovec_shuffle(qiovec qiov) +{ + assert(qiov->i_put <= qiov->i_alloc) ; + assert(qiov->i_get <= qiov->i_put) ; + + assert( ((qiov->i_alloc == 0) && (qiov->vec == NULL)) + || ((qiov->i_alloc != 0) && (qiov->vec != NULL)) ) ; + + if (qiov->i_get > 0) + return ; /* shouldn't be here, really */ + + /* If do not have enough spare space for both margins, allocate extra to + * the tune of both margins -- will leave at least the margins and at + * most nearly twice those margins. + * + * This will allocate a vector is there is none at all. + */ + if ((qiov->i_alloc - qiov->i_put) < (i_get_margin + i_put_margin)) + qiovec_new_vector(qiov, i_get_margin + i_put_margin) ; + + /* Shuffle into place and update i_get and i_put. */ + qiovec_move_vector(qiov, i_get_margin) ; - p_iov->iov_base = miyagi(base) ; - p_iov->iov_len = len ; + confirm(i_get_margin > 0) ; } ; /*------------------------------------------------------------------------------ + * Move contents of vector and set a new i_get (and i_put to suit). + */ +static void +qiovec_move_vector(qiovec qiov, uint new_i_get) +{ + memmove(&qiov->vec[new_i_get], &qiov->vec[qiov->i_get], + (qiov->i_put - qiov->i_get) * sizeof(qiovec_t)) ; + + qiov->i_put -= qiov->i_get - new_i_get ; + qiov->i_get = new_i_get ; +} ; + +/*------------------------------------------------------------------------------ + * Allocate or reallocate vector for given number of items. + */ +static void +qiovec_new_vector(qiovec qiov, uint items) +{ + qiov->i_alloc += items ; + + qiov->vec = XREALLOC(MTYPE_QIOVEC_VEC, qiov->vec, + qiov->i_alloc * sizeof(qiovec_t)) ; +} ; + +/*============================================================================== + * Writing qiovec and iovec + */ + +static ssize_t writev_qdebug_nb(int fd, struct iovec p_iov[], int n) ; + +/*------------------------------------------------------------------------------ * Write given qiovec -- assuming NON-BLOCKING. * * Does nothing if the qiovec is empty. @@ -171,24 +279,24 @@ qiovec_push(qiovec viov, const void* base, size_t len) * -1 => failed -- see errno */ extern int -qiovec_write_nb(int fd, qiovec viov) +qiovec_write_nb(int fd, qiovec qiov) { int n ; int l ; - n = viov->i_put - viov->i_get ; + n = qiov->i_put - qiov->i_get ; - l = iovec_write_nb(fd, &viov->vec[viov->i_get], n) ; + l = iovec_write_nb(fd, (struct iovec*)(&qiov->vec[qiov->i_get]), n) ; if (l == 0) { - viov->writing = 0 ; - viov->i_get = viov->i_put = 0 ; + qiov->writing = false ; + qiov->i_get = qiov->i_put = 0 ; } else { - viov->writing = 1 ; - viov->i_get += (n - l) ; + qiov->writing = true ; + qiov->i_get += (n - l) ; } ; return l ; @@ -223,8 +331,6 @@ qiovec_write_nb(int fd, qiovec viov) extern int iovec_write_nb(int fd, struct iovec p_iov[], int n) { - ssize_t ret ; - assert(n >= 0) ; /* Skip past any leading zero length entries */ @@ -236,7 +342,12 @@ iovec_write_nb(int fd, struct iovec p_iov[], int n) while (n > 0) { - ret = writev(fd, p_iov, (n < IOV_MAX ? n : IOV_MAX)) ; + ssize_t ret ; + + if (qdebug_nb) + ret = writev_qdebug_nb(fd, p_iov, (n < IOV_MAX ? n : IOV_MAX)) ; + else + ret = writev (fd, p_iov, (n < IOV_MAX ? n : IOV_MAX)) ; if (ret > 0) { @@ -257,6 +368,7 @@ iovec_write_nb(int fd, struct iovec p_iov[], int n) ret = 0 ; } ; } ; + } else if (ret == 0) break ; /* not sure can happen... but @@ -267,9 +379,59 @@ iovec_write_nb(int fd, struct iovec p_iov[], int n) if ((err == EAGAIN) || (err == EWOULDBLOCK)) break ; if (err != EINTR) +// assert(0) ; // Pro tem return -1 ; /* failed */ } ; } ; return n ; } ; + +/*============================================================================== + * Simulation of writev() for debug purposes -- generates lots of partial + * writes and lots of blocking + * + */ + +static qrand_seq_t wseq = QRAND_SEQ_INIT(4001) ; + +static const int blocking_errs[] = { EAGAIN, EWOULDBLOCK, EINTR } ; + +/*------------------------------------------------------------------------------ + * Simulate writev() with tiny output buffers and lots of blocking. + */ +static ssize_t +writev_qdebug_nb(int fd, struct iovec p_iov[], int n) +{ + struct iovec* q_iov ; + size_t len ; + ssize_t ret ; + + assert(n > 0) ; + + if (qrand(wseq, 3) == 0) /* 1/3 chance of blocking */ + { + errno = blocking_errs[qrand(wseq, 3)] ; + return -1 ; + } ; + + /* Process 1..n or 1..3 entries */ + n = qrand(wseq, (n < 3) ? n : 3) ; + q_iov = p_iov + n ; + ++n ; + + /* If new last entry is not zero length, write only part of + * it. + */ + len = q_iov->iov_len ; + if (len > 0) + q_iov->iov_len = qrand(wseq, (len < 200) ? len : 200) + 1 ; + + ret = writev(fd, p_iov, n) ; + + q_iov->iov_len = len ; /* restore true length of entry */ + + return ret ; +} ; + + diff --git a/lib/qiovec.h b/lib/qiovec.h index 230198f3..0cbf0364 100644 --- a/lib/qiovec.h +++ b/lib/qiovec.h @@ -26,22 +26,54 @@ #include <sys/uio.h> /* iovec stuff */ /*============================================================================== + * Alternative naming for struct iovec and its entries. + */ +struct qiov_item +{ + const char* base ; + size_t len ; +} ; + +typedef struct qiov_item* qiov_item ; +typedef struct qiov_item qiov_item_t[1] ; +typedef struct qiov_item* qiov_vector ; + +union +{ + struct qiov_item q ; + struct iovec s ; +} union_iovec ; + +CONFIRM(sizeof(struct qiov_item) == sizeof(struct iovec)) ; + +CONFIRM(offsetof(struct qiov_item, base) == offsetof(struct iovec, iov_base)) ; +CONFIRM(sizeof (union_iovec.q. base) == sizeof (union_iovec.s.iov_base)) ; +CONFIRM(offsetof(struct qiov_item, len) == offsetof(struct iovec, iov_len)) ; +CONFIRM(sizeof (union_iovec.q. len) == sizeof (union_iovec.s.iov_len)) ; + +/*============================================================================== * Flexible size "struct iovec" * * NB: a completely zero structure is a valid, empty qiovec. */ -typedef struct qiovec* qiovec ; -typedef struct qiovec qiovec_t ; struct qiovec { - struct iovec* vec ; /* the actual iovec array */ + qiov_vector vec ; /* the actual iovec array */ + + uint i_get ; /* next entry to get */ + uint i_put ; /* next entry to put */ + + uint i_alloc ; /* number of entries allocated */ bool writing ; /* started, but not finished */ +} ; - unsigned i_get ; /* next entry to get */ - unsigned i_put ; /* next entry to put */ +typedef struct qiovec* qiovec ; +typedef struct qiovec qiovec_t[1] ; - unsigned i_alloc ; /* number of entries allocated */ +enum +{ + QIOVEC_INIT_ALL_ZEROS = true } ; /*============================================================================== @@ -50,18 +82,39 @@ struct qiovec extern qiovec qiovec_init_new(qiovec qiov) ; extern qiovec qiovec_reset(qiovec qiov, bool free_structure) ; - -#define qiovec_reset_keep(qiov) qiovec_reset(qiov, 0) -#define qiovec_reset_free(qiov) qiovec_reset(qiov, 1) +Inline qiovec qiovec_free(qiovec qiov) ; Inline bool qiovec_empty(qiovec qiov) ; +extern size_t qiovec_length(qiovec qiov) ; extern void qiovec_clear(qiovec qiov) ; -extern void qiovec_push(qiovec qiov, const void* base, size_t len) ; extern int qiovec_write_nb(int fd, qiovec qiov) ; +Inline uint qiovec_count(qiovec qiov) ; +Inline void qiovec_push_this(qiovec qiov, const void* base, size_t len) ; +Inline void qiovec_push(qiovec qiov, qiov_item item) ; +Inline void qiovec_pop(qiovec qiov, qiov_item item) ; +Inline void qiovec_shift(qiovec qiov, qiov_item item) ; +Inline void qiovec_unshift(qiovec qiov, qiov_item item) ; + extern int iovec_write_nb(int fd, struct iovec* p_iov, int n) ; Inline void iovec_set(struct iovec* p_iov, const void* base, size_t len) ; +Private void qiovec_extend(qiovec qiov) ; +Private void qiovec_shuffle(qiovec qiov) ; + +/*------------------------------------------------------------------------------ + * Free given qiovec + * + * It is the caller's responsibility to free anything that the qiovec may + * point to. + * + * Returns: NULL + */ +Inline qiovec qiovec_free(qiovec qiov) +{ + return qiovec_reset(qiov, free_it) ; +} ; + /*------------------------------------------------------------------------------ * Is given qiov empty ? * @@ -75,6 +128,107 @@ qiovec_empty(qiovec qiov) } ; /*------------------------------------------------------------------------------ + * How many entries in the given qiov ? + */ +Inline uint +qiovec_count(qiovec qiov) +{ + return (qiov->i_put - qiov->i_get) ; +} ; + +/*------------------------------------------------------------------------------ + * Push to qiov -- same as qiovec_push, but with item. + * + * Ignores zero length items. + */ +Inline void +qiovec_push_this(qiovec qiov, const void* base, size_t len) +{ + qiov_item_t item ; + + item->base = base ; + item->len = len ; + qiovec_push(qiov, item) ; +} ; + +/*------------------------------------------------------------------------------ + * Push (copy of) item to qiov -- ie append it to the end of the vector. + */ +Inline void +qiovec_push(qiovec qiov, qiov_item item) +{ + if (item->len != 0) + { + if (qiov->i_put >= qiov->i_alloc) + qiovec_extend(qiov) ; + + qiov->vec[qiov->i_put++] = *item ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Pop last item from qiov -- return empty item if none. + */ +Inline void +qiovec_pop(qiovec qiov, qiov_item item) +{ + if (qiov->i_get < qiov->i_put) + { + *item = qiov->vec[--qiov->i_put] ; + if (qiov->i_get == qiov->i_put) + qiov->i_get = qiov->i_put = 0 ; + } + else + { + assert(qiov->i_get == qiov->i_put) ; + item->base = NULL ; + item->len = 0 ; + + qiov->i_get = qiov->i_put = 0 ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Shift first item off qiov -- return empty item if none. + */ +Inline void +qiovec_shift(qiovec qiov, qiov_item item) +{ + if (qiov->i_get < qiov->i_put) + { + *item = qiov->vec[qiov->i_get++] ; + if (qiov->i_get == qiov->i_put) + qiov->i_get = qiov->i_put = 0 ; + } + else + { + assert(qiov->i_get == qiov->i_put) ; + item->base = NULL ; + item->len = 0 ; + + qiov->i_get = qiov->i_put = 0 ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Unshift item onto qiov -- ie prepend it to the start of the vector. + */ +Inline void +qiovec_unshift(qiovec qiov, qiov_item item) +{ + if (qiov->i_get == 0) + { + if (qiov->i_put == 0) + return qiovec_push(qiov, item) ; + + qiovec_shuffle(qiov) ; + } ; + + if (item->len != 0) + qiov->vec[--qiov->i_get] = *item ; +} ; + +/*------------------------------------------------------------------------------ * Set a given struct iovec * * Gets around the fact that the standard structure does not have a const diff --git a/lib/qlib_init.c b/lib/qlib_init.c index c44de575..d3fde339 100644 --- a/lib/qlib_init.c +++ b/lib/qlib_init.c @@ -22,12 +22,14 @@ #include "qlib_init.h" #include "zassert.h" #include "memory.h" +#include "qpnexus.h" #include "qpthreads.h" #include "qpselect.h" #include "thread.h" #include "privs.h" #include "mqueue.h" #include "pthread_safe.h" +#include "log_local.h" /*============================================================================== * Quagga Library Initialise/Closedown @@ -66,30 +68,33 @@ * */ -void +extern void qlib_init_first_stage(void) { qps_start_up() ; } -void -qlib_init_second_stage(int pthreads) +extern void +qlib_init_second_stage(bool pthreads) { qpt_set_qpthreads_enabled(pthreads); + qpn_init() ; memory_init_r(); thread_init_r(); + log_init_r() ; zprivs_init_r(); mqueue_initialise(); safe_init_r(); } -void +extern void qexit(int exit_code) { safe_finish(); mqueue_finish(); zprivs_finish(); + log_finish(); thread_finish(); memory_finish(); exit (exit_code); diff --git a/lib/qlib_init.h b/lib/qlib_init.h index 0d5fbd7e..6c6c1a01 100644 --- a/lib/qlib_init.h +++ b/lib/qlib_init.h @@ -22,6 +22,8 @@ #ifndef _ZEBRA_QLIB_INIT_H #define _ZEBRA_QLIB_INIT_H +#include "misc.h" + /*============================================================================== * Quagga Library Initialise/Closedown * @@ -33,7 +35,7 @@ extern void qlib_init_first_stage(void) ; -extern void qlib_init_second_stage(int pthreads) ; +extern void qlib_init_second_stage(bool pthreads) ; extern void qexit(int exit_code) ; diff --git a/lib/qpath.c b/lib/qpath.c index 07c294de..e3f690a6 100644 --- a/lib/qpath.c +++ b/lib/qpath.c @@ -24,6 +24,9 @@ #include "zassert.h" +#include <unistd.h> +#include <errno.h> + /*============================================================================== * Some primitive path handling, based on qstrings. * @@ -43,35 +46,40 @@ * */ +static void qpath_reduce(qpath qp) ; + /*------------------------------------------------------------------------------ * Initialise a brand new qpath -- allocate if required. * - * If a path is given, set that path -- allocating body even if path is zero - * length. - * - * If no path is given, leaves qpath with no body. - * - * If path is given, the qpath is set and reduced (see above). + * The result is a path with a not-empty body. All qpath values may be assumed + * to have a body at all times. * * Returns: address of qpath * * NB: assumes initialising a new structure. If not, then caller should - * use qpath_reset() or qs_clear(). + * use qpath_reset() or qpath_clear(). */ extern qpath -qpath_init_new(qpath qp, const char* path) +qpath_init_new(qpath qp) { if (qp == NULL) qp = XCALLOC(MTYPE_QPATH, sizeof(qpath_t)) ; else memset(qp, 0, sizeof(qpath_t)) ; - /* Worry about fields other than the path */ + qs_init_new(qp->path, 50) ; /* Always have a body */ - qs_init_new(qp->path, 0) ; + return qp ; +} ; - if (path != NULL) - qpath_set(qp, path) ; +/*------------------------------------------------------------------------------ + * Create a new qpath if don't already have one. + */ +inline static qpath +qpath_make_if_null(qpath qp) +{ + if (qp == NULL) + qp = qpath_init_new(NULL) ; return qp ; } ; @@ -82,172 +90,251 @@ qpath_init_new(qpath qp, const char* path) * Discards all the contents of the qpath. */ extern qpath -qpath_reset(qpath qp, bool free_structure) +qpath_reset(qpath qp, free_keep_b free_structure) { if (qp == NULL) return NULL ; - qs_reset_keep(&qp->path, keep_it) ; + qs_reset(qp->path, keep_it) ; if (free_structure) XFREE(MTYPE_QPATH, qp) ; /* sets qp = NULL */ else - /* Worry about fields other than the path */ ; return qp ; } ; /*------------------------------------------------------------------------------ - * Set given qpath to copy of the given string -- allocate if required. - * - * If setting an existing qpath, discards any existing contents -- so the qpath - * MUST have been initialised at some time (qpath_init_new). Keeps any body - * that has been allocated if possible. - * - * Reduces the path (see above). - * - * Sets the path len, but does not touch the path cp. + * Clear down given qpath -- retaining any body, but setting it empty. */ extern qpath -qpath_set(qpath qp, const char* path) +qpath_clear(qpath qp) { if (qp == NULL) - qp = qpath_init_new(NULL, path) ; - else - { - if (path != NULL) - qs_set(&qp->path, path) ; - else - qs_clear(qp->path) ; - /* Worry about fields other than the path */ - } ; + return NULL ; - qpath_reduce(qp) ; + qs_clear(qp->path) ; return qp ; } ; /*------------------------------------------------------------------------------ - * Reduce multiple '/' to single '/' (except for exactly "//" at start). + * Set given qpath to copy of the given string -- allocate if required. * - * Reduce "/./" to "/". + * Reduces the path (see above). */ -static void -qpath_reduce(qpath qp, size_t off) +extern qpath +qpath_set(qpath dst, const char* src) { - qpath part ; - qstring qs ; - char* sp ; - char* p ; - char* q ; + return qpath_set_n(dst, src, (src != NULL) ? strlen(src) : 0) ; +} ; - if (qp == NULL) - { - assert(off == 0) ; - return ; /* NULL qpath is empty */ - } ; +/*------------------------------------------------------------------------------ + * Set given qpath to copy of the given qstring -- allocate if required. + * + * Reduces the path (see above). + */ +extern qpath +qpath_set_qs(qpath dst, const qstring src) +{ + return qpath_set_n(dst, qs_char(src), qs_len(src)) ; +} ; - qs = &qp->path ; - assert(off <= qs->len) ; /* Make sure 'off' is kosher */ +/*------------------------------------------------------------------------------ + * Set given qpath to copy of the given string -- allocate if required. + * + * Reduces the path (see above). + */ +extern qpath +qpath_set_n(qpath dst, const char* src, ulen n) +{ + dst = qpath_make_if_null(dst) ; - sp = qs_chars(qs) ; /* NULL if qpath is completely empty */ + qs_set_n(dst->path, src, n) ; - if (sp == NULL) - return ; /* NULL path part is completely empty */ + qpath_reduce(dst) ; - p = sp + off ; + return dst ; +} ; - /* Deal with special case of "//" at start. - * - * If find "//x", where x is anything other than '/', step past the first - * '/'. Could step past both "//", but that stops it seeing "//./zzz" - */ - if ((*p == '/') && (*(p + 1) == '/') && (*(p + 2) != '/')) - ++p ; +/*------------------------------------------------------------------------------ + * Copy qpath to the given qpath -- creating qpath if required. + * The result is an empty qpath if the given one is NULL. + */ +extern qpath +qpath_copy(qpath dst, const qpath src) +{ + if (src != NULL) + return qpath_set_n(dst, qs_char_nn(src->path), qs_len_nn(src->path)) ; + else + return qpath_set_n(dst, NULL, 0) ; +} ; - /* Scan to see if there is anything that needs to be fixed. - * - * Looking for "//" and "/./". - */ - while (1) - { - if (*p++ == '\0') - return ; /* nothing to do if hit end of string */ +/*============================================================================== + * Interfaces to system. + */ - if ( (*p == '/') || ((*p == '.') && (*(p + 1) == '/')) ) - { - if (*(p - 1) == '/') - break ; /* found "//" or "/./" */ - } - } ; +/*------------------------------------------------------------------------------ + * Get the current working directory -- creates a qpath if required. + * + * Returns: the (new) qpath if OK + * NULL if not OK -- any existing qpath is cleared. + * + * If fails will be because some directory on the way back to the root is + * not readable or searchable (!). + */ +extern qpath +qpath_getcwd(qpath dst) +{ + qpath od ; - /* Rats... there is something to be fixed. - * - * *p is second '/' of "//" or '.' of "/./". - */ - q = p ; + od = dst ; + dst = qpath_make_if_null(dst) ; - while (*p != '\0') + qs_new_size(dst->path, 50) ; + + while (1) { - /* Step past any number of '/' and any number of "./". */ - while (1) - { - while (*p == '/') - ++p ; + void* r ; + usize s ; - if ((*p != '.') || (*p != '/')) - break ; + s = qs_size_nn(dst->path) ; + r = getcwd(qs_char_nn(dst->path), s) ; - p += 2 ; + if (r != NULL) + { + qs_set_strlen_nn(dst->path) ; + return dst ; /* exit here if OK. */ } ; - /* Scan, copying stuff, until get to '\0' or find "//" or "/./" */ - while (*p != '\0') - { - *q++ = *p++ ; /* copy non-'\0' */ + if (errno != ERANGE) + break ; /* exit here on failure */ - if ( (*p == '/') || ((*p == '.') && (*(p + 1) == '/')) ) - { - if (*(p - 1) == '/') - break ; /* found "//" or "/./" */ - } ; - } ; + qs_new_size(dst->path, s * 2) ; } ; - /* Adjust the length and terminate */ + if (od == NULL) + qpath_reset(dst, free_it) ; + else + qpath_clear(dst) ; - qs->len = (q - sp) ; /* set the new length (shorter !) */ - *q = '\0' ; /* and terminate */ + return NULL ; } ; /*------------------------------------------------------------------------------ - * Make a copy of the given qpath. + * Set the current working directory * - * Creates a brand new qpath object, which is a full copy of the given one. + * Returns: 0 <=> OK + * errno otherwise + */ +extern int +qpath_setcwd(qpath qp) +{ + return (chdir(qpath_string(qp)) == 0) ? 0 : errno ; +} ; + +/*------------------------------------------------------------------------------ + * Do "stat" for given path. * - * The result is an empty qpath if the given one is NULL. + * Returns: 0 <=> OK + * errno otherwise */ -extern qpath -qpath_copy(qpath qp_x) +extern int +qpath_stat(qpath qp, struct stat* sbuf) { - return qpath_init_new(NULL, qpath_path(qp_x)) ; + return (stat(qpath_string(qp), sbuf) == 0) ? 0 : errno ; } ; /*------------------------------------------------------------------------------ - * Make a copy of a qpath to the given qpath. + * Is given path a file we might access -- according to stat ? * - * If required, creates new qpath object -- so qpath_copy_to(NULL, ...) is the - * same as qpath_copy(...). + * Returns: -1 <=> no -- can access etc, but it's not a file + * 0 <=> yes + * errno otherwise + */ +extern int +qpath_stat_is_file(qpath qp) +{ + struct stat sbuf[1] ; + int err ; + + err = qpath_stat(qp, sbuf) ; + + return (err == 0) ? (S_ISREG(sbuf->st_mode) ? 0 : -1) + : err ; +} ; + +/*------------------------------------------------------------------------------ + * Is given path a directory we might access -- according to stat ? * - * The result is an empty qpath if the given one is NULL. + * Returns: -1 <=> no -- can access etc, but it's not a directory + * 0 <=> yes + * errno otherwise + */ +extern int +qpath_stat_is_directory(qpath qp) +{ + struct stat sbuf[1] ; + int err ; + + err = qpath_stat(qp, sbuf) ; + + return (err == 0) ? (S_ISDIR(sbuf->st_mode) ? 0 : -1) + : err ; +} ; + +/*============================================================================== + * Path editing functions + * + * + */ + +/*------------------------------------------------------------------------------ + * Shave any file part off the end of the given path. + * + * This treats anything after the last '/' of the path as being the "file part". + * + * The cases are (where 'a' is anything except '/' and 'z' is anything): + * + * 1. "" -- empty -> unchanged + * + * 2. "aaa" -- file part only -> "" + * + * 3. "/" -- root only, or -> unchanged + * "//" double root only -> unchanged + * + * 4. "/aaa" -- routed file -> "/" + * "//aaa" double routed file -> "//" + * + * 5. "zzz/" -- no file part -> unchanged + * + * 6. "zzz/aaa" -- non-empty file part -> "zzz/" + * + * Ensures that the path is "reduced" before shaving. + * + * Creates a new, empty path if qp is NULL. */ extern qpath -qpath_copy_to(qpath qp, qpath qp_x) +qpath_shave(qpath qp) { - return qpath_set(qp, qpath_path(qp_x)) ; + pp_t p ; + + qp = qpath_make_if_null(qp) ; + + qs_pp_nn(p, qp->path) ; + + /* Track back to last '/' */ + while ( (p->e > p->p) && (*(p->e - 1) != '/') ) + --p->e ; + + qs_set_len_nn(qp->path, p->e - p->p) ; + + return qp ; } ; +#if 0 + /*============================================================================== * Pop the last part of the given path. * @@ -291,7 +378,7 @@ qpath_copy_to(qpath qp, qpath qp_x) * Note that other forms of multiple '/' have been reduced, already. */ extern qpath -qpath_pop(qpath qp) +qpath_pop(qpath to, qpath from) { qpath part ; qstring qs ; @@ -348,6 +435,9 @@ qpath_pop(qpath qp) return part ; } ; + + + /*============================================================================== * Shift off the first part of the given path. * @@ -444,60 +534,85 @@ qpath_shift(qpath qp) /* Return the part we hacked off */ return part ; } ; +#endif + +/*============================================================================== + * Append, Prepend and Complete + */ + +static ulen qpath_trim_home(const char* p, ulen n) ; /*------------------------------------------------------------------------------ - * Push one path onto the end of the given path. + * Append one path (src) onto the end of the given path (dst). * - * If the given path is NULL, creates a new, empty qpath to push onto. + * If the dst path is NULL, creates a new, empty qpath to append to. * - * The given path is assumed to be the path to a "directory". An empty - * given path is treated as "the current directory". + * The dst path is assumed to be the path to a "directory". An empty dst path + * is treated as "the current directory". * - * If the path to be pushed starts '/' or '~', then it is trimmed, removing - * leading characters upto and including '/' (stopping at '\0' if no '/' found). + * If src path starts '/' or '~', then it is trimmed, removing leading + * characters up to and including '/'. * - * If path to be pushed onto is not empty, and does not end '/', then an '/' - * is appended before the path is pushed. + * If dst path is not empty, and does not end '/', then an '/' is appended + * before the src is appended. * * Note that this means: * - * -- pushing an empty path or one which is just "/", will leave the path - * ending "/" -- unless the given path is empty. + * -- appending an empty path or one which is just "/", will leave the dst + * path ending "/" -- unless the dst path is empty. * - * -- cannot create a rooted path by pushing a path onto an empty path. + * -- cannot create a rooted path by appending a path onto an empty path. * - * -- pushing a "homed" path "~...." is assumed to be pushing onto the + * -- appending a "homed" path "~..../" is assumed to be appending to the * required "home". * * The resulting path is reduced (see above). */ +extern qpath +qpath_append(qpath dst, const qpath src) +{ + if (src != NULL) + return qpath_append_str_n(dst, qs_char_nn(src->path), + qs_len_nn(src->path)) ; + else + return qpath_append_str_n(dst, NULL, 0) ; +} ; +/*------------------------------------------------------------------------------ + * Append src qstring onto the end of the dst path. + * + * See above for discussion of "append" operation. + */ extern qpath -qpath_push(qpath qp, qpath qp_a) +qpath_append_qs(qpath dst, const qstring src) { - return qpath_push_str(qp, qpath_path(qp_a)) ; + return qpath_append_str_n(dst, qs_char(src), qs_len(src)) ; } ; /*------------------------------------------------------------------------------ - * Push path string onto the end of the given path. + * Append src string onto the end of the dst path. * - * See above for discussion of "push" operation. + * See above for discussion of "append" operation. */ extern qpath -qpath_push_str(qpath qp, const char* path) +qpath_append_str(qpath dst, const char* src) { - qstring qs ; - char* ep ; - char* sp ; - size_t len ; - size_t off ; + return qpath_append_str_n(dst, src, (src != NULL) ? strlen(src) : 0) ; +} ; - if (qp == NULL) - qp = qpath_init_new(NULL, NULL) ; +/*------------------------------------------------------------------------------ + * Append src string of given length onto the end of the dst path. + * + * See above for discussion of "append" operation. + */ +extern qpath +qpath_append_str_n(qpath dst, const char* src, ulen n) +{ + ulen l ; - qs = &qp->path ; + dst = qpath_make_if_null(dst) ; - /* Trim the path to be pushed: + /* Trim the path to be appended: * * 1. discard from any leading '~' to the first '/' or to '\0'. * @@ -505,87 +620,257 @@ qpath_push_str(qpath qp, const char* path) * * 3. then establish length of result. */ - if (path != NULL) + l = n ; + n = qpath_trim_home(src, n) ; + src += (l - n) ; /* step past stuff trimmed */ + + /* Worry about whether need to add a '/' to the path before pushing */ + if (qs_len_nn(dst->path) != 0) { - if (*path == '~') - do - { - ++path ; - } while ((*path != '/') && (*path != '\0')) ; - - while (*path == '/') - ++path ; /* Step past leading '/' */ - len = strlen(path) ; - } + if (*(qs_ep_char_nn(dst->path) - 1) != '/') + qs_append_str_n(dst->path, "/", 1) ; + } ; + + /* Now append the src */ + qs_append_str_n(dst->path, src, n) ; + + /* Reduce the new part of the result, and return */ + qpath_reduce(dst) ; + + return dst ; +} ; + +/*------------------------------------------------------------------------------ + * Extend given dst path by simply affixing the given src path. + * + * Does not introduce any '/' or any other stuff. + * + * The resulting path is reduced (see above). + */ +extern qpath +qpath_extend(qpath dst, const qpath src) +{ + if (src != NULL) + return qpath_extend_str_n(dst, qs_char_nn(src->path), + qs_len_nn(src->path)) ; else - len = 0 ; + return qpath_extend_str_n(dst, NULL, 0) ; +} ; - /* Worry about whether need to add a '/' to the path before pushing */ - sp = qs_char(qs) ; - ep = qs_ep_char(qs) ; /* points at trailing '\0' */ +/*------------------------------------------------------------------------------ + * Extend given dst path by simply affixing the given src qstring. + */ +extern qpath +qpath_extend_qs(qpath dst, const qstring src) +{ + return qpath_extend_str_n(dst, qs_char(src), qs_len(src)) ; +} ; - if (sp == NULL) - assert(ep == sp) ; /* ie qs->len == 0 if qs->body == NULL */ +/*------------------------------------------------------------------------------ + * Extend given dst path by simply affixing the given src string. + */ +extern qpath +qpath_extend_str(qpath dst, const char* src) +{ + return qpath_extend_str_n(dst, src, (src != NULL) ? strlen(src) : 0) ; +} ; - off = qs->len ; /* where new stuff starts */ +/*------------------------------------------------------------------------------ + * Extend given dst path by simply affixing the given src string of the given + * length. + */ +extern qpath +qpath_extend_str_n(qpath dst, const char* src, ulen n) +{ + dst = qpath_make_if_null(dst) ; - if (ep != sp) - { - if (*(ep - 1) == '/') - --off ; /* step back to the '/' */ - else - { - /* Destination is not empty and does not end '/', so append one. - * - * Note that we ensure there is space for the path which are - * about to push, so at most one allocation required. - */ - qs_need(qs, (ep - sp) + 1 + len) ; - qs_append_n(qs, "/", 1) ; - } ; - } ; + qs_append_str_n(dst->path, src, n) ; - /* Now push path */ - qs_append_n(qs, path, len) ; + qpath_reduce(dst) ; - /* Reduce the new part of the result, and return - * - * Note that the 'off' points at the '/' which precedes the new stuff. - * So will spot "/./" where the new stuff starts "./". - */ - qpath_reduce(qp, off) ; + return dst ; +} ; - return qp ; +/*------------------------------------------------------------------------------ + * Prepend src path onto front of dst path. + * + * Like append, where the dst ends up being the dst appended to the src. + */ +extern qpath +qpath_prepend(qpath dst, const qpath src) +{ + if (src != NULL) + return qpath_prepend_str_n(dst, qs_char_nn(src->path), + qs_len_nn(src->path)) ; + else + return qpath_prepend_str_n(dst, NULL, 0) ; } ; /*------------------------------------------------------------------------------ - * Join two paths to create a new path. + * Prepend src qstring onto front of dst path. * - * Copies the destination path and then pushes the other path onto it. + * Like append, where the dst ends up being the dst appended to the src. */ extern qpath -qpath_join(qpath qp, qpath qp_a) +qpath_prepend_qs(qpath dst, const qstring src) { - qpath qp_n ; + return qpath_prepend_str_n(dst, qs_char(src), qs_len(src)) ; +} ; - qp_n = qpath_copy(qp) ; - return qpath_push(qp_n, qp_a) ; +/*------------------------------------------------------------------------------ + * Prepend src string onto front of dst path. + * + * Like append, where the dst ends up being the dst appended to the src. + */ +extern qpath +qpath_prepend_str(qpath dst, const char* src) +{ + return qpath_prepend_str_n(dst, src, (src != NULL) ? strlen(src) : 0) ; } ; /*------------------------------------------------------------------------------ - * Join path string to the given path to create a new path. + * Prepend src string of given length onto front of dst path. * - * Copies the destination path and then pushes the path string onto it. + * Like append, where the dst ends up being the dst appended to the src. */ extern qpath -qpath_join_str(qpath qp, const char* path) +qpath_prepend_str_n(qpath dst, const char* src, ulen n) +{ + char* p ; + ulen r ; + bool need_slash ; + + dst = qpath_make_if_null(dst) ; + + /* Trim the path to be prepended to: + * + * 1. discard from any leading '~' to the first '/' (or end). + * + * 2. then discard any leading '/' + * + * 3. then establish length of any part to be replaced. + */ + r = qs_len_nn(dst->path) ; + r -= qpath_trim_home(qs_char_nn(dst->path), r) ; + + /* Worry about whether need to add a '/' to the path before pushing */ + need_slash = (n > 0) && (*(src + n - 1) != '/') ; + + /* Make room for src and possible slash in qstring */ + qs_set_cp_nn(dst->path, 0) ; + qs_replace(dst->path, r, NULL, n + (need_slash ? 1 : 0)) ; + + /* Now copy in the src */ + p = qs_char_nn(dst->path) ; + + if (n > 0) + memmove(p, src, n) ; + + if (need_slash) + *(p + n) = '/' ; + + /* Reduce the new part of the result, and return */ + qpath_reduce(dst) ; + + return dst ; +} ; + +/*------------------------------------------------------------------------------ + * Make a qpath from the given string, completing it, if required, by + * prepending the given directory qpath. + */ +extern qpath +qpath_make(const char* src, const qpath dir) +{ + if (*src == '/') + return qpath_set(NULL, src) ; + + return qpath_append_str(qpath_dup(dir), src) ; +} ; + +/*------------------------------------------------------------------------------ + * If given dst path is not rooted (does not start with '/', prepend the + * given src path to it. Result is reduced. + */ +extern qpath +qpath_complete(qpath dst, const qpath src) +{ + if (src != NULL) + return qpath_prepend_str_n(dst, qs_char_nn(src->path), + qs_len_nn(src->path)) ; + else + return qpath_prepend_str_n(dst, NULL, 0) ; +} ; + +/*------------------------------------------------------------------------------ + * If given dst path is not rooted (does not start with '/', prepend the + * given src qstring to it. Result is reduced. + */ +extern qpath +qpath_complete_qs(qpath dst, const qstring src) +{ + return qpath_complete_str_n(dst, qs_char(src), qs_len(src)) ; +} ; + +/*------------------------------------------------------------------------------ + * If given dst path is not rooted (does not start with '/', prepend the + * given src string to it. Result is reduced. + */ +extern qpath +qpath_complete_str(qpath dst, const char* src) +{ + return qpath_prepend_str_n(dst, src, (src != NULL) ? strlen(src) : 0) ; +} ; + +/*------------------------------------------------------------------------------ + * If given dst path is not rooted (does not start with '/', prepend the + * given src string of given length to it. Result is reduced. + */ +extern qpath +qpath_complete_str_n(qpath dst, const char* src, ulen n) +{ + dst = qpath_make_if_null(dst) ; + + if ((qs_len_nn(dst->path) == 0) || (*(qs_char_nn(dst->path)) == '/')) + qpath_prepend_str_n(dst, src, n) ; + else + qpath_reduce(dst) ; + + return dst ; +} ; + +/*------------------------------------------------------------------------------ + * Trim leading '~' up to and including one or more '/'. + * + * Return remaining length after the trim. + */ +static ulen +qpath_trim_home(const char* p, ulen n) { - qpath qp_n ; + if ((n > 0) && (*p == '~')) + { + do /* Step past leadin '~' to first '/' */ + { + ++p ; + --n ; + } while ((n > 0) && (*p != '/')) ; + } ; + + while ((n > 0) && (*p == '/')) + { + ++p ; /* Step past leading '/' */ + --n ; + } ; - qp_n = qpath_copy(qp) ; - return qpath_push_str(qp_n, path) ; + return n ; } ; +/*============================================================================== + * + */ + +#if 0 + /*------------------------------------------------------------------------------ * Does the given path start and end '/' ? */ @@ -817,3 +1102,111 @@ qpath_is_atom(qpath qp) return false ; } ; +#endif + +/*============================================================================== + * + * + * + */ +/*------------------------------------------------------------------------------ + * Reduce multiple '/' to single '/' (except for exactly "//" at start). + * + * Reduce "/./" to "/". + */ +static void +qpath_reduce(qpath qp) +{ + char* sp ; + char* p ; + char* q ; + + sp = qs_make_string(qp->path) ; + p = sp ; + + /* Deal with special case of "//" at start. + * + * If find "//x", where x is anything other than '/', step past the first + * '/'. Could step past both "//", but that stops it seeing "//./zzz" + */ + if ((*p == '/') && (*(p + 1) == '/') && (*(p + 2) != '/')) + ++p ; + + /* Scan to see if there is anything that needs to be fixed. + * + * Looking for "//" and "/./". + */ + while (1) + { + if (*p == '\0') + return ; /* scanned to end */ + + if (*p++ != '/') /* scanning for '/' */ + continue ; + + if (*p == '/') + break ; /* second '/' */ + + if (*p != '.') + continue ; /* not "//" and not "/." */ + + if (*(p+1) == '/') + break ; /* found "/./" */ + } ; + + /* Rats... there is something to be fixed. + * + * *p is second '/' of "//" or '.' of "/./". + */ + q = p ; /* keep the first '/' */ + + while (*p != '\0') + { + ++p ; /* step past '.' or second '/' */ + + /* Step past any number of '/' and any number of "./". */ + while (*p != '\0') + { + while (*p == '/') /* eat any number of these */ + ++p ; + + if (*p != '.') /* done if not '.' */ + break ; + + if (*(p+1) != '/') /* done if not "./" */ + break ; + + p += 2 ; /* Step past "./" */ + } ; + + /* Here we have *p which is not '/' and not "./", so unless is '\0' + * there is at least one character to move across. + * + * Copying stuff, until get to '\0' or find "//" or "/./" + */ + while (*p != '\0') + { + *q++ = *p ; /* copy non-'\0' */ + + if (*p++ != '/') + continue ; /* keep going if wasn't '/' */ + + if (*p == '/') + break ; /* second '/' */ + + if (*p != '.') + continue ; /* not "//" and not "/." */ + + if (*(p+1) == '/') + break ; /* found "/./" */ + } ; + } ; + + /* Adjust the length and terminate */ + + qs_set_len_nn(qp->path, q - sp) ; /* set the new length (shorter !) */ +} ; + + + + diff --git a/lib/qpath.h b/lib/qpath.h index 27c61dff..23b710a6 100644 --- a/lib/qpath.h +++ b/lib/qpath.h @@ -22,15 +22,10 @@ #ifndef _ZEBRA_QPATH_H #define _ZEBRA_QPATH_H -#include "zebra.h" #include "misc.h" +#include "qstring.h" #include "memory.h" - - - - - - +#include "sys/stat.h" /*============================================================================== * For these purposes a path is "parts" separated by one more '/' characters. @@ -55,56 +50,111 @@ enum qpath_sex qp_double_absolute /* something, starting with "//" */ } ; -typedef struct qpath qpath_t ; -typedef struct qpath* qpath ; - /* The qpath structure is largely a qstring, but one in which there is always * a body, even if it only contains "\0", and the len is kept up to date. */ struct qpath { - qstring_t path ; + qstring_t path ; /* Embedded */ } ; +typedef struct qpath qpath_t[1] ; +typedef struct qpath* qpath ; + /*============================================================================== * Functions */ -extern qpath -qpath_init_new(qpath qp, const char* path) ; +extern qpath qpath_init_new(qpath qp) ; +extern qpath qpath_reset(qpath qp, free_keep_b free_structure) ; +extern qpath qpath_clear(qpath qp) ; -extern qpath -qpath_reset(qpath qp, bool free_structure) ; +Inline qpath qpath_new(void) ; +Inline qpath qpath_free(qpath qp) ; -Inline qpath -qpath_reset_free(qpath qp) { qpath_reset(qp, true) ; } ; +Inline const char* qpath_string(qpath qp) ; +Inline char* qpath_char_string(qpath qp) ; +Inline ulen qpath_len(qpath qp) ; +Inline const qstring qpath_qs(qpath qp) ; -Inline qpath -qpath_reset_keep(qpath qp) { qpath_reset(qp, false) ; } ; +extern qpath qpath_set(qpath dst, const char* src) ; +extern qpath qpath_set_n(qpath dst, const char* src, ulen n) ; +extern qpath qpath_set_qs(qpath dst, const qstring src) ; +extern qpath qpath_copy(qpath dst, const qpath src) ; +Inline qpath qpath_dup(const qpath qp) ; +Inline qpath qpath_dup_str(const char* src) ; -Inline const char* -qpath_path(qpath qp) ; +extern qpath qpath_getcwd(qpath dst) ; +extern int qpath_setcwd(qpath dst) ; +extern int qpath_stat(qpath qp, struct stat* stat) ; +extern int qpath_stat_is_file(qpath qp) ; +extern int qpath_stat_is_directory(qpath qp) ; -extern qpath -qpath_trim(qpath qp) ; +extern qpath qpath_shave(qpath qp) ; -extern qpath -qpath_copy(qpath qp) ; +extern qpath qpath_append(qpath dst, const qpath src) ; +extern qpath qpath_append_qs(qpath dst, const qstring src) ; +extern qpath qpath_append_str(qpath dst, const char* src) ; +extern qpath qpath_append_str_n(qpath dst, const char* src, ulen n) ; -extern qpath -qpath_pop(qpath qp) ; +extern qpath qpath_extend(qpath dst, const qpath src) ; +extern qpath qpath_extend_qs(qpath dst, const qstring src) ; +extern qpath qpath_extend_str(qpath dst, const char* src) ; +extern qpath qpath_extend_str_n(qpath dst, const char* src, ulen n) ; -extern qpath -qpath_push(qpath qp, qpath qp_a) ; +extern qpath qpath_prepend(qpath dst, const qpath src) ; +extern qpath qpath_prepend_qs(qpath dst, const qstring src) ; +extern qpath qpath_prepend_str(qpath dst, const char* src) ; +extern qpath qpath_prepend_str_n(qpath dst, const char* src, ulen n) ; -extern qpath -qpath_join(qpath qp, qpath qp_a) ; +extern qpath qpath_make(const char* src, const qpath dir) ; + +extern qpath qpath_complete(qpath dst, const qpath src) ; +extern qpath qpath_complete_qs(qpath dst, const qstring src) ; +extern qpath qpath_complete_str(qpath dst, const char* src) ; +extern qpath qpath_complete_str_n(qpath dst, const char* src, ulen n) ; /*============================================================================== * Inline stuff */ /*------------------------------------------------------------------------------ + * Create new qpath. + */ +Inline qpath +qpath_new(void) +{ + return qpath_init_new(NULL) ; +} ; + +/*------------------------------------------------------------------------------ + * Free qpath. + */ +Inline qpath +qpath_free(qpath qp) +{ + return qpath_reset(qp, free_it) ; +} ; + +/*------------------------------------------------------------------------------ + * Duplicate qpath -- result will need to be freed. + */ +Inline qpath +qpath_dup(const qpath qp) +{ + return qpath_copy(NULL, qp) ; +} ; + +/*------------------------------------------------------------------------------ + * Duplicate string as a qpath -- result will need to be freed. + */ +Inline qpath +qpath_dup_str(const char* src) +{ + return qpath_set(NULL, src) ; +} ; + +/*------------------------------------------------------------------------------ * Get *temporary* pointer to actual path contained in the given qpath. * * This is *temporary* to the extent that when the qpath is changed or freed, @@ -116,9 +166,50 @@ qpath_join(qpath qp, qpath qp_a) ; * ('\0' terminated ""). */ Inline const char* -qpath_path(qpath qp) +qpath_string(qpath qp) +{ + return (qp != NULL) ? qs_make_string(qp->path) : "" ; +} ; + +/*------------------------------------------------------------------------------ + * Get *temporary* pointer to actual path contained in the given qpath. + * + * This is *temporary* to the extent that when the qpath is changed or freed, + * this pointer will be INVALID -- you have been warned. + * + * This is a *const* pointer. + * + * qpath may *not* be NULL qpath. + */ +Inline char* +qpath_char_string(qpath qp) +{ + return qs_make_string(qp->path) ; +} ; + +/*------------------------------------------------------------------------------ + * Get length of the given qpath -- zero if NULL + */ +Inline ulen +qpath_len(qpath qp) +{ + return (qp != NULL) ? qs_len_nn(qp->path) : 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Get *temporary* pointer to qstring rendering of the given path. + * + * This is *temporary* to the extent that when the qpath is changed or freed, + * this pointer will be INVALID -- you have been warned. + * + * This is a *const* pointer. + * + * For a NULL qpath returns NULL qstring. + */ +Inline const qstring +qpath_qs(qpath qp) { - return qs_string(qp != NULL ? &qp->path : qp) ; + return (qp != NULL) ? qp->path : NULL ; } ; #endif /* _ZEBRA_QPATH_H */ diff --git a/lib/qpnexus.c b/lib/qpnexus.c index 001d5049..8a805900 100644 --- a/lib/qpnexus.c +++ b/lib/qpnexus.c @@ -36,18 +36,39 @@ static void qpn_in_thread_init(qpn_nexus qpn); */ +/*------------------------------------------------------------------------------ + * Initialise the qpnexus handling -- to be done as soon as state of + * qpthreads_enabled is established. + */ +extern void +qpn_init(void) +{ + qpt_data_create(qpn_self) ; /* thread specific data */ +} ; + +/*------------------------------------------------------------------------------ + * Set the thread's qpn_self to point at its qpnexus. + */ +static void +qpn_self_knowledge(qpn_nexus qpn) +{ + qpt_data_set_value(qpn_self, qpn) ; +} ; + /*============================================================================== * Initialisation, add hook, free etc. * */ /*------------------------------------------------------------------------------ - * Initialise a nexus -- allocating it if required. + * Initialise a nexus -- allocating it if required. * - * If main_thread is set then no new thread will be created - * when qpn_exec() is called, instead the finite state machine will be - * run in the calling thread. The main thread will only block the - * message queue's signal. Non-main threads will block most signals. + * If main_thread is set then no new thread will be created when qpn_exec() is + * called, instead the finite state machine will be run in the calling thread. + * + * The main thread will only block the message queue's signal. + * + * Non-main threads will block most signals. * * Returns the qpn_nexus. */ @@ -66,10 +87,13 @@ qpn_init_new(qpn_nexus qpn, bool main_thread) qpn->start = qpn_start; if (main_thread) - qpn->thread_id = qpt_thread_self(); + { + qpn->thread_id = qpt_thread_self(); + qpn_self_knowledge(qpn) ; + } ; return qpn; -} +} ; /*------------------------------------------------------------------------------ * Add a hook function to the given nexus. @@ -259,50 +283,38 @@ qpn_start(void* arg) static void qpn_in_thread_init(qpn_nexus qpn) { - sigset_t newmask; + sigset_t sigmask[1]; qpn->thread_id = qpt_thread_self(); - + qpn_self_knowledge(qpn) ; + + /* Signal mask. + * + * The main thread blocks nothing, except SIG_INTERRUPT. So (a) all + * signals other than the "hard cases" are routed to the main thread, and + * (b) SIG_INTERRUPT is masked until it is unmasked in pselect. + * + * Other threads block everything except the hard cases and SIG_INTERRUPT. + */ if (qpn->main_thread) - { - /* Main thread, block the message queue's signal */ - sigemptyset (&newmask); - sigaddset (&newmask, SIGMQUEUE); - } + sigmakeset(sigmask, SIG_INTERRUPT, -1) ; else - { - /* - * Not main thread. Block most signals, but be careful not to - * defer SIGTRAP because doing so breaks gdb, at least on - * NetBSD 2.0. Avoid asking to block SIGKILL, just because - * we shouldn't be able to do so. Avoid blocking SIGFPE, - * SIGILL, SIGSEGV, SIGBUS as this is undefined by POSIX. - * Don't block SIGPIPE so that is gets ignored on this thread. - */ - sigfillset (&newmask); - sigdelset (&newmask, SIGTRAP); - sigdelset (&newmask, SIGKILL); - sigdelset (&newmask, SIGPIPE); - sigdelset (&newmask, SIGFPE); - sigdelset (&newmask, SIGILL); - sigdelset (&newmask, SIGSEGV); - sigdelset (&newmask, SIGBUS); - } + siginvset(sigmask, signal_get_hard_set()) ; - if (qpthreads_enabled) - qpt_thread_sigmask(SIG_BLOCK, &newmask, NULL); - else - { - if (sigprocmask(SIG_BLOCK, &newmask, NULL) != 0) - zabort_errno("sigprocmask failed") ; - } + qpt_thread_sigmask(SIG_BLOCK, sigmask, NULL); - /* Now we have thread_id and mask, prep for using message queue. */ + /* The signal mask to be used during pselect() */ + sigcopyset(qpn->pselect_mask, sigmask) ; + sigdelset(qpn->pselect_mask, SIG_INTERRUPT) ; + qpn->pselect_signal = SIG_INTERRUPT ; + + /* Now we have thread_id and mask, prep for using message queue. */ if (qpn->queue != NULL) - qpn->mts = mqueue_thread_signal_init(qpn->mts, qpn->thread_id, SIGMQUEUE); + qpn->mts = mqueue_thread_signal_init(qpn->mts, qpn->thread_id, + SIG_INTERRUPT) ; if (qpn->selection != NULL) - qps_set_signal(qpn->selection, SIGMQUEUE, newmask); -} + qps_set_signal(qpn->selection, qpn->pselect_mask); +} ; /*------------------------------------------------------------------------------ * Ask the thread to terminate itself quickly and cleanly. @@ -318,6 +330,6 @@ qpn_terminate(qpn_nexus qpn) /* wake up any pselect */ if (qpthreads_enabled) - qpt_thread_signal(qpn->thread_id, SIGMQUEUE); + qpt_thread_signal(qpn->thread_id, SIG_INTERRUPT); } ; } diff --git a/lib/qpnexus.h b/lib/qpnexus.h index a6c8bf44..1aeefc04 100644 --- a/lib/qpnexus.h +++ b/lib/qpnexus.h @@ -24,9 +24,9 @@ #include "misc.h" #include <time.h> -#include <pthread.h> #include <unistd.h> #include <errno.h> +#include <signal.h> #include "zassert.h" #include "qpthreads.h" @@ -42,15 +42,17 @@ * action routines. * */ +enum +{ + /* maximum time in seconds to sit in a pselect */ + MAX_PSELECT_WAIT = 10, -/* maximum time in seconds to sit in a pselect */ -#define MAX_PSELECT_WAIT 10 - -/* signal for message queues */ -#define SIGMQUEUE SIGUSR2 + /* signal for message queues */ + SIG_INTERRUPT = SIGUSR2, -/* number of hooks per hook list */ -enum { qpn_hooks_max = 4 } ; + /* number of hooks per hook list */ + qpn_hooks_max = 4, +} ; /*============================================================================== * Data Structures. @@ -70,26 +72,30 @@ typedef struct qpn_nexus* qpn_nexus ; struct qpn_nexus { - /* set true to terminate the thread (eventually) */ + /* set true to terminate the thread (eventually) */ bool terminate; - /* true if this is the main thread */ + /* true if this is the main thread */ bool main_thread; - /* thread ID */ - qpt_thread_t thread_id; + /* thread ID */ + qpt_thread_t thread_id; - /* pselect handler */ + /* Signal mask for pselect */ + sigset_t pselect_mask[1] ; + int pselect_signal ; + + /* pselect handler */ qps_selection selection; - /* timer pile */ + /* timer pile */ qtimer_pile pile; - /* message queue */ + /* message queue */ mqueue_queue queue; mqueue_thread_signal mts; - /* qpthread routine, can override */ + /* qpthread routine, can override */ void* (*start)(void*); /* in-thread initialise, can override. Called within the thread after all @@ -138,10 +144,22 @@ struct qpn_nexus struct qpn_hook_list background ; }; +/*------------------------------------------------------------------------------ + * Each thread that has a qpnexus uses this piece of thread specific data in + * order to be able to find its own nexus. + */ +qpt_data_t qpn_self ; /* thread specific data */ + +Inline qpn_nexus +qpn_find_self(void) +{ + return qpt_data_get_value(qpn_self) ; +} ; + /*============================================================================== * Functions */ - +extern void qpn_init(void) ; 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); diff --git a/lib/qpselect.c b/lib/qpselect.c index bcd2e2c9..3fb80daf 100644 --- a/lib/qpselect.c +++ b/lib/qpselect.c @@ -18,6 +18,7 @@ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ +#include "misc.h" #include <signal.h> #include <string.h> @@ -30,18 +31,32 @@ #include "vector.h" /*------------------------------------------------------------------------------ - * Sort out any debug setting + * Sort out QPSELECT_DEBUG. + * + * Set to 1 if defined, but blank. + * Set to QDEBUG if not defined. + * + * Force to 0 if VTY_NO_DEBUG is defined and not zero. + * + * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to: + * + * * can set QPSELECT_DEBUG == 0 to turn off debug + * * or set QPSELECT_DEBUG != 0 to turn on debug + * * or set QPSELECT_NO_DEBUG != to force debug off */ -#ifdef QPSELECT_DEBUG /* Can be forced from outside */ -# if QPSELECT_DEBUG -# define QPSELECT_DEBUG 1 /* Force 1 or 0 */ -#else -# define QPSELECT_DEBUG 0 + +#ifdef QPSELECT_DEBUG /* If defined, make it 1 or 0 */ +# if IS_BLANK_OPTION(QPSELECT_DEBUG) +# undef QPSELECT_DEBUG +# define QPSELECT_DEBUG 1 # endif -#else -# ifdef QDEBUG -# define QPSELECT_DEBUG 1 /* Follow QDEBUG */ -#else +#else /* If not defined, follow QDEBUG */ +# define QPSELECT_DEBUG QDEBUG +#endif + +#ifdef QPSELECT_NO_DEBUG /* Override, if defined */ +# if IS_NOT_ZERO_OPTION(QPSELECT_NO_DEBUG) +# undef QPSELECT_DEBUG # define QPSELECT_DEBUG 0 # endif #endif @@ -283,29 +298,18 @@ qps_selection_ream(qps_selection qps, int free_structure) /*------------------------------------------------------------------------------ * Set the signal mask for the selection. * - * This supports the unmasking of a single signal for the duration of the + * This supports the unmasking of one or more signals for the duration of the * pselect operation. * - * It is assumed that the set of signals generally masked by a thread is - * essentially static. So this function is passed that set. (So the sigmask - * argument must have the signum signal masked.) - * - * If the set of signals masked by the thread changes, then this function + * * If the set of signals masked by the thread changes, then this function * should be called again. * - * Setting a signum == 0 turns OFF the use of the sigmask. + * Setting a sigmask == NULL turns OFF the use of the sigmask. */ -void -qps_set_signal(qps_selection qps, int signum, sigset_t sigmask) +extern void +qps_set_signal(qps_selection qps, const sigset_t* sigmask) { - qps->signum = signum ; - - if (signum != 0) - { - assert(sigismember(&sigmask, signum)) ; - sigdelset(&sigmask, signum) ; - qps->sigmask = sigmask ; - } ; + qps->sigmask = sigmask ; } ; /*------------------------------------------------------------------------------ @@ -377,7 +381,7 @@ qps_pselect(qps_selection qps, qtime_t max_wait) p_fds[qps_write_mnum], p_fds[qps_error_mnum], qtime2timespec(&ts, max_wait), - (qps->signum != 0) ? &qps->sigmask : NULL) ; + qps->sigmask) ; /* If have something, set and return the pending count. */ if (n > 0) @@ -1423,3 +1427,97 @@ qps_selection_validate(qps_selection qps) if (n != qps->pend_count) zabort("Non-empty bit vector where tried count == 0") ; } ; + +/*============================================================================== + * Miniature pselect -- for where want to wait for a small number of things. + */ + +/*------------------------------------------------------------------------------ + * Initialise a qps_mini and set one fd in the given mode. + * + * If the fd is < 0, sets nothing (returns empty qps_mini). + * + * Zero timeout is an indefinite wait ! + */ +extern qps_mini +qps_mini_set(qps_mini qm, int fd, qps_mnum_t mode, uint timeout) +{ + qps_super_set_zero(qm->sets, qps_mnum_count) ; + qm->fd_last = -1 ; + + qm->interval = QTIME(timeout) ; /* convert from time in seconds */ + qm->end_time = qt_add_monotonic(qm->interval) ; + + qps_mini_add(qm, fd, mode) ; + + return qm ; +} ; + +/*------------------------------------------------------------------------------ + * Add given fd in the given mode to the given qps_mini. + * + * If the fd is < 0, adds nothing (returns qps_mini unchanged). + */ +extern qps_mini +qps_mini_add(qps_mini qm, int fd, qps_mnum_t mode) +{ + if (fd >= 0) + { + FD_SET(fd, &(qm->sets[mode].fdset)) ; + if (fd > qm->fd_last) + qm->fd_last = fd ; + } ; + + return qm ; +} ; + +/*------------------------------------------------------------------------------ + * Wait for a qps_mini -- with given timeout, in seconds. + * + * If !signal, loops on EINTR, so will not exit until gets ready state or a + * timeout. + * + * If signal, will exit on EINTR. Can call this again after an EINTR return, + * and will continue to wait the *remainder* of the timeout interval. + * + * Returns what pselect returns: > 0 number of bits set + * == 0 no bits set <=> timed out + * < 0 EINTR (iff signal) + */ +extern int +qps_mini_wait(qps_mini qm, const sigset_t* sigmask, bool signal) +{ + while (1) + { + struct timespec ts[1] ; + int n ; + + n = pselect(qm->fd_last + 1, + &(qm->sets[qps_read_mnum].fdset), + &(qm->sets[qps_write_mnum].fdset), + &(qm->sets[qps_error_mnum].fdset), + (qm->interval > 0) ? qtime2timespec(ts, qm->interval) + : NULL, + sigmask) ; + if (n >= 0) + return n ; + + if (errno != EINTR) + break ; + + if (qm->interval > 0) + { + /* set new interval, in case comes back */ + qm->interval = qm->end_time - qt_get_monotonic() ; + if (qm->interval < 0) + return 0 ; + } ; + + if (signal) + return -1 ; /* signal EINTR */ + } ; + + zabort_errno("Failed in pselect") ; +} ; + + diff --git a/lib/qpselect.h b/lib/qpselect.h index 48c86fe8..37bf8680 100644 --- a/lib/qpselect.h +++ b/lib/qpselect.h @@ -140,8 +140,7 @@ struct qps_selection qps_mnum_t pend_mnum ; /* error/read/write mode pending (if any) */ int pend_fd ; /* fd pending (if any) */ - int signum ; /* signal that sigmask is enabling -- 0 => none */ - sigset_t sigmask ; /* sigmask to use for duration of pselect */ + const sigset_t* sigmask ; /* sigmask to use for duration of pselect */ } ; struct qps_file @@ -181,7 +180,7 @@ qps_selection_ream(qps_selection qps, int free_structure) ; #define qps_selection_ream_keep(qps) qps_selection_ream(qps, 0) extern void -qps_set_signal(qps_selection qps, int signum, sigset_t sigmask) ; +qps_set_signal(qps_selection qps, const sigset_t* sigmask) ; extern int qps_pselect(qps_selection qps, qtime_mono_t timeout) ; @@ -235,4 +234,25 @@ qps_set_file_info(qps_file qf, void* info) qf->file_info = info ; } ; +/*============================================================================== + * Miniature pselect + * + */ +struct qps_mini +{ + fd_full_set sets ; /* bit vectors for pselect enabled stuff */ + int fd_last ; /* highest numbered fd; -1 => none at all */ + + qtime_t interval ; + qtime_mono_t end_time ; +} ; + +typedef struct qps_mini qps_mini_t[1] ; +typedef struct qps_mini* qps_mini ; + +extern qps_mini qps_mini_set(qps_mini qm, int fd, qps_mnum_t mode, + uint timeout) ; +extern qps_mini qps_mini_add(qps_mini qm, int fd, qps_mnum_t mode) ; +extern int qps_mini_wait(qps_mini qm, const sigset_t* sigmask, bool signal) ; + #endif /* _ZEBRA_QPSELECT_H */ diff --git a/lib/qpthreads.c b/lib/qpthreads.c index baa34d52..8909f64b 100644 --- a/lib/qpthreads.c +++ b/lib/qpthreads.c @@ -18,10 +18,7 @@ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ - -/* This MUST come first... otherwise we don't get __USE_UNIX98, which is */ -/* essential if glibc is to allow pthread_mutexattr_settype() to be used. */ -#include "config.h" +#include "misc.h" #include <signal.h> #include <string.h> @@ -29,11 +26,6 @@ #include "qpthreads.h" #include "memory.h" -/* If this is not set, will get errors later. */ -//#ifndef __USE_UNIX98 -//#error "_USE_UNIX98 not defined" -//#endif - /*============================================================================== * Quagga Pthread Interface -- qpt_xxxx * @@ -251,49 +243,56 @@ enum qpthreads_enabled_state qpt_state_set_disabled = 2, qpt_state_set_enabled = 3, } ; +typedef enum qpthreads_enabled_state qpthreads_enabled_state_t ; -static enum qpthreads_enabled_state qpthreads_enabled_state = qpt_state_unset ; +static qpthreads_enabled_state_t qpthreads_enabled_state = qpt_state_unset ; -uint8_t qpthreads_enabled_flag = 0 ; -uint8_t qpthreads_thread_created_flag = 0 ; +bool qpthreads_enabled_flag = false ; +bool qpthreads_thread_created_flag = false ; -/* Function to set qpthreads_enabled, one way or the other. +/*------------------------------------------------------------------------------ + * Function to set qpthreads_enabled, one way or the other. * * Returns: true <=> successful set the required state. * false <=> it is too late to enable qpthreads :-( * * NB: can repeatedly set to the same state, but not change state once set. */ -extern int -qpt_set_qpthreads_enabled(int how) +extern bool +qpt_set_qpthreads_enabled(bool want_enabled) { switch (qpthreads_enabled_state) - { - case qpt_state_unset: - break ; - case qpt_state_set_frozen: - if (how != 0) - return 0 ; - break ; - case qpt_state_set_disabled: - if (how != 0) - zabort("qpthreads_enabled is already set: cannot set enabled") ; - break ; - case qpt_state_set_enabled: - if (how == 0) - zabort("qpthreads_enabled is already set: cannot set disabled") ; - break ; - default: - break ; - } - - qpthreads_enabled_flag = (how != 0) ; - qpthreads_enabled_state = (how != 0) ? qpt_state_set_enabled - : qpt_state_set_disabled ; - return 1 ; + { + case qpt_state_unset: + break ; + + case qpt_state_set_frozen: + if (want_enabled) + return false ; /* too late to set the state */ + break ; + + case qpt_state_set_disabled: + if (want_enabled) + zabort("qpthreads_enabled is already set: cannot set enabled") ; + break ; + + case qpt_state_set_enabled: + if (!want_enabled) + zabort("qpthreads_enabled is already set: cannot set disabled") ; + break ; + + default: + break ; + } ; + + qpthreads_enabled_flag = want_enabled ; + qpthreads_enabled_state = want_enabled ? qpt_state_set_enabled + : qpt_state_set_disabled ; + return true ; } ; -/* Get state of qpthreads_enabled, and freeze if not yet explictly set. +/*------------------------------------------------------------------------------ + * Get state of qpthreads_enabled, and freeze if not yet explictly set. * * Where some initialisation depends on the state of qpthreads_enabled(), this * returns the state and freezes it if it is implicitly not enabled. @@ -320,7 +319,8 @@ qpt_freeze_qpthreads_enabled(void) * are NULL, then the system defaults are used. */ -/* Initialise a set of attributes -- setting the scheduling options. +/*------------------------------------------------------------------------------ + * Initialise a set of attributes -- setting the scheduling options. * * Options: * @@ -351,7 +351,7 @@ qpt_freeze_qpthreads_enabled(void) * * Returns the address of the qpt_thread_attr_t structure. */ -qpt_thread_attr_t* +extern qpt_thread_attr_t* qpt_thread_attr_init(qpt_thread_attr_t* attr, enum qpt_attr_options opts, int scope, int policy, int priority) { @@ -420,7 +420,8 @@ qpt_thread_attr_init(qpt_thread_attr_t* attr, enum qpt_attr_options opts, return attr ; } ; -/* Create Thread with given attributes (if any). +/*------------------------------------------------------------------------------ + * Create Thread with given attributes (if any). * * If no attributes are given (attr == NULL) the thread is created with system * default attributes -- *except* that it is created joinable. @@ -429,7 +430,7 @@ qpt_thread_attr_init(qpt_thread_attr_t* attr, enum qpt_attr_options opts, * * Returns the qpt_thread_t "thread id". */ -qpt_thread_t +extern qpt_thread_t qpt_thread_create(void* (*start)(void*), void* arg, qpt_thread_attr_t* attr) { qpt_thread_attr_t thread_attr ; @@ -438,7 +439,7 @@ qpt_thread_create(void* (*start)(void*), void* arg, qpt_thread_attr_t* attr) int err ; passert(qpthreads_enabled) ; - qpthreads_thread_created_flag = 1 ; /* and at least one thread created */ + qpthreads_thread_created_flag = true ; /* at least one thread created */ default_attr = (attr == NULL) ; if (default_attr) @@ -458,7 +459,8 @@ qpt_thread_create(void* (*start)(void*), void* arg, qpt_thread_attr_t* attr) return thread_id ; } ; -/* Join given thread -- do nothing if !qpthreads_enabled +/*------------------------------------------------------------------------------ + * Join given thread -- do nothing if !qpthreads_enabled * * Tolerates ESRCH (no thread known by given id). * @@ -490,7 +492,8 @@ qpt_thread_join(qpt_thread_t thread_id) * Mutex initialise and destroy. */ -/* Initialise Mutex (allocating if required) +/*------------------------------------------------------------------------------ + * Initialise Mutex (allocating if required) * * Does nothing if !qpthreads_enabled -- but freezes the state (attempting to * later enable qpthreads will be a FATAL error). @@ -508,7 +511,7 @@ qpt_thread_join(qpt_thread_t thread_id) * * Returns the mutex -- or original mx if !qpthreads_enabled. */ -qpt_mutex +extern qpt_mutex qpt_mutex_init_new(qpt_mutex mx, enum qpt_mutex_options opts) { pthread_mutexattr_t mutex_attr ; @@ -569,7 +572,8 @@ qpt_mutex_init_new(qpt_mutex mx, enum qpt_mutex_options opts) return mx ; } ; -/* Destroy given mutex, and (if required) free it. +/*------------------------------------------------------------------------------ + * Destroy given mutex, and (if required) free it. * -- or do nothing if !qpthreads_enabled. * * Returns NULL if freed the mutex, otherwise the address of same. @@ -578,7 +582,7 @@ qpt_mutex_init_new(qpt_mutex mx, enum qpt_mutex_options opts) * anything, so there can be nothing to release -- so does nothing, but * returns the original mutex address (if any). */ -qpt_mutex +extern qpt_mutex qpt_mutex_destroy(qpt_mutex mx, int free_mutex) { int err ; @@ -600,7 +604,8 @@ qpt_mutex_destroy(qpt_mutex mx, int free_mutex) * Condition Variable initialise and destroy. */ -/* Initialise Condition Variable (allocating if required). +/*------------------------------------------------------------------------------ + * Initialise Condition Variable (allocating if required). * * Does nothing if !qpthreads_enabled -- but freezes the state (attempting to * later enable qpthreads will be a FATAL error). @@ -615,7 +620,7 @@ qpt_mutex_destroy(qpt_mutex mx, int free_mutex) * * Returns the condition variable -- or original cv id !qpthreads_enabled. */ -qpt_cond +extern qpt_cond qpt_cond_init_new(qpt_cond cv, enum qpt_cond_options opts) { pthread_condattr_t cond_attr ; @@ -662,7 +667,8 @@ qpt_cond_init_new(qpt_cond cv, enum qpt_cond_options opts) return cv ; } ; -/* Destroy given condition variable, and (if required) free it +/*------------------------------------------------------------------------------ + * Destroy given condition variable, and (if required) free it * -- or do nothing if !qpthreads_enabled. * * NB: if !qpthreads_enabled qpt_cond_init_new() will not have allocated @@ -671,7 +677,7 @@ qpt_cond_init_new(qpt_cond cv, enum qpt_cond_options opts) * * Returns NULL if freed the condition variable, otherwise the address of same. */ -qpt_cond +extern qpt_cond qpt_cond_destroy(qpt_cond cv, int free_cond) { int err ; @@ -689,7 +695,8 @@ qpt_cond_destroy(qpt_cond cv, int free_cond) return cv ; } ; -/* Wait for given condition variable or time-out +/*------------------------------------------------------------------------------ + * Wait for given condition variable or time-out * -- or return immediate success if !qpthreads_enabled. * * Returns: wait succeeded (1 => success, 0 => timed-out). @@ -698,8 +705,7 @@ qpt_cond_destroy(qpt_cond cv, int free_cond) * * Has to check the return value, so zabort_errno if not EBUSY. */ - -int +extern int qpt_cond_timedwait(qpt_cond cv, qpt_mutex mx, qtime_mono_t timeout_time) { struct timespec ts ; @@ -729,40 +735,43 @@ qpt_cond_timedwait(qpt_cond cv, qpt_mutex mx, qtime_mono_t timeout_time) * Signal Handling. */ -/* Set thread signal mask -- requires qpthreads_enabled. - * - * Thin wrapper around pthread_sigmask. +/*------------------------------------------------------------------------------ + * Set thread signal mask. * - * zaborts if gets any error. + * If !qpthreads_enabled will use sigprocmask(), otherwise pthread_sigmask(). * - * NB: it is a FATAL error to do this if !qpthreads_enabled. + * In fact pthread_sigmask() works for single-threaded processes, but we avoid + * depending on pthread library if it's not essential. * - * This is mostly because wish to avoid all pthreads_xxx calls when not - * using pthreads. There is no reason not to use this in a single threaded - * program. + * zaborts if gets any error. */ -void +extern void qpt_thread_sigmask(int how, const sigset_t* set, sigset_t* oset) { - int err ; - - passert(qpthreads_enabled) ; - if (oset != NULL) sigemptyset(oset) ; /* to make absolutely sure */ - err = pthread_sigmask(how, set, oset) ; - if (err != 0) - zabort_err("pthread_sigmask failed", err) ; + if (qpthreads_enabled) + { + int err = pthread_sigmask(how, set, oset) ; + if (err != 0) + zabort_err("pthread_sigmask failed", err) ; + } + else + { + if (sigprocmask(how, set, oset) != 0) + zabort_errno("sigprocmask failed") ; + } ; } ; -/* Send given thread the given signal -- requires qpthreads_enabled (!) +/*------------------------------------------------------------------------------ + * Send given thread the given signal -- requires qpthreads_enabled (!) * * Thin wrapper around pthread_kill. * * zaborts if gets any error. */ -void +extern void qpt_thread_signal(qpt_thread_t thread, int signum) { int err ; @@ -773,3 +782,58 @@ qpt_thread_signal(qpt_thread_t thread, int signum) if (err != 0) zabort_err("pthread_kill failed", err) ; } ; + +/*============================================================================== + * Thread Specific Data Handling. + * + * When creating a key for a piece of thread specific data one could: + * + * a. arrange for all keys to be created before any threads are + * created -- or at least before any have a need for the key. + * + * b. use pthread_once() to protect the creation of the key. + * + * Note that there does not appear to be a way of distinguishing a key + * that has been created from one that has not. + * + * For !qpthreads_enabled systems, the "thread specific" data is embedded in + * the qpt_data object. + */ + +/*------------------------------------------------------------------------------ + * Create the given thread specific data. + * + * NB: if no value is ever set, then qpt_data_get_value() will return NULL + * (whether qpthreads_enabled, or not). + */ +extern void +qpt_data_create(qpt_data data) +{ + memset(data, 0, sizeof(union qpt_data)) ; + + if (qpthreads_enabled_freeze) + { + int err = pthread_key_create(&data->key, NULL) ; + if (err != 0) + zabort_err("pthread_key_create failed", err) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Delete the given thread specific data. + * + * NB: it is the caller's responsibility to release any memory the value of + * the thread specific data may refer to. + */ +extern void +qpt_data_delete(qpt_data data) +{ + if (qpthreads_enabled) + { + int err = pthread_key_delete(data->key) ; + if (err != 0) + zabort_err("pthread_key_delete failed", err) ; + } ; + + memset(data, 0, sizeof(union qpt_data)) ; +} ; diff --git a/lib/qpthreads.h b/lib/qpthreads.h index da6b2319..926d11e5 100644 --- a/lib/qpthreads.h +++ b/lib/qpthreads.h @@ -22,6 +22,8 @@ #ifndef _ZEBRA_QPTHREADS_H #define _ZEBRA_QPTHREADS_H +#include "zconfig.h" + #include "misc.h" #include <time.h> #include <pthread.h> @@ -52,16 +54,33 @@ #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 +/*------------------------------------------------------------------------------ + * Sort out QPTHREADS_DEBUG. + * + * Set to 1 if defined, but blank. + * Set to QDEBUG if not defined. + * + * Force to 0 if QPTHREADS_NO_DEBUG is defined and not zero. + * + * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to: + * + * * can set QPTHREADS_DEBUG == 0 to turn off debug + * * or set QPTHREADS_DEBUG != 0 to turn on debug + * * or set QPTHREADS_NO_DEBUG != 0 to force debug off + */ + +#ifdef QPTHREADS_DEBUG /* If defined, make it 1 or 0 */ +# if IS_BLANK_OPTION(QPTHREADS_DEBUG) +# undef QPTHREADS_DEBUG +# define QPTHREADS_DEBUG 1 # endif -#else -# ifdef QDEBUG -# define QPTHREADS_DEBUG 1 /* Follow QDEBUG */ -#else +#else /* If not defined, follow QDEBUG */ +# define QPTHREADS_DEBUG QDEBUG +#endif + +#ifdef QPTHREADS_NO_DEBUG /* Override, if defined */ +# if IS_NOT_ZERO_OPTION(QPTHREADS_NO_DEBUG) +# undef QPTHREADS_DEBUG # define QPTHREADS_DEBUG 0 # endif #endif @@ -81,42 +100,41 @@ enum { qpthreads_debug = QPTHREADS_DEBUG } ; * qpthreads_enabled_freeze -- to test and freeze unset if not yet enabled */ -#define qpthreads_enabled ((const uint8_t)qpthreads_enabled_flag) +#define qpthreads_enabled ((const bool)qpthreads_enabled_flag) #define qpthreads_enabled_freeze qpt_freeze_qpthreads_enabled() -#define qpthreads_thread_created ((const uint8_t) \ - qpthreads_thread_created_flag) +#define qpthreads_thread_created ((const bool)qpthreads_thread_created_flag) /*============================================================================== * Data types */ - typedef pthread_t qpt_thread_t ; -typedef pthread_mutex_t qpt_mutex_t ; -typedef pthread_cond_t qpt_cond_t ; + +typedef pthread_mutex_t qpt_mutex_t[1] ; +typedef pthread_mutex_t* qpt_mutex ; + +typedef pthread_cond_t qpt_cond_t[1] ; +typedef pthread_cond_t* qpt_cond ; typedef pthread_attr_t qpt_thread_attr_t ; -typedef qpt_mutex_t* qpt_mutex ; -typedef qpt_cond_t* qpt_cond ; /*============================================================================== * Thread Creation -- see qpthreads.c for further discussion. * * NB: it is a FATAL error to attempt these if !qpthreads_enabled. */ - enum qpt_attr_options { qpt_attr_joinable = 0, /* the default for Quagga */ - qpt_attr_detached = 0x0001, /* otherwise will set joinable */ + qpt_attr_detached = BIT(0), /* otherwise will set joinable */ - qpt_attr_sched_inherit = 0x0002, /* otherwise will use default */ + qpt_attr_sched_inherit = BIT(1), /* otherwise will use default */ - qpt_attr_sched_scope = 0x0004, /* otherwise inherit/default */ - qpt_attr_sched_policy = 0x0008, /* otherwise inherit/default */ - qpt_attr_sched_priority = 0x0010, /* otherwise inherit/default */ + qpt_attr_sched_scope = BIT(2), /* otherwise inherit/default */ + qpt_attr_sched_policy = BIT(3), /* otherwise inherit/default */ + qpt_attr_sched_priority = BIT(4), /* otherwise inherit/default */ } ; #define qpt_attr_sched_explicit ( qpt_attr_sched_scope \ @@ -140,11 +158,11 @@ qpt_thread_join(qpt_thread_t thread_id) ; /*============================================================================== * qpthreads_enabled support -- NOT FOR PUBLIC CONSUMPTION ! */ -Private uint8_t qpthreads_enabled_flag ; /* DO NOT TOUCH THIS PLEASE */ -Private uint8_t qpthreads_thread_created_flag ; /* DO NOT TOUCH THIS PLEASE */ +Private bool qpthreads_enabled_flag ; /* DO NOT TOUCH THIS PLEASE */ +Private bool qpthreads_thread_created_flag ; /* DO NOT TOUCH THIS PLEASE */ -Private int -qpt_set_qpthreads_enabled(int how) ; /* qpthreads_enabled := how */ +Private bool +qpt_set_qpthreads_enabled(bool want_enabled) ; /* qpthreads_enabled := want */ Private int qpt_freeze_qpthreads_enabled(void) ; /* get and freeze qpthreads_enabled */ @@ -205,22 +223,25 @@ Inline bool qpt_thread_is_self(qpt_thread_t id) enum qpt_mutex_options { - qpt_mutex_quagga = 0x0000, /* Quagga's default */ - qpt_mutex_normal = 0x0001, - qpt_mutex_recursive = 0x0002, - qpt_mutex_errorcheck = 0x0003, - qpt_mutex_default = 0x0004, /* system default */ + qpt_mutex_quagga = 0, /* Quagga's default */ + qpt_mutex_normal, + qpt_mutex_recursive, + qpt_mutex_errorcheck, + qpt_mutex_default, /* system default */ } ; #ifndef QPT_MUTEX_TYPE_DEFAULT # define QPT_MUTEX_TYPE_DEFAULT PTHREAD_MUTEX_NORMAL #endif +enum +{ #if QPTHREADS_DEBUG -# define QPT_MUTEX_TYPE PTHREAD_MUTEX_ERRORCHECK + QPT_MUTEX_TYPE = PTHREAD_MUTEX_ERRORCHECK #else -# define QPT_MUTEX_TYPE QPT_MUTEX_TYPE_DEFAULT + QPT_MUTEX_TYPE = QPT_MUTEX_TYPE_DEFAULT #endif +} ; extern qpt_mutex /* freezes qpthreads_enabled */ qpt_mutex_init_new(qpt_mutex mx, enum qpt_mutex_options opts) ; @@ -320,7 +341,7 @@ qpt_mutex_lock(qpt_mutex mx) { if (qpthreads_enabled) { -#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS) +#if QPTHREADS_DEBUG pthread_mutex_lock(mx) ; #else int err = pthread_mutex_lock(mx) ; @@ -330,7 +351,8 @@ qpt_mutex_lock(qpt_mutex mx) } ; } ; -/* Try to lock given mutex -- every time a winner if !qpthreads_enabled. +/*------------------------------------------------------------------------------ + * Try to lock given mutex -- every time a winner if !qpthreads_enabled. * * Returns: lock succeeded (1 => have locked, 0 => unable to lock). * @@ -354,23 +376,19 @@ qpt_mutex_trylock(qpt_mutex mx) return 1 ; } ; -/* Unlock given mutex -- or do nothing if !qpthreads_enabled. +/*------------------------------------------------------------------------------ + * Unlock given mutex -- or do nothing if !qpthreads_enabled. * - * Unless both NCHECK_QPTHREADS and NDEBUG are defined, checks that the - * return value is valid -- zabort_errno if it isn't. + * Checks that the return value is valid -- zabort_err if it isn't. */ Inline void qpt_mutex_unlock(qpt_mutex mx) { if (qpthreads_enabled) { -#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS) - pthread_mutex_unlock(mx) ; -#else int err = pthread_mutex_unlock(mx) ; if (err != 0) zabort_err("pthread_mutex_unlock failed", err) ; -#endif } ; } ; @@ -378,73 +396,111 @@ qpt_mutex_unlock(qpt_mutex mx) * Condition variable inline functions */ -/* Wait for given condition variable -- do nothing if !qpthreads_enabled +/*------------------------------------------------------------------------------ + * Wait for given condition variable -- do nothing if !qpthreads_enabled * - * Unless both NCHECK_QPTHREADS and NDEBUG are defined, checks that the - * return value is valid -- zabort_errno if it isn't. + * Checks that the return value is valid -- zabort_err if it isn't. */ Inline void qpt_cond_wait(qpt_cond cv, qpt_mutex mx) { if (qpthreads_enabled) { -#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS) - pthread_cond_wait(cv, mx) ; -#else int err = pthread_cond_wait(cv, mx) ; if (err != 0) zabort_err("pthread_cond_wait failed", err) ; -#endif } ; } ; -/* Signal given condition -- do nothing if !qpthreads_enabled +/*------------------------------------------------------------------------------ + * Signal given condition -- do nothing if !qpthreads_enabled * - * Unless both NCHECK_QPTHREADS and NDEBUG are defined, checks that the - * return value is valid -- zabort_errno if it isn't. + * Checks that the return value is valid -- zabort_err if it isn't. */ Inline void qpt_cond_signal(qpt_cond cv) { if (qpthreads_enabled) { -#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS) - pthread_cond_signal(cv) ; -#else int err = pthread_cond_signal(cv) ; if (err != 0) zabort_err("pthread_cond_signal failed", err) ; -#endif } ; } ; -/* Broadcast given condition -- do nothing if !qpthreads_enabled +/*------------------------------------------------------------------------------ + * Broadcast given condition -- do nothing if !qpthreads_enabled * - * Unless both NCHECK_QPTHREADS and NDEBUG are defined, checks that the - * return value is valid -- zabort_errno if it isn't. + * Checks that the return value is valid -- zabort_err if it isn't. */ Inline void qpt_cond_broadcast(qpt_cond cv) { if (qpthreads_enabled) { -#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS) - pthread_cond_broadcast(cv) ; -#else int err = pthread_cond_broadcast(cv) ; if (err != 0) zabort_err("pthread_cond_broadcast failed", err) ; -#endif } ; } ; /*============================================================================== * Signal Handling. */ -void /* FATAL error if !qpthreads_enabled */ +extern void /* sigprocmask() if !qpthreads_enabled */ qpt_thread_sigmask(int how, const sigset_t* set, sigset_t* oset) ; -void /* FATAL error if !qpthreads_enabled */ +extern void /* FATAL error if !qpthreads_enabled */ qpt_thread_signal(qpt_thread_t thread, int signum) ; +/*============================================================================== + * Thread Specific Data Handling. + * + * Note that if !qpthreads_enabled, this maintains the data value, without + * using any pthread primitives. + * + * Note also that this does not support the pthread value destructor -- because + * cannot support that for non qpthreads_enabled (straightforwardly, anyway). + */ +union qpt_data +{ + pthread_key_t key ; /* if qpthreads_enabled */ + void* value ; /* otherwise */ + const void* cvalue ; +} ; + +typedef union qpt_data qpt_data_t[1] ; +typedef union qpt_data* qpt_data ; + +extern void qpt_data_create(qpt_data data) ; +extern void qpt_data_delete(qpt_data data) ; + +/*------------------------------------------------------------------------------ + * Set thread specific data value -- value is void* + */ +Inline void +qpt_data_set_value(qpt_data data, const void* value) +{ + if (qpthreads_enabled) + { + int err = pthread_setspecific(data->key, value) ; + if (err != 0) + zabort_err("pthread_setspecific failed", err) ; + } + else + data->cvalue = value ; +} ; + +/*------------------------------------------------------------------------------ + * Get thread specific data value -- value is void* + */ +Inline void* +qpt_data_get_value(qpt_data data) +{ + if (qpthreads_enabled) + return pthread_getspecific(data->key) ; + else + return data->value ; +} ; + #endif /* _ZEBRA_QPTHREADS_H */ diff --git a/lib/qrand.c b/lib/qrand.c new file mode 100644 index 00000000..ff52afd4 --- /dev/null +++ b/lib/qrand.c @@ -0,0 +1,48 @@ +/* Pseudo Random Sequence + * Copyright (C) 2010 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. + */ + +#include "qrand.h" + +/*============================================================================== + * Simple 32 bit random sequence. + */ + +/*------------------------------------------------------------------------------ + * Return next in the given sequence. + * + * Returns value in range 0..range-1, or 0..0x7FFF_FFFF if range == 0 + * + * If range == 1, returns 0 every time ! + */ +extern int +qrand(qrand_seq seq, int range) +{ + uint64_t r ; + + r = seq->last ^ 3141592653 ; + r = ((r * 2650845021) + 5) & 0xFFFFFFFF ; /* see Knuth */ + seq->last = r ; + + if (range == 0) + return r >> 1 ; + else + return (r % range) ; +} ; diff --git a/lib/qrand.h b/lib/qrand.h new file mode 100644 index 00000000..e9a18436 --- /dev/null +++ b/lib/qrand.h @@ -0,0 +1,52 @@ +/* Pseudo Random Sequence -- Header + * Copyright (C) 2010 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_QRAND_H +#define _ZEBRA_QRAND_H + +#include "misc.h" + +/*============================================================================== + * Simple 32 bit random sequence. + * + * Produces 32-bit signed integers in 0..0x7FFF_FFFF. + * + * Object of the exercise is to be able to produce repeatable sequence, so can + * debug ! + */ + +struct qrand_seq +{ + uint last ; +} ; + +typedef struct qrand_seq* qrand_seq ; +typedef struct qrand_seq qrand_seq_t[1] ; + +#define QRAND_SEQ_INIT(s) { { s } } + +/*============================================================================== + * Functions + */ + +extern int qrand(qrand_seq seq, int range) ; + +#endif /* _ZEBRA_QRAND_H */ diff --git a/lib/qstring.c b/lib/qstring.c index 19f74e8c..d00e3b70 100644 --- a/lib/qstring.c +++ b/lib/qstring.c @@ -28,18 +28,6 @@ */ /*------------------------------------------------------------------------------ - * Create a new, empty qs - */ -extern qstring -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 @@ -62,20 +50,42 @@ qs_new_body(qstring qs, usize slen, bool keep_alias) } ; /*------------------------------------------------------------------------------ - * Create a new, empty qs with body + * Create a new, empty qs + * + * If non-zero slen is given, a body is allocated (size = slen + 1). + * If zero slen is given, no body is allocated. + * + * Sets 'len' = 'cp' = 0. + * + * Returns: address of qstring */ extern qstring -qs_new_with_body(usize slen) +qs_new(usize slen) { qstring qs ; - qs = qs_new() ; - qs_new_body(qs, slen, false) ; + /* zeroising sets a completely empty qstring -- see qs_init_new() */ + + qs = XCALLOC(MTYPE_QSTRING, sizeof(qstring_t)) ; + + confirm(QSTRING_INIT_ALL_ZEROS) ; + + if (slen != 0) + qs_new_body(qs, slen, false) ; return qs ; } ; /*------------------------------------------------------------------------------ + * Create a new, empty qs with body + */ +extern qstring +qs_new_with_body(usize slen) +{ + return qs_new(slen | 1) ; +} ; + +/*------------------------------------------------------------------------------ * Initialise qstring -- allocate if required. * * If non-zero slen is given, a body is allocated (size = slen + 1). @@ -92,9 +102,9 @@ extern qstring qs_init_new(qstring qs, usize slen) { if (qs == NULL) - qs = qs_new() ; - else - memset(qs, 0, sizeof(qstring_t)) ; + return qs_new(slen) ; + + memset(qs, 0, sizeof(qstring_t)) ; confirm(QSTRING_INIT_ALL_ZEROS) ; @@ -346,10 +356,24 @@ qs_set_fill_n(qstring qs, usize len, const char* src, usize flen) * Sets 'len' to new length. * Does not affect 'cp'. * + * Will work even if the stuff being appended is somewhere in the body of the + * qstring !! + * * Returns: address of the qstring (allocated if required). */ /*------------------------------------------------------------------------------ + * Append given qstring to a qstring. + * + * See notes above. + */ +extern qstring +qs_append(qstring qs, qstring src) +{ + return qs_append_str_n(qs, qs_body(src), qs_len(src)) ; +} ; + +/*------------------------------------------------------------------------------ * Append given string to a qstring. * * Treats src == NULL as an empty string. Otherwise src must be a '\0' @@ -359,9 +383,9 @@ qs_set_fill_n(qstring qs, usize len, const char* src, usize flen) * * See notes above. */ -extern qstring qs_append(qstring qs, const char* src) +extern qstring qs_append_str(qstring qs, const char* src) { - return qs_append_n(qs, src, (src != NULL) ? strlen(src) : 0) ; + return qs_append_str_n(qs, src, (src != NULL) ? strlen(src) : 0) ; } ; /*------------------------------------------------------------------------------ @@ -373,29 +397,18 @@ extern qstring qs_append(qstring qs, const char* src) * See notes above. */ extern qstring -qs_append_n(qstring qs, const char* src, usize n) +qs_append_str_n(qstring qs, const char* src, usize n) { qs = qs_extend(qs, n) ; /* allocate, copy any alias, extend body, set new length, etc */ if (n != 0) - memcpy(qs_ep_char_nn(qs) - n, src, n) ; + memmove(qs_ep_char_nn(qs) - n, src, n) ; return qs ; } ; /*------------------------------------------------------------------------------ - * 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)) ; -} ; - -/*------------------------------------------------------------------------------ * Append given elstring to a qstring. * * See notes above. @@ -403,12 +416,9 @@ qs_append_qs(qstring qs, qstring src) extern qstring qs_append_els(qstring qs, elstring src) { - return qs_append_n(qs, els_body(src), els_len(src)) ; + return qs_append_str_n(qs, els_body(src), els_len(src)) ; } ; - - - /*============================================================================== * Setting of alias. * @@ -461,7 +471,7 @@ extern qstring qs_set_alias_n(qstring qs, const char* src, usize n) { if (qs == NULL) - qs = qs_new() ; + qs = qs_new(0) ; /* 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. @@ -596,7 +606,7 @@ qs_vprintf(qstring qs, const char *format, va_list args) qqs = qs ; /* NULL => need to make qs */ if (qs == NULL) - qqs = qs_new() ; /* Sets size, cp & len = 0 */ + qqs = qs_new(0) ; /* Sets size, cp & len = 0 */ else qs_clear(qqs) ; /* Sets cp & len = 0, discard any alias, but keep existing body */ @@ -642,6 +652,9 @@ qs_vprintf(qstring qs, const char *format, va_list args) * * May increase or decrease 'len'. but does not affect 'cp'. * + * If the given src is NULL, do not insert anything, just leave the space + * ready for it. + * * Returns: number of bytes beyond 'cp' that now exist. * * qstring MUST NOT be NULL @@ -691,7 +704,7 @@ qs_replace(qstring qs, usize r, const void* src, usize n) if (after > 0) /* move the after part before inserting */ memmove(np + cp + n, ap + cp + r, after) ; - if (n > 0) /* insert */ + if ((n > 0) && (src != NULL)) /* insert */ memmove(np + cp, src, n) ; /* Set new 'len' */ diff --git a/lib/qstring.h b/lib/qstring.h index 48124a2d..b05c738b 100644 --- a/lib/qstring.h +++ b/lib/qstring.h @@ -66,6 +66,7 @@ struct qstring usize b_size ; bool alias ; + char* empty ; } ; typedef struct qstring qstring_t[1] ; @@ -116,9 +117,13 @@ 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 usize qs_size(qstring qs) ; +Inline usize qs_size_nn(qstring qs) ; + Inline ulen qs_len(qstring qs) ; Inline ulen qs_len_nn(qstring qs) ; Inline void qs_set_len_nn(qstring qs, ulen len) ; +Inline void qs_set_strlen_nn(qstring qs) ; Inline ulen qs_cp(qstring qs) ; Inline ulen qs_cp_nn(qstring qs) ; @@ -259,6 +264,20 @@ qs_set_body_nn(qstring qs, const void* body) els_set_body_nn(qs->els, body) ; } ; +/* Size of qstring body -- zero if qstring is NULL, or is alias. */ +Inline usize +qs_size(qstring qs) +{ + return (qs != NULL) ? qs_size_nn(qs) : 0 ; +} ; + +/* Size of qstring (not NULL). */ +Inline usize +qs_size_nn(qstring qs) +{ + return (qs->size) ; +} ; + /*----------------------------------------------------------------------------*/ /* 'len' of qstring -- returns 0 if qstring is NULL */ @@ -282,6 +301,13 @@ qs_set_len_nn(qstring qs, ulen len) els_set_len_nn(qs->els, len) ; } ; +/* set 'len' of qstring according to strlen(body) -- nothing NULL ! */ +Inline void +qs_set_strlen_nn(qstring qs) +{ + els_set_len_nn(qs->els, strlen(qs_body_nn(qs))) ; +} ; + /*----------------------------------------------------------------------------*/ /* 'cp' of qstring -- returns 0 if qstring is NULL */ @@ -378,10 +404,11 @@ Inline void qs_set_real_body_nn(qstring qs) * Functions */ -extern qstring qs_new(void) ; +extern qstring qs_new(usize slen) ; 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) ; +Inline qstring qs_free(qstring qs) ; Inline char* qs_make_string(qstring qs) ; Inline const char* qs_string(qstring qs) ; @@ -400,9 +427,9 @@ 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_append_qs(qstring qs, qstring src) ; +extern qstring qs_append_str(qstring qs, const char* src) ; +extern qstring qs_append_str_n(qstring qs, const char* src, usize n) ; +extern qstring qs_append(qstring qs, qstring src) ; extern qstring qs_append_els(qstring qs, elstring src) ; extern qstring qs_set_alias(qstring qs, const char* src) ; @@ -437,6 +464,15 @@ Inline bool qs_substring(qstring a, qstring b) ; */ /*------------------------------------------------------------------------------ + * Free given qstring + */ +Inline qstring +qs_free(qstring qs) +{ + return qs_reset(qs, free_it) ; +} ; + +/*------------------------------------------------------------------------------ * Return pointer to string value -- ensure not alias and '\0' terminated. * * If is alias, copies that before adding '\0' terminator. @@ -444,31 +480,36 @@ Inline bool qs_substring(qstring a, qstring b) ; * 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 ""). + * If qs == NULL returns pointer to empty '\0' terminated string. * * NB: The qstring should not be changed or reset until this pointer has been * discarded ! + * + * NB: The value returned is not "const" BUT caller is NOT entitled to change + * any part of the string -- CERTAINLY nothing from the '\0' onwards ! */ Inline char* qs_make_string(qstring qs) { + static char empty_string[1] ; + usize len ; char* p ; if (qs == NULL) { len = 0 ; - qs = qs_new_with_body(len) ; + p = empty_string ; } 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 = qs_char_nn(qs) ; *(p + len) = '\0' ; return p ; @@ -517,6 +558,9 @@ qs_string(qstring qs) * If is an alias qstring, discard the alias. * * NB: does not create a qstring body if there isn't one. + * + * NB: does not change the qstring body if there is one. (Which is used in + * vio_lc_write_nb(). */ Inline void qs_clear(qstring qs) diff --git a/lib/qtime.c b/lib/qtime.c index 92e67e27..44a27b0f 100644 --- a/lib/qtime.c +++ b/lib/qtime.c @@ -240,3 +240,88 @@ qt_random(uint32_t seed) */ return x ^ ((y >> 16) & 0xFFFF) ^ ((y & 0xFFFF) << 16) ; } ; + +/*============================================================================== + * Tracking the local timezone, so can: + * + * a) rapidly convert clock_gettime(CLOCK_REALTIME, ...) times + * + * b) do that thread-safe + * + * c) do that async-signal-safe + * + * Assumptions: + * + * a) that timezones are on at most 5 minute boundaries (15 probably!) + * + * b) that DST changes are at least 60 days apart + * + * c) that DST changes occur on times which are on 5 minute boundaries + * (60 probably -- but this means that the DST change is on a 5 minute + * bounderay in local and epoch times !) + * + * Sets up and maintains a table containing 8 entries: + * + * [-3] previous - 2 + * [-2] previous - 1 + * [-1] previous -- previous 0-7 days + * [ 0] current -- current 0-7 days + * [+1] next -- next 0-7 days + * [+2] next + 1 + * [+3] next + 2 + * [ X] sentinal + * + * These are configured before any threads or anything else very much runs, so + * they are essentially static. There is a "current index", which is set to + * '0' to start with. + * + * Each entry comprises: + * + * * start time -- entry is valid for epoch times >= start + * * end time -- entry is valid for epoch times < end + * * offset -- add to epoch time to get local + * + * When set up the current timezone initially starts on the nearest 5 minute + * boundary in the past, and covers up to 7 days into the future, unless the + * timezone changes in that time. The timezones on either side are set + * similarly. + * + * At most one of these timezones may be a short one -- so this covers at least + * 14 days into the past and 21 into the future, and as time advances, upto + * 21 days into the past and down to 14 days into the future. + * + * Maximum range is 56 days -- which is within the assumed 60 days between + * DST changes. + * + * When time advances past the current entry the next, next + 1 and + 2 cover + * at least 14 days (21 if none are short entries). + * + * Every now and then (say every 5 minutes) a background process can check the + * current time. If that is no longer in the current entry, needs to update + * the table. Assuming time is moving forward: sets sentinal to be the next + * 0-7 days following the current last entry, and updates the "current index". + * + * BIG ASSUMPTION: that the "current index" value is written atomically, wrt + * to threads as well as signals. + * + * It doesn't matter if a thread or signal action code picks + * up an out of date "current index" value, because all the + * entries for the old state are still valid. + * + * No entry is changed while it is covered by the current + * index -3..+3. + * + * This works fine, UNLESS the clock_gettime(CLOCK_REALTIME, ...) changes + * dramatically -- as might happen if the operator adjusts the system clock a + * long way ! + * + * To cope with this, a spare set of 8 entries are kept, and a new table can + * be built (under mutex). The worst that happens is that threads may be + * blocked waiting for the table to be updated. + * + * If the table is found to be out of date when a signal is bringing the + * system down, then the times logged will just have to use either the first + * or the last entry, and have done with it. + */ + + diff --git a/lib/qtime.h b/lib/qtime.h index 94d468a8..38d717c0 100644 --- a/lib/qtime.h +++ b/lib/qtime.h @@ -52,10 +52,10 @@ typedef qtime_t qtime_mono_t ; /* qtime_t value, monotonic time-base */ typedef qtime_t qtime_tod_t ; /* qtime_t value, timeofday time-base... */ /* ...just in case != CLOCK_REALTIME ! */ -/* A qtime_t second 123456789 -- nano-seconds */ -#define QTIME_SECOND 1000000000 -#define TIMESPEC_SECOND 1000000000 -#define TIMEVAL_SECOND 1000000 +/* A qtime_t second 123456789 -- nano-seconds */ +#define QTIME_SECOND ((qtime_t)1000000000) +#define TIMESPEC_SECOND 1000000000 +#define TIMEVAL_SECOND 1000000 /* Macro to convert time in seconds to a qtime_t */ /* Note that the time to convert may be a float. */ diff --git a/lib/qtimers.h b/lib/qtimers.h index 2af81819..16808f1b 100644 --- a/lib/qtimers.h +++ b/lib/qtimers.h @@ -35,16 +35,33 @@ * 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 +/*------------------------------------------------------------------------------ + * Sort out QTIMERS_DEBUG. + * + * Set to 1 if defined, but blank. + * Set to QDEBUG if not defined. + * + * Force to 0 if QTIMERS_NO_DEBUG is defined and not zero. + * + * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to: + * + * * can set QTIMERS_DEBUG == 0 to turn off debug + * * or set QTIMERS_DEBUG != 0 to turn on debug + * * or set QTIMERS_NO_DEBUG != 0 to force debug off + */ + +#ifdef QTIMERS_DEBUG /* If defined, make it 1 or 0 */ +# if IS_BLANK_OPTION(QTIMERS_DEBUG) +# undef QTIMERS_DEBUG +# define QTIMERS_DEBUG 1 # endif -#else -# ifdef QDEBUG -# define QTIMERS_DEBUG 1 /* Follow QDEBUG */ -#else +#else /* If not defined, follow QDEBUG */ +# define QTIMERS_DEBUG QDEBUG +#endif + +#ifdef QTIMERS_NO_DEBUG /* Override, if defined */ +# if IS_NOT_ZERO_OPTION(QTIMERS_NO_DEBUG) +# undef QTIMERS_DEBUG # define QTIMERS_DEBUG 0 # endif #endif diff --git a/lib/routemap.c b/lib/routemap.c index d716e0b5..6de2098d 100644 --- a/lib/routemap.c +++ b/lib/routemap.c @@ -913,14 +913,15 @@ route_map_finish (void) } /* VTY related functions. */ -DEFUN (route_map, - route_map_cmd, - "route-map WORD (deny|permit) <1-4294967295>", - "Create route-map or enter route-map command mode\n" - "Route map tag\n" - "Route map denies set operations\n" - "Route map permits set operations\n" - "Sequence to insert to/delete from existing route-map entry\n") +DEFUN_ATTR (route_map, + route_map_cmd, + "route-map WORD (deny|permit) <1-4294967295>", + "Create route-map or enter route-map command mode\n" + "Route map tag\n" + "Route map denies set operations\n" + "Route map permits set operations\n" + "Sequence to insert to/delete from existing route-map entry\n", + CMD_ATTR_NODE + RMAP_NODE) { int permit; unsigned long seq; diff --git a/lib/sigevent.c b/lib/sigevent.c index 18fcffb0..c7c1e134 100644 --- a/lib/sigevent.c +++ b/lib/sigevent.c @@ -19,11 +19,19 @@ * 02111-1307, USA. */ -#include <zebra.h> -#include <sigevent.h> -#include <log.h> - -#ifdef SA_SIGINFO +#include "zebra.h" +#include "misc.h" +#include "sigevent.h" +#include "log.h" +#include "vty.h" +#include "qpnexus.h" +#include "qpthreads.h" + +#include <stdarg.h> + +/*------------------------------------------------------------------------------ + * Want to get some context for core and exit handlers. + */ #ifdef HAVE_UCONTEXT_H #ifdef GNU_LINUX /* get REG_EIP from ucontext.h */ @@ -33,50 +41,407 @@ #endif /* GNU_LINUX */ #include <ucontext.h> #endif /* HAVE_UCONTEXT_H */ -#endif /* SA_SIGINFO */ +/*------------------------------------------------------------------------------ + * Use SA_SIGINFO type handlers throughout + */ +#ifndef SA_SIGINFO +#error Sorry... require SA_SIGINFO +#endif + +typedef void sig_handler(int signo, siginfo_t* info, void* context) ; + +/*============================================================================== + * Signal handling for Quagga. + * + * The objectives are: + * + * 1) to handle the abnormal terminations so that they are logged, and + * any available information logged with them. + * + * 2) to ignore a number of signals that have no significance + * + * 3) to catch some signals such that they are treated as events in either + * the qpthreads or the legacy threads worlds. + * + * For the qpthreads world, these are all handled in the main thread. + * + * These may not be any of the "hard" signals or any of the signals + * reserved by the library. + * + * 4) to catch some signals such that they cause an "interrupt" to, e.g. + * pselect(), but have no other effect. + * + * SIGUSR2 is reserved for this purpose for qpthreads world + * (aka SIG_INTERRUPT). + * + * Signal disposition is established early in the morning, and is static from + * then on. + */ + +/*============================================================================== + * Signal Sets. + * + * The following signal sets are initialised by signal_init(). + * + * * hard_signals -- signals that mean that the program has misbehaved, and + * should exit, now -- e.g. SIGSEGV or SIGILL. + * + * In the pthreaded world, these signals are handled + * (or at least, not blocked) by all threads, and it is + * expected that they will be given to the thread which + * has failed. + * + * These signals are not blocked. + * + * This includes SIGKILL and SIGSTOP, which cannot be + * blocked, caught or ignored. + * + * * core_signals -- signals that by default are terminate + core, so this + * more or less the hard_signals, except: + * + * * excludes SIGKILL and SIGSTOP + * + * * includes at least one (SIGQUIT) signal that is + * not a hard_signal. + * + * The default action is to catch these signals, log the + * event and then abort() -- having turned off the + * SIGABRT handler ! + * + * * exit_signals -- signals that by default are terminate, so this + * includes, e.g., SIGTERM or SIGINT. + * + * The default action is to catch these signals, log the + * event and then exit(). + * + * * ignore_signals -- signals which, by default, we wish to ignore, so are + * set to SIG_IGN. + * + * * qsig_signals -- signals which are caught, and are later signalled to + * quagga_sigevent_process(). + * + * Note that multiple signals may be seen before + * quagga_sigevent_process() is run, but they will only + * generate one event. + * + * * qsig_interrupts -- signals which are caught, but otherwise ignored, + * so their only function is to interrupt -- in particular + * to interrupt pselect(). + * + * * qsig_reserved -- signal which the library reserves for itself. + * + * In the pthreads world, all signals other than the hard_signals are blocked + * by all threads other than the main thread. + * + * Note that we leave SIGTRAP alone -- so do not disturb debuggger(s). + */ +static bool signals_initialised = false ; + +static sigset_t hard_signals[1] ; +static sigset_t core_signals[1] ; +static sigset_t exit_signals[1] ; +static sigset_t ignore_signals[1] ; +static sigset_t qsig_signals[1] ; +static sigset_t qsig_interrupts[1] ; +static sigset_t qsig_reserved[1] ; + +static void qsig_add(int signo, qsig_event* event) ; +static int signal_set_set(sigset_t* set, sig_handler* handler) ; +static int signal_set(int signo, sig_handler* handler) ; + +static void __attribute__ ((noreturn)) + core_handler(int signo, siginfo_t *info, void *context) ; +static void __attribute__ ((noreturn)) + exit_handler(int signo, siginfo_t* info, void* context) ; +static void + quagga_signal_handler(int signo, siginfo_t* info, void* context) ; +static void + quagga_interrupt_handler(int signo, siginfo_t* info, void* context) ; + +/*------------------------------------------------------------------------------ + * The following signals are not known to POSIX (2008) or are extensions. + */ +#ifndef SIGEMT +# define SIGEMT 0 +#endif +#ifndef SIGIO +# define SIGIO 0 +#endif +#ifndef SIGIOT +# define SIGIOT 0 +#endif +#ifndef SIGPOLL +# define SIGPOLL 0 +#endif +#ifndef SIGPROF +# define SIGPROF 0 +#endif +#ifndef SIGPWR +# define SIGPWR 0 +#endif +#ifndef SIGSTKFLT +# define SIGSTKFLT 0 +#endif +#ifndef SIGSYS +# define SIGSYS 0 +#endif +#ifndef SIGVTALRM +# define SIGVTALRM 0 +#endif +#ifndef SIGWINCH +# define SIGWINCH 0 +#endif +#ifndef SIGXRES +# define SIGXRES 0 +#endif + +/*------------------------------------------------------------------------------ + * The signal handling below assumes that no signal that we have any interest + * in will have a signal number greater than the following. + * + * All the signals we are interested in are initialised in signal_init, so + * asserts in the code below will trap, very early on, any signal with a + * larger number than this. + * + * This value is used to place a very outside limit on the signal numbers that + * will attempt to deal with. + */ +enum { SIG_MAX = 128, SIG_COUNT } ; + +/* The value is established at signal_init() time, as the maximum signal + * number to consider -- established by sigaddset() on an empty set. + */ +int sig_max = 0 ; + +/*------------------------------------------------------------------------------ + * Quagga signals descriptor struct + * + * Maps real signal numbers to "qso" ordinals... A relatively limited number + * of signals need to be fed into the event system, this mechanism minimises + * the work required to discover which signal has gone off. + * + * The qsig actions are held in a small vector in the static sigmaster + * structure. + */ +enum +{ + sig_null = 0, /* no real signal is this */ + sig_min = 1, /* first real signal is at least this */ +} ; +typedef uchar sig_num_t ; /* signal number */ +CONFIRM(SIG_MAX <= UCHAR_MAX) ; + +enum +{ + qso_null = 0, /* no qsig uses this */ + qso_min = 1, /* first qsig is this */ + qso_max = 10, /* only a handful are really required */ -/* master signals descriptor struct */ -struct quagga_sigevent_master_t + qso_count, /* number qs ordinals */ +} ; +typedef uchar qs_ord_t ; /* qs ordinal */ + +/*------------------------------------------------------------------------------ + * Static structure for all known qsig_signals. + * + * Note that the qsig_interrupts are not recorded here ! + */ +struct quagga_sigevent_master { + qs_ord_t qsigc ; /* number of signals known */ + volatile sig_atomic_t caught[qso_count] ; + + qsig_event* event[qso_count] ; /* how to deal with them */ + + qs_ord_t map[SIG_COUNT] ; /* real signal to qs ordinal */ + +#ifdef SIGEVENT_SCHEDULE_THREAD struct thread *t; +#endif + +} qsig_master ; + +/*------------------------------------------------------------------------------ + * Initialise signals. + * + * 1. construct the signal sets discussed above. + * + * 2. set default handlers for: core_signals -- core_handler() + * exit_signals -- exit_handler() + * ignore_signals -- SIG_IGN + * + * 3. set handlers for signals used by library + * + * 4. set handlers for signals used by the daemon. + * + * This is done once, and once only, early in the morning. + */ +extern void +signal_init (struct thread_master *m, int sigc, + struct quagga_signal_t signals[]) +{ + int i ; + + /* Set sig_max by experiment */ + { + sigset_t trial[1] ; + int signo ; + + sigemptyset(trial) ; + for (signo = sig_min ; signo <= SIG_COUNT ; ++signo) + { + if (sigaddset(trial, signo) < 0) + break ; + } ; + + --signo ; /* last acceptable signo */ + if ((signo < sig_min) || (signo > SIG_MAX)) + zabort("cannot establish reasonable 'sig_max'") ; + + sig_max = signo ; + } ; + + /* Construct the standard sets of signals. */ + sigmakeset(hard_signals, SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGKILL, + SIGSEGV, SIGSTOP, SIGXCPU, SIGXFSZ, + SIGSYS, + SIGEMT, SIGIOT, SIGXRES, + -1) ; + + sigcopyset(core_signals, hard_signals) ; + sigaddset(core_signals, SIGQUIT) ; + sigdelset(core_signals, SIGKILL) ; + sigdelset(core_signals, SIGSTOP) ; + + sigmakeset(exit_signals, SIGALRM, SIGHUP, SIGINT, SIGTERM, SIGUSR1, SIGUSR2, + SIGIO, SIGPOLL, SIGPROF, SIGPWR, SIGSTKFLT, + SIGVTALRM, + -1) ; + + sigmakeset(ignore_signals, SIGCHLD, SIGCONT, SIGPIPE, SIGTSTP, SIGTTIN, + SIGTTOU, SIGURG, SIGWINCH, + -1) ; + + /* Initialise the sig_master, qsig_signals and qsig_interrupts. + * + * Reserve all the hard_signals. + */ + memset(&qsig_master, 0, sizeof(qsig_master)) ; + + sigemptyset(qsig_signals) ; + sigemptyset(qsig_interrupts) ; + sigcopyset(qsig_reserved, hard_signals) ; + + /* The signals used by the library. + * + * Added to qsig_signals or qsig_interrupts and to qsig_reserved. + */ + qsig_add(SIGCHLD, vty_sigchld) ; + qsig_add(SIG_INTERRUPT, NULL) ; + + /* Now collect the daemon's own signals. + * + * Added to qsig_signals or qsig_interrupts and to qsig_reserved. + */ + for (i = 0 ; i < sigc ; ++i) + qsig_add(signals[i].signal, signals[i].handler) ; + + /* In case signals with different names are the same, and to make the + * required reservations, let qsig_reserved take precedence over exit_signals + * and those take precedence over ignore_signals. + */ + sigsubsets(exit_signals, qsig_reserved) ; + sigsubsets(ignore_signals, qsig_reserved) ; + sigsubsets(ignore_signals, exit_signals) ; + + /* Also, remove anything from core_signals which is now qsig_signals or + * qsig_interrupts (possibly SIGQUIT !). + */ + sigsubsets(core_signals, qsig_signals) ; + sigsubsets(core_signals, qsig_interrupts) ; + + /* Install handlers */ + signal_set_set(core_signals, core_handler) ; + signal_set_set(exit_signals, exit_handler) ; + signal_set_set(ignore_signals, NULL) ; + signal_set_set(qsig_signals, quagga_signal_handler) ; + signal_set_set(qsig_interrupts, quagga_interrupt_handler) ; + + /* If using a timer thread to scan for signal events, start that now. + */ +#ifdef SIGEVENT_SCHEDULE_THREAD + sig_master.t = + thread_add_timer (m, quagga_signal_timer, &sig_master, + QUAGGA_SIGNAL_TIMER_INTERVAL); +#endif /* SIGEVENT_SCHEDULE_THREAD */ - struct quagga_signal_t *signals; - int sigc; + /* Signals are now initialised */ + signals_initialised = true ; +} ; - volatile sig_atomic_t caught; -} sigmaster; +/*------------------------------------------------------------------------------ + * Get the hard_signals set + */ +extern const sigset_t* +signal_get_hard_set(void) +{ + assert(signals_initialised) ; + return hard_signals ; +} ; -/* Generic signal handler - * Schedules signal event thread +/*------------------------------------------------------------------------------ + * Add signal to those to be caught either for quagga_sigevent_process() or + * to then be dropped (so called interrupt signals). + * + * Checks that signal is not amongst the reserved signals. + * + * Add signal to the reserved signals. */ static void -quagga_signal_handler (int signo) +qsig_add(int signo, qsig_event* event) { - int i; - struct quagga_signal_t *sig; + int s ; + + s = sigismember(qsig_reserved, signo) ; + if ((s < 0) || (signo > sig_max)) + zabort("invalid or unknown signal number") ; + if (s > 0) + zabort("signal is reserved (or already set)") ; - for (i = 0; i < sigmaster.sigc; i++) + sigaddset(qsig_reserved, signo) ; + + if (event == NULL) + sigaddset(qsig_interrupts, signo) ; + else { - sig = &(sigmaster.signals[i]); + sigaddset(qsig_signals, signo) ; - if (sig->signal == signo) - sig->caught = 1; - } + if (qsig_master.qsigc >= qso_count) + zabort("too many signals to be caught") ; - sigmaster.caught = 1; -} + ++qsig_master.qsigc ; -/* check if signals have been caught and run appropriate handlers + qsig_master.map[signo] = qsig_master.qsigc ; + qsig_master.event[qsig_master.qsigc] = event ; + + } ; +} ; + +/*============================================================================== + * The event level handling of qsig_signals, delivered via qsig_master. + */ + +/*------------------------------------------------------------------------------ + * check if signals have been caught and run respective event functions * * Returns: 0 => nothing to do * -1 => failed * > 0 => done this many signals */ -int +extern int quagga_sigevent_process (void) { - struct quagga_signal_t *sig; int i; int done ; #ifdef SIGEVENT_BLOCK_SIGNALS @@ -100,21 +465,18 @@ quagga_sigevent_process (void) #endif /* SIGEVENT_BLOCK_SIGNALS */ done = 0 ; - if (sigmaster.caught > 0) + if (qsig_master.caught[qso_null] != 0) { - sigmaster.caught = 0; - /* must not read or set sigmaster.caught after here, + qsig_master.caught[qso_null] = 0; + /* must not read or set sigmaster.caught[0] after here, * race condition with per-sig caught flags if one does */ - - for (i = 0; i < sigmaster.sigc; i++) + for (i = 1 ; i <= qsig_master.qsigc ; i++) { - sig = &(sigmaster.signals[i]); - - if (sig->caught > 0) + if (qsig_master.caught[i] != 0) { - sig->caught = 0; - sig->handler (); + qsig_master.caught[i] = 0; + (qsig_master.event[i])() ; ++done ; } } @@ -128,6 +490,9 @@ quagga_sigevent_process (void) return done ; } +/*------------------------------------------------------------------------------ + * Optional timer thread to poll for signals + */ #ifdef SIGEVENT_SCHEDULE_THREAD /* timer thread to check signals. Shouldnt be needed */ int @@ -144,246 +509,395 @@ quagga_signal_timer (struct thread *t) } #endif /* SIGEVENT_SCHEDULE_THREAD */ -/* Initialization of signal handles. */ -/* Signal wrapper. */ -static int -signal_set (int signo) +/*============================================================================== + * The signal handlers. + */ +static void * program_counter(void *context) ; + +/*------------------------------------------------------------------------------ + * Terminate + Core + */ +static void __attribute__ ((noreturn)) +core_handler(int signo, siginfo_t *info, void *context) { - int ret; - struct sigaction sig; - struct sigaction osig; - - sig.sa_handler = &quagga_signal_handler; - sigfillset (&sig.sa_mask); - sig.sa_flags = 0; - if (signo == SIGALRM) { -#ifdef SA_INTERRUPT - sig.sa_flags |= SA_INTERRUPT; /* SunOS */ -#endif - } else { -#ifdef SA_RESTART - sig.sa_flags |= SA_RESTART; -#endif /* SA_RESTART */ - } - - ret = sigaction (signo, &sig, &osig); - if (ret < 0) - return ret; - else - return 0; + zlog_signal(signo, "aborting...", info, program_counter(context)) ; + zabort_abort(); } -#ifdef SA_SIGINFO +/*------------------------------------------------------------------------------ + * Terminate + */ +static void __attribute__ ((noreturn)) +exit_handler(int signo, siginfo_t* info, void* context) +{ + zlog_signal(signo, "exiting...", info, program_counter(context)); + _exit(128+signo); +} + +/*------------------------------------------------------------------------------ + * Generic signal handler -- captures signals in the sig_master caught vector. + * + * quagga_sigevent_process() deals with the caught signals. + */ +static void +quagga_signal_handler(int signo, siginfo_t* info, void* context) +{ + qs_ord_t qso ; + + if (sigismember(qsig_signals, signo) <= 0) + { + zlog_signal(signo, "quagga_signal_handler: unknown or invalid signal", + info, program_counter(context)) ; + zabort_abort(); + } ; + + qso = qsig_master.map[signo] ; + + /* Set individual caught flag before composite. + * + * This works if quagga_signal_handler() and quagga_sigevent_process() + * were to run at the same time -- unlikely though that may be. + * + * If quagga_sigevent_process() sees individual flag before the composite, + * that's fine -- worst that happens is will run through a further time + * and not find anything. + * + * If flags were set in the opposite order, could clear the composite + * and miss the individual, so lose the signal till the next time the + * composite is set. + */ + qsig_master.caught[qso] = 1 ; + qsig_master.caught[qso_null] = 1 ; +} ; + +/*------------------------------------------------------------------------------ + * Generic signal handler -- where signal is to be caught, and immediately + * dropped. + */ +static void +quagga_interrupt_handler(int signo, siginfo_t* info, void* context) +{ + if (sigismember(qsig_interrupts, signo) <= 0) + { + zlog_signal(signo, "quagga_interrupt_handler: unknown or invalid signal", + info, program_counter(context)) ; + zabort_abort(); + } ; +} ; -/* XXX This function should be enhanced to support more platforms - (it currently works only on Linux/x86). */ +/*------------------------------------------------------------------------------ + * Extract program counter from context. + * + * XXX This function should be enhanced to support more platforms + * (it currently works only on Linux/x86). + */ static void * program_counter(void *context) { #ifdef HAVE_UCONTEXT_H -#ifdef GNU_LINUX -#ifdef REG_EIP +# ifdef GNU_LINUX +# ifdef REG_EIP if (context) return (void *)(((ucontext_t *)context)->uc_mcontext.gregs[REG_EIP]); -#endif /* REG_EIP */ -#endif /* GNU_LINUX */ +# endif /* REG_EIP */ +# ifdef REG_RIP + if (context) + return (void *)(((ucontext_t *)context)->uc_mcontext.gregs[REG_RIP]); +# endif /* REG_RIP */ +# endif /* GNU_LINUX */ #endif /* HAVE_UCONTEXT_H */ return NULL; -} +} ; -#endif /* SA_SIGINFO */ +/*============================================================================== + * Signal clearing for abort() and fork()/vfork(). + */ -static void __attribute__ ((noreturn)) -exit_handler(int signo -#ifdef SA_SIGINFO - , siginfo_t *siginfo, void *context -#endif - ) +/*------------------------------------------------------------------------------ + * Set default sigaction for given signo + */ +static int +sigaction_set_default(int signo) { - zlog_signal(signo, "exiting..." -#ifdef SA_SIGINFO - , siginfo, program_counter(context) -#endif - ); - _exit(128+signo); -} + struct sigaction act[1] ; -static void __attribute__ ((noreturn)) -core_handler(int signo -#ifdef SA_SIGINFO - , siginfo_t *siginfo, void *context -#endif - ) + memset(act, 0, sizeof(act)) ; /* inter alia, clear sa_flags */ + act->sa_handler = SIG_DFL ; /* return to default state */ + sigemptyset(&act->sa_mask) ; /* no extra masking */ + + return sigaction(signo, act, NULL) ; +} ; + +/*------------------------------------------------------------------------------ + * When finally aborting, need to turn off the handling of SIGABRT, and need + * to make sure that the signal is not blocked. + */ +extern void +quagga_sigabrt_no_trap(void) { - zlog_signal(signo, "aborting..." -#ifdef SA_SIGINFO - , siginfo, program_counter(context) -#endif - ); - zabort_abort(); -} + sigset_t set[1] ; + + sigaction_set_default(SIGABRT) ; + + sigemptyset(set) ; + sigaddset(set, SIGABRT) ; + qpt_thread_sigmask(SIG_UNBLOCK, set, NULL) ; + /* sigprocmask() if !qpthreads_enabled */ +} ; -/* For the signals known to Quagga, and which are in their default state, - * set a Quagga default handler. +/*------------------------------------------------------------------------------ + * Having forked, make sure that all signals are in default state and that + * no signals are blocked. + * + * Expects not to fail. */ -static void -trap_default_signals(void) +extern void +quagga_signal_reset(void) { - static const int core_signals[] = { - SIGQUIT, - SIGILL, - SIGABRT, -#ifdef SIGEMT - SIGEMT, -#endif -#ifdef SIGIOT - SIGIOT, -#endif - SIGFPE, - SIGBUS, - SIGSEGV, -#ifdef SIGSYS - SIGSYS, -#endif -#ifdef SIGXCPU - SIGXCPU, -#endif -#ifdef SIGXFSZ - SIGXFSZ, -#endif - }; - - static const int exit_signals[] = { - SIGHUP, - SIGINT, - SIGALRM, - SIGTERM, - SIGUSR1, - SIGUSR2, -#ifdef SIGPOLL - SIGPOLL, -#endif -#ifdef SIGVTALRM - SIGVTALRM, -#endif -#ifdef SIGSTKFLT - SIGSTKFLT, -#endif - }; - - static const int ignore_signals[] = { - SIGPIPE, - }; - - static const struct { - const int *sigs; - u_int nsigs; - void (*handler)(int signo -#ifdef SA_SIGINFO - , siginfo_t *info, void *context -#endif - ); - } sigmap[] = { - { core_signals, sizeof(core_signals)/sizeof(core_signals[0]), core_handler}, - { exit_signals, sizeof(exit_signals)/sizeof(exit_signals[0]), exit_handler}, - { ignore_signals, sizeof(ignore_signals)/sizeof(ignore_signals[0]), NULL}, - }; - u_int i; - - for (i = 0; i < sizeof(sigmap)/sizeof(sigmap[0]); i++) + sigset_t set[1] ; + int signo ; + + /* Before changing the handling of any signals, mask everything and + * clear out any pending signals. + */ + sigfillset(set) ; + sigprocmask(SIG_SETMASK, set, NULL) ; + + while (1) { - u_int j; + sigpending(set) ; + if (sighasmember(set) == 0) + break ; + sigwait(set, &signo) ; + } ; + + /* Set all signals to default handler. */ + for (signo = sig_min ; signo <= sig_max ; ++signo) + { + if ((signo == SIGKILL) || (signo == SIGSTOP)) + continue ; - for (j = 0; j < sigmap[i].nsigs; j++) - { - struct sigaction oact; - if (sigaction(sigmap[i].sigs[j], NULL, &oact) < 0) - zlog_warn("Unable to get signal handler for signal %d: %s", - sigmap[i].sigs[j], errtoa(errno, 0).str); - else { -#ifdef SA_SIGINFO - if (oact.sa_flags & SA_SIGINFO) - continue ; /* Don't set again */ + sigaction_set_default(signo) ; + } ; + + /* Unmask everything */ + sigemptyset(set) ; + sigprocmask(SIG_SETMASK, set, NULL) ; +} ; + +/*============================================================================== + * Functions to install signal handlers. + */ + +/*------------------------------------------------------------------------------ + * Set given handler for given set of signals. NULL handler => SIG_IGN. + * + * Returns: < 0 => failed -- value is - failing signo ! + */ +static int +signal_set_set(sigset_t* set, sig_handler* handler) +{ + int signo ; + + signo = 0 ; + for (signo = sig_min ; signo <= sig_max ; ++signo) + { + int s ; + s = sigismember(set, signo) ; + if (s < 0) + break ; + if (s > 0) + if (signal_set(signo, handler) < 0) + return -signo ; + } ; + + return 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Set given handler for given signal. NULL handler => SIG_IGN. + * + * Returns: < 0 => failed + */ +#ifndef SA_INTERRUPT +# define SA_INTERRUPT 0 #endif - if (oact.sa_handler != SIG_DFL) - continue ; /* Don't set again */ - } - if ( (sigaction(sigmap[i].sigs[j], NULL, &oact) == 0) && - (oact.sa_handler == SIG_DFL) ) - { - struct sigaction act; - sigfillset (&act.sa_mask); - if (sigmap[i].handler == NULL) - { - act.sa_handler = SIG_IGN; - act.sa_flags = 0; - } - else - { -#ifdef SA_SIGINFO - /* Request extra arguments to signal handler. */ - act.sa_sigaction = sigmap[i].handler; - act.sa_flags = SA_SIGINFO; -#else - act.sa_handler = sigmap[i].handler; - act.sa_flags = 0; +#ifndef SA_RESTART +# define SA_RESTART 0 #endif - } - if (sigaction(sigmap[i].sigs[j], &act, NULL) < 0) - zlog_warn("Unable to set signal handler for signal %d: %s", - sigmap[i].sigs[j], errtoa(errno, 0).str); - } - } +static int +signal_set(int signo, sig_handler* handler) +{ + struct sigaction act[1] ; + + if (handler == NULL) + { + act->sa_handler = SIG_IGN ; + act->sa_flags = 0 ; } -} + else + { + act->sa_sigaction = handler ; + act->sa_flags = SA_SIGINFO ; + } ; -void -signal_init (struct thread_master *m, int sigc, - struct quagga_signal_t signals[]) + sigfillset (&act->sa_mask) ; /* mask everything */ + + if (signo == SIGALRM) + act->sa_flags |= SA_INTERRUPT ; /* want SIGALRM to interrupt */ + else + act->sa_flags |= SA_RESTART ; /* all others want restart */ + + act->sa_flags |= SA_NOCLDSTOP ; + + return sigaction (signo, act, NULL) ; +} ; + +/*============================================================================== + * Additional signal set support. + */ + +/*------------------------------------------------------------------------------ + * Make a signal set. + * + * Takes variable list of signal number arguments: + * + * * ignores zeros + * + * * stops on first value < 0 + */ +extern void +sigmakeset(sigset_t* set, ...) { + va_list va ; + int signo ; - int i = 0; - struct quagga_signal_t *sig; + va_start(va, set) ; - /* First establish some default handlers that can be overridden by - the application. */ - trap_default_signals(); + sigemptyset(set) ; + while ((signo = va_arg(va, int)) >= 0) + { + if (signo != 0) + if (sigaddset(set, signo) < 0) + zabort("invalid signal number") ; + } ; + + va_end(va) ; +} ; - while (i < sigc) +/*------------------------------------------------------------------------------ + * Copy a signal set. + */ +extern void +sigcopyset(sigset_t* dst, const sigset_t* src) +{ + *dst = *src ; +} ; + +/*------------------------------------------------------------------------------ + * Add signal set 'b' into set 'a'. + */ +extern void +sigaddsets(sigset_t* a, const sigset_t* b) +{ + int signo ; + + for (signo = sig_min ; signo < SIG_MAX ; ++signo) { - sig = &signals[i]; - if ( signal_set (sig->signal) < 0 ) - exit (-1); - i++; - } + int s ; + s = sigismember(b, signo) ; + if (s < 0) + break ; + if (s > 0) + sigaddset(a, signo) ; + } ; +} ; - sigmaster.sigc = sigc; - sigmaster.signals = signals; +/*------------------------------------------------------------------------------ + * Subtract signal set 'b' from set 'a'. + */ +extern void +sigsubsets(sigset_t* a, const sigset_t* b) +{ + int signo ; -#ifdef SIGEVENT_SCHEDULE_THREAD - sigmaster.t = - thread_add_timer (m, quagga_signal_timer, &sigmaster, - QUAGGA_SIGNAL_TIMER_INTERVAL); -#endif /* SIGEVENT_SCHEDULE_THREAD */ -} + for (signo = sig_min ; signo < SIG_MAX ; ++signo) + { + int s ; + s = sigismember(b, signo) ; + if (s < 0) + break ; + if (s > 0) + sigdelset(a, signo) ; + } ; +} ; -/* turn off trap for SIGABRT ! */ -extern void quagga_sigabrt_no_trap(void) +/*------------------------------------------------------------------------------ + * Make set 'a' be the inverse of set 'b' + */ +extern void +siginvset(sigset_t* a, const sigset_t* b) { - struct sigaction new_act ; - sigset_t set ; + int signo ; - sigfillset(&set) ; + sigfillset(a) ; - new_act.sa_handler = SIG_DFL ; - new_act.sa_mask = set ; - new_act.sa_flags = 0 ; - sigaction(SIGABRT, &new_act, NULL) ; + for (signo = sig_min ; signo < SIG_MAX ; ++signo) + { + int s ; + s = sigismember(b, signo) ; + if (s < 0) + break ; + if (s > 0) + sigdelset(a, signo) ; + } ; +} ; - sigemptyset(&set) ; - sigaddset(&set, SIGABRT) ; - sigprocmask(SIG_UNBLOCK, &set, NULL) ; +/*------------------------------------------------------------------------------ + * See if there is any intersection between two sets. + * + * Returns: first signo of intersection -- may be more ! + * 0 <=> none + */ +extern int +sigincommon(const sigset_t* a, const sigset_t* b) +{ + int signo ; + for (signo = sig_min ; signo < SIG_MAX ; ++signo) + { + int s ; + s = sigismember(a, signo) ; + if (s < 0) + return 0 ; + if ((s > 0) && (sigismember(b, signo) > 0)) + return signo ; + } ; + + return 0 ; } ; +/*------------------------------------------------------------------------------ + * See if there is anything in the given set. + * + * Returns: first signo found -- may be more ! + * 0 <=> none + */ +extern int +sighasmember(const sigset_t* a) +{ + int signo ; + + for (signo = sig_min ; signo < SIG_MAX ; ++signo) + { + int s ; + s = sigismember(a, signo) ; + if (s < 0) + return 0 ; + if (s > 0) + return signo ; + } ; + + return 0 ; +} ; diff --git a/lib/sigevent.h b/lib/sigevent.h index 57486bc2..bc4ef945 100644 --- a/lib/sigevent.h +++ b/lib/sigevent.h @@ -25,32 +25,34 @@ #define _QUAGGA_SIGNAL_H #include <thread.h> +#include <signal.h> #define QUAGGA_SIGNAL_TIMER_INTERVAL 2L #define Q_SIGC(sig) (sizeof(sig)/sizeof(sig[0])) +typedef void qsig_event(void) ; + struct quagga_signal_t { - int signal; /* signal number */ - void (*handler) (void); /* handler to call */ - - volatile sig_atomic_t caught; /* private member */ -}; - -/* initialise sigevent system - * takes: - * - pointer to valid struct thread_master - * - number of elements in passed in signals array - * - array of quagga_signal_t's describing signals to handle - * and handlers to use for each signal - */ + int signal ; /* signal number */ + qsig_event* handler ; /* event function */ +} ; + extern void signal_init (struct thread_master *m, int sigc, - struct quagga_signal_t *signals); + struct quagga_signal_t *signals); -/* check whether there are signals to handle, process any found */ extern int quagga_sigevent_process (void); -/* turn off trap for SIGABRT ! */ +extern const sigset_t* signal_get_hard_set(void) ; extern void quagga_sigabrt_no_trap(void) ; +extern void quagga_signal_reset(void) ; + +extern void sigmakeset(sigset_t* set, ...) ; +extern void sigcopyset(sigset_t* dst, const sigset_t* src) ; +extern void sigaddsets(sigset_t* a, const sigset_t* b) ; +extern void sigsubsets(sigset_t* a, const sigset_t* b) ; +extern void siginvset(sigset_t* a, const sigset_t* b) ; +extern int sigincommon(const sigset_t* a, const sigset_t* b) ; +extern int sighasmember(const sigset_t* a) ; #endif /* _QUAGGA_SIGNAL_H */ diff --git a/lib/thread.c b/lib/thread.c index 7a9a3a60..cf2ec425 100644 --- a/lib/thread.c +++ b/lib/thread.c @@ -44,8 +44,8 @@ static unsigned short timers_inited; /* cpu stats needs to be qpthread safe. */ static qpt_mutex_t thread_mutex; -#define LOCK qpt_mutex_lock(&thread_mutex); -#define UNLOCK qpt_mutex_unlock(&thread_mutex); +#define LOCK qpt_mutex_lock(thread_mutex); +#define UNLOCK qpt_mutex_unlock(thread_mutex); static struct hash *cpu_record = NULL; /* Pointer to qtimer pile to be used, if any */ @@ -468,7 +468,7 @@ thread_master_create () { #ifdef USE_MQUEUE sigfillset (&newmask); - sigdelset (&newmask, SIGMQUEUE); + sigdelset (&newmask, SIG_INTERRUPT); #endif if (cpu_record == NULL) @@ -1399,14 +1399,14 @@ funcname_thread_execute (struct thread_master *m, void thread_init_r (void) { - qpt_mutex_init(&thread_mutex, qpt_mutex_quagga); + qpt_mutex_init(thread_mutex, qpt_mutex_quagga); } /* Finished with module */ void thread_finish (void) { - qpt_mutex_destroy(&thread_mutex, 0); + qpt_mutex_destroy(thread_mutex, 0); } #undef USE_MQUEUE diff --git a/lib/vio_fifo.c b/lib/vio_fifo.c index e6365861..46a087f7 100644 --- a/lib/vio_fifo.c +++ b/lib/vio_fifo.c @@ -957,8 +957,11 @@ vio_fifo_vprintf(vio_fifo vff, const char *format, va_list args) /*------------------------------------------------------------------------------ * 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. + * Will read up to the end of the current lump, then will read as may whole + * lumps as are requested -- request of 0 reads up to the end of the current + * lump (at least 1 byte). Will stop if would block. + * + * Except where blocking intervenes, this reads in units of the lump size. * * Returns: 0..n -- number of bytes read * -1 => failed -- see errno @@ -969,7 +972,7 @@ vio_fifo_vprintf(vio_fifo vff, const char *format, va_list args) * something, error or EOF. */ extern int -vio_fifo_read_nb(vio_fifo vff, int fd, size_t request) +vio_fifo_read_nb(vio_fifo vff, int fd, uint request) { size_t total ; @@ -980,7 +983,12 @@ vio_fifo_read_nb(vio_fifo vff, int fd, size_t request) int got ; if (vff->put_ptr >= vff->put_end) - vio_fifo_lump_new(vff, 0) ; /* traps put_ptr > put_end */ + { + vio_fifo_lump_new(vff, 0) ; /* traps put_ptr > put_end */ + + if (request > 0) + --request ; + } ; got = read_nb(fd, vff->put_ptr, vff->put_end - vff->put_ptr) ; @@ -995,7 +1003,7 @@ vio_fifo_read_nb(vio_fifo vff, int fd, size_t request) vff->put_ptr += got ; total += got ; - } while (total < request) ; + } while (request > 0) ; return total ; } ; @@ -1024,9 +1032,6 @@ vio_fifo_copy(vio_fifo dst, vio_fifo src) 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 ; @@ -1346,7 +1351,7 @@ vio_fifo_step_get(vio_fifo vff, size_t* p_have, size_t step) * Write contents of FIFO -- assuming non-blocking file * * Will write all of FIFO up to end mark or put_ptr, or upto but excluding - * the last lump. + * the end_lump. * * Returns: > 0 => blocked * 0 => all gone (up to last lump if !all) @@ -1492,9 +1497,10 @@ vio_fifo_clear_hold_mark(vio_fifo vff) } ; /*------------------------------------------------------------------------------ - * If there is an hold_mark, reset get_ptr *back* to it. + * If there is an hold_mark, reset get_ptr *back* to it, and leave the mark + * set or clear. * - * Leave hold mark set or clear. + * If there is no hold mark, set one at the current position, if required. */ extern void vio_fifo_back_to_hold_mark(vio_fifo vff, bool mark) @@ -1516,7 +1522,67 @@ vio_fifo_back_to_hold_mark(vio_fifo vff, bool mark) VIO_FIFO_DEBUG_VERIFY(vff) ; } else if (mark) - vio_fifo_set_end_mark(vff) ; + vio_fifo_set_hold_mark(vff) ; +} ; + +/*------------------------------------------------------------------------------ + * Set the get_ptr to the hold_mark plus the given offset. + * + * If the offset is zero, and there was no hold mark, set one at the current + * get_ptr. + * + * If the offset is not zero, it is a mistake to do this if there is no + * hold_mark, or if the offset would take the get_ptr beyond the end_mark + * (if any) or the put_ptr. + */ +extern void +vio_fifo_set_get_wrt_hold(vio_fifo vff, size_t hold_offset) +{ + if (hold_offset == 0) + { + /* Offset of zero can be set under all conditions */ + vio_fifo_back_to_hold_mark(vff, true) ; + } + else + { + vio_fifo_lump lump ; + char* ptr ; + + /* There must be a hold_mark and must have something held */ + assert(vff->hold_mark && vff->set) ; + + lump = ddl_head(vff->base) ; + ptr = vff->hold_ptr ; + + while (1) + { + size_t have ; + + if (lump == vff->end_lump) + have = (vff->end_mark ? vff->end_end : vff->put_ptr) - ptr ; + else + have = lump->end - ptr ; + + if (have <= hold_offset) + break ; + + hold_offset -= have ; + + assert(lump != vff->end_lump) ; + + lump = ddl_next(lump, list) ; + ptr = lump->data ; + } ; + + /* Note that may be about to set the get_ptr to the end of the + * current lump, which will be correct if that is the end of the + * fifo, but in any case is dealt with by vio_fifo_sync_get(). + */ + vio_fifo_set_get_ptr(vff, ptr + hold_offset, lump) ; + vio_fifo_sync_get(vff) ; + } ; + + VIO_FIFO_DEBUG_VERIFY(vff) ; } ; /*============================================================================== diff --git a/lib/vio_fifo.h b/lib/vio_fifo.h index 1b7b05d3..97575f51 100644 --- a/lib/vio_fifo.h +++ b/lib/vio_fifo.h @@ -32,16 +32,33 @@ * VTY I/O FIFO -- buffering of arbitrary amounts of I/O. */ -#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 +/*------------------------------------------------------------------------------ + * Sort out VIO_FIFO_DEBUG. + * + * Set to 1 if defined, but blank. + * Set to QDEBUG if not defined. + * + * Force to 0 if VIO_FIFO_NO_DEBUG is defined and not zero. + * + * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to: + * + * * can set VIO_FIFO_DEBUG == 0 to turn off debug + * * or set VIO_FIFO_DEBUG != 0 to turn on debug + * * or set VIO_FIFO_NO_DEBUG != 0 to force debug off + */ + +#ifdef VIO_FIFO_DEBUG /* If defined, make it 1 or 0 */ +# if IS_BLANK_OPTION(VIO_FIFO_DEBUG) +# undef VIO_FIFO_DEBUG +# define VIO_FIFO_DEBUG 1 # endif -#else -# ifdef QDEBUG -# define VIO_FIFO_DEBUG 1 /* Follow QDEBUG */ -#else +#else /* If not defined, follow QDEBUG */ +# define VIO_FIFO_DEBUG QDEBUG +#endif + +#ifdef VIO_FIFO_NO_DEBUG /* Override, if defined */ +# if IS_NOT_ZERO_OPTION(VIO_FIFO_NO_DEBUG) +# undef VIO_FIFO_DEBUG # define VIO_FIFO_DEBUG 0 # endif #endif @@ -116,6 +133,7 @@ extern vio_fifo vio_fifo_reset(vio_fifo vff, free_keep_b free_structure) ; extern void vio_fifo_clear(vio_fifo vff, bool clear_marks) ; Inline bool vio_fifo_empty(vio_fifo vff) ; +Inline bool vio_fifo_tail_empty(vio_fifo vff) ; extern size_t vio_fifo_room(vio_fifo vff) ; extern void vio_fifo_put_bytes(vio_fifo vff, const char* src, size_t n) ; @@ -124,7 +142,7 @@ Inline void vio_fifo_put_byte(vio_fifo vff, char b) ; extern int vio_fifo_printf(vio_fifo vff, const char* format, ...) PRINTF_ATTRIBUTE(2, 3) ; 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 int vio_fifo_read_nb(vio_fifo vff, int fd, uint 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) ; @@ -147,6 +165,7 @@ 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) ; +extern void vio_fifo_set_get_wrt_hold(vio_fifo vff, size_t hold_offset) ; /*============================================================================== * Debug -- verification function @@ -154,11 +173,7 @@ extern void vio_fifo_back_to_hold_mark(vio_fifo vff, bool keep) ; Private void vio_fifo_verify(vio_fifo vff) ; -#if VIO_FIFO_DEBUG -# define VIO_FIFO_DEBUG_VERIFY(vff) vio_fifo_verify(vff) -#else -# define VIO_FIFO_DEBUG_VERIFY(vff) -#endif +#define VIO_FIFO_DEBUG_VERIFY(vff) if (vio_fifo_debug) vio_fifo_verify(vff) /*============================================================================== * Inline Functions @@ -197,7 +212,7 @@ vio_fifo_empty(vio_fifo vff) * Returns true <=> FIFO is empty beyond end_end (if any). */ Inline bool -vio_fifo_empty_tail(vio_fifo vff) +vio_fifo_tail_empty(vio_fifo vff) { /* if vff is NULL, treat as empty ! * if !set, then all pointers should be NULL (so end_end == put_ptr) diff --git a/lib/vio_lines.c b/lib/vio_lines.c index a9268fd5..031f4539 100644 --- a/lib/vio_lines.c +++ b/lib/vio_lines.c @@ -59,7 +59,7 @@ * * WHAT IT DOES DO: * - * 1) maps bare '\n' to '\r''\n'. + * 1) maps bare '\n' to '\r''\n' -- if required. * * Swallows '\r' immediately before '\n' if present. * @@ -80,66 +80,84 @@ * A width of <= 0 => very large width indeed. * A height of <= 0 => indefinite height * - * Pause is unset. vio_lc_append will collect an indefinite number of lines. - * - * Column and line position set to zero. - * - * Returns: address of vio_line_control + * Returns: address of vio_line_control */ extern vio_line_control -vio_lc_init_new(vio_line_control lc, int width, int height) +vio_lc_init_new(vio_line_control lc, int width, int height, const char* newline) { if (lc == NULL) lc = XCALLOC(MTYPE_VIO_LC, sizeof(struct vio_line_control)) ; else - memset(lc, 0, sizeof(struct vio_line_control)) ; + memset(lc, 0, sizeof(vio_line_control_t)) ; /* Zeroising has set: * - * pause = 0 -- no limit on the number of lines to append - * paused = 0 -- not paused + * width = X -- set below. + * height = X -- set below + * + * counter = X -- set below * - * col = 0 -- at column 0 - * lines = 0 -- no lines collected, yet + * incomplete = false -- no incomplete line in hand + * fragments = NULL -- set below + * here = NULL -- set below * - * iov = all 0 -- empty - * writing = 0 -- not writing + * qiov = NULL -- set below + * + * newline = zeros -- set below */ lc->width = width >= 0 ? width : 0 ; lc->height = height >= 0 ? height : 0 ; + vio_lc_counter_reset(lc) ; + + lc->fragments = qiovec_init_new(NULL) ; + lc->here = qs_new(120) ; + + lc->qiov = qiovec_init_new(NULL) ; + + lc->newline->base = newline ; + lc->newline->len = strlen(newline) ; return lc ; } ; /*------------------------------------------------------------------------------ - * Reset vio_line_control (if any) -- release body and (if required) the + * Reset vio_line_control (if any) -- release body and (if required) free the * structure. * * Returns: address of vio_line_control (if any) -- NULL if structure released + * + * NB: if the line control is not freed, it MUST be reinitialised by + * vio_lc_init_new() before it is used again ! + * + * NB: it is the callers responsibility to release anything which was buffered + * for the line control to look after. + * + * It is also the callers responsibility to release the newline string, + * if required. */ extern vio_line_control vio_lc_reset(vio_line_control lc, free_keep_b free_structure) { if (lc != NULL) { + lc->fragments = qiovec_free(lc->fragments) ; + lc->here = qs_free(lc->here) ; + + lc->qiov = qiovec_free(lc->qiov) ; + if (free_structure) XFREE(MTYPE_VIO_LC, lc) ; /* sets lc = NULL */ else - vio_lc_init_new(lc, lc->width, lc->height) ; - /* re-initialise */ + memset(lc, 0, sizeof(vio_line_control_t)) ; } ; return lc ; } ; /*------------------------------------------------------------------------------ - * Clear given vio_line_control. - * - * Sets: pause = 0 - * paused = 0 - * col = 0 - * writing = 0 + * Clear given vio_line_control -- discard all buffered lines and reset the + * counter. * * NB: it is the callers responsibility to release anything buffered because * it was earlier appended. @@ -150,12 +168,14 @@ vio_lc_clear(vio_line_control lc) if (lc == NULL) return ; - qiovec_clear(&lc->qiov) ; + vio_lc_counter_reset(lc) ; + + lc->incomplete = false ; + + qiovec_clear(lc->fragments) ; + qs_clear(lc->here) ; - lc->pause = 0 ; - lc->paused = 0 ; - lc->col = 0 ; - lc->writing = 0 ; + qiovec_clear(lc->qiov) ; } ; /*------------------------------------------------------------------------------ @@ -164,196 +184,404 @@ vio_lc_clear(vio_line_control lc) * A width of <= 0 => very large width indeed. * A height of <= 0 => indefinite height * - * Pause is adjusted if it is not zero, and may become zero and set paused. + * This may happen at almost any time when a Telnet terminal is resized. + * From line control perspective, can happen between calls of vio_lc_append(), + * vio_lc_flush() and vio_lc_write_nb(). + * + * If happens while the line control has stuff buffered, then it is too late to + * change anything, unless was an incomplete line. + * + * Tries to sort out the counter... but is is not possible to do this perfectly. */ extern void vio_lc_set_window(vio_line_control lc, int width, int height) { - unsigned old_height ; + unsigned depth ; - old_height = lc->height ; + depth = lc->height ; lc->width = width >= 0 ? width : 0 ; lc->height = height >= 0 ? height : 0 ; - if (lc->pause != 0) - { - if (lc->height > old_height) - lc->pause += lc->height - old_height ; - else - { - if (lc->pause >= (old_height - lc->height)) - lc->pause = 0 ; - else - lc->pause -= old_height - lc->height ; - } ; - lc->paused = (lc->pause == 0) ; - } ; -} ; + /* If counter already exhausted, or just set indefinite height, need do + * nothing more. + */ + if ((lc->counter <= 0) || (lc->height == 0)) + return ; -/*============================================================================== - * Appending and writing - */ + /* Counter not already exhausted, and is setting a definite height. + * + * If no height was set before, start from scratch, now. + */ + if (depth == 0) + return vio_lc_counter_reset(lc) ; -/*------------------------------------------------------------------------------ - * Sets pause to the current height and clear paused. - */ -extern void -vio_lc_set_pause(vio_line_control lc) -{ - lc->pause = lc->height ; - lc->paused = 0 ; + /* Had a definite height and setting a new one. + * + * Now calculate the depth, which is how far down the old height have + * got to. (The counter should be less than the old height, but if it + * isn't we end up with a huge depth here...) + */ + depth -= lc->counter ; + + /* If the new height is > depth set the counter to be the new height - depth. + * + * Otherwise, set the counter to 0, so is immediately exhausted. + * + * Cannot solve the problem of what to do if the width has changed ! + */ + lc->counter = (lc->height > depth) ? lc->height - depth : 0 ; } ; -/*------------------------------------------------------------------------------ - * Put newline (if required) and account for it +/*============================================================================== + * Appending and writing */ -static inline void -vio_lc_newline(vio_line_control lc, bool required) -{ - if (required) - qiovec_push(&lc->qiov, "\r\n", 2) ; - lc->col = 0 ; - lc->line += 1 ; - if (lc->pause != 0) - { - lc->pause -= 1 ; - lc->paused = (lc->pause == 0) ; - } ; -} ; +static uint vio_lc_trim(vio_line_control lc, qiov_item item, const char* e) ; +static void vio_lc_append_line(vio_line_control lc, qiov_item item) ; /*------------------------------------------------------------------------------ * Append a lump of output to the given line control's buffers. * - * Breaks the output into lines which are no longer than the lc->width. + * Breaks the output into lines which are no longer than the lc->width. If + * that is zero (unset) we use a very large width indeed. + * + * Breaks the output into a number of screen lines, limited by the line counter. + * + * Effect of line counter: + * + * * if definite height, will output only as many screen lines as it can, + * including none at all. + * + * * if indefinite height, when the line counter is reset it is set to some + * limit on the number of screen lines to buffer in the line control. + * + * Note that this is not exact, in particular will *not* stop in the + * middle of a complete line, just because the limit of screen lines has + * been exceeded. + * + * * in any case, the counter may reach exactly 0 if the number of screen + * lines required is exactly the number of screen lines allowed. + * + * Trims trailing whitespace from each line (but not from screen lines), before + * breaking up into screen lines. + * + * NB: this means that does not output anything until gets a '\n'. + * + * See vio_lc_flush(). * * Maps '\n' to '\r''\n'. * - * Discards '\r' if found before '\n', and possibly at other times. + * Discards '\r' amongst trailing whitespace, but not otherwise. * * If lc->width == 0, use a very large width indeed. * - * If lc->pause == 0, append an indefinite number of lines + * Returns: number of bytes able to append to the line control. * - * NB: the buffer presented MUST be retained until the contents of the - * line control's buffers have been written. + * NB: the buffer presented MUST be retained until the contents of the line + * control's buffers have been written -- see vio_lc_write_nb(). * - * Returns: number of bytes able to append + * NB: the line control may buffer stuff "in hand", before it reaches the + * output iovec, either because: + * + * a) the current line is incomplete -- nothing will be output until + * a '\n' is appended, or the line control is flushed. + * + * b) the line counter was exhausted while outputting a line -- this will + * be output on the next call of vio_lc_append(), or when the line + * control is flushed. + * + * So even when the buffers that feed the line control are empty, + * a call of vio_lc_append() may push out some lines buffered in + * the line control. + * + * NB: all output from the line control ends with a newline. + * + * Incomplete lines are held in the line control until they are completed, + * or until the line control is flushed. When the line control is flushed, + * unless the incomplete line is all whitespace, a newline is appended. */ extern size_t vio_lc_append(vio_line_control lc, const void* buf, size_t len) { - const char* p ; - const char* end ; - - unsigned width ; - unsigned pause ; - - /* Prepare local width and pause */ - if (lc->width > 0) - width = lc->width ; - else - width = UINT_MAX ; - - if (lc->pause > 0) - pause = 0 ; - else - pause = 1 ; + qiov_item_t item ; + const char* bp ; + const char* be ; - lc->paused = 0 ; + /* If we have a line which was being output, but was interrupted part + * way through by line counter expiry... try to empty that out, first. + * + * This may be prevented by the line counter (partly or completely), or may + * exhaust it, which will be noticed, shortly, below. + */ + if (!qiovec_empty(lc->fragments) && !lc->incomplete) + { + qiovec_shift(lc->fragments, item) ; /* want first fragment */ + vio_lc_append_line(lc, item) ; + } ; /* Append: stop when run out of data or run out of lines */ - end = (const char*)buf + len ; - p = buf ; + be = (const char*)buf + len ; + bp = buf ; - while ((p < end) && (lc->pause != pause)) + /* If is counter exhausted, stop appending now. + * + * In vio_lc_append_line() updates the counter, and may set it -ve. + */ + while ((bp < be) && (lc->counter > 0)) { - const char* e ; - bool nl ; - int nlx ; + item->base = bp ; /* start of current fragment */ - nlx = 0 ; /* no line ending chars yet */ + /* scan for '\n' -- note that we look for a complete line, + * before worrying about the line */ + bp = memchr(bp, '\n', (be - bp)) ; - /* scan for '\n'. */ - e = memchr(p, '\n', (end - p)) ; - nl = (e != NULL) ; - if (nl) - ++nlx ; /* account for the '\n' */ - else - e = end ; /* use all there is */ + if (bp == NULL) + { + /* We do not have a '\n' -- rats */ + item->len = be - item->base ; + qiovec_push(lc->fragments, item) ; + + lc->incomplete = true ; /* have incomplete line in hand */ + + bp = be ; + break ; /* done */ + } ; - /* peel off trailing '\r'. + /* We have a '\n' -- hurrah ! * - * NB: if have not got a '\n', then this may discard a bare - * '\r' -- but bare '\r' are undefined in any case. + * Have a complete line ready to be added to the qiov -- the + * current fragment and any buffered ones. + * + * Trim off any trailing whitespace and establish the length of the + * last fragment of the current (complete) line. If the current + * fragment is all whitespace, works its way up any in hand fragments. + */ + lc->incomplete = false ; /* definitely not */ + + item->len = vio_lc_trim(lc, item, bp) ; + + ++bp ; /* step past the '\n' */ + + /* If have fragments in hand, then want to have the first fragment + * as the current fragment (currently the current fragment is the + * last fragment). */ - if ((e > p) && (*(e - 1) == '\r')) + if (!qiovec_empty(lc->fragments)) { - --e ; /* strip the '\r' */ - ++nlx ; /* but account for it */ - } + qiovec_push(lc->fragments, item) ; + qiovec_shift(lc->fragments, item) ; + } ; - /* have p..e characters and possibly nl to add to the output. + /* We are now ready to break the current line up into width + * sections, if required -- to the extend allowed by the counter. * - * Note that if enters the while, (e - p) > 0. So there is at least one - * character to add. This avoids generating a spurious line ending if - * the width has been reduced, and the next thing output is a line end. + * Have trimmed off any trailing whitespace, including back across + * any fragments. So: + * + * item = first fragment of the line */ - while ((p < e) && (lc->pause != pause)) + vio_lc_append_line(lc, item) ; + } ; + + /* Exhausted the available data or the line count */ + + return (bp - (const char*)buf) ; /* what have taken */ +} ; + +/*------------------------------------------------------------------------------ + * Flush anything which the line control has buffered. + * + * There are two possible cases: + * + * 1) had collected a complete line in vio_lc_append(), but when putting to + * the qiovec was stopped by the line counter. + * + * So have one or more screen lines buffered up, ready to go. + * + * 2) had collected an incomplete line in vio_lc_append(). + * + * That will now be flushed as if a newline had been appended, except that + * if the result would be a newline *only*, then all the whitespace is + * discarded and nothing is output. + * + * If there is nothing to flush, or an incomplete line turns out to be + * entirely whitespace, return false. + * + * Otherwise we have something to flush, and will attempt to do so. + * + * Effect of line counter: + * + * * if definite height, will output only as many screen lines as it can, + * including none at all. + * + * * if indefinite height, will output as many screen lines as it takes, + * whatever the state of the line counter. + * + * * in any case, the counter may reach exactly 0 if the number of screen + * lines required is exactly the number of screen lines allowed. + * + * Returns: true <=> have something to flush (but counter may be exhausted) + * false <=> nothing to be flushed + */ +extern bool +vio_lc_flush(vio_line_control lc) +{ + qiov_item_t item ; + + if (qiovec_count(lc->fragments) == 0) + return false ; + + /* We have something in hand. + * + * If was an incomplete line, now is the time to trim off any trailing + * whitespace -- more or less as if there had been a '\n' at this point. + */ + if (lc->incomplete) + { + lc->incomplete = false ; + + qiovec_pop(lc->fragments, item) ; + + item->len = vio_lc_trim(lc, item, item->base + item->len) ; + + if (item->len == 0) + { + assert(qiovec_empty(lc->fragments)) ; + return false ; /* it was all whitespace which has all + been discarded. */ + } ; + + qiovec_push(lc->fragments, item) ; + } ; + + /* Have something in hand, so now attempt to output it. */ + qiovec_shift(lc->fragments, item) ; + vio_lc_append_line(lc, item) ; + + return true ; +} ; + +/*------------------------------------------------------------------------------ + * Trim trailing whitespace from given fragment -- ' ', '\t' and '\r'. + * + * If required, pops fragments until finds some non-whitespace, or runs out + * of fragments. + * + * Returns: resulting length. + */ +static uint +vio_lc_trim(vio_line_control lc, qiov_item item, const char* e) +{ + const char* p ; + + while (1) + { + p = item->base ; + + while (e > p) { - const char* t ; - unsigned col ; + char ch ; + + ch = *(e - 1) ; + if ( (ch != '\r') && (ch != ' ') && (ch != '\t') ) + return e - p ; /* <<< found non-whitespace <<< exit */ + + --e ; + } ; - col = lc->col + (e - p) ; /* NB: e > p */ + qiovec_pop(lc->fragments, item) ; /* pops a NULL if empty */ - if (col > width) + if (item->len == 0) + return 0 ; + + e = item->base + item->len ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Append line to the output qiov -- breaking it up into width sections, + * inserting newlines and looking out for and updating the line counter. + * + * If definite height, may stop immediately, without outputting anything. + * If indefinite height, will output the current line, whatever the state of + * the line counter. + * + * Note that do not trim whitespace from screen lines. + * + * At this point all trailing whitespace will have been removed from the + * original line. + * + * Requires that have previously trimmed off any trailing whitespace, including + * back across any fragments. So: + * + * item = first fragment of the line -- NOT in lc->fragments. + * + * It is possible for the first fragment to be empty (empty line !). + */ +static void +vio_lc_append_line(vio_line_control lc, qiov_item item) +{ + bool done = false ; + + while ((lc->counter > 0) || (lc->height == 0)) + { + uint take ; + + /* Take fragments or parts thereof until we have run out of output + * (which sets done == true) or reach the width. + */ + take = (lc->width > 0) ? lc->width : UINT_MAX ; + while (1) + { + if (item->len <= take) { - /* can use only part of what there is */ - if (width > lc->col) - t = p + (width - lc->col) ; - /* take to edge of screen */ + /* Use entire fragment, and step to next (if any) + * + * Note that qiovec_push() ignores zero length items, so if + * this is an empty line, will push no fragments and will stop + * here. + */ + qiovec_push(lc->qiov, item) ; + + if (qiovec_empty(lc->fragments)) + { + done = true ; /* signal all done */ + break ; + } else - t = p ; - assert(t < e) ; /* if not need to deal with nl */ + { + take -= item->len ; + qiovec_shift(lc->fragments, item) ; + } } else { - /* can use all of what there is */ - if (nlx == 2) /* if have crlf, use it */ - { - e += nlx ; /* use the crlf that's there */ - nlx = 0 ; /* used it */ - } ; + /* Use leading part of fragment, and reduce same */ + qiovec_push_this(lc->qiov, item->base, take) ; + item->base += take ; + item->len -= take ; - t = e ; /* take it all */ + break ; } ; - - assert(t >= p) ; - if (t != p) - qiovec_push(&lc->qiov, p, (t - p)) ; - - /* advance. If not taken all the line, need a crlf */ - p = t ; - - if (p < e) - vio_lc_newline(lc, 1) ; } ; - /* If taken all of line, deal with any outstanding nl and nlx */ - if (p == e) - { - if (nl) - vio_lc_newline(lc, (nlx != 0)) ; + qiovec_push(lc->qiov, lc->newline) ; - p += nlx ; /* step past '\r' or '\n' */ - } ; - } ; + /* Count down & exit if all done. */ + --lc->counter ; - /* Exhausted the available data or the line count */ - assert(p <= end) ; + if (done) + return ; + } ; - return (p - (const char*)buf) ; /* what have taken */ + /* Counter exhausted, but we are not done with the current line. + * + * unshift the current item onto the (front of) the fragments qiovec. + * (We know we can do this straightforwardly, because either the qiovec is + * empty, or what was the first item has previously been shifted off.) + */ + qiovec_unshift(lc->fragments, item) ; } ; /*------------------------------------------------------------------------------ @@ -368,15 +596,52 @@ vio_lc_append(vio_line_control lc, const void* buf, size_t len) * -1 => failed -- see errno * * Sets lc->writing if write does not complete + * + * NB: when says "all done" (or failed) the caller may release all buffered + * material that has been accepted by vio_lc_append() -- and not before. */ extern int vio_lc_write_nb(int fd, vio_line_control lc) { int ret ; - ret = qiovec_write_nb(fd, &lc->qiov) ; + ret = qiovec_write_nb(fd, lc->qiov) ; + + if (ret <= 0) + { + /* About to promise that all buffered material previously accepted by + * vio_lc_append() may now be released. + * + * Unfortunately, if we have any line fragments in hand, have to buffer + * those locally, now. + * + * NB: if have a particularly long original line, or a particularly + * narrow or short screen, it is possible to pass through here + * more than once for the same original line. + * + * In this obscure case, the line will already be buffered in + * lc->here. Happily qs_clear() does not disturb the body of the + * qstring, and qs_append_str_n will append from within its own + * body ! + */ + if (!qiovec_empty(lc->fragments)) + { + qs_set_len_nn(lc->here, 0) ; /* ready to append stuff */ + do + { + qiov_item_t item ; + + qiovec_shift(lc->fragments, item) ; + + qs_append_str_n(lc->here, item->base, item->len) ; + } + while (!qiovec_empty(lc->fragments)) ; - lc->writing = (ret > 0) ; + /* One fragment in hand, collected from all previous fragments + */ + qiovec_push_this(lc->fragments, qs_char(lc->here), qs_len(lc->here)) ; + } ; + } ; return ret ; } ; diff --git a/lib/vio_lines.h b/lib/vio_lines.h index dd5545cc..6a3d13e9 100644 --- a/lib/vio_lines.h +++ b/lib/vio_lines.h @@ -24,6 +24,7 @@ #include "misc.h" #include "qiovec.h" +#include "qstring.h" /*============================================================================== * @@ -32,22 +33,24 @@ /*------------------------------------------------------------------------------ * Line control -- collecting lines of a given width for output. * - * NB: a completely zero structure is a valid, clear vio_line_control. + * NB: need to explicitly initialise line control in order to set the required + * new line, the qiovec vectors and a qstring buffer ! */ struct vio_line_control { - unsigned width ; /* console width -- 0 => HUGE */ - unsigned height ; /* console height -- 0 => indefinite */ + uint width ; /* console width -- 0 => HUGE */ + uint height ; /* console height -- 0 => indefinite */ - unsigned pause ; /* number of lines to next pause - 0 => indefinite */ - bool paused ; /* true <=> last append stopped on pause */ + int counter ; /* number of lines to next pause + <= 0 => paused. */ - unsigned col ; /* current column position */ - unsigned line ; /* line number of last complete line */ + bool incomplete ; /* fragments in hand are an incomplete line */ + qiovec fragments ; + qstring here ; /* any fragments after write */ - struct qiovec qiov ; /* iovec control */ - bool writing ; /* write started, but not completed */ + qiovec qiov ; /* output screen lines */ + + qiov_item_t newline ; /* the required sequence */ } ; typedef struct vio_line_control* vio_line_control ; @@ -55,32 +58,118 @@ typedef struct vio_line_control vio_line_control_t[1] ; enum { - VIO_LINE_CONTROL_INIT_ALL_ZEROS = true + VIO_LINE_CONTROL_INIT_ALL_ZEROS = false } ; /*============================================================================== * Functions */ extern vio_line_control vio_lc_init_new(vio_line_control lc, int width, - int height) ; + int height, + const char* newline) ; +Inline vio_line_control vio_lc_new(int width, int height, + const char* newline) ; extern vio_line_control vio_lc_reset(vio_line_control lc, free_keep_b free_structure) ; +Inline vio_line_control vio_lc_free(vio_line_control lc) ; -Inline bool vio_lc_empty(vio_line_control lc) ; extern void vio_lc_clear(vio_line_control lc) ; extern void vio_lc_set_window(vio_line_control lc, int width, int height) ; -extern void vio_lc_set_pause(vio_line_control lc) ; +Inline void vio_lc_counter_reset(vio_line_control lc) ; +Inline void vio_lc_clear_pause(vio_line_control lc) ; + +Inline bool vio_lc_counter_is_exhausted(vio_line_control lc) ; +Inline bool vio_lc_have_complete_line_in_hand(vio_line_control lc) ; +Inline bool vio_lc_is_empty(vio_line_control lc) ; + extern size_t vio_lc_append(vio_line_control lc, const void* buf, size_t len) ; +extern bool vio_lc_flush(vio_line_control lc) ; extern int vio_lc_write_nb(int fd, vio_line_control lc) ; /*------------------------------------------------------------------------------ + * Create new line control. + * + * Returns: address of new line control. + */ +Inline vio_line_control +vio_lc_new(int width, int height, const char* newline) +{ + return vio_lc_init_new(NULL, width, height, newline) ; +} ; + +/*------------------------------------------------------------------------------ + * Free given line control (if any). + * + * It is the caller's responsibility to free anything that the line control may + * point to. + * + * Returns: NULL + */ +Inline vio_line_control +vio_lc_free(vio_line_control lc) +{ + return vio_lc_reset(lc, free_it) ; +} ; + +/*------------------------------------------------------------------------------ + * Counter reset. + */ +Inline void +vio_lc_counter_reset(vio_line_control lc) +{ + lc->counter = (lc->height > 0) ? lc->height : 100 ; +} ; + +/*------------------------------------------------------------------------------ + * If no height is set, set counter to large number -- to do a tranche of + * output. + * + * Otherwise, if the line control is paused (or would pause as soon as any + * output is sent), reset the counter. + */ +Inline void +vio_lc_clear_pause(vio_line_control lc) +{ + if ((lc->counter <= 0) || (lc->height == 0)) + vio_lc_counter_reset(lc) ; +} ; + +/*------------------------------------------------------------------------------ + * Is the given line control counter exhausted ? + * + * Any attempt to output more stuff will be prevented -- except for + * vio_lc_flush() which will succeed if height is indefinite. + */ +Inline bool +vio_lc_counter_is_exhausted(vio_line_control lc) +{ + return (lc->counter <= 0) ; +} ; + +/*------------------------------------------------------------------------------ + * Do we have a complete line "in hand" ? + * + * This will only be the case if have a definite height, and the line counter + * has expired. + */ +Inline bool +vio_lc_have_complete_line_in_hand(vio_line_control lc) +{ + return !lc->incomplete && !qiovec_empty(lc->fragments) ; +} ; + +/*------------------------------------------------------------------------------ * Is given line control empty ? + * + * Is empty if the qiov is empty and there is nothing in hand. + * + * NB: if there is something in hand, it may be complete or incomplete. */ Inline bool -vio_lc_empty(vio_line_control lc) +vio_lc_is_empty(vio_line_control lc) { - return qiovec_empty(&lc->qiov) ; + return qiovec_empty(lc->qiov) && qiovec_empty(lc->fragments) ; } ; #endif /* _ZEBRA_VIO_LINES_H */ @@ -28,13 +28,17 @@ #include <sys/param.h> #include <sys/stat.h> #include <ctype.h> +#include <unistd.h> #include "vty.h" #include "vty_local.h" #include "vty_io.h" +#include "vty_io_file.h" #include "vty_command.h" #include "vty_cli.h" +#include "vty_log.h" #include "vio_fifo.h" +#include "log_local.h" #include "list_util.h" @@ -43,9 +47,10 @@ #include "command_execute.h" #include "command_parse.h" #include "memory.h" -#include "log.h" #include "mqueue.h" #include "qstring.h" +#include "qpath.h" +#include "network.h" /*============================================================================== * The vty family comprises: @@ -104,18 +109,26 @@ struct thread_master* vty_master = NULL ; * actually running pthreaded. */ bool vty_nexus ; /* true <=> in the qpthreads world */ +bool vty_multi_nexus ; /* true <=> more than one qpthread */ -qpn_nexus vty_cli_nexus = NULL ; -qpn_nexus vty_cmd_nexus = NULL ; +qpn_nexus vty_cli_nexus = NULL ; +qpn_nexus vty_cmd_nexus = NULL ; /* List of all known vio */ -vty_io vio_list_base = NULL ; +vty_io vio_live_list = NULL ; /* List of all vty which are in monitor state. */ -vty_io vio_monitors_base = NULL ; +vty_io vio_monitor_list = NULL ; /* List of all vty which are on death watch */ -vty_io vio_death_watch = NULL ; +vty_io vio_death_watch = NULL ; + +/* List of child processes in our care */ +vio_child vio_childer_list = NULL ; + +/* See vty_child_signal_nexus_set() */ +qpt_mutex_t vty_child_signal_mutex ; +qpn_nexus vty_child_signal_nexus = NULL ; /*------------------------------------------------------------------------------ * VTYSH stuff @@ -129,7 +142,6 @@ 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 *); @@ -174,18 +186,21 @@ vty_init (struct thread_master *master_thread) vty_master = master_thread; /* Local pointer to the master thread */ - vty_save_cwd (); /* need cwd for config reading */ - - vio_list_base = NULL ; /* no VTYs yet */ - vio_monitors_base = NULL ; + vio_live_list = NULL ; /* no VTYs yet */ vio_death_watch = NULL ; + vio_childer_list = NULL ; vty_nexus = false ; /* not running qnexus-wise */ + vty_multi_nexus = false ; /* not more than one thread either */ vty_cli_nexus = NULL ; vty_cmd_nexus = NULL ; + vty_child_signal_nexus = NULL ; /* none, yet */ + uty_watch_dog_init() ; /* empty watch dog */ + uty_init_monitor() ; + uty_init_commands() ; /* install nodes */ vty_init_state = vty_init_1st_stage ; @@ -215,11 +230,14 @@ 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 ; + vty_nexus = true ; + vty_multi_nexus = (cli != cmd) ; + vty_cli_nexus = cli ; + vty_cmd_nexus = cmd ; + + qpt_mutex_init(vty_mutex, qpt_mutex_recursive); - qpt_mutex_init(&vty_mutex, qpt_mutex_recursive); + qpt_mutex_init(vty_child_signal_mutex, qpt_mutex_quagga); vty_init_state = vty_init_2nd_stage ; } ; @@ -429,15 +447,28 @@ vty_terminate (void) uty_reset(true, "Shut down") ; /* final reset */ + vty_child_close_register() ; + VTY_UNLOCK() ; - qpt_mutex_destroy(&vty_mutex, 0); + qpt_mutex_destroy(vty_mutex, 0); + qpt_mutex_destroy(vty_child_signal_mutex, 0); vty_init_state = vty_init_terminated ; } /*------------------------------------------------------------------------------ - * Reset -- final curtain or for SIGHUP + * Reset -- for SIGHUP or at final curtain. + * + * For SIGHUP is called via vty_reset_because(), and is sitting in the + * vty_cli_nexus (if pthreaded) with the message queues still running. + * + * For final curtain will + * + * + * is called by vty_terminate(), by which time all qnexus + * have been shut down, so no message queues and no timers etc, are running. + * * * Closes listeners. * @@ -460,18 +491,17 @@ uty_reset (bool curtains, const char* why) vty_io vio ; vty_io next ; - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; + VTY_ASSERT_CLI_THREAD_LOCKED() ; uty_close_listeners() ; - next = sdl_head(vio_list_base) ; + next = sdl_head(vio_live_list) ; while (next != NULL) { vio = next ; next = sdl_next(vio, vio_list) ; - uty_close(vio, curtains, qs_set(NULL, why)) ; + uty_close(vio, why, curtains) ; } ; host.vty_timeout_val = VTY_TIMEOUT_DEFAULT; @@ -483,10 +513,7 @@ uty_reset (bool curtains, const char* why) /* sets host.vty_ipv6_accesslist_name = NULL */ if (curtains) - { - XFREE (MTYPE_HOST, host.vty_cwd); - uty_watch_dog_stop() ; /* and final death watch run */ - } ; + uty_watch_dog_stop() ; /* and final death watch run */ } ; /*============================================================================== @@ -505,32 +532,17 @@ uty_reset (bool curtains, const char* why) * Appears only to be used by vtysh !! TODO ???? */ extern vty -vty_open(vty_type_t type) +vty_open(vty_type_t type, node_type_t node) { struct vty* vty ; VTY_LOCK() ; - vty = uty_new(type) ; + vty = uty_new(type, node) ; VTY_UNLOCK() ; return vty ; } ; -/*------------------------------------------------------------------------------ - * Close the given VTY - */ -extern bool -vty_close(vty vty, bool final, qstring reason) -{ - bool closed ; - - VTY_LOCK() ; - closed = uty_close(vty->vio, final, reason) ; - VTY_UNLOCK() ; - - return closed ; -} - /*============================================================================== * General VTY output. * @@ -564,6 +576,23 @@ vty_out(struct vty *vty, const char *format, ...) } /*------------------------------------------------------------------------------ + * VTY output -- cf write + * + * Returns: >= 0 => OK + * < 0 => failed (see errno) + */ +extern int +vty_write(struct vty *vty, const void* buf, int n) +{ + VTY_LOCK() ; + + vio_fifo_put_bytes(vty->vio->obuf, buf, n) ; + + VTY_UNLOCK() ; + return 0 ; +} + +/*------------------------------------------------------------------------------ * VTY output -- output a given numnber of spaces * * This is for command output, which may be suppressed @@ -613,41 +642,86 @@ vty_time_print (struct vty *vty, int cr) /*------------------------------------------------------------------------------ * Say hello to vty interface. */ -void +extern void vty_hello (struct vty *vty) { + qpath path ; + const char* string ; + VTY_LOCK() ; -#ifdef QDEBUG - vty_out (vty, "%s\n", debug_banner); -#endif - if (host.motdfile) + path = (host.motdfile != NULL) ? qpath_dup(host.motdfile) : NULL ; + string = host.motd ; + + VTY_UNLOCK() ; + + if (qdebug) + vty_out (vty, "%s\n", debug_banner); + + if (path != NULL) + vty_cat_file(vty, path, "motd file") ; + else if (string != NULL) + vty_out(vty, "%s", string); + + /* This "\n" is a bit magic... if the motd file does not end in a "\n", + * then this makes sure that we start on a new line. + * + * Similarly, if the motd string doesn't end '\n', then this makes sure. + * + * This will also trim trailing space from the end of the motd message. + * + * Generally these will end in '\n', so this produces the extra blank line + * before the cheerful "User authentication" message, which is the most + * likely next line. + */ + vty_out(vty, "\n") ; + + qpath_free(path) ; +} ; + +/*------------------------------------------------------------------------------ + * "cat" file to vty + */ +extern cmd_return_code_t +vty_cat_file(vty vty, qpath path, const char* desc) +{ + int fd ; + void* buf ; + + fd = uty_vfd_file_open(qpath_string(path), vfd_io_read | vfd_io_blocking) ; + + if (fd < 0) { - FILE *f; - char buf[4096]; + vty_out (vty, "Cannot open %s file '%s': %s (%s)\n", desc, + qpath_string(path), errtostr(errno, 0).str, + errtoname(errno, 0).str) ; + return CMD_WARNING; + } ; - f = fopen (host.motdfile, "r"); - if (f) - { - while (fgets (buf, sizeof (buf), f)) - { - char *s; - /* work backwards to ignore trailing isspace() */ - for (s = buf + strlen (buf); (s > buf) && isspace ((int)*(s - 1)); - s--); - *s = '\0'; - vty_out(vty, "%s\n", buf); - } - fclose (f); - } + enum { buffer_size = 64 * 1024 } ; + buf = XMALLOC(MTYPE_TMP, buffer_size) ; + + while (1) + { + int r ; + + r = readn(fd, buf, buffer_size) ; + + if (r > 0) + vty_write(vty, buf, r) ; // TODO push ?? else - vty_out(vty, "MOTD file %s not found\n", host.motdfile); - } - else if (host.motd) - vty_out(vty, "%s", host.motd); + { + if (r == 0) + break ; - VTY_UNLOCK() ; -} + // TODO error handling .... + } ; + } ; + + close(fd) ; + + return CMD_SUCCESS; +} ; /*------------------------------------------------------------------------------ * Clear the contents of the command output FIFO etc. @@ -661,6 +735,23 @@ vty_out_clear(vty vty) } ; /*============================================================================== + * Deal with SIGCHLD "event". + * + * NB: if there is a nexus to be signalled, we do that *before* attempting to + * lock the VTY -- because in that case the VTY will be locked by that + * nexus ! + */ +extern void +vty_sigchld(void) +{ + vty_child_signal_nexus_signal() ; + + VTY_LOCK() ; + uty_sigchld() ; + VTY_UNLOCK() ; +} ; + +/*============================================================================== * Reading of configuration file * * The reading of the configuration file occurs at two times: @@ -681,9 +772,10 @@ vty_out_clear(vty vty) * run directly in the thread -- no commands are queued. */ -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) ; +static int vty_use_backup_config (qpath path) ; +static void vty_read_config_file (int conf_fd, const char* name, + cmd_command first_cmd, bool ignore_warnings, + bool full_lex) ; /*------------------------------------------------------------------------------ * Read the given configuration file. @@ -718,10 +810,9 @@ vty_read_config_first_cmd_special(const char *config_file, cmd_command first_cmd, bool ignore_warnings) { - char cwd[MAXPATHLEN] ; - int conf_fd ; - const char *fullpath ; - char *tmp = NULL ; + const char *name ; + qpath path ; + int conf_fd ; /* Deal with VTYSH_ENABLED magic */ if (VTYSH_ENABLED && (config_file == NULL)) @@ -747,7 +838,7 @@ vty_read_config_first_cmd_special(const char *config_file, { ret = stat (integrate_default, &conf_stat); if (ret >= 0) - return; + return; /* TODO leaves host.config_file NULL */ } } ; @@ -755,158 +846,115 @@ vty_read_config_first_cmd_special(const char *config_file, if (config_file == NULL) config_file = config_default ; - if (! IS_DIRECTORY_SEP (config_file[0])) - { - getcwd (cwd, sizeof(cwd)) ; - tmp = XMALLOC (MTYPE_TMP, strlen (cwd) + strlen (config_file) + 2) ; - sprintf (tmp, "%s/%s", cwd, config_file); - fullpath = tmp; - } - else - { - tmp = NULL ; - fullpath = config_file; - } ; + path = qpath_make(config_file, host.cwd) ; + name = qpath_string(path) ; /* try to open the configuration file */ - conf_fd = uty_vfd_file_open(fullpath, vfd_io_read | vfd_io_blocking) ; + conf_fd = uty_vfd_file_open(name, vfd_io_read) ; if (conf_fd < 0) { fprintf (stderr, "%s: failed to open configuration file %s: %s\n", - __func__, fullpath, errtostr(errno, 0).str); + __func__, name, errtostr(errno, 0).str) ; - conf_fd = vty_use_backup_config (fullpath); + conf_fd = vty_use_backup_config (path); if (conf_fd >= 0) fprintf (stderr, "WARNING: using backup configuration file!\n"); else { fprintf (stderr, "can't open backup configuration file [%s%s]\n", - fullpath, CONF_BACKUP_EXT); + name, CONF_BACKUP_EXT); exit(1); } } ; -#ifdef QDEBUG - fprintf(stderr, "Reading config file: %s\n", fullpath); -#endif + if (qdebug) + fprintf(stderr, "Reading config file: %s\n", name); - vty_read_file(conf_fd, fullpath, first_cmd, ignore_warnings); + vty_read_config_file(conf_fd, name, first_cmd, ignore_warnings, false); - host_config_set (fullpath); + cmd_host_config_set(path) ; -#ifdef QDEBUG - fprintf(stderr, "Finished reading config file\n"); -#endif + qpath_free(path) ; - if (tmp) - XFREE (MTYPE_TMP, tmp); + if (qdebug) + fprintf(stderr, "Finished reading config file\n"); } /*------------------------------------------------------------------------------ * Try to use a backup configuration file. * - * Having failed to open the file "<fullpath>", if there is a file called - * "<fullpath>.sav" that can be opened for reading, then: + * Having failed to open the file "<path>", if there is a file called + * "<path>.sav" that can be opened for reading, then: * * - make a copy of that file - * - call it "<fullpath>" + * - call it "<path>" * - return an open FILE * - * Returns: < 0 => no "<fullpath>.sav", or failed doing any of the above + * Returns: < 0 => no "<path>.sav", or failed doing any of the above * >= 0 otherwise, fd file. */ static int -vty_use_backup_config (const char *fullpath) +vty_use_backup_config (qpath path) { - char *tmp_path ; - struct stat buf; - int ret, tmp, sav; - int c, err; - char* buffer ; - - enum { xl = 32 } ; - tmp_path = malloc(strlen(fullpath) + xl) ; + qpath temp ; + char* name ; + int sav_fd, tmp_fd, err ; + bool ok ; /* construct the name "<fullname>.sav", and try to open it. */ - confirm(xl > sizeof(CONF_BACKUP_EXT)) ; - sprintf (tmp_path, "%s%s", fullpath, CONF_BACKUP_EXT) ; + temp = qpath_dup(path) ; + qpath_extend_str(temp, CONF_BACKUP_EXT) ; - if (stat (tmp_path, &buf) != -1) - sav = uty_vfd_file_open(tmp_path, vfd_io_read | vfd_io_blocking) ; - else - sav = -1 ; + sav_fd = -1 ; + tmp_fd = -1 ; - if (sav < 0) - { - err = errno ; /* making sure */ - free (tmp_path) ; - errno = err ; - return sav ; - } ; + sav_fd = uty_vfd_file_open(qpath_string(temp), + vfd_io_read | vfd_io_blocking) ; /* construct a temporary file and copy "<fullpath.sav>" to it. */ - confirm(xl > sizeof(".XXXXXX")) - sprintf (tmp_path, "%s%s", fullpath, ".XXXXXX") ; + qpath_extend_str(temp, ".XXXXXX") ; + name = qpath_char_string(temp) ; - /* Open file to configuration write. */ - tmp = mkstemp (tmp_path); - if (tmp < 0) - { - err = errno ; - free (tmp_path); - close(sav); - errno = err ; - return tmp; - } + if (sav_fd >= 0) + tmp_fd = mkstemp(name) ; - enum { buffer_size = 64 * 1024 } ; - buffer = malloc(buffer_size) ; + ok = ((sav_fd >= 0) && (tmp_fd >= 0)) ; + + if (ok) + ok = (copyn(tmp_fd, sav_fd) == 0) ; - 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) ; + if (tmp_fd >= 0) + close(tmp_fd) ; + if (sav_fd >= 0) + close(sav_fd) ; - if (c < 0) - { - errno = err ; - return c ; - } ; + /* If now OK, then have copied the .sav to the temporary file. */ - /* Make sure that have the required file status */ - if (chmod(tmp_path, CONFIGFILE_MASK) != 0) + if (ok) { + /* Make sure that have the required file status */ + ok = chmod(name, CONFIGFILE_MASK) == 0 ; + + /* Finally, make a link with the original name */ + if (ok) + ok = link(name, qpath_string(path)) == 0 ; + err = errno ; - unlink (tmp_path); - free (tmp_path); - errno = err ; - return -1 ; } ; - /* Make <fullpath> be a name for the new file just created. */ - ret = link (tmp_path, fullpath) ; + if (tmp_fd >= 0) /* if made a temporary, done with it now */ + unlink(name) ; - /* Discard the temporary, now */ - err = errno ; - unlink (tmp_path) ; - free (tmp_path) ; - errno = err ; + qpath_free(temp) ; /* done with the qpath */ - /* If link was successful, try to open -- otherwise, failed. */ - return (ret == 0) ? uty_vfd_file_open(fullpath, vfd_io_read | vfd_io_blocking) - : -1 ; + if (ok) + return uty_vfd_file_open(qpath_string(path), vfd_io_read) ; + + errno = err ; + return -1 ; } ; /*------------------------------------------------------------------------------ @@ -930,82 +978,33 @@ vty_use_backup_config (const char *fullpath) * so all commands are executed directly. */ static void -vty_read_file (int conf_fd, const char* name, - cmd_command first_cmd, bool ignore_warnings) +vty_read_config_file (int fd, const char* name, cmd_command first_cmd, + bool ignore_warnings, bool full_lex) { cmd_return_code_t ret ; vty vty ; - vty_io vio ; - vio_vf vf ; - VTY_LOCK() ; + vty = vty_config_read_open(fd, name, full_lex) ; - /* Set up configuration file reader VTY -- which buffers all output */ - vty = vty_open(VTY_CONFIG_READ); - vty->node = CONFIG_NODE; - - vio = vty->vio ; + vty_cmd_loop_prepare(vty) ; - vf = uty_vf_new(vio, name, conf_fd, vfd_file, vfd_io_read | vfd_io_blocking) ; + zlog_info("Started reading configuration: %s", name) ; - uty_vin_open( vio, vf, VIN_CONFIG, NULL, NULL, 64 * 1024) ; - uty_vout_open(vio, vf, VOUT_STDERR, NULL, NULL, 4 * 1024) ; - - vio->vin->parse_type = cmd_parse_strict | cmd_parse_no_do ; - - /* When we get here the VTY is set up and all ready to go. */ - uty_cmd_prepare(vio) ; - - VTY_UNLOCK() ; - - /* Execute configuration file */ ret = cmd_read_config(vty, first_cmd, ignore_warnings) ; - ret = vty_cmd_loop_exit(vty, ret) ; + zlog_info("Finished reading configuration%s", + (ret == CMD_SUCCESS) ? "." : " -- FAILED") ; - if (ret != CMD_SUCCESS) - { - vty_close(vty, true, qs_set(NULL, "Error in configuration file.")) ; - exit (1); - } ; + vty_cmd_loop_exit(vty) ; - vty_close(vty, false, NULL) ; + if (ret != CMD_SUCCESS) + exit(1) ; } ; -#if 0 /*------------------------------------------------------------------------------ - * Flush the contents of the command output FIFO to the given file. + * Push the given fd as the VOUT_CONFIG. * - * 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) ; - - src = vio_fifo_get_lump(&vio->cmd_obuf, &have) ; - while (src != NULL) - { - fwrite(src, 1, have, file) ; - src = vio_fifo_step_get_lump(&vio->cmd_obuf, &have, have) ; - } ; - - fflush(file) ; -} ; -#endif - -/*============================================================================== - * For writing configuration file by command, temporarily redirect output to - * an actual file. - */ - -/*------------------------------------------------------------------------------ - * Set the given fd as the VTY_FILE output. + * Note that this is a "blocking" vf, so can open and close in the cmd thread. */ extern void vty_open_config_write(vty vty, int fd) @@ -1017,30 +1016,27 @@ vty_open_config_write(vty vty, int fd) vio = vty->vio ; - vf = uty_vf_new(vio, "config write", fd, vfd_file, vfd_io_read) ; - - uty_vout_open(vio, vf, VOUT_FILE, NULL, NULL, 16 * 1024) ; + vf = uty_vf_new(vio, "config write", fd, vfd_file, + vfd_io_read | vfd_io_blocking) ; + uty_vout_push(vio, vf, VOUT_CONFIG, NULL, NULL, 32 * 1024) ; VTY_UNLOCK() ; } ; /*------------------------------------------------------------------------------ - * Write away any pending stuff, and return the VTY to normal. + * Write away any pending stuff, and pop the VOUT_CONFIG. */ -extern int -vty_close_config_write(struct vty* vty) +extern cmd_return_code_t +vty_close_config_write(struct vty* vty, bool final) { -// vty_io vio ; -// int err ; - + cmd_return_code_t ret ; VTY_LOCK() ; - uty_vout_close(vty->vio, false) ; + ret = uty_vout_pop(vty->vio, final) ; VTY_UNLOCK() ; -// return err ; - return 0 ; + return ret ; } ; /*============================================================================== @@ -1057,12 +1053,12 @@ DEFUN_CALL (config_who, VTY_LOCK() ; - vio = vio_list_base ; /* once locked */ + vio = vio_live_list ; /* once locked */ - while (vio != NULL) /* TODO: show only VTY_TERM ??? */ + while (vio != NULL) /* TODO: show only VTY_TERM ??? */ { vty_out(vty, "%svty[%d] connected from %s.\n", - vio->vty->config ? "*" : " ", i, uty_get_name(vio)); + vio->vty->config ? "*" : " ", i, uty_get_name(vio)) ; vio = sdl_next(vio, vio_list) ; } ; @@ -1071,11 +1067,12 @@ DEFUN_CALL (config_who, } /* Move to vty configuration mode. */ -DEFUN_CALL (line_vty, - line_vty_cmd, - "line vty", - "Configure a terminal line\n" - "Virtual terminal\n") +DEFUN_ATTR (line_vty, + line_vty_cmd, + "line vty", + "Configure a terminal line\n" + "Virtual terminal\n", + CMD_ATTR_DIRECT + CMD_ATTR_NODE + VTY_NODE) { VTY_LOCK() ; vty->node = VTY_NODE; @@ -1365,32 +1362,40 @@ DEFUN_CALL (show_history, static int vty_config_write (struct vty *vty) { - vty_out (vty, "line vty\n"); + vty_io vio ; + + VTY_LOCK() ; /* while accessing the host.xxx */ + + vio = vty->vio ; + + uty_out (vio, "line vty\n"); if (host.vty_accesslist_name) - vty_out (vty, " access-class %s\n", host.vty_accesslist_name); + uty_out (vio, " access-class %s\n", host.vty_accesslist_name); if (host.vty_ipv6_accesslist_name) - vty_out (vty, " ipv6 access-class %s\n", host.vty_ipv6_accesslist_name); + uty_out (vio, " ipv6 access-class %s\n", host.vty_ipv6_accesslist_name); /* exec-timeout */ if (host.vty_timeout_val != VTY_TIMEOUT_DEFAULT) - vty_out (vty, " exec-timeout %ld %ld\n", host.vty_timeout_val / 60, + uty_out (vio, " exec-timeout %ld %ld\n", host.vty_timeout_val / 60, host.vty_timeout_val % 60); /* login */ if (host.no_password_check) - vty_out (vty, " no login\n"); + uty_out (vio, " no login\n"); if (host.restricted_mode != restricted_mode_default) { if (restricted_mode_default) - vty_out (vty, " no anonymous restricted\n"); + uty_out (vio, " no anonymous restricted\n"); else - vty_out (vty, " anonymous restricted\n"); + uty_out (vio, " anonymous restricted\n"); } - vty_out (vty, "!\n"); + uty_out (vio, "!\n"); + + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -1400,39 +1405,22 @@ vty_config_write (struct vty *vty) */ /*------------------------------------------------------------------------------ - * Save cwd - * - * This is done early in the morning so that any future operations on files - * can use the original cwd if required. + * Get cwd as at start-up. Never changed -- so no locking required. */ -static void -vty_save_cwd (void) +extern qpath +vty_getcwd (qpath qp) { - char cwd[MAXPATHLEN]; - char *c; + VTY_LOCK() ; - c = getcwd (cwd, MAXPATHLEN); + qp = qpath_copy(qp, host.cwd) ; - if (!c) - { - chdir (SYSCONFDIR); - getcwd (cwd, MAXPATHLEN); - } - - host.vty_cwd = XSTRDUP(MTYPE_HOST, cwd) ; -} ; + VTY_UNLOCK() ; -/*------------------------------------------------------------------------------ - * Get cwd as at start-up. Never changed -- so no locking required. - */ -char * -vty_get_cwd () -{ - return host.vty_cwd; +return qp ; } /*============================================================================== - * Access functions for VTY values, where locking is or might be required. + * Access functions for vio values, where locking is or might be required. */ #if 0 @@ -39,6 +39,7 @@ #include "list_util.h" #include "vector.h" #include "qstring.h" +#include "qpath.h" /*============================================================================== * These are definitions and functions for things which are required outside @@ -48,8 +49,7 @@ /*------------------------------------------------------------------------------ * */ -#define VTY_BUFSIZ 512 -#define VTY_MAXHIST 51 +enum { VTY_HIST_COUNT = 55 } ; /* Integrated configuration file. */ #define INTEGRATE_DEFAULT_CONFIG "Quagga.conf" @@ -79,7 +79,7 @@ enum { VTY_MAX_SPACES = 40 } ; #define VTY_GET_LONG(NAME,V,STR) \ do { \ char *endptr = NULL; \ - (V) = strtoul ((STR), &endptr, 10); \ + (V) = strtoul ((STR), &endptr, 0); \ if (*endptr != '\0' || (V) == ULONG_MAX) \ { \ vty_out (vty, "%% Invalid %s value%s", NAME, VTY_NEWLINE); \ @@ -138,21 +138,16 @@ extern void vty_start(const char *addr, unsigned short port, const char *path) ; #define vty_serv_sock(addr, port, path) vty_start(addr, port, path) extern void vty_restart(const char *addr, unsigned short port, const char *path) ; -extern struct vty* vty_open(enum vty_type type) ; -extern void vty_close_final(struct vty *); - -extern void vty_init_vtysh (void); extern void vty_terminate (void); extern void vty_reset (void); extern void vty_reset_because(const char* why) ; extern int vty_out (struct vty *, const char *, ...) PRINTF_ATTRIBUTE(2, 3); +extern int vty_write(struct vty *vty, const void* buf, int n) ; extern int vty_out_indent(struct vty *vty, int indent) ; extern void vty_out_clear(struct vty *vty) ; - - - +extern void vty_sigchld(void) ; extern void vty_read_config (const char* config_file, const char* config_default); @@ -161,10 +156,14 @@ extern void vty_read_config_first_cmd_special(const char* config_file, cmd_command first_cmd, bool ignore_warnings) ; -extern void vty_time_print (struct vty *, int); +extern qpath vty_getcwd(qpath qp); -extern char *vty_get_cwd (void); - -extern void vty_hello (struct vty *); +/*------------------------------------------------------------------------------ + * vtysh + */ +extern void vty_hello (vty vty); +extern struct vty* vty_open(enum vty_type type, node_type_t node) ; +extern void vty_close_final(vty vty); +extern void vty_init_vtysh (void); #endif /* _ZEBRA_VTY_H */ diff --git a/lib/vty_cli.c b/lib/vty_cli.c index 42d78349..3b82a112 100644 --- a/lib/vty_cli.c +++ b/lib/vty_cli.c @@ -40,6 +40,13 @@ #include "memory.h" +/*------------------------------------------------------------------------------ + * Essential stuff. + */ +#define TELNET_NEWLINE "\r\n" +static const char* telnet_newline = TELNET_NEWLINE ; + const char* uty_cli_newline = TELNET_NEWLINE ; + /*============================================================================== * Construct and destroy CLI object * @@ -49,13 +56,15 @@ static bool uty_cli_iac_callback(keystroke_iac_callback_args) ; static void uty_cli_update_more(vty_cli cli) ; +static void uty_cli_cancel(vty_cli cli) ; + /*------------------------------------------------------------------------------ * Construct and initialise a new CLI object -- never embedded. * * 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. + * output from an implied start command. When the command loop is entered, + * will find its way to uty_cli_want_command(). * * 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 @@ -72,9 +81,10 @@ uty_cli_new(vio_vf vf) * * vf = NULL -- set below * - * hist = empty vector (embedded) -- set up when first used. + * hist = NULL -- set up when first used. * hp = 0 -- hp == h_now => in the present ... * h_now = 0 -- ... see uty_cli_hist_add() + * h_repeat = false -- ... see uty_cli_hist_add() * * width = 0 -- unknown width ) Telnet window size * height = 0 -- unknown height ) @@ -92,46 +102,48 @@ uty_cli_new(vio_vf vf) * drawn = false * dirty = false * + * tilde_prompt = false -- tilde prompt is not drawn + * tilde_enabled = false -- set below if ! multi-threaded + * * 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) + * prompt_for_node = NULL -- not set, yet * - * password_fail = 0 -- so far, so good. - * - * blocked = false -- see below + * dispatched = false -- see below * in_progress = false -- see below + * blocked = false -- see below + * paused = false + * * out_active = false + * flush = false * * more_wait = false - * more_active = false - * - * out_done = false -- not that it matters + * more_enter = false * * more_enabled = false -- not in "--More--" state * - * node = NULL_NODE -- set in vty_cli_start() - * to_do = cmd_do_nothing + * pause_timer = NULL -- set below if multi-threaded * + * context = NULL -- see below + * context_auth = false -- set by uty_cli_want_command() + * + * parsed = NULL -- see below + * to_do = cmd_do_nothing * cl = NULL qstring -- set below + * clo = NULL qstring -- set below * clx = NULL qstring -- set below + * dispatch = all zeros -- nothing to dispatch * - * parsed = all zeros -- empty parsed object - * cbuf = empty fifo (embedded) + * cbuf = NULL -- see below * - * olc = empty line control (embedded) + * olc = NULL -- see below */ - 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 */ + confirm(CMD_ACTION_ALL_ZEROS) ; /* dispatch */ cli->vf = vf ; @@ -139,40 +151,43 @@ uty_cli_new(vio_vf vf) cli->key_stream = keystroke_stream_new('\0', uty_cli_iac_callback, cli) ; /* 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) ; + cli->cl = qs_new(120) ; /* reasonable line length */ + cli->cls = qs_new(120) ; + cli->clx = qs_new(120) ; + + cli->cbuf = vio_fifo_init_new(NULL, 1000) ; - vio_fifo_init_new(cli->cbuf, 500) ; /* just for the CLI stuff */ + cli->olc = vio_lc_new(0 , 0, telnet_newline) ; + + /* Make an empty context object and an empty parsed object */ + cli->context = cmd_context_new() ; + cli->parsed = cmd_parsed_new() ; /* 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 ; + /* Enable "~ " prompt and pause timer if multi-threaded */ + if (vty_nexus) + { + cli->pause_timer = qtimer_init_new(NULL, vty_cli_nexus->pile, + vty_term_pause_timeout, cli) ; + cli->tilde_enabled = vty_multi_nexus ; + } ; + + /* Ready to be started -- paused, out_active, flush & more_wait are false. + * + * Is started by the first call of uty_cli_want_command(), which (inter alia) + * set the cli->context so CLI knows how to prompt etc. + */ + cli->dispatched = true ; cli->in_progress = true ; + cli->blocked = true ; return cli ; } ; /*------------------------------------------------------------------------------ - * 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_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. @@ -180,7 +195,7 @@ uty_cli_start(vty_cli cli, node_type_t node) * 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 dispatched and not final, keeps the cbuf, the olc and clx if dispatched. * If not final, keeps the cbuf and the olc. * * Destroys self if final. @@ -199,11 +214,7 @@ uty_cli_close(vty_cli cli, bool final) if (cli == NULL) return NULL ; - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - /* Revoke any command that is in flight. */ - cq_revoke(cli->vf->vio->vty) ; + VTY_ASSERT_CLI_THREAD_LOCKED() ; /* Bring as much of the command handling to a stop as possible, and * turn off "---more--" etc. so that output can proceed. @@ -211,38 +222,48 @@ uty_cli_close(vty_cli cli, bool final) 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 */ + uty_cli_cancel(cli) ; /* " ^C\r\n" unless believed blank */ - cli->more_wait = false ; /* exit more_wait (if was in it) */ - cli->more_active = false ; /* and so cannot be this */ + cli->more_wait = false ; /* exit more_wait (if was in it) */ + cli->more_enter = false ; /* and so cannot be this */ - cli->out_active = true ; /* if there is any output, it can go */ + cli->blocked = true ; /* do not renter the CLI */ + cli->out_active = true ; /* if there is any output, it can go */ + cli->flush = true ; /* all of it can go */ /* Ream out the history. */ { qstring line ; - while ((line = vector_ream(cli->hist, keep_it)) != NULL) + while ((line = vector_ream(cli->hist, free_it)) != NULL) qs_reset(line, free_it) ; + cli->hist = NULL ; } ; /* 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) + /* Can discard active command line if not dispatched TODO ?? */ + if (!cli->dispatched || final) cli->clx = qs_reset(cli->clx, free_it) ; - /* Can discard parsed object. */ - cmd_parsed_reset(cli->parsed, keep_it) ; + /* Can discard context and parsed objects */ + cli->context = cmd_context_free(cli->context, true) ; /* its a copy */ + cli->parsed = cmd_parsed_free(cli->parsed) ; + + /* Discard any pause_timer, and suppress */ + cli->pause_timer = qtimer_free(cli->pause_timer) ; + cli->paused = false ; + cli->tilde_enabled = false ; /* If final, free the CLI object. */ if (final) { - qs_reset(cli->prompt_for_node, keep_it) ; - cli->cl = qs_reset(cli->cl, free_it) ; + cli->prompt_for_node = qs_reset(cli->prompt_for_node, free_it) ; + cli->cl = qs_reset(cli->cl, free_it) ; + cli->cls = qs_reset(cli->cls, free_it) ; - vio_fifo_reset(cli->cbuf, keep_it) ; /* embedded fifo */ - vio_lc_reset(cli->olc, keep_it) ; /* embedded line control */ + cli->cbuf = vio_fifo_reset(cli->cbuf, free_it) ; + cli->olc = vio_lc_reset(cli->olc, free_it) ; XFREE(MTYPE_VTY_CLI, cli) ; /* sets cli = NULL */ } ; @@ -373,12 +394,18 @@ uty_cli_update_more(vty_cli cli) * 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.] + * not executed inside the CLI code. [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: * - * in_progress -- a command has been dispatched and has not yet completed. + * dispatched -- a command line has been dispatched, and is waiting for + * (dsp) command loop to fetch it. + * + * in_progress -- a command has been taken by the command loop, and is still + * (inp) running -- or at least no further command has been + * fetched by the command loop. * * The command may be a in_pipe, so there may be many commands * to be completed before the CLI level command is. @@ -386,36 +413,48 @@ uty_cli_update_more(vty_cli cli) * or: the CLI has been closed. * * blocked -- is in_progress and a further command is now ready to be - * dispatched. + * (bkd) dispatched. * * or: the CLI has been closed. * * out_active -- the command output FIFO is being emptied. - * - * This is set when a command completes, and cleared when - * everything is written away. - * - * 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. - * - * more_wait -- is in "--more--" wait state - * - * more_active -- is in "--more--" wait state, and the "--more--" prompt - * has not been written away, yet. + * (oa) + * This is set when output is pushed, and cleared when + * everything is written away and flush is set. When it + * is set, any current command line is wiped. + * + * Note that what this flag does is prevent the CLI from + * running until the output completes, and in particular + * prevents it from writing anything to the CLI buffer. + * + * flush -- is set when the CLI is ready for the next command (so + * (fsh) when in_progress is cleared) to cause any incomplete + * command output to be flushed, and to signal that + * out_active should be cleared when all output is complete. + * + * more_wait -- is in "--more--" wait state. => out_active ! + * (mwt) + * The "--more--" CLI uses the CLI output buffer to draw + * and undraw the "--more--" prompt. The buffer will + * otherwise be empty because is out_active. + * + * more_enter -- is in the process of entering the "--more--" wait state, + * (men) waiting to write the "--more--" prompt and prepare the + * keystroke input. * * The following are the valid combinations: * - * 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 + * dsp:inp:bkd: oa:fsh:mwt:men: + * ---:---:---:---:---:---:---:----------------------------------------- + * 0 : 0 : 0 : 0 : 0 : 0 : 0 : collecting a new command + * 1 : 0 : 0 : 0 : 0 : 0 : 0 : waiting for command to be fetched + * 1 : 1 : 0 : X : 0 : 0 : 0 : command fetched and running + * 1 : 1 : 1 : X : 0 : 0 : 0 : waiting for command to complete + * 0 : 0 : 0 : 1 : 1 : 0 : 0 : waiting for command output to finish + * 1 : 1 : X : 1 : X : 1 : 1 : waiting for "--more--" to start + * 1 : 1 : X : 1 : X : 1 : 0 : waiting for "--more--" response + * 1 : 1 : 1 : 1 : 1 : 0 : 0 : waiting for command to complete, + * after the CLI has been closed * * There are two output FIFOs: * @@ -426,7 +465,17 @@ uty_cli_update_more(vty_cli cli) * The CLI FIFO is emptied whenever possible, in preference to the command * 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. + * to be written away when the command completes, or if it pushes the output + * explicitly. Note that where the CLI level command is a pipe-in, the first + * output will set out_active, and that will persist until returns to the CLI + * level. Between commands in the pipe, the output will be pushed, so will + * output stuff more or less as it is generated. + * + * It is expected that each command's output will end with a newline. However, + * the line control imposes some discipline, and holds on to incomplete lines + * until a newline arrives, or the output if flushed. -- so that when the + * CLI is kicked, the cursor will be at the start of an empty line. + * * * 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 @@ -436,32 +485,20 @@ uty_cli_update_more(vty_cli cli) * re-entered again on write_ready/read_ready -- so does one command line at * a time, yielding the processor after each one. * - * Note that select/pselect treat a socket which is at "EOF", or has seen an - * error, or has been half closed, etc. as readable and writable. This means - * that the CLI will continue to move forward even after the socket is no - * longer delivering any data. - * *------------------------------------------------------------------------------ * The "--more--" handling. * * This is largely buried in the output handling. * - * 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. + * When command 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 more_wait and more_active flags and their handling.) - * - * 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. + * main CLI. (See the more_wait and more_enter flags and their handling.) * * If the user decides to abandon output at the "--more--" prompt, then the - * contents of the command output FIFO are discarded. + * then contents of the command output FIFO are discarded. * *------------------------------------------------------------------------------ * The qpthreads/qpnexus extension. @@ -512,9 +549,9 @@ uty_cli_update_more(vty_cli cli) #define CONTROL(X) ((X) & 0x1F) static enum vty_readiness uty_cli_standard(vty_cli cli) ; +static cmd_do_t uty_cli_auth(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) ; /*------------------------------------------------------------------------------ * CLI for VTY_TERMINAL @@ -534,8 +571,8 @@ uty_cli(vty_cli cli) { VTY_ASSERT_LOCKED() ; - if (cli->vf->vin_state != vf_open) - return not_ready ; /* Nothing more from CLI if closing */ + if (cli->vf->vin_state == vf_closed) + return not_ready ; /* Nothing more from CLI if closed */ /* Standard or "--more--" CLI ? */ if (cli->more_wait) @@ -548,14 +585,14 @@ uty_cli(vty_cli cli) * The Standard CLI */ -static cmd_do_t uty_cli_process(vty_cli cli, node_type_t node) ; +static cmd_do_t uty_cli_process(vty_cli cli) ; static void uty_cli_response(vty_cli cli, cmd_do_t cmd_do) ; - -static void uty_cli_dispatch(vty_cli cli) ; +static bool 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) ; +static void uty_cli_pause_start(vty_cli cli) ; /*------------------------------------------------------------------------------ * Standard CLI for VTY_TERM -- if not blocked, runs until: @@ -574,6 +611,8 @@ static void uty_cli_hist_add (vty_cli cli, qstring clx) ; static vty_readiness_t uty_cli_standard(vty_cli cli) { + bool need_prompt ; + VTY_ASSERT_LOCKED() ; assert(!cli->more_wait) ; /* cannot be here in more_wait state ! */ @@ -584,22 +623,24 @@ uty_cli_standard(vty_cli cli) * NB: in both these cases, assumes that other forces are at work to * keep things moving. */ - if (cli->blocked || cli->out_active) + if (cli->blocked || cli->out_active || cli->paused || cli->mon_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. + /* Make sure that the command line is drawn. * - * If there is something to do, that is because a previous command has - * now completed, which may have wiped the pending command or changed - * the required prompt. + * If there is nothing pending, then can run the CLI until there is + * something to do, or runs out of input. */ + if (!cli->drawn) + uty_cli_draw(cli) ; + if (cli->to_do == cmd_do_nothing) - cli->to_do = uty_cli_process(cli, cli->node) ; - else - uty_cli_draw_this(cli, cli->node) ; + cli->to_do = cli->auth_context ? uty_cli_auth(cli) + : uty_cli_process(cli) ; /* If have something to do, do it if we can. */ + need_prompt = false ; + if (cli->to_do != cmd_do_nothing) { /* Reflect immediate response */ @@ -610,25 +651,46 @@ uty_cli_standard(vty_cli cli) * * Otherwise is now blocked until queued command completes. */ - if (cli->in_progress) - cli->blocked = true ; /* blocked waiting for previous */ + if (cli->dispatched) + cli->blocked = true ; /* waiting for previous */ else - uty_cli_dispatch(cli) ; /* can dispatch the latest */ + need_prompt = uty_cli_dispatch(cli) ; /* dispatch latest */ } ; - /* Use write_ready as a proxy for read_ready on the keystroke stream. + /* If blocked, must wait for some other event to continue in CLI. + * + * Note that will be blocked if have just dispatched a command, and is + * "tilde_enabled" -- which will be true if single threaded, and may be + * set for other reasons. * - * Also, if the command line is not drawn, then return write_ready, so - * that + * If the keystroke stream is not empty, use write_ready as a proxy for + * CLI ready -- no point doing anything until any buffered. * - * Note that if has just gone cli_blocked, still returns ready. This is - * defensive: at worst will generate one unnecessary read_ready/write_ready - * event. + * If command prompt has been redrawn, need to kick writer to deal with + * that -- will reenter to then process any keystrokes. */ - if (keystroke_stream_empty(cli->key_stream)) - return read_ready ; - else + assert(!cli->paused) ; + + if (cli->blocked) + return not_ready ; + + if (!keystroke_stream_empty(cli->key_stream) || need_prompt) return write_ready ; + + /* The keystroke stream is empty, but CLI is not blocked. + * + * If a command is dispatched, and output is not active, and the command + * line is not drawn, then go to paused state, so that will delay output + * of the prompt slightly (or until a keystroke arrives, or the current + * command completes, or something else happens). + * + * Note that if a command has been dispatched, and is !tilde_enabled, then + * will now be blocked. + */ + if (cli->dispatched && !cli->out_active && !cli->drawn) + uty_cli_pause_start(cli) ; + + return read_ready ; } ; /*------------------------------------------------------------------------------ @@ -642,8 +704,10 @@ uty_cli_standard(vty_cli cli) * Generally sets cli->to_do = cmd_do_nothing and clears cli->cl to empty. * * Can set cli->cl_do = and cli->cl to be a follow-on command. + * + * Returns: true <=> nothing, in fact, to do: prompt has been redrawn. */ -static void +static bool uty_cli_dispatch(vty_cli cli) { qstring tmp ; @@ -654,16 +718,17 @@ uty_cli_dispatch(vty_cli cli) VTY_ASSERT_LOCKED() ; /* 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) ; + assert(!cli->dispatched && !cli->in_progress + && !cli->blocked && !cli->out_active) ; + qassert(cli->context->node == vio->vty->exec->context->node) ; /* Set cli->clx to the command about to execute & pick up cli->to_do. * * Clear cli->cl and cli->to_do. */ - tmp = cli->clx ; /* swap clx and cl */ - cli->clx = cli->cl ; - cli->cl = tmp ; + tmp = cli->clx ; /* swap clx and cl */ + cli->clx = cli->cl ; + cli->cl = tmp ; to_do_now = cli->to_do ; /* current operation */ @@ -674,10 +739,8 @@ uty_cli_dispatch(vty_cli cli) //uty_out_clear(cli->vio) ; /* clears FIFO and line control */ /* Dispatch command */ - if ((cli->node == AUTH_NODE) && (to_do_now != cmd_do_nothing)) + if (cli->auth_context) 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... */ @@ -686,6 +749,7 @@ uty_cli_dispatch(vty_cli cli) case cmd_do_nothing: case cmd_do_ctrl_c: case cmd_do_eof: + case cmd_do_timed_out: break ; case cmd_do_command: @@ -715,28 +779,34 @@ uty_cli_dispatch(vty_cli cli) if (to_do_now != cmd_do_nothing) { - cli->in_progress = true ; - uty_cmd_dispatch(vio, to_do_now, cli->clx) ; + cmd_action_set(cli->dispatch, to_do_now, cli->clx) ; + cli->dispatched = true ; + + uty_cmd_signal(vio, CMD_SUCCESS) ; + + cli->blocked = (to_do_now != cmd_do_command) || !cli->tilde_enabled ; } else - uty_cli_draw(cli) ; + { + cmd_action_clear(cli->dispatch) ; + cli->dispatched = false ; + + uty_cli_draw(cli) ; + } ; + + return !cli->dispatched ; } ; /*------------------------------------------------------------------------------ - * Enqueue command -- adding to history -- if is not empty or just comment + * Check if command is empty, if not add to history * - * This is for VTY_TERM type VTY. - * - * Returns: CMD_SUCCESS => empty command line (or just comment) - * CMD_WAITING => enqueued for parse and execute + * Returns: cmd_do_nothing -- empty command line + * cmd_do_command -- command ready to be executed (added to history) */ static cmd_do_t uty_cli_command(vty_cli cli) { - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - if (cmd_is_empty(qs_els_nn(cli->clx))) + if (cmd_is_empty(cli->clx)) return cmd_do_nothing ; /* easy when nothing to do ! */ /* Add not empty command to history */ @@ -746,196 +816,79 @@ uty_cli_command(vty_cli cli) } ; /*------------------------------------------------------------------------------ - * 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() ; - - if (cli->more_wait) - return ; /* can do nothing */ - - if (vio_fifo_empty(cli->vf->obuf)) - { - assert(!cli->out_active ) ; - return ; /* need do nothing if is empty */ - } ; - - 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_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. - * - * Note that apart from CMD_CLOSE, don't really care what the return was. Any - * diagnostics or other action must be dealt with elsewhere (as part of the - * command execution. - * - * Note that everything proceeds as if there is some output. So after every - * command goes through at least one write_ready event. - * - * This ensures some multiplexing at the command level. + * Want another command line from the CLI. * - * It also means that the decision about whether there is anything to output - * is left to the output code. - - - - */ -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. + * If in_progress this <=> any previous command has completed, and output may + * be active writing the results away. Will: * - * Some commands are VLI specific... and end up back here. - */ - -/*------------------------------------------------------------------------------ - * Authentication of vty + * * clear cli->dispatched + * * clear cli->in_progress + * * clear cli->blocked * - * Note that if the AUTH_NODE password fails too many times, the terminal is - * closed. + * May be in more_wait state -- so avoids touching that. * - * Returns: CMD_SUCCESS -- OK, one way or another - * CMD_WARNING -- with error message sent to output - * CMD_CLOSE -- too many password failures + * If not in_progress, then if dispatched, that is a new command ready to pass + * to the command loop -- which we do here, and set cli->in_progress. */ extern cmd_return_code_t -uty_cli_auth(vty_cli cli) +uty_cli_want_command(vty_cli cli, cmd_action action, cmd_context context) { - 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() ; - 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) + if (cli->in_progress) { - passwd = host.password ; - encrypted = host.password_encrypted ; + /* Previous command has completed + * + * Make sure state reflects the fact that we are now waiting for a + * command. + */ + cli->dispatched = false ; /* no longer have a dispatched command */ + cli->in_progress = false ; /* command complete */ + cli->blocked = false ; /* releases possibly blocked command */ + cli->paused = false ; /* override paused state */ + + *cli->context = *context ; /* make sure is up to date */ + cli->auth_context = ( (cli->context->node == AUTH_NODE) + || (cli->context->node == AUTH_ENABLE_NODE) ) ; - if (host.advanced) - next_node = host.enable ? VIEW_NODE : ENABLE_NODE; + /* If the output is owned by command output, then set flush flag, so + * that when buffers empty, the output will be released. + * + * If the output side is not owned by command output, wipe any temporary + * prompt. + * + * In any case, kick write_ready to ensure output clears and prompt is + * written and so on. + */ + if (cli->out_active) + cli->flush = true ; else - next_node = VIEW_NODE; - } - else if ((exec->to_do & ~cmd_do_mask) == cmd_do_auth_enable) - { - passwd = host.enable ; - encrypted = host.enable_encrypted ; + uty_cli_draw(cli) ; - next_node = ENABLE_NODE; + uty_term_set_readiness(cli->vf, write_ready) ; } - else - zabort("unknown to_do_value") ; - - /* Check against selected password (if any) */ - if (passwd != NULL) + else if (cli->dispatched) { - 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; + /* New command has been dispatched -- can now pass that to the + * command loop -- setting it in_progress. + */ + assert(cli->dispatch->to_do != cmd_do_nothing) ; + cmd_action_take(action, cli->dispatch) ; - return CMD_SUCCESS ; /* <<< SUCCESS <<<<<<<< */ - } ; + cli->in_progress = true ; } ; - /* Password failed -- or none set ! */ - cli->password_fail++ ; - - ret = CMD_SUCCESS ; /* OK so far */ - - 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 ; - } ; - } ; + return cli->in_progress ? CMD_SUCCESS : CMD_WAITING ; +} ; - return ret ; +/*------------------------------------------------------------------------------ + * Start pause timer and set paused. + */ +static void +uty_cli_pause_start(vty_cli cli) +{ + qtimer_set(cli->pause_timer, qt_add_monotonic(QTIME(0.2)), NULL) ; + cli->paused = true ; } ; /*============================================================================== @@ -944,13 +897,13 @@ uty_cli_auth(vty_cli cli) * 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 - * more_wait flag and clears the out_active flag. + * more_wait and more_enter flags. * * 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 * "--more--" response which was typed before the prompt was issued. * - * The cli_blocked flag indicates that the CLI is in this first stage. + * The more_enter flag indicates that the CLI is in this first stage. */ /*------------------------------------------------------------------------------ @@ -963,49 +916,10 @@ uty_cli_enter_more_wait(vty_cli cli) { VTY_ASSERT_LOCKED() ; - assert(cli->out_active && !cli->more_wait) ; - - uty_cli_wipe(cli, 0) ; /* make absolutely sure that command line is - wiped before change the CLI state */ + assert(cli->out_active && !cli->more_wait && !cli->drawn) ; - cli->out_active = false ; /* stop output pro tem */ - cli->more_wait = true ; /* new state */ - cli->more_active = true ; /* drawing the "--more--" */ - - uty_cli_draw(cli) ; /* draw the "--more--" */ -} ; - -/*------------------------------------------------------------------------------ - * Exit the "--more--" CLI. - * - * Wipes the "--more--" prompt. - * - * This is used when the user responds to the prompt. - * - * It is also used when the vty is "half-closed". In this case, it is (just) - * possible that the '--more--' prompt is yet to be completely written away, - * so: - * - * * 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_cli cli) -{ - VTY_ASSERT_LOCKED() ; - - assert(cli->more_wait) ; - - uty_cli_wipe(cli, 0) ; /* wipe the prompt ('--more--') - before changing the CLI state */ - - cli->more_wait = false ; /* exit more_wait */ - cli->more_active = false ; /* tidy */ - cli->out_active = true ; /* re-enable output */ + cli->more_wait = true ; /* new state */ + cli->more_enter = true ; /* drawing the "--more--" etc. */ } ; /*------------------------------------------------------------------------------ @@ -1018,92 +932,184 @@ uty_cli_exit_more_wait(vty_cli cli) * * EOF on input causes immediate exit from cli_more_state. * - * Returns: read_ready -- waiting to steal a keystroke - * now_ready -- just left cli_more_wait - * not_ready -- otherwise + * Returns: read_ready -- waiting to steal a keystroke + * write_ready -- waiting to draw or undraw the "--more--" prompt + * not_ready -- otherwise */ static vty_readiness_t uty_cli_more_wait(vty_cli cli) { - struct keystroke steal ; + keystroke_t steal ; + bool cancel ; VTY_ASSERT_LOCKED() ; assert(cli->more_wait) ; /* must be in more_wait state ! */ + if (cli->paused || cli->mon_active) + return not_ready ; + /* Deal with the first stage of "--more--" */ - if (cli->more_active) + if (cli->more_enter) { int get ; + uty_cli_draw_if_required(cli) ; /* draw the "--more--" */ + /* 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(cli->cbuf)) return write_ready ; - cli->more_active = false ; + cli->more_enter = false ; /* empty the input buffer into the keystroke stream */ do { get = uty_term_read(cli->vf, NULL) ; } while (get > 0) ; + + return read_ready ; } ; /* 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. + * + * If no complete keystroke available to steal, returns ks_null. + * + * If has hit EOF or timeout (or error etc), returns knull_eof. + */ + uty_term_read(cli->vf, steal) ; + + /* If nothing stolen, make sure prompt is drawn and wait for more + * input. + * + * If anything at all has been stolen, then continue or cancel. + */ + cancel = false ; + switch(steal->type) { - /* The read fetches a reasonable lump from the I/O -- so if there - * is a complete keystroke available, expect to get it. - * - * If no complete keystroke available to steal, returns ks_null. - * - * If has hit EOF (or error etc), returns knull_eof. - */ - uty_term_read(cli->vf, &steal) ; + case ks_null: + switch(steal->value) + { + case knull_not_eof: + // TODO need to refresh "--more--" in case of monitor ?? + return read_ready; /* <<< exit: no keystroke */ + break ; - /* 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(cli)) /* "--more--" if req. */ - { - cli->more_active = true ; /* written "--more--" again */ - return write_ready ; - } - else - return read_ready ; - } ; + case knull_eof: + case knull_timed_out: + cancel = true ; + break ; - /* Stolen a keystroke -- a (very) few terminate all output */ - if (steal.type == ks_char) - { - switch (steal.value) + default: + break ; + } ; + break ; + + case ks_char: + switch (steal->value) { case CONTROL('C'): case 'q': case 'Q': - uty_out_clear(cli->vf->vio) ; + cancel = true ; break; - default: /* everything else, thrown away */ + default: break ; } ; - } ; + break ; + + default: + break ; } ; /* End of "--more--" process * - * Wipe out the prompt and update state. + * Wipe out the prompt (unless "cancel") and update state. * * Return write_ready to tidy up the screen and, unless cleared, write * some more. */ - uty_cli_exit_more_wait(cli) ; + if (cancel) + { + uty_out_clear(cli->vf->vio) ; + vio_lc_clear(cli->olc) ; /* clear & reset counter */ + uty_cli_cancel(cli) ; + } + else + { + vio_lc_counter_reset(cli->olc) ; + uty_cli_wipe(cli, 0) ; + } ; + + cli->more_wait = false ; /* exit more_wait */ + cli->more_enter = false ; /* tidy */ + + return write_ready ; +} ; + +/*============================================================================== + * Monitor output. + * + * To prepare for monitor output, wipe as much as is necessary for the + * monitor line to appear correctly. + * + * After monitor output, may need to do two things: + * + * * if the output was incomplete, place the rump in the CLI buffer, + * so that: + * + * a. don't mess up the console with partial lines + * + * b. don't lose part of a message + * + * c. act as a brake on further monitor output -- cannot do any more + * until the last, part, line is dealt with. + * + * * restore the command line, unless it is empty ! + */ + + /*----------------------------------------------------------------------------- + * Prepare for new monitor output line. + * + * Wipe any existing command line. + */ +extern void +uty_cli_pre_monitor(vty_cli cli) +{ + VTY_ASSERT_LOCKED() ; + + uty_cli_wipe(cli, 0) ; + + cli->mon_active = true ; /* block cli & enable empty of fifo */ +} ; + +/*------------------------------------------------------------------------------ + * Recover from monitor line output. + * + * If monitor line failed to complete, append the rump to the CLI buffer. + * + * If have a non-empty command line, or is cli_more_wait, redraw the command + * line. + * + * Returns: 0 => rump was empty and no command line stuff written + * > 0 => rump not empty or some command line stuff written + */ +extern void +uty_cli_post_monitor(vty_cli cli) +{ + VTY_ASSERT_LOCKED() ; + + uty_cli_pause_start(cli) ; /* do not draw prompt immediately */ - return now_ready ; + cli->more_enter = cli->more_wait ; + /* revert to more_enter state */ + + cli->mon_active = false ; /* unblock cli & turn off output */ } ; /*============================================================================== @@ -1116,14 +1122,9 @@ uty_cli_more_wait(vty_cli cli) * * 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. + * keyboard then may need to have intermediate buffering. * * No actual I/O takes place here-- all "output" is to cli->cbuf - * - * 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. */ enum { cli_rep_count = 32 } ; @@ -1142,12 +1143,11 @@ CONFIRM(sizeof(telnet_backspaces) == sizeof(cli_rep_char)) ; /* 12345678901234567890123456789012 */ static cli_rep telnet_spaces = " " ; static cli_rep telnet_dots = "................................" ; +static cli_rep telnet_stars = "********************************" ; 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_n(vty_cli cli, cli_rep_char chars, int n) ; /*------------------------------------------------------------------------------ @@ -1175,41 +1175,11 @@ uty_cli_out_clear(vty_cli cli) } ; /*------------------------------------------------------------------------------ - * CLI VTY output -- echo user input - * - * Do nothing if echo suppressed (eg in AUTH_NODE) or not write_open - */ -static void -uty_cli_echo(vty_cli cli, const char *this, size_t len) -{ - VTY_ASSERT_LOCKED() ; - - if (!cli->echo_suppress) - uty_cli_write(cli, this, len) ; -} - -/*------------------------------------------------------------------------------ - * CLI VTY output -- echo 'n' characters using a cli_rep_char string - * - * Do nothing if echo suppressed (eg in AUTH_NODE) - */ -static void -uty_cli_echo_n(vty_cli cli, cli_rep_char chars, int n) -{ - VTY_ASSERT_LOCKED() ; - - if (!cli->echo_suppress) - uty_cli_write_n(cli, chars, n) ; -} - -/*------------------------------------------------------------------------------ * CLI VTY output -- cf write() */ extern void uty_cli_write(vty_cli cli, const char *this, int len) { - VTY_ASSERT_LOCKED() ; - vio_fifo_put_bytes(cli->cbuf, this, len) ; } ; @@ -1249,8 +1219,9 @@ uty_cli_write_s(vty_cli cli, const char *str) } ; /*============================================================================== - * Standard Messages + * Prompts and responses */ +static void uty_cli_goto_end_if_drawn(vty_cli cli) ; /*------------------------------------------------------------------------------ * Send newline to the console. @@ -1260,6 +1231,8 @@ uty_cli_write_s(vty_cli cli, const char *str) extern void uty_cli_out_newline(vty_cli cli) { + uty_cli_goto_end_if_drawn(cli) ; + uty_cli_write(cli, telnet_newline, 2) ; cli->drawn = false ; cli->dirty = false ; @@ -1288,6 +1261,42 @@ uty_cli_out_wipe_n(vty_cli cli, int n) } ; /*------------------------------------------------------------------------------ + * If the command line is drawn, then show that it has been cancelled. + * + * If the command line is dirty, then cancel it and start new line. + * + * Sets: cli_drawn = false + * cli_dirty = false + */ +static void +uty_cli_cancel(vty_cli cli) +{ + if (cli->drawn || cli->dirty) + { + uty_cli_goto_end_if_drawn(cli) ; + uty_cli_write_s(cli, " ^C" TELNET_NEWLINE) ; + } ; + + cli->drawn = false ; + cli->dirty = false ; +} ; + +/*------------------------------------------------------------------------------ + * If the command line is drawn and the cursor is not at the end of the line, + * move the physical cursor to the end of the line. + * + * Assumes about to issue newline. + */ +static void +uty_cli_goto_end_if_drawn(vty_cli cli) +{ + ulen after ; + + if (cli->drawn && ( (after = qs_after_cp_nn(cli->cl)) != 0 )) + uty_cli_write(cli, qs_cp_char_nn(cli->cl), after) ; +} ; + +/*------------------------------------------------------------------------------ * Send response to the given cmd_do * * If no command is in progress, then will send newline to signal that the @@ -1299,18 +1308,20 @@ uty_cli_out_wipe_n(vty_cli cli, int n) static const char* cli_response [2][cmd_do_count] = { { /* when not waiting for previous command to complete */ - [cmd_do_command] = "", - [cmd_do_ctrl_c] = "^C", - [cmd_do_ctrl_d] = "^D", - [cmd_do_ctrl_z] = "^Z", - [cmd_do_eof] = "^*" + [cmd_do_command] = "", + [cmd_do_ctrl_c] = "^C", + [cmd_do_ctrl_d] = "^D", + [cmd_do_ctrl_z] = "^Z", + [cmd_do_eof] = "^*", + [cmd_do_timed_out] = "^!" }, { /* when waiting for a previous command to complete */ - [cmd_do_command] = "^", - [cmd_do_ctrl_c] = "^C", - [cmd_do_ctrl_d] = "^D", - [cmd_do_ctrl_z] = "^Z", - [cmd_do_eof] = "^*" + [cmd_do_command] = "^", + [cmd_do_ctrl_c] = "^C", + [cmd_do_ctrl_d] = "^D", + [cmd_do_ctrl_z] = "^Z", + [cmd_do_eof] = "^*", + [cmd_do_timed_out] = "^!" } } ; @@ -1320,16 +1331,16 @@ uty_cli_response(vty_cli cli, cmd_do_t to_do) const char* str ; int len ; - if ((to_do == cmd_do_nothing) || (cli->vf->vin_state != vf_open)) - return ; + assert((to_do != cmd_do_nothing) && cli->drawn + && (qs_cp_nn(cli->cl) == qs_len_nn(cli->cl))) ; str = (to_do < cmd_do_count) - ? cli_response[cli->in_progress ? 1 : 0][to_do] : NULL ; + ? cli_response[cli->dispatched ? 1 : 0][to_do] : NULL ; assert(str != NULL) ; len = uty_cli_write_s(cli, str) ; - if (cli->in_progress) + if (cli->dispatched) { cli->extra_len = len ; uty_cli_write_n(cli, telnet_backspaces, len) ; @@ -1341,28 +1352,6 @@ uty_cli_response(vty_cli cli, cmd_do_t to_do) } ; /*------------------------------------------------------------------------------ - * Send various messages with trailing newline. - */ -#if 0 -static void -uty_cli_out_CMD_ERR_AMBIGUOUS(vty_cli cli) -{ - uty_cli_write_s(cli, "% " MSG_CMD_ERR_AMBIGUOUS ".") ; - uty_cli_out_newline(cli) ; -} ; - -static void -uty_cli_out_CMD_ERR_NO_MATCH(vty_cli cli) -{ - 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 @@ -1393,7 +1382,7 @@ uty_cli_wipe(vty_cli cli, int len) a = cli->extra_len ; b = cli->prompt_len ; - if (!cli->echo_suppress && !cli->more_wait) + if (!cli->more_wait) { a += qs_len_nn(cli->cl) - qs_cp_nn(cli->cl) ; b += qs_cp_nn(cli->cl) ; @@ -1436,17 +1425,6 @@ uty_cli_draw_if_required(vty_cli cli) } ; /*------------------------------------------------------------------------------ - * Draw prompt etc for the current vty node. - * - * See uty_cli_draw_this() - */ -static void -uty_cli_draw(vty_cli cli) -{ - uty_cli_draw_this(cli, cli->node) ; -} ; - -/*------------------------------------------------------------------------------ * Draw prompt and entire command line, leaving current position where it * should be. * @@ -1458,21 +1436,20 @@ uty_cli_draw(vty_cli cli) * * * if is closing, draw nothing -- wipes the current line * - * * if is cli_more_wait, draw the "--more--" prompt + * * if is more_wait, draw the "--more--" prompt * - * * if is cmd_in_progress, draw the vestigial prompt. + * * if is dispatched, draw the vestigial prompt. * * By the time the current command completes, the node may have changed, so * the current prompt may be invalid. * - * Sets: cli_drawn = true - * cli_dirty = false - * cli_prompt_len = length of prompt used - * cli_extra_len = 0 - * cli_echo_suppress = (AUTH_NODE or AUTH_ENABLE_NODE) + * Sets: cli->drawn = true + * cli->dirty = false + * cli->prompt_len = length of prompt used + * cli->extra_len = 0 */ static void -uty_cli_draw_this(vty_cli cli, enum node_type node) +uty_cli_draw(vty_cli cli) { const char* prompt ; size_t l_len ; @@ -1494,7 +1471,7 @@ uty_cli_draw_this(vty_cli cli, enum node_type node) p_len = strlen(prompt) ; l_len = 0 ; } - else if (cli->in_progress) + else if (cli->dispatched) { /* If there is a queued command, the prompt is a minimal affair. */ prompt = "~ " ; @@ -1510,6 +1487,8 @@ uty_cli_draw_this(vty_cli cli, enum node_type node) * * If the host name changes, the cli_prompt_set flag is cleared. */ + node_type_t node = cli->context->node ; + if ((node != cli->prompt_node) || (host.name_gen != cli->prompt_gen)) { const char* prompt ; @@ -1521,7 +1500,8 @@ uty_cli_draw_this(vty_cli cli, enum node_type node) prompt = "%s ???: " ; } ; - qs_printf(cli->prompt_for_node, prompt, host.name); + cli->prompt_for_node = + qs_printf(cli->prompt_for_node, prompt, host.name) ; cli->prompt_node = node ; cli->prompt_gen = host.name_gen ; @@ -1539,134 +1519,60 @@ uty_cli_draw_this(vty_cli cli, enum node_type node) /* Set about writing the prompt and the line */ cli->drawn = true ; cli->extra_len = false ; - cli->echo_suppress = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ; - cli->prompt_len = p_len ; + cli->prompt_len = p_len ; uty_cli_write(cli, prompt, p_len) ; if (l_len != 0) { - uty_cli_write(cli, qs_char(cli->cl), l_len) ; + if (cli->auth_context) + uty_cli_write_n(cli, telnet_stars, l_len) ; + else + 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)) ; } ; } ; /*============================================================================== - * Monitor output. - * - * To prepare for monitor output, wipe as much as is necessary for the - * monitor line to appear correctly. - * - * After monitor output, may need to do two things: - * - * * if the output was incomplete, place the rump in the CLI buffer, - * so that: - * - * a. don't mess up the console with partial lines - * - * b. don't lose part of a message - * - * c. act as a brake on further monitor output -- cannot do any more - * until the last, part, line is dealt with. - * - * * restore the command line, unless it is empty ! - */ - - /*----------------------------------------------------------------------------- - * Prepare for new monitor output line. - * - * Wipe any existing command line. - */ -extern void -uty_cli_pre_monitor(vty_cli cli, size_t len) -{ - VTY_ASSERT_LOCKED() ; - - uty_cli_wipe(cli, len) ; -} ; - -/*------------------------------------------------------------------------------ - * Recover from monitor line output. - * - * If monitor line failed to complete, append the rump to the CLI buffer. - * - * If have a non-empty command line, or is cli_more_wait, redraw the command - * line. - * - * Returns: 0 => rump was empty and no command line stuff written - * > 0 => rump not empty or some command line stuff written - */ -extern int -uty_cli_post_monitor(vty_cli cli, const char* buf, size_t len) -{ - VTY_ASSERT_LOCKED() ; - - if (len != 0) - uty_cli_write(cli, buf, len) ; - - if (cli->more_wait || (qs_len_nn(cli->cl) != 0)) - { - uty_cli_draw(cli) ; - ++len ; - } ; - - return len ; -} ; - -/*============================================================================== * Command line processing loop */ - -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 bool -uty_cli_get_keystroke(vty_cli cli, keystroke stroke) -{ - int got ; - - do - { - if (keystroke_get(cli->key_stream, stroke)) - return true ; - - got = uty_term_read(cli->vf, NULL) ; /* not stealing */ - } while (got > 0) ; - - return false ; -} ; +static cmd_do_t uty_cli_get_keystroke(vty_cli cli, keystroke stroke) ; +static void uty_cli_update_line(vty_cli cli, uint rc) ; + +static void uty_cli_insert (qstring cl, const char* chars, int n) ; +static int uty_cli_forwards(qstring cl, int n) ; +static int uty_cli_backwards(qstring cl, int n) ; +static void uty_cli_del_forwards(qstring cl, int n) ; +static void uty_cli_del_backwards(qstring cl, int n) ; +static void uty_cli_bol(qstring cl) ; +static void uty_cli_eol(qstring cl) ; +static int uty_cli_word_forwards_delta(qstring cl) ; +static void uty_cli_word_forwards(qstring cl) ; +static int uty_cli_word_backwards_delta(qstring cl) ; +static void uty_cli_word_backwards(qstring cl) ; +static void uty_cli_del_word_forwards(qstring cl) ; +static void uty_cli_del_word_backwards(qstring cl) ; +static void uty_cli_del_to_eol(qstring cl) ; +static void uty_cli_clear_line(qstring cl) ; +static void uty_cli_transpose_chars(qstring cl) ; +static void uty_cli_complete_command (vty_cli cli) ; +static void uty_cli_describe_command (vty_cli cli) ; + +enum hist_step +{ + hist_previous = -1, + hist_next = +1, +}; + +static void uty_cli_hist_use(vty_cli cli, enum hist_step) ; /*------------------------------------------------------------------------------ * Process keystrokes until run out of input, or get something to cmd_do. * - * If required, draw the prompt and command line. + * Expects the command line to have been drawn. * * Process keystrokes until run out of stuff to do, or have a "command line" * that must now be executed. @@ -1675,53 +1581,41 @@ uty_cli_get_keystroke(vty_cli cli, keystroke stroke) * ready to read and return. (To give some "sharing".) * * Returns: cmd_do_xxxx - * - * When returns the cl is '\0' terminated. */ static cmd_do_t -uty_cli_process(vty_cli cli, node_type_t node) +uty_cli_process(vty_cli cli) { - struct keystroke stroke ; + keystroke_t stroke ; uint8_t u ; - bool auth ; cmd_do_t to_do ; - auth = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ; + qs_copy(cli->cls, cli->cl) ; + + assert(cli->drawn) ; /* Now process as much as possible of what there is */ - to_do = cmd_do_nothing ; - while (to_do == cmd_do_nothing) + do { - if (!cli->drawn) - uty_cli_draw_this(cli, node) ; - - if (!uty_cli_get_keystroke(cli, &stroke)) - { - to_do = (stroke.value == knull_eof) ? cmd_do_eof : cmd_do_nothing ; - break ; - } ; + to_do = uty_cli_get_keystroke(cli, stroke) ; - if (stroke.flags != 0) - { - /* TODO: deal with broken keystrokes */ - continue ; - } ; + if (to_do != cmd_do_keystroke) + break ; - switch (stroke.type) + switch (stroke->type) { /* Straightforward character -----------------------------------*/ /* Note: only interested in 8-bit characters ! */ case ks_char: - u = (uint8_t)stroke.value ; + u = (uint8_t)stroke->value ; - switch (stroke.value) + switch (stroke->value) { case CONTROL('A'): - uty_cli_bol (cli); + uty_cli_bol(cli->cl) ; break; case CONTROL('B'): - uty_cli_backwards(cli, 1); + uty_cli_backwards(cli->cl, 1); break; case CONTROL('C'): @@ -1729,47 +1623,44 @@ uty_cli_process(vty_cli cli, node_type_t node) break ; case CONTROL('D'): - if (auth) - to_do = cmd_do_ctrl_d ; /* Exit on ^D ..................*/ - else - uty_cli_del_forwards(cli, 1); + uty_cli_del_forwards(cli->cl, 1); break; case CONTROL('E'): - uty_cli_eol (cli); + uty_cli_eol(cli->cl); break; case CONTROL('F'): - uty_cli_forwards(cli, 1); + uty_cli_forwards(cli->cl, 1); break; case CONTROL('H'): case 0x7f: - uty_cli_del_backwards(cli, 1); + uty_cli_del_backwards(cli->cl, 1); break; case CONTROL('K'): - uty_cli_del_to_eol (cli); + uty_cli_del_to_eol(cli->cl); break; case CONTROL('N'): - uty_cli_hist_next (cli); + uty_cli_hist_use(cli, hist_next) ; break; case CONTROL('P'): - uty_cli_hist_previous(cli); + uty_cli_hist_use(cli, hist_previous) ; break; case CONTROL('T'): - uty_cli_transpose_chars (cli); + uty_cli_transpose_chars(cli->cl) ; break; case CONTROL('U'): - uty_cli_clear_line(cli); + uty_cli_clear_line(cli->cl) ; break; case CONTROL('W'): - uty_cli_del_word_backwards (cli); + uty_cli_del_word_backwards(cli->cl) ; break; case CONTROL('Z'): @@ -1782,49 +1673,45 @@ uty_cli_process(vty_cli cli, node_type_t node) break ; case '\t': - if (auth) - uty_cli_insert (cli, " ", 1) ; - else if (cmd_token_position(cli->parsed, cli->cl)) - uty_cli_insert (cli, " ", 1) ; + if (cmd_token_position(cli->parsed, cli->cl)) + uty_cli_insert (cli->cl, " ", 1) ; else - uty_cli_complete_command (cli, node); + uty_cli_complete_command(cli); break; case '?': - 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) ; + if (cmd_token_position(cli->parsed, cli->cl)) + uty_cli_insert(cli->cl, (char*)&u, 1) ; else - uty_cli_describe_command (cli, node); + uty_cli_describe_command(cli); break; default: - if ((stroke.value >= 0x20) && (stroke.value < 0x7F)) - uty_cli_insert (cli, (char*)&u, 1) ; + if ((stroke->value >= 0x20) && (stroke->value < 0x7F)) + uty_cli_insert(cli->cl, (char*)&u, 1) ; break; } break ; /* ESC X -------------------------------------------------------------*/ case ks_esc: - switch (stroke.value) + switch (stroke->value) { case 'b': - uty_cli_word_backwards (cli); + uty_cli_word_backwards(cli->cl); break; case 'f': - uty_cli_word_forwards (cli); + uty_cli_word_forwards(cli->cl); break; case 'd': - uty_cli_del_word_forwards (cli); + uty_cli_del_word_forwards(cli->cl); break; case CONTROL('H'): case 0x7f: - uty_cli_del_word_backwards (cli); + uty_cli_del_word_backwards(cli->cl); break; default: @@ -1832,37 +1719,39 @@ uty_cli_process(vty_cli cli, node_type_t node) } ; break ; - /* ESC [ X -----------------------------------------------------------*/ + /* ESC [ ... ---------------------------------------------------------*/ case ks_csi: - if (stroke.len != 0) - break ; /* only recognise 3 byte sequences */ - - switch (stroke.value) - { - case ('A'): /* up arrow */ - uty_cli_hist_previous(cli); - break; - - case ('B'): /* down arrow */ - uty_cli_hist_next (cli); - break; - - case ('C'): /* right arrow */ - uty_cli_forwards(cli, 1); - break; - - case ('D'): /* left arrow */ - uty_cli_backwards(cli, 1); - break; - - default: - break ; - } ; - break ; - - /* Telnet Command ----------------------------------------------------*/ - case ks_iac: - uty_telnet_command(cli->vf, &stroke, false) ; + if (stroke->len == 0) + { + /* ESC [ X */ + switch (stroke->value) + { + case ('A'): /* up arrow */ + uty_cli_hist_use(cli, hist_previous) ; + break; + + case ('B'): /* down arrow */ + uty_cli_hist_use(cli, hist_next) ; + break; + + case ('C'): /* right arrow */ + uty_cli_forwards(cli->cl, 1) ; + break; + + case ('D'): /* left arrow */ + uty_cli_backwards(cli->cl, 1) ; + break; + + default: + break ; + } ; + } + else if (stroke->len == 1) + { + /* ESC [ 3 ~ only ! */ + if ((stroke->value == '~') && (stroke->buf[0] == '3')) + uty_cli_del_forwards(cli->cl, 1) ; + } ; break ; /* Unknown -----------------------------------------------------------*/ @@ -1870,151 +1759,309 @@ uty_cli_process(vty_cli cli, node_type_t node) zabort("unknown keystroke type") ; } ; - } ; + } + while (to_do == cmd_do_keystroke) ; /* Tidy up and return where got to. */ if (to_do != cmd_do_nothing) - uty_cli_eol (cli) ; /* go to the end of the line */ + uty_cli_eol(cli->cl) ; /* go to the end of the line */ + + uty_cli_update_line(cli, qs_cp_nn(cli->cl)) ; return to_do ; } ; -/*============================================================================== - * Command line operations - */ - /*------------------------------------------------------------------------------ - * Insert 'n' characters at current position in the command line + * Update the command line to reflect the difference between old line and the + * new line. * - * Returns number of characters inserted -- ie 'n' + * Leave the screen cursor at the given required cursor position. */ -static int -uty_cli_insert (vty_cli cli, const char* chars, int n) +static void +uty_cli_update_line(vty_cli cli, uint rc) { - int after ; + const char* np ; /* new line */ + const char* sp ; /* screen line */ - VTY_ASSERT_LOCKED() ; + ulen nl ; /* new length */ + ulen sl ; /* screen length */ - assert((qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) && (n >= 0)) ; + ulen sc ; /* screen cursor */ - if (n <= 0) - return n ; /* avoid trouble */ + ulen s, l ; - after = qs_insert(cli->cl, chars, n) ; + assert(cli->drawn) ; - uty_cli_echo(cli, qs_cp_char(cli->cl), after) ; + np = qs_char_nn(cli->cl) ; + nl = qs_len_nn(cli->cl) ; - if ((after - n) != 0) - uty_cli_echo_n(cli, telnet_backspaces, after - n) ; + sp = qs_char_nn(cli->cls) ; + sl = qs_len_nn(cli->cls) ; + sc = qs_cp_nn(cli->cls) ; - qs_move_cp_nn(cli->cl, n) ; + /* Find how many characters are the same. */ + l = (nl <= sl) ? nl : sl ; + s = 0 ; + while ((s < l) && (*(np + s) == *(sp + s))) + ++s ; - return n ; + /* If the screen and new are different lengths, or the strings are not + * the same, then need to draw stuff to correct what is on the screen. + * That will leave the screen cursor at the end of the new line, after + * any spaces required to wipe out excess characters. + * + * Note that sc is the current cursor position on the screen, and we keep + * that up to date as we draw stuff. + */ + if ((nl != sl) || (s != nl)) + { + /* Move back if the screen cursor is beyond the same section */ + if (sc > s) + { + uty_cli_write_n(cli, telnet_backspaces, sc - s) ; + sc = s ; + } ; + + /* Write from cursor to the end of the new line. */ + uty_cli_write(cli, np + sc, nl - sc) ; + sc = nl ; + + /* If the old line was longer, need to wipe out old stuff */ + if (sl > nl) + { + uty_cli_write_n(cli, telnet_spaces, sl - nl) ; + sc = sl ; + } ; + } ; + + /* Now move cursor to the required cursor position */ + if (sc > rc) + uty_cli_write_n(cli, telnet_backspaces, sc - rc) ; + + if (sc < rc) /* => lines unchanged, but cursor moved */ + uty_cli_write(cli, np + sc, rc - sc) ; } ; /*------------------------------------------------------------------------------ - * Overstrike 'n' characters at current position in the command line + * For password: process keystrokes until run out of input, or get something + * to cmd_do. * - * Move current position forwards. + * Similar to uty_cli_auth, except accepts a limited number of keystrokes. * - * Returns number of characters inserted -- ie 'n' + * Returns: cmd_do_xxxx */ -static int -uty_cli_overwrite (vty_cli cli, char* chars, int n) +static cmd_do_t +uty_cli_auth(vty_cli cli) { - VTY_ASSERT_LOCKED() ; + keystroke_t stroke ; + uint8_t u ; + cmd_do_t to_do ; + int olen, nlen ; - assert((qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) && (n >= 0)) ; + /* For auth command lines, cursor is always at the end */ + assert(qs_cp_nn(cli->cl) == qs_len_nn(cli->cl)) ; - if (n > 0) + olen = qs_len_nn(cli->cl) ; + + /* Now process as much as possible of what there is */ + do { - qs_replace(cli->cl, n, chars, n) ; - uty_cli_echo(cli, chars, n) ; + to_do = uty_cli_get_keystroke(cli, stroke) ; - qs_move_cp_nn(cli->cl, n) ; - } ; + if (to_do != cmd_do_keystroke) + break ; - return n ; -} + switch (stroke->type) + { + /* Straightforward character ---------------------------------*/ + /* Note: only interested in 8-bit characters ! */ + case ks_char: + u = (uint8_t)stroke->value ; + + switch (stroke->value) + { + case CONTROL('C'): + to_do = cmd_do_ctrl_c ; /* Exit on ^C */ + break ; + + case CONTROL('D'): + to_do = cmd_do_ctrl_d ; /* Exit on ^D */ + break; + + case CONTROL('H'): + case 0x7F: + uty_cli_del_backwards(cli->cl, 1); + break; + + case CONTROL('U'): + case CONTROL('W'): + uty_cli_clear_line(cli->cl); + break; + + case CONTROL('Z'): + to_do = cmd_do_ctrl_z ; /* Exit on ^Z */ + break; + + case '\n': + case '\r': + to_do = cmd_do_command ; /* Exit on CR or LF */ + break ; + + default: + if ((stroke->value >= 0x20) && (stroke->value < 0x7F)) + uty_cli_insert(cli->cl, (char*)&u, 1) ; + break; + } + break ; + + /* ESC X -----------------------------------------------------*/ + case ks_esc: + switch (stroke->value) + { + case CONTROL('H'): + case 0x7f: + uty_cli_clear_line(cli->cl); + break; + + default: + break; + } ; + break ; + + /* ESC [ ... ---------------------------------------------------*/ + case ks_csi: + break ; + + /* Unknown -----------------------------------------------------*/ + default: + zabort("unknown keystroke type") ; + } ; + } + while (to_do == cmd_do_keystroke) ; + + /* Tidy up and return where got to. */ + + nlen = qs_len_nn(cli->cl) ; + + if (nlen < olen) + uty_cli_out_wipe_n(cli, nlen - olen) ; + if (nlen > olen) + uty_cli_write_n(cli, telnet_stars, nlen - olen) ; + + return to_do ; +} ; /*------------------------------------------------------------------------------ - * Replace 'm' characters at the current position, by 'n' characters and leave - * cursor at the end of the inserted characters. + * Fetch next keystroke, reading from the file if required. * - * Returns number of characters inserted -- ie 'n' + * Returns: cmd_do_t */ -static int -uty_cli_replace(vty_cli cli, int m, const char* chars, int n) +static cmd_do_t +uty_cli_get_keystroke(vty_cli cli, keystroke stroke) { - int a, b ; + while (1) + { + if (keystroke_get(cli->key_stream, stroke)) + { + if (stroke->flags != 0) + { + /* TODO: deal with broken keystrokes */ + } - VTY_ASSERT_LOCKED() ; + if (stroke->type != ks_iac) + return cmd_do_keystroke ; /* have a keystroke */ - assert((qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) && (n >= 0) && (m >= 0)) ; + /* Deal with telnet command, so invisible to upper level */ + uty_telnet_command(cli->vf, stroke, false) ; + } + else + { + int get ; - b = qs_len_nn(cli->cl) - qs_cp_nn(cli->cl) ; - a = qs_replace(cli->cl, m, chars, n) ; + assert(stroke->type == ks_null) ; - uty_cli_echo(cli, qs_cp_char(cli->cl), a) ; + switch (stroke->value) + { + case knull_not_eof: + break ; - if (a < b) - uty_cli_echo_n(cli, telnet_spaces, b - a) ; - else - b = a ; + case knull_eof: + return cmd_do_eof ; - if (b > n) - uty_cli_echo_n(cli, telnet_backspaces, b - n) ; + case knull_timed_out: + return cmd_do_timed_out ; - qs_move_cp_nn(cli->cl, n) ; + default: + zabort("unknown knull_xxx") ; + break ; + } ; - return n ; + get = uty_term_read(cli->vf, NULL) ; /* sets eof in key_stream + if hit eof or error */ + if (get <= 0) + return cmd_do_nothing ; + } ; + } ; } ; -#if 0 +/*============================================================================== + * Command line operations + */ + /*------------------------------------------------------------------------------ - * Insert a word into vty interface with overwrite mode. + * Insert 'n' characters at current position in the command line, leaving + * cursor after the inserted characters. * - * NB: Assumes result will then be the end of the line. - * - * Returns number of characters inserted -- ie length of string + * NB: assumes line will be updated by uty_cli_update_line() */ -static int -uty_cli_word_overwrite (vty_cli cli, char *str) +static void +uty_cli_insert(qstring cl, const char* chars, int n) { - int n ; - VTY_ASSERT_LOCKED() ; + assert((qs_cp_nn(cl) <= qs_len_nn(cl)) && (n >= 0)) ; - n = uty_cli_overwrite(cli, str, strlen(str)) ; + if (n > 0) + { + qs_insert(cl, chars, n) ; + qs_move_cp_nn(cl, n) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Replace 'm' characters at the current position, by 'n' characters and leave + * cursor at the end of the inserted characters. + * + * NB: assumes line will be updated by uty_cli_update_line() + */ +static void +uty_cli_replace(qstring cl, int m, const char* chars, int n) +{ + assert((qs_cp_nn(cl) <= qs_len_nn(cl)) && (n >= 0) && (m >= 0)) ; - qs_set_len_nn(cli->cl, qs_cp_nn(cli->cl)) ; + qs_replace(cl, m, chars, n) ; - return n ; -} -#endif + qs_move_cp_nn(cl, n) ; +} ; /*------------------------------------------------------------------------------ * Forward 'n' characters -- stop at end of line. * * Returns number of characters actually moved + * + * NB: assumes line will be updated by uty_cli_update_line() */ static int -uty_cli_forwards(vty_cli cli, int n) +uty_cli_forwards(qstring cl, int n) { int have ; - VTY_ASSERT_LOCKED() ; - have = qs_after_cp_nn(cli->cl) ; + have = qs_after_cp_nn(cl) ; if (have < n) n = have ; assert(n >= 0) ; - if (n > 0) - { - uty_cli_echo(cli, qs_cp_char(cli->cl), n) ; - qs_move_cp_nn(cli->cl, n) ; - } ; + qs_move_cp_nn(cl, n) ; return n ; } ; @@ -2023,22 +2070,18 @@ uty_cli_forwards(vty_cli cli, int n) * Backwards 'n' characters -- stop at start of line. * * Returns number of characters actually moved + * + * NB: assumes line will be updated by uty_cli_update_line() */ static int -uty_cli_backwards(vty_cli cli, int n) +uty_cli_backwards(qstring cl, int n) { - VTY_ASSERT_LOCKED() ; - - if ((int)qs_cp_nn(cli->cl) < n) - n = qs_cp_nn(cli->cl) ; + if ((int)qs_cp_nn(cl) < n) + n = qs_cp_nn(cl) ; assert(n >= 0) ; - if (n > 0) - { - uty_cli_echo_n(cli, telnet_backspaces, n) ; - qs_move_cp_nn(cli->cl, -n) ; - } ; + qs_move_cp_nn(cl, -n) ; return n ; } ; @@ -2047,86 +2090,69 @@ uty_cli_backwards(vty_cli cli, int 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 + * NB: assumes line will be updated by uty_cli_update_line() */ -static int -uty_cli_move(vty_cli cli, int n) +static void +uty_cli_move(qstring cl, int n) { - VTY_ASSERT_LOCKED() ; - if (n < 0) - return - uty_cli_backwards(cli, -n) ; + uty_cli_backwards(cl, -n) ; if (n > 0) - return + uty_cli_forwards(cli, +n) ; - - return 0 ; + uty_cli_forwards(cl, +n) ; } ; /*------------------------------------------------------------------------------ * Delete 'n' characters -- forwards -- stop at end of line. * - * Returns number of characters actually deleted. + * NB: assumes line will be updated by uty_cli_update_line() */ -static int -uty_cli_del_forwards(vty_cli cli, int n) +static void +uty_cli_del_forwards(qstring cl, int n) { - int after ; int have ; - VTY_ASSERT_LOCKED() ; - - have = qs_after_cp_nn(cli->cl) ; + have = qs_after_cp_nn(cl) ; if (have < n) n = have ; /* cannot delete more than have */ assert(n >= 0) ; - if (n <= 0) - return 0 ; - - after = qs_delete(cli->cl, n) ; - - if (after > 0) - uty_cli_echo(cli, qs_cp_char(cli->cl), after) ; - - uty_cli_echo_n(cli, telnet_spaces, n) ; - uty_cli_echo_n(cli, telnet_backspaces, after + n) ; - - return n ; + if (n > 0) + qs_delete(cl, n) ; } /*------------------------------------------------------------------------------ * Delete 'n' characters before the point. * - * Returns number of characters actually deleted. + * NB: assumes line will be updated by uty_cli_update_line() */ -static int -uty_cli_del_backwards(vty_cli cli, int n) +static void +uty_cli_del_backwards(qstring cl, int n) { - return uty_cli_del_forwards(cli, uty_cli_backwards(cli, n)) ; + uty_cli_del_forwards(cl, uty_cli_backwards(cl, n)) ; } /*------------------------------------------------------------------------------ * Move to the beginning of the line. * - * Returns number of characters moved over. + * NB: assumes line will be updated by uty_cli_update_line() */ -static int -uty_cli_bol (vty_cli cli) +static void +uty_cli_bol(qstring cl) { - return uty_cli_backwards(cli, qs_cp_nn(cli->cl)) ; + qs_set_cp_nn(cl, 0) ; } ; /*------------------------------------------------------------------------------ * Move to the end of the line. * - * Returns number of characters moved over + * NB: assumes line will be updated by uty_cli_update_line() */ -static int -uty_cli_eol (vty_cli cli) +static void +uty_cli_eol(qstring cl) { - return uty_cli_forwards(cli, qs_after_cp_nn(cli->cl)) ; + qs_set_cp_nn(cl, qs_len_nn(cl)) ; } ; /*------------------------------------------------------------------------------ @@ -2137,25 +2163,23 @@ uty_cli_eol (vty_cli cli) * Steps over non-space characters and then any spaces. */ static int -uty_cli_word_forwards_delta(vty_cli cli) +uty_cli_word_forwards_delta(qstring cl) { char* cp ; char* tp ; char* ep ; - VTY_ASSERT_LOCKED() ; ; + cp = qs_cp_char(cl) ; + ep = qs_ep_char(cl) ; - assert(qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) ; - - cp = qs_cp_char(cli->cl) ; - ep = qs_ep_char(cli->cl) ; + assert(cp <= ep) ; tp = cp ; - while ((tp < ep) && (*tp != ' ')) + while ((tp < ep) && (*tp != ' ')) /* step over spaces */ ++tp ; - while ((tp < ep) && (*tp == ' ')) + while ((tp < ep) && (*tp == ' ')) /* step to space */ ++tp ; return tp - cp ; @@ -2165,11 +2189,13 @@ uty_cli_word_forwards_delta(vty_cli cli) * Forward word -- move to start of next word. * * Moves past any non-spaces, then past any spaces. + * + * NB: assumes line will be updated by uty_cli_update_line() */ -static int -uty_cli_word_forwards(vty_cli cli) +static void +uty_cli_word_forwards(qstring cl) { - return uty_cli_forwards(cli, uty_cli_word_forwards_delta(cli)) ; + uty_cli_forwards(cl, uty_cli_word_forwards_delta(cl)) ; } ; /*------------------------------------------------------------------------------ @@ -2181,24 +2207,21 @@ uty_cli_word_forwards(vty_cli cli) * Steps back until next (backwards) character is space, or hits start of line. */ static int -uty_cli_word_backwards_delta(vty_cli cli, int eat_spaces) +uty_cli_word_backwards_delta(qstring cl) { char* cp ; char* tp ; char* sp ; - VTY_ASSERT_LOCKED() ; ; - - assert(qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) ; + assert(qs_cp_nn(cl) <= qs_len_nn(cl)) ; - cp = qs_cp_char(cli->cl) ; - sp = qs_char(cli->cl) ; + cp = qs_cp_char(cl) ; + sp = qs_char(cl) ; tp = cp ; - if (eat_spaces) - while ((tp > sp) && (*(tp - 1) == ' ')) - --tp ; + while ((tp > sp) && (*(tp - 1) == ' ')) + --tp ; while ((tp > sp) && (*(tp - 1) != ' ')) --tp ; @@ -2206,33 +2229,18 @@ uty_cli_word_backwards_delta(vty_cli cli, int eat_spaces) return cp - tp ; } ; -#if 0 -/*------------------------------------------------------------------------------ - * Backward word, but not trailing spaces. - * - * Move back until next (backwards) character is space or start of line. - * - * Returns number of characters stepped over. - */ -static int -uty_cli_word_backwards_pure (vty_cli cli) -{ - return uty_cli_backwards(cli, uty_cli_word_backwards_delta(cli, 0)) ; -} ; -#endif - /*------------------------------------------------------------------------------ * Backward word -- move to start of previous word. * * Moves past any spaces, then move back until next (backwards) character is * space or start of line. * - * Returns number of characters stepped over. + * NB: assumes line will be updated by uty_cli_update_line() */ -static int -uty_cli_word_backwards (vty_cli cli) +static void +uty_cli_word_backwards(qstring cl) { - return uty_cli_backwards(cli, uty_cli_word_backwards_delta(cli, 1)) ; + uty_cli_backwards(cl, uty_cli_word_backwards_delta(cl)) ; } ; /*------------------------------------------------------------------------------ @@ -2240,12 +2248,12 @@ uty_cli_word_backwards (vty_cli cli) * * Deletes any leading spaces, then deletes upto next space or end of line. * - * Returns number of characters deleted. + * NB: assumes line will be updated by uty_cli_update_line() */ -static int -uty_cli_del_word_forwards(vty_cli cli) +static void +uty_cli_del_word_forwards(qstring cl) { - return uty_cli_del_forwards(cli, uty_cli_word_forwards_delta(cli)) ; + uty_cli_del_forwards(cl, uty_cli_word_forwards_delta(cl)) ; } /*------------------------------------------------------------------------------ @@ -2253,73 +2261,69 @@ uty_cli_del_word_forwards(vty_cli cli) * * Deletes any trailing spaces, then deletes upto next space or start of line. * - * Returns number of characters deleted. + * NB: assumes line will be updated by uty_cli_update_line() */ -static int -uty_cli_del_word_backwards(vty_cli cli) +static void +uty_cli_del_word_backwards(qstring cl) { - return uty_cli_del_backwards(cli, uty_cli_word_backwards_delta(cli, 1)) ; + uty_cli_del_backwards(cl, uty_cli_word_backwards_delta(cl)) ; } ; /*------------------------------------------------------------------------------ * Kill rest of line from current point. * - * Returns number of characters deleted. + * NB: assumes line will be updated by uty_cli_update_line() */ -static int -uty_cli_del_to_eol (vty_cli cli) +static void +uty_cli_del_to_eol(qstring cl) { - return uty_cli_del_forwards(cli, qs_after_cp_nn(cli->cl)) ; + qs_set_len_nn(cl, qs_cp_nn(cl)) ; } ; /*------------------------------------------------------------------------------ * Kill line from the beginning. * - * Returns number of characters deleted. + * NB: assumes line will be updated by uty_cli_update_line() */ -static int -uty_cli_clear_line(vty_cli cli) +static void +uty_cli_clear_line(qstring cl) { - uty_cli_bol(cli) ; - return uty_cli_del_to_eol(cli) ; + qs_set_cp_nn(cl, 0) ; + qs_set_len_nn(cl, 0) ; } ; /*------------------------------------------------------------------------------ * Transpose chars before or at the point. * - * Return number of characters affected. + * NB: assumes line will be updated by uty_cli_update_line() */ -static int -uty_cli_transpose_chars(vty_cli cli) +static void +uty_cli_transpose_chars(qstring cl) { - char chars[2] ; + char ch ; char* cp ; - VTY_ASSERT_LOCKED() ; - /* Give up if < 2 characters or at start of line. */ - if ((qs_len_nn(cli->cl) < 2) || (qs_cp_nn(cli->cl) < 1)) - return 0 ; + if ((qs_len_nn(cl) < 2) || (qs_cp_nn(cl) < 1)) + return ; - /* Move back to first of characters to exchange */ - if (qs_cp_nn(cli->cl) == qs_len_nn(cli->cl)) - uty_cli_backwards(cli, 2) ; - else - uty_cli_backwards(cli, 1) ; + /* If we are not at the end, step past the second character */ + if (qs_after_cp_nn(cl) == 0) + qs_move_cp_nn(cl, 1) ; - /* Pick up in the new order */ - cp = qs_cp_char(cli->cl) ; - chars[1] = *cp++ ; - chars[0] = *cp ; + /* Get address of first character */ + cp = qs_cp_char(cl) - 2 ; - /* And overwrite */ - return uty_cli_overwrite(cli, chars, 2) ; + /* swap characters */ + ch = *(cp + 1) ; + *(cp + 1) = *cp ; + *cp = ch ; } ; /*============================================================================== * Command line history handling * - * cli->hist is vector of qstrings + * cli->hist is vector of qstrings (created on demand) * cli->h_now is index of the present time * cli->hp is index of most recent line read back * @@ -2335,7 +2339,8 @@ uty_cli_transpose_chars(vty_cli cli) * * hp == h_now means we are in the present. * - * Cannot step forwards from hp == h_now (into the future !). + * Cannot step forwards from hp == h_now (into the future, which is the + * same as the oldest thing we can remember !). * * 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. @@ -2347,6 +2352,40 @@ uty_cli_transpose_chars(vty_cli cli) * is the same as the new line, apart from whitespace. */ +static inline uint +hp_next(uint hp) +{ + return (hp != (VTY_HIST_COUNT - 1)) ? hp + 1 : 0 ; +} ; + +static inline uint +hp_prev(uint hp) +{ + return (hp != 0) ? hp - 1 : VTY_HIST_COUNT - 1 ; +} ; + +static inline uint +hp_step(uint hp, enum hist_step step) +{ + if (step == hist_previous) + return hp_prev(hp) ; + + if (step == hist_next) + return hp_next(hp) ; + + zabort("invalid hist_step") ; +} ; + +/*------------------------------------------------------------------------------ + * Create cli->hist. + */ +static void +uty_cli_hist_make(vty_cli cli) +{ + cli->hist = vector_init_new(cli->hist, VTY_HIST_COUNT) ; + vector_set_min_length(cli->hist, VTY_HIST_COUNT) ; +} ; + /*------------------------------------------------------------------------------ * Add given command line to the history buffer. * @@ -2358,19 +2397,12 @@ static void uty_cli_hist_add (vty_cli cli, qstring clx) { qstring hist_line ; - int prev ; + int h_prev ; VTY_ASSERT_LOCKED() ; - /* make sure have a suitable history vector */ - vector_set_min_length(cli->hist, VTY_MAXHIST) ; - - /* get the previous command line */ - prev = cli->h_now - 1 ; - if (prev < 0) - prev = VTY_MAXHIST - 1 ; - - hist_line = vector_get_item(cli->hist, prev) ; + if (cli->hist == NULL) + uty_cli_hist_make(cli) ; /* create if required */ /* If the previous line is NULL, that means the history is empty. * @@ -2384,10 +2416,20 @@ uty_cli_hist_add (vty_cli cli, qstring clx) * Otherwise, leave cli->h_now and point hist_line at the most ancient * line in history. */ + h_prev = hp_prev(cli->h_now) ; + + hist_line = vector_get_item(cli->hist, h_prev) ; + if ((hist_line == NULL) || (qs_cmp_sig(hist_line, clx) == 0)) - cli->h_now = prev ; + { + cli->h_now = h_prev ; + cli->h_repeat = true ; /* latest history is a repeat */ + } else - hist_line = vector_get_item(cli->hist, cli->h_now) ; + { + hist_line = vector_get_item(cli->hist, cli->h_now) ; + cli->h_repeat = false ; /* latest history is novel */ + } ; /* Now replace the h_now entry * @@ -2398,12 +2440,8 @@ uty_cli_hist_add (vty_cli cli, qstring 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 */ - cli->h_now++; - if (cli->h_now == VTY_MAXHIST) - cli->h_now = 0; - - cli->hp = cli->h_now; + /* Advance history */ + cli->hp = cli->h_now = hp_next(cli->h_now) ; } ; /*------------------------------------------------------------------------------ @@ -2413,148 +2451,110 @@ uty_cli_hist_add (vty_cli cli, qstring clx) * * Step -1 is into the past (up) * +1 is towards the present (down) + * + * NB: assumes line will be updated by uty_cli_update_line() */ static void -uty_cli_hist_use(vty_cli cli, int step) +uty_cli_hist_use(vty_cli cli, enum hist_step step) { - int hp ; - ulen old_len ; - ulen new_len ; - ulen after ; - ulen back ; + uint hp ; qstring hist_line ; - VTY_ASSERT_LOCKED() ; - assert((step == +1) || (step == -1)) ; + if (cli->hist == NULL) + uty_cli_hist_make(cli) ; /* create if required */ + hp = cli->hp ; - /* Special case of being at the insertion point */ + /* Special case of being at the insertion point (the present) + * + * Cannot step forwards from 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. So if return to the present, + * the cursor returns to its current position. (When lines are added to + * the history, the cursor is at the end of the line.) + */ 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_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 */ - hp += step ; - if (hp < 0) - hp = VTY_MAXHIST - 1 ; - else if (hp >= VTY_MAXHIST) - hp = 0 ; + /* Advance or retreat and get history line. */ + hp = hp_step(hp, step) ; hist_line = vector_get_item(cli->hist, hp) ; /* 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_line == NULL) || (hp == cli->h_now)) return ; + /* Update the history pointer and copy history line to current line. */ cli->hp = hp ; - - /* Move back to the start of the current line */ - uty_cli_bol(cli) ; - - /* Get previous line from history buffer and echo that */ - 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 > new_len) - after = old_len - new_len ; - else - after = 0 ; - - /* 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 (new_len > 0) - uty_cli_echo(cli, qs_char_nn(cli->cl), new_len) ; - - if (after > 0) - uty_cli_echo_n(cli, telnet_spaces, after) ; - - if (back > 0) - uty_cli_echo_n(cli, telnet_backspaces, back) ; - - return ; } ; /*------------------------------------------------------------------------------ - * Use previous history line, if any (up arrow). - */ -static void -uty_cli_hist_previous(vty_cli cli) -{ - uty_cli_hist_use(cli, -1) ; -} - -/*------------------------------------------------------------------------------ - * Use next history line, if any (down arrow). - */ -static void -uty_cli_hist_next(vty_cli cli) -{ - uty_cli_hist_use(cli, +1) ; -} - -/*------------------------------------------------------------------------------ * Show the contents of the history */ extern void uty_cli_hist_show(vty_cli cli) { - int hp ; + uint hp ; + uint h_end ; VTY_ASSERT_LOCKED() ; + if (cli->hist == NULL) + return ; /* if no history */ + + /* We start with the oldest thing we can remember, which means that + * we start by stepping "forwards" from "now". + * + * Until the history buffer fills, there will be a number of NULL entries + * between "now" and the oldest thing in the history. + * + * We do not show the "now" entry, which is not part of history. + * + * We do not show the entry before "now", because that is the current + * executing command, unless that was a repeat of the command before ! + */ hp = cli->h_now ; - while(1) + h_end = cli->h_repeat ? hp : hp_prev(hp) ; + + while (1) { qstring line ; - ++hp ; - if (hp == VTY_MAXHIST) - hp = 0 ; + hp = hp_next(hp) ; - if (hp == cli->h_now) - break ; /* wrapped round to "now" */ + if (hp == h_end) + break ; /* reached end of history */ line = vector_get_item(cli->hist, hp) ; - if (line == NULL) - break ; /* reached limit of history so far */ - - uty_out(cli->vf->vio, " %s\n", qs_string(line)); - } + if (line != NULL) + uty_out(cli->vf->vio, " %s\n", qs_make_string(line)); + } ; } ; /*============================================================================== * Command Completion and Command Description * */ -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) ; +static uint uty_cli_help_parse(vty_cli cli) ; static void uty_cli_complete_keyword(vty_cli cli, const char* keyword) ; static void uty_cli_complete_list(vty_cli cli, vector item_v) ; @@ -2562,9 +2562,12 @@ static void uty_cli_complete_list(vty_cli cli, vector item_v) ; 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) ; - static uint uty_cli_width_to_use(vty_cli cli) ; +static void uty_cli_help_message(vty_cli cli, const char* msg) ; +static void uty_cli_help_newline(vty_cli cli) ; +static void uty_cli_help_finish(vty_cli cli) ; + /*------------------------------------------------------------------------------ * Command completion * @@ -2572,9 +2575,12 @@ static uint uty_cli_width_to_use(vty_cli cli) ; * establish which token the cursor is in. Must NOT call this if the cursor * is in a "special" place. * + * This is called from inside "uty_cli_process()". + * + * NB: assumes line will be updated by uty_cli_update_line() */ static void -uty_cli_complete_command (vty_cli cli, node_type_t node) +uty_cli_complete_command (vty_cli cli) { uint n_items ; cmd_parsed parsed ; @@ -2585,61 +2591,68 @@ uty_cli_complete_command (vty_cli cli, node_type_t node) parsed = cli->parsed ; /* Establish what items may be present at the current token position. */ - n_items = uty_cli_help_parse(cli, node) ; - - if (n_items == 0) /* quit if nothing to consider */ - return ; + n_items = uty_cli_help_parse(cli) ; - if (n_items > 1) /* render list of alternatives */ - return uty_cli_complete_list(cli, parsed->item_v) ; + if (n_items > 1) /* render list of alternatives */ + uty_cli_complete_list(cli, parsed->item_v) ; + else if (n_items == 1) + { + /* One possible item -- one or more possible commands */ + item = vector_get_item(parsed->item_v, 0) ; - /* One possible item -- one or more possible commands */ - item = vector_get_item(parsed->item_v, 0) ; + switch (item->type) + { + case item_null: + zabort("invalid item_null") ; - switch (item->type) - { - case item_null: - zabort("invalid item_null") ; + case item_eol: - case item_eol: + case item_option_word: - case item_option_word: + case item_vararg: - case item_vararg: + case item_word: - case item_word: + case item_ipv6_prefix: + case item_ipv6_address: + case item_ipv4_prefix: + case item_ipv4_address: - case item_ipv6_prefix: - case item_ipv6_address: - case item_ipv4_prefix: - case item_ipv4_address: + case item_range: + uty_cli_describe_list(cli, parsed->item_v) ; + break ; - case item_range: - return uty_cli_describe_list(cli, parsed->item_v) ; + case item_keyword: + uty_cli_complete_keyword(cli, item->str) ; + break ; - case item_keyword: - return uty_cli_complete_keyword(cli, item->str) ; + default: + zabort("unknown item type") ; + } ; + } ; - default: - zabort("unknown item type") ; - } ; + /* If necessary, redraw the command line */ + uty_cli_help_finish(cli) ; } ; /*------------------------------------------------------------------------------ * Command Description */ static void -uty_cli_describe_command (vty_cli cli, node_type_t node) +uty_cli_describe_command (vty_cli cli) { uint n_items ; VTY_ASSERT_LOCKED() ; /* Establish what items may be present at the current token position. */ - n_items = uty_cli_help_parse(cli, node) ; + n_items = uty_cli_help_parse(cli) ; if (n_items > 0) /* render list of possibilities */ uty_cli_describe_list(cli, cli->parsed->item_v) ; + + /* If necessary, redraw the command line */ + uty_cli_help_finish(cli) ; } ; /*------------------------------------------------------------------------------ @@ -2654,7 +2667,7 @@ uty_cli_describe_command (vty_cli cli, node_type_t node) * Returns: number of items to consider. */ static uint -uty_cli_help_parse(vty_cli cli, node_type_t node) +uty_cli_help_parse(vty_cli cli) { const char* msg ; cmd_return_code_t ret ; @@ -2666,22 +2679,25 @@ uty_cli_help_parse(vty_cli cli, node_type_t node) msg = cmd_help_preflight(cli->parsed) ; if (msg != NULL) { - uty_cli_out_message(cli, msg) ; + uty_cli_help_message(cli, msg) ; return 0 ; } ; /* Now see what the cmd_completion can come up with. */ - ret = cmd_completion(cli->parsed, node) ; + ret = cmd_completion(cli->parsed, cli->context) ; if (ret == CMD_ERR_PARSING) { - uint eloc = cli->prompt_len + cli->parsed->eloc ; + if (cli->parsed->eloc >= 0) + { + uint eloc = cli->prompt_len + cli->parsed->eloc ; - uty_cli_out_newline(cli) ; /* clears cli_drawn */ - uty_cli_write_n(cli, telnet_dots, eloc) ; - uty_cli_write_s(cli, "^") ; + uty_cli_help_newline(cli) ; /* clears cli_drawn etc. */ + uty_cli_write_n(cli, telnet_dots, eloc) ; + uty_cli_write_s(cli, "^") ; + } ; - uty_cli_out_message(cli, cli->parsed->emess) ; + uty_cli_help_message(cli, qs_make_string(cli->parsed->emess)) ; return 0 ; } ; @@ -2692,29 +2708,14 @@ uty_cli_help_parse(vty_cli cli, node_type_t node) n_items = vector_length(cli->parsed->item_v) ; if (n_items == 0) - uty_cli_out_message(cli, "command not recognised") ; + uty_cli_help_message(cli, "command not recognised") ; return n_items ; } ; - - - - - - - -static void -uty_cli_out_message(vty_cli cli, const char* msg) -{ - uty_cli_out_newline(cli) ; /* clears cli_drawn */ - uty_cli_write_s(cli, "% ") ; - uty_cli_write_s(cli, msg) ; - uty_cli_out_newline(cli) ; -} ; - - - +/*------------------------------------------------------------------------------ + * Can complete a keyword. + */ static void uty_cli_complete_keyword(vty_cli cli, const char* keyword) { @@ -2722,13 +2723,14 @@ uty_cli_complete_keyword(vty_cli cli, const char* keyword) 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)) ; + uty_cli_move(cli->cl, pre) ; /* move to start of token */ + uty_cli_replace(cli->cl, rep, keyword, strlen(keyword)) ; + assert(ins <= 2) ; if (ins > 0) - uty_cli_insert(cli, " ", ins) ; + uty_cli_insert(cli->cl, " ", ins) ; - uty_cli_move(cli, mov) ; + uty_cli_move(cli->cl, mov) ; return ; } ; @@ -2775,7 +2777,7 @@ uty_cli_complete_list(vty_cli cli, vector item_v) uty_cli_out(cli, "%-*s ", len, item->str) ; } - uty_cli_out_newline(cli) ; + uty_cli_help_newline(cli) ; } ; /*------------------------------------------------------------------------------ @@ -2877,7 +2879,7 @@ uty_cli_describe_list(vty_cli cli, vector item_v) uty_cli_describe_line(cli, str_width, str, dp, ep - dp) ; } ; - uty_cli_out_newline(cli) ; + uty_cli_help_newline(cli) ; } ; /*------------------------------------------------------------------------------ @@ -2890,7 +2892,7 @@ uty_cli_describe_line(vty_cli cli, uint str_width, const char* str, if ((*str == '\0') && (len == 0)) return ; /* quit if nothing to say */ - uty_cli_out_newline(cli) ; + uty_cli_help_newline(cli) ; if (len == 0) uty_cli_out(cli, " %s", str) ; /* left justify */ @@ -2911,3 +2913,51 @@ uty_cli_width_to_use(vty_cli cli) { return (cli->width == 0) ? 60 : cli->width ; } ; + +/*------------------------------------------------------------------------------ + * Move to new line, issue message and leave on new line. + * + * Deals with udating the command line if we are currently on it. + */ +static void +uty_cli_help_message(vty_cli cli, const char* msg) +{ + uty_cli_help_newline(cli) ; /* clears cli->drawn etc. */ + uty_cli_write_s(cli, "% ") ; + uty_cli_write_s(cli, msg) ; + uty_cli_write(cli, telnet_newline, 2) ; +} ; + +/*------------------------------------------------------------------------------ + * If the command line is drawn, make sure it is up to date, leaving cursor + * at the end of the line, and then issue newline. + * + * Clears cli->drawn and cli->dirty. + */ +static void +uty_cli_help_newline(vty_cli cli) +{ + if (cli->drawn) + uty_cli_update_line(cli, qs_len_nn(cli->cl)) ; + + uty_cli_write(cli, telnet_newline, 2) ; + + cli->drawn = false ; + cli->dirty = false ; +} ; + +/*------------------------------------------------------------------------------ + * If the command line help has "undrawn" the command line, then redraw it now + * and make a new copy to cli->cls. + * + * Sets cli->drawn + */ +static void +uty_cli_help_finish(vty_cli cli) +{ + if (!cli->drawn) + { + uty_cli_draw(cli) ; + qs_copy(cli->cls, cli->cl) ; + } ; +} ; diff --git a/lib/vty_cli.h b/lib/vty_cli.h index 94e502f0..78ccc900 100644 --- a/lib/vty_cli.h +++ b/lib/vty_cli.h @@ -33,6 +33,7 @@ #include "vio_fifo.h" #include "vio_lines.h" #include "qstring.h" +#include "qtimers.h" #include "keystroke.h" /*------------------------------------------------------------------------------ @@ -45,9 +46,10 @@ 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 */ + vector hist ; /* embedded */ + uint hp ; /* current place in history */ + uint h_now ; /* the present moment */ + bool h_repeat ; /* latest entry is repeat */ /* Window width/height as reported by Telnet. 0 => unknown */ int width; @@ -67,13 +69,24 @@ struct vty_cli /* The incoming stuff */ keystroke_stream key_stream ; - /* drawn <=> the current prompt and user input occupy the current - * line on the screen. + /* 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. + * This flag <=> the CLI "owns" the screen. This flag + * must be cleared -- by wiping the command line -- before + * any other output can use the screen. + * + * In particular, must be cleared before setting + * out_active -- see below. + * + * dirty <=> the last command output did not end with a newline. + * + * tilde_enabled <=> do not do the "~ " one command line ahead. * * If drawn is true, the following are valid: * + * tilde_prompt -- the prompt is the "~ " + * * prompt_len -- the length of the prompt part. * (will be the "--more--" prompt in cli_more_wait) * @@ -87,6 +100,9 @@ struct vty_cli bool drawn ; bool dirty ; + bool tilde_prompt ; + bool tilde_enabled ; + int prompt_len ; int extra_len ; @@ -95,80 +111,112 @@ struct vty_cli /* "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 ; + qstring prompt_for_node ; /* State of the CLI * - * in_progress -- command dispatched + * dispatched -- command dispatched by CLI + * in_progress -- command taken by the command loop * blocked -- blocked until current command completes - * out_active -- contents of the command FIFO are being written away + * paused -- command dispatched and nothing else happened + * + * mon_active -- there is stuff in the logging monitor buffer + * + * out_active -- contents of the obuf FIFO are being written away + * though may be blocked in more_wait + * + * This flag <=> that the command output "owns" the screen. + * + * While this flag is set, the CLI may not write to the + * screen. + * + * flush -- this flag => out_active. + * + * When the CLI is ready to read the next CLI command, it + * must wait for all command output to complete. This + * flag is set, so that (a) any final but incomplete + * line of command output will be flushed, and (b) to + * signal that out_active must be cleared when all output + * has completed. * * more_wait -- is in "--more--" wait state - * more_active -- more_wait and waiting for "--more--" prompt to be - * written away. + * more_enter -- more_wait and waiting for "--more--" prompt to be + * written away and keystrokes to be consumed. */ + bool dispatched ; bool in_progress ; bool blocked ; + bool paused ; + + bool mon_active ; bool out_active ; + bool flush ; 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 ; + bool more_enter ; /* This is set only if the "--more--" handling is enabled */ bool more_enabled ; + /* Timer for paused state -- multi-threaded only */ + qtimer pause_timer ; + /* 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. + * context -- the node etc. that the CLI is in. This may be some way behind + * the VTY, but is updated when the CLI level command completes. + * + * Note that this is a copy of the state of exec->context when + * uty_want_command() was last called. + * + * auth_context -- true <=> context->node is AUTH_NODE or AUTH_ENABLE_NODE + * + * parsed -- the parsed object used to parse command for cli help * - * to_do -- when current command being prepared is completed (by - * CR/LF or otherwise) this says what there now is to be done. + * 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. + * cl -- current command line being prepared. + * cls -- current command line on the screen * - * clx -- current command line being executed. + * clx -- current command line being executed. + * + * dispatch -- the last action dispatched. * * NB: during command execution vty->buf is set to point at the '\0' * terminated current command line being executed. */ - node_type_t node ; + cmd_context context ; + bool auth_context ; - cmd_do_t to_do ; + cmd_parsed parsed ; + cmd_do_t to_do ; qstring cl ; - qstring clx ; + qstring cls ; - cmd_parsed_t parsed ; /* embedded */ + qstring clx ; - /* CLI line buffering */ - vio_fifo_t cbuf ; /* embedded */ + cmd_action_t dispatch ; - /* CLI line control for command output & "--more--" stuff */ - vio_line_control_t olc ; /* embedded */ + /* CLI line buffering and line control */ + vio_fifo cbuf ; + vio_line_control olc ; } ; +/*------------------------------------------------------------------------------ + * Functions + */ 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 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 cmd_return_code_t uty_cli_want_command(vty_cli cli, cmd_action action, + cmd_context context) ; extern void uty_cli_out(vty_cli cli, const char *format, ...) PRINTF_ATTRIBUTE(2, 3) ; extern void uty_cli_out_newline(vty_cli cli) ; @@ -183,7 +231,12 @@ extern void uty_cli_exit_more_wait(vty_cli cli) ; extern bool uty_cli_draw_if_required(vty_cli cli) ; -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) ; +extern void uty_cli_pre_monitor(vty_cli cli) ; +extern void uty_cli_post_monitor(vty_cli cli) ; + +/*------------------------------------------------------------------------------ + * Pro tem -- "\r\n" string + */ +extern const char* uty_cli_newline ; #endif /* _ZEBRA_VTY_CLI_H */ diff --git a/lib/vty_command.c b/lib/vty_command.c index 26a9ec6a..0ce31fdf 100644 --- a/lib/vty_command.c +++ b/lib/vty_command.c @@ -34,6 +34,7 @@ #include "vty_cli.h" #include "vio_fifo.h" #include "vty_io_file.h" +#include "vty_io_term.h" #include "list_util.h" #include "qstring.h" @@ -44,12 +45,19 @@ /*------------------------------------------------------------------------------ * Prototypes */ -static void uty_show_error_location(vio_vf vf) ; -static void vty_cmd_failed(vty vty, cmd_return_code_t ret) ; + +static void uty_cmd_loop_prepare(vty_io vio) ; +static cmd_return_code_t uty_cmd_hiatus(vty_io vio, cmd_return_code_t ret) ; +static cmd_return_code_t vty_cmd_auth(vty vty, node_type_t* p_next_node) ; +static uint uty_show_error_context(vio_fifo ebuf, vio_vf vf) ; +static uint uty_cmd_failed(vty_io vio, cmd_return_code_t ret) ; +static void uty_cmd_prepare(vty_io vio) ; +static void uty_cmd_config_lock(vty vty) ; +static void uty_cmd_config_lock_check(struct vty *vty, node_type_t node) ; /*============================================================================== * There are two command loops -- cmd_read_config() and cq_process(). These - * functions support those command loops: + * functions support those command loops: TODO update !! * * * uty_cmd_prepare() * @@ -63,7 +71,7 @@ static void vty_cmd_failed(vty vty, cmd_return_code_t ret) ; * * Hands command line over to the vty_cli_nexus message queue. * - * NB: from this point onwards, the vio is vio->cmd_running ! + * NB: from this point onwards, the vio is vio->cmd_running ! TODO * * * vty_cmd_fetch_line() * @@ -87,216 +95,1021 @@ static void vty_cmd_failed(vty vty, cmd_return_code_t ret) ; * * If required, pops any vout(s). * - * * vty_cmd_loop_exit() - * - * When a command has completed, successfully or otherwise, this is - * called to deal with any errors and return control to. - * * + */ + +/*------------------------------------------------------------------------------ + * Prepare to enter the config read command loop. * + * Initialise exec object, and copy required settings from the current vin + * and vout. + */ +extern void +vty_cmd_loop_prepare(vty vty) +{ + VTY_LOCK() ; + + assert(vty->type == VTY_CONFIG_READ) ; + + uty_cmd_loop_prepare(vty->vio) ; /* by vty->type & vty->node */ + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * Enter the command_queue command loop. + */ +extern void +uty_cmd_loop_enter(vty_io vio) +{ + VTY_ASSERT_CLI_THREAD_LOCKED() ; + + assert(vio->vty->type == VTY_TERMINAL) ; + + uty_cmd_loop_prepare(vio) ; /* by vty->type & vty->node */ + + cq_loop_enter(vio->vty, vio->vty->node != NULL_NODE ? CMD_SUCCESS + : CMD_CLOSE) ; +} ; + +/*------------------------------------------------------------------------------ + * Prepare to enter a command loop. * + * Initialise cmd_exec object, and its cmd_context -- given vty->type and + * vty->node. */ +static void +uty_cmd_loop_prepare(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + assert(vio->vty->exec == NULL) ; + assert(vio->state == vc_null) ; + + vio->vty->exec = cmd_exec_new(vio->vty) ; + vio->state = vc_running ; + + if (vio->vty->node > MAX_NON_CONFIG_NODE) + uty_cmd_config_lock(vio->vty) ; /* TODO cannot fail !? */ + + uty_cmd_prepare(vio) ; +} ; /*------------------------------------------------------------------------------ - * After opening or closing a vin/vout object, update the vty->exec context. + * Signal to the command loop that some I/O has completed -- successfully, or + * with some I/O error (including time out). + * + * If the vio is in vc_waiting state -- so will be exec_hiatus -- send message + * so that command loop continues. + * + * Otherwise, if is vc_running and have CMD_IO_ERROR, set the vc_io_error_trap, + * so that the next interaction with the vty (other than output) will signal + * a pending error. + * + * Accepts: + * + * CMD_SUCCESS -- if vc_waiting, passed in. + * otherwise, ignored + * + * CMD_WAITING -- ignored + * + * CMD_IO_ERROR -- if vc_waiting, passed in + * if vc_running, set vc_io_error_trap + * + * CMD_CLOSE -- if vc_waiting, passed in + * if vc_running, set vc_close_trap + * if vc_io_error_trap, set vc_close_trap */ extern void -uty_cmd_prepare(vty_io vio) +uty_cmd_signal(vty_io vio, cmd_return_code_t ret) { - cmd_exec exec = vio->vty->exec ; + VTY_ASSERT_LOCKED() ; + + if (ret == CMD_WAITING) + return ; - exec->parse_type = vio->vin->parse_type ; + assert((ret == CMD_SUCCESS) || (ret == CMD_IO_ERROR) || (ret == CMD_CLOSE)) ; - exec->out_enabled = vio->vout->out_enabled ; - exec->reflect_enabled = vio->vin->reflect_enabled && - vio->vout->out_enabled ; + switch (vio->state) + { + case vc_null: + zabort("invalid vc_null") ; + break ; + + case vc_running: /* ignore CMD_SUCCESS */ + if (ret == CMD_IO_ERROR) + vio->state = vc_io_error_trap ; + if (ret == CMD_CLOSE) + vio->state = vc_close_trap ; + break ; + + case vc_waiting: /* pass in the return code continue */ + vio->state = vc_running ; + cq_continue(vio->vty, ret) ; + break ; + + case vc_io_error_trap: /* ignore CMD_SUCCESS or duplicate */ + if (ret == CMD_CLOSE) /* CMD_IO_ERROR. */ + vio->state = vc_close_trap ; + break ; + + case vc_close_trap: /* ignore CMD_SUCCESS, CMD_IO_ERROR, */ + case vc_stopped: /* and duplicate/too late CMD_CLOSE. */ + break ; + + case vc_closed: + zabort("invalid vc_closed") ; + break ; + + default: + zabort("unknown vio->state") ; + break ; + } ; } ; /*------------------------------------------------------------------------------ - * This pushes the command line into the vty_cli_message queue, where it will - * be parsed and executed etc. + * Reset the command loop. + * + * This is used by uty_close() to try to shut down the command loop. + * + * Does nothing if already closed or never got going. + * + * "curtains" means that the program is being terminated, so no message or + * event handling is running any more, and all threads other than the main + * thread have stopped. This means that whatever the state of the command + * loop, we can terminate it now. Revokes any outstanding messages/events, + * in order to tidy up. * - * NB: from this moment on, vio->cmd_running is true, so even if the vty is - * closed, it cannot be fully closed and then reaped until the flag - * is cleared. + * If not curtains, then push a CMD_CLOSE into the command loop, to bring it + * to a shuddering halt. * - * While vio->cmd_running the command side is responsible for the vty - * and the vty->exec stuff. + * Will leave the vio->state: + * + * vc_running -- a CMD_CLOSE has been passed in (was vc_waiting). + * Will not be the case if "curtains". + * + * vc_close_trap -- command loop is running, but will stop as soon as it + * sees this trap. + * Will not be the case if "curtains". + * + * vc_stopped -- command loop is stopped + * Will be the case if "curtains" (unless vc_null or + * vc_closed). + * Otherwise will only be the case if the loop had already + * stopped. + * + * vc_null -- never been kissed + * vc_closed -- was already closed + * + * No other states are possible. */ -extern cmd_return_code_t -uty_cmd_dispatch(vty_io vio, cmd_do_t to_do, qstring line) +extern void +uty_cmd_loop_close(vty_io vio, bool curtains) { VTY_ASSERT_LOCKED() ; - vio->cmd_running = true ; - uty_cmd_prepare(vio) ; /* make sure */ + if ((vio->state == vc_null) || (vio->state == vc_closed)) + return ; - cq_dispatch(vio->vty, to_do, line) ; + if (curtains) + { + if (!vio->blocking) + cq_revoke(vio->vty) ; /* collect any outstanding message */ - return CMD_WAITING ; + vio->state = vc_stopped ; + } + else + uty_cmd_signal(vio, CMD_CLOSE) ; } ; /*------------------------------------------------------------------------------ - * Handle a "special" command -- anything not cmd_do_command + * Exit command loop. + * + * Final close of the VTY, giving a reason, if required. + */ +extern void +vty_cmd_loop_exit(vty vty) +{ + VTY_LOCK() ; + + VTY_ASSERT_CAN_CLOSE(vty) ; + + /* Make sure no longer holding the config symbol of power */ + uty_cmd_config_lock_check(vty, NULL_NODE) ; + + /* Can now close the vty */ + uty_close(vty->vio, NULL, false) ; /* not curtains */ + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * Handle a "special" command -- anything not cmd_do_command. + * + * These "commands" are related to VTY_TERMINAL CLI only. */ extern cmd_return_code_t vty_cmd_special(vty vty) { cmd_return_code_t ret ; + cmd_do_t to_do ; + node_type_t next_node ; ret = CMD_SUCCESS ; - /* All other nodes... */ - switch (vty->exec->to_do) - { - case cmd_do_nothing: - break ; - case cmd_do_eof: - ret = CMD_CLOSE ; - break ; + to_do = vty->exec->action->to_do ; - case cmd_do_command: - zabort("invalid cmd_do_command") ; - break ; + /* Note that the next node handling is special here... we establish + * the next node explicitly here -- there is no parse operation to preset + * what CMD_SUCCESS next node will be. + */ - case cmd_do_ctrl_d: - zabort("invalid cmd_do_ctrl_d") ; - break ; + vty->node = vty->exec->context->node ; /* as per all commands */ + next_node = vty->exec->context->node ; /* by default. */ - case cmd_do_ctrl_c: - case cmd_do_ctrl_z: - ret = cmd_end(vty) ; - break ; + switch (to_do & cmd_do_mask) + { + case cmd_do_nothing: + break ; + + case cmd_do_eof: + if (vty->type == VTY_TERMINAL) + vty_out(vty, "%% Terminal closed\n") ; + + ret = CMD_CLOSE ; + break ; + + case cmd_do_timed_out: + if (vty->type == VTY_TERMINAL) + vty_out(vty, "%% Terminal timed out\n") ; + + ret = CMD_CLOSE ; + break ; + + case cmd_do_command: + if ((to_do & cmd_do_auth) != 0) + ret = vty_cmd_auth(vty, &next_node) ; + else + zabort("invalid cmd_do_command") ; + break ; + + case cmd_do_ctrl_d: + if ((to_do & cmd_do_auth) != 0) + next_node = cmd_node_exit_to(vty->node) ; + else + zabort("invalid cmd_do_ctrl_d") ; + break ; + + case cmd_do_ctrl_c: + case cmd_do_ctrl_z: + next_node = cmd_node_end_to(vty->node) ; + break ; + + default: + zabort("unknown or invalid cmd_do") ; + } ; - default: /* must now be cmd_do_auth or cmd_do_auth_enable */ - VTY_LOCK() ; - ret = uty_cli_auth(vty->vio->vin->cli) ; - VTY_UNLOCK() ; - } ; + /* Now worry about changing node */ + if (ret == CMD_CLOSE) + next_node = NULL_NODE ; + else if (next_node == NULL_NODE) + ret = CMD_CLOSE ; + if (next_node != vty->exec->context->node) + { + vty->exec->context->node = next_node ; + vty_cmd_config_lock_check(vty, next_node) ; + } ; return ret ; } ; /*------------------------------------------------------------------------------ - * Fetch the next command line to be executed. + * Check that can enter AUTH_ENABLE_NODE. + * + * Must be: VTY_TERMINAL + * + * and: no pipes, in or out -- so talking directly to terminal + * + * and: be VIEW_NODE if there is no enable password. + * + * Note that "can_enable" <=> vin_depth == 1 and VTY_TERMINAL (or other VTY + * that can authenticate). But it may not => vout_depth == 0. + */ +extern cmd_return_code_t +vty_cmd_can_auth_enable(vty vty) +{ + cmd_return_code_t ret ; + + VTY_LOCK() ; + + assert(vty->exec->parsed->nnode == AUTH_ENABLE_NODE) ; + assert((vty->exec->context->onode == VIEW_NODE) || + (vty->exec->context->onode == RESTRICTED_NODE)) ; + + ret = CMD_WARNING ; + + if (vty->type != VTY_TERMINAL) + uty_out(vty->vio, "%% Wrong VTY type (%d) for 'enable'", vty->type) ; + else if ((vty->exec->context->onode != VIEW_NODE) + && (host.enable == NULL)) + uty_out(vty->vio, "%% cannot enable because there is no enable password") ; + else if ((vty->vio->vin_depth != 1) || (vty->vio->vout_depth != 1)) + uty_out(vty->vio, + "%% cannot authenticate for 'enable' in a pipe command") ; + else + { + assert(vty->exec->context->can_auth_enable) ; + ret = CMD_SUCCESS ; + } ; + + VTY_UNLOCK() ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Authentication of vty + * + * Note that if the AUTH_NODE password fails too many times, the vty is + * closed. + * + * Quagga authentication is a touch complicated. The following appear to be + * the rules: + * + * 1. host.no_password_check -- set by "no login" command + * + * Means that a VTY_TERMINAL can start without going through any + * password check -- including when no host.password is configured. + * + * Note that this does not affect the authentication for enable, except + * at startup of a VTY_TERMINAL... + * + * When a VTY_TERMINAL starts: + * + * * if host.restricted_mode -> RESTRICTED_NODE + * + * * else if host.advanced -> ENABLE_NODE * - * If possible, get another command line to execute -- pops pipes as required - * and reflects command line when have one, if required. + * This is whether or not an enable password exists. * - * Action depends on the type of the current input: + * * otherwise -> VIEW_NODE * - * VIN_NONE invalid + * So being in RESTRICTED_NODE <=> was host.no_password_check && + * host.restricted_mode when the VTY_TERMINAL was started. + * + * 2. host.restricted_mode -- set by "anonymous restricted" + * + * Significant only at VTY_TERMINAL start, and only if no_password_check. + * + * NB: if the enable password is NULL, there is not much point in + * RESTRICTED_NODE, since ENABLE_NODE is but one command away. + * + * NB: that behaviour is is modified here... if is in RESTRICTED_MODE, + * will not authenticate AUTH_ENABLE_NODE if there is no enable + * password. + * + * Note that the check is left to this point, so that is completed + * atomically. Elsewhere, will refuse to enter ENABLE_NODE from + * RESTRICTED_NODE if no enable password. By the time we get here + * it is (just) possible that the situation has changed. + * + * 3. host.advanced -- set by "service advanced-vty" + * + * Significant iff there is no enable password, when it sets ENABLE_NODE + * as the start up node (if no_password_check) or post AUTH_NODE node. + * + * 4. host.password -- set by "password xxx" + * + * Unless no_password_check, if there is no password, you cannot start + * a vty. + * + * 5. host.enable -- set by "enable password xxx" + * + * If this is set, then must authenticate against it for vty to reach + * ENABLE_NODE. + * + * If it is not set, then can enter ENABLE_NODE at any time. + * + * If AUTH_ENABLE_NODE fails, falls back to the node we came from -- which has + * been planted in the context for this purpose. (If host.restricted_mode has + * changed since the vty started, could argue this should change where should + * fall back to... but that seems unnecessarily complicated.) + * + * Returns: CMD_SUCCESS -- OK, one way or another + * CMD_WARNING -- with error message sent to output + * CMD_CLOSE -- too many password failures + */ +static cmd_return_code_t +vty_cmd_auth(vty vty, node_type_t* p_next_node) +{ + char *crypt (const char *, const char *); + + char* passwd = NULL ; + bool encrypted = false ; + bool enable = false ; + bool advanced ; + bool pass ; + cmd_return_code_t ret ; + cmd_exec exec ; + cmd_context context ; + + exec = vty->exec ; + context = exec->context ; + + /* Select the password we need to check against. */ + passwd = NULL ; + encrypted = false ; + enable = false ; + advanced = false ; + + pass = false ; + + VTY_LOCK() ; /* while access host.xxx */ + + switch (vty->node) + { + case AUTH_NODE: + passwd = host.password ; + encrypted = host.password_encrypted ; + enable = false ; + + context->onode = NULL_NODE ; /* started from nowhere */ + + if (host.advanced && (host.enable == NULL)) + { + context->tnode = ENABLE_NODE ; + advanced = true ; + } + else + { + context->tnode = VIEW_NODE ; + advanced = false ; + } ; + break ; + + case AUTH_ENABLE_NODE: + passwd = host.enable ; + encrypted = host.enable_encrypted ; + enable = true ; + advanced = false ; + + assert((context->onode == VIEW_NODE) || + (context->onode == RESTRICTED_NODE)) ; + break ; + + default: + zabort("unknown vty->node") ; + break ; + } ; + + VTY_UNLOCK() ; + + /* Check against selected password (if any) */ + if (passwd == NULL) + { + /* Here we reject any attempt to AUTH_NODE against an empty password. + * + * Otherwise, is dealing with the (largely) theoretical case of + * This fails any attempt to AUTH_ENABLE against an empty password + * if was in RESTRICTED_NODE. + * + * This passes the theoretically possible case of enable in VIEW_NODE, + * when there was an enable password set when the enable command was + * executed, but it has since been unset ! + */ + pass = context->onode == VIEW_NODE ; + } + else + { + char* candidate = qs_make_string(exec->action->line) ; + + if (encrypted) + candidate = crypt(candidate, passwd) ; + + pass = (strcmp(candidate, passwd) == 0) ; + } ; + + /* Now worry about the result */ + ret = CMD_SUCCESS ; /* so far, so good */ + + if (pass) + { + *p_next_node = context->tnode ; + + if (enable || advanced) + context->can_enable = true ; + + if (*p_next_node == CONFIG_NODE) + { + ret = vty_cmd_config_lock(vty) ; + if (ret == CMD_WARNING) + *p_next_node = ENABLE_NODE ; + } ; + + exec->password_failures = 0 ; /* forgive any failures */ + } + else + { + bool no_more = false ; + + if (passwd == NULL) + { + /* Cannot possibly authenticate ! */ + no_more = true ; + vty_out(vty, "%% No password is set, cannot authenticate!\n") ; + } + else + { + exec->password_failures++ ; + + if (exec->password_failures >= 3) + { + no_more = true ; + vty_out(vty, "%% Bad passwords, too many failures!\n") ; + + exec->password_failures = 0 ; /* allow further attempts */ + } ; + } ; + + if (no_more) + { + if (!enable) + { + *p_next_node = NULL_NODE ; + ret = CMD_CLOSE ; + } + else + { + *p_next_node = context->onode ; + ret = CMD_WARNING ; + } ; + } ; + } ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Fetch the next command line to be executed. * - * VIN_TERM ) return CMD_EOF - * VIN_VTYSH ) + * Returns: CMD_SUCCESS => OK -- have a line ready to be processed. * - * VIN_FILE ) - * VIN_PIPE ) fetch another line if can. - * VIN_CONFIG ) + * vty->exec->line points at the line + * vty->exec->to_do says what to do with it * - * VIN_DEV_NULL nothing: return CMD_EOF + * or: CMD_WAITING => OK -- but waiting for command line to arrive + * <=> non-blocking * - * Returns: CMD_SUCCESS => OK -- have a line ready to be processed. + * or: CMD_EOF => OK -- but nothing to fetch from the current vin * - * vty->exec->line points at the line. + * Need to close the current vin and pop vin/vout + * as necessary. * - * or: CMD_EOF => nothing to fetch -- note that if pops back to the - * VIN_TERM or VIN_VTYSH level, then will return - * CMD_EOF. + * or: CMD_HIATUS => OK -- but need to close one or more vin/vout + * to adjust stack. * - * If was VIN_TERM or VIN_VTYSH, signals CMD_EOF to - * exit from the command loop. + * or: any other return code from the current vin when it attempts to + * fetch another command line -- including I/O error or timeout. * - * or: CMD_WAITING => OK -- but waiting for command line to arrive. + * NB: can be called from any thread -- because does no closing of files or + * anything other than read/write. * - * or: .... + * TODO -- dealing with states other than vf_open !! */ extern cmd_return_code_t vty_cmd_fetch_line(vty vty) { cmd_return_code_t ret ; vty_io vio ; - qstring* p_line ; + vio_vf vf ; + cmd_exec exec ; VTY_LOCK() ; - vio = vty->vio ; /* once locked */ - p_line = &vty->exec->line ; + vio = vty->vio ; /* once locked */ + exec = vty->exec ; - *p_line = NULL ; - vty->exec->to_do = cmd_do_nothing ; + cmd_action_clear(exec->action) ; /* tidy */ - while (1) - { - vio_vf vf ; + vf = vio->vin ; - vf = vio->vin ; + ret = CMD_SUCCESS ; /* assume all is well */ - if (vf->vin_state != vf_open) + /* Worry about all the things that stop us from being able to fetch the + * next command line. + */ + if ( (vty->vio->state != vc_running) + || (vio->vin_depth < vio->vout->depth_mark) + || (vio->vin_depth > vio->real_depth) ) + ret = CMD_HIATUS ; + else + { + switch (vf->vin_state) { - switch (vf->vin_state) - { - case vf_closed: - case vf_eof: - ret = CMD_EOF ; - break ; + case vf_open: + case vf_eof: + case vf_timed_out: + switch (vf->vin_type) + { + case VIN_NONE: + zabort("invalid VIN_NONE") ; + break ; + + case VIN_TERM: + ret = uty_term_fetch_command_line(vf, exec->action, + exec->context) ; + break ; + + case VIN_VTYSH: + zabort("invalid VIN_VTYSH") ; + break ; + + case VIN_DEV_NULL: + ret = CMD_EOF ; + break ; + + case VIN_FILE: + case VIN_CONFIG: + ret = uty_file_fetch_command_line(vf, exec->action) ; + break ; + + case VIN_PIPE: + ret = uty_pipe_fetch_command_line(vf, exec->action) ; + break ; + + default: + zabort("unknown vin_type") ; + } ; + break ; + + case vf_closed: /* TODO treat closed as EOF ? */ + ret = CMD_EOF ; + break ; + + case vf_error: + ret = CMD_IO_ERROR ; + break ; + + case vf_closing: + assert(!vf->blocking) ; + ret = CMD_WAITING ; + break ; + + default: + zabort("invalid vf->vin_state") ; + break ; + } ; + } ; - case vf_error: - ret = CMD_IO_ERROR ; - break ; + VTY_UNLOCK() ; - case vf_closing: - ret = CMD_CLOSE ; - break ; + return ret ; +} ; - default: - zabort("invalid vf->vin_state") ; - break ; - } ; - break ; - } ; +/*------------------------------------------------------------------------------ + * Deal with return code at the "exec_hiatus" point in the command loop. + * + * The command_queue command loop runs until something happens that it + * cannot immediately deal with, at which point it enters "exec_hiatus", and + * this function is called. The command loop will deal with CMD_SUCCESS and + * CMD_EMPTY, but otherwise this function must deal with: + * + * CMD_HIATUS -- something requires attention, eg: + * + * - the vout_depth > vin_depth, so the vout needs to + * be closed and popped. + * + * - the vio->state needs to be checked. + * + * CMD_EOF -- from vty_cmd_fetch_line() => current vin has hit eof, + * and must be closed and popped. + * + * CMD_CLOSE -- from a command return (or otherwise) => must close + * and pop the current vin (same as CMD_EOF, really). + * + * CMD_WAITING -- from vty_cmd_fetch_line() or elsewhere => must go to + * vc_waiting and the command loop MUST exit. + * + * CMD_SUCCESS -- see below + * + * CMD_EMPTY -- should not appear, but is treated as CMD_SUCCESS + * + * anything else -- treated as a command or I/O or other error. + * + * The handling of errors depends on the type of error: + * + * * command errors will cause all levels of the stack other than vin_base + * and vout_base to be closed, and a suitable error message output to the + * vout_base. + * + * Inputs are closed without dealing with any further input and discarding + * any buffered input. + * + * Pending output will be pushed out, and pipe return stuff will be sucked + * in and blown out, until the return signals EOF. + * + * * I/O errors will cause all levels of TODO ... depending on error location ?? + * + * I/O errors also cause all closes to be "final", so pending output is + * attempted -- but will be abandoned if would block. Also, any further + * I/O errors will be discarded. + * + * This function will return: + * + * CMD_SUCCESS => OK -- can try and fetch a command line again. + * + * state == vc_running + * + * CMD_WAITING => OK -- but waiting for input to arrive or for something + * to be completely closed. => non-blocking + * + * state == vc_waiting + * + * CMD_EOF => OK -- but nothing more to fetch, close the vty and exit + * command loop. + * <=> the vin_base is now closed. + * + * state == vc_stopped + * + * CMD_IO_ERROR => some error has occurred while closing stuff. + * + * state == vc_io_error_trap + * + * And nothing else. + * + * The CMD_IO_ERROR is returned so that errors are not hidden inside here. + * At some point vty_cmd_hiatus() must be called again to deal with the + * error. + * + * When the command loop has gone vc_waiting, the I/O side of things can wake + * it up by uty_cmd_signal(), which passes in a return code. When the + * command loop runs it will call this function to handle the new return code. + * If CMD_SUCCESS is passed in, will continue trying to adjust the vin/vout + * stacks. + * + * The configuration reader command loop also uses vty_cmd_hiatus() to handle + * all return codes. However, it will exit the command loop at the slightest + * hint of trouble. + * + * All this TODO (after discussion of error handling) - switch (vf->vin_type) - { - case VIN_NONE: - zabort("invalid VIN_NONE") ; - break ; + * NB: can be called from any thread if !blocking, otherwise MUST be cli thread. + */ +extern cmd_return_code_t +vty_cmd_hiatus(vty vty, cmd_return_code_t ret) +{ + VTY_LOCK() ; + VTY_ASSERT_CAN_CLOSE(vty) ; - case VIN_TERM: - case VIN_VTYSH: - case VIN_DEV_NULL: - ret = CMD_EOF ; - break ; + ret = uty_cmd_hiatus(vty->vio, ret) ; - /* fall through */ + VTY_UNLOCK() ; + return ret ; +} ; - case VIN_FILE: - case VIN_PIPE: - case VIN_CONFIG: - ret = uty_file_fetch_command_line(vf, p_line) ; +/*------------------------------------------------------------------------------ + * Inside of vty_cmd_hiatus() -- can return at any time. + */ +static cmd_return_code_t +uty_cmd_hiatus(vty_io vio, cmd_return_code_t ret) +{ + /* (0) worry about state of the vio. + * + * We expect it to generally be be vc_running or vc_waiting, otherwise: + * + * vc_io_error_trap => an I/O error has been posted asynchronously + * + * set state to vc_running and return code to + * the pending CMD_IO_ERROR. + * + * vc_close_trap => the vty has been reset asynchronously + * + * set state to vc_running and return code to + * the pending CMD_CLOSE. + * + * vc_stopped ) + * vc_closed ) invalid -- cannot be here in this state + * vc_null ) + */ + switch (vio->state) + { + case vc_null: + case vc_stopped: + case vc_closed: + zabort("invalid vc_xxxx") ; + break ; + + case vc_running: + case vc_waiting: + break ; + + case vc_io_error_trap: + ret = CMD_IO_ERROR ; /* convert pending IO error */ + break ; + + case vc_close_trap: + ret = CMD_CLOSE ; /* convert pending close */ + break ; + + default: + zabort("unknown vio->state") ; + break ; + } ; - if (ret == CMD_SUCCESS) - { - vty->exec->to_do = cmd_do_command ; - } - else if ((ret == CMD_EOF) && (vio->vin_depth > 0)) + vio->state = vc_running ; /* running in hiatus */ + + /* (1) Handle the return code. + * + * Deal here with the return codes that signify success, or signify + * success but some vin and/or vout need to be closed. + * + * Call uty_cmd_failed() to deal with return codes that signify some + * sort of failure. A failure generally means closing all the way to + * the vin_/vout_base, or possibly completely. + * + * Note that CMD_WAITING is immediately returned ! + */ + switch (ret) + { + case CMD_SUCCESS: + case CMD_EMPTY: + case CMD_HIATUS: + break ; + + case CMD_WAITING: + assert(!vio->blocking) ; + vio->state = vc_waiting ; + return ret ; /* <<< exit here on CMD_WAITING */ + + case CMD_EOF: + case CMD_CLOSE: + uty_out_accept(vio) ; /* accept any buffered remarks. */ + assert(vio->real_depth > 0) ; + --vio->real_depth ; + break ; + + default: + /* If not any of the above, must be an error of some kind: + * + * * set real_depth to close all pipes and files. + * + * Depending on the type of vin_base/vout_base and the type of + * error, may or may not leave the bas I/O open. + * + * * create error messages in the vout_base. + */ + vio->real_depth = uty_cmd_failed(vio, ret) ; + break ; + } ; + + ret = CMD_SUCCESS ; /* OK, so far */ + + /* (2) Do we need to close one or more vin, or are we waiting for one to + * close ? + * + * The close will immediately close the input, and discard anything + * which has been buffered. The only difficulty with closing inputs + * is VIN_PIPE, where the "return" input (from the child stderr) may + * not yet have finished. + * + * For blocking vio, close operations will either complete or fail. + * + * For non-blocking vio, close operations may return CMD_WAITING + * (eg: VIN_PIPE where the child stderr is not yet at EOF, or the + * child completion status has not yet been collected). Where a + * close operation does not complete, the vf is marked vf_closing, + * and the stack stays at its current level. + * + * For hard errors will do "final" close, which immediately closes the + * input (and any pipe return) discarding any buffered input. Any errors + * that occur in the process are discarded. + */ + while ((vio->vin_depth > vio->real_depth) && (ret == CMD_SUCCESS)) + ret = uty_vin_pop(vio, vio->err_hard, vio->vty->exec->context) ; + + /* (3) And, do we need to close one or more vout, or are we waiting for + * one to close ? If so: + * + * Any output after the current end_mark is discarded. Note that we + * do not actually close the vout_base. + * + * In all cases we push any remaining output and collect any remaining + * pipe return, and collect child termination condition. + * + * For blocking vio, close operations will either complete or fail. + * + * For non-blocking vio, close operations may return CMD_WAITING + * (eg: where the output buffers have not yet been written away). + * Where a close operation does not complete, the vf is marked + * vf_closing, and the stack stays at its current level. + * + * For hard errors will attempt to write away anything which is, + * pending, but will stop if would block and on any error, and close + * the output -- discarding any remaining output and any errors. + * + * NB: when we reach the vout_base, turn off the hard error -- so + * never "final" close the vout TODO error handling. + * + * Also: if we are at, or we reach, the vout_base, and there is an error + * message in hand, now is the time to move that to the obuf and + * push it. + */ + while (ret == CMD_SUCCESS) + { + assert(vio->vout->depth_mark >= vio->vout_depth) ; + + if (vio->vout_depth == 1) + { + assert(vio->vout->depth_mark == 1) ; + + vio->err_hard = false ; + + if (vio->ebuf != NULL) { - uty_vin_close(vio) ; - uty_cmd_prepare(vio) ; - continue ; + vio_fifo_copy(vio->obuf, vio->ebuf) ; + vio->ebuf = vio_fifo_reset(vio->ebuf, free_it) ; + + ret = uty_cmd_out_push(vio->vout, vio->err_hard) ; + + if (ret != CMD_SUCCESS) + break ; } ; - break ; + } ; - default: - zabort("unknown vin_type") ; - } ; + if (vio->vout_depth == 0) + assert(vio->vout->depth_mark == 0) ; - break ; + if (vio->vin_depth < vio->vout->depth_mark) + ret = uty_vout_pop(vio, vio->err_hard) ; + else + break ; } ; - VTY_UNLOCK() ; + /* (4) Quit now if not successful on stack adjustment + */ + if (ret != CMD_SUCCESS) + { + if (ret == CMD_WAITING) + { + assert(!vio->blocking) ; + vio->state = vc_waiting ; + return ret ; /* <<< exit here on CMD_WAITING */ + } ; - return ret ; + if (ret == CMD_IO_ERROR) + { + vio->state = vc_io_error_trap ; + return ret ; + } ; + + zabort("invalid return code") ; + } ; + + /* (5) Having dealt with closing of files and adjustment of stack, may + * now be EOF on the vin_base. + */ + if (vio->real_depth == 0) + { + vio->state = vc_stopped ; + return CMD_EOF ; + } ; + + /* (6) All ready now to continue processing commands. + */ + uty_cmd_prepare(vio) ; /* update vty->exec state */ + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * When entering command loop, or after opening or closing a vin/vout object, + * update the vty->exec context. + * + * Output to the vout_base is suppressed for reading of configuration files. + * + * Reflection of the command line depends on the current context, and on the + * state of output suppression. + */ +static void +uty_cmd_prepare(vty_io vio) +{ + cmd_exec exec = vio->vty->exec ; + + exec->out_suppress = (vio->vty->type == VTY_CONFIG_READ) && + (vio->vout_depth == 1) ; + exec->reflect = exec->context->reflect_enabled && !exec->out_suppress ; +} ; + +/*------------------------------------------------------------------------------ + * Set the full_lex flag for further commands, and for any further pipes. + */ +extern void +vty_cmd_set_full_lex(vty vty, bool full_lex) +{ + VTY_LOCK() ; + + vty->exec->context->full_lex = full_lex ; + + VTY_UNLOCK() ; } ; /*------------------------------------------------------------------------------ @@ -304,127 +1117,249 @@ vty_cmd_fetch_line(vty vty) * * Advances the end_mark past the reflected line, so that output (in particular * error stuff) is separate. + * + * NB: pushes the output, so that if the command takes a long time to process, + * it is visible while it proceeds. */ -extern void +extern cmd_return_code_t vty_cmd_reflect_line(vty vty) { - vio_fifo obuf ; - qstring line ; + cmd_return_code_t ret ; VTY_LOCK() ; - obuf = vty->vio->obuf ; /* once locked */ - line = vty->exec->line ; + if (vty->vio->state != vc_running) + ret = CMD_HIATUS ; + else + { + vio_fifo obuf ; + qstring line ; + + obuf = vty->vio->obuf ; /* once locked */ + line = vty->exec->action->line ; - vio_fifo_put_bytes(obuf, qs_char_nn(line), qs_len_nn(line)) ; - vio_fifo_put_byte(obuf, '\n') ; + vio_fifo_put_bytes(obuf, qs_char_nn(line), qs_len_nn(line)) ; + vio_fifo_put_byte(obuf, '\n') ; - uty_out_accept(vty->vio) ; + ret = uty_cmd_out_push(vty->vio->vout, false) ; /* not final */ + } ; VTY_UNLOCK() ; + + return ret ; } ; /*------------------------------------------------------------------------------ - * Open the given file as in in pipe, if possible. + * Set the vio->depth_mark -- about to push vin and/or vout + */ +extern void +uty_cmd_depth_mark(vty_io vio) +{ + vio->depth_mark = vio->vin_depth ; +} + +/*------------------------------------------------------------------------------ + * Open the given file as an in pipe, if possible. * * Puts error messages to vty if fails. + * + * NB: saves the current context to the current vin, before opening and pushing + * the new one. */ extern cmd_return_code_t -vty_cmd_open_in_pipe_file(vty vty, qstring name, bool reflect) +uty_cmd_open_in_pipe_file(vty_io vio, cmd_context context, + qstring name, cmd_pipe_type_t type) { cmd_return_code_t ret ; - VTY_LOCK() ; + VTY_ASSERT_LOCKED() ; - ret = uty_file_read_open(vty->vio, name, reflect) ; + if (vio->state != vc_running) + ret = CMD_HIATUS ; + else + { + ret = uty_file_read_open(vio, name, context) ; - if (ret == CMD_SUCCESS) - uty_cmd_prepare(vty->vio) ; + if (ret == CMD_SUCCESS) + { + context->reflect_enabled = (type & cmd_pipe_reflect) != 0 ; + context->parse_strict = true ; - VTY_UNLOCK() ; + uty_cmd_prepare(vio) ; + } ; + } ; return ret ; } ; +/*------------------------------------------------------------------------------ + * Run the given shell command as an in pipe, if possible. + * + * Puts error messages to vty if fails. + * + * NB: saves the current context to the current vin, before opening and pushing + * the new one. + */ extern cmd_return_code_t -vty_cmd_open_in_pipe_shell(vty vty) +uty_cmd_open_in_pipe_shell(vty_io vio, cmd_context context, qstring command, + cmd_pipe_type_t type) { - return CMD_SUCCESS ; + cmd_return_code_t ret ; + + VTY_ASSERT_LOCKED() ; + + if (vio->state != vc_running) + ret = CMD_HIATUS ; + else + { + ret = uty_pipe_read_open(vio, command, context) ; + + if (ret == CMD_SUCCESS) + { + context->reflect_enabled = (type & cmd_pipe_reflect) != 0 ; + context->parse_strict = true ; + + uty_cmd_prepare(vio) ; + } ; + } ; + + return ret ; } ; +/*------------------------------------------------------------------------------ + * Open the given file as an out pipe, if possible. + * + * Puts error messages to vty if fails. + */ extern cmd_return_code_t -vty_cmd_open_out_pipe_file(vty vty, qstring name, bool append) +uty_cmd_open_out_pipe_file(vty_io vio, cmd_context context, qstring name, + cmd_pipe_type_t type) { cmd_return_code_t ret ; - VTY_LOCK() ; + if (vio->state != vc_running) + ret = CMD_HIATUS ; + else + { + ret = uty_file_write_open(vio, name, + ((type & cmd_pipe_append) != 0), context) ; + if (ret == CMD_SUCCESS) + uty_cmd_prepare(vio) ; + } ; - ret = uty_file_write_open(vty->vio, name, append) ; + return ret ; +} ; - if (ret == CMD_SUCCESS) - uty_cmd_prepare(vty->vio) ; +/*------------------------------------------------------------------------------ + * Open the given shell command as an out pipe, if possible. + * + * Puts error messages to vty if fails. + */ +extern cmd_return_code_t +uty_cmd_open_out_pipe_shell(vty_io vio, cmd_context context, qstring command, + cmd_pipe_type_t type) +{ + cmd_return_code_t ret ; - VTY_UNLOCK() ; + if (vio->state != vc_running) + ret = CMD_HIATUS ; + else + { + ret = uty_pipe_write_open(vio, command, + ((type & cmd_pipe_shell_only) !=0)) ; + + if (ret == CMD_SUCCESS) + uty_cmd_prepare(vio) ; + } ; return ret ; } ; +/*------------------------------------------------------------------------------ + * Open "/dev/null" as an out pipe, if possible. + * + * Puts error messages to vty if fails. + */ extern cmd_return_code_t -vty_cmd_open_out_dev_null(vty vty) +uty_cmd_open_out_dev_null(vty_io vio) { + cmd_return_code_t ret ; vio_vf vf ; - VTY_LOCK() ; + VTY_ASSERT_LOCKED() ; - vf = uty_vf_new(vty->vio, "dev_null", -1, vfd_none, vfd_io_none) ; - uty_vout_open(vty->vio, vf, VOUT_DEV_NULL, NULL, NULL, 0) ; + if (vio->state != vc_running) + ret = CMD_HIATUS ; + else + { + vf = uty_vf_new(vio, "dev_null", -1, vfd_none, vfd_io_none) ; + uty_vout_push(vio, vf, VOUT_DEV_NULL, NULL, NULL, 0) ; - vf->out_enabled = false ; + uty_cmd_prepare(vio) ; - VTY_UNLOCK() ; - return CMD_SUCCESS ; + ret = CMD_SUCCESS ; + } ; + + return ret ; } ; -extern cmd_return_code_t -vty_cmd_open_out_pipe_shell(vty vty) +/*------------------------------------------------------------------------------ + * Complete the given file name, if not rooted. + * + * Returns: given or new qpath (if given was NULL) + */ +extern qpath +uty_cmd_path_name_complete(qpath dst, const char* name, cmd_context context) { - return CMD_SUCCESS ; + if (*name == '/') + return qpath_set(dst, name) ; /* done if is rooted */ + + if (*name != '~') + dst = qpath_copy(dst, context->dir_cd) ; + else if ((*(name + 1) == '/') || (*(name + 1) == '\0')) + dst = qpath_copy(dst, context->dir_home) ; + else if ((*(name + 1) == '.') && + ( (*(name + 2) == '/') || (*(name + 2) == '\0')) ) + dst = qpath_copy(dst, context->dir_here) ; + else + return qpath_set(dst, name) ; /* no idea... probably an error */ + + return qpath_append_str(dst, name) ; /* create the full path */ } ; /*------------------------------------------------------------------------------ * Command has completed successfully. * - * An output generated by the command is now pushed if exec->out_enabled, + * An output generated by the command is now pushed unless exec->out_suppress, * or discarded. - * - * If the vin_depth < vout_depth, then close vouts until the depth is equal. */ extern cmd_return_code_t vty_cmd_success(vty vty) { + cmd_return_code_t ret ; vty_io vio ; VTY_LOCK() ; vio = vty->vio ; /* once locked */ - if (!vio_fifo_empty_tail(vio->obuf)) - { - if (vty->exec->out_enabled) - uty_cmd_out_push(vio) ; - else - uty_out_clear(vio) ; - } ; + ret = CMD_SUCCESS ; - /* Deal with closing out pipe on individual command */ - while (vio->vout_depth > vio->vin_depth) + if (vio->state != vc_running) + ret = CMD_HIATUS ; + else { - uty_vout_close(vio, false) ; /* ignore failures TODO */ - uty_cmd_prepare(vio) ; /* set new state */ + if (!vio_fifo_tail_empty(vio->obuf)) + { + if (!vty->exec->out_suppress) + ret = uty_cmd_out_push(vio->vout, false) ; /* not final */ + else + uty_out_clear(vio) ; + } ; } ; VTY_UNLOCK() ; - return CMD_SUCCESS ; + return ret ; } ; /*------------------------------------------------------------------------------ @@ -432,12 +1367,21 @@ vty_cmd_success(vty vty) * * See uty_cmd_out_push() below. */ -extern void +extern cmd_return_code_t vty_cmd_out_push(vty vty) { + cmd_return_code_t ret ; + VTY_LOCK() ; - uty_cmd_out_push(vty->vio) ; + + if (vty->vio->state != vc_running) + ret = CMD_HIATUS ; + else + ret = uty_cmd_out_push(vty->vio->vout, false) ; /* not final */ + VTY_UNLOCK() ; + + return ret ; } ; /*------------------------------------------------------------------------------ @@ -449,149 +1393,87 @@ vty_cmd_out_push(vty vty) * * Advances the end_mark past the stuff pushed. * - * NB: takes no notice of vf->out_enabled... this is used when outputting - * any error messages when reading the configuration file. + * NB: takes no notice of vf->out_suppress, which applies only to buffered + * output present when successfully complete a command -- vty_cmd_success(). * * TODO: worry about closing state ! + * TODO: need a vf level one of these. */ -extern void -uty_cmd_out_push(vty_io vio) +extern cmd_return_code_t +uty_cmd_out_push(vio_vf vf, bool final) /* TODO: write errors & blocking */ { cmd_return_code_t ret ; - vio_vf vf ; - vf = vio->vout ; + VTY_ASSERT_LOCKED() ; - assert(vio->obuf == vf->obuf) ; + vio_fifo_step_end_mark(vf->obuf) ; /* advance the end mark */ - uty_out_accept(vio) ; /* advance the end mark */ + ret = CMD_SUCCESS ; - if (vf->vout_state != vf_open) + switch (vf->vout_state) { - switch (vf->vout_state) - { - case vf_closed: - case vf_eof: - ret = CMD_EOF ; /* TODO what's to do ? */ - break ; - - case vf_error: - ret = CMD_IO_ERROR ; - break ; - - case vf_closing: - ret = CMD_CLOSE ; /* TODO continue with output ? */ - break ; - - default: - zabort("invalid vf->vout_state") ; - break ; - } ; - - return ; /* TODO errors ?? */ - } ; - - switch (vio->vout->vout_type) - { - case VOUT_NONE: - zabort("invalid vout_none") ; - break ; - - case VOUT_TERM: - uty_cli_out_push(vf->cli) ; - break ; - - case VOUT_VTYSH: - /* Kick the writer */ - break ; - - case VOUT_FILE: - uty_file_out_push(vf) ; - break ; - - case VOUT_PIPE: - /* Kick the writer */ - break ; - - case VOUT_CONFIG: - /* Kick the writer */ - break ; - - case VOUT_DEV_NULL: - uty_out_clear(vio) ; /* clear fifo, keep end mark */ - break ; - - case VOUT_STDOUT: - vio_fifo_fwrite(vio->obuf, stdout) ; - break ; - - case VOUT_STDERR: - vio_fifo_fwrite(vio->obuf, stderr) ; - break ; + case vf_open: + case vf_closing: + switch (vf->vout_type) + { + case VOUT_NONE: + zabort("invalid vout_none") ; + break ; - default: - zabort("unknown vout_type") ; - } ; -} ; + case VOUT_TERM: + ret = uty_term_out_push(vf, final) ; + break ; -/*------------------------------------------------------------------------------ - * The command loop has exited, with the given return code. - * - * There are a number of return codes that are treated as success. On any - * of those, does vty_cmd_success() (which has no effect if already done). - * - * If not a success return code, then close down any pipe-work, and create - * suitable error message. - * - * If is any form of success, returns CMD_SUCCESS -- otherwise returns the - * return code as given. - */ -extern cmd_return_code_t -vty_cmd_loop_exit(vty vty, cmd_return_code_t ret) -{ - bool close ; + case VOUT_VTYSH: + /* Kick the writer */ + break ; - /* Deal with the several names of success and the many names of failure. - */ - close = false ; + case VOUT_FILE: + ret = uty_file_out_push(vf, final) ; + break ; - switch(ret) - { - case CMD_CLOSE: - case CMD_EOF: - close = true ; - fall_through ; + case VOUT_PIPE: + ret = uty_pipe_out_push(vf, final) ; + break ; - case CMD_SUCCESS: - case CMD_EMPTY: - ret = vty_cmd_success(vty) ; - break ; + case VOUT_CONFIG: + ret = uty_file_out_push(vf, final) ; /* treat as file */ + break ; - default: - /* If not CMD_SUCCESS, the treat as some sort of error: - * - * * close down all pipes -- leaving just the vin_base/vout_base. - * * create error messages in the vout_base. - */ - vty_cmd_failed(vty, ret) ; - break ; - } ; + case VOUT_DEV_NULL: + case VOUT_SHELL_ONLY: + vio_fifo_clear(vf->obuf, false) ; /* keep end mark */ + break ; - /* They think it's all over... */ + case VOUT_STDOUT: + vio_fifo_fwrite(vf->obuf, stdout) ; // TODO errors + break ; - VTY_LOCK() ; + case VOUT_STDERR: + vio_fifo_fwrite(vf->obuf, stderr) ; // TODO errors + break ; - vty->vio->cmd_running = false ; + default: + zabort("unknown vout_type") ; + } ; + break ; - if (vty->vio->vin->vin_type == VIN_TERM) - uty_cli_done_command(vty->vio->vin->cli, vty->node) ; + case vf_closed: + case vf_timed_out: + break ; /* immediate success ! */ - /* ... it is now. */ + case vf_eof: + zabort("vf->vout cannot be vf_eof") ; + break ; - if (close) - uty_close(vty->vio, false, NULL) ; + case vf_error: + ret = CMD_IO_ERROR ; + break ; - VTY_UNLOCK() ; + default: + zabort("unknown vf->vout_state") ; + break ; + } ; return ret ; } ; @@ -599,19 +1481,32 @@ vty_cmd_loop_exit(vty vty, cmd_return_code_t ret) /*------------------------------------------------------------------------------ * Dealing with error of some kind. * - * Error is reported on the vout_base. Whatever the command has written - * so far will be appended to the reporting of the error location. + * The current vio->obuf will have an end_mark. After the end_mark will be + * any output generated since the start of the current command (or any out_push + * since then). For command errors, that output is expected to be error + * messages associated with the error. + * + * A new "ebuf" fifo is created, and the location of the error is written to + * that fifo. Once the contents of the "ebuf" are output, the command line in + * which the error occurred should be the last thing output. + * + * Any other error message is then appended to the ebuf. * - * If there is only one vout, then: + * For command errors, the tail of the vio->obuf (stuff after the end_mark) is + * moved to the ebuf. * - * - if is VOUT_TERM, then need to wipe the command line. The failing - * line will be the last output line on screen (unless monitor output, - * which can do nothing about). + * Deals with: * - * - if is VOUT_VTYSH, then the failing line will be the last output line - * on the screen. + * CMD_WARNING = TODO + * CMD_ERROR => * - * - if is VOUT_xxx, then nothing will have been reflected. + * CMD_ERR_PARSING, parser: general parser error + * CMD_ERR_NO_MATCH, parser: command/argument not recognised + * CMD_ERR_AMBIGUOUS, parser: more than on command matches + * CMD_ERR_INCOMPLETE, + * + * CMD_IO_ERROR I/O -- failed :-( + * CMD_IO_TIMEOUT I/O -- timed out :-( * * In any event, need to separate out any output from the failing command, * so that can report error location and type, before showing the error @@ -620,116 +1515,112 @@ vty_cmd_loop_exit(vty vty, cmd_return_code_t ret) * NB: does not expect to see all the possible CMD_XXX return codes (see * below), but treats all as a form of error ! */ -static void -vty_cmd_failed(vty vty, cmd_return_code_t ret) +static uint +uty_cmd_failed(vty_io vio, cmd_return_code_t ret) { - vty_io vio ; - vio_fifo tbuf ; - ulen eloc ; + ulen indent ; - VTY_LOCK() ; - vio = vty->vio ; /* once locked */ - - /* Copy command output to temporary, and remove from the obuf. - * - * Any command output should be messages explaining the error, and we want - * those (a) on the base vout, and (b) after description of where the error - * occurred. - */ - tbuf = vio_fifo_copy_tail(NULL, vio->obuf) ; - vio_fifo_back_to_end_mark(vio->obuf, true) ; - - /* Can now close everything in the vout stack, so all further output - * is to the vout_base. - */ - uty_vout_close_stack(vio, false) ; /* not "final" */ + VTY_ASSERT_LOCKED() ; /* Process the vin stack to generate the error location(s) */ - uty_show_error_location(vio->vin) ; - - if (vio->vin->vin_type == VIN_TERM) - eloc = uty_cli_prompt_len(vio->vin->cli) ; + if (vio->ebuf != NULL) + vio_fifo_clear(vio->ebuf, true) ; else - eloc = 0 ; + vio->ebuf = vio_fifo_init_new(NULL, 1000) ; - /* Can now close everything in the vin stack */ - uty_vin_close_stack(vio) ; + indent = uty_show_error_context(vio->ebuf, vio->vin) ; /* Now any additional error message if required */ switch (ret) { - qstring qs ; - case CMD_WARNING: - if (vio_fifo_empty(tbuf)) - uty_out(vio, "%% WARNING: non-specific warning\n") ; + if (vio_fifo_tail_empty(vio->obuf)) + vio_fifo_printf(vio->ebuf, "%% WARNING: non-specific warning\n") ; break ; case CMD_ERROR: - if (vio_fifo_empty(tbuf)) - uty_out(vio, "%% ERROR: non-specific error\n") ; - break ; - - case CMD_IO_ERROR: + if (vio_fifo_tail_empty(vio->obuf)) + vio_fifo_printf(vio->ebuf, "%% ERROR: non-specific error\n") ; break ; case CMD_ERR_PARSING: - qs = qs_set_fill(NULL, eloc + vio->vty->exec->parsed->eloc, "....") ; - uty_out(vio, "%s^\n%% %s\n", qs_string(qs), - vio->vty->exec->parsed->emess) ; - qs_reset(qs, free_it) ; + cmd_get_parse_error(vio->ebuf, vio->vty->exec->parsed, indent) ; break ; case CMD_ERR_NO_MATCH: - uty_out(vio, "%% Unknown command.\n") ; + vio_fifo_printf(vio->ebuf, "%% Unknown command.\n") ; break; case CMD_ERR_AMBIGUOUS: - uty_out(vio, "%% Ambiguous command.\n"); + vio_fifo_printf(vio->ebuf, "%% Ambiguous command.\n"); break; case CMD_ERR_INCOMPLETE: - uty_out(vio, "%% Command incomplete.\n"); + vio_fifo_printf(vio->ebuf, "%% Command incomplete.\n"); break; - case CMD_SUCCESS: - case CMD_SUCCESS_DAEMON: - case CMD_CLOSE: - case CMD_EMPTY: - case CMD_WAITING: - case CMD_EOF: - case CMD_COMPLETE_FULL_MATCH: - case CMD_COMPLETE_MATCH: - case CMD_COMPLETE_LIST_MATCH: - case CMD_COMPLETE_ALREADY: + case CMD_IO_ERROR: + break ; + + case CMD_IO_TIMEOUT: + break ; + default: - uty_out(vio, "%% Unexpected return code (%d).\n", (int)ret) ; + vio_fifo_printf(vio->ebuf, "%% Unexpected return code (%d).\n", (int)ret); break ; } ; - /* Now stick the tbuf onto the end of the output & discard tbuf. */ - vio_fifo_copy(vio->obuf, tbuf) ; - vio_fifo_reset(tbuf, free_it) ; - - uty_cmd_out_push(vio) ; + /* Now stick the obuf tail onto the end of the ebuf & discard the tail of + * the obuf. + */ + vio_fifo_copy_tail(vio->ebuf, vio->obuf) ; + vio_fifo_back_to_end_mark(vio->obuf, true) ; - VTY_UNLOCK() ; + /* Decide what stack level to close back to. */ + return (vio->vty->type == VTY_TERMINAL) ? 1 : 0 ; // TODO I/O error ?? } ; -static void -uty_show_error_location(vio_vf vf) +/*------------------------------------------------------------------------------ + * In the given fifo, construct message giving the context in which an error + * has occurred. + * + * For file and pipe input (including config file), it is assumed that no + * command line has been reflected, so the context is given as: + * + * % on line 99 of xxxx: + * <the command line -- noting small indent> + * + * For interactive input, if the stack depth is 1, then it is assumed that the + * command line is the last complete line output. Otherwise the context is + * given as: + * + * % in command line: + * <the command line -- noting small indent> + * + * The context starts with level 1 of the vin stack, and ends with the current + * level. + * + * Returns: "eloc" -- start of command line at the current level. + */ +static uint +uty_show_error_context(vio_fifo ebuf, vio_vf vf) { vio_vf vf_next ; + uint indent ; /* Recurse until hit end of the vin stack */ vf_next = ssl_next(vf, vin_next) ; if (vf_next != NULL) - uty_show_error_location(vf_next) ; + uty_show_error_context(ebuf, vf_next) ; else assert(vf == vf->vio->vin_base) ; - /* On the way back, add the error location for each vin entry */ + /* On the way back, add the error location for each vin entry + * and establish the location of the start of the command line as shown. + */ + indent = 0 ; + switch (vf->vin_type) { case VIN_NONE: @@ -737,20 +1628,20 @@ uty_show_error_location(vio_vf vf) break ; case VIN_TERM: - /* Wipe the current command line */ -// uty_term_show_error_location(vf, loc) ; + indent = uty_term_show_error_context(vf, ebuf, vf->vio->vin_depth) ; break ; case VIN_VTYSH: +// eloc = uty_term_show_error_context(vf, ebuf, vf->vio->vin_depth) ; break ; case VIN_FILE: - case VIN_PIPE: - case VIN_CONFIG: - uty_out(vf->vio, "%% on line %d of %s:\n", vf->line_number, vf->name) ; - uty_out(vf->vio, "%s\n", qs_make_string(vf->cl)) ; + vio_fifo_printf(ebuf, "%% on line %d of %s:\n", + vf->line_number, vf->name) ; + vio_fifo_printf(ebuf, " %s\n", qs_make_string(vf->cl)) ; + indent = 2 ; break ; case VIN_DEV_NULL: @@ -759,6 +1650,8 @@ uty_show_error_location(vio_vf vf) default: zabort("unknown vin_type") ; } ; + + return indent ; } ; /*============================================================================== @@ -768,96 +1661,96 @@ uty_show_error_location(vio_vf vf) */ /*------------------------------------------------------------------------------ - * Attempt to gain the configuration symbol of power - * - * If succeeds, set the given node. + * Attempt to gain the configuration symbol of power -- may already own it ! * - * Returns: true <=> now own the symbol of power. + * Returns: true <=> now own the symbol of power (or already did). */ -extern bool -vty_config_lock (struct vty *vty, enum node_type node) +extern cmd_return_code_t +vty_cmd_config_lock (vty vty) { - bool result; - VTY_LOCK() ; + uty_cmd_config_lock(vty) ; + VTY_UNLOCK() ; - if (!host.config) - { - vty->config = true ; - host.config = true ; - } ; + if (vty->config) + return CMD_SUCCESS ; - result = vty->config; + vty_out (vty, "VTY configuration is locked by other VTY\n") ; + return CMD_WARNING ; +} ; - if (result) - vty->node = node ; +/*------------------------------------------------------------------------------ + * Attempt to gain the configuration symbol of power -- may already own it ! + * + * Returns: true <=> now own the symbol of power (or already did). + */ +static void +uty_cmd_config_lock (vty vty) +{ + if (!host.config) /* If nobody owns the lock... */ + { + host.config = true ; /* ...rope it... */ + vty->config = true ; - VTY_UNLOCK() ; + do + ++host.config_brand ; /* ...update the brand... */ + while (host.config_brand == 0) ; - return result; + vty->config_brand = host.config_brand ; /* ...brand it. */ + } + else /* Somebody owns the lock... */ + { + if (vty->config) /* ...if we think it is us, check brand */ + assert(host.config_brand == vty->config_brand) ; + } ; } /*------------------------------------------------------------------------------ - * Give back the configuration symbol of power -- if own it. - * - * Set the given node -- which must be <= MAX_NON_CONFIG_NODE + * Check that given node and ownership of configuration symbol of power... + * ...see below. */ extern void -vty_config_unlock (struct vty *vty, enum node_type node) +vty_cmd_config_lock_check(vty vty, node_type_t node) { VTY_LOCK() ; - uty_config_unlock(vty, node); + uty_cmd_config_lock_check(vty, node) ; VTY_UNLOCK() ; -} +} ; /*------------------------------------------------------------------------------ - * Give back the configuration symbol of power -- if own it. + * Check that given node and ownership of configuration symbol of power + * are mutually consistent. + * + * If node > MAX_NON_CONFIG_NODE, must own the symbol of power. * - * Set the given node -- which must be <= MAX_NON_CONFIG_NODE + * If node <= MAX_NON_CONFIG_NODE, will release symbol of power, if own it, + * PROVIDED is at vin_depth <= 1 !! */ -extern void -uty_config_unlock (struct vty *vty, node_type_t node) +static void +uty_cmd_config_lock_check(vty vty, node_type_t node) { VTY_ASSERT_LOCKED() ; - if (host.config && vty->config) + if (vty->config) { - vty->config = false ; - host.config = false ; - } - - assert(node <= MAX_NON_CONFIG_NODE) ; - vty->node = node ; -} ; - - - - - - - - - - - - - - - + /* We think we own it, so we better had */ + assert(host.config) ; + assert(host.config_brand == vty->config_brand) ; + /* If no longer need it, release */ + if ((node <= MAX_NON_CONFIG_NODE) && (vty->vio->vin_depth <= 1)) + { + host.config = false ; + vty->config = false ; + } ; + } + else + { + /* We don't think we own it, so we had better not */ + if (host.config) + assert(host.config_brand != vty->config_brand) ; -/*------------------------------------------------------------------------------ - * Result of command is to close the input. - * - * Posts the reason for the close. - * - * Returns: CMD_CLOSE - */ -extern cmd_return_code_t -uty_cmd_close(vty_io vio, const char* reason) -{ - vio->close_reason = qs_set(NULL, reason) ; - return CMD_CLOSE ; + /* Also, node had better not require that we do. */ + assert(node <= MAX_NON_CONFIG_NODE) ; + } ; } ; - - diff --git a/lib/vty_command.h b/lib/vty_command.h index 7b13721d..125b9a03 100644 --- a/lib/vty_command.h +++ b/lib/vty_command.h @@ -30,32 +30,38 @@ #include "vty_local.h" #include "vty_io.h" -extern cmd_return_code_t uty_cmd_dispatch(vty_io vio, cmd_do_t to_do, - qstring line) ; +extern void vty_cmd_loop_prepare(vty vty) ; +extern void uty_cmd_loop_enter(vty_io vio) ; +extern void uty_cmd_signal(vty_io vio, cmd_return_code_t ret) ; +extern void uty_cmd_loop_close(vty_io vio, bool curtains) ; +extern void vty_cmd_loop_exit(vty vty) ; + extern cmd_return_code_t vty_cmd_fetch_line(vty vty) ; +extern cmd_return_code_t vty_cmd_hiatus(vty vty, cmd_return_code_t ret) ; extern cmd_return_code_t vty_cmd_special(vty vty) ; -extern void vty_cmd_reflect_line(vty vty) ; -extern void vty_cmd_out_push(vty vty) ; -extern void uty_cmd_out_push(vty_io vio) ; - -extern cmd_return_code_t vty_cmd_open_in_pipe_file(vty vty, qstring name, - bool reflect) ; -extern cmd_return_code_t vty_cmd_open_in_pipe_shell(vty vty) ; -extern cmd_return_code_t vty_cmd_open_out_pipe_file(vty vty, qstring name, - bool append) ; -extern cmd_return_code_t vty_cmd_open_out_dev_null(vty vty) ; -extern cmd_return_code_t vty_cmd_open_out_pipe_shell(vty vty) ; -extern cmd_return_code_t vty_cmd_success(vty vty) ; -extern cmd_return_code_t vty_cmd_loop_exit(vty vty, cmd_return_code_t ret) ; +extern cmd_return_code_t vty_cmd_can_auth_enable(vty vty) ; +extern cmd_return_code_t vty_cmd_reflect_line(vty vty) ; +extern cmd_return_code_t vty_cmd_out_push(vty vty) ; +extern cmd_return_code_t uty_cmd_out_push(vio_vf vf, bool final) ; -extern void uty_cmd_prepare(vty_io vio) ; +extern void vty_cmd_set_full_lex(vty vty, bool full_lex) ; -extern cmd_return_code_t uty_cmd_close(vty_io vio, const char* reason) ; - -extern bool vty_config_lock (vty vty, node_type_t node); -extern void vty_config_unlock (vty vty, node_type_t node); -extern void uty_config_unlock (vty vty, node_type_t node) ; +extern void uty_cmd_depth_mark(vty_io vio) ; +extern cmd_return_code_t uty_cmd_open_in_pipe_file(vty_io vio, + cmd_context context, qstring name, cmd_pipe_type_t type) ; +extern cmd_return_code_t uty_cmd_open_in_pipe_shell(vty_io vio, + cmd_context context, qstring command, cmd_pipe_type_t type) ; +extern cmd_return_code_t uty_cmd_open_out_pipe_file(vty_io vio, + cmd_context context, qstring name, cmd_pipe_type_t type) ; +extern cmd_return_code_t uty_cmd_open_out_dev_null(vty_io vio) ; +extern cmd_return_code_t uty_cmd_open_out_pipe_shell(vty_io vio, + cmd_context context, qstring command, cmd_pipe_type_t type) ; +extern qpath uty_cmd_path_name_complete(qpath dst, const char* name, + cmd_context context) ; +extern cmd_return_code_t vty_cmd_success(vty vty) ; +extern cmd_return_code_t vty_cmd_config_lock (vty vty) ; +extern void vty_cmd_config_lock_check(struct vty *vty, node_type_t node) ; #endif /* _ZEBRA_VTY_COMMAND_H */ diff --git a/lib/vty_common.h b/lib/vty_common.h index 7c0929b8..04429d47 100644 --- a/lib/vty_common.h +++ b/lib/vty_common.h @@ -44,7 +44,7 @@ * 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 + * 1) a "handle" for output produced by commands * * 2) the holder of some context -- notably the current command "node" -- for * command execution to use @@ -53,7 +53,7 @@ * factored out into the "struct vty_io" -- opaque to users of the struct vty. * * There is also context used when parsing and executing commands which is - * private to command.c et al, and is factored out into the "struct execution" + * private to command.c et al, and is factored out into the "struct cmd_exec" * -- also opaque to users of the struct vty. */ enum vty_type /* Command output */ @@ -110,18 +110,30 @@ struct vty void* index_sub ; /* In configure mode (owner of the symbol of power) */ - bool config; + bool config ; /* we own */ + ulong config_brand ; /* and this is our mark */ /*---------------------------------------------------------------------------- * The current cmd_exec environment -- used in command_execute.c et al * - * This is accessed freely by the command handling code while - * vio->cmd_running. + * This is accessed freely by the command handling code because there is + * only one thread of execution per vty -- though for some vty types (notably + * VTY_TERMINAL) that may be in the vty_cli_thread or in the vty_cmd_thread + * at different times. + * + * Where information from the vio is required, the VTY_LOCK is acquired. */ struct cmd_exec* exec ; /* one per vty */ /*---------------------------------------------------------------------- * The following is used inside vty.c etc only -- under VTY_LOCK. + * + * The lock is required because the vty_cli_thread may be doing I/O and + * other stuff at the same time as the vty_cmd_thread is doing I/O, or at the + * same time as other vty are being serviced. + * + * Could have one lock per vty -- but would then need a lock for the common + * parts of the cli thread, so one lock keeps things relatively simple. */ struct vty_io* vio ; /* one per vty */ } ; diff --git a/lib/vty_io.c b/lib/vty_io.c index 4b50c526..dadfd091 100644 --- a/lib/vty_io.c +++ b/lib/vty_io.c @@ -44,6 +44,7 @@ #include <arpa/telnet.h> #include <sys/un.h> /* for VTYSH */ #include <sys/socket.h> +#include <wait.h> #define VTYSH_DEBUG 0 @@ -88,8 +89,10 @@ uty_out(vty_io vio, const char *format, ...) /* Watch-dog timer. */ static vio_timer_t vty_watch_dog ; -static vty_timer_time uty_watch_dog_bark(vio_timer_t* timer, void* info) ; -static bool uty_death_watch_scan(bool final) ; +static vty_timer_time uty_watch_dog_bark(vio_timer timer, void* info) ; +static void uty_death_watch_scan(bool final) ; + +static vty_io uty_dispose(vty_io vio) ; /*------------------------------------------------------------------------------ * Initialise watch dog -- at start-up time. @@ -97,7 +100,7 @@ static bool uty_death_watch_scan(bool final) ; extern void uty_watch_dog_init(void) { - vio_timer_init(&vty_watch_dog, NULL, NULL) ; /* empty */ + vio_timer_init_new(vty_watch_dog, NULL, NULL) ; /* empty */ } ; /*------------------------------------------------------------------------------ @@ -106,8 +109,8 @@ uty_watch_dog_init(void) extern void uty_watch_dog_start(void) { - vio_timer_init(&vty_watch_dog, uty_watch_dog_bark, NULL) ; - vio_timer_set(&vty_watch_dog, VTY_WATCH_DOG_INTERVAL) ; + vio_timer_init_new(vty_watch_dog, uty_watch_dog_bark, NULL) ; + vio_timer_set(vty_watch_dog, VTY_WATCH_DOG_INTERVAL) ; } ; /*------------------------------------------------------------------------------ @@ -118,7 +121,7 @@ uty_watch_dog_start(void) extern void uty_watch_dog_stop(void) { - vio_timer_reset(&vty_watch_dog) ; + vio_timer_reset(vty_watch_dog, keep_it) ; uty_death_watch_scan(true) ; /* scan the death-watch list */ } @@ -126,7 +129,7 @@ uty_watch_dog_stop(void) * Watch dog vio_timer action */ static vty_timer_time -uty_watch_dog_bark(vio_timer_t* timer, void* info) +uty_watch_dog_bark(vio_timer timer, void* info) { cmd_host_name(true) ; /* check for host name change */ @@ -136,49 +139,40 @@ uty_watch_dog_bark(vio_timer_t* timer, void* info) } ; /*------------------------------------------------------------------------------ - * Scan the death watch list. - * - * A vty can finally be freed if it is closed and there is no command running. + * Process the death watch list -- anything on the list can be disposed of. * - * At curtains the command running state is forced off. + * At curtains... */ -static bool -uty_death_watch_scan(bool curtains) +static void +uty_death_watch_scan(bool final) { - vty_io vio ; - vty_io next ; + VTY_ASSERT_CLI_THREAD() ; - next = vio_death_watch ; - while (next != NULL) + /* Dispose of anything on the death watch list. */ + + while (vio_death_watch != NULL) { - vio = next ; - next = sdl_next(vio, vio_list) ; + vty vty ; + vty_io vio ; - /* If this is curtains, override cmd_running ! */ - if (curtains) - vio->cmd_running = false ; + vio = vio_death_watch ; + vio_death_watch = sdl_next(vio, vio_list) ; /* take off death watch */ - if (uty_close(vio, curtains, NULL)) - { - vty vty = vio->vty ; + vty = vio->vty ; - sdl_del(vio_death_watch, vio, vio_list) ; + vty->vio = uty_dispose(vty->vio) ; + assert(vty->exec == NULL) ; - cmd_exec_free(vty->exec) ; - XFREE(MTYPE_VTY, vty->vio) ; - XFREE(MTYPE_VTY, vty) ; - } ; + XFREE(MTYPE_VTY, vty) ; } ; - - return (vio_death_watch == NULL) ; } ; /*============================================================================== * Prototypes. */ - -static void uty_vf_read_close(vio_vf vf) ; -static bool uty_vf_write_close(vio_vf vf, bool final) ; +static void uty_vout_close_reason(vio_vf vf, const char* reason) ; +static cmd_return_code_t uty_vf_read_close(vio_vf vf, bool final) ; +static cmd_return_code_t uty_vf_write_close(vio_vf vf, bool final) ; static vio_vf uty_vf_free(vio_vf vf) ; /*============================================================================== @@ -191,30 +185,28 @@ static vio_vf uty_vf_free(vio_vf vf) ; * * Caller must complete the initialisation of the vty_io, which means: * - * * constructing a suitable vio_vf and doing uty_vin_open() to set the + * * constructing a suitable vio_vf and doing uty_vin_push() to set the * vin_base. * * All vty_io MUST have a vin_base, even if it is /dev/null. * - * * constructing a suitable vio_vf and doing uty_vout_open() to set the - * vout_base. + * * constructing a suitable vio_vf and doing uty_vout_push() to set the + * vout_base and the vio->obuf. * * 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 * - * * setting vio->cli, if required. + * * etc. * - * Caller must also set the initial vty->node, if any. + * No "exec" is allocated. That is done when the command loop is entered. */ extern vty -uty_new(vty_type_t type) +uty_new(vty_type_t type, node_type_t node) { vty vty ; vty_io vio ; - bool execution ; - VTY_ASSERT_LOCKED() ; /* Basic allocation */ @@ -224,20 +216,22 @@ uty_new(vty_type_t type) * * type = X -- set to actual type, below * - * node = NULL_NODE -- set to something sensible elsewhere + * node = X -- set to actual node, below * * index = NULL -- nothing, yet * index_sub = NULL -- nothing, yet * - * config = false -- not in configure mode + * config = false -- not owner of configuration symbol of power + * config_brand = 0 -- none, yet * - * execution = X -- set below + * exec = NULL -- execution state set up when required * vio = X -- set below */ confirm(NULL_NODE == 0) ; confirm(QSTRING_INIT_ALL_ZEROS) ; vty->type = type ; + vty->node = node ; vio = XCALLOC(MTYPE_VTY, sizeof(struct vty_io)) ; @@ -251,57 +245,37 @@ uty_new(vty_type_t type) * vin_base = NULL -- empty input stack * vin_depth = 0 -- no stacked vin's, yet * + * real_depth = 0 -- nothing stacked, yet + * * vout = NULL -- empty output stack * vout_base = NULL -- empty output stack * vout_depth = 0 -- no stacked vout's, yet * - * vout_closing = NULL -- nothing closing yet + * depth_mark = 0 -- no stacked vin/vout, yet + * + * err_hard = false -- no error at all, yet + * ebuf = NULL -- no error at all, yet * * vio_list = NULLs -- not on the vio_list, yet * mon_list = NULLs -- not on the monitors list * * blocking = X -- set below: false unless VTY_CONFIG_READ - * cmd_running = false -- no commands running, yet * - * state = X -- set vf_open, below. + * state = vc_null -- not started vty command loop * - * close_reason = NULL -- not closed, yet + * close_reason = NULL -- none set * - * obuf = NULL -- no output buffer, yet + * obuf = NULL -- no output buffer, yet */ + confirm(vc_null == 0) ; 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 ; - - switch(type) - { - case VTY_TERMINAL: - case VTY_SHELL_SERVER: - case VTY_SHELL_CLIENT: - case VTY_CONFIG_READ: - execution = true ; - break ; - - case VTY_STDOUT: - case VTY_STDERR: - execution = false ; - break ; - - default: - zabort("unknown vty type") ; - } ; - - if (execution) - vty->exec = cmd_exec_new(vty) ; /* Place on list of known vio/vty */ - sdl_push(vio_list_base, vio, vio_list) ; + sdl_push(vio_live_list, vio, vio_list) ; return vty; } ; @@ -316,13 +290,13 @@ uty_new(vty_type_t type) * * Sets the read ready action and the read timer timeout action. * - * NB: a uty_cmd_prepare() is required before command processing can continue. + * NB: is usually called from the cli thread, but may be called from the cmd + * thread for vf which is blocking ! * - * That is not done here because the vin state may not be complete (in - * particular the vin->parse_type and vin->reflect_enabled !). + * NB: a uty_cmd_prepare() is required before command processing can continue. */ extern void -uty_vin_open(vty_io vio, vio_vf vf, vio_in_type_t type, +uty_vin_push(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) @@ -330,34 +304,58 @@ uty_vin_open(vty_io vio, vio_vf vf, vio_in_type_t type, vf->vin_type = type ; vf->vin_state = vf_open ; - if (type < VIN_SPECIALS) + assert(type != VIN_NONE) ; + + if ((type < VIN_SPECIALS) && (!vf->blocking)) { 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) ; + vio->real_depth = ++vio->vin_depth ; + if (vio->vin_base == NULL) { - assert(vio->vin_depth == 0) ; + assert(vio->vin_depth == 1) ; vio->vin_base = vf ; - } - else - { - assert(type != VIN_NONE) ; - ++vio->vin_depth ; } ; if (ibuf_size != 0) { vf->ibuf = vio_fifo_init_new(NULL, ibuf_size) ; - + vf->cl = qs_init_new(NULL, 120) ; vf->line_complete = true ; vf->line_step = 1 ; } ; } ; /*------------------------------------------------------------------------------ + * Save the given context in the current top of the vin stack. + * + * This is done when a new pipe is opened, so that: + * + * a) saves the current context in the current vin (the new pipe has + * not yet been pushed) so that uty_vin_pop() can restore this context + * after closing the then top of the stack. + * + * b) can update context for about to be run vin, eg: + * + * - dir_here -- if required + * + * - can_enable + * + * So the top of the vin stack does not contain the current context, that is + * in the vty->exec ! + */ +extern void +uty_vin_new_context(vty_io vio, cmd_context context, qpath file_here) +{ + assert(vio->vin->context == NULL) ; + vio->vin->context = cmd_context_new_save(context, file_here) ; +} ; + +/*------------------------------------------------------------------------------ * Push a new vf to the vio->vout stack, and set write stuff. * * Sets the vf->vout_type and set vf->write_open. @@ -366,6 +364,15 @@ uty_vin_open(vty_io vio, vio_vf vf, vio_in_type_t type, * * Initialises an output buffer and sets an end_mark. * + * The depth_mark is set to the current vio->depth_mark + 1. This is the + * vin_depth below which the vout should be closed. Before a command line + * is fetched (and hence after the previous command line has completed) the + * vout->depth_mark is checked. If it is > the current vin_depth, then + * the vout is closed before a command line can be fetched. + * + * NB: is usually called from the cli thread, but may be called from the cmd + * thread for vf which is blocking ! + * * NB: VOUT_DEV_NULL, VOUT_STDOUT and VOUT_STDERR are special. * * The write_action and the write_timer_action are ignored. @@ -377,41 +384,41 @@ uty_vin_open(vty_io vio, vio_vf vf, vio_in_type_t type, * 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_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) +uty_vout_push(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) { + VTY_ASSERT_LOCKED() ; + vf->vout_type = type ; vf->vout_state = vf_open ; - if (type < VOUT_SPECIALS) + assert(type != VOUT_NONE) ; + + if ((type < VOUT_SPECIALS) && (!vf->blocking)) { 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) ; + ++vio->vout_depth ; + if (vio->vout_base == NULL) { - assert(vio->vout_depth == 0) ; + assert(vio->vout_depth == 1) ; vio->vout_base = vf ; - } - else - { - 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 ; + vf->depth_mark = vio->depth_mark + 1 ; + + vio->obuf = vf->obuf ; } ; /*------------------------------------------------------------------------------ @@ -434,33 +441,6 @@ uty_set_timeout(vty_io vio, vty_timer_time timeout) } ; /*------------------------------------------------------------------------------ - * 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 0 - if (on && !vio->monitor) - { - if ((vio->vty->type == VTY_TERMINAL) && !vio->closing) - { - 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) ; - } -#endif -} ; - -/*------------------------------------------------------------------------------ * Return "name" of VTY. * * The name of the base vin, or (failing that) the base vout. @@ -478,257 +458,391 @@ uty_get_name(vty_io vio) } ; /*------------------------------------------------------------------------------ - * Close all vin excluding the vin_base. - * - * 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. - * - * This is done on close and when there is a command error. - * - * 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 ! + * Close VTY -- final. * - * 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. + * Two forms: "curtains" and "not-curtains". * - * 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. + * At "curtains" the system is being terminated, and all message and event + * handling has stopped. Can assume that a vty is not in any command loop, + * so can be killed off here and now. * - * 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. + * At "not-curtains" messages and event handling is still running. It is + * possible that a vty is running a command in the cmd_thread, so cannot + * completely close things down -- must leave enough for the command loop + * to continue to work, up to the point that the closing of the vty is + * detected. * - * 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. + * For everything that is closed, uses "final" close, which stops things + * instantly -- will not block, or set any read/write ready, or continue the + * command loop, or take any notice of errors. * - * 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. + * This close is called by: * - * The close sequence is: + * * uty_reset() -- SIGHUP -- !curtains * - * 1. if a close reason has not already been set, set any given one. + * Will close "final" everything except vout_base. * - * 2. if not already closing -- ie this is the first call of uty_close() + * If the command loop is not already stopped, it is signalled to stop + * as soon as possible. When it does it will return here via + * vty_cmd_loop_exit(). * - * a. transfer to death-watch list & set closing. + * If command loop has already stopped, then will proceed to complete + * close -- see below. * - * b. turn off any monitor state + * * uty_reset() -- SIGTERM -- curtains * - * c. close down the input/read side completely. + * Will close "final" everything except vout_base. * - * Empties the vin stack down to vin_base, closing all input. + * The command loop will be set stopped, and will proceed to complete + * close -- see below. * - * Close the vin_base. The vin_base is left !read_open. + * * vty_cmd_loop_exit() -- when the command loop has stopped -- !curtains * - * All read-only vio_vf (except the vin_base) are freed. + * The command loop may have stopped in response to a vc_close_trap, + * which means that have been here before, and the vty is pretty much + * closed already. * - * Note that everything is read closed before anything is write - * closed. + * The command loop may have stopped: * - * A vio_vf is read closed once and only once. + * - because has reached the end of the vin_base input + * - vin_base has timed out + * - there was a command error, on non-interactive vty + * - there was an I/O error * - * 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. + * In all these cases, the stack will already have been closed final or + * otherwise, except for vout_base, and all output will have been pushed. * - * 3. try to close everything on the vout_closing list. + * vout_base will have been closed, but not "final", so will be sitting + * in vf_closing state. * - * even if cmd_running + * So the vio is ready for complete close. * + * For complete close any remaining vf are closed final, and the close reason + * is output to the vout_base, if any and if possible. * + * The vty is then closed and placed on death watch to be finally reaped. */ -extern bool -uty_close(vty_io vio, bool final, qstring reason) +extern void +uty_close(vty_io vio, const char* reason, bool curtains) { - vio_vf vf_next ; + VTY_ASSERT_CAN_CLOSE(vio->vty) ; - VTY_ASSERT_LOCKED() ; + /* Stamp on any monitor output instantly. */ + uty_set_monitor(vio, off) ; - /* quit if already closed */ - if (vio->state == vf_closed) - return true ; + /* Save the close reason for later, unless one is already set. */ + if ((reason != NULL) && (vio->close_reason == NULL)) + vio->close_reason = XSTRDUP(MTYPE_TMP, reason) ; - /* 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) ; - } ; + /* Close the command loop -- if not already stopped (or closed !) + * + * If command loop is not already stopped, the if "curtains" will stop it + * instantly, otherwise will signal to the command loop to close, soonest. + */ + uty_cmd_loop_close(vio, curtains) ; - /* If not already closing, set closing and transfer to the death watch - * list -- turn off any "monitor" status immediately. + /* Close all vin including the vin_base. + * + * Note that the vin_base is closed, but is still on the vin stack. */ - if (vio->state == vf_open) - { - vio->state = vf_closing ; + do + uty_vin_pop(vio, true, NULL) ; /* final close, discard context */ + while (vio->vin != vio->vin_base) ; - /* Transfer to the death-watch */ - sdl_del(vio_list_base, vio, vio_list) ; - sdl_push(vio_death_watch, vio, vio_list) ; + /* Close all the vout excluding the vout_base. */ + while (vio->vout != vio->vout_base) + uty_vout_pop(vio, true) ; /* final close */ + + /* If command loop is still running, this is as far as can go. + * + * The command loop will hit the vc_close_trap or execute CMD_CLOSE, and + * that will cause this function to be called again, in vc_stopped state. + * + * Save the close_reason for later. + */ + if ((vio->state == vc_running) || (vio->state == vc_close_trap)) + return ; - /* TODO turn off any "monitor" IMMEDIATELY. */ + /* If the vout_base is not closed, try to output the close reason, + * if any. + */ + if ((vio->close_reason != NULL) && (vio->vout_base->vout_state != vf_closed)) + uty_vout_close_reason(vio->vout_base, vio->close_reason) ; - /* 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) ; + /* Now final close the vout_base. + * + * Note that the vout_base will be closed, but on the vout stack with an + * empty obuf... just in case TODO ? + */ + uty_vout_pop(vio, true) ; /* final close */ + + assert(vio->obuf == vio->vout_base->obuf) ; + vio_fifo_clear(vio->obuf, true) ; /* and discard any marks */ + + /* All should now be very quiet indeed. */ + if (vty_debug) + { assert(vio->vin == vio->vin_base) ; - uty_vf_read_close(vio->vin) ; + assert(vio->vin_depth == 0) ; + assert(vio->real_depth == 0) ; + + assert(vio->vout == vio->vout_base) ; + assert(vio->vout_depth == 0) ; + + assert(vio->vin->vin_state == vf_closed) ; + assert(vio->vout->vout_state == vf_closed) ; } ; - /* 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". + /* Can dispose of these now -- leave vin/vout for final disposition */ + vio->vty->exec = cmd_exec_free(vio->vty->exec) ; + vio->ebuf = vio_fifo_reset(vio->ebuf, free_it) ; + + /* Command loop is not running, so can place on death watch for final + * disposition. */ - vf_next = vio->vout_closing ; - while (vf_next != NULL) + if (vio->state == vc_stopped) { - vio_vf vf = vf_next ; - vf_next = ssl_next(vf, vout_next) ; + vio->state = vc_closed ; - uty_vf_write_close(vf, final) ; /* removes from vout_closing if - is now completely closed. */ + sdl_del(vio_live_list, vio, vio_list) ; + sdl_push(vio_death_watch, vio, vio_list) ; } ; - /* If is vio->cmd_running, this is as far as we can go this time. */ - if (vio->cmd_running) - return false ; + assert(vio->state == vc_closed) ; /* thank you and good night */ +} ; - /* Make sure no longer holding the config symbol of power */ - uty_config_unlock(vio->vty, NULL_NODE) ; +/*------------------------------------------------------------------------------ + * Dispose unwanted vty. + * + * Called from deathwatch -- must already be removed from deathwatch list. + */ +static vty_io +uty_dispose(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; - /* 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) ; + assert(vio->state == vc_closed) ; - /* 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 */ + /* Stop pointing at vout_base obuf */ + vio->obuf = NULL ; - /* 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)) ; + /* Clear out vout and vin (may be the same) */ + assert(vio->vin == vio->vin_base) ; + vio->vin_base = uty_vf_free(vio->vin_base) ; - if (vio->vin != vio->vout) - vio->vin = uty_vf_free(vio->vin) ; - else - vio->vin = NULL ; - vio->vin_base = NULL ; + assert(vio->vout == vio->vout_base) ; + if (vio->vout != vio->vin) + vio->vout_base = uty_vf_free(vio->vout_base) ; - assert((vio->vout == vio->vout_base) && (vio->vout_depth == 0)) ; + vio->vin = NULL ; + vio->vout = NULL ; - vio->vout = uty_vf_free(vio->vout) ; + /* Remainder of contents of the vio */ + vio->ebuf = vio_fifo_reset(vio->ebuf, free_it) ; - vio->close_reason = qs_reset(vio->close_reason, free_it) ; + /* Really cannot be a monitor any more ! */ + assert(!vio->monitor) ; + vio->mbuf = vio_fifo_reset(vio->mbuf, free_it) ; - vio->obuf = NULL ; + XFREE(MTYPE_VTY, vio) ; - vio->state = vf_closed ; - return true ; + return NULL ; } ; /*------------------------------------------------------------------------------ - * Pop and close the top of the vin stack -- MUST NOT be vin_base. + * Close top of the vin stack and pop when done -- see uty_vf_read_close(). * - * 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 succeeds in closing, unless is vin_base, pops the vin stack and if this + * is read-only will free the vio_vf and all its contents. (So if this is + * vin_base, it is left on the stack, but vf_closed/vf_closing.) * - * If this is read-only will free the vio_vf and all its contents. + * If the given context is not NULL, having popped the vin stack, the context + * in the new top of stack is restored to the given context. * - * NB: a uty_cmd_prepare() is required before command processing can continue. + * On final close, will completely close the input, even if errors occur (and + * no errors are posted). * - * That is not done here because this may be called from outside the - * command loop -- in particular by uty_close(). + * Returns: CMD_SUCCESS -- input completely closed + * CMD_WAITING -- waiting for input to close <=> not-blocking + * (not if final) + * CMD_IO_ERROR -- error or timeout + * + * NB: a uty_cmd_prepare() is required before command processing can continue. */ -extern void -uty_vin_close(vty_io vio) +extern cmd_return_code_t +uty_vin_pop(vty_io vio, bool final, cmd_context context) { - vio_vf vf ; + cmd_return_code_t ret ; + + VTY_ASSERT_LOCKED() ; + + ret = uty_vf_read_close(vio->vin, final) ; + + if ((ret == CMD_SUCCESS) || final) + { + assert(vio->vin->vin_state == vf_closed) ; + + if (vio->vin_depth > 1) + { + vio_vf vf ; - vf = vio->vin ; + vf = ssl_pop(&vf, vio->vin, vin_next) ; + --vio->vin_depth ; - assert(vf != vio->vin_base) ; - assert(vio->vin_depth > 0) ; + if (vf->vout_state == vf_closed) + uty_vf_free(vf) ; + } + else + { + assert(vio->vin == vio->vin_base) ; + vio->vin_depth = 0 ; /* may already have been closed */ + } ; - ssl_del_head(vio->vin, vin_next) ; - --vio->vin_depth ; + if (vio->real_depth > vio->vin_depth) + vio->real_depth = vio->vin_depth ; - uty_vf_read_close(vf) ; + if (vio->vin->context != NULL) + { + if (context != NULL) + vio->vin->context = cmd_context_restore(context, vio->vin->context); + else + vio->vin->context = cmd_context_free(vio->vin->context, false) ; + /* Not a copy */ + } ; + } ; + + return ret ; } ; /*------------------------------------------------------------------------------ - * Pop and close the top of the vout stack -- MUST NOT be vout_base. + * Close top of the vout stack and pop when done -- see uty_vf_write_close(). + * + * If is vout_base, does not completely close, unless is "final" -- if not + * final, CMD_SUCCESS means that the output buffers are empty and the + * vout_depth has been reduced, but the vio_vf is still writeable, held in + * vf_closing state. * - * Moves the vio_vf to the vout_closing list. + * Unless is vout_base, pops the vout stack. If this is write-only will free + * the vio_vf and all its contents. * - * Before closing, push any outstanding output. + * If this is vout_base, does not actually close the vfd and does not close + * the vout side of the vf. This leaves an active vio->obuf (inter alia.) + * + * Before closing, discard anything after end_mark, then push any outstanding + * output. * * 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. + * vf and free it (except for vout_base). + * + * Returns: CMD_SUCCESS -- input completely closed + * CMD_WAITING -- waiting for input to close <=> not-blocking + * CMD_IO_ERROR -- error or timeout * * 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(). + * + * However, ensures that vio->obuf is up to date ! */ -extern void -uty_vout_close(vty_io vio, bool final) +extern cmd_return_code_t +uty_vout_pop(vty_io vio, bool final) +{ + cmd_return_code_t ret ; + + VTY_ASSERT_LOCKED() ; + + ret = uty_vf_write_close(vio->vout, final) ; + + if ((ret == CMD_SUCCESS) || final) + { + if (vio->vout_depth > 1) + { + vio_vf vf ; + + vf = ssl_pop(&vf, vio->vout, vout_next) ; + --vio->vout_depth ; + + uty_vf_free(vf) ; + } + else + { + assert(vio->vout == vio->vout_base) ; + if (final) + assert(vio->vout->vout_state == vf_closed) ; + + vio->vout_depth = 0 ; /* may already have been closed */ + + assert(vio->vin_depth == 0) ; + vio->vout->depth_mark = 0 ; /* align with the end stop */ + } ; + + vio->obuf = vio->vout->obuf ; + } ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Try to output the close reason to the vout_base. + * + * This is the final act for the vout_base. Will attempt to output unless + * the thing is completely closed. + * + * Should by now not be any buffered output -- but if there is, that is + * discarded before the close reason is output. + * + * Any actual output may be done when the vout_base is finally closed. + */ +static void +uty_vout_close_reason(vio_vf vf, const char* reason) { - vio_vf vf ; + if ((vf->vout_state == vf_closed) || (reason == NULL) || (*reason == '\0')) + return ; + + vio_fifo_clear(vf->obuf, true) ; /* clear any markers, too */ + + if (vf->vout_type < VOUT_SPECIALS) + assert(vf->vfd != NULL) ; /* make sure */ + + switch(vf->vout_type) + { + case VOUT_NONE: + zabort("invalid VOUT_NONE") ; + break ; - vf = vio->vout ; + case VOUT_TERM: + uty_term_close_reason(vf, reason) ; + break ; - assert(vf != vio->vout_base) ; - assert(vio->vout_depth > 0) ; + case VOUT_VTYSH: + break ; + + case VOUT_FILE: + case VOUT_PIPE: + case VOUT_SHELL_ONLY: + break ; + + case VOUT_CONFIG: + break ; - ssl_del_head(vio->vout, vout_next) ; - --vio->vout_depth ; + case VOUT_DEV_NULL: + break ; - ssl_push(vio->vout_closing, vf, vout_next) ; + case VOUT_STDOUT: + fprintf(stdout, "%% %s\n", reason) ; + break ; - vio->obuf = vio->vout->obuf ; + case VOUT_STDERR: + fprintf(stderr, "%% %s\n", reason) ; + break ; - uty_vf_write_close(vf, final) ; + default: + zabort("unknown VOUT type") ; + } ; } ; /*============================================================================== @@ -742,7 +856,7 @@ uty_vout_close(vty_io vio, bool final) * * This leaves most things unset/NULL/false. Caller will need to: * - * - uty_vin_open() and/or uty_vout_open() + * - uty_vin_push() and/or uty_vout_push() * * - once those are done, the following optional items remain to be set * if they are required: @@ -750,23 +864,30 @@ uty_vout_close(vty_io vio, bool final) * 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. + * - for pipes the child state needs to be set, and for out pipes the return + * state needs to be set in this vio_vf (the master) and in the next + * vout (the slave). * * 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. + * type (recommend vfd_none) and io_type are ignored. * * 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. + * be set up without an fd. * * 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(). + * is for input only. The required VOUT_STDERR will be set by + * uty_vout_push(). + * + * NB: if the parent vio is blocking, then the vf will be blocking, and so + * will any vfd. An individual vf may be set blocking by setting + * vfd_io_blocking in the io_type. + * + * The vty stuff opens all files, pipes etc. non-blocking. A non-blocking + * vf simulates blocking by local pselect. As far as the vfd level is + * concerned, once the file, pipe etc. is open, there is no difference + * between blocking and non-blocking except that non-blocking vfd are not + * allowed to set read/write ready. */ extern vio_vf uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type, @@ -776,9 +897,6 @@ uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type, VTY_ASSERT_LOCKED() ; - if (vio->blocking) - io_type |= vfd_io_blocking ; - vf = XCALLOC (MTYPE_VTY, sizeof(struct vio_vf)) ; /* Zeroising the structure has set: @@ -786,49 +904,61 @@ uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type, * 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() + * vin_type = VIN_NONE -- see uty_vin_push() + * vin_state = vf_closed -- see uty_vin_push() + * vin_next = NULL -- see uty_vin_push() * - * cli = NULL -- no CLI, yet + * context = NULL -- see uty_vin_new_context() * - * ibuf = NULL -- none, yet -- see uty_vin_open() + * cli = NULL -- no CLI, yet * - * cl = zeros -- empty qstring (embedded) - * line_complete = false -- see uty_vout_open() + * ibuf = NULL -- none, yet -- see uty_vin_push() + * cl = NULL -- none, yet -- see uty_vin_push() + * line_complete = false -- see uty_vin_push() * line_number = 0 -- nothing yet - * line_step = 0 -- see uty_vout_open() + * line_step = 0 -- see uty_vin_push() * - * parse_type = cmd_parse_standard - * reflect_enabled = false + * vout_type = VOUT_NONE -- see uty_vout_push() + * vout_state = vf_closed -- see uty_vout_push() + * vout_next = NULL -- see uty_vout_push() * - * vout_type = VOUT_NONE -- see uty_vout_open() - * vout_state = vf_closed -- see uty_vout_open() - * vout_next = NULL -- see uty_vout_open() + * pr_master = NULL -- none -- see uty_pipe_write_open() * - * obuf = NULL -- none, yet -- see uty_vout_open() + * obuf = NULL -- none -- see uty_vout_push() * - * out_enabled = X -- set below: true iff vfd_io_write + * depth_mark = 0 -- see uty_vout_push() * * vfd = NULL -- no vfd, yet * - * blocking = false -- default is non-blocking I/O - * closing = false -- definitely not + * blocking = X -- see below + * closing = false -- not on the closing list, yet. * * error_seen = 0 -- no error seen, yet * - * read_on = false - * write_on = false - * * read_timeout = 0 -- none * write_timeout = 0 -- none + * + * child = 0 -- none ) + * terminated = false -- not ) -- see uty_pipe_read/write_open() + * term_status = X -- none ) + * + * pr_state = vf_closed -- no pipe return vfd + * -- see uty_pipe_read/write_open() + * pr_vfd = NULL -- no vfd -- see uty_pipe_read/write_open() + * + * pr_slave = NULL -- none -- see uty_pipe_read/write_open() + * + * pr_only = false -- see uty_pipe_read/write_open() */ confirm((VIN_NONE == 0) && (VOUT_NONE == 0)) ; confirm(vf_closed == 0) ; confirm(QSTRING_INIT_ALL_ZEROS) ; - confirm(cmd_parse_standard == 0) ; - vf->vio = vio ; + if (vio->blocking) + io_type |= vfd_io_blocking ; /* inherit blocking state */ + + vf->vio = vio ; + vf->blocking = (io_type & vfd_io_blocking) != 0 ; if (name != NULL) vf->name = XSTRDUP(MTYPE_VTY_NAME, name) ; @@ -836,35 +966,58 @@ uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type, if (fd >= 0) vf->vfd = vio_vfd_new(fd, type, io_type, vf) ; - vf->out_enabled = ((io_type & vfd_io_write) != 0) ; - return vf ; } ; /*------------------------------------------------------------------------------ - * Close the read side of the given vio_vf. + * Close the read side of the given vio_vf -- if can. * * 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. + * For not-final close, if there is any other related I/O, then waits until + * that has completed. For example, with a VIN_PIPE: * - * Note that read_close is effective immediately, there is no delay as there is - * with write_close -- so no need for a "final" option. + * * waits for any return input to complete, and pushes it to the relevant + * vout. + * + * * waits to collect the child, and its termination state. + * + * This means that a not-final close may return errors. For not-blocking vf + * may return waiting state. For blocking vf, may block and may later return + * timeout error. + * + * For a final close, if there is any related I/O then will attempt to complete + * it -- but will give up if would block. I/O errors on final close are + * ignored. A final close may be called in terminating state, so does not + * do any "vty_cmd_signal". + * + * Returns: CMD_SUCCESS -- is all closed + * CMD_WAITING -- cannot close at the moment <=> non-blocking + * (not if "final") + * CMD_IO_ERROR -- something went wrong + * + * NB: on "final" close returns CMD_SUCCESS no matter what happened, and all + * input will have been closed down, and the vf closed. */ -static void -uty_vf_read_close(vio_vf vf) +static cmd_return_code_t +uty_vf_read_close(vio_vf vf, bool final) { - assert(vf->vin_state != vf_closed) ; + cmd_return_code_t ret ; + + VTY_ASSERT_CAN_CLOSE_VF(vf) ; + + ret = CMD_SUCCESS ; + + if (vf->vin_state == vf_closed) + return ret ; /* quit if already closed */ + + vf->vin_state = vf_closing ; /* TODO wipes out error etc ? */ /* 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) { @@ -873,60 +1026,126 @@ uty_vf_read_close(vio_vf vf) break ; case VIN_TERM: - uty_term_read_close(vf) ; + ret = uty_term_read_close(vf, final) ; break ; case VIN_VTYSH: + zabort("tba VIN_VTYSH") ; + break ; + + case VIN_CONFIG: + ret = uty_config_read_close(vf, final) ; break ; case VIN_FILE: - uty_file_read_close(vf) ; + ret = uty_file_read_close(vf, final) ; break ; case VIN_PIPE: - break ; - - case VIN_CONFIG: + ret = uty_pipe_read_close(vf, final) ; break ; case VIN_DEV_NULL: + ret = CMD_SUCCESS ; break ; default: zabort("unknown VIN type") ; } ; - if ((vf->vout_type == VOUT_NONE) && (vf != vf->vio->vin_base)) - uty_vf_free(vf) ; + if ((ret == CMD_SUCCESS) || final) + { + vf->vin_state = vf_closed ; + assert(vf->pr_state == vf_closed) ; + } ; + + return ret ; } ; /*------------------------------------------------------------------------------ * Close the write side of the given vio_vf, if can. * - * Pushes any outstanding output -- non-blocking if not a blocking vty. + * Discards anything beyond the current end_mark, and clears the end_mark. + * + * Pushes any outstanding output, and if is pipe will attempt to collect + * the child. On not-final close, if cannot complete everything, will return + * CMD_WAITING for non-blocking, or block (and may time out). On final close, + * will do as much as possible without blocking, and will then close even if + * there is outstanding output or child has not been collected. + * + * For not-final close, for example, with a VOUT_PIPE: * - * 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. + * * waits for any return input to complete, and pushes it to the relevant + * vout. * - * If output is empty, or if final, close the vf, then if it is on the - * vout_closing list, remove and free it. + * * waits to collect the child, and its termination state. * - * Returns whether output was empty or not. + * This means that a not-final close may return errors. + * + * For a final close, if there is any related I/O then will attempt to complete + * it -- but will give up if would block. I/O errors on final close are + * ignored. A final close may be called in terminating state, so does not + * do any "vty_cmd_signal". + * + * If this is the vout_base, unless "final", does NOT actually close the vf or + * the vfd -- so the vout_base will still work -- but is marked vf_closing ! + * The effect is, essentially, to try to empty out any buffers, but not to + * do anything that would prevent further output. This is used so that a + * command loop can close the vout_base in the usual way, waiting until all + * output is flushed, but when uty_close() is finally called, it can output + * any close reason there is to hand. + * + * Returns: CMD_SUCCESS -- is all closed + * CMD_WAITING -- cannot close at the moment <=> non-blocking + * (not if "final") + * CMD_IO_ERROR -- something went wrong + * + * NB: on "final" all output will have been closed down, and the vfd closed, + * no matter what the return value says. + * + * NB: must not have open vins at this or a higher level in the stack. + * + * NB: does not at this stage discard the obuf. */ -static bool +static cmd_return_code_t uty_vf_write_close(vio_vf vf, bool final) { - bool empty ; + cmd_return_code_t ret ; + bool base ; - assert((vf->vout_state != vf_closed) && (vf->vin_state == vf_closed)) ; + VTY_ASSERT_CAN_CLOSE_VF(vf) ; - /* Worry about remaining return input from vout pipe TODO */ + ret = CMD_SUCCESS ; - /* Worry about appending the close reason to the vout_base TODO */ + if (vf->vout_state == vf_closed) + return ret ; /* quit if already closed */ - /* Now the vout_type specific clean up. */ - empty = false ; + vf->vout_state = vf_closing ; /* TODO wipes out error etc ? */ + base = (vf == vf->vio->vout_base) ; + + /* Must close vin before closing vout at the same level. + * + * Note that cannot currently be a pipe return slave, because if was + * slave to a VOUT_PIPE/VOUT_SHELL_ONLY that vout must have been closed + * already, and if was slave to a VIN_PIPE, then that too will have been + * closed already (because of the above). + */ + assert( (vf->vio->vin_depth < vf->vio->vout->depth_mark) + || (vf->vio->vout_depth ==0) ) ; + assert(vf->pr_master == NULL) ; + + /* If there is anything in the obuf beyond the end_mark, then it is + * assumed to be surplus to requirements, and we clear the end_mark. + */ + vio_fifo_back_to_end_mark(vf->obuf, false) ; + + /* The vout_type specific close functions will attempt to write + * everything away. + * + * If "final", will only keep going until blocks -- at which point will + * bring everything to a shuddering halt. + */ switch(vf->vout_type) { case VOUT_NONE: @@ -934,50 +1153,54 @@ uty_vf_write_close(vio_vf vf, bool final) break ; case VOUT_TERM: - empty = uty_term_write_close(vf, final) ; + ret = uty_term_write_close(vf, final, base) ; break ; case VOUT_VTYSH: break ; case VOUT_FILE: - empty = uty_file_write_close(vf, final) ; + ret = uty_file_write_close(vf, final, base) ; break ; case VOUT_PIPE: + case VOUT_SHELL_ONLY: + ret = uty_pipe_write_close(vf, final, base, + vf->vout_type == VOUT_SHELL_ONLY) ; break ; case VOUT_CONFIG: + ret = uty_file_write_close(vf, final, base) ; /* treat as file */ break ; case VOUT_DEV_NULL: case VOUT_STDOUT: case VOUT_STDERR: - empty = true ; + ret = CMD_SUCCESS ; break ; default: zabort("unknown VOUT type") ; } ; - if (empty || final) + assert(vf->vin_state == vf_closed) ; + + if (((ret == CMD_SUCCESS) && !base) || final) { - /* Do the vfd level close and mark the vf no longer write_open */ + + assert(vf->vio->obuf == vf->obuf) ; + assert(vio_fifo_empty(vf->obuf)) ; + + 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 ; + return ret ; } ; /*------------------------------------------------------------------------------ @@ -992,27 +1215,27 @@ uty_vf_write_close(vio_vf vf, bool final) static vio_vf uty_vf_free(vio_vf vf) { + assert((vf->vin_state == vf_closed) && (vf->vout_state == vf_closed) + && (vf->pr_state == vf_closed)) ; + XFREE(MTYPE_VTY_NAME, vf->name) ; - vf->ibuf = vio_fifo_reset(vf->ibuf, free_it) ; - vf->obuf = vio_fifo_reset(vf->obuf, free_it) ; + assert(vf->cli == NULL) ; + + vf->ibuf = vio_fifo_reset(vf->ibuf, free_it) ; + vf->cl = qs_reset(vf->cl, free_it) ; + vf->obuf = vio_fifo_reset(vf->obuf, free_it) ; - qs_reset(vf->cl, keep_it) ; + vf->context = cmd_context_free(vf->context, false) ; /* not a copy */ - vf->cli = uty_cli_close(vf->cli, true) ; - vf->vfd = vio_vfd_close(vf->vfd) ; /* for completeness */ + vf->vfd = vio_vfd_close(vf->vfd) ; /* for completeness */ + vf->pr_vfd = vio_vfd_close(vf->pr_vfd) ; /* for completeness */ XFREE(MTYPE_VTY, vf) ; return NULL ; } ; -/*------------------------------------------------------------------------------ - * - * - */ - - @@ -1032,7 +1255,7 @@ uty_vf_error(vio_vf vf, const char* what, int err) VTY_ASSERT_CLI_THREAD() ; /* can no longer be a monitor ! *before* any logging ! */ - uty_set_monitor(vio, 0) ; + uty_set_monitor(vio, off) ; /* if this is the first error, log it */ if (vf->error_seen == 0) @@ -1041,13 +1264,16 @@ uty_vf_error(vio_vf vf, const char* what, int err) vf->error_seen = err ; - uzlog(NULL, LOG_WARNING, "%s: %s failed on fd %d: %s", + zlog(NULL, LOG_WARNING, "%s: %s failed on fd %d: %s", type, what, vio_vfd_fd(vf->vfd), errtoa(err, 0).str) ; } ; return -1 ; } ; + + + /*------------------------------------------------------------------------------ * Set required read ready state. Applies the current read timeout. * @@ -1056,31 +1282,31 @@ uty_vf_error(vio_vf vf, const char* what, int err) * Does nothing if: vf->vin_state == vf_closed * or: vf->vfd == NULL * - * Returns new state of vf->read_on + * NB: must NOT be a "blocking" vf */ -extern on_off_b +extern void uty_vf_set_read(vio_vf vf, on_off_b how) { if (vf->vin_state != vf_open) how = off ; - return vf->read_on = (vf->vin_state != vf_closed) - ? vio_vfd_set_read(vf->vfd, how, vf->read_timeout) - : off ; + if (vf->vin_state != vf_closed) + vio_vfd_set_read(vf->vfd, how, vf->read_timeout) ; } ; /*------------------------------------------------------------------------------ - * Set required read ready timeout -- if already read_on, restart it. + * Set required read ready timeout -- if already read on, restart it. * - * Returns new state of vf->read_on + * If this is a blocking vf, will set the timeout value, but since can never + * be "read on", will never attempt to restart any timer ! */ -extern on_off_b +extern void 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 ; + if (!vf->blocking) + uty_vf_set_read(vf, on) ; } ; /*------------------------------------------------------------------------------ @@ -1091,31 +1317,511 @@ uty_vf_set_read_timeout(vio_vf vf, vty_timer_time read_timeout) * Does nothing if: vf->vout_state == vf_closed * or: vf->vfd == NULL * - * Returns new state of vf->write_on + * NB: must NOT be a "blocking" vf */ -extern on_off_b +extern void uty_vf_set_write(vio_vf vf, on_off_b how) { - if (vf->vout_state != vf_open) + if ((vf->vout_state != vf_open) && (vf->vout_state != vf_closing)) how = off ; - return vf->write_on = (vf->vout_state != vf_closed) - ? vio_vfd_set_write(vf->vfd, how, vf->write_timeout) - : off ; + if (vf->vout_state != vf_closed) + vio_vfd_set_write(vf->vfd, how, vf->write_timeout) ; } ; /*------------------------------------------------------------------------------ - * Set required write ready timeout -- if already write_on, restart it. + * Set required write ready timeout -- if already write on, restart it. * - * Returns new state of vf->write_on + * If this is a blocking vf, will set the timeout value, but since can never + * be "write on", will never attempt to restart any timer ! */ -extern on_off_b +extern void 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 ; + if (!vf->blocking) + uty_vf_set_write(vf, on) ; +} ; + +/*============================================================================== + * Child care. + * + * Management of vio_child objects and the vio_childer_list. + * + * When child is registered it is placed on the vio_childer_list. It remains + * there until it is "collected", that is until a waitpid() has returned a + * "report" for the child. + * + * When a parent waits for a child to be collected, it may be in one of three + * states: + * + * TODO + * + * When a child is collected, or becomes overdue, the parent is signalled (if + * required) and the child->awaited state is cleared. + * + * When a parent "dismisses" a child, if it has not yet been collected it is + * "smacked" -- kill(SIGTERM) -- but kept on the register until it is + * collected. When a child which has been collected is dismissed it is freed. + * + * At "curtains" the register may contain children which have been dismissed, + * but not yet collected. Should NOT contain any children with living parents. + * All children remaining on the register are smacked, and all with no living + * parents are freed. (This could leave children on the register, but avoids + * the possibility of a dangling reference from a parent.) + */ +static void uty_child_collected(vio_child child, int report) ; +static vty_timer_time vty_child_overdue(vio_timer timer, void* action_info) ; +static void uty_child_signal_parent(vio_child child) ; +static vio_child uty_child_free(vio_child child) ; + +/*------------------------------------------------------------------------------ + * Set vty_child_signal_nexus() -- if required. + * + * Note that this is set/cleared under VTY_LOCK() and its own mutex. This + * allows it to be read under its own mutex and/or VTY_LOCK(). + */ +extern void +uty_child_signal_nexus_set(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + assert(vio->blocking) ; + + if (!vty_is_cli_thread()) + { + qpt_mutex_lock(vty_child_signal_mutex) ; + + vty_child_signal_nexus = qpn_find_self() ; + + qpt_mutex_unlock(vty_child_signal_mutex) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * If there is a nexus to signal, clear the indicator and signal the + * associated thread. + */ +extern void +vty_child_signal_nexus_signal(void) +{ + qpt_mutex_lock(vty_child_signal_mutex) ; + + if (vty_child_signal_nexus != NULL) + qpt_thread_signal(vty_child_signal_nexus->thread_id, + vty_child_signal_nexus->pselect_signal) ; + + qpt_mutex_unlock(vty_child_signal_mutex) ; +} + +/*------------------------------------------------------------------------------ + * Set vty_child_signal_nexus() -- if required. + * + * Note that this is set/cleared under VTY_LOCK() and its own mutex. This + * allows it to be read under its own mutex and/or VTY_LOCK(). + */ +extern void +uty_child_signal_nexus_clear(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + assert(vio->blocking) ; + + if (!vty_is_cli_thread()) + { + qpt_mutex_lock(vty_child_signal_mutex) ; + + vty_child_signal_nexus = NULL ; + + qpt_mutex_unlock(vty_child_signal_mutex) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * New child. + * + * NB: must register child promptly (under VTY_LOCK, at same time as fork) + * in order to avoid missing the SIG_CHLD ! + */ +extern vio_child +uty_child_register(pid_t pid, vio_vf parent) +{ + vio_child child ; + + VTY_ASSERT_LOCKED() ; + + child = XCALLOC(MTYPE_VTY, sizeof(struct vio_child)) ; + + /* Zeroising has set: + * + * list -- NULLs -- added to vio_childer_list, below + * + * parent -- NULL -- parent vf set, below + * + * pid -- X -- child pid set below + * collected -- false -- not yet collected + * report -- X -- not relevant until collected + * + * awaited -- false -- not waiting for child + * overdue -- false -- child not overdue + * timer -- NULL -- no waiting timer set + */ + + child->parent = parent ; + child->pid = pid ; + + sdl_push(vio_childer_list, child, list) ; + + return child ; +} ; + +/*------------------------------------------------------------------------------ + * Set waiting for child to be collected. + * + * This is for !vf->blocking: set timer and leave, waiting for SIGCHLD event. + */ +extern void +uty_child_awaited(vio_child child, vty_timer_time timeout) +{ + VTY_ASSERT_CLI_THREAD_LOCKED() ; + + assert(child->parent != NULL) ; + assert(!child->parent->blocking) ; + + child->awaited = true ; + + if (child->timer == NULL) + child->timer = vio_timer_init_new(NULL, vty_child_overdue, child) ; + + vio_timer_set(child->timer, timeout) ; +} ; + +/*------------------------------------------------------------------------------ + * See if parent can collect child -- directly. + * + * This is for vf->blocking, + * + * If can, will collect now -- marking collected and taking off the + * vio_childer_list. + * + * If cannot immediately collect, if final mark overdue and return. + * + * Otherwise wait for up to timeout seconds for a suitable SIGCHLD or related + * wake-up signal. + * + * NB: this blocks with the VTY_LOCK() in its hands !! But this is only + * required for configuration file reading... and timeout is limited. + * + * Returns: true <=> collected + * false => timed out or final + */ +extern bool +uty_child_collect(vio_child child, vty_timer_time timeout, bool final) +{ + bool first ; + + VTY_ASSERT_LOCKED() ; + + assert(child->parent != NULL) ; + assert(child->parent->blocking) ; + assert(child->timer == NULL) ; + + assert(child->pid > 0) ; + + first = true ; + while (1) + { + pid_t pid ; + int report ; + + qps_mini_t qm ; + sigset_t* sig_mask = NULL ; + + if (child->collected) + return true ; /* have collected */ + + pid = waitpid(child->pid, &report, WNOHANG) ; + + if (pid == child->pid) + { + /* Collected the child */ + uty_child_collected(child, report) ; + + return true ; /* have collected */ + } ; + + if (pid != 0) + { + int err = 0 ; + + /* With the exception of EINTR, all other returns are, essentially, + * impossible... + * + * (1) ECHLD means that the given pid is not a child... + * ...which is impossible if not collected -- but treat as + * "overdue". + * + * (2) only other known error is EINVAL -- invalid options... + * absolutely impossible. + * + * (3) some pid other than the given child is an invalid response ! + */ + if (pid < 0) + { + if (errno == EINTR) + continue ; + + err = errno ; + + zlog_err("waitpid(%d) returned %s", child->pid, + errtoa(err, 0).str) ; + } + else + zlog_err("waitpid(%d) returned pid=%d", child->pid, pid) ; + + if (err != ECHILD) + zabort("impossible return from waitpid()") ; + + final = true ; /* treat ECHLD as last straw ! */ + } ; + + /* Waiting for child. */ + if (final) + { + child->overdue = true ; + return false ; /* overdue */ + } ; + + /* Need to wait -- if this is the first time through, prepare for + * that. + */ + if (first) + { + qps_mini_set(qm, -1, 0, 6) ; + + if (vty_is_cli_thread()) + sig_mask = NULL ; + else + sig_mask = vty_child_signal_nexus->pselect_mask ; + + first = false ; + } ; + + /* Wait on pselect. */ + if (qps_mini_wait(qm, sig_mask, true) == 0) + final = true ; /* timed out => now final */ + } ; +} ; + +/*------------------------------------------------------------------------------ + * Dismiss child -- if not collected, smack but leave to be collected in + * due course (or swept up at "curtains"). + */ +extern vio_child +uty_child_dismiss(vio_child child, bool final) +{ + VTY_ASSERT_LOCKED() ; + + if (child != NULL) + { + if (!child->collected) + { + assert(child->pid > 0) ; + + kill(child->pid, SIGKILL) ; /* hasten the end */ + + if (final) + { + assert(child->parent == NULL) ; + sdl_del(vio_childer_list, child, list) ; + child->collected = true ; /* forceably */ + } ; + + child->overdue = true ; /* too late for parent */ + } ; + + child->parent = NULL ; /* orphan from now on */ + child->awaited = false ; /* nobody waiting */ + + if (child->collected) + uty_child_free(child) ; + } ; + + return NULL ; +} ; + +/*------------------------------------------------------------------------------ + * At "curtains" -- empty out anything left in the child register. + * + * The only children that can be left are dismissed children that have yet to + * be collected. + */ +extern void +vty_child_close_register(void) +{ + while (vio_childer_list != NULL) + uty_child_dismiss(vio_childer_list, true) ; /* final */ +} ; + +/*------------------------------------------------------------------------------ + * See whether any children are ready for collection, and check each one + * against the register. + * + * Perform waitpid( , , WNOHANG) until no child is returned, and process + * each one against the register. + * + * The is done when a SIGCHLD is route through the event mechanism. + * + * If another SIGCHLD occurs while this is being done, that will later cause + * another call of this function -- at worst it may find that the child in + * question has already been collected. + * + * This is also done when about to block waiting for a child. + * + * Set any children that can be collected, collected and signal to any parents + * that their children are now ready. + */ +extern void +uty_sigchld(void) +{ + VTY_ASSERT_CLI_THREAD_LOCKED() ; + + while (1) + { + vio_child child ; + pid_t pid ; + int report ; + + pid = waitpid(-1, &report, WNOHANG) ; + + if (pid == 0) + break ; + + if (pid < 0) + { + if (errno == EINTR) + continue ; /* loop on "Interrupted" */ + + if (errno != ECHILD) /* returns ECHLD if no children */ + zlog_err("waitpid(-1) returned %s", errtoa(errno, 0).str) ; + + break ; + } ; + + child = vio_childer_list ; + while (1) + { + if (child == NULL) + { + zlog_err("waitpid(-1) returned pid %d, which is not registered", + pid) ; + break ; + } ; + + if (child->pid == pid) + { + /* Have collected child. + * + * Remove from the vio_childer_list, set collected flag. + * + * We can leave any timer object -- if it goes off it will be + * ignored, because the child is no longer awaited. Timer will + * be discarded when the child is dismissed/freed. + * + * If no parent, can free child object now. + */ + uty_child_collected(child, report) ; + + if (child->parent == NULL) + uty_child_free(child) ; + else if (child->awaited) + uty_child_signal_parent(child) ; + + break ; + } ; + + child = sdl_next(child, list) ; + } ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Set the child collected and set the report. + * + * Remove from the vio_childer_list -- is now either back in the hands of the + * parent, or ready to be freed. + */ +static void +uty_child_collected(vio_child child, int report) +{ + assert(!child->collected) ; /* can only collect once */ + + sdl_del(vio_childer_list, child, list) ; + + child->collected = true ; + child->report = report ; +} ; + +/*------------------------------------------------------------------------------ + * Set child as overdue -- vio_timer action routine. + * + * NB: the timer may go off after the child has been collected, but before the + * parent has got round to stopping the timer. + */ +static vty_timer_time +vty_child_overdue(vio_timer timer, void* action_info) +{ + vio_child child ; + + VTY_LOCK() ; + + child = action_info ; + assert(timer == child->timer) ; + + if (child->awaited) + { + child->overdue = true ; + uty_child_signal_parent(child) ; + } ; + + VTY_UNLOCK() ; + + return 0 ; /* stop timer */ +} ; + +/*------------------------------------------------------------------------------ + * Signal that child is ready -- collected or overdue. + * + * Must be "awaited" -- so not "blocking" + */ +static void +uty_child_signal_parent(vio_child child) +{ + assert(child->awaited && (child->parent != NULL)) ; + + assert(!child->parent->vio->blocking) ; + + child->awaited = false ; + uty_cmd_signal(child->parent->vio, CMD_SUCCESS) ; +} ; + +/*------------------------------------------------------------------------------ + * Free the child -- caller must ensure that any parent has disowned the child, + * and that it is collected (so not on the vio_childer_list). + */ +static vio_child +uty_child_free(vio_child child) +{ + if (child != NULL) + { + assert(child->collected && (child->parent == NULL)) ; + + child->timer = vio_timer_reset(child->timer, free_it) ; + XFREE(MTYPE_VTY, child) ; /* sets child = NULL */ + } ; + + return child ; } ; /*============================================================================== diff --git a/lib/vty_io.h b/lib/vty_io.h index 5db12ca2..24790526 100644 --- a/lib/vty_io.h +++ b/lib/vty_io.h @@ -36,6 +36,7 @@ #include "thread.h" #include "command_execute.h" #include "qstring.h" +#include "list_util.h" /*============================================================================== * Structures and other definitions for the top level VTY I/O. @@ -92,9 +93,9 @@ enum vio_out_type /* Command output */ VOUT_VTYSH, /* a vty_shell output pipe */ VOUT_FILE, /* ordinary file */ - VOUT_PIPE, /* pipe (to child process) */ + VOUT_PIPE, /* pipe (to child process, and back again) */ - VOUT_CONFIG, /* config file ?? */ + VOUT_CONFIG, /* config file */ /* The VOUT types >= VOUT_SPECIALS do not have an associated fd. * @@ -105,32 +106,73 @@ enum vio_out_type /* Command output */ VOUT_DEV_NULL = VOUT_SPECIALS, /* black hole output */ + VOUT_SHELL_ONLY, /* pipe (but only back from child process */ + VOUT_STDOUT, /* stdout */ VOUT_STDERR, /* stderr */ }; typedef enum vio_out_type vio_out_type_t ; /*------------------------------------------------------------------------------ - * 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. + * State of a vf -- has separate state for vin/vout. */ enum vf_state { - vf_closed = 0, /* not active -- may be discarded */ + vf_closed = 0, /* the vf has not been opened, or has been + * completely closed -- there will be no vfd. */ + + vf_open, /* the vf has been opened, and any required vfd + * is open and I/O is possible. */ + + vf_eof, /* for a vin: end of file has been reached or + * input has been terminated. The vfd may have + * been closed -- but in any case no further + * I/O should be attempted, and any buffered + * input will be discarded. */ - vf_open = 1, /* open for business */ + vf_closing, /* for a vout: open, but in process of closing. + * May still output stuff. */ - vf_eof = 2, /* open, but at eof */ - vf_error = 3, /* open, but error reported */ + vf_error, /* an I/O error has been reported. + * The vfd may or may not have been closed and + * I/O may or may not be possible TODO */ - vf_closing = 4, /* open, but in process of closing */ + vf_timed_out, /* a timout has been reported. + * The vfd may or may not have been closed and + * I/O may or may not be possible TODO */ } ; typedef enum vf_state vf_state_t ; /*------------------------------------------------------------------------------ + * vio_child structure. + * + * Lives on the vio_childer_list until collected or "curtains". + * + */ +typedef enum vio_child_await vio_child_await_t ; + +struct vio_vf ; /* Forward reference */ +typedef struct vio_vf* vio_vf ; + +typedef struct vio_child* vio_child ; + +struct vio_child +{ + struct dl_list_pair(vio_child) list ; /* in the list of children */ + + vio_vf parent ; + + pid_t pid ; + bool collected ; /* waitpid() done */ + int report ; /* from waitpid() */ + + bool overdue ; /* patience exhausted */ + + bool awaited ; /* if child is awaited -- !vf->blocking */ + vio_timer timer ; /* limit the waiting time */ +} ; + +/*------------------------------------------------------------------------------ * vty_vf -- "vty file" structure * * A vio_vf may be a read, write or read/write object. @@ -148,8 +190,6 @@ typedef enum vf_state vf_state_t ; struct vty_io ; /* Forward reference */ typedef struct vty_io* vty_io ; -typedef struct vio_vf* vio_vf ; - struct vio_vf { vty_io vio ; /* parent */ @@ -162,28 +202,28 @@ struct vio_vf vf_state_t vin_state ; vio_vf vin_next ; /* list of inputs */ + cmd_context context ; /* pushed exec->context. */ struct vty_cli* cli ; /* NULL if not a VTY_TERMINAL ! */ vio_fifo ibuf ; /* input fifo (if required) */ - qstring_t cl ; /* command line buffer */ + qstring 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 */ - cmd_parse_type_t parse_type ; /* iff vin_type != VIN_NONE */ - bool reflect_enabled ; /* false if vin_type == VIN_NONE */ - /* Output side. */ vio_out_type_t vout_type ; vf_state_t vout_state ; vio_vf vout_next ; /* list of outputs */ + vio_vf pr_master ; /* receiving return stuff from there */ + vio_fifo obuf ; /* output fifo (if required) */ - bool out_enabled ; /* false if vout_type == VOUT_NONE */ + uint depth_mark ; /* depth of this vout */ /* I/O */ @@ -193,12 +233,19 @@ struct vio_vf 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 ; + /* Pipe extras */ + + vio_child child ; /* state of child */ + + vf_state_t pr_state ; /* iff pipe */ + vio_vfd pr_vfd ; /* if pr_state == vf_open */ + + vio_vf pr_slave ; /* sending return stuff to there */ + + bool pr_only ; /* no vfd */ } ; enum vty_readiness /* bit significant */ @@ -206,11 +253,36 @@ enum vty_readiness /* bit significant */ not_ready = 0, read_ready = BIT(0), write_ready = BIT(1), /* may take precedence */ - now_ready = BIT(2) } ; typedef enum vty_readiness vty_readiness_t ; /*------------------------------------------------------------------------------ + * State of a vty command loop. + */ +enum vc_state +{ + vc_null = 0, /* the command loop has not yet been entered. */ + + vc_running, /* the command loop is running, and the vty is + * in its hands. */ + + vc_io_error_trap, /* the command loop is running, but an I/O + * error has been posted. */ + + vc_close_trap, /* the command loop is running, but the vty is + * reset and must be closed */ + + vc_waiting, /* the command loop is waiting for I/O. */ + + vc_stopped, /* the command loop has stopped, the vty is due + to be closed (loop cannot run again) */ + + vc_closed, /* the command loop has finished and the vty + * has been closed. */ +} ; +typedef enum vc_state vc_state_t ; + +/*------------------------------------------------------------------------------ * The vty_io structure * * The main elements of the vty_io object are the vin and vout stacks. @@ -223,7 +295,7 @@ typedef enum vty_readiness vty_readiness_t ; * * * - * "cmd_running" means that the VTY is in hands of (or has been passed to) + * "cmd_running" means that the VTY is in hands of (or has been passed to) TODO * a command loop -- the VTY cannot be fully closed until that is no * longer the case. * @@ -237,7 +309,7 @@ typedef enum vty_readiness vty_readiness_t ; * * - any further attempts to read will give "eof" * - * - there may be a command in execution -- see "cmd_running". + * - there may be a command in execution -- see "cmd_running". TODO * * - further writing will be honoured. * @@ -265,62 +337,82 @@ struct vty_io /* typedef appears above */ vio_vf vin_base ; uint vin_depth ; + uint real_depth ; /* less than vin_depth when closing */ + vio_vf vout ; /* vout stack */ vio_vf vout_base ; uint vout_depth ; - vio_vf vout_closing ; /* vout closing list */ + uint depth_mark ; /* vin_depth before pipes opened */ + + /* Error handling */ + bool err_hard ; /* eg I/O error */ + vio_fifo ebuf ; /* buffer for error message */ /* List of all vty_io objects */ struct dl_list_pair(vty_io) vio_list ; - /* List of all vty_io that are in monitor state */ - struct dl_list_pair(vty_io) mon_list ; - - /* VTY state + /* State * * "blocking" is set for configuration reading VTY, so that everything is * done with blocking I/O. * - * "half_closed" 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. - * - * "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. - * - * "closed" means the vty has been fully and finally closed. - * - * - any further attempts to write will be ignored, but return instant - * success. + * "state" as described above. + */ + bool blocking ; /* => all I/O is blocking. */ + + vc_state_t state ; + + char* close_reason ; /* MTYPE_TMP (if any) */ + + /* For ease of output, pointer to current vout->obuf * - * - the file/socket has been fully closed. + * Even when the vty is almost closed, there will remain a valid obuf, + * though anything sent to it under those conditions will be discarded. + */ + vio_fifo obuf ; + + /* The following is for "vty monitor". * - * - the VTY and all attached structures can be reaped by the death_watch. + * With the exception of the "monitor" flag, need the LOG_MUTEX in order + * to change any of this. */ - bool blocking ; /* => all I/O is blocking. */ + bool monitor ; /* is in monitor state */ - bool cmd_running ; /* => cannot be fully closed */ + bool mon_kick ; /* vty needs a kick */ + int maxlvl ; /* message level wish to see */ - vf_state_t state ; + vio_fifo mbuf ; /* monitor output pending */ - qstring close_reason ; /* message to be sent, once all other - output has completed, giving reason - for closing the VTY. */ + /* List of all vty_io that are in monitor state */ + struct dl_list_pair(vty_io) mon_list ; +} ; - /* For ease of output, pointer to current vout->obuf */ - vio_fifo obuf ; /* NULL => no vout ! */ +/*============================================================================== + * Assertions for suitable state to close things ! + */ +Inline void +VTY_ASSERT_CAN_CLOSE(vty vty) +{ + if (vty_debug) + { + VTY_ASSERT_LOCKED() ; + + if (!vty->vio->blocking && !vty_is_cli_thread()) + VTY_ASSERT_FAILED() ; + } ; +} ; + +Inline void +VTY_ASSERT_CAN_CLOSE_VF(vio_vf vf) +{ + if (vty_debug) + { + VTY_ASSERT_LOCKED() ; + + if (!vf->blocking && !vty_is_cli_thread()) + VTY_ASSERT_FAILED() ; + } ; } ; /*============================================================================== @@ -334,35 +426,46 @@ 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_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 vty uty_new (vty_type_t type, node_type_t node) ; +extern void uty_close(vty_io vio, const char* reason, bool curtains) ; 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, +extern void uty_vin_new_context(vty_io vio, cmd_context context, + qpath file_here) ; +extern void uty_vin_push(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, +extern void uty_vout_push(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 cmd_return_code_t uty_vin_pop(vty_io vio, bool final, + cmd_context context) ; +extern cmd_return_code_t uty_vout_pop(vty_io vio, bool final) ; + extern vio_vf uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type, vfd_io_type_t io_type) ; -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_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 void uty_vf_set_read(vio_vf vf, on_off_b on) ; +extern void uty_vf_set_read_timeout(vio_vf vf, vty_timer_time read_timeout) ; +extern void uty_vf_set_write(vio_vf vf, on_off_b on) ; +extern void 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 vio_child uty_child_register(pid_t pid, vio_vf parent) ; +extern void vty_child_close_register(void) ; +extern void uty_child_awaited(vio_child child, vty_timer_time timeout) ; +extern bool uty_child_collect(vio_child child, vty_timer_time timeout, + bool final) ; +extern vio_child uty_child_dismiss(vio_child child, bool final) ; +extern void uty_sigchld(void) ; + +extern void uty_child_signal_nexus_set(vty_io vio) ; +extern void vty_child_signal_nexus_signal(void) ; +extern void uty_child_signal_nexus_clear(vty_io vio) ; + extern void uty_open_listeners(const char *addr, unsigned short port, const char *path) ; diff --git a/lib/vty_io_basic.c b/lib/vty_io_basic.c index 85ea2587..3239c893 100644 --- a/lib/vty_io_basic.c +++ b/lib/vty_io_basic.c @@ -34,7 +34,7 @@ * */ -/*============================================================================== +/*------------------------------------------------------------------------------ * Try to open the given file for the given type of I/O. * * vfd_io_write => create if does not exist (mode 0600) @@ -80,7 +80,7 @@ uty_vfd_file_open(const char* name, vfd_io_type_t io_type) if ((io_type & vfd_io_blocking) == 0) oflag |= O_NONBLOCK ; - return open(name, oflag, S_IRUSR | S_IWUSR) ; + return open(name, oflag, S_IRUSR | S_IWUSR) ; /* TODO umask etc ? */ } ; /*============================================================================== @@ -90,25 +90,8 @@ uty_vfd_file_open(const char* name, vfd_io_type_t io_type) * and an old thread environment are encapsulated here. */ -struct vio_io_set_args /* to CLI thread */ -{ - 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_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_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_vfd_mqb_dispatch(vio_vfd vfd) ; -static struct vio_io_set_args* vio_vfd_mqb_args(vio_vfd vfd) ; +static void vio_vfd_mqb_kick(vio_vfd vfd) ; +static void vio_vfd_mqb_free(vio_vfd vfd) ; /*============================================================================== * File Descriptor handling @@ -130,24 +113,30 @@ 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) ; +static void vio_timer_squelch(vio_timer timer) ; Inline void vio_vfd_do_read_action(vio_vfd vfd) { - if (vfd->active) + if (vfd->read_action != NULL) vfd->read_action(vfd, vfd->action_info) ; } Inline void vio_vfd_do_write_action(vio_vfd vfd) { - if (vfd->active) + if (vfd->write_action != NULL) vfd->write_action(vfd, vfd->action_info) ; } ; /*------------------------------------------------------------------------------ * Create a new vfd structure. + * + * Note that sets the same action info for read, write, read timeout and + * write timeout. + * + * Sets the blocking_vf state, which will disallow any attempt to set read/write + * ready/timeout -- but enable open/close when not in cli thread. */ extern vio_vfd vio_vfd_new(int fd, vfd_type_t type, vfd_io_type_t io_type, void* action_info) @@ -158,45 +147,59 @@ vio_vfd_new(int fd, vfd_type_t type, vfd_io_type_t io_type, void* action_info) /* Has set: * - * active -- false ! + * fd -- X -- see below + * active -- X -- see below + * + * type -- X -- see below + * io_type -- X -- see below + * + * blocking_vf -- X -- see below + * + * action_info -- NULL -- set below if !blocking_vf * * read_action -- NULL * write_action -- NULL * - * f.qf -- NULL + * read_timer -- NULL -- set below if !blocking_vf + * write_timer -- NULL -- set below if !blocking_vf + * + * f.qf -- NULL -- set below if !blocking_vf + * * f.thread.read -- NULL * f.thread.write -- NULL * - * mqb -- NULL + * queued -- false + * + * read_req -- all zeros -- none set + * write_req -- all zeros -- none set + * + * mqb -- NULL -- none, yet */ + confirm(VIO_TIMER_INIT_ZERO) ; - vio_vfd_set_fd(vfd, fd, type, io_type) ; + vfd->fd = fd ; + vfd->type = type ; + vfd->io_type = io_type ; - vio_timer_init(&vfd->read_timer, NULL, NULL) ; - vio_timer_init(&vfd->write_timer, NULL, NULL) ; + vfd->blocking_vf = (io_type & vfd_io_blocking) != 0 ; - vio_vfd_set_action_info(vfd, action_info) ; + if (!vfd->blocking_vf) + { + VTY_ASSERT_CLI_THREAD() ; - return vfd ; -} ; + if (vty_nexus) + { + vfd->f.qf = qps_file_init_new(NULL, NULL) ; + qps_add_file(vty_cli_nexus->selection, vfd->f.qf, vfd->fd, vfd) ; + } ; -/*------------------------------------------------------------------------------ - * If vfd was not fully set up when created, set it up now. - * - * 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_vfd_set_fd(vio_vfd vfd, int fd, vfd_type_t type, vfd_io_type_t io_type) -{ - assert(!vfd->active) ; + vfd->read_timer = vio_timer_init_new(NULL, NULL, NULL) ; + vfd->write_timer = vio_timer_init_new(NULL, NULL, NULL) ; - vfd->fd = fd ; - vfd->active = (fd >= 0) ; - vfd->type = type ; - vfd->io_type = io_type ; + vio_vfd_set_action_info(vfd, action_info) ; + } ; + + return vfd ; } ; /*------------------------------------------------------------------------------ @@ -223,7 +226,7 @@ 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) { - vio_timer_set_action(&vfd->read_timer, action) ; + vio_timer_set_action(vfd->read_timer, action) ; } ; /*------------------------------------------------------------------------------ @@ -232,7 +235,7 @@ vio_vfd_set_read_timeout_action(vio_vfd vfd, vio_timer_action* action) extern void vio_vfd_set_write_timeout_action(vio_vfd vfd, vio_timer_action* action) { - vio_timer_set_action(&vfd->write_timer, action) ; + vio_timer_set_action(vfd->write_timer, action) ; } ; /*------------------------------------------------------------------------------ @@ -242,8 +245,8 @@ extern void 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) ; + vio_timer_set_info(vfd->read_timer, action_info) ; + vio_timer_set_info(vfd->write_timer, action_info) ; } ; #if 0 @@ -278,6 +281,8 @@ vio_vfd_kick_write_action(vio_vfd vfd) * In any case, turns off any read ready and read ready timeout. * * Returns original vfd, or NULL if it has been closed. + * + * NB: if this is not a "blocking_vf", then MUST be in the cli thread. */ extern vio_vfd vio_vfd_read_close(vio_vfd vfd) @@ -287,47 +292,60 @@ vio_vfd_read_close(vio_vfd vfd) if (vfd == NULL) return NULL ; - if (vfd->fd >= 0) - { - assert(vfd->active) ; + if (!vfd->blocking_vf) + VTY_ASSERT_CLI_THREAD() ; - if (vfd->io_type & vfd_io_read) + if ((vfd->io_type & vfd_io_read) != 0) + { + if ((vfd->io_type & vfd_io_write) != 0) { - if (vfd->io_type & vfd_io_write) + /* read & write, so really half-close if can */ + if (vfd->fd >= 0) { - /* read & write, so really half-close if can */ if (vfd->type == vfd_socket) 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_vfd_close(vfd) ; + vfd->io_type ^= vfd_io_read ; /* now write only ! */ } ; + } + else + { + /* read only, so fully close */ + vfd = vio_vfd_close(vfd) ; } ; - } - else - assert(!vfd->active) ; + } ; return vfd ; } ; /*------------------------------------------------------------------------------ - * If there is an fd, close it. + * Close the given vfd (if any). * - * Stops any read/write waiting and releases all memory, including the vfd - * itself if required.. + * If there is an fd, close it. Stops any read/write waiting and releases all + * memory. + * + * NB: if this is not a "blocking_vf", then MUST be in the cli thread. + * Inter alia, this guarantees that cannot be in the middle of a read/write + * ready/timeout operation -- so the file can be closed down, and + * any pending ready/timeout will be swept away. */ -static void -vio_vfd_do_close(vio_vfd vfd, free_keep_b free) +extern vio_vfd +vio_vfd_close(vio_vfd vfd) { + VTY_ASSERT_LOCKED() ; + if (vfd == NULL) - return ; + return NULL ; - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; + if (!vfd->blocking_vf) + VTY_ASSERT_CLI_THREAD() ; + + /* Close the underlying fd, if any */ + + if (vfd->fd >= 0) + close(vfd->fd) ; /* ignores errors TODO */ + + /* Clear out the vfd then free it */ if (vty_nexus) { @@ -337,11 +355,7 @@ vio_vfd_do_close(vio_vfd vfd, free_keep_b free) vfd->f.qf = qps_file_free(vfd->f.qf) ; } ; - if (vfd->mqb != NULL) - { - mqb_free(vfd->mqb) ; - vfd->mqb = NULL ; - } ; + vio_vfd_mqb_free(vfd) ; } else { @@ -362,85 +376,10 @@ vio_vfd_do_close(vio_vfd vfd, free_keep_b free) assert(vfd->mqb == NULL) ; } ; - if (vfd->fd >= 0) - close(vfd->fd) ; /* ignores errors TODO */ - - vio_timer_reset(&vfd->read_timer) ; - vio_timer_reset(&vfd->write_timer) ; - - if (free == free_it) - XFREE(MTYPE_VTY, vfd) ; -} ; - -/*------------------------------------------------------------------------------ - * Close the given vfd (if any). - * - * 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_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_vfd, the qps_file and the mqb. - */ -extern vio_vfd -vio_vfd_close(vio_vfd vfd) -{ - VTY_ASSERT_LOCKED() ; + vfd->read_timer = vio_timer_reset(vfd->read_timer, free_it) ; + vfd->write_timer = vio_timer_reset(vfd->write_timer, free_it) ; - if (vfd == NULL) - return NULL ; - - if (vfd->fd < 0) - { - /* closing an inactive vio_vfd -- make sure all is quiet */ - assert(!vfd->active) ; - if (vty_nexus) - { - assert(vfd->f.qf == NULL) ; - } - else - { - assert(vfd->f.thread.read == NULL) ; - assert(vfd->f.thread.write == NULL) ; - } ; - assert(vfd->mqb == NULL) ; - } - else - { - /* closing an active vio_vfd */ - if (vty_is_cli_thread()) - { - /* In cli thread, so close directly */ - 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_vfd_mqb_args(vfd) ; - - args->close = true ; - - /* in case something goes ready before the close message - * is processed, squelch. - */ - vfd->active = false ; - vfd->read_action = NULL ; - vfd->write_action = NULL ; - vfd->action_info = NULL ; - - vio_timer_squelch(&vfd->read_timer) ; - vio_timer_squelch(&vfd->write_timer) ; - - assert(vfd == mqb_get_arg0(vfd->mqb)) ; - vio_vfd_mqb_dispatch(vfd) ; - } ; - } ; + XFREE(MTYPE_VTY, vfd) ; return NULL ; } ; @@ -448,48 +387,39 @@ vio_vfd_close(vio_vfd vfd) /*------------------------------------------------------------------------------ * Set or unset read ready state on given vio_vfd (if any) if it is active. * + * Do nothing if vfd NULL, fd < 0 or not a read type of fd. + * * 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. + * + * NB: must NOT be a "blocking_vf" !! */ 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 ; - VTY_ASSERT_LOCKED() ; - if ((vfd == NULL) || (!vfd->active)) + if ((vfd == NULL) || (vfd->fd < 0) || ((vfd->io_type & vfd_io_read) == 0)) return off ; + assert(!vfd->blocking_vf) ; + if (vty_is_cli_thread()) { /* In the cli thread (effectively) so do things directly. */ - if (vfd->mqb != NULL) - { - /* discard/override any pending message setting */ - args = mqb_get_args(vfd->mqb) ; - args->readable = false ; - } ; + vfd->read_req.set = false ; /* doing or overriding request */ if (on) { assert(vfd->read_action != NULL) ; if (vty_nexus) - { - if (vfd->f.qf == NULL) - { - vfd->f.qf = qps_file_init_new(NULL, NULL); - qps_add_file(vty_cli_nexus->selection, vfd->f.qf, - vfd->fd, vfd) ; - } ; qps_enable_mode(vfd->f.qf, qps_read_mnum, vio_vfd_qps_read_action) ; - } else { if (vfd->f.thread.read == NULL) @@ -497,34 +427,31 @@ vio_vfd_set_read(vio_vfd vfd, on_off_b on, vty_timer_time timeout) vio_vfd_thread_read_action, vfd, vfd->fd) ; } ; - vio_timer_set(&vfd->read_timer, timeout) ; + vio_timer_set(vfd->read_timer, timeout) ; } else { if (vty_nexus) - { - if (vfd->f.qf != NULL) - qps_disable_modes(vfd->f.qf, qps_read_mbit) ; - } + qps_disable_modes(vfd->f.qf, qps_read_mbit) ; else { if (vfd->f.thread.read != NULL) thread_cancel (vfd->f.thread.read) ; } ; - vio_timer_unset(&vfd->read_timer) ; + vio_timer_unset(vfd->read_timer) ; } ; } else { /* In other threads, must send message to cli thread */ + vfd->read_req.set = true ; + vfd->read_req.on = on ; + vfd->read_req.timeout = timeout ; + + vio_timer_squelch(vfd->read_timer) ; - args = vio_vfd_mqb_args(vfd) ; - args->readable = true ; - args->read_on = on ; - args->read_timeout = timeout ; - vio_timer_squelch(&vfd->read_timer) ; - vio_vfd_mqb_dispatch(vfd) ; + vio_vfd_mqb_kick(vfd) ; } ; return on ; @@ -533,48 +460,39 @@ vio_vfd_set_read(vio_vfd vfd, on_off_b on, vty_timer_time timeout) /*------------------------------------------------------------------------------ * Set or unset write ready state on given vio_vfd (if any) if it is active. * + * Do nothing if vfd NULL, fd < 0 or not a write type of fd. + * * If setting write_on, starts any write timeout timer. * If setting write off, stops any write 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. + * + * NB: must NOT be a "blocking_vf" !! */ 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 ; - VTY_ASSERT_LOCKED() ; - if ((vfd == NULL) || (!vfd->active)) + if ((vfd == NULL) || (vfd->fd < 0) || ((vfd->io_type & vfd_io_write) == 0)) return off ; + assert(!vfd->blocking_vf) ; + if (vty_is_cli_thread()) { /* In the cli thread (effectively) so do things directly. */ - if (vfd->mqb != NULL) - { - /* discard/override any pending message setting */ - args = mqb_get_args(vfd->mqb) ; - args->writable = false ; - } ; + vfd->write_req.set = false ; /* doing or overriding request */ if (on) { assert(vfd->write_action != NULL) ; if (vty_nexus) - { - if (vfd->f.qf == NULL) - { - vfd->f.qf = qps_file_init_new(NULL, NULL); - qps_add_file(vty_cli_nexus->selection, vfd->f.qf, - vfd->fd, vfd) ; - } ; - qps_enable_mode(vfd->f.qf, qps_write_mnum, + qps_enable_mode(vfd->f.qf, qps_write_mnum, vio_vfd_qps_write_action) ; - } else { if (vfd->f.thread.write == NULL) @@ -582,34 +500,31 @@ vio_vfd_set_write(vio_vfd vfd, on_off_b on, vty_timer_time timeout) vio_vfd_thread_write_action, vfd, vfd->fd) ; } ; - vio_timer_set(&vfd->write_timer, timeout) ; + vio_timer_set(vfd->write_timer, timeout) ; } else { if (vty_nexus) - { - if (vfd->f.qf != NULL) - qps_disable_modes(vfd->f.qf, qps_write_mbit) ; - } + qps_disable_modes(vfd->f.qf, qps_write_mbit) ; else { if (vfd->f.thread.write != NULL) thread_cancel (vfd->f.thread.write) ; } ; - vio_timer_unset(&vfd->write_timer) ; + vio_timer_unset(vfd->write_timer) ; } ; } else { /* In other threads, must send message to cli thread */ + vfd->write_req.set = true ; + vfd->write_req.on = on ; + vfd->write_req.timeout = timeout ; + + vio_timer_squelch(vfd->write_timer) ; - args = vio_vfd_mqb_args(vfd) ; - args->writable = true ; - args->write_on = on ; - args->write_timeout = timeout ; - vio_timer_squelch(&vfd->write_timer) ; - vio_vfd_mqb_dispatch(vfd) ; + vio_vfd_mqb_kick(vfd) ; } ; return on ; @@ -619,24 +534,21 @@ vio_vfd_set_write(vio_vfd vfd, on_off_b on, vty_timer_time timeout) * Callback -- qnexus: ready to read * * Clears read ready state and unsets any read timer. - * - * NB: if !vfd->active, then has been closed in another thread, but close - * message is yet to be procesed. */ static void vio_vfd_qps_read_action(qps_file qf, void* file_info) { vio_vfd vfd ; - VTY_ASSERT_CLI_THREAD() ; VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; 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_timer_unset(vfd->read_timer) ; vio_vfd_do_read_action(vfd) ; @@ -656,15 +568,15 @@ vio_vfd_thread_read_action(struct thread *thread) { vio_vfd vfd ; - VTY_ASSERT_CLI_THREAD() ; VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; vfd = THREAD_ARG(thread); assert(vfd->fd == THREAD_FD(thread)) ; vfd->f.thread.read = NULL ; /* implicitly */ - vio_timer_unset(&vfd->read_timer) ; + vio_timer_unset(vfd->read_timer) ; vio_vfd_do_read_action(vfd) ; @@ -685,13 +597,13 @@ vio_vfd_qps_write_action(qps_file qf, void* file_info) { vio_vfd vfd = file_info ; - VTY_ASSERT_CLI_THREAD() ; VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; 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_timer_unset(vfd->write_timer) ; vio_vfd_do_write_action(vfd) ; @@ -711,11 +623,11 @@ vio_vfd_thread_write_action(struct thread *thread) { vio_vfd vfd = THREAD_ARG (thread); - VTY_ASSERT_CLI_THREAD() ; VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; assert(vfd->fd == THREAD_FD(thread)) ; - vio_timer_unset(&vfd->write_timer) ; + vio_timer_unset(vfd->write_timer) ; vfd->f.thread.write = NULL ; /* implicitly */ @@ -740,37 +652,42 @@ vio_vfd_thread_write_action(struct thread *thread) * which it will do when it is dequeued and actioned. */ -static void vio_vfd_set_action(mqueue_block mqb, mqb_flag_t flag) ; +static void vio_vfd_mqb_action(mqueue_block mqb, mqb_flag_t flag) ; /*------------------------------------------------------------------------------ - * Get mqb for the given vfd -- make one if required. + * Dispatch mqb, if not already active */ -static struct vio_io_set_args* -vio_vfd_mqb_args(vio_vfd vfd) +static void +vio_vfd_mqb_kick(vio_vfd vfd) { VTY_ASSERT_LOCKED() ; - if (vfd->mqb == NULL) - vfd->mqb = mqb_init_new(NULL, vio_vfd_set_action, vfd) ; + if (!vfd->queued) + { + vfd->queued = true ; + + if (vfd->mqb == NULL) + vfd->mqb = mqb_init_new(NULL, vio_vfd_mqb_action, vfd) ; - return mqb_get_args(vfd->mqb) ; + mqueue_enqueue(vty_cli_nexus->queue, vfd->mqb, mqb_priority) ; + } ; } ; /*------------------------------------------------------------------------------ - * Dispatch mqb, if not already active + * Free mqb, if exists. */ static void -vio_vfd_mqb_dispatch(vio_vfd vfd) +vio_vfd_mqb_free(vio_vfd vfd) { - struct vio_io_set_args* args = mqb_get_args(vfd->mqb) ; - VTY_ASSERT_LOCKED() ; - if (!args->active) - { - args->active = true ; - mqueue_enqueue(vty_cli_nexus->queue, vfd->mqb, mqb_ordinary) ; - } ; + if (vfd->queued) + mqb_set_arg0(vfd->mqb, NULL) ; /* mqb will suicide */ + else + mqb_free(vfd->mqb) ; /* kill now (if any) */ + + vfd->queued = false ; + vfd->mqb = NULL ; } ; /*------------------------------------------------------------------------------ @@ -783,36 +700,33 @@ vio_vfd_mqb_dispatch(vio_vfd vfd) * If the mqb is being revoked */ static void -vio_vfd_set_action(mqueue_block mqb, mqb_flag_t flag) +vio_vfd_mqb_action(mqueue_block mqb, mqb_flag_t flag) { - struct vio_io_set_args* args ; - vio_vfd vfd ; + vio_vfd vfd ; - VTY_ASSERT_CLI_THREAD() ; VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; - args = mqb_get_args(mqb) ; vfd = mqb_get_arg0(mqb) ; - args->active = false ; + if (vfd != NULL) + { + assert(mqb == vfd->mqb) ; + vfd->queued = false ; + } ; - if ((flag != mqb_destroy) && (!args->close)) + if ((flag != mqb_destroy) && (vfd != NULL)) { - if (args->readable) - vio_vfd_set_read(vfd, args->read_on, args->read_timeout) ; - if (args->writable) - vio_vfd_set_write(vfd, args->write_on, args->write_timeout) ; + if (vfd->read_req.set) + vio_vfd_set_read(vfd, vfd->read_req.on, vfd->read_req.timeout) ; + if (vfd->write_req.set) + vio_vfd_set_write(vfd, vfd->write_req.on, vfd->write_req.timeout) ; } else { - /* 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 */ + mqb_free(mqb) ; /* Suicide */ + if (vfd != NULL) + vfd->mqb = NULL ; /* make sure vfd knows */ } ; VTY_UNLOCK() ; @@ -839,8 +753,7 @@ vio_listener_new(int fd, vio_vfd_accept* accept_action) { vio_listener listener ; - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; + VTY_ASSERT_CLI_THREAD_LOCKED() ; listener = XCALLOC(MTYPE_VTY, sizeof(struct vio_listener)) ; /* sets the next pointer to NULL */ @@ -864,8 +777,7 @@ vio_listener_new(int fd, vio_vfd_accept* accept_action) extern void vio_listener_close(vio_listener listener) { - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; + VTY_ASSERT_CLI_THREAD_LOCKED() ; vio_vfd_close(listener->vfd) ; XFREE(MTYPE_VTY, listener) ; @@ -881,8 +793,7 @@ vio_accept(vio_vfd vfd, void* info) { vio_listener listener ; - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; + VTY_ASSERT_CLI_THREAD_LOCKED() ; listener = info ; assert(vfd == listener->vfd) ; @@ -896,7 +807,7 @@ vio_accept(vio_vfd vfd, void* info) * Timer Handling * * Provides timer primitives that work either in qnexus environment or in - * a thread environment. + * a thread environment. Timer times are seconds. * * The main difference is that thread environment timers are 'one-shot', set up * for one timing run and then destroyed. @@ -906,14 +817,17 @@ static void vio_timer_qtr_action(qtimer qtr, void* timer_info, qtime_t when) ; static int vio_timer_thread_action(struct thread *thread) ; /*------------------------------------------------------------------------------ - * Initialise vio_timer structure. Assumes is all new. - * - * This assumes the vio_timer structure is embedded in another structure. + * Allocate and/or initialise vio_timer structure. */ -extern void -vio_timer_init(vio_timer_t* timer, vio_timer_action* action, void* action_info) +extern vio_timer +vio_timer_init_new(vio_timer timer, vio_timer_action* action, void* action_info) { - memset(timer, 0, sizeof(vio_timer_t)) ; + if (timer == NULL) + timer = XCALLOC(MTYPE_VTY, sizeof(vio_timer_t)) ; + else + memset(timer, 0, sizeof(vio_timer_t)) ; + + confirm(VIO_TIMER_INIT_ZERO) ; /* active -- 0, false * squelch -- 0, false @@ -922,74 +836,88 @@ vio_timer_init(vio_timer_t* timer, vio_timer_action* action, void* action_info) timer->action = action ; timer->action_info = action_info ; + + return timer ; } ; /*------------------------------------------------------------------------------ * Set the action field for the given timer. */ extern void -vio_timer_set_action(vio_timer_t* timer, vio_timer_action* action) +vio_timer_set_action(vio_timer timer, vio_timer_action* action) { - timer->action = action ; + if (timer != NULL) + timer->action = action ; } ; /*------------------------------------------------------------------------------ * Set the info field for the given timer. */ extern void -vio_timer_set_info(vio_timer_t* timer, void* action_info) +vio_timer_set_info(vio_timer timer, void* action_info) { - timer->action_info = action_info ; + if (timer != NULL) + timer->action_info = action_info ; } ; /*------------------------------------------------------------------------------ - * Kill vio_timer -- used when closing . + * Squelch the given timer -- if it goes off, do not call the action routine, + * and leave the rimer inactive. + * + * Used when doing read/write ready from not-cli thread. */ static void -vio_timer_squelch(vio_timer_t* timer) +vio_timer_squelch(vio_timer timer) { VTY_ASSERT_LOCKED() ; - timer->squelch = true ; + if (timer != NULL) + timer->squelch = true ; } ; /*------------------------------------------------------------------------------ * Reset vio_timer structure. Stops any timer and releases all memory. - * - * This assumes the vio_timer structure is embedded in another structure. */ -extern void -vio_timer_reset(vio_timer_t* timer) +extern vio_timer +vio_timer_reset(vio_timer timer, free_keep_b free_structure) { VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - if (timer->t.anon != NULL) + if (timer != NULL) { - if (vty_nexus) - qtimer_free(timer->t.qtr) ; - else - thread_cancel(timer->t.thread) ; + VTY_ASSERT_CLI_THREAD() ; + + if (timer->t.anon != NULL) + { + if (vty_nexus) + qtimer_free(timer->t.qtr) ; + else + thread_cancel(timer->t.thread) ; - timer->t.anon = NULL ; + timer->t.anon = NULL ; + } ; + + timer->active = false ; + timer->squelch = false ; + + if (free_structure) + XFREE(MTYPE_VTY, timer) ; /* sets timer = NULL */ } ; - timer->active = false ; - timer->squelch = false ; + return timer ; } ; /*------------------------------------------------------------------------------ - * Set vio_timer going, with the given time. + * Set vio_timer going, with the given time (in seconds). * * If timer is running, set to new time. * * If the time == 0, stop any current timer, do not restart. */ extern void -vio_timer_set(vio_timer_t* timer, vty_timer_time time) +vio_timer_set(vio_timer timer, vty_timer_time time) { - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; + VTY_ASSERT_CLI_THREAD_LOCKED() ; if (time == 0) { @@ -1022,10 +950,9 @@ vio_timer_set(vio_timer_t* timer, vty_timer_time time) * Stop vio_timer, if any. */ extern void -vio_timer_unset(vio_timer_t* timer) +vio_timer_unset(vio_timer timer) { - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; + VTY_ASSERT_CLI_THREAD_LOCKED() ; if (timer->active) { @@ -1053,11 +980,11 @@ 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 ; + vio_timer timer ; vty_timer_time time ; - VTY_ASSERT_CLI_THREAD() ; VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; timer = timer_info ; @@ -1084,11 +1011,11 @@ 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 ; + vio_timer timer ; vty_timer_time time ; - VTY_ASSERT_CLI_THREAD() ; VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; timer = THREAD_ARG(thread) ; timer->t.thread = NULL ; /* implicitly */ diff --git a/lib/vty_io_basic.h b/lib/vty_io_basic.h index 00ff923b..9d174fdd 100644 --- a/lib/vty_io_basic.h +++ b/lib/vty_io_basic.h @@ -72,8 +72,8 @@ typedef enum vfd_io_type vfd_io_type_t ; */ 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) ; +typedef struct vio_timer* vio_timer ; +typedef vty_timer_time vio_timer_action(vio_timer timer, void* action_info) ; struct vio_timer { @@ -89,6 +89,9 @@ struct vio_timer void* anon ; } t ; } ; +typedef struct vio_timer vio_timer_t[1] ; + +enum { VIO_TIMER_INIT_ZERO = true } ; /*------------------------------------------------------------------------------ * File descriptors -- looks after ready to read and/or write. @@ -100,21 +103,40 @@ typedef struct vio_vfd* vio_vfd ; typedef void vio_vfd_action(vio_vfd vfd, void* action_info) ; +typedef struct +{ + bool set ; + on_off_b on ; + vty_timer_time timeout ; +} vfd_request_t ; + struct vio_vfd { int fd ; - bool active ; /* used for close message */ vfd_type_t type ; /* used for half-close */ vfd_io_type_t io_type ; /* read, write, read/write */ + /* The rest of the vfd is to do with managing read/write ready and + * read/write timeouts. + * + * At the vf level we manage "blocking" vfs, which do not and must not + * use read/write ready or read/write timeouts. When the vfd is created + * this is passed, so that mistakes can be spotted ! + * + * Non-blocking vfs may only be created and closed in the cli thread (or + * when running in the legacy threads environment). This is because can + * only dismantle qps_file structure while in the owning thread. + */ + bool blocking_vf ; /* Reject read/write ready etc. */ + + void* action_info ; /* for all action and time-out */ + vio_vfd_action* read_action ; vio_vfd_action* write_action ; - vio_timer_t read_timer ; - vio_timer_t write_timer ; - - void* action_info ; /* for all action and time-out */ + vio_timer read_timer ; + vio_timer write_timer ; union { @@ -127,7 +149,20 @@ struct vio_vfd } thread ; } f ; - mqueue_block mqb ; + /* To support remote setting clearing of read/write ready and read/write + * timeout -- by remote we mean anything such action not in the cli thread. + * + * This is required because the qpselect and qtimer stuff assumes that those + * structures are private to the thread whose qnexus they belong to ! + * + * This stuff is only required when running multi-pthreaded. + */ + bool queued ; /* message is on queue */ + + vfd_request_t read_req ; + vfd_request_t write_req ; + + mqueue_block mqb ; /* message if any */ } ; /*------------------------------------------------------------------------------ @@ -152,9 +187,7 @@ struct vio_listener 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_vfd_set_fd(vio_vfd vfd, int fd, vfd_type_t type, - vfd_io_type_t io_type) ; + vfd_io_type_t io_type, void* action_info) ; 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, @@ -173,13 +206,13 @@ Inline int vio_vfd_fd(vio_vfd vfd) ; 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, +extern vio_timer vio_timer_init_new(vio_timer timer, vio_timer_action* action, void* action_info) ; -extern void vio_timer_set_action(vio_timer_t* timer, vio_timer_action* action) ; -extern void vio_timer_set_info(vio_timer_t* timer, void* action_info) ; -extern void vio_timer_reset(vio_timer_t* timer) ; -extern void vio_timer_set(vio_timer_t* timer, vty_timer_time time) ; -extern void vio_timer_unset(vio_timer_t* timer) ; +extern vio_timer vio_timer_reset(vio_timer timer, free_keep_b free_structure) ; +extern void vio_timer_set_action(vio_timer timer, vio_timer_action* action) ; +extern void vio_timer_set_info(vio_timer timer, void* action_info) ; +extern void vio_timer_set(vio_timer timer, vty_timer_time time) ; +extern void vio_timer_unset(vio_timer timer) ; /*============================================================================== * Inline Functions diff --git a/lib/vty_io_file.c b/lib/vty_io_file.c index 2d17276a..2322114a 100644 --- a/lib/vty_io_file.c +++ b/lib/vty_io_file.c @@ -20,114 +20,339 @@ * 02111-1307, USA. */ #include "misc.h" +#include <fcntl.h> +#include <sys/wait.h> +#include <errno.h> #include "command_local.h" +#include "vty_io.h" #include "vty_io_file.h" #include "vty_io_basic.h" +#include "vty_command.h" +#include "network.h" +#include "sigevent.h" /*============================================================================== - * VTY File Output + * VTY Configuration file I/O -- VIN_CONFIG and VOUT_CONFIG. + * * - * This is for input and output of configuration files and piped stuff. * - * 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. */ +/*------------------------------------------------------------------------------ + * Set up VTY on which to read configuration file -- using already open fd. + * + * Sets TODO + * + * NB: sets up a blocking vio -- so the vin_base and vout_base will "block" + * (using local pselect() as required), as will any further vin/vout + * opened for this vio. + */ +extern vty +vty_config_read_open(int fd, const char* name, bool full_lex) +{ + vty vty ; + vty_io vio ; + vio_vf vf ; + + VTY_LOCK() ; + + /* Create the vty and its vio. + * + * NB: VTY_CONFIG_READ type vty is set vio->blocking. + */ + vty = uty_new(VTY_CONFIG_READ, CONFIG_NODE) ; + vio = vty->vio ; + + /* Create the vin_base/vout_base vf + * + * NB: don't need to specify vfd_io_blocking, because the vio is set blocking. + */ + vf = uty_vf_new(vio, name, fd, vfd_file, vfd_io_read) ; + + uty_vin_push( vio, vf, VIN_CONFIG, NULL, NULL, 64 * 1024) ; + uty_vout_push(vio, vf, VOUT_STDERR, NULL, NULL, 4 * 1024) ; + + /* Deal with the possibility that while reading the configuration file, may + * use a pipe, and therefore may block waiting to collect a child process. + * + * Before there is any chance of a SIGCHLD being raised for a child of the + * configuration file, invoke the magic required for SIGCHLD to wake up + * a pselect() while waiting to collect child. + */ + uty_child_signal_nexus_set(vio) ; + + VTY_UNLOCK() ; + + return vty ; +} ; + +/*------------------------------------------------------------------------------ + * Tidy up after config input file has been closed. + * + * There is now no further possibility that will block waiting for child to be + * collected. + * + * Nothing further required -- input comes to a halt. + */ +extern cmd_return_code_t +uty_config_read_close(vio_vf vf, bool final) +{ + VTY_ASSERT_LOCKED() ; + + uty_child_signal_nexus_clear(vf->vio) ; + + return CMD_SUCCESS ; +} ; /*============================================================================== - * Prototypes. + * VTY File I/O -- VIN_FILE and VOUT_FILE + * + * This is for input and output of configuration files and piped stuff. + * + * 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. The actual I/O is non-blocking, the "blocking" + * I/O is manufactured using a mini-pselect, so can time-out file writing + * before too long. */ +static void uty_file_read_ready(vio_vfd vfd, void* action_info) ; +static vty_timer_time uty_file_read_timeout(vio_timer timer, + void* action_info) ; + +static void uty_file_write_ready(vio_vfd vfd, void* action_info) ; +static vty_timer_time uty_file_write_timeout(vio_timer timer, + void* action_info) ; + +static cmd_return_code_t uty_fifo_command_line(vio_vf vf, cmd_action action) ; /*------------------------------------------------------------------------------ + * Open file for input, to be read as commands. + * + * If could not open, issues message to the vio. * + * If opens OK, save the current context in the current vin (before pushing + * the new vin). * + * Returns: CMD_SUCCESS -- all set to go + * CMD_WARNING -- failed to open + * + * If "blocking" vf, this can be called from any thread, otherwise must be the + * cli thread -- see uty_vfd_new(). */ extern cmd_return_code_t -uty_file_read_open(vty_io vio, qstring name, bool reflect) +uty_file_read_open(vty_io vio, qstring name, cmd_context context) { - const char* name_str ; - int fd ; - vio_vf vf ; + cmd_return_code_t ret ; + qpath path ; + const char* pns ; + int fd ; + vio_vf vf ; vfd_io_type_t iot ; - name_str = qs_make_string(name) ; + VTY_ASSERT_CLI_THREAD_LOCKED() ; + + assert(vio->vin != NULL) ; /* Not expected to be vin_base */ + + /* Now is the time to complete the name, if required. */ - iot = vfd_io_read | vfd_io_blocking ; /* TODO blocking */ + path = uty_cmd_path_name_complete(NULL, qs_string(name), context) ; + pns = qpath_string(path) ; /* Do the basic file open. */ - fd = uty_vfd_file_open(name_str, iot) ; + iot = vfd_io_read ; + + fd = uty_vfd_file_open(pns, iot) ; if (fd < 0) { - uty_out(vio, "%% Could not open input file %s\n", name_str) ; + uty_out(vio, "%% Could not open input file %s\n", pns) ; - return CMD_WARNING ; + ret = CMD_WARNING ; /* TODO add errno to message ? */ } + else + { + /* We have a file, so now save context and update dir "here" */ + uty_vin_new_context(vio, context, path) ; - /* 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) ; + /* OK -- now push the new input onto the vin_stack. */ + vf = uty_vf_new(vio, pns, fd, vfd_file, iot) ; + uty_vin_push(vio, vf, VIN_FILE, uty_file_read_ready, + uty_file_read_timeout, 16 * 1024) ; + uty_vf_set_read_timeout(vf, 30) ; - vf->parse_type = cmd_parse_strict ; - vf->reflect_enabled = reflect ; + ret = CMD_SUCCESS ; + } ; - return CMD_SUCCESS ; + qpath_free(path) ; + return ret ; } ; /*------------------------------------------------------------------------------ + * Open file for output. + * + * If could not open, issues message to the vio. * + * Returns: CMD_SUCCESS -- all set to go + * CMD_WARNING -- failed to open * + * If "blocking" vf, this can be called from any thread, otherwise must be the + * cli thread -- see uty_vfd_new(). */ extern cmd_return_code_t -uty_file_write_open(vty_io vio, qstring name, bool append) +uty_file_write_open(vty_io vio, qstring name, bool append, cmd_context context) { - const char* name_str ; + cmd_return_code_t ret ; + qpath path ; + const char* pns ; int fd ; vio_vf vf ; vfd_io_type_t iot ; - iot = vfd_io_write | vfd_io_blocking ; /* TODO blocking */ + VTY_ASSERT_CLI_THREAD_LOCKED() ; - if (append) - iot |= vfd_io_append ; + /* Now is the time to complete the name, if required. */ - name_str = qs_make_string(name) ; + path = uty_cmd_path_name_complete(NULL, qs_string(name), context) ; + pns = qpath_string(path) ; /* Do the basic file open. */ - fd = uty_vfd_file_open(name_str, iot) ; + iot = vfd_io_write | (append ? vfd_io_append : 0) ; + + fd = uty_vfd_file_open(pns, iot) ; if (fd < 0) { - uty_out(vio, "%% Could not open output file %s\n", name_str) ; + uty_out(vio, "%% Could not open output file %s\n", pns) ; - return CMD_WARNING ; + ret = CMD_WARNING ; /* TODO add errno to message ? */ } + else + { + /* OK -- now push the new output onto the vout_stack. */ + vf = uty_vf_new(vio, pns, fd, vfd_file, iot) ; + uty_vout_push(vio, vf, VOUT_FILE, uty_file_write_ready, + uty_file_write_timeout, 16 * 1024) ; + ret = CMD_SUCCESS ; + } ; - /* 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) ; - - vf->out_enabled = true ; - - return CMD_SUCCESS ; + qpath_free(path) ; + return ret ; } ; -/*============================================================================== +/*------------------------------------------------------------------------------ * Command line fetch from a file or pipe. * - * 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 + * Returns: CMD_SUCCESS -- have another command line ready to go + * CMD_WAITING -- would not wait for input <=> non-blocking + * CMD_EOF -- ran into EOF + * CMD_IO_ERROR -- ran into an I/O error + * CMD_IO_TIMEOUT -- could wait no longer <=> blocking + * + * In "non-blocking" state, on CMD_WAITING sets read ready, which will + * vty_cmd_signal() the command loop to come round again. + * + * In "blocking" state, will not return until have something, or there is + * nothing to be had, or times out. + * + * This can be called in any thread. + * + * NB: the vin_state will become vf_eof the first time that CMD_EOF is + * returned. */ extern cmd_return_code_t -uty_file_fetch_command_line(vio_vf vf, qstring* line) +uty_file_fetch_command_line(vio_vf vf, cmd_action action) { - assert(vf->vin_state == vf_open) ; + VTY_ASSERT_LOCKED() ; /* In any thread */ + + assert((vf->vin_type == VIN_FILE) || (vf->vin_type == VIN_CONFIG)) ; + assert((vf->vin_state == vf_open) || (vf->vin_state == vf_eof)) ; + + while (1) + { + cmd_return_code_t ret ; + + /* Try to complete line straight from the buffer. + * + * If buffer is empty and have seen eof on the input, signal CMD_EOF. + */ + ret = uty_fifo_command_line(vf, action) ; + + if (ret != CMD_WAITING) + return ret ; + + /* Could not complete line and exhausted contents of fifo */ + while (1) + { + qps_mini_t qm ; + int get ; + + get = vio_fifo_read_nb(vf->ibuf, vio_vfd_fd(vf->vfd), 1) ; + + if (get > 0) + break ; /* loop back to uty_fifo_command_line() */ + + if (get == -1) + return CMD_IO_ERROR ; /* register error */ + + if (get == -2) + { + /* Hit end of file set the vf into vf_eof. + * + * NB: does not know has hit eof until tries to read and nothing + * is returned. + * + * Loop back so that uty_fifo_command_line() can reconsider, now + * that we know that there is no more data to come -- it may + * signal CMD_SUCCESS (non-empty final line) or CMD_EOF. + */ + assert(vio_fifo_empty(vf->ibuf)) ; + + vf->vin_state = vf_eof ; + + break ; /* loop back to uty_fifo_command_line() */ + } ; + + /* Would block -- for non-blocking return CMD_WAITING, for + * blocking we block here with a timeout, and when there is more + * to read, loop back to + */ + assert(get == 0) ; + + if (!vf->blocking) + { + uty_vf_set_read(vf, on) ; + return CMD_WAITING ; + } ; + + /* Implement blocking I/O, with timeout */ + qps_mini_set(qm, vio_vfd_fd(vf->vfd), qps_read_mnum, + vf->read_timeout) ; + if (qps_mini_wait(qm, NULL, false) == 0) + return CMD_IO_TIMEOUT ; /* TODO vf_timeout ?? */ + + /* Loop back to vio_fifo_read_nb() */ + } + } ; +} ; + +/*------------------------------------------------------------------------------ + * Try to complete a command line for the given vf, from the current input + * fifo -- performs NO I/O + * + * Returns: CMD_SUCCESS -- have a command line. + * CMD_EOF -- there is no more + * CMD_WAITING -- waiting for more + * + * If vf->vin_state == vf_eof, will return CMD_SUCCESS if have last part line + * in hand. Otherwise will return CMD_EOF, any number of times. + */ +static cmd_return_code_t +uty_fifo_command_line(vio_vf vf, cmd_action action) +{ + VTY_ASSERT_LOCKED() ; /* In any thread */ if (vf->line_complete) { @@ -142,47 +367,42 @@ uty_file_fetch_command_line(vio_vf vf, qstring* line) while (1) { - char* s, * p, * q, * e ; + char* s, * p, * e ; size_t have ; ulen len ; + bool eol ; + /* Get what we can from the fifo */ s = vio_fifo_get(vf->ibuf, &have) ; - /* If nothing in hand, try and get some more. - * - * Either exits or loops back to set s & have. - */ + /* If fifo is empty, may be last line before eof, eof or waiting */ if (have == 0) { - int get ; - - get = vio_fifo_read_nb(vf->ibuf, vio_vfd_fd(vf->vfd), 100) ; - - if (get > 0) - continue ; /* loop back */ - - if (get == 0) - return CMD_WAITING ; /* need to set read ready ! */ - - if (get == -1) - ; /* register error */ + if (vf->vin_state == vf_eof) + { + if (qs_len_nn(vf->cl) > 0) + break ; /* have non-empty last line */ + else + return CMD_EOF ; + } ; - if (get == -2) - return (qs_len_nn(vf->cl) > 0) ? CMD_SUCCESS : CMD_EOF ; + return CMD_WAITING ; } ; + assert(vf->vin_state != vf_eof) ; /* not empty => not eof */ + /* 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. + * This means that we cope with "\r\n" line terminators. But not + * anything more exotic. */ p = s ; e = s + have ; /* have != 0 */ - q = NULL ; + eol = false ; while (p < e) { if (*p++ < 0x20) @@ -195,9 +415,7 @@ uty_file_fetch_command_line(vio_vf vf, qstring* line) ++vf->line_step ; /* got a '\n' */ - q = p ; /* point just past '\n' */ - do --q ; while ((q > s) && (*(q-1) == ' ')) ; - /* discard trailing "spaces" */ + eol = true ; break ; } ; } ; @@ -212,28 +430,30 @@ uty_file_fetch_command_line(vio_vf vf, qstring* line) * * Loops back to try to get some more form the fifo. */ - if (q == NULL) + if (!eol) { - qs_append_n(vf->cl, s, p - s) ; + qs_append_str_n(vf->cl, s, p - s) ; continue ; } ; + /* Have an eol. Step back across the '\n' and any trailing spaces + * we have in hand. + */ + do --p ; while ((p > s) && (*(p-1) == ' ')) ; + /* 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. */ - len = q - s ; /* length to add */ + len = p - s ; /* length to add */ if (qs_len_nn(vf->cl) == 0) - { - qs_set_alias_n(vf->cl, s, len) ; - p = q ; - } + qs_set_alias_n(vf->cl, s, len) ; else { if (len != 0) - qs_append_n(vf->cl, s, len) ; + qs_append_str_n(vf->cl, s, len) ; s = qs_char_nn(vf->cl) ; p = s + qs_len_nn(vf->cl) ; @@ -265,10 +485,10 @@ uty_file_fetch_command_line(vio_vf vf, qstring* line) * 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) == '\\')) ; + e = p ; + do --p ; while ((p > s) && (*(p-1) == '\\')) ; - if (((p - q) & 1) == 0) + if (((e - p) & 1) == 0) break ; /* even => no continuation => success */ qs_set_len_nn(vf->cl, p - s - 1) ; /* strip odd '\' */ @@ -276,47 +496,1433 @@ uty_file_fetch_command_line(vio_vf vf, qstring* line) continue ; /* loop back to fetch more */ } ; - /* Success have a line in hand */ + /* Success: have a line in hand */ vf->line_complete = true ; - *line = vf->cl ; + action->to_do = cmd_do_command ; + action->line = vf->cl ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * File or pipe is ready to read -- this is the call-back planted in the + * vf->vfd. + * + * The command_queue processing can continue. + * + * Note that the read_ready state and any time out are automatically + * disabled when they go off. + */ +static void +uty_file_read_ready(vio_vfd vfd, void* action_info) +{ + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->vfd == vfd) ; + + uty_cmd_signal(vf->vio, CMD_SUCCESS) ; + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * File or pipe has timed out, waiting to read -- this is the call-back planted + * in the vf->vfd. + * + * ???? + */ +static vty_timer_time +uty_file_read_timeout(vio_timer timer, void* action_info) +{ + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->vfd->write_timer == timer) ; + +//cq_continue(vf->vio->vty) ; TODO + + VTY_UNLOCK() ; + + return 0 ; /* Do not restart timer */ +} ; + +/*------------------------------------------------------------------------------ + * Command output push to a file. + * + * Until the file becomes vf_closing, will not write the end_lump of the fifo, + * so all output is in units of the fifo size -- which should be "chunky". + * + * Although it is unlikely to happen, if blocking, will block if cannot + * completely write away what is required, or enable write ready. + * + * If final, will write as much as possible, but not block and will ignore + * any errors -- but the return code will be an error return code. + * + * Returns: CMD_SUCCESS -- written everything + * CMD_WAITING -- could not write everything (<=> non-blocking) + * CMD_IO_ERROR -- ran into an I/O error + * CMD_IO_TIMEOUT -- not going to wait any longer (<=> blocking) + * + * In "non-blocking" state, on CMD_WAITING the caller will have to take steps + * to come back later. + * + * In "blocking" state, will not return until have written everything there is, + * away, or cannot continue. + * + * This can be called in any thread. + */ +extern cmd_return_code_t +uty_file_out_push(vio_vf vf, bool final) +{ + bool all ; + + VTY_ASSERT_LOCKED() ; /* In any thread */ + assert((vf->vout_state == vf_open) || (vf->vout_state == vf_closing)) ; + assert((vf->vout_type == VOUT_FILE) || (vf->vout_type == VOUT_CONFIG)) ; + + all = (vf->vout_state == vf_closing) ; + /* empty buffers if closing */ + while (1) + { + qps_mini_t qm ; + int n ; + + n = vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), all || final) ; + + if (n < 0) + return CMD_IO_ERROR ; /* TODO */ + + if ((n == 0) || final) /* all gone (or as much as can go) */ + return CMD_SUCCESS ; + + /* Cannot write everything away without waiting */ + if (!vf->blocking) + { + uty_vf_set_write(vf, on) ; + return CMD_WAITING ; + } ; + + /* Implement blocking I/O, with timeout */ + qps_mini_set(qm, vio_vfd_fd(vf->vfd), qps_write_mnum, vf->write_timeout) ; + if (qps_mini_wait(qm, NULL, false) == 0) + return CMD_IO_TIMEOUT ; + + /* Loop back to vio_fifo_write_nb() */ + } ; +} ; + +/*------------------------------------------------------------------------------ + * File or pipe is ready to write -- this is the call-back planted in the + * vf->vfd. + * + * Note that the write_ready state and any time out are automatically + * disabled when they go off. + * + * Since we have gone to all the trouble of going through pselect, might as + * well write everything away. This works while the file is being closed, too. + */ +static void +uty_file_write_ready(vio_vfd vfd, void* action_info) +{ + cmd_return_code_t ret ; + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->vfd == vfd) ; + + ret = uty_file_out_push(vf, false) ; /* re-enable write ready if required */ + + if (ret != CMD_WAITING) + uty_cmd_signal(vf->vio, ret) ; + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * File or pipe is ready to write -- this is the call-back planted in the + * vf->vfd. + * + * Note that the read_ready state and any time out are automatically + * disabled when they go off. + */ +static vty_timer_time +uty_file_write_timeout(vio_timer timer, void* action_info) +{ + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->vfd->write_timer == timer) ; + +//uty_file_out_push(vf) ; // TODO ??????? + + VTY_UNLOCK() ; + + return 0 ; /* Do not restart timer */ +} ; + +/*------------------------------------------------------------------------------ + * Tidy up after input file has been closed. + * + * Nothing further required -- input comes to a halt. + */ +extern cmd_return_code_t +uty_file_read_close(vio_vf vf, bool final) +{ return CMD_SUCCESS ; } ; /*------------------------------------------------------------------------------ - * Command output push to a file or pipe. + * Flush output buffer and close. + * + * See uty_file_out_push() * - * Returns: CMD_SUCCESS -- done - * CMD_IO_ERROR -- ran into an I/O error + * Returns: CMD_SUCCESS -- buffers are empty, or final + * CMD_WAITING -- waiting for I/O to complete + * CMD_xxx TODO */ extern cmd_return_code_t -uty_file_out_push(vio_vf vf) +uty_file_write_close(vio_vf vf, bool final, bool base) { - assert(vf->vout_state == vf_open) ; + VTY_ASSERT_CLI_THREAD_LOCKED() ; + assert(vf->vout_state == vf_closing) ; + assert((vf->vout_type == VOUT_FILE) || (vf->vout_type == VOUT_CONFIG)) ; - if (vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), false) >= 0) - return CMD_SUCCESS ; + return uty_file_out_push(vf, final) ; +} ; + +/*============================================================================== + * Shell pipe stuff + * + */ +typedef int pipe_pair_t[2] ; +typedef int* pipe_pair ; + +enum pipe_half +{ + in_fd = 0, + out_fd = 1, +} ; +typedef enum pipe_half pipe_half_t ; + +CONFIRM(STDIN_FILENO == 0) ; +CONFIRM(STDOUT_FILENO == 1) ; +CONFIRM(STDERR_FILENO == 2) ; + +enum +{ + stdin_fd = STDIN_FILENO, + stdout_fd = STDOUT_FILENO, + stderr_fd = STDERR_FILENO, + + stds = 3 +} ; + +enum pipe_id +{ + in_pipe, + out_pipe, + ret_pipe, + + pipe_count +} ; +typedef enum pipe_id pipe_id_t ; + +typedef pipe_pair_t pipe_set[pipe_count] ; + +static pid_t uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes, + pipe_id_t type) ; +static bool uty_pipe_pair(vty_io vio, const char* cmd_str, + const char* what, pipe_set pipes, + pipe_id_t id, + pipe_half_t half) ; +static bool uty_pipe_fork_fail(vty_io vio, const char* cmd_str, + const char* what, pipe_set pipes, + pipe_pair pair, + const char* action) ; +static void uty_pipe_close_half_pipe(pipe_pair pair, pipe_half_t half) ; +static void uty_pipe_open_complete(vio_vf vf, pid_t pid, int ret_fd, + vio_vf slave) ; +static bool uty_pipe_exec_prepare(vty_io vio, pipe_set pipes) ; + +static cmd_return_code_t uty_pipe_return_close(vio_vf vf, bool final) ; +static cmd_return_code_t uty_pipe_return_empty(vio_vf vf, bool final) ; +static cmd_return_code_t uty_pipe_collect_child(vio_vf vf, bool final) ; +static cmd_return_code_t uty_pipe_release_slave(vio_vf vf, bool final) ; + +static cmd_return_code_t uty_pipe_shovel(vio_vf vf, bool final, bool closing) ; + +static void uty_pipe_read_ready(vio_vfd vfd, void* action_info) ; +static vty_timer_time uty_pipe_read_timeout(vio_timer timer, + void* action_info) ; +static void uty_pipe_return_ready(vio_vfd vfd, void* action_info) ; +static vty_timer_time uty_pipe_return_timeout(vio_timer timer, + void* action_info) ; +static void uty_pipe_write_ready(vio_vfd vfd, void* action_info) ; +static vty_timer_time uty_pipe_write_timeout(vio_timer timer, + void* action_info) ; + +/*------------------------------------------------------------------------------ + * Open pipe whose child's stdout is going to be read and executed as commands. + * + * The child's stderr is read, separately, and that is sent to the current + * vout. + * + * If could not open, issues message to the vio. + * + * The new vin has two vio_fd's, the standard one to read commands, and the + * other (the pr_vfd) to collect any stderr output (the "return") from the + * child, which is sent to the current vout. + * + * The current vout is made the "slave" of the new vin "master". For the + * return the pr_vd reads into the "slave" obuf. + * + * There are three tricky bits: + * + * - while reading from the main vfd, also reads from the return vfd. + * + * Up to eof on the main vfd, reads from the return vfd whenever tries + * to fetch a command. Once a command is fetched, will not shovel anything + * further into the slave obuf until the next command is fetched. This + * means that return stuff is output *between* commands. + * + * - in "blocking" state, will not block on the return unless blocks on the + * main vfd, or the main vfd is at eof. + * + * in "non-blocking" state, will set read ready on the main vfd, until that + * reaches eof, in which case sets read ready on the return vfd (if that + * has not yet reached eof). + * + * - the close operation will not succeed until the return vfd reaches EOF, + * or is a "final" close. + * + * If could not open, issues message to the vio. + * + * Returns: CMD_SUCCESS -- all set to go + * CMD_WARNING -- failed to open + */ +extern cmd_return_code_t +uty_pipe_read_open(vty_io vio, qstring command, cmd_context context) +{ + pipe_set pipes ; + const char* cmd_str ; + vio_vf vf ; + pid_t child ; + + VTY_ASSERT_CLI_THREAD_LOCKED() ; + + assert(vio->vin != NULL) ; /* Not expected to be vin_base */ + + cmd_str = qs_make_string(command) ; + + /* Fork it */ + child = uty_pipe_fork(vio, cmd_str, pipes, in_pipe) ; + + if (child < 0) + return CMD_WARNING ; + + /* We have a pipe, so now save context */ + uty_vin_new_context(vio, context, NULL) ; + + /* OK -- now push the new input onto the vin_stack. */ + vf = uty_vf_new(vio, cmd_str, pipes[in_pipe][in_fd], vfd_pipe, vfd_io_read) ; + uty_vin_push(vio, vf, VIN_PIPE, uty_pipe_read_ready, + uty_pipe_read_timeout, 4 * 1024) ; + + /* And the err_pair is set as the return from the child */ + uty_pipe_open_complete(vf, child, pipes[ret_pipe][in_fd], vio->vout) ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Open pipe which is going to be written to (or not, if shell_only), and + * what the pipe returns will be output to the current vout. + * + * The new vout becomes the "master", and has two vio_vfd's, one for output to + * the pipe (this is NULL if shell_only) and the other for the pipe return. + * The pipe return reads directly into the "slave" obuf. + * + * There are three tricky bits: + * + * - while writing to the output vfd, also reads from the return vfd. + * + * This is to avoid any possibility that the child process blocks because + * it cannot write to the return pipe. + * + * - when closing the master pipe, it must remain open up to the time it + * gets eof on the return pipe. + * + * This means that the all return output is collected before any further + * output to the slave can be done. + * + * - in "blocking" state, reads from the return only when writes to the + * pipe, and when closing the pipe. + * + * in "non-blocking" state, reads from the return and pushes to the slave + * under pselect... so sucks and blows at its own rate. + * + * Returns: CMD_SUCCESS -- all set to go + * CMD_WARNING -- failed to open -- message sent to vio. + */ +extern cmd_return_code_t +uty_pipe_write_open(vty_io vio, qstring command, bool shell_only) +{ + pipe_set pipes ; + const char* cmd_str ; + pid_t child ; + vio_vf vf ; + + VTY_ASSERT_CLI_THREAD_LOCKED() ; + + cmd_str = qs_make_string(command) ; + + /* Do the basic file open. */ + child = uty_pipe_fork(vio, cmd_str, pipes, shell_only ? ret_pipe : out_pipe) ; + if (child < 0) + return CMD_WARNING ; + + /* OK -- now push the new output onto the vout_stack. */ + + if (shell_only) + { + vf = uty_vf_new(vio, cmd_str, -1, vfd_none, vfd_io_none) ; + uty_vout_push(vio, vf, VOUT_SHELL_ONLY, NULL, NULL, 0) ; + } else - return CMD_IO_ERROR ; + { + vf = uty_vf_new(vio, cmd_str, pipes[out_pipe][out_fd], + vfd_pipe, vfd_io_write) ; + uty_vout_push(vio, vf, VOUT_PIPE, uty_pipe_write_ready, + uty_pipe_write_timeout, 4 * 1024) ; + } ; + + /* Record the child pid and set up the pr_vf and enslave vout_next */ + + uty_pipe_open_complete(vf, child, pipes[ret_pipe][in_fd], vf->vout_next) ; + + vf->pr_only = shell_only ; + + /* If not blocking start up the return pipe reader. */ + + if (!vf->blocking) + vio_vfd_set_read(vf->pr_vfd, on, 0) ; /* TODO timeout */ + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Complete the opening of a pipe. + * + * All pipes have a return. For in_pipes this is the child's stderr, for + * out_pipes this is the child's combined stdout/stderr. + * + * VIN_PIPE sucks the return in between reading command lines. + * + * VOUT_PIPE/VOUT_SHELL_ONLY suck the return at the same time as writing to + * the child. + * + * Sets pr_only false. + */ +static void +uty_pipe_open_complete(vio_vf vf, pid_t pid, int ret_fd, vio_vf slave) +{ + vfd_io_type_t iot ; + + vf->child = uty_child_register(pid, vf) ; + + /* And configure the pipe return vfd. */ + iot = vfd_io_read | (vf->blocking ? vfd_io_blocking : 0) ; + + vf->pr_vfd = vio_vfd_new(ret_fd, vfd_pipe, iot, vf) ; + vf->pr_state = vf_open ; + + vf->pr_only = false ; + + if (!vf->blocking) + vio_vfd_set_read_action(vf->pr_vfd, uty_pipe_return_ready) ; + + /* Configure master/slave relationship. */ + slave->pr_master = vf ; + vf->pr_slave = slave ; +} ; + +/*------------------------------------------------------------------------------ + * Fork fork fork + * + * Set up pipes according to type of fork: + * + * in_pipe -- input from child's stdout as main fd + * input from child's stderr as return fd + * + * out_pipe -- output to child's stdin as main fd + * input from child's stderr & stdout as return fd + * + * ret_pipe -- nothing for main fd + * input from child's stderr & stdout as return fd + * + * vfork to create child process and then: + * + * -- in parent close the unused part(s) of the pair(s). + * + * -- in child, close all unused fds, and move relevant part(s) of pair(s) + * to stdin, stdout and stderr, sort out signal, set pwd then exec sh -c. + */ +static pid_t +uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes, pipe_id_t type) +{ + pid_t child ; + int id ; + + /* Set pipes empty */ + for (id = 0 ; id < pipe_count ; ++id) + { + pipes[id][in_fd] = -1 ; + pipes[id][out_fd] = -1 ; + } ; + + /* Open as many pipes as are required. */ + if (type == in_pipe) + if (!uty_pipe_pair(vio, cmd_str, "input pipe", pipes, in_pipe, in_fd)) + return -1 ; + + if (type == out_pipe) + if (!uty_pipe_pair(vio, cmd_str, "output pipe", pipes, out_pipe, out_fd)) + return -1 ; + + if (!uty_pipe_pair(vio, cmd_str, "return pipe", pipes, ret_pipe, in_fd)) + return -1 ; + + /* Off to the races */ + + child = vfork() ; + + if (child == 0) /* In child */ + { + + /* Prepare all file descriptors and then execute */ + if (uty_pipe_exec_prepare(vio, pipes)) + execl("/bin/bash", "bash", "-c", cmd_str, NULL) ; /* does not return */ + else + exit(0x80 | errno) ; + } + else if (child > 0) /* In parent -- success */ + { + /* close the pipe fds we do not need */ + uty_pipe_close_half_pipe(pipes[in_pipe], out_fd) ; + uty_pipe_close_half_pipe(pipes[out_pipe], in_fd) ; + uty_pipe_close_half_pipe(pipes[ret_pipe], out_fd) ; + } + else if (child < 0) /* In parent -- failed */ + uty_pipe_fork_fail(vio, cmd_str, "vfork", pipes, NULL, "child") ; + + return child ; +} ; + +/*------------------------------------------------------------------------------ + * Open a pipe pair -- generate suitable error message if failed and close + * any early pipes that have been opened. + */ +static bool +uty_pipe_pair(vty_io vio, const char* cmd_str, + const char* what, pipe_set pipes, + pipe_id_t id, + pipe_half_t half) +{ + pipe_pair pair ; + + pair = pipes[id] ; + + if (pipe(pair) < 0) + return uty_pipe_fork_fail(vio, cmd_str, what, pipes, pair, "open") ; + + if (set_nonblocking(pair[half]) < 0) + return uty_pipe_fork_fail(vio, cmd_str, what, pipes, pair, + "set non-blocking") ; + + return true ; +} ; + +/*------------------------------------------------------------------------------ + * Open a pipe pair -- generate suitable error message if failed and close + * any early pipes that have been opened. + */ +static bool +uty_pipe_fork_fail(vty_io vio, const char* cmd_str, + const char* what, pipe_set pipes, + pipe_pair pair, + const char* action) +{ + int err = errno ; + int id ; + + /* Close anything that has been opened */ + for (id = 0 ; id < pipe_count ; ++id) + { + if (pipes[id] == pair) + continue ; /* ignore if just failed to open */ + + uty_pipe_close_half_pipe(pipes[id], in_fd) ; + uty_pipe_close_half_pipe(pipes[id], out_fd) ; + } ; + + uty_out(vio, "%% Failed to %s %s for %s\n", action, what, cmd_str) ; + + errno = err ; + + return false ; +} ; + +/*------------------------------------------------------------------------------ + * Close half of a pipe pair, if it is open. + */ +static void +uty_pipe_close_half_pipe(pipe_pair pair, pipe_half_t half) +{ + if (pair[half] >= 0) + { + close(pair[half]) ; + pair[half] = -1 ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * In the child process... prepare to exec the shell. + * + * Discard all fd's except for the those required for the shell command. + * + * Arrange for the pipe fd's to be stdin/stdout/stderr as required. The pairs + * are named wrt to the parent process: + * + * in_pipe -- if present, is: stdout for the child + * out_pipe -- if present, is: stdin for the child + * ret_pipe -- if present, is: stderr for the child + * and: stdout for the child, if no in_pipe + * + * Set current directory. + * + * Reset all signals to default state and unmask everything. + */ +static bool +uty_pipe_exec_prepare(vty_io vio, pipe_set pipes) +{ + int std[stds] ; + int fd ; + + /* Assign fds to child's std[] */ + + std[stdin_fd] = pipes[out_pipe][in_fd] ; /* stdin for child */ + std[stdout_fd] = pipes[in_pipe][out_fd] ; /* stdout for child */ + std[stderr_fd] = pipes[ret_pipe][out_fd] ; /* stderr for child */ + + /* Mark everything to be closed on exec */ + for (fd = 0 ; fd <= 1024 ; ++fd) /* TODO -- max_fd */ + { + int fd_flags ; + fd_flags = fcntl(fd, F_GETFD, 0) ; + if (fd_flags >= 0) + fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC) ; + } ; + + /* Now dup anything of what we keep to ensure is above 2, making sure that + * the dup remains FD_CLOEXEC. + * + * This is highly unlikely, so no real extra work. It simplifies the next + * step which moves fds to the required position, and clears the FD_CLOEXEC + * flag on the duplicate. + */ + for (fd = 0 ; fd < stds ; ++fd) + { + if ((std[fd] >= 0) && (std[fd] < stds)) + if ((std[fd] = fcntl(std[fd], F_DUPFD, stds)) < 0) + return false ; + } ; + + /* Now dup2 to the required location -- destination is NOT FD_CLOEXEC. + */ + for (fd = 0 ; fd < stds ; ++fd) + { + if (std[fd] >= 0) + if (dup2(std[fd], fd) != fd) + return false ; + } ; + + /* Finally (for fds) if we don't have a stdout for the child, + * dup stderr to stdout. + */ + if ((std[stdout_fd] < 0) && (std[stderr_fd] >= 0)) + if (dup2(stderr_fd, stdout_fd) != stdout_fd) + return false ; + + /* Clear down all the signals magic. */ + quagga_signal_reset() ; + + /* Set the working directory */ + + if (qpath_setcwd(vio->vty->exec->context->dir_cd) != 0) + return false ; + + return true ; /* coming, ready or not */ +} ; + +/*------------------------------------------------------------------------------ + * Command line fetch from a pipe and (for blocking) shovel return into + * slave and push. + * + * In "non-blocking" state, on CMD_WAITING may be waiting for read ready on + * this vf, or (indirectly) write ready on the slave. + * + * In "blocking" state, will not return until have something, or there is + * nothing to be had. + * + * Returns: CMD_SUCCESS -- have another command line ready to go + * CMD_WAITING -- would not wait for input <=> non-blocking + * CMD_EOF -- ran into EOF -- on input AND return + * CMD_IO_ERROR -- ran into an I/O error + * CMD_IO_TIMEOUT -- could wait no longer <=> blocking + * + * If returns CMD_EOF will have vf->vin_state == vf_eof. Further, will not + * have vf_eof until returns CMD_EOF (the first time). + * + * This can be called in any thread. + * + * NB: once this has signalled CMD_EOF (on the main input) the close must deal + * with sucking up any remaining return. + */ +extern cmd_return_code_t +uty_pipe_fetch_command_line(vio_vf vf, cmd_action action) +{ + VTY_ASSERT_LOCKED() ; /* In any thread */ + + assert(vf->vin_type == VIN_PIPE) ; + assert((vf->vin_state == vf_open) || (vf->vin_state == vf_eof)) ; + + /* TODO police the state of the two vfs ? */ + + while (1) /* so blocking stuff can loop round */ + { + cmd_return_code_t ret ; + int get ; + qps_mini_t qm ; + + /* Try to complete line straight from the buffer. + * + * If buffer is empty and have seen eof on the input, signal CMD_EOF. + */ + ret = uty_fifo_command_line(vf, action) ; + + if ((ret == CMD_SUCCESS) || (ret == CMD_EOF)) + return ret ; + + /* Worry about the return. + * + * If the child is outputting to both stderr and stdout, we have no + * way of telling the order the data was sent in -- but we treat + * stderr as a higher priority ! + * + * Expect only CMD_SUCCESS or CMD_WAITING (non-blocking), CMD_IO_ERROR + * or CMD_IO_TIMEOUT (blocking). + */ + if (vf->blocking && (vf->pr_state == vf_open)) + { + ret = uty_pipe_shovel(vf, false, false) ; /* not final or closing */ + + if ((ret != CMD_SUCCESS) && (ret != CMD_EOF)) + return ret ; /* cannot continue */ + } ; + + /* Need more from the main input. */ + + get = vio_fifo_read_nb(vf->ibuf, vio_vfd_fd(vf->vfd), 100) ; + + if (get > 0) + continue ; /* loop back */ + + if (get == -1) + return CMD_IO_ERROR ; /* register error */ + + if (get == -2) /* EOF met immediately */ + { + vf->vin_state = vf_eof ; + continue ; /* loop back -- deals with possible + final line and the return. */ + } ; + + assert (get == 0) ; + + /* We get here if main input is not yet at eof. */ + assert ((vf->vin_state == vf_open) || (vf->pr_state == vf_open)) ; + + if (!vf->blocking) + { + uty_vf_set_read(vf, on) ; + return CMD_WAITING ; + } ; + + /* Implement blocking I/O, with timeout */ + qps_mini_set(qm, (vf->vin_state == vf_open) ? vio_vfd_fd(vf->vfd) + : -1, + qps_read_mnum, vf->read_timeout) ; + if (vf->pr_state == vf_open) + qps_mini_add(qm, vio_vfd_fd(vf->pr_vfd), qps_read_mnum) ; + + if (qps_mini_wait(qm, NULL, false) == 0) + return CMD_IO_TIMEOUT ; + + continue ; /* loop back */ + } ; +} ; + +/*------------------------------------------------------------------------------ + * Command output push to a pipe and (for blocking) shovel return into + * slave and push. + * + * Does not push to the pipe while there is return input to be read. For + * blocking I/O may block in the slave output. For non-blocking I/O, will + * return if would block in the slave output. + * + * Until the file becomes vf_closing, will not write the end_lump of the fifo, + * so all output is in units of the fifo size -- which should be "chunky". + * + * Although it is unlikely to happen, if blocking, will block if cannot + * completely write away what is required, or enable write ready. + * + * If final, will do final shovel from return to slave and also attempt to + * empty any output buffer -- will not wait or block, and ignores errors. + * + * Returns: CMD_SUCCESS -- written everything + * CMD_WAITING -- could not write everything (<=> non-blocking) + * CMD_IO_ERROR -- ran into an I/O error + * CMD_IO_TIMEOUT -- not going to wait any longer (<=> blocking) + * + * In "non-blocking" state, on CMD_WAITING the slave output will put the + * master return pr_vfd into read ready state when it sees write ready, or will + * here set the pr_vfd into read ready state. + * + * In "blocking" state, will not return until have written everything there is, + * away, or cannot continue. + * + * This can be called in any thread. + */ +extern cmd_return_code_t +uty_pipe_out_push(vio_vf vf, bool final) +{ + VTY_ASSERT_LOCKED() ; /* In any thread */ + assert((vf->vout_state == vf_open) || (vf->vout_state == vf_closing)) ; + assert((vf->vout_type == VOUT_PIPE) || (vf->vout_type == VOUT_SHELL_ONLY)) ; + + /* If blocking, see if there is anything in the return, and shovel. + * + * For non-blocking, the pselect process looks after this. + */ + if (vf->blocking && (vf->pr_state == vf_open)) + { + cmd_return_code_t ret ; + + ret = uty_pipe_shovel(vf, final, false) ; /* not closing */ + + if ((ret != CMD_SUCCESS) && (ret != CMD_EOF) && !final) + return ret ; + } ; + + /* Now write away everything we can -- nothing if pr_only. + */ + if (!vf->pr_only) + { + bool all ; + + all = (vf->vout_state == vf_closing) ; + + while (1) + { + qps_mini_t qm ; + int put ; + + put = vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), all) ; + + if (put < 0) + return CMD_IO_ERROR ; /* TODO */ + + if ((put == 0) || final) /* all gone or final */ + break ; + + /* Cannot write everything away without waiting */ + if (!vf->blocking) + { + uty_vf_set_write(vf, on) ; + return CMD_WAITING ; + } ; + + /* Implement blocking I/O, with timeout */ + qps_mini_set(qm, vio_vfd_fd(vf->vfd), qps_write_mnum, + vf->write_timeout) ; + if (qps_mini_wait(qm, NULL, false) == 0) + return CMD_IO_TIMEOUT ; + + /* Loop back to vio_fifo_write_nb() */ + } ; + } ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Shovel from return to slave, pushing the slave as we go. + * + * For VIN_PIPE: this is called between command line reads, when a command + * line read needs more input. Wants to suck the return dry before proceeding + * to read any more command lines. Is also called when the vin is closed. + * + * For VOUT_PIPE: for blocking vfs this is called each time the output is + * pushed -- to keep the process moving. And when the vout is closed, to + * complete the process. For non-blocking vfs, TODO + * + * For VOUT_SHELL_ONLY: same as VOUT_PIPE. + * + * For blocking, keeps going until can read no more from the return. The slave + * output may block, and could time out. + * + * With non-blocking, reads from the return and pushes at the slave. If the + * slave obuf is not emptied by the push, the slave will set the master + * read ready, when the slave goes write-ready. + * + * For "final" does not attempt to read anything from the return, but sets it + * vf_eof. + * + * For "closing" (if not "final"), if blocking will block on the return, until + * get eof (or time out). + * + * Returns: CMD_SUCCESS -- shovelled everything, but not hit EOF + * if "closing" <=> non-blocking + * CMD_EOF -- shovelled everything and is now at EOF + * CMD_WAITING -- waiting for slave write-ready <=> non-blocking + * CMD_IO_ERROR -- ran into an I/O error + * CMD_IO_TIMEOUT -- could wait no longer <=> blocking + * + * This can be called in any thread. + */ +static cmd_return_code_t +uty_pipe_shovel(vio_vf vf, bool final, bool closing) +{ + vio_vf slave ; + + VTY_ASSERT_LOCKED() ; /* In any thread */ + + // TODO other pr_state ??? vf_closed/vf_closing/vf_error ?? + + /* Suck and blow -- may block in uty_cmd_out_push() + * + * Note that we do not push the slave if there is nothing new from the + * return -- there shouldn't be anything pending in the slave obuf. + */ + slave = vf->pr_slave ; + assert(vf == slave->pr_master) ; + + while (!final && (vf->pr_state == vf_open)) + { + cmd_return_code_t ret ; + int get ; + + get = vio_fifo_read_nb(slave->obuf, vio_vfd_fd(vf->pr_vfd), 10) ; + + if (get == 0) + { + qps_mini_t qm ; + + if (!closing || !vf->blocking) + return CMD_SUCCESS ; /* quit if now dry */ + + /* Implement blocking I/O, with timeout */ + qps_mini_set(qm, vio_vfd_fd(vf->pr_vfd), qps_read_mnum, + vf->read_timeout) ; + if (qps_mini_wait(qm, NULL, false) == 0) + return CMD_IO_TIMEOUT ; + } + + else if (get >= 0) + { + ret = uty_cmd_out_push(slave, final) ; /* may block etc. */ + + if (ret != CMD_SUCCESS) + return ret ; + } + + else if (get == -1) + return CMD_IO_ERROR ; /* register error TODO */ + + else + { + assert (get == -2) ; /* eof on the return */ + break ; + } ; + } ; + + vf->pr_state = vf_eof ; /* Set EOF TODO: willy nilly ? */ + + return CMD_EOF ; +} ; + +/*------------------------------------------------------------------------------ + * Pipe is ready to read -- this is the call-back planted in the vf->vfd, of + * type VIN_PIPE. + * + * Can restart the command_queue. + * + * Note that the read_ready state and any time out are automatically + * disabled when they go off. + */ +static void +uty_pipe_read_ready(vio_vfd vfd, void* action_info) +{ + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->vfd == vfd) ; + + uty_cmd_signal(vf->vio, CMD_SUCCESS) ; + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * Pipe has timed out, waiting to read -- this is the call-back planted in the + * vf->vfd, of type VIN_PIPE. + * + * ???? + */ +static vty_timer_time +uty_pipe_read_timeout(vio_timer timer, void* action_info) +{ + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->vfd->read_timer == timer) ; + +// TODO .... signal time out + + VTY_UNLOCK() ; + + return 0 ; /* Do not restart timer */ } ; /*------------------------------------------------------------------------------ - * Tidy up after input file has been closed + * Pipe return is ready to read -- this is the call-back planted in the + * vf->pr_vfd: + * + * VIN_PIPE: used only when closing the vin, and are waiting for + * the return to be drained. + * + * VOUT_PIPE: used to continually shovel from the return to the + * slave -- including while closing. + * + * VOUT_SHELL_ONLY: used to continually shovel from the return to the + * slave -- which happens while closing. + * + * If all is well, and more return input is expected, re-enables read ready. + * + * If uty_pipe_shovel() returns CMD_WAITING, then is waiting for the slave + * output buffer to clear. The slave will kick uty_pipe_return_slave_ready(). + * + * For all other returns, kick the command loop, which if it is waiting is TODO + */ +static void +uty_pipe_return_ready(vio_vfd vfd, void* action_info) +{ + cmd_return_code_t ret ; + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->pr_vfd == vfd) ; + + ret = uty_pipe_shovel(vf, false, false) ; /* not final or closing */ + /* TODO -- errors !! */ + if (ret == CMD_SUCCESS) + vio_vfd_set_read(vf->pr_vfd, on, 0) ; /* expect more */ + else if (ret != CMD_WAITING) + uty_cmd_signal(vf->vio, (ret == CMD_EOF) ? CMD_SUCCESS + : ret) ; /* in case TODO */ + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * Pipe return slave is ready to write. + * + * This is called by the pipe return slave, to prompt the master when the slave + * has been waiting to be able to write away its output buffer. + * + * This will set read ready on the return, to keep the process of shovelling + * from return to slave going. */ extern void -uty_file_read_close(vio_vf vf) +uty_pipe_return_slave_ready(vio_vf slave) { - return ; + vio_vf vf ; + + vf = slave->pr_master ; + assert(vf->pr_slave == slave) ; + + vio_vfd_set_read(vf->pr_vfd, on, 0) ; } ; /*------------------------------------------------------------------------------ - * Flush output buffer and close. + * Pipe return has timed out -- this is the call-back planted in the + * vf->pr_vfd of either a VIN_PIPE or a VOUT_PIPE ???? * - * Returns: true <=> buffer (now) empty + * ???? */ -extern bool -uty_file_write_close(vio_vf vf, bool final) +static vty_timer_time +uty_pipe_return_timeout(vio_timer timer, void* action_info) { - return vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), true) == 0 ; + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->pr_vfd->read_timer == timer) ; + +//cq_continue(vf->vio->vty) ; TODO + + VTY_UNLOCK() ; + + return 0 ; /* Do not restart timer */ } ; + +/*------------------------------------------------------------------------------ + * Pipe output is ready to write -- this is the call-back planted in the + * vf->vfd of a VOUT_PIPE + * + * Note that the write_ready state and any time out are automatically + * disabled when they go off. + */ +static void +uty_pipe_write_ready(vio_vfd vfd, void* action_info) +{ + cmd_return_code_t ret ; + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->vfd == vfd) ; + + ret = uty_pipe_out_push(vf, false) ; /* not final */ + + if (ret != CMD_WAITING) + uty_cmd_signal(vf->vio, ret) ; + + if ((ret == CMD_SUCCESS) && (vf->pr_master != NULL)) + uty_pipe_return_slave_ready(vf) ; + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * Pipe output has timed out -- this is the call-back planted in the + * vf->vfd of a VOUT_PIPE + * + * ???? + */ +static vty_timer_time +uty_pipe_write_timeout(vio_timer timer, void* action_info) +{ + vio_vf vf ; + + VTY_LOCK() ; + VTY_ASSERT_CLI_THREAD() ; + + vf = action_info ; + assert(vf->vfd->write_timer == timer) ; + +// TODO + + VTY_UNLOCK() ; + + return 0 ; /* Do not restart timer */ +} ; + +/*------------------------------------------------------------------------------ + * Tidy up after input pipe has been read closed. + * + * Nothing needs to be done with the main input, but do need to close the + * return, collect the child and release the slave. + */ +extern cmd_return_code_t +uty_pipe_read_close(vio_vf vf, bool final) +{ + return uty_pipe_return_close(vf, final) ; +} ; + +/*------------------------------------------------------------------------------ + * Close VOUT_PIPE or VOUT_SHELL_ONLY. + * + * For not-final, wish to flush output buffer. If final will attempt to empty + * the output buffer -- but will not wait or block, and ignores errors. + * + * Must then close the return, collect the child and release the slave. + * + * Returns: CMD_SUCCESS -- pushed what there was, or final + * CMD_WAITING -- would have blocked <=> non-blocking + * CMD_IO_ERROR -- ran into an I/O error + * CMD_IO_TIMEOUT -- could wait no longer <=> blocking + */ +extern cmd_return_code_t +uty_pipe_write_close(vio_vf vf, bool final, bool base, bool shell_only) +{ + cmd_return_code_t ret ; + + VTY_ASSERT_CAN_CLOSE_VF(vf) ; + + /* If the main vfd is still there, keep pushing. + * + * Close it if manage to empty out buffers (or final) -- signals end to the + * child process. + */ + if (!vf->pr_only) + { + ret = uty_pipe_out_push(vf, final) ; + + if ((ret != CMD_SUCCESS) && !final) + return ret ; + + /* Now need to close the output vfd to signal to the child that we + * are done. + * + * Sets pr_only so that if does go CMD_WAITING, then future call of + * uty_pipe_out_push() will not attempt any I/O ! vf->vfd is set NULL, + * which will be ignored by any future vio_vfd_close(). + */ + vf->vfd = vio_vfd_close(vf->vfd) ; + vf->pr_only = true ; /* only the return is left */ + } ; + + /* If the return is still open, try to empty and close that. */ + return uty_pipe_return_close(vf, final) ; +} ; + +/*------------------------------------------------------------------------------ + * Close the return on a pipe. + * + * If not final, may need to drain the return. + * + * Need to collect the child to complete the process. Once the child is + * collected (or have given up waiting, will send a message to the slave if + * not completed with return code == 0. + * + * Once any message has been pushed to the slave, can release the slave + * and the close process will be complete. + * + * If final, then will not block and will ignore errors. Return code reflects + * the last operation. + * + * Returns: CMD_SUCCESS -- closed and child collected + * CMD_WAITING + * CMD_IO_ERROR + * CMD_IO_TIMEOUT + * CMD_xxxx -- child error(s) + */ +static cmd_return_code_t +uty_pipe_return_close(vio_vf vf, bool final) +{ + cmd_return_code_t ret ; + + VTY_ASSERT_CAN_CLOSE_VF(vf) ; + + /* If the return is still open, try to empty and close that. */ + if (vf->pr_state != vf_closed) + { + ret = uty_pipe_return_empty(vf, final) ; + + if (!final) + { + if (ret == CMD_SUCCESS) + { + vio_vfd_set_read(vf->pr_vfd, on, 0) ; /* TODO timeout */ + return CMD_WAITING ; + } ; + + if (ret != CMD_EOF) + return ret ; + } ; + } ; + + /* If not already collected, collect the child. */ + if (vf->child != NULL) + { + ret = uty_pipe_collect_child(vf, final) ; + + if ((ret != CMD_SUCCESS) && !final) + return ret ; + } ; + + /* Finally, release the slave */ + return uty_pipe_release_slave(vf, final) ; +} + +/*------------------------------------------------------------------------------ + * If the return is still open, shovel from return to slave. + * + * If final will not block or wait, and ignores errors -- return code reflects + * the last operation. + * + * If blocking, will block until return hits EOF or timeout. + * + * Returns: CMD_SUCCESS -- shovelled what there was, may be more to come + * <=> non-blocking + * CMD_EOF -- shovelled what there was, is now *closed* + * CMD_WAITING -- waiting for slave write-ready <=> non-blocking + * CMD_IO_ERROR -- ran into an I/O error + * CMD_IO_TIMEOUT -- could wait no longer <=> blocking + */ +static cmd_return_code_t +uty_pipe_return_empty(vio_vf vf, bool final) +{ + cmd_return_code_t ret ; + + // worry about pr_state ?? + + ret = uty_pipe_shovel(vf, final, true) ; /* closing ! */ + + if ((ret == CMD_EOF) || final) + { + vf->pr_vfd = vio_vfd_close(vf->pr_vfd) ; + vf->pr_state = vf_closed ; + } ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Collect child. + * + * When does collect, or times out, will dismiss the child and set vf->child + * to NULL -- which indicates that the child is done with. + * + * Returns: CMD_SUCCESS -- child dismissed, vf->child == NULL + * CMD_WAITING -- waiting to collect child <=> non-blocking + * + */ +static cmd_return_code_t +uty_pipe_collect_child(vio_vf vf, bool final) +{ + vio_vf slave ; + + assert(vf->child != NULL) ; + + /* Collect -- blocking or not blocking */ + if (!vf->child->collected && !vf->child->overdue) + { + /* If we are !blocking and !final, leave the child collection up to + * the SIGCHLD system. + */ + if (!vf->blocking && !final) + { + uty_child_awaited(vf->child, 6) ; + return CMD_WAITING ; + } ; + + /* If we are blocking or final, try to collect the child here and + * now -- if not final may block here. + */ + uty_child_collect(vf->child, 6, final) ; + } ; + + /* If child is overdue, or did not terminate cleanly, write message to + * slave... which we then have to push... + */ + slave = vf->pr_slave ; + assert((slave != NULL) && (vf == slave->pr_master)) ; + + if (!vf->child->collected) + { + vio_fifo_printf(slave->obuf, "%% child process still running\n") ; + } + else if (WIFEXITED(vf->child->report)) + { + int status = WEXITSTATUS(vf->child->report) ; + if (status != 0) + vio_fifo_printf(slave->obuf, + "%% child process ended normally, but with status = %d\n", + status) ; + } + else if (WIFSIGNALED(vf->child->report)) + { + int signal = WTERMSIG(vf->child->report) ; + vio_fifo_printf(slave->obuf, + "%% child process terminated by signal = %d\n", signal) ; + } + else + { + vio_fifo_printf(slave->obuf, + "%% child process ended in unknown state = %d\n", + vf->child->report) ; + } ; + + /* Can now dismiss the child. */ + vf->child = uty_child_dismiss(vf->child, false) ; /* not final */ + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Push output to slave a final time -- to shift any final message about state + * of child on closing. + * + * Then release slave. + */ +static cmd_return_code_t +uty_pipe_release_slave(vio_vf vf, bool final) +{ + cmd_return_code_t ret ; + vio_vf slave ; + + ret = CMD_SUCCESS ; + + slave = vf->pr_slave ; + if (slave != NULL) + { + assert(vf == slave->pr_master) ; + + ret = uty_cmd_out_push(slave, final) ; /* may block etc. */ + + if ((ret != CMD_SUCCESS) && !final) + return ret ; + + slave->pr_master = NULL ; /* release slave */ + vf->pr_slave = NULL ; + } ; + + return ret ; +} ; + +/*============================================================================== + * stdout and stderr + * + * + */ + diff --git a/lib/vty_io_file.h b/lib/vty_io_file.h index ab852e08..5bb5a607 100644 --- a/lib/vty_io_file.h +++ b/lib/vty_io_file.h @@ -26,6 +26,7 @@ #include "misc.h" #include "vty_io.h" +#include "command_parse.h" /*============================================================================== * Here are structures and other definitions which are shared by: @@ -38,16 +39,32 @@ /*============================================================================== * Functions */ +extern vty vty_config_read_open(int fd, const char* name, bool full_lex) ; +extern cmd_return_code_t uty_config_read_close(vio_vf vf, bool final) ; extern cmd_return_code_t uty_file_read_open(vty_io vio, qstring name, - bool reflect) ; + cmd_context context) ; extern cmd_return_code_t uty_file_write_open(vty_io vio, qstring name, - bool append) ; + bool append, cmd_context context) ; -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 cmd_return_code_t uty_file_fetch_command_line(vio_vf vf, + cmd_action action) ; +extern cmd_return_code_t uty_file_out_push(vio_vf vf, bool final) ; -extern void uty_file_read_close(vio_vf vf) ; -extern bool uty_file_write_close(vio_vf vf, bool final) ; +extern cmd_return_code_t uty_file_read_close(vio_vf vf, bool final) ; +extern cmd_return_code_t uty_file_write_close(vio_vf vf, bool final, bool base) ; -#endif /* _ZEBRA_VTY_IO_FILE_H */ + +extern cmd_return_code_t uty_pipe_read_open(vty_io vio, qstring command, + cmd_context context) ; +extern cmd_return_code_t uty_pipe_write_open(vty_io vio, qstring command, + bool shell_only) ; +extern cmd_return_code_t uty_pipe_fetch_command_line(vio_vf vf, + cmd_action action) ; +extern cmd_return_code_t uty_pipe_out_push(vio_vf vf, bool final) ; +extern void uty_pipe_return_slave_ready(vio_vf slave) ; +extern cmd_return_code_t uty_pipe_read_close(vio_vf vf, bool final) ; +extern cmd_return_code_t uty_pipe_write_close(vio_vf vf, bool final, bool base, + bool shell_only) ; + +#endif diff --git a/lib/vty_io_shell.c b/lib/vty_io_shell.c index 7b24b871..0b3cb5c4 100644 --- a/lib/vty_io_shell.c +++ b/lib/vty_io_shell.c @@ -106,7 +106,7 @@ uty_serv_vtysh(const char *path) 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); + zlog(NULL, LOG_ERR, "path too long for unix stream socket: '%s'", path); return -1 ; } ; @@ -117,7 +117,7 @@ uty_serv_vtysh(const char *path) sock = socket (AF_UNIX, SOCK_STREAM, 0); if (sock < 0) { - uzlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s", + zlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s", errtoa(errno, 0).str) ; return -1 ; } @@ -137,7 +137,7 @@ uty_serv_vtysh(const char *path) 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); + zlog(NULL, LOG_ERR, "Cannot bind path %s: %s", path, errtoa(errno, 0).str); if (ret >= 0) ret = set_nonblocking(sock); @@ -146,7 +146,7 @@ uty_serv_vtysh(const char *path) { ret = listen (sock, 5); if (ret < 0) - uzlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock, + zlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock, errtoa(errno, 0).str) ; } ; @@ -156,7 +156,7 @@ uty_serv_vtysh(const char *path) { /* set group of socket */ if ( chown (path, -1, ids.gid_vty) ) - uzlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s", + zlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s", errtoa(errno, 0).str) ; } @@ -196,7 +196,7 @@ uty_accept_shell_serv (vty_listener listener) if (sock_fd < 0) { - uzlog (NULL, LOG_WARNING, "can't accept vty shell socket : %s", + zlog (NULL, LOG_WARNING, "can't accept vty shell socket : %s", errtoa(errno, 0).str) ; return -1; } @@ -216,7 +216,7 @@ uty_accept_shell_serv (vty_listener listener) uty_new_shell_serv(sock_fd) ; /* Log new VTY */ - uzlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock_fd); + zlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock_fd); return 0; } diff --git a/lib/vty_io_term.c b/lib/vty_io_term.c index 0f8e834d..2c9cb643 100644 --- a/lib/vty_io_term.c +++ b/lib/vty_io_term.c @@ -26,10 +26,13 @@ #include "vty_local.h" #include "vty_io.h" #include "vty_io_term.h" +#include "vty_io_file.h" #include "vty_cli.h" #include "vty_command.h" #include "vio_fifo.h" +#include "log_local.h" + #include "qstring.h" #include "keystroke.h" @@ -75,12 +78,21 @@ * Opening and closing VTY_TERMINAL type */ -static void uty_term_ready(vio_vfd vfd, void* action_info) ; -static vty_timer_time uty_term_read_timeout(vio_timer_t* timer, +typedef enum { + utw_error = 0, + utw_done = BIT(0), /* all possible is done */ + utw_stopped = BIT(1), + utw_blocked = BIT(2), /* I/O blocked */ + utw_paused = utw_blocked | utw_stopped, +} utw_ret_t ; + +static void uty_term_read_ready(vio_vfd vfd, void* action_info) ; +static void uty_term_write_ready(vio_vfd vfd, void* action_info) ; +static vty_timer_time uty_term_read_timeout(vio_timer timer, void* action_info) ; -static vty_timer_time uty_term_write_timeout(vio_timer_t* timer, +static vty_timer_time uty_term_write_timeout(vio_timer timer, void* action_info) ; -static vty_readiness_t uty_term_write(vio_vf vf) ; +static utw_ret_t uty_term_write(vio_vf vf) ; static void uty_term_will_echo(vty_cli cli) ; static void uty_term_will_suppress_go_ahead(vty_cli cli) ; @@ -96,34 +108,38 @@ static void uty_term_dont_lflow_ahead(vty_cli cli) ; static void uty_term_open(int sock_fd, union sockunion *su) { + node_type_t node ; vty vty ; vty_io vio ; vio_vf vf ; - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - /* Allocate new vty structure and set up default values. */ - vty = uty_new(VTY_TERMINAL) ; - vio = vty->vio ; + VTY_ASSERT_CLI_THREAD_LOCKED() ; - /* The initial vty->node depends on a number of vty type things, so - * we set that now. + /* The initial vty->node will be authentication, unless the host does not + * require that, in which case it may be a number of other things. * - * This completes the initialisation of the vty object, except that the - * execution and vio objects are largely empty. + * Note that setting NULL_NODE at this point will cause the terminal to be + * closed very quickly, after issuing suitable message. */ + node = (host.password != NULL) ? AUTH_NODE : NULL_NODE ; + if (host.no_password_check) { - if (host.restricted_mode) - vio->vty->node = RESTRICTED_NODE; - else if (host.advanced) - vio->vty->node = ENABLE_NODE; + if (host.restricted_mode) + node = RESTRICTED_NODE; + else if (host.advanced && (host.enable == NULL)) + node = ENABLE_NODE; else - vio->vty->node = VIEW_NODE; - } - else - vio->vty->node = AUTH_NODE; + node = VIEW_NODE; + } ; + + /* Allocate new vty structure and set up default values. + * + * This completes the initialisation of the vty object, except that the + * execution and vio objects are largely empty. + */ + vty = uty_new(VTY_TERMINAL, node) ; + vio = vty->vio ; /* Complete the initialisation of the vty_io object. * @@ -134,7 +150,6 @@ uty_term_open(int sock_fd, union sockunion *su) * * - 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. @@ -143,10 +158,10 @@ uty_term_open(int sock_fd, union sockunion *su) */ vf = uty_vf_new(vio, sutoa(su).str, sock_fd, vfd_socket, vfd_io_read_write) ; - uty_vin_open( vio, vf, VIN_TERM, uty_term_ready, + uty_vin_push( vio, vf, VIN_TERM, uty_term_read_ready, uty_term_read_timeout, 0) ; /* no ibuf required */ - uty_vout_open(vio, vf, VOUT_TERM, uty_term_ready, + uty_vout_push(vio, vf, VOUT_TERM, uty_term_write_ready, uty_term_write_timeout, 4096) ; /* obuf is required */ @@ -155,9 +170,6 @@ uty_term_open(int sock_fd, union sockunion *su) /* Set up the CLI object & initialise */ vf->cli = uty_cli_new(vf) ; - /* When we get here the VTY is set up and all ready to go. */ - uty_cmd_prepare(vio) ; - /* Issue Telnet commands/escapes to be a good telnet citizen -- not much * real negotiating going on -- just a statement of intentions ! */ @@ -171,90 +183,221 @@ uty_term_open(int sock_fd, union sockunion *su) /* Say hello */ vty_hello(vty); - /* If need password, issue prompt or give up if no password to check - * against ! + /* If about to authenticate, issue friendly message. + * + * If cannot authenticate, issue an error message. + */ + if (vty->node == AUTH_NODE) + vty_out(vty, "User Access Verification\n") ; + else if (vty->node == NULL_NODE) + vty_out(vty, "%% Cannot continue because no password is set\n") ; + + /* Enter the command loop. */ + uty_cmd_loop_enter(vio) ; +} ; + +/*------------------------------------------------------------------------------ + * Command line fetch from a VTY_TERMINAL. + * + * Fetching a command line <=> the previous command has completed. + * + * Returns: CMD_SUCCESS -- have another command line ready to go + * CMD_WAITING -- would not wait for input <=> non-blocking + * CMD_EOF -- ?????? + * + * This can be called in any thread. + * + * NB: this does not signal CMD_EOF TODO ???? + */ +extern cmd_return_code_t +uty_term_fetch_command_line(vio_vf vf, cmd_action action, cmd_context context) +{ + return uty_cli_want_command(vf->cli, action, context) ; +} ; + +/*------------------------------------------------------------------------------ + * Showing error context for the VTY_TERMINAL command line. + * + * If the stack is at level == 1, then the last full line displayed will be + * the line in which the error occurred (unless have monitor output, which + * there is little we can do about). So there is no further output required. + * The command line is indented by the current prompt. + * + * If the stack is at level > 1, then may or may not have had output separating + * the command line from the current position, so we output the command line + * to provide context. + * + * Returns: indent position of command line + */ +extern uint +uty_term_show_error_context(vio_vf vf, vio_fifo ebuf, uint depth) +{ + if (depth == 1) + return uty_cli_prompt_len(vf->cli) ; + + vio_fifo_printf(ebuf, "%% in command line:\n") ; + vio_fifo_printf(ebuf, " %s\n", qs_make_string(vf->cli->clx)) ; + + return 2 ; +} ; + +/*------------------------------------------------------------------------------ + * Push output to the terminal. + * + * Returns: CMD_SUCCESS -- all buffers are empty, or final + * CMD_WAITING -- all buffers are not empty + * CMD_IO_ERROR -- failed -- final or not. + * + * This can be called in any thread. If "final" will not turn on any + * read/write ready stuff. + */ +extern cmd_return_code_t +uty_term_out_push(vio_vf vf, bool final) +{ + vty_cli cli = vf->cli ; + utw_ret_t done ; + + /* If have something in the obuf that needs to be written, then if not already + * out_active, make sure the command line is clear, and set out_active. */ - if (vty->node == AUTH_NODE) + if (!cli->out_active && !vio_fifo_empty(vf->obuf)) { - 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.")); + uty_cli_wipe(cli, 0) ; + cli->out_active = true ; + vio_lc_counter_reset(cli->olc) ; + } ; + + /* Give the terminal writing a shove. + * + * If final, keep pushing while succeeds in writing without blocking. + */ + if (final) + cli->flush = cli->out_active ; /* make sure empty everything */ + + do + { + if (final) + vio_lc_counter_reset(cli->olc) ; + + done = uty_term_write(vf) ; + } + while (final && (done == utw_paused)) ; + + if (!final) + { + if (done == utw_error) + return CMD_IO_ERROR ; /* TODO */ + + if ((done & utw_blocked) != 0) + { + confirm((utw_paused & utw_blocked) != 0) ; + + uty_term_set_readiness(vf, write_ready) ; + return CMD_WAITING ; + } ; } ; - /* Push the output to date and start the CLI */ - uty_cmd_out_push(vio) ; - uty_cli_start(vf->cli, vty->node) ; + return CMD_SUCCESS ; } ; /*------------------------------------------------------------------------------ - * Close the reading side of VTY_TERMINAL, and close down CLI as far as + * The read side of the vfd has been closed. Close down CLI as far as * possible, given that output may be continuing. * * Expects to be called once only for the VTY_TERMINAL. + * + * There is no difference between final and not-final close in this case. + * + * Note that this is only closed when the VTY_TERMINAL is forcibly closed, or + * when the user quits. + * + * Returns: CMD_SUCCESS -- all is quiet. */ -extern void -uty_term_read_close(vio_vf vf) +extern cmd_return_code_t +uty_term_read_close(vio_vf vf, bool final) { vty_io vio ; /* Get the vio and ensure that we are all straight */ vio = vf->vio ; assert((vio->vin == vio->vin_base) && (vio->vin == vf)) ; + assert(vio->vin->vin_state == vf_closing) ; - /* 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) ; + /* Close the CLI as far as possible, leaving output side intact. + * + * Can generate some final output, which will be dealt with as the output + * side is closed. + */ uty_cli_close(vf->cli, false) ; - /* Log closing of VTY_TERM */ + /* Log closing of VTY_TERMINAL + */ assert(vio->vty->type == VTY_TERMINAL) ; - uzlog (NULL, LOG_INFO, "Vty connection (fd %d) close", vio_vfd_fd(vf->vfd)) ; + zlog (NULL, LOG_INFO, "Vty connection (fd %d) close", vio_vfd_fd(vf->vfd)) ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Try to output the close reason to the given VOUT_TERM. + * + * If there is anything pending to be output, discard it, first. The obuf has + * already been cleared. + * + * This will be pushed out when the VOUT_TERM is finally closed. + */ +extern void +uty_term_close_reason(vio_vf vf, const char* reason) +{ + vio_lc_clear(vf->cli->olc) ; + vio_fifo_clear(vf->cli->cbuf, true) ; + + uty_cli_out(vf->cli, "%% %s%s", reason, uty_cli_newline) ; } ; /*------------------------------------------------------------------------------ * Close the writing side of VTY_TERMINAL. * - * Pushes any buffered stuff to output and + * Assumes that the read side has been closed already, and so this is the last + * thing to be closed. + * + * Kicks the output side: * + * if final, will push as much as possible until would block. + * + * if not final, will push another tranche and let the uty_term_ready() keep + * pushing until buffers empty and can uty_cmd_signal(). + * + * Returns: CMD_SUCCESS => all written (or final) and CLI closed. + * CMD_WAITING => write ready running to empty the buffers + * CMD_IO_ERROR => + * others TODO */ -extern bool -uty_term_write_close(vio_vf vf, bool final) +extern cmd_return_code_t +uty_term_write_close(vio_vf vf, bool final, bool base) { + cmd_return_code_t ret ; vty_io vio ; - vty_readiness_t ready ; - - /* Get the vio and ensure that we are all straight */ - vio = vf->vio ; - assert((vio->vin == vio->vin_base) && (vio->vin == vf)) ; - /* Do the file side of things + /* Get the vio and ensure that we are all straight * - * Note that half closing the file sets a new timeout, sets read off - * and write on. + * Can only be the vout_base and must also be the vin_base, and the vin_base + * must now be closed. */ - uty_set_monitor(vio, 0) ; - - vf->cli->out_active = true ; /* force the issue */ - - do - { - vf->cli->out_done = false ; - ready = uty_term_write(vf) ; - } while ((ready != write_ready) && vf->cli->out_active) ; - - final = final || !vf->cli->out_active ; + vio = vf->vio ; + assert((vio->vout == vio->vout_base) && (vio->vout == vf)) ; + assert((vio->vin == vio->vin_base) && (vio->vin->vin_state == vf_closed)) ; - if (!final) - uty_term_set_readiness(vf, ready) ; + ret = uty_term_out_push(vf, final) ; - vf->cli = uty_cli_close(vf->cli, final) ; + if (final) + vf->cli = uty_cli_close(vf->cli, final) ; - return final ; + return ret ; } ; /*============================================================================== @@ -272,6 +415,8 @@ uty_term_write_close(vio_vf vf, bool final) * not set both at once. */ +static void uty_term_ready(vio_vf vf) ; + /*------------------------------------------------------------------------------ * Set read/write readiness -- for VIN_TERM/VOUT_TERM * @@ -282,12 +427,41 @@ 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)) ; + if ((ready & write_ready) != 0) + uty_vf_set_write(vf, on) ; + else if ((ready & read_ready) != 0) + uty_vf_set_read(vf, on) ; +} ; + +/*------------------------------------------------------------------------------ + * Terminal read ready + */ +static void +uty_term_read_ready(vio_vfd vfd, void* action_info) +{ + vio_vf vf = action_info ; + + assert(vfd == vf->vfd) ; + + vf->cli->paused = false ; /* read ready clears paused */ + uty_term_ready(vf) ; +} ; + +/*------------------------------------------------------------------------------ + * Terminal write ready + */ +static void +uty_term_write_ready(vio_vfd vfd, void* action_info) +{ + vio_vf vf = action_info ; + + assert(vfd == vf->vfd) ; + + uty_term_ready(vf) ; } ; /*------------------------------------------------------------------------------ - * So there is only one, common, uty_term_ready function, which: + * Terminal, something is ready -- read, write or no longer paused. * * 1. attempts to clear any output it can. * @@ -323,24 +497,142 @@ uty_term_set_readiness(vio_vf vf, vty_readiness_t ready) * Resets the timer because something happened. */ static void -uty_term_ready(vio_vfd vfd, void* action_info) +uty_term_ready(vio_vf vf) { vty_readiness_t ready ; + utw_ret_t done ; + + VTY_ASSERT_LOCKED() ; + + /* Start by trying to write away any outstanding stuff, and then another + * tranche of any outstanding output. + */ + ready = not_ready ; + + if (!vf->cli->more_enabled) + vio_lc_counter_reset(vf->cli->olc) ; /* do one tranche */ + + done = uty_term_write(vf) ; + + while (done != utw_error) + { + utw_ret_t done_before ; + done_before = done ; + + /* Kick the CLI, which may advance either because there is more input, + * or because some output has now completed, or for any other reason. + * + * This may return write_ready, which is a proxy for CLI ready, and + * MUST be honoured, even (especially) if the output buffers are empty. + */ + ready = uty_cli(vf->cli) ; + + /* Now try to write away any new output which may have been generated + * by the CLI. + */ + done = uty_term_write(vf) ; + + if (done == done_before) + break ; /* quit if no change in response */ + } ; + + if (done == utw_error) + ; /* TODO !! */ + + if ((done & utw_blocked) != 0) + { + confirm((utw_paused & utw_blocked) != 0) ; + ready |= write_ready ; + } ; + + if ((done != utw_blocked) && (done != utw_error)) + { + /* Since is not output blocked, tell master that is now ready. */ + if (vf->pr_master != NULL) + uty_pipe_return_slave_ready(vf) ; + } ; + + uty_term_set_readiness(vf, ready) ; + + /* Signal the command loop if out_active and the buffers empty out. + */ + if (done == utw_done) + uty_cmd_signal(vf->vio, CMD_SUCCESS) ; +} ; + +/*------------------------------------------------------------------------------ + * Read timer has expired. + * + * If closing, 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 vty_timer_time +uty_term_read_timeout(vio_timer timer, void* action_info) +{ + vio_vf vf = action_info ; + + assert(timer == vf->vfd->read_timer) ; + + VTY_ASSERT_LOCKED() ; + + vf->vin_state = vf_timed_out ; + keystroke_stream_set_eof(vf->cli->key_stream, true) ; + + vf->cli->paused = false ; + + uty_term_ready(vf) ; + + return 0 ; + } ; +/*------------------------------------------------------------------------------ + * 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 + * + * Otherwise, half close the VTY and leave it to the death-watch to sweep up. + */ +static vty_timer_time +uty_term_write_timeout(vio_timer timer, void* action_info) +{ vio_vf vf = action_info ; - assert(vfd == vf->vfd) ; + assert(timer == vf->vfd->read_timer) ; VTY_ASSERT_LOCKED() ; - uty_term_write(vf) ; /* try to clear outstanding stuff */ - do + vf->cli->paused = false ; + +//uty_close(vio, true, qs_set(NULL, "Timed out")) ; TODO + + return 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Timeout on the cli->paused timer -- clear paused and treat as CLI ready. + */ +extern void +vty_term_pause_timeout(qtimer qtr, void* timer_info, qtime_mono_t when) +{ + vty_cli cli ; + + VTY_LOCK() ; + + cli = timer_info ; + assert(cli->pause_timer == qtr) ; + + if (cli->paused) { - ready = uty_cli(vf->cli) ; /* do any CLI work... */ - ready |= uty_term_write(vf) ; /* ...and any output that generates */ - } while (ready >= now_ready) ; + cli->paused = false ; + uty_term_ready(cli->vf) ; + } ; - uty_term_set_readiness(vf, ready) ; + VTY_UNLOCK() ; } ; /*============================================================================== @@ -362,7 +654,7 @@ uty_term_ready(vio_vfd vfd, void* action_info) * * Returns: 0 => nothing available * > 0 => read at least one byte - * -1 => EOF (or not open, or failed) + * -1 => EOF (or not open, or failed, or timed out, ...) */ extern int uty_term_read(vio_vf vf, keystroke steal) @@ -397,7 +689,7 @@ uty_term_read(vio_vf vf, keystroke steal) * cli->cbuf -- command line -- reflects the status of the command line * * vf->obuf -- command output -- which is written to the file only while - * out_active. + * out_active, and not blocked in more_wait. * * The cli output takes precedence. * @@ -405,351 +697,256 @@ uty_term_read(vio_vf vf, keystroke steal) * "--more--" mechanism. */ -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) ; +static utw_ret_t uty_term_write_lc(vio_line_control lc, vio_vf vf, + vio_fifo vff) ; /*------------------------------------------------------------------------------ - * 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. + * Have some (more) monitor output to send to the vty. * - * Returns: write_ready if should now set write on - * now_ready if should loop back and try again - * not_ready otherwise + * Make sure the command line is clear, then claim screen for monitor output + * and attempt to empty out buffers. */ -static vty_readiness_t -uty_term_write(vio_vf vf) +extern void +uty_term_mon_write(vio_vf vf) { - vty_cli cli = vf->cli ; - int ret ; + utw_ret_t done ; - VTY_ASSERT_LOCKED() ; + uty_cli_pre_monitor(vf->cli) ; /* make sure in a fit state */ - ret = -1 ; - while (vf->vout_state == vf_open) - { - /* Any outstanding line control output takes precedence */ - ret = uty_write_lc(vf, vf->obuf, cli->olc) ; - if (ret != 0) - break ; + done = uty_term_write(vf) ; /* TODO -- errors !! */ - /* Next: empty out the cli output */ - 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 (!cli->out_active) - return not_ready ; /* done all can do */ - - /* If there is something in the command buffer, do that */ - if (!vio_fifo_empty(vf->obuf)) - { -#if 0 - if (cli->out_done) - break ; /* ...but not if done once */ - - cli->out_done = true ; /* done this once */ -#endif - assert(!cli->more_wait) ; - - ret = uty_write_fifo_lc(vf, vf->obuf, cli->olc) ; - if (ret != 0) - { - 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 ; - - return now_ready ; - } ; - } ; - - /* Exciting stuff: there is nothing left to output... - * - * ... watch out for half closed state. - */ -#if 0 - if (vio->closing) - { - if (vio->close_reason != NULL) - { - 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 */ - } ; - - if (!vio->closed) /* avoid recursion */ - uty_close(vio) ; - - return not_ready ; /* it's all over */ - } ; -#endif - - if (uty_cli_draw_if_required(cli)) - continue ; /* do that now. */ - - /* There really is nothing left to output */ - cli->out_active = false ; + if ((done & utw_blocked) != 0) + { + confirm((utw_paused & utw_blocked) != 0) ; - return not_ready ; + uty_term_set_readiness(vf, write_ready) ; } ; - - /* Arrives here if there is more to do, or failed (or was !write_open) */ - - 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->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, true) ; /* throw away cli stuff */ - uty_cli_out_clear(cli) ; /* throw away cmd stuff */ - - cli->out_active = false ; - - return not_ready ; /* NB: NOT blocked by I/O */ } ; /*------------------------------------------------------------------------------ - * Write as much as possible -- for "monitor" output. + * Write as much as possible of what there is. * - * Outputs only: + * Move to more_wait if required. TODO * - * a. outstanding line control stuff. + * If is cli->flush, then when all buffers are emptied out, clears itself and + * the out_active flag. * - * b. contents of CLI buffer + * Returns: * - * And: + * utw_error -- I/O error -- see errno (utw_error = -1) * - * a. does not report any errors. + * utw_blocked -- write blocked -- some write operation would block * - * b. does not change anything except the state of the buffers. + * utw_paused -- have written as much as line control allows in one go. * - * In particular, for the qpthreaded world, does not attempt to change - * the state of the qfile or any other "thread private" structures. + * NB: this does NOT mean is now in more_wait, it means that + * to write more it is necessary to clear the line + * control pause state. * - * Returns: > 0 => blocked - * 0 => all gone - * < 0 => failed (or !write_open) - */ -#if 0 -static int -uty_write_monitor(vio_vf vf) -{ - VTY_ASSERT_LOCKED() ; - - if (!vf->write_open) - return -1 ; - - if (vf->olc != NULL) - { - int ret ; - ret = uty_write_lc(vf, vf->obuf, vf->olc) ; - - if (ret != 0) - return ret ; - } ; - - return vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf), true) ; -} ; -#endif - -/*------------------------------------------------------------------------------ - * Write the given FIFO to output -- subject to line control. + * utw_stopped -- have done as much as can do -- no more output is possible + * until some external event changes things. + * + * This implies that any pending output has completed, in + * particular the line control iovec and the cli->cbuf are + * both empty. * - * 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. + * This state includes: * - * If the line control becomes "paused", it is time to enter "--more--" state - * -- unless the FIFO is empty (or "--more--" is not enabled). + * * !out_active -- if there is something in the vf->obuf, + * we are not yet ready to output it. * - * NB: expects that the sock is write_open + * * more_wait -- waiting for user * - * Returns: > 0 => blocked or completed one tranche (more to go) - * 0 => all gone - * < 0 => failed + * utw_done -- have written everything can find, unless more output + * arrives, there is no more to do. + * + * If was cli->flush the all output really has gone, as + * well as any incomplete line. Also the out_active and + * the flush flags will have been cleared. + * + * If was not cli->flush, the out_active state persists, + * and there may be an incomplete line still pending. */ -static int -uty_write_fifo_lc(vio_vf vf, vio_fifo vff, vio_line_control lc) +static utw_ret_t +uty_term_write(vio_vf vf) { - int ret ; - char* src ; - size_t have ; - vty_cli cli ; + vty_cli cli = vf->cli ; + utw_ret_t ret ; + int did ; + size_t have, take ; + char* src ; - cli = vf->cli ; + VTY_ASSERT_LOCKED() ; - /* 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. + /* If the vout is neither vf_open, not vf_closing, discard all buffered + * output, and return all done. */ - vio_lc_set_pause(lc) ; /* clears lc->paused */ - - vio_fifo_set_hold_mark(vff) ; - - src = vio_fifo_get(vff, &have) ; - while ((src != NULL) && (!lc->paused)) + if ((vf->vout_state != vf_open) && (vf->vout_state != vf_closing)) { - size_t take ; + vio_fifo_clear(vf->obuf, false) ; + vio_fifo_clear(cli->cbuf, false) ; + vio_lc_clear(cli->olc) ; - if (src == NULL) - break ; - - take = vio_lc_append(lc, src, have) ; - src = vio_fifo_step_get(vff, &have, take) ; - } ; + cli->out_active = false ; + cli->flush = false ; - cli->dirty = (lc->col != 0) ; + cli->more_wait = false ; + cli->more_enter = false ; - /* Write the contents of the line control */ - ret = uty_write_lc(vf, vff, lc) ; + return utw_done ; + } ; - if (ret < 0) - return ret ; /* give up now if failed. */ + /* Any outstanding line control output takes precedence */ + ret = uty_term_write_lc(cli->olc, vf, vf->obuf) ; + if (ret != utw_done) + return ret ; /* utw_blocked or utw_error */ - if ((ret == 0) && vio_fifo_empty(vff)) - return 0 ; /* FIFO and line control empty */ + /* Next: empty out the cli output */ + did = vio_fifo_write_nb(cli->cbuf, vio_vfd_fd(vf->vfd), true) ; + if (did != 0) + return (did < 0) ? utw_error : utw_blocked ; - /* If should now do "--more--", now is the time to prepare for that. + /* Next: if there is monitor output to deal with, deal with it. * - * Entering more state issues a new prompt in the CLI buffer, which can - * be written once line control write completes. + * Note that the mon_active flag is set under VTY_LOCK(), so do not + * need to LOG_LOCK() to discover whether there is anything to do. * - * The "--more--" cli will not do anything until the CLI buffer has - * cleared. + * But the vio->mbuf is filled under LOG_LOCK(), so need to write it + * under the same. */ - if (lc->paused && cli->more_enabled) - uty_cli_enter_more_wait(cli) ; + if (cli->mon_active) + { + LOG_LOCK() ; - return 1 ; /* FIFO or line control, not empty */ -} ; + did = vio_fifo_write_nb(vf->vio->mbuf, vio_vfd_fd(vf->vfd), true) ; -/*------------------------------------------------------------------------------ - * 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(vio_vf vf, vio_fifo vff, vio_line_control lc) -{ - int ret ; + LOG_UNLOCK() ; - ret = vio_lc_write_nb(vio_vfd_fd(vf->vfd), lc) ; + if (did != 0) + return (did < 0) ? utw_error : utw_blocked ; - if (ret <= 0) - vio_fifo_clear_hold_mark(vff) ; /* finished with FIFO contents */ + uty_cli_post_monitor(vf->cli) ; + } ; - return ret ; -} ; + /* If not out_active, or in more_wait, then we are stopped, waiting for some + * external event to move things on. + */ + if (cli->flush) + assert(cli->out_active) ; /* makes no sense, otherwise */ + if (!cli->out_active || cli->more_wait) + return utw_stopped ; -#if 0 + /* Push the output fifo and any complete line fragments that may be buffered + * in hand in the line control -- this will stop if the line counter becomes + * exhausted. + * + * Note that this arranges for vio_lc_append() to be called at least once, + * even if the fifo is empty -- this deals with any parts of a complete + * line that may be held in the line control due to counter exhaustion. + * + * If the fifo is or becomes empty, then if is cli->flush, flush out any + * incomplete line which may be held in the line control -- in effect, + * cli->flush is a phantom '\n' at the end of the output fifo ! + */ + vio_fifo_set_hold_mark(vf->obuf) ; /* released in uty_term_write_lc() */ -/*------------------------------------------------------------------------------ - * 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(vio_vf vf) -{ - if (vf->olc != NULL) - vio_lc_clear(vf->olc) ; + src = vio_fifo_get(vf->obuf, &have) ; + while (1) + { + take = vio_lc_append(cli->olc, src, have) ; - vio_fifo_set_hold_mark(vf->obuf) ; /* mark to keep until all gone */ -} ; + if (take == 0) + break ; -#endif + src = vio_fifo_step_get(vf->obuf, &have, take) ; + if (have == 0) + break ; + } ; + if ((have == 0) && (cli->flush)) + vio_lc_flush(cli->olc) ; + ret = uty_term_write_lc(cli->olc, vf, vf->obuf) ; + if (ret != utw_done) + return ret ; /* utw_blocked or utw_error */ -/*============================================================================== - * Timer actions for VTY_TERMINAL - */ + /* If arrive here, then: + * + * * no output is blocked and no errors have occurred. + * + * * the cli->cbuf is empty. + * + * * the line control iovec buffer is empty. + * + * If the fifo is not empty or there is a some part of a complete line in + * hand, then the line counter must be exhausted. + */ + if ((have != 0) || vio_lc_have_complete_line_in_hand(cli->olc)) + { + assert(vio_lc_counter_is_exhausted(cli->olc)) ; -/*------------------------------------------------------------------------------ - * Read timer has expired. - * - * If closing, 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 vty_timer_time -uty_term_read_timeout(vio_timer_t* timer, void* action_info) -{ - vty_io vio = action_info ; + if (cli->more_enabled) + { + uty_cli_enter_more_wait(cli) ; + return utw_stopped ; + } + else + return utw_paused ; /* artificial block */ + } ; - VTY_ASSERT_LOCKED() ; + /* Exciting stuff: there is nothing left to output... + * + * ...with the sole possible exception of an incomplete line buffered + * in the line control, which can do nothing about until there is more + * to be output, or the output is flushed... + * + * ...if not cli->flush, we are stopped, waiting for something else to + * happen. + */ + assert(!cli->more_wait && !cli->more_enter) ; - uty_close(vio, true, qs_set(NULL, "Timed out")) ; + if (cli->flush) + { + /* Even more exciting: is cli->flush ! + * + * This means that any incomplete line must have been flushed, above. + * So all buffers MUST be empty. + */ + assert(vio_fifo_empty(vf->obuf) && vio_lc_is_empty(cli->olc)) ; - return 0 ; - } ; + cli->out_active = false ; + cli->flush = false ; + } ; + + return utw_done ; +} ; /*------------------------------------------------------------------------------ - * Write timer has expired. + * Write contents of line control iovec buffer (if any). * - * If closing, then this is curtains -- have waited long enough ! + * NB: expects that the vf is write_open * - * TODO .... sort out the VTY_TERMINAL time-out & death-watch timeout + * NB: does nothing other than write() and buffer management. * - * Otherwise, half close the VTY and leave it to the death-watch to sweep up. + * Returns: utw_blocked => blocked + * utw_done => all gone (may still have stuff "in hand") + * utw_error => failed */ -static vty_timer_time -uty_term_write_timeout(vio_timer_t* timer, void* action_info) +static utw_ret_t +uty_term_write_lc(vio_line_control lc, vio_vf vf, vio_fifo vff) { - vty_io vio = action_info ; + int did ; - VTY_ASSERT_LOCKED() ; + did = vio_lc_write_nb(vio_vfd_fd(vf->vfd), lc) ; - uty_close(vio, true, qs_set(NULL, "Timed out")) ; + if (did > 0) + return utw_blocked ; - return 0 ; + vio_fifo_clear_hold_mark(vff) ; /* finished with FIFO contents */ + + return (did == 0) ? utw_done : utw_error ; } ; /*============================================================================== @@ -778,7 +975,7 @@ uty_term_open_listeners(const char *addr, unsigned short port) n = uty_term_listen_simple(addr, port); if (n == 0) - uzlog(NULL, LOG_ERR, "could not open any VTY_TERMINAL listeners") ; + zlog(NULL, LOG_ERR, "could not open any VTY_TERMINAL listeners") ; } ; /*------------------------------------------------------------------------------ @@ -875,7 +1072,7 @@ uty_term_listen_simple(const char *addr, unsigned short port) if (ret == 0) sa = &su_addr.sa ; else - uzlog(NULL, LOG_ERR, "bad address %s, cannot listen for VTY", addr); + zlog(NULL, LOG_ERR, "bad address %s, cannot listen for VTY", addr); } ; /* Try for AF_INET */ @@ -896,7 +1093,7 @@ uty_term_listen_simple(const char *addr, unsigned short port) /* If not used the address... something wrong */ if (sa != NULL) - uzlog(NULL, LOG_ERR, "could not use address %s, to listen for VTY", addr); + zlog(NULL, LOG_ERR, "could not use address %s, to listen for VTY", addr); /* Done */ return n ; @@ -1003,7 +1200,7 @@ uty_term_accept(int sock_listen) if (sock_fd < 0) { if (sock_fd == -1) - uzlog (NULL, LOG_WARNING, "can't accept vty socket : %s", + zlog (NULL, LOG_WARNING, "can't accept vty socket : %s", errtoa(errno, 0).str) ; return ; } @@ -1046,7 +1243,7 @@ uty_term_accept(int sock_listen) if (ret != 0) { - uzlog (NULL, LOG_INFO, "Vty connection refused from %s", sutoa(&su).str) ; + zlog (NULL, LOG_INFO, "Vty connection refused from %s", sutoa(&su).str) ; close (sock_fd); return ; } ; @@ -1056,14 +1253,14 @@ uty_term_accept(int sock_listen) 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", + zlog (NULL, LOG_INFO, "can't set sockopt to socket %d: %s", sock_fd, errtoa(errno, 0).str) ; /* 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, + zlog (NULL, LOG_INFO, "Vty connection from %s (fd %d)", sutoa(&su).str, sock_fd) ; return ; @@ -1315,7 +1512,7 @@ uty_telnet_command(vio_vf vf, keystroke stroke, bool callback) case to_NAWS: if (left != 4) { - uzlog(NULL, LOG_WARNING, + zlog(NULL, LOG_WARNING, "RFC 1073 violation detected: telnet NAWS option " "should send %d characters, but we received %d", (3 + 4 + 2), (3 + left + 2)) ; @@ -1352,148 +1549,3 @@ uty_telnet_command(vio_vf vf, keystroke stroke, bool callback) return dealt_with ; } ; -/*============================================================================== - * 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". - */ -extern void -uty_log(struct logline* ll, struct zlog *zl, int priority, - const char *format, va_list va) -{ - 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 0 - 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_vfd_fd(vf->vfd), 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 ; - } ; -#endif - vio = sdl_next(vio, mon_list) ; - } ; -} ; - -/*------------------------------------------------------------------------------ - * Async-signal-safe version of vty_log for fixed strings. - * - * This is last gasp operation. - */ -void -vty_log_fixed (const char *buf, size_t len) -{ - 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_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 f1823a92..d8999b98 100644 --- a/lib/vty_io_term.h +++ b/lib/vty_io_term.h @@ -39,6 +39,7 @@ #include "thread.h" #include "command_local.h" #include "qstring.h" +#include "qtimers.h" /*============================================================================== * Here are structures and other definitions which are shared by: @@ -54,13 +55,20 @@ extern void uty_term_new(vty_io vio, int sock_fd) ; -extern void uty_term_read_close(vio_vf vf) ; -extern bool uty_term_write_close(vio_vf vf, bool final) ; +extern cmd_return_code_t uty_term_fetch_command_line(vio_vf vf, + cmd_action action, cmd_context context) ; +extern cmd_return_code_t uty_term_out_push(vio_vf vf, bool final) ; +extern uint uty_term_show_error_context(vio_vf vf, vio_fifo ebuf, uint depth) ; +extern cmd_return_code_t uty_term_read_close(vio_vf vf, bool final) ; +extern void uty_term_close_reason(vio_vf vf, const char* reason) ; +extern cmd_return_code_t uty_term_write_close(vio_vf vf, bool final, bool base); extern int uty_term_read(vio_vf vf, keystroke steal) ; extern void uty_term_set_readiness(vio_vf vf, vty_readiness_t ready) ; +extern qtimer_action vty_term_pause_timeout ; +extern void uty_term_mon_write(vio_vf vf) ; extern bool uty_telnet_command(vio_vf, keystroke stroke, bool callback) ; diff --git a/lib/vty_io_vsh.c b/lib/vty_io_vsh.c index 7b24b871..0b3cb5c4 100644 --- a/lib/vty_io_vsh.c +++ b/lib/vty_io_vsh.c @@ -106,7 +106,7 @@ uty_serv_vtysh(const char *path) 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); + zlog(NULL, LOG_ERR, "path too long for unix stream socket: '%s'", path); return -1 ; } ; @@ -117,7 +117,7 @@ uty_serv_vtysh(const char *path) sock = socket (AF_UNIX, SOCK_STREAM, 0); if (sock < 0) { - uzlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s", + zlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s", errtoa(errno, 0).str) ; return -1 ; } @@ -137,7 +137,7 @@ uty_serv_vtysh(const char *path) 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); + zlog(NULL, LOG_ERR, "Cannot bind path %s: %s", path, errtoa(errno, 0).str); if (ret >= 0) ret = set_nonblocking(sock); @@ -146,7 +146,7 @@ uty_serv_vtysh(const char *path) { ret = listen (sock, 5); if (ret < 0) - uzlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock, + zlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock, errtoa(errno, 0).str) ; } ; @@ -156,7 +156,7 @@ uty_serv_vtysh(const char *path) { /* set group of socket */ if ( chown (path, -1, ids.gid_vty) ) - uzlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s", + zlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s", errtoa(errno, 0).str) ; } @@ -196,7 +196,7 @@ uty_accept_shell_serv (vty_listener listener) if (sock_fd < 0) { - uzlog (NULL, LOG_WARNING, "can't accept vty shell socket : %s", + zlog (NULL, LOG_WARNING, "can't accept vty shell socket : %s", errtoa(errno, 0).str) ; return -1; } @@ -216,7 +216,7 @@ uty_accept_shell_serv (vty_listener listener) uty_new_shell_serv(sock_fd) ; /* Log new VTY */ - uzlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock_fd); + zlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock_fd); return 0; } diff --git a/lib/vty_local.h b/lib/vty_local.h index 97e5786c..ff389baf 100644 --- a/lib/vty_local.h +++ b/lib/vty_local.h @@ -24,14 +24,18 @@ #ifndef _ZEBRA_VTY_LOCAL_H #define _ZEBRA_VTY_LOCAL_H +#include "misc.h" #include "vty_common.h" /* First and foremost */ #include "command_common.h" +//#include "log_local.h" /* NB: */ + #include "vargs.h" #include "qpthreads.h" #include "qpnexus.h" #include "thread.h" +#include "qpath.h" /*============================================================================== * This is for access to some things in vty.c which are not required @@ -61,56 +65,82 @@ /*============================================================================== * Variables in vty.c -- used in any of the family */ -extern struct vty_io* vio_list_base ; -extern struct vty_io* vio_monitors_base ; +extern struct vty_io* vio_live_list ; +extern struct vty_io* vio_monitor_list ; extern struct vty_io* vio_death_watch ; +extern struct vio_child* vio_childer_list ; + extern struct thread_master* vty_master ; extern bool vty_nexus ; +extern bool vty_multi_nexus ; extern qpn_nexus vty_cli_nexus ; extern qpn_nexus vty_cmd_nexus ; +extern qpt_mutex_t vty_child_signal_mutex ; +extern qpn_nexus vty_child_signal_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. + * A recursive mutex is used, which 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. + * + * vty also interacts with logging functions. Note that where it needs to + * LOG_LOCK() and VTY_LOCK() it will acquire the VTY_LOCK() *first*. */ extern qpt_mutex_t vty_mutex ; -#ifdef VTY_DEBUG /* Can be forced from outside */ -# if VTY_DEBUG -# define VTY_DEBUG 1 /* Force 1 or 0 */ -#else -# define VTY_DEBUG 0 +/*------------------------------------------------------------------------------ + * Sort out VTY_DEBUG. + * + * Set to 1 if defined, but blank. + * Set to QDEBUG if not defined. + * + * Force to 0 if VTY_NO_DEBUG is defined and not zero. + * + * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to: + * + * * can set VTY_DEBUG == 0 to turn off debug + * * or set VTY_DEBUG != 0 to turn on debug + * * or set VTY_NO_DEBUG != 0 to force debug off + */ + +#ifdef VTY_DEBUG /* If defined, make it 1 or 0 */ +# if IS_BLANK_OPTION(VTY_DEBUG) +# undef VTY_DEBUG +# define VTY_DEBUG 1 # endif -#else -# ifdef QDEBUG -# define VTY_DEBUG 1 /* Follow QDEBUG */ -#else +#else /* If not defined, follow QDEBUG */ +# define VTY_DEBUG QDEBUG +#endif + +#ifdef VTY_NO_DEBUG /* Override, if defined */ +# if IS_NOT_ZERO_OPTION(VTY_NO_DEBUG) +# undef VTY_DEBUG # define VTY_DEBUG 0 # endif #endif enum { vty_debug = VTY_DEBUG } ; +/*------------------------------------------------------------------------------ + * Locking and related functions. + */ extern int vty_lock_count ; Inline void VTY_LOCK(void) /* if is qpthreads_enabled, lock vty_mutex */ { - qpt_mutex_lock(&vty_mutex) ; + qpt_mutex_lock(vty_mutex) ; if (vty_debug) ++vty_lock_count ; } ; @@ -120,7 +150,7 @@ VTY_UNLOCK(void) /* if is qpthreads_enabled, unlock vty_mutex */ { if (vty_debug) --vty_lock_count ; - qpt_mutex_unlock(&vty_mutex) ; + qpt_mutex_unlock(vty_mutex) ; } ; Inline bool /* true => is (effectively) cli thread */ @@ -129,6 +159,12 @@ vty_is_cli_thread(void) return !qpthreads_enabled || qpt_thread_is_self(vty_cli_nexus->thread_id) ; } ; +Inline bool /* true => running with more than one pthread */ +vty_is_multi_pthreaded(void) +{ + return !qpthreads_enabled && (vty_cli_nexus != vty_cmd_nexus) ; +} ; + /* For debug (and documentation) purposes, will VTY_ASSERT_LOCKED where that * is required. * @@ -138,7 +174,6 @@ vty_is_cli_thread(void) * code which is called before qpthreads are started up, or which will never * run qpthreaded ! */ -#if VTY_DEBUG extern int vty_assert_fail ; @@ -155,44 +190,45 @@ VTY_ASSERT_FAILED(void) Inline void VTY_ASSERT_LOCKED(void) { - if ((vty_lock_count == 0) && (qpthreads_enabled)) - VTY_ASSERT_FAILED() ; + if (vty_debug) + if ((vty_lock_count == 0) && (qpthreads_enabled)) + VTY_ASSERT_FAILED() ; } ; Inline void VTY_ASSERT_CLI_THREAD(void) { - if (!vty_is_cli_thread()) - VTY_ASSERT_FAILED() ; + if (vty_debug) + if (!vty_is_cli_thread()) + VTY_ASSERT_FAILED() ; } ; -#else - -#define VTY_ASSERT_LOCKED() -#define VTY_ASSERT_CLI_THREAD() - -#endif +Inline void +VTY_ASSERT_CLI_THREAD_LOCKED(void) +{ + if (vty_debug) + { + VTY_ASSERT_CLI_THREAD() ; + VTY_ASSERT_LOCKED() ; + } ; +} ; /*============================================================================== * Functions in vty.c -- used in any of the family */ +extern void vty_hello (vty vty); + extern int vty_out (vty vty, const char* format, ...) PRINTF_ATTRIBUTE(2, 3); +extern int vty_write(struct vty *vty, const void* buf, int n) ; extern int vty_out_indent(vty vty, int indent) ; extern void vty_out_clear(vty vty) ; +extern cmd_return_code_t vty_cat_file(vty vty, qpath path, const char* desc) ; -extern void vty_set_lines(vty vty, int lines); +extern void vty_time_print (struct vty *, int); -extern bool vty_close(vty vty, bool final, qstring reason) ; +extern void vty_set_lines(vty vty, int lines); extern void vty_open_config_write(vty vty, int fd) ; -extern int vty_close_config_write(vty vty) ; - -extern void vty_log_fixed (const char *buf, size_t len); - -struct logline ; /* forward reference */ -struct zlog ; /* forward reference */ - -extern void uty_log (struct logline* ll, struct zlog *zl, int priority, - const char *format, va_list va); +extern cmd_return_code_t vty_close_config_write(struct vty* vty, bool final) ; #endif /* _ZEBRA_VTY_LOCAL_H */ diff --git a/lib/vty_log.c b/lib/vty_log.c new file mode 100644 index 00000000..edb3291b --- /dev/null +++ b/lib/vty_log.c @@ -0,0 +1,258 @@ +/* VTY interface to logging + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 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. + */ + +#include "misc.h" + +#include "vty_log.h" +#include "vty_local.h" +#include "vty_io_term.h" +#include "log_local.h" +#include "list_util.h" +#include "vio_fifo.h" +#include "mqueue.h" + +/*============================================================================== + * This supports the "vty monitor" facility -- which reflects logging + * information to one or more VTY_TERMINAL vty. + * + * There are a number of issues: + * + * a) output of logging information should not be held up any longer than + * is absolutely necessary. + * + * b) console may be busy doing other things, so logging information needs + * to be buffered. + * + * c) zlog() et al, hold the LOG_LOCK(), which is at a lower level than the + * VTY_LOCK(). + * + * d) may have one or more monitor vty, possibly at different levels of + * message. + * + * e) must avoid logging error messages for given vty on that vty ! + * + * + * + * + */ +static vio_fifo monitor_buffer = NULL ; +static uint monitor_count = 0 ; + +static bool mon_kicked = false ; +static mqueue_block mon_mqb = NULL ; + +static void vty_mon_action(mqueue_block mqb, mqb_flag_t flag) ; + +/*------------------------------------------------------------------------------ + * Initialise the vty monitor facility. + * + */ +extern void +uty_init_monitor(void) +{ + LOG_LOCK() ; + + vio_monitor_list = NULL ; + monitor_buffer = NULL ; + + mon_kicked = false ; + mon_mqb = mqb_init_new(NULL, vty_mon_action, &mon_mqb) ; + + LOG_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * 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) +{ + int level ; + int count ; + + VTY_ASSERT_LOCKED() ; + + level = 0 ; + count = 0 ; + + LOG_LOCK() ; + + if (on && !vio->monitor) + { + if (vio->vty->type == VTY_TERMINAL) + { + vio->monitor = true ; + + vio->maxlvl = INT_MAX ; /* pro tem TODO */ + vio->mon_kick = false ; + + if (vio->mbuf == NULL) + vio->mbuf = vio_fifo_init_new(NULL, 8 * 1024) ; + + sdl_push(vio_monitor_list, vio, mon_list) ; + + count = +1 ; + } ; + } + else if (!on && vio->monitor) + { + vio->monitor = false ; + + vio->maxlvl = INT_MAX ; /* pro tem TODO */ + vio->mon_kick = false ; + + sdl_del(vio_monitor_list, vio, mon_list) ; + count = -1 ; + } + + if (count != 0) + uzlog_add_monitor(NULL, count) ; + + monitor_count += count ; + + LOG_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * Put logging message to all suitable monitors. + * + * All we can do here is to shovel stuff into buffers and then kick the VTY + * to do something. If running multi-nexus, then the kick takes the form of + * a message sent to the cli nexus, otherwise can call the message action + * function here and now. + * + * NB: expect incoming line to NOT include '\n' or any other line ending. + */ +extern void +vty_log(int priority, const char* line, uint len) +{ + vty_io vio ; + bool kick ; + + LOG_ASSERT_LOCKED() ; + + vio = vio_monitor_list ; + kick = false ; + while (vio != NULL) + { + if (priority <= vio->maxlvl) + { + vio_fifo_put_bytes(vio->mbuf, line, len) ; + vio_fifo_put_bytes(vio->mbuf, "\r\n", 2) ; + + vio->mon_kick = kick = true ; + } + else + vio->mon_kick = false ; + + vio = sdl_next(vio, mon_list) ; + } ; + + if (kick) + { + if (vty_multi_nexus) + { + if (!mon_kicked) + { + mon_kicked = true ; + mqueue_enqueue(vty_cli_nexus->queue, mon_mqb, mqb_ordinary) ; + } ; + } + else + vty_mon_action(NULL, mqb_action) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Put logging message to all suitable monitors. + * + * All we can do here is to shovel stuff into buffers and then boot the VTY + * to do something. + * + * NB: expect incoming line to NOT include '\n' or any other line ending. + */ +static void +vty_mon_action(mqueue_block mqb, mqb_flag_t flag) +{ + VTY_LOCK() ; + LOG_LOCK() ; /* IN THIS ORDER !!! */ + + mon_kicked = false ; /* If anything else happens, need to kick again */ + + if (flag == mqb_action) + { + vty_io vio ; + + vio = vio_monitor_list ; + while (vio != NULL) + { + assert(vio->vout_base->vout_type == VOUT_TERM) ; + + if (vio->mon_kick) + { + vio->mon_kick = false ; + uty_term_mon_write(vio->vout_base) ; + } ; + + vio = sdl_next(vio, mon_list) ; + } ; + } + else + mqb_free(mqb) ; /* Suicide */ + + LOG_UNLOCK() ; + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * Async-signal-safe version of vty_log for fixed strings. + * + * This is last gasp operation. + */ +extern void +vty_log_fixed (const char *buf, uint len) +{ + vty_io vio ; + + /* Write to all known "monitor" vty + * + * Forget all the niceties -- about to die in any case. + */ + vio = sdl_head(vio_monitor_list) ; + while (vio != NULL) + { + 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_log.h b/lib/vty_log.h new file mode 100644 index 00000000..4a0614a6 --- /dev/null +++ b/lib/vty_log.h @@ -0,0 +1,41 @@ +/* VTY interface to logging -- header + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 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_VTY_LOG_H +#define _ZEBRA_VTY_LOG_H + +#include "misc.h" +#include "vty_io.h" + +/*============================================================================== + */ + +extern void uty_init_monitor(void) ; +extern void uty_terminate_monitor(void) ; + +extern void uty_set_monitor(vty_io vio, bool on) ; + +extern void vty_log(int priority, const char* line, uint len) ; +extern void vty_log_fixed(const char* buf, uint len) ; + +#endif /* _ZEBRA_VTY_LOG_H */ diff --git a/lib/vty_pipe.c b/lib/vty_pipe.c index 3276085c..ad86e30c 100644 --- a/lib/vty_pipe.c +++ b/lib/vty_pipe.c @@ -188,2681 +188,309 @@ * * */ - -/*============================================================================== - * - */ - #if 0 - - - - - - - - - - - -static char* vty_host_name = NULL ; -int vty_host_name_set = 0 ; - -static void uty_new_host_name(const char* name) ; - -/*------------------------------------------------------------------------------ - * Update vty_host_name as per "hostname" or "no hostname" command - */ -extern void -uty_set_host_name(const char* name) -{ - VTY_ASSERT_LOCKED() ; - - vty_host_name_set = (name != NULL) ; - - if (vty_host_name_set) - uty_new_host_name(name) ; - else - uty_check_host_name() ; -} ; - -/*------------------------------------------------------------------------------ - * If vty_host_name is set, free it. - */ -extern void -uty_free_host_name(void) -{ - if (vty_host_name != NULL) - XFREE (MTYPE_HOST, vty_host_name) ; /* sets vty_host_name = NULL */ -} ; - -/*------------------------------------------------------------------------------ - * If the host name is not set by command, see if the actual host name has - * changed, and if so change it. - * - * This is done periodically in case the actual host name changes ! - */ -extern void -uty_check_host_name(void) -{ - struct utsname names ; - - VTY_ASSERT_LOCKED() ; - - if (vty_host_name_set) - return ; /* nothing to do if set by command */ - - uname (&names) ; - - if ((vty_host_name == NULL) || (strcmp(vty_host_name, names.nodename) != 0)) - uty_new_host_name(names.nodename) ; -} ; - -/*------------------------------------------------------------------------------ - * Set new vty_host_name and run along list of VTYs to mark the change. - */ -static void -uty_new_host_name(const char* name) -{ - vty_io vio ; - - VTY_ASSERT_LOCKED() ; - - uty_free_host_name() ; - vty_host_name = XSTRDUP(MTYPE_HOST, name) ; - - vio = vio_list_base ; - while (vio != NULL) - { - vio->cli_prompt_set = 0 ; - vio = sdl_next(vio, vio_list) ; - } ; -} ; - /*============================================================================== - * General mechanism for command execution. - * - * Command execution is driven by select/pselect -- which means that the - * processing of commands is multiplexed with all other activity. In the - * following: - * - * -- read_ready and write_ready are events signalled by select/pselect - * - * -- setting read or write on, means enabling the file for select/pselect to - * consider it for read_ready or write_ready, respectively. - * - * State of the CLI: - * - * cli_blocked -- the CLI is unable to process any further keystrokes. - * - * cmd_in_progress -- a command has been dispatched and has not yet - * completed (may have been queued). - * - * cmd_out_enabled -- the command FIFO is may be emptied. - * - * This is set when a command completes, and cleared when - * everything is written away. - * - * cli_more_wait -- is in "--more--" wait state - * - * 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 - * CLI has been closed - * - * There are two output FIFOs: - * - * 1. for the CLI itself -- uty_cli_out and friends - * - * 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. - * - * 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 - * invokes read_ready. - * - * Note also that after each command dispatch the CLI processor exits, to be - * re-entered again on write_ready/read_ready -- so does one command line at - * a time, yielding the processor after each one. - * - * Note that select/pselect treat a socket which is at "EOF", or has seen an - * error, or has been half closed, etc. as readable and writable. This means - * that the CLI will continue to move forward even after the socket is no - * longer delivering any data. - * - *------------------------------------------------------------------------------ - * The "--more--" handling. - * - * 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 - * - * 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.) - * - * 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. - * - * 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. - * - *------------------------------------------------------------------------------ - * The qpthreads/qpnexus extension. - * - * When running in qnexus mode, many commands are not executed directly in the - * CLI, but are queued for execution by the main "routeing" nexus. - * - * The parsing of commands is context sensitive. The context depends may - * change during the execution of a command. So... it is not possible to - * dispatch a command until the previous one has completed. - * - * In qnexus mode, when a command is queued, the CLI does not go cli_blocked, - * even if some output has already been generated. This allows a further - * command to be entered while the previous one is executed. However, if the - * command is dispatched before the previous one completes, then the cli will - * block. - * - * While the previous command is executing, the current command line has a - * minimal prompt -- to show that the context is not known. When the previous - * command completes, the command line is redrawn in the new context. - * - *------------------------------------------------------------------------------ - * Command line drawn state. - * - * When the cli_drawn flag is set, the current console line contains the - * current prompt and the user input to date. The cursor is positioned where - * the user last placed it. - * - * The command line can be "wiped" -- see uty_cli_wipe() -- which removes all - * output and prompt, and leaves the console at the start of an empty line - * where the command line used to be. - * - * On entry to the CLI, it will draw the command line again if it has been - * wiped. - * - * This mechanism is used to support the partial command line that may be - * entered while a queued command executes. - * - * It is also used for the command help/completion system. - * - * It is also used to support the "monitor" output. - */ - -/*============================================================================== - * The CLI - */ - -#define CONTROL(X) ((X) - '@') - -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->type == VTY_TERM) ; - - 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 ; -} ; - -/*------------------------------------------------------------------------------ - * Close the CLI - * - * 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->type == VTY_TERM) ; - - 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 - * - * Do nothing at all if half closed. - * - * Otherwise do: standard CLI - * or: "--more--" CLI - * - * 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) -{ - VTY_ASSERT_LOCKED() ; - assert(vio->type == VTY_TERM) ; +#include "misc.h" - if (vio->half_closed) - return not_ready ; /* Nothing more if half closed */ +#include "command_local.h" - /* Standard or "--more--" CLI ? */ - if (vio->cli_more_wait) - return uty_cli_more_wait(vio) ; - else - return uty_cli_standard(vio) ; -} ; +#include "vty_io_pipe.h" +#include "vty_io_file.h" +#include "vty_io_basic.h" /*============================================================================== - * 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) ; - -/*------------------------------------------------------------------------------ - * Standard CLI for VTY_TERM -- if not blocked, runs until: - * - * * runs out of keystrokes - * * executes a command - * - * Note that this executes at most one command each time it is called. This - * is to allow for a modicum of sharing of the system. For real keyboard input - * this will make no difference at all ! - * - * Returns: not_ready blocked and was blocked when entered - * write_ready if there is anything in the keystroke stream - * read_ready otherwise - */ -static enum vty_readiness -uty_cli_standard(vty_io vio) -{ - VTY_ASSERT_LOCKED() ; - assert(vio->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. - * - * 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 there is nothing pending, then can run the CLI until there is - * something to do, or runs out of input. - * - * If there is something to do, that is because a previous command has - * 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) ; - else - uty_cli_draw_this(vio, vio->vty->node) ; - - /* If have something to do, do it. */ - if (vio->cli_do != cli_do_nothing) - { - /* Reflect immediate response */ - uty_cli_response(vio, vio->cli_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) ; - else - vio->cli_blocked = 1 ; - } ; - - /* Use write_ready as a proxy for read_ready on the keystroke stream. - * - * Also, if the command line is not drawn, then return write_ready, so - * that - * - * Note that if has just gone cli_blocked, still returns ready. This is - * defensive: at worst will generate one unnecessary read_ready/write_ready - * event. - */ - if (keystroke_stream_empty(vio->key_stream)) - return read_ready ; - else - return write_ready ; -} ; - -/*------------------------------------------------------------------------------ - * Dispatch the current vio->cli_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 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. - */ -static bool -uty_cli_dispatch(vty_io vio) -{ - qstring_t tmp ; - enum cli_do cli_do ; - enum cmd_return_code ret ; - - struct vty* vty = vio->vty ; - - VTY_ASSERT_LOCKED() ; - assert(!vio->cli_blocked && !vio->cmd_in_progress) ; - - /* Set vio->clx to the command about to execute. - * - * Clear vio->cl and vio->cl_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 ; - - qs_term(&vio->clx) ; /* ensure string is terminated */ - vty->buf = qs_chars(&vio->clx) ; /* terminated command line */ - cli_do = vio->cli_do ; /* current operation */ - - vio->cli_do = cli_do_nothing ; /* clear */ - qs_clear(vio->cl) ; /* set cl empty (with '\0') */ - - /* Reset the command output FIFO and line_control */ - assert(vio_fifo_empty(&vio->cmd_obuf)) ; - uty_out_clear(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) ; - } - else - { - /* All other nodes... */ - switch (cli_do) - { - case cli_do_nothing: - ret = CMD_SUCCESS ; - break ; - - case cli_do_command: - ret = uty_dispatch_command(vty) ; - break ; - - case cli_do_ctrl_c: - ret = uty_stop_input(vty) ; - break ; - - case cli_do_ctrl_d: - ret = uty_down_level(vty) ; - break ; - - case cli_do_ctrl_z: - ret = uty_command(vty) ; - if (ret == CMD_QUEUED) - vio->cli_do = cli_do_ctrl_z ; /* defer the ^Z action */ - else - ret = uty_end_config(vty) ; /* do the ^Z now */ - break ; - - case cli_do_eof: - ret = uty_cmd_close(vio->vty, "End") ; - break ; - - default: - zabort("unknown cli_do_xxx value") ; - } ; - } ; - - if (ret == CMD_QUEUED) - { - uty_cli_draw(vio) ; /* draw the prompt */ - return false ; /* command not complete */ - } - else - { - uty_cli_cmd_complete(vio, ret) ; - return true ; /* command complete */ - } ; -} ; - -/*------------------------------------------------------------------------------ - * 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. - */ -extern void -vty_queued_result(struct vty *vty, enum cmd_return_code ret) -{ - vty_io vio ; - - VTY_LOCK() ; - - vio = vty->vio ; - - if (!vio->closed) - { - uty_cli_wipe(vio, 0) ; /* wipe any partly constructed line */ - - /* 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) ; - - /* 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 the VTY is closed, the only reason it still exists is because - * there was cmd_in_progress. - */ - vio->cmd_in_progress = 0 ; - - uty_close(vio) ; /* Final close */ - } ; - - VTY_UNLOCK() ; -} - -/*------------------------------------------------------------------------------ - * 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 - * * and prepare the line control for output - * - * If the return is CMD_CLOSE, then also now does the required half close. - * - * Note that apart from CMD_CLOSE, don't really care what the return was. Any - * diagnostics or other action must be dealt with elsewhere (as part of the - * command execution. - * - * Note that everything proceeds as if there is some output. So after every - * command goes through at least one write_ready event. + * VTY File Output * - * This ensures some multiplexing at the command level. + * This is for input and output of configuration files and piped stuff. * - * It also means that the decision about whether there is anything to output - * is left to the output code. + * 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 void -uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret) -{ - VTY_ASSERT_LOCKED() ; - - assert(vio->cmd_in_progress && !vio->cmd_out_enabled) ; - if (ret == CMD_CLOSE) - uty_half_close(vio, NULL) ; - vio->cmd_in_progress = 0 ; /* command complete */ - vio->cmd_out_enabled = 1 ; /* enable the output */ - vio->cli_blocked = 1 ; /* now blocked waiting for output */ - - vio->vty->buf = NULL ; /* finished with command line */ - - uty_cmd_output_start(vio) ; /* reset line control (belt & braces) */ -} ; /*============================================================================== - * The "--more--" CLI - * - * While command output is being cleared from its FIFO, the CLI is cli_blocked. - * - * When the output side signals that "--more--" is required, it sets the - * cli_more_wait flag and clears the cmd_out_enabled 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 - * "--more--" response which was typed before the prompt was issued. - * - * The cli_blocked flag indicates that the CLI is in this first stage. - */ - -/*------------------------------------------------------------------------------ - * Change the CLI to the "--more--" CLI. - * - * Outputs the new prompt line. + * Prototypes. */ -extern void -uty_cli_enter_more_wait(vty_io vio) -{ - VTY_ASSERT_LOCKED() ; - - assert(vio->cli_blocked && vio->cmd_out_enabled && !vio->cli_more_wait) ; - - uty_cli_wipe(vio, 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 */ - - uty_cli_draw(vio) ; /* draw the "--more--" */ -} ; /*------------------------------------------------------------------------------ - * Exit the "--more--" CLI. * - * Wipes the "--more--" prompt. * - * This is used when the user responds to the prompt. - * - * It is also used when the vty is "half-closed". In this case, it is (just) - * 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) - * - * * 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) +extern cmd_return_code_t +uty_file_read_open(vty_io vio, qstring name, bool reflect) { - VTY_ASSERT_LOCKED() ; + const char* name_str ; + int fd ; + vio_vf vf ; + vfd_io_type_t iot ; - assert( (!vio->cli_blocked || !vio_fifo_empty(&vio->cli_obuf)) - && !vio->cmd_out_enabled && vio->cli_more_wait) ; + name_str = qs_make_string(name) ; - uty_cli_wipe(vio, 0) ; /* wipe the prompt ('--more--') - before changing the CLI state */ + iot = vfd_io_read | vfd_io_blocking ; /* TODO blocking */ - 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 */ -} ; + /* Do the basic file open. */ + fd = uty_vfd_file_open(name_str, iot) ; -/*------------------------------------------------------------------------------ - * Handle the "--more--" state. - * - * Deals with the first stage if cli_blocked. - * - * Tries to steal a keystroke, and when succeeds wipes the "--more--" - * prompt and exits cli_more_wait -- and may cancel all outstanding output. - * - * EOF on input causes immediate exit from cli_more_state. - * - * Returns: read_ready -- waiting to steal a keystroke - * now_ready -- just left cli_more_wait - * not_ready -- otherwise - */ -static enum vty_readiness -uty_cli_more_wait(vty_io vio) -{ - struct keystroke steal ; - - VTY_ASSERT_LOCKED() ; - - /* Deal with the first stage of "--more--" */ - if (vio->cli_blocked) + if (fd < 0) { - 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 ; - - vio->cli_blocked = 0 ; - - /* empty the input buffer into the keystroke stream */ - do - { - get = uty_read(vio, NULL) ; - } while (get > 0) ; - } ; + uty_out(vio, "%% Could not open input file %s\n", name_str) ; - /* Go through the "--more--" process, unless no longer write_open (!) */ - if (vio->sock.write_open) - { - /* The read fetches a reasonable lump from the I/O -- so if there - * is a complete keystroke available, expect to get it. - * - * If no complete keystroke available to steal, returns ks_null. - * - * If has hit EOF (or error etc), returns knull_eof. - */ - uty_read(vio, &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 ; - else - return read_ready ; - } ; - - /* Stolen a keystroke -- a (very) few terminate all output */ - if (steal.type == ks_char) - { - switch (steal.value) - { - case CONTROL('C'): - case 'q': - case 'Q': - uty_out_clear(vio) ; - break; - - default: /* everything else, thrown away */ - break ; - } ; - } ; - } ; - - /* End of "--more--" process - * - * Wipe out the prompt and update state. - * - * Return write_ready to tidy up the screen and, unless cleared, write - * some more. - */ - uty_cli_exit_more_wait(vio) ; - - return now_ready ; -} ; - -/*============================================================================== - * CLI VTY output - * - * 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. - * - * 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 - * - * The "cli_echo" functions discard the output if vio->cli_echo_suppress != 0. - * This is used while passwords are entered and to allow command line changes - * to be made while the line is not visible. - */ - -enum { cli_rep_count = 32 } ; - -typedef const char cli_rep_char[cli_rep_count] ; - -static const char 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, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 - } ; -CONFIRM(sizeof(telnet_backspaces) == sizeof(cli_rep_char)) ; - -static const char telnet_spaces[] = - { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', - ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', - ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', - ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' - } ; -CONFIRM(sizeof(telnet_spaces) == sizeof(cli_rep_char)) ; + return CMD_WARNING ; + } -static const char* telnet_newline = "\r\n" ; + /* OK -- now push the new input onto the vin_stack. */ + vf = uty_vf_new(vio, name_str, fd, vfd_file, iot) ; + uty_vin_push(vio, vf, VIN_FILE, NULL, NULL, 16 * 1024) ; -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) ; + vf->parse_type = cmd_parse_strict ; + vf->reflect_enabled = reflect ; -/*------------------------------------------------------------------------------ - * CLI VTY output -- cf fprintf() - */ -static void -uty_cli_out(vty_io vio, const char *format, ...) -{ - VTY_ASSERT_LOCKED() ; - - if (vio->sock.write_open) - { - va_list args ; - - va_start (args, format); - vio_fifo_vprintf(&vio->cli_obuf, format, args) ; - va_end(args); - } ; + return CMD_SUCCESS ; } ; /*------------------------------------------------------------------------------ - * CLI VTY output -- echo user input * - * 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) -{ - VTY_ASSERT_LOCKED() ; - - if (vio->cli_echo_suppress || !vio->sock.write_open) - return ; - - uty_cli_write(vio, this, len) ; -} - -/*------------------------------------------------------------------------------ - * CLI VTY output -- echo 'n' characters using a cli_rep_char string * - * Do nothing if echo suppressed (eg in AUTH_NODE) */ -static void -uty_cli_echo_n(vty_io vio, cli_rep_char chars, int n) +extern cmd_return_code_t +uty_file_write_open(vty_io vio, qstring name, bool append) { - VTY_ASSERT_LOCKED() ; + const char* name_str ; + int fd ; + vio_vf vf ; + vfd_io_type_t iot ; - if (vio->cli_echo_suppress || !vio->sock.write_open) - return ; + iot = vfd_io_write | vfd_io_blocking ; /* TODO blocking */ - uty_cli_write_n(vio, chars, n) ; -} + if (append) + iot |= vfd_io_append ; -/*------------------------------------------------------------------------------ - * CLI VTY output -- cf write() - */ -inline static void -uty_cli_write(vty_io vio, const char *this, int len) -{ - VTY_ASSERT_LOCKED() ; + name_str = qs_make_string(name) ; - if (vio->sock.write_open) - vio_fifo_put(&vio->cli_obuf, this, len) ; -} ; + /* Do the basic file open. */ + fd = uty_vfd_file_open(name_str, iot) ; -/*------------------------------------------------------------------------------ - * 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) -{ - int len ; - - len = sizeof(cli_rep_char) ; - while (n > 0) + if (fd < 0) { - if (n < len) - len = n ; - uty_cli_write(vio, chars, len) ; - n -= len ; - } ; -} ; + uty_out(vio, "%% Could not open output file %s\n", name_str) ; -/*------------------------------------------------------------------------------ - * CLI VTY output -- write given string - * - * Returns length of string. - */ -static int -uty_cli_write_s(vty_io vio, const char *str) -{ - int len ; + return CMD_WARNING ; + } - len = strlen(str) ; - if (len != 0) - uty_cli_write(vio, str, len) ; + /* OK -- now push the new input onto the vin_stack. */ + vf = uty_vf_new(vio, name_str, fd, vfd_file, iot) ; + uty_vout_push(vio, vf, VOUT_FILE, NULL, NULL, 16 * 1024) ; - return len ; + return CMD_SUCCESS ; } ; /*============================================================================== - * Standard Messages - */ - -/*------------------------------------------------------------------------------ - * Send newline to the console. + * Command line fetch from a file or pipe. * - * Clears the cli_drawn and the cli_dirty flags. + * 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_cli_out_newline(vty_io vio) +extern cmd_return_code_t +uty_file_fetch_command_line(vio_vf vf, qstring* line) { - uty_cli_write(vio, telnet_newline, 2) ; - vio->cli_drawn = 0 ; - vio->cli_dirty = 0 ; -} ; + assert(vf->vin_state == vf_open) ; -/*------------------------------------------------------------------------------ - * Wipe 'n' characters. - * - * If 'n' < 0, wipes characters backwards and moves cursor back. - * 'n' > 0, wipes characters forwards, leaving cursor where it is - */ -static void -uty_cli_out_wipe_n(vty_io vio, int n) -{ - if (n < 0) + if (vf->line_complete) { - n = abs(n) ; - uty_cli_write_n(vio, telnet_backspaces, n); - } ; - - if (n > 0) - { - uty_cli_write_n(vio, telnet_spaces, n) ; - uty_cli_write_n(vio, telnet_backspaces, n) ; - } ; -} ; - -/*------------------------------------------------------------------------------ - * Send response to the given cli_do - * - * If no command is in progress, then will send newline to signal that the - * command is about to be dispatched. - * - * 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] = -{ - { /* 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] = "^*" - }, - { /* 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] = "^*" - } -} ; - -static void -uty_cli_response(vty_io vio, enum cli_do cli_do) -{ - const char* str ; - int len ; - - if ((cli_do == cli_do_nothing) || (vio->half_closed)) - return ; + vio_fifo_set_hold_mark(vf->ibuf) ; /* advance hold */ - str = (cli_do < cli_do_count) - ? cli_response[vio->cmd_in_progress ? 1 : 0][cli_do] : NULL ; - assert(str != NULL) ; + vf->line_complete = false ; + vf->line_number += vf->line_step ; - len = uty_cli_write_s(vio, str) ; - - if (vio->cmd_in_progress) - { - vio->cli_extra_len = len ; - uty_cli_write_n(vio, telnet_backspaces, len) ; - } - else - { - uty_cli_out_newline(vio) ; + qs_set_len_nn(vf->cl, 0) ; + vf->line_step = 0 ; } ; -} ; - -/*------------------------------------------------------------------------------ - * Send various messages with trailing newline. - */ -static void -uty_cli_out_CMD_ERR_AMBIGUOUS(vty_io vio) -{ - uty_cli_write_s(vio, "% " MSG_CMD_ERR_AMBIGUOUS ".") ; - uty_cli_out_newline(vio) ; -} ; - -static void -uty_cli_out_CMD_ERR_NO_MATCH(vty_io vio) -{ - uty_cli_write_s(vio, "% " MSG_CMD_ERR_NO_MATCH ".") ; - uty_cli_out_newline(vio) ; -} ; - -/*============================================================================== - * Command line draw and wipe - */ - -/*------------------------------------------------------------------------------ - * Wipe the current console line -- if any. - */ -static void -uty_cli_wipe(vty_io vio, int len) -{ - int a ; - int b ; - - if (!vio->cli_drawn) - return ; /* quit if already wiped */ - - assert(vio->cl.cp <= vio->cl.len) ; - - /* Establish how much ahead and how much behind the cursor */ - a = vio->cli_extra_len ; - b = vio->cli_prompt_len ; - - if (!vio->cli_echo_suppress && !vio->cli_more_wait) - { - a += vio->cl.len - vio->cl.cp ; - b += vio->cl.cp ; - } ; - - /* Wipe anything ahead of the current position and ahead of new len */ - if ((a + b) > len) - uty_cli_out_wipe_n(vio, +a) ; - - /* Wipe anything behind current position, but ahead of new len */ - if (b > len) + while (1) { - uty_cli_out_wipe_n(vio, -(b - len)) ; - b = len ; /* moved the cursor back */ - } ; + char* s, * p, * q, * e ; + size_t have ; + ulen len ; - /* Back to the beginning of the line */ - uty_cli_write_n(vio, telnet_backspaces, b) ; + s = vio_fifo_get(vf->ibuf, &have) ; - /* Nothing there any more */ - vio->cli_drawn = 0 ; - vio->cli_dirty = 0 ; -} ; + /* If nothing in hand, try and get some more. + * + * Either exits or loops back to set s & have. + */ + if (have == 0) + { + int get ; -/*------------------------------------------------------------------------------ - * If not currently drawn, draw prompt etc according to the current state - * and node. - * - * See uty_cli_draw(). - */ -extern bool -uty_cli_draw_if_required(vty_io vio) -{ - if (vio->cli_drawn) - return false ; + get = vio_fifo_read_nb(vf->ibuf, vio_vfd_fd(vf->vfd), 100) ; - uty_cli_draw(vio) ; + if (get > 0) + continue ; /* loop back */ - return true ; -} ; + if (get == 0) + return CMD_WAITING ; /* need to set read ready ! */ -/*------------------------------------------------------------------------------ - * Draw prompt etc for the current vty node. - * - * See uty_cli_draw_this() - */ -static void -uty_cli_draw(vty_io vio) -{ - uty_cli_draw_this(vio, vio->vty->node) ; -} ; + if (get == -1) + ; /* register error */ -/*------------------------------------------------------------------------------ - * Draw prompt and entire command line, leaving current position where it - * should be. - * - * If command line is currently drawn, this wipes and redraws. - * - * Otherwise, assumes is positioned at start of an empty line. - * - * Draws prompt according to the given 'node', except: - * - * * if is half_closed, draw nothing -- wipes the current line - * - * * if is cli_more_wait, draw the "--more--" prompt - * - * * if is cmd_in_progress, draw the vestigial prompt. - * - * By the time the current command completes, the node may have changed, so - * the current prompt may be invalid. - * - * Sets: cli_drawn = true - * cli_dirty = false - * cli_prompt_len = length of prompt used - * cli_extra_len = 0 - * cli_echo_suppress = (AUTH_NODE or AUTH_ENABLE_NODE) - */ -static void -uty_cli_draw_this(vty_io vio, 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 (get == -2) + return (qs_len_nn(vf->cl) > 0) ? CMD_SUCCESS : CMD_EOF ; + } ; - /* Sort out what the prompt is. */ - if (vio->half_closed) - { - prompt = "" ; - p_len = 0 ; - l_len = 0 ; - } - else if (vio->cli_more_wait) - { - prompt = "--more--" ; - p_len = strlen(prompt) ; - l_len = 0 ; - } - else if (vio->cmd_in_progress) - { - /* If there is a queued command, the prompt is a minimal affair. */ - prompt = "~ " ; - p_len = strlen(prompt) ; - l_len = vio->cl.len ; - } - else - { - /* The prompt depends on the node, and is expected to include the - * host name. + /* Try to find a '\n' -- converting all other control chars to ' ' * - * Caches the prompt so doesn't re-evaluate it every time. + * When we find '\n' step back across any trailing ' ' (which includes + * any control chars before the '\n'). * - * If the host name changes, the cli_prompt_set flag is cleared. + * This means that we cope with "\r\n" line terminators. But not anything + * more exotic. */ - if (!vio->cli_prompt_set || (node != vio->cli_prompt_node)) - { - const char* prompt ; - - if (vty_host_name == NULL) - uty_check_host_name() ; /* should never be required */ + p = s ; + e = s + have ; /* have != 0 */ + q = NULL ; - prompt = cmd_prompt(node) ; - if (prompt == NULL) + while (p < e) + { + if (*p++ < 0x20) { - zlog_err("vty %s has node %d", uty_get_name(vio), node) ; - prompt = "%s ???: " ; - } ; + if (*(p-1) != '\n') + { + *(p-1) = ' ' ; /* everything other than '\n' */ + continue ; + } ; - qs_printf(vio->cli_prompt_for_node, prompt, vty_host_name); + ++vf->line_step ; /* got a '\n' */ - vio->cli_prompt_node = node ; - vio->cli_prompt_set = 1 ; + q = p ; /* point just past '\n' */ + do --q ; while ((q > s) && (*(q-1) == ' ')) ; + /* discard trailing "spaces" */ + break ; + } ; } ; - prompt = vio->cli_prompt_for_node.body ; - p_len = vio->cli_prompt_for_node.len ; - l_len = vio->cl.len ; - } ; - - /* Now, if line is currently drawn, time to wipe it */ - if (vio->cli_drawn) - uty_cli_wipe(vio, 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) ; - - vio->cli_prompt_len = p_len ; - - uty_cli_write(vio, 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) ; - } ; -} ; - -/*============================================================================== - * Monitor output. - * - * To prepare for monitor output, wipe as much as is necessary for the - * monitor line to appear correctly. - * - * After monitor output, may need to do two things: - * - * * if the output was incomplete, place the rump in the CLI buffer, - * so that: - * - * a. don't mess up the console with partial lines - * - * b. don't lose part of a message - * - * c. act as a brake on further monitor output -- cannot do any more - * until the last, part, line is dealt with. - * - * * restore the command line, unless it is empty ! - */ - - /*----------------------------------------------------------------------------- - * Prepare for new monitor output line. - * - * Wipe any existing command line. - */ -extern void -uty_cli_pre_monitor(vty_io vio, size_t len) -{ - VTY_ASSERT_LOCKED() ; - - uty_cli_wipe(vio, len) ; -} ; - -/*------------------------------------------------------------------------------ - * Recover from monitor line output. - * - * If monitor line failed to complete, append the rump to the CLI buffer. - * - * If have a non-empty command line, or is cli_more_wait, redraw the command - * line. - * - * Returns: 0 => rump was empty and no command line stuff written - * > 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) -{ - VTY_ASSERT_LOCKED() ; - - if (len != 0) - uty_cli_write(vio, buf, len) ; - - if (vio->cli_more_wait || (vio->cl.len != 0)) - { - uty_cli_draw(vio) ; - ++len ; - } ; - - return 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) ; - -/*------------------------------------------------------------------------------ - * Fetch next keystroke, reading from the file if required. - */ -static inline bool -uty_cli_get_keystroke(vty_io vio, keystroke stroke) -{ - if (keystroke_get(vio->key_stream, stroke)) - return 1 ; - - uty_read(vio, NULL) ; /* not stealing */ - - return keystroke_get(vio->key_stream, stroke) ; -} ; - -/*------------------------------------------------------------------------------ - * Process keystrokes until run out of input, or get something to cli_do. - * - * If required, draw the prompt and command line. - * - * Process keystrokes until run out of stuff to do, or have a "command line" - * that must now be executed. - * - * 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 - * - * When returns the cl is '\0' terminated. - */ -static enum cli_do -uty_cli_process(vty_io vio, enum node_type node) -{ - struct keystroke stroke ; - uint8_t u ; - int auth ; - enum cli_do ret ; - - auth = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ; - - /* Now process as much as possible of what there is */ - ret = cli_do_nothing ; - while (1) - { - if (!vio->cli_drawn) - uty_cli_draw_this(vio, node) ; - - if (!uty_cli_get_keystroke(vio, &stroke)) - { - ret = (stroke.value == knull_eof) ? cli_do_eof : cli_do_nothing ; - 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) ; - if (stroke.flags != 0) + /* If not found '\n', then we have a line fragment that needs to be + * appended to any previous line fragments. + * + * Loops back to try to get some more form the fifo. + */ + if (q == NULL) { - /* TODO: deal with broken keystrokes */ + qs_append_str_n(vf->cl, s, p - s) ; continue ; } ; - switch (stroke.type) - { - /* Straightforward character -----------------------------------*/ - /* Note: only interested in 8-bit characters ! */ - case ks_char: - u = (uint8_t)stroke.value ; - - switch (stroke.value) - { - case CONTROL('A'): - uty_cli_bol (vio); - break; - - case CONTROL('B'): - uty_cli_backwards(vio, 1); - break; - - case CONTROL('C'): - ret = cli_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 ..................*/ - else - uty_cli_del_forwards(vio, 1); - break; - - case CONTROL('E'): - uty_cli_eol (vio); - break; - - case CONTROL('F'): - uty_cli_forwards(vio, 1); - break; - - case CONTROL('H'): - case 0x7f: - uty_cli_del_backwards(vio, 1); - break; - - case CONTROL('K'): - uty_cli_del_to_eol (vio); - break; - - case CONTROL('N'): - uty_cli_next_line (vio); - break; - - case CONTROL('P'): - uty_cli_previous_line (vio); - break; - - case CONTROL('T'): - uty_cli_transpose_chars (vio); - break; - - case CONTROL('U'): - uty_cli_clear_line(vio); - break; - - case CONTROL('W'): - uty_cli_del_word_backwards (vio); - break; - - case CONTROL('Z'): - ret = cli_do_ctrl_z ; /* Exit on ^Z ..................*/ - break; - - case '\n': - case '\r': - ret = cli_do_command ; /* Exit on CR or LF.............*/ - break ; - - case '\t': - if (auth) - break ; - else - uty_cli_complete_command (vio, node); - break; - - case '?': - if (auth) - uty_cli_insert (vio, (char*)&u, 1); - else - uty_cli_describe_command (vio, node); - break; - - default: - if ((stroke.value >= 0x20) && (stroke.value < 0x7F)) - uty_cli_insert (vio, (char*)&u, 1) ; - break; - } - break ; - - /* ESC X -------------------------------------------------------------*/ - case ks_esc: - switch (stroke.value) - { - case 'b': - uty_cli_word_backwards (vio); - break; - - case 'f': - uty_cli_word_forwards (vio); - break; - - case 'd': - uty_cli_del_word_forwards (vio); - break; - - case CONTROL('H'): - case 0x7f: - uty_cli_del_word_backwards (vio); - break; - - default: - break; - } ; - break ; - - /* ESC [ X -----------------------------------------------------------*/ - case ks_csi: - if (stroke.len != 0) - break ; /* only recognise 3 byte sequences */ - - switch (stroke.value) - { - case ('A'): - uty_cli_previous_line (vio); - break; - - case ('B'): - uty_cli_next_line (vio); - break; - - case ('C'): - uty_cli_forwards(vio, 1); - break; - - case ('D'): - uty_cli_backwards(vio, 1); - break; - default: - break ; - } ; - break ; - - /* Telnet Command ----------------------------------------------------*/ - case ks_iac: - uty_telnet_command(vio, &stroke, false) ; - break ; - - /* Unknown -----------------------------------------------------------*/ - default: - zabort("unknown keystroke type") ; - } ; - - /* After each keystroke..... */ - - if (ret != cli_do_nothing) + /* 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. + */ + len = q - s ; /* length to add */ + if (qs_len_nn(vf->cl) == 0) { - uty_cli_eol (vio) ; /* go to the end of the line */ - break ; /* stop processing */ - } ; - } ; - - /* Tidy up and return where got to. */ - - qs_term(&vio->cl) ; /* add '\0' */ - - return ret ; -} ; - -/*============================================================================== - * Command line operations - */ - -/*------------------------------------------------------------------------------ - * Insert 'n' characters at current position in the command line - * - * Returns number of characters inserted -- ie 'n' - */ -static int -uty_cli_insert (vty_io vio, const char* chars, int n) -{ - int after ; - - VTY_ASSERT_LOCKED() ; - - assert((vio->cl.cp <= vio->cl.len) && (n >= 0)) ; - - if (n <= 0) - return n ; /* avoid trouble */ - - after = qs_insert(vio->cl, chars, n) ; - - uty_cli_echo(vio, qs_cp_char(vio->cl), after) ; - - if ((after - n) != 0) - uty_cli_echo_n(vio, telnet_backspaces, after - n) ; - - vio->cl.cp += n ; - - return n ; -} ; - -/*------------------------------------------------------------------------------ - * Overstrike 'n' characters at current position in the command line - * - * Move current position forwards. - * - * Returns number of characters inserted -- ie 'n' - */ -static int -uty_cli_overwrite (vty_io vio, char* chars, int n) -{ - VTY_ASSERT_LOCKED() ; - - assert((vio->cl.cp <= vio->cl.len) && (n >= 0)) ; - - if (n > 0) - { - qs_replace(vio->cl, n, chars, n) ; - uty_cli_echo(vio, chars, n) ; - - vio->cl.cp += n ; - } ; - - return n ; -} - -/*------------------------------------------------------------------------------ - * Insert a word into vty interface with overwrite mode. - * - * NB: Assumes result will then be the end of the line. - * - * Returns number of characters inserted -- ie length of string - */ -static int -uty_cli_word_overwrite (vty_io vio, char *str) -{ - int n ; - VTY_ASSERT_LOCKED() ; - - n = uty_cli_overwrite(vio, str, strlen(str)) ; - - vio->cl.len = vio->cl.cp ; - - return n ; -} + qs_set_alias_n(vf->cl, s, len) ; + p = q ; + } + else + { + if (len != 0) + qs_append_str_n(vf->cl, s, len) ; -/*------------------------------------------------------------------------------ - * Forward 'n' characters -- stop at end of line. - * - * Returns number of characters actually moved - */ -static int -uty_cli_forwards(vty_io vio, int n) -{ - int have ; - VTY_ASSERT_LOCKED() ; + s = qs_char_nn(vf->cl) ; + p = s + qs_len_nn(vf->cl) ; - have = vio->cl.len - vio->cl.cp ; - if (have < n) - n = have ; + 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) == ' ')) ; - assert(n >= 0) ; + qs_set_len_nn(vf->cl, p - s) ; + } ; + } ; - if (n > 0) - { - uty_cli_echo(vio, qs_cp_char(vio->cl), n) ; - vio->cl.cp += n ; - } ; + /* Now worry about we have a trailing '\'. */ - return n ; -} + if ((p == s) || (*(p-1) != '\\')) + break ; /* no \ => no continuation => success */ -/*------------------------------------------------------------------------------ - * Backwards 'n' characters -- stop at start of line. - * - * Returns number of characters actually moved - */ -static int -uty_cli_backwards(vty_io vio, int n) -{ - VTY_ASSERT_LOCKED() ; + /* 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) == '\\')) ; - if ((int)vio->cl.cp < n) - n = vio->cl.cp ; + if (((p - q) & 1) == 0) + break ; /* even => no continuation => success */ - assert(n >= 0) ; + qs_set_len_nn(vf->cl, p - s - 1) ; /* strip odd '\' */ - if (n > 0) - { - uty_cli_echo_n(vio, telnet_backspaces, n) ; - vio->cl.cp -= n ; + continue ; /* loop back to fetch more */ } ; - return n ; -} - -/*------------------------------------------------------------------------------ - * Delete 'n' characters -- forwards -- stop at end of line. - * - * Returns number of characters actually deleted. - */ -static int -uty_cli_del_forwards(vty_io vio, int n) -{ - int after ; - int have ; - - VTY_ASSERT_LOCKED() ; + /* Success have a line in hand */ - have = vio->cl.len - vio->cl.cp ; - if (have < n) - n = have ; /* cannot delete more than have */ + vf->line_complete = true ; + *line = vf->cl ; - assert(n >= 0) ; - - if (n <= 0) - return 0 ; - - after = qs_delete(vio->cl, n) ; - - if (after > 0) - 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) ; - - return n ; -} - -/*------------------------------------------------------------------------------ - * Delete 'n' characters before the point. - * - * Returns number of characters actually deleted. - */ -static int -uty_cli_del_backwards(vty_io vio, int n) -{ - return uty_cli_del_forwards(vio, uty_cli_backwards(vio, n)) ; -} - -/*------------------------------------------------------------------------------ - * Move to the beginning of the line. - * - * Returns number of characters moved over. - */ -static int -uty_cli_bol (vty_io vio) -{ - return uty_cli_backwards(vio, vio->cl.cp) ; + return CMD_SUCCESS ; } ; /*------------------------------------------------------------------------------ - * Move to the end of the line. + * Command output push to a file or pipe. * - * Returns number of characters moved over + * Returns: CMD_SUCCESS -- done + * CMD_IO_ERROR -- ran into an I/O error */ -static int -uty_cli_eol (vty_io vio) +extern cmd_return_code_t +uty_file_out_push(vio_vf vf) { - return uty_cli_forwards(vio, vio->cl.len - vio->cl.cp) ; -} ; + assert(vf->vout_state == vf_open) ; -/*------------------------------------------------------------------------------ - * Forward word delta -- distance to start of next word. - * - * Return number of characters to step over to reach next word. - * - * Steps over non-space characters and then any spaces. - */ -static int -uty_cli_word_forwards_delta(vty_io vio) -{ - char* cp ; - char* tp ; - char* ep ; - - VTY_ASSERT_LOCKED() ; ; - - assert(vio->cl.cp <= vio->cl.len) ; - - cp = qs_cp_char(vio->cl) ; - ep = qs_ep_char(vio->cl) ; - - tp = cp ; - - while ((tp < ep) && (*tp != ' ')) - ++tp ; - - while ((tp < ep) && (*tp == ' ')) - ++tp ; - - return tp - cp ; -} ; - -/*------------------------------------------------------------------------------ - * Forward word -- move to start of next word. - * - * Moves past any non-spaces, then past any spaces. - */ -static int -uty_cli_word_forwards(vty_io vio) -{ - return uty_cli_forwards(vio, uty_cli_word_forwards_delta(vio)) ; -} ; - -/*------------------------------------------------------------------------------ - * Backward word delta -- distance to start of next word, back. - * - * Return number of characters to step over to reach next word. - * - * If "eat_spaces", starts by stepping over spaces. - * 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) -{ - char* cp ; - char* tp ; - char* sp ; - - VTY_ASSERT_LOCKED() ; ; - - assert(vio->cl.cp <= vio->cl.len) ; - - cp = qs_cp_char(vio->cl) ; - sp = qs_chars(vio->cl) ; - - tp = cp ; - - if (eat_spaces) - while ((tp > sp) && (*(tp - 1) == ' ')) - --tp ; - - while ((tp > sp) && (*(tp - 1) != ' ')) - --tp ; - - return cp - tp ; -} ; - -/*------------------------------------------------------------------------------ - * Backward word, but not trailing spaces. - * - * Move back until next (backwards) character is space or start of line. - * - * Returns number of characters stepped over. - */ -static int -uty_cli_word_backwards_pure (vty_io vio) -{ - return uty_cli_backwards(vio, uty_cli_word_backwards_delta(vio, 0)) ; -} ; - -/*------------------------------------------------------------------------------ - * Backward word -- move to start of previous word. - * - * Moves past any spaces, then move back until next (backwards) character is - * space or start of line. - * - * Returns number of characters stepped over. - */ -static int -uty_cli_word_backwards (vty_io vio) -{ - return uty_cli_backwards(vio, uty_cli_word_backwards_delta(vio, 1)) ; -} ; - -/*------------------------------------------------------------------------------ - * Delete to end of word -- forwards. - * - * Deletes any leading spaces, then deletes upto next space or end of line. - * - * Returns number of characters deleted. - */ -static int -uty_cli_del_word_forwards(vty_io vio) -{ - return uty_cli_del_forwards(vio, uty_cli_word_forwards_delta(vio)) ; -} - -/*------------------------------------------------------------------------------ - * Delete to start of word -- backwards. - * - * Deletes any trailing spaces, then deletes upto next space or start of line. - * - * Returns number of characters deleted. - */ -static int -uty_cli_del_word_backwards(vty_io vio) -{ - return uty_cli_del_backwards(vio, uty_cli_word_backwards_delta(vio, 1)) ; -} ; - -/*------------------------------------------------------------------------------ - * Kill rest of line from current point. - * - * Returns number of characters deleted. - */ -static int -uty_cli_del_to_eol (vty_io vio) -{ - return uty_cli_del_forwards(vio, vio->cl.len - vio->cl.cp) ; -} ; - -/*------------------------------------------------------------------------------ - * Kill line from the beginning. - * - * Returns number of characters deleted. - */ -static int -uty_cli_clear_line(vty_io vio) -{ - uty_cli_bol(vio) ; - return uty_cli_del_to_eol(vio) ; -} ; - -/*------------------------------------------------------------------------------ - * Transpose chars before or at the point. - * - * Return number of characters affected. - */ -static int -uty_cli_transpose_chars(vty_io vio) -{ - char chars[2] ; - char* cp ; - - VTY_ASSERT_LOCKED() ; - - /* Give up if < 2 characters or at start of line. */ - if ((vio->cl.len < 2) || (vio->cl.cp < 1)) - return 0 ; - - /* Move back to first of characters to exchange */ - if (vio->cl.cp == vio->cl.len) - uty_cli_backwards(vio, 2) ; + if (vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), false) >= 0) + return CMD_SUCCESS ; else - uty_cli_backwards(vio, 1) ; - - /* Pick up in the new order */ - cp = qs_cp_char(vio->cl) ; - chars[1] = *cp++ ; - chars[0] = *cp ; - - /* And overwrite */ - return uty_cli_overwrite(vio, chars, 2) ; + return CMD_IO_ERROR ; } ; -/*============================================================================== - * Command line history handling - */ - /*------------------------------------------------------------------------------ - * Add given command line to the history buffer. - * - * This is inserting the vty->buf line into the history. + * Tidy up after input file has been closed */ extern void -uty_cli_hist_add (vty_io vio, const char* cmd_line) -{ - qstring prev_line ; - qstring_t line ; - int prev_index ; - - 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) ; - - /* find the previous command line in the history */ - prev_index = vio->hindex - 1 ; - if (prev_index < 0) - prev_index = VTY_MAXHIST - 1 ; - - prev_line = vector_get_item(vio->hist, prev_index) ; - - /* If the previous line is NULL, that means the history is empty. - * - * If the previous line is essentially the same as the current 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 ! - */ - 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)) ; - - /* Advance to the near future and reset the history pointer */ - vio->hindex++; - if (vio->hindex == VTY_MAXHIST) - vio->hindex = 0; - - vio->hp = vio->hindex; -} ; - -/*------------------------------------------------------------------------------ - * Replace command line by current history. - * - * This function is called from vty_next_line and vty_previous_line. - * - * Step +1 is towards the present - * -1 is into the past - */ -static void -uty_cli_history_use(vty_io vio, int step) +uty_file_read_close(vio_vf vf) { - int index ; - unsigned old_len ; - unsigned after ; - unsigned back ; - qstring hist ; - - VTY_ASSERT_LOCKED() ; - - assert((step == +1) || (step == -1)) ; - - index = vio->hp ; - - /* Special case of being at the insertion point */ - if (index == vio->hindex) - { - 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. - */ - hist = vector_get_item(&vio->hist, vio->hindex) ; - vector_set_item(vio->hist, vio->hindex, qs_copy(hist, vio->cl)) ; - } ; - - /* Advance or retreat */ - index += step ; - if (index < 0) - index = VTY_MAXHIST - 1 ; - else if (index >= VTY_MAXHIST) - index = 0 ; - - hist = vector_get_item(vio->hist, index) ; - - /* If moving backwards in time, may not move back to the insertion - * 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 (step < 0) - if ((hist == NULL) || (index == vio->hindex)) - return ; - - /* Now, if arrived at the insertion point, this is returning to the - * present, which is fine. - */ - vio->hp = index; - - /* Move back to the start of the current line */ - uty_cli_bol(vio) ; - - /* Get previous line from history buffer and echo that */ - old_len = vio->cl.len ; - qs_copy(vio->cl, hist) ; - - /* Sort out wiping out any excess and setting the cursor position */ - if (old_len > vio->cl.len) - after = old_len - vio->cl.len ; - else - after = 0 ; - - back = after ; - if (vio->cl.len > vio->cl.cp) - back += (vio->cl.len - vio->cl.cp) ; - - if (vio->cl.len > 0) - uty_cli_echo(vio, vio->cl.body, vio->cl.len) ; - - if (after > 0) - uty_cli_echo_n(vio, telnet_spaces, after) ; - - if (back > 0) - uty_cli_echo_n(vio, telnet_backspaces, back) ; - return ; } ; /*------------------------------------------------------------------------------ - * Use next history line, if any. - */ -static void -uty_cli_next_line(vty_io vio) -{ - uty_cli_history_use(vio, +1) ; -} - -/*------------------------------------------------------------------------------ - * Use previous history line, if any. - */ -static void -uty_cli_previous_line (vty_io vio) -{ - uty_cli_history_use(vio, -1) ; -} - -/*============================================================================== - * Command Completion and Command Description + * Flush output buffer and close. * - */ -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 - */ -static void -uty_cli_complete_command (vty_io vio, enum node_type node) -{ - unsigned i ; - int ret ; - int len ; - int n ; - vector matched ; - vector vline ; - - 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); - - /* Show the result. */ - switch (ret) - { - 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 ; - } ; - - n = vio->width ; - if (n == 0) - n = 60 ; - n = n / (len + 2) ; - if (n == 0) - n = 1 ; - - 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) ; - - break; - - case CMD_COMPLETE_ALREADY: - default: - break; - } ; - - cmd_free_strvec(matched); -} ; - -/*------------------------------------------------------------------------------ - * Command Description - */ -static void -uty_cli_describe_command (vty_io vio, enum node_type node) -{ - int ret; - vector vline; - vector describe; - - VTY_ASSERT_LOCKED() ; - - /* Try and match the tokenised command line */ - vline = uty_cli_cmd_prepare(vio, 1) ; - describe = cmd_describe_command (vline, node, &ret); - cmd_free_strvec (vline); - - 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 ; - - case CMD_ERR_NO_MATCH: - uty_cli_out_CMD_ERR_NO_MATCH(vio) ; - break ; - - default: - uty_cli_describe_show(vio, describe) ; - break ; - } ; - - if (describe != NULL) - vector_free (describe); -} - -/*------------------------------------------------------------------------------ - * 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: - * - * word description .................................. - * .............text - * - * If one of the options is '<cr>', that is always shown last. - */ -static void -uty_cli_describe_show(vty_io vio, vector describe) -{ - unsigned int i, cmd_width, desc_width; - struct desc *desc, *cr_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; - - if (desc->cmd[0] == '\0') - continue; - - len = strlen (desc->cmd); - if (desc->cmd[0] == '.') - len--; - - if (cmd_width < len) - cmd_width = len; - } - - /* Set width of description string. */ - desc_width = vio->width - (cmd_width + 6); - - /* Print out description. */ - cr_item = NULL ; /* put <cr> last if it appears */ - - for (i = 0; i < vector_active (describe); i++) - if ((desc = vector_slot (describe, i)) != NULL) - { - if (desc->cmd[0] == '\0') - continue; - - if (strcmp (desc->cmd, cr_string) == 0) - { - cr_item = desc; - continue; - } - - uty_cli_describe_fold (vio, cmd_width, desc_width, desc); - } - - if (cr_item != NULL) - uty_cli_describe_fold (vio, cmd_width, desc_width, cr_item); -} ; - -/*------------------------------------------------------------------------------ - * Show one word and the description, folding the description as required. - */ -static void -uty_cli_describe_fold (vty_io vio, int cmd_width, - unsigned int desc_width, struct desc *desc) -{ - char *buf; - const char *cmd, *p; - int pos; - - VTY_ASSERT_LOCKED() ; - - cmd = desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd; - p = desc->str ; - - /* If have a sensible description width */ - if (desc_width > 20) - { - buf = XCALLOC (MTYPE_TMP, strlen (desc->str) + 1); - - while (strlen (p) > desc_width) - { - /* move back to first space */ - for (pos = desc_width; pos > 0; pos--) - if (*(p + pos) == ' ') - break; - - /* if did not find a space, break at width */ - if (pos == 0) - pos = desc_width ; - - strncpy (buf, p, pos); - buf[pos] = '\0'; - uty_cli_describe_line(vio, cmd_width, cmd, buf) ; - - cmd = ""; /* for 2nd and subsequent lines */ - - p += pos ; /* step past what just wrote */ - while (*p == ' ') - ++p ; /* skip spaces */ - } ; - - XFREE (MTYPE_TMP, buf); - } ; - - uty_cli_describe_line(vio, cmd_width, cmd, p) ; -} ; - -/*------------------------------------------------------------------------------ - * 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) ; -} ; - -/*------------------------------------------------------------------------------ - * 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) -{ - if (str != NULL) - uty_cli_out(vio, "%s ", str) ; - else - uty_cli_out(vio, "%d ", (int)u) ; -} ; - -/*------------------------------------------------------------------------------ - * For debug. Put string or value as hex. - */ -static void -uty_cli_out_hex(vty_io vio, const char* str, unsigned char u) -{ - if (str != NULL) - uty_cli_out(vio, "%s ", str) ; - else - uty_cli_out(vio, "0x%02x ", (unsigned)u) ; -} ; - -/*------------------------------------------------------------------------------ - * Send telnet: "WILL TELOPT_ECHO" - */ -static void -uty_will_echo (vty_io vio) -{ - unsigned char cmd[] = { tn_IAC, tn_WILL, to_ECHO }; - VTY_ASSERT_LOCKED() ; - uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); -} - -/*------------------------------------------------------------------------------ - * 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)); -} - -/*------------------------------------------------------------------------------ - * 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)); -} - -/*------------------------------------------------------------------------------ - * 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)); -} - -/*------------------------------------------------------------------------------ - * 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)); -} - -/*------------------------------------------------------------------------------ - * 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. + * Returns: true <=> buffer (now) empty */ extern bool -uty_cli_iac_callback(keystroke_iac_callback_args) +uty_file_write_close(vio_vf vf, bool final) { - return uty_telnet_command((vty_io)context, stroke, true) ; + return vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), true) == 0 ; } ; - -/*------------------------------------------------------------------------------ - * 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. - */ -static bool -uty_telnet_command(vty_io vio, keystroke stroke, bool callback) -{ - uint8_t* p ; - uint8_t o ; - int left ; - bool dealt_with ; - - /* Echo to the other end if required */ - if (TELNET_OPTION_DEBUG) - { - uty_cli_wipe(vio, 0) ; - - p = stroke->buf ; - left = stroke->len ; - - uty_cli_out_hex(vio, telnet_commands[tn_IAC], tn_IAC) ; - - if (left-- > 0) - uty_cli_out_dec(vio, telnet_commands[*p], *p) ; - ++p ; - - if (left-- > 0) - uty_cli_out_dec(vio, telnet_options[*p], *p) ; - ++p ; - - if (left > 0) - { - while(left-- > 0) - uty_cli_out_hex(vio, NULL, *p++) ; - - if (stroke->flags & kf_truncated) - uty_cli_out(vio, "... ") ; - - if (!(stroke->flags & kf_broken)) - { - uty_cli_out_hex(vio, telnet_commands[tn_IAC], tn_IAC) ; - uty_cli_out_hex(vio, telnet_commands[tn_SE], tn_SE) ; - } - } ; - - if (stroke->flags & kf_broken) - uty_cli_out (vio, "BROKEN") ; - - uty_cli_out (vio, "\r\n") ; - } ; - - /* 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 - { - 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 ; - - default: /* no other IAC SB <option> */ - break ; - } ; - break ; - - default: /* no other IAC X */ - break ; - } ; - - return dealt_with ; -} ; - #endif diff --git a/lib/zassert.h b/lib/zassert.h index 9f2465f7..a12464fe 100644 --- a/lib/zassert.h +++ b/lib/zassert.h @@ -6,6 +6,7 @@ #define _QUAGGA_ASSERT_H #include "confirm.h" +#include "misc.h" extern void _zlog_assert_failed (const char *assertion, const char *file, unsigned int line, const char *function) @@ -44,11 +45,14 @@ extern void _zlog_abort_err (const char *mess, int err, const char *file, /* NDEBUG time assert() */ #ifndef NDEBUG -#define dassert(EX) zassert(EX) +# define dassert(EX) zassert(EX) #else -#define dassert(EX) +# define dassert(EX) #endif +/* Assert iff QDEBUG */ +#define qassert(EX) if (qdebug) zassert(EX) + /* Abort with message */ #define zabort(MS) _zlog_abort_mess(MS, __FILE__, __LINE__, __ASSERT_FUNCTION) diff --git a/lib/zconfig.h b/lib/zconfig.h index 052bfe99..afde09c6 100644 --- a/lib/zconfig.h +++ b/lib/zconfig.h @@ -22,7 +22,13 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA #define _ZEBRA_CONFIG_H #ifdef HAVE_CONFIG_H + #include "config.h" + +#if _GNU_SOURCE +#include <features.h> +#endif + #endif /* HAVE_CONFIG_H */ #endif /* _ZEBRA_CONFIG_H */ |