From 5cae7eea451f2b7d65b5892e2c1dafc70f8b836e Mon Sep 17 00:00:00 2001 From: Chris Hall Date: Sun, 13 Feb 2011 23:11:45 +0000 Subject: Second tranche of updates for pipework branch. modified: bgpd/bgp_connection.c modified: bgpd/bgp_debug.c modified: bgpd/bgp_engine.h modified: bgpd/bgp_main.c modified: bgpd/bgp_packet.c modified: bgpd/bgp_peer.c modified: bgpd/bgp_route.c modified: bgpd/bgp_routemap.c modified: bgpd/bgp_session.c modified: bgpd/bgp_vty.c modified: bgpd/bgpd.c modified: bgpd/bgpd.h modified: configure.ac modified: isisd/dict.h modified: isisd/isis_misc.c modified: isisd/isis_routemap.c modified: isisd/isis_spf.c modified: lib/Makefile.am modified: lib/command.c modified: lib/command.h modified: lib/command_execute.h modified: lib/command_parse.c modified: lib/command_parse.h modified: lib/command_queue.c modified: lib/command_queue.h modified: lib/elstring.h modified: lib/heap.c modified: lib/if.c modified: lib/if.h modified: lib/keychain.c modified: lib/keystroke.c modified: lib/keystroke.h modified: lib/list_util.c modified: lib/list_util.h modified: lib/log.c modified: lib/log.h modified: lib/memory.c modified: lib/memory.h modified: lib/memtypes.c modified: lib/misc.h modified: lib/mqueue.c modified: lib/mqueue.h deleted: lib/node_type.h modified: lib/pthread_safe.c modified: lib/qfstring.c modified: lib/qiovec.c modified: lib/qiovec.h modified: lib/qpath.c modified: lib/qpnexus.c modified: lib/qpnexus.h modified: lib/qpselect.c modified: lib/qpthreads.h modified: lib/qstring.c modified: lib/qstring.h modified: lib/qtime.c modified: lib/qtime.h modified: lib/qtimers.c modified: lib/qtimers.h modified: lib/routemap.c modified: lib/symtab.h modified: lib/thread.h deleted: lib/uty.h modified: lib/vector.c modified: lib/vector.h modified: lib/version.h.in modified: lib/vio_fifo.c modified: lib/vio_fifo.h modified: lib/vio_lines.c modified: lib/vio_lines.h modified: lib/vty.c modified: lib/vty.h modified: lib/vty_cli.c modified: lib/vty_cli.h modified: lib/vty_io.c modified: lib/vty_io.h modified: lib/vty_io_basic.c modified: lib/vty_io_basic.h modified: lib/vty_io_file.c modified: lib/vty_io_file.h modified: lib/vty_io_shell.c modified: lib/vty_io_term.c modified: lib/vty_io_term.h modified: lib/vty_local.h modified: lib/vty_pipe.c modified: lib/workqueue.h modified: lib/zebra.h modified: ospf6d/ospf6_lsa.c modified: ripngd/ripngd.c modified: tests/test-list_util.c modified: tests/test-vector.c modified: vtysh/vtysh.c modified: vtysh/vtysh_config.c --- bgpd/bgp_connection.c | 3 +- bgpd/bgp_debug.c | 38 +- bgpd/bgp_engine.h | 30 +- bgpd/bgp_main.c | 27 +- bgpd/bgp_packet.c | 5 +- bgpd/bgp_peer.c | 2 +- bgpd/bgp_route.c | 2 +- bgpd/bgp_routemap.c | 18 +- bgpd/bgp_session.c | 4 +- bgpd/bgp_vty.c | 77 +- bgpd/bgpd.c | 3 +- bgpd/bgpd.h | 4 +- configure.ac | 2 +- isisd/dict.h | 1 + isisd/isis_misc.c | 37 +- isisd/isis_routemap.c | 21 +- isisd/isis_spf.c | 40 +- lib/Makefile.am | 19 +- lib/command.c | 3047 ++++++++------------------------ lib/command.h | 263 +-- lib/command_execute.h | 135 +- lib/command_parse.c | 4512 +++++++++++++++++++++++++++++++++++++++++------- lib/command_parse.h | 806 ++++++--- lib/command_queue.c | 580 ++++++- lib/command_queue.h | 8 +- lib/elstring.h | 259 ++- lib/heap.c | 4 +- lib/if.c | 2 +- lib/if.h | 14 +- lib/keychain.c | 22 +- lib/keystroke.c | 41 +- lib/keystroke.h | 6 +- lib/list_util.c | 12 +- lib/list_util.h | 10 +- lib/log.c | 3 +- lib/log.h | 8 +- lib/memory.c | 26 +- lib/memory.h | 16 +- lib/memtypes.c | 7 +- lib/misc.h | 28 +- lib/mqueue.c | 79 +- lib/mqueue.h | 101 +- lib/node_type.h | 82 - lib/pthread_safe.c | 3 +- lib/qfstring.c | 20 +- lib/qiovec.c | 18 +- lib/qiovec.h | 3 +- lib/qpath.c | 6 +- lib/qpnexus.c | 14 +- lib/qpnexus.h | 5 +- lib/qpselect.c | 25 +- lib/qpthreads.h | 24 +- lib/qstring.c | 861 ++++----- lib/qstring.h | 592 +++++-- lib/qtime.c | 48 + lib/qtime.h | 137 +- lib/qtimers.c | 18 +- lib/qtimers.h | 16 + lib/routemap.c | 2 +- lib/symtab.h | 2 +- lib/thread.h | 2 +- lib/uty.h | 232 --- lib/vector.c | 10 +- lib/vector.h | 14 +- lib/version.h.in | 2 + lib/vio_fifo.c | 1835 +++++++++++++------- lib/vio_fifo.h | 196 ++- lib/vio_lines.c | 2 +- lib/vio_lines.h | 18 +- lib/vty.c | 1078 ++++-------- lib/vty.h | 152 +- lib/vty_cli.c | 2550 ++++++++++++++------------- lib/vty_cli.h | 166 +- lib/vty_io.c | 1196 +++++++------ lib/vty_io.h | 485 +++--- lib/vty_io_basic.c | 387 +++-- lib/vty_io_basic.h | 65 +- lib/vty_io_file.c | 2688 +++-------------------------- lib/vty_io_file.h | 25 +- lib/vty_io_shell.c | 5 +- lib/vty_io_term.c | 987 ++++++----- lib/vty_io_term.h | 15 +- lib/vty_local.h | 25 +- lib/vty_pipe.c | 59 +- lib/workqueue.h | 2 +- lib/zebra.h | 4 +- ospf6d/ospf6_lsa.c | 8 +- ripngd/ripngd.c | 178 +- tests/test-list_util.c | 25 +- tests/test-vector.c | 4 +- vtysh/vtysh.c | 18 +- vtysh/vtysh_config.c | 19 +- 92 files changed, 12624 insertions(+), 12026 deletions(-) delete mode 100644 lib/node_type.h delete mode 100644 lib/uty.h diff --git a/bgpd/bgp_connection.c b/bgpd/bgp_connection.c index 0bdd4f3d..151d3309 100644 --- a/bgpd/bgp_connection.c +++ b/bgpd/bgp_connection.c @@ -22,6 +22,7 @@ #include "bgpd/bgp_connection.h" #include +#include "misc.h" #include "bgpd/bgpd.h" #include "bgpd/bgp_fsm.h" @@ -685,7 +686,7 @@ bgp_connection_stop(bgp_connection connection, bool stop_writer) connection->notification_pending = 0 ; /* Empty out the pending queue and remove from connection queue */ - mqueue_local_reset_keep(&connection->pending_queue) ; + mqueue_local_reset(&connection->pending_queue, keep_it) ; bgp_connection_queue_del(connection) ; /* If required: set write buffer *unwritable* (and empty). */ diff --git a/bgpd/bgp_debug.c b/bgpd/bgp_debug.c index c4cf9cf6..f7494f19 100644 --- a/bgpd/bgp_debug.c +++ b/bgpd/bgp_debug.c @@ -367,7 +367,7 @@ DEFUN (debug_bgp_as4, BGP_STR "BGP AS4 actions\n") { - if (vty_get_node(vty) == CONFIG_NODE) + if (vty->node == CONFIG_NODE) DEBUG_ON (as4, AS4); else { @@ -385,7 +385,7 @@ DEFUN (no_debug_bgp_as4, BGP_STR "BGP AS4 actions\n") { - if (vty_get_node(vty) == CONFIG_NODE) + if (vty->node == CONFIG_NODE) DEBUG_OFF (as4, AS4); else { @@ -410,7 +410,7 @@ DEFUN (debug_bgp_as4_segment, BGP_STR "BGP AS4 aspath segment handling\n") { - if (vty_get_node(vty) == CONFIG_NODE) + if (vty->node == CONFIG_NODE) DEBUG_ON (as4, AS4_SEGMENT); else { @@ -428,7 +428,7 @@ DEFUN (no_debug_bgp_as4_segment, BGP_STR "BGP AS4 aspath segment handling\n") { - if (vty_get_node(vty) == CONFIG_NODE) + if (vty->node == CONFIG_NODE) DEBUG_OFF (as4, AS4_SEGMENT); else { @@ -453,7 +453,7 @@ DEFUN (debug_bgp_fsm, BGP_STR "BGP Finite State Machine\n") { - if (vty_get_node(vty) == CONFIG_NODE) + if (vty->node == CONFIG_NODE) DEBUG_ON (fsm, FSM); else { @@ -471,7 +471,7 @@ DEFUN (no_debug_bgp_fsm, BGP_STR "Finite State Machine\n") { - if (vty_get_node(vty) == CONFIG_NODE) + if (vty->node == CONFIG_NODE) DEBUG_OFF (fsm, FSM); else { @@ -496,7 +496,7 @@ DEFUN (debug_bgp_events, BGP_STR "BGP events\n") { - if (vty_get_node(vty) == CONFIG_NODE) + if (vty->node == CONFIG_NODE) DEBUG_ON (events, EVENTS); else { @@ -514,7 +514,7 @@ DEFUN (no_debug_bgp_events, BGP_STR "BGP events\n") { - if (vty_get_node(vty) == CONFIG_NODE) + if (vty->node == CONFIG_NODE) DEBUG_OFF (events, EVENTS); else { @@ -538,7 +538,7 @@ DEFUN (debug_bgp_filter, BGP_STR "BGP filters\n") { - if (vty_get_node(vty) == CONFIG_NODE) + if (vty->node == CONFIG_NODE) DEBUG_ON (filter, FILTER); else { @@ -556,7 +556,7 @@ DEFUN (no_debug_bgp_filter, BGP_STR "BGP filters\n") { - if (vty_get_node(vty) == CONFIG_NODE) + if (vty->node == CONFIG_NODE) DEBUG_OFF (filter, FILTER); else { @@ -580,7 +580,7 @@ DEFUN (debug_bgp_keepalive, BGP_STR "BGP keepalives\n") { - if (vty_get_node(vty) == CONFIG_NODE) + if (vty->node == CONFIG_NODE) DEBUG_ON (keepalive, KEEPALIVE); else { @@ -598,7 +598,7 @@ DEFUN (no_debug_bgp_keepalive, BGP_STR "BGP keepalives\n") { - if (vty_get_node(vty) == CONFIG_NODE) + if (vty->node == CONFIG_NODE) DEBUG_OFF (keepalive, KEEPALIVE); else { @@ -622,7 +622,7 @@ DEFUN (debug_bgp_update, BGP_STR "BGP updates\n") { - if (vty_get_node(vty) == CONFIG_NODE) + if (vty->node == CONFIG_NODE) { DEBUG_ON (update, UPDATE_IN); DEBUG_ON (update, UPDATE_OUT); @@ -645,7 +645,7 @@ DEFUN (debug_bgp_update_direct, "Inbound updates\n" "Outbound updates\n") { - if (vty_get_node(vty) == CONFIG_NODE) + if (vty->node == CONFIG_NODE) { if (strncmp ("i", argv[0], 1) == 0) { @@ -684,7 +684,7 @@ DEFUN (no_debug_bgp_update, BGP_STR "BGP updates\n") { - if (vty_get_node(vty) == CONFIG_NODE) + if (vty->node == CONFIG_NODE) { DEBUG_OFF (update, UPDATE_IN); DEBUG_OFF (update, UPDATE_OUT); @@ -711,7 +711,7 @@ DEFUN (debug_bgp_normal, DEBUG_STR BGP_STR) { - if (vty_get_node(vty) == CONFIG_NODE) + if (vty->node == CONFIG_NODE) DEBUG_ON (normal, NORMAL); else { @@ -728,7 +728,7 @@ DEFUN (no_debug_bgp_normal, DEBUG_STR BGP_STR) { - if (vty_get_node(vty) == CONFIG_NODE) + if (vty->node == CONFIG_NODE) DEBUG_OFF (normal, NORMAL); else { @@ -751,7 +751,7 @@ DEFUN (debug_bgp_zebra, BGP_STR "BGP Zebra messages\n") { - if (vty_get_node(vty) == CONFIG_NODE) + if (vty->node == CONFIG_NODE) DEBUG_ON (zebra, ZEBRA); else { @@ -769,7 +769,7 @@ DEFUN (no_debug_bgp_zebra, BGP_STR "BGP Zebra messages\n") { - if (vty_get_node(vty) == CONFIG_NODE) + if (vty->node == CONFIG_NODE) DEBUG_OFF (zebra, ZEBRA); else { diff --git a/bgpd/bgp_engine.h b/bgpd/bgp_engine.h index 7e3cceec..bedfc18e 100644 --- a/bgpd/bgp_engine.h +++ b/bgpd/bgp_engine.h @@ -30,13 +30,25 @@ #include "lib/qpnexus.h" #include "lib/log.h" -enum { qdebug = -#ifdef QDEBUG - 1 +/*============================================================================== + * DEBUG setting + */ + +#ifdef BGP_ENGINE_DEBUG /* Can be forced from outside */ +# if BGP_ENGINE_DEBUG +# define BGP_ENGINE_DEBUG 1 /* Force 1 or 0 */ #else - 0 +# define BGP_ENGINE_DEBUG 0 +# endif +#else +# ifdef QDEBUG +# define BGP_ENGINE_DEBUG 1 /* Follow QDEBUG */ +#else +# define BGP_ENGINE_DEBUG 0 +# endif #endif -}; + +enum { bgp_engine_debug = BGP_ENGINE_DEBUG } ; /*============================================================================== * @@ -117,10 +129,10 @@ bgp_queue_logging(const char* name, mqueue_queue mq, struct queue_stats* stats) /* Send given message to the BGP Engine -- priority/ordinary */ Inline void -bgp_to_bgp_engine(mqueue_block mqb, enum mqb_rank priority) +bgp_to_bgp_engine(mqueue_block mqb, mqb_rank_b priority) { mqueue_enqueue(bgp_nexus->queue, mqb, priority) ; - if (qdebug) + if (bgp_engine_debug) bgp_queue_logging("BGP Engine", bgp_nexus->queue, &bgp_engine_queue_stats) ; } ; @@ -131,10 +143,10 @@ bgp_to_bgp_engine(mqueue_block mqb, enum mqb_rank priority) /* Send given message to the Routing Engine -- priority/ordinary */ Inline void -bgp_to_routing_engine(mqueue_block mqb, enum mqb_rank priority) +bgp_to_routing_engine(mqueue_block mqb, mqb_rank_b priority) { mqueue_enqueue(routing_nexus->queue, mqb, priority) ; - if (qdebug) + if (bgp_engine_debug) bgp_queue_logging("Routing Engine", routing_nexus->queue, &routing_engine_queue_stats) ; } ; diff --git a/bgpd/bgp_main.c b/bgpd/bgp_main.c index 60b66533..6bbc8197 100644 --- a/bgpd/bgp_main.c +++ b/bgpd/bgp_main.c @@ -240,14 +240,17 @@ sighup (void) } -/* SIGINT handler. */ +/* SIGINT and SIGTERM handler. */ void sigint (void) { zlog_notice ("Terminating on signal"); + vty_reset_because("Terminating"); + /* tell the routing engine to send notifies to peers and wait - * for all sessions to be disabled */ + * for all sessions to be disabled, then terminate. + */ sigterm_enqueue(); } @@ -301,6 +304,7 @@ bgp_exit (int status) /* reverse bgp_zebra_init/if_init */ if (retain_mode) if_add_hook (IF_DELETE_HOOK, NULL); + for (ALL_LIST_ELEMENTS (iflist, node, nnode, ifp)) { struct listnode *c_node, *c_nnode; @@ -366,10 +370,10 @@ bgp_exit (int status) if (qpthreads_enabled) { - qpn_reset_free(routing_nexus); - qpn_reset_free(bgp_nexus); + qpn_reset(routing_nexus, free_it); + qpn_reset(bgp_nexus, free_it); } ; - cli_nexus = qpn_reset_free(cli_nexus); + cli_nexus = qpn_reset(cli_nexus, free_it); if (CONF_BGP_DEBUG (normal, NORMAL)) log_memstats_stderr ("bgpd"); @@ -744,16 +748,17 @@ sighup_action(mqueue_block mqb, mqb_flag_t flag) { zlog_info ("bgpd restarting!"); - bgp_terminate (0, 0); /* send notifies */ + bgp_terminate (false, false); /* send notifies */ bgp_reset (); - /* Reload config file. */ - vty_read_config (config_file, config_default); + /* Reload config file -- no special first command, now */ + vty_read_config_first_cmd_special(config_file, config_default, + NULL, config_ignore_warnings) ; - /* Create VTY's socket */ + /* Create VTY's socket */ vty_restart(vty_addr, vty_port, BGP_VTYSH_PATH); - /* Try to return to normal operation. */ + /* Try to return to normal operation. */ } mqb_free(mqb); @@ -807,7 +812,7 @@ sigterm_action(mqueue_block mqb, mqb_flag_t flag) */ program_terminating = true ; - bgp_terminate(1, retain_mode); + bgp_terminate(true, retain_mode); qpn_add_hook_function(&routing_nexus->foreground, program_terminate_if_all_peers_deleted) ; diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c index 9a5aaf30..54a47b06 100644 --- a/bgpd/bgp_packet.c +++ b/bgpd/bgp_packet.c @@ -173,9 +173,10 @@ bgp_update_packet (struct peer *peer, afi_t afi, safi_t safi) if (rn->prn) prd = (struct prefix_rd *) &rn->prn->p; - if (binfo && binfo->extra) + if (binfo) { - tag = binfo->extra->tag; + if (binfo->extra) + tag = binfo->extra->tag; from = binfo->peer; } diff --git a/bgpd/bgp_peer.c b/bgpd/bgp_peer.c index 3a1eb71f..59c9a8da 100644 --- a/bgpd/bgp_peer.c +++ b/bgpd/bgp_peer.c @@ -466,7 +466,7 @@ bgp_session_has_disabled(bgp_session session) session->state = bgp_session_sDisabled ; /* Immediately discard any other messages for this session. */ - mqueue_revoke(routing_nexus->queue, session) ; + mqueue_revoke(routing_nexus->queue, session, 0) ; /* If the session is marked "delete_me", do that. * diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index 4d69badb..c9281ca9 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -5033,7 +5033,7 @@ DEFUN (bgp_network_mask_natural_backdoor, ALIAS (bgp_network_mask_natural_backdoor, bgp_network_mask_natural_backdoor_ttl_cmd, - "network A.B.C.D backdoor pathlimit (1-255>", + "network A.B.C.D backdoor pathlimit <1-255>", "Specify a network to announce via BGP\n" "Network number\n" "Specify a BGP backdoor route\n" diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c index f40d057d..ebc37e0d 100644 --- a/bgpd/bgp_routemap.c +++ b/bgpd/bgp_routemap.c @@ -3065,7 +3065,7 @@ DEFUN (set_metric, ALIAS (set_metric, set_metric_addsub_cmd, - "set metric <+/-metric>", + "set metric <+/-32b>", SET_STR "Metric value for destination routing protocol\n" "Add or subtract metric\n") @@ -3155,11 +3155,11 @@ ALIAS (no_set_weight, DEFUN (set_aspath_prepend, set_aspath_prepend_cmd, - "set as-path prepend ." CMD_AS_RANGE, + "set as-path prepend .ASs", SET_STR "Transform BGP AS_PATH attribute\n" "Prepend to the as-path\n" - "AS number\n") + "AS number(s)\n") { int ret; char *str; @@ -3193,20 +3193,20 @@ DEFUN (no_set_aspath_prepend, ALIAS (no_set_aspath_prepend, no_set_aspath_prepend_val_cmd, - "no set as-path prepend ." CMD_AS_RANGE, + "no set as-path prepend .ASs", NO_STR SET_STR "Transform BGP AS_PATH attribute\n" "Prepend to the as-path\n" - "AS number\n") + "AS number(s)\n") DEFUN (set_aspath_exclude, set_aspath_exclude_cmd, - "set as-path exclude ." CMD_AS_RANGE, + "set as-path exclude .ASs", SET_STR "Transform BGP AS-path attribute\n" "Exclude from the as-path\n" - "AS number\n") + "AS number(s)\n") { int ret; char *str; @@ -3239,12 +3239,12 @@ DEFUN (no_set_aspath_exclude, ALIAS (no_set_aspath_exclude, no_set_aspath_exclude_val_cmd, - "no set as-path exclude ." CMD_AS_RANGE, + "no set as-path exclude .ASs", NO_STR SET_STR "Transform BGP AS_PATH attribute\n" "Exclude from the as-path\n" - "AS number\n") + "AS number(s)\n") DEFUN (set_community, set_community_cmd, diff --git a/bgpd/bgp_session.c b/bgpd/bgp_session.c index a734ba99..681075da 100644 --- a/bgpd/bgp_session.c +++ b/bgpd/bgp_session.c @@ -461,7 +461,7 @@ bgp_session_disable(bgp_peer peer, bgp_notify notification) /* Can revoke whatever may be queued already. Will revoke again when the * disable is acknowledged to finally clear the session out of the queue. */ - mqueue_revoke(routing_nexus->queue, session) ; + mqueue_revoke(routing_nexus->queue, session, 0) ; /* Now change to limping state */ session->state = bgp_session_sLimping; @@ -517,7 +517,7 @@ bgp_session_do_disable(mqueue_block mqb, mqb_flag_t flag) if (flag == mqb_action) { /* Immediately discard any other messages for this session. */ - mqueue_revoke(bgp_nexus->queue, session) ; + mqueue_revoke(bgp_nexus->queue, session, 0) ; /* Get the FSM to send any notification and close connections */ bgp_fsm_disable_session(session, args->notification) ; diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c index 23d39b85..9195b9f3 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c @@ -57,9 +57,7 @@ extern struct in_addr router_id_zebra; afi_t bgp_node_afi (struct vty *vty) { - enum node_type node = vty_get_node(vty) ; - - if (node == BGP_IPV6_NODE || node == BGP_IPV6M_NODE) + if (vty->node == BGP_IPV6_NODE || vty->node == BGP_IPV6M_NODE) return AFI_IP6; return AFI_IP; } @@ -69,11 +67,9 @@ bgp_node_afi (struct vty *vty) safi_t bgp_node_safi (struct vty *vty) { - enum node_type node = vty_get_node(vty) ; - - if (node == BGP_VPNV4_NODE) + if (vty->node == BGP_VPNV4_NODE) return SAFI_MPLS_VPN; - if (node == BGP_IPV4M_NODE || node == BGP_IPV6M_NODE) + if (vty->node == BGP_IPV4M_NODE || vty->node == BGP_IPV6M_NODE) return SAFI_MULTICAST; return SAFI_UNICAST; } @@ -352,7 +348,7 @@ DEFUN (router_bgp, return CMD_WARNING; } - vty_set_node(vty, BGP_NODE) ; + vty->node = BGP_NODE ; vty->index = bgp; return CMD_SUCCESS; @@ -601,7 +597,7 @@ ALIAS (no_bgp_confederation_identifier, DEFUN (bgp_confederation_peers, bgp_confederation_peers_cmd, - "bgp confederation peers ." CMD_AS_RANGE, + "bgp confederation peers .ASs", "BGP specific commands\n" "AS confederation parameters\n" "Peer ASs in BGP confederation\n" @@ -631,7 +627,7 @@ DEFUN (bgp_confederation_peers, DEFUN (no_bgp_confederation_peers, no_bgp_confederation_peers_cmd, - "no bgp confederation peers ." CMD_AS_RANGE, + "no bgp confederation peers .ASs", NO_STR "BGP specific commands\n" "AS confederation parameters\n" @@ -4018,7 +4014,7 @@ DEFUN (address_family_ipv4, "Enter Address Family command mode\n" "Address family\n") { - vty_set_node(vty, BGP_IPV4_NODE) ; + vty->node = BGP_IPV4_NODE ; return CMD_SUCCESS; } @@ -4031,9 +4027,9 @@ DEFUN (address_family_ipv4_safi, "Address Family modifier\n") { if (strncmp (argv[0], "m", 1) == 0) - vty_set_node(vty, BGP_IPV4M_NODE) ; + vty->node = BGP_IPV4M_NODE ; else - vty_set_node(vty, BGP_IPV4_NODE) ; + vty->node = BGP_IPV4_NODE ; return CMD_SUCCESS; } @@ -4044,7 +4040,7 @@ DEFUN (address_family_ipv6, "Enter Address Family command mode\n" "Address family\n") { - vty_set_node(vty, BGP_IPV6_NODE) ; + vty->node = BGP_IPV6_NODE ; return CMD_SUCCESS; } @@ -4057,9 +4053,9 @@ DEFUN (address_family_ipv6_safi, "Address Family modifier\n") { if (strncmp (argv[0], "m", 1) == 0) - vty_set_node(vty, BGP_IPV6M_NODE) ; + vty->node = BGP_IPV6M_NODE ; else - vty_set_node(vty, BGP_IPV6_NODE) ; + vty->node = BGP_IPV6_NODE ; return CMD_SUCCESS; } @@ -4070,7 +4066,7 @@ DEFUN (address_family_vpnv4, "Enter Address Family command mode\n" "Address family\n") { - vty_set_node(vty, BGP_VPNV4_NODE) ; + vty->node = BGP_VPNV4_NODE ; return CMD_SUCCESS; } @@ -4086,14 +4082,14 @@ DEFUN (exit_address_family, "exit-address-family", "Exit from Address Family configuration mode\n") { - enum node_type node = vty_get_node(vty) ; + node_type_t node = vty->node ; if (node == BGP_IPV4_NODE || node == BGP_IPV4M_NODE || node == BGP_VPNV4_NODE || node == BGP_IPV6_NODE || node == BGP_IPV6M_NODE) - vty_set_node(vty, BGP_NODE); + vty->node = BGP_NODE ; return CMD_SUCCESS; } @@ -8869,37 +8865,50 @@ bgp_config_write_redistribute (struct vty *vty, struct bgp *bgp, afi_t afi, /* BGP node structure. */ static struct cmd_node bgp_node = { - BGP_NODE, - "%s(config-router)# ", - 1, + .node = BGP_NODE, + .prompt = "%s(config-router)# ", + + .config_to_vtysh = true, }; static struct cmd_node bgp_ipv4_unicast_node = { - BGP_IPV4_NODE, - "%s(config-router-af)# ", - 1, + .node = BGP_IPV4_NODE, + .prompt = "%s(config-router-af)# ", + + .parent = BGP_NODE, + + .config_to_vtysh = true, }; static struct cmd_node bgp_ipv4_multicast_node = { - BGP_IPV4M_NODE, - "%s(config-router-af)# ", - 1, + .node = BGP_IPV4M_NODE, + .prompt = "%s(config-router-af)# ", + + .parent = BGP_NODE, + + .config_to_vtysh = true, }; static struct cmd_node bgp_ipv6_unicast_node = { - BGP_IPV6_NODE, - "%s(config-router-af)# ", - 1, + .node = BGP_IPV6_NODE, + .prompt = "%s(config-router-af)# ", + + .parent = BGP_NODE, + + .config_to_vtysh = true, }; static struct cmd_node bgp_ipv6_multicast_node = { - BGP_IPV6M_NODE, - "%s(config-router-af)# ", - 1, + .node = BGP_IPV6M_NODE, + .prompt = "%s(config-router-af)# ", + + .parent = BGP_NODE, + + .config_to_vtysh = true, }; static struct cmd_node bgp_vpnv4_node = diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c index 79aad2cb..c2d69bd2 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c @@ -19,6 +19,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include +#include "misc.h" #include "prefix.h" #include "thread.h" @@ -4691,7 +4692,7 @@ bgp_init (void) * */ void -bgp_terminate (int terminating, int retain_mode) +bgp_terminate (bool terminating, bool retain_mode) { struct bgp *bgp; struct peer *peer; diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index ee8efa1b..12a01450 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -21,7 +21,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA #ifndef _QUAGGA_BGPD_H #define _QUAGGA_BGPD_H -#include "stdbool.h" +#include "misc.h" #include "bgpd/bgp_common.h" #include "bgpd/bgp_notification.h" @@ -410,7 +410,7 @@ extern qpn_nexus bgp_nexus; extern qpn_nexus routing_nexus; /* Prototypes. */ -extern void bgp_terminate (int, int); +extern void bgp_terminate (bool, bool); extern void bgp_reset (void); extern void bgp_zclient_reset (void); /* See bgp_zebra ! */ diff --git a/configure.ac b/configure.ac index 61afe05d..4f100d94 100755 --- a/configure.ac +++ b/configure.ac @@ -8,7 +8,7 @@ ## $Id$ AC_PREREQ(2.53) -AC_INIT(Quagga, 0.99.15ex09, [http://bugzilla.quagga.net]) +AC_INIT(Quagga, 0.99.15ex10p, [http://bugzilla.quagga.net]) AC_CONFIG_SRCDIR(lib/zebra.h) AC_CONFIG_MACRO_DIR([m4]) diff --git a/isisd/dict.h b/isisd/dict.h index 9395d1c0..8cab985e 100644 --- a/isisd/dict.h +++ b/isisd/dict.h @@ -21,6 +21,7 @@ #ifndef DICT_H #define DICT_H +#include "zconfig.h" #include #ifdef KAZLIB_SIDEEFFECT_DEBUG #include "sfx.h" diff --git a/isisd/isis_misc.c b/isisd/isis_misc.c index fd910835..d45d7aa0 100644 --- a/isisd/isis_misc.c +++ b/isisd/isis_misc.c @@ -3,21 +3,21 @@ * Miscellanous routines * * Copyright (C) 2001,2002 Sampo Saaristo - * Tampere University of Technology + * Tampere University of Technology * Institute of Communications Engineering * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public Licenseas published by the Free - * Software Foundation; either version 2 of the License, or (at your option) + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public Licenseas published by the Free + * Software Foundation; either version 2 of the License, or (at your option) * any later version. * - * This program 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 + * This program 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 this program; if not, write to the Free Software Foundation, Inc., + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ @@ -96,7 +96,7 @@ isonet_print (u_char * from, int len) /* * Returns 0 on error, length of buff on ok - * extract dot from the dotted str, and insert all the number in a buff + * extract dot from the dotted str, and insert all the number in a buff */ int dotformat2buff (u_char * buff, const u_char * dotted) @@ -464,7 +464,7 @@ isis_jitter (unsigned long timer, unsigned long jitter) if (timer == 1) return timer; - /* + /* * randomizing just the percent value provides * no good random numbers - hence the spread * to RANDOM_SPREAD (100000), which is ok as @@ -496,16 +496,5 @@ newprefix2inaddr (u_char * prefix_start, u_char prefix_masklen) const char * unix_hostname (void) { - static struct utsname names; - const char *hostname; - extern struct host host; - - hostname = host.name; - if (!hostname) - { - uname (&names); - hostname = names.nodename; - } - - return hostname; -} + return cmd_host_name(true) ; /* if required, get fresh system hostname */ +} ; diff --git a/isisd/isis_routemap.c b/isisd/isis_routemap.c index cff0fa3f..fcc05264 100644 --- a/isisd/isis_routemap.c +++ b/isisd/isis_routemap.c @@ -2,22 +2,22 @@ * IS-IS Rout(e)ing protocol - isis_routemap.c * * Copyright (C) 2001,2002 Sampo Saaristo - * Tampere University of Technology + * Tampere University of Technology * Institute of Communications Engineering * * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public Licenseas published by the Free - * Software Foundation; either version 2 of the License, or (at your option) + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public Licenseas published by the Free + * Software Foundation; either version 2 of the License, or (at your option) * any later version. * - * This program 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 + * This program 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 this program; if not, write to the Free Software Foundation, Inc., + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include @@ -68,8 +68,7 @@ isis_route_map_upd (const char *name) for (i = 0; i <= ZEBRA_ROUTE_MAX; i++) { if (isis->rmap[i].name) - isis->rmap[i].map = isis->rmap[i].map = - route_map_lookup_by_name (isis->rmap[i].name); + isis->rmap[i].map = route_map_lookup_by_name (isis->rmap[i].name); else isis->rmap[i].map = NULL; } diff --git a/isisd/isis_spf.c b/isisd/isis_spf.c index 5d7e9da4..c4433e49 100644 --- a/isisd/isis_spf.c +++ b/isisd/isis_spf.c @@ -3,21 +3,21 @@ * The SPT algorithm * * Copyright (C) 2001,2002 Sampo Saaristo - * Tampere University of Technology + * Tampere University of Technology * Institute of Communications Engineering * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public Licenseas published by the Free - * Software Foundation; either version 2 of the License, or (at your option) + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public Licenseas published by the Free + * Software Foundation; either version 2 of the License, or (at your option) * any later version. * - * This program 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 + * This program 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 this program; if not, write to the Free Software Foundation, Inc., + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ @@ -64,7 +64,7 @@ remove_excess_adjs (struct list *adjs) struct isis_adjacency *adj, *candidate = NULL; int comp; - for (ALL_LIST_ELEMENTS_RO (adjs, node, adj)) + for (ALL_LIST_ELEMENTS_RO (adjs, node, adj)) { if (excess == NULL) excess = node; @@ -229,7 +229,7 @@ isis_spftree_del (struct isis_spftree *spftree) return; } -#endif +#endif void spftree_area_init (struct isis_area *area) @@ -241,7 +241,7 @@ spftree_area_init (struct isis_area *area) area->spftree6[0] = isis_spftree_new (); #endif - /* thread_add_timer (master, isis_run_spf_l1, area, + /* thread_add_timer (master, isis_run_spf_l1, area, isis_jitter (PERIODIC_SPF_INTERVAL, 10)); */ } @@ -251,7 +251,7 @@ spftree_area_init (struct isis_area *area) #ifdef HAVE_IPV6 area->spftree6[1] = isis_spftree_new (); #endif - /* thread_add_timer (master, isis_run_spf_l2, area, + /* thread_add_timer (master, isis_run_spf_l2, area, isis_jitter (PERIODIC_SPF_INTERVAL, 10)); */ } @@ -416,7 +416,7 @@ isis_spf_add2tent (struct isis_spftree *spftree, enum vertextype vtype, listnode_add (spftree->tents, vertex); return vertex; } - + /* XXX: This cant use the standard ALL_LIST_ELEMENT macro */ for (node = listhead (spftree->tents); node; node = listnextnode (node)) { @@ -613,7 +613,7 @@ lspfragloop: if (family == AF_INET && lsp->tlv_data.ipv4_int_reachs) { prefix.family = AF_INET; - for (ALL_LIST_ELEMENTS_RO (lsp->tlv_data.ipv4_int_reachs, + for (ALL_LIST_ELEMENTS_RO (lsp->tlv_data.ipv4_int_reachs, node, ipreach)) { dist = cost + ipreach->metrics.metric_default; @@ -658,7 +658,7 @@ lspfragloop: if (family == AF_INET6 && lsp->tlv_data.ipv6_reachs) { prefix.family = AF_INET6; - for (ALL_LIST_ELEMENTS_RO (lsp->tlv_data.ipv6_reachs, + for (ALL_LIST_ELEMENTS_RO (lsp->tlv_data.ipv6_reachs, node, ip6reach)) { dist = cost + ip6reach->metric; @@ -789,7 +789,7 @@ isis_spf_preload_tent (struct isis_spftree *spftree, if (family == AF_INET6 && !circuit->ipv6_router) continue; #endif /* HAVE_IPV6 */ - /* + /* * Add IP(v6) addresses of this circuit */ if (family == AF_INET) @@ -872,7 +872,7 @@ isis_spf_preload_tent (struct isis_spftree *spftree, } list_delete (adj_list); /* - * Add the pseudonode + * Add the pseudonode */ if (level == 1) memcpy (lsp_id, circuit->u.bc.l1_desig_is, ISIS_SYS_ID_LEN + 1); @@ -1272,7 +1272,7 @@ isis_spf_schedule6 (struct isis_area *area, int level) spftree->pending = 1; return retval; } - + THREAD_TIMER_OFF (spftree->t_spf); if (diff < MINIMUM_SPF_INTERVAL) @@ -1323,7 +1323,7 @@ isis_print_paths (struct vty *vty, struct list *paths) continue; if (memcmp (vertex->N.id, isis->sysid, ISIS_SYS_ID_LEN) == 0) { - vty_out (vty, "%s --%s", host.name?host.name:"", + vty_out (vty, "%s --%s", cmd_host_name(true), VTY_NEWLINE); } else diff --git a/lib/Makefile.am b/lib/Makefile.am index 39b6b03e..cc8fbd3a 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -14,9 +14,10 @@ libzebra_la_SOURCES = \ zclient.c sockopt.c smux.c md5.c if_rmap.c keychain.c privs.c \ sigevent.c pqueue.c jhash.c memtypes.c workqueue.c symtab.c heap.c \ qtime.c qpthreads.c mqueue.c qpselect.c qtimers.c qpnexus.c \ - command_parse.c command_queue.c qlib_init.c pthread_safe.c list_util.c \ - vty_io.c vty_io_file.c vty_io_shell.c vty_io_term.c vty_cli.c \ - vty_io_basic.c keystroke.c qstring.c vio_fifo.c vio_lines.c \ + command_parse.c command_execute.c command_queue.c vty_command.c \ + vty_io.c vty_io_file.c vty_io_shell.c vty_io_vsh.c vty_io_term.c vty_cli.c \ + vty_io_basic.c keystroke.c qstring.c elstring.c vio_fifo.c vio_lines.c \ + qlib_init.c pthread_safe.c list_util.c \ qiovec.c qfstring.c errno_names.c BUILT_SOURCES = memtypes.h route_types.h @@ -26,19 +27,19 @@ libzebra_la_DEPENDENCIES = @LIB_REGEX@ libzebra_la_LIBADD = @LIB_REGEX@ pkginclude_HEADERS = \ - buffer.h checksum.h command.h filter.h getopt.h hash.h \ + zebra.h zconfig.h buffer.h checksum.h command.h filter.h getopt.h hash.h \ if.h linklist.h log.h \ memory.h network.h prefix.h routemap.h distribute.h sockunion.h \ - str.h stream.h table.h thread.h vector.h version.h vty.h zebra.h \ + str.h stream.h table.h thread.h vector.h version.h vty.h \ plist.h zclient.h sockopt.h smux.h md5.h if_rmap.h keychain.h \ privs.h sigevent.h pqueue.h jhash.h zassert.h memtypes.h \ workqueue.h route_types.h symtab.h heap.h \ qtime.h qpthreads.h mqueue.h qpselect.h qtimers.h qpnexus.h \ - command_parse.h command_queue.h qlib_init.h qafi_safi.h \ + command_parse.h command_queue.h vty_command.h qlib_init.h qafi_safi.h \ confirm.h misc.h vargs.h miyagi.h pthread_safe.h list_util.h \ - tstring.h node_type.h uty.h \ - vty_io.h vty_io_file.h vty_io_shell.h vty_io_term.h vty_cli.h \ - vty_io_basic.h keystroke.h qstring.h vio_fifo.h vio_lines.h \ + tstring.h command_common.h command_local.h vty_common.h vty_local.h \ + vty_io.h vty_io_file.h vty_io_shell.h vty_io_vsh.h vty_io_term.h vty_cli.h \ + vty_io_basic.h keystroke.h qstring.h elstring.h vio_fifo.h vio_lines.h \ qiovec.h qfstring.h errno_names.h \ route_types.h command_execute.h diff --git a/lib/command.c b/lib/command.c index 3c4f056a..adea80e4 100644 --- a/lib/command.c +++ b/lib/command.c @@ -21,86 +21,279 @@ along with GNU Zebra; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include +#include "zconfig.h" +#include "version.h" +#include "misc.h" + +#include +#include +#include +#include #include "memory.h" #include "log.h" -#include #include "thread.h" #include "vector.h" -#include "vty.h" -#include "uty.h" #include "qstring.h" +#include "qtime.h" +#include "workqueue.h" #include "command.h" +#include "command_local.h" +#include "command_parse.h" #include "command_execute.h" -#include "workqueue.h" #include "command_queue.h" -#include "command_parse.h" +#include "vty_local.h" +#include "vty_command.h" +#include "vty_io.h" + +/* Vector of cmd_node, one for each known node, built during daemon + * initialisation. + * + * Declared extern in command_local.h, so it can get at it. + */ +vector node_vector = NULL ; + +/*============================================================================== + * Default motd string. + */ +#define DEFAULT_MOTD \ +"\n" \ +"Hello, this is " QUAGGA_PROGNAME " (version " QUAGGA_VERSION ")\n" \ + QUAGGA_COPYRIGHT "\n" \ +"\n" + +#ifdef QDEBUG +const char *debug_banner = + QUAGGA_PROGNAME " version " QUAGGA_VERSION " QDEBUG=" QDEBUG " " + __DATE__ " " __TIME__; +#endif + +/*============================================================================== + * Host information structure -- shared across command/vty + */ +struct host host = +{ + /* Host name of this router. */ + .name_set = false, + .name = NULL, /* set by cmd_init */ + .name_gen = 0, /* set by cmd_init */ + + /* Password for vty interface. */ + .password = NULL, + .password_encrypted = false, + + /* Enable password */ + .enable = NULL, + .enable_encrypted = false, + + /* System wide terminal lines. */ + .lines = -1, /* unset */ + + /* Log filename. */ + .logfile = NULL, + + /* config file name of this host */ + .config_file = NULL, + + /* Flags for services */ + .advanced = false, + .encrypt = false, + + /* Banner configuration. */ + .motd = DEFAULT_MOTD, + .motdfile = NULL, + + /* allow VTY to start without password */ + .no_password_check = false, + + /* if VTY starts without password, use RESTRICTED_NODE */ + .restricted_mode = false, + + /* Vty timeout value -- see "exec timeout" command */ + .vty_timeout_val = VTY_TIMEOUT_DEFAULT, + + /* Vty access-class command */ + .vty_accesslist_name = NULL, + + /* Vty access-class for IPv6. */ + .vty_ipv6_accesslist_name = NULL, + + /* Current directory -- initialised in vty_init() */ + .vty_cwd = NULL, +} ; + +/*============================================================================== + * host.name handling. + * + * If the host.name is set explicitly by command then host.name_set is true, + * and things are simple. + * + * Otherwise, need to ask the system. Does that once at start up, and if the + * host.name is unset by command -- so there should always be a valid host.name. + * + * However, it is conceivable that the host name changes (!). So, when asking + * for cmd_host_name(), can ask for the system to be asked afresh (if the name + * is not explicitly set). + * + * The VTY watch-dog timer refreshes the host.name on a regular basis, + * cmd_check_host_name(), so need not ask for a fresh host.name, unless wish + * to guarantee to be absolutely up to date. + * + * The VTY prompts need the current host name, but that is debounced using the + * host.name_gen value. host.name_gen is incremented each time the host.name + * actually changes. It is thought unlikely that this will ever wrap round, + * but it is guaranteed not to be zero. + * + * The fact that the host.name can change is reflected in the need to hold + * the VTY_LOCK while have the host.name in hand. + */ + +static void cmd_get_sys_host_name(void) ; +static void cmd_new_host_name(const char* name) ; + +/*------------------------------------------------------------------------------ + * Get the host name: (a) from an explicit host name command + * or: (b) from the last time the system was asked. + * + * Note that the system is asked regularly by the watch dog. + * + * NB: needs to be VTY_LOCK() *or* not running qpthreads. + */ +extern const char* +cmd_host_name(bool fresh) +{ + VTY_ASSERT_LOCKED() ; + + if (!host.name_set && fresh) + cmd_get_sys_host_name() ; + + return host.name ; +} ; + +/*------------------------------------------------------------------------------ + * Set (or unset) the host name from an explicit host name command. + * + * If unsets, immediately asks the system for the system host name (which may + * be the same !). + */ +static cmd_return_code_t +cmd_set_host_name(const char* name) +{ + VTY_LOCK() ; + + host.name_set = (name != NULL) ; + if (host.name_set) + cmd_new_host_name(name) ; + else + cmd_get_sys_host_name() ; + + VTY_UNLOCK() ; + return CMD_SUCCESS ; +} ; -/* Command vector which includes some level of command lists. Normally - each daemon maintains each own cmdvec. */ -vector cmdvec = NULL; +/*------------------------------------------------------------------------------ + * Get the host name from the system and set host.name to that. + * + * NB: result will not be NULL, whatever happens will get at least an empty + * '\0' terminated string. + * + * NB: called early in the morning to initialise host.name and to set + * host.name_gen != 0. + */ +static void +cmd_get_sys_host_name(void) +{ + struct utsname info ; -struct desc desc_cr; -char *command_cr = NULL; + VTY_ASSERT_LOCKED() ; -/* Host information structure. */ -struct host host; + uname (&info) ; + cmd_new_host_name(info.nodename) ; +} ; -/* Store of qstrings, used for parsing. */ -token_vector_t spare_token_strings ; +/*------------------------------------------------------------------------------ + * Set host.name to (possibly) new value. + * + * Does nothing if new name == old name, otherwise increments name_gen. + */ +static void +cmd_new_host_name(const char* name) +{ + if ((host.name == NULL) || (strcmp(host.name, name) != 0)) + { + XFREE(MTYPE_HOST, host.name) ; + host.name = XSTRDUP(MTYPE_HOST, name) ; + do ++host.name_gen ; while (host.name_gen == 0) ; + } ; +} ; -/* Standard command node structures. */ +/*============================================================================== + * node structures for the basic nodes. + * + * Covers everything up to and including the CONFIG_NODE. + */ static struct cmd_node auth_node = { - .node = AUTH_NODE, - "Password: ", + .node = AUTH_NODE, + .prompt = "Password: ", + + .parent = AUTH_NODE, /* self => no parent */ + .exit_to = AUTH_NODE, /* self => no exit */ + .end_to = AUTH_NODE, /* self => no end */ }; static struct cmd_node view_node = { - VIEW_NODE, - "%s> ", + .node = VIEW_NODE, + .prompt = "%s> ", + + .parent = AUTH_NODE, /* self => no parent */ + .exit_to = NULL_NODE, /* close ! */ + .end_to = VIEW_NODE, /* self => no end */ }; static struct cmd_node restricted_node = { - RESTRICTED_NODE, - "%s$ ", + .node = RESTRICTED_NODE, + .prompt = "%s$ ", + + .parent = AUTH_NODE, /* self => no parent */ + .exit_to = NULL_NODE, /* close ! */ + .end_to = RESTRICTED_NODE, /* self => no end */ }; static struct cmd_node auth_enable_node = { - AUTH_ENABLE_NODE, - "Password: ", + .node = AUTH_ENABLE_NODE, + .prompt = "Password: ", + + .parent = AUTH_ENABLE_NODE, /* self => no parent */ + .exit_to = NULL_NODE, /* close ! */ + .end_to = AUTH_ENABLE_NODE, /* self => no end */ }; static struct cmd_node enable_node = { - ENABLE_NODE, - "%s# ", + .node = ENABLE_NODE, + .prompt = "%s# ", + + .parent = ENABLE_NODE, /* self => no parent */ + .exit_to = NULL_NODE, /* close ! */ + .end_to = ENABLE_NODE, /* self => no end */ }; static struct cmd_node config_node = { - CONFIG_NODE, - "%s(config)# ", - 1 -}; + .node = CONFIG_NODE, + .prompt = "%s(config)# ", -/* Default motd string. */ -static const char *default_motd = -"\r\n\ -Hello, this is " QUAGGA_PROGNAME " (version " QUAGGA_VERSION ").\r\n\ -" QUAGGA_COPYRIGHT "\r\n\ -\r\n"; + .parent = CONFIG_NODE, /* self => no parent */ + .exit_to = ENABLE_NODE, /* exit == end for CONFIG_NODE */ + .end_to = ENABLE_NODE, /* standard end action */ -#ifdef QDEBUG -const char *debug_banner = - QUAGGA_PROGNAME " version " QUAGGA_VERSION " QDEBUG=" QDEBUG " " - __DATE__ " " __TIME__; -#endif + .config_to_vtysh = true +}; static const struct facility_map { int facility; @@ -132,18 +325,29 @@ static const struct facility_map { { 0, NULL, 0 }, }; -static struct cmd_element cmd_pipe = +#if 0 +static enum cmd_return_code +cmd_pipe_func(cmd_command self DEFUN_CMD_ARG_UNUSED, + struct vty* vty DEFUN_CMD_ARG_UNUSED, + int argc DEFUN_CMD_ARG_UNUSED, + argv_t argv DEFUN_CMD_ARG_UNUSED) +{ + return CMD_SUCCESS ; +} ; + +static struct cmd_command cmd_pipe_element = { .string = "< or <|", /* Dummy */ .func = cmd_pipe_func, .doc = "Pipe input to command processor", .daemon = 0, - .strvec = NULL, + .items = NULL, .cmdsize = 0, .config = NULL, .subconfig = NULL, .attr = CMD_ATTR_SIMPLE, } ; +#endif static const char * @@ -188,9 +392,12 @@ print_version (const char *progname) } -/* Utility function to concatenate argv argument into a single string - with inserting ' ' character between each argument. */ -char * +/*------------------------------------------------------------------------------ + * Concatenate argv argument into a single string, inserting ' ' between each + * argument. + * + */ +extern char * argv_concat (const char* const* argv, int argc, int shift) { int i; @@ -201,8 +408,10 @@ argv_concat (const char* const* argv, int argc, int shift) len = 0; for (i = shift; i < argc; i++) len += strlen(argv[i])+1; + if (!len) return NULL; + p = str = XMALLOC(MTYPE_TMP, len); for (i = shift; i < argc; i++) { @@ -220,8 +429,8 @@ argv_concat (const char* const* argv, int argc, int shift) static int cmp_node (const void *p, const void *q) { - const struct cmd_element *a = *(struct cmd_element * const *)p; - const struct cmd_element *b = *(struct cmd_element * const *)q; + const struct cmd_command *a = *(struct cmd_command * const *)p; + const struct cmd_command *b = *(struct cmd_command * const *)q; return strcmp (a->string, b->string); } @@ -237,42 +446,146 @@ cmp_desc (const void *p, const void *q) #endif //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -/* Install top node of command vector. */ -void +/*------------------------------------------------------------------------------ + * Install top node of command vector. + * + * Initialised as follows: + * + * .node -- must be set before node is installed + * .prompt -- must be set before node is installed + * .config_to_vtysh -- must be set true/false before node is installed + * + * .parent -- may be set, or if 0 (node_null) will be set to default + * .exit_to -- may be set, or if 0 (node_null) will be set to default + * .end_to -- may be set, or if 0 (node_null) will be set to default + * + * .config_write -- is set from parameter + * + * .cmd_vector -- initialised empty + * + * Default parent node: + * + * * all nodes >= NODE_CONFIG have NODE_CONFIG as a parent + * * all nodes < NODE_CONFIG are their own parents + * + * Default exit_to: + * + * * all nodes > NODE_CONFIG exit_to their parent + * * node == NODE_CONFIG exit_to ENABLE_NODE (same as end_to !) + * * all nodes < NODE_CONFIG exit_to close + * + * Default end_to: + * + * * all nodes >= NODE_CONFIG end_to ENABLE_NODE + * * all nodes < NODE_CONFIG end_to themselves + * + */ +extern void install_node (struct cmd_node *node, - int (*func) (struct vty *)) + int (*config_write) (struct vty *)) { - vector_set_index (cmdvec, node->node, node); - node->func = func; - node->cmd_vector = vector_init (0); -} + confirm(NULL_NODE == 0) ; /* unset value for .parent, .end_to & .exit_to */ -/* Compare two command's string. Used in sort_node (). */ -static int -cmp_node (const struct cmd_element **a, const struct cmd_element **b) + if (node->parent == NULL_NODE) + { + if (node->node >= CONFIG_NODE) + node->parent = CONFIG_NODE ; + else + node->parent = node->node ; + } ; + + if (node->end_to == NULL_NODE) + { + if (node->node >= CONFIG_NODE) + node->end_to = ENABLE_NODE ; + else + node->end_to = node->node ; + } ; + + if (node->exit_to == NULL_NODE) + { + if (node->node > CONFIG_NODE) + node->exit_to = node->parent ; + else if (node->node == CONFIG_NODE) + node->exit_to = ENABLE_NODE ; + else + node->exit_to = NULL_NODE ; + } ; + + node->config_write = config_write ; + + vector_init_new(node->cmd_vector, 0) ; /* embedded */ + + vector_set_index (node_vector, node->node, node); +} ; + +/*------------------------------------------------------------------------------ + * Return address of cmd_node -- asserts is not NULL ! + */ +static cmd_node +cmd_cmd_node(node_type_t node) { - return strcmp ((*a)->string, (*b)->string); -} + cmd_node cn ; + + cn = vector_get_item(node_vector, node) ; + if (cn != NULL) + return cn ; + + zabort("invalid node") ; +} ; + +/*------------------------------------------------------------------------------ + * Return parent node + */ +extern node_type_t +cmd_node_parent(node_type_t node) +{ + return (cmd_cmd_node(node))->parent ; +} ; + +/*------------------------------------------------------------------------------ + * Return exit_to node + */ +extern node_type_t +cmd_node_exit_to(node_type_t node) +{ + return (cmd_cmd_node(node))->exit_to ; +} ; + +/*------------------------------------------------------------------------------ + * Return parent node + */ +extern node_type_t +cmd_node_end_to(node_type_t node) +{ + return (cmd_cmd_node(node))->end_to ; +} ; + + +/*------------------------------------------------------------------------------ + * Sorting of all node cmd_vectors. + */ + +/* Compare two command's string. Used in sort_node (). */ static int -cmp_desc (const struct desc **a, const struct desc **b) +cmp_node (const struct cmd_command **a, const struct cmd_command **b) { - return strcmp ((*a)->cmd, (*b)->cmd); + return strcmp ((*a)->string, (*b)->string); } /* Sort each node's command element according to command string. */ -void +extern void sort_node () { unsigned int i ; - for (i = 0; i < vector_length(cmdvec); i++) + for (i = 0; i < vector_length(node_vector); i++) { struct cmd_node *cnode; vector cmd_vector ; - unsigned int j; - cnode = vector_get_item(cmdvec, i) ; + cnode = vector_get_item(node_vector, i) ; if (cnode == NULL) continue ; @@ -282,25 +595,10 @@ sort_node () continue ; vector_sort(cmd_vector, (vector_sort_cmp*)cmp_node) ; - - for (j = 0; j < vector_length(cmd_vector); j++) - { - struct cmd_element *cmd_element ; - vector descvec ; - - cmd_element = vector_get_item (cmd_vector, j); - if (cmd_element == NULL) - continue ; - - descvec = vector_get_last_item(cmd_element->strvec) ; - if (descvec == NULL) - continue ; - - vector_sort(descvec, (vector_sort_cmp*)cmp_desc) ; - } ; } ; } ; +#if 0 /*------------------------------------------------------------------------------ * Take string and break it into tokens -- see cmd_make_tokens(). * @@ -310,8 +608,11 @@ sort_node () extern vector cmd_make_strvec (const char *string) { +#if 0 return cmd_tokenise(NULL, string) ; #error sort this one out +#endif + return NULL ; } ; /*------------------------------------------------------------------------------ @@ -320,204 +621,47 @@ cmd_make_strvec (const char *string) * Create vector if required. */ extern vector -cmd_add_to_strvec (vector strvec, const char* str) +cmd_add_to_strvec (vector items, const char* str) { - if (strvec == NULL) - strvec = vector_init(1) ; + if (items == NULL) + items = vector_init(1) ; - vector_push_item(strvec, XSTRDUP(MTYPE_STRVEC, str)); + vector_push_item(items, XSTRDUP(MTYPE_STRVEC, str)); - return strvec ; + return items ; } ; /*------------------------------------------------------------------------------ * Free allocated string vector (if any) and all its contents. * - * Note that this is perfectly happy with strvec == NULL. + * Note that this is perfectly happy with items == NULL. */ extern void -cmd_free_strvec (vector strvec) +cmd_free_strvec (vector items) { char *cp; - /* Note that vector_ream_free() returns NULL if strvec == NULL */ - while((cp = vector_ream(strvec, free_it)) != NULL) + /* Note that vector_ream_free() returns NULL if items == NULL */ + while((cp = vector_ream(items, free_it)) != NULL) XFREE (MTYPE_STRVEC, cp); } ; -/*----------------------------------------------------------------------------*/ - -/* Fetch next description. Used in cmd_make_descvec(). */ -static char * -cmd_desc_str (const char **string) -{ - const char *cp, *start; - char *token; - int strlen; - - cp = *string; - - if (cp == NULL) - return NULL; - - /* Skip white spaces. */ - while (isspace ((int) *cp) && *cp != '\0') - cp++; - - /* Return if there is only white spaces */ - if (*cp == '\0') - return NULL; - - start = cp; - - while (!(*cp == '\r' || *cp == '\n') && *cp != '\0') - cp++; - - strlen = cp - start; - token = XMALLOC (MTYPE_STRVEC, strlen + 1); - memcpy (token, start, strlen); - *(token + strlen) = '\0'; - - *string = cp; - - return token; -} - -/* New string vector. */ -static vector -cmd_make_descvec (const char *string, const char *descstr) -{ - int multiple = 0; - const char *sp; - char *token; - int len; - const char *cp; - const char *dp; - vector allvec; - vector strvec = NULL; - struct desc *desc; - - cp = string; - dp = descstr; - - if (cp == NULL) - return NULL; - - allvec = vector_init (0); - - while (1) - { - while (isspace ((int) *cp) && *cp != '\0') - cp++; - - if (*cp == '(') - { - multiple = 1; - cp++; - } - if (*cp == ')') - { - multiple = 0; - cp++; - } - if (*cp == '|') - { - if (! multiple) - { - fprintf (stderr, "Command parse error!: %s\n", string); - exit (1); - } - cp++; - } - - while (isspace ((int) *cp) && *cp != '\0') - cp++; - - if (*cp == '(') - { - multiple = 1; - cp++; - } - - if (*cp == '\0') - return allvec; - - sp = cp; - - while (! (isspace ((int) *cp) || *cp == ')' || *cp == '|') && *cp != '\0') - cp++; - - len = cp - sp; - - token = XMALLOC (MTYPE_STRVEC, len + 1); - memcpy (token, sp, len); - *(token + len) = '\0'; - - desc = XCALLOC (MTYPE_DESC, sizeof (struct desc)); - desc->cmd = token; - desc->str = cmd_desc_str (&dp); - - if (multiple) - { - if (multiple == 1) - { - strvec = vector_init (0); - vector_set (allvec, strvec); - } - multiple++; - } - else - { - strvec = vector_init (0); - vector_set (allvec, strvec); - } - vector_set (strvec, desc); - } -} - -/* Count mandantory string vector size. This is to determine inputed - command has enough command length. */ -static int -cmd_cmdsize (vector strvec) -{ - unsigned int i; - int size = 0; - - for (i = 0; i < vector_length(strvec); i++) - { - vector descvec; - - descvec = vector_get_item (strvec, i) ; - if (descvec == NULL) - continue ; - - if (vector_length(descvec) == 1) - { - struct desc *desc; - - desc = vector_get_item(descvec, 0) ; - if (desc != NULL) - if (desc->cmd == NULL || CMD_OPTION (desc->cmd)) - break ; - } - size++; - } ; - - return size; -} +#endif -/* Return prompt character of specified node. */ -const char * -cmd_prompt (enum node_type node) +/*------------------------------------------------------------------------------ + * Return prompt string for the specified node. + */ +extern const char * +cmd_prompt(node_type_t node) { struct cmd_node *cnode; - assert(cmdvec != NULL) ; - assert(cmdvec->p_items != NULL) ; + assert(node_vector != NULL) ; + assert(node_vector->p_items != NULL) ; cnode = NULL ; - if (node < cmdvec->limit) - cnode = vector_get_item (cmdvec, node); + if (node < node_vector->limit) + cnode = vector_get_item (node_vector, node); if (cnode == NULL) { @@ -528,17 +672,16 @@ cmd_prompt (enum node_type node) return cnode->prompt; } -/* Install a command into a node. */ -void -install_element (enum node_type ntype, struct cmd_element *cmd) +/*------------------------------------------------------------------------------ + * Install a command into a node. + * + */ +extern void +install_element(node_type_t ntype, cmd_command cmd) { - struct cmd_node *cnode; - - /* cmd_init hasn't been called */ - if (!cmdvec) - return; + cmd_node cnode; - cnode = vector_get_item (cmdvec, ntype); + cnode = vector_get_item (node_vector, ntype); if (cnode == NULL) { @@ -549,1795 +692,147 @@ install_element (enum node_type ntype, struct cmd_element *cmd) vector_set (cnode->cmd_vector, cmd); - if (cmd->strvec == NULL) - cmd->strvec = cmd_make_descvec (cmd->string, cmd->doc); - - cmd->cmdsize = cmd_cmdsize (cmd->strvec); -} - -static const unsigned char itoa64[] = -"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - -static void -to64(char *s, long v, int n) -{ - while (--n >= 0) - { - *s++ = itoa64[v&0x3f]; - v >>= 6; - } -} - -static char * -zencrypt (const char *passwd) -{ - char salt[6]; - struct timeval tv; - char *crypt (const char *, const char *); - - gettimeofday(&tv,0); - - to64(&salt[0], random(), 3); - to64(&salt[3], tv.tv_usec, 3); - salt[5] = '\0'; - - return crypt (passwd, salt); -} - -/* This function write configuration of this host. */ -static int -config_write_host (struct vty *vty) -{ - if (qpthreads_enabled) - vty_out (vty, "threaded%s", VTY_NEWLINE); - - if (host.name) - vty_out (vty, "hostname %s%s", host.name, VTY_NEWLINE); - - if (host.encrypt) - { - if (host.password_encrypt) - vty_out (vty, "password 8 %s%s", host.password_encrypt, VTY_NEWLINE); - if (host.enable_encrypt) - vty_out (vty, "enable password 8 %s%s", host.enable_encrypt, VTY_NEWLINE); - } - else - { - if (host.password) - vty_out (vty, "password %s%s", host.password, VTY_NEWLINE); - if (host.enable) - vty_out (vty, "enable password %s%s", host.enable, VTY_NEWLINE); - } - - if (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); - } - - if (host.logfile && (zlog_get_maxlvl(NULL, ZLOG_DEST_FILE) != ZLOG_DISABLED)) - { - vty_out (vty, "log file %s", host.logfile); - if (zlog_get_maxlvl(NULL, ZLOG_DEST_FILE) != zlog_get_default_lvl(NULL)) - vty_out (vty, " %s", - zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_FILE)]); - vty_out (vty, "%s", VTY_NEWLINE); - } - - if (zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT) != ZLOG_DISABLED) - { - vty_out (vty, "log stdout"); - if (zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT) != zlog_get_default_lvl(NULL)) - vty_out (vty, " %s", - zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT)]); - vty_out (vty, "%s", VTY_NEWLINE); - } - - if (zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR) == ZLOG_DISABLED) - vty_out(vty,"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); - - if (zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG) != ZLOG_DISABLED) - { - vty_out (vty, "log syslog"); - if (zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG) != zlog_get_default_lvl(NULL)) - vty_out (vty, " %s", - zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG)]); - vty_out (vty, "%s", VTY_NEWLINE); - } - - if (zlog_get_facility(NULL) != LOG_DAEMON) - vty_out (vty, "log facility %s%s", - facility_name(zlog_get_facility(NULL)), VTY_NEWLINE); - - if (zlog_get_record_priority(NULL) == 1) - vty_out (vty, "log record-priority%s", VTY_NEWLINE); - - if (zlog_get_timestamp_precision(NULL) > 0) - vty_out (vty, "log timestamp precision %d%s", - zlog_get_timestamp_precision(NULL), VTY_NEWLINE); - - if (host.advanced) - vty_out (vty, "service advanced-vty%s", VTY_NEWLINE); - - if (host.encrypt) - vty_out (vty, "service password-encryption%s", VTY_NEWLINE); - - if (host.lines >= 0) - vty_out (vty, "service terminal-length %d%s", host.lines, - VTY_NEWLINE); - - if (host.motdfile) - vty_out (vty, "banner motd file %s%s", host.motdfile, VTY_NEWLINE); - else if (! host.motd) - vty_out (vty, "no banner motd%s", VTY_NEWLINE); - - return 1; -} - -/* Utility function for getting command vector. */ -static vector -cmd_node_vector (vector v, enum node_type ntype) -{ - struct cmd_node *cnode = vector_get_item (v, ntype); - return cnode->cmd_vector; -} - -#if 0 -/* Filter command vector by symbol. This function is not actually used; - * should it be deleted? */ -static int -cmd_filter_by_symbol (char *command, char *symbol) -{ - int i, lim; - - if (strcmp (symbol, "IPV4_ADDRESS") == 0) - { - i = 0; - lim = strlen (command); - while (i < lim) - { - if (! (isdigit ((int) command[i]) || command[i] == '.' || command[i] == '/')) - return 1; - i++; - } - return 0; - } - if (strcmp (symbol, "STRING") == 0) - { - i = 0; - lim = strlen (command); - while (i < lim) - { - if (! (isalpha ((int) command[i]) || command[i] == '_' || command[i] == '-')) - return 1; - i++; - } - return 0; - } - if (strcmp (symbol, "IFNAME") == 0) - { - i = 0; - lim = strlen (command); - while (i < lim) - { - if (! isalnum ((int) command[i])) - return 1; - i++; - } - return 0; - } - return 0; -} -#endif - -/*============================================================================== - * Command "filtering". - * - * The command parsing process starts with a (shallow) copy of the cmd_vector - * entry for the current "node". - * - * So cmd_v contains pointers to struct cmd_element values. When match fails, - * the pointer is set NULL -- so parsing is a process of reducing the cmd_v - * down to just the entries that match. - * - * Each cmd_element has a vector "strvec", which contains an entry for each - * "token" position. That entry is a vector containing the possible values at - * that position. - * - * - */ - -/*------------------------------------------------------------------------------ - * Make strict or completion match and return match type flag. - * - * Takes: command -- address of candidate token - * cmd_v -- vector of commands that is being reduced/filtered - * index -- index of token (position in line -- 0 == first) - * min_match -- any_match => allow partial matching - * exact_match => must match completely - * - * Returns: any of the enum match_type values: - * - * no_match => no match of any kind - * - * extend_match => saw an optional token - * ipv4_prefix_match ) - * ipv4_match ) - * ipv6_prefix_match ) saw full or partial match for this - * ipv6_match ) - * range_match ) - * vararg_match ) - * - * partly_match => saw partial match for a keyword - * exact_match => saw exact match for a keyword - * - * Note that these return values are in ascending order of preference. So, - * if there are multiple possibilities at this position, will return the one - * furthest down this list. - */ -static enum match_type -cmd_filter(const char *command, vector cmd_v, unsigned int index, - match_type_t min_match) -{ - unsigned int i; - unsigned int k; - enum match_type best_match; - size_t c_len ; - - best_match = no_match ; - c_len = strlen(command) ; - - /* If command and cmd_element string do match, keep in vector */ - k = 0 ; - for (i = 0; i < vector_length (cmd_v); i++) - { - const char *str; - struct cmd_element *cmd_element; - vector descvec; - struct desc *desc; - unsigned int j; - bool matched ; - - cmd_element = vector_get_item(cmd_v, i) ; - - /* Skip past NULL cmd_v entries (just in case) */ - if (cmd_element == NULL) - continue ; - - /* Discard cmd_v entry that has no token at the current position */ - descvec = vector_get_item(cmd_element->strvec, index) ; - if (descvec == NULL) - continue ; - - /* See if get any sort of match at current position */ - matched = 0 ; - for (j = 0; j < vector_length (descvec); j++) - { - desc = vector_get_item(descvec, j) ; - if (desc == NULL) - continue ; - - str = desc->cmd; - - if (CMD_VARARG (str)) - { - if (best_match < vararg_match) - best_match = vararg_match; - matched = true ; - } - else if (CMD_RANGE (str)) - { - if (cmd_range_match (str, command)) - { - if (best_match < range_match) - best_match = range_match; - matched = true ; - } - } -#ifdef HAVE_IPV6 - else if (CMD_IPV6 (str)) - { - if (cmd_ipv6_match (command) >= min_match) - { - if (best_match < ipv6_match) - best_match = ipv6_match; - matched = true ; - } - } - else if (CMD_IPV6_PREFIX (str)) - { - if (cmd_ipv6_prefix_match (command) >= min_match) - { - if (best_match < ipv6_prefix_match) - best_match = ipv6_prefix_match; - matched = true ; - } - } -#endif /* HAVE_IPV6 */ - else if (CMD_IPV4 (str)) - { - if (cmd_ipv4_match (command) >= min_match) - { - if (best_match < ipv4_match) - best_match = ipv4_match; - matched = true ; - } - } - else if (CMD_IPV4_PREFIX (str)) - { - if (cmd_ipv4_prefix_match (command) >= min_match) - { - if (best_match < ipv4_prefix_match) - best_match = ipv4_prefix_match; - matched = true ; - } - } - else if (CMD_OPTION (str) || CMD_VARIABLE (str)) - { - if (best_match < extend_match) - best_match = extend_match; - matched = true ; - } - else - { - if (strcmp (command, str) == 0) - { - best_match = exact_match ; - matched = true ; - } - else if (min_match <= partly_match) - { - if (strncmp (command, str, c_len) == 0) - { - if (best_match < partly_match) - best_match = partly_match ; - matched = true ; - } ; - } ; - } ; - } ; - - /* Keep cmd_element if have a match */ - if (matched) - vector_set_item(cmd_v, k++, cmd_element) ; - } ; - - vector_set_length(cmd_v, k) ; /* discard what did not keep */ - - return best_match; -} ; - -/*------------------------------------------------------------------------------ - * Check for ambiguous match - * - * Given the best that cmd_filter_by_completion() or cmd_filter_by_string() - * found, check as follows: - * - * 1. discard all commands for which do not have the type of match selected. - * - * See above for the ranking of matches. - * - * 2. for "partial match", look out for matching more than one keyword, and - * return 1 if finds that. - * - * 3. for "range match", look out for matching more than one range, and - * return 1 if finds that. - * - * 4. for ipv4_prefix_match and ipv6_prefix_match, if get a "partial match", - * return 2. - * - * This appears to catch things which are supposed to be prefixes, but - * do not have a '/' or do not have any digits after the '/'. - * - * Takes: command -- address of candidate token - * cmd_v -- vector of commands that is being reduced/filtered - * index -- index of token (position in line -- 0 == first) - * type -- as returned by cmd_filter_by_completion() - * or cmd_filter_by_string() - * - * Returns: 0 => not ambiguous - * 1 => ambiguous -- the candidate token matches more than one - * keyword, or the candidate number matches more - * than one number range. - * 2 => partial match for ipv4_prefix or ipv6_prefix - * (missing '/' or no digits after '/'). - * - * NB: it is assumed that cannot find both 1 and 2 states. But in any case, - * returns 1 in preference. - */ -static int -is_cmd_ambiguous (const char *command, vector cmd_v, int index, - enum match_type type) -{ - unsigned int i; - unsigned int k; - int ret ; - - ret = 0 ; /* all's well so far */ - k = 0 ; /* nothing kept, yet */ - - for (i = 0; i < vector_length (cmd_v); i++) - { - unsigned int j; - struct cmd_element *cmd_element; - const char *str_matched ; - vector descvec; - struct desc *desc; - bool matched ; - enum match_type mt ; - - cmd_element = vector_get_item (cmd_v, i) ; - - /* Skip past NULL cmd_v entries (just in case) */ - if (cmd_element == NULL) - continue ; - - /* The cmd_v entry MUST have a token at the current position */ - descvec = vector_get_item (cmd_element->strvec, index) ; - assert(descvec != NULL) ; - - /* See if have a match against any of the current possibilities - * - * str_matched is set the first time get a partial string match, - * or the first time get a number range match. - * - * If get a second partial string match or number range match, then - * unless - */ - str_matched = NULL ; - matched = 0; - for (j = 0; j < vector_length (descvec); j++) - { - enum match_type ret; - const char *str ; - - desc = vector_get_item (descvec, j) ; - if (desc == NULL) - continue ; - - str = desc->cmd; - - switch (type) - { - case exact_match: - if (!(CMD_OPTION (str) || CMD_VARIABLE (str)) - && strcmp (command, str) == 0) - matched = true ; - break; - - case partly_match: - if (!(CMD_OPTION (str) || CMD_VARIABLE (str)) - && strncmp (command, str, strlen (command)) == 0) - { - if (str_matched && (strcmp (str_matched, str) != 0)) - ret = 1; /* There is ambiguous match. */ - else - str_matched = str; - matched = true ; - } - break; - - case range_match: - if (cmd_range_match (str, command)) - { - if (str_matched && strcmp (str_matched, str) != 0) - ret = 1; - else - str_matched = str; - matched = true ; - } - break; - -#ifdef HAVE_IPV6 - case ipv6_match: - if (CMD_IPV6 (str)) - matched = true ; - break; - - case ipv6_prefix_match: - if ((mt = cmd_ipv6_prefix_match (command)) != no_match) - { - if ((mt == partly_match) && (ret != 1)) - ret = 2; /* There is incomplete match. */ - matched = true ; - } - break; -#endif /* HAVE_IPV6 */ - - case ipv4_match: - if (CMD_IPV4 (str)) - matched = true ; - break; - - case ipv4_prefix_match: - if ((mt = cmd_ipv4_prefix_match (command)) != no_match) - { - if ((mt == partly_match) && (ret != 1)) - ret = 2; /* There is incomplete match. */ - matched = true ; - } - break; - - case extend_match: - if (CMD_OPTION (str) || CMD_VARIABLE (str)) - matched = true ; - break; - - case no_match: - default: - break; - } ; - } ; - - /* Keep cmd_element if have a match */ - if (matched) - vector_set_item(cmd_v, k++, cmd_element) ; - } ; - - vector_set_length(cmd_v, k) ; /* discard what did not keep */ - - return ret ; -} ; - -/*------------------------------------------------------------------------------ - * If src matches dst return dst string, otherwise return NULL - * - * Returns NULL if dst is an option, variable of vararg. - * - * NULL or empty src are deemed to match. - */ -static const char * -cmd_entry_function (const char *src, const char *dst) -{ - if (CMD_OPTION (dst) || CMD_VARIABLE (dst) || CMD_VARARG (dst)) - return NULL; - - if ((src == NULL) || (*src == '\0')) - return dst; - - if (strncmp (src, dst, strlen (src)) == 0) - return dst; - - return NULL; -} - -/* If src matches dst return dst string, otherwise return NULL */ -/* This version will return the dst string always if it is - CMD_VARIABLE for '?' key processing */ -static const char * -cmd_entry_function_desc (const char *src, const char *dst) -{ - if (CMD_VARARG (dst)) - return dst; - - if (CMD_RANGE (dst)) - { - if (cmd_range_match (dst, src)) - return dst; - else - return NULL; - } - -#ifdef HAVE_IPV6 - if (CMD_IPV6 (dst)) - { - if (cmd_ipv6_match (src)) - return dst; - else - return NULL; - } - - if (CMD_IPV6_PREFIX (dst)) - { - if (cmd_ipv6_prefix_match (src)) - return dst; - else - return NULL; - } -#endif /* HAVE_IPV6 */ - - if (CMD_IPV4 (dst)) - { - if (cmd_ipv4_match (src)) - return dst; - else - return NULL; - } - - if (CMD_IPV4_PREFIX (dst)) - { - if (cmd_ipv4_prefix_match (src)) - return dst; - else - return NULL; - } - - /* Optional or variable commands always match on '?' */ - if (CMD_OPTION (dst) || CMD_VARIABLE (dst)) - return dst; - - /* In case of 'command \t', given src is NULL string. */ - if (src == NULL) - return dst; - - if (strncmp (src, dst, strlen (src)) == 0) - return dst; - else - return NULL; -} - -/*------------------------------------------------------------------------------ - * Check same string element existence. - * - * Returns: 0 => found same string in the vector - * 1 => NOT found same string in the vector - */ -static bool -cmd_unique_string (vector v, const char *str) -{ - unsigned int i; - char *match; - - for (i = 0; i < vector_length (v); i++) - if ((match = vector_get_item (v, i)) != NULL) - if (strcmp (match, str) == 0) - return 0; - return 1; -} - -/* Compare string to description vector. If there is same string - return 1 else return 0. */ -static bool -desc_unique_string (vector v, const char *str) -{ - unsigned int i; - struct desc *desc; - - for (i = 0; i < vector_length (v); i++) - if ((desc = vector_get_item (v, i)) != NULL) - if (strcmp (desc->cmd, str) == 0) - return 1; - return 0; -} - -/*------------------------------------------------------------------------------ - * Special parsing for leading 'do', if current mode allows it. - * - * If finds a valid "do", sets current node and do_shortcut flag, and discards - * the "do" token. - * - * Returns: true <=> dealt with the "do" - * false => no do, or no do allowed. - */ -static bool -cmd_try_do_shortcut(cmd_parsed parsed) -{ - const char* ts ; - - if (parsed->cnode < MIN_DO_SHORTCUT_NODE) - return false ; - - ts = cmd_token_string(cmd_token_get(&parsed->tokens, 0)) ; - if (strcmp("do", ts) != 0) - return false ; - - parsed->cnode = ENABLE_NODE ; - parsed->do_shortcut = true ; - cmd_token_discard(cmd_token_shift(&parsed->tokens)) ; - - return true ; -} ; - -/*============================================================================== - * '?' describe command support. - */ - -/*------------------------------------------------------------------------------ - * Get description of current (partial) command - * - * Returns: NULL => no description available - * - * status set to CMD_ERR_NO_MATCH or CMD_ERR_AMBIGUOUS - * - * or: address of vector of "struct desc" values available. - * - * NB: when a vector is returned it is the caller's responsibility to - * vector_free() it. (The contents are all effectively const, so do not - * themselves need to be freed.) - */ -extern vector -cmd_describe_command (const char* line, node_type_t node, - cmd_return_code_t* status) -{ - vector ret ; - struct cmd_parsed parsed_s ; - cmd_parsed parsed ; - cmd_token_type_t tok_total ; - - /* Set up a parser object and tokenise the command line */ - parsed = cmd_parse_init_new(&parsed_s) ; - tok_total = cmd_tokenise(parsed, line, node) ; - - - - - - - - /* Stop immediately if line is empty apart from comment */ - if ((tok_total & ~cmd_tok_comment) == cmd_tok_null) - return CMD_EMPTY ; /* NB: parsed->cmd == NULL */ - - /* Level 1 parsing - * - * Strip quotes and escapes from all the tokens. - */ - if (tok_total != cmd_tok_simple) - { - ret = cmd_parse_phase_one(parsed) ; - if (ret != CMD_SUCCESS) - return ret ; - } ; - - /* If allowed to 'do', see if there. - * - * 'do' forces command to be parsed in ENABLE_NODE (if allowed) - */ - if (type & cmd_parse_do) - cmd_try_do_shortcut(parsed) ; - - - - - return cmd_describe_command_real (tokens, node, status); - - - - static vector - cmd_describe_command_real (vector tokens, int node, int *status) - { - unsigned int i; - vector cmd_vector; - #define INIT_MATCHVEC_SIZE 10 - vector matchvec; - struct cmd_element *cmd_element; - unsigned int index; - int ret; - enum match_type match; - char *command; - - /* Set index. */ - if (vector_length (tokens) == 0) - { - *status = CMD_ERR_NO_MATCH; - return NULL; - } - else - index = vector_length (tokens) - 1; - - /* Make copy vector of current node's command vector. */ - cmd_vector = vector_copy (cmd_node_vector (cmdvec, node)); - - /* Prepare match vector */ - matchvec = vector_init (INIT_MATCHVEC_SIZE); - - /* Filter commands. */ - /* Only words precedes current word will be checked in this loop. */ - for (i = 0; i < index; i++) - if ((command = vector_get_item (tokens, i))) - { - match = cmd_filter(command, cmd_vector, i, any_match) ; - - if (match == vararg_match) - { - struct cmd_element *cmd_element; - vector descvec; - unsigned int j, k; - - for (j = 0; j < vector_length (cmd_vector); j++) - if ((cmd_element = vector_get_item (cmd_vector, j)) != NULL - && (vector_length (cmd_element->strvec))) - { - descvec = vector_get_item (cmd_element->strvec, - vector_length (cmd_element->strvec) - 1); - for (k = 0; k < vector_length (descvec); k++) - { - struct desc *desc = vector_get_item (descvec, k); - vector_set (matchvec, desc); - } - } - - vector_set (matchvec, &desc_cr); - vector_free (cmd_vector); - - return matchvec; - } ; - - ret = is_cmd_ambiguous (command, cmd_vector, i, match) ; - if (ret != 0) - { - vector_free (cmd_vector); - vector_free (matchvec); - *status = (ret == 1) ? CMD_ERR_AMBIGUOUS - : CMD_ERR_NO_MATCH ; - return NULL ; - } ; - } - - /* Prepare match vector */ - /* matchvec = vector_init (INIT_MATCHVEC_SIZE); */ - - /* Make sure that cmd_vector is filtered based on current word */ - command = vector_get_item (tokens, index); - if (command) - match = cmd_filter(command, cmd_vector, index, any_match); - - /* Make description vector. */ - for (i = 0; i < vector_length (cmd_vector); i++) - { - vector strvec ; - - cmd_element = vector_get_item (cmd_vector, i) ; - if (cmd_element == NULL) - continue ; - - /* Ignore cmd_element if no tokens at index position. - * - * Deal with special case of possible completion. - */ - strvec = cmd_element->strvec; - if (index >= vector_length (strvec)) - { - if (command == NULL && index == vector_length (strvec)) - { - if (!desc_unique_string (matchvec, command_cr)) - vector_push_item(matchvec, &desc_cr); - } - continue ; - } ; - - /* Check if command is completed. */ - unsigned int j; - vector descvec = vector_get_item (strvec, index); - struct desc *desc; - - for (j = 0; j < vector_length (descvec); j++) - if ((desc = vector_get_item (descvec, j))) - { - const char *string; - - string = cmd_entry_function_desc (command, desc->cmd); - if (string) - { - /* Uniqueness check */ - if (!desc_unique_string (matchvec, string)) - vector_push_item(matchvec, desc); - } - } ; - } ; - - vector_free (cmd_vector); - - if (vector_length(matchvec) == 0) - { - vector_free (matchvec); - *status = CMD_ERR_NO_MATCH; - return NULL; - } - - *status = CMD_SUCCESS; - return matchvec; - } - - - - - - - - - - - - - - - - - - - cmd_parse_reset(parsed, false) ; - - return -} ; - -/*------------------------------------------------------------------------------ - * Check LCD of matched command. - * - * Scan list of matched keywords, and by comparing them pair-wise, find the - * longest common leading substring. - * - * Returns: 0 if zero or one matched keywords - * length of longest common leading substring, otherwise. - */ -static int -cmd_lcd (vector matchvec) -{ - int n ; - int i ; - int lcd ; - char *sp, *sq, *ss ; - - n = vector_end(matchvec) ; - if (n < 2) - return 0 ; - - ss = vector_get_item(matchvec, 0) ; - lcd = strlen(ss) ; - - for (i = 1 ; i < n ; i++) - { - sq = ss ; - ss = vector_get_item(matchvec, i) ; - sp = ss ; - - while ((*sp == *sq) && (*sp != '\0')) - { - ++sp ; - ++sq ; - } ; - - if (lcd > (sp - ss)) - lcd = (sp - ss) ; - } - return lcd; -} - -/*------------------------------------------------------------------------------ - * Command line completion support. - */ -static vector -cmd_complete_command_real (vector tokens, int node, int *status) -{ - unsigned int i; - unsigned int ivl ; - unsigned int last_ivl ; - vector cmd_v ; -#define INIT_MATCHVEC_SIZE 10 - vector matchvec; - struct cmd_element *cmd_element; - unsigned int index; - struct desc *desc; - vector descvec; - char *token; - int n ; - - /* Stop immediately if the tokens is empty. */ - if (vector_length (tokens) == 0) - { - *status = CMD_ERR_NO_MATCH; - return NULL; - } - - /* Take (shallow) copy of cmdvec for given node. */ - cmd_v = vector_copy (cmd_node_vector (cmdvec, node)); - - /* First, filter upto, but excluding last token */ - last_ivl = vector_length (tokens) - 1; - - for (ivl = 0; ivl < last_ivl; ivl++) - { - enum match_type match; - int ret; - - /* TODO: does this test make any sense ? */ - if ((token = vector_get_item (tokens, ivl)) == NULL) - continue ; - - /* First try completion match, return best kind of match */ - index = ivl ; - match = cmd_filter_by_completion (token, cmd_v, index) ; - - /* Eliminate all but the selected kind of match */ - ret = is_cmd_ambiguous (token, cmd_v, index, match) ; - - if (ret == 1) - { - /* ret == 1 => either token matches more than one keyword - * or token matches more than one number range - */ - vector_free (cmd_v); - *status = CMD_ERR_AMBIGUOUS; - return NULL; - } -#if 0 - /* For command completion purposes do not appear to care about - * incomplete ipv4 or ipv6 prefixes (missing '/' or digits after). - */ - else if (ret == 2) - { - vector_free (cmd_v); - *status = CMD_ERR_NO_MATCH; - return NULL; - } -#endif - } - - /* Prepare match vector. */ - matchvec = vector_init (INIT_MATCHVEC_SIZE); - - /* Now we got into completion */ - index = last_ivl ; - token = vector_get_item(tokens, last_ivl) ; /* is now the last token */ - - for (i = 0; i < vector_length (cmd_v); i++) - { - unsigned int j; - const char *string; - - if ((cmd_element = vector_get_item (cmd_v, i)) == NULL) - continue ; - - descvec = vector_get_item (cmd_element->strvec, index); - if (descvec == NULL) - continue ; - - for (j = 0; j < vector_length (descvec); j++) - { - desc = vector_get_item (descvec, j) ; - if (desc == NULL) - continue ; - - string = cmd_entry_function(token, desc->cmd) ; - if ((string != NULL) && cmd_unique_string(matchvec, string)) - cmd_add_to_strvec (matchvec, string) ; - } ; - } ; - - n = vector_length(matchvec) ; /* number of entries in the matchvec */ - - /* We don't need cmd_v any more. */ - vector_free (cmd_v); - - /* No matched command */ - if (n == 0) - { - vector_free (matchvec); - - /* In case of 'command \t' pattern. Do you need '?' command at - the end of the line. */ - if (*token == '\0') - *status = CMD_COMPLETE_ALREADY; - else - *status = CMD_ERR_NO_MATCH; - return NULL; - } - - /* Only one matched */ - if (n == 1) - { - *status = CMD_COMPLETE_FULL_MATCH; - return matchvec ; - } - - /* Check LCD of matched strings. */ - if (token != NULL) - { - unsigned lcd = cmd_lcd (matchvec) ; - - if (lcd != 0) - { - if (strlen(token) < lcd) - { - char *lcdstr; - - lcdstr = XMALLOC (MTYPE_STRVEC, lcd + 1); - memcpy (lcdstr, vector_get_item(matchvec, 0), lcd) ; - lcdstr[lcd] = '\0'; - - cmd_free_strvec(matchvec) ; /* discard the match vector */ - - matchvec = vector_init (1); - vector_push_item(matchvec, lcdstr) ; - - *status = CMD_COMPLETE_MATCH; - return matchvec ; - } - } - } - - *status = CMD_COMPLETE_LIST_MATCH; - return matchvec ; -} - -/*------------------------------------------------------------------------------ - * Can the current command be completed ? - */ -extern vector -cmd_complete_command (vector tokens, int node, int *status) -{ - vector ret; - - if ( cmd_try_do_shortcut(node, vector_get_item(tokens, 0) ) ) - { - vector shifted_tokens; - unsigned int index; - - /* We can try it on enable node, cos' the vty is authenticated */ - - shifted_tokens = vector_init (vector_count(tokens)); - /* use memcpy? */ - for (index = 1; index < vector_length (tokens); index++) - { - vector_set_index (shifted_tokens, index-1, - vector_lookup(tokens, index)) ; - } - - ret = cmd_complete_command_real (shifted_tokens, ENABLE_NODE, status); - - vector_free(shifted_tokens); - return ret; - } - - return cmd_complete_command_real (tokens, node, status); -} - -/*------------------------------------------------------------------------------ - * Return parent node - * - * All nodes > CONFIG_NODE are descended from CONFIG_NODE - */ -enum node_type -node_parent ( enum node_type node ) -{ - assert (node > CONFIG_NODE); - - switch (node) - { - case BGP_VPNV4_NODE: - case BGP_IPV4_NODE: - case BGP_IPV4M_NODE: - case BGP_IPV6_NODE: - case BGP_IPV6M_NODE: - return BGP_NODE; - - case KEYCHAIN_KEY_NODE: - return KEYCHAIN_NODE; - - default: - return CONFIG_NODE; - } ; -} ; - -/*============================================================================== - * Parsing of command lines - */ - -/*------------------------------------------------------------------------------ - * Parse a command in the given "node", if possible, ready for execution. - * - * If 'strict': use cmd_filter_by_string() - * otherwise: use cmd_filter_by_completion() - * - * If 'do': see if there is a 'do' at the front and proceed accordingly. - * - * If 'tree': move up the node tree to find command if not found in the - * current node. - */ - -static enum cmd_return_code cmd_parse_phase_one(cmd_parsed parsed) ; -static enum cmd_return_code cmd_parse_phase_two(struct cmd_parsed* parsed, - bool strict) ; - -/*------------------------------------------------------------------------------ - * Parse a command in the given "node", or (if required) any of its ancestors. - * - * Returns: CMD_SUCCESS => successfully parsed command, and the result is - * in the given parsed structure, ready for execution. - * - * NB: parsed->cnode may have changed. - * - * NB: parsed->cmd->daemon => daemon - * - * CMD_EMPTY => empty or comment line - * - * NB: parsed->cmd == NULL - * - * CMD_SUCCESS_DAEMON => parsed successfully. Something for vtysh ?? - * - * CMD_ERR_NO_MATCH ) - * CMD_ERR_AMBIGUOUS ) failed to parse - * CMD_ERR_INCOMPLETE ) - * - * NB: if has failed to parse in the current node - * and in any ancestor nodes, returns the error - * from the attempt to parse in the current node - * (parsed->cnode which is returned unchanged). - * - * NB: the command line MUST be preserved until the parsed command is no - * longer required -- no copy is made. - * - * NB: expects to have free run of everything in the vty structure (except - * the contents of the vty_io sub-structure) until the command completes. - * - * See elsewhere for description of parsed structure. - */ -extern enum cmd_return_code -cmd_parse_command(struct vty* vty, cmd_parse_type_t type) -{ - enum cmd_return_code ret ; - enum cmd_return_code first_ret ; - cmd_parsed parsed ; - cmd_token_type_t tok_total ; - bool varflag ; - unsigned int i, ivl ; - - /* Initialise the parsed structure -- assuming no 'do' */ - if (vty->parsed == NULL) - parsed = vty->parsed = cmd_parse_init_new(NULL) ; - else - { - parsed = vty->parsed ; - - parsed->cmd = NULL ; - parsed->do_shortcut = false ; - - if (parsed->pipes != cmd_pipe_none) - { - parsed->pipes = cmd_pipe_none ; - cmd_empty_parsed_tokens(parsed) ; - } ; - } ; - - /* Parse the line into tokens, set parsed->line, ->cnode & ->onode */ - tok_total = cmd_tokenise(parsed, vty->buf, vty->node) ; - - /* Stop immediately if line is empty apart from comment */ - if ((tok_total & ~cmd_tok_comment) == cmd_tok_null) - return CMD_EMPTY ; /* NB: parsed->cmd == NULL */ - - /* Level 1 parsing - * - * Strip quotes and escapes from all the tokens. - */ - if (tok_total != cmd_tok_simple) - { - ret = cmd_parse_phase_one(parsed) ; - if (ret != CMD_SUCCESS) - return ret ; - } ; - - /* If allowed to 'do', see if there. + /* A cmd_command may appear in a number of cmd_vectors, but the cmd->items + * etc. need only be set up once. * - * 'do' forces command to be parsed in ENABLE_NODE (if allowed) - */ - if (type & cmd_parse_do) - cmd_try_do_shortcut(parsed) ; - - /* Level 2 parsing - * Try in the current node - */ - ret = cmd_parse_phase_two(parsed, type) ; - - if (ret != CMD_SUCCESS) - { - if (((type & cmd_parse_tree) == 0) || parsed->do_shortcut) - return ret ; /* done if not allowed to walk tree - or just tried to parse a 'do' */ - - /* Try in parent node(s) */ - first_ret = ret ; - - while (ret != CMD_SUCCESS) - { - if (parsed->cnode <= CONFIG_NODE) - { - parsed->cnode = parsed->onode ; /* restore node state */ - return first_ret ; /* return original result */ - } ; - - parsed->cnode = node_parent(parsed->cnode) ; - ret = cmd_parse_phase_two(parsed, type) ; - } ; - } ; - - /* Parsed successfully -- construct the arg_vector */ - - varflag = false ; - ivl = vector_length(parsed->cmd->strvec) ; - - cmd_arg_vector_empty(parsed) ; - for (i = 0; i < ivl ; i++) - { - bool take = varflag ; - - if (!varflag) - { - vector descvec = vector_get_item (parsed->cmd->strvec, i); - - if (vector_length (descvec) == 1) - { - struct desc *desc = vector_get_item (descvec, 0); - - if (CMD_VARARG (desc->cmd)) - take = varflag = true ; - else - take = (CMD_VARIABLE (desc->cmd) || CMD_OPTION (desc->cmd)) ; - } - else - take = true ; - } - - if (take) - cmd_arg_vector_push(parsed, - cmd_token_value(cmd_token_get(&parsed->tokens, i))) ; - } ; - - /* Return appropriate form of success */ - return parsed->cmd->daemon ? CMD_SUCCESS_DAEMON - : CMD_SUCCESS ; -} ; - -/*------------------------------------------------------------------------------ - * Phase 1 of command parsing - * - * * At start of line look for: - * - * * '<' -- pipe-in command of some sort - * - * Sets the pipe type and the read_pipe_tokens -- all tokens up to - * '>', '|', '!' or '#'.. - * - * Scan for '>', '|', '!' or '#' - * - * * '>' or '|' -- pipe-out command of some sort - * - * Collect type of pipe, and then all tokens up to '!' or '#' - * - * - * - * * '!', '#', comment -- discards - * - * Returns: CMD_SUCCESS -- parsed successfully - * CMD_ERR_NO_MATCH ) - * CMD_ERR_AMBIGUOUS ) failed to parse - * CMD_ERR_INCOMPLETE ) - */ -static enum cmd_return_code -cmd_parse_phase_one(cmd_parsed parsed) -{ - - - - - - -} ; - -/*------------------------------------------------------------------------------ - * Phase 2 of command parsing - * - * Takes a parsed structure, with the: - * - * cnode -- node to parse in - * tokens -- the line broken into words - * do_shortcut -- true if first word is 'do' (to be ignored) - * - * and parses either strictly or with command completion. - * - * Returns: CMD_SUCCESS -- parsed successfully - * CMD_ERR_NO_MATCH ) - * CMD_ERR_AMBIGUOUS ) failed to parse - * CMD_ERR_INCOMPLETE ) - */ -static enum cmd_return_code -cmd_parse_phase_two(cmd_parsed parsed, cmd_parse_type_t type) -{ - unsigned int i ; - unsigned int ivl ; - unsigned index ; - vector cmd_v; - struct cmd_element *cmd_element; - struct cmd_element *matched_element; - unsigned int matched_count, incomplete_count; - enum match_type match ; - enum match_type filter_level ; - const char *command; - - /* Need number of tokens */ - ivl = cmd_token_count(&parsed->tokens) ; - - /* Make copy of command elements. */ - cmd_v = vector_copy (cmd_node_vector (cmdvec, parsed->cnode)); - - /* Look for an unambiguous result */ - filter_level = (type & cmd_parse_strict) ? exact_match : any_match ; - match = no_match ; /* in case of emptiness */ - for (index = 0 ; index < ivl; index++) - { - int ret ; - - command = cmd_token_string(cmd_token_get(&parsed->tokens, index)) ; - - match = cmd_filter(command, cmd_v, index, filter_level) ; - - if (match == vararg_match) - break; - - ret = is_cmd_ambiguous (command, cmd_v, index, match); - - if (ret != 0) - { - assert((ret == 1) || (ret == 2)) ; - vector_free (cmd_v); - return (ret == 1) ? CMD_ERR_AMBIGUOUS : CMD_ERR_NO_MATCH ; - } - } ; - - /* Check matched count. */ - matched_element = NULL; - matched_count = 0; - incomplete_count = 0; - - for (i = 0; i < vector_length(cmd_v); i++) - { - cmd_element = vector_get_item(cmd_v, i) ; - if (cmd_element == NULL) - continue ; - - if (match == vararg_match || index >= cmd_element->cmdsize) - { - matched_element = cmd_element; -#if 0 - printf ("DEBUG: %s\n", cmd_element->string); -#endif - matched_count++; - } - else - { - incomplete_count++; - } - } ; - - /* Finished with cmd_v. */ - vector_free (cmd_v); - - /* To execute command, matched_count must be 1. */ - if (matched_count != 1) - { - if (matched_count == 0) - return (incomplete_count) ? CMD_ERR_INCOMPLETE : CMD_ERR_NO_MATCH ; - else - return CMD_ERR_AMBIGUOUS ; - } ; - - /* Everything checks out... ready to execute command */ - parsed->cmd = matched_element ; - - return CMD_SUCCESS ; -} ; - -/*------------------------------------------------------------------------------ - * Dispatch a parsed command. - * - * Returns: command return code. NB: may be CMD_QUEUED (unless no_queue). - * - * NB: expects to have free run of everything in the vty structure (except - * the contents of the vty_io sub-structure) until the command completes. - */ -extern enum cmd_return_code -cmd_dispatch(struct vty* vty, bool no_queue) -{ - cmd_parsed parsed = vty->parsed ; - enum cmd_return_code ret ; - - if (parsed->cmd == NULL) - return CMD_SUCCESS ; /* NULL commands are easy */ - - vty->node = parsed->cnode ; - - if (no_queue || !vty_cli_nexus) - { - ret = cmd_dispatch_call(vty) ; - cmd_post_command(vty, ret) ; - } - else - { - /* Don't do it now, but send to bgp qpthread */ - if (parsed->cmd->attr & CMD_ATTR_CALL) - cq_enqueue(vty, vty_cli_nexus) ; - else - cq_enqueue(vty, vty_cmd_nexus) ; - - ret = CMD_QUEUED ; - } ; - - return ret ; -} ; - -/*------------------------------------------------------------------------------ - * Tidy up after executing command. - * - * This is separated out so that can be called when queued command completes. - * - * If have just processed a "do" shortcut command, and it has not set the - * vty->node to something other than ENABLE_NODE, then restore to the original - * state. - * - * Arguments: ret = CMD_XXXX -- NB: CMD_QUEUED => command revoked - */ -extern void -cmd_post_command(struct vty* vty, int ret) -{ - if (vty->parsed->do_shortcut) - { - if (vty->node == ENABLE_NODE) - vty->node = vty->parsed->onode ; - } ; -} ; - -/*------------------------------------------------------------------------------ - * Parse and execute a command. - * - * The command is given by vty->buf and vty->node. - * - * Uses vty->parsed. - * - * -- use strict/completion parsing, as required. - * - * -- parse in current node and in ancestors, as required - * - * If does not find in any ancestor, return error from current node. - * - * -- implement the "do" shortcut, as required - * - * If qpthreads_enabled, then may queue the command rather than execute it - * here. - * - * The vty->node may be changed during the execution of the command, and may - * be returned changed once the command has completed. - * - * NB: expects to have free run of everything in the vty structure (except - * the contents of the vty_io sub-structure) until the command completes. - */ -extern enum cmd_return_code -cmd_execute_command(struct vty *vty, - enum cmd_parse_type type, struct cmd_element **cmd) -{ - enum cmd_return_code ret ; - - /* Try to parse in vty->node or, if required, ancestors thereof. */ - ret = cmd_parse_command(vty, type) ; - - if (cmd != NULL) - *cmd = vty->parsed->cmd ; /* for vtysh */ - - if (ret == CMD_SUCCESS) - ret = cmd_dispatch(vty, cmd_may_queue) ; - else if (ret == CMD_EMPTY) - ret = CMD_SUCCESS ; - - return ret ; -} ; - -/*------------------------------------------------------------------------------ - * Read configuration from file. - * - * In the qpthreads world this assumes that it is running with the vty - * locked, and that all commands are to be executed directly. - * - * If the 'first_cmd' argument is not NULL it is the address of the first - * command that is expected to appear. If the first command is not this, then - * the 'first_cmd' is called with argv == NULL (and argc == 0) to signal the - * command is being invoked by default. - * - * Command processing continues while CMD_SUCCESS is returned by the command - * parser and command execution. - * - * If 'ignore_warning' is set, then any CMD_WARNING returned by command - * execution is converted to CMD_SUCCESS. Note that any CMD_WARNING returned - * by command parsing (or in execution of any default 'first_cmd'). - * - * Returns: cmd_return_code for last command - * vty->buf is last line processed - * vty->lineno is number of last line processed (1 is first) - * - * If the file is empty, will return CMD_SUCCESS. - * - * Never returns CMD_EMPTY -- that counts as CMD_SUCCESS. - * - * If - * - * If return code is not CMD_SUCCESS, the the output buffering contains the - * output from the last command attempted. - */ -extern enum cmd_return_code -config_from_file (struct vty *vty, FILE *fp, struct cmd_element* first_cmd, - qstring buf, bool ignore_warning) -{ - enum cmd_return_code ret; - - vty->buf = buf->body ; - vty->lineno = 0 ; - - ret = CMD_SUCCESS ; /* in case file is empty */ - vty_out_clear(vty) ; - - while (fgets (buf->body, buf->size, fp)) - { - ++vty->lineno ; - - /* Execute configuration command : this is strict match */ - ret = cmd_parse_command(vty, cmd_parse_strict + cmd_parse_tree) ; - - if (ret == CMD_EMPTY) - continue ; /* skip empty/comment */ - - if (ret != CMD_SUCCESS) - break ; /* stop on *any* parsing issue */ - - /* special handling before of first command */ - if (first_cmd != NULL) - { - if (first_cmd != vty->parsed->cmd) - { - ret = (*first_cmd->func)(first_cmd, vty, 0, NULL) ; - if (ret != CMD_SUCCESS) - break ; /* stop on *any* issue with "default" */ - } ; - first_cmd = NULL ; - } - - /* Standard command handling */ - ret = cmd_dispatch(vty, cmd_no_queue) ; - - if (ret != CMD_SUCCESS) - { - /* Ignore CMD_WARNING if required - * - * Ignore CMD_CLOSE at all times. - */ - if ( ((ret == CMD_WARNING) && ignore_warning) - || (ret == CMD_CLOSE) ) - ret = CMD_SUCCESS ; /* in case at EOF */ - else - break ; /* stop */ - } ; - - vty_out_clear(vty) ; - } ; - - if (ret == CMD_EMPTY) - ret = CMD_SUCCESS ; /* OK if end on empty line */ - - return ret ; -} ; - -/*============================================================================== - */ - -static cmd_return_code_t cmd_fetch_command(struct vty* vty) ; - -/*------------------------------------------------------------------------------ - * Command Loop - * - * Read and dispatch commands until can no longer do so for whatever reason: - * - * - reached end of command stream -- CMD_CLOSE - * - encounter error of some kind -- CMD_WARNING, CMD_ERROR, etc - * - waiting for input to arrive -- CMD_WAIT_INPUT - * - waiting for command to complete -- CMD_QUEUED - * - waiting for output to complete -- ?? - * - */ -extern cmd_return_code_t -cmd_command_loop(struct vty *vty, struct cmd_element* first_cmd, - qstring buf, bool ignore_warning) -{ - cmd_return_code_t ret ; - - vty->buf = buf->body ; - vty->lineno = 0 ; - - - /* + * It is assumed that once a cmd_command has been installed it will never be + * changed ! * - */ - - vty_out_clear(vty) ; - - while (1) { - - /* Fetch a command line */ - - ret = cmd_fetch_command(vty) ; - - if (ret != CMD_SUCCESS) - break ; - - ++vty->lineno ; - - /* Parse the command line we now have */ - - ret = cmd_parse_command(vty, cmd_parse_strict + cmd_parse_tree) ; - - if (ret == CMD_EMPTY) - continue ; /* skip empty/comment */ - - if (ret != CMD_SUCCESS) - break ; /* stop on *any* parsing issue */ - - /* special handling before of first command */ - if (first_cmd != NULL) - { - if (first_cmd != vty->parsed->cmd) - { - ret = (*first_cmd->func)(first_cmd, vty, 0, NULL) ; - if (ret != CMD_SUCCESS) - break ; /* stop on *any* issue with "default" */ - } ; - first_cmd = NULL ; - } ; + * Need now to "compile" the command if not already compiled. + */ + if (cmd->items == NULL) + cmd_compile(cmd); - /* Reflect command line if required */ + /* Post compilation check for reasonable cmd_command ! */ + cmd_compile_check(cmd) ; +} ; - /* Standard command handling */ +/*============================================================================== + * Password encryption + */ +static const unsigned char itoa64[] = + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - ret = cmd_dispatch(vty, cmd_no_queue) ; +/* Uses the usual crypt() function. + * + * Note that crypt() is not thread safe ! + */ +static const char * +zencrypt (const char *passwd) +{ + uint32_t r ; + char salt[3]; - if (ret == CMD_QUEUED) - break ; + extern char *crypt (const char *, const char *) ; - /* Output Handling..... */ + r = qt_random(*passwd) ; + salt[0] = itoa64[(r >> (32 - 5)) & 0x3F] ; /* ms 5 */ + salt[1] = itoa64[(r >> (32 - 10)) & 0x3F] ; /* next ms 5 */ + salt[2] = '\0'; - /* Return code handling.... */ + return crypt(passwd, salt) ; +} - if (ret != CMD_SUCCESS) - { - if ((ret == CMD_WARNING) && !ignore_warning) - break ; - if (ret != CMD_CLOSE) - break ; - } ; +/* This function write configuration of this host. */ +static int +config_write_host (struct vty *vty) +{ + VTY_LOCK() ; - vty_out_clear(vty) ; - } ; + if (qpthreads_enabled) + vty_out (vty, "threaded%s", VTY_NEWLINE); - return ret ; -} ; + if (host.name_set) + vty_out (vty, "hostname %s%s", host.name, VTY_NEWLINE); + if (host.password != NULL) + { + if (host.password_encrypted) + vty_out (vty, "password 8 %s\n", host.password); + else + vty_out (vty, "password %s\n", host.password); + } ; -/*------------------------------------------------------------------------------ - * Fetch the next command. - * - * - * - * - */ -static cmd_return_code_t -cmd_fetch_command(struct vty* vty) -{ + if (host.enable != NULL) + { + if (host.enable_encrypted) + vty_out (vty, "enable password 8 %s\n", host.enable); + else + vty_out (vty, "enable password %s\n", host.enable); + } ; + if (zlog_get_default_lvl(NULL) != LOG_DEBUG) + { + 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); + } + if (host.logfile && (zlog_get_maxlvl(NULL, ZLOG_DEST_FILE) != ZLOG_DISABLED)) + { + vty_out (vty, "log file %s", host.logfile); + if (zlog_get_maxlvl(NULL, ZLOG_DEST_FILE) != zlog_get_default_lvl(NULL)) + vty_out (vty, " %s", + zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_FILE)]); + vty_out (vty, "%s", VTY_NEWLINE); + } + if (zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT) != ZLOG_DISABLED) + { + vty_out (vty, "log stdout"); + if (zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT) != zlog_get_default_lvl(NULL)) + vty_out (vty, " %s", + zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT)]); + vty_out (vty, "%s", VTY_NEWLINE); + } -} ; + if (zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR) == ZLOG_DISABLED) + vty_out(vty,"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); + if (zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG) != ZLOG_DISABLED) + { + vty_out (vty, "log syslog"); + if (zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG) != zlog_get_default_lvl(NULL)) + vty_out (vty, " %s", + zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG)]); + vty_out (vty, "%s", VTY_NEWLINE); + } + if (zlog_get_facility(NULL) != LOG_DAEMON) + vty_out (vty, "log facility %s%s", + facility_name(zlog_get_facility(NULL)), VTY_NEWLINE); + if (zlog_get_record_priority(NULL) == 1) + vty_out (vty, "log record-priority%s", VTY_NEWLINE); + if (zlog_get_timestamp_precision(NULL) > 0) + vty_out (vty, "log timestamp precision %d%s", + zlog_get_timestamp_precision(NULL), VTY_NEWLINE); + if (host.advanced) + vty_out (vty, "service advanced-vty%s", VTY_NEWLINE); + if (host.encrypt) + vty_out (vty, "service password-encryption%s", VTY_NEWLINE); + if (host.lines >= 0) + vty_out (vty, "service terminal-length %d%s", host.lines, + VTY_NEWLINE); + if (host.motdfile) + vty_out (vty, "banner motd file %s%s", host.motdfile, VTY_NEWLINE); + else if (! host.motd) + vty_out (vty, "no banner motd%s", VTY_NEWLINE); + VTY_UNLOCK() ; + return 1; +} /*============================================================================*/ @@ -2364,11 +859,10 @@ DEFUN_CALL (enable, "Turn on privileged mode command\n") { /* If enable password is NULL, change to ENABLE_NODE */ - if ((host.enable == NULL && host.enable_encrypt == NULL) || - vty_shell_server(vty)) - vty_set_node(vty, ENABLE_NODE); + if ((host.enable == NULL) || (vty->type == VTY_SHELL_SERVER)) + vty->node = ENABLE_NODE ; else - vty_set_node(vty, AUTH_ENABLE_NODE); + vty->node = AUTH_ENABLE_NODE ; return CMD_SUCCESS; } @@ -2379,8 +873,8 @@ DEFUN_CALL (disable, "disable", "Turn off privileged mode command\n") { - if (vty_get_node(vty) == ENABLE_NODE) - vty_set_node(vty, VIEW_NODE); + if (vty->node == ENABLE_NODE) + vty->node = VIEW_NODE; return CMD_SUCCESS; } @@ -2390,7 +884,7 @@ DEFUN_CALL (config_exit, "exit", "Exit current mode and down to previous mode\n") { - return vty_cmd_exit(vty) ; + return cmd_exit(vty) ; } /* quit is alias of exit. */ @@ -2403,9 +897,9 @@ ALIAS_CALL (config_exit, DEFUN_CALL (config_end, config_end_cmd, "end", - "End current mode and change to enable mode.") + "End current mode and change to enable mode\n") { - return vty_cmd_end(vty) ; + return cmd_end(vty) ; } /* Show version. */ @@ -2453,8 +947,8 @@ DEFUN_CALL (config_list, "Print command list\n") { unsigned int i; - struct cmd_node *cnode = vector_get_item (cmdvec, vty_get_node(vty)); - struct cmd_element *cmd; + struct cmd_node *cnode = vector_get_item (node_vector, vty->node); + struct cmd_command *cmd; for (i = 0; i < vector_length (cnode->cmd_vector); i++) if ((cmd = vector_get_item (cnode->cmd_vector, i)) != NULL @@ -2481,7 +975,7 @@ DEFUN (config_write_file, int ret = CMD_WARNING; /* Check and see if we are operating under vtysh configuration */ - if (host.config == NULL) + if (host.config_file == NULL) { vty_out (vty, "Can't save to configuration file, using vtysh.%s", VTY_NEWLINE); @@ -2489,7 +983,7 @@ DEFUN (config_write_file, } /* Get filename. */ - config_file = host.config; + config_file = host.config_file; config_file_sav = XMALLOC (MTYPE_TMP, strlen (config_file) + strlen (CONF_BACKUP_EXT) + 1); @@ -2504,8 +998,7 @@ DEFUN (config_write_file, fd = mkstemp (config_file_tmp); if (fd < 0) { - vty_out (vty, "Can't open configuration file %s.%s", config_file_tmp, - VTY_NEWLINE); + vty_out (vty, "Can't open configuration file %s.\n", config_file_tmp) ; goto finished; } @@ -2517,15 +1010,18 @@ DEFUN (config_write_file, vty_time_print (vty, 1); vty_out (vty, "!\n"); - for (i = 0; i < vector_length (cmdvec); i++) - if ((node = vector_get_item (cmdvec, i)) && node->func) - { - if ((*node->func) (vty)) - vty_out (vty, "!\n"); - } + for (i = 0; i < vector_length (node_vector); i++) + { + if ((node = vector_get_item (node_vector, i)) && node->config_write) + { + if ((*node->config_write) (vty)) + vty_out (vty, "!\n"); + + vty_cmd_out_push(vty) ; /* Push stuff so far */ + } + } ; err = vty_close_config_write(vty) ; - close(fd) ; if (err != 0) { @@ -2611,31 +1107,34 @@ DEFUN (config_write_terminal, "Write to terminal\n") { unsigned int i; - struct cmd_node *node; + bool vtysh_config ; - if (vty_shell_serv(vty)) - { - for (i = 0; i < vector_length (cmdvec); i++) - if ((node = vector_get_item (cmdvec, i)) && node->func && node->vtysh) - { - if ((*node->func) (vty)) - vty_out (vty, "!%s", VTY_NEWLINE); - } - } - else + vtysh_config = (vty->type == VTY_SHELL_SERVER) ; + + if (!vtysh_config) + vty_out (vty, "\n" + "Current configuration:\n" + "!\n") ; + + for (i = 0 ; i < vector_length(node_vector) ; i++) { - vty_out (vty, "%sCurrent configuration:%s", VTY_NEWLINE, - VTY_NEWLINE); - vty_out (vty, "!%s", VTY_NEWLINE); - - for (i = 0; i < vector_length (cmdvec); i++) - if ((node = vector_get_item (cmdvec, i)) && node->func) - { - if ((*node->func) (vty)) - vty_out (vty, "!%s", VTY_NEWLINE); - } - vty_out (vty, "end%s",VTY_NEWLINE); - } + cmd_node node ; + + node = vector_get_item(node_vector, i) ; + + if (node == NULL) + continue ; + + if (vtysh_config && !node->config_to_vtysh) + continue ; + + if ((*node->config_write != NULL) && ((*node->config_write)(vty) != 0)) + vty_out (vty, "!\n") ; + } ; + + if (!vtysh_config) + vty_out (vty, "end\n") ; + return CMD_SUCCESS; } @@ -2656,11 +1155,11 @@ DEFUN (show_startup_config, char buf[BUFSIZ]; FILE *confp; - confp = fopen (host.config, "r"); + confp = fopen (host.config_file, "r"); if (confp == NULL) { vty_out (vty, "Can't open configuration file [%s]%s", - host.config, VTY_NEWLINE); + host.config_file, VTY_NEWLINE); return CMD_WARNING; } @@ -2689,21 +1188,11 @@ DEFUN_CALL (config_hostname, { if (!isalpha((int) *argv[0])) { - vty_out (vty, "Please specify string starting with alphabet%s", VTY_NEWLINE); + vty_out (vty, "Please specify string starting with alphabet\n"); return CMD_WARNING; } - VTY_LOCK() ; - - if (host.name) - XFREE (MTYPE_HOST, host.name); - - host.name = XSTRDUP (MTYPE_HOST, argv[0]); - uty_set_host_name(host.name) ; - - VTY_UNLOCK() ; - - return CMD_SUCCESS; + return cmd_set_host_name(argv[0]) ; } DEFUN_CALL (config_no_hostname, @@ -2713,26 +1202,18 @@ DEFUN_CALL (config_no_hostname, "Reset system's network name\n" "Host name of this router\n") { - VTY_LOCK() ; - - if (host.name) - XFREE (MTYPE_HOST, host.name); - host.name = NULL; - uty_set_host_name(host.name) ; - - VTY_UNLOCK() ; - - return CMD_SUCCESS; + return cmd_set_host_name(NULL) ; } -/* VTY interface password set. */ -DEFUN_CALL (config_password, password_cmd, - "password (8|) WORD", - "Assign the terminal connection password\n" - "Specifies a HIDDEN password will follow\n" - "dummy string \n" - "The HIDDEN line password string\n") +/*------------------------------------------------------------------------------ + * Password setting function -- common for password and enable password. + */ +static cmd_return_code_t +do_set_password(vty vty, int argc, argv_t argv, char** p_password, + bool* p_encrypted) { + cmd_return_code_t ret ; + /* Argument check. */ if (argc == 0) { @@ -2740,47 +1221,63 @@ DEFUN_CALL (config_password, password_cmd, return CMD_WARNING; } + VTY_LOCK() ; + ret = CMD_SUCCESS ; + if (argc == 2) { + /* Encrypted password argument */ + if (*argv[0] == '8') - { - if (host.password) - XFREE (MTYPE_HOST, host.password); - host.password = NULL; - if (host.password_encrypt) - XFREE (MTYPE_HOST, host.password_encrypt); - host.password_encrypt = XSTRDUP (MTYPE_HOST, argv[1]); - return CMD_SUCCESS; - } + { + XFREE(MTYPE_HOST, *p_password); + *p_encrypted = true ; + *p_password = XSTRDUP(MTYPE_HOST, argv[1]); + } else - { - vty_out (vty, "Unknown encryption type.%s", VTY_NEWLINE); - return CMD_WARNING; - } + { + vty_out(vty, "Unknown encryption type.\n"); + ret = CMD_WARNING; + } } - - if (!isalnum ((int) *argv[0])) + else { - vty_out (vty, - "Please specify string starting with alphanumeric%s", VTY_NEWLINE); - return CMD_WARNING; - } + /* Plaintext password argument */ - if (host.password) - XFREE (MTYPE_HOST, host.password); - host.password = NULL; + if (!isalnum ((int) *argv[0])) + { + vty_out(vty, "Please specify string starting with alphanumeric\n"); + ret = CMD_WARNING ; + } + else + { + /* If host.encrypt, only keeps the encrypted password. */ - if (host.encrypt) - { - if (host.password_encrypt) - XFREE (MTYPE_HOST, host.password_encrypt); - host.password_encrypt = XSTRDUP (MTYPE_HOST, zencrypt (argv[0])); - } - else - host.password = XSTRDUP (MTYPE_HOST, argv[0]); + XFREE (MTYPE_HOST, *p_password); - return CMD_SUCCESS; -} + *p_encrypted = host.encrypt ; + if (*p_encrypted) + *p_password = XSTRDUP (MTYPE_HOST, zencrypt (argv[0])); + else + *p_password = XSTRDUP (MTYPE_HOST, argv[0]); + } ; + } ; + + VTY_UNLOCK() ; + return ret ; +} ; + +/* VTY interface password set. */ +DEFUN_CALL (config_password, password_cmd, + "password (8) WORD", + "Assign the terminal connection password\n" + "Specifies a HIDDEN password will follow\n" + "dummy string \n" + "The HIDDEN line password string\n") +{ + return do_set_password(vty, argc, argv, &host.password, + &host.password_encrypted) ; +} ; ALIAS_CALL (config_password, password_text_cmd, "password LINE", @@ -2789,65 +1286,16 @@ ALIAS_CALL (config_password, password_text_cmd, /* VTY enable password set. */ DEFUN_CALL (config_enable_password, enable_password_cmd, - "enable password (8|) WORD", + "enable password (8) WORD", "Modify enable password parameters\n" "Assign the privileged level password\n" "Specifies a HIDDEN password will follow\n" "dummy string \n" "The HIDDEN 'enable' password string\n") { - /* Argument check. */ - if (argc == 0) - { - vty_out (vty, "Please specify password.%s", VTY_NEWLINE); - return CMD_WARNING; - } - - /* Crypt type is specified. */ - if (argc == 2) - { - if (*argv[0] == '8') - { - if (host.enable) - XFREE (MTYPE_HOST, host.enable); - host.enable = NULL; - - if (host.enable_encrypt) - XFREE (MTYPE_HOST, host.enable_encrypt); - host.enable_encrypt = XSTRDUP (MTYPE_HOST, argv[1]); - - return CMD_SUCCESS; - } - else - { - vty_out (vty, "Unknown encryption type.%s", VTY_NEWLINE); - return CMD_WARNING; - } - } - - if (!isalnum ((int) *argv[0])) - { - vty_out (vty, - "Please specify string starting with alphanumeric%s", VTY_NEWLINE); - return CMD_WARNING; - } - - if (host.enable) - XFREE (MTYPE_HOST, host.enable); - host.enable = NULL; - - /* Plain password input. */ - if (host.encrypt) - { - if (host.enable_encrypt) - XFREE (MTYPE_HOST, host.enable_encrypt); - host.enable_encrypt = XSTRDUP (MTYPE_HOST, zencrypt (argv[0])); - } - else - host.enable = XSTRDUP (MTYPE_HOST, argv[0]); - - return CMD_SUCCESS; -} + return do_set_password(vty, argc, argv, &host.enable, + &host.enable_encrypted) ; +} ; ALIAS_CALL (config_enable_password, enable_password_text_cmd, @@ -2863,14 +1311,12 @@ DEFUN_CALL (no_config_enable_password, no_enable_password_cmd, "Modify enable password parameters\n" "Assign the privileged level password\n") { - if (host.enable) - XFREE (MTYPE_HOST, host.enable); - host.enable = NULL; + VTY_LOCK() ; - if (host.enable_encrypt) - XFREE (MTYPE_HOST, host.enable_encrypt); - host.enable_encrypt = NULL; + host.enable_encrypted = false ; + XFREE (MTYPE_HOST, host.enable); + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -2880,24 +1326,30 @@ DEFUN_CALL (service_password_encrypt, "Set up miscellaneous service\n" "Enable encrypted passwords\n") { - if (host.encrypt) - return CMD_SUCCESS; + VTY_LOCK() ; - host.encrypt = 1; + host.encrypt = true ; - if (host.password) + /* If we have an unencrypted password in hand, convert that now. + * If not, retain any already encrypted password. + */ + if (!host.password_encrypted && (host.password != NULL)) { - if (host.password_encrypt) - XFREE (MTYPE_HOST, host.password_encrypt); - host.password_encrypt = XSTRDUP (MTYPE_HOST, zencrypt (host.password)); - } - if (host.enable) + char* plain = host.password ; + host.password = XSTRDUP(MTYPE_HOST, zencrypt (plain)) ; + XFREE(MTYPE_HOST, plain) ; + host.password_encrypted = true ; + } ; + + if (!host.enable_encrypted && (host.enable != NULL)) { - if (host.enable_encrypt) - XFREE (MTYPE_HOST, host.enable_encrypt); - host.enable_encrypt = XSTRDUP (MTYPE_HOST, zencrypt (host.enable)); - } + char* plain = host.enable ; + host.enable = XSTRDUP(MTYPE_HOST, zencrypt (plain)) ; + XFREE(MTYPE_HOST, plain) ; + host.enable_encrypted = true ; + } ; + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -2908,19 +1360,13 @@ DEFUN_CALL (no_service_password_encrypt, "Set up miscellaneous service\n" "Enable encrypted passwords\n") { - if (! host.encrypt) - return CMD_SUCCESS; - - host.encrypt = 0; + VTY_LOCK() ; - if (host.password_encrypt) - XFREE (MTYPE_HOST, host.password_encrypt); - host.password_encrypt = NULL; + /* Keep any existing passwords, encrypted or not. */ - if (host.enable_encrypt) - XFREE (MTYPE_HOST, host.enable_encrypt); - host.enable_encrypt = NULL; + host.encrypt = false ; + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -2954,6 +1400,15 @@ DEFUN_CALL (config_terminal_no_length, config_terminal_no_length_cmd, return CMD_SUCCESS; } +static cmd_return_code_t +set_host_lines(int lines) +{ + VTY_LOCK() ; + host.lines = lines ; + VTY_UNLOCK() ; + return CMD_SUCCESS ; +} ; + DEFUN_CALL (service_terminal_length, service_terminal_length_cmd, "service terminal-length <0-512>", "Set up miscellaneous service\n" @@ -2969,10 +1424,9 @@ DEFUN_CALL (service_terminal_length, service_terminal_length_cmd, vty_out (vty, "length is malformed%s", VTY_NEWLINE); return CMD_WARNING; } - host.lines = lines; - return CMD_SUCCESS; -} + return set_host_lines(lines) ; +} ; DEFUN_CALL (no_service_terminal_length, no_service_terminal_length_cmd, "no service terminal-length [<0-512>]", @@ -2981,8 +1435,7 @@ DEFUN_CALL (no_service_terminal_length, no_service_terminal_length_cmd, "System wide terminal length configuration\n" "Number of lines of VTY (0 means no line control)\n") { - host.lines = -1; - return CMD_SUCCESS; + return set_host_lines(-1) ; } DEFUN_HID_CALL (do_echo, @@ -2993,10 +1446,10 @@ DEFUN_HID_CALL (do_echo, { char *message; - vty_out (vty, "%s%s", ((message = argv_concat(argv, argc, 0)) ? message : ""), - VTY_NEWLINE); - if (message) - XFREE(MTYPE_TMP, message); + message = argv_concat(argv, argc, 0) ; + vty_out (vty, "%s\n", message ? message : "") ; + XFREE(MTYPE_TMP, message); + return CMD_SUCCESS; } @@ -3015,8 +1468,7 @@ DEFUN_CALL (config_logmsg, message = argv_concat(argv, argc, 1); zlog(NULL, level, "%s", (message ? message : "")); - if (message) - XFREE(MTYPE_TMP, message); + XFREE(MTYPE_TMP, message); return CMD_SUCCESS; } @@ -3436,6 +1888,7 @@ DEFUN_CALL (banner_motd_file, { if (host.motdfile) XFREE (MTYPE_HOST, host.motdfile); + host.motdfile = XSTRDUP (MTYPE_HOST, argv[0]); return CMD_SUCCESS; @@ -3448,7 +1901,7 @@ DEFUN_CALL (banner_motd_default, "Strings for motd\n" "Default string\n") { - host.motd = default_motd; + host.motd = DEFAULT_MOTD ; return CMD_SUCCESS; } @@ -3467,12 +1920,12 @@ DEFUN_CALL (no_banner_motd, } /* Set config filename. Called from vty.c */ -void -host_config_set (char *filename) +extern void +host_config_set (const char* file_name) { - if (host.config) - XFREE (MTYPE_HOST, host.config); - host.config = XSTRDUP (MTYPE_HOST, filename); + if (host.config_file) + XFREE (MTYPE_HOST, host.config_file); + host.config_file = XSTRDUP (MTYPE_HOST, file_name); } void @@ -3495,25 +1948,12 @@ install_default (enum node_type node) void cmd_init (int terminal) { - command_cr = XSTRDUP(MTYPE_STRVEC, ""); - desc_cr.cmd = command_cr; - desc_cr.str = XSTRDUP(MTYPE_STRVEC, ""); + /* Allocate initial top vector of commands. */ + node_vector = vector_init(0); - /* Allocate initial top vector of commands. */ - cmdvec = vector_init(0); + /* Default host value settings are already set, see above */ - /* Allocate vector of spare qstrings for tokens */ - cmd_spare_tokens_init() ; - - /* Default host value settings. */ - host.name = NULL; - host.password = NULL; - host.enable = NULL; - host.logfile = NULL; - host.config = NULL; - host.lines = -1; - host.motd = default_motd; - host.motdfile = NULL; + cmd_get_sys_host_name() ; /* start with system name & name_gen == 1 */ /* Install top nodes. */ install_node (&view_node, NULL); @@ -3622,85 +2062,46 @@ cmd_init (int terminal) void cmd_terminate () { - unsigned int i, j, k, l; - struct cmd_node *cmd_node; - struct cmd_element *cmd_element; - struct desc *desc; - vector cmd_node_v, cmd_element_v, desc_v; + cmd_node cmd_node; + cmd_command cmd ; - cmd_spare_tokens_free() ; - - if (cmdvec) + while ((cmd_node = vector_ream(node_vector, free_it)) != NULL) { - for (i = 0; i < vector_length (cmdvec); i++) + while ((cmd = vector_ream(cmd_node->cmd_vector, free_it)) != NULL) { - cmd_node = vector_get_item (cmdvec, i) ; - if (cmd_node == NULL) - continue ; - - cmd_node_v = cmd_node->cmd_vector; + /* Note that each cmd is a static structure, which may appear in + * more than one cmd_vector. + */ + cmd_item next_item ; - for (j = 0; j < vector_length (cmd_node_v); j++) + while ((next_item = vector_ream(cmd->items, free_it)) != NULL) { - cmd_element = vector_get_item (cmd_node_v, j) ; - if (cmd_element == NULL) - continue ; - - cmd_element_v = cmd_element->strvec ; - if (cmd_element_v == NULL) - continue ; - - for (k = 0; k < vector_length (cmd_element_v); k++) + do { - desc_v = vector_get_item (cmd_element_v, k) ; - if (desc_v == NULL) - continue ; - - for (l = 0; l < vector_length (desc_v); l++) - { - desc = vector_get_item (desc_v, l) ; - if (desc == NULL) - continue ; - - if (desc->cmd) - XFREE (MTYPE_STRVEC, desc->cmd); - if (desc->str) - XFREE (MTYPE_STRVEC, desc->str); - - XFREE (MTYPE_DESC, desc); - } ; - vector_free (desc_v); - } ; - - cmd_element->strvec = NULL; - vector_free (cmd_element_v); + cmd_item item ; + item = next_item ; + next_item = item->next ; + XFREE(MTYPE_CMD_ITEM, item); + } + while (next_item != NULL) ; } ; - vector_free (cmd_node_v); - } ; + cmd->items = NULL ; /* gone */ - vector_free (cmdvec); - cmdvec = NULL; + XFREE (MTYPE_CMD_STRING, cmd->r_string) ; /* sets NULL */ + XFREE (MTYPE_CMD_STRING, cmd->r_doc) ; + } ; } - if (command_cr) - XFREE(MTYPE_STRVEC, command_cr); - if (desc_cr.str) - XFREE(MTYPE_STRVEC, desc_cr.str); - if (host.name) - XFREE (MTYPE_HOST, host.name); - if (host.password) - XFREE (MTYPE_HOST, host.password); - if (host.password_encrypt) - XFREE (MTYPE_HOST, host.password_encrypt); - if (host.enable) - XFREE (MTYPE_HOST, host.enable); - if (host.enable_encrypt) - XFREE (MTYPE_HOST, host.enable_encrypt); - if (host.logfile) - XFREE (MTYPE_HOST, host.logfile); - if (host.motdfile) - XFREE (MTYPE_HOST, host.motdfile); - if (host.config) - XFREE (MTYPE_HOST, host.config); -} + node_vector = NULL ; + + XFREE(MTYPE_HOST, host.name); + XFREE(MTYPE_HOST, host.password); + XFREE(MTYPE_HOST, host.enable); + XFREE(MTYPE_HOST, host.logfile); + XFREE(MTYPE_HOST, host.motdfile); + XFREE(MTYPE_HOST, host.config_file); + XFREE(MTYPE_HOST, host.vty_accesslist_name); + XFREE(MTYPE_HOST, host.vty_ipv6_accesslist_name); + XFREE(MTYPE_HOST, host.vty_cwd); +} ; diff --git a/lib/command.h b/lib/command.h index fe4f0a56..89644ce7 100644 --- a/lib/command.h +++ b/lib/command.h @@ -25,168 +25,39 @@ #include "misc.h" -#include "node_type.h" +#include "command_common.h" +#include "vty.h" + #include "vector.h" #include "qstring.h" -struct vty ; /* in case command.h expanded first */ - -/* Host configuration variable */ -struct host -{ - /* Host name of this router. */ - char *name; - - /* Password for vty interface. */ - char *password; - char *password_encrypt; - - /* Enable password */ - char *enable; - char *enable_encrypt; - - /* System wide terminal lines. */ - int lines; - - /* Log filename. */ - char *logfile; - - /* config file name of this host */ - char *config; - - /* Flags for services */ - int advanced; - int encrypt; - - /* Banner configuration. */ - const char *motd; - char *motdfile; -}; - -/* Node which has some commands and prompt string and configuration - function pointer . */ -struct cmd_node -{ - /* Node index. */ - enum node_type node; - - /* Prompt character at vty interface. */ - const char *prompt; - - /* Is this node's configuration goes to vtysh ? */ - int vtysh; - - /* Node's configuration write function */ - int (*func) (struct vty *); - - /* Vector of this node's command list. */ - vector cmd_vector; -}; - -enum -{ - CMD_ATTR_SIMPLE = 0x00, - /* bit significant */ - CMD_ATTR_DEPRECATED = 0x01, - CMD_ATTR_HIDDEN = 0x02, - CMD_ATTR_CALL = 0x04, -}; - -/* Return values for command handling. - * - * NB: when a command is executed it may return CMD_SUCCESS or CMD_WARNING. - * - * In both cases any output required (including any warning or error - * messages) must already have been output. - * - * All other return codes are for use within the command handler. +/*============================================================================== + * This contains everything required for command handling from outside the + * library. */ -enum cmd_return_code -{ - CMD_SUCCESS = 0, - CMD_WARNING = 1, - CMD_ERROR, - - CMD_EMPTY, - CMD_SUCCESS_DAEMON, - - CMD_WAIT_INPUT, - CMD_CLOSE, - CMD_QUEUED, - - CMD_ERR_NO_MATCH, - CMD_ERR_AMBIGUOUS, - CMD_ERR_INCOMPLETE, - - CMD_COMPLETE_FULL_MATCH, - CMD_COMPLETE_MATCH, - CMD_COMPLETE_LIST_MATCH, - CMD_COMPLETE_ALREADY -} ; - -typedef enum cmd_return_code cmd_return_code_t ; - -#define MSG_CMD_ERR_AMBIGUOUS "Ambiguous command" -#define MSG_CMD_ERR_NO_MATCH "Unrecognised command" -#define MSG_CMD_ERR_NO_MATCH_old "There is no matched command" - -/* Structure of command element. */ - -struct cmd_element ; -typedef struct cmd_element* cmd_element ; - -typedef const char* const argv_t[] ; - -#define DEFUN_CMD_ARG_UNUSED __attribute__ ((unused)) -#define DEFUN_CMD_FUNCTION(name) \ - enum cmd_return_code name (cmd_element self DEFUN_CMD_ARG_UNUSED, \ - struct vty* vty DEFUN_CMD_ARG_UNUSED, \ - int argc DEFUN_CMD_ARG_UNUSED, \ - argv_t argv DEFUN_CMD_ARG_UNUSED) - -typedef DEFUN_CMD_FUNCTION((cmd_function)) ; - -struct cmd_element -{ - const char* string ; /* Command specification by string. */ - cmd_function* func ; - const char* doc ; /* Documentation of this command. */ - int daemon ; /* Daemon to which this command belong. */ - vector strvec ; /* Vector of vectors of struct desc */ - unsigned int cmdsize ; /* Command index count. */ - char* config ; /* Configuration string */ - vector subconfig ; /* Sub configuration string */ - u_char attr ; /* Command attributes */ -}; - -/* Command description structure. */ -struct desc -{ - char* cmd; /* Command string. */ - char* str; /* Command's description. */ -}; - /*------------------------------------------------------------------------------ - * Can now include these + * Turn off these macros when using cpp with extract.pl */ - -#include "vty.h" -#include "uty.h" - -/*----------------------------------------------------------------------------*/ -/* Turn off these macros when uisng cpp with extract.pl */ #ifndef VTYSH_EXTRACT_PL -/* helper defines for end-user DEFUN* macros */ -#define DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attrs, dnum) \ - struct cmd_element cmdname = \ +/* helper defines for end-user DEFUN* macros */ +#define DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, attrs, dnum) \ + struct cmd_command cmdname = \ { \ - .string = cmdstr, \ - .func = funcname, \ - .doc = helpstr, \ - .attr = attrs, \ - .daemon = dnum, \ - }; + .string = cmdstr, \ + .func = funcname, \ + .doc = helpstr, \ + .attr = attrs, \ + .daemon = dnum, \ + \ + .items = NULL, \ + .nt_min = 0, \ + .nt = 0, \ + .nt_max = 0, \ + .vararg = NULL, \ + .r_string = NULL, \ + .r_doc = NULL, \ + } ; #define DEFUN_CMD_FUNC_DECL(funcname) \ static cmd_function funcname; @@ -194,50 +65,52 @@ struct desc #define DEFUN_CMD_FUNC_TEXT(funcname) \ static DEFUN_CMD_FUNCTION(funcname) -/* DEFUN for vty command interafce. Little bit hacky ;-). */ +/* DEFUN for vty command interface. Little bit hacky ;-). */ #define DEFUN(funcname, cmdname, cmdstr, helpstr) \ DEFUN_CMD_FUNC_DECL(funcname) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \ + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, 0, 0) \ DEFUN_CMD_FUNC_TEXT(funcname) #define DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \ DEFUN_CMD_FUNC_DECL(funcname) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0) \ + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, attr, 0) \ DEFUN_CMD_FUNC_TEXT(funcname) +#define DEFUN_CALL(funcname, cmdname, cmdstr, helpstr) \ + DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DIRECT) + #define DEFUN_HIDDEN(funcname, cmdname, cmdstr, helpstr) \ DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN) #define DEFUN_HID_CALL(funcname, cmdname, cmdstr, helpstr) \ - DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, (CMD_ATTR_CALL | CMD_ATTR_HIDDEN)) + DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, \ + (CMD_ATTR_DIRECT | CMD_ATTR_HIDDEN)) #define DEFUN_DEPRECATED(funcname, cmdname, cmdstr, helpstr) \ DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED) #define DEFUN_DEP_CALL(funcname, cmdname, cmdstr, helpstr) \ - DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, (CMD_ATTR_CALL | CMD_ATTR_DEPRECATED)) - -#define DEFUN_CALL(funcname, cmdname, cmdstr, helpstr) \ - DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_CALL) + DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, \ + (CMD_ATTR_DIRECT | CMD_ATTR_DEPRECATED)) -/* DEFUN_NOSH for commands that vtysh should ignore */ +/* DEFUN_NOSH for commands that vtysh should ignore */ #define DEFUN_NOSH(funcname, cmdname, cmdstr, helpstr) \ DEFUN(funcname, cmdname, cmdstr, helpstr) -/* DEFSH for vtysh. */ +/* DEFSH for vtysh. */ #define DEFSH(daemon, cmdname, cmdstr, helpstr) \ - DEFUN_CMD_ELEMENT(NULL, cmdname, cmdstr, helpstr, 0, daemon) + DEFUN_CMD_COMMAND(NULL, cmdname, cmdstr, helpstr, 0, daemon) -/* DEFUN + DEFSH */ +/* DEFUN + DEFSH */ #define DEFUNSH(daemon, funcname, cmdname, cmdstr, helpstr) \ DEFUN_CMD_FUNC_DECL(funcname) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, daemon) \ + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, 0, daemon) \ DEFUN_CMD_FUNC_TEXT(funcname) -/* DEFUN + DEFSH with attributes */ +/* DEFUN + DEFSH with attributes */ #define DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr, attr) \ DEFUN_CMD_FUNC_DECL(funcname) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, daemon) \ + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, attr, daemon) \ DEFUN_CMD_FUNC_TEXT(funcname) #define DEFUNSH_HIDDEN(daemon, funcname, cmdname, cmdstr, helpstr) \ @@ -246,44 +119,34 @@ struct desc #define DEFUNSH_DEPRECATED(daemon, funcname, cmdname, cmdstr, helpstr) \ DEFUNSH_ATTR (daemon, funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED) -/* ALIAS macro which define existing command's alias. */ +/* ALIAS macro which define existing command's alias. */ #define ALIAS(funcname, cmdname, cmdstr, helpstr) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, 0, 0) #define ALIAS_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0) + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, attr, 0) + +#define ALIAS_CALL(funcname, cmdname, cmdstr, helpstr) \ + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DIRECT, 0) #define ALIAS_HIDDEN(funcname, cmdname, cmdstr, helpstr) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, 0) + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, 0) #define ALIAS_DEPRECATED(funcname, cmdname, cmdstr, helpstr) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED, 0) - -#define ALIAS_CALL(funcname, cmdname, cmdstr, helpstr) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_CALL, 0) + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED, 0) #define ALIAS_SH(daemon, funcname, cmdname, cmdstr, helpstr) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, daemon) + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, 0, daemon) #define ALIAS_SH_HIDDEN(daemon, funcname, cmdname, cmdstr, helpstr) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, daemon) + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN, daemon) #define ALIAS_SH_DEPRECATED(daemon, funcname, cmdname, cmdstr, helpstr) \ - DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED, daemon) + DEFUN_CMD_COMMAND(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED,\ + daemon) #endif /* VTYSH_EXTRACT_PL */ -/* Some macroes */ -#define CMD_OPTION(S) ((S[0]) == '[') -#define CMD_VARIABLE(S) (((S[0]) >= 'A' && (S[0]) <= 'Z') || ((S[0]) == '<')) -#define CMD_VARARG(S) ((S[0]) == '.') -#define CMD_RANGE(S) ((S[0] == '<')) - -#define CMD_IPV4(S) ((strcmp ((S), "A.B.C.D") == 0)) -#define CMD_IPV4_PREFIX(S) ((strcmp ((S), "A.B.C.D/M") == 0)) -#define CMD_IPV6(S) ((strcmp ((S), "X:X::X:X") == 0)) -#define CMD_IPV6_PREFIX(S) ((strcmp ((S), "X:X::X:X/M") == 0)) - /* Common descriptions. */ #define SHOW_STR "Show running system information\n" #define IP_STR "IP information\n" @@ -347,17 +210,29 @@ extern void cmd_terminate (void); extern void print_version (const char *); extern void install_node (struct cmd_node *, int (*) (struct vty *)); -extern void install_default (enum node_type); -extern void install_element (enum node_type, struct cmd_element *); +extern void install_default (node_type_t); +extern void install_element (node_type_t, struct cmd_command *); extern void sort_node (void); +extern const char* cmd_host_name(bool fresh) ; + +extern node_type_t cmd_node_parent(node_type_t node) ; +extern node_type_t cmd_node_exit_to(node_type_t node) ; +extern node_type_t cmd_node_end_to(node_type_t node) ; + /* Concatenates argv[shift] through argv[argc-1] into a single NUL-terminated string with a space between each element (allocated using XMALLOC(MTYPE_TMP)). Returns NULL if shift >= argc. */ extern char *argv_concat (const char* const* argv, int argc, int shift); -/* struct host global, ick */ -extern struct host host; +/* Export typical functions. */ +extern struct cmd_command config_end_cmd; +extern struct cmd_command config_exit_cmd; +extern struct cmd_command config_quit_cmd; +extern struct cmd_command config_help_cmd; +extern struct cmd_command config_list_cmd; +extern char *host_config_file (void); +extern void host_config_set (const char *); #ifdef QDEBUG extern const char *debug_banner ; diff --git a/lib/command_execute.h b/lib/command_execute.h index 6a7e2e4b..c1a2bdd0 100644 --- a/lib/command_execute.h +++ b/lib/command_execute.h @@ -23,59 +23,104 @@ #ifndef _ZEBRA_COMMAND_EXECUTE_H #define _ZEBRA_COMMAND_EXECUTE_H -#include "command.h" +#include "command_local.h" #include "command_parse.h" -#include "node_type.h" - -extern vector cmd_make_strvec (const char *); -extern vector cmd_add_to_strvec (vector v, const char* str) ; -extern void cmd_free_strvec (vector); -extern vector cmd_describe_command (const char* line, node_type_t node, - cmd_return_code_t* status) ; -extern vector cmd_complete_command (vector, int, int *status); -extern const char *cmd_prompt (enum node_type); -extern enum cmd_return_code -config_from_file (struct vty* vty, FILE *fp, struct cmd_element* first_cmd, - qstring buf, bool stop_on_warning) ; -extern enum node_type node_parent (enum node_type); -extern enum cmd_return_code cmd_execute_command (struct vty *vty, - enum cmd_parse_type type, struct cmd_element **cmd) ; -extern enum cmd_return_code cmd_execute_command_strict (struct vty *vty, - enum cmd_parse_type type, struct cmd_element **cmd) ; - -extern cmd_parsed cmd_parse_init_new(cmd_parsed parsed) ; -extern cmd_parsed cmd_parse_reset(cmd_parsed parsed, bool free_structure) ; -extern enum cmd_return_code cmd_parse_command(struct vty* vty, - enum cmd_parse_type type) ; -extern enum cmd_return_code cmd_dispatch(struct vty* vty, bool no_queue) ; - -Inline enum cmd_return_code -cmd_dispatch_call(struct vty* vty) +#include "vty_common.h" +#include "qstring.h" +#include "mqueue.h" +#include "qpnexus.h" +#include "thread.h" + +/*============================================================================== + * This is stuff which is used to parse and then execute commands. + */ + +/* State of the execution loop + */ +enum cmd_exec_state { - cmd_parsed parsed = vty->parsed ; - return (*(parsed->cmd->func))(parsed->cmd, vty, cmd_arg_vector_argc(parsed), - cmd_arg_vector_argv(parsed)) ; + exec_null = 0, + + exec_special, /* not a simple command */ + + exec_fetch, + exec_parse, + exec_open_pipes, + exec_execute, + exec_success, + exec_complete, } ; +typedef enum cmd_exec_state cmd_exec_state_t ; + +typedef struct cmd_exec* cmd_exec ; + +struct cmd_exec +{ + vty vty ; /* parent */ -#define cmd_parse_reset_keep(parsed) cmd_parse_reset(parsed, 0) -#define cmd_parse_reset_free(parsed) cmd_parse_reset(parsed, 1) + qstring line ; /* pointer to qstring in vf */ + cmd_do_t to_do ; /* for cli driven stuff */ -extern void config_replace_string (struct cmd_element *, char *, ...); + cmd_parse_type_t parse_type ; /* how should parse */ + bool out_enabled ; /* as required */ + bool reflect_enabled ; /* as required */ + + cmd_parsed_t parsed ; /* embedded */ + + cmd_exec_state_t state ; /* for cq_process */ + qpn_nexus locus ; /* for cq_process */ + + cmd_return_code_t ret ; /* for cq_process */ + + union + { + mqueue_block mqb ; /* for cq_process */ + struct thread* thread ; + } cq ; +} ; + +/*============================================================================== + * Functions + * + */ -/* Export typical functions. */ -extern struct cmd_element config_end_cmd; -extern struct cmd_element config_exit_cmd; -extern struct cmd_element config_quit_cmd; -extern struct cmd_element config_help_cmd; -extern struct cmd_element config_list_cmd; -extern char *host_config_file (void); -extern void host_config_set (char *); +extern cmd_exec cmd_exec_new(vty vty) ; +extern cmd_exec cmd_exec_free(cmd_exec exec) ; -/* "" global */ -extern char *command_cr; +extern cmd_return_code_t cmd_read_config(vty vty, cmd_command first_cmd, + bool ignore_warning) ; +extern cmd_return_code_t cmd_end(vty vty) ; +extern cmd_return_code_t cmd_exit(vty vty) ; + +extern cmd_return_code_t cmd_open_pipes(vty vty) ; + +extern cmd_return_code_t cmd_execute(vty vty) ; + + + + + + +#if 0 + +extern enum cmd_return_code cmd_execute_command (vty vty, + cmd_parse_type_t type, struct cmd_command **cmd) ; +extern enum cmd_return_code cmd_execute_command_strict (vty vty, + enum cmd_parse_type type, struct cmd_command **cmd) ; + + +extern void config_replace_string (cmd_command, char *, ...); -#ifdef QDEBUG -extern const char *debug_banner; #endif +/*============================================================================== + * Inlines + */ + +Inline bool +cmd_is_direct(cmd_parsed parsed) +{ + return ((parsed->cmd->attr & CMD_ATTR_DIRECT) != 0) ; +} ; + #endif /* _ZEBRA_COMMAND_EXECUTE_H */ diff --git a/lib/command_parse.c b/lib/command_parse.c index d8960b8d..c64ee3ba 100644 --- a/lib/command_parse.c +++ b/lib/command_parse.c @@ -1,4 +1,4 @@ -/* Quagga command line parsing -- header +/* Quagga command line parsing * Copyright (C) 1997, 98 Kunihiro Ishiguro * * Recast and extended: Copyright (C) 2010 Chris Hall (GMCH), Highwayman @@ -21,883 +21,4073 @@ * Boston, MA 02111-1307, USA. */ -#include +#include "misc.h" +#include +#include +#include +#include "command_local.h" #include "command_parse.h" #include "memory.h" /*============================================================================== - * Token handling + * Command Description objects. + * */ - -/* Store of qstrings, used for parsing. */ -token_vector_t spare_tokens ; - -static char cmd_token_escape(char e) ; +static void cmd_fail_item(cmd_command cmd, const char* msg) ; +static char* cmd_item_brackets(cmd_command cmd, char* cp) ; +static cmd_item cmd_make_item(cmd_command cmd, char* cp, char* dp) ; +static void cmd_make_item_inner(cmd_command cmd, cmd_item n, char* cp) ; +static char* cmd_make_item_numeric(cmd_command cmd, cmd_item n, char* cp) ; +static long cmd_make_item_number(cmd_command cmd, cmd_item n, char** p_cp) ; +static int cmd_cmp_item(const cmd_item* a, const cmd_item* b) ; +static int cmd_cmp_range_items(const cmd_item a, const cmd_item b) ; +static bool cmd_item_is_option(cmd_item_type_t it) ; +static bool cmd_item_is_vararg(cmd_item_type_t it) ; /*------------------------------------------------------------------------------ - * Initialise a brand new token vector -- empty. + * Dummy eol_item */ -static inline void -cmd_token_vector_init(token_vector tokens) +static struct cmd_item eol_item = { - vector_init_new(tokens->body, 0) ; -} ; + .str = "", + .doc = "", -/*------------------------------------------------------------------------------ - * Initialise empty spare tokens vector - */ -extern void -cmd_spare_tokens_init(void) -{ - cmd_token_vector_init(spare_tokens) ; -} ; + .next = NULL, -/*------------------------------------------------------------------------------ - * Empty out the spare_tokens vector and release all memory - */ -extern void -cmd_spare_tokens_free(void) -{ - token tok ; + .type = item_eol, + .arg = false, - while ((tok = vector_ream(spare_tokens->body, keep_it)) != NULL) - qs_reset(tok->qs, keep_it) ; + .range_sign_allowed = false, + .range_sign_required = false, + .range_min = 0, + .range_max = 0 } ; /*------------------------------------------------------------------------------ - * Take string and break it into tokens. + * Parse cmd_command string and doc to create the items for the cmd_command, + * and fill in: * - * Discards leading and trailing ' ' or '\t'. + * cmd->items -- vector of cmd_item(s). * - * Expects string to have been preprocessed, if required, to ensure that any - * unwanted control characters have been removed. This code only recognises - * '\t'. + * Where a given item may have more than one possible + * value, thet are arranged as a list. * - * 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. + * cmd->nt_min -- count of items up to first [option] + * where (...) counts as 1 + * and .vararg counts as 1 * - * Anything immediately preceded by '\' is ignored by the tokenizer. This - * includes blanks and quotes. + * Is the minimum number of tokens required to match to + * this command. * - * Anything inside "...." is ignored by the tokenizer, including '\"' escapes. + * cmd->nt -- count of all items + * where (...) counts as 1 + * and [option] counts as 1 + * and .vararg counts as 1 * - * Unbalanced "'" or '"' are treated as if eol was a "'" or '"'. + * cmd->nt_max -- count of all items as nt_var, + * except .vararg forces to UINT_MAX * - * Of the things which are not ignored by the tokenizer: + * Is the maximum number of tokens which can be matched + * to this command. * - * * tokens are separated by whitespace -- one ' ' or '\t' characters - * The whitespace is discarded. + * cmd->r_string -- copy of cmd->string, chopped up and referred to by + * the cmd_items. * - * * tokens are separated by "separators", which start with any of: + * cmd->d_string -- copy of the cmd->doc, chopped up and referred to by + * the cmd_items. * - * '!', '#', '<', '>' and '|' + * Note that the cmd_items point into the r_string and the d_string, and + * do not have further copies of their fragments of the original. * - * which may be followed by one or more characters to form a separator - * token. + * Note that the t_string and d_string have all extraneous spaces, tabs and + * control characters removed. * - * - from '!' or '#' to end of line is a comment token. + * Stops dead if not valid ! * - * - '<' opt and '<|' opt are separators, where opt is any combination - * of '+', '*' and '-'. + * Accepts: items separated by one or more spaces. Early on in the process, + * will discard spaces within a bracketed item (and checks for balanced + * brackets). Rejects any control character other than '\t', which is + * converted to ' '. Multiple spaces are reduced to single spaces. * - * - '>', '>>' and '|' are separators. + * - single item is one of: * - * NB: the tokenization mimics the standard shell which makes the piping stuff - * straightforward. It's also well known. Apart from the "'" rule, it - * also seems fine ! + * - keyword -- anything starting a-z or 0-9, followed by any + * alphanumeric, '-', '_' or ':'. + * or * + * - <0-9> -- decimal range + * - WORD -- anything at least starting A-Z, followed by any + * alphanumeric, '-', '_' or ':'. + * - A.B.C.D -- ipv4 address + * - A.B.C.D/M -- ipv4 prefix + * - X:X::X:X -- ipv6 address + * - X:X::X:X/M -- ipv6 prefix + * - .vararg -- anything starting '.', followed by any alphanumeric, + * '-', '_' or ':'. + * - [item] -- optional item any of the above TODO * - * NB: any control characters other than those spotted by isspace() are accepted - * as part of the current token ! + * - multiple item is: '(' item '|' item .... ')' * - * The tokens returned contain all the original characters of the line, except - * for the removal of '\t' between tokens. + * where spaces around items are discarded. The items may be any of the + * above, except: + * + * - must all be different types of item, or for keywords and ranges, + * different values. + * + * - cannot have [item] + * + * - cannot have .var + * + * may have a single item -- whose value is sent out as an argument. + * + * An [item] may only be followed by other [item](s). An [item] matches a + * token or end of line. + * + * A .vararg must be the last item. A .vararg matches one or more tokens. * - * Returns: the types of all tokens or'd together. - * returns cmd_tok_null if the line is empty (apart from ' ' and '\t') - * or if the pointer was NULL. * - * 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. */ -extern cmd_token_type_t -cmd_tokenise(cmd_parsed parsed, const char *line, node_type_t node) +extern void +cmd_compile(cmd_command cmd) { - const char *cp, *ep ; - cmd_token_type_t total ; + vector multvec ; + char* cp ; + char* qp ; + char* dp ; + bool opt ; + bool vararg ; + + /* Initialise the compiled version of the command */ + + assert((cmd->r_doc == NULL) && (cmd->r_string == NULL)) ; + + cmd->items = vector_init_new(NULL, 10) ; /* plenty ! */ + cmd->nt_min = 0 ; + cmd->nt = 0 ; + cmd->nt_max = 0 ; + cmd->vararg = NULL ; + cmd->r_doc = XSTRDUP(MTYPE_CMD_STRING, cmd->doc) ; /* NULL => "" */ + cmd->r_string = XSTRDUP(MTYPE_CMD_STRING, cmd->string) ; + + /* Simplify the command line string by replacing TABs by spaces, and barfing + * on control characters. Strip leading and trailing spaces and any spaces + * between brackets... checking for matching brackets. + */ + cp = cmd->r_string ; + while (*cp != '\0') + { + if (!iscntrl(*cp)) + ++cp ; + else if (*cp == '\t') + *cp++ = ' ' ; + else + cmd_fail_item(cmd, "improper control character in string") ; + } ; - cmd_empty_token_vector(parsed->tokens) ; /* Empty the token vector */ + cp = cmd->r_string ; + while (*cp == ' ') + ++cp ; - parsed->line = line ; - parsed->onode = parsed->cnode = node ; + qp = cmd->r_string ; + while (*cp != '\0') + { + if (*cp != ' ') + { + if ((*cp == '(') || (*cp == '[') || (*cp == '<') || (*cp == '{')) + { + /* Check for balanced brackets and remove any spaces between. + * + * Checks for enclosed brackets being balanced as well. + * + * Leaves cp pointing at the trailing bracket. + */ + char* sp = cp ; + cp = cmd_item_brackets(cmd, cp) ; + while (sp < cp) + { + if (*sp != ' ') + *qp++ = *sp++ ; + else + ++sp ; + } ; + } ; + } + else + { + while (*(cp + 1) == ' ') + ++cp ; + if (*(cp + 1) == '\0') + break ; + } ; - total = cmd_tok_null ; /* nothing yet */ + *qp++ = *cp++ ; + } ; - if (line == NULL) /* tolerate NULL */ - return total ; + *qp++ = '\0' ; /* terminate reduced string */ - cp = line ; - ep = cp + strlen(cp) ; + /* Simplify the documentation string by replacing TABs by spaces, and barfing + * on control characters other than '\n'. + * + * Strips leading spaces and any spaces before or after '\n'. + */ - while (cp < ep) /* process to end */ + qp = dp = cmd->r_doc ; + while (*dp != '\0') { - const char* sp ; - bool end ; - cmd_token_type_t type ; + /* Strip leading */ + while (*dp == ' ') + ++dp ; - if ((*cp == ' ') || (*cp == '\t')) + /* Eat documentation section. */ + while ((*dp != '\n') && (*dp != '\0')) { - /* skip white-space */ - do { ++cp ; } while ((*cp == ' ') || (*cp == '\t')) ; - - if (cp == ep) + if (!iscntrl(*dp)) + *qp++ = *dp++ ; + else if (*dp == '\t') { - if (total != cmd_tok_null) - total |= cmd_tok_trailing ; - break ; - } ; + *qp++ = ' ' ; + ++dp ; + } + else + cmd_fail_item(cmd, "improper control character in documentation") ; } ; - sp = cp ; - end = false ; - type = cmd_tok_simple ; + /* Get here with *dp == '\n' or '\0' + * + * Strip trailing spaces (any before '\n' or '\0' + */ + while ((qp != cmd->r_doc) && (*(qp - 1) == ' ')) + --qp ; + + /* copy '\n', if required. */ + if (*dp == '\n') + *qp++ = *dp++ ; + } ; + + *qp++ = '\0' ; /* terminate reduced string */ + + /* Processing loop */ + + cp = cmd->r_string ; + dp = cmd->r_doc ; + + opt = false ; + vararg = false ; + + multvec = NULL ; + + while (*cp != '\0') + { + uint multiple ; + + /* Deal with single or multiple item. */ + multiple = 0 ; do { - switch (*cp) - { - case '\t': /* whitespace at end of token */ - case ' ': - end = true ; - break ; + cmd_item n ; + char* c_sp ; + char* d_sp ; - case '\'': /* proceed to matching '\'' or end */ - type |= cmd_tok_sq ; - ++cp ; - while (cp < ep) - { - if (*cp++ == '\'') - break ; - } ; - break ; + /* step to the next documentation section */ - case '\\': /* step past escaped character, if any */ - type |= cmd_tok_esc ; - ++cp ; - if (cp < ep) - ++cp ; - break ; + d_sp = dp ; /* start of documentation */ - case '"': /* proceed to matching '"' or end... */ - type |= cmd_tok_dq ; - ++cp ; - while (cp < ep) /* NB: do not register '\\' separately */ + while (*dp != '\0') + { + if (*dp == '\n') { - if (*cp++ == '"') - if (*(cp - 2) != '\\') /* ignore escaped '"' */ - break ; + *dp++ = '\0' ; + break ; } ; - break ; + ++dp ; + } ; - case '>': /* '>' or '>>' separators. */ - end = true ; - if (cp == sp) /* if at start of token */ + /* Deal with '(' if we have one. */ + + if (*cp == '(') /* change up to multiple */ + { + if (multiple != 0) + cmd_fail_item(cmd, "unexpected '('") ; + + multiple = 1 ; /* seen '(' */ + ++cp ; /* step past it */ + + multvec = vector_re_init(multvec, 10) ; /* plenty ! */ + } ; + + /* Find end of current item & '\0' terminate it. */ + c_sp = cp ; + while (1) + { + if (*cp == '|') /* eat '|' */ { - type = cmd_tok_pipe_out ; - ++cp ; - if ((cp < ep) && (*cp == '>')) - ++cp ; + if ((c_sp == cp) || (multiple < 1)) + cmd_fail_item(cmd, "unexpected '|'") ; + *cp++ = '\0' ; + break ; } ; - break ; - case '|': /* '|' separator. */ - end = true ; - if (cp == sp) - type = cmd_tok_pipe_out ; - ++cp ; - break ; - - case '<': /* '<' or '<|' separators. */ - end = true ; - if (cp == sp) + if (*cp == ')') /* eat ')' */ { - type = cmd_tok_pipe_in ; - ++cp ; - if ((cp < ep) && (*cp == '|')) - ++cp ; - if ( (cp < ep) && - ((*cp == '+') || (*cp == '-') || (*cp == '*')) ) - ++cp ; + if ((c_sp == cp) || (multiple < 1)) + cmd_fail_item(cmd, "unexpected ')'") ; + *cp++ = '\0' ; + multiple = 2 ; + + if ((*cp != ' ') && (*cp != '\0')) + cmd_fail_item(cmd, "expect ' ' or nothing after ')'") ; } ; - break ; - case '!': /* '!' and '#' separators. */ - case '#': - end = true ; - if (cp == sp) + if (*cp == ' ') { - type = cmd_tok_comment ; - cp = ep ; + *cp++ = '\0' ; + break ; } ; - break ; - default: + if (*cp == '\0') + break ; + ++cp ; - break ; - } ; - } while (!end && (cp < ep)) ; + } ; + + /* Create the next item and push */ + + n = cmd_make_item(cmd, c_sp, d_sp) ; + + if (multiple == 0) + vector_push_item(cmd->items, n) ; + else + vector_push_item(multvec, n) ; + + /* Extra checks for multiple item. */ + if (multiple > 0) + { + n->arg = true ; /* always */ + + if (cmd_item_is_option(n->type)) + cmd_fail_item(cmd, "cannot have [option] inside (..)") ; + + /* could lift this restriction, but need to check that + * do not have a WORD|.VAR together, because that is tautologous. + */ + + if (cmd_item_is_vararg(n->type)) + cmd_fail_item(cmd, "cannot have .vararg inside (..)") ; + } ; + + /* Check optional item state -- can only be trailing */ + if (cmd_item_is_option(n->type)) + opt = true ; + else if (opt) + cmd_fail_item(cmd, "can only have [option] after [option]") ; + + /* Check vararg item state -- can only be trailing */ + if (vararg) + cmd_fail_item(cmd, "cannot have anything after .vararg") ; + else if (cmd_item_is_vararg(n->type)) + { + vararg = true ; + cmd->vararg = n ; /* remember for parsing */ + } ; + + } while (multiple == 1) ; + + /* count the item */ + if (!opt) + ++cmd->nt_min ; + ++cmd->nt ; + if (!vararg) + ++cmd->nt_max ; + else + cmd->nt_max = UINT_MAX ; + + /* Complete the multiple item. + * + * Sort the items so that are always used and presented in the same + * order. Check that the items are unique. + * + * We must have at least one item. + */ + if (multiple == 2) + { + cmd_item n, p ; + uint i ; + + assert(vector_length(multvec) >= 1) ; + + vector_sort(multvec, (vector_sort_cmp*)cmd_cmp_item) ; + + n = vector_get_item(multvec, 0) ; + vector_push_item(cmd->items, n) ; - cmd_token_push(parsed->tokens, - cmd_token_new(type, sp, cp - sp, sp - line)) ; + for (i = 1 ; i < vector_length(multvec) ; ++i) + { + p = n ; + n = vector_get_item(multvec, i) ; + + p->next = n ; + n->next = NULL ; + + if (p->type == n->type) + { + bool repeat ; + + if (n->type == item_keyword) + repeat = strcmp(n->str, p->str) == 0 ; + else if (n->type == item_range) + repeat = cmd_cmp_range_items(n, p) == 0 ; + else + repeat = true ; + + if (repeat) + cmd_fail_item(cmd, "repeated items in (...)") ; + } ; + } ; + } + else + assert(multiple == 0) ; } ; - return total ; + vector_reset(multvec, free_it) ; + + /* Reduce the vector to the minimum size required */ + vector_decant(cmd->items) ; } ; /*------------------------------------------------------------------------------ - * Process token to remove quotes and escapes (if any). + * Validate a compiled item * - * Returns: true <=> OK - * false => invalid escape or incomplete quotes + * Checks that the contents of the cmd_command are consistent with the + * contents of the srcvec. * - * NB: if fails, returns token completed as far as possible. */ -extern bool -cmd_token_do_complete(token t) +extern void +cmd_compile_check(cmd_command cmd) { - char *s, *p, *q ; - char ch ; - bool ok = true ; - bool dq = false ; + bool ok ; + + uint nt_min = 0 ; + uint nt = 0 ; + uint nt_max = 0 ; + cmd_item vararg = NULL ; + + ok = true ; - p = s = cmd_token_value(t) ; - q = p ; - while (*p != '\0') + /* Require the following to be set to something */ + if ( (cmd->string == NULL) + || (cmd->func == NULL) + || (cmd->items == NULL) + || (cmd->r_string == NULL) + || (cmd->r_doc == NULL) ) + ok = false ; + + /* cmd->nt must match the vector_length and be non-zero. */ + ok = ok && ((nt = vector_length(cmd->items)) == cmd->nt) ; + ok = ok && (nt != 0) ; + + /* Walk the vector of items, and check that those are OK. */ + if (ok) { - switch (*p) - { - case '\'': - ++p ; /* skip leading '\'' */ - while (1) + uint ii = 0 ; + bool opt = false ; + + for (ii = 0 ; ok && (ii < nt) ; ++ii) + { + cmd_item item ; + cmd_item first_item ; + + item = vector_get_item(cmd->items, ii) ; + if (item == NULL) + { + ok = false ; + break ; + } ; + + if (vararg != NULL) /* nothing after vararg */ + { + ok = false ; + break ; + } ; + + first_item = item ; + while (ok && (item != NULL)) { - if (*p == '\0') + /* If this is an option, may only be a single item + * + * Otherwise, after an option must all be options. + */ + if (cmd_item_is_option(item->type)) + { + /* option must be a single item. */ + opt = true ; + if ((item != first_item) || (item->next != NULL)) + { + ok = false ; + break ; + } ; + } + else if (opt) { - ok = false ; /* broken '...' */ + /* once we have an option, must all be options */ + ok = false ; break ; } ; - if (*p == '\'') + /* If this is a vararg, must be the last of this item. + * + * Note that allow for [.varg] and (...., .varg) -- the second + * should be sorted to the back ! + */ + if (cmd_item_is_vararg(item->type)) { - ++p ; /* skip trailing '\'' */ - break ; /* done '....' */ + /* vararg must be last item & only vararg */ + if ((item->next != NULL) || (vararg != NULL)) + { + ok = false ; + break ; + } ; + + vararg = item ; } ; - *q++ = *p++ ; - } ; - break ; - case '"': - ++p ; /* skip '"' */ - dq = !dq ; - break ; + /* If there is a next, this and the next MUST be arg */ + if (item->next != NULL) + { + if (!((item->arg) && (item->next->arg))) + { + ok = false ; + break ; + } ; + } ; - case '\\': - ++p ; /* step past '\\' */ - ch = cmd_token_escape(*p) ; - if (ch == '\0') - { - ok = false ; - ch = *p ; - if (ch == '\0') - ch = '\\' ; /* \ at end is kept */ - else - *q++ = '\\' ; /* otherwise keep \x */ + item = item->next ; } ; - *q++ = ch ; - break ; - default: - *q++ = *p++ ; - } ; - } ; + /* Advance the nt_min and nt_max as required. */ + if (!opt) + ++nt_min ; + + if (vararg == NULL) + ++nt_max ; + else + nt_max = UINT_MAX ; + } ; + } ; + + /* Final checks */ + + ok = ok && (cmd->nt_min == nt_min) + && (cmd->nt == nt) + && (cmd->nt_max == nt_max) + && (cmd->vararg == vararg) ; + + if (!ok) + cmd_fail_item(cmd, "some compile error") ; +} ; + +/*------------------------------------------------------------------------------ + * Reject the cmd_item string or doc. + */ +static void +cmd_fail_item(cmd_command cmd, const char* msg) +{ + fprintf (stderr, "Command parse error!: %s\n", msg) ; + fprintf (stderr, " in command: '%s'\n", cmd->string) ; + exit(2) ; +} + +/*------------------------------------------------------------------------------ + * Advance to matching bracket -- fail if not found. Recurse as required. + * + * Returns: address of matching bracket. + */ +static char* +cmd_item_brackets(cmd_command cmd, char* cp) +{ + char seek ; + + switch (*cp) + { + case '(': + seek = ')' ; + break ; + + case '[': + seek = ']' ; + break ; + + case '<': + seek = '>' ; + break ; + + case '{': + seek = '}' ; + break ; + + default: + return cp ; + } ; + + do + { + ++cp ; + + if (*cp == seek) + return cp ; + else if ((*cp == '(') || (*cp == '[') || (*cp == '<') || (*cp == '{')) + cp = cmd_item_brackets(cmd, cp) ; + } + while (*cp != '\0') ; + + cmd_fail_item(cmd, "unbalanced brackets of some sort") ; + + return cp ; +} ; + +/*------------------------------------------------------------------------------ + * Make descriptor for current item. + * + * cp points at start of '\0' terminated item, which has no spaces and no + * control characters in or around it. Also, if there are brackets, they are + * balanced. + * + * Returns: new descriptor, filled in as required. + */ + +static cmd_item +cmd_make_item(cmd_command cmd, char* cp, char* dp) +{ + char* inner ; + cmd_item n ; + + n = XCALLOC(MTYPE_CMD_ITEM, sizeof(struct cmd_item)) ; + + /* Zeroising has set: + * + * * cmd = NULL -- set below + * * doc = NULL -- set below + * + * * next = NULL -- set elsewhere if multiple. + * + * * type = item_null + * * arg = false -- set if required, below + * + * * range_sign_allowed ) + * * range_sign_required ) -- set elsewhere if required + * * range_min ) + * * range_max ) + */ + confirm(item_null == 0) ; + + n->str = cp ; + n->doc = dp ; + + /* Worry about option state */ + inner = NULL ; + if (*cp == '[') + { + n->arg = true ; /* always true for option */ + + inner = XSTRDUP(MTYPE_TMP, cp) ; + cp = inner + 1 ; /* strip leading '[' */ + *(cp + strlen(cp) - 1) = '\0' ; /* strip trailing ']' */ + + if (*cp == '\0') + cmd_fail_item(cmd, "empty [option]") ; + } ; + + /* Deal with the inner item */ + cmd_make_item_inner(cmd, n, cp) ; + + /* Worry about the option state, again. */ + if (inner != NULL) + { + XFREE(MTYPE_TMP, inner) ; + + if (n->type == item_vararg) + cmd_fail_item(cmd, "cannot have [.vararg]") ; + + n->type = item_option_word ; /* TODO other option types ? */ + } ; + + /* return newly minted cmd_item item */ + assert(n->type != item_null) ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Make inner part of cmd_item -- so can have [...] anything (in principle). + * + * Require '\0' terminated inner part. + */ +static void +cmd_make_item_inner(cmd_command cmd, cmd_item n, char* cp) +{ + bool eat_name_chars ; /* alphanumeric + '-', '_', '.' and ':' */ + + eat_name_chars = false ; + + if (islower(*cp) /* 'a'..'z' */ + || isdigit(*cp)) /* '0'..'9' */ + { + /* item_keyword -- lowercase alpha numeric + '_' and '-' */ + n->type = item_keyword ; + eat_name_chars = true ; + } + else if (*cp == '*') /* '*' */ + { + /* special item_keyword '*' */ + n->type = item_keyword ; + ++cp ; + } + else if (isupper(*cp)) /* 'A'..'Z' */ + { + n->arg = true ; + + /* WORD or other variable */ + if (strcmp(cp, "A.B.C.D") == 0) + n->type = item_ipv4_address ; + else if (strcmp(cp, "A.B.C.D/M") == 0) + n->type = item_ipv4_prefix ; + else if (strcmp(cp, "X:X::X:X") == 0) + n->type = item_ipv6_address ; + else if (strcmp(cp, "X:X::X:X/M") == 0) + n->type = item_ipv6_prefix ; + else + { + n->type = item_word ; + eat_name_chars = true ; + } ; + + if (n->type != item_word) + cp += strlen(cp) ; /* step past "A.B.C.D" et al */ + } + else if (*cp == '.') /* '.' */ + { + n->arg = true ; + n->type = item_vararg ; + eat_name_chars = true ; + } + else if (*cp == '<') /* '<' */ + { + n->arg = true ; + + cp = cmd_make_item_numeric(cmd, n, ++cp) ; + + if (*cp != '>') + cmd_fail_item(cmd, "badly formed <...>") ; + else + ++cp ; + } + else if (*cp == '\0') + cmd_fail_item(cmd, "cannot have an empty item") ; + + if (eat_name_chars) + { + do ++cp ; while ( isalnum(*cp) + || (*cp == '-') + || (*cp == '_') + || (*cp == ':') + || (*cp == '.') ) ; + + } ; + + if (*cp != '\0') + cmd_fail_item(cmd, "invalid item") ; +} ; + +/*------------------------------------------------------------------------------ + * Make <...> types + * + * Require '\0' terminated <...>, pointing after the '<'. + * + * Assumes the '>' is present. + * + * Returns: where processed up to -- pointing at '>' iff OK. + * + * Supports ranges: + * + * 9-10 => unsigned values in 32b range -- NO sign + * + * +9-10 => unsigned value, where '+' is optional + * 9-+10 => unsigned value, where '+' is *required* + * +9-+10 => same as above + * + * -9-10 => signed value, where '+' is optional + * -9-+10 => signed value, where '+' is *required* + * + * -9--8 => signed value, where '-' is required (!) + * + * + * +/-9 => -9-+9 -- sign is required. + * + * In place of decimal number can use 9b -- giving 2^9 - 1. + */ +static char* +cmd_make_item_numeric(cmd_command cmd, cmd_item n, char* cp) +{ + if (isdigit(*cp) || (*cp == '+') || (*cp == '-')) + { + long m ; + bool pm ; + + confirm((LONG_MAX > item_max_number) && (LONG_MIN < -item_max_number)) ; + + n->type = item_range ; + n->range_sign_allowed = false ; + n->range_sign_required = false ; + + if (strncmp(cp, "+/-", 3) == 0) + { + pm = true ; + n->range_sign_required = true ; + cp += 2 ; /* step to '-' to get -ve range_min. */ + } + else + { + pm = false ; + n->range_sign_allowed = (*cp == '+') ; + } ; + + m = cmd_make_item_number(cmd, n, &cp) ; + n->range_min = m ; + + if (pm) + m = -m ; /* for range_max */ + + else if (*cp == '-') + { + ++cp ; /* past the '-' */ + + n->range_sign_required = (*cp == '+') ; + + m = cmd_make_item_number(cmd, n, &cp) ; + } + + else + cmd_fail_item(cmd, "badly formed <0-1>") ; + + n->range_max = m ; + + if (n->range_min > n->range_max) + cmd_fail_item(cmd, "badly formed <0-1> min > max !") ; + + if ((n->range_sign_required) || (n->range_min < 0)) + n->range_sign_allowed = true ; /* allowed if required ! */ + } ; + + return cp ; +} ; + +/*------------------------------------------------------------------------------ + * Get signed or unsigned value -- process the '9b' form. + * + */ +static long +cmd_make_item_number(cmd_command cmd, cmd_item n, char** p_cp) +{ + long m ; + char* cp ; + + cp = *p_cp ; + m = strtol(cp, p_cp, 10) ; + + if ((*p_cp == cp) || (m > item_max_number)) + cmd_fail_item(cmd, "badly formed or out of range number in <...>") ; + + if (**p_cp == 'b') + { + long s ; + + ++(*p_cp) ; /* step past 'b' */ + s = m ; + m = labs(m) ; + if ((m == 0) || (m > 32)) + cmd_fail_item(cmd, "out of range number in 9b form in <...>") ; + + m = ((long)1 << m) - 1 ; + if (s < 0) + m = -m ; + } ; + + return m ; +} ; + +/*------------------------------------------------------------------------------ + * Compare cmd_item items + * + * Note that command types sort with the larger type value before the smaller. + */ +static int +cmd_cmp_item(const cmd_item* a, const cmd_item* b) +{ + if ((*a)->type != (*b)->type) + return ((*a)->type > (*b)->type) ? -1 : +1 ; /* descending order */ + else + { + if ((*a)->type == item_range) + return cmd_cmp_range_items(*a, *b) ; + else + return strcmp ((*a)->str, (*b)->str); + } ; +} ; + +/*------------------------------------------------------------------------------ + * Compare cmd_item item_range items + */ +static int +cmd_cmp_range_items(const cmd_item a, const cmd_item b) +{ + int as, bs ; + + if (a->range_min != b->range_min) + return (a->range_min < b->range_min) ? -1 : +1 ; + + if (a->range_max != b->range_max) + return (a->range_max < b->range_max) ? -1 : +1 ; + + as = a->range_sign_required ? 2 : (a->range_sign_allowed ? 1 : 0) ; + bs = b->range_sign_required ? 2 : (b->range_sign_allowed ? 1 : 0) ; + + if (as != bs) + return (as < bs) ? -1 : +1 ; + + return 0 ; +} ; + +/*============================================================================== + * Token objects + */ + +/*------------------------------------------------------------------------------ + * Make a brand new token object + */ +Private cmd_token +cmd_token_new(void) +{ + return XCALLOC(MTYPE_TOKEN, sizeof(struct cmd_token)) ; + + /* Zeroising the new structure sets: + * + * type = 0 -- cmd_tok_eol + * qs = zeroised qstring -- empty string + * complete = 0 -- false + * + * tp = 0 + * lp = zeroised elstring -- empty string + */ + confirm(cmd_tok_eol == 0) ; + confirm(QSTRING_INIT_ALL_ZEROS) ; + confirm(ELSTRING_INIT_ALL_ZEROS) ; +} ; + +/*------------------------------------------------------------------------------ + * Empty token object and free it. + */ +static void +cmd_token_free(cmd_token t) +{ + qs_reset(t->qs, keep_it) ; /* discard body of qstring */ + XFREE(MTYPE_TOKEN, t) ; +} ; + +/*============================================================================== + * Parser object + */ + +/*------------------------------------------------------------------------------ + * Initialise a new cmd_parsed object, allocating if required + */ +extern cmd_parsed +cmd_parsed_init_new(cmd_parsed parsed) +{ + if (parsed == NULL) + parsed = XCALLOC(MTYPE_CMD_PARSED, sizeof(*parsed)) ; + else + memset(parsed, 0, sizeof(*parsed)) ; + + /* Zeroising the structure has set: + * + * parts = 0 -- cleared by cmd_tokenise() + * tok_total = 0 -- set by cmd_tokenise() + * + * elen = 0 -- set by cmd_tokenise() + * tsp = 0 -- set by cmd_tokenise() + * + * cmd = NULL -- no command yet + * cnode = 0 -- not set + * + * num_tokens = 0 -- set by cmd_tokenise() + * tokens = all zeros -- empty token vector + * + * args = all zeros -- empty vector of arguments + * + * emess = NULL -- no error yet + * eloc = 0 -- no error location + * + * in_pipe = cmd_pipe_none + * out_pipe = cmd_pipe_none + * + * first_in_pipe ) + * num_in_pipe ) + * first_do ) + * num_do ) + * first_command ) none + * num_command ) + * first_out_pipe ) + * num_out_pipe ) + * first_comment ) + * num_comment ) + * + * cti ) set by cmd_token_position() + * rp ) + * + * cmd_v = all zeros -- empty vector of filtered commands + * item_v = all zeros -- empty vector of filtered items + * + * strongest ) + * best_complete ) set by cmd_filter_prepare() + * min_strength ) + * strict ) + */ + confirm(cmd_pipe_none == 0) ; + confirm(TOKEN_VECTOR_INIT_ALL_ZEROS) ; + confirm(ARG_VECTOR_INIT_ALL_ZEROS) ; + + return parsed ; +} ; + +/*------------------------------------------------------------------------------ + * Empty out and (if required) free a cmd_parsed object + */ +extern cmd_parsed +cmd_parsed_reset(cmd_parsed parsed, free_keep_b free_structure) +{ + if (parsed != NULL) + { + cmd_token t ; + + /* Give back all the token objects and release vector body */ + while ((t = vector_ream(parsed->tokens->body, keep_it)) != NULL) + cmd_token_free(t) ; + + vector_reset(parsed->args->body, keep_it) ; /* embedded */ + vector_reset(parsed->cmd_v, keep_it) ; /* embedded */ + vector_reset(parsed->item_v, keep_it) ; /* embedded */ + + if (free_structure) + XFREE(MTYPE_CMD_PARSED, parsed) ; /* sets parsed = NULL */ + else + cmd_parsed_init_new(parsed) ; + } ; + + return parsed ; +} ; + +/*============================================================================== + * Parsing error handling. + * + * + */ + +/*------------------------------------------------------------------------------ + * Register a parsing error. + * + * Takes token in which parsing error was detected, and an offset from the + * start of that, for the location of the error. If the offset is not zero, + * it must be an offset in the original token (!). + * + * The message is a simple constant string (!). + * + * The message will be output as: ..........^ pointing to the location + * followed by: % \n + * + * (The mess does not need to include the '%' or the '\n'.) + * + * Sets: parsed->emess + * parsed->eloc + * + * Returns: CMD_ERR_PARSING -- which MUST only be returned if p + */ +static cmd_return_code_t +cmd_parse_error(cmd_parsed parsed, cmd_token t, usize off, const char* mess) +{ + parsed->emess = mess ; + parsed->eloc = t->tp + off ; + + return CMD_ERR_PARSING ; +} ; + +/*============================================================================== + * Lexical level stuff + */ + +/*------------------------------------------------------------------------------ + * Take elstring and see if it is empty -- only whitespace and/or comment + */ +extern bool +cmd_is_empty(elstring line) +{ + cpp_t lp ; + + els_cpp(lp, line) ; /* NULL -> NULL */ + + while (lp->p < lp->e) + { + if ((*lp->p == ' ') || (*lp->p == '\t')) + ++lp->p ; + else + return ((*lp->p == '!') || (*lp->p == '#')) ; + } ; + + return true ; +} ; + +/*------------------------------------------------------------------------------ + * Reserved characters in a pipe token. + * + * The characters reserved allow for a large number of possible options to + * be attached to the pipe token. + * + * Wish to allow the pipe token to be followed by file name or command name + * without requiring whitespace separation, also do not want to intrude into + * quoted or escaped stuff. So limit the characters that are reserved. + */ +static inline bool +cmd_pipe_reserved_char(char ch) +{ + return strchr("<|>%&*+-=?", ch) != NULL ; +} ; + +/*------------------------------------------------------------------------------ + * Take elstring and break it into tokens. + * + * Discards leading and trailing ' ' or '\t'. + * + * Expects string to have been preprocessed, if required, to ensure that any + * unwanted control characters have been removed. This code only recognises + * '\t' and treats it as whitespace. + * + * Anything between '....' is ignored by the tokenizer. NB: this follows the + * shell convention, so '\' is also ignored and there is no way to include "'" + * in a single quoted string. + * + * Anything immediately preceded by '\' is ignored by the tokenizer. This + * includes blanks and quotes. + * + * Anything inside "...." is ignored by the tokenizer, including '\"' escapes. + * + * Unbalanced "'" or '"' are treated as if eol was a "'" or '"'. + * + * Of the things which are not ignored by the tokenizer: + * + * * tokens are separated by whitespace -- one ' ' or '\t' characters + * The whitespace is discarded. + * + * * tokens which start with any of: + * + * '!', '#', '<' and '>' + * + * terminate themselves, as follows: + * + * - from '!' or '#' to end of line is a comment token. + * + * - '<' followed by pipe_reserved_chars is a token (in_pipe) + * + * - '>' followed by pipe_reserved_chars is a token (out_pipe). + * + * See above for pipe_reserved_ + * + * 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. + * + * NB: the tokenization roughly mimics the (POSIX) standard shell. The + * differences are: + * + * '|' is *not* a pipe ('>|' is), because '|' is a character in the + * regex repertoire. + * + * '<' and '>' do not terminate a token -- so are only significant + * at the start of a token. + * + * '!' is not a comment for the shell. + * + * The requirement for whitespace (or start of line) before '#' is + * consistent with the shell. + * + * The handling of '...' follows the standard shell. + * + * The tokenization does not remove any " ' or \ characters, that is left + * for a later stage, where context may affect the handling. + * + * NB: any control characters other than '\t' are accepted as part of the + * current token ! + * + * The tokens returned contain all the original characters of the line, except + * for the removal of ' ' and '\t' between tokens and at the end of the line. + * + * Note: all the tokens in the vector have at least one character, and no + * entries are NULL. + * + * NB: it is the callers responsibility to release the token objects in due + * course. + * + * NB: the elstring containing the line to be tokenised MUST NOT change + * until the parsed object is finished with. + * + * Returns: the types of all tokens or'd together. + * returns cmd_tok_null if the line is empty (apart from ' ' and '\t') + * or if the elstring was or contained NULL. + * + * Initialises the parsed object, ready for further parsing: + * + * Sets: parsed->parts = cmd_parts_none + * parsed->num_tokens ) + * parsed->elen ) per the results + * parsed->tsp ) + * parsed->tokens ) + * + * Note that the num_tokens does not include the cmd_tok_eol on the end. + */ +extern void +cmd_tokenise(cmd_parsed parsed, qstring line) +{ + cpp_t lp ; + const char *cp, *tp ; + cmd_token_type_t total ; + uint nt ; + + total = 0 ; /* nothing yet */ + nt = 0 ; + + qs_cpp(lp, line) ; /* NULL -> NULL */ + + cp = lp->p ; + tp = cp ; + while (cp < lp->e) /* process to end */ + { + const char* sp ; + bool end ; + cmd_token_type_t type ; + + if ((*cp == ' ') || (*cp == '\t')) + { + /* skip white-space */ + do + { + end = (++cp == lp->e) ; + } while (!end && ((*cp == ' ') || (*cp == '\t'))) ; + + if (end) + break ; + } ; + + end = false ; + sp = cp ; + type = cmd_tok_simple ; + do + { + switch (*cp) + { + case '\t': /* whitespace at end of token */ + case ' ': + end = true ; + break ; + + case '\'': /* proceed to matching ' or end */ + ++cp ; + 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 ; + break ; + + case '"': /* proceed to matching " or end... */ + ++cp ; + 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 */ + { + type = cmd_tok_out_pipe ; + while ((cp < lp->e) && cmd_pipe_reserved_char(*cp)) + ++cp ; + } ; + break ; + + case '<': /* '<' special at start */ + end = (cp == sp) ; + ++cp ; + if (end) /* if special */ + { + type = cmd_tok_in_pipe ; + while ((cp < lp->e) && cmd_pipe_reserved_char(*cp)) + ++cp ; + } ; + break ; + + case '!': /* '!' and '#' special at start */ + case '#': + if ((cp == sp) && (nt == 0)) + { + end = true ; + type = cmd_tok_comment ; + cp = lp->e ; + } + else + ++cp ; + break ; + + default: + ++cp ; + break ; + } ; + } while (!end && (cp < lp->e)) ; + + cmd_token_set(parsed->tokens, nt, type, sp, cp - sp, sp - lp->p) ; + ++nt ; + total |= type ; + + tp = cp ; + } ; + + /* When we get here, tp points just after last character of last token, + * or at start of line if the line is blank (other than whitespace). + * + * If line is empty (apart from whitespace): + * + * - the effective length is zero. + * - the trailing space count is the number of (leading) spaces. + * + * If line is not empty: + * + * - the start position of the first token is the number of leading spaces. + * - the trailing space count is the number of spaces after the last + * token. (If the last token is comment, this will be zero.) + */ + parsed->parts = cmd_parts_none ; + parsed->tok_total = total ; + parsed->num_tokens = nt ; + parsed->elen = tp - lp->p ; + parsed->tsp = lp->e - tp ; + + /* Append an empty end of line token. */ + cmd_token_set(parsed->tokens, parsed->num_tokens, cmd_tok_eol, + lp->e, 0, lp->e - lp->p) ; +} ; + +/*------------------------------------------------------------------------------ + * Process in-pipe token and set the required bits in the pipe type word + * + * Known tokens are: < <| <+ <|+ + */ +static cmd_return_code_t +cmd_parse_in_pipe(cmd_parsed parsed, cmd_token t) +{ + cpp_t p ; + bool ok ; + + els_cpp(p, t->ot) ; + + ok = ((p->p < p->e) && (*p->p++ == '<')) ; + + if (ok) + { + /* First character after '<' may qualify the type of the pipe */ + parsed->in_pipe = cmd_pipe_file ; + + if (p->p < p->e) + { + switch (*p->p++) + { + case '|': + parsed->in_pipe = cmd_pipe_shell ; + break ; + + default: + --p->p ; /* put back */ + break ; + } + } ; + + /* Deal with option characters */ + while (ok && (p->p < p->e)) + { + /* Eat option character, if recognise it. */ + switch (*p->p++) + { + case '+': /* reflect command lines */ + parsed->in_pipe |= cmd_pipe_reflect ; + break ; + + default: + --p->p ; + ok = false ; + break ; + } ; + } ; + } ; + + if (!ok) + return cmd_parse_error(parsed, t, 0, "invalid 'pipe in'") ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Process out-pipe token and set the required bits in the pipe type word + * + * Known tokens are: > >> >| >* + */ +static cmd_return_code_t +cmd_parse_out_pipe(cmd_parsed parsed, cmd_token t) +{ + cpp_t p ; + bool ok ; + + els_cpp(p, t->ot) ; + + ok = ((p->p < p->e) && (*p->p++ == '>')) ; + + if (ok) + { + /* First character after '>' may qualify the type of the pipe */ + parsed->out_pipe = cmd_pipe_file ; + + if (p->p < p->e) + { + switch (*p->p++) + { + case '>': + parsed->out_pipe |= cmd_pipe_append ; + break ; + + case '|': + parsed->out_pipe = cmd_pipe_shell ; + break ; + + case '*': + parsed->out_pipe = cmd_pipe_dev_null ; + break ; + + default: + --p->p ; /* put back */ + break ; + } + } ; + + /* Could now have options, but presently do not */ + if (p->p < p->e) + { + ok = false ; + } ; + } ; + + if (!ok) + return cmd_parse_error(parsed, t, 0, "invalid 'pipe out'") ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * If token is incomplete make a copy of it and '\0' terminate. + * + * If if contains quotes or escapes process those down. + * + * For Quagga purposes, the following is done: + * + * inside '...': all characters stand for themselves, except '\t' -> ' ', + * and, of course, the terminating '. + * + * This is just like the shell. + * + * inside "...": all characters stand for themselves, except '\t' -> ' ', + * and, of course, the terminating ", plus the following + * \x escapes are processed: + * + * \" -> " -- so can have " in "...." + * \\ -> \ -- so can have \ in "...." + * \$ -> $ -- so can have $ in "...." + * + * outside quotes: all characters stand for themselves, except: + * + * \sp -> sp -- so can escape the odd space (cf shell) + * \tab -> sp -- ditto of tab, but map to space + * \? -> ? ) + * \' -> ' ) + * \" -> " ) so can escape the odd meta character + * \< -> < ) where required or to taste. + * \> -> > ) + * \! -> ! ) + * \# -> \# ) + * + * NB: with the exception of $ inside of "..." the carefully avoids the + * regex meta characters: .*+?^$_ and (|)[-] + * and the regex escaped forms of those and the back reference \1 etc. + * + * $ in "..." is reserved for future use as a variable or other + * substitution start. + * + * Returns: CMD_SUCCESS <=> OK + * CMD_ERR_PARSE <=> invalid escape or incomplete quotes + * + * NB: if fails, returns token completed as far as possible. + */ +static cmd_return_code_t +cmd_token_complete(cmd_parsed parsed, cmd_token t) +{ + cpp_t p ; + pp_t q ; + bool dq ; + cmd_return_code_t ret ; + + if ((t->type & cmd_tok_incomplete) == 0) + return CMD_SUCCESS ; /* Quick out if nothing to do */ + + /* To process quotes etc works from the elstring that points to + * the original line, to the qstring. + * + * For quotes and escapes, the result is always no longer than the + * original. + */ + els_cpp_nn(p, t->ot) ; /* original token */ + + qs_new_size(t->qs, p->e - p->p) ; /* discard alias & set qs to be + big enough for original */ + qs_pp_nn(q, t->qs) ; /* where to complete token to */ + + ret = CMD_SUCCESS ; + dq = false ; + while (p->p < p->e) + { + switch (*p->p) + { + case '\t': + *q->p++ = ' ' ; /* '\t' -> ' ' */ + ++p->p ; + break ; + + case '\'': + ++p->p ; /* skip leading ' */ + while (1) + { + if (p->p == p->e) + { + ret = cmd_parse_error(parsed, t, 0, "missing closing '") ; + break ; + } ; + + if (*p->p == '\'') + { + ++p->p ; /* skip trailing ' */ + break ; /* done '....' */ + } ; + + if (*p->p == '\t') + { + *q->p++ = ' ' ; /* '\t' -> ' ' */ + ++p->p ; + } + else + *q->p++ = *p->p++ ; /* rest as is */ + } ; + break ; + + case '"': + ++p->p ; /* skip " */ + dq = !dq ; /* switch state */ + break ; + + case '\\': + *q->p++ = *p->p++ ; /* copy the \ */ + if (p->p == p->e) + ret = cmd_parse_error(parsed, t, 0, "trailing \\") ; + else + { + if (dq) + { + /* inside "...": \", \$ and \\ only */ + if ((*p->p == '"') || (*p->p == '$') || (*p->p == '\\')) + --q->p ; /* strip the \ */ + } + else + { + /* outside quotes: \sp \tab \< \> \! \# */ + if ( (*p->p == '\t') || (*p->p == ' ') || + (*p->p == '\'') || (*p->p == '"') || + (*p->p == '<') || (*p->p == '>') || + (*p->p == '!') || (*p->p == '#') ) + --q->p ; /* strip the \ */ + } ; + + if (*p->p != '\t') + *q->p++ = *p->p++ ; + else + { + *q->p++ = ' ' ; + ++p->p ; + } ; + } ; + break ; + + default: + *q->p++ = *p->p++ ; + break ; + } ; + } ; + + if (dq) + ret = cmd_parse_error(parsed, t, 0, "missing closing \"") ; + + if (ret == CMD_SUCCESS) + { + *q->p = '\0' ; /* '\0' terminate */ + qs_set_len_nn(t->qs, q->p - qs_char_nn(t->qs)) ; + t->term = true ; + } + else + { + qs_set_alias_els(t->qs, t->ot) ; + } ; + + return ret ; +} + +/*------------------------------------------------------------------------------ + * Tokenise the given line and work out where cursor is wrt tokens. + * + * Looks for first token whose end is at or beyond the cursor position. Note + * that: + * + * * where the cursor is just after the last character of a token, it is at + * the end of that token. + * + * * where there is more than one space between tokens, and the cursor is + * after the first space, then it is deemed to be "on" the second token. + * + * - if there are spaces at the start of the line and the cursor is on + * one of those spaces, the it is "on" the first token. + * + * - if the line is blank, with zero or more spaces, the cursor is "on" + * the eol token. + * + * Note that where a line ends in a comment, there are no trailing spaces. + * + * Returns: true <=> "in a special place" + * + * Is "in a special place" if the cursor is: + * + * a. in a quoted string of any type + * b. in an escape (so immediately after a '\') + * c. after the '!' or '#' on a line which consists only of a comment + * d. after the first '<' or '>' of the first pipe token on the line + * + * If NOT "in a special place", will set: + * + * * parsed->cti -- cursor token index + * * parsed->rp -- cursor position relative to the start of token + * + * Note that the cursor token may be a comment or pipe token, or the eol token + * at the end of the line. + */ +extern bool +cmd_token_position(cmd_parsed parsed, qstring line) +{ + cmd_token t ; + uint ti ; + uint cp, ep ; + + cpp_t p ; + const char* q ; + const char* e ; + bool sq, dq, bs ; + + /* Re-initialise parsed object and tokenise the given line */ + cmd_tokenise(parsed, line) ; + + /* Get the cursor position */ + cp = qs_cp_nn(line) ; + + /* Look for the last token whose end is <= cp + * + * Will position on last, "eol" token -- which is not counted in + * parsed->num_tokens -- if is beyond the last real token on the line. + */ + t = NULL ; + ti = 0 ; + while (1) + { + t = cmd_token_get(parsed->tokens, ti) ; + + if (cp > t->tp) + { + /* As soon as we are past '<', '>', '!' or '#' -- return "special" */ + if ((t->type & ( cmd_tok_in_pipe + | cmd_tok_out_pipe | cmd_tok_comment) ) != 0) + return true ; + } ; + + ep = t->tp + els_len_nn(t->ot) ; + + if ((cp <= ep) || (ti == parsed->num_tokens)) + break ; /* stop when found token (or at eol) */ + + ++ti ; + } ; + + parsed->cti = ti ; + parsed->ctl = els_len_nn(t->ot) ; + parsed->rp = (int)cp - (int)t->tp ; + + /* Arrive with t = token in which cp belongs + * + * If the token is incomplete, then need to check for "ins" -- unless is + * already "ins". + */ + if ((t->type & cmd_tok_incomplete) == 0) + return false ; + + /* Scan to see if in '...' or "..." or after '\'. */ + + els_cpp_nn(p, t->ot) ; /* original token */ + + q = p->p + (cp - t->tp) ; /* position interested in */ + assert(q > p->p) ; + + dq = false ; + sq = false ; + bs = false ; + + e = (q <= p->e) ? q : p->e ; /* stop at q or end of token */ + + while (p->p < e) + { + switch (*p->p) + { + case '\'': + if (!dq && !bs) /* ignore ' inside "..." & after \ */ + sq = !sq ; + break ; + + case '\"': /* ignore " inside '...' & after \ */ + if (!sq && !bs) + dq = !dq ; + break ; + + case '\\': + if (!sq) /* ignore \ inside '...' */ + bs = !bs ; /* cope with \\ */ + break ; + + default: + break ; + } ; + + ++p->p ; + } ; + + /* Have scanned to but excluding current character or end of token, whichever + * came first. + * + * If in '...' or "...", then is "ins" + * If is immediately after \, then is "ins". + */ + return sq || dq || (bs && (p->e <= q)) ; +} ; + +/*============================================================================== + * Match functions. + * + * Is the given string a, possibly incomplete, value of the required kind ? + */ + +static match_strength_t cmd_ipv4_match(const char* cp, uint prefix) ; +static match_strength_t cmd_prefix_match(const char* cp, uint prefix) ; + +/*------------------------------------------------------------------------------ + * Is this an IPv4 Address + * + * 999.999.999.999 -- each 0..255, no leading zeros, decimal only. + * + * Returns: mt_no_match -- improperly formed + * mt_ipv4_address_partial -- OK as far as it goes (or empty) + * mt_ipv4_address_complete -- syntactically complete + */ +static match_type_t +cmd_ipv4_address_match(cmd_token t) +{ + match_strength_t ms ; + + ms = cmd_ipv4_match(cmd_token_make_string(t), 0) ; + + if (ms == ms_var_complete) + return mt_ipv4_address_complete ; + if (ms == ms_partial) + return mt_ipv4_address_partial ; + + return mt_no_match ; +} ; + +/*------------------------------------------------------------------------------ + * Is this an IPv4 Prefix + * + * 999.999.999.999/99 -- each 0..255, no leading zeros, decimal only. + * and prefix length must be <= 32 + * + * Returns: mt_no_match -- improperly formed + * mt_ipv4_prefix_partial -- OK as far as it goes (or empty) + * mt_ipv4_prefix_complete -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. + */ +static match_type_t +cmd_ipv4_prefix_match(cmd_token t) +{ + match_strength_t ms ; + + ms = cmd_ipv4_match(cmd_token_make_string(t), 32) ; + + if (ms == ms_var_complete) + return mt_ipv4_prefix_complete ; + if (ms == ms_partial) + return mt_ipv4_prefix_partial ; + + return mt_no_match ; +} ; + +/*------------------------------------------------------------------------------ + * Is this an IPv4 Address or Prefix: + * + * 999.999.999.999[/99] -- each 0..255, no leading zeros, decimal only. + * and prefix length must be <= n + * + * Returns: ms_no_match -- improperly formed + * ms_partial -- OK as far as it goes (or empty) + * ms_var_complete -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. + */ +static match_strength_t +cmd_ipv4_match(const char* cp, uint prefix) +{ + uint nums ; + + for (nums = 0 ; nums < 4 ; ++nums) + { + if (*cp == '.') /* need a '.' except at start */ + { + if (nums == 0) + return ms_no_match ; + + ++cp ; /* step past '.' */ + } + else + { + if (nums != 0) + return (*cp == '\0') ? ms_partial : ms_no_match ; + } ; + + /* Collect a decimal number 0..255, no leading zeros. + * + * Rejects anything other than digits -- including '/' and '.'. + * + * Accepts '\0' as partial -- which accepts empty strings. + */ + if (*cp == '0') + { + ++cp ; + if (isdigit(*cp)) + return ms_no_match ; /* reject leading zeros */ + } + else + { + char* ep ; + + if (isdigit(*cp)) + { + if (strtoul(cp, &ep, 10) <= 255) + cp = ep ; + else + return ms_no_match ; /* reject invalid number */ + } + else + return (*cp == '\0') ? ms_partial : ms_no_match ; + } ; + } ; + + /* Arrive here with 4 numbers */ + + if (prefix == 0) + return (*cp == '\0') ? ms_var_complete : ms_no_match ; + else + return cmd_prefix_match(cp, prefix) ; +} ; + +/*------------------------------------------------------------------------------ + * Is this a Prefix: + * + * /99 no leading zeros, decimal only, value must be <= n. + * + * Arrives here with *cp pointing at where there should be a '/'. + * + * Returns: ms_no_match -- improperly formed + * ms_partial -- OK as far as it goes (or empty). + * ms_var_complete -- syntactically complete + */ +static match_strength_t +cmd_prefix_match(const char* cp, uint prefix) +{ + if (*cp != '/') + return (*cp == '\0') ? ms_partial : ms_no_match ; + + /* OK have '/' and a prefix is now expected. */ + + ++cp ; /* step past '/' */ + + if (*cp == '\0') + return ms_partial ; /* if nothing after '/' */ + + if (*cp == '0') + ++cp ; + else + { + char* ep ; + if (isdigit(*cp) && (strtoul(cp, &ep, 10) <= prefix)) + cp = ep ; + else + return ms_no_match ; /* reject invalid number */ + } ; + + if (*cp != '\0') + return ms_no_match ; /* something other than digits after the '/', + or leading zero, or number too big */ + + return ms_var_complete ; +} ; + +/*------------------------------------------------------------------------------ + * IPv6 Address and Prefix matching. + */ + +#ifdef HAVE_IPV6 + +static match_strength_t cmd_ipv6_match(const char* cp, uint prefix) ; + + + +/*------------------------------------------------------------------------------ + * Is this an IPv6 Address + * + * h:h:... -- RFC 4291 rules & prefix length must be <= n + * + * Returns: mt_no_match -- improperly formed + * mt_ipv6_address_partial -- OK as far as it goes (or empty) + * mt_ipv6_address_complete -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. + */ +static match_type_t +cmd_ipv6_address_match (cmd_token t) +{ + match_strength_t ms ; + + ms = cmd_ipv6_match(cmd_token_make_string(t), 0) ; + + if (ms == ms_var_complete) + return mt_ipv6_address_complete ; + if (ms == ms_partial) + return mt_ipv6_address_partial ; + + return mt_no_match ; +} ; + +/*------------------------------------------------------------------------------ + * Is this an IPv6 Prefix + * + * h:h:...[/99] -- RFC 4291 rules & prefix length must be <= 128 + * + * Returns: mt_no_match -- improperly formed + * mt_ipv6_prefix_partial -- OK as far as it goes (or empty) + * mt_ipv6_prefix_complete -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. + */ +static match_type_t +cmd_ipv6_prefix_match(cmd_token t) +{ + match_strength_t ms ; + + ms = cmd_ipv6_match(cmd_token_make_string(t), 128) ; + + if (ms == ms_var_complete) + return mt_ipv6_prefix_complete ; + if (ms == ms_partial) + return mt_ipv6_prefix_partial ; + + return mt_no_match ; +} ; + +/*------------------------------------------------------------------------------ + * Is this an IPv6 Address or Prefix: + * + * h:h:...[/99] -- RFC 4291 rules & prefix length must be <= n + * + * Returns: ms_no_match -- improperly formed + * ms_partial -- OK as far as it goes (or empty) + * ms_var_complete -- syntactically complete + * + * NB: partly_match is returned for anything valid before the '/', but which + * has no '/' or no number after the '/'. + */ +static match_strength_t +cmd_ipv6_match(const char* cp, uint prefix) +{ + bool double_colon ; + uint nums ; + + double_colon = false ; + nums = 0 ; + + /* At start, the first time around the loop... */ + + for (nums = 0 ; nums < 8 ; ++nums) + { + const char* sp, * ep ; + + /* After number (nums != 0), or at start. + * + * Deal with (a) ':', '::' and '::/'. + * (b) '/' -- valid only if had '::'. + * (c) '\0' -- partial unless have had '::' + * (d) if not at start, must have one of the above. + */ + if (*cp == ':') + { + /* (a) ':', '::' and '::/'. + * + * At start can accept '::', but not ':' (unless '\0' follows). + * + * If not at start, accept ':' and accept '::' if not already seen. + * + * After '::' can have the full complement of numbers, or '/' or + * '\0' which bring the number part to an end. + * + * After ':' we accept '\0' but explicitly reject '/' (and we + * reject a ':' at the start if not followed by '\0'). + */ + ++cp ; /* step past ':' */ + + if (*cp == ':') + { + /* '::' -- counts as number, can be followed by '/' */ + + if (double_colon) + return ms_no_match ; /* at most one */ + + ++cp ; /* step past '::' */ + + double_colon = true ; + ++nums ; /* counts as a number */ + + if ((nums == 8) || (*cp == '/') || (*cp == '\0')) + break ; /* no more numbers */ + } + else if (*cp == '\0') + return ms_partial ; /* accepts bare ':', inter alia */ + + else if ((*cp == '/') || (nums == 0)) + return ms_no_match ; + } + else if (*cp == '/') + { + /* (b) '/' -- valid only if had '::'. */ + if (double_colon) + break ; + else + return ms_no_match ; + } + else if (*cp == '\0') + { + /* (c) '\0' -- partial unless have had '::' */ + if (double_colon) + break ; + else + return ms_partial ; /* accept empty string, inter alia */ + } + else if (nums != 0) + /* (d) if not at start, must have one of the above. */ + return ms_no_match ; + + assert(*cp != '\0') ; + + /* Is now at start, or after ':' and is not '\0'. + * + * Require 1..4 hex digits -- will also accept 1..3 decimals ! + * + * Rejects anything else, including '/' at this stage. + */ + sp = cp ; + ep = cp + 4 ; + do + { + if (((*cp >= '0') && (*cp <= '9')) || ((*cp >= 'A') && (*cp <= 'F')) + || ((*cp >= 'a') && (*cp <= 'f'))) + ++cp ; + else + { + if (cp > sp) + break ; + else + return ms_no_match ; /* no digits */ + } + } + while (cp < ep) ; + + /* Watch out for '.' ! */ + + if (*cp == '.') + { + /* Can have IPv4 trailing part, if that would account for the + * last two number parts of the IPv6. + * + * Note that a '.' after something which is not simple decimal + * 0..255 will be rejected by cmd_ipv4_match(). + * + * Note also that we pass through the prefix requirement. + */ + if ((nums == 6) || (double_colon && (nums < 6))) + return cmd_ipv4_match(sp, prefix) ; + else + return ms_no_match ; + } ; + } ; + + /* Arrives here either because nums == 8, or met '/' or '\0' after ':: + * + * So only get here if have a valid end of the digits part of the IPv6 + */ + + assert((nums == 8) || double_colon) ; + + if (prefix == 0) + return (*cp == '\0') ? ms_var_complete : ms_no_match ; + else + return cmd_prefix_match(cp, prefix) ; +} ; + + +#endif /* HAVE_IPV6 */ + +/*------------------------------------------------------------------------------ + * Is this a decimal number in the allowed range: + * + * Returns: mt_no_match -- improperly formed or empty + * mt_range_partial -- OK as far as it went (or empty string) + * mt_range_complete -- syntactically complete + */ +static match_type_t +cmd_range_match (cmd_item item, cmd_token t) +{ + const char* cp, * dp ; + char *ep ; + int base ; + long val; + + confirm((LONG_MAX > item_max_number) && (LONG_MIN < -item_max_number)) ; + + cp = cmd_token_make_string(t) ; + + /* Worry about any sign */ + + dp = cp ; + + if ((*cp == '-') || (*cp == '+')) + { + if (!item->range_sign_allowed) + return mt_no_match ; /* reject '-' or '+' */ + + ++dp ; /* step to digit */ + } + else + { + if (item->range_sign_required) + return mt_no_match ; + } ; + + /* Worry about leading digits and hex, and no digits at all */ + + base = 10 ; /* by default. */ + + if (*dp == '\0') + return mt_range_partial ; /* accepts empty string, inter alia */ + + else if (*dp == '0') + { + ++dp ; /* step past zero */ + + if (*dp != '\0') + { + /* No leading zeros and no stinking octal -- but allow hex */ + if ((*dp != 'x') && (*dp != 'X')) + return mt_no_match ; + + ++dp ; /* step past 'x' or 'X' */ + base = 16 ; + + if (*dp == '\0') + return mt_range_partial ; + } ; + } + + else if (!isdigit(*dp)) + return mt_no_match ; + + /* The string starts with digit, possibly preceded by sign, and possibly + * an 'x' or 'X' with at least 1 further character. + */ + val = strtol(cp, &ep, base) ; + if (*ep != '\0') + return mt_no_match ; + + /* Is the result in range ? */ + + if (val >= item->range_min && val <= item->range_max) + return mt_range_complete ; /* on the money */ + + /* Want to return mt_range_partial iff adding digits might make + * an in range value. + * + * If val is < 0, then adding digits makes it smaller. + * If val is == 0, not allowed to add digits. + * If val is > 0, then adding digits makes it bigger. + */ + if (val < item->range_min) + { + /* Is less than minimum, so partial match if can get bigger. */ + return (val > 0) ? mt_range_partial : mt_no_match ; + } + else + { + /* Is more than maximum, so partial match if can get smaller. */ + return (val < 0) ? mt_range_partial : mt_no_match ; + } ; +} ; + +/*============================================================================== + * Command "filtering". + * + * The command parsing process starts with a (shallow) copy of the cmd_vector + * entry for the current "node". + * + * So cmd_v contains pointers to struct cmd_command values. When match fails, + * the pointer is set NULL -- so parsing is a process of reducing the cmd_v + * down to just the entries that match. + * + * Each cmd_command has a vector "items", which contains an entry for each + * "token" position. That entry is a vector containing the possible values at + * that position. + */ + +static int cmd_item_filter(cmd_parsed parsed, cmd_item item, cmd_token t) ; + +/*------------------------------------------------------------------------------ + * Prepare to filter commands in the node being parsed in. + * + * The execute option turns off all partial matching -- so will not match, say, + * 222 as a possible IP address ! This means that the result of the filter + * operation will be executable command(s), only. + * + * The execute option also pre-filters the command vector to discard all + * commands which are too short or too long to match the current line. + * + * The strict option turns off partial matching of keywords, so only complete + * keywords will do. This is used for the configuration file, so that new + * commands can be added ! + * + * Returns: number of commands which may match to. + */ +static uint +cmd_filter_prepare(cmd_parsed parsed, cmd_parse_type_t type) +{ + vector src_v ; + uint ci ; + uint nct ; + + bool execution = (type & cmd_parse_execution) != 0 ; + bool strict = (type & cmd_parse_strict) != 0 ; + + /* get commands for the current node */ + src_v = ((cmd_node)vector_get_item(node_vector, parsed->cnode)) + ->cmd_vector ; + + assert(src_v != NULL) ; /* invalid parsed->cnode ?? */ + + /* empty the working commands vector, making sure big enough for the current + * node's commands. + * + * Note that the cmd_v lives for as long as the parsed object, so will + * grow over time to accommodate what is required. + */ + vector_re_init(parsed->cmd_v, vector_length(src_v)) ; + + /* Filter out commands which are too short. + * + * For execution, can filter out commands which are too long. + */ + nct = parsed->num_command ; + + for (ci = 0 ; ci < vector_length(src_v) ; ++ci) + { + cmd_command cmd ; + + cmd = vector_get_item(src_v, ci) ; + + if ( cmd->nt_max < nct) + continue ; /* ignore if too short */ + + if ((cmd->nt_min > nct) && execution) + continue ; /* ignore if too long */ + + vector_push_item(parsed->cmd_v, cmd) ; + } ; + + /* Other preparation for filtering + * + * The min_strength is set according to whether is for execution. The + * strongest match is set from that. This means that any match which does + * not meet the minimum criterion is automatically excluded. + */ + + parsed->min_strength = execution ? ms_min_execute : ms_min_parse ; + parsed->strict = strict ; + + parsed->strongest = parsed->min_strength ; + parsed->best_complete = mt_no_match ; + + return vector_length(parsed->cmd_v) ; +} ; + +/*------------------------------------------------------------------------------ + * Filter set commands by matching items against the given token. + * + * Takes: parsed -- cmd_parsed object, previously prepared by + * cmd_prepare_fiter(). + * ii -- item index (0 == first) + * keep_items -- how to filter + * + * The item index is the index wrt the start of the commands being matched, + * not the index of the token in the command line. + * + * Keywords must match strictly or partly, depending on the filtering state + * in the parsed object. + * + * Variables must match completely if filtering for execution, otherwise may + * match partially. + * + * Returns: the number of items matched. + * + * NB: when filtering for execution, will not accept any partial matches for + * variables (but may accept partial matches for keywords). So, once the + * last token has been filtered against, the number of items matched gives + * the number of unambiguous commands, with complete values, that the line + * matches. + */ +static uint +cmd_filter(cmd_parsed parsed, uint ii, bool keep_items) +{ + uint ci ; /* command index -- in cmd_v */ + uint ti ; /* token index in command line */ + uint c_keep ; + uint i_keep ; + cmd_token t ; + + /* Reset the filtering state */ + + parsed->strongest = parsed->min_strength ; + parsed->best_complete = mt_no_match ; + + /* If we are keeping the items which are matched, set the item_v + * empty and guess that may get one item per command. + * + * Note that the item_v lives for as long as the parsed object, so will + * grow over time to accommodate what is required. + */ + if (keep_items) + vector_re_init(parsed->item_v, vector_length(parsed->cmd_v)) ; + + /* Work down the cmd_v, attempting to match cmd_items against cmd_tokens. + * + * Keep in the cmd_v the commands for which we get a match. + * + * At the same time, if required, keep in the item_v all the items which have + * matched. + */ + if (ii < parsed->num_command) + ti = parsed->first_command + ii ; /* map to token index */ + else + { + /* special case of filtering against the empty token at the end of + * the command line -- this is for command line help stuff. + * + * Note that there may be out_pipe or even comment tokens in between, + * so we here set the ti to the trailing eol token. + */ + assert(ii == parsed->num_command) ; + ti = parsed->num_tokens ; + } + t = cmd_token_get(parsed->tokens, ti) ; + + 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 ; + cmd_item item ; + int best ; + + cmd = vector_get_item(parsed->cmd_v, ci) ; + + if (ii < cmd->nt) + item = vector_get_item(cmd->items, ii) ; + + else if (ii == cmd->nt_max) + item = &eol_item ; /* match at end of line */ + + else if (ii > cmd->nt_max) + continue ; /* discard commands we are beyond, now */ + + else /* cmd->nt < cmd->nt_max <=> vararg. */ + { + /* It is elsewhere arranged that a vararg is always the last + * item for a given command. + * + * We cope with all tokens from the first vararg onwards by matching + * them all against the vararg. Inter alia this allows for a + * vararg to check each argument in turn -- iff feel like doing + * that. + */ + item = cmd->vararg ; + + /* Must have something and must now only check against the varag. + * (Not anything else in the (...) if vararg was in one !) + */ + assert((item != NULL) && (item->next == NULL)) ; + } ; + + /* See if get any sort of match at current position */ + best = -1 ; + while (item != NULL) + { + int ret ; + ret = cmd_item_filter(parsed, item, t) ; + + if (ret >= 0) + { + if (ret > 0) + i_keep = 0 ; + + if (keep_items) + vector_set_item(parsed->item_v, i_keep++, item) ; + else + ++i_keep ; + } ; + + if (ret > best) + best = ret ; + + item = item->next ; + } ; + + /* Keep if had a match */ + if (best >= 0) + { + if (best > 0) + c_keep = 0 ; /* better than all the rest */ + + vector_set_item(parsed->cmd_v, c_keep++, cmd) ; + } ; + } ; + + if (keep_items) + vector_set_length(parsed->item_v, i_keep) ; /* discard what did not keep */ + + vector_set_length(parsed->cmd_v, c_keep) ; /* discard what did not keep */ + + return i_keep ; +} ; + +/*------------------------------------------------------------------------------ + * Filter given item, in given parsed state, attempting to match given token. + * + * Update the parsed state if get a better match. + * + * Returns: < 0 => is not as good ) + * == 0 => equally good ) compared to best match to date. + * > 0 => better than ) + * + * NB: the matching functions will partially match an empty string. + * + * The ms_anything types of item, will match an empty string. + */ +static int +cmd_item_filter(cmd_parsed parsed, cmd_item item, cmd_token t) +{ + match_strength_t ms ; + match_type_t mt ; + int cw ; + + /* Ignore item if the best we can hope for is not as strong as what we + * have already. + */ + mt = item_best_match(item->type) ; + ms = match_match_strength(mt) ; + + if ((mt < parsed->best_complete) || (ms < parsed->strongest)) + return -1 ; /* cannot even be as good */ + + /* Bang the rocks together to get match type + * + * TODO: do we need mt_xxx_partial etc ? + */ + if (t->type == cmd_tok_eol) + { +// mt = (item->type == item_eol) ? mt_eol : mt_eol_partial ; + mt = mt_eol_partial ; + } + else + { + switch (item->type) + { + case item_null: + zabort("invalid item_null") ; + break ; + + case item_eol: + break ; + + case item_keyword: + cw = els_cmp_word(t->ot, item->str) ; + + if (cw > 0) /* nope */ + mt = mt_no_match ; + else if (cw == 0) /* exact match */ + mt = mt_keyword_complete ; + else /* partial match */ + mt = parsed->strict ? mt_no_match + : mt_keyword_incomplete ; + break ; + + case item_range: + mt = cmd_range_match(item, t) ; + break ; + + case item_ipv4_address: + mt = cmd_ipv4_address_match(t) ; + break ; + + case item_ipv4_prefix: + mt = cmd_ipv4_prefix_match(t) ; + break ; + + case item_ipv6_address: + #ifdef HAVE_IPV6 + mt = cmd_ipv6_address_match(t) ; + #endif /* HAVE_IPV6 */ + break ; + + case item_ipv6_prefix: + #ifdef HAVE_IPV6 + mt = cmd_ipv6_prefix_match(t) ; + #endif /* HAVE_IPV6 */ + break ; + + case item_word: + mt = mt_word_match ; + break ; + + case item_vararg: + mt = mt_vararg_match ; + break ; + + case item_option_word: + mt = mt_option_word_match ; + break ; + + default: + zabort("unknown item type") ; + } ; + } ; + + /* Easy if did not match at all */ + if (mt == mt_no_match) + return -1 ; + + /* Is what we got worse, as good or better ? + * + * Update parsed to suit and return the news. + * + * Note that parsed->best_complete will be ms_no_match until parsed->strongest + * is set to ms_var_complete. + */ + ms = match_match_strength(mt) ; + + if (ms < parsed->strongest) + return -1 ; + + if (ms == parsed->strongest) + return 0 ; + + parsed->strongest = ms ; + return +1 ; +} ; + +/*============================================================================== + * Parsing of command lines + */ + +/*------------------------------------------------------------------------------ + * Parse a command in the given "node", if possible, ready for execution. + * + * If 'exact': use cmd_filter_by_string() + * otherwise: use cmd_filter_by_completion() + * + * If 'do': see if there is a 'do' at the front and proceed accordingly. + * + * If 'tree': move up the node tree to find command if not found in the + * current node. + */ + +static cmd_return_code_t cmd_parse_phase_one(cmd_parsed parsed, + cmd_parse_type_t type, node_type_t node) ; +static cmd_return_code_t cmd_parse_phase_one_b(cmd_parsed parsed, uint nt) ; +static cmd_return_code_t cmd_parse_phase_two(cmd_parsed parsed, + cmd_parse_type_t type) ; + +/*------------------------------------------------------------------------------ + * Parse a command in the given "node", or (if required) any of its ancestors. + * + * Requires command line to have been tokenised. + * + * Returns: CMD_SUCCESS => successfully parsed command, and the result is + * in the given parsed structure, ready for execution. + * + * NB: parsed->cnode may not be the incoming node. + * + * NB: parsed->parts is what was found + * + * NB: parsed->cmd->daemon => daemon + * + * CMD_EMPTY => line is empty, except perhaps for comment + * (iff parsing for execution) + * + * CMD_ERR_INCOMPLETE => "do" and nothing more + * (iff parsing for execution) + * + * CMD_SUCCESS_DAEMON => parsed successfully. Something for vtysh ?? + * + * CMD_ERR_NO_MATCH => could find nothing to match the command + * line. + * + * CMD_ERR_AMBIGUOUS => found 2 or more possible matches. + * Or, if not parsing for execution, there + * were no command tokens. + * + * CMD_ERR_PARSING => something wrong ! See parsed->emess. + * + * NB: unless cmd_parse_no_tree, may have tried any ancestor nodes. Returns + * with parsed->cnode with node last tried. + * + * NB: unless cmd_parse_no_do, will have taken a leading "do", and pushed the + * parsed->cnode to ENABLE_NODE (if in MIN_DO_SHORTCUT_NODE or higher). + * + * NB: the command line MUST be preserved until the parsed command is no + * longer required -- no copy is made. + * + * NB: expects to have free run of everything in the vty structure (except + * the contents of the vty_io sub-structure) until the command completes. + * + * See elsewhere for description of parsed structure. + */ +extern cmd_return_code_t +cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type) +{ + cmd_return_code_t ret ; + + /* Level 1 parsing + * + * Break down the tokens into: + * + * 1) in-pipe or command (with or without "do") + * 2) possible out-pipe + * 3) possible comment + * + * Complete any tokens which contain quotes and/or escapes. + * + * If there is a command then: + * + * - sort out any "do" and cut from start of command. + * - set parsed->cnode (from given node or according to "do") + * - set parsed->cmd = NULL + * - empty the argv vector + * + * Note that cmd_parse_phase_one only returns CMD_SUCCESS or CMD_ERR_PARSING. + */ + ret = cmd_parse_phase_one(parsed, type, node) ; + if (ret != CMD_SUCCESS) + { + assert(ret == CMD_ERR_PARSING) ; /* no other error at this point */ + return ret ; + } ; + + /* If no command tokens, and is parsing for execution, then we are done... + * but watch out for bare "do" + */ + if (((parsed->parts & cmd_part_command) == 0) && + ((type & cmd_parse_execution) != 0)) + { + if ((parsed->parts & ~cmd_part_comment) == cmd_parts_none) + return CMD_EMPTY ; /* accept empty */ + + if ((parsed->parts & cmd_part_do) != 0) + return CMD_ERR_INCOMPLETE ; /* reject "do" alone */ + + return CMD_SUCCESS ; /* accept pipes */ + } ; + + /* Level 2 parsing + * + * Try in the current node and then in parent nodes, if can. + * + * Cannot move up the node tree if is already at CONFIG_NODE or below. + * Note that "do" pushes us to the ENABLE_NODE -- which is below the + * CONFIG_NODE. + * + * Note that when not parsing for execution, may get here with no command + * tokens at all -- in which case cmd_parse_phase_two() will return + * CMD_ERR_AMBIGUOUS. + * + * Note that cmd_parse_phase_two only returns CMD_SUCCESS, CMD_ERR_NO_MATCH + * or CMD_ERR_AMBIGUOUS. + */ + while (1) + { + node_type_t pnode ; + + ret = cmd_parse_phase_two(parsed, type) ; + + if (ret == CMD_SUCCESS) + break ; + + if (ret != CMD_ERR_NO_MATCH) + return ret ; + + confirm(ENABLE_NODE < CONFIG_NODE) ; + + if (((type & cmd_parse_no_tree) != 0) || (parsed->cnode <= CONFIG_NODE)) + return CMD_ERR_NO_MATCH ; + + pnode = cmd_node_parent(parsed->cnode) ; + + if (pnode == parsed->cnode) + return CMD_ERR_NO_MATCH ; /* done if no parent node. */ + + parsed->cnode = pnode ; + } ; + + /* Parsed successfully. + * + * If for execution, fill the arg_vector + * + * The arg_vector is an array of pointers to '\0' terminated strings, which + * are pointers to the relevant tokens' qstring bodies. + */ + + if ((type & cmd_parse_execution) != 0) + { + uint ti ; + + cmd_arg_vector_empty(parsed) ; + + for (ti = 0; ti < parsed->num_command ; ti++) + { + bool take ; + + if (ti < parsed->cmd->nt_min) + { + cmd_item item = vector_get_item (parsed->cmd->items, ti); + + take = item->arg ; /* follow item */ + } + else + take = true ; /* option or vararg token */ + + if (take) + { + cmd_token t ; + t = cmd_token_get(parsed->tokens, parsed->first_command + ti) ; + cmd_arg_vector_push(parsed, cmd_token_make_string(t)) ; + } ; + } ; + } ; + + /* Return appropriate form of success */ + return parsed->cmd->daemon ? CMD_SUCCESS_DAEMON + : CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Phase 1 of command parsing + * + * Scan the tokens to break them up into the sections: + * + * - in-pipe -- '<' etc up to '>' or comment + * - do -- leading 'do' on the command (empty if in-pipe) + * - command -- actual command (empty if in-pipe) + * - out-pipe -- '>' etc up to comment + * - comment -- '!' or '#' onwards + * + * Requires line to have been tokenised -- cmd_tokenise(). + * + * Returns: CMD_SUCCESS -- all is well + * CMD_ERR_PARSING -- parsing error -- malformed or misplaced pipe + * -- malformed quotes/escapes + */ +static cmd_return_code_t +cmd_parse_phase_one(cmd_parsed parsed, cmd_parse_type_t type, node_type_t node) +{ + uint nt = parsed->num_tokens ; + + /* Set command and parsing entries */ + parsed->cnode = node ; + parsed->cmd = NULL ; + + /* pick off any comment */ + if ((parsed->tok_total & cmd_tok_comment) != 0) + { + parsed->num_comment = 1 ; + parsed->first_comment = --nt ; /* implicitly the last */ + parsed->tok_total ^= cmd_tok_comment ; + parsed->parts |= cmd_part_comment ; + } ; + + /* If this is not a simple line need to do some extra work: + * + * - identify and check any pipe items + * - + */ + if (parsed->tok_total == cmd_tok_simple) + { + /* All tokens are simple and there is at least one */ + parsed->first_command = 0 ; + parsed->num_command = nt ; + parsed->parts |= cmd_part_command ; + } + else if (parsed->tok_total != 0) + { + /* There is at least one not-simple cmd_token. */ + cmd_return_code_t ret ; + ret = cmd_parse_phase_one_b(parsed, nt) ; + + if (ret != CMD_SUCCESS) + return ret ; + } ; + + /* If have a have a 'do' at the front, account for it */ + if ((parsed->parts & cmd_part_command) != 0) + { + /* Have a command -- worry about "do" if allowed */ + if (((type & cmd_parse_no_do) == 0) && (node >= MIN_DO_SHORTCUT_NODE)) + { + cmd_token t ; + t = cmd_token_get(parsed->tokens, parsed->first_command) ; + if (els_len_nn(t->ot) == 2) + { + const char* p = els_body_nn(t->ot) ; + if ((*p == 'd') && (*(p+1) == 'o')) + { + node = ENABLE_NODE ; /* change to this node */ + + parsed->num_do = 1 ; + parsed->first_do = parsed->first_command ; + --parsed->num_command ; + ++parsed->first_command ; + parsed->parts |= cmd_part_do ; + /* If have *only* the "do", we don't have a command */ + if (parsed->num_command == 0) + parsed->parts ^= cmd_part_command ; + } ; + } ; + } ; + } ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Phase 1b of command parsing + * + * Tokeniser found at least one of: + * + * - in pipe token + * - out pipe token + * - token with quotes or escapes + * + * Deal with all of those, verifying the syntax of any pipes and completing + * any tokens with quotes etc. + * + * Update the "parts" and the relevant first/num values. + * + * Returns: CMD_SUCCESS -- all is well + * CMD_ERR_PARSING -- parsing error -- malformed or misplaced pipe + * -- malformed quotes/escapes + */ +static cmd_return_code_t +cmd_parse_phase_one_b(cmd_parsed parsed, uint nt) +{ + cmd_return_code_t ret ; + cmd_token t ; + cmd_parts_t parts ; + uint i, n ; + uint* pn ; + + parts = cmd_parts_none ; /* no parts yet */ + n = 0 ; /* no tokens in current part */ + pn = NULL ; /* no current part */ + + for (i = 0 ; i < nt ; ++i) + { + t = cmd_token_get(parsed->tokens, i) ; + + if ((t->type & cmd_tok_simple) != 0) + { + if (parts == cmd_parts_none) + { + parts = cmd_part_command ; + parsed->first_command = i ; + pn = &parsed->num_command ; + n = 0 ; + } ; + } ; + + if ((t->type & cmd_tok_in_pipe) != 0) + { + if (parts != cmd_parts_none) + return cmd_parse_error(parsed, t, 0, "unexpected 'pipe in'") ; + + 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 ; + } ; + + if ((t->type & cmd_tok_out_pipe) != 0) + { + if ((parts == cmd_parts_none) || ((parts & cmd_part_out_pipe) != 0)) + return cmd_parse_error(parsed, t, 0, "unexpected 'pipe out'") ; + + ret = cmd_parse_out_pipe(parsed, t) ; + if (ret != CMD_SUCCESS) + return ret ; + + *pn = n ; /* set number of in-pipe/cmd */ + + parts |= cmd_part_out_pipe ; + parsed->first_out_pipe = i ; + pn = &parsed->num_out_pipe ; + n = 0 ; + } ; + + assert(parts != cmd_parts_none) ; /* dealt with all token types */ + + if ((t->type & cmd_tok_incomplete) != 0) + { + ret = cmd_token_complete(parsed, t) ; + if (ret != CMD_SUCCESS) + return ret ; + } ; + + ++n ; /* count up tokens */ + } ; + + if (pn != NULL) + *pn = n ; /* number in last phase */ + + /* If have an in-pipe or an out-pipe, worry about the number of + * arguments + */ + if ((parts & cmd_parts_pipe) != 0) + { + const char* msg = NULL ; + uint i ; + bool e ; + + /* If there is an in_pipe part, check number of arguments */ + if ((msg == NULL) && ((parts & cmd_part_in_pipe) != 0)) + { + assert(parsed->num_in_pipe > 0) ; + + if (((parsed->in_pipe & cmd_pipe_file) != 0) + && (parsed->num_in_pipe != 2)) + { + if (parsed->num_in_pipe == 1) + { + i = parsed->first_in_pipe ; + e = true ; + msg = "requires file" ; + } + else + { + i = parsed->first_in_pipe + 2 ; + e = false ; + msg = "expects file only" ; + } ; + } ; + + if (((parsed->in_pipe & cmd_pipe_shell) != 0) + && (parsed->num_in_pipe < 2)) + { + i = parsed->first_in_pipe ; + e = true ; + msg = "requires shell command" ; + } ; + } ; + + /* If there is an out_pipe part, check the number of arguments */ + if ((msg == NULL) && ((parts & cmd_part_out_pipe) != 0)) + { + assert(parsed->num_out_pipe > 0) ; + + if (((parsed->out_pipe & cmd_pipe_file) != 0) + && (parsed->num_out_pipe != 2)) + { + if (parsed->num_out_pipe == 1) + { + i = parsed->first_out_pipe ; + e = true ; + msg = "requires file" ; + } + else + { + i = parsed->first_out_pipe + 2 ; + e = false ; + msg = "expects file only" ; + } ; + } ; + + if (((parsed->out_pipe & cmd_pipe_shell) != 0) + && (parsed->num_out_pipe < 2)) + { + assert(parsed->num_out_pipe > 0) ; + + i = parsed->first_out_pipe ; + e = true ; + msg = "requires shell command" ; + } ; + } ; + + if (msg != NULL) + { + t = cmd_token_get(parsed->tokens, i) ; + return cmd_parse_error(parsed, t, e ? els_len_nn(t->ot) : 0, msg) ; + } ; + } ; + + /* It's OK -- so record the parts found and return success */ + parsed->parts |= parts ; + + return CMD_SUCCESS ; +} ; + +/*------------------------------------------------------------------------------ + * Phase 2 of command parsing -- parsing for execution. + * + * Assumes phase 1 completed successfully. + * + * Note that if parsed->num_command == 0, will have constructed a cmd_v, with + * all possible commands in it (depending on cmd_parse_execution). + * + * Returns: CMD_SUCCESS -- parsed successfully + * CMD_ERR_NO_MATCH -- could find nothing that matches + * CMD_ERR_AMBIGUOUS -- found more than one match + * or parsed->num_command == 0 + */ +static cmd_return_code_t +cmd_parse_phase_two(cmd_parsed parsed, cmd_parse_type_t type) +{ + uint ii ; + uint match ; + + /* Prepare to filter commands */ + + cmd_filter_prepare(parsed, type) ; + + match = 2 ; /* in case parsed->num_command == 0 ! */ + + for (ii = 0 ; ii < parsed->num_command ; ii++) + match = cmd_filter(parsed, ii, false) ; + + /* Should end up with one command to execute. */ + if (match == 0) + return CMD_ERR_NO_MATCH ; + + if (match > 1) + return CMD_ERR_AMBIGUOUS ; + + parsed->cmd = vector_get_item(parsed->cmd_v, 0) ; + + return CMD_SUCCESS ; +} ; + +#if 0 +/*------------------------------------------------------------------------------ + * If src matches dst return dst string, otherwise return NULL + * + * Returns NULL if dst is an option, variable of vararg. + * + * NULL or empty src are deemed to match. + */ +static const char * +cmd_entry_function (const char *src, const char *dst) +{ + if (CMD_OPTION (dst) || CMD_VARIABLE (dst) || CMD_VARARG (dst)) + return NULL; + + if ((src == NULL) || (*src == '\0')) + return dst; + + if (strncmp (src, dst, strlen (src)) == 0) + return dst; + + return NULL; +} + +/* If src matches dst return dst string, otherwise return NULL */ +/* This version will return the dst string always if it is + CMD_VARIABLE for '?' key processing */ +static const char * +cmd_entry_function_item (const char *src, const char *dst) +{ + if (CMD_VARARG (dst)) + return dst; + + if (CMD_RANGE (dst)) + { + if (cmd_range_match (dst, src)) + return dst; + else + return NULL; + } + +#ifdef HAVE_IPV6 + if (CMD_IPV6 (dst)) + { + if (cmd_ipv6_match (src)) + return dst; + else + return NULL; + } + + if (CMD_IPV6_PREFIX (dst)) + { + if (cmd_ipv6_prefix_match (src)) + return dst; + else + return NULL; + } +#endif /* HAVE_IPV6 */ + + if (CMD_IPV4 (dst)) + { + if (cmd_ipv4_match (src)) + return dst; + else + return NULL; + } + + if (CMD_IPV4_PREFIX (dst)) + { + if (cmd_ipv4_prefix_match (src)) + return dst; + else + return NULL; + } + + /* Optional or variable commands always match on '?' */ + if (CMD_OPTION (dst) || CMD_VARIABLE (dst)) + return dst; + + /* In case of 'command \t', given src is NULL string. */ + if (src == NULL) + return dst; + + if (strncmp (src, dst, strlen (src)) == 0) + return dst; + else + return NULL; +} + +/*------------------------------------------------------------------------------ + * Check same string element existence. + * + * Returns: 0 => found same string in the vector + * 1 => NOT found same string in the vector + */ +static bool +cmd_unique_string (vector v, const char *str) +{ + unsigned int i; + char *match; + + for (i = 0; i < vector_length (v); i++) + if ((match = vector_get_item (v, i)) != NULL) + if (strcmp (match, str) == 0) + return 0; + return 1; +} + +/* Compare string to description vector. If there is same string + return 1 else return 0. */ +static bool +item_unique_string (vector v, const char *str) +{ + unsigned int i; + struct cmd_item *item; + + for (i = 0; i < vector_length (v); i++) + if ((item = vector_get_item (v, i)) != NULL) + if (strcmp (item->cmd, str) == 0) + return 1; + return 0; +} +#endif +/*============================================================================== + * '?' describe command support. + */ + +/*------------------------------------------------------------------------------ + * Get description of current (partial) command + * + * Returns: NULL => no description available + * + * status set to CMD_ERR_NO_MATCH or CMD_ERR_AMBIGUOUS + * + * or: address of vector of "struct cmd_item" values available. + * + * NB: when a vector is returned it is the caller's responsibility to + * vector_free() it. (The contents are all effectively const, so do not + * themselves need to be freed.) + */ +extern vector +cmd_describe_command (const char* line, node_type_t node, + cmd_return_code_t* status) +{ +#if 0 + vector ret ; + struct cmd_parsed parsed_s ; + cmd_parsed parsed ; + cmd_token_type_t tok_total ; + + /* Set up a parser object and tokenise the command line */ + parsed = cmd_parse_init_new(&parsed_s) ; + tok_total = cmd_tokenise(parsed, line, node) ; + + + + + + + + /* Level 1 parsing + * + * Strip quotes and escapes from all the tokens. + */ + if (tok_total != cmd_tok_simple) + { + ret = cmd_parse_phase_one(parsed) ; + if (ret != CMD_SUCCESS) + return ret ; + } ; + + /* If allowed to 'do', see if there. + * + * 'do' forces command to be parsed in ENABLE_NODE (if allowed) + */ + if ((type & cmd_parse_no_do) == 0) + cmd_try_do_shortcut(parsed) ; + + + + + return cmd_describe_command_real (tokens, node, status); + + + + static vector + cmd_describe_command_real (vector tokens, int node, int *status) + { + unsigned int i; + vector cmd_vector; + #define INIT_MATCHVEC_SIZE 10 + vector matchvec; + struct cmd_command *cmd_command; + unsigned int index; + int ret; + enum match_type match; + char *command; + + /* Set index. */ + if (vector_length (tokens) == 0) + { + *status = CMD_ERR_NO_MATCH; + return NULL; + } + else + index = vector_length (tokens) - 1; + + /* Make copy vector of current node's command vector. */ + cmd_vector = vector_copy (cmd_node_vector (cmdvec, node)); + + /* Prepare match vector */ + matchvec = vector_init (INIT_MATCHVEC_SIZE); + + /* Filter commands. */ + /* Only words precedes current word will be checked in this loop. */ + for (i = 0; i < index; i++) + if ((command = vector_get_item (tokens, i))) + { + match = cmd_filter(command, cmd_vector, i, any_match) ; + + if (match == vararg_match) + { + struct cmd_command *cmd_command; + vector descvec; + unsigned int j, k; + + for (j = 0; j < vector_length (cmd_vector); j++) + if ((cmd_command = vector_get_item (cmd_vector, j)) != NULL + && (vector_length (cmd_command->items))) + { + descvec = vector_get_item (cmd_command->items, + vector_length (cmd_command->items) - 1); + for (k = 0; k < vector_length (descvec); k++) + { + struct cmd_item *item = vector_get_item (descvec, k); + vector_set (matchvec, item); + } + } + + vector_set (matchvec, &item_cr); + vector_free (cmd_vector); + + return matchvec; + } ; + + ret = is_cmd_ambiguous (command, cmd_vector, i, match) ; + if (ret != 0) + { + vector_free (cmd_vector); + vector_free (matchvec); + *status = (ret == 1) ? CMD_ERR_AMBIGUOUS + : CMD_ERR_NO_MATCH ; + return NULL ; + } ; + } + + /* Prepare match vector */ + /* matchvec = vector_init (INIT_MATCHVEC_SIZE); */ + + /* Make sure that cmd_vector is filtered based on current word */ + command = vector_get_item (tokens, index); + if (command) + match = cmd_filter(command, cmd_vector, index, any_match); + + /* Make description vector. */ + for (i = 0; i < vector_length (cmd_vector); i++) + { + vector items ; + + cmd_command = vector_get_item (cmd_vector, i) ; + if (cmd_command == NULL) + continue ; + + /* Ignore cmd_command if no tokens at index position. + * + * Deal with special case of possible completion. + */ + items = cmd_command->items; + if (index >= vector_length (items)) + { + if (command == NULL && index == vector_length (items)) + { + if (!item_unique_string (matchvec, cr_string)) + vector_push_item(matchvec, &item_cr); + } + continue ; + } ; + + /* Check if command is completed. */ + unsigned int j; + vector descvec = vector_get_item (items, index); + struct cmd_item *item; + + for (j = 0; j < vector_length (descvec); j++) + if ((item = vector_get_item (descvec, j))) + { + const char *string; + + string = cmd_entry_function_desc (command, item->cmd); + if (string) + { + /* Uniqueness check */ + if (!item_unique_string (matchvec, string)) + vector_push_item(matchvec, item); + } + } ; + } ; + + vector_free (cmd_vector); + + if (vector_length(matchvec) == 0) + { + vector_free (matchvec); + *status = CMD_ERR_NO_MATCH; + return NULL; + } + + *status = CMD_SUCCESS; + return matchvec; + } - qs_term_here(t->qs, q) ; + cmd_parse_reset(parsed, false) ; - return ok && !dq ; -} + return +#endif + + return NULL ; +} ; +#if 0 /*------------------------------------------------------------------------------ - * Return escaped value of \e. + * Check LCD of matched command. * - * Everything except '0'..'9', 'A'..'Z', 'a'..'z' can be escaped -- these are - * reserved for future actual escapes ! + * Scan list of matched keywords, and by comparing them pair-wise, find the + * longest common leading substring. * - * Returns '\0' if e == '\0' or if \e is an invalid escape. + * Returns: 0 if zero or one matched keywords + * length of longest common leading substring, otherwise. */ -static char -cmd_token_escape(char e) +static int +cmd_lcd (vector matchvec) { - if ((e < '0') || (e > 'z')) - return e ; - return isalpha(e) ? '\0' : e ; -} ; + int n ; + int i ; + int lcd ; + char *sp, *sq, *ss ; -/*============================================================================== - * Parser object - */ + n = vector_end(matchvec) ; + if (n < 2) + return 0 ; -/*------------------------------------------------------------------------------ - * Initialise a new cmd_parsed object, allocating if required - */ -extern cmd_parsed -cmd_parse_init_new(cmd_parsed parsed) -{ - if (parsed == NULL) - parsed = XCALLOC(MTYPE_CMD_PARSED, sizeof(*parsed)) ; - else - memset(parsed, 0, sizeof(*parsed)) ; + ss = vector_get_item(matchvec, 0) ; + lcd = strlen(ss) ; - /* Zeroising the structure has set: - * - * cmd = NULL -- no command parsed, yet - * cnode -- no node set, yet - * - * do_shortcut -- false - * onode -- not material (do_shortcut is false) - * - * pipes = 0 -- cmd_pipe_none - */ - confirm(cmd_pipe_none == 0) ; + for (i = 1 ; i < n ; i++) + { + sq = ss ; + ss = vector_get_item(matchvec, i) ; + sp = ss ; - cmd_token_vector_init(parsed->tokens) ; - cmd_token_vector_init(parsed->read_pipe_tokens) ; - cmd_token_vector_init(parsed->write_pipe_tokens) ; - cmd_arg_vector_init(parsed) ; + while ((*sp == *sq) && (*sp != '\0')) + { + ++sp ; + ++sq ; + } ; + + if (lcd > (sp - ss)) + lcd = (sp - ss) ; + } + return lcd; +} + +#endif + +/*============================================================================== + * Command line help support. + * + * + */ + +static bool cmd_post_last(cmd_parsed parsed) ; - return parsed ; -} ; /*------------------------------------------------------------------------------ - * Empty out and (if required) free a cmd_parsed object + * Look out for the following special cases: + * + * a) before or on '#' or '!' + * + * '#' and '!' are only recognised as the start of a comment at the + * start of a line... So can insert something in front, and what + * currently looks like a comment, suddenly needs to be reconsidered + * as tokens... the first of which happens to start with '!' or '#'. + * + * To save work, and leave open the possibility of trailing comments, + * we treat this case as an "error". + * + * b) before or on '<' + * + * currently, pipe-in must be the first token on the command line. + * + * So if there is anything prior to the '<', treat that as an error. + * If is just spaces before the '<', say that nothing may be placed + * before the '<' (cf nothing may be placed before '!' or '#'). + * + * in these cases no help is available -- return message explaining. + * + * NB: assumes that have already done cmd_token_position(), and therefore that + * if there is a '#' or '<' that cannot be positioned *after* it, or + * indeed that can be positioned after a '>'. */ -extern cmd_parsed -cmd_parse_reset(cmd_parsed parsed, bool free_structure) +extern const char* +cmd_help_preflight(cmd_parsed parsed) { - if (parsed != NULL) + if ((parsed->tok_total & cmd_tok_comment) != 0) { - cmd_empty_parsed_tokens(parsed) ; /* give back tokens */ - cmd_arg_vector_free(parsed) ; /* give back vector body */ + return "cannot have a command on a comment line"; + } ; - if (free_structure) - XFREE(MTYPE_CMD_PARSED, parsed) ; /* sets parsed = NULL */ - else - cmd_parse_init_new(parsed) ; + if ((parsed->tok_total & cmd_tok_in_pipe) != 0) + { + return "cannot have a command and an '<' pipe together" ; } ; - return parsed ; + return NULL ; /* OK ! */ } ; -/*============================================================================== - * Match functions. - * - * Is the given string a, possibly incomplete, value of the required kind ? - */ + /*------------------------------------------------------------------------------ - * Is this an IPv4 Address: + * See if current command line can be completed, and if so, how. + * + * NB: must already have done cmd_token_position(). * - * 999.999.999.999 -- where no part may be > 255 + * Must not be called if cmd_token_position() reported a "special". * - * TODO: cmd_ipv4_match() seems to accept leading '.' ? - * TODO: cmd_ipv4_match() seems to accept leading zeros ? + * Returns: CMD_ERR_PARSING -- the parser could not make sense of the line. * - * Returns: no_match -- improperly formed - * partly_match -- accepts empty string - * exact_match -- syntactically complete */ -extern match_type_t -cmd_ipv4_match (const char *str) +extern cmd_return_code_t +cmd_completion(cmd_parsed parsed, node_type_t node) { - const char *sp; - int dots = 0, nums = 0; - char buf[4]; + cmd_return_code_t ret ; - if (str == NULL) - return partly_match; + /* 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) ; - for (;;) - { - memset (buf, 0, sizeof (buf)); - sp = str; - while (*str != '\0') - { - if (*str == '.') - { - if (dots >= 3) - return no_match; + if (ret == CMD_ERR_PARSING) + return ret ; /* nothing more possible */ + + /* Expect now to have a cmd_v set with the result of the filtering, + * with 0, 1 or more possible commands in it. + * + * Now establish what the alternatives are at the current token position. + * + * We do this by filtering again, on the current token position, and + * asking the filter to collect all the possible items. Note that if the + * current token position is beyond the last actual command token, then + * will be filtering on the empty eol token -- which we arrange to match + * anything, including eol -- which gives the dummy item_cr of type + * item_eol. + */ + assert(parsed->cti >= parsed->first_command) ; + assert(parsed->cti <= parsed->first_command + parsed->num_command) ; - if (*(str + 1) == '.') - return no_match; + cmd_filter(parsed, parsed->cti - parsed->first_command, true) ; - if (*(str + 1) == '\0') - return partly_match; + /* Reduce the list of items available at the current position to + * eliminate duplicates and have the possibilities sorted by type and + * by value. + */ + if (vector_length(parsed->item_v) > 1) + { + cmd_item prev ; + uint ii ; + uint i_keep ; - dots++; - break; - } - if (!isdigit ((int) *str)) - return no_match; + vector_sort(parsed->item_v, (vector_sort_cmp*)cmd_cmp_item) ; - str++; - } + i_keep = 1 ; - if (str - sp > 3) - return no_match; + prev = vector_get_item(parsed->item_v, 0) ; + for (ii = 1 ; ii < vector_length(parsed->item_v) ; ++ii) + { + cmd_item item ; - strncpy (buf, sp, str - sp); - if (atoi (buf) > 255) - return no_match; + item = vector_get_item(parsed->item_v, ii) ; + if (cmd_cmp_item(&item, &prev) != 0) + { + vector_set_item(parsed->item_v, i_keep++, item) ; + prev = item ; + } ; + } ; - nums++; + vector_set_length(parsed->item_v, i_keep) ; + /* discard what did not keep */ + } ; - if (*str == '\0') - break; + return CMD_SUCCESS ; +} ; - str++; - } - if (nums < 4) - return partly_match; - return exact_match; -} /*------------------------------------------------------------------------------ - * Is this an IPv4 Prefix: + * How to insert a newly completed keyword ? * - * 999.999.999.999/99 -- where no part may be > 255, - * and prefix length may not be > 32 + * There are a number of cases: * - * TODO: cmd_ipv4_prefix_match() seems to accept leading '.' ? - * TODO: cmd_ipv4_prefix_match() seems to accept leading zeros ? + * 1) the cti is for the token to be completed. * - * Returns: no_match -- improperly formed - * partly_match -- accepts empty string - * exact_match -- syntactically complete + * May be positioned one or more spaces in front of the keyword. * - * NB: partly_match is returned for anything valid before the '/', but which - * has no '/' or no number after the '/'. + * Will replace any leading spaces and the keyword by the new keyword. + * + * 2) the cti is for the empty eol token + * + * Will replace any leading spaces to the eol keyword, by the new + * keyword. + * + * 3) the cti is for a '>' token, or any other !cmd_tok_simple. + * + * If cti is not exactly on the token, then insert the keyword exactly + * where it is -- no spaces need to be removed or added. + * + * If the cti is on the token, insert the keyword and one space. + * + * The second issue is where to leave the cursor... if the command is now + * complete, wish to leave the cursor at the end of the newly completed + * keyword. Otherwise: + * + * 1) if the next token is a cmd_tok_simple, move to it. + * + * 2) otherwise if there is already a space after the newly completed + * keyword, move past it. + * + * Otherwise, insert a space. + * + * Returns: n = number of characters to move the cursor before + * starting to replace characters (may be -ve) + * *rep = number of characters to replace + * *ins = number of spaces to insert + * *mov = number of spaces to move afterwards (may be -ve) */ -extern match_type_t -cmd_ipv4_prefix_match (const char *str) +extern void +cmd_complete_keyword(cmd_parsed parsed, int* pre, int* rep, int* ins, int* mov) { - const char *sp; - int dots = 0; - char buf[4]; + cmd_token t, nt ; + bool last ; + int gap ; - if (str == NULL) - return partly_match; + /* Is this the last token possible on this command line ? */ + last = cmd_post_last(parsed) ; - for (;;) + /* Get the token we are operating and the following one (if any). + * + * Calculate gap between end of token to be replaced and start of next, + * if any. + * + * gap == 0 => is at eol or on !cmd_tok_simple. + */ + t = cmd_token_get(parsed->tokens, parsed->cti) ; + if (parsed->cti < parsed->num_tokens) { - memset (buf, 0, sizeof (buf)); - sp = str; - while (*str != '\0' && *str != '/') - { - if (*str == '.') - { - if (dots == 3) - return no_match; - - if (*(str + 1) == '.' || *(str + 1) == '/') - return no_match; - - if (*(str + 1) == '\0') - return partly_match; + nt = cmd_token_get(parsed->tokens, parsed->cti + 1) ; + gap = nt->tp - (t->tp + els_len_nn(t->ot)) ; + } + else + { + nt = NULL ; + gap = (parsed->rp < 0) ? -parsed->rp : 0 ; + } ; - dots++; - break; - } + /* Now work out what we need to do. */ - if (!isdigit ((int) *str)) - return no_match; + *pre = 0 ; /* preset values */ + *rep = 0 ; + *ins = 0 ; + *mov = 0 ; - str++; - } + if ((t->type & cmd_tok_simple) != 0) + { + /* Replacing an existing simple token ------------------------------ + * + * Move to start of token and replace it. + */ + *pre = -parsed->rp ; + *rep = parsed->ctl ; - if (str - sp > 3) - return no_match; + /* now what do we do after the token ? */ + assert(nt != NULL) ; - strncpy (buf, sp, str - sp); - if (atoi (buf) > 255) - return no_match; + if ((nt->type & cmd_tok_simple) != 0) + { + /* Next token is simple -- step to it */ + assert(!last) ; - if (dots == 3) + *mov = gap ; + } + else if (nt->type == cmd_tok_eol) { - if (*str == '/') + /* Next token is eol + * + * gap is the number of spaces there will be after the + * newly replaced token. + */ + if (!last) { - if (*(str + 1) == '\0') - return partly_match; - - str++; - break; - } - else if (*str == '\0') - return partly_match; + if (gap == 0) + *ins = 1 ; /* need a trailing space */ + else + *mov = 1 ; /* step over existing space */ + } ; } - - if (*str == '\0') - return partly_match; - - str++; + else + { + /* Next token is something special. + * + * gap is the number of spaces there will be after the + * newly replaced token. + */ + if (gap == 0) + { + *ins = last ? 1 : 2 ; + *mov = -1 ; + } + else if (gap == 1) + { + *ins = last ? 0 : 1 ; + } + else + { + *mov = last ? 0 : 1 ; + } + } ; } - sp = str; - while (*str != '\0') + else if (t->type == cmd_tok_eol) { - if (!isdigit ((int) *str)) - return no_match; + /* Inserting at or before eol -------------------------------------- + * + * Insert exactly where we are -- *pre == 0 + */ - str++; - } + /* If last, replace any spaces between cursor and end of line, + * otherwise, keep one of them. + */ + *rep = last ? gap : (gap > 0) ? gap - 1 : gap ; - if (atoi (sp) > 32) - return no_match; + /* now what do we do after the token ? */ + assert(nt == NULL) ; - return exact_match; -} + if (!last) + { + if (gap == 0) + *ins = 1 ; /* need space after */ + else + *mov = 1 ; /* step over one */ + } ; + } + else + { + /* Inserting at or before something special ------------------------ + * + * Insert exactly where we are -- *pre == 0 + * replace nothing -- *rep == 0 + */ + if (gap == 0) + { + *ins = last ? 1 : 2 ; + *mov = -1 ; + } + else if (gap == 1) + { + *ins = last ? 0 : 1 ; + } + else + { + *mov = last ? 0 : 1 ; + } + } ; +} ; /*------------------------------------------------------------------------------ - * Is this an IPv6 Address: + * Do we have just one command left, and have we just filtered on the last + * possible command item & token ? * - * TODO: cmd_ipv6_match() only returns "partly_match" for empty string ? - * - * Returns: no_match -- improperly formed - * partly_match -- accepts empty string - * exact_match -- syntactically complete + * If so, then there is no point filtering any further, and there is nothing + * more that could be added to this command line. */ +static bool +cmd_post_last(cmd_parsed parsed) +{ + if (vector_length(parsed->cmd_v) == 1) + { + cmd_command cmd ; + cmd = vector_get_item(parsed->cmd_v, 0) ; -#define IPV6_ADDR_STR "0123456789abcdefABCDEF:.%" -#define IPV6_PREFIX_STR "0123456789abcdefABCDEF:.%/" -#define STATE_START 1 -#define STATE_COLON 2 -#define STATE_DOUBLE 3 -#define STATE_ADDR 4 -#define STATE_DOT 5 -#define STATE_SLASH 6 -#define STATE_MASK 7 + return ((parsed->cti - parsed->first_command + 1) == cmd->nt_max) ; + } ; -#ifdef HAVE_IPV6 + return false ; +} ; -extern match_type_t -cmd_ipv6_match (const char *str) -{ - int state = STATE_START; - int colons = 0, nums = 0, double_colon = 0; - const char *sp = NULL; - struct sockaddr_in6 sin6_dummy; - int ret; - if (str == NULL) - return partly_match; - if (strspn (str, IPV6_ADDR_STR) != strlen (str)) - return no_match; - /* use inet_pton that has a better support, - * for example inet_pton can support the automatic addresses: - * ::1.2.3.4 - */ - ret = inet_pton(AF_INET6, str, &sin6_dummy.sin6_addr); - if (ret == 1) - return exact_match; - while (*str != '\0') - { - switch (state) - { - case STATE_START: - if (*str == ':') - { - if (*(str + 1) != ':' && *(str + 1) != '\0') - return no_match; - colons--; - state = STATE_COLON; - } - else - { - sp = str; - state = STATE_ADDR; - } - continue; - case STATE_COLON: - colons++; - if (*(str + 1) == ':') - state = STATE_DOUBLE; - else - { - sp = str + 1; - state = STATE_ADDR; - } - break; - case STATE_DOUBLE: - if (double_colon) - return no_match; - if (*(str + 1) == ':') - return no_match; - else - { - if (*(str + 1) != '\0') - colons++; - sp = str + 1; - state = STATE_ADDR; - } - double_colon++; - nums++; - break; - case STATE_ADDR: - if (*(str + 1) == ':' || *(str + 1) == '\0') - { - if (str - sp > 3) - return no_match; - nums++; - state = STATE_COLON; - } - if (*(str + 1) == '.') - state = STATE_DOT; - break; - case STATE_DOT: - state = STATE_ADDR; - break; - default: - break; - } - if (nums > 8) - return no_match; - if (colons > 7) - return no_match; - str++; - } -#if 0 - if (nums < 11) - return partly_match; -#endif /* 0 */ - return exact_match; -} -/*------------------------------------------------------------------------------ - * Is this an IPv6 Prefix: - * - * TODO: cmd_ipv6_prefix_match() hardly returns "partly_match" ? - * TODO: cmd_ipv6_prefix_match() possibly accepts invalid address before '/' ? - * - * Returns: no_match -- improperly formed - * partly_match -- accepts empty string - * exact_match -- syntactically complete - * - * NB: partly_match is returned for anything valid before the '/', but which - * has no '/' or no number after the '/'. - */ -extern match_type_t -cmd_ipv6_prefix_match (const char *str) + +#if 0 +static vector +cmd_complete_command_real (vector tokens, int node, int *status) { - int state = STATE_START; - int colons = 0, nums = 0, double_colon = 0; - int mask; - const char *sp = NULL; - char *endptr = NULL; + unsigned int i; + unsigned int ivl ; + unsigned int last_ivl ; + vector cmd_v ; +#define INIT_MATCHVEC_SIZE 10 + vector matchvec; + struct cmd_command *cmd_command; + unsigned int index; + struct cmd_item *cmd_item; + vector descvec; + char *token; + int n ; + + /* Stop immediately if the tokens is empty. */ + if (vector_length (tokens) == 0) + { + *status = CMD_ERR_NO_MATCH; + return NULL; + } - if (str == NULL) - return partly_match; + /* Take (shallow) copy of cmdvec for given node. */ + cmd_v = vector_copy (cmd_node_vector (cmdvec, node)); - if (strspn (str, IPV6_PREFIX_STR) != strlen (str)) - return no_match; + /* First, filter upto, but excluding last token */ + last_ivl = vector_length (tokens) - 1; - while (*str != '\0' && state != STATE_MASK) + for (ivl = 0; ivl < last_ivl; ivl++) { - switch (state) + enum match_type match; + int ret; + + /* TODO: does this test make any sense ? */ + if ((token = vector_get_item (tokens, ivl)) == NULL) + continue ; + + /* First try completion match, return best kind of match */ + index = ivl ; + match = cmd_filter_by_completion (token, cmd_v, index) ; + + /* Eliminate all but the selected kind of match */ + ret = is_cmd_ambiguous (token, cmd_v, index, match) ; + + if (ret == 1) { - case STATE_START: - if (*str == ':') - { - if (*(str + 1) != ':' && *(str + 1) != '\0') - return no_match; - colons--; - state = STATE_COLON; - } - else - { - sp = str; - state = STATE_ADDR; - } + /* ret == 1 => either token matches more than one keyword + * or token matches more than one number range + */ + vector_free (cmd_v); + *status = CMD_ERR_AMBIGUOUS; + return NULL; + } +#if 0 + /* For command completion purposes do not appear to care about + * incomplete ipv4 or ipv6 prefixes (missing '/' or digits after). + */ + else if (ret == 2) + { + vector_free (cmd_v); + *status = CMD_ERR_NO_MATCH; + return NULL; + } +#endif + } - continue; - case STATE_COLON: - colons++; - if (*(str + 1) == '/') - return no_match; - else if (*(str + 1) == ':') - state = STATE_DOUBLE; - else - { - sp = str + 1; - state = STATE_ADDR; - } - break; - case STATE_DOUBLE: - if (double_colon) - return no_match; + /* Prepare match vector. */ + matchvec = vector_init (INIT_MATCHVEC_SIZE); - if (*(str + 1) == ':') - return no_match; - else - { - if (*(str + 1) != '\0' && *(str + 1) != '/') - colons++; - sp = str + 1; + /* Now we got into completion */ + index = last_ivl ; + token = vector_get_item(tokens, last_ivl) ; /* is now the last token */ - if (*(str + 1) == '/') - state = STATE_SLASH; - else - state = STATE_ADDR; - } + for (i = 0; i < vector_length (cmd_v); i++) + { + unsigned int j; + const char *string; - double_colon++; - nums += 1; - break; - case STATE_ADDR: - if (*(str + 1) == ':' || *(str + 1) == '.' - || *(str + 1) == '\0' || *(str + 1) == '/') - { - if (str - sp > 3) - return no_match; + if ((cmd_command = vector_get_item (cmd_v, i)) == NULL) + continue ; - for (; sp <= str; sp++) - if (*sp == '/') - return no_match; + descvec = vector_get_item (cmd_command->items, index); + if (descvec == NULL) + continue ; - nums++; + for (j = 0; j < vector_length (descvec); j++) + { + item = vector_get_item (descvec, j) ; + if (item == NULL) + continue ; - if (*(str + 1) == ':') - state = STATE_COLON; - else if (*(str + 1) == '.') - state = STATE_DOT; - else if (*(str + 1) == '/') - state = STATE_SLASH; - } - break; - case STATE_DOT: - state = STATE_ADDR; - break; - case STATE_SLASH: - if (*(str + 1) == '\0') - return partly_match; - - state = STATE_MASK; - break; - default: - break; - } + string = cmd_entry_function(token, item->str) ; + if ((string != NULL) && cmd_unique_string(matchvec, string)) + cmd_add_to_strvec (matchvec, string) ; + } ; + } ; + + n = vector_length(matchvec) ; /* number of entries in the matchvec */ - if (nums > 11) - return no_match; + /* We don't need cmd_v any more. */ + vector_free (cmd_v); + + /* No matched command */ + if (n == 0) + { + vector_free (matchvec); - if (colons > 7) - return no_match; + /* In case of 'command \t' pattern. Do you need '?' command at + the end of the line. */ + if (*token == '\0') + *status = CMD_COMPLETE_ALREADY; + else + *status = CMD_ERR_NO_MATCH; + return NULL; + } - str++; + /* Only one matched */ + if (n == 1) + { + *status = CMD_COMPLETE_FULL_MATCH; + return matchvec ; } - if (state < STATE_MASK) - return partly_match; + /* Check LCD of matched strings. */ + if (token != NULL) + { + unsigned lcd = cmd_lcd (matchvec) ; + + if (lcd != 0) + { + if (strlen(token) < lcd) + { + char *lcdstr; - mask = strtol (str, &endptr, 10); - if (*endptr != '\0') - return no_match; + lcdstr = XMALLOC (MTYPE_STRVEC, lcd + 1); + memcpy (lcdstr, vector_get_item(matchvec, 0), lcd) ; + lcdstr[lcd] = '\0'; - if (mask < 0 || mask > 128) - return no_match; + cmd_free_strvec(matchvec) ; /* discard the match vector */ -/* I don't know why mask < 13 makes command match partly. - Forgive me to make this comments. I Want to set static default route - because of lack of function to originate default in ospf6d; sorry - yasu - if (mask < 13) - return partly_match; -*/ + matchvec = vector_init (1); + vector_push_item(matchvec, lcdstr) ; - return exact_match; -} + *status = CMD_COMPLETE_MATCH; + return matchvec ; + } + } + } -#endif /* HAVE_IPV6 */ + *status = CMD_COMPLETE_LIST_MATCH; + return matchvec ; +} +#endif /*------------------------------------------------------------------------------ - * Is this a decimal number in the allowed range: - * - * Returns: true <=> OK -- *including* empty string - * false => not a valid number, or not in required range - * (or invalid range !!) + * Can the current command be completed ? */ +extern vector +cmd_complete_command (vector tokens, int node, int *status) +{ +#if 0 + vector ret; -#define DECIMAL_STRLEN_MAX 10 + if ( cmd_try_do_shortcut(node, vector_get_item(tokens, 0) ) ) + { + vector shifted_tokens; + unsigned int index; -extern bool -cmd_range_match (const char *range, const char *str) -{ - char *p; - char buf[DECIMAL_STRLEN_MAX + 1]; - char *endptr = NULL; - unsigned long min, max, val; + /* We can try it on enable node, cos' the vty is authenticated */ - if (str == NULL) - return true ; + shifted_tokens = vector_init (vector_count(tokens)); + /* use memcpy? */ + for (index = 1; index < vector_length (tokens); index++) + { + vector_set_index (shifted_tokens, index-1, + vector_lookup(tokens, index)) ; + } - val = strtoul (str, &endptr, 10); - if (*endptr != '\0') - return false ; + ret = cmd_complete_command_real (shifted_tokens, ENABLE_NODE, status); + + vector_free(shifted_tokens); + return ret; + } + + return cmd_complete_command_real (tokens, node, status); +#endif + + *status = CMD_ERR_NO_MATCH; + return NULL; +} ; - range++; - p = strchr (range, '-'); - if (p == NULL) - return false ; - if (p - range > DECIMAL_STRLEN_MAX) - return false ; - strncpy (buf, range, p - range); - buf[p - range] = '\0'; - min = strtoul (buf, &endptr, 10); - if (*endptr != '\0') - return false ; - range = p + 1; - p = strchr (range, '>'); - if (p == NULL) - return false ; - if (p - range > DECIMAL_STRLEN_MAX) - return false ; - strncpy (buf, range, p - range); - buf[p - range] = '\0'; - max = strtoul (buf, &endptr, 10); - if (*endptr != '\0') - return false ; - if (val < min || val > max) - return false ; - return true ; -} diff --git a/lib/command_parse.h b/lib/command_parse.h index ab91e7f4..789fe601 100644 --- a/lib/command_parse.h +++ b/lib/command_parse.h @@ -24,13 +24,15 @@ #ifndef _ZEBRA_COMMAND_PARSE_H #define _ZEBRA_COMMAND_PARSE_H -#include +#include "zconfig.h" /* HAVE_IPV6 */ #include "misc.h" -#include "node_type.h" +#include "command_common.h" #include "vector.h" #include "qstring.h" +#include "elstring.h" +#if 0 /*============================================================================== * Parsing of tokens */ @@ -51,42 +53,397 @@ enum cmd_token_spec cmd_ts_ipv6_prefix, cmd_ts_vararg, +} ; +typedef enum cmd_token_spec cmd_token_spec_t ; +#endif + +/*============================================================================== + * Sexing of token types in command descriptions + */ +#define CMD_OPTION(S) ((S[0]) == '[') +#define CMD_VARIABLE(S) (((S[0]) >= 'A' && (S[0]) <= 'Z') || ((S[0]) == '<')) +#define CMD_VARARG(S) ((S[0]) == '.') +#define CMD_RANGE(S) ((S[0] == '<')) + +#define CMD_IPV4(S) ((strcmp ((S), "A.B.C.D") == 0)) +#define CMD_IPV4_PREFIX(S) ((strcmp ((S), "A.B.C.D/M") == 0)) +#define CMD_IPV6(S) ((strcmp ((S), "X:X::X:X") == 0)) +#define CMD_IPV6_PREFIX(S) ((strcmp ((S), "X:X::X:X/M") == 0)) + +/*============================================================================== + * Command Items. + * + * A command is compiled into a vector of lists of command items -- the + * cmd_command->items (so called). + * + */ + +/* Command item types + * + * NB: the command items sort according to this -- highest first. + * + * NB: this is in the same order as the match_type -- and should remain so. + * See match_type below for further discussion of the hierarchy. + */ +enum cmd_item_type +{ + item_null, + + item_eol, + item_option_word, + item_vararg, /* rest of the line */ + item_word, + item_ipv6_prefix, + item_ipv6_address, + item_ipv4_prefix, + item_ipv4_address, + + item_range, + + item_keyword, + + item_type_count, /* number of types */ +} ; +typedef enum cmd_item_type cmd_item_type_t ; + +enum { + item_max_number = 0xFFFFFFFF /* can be +/- this */ +} ; + +CONFIRM(LONG_MAX >= item_max_number) ; + +/*------------------------------------------------------------------------------ + * Sex cmd_item_type and return whether it is an "option" type, or not. + * + * NB: tolerates item_null -- others may well not. + */ +Inline bool +cmd_item_is_option(cmd_item_type_t itt) +{ + const static bool is_option[item_type_count] = + { + [item_null] = false, + + [item_eol] = false, + + [item_option_word] = true, + + [item_vararg] = false, + [item_word] = false, + + [item_ipv6_prefix] = false, + [item_ipv6_address] = false, + [item_ipv4_prefix] = false, + [item_ipv4_address] = false, + + [item_range] = false, + + [item_keyword] = false, + } ; + + assert((itt >= 0) && (itt < item_type_count)) ; + + return is_option[itt] ; +} ; + +/*------------------------------------------------------------------------------ + * Sex cmd_item_type and return whether it is an "vararg" type, or not. + * + * NB: tolerates item_null -- others may well not. + */ +Inline bool +cmd_item_is_vararg(cmd_item_type_t itt) +{ + const static bool is_vararg[item_type_count] = + { + [item_null] = false, + + [item_eol] = false, + + [item_option_word] = false, + + [item_vararg] = true, + [item_word] = false, + + [item_ipv6_prefix] = false, + [item_ipv6_address] = false, + [item_ipv4_prefix] = false, + [item_ipv4_address] = false, + + [item_range] = false, + + [item_keyword] = false, + } ; + + assert((itt >= 0) && (itt < item_type_count)) ; + + return is_vararg[itt] ; +} ; + +/*------------------------------------------------------------------------------ + * The command item structure. + */ +typedef struct cmd_item* cmd_item ; +struct cmd_item +{ + const char* str ; /* in r_string -- original string form */ + const char* doc ; /* in r_doc -- description text */ + + cmd_item next ; /* Next possibility (if any) */ + + cmd_item_type_t type ; + bool arg ; /* include in argv */ + + /* For item_range values */ + bool range_sign_allowed ; + bool range_sign_required ; + long range_min ; + long range_max ; } ; -typedef enum cmd_token_spec cmd_token_spec_t ; /*============================================================================== - * Completion match types. + * Match strengths types and filter settings. + * + * When matching a token, may have a number of competing items, possibly of + * different types. + * + * For execution a token must match completely -- or, for keyword items, + * match partially at most one possible keyword. + */ + +/* Match strength + * + * When matching a token against an item, the following are the possible + * results. + */ +enum match_strength +{ + ms_no_match = 0, /* match failed: token is definitely NOT + * the item in question. + */ + + ms_min_parse = ms_no_match + 1, + /* for parsing must match somehow ! */ + + ms_partial, /* match OK up to the end of the token, but + * more is required to complete it. + * This is used by the variable matches. + */ + + ms_min_execute = ms_partial + 1, + /* for execution must be at least this */ + + ms_anything, /* matches because will match anything. + * This is used for WORD and such like items. + */ + + ms_kwd_incomplete, /* keyword match OK, but not complete. + */ + + ms_var_complete, /* match succeeded: token is a complete and + * valid instance of the item in question + */ + + ms_kwd_complete /* match succeeded: token is a complete and + * valid instance of the item in question + */ +} ; +typedef enum match_strength match_strength_t ; + + +/* The match type indicates what has been matched and the strength of the + * match. + * + * NB: the order of these is significant, higher numbered match types are + * preferred over lower numbered ones. + * + * NB: this is in the same order as the cmd_item_type -- and should remain so. + * + * During the command filtering, when a token and an item match, and the + * match type is better than the match type to date, then all previous matches + * are discarded. + * + * The hierarchy means that, for example, '22' will match an IPv4 prefix + * partially, but that will be forgotten if it later matches a WORD, and that + * will be forgotten if it later matches a number range. + * + * The IPv6 items have a lower priority so that a simple decimal will prefer + * the simpler IPv4 address form. As soon as there is an 'a'..'f' or a ':', + * the IPv4 will no longer match. + * + * The partial keyword match is preferred over a partial anything else, but + * cannot mask a complete value ! + * + * The relative ranking of: mt_option_word_match, mt_vararg_match, and + * mt_word_match, is more or less arbitrary -- if these are ever candidates + * at the same time, the commands are ambiguous. * - * NB: the order of these is significant -- in particular as confirmed below. */ enum match_type { - no_match = 0, /* nope */ - any_match = 1, + mt_no_match, - extend_match = any_match, + mt_eol_partial, /* a partial match ! */ - ipv4_prefix_match, - ipv4_match, - ipv6_prefix_match, - ipv6_match, - range_match, - vararg_match, + mt_ipv6_address_partial, + mt_ipv6_prefix_partial, + mt_ipv4_address_partial, + mt_ipv4_prefix_partial, - partly_match, /* OK as far as it went */ - exact_match, /* Syntactically complete -- greatest match */ + mt_range_partial, + + mt_eol, /* an ms_anything match */ + + mt_option_word_match, /* anything can match a [WORD] */ + + mt_vararg_match, /* anything can match a .vararg */ + mt_word_match, /* anything can match a WORD */ + + mt_keyword_incomplete, + + mt_ipv6_prefix_complete, + mt_ipv6_address_complete, + mt_ipv4_prefix_complete, + mt_ipv4_address_complete, + + mt_range_complete, + + mt_keyword_complete, match_type_count /* Number of match types */ } ; typedef enum match_type match_type_t ; -CONFIRM(no_match == false) ; -CONFIRM(extend_match == (no_match + 1)) ; -CONFIRM(partly_match == (exact_match - 1)) ; -CONFIRM(exact_match == (match_type_count - 1)) ; +/*------------------------------------------------------------------------------ + * Map match_type -> cmd_item_type + * + * From a match type extract the type of item which has been match, partially + * or completely. + */ +Inline cmd_item_type_t +match_item_type(match_type_t mt) +{ + static cmd_item_type_t match_item_type[match_type_count] = + { + [mt_no_match] = item_null, + + [mt_eol_partial] = item_eol, + + [mt_ipv4_prefix_partial] = item_ipv4_prefix, + [mt_ipv4_address_partial] = item_ipv4_address, + [mt_ipv6_prefix_partial] = item_ipv6_prefix, + [mt_ipv6_address_partial] = item_ipv6_address, + + [mt_range_partial] = item_range, + + [mt_eol] = item_eol, + + [mt_option_word_match] = item_option_word, + + [mt_vararg_match] = item_vararg, + [mt_word_match] = item_word, + + [mt_keyword_incomplete] = item_keyword, + + [mt_ipv6_prefix_complete] = item_ipv6_prefix, + [mt_ipv6_address_complete] = item_ipv6_address, + [mt_ipv4_prefix_complete] = item_ipv4_prefix, + [mt_ipv4_address_complete] = item_ipv4_address, + + [mt_range_complete] = item_range, + + [mt_keyword_complete] = item_keyword, + } ; + + assert((mt >= 0) && (mt < match_type_count)) ; + + return match_item_type[mt] ; +} ; + +/*------------------------------------------------------------------------------ + * Map match_type -> match_strength. + * + * From a match type extract the strength of the match. + */ +Inline match_strength_t +match_match_strength(match_type_t mt) +{ + const static match_strength_t match_match_strength[match_type_count] = + { + [mt_no_match] = ms_no_match, + + [mt_eol_partial] = ms_partial, + + [mt_ipv6_prefix_partial] = ms_partial, + [mt_ipv6_address_partial] = ms_partial, + [mt_ipv4_prefix_partial] = ms_partial, + [mt_ipv4_address_partial] = ms_partial, + + [mt_range_partial] = ms_partial, + + [mt_eol] = ms_anything, + + [mt_option_word_match] = ms_anything, + + [mt_vararg_match] = ms_anything, + [mt_word_match] = ms_anything, + + [mt_keyword_incomplete] = ms_kwd_incomplete, + + [mt_ipv6_prefix_complete] = ms_var_complete, + [mt_ipv6_address_complete] = ms_var_complete, + [mt_ipv4_prefix_complete] = ms_var_complete, + [mt_ipv4_address_complete] = ms_var_complete, + + [mt_range_complete] = ms_var_complete, + + [mt_keyword_complete] = ms_kwd_complete, + } ; + + assert((mt >= 0) && (mt < match_type_count)) ; + + return match_match_strength[mt] ; +} ; + +/*------------------------------------------------------------------------------ + * Map cmd_item_type -> best possible match type. + * + * This gives the most optimistic outcome of an attempt to match to the given + * type of item. + * + * NB: tolerates item_null -- others may not + */ +Inline match_type_t +item_best_match(cmd_item_type_t it) +{ + const static match_type_t item_best_match[item_type_count] = + { + [item_null] = mt_no_match, + + [item_eol] = mt_eol, + + [item_option_word] = mt_option_word_match, + + [item_vararg] = mt_vararg_match, + [item_word] = mt_word_match, + + [item_ipv6_prefix] = mt_ipv6_prefix_complete, + [item_ipv6_address] = mt_ipv6_address_complete, + [item_ipv4_prefix] = mt_ipv4_prefix_complete, + [item_ipv4_address] = mt_ipv4_address_complete, + + [item_range] = mt_range_complete, + + [item_keyword] = mt_keyword_complete, + } ; + + assert((it >= 0) && (it < item_type_count)) ; + + return item_best_match[it] ; +} ; /*============================================================================== * @@ -95,11 +452,13 @@ CONFIRM(exact_match == (match_type_count - 1)) ; /* Command parsing options */ enum cmd_parse_type /* bit significant */ { - cmd_parse_completion = 0, + cmd_parse_standard = 0, /* accept short command parts */ + cmd_parse_strict = BIT(0), + cmd_parse_no_do = BIT(1), + cmd_parse_no_tree = BIT(2), - cmd_parse_do = BIT(1), - cmd_parse_tree = BIT(2), + cmd_parse_execution = BIT(3), /* wish to execute command */ } ; typedef enum cmd_parse_type cmd_parse_type_t ; @@ -108,28 +467,44 @@ enum cmd_pipe_type /* bit significant */ { cmd_pipe_none = 0, - cmd_pipe_in_file = BIT(0), - cmd_pipe_in_shell = BIT(1), + cmd_pipe_file = BIT(0), + cmd_pipe_shell = BIT(1), + cmd_pipe_dev_null = BIT(2), /* out pipe only -- black hole */ - cmd_pipe_reflect = BIT(4), - cmd_pipe_output = BIT(5), - cmd_pipe_more = BIT(6), + /* For in pipes */ + cmd_pipe_reflect = BIT(4), /* + option */ - cmd_pipe_out_file = BIT( 8), - cmd_pipe_out_file_append = BIT( 9), - cmd_pipe_out_shell = BIT(10), + /* For out file pipes */ + cmd_pipe_append = BIT(4), /* >> */ } ; typedef enum cmd_pipe_type cmd_pipe_type_t ; +/* Parsed parts */ +enum cmd_parts /* bit significant */ +{ + cmd_parts_none = 0, + + cmd_part_do = BIT(0), + cmd_part_command = BIT(1), + + cmd_part_in_pipe = BIT(2), + cmd_part_out_pipe = BIT(3), + + cmd_parts_pipe = (cmd_part_in_pipe | cmd_part_out_pipe), + + cmd_part_comment = BIT(4), +} ; +typedef enum cmd_parts cmd_parts_t ; + + /*------------------------------------------------------------------------------ * Token object -- a qstring and some other properties. */ enum cmd_token_type /* *bit* significant */ { - cmd_tok_null = 0, /* used for empty lines */ + cmd_tok_eol = 0, /* all lines have one */ cmd_tok_simple = BIT( 0), - cmd_tok_trailing = BIT( 1), cmd_tok_sq = BIT( 8), cmd_tok_dq = BIT( 9), /* '\\' within "..." are not @@ -138,20 +513,24 @@ enum cmd_token_type /* *bit* significant */ cmd_tok_incomplete = (cmd_tok_sq | cmd_tok_dq | cmd_tok_esc), - cmd_tok_pipe_in = BIT(12), - cmd_tok_pipe_out = BIT(13), - cmd_tok_comment = BIT(14), + cmd_tok_in_pipe = BIT(12), /* token starting '<' */ + cmd_tok_out_pipe = BIT(13), /* token starting '>' */ + cmd_tok_comment = BIT(14), /* token starting '!' or '#" */ } ; typedef enum cmd_token_type cmd_token_type_t ; -struct token +struct cmd_token { cmd_token_type_t type ; - qstring_t qs ; - size_t tp ; + qstring_t qs ; /* token string */ + + bool term ; /* token has been '\0' terminated */ + + usize tp ; /* location of token in the line */ + elstring_t ot ; /* original token in the line */ } ; -typedef struct token token_t[1] ; -typedef struct token* token ; + +typedef struct cmd_token* cmd_token ; /*------------------------------------------------------------------------------ * Token vector -- a vector of token objects @@ -164,6 +543,10 @@ struct token_vector typedef struct token_vector token_vector_t[1] ; typedef struct token_vector* token_vector ; +enum { + TOKEN_VECTOR_INIT_ALL_ZEROS = VECTOR_INIT_ALL_ZEROS +} ; + /*------------------------------------------------------------------------------ * Argument vector -- a vector of const char* */ @@ -174,6 +557,10 @@ struct arg_vector typedef struct arg_vector arg_vector_t[1] ; typedef struct arg_vector* arg_vector ; +enum { + ARG_VECTOR_INIT_ALL_ZEROS = VECTOR_INIT_ALL_ZEROS +} ; + /*------------------------------------------------------------------------------ * Parsed command line * @@ -191,252 +578,209 @@ typedef struct arg_vector* arg_vector ; * emptied -- see cmd_empty_parsed_tokens() -- but the next command to be * parsed will tidy up before proceeding. */ -typedef struct cmd_parsed* cmd_parsed ; struct cmd_parsed { - struct cmd_element *cmd ; /* NULL if empty command - or fails to parse */ + cmd_parts_t parts ; /* What parts are present */ - const char* line ; /* the current line */ + cmd_token_type_t tok_total ; /* What token types are present */ - enum node_type cnode ; /* node command is in */ - enum node_type onode ; /* node the parser started in */ + usize elen ; /* effective length (less trailing spaces) */ + usize tsp ; /* number of trailing spaces */ - bool do_shortcut ; /* true => is "do" command */ + uint num_tokens ; /* number of tokens parsed */ - token_vector_t tokens ; /* vector of token objects */ - arg_vector_t args ; /* vector of arguments */ + token_vector_t tokens ; /* vector of token objects */ - cmd_pipe_type_t pipes ; /* if any */ + /* NB: the following are significant only if there is a command part + * or a do part + */ + struct cmd_command *cmd ; /* NULL if empty command + or fails to parse */ + node_type_t cnode ; /* node command is in */ - token_vector_t read_pipe_tokens ; - token_vector_t write_pipe_tokens ; -} ; + arg_vector_t args ; /* vector of arguments -- embedded */ -/* Command dispatch options */ -enum { - cmd_no_queue = true, - cmd_may_queue = false, -} ; + /* NB: the following are significant only if an error is returned */ -/*------------------------------------------------------------------------------ - * Vector of spare token objects -- declared here to allow inlines, defined - * in command_parse.c - */ -extern token_vector_t spare_tokens ; + const char* emess ; /* parse error */ + usize eloc ; /* error location */ -/*============================================================================== - * Prototypes - */ -extern void cmd_spare_tokens_init(void) ; -extern void cmd_spare_tokens_free(void) ; - -extern cmd_parsed cmd_parse_init_new(cmd_parsed parsed) ; -extern cmd_parsed cmd_parse_reset(cmd_parsed parsed, bool free_structure) ; - -extern cmd_token_type_t cmd_tokenise(cmd_parsed parsed, const char *line, - node_type_t node) ; -Inline const char* cmd_token_string(token t) ; -Inline int cmd_token_count(token_vector tv) ; -Inline token cmd_token_get(token_vector tv, vector_index_t i) ; -Inline token cmd_token_pop(token_vector tv) ; -Inline void cmd_token_push(token_vector tv, token t) ; -Inline token cmd_token_shift(token_vector tv) ; -Inline void cmd_token_unshift(token_vector tv, token t) ; -Inline token cmd_token_make(void) ; -Inline token cmd_token_new(cmd_token_type_t type, const char* p, - size_t len, size_t tp) ; - -Inline void cmd_empty_token_vector(token_vector tv) ; -Inline void cmd_empty_parsed_tokens(cmd_parsed parsed) ; - -Inline bool cmd_token_complete(token t) ; -extern bool cmd_token_do_complete(token t) ; - -extern match_type_t cmd_ipv4_match (const char *str) ; -extern match_type_t cmd_ipv4_prefix_match (const char *str) ; -#if HAVE_IPV6 -extern match_type_t cmd_ipv6_match (const char *str) ; -extern match_type_t cmd_ipv6_prefix_match (const char *str) ; -#endif -extern bool cmd_range_match (const char *range, const char *str) ; + /* NB: the following are significant only if respective part is + * present. + */ + cmd_pipe_type_t in_pipe ; /* if any */ + cmd_pipe_type_t out_pipe ; /* if any */ -/*============================================================================== - * Inline Functions - */ + uint first_in_pipe ; + uint num_in_pipe ; -/*------------------------------------------------------------------------------ - * Get pointer to token value. - * - * Returns NULL if token NULL or no string. - */ -Inline char* -cmd_token_value(token t) -{ - return (t == NULL) ? NULL : qs_chars(t->qs) ; -} ; + uint first_do ; + uint num_do ; -/*------------------------------------------------------------------------------ - * Get string value of given token. - * - * Returns an empty (not NULL) string if token NULL or no string. - */ -Inline const char* -cmd_token_string(token t) -{ - const char* s = cmd_token_value(t) ; - return (s == NULL) ? "" : s ; -} ; + uint first_command ; + uint num_command ; -/*------------------------------------------------------------------------------ - * Get number of tokens in the given token vector - */ -Inline int -cmd_token_count(token_vector tv) -{ - return vector_length(tv->body) ; -} ; + uint first_out_pipe ; + uint num_out_pipe ; -/*------------------------------------------------------------------------------ - * Get i'th token from given token vector -- zero origin - */ -Inline token -cmd_token_get(token_vector tv, vector_index_t i) -{ - return vector_get_item(tv->body, i) ; -} ; + uint first_comment ; + uint num_comment ; -/*------------------------------------------------------------------------------ - * Pop token from end of given token vector -- if any. - */ -Inline token -cmd_token_pop(token_vector tv) -{ - return vector_pop_item(tv->body) ; -} ; + /* The following are significant after cmd_token_position() */ -/*------------------------------------------------------------------------------ - * Push token onto end of given token vector. - */ -Inline void -cmd_token_push(token_vector tv, token t) -{ - vector_push_item(tv->body, t) ; + uint cti ; /* cursor token index -- may be eol token */ + uint ctl ; /* cursor token length -- 0 <=> eol token */ + int rp ; /* cursor relative to start of cursor token */ + + /* The following are used while filtering commands */ + + vector_t cmd_v ; /* working vector -- embedded */ + vector_t item_v ; /* working vector -- embedded */ + + match_strength_t strongest ; + match_type_t best_complete ; + + match_strength_t min_strength ; /* for execution */ + bool strict ; /* for strict keyword match */ } ; -/*------------------------------------------------------------------------------ - * Shift first token off front of given token vector -- if any. - */ -Inline token -cmd_token_shift(token_vector tv) +enum { - return vector_shift_item(tv->body) ; + CMD_PARSED_INIT_ALL_ZEROS = (cmd_pipe_none == 0) + && TOKEN_VECTOR_INIT_ALL_ZEROS + && ARG_VECTOR_INIT_ALL_ZEROS } ; -/*------------------------------------------------------------------------------ - * Unshift token onto front of given token vector. - */ -Inline void -cmd_token_unshift(token_vector tv, token t) +typedef struct cmd_parsed cmd_parsed_t[1] ; +typedef struct cmd_parsed* cmd_parsed ; + +/* Command dispatch options */ +enum cmd_queue_b { - vector_unshift_item(tv->body, t) ; + cmd_no_queue = false, + cmd_queue_it = true, } ; +typedef enum cmd_queue_b cmd_queue_b ; -/*------------------------------------------------------------------------------ - * Make a brand new token object +/*============================================================================== + * Prototypes */ -Inline token -cmd_token_make(void) -{ - return XCALLOC(MTYPE_TOKEN, sizeof(struct token)) ; +extern void cmd_compile(cmd_command cmd) ; +extern void cmd_compile_check(cmd_command cmd) ; - /* Zeroising the new structure sets: - * - * type = 0 -- cmd_tok_null - * qs = zeroised qstring -- empty string - */ - confirm(cmd_tok_null == 0) ; -} ; +extern cmd_parsed cmd_parsed_init_new(cmd_parsed parsed) ; +extern cmd_parsed cmd_parsed_reset(cmd_parsed parsed, + free_keep_b free_structure) ; +extern bool cmd_is_empty(elstring line) ; -/*------------------------------------------------------------------------------ - * Create new 'cmd_tok_simple' token from given characters + length - */ -Inline token -cmd_token_new(cmd_token_type_t type, const char* p, size_t len, size_t tp) -{ - token t ; +extern cmd_return_code_t cmd_parse_command(cmd_parsed parsed, + node_type_t node, cmd_parse_type_t type) ; - t = cmd_token_pop(spare_tokens) ; - if (t == NULL) - t = cmd_token_make() ; +extern bool cmd_token_position(cmd_parsed parsed, qstring line) ; +extern const char* cmd_help_preflight(cmd_parsed parsed) ; +extern cmd_return_code_t cmd_completion(cmd_parsed parsed, node_type_t node) ; - t->type = type ; - qs_set_n(&t->qs, p, len) ; - t->tp = tp ; +extern void cmd_complete_keyword(cmd_parsed parsed, + int* pre, int* rep, int* ins, int* mov) ; - return t ; -} ; -/*------------------------------------------------------------------------------ - * Discard given token -- give back to spare tokens list - */ -Inline void -cmd_token_discard(token t) -{ - if (t != NULL) - vector_push_item(spare_tokens->body, t) ; -} ; -/*------------------------------------------------------------------------------ - * Release contents of token vector -- move to the spare_token_strings. + + + +//extern vector cmd_make_strvec (const char *); +//extern vector cmd_add_to_strvec (vector v, const char* str) ; +//extern void cmd_free_strvec (vector); +extern vector cmd_describe_command (const char* line, node_type_t node, + cmd_return_code_t* status) ; +extern vector cmd_complete_command (vector, int, int *status); + + + +extern void cmd_tokenise(cmd_parsed parsed, qstring line) ; +//extern cmd_return_code_t cmd_parse_error(cmd_parsed parsed, cmd_token t, +// usize off, const char* mess) ; + +//Inline const char* cmd_token_string(cmd_token t) ; +//Inline char* cmd_token_make_string(cmd_token t) ; +//Inline int cmd_token_count(token_vector tv) ; +//Inline cmd_token cmd_token_get(token_vector tv, vector_index_t i) ; +//Inline void cmd_token_set(token_vector tv, vector_index_t i, +// cmd_token_type_t type, const char* p, usize len, usize tp) ; + +//extern cmd_return_code_t cmd_token_complete(cmd_parsed parsed, cmd_token t) ; +//Inline const char* cmd_token_string(cmd_token t) ; +//extern cmd_return_code_t cmd_parse_in_pipe(cmd_parsed parsed, cmd_token t) ; +//extern cmd_return_code_t cmd_parse_out_pipe(cmd_parsed parsed, cmd_token t) ; + +//extern match_type_t cmd_ipv4_match (const char *str) ; +//extern match_type_t cmd_ipv4_prefix_match (const char *str) ; +#if HAVE_IPV6 +//extern match_type_t cmd_ipv6_match (const char *str) ; +//extern match_type_t cmd_ipv6_prefix_match (const char *str) ; +#endif +//extern bool cmd_range_match (const char *range, const char *str) ; + +/*============================================================================== + * Inline Functions */ -Inline void -cmd_empty_token_vector(token_vector tv) -{ - if (cmd_token_count(tv) != 0) - vector_move_append(spare_tokens->body, tv->body) ; -} ; + +Private cmd_token cmd_token_new(void) ; /*------------------------------------------------------------------------------ - * Release contents of all token vectors in given parsed object. + * Get pointer to token value. + * + * Returns NULL if token NULL or no string. */ -Inline void -cmd_empty_parsed_tokens(cmd_parsed parsed) +Inline char* +cmd_token_value(cmd_token t) { - cmd_empty_token_vector(parsed->tokens) ; - cmd_empty_token_vector(parsed->read_pipe_tokens) ; - cmd_empty_token_vector(parsed->write_pipe_tokens) ; + return (t == NULL) ? NULL : qs_char_nn(t->qs) ; } ; /*------------------------------------------------------------------------------ - * If token is incomplete (contains quotes or escapes) process those down. + * Get string value of given token. * - * Returns: true <=> OK - * false => invalid escape + * Returns an empty (not NULL) string if token NULL or no string. */ -Inline bool -cmd_token_complete(token t) +Inline char* +cmd_token_make_string(cmd_token t) { - return ((t->type & cmd_tok_incomplete) == 0) ? true - : cmd_token_do_complete(t) ; + if (t->term) + return qs_char_nn(t->qs) ; + else + { + t->term = true ; + return qs_make_string(t->qs) ; + } } ; /*------------------------------------------------------------------------------ - * Initialise arg_vector object in cmd_parsed. + * Get i'th token from given token vector -- zero origin */ -Inline void -cmd_arg_vector_init(cmd_parsed parsed) +Inline cmd_token +cmd_token_get(token_vector tv, vector_index_t i) { - vector_init_new(parsed->args->body, 0) ; + return vector_get_item(tv->body, i) ; } ; /*------------------------------------------------------------------------------ - * Free the body of the arg_vector object in cmd_parsed. + * Set i'th token from given token vector -- zero origin */ Inline void -cmd_arg_vector_free(cmd_parsed parsed) +cmd_token_set(token_vector tv, vector_index_t i, + cmd_token_type_t type, const char* p, usize len, usize tp) { - vector_reset(parsed->args->body, keep_it) ; + cmd_token t = cmd_token_get(tv, i) ; + + if (t == NULL) + vector_set_item(tv->body, i, (t = cmd_token_new())) ; + + t->type = type ; + t->term = false ; + t->tp = tp ; + qs_set_alias_n(t->qs, p, len) ; + qs_els_copy_nn(t->ot, t->qs) ; } ; /*------------------------------------------------------------------------------ diff --git a/lib/command_queue.c b/lib/command_queue.c index 5f14abae..0a09d950 100644 --- a/lib/command_queue.c +++ b/lib/command_queue.c @@ -19,136 +19,564 @@ * Boston, MA 02111-1307, USA. */ -#include +#include "zconfig.h" /* for CONSUMED_TIME_CHECK */ +#include "misc.h" -#include "mqueue.h" #include "qpnexus.h" -#include "memory.h" +#include "mqueue.h" +#include "thread.h" + #include "command_queue.h" #include "command_execute.h" -#include "vty.h" -#include "uty.h" -#include "vector.h" -#include "qstring.h" +#include "vty_command.h" +#include "vty_io.h" +#include "log.h" + +/*============================================================================== + * This command loop processes commands for VTY_TERMINAL and VTY_SHELL_SERVER. + * + * Commands appear from those sources, driven by socket I/O. Once a command + * line is ready it is passed out of the I/O pselect/select driven code + * as a message, and enters the command loop. + * + * If pipes are involved that is all dealt with in the command loop, which + * runs until the vin/vout stacks return to 0 -- the VTY_TERMINAL and + * VTY_SHELL_SERVER. + * + * There are further issues: + * + * 1) in the qpthreads world, commands are parsed in the CLI thread, but most + * are executed in the Routing thread. So parsed commands are passed, by + * message between threads. + * + * 2) input is expected to be non-blocking -- so the command loop will + * exit if a command line cannot be delivered immediately, and will be + * returned to later. + * + * 3) while a VTY is in the command loop it is marked vio->cmd_running. + * + * While that is true, the vty and the vty->exec are in the hands + * of the command loop. The vty->vio is always accessed under VTY_LOCK(). + * + * 4) opening pipes is done in the CLI thread, in case of any possible + * blocking. + * + * 5) all output is to fifo buffers -- when output is pushed the CLI side + * is kicked to manage all output via pselect/select. + * + * In vty_io_basic() it is possible to set read/write ready and associated + * timeouts while running in the CMD thread, but that too depends on the + * passing of messages to the CLI thread. + * + * The smooth running of the command handling depends on the continued + * running of the CLI thread. + * + * To close a VTY must (eventually) arrange for vio->cmd_running to be cleared. + * While a vty is vio->cmd_running, it must be in one of these states: + * + * - on the vty_cli_nexus queue (or the combined queue) + * - executing in the vty_cli_nexus (or the single "both" nexus) + * - on the vty_cmd_nexus queue (if different) + * - executing in the vty_cmd_nexus (if different) + * + * Or, in the legacy threads world: + * + * - on the event queue + * - executing + * + * Where there is only one pthread (and in the legacy threads world) things are + * easy. + * + * + * This revoke runs in the CLI thread, and will catch the message if it is + * on either queue, and vty_revoke() will deal with it -- still in the CLI + * thread. + * + * The command cannot be running in the CLI thread, since that is where we + * are ! + * + * That leaves the command running in the CMD thread. That will run to + * completion... the VTY may be closed in the meantime, which will shut down + * the reading side, so the command loop will come to a halt quite quickly. + * Note, however, that the smooth running of this process requires the CLI + * thread and its messages to be + * + * + */ + + + /*------------------------------------------------------------------------------ - * Form of message passed with command to be executed + * Prototypes */ +static void cq_enqueue(struct vty *vty, qpn_nexus dst, cmd_exec_state_t state, + cmd_return_code_t ret) ; +static void cq_action(mqueue_block mqb, mqb_flag_t flag); +static int cq_thread(struct thread* thread) ; +static void cq_process(vty vty) ; -struct cq_command_args +/*------------------------------------------------------------------------------ + * Enqueue vty for parse and execution of a command. + * + * Sets the vio->cmd_running flag, which will be cleared only when the + * command is completed (including any nested pipes etc.) or when the vty + * is blocked on input or it is revoked. + * + * Note that from now on, exec->ret reflects the state of the return + * code when the vty was last enqueued. + * + * While vio_cmd_running is set, the CLI side MUST NOT fiddle with the main + * part of the VTY or with the vty->exec state. + */ +extern void +cq_dispatch(vty vty, cmd_do_t to_do, qstring line) { - enum cmd_return_code ret ; /* return code from command */ + VTY_ASSERT_CLI_THREAD() ; + + vty->exec->to_do = to_do ; + vty->exec->line = line ; + vty->vio->cmd_running = true ; + cq_enqueue(vty, vty_cli_nexus, + (to_do == cmd_do_command) ? exec_parse + : exec_special, CMD_SUCCESS) ; } ; -MQB_ARGS_SIZE_OK(cq_command_args) ; /*------------------------------------------------------------------------------ - * Prototypes + * Enqueue vty for fetching a command line from an in_pipe which has just + * received more input. + * + * Sets the vio->cmd_running flag, which will be cleared only when the + * command is completed (including any nested pipes etc.) or when the vty + * is blocked on input or it is revoked. + * + * While vio_cmd_running is set, the CLI side MUST NOT fiddle with the main + * part of the VTY or with the vty->exec state. */ -static void cq_action(mqueue_block mqb, mqb_flag_t flag); -static void cq_return(mqueue_block mqb, mqb_flag_t flag); +extern void +cq_go_fetch(vty vty) +{ + VTY_ASSERT_CLI_THREAD() ; + + vty->vio->cmd_running = true ; + cq_enqueue(vty, vty_cli_nexus, exec_fetch, CMD_SUCCESS) ; +} ; /*------------------------------------------------------------------------------ - * Enqueue vty and argv[] for execution in given nexus. + * Enqueue vty for execution in given nexus or issue thread event. + * + * Note that preserves the return code state. */ -void -cq_enqueue(struct vty *vty, qpn_nexus dst) +static void +cq_enqueue(vty vty, qpn_nexus dst, cmd_exec_state_t state, + cmd_return_code_t ret) { - struct cq_command_args* args ; - mqueue_block mqb ; + cmd_exec exec = vty->exec ; + + assert(vty->vio->cmd_running) ; + assert((dst == vty_cli_nexus) || (dst == vty_cmd_nexus)) ; - assert(vty_cli_nexus) ; /* must be running qnexus-wise */ + exec->locus = dst ; + exec->state = state ; + exec->ret = ret ; - mqb = mqb_init_new(NULL, cq_action, vty) ; - args = mqb_get_args(mqb) ; + if (vty_nexus) + { + mqueue_block mqb ; - args->ret = CMD_QUEUED ; + if ((mqb = exec->cq.mqb) == NULL) + mqb = exec->cq.mqb = mqb_init_new(NULL, cq_action, vty) ; - mqueue_enqueue(dst->queue, mqb, 0) ; -} + mqueue_enqueue(dst->queue, mqb, (dst == vty_cmd_nexus) ? mqb_priority + : mqb_ordinary) ; + } + else + { + assert(vty_cli_nexus == vty_cmd_nexus) ; + exec->cq.thread = thread_add_event(vty_master, cq_thread, vty, 0) ; + } ; +} ; /*------------------------------------------------------------------------------ - * Dispatch a command from the message queue block + * Deal with command message -- in the qpthreads world. * - * When done (or revoked/deleted) return the message, so that the sender knows - * that the command has been dealt with (one way or another). - * - * Note that if the command is revoked the return is set to CMD_QUEUED. + * Note that if the command is revoked.... */ static void cq_action(mqueue_block mqb, mqb_flag_t flag) { - struct vty *vty; - struct cq_command_args* args ; + vty vty; - assert(vty_cli_nexus) ; /* must be running qnexus-wise */ + assert(vty_nexus) ; /* must be running qnexus-wise */ vty = mqb_get_arg0(mqb); - args = mqb_get_args(mqb) ; + + assert(vty->exec->cq.mqb == mqb) ; + assert(vty->vio->cmd_running) ; if (flag == mqb_action) - { - args->ret = cmd_dispatch_call(vty) ; - assert(args->ret != CMD_QUEUED) ; /* avoid confusion ! */ - } - else - args->ret = CMD_QUEUED ; + return cq_process(vty) ; /* do not touch vty on way out */ + + mqb_free(mqb) ; + vty->exec->cq.mqb = NULL ; +} ; - mqb_set_action(mqb, cq_return) ; - mqueue_enqueue(vty_cli_nexus->queue, mqb, 0) ; +/*------------------------------------------------------------------------------ + * Deal with command message -- in the legacy threads world. + * + * Note that if the command is revoked.... + */ +static int +cq_thread(struct thread* thread) +{ + vty vty = THREAD_ARG(thread) ; + + assert(vty->exec->cq.thread == thread) ; + assert(vty->vio->cmd_running) ; + + vty->exec->cq.thread = NULL ; + + cq_process(vty) ; /* do not touch vty on way out */ + + return 0 ; } ; /*------------------------------------------------------------------------------ - * Accept return from command which has completed. + * Process command(s) queued from VTY_TERMINAL or from VTY_SHELL_SERVER. * - * The command line processing for the vty may be stalled (with read mode - * disabled) waiting for the return from the command. + * To get into the process loop, or to get back to it when more input has + * arrived a message is sent: * - * Do not care whether the message is being revoked or not... the command - * has completed and that must be signalled to the CLI. Any pending output - * is released. + * cli -> cli -- exec_parse -- when a command line has been gathered + * from input and is ready to be processed. * - * The command itself may have been revoked before it was executed. That - * makes no difference either... the output buffers will simply be empty. - * However, the return code is CMD_QUEUED, to signal the fact that the command - * was never executed. + * cli -> cli -- exec_fetch -- when was waiting for more input from an + * in_pipe of some sort. + * + * In the single-pthread world, where the vty_cli_nexus is the same as the + * vty_cmd_nexus (either because not running qpthreaded, or because are + * running in the legacy threads world), things are reasonably straightforward. + * The process runs to completion or until an in_pipe would block, and the + * above are the only messages in the system. + * + * In the multi-pthread world, things are more complicated... The above + * messages are sent, and in addition the following are sent back and forth to + * transfer between threads in the following states: + * + * cmd -> cli -- exec_open_pipes + * cli -> cmd -- exec_execute + * cmd -> cli -- exec_complete + * + * Note that this means that in the multi-pthread world, only one sort of + * message is sent to the vty_cmd_nexus. + * + * The vty_io_basic stuff allows the multi-pthread vty_cmd_nexus to set read + * and/or write ready state -- which may generate messages to the vty_cli_nexus. + * + * NB: if blocks in exec_fetch, then vty_cmd_fetch_line() will have cleared + * vio->cmd_running -- so on return from cq_process the vty MAY HAVE BEEN + * DELETED. */ static void -cq_return(mqueue_block mqb, mqb_flag_t flag) +cq_process(vty vty) { - struct vty *vty ; - struct cq_command_args* args ; + cmd_exec exec = vty->exec ; + cmd_parsed parsed = exec->parsed ; + cmd_return_code_t ret = exec->ret ; + + /* Have switch wrapped in a while(1) so that can change state by setting + * exec->state and doing "continue". + * + * Breaking out of the switch forces the exec state to exec_complete, + * with the current ret, in the CLI thread. + * + * The exec locus is either vty_cli_nexus or vty_cmd_nexus. If these + * are equal (including both NULL, in the legacy threads world), then is + * running single threaded -- otherwise is running multi-threaded. + */ + while (1) + { + switch(exec->state) + { + /*-------------------------------------------------------------------- + * Should not get here in exec_null state. + */ + case exec_null: + zabort("exec->state == exec_null") ; + break ; + + /*-------------------------------------------------------------------- + * Deal with the "spacial" commands + */ + case exec_special: + ret = vty_cmd_special(vty) ; + if (ret != CMD_SUCCESS) + break ; + + exec->state = exec_fetch ; + /* continue by falling through */ + + /*-------------------------------------------------------------------- + * Need another command to execute => in_pipe ! + * + * Note that at vin_depth == 0 this will return CMD_EOF, and will + * drop out of the loop exec_complete. + * + * Will also receive CMD_EOF if the VTY has been closed. + * + * If multi-threaded: may be in either thread: + * + * vty_cmd_fetch_line() may set read and/or write ready -- so in + * vty_cmd_nexus may generate message to vty_cli_nexus. + */ + case exec_fetch: + ret = vty_cmd_fetch_line(vty) ; + + if (ret != CMD_SUCCESS) + { + /* If is CMD_WAITING, then the vty_cmd_fetch_line() will + * have prepared for command to be re-queued when there is more + * to be read. NB: vio->cmd_running has been cleared, so + * vty MAY HAVE BEEN DELETED ! + * + * If is CMD_EOF then is "closing" or reached EOF on top-most + * pipe. + */ + if (ret == CMD_WAITING) + return ; /* <<<< DONE, pro tem */ + + break ; /* => exec_complete */ + } ; + + if (exec->to_do != cmd_do_command) + { + exec->state = exec_special ; + continue ; + } ; + + exec->state = exec_parse ; + /* continue by falling through */ + + /*-------------------------------------------------------------------- + * Parse command in hand + * + * If multi-threaded: may be in either thread: + * + * vty_cmd_reflect_line() may set read and/or write ready -- so in + * vty_cmd_nexus may generate message to vty_cli_nexus. + */ + case exec_parse: + cmd_tokenise(parsed, exec->line) ; + ret = cmd_parse_command(parsed, vty->node, + exec->parse_type | cmd_parse_execution) ; + if (ret != CMD_SUCCESS) + { + if (ret != CMD_EMPTY) + break ; /* stop on *any* parsing issue */ + + /* Empty lines from in_pipes we simply ignore. + * + * Don't expect to see them otherwise, but if we do then need + * to complete the command execution process. + */ + ret = CMD_SUCCESS ; + + exec->state = exec_success ; + continue ; + } ; + + /* reflection now -- output always succeeds */ + if (exec->reflect_enabled) + vty_cmd_reflect_line(vty) ; + + /* continue by falling through */ + + /*-------------------------------------------------------------------- + * Pipe work if any + * + * Will receive CMD_EOF if the VTY has been closed. + * + * If multi-threaded: must be in vty_cli_nexus to proceed -- so may + * generate message to transfer to vty_cli_nexus. + */ + case exec_open_pipes: + if ((parsed->parts & cmd_parts_pipe) != 0) + { + exec->state = exec_open_pipes ; + + /* If running pthreaded, do open pipes in vty_cli_nexus */ + if (exec->locus != vty_cli_nexus) + return cq_enqueue(vty, vty_cli_nexus, exec_open_pipes, ret) ; + + /* Now in vty_cli_nexus */ + ret = cmd_open_pipes(vty) ; + if (ret != CMD_SUCCESS) + break ; /* quit if open fails */ + } ; - assert(vty_cli_nexus) ; /* must be running qnexus-wise */ + exec->state = exec_execute ; + /* continue by falling through */ - vty = mqb_get_arg0(mqb) ; - args = mqb_get_args(mqb) ; + /*-------------------------------------------------------------------- + * Execute command in hand + * + * If multi-threaded: some commands can run in either thread, most must + * run in the vty_cmd_nexus -- so may generate message to transfer to + * the vty_cmd_nexus. + */ + case exec_execute: + if ((parsed->parts & cmd_part_command) != 0) + { + /* If running pthreaded, do most commands in vty_cmd_nexus */ + if ((exec->locus != vty_cmd_nexus) && + (!cmd_is_direct(parsed))) + return cq_enqueue(vty, vty_cmd_nexus, exec_execute, ret) ; - /* signal end of command */ - cmd_post_command(vty, args->ret) ; - vty_queued_result(vty, args->ret) ; + /* Standard command handling */ +#ifdef CONSUMED_TIME_CHECK + { + RUSAGE_T before; + RUSAGE_T after; + unsigned long realtime, cputime; -//if (qpthreads_enabled) -// qpt_thread_signal(vty_cli_nexus->thread_id, SIGMQUEUE); + GETRUSAGE(&before); +#endif /* CONSUMED_TIME_CHECK */ - mqb_free(mqb); -} + ret = cmd_execute(vty) ; + +#ifdef CONSUMED_TIME_CHECK + GETRUSAGE(&after); + realtime = thread_consumed_time(&after, &before, &cputime) ; + if (realtime > CONSUMED_TIME_CHECK) + /* Warn about CPU hog that must be fixed. */ + uzlog(NULL, LOG_WARNING, + "SLOW COMMAND: command took %lums (cpu time %lums): %s", + realtime/1000, cputime/1000, + qs_make_string(exec->line)) ; + } ; +#endif /* CONSUMED_TIME_CHECK */ + + if (ret != CMD_SUCCESS) + break ; /* stop */ + } ; + + exec->state = exec_success ; + /* continue by falling through */ + + /*-------------------------------------------------------------------- + * Command has completed successfully -- so push the output. + * + * If the vout_depth > vin_depth, pops the vout's -- deals with single + * command lines with a pipe output. + * + * Output cannot block, so this always succeeds. + * + * Then loop back to fetch another command line, if can. + * + * If multi-threaded: may be in either thread: + * + * vty_cmd_success() may set write ready -- so in vty_cmd_nexus may + * generate message to vty_cli_nexus. + */ + case exec_success: + assert(ret == CMD_SUCCESS) ; + + vty_cmd_success(vty) ; + + exec->state = exec_fetch ; + continue ; + + /*-------------------------------------------------------------------- + * End of the command loop ! + * + * If multi-threaded: must return to the vty_cli_nexus. + */ + case exec_complete: + if (exec->locus != vty_cli_nexus) + break ; /* Will send back to the cli */ + + /* Now in the vty_cli_nexus */ + + exec->state = exec_null ; /* all done ! */ + + vty_cmd_loop_exit(vty, ret) ; /* clears vio->cmd_running */ + + return ; /* <<<< Finally finished ! */ + + /*---------------------------------------------------------------------- + * Unknown exec->state ! + */ + default: + zabort("unknown exec->state") ; + break ; + } ; + + /* Have broken out of the switch(). This means that for good or ill, + * the command is complete. If we are not in the vty_cli_nexus, need to + * send back to the vty_cli_nexus for handling. + * + * At all times we treat CMD_EOF and CMD_SUCCESS. + * + * Otherwise, can continue in exec_complete state. + */ + if (ret == CMD_EOF) + ret = CMD_SUCCESS ; + + if (exec->locus != vty_cli_nexus) + return cq_enqueue(vty, vty_cli_nexus, exec_complete, ret) ; + + exec->state = exec_complete ; + } ; +} ; /*------------------------------------------------------------------------------ - * Revoke any messages related to the given VTY -- if running qnexus-wise. + * Revoke any message associated with the given vty from the command queue. + * + * This is used when a VTY_TERMINAL or a VTY_SHELL_SERVER is being closed. + * + * See cq_process above for discussion of what messages there may be. At any + * time there is at most one message in flight. + * + * If we find a message in flight, then we vty_cmd_loop_exit() to bring things + * to a stop tidily. * - * Revokes in vty_cmd_nexus -- so before command is started - * and in vty_cli_nexus -- so after command has completed + * In the single-threaded world, expect that a command, once started will run + * to conclusion, or until blocked on an in_pipe. So after this revoke the + * vty should not be vio->cmd_running. * - * Can do nothing about any command actually being executed in the - * vty_cmd_nexus. + * In the multi-threaded world, this revoke will catch any vty which is on + * either the vty_cli_nexus or vty_cmd_nexus queues, and force completion. + * After this revoke, vio->cmd_running will be true iff the command is + * currently being executed in the vty_cmd_nexus -- we expect that to run to + * conclusion or block on an in_pipe, shortly, which will be collected when + * the vty_cli_nexus message queue is next processed. + * + * Note that the revoke does not affect any vty_cli_nexus messages associated + * with the vty_io_basic operations. */ -void -cq_revoke(struct vty *vty) +extern void +cq_revoke(vty vty) { - if (vty_cli_nexus) + int ret ; + + VTY_ASSERT_CLI_THREAD() ; + + if (vty_nexus) { - mqueue_revoke(vty_cmd_nexus->queue, vty) ; - mqueue_revoke(vty_cli_nexus->queue, vty) ; + ret = mqueue_revoke(vty_cmd_nexus->queue, vty, 1) ; + + if ((ret == 0) && (vty_cli_nexus != vty_cmd_nexus)) + ret = mqueue_revoke(vty_cli_nexus->queue, vty, 1) ; + } + else + ret = thread_cancel_event(vty_master, vty) ; + + if (ret != 0) + { + vty->exec->state = exec_null ; + vty_cmd_loop_exit(vty, vty->exec->ret) ; } ; -} +} ; + + diff --git a/lib/command_queue.h b/lib/command_queue.h index 804dfa1f..0966bd68 100644 --- a/lib/command_queue.h +++ b/lib/command_queue.h @@ -22,10 +22,12 @@ #ifndef COMMAND_QUEUE_H_ #define COMMAND_QUEUE_H_ -#include "command.h" +#include "vty_local.h" +#include "command_execute.h" #include "qpnexus.h" -extern void cq_enqueue(struct vty *vty, qpn_nexus dst) ; -extern void cq_revoke(struct vty* vty) ; +extern void cq_dispatch(vty vty, cmd_do_t to_do, qstring line) ; +extern void cq_go_fetch(vty vty) ; +extern void cq_revoke(vty vty) ; #endif /* COMMAND_QUEUE_H_ */ diff --git a/lib/elstring.h b/lib/elstring.h index 051e5962..8ab1c58d 100644 --- a/lib/elstring.h +++ b/lib/elstring.h @@ -23,8 +23,6 @@ #define _ZEBRA_ELSTRING_H #include "misc.h" -#include "zassert.h" -#include "memory.h" /*============================================================================== * This is some very simple support for strings which are Length/Body @@ -34,8 +32,8 @@ * is the simplest possible encapsulation of strings which are NOT '\0' * terminated. * - * - * + * NB: this object knows NOTHING about whether there is a '\0' beyond the + * 'len' of the string. */ struct elstring { @@ -46,7 +44,6 @@ struct elstring } body ; ulen len ; - bool term ; /* true <=> body is '\0' terminated */ } ; typedef struct elstring elstring_t[1] ; @@ -60,15 +57,48 @@ enum ELSTRING_INIT_ALL_ZEROS = true } ; -/*------------------------------------------------------------------------------ - * Various forms of body -- NB: +/*============================================================================== + * Pointer pair and unsigned pointer pair and const versions. */ +struct pp +{ + char* p ; + char* e ; +} ; +typedef struct pp pp_t[1] ; +typedef struct pp* pp ; -Inline void* -els_body_nn(elstring els) +struct cpp { - return els->body.v ; + const char* p ; + const char* e ; } ; +typedef struct cpp cpp_t[1] ; +typedef struct cpp* cpp ; + +/*============================================================================== + * NULLs for all types of pp and for els + */ +Inline void pp_null(pp p) { p->p = p->e = NULL ; } ; +Inline void cpp_null(cpp p) { p->p = p->e = NULL ; } ; + +Inline void els_null(elstring els) { els->body.v = NULL ; els->len = 0 ; } ; + +/*============================================================================== + * Access functions. + */ + +Inline void* els_body(elstring els) ; +Inline void* els_body_nn(elstring els) ; +Inline ulen els_len(elstring els) ; +Inline ulen els_len_nn(elstring els) ; +Inline void* els_end(elstring els) ; +Inline void* els_end_nn(elstring els) ; + +Inline void els_pp(pp p, elstring els) ; +Inline void els_pp_nn(pp p, elstring els) ; +Inline void els_cpp(cpp p, elstring els) ; +Inline void els_cpp_nn(cpp p, elstring els) ; Inline void* els_body(elstring els) @@ -76,10 +106,10 @@ els_body(elstring els) return (els != NULL) ? els_body_nn(els) : NULL ; } ; -Inline ulen -els_len_nn(elstring els) +Inline void* +els_body_nn(elstring els) { - return els->len ; + return els->body.v ; } ; Inline ulen @@ -88,38 +118,70 @@ els_len(elstring els) return (els != NULL) ? els_len_nn(els) : 0 ; } ; -Inline bool -els_term_nn(elstring els) +Inline ulen +els_len_nn(elstring els) { - return els->term ; + return els->len ; } ; -Inline bool -els_term(elstring els) +Inline void* +els_end(elstring els) { - return (els != NULL) ? els_term_nn(els) : false ; + return (els != NULL) ? els_end_nn(els) : NULL ; } ; -/*============================================================================== - * All so simple that everything is implemented as Inline - */ +Inline void* +els_end_nn(elstring els) +{ + return (void*)((char*)els->body.v + els->len); +} ; -/*------------------------------------------------------------------------------ - * Initialise or create a new elstring - */ -Inline elstring -els_init_new(elstring els) +Inline void +els_pp(pp p, elstring els) { - if (els == NULL) - els = XCALLOC(MTYPE_TMP, sizeof(elstring_t)) ; + if (els != NULL) + els_pp_nn(p, els) ; else - memset(els, 0, sizeof(elstring_t)) ; + pp_null(p) ; +} ; - confirm(ELSTRING_INIT_ALL_ZEROS) ; +Inline void +els_pp_nn(pp p, elstring els) +{ + p->p = els->body.v ; + p->e = p->p + els->len ; +} ; - return els ; +Inline void +els_cpp(cpp p, elstring els) +{ + if (els != NULL) + els_cpp_nn(p, els) ; + else + cpp_null(p) ; +} ; + +Inline void +els_cpp_nn(cpp p, elstring els) +{ + p->p = els->body.cv ; + p->e = p->p + els->len ; } ; +/*============================================================================== + * All so simple that most is implemented as Inline + */ + +extern elstring els_init_new(elstring els) ; +extern elstring els_new(void) ; +extern elstring els_free(elstring els) ; + +extern int els_cmp(elstring a, elstring b) ; +extern int els_cmp_word(elstring a, const char* w) ; +extern int els_cmp_sig(elstring a, elstring b) ; +extern bool els_equal(elstring a, elstring b) ; +extern bool els_substring(elstring a, elstring b) ; + /*------------------------------------------------------------------------------ * Set elstring value from ordinary string. * @@ -134,7 +196,6 @@ els_set_nn(elstring els, const void* str) { els->body.cv = str ; els->len = (str != NULL) ? strlen(str) : 0 ; - els->term = (str != NULL) ; } ; /*------------------------------------------------------------------------------ @@ -148,7 +209,7 @@ Inline elstring els_set(elstring els, const void* str) { if (els == NULL) - els = XCALLOC(MTYPE_TMP, sizeof(elstring_t)) ; + els = els_new() ; els_set_nn(els, str) ; @@ -169,7 +230,6 @@ els_set_n_nn(elstring els, const void* body, ulen len) { els->body.cv = body ; els->len = len ; - els->term = false ; } ; /*------------------------------------------------------------------------------ @@ -183,7 +243,7 @@ Inline elstring els_set_n(elstring els, const void* body, ulen len) { if (els == NULL) - els = XCALLOC(MTYPE_TMP, sizeof(elstring_t)) ; + els = els_new() ; els_set_n_nn(els, body, len) ; @@ -203,31 +263,13 @@ els_clear(elstring els) { els->body.v = NULL ; els->len = 0 ; - els->term = false ; } ; } ; /*------------------------------------------------------------------------------ - * Release dynamically allocated elstring. - * - * Returns NULL. - * - * NB: it is the callers responsibility to free the contents of the elstring. - * if that is required, before freeing the elstring itself. - */ -Inline elstring -els_free(elstring els) -{ - if (els != NULL) - XFREE(MTYPE_TMP, els) ; - - return NULL ; -} ; - -/*------------------------------------------------------------------------------ - * Set elstring length. And set term false. + * Set elstring 'len'. * - * NB: it is the caller's responsibility to set a valid length !! + * NB: it is the caller's responsibility to set a valid body !! * * NB: elstring MUST NOT be NULL. */ @@ -235,120 +277,21 @@ Inline void els_set_len_nn(elstring els, ulen len) { els->len = len ; - els->term = false ; } ; /*------------------------------------------------------------------------------ - * Set elstring body. And set term false. + * Set elstring body. * * NB: it is the caller's responsibility to set a valid body !! * + * NB: it is the caller's responsibility to set a valid 'len'. + * * NB: elstring MUST NOT be NULL. */ Inline void els_set_body_nn(elstring els, const void* body) { els->body.cv = body ; - els->term = false ; -} ; - -/*------------------------------------------------------------------------------ - * Set elstring terminated. - * - * NB: it is the caller's responsibility to set a valid body !! - * - * NB: elstring MUST NOT be NULL. - */ -Inline void -els_set_term_nn(elstring els, bool term) -{ - els->term = term ; -} ; - -/*------------------------------------------------------------------------------ - * Compare two elstrings -- returns the usual -ve, 0, +ve cmp result. - */ -Inline int -els_cmp(elstring a, elstring b) -{ - const uchar* ap ; - const uchar* bp ; - ulen al, bl ; - ulen n ; - - ap = els_body(a) ; - bp = els_body(b) ; - al = els_len(a) ; - bl = els_len(b) ; - - n = (al <= bl) ? al : bl ; - - while (n) - { - int d = *ap++ - *bp++ ; - if (d != 0) - return d ; - --n ; - } ; - - return al < bl ? -1 : (al == bl) ? 0 : +1 ; -} ; - -/*------------------------------------------------------------------------------ - * Are two elstrings equal ? -- returns true if strings equal. - */ -Inline bool -els_equal(elstring a, elstring b) -{ - const uchar* ap ; - const uchar* bp ; - ulen n ; - - n = els_len(b) ; - if (n != els_len(a)) - return false ; - - ap = els_body(a) ; - bp = els_body(b) ; - - while (n) - { - if (*ap++ != *bp++) - return false ; - --n ; - } ; - - return true ; -} ; - -/*------------------------------------------------------------------------------ - * Is 'b' a leading substring of 'a' ? -- returns true if it is. - * - * If 'b' is empty it is always a leading substring. - */ -Inline int -els_substring(elstring a, elstring b) -{ - const uchar* ap ; - const uchar* bp ; - ulen n ; - - n = els_len(b) ; - if (n > els_len(a)) - return false ; - - ap = els_body(a) ; - bp = els_body(b) ; - - while (n) - { - if (*ap++ != *bp++) - return false ; - --n ; - } ; - - return true ; } ; #endif /* _ZEBRA_ELSTRING_H */ - diff --git a/lib/heap.c b/lib/heap.c index 7821a9c9..708e88d0 100644 --- a/lib/heap.c +++ b/lib/heap.c @@ -19,9 +19,7 @@ * 02111-1307, USA. */ -#include - -#include "zassert.h" +#include "misc.h" #include "heap.h" #include "memory.h" diff --git a/lib/if.c b/lib/if.c index b1d0d49a..66a8bfef 100644 --- a/lib/if.c +++ b/lib/if.c @@ -556,7 +556,7 @@ DEFUN (interface, #endif /* SUNOS_5 */ vty->index = ifp; - vty_set_node(vty, INTERFACE_NODE) ; + vty->node = INTERFACE_NODE ; return CMD_SUCCESS; } diff --git a/lib/if.h b/lib/if.h index c99ab81b..b4b83cd5 100644 --- a/lib/if.h +++ b/lib/if.h @@ -300,12 +300,12 @@ extern char *if_indextoname (unsigned int, char *); /* Exported variables. */ extern struct list *iflist; -extern struct cmd_element interface_desc_cmd; -extern struct cmd_element no_interface_desc_cmd; -extern struct cmd_element interface_cmd; -extern struct cmd_element no_interface_cmd; -extern struct cmd_element interface_pseudo_cmd; -extern struct cmd_element no_interface_pseudo_cmd; -extern struct cmd_element show_address_cmd; +extern struct cmd_command interface_desc_cmd; +extern struct cmd_command no_interface_desc_cmd; +extern struct cmd_command interface_cmd; +extern struct cmd_command no_interface_cmd; +extern struct cmd_command interface_pseudo_cmd; +extern struct cmd_command no_interface_pseudo_cmd; +extern struct cmd_command show_address_cmd; #endif /* _ZEBRA_IF_H */ diff --git a/lib/keychain.c b/lib/keychain.c index 2f8a0b77..57ccf7cc 100644 --- a/lib/keychain.c +++ b/lib/keychain.c @@ -238,7 +238,7 @@ DEFUN (key_chain, keychain = keychain_get (argv[0]); vty->index = keychain; - vty_set_node(vty, KEYCHAIN_NODE) ; + vty->node = KEYCHAIN_NODE ; return CMD_SUCCESS; } @@ -281,7 +281,7 @@ DEFUN (key, VTY_GET_INTEGER ("key identifier", index, argv[0]); key = key_get (keychain, index); vty->index_sub = key; - vty_set_node(vty, KEYCHAIN_KEY_NODE) ; + vty->node = KEYCHAIN_KEY_NODE ; return CMD_SUCCESS; } @@ -309,7 +309,7 @@ DEFUN (no_key, key_delete (keychain, key); - vty_set_node(vty, KEYCHAIN_NODE) ; + vty->node = KEYCHAIN_NODE ; return CMD_SUCCESS; } @@ -850,16 +850,20 @@ DEFUN (send_lifetime_duration_month_day, static struct cmd_node keychain_node = { - KEYCHAIN_NODE, - "%s(config-keychain)# ", - 1 + .node = KEYCHAIN_NODE, + .prompt ="%s(config-keychain)# ", + + .config_to_vtysh = true }; static struct cmd_node keychain_key_node = { - KEYCHAIN_KEY_NODE, - "%s(config-keychain-key)# ", - 1 + .node = KEYCHAIN_KEY_NODE, + .prompt = "%s(config-keychain-key)# ", + + .parent = KEYCHAIN_NODE, + + .config_to_vtysh = true }; static int diff --git a/lib/keystroke.c b/lib/keystroke.c index c6eb811e..13b79e39 100644 --- a/lib/keystroke.c +++ b/lib/keystroke.c @@ -272,7 +272,7 @@ struct keystroke_state struct keystroke_stream { - vio_fifo_t fifo ; /* the keystrokes */ + vio_fifo_t fifo ; /* the keystrokes -- embedded */ keystroke_callback* iac_callback ; void* iac_callback_context ; @@ -301,7 +301,8 @@ enum { keystroke_buffer_len = 2000 } ; /* should be plenty ! */ static void keystroke_in_push(keystroke_stream stream, uint8_t u) ; static void keystroke_in_pop(keystroke_stream stream) ; -inline static int keystroke_set_null(keystroke_stream stream, keystroke stroke); +inline static bool keystroke_set_null(keystroke_stream stream, + keystroke stroke) ; inline static uint8_t keystroke_get_byte(keystroke_stream stream) ; inline static void keystroke_add_raw(keystroke_stream stream, uint8_t u) ; static void keystroke_put_char(keystroke_stream stream, uint32_t u) ; @@ -368,7 +369,7 @@ keystroke_stream_new(uint8_t csi_char, keystroke_callback* iac_callback, stream->iac_callback = iac_callback ; stream->iac_callback_context = iac_callback_context ; - vio_fifo_init_new(&stream->fifo, keystroke_buffer_len) ; + vio_fifo_init_new(stream->fifo, keystroke_buffer_len) ; stream->CSI = (csi_char != '\0') ? csi_char : 0x1B ; @@ -385,7 +386,7 @@ keystroke_stream_free(keystroke_stream stream) { if (stream != NULL) { - vio_fifo_reset_keep(&stream->fifo) ; + vio_fifo_reset(stream->fifo, keep_it) ; XFREE(MTYPE_KEY_STREAM, stream) ; } ; @@ -407,7 +408,7 @@ keystroke_stream_free(keystroke_stream stream) extern bool keystroke_stream_empty(keystroke_stream stream) { - return (stream == NULL) || vio_fifo_empty(&stream->fifo) ; + return (stream == NULL) || vio_fifo_empty(stream->fifo) ; } ; /*------------------------------------------------------------------------------ @@ -428,7 +429,7 @@ keystroke_stream_eof(keystroke_stream stream) * is converted to a broken keystroke and placed in the stream. * (So eof_met => no partial keystroke.) */ - return (stream == NULL) || (vio_fifo_empty(&stream->fifo) && stream->eof_met); + return (stream == NULL) || (vio_fifo_empty(stream->fifo) && stream->eof_met); } ; /*------------------------------------------------------------------------------ @@ -441,7 +442,7 @@ keystroke_stream_eof(keystroke_stream stream) extern void keystroke_stream_set_eof(keystroke_stream stream) { - vio_fifo_reset_keep(&stream->fifo) ; + vio_fifo_reset(stream->fifo, keep_it) ; stream->eof_met = true ; /* essential information */ @@ -858,10 +859,10 @@ keystroke_in_pop(keystroke_stream stream) /*============================================================================== * Fetch next keystroke from keystroke stream * - * Returns: 1 => have a stroke type != ks_null - * 0 => stroke type is ks_null (may be EOF). + * Returns: true => have a stroke type != ks_null + * false => stroke type is ks_null (may be EOF). */ -extern int +extern bool keystroke_get(keystroke_stream stream, keystroke stroke) { int b ; @@ -869,7 +870,7 @@ keystroke_get(keystroke_stream stream, keystroke stroke) uint8_t* e ; /* Get first byte and deal with FIFO empty response */ - b = vio_fifo_get_byte(&stream->fifo) ; + b = vio_fifo_get_byte(stream->fifo) ; if (b < 0) return keystroke_set_null(stream, stroke) ; @@ -884,7 +885,7 @@ keystroke_get(keystroke_stream stream, keystroke stroke) stroke->len = 1 ; stroke->buf[0] = b ; - return 1 ; + return true ; } ; /* Sex the compound keystroke */ @@ -954,7 +955,7 @@ keystroke_get(keystroke_stream stream, keystroke stroke) zabort("unknown keystroke type") ; } ; - return 1 ; + return true ; } ; /*------------------------------------------------------------------------------ @@ -962,7 +963,7 @@ keystroke_get(keystroke_stream stream, keystroke stroke) * * Returns: 0 */ -inline static int +inline static bool keystroke_set_null(keystroke_stream stream, keystroke stroke) { stroke->type = ks_null ; @@ -971,7 +972,7 @@ keystroke_set_null(keystroke_stream stream, keystroke stroke) stroke->flags = 0 ; stroke->len = 0 ; - return 0 ; + return false ; } ; /*------------------------------------------------------------------------------ @@ -983,7 +984,7 @@ keystroke_set_null(keystroke_stream stream, keystroke stroke) inline static uint8_t keystroke_get_byte(keystroke_stream stream) { - int b = vio_fifo_get_byte(&stream->fifo) ; + int b = vio_fifo_get_byte(stream->fifo) ; passert(b >= 0) ; @@ -1013,7 +1014,7 @@ static void keystroke_put_char(keystroke_stream stream, uint32_t u) { if (u < 0x80) - vio_fifo_put_byte(&stream->fifo, (uint8_t)u) ; + vio_fifo_put_byte(stream->fifo, (uint8_t)u) ; else { uint8_t buf[4] ; @@ -1140,12 +1141,12 @@ keystroke_put(keystroke_stream stream, enum keystroke_type type, bool broken, type |= kf_truncated ; } ; - vio_fifo_put_byte(&stream->fifo, + vio_fifo_put_byte(stream->fifo, kf_compound | (broken ? kf_broken : 0) | type) ; - vio_fifo_put_byte(&stream->fifo, len) ; + vio_fifo_put_byte(stream->fifo, len) ; if (len > 0) - vio_fifo_put(&stream->fifo, (void*)bytes, len) ; + vio_fifo_put_bytes(stream->fifo, (void*)bytes, len) ; } ; /*------------------------------------------------------------------------------ diff --git a/lib/keystroke.h b/lib/keystroke.h index 10494df0..67c6cd0f 100644 --- a/lib/keystroke.h +++ b/lib/keystroke.h @@ -47,6 +47,7 @@ enum keystroke_type ks_type_reserved = 0x0F, } ; +typedef enum keystroke_type keystroke_type ; CONFIRM(ks_type_count <= ks_type_reserved) ; enum keystroke_null @@ -68,8 +69,9 @@ enum keystroke_flags kf_type_mask = 0x0F, /* extraction of type */ } ; +typedef enum keystroke_type keystroke_flags ; -CONFIRM(ks_type_reserved == kf_type_mask) ; +CONFIRM(ks_type_reserved == (keystroke_type)kf_type_mask) ; typedef struct keystroke* keystroke ; typedef struct keystroke_stream* keystroke_stream ; @@ -182,7 +184,7 @@ keystroke_stream_eof(keystroke_stream stream) ; extern void keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len, keystroke steal) ; -extern int +extern bool keystroke_get(keystroke_stream stream, keystroke stroke) ; #endif /* _ZEBRA_KEYSTROKE_H */ diff --git a/lib/list_util.c b/lib/list_util.c index 8ce01c03..f73244a4 100644 --- a/lib/list_util.c +++ b/lib/list_util.c @@ -49,28 +49,28 @@ * * Note again the cast to (void**). * - * Returns: 0 => OK -- removed item from list (OR item == NULL) - * -1 => item not found on list + * Returns: true => removed item from list + * false => item not found on list (or item == NULL) */ -extern int +extern bool ssl_del_func(void* p_this, void* item, size_t link_offset) { void* this ; if (item == NULL) - return 0 ; + return false ; while ((this = *(void**)p_this) != item) { if (this == NULL) - return -1 ; + return false ; p_this = _sl_p_next(this, link_offset) ; } ; *(void**)p_this = _sl_next(item, link_offset) ; - return 0 ; + return true ; } ; /*============================================================================== diff --git a/lib/list_util.h b/lib/list_util.h index bd796779..f6a01d93 100644 --- a/lib/list_util.h +++ b/lib/list_util.h @@ -133,10 +133,10 @@ struct dl_void_base_pair base_pair(void*) ; * * ssl_del(base, item, next) -- delete from list * - * Treat as function returning int. Does nothing if the item is NULL. + * Treat as function returning bool. Does nothing if the item is NULL. * - * Returns: 0 => OK -- removed item from list (OR item == NULL) - * -1 => item not found on list + * Returns: true => removed item from list + * false => item not found on list (or item was NULL) * * ssl_del_head(base, next) -- delete head of list * @@ -237,7 +237,7 @@ struct dl_void_base_pair base_pair(void*) ; (base) = item ; \ } while (0) -extern int ssl_del_func(void* p_this, void* obj, size_t link_offset) +extern bool ssl_del_func(void* p_this, void* obj, size_t link_offset) __attribute__((noinline)) ; #define ssl_del(base, item, next) \ @@ -506,7 +506,7 @@ extern int ssl_del_func(void* p_this, void* obj, size_t link_offset) * * Treat as function returning void*. * - * Returns old head in dst and as return from "function". + * Returns old tail in dst and as return from "function". * * Returns NULL and sets dst == NULL if list is empty. * diff --git a/lib/log.c b/lib/log.c index 72f967f6..4da1e5e8 100644 --- a/lib/log.c +++ b/lib/log.c @@ -26,9 +26,8 @@ #include "log.h" #include "vty.h" -#include "uty.h" #include "memory.h" -#include "command.h" +#include "command_local.h" #ifndef SUNOS_5 #include #endif diff --git a/lib/log.h b/lib/log.h index 256d09a3..6f4c8265 100644 --- a/lib/log.h +++ b/lib/log.h @@ -26,6 +26,7 @@ #define _ZEBRA_LOG_H #include +#include #include "vargs.h" #include "pthread_safe.h" @@ -109,13 +110,6 @@ extern struct zlog *openzlog (const char *progname, zlog_proto_t protocol, /* Close zlog function. */ extern void closezlog (struct zlog *zl); -/* GCC have printf type attribute check. */ -#ifdef __GNUC__ -#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) -#else -#define PRINTF_ATTRIBUTE(a,b) -#endif /* __GNUC__ */ - /* Generic function for zlog. */ extern void zlog (struct zlog *zl, int priority, const char *format, ...) PRINTF_ATTRIBUTE(3, 4); diff --git a/lib/memory.c b/lib/memory.c index c13404d2..e15492c9 100644 --- a/lib/memory.c +++ b/lib/memory.c @@ -72,7 +72,7 @@ mem_tracker_zeroise(struct mem_tracker* mem) memset(mem, 0, sizeof(struct mem_tracker)) ; } ; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER #include "mem_tracker.c" #endif @@ -130,7 +130,7 @@ zmalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME) else { mstat.mt[mtype].alloc++; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER mem_md_malloc(mtype, memory, size, name) ; #endif UNLOCK ; @@ -159,7 +159,7 @@ zcalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME) else { mstat.mt[mtype].alloc++; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER mem_md_malloc(mtype, memory, size, name) ; #endif UNLOCK ; @@ -188,7 +188,7 @@ zrealloc (enum MTYPE mtype, void *ptr, size_t size MEMORY_TRACKER_NAME) { if (ptr == NULL) mstat.mt[mtype].alloc++; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER mem_md_realloc(mtype, ptr, memory, size, name) ; #endif UNLOCK ; @@ -210,7 +210,7 @@ zfree (enum MTYPE mtype, void *ptr) assert(mstat.mt[mtype].alloc > 0) ; mstat.mt[mtype].alloc--; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER mem_md_free(mtype, ptr) ; #endif @@ -230,7 +230,7 @@ zstrdup (enum MTYPE mtype, const char *str MEMORY_TRACKER_NAME) LOCK ; - dup = strdup (str); + dup = strdup (str ? str : ""); if (dup == NULL) { UNLOCK ; @@ -239,7 +239,7 @@ zstrdup (enum MTYPE mtype, const char *str MEMORY_TRACKER_NAME) else { mstat.mt[mtype].alloc++; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER mem_md_malloc(mtype, dup, strlen(str)+1, name) ; #endif UNLOCK ; @@ -443,7 +443,7 @@ show_memory_type_vty (struct vty *vty, const char* name, vty_out (vty, "-----------------------------%s", VTY_NEWLINE) ; vty_out (vty, "%-30s:", name) ; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER show_memory_tracker_detail(vty, mt, alloc) ; #else vty_out (vty, " %10ld", alloc) ; @@ -464,13 +464,13 @@ show_memory_vty (struct vty *vty, struct memory_list *m, struct mlist* ml, struct mem_tracker mem_one ; struct mem_tracker* mt ; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER struct mem_type_tracker mem_tt ; #endif LOCK ; mst = mstat ; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER mem_tt = mem_type_tracker ; #endif UNLOCK ; @@ -499,7 +499,7 @@ show_memory_vty (struct vty *vty, struct memory_list *m, struct mlist* ml, else { alloc = mst.mt[m->index].alloc ; -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER mt = &(mem_tt.mt[m->index]) ; #else mt = &mem_one ; @@ -580,7 +580,7 @@ DEFUN_CALL (show_memory_summary, "Memory statistics\n" "Summary memory statistics\n") { -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER show_memory_tracker_summary(vty) ; #else long alloc = 0 ; @@ -613,7 +613,7 @@ DEFUN_CALL (show_memory_all, #ifdef HAVE_MALLINFO needsep |= show_memory_mallinfo (vty); #endif /* HAVE_MALLINFO */ -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER needsep |= show_memory_tracker_summary(vty) ; #endif diff --git a/lib/memory.h b/lib/memory.h index 6cc95a5b..4fccb079 100644 --- a/lib/memory.h +++ b/lib/memory.h @@ -58,11 +58,21 @@ typedef enum MTYPE mtype_t ; mtype_zstrdup (__FILE__, __LINE__, (mtype), (str)) #else -#ifdef QDEBUG -#define MEMORY_TRACKER 1 +#ifdef MEMORY_TRACKER /* Can be forced from outside */ +# if MEMORY_TRACKER +# define MEMORY_TRACKER 1 /* Force 1 or 0 */ +#else +# define MEMORY_TRACKER 0 +# endif +#else +# ifdef QDEBUG +# define MEMORY_TRACKER 1 /* Follow QDEBUG */ +#else +# define MEMORY_TRACKER 0 +# endif #endif -#ifdef MEMORY_TRACKER +#if MEMORY_TRACKER #define MEMORY_TRACKER_NAME , const char* name #define MEMORY_TRACKER_FUNC , __func__ #else diff --git a/lib/memtypes.c b/lib/memtypes.c index e5a591d6..7e73e4c1 100644 --- a/lib/memtypes.c +++ b/lib/memtypes.c @@ -16,7 +16,8 @@ struct memory_list memory_list_lib[] = { { MTYPE_TMP, "Temporary memory" }, { MTYPE_STRING, "String (general)" }, - { MTYPE_STRVEC, "String vector" }, + { MTYPE_CMD_ITEM, "Command item" }, + { MTYPE_CMD_STRING, "Command string" }, { MTYPE_VECTOR, "Vector structure" }, { MTYPE_VECTOR_BODY, "Vector body" }, { MTYPE_SYMBOL_TABLE, "Symbol Table structure" }, @@ -44,13 +45,14 @@ struct memory_list memory_list_lib[] = { MTYPE_QPN_NEXUS, "qtn nexus" }, { MTYPE_TSD, "Thread specific data" }, { MTYPE_VTY, "VTY" }, + { MTYPE_CMD_EXEC, "Command exec context" }, { MTYPE_CMD_PARSED, "Parsed command" }, { MTYPE_TOKEN, "Command token" }, - { MTYPE_MARSHAL, "marshalled commands" }, { MTYPE_VTY_OUT_BUF, "VTY output buffer" }, { MTYPE_VTY_HIST, "VTY history" }, { MTYPE_VTY_NAME, "VTY name" }, { MTYPE_KEY_STREAM, "Keystroke Stream" }, + { MTYPE_VTY_CLI, "VTY CLI" }, { MTYPE_VIO_FIFO, "VTY IO FIFO" }, { MTYPE_VIO_FIFO_LUMP, "VTY IO FIFO Lump" }, { MTYPE_VIO_LC, "VTY IO Line Control" }, @@ -87,7 +89,6 @@ struct memory_list memory_list_lib[] = { MTYPE_ROUTE_MAP_RULE, "Route map rule" }, { MTYPE_ROUTE_MAP_RULE_STR, "Route map rule str" }, { MTYPE_ROUTE_MAP_COMPILED, "Route map compiled" }, - { MTYPE_DESC, "Command desc" }, { MTYPE_KEY, "Key" }, { MTYPE_KEYCHAIN, "Key chain" }, { MTYPE_IF_RMAP, "Interface route map" }, diff --git a/lib/misc.h b/lib/misc.h index 9437b617..1bd0680b 100644 --- a/lib/misc.h +++ b/lib/misc.h @@ -22,13 +22,16 @@ #ifndef _ZEBRA_MISC_H #define _ZEBRA_MISC_H +#include "zconfig.h" + /* Stuff which we generally expect to have */ -#include -#include #include +#include + #include +#include +#include #include -#include #include "zassert.h" @@ -43,6 +46,9 @@ */ #define Private extern +/* For use in switch/case */ +#define fall_through + /* Other names of true/false */ enum on_off { @@ -67,14 +73,26 @@ enum free_keep } ; typedef enum free_keep free_keep_b ; -/* We really want to be able to assume that an int is at least 32 bits */ -CONFIRM(UINT_MAX >= 0xFFFFFFFF) ; +/* We really want to be able to assume that an int is at least 32 bits + * and that a long is at least 64 bits ! + */ +CONFIRM(UINT_MAX >= 0xFFFFFFFF) ; +CONFIRM(ULONG_MAX >= 0xFFFFFFFFFFFFFFFF) ; /* Some useful shorthand */ typedef unsigned char byte ; typedef unsigned char uchar ; + typedef unsigned int uint ; typedef unsigned int usize ; typedef unsigned int ulen ; +typedef unsigned long ulong ; + +/* cvp == const void* -- ptr to constant void + * cvp* == void * const* -- ptr to ptr to constant void + * const cvp* == void const* const* -- ptr to constant ptr to constant void + */ +typedef const void* cvp ; + #endif /* _ZEBRA_MISC_H */ diff --git a/lib/mqueue.c b/lib/mqueue.c index 8fa9fbd5..c7037a64 100644 --- a/lib/mqueue.c +++ b/lib/mqueue.c @@ -254,7 +254,7 @@ mqueue_init_new(mqueue_queue mq, enum mqueue_queue_type type) extern void mqueue_empty(mqueue_queue mq) { - mqueue_revoke(mq, NULL) ; + mqueue_revoke(mq, NULL, 0) ; assert((mq->head == NULL) && (mq->count == 0)) ; } ; @@ -268,8 +268,11 @@ mqueue_empty(mqueue_queue mq) * NB: there MUST NOT be ANY waiters ! */ extern mqueue_queue -mqueue_reset(mqueue_queue mq, int free_structure) +mqueue_reset(mqueue_queue mq, free_keep_b free_structure) { + if (mq == NULL) + return NULL ; + mqueue_empty(mq) ; passert(mq->waiters == 0) ; @@ -327,13 +330,10 @@ mqueue_local_init_new(mqueue_local_queue lmq) * * Dequeues entries and dispatches them "mqb_destroy", to empty the queue. * - * See: mqueue_local_reset_keep(lmq) - * mqueue_local_reset_free(lmq) - * * Returns address of Local Message Queue */ extern mqueue_local_queue -mqueue_local_reset(mqueue_local_queue lmq, int free_structure) +mqueue_local_reset(mqueue_local_queue lmq, free_keep_b free_structure) { mqueue_block mqb ; @@ -533,7 +533,7 @@ static void mqueue_dequeue_signal(mqueue_queue mq, mqueue_thread_signal mtsig) ; * never be any waiters... so no kicking is ever done. */ extern void -mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, enum mqb_rank priority) +mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, mqb_rank_b priority) { if (mq == NULL) return mqb_dispatch_destroy(mqb) ; @@ -755,35 +755,41 @@ done: * * Revokes by calling mqb_dispatch_destroy(). * - * During a revoke() operation more items may be enqueued, but no other mqueue - * operations may be performed. Enqueued items may promptly be revoked, except - * for priority items if the revoke operation has already moved past the last - * priority item. + * NB: for safety, holds the queue locked for the duration of the revoke + * operation. + * + * If the destroy code can handle it, this means that can revoke stuff + * from one thread even though it is usually only dequeued by another. + * + * The danger is that if queues get very long, and many revokes happen, + * may (a) spend a lot of time scanning the message queue, which stops + * other threads as soon as they try to enqueue anything, and (b) if this + * happens a lot, could end up in an O(n^2) thing scanning the message + * queue once for each revoked object type. * * If mq is NULL, does nothing. + * + * If num > 0, stops after revoking that many messages. + * + * Returns: number of messages revoked. */ -extern void -mqueue_revoke(mqueue_queue mq, void* arg0) +extern int +mqueue_revoke(mqueue_queue mq, void* arg0, int num) { mqueue_block mqb ; mqueue_block prev ; + int did ; if (mq == NULL) - return ; + return 0 ; qpt_mutex_lock(&mq->mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ + did = 0 ; prev = NULL ; - while (1) + mqb = mq->head ; + while (mqb != NULL) { - if (prev == NULL) - mqb = mq->head ; - else - mqb = prev->next ; - - if (mqb == NULL) - break ; - if ((arg0 == NULL) || (arg0 == mqb->arg0)) { assert(mq->count > 0) ; @@ -800,16 +806,30 @@ mqueue_revoke(mqueue_queue mq, void* arg0) mq->tail_priority = prev ; --mq->count ; + ++did ; + + mqb_dispatch_destroy(mqb) ; - qpt_mutex_unlock(&mq->mutex) ; - mqb_dispatch_destroy(mqb) ; - qpt_mutex_lock(&mq->mutex) ; + if (num == 1) + break ; + else if (num > 1) + --num ; + + if (prev == NULL) + mqb = mq->head ; + else + mqb = prev->next ; } else - prev = mqb ; + { + prev = mqb ; + mqb = mqb->next ; + } ; } ; qpt_mutex_unlock(&mq->mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ + + return did ; } ; /*------------------------------------------------------------------------------ @@ -930,8 +950,11 @@ mqueue_thread_signal_init(mqueue_thread_signal mqt, qpt_thread_t thread, * Otherwise zeroises the structure, and returns address of same. */ extern mqueue_thread_signal -mqueue_thread_signal_reset(mqueue_thread_signal mqt, int free_structure) +mqueue_thread_signal_reset(mqueue_thread_signal mqt, free_keep_b free_structure) { + if (mqt == NULL) + return NULL ; + passert(mqt->prev == NULL) ; if (free_structure) diff --git a/lib/mqueue.h b/lib/mqueue.h index 68dceb15..f33af564 100644 --- a/lib/mqueue.h +++ b/lib/mqueue.h @@ -191,83 +191,48 @@ struct mqueue_local_queue * Functions */ -extern void -mqueue_initialise(void) ; - -extern void -mqueue_finish(void) ; - -extern mqueue_queue -mqueue_init_new(mqueue_queue mq, enum mqueue_queue_type type) ; - -extern void -mqueue_empty(mqueue_queue mq) ; - -extern mqueue_queue -mqueue_reset(mqueue_queue mq, int free_structure) ; - -#define mqueue_reset_keep(mq) mqueue_reset(mq, 0) -#define mqueue_reset_free(mq) mqueue_reset(mq, 1) - -extern mqueue_local_queue -mqueue_local_init_new(mqueue_local_queue lmq) ; - -extern mqueue_local_queue -mqueue_local_reset(mqueue_local_queue lmq, int free_structure) ; - -#define mqueue_local_reset_keep(lmq) mqueue_local_reset(lmq, 0) -#define mqueue_local_reset_free(lmq) mqueue_local_reset(lmq, 1) - -extern void -mqueue_set_timeout_interval(mqueue_queue mq, qtime_t interval) ; - -extern mqueue_thread_signal -mqueue_thread_signal_init(mqueue_thread_signal mqt, qpt_thread_t thread, - int signum) ; -mqueue_thread_signal -mqueue_thread_signal_reset(mqueue_thread_signal mqt, int free_structure) ; - -#define mqueue_thread_signal_reset_keep(mqt) mqueue_thread_signal_reset(mqt, 0) -#define mqueue_thread_signal_reset_free(mqt) mqueue_thread_signal_reset(mqt, 1) - -extern mqueue_block -mqb_init_new(mqueue_block mqb, mqueue_action action, void* arg0) ; - -extern mqueue_block -mqb_re_init(mqueue_block mqb, mqueue_action action, void* arg0) ; - -extern void -mqb_free(mqueue_block mqb) ; +extern void mqueue_initialise(void) ; +extern void mqueue_finish(void) ; + +extern mqueue_queue mqueue_init_new(mqueue_queue mq, + enum mqueue_queue_type type) ; +extern void mqueue_empty(mqueue_queue mq) ; +extern mqueue_queue mqueue_reset(mqueue_queue mq, free_keep_b free_structure) ; + +extern mqueue_local_queue mqueue_local_init_new(mqueue_local_queue lmq) ; +extern mqueue_local_queue mqueue_local_reset(mqueue_local_queue lmq, + free_keep_b free_structure) ; + +extern void mqueue_set_timeout_interval(mqueue_queue mq, qtime_t interval) ; +extern mqueue_thread_signal mqueue_thread_signal_init(mqueue_thread_signal mqt, + qpt_thread_t thread, int signum) ; +mqueue_thread_signal mqueue_thread_signal_reset(mqueue_thread_signal mqt, + free_keep_b free_structure) ; + +extern mqueue_block mqb_init_new(mqueue_block mqb, mqueue_action action, + void* arg0) ; +extern mqueue_block mqb_re_init(mqueue_block mqb, mqueue_action action, + void* arg0) ; +extern void mqb_free(mqueue_block mqb) ; enum mqb_rank { mqb_priority = true, mqb_ordinary = false } ; +typedef enum mqb_rank mqb_rank_b ; -extern void -mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, enum mqb_rank priority) ; - -extern mqueue_block -mqueue_dequeue(mqueue_queue mq, int wait, void* arg) ; +extern void mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, + mqb_rank_b priority) ; +extern mqueue_block mqueue_dequeue(mqueue_queue mq, int wait, void* arg) ; +extern int mqueue_revoke(mqueue_queue mq, void* arg0, int num) ; -extern void -mqueue_revoke(mqueue_queue mq, void* arg0) ; - -extern int -mqueue_done_waiting(mqueue_queue mq, mqueue_thread_signal mtsig) ; - -extern void -mqueue_local_enqueue(mqueue_local_queue lmq, mqueue_block mqb) ; - -extern void -mqueue_local_enqueue_head(mqueue_local_queue lmq, mqueue_block mqb) ; - -Inline mqueue_block -mqueue_local_head(mqueue_local_queue lmq) ; +extern int mqueue_done_waiting(mqueue_queue mq, mqueue_thread_signal mtsig) ; -extern mqueue_block -mqueue_local_dequeue(mqueue_local_queue lmq) ; +extern void mqueue_local_enqueue(mqueue_local_queue lmq, mqueue_block mqb) ; +extern void mqueue_local_enqueue_head(mqueue_local_queue lmq, mqueue_block mqb) ; +Inline mqueue_block mqueue_local_head(mqueue_local_queue lmq) ; +extern mqueue_block mqueue_local_dequeue(mqueue_local_queue lmq) ; /*============================================================================== * Access functions for mqueue_block fields -- mqb_set_xxx/mqb_get_xxx diff --git a/lib/node_type.h b/lib/node_type.h deleted file mode 100644 index b1f26187..00000000 --- a/lib/node_type.h +++ /dev/null @@ -1,82 +0,0 @@ -/* Command handler node_type stuff -- header - * Copyright (C) 1997, 98 Kunihiro Ishiguro - * - * This file is part of GNU Zebra. - * - * GNU Zebra is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published - * by the Free Software Foundation; either version 2, or (at your - * option) any later version. - * - * GNU Zebra is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GNU Zebra; see the file COPYING. If not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#ifndef _ZEBRA_NODE_TYPE_H -#define _ZEBRA_NODE_TYPE_H - -/* There are some command levels which called from command node. */ -enum node_type -{ - AUTH_NODE, /* Authentication mode of vty interface. */ - RESTRICTED_NODE, /* Restricted view mode */ - VIEW_NODE, /* View node. Default mode of vty interface. */ - AUTH_ENABLE_NODE, /* Authentication mode for change enable. */ - ENABLE_NODE, /* Enable node. */ - - MIN_DO_SHORTCUT_NODE = ENABLE_NODE, - /* May not "do xxx" at any node lower */ - MAX_NON_CONFIG_NODE = ENABLE_NODE, - /* May not be higher than this without owning - * the configuration symbol of power */ - - CONFIG_NODE, /* Config node. Default mode of config file. */ - - MIN_CONTEXT_NODE = CONFIG_NODE, - /* May not change context to any node lower */ - - SERVICE_NODE, /* Service node. */ - DEBUG_NODE, /* Debug node. */ - AAA_NODE, /* AAA node. */ - KEYCHAIN_NODE, /* Key-chain node. */ - KEYCHAIN_KEY_NODE, /* Key-chain key node. */ - INTERFACE_NODE, /* Interface mode node. */ - ZEBRA_NODE, /* zebra connection node. */ - TABLE_NODE, /* rtm_table selection node. */ - RIP_NODE, /* RIP protocol mode node. */ - RIPNG_NODE, /* RIPng protocol mode node. */ - BGP_NODE, /* BGP protocol mode which includes BGP4+ */ - BGP_VPNV4_NODE, /* BGP MPLS-VPN PE exchange. */ - BGP_IPV4_NODE, /* BGP IPv4 unicast address family. */ - BGP_IPV4M_NODE, /* BGP IPv4 multicast address family. */ - BGP_IPV6_NODE, /* BGP IPv6 address family */ - BGP_IPV6M_NODE, /* BGP IPv6 multicast address family. */ - OSPF_NODE, /* OSPF protocol mode */ - OSPF6_NODE, /* OSPF protocol for IPv6 mode */ - ISIS_NODE, /* ISIS protocol mode */ - MASC_NODE, /* MASC for multicast. */ - IRDP_NODE, /* ICMP Router Discovery Protocol mode. */ - IP_NODE, /* Static ip route node. */ - ACCESS_NODE, /* Access list node. */ - PREFIX_NODE, /* Prefix list node. */ - ACCESS_IPV6_NODE, /* Access list node. */ - PREFIX_IPV6_NODE, /* Prefix list node. */ - AS_LIST_NODE, /* AS list node. */ - COMMUNITY_LIST_NODE, /* Community list node. */ - RMAP_NODE, /* Route map node. */ - SMUX_NODE, /* SNMP configuration node. */ - DUMP_NODE, /* Packet dump node. */ - FORWARDING_NODE, /* IP forwarding node. */ - PROTOCOL_NODE, /* protocol filtering node */ - VTY_NODE, /* Vty node. */ -} ; -typedef enum node_type node_type_t ; - -#endif /* _ZEBRA_NODE_TYPE_H */ diff --git a/lib/pthread_safe.c b/lib/pthread_safe.c index c4ab9679..e2c7cdc0 100644 --- a/lib/pthread_safe.c +++ b/lib/pthread_safe.c @@ -299,7 +299,8 @@ errtox(strerror_t* st, int err, int len, int want) if ((len + ql) <= qfs_left(&qfs)) /* accounting for "quotes" */ qfs_printf(&qfs, "%s%s%s", q, errm, q) ; else - qfs_printf(&qfs, "%s%.*s...%s", q, qfs_left(&qfs) - ql - 3, errm, q) ; + qfs_printf(&qfs, "%s%.*s...%s", + q, (int)(qfs_left(&qfs) - ql - 3), errm, q) ; /* -ve precision is ignored ! */ } ; diff --git a/lib/qfstring.c b/lib/qfstring.c index 3db3fedf..f40f1417 100644 --- a/lib/qfstring.c +++ b/lib/qfstring.c @@ -26,10 +26,10 @@ */ /*------------------------------------------------------------------------------ - * Initialise qf_str -- to given size (which includes the '\n') + * Initialise qf_str -- to given size (which includes the '\0') * * Sets pointers and terminates an empty string with one byte reserved for the - * terminating '\n'. + * terminating '\0'. * * This operation is async-signal-safe. */ @@ -47,12 +47,12 @@ qfs_init(qf_str qfs, char* str, size_t size) /*------------------------------------------------------------------------------ * Initialise qf_str which already contains string -- to given size (which - * includes the '\n') + * includes the '\0') * * This may be used to prepare for appending to a buffer which already contains * something. * - * Sets pointers, setting the write pointer to the existing terminating '\n'. + * Sets pointers, setting the write pointer to the existing terminating '\0'. * * This operation is async-signal-safe. */ @@ -331,7 +331,7 @@ qfs_pointer(qf_str qfs, void* p_val, enum pf_flags flags, * * If the precision is < 0 it is ignored (unless pf_hex, see below). * - * If the precision is 0 it is ignored unless bf_precision is set. + * If the precision is 0 it is ignored unless pf_precision is set. * * Precedence issues: * @@ -776,7 +776,7 @@ qfs_vprintf(qf_str qfs, const char *format, va_list args) if (precision < 0) { precision = 0 ; - flags &= ~pf_precision ; + flags &= ~pf_precision ; /* completely ignore */ } ; } else @@ -885,7 +885,7 @@ qfs_vprintf(qf_str qfs, const char *format, va_list args) * %s handler -- tolerates NULL pointer * * Accepts: width - * precision + * precision -- ignored if < 0 * pf_precision -- explicit precision * * Rejects: pf_commas -- "'" seen @@ -913,6 +913,12 @@ qfs_arg_string(qf_str qfs, va_list args, enum pf_flags flags, if (flags != (flags & pf_precision)) return pfp_failed ; + if (precision < 0) /* make sure */ + { + precision = 0 ; + flags &= ~pf_precision ; + } ; + len = (src != NULL) ? strlen(src) : 0 ; if (((precision > 0) || (flags & pf_precision)) && (len > precision)) len = precision ; diff --git a/lib/qiovec.c b/lib/qiovec.c index 546dfcb0..38ea0dbb 100644 --- a/lib/qiovec.c +++ b/lib/qiovec.c @@ -18,15 +18,29 @@ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ +#include "zconfig.h" /* Otherwise IOV_MAX is not defined ! */ -#include "zebra.h" +#include +#include "misc.h" #include "memory.h" -#include "zassert.h" #include "miyagi.h" #include "qiovec.h" +/*============================================================================== + * IOV_MAX is defined by POSIX to appear in . + * + * This does not appear unless "zconfig.h" is included, first... + * ...but although it compiles OK, Eclipse cannot see where the value has + * come from. + */ +#ifdef IOV_MAX /* Stops Eclipse whinging */ +#if IOV_MAX < 64 /* check for a reasonable value */ +#error IOV_MAX < 64 +#endif +#endif + /*============================================================================== * Initialise, allocate and reset qiovec */ diff --git a/lib/qiovec.h b/lib/qiovec.h index f1498146..230198f3 100644 --- a/lib/qiovec.h +++ b/lib/qiovec.h @@ -22,9 +22,8 @@ #ifndef _ZEBRA_QIOVEC_H #define _ZEBRA_QIOVEC_H -#include "zebra.h" - #include "misc.h" +#include /* iovec stuff */ /*============================================================================== * Flexible size "struct iovec" diff --git a/lib/qpath.c b/lib/qpath.c index f6157b89..07c294de 100644 --- a/lib/qpath.c +++ b/lib/qpath.c @@ -68,7 +68,7 @@ qpath_init_new(qpath qp, const char* path) /* Worry about fields other than the path */ - qs_init_new(&qp->path, 0) ; + qs_init_new(qp->path, 0) ; if (path != NULL) qpath_set(qp, path) ; @@ -119,7 +119,7 @@ qpath_set(qpath qp, const char* path) if (path != NULL) qs_set(&qp->path, path) ; else - qs_clear(&qp->path) ; + qs_clear(qp->path) ; /* Worry about fields other than the path */ } ; @@ -521,7 +521,7 @@ qpath_push_str(qpath qp, const char* path) len = 0 ; /* Worry about whether need to add a '/' to the path before pushing */ - sp = qs_chars(qs) ; + sp = qs_char(qs) ; ep = qs_ep_char(qs) ; /* points at trailing '\0' */ if (sp == NULL) diff --git a/lib/qpnexus.c b/lib/qpnexus.c index 3b014be2..001d5049 100644 --- a/lib/qpnexus.c +++ b/lib/qpnexus.c @@ -18,9 +18,8 @@ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ - -#include -#include +#include "misc.h" +#include #include "qpnexus.h" #include "memory.h" @@ -91,7 +90,7 @@ qpn_add_hook_function(qpn_hook_list list, void* hook) * object is empty or otherwise out of action. */ extern qpn_nexus -qpn_reset(qpn_nexus qpn, bool free_structure) +qpn_reset(qpn_nexus qpn, free_keep_b free_structure) { qps_file qf; qtimer qtr; @@ -115,11 +114,8 @@ qpn_reset(qpn_nexus qpn, bool free_structure) qpn->selection = NULL ; } - if (qpn->queue != NULL) - qpn->queue = mqueue_reset(qpn->queue, 1); - - if (qpn->mts != NULL) - qpn->mts = mqueue_thread_signal_reset(qpn->mts, 1); + qpn->queue = mqueue_reset(qpn->queue, free_it); + qpn->mts = mqueue_thread_signal_reset(qpn->mts, free_it); if (free_structure) XFREE(MTYPE_QPN_NEXUS, qpn) ; /* sets qpn = NULL */ diff --git a/lib/qpnexus.h b/lib/qpnexus.h index 03b52876..a6c8bf44 100644 --- a/lib/qpnexus.h +++ b/lib/qpnexus.h @@ -146,9 +146,6 @@ extern qpn_nexus qpn_init_new(qpn_nexus qpn, bool main_thread); extern void qpn_add_hook_function(qpn_hook_list list, void* hook) ; extern void qpn_exec(qpn_nexus qpn); extern void qpn_terminate(qpn_nexus qpn); -extern qpn_nexus qpn_reset(qpn_nexus qpn, bool free_structure); - -#define qpn_reset_free(qpn) qpn_reset(qpn, 1) -#define qpn_reset_keep(qpn) qpn_reset(qpn, 0) +extern qpn_nexus qpn_reset(qpn_nexus qpn, free_keep_b free_structure); #endif /* _ZEBRA_QPNEXUS_H */ diff --git a/lib/qpselect.c b/lib/qpselect.c index c15f112e..bcd2e2c9 100644 --- a/lib/qpselect.c +++ b/lib/qpselect.c @@ -29,13 +29,24 @@ #include "memory.h" #include "vector.h" -enum { qdebug = -#ifdef QDEBUG - 1 +/*------------------------------------------------------------------------------ + * Sort out any debug setting + */ +#ifdef QPSELECT_DEBUG /* Can be forced from outside */ +# if QPSELECT_DEBUG +# define QPSELECT_DEBUG 1 /* Force 1 or 0 */ +#else +# define QPSELECT_DEBUG 0 +# endif #else - 0 +# ifdef QDEBUG +# define QPSELECT_DEBUG 1 /* Follow QDEBUG */ +#else +# define QPSELECT_DEBUG 0 +# endif #endif -}; + +enum { qpselect_debug = QPSELECT_DEBUG } ; /*============================================================================== * Quagga pselect -- qps_xxxx @@ -319,7 +330,7 @@ qps_pselect(qps_selection qps, qtime_t max_wait) fd_set* p_fds[qps_mnum_count] ; int n ; - if (qdebug) + if (qpselect_debug) qps_selection_validate(qps) ; /* If there is stuff still pending, tidy up by zeroising the result */ @@ -415,7 +426,7 @@ qps_dispatch_next(qps_selection qps) qps_file qf ; qps_mnum_t mnum ; - if (qdebug) + if (qpselect_debug) qps_selection_validate(qps) ; if (qps->pend_count == 0) diff --git a/lib/qpthreads.h b/lib/qpthreads.h index 959194ad..da6b2319 100644 --- a/lib/qpthreads.h +++ b/lib/qpthreads.h @@ -52,6 +52,22 @@ #error Require _POSIX_THREADS #endif +#ifdef QPTHREADS_DEBUG /* Can be forced from outside */ +# if QPTHREADS_DEBUG +# define QPTHREADS_DEBUG 1 /* Force 1 or 0 */ +#else +# define QPTHREADS_DEBUG 0 +# endif +#else +# ifdef QDEBUG +# define QPTHREADS_DEBUG 1 /* Follow QDEBUG */ +#else +# define QPTHREADS_DEBUG 0 +# endif +#endif + +enum { qpthreads_debug = QPTHREADS_DEBUG } ; + /*============================================================================== * Global Switch -- this allows the library to be run WITHOUT pthreads ! * @@ -165,7 +181,7 @@ Inline bool qpt_thread_is_self(qpt_thread_t id) * * Quagga's default mutex type is: * - * * PTHREAD_MUTEX_ERRORCHECK unless NDEBUG && NDEBUG_QPTHREADS + * * PTHREAD_MUTEX_ERRORCHECK if QPTHREADS_DEBUG * * QPT_MUTEX_TYPE_DEFAULT * * QPT_MUTEX_TYPE_DEFAULT may be set elsewhere. If it is not set then it is @@ -200,10 +216,10 @@ enum qpt_mutex_options # define QPT_MUTEX_TYPE_DEFAULT PTHREAD_MUTEX_NORMAL #endif -#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS) -# define QPT_MUTEX_TYPE QPT_MUTEX_TYPE_DEFAULT -#else +#if QPTHREADS_DEBUG # define QPT_MUTEX_TYPE PTHREAD_MUTEX_ERRORCHECK +#else +# define QPT_MUTEX_TYPE QPT_MUTEX_TYPE_DEFAULT #endif extern qpt_mutex /* freezes qpthreads_enabled */ diff --git a/lib/qstring.c b/lib/qstring.c index c7e5b81c..19f74e8c 100644 --- a/lib/qstring.c +++ b/lib/qstring.c @@ -19,13 +19,10 @@ * Boston, MA 02111-1307, USA. */ -#include -#include +#include "stdio.h" #include "qstring.h" - #include "memory.h" -#include "zassert.h" /*============================================================================== */ @@ -38,15 +35,53 @@ qs_new(void) { /* zeroising sets a completely empty qstring -- see qs_init_new() */ return XCALLOC(MTYPE_QSTRING, sizeof(qstring_t)) ; + + confirm(QSTRING_INIT_ALL_ZEROS) ; +} ; + +/*------------------------------------------------------------------------------ + * Create a new body or extend existing one to accommodate at least slen + 1. + * + * Sets size to multiple of 8 (minimum 16), with at least 9 bytes free beyond + * the requested slen. + * + * Does not affect 'len' or 'cp'. + * + * If 'keep_alias', ONLY sets 'b_size' & 'b_body'. + * + * If !'keep_alias', sets the elstring body & size & clears 'alias'. + */ +static inline void +qs_new_body(qstring qs, usize slen, bool keep_alias) +{ + qs->b_size = (slen + 0x10) & ~(usize)(0x08 - 1) ; + qs->b_body = XREALLOC(MTYPE_STRING, qs->b_body, qs->b_size) ; + + if (!keep_alias) + qs_set_real_body_nn(qs) ; +} ; + +/*------------------------------------------------------------------------------ + * Create a new, empty qs with body + */ +extern qstring +qs_new_with_body(usize slen) +{ + qstring qs ; + + qs = qs_new() ; + qs_new_body(qs, slen, false) ; + + return qs ; } ; /*------------------------------------------------------------------------------ * Initialise qstring -- allocate if required. * - * If non-zero slen is given, a body is allocated (for at least slen + 1). + * If non-zero slen is given, a body is allocated (size = slen + 1). * If zero slen is given, no body is allocated. * - * Sets qs->len = qs->cp = 0. '\0' terminates body if allocates one. + * Sets 'len' = 'cp' = 0. * * Returns: address of qstring * @@ -65,18 +100,20 @@ qs_init_new(qstring qs, usize slen) /* Zeroising has set: * - * body = NULL -- no body - * size = 0 -- no body + * empty elstring -- no body, 'len' = 0 + * + * size = 0 -- no elstring body * - * len = 0 * cp = 0 * - * b_body = NULL -- no body buffer - * b_size = 0 -- no body buffer + * b_body = NULL -- no body buffer + * b_size = 0 -- no body buffer + * + * alias = false -- not an alias qstring */ if (slen != 0) - qs_make_to_size(qs, slen, false) ; + qs_new_body(qs, slen, false) ; return qs ; } ; @@ -104,90 +141,75 @@ qs_reset(qstring qs, free_keep_b free_structure) XFREE(MTYPE_QSTRING, qs) ; /* sets qs = NULL */ else memset(qs, 0, sizeof(qstring_t)) ; /* see qs_init_new */ + + confirm(QSTRING_INIT_ALL_ZEROS) ; } ; return qs ; } ; /*------------------------------------------------------------------------------ - * Allocate or reallocate body so that is big enough for the given "slen". + * Allocate or reallocate body so that is big enough for the given 'slen'. * - * If the qstring is currently an alias, copies all of the alias to a new - * body -- so always returns a non-alias qstring. + * qstring may NOT be NULL. * - * Returns with a body with size > 0. Allocates to 16 byte boundaries with - * space for '\0' beyond given length. + * Returns with a body with size > 0 (even if 'slen' == 0). * - * Does NOT affect qs->len or qs->cp. Does NOT re-terminate. + * Expects, but does not require, that 'slen' > 'size'. * - * NB: will allocate a new body even if the slen == 0. + * Does not change 'cp' or 'len' (even if 'len' > 'slen'). * - * NB: always copies all of any aliased string (even if the slen == 0). - * - * NB: sets terminated false + * If is an alias, copies min('len', 'alen', 'slen') characters to the body. */ Private void -qs_make_to_size(qstring qs, usize slen, bool keep) +qs_make_to_size(qstring qs, usize slen, usize alen) { - usize size ; - usize alen ; + /* If body needs to be extended, do that now */ + if (slen >= qs->b_size) + qs_new_body(qs, slen, (qs->alias && (alen != 0))) ; + /* keeps alias if required */ /* Worry about alias. If we keep it, we keep all of it. */ - if (keep && (qs->size == 0)) + if (qs->alias) { - alen = qs_len_nn(qs) ; /* alias stuff to keep */ - if (slen <= alen) - slen = alen + 1 ; /* making sure can do that. */ - } - else - alen = 0 ; /* no alias stuff to keep */ + /* alias or empty */ + usize len = qs_len_nn(qs) ; - /* Calculate the new size -- multiple of 16, >= 16. */ - size = (slen + 0x10) & ~(usize)(0x10 - 1) ; - dassert(size != 0) ; + if (alen >= len) + alen = len ; /* keep min(alen, len) */ - /* If that requires the body to be extended, do that now */ - if (size > qs->b_size) - { - /* Need to allocate or extend the buffer. - * - * If current size is not zero, extend by doubling size or making at - * least the multiple of 16 calculated for new len. - */ - qs->b_size *= 2 ; - if (qs->b_size < size) - qs->b_size = size ; - qs->b_body = XREALLOC(MTYPE_STRING, qs->b_body, qs->b_size) ; - } ; - - /* If this is a non-empty alias, copy all or part of it. */ - if (alen != 0) - memcpy(qs->b_body, qs_body_nn(qs), alen) ; - - /* Update body and size, and no longer known to be terminated */ - qs_set_body_nn(qs, qs->b_body) ; - qs->size = qs->b_size ; + if (alen > slen) + alen = slen ; /* keep only to new len. */ + if (alen != 0) + memcpy(qs->b_body, qs_body_nn(qs), alen) ; + qs_set_real_body_nn(qs) ; + } ; } ; /*============================================================================== * Setting value of qstring + * + * Copy the given string to the qstring, allocating qstring and/or extending + * it as required. + * + * Any alias is simply discarded. + * + * Sets 'len' to new length + * Sets 'cp' = 0 + * + * Returns: address of the qstring (allocated if required). */ /*------------------------------------------------------------------------------ * Set qstring to be copy of the given string. * - * Allocate qstring if required (setting qs->len = qs->cp = 0). - * - * Allocates a body and copies src to it, adding '\0'. Treats src == NULL as - * an empty string. - * - * Sets qs->len to the length of the string (excluding trailing '\0'). - * Sets qs->cp == 0. + * Treats src == NULL as an empty string. Otherwise src must be a '\0' + * terminated string. * - * Returns: address of the qstring copied to. + * Does not copy or count the '\0' terminator. * - * NB: if copying to a dummy qstring, the old body is simply discarded. + * See notes above. */ extern qstring qs_set(qstring qs, const char* src) @@ -196,157 +218,202 @@ qs_set(qstring qs, const char* src) } ; /*------------------------------------------------------------------------------ - * Set qstring to be leading 'n' bytes of given string. - * - * Allocate qstring if required (setting qs->len = qs->cp = 0). - * - * Allocates a body and copies 'n' bytes from src to it, adding '\0'. The - * src pointer is ignored if n == 0. + * Set qstring to be copy of leading 'n' bytes of given string. * - * Sets qs->len to the length of the string (excluding trailing '\0'). - * Sets qs->cp == 0. + * If n == 0, src is ignored (and may be NULL) + * If n > 0, src string MUST be at least 'n' bytes long. * - * Returns: address of the qstring copied to. - * - * NB: if n == 0, src may be NULL - * - * NB: if n > 0, src string MUST be at least 'n' bytes long. - * - * NB: if copying to a dummy qstring, the old body is simply discarded. + * See notes above. */ extern qstring qs_set_n(qstring qs, const char* src, usize len) { - char* p ; - - qs = qs_new_len(qs, len) ; /* ensures have body > n */ - - p = qs_char_nn(qs) ; + qs = qs_new_size(qs, len) ; /* ensures have body > len */ if (len != 0) - memcpy(p, src, len) ; - - *(p + len) = '\0' ; + memcpy(qs_char_nn(qs), src, len) ; - qs_set_term_nn(qs, true) ; - qs->cp = 0 ; + qs_set_len_nn(qs, len) ; + qs_set_cp_nn(qs, 0) ; return qs ; } ; /*------------------------------------------------------------------------------ - * Append given string to a qstring -- adding at qs->len position. - * - * Allocate qstring if required (setting qs->len = qs->cp = 0). - * - * Allocates or extends the body and copies bytes from src to it, adding '\0'. - * Treats src == NULL as an empty string. - * - * Sets qs->len to the length of the result (excluding trailing '\0') - * Does not change qs->cp. + * Set qstring to be copy of given qstring contents. * - * Returns: address of the qstring appended to. + * If the given qstring is an alias, then the contents of the alias are copied + * (so the result is not an alias). See qs_copy() for the alternative. * - * NB: if appending to a dummy qstring, the old body is copied first. + * See notes above -- and note that 'cp' is set to 0. */ -extern qstring qs_append(qstring qs, const char* src) +extern qstring +qs_set_qs(qstring qs, qstring src) { - return qs_append_n(qs, src, (src != NULL) ? strlen(src) : 0) ; + return qs_set_n(qs, qs_body(src), qs_len(src)) ; } ; /*------------------------------------------------------------------------------ - * Append leading 'n' bytes of given string to a qstring -- adding at qs->len - * position. - * - * Allocate qstring if required (setting qs->len = qs->cp = 0). + * Set qstring to be copy of given elstring contents. * - * Allocates or extends the body and copies 'n' bytes from src to it, - * adding '\0'. The src pointer is ignored if n == 0. + * See notes above. + */ +extern qstring +qs_set_els(qstring qs, elstring src) +{ + return qs_set_n(qs, els_body(src), els_len(src)) ; +} ; + +/*------------------------------------------------------------------------------ + * Set qstring with given pattern to given length. * - * Sets qs->len to the length of the result (excluding trailing '\0') - * Does not change qs->cp. + * Repeats the given pattern as many times as necessary to get to the given + * length -- using a final partial piece of the pattern as required. * - * Returns: address of the qstring appended to. + * If the pattern is zero length, fills with spaces ! * - * NB: if n == 0, src may be NULL + * See notes above. + */ +extern qstring +qs_set_fill(qstring qs, usize len, const char* src) +{ + return qs_set_fill_n(qs, len, src, (src != NULL ? strlen(src) : 0)) ; +} ; + +/*------------------------------------------------------------------------------ + * Set qstring with given pattern to given length. * - * NB: if n > 0, src string MUST be at least 'n' bytes long. + * Repeats the given pattern as many times as necessary to get to the given + * length -- using a final partial piece of the pattern as required. * - * NB: if appending to a dummy qstring, the old body is copied first. + * See notes above. */ extern qstring -qs_append_n(qstring qs, const char* src, usize n) +qs_set_fill_n(qstring qs, usize len, const char* src, usize flen) { - char* ep ; + char* p ; + char* q ; + usize left ; - qs = qs_add_len(qs, n, &ep) ; + qs = qs_new_size(qs, len) ; /* ensures have body > len */ - if (n != 0) - memcpy(ep, src, n) ; + if (len != 0) + { + if (flen == 0) + { + src = " " ; + flen = strlen(src) ; + } ; + + if (len < flen) + flen = len ; + + q = p = qs_char_nn(qs) ; + memcpy(p, src, flen) ; + p += flen ; + left = len - flen ; + + while (left > 0) + { + if (left < flen) + flen = left ; + + memcpy(p, q, flen) ; + p += flen ; + left -= flen ; + + flen += flen ; + } ; + } ; + + qs_set_len_nn(qs, len) ; + qs_set_cp_nn(qs, 0) ; return qs ; } ; -/*------------------------------------------------------------------------------ - * Copy one qstring to another +/*============================================================================== + * Appending to a qstring * - * If both are NULL, returns NULL. + * Copy the given string to the end of the given qstring (at 'len'), + * allocating qstring and/or extending it as required. * - * Otherwise if dst is NULL, creates a new qstring. + * If this is an alias, it is copied to before being appended to (even if + * appending nothing). * - * Sets dst: body = copy of src->len bytes of src->body -- '\0' terminated. - * cp = src->cp - * len = src->len + * Can append to NULL or empty qstring. * - * Where a NULL src has zero cp and len. + * Sets 'len' to new length. + * Does not affect 'cp'. + * + * Returns: address of the qstring (allocated if required). + */ + +/*------------------------------------------------------------------------------ + * Append given string to a qstring. * - * If not NULL, the destination is guaranteed to have a body, and that will be - * '\0' terminated. + * Treats src == NULL as an empty string. Otherwise src must be a '\0' + * terminated string. * - * Returns: the destination qstring + * Does not copy or count the '\0' terminator. * - * NB: if copying to a dummy qstring, the old body is simply discarded. + * See notes above. + */ +extern qstring qs_append(qstring qs, const char* src) +{ + return qs_append_n(qs, src, (src != NULL) ? strlen(src) : 0) ; +} ; + +/*------------------------------------------------------------------------------ + * Append leading 'n' bytes of given string to a qstring. + * + * If n == 0, src may be NULL + * If n > 0, src string MUST be at least 'n' bytes long. + * + * See notes above. */ extern qstring -qs_copy(qstring dst, qstring src) +qs_append_n(qstring qs, const char* src, usize n) { - usize n ; + qs = qs_extend(qs, n) ; /* allocate, copy any alias, extend body, + set new length, etc */ - if (src == NULL) - { - if (dst == NULL) - return dst ; + if (n != 0) + memcpy(qs_ep_char_nn(qs) - n, src, n) ; - n = 0 ; - dst->cp = 0 ; - } - else - { - if (dst == NULL) - dst = qs_new() ; + return qs ; +} ; - n = src->len ; - dst->cp = src->cp ; - } ; +/*------------------------------------------------------------------------------ + * Append given qstring to a qstring. + * + * See notes above. + */ +extern qstring +qs_append_qs(qstring qs, qstring src) +{ + return qs_append_n(qs, qs_body(src), qs_len(src)) ; +} ; - qs_set_len(dst, n) ; /* TODO: Copies alias !! */ +/*------------------------------------------------------------------------------ + * Append given elstring to a qstring. + * + * See notes above. + */ +extern qstring +qs_append_els(qstring qs, elstring src) +{ + return qs_append_n(qs, els_body(src), els_len(src)) ; +} ; - if (n > 0) - memcpy(dst->s.body, src->s.body, n) ; - *(dst->s.char_body + n) = '\0' ; - return dst ; -} ; -/*------------------------------------------------------------------------------ - * Construct a qstring which is an alias for the given string. - * - * Allocates a qstring if required. - * - * Given string must be '\0' terminated. +/*============================================================================== + * Setting of alias. * * Does NOT copy the given string, but sets the qstring to be a pointer to it. + * This means that: * * NB: it is the caller's responsibility to ensure that the original string * stays put for however long the qstring is an alias for it. @@ -361,150 +428,147 @@ qs_copy(qstring dst, qstring src) * use, the qstring must not be released, so that the alias is not * released. * - * Returns: the address of the qstring. + * Preserves any existing qstring body. + * + * Returns: address of the qstring (allocated if required). + */ + +/*------------------------------------------------------------------------------ + * Set qstring to be an alias for the given string. + * + * Treats src == NULL as an empty string. Otherwise src must be a '\0' + * terminated string. + * + * Does not count the '\0' terminator. + * + * See notes above. */ extern qstring qs_set_alias(qstring qs, const char* src) +{ + return qs_set_alias_n(qs, src, (src != NULL) ? strlen(src) : 0) ; +} ; + +/*------------------------------------------------------------------------------ + * Set qstring to be an alias for the leading 'n' bytes of given string. + * + * If n == 0, src may be NULL + * If n > 0, src string MUST be at least 'n' bytes long. + * + * See notes above. + */ +extern qstring +qs_set_alias_n(qstring qs, const char* src, usize n) { if (qs == NULL) - qs = qs_init_new(NULL, 0) ; + qs = qs_new() ; /* Make the alias. Note that any existing b_body and b_size are preserved, * so that any current body can be reused at a later date. */ - qs->s.const_body = (src != NULL) ? src : "" ; - qs->len = strlen(src) ; - qs->cp = 0 ; - qs->size = 0 ; /* <=> this is an alias ! */ - qs->terminated = true ; + qs_set_body_nn(qs, (n != 0) ? src : "") ; + qs_set_len_nn(qs, n) ; + qs_set_cp_nn(qs, 0) ; + qs->alias = true ; + qs->size = 0 ; /* => empty or alias */ return qs ; } ; /*------------------------------------------------------------------------------ - * Construct a qstring which is an alias for the 'n' given characters. - * - * Allocates a qstring if required. - * - * Given characters are assumed not to be '\0' terminated. + * Set qstring to be an alias for the given qstring. * - * Does NOT copy the given characters, but sets the qstring to be a pointer to - * them. + * If the src is not an alias, then the qstring is an alias for the body of + * src -- so must be careful not to disturb that ! * - * NB: it is the caller's responsibility to ensure that the original characters - * stays put for however long the qstring is an alias for them. + * If the src is an alias, then the qstring is another alias. * - * It is also the caller's responsibility to see that the original - * characters are discarded as required (once the alias is no longer - * required.) - * - * NB: if the qstring is changed in any way, a copy of the aliased characters - * will be made first. + * See notes above. + */ +extern qstring +qs_set_alias_qs(qstring qs, qstring src) +{ + return qs_set_alias_n(qs, qs_body(src), qs_len(src)) ; +} ; + +/*------------------------------------------------------------------------------ + * Construct a qstring which is an alias for the given elstring. * - * NB: if a pointer to the body of the qstring is taken, then while that is in - * use, the qstring must not be released, so that the alias is not - * released. + * If n == 0, src may be NULL + * If n > 0, src string MUST be at least 'n' bytes long. * - * Returns: the address of the qstring. + * See notes above. */ extern qstring -qs_set_alias_n(qstring qs, const char* src, usize len) +qs_set_alias_els(qstring qs, elstring src) { - if (qs == NULL) - qs = qs_init_new(NULL, 0) ; - - if (len == 0) - src = "" ; - else - assert(src != NULL) ; - - /* Make the alias. Note that any existing b_body and b_size are preserved, - * so that any current body can be reused at a later date. - */ - qs->s.const_body = src ; - qs->len = len ; - qs->cp = 0 ; - qs->size = 0 ; /* <=> this is an alias ! */ - qs->terminated = false ; - - return qs ; + return qs_set_alias_n(qs, els_body(src), els_len(src)) ; } ; - - - - - - - - - - - - - +/*============================================================================== + * Copying of qstring + */ /*------------------------------------------------------------------------------ - * Add 'n' to the current string length, allocating or extending the body. + * Copy one qstring to another -- allocating/extending as required. * - * Allocate qstring if required (setting qs->len = qs->cp = 0). + * If both are NULL, returns NULL. + * Otherwise if dst is NULL, creates a new qstring. * - * Returns with a body with size > 0. Allocates to 16 byte boundaries with - * space for '\0' beyond given length. + * If src is NULL it is treated as zero length, with 'cp' == 0. * - * Does NOT affect qs->cp. - * Does set the new qs->len -- qs->len += n - * Does NOT reterminate. + * If src is not an alias, a copy is made to dst. + * If src is an alias, dst becomes another alias for the same thing. * - * Returns: address of qstring + * If dst is an alias, that is discarded. * - * also: sets char** p_ep to point at the *end* of the old len. + * Copies the src 'cp' to the dst. * - * NB: will allocate a new body even if the new len == 0. - * - * NB: always copies all of any aliased string (even if the slen == 0). + * Returns: the destination qstring (allocated if required). */ extern qstring -qs_add_len(qstring qs, usize n, char** p_ep) +qs_copy(qstring dst, qstring src) { - usize slen ; - usize len ; - - len = qs_len(qs) ; - slen = len + n ; + if (src == NULL) + { + qs_clear(dst) ; /* if dst not NULL, clear it */ + return dst ; + } ; - /* Set the new length -- creating if required. - * - * Will always return with a body and no longer an alias (if was one). - */ - qs = qs_set_len(qs, slen) ; + if (src->alias) + dst = qs_set_alias_qs(dst, src) ; + else + dst = qs_set_qs(dst, src) ; - /* Set pointer to old end (len) position. */ - *p_ep = ((char*)qs_body_nn(qs)) + len ; + qs_set_cp_nn(dst, qs_cp_nn(src)) ; /* copy in the src cp. */ - return qs ; + return dst ; } ; /*============================================================================== * printf() and vprintf() type functions - */ - -/*------------------------------------------------------------------------------ - * Formatted print to qstring -- cf printf() * - * Allocate qstring if required (setting qs->len = qs->cp = 0). + * Allocate and/or extend qstring as required. * - * If OK: + * Any alias is discarded. * - * Sets qs->len to the length of the null terminated result. - * Does NOT affect qs->cp. + * Sets 'len' = length of the '\0' terminated result (less the '\0'). + * Sets 'cp' = 0 * - * If fails: + * If fails and qs != NULL: * - * Sets qs->len = qs->cp = 0 and terminates to zero length. + * Sets 'len' = 0 + * Sets 'cp' = 0 + * But retains any existing body. * * Returns: address of qstring if OK - * NULL if failed (unlikely though that is) -- qstring set empty. + * NULL if failed (unlikely though that is) + */ + +/*------------------------------------------------------------------------------ + * Formatted print to qstring -- cf printf() + * + * See notes above. */ extern qstring qs_printf(qstring qs, const char* format, ...) @@ -521,19 +585,7 @@ qs_printf(qstring qs, const char* format, ...) /*------------------------------------------------------------------------------ * Formatted print to qstring -- cf vprintf() * - * Allocate qstring if required (setting qs->len = qs->cp = 0). - * - * If OK: - * - * Sets qs->len to the length of the null terminated result. - * Does NOT affect qs->cp. - * - * If fails: - * - * Sets qs->len = qs->cp = 0 and terminates to zero length. - * - * Returns: address of qstring if OK - * NULL if failed (unlikely though that is) + * See notes above. */ extern qstring qs_vprintf(qstring qs, const char *format, va_list args) @@ -542,11 +594,12 @@ qs_vprintf(qstring qs, const char *format, va_list args) int slen ; qstring qqs ; - qqs = qs ; + qqs = qs ; /* NULL => need to make qs */ if (qs == NULL) - qs = qs_new() ; /* sets size == 0 */ + qqs = qs_new() ; /* Sets size, cp & len = 0 */ else - qs_set_len_nn(qs, 0) ; /* Forget current contents */ + qs_clear(qqs) ; /* Sets cp & len = 0, discard any alias, but + keep existing body */ while (1) { @@ -558,25 +611,24 @@ qs_vprintf(qstring qs, const char *format, va_list args) * the result is still the length required. */ va_copy(ac, args); - slen = vsnprintf (qs_body_nn(qs), qs->size, format, ac) ; + slen = vsnprintf(qs_body_nn(qqs), qqs->size, format, ac) ; va_end(ac); if (slen < 0) - break ; /* Quit if failed */ + break ; /* failed */ - if ((usize)slen < qs->size) + if ((usize)slen < qqs->size) { - qs_set_len_nn(qs, slen) ; - return qs ; /* Exit if succeeded */ + qs_set_len_nn(qqs, slen) ; + return qqs ; /* succeeded */ } ; - qs_make_to_size(qs, slen) ; /* Extend body to required len */ + qs_make_to_size(qqs, slen, 0) ; /* need space for slen */ } ; - if (qqs == NULL) - qs_reset(qs, free_it) ; /* discard what was allocated */ - else - qs_clear(qs) ; + /* Failed... discard anything that has been allocated. */ + if (qs == NULL) + qs_reset(qqs, free_it) ; return NULL ; } ; @@ -586,233 +638,64 @@ qs_vprintf(qstring qs, const char *format, va_list args) */ /*------------------------------------------------------------------------------ - * Compare significant parts of two qstrings. + * Replace 'r' bytes at 'cp' by 'n' bytes -- extending if required. * - * By significant, mean excluding leading/trailing isspace() and treating - * multiple isspace() as single isspace(). + * May increase or decrease 'len'. but does not affect 'cp'. * - * Compares the 'len' portions of the strings. + * Returns: number of bytes beyond 'cp' that now exist. * - * If either is NULL, it is deemed to be an empty string. + * qstring MUST NOT be NULL * - * Returns: -1 => a < b - * 0 => a == b - * +1 => a > b - */ -extern int -qs_cmp_sig(qstring a, qstring b) -{ - const unsigned char* p_a ; - const unsigned char* e_a ; - const unsigned char* p_b ; - const unsigned char* e_b ; - - /* Set up pointers and dispense with leading and trailing isspace() - * - * Dummy up if NULL - */ - if (a != NULL) - { - p_a = a->s.uchar_body ; - e_a = p_a + a->len ; - - while ((p_a < e_a) && isspace(*p_a)) - ++p_a ; - while ((p_a < e_a) && isspace(*(e_a - 1))) - --e_a ; - } - else - { - p_a = NULL ; - e_a = NULL ; - } - - if (b != NULL) - { - p_b = b->s.uchar_body ; - e_b = p_b + b->len ; - - while ((p_b < e_b) && isspace(*p_b)) - ++p_b ; - while ((p_b < e_b) && isspace(*(e_b - 1))) - --e_b ; - } - else - { - p_b = NULL ; - e_b = NULL ; - } ; - - /* Now set about finding the first difference */ - while ((p_a != e_a) && (p_b != e_b)) - { - if (isspace(*p_a) && isspace(*p_b)) - { - do { ++p_a ; } while (isspace(*p_a)) ; - do { ++p_b ; } while (isspace(*p_b)) ; - } ; - - if (*p_a != *p_b) - return (*p_a < *p_b) ? -1 : +1 ; - - ++p_a ; - ++p_b ; - } ; - - /* No difference before ran out of one or both */ - if (p_a != e_a) - return +1 ; - else if (p_b != e_b) - return -1 ; - else - return 0 ; -} ; - -/*------------------------------------------------------------------------------ - * Insert 'n' bytes at 'cp' -- moves anything cp..len up. - * - * Increases 'len'. but does not affect 'cp'. - * - * Returns: number of bytes beyond 'cp' that were moved before insert. - * - * NB: qstring MUST NOT be NULL - * - * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce - * one or more undefined bytes. - * - * NB: the string is NOT re-terminated. - * - * NB: if this is a aliased qstring, a copy is made of the original body. - */ -extern usize -qs_insert(qstring qs, const void* src, usize n) -{ - usize after ; - usize len ; - char* p ; - - qs->terminated = false ; /* NB: require qs != NULL ! */ - - len = qs_len_nn(qs) ; - if (len < qs->cp) /* make len = max(len, cp) ! */ - len = qs->cp ; - - after = len - qs->cp ; - - qs_set_len(qs, len + n) ; /* set len and ensure have space - Makes copy of any aliased string. */ - p = qs_cp_char(qs) ; - if (after > 0) - memmove (p + n, p, after) ; - - if (n > 0) - memmove(p, src, n) ; - - return after ; -} ; - -/*------------------------------------------------------------------------------ - * Replace 'n' bytes at 'cp' -- extending if required. - * - * May increase 'len'. but does not affect 'cp'. + * If 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce + * one or more undefined bytes. * - * NB: qstring MUST NOT be NULL - * - * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce - * one or more undefined bytes. - * - * NB: the string is NOT re-terminated. - * - * NB: if this is a aliased qstring, a copy is made of the original body. - */ -extern void -qs_replace(qstring qs, const void* src, usize n) -{ - usize len ; - - qs->terminated = false ; /* NB: require qs != NULL ! */ - - len = qs_len_nn(qs) ; - if (len < (qs->cp + n)) /* make len = max(len, cp + n) */ - len = qs->cp + n ; - - qs_set_len(qs, len) ; /* set len and ensure have space. - Makes copy of any aliased string. */ - - if (n > 0) - memmove(qs_cp_char(qs), src, n) ; -} ; - -/*------------------------------------------------------------------------------ - * Remove 'n' bytes at 'cp' -- extending if required. - * - * May change 'len'. but does not affect 'cp'. - * - * Returns: number of bytes beyond 'cp' that were moved before insert. - * - * NB: qstring MUST NOT be NULL - * - * NB: if 'cp' > 'len', then sets 'len' = 'cp' first -- which will introduce - * one or more undefined bytes. - * - * NB: the string is NOT re-terminated. - * - * NB: if this is a aliased qstring, a copy is made of the original body. + * If this is a aliased qstring, a copy is made, so is no longer an alias. */ extern usize -qs_delete(qstring qs, usize n) +qs_replace(qstring qs, usize r, const void* src, usize n) { - usize after ; - char* p ; - usize len ; + usize cp, len, nlen, after ; + const char* ap ; + char* np ; - qs->terminated = false ; /* NB: require qs != NULL ! */ + len = qs_len_nn(qs) ; + cp = qs_cp_nn(qs) ; - len = qs_len_nn(qs) ; - - /* If deleting to or beyond 'len', force len to cp */ - if ((qs->cp + n) >= len) - { - len = qs->cp ; - qs_set_len_nn(qs, len) ; /* truncate now, so that if this is an - aliased string, only copy what is - going to be kept. */ - after = 0 ; /* nothing to move */ - } + if ((cp + r) >= len) + /* Replacing up to or beyond the end of the string */ + after = 0 ; else - after = len - (qs->cp + n) ; + /* Replacing section short of the end of the string */ + after = len - (cp + r) ; - qs_set_len(qs, len) ; /* set len and ensure have space. - Makes copy of any aliased string. */ + nlen = cp + n + after ; + if (nlen >= qs->b_size) + qs_new_body(qs, nlen, qs->alias) ; /* keeping any alias */ + ap = np = qs->b_body ; + if (qs->alias) + { + ap = qs_body_nn(qs) ; /* copy from the alias */ - /* Watch out for "dummy" */ - if (qs->size == 0) - qs_make_to_size(qs, len) ; + uint before = cp ; + if (before > len) + before = len ; - /* If deleting up to or beyond len, then simply set len == cp - * note that this may reduce or increase len ! - */ - if ((qs->cp + n) >= len) - { - if (qs->cp < len) - qs_set_len_nn(qs, qs->cp) ; /* discard stuff after qs->cp */ + if (before > 0) + memmove(np, ap, before) ; - qs_set_len(qs, qs->cp) ; /* set len */ - return 0 ; /* nothing after */ - } + qs_set_real_body_nn(qs) ; + } ; - /* There is at least one byte after cp (so body must exist) */ - after = len - (qs->cp + n) ; + if (after > 0) /* move the after part before inserting */ + memmove(np + cp + n, ap + cp + r, after) ; - if (n > 0) - { - p = qs_cp_char(qs) ; - memmove (p, p + n, after) ; + if (n > 0) /* insert */ + memmove(np + cp, src, n) ; - qs_set_len_nn(qs, len - n) ; - } ; + /* Set new 'len' */ + qs_set_len_nn(qs, nlen) ; - return after ; + return n + after ; } ; diff --git a/lib/qstring.h b/lib/qstring.h index 5b1d4932..48124a2d 100644 --- a/lib/qstring.h +++ b/lib/qstring.h @@ -38,8 +38,21 @@ * The caller does, however, have to explicitly release the contents of a * qstring when it is done with. * - * - * + * The mechanics of a qstring work on a length/pointer basis. The body of a + * qstring is not guaranteed to be '\0' terminated, but when memory is + * allocated provision is always made for a terminator beyond the 'len'. + * The qs_string() function will add a '\0' at the 'len' position. + * + * The body of a qstring is allocated and extended automatically as the length + * or the size is set. The address of the body can, therefore, change -- so + * should be careful when handling pointers to the body. The qstring handling + * tends to hold on to any body that has been allocated -- only qs_reset() will + * free the body. + * + * The qstring supports an "alias" state. A qstring can be set to be an + * "alias" for another length/pointer string, and at some later date that + * can be copied to the qstring body -- to be changed or for any other + * reason. */ struct qstring { @@ -47,10 +60,12 @@ struct qstring usize size ; /* of the els body */ - usize cp ; + ulen cp ; - usize b_size ; void* b_body ; + usize b_size ; + + bool alias ; } ; typedef struct qstring qstring_t[1] ; @@ -68,111 +83,295 @@ enum /*------------------------------------------------------------------------------ * Access functions for body of qstring -- to take care of casting pointers * - * NB: if the body has not yet been allocated, these functions will return - * NULL or NULL + the offset. + * There are generally two versions of each function: + * + * xxxx_nn -- where the argument may NOT be NULL + * + * xxxx -- where a NULL or zero value is returned if the argument + * is NULL + * + * NB: the various 'cp', 'len' and 'at' functions do not guarantee that these + * positions exist within the current body of the string. + * */ + +Inline elstring qs_els(qstring qs) ; +Inline elstring qs_els_nn(qstring qs) ; +Inline void qs_els_copy(elstring els, qstring qs) ; +Inline void qs_els_copy_nn(elstring els, qstring qs) ; + +Inline char* qs_char(qstring qs) ; +Inline char* qs_char_nn(qstring qs) ; + +Inline char* qs_cp_char(qstring qs) ; +Inline char* qs_cp_char_nn(qstring qs) ; + +Inline char* qs_ep_char(qstring qs) ; +Inline char* qs_ep_char_nn(qstring qs) ; + +Inline char* qs_char_at(qstring qs, usize off) ; +Inline char* qs_char_at_nn(qstring qs, usize off) ; + +Inline void* qs_body(qstring qs) ; +Inline void* qs_body_nn(qstring qs) ; +Inline void qs_set_body_nn(qstring qs, const void* body) ; + +Inline ulen qs_len(qstring qs) ; +Inline ulen qs_len_nn(qstring qs) ; +Inline void qs_set_len_nn(qstring qs, ulen len) ; + +Inline ulen qs_cp(qstring qs) ; +Inline ulen qs_cp_nn(qstring qs) ; +Inline void qs_set_cp_nn(qstring qs, usize cp) ; +Inline void qs_move_cp_nn(qstring qs, int delta) ; + +Inline ulen qs_after_cp(qstring qs) ; +Inline ulen qs_after_cp_nn(qstring qs) ; + +Inline void qs_pp(pp p, qstring qs) ; +Inline void qs_pp_nn(pp p, qstring qs) ; + +Inline void qs_cpp(cpp p, qstring qs) ; +Inline void qs_cpp_nn(cpp p, qstring qs) ; + +/*------------------------------------------------------------------------------ + * Functions to fetch the elstring body of the qstring. + */ + +/* Pointer to elstring of qstring -- returns NULL if qstring is NULL */ Inline elstring -qs_els_nn(qstring qs) +qs_els(qstring qs) { - return qs->els ; + return (qs != NULL) ? qs->els : NULL ; } ; +/* Pointer to elstring of qstring (not NULL) */ Inline elstring -qs_els(qstring qs) +qs_els_nn(qstring qs) { return qs->els ; } ; -Inline void* -qs_body_nn(qstring qs) /* pointer to body of qstring (not NULL) */ +/* Copy elstring of qstring to another elstring (elstring not NULL) */ +Inline void +qs_els_copy(elstring els, qstring qs) { - return els_body_nn(qs->els) ; + if (qs != NULL) + qs_els_copy_nn(els, qs) ; + else + els_null(els) ; +} ; + +/* Copy elstring of qstring to another elstring (neither NULL) */ +Inline void +qs_els_copy_nn(elstring els, qstring qs) +{ + *els = *(qs->els) ; +} ; + +/*------------------------------------------------------------------------------ + * Functions to fetch pointers to or into the string body. + * + * NB: these pointers must be treated with care -- and change to the string + * may invalidate the pointer. Where the qstring is an alias, the pointer + * returned is the alias -- which could disappear ! + */ + +/* Start of qstring -- returns NULL if qstring is NULL, or body is. */ +Inline char* +qs_char(qstring qs) +{ + return qs_body(qs) ; } ; -Inline void* /* pointer to body of qstring */ +/* Start of qstring (not NULL)-- returns NULL if body is NULL. */ +Inline char* +qs_char_nn(qstring qs) +{ + return qs_body_nn(qs) ; +} ; + +/* Offset in qstring -- returns NULL if qstring is NULL + * returns *nonsense if body is NULL */ +Inline char* +qs_char_at(qstring qs, usize off) +{ + return (qs != NULL) ? qs_char_at_nn(qs, off) : NULL ; +} ; + +/* Offset in qstring (not NULL) -- returns *nonsense if body is NULL */ +Inline char* +qs_char_at_nn(qstring qs, usize off) +{ + return qs_char_nn(qs) + off ; +} ; + +/* 'cp' in qstring -- returns NULL if qstring is NULL + * returns *nonsense if body is NULL */ +Inline char* +qs_cp_char(qstring qs) +{ + return (qs != NULL) ? qs_cp_char_nn(qs) : NULL ; +} ; + +/* 'cp' in qstring (not NULL) -- returns *nonsense if body is NULL */ +Inline char* +qs_cp_char_nn(qstring qs) +{ + return qs_char_at_nn(qs, qs_cp_nn(qs)) ; +} ; + +/* 'len' in qstring -- returns NULL if qstring is NULL + * returns *nonsense if body is NULL */ +Inline char* +qs_ep_char(qstring qs) +{ + return (qs != NULL) ? qs_ep_char_nn(qs) : NULL ; +} ; + +/* 'len' in qstring (not NULL) -- returns *nonsense if body is NULL */ +Inline char* +qs_ep_char_nn(qstring qs) +{ + return qs_char_at_nn(qs, qs_len_nn(qs)) ; +} ; + +/* Start of qstring -- returns NULL if qstring is NULL, or body is. */ +Inline void* qs_body(qstring qs) { return (qs != NULL) ? qs_body_nn(qs) : NULL ; } ; -Inline void /* set pointer to body of qstring (not NULL) */ -qs_set_body_nn(qstring qs, const void* body) +/* Start of qstring (not NULL)-- returns NULL if body is NULL. */ +Inline void* +qs_body_nn(qstring qs) { - els_set_body_nn(qs->els, body) ; /* sets term = fase */ + return els_body_nn(qs->els) ; } ; -Inline ulen /* length of qstring (not NULL) */ -qs_len_nn(qstring qs) +/* Set new body of qstring (not NULL). + * + * Caller must ensure that 'size', 'len' & 'cp' are all valid ! */ +Inline void +qs_set_body_nn(qstring qs, const void* body) { - return els_len_nn(qs->els) ; + els_set_body_nn(qs->els, body) ; } ; -Inline ulen /* length of qstring */ +/*----------------------------------------------------------------------------*/ + +/* 'len' of qstring -- returns 0 if qstring is NULL */ +Inline ulen qs_len(qstring qs) { return (qs != NULL) ? qs_len_nn(qs) : 0 ; } ; -Inline void /* set length of qstring (not NULL) */ -qs_do_set_len_nn(qstring qs, ulen len) +/* 'len' of qstring (not NULL) */ +Inline ulen +qs_len_nn(qstring qs) { - els_set_len_nn(qs->els, len) ; /* sets term = false */ + return els_len_nn(qs->els) ; } ; -Inline ulen /* cp of qstring (not NULL) */ -qs_cp_nn(qstring qs) +/* set 'len' of qstring (not NULL) -- caller responsible for validity */ +Inline void +qs_set_len_nn(qstring qs, ulen len) { - return qs->cp ; + els_set_len_nn(qs->els, len) ; } ; -Inline ulen /* cp of qstring */ +/*----------------------------------------------------------------------------*/ + +/* 'cp' of qstring -- returns 0 if qstring is NULL */ +Inline ulen qs_cp(qstring qs) { return (qs != NULL) ? qs_cp_nn(qs) : 0 ; } ; -Inline void /* set cp of qstring (not NULL) */ -qs_do_set_cp_nn(qstring qs, ulen cp) +/* 'cp' of qstring (not NULL) */ +Inline ulen +qs_cp_nn(qstring qs) +{ + return qs->cp ; +} ; + +/* set 'cp' of qstring (not NULL) -- caller responsible for validity */ +Inline void +qs_set_cp_nn(qstring qs, usize cp) { qs->cp = cp ; } ; -Inline char* /* pointer to given offset in qstring */ -qs_char_at_nn(qstring qs, usize off) +/* move 'cp' of qstring (not NULL) -- caller responsible for validity */ +Inline void +qs_move_cp_nn(qstring qs, int delta) { - char* p ; - p = qs_body_nn(qs) ; - return (p != NULL) ? p + off : NULL ; + qs->cp += delta ; } ; -Inline char* /* pointer to given offset in qstring */ -qs_char_at(qstring qs, usize off) +/*----------------------------------------------------------------------------*/ + +/* 'len' - 'cp' of qstring (not NULL) -- zero if 'len' < 'cp' */ +Inline ulen +qs_after_cp_nn(qstring qs) { - return (qs != NULL) ? qs_char_at_nn(qs, off) : NULL ; + return (qs_len_nn(qs) > qs_cp_nn(qs)) ? qs_len_nn(qs) - qs_cp_nn(qs) : 0 ; } ; -Inline char* /* pointer to 'cp' offset in qstring */ -qs_cp_char(qstring qs) +/* 'len' - 'cp' of qstring -- zero if NULL or 'len' < 'cp' */ +Inline ulen +qs_after_cp(qstring qs) { - return (qs != NULL) ? qs_char_at_nn(qs, qs_cp_nn(qs)) : NULL ; + return (qs != NULL) ? qs_after_cp_nn(qs) : 0 ; } ; -Inline char* /* pointer to 'len' offset in qstring */ -qs_ep_char(qstring qs) +/*------------------------------------------------------------------------------ + * Functions to fetch various pointer pairs. + */ + +Inline void +qs_pp(pp p, qstring qs) +{ + if (qs != NULL) + qs_pp_nn(p, qs) ; + else + pp_null(p) ; +} ; + +Inline void +qs_pp_nn(pp p, qstring qs) { - return (qs != NULL) ? qs_char_at_nn(qs, qs_len_nn(qs)) : NULL ; + els_pp_nn(p, qs->els) ; +} ; + +Inline void +qs_cpp(cpp p, qstring qs) +{ + if (qs != NULL) + qs_cpp_nn(p, qs) ; + else + cpp_null(p) ; } ; -Inline bool /* whether qstring is known to be terminated */ -qs_term_nn(qstring qs) +Inline void +qs_cpp_nn(cpp p, qstring qs) { - return els_term_nn(qs->els) ; + els_cpp_nn(p, qs->els) ; } ; -Inline void /* set qstring is known to be terminated */ -qs_set_term_nn(qstring qs, bool how) +/*------------------------------------------------------------------------------ + * Set real body -- discarding any alias. + * + * NB: does not affect 'len' or 'cp' + */ +Inline void qs_set_real_body_nn(qstring qs) { - return els_set_term_nn(qs->els, how) ; + qs->size = qs->b_size ; + qs_set_body_nn(qs, qs->b_body) ; + qs->alias = false ; } ; /*============================================================================== @@ -180,42 +379,140 @@ qs_set_term_nn(qstring qs, bool how) */ extern qstring qs_new(void) ; +extern qstring qs_new_with_body(usize slen) ; extern qstring qs_init_new(qstring qs, usize len) ; extern qstring qs_reset(qstring qs, free_keep_b free_structure) ; -Private void qs_make_to_size(qstring qs, usize len, free_keep_b free) ; +Inline char* qs_make_string(qstring qs) ; +Inline const char* qs_string(qstring qs) ; +Inline void qs_clear(qstring qs) ; +Inline qstring qs_new_size(qstring qs, usize slen) ; +Inline qstring qs_extend(qstring qs, usize elen) ; +Inline void qs_chop(qstring qs, usize clen) ; + +Private void qs_make_to_size(qstring qs, usize len, usize alen) ; extern qstring qs_set(qstring qs, const char* src) ; extern qstring qs_set_n(qstring qs, const char* src, usize n) ; +extern qstring qs_set_qs(qstring qs, qstring src) ; +extern qstring qs_set_els(qstring qs, elstring src) ; +extern qstring qs_set_fill(qstring qs, usize len, const char* src) ; +extern qstring qs_set_fill_n(qstring qs, usize len, const char* src, + usize flen) ; extern qstring qs_append(qstring qs, const char* src) ; extern qstring qs_append_n(qstring qs, const char* src, usize n) ; - -extern qstring qs_copy(qstring dst, qstring src) ; +extern qstring qs_append_qs(qstring qs, qstring src) ; +extern qstring qs_append_els(qstring qs, elstring src) ; extern qstring qs_set_alias(qstring qs, const char* src) ; extern qstring qs_set_alias_n(qstring qs, const char* src, usize len) ; +extern qstring qs_set_alias_qs(qstring qs, qstring src) ; +extern qstring qs_set_alias_els(qstring qs, elstring src) ; + +extern qstring qs_copy(qstring dst, qstring src) ; extern qstring qs_printf(qstring qs, const char* format, ...) PRINTF_ATTRIBUTE(2, 3) ; extern qstring qs_vprintf(qstring qs, const char *format, va_list args) ; -extern usize qs_insert(qstring qs, const void* src, usize n) ; -extern void qs_replace(qstring qs, const void* src, usize n) ; -extern usize qs_delete(qstring qs, usize n) ; +extern usize qs_replace(qstring qs, usize r, const void* src, usize n) ; +Inline usize qs_insert(qstring qs, const void* src, usize n) +{ + return qs_replace(qs, 0, src, n) ; +} ; +Inline usize qs_delete(qstring qs, usize n) +{ + return qs_replace(qs, n, NULL, 0) ; +} ; -extern int qs_cmp_sig(qstring a, qstring b) ; +Inline int qs_cmp(qstring a, qstring b) ; +Inline int qs_cmp_word(qstring a, const char* w) ; +Inline int qs_cmp_sig(qstring a, qstring b) ; +Inline bool qs_equal(qstring a, qstring b) ; +Inline bool qs_substring(qstring a, qstring b) ; /*============================================================================== * The Inline functions. */ +/*------------------------------------------------------------------------------ + * Return pointer to string value -- ensure not alias and '\0' terminated. + * + * If is alias, copies that before adding '\0' terminator. + * + * Sets the '\0' terminator at the 'len' position, extending string if that + * is required. + * + * If qs == NULL or 'len' == 0 returns pointer to constant empty '\0' + * terminated string (ie ""). + * + * NB: The qstring should not be changed or reset until this pointer has been + * discarded ! + */ +Inline char* +qs_make_string(qstring qs) +{ + usize len ; + char* p ; + + if (qs == NULL) + { + len = 0 ; + qs = qs_new_with_body(len) ; + } + else + { + len = qs_len_nn(qs) ; + if (len >= qs->size) /* for alias, qs_size == 0 */ + qs_make_to_size(qs, len, len) ; /* extend and/or copy any alias */ + } ; + + p = qs_char_nn(qs) ; + *(p + len) = '\0' ; + + return p ; +} ; + +/*------------------------------------------------------------------------------ + * Return pointer to string value. + * + * If possible, writes '\0' at 'len' in order to return a terminated string. + * + * If qs == NULL or body == NULL, or 'len' == 0 returns pointer to constant + * empty '\0' terminated string (ie ""). + * + * NB: if 'len' is beyond the current 'size' of the of the qstring, then + * does NOT write '\0' (does NOT extend the string). + * + * NB: if string is an alias, this returns its address, whether it is + * terminated or not. + * + * NB: In any event, the string should not be changed or reset until this + * pointer has been discarded ! + */ +Inline const char* +qs_string(qstring qs) +{ + char* p ; + usize len ; + + if ( (qs == NULL) || ((len = qs_len_nn(qs) ) == 0) + || ((p = qs_char_nn(qs)) == NULL) ) + return "" ; + + if (len < qs->size) /* for alias, qs_size == 0 */ + *(p + len) = '\0' ; + + return p ; +} ; + /*------------------------------------------------------------------------------ * Clear contents of qstring -- preserves any qstring body, but sets len = 0. * * Does nothing if qstring is NULL * - * Sets 'cp' = 'len' = 0. + * Sets 'cp' = 'len' = 0 * * If is an alias qstring, discard the alias. * @@ -226,68 +523,64 @@ qs_clear(qstring qs) { if (qs != NULL) { - qs_do_set_len_nn(qs, 0) ; /* sets term == false */ - if (qs->size == 0) - { - qs_set_body_nn(qs, qs->b_body) ; - qs->size = qs->b_size ; - } ; - qs->cp = 0 ; + if (qs->alias) + qs_set_real_body_nn(qs) ; + qs_set_len_nn(qs, 0) ; + qs_set_cp_nn(qs, 0) ; } ; } ; /*------------------------------------------------------------------------------ - * Need space for a string of 'slen' characters (plus possible '\0'). + * Ensure have space for a string of 'slen' characters (plus possible '\0'), + * and discard any alias. * - * Allocate qstring if required (setting qs->len = qs->cp = 0). + * Allocate qstring if required (setting 'len' = 'cp' = 0). * * Returns: address of qstring -- with body that can be written upto and * including 'slen' + 1. * - * NB: has no effect on 'len' -- even if 'len' > 'slen'. + * Has no effect on 'len' -- even if 'len' > 'slen'. * - * NB: has no effect on 'cp' -- even if 'cp' > 'len' or 'cp' > 'slen'. + * Has no effect on 'cp' -- even if 'cp' > 'len' or 'cp' > 'slen'. * - * NB: if this is a aliased qstring, the alias is discarded and term = false. + * If this is a aliased qstring, the alias is discarded. */ Inline qstring -qs_need(qstring qs, usize slen) +qs_new_size(qstring qs, usize slen) { - if (qs == NULL) - qs = qs_init_new(NULL, slen) ; /* Make the qstring if required */ - else - if (slen >= qs->size) /* for alias qs->size == 0 ! */ - qs_make_to_size(qs, slen, free_it) ; + if (qs == NULL) + qs = qs_new_with_body(slen) ; + else if (slen >= qs->size) /* for alias qs->size == 0 ! */ + qs_make_to_size(qs, slen, 0) ; /* extend and/or make body */ return qs ; } ; /*------------------------------------------------------------------------------ - * Set 'len' -- allocate or extend body as required. + * Extend to 'len' + 'elen' -- allocate or extend body as required. * - * Allocate qstring if required (setting qs->len = qs->cp = 0). + * Allocate qstring if required (setting 'cp' = 0). * - * Returns: address of qstring -- with body that can be written upto and - * including 'len' + 1. + * Returns: address of qstring -- with body that can be written up to and + * including 'len' + 'elen' + 1. * - * Sets 'cp' to the (new) 'len' if 'cp' > 'len'. + * Has no effect on 'cp' -- even if 'cp' > 'len'. * - * NB: if this is a aliased qstring, a copy is made of all of the original body, - * even if that is longer than the required 'slen'. (And term = false.) + * If this is a aliased qstring, a copy is made. */ Inline qstring -qs_set_len(qstring qs, usize len) +qs_extend(qstring qs, usize elen) { if (qs == NULL) - qs = qs_init_new(NULL, len) ; /* Make the qstring if required */ + qs = qs_new_with_body(elen) ; else - if (len >= qs->size) /* for alias qs->size == 0 ! */ - qs_make_to_size(qs, len, keep_it) ; - - qs_do_set_len_nn(qs, len) ; + { + elen = qs_len_nn(qs) + elen ; + if (elen >= qs->size) /* for alias qs->size == 0 ! */ + qs_make_to_size(qs, elen, elen) ; /* extend and/or copy any alias */ + } ; - if (qs->cp > len) - qs->cp = len ; + qs_set_len_nn(qs, elen) ; return qs ; } ; @@ -299,125 +592,74 @@ qs_set_len(qstring qs, usize len) * * Does not change the 'len' if it is <= length to chop to. * - * Sets 'cp' to the (new) 'len' if 'cp' > 'len'. + * NB: has no effect on 'cp' -- even if 'cp' > (new) 'len'. * * NB: if this is a aliased qstring, then it remains an aliased string, but - * shorter and term = false (unless no change made to the length). + * shorter (unless no change made to the length). */ Inline void qs_chop(qstring qs, usize clen) { if (qs != NULL) { - usize len = qs_len_nn(qs) ; - if (len > clen) - qs_do_set_len_nn(qs, (len = clen)) ; /* sets term = false */ - if (qs->cp > len) - qs->cp = len ; + if (clen < qs_len_nn(qs)) + qs_set_len_nn(qs, clen) ; } ; } ; /*------------------------------------------------------------------------------ - * Set 'cp' -- allocate or extend body as required. - * - * Allocates the qstring, if required. - * - * Returns: address of qstring + * Compare two qstrings -- returns the usual -ve, 0, +ve cmp result. * - * NB: if there was no body, allocates a body for the string, even if 'cp' == 0. - * - * NB: if new 'cp' > 'len', extends body (or allocates one), and sets 'len' to - * 'cp'. If this is an alias qstring, a copy of the string is made. + * NULL qstring is treated as empty. */ -Inline qstring -qs_set_cp(qstring qs, usize cp) +Inline int +qs_cmp(qstring a, qstring b) { - if (qs == NULL) - qs = qs_new(qs) ; - - if (qs->size <= cp) - - - if ((qs == NULL) || (cp >(cp > qs_len_nn(qs))) - qs = qs_set_len(qs, cp) ; - qs->cp = cp ; - return qs ; + return els_cmp(qs_els(a), qs_els(b)) ; } ; /*------------------------------------------------------------------------------ - * If not "term": set '\0' at qs->len -- extending body as required. - * - * Does NOT affect qs->cp or qs->len. - * - * Returns address of body -- NULL if the qstring is NULL - * - * NB: if this is an alias, and it is not terminated, make a copy before adding - * terminating '\0'. + * Compare qstrings to given word -- see els_cmp_word */ -Inline void -qs_terminate_nn(qstring qs) +Inline int +qs_cmp_word(qstring a, const char* w) { - if (!qs_term_nn(qs)) - { - usize len ; - - len = qs_len_nn(qs) ; - - if (len >= qs->size) /* alias has size == 0 */ - qs_make_to_size(qs, len) ; /* make sure can insert '\0' */ - - *qs_char_at_nn(qs, len) = '\0' ; - - qs_set_term_nn(qs, true) ; - } ; + return els_cmp_word(qs_els(a), w) ; } ; /*------------------------------------------------------------------------------ - * Return pointer to '\0' terminated string value. + * Compare significant parts of two qstrings -- returns the usual -ve, 0, +ve + * cmp result. * - * If qs is NULL or body is NULL returns pointer to constant empty '\0' - * terminated string. + * By significant, mean excluding leading/trailing isspace() and treating + * multiple isspace() as single isspace(). * - * If string is terminated, return address of string, which may be an alias - * address. - * - * Otherwise, makes sure that there is a '\0' at the qs->len position, making - * a copy of any aliased string if required, and returns address of result. - * - * NB: value returned may be the address of the qstring body, or the address of - * an aliased string. In any event, the string should not be changed or - * reset until this pointer has been discarded ! + * NULL qstring is treated as empty. */ -Inline const char* -qs_string(qstring qs) +Inline int +qs_cmp_sig(qstring a, qstring b) { - if ((qs == NULL) || (qs_len_nn(qs) == 0)) - return "" ; - - qs_terminate_nn(qs) ; + return els_cmp_sig(qs_els(a), qs_els(b)) ; +} ; - return qs_body_nn(qs) ; +/*------------------------------------------------------------------------------ + * Are two qstrings equal ? -- returns true if they are. + */ +Inline bool +qs_equal(qstring a, qstring b) +{ + return els_equal(qs_els(a), qs_els(b)) ; } ; /*------------------------------------------------------------------------------ - * Assuming the given address is within the size of the given qstring, - * set qs->len and insert '\0' terminator there. - * - * Does NOT affect qs->cp. - * - * NB: must NOT be a NULL qs. + * Is 'b' a leading substring of 'a' ? -- returns true if it is. * - * NB: must NOT be an aliased qstring. - * - * NB: must NOT have a NULL body. + * If 'b' is empty it is always a leading substring. */ -Inline void -qs_term_here(qstring qs, char* here) +Inline bool +qs_substring(qstring a, qstring b) { - assert((here >= qs->s.char_body) && (here < (qs->s.char_body + qs->size))) ; - - qs->len = (here - qs->s.char_body) ; - *here = '\0' ; + return els_substring(qs_els(a), qs_els(b)) ; } ; #endif /* _ZEBRA_QSTRING_H */ diff --git a/lib/qtime.c b/lib/qtime.c index 3ec34a72..92e67e27 100644 --- a/lib/qtime.c +++ b/lib/qtime.c @@ -192,3 +192,51 @@ qt_craft_monotonic(void) { return (monotonic * times_scale_q) + ((monotonic * times_scale_r) / times_clk_tcks) ; } ; + +/*============================================================================== + * A simple minded random number generator. + * + * Designed to be reasonably unpredictable... particularly the ms bits ! + */ + +static inline uint32_t +qt_rand(uint64_t q, uint64_t s) +{ + /* Takes q ^ s and reduces to 32 bits by xoring ms and ls halves + * then uses Knuth recommended linear congruent to randomise that, so that + * most of the original bits affect the result. + * + * Note that linear congruent tends to be "more random" in the ms bits. + */ + q ^= s ; + q = (q ^ (q >> 32)) & 0xFFFFFFFF ; + return ((q * 2650845021) + 5) & 0xFFFFFFFF ; +} ; + +extern uint32_t +qt_random(uint32_t seed) +{ + uint32_t x, y ; + uint64_t t ; + + t = qt_get_realtime() ; + + /* Set x by munging the time, the address of x, the current contents of x, + * and the "seed". (Munge the seed a bit for good measure.) + */ + x = qt_rand(t ^ (uint64_t)x ^ (uint64_t)&x, seed ^ 3141592653) ; + /* munge the address and the contents with the seed */ + + /* Set y by munging the time, the address of y, the current contents of y, + * and the "seed". (Munge the seed a bit for good measure.) + */ + y = qt_rand(t ^ (uint64_t)y ^ (uint64_t)&y, seed ^ 3562951413) ; + /* munge the current real time with the seed */ + + /* Return x and y munged together. + * + * Note that we swap the halves of y before munging, in order to spread + * the "more random" part of y down to the ls end of the result. + */ + return x ^ ((y >> 16) & 0xFFFF) ^ ((y & 0xFFFF) << 16) ; +} ; diff --git a/lib/qtime.h b/lib/qtime.h index 38e9ac1a..94d468a8 100644 --- a/lib/qtime.h +++ b/lib/qtime.h @@ -23,14 +23,11 @@ #define _ZEBRA_QTIME_H #include "misc.h" -#include + #include #include #include -#include "zassert.h" -#include "config.h" - /*============================================================================== * qtime_t -- signed 64-bit integer. * @@ -64,17 +61,13 @@ typedef qtime_t qtime_tod_t ; /* qtime_t value, timeofday time-base... */ /* Note that the time to convert may be a float. */ #define QTIME(s) ((qtime_t)((s) * (qtime_t)QTIME_SECOND)) -Inline qtime_t -timespec2qtime(struct timespec* p_ts) ; - -Inline qtime_t -timeval2qtime(struct timeval* p_tv) ; - -Inline struct timespec* -qtime2timespec(struct timespec* p_ts, qtime_t qt) ; - -Inline struct timeval* -qtime2timeval(struct timeval* p_tv, qtime_t qt) ; +/*============================================================================== + * Conversion functions + */ +Inline qtime_t timespec2qtime(struct timespec* p_ts) ; +Inline qtime_t timeval2qtime(struct timeval* p_tv) ; +Inline struct timespec* qtime2timespec(struct timespec* p_ts, qtime_t qt) ; +Inline struct timeval* qtime2timeval(struct timeval* p_tv, qtime_t qt) ; /*============================================================================== * Clocks. @@ -93,44 +86,37 @@ qtime2timeval(struct timeval* p_tv, qtime_t qt) ; * a manufactured equivalent using times() -- see qt_craft_monotonic(). */ -Inline qtime_real_t -qt_get_realtime(void) ; /* clock_gettime(CLOCK_REALTIME, ...) */ +Inline qtime_real_t qt_get_realtime(void) ; + /* clock_gettime(CLOCK_REALTIME, ...) */ +Inline qtime_mono_t qt_add_realtime(qtime_t interval) ; + /* qt_get_realtime() + interval */ -Inline qtime_mono_t -qt_add_realtime(qtime_t interval) ; /* qt_get_realtime() + interval */ - -Inline qtime_mono_t -qt_get_monotonic(void) ; /* clock_gettime(CLOCK_MONOTONIC, ...) */ +Inline qtime_mono_t qt_get_monotonic(void) ; + /* clock_gettime(CLOCK_MONOTONIC, ...) */ /* OR equivalent using times() */ -Inline qtime_mono_t -qt_add_monotonic(qtime_t interval) ; /* qt_get_monotonic() + interval */ - -Inline qtime_mono_t /* monotonic time from CLOCK_REALTIME */ -qt_realtime2monotonic(qtime_real_t realtime) ; -Inline qtime_real_t /* CLOCK_REALTIME from monotonic time */ -qt_monotonic2realtime(qtime_mono_t monotonic) ; - -/* Function to manufacture a monotonic clock. */ -extern qtime_mono_t -qt_craft_monotonic(void) ; +Inline qtime_mono_t qt_add_monotonic(qtime_t interval) ; + /* qt_get_monotonic() + interval */ /* These are provided just in case gettimeofday() != CLOCK_REALTIME */ -Inline qtime_tod_t -qt_get_timeofday(void) ; /* gettimeofday(&tv, NULL) */ +Inline qtime_tod_t qt_get_timeofday(void) ; + /* gettimeofday(&tv, NULL) */ +Inline qtime_tod_t qt_add_timeofday(qtime_t interval) ; + /* qt_get_timeofday() + interval */ -Inline qtime_tod_t -qt_add_timeofday(qtime_t interval) ; /* qt_get_timeofday() + interval */ - -Inline qtime_mono_t /* monotonic time from timeofday */ -qt_timeofday2monotonic(qtime_tod_t timeofday) ; -Inline qtime_tod_t /* timeofday from monotonic time */ -qt_monotonic2timeofday(qtime_mono_t monotonic) ; +/*============================================================================== + * Primitive random number generation. + * + * Uses time and other stuff to produce something which is not particularly + * predictable. + */ +extern uint32_t qt_random(uint32_t seed) ; /*============================================================================== * Inline conversion functions */ -/* Convert timespec to qtime_t +/*------------------------------------------------------------------------------ + * Convert timespec to qtime_t * * Returns qtime_t value. */ @@ -141,7 +127,8 @@ timespec2qtime(struct timespec* p_ts) confirm(QTIME_SECOND == TIMESPEC_SECOND) ; } ; -/* Convert timeval to qtime_t +/*------------------------------------------------------------------------------ + * Convert timeval to qtime_t * * Returns qtime_t value. */ @@ -152,7 +139,8 @@ timeval2qtime(struct timeval* p_tv) confirm(QTIME_SECOND == TIMEVAL_SECOND * 1000) ; } ; -/* Convert qtime_t to timespec +/*------------------------------------------------------------------------------ + * Convert qtime_t to timespec * * Takes address of struct timespec and returns that address. */ @@ -169,7 +157,8 @@ qtime2timespec(struct timespec* p_ts, qtime_t qt) return p_ts ; } ; -/* Convert timespec to qtime_t +/*------------------------------------------------------------------------------ + * Convert timespec to qtime_t * * Takes address of struct timespec and returns that address. */ @@ -190,7 +179,11 @@ qtime2timeval(struct timeval* p_tv, qtime_t qt) * Inline Clock Functions. */ -/* Read given clock & return a qtime_t value. +/* Function to manufacture a monotonic clock. */ +Private qtime_mono_t qt_craft_monotonic(void) ; + +/*------------------------------------------------------------------------------ + * Read given clock & return a qtime_t value. * * While possibility of error is essentially theoretical, must treat it as a * FATAL error -- cannot continue with broken time value ! @@ -207,7 +200,8 @@ qt_clock_gettime(clockid_t clock_id) return timespec2qtime(&ts) ; } ; -/* clock_gettime(CLOCK_REALTIME, ...) -- returning qtime_t value +/*------------------------------------------------------------------------------ + * clock_gettime(CLOCK_REALTIME, ...) -- returning qtime_t value * * While possibility of error is essentially theoretical, must treat it as a * FATAL error -- cannot continue with broken time value ! @@ -218,7 +212,8 @@ qt_get_realtime(void) return qt_clock_gettime(CLOCK_REALTIME) ; } ; -/* qt_get_realtime() + interval +/*------------------------------------------------------------------------------ + * qt_get_realtime() + interval */ Inline qtime_real_t qt_add_realtime(qtime_t interval) @@ -226,7 +221,8 @@ qt_add_realtime(qtime_t interval) return qt_get_realtime() + interval; } ; -/* clock_gettime(CLOCK_MONOTONIC, ...) OR qt_craft_monotonic() +/*------------------------------------------------------------------------------ + * clock_gettime(CLOCK_MONOTONIC, ...) OR qt_craft_monotonic() * -- returning qtime_t value * * While possibility of error is essentially theoretical, must treat it as a @@ -242,7 +238,8 @@ qt_get_monotonic(void) #endif } ; -/* qt_get_monotonic() + interval +/*------------------------------------------------------------------------------ + * qt_get_monotonic() + interval */ Inline qtime_mono_t qt_add_monotonic(qtime_t interval) @@ -250,7 +247,8 @@ qt_add_monotonic(qtime_t interval) return qt_get_monotonic() + interval; } ; -/* gettimeofday(&tv, NULL) -- returning qtime_t value +/*------------------------------------------------------------------------------ + * gettimeofday(&tv, NULL) -- returning qtime_t value */ Inline qtime_tod_t qt_get_timeofday(void) @@ -260,7 +258,8 @@ qt_get_timeofday(void) return timeval2qtime(&tv) ; } -/* qt_get_timeofday() + interval +/*------------------------------------------------------------------------------ + * qt_get_timeofday() + interval */ Inline qtime_tod_t qt_add_timeofday(qtime_t interval) @@ -268,36 +267,4 @@ qt_add_timeofday(qtime_t interval) return qt_get_timeofday() + interval; } ; -/*============================================================================== - * Conversion between realtime/timeofday and monotonic - */ - -/* Convert a CLOCK_REALTIME time to our local monotonic time. */ -Inline qtime_mono_t -qt_realtime2monotonic(qtime_real_t realtime) -{ - return qt_get_monotonic() + (realtime - qt_get_realtime()) ; -} ; - -/* Convert a local monotonic time to CLOCK_REALTIME time. */ -Inline qtime_real_t -qt_monotonic2realtime(qtime_mono_t monotonic) -{ - return qt_get_realtime() + (monotonic - qt_get_monotonic()) ; -} ; - -/* Convert a gettimeofday() time to our local monotonic time. */ -Inline qtime_mono_t -qt_timeofday2monotonic(qtime_tod_t timeofday) -{ - return qt_get_monotonic() + (timeofday - qt_get_timeofday()) ; -} ; - -/* Convert a local monotonic time to gettimeofday() time. */ -Inline qtime_tod_t -qt_monotonic2timeofday(qtime_mono_t monotonic) -{ - return qt_get_timeofday() + (monotonic - qt_get_monotonic()) ; -} ; - #endif /* _ZEBRA_QTIME_H */ diff --git a/lib/qtimers.c b/lib/qtimers.c index 5c0f1518..6ccb7f7d 100644 --- a/lib/qtimers.c +++ b/lib/qtimers.c @@ -27,14 +27,6 @@ #include "memory.h" #include "heap.h" -enum { qdebug = -#ifdef QDEBUG - 1 -#else - 0 -#endif -}; - /*============================================================================== * Quagga Timers -- qtimer_xxxx * @@ -170,7 +162,7 @@ qtimer_pile_dispatch_next(qtimer_pile qtp, qtime_mono_t upto) { qtimer qtr ; - if (qdebug) + if (qtimers_debug) qtimer_pile_verify(qtp) ; qtr = heap_top_item(&qtp->timers) ; @@ -343,7 +335,7 @@ qtimer_set(qtimer qtr, qtime_mono_t when, qtimer_action* action) qtp = qtr->pile ; assert(qtp != NULL) ; - if (qdebug) + if (qtimers_debug) qtimer_pile_verify(qtp) ; qtr->time = when ; @@ -371,7 +363,7 @@ qtimer_set(qtimer qtr, qtime_mono_t when, qtimer_action* action) else assert(qtr->action != NULL) ; - if (qdebug) + if (qtimers_debug) qtimer_pile_verify(qtp) ; } ; @@ -387,7 +379,7 @@ qtimer_unset(qtimer qtr) assert(qtp != NULL) ; - if (qdebug) + if (qtimers_debug) qtimer_pile_verify(qtp) ; if (qtr->active) @@ -397,7 +389,7 @@ qtimer_unset(qtimer qtr) heap_delete_item(&qtp->timers, qtr) ; - if (qdebug) + if (qtimers_debug) qtimer_pile_verify(qtp) ; qtr->active = false ; diff --git a/lib/qtimers.h b/lib/qtimers.h index 8534a789..2af81819 100644 --- a/lib/qtimers.h +++ b/lib/qtimers.h @@ -35,6 +35,22 @@ * each with an action to be executed when the timer expires. */ +#ifdef QTIMERS_DEBUG /* Can be forced from outside */ +# if QTIMERS_DEBUG +# define QTIMERS_DEBUG 1 /* Force 1 or 0 */ +#else +# define QTIMERS_DEBUG 0 +# endif +#else +# ifdef QDEBUG +# define QTIMERS_DEBUG 1 /* Follow QDEBUG */ +#else +# define QTIMERS_DEBUG 0 +# endif +#endif + +enum { qtimers_debug = QTIMERS_DEBUG } ; + /*============================================================================== * Data Structures. */ diff --git a/lib/routemap.c b/lib/routemap.c index b452530d..d716e0b5 100644 --- a/lib/routemap.c +++ b/lib/routemap.c @@ -959,7 +959,7 @@ DEFUN (route_map, index = route_map_index_get (map, permit, seq); vty->index = index; - vty_set_node(vty, RMAP_NODE) ; + vty->node = RMAP_NODE ; return CMD_SUCCESS; } diff --git a/lib/symtab.h b/lib/symtab.h index 4e33390e..008b7853 100644 --- a/lib/symtab.h +++ b/lib/symtab.h @@ -168,7 +168,7 @@ extern void* symbol_set_value(symbol sym, void* new_value) ; Inline void* symbol_unset_value(symbol sym) { - symbol_set_value(sym, NULL) ; + return symbol_set_value(sym, NULL) ; } ; extern void symbol_ref_walk_start(symbol sym, symbol_ref walk) ; diff --git a/lib/thread.h b/lib/thread.h index fa021486..6f74876d 100644 --- a/lib/thread.h +++ b/lib/thread.h @@ -206,7 +206,7 @@ extern int thread_should_yield (struct thread *); /* Internal libzebra exports */ extern void thread_getrusage (RUSAGE_T *); -extern struct cmd_element show_thread_cpu_cmd; +extern struct cmd_command show_thread_cpu_cmd; /* replacements for the system gettimeofday(), clock_gettime() and * time() functions, providing support for non-decrementing clock on diff --git a/lib/uty.h b/lib/uty.h deleted file mode 100644 index cc759673..00000000 --- a/lib/uty.h +++ /dev/null @@ -1,232 +0,0 @@ -/* VTY internal stuff -- header - * Copyright (C) 1997 Kunihiro Ishiguro - * - * Copyright (C) 2009 Chris Hall (GMCH), Highwayman - * - * This file is part of GNU Zebra. - * - * GNU Zebra is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published - * by the Free Software Foundation; either version 2, or (at your - * option) any later version. - * - * GNU Zebra is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GNU Zebra; see the file COPYING. If not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#ifndef _ZEBRA_UTY_H -#define _ZEBRA_UTY_H - -#include "misc.h" -#include "vargs.h" - -#include "qpthreads.h" -#include "qpnexus.h" -#include "thread.h" -#include "list_util.h" -#include "vty.h" -#include "vty_io_basic.h" -#include "node_type.h" - -/*============================================================================== - * This is stuff which is used by the close family of: - * - * vty - * command - * command_queue - * log - * - * and which is not for use elsewhere. - * - * There are also: - * - * vty_io - * vty_cli - * - * Which are the "immediate family" of vty: - * - * * *nothing* in their ".h" is for use anywhere except the immediate family. - * - * * things for use within the rest of the family are published here. - */ - -/*============================================================================== - * Variables in vty.c -- used in any of the family - */ -extern vty_io vio_list_base ; -extern vty_io vio_monitors_base ; -extern vty_io vio_death_watch ; - -extern struct thread_master* vty_master ; - -extern unsigned long vty_timeout_val ; - -extern bool vty_config ; - -extern bool no_password_check ; -extern const bool restricted_mode_default ; -extern bool restricted_mode ; - -char *vty_accesslist_name ; -char *vty_ipv6_accesslist_name ; - -extern qpn_nexus vty_cli_nexus ; -extern qpn_nexus vty_cmd_nexus ; - -/*============================================================================== - * To make vty qpthread safe we use a single mutex. - * - * vty and log recurse through each other, so the same mutex is used - * for both, i.e. they are treated as being part of the same monitor. - * - * A recursive mutex is used. This simplifies the calling from log to vty and - * back again. It also allows for the vty internals to call each other. - * - * There are some "uty" functions which assume the mutex is locked. - * - * vty is closely bound to the command handling -- the main vty structure - * contains the context in which commands are parsed and executed. - */ - -extern qpt_mutex_t vty_mutex ; - -#ifdef NDEBUG -# define VTY_DEBUG 0 /* NDEBUG override */ -#else -# ifndef VTY_DEBUG -# define VTY_DEBUG 1 /* Set to 1 to turn on debug checks */ -# endif -#endif - -#if VTY_DEBUG - -extern int vty_lock_count ; -extern int vty_assert_fail ; - -#endif - -Inline void -VTY_LOCK(void) /* if is qpthreads_enabled, lock vty_mutex */ -{ - qpt_mutex_lock(&vty_mutex) ; - if (VTY_DEBUG) - ++vty_lock_count ; -} ; - -Inline void -VTY_UNLOCK(void) /* if is qpthreads_enabled, unlock vty_mutex */ -{ - if (VTY_DEBUG) - --vty_lock_count ; - qpt_mutex_unlock(&vty_mutex) ; -} ; - -Inline bool /* true => is (effectively) cli thread */ -vty_is_cli_thread(void) -{ - return !qpthreads_enabled || qpt_thread_is_self(vty_cli_nexus->thread_id) ; -} ; - -/* For debug (and documentation) purposes, will VTY_ASSERT_LOCKED where that - * is required. - * - * In some cases, need also to be running in the CLI thread as well. - */ -#if VTY_DEBUG - -Inline void -VTY_ASSERT_FAILED(void) -{ - if (vty_assert_fail == 0) ; - { - vty_assert_fail = 1 ; - assert(0) ; - } ; -} ; - -Inline void -VTY_ASSERT_LOCKED(void) -{ - if (vty_lock_count == 0) - VTY_ASSERT_FAILED() ; -} ; - -Inline void -VTY_ASSERT_CLI_THREAD(void) -{ - if (!vty_is_cli_thread()) - VTY_ASSERT_FAILED() ; -} ; - -#else - -#define VTY_ASSERT_LOCKED() -#define VTY_ASSERT_CLI_THREAD() - -#endif - -/*============================================================================== - * Shared definitions - */ - -enum cli_do -{ - cli_do_nothing = 0, /* no action required */ - - cli_do_command, /* dispatch the current command line */ - cli_do_ctrl_c, /* received ^c */ - cli_do_ctrl_d, /* received ^d on empty line */ - cli_do_ctrl_z, /* received ^z */ - - cli_do_eof, /* hit "EOF" */ - - cli_do_count /* number of different cli_do_xxx */ -} ; - -/*============================================================================== - * Functions in vty.c -- used in any of the family - */ -extern enum cmd_return_code uty_command(struct vty *vty) ; -extern enum cmd_return_code uty_auth (struct vty *vty, const char *buf, - enum cli_do cli_do) ; -extern enum cmd_return_code vty_cmd_exit(struct vty* vty) ; -extern enum cmd_return_code vty_cmd_end(struct vty* vty) ; -extern enum cmd_return_code uty_cmd_close(struct vty *vty, const char* reason) ; -extern enum cmd_return_code uty_stop_input(struct vty *vty) ; -extern enum cmd_return_code uty_end_config (struct vty *vty) ; -extern enum cmd_return_code uty_down_level (struct vty *vty) ; - -extern bool vty_config_lock (struct vty *, enum node_type node); -extern void vty_config_unlock (struct vty *, enum node_type node); -extern void uty_config_unlock (struct vty *vty, enum node_type node) ; - -/*============================================================================== - * Functions in vty_cli -- used outside the immediate vty family - */ -extern void vty_queued_result(struct vty* vty, enum cmd_return_code ret); -extern void uty_set_host_name(const char* name) ; - -/*============================================================================== - * Functions in vty_io -- used outside the immediate vty family - */ -extern void vty_open_config_write(struct vty* vty, int fd) ; -extern int vty_close_config_write(struct vty*) ; - -extern void vty_log_fixed (const char *buf, size_t len); - -extern void uty_log (struct logline* ll, struct zlog *zl, int priority, - const char *format, va_list va); - -/*============================================================================== - * Functions in command.c - */ -extern void cmd_post_command(struct vty* vty, int ret) ; - -#endif /* _ZEBRA_UTY_H */ diff --git a/lib/vector.c b/lib/vector.c index 6df8d409..15371930 100644 --- a/lib/vector.c +++ b/lib/vector.c @@ -22,7 +22,7 @@ * 02111-1307, USA. */ -#include +//#include #include "vector.h" #include "memory.h" @@ -1055,7 +1055,7 @@ vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val, if (v->end <= 1) { - *result = (v->end == 0) ? -1 : cmp(&p_val, (const void**)&v->p_items[0]) ; + *result = (v->end == 0) ? -1 : cmp(&p_val, (const cvp*)&v->p_items[0]) ; return 0 ; /* Stop dead if 0 or 1 items */ } ; @@ -1064,12 +1064,12 @@ vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val, ih = v->end - 1 ; /* Pick off the edge cases: >= last and <= first. */ - if ((c = cmp(&p_val, (const void**)&v->p_items[ih])) >= 0) + if ((c = cmp(&p_val, (const cvp*)&v->p_items[ih])) >= 0) { *result = c ; /* 0 => found. +1 => val > last */ return ih ; /* return high index. */ } ; - if ((c = cmp(&p_val, (const void**)&v->p_items[il])) <= 0) + if ((c = cmp(&p_val, (const cvp*)&v->p_items[il])) <= 0) { *result = c ; /* 0 => found. -1 => val < first */ return il ; /* return low index. */ @@ -1086,7 +1086,7 @@ vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val, return il ; /* return il: item[il] < val < item[il+1] */ } ; /* We now know that il < iv < ih */ - c = cmp(&p_val, (const void**)&v->p_items[iv]) ; + c = cmp(&p_val, (const cvp*)&v->p_items[iv]) ; if (c == 0) { *result = 0 ; diff --git a/lib/vector.h b/lib/vector.h index 58b9e415..bae2a2f8 100644 --- a/lib/vector.h +++ b/lib/vector.h @@ -50,6 +50,16 @@ struct vector typedef struct vector vector_t[1] ; /* embedded vector structure */ typedef struct vector* vector ; /* pointer to vector structure */ +/* Setting a vector object to all zeros is enough to initialise it to + * an empty vector. + */ +enum +{ + VECTOR_INIT_ALL_ZEROS = true +} ; + +#define VECTOR_INIT_EMPTY { .p_items = NULL, .end = 0, .limit = 0 } + /* Under very controlled circumstances, may access the vector body */ typedef p_vector_item const* vector_body_t ; @@ -162,10 +172,10 @@ Inline p_vector_item vector_shift_item(vector v) ; extern void vector_insert(vector v, vector_index_t i, vector_length_t n) ; extern void vector_delete(vector v, vector_index_t i, vector_length_t n) ; -typedef int vector_bsearch_cmp(const void** pp_val, const void** item) ; +typedef int vector_bsearch_cmp(const cvp* pp_val, const cvp* item) ; vector_index_t vector_bsearch(vector v, vector_bsearch_cmp* cmp, const void* p_val, int* result) ; -typedef int vector_sort_cmp(const void** a, const void** b) ; +typedef int vector_sort_cmp(const cvp* a, const cvp* b) ; void vector_sort(vector v, vector_sort_cmp* cmp) ; extern vector vector_copy_here(vector dst, vector src) ; diff --git a/lib/version.h.in b/lib/version.h.in index 429474d1..ded43944 100644 --- a/lib/version.h.in +++ b/lib/version.h.in @@ -34,6 +34,8 @@ #define QUAGGA_COPYRIGHT "Copyright 1996-2005 Kunihiro Ishiguro, et al." +#include + pid_t pid_output (const char *); #ifndef HAVE_DAEMON diff --git a/lib/vio_fifo.c b/lib/vio_fifo.c index 10c4a343..e6365861 100644 --- a/lib/vio_fifo.c +++ b/lib/vio_fifo.c @@ -20,14 +20,13 @@ */ #include "misc.h" +#include #include #include "vio_fifo.h" #include "network.h" -#include "list_util.h" #include "memory.h" -#include "zassert.h" /*============================================================================== * VTY I/O FIFO manages an arbitrary length byte-wise FIFO buffer. @@ -39,7 +38,6 @@ * ever needed. * * When releasing lumps, keeps one lump "spare", to be reused as necessary. - * This is used in ... TODO <<<< And is released... * *------------------------------------------------------------------------------ * Implementation notes: @@ -48,97 +46,218 @@ * * Once a lump has been allocated there is always one lump in the FIFO. * + * The hold_mark allows the get_ptr to move forward, but retaining the data in + * the FIFO until the hold_mark is cleared. Can move the get_ptr back to the + * hold_mark to reread the data. + * + * The end_mark allows put_ptr to move forward, but the new data cannot be got + * from the FIFO until the end_mark is cleared. Can discard the new data + * and move the put_ptr back to the end_mark. + * + * There are four lumps of interest: + * + * * head -- where the hold_mark is, if there is one. + * + * * get_lump -- where the get_ptr is. + * Same as head when no hold_mark. + * + * * end_lump -- where the end_mark is, if there is one. + * Same as tail when no end mark. + * + * * tail -- where the put_ptr is. + * + * Some or all of those may be the same, depending on how big the FIFO is. + * * The following are expected to be true: * - * * put_ptr == get_ptr => FIFO empty + * * set <=> at least one lump in the FIFO + * + * * as_one <=> set && get_lump == tail && !end_mark + * => get_end moves with put_ptr + * + * * hold_mark => there is a hold mark & hold_ptr is valid + * & head lump contains mark + * !hold_mark => get_lump == head & hold_ptr == NULL * - * * put_ptr == tail->end -- at all times (NULL when no lumps) + * * end_mark => there is an end_mark & end_end is valid + * & end_lump contains mark + * !end_mark => end_lump == tail & end_ptr == NULL + * + * * put_ptr == get_ptr => FIFO empty -- unless hold_mark and + * hold_ptr != get_ptr. + * + * * put_end == tail->end -- at all times (NULL when no lumps) * * put_ptr >= tail->data ) otherwise something is broken * put_ptr <= tail->end ) * - * * get_ptr == head->end -- when there is more than one lump - * get_ptr <= put_ptr -- when there is only one lump + * * get_end == get_lump->end -- when get_lump != end_lump + * == end_end -- when get_lump == end_lump & end_mark set + * <= put_ptr -- when get_lump == end_lump & no end_mark + * + * See note below on get_end and as_one flag. * - * get_ptr >= head->data ) otherwise something is broken - * get_ptr <= head->end ) + * get_ptr >= get_lump->data ) otherwise something is broken + * get_ptr <= get_lump->end ) * * * put_ptr == put_end => tail lump is full * put_ptr < put_end => space exists in the tail lump * put_ptr > put_end => broken * - * * get_ptr == get_end => head lump is empty - * BUT if there is only one lump, make sure that - * get_end == put_ptr. - * get_ptr < get_end => data exists in the head lump + * * get_ptr == get_end => nothing to get from current get_lump + * BUT if as_one, make sure that get_end == put_ptr + * get_ptr < get_end => data exists in the current get_lump * get_ptr > get_end => broken * * Note that: * - * * when the get_ptr reaches the put_ptr the pointers are reset to the - * start of the one and only lump. + * * while get_ptr <= get_end can get stuff without worrying about other + * pointers or moving between lumps etc. It is permissible to leave + * get_ptr == get_end -- this will be tidied up on the next get operation, + * or any other time vio_fifo_sync_get() is called. Leaving get_ptr in + * that state delays the discard of the now empty lump, but has no other + * real downside. + * + * Similarly, while put_ptr <= put_end, can put stuff without worrying + * about other pointers or moving between lumps etc. + * + * When getting, if get_ptr == get_end, or require more data than is + * immediately available, need to use vio_fifo_sync_get(), to do that. + * + * * the value of get_end depends on whether get_lump == end_lump, and then + * whether there is an end_mark. + * + * When get_lump == end_lump && !end_mark, then get_end may be out of date + * because get_ptr has been advanced. This is dealt with by + * vio_fifo_sync_get(), which uses the as_one flag to signal that it should + * set get_end = put_ptr and then (re)check for anything to get. + * + * The as_one flag is there to save a little work. Making sure that the + * get_end is up to date can be done often when getting from the FIFO. So + * the maintenance of the flag should be worth the effort. + * + * * some care must be taken to ensure that are not fooled by the + * ambiguity of a pointer to the end of one lump and a pointer to the + * start of the next -- these are really equal, but they don't look as if + * they are ! * - * Everywhere that the get_ptr is moved, must check for meeting the - * put_ptr and reset pointers. At the same time, when reaches the end of - * a lump, gets rid of it. + * - put_ptr -- if at the end of the last lump there is no next lump ! * - * * when advancing the put_ptr does not check for advancing the get_end. + * When a new lump is added, the put_ptr advances to the + * start of the new last lump. * - * The one exception to this, is that when the put_ptr advances to a new - * block, if there was one lump, sets the get_end to the end of that block. + * - end_end -- when set from the put_ptr, this can be at the end of + * the last lump, but as above, there is no next lump. * - * Everywhere that the get_end is used, must check for there being one - * lump and the possibility that put_ptr has changed. + * When a new lump is added, if the end_end is at the + * end of the last lump, it is moved to the start of the + * new last lump (along with the out_ptr). + * + * So... end_end will never be ambiguous. + * + * - get_ptr -- this can be ambiguous. + * + * When getting bytes, if the segment get_ptr..get_end + * is sufficient, then nothing else is required. + * + * Otherwise, vio_fifo_sync_get() will sort things out, + * including resetting all pointers if the FIFO has been + * emptied. + * + * - hold_ptr -- when set from get_ptr this could be at the end of the + * first lump -- but when vio_fifo_sync_get() is called, + * that will be spotted and sorted out. + * + * If get_ptr is set from an ambiguous hold_ptr, that is + * also taken care of by the next vio_fifo_sync_get(). + * + * When doing things with hold_ptr, does a number of + * vio_fifo_sync_get() operations, so the hold_ptr should + * not, in practice, be ambiguous. + * + * * Before the first lump is allocated the FIFO appears empty (of course) + * but may have hold_mark and/or end_mark set, and these work as expected. */ /*============================================================================== * Initialisation, allocation and freeing of FIFO and lumps thereof. */ -/* Return default size, or given size rounded up to 16 byte boundary */ +/*------------------------------------------------------------------------------ + * Return default size, or given size rounded up to 128 byte boundary + */ static size_t vio_fifo_size(size_t size) { +#if VIO_FIFO_DEBUG +#warning VIO_FIFO_DEBUG and 29 byte lumps ! + return 29 ; +#else if (size == 0) return 4096 ; else - return ((size + 16 - 1) / 16) * 16 ; + return ((size + 128 - 1) / 128) * 128 ; +#endif +} ; + +/*------------------------------------------------------------------------------ + * Set and return true end of lump. + * + * End of lump can be set short of true end when putting stuff, if wish to + * move to next lump early (eg if not enough room left in current lump). + * + * When reusing a lump, need to restore the true lump end. + */ +static inline char* +vio_fifo_true_lump_size(vio_fifo_lump lump) +{ + return lump->end = lump->data + lump->size ; } ; /*============================================================================== * Initialise VTY I/O FIFO -- allocating if required. */ extern vio_fifo -vio_fifo_init_new(vio_fifo vf, size_t size) +vio_fifo_init_new(vio_fifo vff, size_t size) { - if (vf == NULL) - vf = XCALLOC(MTYPE_VIO_FIFO, sizeof(vio_fifo_t)) ; + if (vff == NULL) + vff = XCALLOC(MTYPE_VIO_FIFO, sizeof(vio_fifo_t)) ; else - memset(vf, 0, sizeof(vio_fifo_t)) ; + memset(vff, 0, sizeof(vio_fifo_t)) ; /* Zeroising the the vio_fifo_t has set: * - * lump -- base pair, both pointers NULL => list is empty + * base -- base pair, both pointers NULL => list is empty * - * put_ptr -- NULL ) no lump to put anything into - * put_end -- NULL ) put_ptr == put_end => no room in current lump + * set -- false -- nothing set, yet. + * as_one -- false -- get_lump != tail or end_mark + * hold_mark -- false -- no hold mark + * end_mark -- false -- no end mark + * + * hold_ptr -- NULL no hold_mark * + * get_lump -- NULL ) * get_ptr -- NULL ) no lump to get anything from * get_end -- NULL ) get_ptr -- get_end => nothing left in current lump * - * rdr_lump -- NULL ) no rdr_lump - * rdr_ptr -- NULL + * end_lump -- NULL no lump at end of what can get + * end_end -- NULL no end_mark + * + * put_ptr -- NULL ) no lump to put anything into + * put_end -- NULL ) put_ptr == put_end => no room in current lump * - * spare -- NULL no spare lump + * size -- 0 no size set for lumps (yet) * - * ALSO put_ptr == get_ptr => FIFO is empty ! + * spare -- NULL no spare lump */ + confirm(VIO_FIFO_INIT_ALL_ZEROS) ; - vf->size = vio_fifo_size(size) ; + if (size != 0) + vff->size = vio_fifo_size(size) ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; - return vf ; + return vff ; } /*------------------------------------------------------------------------------ @@ -146,7 +265,8 @@ vio_fifo_init_new(vio_fifo vf, size_t size) * * Does nothing if given a NULL pointer -- must already have been freed ! * - * If does not free the FIFO structure, resets it all empty. + * If does not free the FIFO structure, resets it all empty, keeping the + * current size setting. * * Frees *all* FIFO lumps. * @@ -154,104 +274,289 @@ vio_fifo_init_new(vio_fifo vf, size_t size) * vio_fifo_reset_free(vio_fifo) */ extern vio_fifo -vio_fifo_reset(vio_fifo vf, int free_structure) +vio_fifo_reset(vio_fifo vff, free_keep_b free_structure) { vio_fifo_lump lump ; - if (vf == NULL) + if (vff == NULL) return NULL ; - while (ddl_pop(&lump, vf->base, list) != NULL) + while (ddl_pop(&lump, vff->base, list) != NULL) XFREE(MTYPE_VIO_FIFO_LUMP, lump) ; - if (vf->spare != NULL) - XFREE(MTYPE_VIO_FIFO_LUMP, vf->spare) ; + if (vff->spare != NULL) + XFREE(MTYPE_VIO_FIFO_LUMP, vff->spare) ; + + confirm(free_it == true) ; if (free_structure) - XFREE(MTYPE_VIO_FIFO, vf) ; /* sets vf = NULL */ + XFREE(MTYPE_VIO_FIFO, vff) ; /* sets vff = NULL */ else - vio_fifo_init_new(vf, vf->size) ; + vio_fifo_init_new(vff, vff->size) ; + + return vff ; +} ; + +/*------------------------------------------------------------------------------ + * The FIFO has one lump -- set all pointers. + * + * Preserves end_mark -- setting to new put_ptr position + * Preserves hold_mark -- setting to new put_ptr position + * + * Sets as_one if no end_mark. + */ +inline static void +vio_fifo_ptr_set(vio_fifo vff, vio_fifo_lump lump) +{ + vff->as_one = !vff->end_mark ; + + vff->put_ptr = lump->data ; + vff->put_end = vio_fifo_true_lump_size(lump) ; + + vff->hold_ptr = vff->hold_mark ? vff->put_ptr : NULL ; + + vff->get_lump = lump ; + vff->get_ptr = vff->put_ptr ; + vff->get_end = vff->put_ptr ; - return vf ; + vff->end_lump = lump ; + vff->end_end = vff->end_mark ? vff->put_ptr : NULL ; } ; /*------------------------------------------------------------------------------ * The FIFO is empty, with one lump -- reset all pointers. + * + * Preserves end_mark -- setting to new put_ptr position + * Preserves hold_mark -- setting to new put_ptr position + * + * Sets as_one if no end_mark. */ inline static void -vio_fifo_ptr_reset(vio_fifo vf, vio_fifo_lump lump) +vio_fifo_ptr_reset(vio_fifo vff, vio_fifo_lump lump) { - if (vf->rdr_lump != NULL) - { - assert((lump == vf->rdr_lump) && (vf->rdr_ptr == vf->get_ptr)) ; - vf->rdr_ptr = lump->data ; - } ; + assert(vff->set) ; + assert(lump == ddl_tail(vff->base)) ; /* must be tail */ + assert(lump == ddl_head(vff->base)) ; /* and must be head */ - /* Note that sets the lump->end to the true lump->end */ - vf->get_ptr = vf->get_end = vf->put_ptr = lump->data ; - vf->put_end = lump->end = lump->data + lump->size ; + vio_fifo_ptr_set(vff, lump) ; } ; /*------------------------------------------------------------------------------ - * The FIFO is utterly empty, with ZERO lumps -- unset all pointers. + * Synchronise get_end and put_ptr (if required) + * + * When in the same lump, the get_end and the put_ptr should be the same. But + * maintaining that every time something is put into the buffer is a pain, so + * we allow the put_ptr to get ahead, and re-synchronise when get_ptr hits + * get_end, or any other time we need the pointers to be straight. + * + * The as_one flag takes a little maintenance... it means: + * + * (get_lump == tail) && set && !end_mark + * + * If get_ptr is at the end of what can be got from the current lump, advances + * to the next lump if possible, releasing anything that can be released. + * + * If the FIFO is empty (with one lump), will crash all pointers down to start + * of current lump. The FIFO is empty if get_ptr == put_ptr, and there are no + * bytes held before the get_ptr. + * + * NB: object of the hold_mark is to allow users of the FIFO to store pointers + * to sections of the FIFO returned by vio_fifo_get_lump() and stepped + * over by vio_fifo_got_upto(). Note that where there is nothing to + * read vio_fifo_get_lump() returns NULL -- so do not have to preserve + * the hold_ptr when hold_ptr == get_ptr (== end_end) == put_ptr -- that + * is, if hold_ptr == get_ptr, we can move the hold_ptr around with the + * get_ptr. + * + * Can reach empty state without realising it, because the "get" stuff is not + * required to check every time a byte is read -- in fact, it is not necessary + * to check until cannot get anything because get_ptr == get_end. + * + * NB: with an end_mark there is the ambiguous position at the end of a + * lump -- which is the same as the start of the next lump, if any. + * + * Elsewhere we advance the end_end if it was at the very end of the FIFO + * and we advance the put_ptr to a new lump. But we cope here in any + * case. + * + * Returns: true <=> there is something in the (now) current get_lump. + * false <=> there is nothing else to be got (though if there is an + * end_mark, there may be stuff beyond that). + */ +static bool vio_fifo_sync_get_next(vio_fifo vff) ; +static void vio_fifo_release_head(vio_fifo vff, vio_fifo_lump lump) ; + +inline static bool +vio_fifo_sync_get(vio_fifo vff) +{ + if (vff->as_one) /* false if !vff->set */ + vff->get_end = vff->put_ptr ; + + if (vff->get_ptr < vff->get_end) /* both NULL if !vff->set */ + return true ; /* have at least one byte */ + + return vio_fifo_sync_get_next(vff) ; +} ; + +/*------------------------------------------------------------------------------ + * See if fifo really is or is not empty (get_ptr == end_end or == put_ptr). + * + * Used by vio_fifo_empty(). Called iff get_ptr >= get_end ! + * + * So need to vio_fifo_sync_get() and test again against end_end & put_ptr. + */ +Private bool +vio_fifo_do_empty(vio_fifo vff) +{ + return ! vio_fifo_sync_get(vff) ; +} ; + +/*------------------------------------------------------------------------------ + * Set a new get_lump/get_ptr/get_end set. And set as_one to suit. */ inline static void -vio_fifo_ptr_unset(vio_fifo vf) +vio_fifo_set_get_ptr(vio_fifo vff, void* ptr, vio_fifo_lump lump) +{ + vff->get_lump = lump ; + vff->get_ptr = ptr ; + + if (lump != vff->end_lump) + { + vff->get_end = lump->end ; + vff->as_one = false ; + } + else if (vff->end_mark) + { + vff->get_end = vff->end_end ; + vff->as_one = false ; + } + else + { + vff->get_end = vff->put_ptr ; + vff->as_one = true ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Move on to next lump to get stuff from. + * + * For use by vio_fifo_sync_get() *ONLY*. + * + * Asserts that (vff->get_ptr == vff->get_end) and assumes that if as_one, then + * get_end == put_ptr ! + * + * Returns: true <=> at least one byte in FIFO available to get. + */ +static bool +vio_fifo_sync_get_next(vio_fifo vff) { - assert((ddl_head(vf->base) == NULL) && (ddl_tail(vf->base) == NULL)) ; + bool hold_empty ; + + assert(vff->get_ptr == vff->get_end) ; + + VIO_FIFO_DEBUG_VERIFY(vff) ; + + if (!vff->set) + return false ; /* quit before get into any trouble */ + + /* Worry about whether there is anything held. + * + * NB: we know that at this point the get_ptr == get_end, so is at the end + * of the current get_lump. The hold_ptr cannot be ahead of get_ptr, + * so the test hold_ptr == get_ptr is sufficient to detect that there + * is nothing, in fact, held. + */ + hold_empty = !vff->hold_mark || (vff->hold_ptr == vff->get_ptr) ; + + /* If we are not at the end_lump, step forward, discarding current + * lump unless hold_mark. + */ + if (vff->get_lump != vff->end_lump) + { + vio_fifo_lump next ; + next = ddl_next(vff->get_lump, list) ; + + vio_fifo_set_get_ptr(vff, next->data, next) ; + + if (hold_empty) + { + vio_fifo_release_head(vff, next) ; + + /* If there is a hold_mark, then had hold_ptr == get_ptr + * and this is where we keep hold_ptr & get_ptr in sync. + */ + if (vff->hold_mark) + vff->hold_ptr = vff->get_ptr ; + } ; + + /* Return the get state now */ + if (vff->get_ptr < vff->get_end) + return true ; + } ; - vf->one = false ; + /* Still have get_ptr == get_end => nothing more to get. + * + * Check now for empty FIFO, and reset all pointers if that is the case. + */ + if ((vff->get_ptr == vff->put_ptr) && hold_empty) + { + if (vff->hold_mark) + assert(vff->hold_ptr == vff->get_ptr) ; - vf->put_ptr = NULL ; - vf->put_end = NULL ; - vf->get_ptr = NULL ; - vf->get_end = NULL ; + if (vff->end_mark) + assert(vff->end_end == vff->put_ptr) ; - vf->rdr_lump = NULL ; - vf->rdr_ptr = NULL ; + vio_fifo_ptr_reset(vff, vff->end_lump) ; + } + + return false ; /* nothing to get */ } ; /*------------------------------------------------------------------------------ * Clear out contents of FIFO -- will continue to use the FIFO. * + * If required, clears any hold mark and/or end mark. + * * Keeps one FIFO lump. (Frees everything else, including any spare.) * * Does nothing if there is no FIFO ! */ extern void -vio_fifo_clear(vio_fifo vf) +vio_fifo_clear(vio_fifo vff, bool clear_marks) { - vio_fifo_lump tail ; - - if (vf == NULL) + if (vff == NULL) return ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; - tail = ddl_tail(vf->base) ; + vff->as_one = vff->set ; + if (clear_marks) + { + vff->hold_mark = false ; /* Discard marks */ + vff->end_mark = false ; + } ; - if (tail != NULL) + if (vff->set) { - while (ddl_head(vf->base) != tail) + vio_fifo_lump lump ; + + while (1) { - vio_fifo_lump lump ; - ddl_pop(&lump, vf->base, list) ; + lump = ddl_head(vff->base) ; + if (lump == ddl_tail(vff->base)) + break ; + + ddl_pop(&lump, vff->base, list) ; XFREE(MTYPE_VIO_FIFO_LUMP, lump) ; } ; - vf->rdr_lump = NULL ; /* clear rdr */ - vf->rdr_ptr = NULL ; - - vf->one = true ; - vio_fifo_ptr_reset(vf, tail) ; - } - else - vio_fifo_ptr_unset(vf) ; + vio_fifo_ptr_reset(vff, lump) ; + } ; - if (vf->spare != NULL) - XFREE(MTYPE_VIO_FIFO_LUMP, vf->spare) ; /* sets vf->spare = NULL */ + if (vff->spare != NULL) + XFREE(MTYPE_VIO_FIFO_LUMP, vff->spare) ; /* sets vff->spare = NULL */ - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; } ; /*------------------------------------------------------------------------------ @@ -266,476 +571,688 @@ vio_fifo_clear(vio_fifo vf) * Returns: room available as described */ extern size_t -vio_fifo_room(vio_fifo vf) +vio_fifo_room(vio_fifo vff) { - if (vf->put_ptr != NULL) - return vf->put_end - vf->put_ptr ; + if (vff->set) + return vff->put_end - vff->put_ptr ; else - return vf->size ; + return vff->size ; } ; /*------------------------------------------------------------------------------ - * Allocate another lump for putting into. + * Need a new lump to put stuff into. * - * Call when (vf->put_ptr >= vf->put_end) -- asserts that they are equal. + * Call when (vff->put_ptr >= vff->put_end) -- asserts that they are equal. * - * Set the put_ptr/put_end pointers to point at the new lump. + * If the FIFO is, in fact, empty but with at least one lump, then does not + * allocate anything more, but releases all lumps but the last lump and then + * resets all pointers to the start of that lump. * - * If this is the first lump allocated, set the get_ptr/get_end pointers too. + * Otherwise, allocates a new lump (or reuses the spare) to the requested size, + * and updates all pointers as required. (Allocates to vff->size if + * requested size is zero.) * - * If have just filled the first lump on the list, update the get_end pointer - * to reflect the fact that the out lump is now full. + * NB: if there is an end_mark, makes sure that it advances with the put_ptr + * if currently end_end == the put_ptr. */ -extern void -vio_fifo_lump_new(vio_fifo vf, size_t size) +Private void +vio_fifo_lump_new(vio_fifo vff, size_t size) { vio_fifo_lump lump ; - int first_alloc ; + size_t std_size ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + passert(vff->put_ptr == vff->put_end) ; /* must be end of tail lump + (or both NULL) */ + VIO_FIFO_DEBUG_VERIFY(vff) ; - passert(vf->put_ptr == vf->put_end) ; /* must be end of tail lump */ + /* First, make sure that the get side is synchronised, which may advance + * various pointers, release lumps and possibly reset now empty FIFO. + * + * If synchronising the get side does not yield space, then the FIFO is + * either not set or it has something in it (including held stuff). + */ + vio_fifo_sync_get(vff) ; - if (vf->one) - vf->get_end = vf->put_ptr ; /* update get_end */ + if (vff->put_ptr < vff->put_end) + return ; /* Done if have created space */ - lump = ddl_tail(vf->base) ; + /* If we can use the spare, do so, otherwise make it to size */ + lump = vff->spare ; + vff->spare = NULL ; - first_alloc = (lump == NULL) ; /* extra initialisation needed */ + std_size = vio_fifo_size(vff->size) ; /* normalised standard */ - if (first_alloc) - assert(vf->put_ptr == NULL) ; /* must all be NULL together */ + if (size <= std_size) + size = std_size ; /* use standard as a minimum */ else - assert(vf->put_ptr == lump->end) ; /* must be end of tail lump */ +#if VIO_FIFO_DEBUG + size |= 3 ; /* most of the time a little bigger */ +#else + size = vio_fifo_size(size) ; /* normalise requested size */ +#endif - size = vio_fifo_size(size) ; + if ((lump == NULL) || (lump->size < size)) + { + /* If there was no spare, lump == NULL and XREALLOC == XMALLOC. + * If there was a spare that was too small, better extend that than + * keep a sub-standard spare. + */ + lump = XREALLOC(MTYPE_VIO_FIFO_LUMP, lump, + offsetof(vio_fifo_lump_t, data[size])) ; + lump->size = size ; + lump->end = lump->data + size ; + } ; - if ((vf->spare != NULL) && (vf->spare->size >= size)) + ddl_append(vff->base, lump, list) ; + + /* If not just allocated the first lump, set the put_ptr and normalise any + * end_mark or update end_lump. + * + * If is first block to be allocated, set all pointers, taking into account + * any end_mark and/or hold_mark. + */ + if (vff->set) { - lump = vf->spare ; - vf->spare = NULL ; + /* Allocated new lump on the end of a not-empty fifo. + * + * Have to watch out for cases where the get_ptr and/or end_end are + * equal to the put_ptr, which is about to move to the start of a new + * lump, so as to avoid ambiguity ! + */ + + if (vff->get_ptr == vff->put_ptr) + { + /* If the fifo is empty, the vio_fifo_sync_get() above will have + * spotted it. So can only get here iff there is something held in + * the fifo behind the get_ptr. + * + * If was as_one, is still as_one. + * + * If was not as_one then must have end_mark with end_end == put_ptr, + * which will be dealt with below. + */ + assert(vff->hold_mark && (vff->get_ptr != vff->hold_ptr)) ; + + vff->get_ptr = lump->data ; + vff->get_end = lump->data ; + vff->get_lump = lump ; + } + else + { + /* If were as_one, then will no longer be, because put_ptr is about + * to advance to the start of the new lump. + */ + vff->as_one = false ; + } ; + + if (vff->end_mark) + { + assert(!vff->as_one) ; + + /* The end_end follows the put_ptr iff they are equal + * + * If the get_ptr also equals the put_ptr, it has already advanced. + */ + if (vff->end_end == vff->put_ptr) + { + vff->end_end = lump->data ; + vff->end_lump = lump ; + } ; + } + else + { + /* No end_mark => end_lump simply follows the put_ptr. */ + vff->end_lump = lump ; + } ; + + vff->put_ptr = lump->data ; + vff->put_end = vio_fifo_true_lump_size(lump) ; } else { - lump = XMALLOC(MTYPE_VIO_FIFO_LUMP, - offsetof(vio_fifo_lump_t, data[size])) ; - lump->size = size ; + /* Allocated lump for previously empty fifo -- set all pointers to the + * start of the lump, except for put_end. + */ + vff->set = true ; + vio_fifo_ptr_set(vff, lump) ; } ; - lump->end = lump->data + lump->size ; + VIO_FIFO_DEBUG_VERIFY(vff) ; +} ; - ddl_append(vf->base, lump, list) ; +/*------------------------------------------------------------------------------ + * Release the given lump, provided it is neither get_lump nor end_lump. + * + * If don't have a spare lump, keep this one. Otherwise, keep larger of + * this and current spare. + */ +static void +vio_fifo_release_lump(vio_fifo vff, vio_fifo_lump lump) +{ + assert(lump != NULL) ; + assert(lump != vff->get_lump) ; + assert(lump != vff->end_lump) ; - vf->one = first_alloc ; + if (vff->spare != NULL) + { + vio_fifo_lump free ; - vf->put_ptr = lump->data ; - vf->put_end = lump->end ; + free = vff->spare ; - if (first_alloc) - { - vf->get_ptr = vf->put_ptr ; /* get_ptr == put_ptr => empty */ - vf->get_end = vf->put_ptr ; + if (free->size > lump->size) + { + free = lump ; + lump = vff->spare ; + } ; + + XFREE(MTYPE_VIO_FIFO_LUMP, free) ; } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + vff->spare = lump ; } ; /*------------------------------------------------------------------------------ - * Release lump, head or tail (or both) and update pointers. - * - * Note that this does release the lump if it is the only lump. - * - * Do nothing if nothing is yet allocated. + * Release lumps from head up to, but not including, given lump. * - * If releasing the only lump in the FIFO, resets all pointers to NULL. + * NB: must be "set" and must not attempt to release the get_lump or the + * end_lump. * - * If releasing the head lump: + * So MUST advance at least the get_lump before calling this. * - * * the lump MUST be finished with -- so vf->get_ptr must be at the end + * It is the caller's responsibility to update get and/or hold pointers. + */ +static void +vio_fifo_release_head(vio_fifo vff, vio_fifo_lump upto) +{ + assert(vff->set) ; + + while (upto != ddl_head(vff->base)) + { + vio_fifo_lump lump ; + vio_fifo_release_lump(vff, ddl_pop(&lump, vff->base, list)) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Release lumps from tail back to, but not including, given lump. * - * If releasing the only lump, the FIFO MUST be empty. + * NB: must be "set" and must not attempt to release the get_lump or the + * end_lump. * - * * if the lump is the current vf->rdr_lump, the reader must be at the - * end too -- ie it must be the same as the vf->get_ptr ! + * It is the caller's responsibility to update end and/or put pointers. + */ +static void +vio_fifo_release_tail(vio_fifo vff, vio_fifo_lump backto) +{ + assert(vff->set) ; + + while (backto != ddl_tail(vff->base)) + { + vio_fifo_lump lump ; + vio_fifo_release_lump(vff, ddl_crop(&lump, vff->base, list)) ; + } ; +} ; + +/*============================================================================== + * Put data to the FIFO. + */ + +/*------------------------------------------------------------------------------ + * Put 'n' bytes -- allocating as required. + */ +extern void +vio_fifo_put_bytes(vio_fifo vff, const char* src, size_t n) +{ + VIO_FIFO_DEBUG_VERIFY(vff) ; + + while (n > 0) + { + size_t take ; + + if (vff->put_ptr >= vff->put_end) + vio_fifo_lump_new(vff, 0) ; /* traps put_ptr > put_end */ + + take = (vff->put_end - vff->put_ptr) ; + if (take > n) + take = n ; + + memcpy(vff->put_ptr, src, take) ; + vff->put_ptr += take ; + + src += take ; + n -= take ; + } ; + + VIO_FIFO_DEBUG_VERIFY(vff) ; +} ; + +/*------------------------------------------------------------------------------ + * Formatted print to FIFO -- cf printf() * - * If releasing the tail lump: + * Returns: >= 0 -- number of bytes written + * < 0 -- failed (unlikely though that is) + */ +extern int +vio_fifo_printf(vio_fifo vff, const char* format, ...) +{ + va_list args; + int len ; + + va_start (args, format); + len = vio_fifo_vprintf(vff, format, args); + va_end (args); + + return len; +} ; + +/*------------------------------------------------------------------------------ + * Formatted print to FIFO -- cf vprintf() * - * * the lump MUST be empty + * Does nothing if vff is NULL ! * - * If releasing the only lump, the FIFO MUST be empty. + * Returns: >= 0 -- number of bytes written + * < 0 -- failed (unlikely though that is) * - * * if the lump is the current vf->rdr_lump, the reader must be at the - * end too -- ie it must be the same as the vf->get_ptr ! + * NB: does not extend an existing lump in order to make things fit, but + * splits the result across two lumps. This ensures that at all times + * pointers into existing lumps are stable -- so pointer returned by + * vio_fifo_get_lump(), for example, cannot be upset ! */ -static void -vio_fifo_lump_release(vio_fifo vf, vio_fifo_lump lump) +extern int +vio_fifo_vprintf(vio_fifo vff, const char *format, va_list args) { - vio_fifo_lump head ; - vio_fifo_lump tail ; - vio_fifo_lump free ; - bool release_head ; - bool release_tail ; + va_list ac ; + int len ; + int have ; + int had ; + int need ; + char* last ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + if (vff == NULL) + return 0 ; - /* Prepare and check whether removing head or tail (or both) */ - head = ddl_head(vf->base) ; - tail = ddl_tail(vf->base) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; - release_head = (lump == head) ; - release_tail = (lump == tail) ; + /* First the simple way. + * + * Note that vsnprintf() returns the length of what it would like to + * have produced, if it had the space. That length does not include + * the trailing '\0'. In the meantime, it has output as much as it + * can, with a trailing '\0'. + */ + assert(vff->put_ptr <= vff->put_end) ; - assert(release_head || release_tail) ; + have = vff->put_end - vff->put_ptr ; /* what can do in current lump + if any. */ + va_copy(ac, args) ; + len = vsnprintf(vff->put_ptr, have, format, ac) ; + va_end(ac) ; - /* Unless nothing ever allocated -- release the lump. */ - free = lump ; /* expect to free the lump */ - if (lump != NULL) + if ((len < have) || (len == 0)) /* OK, or failed ! */ { - vio_fifo_lump keep ; + if (len > 0) + vff->put_ptr += len ; /* advance put_ptr as required */ - /* Consistency checks */ - if (release_head) - { - if (release_tail) - assert(vf->get_ptr == vf->put_ptr) ; - else - assert(vf->get_ptr == lump->end) ; + return len ; + } ; - if (vf->rdr_lump == lump) - assert(vf->rdr_ptr == vf->get_ptr) ; - } - else if (release_tail) - { - assert(vf->put_ptr == lump->data) ; + /* Now know that we need len + 1 bytes in all to complete the task, and + * that it has written have - 1 bytes to the existing lump (if any). + * + * Also, len > 0. + * + * Allocate a new lump in which we can write the entire result, even if + * that is a non-standard size. + */ + need = len + 1 ; /* need includes the '\0' */ + had = have ; - if (vf->rdr_lump == lump) - assert(vf->rdr_ptr == vf->put_ptr) ; - } ; + if (had > 0) + vff->put_ptr += had ; /* step to the end */ + last = vff->put_ptr ; /* point at end of lump (NULL if none) */ - /* Remove lump from FIFO and decide whether to keep as spare, or - * which of spare and this to free. - */ - ddl_del(vf->base, lump, list) ; + vio_fifo_lump_new(vff, need) ;/* new lump to do it all */ + + have = vff->put_end - vff->put_ptr ; + assert(have >= need) ; /* have >= 2 */ + + /* We really expect to get the same result a second time ! */ + va_copy(ac, args) ; + len = vsnprintf(vff->put_ptr, have, format, ac) ; + va_end(ac) ; + + /* Since have >= what previously said it needed, things have gone + * badly wrong if the new len is >= have. + * + * Also, things have gone badly wrong if new len is < what previously + * had, which was then not enough ! + * + * Also, things have gone badly wrong if new len == 0, because previously + * it was > 0 ! + */ + if ((len >= have) || (len < had) || (len == 0)) + return (len < 0) ? len : -1 ; + + /* Move result around if required -- len >= had */ + have = len ; + if (had > 0) + { + char* frag ; + frag = vff->put_ptr + had ; /* first character to keep */ + *(last - 1) = *(frag - 1) ; /* replace the '\0' */ + + have -= had ; /* amount to keep */ + if (have > 0) + memmove(vff->put_ptr, frag, have) ; + } ; + + /* Advance the put_ptr past what we have in the new lump. */ + vff->put_ptr += have ; + + VIO_FIFO_DEBUG_VERIFY(vff) ; + + return len ; +} ; + +/*------------------------------------------------------------------------------ + * Read part of file into FIFO -- assuming non-blocking file + * + * Will read up to the end of the lump which meets, or exceeds the number of + * bytes requested, or until would block. + * + * Returns: 0..n -- number of bytes read + * -1 => failed -- see errno + * -2 => EOF met immediately + * + * Note: will work perfectly well for a non-blocking file -- which should + * never return EAGAIN/EWOULDBLOCK, so will return from here with + * something, error or EOF. + */ +extern int +vio_fifo_read_nb(vio_fifo vff, int fd, size_t request) +{ + size_t total ; + + total = 0 ; + + do + { + int got ; + + if (vff->put_ptr >= vff->put_end) + vio_fifo_lump_new(vff, 0) ; /* traps put_ptr > put_end */ - keep = vf->spare ; /* expect to keep current spare */ + got = read_nb(fd, vff->put_ptr, vff->put_end - vff->put_ptr) ; - if ((keep == NULL) || (keep->size < lump->size)) + if (got <= 0) { - keep = lump ; - free = vf->spare ; + if (got == -2) /* EOF met */ + return (total > 0) ? (int)total : got ; + else + return (got == 0) ? (int)total : got ; } ; - vf->spare = keep ; + vff->put_ptr += got ; + total += got ; - head = ddl_head(vf->base) ; /* changed if released head */ - tail = ddl_tail(vf->base) ; /* changed if released tail */ - } ; + } while (total < request) ; - /* Now update pointers... depending on what was released and what have - * left. - */ - if (head == NULL) - { - /* Deal with FIFO that now has no lumps or had none to start with */ - if (lump != NULL) - assert(vf->one) ; + return total ; +} ; - vio_fifo_ptr_unset(vf) ; - } - else +/*============================================================================== + * Copy operations -- from one fifo to another. + */ + +/*------------------------------------------------------------------------------ + * Copy src fifo (everything from get_ptr to end_mark or put_ptr) to dst fifo. + * + * Create a dst fifo if there isn't one. + * + * Appends to the dst fifo. + * + * Does not change the src fifo in any way. + */ +extern vio_fifo +vio_fifo_copy(vio_fifo dst, vio_fifo src) +{ + if (dst == NULL) + dst = vio_fifo_init_new(dst, 0) ; + + if ((src != 0) && (src->set)) { - /* Have at least one lump left -- so must have had at least two ! */ - assert(!vf->one) ; + vio_fifo_lump src_lump ; + char* src_ptr ; + + if (src->get_ptr >= src->get_end) + vio_fifo_sync_get(src) ; - vf->one = (head == tail) ; /* update */ + src_lump = src->get_lump ; + src_ptr = src->get_ptr ; - if (release_head) + while (1) { - /* Released the head. - * - * Update the vf->get_ptr and the vf->get_end. - */ - vf->get_ptr = head->data ; - if (vf->one) - vf->get_end = vf->put_ptr ; + char* src_end ; + + if (src_lump != src->end_lump) + src_end = src_lump->end ; else - vf->get_end = head->end ; - - /* Update vf->rdr_ptr and vf->rdr_lump. */ - if (vf->rdr_lump == lump) - { - vf->rdr_lump = head ; - vf->rdr_ptr = head->data ; - } ; - } - else - { - /* Released the tail. - * Update the vf->put_ptr and vf->put_end - */ - vf->put_ptr = vf->put_end = tail->end ; - - /* Update vf->rdr_ptr and vf->rdr_lump. */ - if (vf->rdr_lump == lump) - { - vf->rdr_lump = tail ; - vf->rdr_ptr = tail->end ; - } ; - } ; - } ; + src_end = (src->end_mark) ? src->end_end : src->put_ptr ; + + vio_fifo_put_bytes(dst, src_ptr, src_end - src_ptr) ; - /* Finally, free any lump that is actually to be freed */ + if (src_lump == src->end_lump) + break ; - if (free != NULL) - XFREE(MTYPE_VIO_FIFO_LUMP, free) ; + src_lump = ddl_next(src_lump, list) ; + src_ptr = src_lump->data ; + } ; + } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + return dst ; } ; /*------------------------------------------------------------------------------ - * Re-allocate lump for putting into. + * Copy tail of src fifo (everything from end_mark to put_ptr) to dst fifo. * - * Call when vf->put_ptr == start of last lump, and that lump is not big - * enough ! + * Create a dst fifo if there isn't one. * - * There must be at least one lump. + * Appends to the dst fifo. * - * Updates put_ptr/put_end pointers to point at the new lump. - * - * Updates get_ptr/get_end pointers if required. - * - * Updates rdr_ptr if required. + * Does not change the src fifo in any way. */ -static void -vio_fifo_lump_renew(vio_fifo vf, vio_fifo_lump lump, size_t size) +extern vio_fifo +vio_fifo_copy_tail(vio_fifo dst, vio_fifo src) { - bool rdr_set ; + if (dst == NULL) + dst = vio_fifo_init_new(dst, 0) ; - VIO_FIFO_DEBUG_VERIFY(vf) ; - - /* FIFO may not be completely empty. - * This must be the last lump. - * The last lump must be empty. - */ - assert((lump != NULL) && (lump == ddl_tail(vf->base))) ; + if ((src != 0) && (src->end_mark) && (src->set)) + { + vio_fifo_lump src_lump ; + char* src_ptr ; + vio_fifo_lump tail ; - /* Remove the last, *empty* lump, and update all pointers to suit. */ - rdr_set = (vf->rdr_lump == lump) ; + src_lump = src->end_lump ; + src_ptr = src->end_end ; + tail = ddl_tail(src->base) ; - vio_fifo_lump_release(vf, lump) ; + while (1) + { + char* src_end ; - /* Now allocate a new lump with the required size */ - vio_fifo_lump_new(vf, size) ; + if (src_lump != tail) + src_end = src_lump->end ; + else + src_end = src->put_ptr ; - /* Restore the rdr_ptr, if required */ - if (rdr_set) - { - vio_fifo_lump tail ; + vio_fifo_put_bytes(dst, src_ptr, src_end - src_ptr) ; - tail = ddl_tail(vf->base) ; + if (src_lump == tail) + break ; - vf->rdr_lump = tail ; - vf->rdr_ptr = tail->data ; + src_lump = ddl_next(src_lump, list) ; + src_ptr = src_lump->data ; + } ; } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + return dst ; } ; /*============================================================================== - * Put data to the FIFO. + * End Mark Operations. */ /*------------------------------------------------------------------------------ - * Store 'n' bytes -- allocate new lump if current is exhausted. + * Set end_mark at the current put position. + * + * If there was an end_mark before, move it (forward) to the current put_ptr, + * which keeps everything in between in the FIFO. + * + * Set the get_end to the new reality. */ extern void -vio_fifo_put(vio_fifo vf, const char* src, size_t n) +vio_fifo_set_end_mark(vio_fifo vff) { - size_t take ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; - - while (n > 0) + if (vff->set) { - if (vf->put_ptr >= vf->put_end) - vio_fifo_lump_new(vf, vf->size) ; /* traps put_ptr > put_end */ - - take = (vf->put_end - vf->put_ptr) ; - if (take > n) - take = n ; + vio_fifo_sync_get(vff) ; /* in case is currently empty */ - memcpy(vf->put_ptr, src, take) ; - vf->put_ptr += take ; + vff->end_lump = ddl_tail(vff->base) ; + vff->end_end = vff->put_ptr ; - src += take ; - n -= take ; + vff->get_end = (vff->get_lump == vff->end_lump) ? vff->end_end + : vff->get_lump->end ; } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + vff->as_one = false ; /* not as_one with end_mark */ + vff->end_mark = true ; + + VIO_FIFO_DEBUG_VERIFY(vff) ; } ; /*------------------------------------------------------------------------------ - * Formatted print to fifo -- cf printf() + * If there is an end mark, advance it to the put_ptr. * - * Returns: >= 0 -- number of bytes written - * < 0 -- failed (unlikely though that is) + * If there was an end_mark before, move it (forward) to the current put_ptr, + * which keeps everything in between in the FIFO. + * + * If there was no end_mark before, do nothing. + * + * Set the get_end to the new reality. */ -extern int -vio_fifo_printf(vio_fifo vf, const char* format, ...) +extern void +vio_fifo_step_end_mark(vio_fifo vff) { - va_list args; - int len ; - - va_start (args, format); - len = vio_fifo_vprintf(vf, format, args); - va_end (args); - - return len; + if (vff->end_mark) + vio_fifo_set_end_mark(vff) ; } ; /*------------------------------------------------------------------------------ - * Formatted print to fifo -- cf vprintf() + * If there is an end_mark, clear it -- everything between end mark and + * current put_ptr is kept in the FIFO. * - * Returns: >= 0 -- number of bytes written - * < 0 -- failed (unlikely though that is) + * Set the get_end to the new reality. */ -extern int -vio_fifo_vprintf(vio_fifo vf, const char *format, va_list args) +extern void +vio_fifo_clear_end_mark(vio_fifo vff) { - va_list ac ; - int len ; - int have ; - size_t size ; - vio_fifo_lump lump ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; - - size = vf->size ; /* standard allocation size */ - while (1) + if (vff->end_mark) { - /* Find what space is left in the tail lump, and allocate a new, - * empty lump if required. - */ - if (vf->put_ptr >= vf->put_end) - vio_fifo_lump_new(vf, size) ; /* traps put_ptr > put_end */ - - have = vf->put_end - vf->put_ptr ; - assert(have > 0) ; - - /* Note that vsnprintf() returns the length of what it would like to - * have produced, if it had the space. That length does not include - * the trailing '\0'. - */ - va_copy(ac, args) ; - len = vsnprintf(vf->put_ptr, have, format, ac) ; - va_end(ac) ; + vff->end_mark = false ; - if (len < have) + if (vff->set) { - if (len < 0) - break ; /* quit if failed */ - - vf->put_ptr += len ; - break ; /* done */ - } ; - - /* Not able to complete the operation in the current buffer. - * - * If the required space (len + 1) is greater than the standard - * allocation, then need to increase the allocation for the next lump. - * - * If the current lump is empty, need to renew it with a fresh lump of - * the now known required size. - * - * If the current lump is not empty, need to cut the end off and then - * allocate a fresh lump (of the standard or now known required size). - */ - if (len >= (int)size) - size = len + 1 ; /* need a non-standard size */ + vff->end_lump = ddl_tail(vff->base) ; + vff->end_end = NULL ; - lump = ddl_tail(vf->base) ; + vff->as_one = (vff->get_lump == vff->end_lump) ; + /* since now no end_mark */ + if (!vff->as_one) + vff->get_end = vff->get_lump->end ; + /* would have been end_end */ - if (vf->put_ptr == lump->data) - /* Need to replace the last, empty, lump with another empty lump, but - * big enough. - */ - vio_fifo_lump_renew(vf, lump, size) ; - else - /* Need to cut this lump short, and allocate new lump at top of loop. - */ - lump->end = vf->put_end = vf->put_ptr ; + vio_fifo_sync_get(vff) ; /* sets get_end if as_one and + tidies up if now empty (!) */ + } ; } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; - - return len ; + VIO_FIFO_DEBUG_VERIFY(vff) ; } ; -/*============================================================================== - * Get data from the FIFO. - */ - -static bool vio_fifo_get_next_lump(vio_fifo vf) ; - /*------------------------------------------------------------------------------ - * Get ready to read something out of the FIFO. + * Move put_ptr back to the end mark, if any, and discard data. * - * Makes sure vf->get_end is up to date (if required) and if the FIFO is not - * empty, makes sure vf->get_ptr points at the next byte to be read. + * If there is an end_mark, keep it if required. * - * Returns: true <=> there is something in the FIFO. + * If there is no end mark, do nothing. */ -static inline bool -vio_fifo_get_ready(vio_fifo vf) +extern void +vio_fifo_back_to_end_mark(vio_fifo vff, bool keep) { - assert(vf->rdr_lump == NULL) ; + if (vff->end_mark) + { + if (vff->set) + { + vio_fifo_release_tail(vff, vff->end_lump) ; - if (vf->one) - vf->get_end = vf->put_ptr ; /* make sure have everything */ + vff->put_ptr = vff->end_end ; + vff->put_end = vio_fifo_true_lump_size(vff->end_lump) ; - if (vf->get_ptr >= vf->get_end) - if (!vio_fifo_get_next_lump(vf)) - return 0 ; /* quit now if nothing there */ + /* If retaining the existing end_mark, we retain the end_end and + * the current as_one (false). + * + * Otherwise... + */ + if (!keep) + { + vff->end_mark = false ; + vff->end_end = NULL ; + vff->as_one = (vff->get_lump == vff->end_lump) ; + } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + vio_fifo_sync_get(vff) ; /* in case now empty */ - return 1 ; + VIO_FIFO_DEBUG_VERIFY(vff) ; + } + else + vff->end_mark = keep ; + } ; } ; +/*============================================================================== + * Get data from the FIFO. + */ + /*------------------------------------------------------------------------------ * Get upto 'n' bytes. * * Returns: number of bytes got -- may be zero. */ extern size_t -vio_fifo_get(vio_fifo vf, void* dst, size_t n) +vio_fifo_get_bytes(vio_fifo vff, void* dst, size_t n) { size_t have ; void* dst_in ; - if (!vio_fifo_get_ready(vf)) - return 0 ; /* quit now if nothing there */ + VIO_FIFO_DEBUG_VERIFY(vff) ; dst_in = dst ; - while (n > 0) + while (vio_fifo_sync_get(vff) && (n > 0)) { - have = vf->get_end - vf->get_ptr ; + have = vff->get_end - vff->get_ptr ; if (have > n) have = n ; - memcpy(dst, vf->get_ptr, have) ; - vf->get_ptr += have ; + memcpy(dst, vff->get_ptr, have) ; + vff->get_ptr += have ; dst = (char*)dst + have ; - if (vf->get_ptr >= vf->get_end) /* deal with exhausted lump */ - if (!vio_fifo_get_next_lump(vf)) - break ; /* quit if nothing more to come */ - n -= have ; } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; return (char*)dst - (char*)dst_in ; } ; @@ -752,76 +1269,84 @@ vio_fifo_get(vio_fifo vf, void* dst, size_t n) * Returns: 0x00..0xFF -- byte value (as an int) * -1 => FIFO is empty. */ - -extern int -vio_fifo_get_next_byte(vio_fifo vf) +Private int +vio_fifo_get_next_byte(vio_fifo vff) { - unsigned char u ; - - if (!vio_fifo_get_ready(vf)) - return -1 ; /* quit now if nothing there */ - - u = *vf->get_ptr++ ; - - /* As soon as reach the end want either to discard empty lump, or reset - * the pointers. - */ - if (vf->get_ptr >= vf->get_end) /* deal with exhausted lump */ - vio_fifo_get_next_lump(vf) ; + if (vio_fifo_sync_get(vff)) + return (uchar)*vff->get_ptr++ ; - VIO_FIFO_DEBUG_VERIFY(vf) ; - - return u ; + return -1 ; } ; /*------------------------------------------------------------------------------ - * Get pointer to a lump of bytes. + * Get pointer to as many bytes as are available in the current lump (or next + * lump if nothing available in the current). * * Returns: address of next byte to get, *p_have = number of bytes available * or: NULL => FIFO is empty, *p_have = 0 * - * If the FIFO is not empty, will return pointer to at least one byte. - * - * Returns number of bytes to the end of the current lump. There may be - * further lumps beyond the current one. + * If the FIFO is not empty and not at the end_mark, will return pointer to at + * least one byte. There may be more bytes to get in further lumps. */ extern void* -vio_fifo_get_lump(vio_fifo vf, size_t* p_have) +vio_fifo_get(vio_fifo vff, size_t* p_have) { - if (!vio_fifo_get_ready(vf)) + if (vio_fifo_sync_get(vff)) { - *p_have = 0 ; - return NULL ; + *p_have = (vff->get_end - vff->get_ptr) ; + return vff->get_ptr ; } ; - *p_have = (vf->get_end - vf->get_ptr) ; - return vf->get_ptr ; + *p_have = 0 ; + return NULL ; } ; /*------------------------------------------------------------------------------ - * Advance FIFO to position reached. + * Step FIFO past bytes used. * - * Having done vio_fifo_get_lump(), can take any number of bytes (up to the - * number that "have"), then call this function to advance the pointers. + * Can be called after a vio_fifo_get() or vio_fifo_step_get(). * - * The "here" argument must the the address returned by vio_fifo_get_lump() - * plus the number of bytes taken. + * NB: the "step" argument MUST not exceed the "have" previously returned. */ extern void -vio_fifo_got_upto(vio_fifo vf, void* here) +vio_fifo_step(vio_fifo vff, size_t step) +{ + vff->get_ptr += step ; + vio_fifo_sync_get(vff) ; /* ensure up to date with that */ + + VIO_FIFO_DEBUG_VERIFY(vff) ; +} ; + +/*------------------------------------------------------------------------------ + * Step FIFO past bytes used, the get pointer to as many bytes as are available + * in the current lump (or next lump if nothing available in the current). + * + * Same as vio_fifo_step() followed by vio_fifo_get(). + * + * Can be called after a vio_fifo_get() or vio_fifo_step_get(). + * + * NB: the "step" argument MUST not exceed the "have" previously returned. + */ +extern void* +vio_fifo_step_get(vio_fifo vff, size_t* p_have, size_t step) { - vf->get_ptr = here ; + vff->get_ptr += step ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + if (vio_fifo_sync_get(vff)) + { + *p_have = (vff->get_end - vff->get_ptr) ; + return vff->get_ptr ; + } ; - if (vf->get_ptr >= vf->get_end) - vio_fifo_get_next_lump(vf) ; + *p_have = 0 ; + return NULL ; } ; /*------------------------------------------------------------------------------ * Write contents of FIFO -- assuming non-blocking file * - * Will write all of FIFO, or upto but excluding the last lump. + * Will write all of FIFO up to end mark or put_ptr, or upto but excluding + * the last lump. * * Returns: > 0 => blocked * 0 => all gone (up to last lump if !all) @@ -831,338 +1356,314 @@ vio_fifo_got_upto(vio_fifo vf, void* here) * never return EAGAIN/EWOULDBLOCK, so will return from here "all gone". */ extern int -vio_fifo_write_nb(vio_fifo vf, int fd, bool all) +vio_fifo_write_nb(vio_fifo vff, int fd, bool all) { char* src ; size_t have ; - int done ; - while ((src = vio_fifo_get_lump(vf, &have)) != NULL) + while ((src = vio_fifo_get(vff, &have)) != NULL) { - if (!all && vf->one) - break ; /* don't write last lump */ + int done ; + + if ((vff->get_lump == vff->end_lump) && !all) + break ; /* don't write last lump */ done = write_nb(fd, src, have) ; if (done < 0) - return -1 ; /* failed */ + return -1 ; /* failed */ - vio_fifo_got_upto(vf, src + done) ; + vio_fifo_step(vff, done) ; if (done < (int)have) - return 1 ; /* blocked */ + return 1 ; /* blocked */ } ; - return 0 ; /* all gone */ -} ; - -/*------------------------------------------------------------------------------ - * Get the current rdr_end value. - * - * Unlike get_end, do not have a field for this, but find it each time. - */ -inline static char* -vio_fifo_rdr_end(vio_fifo vf) -{ - if (vf->rdr_lump == ddl_tail(vf->base)) - return vf->put_ptr ; - else - return vf->rdr_lump->end ; + return 0 ; /* all gone */ } ; /*------------------------------------------------------------------------------ - * Get the current rdr position -- sets it up if not currently set. - * - * Returns: address of next byte to get, *p_have = number of bytes available - * or: NULL => FIFO is empty, *p_have = 0 + * Write contents of FIFO -- assuming blocking file * - * If the FIFO is not empty, will return pointer to at least one byte. + * Will write all of FIFO up to end mark or put_ptr. * - * Returns number of bytes to the end of the current lump. There may be - * further lumps beyond the current one. + * Returns: 0 => all gone + * < 0 => failed -- see errno * - * NB: unless returns FIFO is empty, it is a mistake to now do any "get" - * operation other than vio_fifo_step_rdr(), until do vio_fifo_sync_rdr() - * or vio_fifo_drop_rdr. + * Note: will work perfectly well for a non-blocking file -- which should + * never return EAGAIN/EWOULDBLOCK, so will return from here "all gone". */ -extern void* -vio_fifo_get_rdr(vio_fifo vf, size_t* p_have) +extern int +vio_fifo_fwrite(vio_fifo vff, FILE* file) { - if (!vio_fifo_get_ready(vf)) - { - *p_have = 0 ; - return NULL ; - } ; + char* src ; + size_t have ; - if (vf->rdr_lump == NULL) /* set up new rdr if required */ + while ((src = vio_fifo_get(vff, &have)) != NULL) { - vf->rdr_lump = ddl_head(vf->base) ; - vf->rdr_ptr = vf->get_ptr ; - } ; + size_t done ; + + done = fwrite(src, have, 1, file) ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + if (done < 1) + return -1 ; /* failed */ - *p_have = vio_fifo_rdr_end(vf) - vf->rdr_ptr ; - return vf->rdr_ptr ; + vio_fifo_step(vff, have) ; + } ; + + return 0 ; /* all gone */ } ; /*------------------------------------------------------------------------------ - * Step the rdr forward by the given number of bytes. - * - * Returns: address of next byte to get, *p_have = number of bytes available - * or: NULL => FIFO is empty, *p_have = 0 - * - * If the FIFO is not empty, will return pointer to at least one byte. - * - * Returns number of bytes to the end of the current lump. There may be - * further lumps beyond the current one. - * - * NB: this does not change the get pointers, so all the data being stepped - * over is preserved in the FIFO, until vio_fifo_sync_rdr(). + * Skip get_ptr to the current end -- which may be the current end_mark. * - * NB: the step may NOT exceed the last reported "have". + * Does not clear any hold_mark or end_mark. */ -extern void* -vio_fifo_step_rdr(vio_fifo vf, size_t* p_have, size_t step) +extern void +vio_fifo_skip_to_end(vio_fifo vff) { - char* rdr_end ; + vio_fifo_sync_get(vff) ; /* ensure all straight */ - assert(vf->rdr_lump != NULL) ; + /* Setting the get_ptr to the start of the end_lump does the bulk + * of the work -- then just skip to the get_end which that sets. + */ + vio_fifo_set_get_ptr(vff, vff->end_lump->data, vff->end_lump) ; + vff->get_ptr = vff->get_end ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + vio_fifo_sync_get(vff) ; /* crunch */ +} ; - rdr_end = vio_fifo_rdr_end(vf) ; - vf->rdr_ptr += step ; +/*============================================================================== + * Hold Mark Operations. + */ - if (vf->rdr_ptr >= rdr_end) +/*------------------------------------------------------------------------------ + * If there is a hold_mark, clear it -- discard all contents up to the + * current get_ptr. + * + * Set hold_mark at the current get_ptr. + */ +extern void +vio_fifo_set_hold_mark(vio_fifo vff) +{ + if (vff->set) { - assert(vf->rdr_ptr == rdr_end) ; - - if (vf->rdr_lump != ddl_tail(vf->base)) - { - vf->rdr_lump = ddl_next(vf->rdr_lump, list) ; - vf->rdr_ptr = vf->rdr_lump->data ; + if (vff->hold_mark) + vio_fifo_clear_hold_mark(vff) ; /* clear existing mark & sync */ + else + vio_fifo_sync_get(vff) ; /* ensure all straight */ - rdr_end = vio_fifo_rdr_end(vf) ; - } ; + vff->hold_ptr = vff->get_ptr ; } ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + vff->hold_mark = true ; - *p_have = (rdr_end - vf->rdr_ptr) ; - return (*p_have > 0) ? vf->rdr_ptr : NULL ; + VIO_FIFO_DEBUG_VERIFY(vff) ; } ; /*------------------------------------------------------------------------------ - * Move FIFO get position to the rdr position, if any. + * If there is a hold_mark, clear it -- discard all contents up to the + * current get_ptr. * - * This clears the rdr position, and removes all data between the current and - * new get positions from the FIFO. + * The get_ptr is synchronised. */ extern void -vio_fifo_sync_rdr(vio_fifo vf) +vio_fifo_clear_hold_mark(vio_fifo vff) { - vio_fifo_lump head ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; - - if (vf->rdr_lump == NULL) - return ; + /* Make sure all is up to date, and in particular that the get_ptr + * is not sitting at the end of a lump when there is a following lump. + */ + vio_fifo_sync_get(vff) ; - while ((head = ddl_head(vf->base)) != vf->rdr_lump) + if ((vff->hold_mark) && (vff->set)) { - vf->get_ptr = vf->get_end ; /* jump to end of lump */ - vio_fifo_lump_release(vf, head) ; - } ; - - vf->get_ptr = vf->rdr_ptr ; /* jump to rdr_ptr */ + /* Release everything upto but not including the current get_lump. + * + * This has no effect on the get_ptr etc. so they remain straight. + */ + vio_fifo_release_head(vff, vff->get_lump) ; - vf->rdr_lump = NULL ; /* clear the rdr */ - vf->rdr_ptr = NULL ; + vff->hold_ptr = NULL ; + } ; - if (vf->one) - { - if (vf->put_ptr == vf->get_ptr) /* reset pointers if FIFO empty */ - vio_fifo_ptr_reset(vf, head) ; - else - vf->get_end = vf->put_ptr ; - } - else - vf->get_end = head->end ; + vff->hold_mark = false ; - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; } ; /*------------------------------------------------------------------------------ - * Drop the rdr position (if any). + * If there is an hold_mark, reset get_ptr *back* to it. * - * This clears the rdr position leaving the get position and FIFO unchanged. + * Leave hold mark set or clear. */ extern void -vio_fifo_drop_rdr(vio_fifo vf) -{ - VIO_FIFO_DEBUG_VERIFY(vf) ; - - vf->rdr_lump = NULL ; - vf->rdr_ptr = NULL ; -} ; - -/*------------------------------------------------------------------------------ - * Move on to next lump to get stuff from. - * - * Advance pointers etc. so that have at least one byte available, unless - * the FIFO is entirely empty. - * - * This should be called if (vf->get_ptr >= vf->get_end) -- asserts that - * these are equal ! - * - * NB: when there is only one block, it may be that get_end is out of date, - * and should be advanced to the current put_ptr position. - * - * That is done here, but may be worth updating get_end before testing - * against get_ptr. - * - * Returns: true <=> at least one byte in FIFO. - * - * NB: if finds that the FIFO is empty, resets the pointers to the start - * of the last lump -- does not release the last lump. - */ -static bool -vio_fifo_get_next_lump(vio_fifo vf) +vio_fifo_back_to_hold_mark(vio_fifo vff, bool mark) { - vio_fifo_lump head ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; - assert(vf->get_ptr == vf->get_end) ; - - head = ddl_head(vf->base) ; /* current lump for get */ - - /* Deal with the simple case of one lump, first. - * - * To save work when putting data into the FIFO (particularly when putting - * a byte at a time) does not keep the vf->get_end up to date (when there is - * only one lump). - * - * If the FIFO is empty, reset pointers and return empty. - */ - if (vf->one) + if (vff->hold_mark) { - assert( (head != NULL) && (head == ddl_tail(vf->base)) ) ; - - if (vf->get_ptr < vf->put_ptr) + if (vff->set) { - /* Had an out of date vf->get_end */ - vf->get_end = vf->put_ptr ; - - return true ; /* FIFO not empty */ - } ; - - assert(vf->get_ptr == vf->put_ptr) ; + vio_fifo_set_get_ptr(vff, vff->hold_ptr, ddl_head(vff->base)) ; + /* Set back to hold position */ - /* FIFO is empty -- reset pointers and exit */ - vio_fifo_ptr_reset(vf, head) ; - - return false ; /* FIFO empty */ - } ; + vff->end_mark = mark ; /* new state */ + if (!mark) + vff->hold_ptr = NULL ; /* clear if required */ - /* Release the head and update pointers - * - * Deals with possibility that nothing has yet been allocated - */ - vio_fifo_lump_release(vf, head) ; + vio_fifo_sync_get(vff) ; /* to be absolutely sure ! */ + } ; - return (vf->get_ptr < vf->get_end) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; + } + else if (mark) + vio_fifo_set_end_mark(vff) ; } ; /*============================================================================== * For debug purposes -- verify the state of the given FIFO */ Private void -vio_fifo_verify(vio_fifo vf) +vio_fifo_verify(vio_fifo vff) { vio_fifo_lump head ; vio_fifo_lump lump ; vio_fifo_lump tail ; - head = ddl_head(vf->base) ; - tail = ddl_tail(vf->base) ; + head = ddl_head(vff->base) ; + tail = ddl_tail(vff->base) ; - /* If nothing allocated, should all be NULL & !vf->one */ + /* If nothing allocated, should all be NULL & !vff->set */ /* If something allocated, tail must not be NULL */ if (head == NULL) { if ( (tail != NULL) - || (vf->put_ptr != NULL) - || (vf->put_end != NULL) - || (vf->get_ptr != NULL) - || (vf->rdr_lump != NULL) - || (vf->rdr_ptr != NULL) - || (vf->one) ) + || (vff->set) + || (vff->as_one) + || (vff->hold_ptr != NULL) + || (vff->get_lump != NULL) + || (vff->get_ptr != NULL) + || (vff->get_end != NULL) + || (vff->end_lump != NULL) + || (vff->end_end != NULL) + || (vff->put_ptr != NULL) + || (vff->put_end != NULL) ) zabort("nothing allocated, but not all NULL") ; return ; } else { if (tail == NULL) - zabort("head pointer not NULL, but tail pointer is") ; + zabort("head not NULL, but tail is") ; } ; - /* Check that all the pointers are within respective lumps + /* Must now be set ! */ + if (!vff->set) + zabort("head not NULL, but set is false") ; + + /* Make sure that the lump pointers all work * - * Know that put_end is always tail->end, but get_end need not be. + * When finished, know that head <= get_lump <= end_lump <= tail. */ - if ( (tail->data > vf->put_ptr) - || (vf->put_ptr > vf->put_end) - || (vf->put_end != tail->end) ) - zabort("put pointers outside the tail lump") ; + lump = head ; + while (lump != vff->get_lump) + { + lump = ddl_next(lump, list) ; + if (lump == NULL) + zabort("ran out of lumps looking for get_lump") ; + } ; + + while (lump != vff->end_lump) + { + lump = ddl_next(lump, list) ; + if (lump == NULL) + zabort("ran out of lumps looking for end_lump") ; + } ; - if ( (head->data > vf->get_ptr) - || (vf->get_ptr > vf->get_end) - || (vf->get_end > head->end) ) - zabort("get pointers outside the head lump") ; + while (lump != tail) + { + lump = ddl_next(lump, list) ; + if (lump == NULL) + zabort("ran out of lumps looking for tail") ; + } ; - /* If head == tail, should be vf->one, etc. */ - if (head == tail) + /* Check that all the pointers are within respective lumps + * + * Know that put_end is always tail->end, but get_end need not be. + * + * When finished, know that: + * + * - get_lump == head if !hold_mark + * - end_lump == tail if !end_mark + * - that all pointers are within their respective lumps + * - all ptr are <= their respective ends + * - if hold_mark: hold_ptr <= get_ptr or head != get_lump + * - if end_mark: end_end <= put_ptr or tail != end_lump + */ + if (vff->hold_mark) { - if (!vf->one) - zabort("have one lump, but !vf->one") ; + if ( (head->data > vff->hold_ptr) + || (vff->hold_ptr > head->end) ) + zabort("hold pointer outside the head lump") ; - if (vf->get_end > vf->put_ptr) - zabort("get_end is greater than put_ptr when vf->one") ; + if ((vff->get_lump == head) && (vff->hold_ptr > vff->get_ptr)) + zabort("hold pointer greater than get pointer") ; } else { - if (vf->one) - zabort("have two or more lumps, but vf->one is true") ; - - if (vf->get_end != head->end) - zabort("get_end is not head->end when !vf->one") ; + if (vff->hold_ptr != NULL) + zabort("no hold_mark, but hold pointer not NULL") ; + if (vff->get_lump != head) + zabort("no hold_mark, but get_lump is not head") ; } ; - /* If have an rdr_lump -- make sure everything else is valid */ - if (vf->rdr_lump != NULL) + if ( (vff->get_lump->data > vff->get_ptr) + || (vff->get_ptr > vff->get_end) + || (vff->get_end > vff->get_lump->end)) + zabort("get pointers outside the get lump") ; + + if (vff->end_mark) { - lump = head ; - while (lump != vf->rdr_lump) - { - if (lump == tail) - zabort("rdr_lump is not part of FIFO") ; - lump = ddl_next(lump, list) ; - } ; + if ( (vff->end_lump->data > vff->end_end) + || (vff->end_end > vff->end_lump->end) ) + zabort("end pointer outside the end lump") ; - if ( (lump->data > vf->rdr_ptr) - || (vf->rdr_ptr > lump->end) ) - zabort("rdr_ptr outside its lump") ; + if ((vff->end_lump == tail) && (vff->end_end > vff->put_ptr)) + zabort("end pointer greater than put pointer") ; + } + else + { + if (vff->end_end != NULL) + zabort("no end_mark, but end end not NULL") ; + if (vff->end_lump != tail) + zabort("no end_mark, but end_lump is not tail") ; + } ; - if ( (lump == head) && (vf->rdr_ptr < vf->get_ptr)) - zabort("rdr_ptr is less than get_ptr in first lump") ; + if ( (tail->data > vff->put_ptr) + || (vff->put_ptr > vff->put_end) + || (vff->put_end != tail->end) ) + zabort("put pointers outside the tail lump") ; - if ( (lump == tail) && (vf->rdr_ptr > vf->put_ptr)) - zabort("rdr_ptr is greater than put_ptr in last lump") ; + /* The as_one state & get_end + */ + if (vff->get_lump != vff->end_lump) + { + if (vff->as_one) + zabort("get_lump != end_lump, but as_one true") ; + if (vff->get_end != vff->get_lump->end) + zabort("get_lump != end_lump, but get_end != get_lump->end") ; } - else + else if (vff->end_mark) { - if (vf->rdr_ptr != NULL) - zabort("rdr_ptr not NULL when rdr_lump is") ; + if (vff->as_one) + zabort("end_mark true, but as_one also true") ; + if (vff->get_end != vff->end_end) + zabort("get_lump == end_lump and end_mark, but get_end != end_end") ; } + else + { + if (!vff->as_one) + zabort("get_lump == end_lump and !end_mark, but as_one not true") ; + if (vff->get_end > vff->put_ptr) + zabort("is as_one, but get_end > put_ptr") ; + } ; } ; diff --git a/lib/vio_fifo.h b/lib/vio_fifo.h index 08af4590..1b7b05d3 100644 --- a/lib/vio_fifo.h +++ b/lib/vio_fifo.h @@ -22,37 +22,35 @@ #ifndef _ZEBRA_VIO_FIFO_H #define _ZEBRA_VIO_FIFO_H -#include "zebra.h" #include "misc.h" +#include "vargs.h" +#include #include "list_util.h" -#include "zassert.h" - -/* GCC have printf type attribute check. */ -#ifdef __GNUC__ -#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) -#else -#define PRINTF_ATTRIBUTE(a,b) -#endif /* __GNUC__ */ /*============================================================================== * VTY I/O FIFO -- buffering of arbitrary amounts of I/O. */ -#ifdef NDEBUG -# define VIO_FIFO_DEBUG 0 /* NDEBUG override */ +#ifdef VIO_FIFO_DEBUG /* Can be forced from outside */ +# if VIO_FIFO_DEBUG +# define VIO_FIFO_DEBUG 1 /* Force 1 or 0 */ +#else +# define VIO_FIFO_DEBUG 0 +# endif +#else +# ifdef QDEBUG +# define VIO_FIFO_DEBUG 1 /* Follow QDEBUG */ #else -# ifndef VIO_FIFO_DEBUG -# define VIO_FIFO_DEBUG 1 /* Set to 1 to turn on debug checks */ +# define VIO_FIFO_DEBUG 0 # endif #endif +enum { vio_fifo_debug = VIO_FIFO_DEBUG } ; + /*============================================================================== * Data Structures */ -typedef struct vio_fifo vio_fifo_t ; -typedef struct vio_fifo* vio_fifo ; - typedef struct vio_fifo_lump vio_fifo_lump_t ; typedef struct vio_fifo_lump* vio_fifo_lump ; @@ -60,22 +58,46 @@ struct vio_fifo { struct dl_base_pair(vio_fifo_lump) base ; - bool one ; + bool set ; /* have at least one lump */ - char* put_ptr ; - char* put_end ; + bool as_one ; /* get_lump == tail && !end_mark + => get_end may not be up to date */ + bool hold_mark ; /* hold stuff while getting */ + bool end_mark ; /* do not get beyond end */ + + char* hold_ptr ; /* implicitly in the head lump */ + /* used only if "hold_mark" */ + + vio_fifo_lump get_lump ; /* head lump unless "hold_mark" */ char* get_ptr ; char* get_end ; - vio_fifo_lump rdr_lump ; - char* rdr_ptr ; + vio_fifo_lump end_lump ; /* tail lump unless "end_mark" */ + char* end_end ; /* used only if "end_mark" */ + + char* put_ptr ; /* implicitly in the tail lump */ + char* put_end ; size_t size ; vio_fifo_lump spare ; } ; +typedef struct vio_fifo vio_fifo_t[1] ; /* embedded */ +typedef struct vio_fifo* vio_fifo ; + +/* Setting a FIFO object to all zeros is enough to initialise it to an + * empty FIFO (with default lump sizes). + */ +enum +{ + VIO_FIFO_INIT_ALL_ZEROS = true, + VIO_FIFO_DEFAULT_LUMP_SIZE = 4 * 1024 +} ; + +#define VIO_FIFO_INIT_EMPTY { 0 } + struct vio_fifo_lump { struct dl_list_pair(vio_fifo_lump) list ; @@ -89,76 +111,114 @@ struct vio_fifo_lump * Functions */ -extern vio_fifo vio_fifo_init_new(vio_fifo vf, size_t size) ; -extern vio_fifo vio_fifo_reset(vio_fifo vf, int free_structure) ; +extern vio_fifo vio_fifo_init_new(vio_fifo vff, size_t size) ; +extern vio_fifo vio_fifo_reset(vio_fifo vff, free_keep_b free_structure) ; -#define vio_fifo_reset_keep(vf) vio_fifo_reset(vf, 0) -#define vio_fifo_reset_free(vf) vio_fifo_reset(vf, 1) +extern void vio_fifo_clear(vio_fifo vff, bool clear_marks) ; +Inline bool vio_fifo_empty(vio_fifo vff) ; +extern size_t vio_fifo_room(vio_fifo vff) ; -extern void vio_fifo_clear(vio_fifo vf) ; -Inline bool vio_fifo_empty(vio_fifo vf) ; -extern size_t vio_fifo_room(vio_fifo vf) ; +extern void vio_fifo_put_bytes(vio_fifo vff, const char* src, size_t n) ; +Inline void vio_fifo_put_byte(vio_fifo vff, char b) ; -extern void vio_fifo_put(vio_fifo vf, const char* src, size_t n) ; -Inline void vio_fifo_put_byte(vio_fifo vf, char b) ; - -extern int vio_fifo_printf(vio_fifo vf, const char* format, ...) +extern int vio_fifo_printf(vio_fifo vff, const char* format, ...) PRINTF_ATTRIBUTE(2, 3) ; -extern int vio_fifo_vprintf(vio_fifo vf, const char *format, va_list args) ; - -extern size_t vio_fifo_get(vio_fifo vf, void* dst, size_t n) ; -Inline int vio_fifo_get_byte(vio_fifo vf) ; -extern void* vio_fifo_get_lump(vio_fifo vf, size_t* have) ; -extern void vio_fifo_got_upto(vio_fifo vf, void* here) ; - -Inline bool vio_fifo_full_lump(vio_fifo vf) ; -extern int vio_fifo_write_nb(vio_fifo vf, int fd, bool all) ; - -extern void* vio_fifo_get_rdr(vio_fifo vf, size_t* have) ; -extern void* vio_fifo_step_rdr(vio_fifo vf, size_t* have, size_t step) ; -extern void vio_fifo_sync_rdr(vio_fifo vf) ; -extern void vio_fifo_drop_rdr(vio_fifo vf) ; - -Private void vio_fifo_lump_new(vio_fifo vf, size_t size) ; -Private int vio_fifo_get_next_byte(vio_fifo vf) ; +extern int vio_fifo_vprintf(vio_fifo vff, const char *format, va_list args) ; +extern int vio_fifo_read_nb(vio_fifo vff, int fd, size_t request) ; + +extern size_t vio_fifo_get_bytes(vio_fifo vff, void* dst, size_t n) ; +Inline int vio_fifo_get_byte(vio_fifo vff) ; +extern void* vio_fifo_get(vio_fifo vff, size_t* have) ; +extern void vio_fifo_step(vio_fifo vff, size_t step) ; +extern void* vio_fifo_step_get(vio_fifo vff, size_t* p_have, size_t step) ; + +extern vio_fifo vio_fifo_copy(vio_fifo dst, vio_fifo src) ; +extern vio_fifo vio_fifo_copy_tail(vio_fifo dst, vio_fifo src) ; + +Inline bool vio_fifo_full_lump(vio_fifo vff) ; +extern int vio_fifo_write_nb(vio_fifo vff, int fd, bool all) ; +extern int vio_fifo_fwrite(vio_fifo vff, FILE* file) ; + +extern void vio_fifo_skip_to_end(vio_fifo vff) ; +extern void vio_fifo_set_end_mark(vio_fifo vff) ; +extern void vio_fifo_step_end_mark(vio_fifo vff) ; +extern void vio_fifo_clear_end_mark(vio_fifo vff) ; +extern void vio_fifo_back_to_end_mark(vio_fifo vff, bool keep) ; +extern void vio_fifo_set_hold_mark(vio_fifo vff) ; +extern void vio_fifo_clear_hold_mark(vio_fifo vff) ; +extern void vio_fifo_back_to_hold_mark(vio_fifo vff, bool keep) ; /*============================================================================== * Debug -- verification function */ -Private void vio_fifo_verify(vio_fifo vf) ; +Private void vio_fifo_verify(vio_fifo vff) ; #if VIO_FIFO_DEBUG -# define VIO_FIFO_DEBUG_VERIFY(vf) vio_fifo_verify(vf) +# define VIO_FIFO_DEBUG_VERIFY(vff) vio_fifo_verify(vff) #else -# define VIO_FIFO_DEBUG_VERIFY(vf) +# define VIO_FIFO_DEBUG_VERIFY(vff) #endif /*============================================================================== * Inline Functions */ +Private void vio_fifo_lump_new(vio_fifo vff, size_t size) ; +Private int vio_fifo_get_next_byte(vio_fifo vff) ; +Private bool vio_fifo_do_empty(vio_fifo vff) ; + /*------------------------------------------------------------------------------ - * Returns true <=> FIFO is empty + * Returns true <=> FIFO is empty -- at least: get_ptr == end_end (if any) + * or: get_ptr == put_ptr. */ Inline bool -vio_fifo_empty(vio_fifo vf) +vio_fifo_empty(vio_fifo vff) { - return (vf->get_ptr == vf->put_ptr) ; -} + /* if vff is NULL, treat as empty ! + * if !set, then all pointers should be NULL (so get_ptr == put_ptr) + * if !end_mark, then vff->end_end will be NULL (so get_ptr != end_end, + * unless !set) + * Is definitely empty if any of the following is true. + */ + if ((vff == NULL) || (vff->get_ptr == vff->put_ptr) + || (vff->get_ptr == vff->end_end)) + return true ; + + /* If get_ptr < get_end is NOT empty */ + if (vff->get_ptr < vff->get_end) + return false ; + + /* See if can advance get_ptr, and if so whether is then empty. */ + return vio_fifo_do_empty(vff) ; +} ; + +/*------------------------------------------------------------------------------ + * Returns true <=> FIFO is empty beyond end_end (if any). + */ +Inline bool +vio_fifo_empty_tail(vio_fifo vff) +{ + /* if vff is NULL, treat as empty ! + * if !set, then all pointers should be NULL (so end_end == put_ptr) + * if !end_mark, then tail is empty ! + * else tail is empty iff end_end == put_ptr. + */ + return (vff == NULL) || !vff->end_mark || (vff->end_end == vff->put_ptr) ; +} ; /*------------------------------------------------------------------------------ * Put one byte to the FIFO */ Inline void -vio_fifo_put_byte(vio_fifo vf, char b) +vio_fifo_put_byte(vio_fifo vff, char b) { - if (vf->put_ptr >= vf->put_end) - vio_fifo_lump_new(vf, vf->size) ; /* traps put_ptr > put_end */ + if (vff->put_ptr >= vff->put_end) + vio_fifo_lump_new(vff, 0) ; /* traps put_ptr > put_end */ - VIO_FIFO_DEBUG_VERIFY(vf) ; + VIO_FIFO_DEBUG_VERIFY(vff) ; - *vf->put_ptr++ = b ; + *vff->put_ptr++ = b ; } ; /*------------------------------------------------------------------------------ @@ -168,14 +228,12 @@ vio_fifo_put_byte(vio_fifo vf, char b) * -1 => FIFO is empty. */ Inline int -vio_fifo_get_byte(vio_fifo vf) +vio_fifo_get_byte(vio_fifo vff) { - if (vf->get_end <= (vf->get_ptr + 1)) - return vio_fifo_get_next_byte(vf) ; - - VIO_FIFO_DEBUG_VERIFY(vf) ; + if (vff->get_ptr < vff->get_end) + return (uchar)*vff->get_ptr++ ; - return (unsigned char)*vf->get_ptr++ ; + return vio_fifo_get_next_byte(vff) ; } ; /*------------------------------------------------------------------------------ @@ -188,9 +246,9 @@ vio_fifo_get_byte(vio_fifo vf) * (excluding the last lump if it happens to be full) */ Inline bool -vio_fifo_full_lump(vio_fifo vf) +vio_fifo_full_lump(vio_fifo vff) { - return (!vf->one && (vf->put_ptr != NULL)) ; + return (ddl_head(vff->base) != ddl_tail(vff->base)) ; } ; #endif /* _ZEBRA_VIO_FIFO_H */ diff --git a/lib/vio_lines.c b/lib/vio_lines.c index 2ac874d1..a9268fd5 100644 --- a/lib/vio_lines.c +++ b/lib/vio_lines.c @@ -119,7 +119,7 @@ vio_lc_init_new(vio_line_control lc, int width, int height) * Returns: address of vio_line_control (if any) -- NULL if structure released */ extern vio_line_control -vio_lc_reset(vio_line_control lc, bool free_structure) +vio_lc_reset(vio_line_control lc, free_keep_b free_structure) { if (lc != NULL) { diff --git a/lib/vio_lines.h b/lib/vio_lines.h index 7097fe9c..dd5545cc 100644 --- a/lib/vio_lines.h +++ b/lib/vio_lines.h @@ -22,7 +22,6 @@ #ifndef _ZEBRA_VIO_LINES_H #define _ZEBRA_VIO_LINES_H -#include "zebra.h" #include "misc.h" #include "qiovec.h" @@ -35,9 +34,6 @@ * * NB: a completely zero structure is a valid, clear vio_line_control. */ - -typedef struct vio_line_control* vio_line_control ; -typedef struct vio_line_control vio_line_control_t ; struct vio_line_control { unsigned width ; /* console width -- 0 => HUGE */ @@ -54,15 +50,21 @@ struct vio_line_control bool writing ; /* write started, but not completed */ } ; +typedef struct vio_line_control* vio_line_control ; +typedef struct vio_line_control vio_line_control_t[1] ; + +enum +{ + VIO_LINE_CONTROL_INIT_ALL_ZEROS = true +} ; + /*============================================================================== * Functions */ extern vio_line_control vio_lc_init_new(vio_line_control lc, int width, int height) ; -extern vio_line_control vio_lc_reset(vio_line_control lc, bool free_structure) ; - -#define vio_lc_reset_keep(lc) vio_lc_reset(lc, 0) -#define vio_lc_reset_free(lc) vio_lc_reset(lc, 1) +extern vio_line_control vio_lc_reset(vio_line_control lc, + free_keep_b free_structure) ; Inline bool vio_lc_empty(vio_line_control lc) ; extern void vio_lc_clear(vio_line_control lc) ; diff --git a/lib/vty.c b/lib/vty.c index 3c1e62b7..da94b992 100644 --- a/lib/vty.c +++ b/lib/vty.c @@ -1,4 +1,4 @@ -/* VTY top level +/* VTY external interface * Copyright (C) 1997, 98 Kunihiro Ishiguro * * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman @@ -21,36 +21,74 @@ * 02111-1307, USA. */ -#include "zebra.h" +#include "zconfig.h" #include "misc.h" #include "lib/version.h" -#include "vty_io.h" +#include +#include +#include + #include "vty.h" -#include "uty.h" +#include "vty_local.h" +#include "vty_io.h" +#include "vty_command.h" #include "vty_cli.h" +#include "vio_fifo.h" #include "list_util.h" #include "command.h" -#include "command_queue.h" +#include "command_local.h" #include "command_execute.h" +#include "command_parse.h" #include "memory.h" #include "log.h" #include "mqueue.h" +#include "qstring.h" + +/*============================================================================== + * The vty family comprises: + * + * vty -- level visible from outside the vty/command/log family + * and within those families. + * + * vty_common.h -- definitions ... + * vty_local.h + * + * vty_io -- top level of the vio handling + * + * vty_command -- functions called by the command family + * vty_log -- functions called by the log family + * + * vty_cli -- terminal command line handling + * vty_io_term -- terminal (telnet) I/O + * vty_io_vsh -- vtysh I/O + * vty_io_file -- file I/O + * vty_io_shell -- system shell I/O + * + * vty_io_basic -- common low level I/O handling + * encapsulates the differences between qpselect and legacy + * thread/select worlds. + * + * vio_lines -- for terminal: handles width, CRLF, line counting etc. + * vio_fifo -- + * qiovec + * + */ + /*============================================================================== - * Variables etc. (see uty.h) + * Variables etc. (see vty_local.h) */ /* The mutex and related debug counters */ qpt_mutex_t vty_mutex ; -#if VTY_DEBUG - int vty_lock_count = 0 ; -int vty_assert_fail = 0 ; +#if VTY_DEBUG +int vty_assert_fail = 0 ; #endif /* For thread handling -- initialised in vty_init */ @@ -59,7 +97,14 @@ struct thread_master* vty_master = NULL ; /* In the qpthreads world, have nexus for the CLI and one for the Routeing * Engine. Some commands are processed directly in the CLI, most have to * be sent to the Routeing Engine. + * + * If not in the qpthreads world, vty_cli_nexus == vty_cmd_nexus == NULL. + * + * If in the qpthreads world these vty_cli_nexus == vty_cmd_nexus if not + * actually running pthreaded. */ +bool vty_nexus ; /* true <=> in the qpthreads world */ + qpn_nexus vty_cli_nexus = NULL ; qpn_nexus vty_cmd_nexus = NULL ; @@ -72,28 +117,6 @@ vty_io vio_monitors_base = NULL ; /* List of all vty which are on death watch */ vty_io vio_death_watch = NULL ; -/* Vty timeout value -- see "exec timeout" command */ -unsigned long vty_timeout_val = VTY_TIMEOUT_DEFAULT; - -/* Vty access-class command */ -char *vty_accesslist_name = NULL; - -/* Vty access-class for IPv6. */ -char *vty_ipv6_accesslist_name = NULL; - -/* Current directory -- initialised in vty_init() */ -static char *vty_cwd = NULL; - -/* Configure lock -- only one vty may be in CONFIG_NODE or above ! */ -bool vty_config = 0 ; - -/* Login password check override. */ -bool no_password_check = 0; - -/* Restrict unauthenticated logins? */ -const bool restricted_mode_default = 0 ; - bool restricted_mode = 0 ; - /*------------------------------------------------------------------------------ * VTYSH stuff */ @@ -107,9 +130,10 @@ char integrate_default[] = SYSCONFDIR INTEGRATE_DEFAULT_CONFIG ; static void uty_reset (bool final, const char* why) ; static void uty_init_commands (void) ; static void vty_save_cwd (void) ; -static bool vty_terminal (struct vty *); -static bool vty_shell_server (struct vty *); -static bool vty_shell_client (struct vty *); + +//static bool vty_terminal (struct vty *); +//static bool vty_shell_server (struct vty *); +//static bool vty_shell_client (struct vty *); /*------------------------------------------------------------------------------ * Tracking the initialisation state. @@ -143,8 +167,8 @@ static enum vty_init_state vty_init_state ; extern void vty_init (struct thread_master *master_thread) { - VTY_LOCK() ; /* Does nothing if !qpthreads_enabled */ VTY_ASSERT_CLI_THREAD() ; /* True if !qpthreads_enabled */ + VTY_LOCK() ; /* Does nothing if !qpthreads_enabled */ assert(vty_init_state == vty_init_pending) ; @@ -156,7 +180,8 @@ vty_init (struct thread_master *master_thread) vio_monitors_base = NULL ; vio_death_watch = NULL ; - vty_cli_nexus = NULL ; /* not running qnexus-wise */ + vty_nexus = false ; /* not running qnexus-wise */ + vty_cli_nexus = NULL ; vty_cmd_nexus = NULL ; uty_watch_dog_init() ; /* empty watch dog */ @@ -190,6 +215,7 @@ vty_init_r (qpn_nexus cli, qpn_nexus cmd) { assert(vty_init_state == vty_init_1st_stage) ; + vty_nexus = true ; vty_cli_nexus = cli ; vty_cmd_nexus = cmd ; @@ -214,7 +240,7 @@ vty_init_vtysh (void) /*------------------------------------------------------------------------------ * Start the VTY going. * - * This starts the listeners for VTY_TERM and VTY_SHELL_SERV. + * This starts the listeners for VTY_TERMINAL and VTY_SHELL_SERVER. * * Also starts the watch dog. * @@ -222,14 +248,12 @@ vty_init_vtysh (void) * any threads are started -- so is, implicitly, in the CLI thread. * * NB: may be called once and once only. - * - * NB: MUST be in the CLI thread (if any). */ extern void vty_start(const char *addr, unsigned short port, const char *path) { - VTY_LOCK() ; VTY_ASSERT_CLI_THREAD() ; + VTY_LOCK() ; assert( (vty_init_state == vty_init_1st_stage) || (vty_init_state == vty_init_2nd_stage) ) ; @@ -254,10 +278,11 @@ vty_reset() /*------------------------------------------------------------------------------ * Reset all VTY status * - * This is done in response to SIGHUP -- and runs in the CLI thread. + * This is done in response to SIGHUP/SIGINT/SIGTERM -- and runs in the + * CLI thread (if there is one). * - * Half closes all VTY, leaving the death watch to tidy up once all output - * and any command in progress have completed. + * Closes all VTY, leaving the death watch to tidy up once all output and any + * command in progress have completed. * * Closes all listening sockets. * @@ -268,21 +293,24 @@ vty_reset() extern void vty_reset_because(const char* why) { - VTY_LOCK() ; VTY_ASSERT_CLI_THREAD() ; + VTY_LOCK() ; - assert(vty_init_state == vty_init_started) ; + if (vty_init_state != vty_init_reset) + { + assert(vty_init_state == vty_init_started) ; - uty_reset(0, why) ; /* not final ! */ + uty_reset(false, why) ; /* not final ! */ - vty_init_state = vty_init_reset ; + vty_init_state = vty_init_reset ; + } ; VTY_UNLOCK() ; } /*------------------------------------------------------------------------------ * Restart the VTY, following a vty_reset(). * - * This starts the listeners for VTY_TERM and VTY_SHELL_SERV, again. + * This starts the listeners for VTY_TERMINAL and VTY_SHELL_SERVER, again. * * NB: may be called once, and once only, *after* a vty_reset(). * @@ -296,7 +324,7 @@ struct vty_restart_args } ; MQB_ARGS_SIZE_OK(vty_restart_args) ; -static void uty_restart_action(mqueue_block mqb, mqb_flag_t flag) ; +static void vty_restart_action(mqueue_block mqb, mqb_flag_t flag) ; static void uty_restart(const char *addr, unsigned short port, const char *path) ; extern void @@ -308,14 +336,14 @@ vty_restart(const char *addr, unsigned short port, const char *path) * * Otherwise, construct and dispatch message to do a uty_restart. */ - if (!vty_cli_nexus) + if (!vty_nexus) uty_restart(addr, port, path) ; else { mqueue_block mqb ; struct vty_restart_args* args ; - mqb = mqb_init_new(NULL, uty_restart_action, vty_cli_nexus) ; + mqb = mqb_init_new(NULL, vty_restart_action, vty_cli_nexus) ; args = mqb_get_args(mqb) ; if (addr != NULL) @@ -330,7 +358,7 @@ vty_restart(const char *addr, unsigned short port, const char *path) else args->path = NULL ; - mqueue_enqueue(vty_cli_nexus->queue, mqb, 0) ; + mqueue_enqueue(vty_cli_nexus->queue, mqb, mqb_priority) ; } ; VTY_UNLOCK() ; @@ -338,7 +366,7 @@ vty_restart(const char *addr, unsigned short port, const char *path) /* Deal with the uty_restart message */ static void -uty_restart_action(mqueue_block mqb, mqb_flag_t flag) +vty_restart_action(mqueue_block mqb, mqb_flag_t flag) { struct vty_restart_args* args ; args = mqb_get_args(mqb) ; @@ -388,17 +416,18 @@ uty_restart(const char *addr, unsigned short port, const char *path) extern void vty_terminate (void) { + VTY_ASSERT_CLI_THREAD() ; + if ( (vty_init_state == vty_init_pending) || (vty_init_state == vty_init_terminated) ) - return ; /* nothing to do ! */ + return ; /* nothing to do ! */ VTY_LOCK() ; - VTY_ASSERT_CLI_THREAD() ; assert( (vty_init_state > vty_init_pending) && (vty_init_state < vty_init_terminated) ) ; - uty_reset(1, "Shut down") ; /* final reset */ + uty_reset(true, "Shut down") ; /* final reset */ VTY_UNLOCK() ; @@ -408,12 +437,14 @@ vty_terminate (void) } /*------------------------------------------------------------------------------ - * Reset -- final or for SIGHUP + * Reset -- final curtain or for SIGHUP * * Closes listeners. * - * Closes (final) or half closes (SIGHUP) all VTY, and revokes any outstanding - * commands. + * Revokes any outstanding commands and close (SIGHUP) or close_final + * (curtains) all VTY. + * + * * * Resets the vty timeout and access lists. * @@ -440,34 +471,22 @@ uty_reset (bool curtains, const char* why) vio = next ; next = sdl_next(vio, vio_list) ; - if (uty_is_terminal(vio->vty)) - cq_revoke(vio->vty) ; - - if (curtains) - uty_close_final(vio, why) ; - else - uty_close(vio, why) ; + uty_close(vio, curtains, qs_set(NULL, why)) ; } ; - vty_timeout_val = VTY_TIMEOUT_DEFAULT; - - if (vty_accesslist_name) - { - XFREE(MTYPE_VTY, vty_accesslist_name); - vty_accesslist_name = NULL; - } + host.vty_timeout_val = VTY_TIMEOUT_DEFAULT; - if (vty_ipv6_accesslist_name) - { - XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); - vty_ipv6_accesslist_name = NULL; - } + XFREE(MTYPE_HOST, host.vty_accesslist_name) ; + /* sets host.vty_accesslist_name = NULL */ - if (curtains && vty_cwd) - XFREE (MTYPE_TMP, vty_cwd); + XFREE(MTYPE_HOST, host.vty_ipv6_accesslist_name); + /* sets host.vty_ipv6_accesslist_name = NULL */ if (curtains) - uty_watch_dog_stop() ; /* and final death watch run */ + { + XFREE (MTYPE_HOST, host.vty_cwd); + uty_watch_dog_stop() ; /* and final death watch run */ + } ; } ; /*============================================================================== @@ -481,15 +500,17 @@ uty_reset (bool curtains, const char* why) /*------------------------------------------------------------------------------ * Create a new VTY of the given type * - * The type may NOT be: VTY_TERM or VTY_SHELL_SERV + * The type may NOT be: VTY_TERMINAL or VTY_SHELL_SERVER + * + * Appears only to be used by vtysh !! TODO ???? */ -extern struct vty * -vty_open(enum vty_type type) +extern vty +vty_open(vty_type_t type) { struct vty* vty ; VTY_LOCK() ; - vty = uty_new(type, -1) ; /* fails for VTY_TERM or VTY_SHELL_SERV */ + vty = uty_new(type) ; VTY_UNLOCK() ; return vty ; @@ -498,12 +519,16 @@ vty_open(enum vty_type type) /*------------------------------------------------------------------------------ * Close the given VTY */ -extern void -vty_close (struct vty *vty) +extern bool +vty_close(vty vty, bool final, qstring reason) { + bool closed ; + VTY_LOCK() ; - uty_close(vty->vio) ; + closed = uty_close(vty->vio, final, reason) ; VTY_UNLOCK() ; + + return closed ; } /*============================================================================== @@ -517,59 +542,22 @@ vty_close (struct vty *vty) /*------------------------------------------------------------------------------ * VTY output -- cf fprintf ! * - * This is for command output, which may be suppressed - * - * Returns: >= 0 => OK - * < 0 => failed (see errno) - */ -extern int -vty_out (struct vty *vty, const char *format, ...) -{ - int ret ; - va_list args ; - - VTY_LOCK() ; - - if (vty->output_enabled) - { - va_start (args, format) ; - ret = uty_vprintf(vty, format, args) ; - va_end (args) ; - } - else - ret = 0 ; - - VTY_UNLOCK() ; - return ret ; -} - -/*------------------------------------------------------------------------------ - * VTY output error message -- cf fprintf ! - * - * If command has not yet been reflected, do that first. + * This is for command output, which may later be suppressed * * Returns: >= 0 => OK * < 0 => failed (see errno) */ extern int -vty_out_error (struct vty *vty, const char *format, ...) +vty_out(struct vty *vty, const char *format, ...) { int ret ; va_list args ; VTY_LOCK() ; - if (!vty->reflected) - ret = uty_reflect(vty) ; - else - ret = 0 ; - - if (ret == 0) - { - va_start (args, format) ; - ret = uty_vprintf(vty, format, args) ; - va_end (args) ; - } ; + va_start (args, format) ; + ret = vio_fifo_vprintf(vty->vio->obuf, format, args) ; + va_end (args) ; VTY_UNLOCK() ; return ret ; @@ -631,7 +619,7 @@ vty_hello (struct vty *vty) VTY_LOCK() ; #ifdef QDEBUG - uty_out (vty, "%s\n", debug_banner); + vty_out (vty, "%s\n", debug_banner); #endif if (host.motdfile) { @@ -648,15 +636,15 @@ vty_hello (struct vty *vty) for (s = buf + strlen (buf); (s > buf) && isspace ((int)*(s - 1)); s--); *s = '\0'; - uty_output (vty, "%s\n", buf); + vty_out(vty, "%s\n", buf); } fclose (f); } else - uty_output (vty, "MOTD file %s not found\n", host.motdfile); + vty_out(vty, "MOTD file %s not found\n", host.motdfile); } else if (host.motd) - uty_output (vty, "%s", host.motd); + vty_out(vty, "%s", host.motd); VTY_UNLOCK() ; } @@ -665,436 +653,13 @@ vty_hello (struct vty *vty) * Clear the contents of the command output FIFO etc. */ extern void -vty_out_clear(struct vty* vty) +vty_out_clear(vty vty) { VTY_LOCK() ; uty_out_clear(vty->vio) ; VTY_UNLOCK() ; } ; -/*============================================================================== - * Command Execution - */ - -/*------------------------------------------------------------------------------ - * Execute command -- adding to history is not empty or just comment - * - * This is for VTY_TERM type VTY. - * - * Outputs diagnostics if fails to parse. - * - * Returns: command return code - */ -extern enum cmd_return_code -uty_command(struct vty *vty) -{ - enum cmd_return_code ret; - - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - assert(uty_is_terminal(vty)) ; - - /* Parse the command and add to history (if not empty) */ - ret = cmd_parse_command(vty, - cmd_parse_completion + cmd_parse_do + cmd_parse_tree) ; - if (ret != CMD_EMPTY) - uty_cli_hist_add (vty->vio, vty->buf) ; - - /* If parsed and not empty, dispatch */ - if (ret == CMD_SUCCESS) - { -#ifdef CONSUMED_TIME_CHECK - RUSAGE_T before; - RUSAGE_T after; - unsigned long realtime, cputime; - - GETRUSAGE(&before); -#endif /* CONSUMED_TIME_CHECK */ - - ret = cmd_dispatch(vty, cmd_may_queue) ; - -#ifdef CONSUMED_TIME_CHECK - GETRUSAGE(&after); - if ((realtime = thread_consumed_time(&after, &before, &cputime)) > - CONSUMED_TIME_CHECK) - /* Warn about CPU hog that must be fixed. */ - uzlog(NULL, LOG_WARNING, - "SLOW COMMAND: command took %lums (cpu time %lums): %s", - realtime/1000, cputime/1000, vty->buf) ; -#endif /* CONSUMED_TIME_CHECK */ - } ; - - /* Deal with the return code */ - switch (ret) - { - case CMD_ERR_AMBIGUOUS: - vty_out_error(vty, "%% Ambiguous command.\n"); - break; - - case CMD_ERR_NO_MATCH: - vty_out_error(vty, "%% Unknown command.\n") ; - break; - - case CMD_ERR_INCOMPLETE: - vty_out_error(vty, "%% Command incomplete.\n"); - break; - - default: - break ; - } ; - - return ret ; -} ; - -/*------------------------------------------------------------------------------ - * Authentication of vty - * - * During AUTH_NODE and AUTH_ENABLE_NODE, when a command line is dispatched by - * any means this function is called. - * - * Note that if the AUTH_NODE password fails too many times, the terminal is - * closed. - * - * Returns: command return code - */ -extern enum cmd_return_code -uty_auth (struct vty *vty, const char *buf, enum cli_do cli_do) -{ - char *passwd = NULL; - enum node_type next_node = 0; - int fail; - char *crypt (const char *, const char *); - enum cmd_return_code ret ; - - vty_io vio = vty->vio ; - - VTY_ASSERT_LOCKED() ; - VTY_ASSERT_CLI_THREAD() ; - - /* What to do ? - * - * In fact, all the exotic command terminators simply discard any input - * and return. - */ - switch (cli_do) - { - case cli_do_nothing: - case cli_do_ctrl_c: - case cli_do_ctrl_z: - return CMD_SUCCESS ; - - case cli_do_command: - break ; - - case cli_do_ctrl_d: - case cli_do_eof: - return uty_cmd_close(vty, "End") ; - - default: - zabort("unknown or invalid cli_do") ; - } ; - - /* Ordinary command dispatch -- see if password is OK. */ - switch (vty->node) - { - case AUTH_NODE: - if (host.encrypt) - passwd = host.password_encrypt; - else - passwd = host.password; - if (host.advanced) - next_node = host.enable ? VIEW_NODE : ENABLE_NODE; - else - next_node = VIEW_NODE; - break; - - case AUTH_ENABLE_NODE: - if (host.encrypt) - passwd = host.enable_encrypt; - else - passwd = host.enable; - next_node = ENABLE_NODE; - break; - - default: - zabort("unknown node type") ; - } - - if (passwd) - { - if (host.encrypt) - fail = strcmp (crypt(buf, passwd), passwd); - else - fail = strcmp (buf, passwd); - } - else - fail = 1; - - ret = CMD_SUCCESS ; - - if (! fail) - { - vio->fail = 0; - vty->node = next_node; /* Success ! */ - } - else - { - vio->fail++; - if (vio->fail >= 3) - { - if (vty->node == AUTH_NODE) - { - ret = uty_cmd_close(vty, "Bad passwords, too many failures!") ; - } - else - { - /* AUTH_ENABLE_NODE */ - vio->fail = 0; - vty_out_error(vty, - "%% Bad enable passwords, too many failures!\n") ; - vty->node = restricted_mode ? RESTRICTED_NODE : VIEW_NODE; - - ret = CMD_WARNING ; - } - } - } - - return ret ; -} ; - -/*------------------------------------------------------------------------------ - * Command line "exit" command -- aka "quit" - * - * Falls back one NODE level. - * - * Returns: command return code - */ -extern enum cmd_return_code -vty_cmd_exit(struct vty* vty) -{ - enum cmd_return_code ret ; - - VTY_LOCK() ; - - ret = CMD_SUCCESS ; - switch (vty->node) - { - case VIEW_NODE: - case ENABLE_NODE: - case RESTRICTED_NODE: - if (uty_shell_client(vty)) - exit (0); - else - ret = uty_cmd_close(vty, "Exit") ; - break; - case CONFIG_NODE: - uty_config_unlock (vty, ENABLE_NODE); - break; - case INTERFACE_NODE: - case ZEBRA_NODE: - case BGP_NODE: - case RIP_NODE: - case RIPNG_NODE: - case OSPF_NODE: - case OSPF6_NODE: - case ISIS_NODE: - case KEYCHAIN_NODE: - case MASC_NODE: - case RMAP_NODE: - case VTY_NODE: - vty->node = CONFIG_NODE ; - break; - case BGP_VPNV4_NODE: - case BGP_IPV4_NODE: - case BGP_IPV4M_NODE: - case BGP_IPV6_NODE: - case BGP_IPV6M_NODE: - vty->node = BGP_NODE ; - break; - case KEYCHAIN_KEY_NODE: - vty->node = KEYCHAIN_NODE ; - break; - default: - break; - } - - VTY_UNLOCK() ; - return ret ; -} - -/*------------------------------------------------------------------------------ - * Command line "end" command - * - * Falls back to ENABLE_NODE. - * - * Returns: command return code - */ -extern enum cmd_return_code -vty_cmd_end(struct vty* vty) -{ - VTY_LOCK() ; - - switch (vty->node) - { - case VIEW_NODE: - case ENABLE_NODE: - case RESTRICTED_NODE: - /* Nothing to do. */ - break; - case CONFIG_NODE: - case INTERFACE_NODE: - case ZEBRA_NODE: - case RIP_NODE: - case RIPNG_NODE: - case BGP_NODE: - case BGP_VPNV4_NODE: - case BGP_IPV4_NODE: - case BGP_IPV4M_NODE: - case BGP_IPV6_NODE: - case BGP_IPV6M_NODE: - case RMAP_NODE: - case OSPF_NODE: - case OSPF6_NODE: - case ISIS_NODE: - case KEYCHAIN_NODE: - case KEYCHAIN_KEY_NODE: - case MASC_NODE: - case VTY_NODE: - uty_config_unlock (vty, ENABLE_NODE); - break; - default: - break; - } - - VTY_UNLOCK() ; - return CMD_SUCCESS ; -} ; - -/*------------------------------------------------------------------------------ - * Result of command is to close the input. - * - * Posts the reason for the close. - * - * Returns: CMD_CLOSE - */ -extern enum cmd_return_code -uty_cmd_close(struct vty *vty, const char* reason) -{ - vty->vio->close_reason = reason ; - return CMD_CLOSE ; -} ; - -/*------------------------------------------------------------------------------ - * Command line ^C action. - * - * Ignores contents of command line (including not adding to history). - * - * Fall back to ENABLE_NODE if in any one of a number of nodes. - * - * Resets the history pointer. - * - * Returns: command return code - */ -extern enum cmd_return_code -uty_stop_input(struct vty *vty) -{ - vty_io vio = vty->vio ; - - VTY_ASSERT_LOCKED() ; - - switch (vty->node) - { - case CONFIG_NODE: - case INTERFACE_NODE: - case ZEBRA_NODE: - case RIP_NODE: - case RIPNG_NODE: - case BGP_NODE: - case RMAP_NODE: - case OSPF_NODE: - case OSPF6_NODE: - case ISIS_NODE: - case KEYCHAIN_NODE: - case KEYCHAIN_KEY_NODE: - case MASC_NODE: - case VTY_NODE: - uty_config_unlock (vty, ENABLE_NODE) ; - break; - default: - /* Unknown node, we have to ignore it. */ - break; - } - - /* Set history pointer to the latest one. */ - vio->hp = vio->hindex; - - return CMD_SUCCESS ; -} ; - -/*------------------------------------------------------------------------------ - * Command ^Z action. - * - * Ignores contents of command line (including not adding to history). - * - * Fall back to ENABLE_NODE if in any one of a number of nodes. - * - * Returns: command return code - */ -extern enum cmd_return_code -uty_end_config (struct vty *vty) -{ - VTY_ASSERT_LOCKED() ; - - switch (vty->node) - { - case VIEW_NODE: - case ENABLE_NODE: - case RESTRICTED_NODE: - /* Nothing to do. */ - break; - case CONFIG_NODE: - case INTERFACE_NODE: - case ZEBRA_NODE: - case RIP_NODE: - case RIPNG_NODE: - case BGP_NODE: - case BGP_VPNV4_NODE: - case BGP_IPV4_NODE: - case BGP_IPV4M_NODE: - case BGP_IPV6_NODE: - case BGP_IPV6M_NODE: - case RMAP_NODE: - case OSPF_NODE: - case OSPF6_NODE: - case ISIS_NODE: - case KEYCHAIN_NODE: - case KEYCHAIN_KEY_NODE: - case MASC_NODE: - case VTY_NODE: - uty_config_unlock (vty, ENABLE_NODE) ; - break; - default: - /* Unknown node, we have to ignore it. */ - break; - } - - return CMD_SUCCESS ; -} - -/*------------------------------------------------------------------------------ - * Command ^D action -- when nothing else on command line. - * - * Same as "exit" command. - * - * Returns: command return code - */ -extern enum cmd_return_code -uty_down_level (struct vty *vty) -{ - return vty_cmd_exit(vty) ; -} ; - /*============================================================================== * Reading of configuration file * @@ -1116,18 +681,18 @@ uty_down_level (struct vty *vty) * run directly in the thread -- no commands are queued. */ -static FILE * vty_use_backup_config (char *fullpath) ; -static void vty_read_file (FILE *confp, struct cmd_element* first_cmd, - bool ignore_warnings) ; +static int vty_use_backup_config (const char *fullpath) ; +static void vty_read_file (int conf_fd, const char* name, + cmd_command first_cmd, bool ignore_warnings) ; /*------------------------------------------------------------------------------ * Read the given configuration file. */ extern void -vty_read_config (char *config_file, - char *config_default) +vty_read_config (const char *config_file, + const char *config_default) { - vty_read_config_first_cmd_special(config_file, config_default, NULL, 1); + vty_read_config_first_cmd_special(config_file, config_default, NULL, true); } /*------------------------------------------------------------------------------ @@ -1148,15 +713,15 @@ vty_read_config (char *config_file, * command. */ extern void -vty_read_config_first_cmd_special(char *config_file, - char *config_default, - struct cmd_element* first_cmd, +vty_read_config_first_cmd_special(const char *config_file, + const char *config_default, + cmd_command first_cmd, bool ignore_warnings) { - char cwd[MAXPATHLEN]; - FILE *confp = NULL; - char *fullpath; - char *tmp = NULL; + char cwd[MAXPATHLEN] ; + int conf_fd ; + const char *fullpath ; + char *tmp = NULL ; /* Deal with VTYSH_ENABLED magic */ if (VTYSH_ENABLED && (config_file == NULL)) @@ -1204,15 +769,15 @@ vty_read_config_first_cmd_special(char *config_file, } ; /* try to open the configuration file */ - confp = fopen (fullpath, "r"); + conf_fd = uty_vfd_file_open(fullpath, vfd_io_read | vfd_io_blocking) ; - if (confp == NULL) + if (conf_fd < 0) { fprintf (stderr, "%s: failed to open configuration file %s: %s\n", __func__, fullpath, errtostr(errno, 0).str); - confp = vty_use_backup_config (fullpath); - if (confp) + conf_fd = vty_use_backup_config (fullpath); + if (conf_fd >= 0) fprintf (stderr, "WARNING: using backup configuration file!\n"); else { @@ -1226,8 +791,7 @@ vty_read_config_first_cmd_special(char *config_file, fprintf(stderr, "Reading config file: %s\n", fullpath); #endif - vty_read_file (confp, first_cmd, ignore_warnings); - fclose (confp); + vty_read_file(conf_fd, fullpath, first_cmd, ignore_warnings); host_config_set (fullpath); @@ -1249,17 +813,17 @@ vty_read_config_first_cmd_special(char *config_file, * - call it "" * - return an open FILE * - * Returns: NULL => no ".sav", or faild doing any of the above - * otherwise, returns FILE* for open file. + * Returns: < 0 => no ".sav", or failed doing any of the above + * >= 0 otherwise, fd file. */ -static FILE * -vty_use_backup_config (char *fullpath) +static int +vty_use_backup_config (const char *fullpath) { - char *tmp_path ; + char *tmp_path ; struct stat buf; - int ret, tmp, sav; - int c; - char buffer[4096] ; + int ret, tmp, sav; + int c, err; + char* buffer ; enum { xl = 32 } ; tmp_path = malloc(strlen(fullpath) + xl) ; @@ -1268,14 +832,17 @@ vty_use_backup_config (char *fullpath) confirm(xl > sizeof(CONF_BACKUP_EXT)) ; sprintf (tmp_path, "%s%s", fullpath, CONF_BACKUP_EXT) ; - sav = -1 ; if (stat (tmp_path, &buf) != -1) - sav = open (tmp_path, O_RDONLY); + sav = uty_vfd_file_open(tmp_path, vfd_io_read | vfd_io_blocking) ; + else + sav = -1 ; if (sav < 0) { - free (tmp_path); - return NULL; + err = errno ; /* making sure */ + free (tmp_path) ; + errno = err ; + return sav ; } ; /* construct a temporary file and copy "" to it. */ @@ -1286,34 +853,60 @@ vty_use_backup_config (char *fullpath) tmp = mkstemp (tmp_path); if (tmp < 0) { + err = errno ; free (tmp_path); close(sav); - return NULL; + errno = err ; + return tmp; } - while((c = read (sav, buffer, sizeof(buffer))) > 0) - write (tmp, buffer, c); + enum { buffer_size = 64 * 1024 } ; + buffer = malloc(buffer_size) ; + + c = 1 ; + while (c > 0) + { + c = read(sav, buffer, buffer_size) ; + if (c > 0) + { + if (write(tmp, buffer, c) < c) + c = -1 ; + } ; + } ; + err = errno ; + + free(buffer) ; + close(sav) ; + close(tmp) ; - close (sav); - close (tmp); + if (c < 0) + { + errno = err ; + return c ; + } ; /* Make sure that have the required file status */ if (chmod(tmp_path, CONFIGFILE_MASK) != 0) { + err = errno ; unlink (tmp_path); free (tmp_path); - return NULL; - } + errno = err ; + return -1 ; + } ; /* Make be a name for the new file just created. */ ret = link (tmp_path, fullpath) ; /* Discard the temporary, now */ + err = errno ; unlink (tmp_path) ; free (tmp_path) ; + errno = err ; /* If link was successful, try to open -- otherwise, failed. */ - return (ret == 0) ? fopen (fullpath, "r") : NULL ; + return (ret == 0) ? uty_vfd_file_open(fullpath, vfd_io_read | vfd_io_blocking) + : -1 ; } ; /*------------------------------------------------------------------------------ @@ -1337,68 +930,49 @@ vty_use_backup_config (char *fullpath) * so all commands are executed directly. */ static void -vty_read_file (FILE *confp, struct cmd_element* first_cmd, bool ignore_warnings) +vty_read_file (int conf_fd, const char* name, + cmd_command first_cmd, bool ignore_warnings) { - enum cmd_return_code ret ; - struct vty *vty ; + cmd_return_code_t ret ; + vty vty ; + vty_io vio ; + vio_vf vf ; + + VTY_LOCK() ; /* Set up configuration file reader VTY -- which buffers all output */ vty = vty_open(VTY_CONFIG_READ); vty->node = CONFIG_NODE; - /* Make sure we have a suitable buffer, and set vty->buf to point at - * it -- same like other command execution. - */ - qs_need(&vty->vio->clx, VTY_BUFSIZ) ; - vty->buf = qs_chars(&vty->vio->clx) ; + vio = vty->vio ; - /* Execute configuration file */ - ret = config_from_file (vty, confp, first_cmd, &vty->vio->clx, - ignore_warnings) ; - - VTY_LOCK() ; - - if (ret != CMD_SUCCESS) - { - fprintf (stderr, "%% while processing line %u of the configuration:\n" - "%s", vty->lineno, vty->buf) ; - - switch (ret) - { - case CMD_WARNING: - fprintf (stderr, "%% Warning...\n"); - break; + vf = uty_vf_new(vio, name, conf_fd, vfd_file, vfd_io_read | vfd_io_blocking) ; - case CMD_ERROR: - fprintf (stderr, "%% Error...\n"); - break; + uty_vin_open( vio, vf, VIN_CONFIG, NULL, NULL, 64 * 1024) ; + uty_vout_open(vio, vf, VOUT_STDERR, NULL, NULL, 4 * 1024) ; - case CMD_ERR_AMBIGUOUS: - fprintf (stderr, "%% Ambiguous command.\n"); - break; + vio->vin->parse_type = cmd_parse_strict | cmd_parse_no_do ; - case CMD_ERR_NO_MATCH: - fprintf (stderr, "%% There is no such command.\n"); - break; + /* When we get here the VTY is set up and all ready to go. */ + uty_cmd_prepare(vio) ; - case CMD_ERR_INCOMPLETE: - fprintf (stderr, "%% Incomplete command.\n"); - break; + VTY_UNLOCK() ; - default: - fprintf(stderr, "%% (unknown cause %d)\n", ret) ; - break ; - } ; + /* Execute configuration file */ + ret = cmd_read_config(vty, first_cmd, ignore_warnings) ; - uty_out_fflush(vty->vio, stderr) ; /* flush command output buffer */ + ret = vty_cmd_loop_exit(vty, ret) ; + if (ret != CMD_SUCCESS) + { + vty_close(vty, true, qs_set(NULL, "Error in configuration file.")) ; exit (1); } ; - uty_close(vty->vio) ; - VTY_UNLOCK() ; + vty_close(vty, false, NULL) ; } ; +#if 0 /*------------------------------------------------------------------------------ * Flush the contents of the command output FIFO to the given file. * @@ -1414,84 +988,59 @@ uty_out_fflush(vty_io vio, FILE* file) fflush(file) ; - while ((src = vio_fifo_get_lump(&vio->cmd_obuf, &have)) != NULL) + src = vio_fifo_get_lump(&vio->cmd_obuf, &have) ; + while (src != NULL) { fwrite(src, 1, have, file) ; - vio_fifo_got_upto(&vio->cmd_obuf, src + have) ; + src = vio_fifo_step_get_lump(&vio->cmd_obuf, &have, have) ; } ; fflush(file) ; } ; - - - +#endif /*============================================================================== - * Configuration node/state handling - * - * At most one VTY may hold the configuration symbol of power at any time. + * For writing configuration file by command, temporarily redirect output to + * an actual file. */ /*------------------------------------------------------------------------------ - * Attempt to gain the configuration symbol of power - * - * If succeeds, set the given node. - * - * Returns: true <=> now own the symbol of power. + * Set the given fd as the VTY_FILE output. */ -extern bool -vty_config_lock (struct vty *vty, enum node_type node) +extern void +vty_open_config_write(vty vty, int fd) { - bool result; + vty_io vio ; + vio_vf vf ; VTY_LOCK() ; - if (!vty_config) - { - vty->config = true ; - vty_config = true ; - } ; + vio = vty->vio ; - result = vty->config; + vf = uty_vf_new(vio, "config write", fd, vfd_file, vfd_io_read) ; - if (result) - vty->node = node ; + uty_vout_open(vio, vf, VOUT_FILE, NULL, NULL, 16 * 1024) ; VTY_UNLOCK() ; - - return result; -} +} ; /*------------------------------------------------------------------------------ - * Give back the configuration symbol of power -- if own it. - * - * Set the given node -- which must be <= MAX_NON_CONFIG_NODE + * Write away any pending stuff, and return the VTY to normal. */ -extern void -vty_config_unlock (struct vty *vty, enum node_type node) +extern int +vty_close_config_write(struct vty* vty) { +// vty_io vio ; +// int err ; + VTY_LOCK() ; - uty_config_unlock(vty, node); - VTY_UNLOCK() ; -} -/*------------------------------------------------------------------------------ - * Give back the configuration symbol of power -- if own it. - * - * Set the given node -- which must be <= MAX_NON_CONFIG_NODE - */ -extern void -uty_config_unlock (struct vty *vty, enum node_type node) -{ - VTY_ASSERT_LOCKED() ; - if (vty_config && vty->config) - { - vty->config = false ; - vty_config = false ; - } + uty_vout_close(vty->vio, false) ; + + VTY_UNLOCK() ; - assert(node <= MAX_NON_CONFIG_NODE) ; - vty->node = node ; +// return err ; + return 0 ; } ; /*============================================================================== @@ -1508,13 +1057,15 @@ DEFUN_CALL (config_who, VTY_LOCK() ; - vio = vio_list_base ; + vio = vio_list_base ; /* once locked */ + while (vio != NULL) /* TODO: show only VTY_TERM ??? */ { - uty_output (vty, "%svty[%d] connected from %s.\n", + vty_out(vty, "%svty[%d] connected from %s.\n", vio->vty->config ? "*" : " ", i, uty_get_name(vio)); vio = sdl_next(vio, vio_list) ; } ; + VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -1532,7 +1083,11 @@ DEFUN_CALL (line_vty, return CMD_SUCCESS; } -/* Set time out value. */ +/*------------------------------------------------------------------------------ + * Set time out value. + * + * Affects this and any future VTY_TERMINAL or VTY_SHELL_SERVER type VTY. + */ static int exec_timeout (struct vty *vty, const char *min_str, const char *sec_str) { @@ -1550,10 +1105,9 @@ exec_timeout (struct vty *vty, const char *min_str, const char *sec_str) if (sec_str) timeout += strtol (sec_str, NULL, 10); - vty_timeout_val = timeout; + host.vty_timeout_val = timeout; - if (uty_is_terminal(vty) || uty_is_shell_server(vty)) - uty_sock_set_timer(&vty->vio->sock, timeout) ; + uty_set_timeout(vty->vio, timeout) ; /* update own timeout, if required */ VTY_UNLOCK() ; return CMD_SUCCESS; @@ -1596,10 +1150,9 @@ DEFUN_CALL (vty_access_class, { VTY_LOCK() ; - if (vty_accesslist_name) - XFREE(MTYPE_VTY, vty_accesslist_name); + XFREE(MTYPE_HOST, host.vty_accesslist_name); - vty_accesslist_name = XSTRDUP(MTYPE_VTY, argv[0]); + host.vty_accesslist_name = XSTRDUP(MTYPE_HOST, argv[0]); VTY_UNLOCK() ; return CMD_SUCCESS; @@ -1613,22 +1166,25 @@ DEFUN_CALL (no_vty_access_class, "Filter connections based on an IP access list\n" "IP access list\n") { - int result = CMD_SUCCESS; + cmd_return_code_t ret ; VTY_LOCK() ; - if (! vty_accesslist_name || (argc && strcmp(vty_accesslist_name, argv[0]))) + + if ((argc == 0) || ( (host.vty_accesslist_name != NULL) && + (strcmp(host.vty_accesslist_name, argv[0]) == 0) )) { - vty_out_error(vty, "Access-class is not currently applied to vty\n"); - result = CMD_WARNING; + XFREE(MTYPE_HOST, host.vty_accesslist_name); + /* sets host.vty_accesslist_name = NULL */ + ret = CMD_SUCCESS ; } else { - XFREE(MTYPE_VTY, vty_accesslist_name); - vty_accesslist_name = NULL; - } + vty_out(vty, "Access-class is not currently applied to vty\n") ; + ret = CMD_WARNING; + } ; VTY_UNLOCK() ; - return result; + return ret; } #ifdef HAVE_IPV6 @@ -1641,10 +1197,10 @@ DEFUN_CALL (vty_ipv6_access_class, "IPv6 access list\n") { VTY_LOCK() ; - if (vty_ipv6_accesslist_name) - XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); - vty_ipv6_accesslist_name = XSTRDUP(MTYPE_VTY, argv[0]); + XFREE(MTYPE_HOST, host.vty_ipv6_accesslist_name); + + host.vty_ipv6_accesslist_name = XSTRDUP(MTYPE_HOST, argv[0]); VTY_UNLOCK() ; return CMD_SUCCESS; @@ -1659,25 +1215,25 @@ DEFUN_CALL (no_vty_ipv6_access_class, "Filter connections based on an IP access list\n" "IPv6 access list\n") { - int result = CMD_SUCCESS; + cmd_return_code_t ret ; VTY_LOCK() ; - if (! vty_ipv6_accesslist_name || - (argc && strcmp(vty_ipv6_accesslist_name, argv[0]))) + if ((argc == 0) || ( (host.vty_ipv6_accesslist_name != NULL) && + (strcmp(host.vty_ipv6_accesslist_name, argv[0]) == 0) )) { - vty_out_error(vty, "IPv6 access-class is not currently applied to vty\n") ; - result = CMD_WARNING; + XFREE(MTYPE_HOST, host.vty_ipv6_accesslist_name); + /* sets host.vty_ipv6_accesslist_name = NULL */ + ret = CMD_SUCCESS ; } else { - XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); - - vty_ipv6_accesslist_name = NULL; - } + vty_out(vty, "IPv6 access-class is not currently applied to vty\n") ; + ret = CMD_WARNING; + } ; VTY_UNLOCK() ; - return CMD_SUCCESS; + return ret; } #endif /* HAVE_IPV6 */ @@ -1688,7 +1244,7 @@ DEFUN_CALL (vty_login, "Enable password checking\n") { VTY_LOCK() ; - no_password_check = 0; + host.no_password_check = false ; VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -1700,7 +1256,7 @@ DEFUN_CALL (no_vty_login, "Enable password checking\n") { VTY_LOCK() ; - no_password_check = 1; + host.no_password_check = true ; VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -1712,7 +1268,7 @@ DEFUN_CALL (vty_restricted_mode, "Restrict view commands available in anonymous, unauthenticated vty\n") { VTY_LOCK() ; - restricted_mode = 1; + host.restricted_mode = true; VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -1724,7 +1280,7 @@ DEFUN_CALL (vty_no_restricted_mode, "Enable password checking\n") { VTY_LOCK() ; - restricted_mode = 0; + host.restricted_mode = false; VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -1736,7 +1292,7 @@ DEFUN_CALL (service_advanced_vty, "Enable advanced mode vty interface\n") { VTY_LOCK() ; - host.advanced = 1; + host.advanced = true; VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -1749,7 +1305,7 @@ DEFUN_CALL (no_service_advanced_vty, "Enable advanced mode vty interface\n") { VTY_LOCK() ; - host.advanced = 0; + host.advanced = false; VTY_UNLOCK() ; return CMD_SUCCESS; } @@ -1792,26 +1348,10 @@ DEFUN_CALL (show_history, SHOW_STR "Display the session command history\n") { - int index; - VTY_LOCK() ; - for (index = vty->vio->hindex + 1; index != vty->vio->hindex;) - { - qstring line ; - - if (index == VTY_MAXHIST) - { - index = 0; - continue; - } - - line = vector_get_item(vty->vio->hist, index) ; - if (line != NULL) - uty_output (vty, " %s\n", line->char_body); - - index++; - } + if (vty->type == VTY_TERMINAL) + uty_cli_hist_show(vty->vio->vin->cli) ; VTY_UNLOCK() ; return CMD_SUCCESS; @@ -1827,22 +1367,22 @@ vty_config_write (struct vty *vty) { vty_out (vty, "line vty\n"); - if (vty_accesslist_name) - vty_out (vty, " access-class %s\n", vty_accesslist_name); + if (host.vty_accesslist_name) + vty_out (vty, " access-class %s\n", host.vty_accesslist_name); - if (vty_ipv6_accesslist_name) - vty_out (vty, " ipv6 access-class %s\n", vty_ipv6_accesslist_name); + if (host.vty_ipv6_accesslist_name) + vty_out (vty, " ipv6 access-class %s\n", host.vty_ipv6_accesslist_name); /* exec-timeout */ - if (vty_timeout_val != VTY_TIMEOUT_DEFAULT) - vty_out (vty, " exec-timeout %ld %ld\n", vty_timeout_val / 60, - vty_timeout_val % 60); + if (host.vty_timeout_val != VTY_TIMEOUT_DEFAULT) + vty_out (vty, " exec-timeout %ld %ld\n", host.vty_timeout_val / 60, + host.vty_timeout_val % 60); /* login */ - if (no_password_check) + if (host.no_password_check) vty_out (vty, " no login\n"); - if (restricted_mode != restricted_mode_default) + if (host.restricted_mode != restricted_mode_default) { if (restricted_mode_default) vty_out (vty, " no anonymous restricted\n"); @@ -1879,7 +1419,7 @@ vty_save_cwd (void) getcwd (cwd, MAXPATHLEN); } - vty_cwd = XSTRDUP(MTYPE_TMP, cwd) ; + host.vty_cwd = XSTRDUP(MTYPE_HOST, cwd) ; } ; /*------------------------------------------------------------------------------ @@ -1888,12 +1428,13 @@ vty_save_cwd (void) char * vty_get_cwd () { - return vty_cwd; + return host.vty_cwd; } /*============================================================================== * Access functions for VTY values, where locking is or might be required. */ +#if 0 static bool vty_is_terminal(struct vty *vty) @@ -1925,31 +1466,16 @@ vty_is_shell_client (struct vty *vty) return result; } -enum node_type -vty_get_node(struct vty *vty) -{ - int result; - VTY_LOCK() ; - result = vty->node; - VTY_UNLOCK() ; - return result; -} - -void -vty_set_node(struct vty *vty, enum node_type node) -{ - VTY_LOCK() ; - vty->node = node; - VTY_UNLOCK() ; -} +#endif void vty_set_lines(struct vty *vty, int lines) { VTY_LOCK() ; - vty->vio->lines = lines; - vty->vio->lines_set = 1 ; - uty_set_height(vty->vio) ; + + if (vty->type == VTY_TERMINAL) + uty_cli_set_lines(vty->vio->vin->cli, lines, true) ; + VTY_UNLOCK() ; } diff --git a/lib/vty.h b/lib/vty.h index b8621588..99b08d96 100644 --- a/lib/vty.h +++ b/lib/vty.h @@ -1,4 +1,4 @@ -/* VTY top level +/* VTY external interface -- header * Copyright (C) 1997, 98 Kunihiro Ishiguro * * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman @@ -25,6 +25,10 @@ #define _ZEBRA_VTY_H #include "misc.h" +#include "vargs.h" + +#include "command_common.h" /* NB: *not* command.h */ +#include "vty_common.h" /* struct vty & VTY types */ #include "thread.h" #include "log.h" @@ -35,128 +39,12 @@ #include "list_util.h" #include "vector.h" #include "qstring.h" -#include "node_type.h" - -/*============================================================================== - * The VTYSH uses a unix socket to talk to the daemon. - * - * The ability to respond to a connection from VTYSH appears to be a *compile* - * time option. In the interests of keeping the code up to date, the VTYSH - * option is turned into a testable constant. - */ -#ifdef VTYSH - enum { VTYSH_ENABLED = true } ; -#else - enum { VTYSH_ENABLED = false } ; -#endif - -/*============================================================================== - * VTY Types - */ -enum vty_type /* Command output */ -{ - VTY_TERMINAL, /* a telnet terminal server */ - VTY_SHELL_SERVER, /* a vty_shell server */ - - VTY_SHELL_CLIENT, /* a vty_shell client */ - - VTY_CONFIG_READ, /* configuration file reader */ - - VTY_STDOUT, /* stdout */ - VTY_STDERR, /* stderr */ -} ; -typedef enum vty_type vty_type_t ; - -/*============================================================================== - * - * - * - * - */ - -typedef unsigned long vty_timer_time ; /* Time out time in seconds */ - -enum -{ - VTY_WATCH_DOG_INTERVAL = 5, /* interval between barks */ - - VTY_HALF_CLOSE_TIMEOUT = 120, /* timeout after half_close */ - - VTY_TIMEOUT_DEFAULT = 600, /* terminal timeout value */ -} ; /*============================================================================== - * VTY struct. - */ - -typedef struct vty_io* vty_io ; /* private to vty.c */ - -struct cmd_parsed ; /* in case vty.h expanded before command.h */ - -typedef struct vty* vty ; -struct vty -{ - vty_type_t type ; - - /*---------------------------------------------------------------------- - * The following are the context in which commands are executed. - */ - - /* Node status of this vty - * - * NB: when using qpthreads should lock VTY to access this -- so use - * vty_get_node() and vty_set_node(). - */ - enum node_type node ; - - /* For current referencing point of interface, route-map, access-list - * etc... - * - * NB: this value is private to the command execution, which is assumed - * to all be in the one thread... so no lock required. - */ - void *index ; - - /* For multiple level index treatment such as key chain and key. - * - * NB: this value is private to the command execution, which is assumed - * to all be in the one thread... so no lock required. - */ - void *index_sub ; - - /* In configure mode. */ - bool config; - - /*---------------------------------------------------------------------------- - * The current command line. - * - * These are set when a command is parsed and dispatched. - * - * They are not touched until the command completes -- so may be read while - * the command is being parsed and executed. - */ - const char* buf ; - struct cmd_parsed* parsed ; - unsigned lineno ; - - bool output_enabled ; - bool reflect_enabled ; - bool more_enabled ; - - bool reflected ; - - /*---------------------------------------------------------------------- - * The following are used inside vty.c only. - */ - vty_io vio ; /* one vio object per vty */ -} ; - -/*------------------------------------------------------------------------------ - * Can now include this + * These are definitions and functions for things which are required outside + * the vty and command family. */ -#include "command.h" - /*------------------------------------------------------------------------------ * */ @@ -187,13 +75,6 @@ enum { VTY_MAX_SPACES = 40 } ; #define IS_DIRECTORY_SEP(c) ((c) == DIRECTORY_SEP) #endif -/* GCC have printf type attribute check. */ -#ifdef __GNUC__ -#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) -#else -#define PRINTF_ATTRIBUTE(a,b) -#endif /* __GNUC__ */ - /* Utility macros to convert VTY argument to unsigned long or integer. */ #define VTY_GET_LONG(NAME,V,STR) \ do { \ @@ -267,22 +148,23 @@ extern void vty_reset_because(const char* why) ; extern int vty_out (struct vty *, const char *, ...) PRINTF_ATTRIBUTE(2, 3); extern int vty_out_indent(struct vty *vty, int indent) ; -extern int vty_out_error (struct vty *vty, const char *format, ...) - PRINTF_ATTRIBUTE(2, 3); extern void vty_out_clear(struct vty *vty) ; -extern void vty_read_config (char *config_file, char *config_default); -extern void vty_read_config_first_cmd_special( - char *config_file, char *config_default, - struct cmd_element* first_cmd, bool ignore_warnings) ; + + + + +extern void vty_read_config (const char* config_file, + const char* config_default); +extern void vty_read_config_first_cmd_special(const char* config_file, + const char* config_default, + cmd_command first_cmd, + bool ignore_warnings) ; extern void vty_time_print (struct vty *, int); extern char *vty_get_cwd (void); extern void vty_hello (struct vty *); -extern enum node_type vty_get_node(struct vty *); -extern void vty_set_node(struct vty *, enum node_type); -extern void vty_set_lines(struct vty *, int); #endif /* _ZEBRA_VTY_H */ diff --git a/lib/vty_cli.c b/lib/vty_cli.c index 17351f58..42d78349 100644 --- a/lib/vty_cli.c +++ b/lib/vty_cli.c @@ -20,17 +20,20 @@ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ +#include "misc.h" +#include -#include - -#include "keystroke.h" -#include "vty.h" -#include "uty.h" +#include "vty_local.h" +#include "vty_command.h" #include "vty_cli.h" +#include "vty_io_term.h" #include "vty_io.h" #include "vio_lines.h" +#include "vio_fifo.h" + +#include "keystroke.h" -#include "command.h" +#include "command_common.h" #include "command_parse.h" #include "command_execute.h" #include "command_queue.h" @@ -38,86 +41,323 @@ #include "memory.h" /*============================================================================== - * Host name handling + * Construct and destroy CLI object + * * - * The host name is used in the command line prompt. The name used is either - * the name set by "hostname" command, or the current machine host name. * - * Static variables -- under the VTY_LOCK ! */ +static bool uty_cli_iac_callback(keystroke_iac_callback_args) ; +static void uty_cli_update_more(vty_cli cli) ; -static char* vty_host_name = NULL ; -int vty_host_name_set = 0 ; +/*------------------------------------------------------------------------------ + * 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. + * + * Note that the vty_io_term stuff sends out a bunch of telnet commands, which + * will be buffered in the CLI buffer. That will be output, before the + * start-up messages etc. on uty_cli_start(). + */ +extern vty_cli +uty_cli_new(vio_vf vf) +{ + vty_cli cli ; -static void uty_new_host_name(const char* name) ; + cli = XCALLOC(MTYPE_VTY_CLI, sizeof(struct vty_cli)) ; + + /* Zeroising has initialised: + * + * vf = NULL -- set below + * + * hist = empty vector (embedded) -- set up when first used. + * hp = 0 -- hp == h_now => in the present ... + * h_now = 0 -- ... see uty_cli_hist_add() + * + * width = 0 -- unknown width ) Telnet window size + * height = 0 -- unknown height ) + * + * lines = 0 -- set below + * lines_set = false -- set below + * + * monitor = false -- not a "monitor" + * monitor_busy = false -- so not busy either + * + * v_timeout = ??? + * + * key_stream = X -- set below + * + * drawn = false + * dirty = false + * + * prompt_len = 0 -- not drawn, in any case + * extra_len = 0 -- not drawn, in any case + * + * echo_suppress = false + * + * prompt_node = NULL_NODE -- so not set ! + * prompt_gen = 0 -- not a generation number + * prompt_for_node = empty qstring (embedded) + * + * password_fail = 0 -- so far, so good. + * + * blocked = false -- see below + * in_progress = false -- see below + * out_active = false + * + * more_wait = false + * more_active = false + * + * out_done = false -- not that it matters + * + * more_enabled = false -- not in "--More--" state + * + * node = NULL_NODE -- set in vty_cli_start() + * to_do = cmd_do_nothing + * + * cl = NULL qstring -- set below + * clx = NULL qstring -- set below + * + * parsed = all zeros -- empty parsed object + * cbuf = empty fifo (embedded) + * + * olc = empty line control (embedded) + */ + confirm(VECTOR_INIT_ALL_ZEROS) ; /* hist */ + confirm(QSTRING_INIT_ALL_ZEROS) ; /* prompt_for_node */ + confirm(NULL_NODE == 0) ; /* prompt_node & node */ + confirm(cmd_do_nothing == 0) ; /* to_do */ + confirm(VIO_FIFO_INIT_ALL_ZEROS) ; /* cbuf */ + confirm(VIO_LINE_CONTROL_INIT_ALL_ZEROS) ; /* olc */ + confirm(CMD_PARSED_INIT_ALL_ZEROS) ; /* parsed */ + + cli->vf = vf ; + + /* Allocate and initialise a keystroke stream TODO: CSI ?? */ + cli->key_stream = keystroke_stream_new('\0', uty_cli_iac_callback, cli) ; + + /* 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) ; + + vio_fifo_init_new(cli->cbuf, 500) ; /* just for the CLI stuff */ + + /* Use global 'lines' setting, as default -- but 'lines_set' false. */ + uty_cli_set_lines(cli, host.lines, false) ; + uty_cli_update_more(cli) ; /* and update the olc etc to suit. */ + + /* Ready to be started -- out_active & more_wait are false. */ + cli->blocked = true ; + cli->in_progress = true ; + + return cli ; +} ; /*------------------------------------------------------------------------------ - * Update vty_host_name as per "hostname" or "no hostname" command + * Start the CLI. + * + * The implied start "command" is complete and everything is ready to start + * the CLI. + * + * Note that before releasing any pending output, calls + * + * Returns: write_ready -- so the first event is a write event, to flush + * any output to date. */ extern void -uty_set_host_name(const char* name) +uty_cli_start(vty_cli cli, node_type_t node) { + uty_cli_done_command(cli, node) ; /* implied push output */ +} ; + +/*------------------------------------------------------------------------------ + * Close CLI object, if any -- may be called more than once. + * + * Shuts down and discards anything to do with the input. + * + * Revokes anything that can be revoked, which may allow output to proceed + * and the vty to clear itself down. + * + * If in_progress and not final, keeps the cbuf, the olc and clx if in_progress. + * If not final, keeps the cbuf and the olc. + * + * Destroys self if final. + * + * Expects other code to: + * + * - close down command loop and empty the vin/vout stacks. + * + * - turn of any monitor status. + * + * - half close the telnet connection. + */ +extern vty_cli +uty_cli_close(vty_cli cli, bool final) +{ + if (cli == NULL) + return NULL ; + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; - vty_host_name_set = (name != NULL) ; + /* Revoke any command that is in flight. */ + cq_revoke(cli->vf->vio->vty) ; - if (vty_host_name_set) - uty_new_host_name(name) ; - else - uty_check_host_name() ; + /* Bring as much of the command handling to a stop as possible, and + * turn off "---more--" etc. so that output can proceed. + */ + cli->more_enabled = false ; + cli->lines = 0 ; /* so will not be re-enabled */ + + uty_cli_wipe(cli, 0) ; /* wipe anything the CLI has on screen */ + + cli->more_wait = false ; /* exit more_wait (if was in it) */ + cli->more_active = false ; /* and so cannot be this */ + + cli->out_active = true ; /* if there is any output, it can go */ + + /* Ream out the history. */ + { + qstring line ; + while ((line = vector_ream(cli->hist, keep_it)) != NULL) + qs_reset(line, free_it) ; + } ; + + /* Empty the keystroke handling. */ + cli->key_stream = keystroke_stream_free(cli->key_stream) ; + + /* Can discard active command line if not in_progress. */ + if (!cli->in_progress || final) + cli->clx = qs_reset(cli->clx, free_it) ; + + /* Can discard parsed object. */ + cmd_parsed_reset(cli->parsed, keep_it) ; + + /* If final, free the CLI object. */ + if (final) + { + qs_reset(cli->prompt_for_node, keep_it) ; + cli->cl = qs_reset(cli->cl, free_it) ; + + vio_fifo_reset(cli->cbuf, keep_it) ; /* embedded fifo */ + vio_lc_reset(cli->olc, keep_it) ; /* embedded line control */ + + XFREE(MTYPE_VTY_CLI, cli) ; /* sets cli = NULL */ + } ; + + return cli ; } ; /*------------------------------------------------------------------------------ - * If vty_host_name is set, free it. + * The keystroke iac callback function. + * + * This deals with IAC sequences that should be dealt with as soon as they + * are read -- not stored in the keystroke stream for later processing. */ -extern void -uty_free_host_name(void) +static bool +uty_cli_iac_callback(keystroke_iac_callback_args) { - if (vty_host_name != NULL) - XFREE (MTYPE_HOST, vty_host_name) ; /* sets vty_host_name = NULL */ + return uty_telnet_command(((vty_cli)context)->vf, stroke, true) ; } ; /*------------------------------------------------------------------------------ - * If the host name is not set by command, see if the actual host name has - * changed, and if so change it. + * Set the cli->lines, explicitly or implicitly, and update the "--more--" + * state to suit. * - * This is done periodically in case the actual host name changes ! + * lines < 0 => use whatever telnet has said, or no "--more--" + * lines = 0 => no "--more--" + * lines > 0 => use */ extern void -uty_check_host_name(void) +uty_cli_set_lines(vty_cli cli, int lines, bool explicit) { - struct utsname names ; - - VTY_ASSERT_LOCKED() ; + cli->lines = lines ; + cli->lines_set = explicit ; - if (vty_host_name_set) - return ; /* nothing to do if set by command */ + uty_cli_update_more(cli) ; +} ; - uname (&names) ; +/*------------------------------------------------------------------------------ + * Set the cli->lines, explicitly or implicitly, and update the "--more--" + * state to suit. + * + * lines < 0 => use whatever telnet has said, or no "--more--" + * lines = 0 => no "--more--" + * lines > 0 => + */ +extern void +uty_cli_set_window(vty_cli cli, int width, int height) +{ + cli->width = width ; + cli->height = height ; - if ((vty_host_name == NULL) || (strcmp(vty_host_name, names.nodename) != 0)) - uty_new_host_name(names.nodename) ; + uty_cli_update_more(cli) ; } ; /*------------------------------------------------------------------------------ - * Set new vty_host_name and run along list of VTYs to mark the change. + * Update the "--more--" state, after changing one or more of: + * + * cli->lines + * cli->lines_set + * cli->width + * cli->height + * + * Set the effective height for line control, if required, which may enable the + * "--more--" output handling. + * + * If not, want some limit on the amount of stuff output at a time. + * + * Sets the line control window width and height. + * Sets cli_more_enabled if "--more--" is enabled. */ static void -uty_new_host_name(const char* name) +uty_cli_update_more(vty_cli cli) { - vty_io vio ; - - VTY_ASSERT_LOCKED() ; + bool on ; - uty_free_host_name() ; - vty_host_name = XSTRDUP(MTYPE_HOST, name) ; + on = false ; /* default state */ - vio = vio_list_base ; - while (vio != NULL) + if (cli->vf->vout_state == vf_open) { - vio->cli_prompt_set = 0 ; - vio = sdl_next(vio, vio_list) ; + int height ; + + height = 0 ; /* default state: no "--more--" */ + + if ((cli->width) != 0) + { + /* If window size is known, use lines or given height */ + if (cli->lines >= 0) + height = cli->lines ; + else + { + /* Window height, leaving one line from previous "page" + * and one line for the "--more--" -- if at all possible + */ + height = cli->height - 2 ; + if (height < 1) + height = 1 ; + } ; + } + else + { + /* If window size not known, use lines if that has been set + * explicitly for this terminal. + */ + if (cli->lines_set) + height = cli->lines ; + } ; + + if (height > 0) + on = true ; /* have a defined height */ + else + height = 200 ; /* but no "--more--" */ + + vio_lc_set_window(cli->olc, cli->width, height) ; } ; + + cli->more_enabled = on ; } ; /*============================================================================== @@ -132,31 +372,49 @@ uty_new_host_name(const char* name) * -- setting read or write on, means enabling the file for select/pselect to * consider it for read_ready or write_ready, respectively. * + * All command actions are dispatched via the command queue -- commands are + * not executed in the cli. [For legacy threads the event queue is used. If + * that is too slow, a priority event queue will have to be invented.] + * * State of the CLI: * - * cli_blocked -- the CLI is unable to process any further keystrokes. + * in_progress -- a command has been dispatched and has not yet completed. + * + * The command may be a in_pipe, so there may be many commands + * to be completed before the CLI level command is. * - * cmd_in_progress -- a command has been dispatched and has not yet - * completed (may have been queued). + * or: the CLI has been closed. * - * cmd_out_enabled -- the command FIFO is may be emptied. + * blocked -- is in_progress and a further command is now ready to be + * dispatched. * - * This is set when a command completes, and cleared when - * everything is written away. + * or: the CLI has been closed. * - * cli_more_wait -- is in "--more--" wait state + * 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. * * The following are the valid combinations: * - * blkd : cip : o_en : m_wt : - * -----:------:------:------:-------------------------------------------- - * 0 : 0 : 0 : 0 : collecting a new command - * 0 : 1 : 0 : 0 : command dispatched - * 1 : 1 : 0 : 0 : waiting for (queued) command to complete - * 1 : 0 : 1 : 0 : waiting for command output to complete - * 1 : 0 : 0 : 1 : waiting for "--more--" to be written away - * 0 : 0 : 0 : 1 : waiting for "--more--" response - * 1 : 1 : 1 : 0 : waiting for command to complete, after the + * in_p: blkd: o_ac: m_wt: m_ac: + * ----:-----:-----:-----:-----:----------------------------------------- + * 0 : 0 : 0 : 0 : 0 : collecting a new command + * 0 : 0 : 1 : 0 : 0 : waiting for command output to finish + * 1 : 0 : X : 0 : 0 : command dispatched + * 1 : 1 : X : 0 : 0 : waiting for command to complete + * X : X : 0 : 1 : 1 : waiting for "--more--" to be written away + * X : X : 0 : 1 : 0 : waiting for "--more--" response + * 1 : 1 : 1 : 0 : 0 : waiting for command to complete, after the * CLI has been closed * * There are two output FIFOs: @@ -166,9 +424,9 @@ uty_new_host_name(const char* name) * 2. for output generated by commands -- vty_out and friends. * * The CLI FIFO is emptied whenever possible, in preference to the command - * FIFO. The command FIFO is emptied when cmd_out_enabled. While - * cmd_in_progress is also !cmd_out_enabled -- so that all the output from a - * given command is collected together before being sent to the file. + * FIFO. The command FIFO is emptied when out_active. While a command is + * in_progress all its output is collected in the command output buffer, + * to be written away when the command completes. * * Note that only sets read on when the keystroke stream is empty and has not * yet hit eof. The CLI process is driven mostly by write_ready -- which @@ -188,32 +446,20 @@ uty_new_host_name(const char* name) * * This is largely buried in the output handling. * - * While cmd_in_progress is true cmd_out_enabled will be false. When the - * command completes: - * - * * cmd_in_progress is cleared - * - * * cmd_out_enabled is set - * - * * cli_blocked will be set - * - * * the line_control structure is reset - * - * * the output process is kicked off by setting write on + * When a command completes and its output is pushed to written away, + * out_active will be set (and any command line will be wiped). The output + * process is then kicked. * * The output process used the line_control structure to manage the output, and * occasionally enter the trivial "--more--" CLI. This is invisible to the - * main CLI. (See the cli_more_wait flag and its handling.) + * main CLI. (See the more_wait and more_active flags and their handling.) * - * When all the output has completed the CLI will be kicked, which will see - * that the output buffer is now empty, and it can proceed. + * When all the output has completed the out_active flag is cleared and the CLI + * will be kicked. * * It is expected that the output will end with a newline -- so that when the * CLI is kicked, the cursor will be at the start of an empty line. * - * This mechanism means that the command output FIFO only ever contains the - * output from the last command executed. - * * If the user decides to abandon output at the "--more--" prompt, then the * contents of the command output FIFO are discarded. * @@ -265,102 +511,15 @@ uty_new_host_name(const char* name) #define CONTROL(X) ((X) & 0x1F) -static void uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret) ; -static enum vty_readiness uty_cli_standard(vty_io vio) ; -static enum vty_readiness uty_cli_more_wait(vty_io vio) ; -static void uty_cli_draw(vty_io vio) ; -static void uty_cli_draw_this(vty_io vio, enum node_type node) ; -static void uty_cli_wipe(vty_io vio, int len) ; - -static void uty_will_echo (vty_io vio) ; -static void uty_will_suppress_go_ahead (vty_io vio) ; -static void uty_dont_linemode (vty_io vio) ; -static void uty_do_window_size (vty_io vio) ; -static void uty_dont_lflow_ahead (vty_io vio) ; +static enum vty_readiness uty_cli_standard(vty_cli cli) ; +static enum vty_readiness uty_cli_more_wait(vty_cli cli) ; +static void uty_cli_draw(vty_cli cli) ; +static void uty_cli_draw_this(vty_cli cli, node_type_t node) ; /*------------------------------------------------------------------------------ - * 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 for VTY_TERMINAL * - * cli_more_enabled - * - * Sets the CLI such that there is apparently a command in progress, so that - * further initialisation (in particular hello messages and the like) is - * treated as a "start up command". - * - * Sends a suitable set of Telnet commands to start the process. - */ -extern void -uty_cli_init(vty_io vio) -{ - assert(vio->vty->type == VTY_TERMINAL) ; - - vio->cmd_in_progress = 1 ; - vio->cli_blocked = 1 ; - - vio->cli_do = cli_do_nothing ; - - /* Setting up terminal. */ - uty_will_echo (vio); - uty_will_suppress_go_ahead (vio); - uty_dont_linemode (vio); - uty_do_window_size (vio); - if (0) - uty_dont_lflow_ahead (vio) ; -} ; - -/*------------------------------------------------------------------------------ - * Start the CLI. - * - * All start-up operations are complete -- so the "command" is now complete. - * - * Returns: write_ready -- so the first event is a write event, to flush - * any output to date. - */ -extern enum vty_readiness -uty_cli_start(vty_io vio) -{ - uty_cli_cmd_complete(vio, CMD_SUCCESS) ; - return write_ready ; -} ; - -/*------------------------------------------------------------------------------ - * 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->vty->type == VTY_TERMINAL) ; - - cq_revoke(vio->vty) ; - - vio->cli_blocked = 1 ; /* don't attempt any more */ - vio->cmd_out_enabled = 1 ; /* allow output to clear */ -} ; - -/*------------------------------------------------------------------------------ - * CLI for VTY_TERM + * Called by uty_term_ready(), so driven by read/write ready. * * Do nothing at all if half closed. * @@ -370,29 +529,33 @@ uty_cli_close(vty_io vio) * NB: on return, requires that an attempt is made to write away anything that * may be ready for that. */ -extern enum vty_readiness -uty_cli(vty_io vio) +extern vty_readiness_t +uty_cli(vty_cli cli) { VTY_ASSERT_LOCKED() ; - assert(vio->vty_type == VTY_TERM) ; - if (vio->half_closed) - return not_ready ; /* Nothing more if half closed */ + if (cli->vf->vin_state != vf_open) + return not_ready ; /* Nothing more from CLI if closing */ /* Standard or "--more--" CLI ? */ - if (vio->cli_more_wait) - return uty_cli_more_wait(vio) ; + if (cli->more_wait) + return uty_cli_more_wait(cli) ; else - return uty_cli_standard(vio) ; + return uty_cli_standard(cli) ; } ; /*============================================================================== * The Standard CLI */ -static enum cli_do uty_cli_process(vty_io vio, enum node_type node) ; -static void uty_cli_response(vty_io vio, enum cli_do cli_do) ; -static bool uty_cli_dispatch(vty_io vio) ; +static cmd_do_t uty_cli_process(vty_cli cli, node_type_t node) ; +static void uty_cli_response(vty_cli cli, cmd_do_t cmd_do) ; + + +static void uty_cli_dispatch(vty_cli cli) ; +static cmd_do_t uty_cli_command(vty_cli cli) ; +static void uty_cli_hist_add (vty_cli cli, qstring clx) ; + /*------------------------------------------------------------------------------ * Standard CLI for VTY_TERM -- if not blocked, runs until: @@ -408,34 +571,21 @@ static bool uty_cli_dispatch(vty_io vio) ; * write_ready if there is anything in the keystroke stream * read_ready otherwise */ -static enum vty_readiness -uty_cli_standard(vty_io vio) +static vty_readiness_t +uty_cli_standard(vty_cli cli) { VTY_ASSERT_LOCKED() ; - assert(vio->vty_type == VTY_TERM) ; - /* cli_blocked is set when is waiting for a command, or its output to - * complete -- unless either of those has happened, is still blocked. + assert(!cli->more_wait) ; /* cannot be here in more_wait state ! */ + + /* cli_blocked is set when is waiting for a command, or some output to + * complete. * * NB: in both these cases, assumes that other forces are at work to * keep things moving. */ - if (vio->cli_blocked) - { - assert(vio->cmd_in_progress || vio->cmd_out_enabled) ; - - if (vio->cmd_in_progress) - { - assert(!vio->cmd_out_enabled) ; - return not_ready ; - } ; - - if (!vio_fifo_empty(&vio->cmd_obuf)) - return not_ready ; - - vio->cli_blocked = 0 ; - vio->cmd_out_enabled = 0 ; - } ; + if (cli->blocked || cli->out_active) + return not_ready ; /* If there is nothing pending, then can run the CLI until there is * something to do, or runs out of input. @@ -444,26 +594,26 @@ uty_cli_standard(vty_io vio) * now completed, which may have wiped the pending command or changed * the required prompt. */ - if (vio->cli_do == cli_do_nothing) - vio->cli_do = uty_cli_process(vio, vio->vty->node) ; + if (cli->to_do == cmd_do_nothing) + cli->to_do = uty_cli_process(cli, cli->node) ; else - uty_cli_draw_this(vio, vio->vty->node) ; + uty_cli_draw_this(cli, cli->node) ; - /* If have something to do, do it. */ - if (vio->cli_do != cli_do_nothing) + /* If have something to do, do it if we can. */ + if (cli->to_do != cmd_do_nothing) { - /* Reflect immediate response */ - uty_cli_response(vio, vio->cli_do) ; + /* Reflect immediate response */ + uty_cli_response(cli, cli->to_do) ; /* If command not already in progress, dispatch this one, which may * set the CLI blocked. * * Otherwise is now blocked until queued command completes. */ - if (!vio->cmd_in_progress) - vio->cli_blocked = uty_cli_dispatch(vio) ; + if (cli->in_progress) + cli->blocked = true ; /* blocked waiting for previous */ else - vio->cli_blocked = 1 ; + uty_cli_dispatch(cli) ; /* can dispatch the latest */ } ; /* Use write_ready as a proxy for read_ready on the keystroke stream. @@ -475,166 +625,164 @@ uty_cli_standard(vty_io vio) * defensive: at worst will generate one unnecessary read_ready/write_ready * event. */ - if (keystroke_stream_empty(vio->key_stream)) + if (keystroke_stream_empty(cli->key_stream)) return read_ready ; else return write_ready ; } ; /*------------------------------------------------------------------------------ - * Dispatch the current vio->cli_do -- queueing it if necessary. + * Dispatch the current cli->to_do -- queueing it if necessary. * * Requires that are NOT blocked and NO command is queued. * * Expects to be on new blank line, and when returns will be on new, blank * line. * - * Returns: true <=> command completed and output is pending - * false => command has been queued and is now in progress - * - * Generally sets vio->cl_do = cli_do_nothing and clears vio->cl to empty. + * Generally sets cli->to_do = cmd_do_nothing and clears cli->cl to empty. * - * Can set vio->cl_do = and vio->cl to be a follow-on command. + * Can set cli->cl_do = and cli->cl to be a follow-on command. */ -static bool -uty_cli_dispatch(vty_io vio) +static void +uty_cli_dispatch(vty_cli cli) { - qstring_t tmp ; - enum cli_do cli_do ; - enum cmd_return_code ret ; + qstring tmp ; + cmd_do_t to_do_now ; - struct vty* vty = vio->vty ; + vty_io vio = cli->vf->vio ; VTY_ASSERT_LOCKED() ; - assert(!vio->cli_blocked && !vio->cmd_in_progress) ; - /* Set vio->clx to the command about to execute. + /* About to dispatch a command, so must be in the following state. */ + assert(!cli->in_progress && !cli->out_active && !cli->blocked) ; + assert(cli->node == vio->vty->node) ; + + /* Set cli->clx to the command about to execute & pick up cli->to_do. * - * Clear vio->cl and vio->cl_do. + * Clear cli->cl and cli->to_do. */ - vio->cmd_in_progress = 1 ; /* => vty->buf is valid */ - vio->cmd_out_enabled = 0 ; /* => collect all output */ + tmp = cli->clx ; /* swap clx and cl */ + cli->clx = cli->cl ; + cli->cl = tmp ; - tmp = vio->clx ; /* swap clx and cl */ - vio->clx = vio->cl ; - vio->cl = tmp ; + to_do_now = cli->to_do ; /* current operation */ - qs_term(&vio->clx) ; /* ensure string is terminated */ - vty->buf = qs_chars(&vio->clx) ; /* terminated command line */ - cli_do = vio->cli_do ; /* current operation */ + cli->to_do = cmd_do_nothing ; /* clear */ + qs_clear(cli->cl) ; /* set cl empty */ - 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 */ + /* Reset the command output FIFO and line_control TODO */ +//uty_out_clear(cli->vio) ; /* clears FIFO and line control */ /* Dispatch command */ - if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE)) - { - /* AUTH_NODE and AUTH_ENABLE_NODE are unique */ - ret = uty_auth(vty, vty->buf, cli_do) ; - } + if ((cli->node == AUTH_NODE) && (to_do_now != cmd_do_nothing)) + to_do_now |= cmd_do_auth ; + else if ((cli->node == AUTH_ENABLE_NODE) && (to_do_now != cmd_do_nothing)) + to_do_now |= cmd_do_auth_enable ; else { /* All other nodes... */ - switch (cli_do) + switch (to_do_now) { - case cli_do_nothing: - ret = CMD_SUCCESS ; + case cmd_do_nothing: + case cmd_do_ctrl_c: + case cmd_do_eof: break ; - case cli_do_command: - ret = uty_command(vty) ; + case cmd_do_command: + to_do_now = uty_cli_command(cli) ; break ; - case cli_do_ctrl_c: - ret = uty_stop_input(vty) ; + case cmd_do_ctrl_d: + zabort("invalid cmd_do_ctrl_d") ; break ; - case cli_do_ctrl_d: - ret = uty_down_level(vty) ; - break ; + case cmd_do_ctrl_z: + to_do_now = uty_cli_command(cli) ; - case cli_do_ctrl_z: - ret = uty_command(vty) ; - if (ret == CMD_QUEUED) - vio->cli_do = cli_do_ctrl_z ; /* defer the ^Z action */ + if (to_do_now == cmd_do_nothing) + to_do_now = cmd_do_ctrl_z ; else - ret = uty_end_config(vty) ; /* do the ^Z now */ - break ; + cli->to_do = cmd_do_ctrl_z ; /* defer the ^Z */ - case cli_do_eof: - ret = uty_cmd_close(vio->vty, "End") ; break ; default: - zabort("unknown cli_do_xxx value") ; + zabort("unknown cmd_do_xxx value") ; } ; } ; - if (ret == CMD_QUEUED) + cli->hp = cli->h_now ; /* in any event, back to the present */ + + if (to_do_now != cmd_do_nothing) { - uty_cli_draw(vio) ; /* draw the prompt */ - return false ; /* command not complete */ + cli->in_progress = true ; + uty_cmd_dispatch(vio, to_do_now, cli->clx) ; } else - { - uty_cli_cmd_complete(vio, ret) ; - return true ; /* command complete */ - } ; + uty_cli_draw(cli) ; } ; /*------------------------------------------------------------------------------ - * Queued command has completed. + * Enqueue command -- adding to history -- if is not empty or just comment * - * Note that sets write on whether there is anything in the output buffer - * or not... write_ready will kick read_ready. + * This is for VTY_TERM type VTY. + * + * Returns: CMD_SUCCESS => empty command line (or just comment) + * CMD_WAITING => enqueued for parse and execute */ -extern void -vty_queued_result(struct vty *vty, enum cmd_return_code ret) +static cmd_do_t +uty_cli_command(vty_cli cli) { - vty_io vio ; + VTY_ASSERT_LOCKED() ; + VTY_ASSERT_CLI_THREAD() ; - VTY_LOCK() ; + if (cmd_is_empty(qs_els_nn(cli->clx))) + return cmd_do_nothing ; /* easy when nothing to do ! */ - vio = vty->tos ; + /* Add not empty command to history */ + uty_cli_hist_add(cli, cli->clx) ; - if (!vio->closed) - { - uty_cli_wipe(vio, 0) ; /* wipe any partly constructed line */ + return cmd_do_command ; +} ; - /* Do the command completion actions that were deferred because the - * command was queued. - * - * Return of CMD_QUEUED => command was revoked before being executed. - * However interesting that might be... frankly don't care. - */ - uty_cli_cmd_complete(vio, ret) ; +/*------------------------------------------------------------------------------ + * A command has completed, and there is (or may be) some output to now + * write away. + */ +extern void +uty_cli_out_push(vty_cli cli) +{ + VTY_ASSERT_LOCKED() ; - /* Kick the socket -- to write away any outstanding output, and - * re-enter the CLI when that's done. - */ - uty_sock_set_readiness(&vio->sock, write_ready) ; - } - else + if (cli->more_wait) + return ; /* can do nothing */ + + if (vio_fifo_empty(cli->vf->obuf)) { - /* If the VTY is closed, the only reason it still exists is because - * there was cmd_in_progress. - */ - vio->cmd_in_progress = 0 ; /* death watch will apply coup de grace */ + assert(!cli->out_active ) ; + return ; /* need do nothing if is empty */ } ; - VTY_UNLOCK() ; -} + uty_cli_wipe(cli, 0) ; /* wipe any partly constructed line */ + + cli->out_active = true ; /* enable the output */ + + uty_term_set_readiness(cli->vf, write_ready) ; + /* kick the write side into action */ +} ; /*------------------------------------------------------------------------------ + * Queued command has completed. + * + * Note that sets write on whether there is anything in the output buffer + * or not... write_ready will kick read_ready. + + * Command has completed, so: * * * clear cmd_in_progress - * * set cmd_out_enabled -- so any output can now proceed - * * set cli_blocked -- waiting for output to complete + * * set cmd_out_active -- so any output can now proceed + * * set cli_blocked -- waiting for output to complete * * and prepare the line control for output * * If the return is CMD_CLOSE, then also now does the required half close. @@ -650,33 +798,153 @@ vty_queued_result(struct vty *vty, enum cmd_return_code ret) * * It also means that the decision about whether there is anything to output * is left to the output code. + + + */ -static void -uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret) +extern void +uty_cli_done_command(vty_cli cli, node_type_t node) { VTY_ASSERT_LOCKED() ; - assert(vio->cmd_in_progress && !vio->cmd_out_enabled) ; + uty_cli_out_push(cli) ; /* just in case */ + + cli->in_progress = false ; /* command complete */ + cli->blocked = false ; /* releases possibly blocked command */ + cli->node = node ; /* and now in this node */ + + /* Reach all the way back, and get the now current node. */ + cli->node = cli->vf->vio->vty->node ; + + uty_term_set_readiness(cli->vf, write_ready) ; + /* kick the write side into action */ +} ; + +/*============================================================================== + * All commands are dispatched to command_queue. + * + * Some commands are VLI specific... and end up back here. + */ + +/*------------------------------------------------------------------------------ + * Authentication of vty + * + * Note that if the AUTH_NODE password fails too many times, the terminal is + * closed. + * + * Returns: CMD_SUCCESS -- OK, one way or another + * CMD_WARNING -- with error message sent to output + * CMD_CLOSE -- too many password failures + */ +extern cmd_return_code_t +uty_cli_auth(vty_cli cli) +{ + char *crypt (const char *, const char *); + + char* passwd = NULL ; + bool encrypted = false ; + node_type_t next_node = 0 ; + cmd_return_code_t ret ; + vty_io vio ; + cmd_exec exec ; + + VTY_ASSERT_LOCKED() ; + + vio = cli->vf->vio ; + exec = vio->vty->exec ; + + /* Deal with the exotic terminators. */ + switch (exec->to_do & cmd_do_mask) + { + case cmd_do_nothing: + case cmd_do_ctrl_c: + case cmd_do_ctrl_z: + return CMD_SUCCESS ; + + case cmd_do_command: + break ; + + case cmd_do_ctrl_d: + case cmd_do_eof: + return uty_cmd_close(vio, "End") ; + + default: + zabort("unknown or invalid cmd_do") ; + } ; + + /* Ordinary command dispatch -- see if password is OK. + * + * Select the password we need to check against. + */ + if ((exec->to_do & ~cmd_do_mask) == cmd_do_auth) + { + passwd = host.password ; + encrypted = host.password_encrypted ; + + if (host.advanced) + next_node = host.enable ? VIEW_NODE : ENABLE_NODE; + else + next_node = VIEW_NODE; + } + else if ((exec->to_do & ~cmd_do_mask) == cmd_do_auth_enable) + { + passwd = host.enable ; + encrypted = host.enable_encrypted ; - if (ret == CMD_CLOSE) - uty_close(vio, NULL) ; + next_node = ENABLE_NODE; + } + else + zabort("unknown to_do_value") ; - vio->cmd_in_progress = 0 ; /* command complete */ - vio->cmd_out_enabled = 1 ; /* enable the output */ - vio->cli_blocked = 1 ; /* now blocked waiting for output */ + /* Check against selected password (if any) */ + if (passwd != NULL) + { + char* candidate = qs_make_string(exec->line) ; - vio->vty->buf = NULL ; /* finished with command line */ + if (encrypted) + candidate = crypt(candidate, passwd) ; - uty_cmd_output_start(vio) ; /* reset line control (belt & braces) */ + if (strcmp(candidate, passwd) == 0) + { + cli->password_fail = 0 ; /* forgive any recent failures */ + vio->vty->node = next_node; + + return CMD_SUCCESS ; /* <<< SUCCESS <<<<<<<< */ + } ; + } ; + + /* 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 ret ; } ; /*============================================================================== * The "--more--" CLI * - * While command output is being cleared from its FIFO, the CLI is cli_blocked. + * While command output is being cleared from its FIFO, the CLI is blocked. * * When the output side signals that "--more--" is required, it sets the - * cli_more_wait flag and clears the cmd_out_enabled flag. + * more_wait flag and clears the out_active flag. * * The first stage of handling "--more--" is to suck the input dry, so that * (as far as is reasonably possible) does not steal a keystroke as the @@ -691,19 +959,20 @@ uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret) * Outputs the new prompt line. */ extern void -uty_cli_enter_more_wait(vty_io vio) +uty_cli_enter_more_wait(vty_cli cli) { VTY_ASSERT_LOCKED() ; - assert(vio->cli_blocked && vio->cmd_out_enabled && !vio->cli_more_wait) ; + assert(cli->out_active && !cli->more_wait) ; - uty_cli_wipe(vio, 0) ; /* make absolutely sure that command line is + uty_cli_wipe(cli, 0) ; /* make absolutely sure that command line is wiped before change the CLI state */ - vio->cmd_out_enabled = 0 ; /* stop output pro tem */ - vio->cli_more_wait = 1 ; /* new state */ + cli->out_active = false ; /* stop output pro tem */ + cli->more_wait = true ; /* new state */ + cli->more_active = true ; /* drawing the "--more--" */ - uty_cli_draw(vio) ; /* draw the "--more--" */ + uty_cli_draw(cli) ; /* draw the "--more--" */ } ; /*------------------------------------------------------------------------------ @@ -717,27 +986,26 @@ uty_cli_enter_more_wait(vty_io vio) * possible that the '--more--' prompt is yet to be completely written away, * so: * - * * assert that is either: !vio->cli_blocked (most of the time it will) - * or: !vio_fifo_empty(&vio->cli_obuf) + * * assert that is either: !cli->blocked (most of the time it will) + * or: !vio_fifo_empty(cli->cbuf) * * * note that can wipe the prompt even though it hasn't been fully * written away yet. (The effect is to append the wipe action to the * cli_obuf !) */ extern void -uty_cli_exit_more_wait(vty_io vio) +uty_cli_exit_more_wait(vty_cli cli) { VTY_ASSERT_LOCKED() ; - assert( (!vio->cli_blocked || !vio_fifo_empty(&vio->cli_obuf)) - && !vio->cmd_out_enabled && vio->cli_more_wait) ; + assert(cli->more_wait) ; - uty_cli_wipe(vio, 0) ; /* wipe the prompt ('--more--') + uty_cli_wipe(cli, 0) ; /* wipe the prompt ('--more--') before changing the CLI state */ - vio->cli_blocked = 1 ; /* back to blocked waiting for output */ - vio->cli_more_wait = 0 ; /* exit more_wait */ - vio->cmd_out_enabled = 1 ; /* re-enable output */ + cli->more_wait = false ; /* exit more_wait */ + cli->more_active = false ; /* tidy */ + cli->out_active = true ; /* re-enable output */ } ; /*------------------------------------------------------------------------------ @@ -754,35 +1022,37 @@ uty_cli_exit_more_wait(vty_io vio) * now_ready -- just left cli_more_wait * not_ready -- otherwise */ -static enum vty_readiness -uty_cli_more_wait(vty_io vio) +static vty_readiness_t +uty_cli_more_wait(vty_cli cli) { struct keystroke steal ; VTY_ASSERT_LOCKED() ; + assert(cli->more_wait) ; /* must be in more_wait state ! */ + /* Deal with the first stage of "--more--" */ - if (vio->cli_blocked) + if (cli->more_active) { int get ; /* If the CLI buffer is not yet empty, then is waiting for the * initial prompt to clear, so nothing to be done here. */ - if (!vio_fifo_empty(&vio->cli_obuf)) - return not_ready ; + if (!vio_fifo_empty(cli->cbuf)) + return write_ready ; - vio->cli_blocked = 0 ; + cli->more_active = false ; /* empty the input buffer into the keystroke stream */ do { - get = uty_read(vio, NULL) ; + get = uty_term_read(cli->vf, NULL) ; } while (get > 0) ; } ; - /* Go through the "--more--" process, unless no longer write_open (!) */ - if (vio->sock.write_open) + /* Go through the "--more--" process, unless closing */ + if (cli->vf->vout_state == vf_open) { /* The read fetches a reasonable lump from the I/O -- so if there * is a complete keystroke available, expect to get it. @@ -791,15 +1061,18 @@ uty_cli_more_wait(vty_io vio) * * If has hit EOF (or error etc), returns knull_eof. */ - uty_read(vio, &steal) ; + uty_term_read(cli->vf, &steal) ; /* If nothing stolen, make sure prompt is drawn and wait for more * input. */ if ((steal.type == ks_null) && (steal.value != knull_eof)) { - if (uty_cli_draw_if_required(vio)) /* "--more--" if req. */ - return write_ready ; + if (uty_cli_draw_if_required(cli)) /* "--more--" if req. */ + { + cli->more_active = true ; /* written "--more--" again */ + return write_ready ; + } else return read_ready ; } ; @@ -812,7 +1085,7 @@ uty_cli_more_wait(vty_io vio) case CONTROL('C'): case 'q': case 'Q': - uty_out_clear(vio) ; + uty_out_clear(cli->vf->vio) ; break; default: /* everything else, thrown away */ @@ -828,7 +1101,7 @@ uty_cli_more_wait(vty_io vio) * Return write_ready to tidy up the screen and, unless cleared, write * some more. */ - uty_cli_exit_more_wait(vio) ; + uty_cli_exit_more_wait(cli) ; return now_ready ; } ; @@ -838,18 +1111,17 @@ uty_cli_more_wait(vty_io vio) * * This is buffered separately from the general (command) VTY output above. * - * Has a dedicated buffer in the struct vty, which is flushed regularly during - * command processing. + * Has a dedicated buffer in the struct vty_cli, which is flushed regularly + * during command processing. * * It is expected that can flush straight to the file, since this is running at * CLI speed. However, if the CLI is being driven by something other than a * keyboard, or "monitor" output has filled the buffers, then may need to * have intermediate buffering. * - * No actual I/O takes place here-- all "output" is to vio->cli_obuf - * and/or vio->cli_ex_obuf + * No actual I/O takes place here-- all "output" is to cli->cbuf * - * The "cli_echo" functions discard the output if vio->cli_echo_suppress != 0. + * The "cli_echo" functions discard the output if cli->echo_suppress. * This is used while passwords are entered and to allow command line changes * to be made while the line is not visible. */ @@ -857,8 +1129,9 @@ uty_cli_more_wait(vty_io vio) enum { cli_rep_count = 32 } ; typedef const char cli_rep_char[cli_rep_count] ; +typedef const char cli_rep[] ; -static const char telnet_backspaces[] = +static cli_rep telnet_backspaces = { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, @@ -866,35 +1139,39 @@ static const char telnet_backspaces[] = } ; CONFIRM(sizeof(telnet_backspaces) == sizeof(cli_rep_char)) ; -static const char telnet_spaces[] = - { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', - ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', - ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', - ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' - } ; -CONFIRM(sizeof(telnet_spaces) == sizeof(cli_rep_char)) ; +/* 12345678901234567890123456789012 */ +static cli_rep telnet_spaces = " " ; +static cli_rep telnet_dots = "................................" ; + +CONFIRM(sizeof(telnet_spaces) == (sizeof(cli_rep_char) + 1)) ; +CONFIRM(sizeof(telnet_dots) == (sizeof(cli_rep_char) + 1)) ; static const char* telnet_newline = "\r\n" ; -static void uty_cli_write(vty_io vio, const char *this, int len) ; -static void uty_cli_write_n(vty_io vio, cli_rep_char chars, int n) ; +static void uty_cli_write_n(vty_cli cli, cli_rep_char chars, int n) ; /*------------------------------------------------------------------------------ * CLI VTY output -- cf fprintf() */ -static void -uty_cli_out(vty_io vio, const char *format, ...) +extern void +uty_cli_out(vty_cli cli, const char *format, ...) { + va_list args ; + VTY_ASSERT_LOCKED() ; - if (vio->sock.write_open) - { - va_list args ; + va_start (args, format); + vio_fifo_vprintf(cli->cbuf, format, args) ; + va_end(args); +} ; - va_start (args, format); - vio_fifo_vprintf(&vio->cli_obuf, format, args) ; - va_end(args); - } ; +/*------------------------------------------------------------------------------ + * Completely empty the cli command buffer + */ +extern void +uty_cli_out_clear(vty_cli cli) +{ + vio_fifo_clear(cli->cbuf, true) ; } ; /*------------------------------------------------------------------------------ @@ -903,14 +1180,12 @@ uty_cli_out(vty_io vio, const char *format, ...) * Do nothing if echo suppressed (eg in AUTH_NODE) or not write_open */ static void -uty_cli_echo(vty_io vio, const char *this, size_t len) +uty_cli_echo(vty_cli cli, const char *this, size_t len) { VTY_ASSERT_LOCKED() ; - if (vio->cli_echo_suppress || !vio->sock.write_open) - return ; - - uty_cli_write(vio, this, len) ; + if (!cli->echo_suppress) + uty_cli_write(cli, this, len) ; } /*------------------------------------------------------------------------------ @@ -919,33 +1194,30 @@ uty_cli_echo(vty_io vio, const char *this, size_t len) * Do nothing if echo suppressed (eg in AUTH_NODE) */ static void -uty_cli_echo_n(vty_io vio, cli_rep_char chars, int n) +uty_cli_echo_n(vty_cli cli, cli_rep_char chars, int n) { VTY_ASSERT_LOCKED() ; - if (vio->cli_echo_suppress || !vio->sock.write_open) - return ; - - uty_cli_write_n(vio, chars, n) ; + if (!cli->echo_suppress) + uty_cli_write_n(cli, chars, n) ; } /*------------------------------------------------------------------------------ * CLI VTY output -- cf write() */ -inline static void -uty_cli_write(vty_io vio, const char *this, int len) +extern void +uty_cli_write(vty_cli cli, const char *this, int len) { VTY_ASSERT_LOCKED() ; - if (vio->sock.write_open) - vio_fifo_put(&vio->cli_obuf, this, len) ; + vio_fifo_put_bytes(cli->cbuf, this, len) ; } ; /*------------------------------------------------------------------------------ * CLI VTY output -- write 'n' characters using a cli_rep_char string */ static void -uty_cli_write_n(vty_io vio, cli_rep_char chars, int n) +uty_cli_write_n(vty_cli cli, cli_rep_char chars, int n) { int len ; @@ -954,7 +1226,7 @@ uty_cli_write_n(vty_io vio, cli_rep_char chars, int n) { if (n < len) len = n ; - uty_cli_write(vio, chars, len) ; + uty_cli_write(cli, chars, len) ; n -= len ; } ; } ; @@ -965,13 +1237,13 @@ uty_cli_write_n(vty_io vio, cli_rep_char chars, int n) * Returns length of string. */ static int -uty_cli_write_s(vty_io vio, const char *str) +uty_cli_write_s(vty_cli cli, const char *str) { int len ; len = strlen(str) ; if (len != 0) - uty_cli_write(vio, str, len) ; + uty_cli_write(cli, str, len) ; return len ; } ; @@ -985,12 +1257,12 @@ uty_cli_write_s(vty_io vio, const char *str) * * Clears the cli_drawn and the cli_dirty flags. */ -static void -uty_cli_out_newline(vty_io vio) +extern void +uty_cli_out_newline(vty_cli cli) { - uty_cli_write(vio, telnet_newline, 2) ; - vio->cli_drawn = 0 ; - vio->cli_dirty = 0 ; + uty_cli_write(cli, telnet_newline, 2) ; + cli->drawn = false ; + cli->dirty = false ; } ; /*------------------------------------------------------------------------------ @@ -1000,23 +1272,23 @@ uty_cli_out_newline(vty_io vio) * 'n' > 0, wipes characters forwards, leaving cursor where it is */ static void -uty_cli_out_wipe_n(vty_io vio, int n) +uty_cli_out_wipe_n(vty_cli cli, int n) { if (n < 0) { n = abs(n) ; - uty_cli_write_n(vio, telnet_backspaces, n); + uty_cli_write_n(cli, telnet_backspaces, n); } ; if (n > 0) { - uty_cli_write_n(vio, telnet_spaces, n) ; - uty_cli_write_n(vio, telnet_backspaces, n) ; + uty_cli_write_n(cli, telnet_spaces, n) ; + uty_cli_write_n(cli, telnet_backspaces, n) ; } ; } ; /*------------------------------------------------------------------------------ - * Send response to the given cli_do + * Send response to the given cmd_do * * If no command is in progress, then will send newline to signal that the * command is about to be dispatched. @@ -1024,113 +1296,126 @@ uty_cli_out_wipe_n(vty_io vio, int n) * If command is in progress, then leaves cursor on '^' to signal that is now * waiting for previous command to complete. */ -static const char* cli_response [2][cli_do_count] = +static const char* cli_response [2][cmd_do_count] = { { /* when not waiting for previous command to complete */ - [cli_do_command] = "", - [cli_do_ctrl_c] = "^C", - [cli_do_ctrl_d] = "^D", - [cli_do_ctrl_z] = "^Z", - [cli_do_eof] = "^*" + [cmd_do_command] = "", + [cmd_do_ctrl_c] = "^C", + [cmd_do_ctrl_d] = "^D", + [cmd_do_ctrl_z] = "^Z", + [cmd_do_eof] = "^*" }, { /* when waiting for a previous command to complete */ - [cli_do_command] = "^", - [cli_do_ctrl_c] = "^C", - [cli_do_ctrl_d] = "^D", - [cli_do_ctrl_z] = "^Z", - [cli_do_eof] = "^*" + [cmd_do_command] = "^", + [cmd_do_ctrl_c] = "^C", + [cmd_do_ctrl_d] = "^D", + [cmd_do_ctrl_z] = "^Z", + [cmd_do_eof] = "^*" } } ; static void -uty_cli_response(vty_io vio, enum cli_do cli_do) +uty_cli_response(vty_cli cli, cmd_do_t to_do) { const char* str ; int len ; - if ((cli_do == cli_do_nothing) || (vio->half_closed)) + if ((to_do == cmd_do_nothing) || (cli->vf->vin_state != vf_open)) return ; - str = (cli_do < cli_do_count) - ? cli_response[vio->cmd_in_progress ? 1 : 0][cli_do] : NULL ; + str = (to_do < cmd_do_count) + ? cli_response[cli->in_progress ? 1 : 0][to_do] : NULL ; assert(str != NULL) ; - len = uty_cli_write_s(vio, str) ; + len = uty_cli_write_s(cli, str) ; - if (vio->cmd_in_progress) + if (cli->in_progress) { - vio->cli_extra_len = len ; - uty_cli_write_n(vio, telnet_backspaces, len) ; + cli->extra_len = len ; + uty_cli_write_n(cli, telnet_backspaces, len) ; } else { - uty_cli_out_newline(vio) ; + uty_cli_out_newline(cli) ; } ; } ; /*------------------------------------------------------------------------------ * Send various messages with trailing newline. */ - +#if 0 static void -uty_cli_out_CMD_ERR_AMBIGUOUS(vty_io vio) +uty_cli_out_CMD_ERR_AMBIGUOUS(vty_cli cli) { - uty_cli_write_s(vio, "% " MSG_CMD_ERR_AMBIGUOUS ".") ; - uty_cli_out_newline(vio) ; + uty_cli_write_s(cli, "% " MSG_CMD_ERR_AMBIGUOUS ".") ; + uty_cli_out_newline(cli) ; } ; static void -uty_cli_out_CMD_ERR_NO_MATCH(vty_io vio) +uty_cli_out_CMD_ERR_NO_MATCH(vty_cli cli) { - uty_cli_write_s(vio, "% " MSG_CMD_ERR_NO_MATCH ".") ; - uty_cli_out_newline(vio) ; + uty_cli_write_s(cli, "% " MSG_CMD_ERR_NO_MATCH ".") ; + uty_cli_out_newline(cli) ; } ; - +#endif /*============================================================================== * Command line draw and wipe */ +/*------------------------------------------------------------------------------ + * Current prompt length + */ +extern ulen +uty_cli_prompt_len(vty_cli cli) +{ + return cli->prompt_len ; +} ; + /*------------------------------------------------------------------------------ * Wipe the current console line -- if any. + * + * If it is known that some characters will immediately be written, then + * passing the number of them as "len" will reduce the number of characters + * that have to be sent. */ -static void -uty_cli_wipe(vty_io vio, int len) +extern void +uty_cli_wipe(vty_cli cli, int len) { int a ; int b ; - if (!vio->cli_drawn) + if (!cli->drawn) return ; /* quit if already wiped */ - assert(vio->cl.cp <= vio->cl.len) ; + assert(qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) ; /* Establish how much ahead and how much behind the cursor */ - a = vio->cli_extra_len ; - b = vio->cli_prompt_len ; + a = cli->extra_len ; + b = cli->prompt_len ; - if (!vio->cli_echo_suppress && !vio->cli_more_wait) + if (!cli->echo_suppress && !cli->more_wait) { - a += vio->cl.len - vio->cl.cp ; - b += vio->cl.cp ; + a += qs_len_nn(cli->cl) - qs_cp_nn(cli->cl) ; + b += qs_cp_nn(cli->cl) ; } ; /* Wipe anything ahead of the current position and ahead of new len */ if ((a + b) > len) - uty_cli_out_wipe_n(vio, +a) ; + uty_cli_out_wipe_n(cli, +a) ; /* Wipe anything behind current position, but ahead of new len */ if (b > len) { - uty_cli_out_wipe_n(vio, -(b - len)) ; + uty_cli_out_wipe_n(cli, -(b - len)) ; b = len ; /* moved the cursor back */ } ; /* Back to the beginning of the line */ - uty_cli_write_n(vio, telnet_backspaces, b) ; + uty_cli_write_n(cli, telnet_backspaces, b) ; /* Nothing there any more */ - vio->cli_drawn = 0 ; - vio->cli_dirty = 0 ; + cli->drawn = false ; + cli->dirty = false ; } ; /*------------------------------------------------------------------------------ @@ -1140,12 +1425,12 @@ uty_cli_wipe(vty_io vio, int len) * See uty_cli_draw(). */ extern bool -uty_cli_draw_if_required(vty_io vio) +uty_cli_draw_if_required(vty_cli cli) { - if (vio->cli_drawn) + if (cli->drawn) return false ; - uty_cli_draw(vio) ; + uty_cli_draw(cli) ; return true ; } ; @@ -1156,9 +1441,9 @@ uty_cli_draw_if_required(vty_io vio) * See uty_cli_draw_this() */ static void -uty_cli_draw(vty_io vio) +uty_cli_draw(vty_cli cli) { - uty_cli_draw_this(vio, vio->vty->node) ; + uty_cli_draw_this(cli, cli->node) ; } ; /*------------------------------------------------------------------------------ @@ -1171,7 +1456,7 @@ uty_cli_draw(vty_io vio) * * Draws prompt according to the given 'node', except: * - * * if is half_closed, draw nothing -- wipes the current line + * * if is closing, draw nothing -- wipes the current line * * * if is cli_more_wait, draw the "--more--" prompt * @@ -1187,34 +1472,34 @@ uty_cli_draw(vty_io vio) * cli_echo_suppress = (AUTH_NODE or AUTH_ENABLE_NODE) */ static void -uty_cli_draw_this(vty_io vio, enum node_type node) +uty_cli_draw_this(vty_cli cli, enum node_type node) { const char* prompt ; size_t l_len ; int p_len ; - if (vio->cli_dirty) - uty_cli_out_newline(vio) ; /* clears cli_dirty and cli_drawn */ + if (cli->dirty) + uty_cli_out_newline(cli) ; /* clears cli_dirty and cli_drawn */ /* Sort out what the prompt is. */ - if (vio->half_closed) + if (cli->vf->vin_state != vf_open) { prompt = "" ; p_len = 0 ; l_len = 0 ; } - else if (vio->cli_more_wait) + else if (cli->more_wait) { prompt = "--more--" ; p_len = strlen(prompt) ; l_len = 0 ; } - else if (vio->cmd_in_progress) + else if (cli->in_progress) { /* If there is a queued command, the prompt is a minimal affair. */ prompt = "~ " ; p_len = strlen(prompt) ; - l_len = vio->cl.len ; + l_len = qs_len_nn(cli->cl) ; } else { @@ -1225,49 +1510,46 @@ uty_cli_draw_this(vty_io vio, enum node_type node) * * If the host name changes, the cli_prompt_set flag is cleared. */ - if (!vio->cli_prompt_set || (node != vio->cli_prompt_node)) + if ((node != cli->prompt_node) || (host.name_gen != cli->prompt_gen)) { const char* prompt ; - if (vty_host_name == NULL) - uty_check_host_name() ; /* should never be required */ - prompt = cmd_prompt(node) ; if (prompt == NULL) { - zlog_err("vty %s has node %d", uty_get_name(vio), node) ; + zlog_err("vty %s has node %d", uty_get_name(cli->vf->vio), node) ; prompt = "%s ???: " ; } ; - qs_printf(&vio->cli_prompt_for_node, prompt, vty_host_name); + qs_printf(cli->prompt_for_node, prompt, host.name); - vio->cli_prompt_node = node ; - vio->cli_prompt_set = 1 ; + cli->prompt_node = node ; + cli->prompt_gen = host.name_gen ; } ; - prompt = vio->cli_prompt_for_node.body ; - p_len = vio->cli_prompt_for_node.len ; - l_len = vio->cl.len ; + prompt = qs_char(cli->prompt_for_node) ; + p_len = qs_len(cli->prompt_for_node) ; + l_len = qs_len_nn(cli->cl) ; } ; /* Now, if line is currently drawn, time to wipe it */ - if (vio->cli_drawn) - uty_cli_wipe(vio, p_len + l_len) ; + if (cli->drawn) + uty_cli_wipe(cli, p_len + l_len) ; /* Set about writing the prompt and the line */ - vio->cli_drawn = 1 ; - vio->cli_extra_len = 0 ; - vio->cli_echo_suppress = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ; + cli->drawn = true ; + cli->extra_len = false ; + cli->echo_suppress = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ; - vio->cli_prompt_len = p_len ; + cli->prompt_len = p_len ; - uty_cli_write(vio, prompt, p_len) ; + uty_cli_write(cli, prompt, p_len) ; if (l_len != 0) { - uty_cli_write(vio, qs_chars(&vio->cl), l_len) ; - if (vio->cl.cp < l_len) - uty_cli_write_n(vio, telnet_backspaces, l_len - vio->cl.cp) ; + uty_cli_write(cli, qs_char(cli->cl), l_len) ; + if (qs_cp_nn(cli->cl) < l_len) + uty_cli_write_n(cli, telnet_backspaces, l_len - qs_cp_nn(cli->cl)) ; } ; } ; @@ -1298,11 +1580,11 @@ uty_cli_draw_this(vty_io vio, enum node_type node) * Wipe any existing command line. */ extern void -uty_cli_pre_monitor(vty_io vio, size_t len) +uty_cli_pre_monitor(vty_cli cli, size_t len) { VTY_ASSERT_LOCKED() ; - uty_cli_wipe(vio, len) ; + uty_cli_wipe(cli, len) ; } ; /*------------------------------------------------------------------------------ @@ -1317,16 +1599,16 @@ uty_cli_pre_monitor(vty_io vio, size_t len) * > 0 => rump not empty or some command line stuff written */ extern int -uty_cli_post_monitor(vty_io vio, const char* buf, size_t len) +uty_cli_post_monitor(vty_cli cli, const char* buf, size_t len) { VTY_ASSERT_LOCKED() ; if (len != 0) - uty_cli_write(vio, buf, len) ; + uty_cli_write(cli, buf, len) ; - if (vio->cli_more_wait || (vio->cl.len != 0)) + if (cli->more_wait || (qs_len_nn(cli->cl) != 0)) { - uty_cli_draw(vio) ; + uty_cli_draw(cli) ; ++len ; } ; @@ -1337,48 +1619,52 @@ uty_cli_post_monitor(vty_io vio, const char* buf, size_t len) * Command line processing loop */ -static bool uty_telnet_command(vty_io vio, keystroke stroke, bool callback) ; -static int uty_cli_insert (vty_io vio, const char* chars, int n) ; -static int uty_cli_overwrite (vty_io vio, char* chars, int n) ; -static int uty_cli_word_overwrite (vty_io vio, char *str) ; -static int uty_cli_forwards(vty_io vio, int n) ; -static int uty_cli_backwards(vty_io vio, int n) ; -static int uty_cli_del_forwards(vty_io vio, int n) ; -static int uty_cli_del_backwards(vty_io vio, int n) ; -static int uty_cli_bol (vty_io vio) ; -static int uty_cli_eol (vty_io vio) ; -static int uty_cli_word_forwards_delta(vty_io vio) ; -static int uty_cli_word_forwards(vty_io vio) ; -static int uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) ; -static int uty_cli_word_backwards_pure (vty_io vio) ; -static int uty_cli_word_backwards (vty_io vio) ; -static int uty_cli_del_word_forwards(vty_io vio) ; -static int uty_cli_del_word_backwards(vty_io vio) ; -static int uty_cli_del_to_eol (vty_io vio) ; -static int uty_cli_clear_line(vty_io vio) ; -static int uty_cli_transpose_chars(vty_io vio) ; -static void uty_cli_history_use(vty_io vio, int step) ; -static void uty_cli_next_line(vty_io vio) ; -static void uty_cli_previous_line (vty_io vio) ; -static void uty_cli_complete_command (vty_io vio, enum node_type node) ; -static void uty_cli_describe_command (vty_io vio, enum node_type node) ; +static int uty_cli_insert (vty_cli cli, const char* chars, int n) ; +static int uty_cli_overwrite (vty_cli cli, char* chars, int n) ; +//static int uty_cli_word_overwrite (vty_cli cli, char *str) ; +static int uty_cli_forwards(vty_cli cli, int n) ; +static int uty_cli_backwards(vty_cli cli, int n) ; +static int uty_cli_del_forwards(vty_cli cli, int n) ; +static int uty_cli_del_backwards(vty_cli cli, int n) ; +static int uty_cli_bol (vty_cli cli) ; +static int uty_cli_eol (vty_cli cli) ; +static int uty_cli_word_forwards_delta(vty_cli cli) ; +static int uty_cli_word_forwards(vty_cli cli) ; +static int uty_cli_word_backwards_delta(vty_cli cli, int eat_spaces) ; +//static int uty_cli_word_backwards_pure (vty_cli cli) ; +static int uty_cli_word_backwards (vty_cli cli) ; +static int uty_cli_del_word_forwards(vty_cli cli) ; +static int uty_cli_del_word_backwards(vty_cli cli) ; +static int uty_cli_del_to_eol (vty_cli cli) ; +static int uty_cli_clear_line(vty_cli cli) ; +static int uty_cli_transpose_chars(vty_cli cli) ; +static void uty_cli_hist_use(vty_cli cli, int step) ; +static void uty_cli_hist_next(vty_cli cli) ; +static void uty_cli_hist_previous(vty_cli cli) ; +static void uty_cli_complete_command (vty_cli cli, node_type_t node) ; +static void uty_cli_describe_command (vty_cli cli, node_type_t node) ; /*------------------------------------------------------------------------------ * Fetch next keystroke, reading from the file if required. */ -static inline bool -uty_cli_get_keystroke(vty_io vio, keystroke stroke) +static bool +uty_cli_get_keystroke(vty_cli cli, keystroke stroke) { - if (keystroke_get(vio->key_stream, stroke)) - return 1 ; + int got ; - uty_read(vio, NULL) ; /* not stealing */ + do + { + if (keystroke_get(cli->key_stream, stroke)) + return true ; + + got = uty_term_read(cli->vf, NULL) ; /* not stealing */ + } while (got > 0) ; - return keystroke_get(vio->key_stream, stroke) ; + return false ; } ; /*------------------------------------------------------------------------------ - * Process keystrokes until run out of input, or get something to cli_do. + * Process keystrokes until run out of input, or get something to cmd_do. * * If required, draw the prompt and command line. * @@ -1388,30 +1674,30 @@ uty_cli_get_keystroke(vty_io vio, keystroke stroke) * Processes the contents of the keystroke stream. If exhausts that, will set * ready to read and return. (To give some "sharing".) * - * Returns: cli_do_xxxx + * Returns: cmd_do_xxxx * * When returns the cl is '\0' terminated. */ -static enum cli_do -uty_cli_process(vty_io vio, enum node_type node) +static cmd_do_t +uty_cli_process(vty_cli cli, node_type_t node) { - struct keystroke stroke ; - uint8_t u ; - int auth ; - enum cli_do ret ; + struct keystroke stroke ; + uint8_t u ; + bool auth ; + cmd_do_t to_do ; auth = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ; /* Now process as much as possible of what there is */ - ret = cli_do_nothing ; - while (ret == cli_do_nothing) + to_do = cmd_do_nothing ; + while (to_do == cmd_do_nothing) { - if (!vio->cli_drawn) - uty_cli_draw_this(vio, node) ; + if (!cli->drawn) + uty_cli_draw_this(cli, node) ; - if (!uty_cli_get_keystroke(vio, &stroke)) + if (!uty_cli_get_keystroke(cli, &stroke)) { - ret = (stroke.value == knull_eof) ? cli_do_eof : cli_do_nothing ; + to_do = (stroke.value == knull_eof) ? cmd_do_eof : cmd_do_nothing ; break ; } ; @@ -1431,87 +1717,91 @@ uty_cli_process(vty_io vio, enum node_type node) switch (stroke.value) { case CONTROL('A'): - uty_cli_bol (vio); + uty_cli_bol (cli); break; case CONTROL('B'): - uty_cli_backwards(vio, 1); + uty_cli_backwards(cli, 1); break; case CONTROL('C'): - ret = cli_do_ctrl_c ; /* Exit on ^C ..................*/ + to_do = cmd_do_ctrl_c ; /* Exit on ^C ..................*/ break ; case CONTROL('D'): - if (vio->cl.len == 0) /* if at start of empty line */ - ret = cli_do_ctrl_d ; /* Exit on ^D ..................*/ + if (auth) + to_do = cmd_do_ctrl_d ; /* Exit on ^D ..................*/ else - uty_cli_del_forwards(vio, 1); + uty_cli_del_forwards(cli, 1); break; case CONTROL('E'): - uty_cli_eol (vio); + uty_cli_eol (cli); break; case CONTROL('F'): - uty_cli_forwards(vio, 1); + uty_cli_forwards(cli, 1); break; case CONTROL('H'): case 0x7f: - uty_cli_del_backwards(vio, 1); + uty_cli_del_backwards(cli, 1); break; case CONTROL('K'): - uty_cli_del_to_eol (vio); + uty_cli_del_to_eol (cli); break; case CONTROL('N'): - uty_cli_next_line (vio); + uty_cli_hist_next (cli); break; case CONTROL('P'): - uty_cli_previous_line (vio); + uty_cli_hist_previous(cli); break; case CONTROL('T'): - uty_cli_transpose_chars (vio); + uty_cli_transpose_chars (cli); break; case CONTROL('U'): - uty_cli_clear_line(vio); + uty_cli_clear_line(cli); break; case CONTROL('W'): - uty_cli_del_word_backwards (vio); + uty_cli_del_word_backwards (cli); break; case CONTROL('Z'): - ret = cli_do_ctrl_z ; /* Exit on ^Z ..................*/ + to_do = cmd_do_ctrl_z ; /* Exit on ^Z ..................*/ break; case '\n': case '\r': - ret = cli_do_command ; /* Exit on CR or LF.............*/ + to_do = cmd_do_command ; /* Exit on CR or LF.............*/ break ; case '\t': if (auth) - break ; + uty_cli_insert (cli, " ", 1) ; + else if (cmd_token_position(cli->parsed, cli->cl)) + uty_cli_insert (cli, " ", 1) ; else - uty_cli_complete_command (vio, node); + uty_cli_complete_command (cli, node); break; case '?': - if (auth) - uty_cli_insert (vio, (char*)&u, 1); + if (auth) + uty_cli_insert (cli, (char*)&u, 1) ; + else if (cmd_token_position(cli->parsed, cli->cl)) + uty_cli_insert (cli, (char*)&u, 1) ; else - uty_cli_describe_command (vio, node); + uty_cli_describe_command (cli, node); break; default: if ((stroke.value >= 0x20) && (stroke.value < 0x7F)) - uty_cli_insert (vio, (char*)&u, 1) ; + uty_cli_insert (cli, (char*)&u, 1) ; break; } break ; @@ -1521,20 +1811,20 @@ uty_cli_process(vty_io vio, enum node_type node) switch (stroke.value) { case 'b': - uty_cli_word_backwards (vio); + uty_cli_word_backwards (cli); break; case 'f': - uty_cli_word_forwards (vio); + uty_cli_word_forwards (cli); break; case 'd': - uty_cli_del_word_forwards (vio); + uty_cli_del_word_forwards (cli); break; case CONTROL('H'): case 0x7f: - uty_cli_del_word_backwards (vio); + uty_cli_del_word_backwards (cli); break; default: @@ -1549,20 +1839,20 @@ uty_cli_process(vty_io vio, enum node_type node) switch (stroke.value) { - case ('A'): - uty_cli_previous_line (vio); + case ('A'): /* up arrow */ + uty_cli_hist_previous(cli); break; - case ('B'): - uty_cli_next_line (vio); + case ('B'): /* down arrow */ + uty_cli_hist_next (cli); break; - case ('C'): - uty_cli_forwards(vio, 1); + case ('C'): /* right arrow */ + uty_cli_forwards(cli, 1); break; - case ('D'): - uty_cli_backwards(vio, 1); + case ('D'): /* left arrow */ + uty_cli_backwards(cli, 1); break; default: @@ -1572,7 +1862,7 @@ uty_cli_process(vty_io vio, enum node_type node) /* Telnet Command ----------------------------------------------------*/ case ks_iac: - uty_telnet_command(vio, &stroke, false) ; + uty_telnet_command(cli->vf, &stroke, false) ; break ; /* Unknown -----------------------------------------------------------*/ @@ -1584,12 +1874,10 @@ uty_cli_process(vty_io vio, enum node_type node) /* Tidy up and return where got to. */ - if (ret != cli_do_nothing) - uty_cli_eol (vio) ; /* go to the end of the line */ - - qs_term(&vio->cl) ; /* add '\0' */ + if (to_do != cmd_do_nothing) + uty_cli_eol (cli) ; /* go to the end of the line */ - return ret ; + return to_do ; } ; /*============================================================================== @@ -1602,25 +1890,25 @@ uty_cli_process(vty_io vio, enum node_type node) * Returns number of characters inserted -- ie 'n' */ static int -uty_cli_insert (vty_io vio, const char* chars, int n) +uty_cli_insert (vty_cli cli, const char* chars, int n) { int after ; VTY_ASSERT_LOCKED() ; - assert((vio->cl.cp <= vio->cl.len) && (n >= 0)) ; + assert((qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) && (n >= 0)) ; if (n <= 0) return n ; /* avoid trouble */ - after = qs_insert(&vio->cl, chars, n) ; + after = qs_insert(cli->cl, chars, n) ; - uty_cli_echo(vio, qs_cp_char(&vio->cl), after + n) ; + uty_cli_echo(cli, qs_cp_char(cli->cl), after) ; - if (after != 0) - uty_cli_echo_n(vio, telnet_backspaces, after) ; + if ((after - n) != 0) + uty_cli_echo_n(cli, telnet_backspaces, after - n) ; - vio->cl.cp += n ; + qs_move_cp_nn(cli->cl, n) ; return n ; } ; @@ -1633,23 +1921,57 @@ uty_cli_insert (vty_io vio, const char* chars, int n) * Returns number of characters inserted -- ie 'n' */ static int -uty_cli_overwrite (vty_io vio, char* chars, int n) +uty_cli_overwrite (vty_cli cli, char* chars, int n) +{ + VTY_ASSERT_LOCKED() ; + + assert((qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) && (n >= 0)) ; + + if (n > 0) + { + qs_replace(cli->cl, n, chars, n) ; + uty_cli_echo(cli, chars, n) ; + + qs_move_cp_nn(cli->cl, n) ; + } ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Replace 'm' characters at the current position, by 'n' characters and leave + * cursor at the end of the inserted characters. + * + * Returns number of characters inserted -- ie 'n' + */ +static int +uty_cli_replace(vty_cli cli, int m, const char* chars, int n) { + int a, b ; + VTY_ASSERT_LOCKED() ; - assert((vio->cl.cp <= vio->cl.len) && (n >= 0)) ; + assert((qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) && (n >= 0) && (m >= 0)) ; + + b = qs_len_nn(cli->cl) - qs_cp_nn(cli->cl) ; + a = qs_replace(cli->cl, m, chars, n) ; - if (n > 0) - { - qs_replace(&vio->cl, chars, n) ; - uty_cli_echo(vio, chars, n) ; + uty_cli_echo(cli, qs_cp_char(cli->cl), a) ; - vio->cl.cp += n ; - } ; + if (a < b) + uty_cli_echo_n(cli, telnet_spaces, b - a) ; + else + b = a ; + + if (b > n) + uty_cli_echo_n(cli, telnet_backspaces, b - n) ; + + qs_move_cp_nn(cli->cl, n) ; return n ; -} +} ; +#if 0 /*------------------------------------------------------------------------------ * Insert a word into vty interface with overwrite mode. * @@ -1658,17 +1980,18 @@ uty_cli_overwrite (vty_io vio, char* chars, int n) * Returns number of characters inserted -- ie length of string */ static int -uty_cli_word_overwrite (vty_io vio, char *str) +uty_cli_word_overwrite (vty_cli cli, char *str) { int n ; VTY_ASSERT_LOCKED() ; - n = uty_cli_overwrite(vio, str, strlen(str)) ; + n = uty_cli_overwrite(cli, str, strlen(str)) ; - vio->cl.len = vio->cl.cp ; + qs_set_len_nn(cli->cl, qs_cp_nn(cli->cl)) ; return n ; } +#endif /*------------------------------------------------------------------------------ * Forward 'n' characters -- stop at end of line. @@ -1676,12 +1999,12 @@ uty_cli_word_overwrite (vty_io vio, char *str) * Returns number of characters actually moved */ static int -uty_cli_forwards(vty_io vio, int n) +uty_cli_forwards(vty_cli cli, int n) { int have ; VTY_ASSERT_LOCKED() ; - have = vio->cl.len - vio->cl.cp ; + have = qs_after_cp_nn(cli->cl) ; if (have < n) n = have ; @@ -1689,12 +2012,12 @@ uty_cli_forwards(vty_io vio, int n) if (n > 0) { - uty_cli_echo(vio, qs_cp_char(&vio->cl), n) ; - vio->cl.cp += n ; + uty_cli_echo(cli, qs_cp_char(cli->cl), n) ; + qs_move_cp_nn(cli->cl, n) ; } ; return n ; -} +} ; /*------------------------------------------------------------------------------ * Backwards 'n' characters -- stop at start of line. @@ -1702,23 +2025,43 @@ uty_cli_forwards(vty_io vio, int n) * Returns number of characters actually moved */ static int -uty_cli_backwards(vty_io vio, int n) +uty_cli_backwards(vty_cli cli, int n) { VTY_ASSERT_LOCKED() ; - if ((int)vio->cl.cp < n) - n = vio->cl.cp ; + if ((int)qs_cp_nn(cli->cl) < n) + n = qs_cp_nn(cli->cl) ; assert(n >= 0) ; if (n > 0) { - uty_cli_echo_n(vio, telnet_backspaces, n) ; - vio->cl.cp -= n ; + uty_cli_echo_n(cli, telnet_backspaces, n) ; + qs_move_cp_nn(cli->cl, -n) ; } ; return n ; -} +} ; + +/*------------------------------------------------------------------------------ + * Move forwards (if n > 0) or backwards (if n < 0) -- stop at start or end of + * line. + * + * Returns number of characters actually moved -- signed + */ +static int +uty_cli_move(vty_cli cli, int n) +{ + VTY_ASSERT_LOCKED() ; + + if (n < 0) + return - uty_cli_backwards(cli, -n) ; + + if (n > 0) + return + uty_cli_forwards(cli, +n) ; + + return 0 ; +} ; /*------------------------------------------------------------------------------ * Delete 'n' characters -- forwards -- stop at end of line. @@ -1726,14 +2069,14 @@ uty_cli_backwards(vty_io vio, int n) * Returns number of characters actually deleted. */ static int -uty_cli_del_forwards(vty_io vio, int n) +uty_cli_del_forwards(vty_cli cli, int n) { int after ; int have ; VTY_ASSERT_LOCKED() ; - have = vio->cl.len - vio->cl.cp ; + have = qs_after_cp_nn(cli->cl) ; if (have < n) n = have ; /* cannot delete more than have */ @@ -1742,13 +2085,13 @@ uty_cli_del_forwards(vty_io vio, int n) if (n <= 0) return 0 ; - after = qs_delete(&vio->cl, n) ; + after = qs_delete(cli->cl, n) ; if (after > 0) - uty_cli_echo(vio, qs_cp_char(&vio->cl), after) ; + uty_cli_echo(cli, qs_cp_char(cli->cl), after) ; - uty_cli_echo_n(vio, telnet_spaces, n) ; - uty_cli_echo_n(vio, telnet_backspaces, after + n) ; + uty_cli_echo_n(cli, telnet_spaces, n) ; + uty_cli_echo_n(cli, telnet_backspaces, after + n) ; return n ; } @@ -1759,9 +2102,9 @@ uty_cli_del_forwards(vty_io vio, int n) * Returns number of characters actually deleted. */ static int -uty_cli_del_backwards(vty_io vio, int n) +uty_cli_del_backwards(vty_cli cli, int n) { - return uty_cli_del_forwards(vio, uty_cli_backwards(vio, n)) ; + return uty_cli_del_forwards(cli, uty_cli_backwards(cli, n)) ; } /*------------------------------------------------------------------------------ @@ -1770,9 +2113,9 @@ uty_cli_del_backwards(vty_io vio, int n) * Returns number of characters moved over. */ static int -uty_cli_bol (vty_io vio) +uty_cli_bol (vty_cli cli) { - return uty_cli_backwards(vio, vio->cl.cp) ; + return uty_cli_backwards(cli, qs_cp_nn(cli->cl)) ; } ; /*------------------------------------------------------------------------------ @@ -1781,9 +2124,9 @@ uty_cli_bol (vty_io vio) * Returns number of characters moved over */ static int -uty_cli_eol (vty_io vio) +uty_cli_eol (vty_cli cli) { - return uty_cli_forwards(vio, vio->cl.len - vio->cl.cp) ; + return uty_cli_forwards(cli, qs_after_cp_nn(cli->cl)) ; } ; /*------------------------------------------------------------------------------ @@ -1794,7 +2137,7 @@ uty_cli_eol (vty_io vio) * Steps over non-space characters and then any spaces. */ static int -uty_cli_word_forwards_delta(vty_io vio) +uty_cli_word_forwards_delta(vty_cli cli) { char* cp ; char* tp ; @@ -1802,10 +2145,10 @@ uty_cli_word_forwards_delta(vty_io vio) VTY_ASSERT_LOCKED() ; ; - assert(vio->cl.cp <= vio->cl.len) ; + assert(qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) ; - cp = qs_cp_char(&vio->cl) ; - ep = qs_ep_char(&vio->cl) ; + cp = qs_cp_char(cli->cl) ; + ep = qs_ep_char(cli->cl) ; tp = cp ; @@ -1824,9 +2167,9 @@ uty_cli_word_forwards_delta(vty_io vio) * Moves past any non-spaces, then past any spaces. */ static int -uty_cli_word_forwards(vty_io vio) +uty_cli_word_forwards(vty_cli cli) { - return uty_cli_forwards(vio, uty_cli_word_forwards_delta(vio)) ; + return uty_cli_forwards(cli, uty_cli_word_forwards_delta(cli)) ; } ; /*------------------------------------------------------------------------------ @@ -1838,7 +2181,7 @@ uty_cli_word_forwards(vty_io vio) * Steps back until next (backwards) character is space, or hits start of line. */ static int -uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) +uty_cli_word_backwards_delta(vty_cli cli, int eat_spaces) { char* cp ; char* tp ; @@ -1846,10 +2189,10 @@ uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) VTY_ASSERT_LOCKED() ; ; - assert(vio->cl.cp <= vio->cl.len) ; + assert(qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) ; - cp = qs_cp_char(&vio->cl) ; - sp = qs_chars(&vio->cl) ; + cp = qs_cp_char(cli->cl) ; + sp = qs_char(cli->cl) ; tp = cp ; @@ -1863,6 +2206,7 @@ uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) return cp - tp ; } ; +#if 0 /*------------------------------------------------------------------------------ * Backward word, but not trailing spaces. * @@ -1871,10 +2215,11 @@ uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) * Returns number of characters stepped over. */ static int -uty_cli_word_backwards_pure (vty_io vio) +uty_cli_word_backwards_pure (vty_cli cli) { - return uty_cli_backwards(vio, uty_cli_word_backwards_delta(vio, 0)) ; + return uty_cli_backwards(cli, uty_cli_word_backwards_delta(cli, 0)) ; } ; +#endif /*------------------------------------------------------------------------------ * Backward word -- move to start of previous word. @@ -1885,9 +2230,9 @@ uty_cli_word_backwards_pure (vty_io vio) * Returns number of characters stepped over. */ static int -uty_cli_word_backwards (vty_io vio) +uty_cli_word_backwards (vty_cli cli) { - return uty_cli_backwards(vio, uty_cli_word_backwards_delta(vio, 1)) ; + return uty_cli_backwards(cli, uty_cli_word_backwards_delta(cli, 1)) ; } ; /*------------------------------------------------------------------------------ @@ -1898,9 +2243,9 @@ uty_cli_word_backwards (vty_io vio) * Returns number of characters deleted. */ static int -uty_cli_del_word_forwards(vty_io vio) +uty_cli_del_word_forwards(vty_cli cli) { - return uty_cli_del_forwards(vio, uty_cli_word_forwards_delta(vio)) ; + return uty_cli_del_forwards(cli, uty_cli_word_forwards_delta(cli)) ; } /*------------------------------------------------------------------------------ @@ -1911,9 +2256,9 @@ uty_cli_del_word_forwards(vty_io vio) * Returns number of characters deleted. */ static int -uty_cli_del_word_backwards(vty_io vio) +uty_cli_del_word_backwards(vty_cli cli) { - return uty_cli_del_backwards(vio, uty_cli_word_backwards_delta(vio, 1)) ; + return uty_cli_del_backwards(cli, uty_cli_word_backwards_delta(cli, 1)) ; } ; /*------------------------------------------------------------------------------ @@ -1922,9 +2267,9 @@ uty_cli_del_word_backwards(vty_io vio) * Returns number of characters deleted. */ static int -uty_cli_del_to_eol (vty_io vio) +uty_cli_del_to_eol (vty_cli cli) { - return uty_cli_del_forwards(vio, vio->cl.len - vio->cl.cp) ; + return uty_cli_del_forwards(cli, qs_after_cp_nn(cli->cl)) ; } ; /*------------------------------------------------------------------------------ @@ -1933,10 +2278,10 @@ uty_cli_del_to_eol (vty_io vio) * Returns number of characters deleted. */ static int -uty_cli_clear_line(vty_io vio) +uty_cli_clear_line(vty_cli cli) { - uty_cli_bol(vio) ; - return uty_cli_del_to_eol(vio) ; + uty_cli_bol(cli) ; + return uty_cli_del_to_eol(cli) ; } ; /*------------------------------------------------------------------------------ @@ -1945,7 +2290,7 @@ uty_cli_clear_line(vty_io vio) * Return number of characters affected. */ static int -uty_cli_transpose_chars(vty_io vio) +uty_cli_transpose_chars(vty_cli cli) { char chars[2] ; char* cp ; @@ -1953,54 +2298,79 @@ uty_cli_transpose_chars(vty_io vio) VTY_ASSERT_LOCKED() ; /* Give up if < 2 characters or at start of line. */ - if ((vio->cl.len < 2) || (vio->cl.cp < 1)) + if ((qs_len_nn(cli->cl) < 2) || (qs_cp_nn(cli->cl) < 1)) return 0 ; /* Move back to first of characters to exchange */ - if (vio->cl.cp == vio->cl.len) - uty_cli_backwards(vio, 2) ; + if (qs_cp_nn(cli->cl) == qs_len_nn(cli->cl)) + uty_cli_backwards(cli, 2) ; else - uty_cli_backwards(vio, 1) ; + uty_cli_backwards(cli, 1) ; /* Pick up in the new order */ - cp = qs_cp_char(&vio->cl) ; + cp = qs_cp_char(cli->cl) ; chars[1] = *cp++ ; chars[0] = *cp ; /* And overwrite */ - return uty_cli_overwrite(vio, chars, 2) ; + return uty_cli_overwrite(cli, chars, 2) ; } ; /*============================================================================== * Command line history handling + * + * cli->hist is vector of qstrings + * cli->h_now is index of the present time + * cli->hp is index of most recent line read back + * + * cli->hist is initialised empty, with h_now == hp == 0. + * + * On first use it is set to VTY_MAX_HIST entries, and its size never changes. + * Before VTY_MAX_HIST lines have been inserted, a NULL entry signals the end + * of history (to date). + * + * h_now is incremented after a line is inserted (and wraps round). So + * stepping +1 moves towards the present (down) and -1 moves towards the past + * (up). + * + * hp == h_now means we are in the present. + * + * Cannot step forwards from hp == h_now (into the future !). + * + * Before stepping backwards from hp == hp_now, sets hp_now to be a copy of + * the current line (complete with cp), so can return to the present. + * + * Cannot step backwards to hp == hp_now -- that would be to wrap round from + * the ancient past to the now time. + * + * When storing a line in the history, replaces the last line stored if that + * is the same as the new line, apart from whitespace. */ /*------------------------------------------------------------------------------ * Add given command line to the history buffer. * * This is inserting the vty->buf line into the history. + * + * Resets hp == h_now. */ -extern void -uty_cli_hist_add (vty_io vio, const char* cmd_line) +static void +uty_cli_hist_add (vty_cli cli, qstring clx) { - qstring prev_line ; - qstring_t line ; - int prev_index ; + qstring hist_line ; + int prev ; VTY_ASSERT_LOCKED() ; - /* Construct a dummy qstring for the given command line */ - qs_dummy(&line, cmd_line, 1) ; /* set cursor to the end */ - /* make sure have a suitable history vector */ - vector_set_min_length(vio->hist, VTY_MAXHIST) ; + vector_set_min_length(cli->hist, VTY_MAXHIST) ; - /* find the previous command line in the history */ - prev_index = vio->hindex - 1 ; - if (prev_index < 0) - prev_index = VTY_MAXHIST - 1 ; + /* get the previous command line */ + prev = cli->h_now - 1 ; + if (prev < 0) + prev = VTY_MAXHIST - 1 ; - prev_line = vector_get_item(vio->hist, prev_index) ; + hist_line = vector_get_item(cli->hist, prev) ; /* If the previous line is NULL, that means the history is empty. * @@ -2008,23 +2378,32 @@ uty_cli_hist_add (vty_io vio, const char* cmd_line) * replace it with the current line -- so that the latest whitespace * version is saved. * - * Either way, replace the the previous line entry by moving hindex - * back ! + * In both those cases, replace the the previous line entry by moving + * h_now back to it -- leaving hist_line pointing at it. + * + * Otherwise, leave cli->h_now and point hist_line at the most ancient + * line in history. */ - if ((prev_line == NULL) || (qs_cmp_sig(prev_line, &line) == 0)) - vio->hindex = prev_index ; + if ((hist_line == NULL) || (qs_cmp_sig(hist_line, clx) == 0)) + cli->h_now = prev ; else - prev_line = vector_get_item(vio->hist, vio->hindex) ; + hist_line = vector_get_item(cli->hist, cli->h_now) ; - /* Now replace the hindex entry */ - vector_set_item(vio->hist, vio->hindex, qs_copy(prev_line, &line)) ; + /* Now replace the h_now entry + * + * Note that the line inserted in the history has it's 'cp' set to the end of + * the line -- so that it is there when it comes back out again. + */ + hist_line = qs_copy(hist_line, clx) ; + qs_set_cp_nn(hist_line, qs_len_nn(hist_line)) ; + vector_set_item(cli->hist, cli->h_now, hist_line) ; /* Advance to the near future and reset the history pointer */ - vio->hindex++; - if (vio->hindex == VTY_MAXHIST) - vio->hindex = 0; + cli->h_now++; + if (cli->h_now == VTY_MAXHIST) + cli->h_now = 0; - vio->hp = vio->hindex; + cli->hp = cli->h_now; } ; /*------------------------------------------------------------------------------ @@ -2032,668 +2411,503 @@ uty_cli_hist_add (vty_io vio, const char* cmd_line) * * This function is called from vty_next_line and vty_previous_line. * - * Step +1 is towards the present - * -1 is into the past + * Step -1 is into the past (up) + * +1 is towards the present (down) */ static void -uty_cli_history_use(vty_io vio, int step) +uty_cli_hist_use(vty_cli cli, int step) { - int index ; - unsigned old_len ; - unsigned after ; - unsigned back ; - qstring hist ; + int hp ; + ulen old_len ; + ulen new_len ; + ulen after ; + ulen back ; + qstring hist_line ; VTY_ASSERT_LOCKED() ; assert((step == +1) || (step == -1)) ; - index = vio->hp ; + hp = cli->hp ; /* Special case of being at the insertion point */ - if (index == vio->hindex) + if (hp == cli->h_now) { if (step > 0) return ; /* already in the present */ /* before stepping back from the present, take a copy of the * current command line -- so can get back to it. + * + * Note that the 'cp' is stored with the line. If return to here + * and later enter the line it will replace this. */ - hist = vector_get_item(vio->hist, vio->hindex) ; - vector_set_item(vio->hist, vio->hindex, qs_copy(hist, &vio->cl)) ; + hist_line = vector_get_item(cli->hist, cli->h_now) ; + vector_set_item(cli->hist, cli->h_now, qs_copy(hist_line, cli->cl)) ; } ; /* Advance or retreat */ - index += step ; - if (index < 0) - index = VTY_MAXHIST - 1 ; - else if (index >= VTY_MAXHIST) - index = 0 ; + hp += step ; + if (hp < 0) + hp = VTY_MAXHIST - 1 ; + else if (hp >= VTY_MAXHIST) + hp = 0 ; - hist = vector_get_item(vio->hist, index) ; + hist_line = vector_get_item(cli->hist, hp) ; - /* If moving backwards in time, may not move back to the insertion + /* If moving backwards in time, may not move back to the h_now * point (that would be wrapping round to the present) and may not * move back to a NULL entry (that would be going back before '.'). + * + * If moving forwards in time, may return to the present, with + * hp == cli->h_now. */ if (step < 0) - if ((hist == NULL) || (index == vio->hindex)) + if ((hist_line == NULL) || (hp == cli->h_now)) return ; - /* Now, if arrived at the insertion point, this is returning to the - * present, which is fine. - */ - vio->hp = index; + cli->hp = hp ; /* Move back to the start of the current line */ - uty_cli_bol(vio) ; + uty_cli_bol(cli) ; /* Get previous line from history buffer and echo that */ - old_len = vio->cl.len ; - qs_copy(&vio->cl, hist) ; + old_len = qs_len_nn(cli->cl) ; + qs_copy(cli->cl, hist_line) ; + new_len = qs_len_nn(cli->cl) ; /* Sort out wiping out any excess and setting the cursor position */ - if (old_len > vio->cl.len) - after = old_len - vio->cl.len ; + if (old_len > new_len) + after = old_len - new_len ; else after = 0 ; - back = after ; - if (vio->cl.len > vio->cl.cp) - back += (vio->cl.len - vio->cl.cp) ; + /* Return cursor to stored 'cp' -- which will be end of line unless + * this is the copy of the original current line stored above. + */ + back = after + qs_after_cp_nn(cli->cl) ; - if (vio->cl.len > 0) - uty_cli_echo(vio, vio->cl.body, vio->cl.len) ; + if (new_len > 0) + uty_cli_echo(cli, qs_char_nn(cli->cl), new_len) ; if (after > 0) - uty_cli_echo_n(vio, telnet_spaces, after) ; + uty_cli_echo_n(cli, telnet_spaces, after) ; if (back > 0) - uty_cli_echo_n(vio, telnet_backspaces, back) ; + uty_cli_echo_n(cli, telnet_backspaces, back) ; return ; } ; /*------------------------------------------------------------------------------ - * Use next history line, if any. + * Use previous history line, if any (up arrow). */ static void -uty_cli_next_line(vty_io vio) +uty_cli_hist_previous(vty_cli cli) { - uty_cli_history_use(vio, +1) ; + uty_cli_hist_use(cli, -1) ; } /*------------------------------------------------------------------------------ - * Use previous history line, if any. + * Use next history line, if any (down arrow). */ static void -uty_cli_previous_line (vty_io vio) +uty_cli_hist_next(vty_cli cli) { - uty_cli_history_use(vio, -1) ; + uty_cli_hist_use(cli, +1) ; } -/*============================================================================== - * Command Completion and Command Description - * - */ -static void uty_cli_describe_show(vty_io vio, vector describe) ; -static void uty_cli_describe_fold (vty_io vio, int cmd_width, - unsigned int desc_width, struct desc *desc) ; -static void uty_cli_describe_line(vty_io vio, int cmd_width, const char* cmd, - const char* str) ; - -static vector uty_cli_cmd_prepare(vty_io vio, int help) ; - /*------------------------------------------------------------------------------ - * Command completion + * Show the contents of the history */ -static void -uty_cli_complete_command (vty_io vio, enum node_type node) +extern void +uty_cli_hist_show(vty_cli cli) { - unsigned i ; - int ret ; - int len ; - int n ; - vector matched ; - vector vline ; + int hp ; VTY_ASSERT_LOCKED() ; - /* Try and match the tokenised command line */ - vline = uty_cli_cmd_prepare(vio, 1) ; - matched = cmd_complete_command (vline, node, &ret); - cmd_free_strvec (vline); + hp = cli->h_now ; - /* Show the result. */ - switch (ret) + while(1) { - case CMD_ERR_AMBIGUOUS: - uty_cli_out_newline(vio) ; /* clears cli_drawn */ - uty_cli_out_CMD_ERR_AMBIGUOUS(vio) ; - break ; - - case CMD_ERR_NO_MATCH: - uty_cli_out_newline(vio) ; /* clears cli_drawn */ - uty_cli_out_CMD_ERR_NO_MATCH(vio) ; - break ; - - case CMD_COMPLETE_FULL_MATCH: - uty_cli_eol (vio) ; - uty_cli_word_backwards_pure (vio); - uty_cli_word_overwrite (vio, vector_get_item(matched, 0)); - uty_cli_insert(vio, " ", 1); - break ; - - case CMD_COMPLETE_MATCH: - uty_cli_eol (vio) ; - uty_cli_word_backwards_pure (vio); - uty_cli_word_overwrite (vio, vector_get_item(matched, 0)); - break ; - - case CMD_COMPLETE_LIST_MATCH: - len = 6 ; - for (i = 0; i < vector_end(matched); i++) - { - int sl = strlen((char*)vector_get_item(matched, i)) ; - if (len < sl) - len = sl ; - } ; + qstring line ; - n = vio->width ; - if (n == 0) - n = 60 ; - n = n / (len + 2) ; - if (n == 0) - n = 1 ; + ++hp ; + if (hp == VTY_MAXHIST) + hp = 0 ; - for (i = 0; i < vector_end(matched); i++) - { - if ((i % n) == 0) - uty_cli_out_newline(vio) ; /* clears cli_drawn */ - uty_cli_out(vio, "%-*s ", len, (char*)vector_get_item(matched, i)); - } - uty_cli_out_newline(vio) ; + if (hp == cli->h_now) + break ; /* wrapped round to "now" */ - break; + line = vector_get_item(cli->hist, hp) ; - case CMD_COMPLETE_ALREADY: - default: - break; - } ; + if (line == NULL) + break ; /* reached limit of history so far */ - cmd_free_strvec(matched); + uty_out(cli->vf->vio, " %s\n", qs_string(line)); + } } ; -/*------------------------------------------------------------------------------ - * Command Description +/*============================================================================== + * Command Completion and Command Description + * */ -static void -uty_cli_describe_command (vty_io vio, node_type_t node) -{ - cmd_return_code_t ret ; - vector describe ; - - VTY_ASSERT_LOCKED() ; +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) ; - /* Try and match the tokenised command line */ - describe = cmd_describe_command (qs_term(&vio->cl), node, &ret); +static void uty_cli_complete_keyword(vty_cli cli, const char* keyword) ; +static void uty_cli_complete_list(vty_cli cli, vector item_v) ; - uty_cli_out_newline(vio); /* clears cli_drawn */ +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) ; - /* 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); -} +static uint uty_cli_width_to_use(vty_cli cli) ; /*------------------------------------------------------------------------------ - * Show the command description. - * - * Generates lines of the form: - * - * word description text - * - * Where the word field is adjusted to suit the longest word, and the - * description text is wrapped if required (if the width of the console is - * known) so that get: + * Command completion * - * word description .................................. - * .............text + * Requires that cmd_token_position() has been called to tokenise the line and + * establish which token the cursor is in. Must NOT call this if the cursor + * is in a "special" place. * - * If one of the options is '', that is always shown last. */ static void -uty_cli_describe_show(vty_io vio, vector describe) +uty_cli_complete_command (vty_cli cli, node_type_t node) { - unsigned int i, cmd_width, desc_width; - struct desc *desc, *desc_cr ; + uint n_items ; + cmd_parsed parsed ; + cmd_item item ; - /* Get width of the longest "word" */ - cmd_width = 0; - for (i = 0; i < vector_active (describe); i++) - if ((desc = vector_slot (describe, i)) != NULL) - { - unsigned int len; + VTY_ASSERT_LOCKED() ; - if (desc->cmd[0] == '\0') - continue; + parsed = cli->parsed ; - len = strlen (desc->cmd); - if (desc->cmd[0] == '.') - len--; + /* Establish what items may be present at the current token position. */ + n_items = uty_cli_help_parse(cli, node) ; - if (cmd_width < len) - cmd_width = len; - } + if (n_items == 0) /* quit if nothing to consider */ + return ; - /* Set width of description string. */ - desc_width = vio->width - (cmd_width + 6); + if (n_items > 1) /* render list of alternatives */ + return uty_cli_complete_list(cli, parsed->item_v) ; - /* Print out description. */ - desc_cr = NULL ; /* put last if it appears */ + /* One possible item -- one or more possible commands */ + item = vector_get_item(parsed->item_v, 0) ; - for (i = 0; i < vector_active (describe); i++) - if ((desc = vector_slot (describe, i)) != NULL) - { - if (desc->cmd[0] == '\0') - continue; + switch (item->type) + { + case item_null: + zabort("invalid item_null") ; - if (strcmp (desc->cmd, command_cr) == 0) - { - desc_cr = desc; - continue; - } + case item_eol: + + case item_option_word: + + case item_vararg: - uty_cli_describe_fold (vio, cmd_width, desc_width, desc); - } + case item_word: - if (desc_cr != NULL) - uty_cli_describe_fold (vio, cmd_width, desc_width, desc_cr); + case item_ipv6_prefix: + case item_ipv6_address: + case item_ipv4_prefix: + case item_ipv4_address: + + case item_range: + return uty_cli_describe_list(cli, parsed->item_v) ; + + case item_keyword: + return uty_cli_complete_keyword(cli, item->str) ; + + default: + zabort("unknown item type") ; + } ; } ; /*------------------------------------------------------------------------------ - * Show one word and the description, folding the description as required. + * Command Description */ static void -uty_cli_describe_fold (vty_io vio, int cmd_width, - unsigned int desc_width, struct desc *desc) +uty_cli_describe_command (vty_cli cli, node_type_t node) { - char *buf; - const char *cmd, *p; - int pos; + uint n_items ; VTY_ASSERT_LOCKED() ; - cmd = desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd; - p = desc->str ; + /* Establish what items may be present at the current token position. */ + n_items = uty_cli_help_parse(cli, node) ; - /* If have a sensible description width */ - if (desc_width > 20) - { - buf = XCALLOC (MTYPE_TMP, strlen (desc->str) + 1); + if (n_items > 0) /* render list of possibilities */ + uty_cli_describe_list(cli, cli->parsed->item_v) ; +} ; - while (strlen (p) > desc_width) - { - /* move back to first space */ - for (pos = desc_width; pos > 0; pos--) - if (*(p + pos) == ' ') - break; +/*------------------------------------------------------------------------------ + * Parse for command completion and command description. + * + * Requires that cmd_token_position() has been called to tokenise the line and + * establish which token the cursor is in. Must NOT call this if the cursor + * is in a "special" place. + * + * Deal with all cases which yield no items at all. + * + * Returns: number of items to consider. + */ +static uint +uty_cli_help_parse(vty_cli cli, node_type_t node) +{ + const char* msg ; + cmd_return_code_t ret ; + uint n_items ; + + /* The preflight checks avoid getting into trouble doing command completion + * on a line with comment + */ + msg = cmd_help_preflight(cli->parsed) ; + if (msg != NULL) + { + uty_cli_out_message(cli, msg) ; + return 0 ; + } ; - /* if did not find a space, break at width */ - if (pos == 0) - pos = desc_width ; + /* Now see what the cmd_completion can come up with. */ + ret = cmd_completion(cli->parsed, node) ; - strncpy (buf, p, pos); - buf[pos] = '\0'; - uty_cli_describe_line(vio, cmd_width, cmd, buf) ; + if (ret == CMD_ERR_PARSING) + { + uint eloc = cli->prompt_len + cli->parsed->eloc ; - cmd = ""; /* for 2nd and subsequent lines */ + uty_cli_out_newline(cli) ; /* clears cli_drawn */ + uty_cli_write_n(cli, telnet_dots, eloc) ; + uty_cli_write_s(cli, "^") ; - p += pos ; /* step past what just wrote */ - while (*p == ' ') - ++p ; /* skip spaces */ - } ; + uty_cli_out_message(cli, cli->parsed->emess) ; - XFREE (MTYPE_TMP, buf); + return 0 ; } ; - uty_cli_describe_line(vio, cmd_width, cmd, p) ; -} ; + /* Will now have 0, 1 or more items which match at the current + * cursor token. + */ + n_items = vector_length(cli->parsed->item_v) ; -/*------------------------------------------------------------------------------ - * Show one description line. - */ -static void -uty_cli_describe_line(vty_io vio, int cmd_width, const char* cmd, - const char* str) -{ - if (str != NULL) - uty_cli_out (vio, " %-*s %s", cmd_width, cmd, str) ; - else - uty_cli_out (vio, " %-s", cmd) ; - uty_cli_out_newline(vio) ; + if (n_items == 0) + uty_cli_out_message(cli, "command not recognised") ; + + return n_items ; } ; -/*------------------------------------------------------------------------------ - * Prepare "vline" token array for command handler. - * - * For "help" (command completion/description), if the command line is empty, - * or ends in ' ', adds an empty token to the end of the token array. - */ -static vector -uty_cli_cmd_prepare(vty_io vio, int help) -{ - vector vline ; - vline = cmd_make_strvec(qs_term(&vio->cl)) ; - /* Note that if there is a vector of tokens, then there is at least one - * token, so can guarantee that vio->cl.len >= 1 ! - */ - if (help) - if ((vline == NULL) || isspace(*qs_chars_at(&vio->cl, vio->cl.len - 1))) - vline = cmd_add_to_strvec(vline, "") ; - return vline ; -} ; -/*============================================================================== - * VTY telnet stuff - */ - -#define TELNET_OPTION_DEBUG 0 /* 0 to turn off */ - -static const char* telnet_commands[256] = -{ - [tn_IAC ] = "IAC", - [tn_DONT ] = "DONT", - [tn_DO ] = "DO", - [tn_WONT ] = "WONT", - [tn_WILL ] = "WILL", - [tn_SB ] = "SB", - [tn_GA ] = "GA", - [tn_EL ] = "EL", - [tn_EC ] = "EC", - [tn_AYT ] = "AYT", - [tn_AO ] = "AO", - [tn_IP ] = "IP", - [tn_BREAK] = "BREAK", - [tn_DM ] = "DM", - [tn_NOP ] = "NOP", - [tn_SE ] = "SE", - [tn_EOR ] = "EOR", - [tn_ABORT] = "ABORT", - [tn_SUSP ] = "SUSP", - [tn_EOF ] = "EOF", -} ; - -static const char* telnet_options[256] = -{ - [to_BINARY] = "BINARY", /* 8-bit data path */ - [to_ECHO] = "ECHO", /* echo */ - [to_RCP] = "RCP", /* prepare to reconnect */ - [to_SGA] = "SGA", /* suppress go ahead */ - [to_NAMS] = "NAMS", /* approximate message size */ - [to_STATUS] = "STATUS", /* give status */ - [to_TM] = "TM", /* timing mark */ - [to_RCTE] = "RCTE", /* remote controlled tx and echo */ - [to_NAOL] = "NAOL", /* neg. about output line width */ - [to_NAOP] = "NAOP", /* neg. about output page size */ - [to_NAOCRD] = "NAOCRD", /* neg. about CR disposition */ - [to_NAOHTS] = "NAOHTS", /* neg. about horizontal tabstops */ - [to_NAOHTD] = "NAOHTD", /* neg. about horizontal tab disp. */ - [to_NAOFFD] = "NAOFFD", /* neg. about formfeed disposition */ - [to_NAOVTS] = "NAOVTS", /* neg. about vertical tab stops */ - [to_NAOVTD] = "NAOVTD", /* neg. about vertical tab disp. */ - [to_NAOLFD] = "NAOLFD", /* neg. about output LF disposition */ - [to_XASCII] = "XASCII", /* extended ascii character set */ - [to_LOGOUT] = "LOGOUT", /* force logout */ - [to_BM] = "BM", /* byte macro */ - [to_DET] = "DET", /* data entry terminal */ - [to_SUPDUP] = "SUPDUP", /* supdup protocol */ - [to_SUPDUPOUTPUT] = "SUPDUPOUTPUT",/* supdup output */ - [to_SNDLOC] = "SNDLOC", /* send location */ - [to_TTYPE] = "TTYPE", /* terminal type */ - [to_EOR] = "EOR", /* end or record */ - [to_TUID] = "TUID", /* TACACS user identification */ - [to_OUTMRK] = "OUTMRK", /* output marking */ - [to_TTYLOC] = "TTYLOC", /* terminal location number */ - [to_3270REGIME] = "3270REGIME", /* 3270 regime */ - [to_X3PAD] = "X3PAD", /* X.3 PAD */ - [to_NAWS] = "NAWS", /* window size */ - [to_TSPEED] = "TSPEED", /* terminal speed */ - [to_LFLOW] = "LFLOW", /* remote flow control */ - [to_LINEMODE] = "LINEMODE", /* Linemode option */ - [to_XDISPLOC] = "XDISPLOC", /* X Display Location */ - [to_OLD_ENVIRON] = "OLD_ENVIRON", /* Old - Environment variables */ - [to_AUTHENTICATION] = "AUTHENTICATION", /* Authenticate */ - [to_ENCRYPT] = "ENCRYPT", /* Encryption option */ - [to_NEW_ENVIRON] = "NEW_ENVIRON", /* New - Environment variables */ - [to_EXOPL] = "EXOPL", /* extended-options-list */ -} ; - -/*------------------------------------------------------------------------------ - * For debug. Put string or value as decimal. - */ + + + static void -uty_cli_out_dec(vty_io vio, const char* str, unsigned char u) +uty_cli_out_message(vty_cli cli, const char* msg) { - if (str != NULL) - uty_cli_out(vio, "%s ", str) ; - else - uty_cli_out(vio, "%d ", (int)u) ; + uty_cli_out_newline(cli) ; /* clears cli_drawn */ + uty_cli_write_s(cli, "% ") ; + uty_cli_write_s(cli, msg) ; + uty_cli_out_newline(cli) ; } ; -/*------------------------------------------------------------------------------ - * For debug. Put string or value as hex. - */ + + static void -uty_cli_out_hex(vty_io vio, const char* str, unsigned char u) +uty_cli_complete_keyword(vty_cli cli, const char* keyword) { - if (str != NULL) - uty_cli_out(vio, "%s ", str) ; - else - uty_cli_out(vio, "0x%02x ", (unsigned)u) ; + int pre, rep, ins, mov ; + + cmd_complete_keyword(cli->parsed, &pre, &rep, &ins, &mov) ; + + uty_cli_move(cli, pre) ; /* move to start of token */ + uty_cli_replace(cli, rep, keyword, strlen(keyword)) ; + + if (ins > 0) + uty_cli_insert(cli, " ", ins) ; + + uty_cli_move(cli, mov) ; + + return ; } ; /*------------------------------------------------------------------------------ - * Send telnet: "WILL TELOPT_ECHO" + * Show the command completions -- usually more than one. + * + * Generates a table of possible items, with fixed width entries, depending + * on the longest option. + * + * NB: the items will have been sorted before we get here. Inter alia, that + * ensures that any is shown last. */ static void -uty_will_echo (vty_io vio) +uty_cli_complete_list(vty_cli cli, vector item_v) { - unsigned char cmd[] = { tn_IAC, tn_WILL, to_ECHO }; - VTY_ASSERT_LOCKED() ; - uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); -} + uint i, len, n ; -/*------------------------------------------------------------------------------ - * Send telnet: "suppress Go-Ahead" - */ -static void -uty_will_suppress_go_ahead (vty_io vio) -{ - unsigned char cmd[] = { tn_IAC, tn_WILL, to_SGA }; - VTY_ASSERT_LOCKED() ; - uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); -} + len = 6 ; + for (i = 0 ; i < vector_length(item_v) ; ++i) + { + cmd_item item ; + uint sl ; -/*------------------------------------------------------------------------------ - * Send telnet: "don't use linemode" - */ -static void -uty_dont_linemode (vty_io vio) -{ - unsigned char cmd[] = { tn_IAC, tn_DONT, to_LINEMODE }; - VTY_ASSERT_LOCKED() ; - uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); -} + item = vector_get_item(item_v, i) ; -/*------------------------------------------------------------------------------ - * Send telnet: "Use window size" - */ -static void -uty_do_window_size (vty_io vio) -{ - unsigned char cmd[] = { tn_IAC, tn_DO, to_NAWS }; - VTY_ASSERT_LOCKED() ; - uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); -} + sl = strlen(item->str) ; + if (len < sl) + len = sl ; + } ; -/*------------------------------------------------------------------------------ - * Send telnet: "don't use lflow" -- not currently used - */ -static void -uty_dont_lflow_ahead (vty_io vio) -{ - unsigned char cmd[] = { tn_IAC, tn_DONT, to_LFLOW }; - VTY_ASSERT_LOCKED() ; - uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); -} + n = uty_cli_width_to_use(cli) / (len + 2) ; -/*------------------------------------------------------------------------------ - * The keystroke iac callback function. - * - * This deals with IAC sequences that should be dealt with as soon as they - * are read -- not stored in the keystroke stream for later processing. - */ -extern bool -uty_cli_iac_callback(keystroke_iac_callback_args) -{ - return uty_telnet_command((vty_io)context, stroke, true) ; + if (n == 0) + n = 1 ; + + for (i = 0 ; i < vector_length(item_v) ; ++i) + { + cmd_item item ; + item = vector_get_item(item_v, i) ; + + if ((i % n) == 0) + uty_cli_out_newline(cli) ; /* clears cli_drawn */ + + uty_cli_out(cli, "%-*s ", len, item->str) ; + } + uty_cli_out_newline(cli) ; } ; /*------------------------------------------------------------------------------ - * Process incoming Telnet Option(s) + * Show the command description. + * + * Generates lines of the form: * - * May be called during keystroke iac callback, or when processing CLI - * keystrokes. + * word description text * - * In particular: get telnet window size. + * Where the word field is adjusted to suit the longest word, and the + * description text is wrapped if required (if the width of the console is + * known) so that get: * - * Returns: true <=> dealt with, for: + * word description .................................. + * .............text * - * * telnet window size. + * NB: the items will have been sorted before we get here. Inter alia, that + * ensures that any is shown last. */ -static bool -uty_telnet_command(vty_io vio, keystroke stroke, bool callback) +static void +uty_cli_describe_list(vty_cli cli, vector item_v) { - uint8_t* p ; - uint8_t o ; - int left ; - bool dealt_with ; + uint i, str_width, doc_width, width ; - /* Echo to the other end if required */ - if (TELNET_OPTION_DEBUG) + /* Get width of the longest "word" */ + str_width = 0; + for (i = 0 ; i < vector_length(item_v) ; ++i) { - uty_cli_wipe(vio, 0) ; + cmd_item item ; + uint len ; + + item = vector_get_item(item_v, i) ; + + len = strlen(item->str) ; + if (item->str[0] == '.') + len--; - p = stroke->buf ; - left = stroke->len ; + if (len > str_width) + str_width = len ; + } ; - uty_cli_out_hex(vio, telnet_commands[tn_IAC], tn_IAC) ; + /* Set width of description string. + * + * Format is: + * + * __wo.....rd__description... + * ...continues + * + * The width of the word part has been established above as the width of the + * widest word. + * + * There are two spaces on either side of the word, so we here calculate the + * width of the description part + */ + width = uty_cli_width_to_use(cli) ; - if (left-- > 0) - uty_cli_out_dec(vio, telnet_commands[*p], *p) ; - ++p ; + if (width > ((str_width + 6) + 20)) + doc_width = width - (str_width + 6) ; + else + doc_width = 0 ; - if (left-- > 0) - uty_cli_out_dec(vio, telnet_options[*p], *p) ; - ++p ; + /* Print out description. */ + for (i = 0 ; i < vector_length(item_v) ; ++i) + { + cmd_item item ; + const char* str, * dp, * ep ; - if (left > 0) - { - while(left-- > 0) - uty_cli_out_hex(vio, NULL, *p++) ; + item = vector_get_item(item_v, i) ; - if (stroke->flags & kf_truncated) - uty_cli_out(vio, "... ") ; + str = item->str[0] == '.' ? item->str + 1 : item->str; + dp = item->doc ; + ep = dp + strlen(dp) ; - if (!(stroke->flags & kf_broken)) + /* If have a sensible description width */ + if (doc_width > 20) + { + while ((ep - dp) > doc_width) { - uty_cli_out_hex(vio, telnet_commands[tn_IAC], tn_IAC) ; - uty_cli_out_hex(vio, telnet_commands[tn_SE], tn_SE) ; - } - } ; + const char* np ; - if (stroke->flags & kf_broken) - uty_cli_out (vio, "BROKEN") ; + np = dp + doc_width ; /* target next position */ - uty_cli_out (vio, "\r\n") ; - } ; + while ((np > dp) && (*np != ' ')) + --np ; /* seek back to ' ' */ - /* Process the telnet command */ - dealt_with = false ; + if (np == dp) /* if no space... */ + np = dp + doc_width ; /* ...force break */ - if (stroke->flags != 0) - return dealt_with ; /* go no further if broken */ + uty_cli_describe_line(cli, str_width, str, dp, np - dp) ; - p = stroke->buf ; - left = stroke->len ; + str = ""; /* for 2nd and subsequent lines */ - passert(left >= 1) ; /* must be if not broken ! */ - passert(stroke->value == *p) ; /* or something is wrong */ + dp = np ; /* step past what just wrote */ + while (*dp == ' ') + ++dp ; /* skip spaces */ + } ; + } ; - ++p ; /* step past X of IAC X */ - --left ; + uty_cli_describe_line(cli, str_width, str, dp, ep - dp) ; + } ; - /* Decode the one command that is interesting -- "NAWS" */ - switch (stroke->value) - { - case tn_SB: - passert(left > 0) ; /* or parser failed */ + uty_cli_out_newline(cli) ; +} ; - o = *p++ ; /* the option byte */ - --left ; - switch(o) - { - case to_NAWS: - if (left != 4) - { - uzlog(NULL, LOG_WARNING, - "RFC 1073 violation detected: telnet NAWS option " - "should send %d characters, but we received %d", - (3 + 4 + 2), (3 + left + 2)) ; - } - else - { - vio->width = *p++ << 8 ; - vio->width += *p++ ; - vio->height = *p++ << 8 ; - vio->height += *p ; - - if (TELNET_OPTION_DEBUG) - uty_cli_out(vio, "TELNET NAWS window size received: " - "width %d, height %d%s", - vio->width, vio->height, telnet_newline) ; - uty_set_height(vio) ; - - dealt_with = true ; - } ; - break ; +/*------------------------------------------------------------------------------ + * Show one description line. + */ +static void +uty_cli_describe_line(vty_cli cli, uint str_width, const char* str, + const char* doc, uint len) +{ + if ((*str == '\0') && (len == 0)) + return ; /* quit if nothing to say */ - default: /* no other IAC SB