summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Hall <chris.hall@highwayman.com>2011-03-21 01:16:05 +0000
committerChris Hall <chris.hall@highwayman.com>2011-03-21 01:16:05 +0000
commit9470cb2c32eab220f796b1438b787528272cbe84 (patch)
treeb9b2cc12446173436d2bc7a32e82cc3378ec721e
parent5cae7eea451f2b7d65b5892e2c1dafc70f8b836e (diff)
downloadquagga-ex11p.tar.bz2
quagga-ex11p.tar.xz
Upgrade of "pipework" -- including piping to/from shell commandsex11p
Version 0.99.15ex11p A major overhaul.
-rw-r--r--bgpd/bgp_connection.c2
-rw-r--r--bgpd/bgp_dump.c24
-rw-r--r--bgpd/bgp_engine.h40
-rw-r--r--bgpd/bgp_main.c62
-rw-r--r--bgpd/bgp_session.c4
-rw-r--r--bgpd/bgp_session.h4
-rw-r--r--bgpd/bgp_vty.c160
-rwxr-xr-xconfigure.ac2
-rw-r--r--lib/Makefile.am64
-rw-r--r--lib/command.c904
-rw-r--r--lib/command.h7
-rw-r--r--lib/command_common.h99
-rw-r--r--lib/command_execute.c524
-rw-r--r--lib/command_execute.h52
-rw-r--r--lib/command_local.h74
-rw-r--r--lib/command_parse.c1278
-rw-r--r--lib/command_parse.h203
-rw-r--r--lib/command_queue.c368
-rw-r--r--lib/command_queue.h6
-rw-r--r--lib/if.c11
-rw-r--r--lib/keychain.c37
-rw-r--r--lib/keystroke.c49
-rw-r--r--lib/keystroke.h22
-rw-r--r--lib/log.c1352
-rw-r--r--lib/log.h185
-rw-r--r--lib/log_local.h155
-rw-r--r--lib/memory.c108
-rw-r--r--lib/memory.h77
-rw-r--r--lib/memtypes.c1
-rw-r--r--lib/misc.h45
-rw-r--r--lib/mqueue.c43
-rw-r--r--lib/mqueue.h2
-rw-r--r--lib/network.c250
-rw-r--r--lib/network.h7
-rw-r--r--lib/privs.c72
-rw-r--r--lib/pthread_safe.c34
-rw-r--r--lib/qdebug_nb.h61
-rw-r--r--lib/qfstring.c92
-rw-r--r--lib/qfstring.h6
-rw-r--r--lib/qiovec.c280
-rw-r--r--lib/qiovec.h174
-rw-r--r--lib/qlib_init.c13
-rw-r--r--lib/qlib_init.h4
-rw-r--r--lib/qpath.c785
-rw-r--r--lib/qpath.h157
-rw-r--r--lib/qpnexus.c100
-rw-r--r--lib/qpnexus.h52
-rw-r--r--lib/qpselect.c154
-rw-r--r--lib/qpselect.h26
-rw-r--r--lib/qpthreads.c212
-rw-r--r--lib/qpthreads.h188
-rw-r--r--lib/qrand.c48
-rw-r--r--lib/qrand.h52
-rw-r--r--lib/qstring.c95
-rw-r--r--lib/qstring.h60
-rw-r--r--lib/qtime.c85
-rw-r--r--lib/qtime.h8
-rw-r--r--lib/qtimers.h35
-rw-r--r--lib/routemap.c17
-rw-r--r--lib/sigevent.c990
-rw-r--r--lib/sigevent.h34
-rw-r--r--lib/thread.c10
-rw-r--r--lib/vio_fifo.c90
-rw-r--r--lib/vio_fifo.h47
-rw-r--r--lib/vio_lines.c585
-rw-r--r--lib/vio_lines.h121
-rw-r--r--lib/vty.c570
-rw-r--r--lib/vty.h27
-rw-r--r--lib/vty_cli.c2192
-rw-r--r--lib/vty_cli.h135
-rw-r--r--lib/vty_command.c1785
-rw-r--r--lib/vty_command.h48
-rw-r--r--lib/vty_common.h22
-rw-r--r--lib/vty_io.c1532
-rw-r--r--lib/vty_io.h257
-rw-r--r--lib/vty_io_basic.c543
-rw-r--r--lib/vty_io_basic.h67
-rw-r--r--lib/vty_io_file.c1802
-rw-r--r--lib/vty_io_file.h31
-rw-r--r--lib/vty_io_shell.c14
-rw-r--r--lib/vty_io_term.c1084
-rw-r--r--lib/vty_io_term.h12
-rw-r--r--lib/vty_io_vsh.c14
-rw-r--r--lib/vty_local.h116
-rw-r--r--lib/vty_log.c258
-rw-r--r--lib/vty_log.h41
-rw-r--r--lib/vty_pipe.c2742
-rw-r--r--lib/zassert.h8
-rw-r--r--lib/zconfig.h6
89 files changed, 14860 insertions, 9354 deletions
diff --git a/bgpd/bgp_connection.c b/bgpd/bgp_connection.c
index 151d3309..560bac4d 100644
--- a/bgpd/bgp_connection.c
+++ b/bgpd/bgp_connection.c
@@ -155,7 +155,7 @@ bgp_connection_init_new(bgp_connection connection, bgp_session session,
/* Link back to session, point at its mutex and point session here */
connection->session = session ;
- connection->p_mutex = &session->mutex ;
+ connection->p_mutex = session->mutex ;
connection->lock_count = 0 ; /* no question about it */
connection->paf = AF_UNSPEC ;
diff --git a/bgpd/bgp_dump.c b/bgpd/bgp_dump.c
index 3bc318a5..112cfd34 100644
--- a/bgpd/bgp_dump.c
+++ b/bgpd/bgp_dump.c
@@ -21,6 +21,7 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
#include <zebra.h>
#include "log.h"
+#include "vty.h"
#include "stream.h"
#include "sockunion.h"
#include "command.h"
@@ -28,6 +29,8 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
#include "thread.h"
#include "linklist.h"
#include "bgpd/bgp_table.h"
+#include "qpath.h"
+#include "qstring.h"
#include "bgpd/bgpd.h"
#include "bgpd/bgp_route.h"
@@ -97,8 +100,8 @@ bgp_dump_open_file (struct bgp_dump *bgp_dump)
int ret;
time_t clock;
struct tm tm;
- char fullpath[MAXPATHLEN];
- char realpath[MAXPATHLEN];
+ qpath path ;
+ qstring name ;
mode_t oldumask;
time (&clock);
@@ -106,11 +109,17 @@ bgp_dump_open_file (struct bgp_dump *bgp_dump)
if (bgp_dump->filename[0] != DIRECTORY_SEP)
{
- sprintf (fullpath, "%s/%s", vty_get_cwd (), bgp_dump->filename);
- ret = strftime (realpath, MAXPATHLEN, fullpath, &tm);
+ path = vty_getcwd(NULL) ;
+ qpath_append_str(path, bgp_dump->filename) ;
}
else
- ret = strftime (realpath, MAXPATHLEN, bgp_dump->filename, &tm);
+ path = qpath_set(NULL, bgp_dump->filename) ;
+
+ name = qs_new_size(NULL, qpath_len(path) + 60) ;
+
+ ret = strftime (qs_char_nn(name), qs_len_nn(name), qpath_string(path), &tm);
+
+ qpath_free(path) ;
if (ret == 0)
{
@@ -123,11 +132,12 @@ bgp_dump_open_file (struct bgp_dump *bgp_dump)
oldumask = umask(0777 & ~LOGFILE_MASK);
- bgp_dump->fp = fopen (realpath, "w");
+ bgp_dump->fp = fopen (qs_char_nn(name), "w");
if (bgp_dump->fp == NULL)
{
- zlog_warn("bgp_dump_open_file: %s: %s", realpath, errtoa(errno, 0).str);
+ zlog_warn("bgp_dump_open_file: %s: %s", qs_char_nn(name),
+ errtoa(errno, 0).str);
umask(oldumask);
return NULL;
}
diff --git a/bgpd/bgp_engine.h b/bgpd/bgp_engine.h
index bedfc18e..821d8127 100644
--- a/bgpd/bgp_engine.h
+++ b/bgpd/bgp_engine.h
@@ -31,19 +31,31 @@
#include "lib/log.h"
/*==============================================================================
- * DEBUG setting
+ * BGP_ENGINE_DEBUG setting
+ *
+ * Set to 1 if defined, but blank.
+ * Set to QDEBUG if not defined.
+ *
+ * Force to 0 if BGP_ENGINE_NO_DEBUG is defined and not zero.
+ *
+ * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to:
+ *
+ * * can set BGP_ENGINE_DEBUG == 0 to turn off debug
+ * * or set BGP_ENGINE_DEBUG != 0 to turn on debug
+ * * or set BGP_ENGINE_NO_DEBUG != to force debug off
*/
-
-#ifdef BGP_ENGINE_DEBUG /* Can be forced from outside */
-# if BGP_ENGINE_DEBUG
-# define BGP_ENGINE_DEBUG 1 /* Force 1 or 0 */
-#else
-# define BGP_ENGINE_DEBUG 0
+#ifdef BGP_ENGINE_DEBUG /* If defined, make it 1 or 0 */
+# if IS_BLANK_OPTION(BGP_ENGINE_DEBUG)
+# undef BGP_ENGINE_DEBUG
+# define BGP_ENGINE_DEBUG 1
# endif
-#else
-# ifdef QDEBUG
-# define BGP_ENGINE_DEBUG 1 /* Follow QDEBUG */
-#else
+#else /* If not defined, follow QDEBUG */
+# define BGP_ENGINE_DEBUG QDEBUG
+#endif
+
+#ifdef BGP_ENGINE_NO_DEBUG /* Override, if defined */
+# if IS_NOT_ZERO_OPTION(BGP_ENGINE_NO_DEBUG)
+# undef BGP_ENGINE_DEBUG
# define BGP_ENGINE_DEBUG 0
# endif
#endif
@@ -80,7 +92,7 @@ bgp_queue_logging(const char* name, mqueue_queue mq, struct queue_stats* stats)
++stats->count ;
- qpt_mutex_lock(&mq->mutex) ;
+ qpt_mutex_lock(mq->mutex) ;
if (mq->count > stats->max)
stats->max = mq->count ;
@@ -91,7 +103,7 @@ bgp_queue_logging(const char* name, mqueue_queue mq, struct queue_stats* stats)
if (stats->count < 1000)
{
- qpt_mutex_unlock(&mq->mutex) ;
+ qpt_mutex_unlock(mq->mutex) ;
return ;
} ;
@@ -106,7 +118,7 @@ bgp_queue_logging(const char* name, mqueue_queue mq, struct queue_stats* stats)
assert(my_count == mq->count) ;
- qpt_mutex_unlock(&mq->mutex) ;
+ qpt_mutex_unlock(mq->mutex) ;
average = stats->total * 1000 ;
average = (average / stats->count) + 5 ;
diff --git a/bgpd/bgp_main.c b/bgpd/bgp_main.c
index 6bbc8197..82db783a 100644
--- a/bgpd/bgp_main.c
+++ b/bgpd/bgp_main.c
@@ -18,15 +18,15 @@ along with GNU Zebra; see the file COPYING. If not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA. */
-#include <zebra.h>
-#include <stdbool.h>
+#include "zebra.h"
+#include "misc.h"
#include "vector.h"
#include "vty.h"
#include "command.h"
#include "getopt.h"
#include "thread.h"
-#include <lib/version.h>
+#include "lib/version.h"
#include "memory.h"
#include "prefix.h"
#include "log.h"
@@ -118,13 +118,13 @@ static zebra_capabilities_t _caps_p [] =
struct zebra_privs_t bgpd_privs =
{
#if defined(QUAGGA_USER) && defined(QUAGGA_GROUP)
- .user = QUAGGA_USER,
- .group = QUAGGA_GROUP,
+ .user = QUAGGA_USER,
+ .group = QUAGGA_GROUP,
#endif
#ifdef VTY_GROUP
.vty_group = VTY_GROUP,
#endif
- .caps_p = _caps_p,
+ .caps_p = _caps_p,
.cap_num_p = sizeof(_caps_p)/sizeof(_caps_p[0]),
.cap_num_i = 0,
};
@@ -184,7 +184,7 @@ void sigusr2 (void);
/* prototypes */
static void bgp_exit (int);
-static void init_second_stage(int pthreads);
+static void init_second_stage(bool pthreads);
static void bgp_in_thread_init(void);
static void routing_start(void) ;
static void routing_finish(void) ;
@@ -206,10 +206,6 @@ static struct quagga_signal_t bgp_signals[] =
.handler = &sigusr1,
},
{
- .signal = SIGUSR2,
- .handler = &sigusr2,
- },
- {
.signal = SIGINT,
.handler = &sigint,
},
@@ -261,17 +257,6 @@ sigusr1 (void)
zlog_rotate (NULL);
}
-/* SIGUSR2 handler. */
-void
-sigusr2 (void)
-{
- /* Used to signal message queues */
- if (qpthreads_enabled)
- return;
- else
- exit(1);
-}
-
/*------------------------------------------------------------------------------
* Final exit code...
*
@@ -392,8 +377,8 @@ bgp_exit (int status)
*
* 1. if it's there, invoke the command in the usual way
*
- * 2. if it's not there, invoke the command but with a NULL set of arguments,
- * which signals the "default" nature of the call.
+ * 2. if it's not there, invoke the command but with a *negative* count of
+ * arguments, which signals the "default" nature of the call.
*
* This mechanism is used so that the "threaded_cmd" is the time at which
* second stage initialisation is done. (But only once -- not on rereading
@@ -408,8 +393,8 @@ DEFUN_HID_CALL (threaded,
"threaded",
"Use pthreads\n")
{
- if (argv != NULL)
- config_threaded = 1 ; /* Explicit command => turn on threading */
+ if (argc == 0)
+ config_threaded = true ; /* Explicit command => turn on threading */
if (!done_2nd_stage_init)
init_second_stage(config_threaded) ;
@@ -425,7 +410,7 @@ DEFUN_HID_CALL (threaded,
* the message queues are available for the configuration data.
*/
static void
-init_second_stage(int pthreads)
+init_second_stage(bool pthreads)
{
assert(!done_2nd_stage_init) ;
@@ -435,13 +420,13 @@ init_second_stage(int pthreads)
bgp_peer_index_mutex_init();
/* Make nexus for main thread, always needed */
- cli_nexus = qpn_init_new(cli_nexus, 1); /* main thread */
+ cli_nexus = qpn_init_new(cli_nexus, true); /* main thread */
/* if using pthreads create additional nexus */
if (qpthreads_enabled)
{
- bgp_nexus = qpn_init_new(bgp_nexus, 0);
- routing_nexus = qpn_init_new(routing_nexus, 0);
+ bgp_nexus = qpn_init_new(bgp_nexus, false);
+ routing_nexus = qpn_init_new(routing_nexus, false);
}
else
{
@@ -499,9 +484,8 @@ main (int argc, char **argv)
/* Set umask before anything for security */
umask (0027);
-#ifdef QDEBUG
- fprintf(stderr, "%s\n", debug_banner);
-#endif
+ if (qdebug)
+ fprintf(stderr, "%s\n", debug_banner);
qlib_init_first_stage();
@@ -603,7 +587,11 @@ main (int argc, char **argv)
/* Initializations. */
srand (time (NULL));
signal_init (master, Q_SIGC(bgp_signals), bgp_signals);
- zprivs_init (&bgpd_privs);
+
+ cmd_getcwd() ; /* while have privilege */
+
+ zprivs_init (&bgpd_privs); /* lowers privileges */
+
cmd_init (1);
install_element (CONFIG_NODE, &threaded_cmd);
vty_init (master);
@@ -652,9 +640,9 @@ main (int argc, char **argv)
vty_start(vty_addr, vty_port, BGP_VTYSH_PATH);
/* Print banner. */
-#ifdef QDEBUG
- zlog_notice("%s", debug_banner);
-#endif
+ if (qdebug)
+ zlog_notice("%s", debug_banner);
+
zlog_notice ("BGPd %s%s starting: vty@%d, bgp@%s:%d",
QUAGGA_VERSION,
(qpthreads_enabled ? " pthreaded" : ""),
diff --git a/bgpd/bgp_session.c b/bgpd/bgp_session.c
index 681075da..0c021b0f 100644
--- a/bgpd/bgp_session.c
+++ b/bgpd/bgp_session.c
@@ -124,7 +124,7 @@ bgp_session_init_new(bgp_peer peer)
session = XCALLOC(MTYPE_BGP_SESSION, sizeof(struct bgp_session)) ;
- qpt_mutex_init_new(&session->mutex, qpt_mutex_recursive) ;
+ qpt_mutex_init_new(session->mutex, qpt_mutex_recursive) ;
session->peer = peer ;
bgp_peer_lock(peer) ; /* Account for the session->peer pointer */
@@ -240,7 +240,7 @@ bgp_session_delete(bgp_peer peer)
BGP_SESSION_UNLOCK(session) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
- qpt_mutex_destroy(&session->mutex, 0) ;
+ qpt_mutex_destroy(session->mutex, 0) ;
/* Proceed to dismantle the session. */
diff --git a/bgpd/bgp_session.h b/bgpd/bgp_session.h
index e1a4c51b..cc8acc19 100644
--- a/bgpd/bgp_session.h
+++ b/bgpd/bgp_session.h
@@ -304,12 +304,12 @@ MQB_ARGS_SIZE_OK(bgp_session_ttl_args) ;
inline static void BGP_SESSION_LOCK(bgp_session session)
{
- qpt_mutex_lock(&session->mutex) ;
+ qpt_mutex_lock(session->mutex) ;
} ;
inline static void BGP_SESSION_UNLOCK(bgp_session session)
{
- qpt_mutex_unlock(&session->mutex) ;
+ qpt_mutex_unlock(session->mutex) ;
} ;
/*==============================================================================
diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c
index 9195b9f3..dc72f6e7 100644
--- a/bgpd/bgp_vty.c
+++ b/bgpd/bgp_vty.c
@@ -314,12 +314,13 @@ DEFUN_DEPRECATED (neighbor_version,
}
/* "router bgp" commands. */
-DEFUN (router_bgp,
- router_bgp_cmd,
- "router bgp " CMD_AS_RANGE,
- ROUTER_STR
- BGP_STR
- AS_STR)
+DEFUN_ATTR (router_bgp,
+ router_bgp_cmd,
+ "router bgp " CMD_AS_RANGE,
+ ROUTER_STR
+ BGP_STR
+ AS_STR,
+ CMD_ATTR_NODE + BGP_NODE)
{
int ret;
as_t as;
@@ -338,9 +339,11 @@ DEFUN (router_bgp,
vty_out (vty, "Please specify 'bgp multiple-instance' first%s",
VTY_NEWLINE);
return CMD_WARNING;
+
case BGP_ERR_AS_MISMATCH:
vty_out (vty, "BGP is already running; AS is %u%s", as, VTY_NEWLINE);
return CMD_WARNING;
+
case BGP_ERR_INSTANCE_MISMATCH:
vty_out (vty, "BGP view name and AS number mismatch%s", VTY_NEWLINE);
vty_out (vty, "BGP instance is already running; AS is %u%s",
@@ -354,14 +357,15 @@ DEFUN (router_bgp,
return CMD_SUCCESS;
}
-ALIAS (router_bgp,
- router_bgp_view_cmd,
- "router bgp " CMD_AS_RANGE " view WORD",
- ROUTER_STR
- BGP_STR
- AS_STR
- "BGP view\n"
- "view name\n")
+ALIAS_ATTR (router_bgp,
+ router_bgp_view_cmd,
+ "router bgp " CMD_AS_RANGE " view WORD",
+ ROUTER_STR
+ BGP_STR
+ AS_STR
+ "BGP view\n"
+ "view name\n",
+ CMD_ATTR_NODE + BGP_NODE)
/* "no router bgp" commands. */
DEFUN (no_router_bgp,
@@ -4008,90 +4012,118 @@ DEFUN (no_neighbor_allowas_in,
}
/* Address family configuration. */
-DEFUN (address_family_ipv4,
- address_family_ipv4_cmd,
- "address-family ipv4",
- "Enter Address Family command mode\n"
- "Address family\n")
+DEFUN_ATTR (address_family_ipv4,
+ address_family_ipv4_cmd,
+ "address-family ipv4",
+ "Enter Address Family command mode\n"
+ "Address family\n",
+ CMD_ATTR_NODE + BGP_IPV4_NODE)
{
vty->node = BGP_IPV4_NODE ;
return CMD_SUCCESS;
}
-DEFUN (address_family_ipv4_safi,
- address_family_ipv4_safi_cmd,
- "address-family ipv4 (unicast|multicast)",
- "Enter Address Family command mode\n"
- "Address family\n"
- "Address Family modifier\n"
- "Address Family modifier\n")
+DEFUN_ATTR (address_family_ipv4_safi_unicast,
+ address_family_ipv4_safi_unicast_cmd,
+ "address-family ipv4 unicast",
+ "Enter Address Family command mode\n"
+ "Address family\n"
+ "Address Family modifier\n",
+ CMD_ATTR_NODE + BGP_IPV4_NODE)
{
- if (strncmp (argv[0], "m", 1) == 0)
- vty->node = BGP_IPV4M_NODE ;
- else
- vty->node = BGP_IPV4_NODE ;
+ vty->node = BGP_IPV4_NODE ;
+ return CMD_SUCCESS;
+}
+DEFUN_ATTR (address_family_ipv4_safi_multicast,
+ address_family_ipv4_safi_multicast_cmd,
+ "address-family ipv4 multicast",
+ "Enter Address Family command mode\n"
+ "Address family\n"
+ "Address Family modifier\n",
+ CMD_ATTR_NODE + BGP_IPV4M_NODE)
+{
+ vty->node = BGP_IPV4M_NODE ;
return CMD_SUCCESS;
}
-DEFUN (address_family_ipv6,
- address_family_ipv6_cmd,
- "address-family ipv6",
- "Enter Address Family command mode\n"
- "Address family\n")
+DEFUN_ATTR (address_family_ipv6,
+ address_family_ipv6_cmd,
+ "address-family ipv6",
+ "Enter Address Family command mode\n"
+ "Address family\n",
+ CMD_ATTR_NODE + BGP_IPV6_NODE)
{
vty->node = BGP_IPV6_NODE ;
return CMD_SUCCESS;
}
-DEFUN (address_family_ipv6_safi,
- address_family_ipv6_safi_cmd,
- "address-family ipv6 (unicast|multicast)",
- "Enter Address Family command mode\n"
- "Address family\n"
- "Address Family modifier\n"
- "Address Family modifier\n")
+DEFUN_ATTR (address_family_ipv6_safi_unicast,
+ address_family_ipv6_safi_unicast_cmd,
+ "address-family ipv6 unicast",
+ "Enter Address Family command mode\n"
+ "Address family\n"
+ "Address Family modifier\n",
+ CMD_ATTR_NODE + BGP_IPV6_NODE)
{
- if (strncmp (argv[0], "m", 1) == 0)
- vty->node = BGP_IPV6M_NODE ;
- else
- vty->node = BGP_IPV6_NODE ;
+ vty->node = BGP_IPV6_NODE ;
+ return CMD_SUCCESS;
+}
+DEFUN_ATTR (address_family_ipv6_safi_multicast,
+ address_family_ipv6_safi_multicast_cmd,
+ "address-family ipv6 multicast",
+ "Enter Address Family command mode\n"
+ "Address family\n"
+ "Address Family modifier\n",
+ CMD_ATTR_NODE + BGP_IPV6M_NODE)
+{
+ vty->node = BGP_IPV6M_NODE ;
return CMD_SUCCESS;
}
-DEFUN (address_family_vpnv4,
- address_family_vpnv4_cmd,
- "address-family vpnv4",
- "Enter Address Family command mode\n"
- "Address family\n")
+DEFUN_ATTR (address_family_vpnv4,
+ address_family_vpnv4_cmd,
+ "address-family vpnv4",
+ "Enter Address Family command mode\n"
+ "Address family\n",
+ CMD_ATTR_NODE + BGP_VPNV4_NODE)
{
vty->node = BGP_VPNV4_NODE ;
return CMD_SUCCESS;
}
-ALIAS (address_family_vpnv4,
+ALIAS_ATTR (address_family_vpnv4,
address_family_vpnv4_unicast_cmd,
"address-family vpnv4 unicast",
"Enter Address Family command mode\n"
"Address family\n"
- "Address Family Modifier\n")
+ "Address Family Modifier\n",
+ CMD_ATTR_NODE + BGP_VPNV4_NODE)
-DEFUN (exit_address_family,
- exit_address_family_cmd,
- "exit-address-family",
- "Exit from Address Family configuration mode\n")
+DEFUN_ATTR (exit_address_family,
+ exit_address_family_cmd,
+ "exit-address-family",
+ "Exit from Address Family configuration mode\n",
+ CMD_ATTR_NODE + BGP_NODE)
{
node_type_t node = vty->node ;
- if (node == BGP_IPV4_NODE
+ if ( node == BGP_IPV4_NODE
|| node == BGP_IPV4M_NODE
|| node == BGP_VPNV4_NODE
|| node == BGP_IPV6_NODE
|| node == BGP_IPV6M_NODE)
- vty->node = BGP_NODE ;
- return CMD_SUCCESS;
-}
+ {
+ vty->node = BGP_NODE ;
+ return CMD_SUCCESS ;
+ }
+ else
+ {
+ vty_out(vty, "%% No address family to leave\n") ;
+ return CMD_WARNING ;
+ } ;
+} ;
/* BGP clear sort. */
enum clear_sort
@@ -9626,10 +9658,12 @@ bgp_vty_init (void)
/* address-family commands. */
install_element (BGP_NODE, &address_family_ipv4_cmd);
- install_element (BGP_NODE, &address_family_ipv4_safi_cmd);
+ install_element (BGP_NODE, &address_family_ipv4_safi_unicast_cmd);
+ install_element (BGP_NODE, &address_family_ipv4_safi_multicast_cmd);
#ifdef HAVE_IPV6
install_element (BGP_NODE, &address_family_ipv6_cmd);
- install_element (BGP_NODE, &address_family_ipv6_safi_cmd);
+ install_element (BGP_NODE, &address_family_ipv6_safi_unicast_cmd);
+ install_element (BGP_NODE, &address_family_ipv6_safi_multicast_cmd);
#endif /* HAVE_IPV6 */
install_element (BGP_NODE, &address_family_vpnv4_cmd);
install_element (BGP_NODE, &address_family_vpnv4_unicast_cmd);
diff --git a/configure.ac b/configure.ac
index 4f100d94..462ee76d 100755
--- a/configure.ac
+++ b/configure.ac
@@ -8,7 +8,7 @@
## $Id$
AC_PREREQ(2.53)
-AC_INIT(Quagga, 0.99.15ex10p, [http://bugzilla.quagga.net])
+AC_INIT(Quagga, 0.99.15ex11p, [http://bugzilla.quagga.net])
AC_CONFIG_SRCDIR(lib/zebra.h)
AC_CONFIG_MACRO_DIR([m4])
diff --git a/lib/Makefile.am b/lib/Makefile.am
index cc8fbd3a..2d41399c 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -4,22 +4,24 @@ INCLUDES = @INCLUDES@ -I.. -I$(top_srcdir) -I$(top_srcdir)/lib @SNMP_INCLUDES@
DEFS = @DEFS@ -DSYSCONFDIR=\"$(sysconfdir)/\"
lib_LTLIBRARIES = libzebra.la
-libzebra_la_LDFLAGS = -version-info 0:0:0
+libzebra_la_LDFLAGS = -version-info 0:0:0
libzebra_la_SOURCES = \
- network.c pid_output.c getopt.c getopt1.c daemon.c \
- checksum.c vector.c linklist.c vty.c command.c \
- sockunion.c prefix.c thread.c if.c memory.c buffer.c table.c hash.c \
- filter.c routemap.c distribute.c stream.c str.c log.c plist.c \
- zclient.c sockopt.c smux.c md5.c if_rmap.c keychain.c privs.c \
- sigevent.c pqueue.c jhash.c memtypes.c workqueue.c symtab.c heap.c \
- qtime.c qpthreads.c mqueue.c qpselect.c qtimers.c qpnexus.c \
- command_parse.c command_execute.c command_queue.c vty_command.c \
- vty_io.c vty_io_file.c vty_io_shell.c vty_io_vsh.c vty_io_term.c vty_cli.c \
- vty_io_basic.c keystroke.c qstring.c elstring.c vio_fifo.c vio_lines.c \
- qlib_init.c pthread_safe.c list_util.c \
- qiovec.c qfstring.c errno_names.c
-
+ buffer.c checksum.c command.c command_execute.c \
+ command_parse.c command_queue.c daemon.c distribute.c \
+ elstring.c errno_names.c filter.c getopt.c getopt1.c \
+ hash.c heap.c if.c if_rmap.c jhash.c keychain.c keystroke.c \
+ linklist.c list_util.c log.c md5.c memory.c memtypes.c mqueue.c \
+ network.c pid_output.c plist.c pqueue.c prefix.c privs.c \
+ pthread_safe.c qfstring.c qiovec.c qlib_init.c qpath.c \
+ qpnexus.c qpselect.c qpthreads.c qrand.c qstring.c \
+ qtime.c qtimers.c routemap.c sigevent.c smux.c \
+ sockopt.c sockunion.c str.c stream.c symtab.c \
+ table.c thread.c vector.c vio_fifo.c vio_lines.c \
+ vty.c vty_cli.c vty_command.c vty_io.c vty_io_basic.c \
+ vty_io_file.c vty_io_shell.c vty_io_term.c vty_io_vsh.c \
+ vty_log.c workqueue.c zclient.c
+
BUILT_SOURCES = memtypes.h route_types.h
libzebra_la_DEPENDENCIES = @LIB_REGEX@
@@ -27,22 +29,24 @@ libzebra_la_DEPENDENCIES = @LIB_REGEX@
libzebra_la_LIBADD = @LIB_REGEX@
pkginclude_HEADERS = \
- zebra.h zconfig.h buffer.h checksum.h command.h filter.h getopt.h hash.h \
- if.h linklist.h log.h \
- memory.h network.h prefix.h routemap.h distribute.h sockunion.h \
- str.h stream.h table.h thread.h vector.h version.h vty.h \
- plist.h zclient.h sockopt.h smux.h md5.h if_rmap.h keychain.h \
- privs.h sigevent.h pqueue.h jhash.h zassert.h memtypes.h \
- workqueue.h route_types.h symtab.h heap.h \
- qtime.h qpthreads.h mqueue.h qpselect.h qtimers.h qpnexus.h \
- command_parse.h command_queue.h vty_command.h qlib_init.h qafi_safi.h \
- confirm.h misc.h vargs.h miyagi.h pthread_safe.h list_util.h \
- tstring.h command_common.h command_local.h vty_common.h vty_local.h \
- vty_io.h vty_io_file.h vty_io_shell.h vty_io_vsh.h vty_io_term.h vty_cli.h \
- vty_io_basic.h keystroke.h qstring.h elstring.h vio_fifo.h vio_lines.h \
- qiovec.h qfstring.h errno_names.h \
- route_types.h command_execute.h
-
+ command.h command_common.h command_execute.h \
+ command_local.h command_parse.h command_queue.h confirm.h \
+ distribute.h elstring.h errno_names.h filter.h getopt.h \
+ hash.h heap.h if.h if_rmap.h jhash.h keychain.h \
+ keystroke.h linklist.h list_util.h log.h log_local.h \
+ md5.h memory.h memtypes.h misc.h miyagi.h mqueue.h \
+ network.h plist.h pqueue.h prefix.h privs.h \
+ pthread_safe.h qafi_safi.h qdebug_nb.h qfstring.h qiovec.h \
+ qlib_init.h qpath.h qpnexus.h qpselect.h qpthreads.h \
+ qrand.h qstring.h qtime.h qtimers.h route_types.h \
+ routemap.h sigevent.h smux.h sockopt.h sockunion.h str.h \
+ stream.h symtab.h table.h thread.h tstring.h vargs.h \
+ vector.h version.h vio_fifo.h vio_lines.h vty.h vty_cli.h \
+ vty_command.h vty_common.h vty_io.h vty_io_basic.h \
+ vty_io_file.h vty_io_shell.h vty_io_term.h vty_io_vsh.h \
+ vty_local.h vty_log.h workqueue.h zassert.h zclient.h \
+ zconfig.h zebra.h
+
EXTRA_DIST = regex.c regex-gnu.h memtypes.awk route_types.awk route_types.txt
memtypes.h: $(srcdir)/memtypes.c $(srcdir)/memtypes.awk
diff --git a/lib/command.c b/lib/command.c
index adea80e4..61f554bb 100644
--- a/lib/command.c
+++ b/lib/command.c
@@ -46,6 +46,7 @@ Boston, MA 02111-1307, USA. */
#include "vty_local.h"
#include "vty_command.h"
#include "vty_io.h"
+#include "network.h"
/* Vector of cmd_node, one for each known node, built during daemon
* initialisation.
@@ -57,52 +58,57 @@ vector node_vector = NULL ;
/*==============================================================================
* Default motd string.
*/
-#define DEFAULT_MOTD \
-"\n" \
-"Hello, this is " QUAGGA_PROGNAME " (version " QUAGGA_VERSION ")\n" \
- QUAGGA_COPYRIGHT "\n" \
+const char* default_motd =
"\n"
+"Hello, this is " QUAGGA_PROGNAME " (version " QUAGGA_VERSION ")\n"
+ QUAGGA_COPYRIGHT "\n"
+"\n" ;
-#ifdef QDEBUG
-const char *debug_banner =
- QUAGGA_PROGNAME " version " QUAGGA_VERSION " QDEBUG=" QDEBUG " "
- __DATE__ " " __TIME__;
-#endif
+const char* debug_banner =
+ QUAGGA_PROGNAME " version " QUAGGA_VERSION " QDEBUG=" QDEBUG_NAME " "
+ __DATE__ " " __TIME__ ;
/*==============================================================================
* Host information structure -- shared across command/vty
+ *
+ * Must have VTY_LOCK() or not be running multiple pthreads to access this !
*/
struct host host =
{
/* Host name of this router. */
- .name_set = false,
- .name = NULL, /* set by cmd_init */
- .name_gen = 0, /* set by cmd_init */
+ .name = NULL, /* set by cmd_init */
+ .name_set = false,
+ .name_gen = 0, /* set by cmd_init */
/* Password for vty interface. */
- .password = NULL,
+ .password = NULL,
.password_encrypted = false,
/* Enable password */
- .enable = NULL,
- .enable_encrypted = false,
+ .enable = NULL,
+ .enable_encrypted = false,
/* System wide terminal lines. */
- .lines = -1, /* unset */
+ .lines = -1, /* unset */
/* Log filename. */
- .logfile = NULL,
+ .logfile = NULL,
/* config file name of this host */
- .config_file = NULL,
+ .config_file = NULL,
+ .config_dir = NULL,
/* Flags for services */
- .advanced = false,
- .encrypt = false,
+ .advanced = false,
+ .encrypt = false,
/* Banner configuration. */
- .motd = DEFAULT_MOTD,
- .motdfile = NULL,
+ .motd = NULL,
+ .motdfile = NULL,
+
+ /* Nobody has the config symbol of power */
+ .config = false,
+ .config_brand = 0,
/* allow VTY to start without password */
.no_password_check = false,
@@ -119,8 +125,8 @@ struct host host =
/* Vty access-class for IPv6. */
.vty_ipv6_accesslist_name = NULL,
- /* Current directory -- initialised in vty_init() */
- .vty_cwd = NULL,
+ /* Current directory -- initialised in cmd_getcwd() */
+ .cwd = NULL,
} ;
/*==============================================================================
@@ -220,6 +226,8 @@ cmd_get_sys_host_name(void)
static void
cmd_new_host_name(const char* name)
{
+ VTY_ASSERT_LOCKED() ;
+
if ((host.name == NULL) || (strcmp(host.name, name) != 0))
{
XFREE(MTYPE_HOST, host.name) ;
@@ -239,7 +247,7 @@ static struct cmd_node auth_node =
.prompt = "Password: ",
.parent = AUTH_NODE, /* self => no parent */
- .exit_to = AUTH_NODE, /* self => no exit */
+ .exit_to = NULL_NODE, /* close ! */
.end_to = AUTH_NODE, /* self => no end */
};
@@ -248,7 +256,7 @@ static struct cmd_node view_node =
.node = VIEW_NODE,
.prompt = "%s> ",
- .parent = AUTH_NODE, /* self => no parent */
+ .parent = VIEW_NODE, /* self => no parent */
.exit_to = NULL_NODE, /* close ! */
.end_to = VIEW_NODE, /* self => no end */
};
@@ -258,7 +266,7 @@ static struct cmd_node restricted_node =
.node = RESTRICTED_NODE,
.prompt = "%s$ ",
- .parent = AUTH_NODE, /* self => no parent */
+ .parent = RESTRICTED_NODE, /* self => no parent */
.exit_to = NULL_NODE, /* close ! */
.end_to = RESTRICTED_NODE, /* self => no end */
};
@@ -269,7 +277,7 @@ static struct cmd_node auth_enable_node =
.prompt = "Password: ",
.parent = AUTH_ENABLE_NODE, /* self => no parent */
- .exit_to = NULL_NODE, /* close ! */
+ .exit_to = VIEW_NODE, /* fall back */
.end_to = AUTH_ENABLE_NODE, /* self => no end */
};
@@ -288,7 +296,7 @@ static struct cmd_node config_node =
.node = CONFIG_NODE,
.prompt = "%s(config)# ",
- .parent = CONFIG_NODE, /* self => no parent */
+ .parent = ENABLE_NODE, /* more or less */
.exit_to = ENABLE_NODE, /* exit == end for CONFIG_NODE */
.end_to = ENABLE_NODE, /* standard end action */
@@ -465,13 +473,14 @@ cmp_desc (const void *p, const void *q)
*
* Default parent node:
*
- * * all nodes >= NODE_CONFIG have NODE_CONFIG as a parent
- * * all nodes < NODE_CONFIG are their own parents
+ * * all nodes > NODE_CONFIG have NODE_CONFIG as parent
+ * * node == NODE_CONFIG has ENABLE_NODE as parent
+ * * all nodes < NODE_CONFIG are their own parents (including ENABLE_NODE)
*
* Default exit_to:
*
* * all nodes > NODE_CONFIG exit_to their parent
- * * node == NODE_CONFIG exit_to ENABLE_NODE (same as end_to !)
+ * * node == NODE_CONFIG exit_to ENABLE_NODE (its parent)
* * all nodes < NODE_CONFIG exit_to close
*
* Default end_to:
@@ -488,8 +497,10 @@ install_node (struct cmd_node *node,
if (node->parent == NULL_NODE)
{
- if (node->node >= CONFIG_NODE)
+ if (node->node > CONFIG_NODE)
node->parent = CONFIG_NODE ;
+ else if (node->node == CONFIG_NODE)
+ node->parent = ENABLE_NODE ;
else
node->parent = node->node ;
} ;
@@ -504,10 +515,8 @@ install_node (struct cmd_node *node,
if (node->exit_to == NULL_NODE)
{
- if (node->node > CONFIG_NODE)
+ if (node->node >= CONFIG_NODE)
node->exit_to = node->parent ;
- else if (node->node == CONFIG_NODE)
- node->exit_to = ENABLE_NODE ;
else
node->exit_to = NULL_NODE ;
} ;
@@ -553,7 +562,7 @@ cmd_node_exit_to(node_type_t node)
} ;
/*------------------------------------------------------------------------------
- * Return parent node
+ * Return end_to node
*/
extern node_type_t
cmd_node_end_to(node_type_t node)
@@ -561,8 +570,6 @@ cmd_node_end_to(node_type_t node)
return (cmd_cmd_node(node))->end_to ;
} ;
-
-
/*------------------------------------------------------------------------------
* Sorting of all node cmd_vectors.
*/
@@ -582,15 +589,15 @@ sort_node ()
for (i = 0; i < vector_length(node_vector); i++)
{
- struct cmd_node *cnode;
+ struct cmd_node *cn;
vector cmd_vector ;
- cnode = vector_get_item(node_vector, i) ;
+ cn = vector_get_item(node_vector, i) ;
- if (cnode == NULL)
+ if (cn == NULL)
continue ;
- cmd_vector = cnode->cmd_vector;
+ cmd_vector = cn->cmd_vector;
if (cmd_vector == NULL)
continue ;
@@ -609,7 +616,7 @@ extern vector
cmd_make_strvec (const char *string)
{
#if 0
- return cmd_tokenise(NULL, string) ;
+ return cmd_tokenize(NULL, string) ;
#error sort this one out
#endif
return NULL ;
@@ -654,22 +661,22 @@ cmd_free_strvec (vector items)
extern const char *
cmd_prompt(node_type_t node)
{
- struct cmd_node *cnode;
+ struct cmd_node *cn ;
assert(node_vector != NULL) ;
assert(node_vector->p_items != NULL) ;
- cnode = NULL ;
+ cn = NULL ;
if (node < node_vector->limit)
- cnode = vector_get_item (node_vector, node);
+ cn = vector_get_item (node_vector, node);
- if (cnode == NULL)
+ if (cn == NULL)
{
zlog_err("Could not find prompt for node %d for", node) ;
return NULL ;
} ;
- return cnode->prompt;
+ return cn->prompt;
}
/*------------------------------------------------------------------------------
@@ -679,18 +686,18 @@ cmd_prompt(node_type_t node)
extern void
install_element(node_type_t ntype, cmd_command cmd)
{
- cmd_node cnode;
+ cmd_node cn ;
- cnode = vector_get_item (node_vector, ntype);
+ cn = vector_get_item (node_vector, ntype);
- if (cnode == NULL)
+ if (cn == NULL)
{
fprintf (stderr, "Command node %d doesn't exist, please check it\n",
ntype);
exit (1);
}
- vector_set (cnode->cmd_vector, cmd);
+ vector_set (cn->cmd_vector, cmd);
/* A cmd_command may appear in a number of cmd_vectors, but the cmd->items
* etc. need only be set up once.
@@ -736,350 +743,454 @@ zencrypt (const char *passwd)
/* This function write configuration of this host. */
static int
-config_write_host (struct vty *vty)
+config_write_host (vty vty)
{
+ vty_io vio ;
+
VTY_LOCK() ;
+ vio = vty->vio ;
+
if (qpthreads_enabled)
- vty_out (vty, "threaded%s", VTY_NEWLINE);
+ uty_out (vio, "threaded\n");
if (host.name_set)
- vty_out (vty, "hostname %s%s", host.name, VTY_NEWLINE);
+ uty_out (vio, "hostname %s\n", host.name);
if (host.password != NULL)
{
if (host.password_encrypted)
- vty_out (vty, "password 8 %s\n", host.password);
+ uty_out (vio, "password 8 %s\n", host.password);
else
- vty_out (vty, "password %s\n", host.password);
+ uty_out (vio, "password %s\n", host.password);
} ;
if (host.enable != NULL)
{
if (host.enable_encrypted)
- vty_out (vty, "enable password 8 %s\n", host.enable);
+ uty_out (vio, "enable password 8 %s\n", host.enable);
else
- vty_out (vty, "enable password %s\n", host.enable);
+ uty_out (vio, "enable password %s\n", host.enable);
} ;
if (zlog_get_default_lvl(NULL) != LOG_DEBUG)
{
- vty_out (vty, "! N.B. The 'log trap' command is deprecated.%s",
- VTY_NEWLINE);
- vty_out (vty, "log trap %s%s",
- zlog_priority[zlog_get_default_lvl(NULL)], VTY_NEWLINE);
+ uty_out (vio, "! N.B. The 'log trap' command is deprecated.\n");
+ uty_out (vio, "log trap %s\n", zlog_priority[zlog_get_default_lvl(NULL)]);
}
if (host.logfile && (zlog_get_maxlvl(NULL, ZLOG_DEST_FILE) != ZLOG_DISABLED))
{
- vty_out (vty, "log file %s", host.logfile);
+ uty_out (vio, "log file %s", qpath_string(host.logfile));
if (zlog_get_maxlvl(NULL, ZLOG_DEST_FILE) != zlog_get_default_lvl(NULL))
- vty_out (vty, " %s",
+ uty_out (vio, " %s",
zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_FILE)]);
- vty_out (vty, "%s", VTY_NEWLINE);
+ uty_out (vio, "\n");
}
if (zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT) != ZLOG_DISABLED)
{
- vty_out (vty, "log stdout");
+ uty_out (vio, "log stdout");
if (zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT) != zlog_get_default_lvl(NULL))
- vty_out (vty, " %s",
+ uty_out (vio, " %s",
zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT)]);
- vty_out (vty, "%s", VTY_NEWLINE);
+ uty_out (vio, "\n");
}
if (zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR) == ZLOG_DISABLED)
- vty_out(vty,"no log monitor%s",VTY_NEWLINE);
+ uty_out(vio,"no log monitor%s",VTY_NEWLINE);
else if (zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR) != zlog_get_default_lvl(NULL))
- vty_out(vty,"log monitor %s%s",
- zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR)],VTY_NEWLINE);
+ uty_out(vio,"log monitor %s\n",
+ zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR)]);
if (zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG) != ZLOG_DISABLED)
{
- vty_out (vty, "log syslog");
+ uty_out (vio, "log syslog");
if (zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG) != zlog_get_default_lvl(NULL))
- vty_out (vty, " %s",
+ uty_out (vio, " %s",
zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG)]);
- vty_out (vty, "%s", VTY_NEWLINE);
+ uty_out (vio, "\n");
}
if (zlog_get_facility(NULL) != LOG_DAEMON)
- vty_out (vty, "log facility %s%s",
- facility_name(zlog_get_facility(NULL)), VTY_NEWLINE);
+ uty_out (vio, "log facility %s\n", facility_name(zlog_get_facility(NULL)));
if (zlog_get_record_priority(NULL) == 1)
- vty_out (vty, "log record-priority%s", VTY_NEWLINE);
+ uty_out (vio, "log record-priority\n");
if (zlog_get_timestamp_precision(NULL) > 0)
- vty_out (vty, "log timestamp precision %d%s",
- zlog_get_timestamp_precision(NULL), VTY_NEWLINE);
+ uty_out (vio, "log timestamp precision %d\n",
+ zlog_get_timestamp_precision(NULL));
if (host.advanced)
- vty_out (vty, "service advanced-vty%s", VTY_NEWLINE);
+ uty_out (vio, "service advanced-vty\n");
if (host.encrypt)
- vty_out (vty, "service password-encryption%s", VTY_NEWLINE);
+ uty_out (vio, "service password-encryption\n");
if (host.lines >= 0)
- vty_out (vty, "service terminal-length %d%s", host.lines,
- VTY_NEWLINE);
+ uty_out (vio, "service terminal-length %d\n", host.lines);
if (host.motdfile)
- vty_out (vty, "banner motd file %s%s", host.motdfile, VTY_NEWLINE);
+ uty_out (vio, "banner motd file %s\n", qpath_string(host.motdfile));
else if (! host.motd)
- vty_out (vty, "no banner motd%s", VTY_NEWLINE);
+ uty_out (vio, "no banner motd\n");
VTY_UNLOCK() ;
return 1;
}
-/*============================================================================*/
-
-/*----------------------------------------------------------------------------*/
+/*==============================================================================
+ * Commands and other stuff related to:
+ *
+ * * end (and ^Z) -- go to ENABLE_NODE (aka Privileged Exec) if above that,
+ * otherwise do nothing.
+ *
+ * This is installed in all nodes.
+ *
+ * * exit -- go to parent node, if in CONFIG_NODE or any of its
+ * sub-nodes.
+ *
+ * Parent of CONFIG_NODE is ENABLE_NODE.
+ *
+ * For all other nodes, this is EOF (which for the
+ * terminal is "close").
+ *
+ * This is installed in all nodes.
+ *
+ * * enable -- go to ENABLE_NODE, if can.
+ *
+ * This is installed in VIEW_NODE and RESTRICTED_NODE.
+ *
+ * It is also installed in ENABLE_NODE (and hence is
+ * available anywhere), where it is a synonym for 'end' !
+ *
+ * For configuration reading (VTY_CONFIG_READ) and for
+ * VTY_SHELL_SERVER, no password is required.
+ *
+ * For VTY_TERMINAL, must already have authenticated
+ * once, or must be able to enter AUTH_ENABLE_NODE.
+ *
+ * * disable -- go to VIEW_NODE (aka User Exec).
+ *
+ * This is installed in ENABLE_NODE *only*.
+ *
+ * Note, however, that all ENABLE_NODE commands are
+ * available at ENABLE_NODE and above !
+ */
-/* Configration from terminal */
-DEFUN_CALL (config_terminal,
- config_terminal_cmd,
- "configure terminal",
- "Configuration from vty interface\n"
- "Configuration terminal\n")
-{
- if (vty_config_lock (vty, CONFIG_NODE))
- return CMD_SUCCESS;
+/*------------------------------------------------------------------------------
+ * Enter CONFIG_NODE, possibly via password check.
+ *
+ * If the parser established that can enter CONFIG_NODE directly, that's what
+ * happens.
+ *
+ * If the parser established that must authenticate, then may fail here if
+ * we are not in the right state to run the authentication.
+ *
+ * The authentication itself may fail...
+ *
+ * NB: installed in VIEW_NODE, RESTRICTED_NODE and ENABLE_NODE.
+ */
+DEFUN_ATTR (config_terminal,
+ config_terminal_cmd,
+ "configure terminal",
+ "Configuration from vty interface\n"
+ "Configuration terminal\n",
+ CMD_ATTR_DIRECT + cmd_sp_configure)
+{
+ if (vty->exec->parsed->nnode == CONFIG_NODE)
+ return vty_cmd_config_lock(vty) ; ;
+
+ /* Otherwise, must authenticate to enter CONFIG_NODE. */
+ return vty_cmd_can_auth_enable(vty) ;
+} ;
- vty_out (vty, "VTY configuration is locked by other VTY%s", VTY_NEWLINE);
- return CMD_WARNING;
-}
+ALIAS_ATTR (config_terminal,
+ config_enable_configure_cmd,
+ "enable configure",
+ "Turn on privileged mode command\n"
+ "Configuration terminal\n",
+ CMD_ATTR_DIRECT + cmd_sp_configure)
-/* Enable command */
-DEFUN_CALL (enable,
- config_enable_cmd,
- "enable",
- "Turn on privileged mode command\n")
+/*------------------------------------------------------------------------------
+ * Enter ENABLE_NODE, possibly via password check.
+ *
+ * If the parser established that can enter ENABLE_NODE directly, that's what
+ * happens.
+ *
+ * If the parser established that must authenticate, then may fail here if
+ * we are not in the right state to run the authentication.
+ *
+ * The authentication itself may fail...
+ *
+ * NB: installed in VIEW_NODE, RESTRICTED_NODE and ENABLE_NODE.
+ */
+DEFUN_ATTR (enable,
+ config_enable_cmd,
+ "enable",
+ "Turn on privileged mode command\n",
+ CMD_ATTR_DIRECT + cmd_sp_enable)
{
- /* If enable password is NULL, change to ENABLE_NODE */
- if ((host.enable == NULL) || (vty->type == VTY_SHELL_SERVER))
- vty->node = ENABLE_NODE ;
- else
- vty->node = AUTH_ENABLE_NODE ;
+ if (vty->exec->parsed->nnode == ENABLE_NODE)
+ return CMD_SUCCESS ;
- return CMD_SUCCESS;
-}
+ /* Otherwise, must authenticate to enter ENABLE_NODE. */
+ return vty_cmd_can_auth_enable(vty) ;
+} ;
-/* Disable command */
-DEFUN_CALL (disable,
- config_disable_cmd,
- "disable",
- "Turn off privileged mode command\n")
+/*------------------------------------------------------------------------------
+ * disable command: end enabled state -> VIEW_NODE.
+ *
+ * NB: although only installed in ENABLE_NODE, it will be implicitly available
+ * in all higher nodes -- as a quick way of crashing out to VIEW_NODE !
+ */
+DEFUN_ATTR(disable,
+ config_disable_cmd,
+ "disable",
+ "Turn off privileged mode command\n",
+ CMD_ATTR_DIRECT + CMD_ATTR_NODE + VIEW_NODE)
{
- if (vty->node == ENABLE_NODE)
- vty->node = VIEW_NODE;
- return CMD_SUCCESS;
+ return CMD_SUCCESS ; /* will disable to parsed->nnode */
}
-/* Down vty node level. */
-DEFUN_CALL (config_exit,
- config_exit_cmd,
- "exit",
- "Exit current mode and down to previous mode\n")
+/*------------------------------------------------------------------------------
+ * exit command: down one node level, including exit command processor.
+ */
+DEFUN_ATTR(config_exit,
+ config_exit_cmd,
+ "exit",
+ "Exit current mode and down to previous mode\n",
+ CMD_ATTR_DIRECT + cmd_sp_exit)
{
- return cmd_exit(vty) ;
+ return CMD_SUCCESS ; /* will exit to parsed->nnode */
}
-/* quit is alias of exit. */
-ALIAS_CALL (config_exit,
- config_quit_cmd,
- "quit",
- "Exit current mode and down to previous mode\n")
+/* quit is alias of exit. */
+ALIAS_ATTR (config_exit,
+ config_quit_cmd,
+ "quit",
+ "Exit current mode and down to previous mode\n",
+ CMD_ATTR_DIRECT + cmd_sp_exit) ;
-/* End of configuration. */
-DEFUN_CALL (config_end,
- config_end_cmd,
- "end",
- "End current mode and change to enable mode\n")
+/*------------------------------------------------------------------------------
+ * end command: down to enable mode.
+ */
+DEFUN_ATTR (config_end,
+ config_end_cmd,
+ "end",
+ "End current mode and change to enable mode\n",
+ CMD_ATTR_DIRECT + cmd_sp_end)
{
- return cmd_end(vty) ;
+ return CMD_SUCCESS ; /* will end to parsed->nnode */
}
-/* Show version. */
+/* Show version. */
DEFUN_CALL (show_version,
- show_version_cmd,
- "show version",
- SHOW_STR
- "Displays zebra version\n")
+ show_version_cmd,
+ "show version",
+ SHOW_STR
+ "Displays zebra version\n")
{
- vty_out (vty, "Quagga %s (%s).%s", QUAGGA_VERSION, host.name?host.name:"",
- VTY_NEWLINE);
- vty_out (vty, "%s%s", QUAGGA_COPYRIGHT, VTY_NEWLINE);
+ VTY_LOCK() ;
+
+ uty_out (vty->vio, "Quagga %s (%s).\n", QUAGGA_VERSION,
+ (host.name != NULL) ? host.name : "") ;
+ uty_out (vty->vio, "%s\n", QUAGGA_COPYRIGHT);
+
+ VTY_UNLOCK() ;
return CMD_SUCCESS;
}
-/* Help display function for all node. */
+/* Help display function for all node. */
DEFUN_CALL (config_help,
config_help_cmd,
"help",
"Description of the interactive help system\n")
{
vty_out (vty,
- "Quagga VTY provides advanced help feature. When you need help,%s\
-anytime at the command line please press '?'.%s\
-%s\
-If nothing matches, the help list will be empty and you must backup%s\
- until entering a '?' shows the available options.%s\
-Two styles of help are provided:%s\
-1. Full help is available when you are ready to enter a%s\
-command argument (e.g. 'show ?') and describes each possible%s\
-argument.%s\
-2. Partial help is provided when an abbreviated argument is entered%s\
- and you want to know what arguments match the input%s\
- (e.g. 'show me?'.)%s%s", VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE,
- VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE,
- VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+ "Quagga VTY provides advanced help feature. When you need help,\n"
+ "anytime at the command line please press '?'.\n"
+ "\n"
+ "If nothing matches, the help list will be empty and you must backup\n"
+ "until entering a '?' shows the available options.\n"
+ "Two styles of help are provided:\n"
+ " 1. Full help is available when you are ready to enter a\n"
+ " command argument (e.g. 'show ?') and describes each possible\n"
+ " argument.\n"
+ " 2. Partial help is provided when an abbreviated argument is entered\n"
+ " and you want to know what arguments match the input\n"
+ " (e.g. 'show me?'.)\n"
+ "\n") ;
return CMD_SUCCESS;
}
-/* Help display function for all node. */
+/* Help display function for all node. */
DEFUN_CALL (config_list,
- config_list_cmd,
- "list",
- "Print command list\n")
+ config_list_cmd,
+ "list",
+ "Print command list\n")
{
unsigned int i;
- struct cmd_node *cnode = vector_get_item (node_vector, vty->node);
+ struct cmd_node *cn ;
struct cmd_command *cmd;
- for (i = 0; i < vector_length (cnode->cmd_vector); i++)
- if ((cmd = vector_get_item (cnode->cmd_vector, i)) != NULL
- && !(cmd->attr & (CMD_ATTR_DEPRECATED | CMD_ATTR_HIDDEN)))
- vty_out (vty, " %s%s", cmd->string,
- VTY_NEWLINE);
+ cn = vector_get_item (node_vector, vty->node);
+
+ for (i = 0; i < vector_length (cn->cmd_vector); i++)
+ if ( ((cmd = vector_get_item (cn->cmd_vector, i)) != NULL)
+ && ((cmd->attr & (CMD_ATTR_DEPRECATED | CMD_ATTR_HIDDEN)) == 0) )
+ vty_out (vty, " %s\n", cmd->string);
+
return CMD_SUCCESS;
}
-/* Write current configuration into file. */
+/* Write current configuration into file. */
DEFUN (config_write_file,
config_write_file_cmd,
"write file",
"Write running configuration to memory, network, or terminal\n"
"Write to configuration file\n")
{
- unsigned int i;
- int fd;
- int err;
- struct cmd_node *node;
- char *config_file;
- char *config_file_tmp = NULL;
- char *config_file_sav = NULL;
- int ret = CMD_WARNING;
-
- /* Check and see if we are operating under vtysh configuration */
- if (host.config_file == NULL)
+ qpath path ;
+ qpath temp ;
+ qpath save ;
+ cmd_return_code_t ret, retw ;
+ unsigned int i ;
+ int fd, err ;
+ struct cmd_node *cn;
+ const char *config_name ;
+ const char *save_name ;
+ char* temp_name ;
+
+ err = 0 ; /* so far, so good */
+
+ VTY_LOCK() ;
+ path = (host.config_file != NULL) ? qpath_dup(host.config_file) : NULL ;
+ VTY_UNLOCK() ;
+
+ /* Check and see if we are operating under vtysh configuration */
+ if (path == NULL)
{
- vty_out (vty, "Can't save to configuration file, using vtysh.%s",
- VTY_NEWLINE);
+ vty_out (vty, "%% Cannot save to configuration file, using vtysh.\n");
return CMD_WARNING;
}
- /* Get filename. */
- config_file = host.config_file;
-
- config_file_sav =
- XMALLOC (MTYPE_TMP, strlen (config_file) + strlen (CONF_BACKUP_EXT) + 1);
- strcpy (config_file_sav, config_file);
- strcat (config_file_sav, CONF_BACKUP_EXT);
+ /* Set up the file names. */
+ config_name = qpath_string(path) ;
+ save = qpath_dup(path) ;
+ qpath_extend_str(save, CONF_BACKUP_EXT) ;
+ save_name = qpath_string(save) ;
- config_file_tmp = XMALLOC (MTYPE_TMP, strlen (config_file) + 8);
- sprintf (config_file_tmp, "%s.XXXXXX", config_file);
+ temp = qpath_dup(path) ;
+ qpath_extend_str(temp, ".XXXXXX") ;
+ temp_name = qpath_char_string(temp) ;
- /* Open file to configuration write. */
- fd = mkstemp (config_file_tmp);
+ /* Open file to configuration write. */
+ fd = mkstemp (temp_name);
if (fd < 0)
{
- vty_out (vty, "Can't open configuration file %s.\n", config_file_tmp) ;
+ err = errno ;
+ vty_out (vty, "%% Can't open configuration file %s", temp_name) ;
goto finished;
}
- /* Make vty for configuration file. */
+ /* Make vty for configuration file. */
vty_open_config_write(vty, fd) ;
- /* Config file header print. */
vty_out (vty, "!\n! Zebra configuration saved from vty\n! ");
vty_time_print (vty, 1);
vty_out (vty, "!\n");
+ retw = CMD_SUCCESS ;
+
for (i = 0; i < vector_length (node_vector); i++)
{
- if ((node = vector_get_item (node_vector, i)) && node->config_write)
+ if ((cn = vector_get_item (node_vector, i)) && cn->config_write)
{
- if ((*node->config_write) (vty))
+ if ((*cn->config_write) (vty))
vty_out (vty, "!\n");
- vty_cmd_out_push(vty) ; /* Push stuff so far */
- }
+ retw = vty_cmd_out_push(vty) ; /* Push stuff so far */
+
+ if (retw != CMD_SUCCESS)
+ break ;
+ } ;
} ;
- err = vty_close_config_write(vty) ;
+ ret = vty_close_config_write(vty, (retw != CMD_SUCCESS)) ;
- if (err != 0)
+ if ((ret != CMD_SUCCESS) || (retw != CMD_SUCCESS))
{
- vty_out (vty, "Failed while writing configuration file %s.%s",
- config_file_tmp, VTY_NEWLINE);
+ vty_out (vty, "%% Failed while writing configuration file %s.\n",
+ temp_name) ;
goto finished;
}
- if (unlink (config_file_sav) != 0)
+ /* Now move files around to make .sav and the real file */
+ ret = CMD_WARNING ;
+
+ if (unlink (save_name) != 0)
if (errno != ENOENT)
{
- vty_out (vty, "Can't unlink backup configuration file %s.%s",
- config_file_sav, VTY_NEWLINE);
+ err = errno ;
+ vty_out (vty, "%% Can't unlink backup configuration file %s",
+ save_name) ;
goto finished;
} ;
- if (link (config_file, config_file_sav) != 0)
+ if (link (config_name, save_name) != 0)
{
- vty_out (vty, "Can't backup old configuration file %s.%s",
- config_file_sav, VTY_NEWLINE);
+ err = errno ;
+ vty_out (vty, "%% Can't backup old configuration file %s", config_name) ;
goto finished;
} ;
sync () ;
- if (unlink (config_file) != 0)
+ if (unlink (config_name) != 0)
{
- vty_out (vty, "Can't unlink configuration file %s.%s",
- config_file, VTY_NEWLINE);
+ err = errno ;
+ vty_out (vty, "%% Can't unlink configuration file %s", config_name);
goto finished;
} ;
- if (link (config_file_tmp, config_file) != 0)
+ if (link (temp_name, config_name) != 0)
{
- vty_out (vty, "Can't save configuration file %s.%s",
- config_file, VTY_NEWLINE);
+ err = errno ;
+ vty_out (vty, "%% Can't save configuration file %s", config_name);
goto finished;
} ;
sync ();
- if (chmod (config_file, CONFIGFILE_MASK) != 0)
+ if (chmod (config_name, CONFIGFILE_MASK) != 0)
{
- vty_out (vty, "Can't chmod configuration file %s: %s (%s).\n",
- config_file, errtostr(errno, 0).str, errtoname(errno, 0).str);
+ err = errno ;
+ vty_out (vty, "%% Can't chmod configuration file %s", config_name) ;
goto finished;
}
- vty_out (vty, "Configuration saved to %s\n", config_file);
+ vty_out (vty, "Configuration saved to %s\n", config_name) ;
ret = CMD_SUCCESS;
finished:
- unlink (config_file_tmp);
- XFREE (MTYPE_TMP, config_file_tmp);
- XFREE (MTYPE_TMP, config_file_sav);
+ if (err != 0)
+ vty_out(vty, ": %s (%s).\n", errtostr(errno, 0).str,
+ errtoname(errno, 0).str) ;
+ if (fd >= 0)
+ unlink (temp_name);
+
+ qpath_free(temp) ;
+ qpath_free(save) ;
+ qpath_free(path) ;
+
return ret;
-}
+} ;
ALIAS (config_write_file,
config_write_cmd,
@@ -1099,7 +1210,7 @@ ALIAS (config_write_file,
"Copy running config to... \n"
"Copy running config to startup config (same as write file)\n")
-/* Write current configuration into the terminal. */
+/* Write current configuration into the terminal. */
DEFUN (config_write_terminal,
config_write_terminal_cmd,
"write terminal",
@@ -1138,48 +1249,40 @@ DEFUN (config_write_terminal,
return CMD_SUCCESS;
}
-/* Write current configuration into the terminal. */
+/* Write current configuration into the terminal. */
ALIAS (config_write_terminal,
show_running_config_cmd,
"show running-config",
SHOW_STR
"running configuration\n")
-/* Write startup configuration into the terminal. */
+/* Write startup configuration into the terminal. */
DEFUN (show_startup_config,
show_startup_config_cmd,
"show startup-config",
SHOW_STR
- "Contentes of startup configuration\n")
+ "Contents of startup configuration\n")
{
- char buf[BUFSIZ];
- FILE *confp;
+ cmd_return_code_t ret ;
+ qpath path ;
- confp = fopen (host.config_file, "r");
- if (confp == NULL)
- {
- vty_out (vty, "Can't open configuration file [%s]%s",
- host.config_file, VTY_NEWLINE);
- return CMD_WARNING;
- }
+ VTY_LOCK() ;
+ path = (host.config_file != NULL) ? qpath_dup(host.config_file) : NULL ;
+ VTY_UNLOCK() ;
- while (fgets (buf, BUFSIZ, confp))
+ if (path == NULL)
{
- char *cp = buf;
-
- while (*cp != '\r' && *cp != '\n' && *cp != '\0')
- cp++;
- *cp = '\0';
-
- vty_out (vty, "%s%s", buf, VTY_NEWLINE);
- }
+ vty_out (vty, "%% Cannot show configuration file, using vtysh.\n");
+ return CMD_WARNING;
+ } ;
- fclose (confp);
+ ret = vty_cat_file(vty, path, "configuration file") ;
+ qpath_free(path) ;
- return CMD_SUCCESS;
+ return ret ;
}
-/* Hostname configuration */
+/* Hostname configuration */
DEFUN_CALL (config_hostname,
hostname_cmd,
"hostname WORD",
@@ -1453,6 +1556,10 @@ DEFUN_HID_CALL (do_echo,
return CMD_SUCCESS;
}
+/*==============================================================================
+ * Logging configuration.
+ */
+
DEFUN_CALL (config_logmsg,
config_logmsg_cmd,
"logmsg "LOG_LEVELS" .MESSAGE",
@@ -1601,54 +1708,34 @@ DEFUN_CALL (no_config_log_monitor,
return CMD_SUCCESS;
}
+/*------------------------------------------------------------------------------
+ * Set new logging file and level -- "log file FILENAME [LEVEL]"
+ *
+ * Note that even if fail to open the new log file, will set host.logfile.
+ *
+ * Failure here is an error.
+ */
static int
set_log_file(struct vty *vty, const char *fname, int loglevel)
{
- int ret;
- char *p = NULL;
- const char *fullpath;
-
- /* Path detection. */
- if (! IS_DIRECTORY_SEP (*fname))
- {
- char cwd[MAXPATHLEN+1];
- cwd[MAXPATHLEN] = '\0';
-
- if (getcwd (cwd, MAXPATHLEN) == NULL)
- {
- zlog_err ("config_log_file: Unable to alloc mem!");
- return CMD_WARNING;
- }
+ int err ;
- if ( (p = XMALLOC (MTYPE_TMP, strlen (cwd) + strlen (fname) + 2))
- == NULL)
- {
- zlog_err ("config_log_file: Unable to alloc mem!");
- return CMD_WARNING;
- }
- sprintf (p, "%s/%s", cwd, fname);
- fullpath = p;
- }
- else
- fullpath = fname;
-
- ret = zlog_set_file (NULL, fullpath, loglevel);
+ VTY_LOCK() ;
- if (p)
- XFREE (MTYPE_TMP, p);
+ host.logfile = uty_cmd_path_name_complete(host.logfile, fname,
+ vty->exec->context) ;
+ err = zlog_set_file (NULL, qpath_string(host.logfile), loglevel) ;
- if (!ret)
- {
- vty_out (vty, "can't open logfile %s\n", fname);
- return CMD_WARNING;
- }
-
- if (host.logfile)
- XFREE (MTYPE_HOST, host.logfile);
+ VTY_UNLOCK() ;
- host.logfile = XSTRDUP (MTYPE_HOST, fname);
+ if (err == 0)
+ return CMD_SUCCESS ;
- return CMD_SUCCESS;
+ vty_out(vty, "%% failed to open log file %s: %s (%s)\n",
+ qpath_string(host.logfile),
+ errtostr(err, 0).str,
+ errtoname(err, 0).str) ;
+ return CMD_WARNING ;
}
DEFUN_CALL (config_log_file,
@@ -1663,7 +1750,7 @@ DEFUN_CALL (config_log_file,
DEFUN_CALL (config_log_file_level,
config_log_file_level_cmd,
- "log file FILENAME "LOG_LEVELS,
+ "log file FILENAME " LOG_LEVELS,
"Logging control\n"
"Logging to file\n"
"Logging filename\n"
@@ -1673,6 +1760,7 @@ DEFUN_CALL (config_log_file_level,
if ((level = level_match(argv[1])) == ZLOG_DISABLED)
return CMD_ERR_NO_MATCH;
+
return set_log_file(vty, argv[0], level);
}
@@ -1684,13 +1772,13 @@ DEFUN_CALL (no_config_log_file,
"Cancel logging to file\n"
"Logging file name\n")
{
- zlog_reset_file (NULL);
+ VTY_LOCK() ;
- if (host.logfile)
- XFREE (MTYPE_HOST, host.logfile);
+ zlog_reset_file (NULL);
- host.logfile = NULL;
+ host.logfile = qpath_free(host.logfile) ;
+ VTY_UNLOCK() ;
return CMD_SUCCESS;
}
@@ -1878,21 +1966,43 @@ DEFUN_CALL (no_config_log_timestamp_precision,
return CMD_SUCCESS;
}
+/*==============================================================================
+ * MOTD commands and set up.
+ *
+ * Note that can set a MOTD file that does not exist at the time. A friendly
+ * message warns about this, but it is not an error. The message will not be
+ * seen while reading the configuration file -- but it is not worth stopping
+ * the configuration file reader for this !
+ */
DEFUN_CALL (banner_motd_file,
banner_motd_file_cmd,
- "banner motd file [FILE]",
+ "banner motd file FILE",
"Set banner\n"
"Banner for motd\n"
"Banner from a file\n"
"Filename\n")
{
- if (host.motdfile)
- XFREE (MTYPE_HOST, host.motdfile);
+ int err ;
- host.motdfile = XSTRDUP (MTYPE_HOST, argv[0]);
+ VTY_LOCK() ;
- return CMD_SUCCESS;
-}
+ host.motdfile = uty_cmd_path_name_complete(host.motdfile,
+ argv[0], vty->exec->context) ;
+ err = qpath_stat_is_file(host.motdfile) ;
+
+ if (err != 0)
+ {
+ vty_out(vty, "NB: '%s': ", qpath_string(host.motdfile)) ;
+ if (err < 0)
+ vty_out(vty, "is not a file\n") ;
+ else
+ vty_out(vty, "%s (%s)\n", errtostr(err, 0).str,
+ errtoname(err, 0).str) ;
+ } ;
+
+ VTY_UNLOCK() ;
+ return CMD_SUCCESS ;
+} ;
DEFUN_CALL (banner_motd_default,
banner_motd_default_cmd,
@@ -1901,7 +2011,11 @@ DEFUN_CALL (banner_motd_default,
"Strings for motd\n"
"Default string\n")
{
- host.motd = DEFAULT_MOTD ;
+ VTY_LOCK() ;
+
+ host.motd = default_motd ;
+
+ VTY_UNLOCK() ;
return CMD_SUCCESS;
}
@@ -1912,23 +2026,114 @@ DEFUN_CALL (no_banner_motd,
"Set banner string\n"
"Strings for motd\n")
{
- host.motd = NULL;
- if (host.motdfile)
- XFREE (MTYPE_HOST, host.motdfile);
- host.motdfile = NULL;
+ VTY_LOCK() ;
+
+ host.motd = NULL ;
+ host.motdfile = qpath_free(host.motdfile) ;
+
+ VTY_UNLOCK() ;
return CMD_SUCCESS;
}
-/* Set config filename. Called from vty.c */
+/*==============================================================================
+ * Current directory handling
+ */
+DEFUN_CALL (do_chdir,
+ chdir_cmd,
+ "chdir DIR",
+ "Set current directory\n"
+ "Directory to set\n")
+{
+ cmd_return_code_t ret ;
+ qpath path ;
+ int err ;
+
+ ret = CMD_SUCCESS ;
+
+ path = uty_cmd_path_name_complete(NULL, argv[0], vty->exec->context) ;
+ err = qpath_stat_is_directory(path) ;
+
+ if (err == 0)
+ qpath_copy(vty->exec->context->dir_cd, path) ;
+ else
+ {
+ vty_out(vty, "%% chdir %s: ", qpath_string(path)) ;
+ if (err < 0)
+ vty_out(vty, "is not a directory\n") ;
+ else
+ vty_out(vty, "%s (%s)\n", errtostr(err, 0).str,
+ errtoname(err, 0).str) ;
+ ret = CMD_WARNING ;
+ } ;
+
+ qpath_free(path) ;
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get cwd.
+ *
+ * This is done very early in the morning, before lowering privileges, to
+ * minimise chance of not being able to get the cwd. If cwd itself is not
+ * accessible in lowered privilege state, that will later become clear.
+ *
+ * Sets host.cwd, which is torn down in cmd_terminate().
+ *
+ */
+extern void
+cmd_getcwd(void)
+{
+ host.cwd = qpath_getcwd(NULL) ;
+
+ if (host.cwd == NULL)
+ {
+ fprintf(stderr, "Cannot getcwd()\n") ;
+ exit(1) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set host.config_file and host.config_dir.
+ */
extern void
-host_config_set (const char* file_name)
+cmd_host_config_set (qpath config_file)
{
- if (host.config_file)
- XFREE (MTYPE_HOST, host.config_file);
- host.config_file = XSTRDUP (MTYPE_HOST, file_name);
+ VTY_LOCK() ;
+
+ host.config_file = qpath_copy(host.config_file, config_file) ;
+ host.config_dir = qpath_copy(host.config_dir, config_file) ;
+
+ qpath_shave(host.config_dir) ;
+
+ VTY_UNLOCK() ;
}
-void
+/*------------------------------------------------------------------------------
+ * Set the lexical level for further command processing.
+ */
+DEFUN_CALL (lexical_level,
+ lexical_level_cmd,
+ "lexical-level <0-1>",
+ "Set lexical level\n"
+ "The required lexical level\n")
+{
+ int level ;
+
+ level = strtol(argv[0], NULL, 0) ;
+
+ vty_cmd_set_full_lex(vty, (level != 0)) ;
+
+ return CMD_SUCCESS;
+}
+
+/*==============================================================================
+ * Command handling initialisation and termination.
+ */
+
+/*------------------------------------------------------------------------------
+ * Install copy of the default commands in the given node.
+ */
+extern void
install_default (enum node_type node)
{
install_element (node, &config_exit_cmd);
@@ -1944,13 +2149,28 @@ install_default (enum node_type node)
install_element (node, &show_running_config_cmd);
}
-/* Initialize command interface. Install basic nodes and commands. */
-void
-cmd_init (int terminal)
+/*------------------------------------------------------------------------------
+ * Initialise command handling.
+ *
+ * Install basic nodes and commands. Initialise the host structure.
+ *
+ * Sets srand(time(NULL))
+ */
+extern void
+cmd_init (bool terminal)
{
+ srand(time(NULL)) ;
+
+ if (host.cwd == NULL) /* in case cmd_cwd() not called, yet */
+ cmd_getcwd() ;
+
/* Allocate initial top vector of commands. */
node_vector = vector_init(0);
+ /* Set default motd */
+ host.motd = default_motd ;
+ host.config_brand = rand() ;
+
/* Default host value settings are already set, see above */
cmd_get_sys_host_name() ; /* start with system name & name_gen == 1 */
@@ -1972,30 +2192,41 @@ cmd_init (int terminal)
install_element (VIEW_NODE, &config_quit_cmd);
install_element (VIEW_NODE, &config_help_cmd);
install_element (VIEW_NODE, &config_enable_cmd);
+ install_element (VIEW_NODE, &config_enable_configure_cmd);
+ install_element (VIEW_NODE, &config_terminal_cmd);
install_element (VIEW_NODE, &config_terminal_length_cmd);
install_element (VIEW_NODE, &config_terminal_no_length_cmd);
install_element (VIEW_NODE, &show_logging_cmd);
install_element (VIEW_NODE, &echo_cmd);
+ install_element (VIEW_NODE, &chdir_cmd);
install_element (RESTRICTED_NODE, &config_list_cmd);
install_element (RESTRICTED_NODE, &config_exit_cmd);
install_element (RESTRICTED_NODE, &config_quit_cmd);
install_element (RESTRICTED_NODE, &config_help_cmd);
install_element (RESTRICTED_NODE, &config_enable_cmd);
+ install_element (RESTRICTED_NODE, &config_enable_configure_cmd);
+ install_element (RESTRICTED_NODE, &config_terminal_cmd);
install_element (RESTRICTED_NODE, &config_terminal_length_cmd);
install_element (RESTRICTED_NODE, &config_terminal_no_length_cmd);
install_element (RESTRICTED_NODE, &echo_cmd);
+ install_element (RESTRICTED_NODE, &chdir_cmd);
}
if (terminal)
{
install_default (ENABLE_NODE);
install_element (ENABLE_NODE, &config_disable_cmd);
+ install_element (ENABLE_NODE, &config_enable_cmd);
+ install_element (ENABLE_NODE, &config_enable_configure_cmd);
install_element (ENABLE_NODE, &config_terminal_cmd);
install_element (ENABLE_NODE, &copy_runningconfig_startupconfig_cmd);
}
+
install_element (ENABLE_NODE, &show_startup_config_cmd);
install_element (ENABLE_NODE, &show_version_cmd);
+ install_element (ENABLE_NODE, &lexical_level_cmd);
+ install_element (ENABLE_NODE, &chdir_cmd);
if (terminal)
{
@@ -2010,9 +2241,12 @@ cmd_init (int terminal)
install_element (CONFIG_NODE, &hostname_cmd);
install_element (CONFIG_NODE, &no_hostname_cmd);
+ install_element (CONFIG_NODE, &lexical_level_cmd);
if (terminal)
{
+ install_element (CONFIG_NODE, &echo_cmd);
+
install_element (CONFIG_NODE, &password_cmd);
install_element (CONFIG_NODE, &password_text_cmd);
install_element (CONFIG_NODE, &enable_password_cmd);
@@ -2050,27 +2284,38 @@ cmd_init (int terminal)
install_element (CONFIG_NODE, &service_terminal_length_cmd);
install_element (CONFIG_NODE, &no_service_terminal_length_cmd);
+ install_element (RESTRICTED_NODE, &show_thread_cpu_cmd);
install_element (VIEW_NODE, &show_thread_cpu_cmd);
install_element (ENABLE_NODE, &show_thread_cpu_cmd);
- install_element (RESTRICTED_NODE, &show_thread_cpu_cmd);
install_element (VIEW_NODE, &show_work_queues_cmd);
install_element (ENABLE_NODE, &show_work_queues_cmd);
- }
- srand(time(NULL));
-}
+ } ;
+} ;
+/*------------------------------------------------------------------------------
+ * Close down command interface.
+ *
+ * Dismantle the node_vector and all commands.
+ *
+ * Clear out the host structure.
+ */
void
cmd_terminate ()
{
cmd_node cmd_node;
cmd_command cmd ;
+ /* Ream out the vector of command nodes. */
while ((cmd_node = vector_ream(node_vector, free_it)) != NULL)
{
- while ((cmd = vector_ream(cmd_node->cmd_vector, free_it)) != NULL)
+ /* Ream out the (embedded) vector of commands per node. */
+ while ((cmd = vector_ream(cmd_node->cmd_vector, keep_it)) != NULL)
{
- /* Note that each cmd is a static structure, which may appear in
- * more than one cmd_vector.
+ /* Ream out the vector of items for each command.
+ *
+ * Note that each cmd is a static structure, which may appear in
+ * more than one cmd_vector -- but the "compiled" portions are
+ * dynamically allocated.
*/
cmd_item next_item ;
@@ -2098,10 +2343,11 @@ cmd_terminate ()
XFREE(MTYPE_HOST, host.name);
XFREE(MTYPE_HOST, host.password);
XFREE(MTYPE_HOST, host.enable);
- XFREE(MTYPE_HOST, host.logfile);
- XFREE(MTYPE_HOST, host.motdfile);
- XFREE(MTYPE_HOST, host.config_file);
+ host.logfile = qpath_free(host.logfile) ;
+ host.motdfile = qpath_free(host.motdfile) ;
+ host.config_file = qpath_free(host.config_file) ;
+ host.config_dir = qpath_free(host.config_dir) ;
XFREE(MTYPE_HOST, host.vty_accesslist_name);
XFREE(MTYPE_HOST, host.vty_ipv6_accesslist_name);
- XFREE(MTYPE_HOST, host.vty_cwd);
+ host.cwd = qpath_free(host.cwd) ;
} ;
diff --git a/lib/command.h b/lib/command.h
index 89644ce7..2b835cc8 100644
--- a/lib/command.h
+++ b/lib/command.h
@@ -204,7 +204,8 @@
#endif /* HAVE_IPV6 */
/* Prototypes. */
-extern void cmd_init (int);
+extern void cmd_getcwd(void) ;
+extern void cmd_init (bool);
extern void cmd_terminate (void);
extern void print_version (const char *);
@@ -231,11 +232,7 @@ extern struct cmd_command config_exit_cmd;
extern struct cmd_command config_quit_cmd;
extern struct cmd_command config_help_cmd;
extern struct cmd_command config_list_cmd;
-extern char *host_config_file (void);
-extern void host_config_set (const char *);
-#ifdef QDEBUG
extern const char *debug_banner ;
-#endif
#endif /* _ZEBRA_COMMAND_H */
diff --git a/lib/command_common.h b/lib/command_common.h
index 0f7c4123..26f2909f 100644
--- a/lib/command_common.h
+++ b/lib/command_common.h
@@ -38,7 +38,7 @@ enum node_type
AUTH_NODE, /* VTY login -> VIEW_NODE */
RESTRICTED_NODE, /* if no login required, may use this node */
VIEW_NODE, /* aka user EXEC */
- AUTH_ENABLE_NODE, /* enable login -> ENABLE_NODE */
+ AUTH_ENABLE_NODE, /* enable login -> ENABLE_NODE/CONFIG_NODE */
ENABLE_NODE, /* aka privileged EXEC */
MIN_DO_SHORTCUT_NODE = ENABLE_NODE,
@@ -86,6 +86,9 @@ enum node_type
FORWARDING_NODE, /* forwarding config write -- see zserv.c */
PROTOCOL_NODE, /* protocol config write -- see zebra_vty.c */
VTY_NODE, /* line vty commands */
+
+ MAX_PLUS_1_NODE,
+ MAX_NODE = MAX_PLUS_1_NODE - 1
} ;
typedef enum node_type node_type_t ;
@@ -110,30 +113,44 @@ typedef enum node_type node_type_t ;
*/
enum cmd_return_code
{
- CMD_SUCCESS = 0,
- CMD_WARNING = 1,
- CMD_ERROR,
+ CMD_SUCCESS = 0, /* used generally */
- CMD_IO_ERROR, /* I/O -- failed :-( */
+ /* Return codes suitable for command execution functions */
- CMD_SUCCESS_DAEMON, /* parser: success & command is for vtysh ? */
+ CMD_WARNING = 1,
+ CMD_ERROR,
- CMD_CLOSE, /* command: used by "exit" */
+ /* Return codes from the command parser */
CMD_EMPTY, /* parser: nothing to execute */
- CMD_WAITING, /* I/O: waiting for more input */
- CMD_EOF, /* I/O: nothing more to come */
CMD_ERR_PARSING, /* parser: general parser error */
CMD_ERR_NO_MATCH, /* parser: command/argument not recognised */
CMD_ERR_AMBIGUOUS, /* parser: more than on command matches */
CMD_ERR_INCOMPLETE,
+ CMD_CLOSE, /* command: used by "exit" */
+
+
+
+ CMD_WAITING, /* I/O: waiting for more input */
+ CMD_EOF, /* I/O: nothing more to come */
+
+ CMD_HIATUS, /* Something requires attention */
+
+ CMD_IO_ERROR, /* I/O -- failed :-( */
+ CMD_IO_TIMEOUT, /* I/O -- timed out :-( */
+
+ /* For the chop ???? */
+
CMD_COMPLETE_FULL_MATCH, /* cmd_completion returns */
CMD_COMPLETE_MATCH,
CMD_COMPLETE_LIST_MATCH,
- CMD_COMPLETE_ALREADY
+ CMD_COMPLETE_ALREADY,
+
+
+ CMD_SUCCESS_DAEMON, /* parser: success & command is for vtysh ? */
} ;
typedef enum cmd_return_code cmd_return_code_t ;
@@ -166,18 +183,66 @@ typedef struct cmd_node cmd_node_t ;
typedef struct cmd_node* cmd_node ;
/*------------------------------------------------------------------------------
- * Command elements -- contents of the node's cmd_vector
+ * Commands -- contents of the nodes' cmd_vector(s).
+ *
+ * A cmd_command is a static structure, which contains dynamic elements
+ * which are set when a command is installed. Note that is not uncommon for
+ * one cmd_command to appear in more than one node.
+ *
+ * The command attributes affect:
+ *
+ * * the parsing of the command -- in particular how the next_node is
+ * established.
+ *
+ * * whether the command is shown in help (or some forms of help if
+ * deprecated.
+ *
+ * * whether the command can be executed directly in the cli thread
+ * (avoiding having to wait for the cmd thread's attention -- this may be
+ * less useful now that all commands are treated a "priority" messages
+ * going into the cmd thread).
+ *
+ * If the command is marked CMD_ATTR_NODE, then the CMD_ATTR_MASK will
+ * extract the node_type_t that the command will set, if CMD_SUCCESS. This
+ * means that can parse commands without executing them.
+ *
+ * If the command is not marked CMD_ATTR_NODE, then the CMD_ATTR_MASK will
+ * extract the cmd_special_t value for the command -- which will be
+ * interesting if it isn't cmd_sp_simple.
*/
enum cmd_attr
{
- CMD_ATTR_SIMPLE = 0, /* bit significant */
- CMD_ATTR_DEPRECATED = BIT(0),
- CMD_ATTR_HIDDEN = BIT(1),
- CMD_ATTR_DIRECT = BIT(2),
+ CMD_ATTR_SIMPLE = 0, /* bit significant */
+
+ CMD_ATTR_NODE = BIT(7), /* sets given node */
+ CMD_ATTR_MASK = CMD_ATTR_NODE - 1,
+
+ CMD_ATTR_DEPRECATED = BIT(12),
+ CMD_ATTR_HIDDEN = BIT(13), /* not shown in help */
+ CMD_ATTR_DIRECT = BIT(14), /* can run in cli thread */
};
typedef enum cmd_attr cmd_attr_t ;
-/* Structure of command element. */
+CONFIRM(CMD_ATTR_MASK >= (cmd_attr_t)MAX_NODE) ;
+
+/* Special commands, which require extra processing at parse time. */
+enum cmd_special
+{
+ cmd_sp_simple = 0,
+
+ cmd_sp_end,
+ cmd_sp_exit,
+ cmd_sp_enable,
+ cmd_sp_configure,
+
+ cmd_sp_max_plus_1,
+ cmd_sp_max = cmd_sp_max_plus_1 - 1
+} ;
+typedef enum cmd_special cmd_special_t ;
+
+CONFIRM(CMD_ATTR_MASK >= (cmd_attr_t)cmd_sp_max) ;
+
+/* Command functions and macros to define same */
struct cmd_command ;
typedef struct cmd_command* cmd_command ;
@@ -193,6 +258,8 @@ typedef const char* const argv_t[] ;
typedef DEFUN_CMD_FUNCTION((cmd_function)) ;
+/* The cmd_command structure itself */
+
struct cmd_item ; /* Defined in command_parse.h */
struct cmd_command
diff --git a/lib/command_execute.c b/lib/command_execute.c
index cbf260b9..32698d94 100644
--- a/lib/command_execute.c
+++ b/lib/command_execute.c
@@ -40,135 +40,226 @@
/*------------------------------------------------------------------------------
* Construct cmd_exec object and initialise.
*
- * The following are set by uty_cmd_prepare() which is called after something
- * has been pushed/popped on the vin/vout stacks, and before any command
- * execution starts:
+ * This is done when a command loop is entered.
*
- * - parse_type
- * - reflect_enabled
- * - out_enabled
- *
- * to reflect the then current VTY state.
+ * The initial cmd_context reflects the vty->type and initial vty->node.
*/
extern cmd_exec
cmd_exec_new(vty vty)
{
- cmd_exec exec ;
+ cmd_exec exec ;
+ cmd_context context ;
exec = XCALLOC(MTYPE_CMD_EXEC, sizeof(struct cmd_exec)) ;
/* Zeroising has set:
*
- * vty = X -- set below
+ * vty = X -- set below
*
- * line = NULL -- no command line, yet
- * to_do = cmd_do_nothing
+ * action = all zeros
*
- * parse_type = cmd_parse_standard
+ * context = NULL -- see below
*
- * reflect_enabled = false -- not enabled
- * out_enabled = false -- not enabled
+ * parsed = NULL -- see below
*
- * parsed all zeros -- empty parsed object (embedded)
+ * password_failures = 0 -- none, yet
*
- * state = exec_null
- * locus = NULL -- not significant in exec_null
- * ret = CMD_SUCCESS
+ * state = exec_null
+ * locus = NULL -- not significant in exec_null
+ * ret = CMD_SUCCESS
*
- * cq = NULL -- no mqb (qpthreads)
- * -- no thread (legacy thread)
+ * cq = NULL -- no mqb (qpthreads)
+ * -- no thread (legacy thread)
*/
- confirm(cmd_do_nothing == 0) ;
- confirm(cmd_parse_standard == 0) ;
- confirm(CMD_PARSED_INIT_ALL_ZEROS) ;
- confirm(exec_null == 0) ;
- confirm(CMD_SUCCESS == 0) ;
+ confirm(CMD_ACTION_ALL_ZEROS) ; /* action */
+ confirm(exec_null == 0) ; /* state */
+ confirm(CMD_SUCCESS == 0) ; /* ret */
+
+ exec->vty = vty ;
+
+ exec->parsed = cmd_parsed_new() ;
+
+ /* Initialise the context */
+ VTY_ASSERT_LOCKED() ;
+
+ exec->context = context = cmd_context_new() ;
+
+ context->node = vty->node ;
+
+ context->parse_execution = true ;
+ context->parse_only = false ;
+ context->reflect_enabled = false ;
+
+ context->onode = NULL_NODE ;
+
+ context->dir_cd = qpath_dup(host.cwd) ;
+ context->dir_home = qpath_dup(host.config_dir) ;
+
+ switch (vty->type)
+ {
+ case VTY_TERMINAL:
+ context->full_lex = true ;
+
+ context->parse_strict = false ;
+ context->parse_no_do = false ;
+ context->parse_no_tree = false ;
+
+ context->can_enable = context->node >= ENABLE_NODE ;
+ context->can_auth_enable = true ;
+
+ context->dir_here = qpath_dup(context->dir_cd) ;
+
+ break ;
+
+ case VTY_SHELL_SERVER:
+ zabort("missing VTY_SHELL_SERVER context") ;
+
+ context->full_lex = true ;
+
+ context->parse_strict = false ;
+ context->parse_no_do = false ;
+ context->parse_no_tree = false ;
+
+ context->can_enable = true ;
+ context->can_auth_enable = false ;
- exec->vty = vty ;
+ context->dir_here = qpath_dup(context->dir_cd) ;
+
+ break ;
+
+ case VTY_CONFIG_READ:
+ context->full_lex = false ;
+
+ context->parse_strict = true ;
+ context->parse_no_do = true ;
+ context->parse_no_tree = false ;
+
+ context->can_enable = true ;
+ context->can_auth_enable = false ;
+
+ context->dir_here = qpath_dup(context->dir_home) ;
+
+ break ;
+
+ default:
+ zabort("vty type unknown to cmd_exec_new()") ;
+ } ;
return exec ;
} ;
/*------------------------------------------------------------------------------
- * Destroy cmd_exec object.
+ * Make a new context -- returns a completely empty context object.
*/
-extern cmd_exec
-cmd_exec_free(cmd_exec exec)
+extern cmd_context
+cmd_context_new(void)
{
- cmd_parsed_reset(exec->parsed, keep_it) ;
+ return XCALLOC(MTYPE_CMD_EXEC, sizeof(struct cmd_context)) ;
+} ;
+/*------------------------------------------------------------------------------
+ * Save current context and update for new, child vin.
+ *
+ * - updates the dir_here if required
+ *
+ * - cannot inherit can_enable unless is ENABLE_NODE or better
+ *
+ * - cannot inherit can_auth_enable, no how
+ */
+extern cmd_context
+cmd_context_new_save(cmd_context context, qpath file_here)
+{
+ cmd_context saved ;
+ saved = cmd_context_new() ;
- XFREE(MTYPE_CMD_EXEC, exec) ;
+ *saved = *context ; /* copy as is */
- return NULL ;
-} ;
+ /* The saved copy of the context now owns the current paths, so now need
+ * to duplicate (or set new) paths.
+ */
+ context->dir_cd = qpath_dup(saved->dir_cd) ;
+ context->dir_home = qpath_dup(saved->dir_home) ;
+
+ if (file_here == NULL)
+ context->dir_here = qpath_dup(saved->dir_here) ;
+ else
+ {
+ context->dir_here = qpath_dup(file_here) ;
+ qpath_shave(context->dir_here) ;
+ } ;
+ /* The inheritance of can_enable is tricky. Will not bequeath can_enable
+ * is is not already in ENABLE_NODE or better !
+ */
+ if (context->node <= ENABLE_NODE)
+ context->can_enable = false ;
-/*==============================================================================
- *
- */
+ context->can_auth_enable = false ;
+
+ return saved ;
+} ;
/*------------------------------------------------------------------------------
- * Set new node.
+ * Restore given context -- frees the copy restored from.
*
- * If old node >= CONFIG_NODE, and new node < CONFIG_NODE, give up the config
- * symbol of power.
+ * Has to free the directories in the context being restored to.
*
- * Returns: CMD_SUCCESS -- OK
- * CMD_CLOSE -- if new node == NODE_NULL
+ * Returns NULL.
*/
-static cmd_return_code_t
-cmd_set_node(vty vty, node_type_t node)
+extern cmd_context
+cmd_context_restore(cmd_context dst, cmd_context src)
{
- if ((vty->node >= MIN_CONFIG_NODE) && (node < MIN_CONFIG_NODE))
- vty_config_unlock(vty, node) ;
- else
- vty->node = node ;
+ assert(src != NULL) ;
+
+ qpath_free(dst->dir_cd) ;
+ qpath_free(dst->dir_home) ;
+ qpath_free(dst->dir_here) ;
+
+ *dst = *src ; /* copy as is */
- return (vty->node != NULL_NODE) ? CMD_SUCCESS : CMD_CLOSE ;
+ return cmd_context_free(src, true) ;
} ;
/*------------------------------------------------------------------------------
- * Command line "end" command
+ * Free the given context.
*
- * Falls back to the current node's end_to node. If leaves configuration
- * mode, give away the configuration symbol of power.
- *
- * Generally, for all configuration nodes end -> NODE_ENABLE (releasing the
- * configuration lock), and all other nodes end does nothing.
- *
- * Returns: CMD_SUCCESS -- OK
- * CMD_CLOSE -- if new node == NODE_NULL
+ * If the context is a copy of an existing context, then must not free the
+ * directories -- they will be freed when that existing context is freed.
*/
-extern cmd_return_code_t
-cmd_end(vty vty)
+extern cmd_context
+cmd_context_free(cmd_context context, bool copy)
{
- return cmd_set_node(vty, cmd_node_end_to(vty->node)) ;
+ if (context != NULL)
+ {
+ if (!copy)
+ {
+ qpath_free(context->dir_cd) ;
+ qpath_free(context->dir_home) ;
+ qpath_free(context->dir_here) ;
+ } ;
+
+ XFREE(MTYPE_CMD_EXEC, context) ; /* sets context = NULL */
+ } ;
+
+ return context ;
} ;
/*------------------------------------------------------------------------------
- * Command line "exit" command -- aka "quit"
- *
- * Falls back to the current node's exit_to node. If leaves configuration
- * mode, give away the configuration symbol of power.
- *
- * Generally:
- *
- * - for all configuration nodes > NODE_CONFIG exit -> parent node.
- *
- * - for NODE_CONFIG exit -> ENABLE_NODE (and release configuration symbol
- * of power)
- *
- * - for all nodes < NODE_CONFIG -> close the VTY
- *
- * Returns: CMD_SUCCESS -- OK
- * CMD_CLOSE -- if new node == NODE_NULL
+ * Destroy cmd_exec object.
*/
-extern cmd_return_code_t
-cmd_exit(vty vty)
+extern cmd_exec
+cmd_exec_free(cmd_exec exec)
{
- return cmd_set_node(vty, cmd_node_exit_to(vty->node)) ;
+ if (exec != NULL)
+ {
+ exec->parsed = cmd_parsed_free(exec->parsed) ;
+ exec->context = cmd_context_free(exec->context, false) ; /* not a copy */
+
+ XFREE(MTYPE_CMD_EXEC, exec) ;
+ } ;
+
+ return NULL ;
} ;
/*==============================================================================
@@ -236,16 +327,12 @@ cmd_execute_command(struct vty *vty,
* execution is converted to CMD_SUCCESS. Note that any CMD_WARNING returned
* by command parsing (or in execution of any default 'first_cmd').
*
- * Returns: cmd_return_code for last command
- * vty->buf is last line processed
- * vty->lineno is number of last line processed (1 is first)
- *
- * If the file is empty, will return CMD_SUCCESS.
+ * Returns: cmd_return_code for last command or I/O operation
*
- * If
+ * when returns, the entire vin/vout stack will have been closed.
*
- * If return code is not CMD_SUCCESS, the the output buffering contains the
- * output from the last command attempted.
+ * If reaches EOF on the config file, returns CMD_SUCCESS. If anything else
+ * happens, will generate an error message and exits the command loop.
*/
extern cmd_return_code_t
cmd_read_config(struct vty *vty, cmd_command first_cmd, bool ignore_warning)
@@ -254,89 +341,122 @@ cmd_read_config(struct vty *vty, cmd_command first_cmd, bool ignore_warning)
cmd_parsed parsed = exec->parsed ;
cmd_return_code_t ret;
+ ret = CMD_SUCCESS ; /* so far, so good */
+
while (1)
{
- /* Need a command line, pops pipes as required */
- ret = vty_cmd_fetch_line(vty) ; /* sets exec->line */
+ /* Deal with anything which is not success !
+ */
+ if ((ret != CMD_SUCCESS) && (ret != CMD_EMPTY))
+ {
+ /* Will drop straight out of the loop if have anything other
+ * than CMD_HIATUS, CMD_EOF or CMD_CLOSE, which are all signals
+ * that some adjustment to the vin/vout stacks is required,
+ * or that we are all done here.
+ *
+ * Everything else is deemed to be an error that stops the
+ * command loop.
+ */
+ if ((ret != CMD_HIATUS) && (ret != CMD_EOF) && (ret != CMD_CLOSE))
+ break ;
+
+ ret = vty_cmd_hiatus(vty, ret) ;
+ /* for CMD_EOF & CMD_HIATUS only */
+ if (ret == CMD_EOF)
+ return CMD_SUCCESS ; /* eof on the config file is
+ the expected outcome ! */
+ if (ret != CMD_SUCCESS)
+ break ;
+ } ;
+
+ /* If all is well, need another command line */
+
+ ret = vty_cmd_fetch_line(vty) ; /* sets exec->action */
if (ret != CMD_SUCCESS)
- break ; /* stop on any and all problems */
+ continue ;
/* Parse the command line we now have */
- cmd_tokenise(parsed, exec->line) ;
- ret = cmd_parse_command(parsed, vty->node,
- exec->parse_type | cmd_parse_execution) ;
+ assert(exec->action->to_do == cmd_do_command) ;
- if (ret == CMD_EMPTY)
- continue ; /* easy if empty line */
+ cmd_tokenize(parsed, exec->action->line, exec->context->full_lex) ;
+ ret = cmd_parse_command(parsed, exec->context) ;
if (ret != CMD_SUCCESS)
- break ; /* stop on *any* parsing issue */
+ continue ;
/* Special handling before first active line. */
if (first_cmd != NULL)
{
if (first_cmd != parsed->cmd)
{
- ret = (*first_cmd->func)(first_cmd, vty, 0, NULL) ;
+ ret = (*first_cmd->func)(first_cmd, vty, -1, NULL) ;
if (ret != CMD_SUCCESS)
- break ; /* stop on *any* issue with "default" */
+ continue ;
} ;
first_cmd = NULL ;
} ;
/* reflection now..... */
- if (exec->reflect_enabled)
- vty_cmd_reflect_line(vty) ;
+ if (exec->reflect)
+ {
+ ret = vty_cmd_reflect_line(vty) ;
+ if (ret != CMD_SUCCESS)
+ continue ;
+ } ;
/* Pipe work, if any */
if ((parsed->parts & cmd_parts_pipe) != 0)
{
ret = cmd_open_pipes(vty) ;
if (ret != CMD_SUCCESS)
- break ;
+ continue ;
} ;
/* Command execution, if any */
if ((parsed->parts & cmd_part_command) != 0)
- {
- ret = cmd_execute(vty) ;
+ ret = cmd_execute(vty) ;
- if (ret != CMD_SUCCESS)
- {
- /* If ignoring warnings, treat CMD_WARNING as CMD_SUCCESS */
- if (ignore_warning && (ret == CMD_WARNING))
- ret = CMD_SUCCESS ;
-
- /* Treat CMD_CLOSE as CMD_SUCCESS */
- else if (ret == CMD_CLOSE)
- ret = CMD_SUCCESS ;
-
- /* Everything else -> stop */
- else
- break ;
- } ;
- } ;
-
- /* When we get here the last command was CMD_SUCCESS !
- * (Or CMD_WARNING and ignore_warning.)
- *
- * Deals with closing out pipe(s) if required.
- */
- vty_cmd_success(vty) ;
+ /* Deal with success (or suppressed warning). */
+ if ((ret == CMD_SUCCESS) || ((ret == CMD_WARNING) && ignore_warning))
+ ret = vty_cmd_success(vty) ;
} ;
- /* Deal with any errors */
- if (ret == CMD_EOF)
- return CMD_SUCCESS ;
+ /* Arrives here if:
+ *
+ * - vty_cmd_fetch_line() returns anything except CMD_SUCCESS, CMD_EOF or
+ * CMD_HIATUS -- which are not errors.
+ *
+ * - any other operation returns anything except CMD_SUCCESS
+ * (or CMD_WARNING, if they are being ignored), CMD_EOF or CMD_CLOSE.
+ *
+ * Deal with any errors -- generate suitable error messages and close back
+ * to (but excluding) vout_base.
+ *
+ * CMD_SUCCESS and CMD_EMPTY are impossible at this point -- they should
+ * have been dealt with in the loop.
+ *
+ * CMD_EOF is also impossible -- vty_cmd_fetch_line() or vty_cmd_hiatus()
+ * can return that, but that will have been dealt with.
+ *
+ * CMD_CLOSE is also impossible -- commands can return that, but that will
+ * have been dealt with.
+ *
+ * CMD_WAITING is not valid for blocking vio !
+ */
+ assert(ret != CMD_SUCCESS) ;
+ assert(ret != CMD_EMPTY) ;
+ assert(ret != CMD_EOF) ;
+ assert(ret != CMD_CLOSE) ;
+ assert(ret != CMD_WAITING) ;
+
+ vty_cmd_hiatus(vty, ret) ;
return ret ;
} ;
/*==============================================================================
*/
-static qstring cmd_get_pipe_file_name(cmd_parsed parsed, uint ti, uint nt) ;
-
/*------------------------------------------------------------------------------
* Open in and/or out pipes
@@ -352,109 +472,127 @@ cmd_open_pipes(vty vty)
cmd_exec exec = vty->exec ;
cmd_parsed parsed = exec->parsed ;
cmd_return_code_t ret ;
+ vty_io vio ;
+
+ VTY_LOCK() ;
+ vio = vty->vio ;
ret = CMD_SUCCESS ;
+ uty_cmd_depth_mark(vio) ; /* about to push vin and/or vout */
+
/* Deal with any in pipe stuff */
if ((parsed->parts & cmd_part_in_pipe) != 0)
{
- if ((parsed->in_pipe & cmd_pipe_file) != 0)
- {
- qstring name ;
- name = cmd_get_pipe_file_name(parsed, parsed->first_in_pipe,
- parsed->num_in_pipe) ;
+ qstring args ;
- ret = vty_cmd_open_in_pipe_file(vty, name,
- (parsed->in_pipe & cmd_pipe_reflect) != 0) ;
+ args = cmd_tokens_concat(parsed, parsed->first_in_pipe,
+ parsed->num_in_pipe) ;
- qs_reset(name, free_it) ;
- }
+ if ((parsed->in_pipe & cmd_pipe_file) != 0)
+ ret = uty_cmd_open_in_pipe_file(vio, exec->context, args,
+ parsed->in_pipe) ;
else if ((parsed->in_pipe & cmd_pipe_shell) != 0)
- {
- }
+ ret = uty_cmd_open_in_pipe_shell(vio, exec->context, args,
+ parsed->in_pipe) ;
else
zabort("invalid in pipe state") ;
- if (ret != CMD_SUCCESS)
- return ret ;
+ qs_free(args) ;
} ;
/* Deal with any out pipe stuff */
- if ((parsed->parts & cmd_part_out_pipe) != 0)
+ if (((parsed->parts & cmd_part_out_pipe) != 0) && (ret == CMD_SUCCESS))
{
- if ((parsed->out_pipe & cmd_pipe_file) != 0)
- {
- qstring name ;
- name = cmd_get_pipe_file_name(parsed, parsed->first_out_pipe,
- parsed->num_out_pipe) ;
+ qstring args ;
- ret = vty_cmd_open_out_pipe_file(vty, name,
- ((parsed->out_pipe & cmd_pipe_append) != 0)) ;
+ args = cmd_tokens_concat(parsed, parsed->first_out_pipe,
+ parsed->num_out_pipe) ;
- qs_reset(name, free_it) ;
- }
+ if ((parsed->out_pipe & cmd_pipe_file) != 0)
+ ret = uty_cmd_open_out_pipe_file(vio, exec->context, args,
+ parsed->out_pipe) ;
else if ((parsed->out_pipe & cmd_pipe_shell) != 0)
- {
- ret = vty_cmd_open_out_dev_null(vty) ;
- }
+ ret = uty_cmd_open_out_pipe_shell(vio, exec->context, args,
+ parsed->out_pipe) ;
else if ((parsed->out_pipe & cmd_pipe_dev_null) != 0)
- {
- ret = vty_cmd_open_out_dev_null(vty) ;
- }
+ ret = uty_cmd_open_out_dev_null(vio) ;
else
zabort("invalid out pipe state") ;
- if (ret != CMD_SUCCESS)
- return ret ;
+ qs_free(args) ;
} ;
- return CMD_SUCCESS ;
+ VTY_UNLOCK() ;
+ return ret ;
} ;
/*------------------------------------------------------------------------------
- * Get pipe file name
+ * Command Execution.
*
- * Returns a brand new qstring that must be discarded after use.
+ * If !parse_only, set vty->node and dispatch the command.
*
- * Pro tem this just gets the token value !! TODO
- */
-static qstring
-cmd_get_pipe_file_name(cmd_parsed parsed, uint ti, uint nt)
-{
- cmd_token t ;
-
- assert(nt == 2) ;
-
- t = cmd_token_get(parsed->tokens, ti + 1) ;
-
- return qs_copy(NULL, t->qs) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Command Execution
+ * Returns: CMD_SUCCESS -- it all want very well
+ * CMD_WARNING -- not so good: warning message sent by vty_out()
+ * CMD_ERROR -- not so good: warning message sent by vty_out()
+ * CMD_CLOSE -- close the current input
*
- * Returns:
+ * NB: the distinction between CMD_WARNING and CMD_ERROR is that CMD_WARNING
+ * may be ignored when reading a configuration file.
*
- * - have command ready for execution -- CMD_SUCCESS
- * - reached end of command stream -- CMD_CLOSE
- * - encounter error of some kind -- CMD_WARNING, CMD_ERROR, etc
+ * NB: no other returns are acceptable !
*/
extern cmd_return_code_t
cmd_execute(vty vty)
{
- cmd_parsed parsed = vty->exec->parsed ;
- cmd_command cmd = parsed->cmd ;
+ cmd_parsed parsed = vty->exec->parsed ;
+ cmd_context context = vty->exec->context ;
+ cmd_command cmd = parsed->cmd ;
+
cmd_return_code_t ret ;
- node_type_t onode ;
- onode = vty->node ;
vty->node = parsed->cnode ;
- ret = (*(cmd->func))(cmd, vty, cmd_arg_vector_argc(parsed),
- cmd_arg_vector_argv(parsed)) ;
+ if (context->parse_only)
+ ret = CMD_SUCCESS ;
+ else
+ ret = (*(cmd->func))(cmd, vty, cmd_arg_vector_argc(parsed),
+ cmd_arg_vector_argv(parsed)) ;
+
+ if (ret == CMD_SUCCESS)
+ {
+ /* If the node is changed by the command, do that now and make sure
+ * that the configuration symbol of power is straight.
+ *
+ * If the new node is >= CONFIG_NODE, then MUST already have acquired
+ * the symbol of power (otherwise the command would have failed !)
+ *
+ * If the new node is < CONFIG_NODE, then we will here release the
+ * symbol of power iff we are at the vin_base !
+ *
+ * If the new node is NULL_NODE, then treat as CMD_CLOSE.
+ */
+ if (context->node != parsed->nnode)
+ {
+ context->node = parsed->nnode ;
+ vty_cmd_config_lock_check(vty, context->node) ;
+
+ if (context->node == NULL_NODE)
+ ret = CMD_CLOSE ;
+ } ;
- if (((parsed->parts & cmd_part_do) != 0) && (vty->node == ENABLE_NODE))
- vty->node = onode ;
+ /* The command should (no longer) change the vty->node, but if it does,
+ * it had better be to the same as what the parser expected -- for if
+ * not, that will break "parse_only" and generally cause confusion.
+ */
+ qassert((vty->node == parsed->cnode) || (vty->node == parsed->nnode)) ;
+ }
+ else
+ {
+ /* Enforce restrictions on return codes. */
+ assert((ret == CMD_WARNING) || (ret == CMD_ERROR)
+ || (ret == CMD_CLOSE)) ;
+ } ;
return ret ;
} ;
diff --git a/lib/command_execute.h b/lib/command_execute.h
index c1a2bdd0..97033b6e 100644
--- a/lib/command_execute.h
+++ b/lib/command_execute.h
@@ -39,16 +39,14 @@
*/
enum cmd_exec_state
{
- exec_null = 0,
-
- exec_special, /* not a simple command */
-
- exec_fetch,
- exec_parse,
- exec_open_pipes,
- exec_execute,
- exec_success,
- exec_complete,
+ exec_null = 0, /* not started, yet */
+ exec_fetch, /* fetch command line */
+ exec_open_pipes, /* open pipes on command line */
+ exec_execute, /* execute standard command */
+ exec_special, /* execute special command */
+ exec_done_cmd, /* command has completed */
+ exec_hiatus, /* while issues are dealt with */
+ exec_stopped, /* command loop has stopped */
} ;
typedef enum cmd_exec_state cmd_exec_state_t ;
@@ -56,25 +54,27 @@ typedef struct cmd_exec* cmd_exec ;
struct cmd_exec
{
- vty vty ; /* parent */
+ vty vty ; /* parent */
+
+ cmd_action_t action ; /* to do + line */
+
+ cmd_context context ; /* how to parse/execute */
- qstring line ; /* pointer to qstring in vf */
- cmd_do_t to_do ; /* for cli driven stuff */
+ bool out_suppress ; /* for configuration reading */
+ bool reflect ; /* actually reflect */
- cmd_parse_type_t parse_type ; /* how should parse */
- bool out_enabled ; /* as required */
- bool reflect_enabled ; /* as required */
+ cmd_parsed parsed ; /* parsing and its result */
- cmd_parsed_t parsed ; /* embedded */
+ uint password_failures ; /* AUTH_NODE & AUTH_ENABLE_NODE */
- cmd_exec_state_t state ; /* for cq_process */
- qpn_nexus locus ; /* for cq_process */
+ cmd_exec_state_t state ; /* for cq_process */
+ qpn_nexus locus ; /* for cq_process */
- cmd_return_code_t ret ; /* for cq_process */
+ cmd_return_code_t ret ; /* for cq_process */
union
{
- mqueue_block mqb ; /* for cq_process */
+ mqueue_block mqb ; /* for cq_process */
struct thread* thread ;
} cq ;
} ;
@@ -89,16 +89,14 @@ extern cmd_exec cmd_exec_free(cmd_exec exec) ;
extern cmd_return_code_t cmd_read_config(vty vty, cmd_command first_cmd,
bool ignore_warning) ;
-extern cmd_return_code_t cmd_end(vty vty) ;
-extern cmd_return_code_t cmd_exit(vty vty) ;
-
extern cmd_return_code_t cmd_open_pipes(vty vty) ;
extern cmd_return_code_t cmd_execute(vty vty) ;
-
-
-
+extern cmd_context cmd_context_new(void) ;
+extern cmd_context cmd_context_new_save(cmd_context src, qpath file_here) ;
+extern cmd_context cmd_context_restore(cmd_context dst, cmd_context src) ;
+extern cmd_context cmd_context_free(cmd_context context, bool copy) ;
#if 0
diff --git a/lib/command_local.h b/lib/command_local.h
index 321ed33e..0925930d 100644
--- a/lib/command_local.h
+++ b/lib/command_local.h
@@ -26,6 +26,7 @@
#include "vty_local.h"
#include "vector.h"
+#include "qpath.h"
/*==============================================================================
* This is for access to some things in command.c which are not required
@@ -63,39 +64,41 @@ struct host
int lines;
/* Log filename. */
- char* logfile;
+ qpath logfile;
/* config file name of this host */
- char* config_file ;
+ qpath config_file ;
+ qpath config_dir ;
/* Flags for services */
bool advanced;
bool encrypt;
/* Banner configuration. */
- const char* motd;
- char* motdfile;
+ const char* motd ;
+ qpath motdfile;
/* Someone has the config symbol of power */
bool config ;
+ ulong config_brand ;
- /* Allow VTY to start without password */
+ /* Allow vty to start without password */
bool no_password_check ;
/* Restrict unauthenticated logins? */
bool restricted_mode ;
- /* Vty timeout value -- see "exec timeout" command */
+ /* vty timeout value -- see "exec timeout" command */
unsigned long vty_timeout_val ;
- /* Vty access-class command */
+ /* vty access-class command */
char* vty_accesslist_name ;
- /* Vty access-class for IPv6. */
+ /* vty access-class for IPv6. */
char* vty_ipv6_accesslist_name ;
- /* Current directory -- initialised in vty_init() */
- char* vty_cwd ;
+ /* Current directory -- initialised cmd_cwd() */
+ qpath cwd ;
} ;
enum
@@ -115,17 +118,20 @@ enum cmd_do
cmd_do_command = 1, /* dispatch the current command line */
- cmd_do_ctrl_c, /* received ^c */
- cmd_do_ctrl_d, /* received ^d */
- cmd_do_ctrl_z, /* received ^z */
+ cmd_do_ctrl_c, /* received ^C */
+ cmd_do_ctrl_d, /* received ^D */
+ cmd_do_ctrl_z, /* received ^Z */
cmd_do_eof, /* hit "EOF" */
+ cmd_do_timed_out, /* terminal timed out */
cmd_do_count, /* number of different cli_do_xxx */
cmd_do_mask = 0x0F,
+
cmd_do_auth = 0x10,
- cmd_do_auth_enable = 0x20,
+
+ cmd_do_keystroke = 0xFF, /* special for keystroke reader */
} ;
CONFIRM(cmd_do_count <= (cmd_do_mask + 1)) ;
@@ -133,6 +139,19 @@ CONFIRM(cmd_do_count <= (cmd_do_mask + 1)) ;
typedef enum cmd_do cmd_do_t ;
/*------------------------------------------------------------------------------
+ * Command action -- qualifier + line
+ */
+struct cmd_action
+{
+ cmd_do_t to_do ;
+ qstring line ;
+} ;
+typedef struct cmd_action cmd_action_t[1] ;
+typedef struct cmd_action* cmd_action ;
+
+enum { CMD_ACTION_ALL_ZEROS = (cmd_do_nothing == 0) } ;
+
+/*------------------------------------------------------------------------------
* Vector of nodes -- defined in command.c, declared here so the parser can
* reach it.
*/
@@ -149,8 +168,7 @@ extern vector node_vector ;
*/
extern const char* cmd_host_name(bool fresh) ;
-extern char *host_config_file(void);
-extern void host_config_set(const char* file_name);
+extern void cmd_host_config_set(qpath config_file);
extern const char* cmd_prompt(node_type_t node) ;
@@ -158,4 +176,28 @@ extern node_type_t cmd_node_parent(node_type_t node) ;
extern node_type_t cmd_node_exit_to(node_type_t node) ;
extern node_type_t cmd_node_end_to(node_type_t node) ;
+/*==============================================================================
+ *
+ */
+Inline void
+cmd_action_clear(cmd_action act)
+{
+ act->to_do = cmd_do_nothing ;
+ act->line = NULL ; /* not essential, but tidy */
+} ;
+
+Inline void
+cmd_action_set(cmd_action act, cmd_do_t to_do, qstring line)
+{
+ act->to_do = to_do ;
+ act->line = line ;
+} ;
+
+Inline void
+cmd_action_take(cmd_action dst, cmd_action src)
+{
+ *dst = *src ;
+ cmd_action_clear(src) ;
+} ;
+
#endif /* _ZEBRA_COMMAND_LOCAL_H */
diff --git a/lib/command_parse.c b/lib/command_parse.c
index c64ee3ba..ba1b217e 100644
--- a/lib/command_parse.c
+++ b/lib/command_parse.c
@@ -963,35 +963,184 @@ cmd_cmp_range_items(const cmd_item a, const cmd_item b)
*/
/*------------------------------------------------------------------------------
- * Make a brand new token object
+ * Create a new, empty token_vector with room for a dozen arguments (initially).
*/
-Private cmd_token
-cmd_token_new(void)
+static token_vector
+cmd_token_vector_new(void)
{
- return XCALLOC(MTYPE_TOKEN, sizeof(struct cmd_token)) ;
+ token_vector tv ;
- /* Zeroising the new structure sets:
- *
- * type = 0 -- cmd_tok_eol
- * qs = zeroised qstring -- empty string
- * complete = 0 -- false
- *
- * tp = 0
- * lp = zeroised elstring -- empty string
- */
- confirm(cmd_tok_eol == 0) ;
- confirm(QSTRING_INIT_ALL_ZEROS) ;
- confirm(ELSTRING_INIT_ALL_ZEROS) ;
+ tv = XCALLOC(MTYPE_CMD_PARSED, sizeof(token_vector_t)) ;
+
+ vector_init_new(tv->body, 12) ;
+
+ return tv ;
} ;
/*------------------------------------------------------------------------------
- * Empty token object and free it.
+ * Empty token_vector and release all memory if required.
*/
-static void
-cmd_token_free(cmd_token t)
+static token_vector
+cmd_token_vector_free(token_vector tv)
+{
+ if (tv != NULL)
+ {
+ cmd_token t ;
+
+ /* Give back all the token objects and release vector body */
+ while ((t = vector_ream(tv->body, keep_it)) != NULL)
+ {
+ qs_reset(t->qs, keep_it) ; /* discard body of qstring */
+ XFREE(MTYPE_TOKEN, t) ;
+ } ;
+
+ XFREE(MTYPE_CMD_PARSED, tv) ;
+ } ;
+
+ return NULL ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get string value of given token.
+ *
+ * Returns an empty (not NULL) string if token NULL or no string.
+ */
+inline static char*
+cmd_token_make_string(cmd_token t)
+{
+ if (t->term)
+ return qs_char_nn(t->qs) ;
+ else
+ {
+ t->term = true ;
+ return qs_make_string(t->qs) ;
+ }
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get i'th token from given token vector -- zero origin
+ */
+Inline cmd_token
+cmd_token_get(token_vector tv, vector_index_t i)
+{
+ return vector_get_item(tv->body, i) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set i'th token from given token vector -- zero origin
+ */
+inline static void
+cmd_token_set(token_vector tv, vector_index_t i,
+ cmd_token_type_t type, const char* p, usize len, usize tp)
+{
+ cmd_token t = cmd_token_get(tv, i) ;
+
+ if (t == NULL)
+ {
+ /* Make a brand new token object */
+ t = XCALLOC(MTYPE_TOKEN, sizeof(struct cmd_token)) ;
+
+ /* Zeroising the new structure sets:
+ *
+ * type = 0 -- cmd_tok_eol
+ * qs = zeroised qstring -- empty string
+ * complete = 0 -- false
+ *
+ * tp = 0
+ * lp = zeroised elstring -- empty string
+ */
+ confirm(cmd_tok_eol == 0) ;
+ confirm(QSTRING_INIT_ALL_ZEROS) ;
+ confirm(ELSTRING_INIT_ALL_ZEROS) ;
+
+ vector_set_item(tv->body, i, t) ;
+ } ;
+
+ t->type = type ;
+ t->term = false ;
+ t->tp = tp ;
+
+ qs_set_alias_n(t->qs, p, len) ;
+ qs_els_copy_nn(t->ot, t->qs) ;
+} ;
+
+
+/*------------------------------------------------------------------------------
+ * Get one or more original token values, concatenated with space between each.
+ *
+ * Returns a brand new qstring that must be discarded after use.
+ */
+extern qstring
+cmd_tokens_concat(cmd_parsed parsed, uint ti, uint nt)
+{
+ cmd_token t ;
+ qstring qs ;
+
+ assert(nt >= 2) ;
+
+ t = cmd_token_get(parsed->tokens, ++ti) ;
+ qs = qs_set_els(NULL, t->ot) ;
+
+ while (--nt >= 2)
+ {
+ t = cmd_token_get(parsed->tokens, ++ti) ;
+ qs_append_str(qs, " ") ;
+ qs_append_els(qs, t->ot) ;
+ } ;
+
+ return qs ;
+} ;
+
+/*==============================================================================
+ * Argument vector
+ */
+
+/*------------------------------------------------------------------------------
+ * Create a new, empty arg_vector with room for a dozen arguments (initially).
+ */
+static arg_vector
+cmd_arg_vector_new(void)
{
- qs_reset(t->qs, keep_it) ; /* discard body of qstring */
- XFREE(MTYPE_TOKEN, t) ;
+ arg_vector args ;
+
+ args = XCALLOC(MTYPE_CMD_PARSED, sizeof(arg_vector_t)) ;
+
+ vector_init_new(args->body, 12) ;
+
+ return args ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Empty arg_vector and release all memory if required.
+ */
+static arg_vector
+cmd_arg_vector_free(arg_vector args)
+{
+ if (args != NULL)
+ {
+ vector_reset(args->body, keep_it) ;
+ XFREE(MTYPE_CMD_PARSED, args) ;
+ } ;
+
+ return NULL ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Empty the body of the arg_vector object in cmd_parsed.
+ */
+Inline void
+cmd_arg_vector_empty(cmd_parsed parsed)
+{
+ vector_set_length(parsed->args->body, 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Push argument to the argument vector.
+ */
+Inline void
+cmd_arg_vector_push(cmd_parsed parsed, char* arg)
+{
+ vector_push_item(parsed->args->body, arg) ;
} ;
/*==============================================================================
@@ -999,31 +1148,31 @@ cmd_token_free(cmd_token t)
*/
/*------------------------------------------------------------------------------
- * Initialise a new cmd_parsed object, allocating if required
+ * Allocate and initialise a new cmd_parsed object
*/
extern cmd_parsed
-cmd_parsed_init_new(cmd_parsed parsed)
+cmd_parsed_new(void)
{
- if (parsed == NULL)
- parsed = XCALLOC(MTYPE_CMD_PARSED, sizeof(*parsed)) ;
- else
- memset(parsed, 0, sizeof(*parsed)) ;
+ cmd_parsed parsed ;
+
+ parsed = XCALLOC(MTYPE_CMD_PARSED, sizeof(cmd_parsed_t)) ;
/* Zeroising the structure has set:
*
- * parts = 0 -- cleared by cmd_tokenise()
- * tok_total = 0 -- set by cmd_tokenise()
+ * parts = 0 -- cleared by cmd_tokenize()
+ * tok_total = 0 -- set by cmd_tokenize()
*
- * elen = 0 -- set by cmd_tokenise()
- * tsp = 0 -- set by cmd_tokenise()
+ * elen = 0 -- set by cmd_tokenize()
+ * tsp = 0 -- set by cmd_tokenize()
*
* cmd = NULL -- no command yet
* cnode = 0 -- not set
+ * nnode = 0 -- not set
*
- * num_tokens = 0 -- set by cmd_tokenise()
- * tokens = all zeros -- empty token vector
+ * num_tokens = 0 -- set by cmd_tokenize()
+ * tokens = NULL -- see below
*
- * args = all zeros -- empty vector of arguments
+ * args = NULL -- see below
*
* emess = NULL -- no error yet
* eloc = 0 -- no error location
@@ -1045,8 +1194,8 @@ cmd_parsed_init_new(cmd_parsed parsed)
* cti ) set by cmd_token_position()
* rp )
*
- * cmd_v = all zeros -- empty vector of filtered commands
- * item_v = all zeros -- empty vector of filtered items
+ * cmd_v = NULL -- no vector of filtered commands
+ * item_v = NULL -- no vector of filtered items
*
* strongest )
* best_complete ) set by cmd_filter_prepare()
@@ -1054,37 +1203,33 @@ cmd_parsed_init_new(cmd_parsed parsed)
* strict )
*/
confirm(cmd_pipe_none == 0) ;
- confirm(TOKEN_VECTOR_INIT_ALL_ZEROS) ;
- confirm(ARG_VECTOR_INIT_ALL_ZEROS) ;
+
+ parsed->tokens = cmd_token_vector_new() ;
+ parsed->args = cmd_arg_vector_new() ;
return parsed ;
} ;
/*------------------------------------------------------------------------------
- * Empty out and (if required) free a cmd_parsed object
+ * Empty out and free a cmd_parsed object
*/
extern cmd_parsed
-cmd_parsed_reset(cmd_parsed parsed, free_keep_b free_structure)
+cmd_parsed_free(cmd_parsed parsed)
{
if (parsed != NULL)
{
- cmd_token t ;
+ parsed->tokens = cmd_token_vector_free(parsed->tokens) ;
+ parsed->args = cmd_arg_vector_free(parsed->args) ;
- /* Give back all the token objects and release vector body */
- while ((t = vector_ream(parsed->tokens->body, keep_it)) != NULL)
- cmd_token_free(t) ;
+ parsed->cmd_v = vector_reset(parsed->cmd_v, free_it) ;
+ parsed->item_v = vector_reset(parsed->item_v, free_it) ;
- vector_reset(parsed->args->body, keep_it) ; /* embedded */
- vector_reset(parsed->cmd_v, keep_it) ; /* embedded */
- vector_reset(parsed->item_v, keep_it) ; /* embedded */
+ parsed->emess = qs_reset(parsed->emess, free_it) ;
- if (free_structure)
- XFREE(MTYPE_CMD_PARSED, parsed) ; /* sets parsed = NULL */
- else
- cmd_parsed_init_new(parsed) ;
+ XFREE(MTYPE_CMD_PARSED, parsed) ;
} ;
- return parsed ;
+ return NULL ;
} ;
/*==============================================================================
@@ -1092,6 +1237,9 @@ cmd_parsed_reset(cmd_parsed parsed, free_keep_b free_structure)
*
*
*/
+static cmd_return_code_t
+cmd_set_parse_error(cmd_parsed parsed, cmd_token t, usize off,
+ const char* format, ...) PRINTF_ATTRIBUTE(4, 5) ;
/*------------------------------------------------------------------------------
* Register a parsing error.
@@ -1113,27 +1261,72 @@ cmd_parsed_reset(cmd_parsed parsed, free_keep_b free_structure)
* Returns: CMD_ERR_PARSING -- which MUST only be returned if p
*/
static cmd_return_code_t
-cmd_parse_error(cmd_parsed parsed, cmd_token t, usize off, const char* mess)
+cmd_set_parse_error(cmd_parsed parsed, cmd_token t, usize off,
+ const char* format, ...)
{
- parsed->emess = mess ;
- parsed->eloc = t->tp + off ;
+ va_list args ;
+
+ qs_clear(parsed->emess) ;
+
+ va_start (args, format);
+ parsed->emess = qs_vprintf(parsed->emess, format, args);
+ va_end (args) ;
+
+ if (t != NULL)
+ parsed->eloc = t->tp + off ;
+ else
+ parsed->eloc = -1 ;
return CMD_ERR_PARSING ;
} ;
+/*------------------------------------------------------------------------------
+ * Register a parsing error.
+ *
+ * Takes token in which parsing error was detected, and an offset from the
+ * start of that, for the location of the error. If the offset is not zero,
+ * it must be an offset in the original token (!).
+ *
+ * The message is a simple constant string (!).
+ *
+ * The message will be output as: ..........^ pointing to the location
+ * followed by: % <mess>\n
+ *
+ * (The mess does not need to include the '%' or the '\n'.)
+ *
+ * Sets: parsed->emess
+ * parsed->eloc
+ *
+ * Returns: CMD_ERR_PARSING -- which MUST only be returned if p
+ */
+extern void
+cmd_get_parse_error(vio_fifo ebuf, cmd_parsed parsed, uint indent)
+{
+ if (parsed->eloc >= 0)
+ {
+ qstring here ;
+
+ here = qs_set_fill(NULL, indent + parsed->eloc, "....") ;
+ vio_fifo_put_bytes(ebuf, qs_body_nn(here), qs_len_nn(here)) ;
+ qs_reset(here, free_it) ;
+ } ;
+
+ vio_fifo_printf(ebuf, "^\n%% %s\n", qs_make_string(parsed->emess)) ;
+} ;
+
/*==============================================================================
* Lexical level stuff
*/
/*------------------------------------------------------------------------------
- * Take elstring and see if it is empty -- only whitespace and/or comment
+ * Take qstring and see if it is empty -- only whitespace and/or comment
*/
extern bool
-cmd_is_empty(elstring line)
+cmd_is_empty(qstring line)
{
cpp_t lp ;
- els_cpp(lp, line) ; /* NULL -> NULL */
+ qs_cpp(lp, line) ; /* NULL -> NULL */
while (lp->p < lp->e)
{
@@ -1156,87 +1349,82 @@ cmd_is_empty(elstring line)
* without requiring whitespace separation, also do not want to intrude into
* quoted or escaped stuff. So limit the characters that are reserved.
*/
-static inline bool
+static bool
cmd_pipe_reserved_char(char ch)
{
return strchr("<|>%&*+-=?", ch) != NULL ;
} ;
/*------------------------------------------------------------------------------
- * Take elstring and break it into tokens.
- *
- * Discards leading and trailing ' ' or '\t'.
+ * Take qstring and break it into tokens.
*
* Expects string to have been preprocessed, if required, to ensure that any
* unwanted control characters have been removed. This code only recognises
- * '\t' and treats it as whitespace.
+ * '\t' and treats it as whitespace (so any other control characters will
+ * end up as part of a token).
*
- * Anything between '....' is ignored by the tokenizer. NB: this follows the
- * shell convention, so '\' is also ignored and there is no way to include "'"
- * in a single quoted string.
+ * Ignores leading and trailing ' ' or '\t' (whitespace).
*
- * Anything immediately preceded by '\' is ignored by the tokenizer. This
- * includes blanks and quotes.
+ * Have "full_lex" flag -- to distinguish old and new.
*
- * Anything inside "...." is ignored by the tokenizer, including '\"' escapes.
+ * If not full_lex, we have this simple tokenization:
*
- * Unbalanced "'" or '"' are treated as if eol was a "'" or '"'.
+ * * tokens are separated by one or more whitespace characters.
*
- * Of the things which are not ignored by the tokenizer:
+ * * if the first non-whitespace character is '!', this is a comment line.
*
- * * tokens are separated by whitespace -- one ' ' or '\t' characters
- * The whitespace is discarded.
+ * For the "full_lex" we more or less follow the usual shell conventions,
+ * except:
*
- * * tokens which start with any of:
+ * * can have '!' as well as '#' to start comments
*
- * '!', '#', '<' and '>'
+ * * may only have '|' at the start of a line. '>|' must be used for
+ * piping command output to shell command. So '|' is, effectively, a
+ * "shell command prefix".
*
- * terminate themselves, as follows:
+ * Limiting '|' in this way removes problems with regular expressions.
*
- * - from '!' or '#' to end of line is a comment token.
+ * Also allows a <| shell to use pipes !!
*
- * - '<' followed by pipe_reserved_chars is a token (in_pipe)
+ * So the "full_lex" will:
*
- * - '>' followed by pipe_reserved_chars is a token (out_pipe).
+ * * ignore anything between '....' -- as per shell convention, '\' is
+ * ignored and there is no way to include "'" in a single quoted string.
*
- * See above for pipe_reserved_
+ * * ignore anything immediately preceded by '\' -- including space
+ * (or tab converted to a space) and double quote characters.
*
- * NB: this means that for '!', '#', '<' and '>' to be significant, they
- * MUST be preceeded by whitespace (or start of line). This ever so
- * slightly reduces the impact of the new lexical conventions.
+ * * ignore anything between "...." -- including '\"' escapes.
*
- * NB: the tokenization roughly mimics the (POSIX) standard shell. The
- * differences are:
+ * * unbalanced "'" or '"' are treated as if eol was a "'" or '"'.
*
- * '|' is *not* a pipe ('>|' is), because '|' is a character in the
- * regex repertoire.
+ * Except when ignored by the rules above, the "full lex" will recognise
+ * the following:
*
- * '<' and '>' do not terminate a token -- so are only significant
- * at the start of a token.
+ * * tokens are separated by whitespace -- one ' ' or '\t' characters.
+ * Whitespace before, after or between tokens is not part of the tokens.
*
- * '!' is not a comment for the shell.
+ * * the character '|' is significant at the start of a line (after any
+ * leading whitespace) only. It is then the start of an out_pipe
+ * token -- so self terminates after any pipe_reserved_chars.
*
- * The requirement for whitespace (or start of line) before '#' is
- * consistent with the shell.
+ * * the characters '!', '#' are significant only at the start of a token,
+ * and then from there to end of line is comment.
*
- * The handling of '...' follows the standard shell.
+ * Note that this means that comment after a token must be separated by
+ * at least one space from that token.
*
- * The tokenization does not remove any " ' or \ characters, that is left
- * for a later stage, where context may affect the handling.
+ * * the characters '<' and '>' are separators -- they terminate any
+ * preceding token. They are in_pipe or out_pipe tokens, and self
+ * terminate after any pipe_reserved_chars.
*
- * NB: any control characters other than '\t' are accepted as part of the
- * current token !
+ * The tokenization does not remove any " ' or \ characters, that is left for
+ * a later stage, where context may affect the handling.
*
* The tokens returned contain all the original characters of the line, except
* for the removal of ' ' and '\t' between tokens and at the end of the line.
*
- * Note: all the tokens in the vector have at least one character, and no
- * entries are NULL.
- *
- * NB: it is the callers responsibility to release the token objects in due
- * course.
- *
- * NB: the elstring containing the line to be tokenised MUST NOT change
+ * NB: the elstring containing the line to be tokenized MUST NOT change
* until the parsed object is finished with.
*
* Returns: the types of all tokens or'd together.
@@ -1254,7 +1442,7 @@ cmd_pipe_reserved_char(char ch)
* Note that the num_tokens does not include the cmd_tok_eol on the end.
*/
extern void
-cmd_tokenise(cmd_parsed parsed, qstring line)
+cmd_tokenize(cmd_parsed parsed, qstring line, bool full_lex)
{
cpp_t lp ;
const char *cp, *tp ;
@@ -1300,59 +1488,94 @@ cmd_tokenise(cmd_parsed parsed, qstring line)
case '\'': /* proceed to matching ' or end */
++cp ;
- type |= cmd_tok_sq ;
- while (cp < lp->e)
+ if (full_lex)
{
- if (*cp++ == '\'')
- break ;
+ type |= cmd_tok_sq ;
+ while (cp < lp->e)
+ {
+ if (*cp++ == '\'')
+ break ;
+ } ;
} ;
break ;
case '\\': /* step past escaped character, if any */
++cp ;
- type |= cmd_tok_esc ;
- if (cp < lp->e)
- ++cp ;
+ if (full_lex)
+ {
+ type |= cmd_tok_esc ;
+ if (cp < lp->e)
+ ++cp ;
+ } ;
break ;
case '"': /* proceed to matching " or end... */
++cp ;
- type |= cmd_tok_dq ;
- while (cp < lp->e) /* NB: do not register \ separately */
+ if (full_lex)
{
- if (*cp++ == '"')
- if (*(cp - 2) != '\\') /* ignore escaped " */
- break ;
+ type |= cmd_tok_dq ;
+ while (cp < lp->e) /* NB: do not register \ separately */
+ {
+ if (*cp++ == '"')
+ if (*(cp - 2) != '\\') /* ignore escaped " */
+ break ;
+ } ;
} ;
break ;
- case '>': /* '>' special at start */
- end = (cp == sp) ;
- ++cp ;
- if (end) /* if special */
+ case '|': /* only at start of line */
+ if (full_lex && (nt == 0) && (cp == sp))
{
- type = cmd_tok_out_pipe ;
- while ((cp < lp->e) && cmd_pipe_reserved_char(*cp))
- ++cp ;
- } ;
+ type = cmd_tok_out_shell ;
+ do ++cp ;
+ while ((cp < lp->e) && cmd_pipe_reserved_char(*cp)) ;
+ end = true ;
+ }
+ else
+ ++cp ;
break ;
- case '<': /* '<' special at start */
- end = (cp == sp) ;
- ++cp ;
- if (end) /* if special */
+ case '>': /* '>' is a separator */
+ if (full_lex)
{
- type = cmd_tok_in_pipe ;
- while ((cp < lp->e) && cmd_pipe_reserved_char(*cp))
- ++cp ;
- } ;
+ if (cp == sp)
+ {
+ type = cmd_tok_out_pipe ;
+ do ++cp ;
+ while ((cp < lp->e) && cmd_pipe_reserved_char(*cp)) ;
+ } ;
+ end = true ;
+ }
+ else
+ ++cp ;
break ;
- case '!': /* '!' and '#' special at start */
- case '#':
- if ((cp == sp) && (nt == 0))
+ case '<': /* '<' is a separator */
+ if (full_lex)
{
+ if (cp == sp)
+ {
+ type = cmd_tok_in_pipe ;
+ do ++cp ;
+ while ((cp < lp->e) && cmd_pipe_reserved_char(*cp)) ;
+ } ;
end = true ;
+ }
+ else
+ ++cp ;
+ break ;
+
+ case '#':
+ if (!full_lex)
+ {
+ ++cp ;
+ break ;
+ } ;
+ fall_through ; /* treat as '!'. */
+
+ case '!': /* '!' and '#' special at token start */
+ if ((cp == sp) && (full_lex || (nt == 0)))
+ {
type = cmd_tok_comment ;
cp = lp->e ;
}
@@ -1398,68 +1621,237 @@ cmd_tokenise(cmd_parsed parsed, qstring line)
lp->e, 0, lp->e - lp->p) ;
} ;
+
+
+
+
+/*==============================================================================
+ * VTY Command Line Input Pipe
+ *
+ * Here are the mechanics which support the:
+ *
+ * < file_name
+ *
+ * <| shell_command
+ *
+ * and the:
+ *
+ * > file_name
+ * >> file_name
+ * | shell_command
+ *
+ *==============================================================================
+ * The file_name handling
+ *
+ * Two directories are supported:
+ *
+ * "home" -- being the root for configuration files
+ *
+ * "cd" -- being the root for relative filenames, in the usual way
+ *
+ * "here" -- being the directory for the enclosing "< filename"
+ * see below for more detailed semantics
+ *
+ * There are the global values:
+ *
+ * config home -- defaults to directory for configuration file.
+ * may be set by command.
+ *
+ * config cd -- defaults to cwd at start up
+ *
+ * There are the local values, within a given CLI instance (ie VTY):
+ *
+ * cli home -- set to config home when CLI instance starts, or when
+ * config home is set within the CLI.
+ *
+ * cli cd -- similarly
+ *
+ * cli here -- outside < is same as cli home, unless explicitly set
+ * - set by cli here
+ * - pushed each time executes <, and set to directory for
+ * the < filename.
+ * - pushed each time executes <| and left unchanged
+ * - popped each time exits < or <|
+ * - set to parent by "no cli here" inside <, or to
+ * default state outside <
+ *
+ * And then the filename syntax:
+ *
+ * /path -- absolute path -- in the usual way
+ *
+ * path -- path wrt "cli cd" -- in the usual way
+ *
+ * ~/path -- path in "cli home" -- so "config" stuff
+ *
+ * ~./path -- path in "here" -- so wrt to enclosing < file
+ *
+ *==============================================================================
+ * The Input Pipe Commands
+ *
+ * These are "universal commands".
+ *
+ * They are:
+ *
+ * < filename -- filename as above
+ *
+ * treat contents of given file as command lines.
+ * (That may include further < commands.)
+ *
+ * See notes on cli here for pushing/popping the here
+ * directory.
+ *
+ * TODO: Filename and quotes ??
+ *
+ * <| shell_command -- the shell command is executed "as is" by system().
+ *
+ * the stdout and stderr are collected.
+ *
+ * treats stdout as command lines.
+ * (That may include further < commands.)
+ *
+ * anything from stderr is sent to the VTY output.
+ *
+ * As far as the top level CLI is concerned, these are discrete commands.
+ * That is to say:
+ *
+ * -- except where blocked while reading the "pipe", all commands are
+ * executed one after another, in one Routing Engine operation.
+ *
+ * -- in any event, all output is gathered in the VTY buffering, and will
+ * be sent to the console (or where ever) only when the outermost command
+ * completes.
+ *
+ * There are three options associated with the output from a < operation:
+ *
+ * -- suppress command line reflect
+ *
+ * whether to suppress reflect of commands to the VTY before they are
+ * dispatched.
+ *
+ * The default is not to suppress command line reflect.
+ *
+ * -- suppress command results
+ *
+ * whether to suppress any output generated by each command.
+ *
+ * The default is not to suppress command results.
+ *
+ * -- suppress "more"
+ *
+ * whether to do "--more--", if currently applies, when finally
+ * outputting all the command results.
+ *
+ * The default is not to suppress "more".
+ *
+ * This option can only be set for the outermost < operation.
+ *
+ * Remembering that all output occurs in one go when the outermost < operation
+ * completes.
+ *
+ * The default is to show everything and implement "--more--", pretty much as
+ * if the commands had been typed in. Except that "--more--" applies to
+ * everything (including the command lines) together, rather than to the output
+ * of each command individually.
+ *
+ * These options may be changed by flags attached to the <, as follows:
+ *
+ * ! suppress command line reflect
+ *
+ * ? suppress "--more--"
+ *
+ * * suppress command output
+ *
+ * TODO: cli xxx commands for reflect and output suppression and notes ....
+ *
+ * TODO: Error handling....
+ *
+ * TODO: Closing the CLI....
+ *
+ * TODO: Time out and the CLI....
+ *
+ *
+ */
+
+
+
+
+/*------------------------------------------------------------------------------
+ * Get next cpp char & step -- unless at end of cpp.
+ */
+inline static char
+cpp_getch(cpp p)
+{
+ if (p->p < p->e)
+ return *p->p++ ;
+ else
+ return '\0' ;
+}
+
/*------------------------------------------------------------------------------
* Process in-pipe token and set the required bits in the pipe type word
*
- * Known tokens are: < <| <+ <|+
+ * Known tokens are: < <|
+ * Known options are: +
*/
static cmd_return_code_t
cmd_parse_in_pipe(cmd_parsed parsed, cmd_token t)
{
cpp_t p ;
- bool ok ;
+ bool ok ;
els_cpp(p, t->ot) ;
- ok = ((p->p < p->e) && (*p->p++ == '<')) ;
+ ok = true ;
- if (ok)
- {
- /* First character after '<' may qualify the type of the pipe */
- parsed->in_pipe = cmd_pipe_file ;
+ switch (cpp_getch(p))
+ {
+ case '<':
+ switch (cpp_getch(p))
+ {
+ default:
+ --p->p ;
+ fall_through ;
- if (p->p < p->e)
- {
- switch (*p->p++)
- {
- case '|':
- parsed->in_pipe = cmd_pipe_shell ;
- break ;
+ case '\0':
+ parsed->in_pipe = cmd_pipe_file ;
+ break ;
- default:
- --p->p ; /* put back */
- break ;
- }
- } ;
+ case '|':
+ parsed->in_pipe = cmd_pipe_shell ;
+ break ;
+ } ;
+ break ;
- /* Deal with option characters */
- while (ok && (p->p < p->e))
- {
- /* Eat option character, if recognise it. */
- switch (*p->p++)
- {
- case '+': /* reflect command lines */
- parsed->in_pipe |= cmd_pipe_reflect ;
- break ;
+ default:
+ ok = false ;
+ break ;
+ } ;
- default:
- --p->p ;
- ok = false ;
- break ;
- } ;
- } ;
+ while (ok && (p->p < p->e))
+ {
+ switch (*p->p++)
+ {
+ case '+':
+ parsed->in_pipe |= cmd_pipe_reflect ;
+ break ;
+
+ default:
+ ok = false ;
+ break ;
+ } ;
} ;
- if (!ok)
- return cmd_parse_error(parsed, t, 0, "invalid 'pipe in'") ;
+ if (ok)
+ return CMD_SUCCESS ;
- return CMD_SUCCESS ;
+ return cmd_set_parse_error(parsed, t, 0, "invalid 'pipe in'") ;
} ;
/*------------------------------------------------------------------------------
* Process out-pipe token and set the required bits in the pipe type word
*
- * Known tokens are: > >> >| >*
+ * Known tokens are: | > >> >| >*
+ * Known options are: none
*/
static cmd_return_code_t
cmd_parse_out_pipe(cmd_parsed parsed, cmd_token t)
@@ -1469,46 +1861,58 @@ cmd_parse_out_pipe(cmd_parsed parsed, cmd_token t)
els_cpp(p, t->ot) ;
- ok = ((p->p < p->e) && (*p->p++ == '>')) ;
+ ok = true ;
- if (ok)
- {
- /* First character after '>' may qualify the type of the pipe */
- parsed->out_pipe = cmd_pipe_file ;
+ switch (cpp_getch(p))
+ {
+ case '|':
+ parsed->out_pipe = cmd_pipe_shell | cmd_pipe_shell_only ;
+ break ;
- if (p->p < p->e)
- {
- switch (*p->p++)
- {
- case '>':
- parsed->out_pipe |= cmd_pipe_append ;
- break ;
+ case '>':
+ switch (cpp_getch(p))
+ {
+ default:
+ --p->p ;
+ fall_through ;
- case '|':
- parsed->out_pipe = cmd_pipe_shell ;
- break ;
+ case '\0':
+ parsed->out_pipe = cmd_pipe_file ;
+ break ;
- case '*':
- parsed->out_pipe = cmd_pipe_dev_null ;
- break ;
+ case '>':
+ parsed->out_pipe = cmd_pipe_file | cmd_pipe_append ;
+ break ;
- default:
- --p->p ; /* put back */
- break ;
- }
- } ;
+ case '|':
+ parsed->out_pipe = cmd_pipe_shell ;
+ break ;
- /* Could now have options, but presently do not */
- if (p->p < p->e)
- {
+ case '*':
+ parsed->out_pipe = cmd_pipe_dev_null ;
+ break ;
+ } ;
+ break ;
+
+ default:
+ ok = false ;
+ break ;
+ } ;
+
+ while (ok && (p->p < p->e))
+ {
+ switch (*p->p++)
+ {
+ default:
ok = false ;
- } ;
+ break ;
+ } ;
} ;
- if (!ok)
- return cmd_parse_error(parsed, t, 0, "invalid 'pipe out'") ;
+ if (ok)
+ return CMD_SUCCESS ;
- return CMD_SUCCESS ;
+ return cmd_set_parse_error(parsed, t, 0, "invalid 'pipe out'") ;
} ;
/*------------------------------------------------------------------------------
@@ -1543,7 +1947,7 @@ cmd_parse_out_pipe(cmd_parsed parsed, cmd_token t)
* \! -> ! )
* \# -> \# )
*
- * NB: with the exception of $ inside of "..." the carefully avoids the
+ * NB: with the exception of $ inside of "..." this carefully avoids the
* regex meta characters: .*+?^$_ and (|)[-]
* and the regex escaped forms of those and the back reference \1 etc.
*
@@ -1595,7 +1999,7 @@ cmd_token_complete(cmd_parsed parsed, cmd_token t)
{
if (p->p == p->e)
{
- ret = cmd_parse_error(parsed, t, 0, "missing closing '") ;
+ ret = cmd_set_parse_error(parsed, t, 0, "missing closing '") ;
break ;
} ;
@@ -1623,7 +2027,7 @@ cmd_token_complete(cmd_parsed parsed, cmd_token t)
case '\\':
*q->p++ = *p->p++ ; /* copy the \ */
if (p->p == p->e)
- ret = cmd_parse_error(parsed, t, 0, "trailing \\") ;
+ ret = cmd_set_parse_error(parsed, t, 0, "trailing \\") ;
else
{
if (dq)
@@ -1659,7 +2063,7 @@ cmd_token_complete(cmd_parsed parsed, cmd_token t)
} ;
if (dq)
- ret = cmd_parse_error(parsed, t, 0, "missing closing \"") ;
+ ret = cmd_set_parse_error(parsed, t, 0, "missing closing \"") ;
if (ret == CMD_SUCCESS)
{
@@ -1676,7 +2080,9 @@ cmd_token_complete(cmd_parsed parsed, cmd_token t)
}
/*------------------------------------------------------------------------------
- * Tokenise the given line and work out where cursor is wrt tokens.
+ * Tokenize the given line and work out where cursor is wrt tokens.
+ *
+ * Note that this is implicitly "full lex".
*
* Looks for first token whose end is at or beyond the cursor position. Note
* that:
@@ -1724,8 +2130,8 @@ cmd_token_position(cmd_parsed parsed, qstring line)
const char* e ;
bool sq, dq, bs ;
- /* Re-initialise parsed object and tokenise the given line */
- cmd_tokenise(parsed, line) ;
+ /* Re-initialise parsed object and tokenize the given line */
+ cmd_tokenize(parsed, line, true) ;
/* Get the cursor position */
cp = qs_cp_nn(line) ;
@@ -1743,9 +2149,13 @@ cmd_token_position(cmd_parsed parsed, qstring line)
if (cp > t->tp)
{
- /* As soon as we are past '<', '>', '!' or '#' -- return "special" */
+ /* As soon as we are past '|', '<', '>', '!' or '#'
+ * return "special"
+ */
if ((t->type & ( cmd_tok_in_pipe
- | cmd_tok_out_pipe | cmd_tok_comment) ) != 0)
+ | cmd_tok_out_pipe
+ | cmd_tok_out_shell
+ | cmd_tok_comment) ) != 0)
return true ;
} ;
@@ -2333,28 +2743,24 @@ static int cmd_item_filter(cmd_parsed parsed, cmd_item item, cmd_token t) ;
* Returns: number of commands which may match to.
*/
static uint
-cmd_filter_prepare(cmd_parsed parsed, cmd_parse_type_t type)
+cmd_filter_prepare(cmd_parsed parsed, cmd_context context)
{
vector src_v ;
uint ci ;
uint nct ;
- bool execution = (type & cmd_parse_execution) != 0 ;
- bool strict = (type & cmd_parse_strict) != 0 ;
-
/* get commands for the current node */
- src_v = ((cmd_node)vector_get_item(node_vector, parsed->cnode))
- ->cmd_vector ;
+ src_v = ((cmd_node)vector_get_item(node_vector, parsed->cnode))->cmd_vector ;
assert(src_v != NULL) ; /* invalid parsed->cnode ?? */
- /* empty the working commands vector, making sure big enough for the current
- * node's commands.
+ /* Empty the working commands vector, making sure big enough for the current
+ * node's commands -- creates vector if required.
*
* Note that the cmd_v lives for as long as the parsed object, so will
* grow over time to accommodate what is required.
*/
- vector_re_init(parsed->cmd_v, vector_length(src_v)) ;
+ parsed->cmd_v = vector_re_init(parsed->cmd_v, vector_length(src_v)) ;
/* Filter out commands which are too short.
*
@@ -2371,7 +2777,7 @@ cmd_filter_prepare(cmd_parsed parsed, cmd_parse_type_t type)
if ( cmd->nt_max < nct)
continue ; /* ignore if too short */
- if ((cmd->nt_min > nct) && execution)
+ if ((cmd->nt_min > nct) && context->parse_execution)
continue ; /* ignore if too long */
vector_push_item(parsed->cmd_v, cmd) ;
@@ -2384,8 +2790,9 @@ cmd_filter_prepare(cmd_parsed parsed, cmd_parse_type_t type)
* not meet the minimum criterion is automatically excluded.
*/
- parsed->min_strength = execution ? ms_min_execute : ms_min_parse ;
- parsed->strict = strict ;
+ parsed->min_strength = context->parse_execution ? ms_min_execute
+ : ms_min_parse ;
+ parsed->strict = context->parse_strict ;
parsed->strongest = parsed->min_strength ;
parsed->best_complete = mt_no_match ;
@@ -2432,22 +2839,7 @@ cmd_filter(cmd_parsed parsed, uint ii, bool keep_items)
parsed->strongest = parsed->min_strength ;
parsed->best_complete = mt_no_match ;
- /* If we are keeping the items which are matched, set the item_v
- * empty and guess that may get one item per command.
- *
- * Note that the item_v lives for as long as the parsed object, so will
- * grow over time to accommodate what is required.
- */
- if (keep_items)
- vector_re_init(parsed->item_v, vector_length(parsed->cmd_v)) ;
-
- /* Work down the cmd_v, attempting to match cmd_items against cmd_tokens.
- *
- * Keep in the cmd_v the commands for which we get a match.
- *
- * At the same time, if required, keep in the item_v all the items which have
- * matched.
- */
+ /* Decide which token we are trying to match. */
if (ii < parsed->num_command)
ti = parsed->first_command + ii ; /* map to token index */
else
@@ -2463,12 +2855,27 @@ cmd_filter(cmd_parsed parsed, uint ii, bool keep_items)
}
t = cmd_token_get(parsed->tokens, ti) ;
+ /* If we are keeping the items which are matched, set the item_v
+ * empty and guess that may get one item per command -- creates the item_v
+ * vector if required..
+ *
+ * Note that the item_v lives for as long as the parsed object, so will
+ * grow over time to accommodate what is required.
+ */
+ if (keep_items)
+ parsed->item_v = vector_re_init(parsed->item_v,
+ vector_length(parsed->cmd_v)) ;
+
+ /* Work down the cmd_v, attempting to match cmd_items against the cmd_token.
+ *
+ * Keep in the cmd_v the commands for which we get a match.
+ *
+ * At the same time, if required, keep in the item_v all the items which have
+ * matched.
+ */
c_keep = 0 ;
i_keep = 0 ;
- if (keep_items)
- vector_re_init(parsed->item_v, vector_length(parsed->cmd_v)) ;
-
for (ci = 0; ci < vector_length(parsed->cmd_v); ci++)
{
cmd_command cmd ;
@@ -2688,24 +3095,29 @@ cmd_item_filter(cmd_parsed parsed, cmd_item item, cmd_token t)
*/
static cmd_return_code_t cmd_parse_phase_one(cmd_parsed parsed,
- cmd_parse_type_t type, node_type_t node) ;
+ cmd_context context) ;
static cmd_return_code_t cmd_parse_phase_one_b(cmd_parsed parsed, uint nt) ;
static cmd_return_code_t cmd_parse_phase_two(cmd_parsed parsed,
- cmd_parse_type_t type) ;
+ cmd_context context) ;
+static cmd_return_code_t cmd_parse_specials(cmd_parsed parsed,
+ cmd_context context) ;
+static node_type_t cmd_auth_specials(cmd_context context, node_type_t target) ;
/*------------------------------------------------------------------------------
* Parse a command in the given "node", or (if required) any of its ancestors.
*
- * Requires command line to have been tokenised.
+ * Requires command line to have been tokenized.
*
* Returns: CMD_SUCCESS => successfully parsed command, and the result is
* in the given parsed structure, ready for execution.
*
- * NB: parsed->cnode may not be the incoming node.
+ * - parsed->cnode may not be the incoming node.
*
- * NB: parsed->parts is what was found
+ * - parsed->nnode is set for when command succeeds.
*
- * NB: parsed->cmd->daemon => daemon
+ * - parsed->parts is what was found
+ *
+ * - parsed->cmd->daemon => daemon
*
* CMD_EMPTY => line is empty, except perhaps for comment
* (iff parsing for execution)
@@ -2739,7 +3151,7 @@ static cmd_return_code_t cmd_parse_phase_two(cmd_parsed parsed,
* See elsewhere for description of parsed structure.
*/
extern cmd_return_code_t
-cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type)
+cmd_parse_command(cmd_parsed parsed, cmd_context context)
{
cmd_return_code_t ret ;
@@ -2753,16 +3165,17 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type)
*
* Complete any tokens which contain quotes and/or escapes.
*
- * If there is a command then:
+ * Unless CMD_ERR_PARSING:
*
* - sort out any "do" and cut from start of command.
* - set parsed->cnode (from given node or according to "do")
+ * - set parsed->nnode (from given node)
* - set parsed->cmd = NULL
* - empty the argv vector
*
* Note that cmd_parse_phase_one only returns CMD_SUCCESS or CMD_ERR_PARSING.
*/
- ret = cmd_parse_phase_one(parsed, type, node) ;
+ ret = cmd_parse_phase_one(parsed, context) ;
if (ret != CMD_SUCCESS)
{
assert(ret == CMD_ERR_PARSING) ; /* no other error at this point */
@@ -2772,8 +3185,7 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type)
/* If no command tokens, and is parsing for execution, then we are done...
* but watch out for bare "do"
*/
- if (((parsed->parts & cmd_part_command) == 0) &&
- ((type & cmd_parse_execution) != 0))
+ if (((parsed->parts & cmd_part_command) == 0) && context->parse_execution)
{
if ((parsed->parts & ~cmd_part_comment) == cmd_parts_none)
return CMD_EMPTY ; /* accept empty */
@@ -2786,11 +3198,11 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type)
/* Level 2 parsing
*
- * Try in the current node and then in parent nodes, if can.
+ * Try in the current node and then in parent nodes, if allowed.
*
- * Cannot move up the node tree if is already at CONFIG_NODE or below.
- * Note that "do" pushes us to the ENABLE_NODE -- which is below the
- * CONFIG_NODE.
+ * Will stop moving up the tree when hits a node which is its own parent.
+ * All nodes from NODE_CONFIG up have a parent. All nodes from NODE_ENABLE
+ * down are their own parent.
*
* Note that when not parsing for execution, may get here with no command
* tokens at all -- in which case cmd_parse_phase_two() will return
@@ -2803,19 +3215,14 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type)
{
node_type_t pnode ;
- ret = cmd_parse_phase_two(parsed, type) ;
+ ret = cmd_parse_phase_two(parsed, context) ;
if (ret == CMD_SUCCESS)
break ;
- if (ret != CMD_ERR_NO_MATCH)
+ if ((ret != CMD_ERR_NO_MATCH) || context->parse_no_tree)
return ret ;
- confirm(ENABLE_NODE < CONFIG_NODE) ;
-
- if (((type & cmd_parse_no_tree) != 0) || (parsed->cnode <= CONFIG_NODE))
- return CMD_ERR_NO_MATCH ;
-
pnode = cmd_node_parent(parsed->cnode) ;
if (pnode == parsed->cnode)
@@ -2824,15 +3231,46 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type)
parsed->cnode = pnode ;
} ;
- /* Parsed successfully.
+ /* Parsed successfully -- worry about parsed->nnode.
+ *
+ * Currently parsed->nnode is set to the node we came in on. If the
+ * parsed->cnode is not the same, then we have two choices:
*
- * If for execution, fill the arg_vector
+ * (a) parsed->cnode is ENABLE_NODE -- either because have 'do' or by tree
+ * walk -- in which case we leave parsed->nnode;
+ *
+ * (b) parsed->cnode is not ENABLE_NODE -- so this command, if successful
+ * will change context, in which cas we need to set parsed->nnode.
+ */
+ if ((parsed->nnode != parsed->cnode) && (parsed->cnode != ENABLE_NODE))
+ parsed->nnode = parsed->cnode ;
+
+ /* Worry about "special" commands and those that set the next node. */
+ if ((parsed->cmd->attr & (CMD_ATTR_NODE | CMD_ATTR_MASK)) != 0)
+ {
+ if ((parsed->cmd->attr & CMD_ATTR_NODE) != 0)
+ parsed->nnode = parsed->cmd->attr & CMD_ATTR_MASK ;
+ else
+ {
+ /* This is a "special" command, which may have some (limited)
+ * semantic restrictions.
+ *
+ * The main thing we are interested in is commands which have
+ * special effects on parsed->nnode (in particular exit and end).
+ */
+ ret = cmd_parse_specials(parsed, context) ;
+
+ if (ret != CMD_SUCCESS)
+ return ret ;
+ } ;
+ } ;
+
+ /* If for execution, fill the arg_vector
*
* The arg_vector is an array of pointers to '\0' terminated strings, which
* are pointers to the relevant tokens' qstring bodies.
*/
-
- if ((type & cmd_parse_execution) != 0)
+ if (context->parse_execution)
{
uint ti ;
@@ -2866,7 +3304,7 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type)
} ;
/*------------------------------------------------------------------------------
- * Phase 1 of command parsing
+ * Phase 1 of command parsing -- get ready to search the command system.
*
* Scan the tokens to break them up into the sections:
*
@@ -2876,20 +3314,25 @@ cmd_parse_command(cmd_parsed parsed, node_type_t node, cmd_parse_type_t type)
* - out-pipe -- '>' etc up to comment
* - comment -- '!' or '#' onwards
*
- * Requires line to have been tokenised -- cmd_tokenise().
+ * Sets parsed->cnode to the given node, or to ENABLE_NODE if have 'do' (and
+ * the original node allows it).
+ *
+ * Sets parsed->nnode to the given node.
+ *
+ * Requires line to have been tokenized -- cmd_tokenize().
*
* Returns: CMD_SUCCESS -- all is well
* CMD_ERR_PARSING -- parsing error -- malformed or misplaced pipe
* -- malformed quotes/escapes
*/
static cmd_return_code_t
-cmd_parse_phase_one(cmd_parsed parsed, cmd_parse_type_t type, node_type_t node)
+cmd_parse_phase_one(cmd_parsed parsed, cmd_context context)
{
uint nt = parsed->num_tokens ;
/* Set command and parsing entries */
- parsed->cnode = node ;
- parsed->cmd = NULL ;
+ parsed->nnode = parsed->cnode = context->node ;
+ parsed->cmd = NULL ;
/* pick off any comment */
if ((parsed->tok_total & cmd_tok_comment) != 0)
@@ -2926,7 +3369,7 @@ cmd_parse_phase_one(cmd_parsed parsed, cmd_parse_type_t type, node_type_t node)
if ((parsed->parts & cmd_part_command) != 0)
{
/* Have a command -- worry about "do" if allowed */
- if (((type & cmd_parse_no_do) == 0) && (node >= MIN_DO_SHORTCUT_NODE))
+ if (!context->parse_no_do && (context->node >= MIN_DO_SHORTCUT_NODE))
{
cmd_token t ;
t = cmd_token_get(parsed->tokens, parsed->first_command) ;
@@ -2935,7 +3378,7 @@ cmd_parse_phase_one(cmd_parsed parsed, cmd_parse_type_t type, node_type_t node)
const char* p = els_body_nn(t->ot) ;
if ((*p == 'd') && (*(p+1) == 'o'))
{
- node = ENABLE_NODE ; /* change to this node */
+ parsed->cnode = ENABLE_NODE ; /* change to this node */
parsed->num_do = 1 ;
parsed->first_do = parsed->first_command ;
@@ -2956,7 +3399,7 @@ cmd_parse_phase_one(cmd_parsed parsed, cmd_parse_type_t type, node_type_t node)
/*------------------------------------------------------------------------------
* Phase 1b of command parsing
*
- * Tokeniser found at least one of:
+ * tokenizer found at least one of:
*
* - in pipe token
* - out pipe token
@@ -2967,6 +3410,8 @@ cmd_parse_phase_one(cmd_parsed parsed, cmd_parse_type_t type, node_type_t node)
*
* Update the "parts" and the relevant first/num values.
*
+ * Note that the number of tokens (nt) *excludes* any comment token.
+ *
* Returns: CMD_SUCCESS -- all is well
* CMD_ERR_PARSING -- parsing error -- malformed or misplaced pipe
* -- malformed quotes/escapes
@@ -2984,11 +3429,24 @@ cmd_parse_phase_one_b(cmd_parsed parsed, uint nt)
n = 0 ; /* no tokens in current part */
pn = NULL ; /* no current part */
+ /* Walk the tokens, establishing the start and end of:
+ *
+ * - command part )
+ * - in pipe part ) exclusive
+ * - out shell part )
+ * - out pipe part which may only follow command or in pipe
+ *
+ *
+ */
for (i = 0 ; i < nt ; ++i)
{
t = cmd_token_get(parsed->tokens, i) ;
- if ((t->type & cmd_tok_simple) != 0)
+ /* Simple tokens can appear anywhere.
+ *
+ * If this is the first token, start a cmd_part_command.
+ */
+ if ((t->type & cmd_tok_simple) != 0)
{
if (parts == cmd_parts_none)
{
@@ -2997,42 +3455,80 @@ cmd_parse_phase_one_b(cmd_parsed parsed, uint nt)
pn = &parsed->num_command ;
n = 0 ;
} ;
- } ;
+ }
- if ((t->type & cmd_tok_in_pipe) != 0)
+ /* '<' types of token can appear anywhere, except in a cmd_part_command.
+ *
+ * If this is the first token, start a cmd_part_in_pipe.
+ */
+ else if ((t->type & cmd_tok_in_pipe) != 0)
{
- if (parts != cmd_parts_none)
- return cmd_parse_error(parsed, t, 0, "unexpected 'pipe in'") ;
+ if ((parts & (cmd_part_command | cmd_parts_pipe)) == cmd_part_command)
+ return cmd_set_parse_error(parsed, t, 0, "unexpected 'pipe in'") ;
- ret = cmd_parse_in_pipe(parsed, t) ;
- if (ret != CMD_SUCCESS)
- return ret ;
+ if (parts == cmd_parts_none)
+ {
+ ret = cmd_parse_in_pipe(parsed, t) ;
+ if (ret != CMD_SUCCESS)
+ return ret ;
- parts = cmd_part_in_pipe ;
- parsed->first_in_pipe = i ;
- pn = &parsed->num_in_pipe ;
- n = 0 ;
- } ;
+ parts = cmd_part_in_pipe ;
+ parsed->first_in_pipe = i ;
+ pn = &parsed->num_in_pipe ;
+ n = 0 ;
+ }
+ }
- if ((t->type & cmd_tok_out_pipe) != 0)
+ /* '|' tokens can appear anywhere.
+ *
+ * If this is the first token, start a cmd_part_out_pipe.
+ */
+ else if ((t->type & cmd_tok_out_shell) != 0)
{
- if ((parts == cmd_parts_none) || ((parts & cmd_part_out_pipe) != 0))
- return cmd_parse_error(parsed, t, 0, "unexpected 'pipe out'") ;
+ if ((parts & (cmd_part_command | cmd_parts_pipe)) == cmd_part_command)
+ return cmd_set_parse_error(parsed, t, 0, "unexpected 'shell pipe'") ;
- ret = cmd_parse_out_pipe(parsed, t) ;
- if (ret != CMD_SUCCESS)
- return ret ;
+ if (parts == cmd_parts_none)
+ {
+ ret = cmd_parse_out_pipe(parsed, t) ;
+ if (ret != CMD_SUCCESS)
+ return ret ;
+
+ parts = cmd_part_out_pipe ;
+ parsed->first_out_pipe = i ;
+ pn = &parsed->num_out_pipe ;
+ n = 0 ;
+ } ;
+ }
+
+ /* '>' types of token can appear anywhere, except on an empty line.
+ *
+ * If not already in cmd_part_out_pipe, start a cmd_part_out_pipe.
+ */
+ else if ((t->type & cmd_tok_out_pipe) != 0)
+ {
+ if (parts == cmd_parts_none)
+ return cmd_set_parse_error(parsed, t, 0, "unexpected 'pipe out'") ;
- *pn = n ; /* set number of in-pipe/cmd */
+ if ((parts & cmd_part_out_pipe) == 0)
+ {
+ ret = cmd_parse_out_pipe(parsed, t) ;
+ if (ret != CMD_SUCCESS)
+ return ret ;
+
+ *pn = n ; /* set number of in-pipe/cmd */
- parts |= cmd_part_out_pipe ;
- parsed->first_out_pipe = i ;
- pn = &parsed->num_out_pipe ;
- n = 0 ;
+ parts |= cmd_part_out_pipe ;
+ parsed->first_out_pipe = i ;
+ pn = &parsed->num_out_pipe ;
+ n = 0 ;
+ } ;
} ;
assert(parts != cmd_parts_none) ; /* dealt with all token types */
+ /* Now deal with any "incompleteness" */
+
if ((t->type & cmd_tok_incomplete) != 0)
{
ret = cmd_token_complete(parsed, t) ;
@@ -3122,7 +3618,7 @@ cmd_parse_phase_one_b(cmd_parsed parsed, uint nt)
if (msg != NULL)
{
t = cmd_token_get(parsed->tokens, i) ;
- return cmd_parse_error(parsed, t, e ? els_len_nn(t->ot) : 0, msg) ;
+ return cmd_set_parse_error(parsed, t, e ? els_len_nn(t->ot) : 0, msg) ;
} ;
} ;
@@ -3146,14 +3642,14 @@ cmd_parse_phase_one_b(cmd_parsed parsed, uint nt)
* or parsed->num_command == 0
*/
static cmd_return_code_t
-cmd_parse_phase_two(cmd_parsed parsed, cmd_parse_type_t type)
+cmd_parse_phase_two(cmd_parsed parsed, cmd_context context)
{
uint ii ;
uint match ;
/* Prepare to filter commands */
- cmd_filter_prepare(parsed, type) ;
+ cmd_filter_prepare(parsed, context) ;
match = 2 ; /* in case parsed->num_command == 0 ! */
@@ -3172,6 +3668,158 @@ cmd_parse_phase_two(cmd_parsed parsed, cmd_parse_type_t type)
return CMD_SUCCESS ;
} ;
+/*------------------------------------------------------------------------------
+ * Perform the special handling required for the command that has parsed
+ * successfully so far.
+ *
+ * This mechanism is used to deal with commands which have a context sensitive
+ * effect on parsed->nnode.
+ *
+ * Can be used to do any other semantic checks, subject to the information
+ * being available.
+ *
+ * Returns: CMD_SUCCESS -- OK
+ * CMD_ERR_PARSING -- failed some check, see parsed->emess
+ *
+ * Dealt with here are:
+ *
+ * * "exit" and "quit"
+ *
+ * The next node depends on the current node.
+ *
+ * * "end"
+ *
+ * The next node depends on the current node.
+ *
+ * * "enable"
+ *
+ * This command is really only intended for interactive use, but need to
+ * do something with it in other contexts.
+ *
+ * Can go to ENABLE_NODE directly if can_enable is set in the context.
+ *
+ * The can_enable is set when the vty starts if it does not require any
+ * authentication to enter ENABLE_NODE (eg VTY_CONFIG_READ) or because
+ * it started in ENABLE_NODE or greater (eg VTY_TERMINAL with no password
+ * and advanced mode !).
+ *
+ * Once can_enable is set it is not unset. So getting to enable once is
+ * sufficient for a given VTY.
+ *
+ * A pipe will inherit can_enable, provided that the parent is in
+ * ENABLE_NODE or better.
+ *
+ * A pipe cannot inherit can_auth_enable -- this means that a pipe
+ * can either immediately enable, or cannot enable at all.
+ *
+ * The effect of all this is that "enable" is straightforward, except for
+ * VTY_TERMINAL. For VTY_TERMINAL:
+ *
+ * - if the VTY starts in any node >= ENABLE_NODE, then can_enable
+ * is set from the beginning !
+ *
+ * If has ever reached ENABLE_NODE, then can_enable will be set.
+ *
+ * - otherwise: when enable command is seen, must authenticate.
+ *
+ * - if there is an enable password, then must get and accept the
+ * password, which can only happen at vin_depth == vout_depth == 0
+ * -- see vty_cmd_can_auth_enable().
+ *
+ * - if there is no enable password, then is implicitly authenticated
+ * if is in VIEW_NODE.
+ *
+ * Note that will not accept enable with no password if is in
+ * RESTRICTED_NODE. Can only be in RESTRICTED_NODE if started with
+ * no password, but host.restricted_mode is set. Doesn't seem much
+ * point having a restricted_mode if you can go straight to
+ * ENABLE_NODE just because a password has not been set !
+ */
+static cmd_return_code_t
+cmd_parse_specials(cmd_parsed parsed, cmd_context context)
+{
+ cmd_return_code_t ret ;
+
+ ret = CMD_SUCCESS ;
+
+ switch (parsed->cmd->attr & CMD_ATTR_MASK)
+ {
+ case cmd_sp_simple:
+ zabort("invalid cmd_sp_simple") ;
+ break ;
+
+ case cmd_sp_end:
+ parsed->nnode = cmd_node_end_to(parsed->cnode) ;
+ break ;
+
+ case cmd_sp_exit: /* NULL_NODE <=> CMD_EOF */
+ parsed->nnode = cmd_node_exit_to(parsed->cnode) ;
+ break ;
+
+ case cmd_sp_enable:
+ parsed->nnode = cmd_auth_specials(context, ENABLE_NODE) ;
+ break ;
+
+ case cmd_sp_configure:
+ parsed->nnode = cmd_auth_specials(context, CONFIG_NODE) ;
+ break ;
+
+ default:
+ zabort("unknown cmd_sp_xxx") ;
+ break ;
+ } ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Deal with commands which may require AUTH_ENABLE_NODE authentication.
+ *
+ * The rule is that the parser must set what node a given command *will* change
+ * to iff it succeeds.
+ *
+ * So if we can go directly to the target node, must establish that now.
+ *
+ * If cannot go to directly, we set that should go to AUTH_ENABLE_NODE. That
+ * may fail if not in a suitable state to do that -- and issue suitable
+ * message.
+ */
+static node_type_t
+cmd_auth_specials(cmd_context context, node_type_t target)
+{
+ if (!context->can_enable)
+ {
+ /* Can enable if is already ENABLE_NODE or better (this should
+ * not be required -- but does no harm).
+ *
+ * If we are allowed to authenticate, then the authentication
+ * is trivial if there is no password set and we are in
+ * VIEW_NODE !
+ *
+ * Note that if we are in RESTRICTED_NODE, then must authenticate,
+ * which will later be refused if no password is set at the time !
+ */
+ if (context->node >= ENABLE_NODE)
+ context->can_enable = true ;
+ else if (context->can_auth_enable)
+ {
+ /* Can do a*/
+ bool no_pw ;
+
+ VTY_LOCK() ;
+ no_pw = (host.enable == NULL) ;
+ VTY_UNLOCK() ;
+
+ context->can_enable = no_pw && (context->node == VIEW_NODE) ;
+ } ;
+ } ;
+
+ context->onode = context->node ; /* see vty_cmd_auth() */
+ context->tnode = target ; /* see vty_cmd_auth() */
+
+ return context->can_enable ? target : AUTH_ENABLE_NODE ;
+} ;
+
#if 0
/*------------------------------------------------------------------------------
* If src matches dst return dst string, otherwise return NULL
@@ -3321,9 +3969,9 @@ cmd_describe_command (const char* line, node_type_t node,
cmd_parsed parsed ;
cmd_token_type_t tok_total ;
- /* Set up a parser object and tokenise the command line */
+ /* Set up a parser object and tokenize the command line */
parsed = cmd_parse_init_new(&parsed_s) ;
- tok_total = cmd_tokenise(parsed, line, node) ;
+ tok_total = cmd_tokenize(parsed, line, node) ;
@@ -3611,14 +4259,21 @@ cmd_help_preflight(cmd_parsed parsed)
*
*/
extern cmd_return_code_t
-cmd_completion(cmd_parsed parsed, node_type_t node)
+cmd_completion(cmd_parsed parsed, cmd_context context)
{
cmd_return_code_t ret ;
/* Parse the line -- allow completion, allow do, allow backing up the tree,
* but do not parse for execution.
*/
- ret = cmd_parse_command(parsed, node, cmd_parse_standard) ;
+ context->can_enable = true ;
+ context->full_lex = true ;
+ context->parse_execution = false ;
+ context->parse_no_do = false ;
+ context->parse_no_tree = false ;
+ context->parse_strict = false ;
+
+ ret = cmd_parse_command(parsed, context) ;
if (ret == CMD_ERR_PARSING)
return ret ; /* nothing more possible */
@@ -3674,9 +4329,6 @@ cmd_completion(cmd_parsed parsed, node_type_t node)
return CMD_SUCCESS ;
} ;
-
-
-
/*------------------------------------------------------------------------------
* How to insert a newly completed keyword ?
*
@@ -3714,7 +4366,7 @@ cmd_completion(cmd_parsed parsed, node_type_t node)
* Returns: n = number of characters to move the cursor before
* starting to replace characters (may be -ve)
* *rep = number of characters to replace
- * *ins = number of spaces to insert
+ * *ins = number of spaces to insert : 0..2
* *mov = number of spaces to move afterwards (may be -ve)
*/
extern void
diff --git a/lib/command_parse.h b/lib/command_parse.h
index 789fe601..a1e801ce 100644
--- a/lib/command_parse.h
+++ b/lib/command_parse.h
@@ -29,8 +29,11 @@
#include "command_common.h"
#include "vector.h"
+#include "vio_fifo.h"
#include "qstring.h"
+#include "qpath.h"
#include "elstring.h"
+#include "memory.h"
#if 0
/*==============================================================================
@@ -449,19 +452,6 @@ item_best_match(cmd_item_type_t it)
*
*/
-/* Command parsing options */
-enum cmd_parse_type /* bit significant */
-{
- cmd_parse_standard = 0, /* accept short command parts */
-
- cmd_parse_strict = BIT(0),
- cmd_parse_no_do = BIT(1),
- cmd_parse_no_tree = BIT(2),
-
- cmd_parse_execution = BIT(3), /* wish to execute command */
-} ;
-typedef enum cmd_parse_type cmd_parse_type_t ;
-
/* Pipe types */
enum cmd_pipe_type /* bit significant */
{
@@ -476,6 +466,9 @@ enum cmd_pipe_type /* bit significant */
/* For out file pipes */
cmd_pipe_append = BIT(4), /* >> */
+
+ /* For out shell pipes */
+ cmd_pipe_shell_only = BIT(4), /* | at start of line */
} ;
typedef enum cmd_pipe_type cmd_pipe_type_t ;
@@ -506,15 +499,17 @@ enum cmd_token_type /* *bit* significant */
cmd_tok_simple = BIT( 0),
- cmd_tok_sq = BIT( 8),
- cmd_tok_dq = BIT( 9), /* '\\' within "..." are not
+ cmd_tok_sq = BIT( 4),
+ cmd_tok_dq = BIT( 5), /* '\\' within "..." are not
registered separately. */
- cmd_tok_esc = BIT(10),
+ cmd_tok_esc = BIT( 6),
cmd_tok_incomplete = (cmd_tok_sq | cmd_tok_dq | cmd_tok_esc),
- cmd_tok_in_pipe = BIT(12), /* token starting '<' */
- cmd_tok_out_pipe = BIT(13), /* token starting '>' */
+ cmd_tok_in_pipe = BIT( 8), /* token starting '<' */
+ cmd_tok_out_pipe = BIT( 9), /* token starting '>' */
+ cmd_tok_out_shell = BIT(10), /* token starting '|' */
+
cmd_tok_comment = BIT(14), /* token starting '!' or '#" */
} ;
typedef enum cmd_token_type cmd_token_type_t ;
@@ -522,11 +517,11 @@ typedef enum cmd_token_type cmd_token_type_t ;
struct cmd_token
{
cmd_token_type_t type ;
- qstring_t qs ; /* token string */
+ usize tp ; /* location of token in the line */
bool term ; /* token has been '\0' terminated */
- usize tp ; /* location of token in the line */
+ qstring_t qs ; /* token string */
elstring_t ot ; /* original token in the line */
} ;
@@ -587,9 +582,9 @@ struct cmd_parsed
usize elen ; /* effective length (less trailing spaces) */
usize tsp ; /* number of trailing spaces */
- uint num_tokens ; /* number of tokens parsed */
+ uint num_tokens ; /* number of tokens parsed */
- token_vector_t tokens ; /* vector of token objects */
+ token_vector tokens ; /* vector of token objects */
/* NB: the following are significant only if there is a command part
* or a do part
@@ -597,13 +592,14 @@ struct cmd_parsed
struct cmd_command *cmd ; /* NULL if empty command
or fails to parse */
node_type_t cnode ; /* node command is in */
+ node_type_t nnode ; /* node to set if command succeeds */
- arg_vector_t args ; /* vector of arguments -- embedded */
+ arg_vector args ; /* vector of arguments */
/* NB: the following are significant only if an error is returned */
- const char* emess ; /* parse error */
- usize eloc ; /* error location */
+ qstring emess ; /* parse error */
+ ssize eloc ; /* error location */
/* NB: the following are significant only if respective part is
* present.
@@ -634,8 +630,8 @@ struct cmd_parsed
/* The following are used while filtering commands */
- vector_t cmd_v ; /* working vector -- embedded */
- vector_t item_v ; /* working vector -- embedded */
+ vector cmd_v ; /* working vector */
+ vector item_v ; /* working vector */
match_strength_t strongest ;
match_type_t best_complete ;
@@ -647,20 +643,65 @@ struct cmd_parsed
enum
{
CMD_PARSED_INIT_ALL_ZEROS = (cmd_pipe_none == 0)
- && TOKEN_VECTOR_INIT_ALL_ZEROS
- && ARG_VECTOR_INIT_ALL_ZEROS
} ;
typedef struct cmd_parsed cmd_parsed_t[1] ;
typedef struct cmd_parsed* cmd_parsed ;
-/* Command dispatch options */
-enum cmd_queue_b
+/*==============================================================================
+ * This is the stuff that defines the context in which commands are parsed,
+ * and then executed.
+ *
+ * The context lives in the cmd_exec. The CLI has a copy of the context,
+ * which it uses for the prompt and for command line help handling.
+ *
+ * Each time a new vin is pushed, the current context is copied to the current
+ * TOS (before the push).
+ *
+ * Easch time a vin is popped, the context is restored.
+ *
+ * Each time a vin or vout is pushed or popped the context the cmd_exec
+ * out_suppress and reflect flags must be updated.
+ */
+struct cmd_context
{
- cmd_no_queue = false,
- cmd_queue_it = true,
+ /* The node between commands. */
+
+ node_type_t node ; /* updated on CMD_SUCCESS */
+
+ /* These properties affect the parsing of command lines. */
+
+ bool full_lex ; /* as required */
+
+ bool parse_execution ; /* parsing to execute */
+
+ bool parse_only ; /* do not execute */
+
+ bool parse_strict ; /* no incomplete keywords */
+ bool parse_no_do ; /* no 'do' commands */
+ bool parse_no_tree ; /* no tree walking */
+
+ bool can_auth_enable ; /* if required */
+ bool can_enable ; /* no (further) password needed */
+
+ /* These properties affect the execution of parsed commands. */
+
+ bool reflect_enabled ; /* per the pipe */
+
+ /* Special for AUTH_ENABLE_NODE -- going from/to */
+
+ node_type_t onode ; /* VIEW_NODE or RESTRICTED_NODE */
+ node_type_t tnode ; /* ENABLE_NODE or CONFIG_NODE */
+
+ /* The current directories. */
+
+ qpath dir_cd ; /* chdir directory */
+ qpath dir_home ; /* "~/" directory */
+ qpath dir_here ; /* "~./" directory */
} ;
-typedef enum cmd_queue_b cmd_queue_b ;
+
+typedef struct cmd_context cmd_context_t[1] ;
+typedef struct cmd_context* cmd_context ;
/*==============================================================================
* Prototypes
@@ -668,21 +709,24 @@ typedef enum cmd_queue_b cmd_queue_b ;
extern void cmd_compile(cmd_command cmd) ;
extern void cmd_compile_check(cmd_command cmd) ;
-extern cmd_parsed cmd_parsed_init_new(cmd_parsed parsed) ;
-extern cmd_parsed cmd_parsed_reset(cmd_parsed parsed,
- free_keep_b free_structure) ;
-extern bool cmd_is_empty(elstring line) ;
+extern cmd_parsed cmd_parsed_new(void) ;
+extern cmd_parsed cmd_parsed_free(cmd_parsed parsed) ;
+
+extern bool cmd_is_empty(qstring line) ;
+extern void cmd_tokenize(cmd_parsed parsed, qstring line, bool full_lex) ;
+extern qstring cmd_tokens_concat(cmd_parsed parsed, uint ti, uint nt) ;
extern cmd_return_code_t cmd_parse_command(cmd_parsed parsed,
- node_type_t node, cmd_parse_type_t type) ;
+ cmd_context context) ;
extern bool cmd_token_position(cmd_parsed parsed, qstring line) ;
extern const char* cmd_help_preflight(cmd_parsed parsed) ;
-extern cmd_return_code_t cmd_completion(cmd_parsed parsed, node_type_t node) ;
+extern cmd_return_code_t cmd_completion(cmd_parsed parsed, cmd_context context);
extern void cmd_complete_keyword(cmd_parsed parsed,
int* pre, int* rep, int* ins, int* mov) ;
+extern void cmd_get_parse_error(vio_fifo ebuf, cmd_parsed parsed, uint indent) ;
@@ -697,7 +741,6 @@ extern vector cmd_complete_command (vector, int, int *status);
-extern void cmd_tokenise(cmd_parsed parsed, qstring line) ;
//extern cmd_return_code_t cmd_parse_error(cmd_parsed parsed, cmd_token t,
// usize off, const char* mess) ;
@@ -722,84 +765,8 @@ extern void cmd_tokenise(cmd_parsed parsed, qstring line) ;
//extern bool cmd_range_match (const char *range, const char *str) ;
/*==============================================================================
- * Inline Functions
- */
-
-Private cmd_token cmd_token_new(void) ;
-
-/*------------------------------------------------------------------------------
- * Get pointer to token value.
- *
- * Returns NULL if token NULL or no string.
- */
-Inline char*
-cmd_token_value(cmd_token t)
-{
- return (t == NULL) ? NULL : qs_char_nn(t->qs) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Get string value of given token.
- *
- * Returns an empty (not NULL) string if token NULL or no string.
- */
-Inline char*
-cmd_token_make_string(cmd_token t)
-{
- if (t->term)
- return qs_char_nn(t->qs) ;
- else
- {
- t->term = true ;
- return qs_make_string(t->qs) ;
- }
-} ;
-
-/*------------------------------------------------------------------------------
- * Get i'th token from given token vector -- zero origin
- */
-Inline cmd_token
-cmd_token_get(token_vector tv, vector_index_t i)
-{
- return vector_get_item(tv->body, i) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Set i'th token from given token vector -- zero origin
+ * Inlines
*/
-Inline void
-cmd_token_set(token_vector tv, vector_index_t i,
- cmd_token_type_t type, const char* p, usize len, usize tp)
-{
- cmd_token t = cmd_token_get(tv, i) ;
-
- if (t == NULL)
- vector_set_item(tv->body, i, (t = cmd_token_new())) ;
-
- t->type = type ;
- t->term = false ;
- t->tp = tp ;
- qs_set_alias_n(t->qs, p, len) ;
- qs_els_copy_nn(t->ot, t->qs) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Empty the body of the arg_vector object in cmd_parsed.
- */
-Inline void
-cmd_arg_vector_empty(cmd_parsed parsed)
-{
- vector_set_length(parsed->args->body, 0) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Empty the body of the arg_vector object in cmd_parsed.
- */
-Inline void
-cmd_arg_vector_push(cmd_parsed parsed, char* arg)
-{
- vector_push_item(parsed->args->body, arg) ;
-} ;
/*------------------------------------------------------------------------------
* Get the body of the argument vector.
diff --git a/lib/command_queue.c b/lib/command_queue.c
index 0a09d950..f83e3291 100644
--- a/lib/command_queue.c
+++ b/lib/command_queue.c
@@ -43,7 +43,7 @@
* runs until the vin/vout stacks return to 0 -- the VTY_TERMINAL and
* VTY_SHELL_SERVER.
*
- * There are further issues:
+ * There are further issues: fix to current state TODO
*
* 1) in the qpthreads world, commands are parsed in the CLI thread, but most
* are executed in the Routing thread. So parsed commands are passed, by
@@ -53,7 +53,7 @@
* exit if a command line cannot be delivered immediately, and will be
* returned to later.
*
- * 3) while a VTY is in the command loop it is marked vio->cmd_running.
+ * 3) while a VTY is in the command loop it is marked vio->cmd_running. TODO
*
* While that is true, the vty and the vty->exec are in the hands
* of the command loop. The vty->vio is always accessed under VTY_LOCK().
@@ -72,7 +72,7 @@
* running of the CLI thread.
*
* To close a VTY must (eventually) arrange for vio->cmd_running to be cleared.
- * While a vty is vio->cmd_running, it must be in one of these states:
+ * While a vty is vio->cmd_running, it must be in one of these states: TODO
*
* - on the vty_cli_nexus queue (or the combined queue)
* - executing in the vty_cli_nexus (or the single "both" nexus)
@@ -111,69 +111,54 @@
* Prototypes
*/
static void cq_enqueue(struct vty *vty, qpn_nexus dst, cmd_exec_state_t state,
- cmd_return_code_t ret) ;
+ cmd_return_code_t ret) ;
static void cq_action(mqueue_block mqb, mqb_flag_t flag);
static int cq_thread(struct thread* thread) ;
-static void cq_process(vty vty) ;
+static void cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret) ;
/*------------------------------------------------------------------------------
- * Enqueue vty for parse and execution of a command.
+ * Enqueue vty to enter the command loop -- must be exec_null
*
- * Sets the vio->cmd_running flag, which will be cleared only when the
- * command is completed (including any nested pipes etc.) or when the vty
- * is blocked on input or it is revoked.
- *
- * Note that from now on, exec->ret reflects the state of the return
- * code when the vty was last enqueued.
- *
- * While vio_cmd_running is set, the CLI side MUST NOT fiddle with the main
- * part of the VTY or with the vty->exec state.
+ * Expects the vty start up process to have output some cheerful messages,
+ * which is treated as a dummy "login" command. So the loop is entered at
+ * "exec_done_cmd", which will push out the output, deal with the return code,
+ * and loop round to fetch the first command line (if required).
*/
extern void
-cq_dispatch(vty vty, cmd_do_t to_do, qstring line)
+cq_loop_enter(vty vty, cmd_return_code_t ret)
{
VTY_ASSERT_CLI_THREAD() ;
- vty->exec->to_do = to_do ;
- vty->exec->line = line ;
- vty->vio->cmd_running = true ;
- cq_enqueue(vty, vty_cli_nexus,
- (to_do == cmd_do_command) ? exec_parse
- : exec_special, CMD_SUCCESS) ;
+ assert(vty->exec->state == exec_null) ;
+
+ cq_enqueue(vty, vty_cli_nexus, exec_done_cmd, ret) ;
} ;
/*------------------------------------------------------------------------------
- * Enqueue vty for fetching a command line from an in_pipe which has just
- * received more input.
- *
- * Sets the vio->cmd_running flag, which will be cleared only when the
- * command is completed (including any nested pipes etc.) or when the vty
- * is blocked on input or it is revoked.
- *
- * While vio_cmd_running is set, the CLI side MUST NOT fiddle with the main
- * part of the VTY or with the vty->exec state.
+ * Continue (resume) at hiatus -- must be exec_hiatus
*/
extern void
-cq_go_fetch(vty vty)
+cq_continue(vty vty, cmd_return_code_t ret)
{
VTY_ASSERT_CLI_THREAD() ;
- vty->vio->cmd_running = true ;
- cq_enqueue(vty, vty_cli_nexus, exec_fetch, CMD_SUCCESS) ;
+ assert(vty->exec->state == exec_hiatus) ;
+
+ cq_enqueue(vty, vty_cli_nexus, exec_hiatus, ret) ;
} ;
/*------------------------------------------------------------------------------
* Enqueue vty for execution in given nexus or issue thread event.
*
- * Note that preserves the return code state.
+ * Will execute in the current exec->state, passing in the given return
+ * code.
*/
static void
cq_enqueue(vty vty, qpn_nexus dst, cmd_exec_state_t state,
- cmd_return_code_t ret)
+ cmd_return_code_t ret)
{
cmd_exec exec = vty->exec ;
- assert(vty->vio->cmd_running) ;
assert((dst == vty_cli_nexus) || (dst == vty_cmd_nexus)) ;
exec->locus = dst ;
@@ -200,7 +185,7 @@ cq_enqueue(vty vty, qpn_nexus dst, cmd_exec_state_t state,
/*------------------------------------------------------------------------------
* Deal with command message -- in the qpthreads world.
*
- * Note that if the command is revoked....
+ * Note that if the command is revoked.... TODO
*/
static void
cq_action(mqueue_block mqb, mqb_flag_t flag)
@@ -212,11 +197,12 @@ cq_action(mqueue_block mqb, mqb_flag_t flag)
vty = mqb_get_arg0(mqb);
assert(vty->exec->cq.mqb == mqb) ;
- assert(vty->vio->cmd_running) ;
if (flag == mqb_action)
- return cq_process(vty) ; /* do not touch vty on way out */
+ return cq_process(vty, vty->exec->state, vty->exec->ret) ;
+ /* do not touch vty on way out */
+ /* Revoke action. */
mqb_free(mqb) ;
vty->exec->cq.mqb = NULL ;
} ;
@@ -224,7 +210,7 @@ cq_action(mqueue_block mqb, mqb_flag_t flag)
/*------------------------------------------------------------------------------
* Deal with command message -- in the legacy threads world.
*
- * Note that if the command is revoked....
+ * Note that if the command is revoked.... TODO
*/
static int
cq_thread(struct thread* thread)
@@ -232,63 +218,64 @@ cq_thread(struct thread* thread)
vty vty = THREAD_ARG(thread) ;
assert(vty->exec->cq.thread == thread) ;
- assert(vty->vio->cmd_running) ;
vty->exec->cq.thread = NULL ;
- cq_process(vty) ; /* do not touch vty on way out */
-
+ cq_process(vty, vty->exec->state, vty->exec->ret) ;
+ /* do not touch vty on way out */
return 0 ;
} ;
/*------------------------------------------------------------------------------
* Process command(s) queued from VTY_TERMINAL or from VTY_SHELL_SERVER.
*
- * To get into the process loop, or to get back to it when more input has
- * arrived a message is sent:
+ * This is essentially a coroutine, where the state is in the vty, noting that:
+ *
+ * vty->exec->state is the "return address"
+ * vty->exec->ret is the "return code"
+ *
+ * The command loop runs at the mqueue level in the qpnexus world, or is
+ * driven by "events" in the legacy threads world. The two ways into the
+ * loop are:
*
- * cli -> cli -- exec_parse -- when a command line has been gathered
- * from input and is ready to be processed.
+ * cq_loop_enter() -- called once and once only to start the loop.
*
- * cli -> cli -- exec_fetch -- when was waiting for more input from an
- * in_pipe of some sort.
+ * cq_continue() -- called when some I/O process has completed and
+ * the loop is in hiatus, waiting for I/O.
*
- * In the single-pthread world, where the vty_cli_nexus is the same as the
- * vty_cmd_nexus (either because not running qpthreaded, or because are
- * running in the legacy threads world), things are reasonably straightforward.
- * The process runs to completion or until an in_pipe would block, and the
- * above are the only messages in the system.
+ * The one way out of the loop is when it hits a hiatus, which is triggered
+ * by any operation not returning CMD_SUCCESS. In hiatus, vty_cmd_hiatus()
+ * is called and given the current return code to deal with. It will
+ * return:
*
- * In the multi-pthread world, things are more complicated... The above
- * messages are sent, and in addition the following are sent back and forth to
- * transfer between threads in the following states:
+ * CMD_SUCCESS => can proceed to fetch the next command
+ * CMD_WAITING => must leave the command loop, waiting for I/O
+ * CMD_EOF => close the vty and leave command loop
+ * CMD_IO_ERROR => loop back and deal with the error
*
- * cmd -> cli -- exec_open_pipes
- * cli -> cmd -- exec_execute
- * cmd -> cli -- exec_complete
+ * The cq_loop_enter() and cq_continue() operations send a message (to the
+ * cli thread, in the mult-pthreaded world), whose action routine is to call
+ * cq_process().
*
- * Note that this means that in the multi-pthread world, only one sort of
- * message is sent to the vty_cmd_nexus.
+ * In the multi-pthreaded world, opening and closing files MUST be done in the
+ * cli thread. Most commands MUST be executed in the cmd thread. So the
+ * command loop is passed between the threads in a number of places. (To keep
+ * life simple, switches back to the cli thread if ends up waiting for I/O.)
*
* The vty_io_basic stuff allows the multi-pthread vty_cmd_nexus to set read
* and/or write ready state -- which may generate messages to the vty_cli_nexus.
- *
- * NB: if blocks in exec_fetch, then vty_cmd_fetch_line() will have cleared
- * vio->cmd_running -- so on return from cq_process the vty MAY HAVE BEEN
- * DELETED.
*/
static void
-cq_process(vty vty)
+cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret)
{
- cmd_exec exec = vty->exec ;
- cmd_parsed parsed = exec->parsed ;
- cmd_return_code_t ret = exec->ret ;
+ cmd_exec exec = vty->exec ;
+ cmd_parsed parsed = exec->parsed ;
/* Have switch wrapped in a while(1) so that can change state by setting
* exec->state and doing "continue".
*
- * Breaking out of the switch forces the exec state to exec_complete,
- * with the current ret, in the CLI thread.
+ * Breaking out of the switch forces the exec state to exec_hiatus,
+ * with the current ret (which will switch to the CLI thread).
*
* The exec locus is either vty_cli_nexus or vty_cmd_nexus. If these
* are equal (including both NULL, in the legacy threads world), then is
@@ -296,79 +283,44 @@ cq_process(vty vty)
*/
while (1)
{
- switch(exec->state)
+ switch(state)
{
/*--------------------------------------------------------------------
* Should not get here in exec_null state.
*/
case exec_null:
- zabort("exec->state == exec_null") ;
+ zabort("exec state == exec_null") ;
break ;
/*--------------------------------------------------------------------
- * Deal with the "spacial" commands
- */
- case exec_special:
- ret = vty_cmd_special(vty) ;
- if (ret != CMD_SUCCESS)
- break ;
-
- exec->state = exec_fetch ;
- /* continue by falling through */
-
- /*--------------------------------------------------------------------
* Need another command to execute => in_pipe !
*
- * Note that at vin_depth == 0 this will return CMD_EOF, and will
- * drop out of the loop exec_complete.
- *
- * Will also receive CMD_EOF if the VTY has been closed.
- *
- * If multi-threaded: may be in either thread:
- *
- * vty_cmd_fetch_line() may set read and/or write ready -- so in
- * vty_cmd_nexus may generate message to vty_cli_nexus.
+ * If multi-threaded: may be in either thread. If vty_cmd_fetch_line()
+ * cannot, for any reason, return a command line, will return something
+ * other than CMD_SUCCESS -- which enters the exec_hiatus.
*/
case exec_fetch:
ret = vty_cmd_fetch_line(vty) ;
if (ret != CMD_SUCCESS)
- {
- /* If is CMD_WAITING, then the vty_cmd_fetch_line() will
- * have prepared for command to be re-queued when there is more
- * to be read. NB: vio->cmd_running has been cleared, so
- * vty MAY HAVE BEEN DELETED !
- *
- * If is CMD_EOF then is "closing" or reached EOF on top-most
- * pipe.
- */
- if (ret == CMD_WAITING)
- return ; /* <<<< DONE, pro tem */
-
- break ; /* => exec_complete */
- } ;
+ break ;
- if (exec->to_do != cmd_do_command)
+ if (exec->action->to_do != cmd_do_command)
{
- exec->state = exec_special ;
+ state = exec_special ;
continue ;
} ;
- exec->state = exec_parse ;
- /* continue by falling through */
+ fall_through ; /* with ret == CMD_SUCCESS */
/*--------------------------------------------------------------------
* Parse command in hand
*
- * If multi-threaded: may be in either thread:
- *
- * vty_cmd_reflect_line() may set read and/or write ready -- so in
- * vty_cmd_nexus may generate message to vty_cli_nexus.
+ * If multi-threaded: may be in either thread.
*/
- case exec_parse:
- cmd_tokenise(parsed, exec->line) ;
- ret = cmd_parse_command(parsed, vty->node,
- exec->parse_type | cmd_parse_execution) ;
+ cmd_tokenize(parsed, exec->action->line, exec->context->full_lex) ;
+
+ ret = cmd_parse_command(parsed, exec->context) ;
if (ret != CMD_SUCCESS)
{
if (ret != CMD_EMPTY)
@@ -381,15 +333,20 @@ cq_process(vty vty)
*/
ret = CMD_SUCCESS ;
- exec->state = exec_success ;
+ state = exec_done_cmd ;
continue ;
} ;
- /* reflection now -- output always succeeds */
- if (exec->reflect_enabled)
- vty_cmd_reflect_line(vty) ;
+ /* reflection now */
+ if (exec->reflect)
+ {
+ ret = vty_cmd_reflect_line(vty) ;
+
+ if ((ret != CMD_SUCCESS) && (ret != CMD_WAITING))
+ break ;
+ } ;
- /* continue by falling through */
+ fall_through ;
/*--------------------------------------------------------------------
* Pipe work if any
@@ -402,8 +359,6 @@ cq_process(vty vty)
case exec_open_pipes:
if ((parsed->parts & cmd_parts_pipe) != 0)
{
- exec->state = exec_open_pipes ;
-
/* If running pthreaded, do open pipes in vty_cli_nexus */
if (exec->locus != vty_cli_nexus)
return cq_enqueue(vty, vty_cli_nexus, exec_open_pipes, ret) ;
@@ -414,11 +369,10 @@ cq_process(vty vty)
break ; /* quit if open fails */
} ;
- exec->state = exec_execute ;
- /* continue by falling through */
+ fall_through ; /* with ret == CMD_SUCCESS */
/*--------------------------------------------------------------------
- * Execute command in hand
+ * Execute command in hand (if any)
*
* If multi-threaded: some commands can run in either thread, most must
* run in the vty_cmd_nexus -- so may generate message to transfer to
@@ -428,8 +382,7 @@ cq_process(vty vty)
if ((parsed->parts & cmd_part_command) != 0)
{
/* If running pthreaded, do most commands in vty_cmd_nexus */
- if ((exec->locus != vty_cmd_nexus) &&
- (!cmd_is_direct(parsed)))
+ if ((exec->locus != vty_cmd_nexus) && (!cmd_is_direct(parsed)))
return cq_enqueue(vty, vty_cmd_nexus, exec_execute, ret) ;
/* Standard command handling */
@@ -449,59 +402,97 @@ cq_process(vty vty)
realtime = thread_consumed_time(&after, &before, &cputime) ;
if (realtime > CONSUMED_TIME_CHECK)
/* Warn about CPU hog that must be fixed. */
- uzlog(NULL, LOG_WARNING,
+ zlog(NULL, LOG_WARNING,
"SLOW COMMAND: command took %lums (cpu time %lums): %s",
realtime/1000, cputime/1000,
- qs_make_string(exec->line)) ;
+ qs_make_string(exec->action->line)) ;
} ;
#endif /* CONSUMED_TIME_CHECK */
-
- if (ret != CMD_SUCCESS)
- break ; /* stop */
} ;
- exec->state = exec_success ;
- /* continue by falling through */
+ fall_through ;
/*--------------------------------------------------------------------
- * Command has completed successfully -- so push the output.
- *
- * If the vout_depth > vin_depth, pops the vout's -- deals with single
- * command lines with a pipe output.
+ * Command has completed -- if successful, push output and loop back
+ * to fetch another command.
*
- * Output cannot block, so this always succeeds.
- *
- * Then loop back to fetch another command line, if can.
+ * Break if not successful, or push fails or must wait.
*
* If multi-threaded: may be in either thread:
*
* vty_cmd_success() may set write ready -- so in vty_cmd_nexus may
* generate message to vty_cli_nexus.
*/
- case exec_success:
- assert(ret == CMD_SUCCESS) ;
+ case exec_done_cmd:
+ if (ret == CMD_SUCCESS)
+ ret = vty_cmd_success(vty) ;
- vty_cmd_success(vty) ;
+ if ((ret != CMD_SUCCESS) && (ret != CMD_WAITING))
+ break ; /* stop */
- exec->state = exec_fetch ;
+ state = exec_fetch ;
continue ;
/*--------------------------------------------------------------------
- * End of the command loop !
- *
- * If multi-threaded: must return to the vty_cli_nexus.
+ * Deal with the "special" commands
+ */
+ case exec_special:
+ if (exec->locus != vty_cli_nexus)
+ return cq_enqueue(vty, vty_cli_nexus, exec_special, ret) ;
+
+ ret = vty_cmd_special(vty) ;
+ if (ret != CMD_SUCCESS)
+ break ;
+
+ state = exec_done_cmd ;
+ continue ;
+
+ /*--------------------------------------------------------------------
+ * Hiatus state -- some return code to be dealt with !
*/
- case exec_complete:
+ case exec_hiatus:
if (exec->locus != vty_cli_nexus)
- break ; /* Will send back to the cli */
+ return cq_enqueue(vty, vty_cli_nexus, exec_hiatus, ret) ;
+
+ while (1)
+ {
+ /* Let vty_cmd_hiatus() deal with the return code, and adjust
+ * the stack as required.
+ */
+ ret = vty_cmd_hiatus(vty, ret) ;
+
+ if (ret == CMD_SUCCESS)
+ break ;
- /* Now in the vty_cli_nexus */
+ if (ret == CMD_WAITING)
+ {
+ exec->state = exec_hiatus ;
+ return ; /* <<< DONE, pro tem */
+ } ;
- exec->state = exec_null ; /* all done ! */
+ if (ret == CMD_EOF)
+ {
+ exec->state = exec_stopped ;
+ vty_cmd_loop_exit(vty) ;
- vty_cmd_loop_exit(vty, ret) ; /* clears vio->cmd_running */
+ return ; /* <<< DONE, permanently */
+ } ;
- return ; /* <<<< Finally finished ! */
+ if (ret == CMD_IO_ERROR)
+ continue ;
+
+ zabort("invalid return from vty_cmd_hiatus()") ;
+ } ;
+
+ state = exec_fetch ;
+ continue ; /* can fetch, again */
+
+ /*--------------------------------------------------------------------
+ * Should not get here in exec_stopped state.
+ */
+ case exec_stopped:
+ zabort("exec state == exec_exit") ;
+ break ;
/*----------------------------------------------------------------------
* Unknown exec->state !
@@ -511,22 +502,9 @@ cq_process(vty vty)
break ;
} ;
- /* Have broken out of the switch(). This means that for good or ill,
- * the command is complete. If we are not in the vty_cli_nexus, need to
- * send back to the vty_cli_nexus for handling.
- *
- * At all times we treat CMD_EOF and CMD_SUCCESS.
- *
- * Otherwise, can continue in exec_complete state.
- */
- if (ret == CMD_EOF)
- ret = CMD_SUCCESS ;
-
- if (exec->locus != vty_cli_nexus)
- return cq_enqueue(vty, vty_cli_nexus, exec_complete, ret) ;
-
- exec->state = exec_complete ;
- } ;
+ /* Have broken out of the switch() => exec_hiatus */
+ state = exec_hiatus ;
+ } ;
} ;
/*------------------------------------------------------------------------------
@@ -537,45 +515,41 @@ cq_process(vty vty)
* See cq_process above for discussion of what messages there may be. At any
* time there is at most one message in flight.
*
- * If we find a message in flight, then we vty_cmd_loop_exit() to bring things
- * to a stop tidily.
- *
- * In the single-threaded world, expect that a command, once started will run
- * to conclusion, or until blocked on an in_pipe. So after this revoke the
- * vty should not be vio->cmd_running.
+ * In the single-threaded world (including legacy threads), messages or events
+ * are used to enter or resume the command loop. If a message or event can
+ * be revoked, then the command loop is effectively stopped.
*
- * In the multi-threaded world, this revoke will catch any vty which is on
- * either the vty_cli_nexus or vty_cmd_nexus queues, and force completion.
- * After this revoke, vio->cmd_running will be true iff the command is
- * currently being executed in the vty_cmd_nexus -- we expect that to run to
- * conclusion or block on an in_pipe, shortly, which will be collected when
- * the vty_cli_nexus message queue is next processed.
+ * In the multi-threaded world, messages are used to enter or resume the
+ * command loop, and to transfer it to and from the cli and cmd threads. If
+ * a message can be revoked, the command loop is effectively stopped.
*
* Note that the revoke does not affect any vty_cli_nexus messages associated
* with the vty_io_basic operations.
+ *
+ * Returns: true <=> have revoked a pending message/event
*/
-extern void
+extern bool
cq_revoke(vty vty)
{
int ret ;
- VTY_ASSERT_CLI_THREAD() ;
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
if (vty_nexus)
{
- ret = mqueue_revoke(vty_cmd_nexus->queue, vty, 1) ;
+ ret = mqueue_revoke(vty_cli_nexus->queue, vty, 1) ;
- if ((ret == 0) && (vty_cli_nexus != vty_cmd_nexus))
- ret = mqueue_revoke(vty_cli_nexus->queue, vty, 1) ;
+ if ((ret == 0) && vty_multi_nexus)
+ ret = mqueue_revoke(vty_cmd_nexus->queue, vty, 1) ;
}
else
ret = thread_cancel_event(vty_master, vty) ;
+ /* If revoked message/event, the command loop is stopped. */
if (ret != 0)
- {
- vty->exec->state = exec_null ;
- vty_cmd_loop_exit(vty, vty->exec->ret) ;
- } ;
+ vty->exec->state = exec_stopped ;
+
+ return (ret != 0) ;
} ;
diff --git a/lib/command_queue.h b/lib/command_queue.h
index 0966bd68..0f4863d6 100644
--- a/lib/command_queue.h
+++ b/lib/command_queue.h
@@ -26,8 +26,8 @@
#include "command_execute.h"
#include "qpnexus.h"
-extern void cq_dispatch(vty vty, cmd_do_t to_do, qstring line) ;
-extern void cq_go_fetch(vty vty) ;
-extern void cq_revoke(vty vty) ;
+extern void cq_loop_enter(vty vty, cmd_return_code_t ret) ;
+extern void cq_continue(vty vty, cmd_return_code_t ret) ;
+extern bool cq_revoke(vty vty) ;
#endif /* COMMAND_QUEUE_H_ */
diff --git a/lib/if.c b/lib/if.c
index 66a8bfef..21427e71 100644
--- a/lib/if.c
+++ b/lib/if.c
@@ -532,11 +532,12 @@ if_sunwzebra_get (const char *name, size_t nlen)
}
#endif /* SUNOS_5 */
-DEFUN (interface,
- interface_cmd,
- "interface IFNAME",
- "Select an interface to configure\n"
- "Interface's name\n")
+DEFUN_ATTR (interface,
+ interface_cmd,
+ "interface IFNAME",
+ "Select an interface to configure\n"
+ "Interface's name\n",
+ CMD_ATTR_NODE + INTERFACE_NODE)
{
struct interface *ifp;
size_t sl;
diff --git a/lib/keychain.c b/lib/keychain.c
index 57ccf7cc..a7929751 100644
--- a/lib/keychain.c
+++ b/lib/keychain.c
@@ -227,12 +227,13 @@ key_delete (struct keychain *keychain, struct key *key)
key_free (key);
}
-DEFUN (key_chain,
- key_chain_cmd,
- "key chain WORD",
- "Authentication key management\n"
- "Key-chain management\n"
- "Key-chain name\n")
+DEFUN_ATTR (key_chain,
+ key_chain_cmd,
+ "key chain WORD",
+ "Authentication key management\n"
+ "Key-chain management\n"
+ "Key-chain name\n",
+ CMD_ATTR_NODE + KEYCHAIN_NODE)
{
struct keychain *keychain;
@@ -266,11 +267,12 @@ DEFUN (no_key_chain,
return CMD_SUCCESS;
}
-DEFUN (key,
- key_cmd,
- "key <0-2147483647>",
- "Configure a key\n"
- "Key identifier number\n")
+DEFUN_ATTR (key,
+ key_cmd,
+ "key <0-2147483647>",
+ "Configure a key\n"
+ "Key identifier number\n",
+ CMD_ATTR_NODE + KEYCHAIN_KEY_NODE)
{
struct keychain *keychain;
struct key *key;
@@ -286,12 +288,13 @@ DEFUN (key,
return CMD_SUCCESS;
}
-DEFUN (no_key,
- no_key_cmd,
- "no key <0-2147483647>",
- NO_STR
- "Delete a key\n"
- "Key identifier number\n")
+DEFUN_ATTR (no_key,
+ no_key_cmd,
+ "no key <0-2147483647>",
+ NO_STR
+ "Delete a key\n"
+ "Key identifier number\n",
+ CMD_ATTR_NODE + KEYCHAIN_NODE)
{
struct keychain *keychain;
struct key *key;
diff --git a/lib/keystroke.c b/lib/keystroke.c
index 13b79e39..8325b8f8 100644
--- a/lib/keystroke.c
+++ b/lib/keystroke.c
@@ -262,13 +262,15 @@ enum stream_state
kst_iac_option, /* waiting for option (just seen IAC X) */
kst_iac_sub, /* waiting for IAC SE */
} ;
+typedef enum stream_state stream_state_t ;
struct keystroke_state
{
- enum stream_state state ;
- unsigned len ;
- uint8_t raw[keystroke_max_len] ;
+ stream_state_t state ;
+ uint len ;
+ uint8_t raw[keystroke_max_len] ;
} ;
+typedef struct keystroke_state keystroke_state_t ;
struct keystroke_stream
{
@@ -280,14 +282,15 @@ struct keystroke_stream
uint8_t CSI ; /* CSI character value (if any) */
bool eof_met ; /* nothing more to come */
+ bool timed_out ; /* eof because timed out */
bool steal_this ; /* steal current keystroke when complete */
bool iac ; /* last character was an IAC */
- struct keystroke_state in ; /* current keystroke being collected */
+ keystroke_state_t in ; /* current keystroke being collected */
- struct keystroke_state pushed_in ;
+ keystroke_state_t pushed_in ;
/* keystroke interrupted by IAC */
} ;
@@ -318,7 +321,7 @@ static void keystroke_steal_esc(keystroke steal, keystroke_stream stream,
uint8_t u) ;
static void keystroke_steal_csi(keystroke steal, keystroke_stream stream,
uint8_t u) ;
-static void keystroke_put(keystroke_stream stream, enum keystroke_type type,
+static void keystroke_put(keystroke_stream stream, keystroke_compound_t type,
bool broken, uint8_t* bytes, int len) ;
/*==============================================================================
@@ -354,12 +357,13 @@ keystroke_stream_new(uint8_t csi_char, keystroke_callback* iac_callback,
* iac_callback = NULL -- none
* iac_callback_context = NULL -- none
*
- * eof_met = false -- no EOF yet
- * steal = false -- no stealing set
- * iac = false -- last character was not an IAC
+ * eof_met = false -- no EOF yet
+ * timed_out = false -- not timed out yet
+ * steal = false -- no stealing set
+ * iac = false -- last character was not an IAC
*
- * in.state = kst_null
- * in.len = 0 -- nothing in the buffer
+ * in.state = kst_null
+ * in.len = 0 -- nothing in the buffer
*
* pushed_in.state ) ditto
* pushed_in.len )
@@ -429,7 +433,7 @@ keystroke_stream_eof(keystroke_stream stream)
* is converted to a broken keystroke and placed in the stream.
* (So eof_met => no partial keystroke.)
*/
- return (stream == NULL) || (vio_fifo_empty(stream->fifo) && stream->eof_met);
+ return (stream == NULL) || (vio_fifo_empty(stream->fifo) && stream->eof_met) ;
} ;
/*------------------------------------------------------------------------------
@@ -437,14 +441,15 @@ keystroke_stream_eof(keystroke_stream stream)
*
* * discard contents of the stream, including any partial keystroke.
*
- * * set the stream "eof_met".
+ * * set the stream "eof_met" with or without timed_out
*/
extern void
-keystroke_stream_set_eof(keystroke_stream stream)
+keystroke_stream_set_eof(keystroke_stream stream, bool timed_out)
{
vio_fifo_reset(stream->fifo, keep_it) ;
stream->eof_met = true ; /* essential information */
+ stream->timed_out = timed_out ; /* variant of eof */
stream->steal_this = false ; /* keep tidy */
stream->iac = false ;
@@ -473,7 +478,7 @@ keystroke_stream_set_eof(keystroke_stream stream)
* If steal != NULL the keystroke will be set to the stolen keystroke. That
* will be type ks_null if nothing was available, and may be knull_eof.
*
- * Note that never steals broken or truncated keystrokes.
+ * Note that never steals broken or truncated keystrokes, or IAC.
*
* Passing len == 0 and ptr == NULL signals EOF to the keystroke_stream.
*
@@ -496,6 +501,7 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len,
if ((len == 0) && (ptr == NULL))
{
stream->eof_met = true ;
+ stream->timed_out = false ;
stream->steal_this = false ;
/* Loop to deal with any pending IAC and partial keystroke.
@@ -967,8 +973,9 @@ inline static bool
keystroke_set_null(keystroke_stream stream, keystroke stroke)
{
stroke->type = ks_null ;
- stroke->value = stream->eof_met ? knull_eof : knull_not_eof ;
-
+ stroke->value = stream->eof_met ? (stream->timed_out ? knull_timed_out
+ : knull_eof)
+ : knull_not_eof ;
stroke->flags = 0 ;
stroke->len = 0 ;
@@ -1013,7 +1020,7 @@ keystroke_add_raw(keystroke_stream stream, uint8_t u)
static void
keystroke_put_char(keystroke_stream stream, uint32_t u)
{
- if (u < 0x80)
+ if (u < kf_compound)
vio_fifo_put_byte(stream->fifo, (uint8_t)u) ;
else
{
@@ -1029,7 +1036,7 @@ keystroke_put_char(keystroke_stream stream, uint32_t u)
}
while (u != 0) ;
- keystroke_put(stream, ks_char, 0, p, (buf + 4) - p) ;
+ keystroke_put(stream, ks_char, false, p, (buf + 4) - p) ;
} ;
} ;
@@ -1127,12 +1134,12 @@ keystroke_clear_iac(keystroke_stream stream)
} ;
/*------------------------------------------------------------------------------
- * Store <first> <len> [<bytes>]
+ * Store <first> <len> [<bytes>] -- compound keystroke.
*
* If len == 0, bytes may be NULL
*/
static void
-keystroke_put(keystroke_stream stream, enum keystroke_type type, bool broken,
+keystroke_put(keystroke_stream stream, keystroke_compound_t type, bool broken,
uint8_t* bytes, int len)
{
if (len > keystroke_max_len)
diff --git a/lib/keystroke.h b/lib/keystroke.h
index 67c6cd0f..70aa120b 100644
--- a/lib/keystroke.h
+++ b/lib/keystroke.h
@@ -47,13 +47,14 @@ enum keystroke_type
ks_type_reserved = 0x0F,
} ;
-typedef enum keystroke_type keystroke_type ;
+typedef enum keystroke_type keystroke_type_t ;
CONFIRM(ks_type_count <= ks_type_reserved) ;
enum keystroke_null
{
knull_not_eof,
- knull_eof
+ knull_eof,
+ knull_timed_out,
};
enum keystroke_flags
@@ -69,23 +70,30 @@ enum keystroke_flags
kf_type_mask = 0x0F, /* extraction of type */
} ;
-typedef enum keystroke_type keystroke_flags ;
+typedef enum keystroke_type keystroke_flags_t ;
-CONFIRM(ks_type_reserved == (keystroke_type)kf_type_mask) ;
+/* The keystroke type and flags are designed so that they can be packed
+ * together as a single byte, the first of a "compound character".
+ */
+typedef uint8_t keystroke_compound_t ;
+
+CONFIRM(ks_type_reserved == (keystroke_type_t)kf_type_mask) ;
+CONFIRM((kf_compound | kf_flag_mask | kf_type_mask) <= 0xFF) ;
typedef struct keystroke* keystroke ;
typedef struct keystroke_stream* keystroke_stream ;
struct keystroke
{
- enum keystroke_type type ;
- uint8_t flags ; /* the kf_flag_mask flags */
+ keystroke_type_t type ;
+ keystroke_flags_t flags ; /* just the kf_flag_mask bits */
uint32_t value ;
unsigned len ;
uint8_t buf[keystroke_max_len] ;
} ;
+typedef struct keystroke keystroke_t[1] ;
#define keystroke_iac_callback_args void* context, keystroke stroke
typedef bool (keystroke_callback)(keystroke_iac_callback_args) ;
@@ -171,7 +179,7 @@ keystroke_stream_new(uint8_t csi_char, keystroke_callback* iac_callback,
void* iac_callback_context) ;
extern void
-keystroke_stream_set_eof(keystroke_stream stream) ;
+keystroke_stream_set_eof(keystroke_stream stream, bool timed_out) ;
extern keystroke_stream
keystroke_stream_free(keystroke_stream stream) ;
diff --git a/lib/log.c b/lib/log.c
index 4da1e5e8..e3296d3a 100644
--- a/lib/log.c
+++ b/lib/log.c
@@ -25,13 +25,14 @@
#include <zebra.h>
#include "log.h"
-#include "vty.h"
+#include "log_local.h"
+#include "vty_log.h"
#include "memory.h"
-#include "command_local.h"
+
#ifndef SUNOS_5
#include <sys/un.h>
#endif
-/* for printstack on solaris */
+/* for printstack on solaris */
#ifdef HAVE_UCONTEXT_H
#include <ucontext.h>
#endif
@@ -39,18 +40,11 @@
#include "qfstring.h"
#include "sigevent.h"
-/* log is protected by the same mutext as vty, see comments in vty.c */
-
-/* prototypes */
-static int uzlog_reset_file (struct zlog *zl);
-static void zlog_abort (const char *mess) __attribute__ ((noreturn));
-static void vzlog (struct zlog *zl, int priority, const char *format, va_list args);
+/*------------------------------------------------------------------------------
+ * Prototypes
+ */
+static void zlog_abort (const char *mess) __attribute__ ((noreturn)) ;
static void uzlog_backtrace(int priority);
-static void uvzlog (struct zlog *zl, int priority, const char *format, va_list args);
-
-static int logfile_fd = -1; /* Used in signal handler. */
-
-struct zlog *zlog_default = NULL;
const char *zlog_proto_names[] =
{
@@ -80,23 +74,310 @@ const char *zlog_priority[] =
NULL,
};
+/*------------------------------------------------------------------------------
+ * Static variables
+ */
+struct zlog* zlog_default = NULL;
+
+struct zlog* zlog_list = NULL ;
+
+static volatile sig_atomic_t max_maxlvl = INT_MAX ;
+
+qpt_mutex_t log_mutex ;
+
+int log_lock_count = 0 ;
+int log_assert_fail = 0 ;
+
/*==============================================================================
- * Time stamp handling -- gettimeofday(), localtime() and strftime().
+ * Log locking and relationship with VTY.
*
- * Maintains a cached form of the current time (under the vty/log mutex), so
- * that can avoid multiple calls of localtime() and strftime() per second.
+ * For pthreads purposes there is a LOG_LOCK() mutex.
*
- * The value from gettimeofday() is in micro-seconds. Can provide timestamp
- * with any number of decimal digits, but at most 6 will be significant.
+ * To support interaction with the VTY:
+ *
+ * a. setting logging configuration.
+ *
+ * b. issuing log messages within the VTY
+ *
+ * the LOG_LOCK() may be collected while VTY_LOCK() -- BUT NOT vice versa !
+ * This is not too difficult, because the logging has no need to call VTY
+ * functions... except for "vty monitor" VTY, so, for that purpose:
+ *
+ * a. for each monitor VTY there is a "monitor_fifo", into which log messages
+ * are place if required. This buffer and some related flags must be
+ * handled under the LOG_LOCK().
+ *
+ * The vty_log() function takes care of this, and takes care of prompting
+ * the vty(s) to output the contents of the fifo.
+ *
+ * This will be called under LOG_LOCK(). It will NOT VTY_LOCK(). It
+ * will return promptly.
+ *
+ * b. once any logging has been handed to the VTY, the logging can forget
+ * about it.
+ *
+ * c. the interface from logging to VTY is in vty_log.h
+ *
+ * The logging system supports some logging once a signal (in particular,
+ * the hard exceptions, such as SIGSEGV) has gone off and the system is being
+ * brought to a dead stop. A separate mechanism is provided for outputting
+ * directly to any vty monitor VTY -- at a file descriptor level !
+ *
+ * The VTY may call logging functions in the same was as any other part of the
+ * system. When interacting with configuration etc, may need to acquire the
+ * LOG_LOCK, etc. The interface from VTY to logging is in log_local.h.
*/
+/*------------------------------------------------------------------------------
+ * Further initialisation for qpthreads.
+ *
+ * This is done during "second stage" initialisation, when all nexuses have
+ * been set up and the qpthread_enabled state established.
+ *
+ * Initialise mutex.
+ *
+ * NB: may be called once and once only.
+ */
+extern void
+log_init_r(void)
+{
+ qpt_mutex_init(log_mutex, qpt_mutex_recursive);
+} ;
+
+/*------------------------------------------------------------------------------
+ * Close down for qpthreads.
+ */
+extern void
+log_finish(void)
+{
+ qpt_mutex_destroy(log_mutex, 0);
+} ;
+
+/*==============================================================================
+ * The actual logging function and creation of the logging lines.
+ *
+ */
+#define TIMESTAMP_FORM "%Y/%m/%d %H:%M:%S"
+
+/* Structure used to hold line for log output -- so that need be generated
+ * just once even if output to multiple destinations.
+ *
+ * Note that the buffer length is a hard limit (including terminating "\n")
+ * Do not wish to malloc any larger buffer while logging.
+ */
+enum { logline_buffer_len = 1008 } ;
+
+struct logline {
+ size_t len ; /* length including either '\r''\n' or '\n' */
+
+ char line[logline_buffer_len]; /* buffer */
+} ;
+
+typedef struct logline logline_t[1] ;
+typedef struct logline* logline ;
+
+static void uvzlog_line(logline ll, struct zlog *zl, int priority,
+ const char *format, va_list va) ;
static void uquagga_timestamp(qf_str qfs, int timestamp_precision) ;
+/*==============================================================================
+ * The main logging function.
+ */
+extern void
+zlog (struct zlog *zl, int priority, const char *format, ...)
+{
+ logline_t ll ;
+
+ /* Decide whether any logging at all is required.
+ *
+ * NB: the max_maxlvl is established every time any change is made to
+ * logging facilities. Those changes are made under LOG_LOCK(), so
+ * only one thread will write to max_max_lvl at any time, and that
+ * will be consistent with the state of all known logging at the
+ * time.
+ *
+ * The max_maxlvl is sig_atomic_t and volatile. So, we assume that:
+ *
+ * a) writing to max_maxlvl is atomic wrt all forms of interrupt.
+ * So the variable cannot exist in a partly written state (with,
+ * say, some bytes of the old value and some of the new).
+ *
+ * b) reading max_maxlvl will either collect the state before some
+ * change to the logging levels, or after.
+ *
+ * If passes the initial test, immediately acquires the LOG_LOCK().
+ *
+ * So, if the logging facilities are being changed, then:
+ *
+ * a) if the level is about to be increased, so the current
+ * priority would pass, then that change is just too late
+ * for this particular logging operation.
+ *
+ * b) if the level is about to be reduced, will get past the
+ * initial test, but will fail the later tests, under the
+ * LOG_LOCK().
+ *
+ * NB: max_maxlvl is statically initialised to INT_MAX !
+ */
+ if (priority > max_maxlvl)
+ return ;
+
+ /* Decide where we are logging to. */
+
+ LOG_LOCK() ;
+
+ if (zl == NULL)
+ {
+ zl = zlog_default ;
+
+ if (zl == NULL)
+ {
+ /* Have to get up very early in the morning to get to here, because
+ * zlog_default should be initialised -- by openzlog() -- very early
+ * indeed.
+ *
+ * So... "log" to stderr.
+ */
+ va_list va;
+ va_start(va, format);
+ uvzlog_line(ll, zl, priority, format, va) ;
+ va_end (va);
+
+ write(fileno(stderr), ll->line, ll->len) ;
+
+ return ;
+ } ;
+ } ;
+
+ /* Step through the logging destinations and log as required. */
+
+ ll->len = 0 ; /* Nothing generated, yet */
+
+ /* Syslog output */
+ if (priority <= zl->emaxlvl[ZLOG_DEST_SYSLOG])
+ {
+ va_list va;
+ va_start(va, format);
+ vsyslog (priority|zlog_default->facility, format, va);
+ va_end(va);
+ }
+
+ /* File output. */
+ if (priority <= zl->emaxlvl[ZLOG_DEST_FILE])
+ {
+ if (ll->len == 0)
+ {
+ va_list va;
+ va_start(va, format);
+ uvzlog_line(ll, zl, priority, format, va) ;
+ va_end (va);
+ } ;
+ write(zl->file_fd, ll->line, ll->len) ;
+ }
+
+ /* stdout output. */
+ if (priority <= zl->emaxlvl[ZLOG_DEST_STDOUT])
+ {
+ if (ll->len == 0)
+ {
+ va_list va;
+ va_start(va, format);
+ uvzlog_line(ll, zl, priority, format, va) ;
+ va_end (va);
+ } ;
+ write(zl->stdout_fd, ll->line, ll->len) ;
+ }
+
+ /* Terminal monitor. */
+ if (priority <= zl->emaxlvl[ZLOG_DEST_MONITOR])
+ {
+ if (ll->len == 0)
+ {
+ va_list va;
+ va_start(va, format);
+ uvzlog_line(ll, zl, priority, format, va) ;
+ va_end (va);
+ } ;
+ vty_log(priority, ll->line, ll->len - 1) ; /* less the '\n' */
+ } ;
+
+ LOG_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Preparation of line to send to logging: file, stdout or "monitor" terminals.
+ */
+static void
+uvzlog_line(logline ll, struct zlog *zl, int priority,
+ const char *format, va_list va)
+{
+ const char* q ;
+ qf_str_t qfs ;
+
+ qfs_init(qfs, ll->line, sizeof(ll->line) - 1) ; /* leave space for '\n' */
+ /* "<time stamp>" */
+ uquagga_timestamp(qfs, (zl != NULL) ? zl->timestamp_precision : 0) ;
+
+ qfs_append_n(qfs, " ", 1) ;
+
+ /* "<priority>: " if required */
+ if ((zl != NULL) && zl->record_priority)
+ {
+ qfs_append(qfs, zlog_priority[priority]) ;
+ qfs_append(qfs, ": ") ;
+ } ;
+
+ /* "<protocol>: " or "unknown: " */
+ if (zl != NULL)
+ q = zlog_proto_names[zl->protocol] ;
+ else
+ q = "unknown" ;
+
+ qfs_append(qfs, q) ;
+ qfs_append(qfs, ": ") ;
+
+ /* Now the log line itself (uses a *copy* of the va_list) */
+ qfs_vprintf(qfs, format, va) ;
+
+ /* Set pointer to where the '\n' is going */
+ qfs_append_n(qfs, "\n", 1) ;
+ ll->len = qfs_len(qfs) ;
+} ;
+
/*------------------------------------------------------------------------------
* Fill buffer with current time, to given number of decimal digits.
*
* If given buffer is too small, provides as many characters as possible.
+
+ * Time stamp handling -- gettimeofday(), localtime() and strftime().
+ *
+ * Maintains a cached form of the current time (under the vty/log mutex), so
+ * that can avoid multiple calls of localtime() and strftime() per second.
+ *
+ * The value from gettimeofday() is in micro-seconds. Can provide timestamp
+ * with any number of decimal digits, but at most 6 will be significant.
+ *
+ * Puts a current timestamp in buf and returns the number of characters
+ * written (not including the terminating NUL). The purpose of
+ * this function is to avoid calls to localtime appearing all over the code.
+ * It caches the most recent localtime result and can therefore
+ * avoid multiple calls within the same second.
*
+ * The buflen MUST be > 1 and the buffer address MUST NOT be NULL.
+ *
+ * If buflen is too small, writes buflen-1 characters followed by '\0'.
+ *
+ * Time stamp is rendered in the form: %Y/%m/%d %H:%M:%S
+ *
+ * This has a fixed length (leading zeros are included) of 19 characters
+ * (unless this code is still in use beyond the year 9999 !)
+ *
+ * Which may be followed by "." and a number of decimal digits, usually 1..6.
+ *
+ * So the maximum time stamp is 19 + 1 + 6 = 26. Adding the trailing '\n', and
+ * rounding up for good measure -- buffer size = 32.
+
+
* Returns: number of characters in buffer, not including trailing '\0'.
*
* NB: does no rounding.
@@ -108,18 +389,21 @@ quagga_timestamp(int timestamp_precision, char *buf, size_t buflen)
{
qf_str_t qfs ;
- VTY_LOCK() ;
+ LOG_LOCK() ;
- qfs_init(&qfs, buf, buflen) ;
- uquagga_timestamp(&qfs, timestamp_precision) ;
+ qfs_init(qfs, buf, buflen) ;
+ uquagga_timestamp(qfs, timestamp_precision) ;
- VTY_UNLOCK() ;
- return qfs_len(&qfs) ;
+ LOG_UNLOCK() ;
+ return qfs_len(qfs) ;
}
/*------------------------------------------------------------------------------
* unprotected version for when mutex already held
*/
+
+// used in uvzlog_line
+
static void
uquagga_timestamp(qf_str qfs, int timestamp_precision)
{
@@ -134,7 +418,7 @@ uquagga_timestamp(qf_str qfs, int timestamp_precision)
/* would it be sufficient to use global 'recent_time' here? I fear not... */
gettimeofday(&clock, NULL);
- /* first, we update the cache if the time has changed */
+ /* first, we update the cache if the seconds time has changed */
if (cache.last != clock.tv_sec)
{
struct tm tm;
@@ -173,137 +457,54 @@ uquagga_timestamp(qf_str qfs, int timestamp_precision)
} ;
/*==============================================================================
- * va_list version of zlog
+ *
*/
-static void
-vzlog (struct zlog *zl, int priority, const char *format, va_list args)
+
+void
+_zlog_assert_failed (const char *assertion, const char *file,
+ unsigned int line, const char *function)
{
- VTY_LOCK() ;
- uvzlog(zl, priority, format, args);
- VTY_UNLOCK() ;
+ const static size_t buff_size = 1024;
+ char buff[buff_size];
+ snprintf(buff, buff_size,
+ "Assertion `%s' failed in file %s, line %u, function %s",
+ assertion, file, line, (function ? function : "?"));
+ zlog_abort(buff);
}
-/* va_list version of zlog. Unprotected assumes mutex already held*/
-static void
-uvzlog (struct zlog *zl, int priority, const char *format, va_list va)
+/* Abort with message */
+void
+_zlog_abort_mess (const char *mess, const char *file,
+ unsigned int line, const char *function)
{
- struct logline ll ; /* prepares line for output, here */
-
- VTY_ASSERT_LOCKED() ;
-
- ll.p_nl = NULL ; /* Nothing generated, yet */
-
- /* If zlog is not specified, use default one. */
- if (zl == NULL)
- zl = zlog_default ;
-
- /* When zlog_default is also NULL, use stderr for logging. */
- if (zl == NULL)
- {
- uvzlog_line(&ll, zl, priority, format, va, llt_lf) ;
- write(fileno(stderr), ll.line, ll.len) ;
- }
- else
- {
- /* Syslog output */
- if (priority <= zl->maxlvl[ZLOG_DEST_SYSLOG])
- {
- va_list ac;
- va_copy(ac, va);
- vsyslog (priority|zlog_default->facility, format, ac);
- va_end(ac);
- }
-
- /* File output. */
- if ((priority <= zl->maxlvl[ZLOG_DEST_FILE]) && zl->fp)
- {
- uvzlog_line(&ll, zl, priority, format, va, llt_lf) ;
- write(fileno(zl->fp), ll.line, ll.len) ;
- }
-
- /* stdout output. */
- if (priority <= zl->maxlvl[ZLOG_DEST_STDOUT])
- {
- uvzlog_line(&ll, zl, priority, format, va, llt_lf) ;
- write(fileno(zl->fp), ll.line, ll.len) ;
- }
-
- /* Terminal monitor. */
- if (priority <= zl->maxlvl[ZLOG_DEST_MONITOR])
- uty_log(&ll, zl, priority, format, va) ;
- }
+ const static size_t buff_size = 1024;
+ char buff[buff_size];
+ snprintf(buff, buff_size, "%s, in file %s, line %u, function %s",
+ mess, file, line, (function ? function : "?"));
+ zlog_abort(buff);
}
-/*------------------------------------------------------------------------------
- * Preparation of line to send to logging: file, stdout or "monitor" terminals.
- *
- * Takes copy of va_list before using it, so the va_list is unchanged.
- */
-extern void
-uvzlog_line(struct logline* ll, struct zlog *zl, int priority,
- const char *format, va_list va, enum ll_term term)
+/* Abort with message and errno and strerror() thereof */
+void
+_zlog_abort_errno (const char *mess, const char *file,
+ unsigned int line, const char *function)
{
- char* p ;
- const char* q ;
-
- p = ll->p_nl ;
-
- if (p != NULL)
- {
- /* we have the line -- just need to worry about the crlf state */
- if (term == ll->term)
- return ; /* exit here if all set */
- }
- else
- {
- /* must construct the line */
- qf_str_t qfs ;
- va_list vac ;
-
- qfs_init(&qfs, ll->line, sizeof(ll->line) - 2) ;
- /* leave space for '\n' or '\r''\n' */
- /* "<time stamp>" */
- uquagga_timestamp(&qfs, (zl != NULL) ? zl->timestamp_precision : 0) ;
-
- qfs_append_n(&qfs, " ", 1) ;
-
- /* "<priority>: " if required */
- if ((zl != NULL) && (zl->record_priority))
- {
- qfs_append(&qfs, zlog_priority[priority]) ;
- qfs_append(&qfs, ": ") ;
- } ;
-
- /* "<protocol>: " or "unknown: " */
- if (zl != NULL)
- q = zlog_proto_names[zl->protocol] ;
- else
- q = "unknown" ;
-
- qfs_append(&qfs, q) ;
- qfs_append(&qfs, ": ") ;
-
- /* Now the log line itself */
- va_copy(vac, va);
- qfs_vprintf(&qfs, format, vac) ;
- va_end(vac);
-
- /* Set pointer to where the '\0' is. */
- p = ll->p_nl = qfs_ptr(&qfs) ;
- } ;
-
- /* finish off with '\r''\n''\0' or '\n''\0' as required */
- if (term == llt_crlf)
- *p++ = '\r' ;
-
- if (term != llt_nul)
- *p++ = '\n' ;
-
- *p = '\0' ;
+ _zlog_abort_err(mess, errno, file, line, function);
+}
- ll->len = p - ll->line ;
- ll->term = term ;
-} ;
+/* Abort with message and given error and strerror() thereof */
+void
+_zlog_abort_err (const char *mess, int err, const char *file,
+ unsigned int line, const char *function)
+{
+ const static size_t buff_size = 1024;
+ char buff[buff_size];
+ snprintf(buff, buff_size,
+ "%s, in file %s, line %u, function %s, %s",
+ mess, file, line, (function ? function : "?"),
+ errtoa(err, 0).str);
+ zlog_abort(buff);
+}
/*============================================================================*/
@@ -444,18 +645,18 @@ open_crashlog(void)
#undef CRASHLOG_PREFIX
}
-/* Note: the goal here is to use only async-signal-safe functions. */
-void
-zlog_signal(int signo, const char *action
-#ifdef SA_SIGINFO
- , siginfo_t *siginfo, void *program_counter
-#endif
- )
+/*------------------------------------------------------------------------------
+ * Note: the goal here is to use only async-signal-safe functions.
+ */
+extern void
+zlog_signal(int signo, const char *action, siginfo_t *siginfo,
+ void *program_counter)
{
time_t now;
char buf[sizeof("DEFAULT: Received signal S at T (si_addr 0xP, PC 0xP); aborting...")+100];
char *s = buf;
char *msgstart = buf;
+ int file_fd ;
#define LOC s,buf+sizeof(buf)-s
time(&now);
@@ -489,10 +690,16 @@ zlog_signal(int signo, const char *action
/* N.B. implicit priority is most severe */
#define PRI LOG_CRIT
-#define DUMP(FD) write(FD, buf, s-buf);
+#define DUMP(FD) write(FD, buf, s - buf);
+
/* If no file logging configured, try to write to fallback log file. */
- if ((logfile_fd >= 0) || ((logfile_fd = open_crashlog()) >= 0))
- DUMP(logfile_fd)
+ file_fd = (zlog_default != NULL) ? zlog_default->file_fd : -1 ;
+ if (file_fd < 0)
+ file_fd = open_crashlog() ;
+
+ if (file_fd >= 0)
+ DUMP(file_fd)
+
if (!zlog_default)
DUMP(STDERR_FILENO)
else
@@ -502,7 +709,7 @@ zlog_signal(int signo, const char *action
/* Remove trailing '\n' for monitor and syslog */
*--s = '\0';
if (PRI <= zlog_default->maxlvl[ZLOG_DEST_MONITOR])
- vty_log_fixed(buf,s-buf);
+ vty_log_fixed(buf, s - buf);
if (PRI <= zlog_default->maxlvl[ZLOG_DEST_SYSLOG])
syslog_sigsafe(PRI|zlog_default->facility,msgstart,s-msgstart);
}
@@ -537,6 +744,7 @@ zlog_backtrace_sigsafe(int priority, void *program_counter)
int size;
char buf[100];
char *s, **bt = NULL;
+ int file_fd ;
#define LOC s,buf+sizeof(buf)-s
#ifdef HAVE_GLIBC_BACKTRACE
@@ -567,8 +775,13 @@ zlog_backtrace_sigsafe(int priority, void *program_counter)
s = num_append(LOC,size);
s = str_append(LOC," stack frames:\n");
- if ((logfile_fd >= 0) || ((logfile_fd = open_crashlog()) >= 0))
- DUMP(logfile_fd)
+ file_fd = (zlog_default != NULL) ? zlog_default->file_fd : -1 ;
+ if (file_fd < 0)
+ file_fd = open_crashlog() ;
+
+ if (file_fd >= 0)
+ DUMP(file_fd)
+
if (!zlog_default)
DUMP(STDERR_FILENO)
else
@@ -616,16 +829,16 @@ zlog_backtrace_sigsafe(int priority, void *program_counter)
void
zlog_backtrace(int priority)
{
- VTY_LOCK() ;
+ LOG_LOCK() ;
uzlog_backtrace(priority);
- VTY_UNLOCK() ;
+ LOG_UNLOCK() ;
}
static void
uzlog_backtrace(int priority)
{
#ifndef HAVE_GLIBC_BACKTRACE
- uzlog(NULL, priority, "No backtrace available on this platform.");
+ zlog(NULL, priority, "No backtrace available on this platform.");
#else
void *array[20];
int size, i;
@@ -634,161 +847,79 @@ uzlog_backtrace(int priority)
if (((size = backtrace(array,sizeof(array)/sizeof(array[0]))) <= 0) ||
((size_t)size > sizeof(array)/sizeof(array[0])))
{
- uzlog(NULL, LOG_ERR, "Cannot get backtrace, returned invalid # of frames %d "
+ zlog(NULL, LOG_ERR, "Cannot get backtrace, returned invalid # of frames %d "
"(valid range is between 1 and %lu)",
size, (unsigned long)(sizeof(array)/sizeof(array[0])));
return;
}
- uzlog(NULL, priority, "Backtrace for %d stack frames:", size);
+ zlog(NULL, priority, "Backtrace for %d stack frames:", size);
if (!(strings = backtrace_symbols(array, size)))
{
- uzlog(NULL, LOG_ERR, "Cannot get backtrace symbols (out of memory?)");
+ zlog(NULL, LOG_ERR, "Cannot get backtrace symbols (out of memory?)");
for (i = 0; i < size; i++)
- uzlog(NULL, priority, "[bt %d] %p",i,array[i]);
+ zlog(NULL, priority, "[bt %d] %p",i,array[i]);
}
else
{
for (i = 0; i < size; i++)
- uzlog(NULL, priority, "[bt %d] %s",i,strings[i]);
+ zlog(NULL, priority, "[bt %d] %s",i,strings[i]);
free(strings);
}
#endif /* HAVE_GLIBC_BACKTRACE */
}
-/* unlocked version */
-void
-uzlog (struct zlog *zl, int priority, const char *format, ...)
-{
- va_list args;
-
- va_start(args, format);
- uvzlog (zl, priority, format, args);
- va_end (args);
-}
-
-void
-zlog (struct zlog *zl, int priority, const char *format, ...)
-{
- va_list args;
-
- va_start(args, format);
- vzlog (zl, priority, format, args);
- va_end (args);
-}
-
-#define ZLOG_FUNC(FUNCNAME,PRIORITY) \
-void \
-FUNCNAME(const char *format, ...) \
-{ \
- va_list args; \
- va_start(args, format); \
- vzlog (NULL, PRIORITY, format, args); \
- va_end(args); \
-}
-
-ZLOG_FUNC(zlog_err, LOG_ERR)
-
-ZLOG_FUNC(zlog_warn, LOG_WARNING)
-
-ZLOG_FUNC(zlog_info, LOG_INFO)
-
-ZLOG_FUNC(zlog_notice, LOG_NOTICE)
-
-ZLOG_FUNC(zlog_debug, LOG_DEBUG)
-
-#undef ZLOG_FUNC
-
-#define PLOG_FUNC(FUNCNAME,PRIORITY) \
-void \
-FUNCNAME(struct zlog *zl, const char *format, ...) \
-{ \
- va_list args; \
- va_start(args, format); \
- vzlog (zl, PRIORITY, format, args); \
- va_end(args); \
-}
-
-PLOG_FUNC(plog_err, LOG_ERR)
-
-PLOG_FUNC(plog_warn, LOG_WARNING)
-
-PLOG_FUNC(plog_info, LOG_INFO)
-
-PLOG_FUNC(plog_notice, LOG_NOTICE)
-
-PLOG_FUNC(plog_debug, LOG_DEBUG)
-
-#undef PLOG_FUNC
-
-void
-_zlog_assert_failed (const char *assertion, const char *file,
- unsigned int line, const char *function)
-{
- const static size_t buff_size = 1024;
- char buff[buff_size];
- snprintf(buff, buff_size,
- "Assertion `%s' failed in file %s, line %u, function %s",
- assertion, file, line, (function ? function : "?"));
- zlog_abort(buff);
-}
-
-/* Abort with message */
-void
-_zlog_abort_mess (const char *mess, const char *file,
- unsigned int line, const char *function)
-{
- const static size_t buff_size = 1024;
- char buff[buff_size];
- snprintf(buff, buff_size, "%s, in file %s, line %u, function %s",
- mess, file, line, (function ? function : "?"));
- zlog_abort(buff);
-}
-
-/* Abort with message and errno and strerror() thereof */
-void
-_zlog_abort_errno (const char *mess, const char *file,
- unsigned int line, const char *function)
-{
- _zlog_abort_err(mess, errno, file, line, function);
-}
-
-/* Abort with message and given error and strerror() thereof */
-void
-_zlog_abort_err (const char *mess, int err, const char *file,
- unsigned int line, const char *function)
-{
- const static size_t buff_size = 1024;
- char buff[buff_size];
- snprintf(buff, buff_size,
- "%s, in file %s, line %u, function %s, %s",
- mess, file, line, (function ? function : "?"),
- errtoa(err, 0).str);
- zlog_abort(buff);
-}
+static void uzlog_set_effective_level(struct zlog* zl) ;
static void
zlog_abort (const char *mess)
{
-#if VTY_DEBUG
+#if LOG_DEBUG
/* May not be locked -- but that doesn't matter any more */
- ++vty_lock_count ;
+ ++log_lock_count ;
#endif
- /* Force fallback file logging? */
- if (zlog_default && !zlog_default->fp &&
- ((logfile_fd = open_crashlog()) >= 0) &&
- ((zlog_default->fp = fdopen(logfile_fd, "w")) != NULL))
- zlog_default->maxlvl[ZLOG_DEST_FILE] = LOG_ERR;
+ if (zlog_default != NULL)
+ {
+ if (zlog_default->file_fd < 0)
+ zlog_default->file_fd = open_crashlog() ;
+
+ if (zlog_default->file_fd >= 0)
+ {
+ zlog_default->maxlvl[ZLOG_DEST_FILE] = LOG_ERR;
+ uzlog_set_effective_level(zlog_default) ;
+ } ;
+ } ;
- uzlog(NULL, LOG_CRIT, "%s", mess);
+ zlog(NULL, LOG_CRIT, "%s", mess);
uzlog_backtrace(LOG_CRIT);
zabort_abort();
}
+/*==============================================================================
+ * Opening, closing, setting log levels etc.
+ *
+ */
+static int uzlog_set_file(struct zlog *zl, const char *filename, int level) ;
+static int uzlog_reset_file(struct zlog *zl) ;
+static void uzlog_set_effective_level(struct zlog* zl) ;
+
+/*------------------------------------------------------------------------------
+ * Get the effective zlog stream.
+ */
+static inline struct zlog* zlog_actual(struct zlog* zl)
+{
+ return zl != NULL ? zl : zlog_default ;
+} ;
-/* Open log stream */
-struct zlog *
+/*------------------------------------------------------------------------------
+ * Open logging -- create and initialise a struct zlog object.
+ *
+ * Opens a connection to syslog.
+ *
+ * This must be done very early in the morning, before any pthreading starts.
+ */
+extern struct zlog *
openzlog (const char *progname, zlog_proto_t protocol,
int syslog_flags, int syslog_facility)
{
@@ -797,438 +928,515 @@ openzlog (const char *progname, zlog_proto_t protocol,
zl = XCALLOC(MTYPE_ZLOG, sizeof (struct zlog));
- zl->ident = progname;
- zl->protocol = protocol;
- zl->facility = syslog_facility;
+ zl->ident = progname;
+ zl->protocol = protocol;
+ zl->facility = syslog_facility;
zl->syslog_options = syslog_flags;
- /* Set default logging levels. */
- for (i = 0; i < sizeof(zl->maxlvl)/sizeof(zl->maxlvl[0]); i++)
- zl->maxlvl[i] = ZLOG_DISABLED;
- zl->maxlvl[ZLOG_DEST_MONITOR] = LOG_DEBUG;
- zl->default_lvl = LOG_DEBUG;
+ /* Set default logging levels. */
+ for (i = 0 ; i < ZLOG_DEST_COUNT ; ++i)
+ zl->maxlvl[i] = ZLOG_DISABLED ;
+
+ zl->default_lvl = LOG_DEBUG ;
openlog (progname, syslog_flags, zl->facility);
+ zl->syslog = true ; /* have syslog */
+ zl->stdout_fd = fileno(stdout) ; /* assume have stdout */
+ zl->file_fd = -1 ; /* no file, yet */
+ zl->monitors = 0 ; /* no monitors, yet */
+
+ assert(zlog_list == NULL) ; /* can do this once ! */
+ zlog_list = zl ;
+
+ uzlog_set_effective_level(zl) ;
+
return zl;
-}
+} ;
-void
+/*------------------------------------------------------------------------------
+ * Close logging -- destroy struct zlog object.
+ *
+ * Closes connection to syslog and any log file.
+ */
+extern void
closezlog (struct zlog *zl)
{
+ assert((zl == zlog_list) && (zl->next == NULL)) ; /* pro tem */
+
closelog();
+ if (zl->file_fd >= 0)
+ close (zl->file_fd) ;
- if (zl->fp != NULL)
- fclose (zl->fp);
+ if (zl->filename != NULL)
+ free (zl->filename) ;
+
+ zl->syslog = false ;
+ zl->file_fd = -1 ;
+ zl->stdout_fd = -1 ;
+ zl->monitors = 0 ; /* TODO... state of VTY ?? */
+
+ uzlog_set_effective_level(zl) ;
XFREE (MTYPE_ZLOG, zl);
}
-/* Called from command.c. */
-void
-zlog_set_level (struct zlog *zl, zlog_dest_t dest, int log_level)
+/*------------------------------------------------------------------------------
+ * Set new logging level for the given destination.
+ *
+ * Update the effective maxlvl for this zlog, and the max_maxlvl for all zlog.
+ */
+extern void
+zlog_set_level (struct zlog *zl, zlog_dest_t dest, int level)
{
- VTY_LOCK() ;
-
- if (zl == NULL)
- zl = zlog_default;
+ LOG_LOCK() ;
- if (zl != NULL)
+ if ((zl = zlog_actual(zl)) != NULL)
{
- zl->maxlvl[dest] = log_level;
+ zl->maxlvl[dest] = level ;
+ uzlog_set_effective_level(zl) ;
}
- VTY_UNLOCK() ;
+ LOG_UNLOCK() ;
}
-int
-zlog_set_file (struct zlog *zl, const char *filename, int log_level)
+/*------------------------------------------------------------------------------
+ * Set new log file: name and level.
+ *
+ * Note that this closes any existing file
+ *
+ * Returns: 0 => OK
+ * errno otherwise.
+ */
+extern int
+zlog_set_file(struct zlog *zl, const char *filename, int level)
{
- FILE *fp;
- mode_t oldumask;
- int result = 1;
+ int err ;
- VTY_LOCK() ;
+ LOG_LOCK() ;
- /* There is opend file. */
- uzlog_reset_file (zl);
+ err = uzlog_set_file(zl, filename, level) ;
- /* Set default zl. */
- if (zl == NULL)
- zl = zlog_default;
+ LOG_UNLOCK() ;
+ return err ;
+}
- if (zl != NULL)
+static int
+uzlog_set_file(struct zlog *zl, const char *filename, int level)
+{
+ int err ;
+
+ LOG_ASSERT_LOCKED() ;
+
+ err = 0 ;
+ if ((zl = zlog_actual(zl)) != NULL)
{
- /* Open file. */
+ mode_t oldumask;
+ int fd ;
+
+ /* Close any existing file */
+ uzlog_reset_file(zl);
+
+ /* Open file making damn sure we get the mode we want ! */
oldumask = umask (0777 & ~LOGFILE_MASK);
- fp = fopen (filename, "a");
- umask(oldumask);
- if (fp == NULL)
- result = 0;
+ fd = open(filename, O_WRONLY | O_APPEND | O_CREAT, LOGFILE_MASK);
+ if (fd < 0)
+ err = errno ;
else
{
- /* Set flags. */
- zl->filename = strdup (filename);
- zl->maxlvl[ZLOG_DEST_FILE] = log_level;
- zl->fp = fp;
- logfile_fd = fileno(fp);
- }
- }
+ /* Set flags. */
+ zl->filename = strdup (filename) ;
+ zl->file_fd = fd ;
+ zl->maxlvl[ZLOG_DEST_FILE] = level;
+
+ uzlog_set_effective_level(zl) ;
+ } ;
+
+ umask(oldumask);
+ } ;
- VTY_UNLOCK() ;
- return result;
+ return err ;
}
-/* Reset opend file. */
-int
-zlog_reset_file (struct zlog *zl)
+/*------------------------------------------------------------------------------
+ * Close any existing log file. Set level to ZLOG_DISABLED.
+ *
+ * Note that this closes any existing file
+ *
+ * Returns: 1
+ */
+extern int
+zlog_reset_file(struct zlog *zl)
{
- int result;
- VTY_LOCK() ;
- result = uzlog_reset_file(zl);
- VTY_UNLOCK() ;
- return result;
+ int ret ;
+ LOG_LOCK() ;
+
+ ret = uzlog_reset_file(zl) ;
+
+ LOG_UNLOCK() ;
+ return ret ;
}
static int
-uzlog_reset_file (struct zlog *zl)
- {
- if (zl == NULL)
- zl = zlog_default;
+uzlog_reset_file(struct zlog *zl)
+{
+ LOG_ASSERT_LOCKED() ;
- if (zl != NULL)
+ if ((zl = zlog_actual(zl)) != NULL)
{
- if (zl->fp)
- fclose (zl->fp);
- zl->fp = NULL;
- logfile_fd = -1;
+ if (zl->file_fd >= 0)
+ close (zl->file_fd) ;
+
+ zl->file_fd = -1 ;
zl->maxlvl[ZLOG_DEST_FILE] = ZLOG_DISABLED;
- if (zl->filename)
- free (zl->filename);
- zl->filename = NULL;
- }
+ if (zl->filename != NULL)
+ free (zl->filename) ;
+
+ zl->filename = NULL ;
+
+ uzlog_set_effective_level(zl) ;
+ } ;
return 1;
}
-/* Reopen log file. */
-int
+/*------------------------------------------------------------------------------
+ * Close and reopen log file -- TODO and the point ??
+ */
+extern int
zlog_rotate (struct zlog *zl)
{
- int level;
- int result = 1;
+ int err ;
+ char* filename ;
- VTY_LOCK() ;
+ filename = NULL ;
+ err = 0 ;
- if (zl == NULL)
- zl = zlog_default;
+ LOG_LOCK() ;
- if (zl != NULL)
+ if ((zl = zlog_actual(zl)) != NULL)
{
- if (zl->fp)
- fclose (zl->fp);
- zl->fp = NULL;
- logfile_fd = -1;
- level = zl->maxlvl[ZLOG_DEST_FILE];
- zl->maxlvl[ZLOG_DEST_FILE] = ZLOG_DISABLED;
-
- if (zl->filename)
+ if (zl->file_fd >= 0)
{
- mode_t oldumask;
- int save_errno;
-
- oldumask = umask (0777 & ~LOGFILE_MASK);
- zl->fp = fopen (zl->filename, "a");
- save_errno = errno;
- umask(oldumask);
- if (zl->fp == NULL)
- {
- /* can't call logging while locked */
- char *fname = strdup(zl->filename);
- uzlog(NULL, LOG_ERR,
- "Log rotate failed: cannot open file %s for append: %s",
- fname, errtoa(save_errno, 0).str);
- free(fname);
- result = -1;
- }
- else
- {
- logfile_fd = fileno(zl->fp);
- zl->maxlvl[ZLOG_DEST_FILE] = level;
- }
- }
- }
- VTY_UNLOCK() ;
- return result;
+ filename = zl->filename ;
+ zl->filename = NULL ; /* co-opt the name */
+
+ err = uzlog_set_file(zl, filename, zl->maxlvl[ZLOG_DEST_FILE]) ;
+ } ;
+ } ;
+
+ LOG_UNLOCK() ;
+
+ if (err != 0)
+ zlog(NULL, LOG_ERR,
+ "Log rotate failed: cannot open file %s for append: %s",
+ filename, errtoa(err, 0).str) ;
+ if (filename != NULL)
+ free(filename) ; /* discard old copy */
+
+ return (err == 0) ? 1 : -1 ;
}
-int
-zlog_get_default_lvl (struct zlog *zl)
+/*------------------------------------------------------------------------------
+ * Increment or decrement the number of monitor terminals.
+ */
+extern void
+uzlog_add_monitor(struct zlog *zl, int count)
{
- int result = LOG_DEBUG;
+ if ((zl = zlog_actual(zl)) != NULL)
+ {
+ zl->monitors += count ;
+ uzlog_set_effective_level(zl) ;
+ } ;
+}
- VTY_LOCK() ;
- if (zl == NULL)
- zl = zlog_default;
+/*------------------------------------------------------------------------------
+ * get the current default level
+ */
+extern int
+zlog_get_default_lvl (struct zlog *zl)
+{
+ int level ;
- if (zl != NULL)
- {
- result = zl->default_lvl;
- }
+ LOG_LOCK() ;
+
+ zl = zlog_actual(zl) ;
+ level = (zl != NULL) ? zl->default_lvl : LOG_DEBUG ;
- VTY_UNLOCK() ;
- return result;
+ LOG_UNLOCK() ;
+ return level ;
}
-void
+/*------------------------------------------------------------------------------
+ * set the default level
+ */
+extern void
zlog_set_default_lvl (struct zlog *zl, int level)
{
- VTY_LOCK() ;
+ LOG_LOCK() ;
- if (zl == NULL)
- zl = zlog_default;
+ if ((zl = zlog_actual(zl)) != NULL)
+ zl->default_lvl = level;
- if (zl != NULL)
- {
- zl->default_lvl = level;
- }
-
- VTY_UNLOCK() ;
+ LOG_UNLOCK() ;
}
-/* Set logging level and default for all destinations */
-void
+/*------------------------------------------------------------------------------
+ * Set default logging level and the level for any destination not ZLOG_DISABLED
+ *
+ * Note that on openzlog(), all destinations are set ZLOG_DISABLED.
+ */
+extern void
zlog_set_default_lvl_dest (struct zlog *zl, int level)
{
- int i;
+ LOG_LOCK() ;
- VTY_LOCK() ;
-
- if (zl == NULL)
- zl = zlog_default;
-
- if (zl != NULL)
+ if ((zl = zlog_actual(zl)) != NULL)
{
+ int i;
+
zl->default_lvl = level;
- for (i = 0; i < ZLOG_NUM_DESTS; i++)
+ for (i = 0; i < ZLOG_DEST_COUNT; i++)
if (zl->maxlvl[i] != ZLOG_DISABLED)
- zl->maxlvl[i] = level;
- }
+ zl->maxlvl[i] = level ;
- VTY_UNLOCK() ;
-}
+ uzlog_set_effective_level(zl) ;
+ } ;
-int
+ LOG_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get the current level for the given destination.
+ */
+extern int
zlog_get_maxlvl (struct zlog *zl, zlog_dest_t dest)
{
- int result = ZLOG_DISABLED;
+ int level ;
- VTY_LOCK() ;
+ LOG_LOCK() ;
- if (zl == NULL)
- zl = zlog_default;
-
- if (zl != NULL)
- {
- result = zl->maxlvl[dest];
- }
+ zl = zlog_actual(zl) ;
+ level = (zl != NULL) ? zl->maxlvl[dest] : ZLOG_DISABLED ;
- VTY_UNLOCK() ;
- return result;
+ LOG_UNLOCK() ;
+ return level;
}
-int
+/*------------------------------------------------------------------------------
+ * Get the current facility setting for syslog
+ */
+extern int
zlog_get_facility (struct zlog *zl)
{
- int result = LOG_DAEMON;
+ int facility ;
- VTY_LOCK() ;
+ LOG_LOCK() ;
- if (zl == NULL)
- zl = zlog_default;
-
- if (zl != NULL)
- {
- result = zl->facility;
- }
+ zl = zlog_actual(zl) ;
+ facility = (zl != NULL) ? zl->facility : LOG_DAEMON ;
- VTY_UNLOCK() ;
- return result;
-}
+ LOG_UNLOCK() ;
+ return facility ;
+} ;
-void
+/*------------------------------------------------------------------------------
+ * Set the current facility setting for syslog
+ */
+extern void
zlog_set_facility (struct zlog *zl, int facility)
{
- VTY_LOCK() ;
-
- if (zl == NULL)
- zl = zlog_default;
+ LOG_LOCK() ;
- if (zl != NULL)
- {
- zl->facility = facility;
- }
+ if ((zl = zlog_actual(zl)) != NULL)
+ zl->facility = facility ;
- VTY_UNLOCK() ;
+ LOG_UNLOCK() ;
}
-int
+/*------------------------------------------------------------------------------
+ * Get the record priority setting
+ */
+extern bool
zlog_get_record_priority (struct zlog *zl)
{
- int result = 0;
+ bool priority ;
- VTY_LOCK() ;
+ LOG_LOCK() ;
- if (zl == NULL)
- zl = zlog_default;
+ zl = zlog_actual(zl) ;
+ priority = (zl != NULL) ? zl->record_priority : false ;
- if (zl != NULL)
- {
- result = zl->record_priority;
- }
-
- VTY_UNLOCK() ;
- return result;
+ LOG_UNLOCK() ;
+ return priority ;
}
-void
-zlog_set_record_priority (struct zlog *zl, int record_priority)
+/*------------------------------------------------------------------------------
+ * Set the record priority setting
+ */
+extern void
+zlog_set_record_priority (struct zlog *zl, bool record_priority)
{
- VTY_LOCK() ;
+ LOG_LOCK() ;
- if (zl == NULL)
- zl = zlog_default;
+ if ((zl = zlog_actual(zl)) != NULL)
+ zl->record_priority = record_priority ;
- if (zl != NULL)
- {
- zl->record_priority = record_priority;
- }
- VTY_UNLOCK() ;
+ LOG_UNLOCK() ;
}
-int
+/*------------------------------------------------------------------------------
+ * Get the timestamp precision setting
+ */
+extern int
zlog_get_timestamp_precision (struct zlog *zl)
{
- int result = 0;
+ int precision = 0;
- VTY_LOCK() ;
+ LOG_LOCK() ;
- if (zl == NULL)
- zl = zlog_default;
+ zl = zlog_actual(zl) ;
+ precision = (zl != NULL) ? zl->timestamp_precision : 0 ;
- if (zl != NULL)
- {
- result = zl->timestamp_precision;
- }
- VTY_UNLOCK() ;
- return result;
+ LOG_UNLOCK() ;
+ return precision ;
}
-void
+/*------------------------------------------------------------------------------
+ * Get the timestamp precision setting
+ */
+extern void
zlog_set_timestamp_precision (struct zlog *zl, int timestamp_precision)
{
- VTY_LOCK() ;
+ LOG_LOCK() ;
- if (zl == NULL)
- zl = zlog_default;
-
- if (zl != NULL)
- {
- zl->timestamp_precision = timestamp_precision;
- }
+ if ((zl = zlog_actual(zl)) != NULL)
+ zl->timestamp_precision = timestamp_precision;
- VTY_UNLOCK() ;
+ LOG_UNLOCK() ;
}
-/* returns name of ZLOG_NONE if no zlog given and no default set */
-const char *
+/*------------------------------------------------------------------------------
+ * Get name of the protocol -- name of ZLOG_NONE if no zlog set up yet.
+ */
+extern const char *
zlog_get_proto_name (struct zlog *zl)
{
- const char * result;
- VTY_LOCK() ;
- result = uzlog_get_proto_name(zl);
- VTY_UNLOCK() ;
- return result;
+ const char* name ;
+ LOG_LOCK() ;
+
+ zl = zlog_actual(zl) ;
+ name = zlog_proto_names[(zl != NULL) ? zl->protocol : ZLOG_NONE] ;
+
+ LOG_UNLOCK() ;
+ return name ;
}
-/* unprotected version, assumes mutex held */
-const char *
-uzlog_get_proto_name (struct zlog *zl)
+/*------------------------------------------------------------------------------
+ * caller must free result
+ */
+extern char *
+zlog_get_filename (struct zlog *zl)
{
- zlog_proto_t protocol = ZLOG_NONE;
+ char* name = NULL;
- if (zl == NULL)
- zl = zlog_default;
+ LOG_LOCK() ;
- if (zl != NULL)
- {
- protocol = zl->protocol;
- }
-
- return zlog_proto_names[protocol];
+ zl = zlog_actual(zl) ;
+ name = ((zl != NULL) && (zl->filename != NULL)) ? strdup(zl->filename)
+ : NULL ;
+ LOG_UNLOCK() ;
+ return name ;
}
-/* caller must free result */
-char *
-zlog_get_filename (struct zlog *zl)
+/*------------------------------------------------------------------------------
+ * Get address of ident string
+ */
+extern const char *
+zlog_get_ident (struct zlog *zl)
{
- char * result = NULL;
+ const char* ident ;
- VTY_LOCK() ;
-
- if (zl == NULL)
- zl = zlog_default;
+ LOG_LOCK() ;
- if (zl != NULL && zl->filename != NULL)
- {
- result = strdup(zl->filename);
- }
+ zl = zlog_actual(zl) ;
+ ident = (zl != NULL) ? zl->ident : NULL ;
- VTY_UNLOCK() ;
- return result;
+ LOG_UNLOCK() ;
+ return ident ;
}
-const char *
-zlog_get_ident (struct zlog *zl)
+/*------------------------------------------------------------------------------
+ * logging to a file?
+ */
+extern bool
+zlog_is_file (struct zlog *zl)
{
- const char * result = NULL;
+ bool is_file ;
- VTY_LOCK() ;
+ LOG_LOCK() ;
- if (zl == NULL)
- zl = zlog_default;
+ zl = zlog_actual(zl) ;
+ is_file = (zl != NULL) ? (zl->file_fd >= 0) : false ;
- if (zl != NULL)
- {
- result = zl->ident;
- }
-
- VTY_UNLOCK() ;
- return result;
+ LOG_UNLOCK() ;;
+ return is_file ;
}
-/* logging to a file? */
-int
-zlog_is_file (struct zlog *zl)
+/*------------------------------------------------------------------------------
+ * Setting of emaxlvl for given destination.
+ */
+inline static void
+uzlog_set_emaxlvl(struct zlog *zl, zlog_dest_t dest, bool test)
+{
+ zl->emaxlvl[dest] = test ? zl->maxlvl[dest] : ZLOG_DISABLED ;
+};
+
+/*------------------------------------------------------------------------------
+ * Update effective logging level for the given destination, and max_maxlvl.
+ *
+ * The effective logging level takes into account whether the destination is
+ * enabled or not.
+ */
+static void
+uzlog_set_effective_level(struct zlog *zl)
{
- int result = 0;
+ int emaxlvl ;
- VTY_LOCK() ;
+ LOG_ASSERT_LOCKED() ;
- if (zl == NULL)
- zl = zlog_default;
+ /* Re-establish the emaxlvl for this logging stream. */
+ uzlog_set_emaxlvl(zl, ZLOG_DEST_SYSLOG, zl->syslog ) ;
+ uzlog_set_emaxlvl(zl, ZLOG_DEST_FILE, zl->file_fd >= 0) ;
+ uzlog_set_emaxlvl(zl, ZLOG_DEST_STDOUT, zl->stdout_fd >= 0) ;
+ uzlog_set_emaxlvl(zl, ZLOG_DEST_MONITOR, zl->monitors > 0) ;
+ confirm(ZLOG_DEST_COUNT == 4) ;
- if (zl != NULL)
+ /* Scan all known logging streams, and re-establish max_maxlvl.
+ *
+ * Do not expect there to be many of these, or that we will change them very
+ * often -- so nothing clever, here.
+ */
+ emaxlvl = ZLOG_DISABLED ;
+ zl = zlog_list ;
+ while (zl != NULL)
{
- result = (zl->fp != NULL);
- }
+ uint i ;
+ for (i = 0 ; i < ZLOG_DEST_COUNT ; ++i)
+ if (emaxlvl < zl->emaxlvl[i])
+ emaxlvl = zl->emaxlvl[i] ;
+ zl = zl->next ;
+ } ;
- VTY_UNLOCK() ;;
- return result;
-}
+ max_maxlvl = emaxlvl ;
+} ;
+
+/*==============================================================================
+ *
+ */
/* Message lookup function. */
const char *
diff --git a/lib/log.h b/lib/log.h
index 6f4c8265..cf5bc9ec 100644
--- a/lib/log.h
+++ b/lib/log.h
@@ -25,6 +25,9 @@
#ifndef _ZEBRA_LOG_H
#define _ZEBRA_LOG_H
+#include "zconfig.h"
+#include "misc.h"
+#include <signal.h>
#include <syslog.h>
#include <stdio.h>
#include "vargs.h"
@@ -61,111 +64,117 @@ typedef enum
} zlog_proto_t;
/* If maxlvl is set to ZLOG_DISABLED, then no messages will be sent
- to that logging destination. */
-#define ZLOG_DISABLED (LOG_EMERG-1)
+ * to that logging destination.
+ */
+enum { ZLOG_DISABLED = LOG_EMERG - 1 } ;
typedef enum
{
ZLOG_DEST_SYSLOG = 0,
+ ZLOG_DEST_FILE,
ZLOG_DEST_STDOUT,
ZLOG_DEST_MONITOR,
- ZLOG_DEST_FILE
+
+ ZLOG_DEST_COUNT /* Number of destinations */
} zlog_dest_t;
-#define ZLOG_NUM_DESTS (ZLOG_DEST_FILE+1)
struct zlog
{
- const char *ident; /* daemon name (first arg to openlog) */
- zlog_proto_t protocol;
- int maxlvl[ZLOG_NUM_DESTS]; /* maximum priority to send to associated
- logging destination */
- int default_lvl; /* maxlvl to use if none is specified */
- FILE *fp;
- char *filename;
- int facility; /* as per syslog facility */
- int record_priority; /* should messages logged through stdio include the
- priority of the message? */
- int syslog_options; /* 2nd arg to openlog */
- int timestamp_precision; /* # of digits of subsecond precision */
-};
+ struct zlog* next ; /* To support multiple logging streams */
+
+ const char *ident; /* daemon name (first arg to openlog) */
+ zlog_proto_t protocol ;
+
+ int maxlvl[ZLOG_DEST_COUNT]; /* maximum priority set */
+ int default_lvl; /* maxlvl to use if none is specified */
+
+ int emaxlvl[ZLOG_DEST_COUNT]; /* effective maximum priority */
+
+ bool syslog ; /* have active syslog */
+ int file_fd ; /* fd for ZLOG_DEST_FILE (if any) */
+ int stdout_fd ; /* fd for ZLOG_DEST_STDOUT */
+ int monitors ; /* count of monitors */
+
+ char *filename; /* for ZLOG_DEST_FILE */
+
+ int facility; /* as per syslog facility */
+ int syslog_options; /* 2nd arg to openlog */
-/* Message structure. */
+ int timestamp_precision; /* # of digits of subsecond precision */
+ bool record_priority; /* should messages logged through stdio
+ include the priority of the message? */
+} ;
+
+/* Message structure. */
struct message
{
int key;
const char *str;
};
-/* module initialization */
+/* module initialization */
extern void zlog_init_r(void);
extern void zlog_destroy_r(void);
-/* Default logging structure. */
+/* Default logging structure. */
extern struct zlog *zlog_default;
-/* Open zlog function */
+/* Open zlog function */
extern struct zlog *openzlog (const char *progname, zlog_proto_t protocol,
int syslog_options, int syslog_facility);
-/* Close zlog function. */
+/* Close zlog function. */
extern void closezlog (struct zlog *zl);
-/* Generic function for zlog. */
+/* Generic function for zlog. */
extern void zlog (struct zlog *zl, int priority, const char *format, ...)
- PRINTF_ATTRIBUTE(3, 4);
-/* assumed locked version for close friends */
-extern void uzlog (struct zlog *zl, int priority, const char *format, ...)
- PRINTF_ATTRIBUTE(3, 4);
-
-/* Handy zlog functions. */
-extern void zlog_err (const char *format, ...) PRINTF_ATTRIBUTE(1, 2);
-extern void zlog_warn (const char *format, ...) PRINTF_ATTRIBUTE(1, 2);
-extern void zlog_info (const char *format, ...) PRINTF_ATTRIBUTE(1, 2);
-extern void zlog_notice (const char *format, ...) PRINTF_ATTRIBUTE(1, 2);
-extern void zlog_debug (const char *format, ...) PRINTF_ATTRIBUTE(1, 2);
+ PRINTF_ATTRIBUTE(3, 4);
+
+/* Handy zlog functions. */
+#define zlog_thus(thus, ...) zlog(zlog_default, thus, __VA_ARGS__)
+#define zlog_err(...) zlog(zlog_default, LOG_ERR, __VA_ARGS__)
+#define zlog_warn(...) zlog(zlog_default, LOG_WARNING, __VA_ARGS__)
+#define zlog_info(...) zlog(zlog_default, LOG_INFO, __VA_ARGS__)
+#define zlog_notice(...) zlog(zlog_default, LOG_NOTICE, __VA_ARGS__)
+#define zlog_debug(...) zlog(zlog_default, LOG_DEBUG, __VA_ARGS__)
/* For bgpd's peer oriented log. */
-extern void plog_err (struct zlog *, const char *format, ...)
- PRINTF_ATTRIBUTE(2, 3);
-extern void plog_warn (struct zlog *, const char *format, ...)
- PRINTF_ATTRIBUTE(2, 3);
-extern void plog_info (struct zlog *, const char *format, ...)
- PRINTF_ATTRIBUTE(2, 3);
-extern void plog_notice (struct zlog *, const char *format, ...)
- PRINTF_ATTRIBUTE(2, 3);
-extern void plog_debug (struct zlog *, const char *format, ...)
- PRINTF_ATTRIBUTE(2, 3);
+#define plog_thus(zl, thus, ...) zlog(zl, thus, __VA_ARGS__)
+#define plog_err(zl, ...) zlog(zl, LOG_ERR, __VA_ARGS__)
+#define plog_warn(zl, ...) zlog(zl, LOG_WARNING, __VA_ARGS__)
+#define plog_info(zl, ...) zlog(zl, LOG_INFO, __VA_ARGS__)
+#define plog_notice(zl, ...) zlog(zl, LOG_NOTICE, __VA_ARGS__)
+#define plog_debug(zl, ...) zlog(zl, LOG_DEBUG, __VA_ARGS__)
/* Set logging level for the given destination. If the log_level
argument is ZLOG_DISABLED, then the destination is disabled.
- This function should not be used for file logging (use zlog_set_file
+ This function should not be used for file logging (use uzlog_set_file
or zlog_reset_file instead). */
-extern void zlog_set_level (struct zlog *zl, zlog_dest_t, int log_level);
+extern void zlog_set_level(struct zlog *zl, zlog_dest_t, int log_level);
/* Set logging to the given filename at the specified level. */
-extern int zlog_set_file (struct zlog *zl, const char *filename, int log_level);
+extern int zlog_set_file(struct zlog *zl, const char *filename, int log_level);
/* Disable file logging. */
-extern int zlog_reset_file (struct zlog *zl);
+extern int zlog_reset_file(struct zlog *zl);
/* Rotate log. */
extern int zlog_rotate (struct zlog *);
/* getters & setters */
-extern int zlog_get_default_lvl (struct zlog *zl);
+extern int zlog_get_default_lvl (struct zlog *zl);
extern void zlog_set_default_lvl (struct zlog *zl, int level);
extern void zlog_set_default_lvl_dest (struct zlog *zl, int level);
-extern int zlog_get_maxlvl (struct zlog *zl, zlog_dest_t dest);
-extern int zlog_get_facility (struct zlog *zl);
+extern int zlog_get_maxlvl (struct zlog *zl, zlog_dest_t dest);
+extern int zlog_get_facility (struct zlog *zl);
extern void zlog_set_facility (struct zlog *zl, int facility);
-extern int zlog_get_record_priority (struct zlog *zl);
-extern void zlog_set_record_priority (struct zlog *zl, int record_priority);
-extern int zlog_get_timestamp_precision (struct zlog *zl);
+extern bool zlog_get_record_priority (struct zlog *zl);
+extern void zlog_set_record_priority (struct zlog *zl, bool record_priority);
+extern int zlog_get_timestamp_precision (struct zlog *zl);
extern void zlog_set_timestamp_precision (struct zlog *zl, int timestamp_precision);
extern const char * zlog_get_ident (struct zlog *zl);
extern char * zlog_get_filename (struct zlog *zl);
-extern int zlog_is_file (struct zlog *zl);
+extern bool zlog_is_file (struct zlog *zl);
extern const char * zlog_get_proto_name (struct zlog *zl);
-extern const char * uzlog_get_proto_name (struct zlog *zl);
/* For hackey massage lookup and check */
#define LOOKUP(x, y) mes_lookup(x, x ## _max, y, "(no item found)")
@@ -182,11 +191,8 @@ extern const char *zlog_proto_names[];
extern const char *safe_strerror(int errnum);
/* To be called when a fatal signal is caught. */
-extern void zlog_signal(int signo, const char *action
-#ifdef SA_SIGINFO
- , siginfo_t *siginfo, void *program_counter
-#endif
- );
+extern void zlog_signal(int signo, const char *action, siginfo_t *siginfo,
+ void *program_counter) ;
/* Ring down the curtain -- turn of SIGABRT handler and abort() */
extern void zabort_abort(void) __attribute__ ((noreturn)) ;
@@ -200,63 +206,6 @@ extern void zlog_backtrace(int priority);
that is logged in addition to the current backtrace. */
extern void zlog_backtrace_sigsafe(int priority, void *program_counter);
-/* Puts a current timestamp in buf and returns the number of characters
- * written (not including the terminating NUL). The purpose of
- * this function is to avoid calls to localtime appearing all over the code.
- * It caches the most recent localtime result and can therefore
- * avoid multiple calls within the same second.
- *
- * The buflen MUST be > 1 and the buffer address MUST NOT be NULL.
- *
- * If buflen is too small, writes buflen-1 characters followed by '\0'.
- *
- * Time stamp is rendered in the form: %Y/%m/%d %H:%M:%S
- *
- * This has a fixed length (leading zeros are included) of 19 characters
- * (unless this code is still in use beyond the year 9999 !)
- *
- * Which may be followed by "." and a number of decimal digits, usually 1..6.
- *
- * So the maximum time stamp is 19 + 1 + 6 = 26. Adding the trailing '\n', and
- * rounding up for good measure -- buffer size = 32.
- */
-#define TIMESTAMP_FORM "%Y/%m/%d %H:%M:%S"
-
-enum { timestamp_buffer_len = 32 } ;
-
-extern size_t quagga_timestamp(int timestamp_precision /* # subsecond digits */,
- char *buf, size_t buflen);
-
-/* Generate line to be logged
- *
- * Structure used to hold line for log output -- so that need be generated
- * just once even if output to multiple destinations.
- *
- * Note that the buffer length is a hard limit (including terminating '\n''\0'
- * or '\r''\n''\0'). Do not wish to malloc any larger buffer while logging.
- */
-enum { logline_buffer_len = 1008 } ;
-enum ll_term
-{
- llt_nul = 0, /* NB: also length of the terminator */
- llt_lf = 1,
- llt_crlf = 2,
-} ;
-
-struct logline {
- char* p_nl ; /* address of the terminator */
-
- enum ll_term term ; /* how line is terminated */
-
- size_t len ; /* length including either '\r''\n' or '\n' */
-
- char line[logline_buffer_len]; /* buffer */
-} ;
-
-extern void
-uvzlog_line(struct logline* ll, struct zlog *zl, int priority,
- const char *format, va_list va, enum ll_term term) ;
-
/* Defines for use in command construction: */
#define LOG_LEVELS "(emergencies|alerts|critical|errors|" \
diff --git a/lib/log_local.h b/lib/log_local.h
new file mode 100644
index 00000000..8e38bf48
--- /dev/null
+++ b/lib/log_local.h
@@ -0,0 +1,155 @@
+/* Logging locking and interface presented to vty et al
+ * Copyright (C) 2011 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_LOG_LOCAL_H
+#define _ZEBRA_LOG_LOCAL_H
+
+#include "misc.h"
+
+#include "qpthreads.h"
+
+/*==============================================================================
+ * This is for access to some things in log.c which are not required
+ * by external users, who use log.h.
+ *
+ * This is for use within the log/command/vty family.
+ *
+ * Should not be used with log.h ! (Except in log.c itself.)
+ *
+ * This may duplicate things published in log.h, but also includes things
+ * which are not intended for "external" use.
+ */
+
+/*==============================================================================
+ * To make logging qpthread safe we use a single mutex.
+ *
+ * A normal mutex is used -- recursion is not expected or required.
+ *
+ * There are some "ulog" functions which assume the mutex is locked.
+ *
+ * It is assumed that while the log_mutex is owned, NO OTHER SIGNIFICANT lock
+ * will be acquired -- in particular, NOT the VTY_LOCK() !!
+ */
+
+extern qpt_mutex_t log_mutex ;
+
+/*------------------------------------------------------------------------------
+ * Sort out LOG_DEBUG.
+ *
+ * Set to 1 if defined, but blank.
+ * Set to QDEBUG if not defined.
+ *
+ * Force to 0 if LOG_NO_DEBUG is defined and not zero.
+ *
+ * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to:
+ *
+ * * can set LOG_DEBUG == 0 to turn off debug
+ * * or set LOG_DEBUG != 0 to turn on debug
+ * * or set LOG_NO_DEBUG != 0 to force debug off
+ */
+
+#ifdef LOG_DEBUG /* If defined, make it 1 or 0 */
+# if IS_BLANK_OPTION(LOG_DEBUG)
+# undef LOG_DEBUG
+# define LOG_DEBUG 1
+# endif
+#else /* If not defined, follow QDEBUG */
+# define LOG_DEBUG QDEBUG
+#endif
+
+#ifdef LOG_NO_DEBUG /* Override, if defined */
+# if IS_NOT_ZERO_OPTION(LOG_NO_DEBUG)
+# undef LOG_DEBUG
+# define LOG_DEBUG 0
+# endif
+#endif
+
+enum { log_debug = LOG_DEBUG } ;
+
+/*------------------------------------------------------------------------------
+ * Locking and related functions.
+ */
+extern int log_lock_count ;
+
+Inline void
+LOG_LOCK(void) /* if is qpthreads_enabled, lock log_mutex */
+{
+ qpt_mutex_lock(log_mutex) ;
+ if (log_debug)
+ ++log_lock_count ;
+} ;
+
+Inline void
+LOG_UNLOCK(void) /* if is qpthreads_enabled, unlock log_mutex */
+{
+ if (log_debug)
+ --log_lock_count ;
+ qpt_mutex_unlock(log_mutex) ;
+} ;
+
+/* For debug (and documentation) purposes, will LOG_ASSERT_LOCKED where that
+ * is required.
+ *
+ * Note that both these checks will pass if !qpthreads_enabled. So can have
+ * code which is called before qpthreads are started up, or which will never
+ * run qpthreaded !
+ */
+
+extern int log_assert_fail ;
+
+Inline void
+LOG_ASSERT_FAILED(void)
+{
+ if (log_assert_fail == 0) ;
+ {
+ log_assert_fail = 1 ;
+ assert(0) ;
+ } ;
+} ;
+
+Inline void
+LOG_ASSERT_LOCKED(void)
+{
+ if (log_debug)
+ if ((log_lock_count == 0) && (qpthreads_enabled))
+ LOG_ASSERT_FAILED() ;
+} ;
+
+
+
+enum { timestamp_buffer_len = 32 } ;
+
+extern size_t quagga_timestamp(int timestamp_precision /* # subsecond digits */,
+ char *buf, size_t buflen);
+
+
+/*==============================================================================
+ * Functions in log.c -- used in any of the log/command/vty
+ */
+struct zlog ;
+
+extern void uzlog_add_monitor(struct zlog *zl, int count) ;
+
+extern void log_init_r(void) ;
+extern void log_finish(void);
+
+
+#endif /* _ZEBRA_LOG_LOCAL_H */
diff --git a/lib/memory.c b/lib/memory.c
index e15492c9..d96ff21d 100644
--- a/lib/memory.c
+++ b/lib/memory.c
@@ -35,8 +35,8 @@
*/
static qpt_mutex_t memory_mutex;
-#define LOCK qpt_mutex_lock(&memory_mutex);
-#define UNLOCK qpt_mutex_unlock(&memory_mutex);
+#define LOCK qpt_mutex_lock(memory_mutex);
+#define UNLOCK qpt_mutex_unlock(memory_mutex);
static void log_memstats(int log_priority);
@@ -50,8 +50,13 @@ static const struct message mstr [] =
{ 0, NULL },
};
-/* If using the mem_tracker, include it now. */
-
+/*------------------------------------------------------------------------------
+ * Include the memory tracker, if required.
+ *
+ * Makes sure that the tracker is always part of the source -- so kept up to
+ * date -- depending on dead code removal to eliminate overhead when not
+ * used.
+ */
typedef struct mem_tracker* mem_tracker ;
struct mem_tracker
{
@@ -72,9 +77,7 @@ mem_tracker_zeroise(struct mem_tracker* mem)
memset(mem, 0, sizeof(struct mem_tracker)) ;
} ;
-#if MEMORY_TRACKER
#include "mem_tracker.c"
-#endif
/*==============================================================================
* Keeping track of number of allocated objects of given type
@@ -130,9 +133,9 @@ zmalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME)
else
{
mstat.mt[mtype].alloc++;
-#if MEMORY_TRACKER
- mem_md_malloc(mtype, memory, size, name) ;
-#endif
+ if (memory_tracker)
+ mem_md_malloc(mtype, memory, size, MEMORY_TRACKER_NAME_ARG) ;
+
UNLOCK ;
} ;
@@ -159,9 +162,8 @@ zcalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME)
else
{
mstat.mt[mtype].alloc++;
-#if MEMORY_TRACKER
- mem_md_malloc(mtype, memory, size, name) ;
-#endif
+ if (memory_tracker)
+ mem_md_malloc(mtype, memory, size, MEMORY_TRACKER_NAME_ARG) ;
UNLOCK ;
} ;
@@ -170,6 +172,8 @@ zcalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME)
/*------------------------------------------------------------------------------
* Memory reallocation.
+ *
+ * NB: just like real realloc(), is same as malloc() if ptr == NULL.
*/
void *
zrealloc (enum MTYPE mtype, void *ptr, size_t size MEMORY_TRACKER_NAME)
@@ -188,9 +192,8 @@ zrealloc (enum MTYPE mtype, void *ptr, size_t size MEMORY_TRACKER_NAME)
{
if (ptr == NULL)
mstat.mt[mtype].alloc++;
-#if MEMORY_TRACKER
- mem_md_realloc(mtype, ptr, memory, size, name) ;
-#endif
+ if (memory_tracker)
+ mem_md_realloc(mtype, ptr, memory, size, MEMORY_TRACKER_NAME_ARG) ;
UNLOCK ;
} ;
@@ -199,6 +202,8 @@ zrealloc (enum MTYPE mtype, void *ptr, size_t size MEMORY_TRACKER_NAME)
/*------------------------------------------------------------------------------
* Memory free.
+ *
+ * NB: just like real free(), does nothing if ptr == NULL.
*/
void
zfree (enum MTYPE mtype, void *ptr)
@@ -210,9 +215,8 @@ zfree (enum MTYPE mtype, void *ptr)
assert(mstat.mt[mtype].alloc > 0) ;
mstat.mt[mtype].alloc--;
-#if MEMORY_TRACKER
- mem_md_free(mtype, ptr) ;
-#endif
+ if (memory_tracker)
+ mem_md_free(mtype, ptr) ;
free (ptr);
@@ -239,9 +243,9 @@ zstrdup (enum MTYPE mtype, const char *str MEMORY_TRACKER_NAME)
else
{
mstat.mt[mtype].alloc++;
-#if MEMORY_TRACKER
- mem_md_malloc(mtype, dup, strlen(str)+1, name) ;
-#endif
+ if (memory_tracker)
+ mem_md_malloc(mtype, dup, strlen(str)+1, MEMORY_TRACKER_NAME_ARG) ;
+
UNLOCK ;
} ;
@@ -443,11 +447,12 @@ show_memory_type_vty (struct vty *vty, const char* name,
vty_out (vty, "-----------------------------%s", VTY_NEWLINE) ;
vty_out (vty, "%-30s:", name) ;
-#if MEMORY_TRACKER
- show_memory_tracker_detail(vty, mt, alloc) ;
-#else
- vty_out (vty, " %10ld", alloc) ;
-#endif
+
+ if (memory_tracker)
+ show_memory_tracker_detail(vty, mt, alloc) ;
+ else
+ vty_out (vty, " %10ld", alloc) ;
+
vty_out (vty, "%s", VTY_NEWLINE);
} ;
@@ -464,15 +469,13 @@ show_memory_vty (struct vty *vty, struct memory_list *m, struct mlist* ml,
struct mem_tracker mem_one ;
struct mem_tracker* mt ;
-#if MEMORY_TRACKER
struct mem_type_tracker mem_tt ;
-#endif
LOCK ;
+
mst = mstat ;
-#if MEMORY_TRACKER
mem_tt = mem_type_tracker ;
-#endif
+
UNLOCK ;
mem_tracker_zeroise(&mem_tot) ;
@@ -499,12 +502,11 @@ show_memory_vty (struct vty *vty, struct memory_list *m, struct mlist* ml,
else
{
alloc = mst.mt[m->index].alloc ;
-#if MEMORY_TRACKER
- mt = &(mem_tt.mt[m->index]) ;
-#else
- mt = &mem_one ;
+ if (memory_tracker)
+ mt = &(mem_tt.mt[m->index]) ;
+ else
+ mt = &mem_one ;
mt->tracked_count = alloc ;
-#endif
mem_tot.malloc_count += mt->malloc_count ;
mem_tot.free_count += mt->free_count ;
@@ -580,23 +582,23 @@ DEFUN_CALL (show_memory_summary,
"Memory statistics\n"
"Summary memory statistics\n")
{
-#if MEMORY_TRACKER
- show_memory_tracker_summary(vty) ;
-#else
- long alloc = 0 ;
- int mtype ;
+ if (memory_tracker)
+ show_memory_tracker_summary(vty) ;
+ else
+ {
+ long alloc = 0 ;
+ int mtype ;
# ifdef HAVE_MALLINFO
- show_memory_mallinfo (vty);
+ show_memory_mallinfo (vty);
# endif /* HAVE_MALLINFO */
- LOCK ;
- for (mtype = 1 ; mtype < MTYPE_MAX ; ++mtype)
- alloc += mstat.mt[mtype].alloc ;
- UNLOCK
- vty_out(vty, "%ld items allocated%s", alloc, VTY_NEWLINE) ;
-
-#endif /* MEMORY_TRACKER */
+ LOCK ;
+ for (mtype = 1 ; mtype < MTYPE_MAX ; ++mtype)
+ alloc += mstat.mt[mtype].alloc ;
+ UNLOCK
+ vty_out(vty, "%ld items allocated%s", alloc, VTY_NEWLINE) ;
+ } ;
return CMD_SUCCESS;
}
@@ -613,9 +615,9 @@ DEFUN_CALL (show_memory_all,
#ifdef HAVE_MALLINFO
needsep |= show_memory_mallinfo (vty);
#endif /* HAVE_MALLINFO */
-#if MEMORY_TRACKER
- needsep |= show_memory_tracker_summary(vty) ;
-#endif
+
+ if (memory_tracker)
+ needsep |= show_memory_tracker_summary(vty) ;
show_memory_vty (vty, NULL, mlists, needsep);
@@ -720,14 +722,14 @@ DEFUN_CALL (show_memory_isis,
void
memory_init_r (void)
{
- qpt_mutex_init(&memory_mutex, qpt_mutex_quagga);
+ qpt_mutex_init(memory_mutex, qpt_mutex_quagga);
}
/* Finished with module */
void
memory_finish (void)
{
- qpt_mutex_destroy(&memory_mutex, 0);
+ qpt_mutex_destroy(memory_mutex, 0);
}
void
diff --git a/lib/memory.h b/lib/memory.h
index 4fccb079..0585307b 100644
--- a/lib/memory.h
+++ b/lib/memory.h
@@ -21,27 +21,37 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
#ifndef _ZEBRA_MEMORY_H
#define _ZEBRA_MEMORY_H
-#include <stddef.h>
-
-/* For pretty printing of memory allocate information. */
-struct memory_list
+#include "misc.h"
+
+/*------------------------------------------------------------------------------
+ * The file "lib/memtypes.h is created automatically (in the make) from
+ * memtypes.c, and contains a large enum which defines all the memory type
+ * names.
+ *
+ * The file memtypes.c contains a number of arrays of struct memory_list, which
+ * map memory type names to a string, for printing. Those arrays are
+ * collected together in an array of struvt mlist.
+ */
+struct memory_list /* one per memory type */
{
int index;
const char *format;
};
-struct mlist {
+struct mlist { /* one per class of memory types */
struct memory_list *list;
const char *name;
};
-extern struct mlist mlists[];
+extern struct mlist mlists[]; /* all classes of memory */
#include "lib/memtypes.h"
typedef enum MTYPE mtype_t ;
-/* #define MEMORY_LOG */
+/*------------------------------------------------------------------------------
+ * Option for logging memory operations.
+ */
#ifdef MEMORY_LOG
#define XMALLOC(mtype, size) \
mtype_zmalloc (__FILE__, __LINE__, (mtype), (size))
@@ -58,38 +68,61 @@ typedef enum MTYPE mtype_t ;
mtype_zstrdup (__FILE__, __LINE__, (mtype), (str))
#else
-#ifdef MEMORY_TRACKER /* Can be forced from outside */
-# if MEMORY_TRACKER
-# define MEMORY_TRACKER 1 /* Force 1 or 0 */
-#else
-# define MEMORY_TRACKER 0
+/*------------------------------------------------------------------------------
+ * Sort out MEMORY_TRACKER -- option to keep track of memory allocation.
+ *
+ * Set to 1 if defined, but blank.
+ * Set to QDEBUG if not defined.
+ *
+ * Force to 0 if NO_MEMORY_TRACKER is defined and not zero.
+ *
+ * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to:
+ *
+ * * can set MEMORY_TRACKER == 0 to turn off debug
+ * * or set MEMORY_TRACKER != 0 to turn on debug
+ * * or set VTY_NO_DEBUG != to force debug off
+ */
+
+#ifdef MEMORY_TRACKER /* If defined, make it 1 or 0 */
+# if IS_BLANK_OPTION(MEMORY_TRACKER)
+# undef MEMORY_TRACKER
+# define MEMORY_TRACKER 1
# endif
-#else
-# ifdef QDEBUG
-# define MEMORY_TRACKER 1 /* Follow QDEBUG */
-#else
+#else /* If not defined, follow QDEBUG */
+# define MEMORY_TRACKER QDEBUG
+#endif
+
+#ifdef NO_MEMORY_TRACKER /* Override, if defined. */
+# if IS_NOT_ZERO_OPTION(VTY_NO_DEBUG)
+# undef MEMORY_TRACKER
# define MEMORY_TRACKER 0
# endif
#endif
+enum { memory_tracker = MEMORY_TRACKER } ;
+
#if MEMORY_TRACKER
-#define MEMORY_TRACKER_NAME , const char* name
-#define MEMORY_TRACKER_FUNC , __func__
+#define MEMORY_TRACKER_NAME_ARG name
+#define MEMORY_TRACKER_NAME , const char* MEMORY_TRACKER_NAME_ARG
+#define MEMORY_TRACKER_FUNC , __func__
#else
+#define MEMORY_TRACKER_NAME_ARG "*dummy*"
#define MEMORY_TRACKER_NAME
#define MEMORY_TRACKER_FUNC
#endif
+/*------------------------------------------------------------------------------
+ * The macros used for all Quagga dynamic memory.
+ */
+
#define XMALLOC(mtype, size) zmalloc ((mtype), (size) \
MEMORY_TRACKER_FUNC)
#define XCALLOC(mtype, size) zcalloc ((mtype), (size) \
MEMORY_TRACKER_FUNC)
#define XREALLOC(mtype, ptr, size) zrealloc ((mtype), (ptr), (size) \
MEMORY_TRACKER_FUNC)
-#define XFREE(mtype, ptr) do { \
- zfree ((mtype), (ptr)); \
- ptr = NULL; } \
- while (0)
+#define XFREE(mtype, ptr) do { zfree ((mtype), (ptr)); \
+ ptr = NULL; } while (0)
#define XSTRDUP(mtype, str) zstrdup ((mtype), (str) \
MEMORY_TRACKER_FUNC)
diff --git a/lib/memtypes.c b/lib/memtypes.c
index 7e73e4c1..66d65328 100644
--- a/lib/memtypes.c
+++ b/lib/memtypes.c
@@ -57,6 +57,7 @@ struct memory_list memory_list_lib[] =
{ MTYPE_VIO_FIFO_LUMP, "VTY IO FIFO Lump" },
{ MTYPE_VIO_LC, "VTY IO Line Control" },
{ MTYPE_QSTRING, "qstring structure" },
+ { MTYPE_QPATH, "qpath structure" },
{ MTYPE_QIOVEC, "qiovec structure" },
{ MTYPE_QIOVEC_VEC, "qiovec iovec vector" },
{ MTYPE_IF, "Interface" },
diff --git a/lib/misc.h b/lib/misc.h
index 1bd0680b..1c53f2e2 100644
--- a/lib/misc.h
+++ b/lib/misc.h
@@ -87,6 +87,9 @@ typedef unsigned int uint ;
typedef unsigned int usize ;
typedef unsigned int ulen ;
+typedef int ssize ;
+typedef int slen ;
+
typedef unsigned long ulong ;
/* cvp == const void* -- ptr to constant void
@@ -95,4 +98,46 @@ typedef unsigned long ulong ;
*/
typedef const void* cvp ;
+/* Macros for sexing value of compilation options.
+ *
+ * In particular allow a blank option to be treated as true, and a zero option
+ * to be treated as false.
+ *
+ * NB: the option MUST be defined, and must be decimal numeric !!
+ */
+#define STRING_VALUE_INNER(x) #x
+#define STRING_VALUE(x) STRING_VALUE_INNER(x)
+
+#define IS_BLANK_OPTION(x) IS_BLANK_OPTION_INNER(x)
+#define IS_ZERO_OPTION(x) IS_ZERO_OPTION_INNER(x)
+#define IS_NOT_ZERO_OPTION(x) IS_NOT_OPTION_ZERO_INNER(x)
+
+#define IS_BLANK_OPTION_INNER(x) (1##x##1 == 11)
+#define IS_ZERO_OPTION_INNER(x) (1##x##1 == 101)
+#define IS_NOT_ZERO_OPTION_INNER(x) (1##x##1 != 101)
+
+/* If QDEBUG is defined, make QDEBUG_NAME and set QDEBUG
+ *
+ * Numeric value for QDEBUG: undefined => 0
+ * defined, blank => 1
+ * defined, 0 => 0
+ * defined, other => other
+ *
+ * Template for turning compilation option into a value.
+ */
+#ifdef QDEBUG
+# if IS_BLANK_OPTION(QDEBUG)
+# undef QDEBUG
+# define QDEBUG 1
+# endif
+#else
+# define QDEBUG 0
+#endif
+
+enum { qdebug = QDEBUG } ;
+
+#ifndef QDEBUG_NAME
+# define QDEBUG_NAME STRING_VALUE(QDEBUG)
+#endif
+
#endif /* _ZEBRA_MISC_H */
diff --git a/lib/mqueue.c b/lib/mqueue.c
index c7037a64..8cef1ad9 100644
--- a/lib/mqueue.c
+++ b/lib/mqueue.c
@@ -211,7 +211,7 @@ mqueue_init_new(mqueue_queue mq, enum mqueue_queue_type type)
memset(mq, 0, sizeof(struct mqueue_queue)) ;
if (qpt_freeze_qpthreads_enabled())
- qpt_mutex_init_new(&mq->mutex, qpt_mutex_quagga) ;
+ qpt_mutex_init_new(mq->mutex, qpt_mutex_quagga) ;
/* head, tail and tail_priority set NULL already */
/* count set zero already */
@@ -222,7 +222,7 @@ mqueue_init_new(mqueue_queue mq, enum mqueue_queue_type type)
{
case mqt_cond_unicast:
case mqt_cond_broadcast:
- qpt_cond_init_new(&mq->kick.cond.wait_here, qpt_cond_quagga) ;
+ qpt_cond_init_new(mq->kick.cond.wait_here, qpt_cond_quagga) ;
if (MQUEUE_DEFAULT_INTERVAL != 0)
{
@@ -277,13 +277,13 @@ mqueue_reset(mqueue_queue mq, free_keep_b free_structure)
passert(mq->waiters == 0) ;
- qpt_mutex_destroy_keep(&mq->mutex) ;
+ qpt_mutex_destroy_keep(mq->mutex) ;
switch (mq->type)
{
case mqt_cond_unicast:
case mqt_cond_broadcast:
- qpt_cond_destroy_keep(&mq->kick.cond.wait_here) ;
+ qpt_cond_destroy_keep(mq->kick.cond.wait_here) ;
break;
case mqt_signal_unicast:
@@ -362,7 +362,7 @@ mqueue_local_reset(mqueue_local_queue lmq, free_keep_b free_structure)
extern void
mqueue_set_timeout_interval(mqueue_queue mq, qtime_t interval)
{
- qpt_mutex_lock(&mq->mutex) ;
+ qpt_mutex_lock(mq->mutex) ;
dassert( (mq->type == mqt_cond_unicast) ||
(mq->type == mqt_cond_broadcast) ) ;
@@ -370,7 +370,7 @@ mqueue_set_timeout_interval(mqueue_queue mq, qtime_t interval)
mq->kick.cond.interval = interval ;
mq->kick.cond.timeout = (interval > 0) ? qt_add_monotonic(interval)
: 0 ;
- qpt_mutex_unlock(&mq->mutex) ;
+ qpt_mutex_unlock(mq->mutex) ;
} ;
/*==============================================================================
@@ -484,9 +484,12 @@ mqb_re_init(mqueue_block mqb, mqueue_action action, void* arg0)
* NB: it is the caller's responsibility to free the value of any argument that
* requires it.
*/
-extern void
+extern mqueue_block
mqb_free(mqueue_block mqb)
{
+ if (mqb == NULL)
+ return NULL ;
+
if (mqb->argv != NULL)
XFREE(MTYPE_MQUEUE_BLOCK_ARGV, mqb->argv) ;
@@ -497,6 +500,8 @@ mqb_free(mqueue_block mqb)
++mqb_free_count ;
qpt_mutex_unlock(&mqb_mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
+
+ return NULL ;
} ;
/*==============================================================================
@@ -538,7 +543,7 @@ mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, mqb_rank_b priority)
if (mq == NULL)
return mqb_dispatch_destroy(mqb) ;
- qpt_mutex_lock(&mq->mutex) ;
+ qpt_mutex_lock(mq->mutex) ;
if (mq->head == NULL)
{
@@ -588,12 +593,12 @@ mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, mqb_rank_b priority)
switch (mq->type)
{
case mqt_cond_unicast:
- qpt_cond_signal(&mq->kick.cond.wait_here) ;
+ qpt_cond_signal(mq->kick.cond.wait_here) ;
--mq->waiters ;
break ;
case mqt_cond_broadcast:
- qpt_cond_broadcast(&mq->kick.cond.wait_here) ;
+ qpt_cond_broadcast(mq->kick.cond.wait_here) ;
mq->waiters = 0 ;
break ;
@@ -612,7 +617,7 @@ mqueue_enqueue(mqueue_queue mq, mqueue_block mqb, mqb_rank_b priority)
} ;
} ;
- qpt_mutex_unlock(&mq->mutex) ;
+ qpt_mutex_unlock(mq->mutex) ;
} ;
/*------------------------------------------------------------------------------
@@ -655,7 +660,7 @@ mqueue_dequeue(mqueue_queue mq, int wait, void* arg)
if (mq == NULL)
return NULL ;
- qpt_mutex_lock(&mq->mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
+ qpt_mutex_lock(mq->mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
while (1)
{
@@ -676,13 +681,13 @@ mqueue_dequeue(mqueue_queue mq, int wait, void* arg)
case mqt_cond_unicast: /* Now wait here */
case mqt_cond_broadcast:
if ((arg == NULL) && (mq->kick.cond.interval <= 0))
- qpt_cond_wait(&mq->kick.cond.wait_here, &mq->mutex) ;
+ qpt_cond_wait(mq->kick.cond.wait_here, mq->mutex) ;
else
{
timeout_time = (arg != NULL) ? *(qtime_mono_t*)arg
: mq->kick.cond.timeout ;
- if (qpt_cond_timedwait(&mq->kick.cond.wait_here, &mq->mutex,
+ if (qpt_cond_timedwait(mq->kick.cond.wait_here, mq->mutex,
timeout_time) == 0)
{
/* Timed out -- update timeout time, if required */
@@ -742,7 +747,7 @@ mqueue_dequeue(mqueue_queue mq, int wait, void* arg)
mq->tail_priority = NULL ;
done:
- qpt_mutex_unlock(&mq->mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
+ qpt_mutex_unlock(mq->mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
return mqb ;
} ;
@@ -783,7 +788,7 @@ mqueue_revoke(mqueue_queue mq, void* arg0, int num)
if (mq == NULL)
return 0 ;
- qpt_mutex_lock(&mq->mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
+ qpt_mutex_lock(mq->mutex) ; /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/
did = 0 ;
prev = NULL ;
@@ -827,7 +832,7 @@ mqueue_revoke(mqueue_queue mq, void* arg0, int num)
} ;
} ;
- qpt_mutex_unlock(&mq->mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
+ qpt_mutex_unlock(mq->mutex) ; /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
return did ;
} ;
@@ -847,7 +852,7 @@ mqueue_done_waiting(mqueue_queue mq, mqueue_thread_signal mtsig)
if (!qpthreads_enabled)
return 0 ;
- qpt_mutex_lock(&mq->mutex) ;
+ qpt_mutex_lock(mq->mutex) ;
dassert( (mq->type == mqt_signal_unicast) ||
(mq->type == mqt_signal_broadcast) ) ;
@@ -864,7 +869,7 @@ mqueue_done_waiting(mqueue_queue mq, mqueue_thread_signal mtsig)
if (!kicked)
mqueue_dequeue_signal(mq, mtsig) ;
- qpt_mutex_unlock(&mq->mutex) ;
+ qpt_mutex_unlock(mq->mutex) ;
return kicked ;
} ;
diff --git a/lib/mqueue.h b/lib/mqueue.h
index f33af564..c787e19b 100644
--- a/lib/mqueue.h
+++ b/lib/mqueue.h
@@ -213,7 +213,7 @@ extern mqueue_block mqb_init_new(mqueue_block mqb, mqueue_action action,
void* arg0) ;
extern mqueue_block mqb_re_init(mqueue_block mqb, mqueue_action action,
void* arg0) ;
-extern void mqb_free(mqueue_block mqb) ;
+extern mqueue_block mqb_free(mqueue_block mqb) ;
enum mqb_rank
{
diff --git a/lib/network.c b/lib/network.c
index ae67a402..57cca0d4 100644
--- a/lib/network.c
+++ b/lib/network.c
@@ -20,9 +20,29 @@
* 02111-1307, USA.
*/
-#include <zebra.h>
+//#include <zebra.h>
+
+#include "misc.h"
+#include "qdebug_nb.h"
+
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+
#include "log.h"
#include "network.h"
+#include "memory.h"
+
+/*==============================================================================
+ * Read and write loops -- assuming blocking or not-blocking.
+ *
+ *
+ *
+ */
+
+static ssize_t read_qdebug_nb(int fd, void* buf, size_t nbyte) ;
+static ssize_t write_qdebug_nb(int fd, const void* buf, size_t nbyte) ;
/*------------------------------------------------------------------------------
* Read nbytes from fd and store into ptr -- BLOCKING
@@ -34,8 +54,8 @@
*
* NB: if applied to a NON-BLOCKING fd, may return EAGAIN or EWOULDBLOCK
*/
-int
-readn (int fd, u_char *ptr, int nbytes)
+extern int
+readn (int fd, void* buf, int nbytes)
{
int nleft;
int nread;
@@ -44,15 +64,17 @@ readn (int fd, u_char *ptr, int nbytes)
while (nleft > 0)
{
- nread = read (fd, ptr, nleft);
+ nread = read (fd, buf, nleft);
if (nread > 0)
{
nleft -= nread;
- ptr += nread;
+ buf = (char*)buf + nread;
}
+
else if (nread == 0)
break;
+
else
{
if (errno != EINTR)
@@ -73,57 +95,122 @@ readn (int fd, u_char *ptr, int nbytes)
* -1 => failed -- see errno
* -2 => EOF met immediately
*
- * NB: if asked to write zero bytes, does nothing and will return 0.
+ * NB: if asked to read zero bytes, does nothing and will return 0.
*
* Reading zero bytes is defined for all types of files, and may be used
* to probe for error state.
*/
-int
+extern int
read_nb(int fd, void* buf, size_t nbyte)
{
- size_t nleft = nbyte ;
+ size_t nleft ;
+
+// char* p = buf ;
+
+ nleft = nbyte ;
do
{
- int ret = read(fd, buf, nleft);
+ ssize_t ret ;
+
+ if (qdebug_nb)
+ ret = read_qdebug_nb(fd, buf, nleft) ;
+ else
+ ret = read(fd, buf, nleft) ;
if (ret > 0)
{
buf = (char*)buf + ret ;
nleft -= ret ;
}
+
else if (ret == 0)
{
if (nleft < nbyte)
break ; /* if read something before EOF */
- return -2 ; /* hit EOF immediately */
+// fprintf(stderr, "[read (%d) %s]\n", fd, (nbyte == 0) ? "OK" : "EOF") ;
+
+ return (nbyte == 0) ? 0 : -2 ; /* OK or EOF */
}
+
else
{
int err = errno ;
if ((err == EAGAIN) || (err == EWOULDBLOCK))
break ;
if (err != EINTR)
+// assert(0) ; // PRO TEM
return -1 ; /* failed */
} ;
} while (nleft > 0) ;
+#if 0
+{
+ int n ;
+ char buffer[100] ;
+ char* q, * e ;
+ e = buffer + 95 ;
+
+ n = nbyte - nleft ;
+
+ fprintf(stderr, "[read (%d) %d: '", fd, n) ;
+ while (n > 0)
+ {
+ q = buffer ;
+ while ((q < e) && (n > 0))
+ {
+ char ch = *p++ ;
+ --n ;
+
+ if (ch < 0x20)
+ {
+ *q++ = '\\' ;
+ switch (ch)
+ {
+ case '\n':
+ ch = 'n' ;
+ break ;
+
+ case '\r':
+ ch = 'r' ;
+ break ;
+
+ case '\t':
+ ch = 't' ;
+ break ;
+
+ default:
+ ch += '@' ;
+ break ;
+ } ;
+ } ;
+
+ *q++ = ch ;
+ } ;
+ *q++ = '\0' ;
+
+ fprintf(stderr, "%s", buffer) ;
+ }
+ fprintf(stderr, "']\n") ;
+} ;
+#endif
+
return (nbyte - nleft) ;
} ;
/*------------------------------------------------------------------------------
- * Write nbytes to fd from ptr -- BLOCKING
+ * Write nbytes to fd from buf -- BLOCKING
*
* Loops internally if gets EINTR.
*
* Returns: >= 0 -- number of bytes written
- * < 0 => error
+ * -1 => error
*
* NB: if applied to a NON-BLOCKING fd, may return EAGAIN or EWOULDBLOCK
*/
-int
-writen(int fd, const u_char *ptr, int nbytes)
+extern int
+writen(int fd, const void* buf, int nbytes)
{
int nleft;
int nwritten;
@@ -132,21 +219,23 @@ writen(int fd, const u_char *ptr, int nbytes)
while (nleft > 0)
{
- nwritten = write(fd, ptr, nleft);
+ nwritten = write(fd, buf, nleft);
- if (nwritten > 0)
+ if (nwritten >= 0)
{
+ /* write() is not expected to return 0 unless the request is 0,
+ * which in this case it isn't. So cannot happen -- and if it did,
+ * wouldn't know what else to do with it !
+ */
nleft -= nwritten;
- ptr += nwritten;
+ buf = (const char*)buf + nwritten;
}
- else if (nwritten == 0)
- break ;
else
{
if (errno != EINTR)
- return (nwritten);
- }
- }
+ return -1 ;
+ } ;
+ } ;
return nbytes - nleft;
}
@@ -163,29 +252,39 @@ writen(int fd, const u_char *ptr, int nbytes)
* Writing zero bytes is defined for "regular files", but not for anything
* else.
*/
-int
-write_nb(int fd, void* buf, size_t nbyte)
+extern int
+write_nb(int fd, const void* buf, size_t nbyte)
{
- size_t nleft = nbyte ;
+ size_t nleft ;
+
+ nleft = nbyte ;
while (nleft > 0)
{
- int ret = write(fd, buf, nleft);
+ ssize_t ret ;
- if (ret > 0)
+ if (qdebug_nb)
+ ret = write_qdebug_nb(fd, buf, nleft) ;
+ else
+ ret = write(fd, buf, nleft) ;
+
+ if (ret > 0)
{
- buf = (char*)buf + ret ;
+ buf = (const char*)buf + ret ;
nleft -= ret ;
}
else if (ret == 0)
- break ; /* not sure can happen... but
- cannot assume will go away */
+ /* write() is not expected to return 0 unless the request is 0,
+ * which in this case it isn't. So cannot happen -- but if it were
+ * to happen, this treats it as another form of "EAGAIN" !
+ */
+ break ;
else
{
- int err = errno ;
- if ((err == EAGAIN) || (err == EWOULDBLOCK))
+ if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
break ;
- if (err != EINTR)
+ if (errno != EINTR)
+// assert(0) ; // PRO TEM
return -1 ; /* failed */
} ;
} ;
@@ -194,12 +293,46 @@ write_nb(int fd, void* buf, size_t nbyte)
} ;
/*------------------------------------------------------------------------------
+ * Copy from one fd to another -- blocking.
+ *
+ * NB: if applied to a NON-BLOCKING fd, may return EAGAIN or EWOULDBLOCK
+ */
+extern int
+copyn (int dst_fd, int src_fd)
+{
+ void* buffer ;
+ int r, err ;
+
+ enum { buffer_size = 64 * 1024 } ;
+ buffer = XMALLOC(MTYPE_TMP, buffer_size) ;
+
+ do
+ {
+ r = readn(src_fd, buffer, buffer_size) ;
+ if (r > 0)
+ r = writen(dst_fd, buffer, r) ;
+ }
+ while (r > 0) ;
+
+ err = errno ;
+
+ XFREE(MTYPE_TMP, buffer) ;
+
+ errno = err ;
+ return r ;
+} ;
+
+/*==============================================================================
+ *
+ */
+
+/*------------------------------------------------------------------------------
* Set fd to non-blocking
*
* Returns: 0 => OK
* -1 => failed
*/
-int
+extern int
set_nonblocking(int fd)
{
int flags;
@@ -220,3 +353,52 @@ set_nonblocking(int fd)
}
return 0;
}
+
+/*==============================================================================
+ * Simulate read/write with tiny buffers and a lot of blocking.
+ */
+
+static qrand_seq_t rseq = QRAND_SEQ_INIT(2001) ;
+static qrand_seq_t wseq = QRAND_SEQ_INIT(3001) ;
+
+static const int blocking_errs[] = { EAGAIN, EWOULDBLOCK, EINTR } ;
+
+/*------------------------------------------------------------------------------
+ * Simulate read(), with tiny input buffer and lots of blocking.
+ */
+static ssize_t
+read_qdebug_nb(int fd, void* buf, size_t nbyte)
+{
+ if (nbyte > 0)
+ {
+ if (qrand(rseq, 3) == 0) /* 1/3 chance of blocking */
+ {
+ errno = blocking_errs[qrand(rseq, 3)] ;
+ return -1 ;
+ } ;
+
+ nbyte = qrand(rseq, (nbyte < 200) ? nbyte : 200) + 1 ;
+ } ;
+
+
+ return read(fd, buf, nbyte) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Simulate write(), with tiny input buffer and lots of blocking.
+ */
+static ssize_t
+write_qdebug_nb(int fd, const void* buf, size_t nbyte)
+{
+ assert(nbyte > 0) ;
+
+ if (qrand(wseq, 3) == 0) /* 1/3 chance of blocking */
+ {
+ errno = blocking_errs[qrand(wseq, 3)] ;
+ return -1 ;
+ } ;
+
+ nbyte = qrand(wseq, (nbyte < 200) ? nbyte : 200) + 1 ;
+
+ return write(fd, buf, nbyte) ;
+} ;
diff --git a/lib/network.h b/lib/network.h
index 72d38b52..0e626e72 100644
--- a/lib/network.h
+++ b/lib/network.h
@@ -26,8 +26,9 @@
/* Both readn and writen are deprecated and will be removed. They are not
suitable for use with non-blocking file descriptors.
*/
-extern int readn (int, u_char *, int);
-extern int writen (int, const u_char *, int);
+extern int readn (int, void*, int);
+extern int writen (int, const void*, int);
+extern int copyn (int dst_fd, int src_fd) ;
/* Set the file descriptor to use non-blocking I/O. Returns 0 for success,
-1 on error. */
@@ -35,7 +36,7 @@ extern int set_nonblocking(int fd);
/* Non-Blocking versions of read/write */
int read_nb(int fd, void* buf, size_t nbyte) ;
-int write_nb(int fd, void* buf, size_t nbyte) ;
+int write_nb(int fd, const void* buf, size_t nbyte) ;
/* Does the I/O error indicate that the operation should be retried later? */
#define ERRNO_IO_RETRY(EN) \
diff --git a/lib/privs.c b/lib/privs.c
index be3265ed..bcee6228 100644
--- a/lib/privs.c
+++ b/lib/privs.c
@@ -21,7 +21,8 @@
* Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
-#include <zebra.h>
+#include "zebra.h"
+#include "zconfig.h"
#include "log.h"
#include "privs.h"
#include "memory.h"
@@ -29,8 +30,8 @@
/* Needs to be qpthread safe */
static qpt_mutex_t privs_mutex;
-#define LOCK qpt_mutex_lock(&privs_mutex);
-#define UNLOCK qpt_mutex_unlock(&privs_mutex);
+#define LOCK qpt_mutex_lock(privs_mutex);
+#define UNLOCK qpt_mutex_unlock(privs_mutex);
#ifdef HAVE_CAPABILITIES
/* sort out some generic internal types for:
@@ -675,20 +676,28 @@ zprivs_state_null (void)
void
zprivs_init_r()
{
- qpt_mutex_init(&privs_mutex, qpt_mutex_quagga);
+ qpt_mutex_init(privs_mutex, qpt_mutex_quagga);
}
void
zprivs_finish(void)
{
- qpt_mutex_destroy(&privs_mutex, 0);
+ qpt_mutex_destroy(privs_mutex, 0);
}
+/*------------------------------------------------------------------------------
+ * Initialise privilege handling and set any running group.
+ *
+ * Returns with lowered privileges.
+ *
+ * Must be initialised before vty. Will be initialised very early in the
+ * morning, before daemonisation and therefore before any pthreads.
+ */
void
zprivs_init(struct zebra_privs_t *zprivs)
{
struct passwd *pwentry = NULL;
- struct group *grentry = NULL;
+ struct group *grentry = NULL;
if (!zprivs)
{
@@ -696,18 +705,18 @@ zprivs_init(struct zebra_privs_t *zprivs)
exit (1);
}
- LOCK
-
- /* NULL privs */
- if (! (zprivs->user || zprivs->group
- || zprivs->cap_num_p || zprivs->cap_num_i) )
+ /* NULL privs */
+ if ( (zprivs->user == NULL) && (zprivs->group == NULL)
+ && (zprivs->cap_num_p == 0)
+ && (zprivs->cap_num_i == 0) )
{
- zprivs->change = zprivs_change_null;
+ zprivs->change = zprivs_change_null;
zprivs->current_state = zprivs_state_null;
- UNLOCK
+
return;
}
+ /* Get uid for configured user (if any) */
if (zprivs->user)
{
if ( (pwentry = getpwnam (zprivs->user)) )
@@ -719,11 +728,11 @@ zprivs_init(struct zebra_privs_t *zprivs)
/* cant use log.h here as it depends on vty */
fprintf (stderr, "privs_init: could not lookup user %s\n",
zprivs->user);
- UNLOCK
exit (1);
}
}
+ /* Get gid for vty_group and add to our groups */
grentry = NULL;
if (zprivs->vty_group)
@@ -736,7 +745,6 @@ zprivs_init(struct zebra_privs_t *zprivs)
{
fprintf (stderr, "privs_init: could not setgroups, %s\n",
errtostr(errno, 0).str) ;
- UNLOCK
exit (1);
}
}
@@ -744,11 +752,11 @@ zprivs_init(struct zebra_privs_t *zprivs)
{
fprintf (stderr, "privs_init: could not lookup vty group %s\n",
zprivs->vty_group);
- UNLOCK
exit (1);
}
}
+ /* Get gid for configured group and switch to same, now. */
if (zprivs->group)
{
if ( (grentry = getgrnam (zprivs->group)) )
@@ -759,15 +767,13 @@ zprivs_init(struct zebra_privs_t *zprivs)
{
fprintf (stderr, "privs_init: could not lookup group %s\n",
zprivs->group);
- UNLOCK
exit (1);
}
- /* change group now, forever. uid we do later */
+ /* change group now, forever. uid we do later */
if ( setregid (zprivs_state.zgid, zprivs_state.zgid) )
{
fprintf (stderr, "zprivs_init: could not setregid, %s\n",
errtostr(errno, 0).str) ;
- UNLOCK
exit (1);
}
}
@@ -775,7 +781,8 @@ zprivs_init(struct zebra_privs_t *zprivs)
#ifdef HAVE_CAPABILITIES
zprivs_caps_init (zprivs);
#else /* !HAVE_CAPABILITIES */
- /* we dont have caps. we'll need to maintain rid and saved uid
+
+ /* We don't have caps. we'll need to maintain rid and saved uid
* and change euid back to saved uid (who we presume has all necessary
* privileges) whenever we are asked to raise our privileges.
*
@@ -784,20 +791,23 @@ zprivs_init(struct zebra_privs_t *zprivs)
zprivs_state.zsuid = geteuid();
if ( zprivs_state.zuid )
{
+ /* If the zuid != real uid, will set the saved uid == zuid, leaving
+ * just the two zuid to choose from -- though if the real uid is root,
+ * this makes little difference.
+ */
if ( setreuid (-1, zprivs_state.zuid) )
{
fprintf (stderr, "privs_init (uid): could not setreuid, %s\n",
errtoa(errno, 0).str);
- UNLOCK
exit (1);
}
}
- zprivs->change = zprivs_change_uid;
- zprivs->current_state = zprivs_state_uid;
+ zprivs->change = zprivs_change_uid ;
+ zprivs->current_state = zprivs_state_uid ;
+
#endif /* HAVE_CAPABILITIES */
- UNLOCK
}
void
@@ -826,9 +836,9 @@ zprivs_terminate (struct zebra_privs_t *zprivs)
}
#endif /* HAVE_LCAPS */
- zprivs->change = zprivs_change_null;
+ zprivs->change = zprivs_change_null;
zprivs->current_state = zprivs_state_null;
- zprivs_null_state = ZPRIVS_LOWERED;
+ zprivs_null_state = ZPRIVS_LOWERED;
raise_count = 0;
UNLOCK
@@ -841,12 +851,12 @@ zprivs_get_ids(struct zprivs_ids_t *ids)
LOCK
ids->uid_priv = getuid();
- (zprivs_state.zuid) ? (ids->uid_normal = zprivs_state.zuid)
- : (ids->uid_normal = -1);
- (zprivs_state.zgid) ? (ids->gid_normal = zprivs_state.zgid)
- : (ids->gid_normal = -1);
+ (zprivs_state.zuid) ? (ids->uid_normal = zprivs_state.zuid)
+ : (ids->uid_normal = -1);
+ (zprivs_state.zgid) ? (ids->gid_normal = zprivs_state.zgid)
+ : (ids->gid_normal = -1);
(zprivs_state.vtygrp) ? (ids->gid_vty = zprivs_state.vtygrp)
- : (ids->gid_vty = -1);
+ : (ids->gid_vty = -1);
UNLOCK
return;
diff --git a/lib/pthread_safe.c b/lib/pthread_safe.c
index e2c7cdc0..828b8e29 100644
--- a/lib/pthread_safe.c
+++ b/lib/pthread_safe.c
@@ -203,7 +203,7 @@ errtox(strerror_t* st, int err, int len, int want)
if ((len <= 0) || (len >= (int)sizeof(st->str)))
len = sizeof(st->str) - 1 ;
- qfs_init(&qfs, st->str, len + 1) ;
+ qfs_init(qfs, st->str, len + 1) ;
q = "" ;
ql = 0 ;
@@ -214,15 +214,15 @@ errtox(strerror_t* st, int err, int len, int want)
const char* name = errno_name_lookup(err) ;
if (name != NULL)
- qfs_append(&qfs, name) ;
+ qfs_append(qfs, name) ;
else
- qfs_printf(&qfs, "ERRNO=%d", err) ;
+ qfs_printf(qfs, "ERRNO=%d", err) ;
} ;
/* name and string ? */
if (want == 3)
{
- qfs_append(&qfs, " ") ;
+ qfs_append(qfs, " ") ;
q = "'" ;
ql = 2 ;
} ;
@@ -285,8 +285,8 @@ errtox(strerror_t* st, int err, int len, int want)
else
{
qf_str_t qfs_b ;
- qfs_init(&qfs_b, buf, sizeof(buf)) ;
- qfs_printf(&qfs_b, "strerror%s(%d) returned error %d",
+ qfs_init(qfs_b, buf, sizeof(buf)) ;
+ qfs_printf(qfs_b, "strerror%s(%d) returned error %d",
qpthreads_enabled ? "_r" : "", err, ret) ;
errm = buf ;
} ;
@@ -296,11 +296,11 @@ errtox(strerror_t* st, int err, int len, int want)
/* Add strerror to the result... looking out for overflow. */
len = strlen(errm) ;
- if ((len + ql) <= qfs_left(&qfs)) /* accounting for "quotes" */
- qfs_printf(&qfs, "%s%s%s", q, errm, q) ;
+ if ((len + ql) <= qfs_left(qfs)) /* accounting for "quotes" */
+ qfs_printf(qfs, "%s%s%s", q, errm, q) ;
else
- qfs_printf(&qfs, "%s%.*s...%s",
- q, (int)(qfs_left(&qfs) - ql - 3), errm, q) ;
+ qfs_printf(qfs, "%s%.*s...%s",
+ q, (int)(qfs_left(qfs) - ql - 3), errm, q) ;
/* -ve precision is ignored ! */
} ;
@@ -421,7 +421,7 @@ eaitox(strerror_t* st, int eai, int err, int len, int want)
if ((len <= 0) || (len >= (int)sizeof(st->str)))
len = sizeof(st->str) - 1 ;
- qfs_init(&qfs, st->str, len + 1) ;
+ qfs_init(qfs, st->str, len + 1) ;
q = "" ;
ql = 0 ;
@@ -432,15 +432,15 @@ eaitox(strerror_t* st, int eai, int err, int len, int want)
const char* name = eaino_name_lookup(eai) ;
if (name != NULL)
- qfs_append(&qfs, name) ;
+ qfs_append(qfs, name) ;
else
- qfs_printf(&qfs, "EAI=%d", eai) ;
+ qfs_printf(qfs, "EAI=%d", eai) ;
} ;
/* name and string ? */
if (want == 3)
{
- qfs_append(&qfs, " ") ;
+ qfs_append(qfs, " ") ;
q = "'" ;
ql = 2 ;
} ;
@@ -458,10 +458,10 @@ eaitox(strerror_t* st, int eai, int err, int len, int want)
/* Add strerror to the result... looking out for overflow. */
len = strlen(eaim) ;
- if ((len + ql) <= qfs_left(&qfs)) /* accounting for "quotes" */
- qfs_printf(&qfs, "%s%s%s", q, eaim, q) ;
+ if ((len + ql) <= qfs_left(qfs)) /* accounting for "quotes" */
+ qfs_printf(qfs, "%s%s%s", q, eaim, q) ;
else
- qfs_printf(&qfs, "%s%.*s...%s", q, qfs_left(&qfs) - ql - 3, eaim, q) ;
+ qfs_printf(qfs, "%s%.*s...%s", q, qfs_left(qfs) - ql - 3, eaim, q) ;
/* -ve precision is ignored ! */
} ;
diff --git a/lib/qdebug_nb.h b/lib/qdebug_nb.h
new file mode 100644
index 00000000..6da2a120
--- /dev/null
+++ b/lib/qdebug_nb.h
@@ -0,0 +1,61 @@
+/* Debug definitions for non-blocking I/O testing
+ * Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_QDEBUG_NB_H
+#define _ZEBRA_QDEBUG_NB_H
+
+#include "misc.h"
+#include "qrand.h"
+
+/*------------------------------------------------------------------------------
+ * Sort out QDEBUG_NB_DEBUG.
+ *
+ * Set to 1 if defined, but blank.
+ * Set to QDEBUG if not defined.
+ *
+ * Force to 0 if VTY_NO_DEBUG is defined and not zero.
+ *
+ * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to:
+ *
+ * * can set QDEBUG_NB_DEBUG == 0 to turn off debug
+ * * or set QDEBUG_NB_DEBUG != 0 to turn on debug
+ * * or set QDEBUG_NB_NO_DEBUG != to force debug off
+ */
+
+#ifdef QDEBUG_NB /* If defined, make it 1 or 0 */
+# if IS_BLANK_OPTION(QDEBUG_NB)
+# undef QDEBUG_NB
+# define QDEBUG_NB 1
+# endif
+#else /* If not defined, follow QDEBUG */
+# define QDEBUG_NB QDEBUG
+#endif
+
+#ifdef QDEBUG_NB_NO /* Override, if defined */
+# if IS_NOT_ZERO_OPTION(QDEBUG_NB_NO)
+# undef QDEBUG_NB
+# define QDEBUG_NB 0
+# endif
+#endif
+
+enum { qdebug_nb = QDEBUG_NB } ;
+
+#endif /* _ZEBRA_QDEBUG_NB_H */
diff --git a/lib/qfstring.c b/lib/qfstring.c
index f40f1417..3639bccb 100644
--- a/lib/qfstring.c
+++ b/lib/qfstring.c
@@ -651,11 +651,11 @@ enum arg_num_type
ant_default = ant_int,
};
-static enum pf_phase qfs_arg_string(qf_str qfs, va_list args,
+static enum pf_phase qfs_arg_string(qf_str qfs, const char* src,
enum pf_flags flags, int width, int precision) ;
-static enum pf_phase qfs_arg_char(qf_str qfs, va_list args,
+static enum pf_phase qfs_arg_char(qf_str qfs, char ch,
enum pf_flags flags, int width, int precision) ;
-static enum pf_phase qfs_arg_number(qf_str qfs, va_list args,
+static enum pf_phase qfs_arg_number(qf_str qfs, va_list* p_va,
enum pf_flags flags, int width, int precision, enum arg_num_type ant) ;
/*------------------------------------------------------------------------------
@@ -666,24 +666,30 @@ static enum pf_phase qfs_arg_number(qf_str qfs, va_list args,
extern void
qfs_printf(qf_str qfs, const char* format, ...)
{
- va_list args;
+ va_list va ;
- va_start (args, format);
- qfs_vprintf(qfs, format, args);
- va_end (args);
+ va_start (va, format);
+ qfs_vprintf(qfs, format, va);
+ va_end (va);
} ;
/*------------------------------------------------------------------------------
* Formatted print to qf_str -- cf vprintf()
*
* This operation is async-signal-safe.
+ *
+ * Operates on a copy of the va_list -- so the original is *unchanged*.
*/
extern void
-qfs_vprintf(qf_str qfs, const char *format, va_list args)
+qfs_vprintf(qf_str qfs, const char *format, va_list va)
{
+ va_list vac ;
+
if (format == NULL)
return ;
+ va_copy(vac, va) ;
+
while ((qfs->ptr < qfs->end) && (*format != '\0'))
{
/* Have space for one byte and current format byte is not '\0' */
@@ -768,11 +774,11 @@ qfs_vprintf(qf_str qfs, const char *format, va_list args)
if (!star && !digit && (phase <= pfp_width))
{
phase = pfp_width ;
- width = va_arg(args, int) ;
+ width = va_arg(vac, int) ;
}
else if (!star && !digit && (phase == pfp_precision))
{
- precision = va_arg(args, int) ;
+ precision = va_arg(vac, int) ;
if (precision < 0)
{
precision = 0 ;
@@ -825,34 +831,36 @@ qfs_vprintf(qf_str qfs, const char *format, va_list args)
if (phase == pfp_num_type)
phase = pfp_failed ; /* don't do 'l' etc. */
else
- phase = qfs_arg_string(qfs, args, flags, width, precision) ;
+ phase = qfs_arg_string(qfs, va_arg(vac, char*),
+ flags, width, precision) ;
break ;
case 'c':
if (phase == pfp_num_type)
phase = pfp_failed ; /* don't do 'l' etc. */
else
- phase = qfs_arg_char(qfs, args, flags, width, precision) ;
+ phase = qfs_arg_char(qfs, (char)va_arg(vac, int),
+ flags, width, precision) ;
break ;
case 'd':
case 'i':
- phase = qfs_arg_number(qfs, args, flags, width, precision,
+ phase = qfs_arg_number(qfs, &vac, flags, width, precision,
ant) ;
break ;
case 'u':
- phase = qfs_arg_number(qfs, args, flags | pf_unsigned, width,
+ phase = qfs_arg_number(qfs, &vac, flags | pf_unsigned, width,
precision, ant) ;
break ;
case 'x':
- phase = qfs_arg_number(qfs, args, flags | pf_hex_x, width,
+ phase = qfs_arg_number(qfs, &vac, flags | pf_hex_x, width,
precision, ant) ;
break ;
case 'X':
- phase = qfs_arg_number(qfs, args, flags | pf_hex_X, width,
+ phase = qfs_arg_number(qfs, &vac, flags | pf_hex_X, width,
precision, ant) ;
break ;
@@ -860,7 +868,7 @@ qfs_vprintf(qf_str qfs, const char *format, va_list args)
if (phase == pfp_num_type)
phase = pfp_failed ;
else
- phase = qfs_arg_number(qfs, args, flags | pf_void_p, width,
+ phase = qfs_arg_number(qfs, &vac, flags | pf_void_p, width,
precision, ant_ptr_t) ;
break ;
@@ -879,6 +887,8 @@ qfs_vprintf(qf_str qfs, const char *format, va_list args)
} ;
*qfs->ptr = '\0' ;
+
+ va_end(vac) ;
} ;
/*------------------------------------------------------------------------------
@@ -902,14 +912,11 @@ qfs_vprintf(qf_str qfs, const char *format, va_list args)
* This operation is async-signal-safe.
*/
static enum pf_phase
-qfs_arg_string(qf_str qfs, va_list args, enum pf_flags flags,
+qfs_arg_string(qf_str qfs, const char* src, enum pf_flags flags,
int width, int precision)
{
- const char* src ;
int len ;
- src = va_arg(args, char*) ;
-
if (flags != (flags & pf_precision))
return pfp_failed ;
@@ -949,13 +956,8 @@ qfs_arg_string(qf_str qfs, va_list args, enum pf_flags flags,
* This operation is async-signal-safe.
*/
static enum pf_phase
-qfs_arg_char(qf_str qfs, va_list args, enum pf_flags flags,
- int width, int precision)
+qfs_arg_char(qf_str qfs, char ch, enum pf_flags flags, int width, int precision)
{
- unsigned char ch ;
-
- ch = va_arg(args, int) ;
-
if ((flags != 0) || (precision != 0))
return pfp_failed ;
@@ -986,7 +988,7 @@ qfs_arg_char(qf_str qfs, va_list args, enum pf_flags flags,
* This operation is async-signal-safe.
*/
static enum pf_phase
-qfs_arg_number(qf_str qfs, va_list args, enum pf_flags flags,
+qfs_arg_number(qf_str qfs, va_list* p_va, enum pf_flags flags,
int width, int precision, enum arg_num_type ant)
{
uintmax_t u_val ;
@@ -995,7 +997,7 @@ qfs_arg_number(qf_str qfs, va_list args, enum pf_flags flags,
/* Special for hex with '0... if no explicit precision, set -1 for byte
* and -2 for everything else -- see qfs_number().
*/
- if (((flags & pf_precision) == 0) && (flags & pf_hex))
+ if ((flags & (pf_hex | pf_precision)) == pf_hex)
{
if ((flags & (pf_commas | pf_zeros)) == (pf_commas | pf_zeros))
{
@@ -1013,31 +1015,28 @@ qfs_arg_number(qf_str qfs, va_list args, enum pf_flags flags,
{
case ant_char:
case ant_short:
- u_val = va_arg(args, int) ;
- break ;
-
case ant_int:
- u_val = va_arg(args, unsigned int) ;
+ u_val = va_arg(*p_va, unsigned int) ;
break ;
case ant_long:
- u_val = va_arg(args, unsigned long) ;
+ u_val = va_arg(*p_va, unsigned long) ;
break ;
case ant_long_long:
- u_val = va_arg(args, unsigned long long) ;
+ u_val = va_arg(*p_va, unsigned long long) ;
break ;
case ant_intmax_t:
- u_val = va_arg(args, uintmax_t) ;
+ u_val = va_arg(*p_va, uintmax_t) ;
break ;
case ant_size_t:
- u_val = va_arg(args, size_t) ;
+ u_val = va_arg(*p_va, size_t) ;
break ;
case ant_ptr_t:
- u_val = va_arg(args, uintptr_t) ;
+ u_val = va_arg(*p_va, uintptr_t) ;
break ;
default:
@@ -1052,31 +1051,28 @@ qfs_arg_number(qf_str qfs, va_list args, enum pf_flags flags,
{
case ant_char:
case ant_short:
- s_val = va_arg(args, int) ;
- break ;
-
case ant_int:
- s_val = va_arg(args, signed int) ;
+ s_val = va_arg(*p_va, signed int) ;
break ;
case ant_long:
- s_val = va_arg(args, signed long) ;
+ s_val = va_arg(*p_va, signed long) ;
break ;
case ant_long_long:
- s_val = va_arg(args, signed long long) ;
+ s_val = va_arg(*p_va, signed long long) ;
break ;
case ant_intmax_t:
- s_val = va_arg(args, intmax_t) ;
+ s_val = va_arg(*p_va, intmax_t) ;
break ;
case ant_size_t:
- s_val = va_arg(args, ssize_t) ;
+ s_val = va_arg(*p_va, ssize_t) ;
break ;
case ant_ptr_t:
- s_val = va_arg(args, intptr_t) ;
+ s_val = va_arg(*p_va, intptr_t) ;
break ;
default:
@@ -1086,8 +1082,6 @@ qfs_arg_number(qf_str qfs, va_list args, enum pf_flags flags,
qfs_signed(qfs, s_val, flags, width, precision) ;
} ;
- /* construct a digit string, the hard way */
-
return pfp_done ;
} ;
diff --git a/lib/qfstring.h b/lib/qfstring.h
index 990b4ef4..c57bd4ea 100644
--- a/lib/qfstring.h
+++ b/lib/qfstring.h
@@ -30,9 +30,6 @@
* strings, particularly where the string handling must be async-signal-safe.
*/
-typedef struct qf_str qf_str_t ;
-typedef struct qf_str* qf_str ;
-
/* When initialised a qf_string is set:
*
* str = start of string -- and this is never changed
@@ -47,6 +44,9 @@ struct qf_str
char* end ; /* end of string */
} ;
+typedef struct qf_str qf_str_t[1] ;
+typedef struct qf_str* qf_str ;
+
/*------------------------------------------------------------------------------
* Print format flags for number printing
*/
diff --git a/lib/qiovec.c b/lib/qiovec.c
index 38ea0dbb..2946efaa 100644
--- a/lib/qiovec.c
+++ b/lib/qiovec.c
@@ -22,6 +22,7 @@
#include <errno.h>
#include "misc.h"
+#include "qdebug_nb.h"
#include "memory.h"
#include "miyagi.h"
@@ -54,12 +55,12 @@
* Returns: address of qiovec
*/
extern qiovec
-qiovec_init_new(qiovec viov)
+qiovec_init_new(qiovec qiov)
{
- if (viov == NULL)
- viov = XCALLOC(MTYPE_QIOVEC, sizeof(struct qiovec)) ;
+ if (qiov == NULL)
+ qiov = XCALLOC(MTYPE_QIOVEC, sizeof(struct qiovec)) ;
else
- memset(viov, 0, sizeof(struct qiovec)) ;
+ memset(qiov, 0, sizeof(struct qiovec)) ;
/* Zeroising has set:
*
@@ -74,7 +75,7 @@ qiovec_init_new(qiovec viov)
* Nothing more is required.
*/
- return viov ;
+ return qiov ;
} ;
/*------------------------------------------------------------------------------
@@ -83,81 +84,188 @@ qiovec_init_new(qiovec viov)
* Returns: address of qiovec (if any) -- NULL if structure released
*/
extern qiovec
-qiovec_reset(qiovec viov, bool free_structure)
+qiovec_reset(qiovec qiov, bool free_structure)
{
- if (viov != NULL)
+ if (qiov != NULL)
{
- if (viov->vec != NULL)
- XFREE(MTYPE_QIOVEC_VEC, viov->vec) ;
+ if (qiov->vec != NULL)
+ XFREE(MTYPE_QIOVEC_VEC, qiov->vec) ;
if (free_structure)
- XFREE(MTYPE_QIOVEC, viov) ; /* sets viov = NULL */
+ XFREE(MTYPE_QIOVEC, qiov) ; /* sets qiov = NULL */
else
- qiovec_init_new(viov) ; /* re-initialise */
+ qiovec_init_new(qiov) ; /* re-initialise */
} ;
- return viov ;
+ return qiov ;
} ;
/*------------------------------------------------------------------------------
* Clear given qiovec.
*/
extern void
-qiovec_clear(qiovec viov)
+qiovec_clear(qiovec qiov)
{
- viov->i_get = 0 ;
- viov->i_put = 0 ;
- viov->writing = 0 ;
+ qiov->i_get = 0 ;
+ qiov->i_put = 0 ;
+ qiov->writing = 0 ;
} ;
/*------------------------------------------------------------------------------
- * Push item to given qiovec
- *
- * NB: avoids pushing zero length items.
+ * Establish total length of the qiov -- add up all the item lengths.
*/
-extern void
-qiovec_push(qiovec viov, const void* base, size_t len)
+extern size_t
+qiovec_length(qiovec qiov)
{
- struct iovec* p_iov ;
+ if (qiov->i_put > qiov->i_get)
+ {
+ size_t length ;
+ uint n ;
+ qiov_item item ;
- if (len == 0)
- return ;
+ n = qiov->i_put - qiov->i_get ;
+ item = &qiov->vec[qiov->i_get] ;
- if (viov->i_put >= viov->i_alloc)
+ length = item->len ;
+ while (--n)
+ length += (++item)->len ;
+
+ return length ;
+ }
+ else
{
- size_t size ;
- assert(viov->i_put == viov->i_alloc) ;
+ assert(qiov->i_get == qiov->i_put) ;
+ qiov->i_get = qiov->i_put = 0 ;
- assert( ((viov->i_alloc == 0) && (viov->vec == NULL))
- || ((viov->i_alloc != 0) && (viov->vec != NULL)) ) ;
+ return 0 ;
+ } ;
+} ;
- if (viov->i_get > 200) /* keep in check */
- {
- size = (viov->i_put - viov->i_get) * sizeof(struct iovec) ;
- if (size != 0)
- memmove(viov->vec, &viov->vec[viov->i_get], size) ;
- viov->i_put -= viov->i_get ;
- viov->i_get = 0 ;
- }
- else
- {
- viov->i_alloc += 100 ; /* a sizable chunk */
- size = viov->i_alloc * sizeof(struct iovec) ;
- if (viov->vec == NULL)
- viov->vec = XMALLOC(MTYPE_QIOVEC_VEC, size) ;
- else
- viov->vec = XREALLOC(MTYPE_QIOVEC_VEC, viov->vec, size) ;
- } ;
+/*==============================================================================
+ * Extending and shuffling qiovec in order to support push and unshift
+ * operations.
+ *
+ * Note that if unshift becomes common, then should adjust the following.
+ * Also address the tendency to reset i_get and i_put to zero whenever they
+ * are found to be equal.
+ */
+enum
+{
+ i_get_limit = 200, /* in qiovec_extend, shuffle vector down if
+ i_get is >= i_get_limit */
+
+ i_alloc_unit = 100, /* allocate in lots of this much */
+
+ i_get_margin = 20, /* in qiovec_shuffle, if have to reorganise,
+ allow for this many unshifts. */
+
+ i_put_margin = 40, /* in qiovec_shuffle, if have to reorganise,
+ allow for this many pushes. */
+} ;
+
+static void qiovec_move_vector(qiovec qiov, uint new_i_get) ;
+static void qiovec_new_vector(qiovec qiov, uint items) ;
+
+/*------------------------------------------------------------------------------
+ * Extend -- see qiovec_push.
+ *
+ * NB: underlying vector may be reorganised, reallocated, etc. All pointers
+ * to items are now INVALID.
+ */
+Private void
+qiovec_extend(qiovec qiov)
+{
+ assert(qiov->i_put <= qiov->i_alloc) ;
+ assert(qiov->i_get <= qiov->i_put) ;
+
+ assert( ((qiov->i_alloc == 0) && (qiov->vec == NULL))
+ || ((qiov->i_alloc != 0) && (qiov->vec != NULL)) ) ;
+
+ /* Short circuit if can make space by resetting indexes */
+ if ((qiov->i_put == qiov->i_get) && (qiov->i_alloc != 0))
+ {
+ qiov->i_put = 0 ;
+ qiov->i_get = 0 ;
+
+ return ;
} ;
- p_iov = &viov->vec[viov->i_put++] ;
+ if (qiov->i_get > i_get_limit) /* keep in check */
+ qiovec_move_vector(qiov, 0) ;
+ else
+ qiovec_new_vector(qiov, i_alloc_unit) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Shuffle to allow for qiovec_unshift.
+ *
+ * Don't expect this to happen often -- but will do it if required.
+ *
+ * On exit the i_get will be > 0.
+ *
+ * NB: underlying vector may be reorganised, reallocated, etc. All pointers
+ * to items are now INVALID.
+ */
+Private void
+qiovec_shuffle(qiovec qiov)
+{
+ assert(qiov->i_put <= qiov->i_alloc) ;
+ assert(qiov->i_get <= qiov->i_put) ;
+
+ assert( ((qiov->i_alloc == 0) && (qiov->vec == NULL))
+ || ((qiov->i_alloc != 0) && (qiov->vec != NULL)) ) ;
+
+ if (qiov->i_get > 0)
+ return ; /* shouldn't be here, really */
+
+ /* If do not have enough spare space for both margins, allocate extra to
+ * the tune of both margins -- will leave at least the margins and at
+ * most nearly twice those margins.
+ *
+ * This will allocate a vector is there is none at all.
+ */
+ if ((qiov->i_alloc - qiov->i_put) < (i_get_margin + i_put_margin))
+ qiovec_new_vector(qiov, i_get_margin + i_put_margin) ;
+
+ /* Shuffle into place and update i_get and i_put. */
+ qiovec_move_vector(qiov, i_get_margin) ;
- p_iov->iov_base = miyagi(base) ;
- p_iov->iov_len = len ;
+ confirm(i_get_margin > 0) ;
} ;
/*------------------------------------------------------------------------------
+ * Move contents of vector and set a new i_get (and i_put to suit).
+ */
+static void
+qiovec_move_vector(qiovec qiov, uint new_i_get)
+{
+ memmove(&qiov->vec[new_i_get], &qiov->vec[qiov->i_get],
+ (qiov->i_put - qiov->i_get) * sizeof(qiovec_t)) ;
+
+ qiov->i_put -= qiov->i_get - new_i_get ;
+ qiov->i_get = new_i_get ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Allocate or reallocate vector for given number of items.
+ */
+static void
+qiovec_new_vector(qiovec qiov, uint items)
+{
+ qiov->i_alloc += items ;
+
+ qiov->vec = XREALLOC(MTYPE_QIOVEC_VEC, qiov->vec,
+ qiov->i_alloc * sizeof(qiovec_t)) ;
+} ;
+
+/*==============================================================================
+ * Writing qiovec and iovec
+ */
+
+static ssize_t writev_qdebug_nb(int fd, struct iovec p_iov[], int n) ;
+
+/*------------------------------------------------------------------------------
* Write given qiovec -- assuming NON-BLOCKING.
*
* Does nothing if the qiovec is empty.
@@ -171,24 +279,24 @@ qiovec_push(qiovec viov, const void* base, size_t len)
* -1 => failed -- see errno
*/
extern int
-qiovec_write_nb(int fd, qiovec viov)
+qiovec_write_nb(int fd, qiovec qiov)
{
int n ;
int l ;
- n = viov->i_put - viov->i_get ;
+ n = qiov->i_put - qiov->i_get ;
- l = iovec_write_nb(fd, &viov->vec[viov->i_get], n) ;
+ l = iovec_write_nb(fd, (struct iovec*)(&qiov->vec[qiov->i_get]), n) ;
if (l == 0)
{
- viov->writing = 0 ;
- viov->i_get = viov->i_put = 0 ;
+ qiov->writing = false ;
+ qiov->i_get = qiov->i_put = 0 ;
}
else
{
- viov->writing = 1 ;
- viov->i_get += (n - l) ;
+ qiov->writing = true ;
+ qiov->i_get += (n - l) ;
} ;
return l ;
@@ -223,8 +331,6 @@ qiovec_write_nb(int fd, qiovec viov)
extern int
iovec_write_nb(int fd, struct iovec p_iov[], int n)
{
- ssize_t ret ;
-
assert(n >= 0) ;
/* Skip past any leading zero length entries */
@@ -236,7 +342,12 @@ iovec_write_nb(int fd, struct iovec p_iov[], int n)
while (n > 0)
{
- ret = writev(fd, p_iov, (n < IOV_MAX ? n : IOV_MAX)) ;
+ ssize_t ret ;
+
+ if (qdebug_nb)
+ ret = writev_qdebug_nb(fd, p_iov, (n < IOV_MAX ? n : IOV_MAX)) ;
+ else
+ ret = writev (fd, p_iov, (n < IOV_MAX ? n : IOV_MAX)) ;
if (ret > 0)
{
@@ -257,6 +368,7 @@ iovec_write_nb(int fd, struct iovec p_iov[], int n)
ret = 0 ;
} ;
} ;
+
}
else if (ret == 0)
break ; /* not sure can happen... but
@@ -267,9 +379,59 @@ iovec_write_nb(int fd, struct iovec p_iov[], int n)
if ((err == EAGAIN) || (err == EWOULDBLOCK))
break ;
if (err != EINTR)
+// assert(0) ; // Pro tem
return -1 ; /* failed */
} ;
} ;
return n ;
} ;
+
+/*==============================================================================
+ * Simulation of writev() for debug purposes -- generates lots of partial
+ * writes and lots of blocking
+ *
+ */
+
+static qrand_seq_t wseq = QRAND_SEQ_INIT(4001) ;
+
+static const int blocking_errs[] = { EAGAIN, EWOULDBLOCK, EINTR } ;
+
+/*------------------------------------------------------------------------------
+ * Simulate writev() with tiny output buffers and lots of blocking.
+ */
+static ssize_t
+writev_qdebug_nb(int fd, struct iovec p_iov[], int n)
+{
+ struct iovec* q_iov ;
+ size_t len ;
+ ssize_t ret ;
+
+ assert(n > 0) ;
+
+ if (qrand(wseq, 3) == 0) /* 1/3 chance of blocking */
+ {
+ errno = blocking_errs[qrand(wseq, 3)] ;
+ return -1 ;
+ } ;
+
+ /* Process 1..n or 1..3 entries */
+ n = qrand(wseq, (n < 3) ? n : 3) ;
+ q_iov = p_iov + n ;
+ ++n ;
+
+ /* If new last entry is not zero length, write only part of
+ * it.
+ */
+ len = q_iov->iov_len ;
+ if (len > 0)
+ q_iov->iov_len = qrand(wseq, (len < 200) ? len : 200) + 1 ;
+
+ ret = writev(fd, p_iov, n) ;
+
+ q_iov->iov_len = len ; /* restore true length of entry */
+
+ return ret ;
+} ;
+
+
diff --git a/lib/qiovec.h b/lib/qiovec.h
index 230198f3..0cbf0364 100644
--- a/lib/qiovec.h
+++ b/lib/qiovec.h
@@ -26,22 +26,54 @@
#include <sys/uio.h> /* iovec stuff */
/*==============================================================================
+ * Alternative naming for struct iovec and its entries.
+ */
+struct qiov_item
+{
+ const char* base ;
+ size_t len ;
+} ;
+
+typedef struct qiov_item* qiov_item ;
+typedef struct qiov_item qiov_item_t[1] ;
+typedef struct qiov_item* qiov_vector ;
+
+union
+{
+ struct qiov_item q ;
+ struct iovec s ;
+} union_iovec ;
+
+CONFIRM(sizeof(struct qiov_item) == sizeof(struct iovec)) ;
+
+CONFIRM(offsetof(struct qiov_item, base) == offsetof(struct iovec, iov_base)) ;
+CONFIRM(sizeof (union_iovec.q. base) == sizeof (union_iovec.s.iov_base)) ;
+CONFIRM(offsetof(struct qiov_item, len) == offsetof(struct iovec, iov_len)) ;
+CONFIRM(sizeof (union_iovec.q. len) == sizeof (union_iovec.s.iov_len)) ;
+
+/*==============================================================================
* Flexible size "struct iovec"
*
* NB: a completely zero structure is a valid, empty qiovec.
*/
-typedef struct qiovec* qiovec ;
-typedef struct qiovec qiovec_t ;
struct qiovec
{
- struct iovec* vec ; /* the actual iovec array */
+ qiov_vector vec ; /* the actual iovec array */
+
+ uint i_get ; /* next entry to get */
+ uint i_put ; /* next entry to put */
+
+ uint i_alloc ; /* number of entries allocated */
bool writing ; /* started, but not finished */
+} ;
- unsigned i_get ; /* next entry to get */
- unsigned i_put ; /* next entry to put */
+typedef struct qiovec* qiovec ;
+typedef struct qiovec qiovec_t[1] ;
- unsigned i_alloc ; /* number of entries allocated */
+enum
+{
+ QIOVEC_INIT_ALL_ZEROS = true
} ;
/*==============================================================================
@@ -50,18 +82,39 @@ struct qiovec
extern qiovec qiovec_init_new(qiovec qiov) ;
extern qiovec qiovec_reset(qiovec qiov, bool free_structure) ;
-
-#define qiovec_reset_keep(qiov) qiovec_reset(qiov, 0)
-#define qiovec_reset_free(qiov) qiovec_reset(qiov, 1)
+Inline qiovec qiovec_free(qiovec qiov) ;
Inline bool qiovec_empty(qiovec qiov) ;
+extern size_t qiovec_length(qiovec qiov) ;
extern void qiovec_clear(qiovec qiov) ;
-extern void qiovec_push(qiovec qiov, const void* base, size_t len) ;
extern int qiovec_write_nb(int fd, qiovec qiov) ;
+Inline uint qiovec_count(qiovec qiov) ;
+Inline void qiovec_push_this(qiovec qiov, const void* base, size_t len) ;
+Inline void qiovec_push(qiovec qiov, qiov_item item) ;
+Inline void qiovec_pop(qiovec qiov, qiov_item item) ;
+Inline void qiovec_shift(qiovec qiov, qiov_item item) ;
+Inline void qiovec_unshift(qiovec qiov, qiov_item item) ;
+
extern int iovec_write_nb(int fd, struct iovec* p_iov, int n) ;
Inline void iovec_set(struct iovec* p_iov, const void* base, size_t len) ;
+Private void qiovec_extend(qiovec qiov) ;
+Private void qiovec_shuffle(qiovec qiov) ;
+
+/*------------------------------------------------------------------------------
+ * Free given qiovec
+ *
+ * It is the caller's responsibility to free anything that the qiovec may
+ * point to.
+ *
+ * Returns: NULL
+ */
+Inline qiovec qiovec_free(qiovec qiov)
+{
+ return qiovec_reset(qiov, free_it) ;
+} ;
+
/*------------------------------------------------------------------------------
* Is given qiov empty ?
*
@@ -75,6 +128,107 @@ qiovec_empty(qiovec qiov)
} ;
/*------------------------------------------------------------------------------
+ * How many entries in the given qiov ?
+ */
+Inline uint
+qiovec_count(qiovec qiov)
+{
+ return (qiov->i_put - qiov->i_get) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Push to qiov -- same as qiovec_push, but with item.
+ *
+ * Ignores zero length items.
+ */
+Inline void
+qiovec_push_this(qiovec qiov, const void* base, size_t len)
+{
+ qiov_item_t item ;
+
+ item->base = base ;
+ item->len = len ;
+ qiovec_push(qiov, item) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Push (copy of) item to qiov -- ie append it to the end of the vector.
+ */
+Inline void
+qiovec_push(qiovec qiov, qiov_item item)
+{
+ if (item->len != 0)
+ {
+ if (qiov->i_put >= qiov->i_alloc)
+ qiovec_extend(qiov) ;
+
+ qiov->vec[qiov->i_put++] = *item ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Pop last item from qiov -- return empty item if none.
+ */
+Inline void
+qiovec_pop(qiovec qiov, qiov_item item)
+{
+ if (qiov->i_get < qiov->i_put)
+ {
+ *item = qiov->vec[--qiov->i_put] ;
+ if (qiov->i_get == qiov->i_put)
+ qiov->i_get = qiov->i_put = 0 ;
+ }
+ else
+ {
+ assert(qiov->i_get == qiov->i_put) ;
+ item->base = NULL ;
+ item->len = 0 ;
+
+ qiov->i_get = qiov->i_put = 0 ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Shift first item off qiov -- return empty item if none.
+ */
+Inline void
+qiovec_shift(qiovec qiov, qiov_item item)
+{
+ if (qiov->i_get < qiov->i_put)
+ {
+ *item = qiov->vec[qiov->i_get++] ;
+ if (qiov->i_get == qiov->i_put)
+ qiov->i_get = qiov->i_put = 0 ;
+ }
+ else
+ {
+ assert(qiov->i_get == qiov->i_put) ;
+ item->base = NULL ;
+ item->len = 0 ;
+
+ qiov->i_get = qiov->i_put = 0 ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Unshift item onto qiov -- ie prepend it to the start of the vector.
+ */
+Inline void
+qiovec_unshift(qiovec qiov, qiov_item item)
+{
+ if (qiov->i_get == 0)
+ {
+ if (qiov->i_put == 0)
+ return qiovec_push(qiov, item) ;
+
+ qiovec_shuffle(qiov) ;
+ } ;
+
+ if (item->len != 0)
+ qiov->vec[--qiov->i_get] = *item ;
+} ;
+
+/*------------------------------------------------------------------------------
* Set a given struct iovec
*
* Gets around the fact that the standard structure does not have a const
diff --git a/lib/qlib_init.c b/lib/qlib_init.c
index c44de575..d3fde339 100644
--- a/lib/qlib_init.c
+++ b/lib/qlib_init.c
@@ -22,12 +22,14 @@
#include "qlib_init.h"
#include "zassert.h"
#include "memory.h"
+#include "qpnexus.h"
#include "qpthreads.h"
#include "qpselect.h"
#include "thread.h"
#include "privs.h"
#include "mqueue.h"
#include "pthread_safe.h"
+#include "log_local.h"
/*==============================================================================
* Quagga Library Initialise/Closedown
@@ -66,30 +68,33 @@
*
*/
-void
+extern void
qlib_init_first_stage(void)
{
qps_start_up() ;
}
-void
-qlib_init_second_stage(int pthreads)
+extern void
+qlib_init_second_stage(bool pthreads)
{
qpt_set_qpthreads_enabled(pthreads);
+ qpn_init() ;
memory_init_r();
thread_init_r();
+ log_init_r() ;
zprivs_init_r();
mqueue_initialise();
safe_init_r();
}
-void
+extern void
qexit(int exit_code)
{
safe_finish();
mqueue_finish();
zprivs_finish();
+ log_finish();
thread_finish();
memory_finish();
exit (exit_code);
diff --git a/lib/qlib_init.h b/lib/qlib_init.h
index 0d5fbd7e..6c6c1a01 100644
--- a/lib/qlib_init.h
+++ b/lib/qlib_init.h
@@ -22,6 +22,8 @@
#ifndef _ZEBRA_QLIB_INIT_H
#define _ZEBRA_QLIB_INIT_H
+#include "misc.h"
+
/*==============================================================================
* Quagga Library Initialise/Closedown
*
@@ -33,7 +35,7 @@
extern void qlib_init_first_stage(void) ;
-extern void qlib_init_second_stage(int pthreads) ;
+extern void qlib_init_second_stage(bool pthreads) ;
extern void qexit(int exit_code) ;
diff --git a/lib/qpath.c b/lib/qpath.c
index 07c294de..e3f690a6 100644
--- a/lib/qpath.c
+++ b/lib/qpath.c
@@ -24,6 +24,9 @@
#include "zassert.h"
+#include <unistd.h>
+#include <errno.h>
+
/*==============================================================================
* Some primitive path handling, based on qstrings.
*
@@ -43,35 +46,40 @@
*
*/
+static void qpath_reduce(qpath qp) ;
+
/*------------------------------------------------------------------------------
* Initialise a brand new qpath -- allocate if required.
*
- * If a path is given, set that path -- allocating body even if path is zero
- * length.
- *
- * If no path is given, leaves qpath with no body.
- *
- * If path is given, the qpath is set and reduced (see above).
+ * The result is a path with a not-empty body. All qpath values may be assumed
+ * to have a body at all times.
*
* Returns: address of qpath
*
* NB: assumes initialising a new structure. If not, then caller should
- * use qpath_reset() or qs_clear().
+ * use qpath_reset() or qpath_clear().
*/
extern qpath
-qpath_init_new(qpath qp, const char* path)
+qpath_init_new(qpath qp)
{
if (qp == NULL)
qp = XCALLOC(MTYPE_QPATH, sizeof(qpath_t)) ;
else
memset(qp, 0, sizeof(qpath_t)) ;
- /* Worry about fields other than the path */
+ qs_init_new(qp->path, 50) ; /* Always have a body */
- qs_init_new(qp->path, 0) ;
+ return qp ;
+} ;
- if (path != NULL)
- qpath_set(qp, path) ;
+/*------------------------------------------------------------------------------
+ * Create a new qpath if don't already have one.
+ */
+inline static qpath
+qpath_make_if_null(qpath qp)
+{
+ if (qp == NULL)
+ qp = qpath_init_new(NULL) ;
return qp ;
} ;
@@ -82,172 +90,251 @@ qpath_init_new(qpath qp, const char* path)
* Discards all the contents of the qpath.
*/
extern qpath
-qpath_reset(qpath qp, bool free_structure)
+qpath_reset(qpath qp, free_keep_b free_structure)
{
if (qp == NULL)
return NULL ;
- qs_reset_keep(&qp->path, keep_it) ;
+ qs_reset(qp->path, keep_it) ;
if (free_structure)
XFREE(MTYPE_QPATH, qp) ; /* sets qp = NULL */
else
- /* Worry about fields other than the path */
;
return qp ;
} ;
/*------------------------------------------------------------------------------
- * Set given qpath to copy of the given string -- allocate if required.
- *
- * If setting an existing qpath, discards any existing contents -- so the qpath
- * MUST have been initialised at some time (qpath_init_new). Keeps any body
- * that has been allocated if possible.
- *
- * Reduces the path (see above).
- *
- * Sets the path len, but does not touch the path cp.
+ * Clear down given qpath -- retaining any body, but setting it empty.
*/
extern qpath
-qpath_set(qpath qp, const char* path)
+qpath_clear(qpath qp)
{
if (qp == NULL)
- qp = qpath_init_new(NULL, path) ;
- else
- {
- if (path != NULL)
- qs_set(&qp->path, path) ;
- else
- qs_clear(qp->path) ;
- /* Worry about fields other than the path */
- } ;
+ return NULL ;
- qpath_reduce(qp) ;
+ qs_clear(qp->path) ;
return qp ;
} ;
/*------------------------------------------------------------------------------
- * Reduce multiple '/' to single '/' (except for exactly "//" at start).
+ * Set given qpath to copy of the given string -- allocate if required.
*
- * Reduce "/./" to "/".
+ * Reduces the path (see above).
*/
-static void
-qpath_reduce(qpath qp, size_t off)
+extern qpath
+qpath_set(qpath dst, const char* src)
{
- qpath part ;
- qstring qs ;
- char* sp ;
- char* p ;
- char* q ;
+ return qpath_set_n(dst, src, (src != NULL) ? strlen(src) : 0) ;
+} ;
- if (qp == NULL)
- {
- assert(off == 0) ;
- return ; /* NULL qpath is empty */
- } ;
+/*------------------------------------------------------------------------------
+ * Set given qpath to copy of the given qstring -- allocate if required.
+ *
+ * Reduces the path (see above).
+ */
+extern qpath
+qpath_set_qs(qpath dst, const qstring src)
+{
+ return qpath_set_n(dst, qs_char(src), qs_len(src)) ;
+} ;
- qs = &qp->path ;
- assert(off <= qs->len) ; /* Make sure 'off' is kosher */
+/*------------------------------------------------------------------------------
+ * Set given qpath to copy of the given string -- allocate if required.
+ *
+ * Reduces the path (see above).
+ */
+extern qpath
+qpath_set_n(qpath dst, const char* src, ulen n)
+{
+ dst = qpath_make_if_null(dst) ;
- sp = qs_chars(qs) ; /* NULL if qpath is completely empty */
+ qs_set_n(dst->path, src, n) ;
- if (sp == NULL)
- return ; /* NULL path part is completely empty */
+ qpath_reduce(dst) ;
- p = sp + off ;
+ return dst ;
+} ;
- /* Deal with special case of "//" at start.
- *
- * If find "//x", where x is anything other than '/', step past the first
- * '/'. Could step past both "//", but that stops it seeing "//./zzz"
- */
- if ((*p == '/') && (*(p + 1) == '/') && (*(p + 2) != '/'))
- ++p ;
+/*------------------------------------------------------------------------------
+ * Copy qpath to the given qpath -- creating qpath if required.
+ * The result is an empty qpath if the given one is NULL.
+ */
+extern qpath
+qpath_copy(qpath dst, const qpath src)
+{
+ if (src != NULL)
+ return qpath_set_n(dst, qs_char_nn(src->path), qs_len_nn(src->path)) ;
+ else
+ return qpath_set_n(dst, NULL, 0) ;
+} ;
- /* Scan to see if there is anything that needs to be fixed.
- *
- * Looking for "//" and "/./".
- */
- while (1)
- {
- if (*p++ == '\0')
- return ; /* nothing to do if hit end of string */
+/*==============================================================================
+ * Interfaces to system.
+ */
- if ( (*p == '/') || ((*p == '.') && (*(p + 1) == '/')) )
- {
- if (*(p - 1) == '/')
- break ; /* found "//" or "/./" */
- }
- } ;
+/*------------------------------------------------------------------------------
+ * Get the current working directory -- creates a qpath if required.
+ *
+ * Returns: the (new) qpath if OK
+ * NULL if not OK -- any existing qpath is cleared.
+ *
+ * If fails will be because some directory on the way back to the root is
+ * not readable or searchable (!).
+ */
+extern qpath
+qpath_getcwd(qpath dst)
+{
+ qpath od ;
- /* Rats... there is something to be fixed.
- *
- * *p is second '/' of "//" or '.' of "/./".
- */
- q = p ;
+ od = dst ;
+ dst = qpath_make_if_null(dst) ;
- while (*p != '\0')
+ qs_new_size(dst->path, 50) ;
+
+ while (1)
{
- /* Step past any number of '/' and any number of "./". */
- while (1)
- {
- while (*p == '/')
- ++p ;
+ void* r ;
+ usize s ;
- if ((*p != '.') || (*p != '/'))
- break ;
+ s = qs_size_nn(dst->path) ;
+ r = getcwd(qs_char_nn(dst->path), s) ;
- p += 2 ;
+ if (r != NULL)
+ {
+ qs_set_strlen_nn(dst->path) ;
+ return dst ; /* exit here if OK. */
} ;
- /* Scan, copying stuff, until get to '\0' or find "//" or "/./" */
- while (*p != '\0')
- {
- *q++ = *p++ ; /* copy non-'\0' */
+ if (errno != ERANGE)
+ break ; /* exit here on failure */
- if ( (*p == '/') || ((*p == '.') && (*(p + 1) == '/')) )
- {
- if (*(p - 1) == '/')
- break ; /* found "//" or "/./" */
- } ;
- } ;
+ qs_new_size(dst->path, s * 2) ;
} ;
- /* Adjust the length and terminate */
+ if (od == NULL)
+ qpath_reset(dst, free_it) ;
+ else
+ qpath_clear(dst) ;
- qs->len = (q - sp) ; /* set the new length (shorter !) */
- *q = '\0' ; /* and terminate */
+ return NULL ;
} ;
/*------------------------------------------------------------------------------
- * Make a copy of the given qpath.
+ * Set the current working directory
*
- * Creates a brand new qpath object, which is a full copy of the given one.
+ * Returns: 0 <=> OK
+ * errno otherwise
+ */
+extern int
+qpath_setcwd(qpath qp)
+{
+ return (chdir(qpath_string(qp)) == 0) ? 0 : errno ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Do "stat" for given path.
*
- * The result is an empty qpath if the given one is NULL.
+ * Returns: 0 <=> OK
+ * errno otherwise
*/
-extern qpath
-qpath_copy(qpath qp_x)
+extern int
+qpath_stat(qpath qp, struct stat* sbuf)
{
- return qpath_init_new(NULL, qpath_path(qp_x)) ;
+ return (stat(qpath_string(qp), sbuf) == 0) ? 0 : errno ;
} ;
/*------------------------------------------------------------------------------
- * Make a copy of a qpath to the given qpath.
+ * Is given path a file we might access -- according to stat ?
*
- * If required, creates new qpath object -- so qpath_copy_to(NULL, ...) is the
- * same as qpath_copy(...).
+ * Returns: -1 <=> no -- can access etc, but it's not a file
+ * 0 <=> yes
+ * errno otherwise
+ */
+extern int
+qpath_stat_is_file(qpath qp)
+{
+ struct stat sbuf[1] ;
+ int err ;
+
+ err = qpath_stat(qp, sbuf) ;
+
+ return (err == 0) ? (S_ISREG(sbuf->st_mode) ? 0 : -1)
+ : err ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is given path a directory we might access -- according to stat ?
*
- * The result is an empty qpath if the given one is NULL.
+ * Returns: -1 <=> no -- can access etc, but it's not a directory
+ * 0 <=> yes
+ * errno otherwise
+ */
+extern int
+qpath_stat_is_directory(qpath qp)
+{
+ struct stat sbuf[1] ;
+ int err ;
+
+ err = qpath_stat(qp, sbuf) ;
+
+ return (err == 0) ? (S_ISDIR(sbuf->st_mode) ? 0 : -1)
+ : err ;
+} ;
+
+/*==============================================================================
+ * Path editing functions
+ *
+ *
+ */
+
+/*------------------------------------------------------------------------------
+ * Shave any file part off the end of the given path.
+ *
+ * This treats anything after the last '/' of the path as being the "file part".
+ *
+ * The cases are (where 'a' is anything except '/' and 'z' is anything):
+ *
+ * 1. "" -- empty -> unchanged
+ *
+ * 2. "aaa" -- file part only -> ""
+ *
+ * 3. "/" -- root only, or -> unchanged
+ * "//" double root only -> unchanged
+ *
+ * 4. "/aaa" -- routed file -> "/"
+ * "//aaa" double routed file -> "//"
+ *
+ * 5. "zzz/" -- no file part -> unchanged
+ *
+ * 6. "zzz/aaa" -- non-empty file part -> "zzz/"
+ *
+ * Ensures that the path is "reduced" before shaving.
+ *
+ * Creates a new, empty path if qp is NULL.
*/
extern qpath
-qpath_copy_to(qpath qp, qpath qp_x)
+qpath_shave(qpath qp)
{
- return qpath_set(qp, qpath_path(qp_x)) ;
+ pp_t p ;
+
+ qp = qpath_make_if_null(qp) ;
+
+ qs_pp_nn(p, qp->path) ;
+
+ /* Track back to last '/' */
+ while ( (p->e > p->p) && (*(p->e - 1) != '/') )
+ --p->e ;
+
+ qs_set_len_nn(qp->path, p->e - p->p) ;
+
+ return qp ;
} ;
+#if 0
+
/*==============================================================================
* Pop the last part of the given path.
*
@@ -291,7 +378,7 @@ qpath_copy_to(qpath qp, qpath qp_x)
* Note that other forms of multiple '/' have been reduced, already.
*/
extern qpath
-qpath_pop(qpath qp)
+qpath_pop(qpath to, qpath from)
{
qpath part ;
qstring qs ;
@@ -348,6 +435,9 @@ qpath_pop(qpath qp)
return part ;
} ;
+
+
+
/*==============================================================================
* Shift off the first part of the given path.
*
@@ -444,60 +534,85 @@ qpath_shift(qpath qp)
/* Return the part we hacked off */
return part ;
} ;
+#endif
+
+/*==============================================================================
+ * Append, Prepend and Complete
+ */
+
+static ulen qpath_trim_home(const char* p, ulen n) ;
/*------------------------------------------------------------------------------
- * Push one path onto the end of the given path.
+ * Append one path (src) onto the end of the given path (dst).
*
- * If the given path is NULL, creates a new, empty qpath to push onto.
+ * If the dst path is NULL, creates a new, empty qpath to append to.
*
- * The given path is assumed to be the path to a "directory". An empty
- * given path is treated as "the current directory".
+ * The dst path is assumed to be the path to a "directory". An empty dst path
+ * is treated as "the current directory".
*
- * If the path to be pushed starts '/' or '~', then it is trimmed, removing
- * leading characters upto and including '/' (stopping at '\0' if no '/' found).
+ * If src path starts '/' or '~', then it is trimmed, removing leading
+ * characters up to and including '/'.
*
- * If path to be pushed onto is not empty, and does not end '/', then an '/'
- * is appended before the path is pushed.
+ * If dst path is not empty, and does not end '/', then an '/' is appended
+ * before the src is appended.
*
* Note that this means:
*
- * -- pushing an empty path or one which is just "/", will leave the path
- * ending "/" -- unless the given path is empty.
+ * -- appending an empty path or one which is just "/", will leave the dst
+ * path ending "/" -- unless the dst path is empty.
*
- * -- cannot create a rooted path by pushing a path onto an empty path.
+ * -- cannot create a rooted path by appending a path onto an empty path.
*
- * -- pushing a "homed" path "~...." is assumed to be pushing onto the
+ * -- appending a "homed" path "~..../" is assumed to be appending to the
* required "home".
*
* The resulting path is reduced (see above).
*/
+extern qpath
+qpath_append(qpath dst, const qpath src)
+{
+ if (src != NULL)
+ return qpath_append_str_n(dst, qs_char_nn(src->path),
+ qs_len_nn(src->path)) ;
+ else
+ return qpath_append_str_n(dst, NULL, 0) ;
+} ;
+/*------------------------------------------------------------------------------
+ * Append src qstring onto the end of the dst path.
+ *
+ * See above for discussion of "append" operation.
+ */
extern qpath
-qpath_push(qpath qp, qpath qp_a)
+qpath_append_qs(qpath dst, const qstring src)
{
- return qpath_push_str(qp, qpath_path(qp_a)) ;
+ return qpath_append_str_n(dst, qs_char(src), qs_len(src)) ;
} ;
/*------------------------------------------------------------------------------
- * Push path string onto the end of the given path.
+ * Append src string onto the end of the dst path.
*
- * See above for discussion of "push" operation.
+ * See above for discussion of "append" operation.
*/
extern qpath
-qpath_push_str(qpath qp, const char* path)
+qpath_append_str(qpath dst, const char* src)
{
- qstring qs ;
- char* ep ;
- char* sp ;
- size_t len ;
- size_t off ;
+ return qpath_append_str_n(dst, src, (src != NULL) ? strlen(src) : 0) ;
+} ;
- if (qp == NULL)
- qp = qpath_init_new(NULL, NULL) ;
+/*------------------------------------------------------------------------------
+ * Append src string of given length onto the end of the dst path.
+ *
+ * See above for discussion of "append" operation.
+ */
+extern qpath
+qpath_append_str_n(qpath dst, const char* src, ulen n)
+{
+ ulen l ;
- qs = &qp->path ;
+ dst = qpath_make_if_null(dst) ;
- /* Trim the path to be pushed:
+ /* Trim the path to be appended:
*
* 1. discard from any leading '~' to the first '/' or to '\0'.
*
@@ -505,87 +620,257 @@ qpath_push_str(qpath qp, const char* path)
*
* 3. then establish length of result.
*/
- if (path != NULL)
+ l = n ;
+ n = qpath_trim_home(src, n) ;
+ src += (l - n) ; /* step past stuff trimmed */
+
+ /* Worry about whether need to add a '/' to the path before pushing */
+ if (qs_len_nn(dst->path) != 0)
{
- if (*path == '~')
- do
- {
- ++path ;
- } while ((*path != '/') && (*path != '\0')) ;
-
- while (*path == '/')
- ++path ; /* Step past leading '/' */
- len = strlen(path) ;
- }
+ if (*(qs_ep_char_nn(dst->path) - 1) != '/')
+ qs_append_str_n(dst->path, "/", 1) ;
+ } ;
+
+ /* Now append the src */
+ qs_append_str_n(dst->path, src, n) ;
+
+ /* Reduce the new part of the result, and return */
+ qpath_reduce(dst) ;
+
+ return dst ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Extend given dst path by simply affixing the given src path.
+ *
+ * Does not introduce any '/' or any other stuff.
+ *
+ * The resulting path is reduced (see above).
+ */
+extern qpath
+qpath_extend(qpath dst, const qpath src)
+{
+ if (src != NULL)
+ return qpath_extend_str_n(dst, qs_char_nn(src->path),
+ qs_len_nn(src->path)) ;
else
- len = 0 ;
+ return qpath_extend_str_n(dst, NULL, 0) ;
+} ;
- /* Worry about whether need to add a '/' to the path before pushing */
- sp = qs_char(qs) ;
- ep = qs_ep_char(qs) ; /* points at trailing '\0' */
+/*------------------------------------------------------------------------------
+ * Extend given dst path by simply affixing the given src qstring.
+ */
+extern qpath
+qpath_extend_qs(qpath dst, const qstring src)
+{
+ return qpath_extend_str_n(dst, qs_char(src), qs_len(src)) ;
+} ;
- if (sp == NULL)
- assert(ep == sp) ; /* ie qs->len == 0 if qs->body == NULL */
+/*------------------------------------------------------------------------------
+ * Extend given dst path by simply affixing the given src string.
+ */
+extern qpath
+qpath_extend_str(qpath dst, const char* src)
+{
+ return qpath_extend_str_n(dst, src, (src != NULL) ? strlen(src) : 0) ;
+} ;
- off = qs->len ; /* where new stuff starts */
+/*------------------------------------------------------------------------------
+ * Extend given dst path by simply affixing the given src string of the given
+ * length.
+ */
+extern qpath
+qpath_extend_str_n(qpath dst, const char* src, ulen n)
+{
+ dst = qpath_make_if_null(dst) ;
- if (ep != sp)
- {
- if (*(ep - 1) == '/')
- --off ; /* step back to the '/' */
- else
- {
- /* Destination is not empty and does not end '/', so append one.
- *
- * Note that we ensure there is space for the path which are
- * about to push, so at most one allocation required.
- */
- qs_need(qs, (ep - sp) + 1 + len) ;
- qs_append_n(qs, "/", 1) ;
- } ;
- } ;
+ qs_append_str_n(dst->path, src, n) ;
- /* Now push path */
- qs_append_n(qs, path, len) ;
+ qpath_reduce(dst) ;
- /* Reduce the new part of the result, and return
- *
- * Note that the 'off' points at the '/' which precedes the new stuff.
- * So will spot "/./" where the new stuff starts "./".
- */
- qpath_reduce(qp, off) ;
+ return dst ;
+} ;
- return qp ;
+/*------------------------------------------------------------------------------
+ * Prepend src path onto front of dst path.
+ *
+ * Like append, where the dst ends up being the dst appended to the src.
+ */
+extern qpath
+qpath_prepend(qpath dst, const qpath src)
+{
+ if (src != NULL)
+ return qpath_prepend_str_n(dst, qs_char_nn(src->path),
+ qs_len_nn(src->path)) ;
+ else
+ return qpath_prepend_str_n(dst, NULL, 0) ;
} ;
/*------------------------------------------------------------------------------
- * Join two paths to create a new path.
+ * Prepend src qstring onto front of dst path.
*
- * Copies the destination path and then pushes the other path onto it.
+ * Like append, where the dst ends up being the dst appended to the src.
*/
extern qpath
-qpath_join(qpath qp, qpath qp_a)
+qpath_prepend_qs(qpath dst, const qstring src)
{
- qpath qp_n ;
+ return qpath_prepend_str_n(dst, qs_char(src), qs_len(src)) ;
+} ;
- qp_n = qpath_copy(qp) ;
- return qpath_push(qp_n, qp_a) ;
+/*------------------------------------------------------------------------------
+ * Prepend src string onto front of dst path.
+ *
+ * Like append, where the dst ends up being the dst appended to the src.
+ */
+extern qpath
+qpath_prepend_str(qpath dst, const char* src)
+{
+ return qpath_prepend_str_n(dst, src, (src != NULL) ? strlen(src) : 0) ;
} ;
/*------------------------------------------------------------------------------
- * Join path string to the given path to create a new path.
+ * Prepend src string of given length onto front of dst path.
*
- * Copies the destination path and then pushes the path string onto it.
+ * Like append, where the dst ends up being the dst appended to the src.
*/
extern qpath
-qpath_join_str(qpath qp, const char* path)
+qpath_prepend_str_n(qpath dst, const char* src, ulen n)
+{
+ char* p ;
+ ulen r ;
+ bool need_slash ;
+
+ dst = qpath_make_if_null(dst) ;
+
+ /* Trim the path to be prepended to:
+ *
+ * 1. discard from any leading '~' to the first '/' (or end).
+ *
+ * 2. then discard any leading '/'
+ *
+ * 3. then establish length of any part to be replaced.
+ */
+ r = qs_len_nn(dst->path) ;
+ r -= qpath_trim_home(qs_char_nn(dst->path), r) ;
+
+ /* Worry about whether need to add a '/' to the path before pushing */
+ need_slash = (n > 0) && (*(src + n - 1) != '/') ;
+
+ /* Make room for src and possible slash in qstring */
+ qs_set_cp_nn(dst->path, 0) ;
+ qs_replace(dst->path, r, NULL, n + (need_slash ? 1 : 0)) ;
+
+ /* Now copy in the src */
+ p = qs_char_nn(dst->path) ;
+
+ if (n > 0)
+ memmove(p, src, n) ;
+
+ if (need_slash)
+ *(p + n) = '/' ;
+
+ /* Reduce the new part of the result, and return */
+ qpath_reduce(dst) ;
+
+ return dst ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Make a qpath from the given string, completing it, if required, by
+ * prepending the given directory qpath.
+ */
+extern qpath
+qpath_make(const char* src, const qpath dir)
+{
+ if (*src == '/')
+ return qpath_set(NULL, src) ;
+
+ return qpath_append_str(qpath_dup(dir), src) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If given dst path is not rooted (does not start with '/', prepend the
+ * given src path to it. Result is reduced.
+ */
+extern qpath
+qpath_complete(qpath dst, const qpath src)
+{
+ if (src != NULL)
+ return qpath_prepend_str_n(dst, qs_char_nn(src->path),
+ qs_len_nn(src->path)) ;
+ else
+ return qpath_prepend_str_n(dst, NULL, 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If given dst path is not rooted (does not start with '/', prepend the
+ * given src qstring to it. Result is reduced.
+ */
+extern qpath
+qpath_complete_qs(qpath dst, const qstring src)
+{
+ return qpath_complete_str_n(dst, qs_char(src), qs_len(src)) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If given dst path is not rooted (does not start with '/', prepend the
+ * given src string to it. Result is reduced.
+ */
+extern qpath
+qpath_complete_str(qpath dst, const char* src)
+{
+ return qpath_prepend_str_n(dst, src, (src != NULL) ? strlen(src) : 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If given dst path is not rooted (does not start with '/', prepend the
+ * given src string of given length to it. Result is reduced.
+ */
+extern qpath
+qpath_complete_str_n(qpath dst, const char* src, ulen n)
+{
+ dst = qpath_make_if_null(dst) ;
+
+ if ((qs_len_nn(dst->path) == 0) || (*(qs_char_nn(dst->path)) == '/'))
+ qpath_prepend_str_n(dst, src, n) ;
+ else
+ qpath_reduce(dst) ;
+
+ return dst ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Trim leading '~' up to and including one or more '/'.
+ *
+ * Return remaining length after the trim.
+ */
+static ulen
+qpath_trim_home(const char* p, ulen n)
{
- qpath qp_n ;
+ if ((n > 0) && (*p == '~'))
+ {
+ do /* Step past leadin '~' to first '/' */
+ {
+ ++p ;
+ --n ;
+ } while ((n > 0) && (*p != '/')) ;
+ } ;
+
+ while ((n > 0) && (*p == '/'))
+ {
+ ++p ; /* Step past leading '/' */
+ --n ;
+ } ;
- qp_n = qpath_copy(qp) ;
- return qpath_push_str(qp_n, path) ;
+ return n ;
} ;
+/*==============================================================================
+ *
+ */
+
+#if 0
+
/*------------------------------------------------------------------------------
* Does the given path start and end '/' ?
*/
@@ -817,3 +1102,111 @@ qpath_is_atom(qpath qp)
return false ;
} ;
+#endif
+
+/*==============================================================================
+ *
+ *
+ *
+ */
+/*------------------------------------------------------------------------------
+ * Reduce multiple '/' to single '/' (except for exactly "//" at start).
+ *
+ * Reduce "/./" to "/".
+ */
+static void
+qpath_reduce(qpath qp)
+{
+ char* sp ;
+ char* p ;
+ char* q ;
+
+ sp = qs_make_string(qp->path) ;
+ p = sp ;
+
+ /* Deal with special case of "//" at start.
+ *
+ * If find "//x", where x is anything other than '/', step past the first
+ * '/'. Could step past both "//", but that stops it seeing "//./zzz"
+ */
+ if ((*p == '/') && (*(p + 1) == '/') && (*(p + 2) != '/'))
+ ++p ;
+
+ /* Scan to see if there is anything that needs to be fixed.
+ *
+ * Looking for "//" and "/./".
+ */
+ while (1)
+ {
+ if (*p == '\0')
+ return ; /* scanned to end */
+
+ if (*p++ != '/') /* scanning for '/' */
+ continue ;
+
+ if (*p == '/')
+ break ; /* second '/' */
+
+ if (*p != '.')
+ continue ; /* not "//" and not "/." */
+
+ if (*(p+1) == '/')
+ break ; /* found "/./" */
+ } ;
+
+ /* Rats... there is something to be fixed.
+ *
+ * *p is second '/' of "//" or '.' of "/./".
+ */
+ q = p ; /* keep the first '/' */
+
+ while (*p != '\0')
+ {
+ ++p ; /* step past '.' or second '/' */
+
+ /* Step past any number of '/' and any number of "./". */
+ while (*p != '\0')
+ {
+ while (*p == '/') /* eat any number of these */
+ ++p ;
+
+ if (*p != '.') /* done if not '.' */
+ break ;
+
+ if (*(p+1) != '/') /* done if not "./" */
+ break ;
+
+ p += 2 ; /* Step past "./" */
+ } ;
+
+ /* Here we have *p which is not '/' and not "./", so unless is '\0'
+ * there is at least one character to move across.
+ *
+ * Copying stuff, until get to '\0' or find "//" or "/./"
+ */
+ while (*p != '\0')
+ {
+ *q++ = *p ; /* copy non-'\0' */
+
+ if (*p++ != '/')
+ continue ; /* keep going if wasn't '/' */
+
+ if (*p == '/')
+ break ; /* second '/' */
+
+ if (*p != '.')
+ continue ; /* not "//" and not "/." */
+
+ if (*(p+1) == '/')
+ break ; /* found "/./" */
+ } ;
+ } ;
+
+ /* Adjust the length and terminate */
+
+ qs_set_len_nn(qp->path, q - sp) ; /* set the new length (shorter !) */
+} ;
+
+
+
+
diff --git a/lib/qpath.h b/lib/qpath.h
index 27c61dff..23b710a6 100644
--- a/lib/qpath.h
+++ b/lib/qpath.h
@@ -22,15 +22,10 @@
#ifndef _ZEBRA_QPATH_H
#define _ZEBRA_QPATH_H
-#include "zebra.h"
#include "misc.h"
+#include "qstring.h"
#include "memory.h"
-
-
-
-
-
-
+#include "sys/stat.h"
/*==============================================================================
* For these purposes a path is "parts" separated by one more '/' characters.
@@ -55,56 +50,111 @@ enum qpath_sex
qp_double_absolute /* something, starting with "//" */
} ;
-typedef struct qpath qpath_t ;
-typedef struct qpath* qpath ;
-
/* The qpath structure is largely a qstring, but one in which there is always
* a body, even if it only contains "\0", and the len is kept up to date.
*/
struct qpath
{
- qstring_t path ;
+ qstring_t path ; /* Embedded */
} ;
+typedef struct qpath qpath_t[1] ;
+typedef struct qpath* qpath ;
+
/*==============================================================================
* Functions
*/
-extern qpath
-qpath_init_new(qpath qp, const char* path) ;
+extern qpath qpath_init_new(qpath qp) ;
+extern qpath qpath_reset(qpath qp, free_keep_b free_structure) ;
+extern qpath qpath_clear(qpath qp) ;
-extern qpath
-qpath_reset(qpath qp, bool free_structure) ;
+Inline qpath qpath_new(void) ;
+Inline qpath qpath_free(qpath qp) ;
-Inline qpath
-qpath_reset_free(qpath qp) { qpath_reset(qp, true) ; } ;
+Inline const char* qpath_string(qpath qp) ;
+Inline char* qpath_char_string(qpath qp) ;
+Inline ulen qpath_len(qpath qp) ;
+Inline const qstring qpath_qs(qpath qp) ;
-Inline qpath
-qpath_reset_keep(qpath qp) { qpath_reset(qp, false) ; } ;
+extern qpath qpath_set(qpath dst, const char* src) ;
+extern qpath qpath_set_n(qpath dst, const char* src, ulen n) ;
+extern qpath qpath_set_qs(qpath dst, const qstring src) ;
+extern qpath qpath_copy(qpath dst, const qpath src) ;
+Inline qpath qpath_dup(const qpath qp) ;
+Inline qpath qpath_dup_str(const char* src) ;
-Inline const char*
-qpath_path(qpath qp) ;
+extern qpath qpath_getcwd(qpath dst) ;
+extern int qpath_setcwd(qpath dst) ;
+extern int qpath_stat(qpath qp, struct stat* stat) ;
+extern int qpath_stat_is_file(qpath qp) ;
+extern int qpath_stat_is_directory(qpath qp) ;
-extern qpath
-qpath_trim(qpath qp) ;
+extern qpath qpath_shave(qpath qp) ;
-extern qpath
-qpath_copy(qpath qp) ;
+extern qpath qpath_append(qpath dst, const qpath src) ;
+extern qpath qpath_append_qs(qpath dst, const qstring src) ;
+extern qpath qpath_append_str(qpath dst, const char* src) ;
+extern qpath qpath_append_str_n(qpath dst, const char* src, ulen n) ;
-extern qpath
-qpath_pop(qpath qp) ;
+extern qpath qpath_extend(qpath dst, const qpath src) ;
+extern qpath qpath_extend_qs(qpath dst, const qstring src) ;
+extern qpath qpath_extend_str(qpath dst, const char* src) ;
+extern qpath qpath_extend_str_n(qpath dst, const char* src, ulen n) ;
-extern qpath
-qpath_push(qpath qp, qpath qp_a) ;
+extern qpath qpath_prepend(qpath dst, const qpath src) ;
+extern qpath qpath_prepend_qs(qpath dst, const qstring src) ;
+extern qpath qpath_prepend_str(qpath dst, const char* src) ;
+extern qpath qpath_prepend_str_n(qpath dst, const char* src, ulen n) ;
-extern qpath
-qpath_join(qpath qp, qpath qp_a) ;
+extern qpath qpath_make(const char* src, const qpath dir) ;
+
+extern qpath qpath_complete(qpath dst, const qpath src) ;
+extern qpath qpath_complete_qs(qpath dst, const qstring src) ;
+extern qpath qpath_complete_str(qpath dst, const char* src) ;
+extern qpath qpath_complete_str_n(qpath dst, const char* src, ulen n) ;
/*==============================================================================
* Inline stuff
*/
/*------------------------------------------------------------------------------
+ * Create new qpath.
+ */
+Inline qpath
+qpath_new(void)
+{
+ return qpath_init_new(NULL) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Free qpath.
+ */
+Inline qpath
+qpath_free(qpath qp)
+{
+ return qpath_reset(qp, free_it) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Duplicate qpath -- result will need to be freed.
+ */
+Inline qpath
+qpath_dup(const qpath qp)
+{
+ return qpath_copy(NULL, qp) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Duplicate string as a qpath -- result will need to be freed.
+ */
+Inline qpath
+qpath_dup_str(const char* src)
+{
+ return qpath_set(NULL, src) ;
+} ;
+
+/*------------------------------------------------------------------------------
* Get *temporary* pointer to actual path contained in the given qpath.
*
* This is *temporary* to the extent that when the qpath is changed or freed,
@@ -116,9 +166,50 @@ qpath_join(qpath qp, qpath qp_a) ;
* ('\0' terminated "").
*/
Inline const char*
-qpath_path(qpath qp)
+qpath_string(qpath qp)
+{
+ return (qp != NULL) ? qs_make_string(qp->path) : "" ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get *temporary* pointer to actual path contained in the given qpath.
+ *
+ * This is *temporary* to the extent that when the qpath is changed or freed,
+ * this pointer will be INVALID -- you have been warned.
+ *
+ * This is a *const* pointer.
+ *
+ * qpath may *not* be NULL qpath.
+ */
+Inline char*
+qpath_char_string(qpath qp)
+{
+ return qs_make_string(qp->path) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get length of the given qpath -- zero if NULL
+ */
+Inline ulen
+qpath_len(qpath qp)
+{
+ return (qp != NULL) ? qs_len_nn(qp->path) : 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get *temporary* pointer to qstring rendering of the given path.
+ *
+ * This is *temporary* to the extent that when the qpath is changed or freed,
+ * this pointer will be INVALID -- you have been warned.
+ *
+ * This is a *const* pointer.
+ *
+ * For a NULL qpath returns NULL qstring.
+ */
+Inline const qstring
+qpath_qs(qpath qp)
{
- return qs_string(qp != NULL ? &qp->path : qp) ;
+ return (qp != NULL) ? qp->path : NULL ;
} ;
#endif /* _ZEBRA_QPATH_H */
diff --git a/lib/qpnexus.c b/lib/qpnexus.c
index 001d5049..8a805900 100644
--- a/lib/qpnexus.c
+++ b/lib/qpnexus.c
@@ -36,18 +36,39 @@ static void qpn_in_thread_init(qpn_nexus qpn);
*/
+/*------------------------------------------------------------------------------
+ * Initialise the qpnexus handling -- to be done as soon as state of
+ * qpthreads_enabled is established.
+ */
+extern void
+qpn_init(void)
+{
+ qpt_data_create(qpn_self) ; /* thread specific data */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the thread's qpn_self to point at its qpnexus.
+ */
+static void
+qpn_self_knowledge(qpn_nexus qpn)
+{
+ qpt_data_set_value(qpn_self, qpn) ;
+} ;
+
/*==============================================================================
* Initialisation, add hook, free etc.
*
*/
/*------------------------------------------------------------------------------
- * Initialise a nexus -- allocating it if required.
+ * Initialise a nexus -- allocating it if required.
*
- * If main_thread is set then no new thread will be created
- * when qpn_exec() is called, instead the finite state machine will be
- * run in the calling thread. The main thread will only block the
- * message queue's signal. Non-main threads will block most signals.
+ * If main_thread is set then no new thread will be created when qpn_exec() is
+ * called, instead the finite state machine will be run in the calling thread.
+ *
+ * The main thread will only block the message queue's signal.
+ *
+ * Non-main threads will block most signals.
*
* Returns the qpn_nexus.
*/
@@ -66,10 +87,13 @@ qpn_init_new(qpn_nexus qpn, bool main_thread)
qpn->start = qpn_start;
if (main_thread)
- qpn->thread_id = qpt_thread_self();
+ {
+ qpn->thread_id = qpt_thread_self();
+ qpn_self_knowledge(qpn) ;
+ } ;
return qpn;
-}
+} ;
/*------------------------------------------------------------------------------
* Add a hook function to the given nexus.
@@ -259,50 +283,38 @@ qpn_start(void* arg)
static void
qpn_in_thread_init(qpn_nexus qpn)
{
- sigset_t newmask;
+ sigset_t sigmask[1];
qpn->thread_id = qpt_thread_self();
-
+ qpn_self_knowledge(qpn) ;
+
+ /* Signal mask.
+ *
+ * The main thread blocks nothing, except SIG_INTERRUPT. So (a) all
+ * signals other than the "hard cases" are routed to the main thread, and
+ * (b) SIG_INTERRUPT is masked until it is unmasked in pselect.
+ *
+ * Other threads block everything except the hard cases and SIG_INTERRUPT.
+ */
if (qpn->main_thread)
- {
- /* Main thread, block the message queue's signal */
- sigemptyset (&newmask);
- sigaddset (&newmask, SIGMQUEUE);
- }
+ sigmakeset(sigmask, SIG_INTERRUPT, -1) ;
else
- {
- /*
- * Not main thread. Block most signals, but be careful not to
- * defer SIGTRAP because doing so breaks gdb, at least on
- * NetBSD 2.0. Avoid asking to block SIGKILL, just because
- * we shouldn't be able to do so. Avoid blocking SIGFPE,
- * SIGILL, SIGSEGV, SIGBUS as this is undefined by POSIX.
- * Don't block SIGPIPE so that is gets ignored on this thread.
- */
- sigfillset (&newmask);
- sigdelset (&newmask, SIGTRAP);
- sigdelset (&newmask, SIGKILL);
- sigdelset (&newmask, SIGPIPE);
- sigdelset (&newmask, SIGFPE);
- sigdelset (&newmask, SIGILL);
- sigdelset (&newmask, SIGSEGV);
- sigdelset (&newmask, SIGBUS);
- }
+ siginvset(sigmask, signal_get_hard_set()) ;
- if (qpthreads_enabled)
- qpt_thread_sigmask(SIG_BLOCK, &newmask, NULL);
- else
- {
- if (sigprocmask(SIG_BLOCK, &newmask, NULL) != 0)
- zabort_errno("sigprocmask failed") ;
- }
+ qpt_thread_sigmask(SIG_BLOCK, sigmask, NULL);
- /* Now we have thread_id and mask, prep for using message queue. */
+ /* The signal mask to be used during pselect() */
+ sigcopyset(qpn->pselect_mask, sigmask) ;
+ sigdelset(qpn->pselect_mask, SIG_INTERRUPT) ;
+ qpn->pselect_signal = SIG_INTERRUPT ;
+
+ /* Now we have thread_id and mask, prep for using message queue. */
if (qpn->queue != NULL)
- qpn->mts = mqueue_thread_signal_init(qpn->mts, qpn->thread_id, SIGMQUEUE);
+ qpn->mts = mqueue_thread_signal_init(qpn->mts, qpn->thread_id,
+ SIG_INTERRUPT) ;
if (qpn->selection != NULL)
- qps_set_signal(qpn->selection, SIGMQUEUE, newmask);
-}
+ qps_set_signal(qpn->selection, qpn->pselect_mask);
+} ;
/*------------------------------------------------------------------------------
* Ask the thread to terminate itself quickly and cleanly.
@@ -318,6 +330,6 @@ qpn_terminate(qpn_nexus qpn)
/* wake up any pselect */
if (qpthreads_enabled)
- qpt_thread_signal(qpn->thread_id, SIGMQUEUE);
+ qpt_thread_signal(qpn->thread_id, SIG_INTERRUPT);
} ;
}
diff --git a/lib/qpnexus.h b/lib/qpnexus.h
index a6c8bf44..1aeefc04 100644
--- a/lib/qpnexus.h
+++ b/lib/qpnexus.h
@@ -24,9 +24,9 @@
#include "misc.h"
#include <time.h>
-#include <pthread.h>
#include <unistd.h>
#include <errno.h>
+#include <signal.h>
#include "zassert.h"
#include "qpthreads.h"
@@ -42,15 +42,17 @@
* action routines.
*
*/
+enum
+{
+ /* maximum time in seconds to sit in a pselect */
+ MAX_PSELECT_WAIT = 10,
-/* maximum time in seconds to sit in a pselect */
-#define MAX_PSELECT_WAIT 10
-
-/* signal for message queues */
-#define SIGMQUEUE SIGUSR2
+ /* signal for message queues */
+ SIG_INTERRUPT = SIGUSR2,
-/* number of hooks per hook list */
-enum { qpn_hooks_max = 4 } ;
+ /* number of hooks per hook list */
+ qpn_hooks_max = 4,
+} ;
/*==============================================================================
* Data Structures.
@@ -70,26 +72,30 @@ typedef struct qpn_nexus* qpn_nexus ;
struct qpn_nexus
{
- /* set true to terminate the thread (eventually) */
+ /* set true to terminate the thread (eventually) */
bool terminate;
- /* true if this is the main thread */
+ /* true if this is the main thread */
bool main_thread;
- /* thread ID */
- qpt_thread_t thread_id;
+ /* thread ID */
+ qpt_thread_t thread_id;
- /* pselect handler */
+ /* Signal mask for pselect */
+ sigset_t pselect_mask[1] ;
+ int pselect_signal ;
+
+ /* pselect handler */
qps_selection selection;
- /* timer pile */
+ /* timer pile */
qtimer_pile pile;
- /* message queue */
+ /* message queue */
mqueue_queue queue;
mqueue_thread_signal mts;
- /* qpthread routine, can override */
+ /* qpthread routine, can override */
void* (*start)(void*);
/* in-thread initialise, can override. Called within the thread after all
@@ -138,10 +144,22 @@ struct qpn_nexus
struct qpn_hook_list background ;
};
+/*------------------------------------------------------------------------------
+ * Each thread that has a qpnexus uses this piece of thread specific data in
+ * order to be able to find its own nexus.
+ */
+qpt_data_t qpn_self ; /* thread specific data */
+
+Inline qpn_nexus
+qpn_find_self(void)
+{
+ return qpt_data_get_value(qpn_self) ;
+} ;
+
/*==============================================================================
* Functions
*/
-
+extern void qpn_init(void) ;
extern qpn_nexus qpn_init_new(qpn_nexus qpn, bool main_thread);
extern void qpn_add_hook_function(qpn_hook_list list, void* hook) ;
extern void qpn_exec(qpn_nexus qpn);
diff --git a/lib/qpselect.c b/lib/qpselect.c
index bcd2e2c9..3fb80daf 100644
--- a/lib/qpselect.c
+++ b/lib/qpselect.c
@@ -18,6 +18,7 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
+#include "misc.h"
#include <signal.h>
#include <string.h>
@@ -30,18 +31,32 @@
#include "vector.h"
/*------------------------------------------------------------------------------
- * Sort out any debug setting
+ * Sort out QPSELECT_DEBUG.
+ *
+ * Set to 1 if defined, but blank.
+ * Set to QDEBUG if not defined.
+ *
+ * Force to 0 if VTY_NO_DEBUG is defined and not zero.
+ *
+ * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to:
+ *
+ * * can set QPSELECT_DEBUG == 0 to turn off debug
+ * * or set QPSELECT_DEBUG != 0 to turn on debug
+ * * or set QPSELECT_NO_DEBUG != to force debug off
*/
-#ifdef QPSELECT_DEBUG /* Can be forced from outside */
-# if QPSELECT_DEBUG
-# define QPSELECT_DEBUG 1 /* Force 1 or 0 */
-#else
-# define QPSELECT_DEBUG 0
+
+#ifdef QPSELECT_DEBUG /* If defined, make it 1 or 0 */
+# if IS_BLANK_OPTION(QPSELECT_DEBUG)
+# undef QPSELECT_DEBUG
+# define QPSELECT_DEBUG 1
# endif
-#else
-# ifdef QDEBUG
-# define QPSELECT_DEBUG 1 /* Follow QDEBUG */
-#else
+#else /* If not defined, follow QDEBUG */
+# define QPSELECT_DEBUG QDEBUG
+#endif
+
+#ifdef QPSELECT_NO_DEBUG /* Override, if defined */
+# if IS_NOT_ZERO_OPTION(QPSELECT_NO_DEBUG)
+# undef QPSELECT_DEBUG
# define QPSELECT_DEBUG 0
# endif
#endif
@@ -283,29 +298,18 @@ qps_selection_ream(qps_selection qps, int free_structure)
/*------------------------------------------------------------------------------
* Set the signal mask for the selection.
*
- * This supports the unmasking of a single signal for the duration of the
+ * This supports the unmasking of one or more signals for the duration of the
* pselect operation.
*
- * It is assumed that the set of signals generally masked by a thread is
- * essentially static. So this function is passed that set. (So the sigmask
- * argument must have the signum signal masked.)
- *
- * If the set of signals masked by the thread changes, then this function
+ * * If the set of signals masked by the thread changes, then this function
* should be called again.
*
- * Setting a signum == 0 turns OFF the use of the sigmask.
+ * Setting a sigmask == NULL turns OFF the use of the sigmask.
*/
-void
-qps_set_signal(qps_selection qps, int signum, sigset_t sigmask)
+extern void
+qps_set_signal(qps_selection qps, const sigset_t* sigmask)
{
- qps->signum = signum ;
-
- if (signum != 0)
- {
- assert(sigismember(&sigmask, signum)) ;
- sigdelset(&sigmask, signum) ;
- qps->sigmask = sigmask ;
- } ;
+ qps->sigmask = sigmask ;
} ;
/*------------------------------------------------------------------------------
@@ -377,7 +381,7 @@ qps_pselect(qps_selection qps, qtime_t max_wait)
p_fds[qps_write_mnum],
p_fds[qps_error_mnum],
qtime2timespec(&ts, max_wait),
- (qps->signum != 0) ? &qps->sigmask : NULL) ;
+ qps->sigmask) ;
/* If have something, set and return the pending count. */
if (n > 0)
@@ -1423,3 +1427,97 @@ qps_selection_validate(qps_selection qps)
if (n != qps->pend_count)
zabort("Non-empty bit vector where tried count == 0") ;
} ;
+
+/*==============================================================================
+ * Miniature pselect -- for where want to wait for a small number of things.
+ */
+
+/*------------------------------------------------------------------------------
+ * Initialise a qps_mini and set one fd in the given mode.
+ *
+ * If the fd is < 0, sets nothing (returns empty qps_mini).
+ *
+ * Zero timeout is an indefinite wait !
+ */
+extern qps_mini
+qps_mini_set(qps_mini qm, int fd, qps_mnum_t mode, uint timeout)
+{
+ qps_super_set_zero(qm->sets, qps_mnum_count) ;
+ qm->fd_last = -1 ;
+
+ qm->interval = QTIME(timeout) ; /* convert from time in seconds */
+ qm->end_time = qt_add_monotonic(qm->interval) ;
+
+ qps_mini_add(qm, fd, mode) ;
+
+ return qm ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Add given fd in the given mode to the given qps_mini.
+ *
+ * If the fd is < 0, adds nothing (returns qps_mini unchanged).
+ */
+extern qps_mini
+qps_mini_add(qps_mini qm, int fd, qps_mnum_t mode)
+{
+ if (fd >= 0)
+ {
+ FD_SET(fd, &(qm->sets[mode].fdset)) ;
+ if (fd > qm->fd_last)
+ qm->fd_last = fd ;
+ } ;
+
+ return qm ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Wait for a qps_mini -- with given timeout, in seconds.
+ *
+ * If !signal, loops on EINTR, so will not exit until gets ready state or a
+ * timeout.
+ *
+ * If signal, will exit on EINTR. Can call this again after an EINTR return,
+ * and will continue to wait the *remainder* of the timeout interval.
+ *
+ * Returns what pselect returns: > 0 number of bits set
+ * == 0 no bits set <=> timed out
+ * < 0 EINTR (iff signal)
+ */
+extern int
+qps_mini_wait(qps_mini qm, const sigset_t* sigmask, bool signal)
+{
+ while (1)
+ {
+ struct timespec ts[1] ;
+ int n ;
+
+ n = pselect(qm->fd_last + 1,
+ &(qm->sets[qps_read_mnum].fdset),
+ &(qm->sets[qps_write_mnum].fdset),
+ &(qm->sets[qps_error_mnum].fdset),
+ (qm->interval > 0) ? qtime2timespec(ts, qm->interval)
+ : NULL,
+ sigmask) ;
+ if (n >= 0)
+ return n ;
+
+ if (errno != EINTR)
+ break ;
+
+ if (qm->interval > 0)
+ {
+ /* set new interval, in case comes back */
+ qm->interval = qm->end_time - qt_get_monotonic() ;
+ if (qm->interval < 0)
+ return 0 ;
+ } ;
+
+ if (signal)
+ return -1 ; /* signal EINTR */
+ } ;
+
+ zabort_errno("Failed in pselect") ;
+} ;
+
+
diff --git a/lib/qpselect.h b/lib/qpselect.h
index 48c86fe8..37bf8680 100644
--- a/lib/qpselect.h
+++ b/lib/qpselect.h
@@ -140,8 +140,7 @@ struct qps_selection
qps_mnum_t pend_mnum ; /* error/read/write mode pending (if any) */
int pend_fd ; /* fd pending (if any) */
- int signum ; /* signal that sigmask is enabling -- 0 => none */
- sigset_t sigmask ; /* sigmask to use for duration of pselect */
+ const sigset_t* sigmask ; /* sigmask to use for duration of pselect */
} ;
struct qps_file
@@ -181,7 +180,7 @@ qps_selection_ream(qps_selection qps, int free_structure) ;
#define qps_selection_ream_keep(qps) qps_selection_ream(qps, 0)
extern void
-qps_set_signal(qps_selection qps, int signum, sigset_t sigmask) ;
+qps_set_signal(qps_selection qps, const sigset_t* sigmask) ;
extern int
qps_pselect(qps_selection qps, qtime_mono_t timeout) ;
@@ -235,4 +234,25 @@ qps_set_file_info(qps_file qf, void* info)
qf->file_info = info ;
} ;
+/*==============================================================================
+ * Miniature pselect
+ *
+ */
+struct qps_mini
+{
+ fd_full_set sets ; /* bit vectors for pselect enabled stuff */
+ int fd_last ; /* highest numbered fd; -1 => none at all */
+
+ qtime_t interval ;
+ qtime_mono_t end_time ;
+} ;
+
+typedef struct qps_mini qps_mini_t[1] ;
+typedef struct qps_mini* qps_mini ;
+
+extern qps_mini qps_mini_set(qps_mini qm, int fd, qps_mnum_t mode,
+ uint timeout) ;
+extern qps_mini qps_mini_add(qps_mini qm, int fd, qps_mnum_t mode) ;
+extern int qps_mini_wait(qps_mini qm, const sigset_t* sigmask, bool signal) ;
+
#endif /* _ZEBRA_QPSELECT_H */
diff --git a/lib/qpthreads.c b/lib/qpthreads.c
index baa34d52..8909f64b 100644
--- a/lib/qpthreads.c
+++ b/lib/qpthreads.c
@@ -18,10 +18,7 @@
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
-
-/* This MUST come first... otherwise we don't get __USE_UNIX98, which is */
-/* essential if glibc is to allow pthread_mutexattr_settype() to be used. */
-#include "config.h"
+#include "misc.h"
#include <signal.h>
#include <string.h>
@@ -29,11 +26,6 @@
#include "qpthreads.h"
#include "memory.h"
-/* If this is not set, will get errors later. */
-//#ifndef __USE_UNIX98
-//#error "_USE_UNIX98 not defined"
-//#endif
-
/*==============================================================================
* Quagga Pthread Interface -- qpt_xxxx
*
@@ -251,49 +243,56 @@ enum qpthreads_enabled_state
qpt_state_set_disabled = 2,
qpt_state_set_enabled = 3,
} ;
+typedef enum qpthreads_enabled_state qpthreads_enabled_state_t ;
-static enum qpthreads_enabled_state qpthreads_enabled_state = qpt_state_unset ;
+static qpthreads_enabled_state_t qpthreads_enabled_state = qpt_state_unset ;
-uint8_t qpthreads_enabled_flag = 0 ;
-uint8_t qpthreads_thread_created_flag = 0 ;
+bool qpthreads_enabled_flag = false ;
+bool qpthreads_thread_created_flag = false ;
-/* Function to set qpthreads_enabled, one way or the other.
+/*------------------------------------------------------------------------------
+ * Function to set qpthreads_enabled, one way or the other.
*
* Returns: true <=> successful set the required state.
* false <=> it is too late to enable qpthreads :-(
*
* NB: can repeatedly set to the same state, but not change state once set.
*/
-extern int
-qpt_set_qpthreads_enabled(int how)
+extern bool
+qpt_set_qpthreads_enabled(bool want_enabled)
{
switch (qpthreads_enabled_state)
- {
- case qpt_state_unset:
- break ;
- case qpt_state_set_frozen:
- if (how != 0)
- return 0 ;
- break ;
- case qpt_state_set_disabled:
- if (how != 0)
- zabort("qpthreads_enabled is already set: cannot set enabled") ;
- break ;
- case qpt_state_set_enabled:
- if (how == 0)
- zabort("qpthreads_enabled is already set: cannot set disabled") ;
- break ;
- default:
- break ;
- }
-
- qpthreads_enabled_flag = (how != 0) ;
- qpthreads_enabled_state = (how != 0) ? qpt_state_set_enabled
- : qpt_state_set_disabled ;
- return 1 ;
+ {
+ case qpt_state_unset:
+ break ;
+
+ case qpt_state_set_frozen:
+ if (want_enabled)
+ return false ; /* too late to set the state */
+ break ;
+
+ case qpt_state_set_disabled:
+ if (want_enabled)
+ zabort("qpthreads_enabled is already set: cannot set enabled") ;
+ break ;
+
+ case qpt_state_set_enabled:
+ if (!want_enabled)
+ zabort("qpthreads_enabled is already set: cannot set disabled") ;
+ break ;
+
+ default:
+ break ;
+ } ;
+
+ qpthreads_enabled_flag = want_enabled ;
+ qpthreads_enabled_state = want_enabled ? qpt_state_set_enabled
+ : qpt_state_set_disabled ;
+ return true ;
} ;
-/* Get state of qpthreads_enabled, and freeze if not yet explictly set.
+/*------------------------------------------------------------------------------
+ * Get state of qpthreads_enabled, and freeze if not yet explictly set.
*
* Where some initialisation depends on the state of qpthreads_enabled(), this
* returns the state and freezes it if it is implicitly not enabled.
@@ -320,7 +319,8 @@ qpt_freeze_qpthreads_enabled(void)
* are NULL, then the system defaults are used.
*/
-/* Initialise a set of attributes -- setting the scheduling options.
+/*------------------------------------------------------------------------------
+ * Initialise a set of attributes -- setting the scheduling options.
*
* Options:
*
@@ -351,7 +351,7 @@ qpt_freeze_qpthreads_enabled(void)
*
* Returns the address of the qpt_thread_attr_t structure.
*/
-qpt_thread_attr_t*
+extern qpt_thread_attr_t*
qpt_thread_attr_init(qpt_thread_attr_t* attr, enum qpt_attr_options opts,
int scope, int policy, int priority)
{
@@ -420,7 +420,8 @@ qpt_thread_attr_init(qpt_thread_attr_t* attr, enum qpt_attr_options opts,
return attr ;
} ;
-/* Create Thread with given attributes (if any).
+/*------------------------------------------------------------------------------
+ * Create Thread with given attributes (if any).
*
* If no attributes are given (attr == NULL) the thread is created with system
* default attributes -- *except* that it is created joinable.
@@ -429,7 +430,7 @@ qpt_thread_attr_init(qpt_thread_attr_t* attr, enum qpt_attr_options opts,
*
* Returns the qpt_thread_t "thread id".
*/
-qpt_thread_t
+extern qpt_thread_t
qpt_thread_create(void* (*start)(void*), void* arg, qpt_thread_attr_t* attr)
{
qpt_thread_attr_t thread_attr ;
@@ -438,7 +439,7 @@ qpt_thread_create(void* (*start)(void*), void* arg, qpt_thread_attr_t* attr)
int err ;
passert(qpthreads_enabled) ;
- qpthreads_thread_created_flag = 1 ; /* and at least one thread created */
+ qpthreads_thread_created_flag = true ; /* at least one thread created */
default_attr = (attr == NULL) ;
if (default_attr)
@@ -458,7 +459,8 @@ qpt_thread_create(void* (*start)(void*), void* arg, qpt_thread_attr_t* attr)
return thread_id ;
} ;
-/* Join given thread -- do nothing if !qpthreads_enabled
+/*------------------------------------------------------------------------------
+ * Join given thread -- do nothing if !qpthreads_enabled
*
* Tolerates ESRCH (no thread known by given id).
*
@@ -490,7 +492,8 @@ qpt_thread_join(qpt_thread_t thread_id)
* Mutex initialise and destroy.
*/
-/* Initialise Mutex (allocating if required)
+/*------------------------------------------------------------------------------
+ * Initialise Mutex (allocating if required)
*
* Does nothing if !qpthreads_enabled -- but freezes the state (attempting to
* later enable qpthreads will be a FATAL error).
@@ -508,7 +511,7 @@ qpt_thread_join(qpt_thread_t thread_id)
*
* Returns the mutex -- or original mx if !qpthreads_enabled.
*/
-qpt_mutex
+extern qpt_mutex
qpt_mutex_init_new(qpt_mutex mx, enum qpt_mutex_options opts)
{
pthread_mutexattr_t mutex_attr ;
@@ -569,7 +572,8 @@ qpt_mutex_init_new(qpt_mutex mx, enum qpt_mutex_options opts)
return mx ;
} ;
-/* Destroy given mutex, and (if required) free it.
+/*------------------------------------------------------------------------------
+ * Destroy given mutex, and (if required) free it.
* -- or do nothing if !qpthreads_enabled.
*
* Returns NULL if freed the mutex, otherwise the address of same.
@@ -578,7 +582,7 @@ qpt_mutex_init_new(qpt_mutex mx, enum qpt_mutex_options opts)
* anything, so there can be nothing to release -- so does nothing, but
* returns the original mutex address (if any).
*/
-qpt_mutex
+extern qpt_mutex
qpt_mutex_destroy(qpt_mutex mx, int free_mutex)
{
int err ;
@@ -600,7 +604,8 @@ qpt_mutex_destroy(qpt_mutex mx, int free_mutex)
* Condition Variable initialise and destroy.
*/
-/* Initialise Condition Variable (allocating if required).
+/*------------------------------------------------------------------------------
+ * Initialise Condition Variable (allocating if required).
*
* Does nothing if !qpthreads_enabled -- but freezes the state (attempting to
* later enable qpthreads will be a FATAL error).
@@ -615,7 +620,7 @@ qpt_mutex_destroy(qpt_mutex mx, int free_mutex)
*
* Returns the condition variable -- or original cv id !qpthreads_enabled.
*/
-qpt_cond
+extern qpt_cond
qpt_cond_init_new(qpt_cond cv, enum qpt_cond_options opts)
{
pthread_condattr_t cond_attr ;
@@ -662,7 +667,8 @@ qpt_cond_init_new(qpt_cond cv, enum qpt_cond_options opts)
return cv ;
} ;
-/* Destroy given condition variable, and (if required) free it
+/*------------------------------------------------------------------------------
+ * Destroy given condition variable, and (if required) free it
* -- or do nothing if !qpthreads_enabled.
*
* NB: if !qpthreads_enabled qpt_cond_init_new() will not have allocated
@@ -671,7 +677,7 @@ qpt_cond_init_new(qpt_cond cv, enum qpt_cond_options opts)
*
* Returns NULL if freed the condition variable, otherwise the address of same.
*/
-qpt_cond
+extern qpt_cond
qpt_cond_destroy(qpt_cond cv, int free_cond)
{
int err ;
@@ -689,7 +695,8 @@ qpt_cond_destroy(qpt_cond cv, int free_cond)
return cv ;
} ;
-/* Wait for given condition variable or time-out
+/*------------------------------------------------------------------------------
+ * Wait for given condition variable or time-out
* -- or return immediate success if !qpthreads_enabled.
*
* Returns: wait succeeded (1 => success, 0 => timed-out).
@@ -698,8 +705,7 @@ qpt_cond_destroy(qpt_cond cv, int free_cond)
*
* Has to check the return value, so zabort_errno if not EBUSY.
*/
-
-int
+extern int
qpt_cond_timedwait(qpt_cond cv, qpt_mutex mx, qtime_mono_t timeout_time)
{
struct timespec ts ;
@@ -729,40 +735,43 @@ qpt_cond_timedwait(qpt_cond cv, qpt_mutex mx, qtime_mono_t timeout_time)
* Signal Handling.
*/
-/* Set thread signal mask -- requires qpthreads_enabled.
- *
- * Thin wrapper around pthread_sigmask.
+/*------------------------------------------------------------------------------
+ * Set thread signal mask.
*
- * zaborts if gets any error.
+ * If !qpthreads_enabled will use sigprocmask(), otherwise pthread_sigmask().
*
- * NB: it is a FATAL error to do this if !qpthreads_enabled.
+ * In fact pthread_sigmask() works for single-threaded processes, but we avoid
+ * depending on pthread library if it's not essential.
*
- * This is mostly because wish to avoid all pthreads_xxx calls when not
- * using pthreads. There is no reason not to use this in a single threaded
- * program.
+ * zaborts if gets any error.
*/
-void
+extern void
qpt_thread_sigmask(int how, const sigset_t* set, sigset_t* oset)
{
- int err ;
-
- passert(qpthreads_enabled) ;
-
if (oset != NULL)
sigemptyset(oset) ; /* to make absolutely sure */
- err = pthread_sigmask(how, set, oset) ;
- if (err != 0)
- zabort_err("pthread_sigmask failed", err) ;
+ if (qpthreads_enabled)
+ {
+ int err = pthread_sigmask(how, set, oset) ;
+ if (err != 0)
+ zabort_err("pthread_sigmask failed", err) ;
+ }
+ else
+ {
+ if (sigprocmask(how, set, oset) != 0)
+ zabort_errno("sigprocmask failed") ;
+ } ;
} ;
-/* Send given thread the given signal -- requires qpthreads_enabled (!)
+/*------------------------------------------------------------------------------
+ * Send given thread the given signal -- requires qpthreads_enabled (!)
*
* Thin wrapper around pthread_kill.
*
* zaborts if gets any error.
*/
-void
+extern void
qpt_thread_signal(qpt_thread_t thread, int signum)
{
int err ;
@@ -773,3 +782,58 @@ qpt_thread_signal(qpt_thread_t thread, int signum)
if (err != 0)
zabort_err("pthread_kill failed", err) ;
} ;
+
+/*==============================================================================
+ * Thread Specific Data Handling.
+ *
+ * When creating a key for a piece of thread specific data one could:
+ *
+ * a. arrange for all keys to be created before any threads are
+ * created -- or at least before any have a need for the key.
+ *
+ * b. use pthread_once() to protect the creation of the key.
+ *
+ * Note that there does not appear to be a way of distinguishing a key
+ * that has been created from one that has not.
+ *
+ * For !qpthreads_enabled systems, the "thread specific" data is embedded in
+ * the qpt_data object.
+ */
+
+/*------------------------------------------------------------------------------
+ * Create the given thread specific data.
+ *
+ * NB: if no value is ever set, then qpt_data_get_value() will return NULL
+ * (whether qpthreads_enabled, or not).
+ */
+extern void
+qpt_data_create(qpt_data data)
+{
+ memset(data, 0, sizeof(union qpt_data)) ;
+
+ if (qpthreads_enabled_freeze)
+ {
+ int err = pthread_key_create(&data->key, NULL) ;
+ if (err != 0)
+ zabort_err("pthread_key_create failed", err) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Delete the given thread specific data.
+ *
+ * NB: it is the caller's responsibility to release any memory the value of
+ * the thread specific data may refer to.
+ */
+extern void
+qpt_data_delete(qpt_data data)
+{
+ if (qpthreads_enabled)
+ {
+ int err = pthread_key_delete(data->key) ;
+ if (err != 0)
+ zabort_err("pthread_key_delete failed", err) ;
+ } ;
+
+ memset(data, 0, sizeof(union qpt_data)) ;
+} ;
diff --git a/lib/qpthreads.h b/lib/qpthreads.h
index da6b2319..926d11e5 100644
--- a/lib/qpthreads.h
+++ b/lib/qpthreads.h
@@ -22,6 +22,8 @@
#ifndef _ZEBRA_QPTHREADS_H
#define _ZEBRA_QPTHREADS_H
+#include "zconfig.h"
+
#include "misc.h"
#include <time.h>
#include <pthread.h>
@@ -52,16 +54,33 @@
#error Require _POSIX_THREADS
#endif
-#ifdef QPTHREADS_DEBUG /* Can be forced from outside */
-# if QPTHREADS_DEBUG
-# define QPTHREADS_DEBUG 1 /* Force 1 or 0 */
-#else
-# define QPTHREADS_DEBUG 0
+/*------------------------------------------------------------------------------
+ * Sort out QPTHREADS_DEBUG.
+ *
+ * Set to 1 if defined, but blank.
+ * Set to QDEBUG if not defined.
+ *
+ * Force to 0 if QPTHREADS_NO_DEBUG is defined and not zero.
+ *
+ * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to:
+ *
+ * * can set QPTHREADS_DEBUG == 0 to turn off debug
+ * * or set QPTHREADS_DEBUG != 0 to turn on debug
+ * * or set QPTHREADS_NO_DEBUG != 0 to force debug off
+ */
+
+#ifdef QPTHREADS_DEBUG /* If defined, make it 1 or 0 */
+# if IS_BLANK_OPTION(QPTHREADS_DEBUG)
+# undef QPTHREADS_DEBUG
+# define QPTHREADS_DEBUG 1
# endif
-#else
-# ifdef QDEBUG
-# define QPTHREADS_DEBUG 1 /* Follow QDEBUG */
-#else
+#else /* If not defined, follow QDEBUG */
+# define QPTHREADS_DEBUG QDEBUG
+#endif
+
+#ifdef QPTHREADS_NO_DEBUG /* Override, if defined */
+# if IS_NOT_ZERO_OPTION(QPTHREADS_NO_DEBUG)
+# undef QPTHREADS_DEBUG
# define QPTHREADS_DEBUG 0
# endif
#endif
@@ -81,42 +100,41 @@ enum { qpthreads_debug = QPTHREADS_DEBUG } ;
* qpthreads_enabled_freeze -- to test and freeze unset if not yet enabled
*/
-#define qpthreads_enabled ((const uint8_t)qpthreads_enabled_flag)
+#define qpthreads_enabled ((const bool)qpthreads_enabled_flag)
#define qpthreads_enabled_freeze qpt_freeze_qpthreads_enabled()
-#define qpthreads_thread_created ((const uint8_t) \
- qpthreads_thread_created_flag)
+#define qpthreads_thread_created ((const bool)qpthreads_thread_created_flag)
/*==============================================================================
* Data types
*/
-
typedef pthread_t qpt_thread_t ;
-typedef pthread_mutex_t qpt_mutex_t ;
-typedef pthread_cond_t qpt_cond_t ;
+
+typedef pthread_mutex_t qpt_mutex_t[1] ;
+typedef pthread_mutex_t* qpt_mutex ;
+
+typedef pthread_cond_t qpt_cond_t[1] ;
+typedef pthread_cond_t* qpt_cond ;
typedef pthread_attr_t qpt_thread_attr_t ;
-typedef qpt_mutex_t* qpt_mutex ;
-typedef qpt_cond_t* qpt_cond ;
/*==============================================================================
* Thread Creation -- see qpthreads.c for further discussion.
*
* NB: it is a FATAL error to attempt these if !qpthreads_enabled.
*/
-
enum qpt_attr_options
{
qpt_attr_joinable = 0, /* the default for Quagga */
- qpt_attr_detached = 0x0001, /* otherwise will set joinable */
+ qpt_attr_detached = BIT(0), /* otherwise will set joinable */
- qpt_attr_sched_inherit = 0x0002, /* otherwise will use default */
+ qpt_attr_sched_inherit = BIT(1), /* otherwise will use default */
- qpt_attr_sched_scope = 0x0004, /* otherwise inherit/default */
- qpt_attr_sched_policy = 0x0008, /* otherwise inherit/default */
- qpt_attr_sched_priority = 0x0010, /* otherwise inherit/default */
+ qpt_attr_sched_scope = BIT(2), /* otherwise inherit/default */
+ qpt_attr_sched_policy = BIT(3), /* otherwise inherit/default */
+ qpt_attr_sched_priority = BIT(4), /* otherwise inherit/default */
} ;
#define qpt_attr_sched_explicit ( qpt_attr_sched_scope \
@@ -140,11 +158,11 @@ qpt_thread_join(qpt_thread_t thread_id) ;
/*==============================================================================
* qpthreads_enabled support -- NOT FOR PUBLIC CONSUMPTION !
*/
-Private uint8_t qpthreads_enabled_flag ; /* DO NOT TOUCH THIS PLEASE */
-Private uint8_t qpthreads_thread_created_flag ; /* DO NOT TOUCH THIS PLEASE */
+Private bool qpthreads_enabled_flag ; /* DO NOT TOUCH THIS PLEASE */
+Private bool qpthreads_thread_created_flag ; /* DO NOT TOUCH THIS PLEASE */
-Private int
-qpt_set_qpthreads_enabled(int how) ; /* qpthreads_enabled := how */
+Private bool
+qpt_set_qpthreads_enabled(bool want_enabled) ; /* qpthreads_enabled := want */
Private int
qpt_freeze_qpthreads_enabled(void) ; /* get and freeze qpthreads_enabled */
@@ -205,22 +223,25 @@ Inline bool qpt_thread_is_self(qpt_thread_t id)
enum qpt_mutex_options
{
- qpt_mutex_quagga = 0x0000, /* Quagga's default */
- qpt_mutex_normal = 0x0001,
- qpt_mutex_recursive = 0x0002,
- qpt_mutex_errorcheck = 0x0003,
- qpt_mutex_default = 0x0004, /* system default */
+ qpt_mutex_quagga = 0, /* Quagga's default */
+ qpt_mutex_normal,
+ qpt_mutex_recursive,
+ qpt_mutex_errorcheck,
+ qpt_mutex_default, /* system default */
} ;
#ifndef QPT_MUTEX_TYPE_DEFAULT
# define QPT_MUTEX_TYPE_DEFAULT PTHREAD_MUTEX_NORMAL
#endif
+enum
+{
#if QPTHREADS_DEBUG
-# define QPT_MUTEX_TYPE PTHREAD_MUTEX_ERRORCHECK
+ QPT_MUTEX_TYPE = PTHREAD_MUTEX_ERRORCHECK
#else
-# define QPT_MUTEX_TYPE QPT_MUTEX_TYPE_DEFAULT
+ QPT_MUTEX_TYPE = QPT_MUTEX_TYPE_DEFAULT
#endif
+} ;
extern qpt_mutex /* freezes qpthreads_enabled */
qpt_mutex_init_new(qpt_mutex mx, enum qpt_mutex_options opts) ;
@@ -320,7 +341,7 @@ qpt_mutex_lock(qpt_mutex mx)
{
if (qpthreads_enabled)
{
-#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS)
+#if QPTHREADS_DEBUG
pthread_mutex_lock(mx) ;
#else
int err = pthread_mutex_lock(mx) ;
@@ -330,7 +351,8 @@ qpt_mutex_lock(qpt_mutex mx)
} ;
} ;
-/* Try to lock given mutex -- every time a winner if !qpthreads_enabled.
+/*------------------------------------------------------------------------------
+ * Try to lock given mutex -- every time a winner if !qpthreads_enabled.
*
* Returns: lock succeeded (1 => have locked, 0 => unable to lock).
*
@@ -354,23 +376,19 @@ qpt_mutex_trylock(qpt_mutex mx)
return 1 ;
} ;
-/* Unlock given mutex -- or do nothing if !qpthreads_enabled.
+/*------------------------------------------------------------------------------
+ * Unlock given mutex -- or do nothing if !qpthreads_enabled.
*
- * Unless both NCHECK_QPTHREADS and NDEBUG are defined, checks that the
- * return value is valid -- zabort_errno if it isn't.
+ * Checks that the return value is valid -- zabort_err if it isn't.
*/
Inline void
qpt_mutex_unlock(qpt_mutex mx)
{
if (qpthreads_enabled)
{
-#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS)
- pthread_mutex_unlock(mx) ;
-#else
int err = pthread_mutex_unlock(mx) ;
if (err != 0)
zabort_err("pthread_mutex_unlock failed", err) ;
-#endif
} ;
} ;
@@ -378,73 +396,111 @@ qpt_mutex_unlock(qpt_mutex mx)
* Condition variable inline functions
*/
-/* Wait for given condition variable -- do nothing if !qpthreads_enabled
+/*------------------------------------------------------------------------------
+ * Wait for given condition variable -- do nothing if !qpthreads_enabled
*
- * Unless both NCHECK_QPTHREADS and NDEBUG are defined, checks that the
- * return value is valid -- zabort_errno if it isn't.
+ * Checks that the return value is valid -- zabort_err if it isn't.
*/
Inline void
qpt_cond_wait(qpt_cond cv, qpt_mutex mx)
{
if (qpthreads_enabled)
{
-#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS)
- pthread_cond_wait(cv, mx) ;
-#else
int err = pthread_cond_wait(cv, mx) ;
if (err != 0)
zabort_err("pthread_cond_wait failed", err) ;
-#endif
} ;
} ;
-/* Signal given condition -- do nothing if !qpthreads_enabled
+/*------------------------------------------------------------------------------
+ * Signal given condition -- do nothing if !qpthreads_enabled
*
- * Unless both NCHECK_QPTHREADS and NDEBUG are defined, checks that the
- * return value is valid -- zabort_errno if it isn't.
+ * Checks that the return value is valid -- zabort_err if it isn't.
*/
Inline void
qpt_cond_signal(qpt_cond cv)
{
if (qpthreads_enabled)
{
-#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS)
- pthread_cond_signal(cv) ;
-#else
int err = pthread_cond_signal(cv) ;
if (err != 0)
zabort_err("pthread_cond_signal failed", err) ;
-#endif
} ;
} ;
-/* Broadcast given condition -- do nothing if !qpthreads_enabled
+/*------------------------------------------------------------------------------
+ * Broadcast given condition -- do nothing if !qpthreads_enabled
*
- * Unless both NCHECK_QPTHREADS and NDEBUG are defined, checks that the
- * return value is valid -- zabort_errno if it isn't.
+ * Checks that the return value is valid -- zabort_err if it isn't.
*/
Inline void
qpt_cond_broadcast(qpt_cond cv)
{
if (qpthreads_enabled)
{
-#if defined(NDEBUG) && defined(NDEBUG_QPTHREADS)
- pthread_cond_broadcast(cv) ;
-#else
int err = pthread_cond_broadcast(cv) ;
if (err != 0)
zabort_err("pthread_cond_broadcast failed", err) ;
-#endif
} ;
} ;
/*==============================================================================
* Signal Handling.
*/
-void /* FATAL error if !qpthreads_enabled */
+extern void /* sigprocmask() if !qpthreads_enabled */
qpt_thread_sigmask(int how, const sigset_t* set, sigset_t* oset) ;
-void /* FATAL error if !qpthreads_enabled */
+extern void /* FATAL error if !qpthreads_enabled */
qpt_thread_signal(qpt_thread_t thread, int signum) ;
+/*==============================================================================
+ * Thread Specific Data Handling.
+ *
+ * Note that if !qpthreads_enabled, this maintains the data value, without
+ * using any pthread primitives.
+ *
+ * Note also that this does not support the pthread value destructor -- because
+ * cannot support that for non qpthreads_enabled (straightforwardly, anyway).
+ */
+union qpt_data
+{
+ pthread_key_t key ; /* if qpthreads_enabled */
+ void* value ; /* otherwise */
+ const void* cvalue ;
+} ;
+
+typedef union qpt_data qpt_data_t[1] ;
+typedef union qpt_data* qpt_data ;
+
+extern void qpt_data_create(qpt_data data) ;
+extern void qpt_data_delete(qpt_data data) ;
+
+/*------------------------------------------------------------------------------
+ * Set thread specific data value -- value is void*
+ */
+Inline void
+qpt_data_set_value(qpt_data data, const void* value)
+{
+ if (qpthreads_enabled)
+ {
+ int err = pthread_setspecific(data->key, value) ;
+ if (err != 0)
+ zabort_err("pthread_setspecific failed", err) ;
+ }
+ else
+ data->cvalue = value ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get thread specific data value -- value is void*
+ */
+Inline void*
+qpt_data_get_value(qpt_data data)
+{
+ if (qpthreads_enabled)
+ return pthread_getspecific(data->key) ;
+ else
+ return data->value ;
+} ;
+
#endif /* _ZEBRA_QPTHREADS_H */
diff --git a/lib/qrand.c b/lib/qrand.c
new file mode 100644
index 00000000..ff52afd4
--- /dev/null
+++ b/lib/qrand.c
@@ -0,0 +1,48 @@
+/* Pseudo Random Sequence
+ * Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "qrand.h"
+
+/*==============================================================================
+ * Simple 32 bit random sequence.
+ */
+
+/*------------------------------------------------------------------------------
+ * Return next in the given sequence.
+ *
+ * Returns value in range 0..range-1, or 0..0x7FFF_FFFF if range == 0
+ *
+ * If range == 1, returns 0 every time !
+ */
+extern int
+qrand(qrand_seq seq, int range)
+{
+ uint64_t r ;
+
+ r = seq->last ^ 3141592653 ;
+ r = ((r * 2650845021) + 5) & 0xFFFFFFFF ; /* see Knuth */
+ seq->last = r ;
+
+ if (range == 0)
+ return r >> 1 ;
+ else
+ return (r % range) ;
+} ;
diff --git a/lib/qrand.h b/lib/qrand.h
new file mode 100644
index 00000000..e9a18436
--- /dev/null
+++ b/lib/qrand.h
@@ -0,0 +1,52 @@
+/* Pseudo Random Sequence -- Header
+ * Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_QRAND_H
+#define _ZEBRA_QRAND_H
+
+#include "misc.h"
+
+/*==============================================================================
+ * Simple 32 bit random sequence.
+ *
+ * Produces 32-bit signed integers in 0..0x7FFF_FFFF.
+ *
+ * Object of the exercise is to be able to produce repeatable sequence, so can
+ * debug !
+ */
+
+struct qrand_seq
+{
+ uint last ;
+} ;
+
+typedef struct qrand_seq* qrand_seq ;
+typedef struct qrand_seq qrand_seq_t[1] ;
+
+#define QRAND_SEQ_INIT(s) { { s } }
+
+/*==============================================================================
+ * Functions
+ */
+
+extern int qrand(qrand_seq seq, int range) ;
+
+#endif /* _ZEBRA_QRAND_H */
diff --git a/lib/qstring.c b/lib/qstring.c
index 19f74e8c..d00e3b70 100644
--- a/lib/qstring.c
+++ b/lib/qstring.c
@@ -28,18 +28,6 @@
*/
/*------------------------------------------------------------------------------
- * Create a new, empty qs
- */
-extern qstring
-qs_new(void)
-{
- /* zeroising sets a completely empty qstring -- see qs_init_new() */
- return XCALLOC(MTYPE_QSTRING, sizeof(qstring_t)) ;
-
- confirm(QSTRING_INIT_ALL_ZEROS) ;
-} ;
-
-/*------------------------------------------------------------------------------
* Create a new body or extend existing one to accommodate at least slen + 1.
*
* Sets size to multiple of 8 (minimum 16), with at least 9 bytes free beyond
@@ -62,20 +50,42 @@ qs_new_body(qstring qs, usize slen, bool keep_alias)
} ;
/*------------------------------------------------------------------------------
- * Create a new, empty qs with body
+ * Create a new, empty qs
+ *
+ * If non-zero slen is given, a body is allocated (size = slen + 1).
+ * If zero slen is given, no body is allocated.
+ *
+ * Sets 'len' = 'cp' = 0.
+ *
+ * Returns: address of qstring
*/
extern qstring
-qs_new_with_body(usize slen)
+qs_new(usize slen)
{
qstring qs ;
- qs = qs_new() ;
- qs_new_body(qs, slen, false) ;
+ /* zeroising sets a completely empty qstring -- see qs_init_new() */
+
+ qs = XCALLOC(MTYPE_QSTRING, sizeof(qstring_t)) ;
+
+ confirm(QSTRING_INIT_ALL_ZEROS) ;
+
+ if (slen != 0)
+ qs_new_body(qs, slen, false) ;
return qs ;
} ;
/*------------------------------------------------------------------------------
+ * Create a new, empty qs with body
+ */
+extern qstring
+qs_new_with_body(usize slen)
+{
+ return qs_new(slen | 1) ;
+} ;
+
+/*------------------------------------------------------------------------------
* Initialise qstring -- allocate if required.
*
* If non-zero slen is given, a body is allocated (size = slen + 1).
@@ -92,9 +102,9 @@ extern qstring
qs_init_new(qstring qs, usize slen)
{
if (qs == NULL)
- qs = qs_new() ;
- else
- memset(qs, 0, sizeof(qstring_t)) ;
+ return qs_new(slen) ;
+
+ memset(qs, 0, sizeof(qstring_t)) ;
confirm(QSTRING_INIT_ALL_ZEROS) ;
@@ -346,10 +356,24 @@ qs_set_fill_n(qstring qs, usize len, const char* src, usize flen)
* Sets 'len' to new length.
* Does not affect 'cp'.
*
+ * Will work even if the stuff being appended is somewhere in the body of the
+ * qstring !!
+ *
* Returns: address of the qstring (allocated if required).
*/
/*------------------------------------------------------------------------------
+ * Append given qstring to a qstring.
+ *
+ * See notes above.
+ */
+extern qstring
+qs_append(qstring qs, qstring src)
+{
+ return qs_append_str_n(qs, qs_body(src), qs_len(src)) ;
+} ;
+
+/*------------------------------------------------------------------------------
* Append given string to a qstring.
*
* Treats src == NULL as an empty string. Otherwise src must be a '\0'
@@ -359,9 +383,9 @@ qs_set_fill_n(qstring qs, usize len, const char* src, usize flen)
*
* See notes above.
*/
-extern qstring qs_append(qstring qs, const char* src)
+extern qstring qs_append_str(qstring qs, const char* src)
{
- return qs_append_n(qs, src, (src != NULL) ? strlen(src) : 0) ;
+ return qs_append_str_n(qs, src, (src != NULL) ? strlen(src) : 0) ;
} ;
/*------------------------------------------------------------------------------
@@ -373,29 +397,18 @@ extern qstring qs_append(qstring qs, const char* src)
* See notes above.
*/
extern qstring
-qs_append_n(qstring qs, const char* src, usize n)
+qs_append_str_n(qstring qs, const char* src, usize n)
{
qs = qs_extend(qs, n) ; /* allocate, copy any alias, extend body,
set new length, etc */
if (n != 0)
- memcpy(qs_ep_char_nn(qs) - n, src, n) ;
+ memmove(qs_ep_char_nn(qs) - n, src, n) ;
return qs ;
} ;
/*------------------------------------------------------------------------------
- * Append given qstring to a qstring.
- *
- * See notes above.
- */
-extern qstring
-qs_append_qs(qstring qs, qstring src)
-{
- return qs_append_n(qs, qs_body(src), qs_len(src)) ;
-} ;
-
-/*------------------------------------------------------------------------------
* Append given elstring to a qstring.
*
* See notes above.
@@ -403,12 +416,9 @@ qs_append_qs(qstring qs, qstring src)
extern qstring
qs_append_els(qstring qs, elstring src)
{
- return qs_append_n(qs, els_body(src), els_len(src)) ;
+ return qs_append_str_n(qs, els_body(src), els_len(src)) ;
} ;
-
-
-
/*==============================================================================
* Setting of alias.
*
@@ -461,7 +471,7 @@ extern qstring
qs_set_alias_n(qstring qs, const char* src, usize n)
{
if (qs == NULL)
- qs = qs_new() ;
+ qs = qs_new(0) ;
/* Make the alias. Note that any existing b_body and b_size are preserved,
* so that any current body can be reused at a later date.
@@ -596,7 +606,7 @@ qs_vprintf(qstring qs, const char *format, va_list args)
qqs = qs ; /* NULL => need to make qs */
if (qs == NULL)
- qqs = qs_new() ; /* Sets size, cp & len = 0 */
+ qqs = qs_new(0) ; /* Sets size, cp & len = 0 */
else
qs_clear(qqs) ; /* Sets cp & len = 0, discard any alias, but
keep existing body */
@@ -642,6 +652,9 @@ qs_vprintf(qstring qs, const char *format, va_list args)
*
* May increase or decrease 'len'. but does not affect 'cp'.
*
+ * If the given src is NULL, do not insert anything, just leave the space
+ * ready for it.
+ *
* Returns: number of bytes beyond 'cp' that now exist.
*
* qstring MUST NOT be NULL
@@ -691,7 +704,7 @@ qs_replace(qstring qs, usize r, const void* src, usize n)
if (after > 0) /* move the after part before inserting */
memmove(np + cp + n, ap + cp + r, after) ;
- if (n > 0) /* insert */
+ if ((n > 0) && (src != NULL)) /* insert */
memmove(np + cp, src, n) ;
/* Set new 'len' */
diff --git a/lib/qstring.h b/lib/qstring.h
index 48124a2d..b05c738b 100644
--- a/lib/qstring.h
+++ b/lib/qstring.h
@@ -66,6 +66,7 @@ struct qstring
usize b_size ;
bool alias ;
+ char* empty ;
} ;
typedef struct qstring qstring_t[1] ;
@@ -116,9 +117,13 @@ Inline void* qs_body(qstring qs) ;
Inline void* qs_body_nn(qstring qs) ;
Inline void qs_set_body_nn(qstring qs, const void* body) ;
+Inline usize qs_size(qstring qs) ;
+Inline usize qs_size_nn(qstring qs) ;
+
Inline ulen qs_len(qstring qs) ;
Inline ulen qs_len_nn(qstring qs) ;
Inline void qs_set_len_nn(qstring qs, ulen len) ;
+Inline void qs_set_strlen_nn(qstring qs) ;
Inline ulen qs_cp(qstring qs) ;
Inline ulen qs_cp_nn(qstring qs) ;
@@ -259,6 +264,20 @@ qs_set_body_nn(qstring qs, const void* body)
els_set_body_nn(qs->els, body) ;
} ;
+/* Size of qstring body -- zero if qstring is NULL, or is alias. */
+Inline usize
+qs_size(qstring qs)
+{
+ return (qs != NULL) ? qs_size_nn(qs) : 0 ;
+} ;
+
+/* Size of qstring (not NULL). */
+Inline usize
+qs_size_nn(qstring qs)
+{
+ return (qs->size) ;
+} ;
+
/*----------------------------------------------------------------------------*/
/* 'len' of qstring -- returns 0 if qstring is NULL */
@@ -282,6 +301,13 @@ qs_set_len_nn(qstring qs, ulen len)
els_set_len_nn(qs->els, len) ;
} ;
+/* set 'len' of qstring according to strlen(body) -- nothing NULL ! */
+Inline void
+qs_set_strlen_nn(qstring qs)
+{
+ els_set_len_nn(qs->els, strlen(qs_body_nn(qs))) ;
+} ;
+
/*----------------------------------------------------------------------------*/
/* 'cp' of qstring -- returns 0 if qstring is NULL */
@@ -378,10 +404,11 @@ Inline void qs_set_real_body_nn(qstring qs)
* Functions
*/
-extern qstring qs_new(void) ;
+extern qstring qs_new(usize slen) ;
extern qstring qs_new_with_body(usize slen) ;
extern qstring qs_init_new(qstring qs, usize len) ;
extern qstring qs_reset(qstring qs, free_keep_b free_structure) ;
+Inline qstring qs_free(qstring qs) ;
Inline char* qs_make_string(qstring qs) ;
Inline const char* qs_string(qstring qs) ;
@@ -400,9 +427,9 @@ extern qstring qs_set_fill(qstring qs, usize len, const char* src) ;
extern qstring qs_set_fill_n(qstring qs, usize len, const char* src,
usize flen) ;
-extern qstring qs_append(qstring qs, const char* src) ;
-extern qstring qs_append_n(qstring qs, const char* src, usize n) ;
-extern qstring qs_append_qs(qstring qs, qstring src) ;
+extern qstring qs_append_str(qstring qs, const char* src) ;
+extern qstring qs_append_str_n(qstring qs, const char* src, usize n) ;
+extern qstring qs_append(qstring qs, qstring src) ;
extern qstring qs_append_els(qstring qs, elstring src) ;
extern qstring qs_set_alias(qstring qs, const char* src) ;
@@ -437,6 +464,15 @@ Inline bool qs_substring(qstring a, qstring b) ;
*/
/*------------------------------------------------------------------------------
+ * Free given qstring
+ */
+Inline qstring
+qs_free(qstring qs)
+{
+ return qs_reset(qs, free_it) ;
+} ;
+
+/*------------------------------------------------------------------------------
* Return pointer to string value -- ensure not alias and '\0' terminated.
*
* If is alias, copies that before adding '\0' terminator.
@@ -444,31 +480,36 @@ Inline bool qs_substring(qstring a, qstring b) ;
* Sets the '\0' terminator at the 'len' position, extending string if that
* is required.
*
- * If qs == NULL or 'len' == 0 returns pointer to constant empty '\0'
- * terminated string (ie "").
+ * If qs == NULL returns pointer to empty '\0' terminated string.
*
* NB: The qstring should not be changed or reset until this pointer has been
* discarded !
+ *
+ * NB: The value returned is not "const" BUT caller is NOT entitled to change
+ * any part of the string -- CERTAINLY nothing from the '\0' onwards !
*/
Inline char*
qs_make_string(qstring qs)
{
+ static char empty_string[1] ;
+
usize len ;
char* p ;
if (qs == NULL)
{
len = 0 ;
- qs = qs_new_with_body(len) ;
+ p = empty_string ;
}
else
{
len = qs_len_nn(qs) ;
if (len >= qs->size) /* for alias, qs_size == 0 */
qs_make_to_size(qs, len, len) ; /* extend and/or copy any alias */
+
+ p = qs_char_nn(qs) ;
} ;
- p = qs_char_nn(qs) ;
*(p + len) = '\0' ;
return p ;
@@ -517,6 +558,9 @@ qs_string(qstring qs)
* If is an alias qstring, discard the alias.
*
* NB: does not create a qstring body if there isn't one.
+ *
+ * NB: does not change the qstring body if there is one. (Which is used in
+ * vio_lc_write_nb().
*/
Inline void
qs_clear(qstring qs)
diff --git a/lib/qtime.c b/lib/qtime.c
index 92e67e27..44a27b0f 100644
--- a/lib/qtime.c
+++ b/lib/qtime.c
@@ -240,3 +240,88 @@ qt_random(uint32_t seed)
*/
return x ^ ((y >> 16) & 0xFFFF) ^ ((y & 0xFFFF) << 16) ;
} ;
+
+/*==============================================================================
+ * Tracking the local timezone, so can:
+ *
+ * a) rapidly convert clock_gettime(CLOCK_REALTIME, ...) times
+ *
+ * b) do that thread-safe
+ *
+ * c) do that async-signal-safe
+ *
+ * Assumptions:
+ *
+ * a) that timezones are on at most 5 minute boundaries (15 probably!)
+ *
+ * b) that DST changes are at least 60 days apart
+ *
+ * c) that DST changes occur on times which are on 5 minute boundaries
+ * (60 probably -- but this means that the DST change is on a 5 minute
+ * bounderay in local and epoch times !)
+ *
+ * Sets up and maintains a table containing 8 entries:
+ *
+ * [-3] previous - 2
+ * [-2] previous - 1
+ * [-1] previous -- previous 0-7 days
+ * [ 0] current -- current 0-7 days
+ * [+1] next -- next 0-7 days
+ * [+2] next + 1
+ * [+3] next + 2
+ * [ X] sentinal
+ *
+ * These are configured before any threads or anything else very much runs, so
+ * they are essentially static. There is a "current index", which is set to
+ * '0' to start with.
+ *
+ * Each entry comprises:
+ *
+ * * start time -- entry is valid for epoch times >= start
+ * * end time -- entry is valid for epoch times < end
+ * * offset -- add to epoch time to get local
+ *
+ * When set up the current timezone initially starts on the nearest 5 minute
+ * boundary in the past, and covers up to 7 days into the future, unless the
+ * timezone changes in that time. The timezones on either side are set
+ * similarly.
+ *
+ * At most one of these timezones may be a short one -- so this covers at least
+ * 14 days into the past and 21 into the future, and as time advances, upto
+ * 21 days into the past and down to 14 days into the future.
+ *
+ * Maximum range is 56 days -- which is within the assumed 60 days between
+ * DST changes.
+ *
+ * When time advances past the current entry the next, next + 1 and + 2 cover
+ * at least 14 days (21 if none are short entries).
+ *
+ * Every now and then (say every 5 minutes) a background process can check the
+ * current time. If that is no longer in the current entry, needs to update
+ * the table. Assuming time is moving forward: sets sentinal to be the next
+ * 0-7 days following the current last entry, and updates the "current index".
+ *
+ * BIG ASSUMPTION: that the "current index" value is written atomically, wrt
+ * to threads as well as signals.
+ *
+ * It doesn't matter if a thread or signal action code picks
+ * up an out of date "current index" value, because all the
+ * entries for the old state are still valid.
+ *
+ * No entry is changed while it is covered by the current
+ * index -3..+3.
+ *
+ * This works fine, UNLESS the clock_gettime(CLOCK_REALTIME, ...) changes
+ * dramatically -- as might happen if the operator adjusts the system clock a
+ * long way !
+ *
+ * To cope with this, a spare set of 8 entries are kept, and a new table can
+ * be built (under mutex). The worst that happens is that threads may be
+ * blocked waiting for the table to be updated.
+ *
+ * If the table is found to be out of date when a signal is bringing the
+ * system down, then the times logged will just have to use either the first
+ * or the last entry, and have done with it.
+ */
+
+
diff --git a/lib/qtime.h b/lib/qtime.h
index 94d468a8..38d717c0 100644
--- a/lib/qtime.h
+++ b/lib/qtime.h
@@ -52,10 +52,10 @@ typedef qtime_t qtime_mono_t ; /* qtime_t value, monotonic time-base */
typedef qtime_t qtime_tod_t ; /* qtime_t value, timeofday time-base... */
/* ...just in case != CLOCK_REALTIME ! */
-/* A qtime_t second 123456789 -- nano-seconds */
-#define QTIME_SECOND 1000000000
-#define TIMESPEC_SECOND 1000000000
-#define TIMEVAL_SECOND 1000000
+/* A qtime_t second 123456789 -- nano-seconds */
+#define QTIME_SECOND ((qtime_t)1000000000)
+#define TIMESPEC_SECOND 1000000000
+#define TIMEVAL_SECOND 1000000
/* Macro to convert time in seconds to a qtime_t */
/* Note that the time to convert may be a float. */
diff --git a/lib/qtimers.h b/lib/qtimers.h
index 2af81819..16808f1b 100644
--- a/lib/qtimers.h
+++ b/lib/qtimers.h
@@ -35,16 +35,33 @@
* each with an action to be executed when the timer expires.
*/
-#ifdef QTIMERS_DEBUG /* Can be forced from outside */
-# if QTIMERS_DEBUG
-# define QTIMERS_DEBUG 1 /* Force 1 or 0 */
-#else
-# define QTIMERS_DEBUG 0
+/*------------------------------------------------------------------------------
+ * Sort out QTIMERS_DEBUG.
+ *
+ * Set to 1 if defined, but blank.
+ * Set to QDEBUG if not defined.
+ *
+ * Force to 0 if QTIMERS_NO_DEBUG is defined and not zero.
+ *
+ * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to:
+ *
+ * * can set QTIMERS_DEBUG == 0 to turn off debug
+ * * or set QTIMERS_DEBUG != 0 to turn on debug
+ * * or set QTIMERS_NO_DEBUG != 0 to force debug off
+ */
+
+#ifdef QTIMERS_DEBUG /* If defined, make it 1 or 0 */
+# if IS_BLANK_OPTION(QTIMERS_DEBUG)
+# undef QTIMERS_DEBUG
+# define QTIMERS_DEBUG 1
# endif
-#else
-# ifdef QDEBUG
-# define QTIMERS_DEBUG 1 /* Follow QDEBUG */
-#else
+#else /* If not defined, follow QDEBUG */
+# define QTIMERS_DEBUG QDEBUG
+#endif
+
+#ifdef QTIMERS_NO_DEBUG /* Override, if defined */
+# if IS_NOT_ZERO_OPTION(QTIMERS_NO_DEBUG)
+# undef QTIMERS_DEBUG
# define QTIMERS_DEBUG 0
# endif
#endif
diff --git a/lib/routemap.c b/lib/routemap.c
index d716e0b5..6de2098d 100644
--- a/lib/routemap.c
+++ b/lib/routemap.c
@@ -913,14 +913,15 @@ route_map_finish (void)
}
/* VTY related functions. */
-DEFUN (route_map,
- route_map_cmd,
- "route-map WORD (deny|permit) <1-4294967295>",
- "Create route-map or enter route-map command mode\n"
- "Route map tag\n"
- "Route map denies set operations\n"
- "Route map permits set operations\n"
- "Sequence to insert to/delete from existing route-map entry\n")
+DEFUN_ATTR (route_map,
+ route_map_cmd,
+ "route-map WORD (deny|permit) <1-4294967295>",
+ "Create route-map or enter route-map command mode\n"
+ "Route map tag\n"
+ "Route map denies set operations\n"
+ "Route map permits set operations\n"
+ "Sequence to insert to/delete from existing route-map entry\n",
+ CMD_ATTR_NODE + RMAP_NODE)
{
int permit;
unsigned long seq;
diff --git a/lib/sigevent.c b/lib/sigevent.c
index 18fcffb0..c7c1e134 100644
--- a/lib/sigevent.c
+++ b/lib/sigevent.c
@@ -19,11 +19,19 @@
* 02111-1307, USA.
*/
-#include <zebra.h>
-#include <sigevent.h>
-#include <log.h>
-
-#ifdef SA_SIGINFO
+#include "zebra.h"
+#include "misc.h"
+#include "sigevent.h"
+#include "log.h"
+#include "vty.h"
+#include "qpnexus.h"
+#include "qpthreads.h"
+
+#include <stdarg.h>
+
+/*------------------------------------------------------------------------------
+ * Want to get some context for core and exit handlers.
+ */
#ifdef HAVE_UCONTEXT_H
#ifdef GNU_LINUX
/* get REG_EIP from ucontext.h */
@@ -33,50 +41,407 @@
#endif /* GNU_LINUX */
#include <ucontext.h>
#endif /* HAVE_UCONTEXT_H */
-#endif /* SA_SIGINFO */
+/*------------------------------------------------------------------------------
+ * Use SA_SIGINFO type handlers throughout
+ */
+#ifndef SA_SIGINFO
+#error Sorry... require SA_SIGINFO
+#endif
+
+typedef void sig_handler(int signo, siginfo_t* info, void* context) ;
+
+/*==============================================================================
+ * Signal handling for Quagga.
+ *
+ * The objectives are:
+ *
+ * 1) to handle the abnormal terminations so that they are logged, and
+ * any available information logged with them.
+ *
+ * 2) to ignore a number of signals that have no significance
+ *
+ * 3) to catch some signals such that they are treated as events in either
+ * the qpthreads or the legacy threads worlds.
+ *
+ * For the qpthreads world, these are all handled in the main thread.
+ *
+ * These may not be any of the "hard" signals or any of the signals
+ * reserved by the library.
+ *
+ * 4) to catch some signals such that they cause an "interrupt" to, e.g.
+ * pselect(), but have no other effect.
+ *
+ * SIGUSR2 is reserved for this purpose for qpthreads world
+ * (aka SIG_INTERRUPT).
+ *
+ * Signal disposition is established early in the morning, and is static from
+ * then on.
+ */
+
+/*==============================================================================
+ * Signal Sets.
+ *
+ * The following signal sets are initialised by signal_init().
+ *
+ * * hard_signals -- signals that mean that the program has misbehaved, and
+ * should exit, now -- e.g. SIGSEGV or SIGILL.
+ *
+ * In the pthreaded world, these signals are handled
+ * (or at least, not blocked) by all threads, and it is
+ * expected that they will be given to the thread which
+ * has failed.
+ *
+ * These signals are not blocked.
+ *
+ * This includes SIGKILL and SIGSTOP, which cannot be
+ * blocked, caught or ignored.
+ *
+ * * core_signals -- signals that by default are terminate + core, so this
+ * more or less the hard_signals, except:
+ *
+ * * excludes SIGKILL and SIGSTOP
+ *
+ * * includes at least one (SIGQUIT) signal that is
+ * not a hard_signal.
+ *
+ * The default action is to catch these signals, log the
+ * event and then abort() -- having turned off the
+ * SIGABRT handler !
+ *
+ * * exit_signals -- signals that by default are terminate, so this
+ * includes, e.g., SIGTERM or SIGINT.
+ *
+ * The default action is to catch these signals, log the
+ * event and then exit().
+ *
+ * * ignore_signals -- signals which, by default, we wish to ignore, so are
+ * set to SIG_IGN.
+ *
+ * * qsig_signals -- signals which are caught, and are later signalled to
+ * quagga_sigevent_process().
+ *
+ * Note that multiple signals may be seen before
+ * quagga_sigevent_process() is run, but they will only
+ * generate one event.
+ *
+ * * qsig_interrupts -- signals which are caught, but otherwise ignored,
+ * so their only function is to interrupt -- in particular
+ * to interrupt pselect().
+ *
+ * * qsig_reserved -- signal which the library reserves for itself.
+ *
+ * In the pthreads world, all signals other than the hard_signals are blocked
+ * by all threads other than the main thread.
+ *
+ * Note that we leave SIGTRAP alone -- so do not disturb debuggger(s).
+ */
+static bool signals_initialised = false ;
+
+static sigset_t hard_signals[1] ;
+static sigset_t core_signals[1] ;
+static sigset_t exit_signals[1] ;
+static sigset_t ignore_signals[1] ;
+static sigset_t qsig_signals[1] ;
+static sigset_t qsig_interrupts[1] ;
+static sigset_t qsig_reserved[1] ;
+
+static void qsig_add(int signo, qsig_event* event) ;
+static int signal_set_set(sigset_t* set, sig_handler* handler) ;
+static int signal_set(int signo, sig_handler* handler) ;
+
+static void __attribute__ ((noreturn))
+ core_handler(int signo, siginfo_t *info, void *context) ;
+static void __attribute__ ((noreturn))
+ exit_handler(int signo, siginfo_t* info, void* context) ;
+static void
+ quagga_signal_handler(int signo, siginfo_t* info, void* context) ;
+static void
+ quagga_interrupt_handler(int signo, siginfo_t* info, void* context) ;
+
+/*------------------------------------------------------------------------------
+ * The following signals are not known to POSIX (2008) or are extensions.
+ */
+#ifndef SIGEMT
+# define SIGEMT 0
+#endif
+#ifndef SIGIO
+# define SIGIO 0
+#endif
+#ifndef SIGIOT
+# define SIGIOT 0
+#endif
+#ifndef SIGPOLL
+# define SIGPOLL 0
+#endif
+#ifndef SIGPROF
+# define SIGPROF 0
+#endif
+#ifndef SIGPWR
+# define SIGPWR 0
+#endif
+#ifndef SIGSTKFLT
+# define SIGSTKFLT 0
+#endif
+#ifndef SIGSYS
+# define SIGSYS 0
+#endif
+#ifndef SIGVTALRM
+# define SIGVTALRM 0
+#endif
+#ifndef SIGWINCH
+# define SIGWINCH 0
+#endif
+#ifndef SIGXRES
+# define SIGXRES 0
+#endif
+
+/*------------------------------------------------------------------------------
+ * The signal handling below assumes that no signal that we have any interest
+ * in will have a signal number greater than the following.
+ *
+ * All the signals we are interested in are initialised in signal_init, so
+ * asserts in the code below will trap, very early on, any signal with a
+ * larger number than this.
+ *
+ * This value is used to place a very outside limit on the signal numbers that
+ * will attempt to deal with.
+ */
+enum { SIG_MAX = 128, SIG_COUNT } ;
+
+/* The value is established at signal_init() time, as the maximum signal
+ * number to consider -- established by sigaddset() on an empty set.
+ */
+int sig_max = 0 ;
+
+/*------------------------------------------------------------------------------
+ * Quagga signals descriptor struct
+ *
+ * Maps real signal numbers to "qso" ordinals... A relatively limited number
+ * of signals need to be fed into the event system, this mechanism minimises
+ * the work required to discover which signal has gone off.
+ *
+ * The qsig actions are held in a small vector in the static sigmaster
+ * structure.
+ */
+enum
+{
+ sig_null = 0, /* no real signal is this */
+ sig_min = 1, /* first real signal is at least this */
+} ;
+typedef uchar sig_num_t ; /* signal number */
+CONFIRM(SIG_MAX <= UCHAR_MAX) ;
+
+enum
+{
+ qso_null = 0, /* no qsig uses this */
+ qso_min = 1, /* first qsig is this */
+ qso_max = 10, /* only a handful are really required */
-/* master signals descriptor struct */
-struct quagga_sigevent_master_t
+ qso_count, /* number qs ordinals */
+} ;
+typedef uchar qs_ord_t ; /* qs ordinal */
+
+/*------------------------------------------------------------------------------
+ * Static structure for all known qsig_signals.
+ *
+ * Note that the qsig_interrupts are not recorded here !
+ */
+struct quagga_sigevent_master
{
+ qs_ord_t qsigc ; /* number of signals known */
+ volatile sig_atomic_t caught[qso_count] ;
+
+ qsig_event* event[qso_count] ; /* how to deal with them */
+
+ qs_ord_t map[SIG_COUNT] ; /* real signal to qs ordinal */
+
+#ifdef SIGEVENT_SCHEDULE_THREAD
struct thread *t;
+#endif
+
+} qsig_master ;
+
+/*------------------------------------------------------------------------------
+ * Initialise signals.
+ *
+ * 1. construct the signal sets discussed above.
+ *
+ * 2. set default handlers for: core_signals -- core_handler()
+ * exit_signals -- exit_handler()
+ * ignore_signals -- SIG_IGN
+ *
+ * 3. set handlers for signals used by library
+ *
+ * 4. set handlers for signals used by the daemon.
+ *
+ * This is done once, and once only, early in the morning.
+ */
+extern void
+signal_init (struct thread_master *m, int sigc,
+ struct quagga_signal_t signals[])
+{
+ int i ;
+
+ /* Set sig_max by experiment */
+ {
+ sigset_t trial[1] ;
+ int signo ;
+
+ sigemptyset(trial) ;
+ for (signo = sig_min ; signo <= SIG_COUNT ; ++signo)
+ {
+ if (sigaddset(trial, signo) < 0)
+ break ;
+ } ;
+
+ --signo ; /* last acceptable signo */
+ if ((signo < sig_min) || (signo > SIG_MAX))
+ zabort("cannot establish reasonable 'sig_max'") ;
+
+ sig_max = signo ;
+ } ;
+
+ /* Construct the standard sets of signals. */
+ sigmakeset(hard_signals, SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGKILL,
+ SIGSEGV, SIGSTOP, SIGXCPU, SIGXFSZ,
+ SIGSYS,
+ SIGEMT, SIGIOT, SIGXRES,
+ -1) ;
+
+ sigcopyset(core_signals, hard_signals) ;
+ sigaddset(core_signals, SIGQUIT) ;
+ sigdelset(core_signals, SIGKILL) ;
+ sigdelset(core_signals, SIGSTOP) ;
+
+ sigmakeset(exit_signals, SIGALRM, SIGHUP, SIGINT, SIGTERM, SIGUSR1, SIGUSR2,
+ SIGIO, SIGPOLL, SIGPROF, SIGPWR, SIGSTKFLT,
+ SIGVTALRM,
+ -1) ;
+
+ sigmakeset(ignore_signals, SIGCHLD, SIGCONT, SIGPIPE, SIGTSTP, SIGTTIN,
+ SIGTTOU, SIGURG, SIGWINCH,
+ -1) ;
+
+ /* Initialise the sig_master, qsig_signals and qsig_interrupts.
+ *
+ * Reserve all the hard_signals.
+ */
+ memset(&qsig_master, 0, sizeof(qsig_master)) ;
+
+ sigemptyset(qsig_signals) ;
+ sigemptyset(qsig_interrupts) ;
+ sigcopyset(qsig_reserved, hard_signals) ;
+
+ /* The signals used by the library.
+ *
+ * Added to qsig_signals or qsig_interrupts and to qsig_reserved.
+ */
+ qsig_add(SIGCHLD, vty_sigchld) ;
+ qsig_add(SIG_INTERRUPT, NULL) ;
+
+ /* Now collect the daemon's own signals.
+ *
+ * Added to qsig_signals or qsig_interrupts and to qsig_reserved.
+ */
+ for (i = 0 ; i < sigc ; ++i)
+ qsig_add(signals[i].signal, signals[i].handler) ;
+
+ /* In case signals with different names are the same, and to make the
+ * required reservations, let qsig_reserved take precedence over exit_signals
+ * and those take precedence over ignore_signals.
+ */
+ sigsubsets(exit_signals, qsig_reserved) ;
+ sigsubsets(ignore_signals, qsig_reserved) ;
+ sigsubsets(ignore_signals, exit_signals) ;
+
+ /* Also, remove anything from core_signals which is now qsig_signals or
+ * qsig_interrupts (possibly SIGQUIT !).
+ */
+ sigsubsets(core_signals, qsig_signals) ;
+ sigsubsets(core_signals, qsig_interrupts) ;
+
+ /* Install handlers */
+ signal_set_set(core_signals, core_handler) ;
+ signal_set_set(exit_signals, exit_handler) ;
+ signal_set_set(ignore_signals, NULL) ;
+ signal_set_set(qsig_signals, quagga_signal_handler) ;
+ signal_set_set(qsig_interrupts, quagga_interrupt_handler) ;
+
+ /* If using a timer thread to scan for signal events, start that now.
+ */
+#ifdef SIGEVENT_SCHEDULE_THREAD
+ sig_master.t =
+ thread_add_timer (m, quagga_signal_timer, &sig_master,
+ QUAGGA_SIGNAL_TIMER_INTERVAL);
+#endif /* SIGEVENT_SCHEDULE_THREAD */
- struct quagga_signal_t *signals;
- int sigc;
+ /* Signals are now initialised */
+ signals_initialised = true ;
+} ;
- volatile sig_atomic_t caught;
-} sigmaster;
+/*------------------------------------------------------------------------------
+ * Get the hard_signals set
+ */
+extern const sigset_t*
+signal_get_hard_set(void)
+{
+ assert(signals_initialised) ;
+ return hard_signals ;
+} ;
-/* Generic signal handler
- * Schedules signal event thread
+/*------------------------------------------------------------------------------
+ * Add signal to those to be caught either for quagga_sigevent_process() or
+ * to then be dropped (so called interrupt signals).
+ *
+ * Checks that signal is not amongst the reserved signals.
+ *
+ * Add signal to the reserved signals.
*/
static void
-quagga_signal_handler (int signo)
+qsig_add(int signo, qsig_event* event)
{
- int i;
- struct quagga_signal_t *sig;
+ int s ;
+
+ s = sigismember(qsig_reserved, signo) ;
+ if ((s < 0) || (signo > sig_max))
+ zabort("invalid or unknown signal number") ;
+ if (s > 0)
+ zabort("signal is reserved (or already set)") ;
- for (i = 0; i < sigmaster.sigc; i++)
+ sigaddset(qsig_reserved, signo) ;
+
+ if (event == NULL)
+ sigaddset(qsig_interrupts, signo) ;
+ else
{
- sig = &(sigmaster.signals[i]);
+ sigaddset(qsig_signals, signo) ;
- if (sig->signal == signo)
- sig->caught = 1;
- }
+ if (qsig_master.qsigc >= qso_count)
+ zabort("too many signals to be caught") ;
- sigmaster.caught = 1;
-}
+ ++qsig_master.qsigc ;
-/* check if signals have been caught and run appropriate handlers
+ qsig_master.map[signo] = qsig_master.qsigc ;
+ qsig_master.event[qsig_master.qsigc] = event ;
+
+ } ;
+} ;
+
+/*==============================================================================
+ * The event level handling of qsig_signals, delivered via qsig_master.
+ */
+
+/*------------------------------------------------------------------------------
+ * check if signals have been caught and run respective event functions
*
* Returns: 0 => nothing to do
* -1 => failed
* > 0 => done this many signals
*/
-int
+extern int
quagga_sigevent_process (void)
{
- struct quagga_signal_t *sig;
int i;
int done ;
#ifdef SIGEVENT_BLOCK_SIGNALS
@@ -100,21 +465,18 @@ quagga_sigevent_process (void)
#endif /* SIGEVENT_BLOCK_SIGNALS */
done = 0 ;
- if (sigmaster.caught > 0)
+ if (qsig_master.caught[qso_null] != 0)
{
- sigmaster.caught = 0;
- /* must not read or set sigmaster.caught after here,
+ qsig_master.caught[qso_null] = 0;
+ /* must not read or set sigmaster.caught[0] after here,
* race condition with per-sig caught flags if one does
*/
-
- for (i = 0; i < sigmaster.sigc; i++)
+ for (i = 1 ; i <= qsig_master.qsigc ; i++)
{
- sig = &(sigmaster.signals[i]);
-
- if (sig->caught > 0)
+ if (qsig_master.caught[i] != 0)
{
- sig->caught = 0;
- sig->handler ();
+ qsig_master.caught[i] = 0;
+ (qsig_master.event[i])() ;
++done ;
}
}
@@ -128,6 +490,9 @@ quagga_sigevent_process (void)
return done ;
}
+/*------------------------------------------------------------------------------
+ * Optional timer thread to poll for signals
+ */
#ifdef SIGEVENT_SCHEDULE_THREAD
/* timer thread to check signals. Shouldnt be needed */
int
@@ -144,246 +509,395 @@ quagga_signal_timer (struct thread *t)
}
#endif /* SIGEVENT_SCHEDULE_THREAD */
-/* Initialization of signal handles. */
-/* Signal wrapper. */
-static int
-signal_set (int signo)
+/*==============================================================================
+ * The signal handlers.
+ */
+static void * program_counter(void *context) ;
+
+/*------------------------------------------------------------------------------
+ * Terminate + Core
+ */
+static void __attribute__ ((noreturn))
+core_handler(int signo, siginfo_t *info, void *context)
{
- int ret;
- struct sigaction sig;
- struct sigaction osig;
-
- sig.sa_handler = &quagga_signal_handler;
- sigfillset (&sig.sa_mask);
- sig.sa_flags = 0;
- if (signo == SIGALRM) {
-#ifdef SA_INTERRUPT
- sig.sa_flags |= SA_INTERRUPT; /* SunOS */
-#endif
- } else {
-#ifdef SA_RESTART
- sig.sa_flags |= SA_RESTART;
-#endif /* SA_RESTART */
- }
-
- ret = sigaction (signo, &sig, &osig);
- if (ret < 0)
- return ret;
- else
- return 0;
+ zlog_signal(signo, "aborting...", info, program_counter(context)) ;
+ zabort_abort();
}
-#ifdef SA_SIGINFO
+/*------------------------------------------------------------------------------
+ * Terminate
+ */
+static void __attribute__ ((noreturn))
+exit_handler(int signo, siginfo_t* info, void* context)
+{
+ zlog_signal(signo, "exiting...", info, program_counter(context));
+ _exit(128+signo);
+}
+
+/*------------------------------------------------------------------------------
+ * Generic signal handler -- captures signals in the sig_master caught vector.
+ *
+ * quagga_sigevent_process() deals with the caught signals.
+ */
+static void
+quagga_signal_handler(int signo, siginfo_t* info, void* context)
+{
+ qs_ord_t qso ;
+
+ if (sigismember(qsig_signals, signo) <= 0)
+ {
+ zlog_signal(signo, "quagga_signal_handler: unknown or invalid signal",
+ info, program_counter(context)) ;
+ zabort_abort();
+ } ;
+
+ qso = qsig_master.map[signo] ;
+
+ /* Set individual caught flag before composite.
+ *
+ * This works if quagga_signal_handler() and quagga_sigevent_process()
+ * were to run at the same time -- unlikely though that may be.
+ *
+ * If quagga_sigevent_process() sees individual flag before the composite,
+ * that's fine -- worst that happens is will run through a further time
+ * and not find anything.
+ *
+ * If flags were set in the opposite order, could clear the composite
+ * and miss the individual, so lose the signal till the next time the
+ * composite is set.
+ */
+ qsig_master.caught[qso] = 1 ;
+ qsig_master.caught[qso_null] = 1 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Generic signal handler -- where signal is to be caught, and immediately
+ * dropped.
+ */
+static void
+quagga_interrupt_handler(int signo, siginfo_t* info, void* context)
+{
+ if (sigismember(qsig_interrupts, signo) <= 0)
+ {
+ zlog_signal(signo, "quagga_interrupt_handler: unknown or invalid signal",
+ info, program_counter(context)) ;
+ zabort_abort();
+ } ;
+} ;
-/* XXX This function should be enhanced to support more platforms
- (it currently works only on Linux/x86). */
+/*------------------------------------------------------------------------------
+ * Extract program counter from context.
+ *
+ * XXX This function should be enhanced to support more platforms
+ * (it currently works only on Linux/x86).
+ */
static void *
program_counter(void *context)
{
#ifdef HAVE_UCONTEXT_H
-#ifdef GNU_LINUX
-#ifdef REG_EIP
+# ifdef GNU_LINUX
+# ifdef REG_EIP
if (context)
return (void *)(((ucontext_t *)context)->uc_mcontext.gregs[REG_EIP]);
-#endif /* REG_EIP */
-#endif /* GNU_LINUX */
+# endif /* REG_EIP */
+# ifdef REG_RIP
+ if (context)
+ return (void *)(((ucontext_t *)context)->uc_mcontext.gregs[REG_RIP]);
+# endif /* REG_RIP */
+# endif /* GNU_LINUX */
#endif /* HAVE_UCONTEXT_H */
return NULL;
-}
+} ;
-#endif /* SA_SIGINFO */
+/*==============================================================================
+ * Signal clearing for abort() and fork()/vfork().
+ */
-static void __attribute__ ((noreturn))
-exit_handler(int signo
-#ifdef SA_SIGINFO
- , siginfo_t *siginfo, void *context
-#endif
- )
+/*------------------------------------------------------------------------------
+ * Set default sigaction for given signo
+ */
+static int
+sigaction_set_default(int signo)
{
- zlog_signal(signo, "exiting..."
-#ifdef SA_SIGINFO
- , siginfo, program_counter(context)
-#endif
- );
- _exit(128+signo);
-}
+ struct sigaction act[1] ;
-static void __attribute__ ((noreturn))
-core_handler(int signo
-#ifdef SA_SIGINFO
- , siginfo_t *siginfo, void *context
-#endif
- )
+ memset(act, 0, sizeof(act)) ; /* inter alia, clear sa_flags */
+ act->sa_handler = SIG_DFL ; /* return to default state */
+ sigemptyset(&act->sa_mask) ; /* no extra masking */
+
+ return sigaction(signo, act, NULL) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * When finally aborting, need to turn off the handling of SIGABRT, and need
+ * to make sure that the signal is not blocked.
+ */
+extern void
+quagga_sigabrt_no_trap(void)
{
- zlog_signal(signo, "aborting..."
-#ifdef SA_SIGINFO
- , siginfo, program_counter(context)
-#endif
- );
- zabort_abort();
-}
+ sigset_t set[1] ;
+
+ sigaction_set_default(SIGABRT) ;
+
+ sigemptyset(set) ;
+ sigaddset(set, SIGABRT) ;
+ qpt_thread_sigmask(SIG_UNBLOCK, set, NULL) ;
+ /* sigprocmask() if !qpthreads_enabled */
+} ;
-/* For the signals known to Quagga, and which are in their default state,
- * set a Quagga default handler.
+/*------------------------------------------------------------------------------
+ * Having forked, make sure that all signals are in default state and that
+ * no signals are blocked.
+ *
+ * Expects not to fail.
*/
-static void
-trap_default_signals(void)
+extern void
+quagga_signal_reset(void)
{
- static const int core_signals[] = {
- SIGQUIT,
- SIGILL,
- SIGABRT,
-#ifdef SIGEMT
- SIGEMT,
-#endif
-#ifdef SIGIOT
- SIGIOT,
-#endif
- SIGFPE,
- SIGBUS,
- SIGSEGV,
-#ifdef SIGSYS
- SIGSYS,
-#endif
-#ifdef SIGXCPU
- SIGXCPU,
-#endif
-#ifdef SIGXFSZ
- SIGXFSZ,
-#endif
- };
-
- static const int exit_signals[] = {
- SIGHUP,
- SIGINT,
- SIGALRM,
- SIGTERM,
- SIGUSR1,
- SIGUSR2,
-#ifdef SIGPOLL
- SIGPOLL,
-#endif
-#ifdef SIGVTALRM
- SIGVTALRM,
-#endif
-#ifdef SIGSTKFLT
- SIGSTKFLT,
-#endif
- };
-
- static const int ignore_signals[] = {
- SIGPIPE,
- };
-
- static const struct {
- const int *sigs;
- u_int nsigs;
- void (*handler)(int signo
-#ifdef SA_SIGINFO
- , siginfo_t *info, void *context
-#endif
- );
- } sigmap[] = {
- { core_signals, sizeof(core_signals)/sizeof(core_signals[0]), core_handler},
- { exit_signals, sizeof(exit_signals)/sizeof(exit_signals[0]), exit_handler},
- { ignore_signals, sizeof(ignore_signals)/sizeof(ignore_signals[0]), NULL},
- };
- u_int i;
-
- for (i = 0; i < sizeof(sigmap)/sizeof(sigmap[0]); i++)
+ sigset_t set[1] ;
+ int signo ;
+
+ /* Before changing the handling of any signals, mask everything and
+ * clear out any pending signals.
+ */
+ sigfillset(set) ;
+ sigprocmask(SIG_SETMASK, set, NULL) ;
+
+ while (1)
{
- u_int j;
+ sigpending(set) ;
+ if (sighasmember(set) == 0)
+ break ;
+ sigwait(set, &signo) ;
+ } ;
+
+ /* Set all signals to default handler. */
+ for (signo = sig_min ; signo <= sig_max ; ++signo)
+ {
+ if ((signo == SIGKILL) || (signo == SIGSTOP))
+ continue ;
- for (j = 0; j < sigmap[i].nsigs; j++)
- {
- struct sigaction oact;
- if (sigaction(sigmap[i].sigs[j], NULL, &oact) < 0)
- zlog_warn("Unable to get signal handler for signal %d: %s",
- sigmap[i].sigs[j], errtoa(errno, 0).str);
- else {
-#ifdef SA_SIGINFO
- if (oact.sa_flags & SA_SIGINFO)
- continue ; /* Don't set again */
+ sigaction_set_default(signo) ;
+ } ;
+
+ /* Unmask everything */
+ sigemptyset(set) ;
+ sigprocmask(SIG_SETMASK, set, NULL) ;
+} ;
+
+/*==============================================================================
+ * Functions to install signal handlers.
+ */
+
+/*------------------------------------------------------------------------------
+ * Set given handler for given set of signals. NULL handler => SIG_IGN.
+ *
+ * Returns: < 0 => failed -- value is - failing signo !
+ */
+static int
+signal_set_set(sigset_t* set, sig_handler* handler)
+{
+ int signo ;
+
+ signo = 0 ;
+ for (signo = sig_min ; signo <= sig_max ; ++signo)
+ {
+ int s ;
+ s = sigismember(set, signo) ;
+ if (s < 0)
+ break ;
+ if (s > 0)
+ if (signal_set(signo, handler) < 0)
+ return -signo ;
+ } ;
+
+ return 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set given handler for given signal. NULL handler => SIG_IGN.
+ *
+ * Returns: < 0 => failed
+ */
+#ifndef SA_INTERRUPT
+# define SA_INTERRUPT 0
#endif
- if (oact.sa_handler != SIG_DFL)
- continue ; /* Don't set again */
- }
- if ( (sigaction(sigmap[i].sigs[j], NULL, &oact) == 0) &&
- (oact.sa_handler == SIG_DFL) )
- {
- struct sigaction act;
- sigfillset (&act.sa_mask);
- if (sigmap[i].handler == NULL)
- {
- act.sa_handler = SIG_IGN;
- act.sa_flags = 0;
- }
- else
- {
-#ifdef SA_SIGINFO
- /* Request extra arguments to signal handler. */
- act.sa_sigaction = sigmap[i].handler;
- act.sa_flags = SA_SIGINFO;
-#else
- act.sa_handler = sigmap[i].handler;
- act.sa_flags = 0;
+#ifndef SA_RESTART
+# define SA_RESTART 0
#endif
- }
- if (sigaction(sigmap[i].sigs[j], &act, NULL) < 0)
- zlog_warn("Unable to set signal handler for signal %d: %s",
- sigmap[i].sigs[j], errtoa(errno, 0).str);
- }
- }
+static int
+signal_set(int signo, sig_handler* handler)
+{
+ struct sigaction act[1] ;
+
+ if (handler == NULL)
+ {
+ act->sa_handler = SIG_IGN ;
+ act->sa_flags = 0 ;
}
-}
+ else
+ {
+ act->sa_sigaction = handler ;
+ act->sa_flags = SA_SIGINFO ;
+ } ;
-void
-signal_init (struct thread_master *m, int sigc,
- struct quagga_signal_t signals[])
+ sigfillset (&act->sa_mask) ; /* mask everything */
+
+ if (signo == SIGALRM)
+ act->sa_flags |= SA_INTERRUPT ; /* want SIGALRM to interrupt */
+ else
+ act->sa_flags |= SA_RESTART ; /* all others want restart */
+
+ act->sa_flags |= SA_NOCLDSTOP ;
+
+ return sigaction (signo, act, NULL) ;
+} ;
+
+/*==============================================================================
+ * Additional signal set support.
+ */
+
+/*------------------------------------------------------------------------------
+ * Make a signal set.
+ *
+ * Takes variable list of signal number arguments:
+ *
+ * * ignores zeros
+ *
+ * * stops on first value < 0
+ */
+extern void
+sigmakeset(sigset_t* set, ...)
{
+ va_list va ;
+ int signo ;
- int i = 0;
- struct quagga_signal_t *sig;
+ va_start(va, set) ;
- /* First establish some default handlers that can be overridden by
- the application. */
- trap_default_signals();
+ sigemptyset(set) ;
+ while ((signo = va_arg(va, int)) >= 0)
+ {
+ if (signo != 0)
+ if (sigaddset(set, signo) < 0)
+ zabort("invalid signal number") ;
+ } ;
+
+ va_end(va) ;
+} ;
- while (i < sigc)
+/*------------------------------------------------------------------------------
+ * Copy a signal set.
+ */
+extern void
+sigcopyset(sigset_t* dst, const sigset_t* src)
+{
+ *dst = *src ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Add signal set 'b' into set 'a'.
+ */
+extern void
+sigaddsets(sigset_t* a, const sigset_t* b)
+{
+ int signo ;
+
+ for (signo = sig_min ; signo < SIG_MAX ; ++signo)
{
- sig = &signals[i];
- if ( signal_set (sig->signal) < 0 )
- exit (-1);
- i++;
- }
+ int s ;
+ s = sigismember(b, signo) ;
+ if (s < 0)
+ break ;
+ if (s > 0)
+ sigaddset(a, signo) ;
+ } ;
+} ;
- sigmaster.sigc = sigc;
- sigmaster.signals = signals;
+/*------------------------------------------------------------------------------
+ * Subtract signal set 'b' from set 'a'.
+ */
+extern void
+sigsubsets(sigset_t* a, const sigset_t* b)
+{
+ int signo ;
-#ifdef SIGEVENT_SCHEDULE_THREAD
- sigmaster.t =
- thread_add_timer (m, quagga_signal_timer, &sigmaster,
- QUAGGA_SIGNAL_TIMER_INTERVAL);
-#endif /* SIGEVENT_SCHEDULE_THREAD */
-}
+ for (signo = sig_min ; signo < SIG_MAX ; ++signo)
+ {
+ int s ;
+ s = sigismember(b, signo) ;
+ if (s < 0)
+ break ;
+ if (s > 0)
+ sigdelset(a, signo) ;
+ } ;
+} ;
-/* turn off trap for SIGABRT ! */
-extern void quagga_sigabrt_no_trap(void)
+/*------------------------------------------------------------------------------
+ * Make set 'a' be the inverse of set 'b'
+ */
+extern void
+siginvset(sigset_t* a, const sigset_t* b)
{
- struct sigaction new_act ;
- sigset_t set ;
+ int signo ;
- sigfillset(&set) ;
+ sigfillset(a) ;
- new_act.sa_handler = SIG_DFL ;
- new_act.sa_mask = set ;
- new_act.sa_flags = 0 ;
- sigaction(SIGABRT, &new_act, NULL) ;
+ for (signo = sig_min ; signo < SIG_MAX ; ++signo)
+ {
+ int s ;
+ s = sigismember(b, signo) ;
+ if (s < 0)
+ break ;
+ if (s > 0)
+ sigdelset(a, signo) ;
+ } ;
+} ;
- sigemptyset(&set) ;
- sigaddset(&set, SIGABRT) ;
- sigprocmask(SIG_UNBLOCK, &set, NULL) ;
+/*------------------------------------------------------------------------------
+ * See if there is any intersection between two sets.
+ *
+ * Returns: first signo of intersection -- may be more !
+ * 0 <=> none
+ */
+extern int
+sigincommon(const sigset_t* a, const sigset_t* b)
+{
+ int signo ;
+ for (signo = sig_min ; signo < SIG_MAX ; ++signo)
+ {
+ int s ;
+ s = sigismember(a, signo) ;
+ if (s < 0)
+ return 0 ;
+ if ((s > 0) && (sigismember(b, signo) > 0))
+ return signo ;
+ } ;
+
+ return 0 ;
} ;
+/*------------------------------------------------------------------------------
+ * See if there is anything in the given set.
+ *
+ * Returns: first signo found -- may be more !
+ * 0 <=> none
+ */
+extern int
+sighasmember(const sigset_t* a)
+{
+ int signo ;
+
+ for (signo = sig_min ; signo < SIG_MAX ; ++signo)
+ {
+ int s ;
+ s = sigismember(a, signo) ;
+ if (s < 0)
+ return 0 ;
+ if (s > 0)
+ return signo ;
+ } ;
+
+ return 0 ;
+} ;
diff --git a/lib/sigevent.h b/lib/sigevent.h
index 57486bc2..bc4ef945 100644
--- a/lib/sigevent.h
+++ b/lib/sigevent.h
@@ -25,32 +25,34 @@
#define _QUAGGA_SIGNAL_H
#include <thread.h>
+#include <signal.h>
#define QUAGGA_SIGNAL_TIMER_INTERVAL 2L
#define Q_SIGC(sig) (sizeof(sig)/sizeof(sig[0]))
+typedef void qsig_event(void) ;
+
struct quagga_signal_t
{
- int signal; /* signal number */
- void (*handler) (void); /* handler to call */
-
- volatile sig_atomic_t caught; /* private member */
-};
-
-/* initialise sigevent system
- * takes:
- * - pointer to valid struct thread_master
- * - number of elements in passed in signals array
- * - array of quagga_signal_t's describing signals to handle
- * and handlers to use for each signal
- */
+ int signal ; /* signal number */
+ qsig_event* handler ; /* event function */
+} ;
+
extern void signal_init (struct thread_master *m, int sigc,
- struct quagga_signal_t *signals);
+ struct quagga_signal_t *signals);
-/* check whether there are signals to handle, process any found */
extern int quagga_sigevent_process (void);
-/* turn off trap for SIGABRT ! */
+extern const sigset_t* signal_get_hard_set(void) ;
extern void quagga_sigabrt_no_trap(void) ;
+extern void quagga_signal_reset(void) ;
+
+extern void sigmakeset(sigset_t* set, ...) ;
+extern void sigcopyset(sigset_t* dst, const sigset_t* src) ;
+extern void sigaddsets(sigset_t* a, const sigset_t* b) ;
+extern void sigsubsets(sigset_t* a, const sigset_t* b) ;
+extern void siginvset(sigset_t* a, const sigset_t* b) ;
+extern int sigincommon(const sigset_t* a, const sigset_t* b) ;
+extern int sighasmember(const sigset_t* a) ;
#endif /* _QUAGGA_SIGNAL_H */
diff --git a/lib/thread.c b/lib/thread.c
index 7a9a3a60..cf2ec425 100644
--- a/lib/thread.c
+++ b/lib/thread.c
@@ -44,8 +44,8 @@ static unsigned short timers_inited;
/* cpu stats needs to be qpthread safe. */
static qpt_mutex_t thread_mutex;
-#define LOCK qpt_mutex_lock(&thread_mutex);
-#define UNLOCK qpt_mutex_unlock(&thread_mutex);
+#define LOCK qpt_mutex_lock(thread_mutex);
+#define UNLOCK qpt_mutex_unlock(thread_mutex);
static struct hash *cpu_record = NULL;
/* Pointer to qtimer pile to be used, if any */
@@ -468,7 +468,7 @@ thread_master_create ()
{
#ifdef USE_MQUEUE
sigfillset (&newmask);
- sigdelset (&newmask, SIGMQUEUE);
+ sigdelset (&newmask, SIG_INTERRUPT);
#endif
if (cpu_record == NULL)
@@ -1399,14 +1399,14 @@ funcname_thread_execute (struct thread_master *m,
void
thread_init_r (void)
{
- qpt_mutex_init(&thread_mutex, qpt_mutex_quagga);
+ qpt_mutex_init(thread_mutex, qpt_mutex_quagga);
}
/* Finished with module */
void
thread_finish (void)
{
- qpt_mutex_destroy(&thread_mutex, 0);
+ qpt_mutex_destroy(thread_mutex, 0);
}
#undef USE_MQUEUE
diff --git a/lib/vio_fifo.c b/lib/vio_fifo.c
index e6365861..46a087f7 100644
--- a/lib/vio_fifo.c
+++ b/lib/vio_fifo.c
@@ -957,8 +957,11 @@ vio_fifo_vprintf(vio_fifo vff, const char *format, va_list args)
/*------------------------------------------------------------------------------
* Read part of file into FIFO -- assuming non-blocking file
*
- * Will read up to the end of the lump which meets, or exceeds the number of
- * bytes requested, or until would block.
+ * Will read up to the end of the current lump, then will read as may whole
+ * lumps as are requested -- request of 0 reads up to the end of the current
+ * lump (at least 1 byte). Will stop if would block.
+ *
+ * Except where blocking intervenes, this reads in units of the lump size.
*
* Returns: 0..n -- number of bytes read
* -1 => failed -- see errno
@@ -969,7 +972,7 @@ vio_fifo_vprintf(vio_fifo vff, const char *format, va_list args)
* something, error or EOF.
*/
extern int
-vio_fifo_read_nb(vio_fifo vff, int fd, size_t request)
+vio_fifo_read_nb(vio_fifo vff, int fd, uint request)
{
size_t total ;
@@ -980,7 +983,12 @@ vio_fifo_read_nb(vio_fifo vff, int fd, size_t request)
int got ;
if (vff->put_ptr >= vff->put_end)
- vio_fifo_lump_new(vff, 0) ; /* traps put_ptr > put_end */
+ {
+ vio_fifo_lump_new(vff, 0) ; /* traps put_ptr > put_end */
+
+ if (request > 0)
+ --request ;
+ } ;
got = read_nb(fd, vff->put_ptr, vff->put_end - vff->put_ptr) ;
@@ -995,7 +1003,7 @@ vio_fifo_read_nb(vio_fifo vff, int fd, size_t request)
vff->put_ptr += got ;
total += got ;
- } while (total < request) ;
+ } while (request > 0) ;
return total ;
} ;
@@ -1024,9 +1032,6 @@ vio_fifo_copy(vio_fifo dst, vio_fifo src)
vio_fifo_lump src_lump ;
char* src_ptr ;
- if (src->get_ptr >= src->get_end)
- vio_fifo_sync_get(src) ;
-
src_lump = src->get_lump ;
src_ptr = src->get_ptr ;
@@ -1346,7 +1351,7 @@ vio_fifo_step_get(vio_fifo vff, size_t* p_have, size_t step)
* Write contents of FIFO -- assuming non-blocking file
*
* Will write all of FIFO up to end mark or put_ptr, or upto but excluding
- * the last lump.
+ * the end_lump.
*
* Returns: > 0 => blocked
* 0 => all gone (up to last lump if !all)
@@ -1492,9 +1497,10 @@ vio_fifo_clear_hold_mark(vio_fifo vff)
} ;
/*------------------------------------------------------------------------------
- * If there is an hold_mark, reset get_ptr *back* to it.
+ * If there is an hold_mark, reset get_ptr *back* to it, and leave the mark
+ * set or clear.
*
- * Leave hold mark set or clear.
+ * If there is no hold mark, set one at the current position, if required.
*/
extern void
vio_fifo_back_to_hold_mark(vio_fifo vff, bool mark)
@@ -1516,7 +1522,67 @@ vio_fifo_back_to_hold_mark(vio_fifo vff, bool mark)
VIO_FIFO_DEBUG_VERIFY(vff) ;
}
else if (mark)
- vio_fifo_set_end_mark(vff) ;
+ vio_fifo_set_hold_mark(vff) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the get_ptr to the hold_mark plus the given offset.
+ *
+ * If the offset is zero, and there was no hold mark, set one at the current
+ * get_ptr.
+ *
+ * If the offset is not zero, it is a mistake to do this if there is no
+ * hold_mark, or if the offset would take the get_ptr beyond the end_mark
+ * (if any) or the put_ptr.
+ */
+extern void
+vio_fifo_set_get_wrt_hold(vio_fifo vff, size_t hold_offset)
+{
+ if (hold_offset == 0)
+ {
+ /* Offset of zero can be set under all conditions */
+ vio_fifo_back_to_hold_mark(vff, true) ;
+ }
+ else
+ {
+ vio_fifo_lump lump ;
+ char* ptr ;
+
+ /* There must be a hold_mark and must have something held */
+ assert(vff->hold_mark && vff->set) ;
+
+ lump = ddl_head(vff->base) ;
+ ptr = vff->hold_ptr ;
+
+ while (1)
+ {
+ size_t have ;
+
+ if (lump == vff->end_lump)
+ have = (vff->end_mark ? vff->end_end : vff->put_ptr) - ptr ;
+ else
+ have = lump->end - ptr ;
+
+ if (have <= hold_offset)
+ break ;
+
+ hold_offset -= have ;
+
+ assert(lump != vff->end_lump) ;
+
+ lump = ddl_next(lump, list) ;
+ ptr = lump->data ;
+ } ;
+
+ /* Note that may be about to set the get_ptr to the end of the
+ * current lump, which will be correct if that is the end of the
+ * fifo, but in any case is dealt with by vio_fifo_sync_get().
+ */
+ vio_fifo_set_get_ptr(vff, ptr + hold_offset, lump) ;
+ vio_fifo_sync_get(vff) ;
+ } ;
+
+ VIO_FIFO_DEBUG_VERIFY(vff) ;
} ;
/*==============================================================================
diff --git a/lib/vio_fifo.h b/lib/vio_fifo.h
index 1b7b05d3..97575f51 100644
--- a/lib/vio_fifo.h
+++ b/lib/vio_fifo.h
@@ -32,16 +32,33 @@
* VTY I/O FIFO -- buffering of arbitrary amounts of I/O.
*/
-#ifdef VIO_FIFO_DEBUG /* Can be forced from outside */
-# if VIO_FIFO_DEBUG
-# define VIO_FIFO_DEBUG 1 /* Force 1 or 0 */
-#else
-# define VIO_FIFO_DEBUG 0
+/*------------------------------------------------------------------------------
+ * Sort out VIO_FIFO_DEBUG.
+ *
+ * Set to 1 if defined, but blank.
+ * Set to QDEBUG if not defined.
+ *
+ * Force to 0 if VIO_FIFO_NO_DEBUG is defined and not zero.
+ *
+ * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to:
+ *
+ * * can set VIO_FIFO_DEBUG == 0 to turn off debug
+ * * or set VIO_FIFO_DEBUG != 0 to turn on debug
+ * * or set VIO_FIFO_NO_DEBUG != 0 to force debug off
+ */
+
+#ifdef VIO_FIFO_DEBUG /* If defined, make it 1 or 0 */
+# if IS_BLANK_OPTION(VIO_FIFO_DEBUG)
+# undef VIO_FIFO_DEBUG
+# define VIO_FIFO_DEBUG 1
# endif
-#else
-# ifdef QDEBUG
-# define VIO_FIFO_DEBUG 1 /* Follow QDEBUG */
-#else
+#else /* If not defined, follow QDEBUG */
+# define VIO_FIFO_DEBUG QDEBUG
+#endif
+
+#ifdef VIO_FIFO_NO_DEBUG /* Override, if defined */
+# if IS_NOT_ZERO_OPTION(VIO_FIFO_NO_DEBUG)
+# undef VIO_FIFO_DEBUG
# define VIO_FIFO_DEBUG 0
# endif
#endif
@@ -116,6 +133,7 @@ extern vio_fifo vio_fifo_reset(vio_fifo vff, free_keep_b free_structure) ;
extern void vio_fifo_clear(vio_fifo vff, bool clear_marks) ;
Inline bool vio_fifo_empty(vio_fifo vff) ;
+Inline bool vio_fifo_tail_empty(vio_fifo vff) ;
extern size_t vio_fifo_room(vio_fifo vff) ;
extern void vio_fifo_put_bytes(vio_fifo vff, const char* src, size_t n) ;
@@ -124,7 +142,7 @@ Inline void vio_fifo_put_byte(vio_fifo vff, char b) ;
extern int vio_fifo_printf(vio_fifo vff, const char* format, ...)
PRINTF_ATTRIBUTE(2, 3) ;
extern int vio_fifo_vprintf(vio_fifo vff, const char *format, va_list args) ;
-extern int vio_fifo_read_nb(vio_fifo vff, int fd, size_t request) ;
+extern int vio_fifo_read_nb(vio_fifo vff, int fd, uint request) ;
extern size_t vio_fifo_get_bytes(vio_fifo vff, void* dst, size_t n) ;
Inline int vio_fifo_get_byte(vio_fifo vff) ;
@@ -147,6 +165,7 @@ extern void vio_fifo_back_to_end_mark(vio_fifo vff, bool keep) ;
extern void vio_fifo_set_hold_mark(vio_fifo vff) ;
extern void vio_fifo_clear_hold_mark(vio_fifo vff) ;
extern void vio_fifo_back_to_hold_mark(vio_fifo vff, bool keep) ;
+extern void vio_fifo_set_get_wrt_hold(vio_fifo vff, size_t hold_offset) ;
/*==============================================================================
* Debug -- verification function
@@ -154,11 +173,7 @@ extern void vio_fifo_back_to_hold_mark(vio_fifo vff, bool keep) ;
Private void vio_fifo_verify(vio_fifo vff) ;
-#if VIO_FIFO_DEBUG
-# define VIO_FIFO_DEBUG_VERIFY(vff) vio_fifo_verify(vff)
-#else
-# define VIO_FIFO_DEBUG_VERIFY(vff)
-#endif
+#define VIO_FIFO_DEBUG_VERIFY(vff) if (vio_fifo_debug) vio_fifo_verify(vff)
/*==============================================================================
* Inline Functions
@@ -197,7 +212,7 @@ vio_fifo_empty(vio_fifo vff)
* Returns true <=> FIFO is empty beyond end_end (if any).
*/
Inline bool
-vio_fifo_empty_tail(vio_fifo vff)
+vio_fifo_tail_empty(vio_fifo vff)
{
/* if vff is NULL, treat as empty !
* if !set, then all pointers should be NULL (so end_end == put_ptr)
diff --git a/lib/vio_lines.c b/lib/vio_lines.c
index a9268fd5..031f4539 100644
--- a/lib/vio_lines.c
+++ b/lib/vio_lines.c
@@ -59,7 +59,7 @@
*
* WHAT IT DOES DO:
*
- * 1) maps bare '\n' to '\r''\n'.
+ * 1) maps bare '\n' to '\r''\n' -- if required.
*
* Swallows '\r' immediately before '\n' if present.
*
@@ -80,66 +80,84 @@
* A width of <= 0 => very large width indeed.
* A height of <= 0 => indefinite height
*
- * Pause is unset. vio_lc_append will collect an indefinite number of lines.
- *
- * Column and line position set to zero.
- *
- * Returns: address of vio_line_control
+ * Returns: address of vio_line_control
*/
extern vio_line_control
-vio_lc_init_new(vio_line_control lc, int width, int height)
+vio_lc_init_new(vio_line_control lc, int width, int height, const char* newline)
{
if (lc == NULL)
lc = XCALLOC(MTYPE_VIO_LC, sizeof(struct vio_line_control)) ;
else
- memset(lc, 0, sizeof(struct vio_line_control)) ;
+ memset(lc, 0, sizeof(vio_line_control_t)) ;
/* Zeroising has set:
*
- * pause = 0 -- no limit on the number of lines to append
- * paused = 0 -- not paused
+ * width = X -- set below.
+ * height = X -- set below
+ *
+ * counter = X -- set below
*
- * col = 0 -- at column 0
- * lines = 0 -- no lines collected, yet
+ * incomplete = false -- no incomplete line in hand
+ * fragments = NULL -- set below
+ * here = NULL -- set below
*
- * iov = all 0 -- empty
- * writing = 0 -- not writing
+ * qiov = NULL -- set below
+ *
+ * newline = zeros -- set below
*/
lc->width = width >= 0 ? width : 0 ;
lc->height = height >= 0 ? height : 0 ;
+ vio_lc_counter_reset(lc) ;
+
+ lc->fragments = qiovec_init_new(NULL) ;
+ lc->here = qs_new(120) ;
+
+ lc->qiov = qiovec_init_new(NULL) ;
+
+ lc->newline->base = newline ;
+ lc->newline->len = strlen(newline) ;
return lc ;
} ;
/*------------------------------------------------------------------------------
- * Reset vio_line_control (if any) -- release body and (if required) the
+ * Reset vio_line_control (if any) -- release body and (if required) free the
* structure.
*
* Returns: address of vio_line_control (if any) -- NULL if structure released
+ *
+ * NB: if the line control is not freed, it MUST be reinitialised by
+ * vio_lc_init_new() before it is used again !
+ *
+ * NB: it is the callers responsibility to release anything which was buffered
+ * for the line control to look after.
+ *
+ * It is also the callers responsibility to release the newline string,
+ * if required.
*/
extern vio_line_control
vio_lc_reset(vio_line_control lc, free_keep_b free_structure)
{
if (lc != NULL)
{
+ lc->fragments = qiovec_free(lc->fragments) ;
+ lc->here = qs_free(lc->here) ;
+
+ lc->qiov = qiovec_free(lc->qiov) ;
+
if (free_structure)
XFREE(MTYPE_VIO_LC, lc) ; /* sets lc = NULL */
else
- vio_lc_init_new(lc, lc->width, lc->height) ;
- /* re-initialise */
+ memset(lc, 0, sizeof(vio_line_control_t)) ;
} ;
return lc ;
} ;
/*------------------------------------------------------------------------------
- * Clear given vio_line_control.
- *
- * Sets: pause = 0
- * paused = 0
- * col = 0
- * writing = 0
+ * Clear given vio_line_control -- discard all buffered lines and reset the
+ * counter.
*
* NB: it is the callers responsibility to release anything buffered because
* it was earlier appended.
@@ -150,12 +168,14 @@ vio_lc_clear(vio_line_control lc)
if (lc == NULL)
return ;
- qiovec_clear(&lc->qiov) ;
+ vio_lc_counter_reset(lc) ;
+
+ lc->incomplete = false ;
+
+ qiovec_clear(lc->fragments) ;
+ qs_clear(lc->here) ;
- lc->pause = 0 ;
- lc->paused = 0 ;
- lc->col = 0 ;
- lc->writing = 0 ;
+ qiovec_clear(lc->qiov) ;
} ;
/*------------------------------------------------------------------------------
@@ -164,196 +184,404 @@ vio_lc_clear(vio_line_control lc)
* A width of <= 0 => very large width indeed.
* A height of <= 0 => indefinite height
*
- * Pause is adjusted if it is not zero, and may become zero and set paused.
+ * This may happen at almost any time when a Telnet terminal is resized.
+ * From line control perspective, can happen between calls of vio_lc_append(),
+ * vio_lc_flush() and vio_lc_write_nb().
+ *
+ * If happens while the line control has stuff buffered, then it is too late to
+ * change anything, unless was an incomplete line.
+ *
+ * Tries to sort out the counter... but is is not possible to do this perfectly.
*/
extern void
vio_lc_set_window(vio_line_control lc, int width, int height)
{
- unsigned old_height ;
+ unsigned depth ;
- old_height = lc->height ;
+ depth = lc->height ;
lc->width = width >= 0 ? width : 0 ;
lc->height = height >= 0 ? height : 0 ;
- if (lc->pause != 0)
- {
- if (lc->height > old_height)
- lc->pause += lc->height - old_height ;
- else
- {
- if (lc->pause >= (old_height - lc->height))
- lc->pause = 0 ;
- else
- lc->pause -= old_height - lc->height ;
- } ;
- lc->paused = (lc->pause == 0) ;
- } ;
-} ;
+ /* If counter already exhausted, or just set indefinite height, need do
+ * nothing more.
+ */
+ if ((lc->counter <= 0) || (lc->height == 0))
+ return ;
-/*==============================================================================
- * Appending and writing
- */
+ /* Counter not already exhausted, and is setting a definite height.
+ *
+ * If no height was set before, start from scratch, now.
+ */
+ if (depth == 0)
+ return vio_lc_counter_reset(lc) ;
-/*------------------------------------------------------------------------------
- * Sets pause to the current height and clear paused.
- */
-extern void
-vio_lc_set_pause(vio_line_control lc)
-{
- lc->pause = lc->height ;
- lc->paused = 0 ;
+ /* Had a definite height and setting a new one.
+ *
+ * Now calculate the depth, which is how far down the old height have
+ * got to. (The counter should be less than the old height, but if it
+ * isn't we end up with a huge depth here...)
+ */
+ depth -= lc->counter ;
+
+ /* If the new height is > depth set the counter to be the new height - depth.
+ *
+ * Otherwise, set the counter to 0, so is immediately exhausted.
+ *
+ * Cannot solve the problem of what to do if the width has changed !
+ */
+ lc->counter = (lc->height > depth) ? lc->height - depth : 0 ;
} ;
-/*------------------------------------------------------------------------------
- * Put newline (if required) and account for it
+/*==============================================================================
+ * Appending and writing
*/
-static inline void
-vio_lc_newline(vio_line_control lc, bool required)
-{
- if (required)
- qiovec_push(&lc->qiov, "\r\n", 2) ;
- lc->col = 0 ;
- lc->line += 1 ;
- if (lc->pause != 0)
- {
- lc->pause -= 1 ;
- lc->paused = (lc->pause == 0) ;
- } ;
-} ;
+static uint vio_lc_trim(vio_line_control lc, qiov_item item, const char* e) ;
+static void vio_lc_append_line(vio_line_control lc, qiov_item item) ;
/*------------------------------------------------------------------------------
* Append a lump of output to the given line control's buffers.
*
- * Breaks the output into lines which are no longer than the lc->width.
+ * Breaks the output into lines which are no longer than the lc->width. If
+ * that is zero (unset) we use a very large width indeed.
+ *
+ * Breaks the output into a number of screen lines, limited by the line counter.
+ *
+ * Effect of line counter:
+ *
+ * * if definite height, will output only as many screen lines as it can,
+ * including none at all.
+ *
+ * * if indefinite height, when the line counter is reset it is set to some
+ * limit on the number of screen lines to buffer in the line control.
+ *
+ * Note that this is not exact, in particular will *not* stop in the
+ * middle of a complete line, just because the limit of screen lines has
+ * been exceeded.
+ *
+ * * in any case, the counter may reach exactly 0 if the number of screen
+ * lines required is exactly the number of screen lines allowed.
+ *
+ * Trims trailing whitespace from each line (but not from screen lines), before
+ * breaking up into screen lines.
+ *
+ * NB: this means that does not output anything until gets a '\n'.
+ *
+ * See vio_lc_flush().
*
* Maps '\n' to '\r''\n'.
*
- * Discards '\r' if found before '\n', and possibly at other times.
+ * Discards '\r' amongst trailing whitespace, but not otherwise.
*
* If lc->width == 0, use a very large width indeed.
*
- * If lc->pause == 0, append an indefinite number of lines
+ * Returns: number of bytes able to append to the line control.
*
- * NB: the buffer presented MUST be retained until the contents of the
- * line control's buffers have been written.
+ * NB: the buffer presented MUST be retained until the contents of the line
+ * control's buffers have been written -- see vio_lc_write_nb().
*
- * Returns: number of bytes able to append
+ * NB: the line control may buffer stuff "in hand", before it reaches the
+ * output iovec, either because:
+ *
+ * a) the current line is incomplete -- nothing will be output until
+ * a '\n' is appended, or the line control is flushed.
+ *
+ * b) the line counter was exhausted while outputting a line -- this will
+ * be output on the next call of vio_lc_append(), or when the line
+ * control is flushed.
+ *
+ * So even when the buffers that feed the line control are empty,
+ * a call of vio_lc_append() may push out some lines buffered in
+ * the line control.
+ *
+ * NB: all output from the line control ends with a newline.
+ *
+ * Incomplete lines are held in the line control until they are completed,
+ * or until the line control is flushed. When the line control is flushed,
+ * unless the incomplete line is all whitespace, a newline is appended.
*/
extern size_t
vio_lc_append(vio_line_control lc, const void* buf, size_t len)
{
- const char* p ;
- const char* end ;
-
- unsigned width ;
- unsigned pause ;
-
- /* Prepare local width and pause */
- if (lc->width > 0)
- width = lc->width ;
- else
- width = UINT_MAX ;
-
- if (lc->pause > 0)
- pause = 0 ;
- else
- pause = 1 ;
+ qiov_item_t item ;
+ const char* bp ;
+ const char* be ;
- lc->paused = 0 ;
+ /* If we have a line which was being output, but was interrupted part
+ * way through by line counter expiry... try to empty that out, first.
+ *
+ * This may be prevented by the line counter (partly or completely), or may
+ * exhaust it, which will be noticed, shortly, below.
+ */
+ if (!qiovec_empty(lc->fragments) && !lc->incomplete)
+ {
+ qiovec_shift(lc->fragments, item) ; /* want first fragment */
+ vio_lc_append_line(lc, item) ;
+ } ;
/* Append: stop when run out of data or run out of lines */
- end = (const char*)buf + len ;
- p = buf ;
+ be = (const char*)buf + len ;
+ bp = buf ;
- while ((p < end) && (lc->pause != pause))
+ /* If is counter exhausted, stop appending now.
+ *
+ * In vio_lc_append_line() updates the counter, and may set it -ve.
+ */
+ while ((bp < be) && (lc->counter > 0))
{
- const char* e ;
- bool nl ;
- int nlx ;
+ item->base = bp ; /* start of current fragment */
- nlx = 0 ; /* no line ending chars yet */
+ /* scan for '\n' -- note that we look for a complete line,
+ * before worrying about the line */
+ bp = memchr(bp, '\n', (be - bp)) ;
- /* scan for '\n'. */
- e = memchr(p, '\n', (end - p)) ;
- nl = (e != NULL) ;
- if (nl)
- ++nlx ; /* account for the '\n' */
- else
- e = end ; /* use all there is */
+ if (bp == NULL)
+ {
+ /* We do not have a '\n' -- rats */
+ item->len = be - item->base ;
+ qiovec_push(lc->fragments, item) ;
+
+ lc->incomplete = true ; /* have incomplete line in hand */
+
+ bp = be ;
+ break ; /* done */
+ } ;
- /* peel off trailing '\r'.
+ /* We have a '\n' -- hurrah !
*
- * NB: if have not got a '\n', then this may discard a bare
- * '\r' -- but bare '\r' are undefined in any case.
+ * Have a complete line ready to be added to the qiov -- the
+ * current fragment and any buffered ones.
+ *
+ * Trim off any trailing whitespace and establish the length of the
+ * last fragment of the current (complete) line. If the current
+ * fragment is all whitespace, works its way up any in hand fragments.
+ */
+ lc->incomplete = false ; /* definitely not */
+
+ item->len = vio_lc_trim(lc, item, bp) ;
+
+ ++bp ; /* step past the '\n' */
+
+ /* If have fragments in hand, then want to have the first fragment
+ * as the current fragment (currently the current fragment is the
+ * last fragment).
*/
- if ((e > p) && (*(e - 1) == '\r'))
+ if (!qiovec_empty(lc->fragments))
{
- --e ; /* strip the '\r' */
- ++nlx ; /* but account for it */
- }
+ qiovec_push(lc->fragments, item) ;
+ qiovec_shift(lc->fragments, item) ;
+ } ;
- /* have p..e characters and possibly nl to add to the output.
+ /* We are now ready to break the current line up into width
+ * sections, if required -- to the extend allowed by the counter.
*
- * Note that if enters the while, (e - p) > 0. So there is at least one
- * character to add. This avoids generating a spurious line ending if
- * the width has been reduced, and the next thing output is a line end.
+ * Have trimmed off any trailing whitespace, including back across
+ * any fragments. So:
+ *
+ * item = first fragment of the line
*/
- while ((p < e) && (lc->pause != pause))
+ vio_lc_append_line(lc, item) ;
+ } ;
+
+ /* Exhausted the available data or the line count */
+
+ return (bp - (const char*)buf) ; /* what have taken */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Flush anything which the line control has buffered.
+ *
+ * There are two possible cases:
+ *
+ * 1) had collected a complete line in vio_lc_append(), but when putting to
+ * the qiovec was stopped by the line counter.
+ *
+ * So have one or more screen lines buffered up, ready to go.
+ *
+ * 2) had collected an incomplete line in vio_lc_append().
+ *
+ * That will now be flushed as if a newline had been appended, except that
+ * if the result would be a newline *only*, then all the whitespace is
+ * discarded and nothing is output.
+ *
+ * If there is nothing to flush, or an incomplete line turns out to be
+ * entirely whitespace, return false.
+ *
+ * Otherwise we have something to flush, and will attempt to do so.
+ *
+ * Effect of line counter:
+ *
+ * * if definite height, will output only as many screen lines as it can,
+ * including none at all.
+ *
+ * * if indefinite height, will output as many screen lines as it takes,
+ * whatever the state of the line counter.
+ *
+ * * in any case, the counter may reach exactly 0 if the number of screen
+ * lines required is exactly the number of screen lines allowed.
+ *
+ * Returns: true <=> have something to flush (but counter may be exhausted)
+ * false <=> nothing to be flushed
+ */
+extern bool
+vio_lc_flush(vio_line_control lc)
+{
+ qiov_item_t item ;
+
+ if (qiovec_count(lc->fragments) == 0)
+ return false ;
+
+ /* We have something in hand.
+ *
+ * If was an incomplete line, now is the time to trim off any trailing
+ * whitespace -- more or less as if there had been a '\n' at this point.
+ */
+ if (lc->incomplete)
+ {
+ lc->incomplete = false ;
+
+ qiovec_pop(lc->fragments, item) ;
+
+ item->len = vio_lc_trim(lc, item, item->base + item->len) ;
+
+ if (item->len == 0)
+ {
+ assert(qiovec_empty(lc->fragments)) ;
+ return false ; /* it was all whitespace which has all
+ been discarded. */
+ } ;
+
+ qiovec_push(lc->fragments, item) ;
+ } ;
+
+ /* Have something in hand, so now attempt to output it. */
+ qiovec_shift(lc->fragments, item) ;
+ vio_lc_append_line(lc, item) ;
+
+ return true ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Trim trailing whitespace from given fragment -- ' ', '\t' and '\r'.
+ *
+ * If required, pops fragments until finds some non-whitespace, or runs out
+ * of fragments.
+ *
+ * Returns: resulting length.
+ */
+static uint
+vio_lc_trim(vio_line_control lc, qiov_item item, const char* e)
+{
+ const char* p ;
+
+ while (1)
+ {
+ p = item->base ;
+
+ while (e > p)
{
- const char* t ;
- unsigned col ;
+ char ch ;
+
+ ch = *(e - 1) ;
+ if ( (ch != '\r') && (ch != ' ') && (ch != '\t') )
+ return e - p ; /* <<< found non-whitespace <<< exit */
+
+ --e ;
+ } ;
- col = lc->col + (e - p) ; /* NB: e > p */
+ qiovec_pop(lc->fragments, item) ; /* pops a NULL if empty */
- if (col > width)
+ if (item->len == 0)
+ return 0 ;
+
+ e = item->base + item->len ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Append line to the output qiov -- breaking it up into width sections,
+ * inserting newlines and looking out for and updating the line counter.
+ *
+ * If definite height, may stop immediately, without outputting anything.
+ * If indefinite height, will output the current line, whatever the state of
+ * the line counter.
+ *
+ * Note that do not trim whitespace from screen lines.
+ *
+ * At this point all trailing whitespace will have been removed from the
+ * original line.
+ *
+ * Requires that have previously trimmed off any trailing whitespace, including
+ * back across any fragments. So:
+ *
+ * item = first fragment of the line -- NOT in lc->fragments.
+ *
+ * It is possible for the first fragment to be empty (empty line !).
+ */
+static void
+vio_lc_append_line(vio_line_control lc, qiov_item item)
+{
+ bool done = false ;
+
+ while ((lc->counter > 0) || (lc->height == 0))
+ {
+ uint take ;
+
+ /* Take fragments or parts thereof until we have run out of output
+ * (which sets done == true) or reach the width.
+ */
+ take = (lc->width > 0) ? lc->width : UINT_MAX ;
+ while (1)
+ {
+ if (item->len <= take)
{
- /* can use only part of what there is */
- if (width > lc->col)
- t = p + (width - lc->col) ;
- /* take to edge of screen */
+ /* Use entire fragment, and step to next (if any)
+ *
+ * Note that qiovec_push() ignores zero length items, so if
+ * this is an empty line, will push no fragments and will stop
+ * here.
+ */
+ qiovec_push(lc->qiov, item) ;
+
+ if (qiovec_empty(lc->fragments))
+ {
+ done = true ; /* signal all done */
+ break ;
+ }
else
- t = p ;
- assert(t < e) ; /* if not need to deal with nl */
+ {
+ take -= item->len ;
+ qiovec_shift(lc->fragments, item) ;
+ }
}
else
{
- /* can use all of what there is */
- if (nlx == 2) /* if have crlf, use it */
- {
- e += nlx ; /* use the crlf that's there */
- nlx = 0 ; /* used it */
- } ;
+ /* Use leading part of fragment, and reduce same */
+ qiovec_push_this(lc->qiov, item->base, take) ;
+ item->base += take ;
+ item->len -= take ;
- t = e ; /* take it all */
+ break ;
} ;
-
- assert(t >= p) ;
- if (t != p)
- qiovec_push(&lc->qiov, p, (t - p)) ;
-
- /* advance. If not taken all the line, need a crlf */
- p = t ;
-
- if (p < e)
- vio_lc_newline(lc, 1) ;
} ;
- /* If taken all of line, deal with any outstanding nl and nlx */
- if (p == e)
- {
- if (nl)
- vio_lc_newline(lc, (nlx != 0)) ;
+ qiovec_push(lc->qiov, lc->newline) ;
- p += nlx ; /* step past '\r' or '\n' */
- } ;
- } ;
+ /* Count down & exit if all done. */
+ --lc->counter ;
- /* Exhausted the available data or the line count */
- assert(p <= end) ;
+ if (done)
+ return ;
+ } ;
- return (p - (const char*)buf) ; /* what have taken */
+ /* Counter exhausted, but we are not done with the current line.
+ *
+ * unshift the current item onto the (front of) the fragments qiovec.
+ * (We know we can do this straightforwardly, because either the qiovec is
+ * empty, or what was the first item has previously been shifted off.)
+ */
+ qiovec_unshift(lc->fragments, item) ;
} ;
/*------------------------------------------------------------------------------
@@ -368,15 +596,52 @@ vio_lc_append(vio_line_control lc, const void* buf, size_t len)
* -1 => failed -- see errno
*
* Sets lc->writing if write does not complete
+ *
+ * NB: when says "all done" (or failed) the caller may release all buffered
+ * material that has been accepted by vio_lc_append() -- and not before.
*/
extern int
vio_lc_write_nb(int fd, vio_line_control lc)
{
int ret ;
- ret = qiovec_write_nb(fd, &lc->qiov) ;
+ ret = qiovec_write_nb(fd, lc->qiov) ;
+
+ if (ret <= 0)
+ {
+ /* About to promise that all buffered material previously accepted by
+ * vio_lc_append() may now be released.
+ *
+ * Unfortunately, if we have any line fragments in hand, have to buffer
+ * those locally, now.
+ *
+ * NB: if have a particularly long original line, or a particularly
+ * narrow or short screen, it is possible to pass through here
+ * more than once for the same original line.
+ *
+ * In this obscure case, the line will already be buffered in
+ * lc->here. Happily qs_clear() does not disturb the body of the
+ * qstring, and qs_append_str_n will append from within its own
+ * body !
+ */
+ if (!qiovec_empty(lc->fragments))
+ {
+ qs_set_len_nn(lc->here, 0) ; /* ready to append stuff */
+ do
+ {
+ qiov_item_t item ;
+
+ qiovec_shift(lc->fragments, item) ;
+
+ qs_append_str_n(lc->here, item->base, item->len) ;
+ }
+ while (!qiovec_empty(lc->fragments)) ;
- lc->writing = (ret > 0) ;
+ /* One fragment in hand, collected from all previous fragments
+ */
+ qiovec_push_this(lc->fragments, qs_char(lc->here), qs_len(lc->here)) ;
+ } ;
+ } ;
return ret ;
} ;
diff --git a/lib/vio_lines.h b/lib/vio_lines.h
index dd5545cc..6a3d13e9 100644
--- a/lib/vio_lines.h
+++ b/lib/vio_lines.h
@@ -24,6 +24,7 @@
#include "misc.h"
#include "qiovec.h"
+#include "qstring.h"
/*==============================================================================
*
@@ -32,22 +33,24 @@
/*------------------------------------------------------------------------------
* Line control -- collecting lines of a given width for output.
*
- * NB: a completely zero structure is a valid, clear vio_line_control.
+ * NB: need to explicitly initialise line control in order to set the required
+ * new line, the qiovec vectors and a qstring buffer !
*/
struct vio_line_control
{
- unsigned width ; /* console width -- 0 => HUGE */
- unsigned height ; /* console height -- 0 => indefinite */
+ uint width ; /* console width -- 0 => HUGE */
+ uint height ; /* console height -- 0 => indefinite */
- unsigned pause ; /* number of lines to next pause
- 0 => indefinite */
- bool paused ; /* true <=> last append stopped on pause */
+ int counter ; /* number of lines to next pause
+ <= 0 => paused. */
- unsigned col ; /* current column position */
- unsigned line ; /* line number of last complete line */
+ bool incomplete ; /* fragments in hand are an incomplete line */
+ qiovec fragments ;
+ qstring here ; /* any fragments after write */
- struct qiovec qiov ; /* iovec control */
- bool writing ; /* write started, but not completed */
+ qiovec qiov ; /* output screen lines */
+
+ qiov_item_t newline ; /* the required sequence */
} ;
typedef struct vio_line_control* vio_line_control ;
@@ -55,32 +58,118 @@ typedef struct vio_line_control vio_line_control_t[1] ;
enum
{
- VIO_LINE_CONTROL_INIT_ALL_ZEROS = true
+ VIO_LINE_CONTROL_INIT_ALL_ZEROS = false
} ;
/*==============================================================================
* Functions
*/
extern vio_line_control vio_lc_init_new(vio_line_control lc, int width,
- int height) ;
+ int height,
+ const char* newline) ;
+Inline vio_line_control vio_lc_new(int width, int height,
+ const char* newline) ;
extern vio_line_control vio_lc_reset(vio_line_control lc,
free_keep_b free_structure) ;
+Inline vio_line_control vio_lc_free(vio_line_control lc) ;
-Inline bool vio_lc_empty(vio_line_control lc) ;
extern void vio_lc_clear(vio_line_control lc) ;
extern void vio_lc_set_window(vio_line_control lc, int width, int height) ;
-extern void vio_lc_set_pause(vio_line_control lc) ;
+Inline void vio_lc_counter_reset(vio_line_control lc) ;
+Inline void vio_lc_clear_pause(vio_line_control lc) ;
+
+Inline bool vio_lc_counter_is_exhausted(vio_line_control lc) ;
+Inline bool vio_lc_have_complete_line_in_hand(vio_line_control lc) ;
+Inline bool vio_lc_is_empty(vio_line_control lc) ;
+
extern size_t vio_lc_append(vio_line_control lc, const void* buf, size_t len) ;
+extern bool vio_lc_flush(vio_line_control lc) ;
extern int vio_lc_write_nb(int fd, vio_line_control lc) ;
/*------------------------------------------------------------------------------
+ * Create new line control.
+ *
+ * Returns: address of new line control.
+ */
+Inline vio_line_control
+vio_lc_new(int width, int height, const char* newline)
+{
+ return vio_lc_init_new(NULL, width, height, newline) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Free given line control (if any).
+ *
+ * It is the caller's responsibility to free anything that the line control may
+ * point to.
+ *
+ * Returns: NULL
+ */
+Inline vio_line_control
+vio_lc_free(vio_line_control lc)
+{
+ return vio_lc_reset(lc, free_it) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Counter reset.
+ */
+Inline void
+vio_lc_counter_reset(vio_line_control lc)
+{
+ lc->counter = (lc->height > 0) ? lc->height : 100 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If no height is set, set counter to large number -- to do a tranche of
+ * output.
+ *
+ * Otherwise, if the line control is paused (or would pause as soon as any
+ * output is sent), reset the counter.
+ */
+Inline void
+vio_lc_clear_pause(vio_line_control lc)
+{
+ if ((lc->counter <= 0) || (lc->height == 0))
+ vio_lc_counter_reset(lc) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Is the given line control counter exhausted ?
+ *
+ * Any attempt to output more stuff will be prevented -- except for
+ * vio_lc_flush() which will succeed if height is indefinite.
+ */
+Inline bool
+vio_lc_counter_is_exhausted(vio_line_control lc)
+{
+ return (lc->counter <= 0) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Do we have a complete line "in hand" ?
+ *
+ * This will only be the case if have a definite height, and the line counter
+ * has expired.
+ */
+Inline bool
+vio_lc_have_complete_line_in_hand(vio_line_control lc)
+{
+ return !lc->incomplete && !qiovec_empty(lc->fragments) ;
+} ;
+
+/*------------------------------------------------------------------------------
* Is given line control empty ?
+ *
+ * Is empty if the qiov is empty and there is nothing in hand.
+ *
+ * NB: if there is something in hand, it may be complete or incomplete.
*/
Inline bool
-vio_lc_empty(vio_line_control lc)
+vio_lc_is_empty(vio_line_control lc)
{
- return qiovec_empty(&lc->qiov) ;
+ return qiovec_empty(lc->qiov) && qiovec_empty(lc->fragments) ;
} ;
#endif /* _ZEBRA_VIO_LINES_H */
diff --git a/lib/vty.c b/lib/vty.c
index da94b992..9020cb61 100644
--- a/lib/vty.c
+++ b/lib/vty.c
@@ -28,13 +28,17 @@
#include <sys/param.h>
#include <sys/stat.h>
#include <ctype.h>
+#include <unistd.h>
#include "vty.h"
#include "vty_local.h"
#include "vty_io.h"
+#include "vty_io_file.h"
#include "vty_command.h"
#include "vty_cli.h"
+#include "vty_log.h"
#include "vio_fifo.h"
+#include "log_local.h"
#include "list_util.h"
@@ -43,9 +47,10 @@
#include "command_execute.h"
#include "command_parse.h"
#include "memory.h"
-#include "log.h"
#include "mqueue.h"
#include "qstring.h"
+#include "qpath.h"
+#include "network.h"
/*==============================================================================
* The vty family comprises:
@@ -104,18 +109,26 @@ struct thread_master* vty_master = NULL ;
* actually running pthreaded.
*/
bool vty_nexus ; /* true <=> in the qpthreads world */
+bool vty_multi_nexus ; /* true <=> more than one qpthread */
-qpn_nexus vty_cli_nexus = NULL ;
-qpn_nexus vty_cmd_nexus = NULL ;
+qpn_nexus vty_cli_nexus = NULL ;
+qpn_nexus vty_cmd_nexus = NULL ;
/* List of all known vio */
-vty_io vio_list_base = NULL ;
+vty_io vio_live_list = NULL ;
/* List of all vty which are in monitor state. */
-vty_io vio_monitors_base = NULL ;
+vty_io vio_monitor_list = NULL ;
/* List of all vty which are on death watch */
-vty_io vio_death_watch = NULL ;
+vty_io vio_death_watch = NULL ;
+
+/* List of child processes in our care */
+vio_child vio_childer_list = NULL ;
+
+/* See vty_child_signal_nexus_set() */
+qpt_mutex_t vty_child_signal_mutex ;
+qpn_nexus vty_child_signal_nexus = NULL ;
/*------------------------------------------------------------------------------
* VTYSH stuff
@@ -129,7 +142,6 @@ char integrate_default[] = SYSCONFDIR INTEGRATE_DEFAULT_CONFIG ;
*/
static void uty_reset (bool final, const char* why) ;
static void uty_init_commands (void) ;
-static void vty_save_cwd (void) ;
//static bool vty_terminal (struct vty *);
//static bool vty_shell_server (struct vty *);
@@ -174,18 +186,21 @@ vty_init (struct thread_master *master_thread)
vty_master = master_thread; /* Local pointer to the master thread */
- vty_save_cwd (); /* need cwd for config reading */
-
- vio_list_base = NULL ; /* no VTYs yet */
- vio_monitors_base = NULL ;
+ vio_live_list = NULL ; /* no VTYs yet */
vio_death_watch = NULL ;
+ vio_childer_list = NULL ;
vty_nexus = false ; /* not running qnexus-wise */
+ vty_multi_nexus = false ; /* not more than one thread either */
vty_cli_nexus = NULL ;
vty_cmd_nexus = NULL ;
+ vty_child_signal_nexus = NULL ; /* none, yet */
+
uty_watch_dog_init() ; /* empty watch dog */
+ uty_init_monitor() ;
+
uty_init_commands() ; /* install nodes */
vty_init_state = vty_init_1st_stage ;
@@ -215,11 +230,14 @@ vty_init_r (qpn_nexus cli, qpn_nexus cmd)
{
assert(vty_init_state == vty_init_1st_stage) ;
- vty_nexus = true ;
- vty_cli_nexus = cli ;
- vty_cmd_nexus = cmd ;
+ vty_nexus = true ;
+ vty_multi_nexus = (cli != cmd) ;
+ vty_cli_nexus = cli ;
+ vty_cmd_nexus = cmd ;
+
+ qpt_mutex_init(vty_mutex, qpt_mutex_recursive);
- qpt_mutex_init(&vty_mutex, qpt_mutex_recursive);
+ qpt_mutex_init(vty_child_signal_mutex, qpt_mutex_quagga);
vty_init_state = vty_init_2nd_stage ;
} ;
@@ -429,15 +447,28 @@ vty_terminate (void)
uty_reset(true, "Shut down") ; /* final reset */
+ vty_child_close_register() ;
+
VTY_UNLOCK() ;
- qpt_mutex_destroy(&vty_mutex, 0);
+ qpt_mutex_destroy(vty_mutex, 0);
+ qpt_mutex_destroy(vty_child_signal_mutex, 0);
vty_init_state = vty_init_terminated ;
}
/*------------------------------------------------------------------------------
- * Reset -- final curtain or for SIGHUP
+ * Reset -- for SIGHUP or at final curtain.
+ *
+ * For SIGHUP is called via vty_reset_because(), and is sitting in the
+ * vty_cli_nexus (if pthreaded) with the message queues still running.
+ *
+ * For final curtain will
+ *
+ *
+ * is called by vty_terminate(), by which time all qnexus
+ * have been shut down, so no message queues and no timers etc, are running.
+ *
*
* Closes listeners.
*
@@ -460,18 +491,17 @@ uty_reset (bool curtains, const char* why)
vty_io vio ;
vty_io next ;
- VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
uty_close_listeners() ;
- next = sdl_head(vio_list_base) ;
+ next = sdl_head(vio_live_list) ;
while (next != NULL)
{
vio = next ;
next = sdl_next(vio, vio_list) ;
- uty_close(vio, curtains, qs_set(NULL, why)) ;
+ uty_close(vio, why, curtains) ;
} ;
host.vty_timeout_val = VTY_TIMEOUT_DEFAULT;
@@ -483,10 +513,7 @@ uty_reset (bool curtains, const char* why)
/* sets host.vty_ipv6_accesslist_name = NULL */
if (curtains)
- {
- XFREE (MTYPE_HOST, host.vty_cwd);
- uty_watch_dog_stop() ; /* and final death watch run */
- } ;
+ uty_watch_dog_stop() ; /* and final death watch run */
} ;
/*==============================================================================
@@ -505,32 +532,17 @@ uty_reset (bool curtains, const char* why)
* Appears only to be used by vtysh !! TODO ????
*/
extern vty
-vty_open(vty_type_t type)
+vty_open(vty_type_t type, node_type_t node)
{
struct vty* vty ;
VTY_LOCK() ;
- vty = uty_new(type) ;
+ vty = uty_new(type, node) ;
VTY_UNLOCK() ;
return vty ;
} ;
-/*------------------------------------------------------------------------------
- * Close the given VTY
- */
-extern bool
-vty_close(vty vty, bool final, qstring reason)
-{
- bool closed ;
-
- VTY_LOCK() ;
- closed = uty_close(vty->vio, final, reason) ;
- VTY_UNLOCK() ;
-
- return closed ;
-}
-
/*==============================================================================
* General VTY output.
*
@@ -564,6 +576,23 @@ vty_out(struct vty *vty, const char *format, ...)
}
/*------------------------------------------------------------------------------
+ * VTY output -- cf write
+ *
+ * Returns: >= 0 => OK
+ * < 0 => failed (see errno)
+ */
+extern int
+vty_write(struct vty *vty, const void* buf, int n)
+{
+ VTY_LOCK() ;
+
+ vio_fifo_put_bytes(vty->vio->obuf, buf, n) ;
+
+ VTY_UNLOCK() ;
+ return 0 ;
+}
+
+/*------------------------------------------------------------------------------
* VTY output -- output a given numnber of spaces
*
* This is for command output, which may be suppressed
@@ -613,41 +642,86 @@ vty_time_print (struct vty *vty, int cr)
/*------------------------------------------------------------------------------
* Say hello to vty interface.
*/
-void
+extern void
vty_hello (struct vty *vty)
{
+ qpath path ;
+ const char* string ;
+
VTY_LOCK() ;
-#ifdef QDEBUG
- vty_out (vty, "%s\n", debug_banner);
-#endif
- if (host.motdfile)
+ path = (host.motdfile != NULL) ? qpath_dup(host.motdfile) : NULL ;
+ string = host.motd ;
+
+ VTY_UNLOCK() ;
+
+ if (qdebug)
+ vty_out (vty, "%s\n", debug_banner);
+
+ if (path != NULL)
+ vty_cat_file(vty, path, "motd file") ;
+ else if (string != NULL)
+ vty_out(vty, "%s", string);
+
+ /* This "\n" is a bit magic... if the motd file does not end in a "\n",
+ * then this makes sure that we start on a new line.
+ *
+ * Similarly, if the motd string doesn't end '\n', then this makes sure.
+ *
+ * This will also trim trailing space from the end of the motd message.
+ *
+ * Generally these will end in '\n', so this produces the extra blank line
+ * before the cheerful "User authentication" message, which is the most
+ * likely next line.
+ */
+ vty_out(vty, "\n") ;
+
+ qpath_free(path) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * "cat" file to vty
+ */
+extern cmd_return_code_t
+vty_cat_file(vty vty, qpath path, const char* desc)
+{
+ int fd ;
+ void* buf ;
+
+ fd = uty_vfd_file_open(qpath_string(path), vfd_io_read | vfd_io_blocking) ;
+
+ if (fd < 0)
{
- FILE *f;
- char buf[4096];
+ vty_out (vty, "Cannot open %s file '%s': %s (%s)\n", desc,
+ qpath_string(path), errtostr(errno, 0).str,
+ errtoname(errno, 0).str) ;
+ return CMD_WARNING;
+ } ;
- f = fopen (host.motdfile, "r");
- if (f)
- {
- while (fgets (buf, sizeof (buf), f))
- {
- char *s;
- /* work backwards to ignore trailing isspace() */
- for (s = buf + strlen (buf); (s > buf) && isspace ((int)*(s - 1));
- s--);
- *s = '\0';
- vty_out(vty, "%s\n", buf);
- }
- fclose (f);
- }
+ enum { buffer_size = 64 * 1024 } ;
+ buf = XMALLOC(MTYPE_TMP, buffer_size) ;
+
+ while (1)
+ {
+ int r ;
+
+ r = readn(fd, buf, buffer_size) ;
+
+ if (r > 0)
+ vty_write(vty, buf, r) ; // TODO push ??
else
- vty_out(vty, "MOTD file %s not found\n", host.motdfile);
- }
- else if (host.motd)
- vty_out(vty, "%s", host.motd);
+ {
+ if (r == 0)
+ break ;
- VTY_UNLOCK() ;
-}
+ // TODO error handling ....
+ } ;
+ } ;
+
+ close(fd) ;
+
+ return CMD_SUCCESS;
+} ;
/*------------------------------------------------------------------------------
* Clear the contents of the command output FIFO etc.
@@ -661,6 +735,23 @@ vty_out_clear(vty vty)
} ;
/*==============================================================================
+ * Deal with SIGCHLD "event".
+ *
+ * NB: if there is a nexus to be signalled, we do that *before* attempting to
+ * lock the VTY -- because in that case the VTY will be locked by that
+ * nexus !
+ */
+extern void
+vty_sigchld(void)
+{
+ vty_child_signal_nexus_signal() ;
+
+ VTY_LOCK() ;
+ uty_sigchld() ;
+ VTY_UNLOCK() ;
+} ;
+
+/*==============================================================================
* Reading of configuration file
*
* The reading of the configuration file occurs at two times:
@@ -681,9 +772,10 @@ vty_out_clear(vty vty)
* run directly in the thread -- no commands are queued.
*/
-static int vty_use_backup_config (const char *fullpath) ;
-static void vty_read_file (int conf_fd, const char* name,
- cmd_command first_cmd, bool ignore_warnings) ;
+static int vty_use_backup_config (qpath path) ;
+static void vty_read_config_file (int conf_fd, const char* name,
+ cmd_command first_cmd, bool ignore_warnings,
+ bool full_lex) ;
/*------------------------------------------------------------------------------
* Read the given configuration file.
@@ -718,10 +810,9 @@ vty_read_config_first_cmd_special(const char *config_file,
cmd_command first_cmd,
bool ignore_warnings)
{
- char cwd[MAXPATHLEN] ;
- int conf_fd ;
- const char *fullpath ;
- char *tmp = NULL ;
+ const char *name ;
+ qpath path ;
+ int conf_fd ;
/* Deal with VTYSH_ENABLED magic */
if (VTYSH_ENABLED && (config_file == NULL))
@@ -747,7 +838,7 @@ vty_read_config_first_cmd_special(const char *config_file,
{
ret = stat (integrate_default, &conf_stat);
if (ret >= 0)
- return;
+ return; /* TODO leaves host.config_file NULL */
}
} ;
@@ -755,158 +846,115 @@ vty_read_config_first_cmd_special(const char *config_file,
if (config_file == NULL)
config_file = config_default ;
- if (! IS_DIRECTORY_SEP (config_file[0]))
- {
- getcwd (cwd, sizeof(cwd)) ;
- tmp = XMALLOC (MTYPE_TMP, strlen (cwd) + strlen (config_file) + 2) ;
- sprintf (tmp, "%s/%s", cwd, config_file);
- fullpath = tmp;
- }
- else
- {
- tmp = NULL ;
- fullpath = config_file;
- } ;
+ path = qpath_make(config_file, host.cwd) ;
+ name = qpath_string(path) ;
/* try to open the configuration file */
- conf_fd = uty_vfd_file_open(fullpath, vfd_io_read | vfd_io_blocking) ;
+ conf_fd = uty_vfd_file_open(name, vfd_io_read) ;
if (conf_fd < 0)
{
fprintf (stderr, "%s: failed to open configuration file %s: %s\n",
- __func__, fullpath, errtostr(errno, 0).str);
+ __func__, name, errtostr(errno, 0).str) ;
- conf_fd = vty_use_backup_config (fullpath);
+ conf_fd = vty_use_backup_config (path);
if (conf_fd >= 0)
fprintf (stderr, "WARNING: using backup configuration file!\n");
else
{
fprintf (stderr, "can't open backup configuration file [%s%s]\n",
- fullpath, CONF_BACKUP_EXT);
+ name, CONF_BACKUP_EXT);
exit(1);
}
} ;
-#ifdef QDEBUG
- fprintf(stderr, "Reading config file: %s\n", fullpath);
-#endif
+ if (qdebug)
+ fprintf(stderr, "Reading config file: %s\n", name);
- vty_read_file(conf_fd, fullpath, first_cmd, ignore_warnings);
+ vty_read_config_file(conf_fd, name, first_cmd, ignore_warnings, false);
- host_config_set (fullpath);
+ cmd_host_config_set(path) ;
-#ifdef QDEBUG
- fprintf(stderr, "Finished reading config file\n");
-#endif
+ qpath_free(path) ;
- if (tmp)
- XFREE (MTYPE_TMP, tmp);
+ if (qdebug)
+ fprintf(stderr, "Finished reading config file\n");
}
/*------------------------------------------------------------------------------
* Try to use a backup configuration file.
*
- * Having failed to open the file "<fullpath>", if there is a file called
- * "<fullpath>.sav" that can be opened for reading, then:
+ * Having failed to open the file "<path>", if there is a file called
+ * "<path>.sav" that can be opened for reading, then:
*
* - make a copy of that file
- * - call it "<fullpath>"
+ * - call it "<path>"
* - return an open FILE
*
- * Returns: < 0 => no "<fullpath>.sav", or failed doing any of the above
+ * Returns: < 0 => no "<path>.sav", or failed doing any of the above
* >= 0 otherwise, fd file.
*/
static int
-vty_use_backup_config (const char *fullpath)
+vty_use_backup_config (qpath path)
{
- char *tmp_path ;
- struct stat buf;
- int ret, tmp, sav;
- int c, err;
- char* buffer ;
-
- enum { xl = 32 } ;
- tmp_path = malloc(strlen(fullpath) + xl) ;
+ qpath temp ;
+ char* name ;
+ int sav_fd, tmp_fd, err ;
+ bool ok ;
/* construct the name "<fullname>.sav", and try to open it. */
- confirm(xl > sizeof(CONF_BACKUP_EXT)) ;
- sprintf (tmp_path, "%s%s", fullpath, CONF_BACKUP_EXT) ;
+ temp = qpath_dup(path) ;
+ qpath_extend_str(temp, CONF_BACKUP_EXT) ;
- if (stat (tmp_path, &buf) != -1)
- sav = uty_vfd_file_open(tmp_path, vfd_io_read | vfd_io_blocking) ;
- else
- sav = -1 ;
+ sav_fd = -1 ;
+ tmp_fd = -1 ;
- if (sav < 0)
- {
- err = errno ; /* making sure */
- free (tmp_path) ;
- errno = err ;
- return sav ;
- } ;
+ sav_fd = uty_vfd_file_open(qpath_string(temp),
+ vfd_io_read | vfd_io_blocking) ;
/* construct a temporary file and copy "<fullpath.sav>" to it. */
- confirm(xl > sizeof(".XXXXXX"))
- sprintf (tmp_path, "%s%s", fullpath, ".XXXXXX") ;
+ qpath_extend_str(temp, ".XXXXXX") ;
+ name = qpath_char_string(temp) ;
- /* Open file to configuration write. */
- tmp = mkstemp (tmp_path);
- if (tmp < 0)
- {
- err = errno ;
- free (tmp_path);
- close(sav);
- errno = err ;
- return tmp;
- }
+ if (sav_fd >= 0)
+ tmp_fd = mkstemp(name) ;
- enum { buffer_size = 64 * 1024 } ;
- buffer = malloc(buffer_size) ;
+ ok = ((sav_fd >= 0) && (tmp_fd >= 0)) ;
+
+ if (ok)
+ ok = (copyn(tmp_fd, sav_fd) == 0) ;
- c = 1 ;
- while (c > 0)
- {
- c = read(sav, buffer, buffer_size) ;
- if (c > 0)
- {
- if (write(tmp, buffer, c) < c)
- c = -1 ;
- } ;
- } ;
err = errno ;
- free(buffer) ;
- close(sav) ;
- close(tmp) ;
+ if (tmp_fd >= 0)
+ close(tmp_fd) ;
+ if (sav_fd >= 0)
+ close(sav_fd) ;
- if (c < 0)
- {
- errno = err ;
- return c ;
- } ;
+ /* If now OK, then have copied the .sav to the temporary file. */
- /* Make sure that have the required file status */
- if (chmod(tmp_path, CONFIGFILE_MASK) != 0)
+ if (ok)
{
+ /* Make sure that have the required file status */
+ ok = chmod(name, CONFIGFILE_MASK) == 0 ;
+
+ /* Finally, make a link with the original name */
+ if (ok)
+ ok = link(name, qpath_string(path)) == 0 ;
+
err = errno ;
- unlink (tmp_path);
- free (tmp_path);
- errno = err ;
- return -1 ;
} ;
- /* Make <fullpath> be a name for the new file just created. */
- ret = link (tmp_path, fullpath) ;
+ if (tmp_fd >= 0) /* if made a temporary, done with it now */
+ unlink(name) ;
- /* Discard the temporary, now */
- err = errno ;
- unlink (tmp_path) ;
- free (tmp_path) ;
- errno = err ;
+ qpath_free(temp) ; /* done with the qpath */
- /* If link was successful, try to open -- otherwise, failed. */
- return (ret == 0) ? uty_vfd_file_open(fullpath, vfd_io_read | vfd_io_blocking)
- : -1 ;
+ if (ok)
+ return uty_vfd_file_open(qpath_string(path), vfd_io_read) ;
+
+ errno = err ;
+ return -1 ;
} ;
/*------------------------------------------------------------------------------
@@ -930,82 +978,33 @@ vty_use_backup_config (const char *fullpath)
* so all commands are executed directly.
*/
static void
-vty_read_file (int conf_fd, const char* name,
- cmd_command first_cmd, bool ignore_warnings)
+vty_read_config_file (int fd, const char* name, cmd_command first_cmd,
+ bool ignore_warnings, bool full_lex)
{
cmd_return_code_t ret ;
vty vty ;
- vty_io vio ;
- vio_vf vf ;
- VTY_LOCK() ;
+ vty = vty_config_read_open(fd, name, full_lex) ;
- /* Set up configuration file reader VTY -- which buffers all output */
- vty = vty_open(VTY_CONFIG_READ);
- vty->node = CONFIG_NODE;
-
- vio = vty->vio ;
+ vty_cmd_loop_prepare(vty) ;
- vf = uty_vf_new(vio, name, conf_fd, vfd_file, vfd_io_read | vfd_io_blocking) ;
+ zlog_info("Started reading configuration: %s", name) ;
- uty_vin_open( vio, vf, VIN_CONFIG, NULL, NULL, 64 * 1024) ;
- uty_vout_open(vio, vf, VOUT_STDERR, NULL, NULL, 4 * 1024) ;
-
- vio->vin->parse_type = cmd_parse_strict | cmd_parse_no_do ;
-
- /* When we get here the VTY is set up and all ready to go. */
- uty_cmd_prepare(vio) ;
-
- VTY_UNLOCK() ;
-
- /* Execute configuration file */
ret = cmd_read_config(vty, first_cmd, ignore_warnings) ;
- ret = vty_cmd_loop_exit(vty, ret) ;
+ zlog_info("Finished reading configuration%s",
+ (ret == CMD_SUCCESS) ? "." : " -- FAILED") ;
- if (ret != CMD_SUCCESS)
- {
- vty_close(vty, true, qs_set(NULL, "Error in configuration file.")) ;
- exit (1);
- } ;
+ vty_cmd_loop_exit(vty) ;
- vty_close(vty, false, NULL) ;
+ if (ret != CMD_SUCCESS)
+ exit(1) ;
} ;
-#if 0
/*------------------------------------------------------------------------------
- * Flush the contents of the command output FIFO to the given file.
+ * Push the given fd as the VOUT_CONFIG.
*
- * Takes no notice of any errors !
- */
-extern void
-uty_out_fflush(vty_io vio, FILE* file)
-{
- char* src ;
- size_t have ;
-
- VTY_ASSERT_LOCKED() ;
-
- fflush(file) ;
-
- src = vio_fifo_get_lump(&vio->cmd_obuf, &have) ;
- while (src != NULL)
- {
- fwrite(src, 1, have, file) ;
- src = vio_fifo_step_get_lump(&vio->cmd_obuf, &have, have) ;
- } ;
-
- fflush(file) ;
-} ;
-#endif
-
-/*==============================================================================
- * For writing configuration file by command, temporarily redirect output to
- * an actual file.
- */
-
-/*------------------------------------------------------------------------------
- * Set the given fd as the VTY_FILE output.
+ * Note that this is a "blocking" vf, so can open and close in the cmd thread.
*/
extern void
vty_open_config_write(vty vty, int fd)
@@ -1017,30 +1016,27 @@ vty_open_config_write(vty vty, int fd)
vio = vty->vio ;
- vf = uty_vf_new(vio, "config write", fd, vfd_file, vfd_io_read) ;
-
- uty_vout_open(vio, vf, VOUT_FILE, NULL, NULL, 16 * 1024) ;
+ vf = uty_vf_new(vio, "config write", fd, vfd_file,
+ vfd_io_read | vfd_io_blocking) ;
+ uty_vout_push(vio, vf, VOUT_CONFIG, NULL, NULL, 32 * 1024) ;
VTY_UNLOCK() ;
} ;
/*------------------------------------------------------------------------------
- * Write away any pending stuff, and return the VTY to normal.
+ * Write away any pending stuff, and pop the VOUT_CONFIG.
*/
-extern int
-vty_close_config_write(struct vty* vty)
+extern cmd_return_code_t
+vty_close_config_write(struct vty* vty, bool final)
{
-// vty_io vio ;
-// int err ;
-
+ cmd_return_code_t ret ;
VTY_LOCK() ;
- uty_vout_close(vty->vio, false) ;
+ ret = uty_vout_pop(vty->vio, final) ;
VTY_UNLOCK() ;
-// return err ;
- return 0 ;
+ return ret ;
} ;
/*==============================================================================
@@ -1057,12 +1053,12 @@ DEFUN_CALL (config_who,
VTY_LOCK() ;
- vio = vio_list_base ; /* once locked */
+ vio = vio_live_list ; /* once locked */
- while (vio != NULL) /* TODO: show only VTY_TERM ??? */
+ while (vio != NULL) /* TODO: show only VTY_TERM ??? */
{
vty_out(vty, "%svty[%d] connected from %s.\n",
- vio->vty->config ? "*" : " ", i, uty_get_name(vio));
+ vio->vty->config ? "*" : " ", i, uty_get_name(vio)) ;
vio = sdl_next(vio, vio_list) ;
} ;
@@ -1071,11 +1067,12 @@ DEFUN_CALL (config_who,
}
/* Move to vty configuration mode. */
-DEFUN_CALL (line_vty,
- line_vty_cmd,
- "line vty",
- "Configure a terminal line\n"
- "Virtual terminal\n")
+DEFUN_ATTR (line_vty,
+ line_vty_cmd,
+ "line vty",
+ "Configure a terminal line\n"
+ "Virtual terminal\n",
+ CMD_ATTR_DIRECT + CMD_ATTR_NODE + VTY_NODE)
{
VTY_LOCK() ;
vty->node = VTY_NODE;
@@ -1365,32 +1362,40 @@ DEFUN_CALL (show_history,
static int
vty_config_write (struct vty *vty)
{
- vty_out (vty, "line vty\n");
+ vty_io vio ;
+
+ VTY_LOCK() ; /* while accessing the host.xxx */
+
+ vio = vty->vio ;
+
+ uty_out (vio, "line vty\n");
if (host.vty_accesslist_name)
- vty_out (vty, " access-class %s\n", host.vty_accesslist_name);
+ uty_out (vio, " access-class %s\n", host.vty_accesslist_name);
if (host.vty_ipv6_accesslist_name)
- vty_out (vty, " ipv6 access-class %s\n", host.vty_ipv6_accesslist_name);
+ uty_out (vio, " ipv6 access-class %s\n", host.vty_ipv6_accesslist_name);
/* exec-timeout */
if (host.vty_timeout_val != VTY_TIMEOUT_DEFAULT)
- vty_out (vty, " exec-timeout %ld %ld\n", host.vty_timeout_val / 60,
+ uty_out (vio, " exec-timeout %ld %ld\n", host.vty_timeout_val / 60,
host.vty_timeout_val % 60);
/* login */
if (host.no_password_check)
- vty_out (vty, " no login\n");
+ uty_out (vio, " no login\n");
if (host.restricted_mode != restricted_mode_default)
{
if (restricted_mode_default)
- vty_out (vty, " no anonymous restricted\n");
+ uty_out (vio, " no anonymous restricted\n");
else
- vty_out (vty, " anonymous restricted\n");
+ uty_out (vio, " anonymous restricted\n");
}
- vty_out (vty, "!\n");
+ uty_out (vio, "!\n");
+
+ VTY_UNLOCK() ;
return CMD_SUCCESS;
}
@@ -1400,39 +1405,22 @@ vty_config_write (struct vty *vty)
*/
/*------------------------------------------------------------------------------
- * Save cwd
- *
- * This is done early in the morning so that any future operations on files
- * can use the original cwd if required.
+ * Get cwd as at start-up. Never changed -- so no locking required.
*/
-static void
-vty_save_cwd (void)
+extern qpath
+vty_getcwd (qpath qp)
{
- char cwd[MAXPATHLEN];
- char *c;
+ VTY_LOCK() ;
- c = getcwd (cwd, MAXPATHLEN);
+ qp = qpath_copy(qp, host.cwd) ;
- if (!c)
- {
- chdir (SYSCONFDIR);
- getcwd (cwd, MAXPATHLEN);
- }
-
- host.vty_cwd = XSTRDUP(MTYPE_HOST, cwd) ;
-} ;
+ VTY_UNLOCK() ;
-/*------------------------------------------------------------------------------
- * Get cwd as at start-up. Never changed -- so no locking required.
- */
-char *
-vty_get_cwd ()
-{
- return host.vty_cwd;
+return qp ;
}
/*==============================================================================
- * Access functions for VTY values, where locking is or might be required.
+ * Access functions for vio values, where locking is or might be required.
*/
#if 0
diff --git a/lib/vty.h b/lib/vty.h
index 99b08d96..2a13ba6e 100644
--- a/lib/vty.h
+++ b/lib/vty.h
@@ -39,6 +39,7 @@
#include "list_util.h"
#include "vector.h"
#include "qstring.h"
+#include "qpath.h"
/*==============================================================================
* These are definitions and functions for things which are required outside
@@ -48,8 +49,7 @@
/*------------------------------------------------------------------------------
*
*/
-#define VTY_BUFSIZ 512
-#define VTY_MAXHIST 51
+enum { VTY_HIST_COUNT = 55 } ;
/* Integrated configuration file. */
#define INTEGRATE_DEFAULT_CONFIG "Quagga.conf"
@@ -79,7 +79,7 @@ enum { VTY_MAX_SPACES = 40 } ;
#define VTY_GET_LONG(NAME,V,STR) \
do { \
char *endptr = NULL; \
- (V) = strtoul ((STR), &endptr, 10); \
+ (V) = strtoul ((STR), &endptr, 0); \
if (*endptr != '\0' || (V) == ULONG_MAX) \
{ \
vty_out (vty, "%% Invalid %s value%s", NAME, VTY_NEWLINE); \
@@ -138,21 +138,16 @@ extern void vty_start(const char *addr, unsigned short port, const char *path) ;
#define vty_serv_sock(addr, port, path) vty_start(addr, port, path)
extern void vty_restart(const char *addr, unsigned short port,
const char *path) ;
-extern struct vty* vty_open(enum vty_type type) ;
-extern void vty_close_final(struct vty *);
-
-extern void vty_init_vtysh (void);
extern void vty_terminate (void);
extern void vty_reset (void);
extern void vty_reset_because(const char* why) ;
extern int vty_out (struct vty *, const char *, ...) PRINTF_ATTRIBUTE(2, 3);
+extern int vty_write(struct vty *vty, const void* buf, int n) ;
extern int vty_out_indent(struct vty *vty, int indent) ;
extern void vty_out_clear(struct vty *vty) ;
-
-
-
+extern void vty_sigchld(void) ;
extern void vty_read_config (const char* config_file,
const char* config_default);
@@ -161,10 +156,14 @@ extern void vty_read_config_first_cmd_special(const char* config_file,
cmd_command first_cmd,
bool ignore_warnings) ;
-extern void vty_time_print (struct vty *, int);
+extern qpath vty_getcwd(qpath qp);
-extern char *vty_get_cwd (void);
-
-extern void vty_hello (struct vty *);
+/*------------------------------------------------------------------------------
+ * vtysh
+ */
+extern void vty_hello (vty vty);
+extern struct vty* vty_open(enum vty_type type, node_type_t node) ;
+extern void vty_close_final(vty vty);
+extern void vty_init_vtysh (void);
#endif /* _ZEBRA_VTY_H */
diff --git a/lib/vty_cli.c b/lib/vty_cli.c
index 42d78349..3b82a112 100644
--- a/lib/vty_cli.c
+++ b/lib/vty_cli.c
@@ -40,6 +40,13 @@
#include "memory.h"
+/*------------------------------------------------------------------------------
+ * Essential stuff.
+ */
+#define TELNET_NEWLINE "\r\n"
+static const char* telnet_newline = TELNET_NEWLINE ;
+ const char* uty_cli_newline = TELNET_NEWLINE ;
+
/*==============================================================================
* Construct and destroy CLI object
*
@@ -49,13 +56,15 @@
static bool uty_cli_iac_callback(keystroke_iac_callback_args) ;
static void uty_cli_update_more(vty_cli cli) ;
+static void uty_cli_cancel(vty_cli cli) ;
+
/*------------------------------------------------------------------------------
* Construct and initialise a new CLI object -- never embedded.
*
* The CLI is started up in the state it is in waiting for a command to
* complete. This means that all start-up messages etc. are handled as the
- * output from an implied start command. uty_cli_start() is called when the
- * start-up messages etc. are complete.
+ * output from an implied start command. When the command loop is entered,
+ * will find its way to uty_cli_want_command().
*
* Note that the vty_io_term stuff sends out a bunch of telnet commands, which
* will be buffered in the CLI buffer. That will be output, before the
@@ -72,9 +81,10 @@ uty_cli_new(vio_vf vf)
*
* vf = NULL -- set below
*
- * hist = empty vector (embedded) -- set up when first used.
+ * hist = NULL -- set up when first used.
* hp = 0 -- hp == h_now => in the present ...
* h_now = 0 -- ... see uty_cli_hist_add()
+ * h_repeat = false -- ... see uty_cli_hist_add()
*
* width = 0 -- unknown width ) Telnet window size
* height = 0 -- unknown height )
@@ -92,46 +102,48 @@ uty_cli_new(vio_vf vf)
* drawn = false
* dirty = false
*
+ * tilde_prompt = false -- tilde prompt is not drawn
+ * tilde_enabled = false -- set below if ! multi-threaded
+ *
* prompt_len = 0 -- not drawn, in any case
* extra_len = 0 -- not drawn, in any case
*
- * echo_suppress = false
- *
* prompt_node = NULL_NODE -- so not set !
* prompt_gen = 0 -- not a generation number
- * prompt_for_node = empty qstring (embedded)
+ * prompt_for_node = NULL -- not set, yet
*
- * password_fail = 0 -- so far, so good.
- *
- * blocked = false -- see below
+ * dispatched = false -- see below
* in_progress = false -- see below
+ * blocked = false -- see below
+ * paused = false
+ *
* out_active = false
+ * flush = false
*
* more_wait = false
- * more_active = false
- *
- * out_done = false -- not that it matters
+ * more_enter = false
*
* more_enabled = false -- not in "--More--" state
*
- * node = NULL_NODE -- set in vty_cli_start()
- * to_do = cmd_do_nothing
+ * pause_timer = NULL -- set below if multi-threaded
*
+ * context = NULL -- see below
+ * context_auth = false -- set by uty_cli_want_command()
+ *
+ * parsed = NULL -- see below
+ * to_do = cmd_do_nothing
* cl = NULL qstring -- set below
+ * clo = NULL qstring -- set below
* clx = NULL qstring -- set below
+ * dispatch = all zeros -- nothing to dispatch
*
- * parsed = all zeros -- empty parsed object
- * cbuf = empty fifo (embedded)
+ * cbuf = NULL -- see below
*
- * olc = empty line control (embedded)
+ * olc = NULL -- see below
*/
- confirm(VECTOR_INIT_ALL_ZEROS) ; /* hist */
- confirm(QSTRING_INIT_ALL_ZEROS) ; /* prompt_for_node */
confirm(NULL_NODE == 0) ; /* prompt_node & node */
confirm(cmd_do_nothing == 0) ; /* to_do */
- confirm(VIO_FIFO_INIT_ALL_ZEROS) ; /* cbuf */
- confirm(VIO_LINE_CONTROL_INIT_ALL_ZEROS) ; /* olc */
- confirm(CMD_PARSED_INIT_ALL_ZEROS) ; /* parsed */
+ confirm(CMD_ACTION_ALL_ZEROS) ; /* dispatch */
cli->vf = vf ;
@@ -139,40 +151,43 @@ uty_cli_new(vio_vf vf)
cli->key_stream = keystroke_stream_new('\0', uty_cli_iac_callback, cli) ;
/* Set up cl and clx qstrings and the command line output fifo */
- cli->cl = qs_init_new(NULL, 120) ; /* reasonable line length */
- cli->clx = qs_init_new(NULL, 120) ;
+ cli->cl = qs_new(120) ; /* reasonable line length */
+ cli->cls = qs_new(120) ;
+ cli->clx = qs_new(120) ;
+
+ cli->cbuf = vio_fifo_init_new(NULL, 1000) ;
- vio_fifo_init_new(cli->cbuf, 500) ; /* just for the CLI stuff */
+ cli->olc = vio_lc_new(0 , 0, telnet_newline) ;
+
+ /* Make an empty context object and an empty parsed object */
+ cli->context = cmd_context_new() ;
+ cli->parsed = cmd_parsed_new() ;
/* Use global 'lines' setting, as default -- but 'lines_set' false. */
uty_cli_set_lines(cli, host.lines, false) ;
uty_cli_update_more(cli) ; /* and update the olc etc to suit. */
- /* Ready to be started -- out_active & more_wait are false. */
- cli->blocked = true ;
+ /* Enable "~ " prompt and pause timer if multi-threaded */
+ if (vty_nexus)
+ {
+ cli->pause_timer = qtimer_init_new(NULL, vty_cli_nexus->pile,
+ vty_term_pause_timeout, cli) ;
+ cli->tilde_enabled = vty_multi_nexus ;
+ } ;
+
+ /* Ready to be started -- paused, out_active, flush & more_wait are false.
+ *
+ * Is started by the first call of uty_cli_want_command(), which (inter alia)
+ * set the cli->context so CLI knows how to prompt etc.
+ */
+ cli->dispatched = true ;
cli->in_progress = true ;
+ cli->blocked = true ;
return cli ;
} ;
/*------------------------------------------------------------------------------
- * Start the CLI.
- *
- * The implied start "command" is complete and everything is ready to start
- * the CLI.
- *
- * Note that before releasing any pending output, calls
- *
- * Returns: write_ready -- so the first event is a write event, to flush
- * any output to date.
- */
-extern void
-uty_cli_start(vty_cli cli, node_type_t node)
-{
- uty_cli_done_command(cli, node) ; /* implied push output */
-} ;
-
-/*------------------------------------------------------------------------------
* Close CLI object, if any -- may be called more than once.
*
* Shuts down and discards anything to do with the input.
@@ -180,7 +195,7 @@ uty_cli_start(vty_cli cli, node_type_t node)
* Revokes anything that can be revoked, which may allow output to proceed
* and the vty to clear itself down.
*
- * If in_progress and not final, keeps the cbuf, the olc and clx if in_progress.
+ * If dispatched and not final, keeps the cbuf, the olc and clx if dispatched.
* If not final, keeps the cbuf and the olc.
*
* Destroys self if final.
@@ -199,11 +214,7 @@ uty_cli_close(vty_cli cli, bool final)
if (cli == NULL)
return NULL ;
- VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
-
- /* Revoke any command that is in flight. */
- cq_revoke(cli->vf->vio->vty) ;
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
/* Bring as much of the command handling to a stop as possible, and
* turn off "---more--" etc. so that output can proceed.
@@ -211,38 +222,48 @@ uty_cli_close(vty_cli cli, bool final)
cli->more_enabled = false ;
cli->lines = 0 ; /* so will not be re-enabled */
- uty_cli_wipe(cli, 0) ; /* wipe anything the CLI has on screen */
+ uty_cli_cancel(cli) ; /* " ^C\r\n" unless believed blank */
- cli->more_wait = false ; /* exit more_wait (if was in it) */
- cli->more_active = false ; /* and so cannot be this */
+ cli->more_wait = false ; /* exit more_wait (if was in it) */
+ cli->more_enter = false ; /* and so cannot be this */
- cli->out_active = true ; /* if there is any output, it can go */
+ cli->blocked = true ; /* do not renter the CLI */
+ cli->out_active = true ; /* if there is any output, it can go */
+ cli->flush = true ; /* all of it can go */
/* Ream out the history. */
{
qstring line ;
- while ((line = vector_ream(cli->hist, keep_it)) != NULL)
+ while ((line = vector_ream(cli->hist, free_it)) != NULL)
qs_reset(line, free_it) ;
+ cli->hist = NULL ;
} ;
/* Empty the keystroke handling. */
cli->key_stream = keystroke_stream_free(cli->key_stream) ;
- /* Can discard active command line if not in_progress. */
- if (!cli->in_progress || final)
+ /* Can discard active command line if not dispatched TODO ?? */
+ if (!cli->dispatched || final)
cli->clx = qs_reset(cli->clx, free_it) ;
- /* Can discard parsed object. */
- cmd_parsed_reset(cli->parsed, keep_it) ;
+ /* Can discard context and parsed objects */
+ cli->context = cmd_context_free(cli->context, true) ; /* its a copy */
+ cli->parsed = cmd_parsed_free(cli->parsed) ;
+
+ /* Discard any pause_timer, and suppress */
+ cli->pause_timer = qtimer_free(cli->pause_timer) ;
+ cli->paused = false ;
+ cli->tilde_enabled = false ;
/* If final, free the CLI object. */
if (final)
{
- qs_reset(cli->prompt_for_node, keep_it) ;
- cli->cl = qs_reset(cli->cl, free_it) ;
+ cli->prompt_for_node = qs_reset(cli->prompt_for_node, free_it) ;
+ cli->cl = qs_reset(cli->cl, free_it) ;
+ cli->cls = qs_reset(cli->cls, free_it) ;
- vio_fifo_reset(cli->cbuf, keep_it) ; /* embedded fifo */
- vio_lc_reset(cli->olc, keep_it) ; /* embedded line control */
+ cli->cbuf = vio_fifo_reset(cli->cbuf, free_it) ;
+ cli->olc = vio_lc_reset(cli->olc, free_it) ;
XFREE(MTYPE_VTY_CLI, cli) ; /* sets cli = NULL */
} ;
@@ -373,12 +394,18 @@ uty_cli_update_more(vty_cli cli)
* consider it for read_ready or write_ready, respectively.
*
* All command actions are dispatched via the command queue -- commands are
- * not executed in the cli. [For legacy threads the event queue is used. If
- * that is too slow, a priority event queue will have to be invented.]
+ * not executed inside the CLI code. [For legacy threads the event queue is
+ * used. If that is too slow, a priority event queue will have to be
+ * invented.]
*
* State of the CLI:
*
- * in_progress -- a command has been dispatched and has not yet completed.
+ * dispatched -- a command line has been dispatched, and is waiting for
+ * (dsp) command loop to fetch it.
+ *
+ * in_progress -- a command has been taken by the command loop, and is still
+ * (inp) running -- or at least no further command has been
+ * fetched by the command loop.
*
* The command may be a in_pipe, so there may be many commands
* to be completed before the CLI level command is.
@@ -386,36 +413,48 @@ uty_cli_update_more(vty_cli cli)
* or: the CLI has been closed.
*
* blocked -- is in_progress and a further command is now ready to be
- * dispatched.
+ * (bkd) dispatched.
*
* or: the CLI has been closed.
*
* out_active -- the command output FIFO is being emptied.
- *
- * This is set when a command completes, and cleared when
- * everything is written away.
- *
- * Note that in this context a command is an individual
- * command -- where in_progress may cover any number of
- * command if the CLI level command is an in_pipe.
- *
- * more_wait -- is in "--more--" wait state
- *
- * more_active -- is in "--more--" wait state, and the "--more--" prompt
- * has not been written away, yet.
+ * (oa)
+ * This is set when output is pushed, and cleared when
+ * everything is written away and flush is set. When it
+ * is set, any current command line is wiped.
+ *
+ * Note that what this flag does is prevent the CLI from
+ * running until the output completes, and in particular
+ * prevents it from writing anything to the CLI buffer.
+ *
+ * flush -- is set when the CLI is ready for the next command (so
+ * (fsh) when in_progress is cleared) to cause any incomplete
+ * command output to be flushed, and to signal that
+ * out_active should be cleared when all output is complete.
+ *
+ * more_wait -- is in "--more--" wait state. => out_active !
+ * (mwt)
+ * The "--more--" CLI uses the CLI output buffer to draw
+ * and undraw the "--more--" prompt. The buffer will
+ * otherwise be empty because is out_active.
+ *
+ * more_enter -- is in the process of entering the "--more--" wait state,
+ * (men) waiting to write the "--more--" prompt and prepare the
+ * keystroke input.
*
* The following are the valid combinations:
*
- * in_p: blkd: o_ac: m_wt: m_ac:
- * ----:-----:-----:-----:-----:-----------------------------------------
- * 0 : 0 : 0 : 0 : 0 : collecting a new command
- * 0 : 0 : 1 : 0 : 0 : waiting for command output to finish
- * 1 : 0 : X : 0 : 0 : command dispatched
- * 1 : 1 : X : 0 : 0 : waiting for command to complete
- * X : X : 0 : 1 : 1 : waiting for "--more--" to be written away
- * X : X : 0 : 1 : 0 : waiting for "--more--" response
- * 1 : 1 : 1 : 0 : 0 : waiting for command to complete, after the
- * CLI has been closed
+ * dsp:inp:bkd: oa:fsh:mwt:men:
+ * ---:---:---:---:---:---:---:-----------------------------------------
+ * 0 : 0 : 0 : 0 : 0 : 0 : 0 : collecting a new command
+ * 1 : 0 : 0 : 0 : 0 : 0 : 0 : waiting for command to be fetched
+ * 1 : 1 : 0 : X : 0 : 0 : 0 : command fetched and running
+ * 1 : 1 : 1 : X : 0 : 0 : 0 : waiting for command to complete
+ * 0 : 0 : 0 : 1 : 1 : 0 : 0 : waiting for command output to finish
+ * 1 : 1 : X : 1 : X : 1 : 1 : waiting for "--more--" to start
+ * 1 : 1 : X : 1 : X : 1 : 0 : waiting for "--more--" response
+ * 1 : 1 : 1 : 1 : 1 : 0 : 0 : waiting for command to complete,
+ * after the CLI has been closed
*
* There are two output FIFOs:
*
@@ -426,7 +465,17 @@ uty_cli_update_more(vty_cli cli)
* The CLI FIFO is emptied whenever possible, in preference to the command
* FIFO. The command FIFO is emptied when out_active. While a command is
* in_progress all its output is collected in the command output buffer,
- * to be written away when the command completes.
+ * to be written away when the command completes, or if it pushes the output
+ * explicitly. Note that where the CLI level command is a pipe-in, the first
+ * output will set out_active, and that will persist until returns to the CLI
+ * level. Between commands in the pipe, the output will be pushed, so will
+ * output stuff more or less as it is generated.
+ *
+ * It is expected that each command's output will end with a newline. However,
+ * the line control imposes some discipline, and holds on to incomplete lines
+ * until a newline arrives, or the output if flushed. -- so that when the
+ * CLI is kicked, the cursor will be at the start of an empty line.
+ *
*
* Note that only sets read on when the keystroke stream is empty and has not
* yet hit eof. The CLI process is driven mostly by write_ready -- which
@@ -436,32 +485,20 @@ uty_cli_update_more(vty_cli cli)
* re-entered again on write_ready/read_ready -- so does one command line at
* a time, yielding the processor after each one.
*
- * Note that select/pselect treat a socket which is at "EOF", or has seen an
- * error, or has been half closed, etc. as readable and writable. This means
- * that the CLI will continue to move forward even after the socket is no
- * longer delivering any data.
- *
*------------------------------------------------------------------------------
* The "--more--" handling.
*
* This is largely buried in the output handling.
*
- * When a command completes and its output is pushed to written away,
- * out_active will be set (and any command line will be wiped). The output
- * process is then kicked.
+ * When command is pushed to written away, out_active will be set (and any
+ * command line will be wiped). The output process is then kicked.
*
* The output process used the line_control structure to manage the output, and
* occasionally enter the trivial "--more--" CLI. This is invisible to the
- * main CLI. (See the more_wait and more_active flags and their handling.)
- *
- * When all the output has completed the out_active flag is cleared and the CLI
- * will be kicked.
- *
- * It is expected that the output will end with a newline -- so that when the
- * CLI is kicked, the cursor will be at the start of an empty line.
+ * main CLI. (See the more_wait and more_enter flags and their handling.)
*
* If the user decides to abandon output at the "--more--" prompt, then the
- * contents of the command output FIFO are discarded.
+ * then contents of the command output FIFO are discarded.
*
*------------------------------------------------------------------------------
* The qpthreads/qpnexus extension.
@@ -512,9 +549,9 @@ uty_cli_update_more(vty_cli cli)
#define CONTROL(X) ((X) & 0x1F)
static enum vty_readiness uty_cli_standard(vty_cli cli) ;
+static cmd_do_t uty_cli_auth(vty_cli cli) ;
static enum vty_readiness uty_cli_more_wait(vty_cli cli) ;
static void uty_cli_draw(vty_cli cli) ;
-static void uty_cli_draw_this(vty_cli cli, node_type_t node) ;
/*------------------------------------------------------------------------------
* CLI for VTY_TERMINAL
@@ -534,8 +571,8 @@ uty_cli(vty_cli cli)
{
VTY_ASSERT_LOCKED() ;
- if (cli->vf->vin_state != vf_open)
- return not_ready ; /* Nothing more from CLI if closing */
+ if (cli->vf->vin_state == vf_closed)
+ return not_ready ; /* Nothing more from CLI if closed */
/* Standard or "--more--" CLI ? */
if (cli->more_wait)
@@ -548,14 +585,14 @@ uty_cli(vty_cli cli)
* The Standard CLI
*/
-static cmd_do_t uty_cli_process(vty_cli cli, node_type_t node) ;
+static cmd_do_t uty_cli_process(vty_cli cli) ;
static void uty_cli_response(vty_cli cli, cmd_do_t cmd_do) ;
-
-static void uty_cli_dispatch(vty_cli cli) ;
+static bool uty_cli_dispatch(vty_cli cli) ;
static cmd_do_t uty_cli_command(vty_cli cli) ;
static void uty_cli_hist_add (vty_cli cli, qstring clx) ;
+static void uty_cli_pause_start(vty_cli cli) ;
/*------------------------------------------------------------------------------
* Standard CLI for VTY_TERM -- if not blocked, runs until:
@@ -574,6 +611,8 @@ static void uty_cli_hist_add (vty_cli cli, qstring clx) ;
static vty_readiness_t
uty_cli_standard(vty_cli cli)
{
+ bool need_prompt ;
+
VTY_ASSERT_LOCKED() ;
assert(!cli->more_wait) ; /* cannot be here in more_wait state ! */
@@ -584,22 +623,24 @@ uty_cli_standard(vty_cli cli)
* NB: in both these cases, assumes that other forces are at work to
* keep things moving.
*/
- if (cli->blocked || cli->out_active)
+ if (cli->blocked || cli->out_active || cli->paused || cli->mon_active)
return not_ready ;
- /* If there is nothing pending, then can run the CLI until there is
- * something to do, or runs out of input.
+ /* Make sure that the command line is drawn.
*
- * If there is something to do, that is because a previous command has
- * now completed, which may have wiped the pending command or changed
- * the required prompt.
+ * If there is nothing pending, then can run the CLI until there is
+ * something to do, or runs out of input.
*/
+ if (!cli->drawn)
+ uty_cli_draw(cli) ;
+
if (cli->to_do == cmd_do_nothing)
- cli->to_do = uty_cli_process(cli, cli->node) ;
- else
- uty_cli_draw_this(cli, cli->node) ;
+ cli->to_do = cli->auth_context ? uty_cli_auth(cli)
+ : uty_cli_process(cli) ;
/* If have something to do, do it if we can. */
+ need_prompt = false ;
+
if (cli->to_do != cmd_do_nothing)
{
/* Reflect immediate response */
@@ -610,25 +651,46 @@ uty_cli_standard(vty_cli cli)
*
* Otherwise is now blocked until queued command completes.
*/
- if (cli->in_progress)
- cli->blocked = true ; /* blocked waiting for previous */
+ if (cli->dispatched)
+ cli->blocked = true ; /* waiting for previous */
else
- uty_cli_dispatch(cli) ; /* can dispatch the latest */
+ need_prompt = uty_cli_dispatch(cli) ; /* dispatch latest */
} ;
- /* Use write_ready as a proxy for read_ready on the keystroke stream.
+ /* If blocked, must wait for some other event to continue in CLI.
+ *
+ * Note that will be blocked if have just dispatched a command, and is
+ * "tilde_enabled" -- which will be true if single threaded, and may be
+ * set for other reasons.
*
- * Also, if the command line is not drawn, then return write_ready, so
- * that
+ * If the keystroke stream is not empty, use write_ready as a proxy for
+ * CLI ready -- no point doing anything until any buffered.
*
- * Note that if has just gone cli_blocked, still returns ready. This is
- * defensive: at worst will generate one unnecessary read_ready/write_ready
- * event.
+ * If command prompt has been redrawn, need to kick writer to deal with
+ * that -- will reenter to then process any keystrokes.
*/
- if (keystroke_stream_empty(cli->key_stream))
- return read_ready ;
- else
+ assert(!cli->paused) ;
+
+ if (cli->blocked)
+ return not_ready ;
+
+ if (!keystroke_stream_empty(cli->key_stream) || need_prompt)
return write_ready ;
+
+ /* The keystroke stream is empty, but CLI is not blocked.
+ *
+ * If a command is dispatched, and output is not active, and the command
+ * line is not drawn, then go to paused state, so that will delay output
+ * of the prompt slightly (or until a keystroke arrives, or the current
+ * command completes, or something else happens).
+ *
+ * Note that if a command has been dispatched, and is !tilde_enabled, then
+ * will now be blocked.
+ */
+ if (cli->dispatched && !cli->out_active && !cli->drawn)
+ uty_cli_pause_start(cli) ;
+
+ return read_ready ;
} ;
/*------------------------------------------------------------------------------
@@ -642,8 +704,10 @@ uty_cli_standard(vty_cli cli)
* Generally sets cli->to_do = cmd_do_nothing and clears cli->cl to empty.
*
* Can set cli->cl_do = and cli->cl to be a follow-on command.
+ *
+ * Returns: true <=> nothing, in fact, to do: prompt has been redrawn.
*/
-static void
+static bool
uty_cli_dispatch(vty_cli cli)
{
qstring tmp ;
@@ -654,16 +718,17 @@ uty_cli_dispatch(vty_cli cli)
VTY_ASSERT_LOCKED() ;
/* About to dispatch a command, so must be in the following state. */
- assert(!cli->in_progress && !cli->out_active && !cli->blocked) ;
- assert(cli->node == vio->vty->node) ;
+ assert(!cli->dispatched && !cli->in_progress
+ && !cli->blocked && !cli->out_active) ;
+ qassert(cli->context->node == vio->vty->exec->context->node) ;
/* Set cli->clx to the command about to execute & pick up cli->to_do.
*
* Clear cli->cl and cli->to_do.
*/
- tmp = cli->clx ; /* swap clx and cl */
- cli->clx = cli->cl ;
- cli->cl = tmp ;
+ tmp = cli->clx ; /* swap clx and cl */
+ cli->clx = cli->cl ;
+ cli->cl = tmp ;
to_do_now = cli->to_do ; /* current operation */
@@ -674,10 +739,8 @@ uty_cli_dispatch(vty_cli cli)
//uty_out_clear(cli->vio) ; /* clears FIFO and line control */
/* Dispatch command */
- if ((cli->node == AUTH_NODE) && (to_do_now != cmd_do_nothing))
+ if (cli->auth_context)
to_do_now |= cmd_do_auth ;
- else if ((cli->node == AUTH_ENABLE_NODE) && (to_do_now != cmd_do_nothing))
- to_do_now |= cmd_do_auth_enable ;
else
{
/* All other nodes... */
@@ -686,6 +749,7 @@ uty_cli_dispatch(vty_cli cli)
case cmd_do_nothing:
case cmd_do_ctrl_c:
case cmd_do_eof:
+ case cmd_do_timed_out:
break ;
case cmd_do_command:
@@ -715,28 +779,34 @@ uty_cli_dispatch(vty_cli cli)
if (to_do_now != cmd_do_nothing)
{
- cli->in_progress = true ;
- uty_cmd_dispatch(vio, to_do_now, cli->clx) ;
+ cmd_action_set(cli->dispatch, to_do_now, cli->clx) ;
+ cli->dispatched = true ;
+
+ uty_cmd_signal(vio, CMD_SUCCESS) ;
+
+ cli->blocked = (to_do_now != cmd_do_command) || !cli->tilde_enabled ;
}
else
- uty_cli_draw(cli) ;
+ {
+ cmd_action_clear(cli->dispatch) ;
+ cli->dispatched = false ;
+
+ uty_cli_draw(cli) ;
+ } ;
+
+ return !cli->dispatched ;
} ;
/*------------------------------------------------------------------------------
- * Enqueue command -- adding to history -- if is not empty or just comment
+ * Check if command is empty, if not add to history
*
- * This is for VTY_TERM type VTY.
- *
- * Returns: CMD_SUCCESS => empty command line (or just comment)
- * CMD_WAITING => enqueued for parse and execute
+ * Returns: cmd_do_nothing -- empty command line
+ * cmd_do_command -- command ready to be executed (added to history)
*/
static cmd_do_t
uty_cli_command(vty_cli cli)
{
- VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
-
- if (cmd_is_empty(qs_els_nn(cli->clx)))
+ if (cmd_is_empty(cli->clx))
return cmd_do_nothing ; /* easy when nothing to do ! */
/* Add not empty command to history */
@@ -746,196 +816,79 @@ uty_cli_command(vty_cli cli)
} ;
/*------------------------------------------------------------------------------
- * A command has completed, and there is (or may be) some output to now
- * write away.
- */
-extern void
-uty_cli_out_push(vty_cli cli)
-{
- VTY_ASSERT_LOCKED() ;
-
- if (cli->more_wait)
- return ; /* can do nothing */
-
- if (vio_fifo_empty(cli->vf->obuf))
- {
- assert(!cli->out_active ) ;
- return ; /* need do nothing if is empty */
- } ;
-
- uty_cli_wipe(cli, 0) ; /* wipe any partly constructed line */
-
- cli->out_active = true ; /* enable the output */
-
- uty_term_set_readiness(cli->vf, write_ready) ;
- /* kick the write side into action */
-} ;
-
-/*------------------------------------------------------------------------------
- * Queued command has completed.
- *
- * Note that sets write on whether there is anything in the output buffer
- * or not... write_ready will kick read_ready.
-
-
- * Command has completed, so:
- *
- * * clear cmd_in_progress
- * * set cmd_out_active -- so any output can now proceed
- * * set cli_blocked -- waiting for output to complete
- * * and prepare the line control for output
- *
- * If the return is CMD_CLOSE, then also now does the required half close.
- *
- * Note that apart from CMD_CLOSE, don't really care what the return was. Any
- * diagnostics or other action must be dealt with elsewhere (as part of the
- * command execution.
- *
- * Note that everything proceeds as if there is some output. So after every
- * command goes through at least one write_ready event.
- *
- * This ensures some multiplexing at the command level.
+ * Want another command line from the CLI.
*
- * It also means that the decision about whether there is anything to output
- * is left to the output code.
-
-
-
- */
-extern void
-uty_cli_done_command(vty_cli cli, node_type_t node)
-{
- VTY_ASSERT_LOCKED() ;
-
- uty_cli_out_push(cli) ; /* just in case */
-
- cli->in_progress = false ; /* command complete */
- cli->blocked = false ; /* releases possibly blocked command */
- cli->node = node ; /* and now in this node */
-
- /* Reach all the way back, and get the now current node. */
- cli->node = cli->vf->vio->vty->node ;
-
- uty_term_set_readiness(cli->vf, write_ready) ;
- /* kick the write side into action */
-} ;
-
-/*==============================================================================
- * All commands are dispatched to command_queue.
+ * If in_progress this <=> any previous command has completed, and output may
+ * be active writing the results away. Will:
*
- * Some commands are VLI specific... and end up back here.
- */
-
-/*------------------------------------------------------------------------------
- * Authentication of vty
+ * * clear cli->dispatched
+ * * clear cli->in_progress
+ * * clear cli->blocked
*
- * Note that if the AUTH_NODE password fails too many times, the terminal is
- * closed.
+ * May be in more_wait state -- so avoids touching that.
*
- * Returns: CMD_SUCCESS -- OK, one way or another
- * CMD_WARNING -- with error message sent to output
- * CMD_CLOSE -- too many password failures
+ * If not in_progress, then if dispatched, that is a new command ready to pass
+ * to the command loop -- which we do here, and set cli->in_progress.
*/
extern cmd_return_code_t
-uty_cli_auth(vty_cli cli)
+uty_cli_want_command(vty_cli cli, cmd_action action, cmd_context context)
{
- char *crypt (const char *, const char *);
-
- char* passwd = NULL ;
- bool encrypted = false ;
- node_type_t next_node = 0 ;
- cmd_return_code_t ret ;
- vty_io vio ;
- cmd_exec exec ;
-
VTY_ASSERT_LOCKED() ;
- vio = cli->vf->vio ;
- exec = vio->vty->exec ;
-
- /* Deal with the exotic terminators. */
- switch (exec->to_do & cmd_do_mask)
- {
- case cmd_do_nothing:
- case cmd_do_ctrl_c:
- case cmd_do_ctrl_z:
- return CMD_SUCCESS ;
-
- case cmd_do_command:
- break ;
-
- case cmd_do_ctrl_d:
- case cmd_do_eof:
- return uty_cmd_close(vio, "End") ;
-
- default:
- zabort("unknown or invalid cmd_do") ;
- } ;
-
- /* Ordinary command dispatch -- see if password is OK.
- *
- * Select the password we need to check against.
- */
- if ((exec->to_do & ~cmd_do_mask) == cmd_do_auth)
+ if (cli->in_progress)
{
- passwd = host.password ;
- encrypted = host.password_encrypted ;
+ /* Previous command has completed
+ *
+ * Make sure state reflects the fact that we are now waiting for a
+ * command.
+ */
+ cli->dispatched = false ; /* no longer have a dispatched command */
+ cli->in_progress = false ; /* command complete */
+ cli->blocked = false ; /* releases possibly blocked command */
+ cli->paused = false ; /* override paused state */
+
+ *cli->context = *context ; /* make sure is up to date */
+ cli->auth_context = ( (cli->context->node == AUTH_NODE)
+ || (cli->context->node == AUTH_ENABLE_NODE) ) ;
- if (host.advanced)
- next_node = host.enable ? VIEW_NODE : ENABLE_NODE;
+ /* If the output is owned by command output, then set flush flag, so
+ * that when buffers empty, the output will be released.
+ *
+ * If the output side is not owned by command output, wipe any temporary
+ * prompt.
+ *
+ * In any case, kick write_ready to ensure output clears and prompt is
+ * written and so on.
+ */
+ if (cli->out_active)
+ cli->flush = true ;
else
- next_node = VIEW_NODE;
- }
- else if ((exec->to_do & ~cmd_do_mask) == cmd_do_auth_enable)
- {
- passwd = host.enable ;
- encrypted = host.enable_encrypted ;
+ uty_cli_draw(cli) ;
- next_node = ENABLE_NODE;
+ uty_term_set_readiness(cli->vf, write_ready) ;
}
- else
- zabort("unknown to_do_value") ;
-
- /* Check against selected password (if any) */
- if (passwd != NULL)
+ else if (cli->dispatched)
{
- char* candidate = qs_make_string(exec->line) ;
-
- if (encrypted)
- candidate = crypt(candidate, passwd) ;
-
- if (strcmp(candidate, passwd) == 0)
- {
- cli->password_fail = 0 ; /* forgive any recent failures */
- vio->vty->node = next_node;
+ /* New command has been dispatched -- can now pass that to the
+ * command loop -- setting it in_progress.
+ */
+ assert(cli->dispatch->to_do != cmd_do_nothing) ;
+ cmd_action_take(action, cli->dispatch) ;
- return CMD_SUCCESS ; /* <<< SUCCESS <<<<<<<< */
- } ;
+ cli->in_progress = true ;
} ;
- /* Password failed -- or none set ! */
- cli->password_fail++ ;
-
- ret = CMD_SUCCESS ; /* OK so far */
-
- if (cli->password_fail >= 3)
- {
- if ((exec->to_do & ~cmd_do_mask) == cmd_do_auth)
- {
- ret = uty_cmd_close(vio, "Bad passwords, too many failures!") ;
- }
- else
- {
- /* AUTH_ENABLE_NODE */
- cli->password_fail = 0 ; /* allow further attempts */
- uty_out(vio, "%% Bad enable passwords, too many failures!\n") ;
- vio->vty->node = host.restricted_mode ? RESTRICTED_NODE
- : VIEW_NODE ;
- ret = CMD_WARNING ;
- } ;
- } ;
+ return cli->in_progress ? CMD_SUCCESS : CMD_WAITING ;
+} ;
- return ret ;
+/*------------------------------------------------------------------------------
+ * Start pause timer and set paused.
+ */
+static void
+uty_cli_pause_start(vty_cli cli)
+{
+ qtimer_set(cli->pause_timer, qt_add_monotonic(QTIME(0.2)), NULL) ;
+ cli->paused = true ;
} ;
/*==============================================================================
@@ -944,13 +897,13 @@ uty_cli_auth(vty_cli cli)
* While command output is being cleared from its FIFO, the CLI is blocked.
*
* When the output side signals that "--more--" is required, it sets the
- * more_wait flag and clears the out_active flag.
+ * more_wait and more_enter flags.
*
* The first stage of handling "--more--" is to suck the input dry, so that
* (as far as is reasonably possible) does not steal a keystroke as the
* "--more--" response which was typed before the prompt was issued.
*
- * The cli_blocked flag indicates that the CLI is in this first stage.
+ * The more_enter flag indicates that the CLI is in this first stage.
*/
/*------------------------------------------------------------------------------
@@ -963,49 +916,10 @@ uty_cli_enter_more_wait(vty_cli cli)
{
VTY_ASSERT_LOCKED() ;
- assert(cli->out_active && !cli->more_wait) ;
-
- uty_cli_wipe(cli, 0) ; /* make absolutely sure that command line is
- wiped before change the CLI state */
+ assert(cli->out_active && !cli->more_wait && !cli->drawn) ;
- cli->out_active = false ; /* stop output pro tem */
- cli->more_wait = true ; /* new state */
- cli->more_active = true ; /* drawing the "--more--" */
-
- uty_cli_draw(cli) ; /* draw the "--more--" */
-} ;
-
-/*------------------------------------------------------------------------------
- * Exit the "--more--" CLI.
- *
- * Wipes the "--more--" prompt.
- *
- * This is used when the user responds to the prompt.
- *
- * It is also used when the vty is "half-closed". In this case, it is (just)
- * possible that the '--more--' prompt is yet to be completely written away,
- * so:
- *
- * * assert that is either: !cli->blocked (most of the time it will)
- * or: !vio_fifo_empty(cli->cbuf)
- *
- * * note that can wipe the prompt even though it hasn't been fully
- * written away yet. (The effect is to append the wipe action to the
- * cli_obuf !)
- */
-extern void
-uty_cli_exit_more_wait(vty_cli cli)
-{
- VTY_ASSERT_LOCKED() ;
-
- assert(cli->more_wait) ;
-
- uty_cli_wipe(cli, 0) ; /* wipe the prompt ('--more--')
- before changing the CLI state */
-
- cli->more_wait = false ; /* exit more_wait */
- cli->more_active = false ; /* tidy */
- cli->out_active = true ; /* re-enable output */
+ cli->more_wait = true ; /* new state */
+ cli->more_enter = true ; /* drawing the "--more--" etc. */
} ;
/*------------------------------------------------------------------------------
@@ -1018,92 +932,184 @@ uty_cli_exit_more_wait(vty_cli cli)
*
* EOF on input causes immediate exit from cli_more_state.
*
- * Returns: read_ready -- waiting to steal a keystroke
- * now_ready -- just left cli_more_wait
- * not_ready -- otherwise
+ * Returns: read_ready -- waiting to steal a keystroke
+ * write_ready -- waiting to draw or undraw the "--more--" prompt
+ * not_ready -- otherwise
*/
static vty_readiness_t
uty_cli_more_wait(vty_cli cli)
{
- struct keystroke steal ;
+ keystroke_t steal ;
+ bool cancel ;
VTY_ASSERT_LOCKED() ;
assert(cli->more_wait) ; /* must be in more_wait state ! */
+ if (cli->paused || cli->mon_active)
+ return not_ready ;
+
/* Deal with the first stage of "--more--" */
- if (cli->more_active)
+ if (cli->more_enter)
{
int get ;
+ uty_cli_draw_if_required(cli) ; /* draw the "--more--" */
+
/* If the CLI buffer is not yet empty, then is waiting for the
* initial prompt to clear, so nothing to be done here.
*/
if (!vio_fifo_empty(cli->cbuf))
return write_ready ;
- cli->more_active = false ;
+ cli->more_enter = false ;
/* empty the input buffer into the keystroke stream */
do
{
get = uty_term_read(cli->vf, NULL) ;
} while (get > 0) ;
+
+ return read_ready ;
} ;
/* Go through the "--more--" process, unless closing */
- if (cli->vf->vout_state == vf_open)
+ /* The read fetches a reasonable lump from the I/O -- so if there
+ * is a complete keystroke available, expect to get it.
+ *
+ * If no complete keystroke available to steal, returns ks_null.
+ *
+ * If has hit EOF or timeout (or error etc), returns knull_eof.
+ */
+ uty_term_read(cli->vf, steal) ;
+
+ /* If nothing stolen, make sure prompt is drawn and wait for more
+ * input.
+ *
+ * If anything at all has been stolen, then continue or cancel.
+ */
+ cancel = false ;
+ switch(steal->type)
{
- /* The read fetches a reasonable lump from the I/O -- so if there
- * is a complete keystroke available, expect to get it.
- *
- * If no complete keystroke available to steal, returns ks_null.
- *
- * If has hit EOF (or error etc), returns knull_eof.
- */
- uty_term_read(cli->vf, &steal) ;
+ case ks_null:
+ switch(steal->value)
+ {
+ case knull_not_eof:
+ // TODO need to refresh "--more--" in case of monitor ??
+ return read_ready; /* <<< exit: no keystroke */
+ break ;
- /* If nothing stolen, make sure prompt is drawn and wait for more
- * input.
- */
- if ((steal.type == ks_null) && (steal.value != knull_eof))
- {
- if (uty_cli_draw_if_required(cli)) /* "--more--" if req. */
- {
- cli->more_active = true ; /* written "--more--" again */
- return write_ready ;
- }
- else
- return read_ready ;
- } ;
+ case knull_eof:
+ case knull_timed_out:
+ cancel = true ;
+ break ;
- /* Stolen a keystroke -- a (very) few terminate all output */
- if (steal.type == ks_char)
- {
- switch (steal.value)
+ default:
+ break ;
+ } ;
+ break ;
+
+ case ks_char:
+ switch (steal->value)
{
case CONTROL('C'):
case 'q':
case 'Q':
- uty_out_clear(cli->vf->vio) ;
+ cancel = true ;
break;
- default: /* everything else, thrown away */
+ default:
break ;
} ;
- } ;
+ break ;
+
+ default:
+ break ;
} ;
/* End of "--more--" process
*
- * Wipe out the prompt and update state.
+ * Wipe out the prompt (unless "cancel") and update state.
*
* Return write_ready to tidy up the screen and, unless cleared, write
* some more.
*/
- uty_cli_exit_more_wait(cli) ;
+ if (cancel)
+ {
+ uty_out_clear(cli->vf->vio) ;
+ vio_lc_clear(cli->olc) ; /* clear & reset counter */
+ uty_cli_cancel(cli) ;
+ }
+ else
+ {
+ vio_lc_counter_reset(cli->olc) ;
+ uty_cli_wipe(cli, 0) ;
+ } ;
+
+ cli->more_wait = false ; /* exit more_wait */
+ cli->more_enter = false ; /* tidy */
+
+ return write_ready ;
+} ;
+
+/*==============================================================================
+ * Monitor output.
+ *
+ * To prepare for monitor output, wipe as much as is necessary for the
+ * monitor line to appear correctly.
+ *
+ * After monitor output, may need to do two things:
+ *
+ * * if the output was incomplete, place the rump in the CLI buffer,
+ * so that:
+ *
+ * a. don't mess up the console with partial lines
+ *
+ * b. don't lose part of a message
+ *
+ * c. act as a brake on further monitor output -- cannot do any more
+ * until the last, part, line is dealt with.
+ *
+ * * restore the command line, unless it is empty !
+ */
+
+ /*-----------------------------------------------------------------------------
+ * Prepare for new monitor output line.
+ *
+ * Wipe any existing command line.
+ */
+extern void
+uty_cli_pre_monitor(vty_cli cli)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ uty_cli_wipe(cli, 0) ;
+
+ cli->mon_active = true ; /* block cli & enable empty of fifo */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Recover from monitor line output.
+ *
+ * If monitor line failed to complete, append the rump to the CLI buffer.
+ *
+ * If have a non-empty command line, or is cli_more_wait, redraw the command
+ * line.
+ *
+ * Returns: 0 => rump was empty and no command line stuff written
+ * > 0 => rump not empty or some command line stuff written
+ */
+extern void
+uty_cli_post_monitor(vty_cli cli)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ uty_cli_pause_start(cli) ; /* do not draw prompt immediately */
- return now_ready ;
+ cli->more_enter = cli->more_wait ;
+ /* revert to more_enter state */
+
+ cli->mon_active = false ; /* unblock cli & turn off output */
} ;
/*==============================================================================
@@ -1116,14 +1122,9 @@ uty_cli_more_wait(vty_cli cli)
*
* It is expected that can flush straight to the file, since this is running at
* CLI speed. However, if the CLI is being driven by something other than a
- * keyboard, or "monitor" output has filled the buffers, then may need to
- * have intermediate buffering.
+ * keyboard then may need to have intermediate buffering.
*
* No actual I/O takes place here-- all "output" is to cli->cbuf
- *
- * The "cli_echo" functions discard the output if cli->echo_suppress.
- * This is used while passwords are entered and to allow command line changes
- * to be made while the line is not visible.
*/
enum { cli_rep_count = 32 } ;
@@ -1142,12 +1143,11 @@ CONFIRM(sizeof(telnet_backspaces) == sizeof(cli_rep_char)) ;
/* 12345678901234567890123456789012 */
static cli_rep telnet_spaces = " " ;
static cli_rep telnet_dots = "................................" ;
+static cli_rep telnet_stars = "********************************" ;
CONFIRM(sizeof(telnet_spaces) == (sizeof(cli_rep_char) + 1)) ;
CONFIRM(sizeof(telnet_dots) == (sizeof(cli_rep_char) + 1)) ;
-static const char* telnet_newline = "\r\n" ;
-
static void uty_cli_write_n(vty_cli cli, cli_rep_char chars, int n) ;
/*------------------------------------------------------------------------------
@@ -1175,41 +1175,11 @@ uty_cli_out_clear(vty_cli cli)
} ;
/*------------------------------------------------------------------------------
- * CLI VTY output -- echo user input
- *
- * Do nothing if echo suppressed (eg in AUTH_NODE) or not write_open
- */
-static void
-uty_cli_echo(vty_cli cli, const char *this, size_t len)
-{
- VTY_ASSERT_LOCKED() ;
-
- if (!cli->echo_suppress)
- uty_cli_write(cli, this, len) ;
-}
-
-/*------------------------------------------------------------------------------
- * CLI VTY output -- echo 'n' characters using a cli_rep_char string
- *
- * Do nothing if echo suppressed (eg in AUTH_NODE)
- */
-static void
-uty_cli_echo_n(vty_cli cli, cli_rep_char chars, int n)
-{
- VTY_ASSERT_LOCKED() ;
-
- if (!cli->echo_suppress)
- uty_cli_write_n(cli, chars, n) ;
-}
-
-/*------------------------------------------------------------------------------
* CLI VTY output -- cf write()
*/
extern void
uty_cli_write(vty_cli cli, const char *this, int len)
{
- VTY_ASSERT_LOCKED() ;
-
vio_fifo_put_bytes(cli->cbuf, this, len) ;
} ;
@@ -1249,8 +1219,9 @@ uty_cli_write_s(vty_cli cli, const char *str)
} ;
/*==============================================================================
- * Standard Messages
+ * Prompts and responses
*/
+static void uty_cli_goto_end_if_drawn(vty_cli cli) ;
/*------------------------------------------------------------------------------
* Send newline to the console.
@@ -1260,6 +1231,8 @@ uty_cli_write_s(vty_cli cli, const char *str)
extern void
uty_cli_out_newline(vty_cli cli)
{
+ uty_cli_goto_end_if_drawn(cli) ;
+
uty_cli_write(cli, telnet_newline, 2) ;
cli->drawn = false ;
cli->dirty = false ;
@@ -1288,6 +1261,42 @@ uty_cli_out_wipe_n(vty_cli cli, int n)
} ;
/*------------------------------------------------------------------------------
+ * If the command line is drawn, then show that it has been cancelled.
+ *
+ * If the command line is dirty, then cancel it and start new line.
+ *
+ * Sets: cli_drawn = false
+ * cli_dirty = false
+ */
+static void
+uty_cli_cancel(vty_cli cli)
+{
+ if (cli->drawn || cli->dirty)
+ {
+ uty_cli_goto_end_if_drawn(cli) ;
+ uty_cli_write_s(cli, " ^C" TELNET_NEWLINE) ;
+ } ;
+
+ cli->drawn = false ;
+ cli->dirty = false ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If the command line is drawn and the cursor is not at the end of the line,
+ * move the physical cursor to the end of the line.
+ *
+ * Assumes about to issue newline.
+ */
+static void
+uty_cli_goto_end_if_drawn(vty_cli cli)
+{
+ ulen after ;
+
+ if (cli->drawn && ( (after = qs_after_cp_nn(cli->cl)) != 0 ))
+ uty_cli_write(cli, qs_cp_char_nn(cli->cl), after) ;
+} ;
+
+/*------------------------------------------------------------------------------
* Send response to the given cmd_do
*
* If no command is in progress, then will send newline to signal that the
@@ -1299,18 +1308,20 @@ uty_cli_out_wipe_n(vty_cli cli, int n)
static const char* cli_response [2][cmd_do_count] =
{
{ /* when not waiting for previous command to complete */
- [cmd_do_command] = "",
- [cmd_do_ctrl_c] = "^C",
- [cmd_do_ctrl_d] = "^D",
- [cmd_do_ctrl_z] = "^Z",
- [cmd_do_eof] = "^*"
+ [cmd_do_command] = "",
+ [cmd_do_ctrl_c] = "^C",
+ [cmd_do_ctrl_d] = "^D",
+ [cmd_do_ctrl_z] = "^Z",
+ [cmd_do_eof] = "^*",
+ [cmd_do_timed_out] = "^!"
},
{ /* when waiting for a previous command to complete */
- [cmd_do_command] = "^",
- [cmd_do_ctrl_c] = "^C",
- [cmd_do_ctrl_d] = "^D",
- [cmd_do_ctrl_z] = "^Z",
- [cmd_do_eof] = "^*"
+ [cmd_do_command] = "^",
+ [cmd_do_ctrl_c] = "^C",
+ [cmd_do_ctrl_d] = "^D",
+ [cmd_do_ctrl_z] = "^Z",
+ [cmd_do_eof] = "^*",
+ [cmd_do_timed_out] = "^!"
}
} ;
@@ -1320,16 +1331,16 @@ uty_cli_response(vty_cli cli, cmd_do_t to_do)
const char* str ;
int len ;
- if ((to_do == cmd_do_nothing) || (cli->vf->vin_state != vf_open))
- return ;
+ assert((to_do != cmd_do_nothing) && cli->drawn
+ && (qs_cp_nn(cli->cl) == qs_len_nn(cli->cl))) ;
str = (to_do < cmd_do_count)
- ? cli_response[cli->in_progress ? 1 : 0][to_do] : NULL ;
+ ? cli_response[cli->dispatched ? 1 : 0][to_do] : NULL ;
assert(str != NULL) ;
len = uty_cli_write_s(cli, str) ;
- if (cli->in_progress)
+ if (cli->dispatched)
{
cli->extra_len = len ;
uty_cli_write_n(cli, telnet_backspaces, len) ;
@@ -1341,28 +1352,6 @@ uty_cli_response(vty_cli cli, cmd_do_t to_do)
} ;
/*------------------------------------------------------------------------------
- * Send various messages with trailing newline.
- */
-#if 0
-static void
-uty_cli_out_CMD_ERR_AMBIGUOUS(vty_cli cli)
-{
- uty_cli_write_s(cli, "% " MSG_CMD_ERR_AMBIGUOUS ".") ;
- uty_cli_out_newline(cli) ;
-} ;
-
-static void
-uty_cli_out_CMD_ERR_NO_MATCH(vty_cli cli)
-{
- uty_cli_write_s(cli, "% " MSG_CMD_ERR_NO_MATCH ".") ;
- uty_cli_out_newline(cli) ;
-} ;
-#endif
-/*==============================================================================
- * Command line draw and wipe
- */
-
-/*------------------------------------------------------------------------------
* Current prompt length
*/
extern ulen
@@ -1393,7 +1382,7 @@ uty_cli_wipe(vty_cli cli, int len)
a = cli->extra_len ;
b = cli->prompt_len ;
- if (!cli->echo_suppress && !cli->more_wait)
+ if (!cli->more_wait)
{
a += qs_len_nn(cli->cl) - qs_cp_nn(cli->cl) ;
b += qs_cp_nn(cli->cl) ;
@@ -1436,17 +1425,6 @@ uty_cli_draw_if_required(vty_cli cli)
} ;
/*------------------------------------------------------------------------------
- * Draw prompt etc for the current vty node.
- *
- * See uty_cli_draw_this()
- */
-static void
-uty_cli_draw(vty_cli cli)
-{
- uty_cli_draw_this(cli, cli->node) ;
-} ;
-
-/*------------------------------------------------------------------------------
* Draw prompt and entire command line, leaving current position where it
* should be.
*
@@ -1458,21 +1436,20 @@ uty_cli_draw(vty_cli cli)
*
* * if is closing, draw nothing -- wipes the current line
*
- * * if is cli_more_wait, draw the "--more--" prompt
+ * * if is more_wait, draw the "--more--" prompt
*
- * * if is cmd_in_progress, draw the vestigial prompt.
+ * * if is dispatched, draw the vestigial prompt.
*
* By the time the current command completes, the node may have changed, so
* the current prompt may be invalid.
*
- * Sets: cli_drawn = true
- * cli_dirty = false
- * cli_prompt_len = length of prompt used
- * cli_extra_len = 0
- * cli_echo_suppress = (AUTH_NODE or AUTH_ENABLE_NODE)
+ * Sets: cli->drawn = true
+ * cli->dirty = false
+ * cli->prompt_len = length of prompt used
+ * cli->extra_len = 0
*/
static void
-uty_cli_draw_this(vty_cli cli, enum node_type node)
+uty_cli_draw(vty_cli cli)
{
const char* prompt ;
size_t l_len ;
@@ -1494,7 +1471,7 @@ uty_cli_draw_this(vty_cli cli, enum node_type node)
p_len = strlen(prompt) ;
l_len = 0 ;
}
- else if (cli->in_progress)
+ else if (cli->dispatched)
{
/* If there is a queued command, the prompt is a minimal affair. */
prompt = "~ " ;
@@ -1510,6 +1487,8 @@ uty_cli_draw_this(vty_cli cli, enum node_type node)
*
* If the host name changes, the cli_prompt_set flag is cleared.
*/
+ node_type_t node = cli->context->node ;
+
if ((node != cli->prompt_node) || (host.name_gen != cli->prompt_gen))
{
const char* prompt ;
@@ -1521,7 +1500,8 @@ uty_cli_draw_this(vty_cli cli, enum node_type node)
prompt = "%s ???: " ;
} ;
- qs_printf(cli->prompt_for_node, prompt, host.name);
+ cli->prompt_for_node =
+ qs_printf(cli->prompt_for_node, prompt, host.name) ;
cli->prompt_node = node ;
cli->prompt_gen = host.name_gen ;
@@ -1539,134 +1519,60 @@ uty_cli_draw_this(vty_cli cli, enum node_type node)
/* Set about writing the prompt and the line */
cli->drawn = true ;
cli->extra_len = false ;
- cli->echo_suppress = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ;
- cli->prompt_len = p_len ;
+ cli->prompt_len = p_len ;
uty_cli_write(cli, prompt, p_len) ;
if (l_len != 0)
{
- uty_cli_write(cli, qs_char(cli->cl), l_len) ;
+ if (cli->auth_context)
+ uty_cli_write_n(cli, telnet_stars, l_len) ;
+ else
+ uty_cli_write(cli, qs_char(cli->cl), l_len) ;
+
if (qs_cp_nn(cli->cl) < l_len)
uty_cli_write_n(cli, telnet_backspaces, l_len - qs_cp_nn(cli->cl)) ;
} ;
} ;
/*==============================================================================
- * Monitor output.
- *
- * To prepare for monitor output, wipe as much as is necessary for the
- * monitor line to appear correctly.
- *
- * After monitor output, may need to do two things:
- *
- * * if the output was incomplete, place the rump in the CLI buffer,
- * so that:
- *
- * a. don't mess up the console with partial lines
- *
- * b. don't lose part of a message
- *
- * c. act as a brake on further monitor output -- cannot do any more
- * until the last, part, line is dealt with.
- *
- * * restore the command line, unless it is empty !
- */
-
- /*-----------------------------------------------------------------------------
- * Prepare for new monitor output line.
- *
- * Wipe any existing command line.
- */
-extern void
-uty_cli_pre_monitor(vty_cli cli, size_t len)
-{
- VTY_ASSERT_LOCKED() ;
-
- uty_cli_wipe(cli, len) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Recover from monitor line output.
- *
- * If monitor line failed to complete, append the rump to the CLI buffer.
- *
- * If have a non-empty command line, or is cli_more_wait, redraw the command
- * line.
- *
- * Returns: 0 => rump was empty and no command line stuff written
- * > 0 => rump not empty or some command line stuff written
- */
-extern int
-uty_cli_post_monitor(vty_cli cli, const char* buf, size_t len)
-{
- VTY_ASSERT_LOCKED() ;
-
- if (len != 0)
- uty_cli_write(cli, buf, len) ;
-
- if (cli->more_wait || (qs_len_nn(cli->cl) != 0))
- {
- uty_cli_draw(cli) ;
- ++len ;
- } ;
-
- return len ;
-} ;
-
-/*==============================================================================
* Command line processing loop
*/
-
-static int uty_cli_insert (vty_cli cli, const char* chars, int n) ;
-static int uty_cli_overwrite (vty_cli cli, char* chars, int n) ;
-//static int uty_cli_word_overwrite (vty_cli cli, char *str) ;
-static int uty_cli_forwards(vty_cli cli, int n) ;
-static int uty_cli_backwards(vty_cli cli, int n) ;
-static int uty_cli_del_forwards(vty_cli cli, int n) ;
-static int uty_cli_del_backwards(vty_cli cli, int n) ;
-static int uty_cli_bol (vty_cli cli) ;
-static int uty_cli_eol (vty_cli cli) ;
-static int uty_cli_word_forwards_delta(vty_cli cli) ;
-static int uty_cli_word_forwards(vty_cli cli) ;
-static int uty_cli_word_backwards_delta(vty_cli cli, int eat_spaces) ;
-//static int uty_cli_word_backwards_pure (vty_cli cli) ;
-static int uty_cli_word_backwards (vty_cli cli) ;
-static int uty_cli_del_word_forwards(vty_cli cli) ;
-static int uty_cli_del_word_backwards(vty_cli cli) ;
-static int uty_cli_del_to_eol (vty_cli cli) ;
-static int uty_cli_clear_line(vty_cli cli) ;
-static int uty_cli_transpose_chars(vty_cli cli) ;
-static void uty_cli_hist_use(vty_cli cli, int step) ;
-static void uty_cli_hist_next(vty_cli cli) ;
-static void uty_cli_hist_previous(vty_cli cli) ;
-static void uty_cli_complete_command (vty_cli cli, node_type_t node) ;
-static void uty_cli_describe_command (vty_cli cli, node_type_t node) ;
-
-/*------------------------------------------------------------------------------
- * Fetch next keystroke, reading from the file if required.
- */
-static bool
-uty_cli_get_keystroke(vty_cli cli, keystroke stroke)
-{
- int got ;
-
- do
- {
- if (keystroke_get(cli->key_stream, stroke))
- return true ;
-
- got = uty_term_read(cli->vf, NULL) ; /* not stealing */
- } while (got > 0) ;
-
- return false ;
-} ;
+static cmd_do_t uty_cli_get_keystroke(vty_cli cli, keystroke stroke) ;
+static void uty_cli_update_line(vty_cli cli, uint rc) ;
+
+static void uty_cli_insert (qstring cl, const char* chars, int n) ;
+static int uty_cli_forwards(qstring cl, int n) ;
+static int uty_cli_backwards(qstring cl, int n) ;
+static void uty_cli_del_forwards(qstring cl, int n) ;
+static void uty_cli_del_backwards(qstring cl, int n) ;
+static void uty_cli_bol(qstring cl) ;
+static void uty_cli_eol(qstring cl) ;
+static int uty_cli_word_forwards_delta(qstring cl) ;
+static void uty_cli_word_forwards(qstring cl) ;
+static int uty_cli_word_backwards_delta(qstring cl) ;
+static void uty_cli_word_backwards(qstring cl) ;
+static void uty_cli_del_word_forwards(qstring cl) ;
+static void uty_cli_del_word_backwards(qstring cl) ;
+static void uty_cli_del_to_eol(qstring cl) ;
+static void uty_cli_clear_line(qstring cl) ;
+static void uty_cli_transpose_chars(qstring cl) ;
+static void uty_cli_complete_command (vty_cli cli) ;
+static void uty_cli_describe_command (vty_cli cli) ;
+
+enum hist_step
+{
+ hist_previous = -1,
+ hist_next = +1,
+};
+
+static void uty_cli_hist_use(vty_cli cli, enum hist_step) ;
/*------------------------------------------------------------------------------
* Process keystrokes until run out of input, or get something to cmd_do.
*
- * If required, draw the prompt and command line.
+ * Expects the command line to have been drawn.
*
* Process keystrokes until run out of stuff to do, or have a "command line"
* that must now be executed.
@@ -1675,53 +1581,41 @@ uty_cli_get_keystroke(vty_cli cli, keystroke stroke)
* ready to read and return. (To give some "sharing".)
*
* Returns: cmd_do_xxxx
- *
- * When returns the cl is '\0' terminated.
*/
static cmd_do_t
-uty_cli_process(vty_cli cli, node_type_t node)
+uty_cli_process(vty_cli cli)
{
- struct keystroke stroke ;
+ keystroke_t stroke ;
uint8_t u ;
- bool auth ;
cmd_do_t to_do ;
- auth = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ;
+ qs_copy(cli->cls, cli->cl) ;
+
+ assert(cli->drawn) ;
/* Now process as much as possible of what there is */
- to_do = cmd_do_nothing ;
- while (to_do == cmd_do_nothing)
+ do
{
- if (!cli->drawn)
- uty_cli_draw_this(cli, node) ;
-
- if (!uty_cli_get_keystroke(cli, &stroke))
- {
- to_do = (stroke.value == knull_eof) ? cmd_do_eof : cmd_do_nothing ;
- break ;
- } ;
+ to_do = uty_cli_get_keystroke(cli, stroke) ;
- if (stroke.flags != 0)
- {
- /* TODO: deal with broken keystrokes */
- continue ;
- } ;
+ if (to_do != cmd_do_keystroke)
+ break ;
- switch (stroke.type)
+ switch (stroke->type)
{
/* Straightforward character -----------------------------------*/
/* Note: only interested in 8-bit characters ! */
case ks_char:
- u = (uint8_t)stroke.value ;
+ u = (uint8_t)stroke->value ;
- switch (stroke.value)
+ switch (stroke->value)
{
case CONTROL('A'):
- uty_cli_bol (cli);
+ uty_cli_bol(cli->cl) ;
break;
case CONTROL('B'):
- uty_cli_backwards(cli, 1);
+ uty_cli_backwards(cli->cl, 1);
break;
case CONTROL('C'):
@@ -1729,47 +1623,44 @@ uty_cli_process(vty_cli cli, node_type_t node)
break ;
case CONTROL('D'):
- if (auth)
- to_do = cmd_do_ctrl_d ; /* Exit on ^D ..................*/
- else
- uty_cli_del_forwards(cli, 1);
+ uty_cli_del_forwards(cli->cl, 1);
break;
case CONTROL('E'):
- uty_cli_eol (cli);
+ uty_cli_eol(cli->cl);
break;
case CONTROL('F'):
- uty_cli_forwards(cli, 1);
+ uty_cli_forwards(cli->cl, 1);
break;
case CONTROL('H'):
case 0x7f:
- uty_cli_del_backwards(cli, 1);
+ uty_cli_del_backwards(cli->cl, 1);
break;
case CONTROL('K'):
- uty_cli_del_to_eol (cli);
+ uty_cli_del_to_eol(cli->cl);
break;
case CONTROL('N'):
- uty_cli_hist_next (cli);
+ uty_cli_hist_use(cli, hist_next) ;
break;
case CONTROL('P'):
- uty_cli_hist_previous(cli);
+ uty_cli_hist_use(cli, hist_previous) ;
break;
case CONTROL('T'):
- uty_cli_transpose_chars (cli);
+ uty_cli_transpose_chars(cli->cl) ;
break;
case CONTROL('U'):
- uty_cli_clear_line(cli);
+ uty_cli_clear_line(cli->cl) ;
break;
case CONTROL('W'):
- uty_cli_del_word_backwards (cli);
+ uty_cli_del_word_backwards(cli->cl) ;
break;
case CONTROL('Z'):
@@ -1782,49 +1673,45 @@ uty_cli_process(vty_cli cli, node_type_t node)
break ;
case '\t':
- if (auth)
- uty_cli_insert (cli, " ", 1) ;
- else if (cmd_token_position(cli->parsed, cli->cl))
- uty_cli_insert (cli, " ", 1) ;
+ if (cmd_token_position(cli->parsed, cli->cl))
+ uty_cli_insert (cli->cl, " ", 1) ;
else
- uty_cli_complete_command (cli, node);
+ uty_cli_complete_command(cli);
break;
case '?':
- if (auth)
- uty_cli_insert (cli, (char*)&u, 1) ;
- else if (cmd_token_position(cli->parsed, cli->cl))
- uty_cli_insert (cli, (char*)&u, 1) ;
+ if (cmd_token_position(cli->parsed, cli->cl))
+ uty_cli_insert(cli->cl, (char*)&u, 1) ;
else
- uty_cli_describe_command (cli, node);
+ uty_cli_describe_command(cli);
break;
default:
- if ((stroke.value >= 0x20) && (stroke.value < 0x7F))
- uty_cli_insert (cli, (char*)&u, 1) ;
+ if ((stroke->value >= 0x20) && (stroke->value < 0x7F))
+ uty_cli_insert(cli->cl, (char*)&u, 1) ;
break;
}
break ;
/* ESC X -------------------------------------------------------------*/
case ks_esc:
- switch (stroke.value)
+ switch (stroke->value)
{
case 'b':
- uty_cli_word_backwards (cli);
+ uty_cli_word_backwards(cli->cl);
break;
case 'f':
- uty_cli_word_forwards (cli);
+ uty_cli_word_forwards(cli->cl);
break;
case 'd':
- uty_cli_del_word_forwards (cli);
+ uty_cli_del_word_forwards(cli->cl);
break;
case CONTROL('H'):
case 0x7f:
- uty_cli_del_word_backwards (cli);
+ uty_cli_del_word_backwards(cli->cl);
break;
default:
@@ -1832,37 +1719,39 @@ uty_cli_process(vty_cli cli, node_type_t node)
} ;
break ;
- /* ESC [ X -----------------------------------------------------------*/
+ /* ESC [ ... ---------------------------------------------------------*/
case ks_csi:
- if (stroke.len != 0)
- break ; /* only recognise 3 byte sequences */
-
- switch (stroke.value)
- {
- case ('A'): /* up arrow */
- uty_cli_hist_previous(cli);
- break;
-
- case ('B'): /* down arrow */
- uty_cli_hist_next (cli);
- break;
-
- case ('C'): /* right arrow */
- uty_cli_forwards(cli, 1);
- break;
-
- case ('D'): /* left arrow */
- uty_cli_backwards(cli, 1);
- break;
-
- default:
- break ;
- } ;
- break ;
-
- /* Telnet Command ----------------------------------------------------*/
- case ks_iac:
- uty_telnet_command(cli->vf, &stroke, false) ;
+ if (stroke->len == 0)
+ {
+ /* ESC [ X */
+ switch (stroke->value)
+ {
+ case ('A'): /* up arrow */
+ uty_cli_hist_use(cli, hist_previous) ;
+ break;
+
+ case ('B'): /* down arrow */
+ uty_cli_hist_use(cli, hist_next) ;
+ break;
+
+ case ('C'): /* right arrow */
+ uty_cli_forwards(cli->cl, 1) ;
+ break;
+
+ case ('D'): /* left arrow */
+ uty_cli_backwards(cli->cl, 1) ;
+ break;
+
+ default:
+ break ;
+ } ;
+ }
+ else if (stroke->len == 1)
+ {
+ /* ESC [ 3 ~ only ! */
+ if ((stroke->value == '~') && (stroke->buf[0] == '3'))
+ uty_cli_del_forwards(cli->cl, 1) ;
+ } ;
break ;
/* Unknown -----------------------------------------------------------*/
@@ -1870,151 +1759,309 @@ uty_cli_process(vty_cli cli, node_type_t node)
zabort("unknown keystroke type") ;
} ;
- } ;
+ }
+ while (to_do == cmd_do_keystroke) ;
/* Tidy up and return where got to. */
if (to_do != cmd_do_nothing)
- uty_cli_eol (cli) ; /* go to the end of the line */
+ uty_cli_eol(cli->cl) ; /* go to the end of the line */
+
+ uty_cli_update_line(cli, qs_cp_nn(cli->cl)) ;
return to_do ;
} ;
-/*==============================================================================
- * Command line operations
- */
-
/*------------------------------------------------------------------------------
- * Insert 'n' characters at current position in the command line
+ * Update the command line to reflect the difference between old line and the
+ * new line.
*
- * Returns number of characters inserted -- ie 'n'
+ * Leave the screen cursor at the given required cursor position.
*/
-static int
-uty_cli_insert (vty_cli cli, const char* chars, int n)
+static void
+uty_cli_update_line(vty_cli cli, uint rc)
{
- int after ;
+ const char* np ; /* new line */
+ const char* sp ; /* screen line */
- VTY_ASSERT_LOCKED() ;
+ ulen nl ; /* new length */
+ ulen sl ; /* screen length */
- assert((qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) && (n >= 0)) ;
+ ulen sc ; /* screen cursor */
- if (n <= 0)
- return n ; /* avoid trouble */
+ ulen s, l ;
- after = qs_insert(cli->cl, chars, n) ;
+ assert(cli->drawn) ;
- uty_cli_echo(cli, qs_cp_char(cli->cl), after) ;
+ np = qs_char_nn(cli->cl) ;
+ nl = qs_len_nn(cli->cl) ;
- if ((after - n) != 0)
- uty_cli_echo_n(cli, telnet_backspaces, after - n) ;
+ sp = qs_char_nn(cli->cls) ;
+ sl = qs_len_nn(cli->cls) ;
+ sc = qs_cp_nn(cli->cls) ;
- qs_move_cp_nn(cli->cl, n) ;
+ /* Find how many characters are the same. */
+ l = (nl <= sl) ? nl : sl ;
+ s = 0 ;
+ while ((s < l) && (*(np + s) == *(sp + s)))
+ ++s ;
- return n ;
+ /* If the screen and new are different lengths, or the strings are not
+ * the same, then need to draw stuff to correct what is on the screen.
+ * That will leave the screen cursor at the end of the new line, after
+ * any spaces required to wipe out excess characters.
+ *
+ * Note that sc is the current cursor position on the screen, and we keep
+ * that up to date as we draw stuff.
+ */
+ if ((nl != sl) || (s != nl))
+ {
+ /* Move back if the screen cursor is beyond the same section */
+ if (sc > s)
+ {
+ uty_cli_write_n(cli, telnet_backspaces, sc - s) ;
+ sc = s ;
+ } ;
+
+ /* Write from cursor to the end of the new line. */
+ uty_cli_write(cli, np + sc, nl - sc) ;
+ sc = nl ;
+
+ /* If the old line was longer, need to wipe out old stuff */
+ if (sl > nl)
+ {
+ uty_cli_write_n(cli, telnet_spaces, sl - nl) ;
+ sc = sl ;
+ } ;
+ } ;
+
+ /* Now move cursor to the required cursor position */
+ if (sc > rc)
+ uty_cli_write_n(cli, telnet_backspaces, sc - rc) ;
+
+ if (sc < rc) /* => lines unchanged, but cursor moved */
+ uty_cli_write(cli, np + sc, rc - sc) ;
} ;
/*------------------------------------------------------------------------------
- * Overstrike 'n' characters at current position in the command line
+ * For password: process keystrokes until run out of input, or get something
+ * to cmd_do.
*
- * Move current position forwards.
+ * Similar to uty_cli_auth, except accepts a limited number of keystrokes.
*
- * Returns number of characters inserted -- ie 'n'
+ * Returns: cmd_do_xxxx
*/
-static int
-uty_cli_overwrite (vty_cli cli, char* chars, int n)
+static cmd_do_t
+uty_cli_auth(vty_cli cli)
{
- VTY_ASSERT_LOCKED() ;
+ keystroke_t stroke ;
+ uint8_t u ;
+ cmd_do_t to_do ;
+ int olen, nlen ;
- assert((qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) && (n >= 0)) ;
+ /* For auth command lines, cursor is always at the end */
+ assert(qs_cp_nn(cli->cl) == qs_len_nn(cli->cl)) ;
- if (n > 0)
+ olen = qs_len_nn(cli->cl) ;
+
+ /* Now process as much as possible of what there is */
+ do
{
- qs_replace(cli->cl, n, chars, n) ;
- uty_cli_echo(cli, chars, n) ;
+ to_do = uty_cli_get_keystroke(cli, stroke) ;
- qs_move_cp_nn(cli->cl, n) ;
- } ;
+ if (to_do != cmd_do_keystroke)
+ break ;
- return n ;
-}
+ switch (stroke->type)
+ {
+ /* Straightforward character ---------------------------------*/
+ /* Note: only interested in 8-bit characters ! */
+ case ks_char:
+ u = (uint8_t)stroke->value ;
+
+ switch (stroke->value)
+ {
+ case CONTROL('C'):
+ to_do = cmd_do_ctrl_c ; /* Exit on ^C */
+ break ;
+
+ case CONTROL('D'):
+ to_do = cmd_do_ctrl_d ; /* Exit on ^D */
+ break;
+
+ case CONTROL('H'):
+ case 0x7F:
+ uty_cli_del_backwards(cli->cl, 1);
+ break;
+
+ case CONTROL('U'):
+ case CONTROL('W'):
+ uty_cli_clear_line(cli->cl);
+ break;
+
+ case CONTROL('Z'):
+ to_do = cmd_do_ctrl_z ; /* Exit on ^Z */
+ break;
+
+ case '\n':
+ case '\r':
+ to_do = cmd_do_command ; /* Exit on CR or LF */
+ break ;
+
+ default:
+ if ((stroke->value >= 0x20) && (stroke->value < 0x7F))
+ uty_cli_insert(cli->cl, (char*)&u, 1) ;
+ break;
+ }
+ break ;
+
+ /* ESC X -----------------------------------------------------*/
+ case ks_esc:
+ switch (stroke->value)
+ {
+ case CONTROL('H'):
+ case 0x7f:
+ uty_cli_clear_line(cli->cl);
+ break;
+
+ default:
+ break;
+ } ;
+ break ;
+
+ /* ESC [ ... ---------------------------------------------------*/
+ case ks_csi:
+ break ;
+
+ /* Unknown -----------------------------------------------------*/
+ default:
+ zabort("unknown keystroke type") ;
+ } ;
+ }
+ while (to_do == cmd_do_keystroke) ;
+
+ /* Tidy up and return where got to. */
+
+ nlen = qs_len_nn(cli->cl) ;
+
+ if (nlen < olen)
+ uty_cli_out_wipe_n(cli, nlen - olen) ;
+ if (nlen > olen)
+ uty_cli_write_n(cli, telnet_stars, nlen - olen) ;
+
+ return to_do ;
+} ;
/*------------------------------------------------------------------------------
- * Replace 'm' characters at the current position, by 'n' characters and leave
- * cursor at the end of the inserted characters.
+ * Fetch next keystroke, reading from the file if required.
*
- * Returns number of characters inserted -- ie 'n'
+ * Returns: cmd_do_t
*/
-static int
-uty_cli_replace(vty_cli cli, int m, const char* chars, int n)
+static cmd_do_t
+uty_cli_get_keystroke(vty_cli cli, keystroke stroke)
{
- int a, b ;
+ while (1)
+ {
+ if (keystroke_get(cli->key_stream, stroke))
+ {
+ if (stroke->flags != 0)
+ {
+ /* TODO: deal with broken keystrokes */
+ }
- VTY_ASSERT_LOCKED() ;
+ if (stroke->type != ks_iac)
+ return cmd_do_keystroke ; /* have a keystroke */
- assert((qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) && (n >= 0) && (m >= 0)) ;
+ /* Deal with telnet command, so invisible to upper level */
+ uty_telnet_command(cli->vf, stroke, false) ;
+ }
+ else
+ {
+ int get ;
- b = qs_len_nn(cli->cl) - qs_cp_nn(cli->cl) ;
- a = qs_replace(cli->cl, m, chars, n) ;
+ assert(stroke->type == ks_null) ;
- uty_cli_echo(cli, qs_cp_char(cli->cl), a) ;
+ switch (stroke->value)
+ {
+ case knull_not_eof:
+ break ;
- if (a < b)
- uty_cli_echo_n(cli, telnet_spaces, b - a) ;
- else
- b = a ;
+ case knull_eof:
+ return cmd_do_eof ;
- if (b > n)
- uty_cli_echo_n(cli, telnet_backspaces, b - n) ;
+ case knull_timed_out:
+ return cmd_do_timed_out ;
- qs_move_cp_nn(cli->cl, n) ;
+ default:
+ zabort("unknown knull_xxx") ;
+ break ;
+ } ;
- return n ;
+ get = uty_term_read(cli->vf, NULL) ; /* sets eof in key_stream
+ if hit eof or error */
+ if (get <= 0)
+ return cmd_do_nothing ;
+ } ;
+ } ;
} ;
-#if 0
+/*==============================================================================
+ * Command line operations
+ */
+
/*------------------------------------------------------------------------------
- * Insert a word into vty interface with overwrite mode.
+ * Insert 'n' characters at current position in the command line, leaving
+ * cursor after the inserted characters.
*
- * NB: Assumes result will then be the end of the line.
- *
- * Returns number of characters inserted -- ie length of string
+ * NB: assumes line will be updated by uty_cli_update_line()
*/
-static int
-uty_cli_word_overwrite (vty_cli cli, char *str)
+static void
+uty_cli_insert(qstring cl, const char* chars, int n)
{
- int n ;
- VTY_ASSERT_LOCKED() ;
+ assert((qs_cp_nn(cl) <= qs_len_nn(cl)) && (n >= 0)) ;
- n = uty_cli_overwrite(cli, str, strlen(str)) ;
+ if (n > 0)
+ {
+ qs_insert(cl, chars, n) ;
+ qs_move_cp_nn(cl, n) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Replace 'm' characters at the current position, by 'n' characters and leave
+ * cursor at the end of the inserted characters.
+ *
+ * NB: assumes line will be updated by uty_cli_update_line()
+ */
+static void
+uty_cli_replace(qstring cl, int m, const char* chars, int n)
+{
+ assert((qs_cp_nn(cl) <= qs_len_nn(cl)) && (n >= 0) && (m >= 0)) ;
- qs_set_len_nn(cli->cl, qs_cp_nn(cli->cl)) ;
+ qs_replace(cl, m, chars, n) ;
- return n ;
-}
-#endif
+ qs_move_cp_nn(cl, n) ;
+} ;
/*------------------------------------------------------------------------------
* Forward 'n' characters -- stop at end of line.
*
* Returns number of characters actually moved
+ *
+ * NB: assumes line will be updated by uty_cli_update_line()
*/
static int
-uty_cli_forwards(vty_cli cli, int n)
+uty_cli_forwards(qstring cl, int n)
{
int have ;
- VTY_ASSERT_LOCKED() ;
- have = qs_after_cp_nn(cli->cl) ;
+ have = qs_after_cp_nn(cl) ;
if (have < n)
n = have ;
assert(n >= 0) ;
- if (n > 0)
- {
- uty_cli_echo(cli, qs_cp_char(cli->cl), n) ;
- qs_move_cp_nn(cli->cl, n) ;
- } ;
+ qs_move_cp_nn(cl, n) ;
return n ;
} ;
@@ -2023,22 +2070,18 @@ uty_cli_forwards(vty_cli cli, int n)
* Backwards 'n' characters -- stop at start of line.
*
* Returns number of characters actually moved
+ *
+ * NB: assumes line will be updated by uty_cli_update_line()
*/
static int
-uty_cli_backwards(vty_cli cli, int n)
+uty_cli_backwards(qstring cl, int n)
{
- VTY_ASSERT_LOCKED() ;
-
- if ((int)qs_cp_nn(cli->cl) < n)
- n = qs_cp_nn(cli->cl) ;
+ if ((int)qs_cp_nn(cl) < n)
+ n = qs_cp_nn(cl) ;
assert(n >= 0) ;
- if (n > 0)
- {
- uty_cli_echo_n(cli, telnet_backspaces, n) ;
- qs_move_cp_nn(cli->cl, -n) ;
- } ;
+ qs_move_cp_nn(cl, -n) ;
return n ;
} ;
@@ -2047,86 +2090,69 @@ uty_cli_backwards(vty_cli cli, int n)
* Move forwards (if n > 0) or backwards (if n < 0) -- stop at start or end of
* line.
*
- * Returns number of characters actually moved -- signed
+ * NB: assumes line will be updated by uty_cli_update_line()
*/
-static int
-uty_cli_move(vty_cli cli, int n)
+static void
+uty_cli_move(qstring cl, int n)
{
- VTY_ASSERT_LOCKED() ;
-
if (n < 0)
- return - uty_cli_backwards(cli, -n) ;
+ uty_cli_backwards(cl, -n) ;
if (n > 0)
- return + uty_cli_forwards(cli, +n) ;
-
- return 0 ;
+ uty_cli_forwards(cl, +n) ;
} ;
/*------------------------------------------------------------------------------
* Delete 'n' characters -- forwards -- stop at end of line.
*
- * Returns number of characters actually deleted.
+ * NB: assumes line will be updated by uty_cli_update_line()
*/
-static int
-uty_cli_del_forwards(vty_cli cli, int n)
+static void
+uty_cli_del_forwards(qstring cl, int n)
{
- int after ;
int have ;
- VTY_ASSERT_LOCKED() ;
-
- have = qs_after_cp_nn(cli->cl) ;
+ have = qs_after_cp_nn(cl) ;
if (have < n)
n = have ; /* cannot delete more than have */
assert(n >= 0) ;
- if (n <= 0)
- return 0 ;
-
- after = qs_delete(cli->cl, n) ;
-
- if (after > 0)
- uty_cli_echo(cli, qs_cp_char(cli->cl), after) ;
-
- uty_cli_echo_n(cli, telnet_spaces, n) ;
- uty_cli_echo_n(cli, telnet_backspaces, after + n) ;
-
- return n ;
+ if (n > 0)
+ qs_delete(cl, n) ;
}
/*------------------------------------------------------------------------------
* Delete 'n' characters before the point.
*
- * Returns number of characters actually deleted.
+ * NB: assumes line will be updated by uty_cli_update_line()
*/
-static int
-uty_cli_del_backwards(vty_cli cli, int n)
+static void
+uty_cli_del_backwards(qstring cl, int n)
{
- return uty_cli_del_forwards(cli, uty_cli_backwards(cli, n)) ;
+ uty_cli_del_forwards(cl, uty_cli_backwards(cl, n)) ;
}
/*------------------------------------------------------------------------------
* Move to the beginning of the line.
*
- * Returns number of characters moved over.
+ * NB: assumes line will be updated by uty_cli_update_line()
*/
-static int
-uty_cli_bol (vty_cli cli)
+static void
+uty_cli_bol(qstring cl)
{
- return uty_cli_backwards(cli, qs_cp_nn(cli->cl)) ;
+ qs_set_cp_nn(cl, 0) ;
} ;
/*------------------------------------------------------------------------------
* Move to the end of the line.
*
- * Returns number of characters moved over
+ * NB: assumes line will be updated by uty_cli_update_line()
*/
-static int
-uty_cli_eol (vty_cli cli)
+static void
+uty_cli_eol(qstring cl)
{
- return uty_cli_forwards(cli, qs_after_cp_nn(cli->cl)) ;
+ qs_set_cp_nn(cl, qs_len_nn(cl)) ;
} ;
/*------------------------------------------------------------------------------
@@ -2137,25 +2163,23 @@ uty_cli_eol (vty_cli cli)
* Steps over non-space characters and then any spaces.
*/
static int
-uty_cli_word_forwards_delta(vty_cli cli)
+uty_cli_word_forwards_delta(qstring cl)
{
char* cp ;
char* tp ;
char* ep ;
- VTY_ASSERT_LOCKED() ; ;
+ cp = qs_cp_char(cl) ;
+ ep = qs_ep_char(cl) ;
- assert(qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) ;
-
- cp = qs_cp_char(cli->cl) ;
- ep = qs_ep_char(cli->cl) ;
+ assert(cp <= ep) ;
tp = cp ;
- while ((tp < ep) && (*tp != ' '))
+ while ((tp < ep) && (*tp != ' ')) /* step over spaces */
++tp ;
- while ((tp < ep) && (*tp == ' '))
+ while ((tp < ep) && (*tp == ' ')) /* step to space */
++tp ;
return tp - cp ;
@@ -2165,11 +2189,13 @@ uty_cli_word_forwards_delta(vty_cli cli)
* Forward word -- move to start of next word.
*
* Moves past any non-spaces, then past any spaces.
+ *
+ * NB: assumes line will be updated by uty_cli_update_line()
*/
-static int
-uty_cli_word_forwards(vty_cli cli)
+static void
+uty_cli_word_forwards(qstring cl)
{
- return uty_cli_forwards(cli, uty_cli_word_forwards_delta(cli)) ;
+ uty_cli_forwards(cl, uty_cli_word_forwards_delta(cl)) ;
} ;
/*------------------------------------------------------------------------------
@@ -2181,24 +2207,21 @@ uty_cli_word_forwards(vty_cli cli)
* Steps back until next (backwards) character is space, or hits start of line.
*/
static int
-uty_cli_word_backwards_delta(vty_cli cli, int eat_spaces)
+uty_cli_word_backwards_delta(qstring cl)
{
char* cp ;
char* tp ;
char* sp ;
- VTY_ASSERT_LOCKED() ; ;
-
- assert(qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) ;
+ assert(qs_cp_nn(cl) <= qs_len_nn(cl)) ;
- cp = qs_cp_char(cli->cl) ;
- sp = qs_char(cli->cl) ;
+ cp = qs_cp_char(cl) ;
+ sp = qs_char(cl) ;
tp = cp ;
- if (eat_spaces)
- while ((tp > sp) && (*(tp - 1) == ' '))
- --tp ;
+ while ((tp > sp) && (*(tp - 1) == ' '))
+ --tp ;
while ((tp > sp) && (*(tp - 1) != ' '))
--tp ;
@@ -2206,33 +2229,18 @@ uty_cli_word_backwards_delta(vty_cli cli, int eat_spaces)
return cp - tp ;
} ;
-#if 0
-/*------------------------------------------------------------------------------
- * Backward word, but not trailing spaces.
- *
- * Move back until next (backwards) character is space or start of line.
- *
- * Returns number of characters stepped over.
- */
-static int
-uty_cli_word_backwards_pure (vty_cli cli)
-{
- return uty_cli_backwards(cli, uty_cli_word_backwards_delta(cli, 0)) ;
-} ;
-#endif
-
/*------------------------------------------------------------------------------
* Backward word -- move to start of previous word.
*
* Moves past any spaces, then move back until next (backwards) character is
* space or start of line.
*
- * Returns number of characters stepped over.
+ * NB: assumes line will be updated by uty_cli_update_line()
*/
-static int
-uty_cli_word_backwards (vty_cli cli)
+static void
+uty_cli_word_backwards(qstring cl)
{
- return uty_cli_backwards(cli, uty_cli_word_backwards_delta(cli, 1)) ;
+ uty_cli_backwards(cl, uty_cli_word_backwards_delta(cl)) ;
} ;
/*------------------------------------------------------------------------------
@@ -2240,12 +2248,12 @@ uty_cli_word_backwards (vty_cli cli)
*
* Deletes any leading spaces, then deletes upto next space or end of line.
*
- * Returns number of characters deleted.
+ * NB: assumes line will be updated by uty_cli_update_line()
*/
-static int
-uty_cli_del_word_forwards(vty_cli cli)
+static void
+uty_cli_del_word_forwards(qstring cl)
{
- return uty_cli_del_forwards(cli, uty_cli_word_forwards_delta(cli)) ;
+ uty_cli_del_forwards(cl, uty_cli_word_forwards_delta(cl)) ;
}
/*------------------------------------------------------------------------------
@@ -2253,73 +2261,69 @@ uty_cli_del_word_forwards(vty_cli cli)
*
* Deletes any trailing spaces, then deletes upto next space or start of line.
*
- * Returns number of characters deleted.
+ * NB: assumes line will be updated by uty_cli_update_line()
*/
-static int
-uty_cli_del_word_backwards(vty_cli cli)
+static void
+uty_cli_del_word_backwards(qstring cl)
{
- return uty_cli_del_backwards(cli, uty_cli_word_backwards_delta(cli, 1)) ;
+ uty_cli_del_backwards(cl, uty_cli_word_backwards_delta(cl)) ;
} ;
/*------------------------------------------------------------------------------
* Kill rest of line from current point.
*
- * Returns number of characters deleted.
+ * NB: assumes line will be updated by uty_cli_update_line()
*/
-static int
-uty_cli_del_to_eol (vty_cli cli)
+static void
+uty_cli_del_to_eol(qstring cl)
{
- return uty_cli_del_forwards(cli, qs_after_cp_nn(cli->cl)) ;
+ qs_set_len_nn(cl, qs_cp_nn(cl)) ;
} ;
/*------------------------------------------------------------------------------
* Kill line from the beginning.
*
- * Returns number of characters deleted.
+ * NB: assumes line will be updated by uty_cli_update_line()
*/
-static int
-uty_cli_clear_line(vty_cli cli)
+static void
+uty_cli_clear_line(qstring cl)
{
- uty_cli_bol(cli) ;
- return uty_cli_del_to_eol(cli) ;
+ qs_set_cp_nn(cl, 0) ;
+ qs_set_len_nn(cl, 0) ;
} ;
/*------------------------------------------------------------------------------
* Transpose chars before or at the point.
*
- * Return number of characters affected.
+ * NB: assumes line will be updated by uty_cli_update_line()
*/
-static int
-uty_cli_transpose_chars(vty_cli cli)
+static void
+uty_cli_transpose_chars(qstring cl)
{
- char chars[2] ;
+ char ch ;
char* cp ;
- VTY_ASSERT_LOCKED() ;
-
/* Give up if < 2 characters or at start of line. */
- if ((qs_len_nn(cli->cl) < 2) || (qs_cp_nn(cli->cl) < 1))
- return 0 ;
+ if ((qs_len_nn(cl) < 2) || (qs_cp_nn(cl) < 1))
+ return ;
- /* Move back to first of characters to exchange */
- if (qs_cp_nn(cli->cl) == qs_len_nn(cli->cl))
- uty_cli_backwards(cli, 2) ;
- else
- uty_cli_backwards(cli, 1) ;
+ /* If we are not at the end, step past the second character */
+ if (qs_after_cp_nn(cl) == 0)
+ qs_move_cp_nn(cl, 1) ;
- /* Pick up in the new order */
- cp = qs_cp_char(cli->cl) ;
- chars[1] = *cp++ ;
- chars[0] = *cp ;
+ /* Get address of first character */
+ cp = qs_cp_char(cl) - 2 ;
- /* And overwrite */
- return uty_cli_overwrite(cli, chars, 2) ;
+ /* swap characters */
+ ch = *(cp + 1) ;
+ *(cp + 1) = *cp ;
+ *cp = ch ;
} ;
/*==============================================================================
* Command line history handling
*
- * cli->hist is vector of qstrings
+ * cli->hist is vector of qstrings (created on demand)
* cli->h_now is index of the present time
* cli->hp is index of most recent line read back
*
@@ -2335,7 +2339,8 @@ uty_cli_transpose_chars(vty_cli cli)
*
* hp == h_now means we are in the present.
*
- * Cannot step forwards from hp == h_now (into the future !).
+ * Cannot step forwards from hp == h_now (into the future, which is the
+ * same as the oldest thing we can remember !).
*
* Before stepping backwards from hp == hp_now, sets hp_now to be a copy of
* the current line (complete with cp), so can return to the present.
@@ -2347,6 +2352,40 @@ uty_cli_transpose_chars(vty_cli cli)
* is the same as the new line, apart from whitespace.
*/
+static inline uint
+hp_next(uint hp)
+{
+ return (hp != (VTY_HIST_COUNT - 1)) ? hp + 1 : 0 ;
+} ;
+
+static inline uint
+hp_prev(uint hp)
+{
+ return (hp != 0) ? hp - 1 : VTY_HIST_COUNT - 1 ;
+} ;
+
+static inline uint
+hp_step(uint hp, enum hist_step step)
+{
+ if (step == hist_previous)
+ return hp_prev(hp) ;
+
+ if (step == hist_next)
+ return hp_next(hp) ;
+
+ zabort("invalid hist_step") ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Create cli->hist.
+ */
+static void
+uty_cli_hist_make(vty_cli cli)
+{
+ cli->hist = vector_init_new(cli->hist, VTY_HIST_COUNT) ;
+ vector_set_min_length(cli->hist, VTY_HIST_COUNT) ;
+} ;
+
/*------------------------------------------------------------------------------
* Add given command line to the history buffer.
*
@@ -2358,19 +2397,12 @@ static void
uty_cli_hist_add (vty_cli cli, qstring clx)
{
qstring hist_line ;
- int prev ;
+ int h_prev ;
VTY_ASSERT_LOCKED() ;
- /* make sure have a suitable history vector */
- vector_set_min_length(cli->hist, VTY_MAXHIST) ;
-
- /* get the previous command line */
- prev = cli->h_now - 1 ;
- if (prev < 0)
- prev = VTY_MAXHIST - 1 ;
-
- hist_line = vector_get_item(cli->hist, prev) ;
+ if (cli->hist == NULL)
+ uty_cli_hist_make(cli) ; /* create if required */
/* If the previous line is NULL, that means the history is empty.
*
@@ -2384,10 +2416,20 @@ uty_cli_hist_add (vty_cli cli, qstring clx)
* Otherwise, leave cli->h_now and point hist_line at the most ancient
* line in history.
*/
+ h_prev = hp_prev(cli->h_now) ;
+
+ hist_line = vector_get_item(cli->hist, h_prev) ;
+
if ((hist_line == NULL) || (qs_cmp_sig(hist_line, clx) == 0))
- cli->h_now = prev ;
+ {
+ cli->h_now = h_prev ;
+ cli->h_repeat = true ; /* latest history is a repeat */
+ }
else
- hist_line = vector_get_item(cli->hist, cli->h_now) ;
+ {
+ hist_line = vector_get_item(cli->hist, cli->h_now) ;
+ cli->h_repeat = false ; /* latest history is novel */
+ } ;
/* Now replace the h_now entry
*
@@ -2398,12 +2440,8 @@ uty_cli_hist_add (vty_cli cli, qstring clx)
qs_set_cp_nn(hist_line, qs_len_nn(hist_line)) ;
vector_set_item(cli->hist, cli->h_now, hist_line) ;
- /* Advance to the near future and reset the history pointer */
- cli->h_now++;
- if (cli->h_now == VTY_MAXHIST)
- cli->h_now = 0;
-
- cli->hp = cli->h_now;
+ /* Advance history */
+ cli->hp = cli->h_now = hp_next(cli->h_now) ;
} ;
/*------------------------------------------------------------------------------
@@ -2413,148 +2451,110 @@ uty_cli_hist_add (vty_cli cli, qstring clx)
*
* Step -1 is into the past (up)
* +1 is towards the present (down)
+ *
+ * NB: assumes line will be updated by uty_cli_update_line()
*/
static void
-uty_cli_hist_use(vty_cli cli, int step)
+uty_cli_hist_use(vty_cli cli, enum hist_step step)
{
- int hp ;
- ulen old_len ;
- ulen new_len ;
- ulen after ;
- ulen back ;
+ uint hp ;
qstring hist_line ;
- VTY_ASSERT_LOCKED() ;
-
assert((step == +1) || (step == -1)) ;
+ if (cli->hist == NULL)
+ uty_cli_hist_make(cli) ; /* create if required */
+
hp = cli->hp ;
- /* Special case of being at the insertion point */
+ /* Special case of being at the insertion point (the present)
+ *
+ * Cannot step forwards from the present.
+ *
+ * before stepping back from the present, take a copy of the current
+ * command line -- so can get back to it.
+ *
+ * Note that the 'cp' is stored with the line. So if return to the present,
+ * the cursor returns to its current position. (When lines are added to
+ * the history, the cursor is at the end of the line.)
+ */
if (hp == cli->h_now)
{
if (step > 0)
return ; /* already in the present */
- /* before stepping back from the present, take a copy of the
- * current command line -- so can get back to it.
- *
- * Note that the 'cp' is stored with the line. If return to here
- * and later enter the line it will replace this.
- */
hist_line = vector_get_item(cli->hist, cli->h_now) ;
vector_set_item(cli->hist, cli->h_now, qs_copy(hist_line, cli->cl)) ;
} ;
- /* Advance or retreat */
- hp += step ;
- if (hp < 0)
- hp = VTY_MAXHIST - 1 ;
- else if (hp >= VTY_MAXHIST)
- hp = 0 ;
+ /* Advance or retreat and get history line. */
+ hp = hp_step(hp, step) ;
hist_line = vector_get_item(cli->hist, hp) ;
/* If moving backwards in time, may not move back to the h_now
* point (that would be wrapping round to the present) and may not
* move back to a NULL entry (that would be going back before '.').
- *
- * If moving forwards in time, may return to the present, with
- * hp == cli->h_now.
*/
if (step < 0)
if ((hist_line == NULL) || (hp == cli->h_now))
return ;
+ /* Update the history pointer and copy history line to current line. */
cli->hp = hp ;
-
- /* Move back to the start of the current line */
- uty_cli_bol(cli) ;
-
- /* Get previous line from history buffer and echo that */
- old_len = qs_len_nn(cli->cl) ;
qs_copy(cli->cl, hist_line) ;
- new_len = qs_len_nn(cli->cl) ;
-
- /* Sort out wiping out any excess and setting the cursor position */
- if (old_len > new_len)
- after = old_len - new_len ;
- else
- after = 0 ;
-
- /* Return cursor to stored 'cp' -- which will be end of line unless
- * this is the copy of the original current line stored above.
- */
- back = after + qs_after_cp_nn(cli->cl) ;
-
- if (new_len > 0)
- uty_cli_echo(cli, qs_char_nn(cli->cl), new_len) ;
-
- if (after > 0)
- uty_cli_echo_n(cli, telnet_spaces, after) ;
-
- if (back > 0)
- uty_cli_echo_n(cli, telnet_backspaces, back) ;
-
- return ;
} ;
/*------------------------------------------------------------------------------
- * Use previous history line, if any (up arrow).
- */
-static void
-uty_cli_hist_previous(vty_cli cli)
-{
- uty_cli_hist_use(cli, -1) ;
-}
-
-/*------------------------------------------------------------------------------
- * Use next history line, if any (down arrow).
- */
-static void
-uty_cli_hist_next(vty_cli cli)
-{
- uty_cli_hist_use(cli, +1) ;
-}
-
-/*------------------------------------------------------------------------------
* Show the contents of the history
*/
extern void
uty_cli_hist_show(vty_cli cli)
{
- int hp ;
+ uint hp ;
+ uint h_end ;
VTY_ASSERT_LOCKED() ;
+ if (cli->hist == NULL)
+ return ; /* if no history */
+
+ /* We start with the oldest thing we can remember, which means that
+ * we start by stepping "forwards" from "now".
+ *
+ * Until the history buffer fills, there will be a number of NULL entries
+ * between "now" and the oldest thing in the history.
+ *
+ * We do not show the "now" entry, which is not part of history.
+ *
+ * We do not show the entry before "now", because that is the current
+ * executing command, unless that was a repeat of the command before !
+ */
hp = cli->h_now ;
- while(1)
+ h_end = cli->h_repeat ? hp : hp_prev(hp) ;
+
+ while (1)
{
qstring line ;
- ++hp ;
- if (hp == VTY_MAXHIST)
- hp = 0 ;
+ hp = hp_next(hp) ;
- if (hp == cli->h_now)
- break ; /* wrapped round to "now" */
+ if (hp == h_end)
+ break ; /* reached end of history */
line = vector_get_item(cli->hist, hp) ;
- if (line == NULL)
- break ; /* reached limit of history so far */
-
- uty_out(cli->vf->vio, " %s\n", qs_string(line));
- }
+ if (line != NULL)
+ uty_out(cli->vf->vio, " %s\n", qs_make_string(line));
+ } ;
} ;
/*==============================================================================
* Command Completion and Command Description
*
*/
-static uint uty_cli_help_parse(vty_cli cli, node_type_t node) ;
-static void uty_cli_out_message(vty_cli cli, const char* msg) ;
+static uint uty_cli_help_parse(vty_cli cli) ;
static void uty_cli_complete_keyword(vty_cli cli, const char* keyword) ;
static void uty_cli_complete_list(vty_cli cli, vector item_v) ;
@@ -2562,9 +2562,12 @@ static void uty_cli_complete_list(vty_cli cli, vector item_v) ;
static void uty_cli_describe_list(vty_cli cli, vector item_v) ;
static void uty_cli_describe_line(vty_cli cli, uint str_width, const char* str,
const char* doc, uint len) ;
-
static uint uty_cli_width_to_use(vty_cli cli) ;
+static void uty_cli_help_message(vty_cli cli, const char* msg) ;
+static void uty_cli_help_newline(vty_cli cli) ;
+static void uty_cli_help_finish(vty_cli cli) ;
+
/*------------------------------------------------------------------------------
* Command completion
*
@@ -2572,9 +2575,12 @@ static uint uty_cli_width_to_use(vty_cli cli) ;
* establish which token the cursor is in. Must NOT call this if the cursor
* is in a "special" place.
*
+ * This is called from inside "uty_cli_process()".
+ *
+ * NB: assumes line will be updated by uty_cli_update_line()
*/
static void
-uty_cli_complete_command (vty_cli cli, node_type_t node)
+uty_cli_complete_command (vty_cli cli)
{
uint n_items ;
cmd_parsed parsed ;
@@ -2585,61 +2591,68 @@ uty_cli_complete_command (vty_cli cli, node_type_t node)
parsed = cli->parsed ;
/* Establish what items may be present at the current token position. */
- n_items = uty_cli_help_parse(cli, node) ;
-
- if (n_items == 0) /* quit if nothing to consider */
- return ;
+ n_items = uty_cli_help_parse(cli) ;
- if (n_items > 1) /* render list of alternatives */
- return uty_cli_complete_list(cli, parsed->item_v) ;
+ if (n_items > 1) /* render list of alternatives */
+ uty_cli_complete_list(cli, parsed->item_v) ;
+ else if (n_items == 1)
+ {
+ /* One possible item -- one or more possible commands */
+ item = vector_get_item(parsed->item_v, 0) ;
- /* One possible item -- one or more possible commands */
- item = vector_get_item(parsed->item_v, 0) ;
+ switch (item->type)
+ {
+ case item_null:
+ zabort("invalid item_null") ;
- switch (item->type)
- {
- case item_null:
- zabort("invalid item_null") ;
+ case item_eol:
- case item_eol:
+ case item_option_word:
- case item_option_word:
+ case item_vararg:
- case item_vararg:
+ case item_word:
- case item_word:
+ case item_ipv6_prefix:
+ case item_ipv6_address:
+ case item_ipv4_prefix:
+ case item_ipv4_address:
- case item_ipv6_prefix:
- case item_ipv6_address:
- case item_ipv4_prefix:
- case item_ipv4_address:
+ case item_range:
+ uty_cli_describe_list(cli, parsed->item_v) ;
+ break ;
- case item_range:
- return uty_cli_describe_list(cli, parsed->item_v) ;
+ case item_keyword:
+ uty_cli_complete_keyword(cli, item->str) ;
+ break ;
- case item_keyword:
- return uty_cli_complete_keyword(cli, item->str) ;
+ default:
+ zabort("unknown item type") ;
+ } ;
+ } ;
- default:
- zabort("unknown item type") ;
- } ;
+ /* If necessary, redraw the command line */
+ uty_cli_help_finish(cli) ;
} ;
/*------------------------------------------------------------------------------
* Command Description
*/
static void
-uty_cli_describe_command (vty_cli cli, node_type_t node)
+uty_cli_describe_command (vty_cli cli)
{
uint n_items ;
VTY_ASSERT_LOCKED() ;
/* Establish what items may be present at the current token position. */
- n_items = uty_cli_help_parse(cli, node) ;
+ n_items = uty_cli_help_parse(cli) ;
if (n_items > 0) /* render list of possibilities */
uty_cli_describe_list(cli, cli->parsed->item_v) ;
+
+ /* If necessary, redraw the command line */
+ uty_cli_help_finish(cli) ;
} ;
/*------------------------------------------------------------------------------
@@ -2654,7 +2667,7 @@ uty_cli_describe_command (vty_cli cli, node_type_t node)
* Returns: number of items to consider.
*/
static uint
-uty_cli_help_parse(vty_cli cli, node_type_t node)
+uty_cli_help_parse(vty_cli cli)
{
const char* msg ;
cmd_return_code_t ret ;
@@ -2666,22 +2679,25 @@ uty_cli_help_parse(vty_cli cli, node_type_t node)
msg = cmd_help_preflight(cli->parsed) ;
if (msg != NULL)
{
- uty_cli_out_message(cli, msg) ;
+ uty_cli_help_message(cli, msg) ;
return 0 ;
} ;
/* Now see what the cmd_completion can come up with. */
- ret = cmd_completion(cli->parsed, node) ;
+ ret = cmd_completion(cli->parsed, cli->context) ;
if (ret == CMD_ERR_PARSING)
{
- uint eloc = cli->prompt_len + cli->parsed->eloc ;
+ if (cli->parsed->eloc >= 0)
+ {
+ uint eloc = cli->prompt_len + cli->parsed->eloc ;
- uty_cli_out_newline(cli) ; /* clears cli_drawn */
- uty_cli_write_n(cli, telnet_dots, eloc) ;
- uty_cli_write_s(cli, "^") ;
+ uty_cli_help_newline(cli) ; /* clears cli_drawn etc. */
+ uty_cli_write_n(cli, telnet_dots, eloc) ;
+ uty_cli_write_s(cli, "^") ;
+ } ;
- uty_cli_out_message(cli, cli->parsed->emess) ;
+ uty_cli_help_message(cli, qs_make_string(cli->parsed->emess)) ;
return 0 ;
} ;
@@ -2692,29 +2708,14 @@ uty_cli_help_parse(vty_cli cli, node_type_t node)
n_items = vector_length(cli->parsed->item_v) ;
if (n_items == 0)
- uty_cli_out_message(cli, "command not recognised") ;
+ uty_cli_help_message(cli, "command not recognised") ;
return n_items ;
} ;
-
-
-
-
-
-
-
-static void
-uty_cli_out_message(vty_cli cli, const char* msg)
-{
- uty_cli_out_newline(cli) ; /* clears cli_drawn */
- uty_cli_write_s(cli, "% ") ;
- uty_cli_write_s(cli, msg) ;
- uty_cli_out_newline(cli) ;
-} ;
-
-
-
+/*------------------------------------------------------------------------------
+ * Can complete a keyword.
+ */
static void
uty_cli_complete_keyword(vty_cli cli, const char* keyword)
{
@@ -2722,13 +2723,14 @@ uty_cli_complete_keyword(vty_cli cli, const char* keyword)
cmd_complete_keyword(cli->parsed, &pre, &rep, &ins, &mov) ;
- uty_cli_move(cli, pre) ; /* move to start of token */
- uty_cli_replace(cli, rep, keyword, strlen(keyword)) ;
+ uty_cli_move(cli->cl, pre) ; /* move to start of token */
+ uty_cli_replace(cli->cl, rep, keyword, strlen(keyword)) ;
+ assert(ins <= 2) ;
if (ins > 0)
- uty_cli_insert(cli, " ", ins) ;
+ uty_cli_insert(cli->cl, " ", ins) ;
- uty_cli_move(cli, mov) ;
+ uty_cli_move(cli->cl, mov) ;
return ;
} ;
@@ -2775,7 +2777,7 @@ uty_cli_complete_list(vty_cli cli, vector item_v)
uty_cli_out(cli, "%-*s ", len, item->str) ;
}
- uty_cli_out_newline(cli) ;
+ uty_cli_help_newline(cli) ;
} ;
/*------------------------------------------------------------------------------
@@ -2877,7 +2879,7 @@ uty_cli_describe_list(vty_cli cli, vector item_v)
uty_cli_describe_line(cli, str_width, str, dp, ep - dp) ;
} ;
- uty_cli_out_newline(cli) ;
+ uty_cli_help_newline(cli) ;
} ;
/*------------------------------------------------------------------------------
@@ -2890,7 +2892,7 @@ uty_cli_describe_line(vty_cli cli, uint str_width, const char* str,
if ((*str == '\0') && (len == 0))
return ; /* quit if nothing to say */
- uty_cli_out_newline(cli) ;
+ uty_cli_help_newline(cli) ;
if (len == 0)
uty_cli_out(cli, " %s", str) ; /* left justify */
@@ -2911,3 +2913,51 @@ uty_cli_width_to_use(vty_cli cli)
{
return (cli->width == 0) ? 60 : cli->width ;
} ;
+
+/*------------------------------------------------------------------------------
+ * Move to new line, issue message and leave on new line.
+ *
+ * Deals with udating the command line if we are currently on it.
+ */
+static void
+uty_cli_help_message(vty_cli cli, const char* msg)
+{
+ uty_cli_help_newline(cli) ; /* clears cli->drawn etc. */
+ uty_cli_write_s(cli, "% ") ;
+ uty_cli_write_s(cli, msg) ;
+ uty_cli_write(cli, telnet_newline, 2) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If the command line is drawn, make sure it is up to date, leaving cursor
+ * at the end of the line, and then issue newline.
+ *
+ * Clears cli->drawn and cli->dirty.
+ */
+static void
+uty_cli_help_newline(vty_cli cli)
+{
+ if (cli->drawn)
+ uty_cli_update_line(cli, qs_len_nn(cli->cl)) ;
+
+ uty_cli_write(cli, telnet_newline, 2) ;
+
+ cli->drawn = false ;
+ cli->dirty = false ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If the command line help has "undrawn" the command line, then redraw it now
+ * and make a new copy to cli->cls.
+ *
+ * Sets cli->drawn
+ */
+static void
+uty_cli_help_finish(vty_cli cli)
+{
+ if (!cli->drawn)
+ {
+ uty_cli_draw(cli) ;
+ qs_copy(cli->cls, cli->cl) ;
+ } ;
+} ;
diff --git a/lib/vty_cli.h b/lib/vty_cli.h
index 94e502f0..78ccc900 100644
--- a/lib/vty_cli.h
+++ b/lib/vty_cli.h
@@ -33,6 +33,7 @@
#include "vio_fifo.h"
#include "vio_lines.h"
#include "qstring.h"
+#include "qtimers.h"
#include "keystroke.h"
/*------------------------------------------------------------------------------
@@ -45,9 +46,10 @@ struct vty_cli
vio_vf vf ; /* parent */
/* History of commands */
- vector_t hist ; /* embedded */
- int hp ; /* current place in history */
- int h_now ; /* the present moment */
+ vector hist ; /* embedded */
+ uint hp ; /* current place in history */
+ uint h_now ; /* the present moment */
+ bool h_repeat ; /* latest entry is repeat */
/* Window width/height as reported by Telnet. 0 => unknown */
int width;
@@ -67,13 +69,24 @@ struct vty_cli
/* The incoming stuff */
keystroke_stream key_stream ;
- /* drawn <=> the current prompt and user input occupy the current
- * line on the screen.
+ /* drawn <=> the current prompt and user input occupy the current
+ * line on the screen.
*
- * dirty <=> the last command output did not end with a newline.
+ * This flag <=> the CLI "owns" the screen. This flag
+ * must be cleared -- by wiping the command line -- before
+ * any other output can use the screen.
+ *
+ * In particular, must be cleared before setting
+ * out_active -- see below.
+ *
+ * dirty <=> the last command output did not end with a newline.
+ *
+ * tilde_enabled <=> do not do the "~ " one command line ahead.
*
* If drawn is true, the following are valid:
*
+ * tilde_prompt -- the prompt is the "~ "
+ *
* prompt_len -- the length of the prompt part.
* (will be the "--more--" prompt in cli_more_wait)
*
@@ -87,6 +100,9 @@ struct vty_cli
bool drawn ;
bool dirty ;
+ bool tilde_prompt ;
+ bool tilde_enabled ;
+
int prompt_len ;
int extra_len ;
@@ -95,80 +111,112 @@ struct vty_cli
/* "cache" for prompt -- when node or host name changes, prompt does */
node_type_t prompt_node ;
name_gen_t prompt_gen ;
- qstring_t prompt_for_node ;
-
- /* password failure count -- main login or enable login. */
- int password_fail ;
+ qstring prompt_for_node ;
/* State of the CLI
*
- * in_progress -- command dispatched
+ * dispatched -- command dispatched by CLI
+ * in_progress -- command taken by the command loop
* blocked -- blocked until current command completes
- * out_active -- contents of the command FIFO are being written away
+ * paused -- command dispatched and nothing else happened
+ *
+ * mon_active -- there is stuff in the logging monitor buffer
+ *
+ * out_active -- contents of the obuf FIFO are being written away
+ * though may be blocked in more_wait
+ *
+ * This flag <=> that the command output "owns" the screen.
+ *
+ * While this flag is set, the CLI may not write to the
+ * screen.
+ *
+ * flush -- this flag => out_active.
+ *
+ * When the CLI is ready to read the next CLI command, it
+ * must wait for all command output to complete. This
+ * flag is set, so that (a) any final but incomplete
+ * line of command output will be flushed, and (b) to
+ * signal that out_active must be cleared when all output
+ * has completed.
*
* more_wait -- is in "--more--" wait state
- * more_active -- more_wait and waiting for "--more--" prompt to be
- * written away.
+ * more_enter -- more_wait and waiting for "--more--" prompt to be
+ * written away and keystrokes to be consumed.
*/
+ bool dispatched ;
bool in_progress ;
bool blocked ;
+ bool paused ;
+
+ bool mon_active ;
bool out_active ;
+ bool flush ;
bool more_wait ;
- bool more_active ;
-
- /* This is used to control command output, so that each write_ready event
- * generates at most one tranche of output.
- */
- bool out_done ;
+ bool more_enter ;
/* This is set only if the "--more--" handling is enabled */
bool more_enabled ;
+ /* Timer for paused state -- multi-threaded only */
+ qtimer pause_timer ;
+
/* Command Line(s)
*
- * node -- the node that the CLI is in. This may be some way behind
- * the VTY, but is updated when the CLI level command completes.
+ * context -- the node etc. that the CLI is in. This may be some way behind
+ * the VTY, but is updated when the CLI level command completes.
+ *
+ * Note that this is a copy of the state of exec->context when
+ * uty_want_command() was last called.
+ *
+ * auth_context -- true <=> context->node is AUTH_NODE or AUTH_ENABLE_NODE
+ *
+ * parsed -- the parsed object used to parse command for cli help
*
- * to_do -- when current command being prepared is completed (by
- * CR/LF or otherwise) this says what there now is to be done.
+ * to_do -- when current command being prepared is completed (by
+ * CR/LF or otherwise) this says what there now is to be done.
*
- * cl -- current command line being prepared.
+ * cl -- current command line being prepared.
+ * cls -- current command line on the screen
*
- * clx -- current command line being executed.
+ * clx -- current command line being executed.
+ *
+ * dispatch -- the last action dispatched.
*
* NB: during command execution vty->buf is set to point at the '\0'
* terminated current command line being executed.
*/
- node_type_t node ;
+ cmd_context context ;
+ bool auth_context ;
- cmd_do_t to_do ;
+ cmd_parsed parsed ;
+ cmd_do_t to_do ;
qstring cl ;
- qstring clx ;
+ qstring cls ;
- cmd_parsed_t parsed ; /* embedded */
+ qstring clx ;
- /* CLI line buffering */
- vio_fifo_t cbuf ; /* embedded */
+ cmd_action_t dispatch ;
- /* CLI line control for command output & "--more--" stuff */
- vio_line_control_t olc ; /* embedded */
+ /* CLI line buffering and line control */
+ vio_fifo cbuf ;
+ vio_line_control olc ;
} ;
+/*------------------------------------------------------------------------------
+ * Functions
+ */
extern vty_cli uty_cli_new(vio_vf vf) ;
-extern void uty_cli_start(vty_cli cli, node_type_t node) ;
-
extern vty_cli uty_cli_close(vty_cli cli, bool final) ;
-extern cmd_return_code_t uty_cli_auth(vty_cli) ;
extern void uty_cli_hist_show(vty_cli cli) ;
extern ulen uty_cli_prompt_len(vty_cli cli) ;
extern vty_readiness_t uty_cli(vty_cli cli) ;
-extern void uty_cli_out_push(vty_cli cli) ;
-extern void uty_cli_done_command(vty_cli cli, node_type_t node) ;
+extern cmd_return_code_t uty_cli_want_command(vty_cli cli, cmd_action action,
+ cmd_context context) ;
extern void uty_cli_out(vty_cli cli, const char *format, ...)
PRINTF_ATTRIBUTE(2, 3) ;
extern void uty_cli_out_newline(vty_cli cli) ;
@@ -183,7 +231,12 @@ extern void uty_cli_exit_more_wait(vty_cli cli) ;
extern bool uty_cli_draw_if_required(vty_cli cli) ;
-extern void uty_cli_pre_monitor(vty_cli cli, size_t len) ;
-extern int uty_cli_post_monitor(vty_cli cli, const char* buf, size_t len) ;
+extern void uty_cli_pre_monitor(vty_cli cli) ;
+extern void uty_cli_post_monitor(vty_cli cli) ;
+
+/*------------------------------------------------------------------------------
+ * Pro tem -- "\r\n" string
+ */
+extern const char* uty_cli_newline ;
#endif /* _ZEBRA_VTY_CLI_H */
diff --git a/lib/vty_command.c b/lib/vty_command.c
index 26a9ec6a..0ce31fdf 100644
--- a/lib/vty_command.c
+++ b/lib/vty_command.c
@@ -34,6 +34,7 @@
#include "vty_cli.h"
#include "vio_fifo.h"
#include "vty_io_file.h"
+#include "vty_io_term.h"
#include "list_util.h"
#include "qstring.h"
@@ -44,12 +45,19 @@
/*------------------------------------------------------------------------------
* Prototypes
*/
-static void uty_show_error_location(vio_vf vf) ;
-static void vty_cmd_failed(vty vty, cmd_return_code_t ret) ;
+
+static void uty_cmd_loop_prepare(vty_io vio) ;
+static cmd_return_code_t uty_cmd_hiatus(vty_io vio, cmd_return_code_t ret) ;
+static cmd_return_code_t vty_cmd_auth(vty vty, node_type_t* p_next_node) ;
+static uint uty_show_error_context(vio_fifo ebuf, vio_vf vf) ;
+static uint uty_cmd_failed(vty_io vio, cmd_return_code_t ret) ;
+static void uty_cmd_prepare(vty_io vio) ;
+static void uty_cmd_config_lock(vty vty) ;
+static void uty_cmd_config_lock_check(struct vty *vty, node_type_t node) ;
/*==============================================================================
* There are two command loops -- cmd_read_config() and cq_process(). These
- * functions support those command loops:
+ * functions support those command loops: TODO update !!
*
* * uty_cmd_prepare()
*
@@ -63,7 +71,7 @@ static void vty_cmd_failed(vty vty, cmd_return_code_t ret) ;
*
* Hands command line over to the vty_cli_nexus message queue.
*
- * NB: from this point onwards, the vio is vio->cmd_running !
+ * NB: from this point onwards, the vio is vio->cmd_running ! TODO
*
* * vty_cmd_fetch_line()
*
@@ -87,216 +95,1021 @@ static void vty_cmd_failed(vty vty, cmd_return_code_t ret) ;
*
* If required, pops any vout(s).
*
- * * vty_cmd_loop_exit()
- *
- * When a command has completed, successfully or otherwise, this is
- * called to deal with any errors and return control to.
- *
*
+ */
+
+/*------------------------------------------------------------------------------
+ * Prepare to enter the config read command loop.
*
+ * Initialise exec object, and copy required settings from the current vin
+ * and vout.
+ */
+extern void
+vty_cmd_loop_prepare(vty vty)
+{
+ VTY_LOCK() ;
+
+ assert(vty->type == VTY_CONFIG_READ) ;
+
+ uty_cmd_loop_prepare(vty->vio) ; /* by vty->type & vty->node */
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Enter the command_queue command loop.
+ */
+extern void
+uty_cmd_loop_enter(vty_io vio)
+{
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+
+ assert(vio->vty->type == VTY_TERMINAL) ;
+
+ uty_cmd_loop_prepare(vio) ; /* by vty->type & vty->node */
+
+ cq_loop_enter(vio->vty, vio->vty->node != NULL_NODE ? CMD_SUCCESS
+ : CMD_CLOSE) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Prepare to enter a command loop.
*
+ * Initialise cmd_exec object, and its cmd_context -- given vty->type and
+ * vty->node.
*/
+static void
+uty_cmd_loop_prepare(vty_io vio)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ assert(vio->vty->exec == NULL) ;
+ assert(vio->state == vc_null) ;
+
+ vio->vty->exec = cmd_exec_new(vio->vty) ;
+ vio->state = vc_running ;
+
+ if (vio->vty->node > MAX_NON_CONFIG_NODE)
+ uty_cmd_config_lock(vio->vty) ; /* TODO cannot fail !? */
+
+ uty_cmd_prepare(vio) ;
+} ;
/*------------------------------------------------------------------------------
- * After opening or closing a vin/vout object, update the vty->exec context.
+ * Signal to the command loop that some I/O has completed -- successfully, or
+ * with some I/O error (including time out).
+ *
+ * If the vio is in vc_waiting state -- so will be exec_hiatus -- send message
+ * so that command loop continues.
+ *
+ * Otherwise, if is vc_running and have CMD_IO_ERROR, set the vc_io_error_trap,
+ * so that the next interaction with the vty (other than output) will signal
+ * a pending error.
+ *
+ * Accepts:
+ *
+ * CMD_SUCCESS -- if vc_waiting, passed in.
+ * otherwise, ignored
+ *
+ * CMD_WAITING -- ignored
+ *
+ * CMD_IO_ERROR -- if vc_waiting, passed in
+ * if vc_running, set vc_io_error_trap
+ *
+ * CMD_CLOSE -- if vc_waiting, passed in
+ * if vc_running, set vc_close_trap
+ * if vc_io_error_trap, set vc_close_trap
*/
extern void
-uty_cmd_prepare(vty_io vio)
+uty_cmd_signal(vty_io vio, cmd_return_code_t ret)
{
- cmd_exec exec = vio->vty->exec ;
+ VTY_ASSERT_LOCKED() ;
+
+ if (ret == CMD_WAITING)
+ return ;
- exec->parse_type = vio->vin->parse_type ;
+ assert((ret == CMD_SUCCESS) || (ret == CMD_IO_ERROR) || (ret == CMD_CLOSE)) ;
- exec->out_enabled = vio->vout->out_enabled ;
- exec->reflect_enabled = vio->vin->reflect_enabled &&
- vio->vout->out_enabled ;
+ switch (vio->state)
+ {
+ case vc_null:
+ zabort("invalid vc_null") ;
+ break ;
+
+ case vc_running: /* ignore CMD_SUCCESS */
+ if (ret == CMD_IO_ERROR)
+ vio->state = vc_io_error_trap ;
+ if (ret == CMD_CLOSE)
+ vio->state = vc_close_trap ;
+ break ;
+
+ case vc_waiting: /* pass in the return code continue */
+ vio->state = vc_running ;
+ cq_continue(vio->vty, ret) ;
+ break ;
+
+ case vc_io_error_trap: /* ignore CMD_SUCCESS or duplicate */
+ if (ret == CMD_CLOSE) /* CMD_IO_ERROR. */
+ vio->state = vc_close_trap ;
+ break ;
+
+ case vc_close_trap: /* ignore CMD_SUCCESS, CMD_IO_ERROR, */
+ case vc_stopped: /* and duplicate/too late CMD_CLOSE. */
+ break ;
+
+ case vc_closed:
+ zabort("invalid vc_closed") ;
+ break ;
+
+ default:
+ zabort("unknown vio->state") ;
+ break ;
+ } ;
} ;
/*------------------------------------------------------------------------------
- * This pushes the command line into the vty_cli_message queue, where it will
- * be parsed and executed etc.
+ * Reset the command loop.
+ *
+ * This is used by uty_close() to try to shut down the command loop.
+ *
+ * Does nothing if already closed or never got going.
+ *
+ * "curtains" means that the program is being terminated, so no message or
+ * event handling is running any more, and all threads other than the main
+ * thread have stopped. This means that whatever the state of the command
+ * loop, we can terminate it now. Revokes any outstanding messages/events,
+ * in order to tidy up.
*
- * NB: from this moment on, vio->cmd_running is true, so even if the vty is
- * closed, it cannot be fully closed and then reaped until the flag
- * is cleared.
+ * If not curtains, then push a CMD_CLOSE into the command loop, to bring it
+ * to a shuddering halt.
*
- * While vio->cmd_running the command side is responsible for the vty
- * and the vty->exec stuff.
+ * Will leave the vio->state:
+ *
+ * vc_running -- a CMD_CLOSE has been passed in (was vc_waiting).
+ * Will not be the case if "curtains".
+ *
+ * vc_close_trap -- command loop is running, but will stop as soon as it
+ * sees this trap.
+ * Will not be the case if "curtains".
+ *
+ * vc_stopped -- command loop is stopped
+ * Will be the case if "curtains" (unless vc_null or
+ * vc_closed).
+ * Otherwise will only be the case if the loop had already
+ * stopped.
+ *
+ * vc_null -- never been kissed
+ * vc_closed -- was already closed
+ *
+ * No other states are possible.
*/
-extern cmd_return_code_t
-uty_cmd_dispatch(vty_io vio, cmd_do_t to_do, qstring line)
+extern void
+uty_cmd_loop_close(vty_io vio, bool curtains)
{
VTY_ASSERT_LOCKED() ;
- vio->cmd_running = true ;
- uty_cmd_prepare(vio) ; /* make sure */
+ if ((vio->state == vc_null) || (vio->state == vc_closed))
+ return ;
- cq_dispatch(vio->vty, to_do, line) ;
+ if (curtains)
+ {
+ if (!vio->blocking)
+ cq_revoke(vio->vty) ; /* collect any outstanding message */
- return CMD_WAITING ;
+ vio->state = vc_stopped ;
+ }
+ else
+ uty_cmd_signal(vio, CMD_CLOSE) ;
} ;
/*------------------------------------------------------------------------------
- * Handle a "special" command -- anything not cmd_do_command
+ * Exit command loop.
+ *
+ * Final close of the VTY, giving a reason, if required.
+ */
+extern void
+vty_cmd_loop_exit(vty vty)
+{
+ VTY_LOCK() ;
+
+ VTY_ASSERT_CAN_CLOSE(vty) ;
+
+ /* Make sure no longer holding the config symbol of power */
+ uty_cmd_config_lock_check(vty, NULL_NODE) ;
+
+ /* Can now close the vty */
+ uty_close(vty->vio, NULL, false) ; /* not curtains */
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Handle a "special" command -- anything not cmd_do_command.
+ *
+ * These "commands" are related to VTY_TERMINAL CLI only.
*/
extern cmd_return_code_t
vty_cmd_special(vty vty)
{
cmd_return_code_t ret ;
+ cmd_do_t to_do ;
+ node_type_t next_node ;
ret = CMD_SUCCESS ;
- /* All other nodes... */
- switch (vty->exec->to_do)
- {
- case cmd_do_nothing:
- break ;
- case cmd_do_eof:
- ret = CMD_CLOSE ;
- break ;
+ to_do = vty->exec->action->to_do ;
- case cmd_do_command:
- zabort("invalid cmd_do_command") ;
- break ;
+ /* Note that the next node handling is special here... we establish
+ * the next node explicitly here -- there is no parse operation to preset
+ * what CMD_SUCCESS next node will be.
+ */
- case cmd_do_ctrl_d:
- zabort("invalid cmd_do_ctrl_d") ;
- break ;
+ vty->node = vty->exec->context->node ; /* as per all commands */
+ next_node = vty->exec->context->node ; /* by default. */
- case cmd_do_ctrl_c:
- case cmd_do_ctrl_z:
- ret = cmd_end(vty) ;
- break ;
+ switch (to_do & cmd_do_mask)
+ {
+ case cmd_do_nothing:
+ break ;
+
+ case cmd_do_eof:
+ if (vty->type == VTY_TERMINAL)
+ vty_out(vty, "%% Terminal closed\n") ;
+
+ ret = CMD_CLOSE ;
+ break ;
+
+ case cmd_do_timed_out:
+ if (vty->type == VTY_TERMINAL)
+ vty_out(vty, "%% Terminal timed out\n") ;
+
+ ret = CMD_CLOSE ;
+ break ;
+
+ case cmd_do_command:
+ if ((to_do & cmd_do_auth) != 0)
+ ret = vty_cmd_auth(vty, &next_node) ;
+ else
+ zabort("invalid cmd_do_command") ;
+ break ;
+
+ case cmd_do_ctrl_d:
+ if ((to_do & cmd_do_auth) != 0)
+ next_node = cmd_node_exit_to(vty->node) ;
+ else
+ zabort("invalid cmd_do_ctrl_d") ;
+ break ;
+
+ case cmd_do_ctrl_c:
+ case cmd_do_ctrl_z:
+ next_node = cmd_node_end_to(vty->node) ;
+ break ;
+
+ default:
+ zabort("unknown or invalid cmd_do") ;
+ } ;
- default: /* must now be cmd_do_auth or cmd_do_auth_enable */
- VTY_LOCK() ;
- ret = uty_cli_auth(vty->vio->vin->cli) ;
- VTY_UNLOCK() ;
- } ;
+ /* Now worry about changing node */
+ if (ret == CMD_CLOSE)
+ next_node = NULL_NODE ;
+ else if (next_node == NULL_NODE)
+ ret = CMD_CLOSE ;
+ if (next_node != vty->exec->context->node)
+ {
+ vty->exec->context->node = next_node ;
+ vty_cmd_config_lock_check(vty, next_node) ;
+ } ;
return ret ;
} ;
/*------------------------------------------------------------------------------
- * Fetch the next command line to be executed.
+ * Check that can enter AUTH_ENABLE_NODE.
+ *
+ * Must be: VTY_TERMINAL
+ *
+ * and: no pipes, in or out -- so talking directly to terminal
+ *
+ * and: be VIEW_NODE if there is no enable password.
+ *
+ * Note that "can_enable" <=> vin_depth == 1 and VTY_TERMINAL (or other VTY
+ * that can authenticate). But it may not => vout_depth == 0.
+ */
+extern cmd_return_code_t
+vty_cmd_can_auth_enable(vty vty)
+{
+ cmd_return_code_t ret ;
+
+ VTY_LOCK() ;
+
+ assert(vty->exec->parsed->nnode == AUTH_ENABLE_NODE) ;
+ assert((vty->exec->context->onode == VIEW_NODE) ||
+ (vty->exec->context->onode == RESTRICTED_NODE)) ;
+
+ ret = CMD_WARNING ;
+
+ if (vty->type != VTY_TERMINAL)
+ uty_out(vty->vio, "%% Wrong VTY type (%d) for 'enable'", vty->type) ;
+ else if ((vty->exec->context->onode != VIEW_NODE)
+ && (host.enable == NULL))
+ uty_out(vty->vio, "%% cannot enable because there is no enable password") ;
+ else if ((vty->vio->vin_depth != 1) || (vty->vio->vout_depth != 1))
+ uty_out(vty->vio,
+ "%% cannot authenticate for 'enable' in a pipe command") ;
+ else
+ {
+ assert(vty->exec->context->can_auth_enable) ;
+ ret = CMD_SUCCESS ;
+ } ;
+
+ VTY_UNLOCK() ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Authentication of vty
+ *
+ * Note that if the AUTH_NODE password fails too many times, the vty is
+ * closed.
+ *
+ * Quagga authentication is a touch complicated. The following appear to be
+ * the rules:
+ *
+ * 1. host.no_password_check -- set by "no login" command
+ *
+ * Means that a VTY_TERMINAL can start without going through any
+ * password check -- including when no host.password is configured.
+ *
+ * Note that this does not affect the authentication for enable, except
+ * at startup of a VTY_TERMINAL...
+ *
+ * When a VTY_TERMINAL starts:
+ *
+ * * if host.restricted_mode -> RESTRICTED_NODE
+ *
+ * * else if host.advanced -> ENABLE_NODE
*
- * If possible, get another command line to execute -- pops pipes as required
- * and reflects command line when have one, if required.
+ * This is whether or not an enable password exists.
*
- * Action depends on the type of the current input:
+ * * otherwise -> VIEW_NODE
*
- * VIN_NONE invalid
+ * So being in RESTRICTED_NODE <=> was host.no_password_check &&
+ * host.restricted_mode when the VTY_TERMINAL was started.
+ *
+ * 2. host.restricted_mode -- set by "anonymous restricted"
+ *
+ * Significant only at VTY_TERMINAL start, and only if no_password_check.
+ *
+ * NB: if the enable password is NULL, there is not much point in
+ * RESTRICTED_NODE, since ENABLE_NODE is but one command away.
+ *
+ * NB: that behaviour is is modified here... if is in RESTRICTED_MODE,
+ * will not authenticate AUTH_ENABLE_NODE if there is no enable
+ * password.
+ *
+ * Note that the check is left to this point, so that is completed
+ * atomically. Elsewhere, will refuse to enter ENABLE_NODE from
+ * RESTRICTED_NODE if no enable password. By the time we get here
+ * it is (just) possible that the situation has changed.
+ *
+ * 3. host.advanced -- set by "service advanced-vty"
+ *
+ * Significant iff there is no enable password, when it sets ENABLE_NODE
+ * as the start up node (if no_password_check) or post AUTH_NODE node.
+ *
+ * 4. host.password -- set by "password xxx"
+ *
+ * Unless no_password_check, if there is no password, you cannot start
+ * a vty.
+ *
+ * 5. host.enable -- set by "enable password xxx"
+ *
+ * If this is set, then must authenticate against it for vty to reach
+ * ENABLE_NODE.
+ *
+ * If it is not set, then can enter ENABLE_NODE at any time.
+ *
+ * If AUTH_ENABLE_NODE fails, falls back to the node we came from -- which has
+ * been planted in the context for this purpose. (If host.restricted_mode has
+ * changed since the vty started, could argue this should change where should
+ * fall back to... but that seems unnecessarily complicated.)
+ *
+ * Returns: CMD_SUCCESS -- OK, one way or another
+ * CMD_WARNING -- with error message sent to output
+ * CMD_CLOSE -- too many password failures
+ */
+static cmd_return_code_t
+vty_cmd_auth(vty vty, node_type_t* p_next_node)
+{
+ char *crypt (const char *, const char *);
+
+ char* passwd = NULL ;
+ bool encrypted = false ;
+ bool enable = false ;
+ bool advanced ;
+ bool pass ;
+ cmd_return_code_t ret ;
+ cmd_exec exec ;
+ cmd_context context ;
+
+ exec = vty->exec ;
+ context = exec->context ;
+
+ /* Select the password we need to check against. */
+ passwd = NULL ;
+ encrypted = false ;
+ enable = false ;
+ advanced = false ;
+
+ pass = false ;
+
+ VTY_LOCK() ; /* while access host.xxx */
+
+ switch (vty->node)
+ {
+ case AUTH_NODE:
+ passwd = host.password ;
+ encrypted = host.password_encrypted ;
+ enable = false ;
+
+ context->onode = NULL_NODE ; /* started from nowhere */
+
+ if (host.advanced && (host.enable == NULL))
+ {
+ context->tnode = ENABLE_NODE ;
+ advanced = true ;
+ }
+ else
+ {
+ context->tnode = VIEW_NODE ;
+ advanced = false ;
+ } ;
+ break ;
+
+ case AUTH_ENABLE_NODE:
+ passwd = host.enable ;
+ encrypted = host.enable_encrypted ;
+ enable = true ;
+ advanced = false ;
+
+ assert((context->onode == VIEW_NODE) ||
+ (context->onode == RESTRICTED_NODE)) ;
+ break ;
+
+ default:
+ zabort("unknown vty->node") ;
+ break ;
+ } ;
+
+ VTY_UNLOCK() ;
+
+ /* Check against selected password (if any) */
+ if (passwd == NULL)
+ {
+ /* Here we reject any attempt to AUTH_NODE against an empty password.
+ *
+ * Otherwise, is dealing with the (largely) theoretical case of
+ * This fails any attempt to AUTH_ENABLE against an empty password
+ * if was in RESTRICTED_NODE.
+ *
+ * This passes the theoretically possible case of enable in VIEW_NODE,
+ * when there was an enable password set when the enable command was
+ * executed, but it has since been unset !
+ */
+ pass = context->onode == VIEW_NODE ;
+ }
+ else
+ {
+ char* candidate = qs_make_string(exec->action->line) ;
+
+ if (encrypted)
+ candidate = crypt(candidate, passwd) ;
+
+ pass = (strcmp(candidate, passwd) == 0) ;
+ } ;
+
+ /* Now worry about the result */
+ ret = CMD_SUCCESS ; /* so far, so good */
+
+ if (pass)
+ {
+ *p_next_node = context->tnode ;
+
+ if (enable || advanced)
+ context->can_enable = true ;
+
+ if (*p_next_node == CONFIG_NODE)
+ {
+ ret = vty_cmd_config_lock(vty) ;
+ if (ret == CMD_WARNING)
+ *p_next_node = ENABLE_NODE ;
+ } ;
+
+ exec->password_failures = 0 ; /* forgive any failures */
+ }
+ else
+ {
+ bool no_more = false ;
+
+ if (passwd == NULL)
+ {
+ /* Cannot possibly authenticate ! */
+ no_more = true ;
+ vty_out(vty, "%% No password is set, cannot authenticate!\n") ;
+ }
+ else
+ {
+ exec->password_failures++ ;
+
+ if (exec->password_failures >= 3)
+ {
+ no_more = true ;
+ vty_out(vty, "%% Bad passwords, too many failures!\n") ;
+
+ exec->password_failures = 0 ; /* allow further attempts */
+ } ;
+ } ;
+
+ if (no_more)
+ {
+ if (!enable)
+ {
+ *p_next_node = NULL_NODE ;
+ ret = CMD_CLOSE ;
+ }
+ else
+ {
+ *p_next_node = context->onode ;
+ ret = CMD_WARNING ;
+ } ;
+ } ;
+ } ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Fetch the next command line to be executed.
*
- * VIN_TERM ) return CMD_EOF
- * VIN_VTYSH )
+ * Returns: CMD_SUCCESS => OK -- have a line ready to be processed.
*
- * VIN_FILE )
- * VIN_PIPE ) fetch another line if can.
- * VIN_CONFIG )
+ * vty->exec->line points at the line
+ * vty->exec->to_do says what to do with it
*
- * VIN_DEV_NULL nothing: return CMD_EOF
+ * or: CMD_WAITING => OK -- but waiting for command line to arrive
+ * <=> non-blocking
*
- * Returns: CMD_SUCCESS => OK -- have a line ready to be processed.
+ * or: CMD_EOF => OK -- but nothing to fetch from the current vin
*
- * vty->exec->line points at the line.
+ * Need to close the current vin and pop vin/vout
+ * as necessary.
*
- * or: CMD_EOF => nothing to fetch -- note that if pops back to the
- * VIN_TERM or VIN_VTYSH level, then will return
- * CMD_EOF.
+ * or: CMD_HIATUS => OK -- but need to close one or more vin/vout
+ * to adjust stack.
*
- * If was VIN_TERM or VIN_VTYSH, signals CMD_EOF to
- * exit from the command loop.
+ * or: any other return code from the current vin when it attempts to
+ * fetch another command line -- including I/O error or timeout.
*
- * or: CMD_WAITING => OK -- but waiting for command line to arrive.
+ * NB: can be called from any thread -- because does no closing of files or
+ * anything other than read/write.
*
- * or: ....
+ * TODO -- dealing with states other than vf_open !!
*/
extern cmd_return_code_t
vty_cmd_fetch_line(vty vty)
{
cmd_return_code_t ret ;
vty_io vio ;
- qstring* p_line ;
+ vio_vf vf ;
+ cmd_exec exec ;
VTY_LOCK() ;
- vio = vty->vio ; /* once locked */
- p_line = &vty->exec->line ;
+ vio = vty->vio ; /* once locked */
+ exec = vty->exec ;
- *p_line = NULL ;
- vty->exec->to_do = cmd_do_nothing ;
+ cmd_action_clear(exec->action) ; /* tidy */
- while (1)
- {
- vio_vf vf ;
+ vf = vio->vin ;
- vf = vio->vin ;
+ ret = CMD_SUCCESS ; /* assume all is well */
- if (vf->vin_state != vf_open)
+ /* Worry about all the things that stop us from being able to fetch the
+ * next command line.
+ */
+ if ( (vty->vio->state != vc_running)
+ || (vio->vin_depth < vio->vout->depth_mark)
+ || (vio->vin_depth > vio->real_depth) )
+ ret = CMD_HIATUS ;
+ else
+ {
+ switch (vf->vin_state)
{
- switch (vf->vin_state)
- {
- case vf_closed:
- case vf_eof:
- ret = CMD_EOF ;
- break ;
+ case vf_open:
+ case vf_eof:
+ case vf_timed_out:
+ switch (vf->vin_type)
+ {
+ case VIN_NONE:
+ zabort("invalid VIN_NONE") ;
+ break ;
+
+ case VIN_TERM:
+ ret = uty_term_fetch_command_line(vf, exec->action,
+ exec->context) ;
+ break ;
+
+ case VIN_VTYSH:
+ zabort("invalid VIN_VTYSH") ;
+ break ;
+
+ case VIN_DEV_NULL:
+ ret = CMD_EOF ;
+ break ;
+
+ case VIN_FILE:
+ case VIN_CONFIG:
+ ret = uty_file_fetch_command_line(vf, exec->action) ;
+ break ;
+
+ case VIN_PIPE:
+ ret = uty_pipe_fetch_command_line(vf, exec->action) ;
+ break ;
+
+ default:
+ zabort("unknown vin_type") ;
+ } ;
+ break ;
+
+ case vf_closed: /* TODO treat closed as EOF ? */
+ ret = CMD_EOF ;
+ break ;
+
+ case vf_error:
+ ret = CMD_IO_ERROR ;
+ break ;
+
+ case vf_closing:
+ assert(!vf->blocking) ;
+ ret = CMD_WAITING ;
+ break ;
+
+ default:
+ zabort("invalid vf->vin_state") ;
+ break ;
+ } ;
+ } ;
- case vf_error:
- ret = CMD_IO_ERROR ;
- break ;
+ VTY_UNLOCK() ;
- case vf_closing:
- ret = CMD_CLOSE ;
- break ;
+ return ret ;
+} ;
- default:
- zabort("invalid vf->vin_state") ;
- break ;
- } ;
- break ;
- } ;
+/*------------------------------------------------------------------------------
+ * Deal with return code at the "exec_hiatus" point in the command loop.
+ *
+ * The command_queue command loop runs until something happens that it
+ * cannot immediately deal with, at which point it enters "exec_hiatus", and
+ * this function is called. The command loop will deal with CMD_SUCCESS and
+ * CMD_EMPTY, but otherwise this function must deal with:
+ *
+ * CMD_HIATUS -- something requires attention, eg:
+ *
+ * - the vout_depth > vin_depth, so the vout needs to
+ * be closed and popped.
+ *
+ * - the vio->state needs to be checked.
+ *
+ * CMD_EOF -- from vty_cmd_fetch_line() => current vin has hit eof,
+ * and must be closed and popped.
+ *
+ * CMD_CLOSE -- from a command return (or otherwise) => must close
+ * and pop the current vin (same as CMD_EOF, really).
+ *
+ * CMD_WAITING -- from vty_cmd_fetch_line() or elsewhere => must go to
+ * vc_waiting and the command loop MUST exit.
+ *
+ * CMD_SUCCESS -- see below
+ *
+ * CMD_EMPTY -- should not appear, but is treated as CMD_SUCCESS
+ *
+ * anything else -- treated as a command or I/O or other error.
+ *
+ * The handling of errors depends on the type of error:
+ *
+ * * command errors will cause all levels of the stack other than vin_base
+ * and vout_base to be closed, and a suitable error message output to the
+ * vout_base.
+ *
+ * Inputs are closed without dealing with any further input and discarding
+ * any buffered input.
+ *
+ * Pending output will be pushed out, and pipe return stuff will be sucked
+ * in and blown out, until the return signals EOF.
+ *
+ * * I/O errors will cause all levels of TODO ... depending on error location ??
+ *
+ * I/O errors also cause all closes to be "final", so pending output is
+ * attempted -- but will be abandoned if would block. Also, any further
+ * I/O errors will be discarded.
+ *
+ * This function will return:
+ *
+ * CMD_SUCCESS => OK -- can try and fetch a command line again.
+ *
+ * state == vc_running
+ *
+ * CMD_WAITING => OK -- but waiting for input to arrive or for something
+ * to be completely closed. => non-blocking
+ *
+ * state == vc_waiting
+ *
+ * CMD_EOF => OK -- but nothing more to fetch, close the vty and exit
+ * command loop.
+ * <=> the vin_base is now closed.
+ *
+ * state == vc_stopped
+ *
+ * CMD_IO_ERROR => some error has occurred while closing stuff.
+ *
+ * state == vc_io_error_trap
+ *
+ * And nothing else.
+ *
+ * The CMD_IO_ERROR is returned so that errors are not hidden inside here.
+ * At some point vty_cmd_hiatus() must be called again to deal with the
+ * error.
+ *
+ * When the command loop has gone vc_waiting, the I/O side of things can wake
+ * it up by uty_cmd_signal(), which passes in a return code. When the
+ * command loop runs it will call this function to handle the new return code.
+ * If CMD_SUCCESS is passed in, will continue trying to adjust the vin/vout
+ * stacks.
+ *
+ * The configuration reader command loop also uses vty_cmd_hiatus() to handle
+ * all return codes. However, it will exit the command loop at the slightest
+ * hint of trouble.
+ *
+ * All this TODO (after discussion of error handling)
- switch (vf->vin_type)
- {
- case VIN_NONE:
- zabort("invalid VIN_NONE") ;
- break ;
+ * NB: can be called from any thread if !blocking, otherwise MUST be cli thread.
+ */
+extern cmd_return_code_t
+vty_cmd_hiatus(vty vty, cmd_return_code_t ret)
+{
+ VTY_LOCK() ;
+ VTY_ASSERT_CAN_CLOSE(vty) ;
- case VIN_TERM:
- case VIN_VTYSH:
- case VIN_DEV_NULL:
- ret = CMD_EOF ;
- break ;
+ ret = uty_cmd_hiatus(vty->vio, ret) ;
- /* fall through */
+ VTY_UNLOCK() ;
+ return ret ;
+} ;
- case VIN_FILE:
- case VIN_PIPE:
- case VIN_CONFIG:
- ret = uty_file_fetch_command_line(vf, p_line) ;
+/*------------------------------------------------------------------------------
+ * Inside of vty_cmd_hiatus() -- can return at any time.
+ */
+static cmd_return_code_t
+uty_cmd_hiatus(vty_io vio, cmd_return_code_t ret)
+{
+ /* (0) worry about state of the vio.
+ *
+ * We expect it to generally be be vc_running or vc_waiting, otherwise:
+ *
+ * vc_io_error_trap => an I/O error has been posted asynchronously
+ *
+ * set state to vc_running and return code to
+ * the pending CMD_IO_ERROR.
+ *
+ * vc_close_trap => the vty has been reset asynchronously
+ *
+ * set state to vc_running and return code to
+ * the pending CMD_CLOSE.
+ *
+ * vc_stopped )
+ * vc_closed ) invalid -- cannot be here in this state
+ * vc_null )
+ */
+ switch (vio->state)
+ {
+ case vc_null:
+ case vc_stopped:
+ case vc_closed:
+ zabort("invalid vc_xxxx") ;
+ break ;
+
+ case vc_running:
+ case vc_waiting:
+ break ;
+
+ case vc_io_error_trap:
+ ret = CMD_IO_ERROR ; /* convert pending IO error */
+ break ;
+
+ case vc_close_trap:
+ ret = CMD_CLOSE ; /* convert pending close */
+ break ;
+
+ default:
+ zabort("unknown vio->state") ;
+ break ;
+ } ;
- if (ret == CMD_SUCCESS)
- {
- vty->exec->to_do = cmd_do_command ;
- }
- else if ((ret == CMD_EOF) && (vio->vin_depth > 0))
+ vio->state = vc_running ; /* running in hiatus */
+
+ /* (1) Handle the return code.
+ *
+ * Deal here with the return codes that signify success, or signify
+ * success but some vin and/or vout need to be closed.
+ *
+ * Call uty_cmd_failed() to deal with return codes that signify some
+ * sort of failure. A failure generally means closing all the way to
+ * the vin_/vout_base, or possibly completely.
+ *
+ * Note that CMD_WAITING is immediately returned !
+ */
+ switch (ret)
+ {
+ case CMD_SUCCESS:
+ case CMD_EMPTY:
+ case CMD_HIATUS:
+ break ;
+
+ case CMD_WAITING:
+ assert(!vio->blocking) ;
+ vio->state = vc_waiting ;
+ return ret ; /* <<< exit here on CMD_WAITING */
+
+ case CMD_EOF:
+ case CMD_CLOSE:
+ uty_out_accept(vio) ; /* accept any buffered remarks. */
+ assert(vio->real_depth > 0) ;
+ --vio->real_depth ;
+ break ;
+
+ default:
+ /* If not any of the above, must be an error of some kind:
+ *
+ * * set real_depth to close all pipes and files.
+ *
+ * Depending on the type of vin_base/vout_base and the type of
+ * error, may or may not leave the bas I/O open.
+ *
+ * * create error messages in the vout_base.
+ */
+ vio->real_depth = uty_cmd_failed(vio, ret) ;
+ break ;
+ } ;
+
+ ret = CMD_SUCCESS ; /* OK, so far */
+
+ /* (2) Do we need to close one or more vin, or are we waiting for one to
+ * close ?
+ *
+ * The close will immediately close the input, and discard anything
+ * which has been buffered. The only difficulty with closing inputs
+ * is VIN_PIPE, where the "return" input (from the child stderr) may
+ * not yet have finished.
+ *
+ * For blocking vio, close operations will either complete or fail.
+ *
+ * For non-blocking vio, close operations may return CMD_WAITING
+ * (eg: VIN_PIPE where the child stderr is not yet at EOF, or the
+ * child completion status has not yet been collected). Where a
+ * close operation does not complete, the vf is marked vf_closing,
+ * and the stack stays at its current level.
+ *
+ * For hard errors will do "final" close, which immediately closes the
+ * input (and any pipe return) discarding any buffered input. Any errors
+ * that occur in the process are discarded.
+ */
+ while ((vio->vin_depth > vio->real_depth) && (ret == CMD_SUCCESS))
+ ret = uty_vin_pop(vio, vio->err_hard, vio->vty->exec->context) ;
+
+ /* (3) And, do we need to close one or more vout, or are we waiting for
+ * one to close ? If so:
+ *
+ * Any output after the current end_mark is discarded. Note that we
+ * do not actually close the vout_base.
+ *
+ * In all cases we push any remaining output and collect any remaining
+ * pipe return, and collect child termination condition.
+ *
+ * For blocking vio, close operations will either complete or fail.
+ *
+ * For non-blocking vio, close operations may return CMD_WAITING
+ * (eg: where the output buffers have not yet been written away).
+ * Where a close operation does not complete, the vf is marked
+ * vf_closing, and the stack stays at its current level.
+ *
+ * For hard errors will attempt to write away anything which is,
+ * pending, but will stop if would block and on any error, and close
+ * the output -- discarding any remaining output and any errors.
+ *
+ * NB: when we reach the vout_base, turn off the hard error -- so
+ * never "final" close the vout TODO error handling.
+ *
+ * Also: if we are at, or we reach, the vout_base, and there is an error
+ * message in hand, now is the time to move that to the obuf and
+ * push it.
+ */
+ while (ret == CMD_SUCCESS)
+ {
+ assert(vio->vout->depth_mark >= vio->vout_depth) ;
+
+ if (vio->vout_depth == 1)
+ {
+ assert(vio->vout->depth_mark == 1) ;
+
+ vio->err_hard = false ;
+
+ if (vio->ebuf != NULL)
{
- uty_vin_close(vio) ;
- uty_cmd_prepare(vio) ;
- continue ;
+ vio_fifo_copy(vio->obuf, vio->ebuf) ;
+ vio->ebuf = vio_fifo_reset(vio->ebuf, free_it) ;
+
+ ret = uty_cmd_out_push(vio->vout, vio->err_hard) ;
+
+ if (ret != CMD_SUCCESS)
+ break ;
} ;
- break ;
+ } ;
- default:
- zabort("unknown vin_type") ;
- } ;
+ if (vio->vout_depth == 0)
+ assert(vio->vout->depth_mark == 0) ;
- break ;
+ if (vio->vin_depth < vio->vout->depth_mark)
+ ret = uty_vout_pop(vio, vio->err_hard) ;
+ else
+ break ;
} ;
- VTY_UNLOCK() ;
+ /* (4) Quit now if not successful on stack adjustment
+ */
+ if (ret != CMD_SUCCESS)
+ {
+ if (ret == CMD_WAITING)
+ {
+ assert(!vio->blocking) ;
+ vio->state = vc_waiting ;
+ return ret ; /* <<< exit here on CMD_WAITING */
+ } ;
- return ret ;
+ if (ret == CMD_IO_ERROR)
+ {
+ vio->state = vc_io_error_trap ;
+ return ret ;
+ } ;
+
+ zabort("invalid return code") ;
+ } ;
+
+ /* (5) Having dealt with closing of files and adjustment of stack, may
+ * now be EOF on the vin_base.
+ */
+ if (vio->real_depth == 0)
+ {
+ vio->state = vc_stopped ;
+ return CMD_EOF ;
+ } ;
+
+ /* (6) All ready now to continue processing commands.
+ */
+ uty_cmd_prepare(vio) ; /* update vty->exec state */
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * When entering command loop, or after opening or closing a vin/vout object,
+ * update the vty->exec context.
+ *
+ * Output to the vout_base is suppressed for reading of configuration files.
+ *
+ * Reflection of the command line depends on the current context, and on the
+ * state of output suppression.
+ */
+static void
+uty_cmd_prepare(vty_io vio)
+{
+ cmd_exec exec = vio->vty->exec ;
+
+ exec->out_suppress = (vio->vty->type == VTY_CONFIG_READ) &&
+ (vio->vout_depth == 1) ;
+ exec->reflect = exec->context->reflect_enabled && !exec->out_suppress ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the full_lex flag for further commands, and for any further pipes.
+ */
+extern void
+vty_cmd_set_full_lex(vty vty, bool full_lex)
+{
+ VTY_LOCK() ;
+
+ vty->exec->context->full_lex = full_lex ;
+
+ VTY_UNLOCK() ;
} ;
/*------------------------------------------------------------------------------
@@ -304,127 +1117,249 @@ vty_cmd_fetch_line(vty vty)
*
* Advances the end_mark past the reflected line, so that output (in particular
* error stuff) is separate.
+ *
+ * NB: pushes the output, so that if the command takes a long time to process,
+ * it is visible while it proceeds.
*/
-extern void
+extern cmd_return_code_t
vty_cmd_reflect_line(vty vty)
{
- vio_fifo obuf ;
- qstring line ;
+ cmd_return_code_t ret ;
VTY_LOCK() ;
- obuf = vty->vio->obuf ; /* once locked */
- line = vty->exec->line ;
+ if (vty->vio->state != vc_running)
+ ret = CMD_HIATUS ;
+ else
+ {
+ vio_fifo obuf ;
+ qstring line ;
+
+ obuf = vty->vio->obuf ; /* once locked */
+ line = vty->exec->action->line ;
- vio_fifo_put_bytes(obuf, qs_char_nn(line), qs_len_nn(line)) ;
- vio_fifo_put_byte(obuf, '\n') ;
+ vio_fifo_put_bytes(obuf, qs_char_nn(line), qs_len_nn(line)) ;
+ vio_fifo_put_byte(obuf, '\n') ;
- uty_out_accept(vty->vio) ;
+ ret = uty_cmd_out_push(vty->vio->vout, false) ; /* not final */
+ } ;
VTY_UNLOCK() ;
+
+ return ret ;
} ;
/*------------------------------------------------------------------------------
- * Open the given file as in in pipe, if possible.
+ * Set the vio->depth_mark -- about to push vin and/or vout
+ */
+extern void
+uty_cmd_depth_mark(vty_io vio)
+{
+ vio->depth_mark = vio->vin_depth ;
+}
+
+/*------------------------------------------------------------------------------
+ * Open the given file as an in pipe, if possible.
*
* Puts error messages to vty if fails.
+ *
+ * NB: saves the current context to the current vin, before opening and pushing
+ * the new one.
*/
extern cmd_return_code_t
-vty_cmd_open_in_pipe_file(vty vty, qstring name, bool reflect)
+uty_cmd_open_in_pipe_file(vty_io vio, cmd_context context,
+ qstring name, cmd_pipe_type_t type)
{
cmd_return_code_t ret ;
- VTY_LOCK() ;
+ VTY_ASSERT_LOCKED() ;
- ret = uty_file_read_open(vty->vio, name, reflect) ;
+ if (vio->state != vc_running)
+ ret = CMD_HIATUS ;
+ else
+ {
+ ret = uty_file_read_open(vio, name, context) ;
- if (ret == CMD_SUCCESS)
- uty_cmd_prepare(vty->vio) ;
+ if (ret == CMD_SUCCESS)
+ {
+ context->reflect_enabled = (type & cmd_pipe_reflect) != 0 ;
+ context->parse_strict = true ;
- VTY_UNLOCK() ;
+ uty_cmd_prepare(vio) ;
+ } ;
+ } ;
return ret ;
} ;
+/*------------------------------------------------------------------------------
+ * Run the given shell command as an in pipe, if possible.
+ *
+ * Puts error messages to vty if fails.
+ *
+ * NB: saves the current context to the current vin, before opening and pushing
+ * the new one.
+ */
extern cmd_return_code_t
-vty_cmd_open_in_pipe_shell(vty vty)
+uty_cmd_open_in_pipe_shell(vty_io vio, cmd_context context, qstring command,
+ cmd_pipe_type_t type)
{
- return CMD_SUCCESS ;
+ cmd_return_code_t ret ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ if (vio->state != vc_running)
+ ret = CMD_HIATUS ;
+ else
+ {
+ ret = uty_pipe_read_open(vio, command, context) ;
+
+ if (ret == CMD_SUCCESS)
+ {
+ context->reflect_enabled = (type & cmd_pipe_reflect) != 0 ;
+ context->parse_strict = true ;
+
+ uty_cmd_prepare(vio) ;
+ } ;
+ } ;
+
+ return ret ;
} ;
+/*------------------------------------------------------------------------------
+ * Open the given file as an out pipe, if possible.
+ *
+ * Puts error messages to vty if fails.
+ */
extern cmd_return_code_t
-vty_cmd_open_out_pipe_file(vty vty, qstring name, bool append)
+uty_cmd_open_out_pipe_file(vty_io vio, cmd_context context, qstring name,
+ cmd_pipe_type_t type)
{
cmd_return_code_t ret ;
- VTY_LOCK() ;
+ if (vio->state != vc_running)
+ ret = CMD_HIATUS ;
+ else
+ {
+ ret = uty_file_write_open(vio, name,
+ ((type & cmd_pipe_append) != 0), context) ;
+ if (ret == CMD_SUCCESS)
+ uty_cmd_prepare(vio) ;
+ } ;
- ret = uty_file_write_open(vty->vio, name, append) ;
+ return ret ;
+} ;
- if (ret == CMD_SUCCESS)
- uty_cmd_prepare(vty->vio) ;
+/*------------------------------------------------------------------------------
+ * Open the given shell command as an out pipe, if possible.
+ *
+ * Puts error messages to vty if fails.
+ */
+extern cmd_return_code_t
+uty_cmd_open_out_pipe_shell(vty_io vio, cmd_context context, qstring command,
+ cmd_pipe_type_t type)
+{
+ cmd_return_code_t ret ;
- VTY_UNLOCK() ;
+ if (vio->state != vc_running)
+ ret = CMD_HIATUS ;
+ else
+ {
+ ret = uty_pipe_write_open(vio, command,
+ ((type & cmd_pipe_shell_only) !=0)) ;
+
+ if (ret == CMD_SUCCESS)
+ uty_cmd_prepare(vio) ;
+ } ;
return ret ;
} ;
+/*------------------------------------------------------------------------------
+ * Open "/dev/null" as an out pipe, if possible.
+ *
+ * Puts error messages to vty if fails.
+ */
extern cmd_return_code_t
-vty_cmd_open_out_dev_null(vty vty)
+uty_cmd_open_out_dev_null(vty_io vio)
{
+ cmd_return_code_t ret ;
vio_vf vf ;
- VTY_LOCK() ;
+ VTY_ASSERT_LOCKED() ;
- vf = uty_vf_new(vty->vio, "dev_null", -1, vfd_none, vfd_io_none) ;
- uty_vout_open(vty->vio, vf, VOUT_DEV_NULL, NULL, NULL, 0) ;
+ if (vio->state != vc_running)
+ ret = CMD_HIATUS ;
+ else
+ {
+ vf = uty_vf_new(vio, "dev_null", -1, vfd_none, vfd_io_none) ;
+ uty_vout_push(vio, vf, VOUT_DEV_NULL, NULL, NULL, 0) ;
- vf->out_enabled = false ;
+ uty_cmd_prepare(vio) ;
- VTY_UNLOCK() ;
- return CMD_SUCCESS ;
+ ret = CMD_SUCCESS ;
+ } ;
+
+ return ret ;
} ;
-extern cmd_return_code_t
-vty_cmd_open_out_pipe_shell(vty vty)
+/*------------------------------------------------------------------------------
+ * Complete the given file name, if not rooted.
+ *
+ * Returns: given or new qpath (if given was NULL)
+ */
+extern qpath
+uty_cmd_path_name_complete(qpath dst, const char* name, cmd_context context)
{
- return CMD_SUCCESS ;
+ if (*name == '/')
+ return qpath_set(dst, name) ; /* done if is rooted */
+
+ if (*name != '~')
+ dst = qpath_copy(dst, context->dir_cd) ;
+ else if ((*(name + 1) == '/') || (*(name + 1) == '\0'))
+ dst = qpath_copy(dst, context->dir_home) ;
+ else if ((*(name + 1) == '.') &&
+ ( (*(name + 2) == '/') || (*(name + 2) == '\0')) )
+ dst = qpath_copy(dst, context->dir_here) ;
+ else
+ return qpath_set(dst, name) ; /* no idea... probably an error */
+
+ return qpath_append_str(dst, name) ; /* create the full path */
} ;
/*------------------------------------------------------------------------------
* Command has completed successfully.
*
- * An output generated by the command is now pushed if exec->out_enabled,
+ * An output generated by the command is now pushed unless exec->out_suppress,
* or discarded.
- *
- * If the vin_depth < vout_depth, then close vouts until the depth is equal.
*/
extern cmd_return_code_t
vty_cmd_success(vty vty)
{
+ cmd_return_code_t ret ;
vty_io vio ;
VTY_LOCK() ;
vio = vty->vio ; /* once locked */
- if (!vio_fifo_empty_tail(vio->obuf))
- {
- if (vty->exec->out_enabled)
- uty_cmd_out_push(vio) ;
- else
- uty_out_clear(vio) ;
- } ;
+ ret = CMD_SUCCESS ;
- /* Deal with closing out pipe on individual command */
- while (vio->vout_depth > vio->vin_depth)
+ if (vio->state != vc_running)
+ ret = CMD_HIATUS ;
+ else
{
- uty_vout_close(vio, false) ; /* ignore failures TODO */
- uty_cmd_prepare(vio) ; /* set new state */
+ if (!vio_fifo_tail_empty(vio->obuf))
+ {
+ if (!vty->exec->out_suppress)
+ ret = uty_cmd_out_push(vio->vout, false) ; /* not final */
+ else
+ uty_out_clear(vio) ;
+ } ;
} ;
VTY_UNLOCK() ;
- return CMD_SUCCESS ;
+ return ret ;
} ;
/*------------------------------------------------------------------------------
@@ -432,12 +1367,21 @@ vty_cmd_success(vty vty)
*
* See uty_cmd_out_push() below.
*/
-extern void
+extern cmd_return_code_t
vty_cmd_out_push(vty vty)
{
+ cmd_return_code_t ret ;
+
VTY_LOCK() ;
- uty_cmd_out_push(vty->vio) ;
+
+ if (vty->vio->state != vc_running)
+ ret = CMD_HIATUS ;
+ else
+ ret = uty_cmd_out_push(vty->vio->vout, false) ; /* not final */
+
VTY_UNLOCK() ;
+
+ return ret ;
} ;
/*------------------------------------------------------------------------------
@@ -449,149 +1393,87 @@ vty_cmd_out_push(vty vty)
*
* Advances the end_mark past the stuff pushed.
*
- * NB: takes no notice of vf->out_enabled... this is used when outputting
- * any error messages when reading the configuration file.
+ * NB: takes no notice of vf->out_suppress, which applies only to buffered
+ * output present when successfully complete a command -- vty_cmd_success().
*
* TODO: worry about closing state !
+ * TODO: need a vf level one of these.
*/
-extern void
-uty_cmd_out_push(vty_io vio)
+extern cmd_return_code_t
+uty_cmd_out_push(vio_vf vf, bool final) /* TODO: write errors & blocking */
{
cmd_return_code_t ret ;
- vio_vf vf ;
- vf = vio->vout ;
+ VTY_ASSERT_LOCKED() ;
- assert(vio->obuf == vf->obuf) ;
+ vio_fifo_step_end_mark(vf->obuf) ; /* advance the end mark */
- uty_out_accept(vio) ; /* advance the end mark */
+ ret = CMD_SUCCESS ;
- if (vf->vout_state != vf_open)
+ switch (vf->vout_state)
{
- switch (vf->vout_state)
- {
- case vf_closed:
- case vf_eof:
- ret = CMD_EOF ; /* TODO what's to do ? */
- break ;
-
- case vf_error:
- ret = CMD_IO_ERROR ;
- break ;
-
- case vf_closing:
- ret = CMD_CLOSE ; /* TODO continue with output ? */
- break ;
-
- default:
- zabort("invalid vf->vout_state") ;
- break ;
- } ;
-
- return ; /* TODO errors ?? */
- } ;
-
- switch (vio->vout->vout_type)
- {
- case VOUT_NONE:
- zabort("invalid vout_none") ;
- break ;
-
- case VOUT_TERM:
- uty_cli_out_push(vf->cli) ;
- break ;
-
- case VOUT_VTYSH:
- /* Kick the writer */
- break ;
-
- case VOUT_FILE:
- uty_file_out_push(vf) ;
- break ;
-
- case VOUT_PIPE:
- /* Kick the writer */
- break ;
-
- case VOUT_CONFIG:
- /* Kick the writer */
- break ;
-
- case VOUT_DEV_NULL:
- uty_out_clear(vio) ; /* clear fifo, keep end mark */
- break ;
-
- case VOUT_STDOUT:
- vio_fifo_fwrite(vio->obuf, stdout) ;
- break ;
-
- case VOUT_STDERR:
- vio_fifo_fwrite(vio->obuf, stderr) ;
- break ;
+ case vf_open:
+ case vf_closing:
+ switch (vf->vout_type)
+ {
+ case VOUT_NONE:
+ zabort("invalid vout_none") ;
+ break ;
- default:
- zabort("unknown vout_type") ;
- } ;
-} ;
+ case VOUT_TERM:
+ ret = uty_term_out_push(vf, final) ;
+ break ;
-/*------------------------------------------------------------------------------
- * The command loop has exited, with the given return code.
- *
- * There are a number of return codes that are treated as success. On any
- * of those, does vty_cmd_success() (which has no effect if already done).
- *
- * If not a success return code, then close down any pipe-work, and create
- * suitable error message.
- *
- * If is any form of success, returns CMD_SUCCESS -- otherwise returns the
- * return code as given.
- */
-extern cmd_return_code_t
-vty_cmd_loop_exit(vty vty, cmd_return_code_t ret)
-{
- bool close ;
+ case VOUT_VTYSH:
+ /* Kick the writer */
+ break ;
- /* Deal with the several names of success and the many names of failure.
- */
- close = false ;
+ case VOUT_FILE:
+ ret = uty_file_out_push(vf, final) ;
+ break ;
- switch(ret)
- {
- case CMD_CLOSE:
- case CMD_EOF:
- close = true ;
- fall_through ;
+ case VOUT_PIPE:
+ ret = uty_pipe_out_push(vf, final) ;
+ break ;
- case CMD_SUCCESS:
- case CMD_EMPTY:
- ret = vty_cmd_success(vty) ;
- break ;
+ case VOUT_CONFIG:
+ ret = uty_file_out_push(vf, final) ; /* treat as file */
+ break ;
- default:
- /* If not CMD_SUCCESS, the treat as some sort of error:
- *
- * * close down all pipes -- leaving just the vin_base/vout_base.
- * * create error messages in the vout_base.
- */
- vty_cmd_failed(vty, ret) ;
- break ;
- } ;
+ case VOUT_DEV_NULL:
+ case VOUT_SHELL_ONLY:
+ vio_fifo_clear(vf->obuf, false) ; /* keep end mark */
+ break ;
- /* They think it's all over... */
+ case VOUT_STDOUT:
+ vio_fifo_fwrite(vf->obuf, stdout) ; // TODO errors
+ break ;
- VTY_LOCK() ;
+ case VOUT_STDERR:
+ vio_fifo_fwrite(vf->obuf, stderr) ; // TODO errors
+ break ;
- vty->vio->cmd_running = false ;
+ default:
+ zabort("unknown vout_type") ;
+ } ;
+ break ;
- if (vty->vio->vin->vin_type == VIN_TERM)
- uty_cli_done_command(vty->vio->vin->cli, vty->node) ;
+ case vf_closed:
+ case vf_timed_out:
+ break ; /* immediate success ! */
- /* ... it is now. */
+ case vf_eof:
+ zabort("vf->vout cannot be vf_eof") ;
+ break ;
- if (close)
- uty_close(vty->vio, false, NULL) ;
+ case vf_error:
+ ret = CMD_IO_ERROR ;
+ break ;
- VTY_UNLOCK() ;
+ default:
+ zabort("unknown vf->vout_state") ;
+ break ;
+ } ;
return ret ;
} ;
@@ -599,19 +1481,32 @@ vty_cmd_loop_exit(vty vty, cmd_return_code_t ret)
/*------------------------------------------------------------------------------
* Dealing with error of some kind.
*
- * Error is reported on the vout_base. Whatever the command has written
- * so far will be appended to the reporting of the error location.
+ * The current vio->obuf will have an end_mark. After the end_mark will be
+ * any output generated since the start of the current command (or any out_push
+ * since then). For command errors, that output is expected to be error
+ * messages associated with the error.
+ *
+ * A new "ebuf" fifo is created, and the location of the error is written to
+ * that fifo. Once the contents of the "ebuf" are output, the command line in
+ * which the error occurred should be the last thing output.
+ *
+ * Any other error message is then appended to the ebuf.
*
- * If there is only one vout, then:
+ * For command errors, the tail of the vio->obuf (stuff after the end_mark) is
+ * moved to the ebuf.
*
- * - if is VOUT_TERM, then need to wipe the command line. The failing
- * line will be the last output line on screen (unless monitor output,
- * which can do nothing about).
+ * Deals with:
*
- * - if is VOUT_VTYSH, then the failing line will be the last output line
- * on the screen.
+ * CMD_WARNING = TODO
+ * CMD_ERROR =>
*
- * - if is VOUT_xxx, then nothing will have been reflected.
+ * CMD_ERR_PARSING, parser: general parser error
+ * CMD_ERR_NO_MATCH, parser: command/argument not recognised
+ * CMD_ERR_AMBIGUOUS, parser: more than on command matches
+ * CMD_ERR_INCOMPLETE,
+ *
+ * CMD_IO_ERROR I/O -- failed :-(
+ * CMD_IO_TIMEOUT I/O -- timed out :-(
*
* In any event, need to separate out any output from the failing command,
* so that can report error location and type, before showing the error
@@ -620,116 +1515,112 @@ vty_cmd_loop_exit(vty vty, cmd_return_code_t ret)
* NB: does not expect to see all the possible CMD_XXX return codes (see
* below), but treats all as a form of error !
*/
-static void
-vty_cmd_failed(vty vty, cmd_return_code_t ret)
+static uint
+uty_cmd_failed(vty_io vio, cmd_return_code_t ret)
{
- vty_io vio ;
- vio_fifo tbuf ;
- ulen eloc ;
+ ulen indent ;
- VTY_LOCK() ;
- vio = vty->vio ; /* once locked */
-
- /* Copy command output to temporary, and remove from the obuf.
- *
- * Any command output should be messages explaining the error, and we want
- * those (a) on the base vout, and (b) after description of where the error
- * occurred.
- */
- tbuf = vio_fifo_copy_tail(NULL, vio->obuf) ;
- vio_fifo_back_to_end_mark(vio->obuf, true) ;
-
- /* Can now close everything in the vout stack, so all further output
- * is to the vout_base.
- */
- uty_vout_close_stack(vio, false) ; /* not "final" */
+ VTY_ASSERT_LOCKED() ;
/* Process the vin stack to generate the error location(s) */
- uty_show_error_location(vio->vin) ;
-
- if (vio->vin->vin_type == VIN_TERM)
- eloc = uty_cli_prompt_len(vio->vin->cli) ;
+ if (vio->ebuf != NULL)
+ vio_fifo_clear(vio->ebuf, true) ;
else
- eloc = 0 ;
+ vio->ebuf = vio_fifo_init_new(NULL, 1000) ;
- /* Can now close everything in the vin stack */
- uty_vin_close_stack(vio) ;
+ indent = uty_show_error_context(vio->ebuf, vio->vin) ;
/* Now any additional error message if required */
switch (ret)
{
- qstring qs ;
-
case CMD_WARNING:
- if (vio_fifo_empty(tbuf))
- uty_out(vio, "%% WARNING: non-specific warning\n") ;
+ if (vio_fifo_tail_empty(vio->obuf))
+ vio_fifo_printf(vio->ebuf, "%% WARNING: non-specific warning\n") ;
break ;
case CMD_ERROR:
- if (vio_fifo_empty(tbuf))
- uty_out(vio, "%% ERROR: non-specific error\n") ;
- break ;
-
- case CMD_IO_ERROR:
+ if (vio_fifo_tail_empty(vio->obuf))
+ vio_fifo_printf(vio->ebuf, "%% ERROR: non-specific error\n") ;
break ;
case CMD_ERR_PARSING:
- qs = qs_set_fill(NULL, eloc + vio->vty->exec->parsed->eloc, "....") ;
- uty_out(vio, "%s^\n%% %s\n", qs_string(qs),
- vio->vty->exec->parsed->emess) ;
- qs_reset(qs, free_it) ;
+ cmd_get_parse_error(vio->ebuf, vio->vty->exec->parsed, indent) ;
break ;
case CMD_ERR_NO_MATCH:
- uty_out(vio, "%% Unknown command.\n") ;
+ vio_fifo_printf(vio->ebuf, "%% Unknown command.\n") ;
break;
case CMD_ERR_AMBIGUOUS:
- uty_out(vio, "%% Ambiguous command.\n");
+ vio_fifo_printf(vio->ebuf, "%% Ambiguous command.\n");
break;
case CMD_ERR_INCOMPLETE:
- uty_out(vio, "%% Command incomplete.\n");
+ vio_fifo_printf(vio->ebuf, "%% Command incomplete.\n");
break;
- case CMD_SUCCESS:
- case CMD_SUCCESS_DAEMON:
- case CMD_CLOSE:
- case CMD_EMPTY:
- case CMD_WAITING:
- case CMD_EOF:
- case CMD_COMPLETE_FULL_MATCH:
- case CMD_COMPLETE_MATCH:
- case CMD_COMPLETE_LIST_MATCH:
- case CMD_COMPLETE_ALREADY:
+ case CMD_IO_ERROR:
+ break ;
+
+ case CMD_IO_TIMEOUT:
+ break ;
+
default:
- uty_out(vio, "%% Unexpected return code (%d).\n", (int)ret) ;
+ vio_fifo_printf(vio->ebuf, "%% Unexpected return code (%d).\n", (int)ret);
break ;
} ;
- /* Now stick the tbuf onto the end of the output & discard tbuf. */
- vio_fifo_copy(vio->obuf, tbuf) ;
- vio_fifo_reset(tbuf, free_it) ;
-
- uty_cmd_out_push(vio) ;
+ /* Now stick the obuf tail onto the end of the ebuf & discard the tail of
+ * the obuf.
+ */
+ vio_fifo_copy_tail(vio->ebuf, vio->obuf) ;
+ vio_fifo_back_to_end_mark(vio->obuf, true) ;
- VTY_UNLOCK() ;
+ /* Decide what stack level to close back to. */
+ return (vio->vty->type == VTY_TERMINAL) ? 1 : 0 ; // TODO I/O error ??
} ;
-static void
-uty_show_error_location(vio_vf vf)
+/*------------------------------------------------------------------------------
+ * In the given fifo, construct message giving the context in which an error
+ * has occurred.
+ *
+ * For file and pipe input (including config file), it is assumed that no
+ * command line has been reflected, so the context is given as:
+ *
+ * % on line 99 of xxxx:
+ * <the command line -- noting small indent>
+ *
+ * For interactive input, if the stack depth is 1, then it is assumed that the
+ * command line is the last complete line output. Otherwise the context is
+ * given as:
+ *
+ * % in command line:
+ * <the command line -- noting small indent>
+ *
+ * The context starts with level 1 of the vin stack, and ends with the current
+ * level.
+ *
+ * Returns: "eloc" -- start of command line at the current level.
+ */
+static uint
+uty_show_error_context(vio_fifo ebuf, vio_vf vf)
{
vio_vf vf_next ;
+ uint indent ;
/* Recurse until hit end of the vin stack */
vf_next = ssl_next(vf, vin_next) ;
if (vf_next != NULL)
- uty_show_error_location(vf_next) ;
+ uty_show_error_context(ebuf, vf_next) ;
else
assert(vf == vf->vio->vin_base) ;
- /* On the way back, add the error location for each vin entry */
+ /* On the way back, add the error location for each vin entry
+ * and establish the location of the start of the command line as shown.
+ */
+ indent = 0 ;
+
switch (vf->vin_type)
{
case VIN_NONE:
@@ -737,20 +1628,20 @@ uty_show_error_location(vio_vf vf)
break ;
case VIN_TERM:
- /* Wipe the current command line */
-// uty_term_show_error_location(vf, loc) ;
+ indent = uty_term_show_error_context(vf, ebuf, vf->vio->vin_depth) ;
break ;
case VIN_VTYSH:
+// eloc = uty_term_show_error_context(vf, ebuf, vf->vio->vin_depth) ;
break ;
case VIN_FILE:
-
case VIN_PIPE:
-
case VIN_CONFIG:
- uty_out(vf->vio, "%% on line %d of %s:\n", vf->line_number, vf->name) ;
- uty_out(vf->vio, "%s\n", qs_make_string(vf->cl)) ;
+ vio_fifo_printf(ebuf, "%% on line %d of %s:\n",
+ vf->line_number, vf->name) ;
+ vio_fifo_printf(ebuf, " %s\n", qs_make_string(vf->cl)) ;
+ indent = 2 ;
break ;
case VIN_DEV_NULL:
@@ -759,6 +1650,8 @@ uty_show_error_location(vio_vf vf)
default:
zabort("unknown vin_type") ;
} ;
+
+ return indent ;
} ;
/*==============================================================================
@@ -768,96 +1661,96 @@ uty_show_error_location(vio_vf vf)
*/
/*------------------------------------------------------------------------------
- * Attempt to gain the configuration symbol of power
- *
- * If succeeds, set the given node.
+ * Attempt to gain the configuration symbol of power -- may already own it !
*
- * Returns: true <=> now own the symbol of power.
+ * Returns: true <=> now own the symbol of power (or already did).
*/
-extern bool
-vty_config_lock (struct vty *vty, enum node_type node)
+extern cmd_return_code_t
+vty_cmd_config_lock (vty vty)
{
- bool result;
-
VTY_LOCK() ;
+ uty_cmd_config_lock(vty) ;
+ VTY_UNLOCK() ;
- if (!host.config)
- {
- vty->config = true ;
- host.config = true ;
- } ;
+ if (vty->config)
+ return CMD_SUCCESS ;
- result = vty->config;
+ vty_out (vty, "VTY configuration is locked by other VTY\n") ;
+ return CMD_WARNING ;
+} ;
- if (result)
- vty->node = node ;
+/*------------------------------------------------------------------------------
+ * Attempt to gain the configuration symbol of power -- may already own it !
+ *
+ * Returns: true <=> now own the symbol of power (or already did).
+ */
+static void
+uty_cmd_config_lock (vty vty)
+{
+ if (!host.config) /* If nobody owns the lock... */
+ {
+ host.config = true ; /* ...rope it... */
+ vty->config = true ;
- VTY_UNLOCK() ;
+ do
+ ++host.config_brand ; /* ...update the brand... */
+ while (host.config_brand == 0) ;
- return result;
+ vty->config_brand = host.config_brand ; /* ...brand it. */
+ }
+ else /* Somebody owns the lock... */
+ {
+ if (vty->config) /* ...if we think it is us, check brand */
+ assert(host.config_brand == vty->config_brand) ;
+ } ;
}
/*------------------------------------------------------------------------------
- * Give back the configuration symbol of power -- if own it.
- *
- * Set the given node -- which must be <= MAX_NON_CONFIG_NODE
+ * Check that given node and ownership of configuration symbol of power...
+ * ...see below.
*/
extern void
-vty_config_unlock (struct vty *vty, enum node_type node)
+vty_cmd_config_lock_check(vty vty, node_type_t node)
{
VTY_LOCK() ;
- uty_config_unlock(vty, node);
+ uty_cmd_config_lock_check(vty, node) ;
VTY_UNLOCK() ;
-}
+} ;
/*------------------------------------------------------------------------------
- * Give back the configuration symbol of power -- if own it.
+ * Check that given node and ownership of configuration symbol of power
+ * are mutually consistent.
+ *
+ * If node > MAX_NON_CONFIG_NODE, must own the symbol of power.
*
- * Set the given node -- which must be <= MAX_NON_CONFIG_NODE
+ * If node <= MAX_NON_CONFIG_NODE, will release symbol of power, if own it,
+ * PROVIDED is at vin_depth <= 1 !!
*/
-extern void
-uty_config_unlock (struct vty *vty, node_type_t node)
+static void
+uty_cmd_config_lock_check(vty vty, node_type_t node)
{
VTY_ASSERT_LOCKED() ;
- if (host.config && vty->config)
+ if (vty->config)
{
- vty->config = false ;
- host.config = false ;
- }
-
- assert(node <= MAX_NON_CONFIG_NODE) ;
- vty->node = node ;
-} ;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ /* We think we own it, so we better had */
+ assert(host.config) ;
+ assert(host.config_brand == vty->config_brand) ;
+ /* If no longer need it, release */
+ if ((node <= MAX_NON_CONFIG_NODE) && (vty->vio->vin_depth <= 1))
+ {
+ host.config = false ;
+ vty->config = false ;
+ } ;
+ }
+ else
+ {
+ /* We don't think we own it, so we had better not */
+ if (host.config)
+ assert(host.config_brand != vty->config_brand) ;
-/*------------------------------------------------------------------------------
- * Result of command is to close the input.
- *
- * Posts the reason for the close.
- *
- * Returns: CMD_CLOSE
- */
-extern cmd_return_code_t
-uty_cmd_close(vty_io vio, const char* reason)
-{
- vio->close_reason = qs_set(NULL, reason) ;
- return CMD_CLOSE ;
+ /* Also, node had better not require that we do. */
+ assert(node <= MAX_NON_CONFIG_NODE) ;
+ } ;
} ;
-
-
diff --git a/lib/vty_command.h b/lib/vty_command.h
index 7b13721d..125b9a03 100644
--- a/lib/vty_command.h
+++ b/lib/vty_command.h
@@ -30,32 +30,38 @@
#include "vty_local.h"
#include "vty_io.h"
-extern cmd_return_code_t uty_cmd_dispatch(vty_io vio, cmd_do_t to_do,
- qstring line) ;
+extern void vty_cmd_loop_prepare(vty vty) ;
+extern void uty_cmd_loop_enter(vty_io vio) ;
+extern void uty_cmd_signal(vty_io vio, cmd_return_code_t ret) ;
+extern void uty_cmd_loop_close(vty_io vio, bool curtains) ;
+extern void vty_cmd_loop_exit(vty vty) ;
+
extern cmd_return_code_t vty_cmd_fetch_line(vty vty) ;
+extern cmd_return_code_t vty_cmd_hiatus(vty vty, cmd_return_code_t ret) ;
extern cmd_return_code_t vty_cmd_special(vty vty) ;
-extern void vty_cmd_reflect_line(vty vty) ;
-extern void vty_cmd_out_push(vty vty) ;
-extern void uty_cmd_out_push(vty_io vio) ;
-
-extern cmd_return_code_t vty_cmd_open_in_pipe_file(vty vty, qstring name,
- bool reflect) ;
-extern cmd_return_code_t vty_cmd_open_in_pipe_shell(vty vty) ;
-extern cmd_return_code_t vty_cmd_open_out_pipe_file(vty vty, qstring name,
- bool append) ;
-extern cmd_return_code_t vty_cmd_open_out_dev_null(vty vty) ;
-extern cmd_return_code_t vty_cmd_open_out_pipe_shell(vty vty) ;
-extern cmd_return_code_t vty_cmd_success(vty vty) ;
-extern cmd_return_code_t vty_cmd_loop_exit(vty vty, cmd_return_code_t ret) ;
+extern cmd_return_code_t vty_cmd_can_auth_enable(vty vty) ;
+extern cmd_return_code_t vty_cmd_reflect_line(vty vty) ;
+extern cmd_return_code_t vty_cmd_out_push(vty vty) ;
+extern cmd_return_code_t uty_cmd_out_push(vio_vf vf, bool final) ;
-extern void uty_cmd_prepare(vty_io vio) ;
+extern void vty_cmd_set_full_lex(vty vty, bool full_lex) ;
-extern cmd_return_code_t uty_cmd_close(vty_io vio, const char* reason) ;
-
-extern bool vty_config_lock (vty vty, node_type_t node);
-extern void vty_config_unlock (vty vty, node_type_t node);
-extern void uty_config_unlock (vty vty, node_type_t node) ;
+extern void uty_cmd_depth_mark(vty_io vio) ;
+extern cmd_return_code_t uty_cmd_open_in_pipe_file(vty_io vio,
+ cmd_context context, qstring name, cmd_pipe_type_t type) ;
+extern cmd_return_code_t uty_cmd_open_in_pipe_shell(vty_io vio,
+ cmd_context context, qstring command, cmd_pipe_type_t type) ;
+extern cmd_return_code_t uty_cmd_open_out_pipe_file(vty_io vio,
+ cmd_context context, qstring name, cmd_pipe_type_t type) ;
+extern cmd_return_code_t uty_cmd_open_out_dev_null(vty_io vio) ;
+extern cmd_return_code_t uty_cmd_open_out_pipe_shell(vty_io vio,
+ cmd_context context, qstring command, cmd_pipe_type_t type) ;
+extern qpath uty_cmd_path_name_complete(qpath dst, const char* name,
+ cmd_context context) ;
+extern cmd_return_code_t vty_cmd_success(vty vty) ;
+extern cmd_return_code_t vty_cmd_config_lock (vty vty) ;
+extern void vty_cmd_config_lock_check(struct vty *vty, node_type_t node) ;
#endif /* _ZEBRA_VTY_COMMAND_H */
diff --git a/lib/vty_common.h b/lib/vty_common.h
index 7c0929b8..04429d47 100644
--- a/lib/vty_common.h
+++ b/lib/vty_common.h
@@ -44,7 +44,7 @@
* The "struct vty" is used extensively across the Quagga daemons, where it
* has two functions relating to command handling as:
*
- * 1) a "file handle" for output produced by commands
+ * 1) a "handle" for output produced by commands
*
* 2) the holder of some context -- notably the current command "node" -- for
* command execution to use
@@ -53,7 +53,7 @@
* factored out into the "struct vty_io" -- opaque to users of the struct vty.
*
* There is also context used when parsing and executing commands which is
- * private to command.c et al, and is factored out into the "struct execution"
+ * private to command.c et al, and is factored out into the "struct cmd_exec"
* -- also opaque to users of the struct vty.
*/
enum vty_type /* Command output */
@@ -110,18 +110,30 @@ struct vty
void* index_sub ;
/* In configure mode (owner of the symbol of power) */
- bool config;
+ bool config ; /* we own */
+ ulong config_brand ; /* and this is our mark */
/*----------------------------------------------------------------------------
* The current cmd_exec environment -- used in command_execute.c et al
*
- * This is accessed freely by the command handling code while
- * vio->cmd_running.
+ * This is accessed freely by the command handling code because there is
+ * only one thread of execution per vty -- though for some vty types (notably
+ * VTY_TERMINAL) that may be in the vty_cli_thread or in the vty_cmd_thread
+ * at different times.
+ *
+ * Where information from the vio is required, the VTY_LOCK is acquired.
*/
struct cmd_exec* exec ; /* one per vty */
/*----------------------------------------------------------------------
* The following is used inside vty.c etc only -- under VTY_LOCK.
+ *
+ * The lock is required because the vty_cli_thread may be doing I/O and
+ * other stuff at the same time as the vty_cmd_thread is doing I/O, or at the
+ * same time as other vty are being serviced.
+ *
+ * Could have one lock per vty -- but would then need a lock for the common
+ * parts of the cli thread, so one lock keeps things relatively simple.
*/
struct vty_io* vio ; /* one per vty */
} ;
diff --git a/lib/vty_io.c b/lib/vty_io.c
index 4b50c526..dadfd091 100644
--- a/lib/vty_io.c
+++ b/lib/vty_io.c
@@ -44,6 +44,7 @@
#include <arpa/telnet.h>
#include <sys/un.h> /* for VTYSH */
#include <sys/socket.h>
+#include <wait.h>
#define VTYSH_DEBUG 0
@@ -88,8 +89,10 @@ uty_out(vty_io vio, const char *format, ...)
/* Watch-dog timer. */
static vio_timer_t vty_watch_dog ;
-static vty_timer_time uty_watch_dog_bark(vio_timer_t* timer, void* info) ;
-static bool uty_death_watch_scan(bool final) ;
+static vty_timer_time uty_watch_dog_bark(vio_timer timer, void* info) ;
+static void uty_death_watch_scan(bool final) ;
+
+static vty_io uty_dispose(vty_io vio) ;
/*------------------------------------------------------------------------------
* Initialise watch dog -- at start-up time.
@@ -97,7 +100,7 @@ static bool uty_death_watch_scan(bool final) ;
extern void
uty_watch_dog_init(void)
{
- vio_timer_init(&vty_watch_dog, NULL, NULL) ; /* empty */
+ vio_timer_init_new(vty_watch_dog, NULL, NULL) ; /* empty */
} ;
/*------------------------------------------------------------------------------
@@ -106,8 +109,8 @@ uty_watch_dog_init(void)
extern void
uty_watch_dog_start(void)
{
- vio_timer_init(&vty_watch_dog, uty_watch_dog_bark, NULL) ;
- vio_timer_set(&vty_watch_dog, VTY_WATCH_DOG_INTERVAL) ;
+ vio_timer_init_new(vty_watch_dog, uty_watch_dog_bark, NULL) ;
+ vio_timer_set(vty_watch_dog, VTY_WATCH_DOG_INTERVAL) ;
} ;
/*------------------------------------------------------------------------------
@@ -118,7 +121,7 @@ uty_watch_dog_start(void)
extern void
uty_watch_dog_stop(void)
{
- vio_timer_reset(&vty_watch_dog) ;
+ vio_timer_reset(vty_watch_dog, keep_it) ;
uty_death_watch_scan(true) ; /* scan the death-watch list */
}
@@ -126,7 +129,7 @@ uty_watch_dog_stop(void)
* Watch dog vio_timer action
*/
static vty_timer_time
-uty_watch_dog_bark(vio_timer_t* timer, void* info)
+uty_watch_dog_bark(vio_timer timer, void* info)
{
cmd_host_name(true) ; /* check for host name change */
@@ -136,49 +139,40 @@ uty_watch_dog_bark(vio_timer_t* timer, void* info)
} ;
/*------------------------------------------------------------------------------
- * Scan the death watch list.
- *
- * A vty can finally be freed if it is closed and there is no command running.
+ * Process the death watch list -- anything on the list can be disposed of.
*
- * At curtains the command running state is forced off.
+ * At curtains...
*/
-static bool
-uty_death_watch_scan(bool curtains)
+static void
+uty_death_watch_scan(bool final)
{
- vty_io vio ;
- vty_io next ;
+ VTY_ASSERT_CLI_THREAD() ;
- next = vio_death_watch ;
- while (next != NULL)
+ /* Dispose of anything on the death watch list. */
+
+ while (vio_death_watch != NULL)
{
- vio = next ;
- next = sdl_next(vio, vio_list) ;
+ vty vty ;
+ vty_io vio ;
- /* If this is curtains, override cmd_running ! */
- if (curtains)
- vio->cmd_running = false ;
+ vio = vio_death_watch ;
+ vio_death_watch = sdl_next(vio, vio_list) ; /* take off death watch */
- if (uty_close(vio, curtains, NULL))
- {
- vty vty = vio->vty ;
+ vty = vio->vty ;
- sdl_del(vio_death_watch, vio, vio_list) ;
+ vty->vio = uty_dispose(vty->vio) ;
+ assert(vty->exec == NULL) ;
- cmd_exec_free(vty->exec) ;
- XFREE(MTYPE_VTY, vty->vio) ;
- XFREE(MTYPE_VTY, vty) ;
- } ;
+ XFREE(MTYPE_VTY, vty) ;
} ;
-
- return (vio_death_watch == NULL) ;
} ;
/*==============================================================================
* Prototypes.
*/
-
-static void uty_vf_read_close(vio_vf vf) ;
-static bool uty_vf_write_close(vio_vf vf, bool final) ;
+static void uty_vout_close_reason(vio_vf vf, const char* reason) ;
+static cmd_return_code_t uty_vf_read_close(vio_vf vf, bool final) ;
+static cmd_return_code_t uty_vf_write_close(vio_vf vf, bool final) ;
static vio_vf uty_vf_free(vio_vf vf) ;
/*==============================================================================
@@ -191,30 +185,28 @@ static vio_vf uty_vf_free(vio_vf vf) ;
*
* Caller must complete the initialisation of the vty_io, which means:
*
- * * constructing a suitable vio_vf and doing uty_vin_open() to set the
+ * * constructing a suitable vio_vf and doing uty_vin_push() to set the
* vin_base.
*
* All vty_io MUST have a vin_base, even if it is /dev/null.
*
- * * constructing a suitable vio_vf and doing uty_vout_open() to set the
- * vout_base.
+ * * constructing a suitable vio_vf and doing uty_vout_push() to set the
+ * vout_base and the vio->obuf.
*
* All vty_io MUST have a vout_base, even if it is /dev/null.
*
- * * setting the vio->obuf to the vout_base obuf... TODO ???
+ * * setting vio->cli, if required
*
- * * setting vio->cli, if required.
+ * * etc.
*
- * Caller must also set the initial vty->node, if any.
+ * No "exec" is allocated. That is done when the command loop is entered.
*/
extern vty
-uty_new(vty_type_t type)
+uty_new(vty_type_t type, node_type_t node)
{
vty vty ;
vty_io vio ;
- bool execution ;
-
VTY_ASSERT_LOCKED() ;
/* Basic allocation */
@@ -224,20 +216,22 @@ uty_new(vty_type_t type)
*
* type = X -- set to actual type, below
*
- * node = NULL_NODE -- set to something sensible elsewhere
+ * node = X -- set to actual node, below
*
* index = NULL -- nothing, yet
* index_sub = NULL -- nothing, yet
*
- * config = false -- not in configure mode
+ * config = false -- not owner of configuration symbol of power
+ * config_brand = 0 -- none, yet
*
- * execution = X -- set below
+ * exec = NULL -- execution state set up when required
* vio = X -- set below
*/
confirm(NULL_NODE == 0) ;
confirm(QSTRING_INIT_ALL_ZEROS) ;
vty->type = type ;
+ vty->node = node ;
vio = XCALLOC(MTYPE_VTY, sizeof(struct vty_io)) ;
@@ -251,57 +245,37 @@ uty_new(vty_type_t type)
* vin_base = NULL -- empty input stack
* vin_depth = 0 -- no stacked vin's, yet
*
+ * real_depth = 0 -- nothing stacked, yet
+ *
* vout = NULL -- empty output stack
* vout_base = NULL -- empty output stack
* vout_depth = 0 -- no stacked vout's, yet
*
- * vout_closing = NULL -- nothing closing yet
+ * depth_mark = 0 -- no stacked vin/vout, yet
+ *
+ * err_hard = false -- no error at all, yet
+ * ebuf = NULL -- no error at all, yet
*
* vio_list = NULLs -- not on the vio_list, yet
* mon_list = NULLs -- not on the monitors list
*
* blocking = X -- set below: false unless VTY_CONFIG_READ
- * cmd_running = false -- no commands running, yet
*
- * state = X -- set vf_open, below.
+ * state = vc_null -- not started vty command loop
*
- * close_reason = NULL -- not closed, yet
+ * close_reason = NULL -- none set
*
- * obuf = NULL -- no output buffer, yet
+ * obuf = NULL -- no output buffer, yet
*/
+ confirm(vc_null == 0) ;
vty->vio = vio ;
vio->vty = vty ;
vio->blocking = (type == VTY_CONFIG_READ) ;
- vio->state = vf_open ;
-
- /* Create and initialise the command execution environment (if any) */
- execution = true ;
-
- switch(type)
- {
- case VTY_TERMINAL:
- case VTY_SHELL_SERVER:
- case VTY_SHELL_CLIENT:
- case VTY_CONFIG_READ:
- execution = true ;
- break ;
-
- case VTY_STDOUT:
- case VTY_STDERR:
- execution = false ;
- break ;
-
- default:
- zabort("unknown vty type") ;
- } ;
-
- if (execution)
- vty->exec = cmd_exec_new(vty) ;
/* Place on list of known vio/vty */
- sdl_push(vio_list_base, vio, vio_list) ;
+ sdl_push(vio_live_list, vio, vio_list) ;
return vty;
} ;
@@ -316,13 +290,13 @@ uty_new(vty_type_t type)
*
* Sets the read ready action and the read timer timeout action.
*
- * NB: a uty_cmd_prepare() is required before command processing can continue.
+ * NB: is usually called from the cli thread, but may be called from the cmd
+ * thread for vf which is blocking !
*
- * That is not done here because the vin state may not be complete (in
- * particular the vin->parse_type and vin->reflect_enabled !).
+ * NB: a uty_cmd_prepare() is required before command processing can continue.
*/
extern void
-uty_vin_open(vty_io vio, vio_vf vf, vio_in_type_t type,
+uty_vin_push(vty_io vio, vio_vf vf, vio_in_type_t type,
vio_vfd_action* read_action,
vio_timer_action* read_timer_action,
usize ibuf_size)
@@ -330,34 +304,58 @@ uty_vin_open(vty_io vio, vio_vf vf, vio_in_type_t type,
vf->vin_type = type ;
vf->vin_state = vf_open ;
- if (type < VIN_SPECIALS)
+ assert(type != VIN_NONE) ;
+
+ if ((type < VIN_SPECIALS) && (!vf->blocking))
{
vio_vfd_set_read_action(vf->vfd, read_action) ;
vio_vfd_set_read_timeout_action(vf->vfd, read_timer_action) ;
} ;
ssl_push(vio->vin, vf, vin_next) ;
+ vio->real_depth = ++vio->vin_depth ;
+
if (vio->vin_base == NULL)
{
- assert(vio->vin_depth == 0) ;
+ assert(vio->vin_depth == 1) ;
vio->vin_base = vf ;
- }
- else
- {
- assert(type != VIN_NONE) ;
- ++vio->vin_depth ;
} ;
if (ibuf_size != 0)
{
vf->ibuf = vio_fifo_init_new(NULL, ibuf_size) ;
-
+ vf->cl = qs_init_new(NULL, 120) ;
vf->line_complete = true ;
vf->line_step = 1 ;
} ;
} ;
/*------------------------------------------------------------------------------
+ * Save the given context in the current top of the vin stack.
+ *
+ * This is done when a new pipe is opened, so that:
+ *
+ * a) saves the current context in the current vin (the new pipe has
+ * not yet been pushed) so that uty_vin_pop() can restore this context
+ * after closing the then top of the stack.
+ *
+ * b) can update context for about to be run vin, eg:
+ *
+ * - dir_here -- if required
+ *
+ * - can_enable
+ *
+ * So the top of the vin stack does not contain the current context, that is
+ * in the vty->exec !
+ */
+extern void
+uty_vin_new_context(vty_io vio, cmd_context context, qpath file_here)
+{
+ assert(vio->vin->context == NULL) ;
+ vio->vin->context = cmd_context_new_save(context, file_here) ;
+} ;
+
+/*------------------------------------------------------------------------------
* Push a new vf to the vio->vout stack, and set write stuff.
*
* Sets the vf->vout_type and set vf->write_open.
@@ -366,6 +364,15 @@ uty_vin_open(vty_io vio, vio_vf vf, vio_in_type_t type,
*
* Initialises an output buffer and sets an end_mark.
*
+ * The depth_mark is set to the current vio->depth_mark + 1. This is the
+ * vin_depth below which the vout should be closed. Before a command line
+ * is fetched (and hence after the previous command line has completed) the
+ * vout->depth_mark is checked. If it is > the current vin_depth, then
+ * the vout is closed before a command line can be fetched.
+ *
+ * NB: is usually called from the cli thread, but may be called from the cmd
+ * thread for vf which is blocking !
+ *
* NB: VOUT_DEV_NULL, VOUT_STDOUT and VOUT_STDERR are special.
*
* The write_action and the write_timer_action are ignored.
@@ -377,41 +384,41 @@ uty_vin_open(vty_io vio, vio_vf vf, vio_in_type_t type,
* if it is later to be discarded.
*
* NB: a uty_cmd_prepare() is required before command processing can continue.
- *
- * That is not done here because the vout state may not be complete (in
- * particular the vout->out_enabled !).
*/
extern void
-uty_vout_open(vty_io vio, vio_vf vf, vio_out_type_t type,
- vio_vfd_action* write_action,
- vio_timer_action* write_timer_action,
- usize obuf_size)
+uty_vout_push(vty_io vio, vio_vf vf, vio_out_type_t type,
+ vio_vfd_action* write_action,
+ vio_timer_action* write_timer_action,
+ usize obuf_size)
{
+ VTY_ASSERT_LOCKED() ;
+
vf->vout_type = type ;
vf->vout_state = vf_open ;
- if (type < VOUT_SPECIALS)
+ assert(type != VOUT_NONE) ;
+
+ if ((type < VOUT_SPECIALS) && (!vf->blocking))
{
vio_vfd_set_write_action(vf->vfd, write_action) ;
vio_vfd_set_write_timeout_action(vf->vfd, write_timer_action) ;
} ;
ssl_push(vio->vout, vf, vout_next) ;
+ ++vio->vout_depth ;
+
if (vio->vout_base == NULL)
{
- assert(vio->vout_depth == 0) ;
+ assert(vio->vout_depth == 1) ;
vio->vout_base = vf ;
- }
- else
- {
- assert(type != VOUT_NONE) ;
- ++vio->vout_depth ;
} ;
vf->obuf = vio_fifo_init_new(NULL, obuf_size) ;
vio_fifo_set_end_mark(vf->obuf) ;
- vio->obuf = vio->vout->obuf ;
+ vf->depth_mark = vio->depth_mark + 1 ;
+
+ vio->obuf = vf->obuf ;
} ;
/*------------------------------------------------------------------------------
@@ -434,33 +441,6 @@ uty_set_timeout(vty_io vio, vty_timer_time timeout)
} ;
/*------------------------------------------------------------------------------
- * Set/Clear "monitor" state:
- *
- * set: if VTY_TERM and not already "monitor" (and write_open !)
- * clear: if is "monitor"
- */
-extern void
-uty_set_monitor(vty_io vio, bool on)
-{
- VTY_ASSERT_LOCKED() ;
-#if 0
- if (on && !vio->monitor)
- {
- if ((vio->vty->type == VTY_TERMINAL) && !vio->closing)
- {
- vio->monitor = 1 ;
- sdl_push(vio_monitors_base, vio, mon_list) ;
- } ;
- }
- else if (!on && vio->monitor)
- {
- vio->monitor = 0 ;
- sdl_del(vio_monitors_base, vio, mon_list) ;
- }
-#endif
-} ;
-
-/*------------------------------------------------------------------------------
* Return "name" of VTY.
*
* The name of the base vin, or (failing that) the base vout.
@@ -478,257 +458,391 @@ uty_get_name(vty_io vio)
} ;
/*------------------------------------------------------------------------------
- * Close all vin excluding the vin_base.
- *
- * This is done on close and when there is a command error.
- */
-extern void
-uty_vin_close_stack(vty_io vio)
-{
- while (vio->vin != vio->vin_base)
- uty_vin_close(vio) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Close all vout except for the vout_base.
- *
- * This is done on close and when there is a command error.
- *
- * Should only be "final" on a call from the watch-dog !
- */
-extern void
-uty_vout_close_stack(vty_io vio, bool final)
-{
- while (vio->vout != vio->vout_base)
- uty_vout_close(vio, final) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Close VTY -- may be called any number of times !
+ * Close VTY -- final.
*
- * If no reason for the close has already been set, sets the given reason.
- * The close reason is reported at the end of the output to the vout_base.
+ * Two forms: "curtains" and "not-curtains".
*
- * Once a VTY has been closed, it will provide no further input. However, until
- * "final" close, it will continue to output anything which is pending at the
- * time of the close, which includes: (a) anything in the output buffer,
- * (b) any further output from a then running command and (c) anything that an
- * out_pipe may be in the process of returning.
+ * At "curtains" the system is being terminated, and all message and event
+ * handling has stopped. Can assume that a vty is not in any command loop,
+ * so can be killed off here and now.
*
- * The main complication is that in a multi-threaded world there may be a
- * vio->cmd_running. The close shuts down everything on the input stack, so
- * any active command loop will come to a halt as soon as the current command
- * does complete. The output stack is preserved, but will be closed on
- * completion of the command.
+ * At "not-curtains" messages and event handling is still running. It is
+ * possible that a vty is running a command in the cmd_thread, so cannot
+ * completely close things down -- must leave enough for the command loop
+ * to continue to work, up to the point that the closing of the vty is
+ * detected.
*
- * Closing a VTY places it on the death-watch list. Once any pending output
- * has been dealt with and any cmd_running has completed, then the death-watch
- * will apply the coup de grace.
+ * For everything that is closed, uses "final" close, which stops things
+ * instantly -- will not block, or set any read/write ready, or continue the
+ * command loop, or take any notice of errors.
*
- * Getting the death-watch to finally close the VTY also allows the vty to be
- * closed from somewhere deep (e.g. when there is an I/O error) without
- * destroying the VTY and upsetting code that might not realise the VTY has
- * vanished.
+ * This close is called by:
*
- * The close sequence is:
+ * * uty_reset() -- SIGHUP -- !curtains
*
- * 1. if a close reason has not already been set, set any given one.
+ * Will close "final" everything except vout_base.
*
- * 2. if not already closing -- ie this is the first call of uty_close()
+ * If the command loop is not already stopped, it is signalled to stop
+ * as soon as possible. When it does it will return here via
+ * vty_cmd_loop_exit().
*
- * a. transfer to death-watch list & set closing.
+ * If command loop has already stopped, then will proceed to complete
+ * close -- see below.
*
- * b. turn off any monitor state
+ * * uty_reset() -- SIGTERM -- curtains
*
- * c. close down the input/read side completely.
+ * Will close "final" everything except vout_base.
*
- * Empties the vin stack down to vin_base, closing all input.
+ * The command loop will be set stopped, and will proceed to complete
+ * close -- see below.
*
- * Close the vin_base. The vin_base is left !read_open.
+ * * vty_cmd_loop_exit() -- when the command loop has stopped -- !curtains
*
- * All read-only vio_vf (except the vin_base) are freed.
+ * The command loop may have stopped in response to a vc_close_trap,
+ * which means that have been here before, and the vty is pretty much
+ * closed already.
*
- * Note that everything is read closed before anything is write
- * closed.
+ * The command loop may have stopped:
*
- * A vio_vf is read closed once and only once.
+ * - because has reached the end of the vin_base input
+ * - vin_base has timed out
+ * - there was a command error, on non-interactive vty
+ * - there was an I/O error
*
- * The vin_base is closed only by this function. Until the final
- * uty_close, there is always at least the vin_base, even if it is
- * read_closed.
+ * In all these cases, the stack will already have been closed final or
+ * otherwise, except for vout_base, and all output will have been pushed.
*
- * 3. try to close everything on the vout_closing list.
+ * vout_base will have been closed, but not "final", so will be sitting
+ * in vf_closing state.
*
- * even if cmd_running
+ * So the vio is ready for complete close.
*
+ * For complete close any remaining vf are closed final, and the close reason
+ * is output to the vout_base, if any and if possible.
*
+ * The vty is then closed and placed on death watch to be finally reaped.
*/
-extern bool
-uty_close(vty_io vio, bool final, qstring reason)
+extern void
+uty_close(vty_io vio, const char* reason, bool curtains)
{
- vio_vf vf_next ;
+ VTY_ASSERT_CAN_CLOSE(vio->vty) ;
- VTY_ASSERT_LOCKED() ;
+ /* Stamp on any monitor output instantly. */
+ uty_set_monitor(vio, off) ;
- /* quit if already closed */
- if (vio->state == vf_closed)
- return true ;
+ /* Save the close reason for later, unless one is already set. */
+ if ((reason != NULL) && (vio->close_reason == NULL))
+ vio->close_reason = XSTRDUP(MTYPE_TMP, reason) ;
- /* Set the close reason, if not already set. */
- if (reason != NULL)
- {
- if (vio->close_reason == NULL)
- vio->close_reason = reason ;
- else
- qs_reset(reason, free_it) ;
- } ;
+ /* Close the command loop -- if not already stopped (or closed !)
+ *
+ * If command loop is not already stopped, the if "curtains" will stop it
+ * instantly, otherwise will signal to the command loop to close, soonest.
+ */
+ uty_cmd_loop_close(vio, curtains) ;
- /* If not already closing, set closing and transfer to the death watch
- * list -- turn off any "monitor" status immediately.
+ /* Close all vin including the vin_base.
+ *
+ * Note that the vin_base is closed, but is still on the vin stack.
*/
- if (vio->state == vf_open)
- {
- vio->state = vf_closing ;
+ do
+ uty_vin_pop(vio, true, NULL) ; /* final close, discard context */
+ while (vio->vin != vio->vin_base) ;
- /* Transfer to the death-watch */
- sdl_del(vio_list_base, vio, vio_list) ;
- sdl_push(vio_death_watch, vio, vio_list) ;
+ /* Close all the vout excluding the vout_base. */
+ while (vio->vout != vio->vout_base)
+ uty_vout_pop(vio, true) ; /* final close */
+
+ /* If command loop is still running, this is as far as can go.
+ *
+ * The command loop will hit the vc_close_trap or execute CMD_CLOSE, and
+ * that will cause this function to be called again, in vc_stopped state.
+ *
+ * Save the close_reason for later.
+ */
+ if ((vio->state == vc_running) || (vio->state == vc_close_trap))
+ return ;
- /* TODO turn off any "monitor" IMMEDIATELY. */
+ /* If the vout_base is not closed, try to output the close reason,
+ * if any.
+ */
+ if ((vio->close_reason != NULL) && (vio->vout_base->vout_state != vf_closed))
+ uty_vout_close_reason(vio->vout_base, vio->close_reason) ;
- /* Flush the vin stack.
- *
- * Where possible this will revoke commands bring command processing to
- * as sudden a halt as possible -- though may still be processing
- * commands.
- */
- uty_vin_close_stack(vio) ;
+ /* Now final close the vout_base.
+ *
+ * Note that the vout_base will be closed, but on the vout stack with an
+ * empty obuf... just in case TODO ?
+ */
+ uty_vout_pop(vio, true) ; /* final close */
+
+ assert(vio->obuf == vio->vout_base->obuf) ;
+ vio_fifo_clear(vio->obuf, true) ; /* and discard any marks */
+
+ /* All should now be very quiet indeed. */
+ if (vty_debug)
+ {
assert(vio->vin == vio->vin_base) ;
- uty_vf_read_close(vio->vin) ;
+ assert(vio->vin_depth == 0) ;
+ assert(vio->real_depth == 0) ;
+
+ assert(vio->vout == vio->vout_base) ;
+ assert(vio->vout_depth == 0) ;
+
+ assert(vio->vin->vin_state == vf_closed) ;
+ assert(vio->vout->vout_state == vf_closed) ;
} ;
- /* If there is anything on the vout_closing list, give it a shove in case
- * that manages to close anything -- which it will do if "final".
+ /* Can dispose of these now -- leave vin/vout for final disposition */
+ vio->vty->exec = cmd_exec_free(vio->vty->exec) ;
+ vio->ebuf = vio_fifo_reset(vio->ebuf, free_it) ;
+
+ /* Command loop is not running, so can place on death watch for final
+ * disposition.
*/
- vf_next = vio->vout_closing ;
- while (vf_next != NULL)
+ if (vio->state == vc_stopped)
{
- vio_vf vf = vf_next ;
- vf_next = ssl_next(vf, vout_next) ;
+ vio->state = vc_closed ;
- uty_vf_write_close(vf, final) ; /* removes from vout_closing if
- is now completely closed. */
+ sdl_del(vio_live_list, vio, vio_list) ;
+ sdl_push(vio_death_watch, vio, vio_list) ;
} ;
- /* If is vio->cmd_running, this is as far as we can go this time. */
- if (vio->cmd_running)
- return false ;
+ assert(vio->state == vc_closed) ; /* thank you and good night */
+} ;
- /* Make sure no longer holding the config symbol of power */
- uty_config_unlock(vio->vty, NULL_NODE) ;
+/*------------------------------------------------------------------------------
+ * Dispose unwanted vty.
+ *
+ * Called from deathwatch -- must already be removed from deathwatch list.
+ */
+static vty_io
+uty_dispose(vty_io vio)
+{
+ VTY_ASSERT_LOCKED() ;
- /* Flush the vout stack.
- *
- * If cannot completely close a vf, places it on the vout_closing list,
- * except for the vout_base.
- */
- uty_vout_close_stack(vio, final) ;
- assert(vio->vout == vio->vout_base) ;
- uty_vf_write_close(vio->vout, final) ;
+ assert(vio->state == vc_closed) ;
- /* See if have now successfully closed everything. */
- if ((vio->vout_closing != NULL) || (vio->vout_base->vout_state != vf_closed))
- return false ; /* something is still open */
+ /* Stop pointing at vout_base obuf */
+ vio->obuf = NULL ;
- /* Empty everything out of the vio and return the good news.
- *
- * NB: retains vio->vty & the vio->vio_list for death-watch.
- */
- assert((vio->vin == vio->vin_base) && (vio->vin_depth == 0)) ;
+ /* Clear out vout and vin (may be the same) */
+ assert(vio->vin == vio->vin_base) ;
+ vio->vin_base = uty_vf_free(vio->vin_base) ;
- if (vio->vin != vio->vout)
- vio->vin = uty_vf_free(vio->vin) ;
- else
- vio->vin = NULL ;
- vio->vin_base = NULL ;
+ assert(vio->vout == vio->vout_base) ;
+ if (vio->vout != vio->vin)
+ vio->vout_base = uty_vf_free(vio->vout_base) ;
- assert((vio->vout == vio->vout_base) && (vio->vout_depth == 0)) ;
+ vio->vin = NULL ;
+ vio->vout = NULL ;
- vio->vout = uty_vf_free(vio->vout) ;
+ /* Remainder of contents of the vio */
+ vio->ebuf = vio_fifo_reset(vio->ebuf, free_it) ;
- vio->close_reason = qs_reset(vio->close_reason, free_it) ;
+ /* Really cannot be a monitor any more ! */
+ assert(!vio->monitor) ;
+ vio->mbuf = vio_fifo_reset(vio->mbuf, free_it) ;
- vio->obuf = NULL ;
+ XFREE(MTYPE_VTY, vio) ;
- vio->state = vf_closed ;
- return true ;
+ return NULL ;
} ;
/*------------------------------------------------------------------------------
- * Pop and close the top of the vin stack -- MUST NOT be vin_base.
+ * Close top of the vin stack and pop when done -- see uty_vf_read_close().
*
- * Read closes the vio_vfd -- if the vio_vfd was read-only, this will fully
- * close it, and the vio_vfd will have been freed.
+ * If succeeds in closing, unless is vin_base, pops the vin stack and if this
+ * is read-only will free the vio_vf and all its contents. (So if this is
+ * vin_base, it is left on the stack, but vf_closed/vf_closing.)
*
- * If this is read-only will free the vio_vf and all its contents.
+ * If the given context is not NULL, having popped the vin stack, the context
+ * in the new top of stack is restored to the given context.
*
- * NB: a uty_cmd_prepare() is required before command processing can continue.
+ * On final close, will completely close the input, even if errors occur (and
+ * no errors are posted).
*
- * That is not done here because this may be called from outside the
- * command loop -- in particular by uty_close().
+ * Returns: CMD_SUCCESS -- input completely closed
+ * CMD_WAITING -- waiting for input to close <=> not-blocking
+ * (not if final)
+ * CMD_IO_ERROR -- error or timeout
+ *
+ * NB: a uty_cmd_prepare() is required before command processing can continue.
*/
-extern void
-uty_vin_close(vty_io vio)
+extern cmd_return_code_t
+uty_vin_pop(vty_io vio, bool final, cmd_context context)
{
- vio_vf vf ;
+ cmd_return_code_t ret ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ ret = uty_vf_read_close(vio->vin, final) ;
+
+ if ((ret == CMD_SUCCESS) || final)
+ {
+ assert(vio->vin->vin_state == vf_closed) ;
+
+ if (vio->vin_depth > 1)
+ {
+ vio_vf vf ;
- vf = vio->vin ;
+ vf = ssl_pop(&vf, vio->vin, vin_next) ;
+ --vio->vin_depth ;
- assert(vf != vio->vin_base) ;
- assert(vio->vin_depth > 0) ;
+ if (vf->vout_state == vf_closed)
+ uty_vf_free(vf) ;
+ }
+ else
+ {
+ assert(vio->vin == vio->vin_base) ;
+ vio->vin_depth = 0 ; /* may already have been closed */
+ } ;
- ssl_del_head(vio->vin, vin_next) ;
- --vio->vin_depth ;
+ if (vio->real_depth > vio->vin_depth)
+ vio->real_depth = vio->vin_depth ;
- uty_vf_read_close(vf) ;
+ if (vio->vin->context != NULL)
+ {
+ if (context != NULL)
+ vio->vin->context = cmd_context_restore(context, vio->vin->context);
+ else
+ vio->vin->context = cmd_context_free(vio->vin->context, false) ;
+ /* Not a copy */
+ } ;
+ } ;
+
+ return ret ;
} ;
/*------------------------------------------------------------------------------
- * Pop and close the top of the vout stack -- MUST NOT be vout_base.
+ * Close top of the vout stack and pop when done -- see uty_vf_write_close().
+ *
+ * If is vout_base, does not completely close, unless is "final" -- if not
+ * final, CMD_SUCCESS means that the output buffers are empty and the
+ * vout_depth has been reduced, but the vio_vf is still writeable, held in
+ * vf_closing state.
*
- * Moves the vio_vf to the vout_closing list.
+ * Unless is vout_base, pops the vout stack. If this is write-only will free
+ * the vio_vf and all its contents.
*
- * Before closing, push any outstanding output.
+ * If this is vout_base, does not actually close the vfd and does not close
+ * the vout side of the vf. This leaves an active vio->obuf (inter alia.)
+ *
+ * Before closing, discard anything after end_mark, then push any outstanding
+ * output.
*
* Unless "final", the close is soft, that is, if there is any output still
* outstanding does not actually close the vout.
*
* If there is no outstanding output (or if final) will completely close the
- * vf and free it.
+ * vf and free it (except for vout_base).
+ *
+ * Returns: CMD_SUCCESS -- input completely closed
+ * CMD_WAITING -- waiting for input to close <=> not-blocking
+ * CMD_IO_ERROR -- error or timeout
*
* NB: a uty_cmd_prepare() is required before command processing can continue.
*
* That is not done here because this may be called from outside the
* command loop -- in particular by uty_close().
+ *
+ * However, ensures that vio->obuf is up to date !
*/
-extern void
-uty_vout_close(vty_io vio, bool final)
+extern cmd_return_code_t
+uty_vout_pop(vty_io vio, bool final)
+{
+ cmd_return_code_t ret ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ ret = uty_vf_write_close(vio->vout, final) ;
+
+ if ((ret == CMD_SUCCESS) || final)
+ {
+ if (vio->vout_depth > 1)
+ {
+ vio_vf vf ;
+
+ vf = ssl_pop(&vf, vio->vout, vout_next) ;
+ --vio->vout_depth ;
+
+ uty_vf_free(vf) ;
+ }
+ else
+ {
+ assert(vio->vout == vio->vout_base) ;
+ if (final)
+ assert(vio->vout->vout_state == vf_closed) ;
+
+ vio->vout_depth = 0 ; /* may already have been closed */
+
+ assert(vio->vin_depth == 0) ;
+ vio->vout->depth_mark = 0 ; /* align with the end stop */
+ } ;
+
+ vio->obuf = vio->vout->obuf ;
+ } ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Try to output the close reason to the vout_base.
+ *
+ * This is the final act for the vout_base. Will attempt to output unless
+ * the thing is completely closed.
+ *
+ * Should by now not be any buffered output -- but if there is, that is
+ * discarded before the close reason is output.
+ *
+ * Any actual output may be done when the vout_base is finally closed.
+ */
+static void
+uty_vout_close_reason(vio_vf vf, const char* reason)
{
- vio_vf vf ;
+ if ((vf->vout_state == vf_closed) || (reason == NULL) || (*reason == '\0'))
+ return ;
+
+ vio_fifo_clear(vf->obuf, true) ; /* clear any markers, too */
+
+ if (vf->vout_type < VOUT_SPECIALS)
+ assert(vf->vfd != NULL) ; /* make sure */
+
+ switch(vf->vout_type)
+ {
+ case VOUT_NONE:
+ zabort("invalid VOUT_NONE") ;
+ break ;
- vf = vio->vout ;
+ case VOUT_TERM:
+ uty_term_close_reason(vf, reason) ;
+ break ;
- assert(vf != vio->vout_base) ;
- assert(vio->vout_depth > 0) ;
+ case VOUT_VTYSH:
+ break ;
+
+ case VOUT_FILE:
+ case VOUT_PIPE:
+ case VOUT_SHELL_ONLY:
+ break ;
+
+ case VOUT_CONFIG:
+ break ;
- ssl_del_head(vio->vout, vout_next) ;
- --vio->vout_depth ;
+ case VOUT_DEV_NULL:
+ break ;
- ssl_push(vio->vout_closing, vf, vout_next) ;
+ case VOUT_STDOUT:
+ fprintf(stdout, "%% %s\n", reason) ;
+ break ;
- vio->obuf = vio->vout->obuf ;
+ case VOUT_STDERR:
+ fprintf(stderr, "%% %s\n", reason) ;
+ break ;
- uty_vf_write_close(vf, final) ;
+ default:
+ zabort("unknown VOUT type") ;
+ } ;
} ;
/*==============================================================================
@@ -742,7 +856,7 @@ uty_vout_close(vty_io vio, bool final)
*
* This leaves most things unset/NULL/false. Caller will need to:
*
- * - uty_vin_open() and/or uty_vout_open()
+ * - uty_vin_push() and/or uty_vout_push()
*
* - once those are done, the following optional items remain to be set
* if they are required:
@@ -750,23 +864,30 @@ uty_vout_close(vty_io vio, bool final)
* read_timeout -- default = 0 => no timeout
* write_timeout -- default = 0 => no timeout
*
- * parse_type -- default = cmd_parse_standard
- * reflect_enabled -- default = false
- * out_enabled -- default = true iff vfd_io_write
- *
- * If vio->blocking, adds vfd_io_blocking to the io_type.
+ * - for pipes the child state needs to be set, and for out pipes the return
+ * state needs to be set in this vio_vf (the master) and in the next
+ * vout (the slave).
*
* NB: if there is no fd for this vio_vf, it should be set to -1, and the
- * type (recommend vfd_none) and io_type are ignored -- except for
- * vfd_io_write and the out_enabled state.
+ * type (recommend vfd_none) and io_type are ignored.
*
* A VTY_STDOUT or a VTY_STDERR (output only, to the standard I/O) can
- * be set up without an fd, and with/without out_enabled.
+ * be set up without an fd.
*
* A VTY_CONFIG_READ can be set up with the fd of the input file, but
* MUST be type = vfd_file and io_type = vfd_io_read -- because the fd
- * is for input only. This will set out_enabled false (as required).
- * The required VOUT_STDERR will be set by uty_vout_open().
+ * is for input only. The required VOUT_STDERR will be set by
+ * uty_vout_push().
+ *
+ * NB: if the parent vio is blocking, then the vf will be blocking, and so
+ * will any vfd. An individual vf may be set blocking by setting
+ * vfd_io_blocking in the io_type.
+ *
+ * The vty stuff opens all files, pipes etc. non-blocking. A non-blocking
+ * vf simulates blocking by local pselect. As far as the vfd level is
+ * concerned, once the file, pipe etc. is open, there is no difference
+ * between blocking and non-blocking except that non-blocking vfd are not
+ * allowed to set read/write ready.
*/
extern vio_vf
uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type,
@@ -776,9 +897,6 @@ uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type,
VTY_ASSERT_LOCKED() ;
- if (vio->blocking)
- io_type |= vfd_io_blocking ;
-
vf = XCALLOC (MTYPE_VTY, sizeof(struct vio_vf)) ;
/* Zeroising the structure has set:
@@ -786,49 +904,61 @@ uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type,
* vio = X -- set below
* name = X -- set below
*
- * vin_type = VIN_NONE -- see uty_vin_open()
- * vin_state = vf_closed -- see uty_vin_open()
- * vin_next = NULL -- see uty_vin_open()
+ * vin_type = VIN_NONE -- see uty_vin_push()
+ * vin_state = vf_closed -- see uty_vin_push()
+ * vin_next = NULL -- see uty_vin_push()
*
- * cli = NULL -- no CLI, yet
+ * context = NULL -- see uty_vin_new_context()
*
- * ibuf = NULL -- none, yet -- see uty_vin_open()
+ * cli = NULL -- no CLI, yet
*
- * cl = zeros -- empty qstring (embedded)
- * line_complete = false -- see uty_vout_open()
+ * ibuf = NULL -- none, yet -- see uty_vin_push()
+ * cl = NULL -- none, yet -- see uty_vin_push()
+ * line_complete = false -- see uty_vin_push()
* line_number = 0 -- nothing yet
- * line_step = 0 -- see uty_vout_open()
+ * line_step = 0 -- see uty_vin_push()
*
- * parse_type = cmd_parse_standard
- * reflect_enabled = false
+ * vout_type = VOUT_NONE -- see uty_vout_push()
+ * vout_state = vf_closed -- see uty_vout_push()
+ * vout_next = NULL -- see uty_vout_push()
*
- * vout_type = VOUT_NONE -- see uty_vout_open()
- * vout_state = vf_closed -- see uty_vout_open()
- * vout_next = NULL -- see uty_vout_open()
+ * pr_master = NULL -- none -- see uty_pipe_write_open()
*
- * obuf = NULL -- none, yet -- see uty_vout_open()
+ * obuf = NULL -- none -- see uty_vout_push()
*
- * out_enabled = X -- set below: true iff vfd_io_write
+ * depth_mark = 0 -- see uty_vout_push()
*
* vfd = NULL -- no vfd, yet
*
- * blocking = false -- default is non-blocking I/O
- * closing = false -- definitely not
+ * blocking = X -- see below
+ * closing = false -- not on the closing list, yet.
*
* error_seen = 0 -- no error seen, yet
*
- * read_on = false
- * write_on = false
- *
* read_timeout = 0 -- none
* write_timeout = 0 -- none
+ *
+ * child = 0 -- none )
+ * terminated = false -- not ) -- see uty_pipe_read/write_open()
+ * term_status = X -- none )
+ *
+ * pr_state = vf_closed -- no pipe return vfd
+ * -- see uty_pipe_read/write_open()
+ * pr_vfd = NULL -- no vfd -- see uty_pipe_read/write_open()
+ *
+ * pr_slave = NULL -- none -- see uty_pipe_read/write_open()
+ *
+ * pr_only = false -- see uty_pipe_read/write_open()
*/
confirm((VIN_NONE == 0) && (VOUT_NONE == 0)) ;
confirm(vf_closed == 0) ;
confirm(QSTRING_INIT_ALL_ZEROS) ;
- confirm(cmd_parse_standard == 0) ;
- vf->vio = vio ;
+ if (vio->blocking)
+ io_type |= vfd_io_blocking ; /* inherit blocking state */
+
+ vf->vio = vio ;
+ vf->blocking = (io_type & vfd_io_blocking) != 0 ;
if (name != NULL)
vf->name = XSTRDUP(MTYPE_VTY_NAME, name) ;
@@ -836,35 +966,58 @@ uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type,
if (fd >= 0)
vf->vfd = vio_vfd_new(fd, type, io_type, vf) ;
- vf->out_enabled = ((io_type & vfd_io_write) != 0) ;
-
return vf ;
} ;
/*------------------------------------------------------------------------------
- * Close the read side of the given vio_vf.
+ * Close the read side of the given vio_vf -- if can.
*
* Read closes the vio_vfd -- if the vio_vfd was read-only, this will fully
* close it, and the vio_vfd will have been freed.
*
- * If this is read-only (ie vout_type == VOUT_NONE) will free the vio_vf as
- * well as the vio_vfd -- unless this is the vin_base.
+ * For not-final close, if there is any other related I/O, then waits until
+ * that has completed. For example, with a VIN_PIPE:
*
- * Note that read_close is effective immediately, there is no delay as there is
- * with write_close -- so no need for a "final" option.
+ * * waits for any return input to complete, and pushes it to the relevant
+ * vout.
+ *
+ * * waits to collect the child, and its termination state.
+ *
+ * This means that a not-final close may return errors. For not-blocking vf
+ * may return waiting state. For blocking vf, may block and may later return
+ * timeout error.
+ *
+ * For a final close, if there is any related I/O then will attempt to complete
+ * it -- but will give up if would block. I/O errors on final close are
+ * ignored. A final close may be called in terminating state, so does not
+ * do any "vty_cmd_signal".
+ *
+ * Returns: CMD_SUCCESS -- is all closed
+ * CMD_WAITING -- cannot close at the moment <=> non-blocking
+ * (not if "final")
+ * CMD_IO_ERROR -- something went wrong
+ *
+ * NB: on "final" close returns CMD_SUCCESS no matter what happened, and all
+ * input will have been closed down, and the vf closed.
*/
-static void
-uty_vf_read_close(vio_vf vf)
+static cmd_return_code_t
+uty_vf_read_close(vio_vf vf, bool final)
{
- assert(vf->vin_state != vf_closed) ;
+ cmd_return_code_t ret ;
+
+ VTY_ASSERT_CAN_CLOSE_VF(vf) ;
+
+ ret = CMD_SUCCESS ;
+
+ if (vf->vin_state == vf_closed)
+ return ret ; /* quit if already closed */
+
+ vf->vin_state = vf_closing ; /* TODO wipes out error etc ? */
/* Do the vfd level read close and mark the vf no longer read_open */
if (vf->vin_type < VIN_SPECIALS)
vf->vfd = vio_vfd_read_close(vf->vfd) ;
- vf->vin_state = vf_closed ;
- vf->read_on = off ;
-
/* Now the vin_type specific clean up. */
switch(vf->vin_type)
{
@@ -873,60 +1026,126 @@ uty_vf_read_close(vio_vf vf)
break ;
case VIN_TERM:
- uty_term_read_close(vf) ;
+ ret = uty_term_read_close(vf, final) ;
break ;
case VIN_VTYSH:
+ zabort("tba VIN_VTYSH") ;
+ break ;
+
+ case VIN_CONFIG:
+ ret = uty_config_read_close(vf, final) ;
break ;
case VIN_FILE:
- uty_file_read_close(vf) ;
+ ret = uty_file_read_close(vf, final) ;
break ;
case VIN_PIPE:
- break ;
-
- case VIN_CONFIG:
+ ret = uty_pipe_read_close(vf, final) ;
break ;
case VIN_DEV_NULL:
+ ret = CMD_SUCCESS ;
break ;
default:
zabort("unknown VIN type") ;
} ;
- if ((vf->vout_type == VOUT_NONE) && (vf != vf->vio->vin_base))
- uty_vf_free(vf) ;
+ if ((ret == CMD_SUCCESS) || final)
+ {
+ vf->vin_state = vf_closed ;
+ assert(vf->pr_state == vf_closed) ;
+ } ;
+
+ return ret ;
} ;
/*------------------------------------------------------------------------------
* Close the write side of the given vio_vf, if can.
*
- * Pushes any outstanding output -- non-blocking if not a blocking vty.
+ * Discards anything beyond the current end_mark, and clears the end_mark.
+ *
+ * Pushes any outstanding output, and if is pipe will attempt to collect
+ * the child. On not-final close, if cannot complete everything, will return
+ * CMD_WAITING for non-blocking, or block (and may time out). On final close,
+ * will do as much as possible without blocking, and will then close even if
+ * there is outstanding output or child has not been collected.
+ *
+ * For not-final close, for example, with a VOUT_PIPE:
*
- * The vio_vf MUST either be on the vout_closing list or be vout_base.
- * The vio_vf MUST be write_open and MUST NOT be read_open.
+ * * waits for any return input to complete, and pushes it to the relevant
+ * vout.
*
- * If output is empty, or if final, close the vf, then if it is on the
- * vout_closing list, remove and free it.
+ * * waits to collect the child, and its termination state.
*
- * Returns whether output was empty or not.
+ * This means that a not-final close may return errors.
+ *
+ * For a final close, if there is any related I/O then will attempt to complete
+ * it -- but will give up if would block. I/O errors on final close are
+ * ignored. A final close may be called in terminating state, so does not
+ * do any "vty_cmd_signal".
+ *
+ * If this is the vout_base, unless "final", does NOT actually close the vf or
+ * the vfd -- so the vout_base will still work -- but is marked vf_closing !
+ * The effect is, essentially, to try to empty out any buffers, but not to
+ * do anything that would prevent further output. This is used so that a
+ * command loop can close the vout_base in the usual way, waiting until all
+ * output is flushed, but when uty_close() is finally called, it can output
+ * any close reason there is to hand.
+ *
+ * Returns: CMD_SUCCESS -- is all closed
+ * CMD_WAITING -- cannot close at the moment <=> non-blocking
+ * (not if "final")
+ * CMD_IO_ERROR -- something went wrong
+ *
+ * NB: on "final" all output will have been closed down, and the vfd closed,
+ * no matter what the return value says.
+ *
+ * NB: must not have open vins at this or a higher level in the stack.
+ *
+ * NB: does not at this stage discard the obuf.
*/
-static bool
+static cmd_return_code_t
uty_vf_write_close(vio_vf vf, bool final)
{
- bool empty ;
+ cmd_return_code_t ret ;
+ bool base ;
- assert((vf->vout_state != vf_closed) && (vf->vin_state == vf_closed)) ;
+ VTY_ASSERT_CAN_CLOSE_VF(vf) ;
- /* Worry about remaining return input from vout pipe TODO */
+ ret = CMD_SUCCESS ;
- /* Worry about appending the close reason to the vout_base TODO */
+ if (vf->vout_state == vf_closed)
+ return ret ; /* quit if already closed */
- /* Now the vout_type specific clean up. */
- empty = false ;
+ vf->vout_state = vf_closing ; /* TODO wipes out error etc ? */
+ base = (vf == vf->vio->vout_base) ;
+
+ /* Must close vin before closing vout at the same level.
+ *
+ * Note that cannot currently be a pipe return slave, because if was
+ * slave to a VOUT_PIPE/VOUT_SHELL_ONLY that vout must have been closed
+ * already, and if was slave to a VIN_PIPE, then that too will have been
+ * closed already (because of the above).
+ */
+ assert( (vf->vio->vin_depth < vf->vio->vout->depth_mark)
+ || (vf->vio->vout_depth ==0) ) ;
+ assert(vf->pr_master == NULL) ;
+
+ /* If there is anything in the obuf beyond the end_mark, then it is
+ * assumed to be surplus to requirements, and we clear the end_mark.
+ */
+ vio_fifo_back_to_end_mark(vf->obuf, false) ;
+
+ /* The vout_type specific close functions will attempt to write
+ * everything away.
+ *
+ * If "final", will only keep going until blocks -- at which point will
+ * bring everything to a shuddering halt.
+ */
switch(vf->vout_type)
{
case VOUT_NONE:
@@ -934,50 +1153,54 @@ uty_vf_write_close(vio_vf vf, bool final)
break ;
case VOUT_TERM:
- empty = uty_term_write_close(vf, final) ;
+ ret = uty_term_write_close(vf, final, base) ;
break ;
case VOUT_VTYSH:
break ;
case VOUT_FILE:
- empty = uty_file_write_close(vf, final) ;
+ ret = uty_file_write_close(vf, final, base) ;
break ;
case VOUT_PIPE:
+ case VOUT_SHELL_ONLY:
+ ret = uty_pipe_write_close(vf, final, base,
+ vf->vout_type == VOUT_SHELL_ONLY) ;
break ;
case VOUT_CONFIG:
+ ret = uty_file_write_close(vf, final, base) ; /* treat as file */
break ;
case VOUT_DEV_NULL:
case VOUT_STDOUT:
case VOUT_STDERR:
- empty = true ;
+ ret = CMD_SUCCESS ;
break ;
default:
zabort("unknown VOUT type") ;
} ;
- if (empty || final)
+ assert(vf->vin_state == vf_closed) ;
+
+ if (((ret == CMD_SUCCESS) && !base) || final)
{
- /* Do the vfd level close and mark the vf no longer write_open */
+
+ assert(vf->vio->obuf == vf->obuf) ;
+ assert(vio_fifo_empty(vf->obuf)) ;
+
+
if (vf->vout_type < VOUT_SPECIALS)
vf->vfd = vio_vfd_close(vf->vfd) ;
else
assert(vf->vfd == NULL) ;
vf->vout_state = vf_closed ;
- vf->write_on = off ;
-
- if (ssl_del(vf->vio->vout_closing, vf, vout_next))
- uty_vf_free(vf) ;
- else
- assert(vf == vf->vio->vout_base) ;
} ;
- return empty ;
+ return ret ;
} ;
/*------------------------------------------------------------------------------
@@ -992,27 +1215,27 @@ uty_vf_write_close(vio_vf vf, bool final)
static vio_vf
uty_vf_free(vio_vf vf)
{
+ assert((vf->vin_state == vf_closed) && (vf->vout_state == vf_closed)
+ && (vf->pr_state == vf_closed)) ;
+
XFREE(MTYPE_VTY_NAME, vf->name) ;
- vf->ibuf = vio_fifo_reset(vf->ibuf, free_it) ;
- vf->obuf = vio_fifo_reset(vf->obuf, free_it) ;
+ assert(vf->cli == NULL) ;
+
+ vf->ibuf = vio_fifo_reset(vf->ibuf, free_it) ;
+ vf->cl = qs_reset(vf->cl, free_it) ;
+ vf->obuf = vio_fifo_reset(vf->obuf, free_it) ;
- qs_reset(vf->cl, keep_it) ;
+ vf->context = cmd_context_free(vf->context, false) ; /* not a copy */
- vf->cli = uty_cli_close(vf->cli, true) ;
- vf->vfd = vio_vfd_close(vf->vfd) ; /* for completeness */
+ vf->vfd = vio_vfd_close(vf->vfd) ; /* for completeness */
+ vf->pr_vfd = vio_vfd_close(vf->pr_vfd) ; /* for completeness */
XFREE(MTYPE_VTY, vf) ;
return NULL ;
} ;
-/*------------------------------------------------------------------------------
- *
- *
- */
-
-
@@ -1032,7 +1255,7 @@ uty_vf_error(vio_vf vf, const char* what, int err)
VTY_ASSERT_CLI_THREAD() ;
/* can no longer be a monitor ! *before* any logging ! */
- uty_set_monitor(vio, 0) ;
+ uty_set_monitor(vio, off) ;
/* if this is the first error, log it */
if (vf->error_seen == 0)
@@ -1041,13 +1264,16 @@ uty_vf_error(vio_vf vf, const char* what, int err)
vf->error_seen = err ;
- uzlog(NULL, LOG_WARNING, "%s: %s failed on fd %d: %s",
+ zlog(NULL, LOG_WARNING, "%s: %s failed on fd %d: %s",
type, what, vio_vfd_fd(vf->vfd), errtoa(err, 0).str) ;
} ;
return -1 ;
} ;
+
+
+
/*------------------------------------------------------------------------------
* Set required read ready state. Applies the current read timeout.
*
@@ -1056,31 +1282,31 @@ uty_vf_error(vio_vf vf, const char* what, int err)
* Does nothing if: vf->vin_state == vf_closed
* or: vf->vfd == NULL
*
- * Returns new state of vf->read_on
+ * NB: must NOT be a "blocking" vf
*/
-extern on_off_b
+extern void
uty_vf_set_read(vio_vf vf, on_off_b how)
{
if (vf->vin_state != vf_open)
how = off ;
- return vf->read_on = (vf->vin_state != vf_closed)
- ? vio_vfd_set_read(vf->vfd, how, vf->read_timeout)
- : off ;
+ if (vf->vin_state != vf_closed)
+ vio_vfd_set_read(vf->vfd, how, vf->read_timeout) ;
} ;
/*------------------------------------------------------------------------------
- * Set required read ready timeout -- if already read_on, restart it.
+ * Set required read ready timeout -- if already read on, restart it.
*
- * Returns new state of vf->read_on
+ * If this is a blocking vf, will set the timeout value, but since can never
+ * be "read on", will never attempt to restart any timer !
*/
-extern on_off_b
+extern void
uty_vf_set_read_timeout(vio_vf vf, vty_timer_time read_timeout)
{
vf->read_timeout = read_timeout ;
- return vf->read_on ? uty_vf_set_read(vf, on)
- : off ;
+ if (!vf->blocking)
+ uty_vf_set_read(vf, on) ;
} ;
/*------------------------------------------------------------------------------
@@ -1091,31 +1317,511 @@ uty_vf_set_read_timeout(vio_vf vf, vty_timer_time read_timeout)
* Does nothing if: vf->vout_state == vf_closed
* or: vf->vfd == NULL
*
- * Returns new state of vf->write_on
+ * NB: must NOT be a "blocking" vf
*/
-extern on_off_b
+extern void
uty_vf_set_write(vio_vf vf, on_off_b how)
{
- if (vf->vout_state != vf_open)
+ if ((vf->vout_state != vf_open) && (vf->vout_state != vf_closing))
how = off ;
- return vf->write_on = (vf->vout_state != vf_closed)
- ? vio_vfd_set_write(vf->vfd, how, vf->write_timeout)
- : off ;
+ if (vf->vout_state != vf_closed)
+ vio_vfd_set_write(vf->vfd, how, vf->write_timeout) ;
} ;
/*------------------------------------------------------------------------------
- * Set required write ready timeout -- if already write_on, restart it.
+ * Set required write ready timeout -- if already write on, restart it.
*
- * Returns new state of vf->write_on
+ * If this is a blocking vf, will set the timeout value, but since can never
+ * be "write on", will never attempt to restart any timer !
*/
-extern on_off_b
+extern void
uty_vf_set_write_timeout(vio_vf vf, vty_timer_time write_timeout)
{
vf->write_timeout = write_timeout ;
- return vf->write_on ? uty_vf_set_write(vf, on)
- : off ;
+ if (!vf->blocking)
+ uty_vf_set_write(vf, on) ;
+} ;
+
+/*==============================================================================
+ * Child care.
+ *
+ * Management of vio_child objects and the vio_childer_list.
+ *
+ * When child is registered it is placed on the vio_childer_list. It remains
+ * there until it is "collected", that is until a waitpid() has returned a
+ * "report" for the child.
+ *
+ * When a parent waits for a child to be collected, it may be in one of three
+ * states:
+ *
+ * TODO
+ *
+ * When a child is collected, or becomes overdue, the parent is signalled (if
+ * required) and the child->awaited state is cleared.
+ *
+ * When a parent "dismisses" a child, if it has not yet been collected it is
+ * "smacked" -- kill(SIGTERM) -- but kept on the register until it is
+ * collected. When a child which has been collected is dismissed it is freed.
+ *
+ * At "curtains" the register may contain children which have been dismissed,
+ * but not yet collected. Should NOT contain any children with living parents.
+ * All children remaining on the register are smacked, and all with no living
+ * parents are freed. (This could leave children on the register, but avoids
+ * the possibility of a dangling reference from a parent.)
+ */
+static void uty_child_collected(vio_child child, int report) ;
+static vty_timer_time vty_child_overdue(vio_timer timer, void* action_info) ;
+static void uty_child_signal_parent(vio_child child) ;
+static vio_child uty_child_free(vio_child child) ;
+
+/*------------------------------------------------------------------------------
+ * Set vty_child_signal_nexus() -- if required.
+ *
+ * Note that this is set/cleared under VTY_LOCK() and its own mutex. This
+ * allows it to be read under its own mutex and/or VTY_LOCK().
+ */
+extern void
+uty_child_signal_nexus_set(vty_io vio)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ assert(vio->blocking) ;
+
+ if (!vty_is_cli_thread())
+ {
+ qpt_mutex_lock(vty_child_signal_mutex) ;
+
+ vty_child_signal_nexus = qpn_find_self() ;
+
+ qpt_mutex_unlock(vty_child_signal_mutex) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If there is a nexus to signal, clear the indicator and signal the
+ * associated thread.
+ */
+extern void
+vty_child_signal_nexus_signal(void)
+{
+ qpt_mutex_lock(vty_child_signal_mutex) ;
+
+ if (vty_child_signal_nexus != NULL)
+ qpt_thread_signal(vty_child_signal_nexus->thread_id,
+ vty_child_signal_nexus->pselect_signal) ;
+
+ qpt_mutex_unlock(vty_child_signal_mutex) ;
+}
+
+/*------------------------------------------------------------------------------
+ * Set vty_child_signal_nexus() -- if required.
+ *
+ * Note that this is set/cleared under VTY_LOCK() and its own mutex. This
+ * allows it to be read under its own mutex and/or VTY_LOCK().
+ */
+extern void
+uty_child_signal_nexus_clear(vty_io vio)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ assert(vio->blocking) ;
+
+ if (!vty_is_cli_thread())
+ {
+ qpt_mutex_lock(vty_child_signal_mutex) ;
+
+ vty_child_signal_nexus = NULL ;
+
+ qpt_mutex_unlock(vty_child_signal_mutex) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * New child.
+ *
+ * NB: must register child promptly (under VTY_LOCK, at same time as fork)
+ * in order to avoid missing the SIG_CHLD !
+ */
+extern vio_child
+uty_child_register(pid_t pid, vio_vf parent)
+{
+ vio_child child ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ child = XCALLOC(MTYPE_VTY, sizeof(struct vio_child)) ;
+
+ /* Zeroising has set:
+ *
+ * list -- NULLs -- added to vio_childer_list, below
+ *
+ * parent -- NULL -- parent vf set, below
+ *
+ * pid -- X -- child pid set below
+ * collected -- false -- not yet collected
+ * report -- X -- not relevant until collected
+ *
+ * awaited -- false -- not waiting for child
+ * overdue -- false -- child not overdue
+ * timer -- NULL -- no waiting timer set
+ */
+
+ child->parent = parent ;
+ child->pid = pid ;
+
+ sdl_push(vio_childer_list, child, list) ;
+
+ return child ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set waiting for child to be collected.
+ *
+ * This is for !vf->blocking: set timer and leave, waiting for SIGCHLD event.
+ */
+extern void
+uty_child_awaited(vio_child child, vty_timer_time timeout)
+{
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+
+ assert(child->parent != NULL) ;
+ assert(!child->parent->blocking) ;
+
+ child->awaited = true ;
+
+ if (child->timer == NULL)
+ child->timer = vio_timer_init_new(NULL, vty_child_overdue, child) ;
+
+ vio_timer_set(child->timer, timeout) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * See if parent can collect child -- directly.
+ *
+ * This is for vf->blocking,
+ *
+ * If can, will collect now -- marking collected and taking off the
+ * vio_childer_list.
+ *
+ * If cannot immediately collect, if final mark overdue and return.
+ *
+ * Otherwise wait for up to timeout seconds for a suitable SIGCHLD or related
+ * wake-up signal.
+ *
+ * NB: this blocks with the VTY_LOCK() in its hands !! But this is only
+ * required for configuration file reading... and timeout is limited.
+ *
+ * Returns: true <=> collected
+ * false => timed out or final
+ */
+extern bool
+uty_child_collect(vio_child child, vty_timer_time timeout, bool final)
+{
+ bool first ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ assert(child->parent != NULL) ;
+ assert(child->parent->blocking) ;
+ assert(child->timer == NULL) ;
+
+ assert(child->pid > 0) ;
+
+ first = true ;
+ while (1)
+ {
+ pid_t pid ;
+ int report ;
+
+ qps_mini_t qm ;
+ sigset_t* sig_mask = NULL ;
+
+ if (child->collected)
+ return true ; /* have collected */
+
+ pid = waitpid(child->pid, &report, WNOHANG) ;
+
+ if (pid == child->pid)
+ {
+ /* Collected the child */
+ uty_child_collected(child, report) ;
+
+ return true ; /* have collected */
+ } ;
+
+ if (pid != 0)
+ {
+ int err = 0 ;
+
+ /* With the exception of EINTR, all other returns are, essentially,
+ * impossible...
+ *
+ * (1) ECHLD means that the given pid is not a child...
+ * ...which is impossible if not collected -- but treat as
+ * "overdue".
+ *
+ * (2) only other known error is EINVAL -- invalid options...
+ * absolutely impossible.
+ *
+ * (3) some pid other than the given child is an invalid response !
+ */
+ if (pid < 0)
+ {
+ if (errno == EINTR)
+ continue ;
+
+ err = errno ;
+
+ zlog_err("waitpid(%d) returned %s", child->pid,
+ errtoa(err, 0).str) ;
+ }
+ else
+ zlog_err("waitpid(%d) returned pid=%d", child->pid, pid) ;
+
+ if (err != ECHILD)
+ zabort("impossible return from waitpid()") ;
+
+ final = true ; /* treat ECHLD as last straw ! */
+ } ;
+
+ /* Waiting for child. */
+ if (final)
+ {
+ child->overdue = true ;
+ return false ; /* overdue */
+ } ;
+
+ /* Need to wait -- if this is the first time through, prepare for
+ * that.
+ */
+ if (first)
+ {
+ qps_mini_set(qm, -1, 0, 6) ;
+
+ if (vty_is_cli_thread())
+ sig_mask = NULL ;
+ else
+ sig_mask = vty_child_signal_nexus->pselect_mask ;
+
+ first = false ;
+ } ;
+
+ /* Wait on pselect. */
+ if (qps_mini_wait(qm, sig_mask, true) == 0)
+ final = true ; /* timed out => now final */
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Dismiss child -- if not collected, smack but leave to be collected in
+ * due course (or swept up at "curtains").
+ */
+extern vio_child
+uty_child_dismiss(vio_child child, bool final)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if (child != NULL)
+ {
+ if (!child->collected)
+ {
+ assert(child->pid > 0) ;
+
+ kill(child->pid, SIGKILL) ; /* hasten the end */
+
+ if (final)
+ {
+ assert(child->parent == NULL) ;
+ sdl_del(vio_childer_list, child, list) ;
+ child->collected = true ; /* forceably */
+ } ;
+
+ child->overdue = true ; /* too late for parent */
+ } ;
+
+ child->parent = NULL ; /* orphan from now on */
+ child->awaited = false ; /* nobody waiting */
+
+ if (child->collected)
+ uty_child_free(child) ;
+ } ;
+
+ return NULL ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * At "curtains" -- empty out anything left in the child register.
+ *
+ * The only children that can be left are dismissed children that have yet to
+ * be collected.
+ */
+extern void
+vty_child_close_register(void)
+{
+ while (vio_childer_list != NULL)
+ uty_child_dismiss(vio_childer_list, true) ; /* final */
+} ;
+
+/*------------------------------------------------------------------------------
+ * See whether any children are ready for collection, and check each one
+ * against the register.
+ *
+ * Perform waitpid( , , WNOHANG) until no child is returned, and process
+ * each one against the register.
+ *
+ * The is done when a SIGCHLD is route through the event mechanism.
+ *
+ * If another SIGCHLD occurs while this is being done, that will later cause
+ * another call of this function -- at worst it may find that the child in
+ * question has already been collected.
+ *
+ * This is also done when about to block waiting for a child.
+ *
+ * Set any children that can be collected, collected and signal to any parents
+ * that their children are now ready.
+ */
+extern void
+uty_sigchld(void)
+{
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+
+ while (1)
+ {
+ vio_child child ;
+ pid_t pid ;
+ int report ;
+
+ pid = waitpid(-1, &report, WNOHANG) ;
+
+ if (pid == 0)
+ break ;
+
+ if (pid < 0)
+ {
+ if (errno == EINTR)
+ continue ; /* loop on "Interrupted" */
+
+ if (errno != ECHILD) /* returns ECHLD if no children */
+ zlog_err("waitpid(-1) returned %s", errtoa(errno, 0).str) ;
+
+ break ;
+ } ;
+
+ child = vio_childer_list ;
+ while (1)
+ {
+ if (child == NULL)
+ {
+ zlog_err("waitpid(-1) returned pid %d, which is not registered",
+ pid) ;
+ break ;
+ } ;
+
+ if (child->pid == pid)
+ {
+ /* Have collected child.
+ *
+ * Remove from the vio_childer_list, set collected flag.
+ *
+ * We can leave any timer object -- if it goes off it will be
+ * ignored, because the child is no longer awaited. Timer will
+ * be discarded when the child is dismissed/freed.
+ *
+ * If no parent, can free child object now.
+ */
+ uty_child_collected(child, report) ;
+
+ if (child->parent == NULL)
+ uty_child_free(child) ;
+ else if (child->awaited)
+ uty_child_signal_parent(child) ;
+
+ break ;
+ } ;
+
+ child = sdl_next(child, list) ;
+ } ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the child collected and set the report.
+ *
+ * Remove from the vio_childer_list -- is now either back in the hands of the
+ * parent, or ready to be freed.
+ */
+static void
+uty_child_collected(vio_child child, int report)
+{
+ assert(!child->collected) ; /* can only collect once */
+
+ sdl_del(vio_childer_list, child, list) ;
+
+ child->collected = true ;
+ child->report = report ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set child as overdue -- vio_timer action routine.
+ *
+ * NB: the timer may go off after the child has been collected, but before the
+ * parent has got round to stopping the timer.
+ */
+static vty_timer_time
+vty_child_overdue(vio_timer timer, void* action_info)
+{
+ vio_child child ;
+
+ VTY_LOCK() ;
+
+ child = action_info ;
+ assert(timer == child->timer) ;
+
+ if (child->awaited)
+ {
+ child->overdue = true ;
+ uty_child_signal_parent(child) ;
+ } ;
+
+ VTY_UNLOCK() ;
+
+ return 0 ; /* stop timer */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Signal that child is ready -- collected or overdue.
+ *
+ * Must be "awaited" -- so not "blocking"
+ */
+static void
+uty_child_signal_parent(vio_child child)
+{
+ assert(child->awaited && (child->parent != NULL)) ;
+
+ assert(!child->parent->vio->blocking) ;
+
+ child->awaited = false ;
+ uty_cmd_signal(child->parent->vio, CMD_SUCCESS) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Free the child -- caller must ensure that any parent has disowned the child,
+ * and that it is collected (so not on the vio_childer_list).
+ */
+static vio_child
+uty_child_free(vio_child child)
+{
+ if (child != NULL)
+ {
+ assert(child->collected && (child->parent == NULL)) ;
+
+ child->timer = vio_timer_reset(child->timer, free_it) ;
+ XFREE(MTYPE_VTY, child) ; /* sets child = NULL */
+ } ;
+
+ return child ;
} ;
/*==============================================================================
diff --git a/lib/vty_io.h b/lib/vty_io.h
index 5db12ca2..24790526 100644
--- a/lib/vty_io.h
+++ b/lib/vty_io.h
@@ -36,6 +36,7 @@
#include "thread.h"
#include "command_execute.h"
#include "qstring.h"
+#include "list_util.h"
/*==============================================================================
* Structures and other definitions for the top level VTY I/O.
@@ -92,9 +93,9 @@ enum vio_out_type /* Command output */
VOUT_VTYSH, /* a vty_shell output pipe */
VOUT_FILE, /* ordinary file */
- VOUT_PIPE, /* pipe (to child process) */
+ VOUT_PIPE, /* pipe (to child process, and back again) */
- VOUT_CONFIG, /* config file ?? */
+ VOUT_CONFIG, /* config file */
/* The VOUT types >= VOUT_SPECIALS do not have an associated fd.
*
@@ -105,32 +106,73 @@ enum vio_out_type /* Command output */
VOUT_DEV_NULL = VOUT_SPECIALS,
/* black hole output */
+ VOUT_SHELL_ONLY, /* pipe (but only back from child process */
+
VOUT_STDOUT, /* stdout */
VOUT_STDERR, /* stderr */
};
typedef enum vio_out_type vio_out_type_t ;
/*------------------------------------------------------------------------------
- * State of a vf or of the entire vio.
- *
- * For a vf: I/O is possible iff vf_open -- has separate state for vin/vout.
- *
- * For a vio: used to manage the closing process.
+ * State of a vf -- has separate state for vin/vout.
*/
enum vf_state
{
- vf_closed = 0, /* not active -- may be discarded */
+ vf_closed = 0, /* the vf has not been opened, or has been
+ * completely closed -- there will be no vfd. */
+
+ vf_open, /* the vf has been opened, and any required vfd
+ * is open and I/O is possible. */
+
+ vf_eof, /* for a vin: end of file has been reached or
+ * input has been terminated. The vfd may have
+ * been closed -- but in any case no further
+ * I/O should be attempted, and any buffered
+ * input will be discarded. */
- vf_open = 1, /* open for business */
+ vf_closing, /* for a vout: open, but in process of closing.
+ * May still output stuff. */
- vf_eof = 2, /* open, but at eof */
- vf_error = 3, /* open, but error reported */
+ vf_error, /* an I/O error has been reported.
+ * The vfd may or may not have been closed and
+ * I/O may or may not be possible TODO */
- vf_closing = 4, /* open, but in process of closing */
+ vf_timed_out, /* a timout has been reported.
+ * The vfd may or may not have been closed and
+ * I/O may or may not be possible TODO */
} ;
typedef enum vf_state vf_state_t ;
/*------------------------------------------------------------------------------
+ * vio_child structure.
+ *
+ * Lives on the vio_childer_list until collected or "curtains".
+ *
+ */
+typedef enum vio_child_await vio_child_await_t ;
+
+struct vio_vf ; /* Forward reference */
+typedef struct vio_vf* vio_vf ;
+
+typedef struct vio_child* vio_child ;
+
+struct vio_child
+{
+ struct dl_list_pair(vio_child) list ; /* in the list of children */
+
+ vio_vf parent ;
+
+ pid_t pid ;
+ bool collected ; /* waitpid() done */
+ int report ; /* from waitpid() */
+
+ bool overdue ; /* patience exhausted */
+
+ bool awaited ; /* if child is awaited -- !vf->blocking */
+ vio_timer timer ; /* limit the waiting time */
+} ;
+
+/*------------------------------------------------------------------------------
* vty_vf -- "vty file" structure
*
* A vio_vf may be a read, write or read/write object.
@@ -148,8 +190,6 @@ typedef enum vf_state vf_state_t ;
struct vty_io ; /* Forward reference */
typedef struct vty_io* vty_io ;
-typedef struct vio_vf* vio_vf ;
-
struct vio_vf
{
vty_io vio ; /* parent */
@@ -162,28 +202,28 @@ struct vio_vf
vf_state_t vin_state ;
vio_vf vin_next ; /* list of inputs */
+ cmd_context context ; /* pushed exec->context. */
struct vty_cli* cli ; /* NULL if not a VTY_TERMINAL ! */
vio_fifo ibuf ; /* input fifo (if required) */
- qstring_t cl ; /* command line buffer */
+ qstring cl ; /* command line buffer */
bool line_complete ; /* false => line in construction */
uint line_number ; /* number of first line in cl */
uint line_step ; /* number of real lines in cl */
- cmd_parse_type_t parse_type ; /* iff vin_type != VIN_NONE */
- bool reflect_enabled ; /* false if vin_type == VIN_NONE */
-
/* Output side. */
vio_out_type_t vout_type ;
vf_state_t vout_state ;
vio_vf vout_next ; /* list of outputs */
+ vio_vf pr_master ; /* receiving return stuff from there */
+
vio_fifo obuf ; /* output fifo (if required) */
- bool out_enabled ; /* false if vout_type == VOUT_NONE */
+ uint depth_mark ; /* depth of this vout */
/* I/O */
@@ -193,12 +233,19 @@ struct vio_vf
int error_seen ; /* non-zero => failed */
- on_off_b read_on ;
- on_off_b write_on ;
-
vty_timer_time read_timeout ;
vty_timer_time write_timeout ;
+ /* Pipe extras */
+
+ vio_child child ; /* state of child */
+
+ vf_state_t pr_state ; /* iff pipe */
+ vio_vfd pr_vfd ; /* if pr_state == vf_open */
+
+ vio_vf pr_slave ; /* sending return stuff to there */
+
+ bool pr_only ; /* no vfd */
} ;
enum vty_readiness /* bit significant */
@@ -206,11 +253,36 @@ enum vty_readiness /* bit significant */
not_ready = 0,
read_ready = BIT(0),
write_ready = BIT(1), /* may take precedence */
- now_ready = BIT(2)
} ;
typedef enum vty_readiness vty_readiness_t ;
/*------------------------------------------------------------------------------
+ * State of a vty command loop.
+ */
+enum vc_state
+{
+ vc_null = 0, /* the command loop has not yet been entered. */
+
+ vc_running, /* the command loop is running, and the vty is
+ * in its hands. */
+
+ vc_io_error_trap, /* the command loop is running, but an I/O
+ * error has been posted. */
+
+ vc_close_trap, /* the command loop is running, but the vty is
+ * reset and must be closed */
+
+ vc_waiting, /* the command loop is waiting for I/O. */
+
+ vc_stopped, /* the command loop has stopped, the vty is due
+ to be closed (loop cannot run again) */
+
+ vc_closed, /* the command loop has finished and the vty
+ * has been closed. */
+} ;
+typedef enum vc_state vc_state_t ;
+
+/*------------------------------------------------------------------------------
* The vty_io structure
*
* The main elements of the vty_io object are the vin and vout stacks.
@@ -223,7 +295,7 @@ typedef enum vty_readiness vty_readiness_t ;
*
*
*
- * "cmd_running" means that the VTY is in hands of (or has been passed to)
+ * "cmd_running" means that the VTY is in hands of (or has been passed to) TODO
* a command loop -- the VTY cannot be fully closed until that is no
* longer the case.
*
@@ -237,7 +309,7 @@ typedef enum vty_readiness vty_readiness_t ;
*
* - any further attempts to read will give "eof"
*
- * - there may be a command in execution -- see "cmd_running".
+ * - there may be a command in execution -- see "cmd_running". TODO
*
* - further writing will be honoured.
*
@@ -265,62 +337,82 @@ struct vty_io /* typedef appears above */
vio_vf vin_base ;
uint vin_depth ;
+ uint real_depth ; /* less than vin_depth when closing */
+
vio_vf vout ; /* vout stack */
vio_vf vout_base ;
uint vout_depth ;
- vio_vf vout_closing ; /* vout closing list */
+ uint depth_mark ; /* vin_depth before pipes opened */
+
+ /* Error handling */
+ bool err_hard ; /* eg I/O error */
+ vio_fifo ebuf ; /* buffer for error message */
/* List of all vty_io objects */
struct dl_list_pair(vty_io) vio_list ;
- /* List of all vty_io that are in monitor state */
- struct dl_list_pair(vty_io) mon_list ;
-
- /* VTY state
+ /* State
*
* "blocking" is set for configuration reading VTY, so that everything is
* done with blocking I/O.
*
- * "half_closed" means that the vty has been closed (!), but a command
- * and or output may still be active:
- *
- * - if is a socket, will have shut_down the read side (half-closed)
- *
- * - any further attempts to read will give "eof"
- *
- * - there may be a command in execution -- see "cmd_running".
- *
- * - further writing will be honoured.
- *
- * - the write side may still be active, attempting to empty out any
- * pending output.
- *
- * "cmd_running" means that the VTY is in hands of (or has been passed to)
- * a command loop -- the VTY cannot be fully closed until that is no
- * longer the case.
- *
- * "closed" means the vty has been fully and finally closed.
- *
- * - any further attempts to write will be ignored, but return instant
- * success.
+ * "state" as described above.
+ */
+ bool blocking ; /* => all I/O is blocking. */
+
+ vc_state_t state ;
+
+ char* close_reason ; /* MTYPE_TMP (if any) */
+
+ /* For ease of output, pointer to current vout->obuf
*
- * - the file/socket has been fully closed.
+ * Even when the vty is almost closed, there will remain a valid obuf,
+ * though anything sent to it under those conditions will be discarded.
+ */
+ vio_fifo obuf ;
+
+ /* The following is for "vty monitor".
*
- * - the VTY and all attached structures can be reaped by the death_watch.
+ * With the exception of the "monitor" flag, need the LOG_MUTEX in order
+ * to change any of this.
*/
- bool blocking ; /* => all I/O is blocking. */
+ bool monitor ; /* is in monitor state */
- bool cmd_running ; /* => cannot be fully closed */
+ bool mon_kick ; /* vty needs a kick */
+ int maxlvl ; /* message level wish to see */
- vf_state_t state ;
+ vio_fifo mbuf ; /* monitor output pending */
- qstring close_reason ; /* message to be sent, once all other
- output has completed, giving reason
- for closing the VTY. */
+ /* List of all vty_io that are in monitor state */
+ struct dl_list_pair(vty_io) mon_list ;
+} ;
- /* For ease of output, pointer to current vout->obuf */
- vio_fifo obuf ; /* NULL => no vout ! */
+/*==============================================================================
+ * Assertions for suitable state to close things !
+ */
+Inline void
+VTY_ASSERT_CAN_CLOSE(vty vty)
+{
+ if (vty_debug)
+ {
+ VTY_ASSERT_LOCKED() ;
+
+ if (!vty->vio->blocking && !vty_is_cli_thread())
+ VTY_ASSERT_FAILED() ;
+ } ;
+} ;
+
+Inline void
+VTY_ASSERT_CAN_CLOSE_VF(vio_vf vf)
+{
+ if (vty_debug)
+ {
+ VTY_ASSERT_LOCKED() ;
+
+ if (!vf->blocking && !vty_is_cli_thread())
+ VTY_ASSERT_FAILED() ;
+ } ;
} ;
/*==============================================================================
@@ -334,35 +426,46 @@ Inline void uty_out_clear(vty_io vio) ;
Inline void uty_out_accept(vty_io vio) ;
Inline void uty_out_reject(vty_io vio) ;
-extern vty uty_new (vty_type_t type) ;
-extern bool uty_close(vty_io vio, bool final, qstring reason) ;
-
-extern void uty_vin_close(vty_io vio) ;
-extern void uty_vout_close(vty_io vio, bool final) ;
-extern void uty_vin_close_stack(vty_io vio) ;
-extern void uty_vout_close_stack(vty_io vio, bool final) ;
+extern vty uty_new (vty_type_t type, node_type_t node) ;
+extern void uty_close(vty_io vio, const char* reason, bool curtains) ;
extern void uty_set_timeout(vty_io vio, vty_timer_time timeout) ;
-extern void uty_vin_open(vty_io vio, vio_vf vf, vio_in_type_t type,
+extern void uty_vin_new_context(vty_io vio, cmd_context context,
+ qpath file_here) ;
+extern void uty_vin_push(vty_io vio, vio_vf vf, vio_in_type_t type,
vio_vfd_action* read_action,
vio_timer_action* read_timer_action,
usize ibuf_size) ;
-extern void uty_vout_open(vty_io vio, vio_vf vf, vio_out_type_t type,
+extern void uty_vout_push(vty_io vio, vio_vf vf, vio_out_type_t type,
vio_vfd_action* write_action,
vio_timer_action* write_timer_action,
usize obuf_size) ;
+extern cmd_return_code_t uty_vin_pop(vty_io vio, bool final,
+ cmd_context context) ;
+extern cmd_return_code_t uty_vout_pop(vty_io vio, bool final) ;
+
extern vio_vf uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type,
vfd_io_type_t io_type) ;
-extern on_off_b uty_vf_set_read(vio_vf vf, on_off_b on) ;
-extern on_off_b uty_vf_set_read_timeout(vio_vf vf,
- vty_timer_time read_timeout) ;
-extern on_off_b uty_vf_set_write(vio_vf vf, on_off_b on) ;
-extern on_off_b uty_vf_set_write_timeout(vio_vf vf,
- vty_timer_time write_timeout) ;
+extern void uty_vf_set_read(vio_vf vf, on_off_b on) ;
+extern void uty_vf_set_read_timeout(vio_vf vf, vty_timer_time read_timeout) ;
+extern void uty_vf_set_write(vio_vf vf, on_off_b on) ;
+extern void uty_vf_set_write_timeout(vio_vf vf, vty_timer_time write_timeout) ;
extern int uty_vf_error(vio_vf vf, const char* what, int err) ;
+extern vio_child uty_child_register(pid_t pid, vio_vf parent) ;
+extern void vty_child_close_register(void) ;
+extern void uty_child_awaited(vio_child child, vty_timer_time timeout) ;
+extern bool uty_child_collect(vio_child child, vty_timer_time timeout,
+ bool final) ;
+extern vio_child uty_child_dismiss(vio_child child, bool final) ;
+extern void uty_sigchld(void) ;
+
+extern void uty_child_signal_nexus_set(vty_io vio) ;
+extern void vty_child_signal_nexus_signal(void) ;
+extern void uty_child_signal_nexus_clear(vty_io vio) ;
+
extern void uty_open_listeners(const char *addr, unsigned short port,
const char *path) ;
diff --git a/lib/vty_io_basic.c b/lib/vty_io_basic.c
index 85ea2587..3239c893 100644
--- a/lib/vty_io_basic.c
+++ b/lib/vty_io_basic.c
@@ -34,7 +34,7 @@
*
*/
-/*==============================================================================
+/*------------------------------------------------------------------------------
* Try to open the given file for the given type of I/O.
*
* vfd_io_write => create if does not exist (mode 0600)
@@ -80,7 +80,7 @@ uty_vfd_file_open(const char* name, vfd_io_type_t io_type)
if ((io_type & vfd_io_blocking) == 0)
oflag |= O_NONBLOCK ;
- return open(name, oflag, S_IRUSR | S_IWUSR) ;
+ return open(name, oflag, S_IRUSR | S_IWUSR) ; /* TODO umask etc ? */
} ;
/*==============================================================================
@@ -90,25 +90,8 @@ uty_vfd_file_open(const char* name, vfd_io_type_t io_type)
* and an old thread environment are encapsulated here.
*/
-struct vio_io_set_args /* to CLI thread */
-{
- bool active ; /* set when queued, cleared when dequeued */
- bool close ; /* close and free the vio_vfd and mqb */
-
- bool readable ; /* set when read state to be changed */
- on_off_b read_on ; /* what to change read to */
- vty_timer_time read_timeout ;
- /* what to set the timeout to, if any */
-
- bool writable ; /* set when write state to be changed */
- on_off_b write_on ; /* what to change write to */
- vty_timer_time write_timeout ;
- /* what to set the timeout to, if any */
-} ;
-MQB_ARGS_SIZE_OK(vio_io_set_args) ;
-
-static void vio_vfd_mqb_dispatch(vio_vfd vfd) ;
-static struct vio_io_set_args* vio_vfd_mqb_args(vio_vfd vfd) ;
+static void vio_vfd_mqb_kick(vio_vfd vfd) ;
+static void vio_vfd_mqb_free(vio_vfd vfd) ;
/*==============================================================================
* File Descriptor handling
@@ -130,24 +113,30 @@ static void vio_vfd_qps_write_action(qps_file qf, void* file_info) ;
static int vio_vfd_thread_read_action(struct thread *thread) ;
static int vio_vfd_thread_write_action(struct thread *thread) ;
-static void vio_timer_squelch(vio_timer_t* timer) ;
+static void vio_timer_squelch(vio_timer timer) ;
Inline void
vio_vfd_do_read_action(vio_vfd vfd)
{
- if (vfd->active)
+ if (vfd->read_action != NULL)
vfd->read_action(vfd, vfd->action_info) ;
}
Inline void
vio_vfd_do_write_action(vio_vfd vfd)
{
- if (vfd->active)
+ if (vfd->write_action != NULL)
vfd->write_action(vfd, vfd->action_info) ;
} ;
/*------------------------------------------------------------------------------
* Create a new vfd structure.
+ *
+ * Note that sets the same action info for read, write, read timeout and
+ * write timeout.
+ *
+ * Sets the blocking_vf state, which will disallow any attempt to set read/write
+ * ready/timeout -- but enable open/close when not in cli thread.
*/
extern vio_vfd
vio_vfd_new(int fd, vfd_type_t type, vfd_io_type_t io_type, void* action_info)
@@ -158,45 +147,59 @@ vio_vfd_new(int fd, vfd_type_t type, vfd_io_type_t io_type, void* action_info)
/* Has set:
*
- * active -- false !
+ * fd -- X -- see below
+ * active -- X -- see below
+ *
+ * type -- X -- see below
+ * io_type -- X -- see below
+ *
+ * blocking_vf -- X -- see below
+ *
+ * action_info -- NULL -- set below if !blocking_vf
*
* read_action -- NULL
* write_action -- NULL
*
- * f.qf -- NULL
+ * read_timer -- NULL -- set below if !blocking_vf
+ * write_timer -- NULL -- set below if !blocking_vf
+ *
+ * f.qf -- NULL -- set below if !blocking_vf
+ *
* f.thread.read -- NULL
* f.thread.write -- NULL
*
- * mqb -- NULL
+ * queued -- false
+ *
+ * read_req -- all zeros -- none set
+ * write_req -- all zeros -- none set
+ *
+ * mqb -- NULL -- none, yet
*/
+ confirm(VIO_TIMER_INIT_ZERO) ;
- vio_vfd_set_fd(vfd, fd, type, io_type) ;
+ vfd->fd = fd ;
+ vfd->type = type ;
+ vfd->io_type = io_type ;
- vio_timer_init(&vfd->read_timer, NULL, NULL) ;
- vio_timer_init(&vfd->write_timer, NULL, NULL) ;
+ vfd->blocking_vf = (io_type & vfd_io_blocking) != 0 ;
- vio_vfd_set_action_info(vfd, action_info) ;
+ if (!vfd->blocking_vf)
+ {
+ VTY_ASSERT_CLI_THREAD() ;
- return vfd ;
-} ;
+ if (vty_nexus)
+ {
+ vfd->f.qf = qps_file_init_new(NULL, NULL) ;
+ qps_add_file(vty_cli_nexus->selection, vfd->f.qf, vfd->fd, vfd) ;
+ } ;
-/*------------------------------------------------------------------------------
- * If vfd was not fully set up when created, set it up now.
- *
- * To close an active vfd, use vio_vfd_close() !
- *
- * NB: for use when vfd has been created, but the fd was not known at that
- * time -- ie the vfd is NOT active.
- */
-extern void
-vio_vfd_set_fd(vio_vfd vfd, int fd, vfd_type_t type, vfd_io_type_t io_type)
-{
- assert(!vfd->active) ;
+ vfd->read_timer = vio_timer_init_new(NULL, NULL, NULL) ;
+ vfd->write_timer = vio_timer_init_new(NULL, NULL, NULL) ;
- vfd->fd = fd ;
- vfd->active = (fd >= 0) ;
- vfd->type = type ;
- vfd->io_type = io_type ;
+ vio_vfd_set_action_info(vfd, action_info) ;
+ } ;
+
+ return vfd ;
} ;
/*------------------------------------------------------------------------------
@@ -223,7 +226,7 @@ vio_vfd_set_write_action(vio_vfd vfd, vio_vfd_action* action)
extern void
vio_vfd_set_read_timeout_action(vio_vfd vfd, vio_timer_action* action)
{
- vio_timer_set_action(&vfd->read_timer, action) ;
+ vio_timer_set_action(vfd->read_timer, action) ;
} ;
/*------------------------------------------------------------------------------
@@ -232,7 +235,7 @@ vio_vfd_set_read_timeout_action(vio_vfd vfd, vio_timer_action* action)
extern void
vio_vfd_set_write_timeout_action(vio_vfd vfd, vio_timer_action* action)
{
- vio_timer_set_action(&vfd->write_timer, action) ;
+ vio_timer_set_action(vfd->write_timer, action) ;
} ;
/*------------------------------------------------------------------------------
@@ -242,8 +245,8 @@ extern void
vio_vfd_set_action_info(vio_vfd vfd, void* action_info)
{
vfd->action_info = action_info ;
- vio_timer_set_info(&vfd->read_timer, action_info) ;
- vio_timer_set_info(&vfd->write_timer, action_info) ;
+ vio_timer_set_info(vfd->read_timer, action_info) ;
+ vio_timer_set_info(vfd->write_timer, action_info) ;
} ;
#if 0
@@ -278,6 +281,8 @@ vio_vfd_kick_write_action(vio_vfd vfd)
* In any case, turns off any read ready and read ready timeout.
*
* Returns original vfd, or NULL if it has been closed.
+ *
+ * NB: if this is not a "blocking_vf", then MUST be in the cli thread.
*/
extern vio_vfd
vio_vfd_read_close(vio_vfd vfd)
@@ -287,47 +292,60 @@ vio_vfd_read_close(vio_vfd vfd)
if (vfd == NULL)
return NULL ;
- if (vfd->fd >= 0)
- {
- assert(vfd->active) ;
+ if (!vfd->blocking_vf)
+ VTY_ASSERT_CLI_THREAD() ;
- if (vfd->io_type & vfd_io_read)
+ if ((vfd->io_type & vfd_io_read) != 0)
+ {
+ if ((vfd->io_type & vfd_io_write) != 0)
{
- if (vfd->io_type & vfd_io_write)
+ /* read & write, so really half-close if can */
+ if (vfd->fd >= 0)
{
- /* read & write, so really half-close if can */
if (vfd->type == vfd_socket)
shutdown(vfd->fd, SHUT_RD) ; /* ignore errors TODO */
vio_vfd_set_read(vfd, off, 0) ;
- vfd->io_type ^= vfd_io_read ; /* now write only ! */
- }
- else
- {
- /* read only, so fully close */
- vfd = vio_vfd_close(vfd) ;
+ vfd->io_type ^= vfd_io_read ; /* now write only ! */
} ;
+ }
+ else
+ {
+ /* read only, so fully close */
+ vfd = vio_vfd_close(vfd) ;
} ;
- }
- else
- assert(!vfd->active) ;
+ } ;
return vfd ;
} ;
/*------------------------------------------------------------------------------
- * If there is an fd, close it.
+ * Close the given vfd (if any).
*
- * Stops any read/write waiting and releases all memory, including the vfd
- * itself if required..
+ * If there is an fd, close it. Stops any read/write waiting and releases all
+ * memory.
+ *
+ * NB: if this is not a "blocking_vf", then MUST be in the cli thread.
+ * Inter alia, this guarantees that cannot be in the middle of a read/write
+ * ready/timeout operation -- so the file can be closed down, and
+ * any pending ready/timeout will be swept away.
*/
-static void
-vio_vfd_do_close(vio_vfd vfd, free_keep_b free)
+extern vio_vfd
+vio_vfd_close(vio_vfd vfd)
{
+ VTY_ASSERT_LOCKED() ;
+
if (vfd == NULL)
- return ;
+ return NULL ;
- VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
+ if (!vfd->blocking_vf)
+ VTY_ASSERT_CLI_THREAD() ;
+
+ /* Close the underlying fd, if any */
+
+ if (vfd->fd >= 0)
+ close(vfd->fd) ; /* ignores errors TODO */
+
+ /* Clear out the vfd then free it */
if (vty_nexus)
{
@@ -337,11 +355,7 @@ vio_vfd_do_close(vio_vfd vfd, free_keep_b free)
vfd->f.qf = qps_file_free(vfd->f.qf) ;
} ;
- if (vfd->mqb != NULL)
- {
- mqb_free(vfd->mqb) ;
- vfd->mqb = NULL ;
- } ;
+ vio_vfd_mqb_free(vfd) ;
}
else
{
@@ -362,85 +376,10 @@ vio_vfd_do_close(vio_vfd vfd, free_keep_b free)
assert(vfd->mqb == NULL) ;
} ;
- if (vfd->fd >= 0)
- close(vfd->fd) ; /* ignores errors TODO */
-
- vio_timer_reset(&vfd->read_timer) ;
- vio_timer_reset(&vfd->write_timer) ;
-
- if (free == free_it)
- XFREE(MTYPE_VTY, vfd) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Close the given vfd (if any).
- *
- * If there is an fd, close it. Stops any read/write waiting and releases all
- * memory.
- *
- * NB: this can done from any thread, but if not done from the CLI thread,
- * and there is a qf, a message must be sent to the CLI thread to actually
- * implement: which passes the vio_vfd to the CLI thread for later
- * close and destruction.
- *
- * The actual close has to be delayed, so that cannot open another fd
- * and bang into a still active qps_file !
- *
- * The message looks after freeing the vio_vfd, the qps_file and the mqb.
- */
-extern vio_vfd
-vio_vfd_close(vio_vfd vfd)
-{
- VTY_ASSERT_LOCKED() ;
+ vfd->read_timer = vio_timer_reset(vfd->read_timer, free_it) ;
+ vfd->write_timer = vio_timer_reset(vfd->write_timer, free_it) ;
- if (vfd == NULL)
- return NULL ;
-
- if (vfd->fd < 0)
- {
- /* closing an inactive vio_vfd -- make sure all is quiet */
- assert(!vfd->active) ;
- if (vty_nexus)
- {
- assert(vfd->f.qf == NULL) ;
- }
- else
- {
- assert(vfd->f.thread.read == NULL) ;
- assert(vfd->f.thread.write == NULL) ;
- } ;
- assert(vfd->mqb == NULL) ;
- }
- else
- {
- /* closing an active vio_vfd */
- if (vty_is_cli_thread())
- {
- /* In cli thread, so close directly */
- vio_vfd_do_close(vfd, free_it) ;
- }
- else
- {
- /* Rats... have to send message to cli thread to close */
- struct vio_io_set_args* args = vio_vfd_mqb_args(vfd) ;
-
- args->close = true ;
-
- /* in case something goes ready before the close message
- * is processed, squelch.
- */
- vfd->active = false ;
- vfd->read_action = NULL ;
- vfd->write_action = NULL ;
- vfd->action_info = NULL ;
-
- vio_timer_squelch(&vfd->read_timer) ;
- vio_timer_squelch(&vfd->write_timer) ;
-
- assert(vfd == mqb_get_arg0(vfd->mqb)) ;
- vio_vfd_mqb_dispatch(vfd) ;
- } ;
- } ;
+ XFREE(MTYPE_VTY, vfd) ;
return NULL ;
} ;
@@ -448,48 +387,39 @@ vio_vfd_close(vio_vfd vfd)
/*------------------------------------------------------------------------------
* Set or unset read ready state on given vio_vfd (if any) if it is active.
*
+ * Do nothing if vfd NULL, fd < 0 or not a read type of fd.
+ *
* If setting read_on, starts any read timeout timer (or stops it if 0).
* If setting read off, stops any read timeout timer.
*
* NB: this can done from any thread, but if not done from the CLI thread,
* a message must be sent to the CLI thread to actually implement.
+ *
+ * NB: must NOT be a "blocking_vf" !!
*/
extern on_off_b
vio_vfd_set_read(vio_vfd vfd, on_off_b on, vty_timer_time timeout)
{
- struct vio_io_set_args* args ;
-
VTY_ASSERT_LOCKED() ;
- if ((vfd == NULL) || (!vfd->active))
+ if ((vfd == NULL) || (vfd->fd < 0) || ((vfd->io_type & vfd_io_read) == 0))
return off ;
+ assert(!vfd->blocking_vf) ;
+
if (vty_is_cli_thread())
{
/* In the cli thread (effectively) so do things directly. */
- if (vfd->mqb != NULL)
- {
- /* discard/override any pending message setting */
- args = mqb_get_args(vfd->mqb) ;
- args->readable = false ;
- } ;
+ vfd->read_req.set = false ; /* doing or overriding request */
if (on)
{
assert(vfd->read_action != NULL) ;
if (vty_nexus)
- {
- if (vfd->f.qf == NULL)
- {
- vfd->f.qf = qps_file_init_new(NULL, NULL);
- qps_add_file(vty_cli_nexus->selection, vfd->f.qf,
- vfd->fd, vfd) ;
- } ;
qps_enable_mode(vfd->f.qf, qps_read_mnum,
vio_vfd_qps_read_action) ;
- }
else
{
if (vfd->f.thread.read == NULL)
@@ -497,34 +427,31 @@ vio_vfd_set_read(vio_vfd vfd, on_off_b on, vty_timer_time timeout)
vio_vfd_thread_read_action, vfd, vfd->fd) ;
} ;
- vio_timer_set(&vfd->read_timer, timeout) ;
+ vio_timer_set(vfd->read_timer, timeout) ;
}
else
{
if (vty_nexus)
- {
- if (vfd->f.qf != NULL)
- qps_disable_modes(vfd->f.qf, qps_read_mbit) ;
- }
+ qps_disable_modes(vfd->f.qf, qps_read_mbit) ;
else
{
if (vfd->f.thread.read != NULL)
thread_cancel (vfd->f.thread.read) ;
} ;
- vio_timer_unset(&vfd->read_timer) ;
+ vio_timer_unset(vfd->read_timer) ;
} ;
}
else
{
/* In other threads, must send message to cli thread */
+ vfd->read_req.set = true ;
+ vfd->read_req.on = on ;
+ vfd->read_req.timeout = timeout ;
+
+ vio_timer_squelch(vfd->read_timer) ;
- args = vio_vfd_mqb_args(vfd) ;
- args->readable = true ;
- args->read_on = on ;
- args->read_timeout = timeout ;
- vio_timer_squelch(&vfd->read_timer) ;
- vio_vfd_mqb_dispatch(vfd) ;
+ vio_vfd_mqb_kick(vfd) ;
} ;
return on ;
@@ -533,48 +460,39 @@ vio_vfd_set_read(vio_vfd vfd, on_off_b on, vty_timer_time timeout)
/*------------------------------------------------------------------------------
* Set or unset write ready state on given vio_vfd (if any) if it is active.
*
+ * Do nothing if vfd NULL, fd < 0 or not a write type of fd.
+ *
* If setting write_on, starts any write timeout timer.
* If setting write off, stops any write timeout timer.
*
* NB: this can done from any thread, but if not done from the CLI thread,
* a message must be sent to the CLI thread to actually implement.
+ *
+ * NB: must NOT be a "blocking_vf" !!
*/
extern on_off_b
vio_vfd_set_write(vio_vfd vfd, on_off_b on, vty_timer_time timeout)
{
- struct vio_io_set_args* args ;
-
VTY_ASSERT_LOCKED() ;
- if ((vfd == NULL) || (!vfd->active))
+ if ((vfd == NULL) || (vfd->fd < 0) || ((vfd->io_type & vfd_io_write) == 0))
return off ;
+ assert(!vfd->blocking_vf) ;
+
if (vty_is_cli_thread())
{
/* In the cli thread (effectively) so do things directly. */
- if (vfd->mqb != NULL)
- {
- /* discard/override any pending message setting */
- args = mqb_get_args(vfd->mqb) ;
- args->writable = false ;
- } ;
+ vfd->write_req.set = false ; /* doing or overriding request */
if (on)
{
assert(vfd->write_action != NULL) ;
if (vty_nexus)
- {
- if (vfd->f.qf == NULL)
- {
- vfd->f.qf = qps_file_init_new(NULL, NULL);
- qps_add_file(vty_cli_nexus->selection, vfd->f.qf,
- vfd->fd, vfd) ;
- } ;
- qps_enable_mode(vfd->f.qf, qps_write_mnum,
+ qps_enable_mode(vfd->f.qf, qps_write_mnum,
vio_vfd_qps_write_action) ;
- }
else
{
if (vfd->f.thread.write == NULL)
@@ -582,34 +500,31 @@ vio_vfd_set_write(vio_vfd vfd, on_off_b on, vty_timer_time timeout)
vio_vfd_thread_write_action, vfd, vfd->fd) ;
} ;
- vio_timer_set(&vfd->write_timer, timeout) ;
+ vio_timer_set(vfd->write_timer, timeout) ;
}
else
{
if (vty_nexus)
- {
- if (vfd->f.qf != NULL)
- qps_disable_modes(vfd->f.qf, qps_write_mbit) ;
- }
+ qps_disable_modes(vfd->f.qf, qps_write_mbit) ;
else
{
if (vfd->f.thread.write != NULL)
thread_cancel (vfd->f.thread.write) ;
} ;
- vio_timer_unset(&vfd->write_timer) ;
+ vio_timer_unset(vfd->write_timer) ;
} ;
}
else
{
/* In other threads, must send message to cli thread */
+ vfd->write_req.set = true ;
+ vfd->write_req.on = on ;
+ vfd->write_req.timeout = timeout ;
+
+ vio_timer_squelch(vfd->write_timer) ;
- args = vio_vfd_mqb_args(vfd) ;
- args->writable = true ;
- args->write_on = on ;
- args->write_timeout = timeout ;
- vio_timer_squelch(&vfd->write_timer) ;
- vio_vfd_mqb_dispatch(vfd) ;
+ vio_vfd_mqb_kick(vfd) ;
} ;
return on ;
@@ -619,24 +534,21 @@ vio_vfd_set_write(vio_vfd vfd, on_off_b on, vty_timer_time timeout)
* Callback -- qnexus: ready to read
*
* Clears read ready state and unsets any read timer.
- *
- * NB: if !vfd->active, then has been closed in another thread, but close
- * message is yet to be procesed.
*/
static void
vio_vfd_qps_read_action(qps_file qf, void* file_info)
{
vio_vfd vfd ;
- VTY_ASSERT_CLI_THREAD() ;
VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
vfd = file_info ;
assert((vfd->fd == qf->fd) && (vfd->f.qf == qf)) ;
qps_disable_modes(vfd->f.qf, qps_read_mbit) ;
- vio_timer_unset(&vfd->read_timer) ;
+ vio_timer_unset(vfd->read_timer) ;
vio_vfd_do_read_action(vfd) ;
@@ -656,15 +568,15 @@ vio_vfd_thread_read_action(struct thread *thread)
{
vio_vfd vfd ;
- VTY_ASSERT_CLI_THREAD() ;
VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
vfd = THREAD_ARG(thread);
assert(vfd->fd == THREAD_FD(thread)) ;
vfd->f.thread.read = NULL ; /* implicitly */
- vio_timer_unset(&vfd->read_timer) ;
+ vio_timer_unset(vfd->read_timer) ;
vio_vfd_do_read_action(vfd) ;
@@ -685,13 +597,13 @@ vio_vfd_qps_write_action(qps_file qf, void* file_info)
{
vio_vfd vfd = file_info ;
- VTY_ASSERT_CLI_THREAD() ;
VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
assert((vfd->fd == qf->fd) && (vfd->f.qf == qf)) ;
qps_disable_modes(vfd->f.qf, qps_write_mbit) ;
- vio_timer_unset(&vfd->write_timer) ;
+ vio_timer_unset(vfd->write_timer) ;
vio_vfd_do_write_action(vfd) ;
@@ -711,11 +623,11 @@ vio_vfd_thread_write_action(struct thread *thread)
{
vio_vfd vfd = THREAD_ARG (thread);
- VTY_ASSERT_CLI_THREAD() ;
VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
assert(vfd->fd == THREAD_FD(thread)) ;
- vio_timer_unset(&vfd->write_timer) ;
+ vio_timer_unset(vfd->write_timer) ;
vfd->f.thread.write = NULL ; /* implicitly */
@@ -740,37 +652,42 @@ vio_vfd_thread_write_action(struct thread *thread)
* which it will do when it is dequeued and actioned.
*/
-static void vio_vfd_set_action(mqueue_block mqb, mqb_flag_t flag) ;
+static void vio_vfd_mqb_action(mqueue_block mqb, mqb_flag_t flag) ;
/*------------------------------------------------------------------------------
- * Get mqb for the given vfd -- make one if required.
+ * Dispatch mqb, if not already active
*/
-static struct vio_io_set_args*
-vio_vfd_mqb_args(vio_vfd vfd)
+static void
+vio_vfd_mqb_kick(vio_vfd vfd)
{
VTY_ASSERT_LOCKED() ;
- if (vfd->mqb == NULL)
- vfd->mqb = mqb_init_new(NULL, vio_vfd_set_action, vfd) ;
+ if (!vfd->queued)
+ {
+ vfd->queued = true ;
+
+ if (vfd->mqb == NULL)
+ vfd->mqb = mqb_init_new(NULL, vio_vfd_mqb_action, vfd) ;
- return mqb_get_args(vfd->mqb) ;
+ mqueue_enqueue(vty_cli_nexus->queue, vfd->mqb, mqb_priority) ;
+ } ;
} ;
/*------------------------------------------------------------------------------
- * Dispatch mqb, if not already active
+ * Free mqb, if exists.
*/
static void
-vio_vfd_mqb_dispatch(vio_vfd vfd)
+vio_vfd_mqb_free(vio_vfd vfd)
{
- struct vio_io_set_args* args = mqb_get_args(vfd->mqb) ;
-
VTY_ASSERT_LOCKED() ;
- if (!args->active)
- {
- args->active = true ;
- mqueue_enqueue(vty_cli_nexus->queue, vfd->mqb, mqb_ordinary) ;
- } ;
+ if (vfd->queued)
+ mqb_set_arg0(vfd->mqb, NULL) ; /* mqb will suicide */
+ else
+ mqb_free(vfd->mqb) ; /* kill now (if any) */
+
+ vfd->queued = false ;
+ vfd->mqb = NULL ;
} ;
/*------------------------------------------------------------------------------
@@ -783,36 +700,33 @@ vio_vfd_mqb_dispatch(vio_vfd vfd)
* If the mqb is being revoked
*/
static void
-vio_vfd_set_action(mqueue_block mqb, mqb_flag_t flag)
+vio_vfd_mqb_action(mqueue_block mqb, mqb_flag_t flag)
{
- struct vio_io_set_args* args ;
- vio_vfd vfd ;
+ vio_vfd vfd ;
- VTY_ASSERT_CLI_THREAD() ;
VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
- args = mqb_get_args(mqb) ;
vfd = mqb_get_arg0(mqb) ;
- args->active = false ;
+ if (vfd != NULL)
+ {
+ assert(mqb == vfd->mqb) ;
+ vfd->queued = false ;
+ } ;
- if ((flag != mqb_destroy) && (!args->close))
+ if ((flag != mqb_destroy) && (vfd != NULL))
{
- if (args->readable)
- vio_vfd_set_read(vfd, args->read_on, args->read_timeout) ;
- if (args->writable)
- vio_vfd_set_write(vfd, args->write_on, args->write_timeout) ;
+ if (vfd->read_req.set)
+ vio_vfd_set_read(vfd, vfd->read_req.on, vfd->read_req.timeout) ;
+ if (vfd->write_req.set)
+ vio_vfd_set_write(vfd, vfd->write_req.on, vfd->write_req.timeout) ;
}
else
{
- /* Revoke and/or close.
- *
- * If close can free the vfd.
- *
- * If just revoke (should not happen), then cannot free the vfd, because
- * somewhere there can be a dangling reference.
- */
- vio_vfd_do_close(vfd, args->close) ; /* frees the mqb */
+ mqb_free(mqb) ; /* Suicide */
+ if (vfd != NULL)
+ vfd->mqb = NULL ; /* make sure vfd knows */
} ;
VTY_UNLOCK() ;
@@ -839,8 +753,7 @@ vio_listener_new(int fd, vio_vfd_accept* accept_action)
{
vio_listener listener ;
- VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
listener = XCALLOC(MTYPE_VTY, sizeof(struct vio_listener)) ;
/* sets the next pointer to NULL */
@@ -864,8 +777,7 @@ vio_listener_new(int fd, vio_vfd_accept* accept_action)
extern void
vio_listener_close(vio_listener listener)
{
- VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
vio_vfd_close(listener->vfd) ;
XFREE(MTYPE_VTY, listener) ;
@@ -881,8 +793,7 @@ vio_accept(vio_vfd vfd, void* info)
{
vio_listener listener ;
- VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
listener = info ;
assert(vfd == listener->vfd) ;
@@ -896,7 +807,7 @@ vio_accept(vio_vfd vfd, void* info)
* Timer Handling
*
* Provides timer primitives that work either in qnexus environment or in
- * a thread environment.
+ * a thread environment. Timer times are seconds.
*
* The main difference is that thread environment timers are 'one-shot', set up
* for one timing run and then destroyed.
@@ -906,14 +817,17 @@ static void vio_timer_qtr_action(qtimer qtr, void* timer_info, qtime_t when) ;
static int vio_timer_thread_action(struct thread *thread) ;
/*------------------------------------------------------------------------------
- * Initialise vio_timer structure. Assumes is all new.
- *
- * This assumes the vio_timer structure is embedded in another structure.
+ * Allocate and/or initialise vio_timer structure.
*/
-extern void
-vio_timer_init(vio_timer_t* timer, vio_timer_action* action, void* action_info)
+extern vio_timer
+vio_timer_init_new(vio_timer timer, vio_timer_action* action, void* action_info)
{
- memset(timer, 0, sizeof(vio_timer_t)) ;
+ if (timer == NULL)
+ timer = XCALLOC(MTYPE_VTY, sizeof(vio_timer_t)) ;
+ else
+ memset(timer, 0, sizeof(vio_timer_t)) ;
+
+ confirm(VIO_TIMER_INIT_ZERO) ;
/* active -- 0, false
* squelch -- 0, false
@@ -922,74 +836,88 @@ vio_timer_init(vio_timer_t* timer, vio_timer_action* action, void* action_info)
timer->action = action ;
timer->action_info = action_info ;
+
+ return timer ;
} ;
/*------------------------------------------------------------------------------
* Set the action field for the given timer.
*/
extern void
-vio_timer_set_action(vio_timer_t* timer, vio_timer_action* action)
+vio_timer_set_action(vio_timer timer, vio_timer_action* action)
{
- timer->action = action ;
+ if (timer != NULL)
+ timer->action = action ;
} ;
/*------------------------------------------------------------------------------
* Set the info field for the given timer.
*/
extern void
-vio_timer_set_info(vio_timer_t* timer, void* action_info)
+vio_timer_set_info(vio_timer timer, void* action_info)
{
- timer->action_info = action_info ;
+ if (timer != NULL)
+ timer->action_info = action_info ;
} ;
/*------------------------------------------------------------------------------
- * Kill vio_timer -- used when closing .
+ * Squelch the given timer -- if it goes off, do not call the action routine,
+ * and leave the rimer inactive.
+ *
+ * Used when doing read/write ready from not-cli thread.
*/
static void
-vio_timer_squelch(vio_timer_t* timer)
+vio_timer_squelch(vio_timer timer)
{
VTY_ASSERT_LOCKED() ;
- timer->squelch = true ;
+ if (timer != NULL)
+ timer->squelch = true ;
} ;
/*------------------------------------------------------------------------------
* Reset vio_timer structure. Stops any timer and releases all memory.
- *
- * This assumes the vio_timer structure is embedded in another structure.
*/
-extern void
-vio_timer_reset(vio_timer_t* timer)
+extern vio_timer
+vio_timer_reset(vio_timer timer, free_keep_b free_structure)
{
VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
- if (timer->t.anon != NULL)
+ if (timer != NULL)
{
- if (vty_nexus)
- qtimer_free(timer->t.qtr) ;
- else
- thread_cancel(timer->t.thread) ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ if (timer->t.anon != NULL)
+ {
+ if (vty_nexus)
+ qtimer_free(timer->t.qtr) ;
+ else
+ thread_cancel(timer->t.thread) ;
- timer->t.anon = NULL ;
+ timer->t.anon = NULL ;
+ } ;
+
+ timer->active = false ;
+ timer->squelch = false ;
+
+ if (free_structure)
+ XFREE(MTYPE_VTY, timer) ; /* sets timer = NULL */
} ;
- timer->active = false ;
- timer->squelch = false ;
+ return timer ;
} ;
/*------------------------------------------------------------------------------
- * Set vio_timer going, with the given time.
+ * Set vio_timer going, with the given time (in seconds).
*
* If timer is running, set to new time.
*
* If the time == 0, stop any current timer, do not restart.
*/
extern void
-vio_timer_set(vio_timer_t* timer, vty_timer_time time)
+vio_timer_set(vio_timer timer, vty_timer_time time)
{
- VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
if (time == 0)
{
@@ -1022,10 +950,9 @@ vio_timer_set(vio_timer_t* timer, vty_timer_time time)
* Stop vio_timer, if any.
*/
extern void
-vio_timer_unset(vio_timer_t* timer)
+vio_timer_unset(vio_timer timer)
{
- VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
if (timer->active)
{
@@ -1053,11 +980,11 @@ vio_timer_unset(vio_timer_t* timer)
static void
vio_timer_qtr_action(qtimer qtr, void* timer_info, qtime_t when)
{
- vio_timer_t* timer ;
+ vio_timer timer ;
vty_timer_time time ;
- VTY_ASSERT_CLI_THREAD() ;
VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
timer = timer_info ;
@@ -1084,11 +1011,11 @@ vio_timer_qtr_action(qtimer qtr, void* timer_info, qtime_t when)
static int
vio_timer_thread_action(struct thread *thread)
{
- vio_timer_t* timer ;
+ vio_timer timer ;
vty_timer_time time ;
- VTY_ASSERT_CLI_THREAD() ;
VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
timer = THREAD_ARG(thread) ;
timer->t.thread = NULL ; /* implicitly */
diff --git a/lib/vty_io_basic.h b/lib/vty_io_basic.h
index 00ff923b..9d174fdd 100644
--- a/lib/vty_io_basic.h
+++ b/lib/vty_io_basic.h
@@ -72,8 +72,8 @@ typedef enum vfd_io_type vfd_io_type_t ;
*/
typedef unsigned long vty_timer_time ; /* Time out time in seconds */
-typedef struct vio_timer vio_timer_t ;
-typedef vty_timer_time vio_timer_action(vio_timer_t* timer, void* action_info) ;
+typedef struct vio_timer* vio_timer ;
+typedef vty_timer_time vio_timer_action(vio_timer timer, void* action_info) ;
struct vio_timer
{
@@ -89,6 +89,9 @@ struct vio_timer
void* anon ;
} t ;
} ;
+typedef struct vio_timer vio_timer_t[1] ;
+
+enum { VIO_TIMER_INIT_ZERO = true } ;
/*------------------------------------------------------------------------------
* File descriptors -- looks after ready to read and/or write.
@@ -100,21 +103,40 @@ typedef struct vio_vfd* vio_vfd ;
typedef void vio_vfd_action(vio_vfd vfd, void* action_info) ;
+typedef struct
+{
+ bool set ;
+ on_off_b on ;
+ vty_timer_time timeout ;
+} vfd_request_t ;
+
struct vio_vfd
{
int fd ;
- bool active ; /* used for close message */
vfd_type_t type ; /* used for half-close */
vfd_io_type_t io_type ; /* read, write, read/write */
+ /* The rest of the vfd is to do with managing read/write ready and
+ * read/write timeouts.
+ *
+ * At the vf level we manage "blocking" vfs, which do not and must not
+ * use read/write ready or read/write timeouts. When the vfd is created
+ * this is passed, so that mistakes can be spotted !
+ *
+ * Non-blocking vfs may only be created and closed in the cli thread (or
+ * when running in the legacy threads environment). This is because can
+ * only dismantle qps_file structure while in the owning thread.
+ */
+ bool blocking_vf ; /* Reject read/write ready etc. */
+
+ void* action_info ; /* for all action and time-out */
+
vio_vfd_action* read_action ;
vio_vfd_action* write_action ;
- vio_timer_t read_timer ;
- vio_timer_t write_timer ;
-
- void* action_info ; /* for all action and time-out */
+ vio_timer read_timer ;
+ vio_timer write_timer ;
union
{
@@ -127,7 +149,20 @@ struct vio_vfd
} thread ;
} f ;
- mqueue_block mqb ;
+ /* To support remote setting clearing of read/write ready and read/write
+ * timeout -- by remote we mean anything such action not in the cli thread.
+ *
+ * This is required because the qpselect and qtimer stuff assumes that those
+ * structures are private to the thread whose qnexus they belong to !
+ *
+ * This stuff is only required when running multi-pthreaded.
+ */
+ bool queued ; /* message is on queue */
+
+ vfd_request_t read_req ;
+ vfd_request_t write_req ;
+
+ mqueue_block mqb ; /* message if any */
} ;
/*------------------------------------------------------------------------------
@@ -152,9 +187,7 @@ struct vio_listener
extern int uty_vfd_file_open(const char* name, vfd_io_type_t io_type) ;
extern vio_vfd vio_vfd_new(int fd, vfd_type_t type,
- vfd_io_type_t io_type, void* action_info) ;
-extern void vio_vfd_set_fd(vio_vfd vfd, int fd, vfd_type_t type,
- vfd_io_type_t io_type) ;
+ vfd_io_type_t io_type, void* action_info) ;
extern void vio_vfd_set_read_action(vio_vfd vfd, vio_vfd_action* action) ;
extern void vio_vfd_set_write_action(vio_vfd vfd, vio_vfd_action* action) ;
extern void vio_vfd_set_read_timeout_action(vio_vfd vfd,
@@ -173,13 +206,13 @@ Inline int vio_vfd_fd(vio_vfd vfd) ;
extern vio_listener vio_listener_new(int fd, vio_vfd_accept* accept) ;
extern void vio_listener_close(vio_listener listener) ;
-extern void vio_timer_init(vio_timer_t* timer, vio_timer_action* action,
+extern vio_timer vio_timer_init_new(vio_timer timer, vio_timer_action* action,
void* action_info) ;
-extern void vio_timer_set_action(vio_timer_t* timer, vio_timer_action* action) ;
-extern void vio_timer_set_info(vio_timer_t* timer, void* action_info) ;
-extern void vio_timer_reset(vio_timer_t* timer) ;
-extern void vio_timer_set(vio_timer_t* timer, vty_timer_time time) ;
-extern void vio_timer_unset(vio_timer_t* timer) ;
+extern vio_timer vio_timer_reset(vio_timer timer, free_keep_b free_structure) ;
+extern void vio_timer_set_action(vio_timer timer, vio_timer_action* action) ;
+extern void vio_timer_set_info(vio_timer timer, void* action_info) ;
+extern void vio_timer_set(vio_timer timer, vty_timer_time time) ;
+extern void vio_timer_unset(vio_timer timer) ;
/*==============================================================================
* Inline Functions
diff --git a/lib/vty_io_file.c b/lib/vty_io_file.c
index 2d17276a..2322114a 100644
--- a/lib/vty_io_file.c
+++ b/lib/vty_io_file.c
@@ -20,114 +20,339 @@
* 02111-1307, USA.
*/
#include "misc.h"
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <errno.h>
#include "command_local.h"
+#include "vty_io.h"
#include "vty_io_file.h"
#include "vty_io_basic.h"
+#include "vty_command.h"
+#include "network.h"
+#include "sigevent.h"
/*==============================================================================
- * VTY File Output
+ * VTY Configuration file I/O -- VIN_CONFIG and VOUT_CONFIG.
+ *
*
- * This is for input and output of configuration files and piped stuff.
*
- * When reading the configuration (and piped stuff in the configuration) I/O
- * is blocking... nothing else can run while this is going on. Otherwise,
- * all I/O is non-blocking.
*/
+/*------------------------------------------------------------------------------
+ * Set up VTY on which to read configuration file -- using already open fd.
+ *
+ * Sets TODO
+ *
+ * NB: sets up a blocking vio -- so the vin_base and vout_base will "block"
+ * (using local pselect() as required), as will any further vin/vout
+ * opened for this vio.
+ */
+extern vty
+vty_config_read_open(int fd, const char* name, bool full_lex)
+{
+ vty vty ;
+ vty_io vio ;
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+
+ /* Create the vty and its vio.
+ *
+ * NB: VTY_CONFIG_READ type vty is set vio->blocking.
+ */
+ vty = uty_new(VTY_CONFIG_READ, CONFIG_NODE) ;
+ vio = vty->vio ;
+
+ /* Create the vin_base/vout_base vf
+ *
+ * NB: don't need to specify vfd_io_blocking, because the vio is set blocking.
+ */
+ vf = uty_vf_new(vio, name, fd, vfd_file, vfd_io_read) ;
+
+ uty_vin_push( vio, vf, VIN_CONFIG, NULL, NULL, 64 * 1024) ;
+ uty_vout_push(vio, vf, VOUT_STDERR, NULL, NULL, 4 * 1024) ;
+
+ /* Deal with the possibility that while reading the configuration file, may
+ * use a pipe, and therefore may block waiting to collect a child process.
+ *
+ * Before there is any chance of a SIGCHLD being raised for a child of the
+ * configuration file, invoke the magic required for SIGCHLD to wake up
+ * a pselect() while waiting to collect child.
+ */
+ uty_child_signal_nexus_set(vio) ;
+
+ VTY_UNLOCK() ;
+
+ return vty ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Tidy up after config input file has been closed.
+ *
+ * There is now no further possibility that will block waiting for child to be
+ * collected.
+ *
+ * Nothing further required -- input comes to a halt.
+ */
+extern cmd_return_code_t
+uty_config_read_close(vio_vf vf, bool final)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ uty_child_signal_nexus_clear(vf->vio) ;
+
+ return CMD_SUCCESS ;
+} ;
/*==============================================================================
- * Prototypes.
+ * VTY File I/O -- VIN_FILE and VOUT_FILE
+ *
+ * This is for input and output of configuration files and piped stuff.
+ *
+ * When reading the configuration (and piped stuff in the configuration) I/O
+ * is blocking... nothing else can run while this is going on. Otherwise,
+ * all I/O is non-blocking. The actual I/O is non-blocking, the "blocking"
+ * I/O is manufactured using a mini-pselect, so can time-out file writing
+ * before too long.
*/
+static void uty_file_read_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time uty_file_read_timeout(vio_timer timer,
+ void* action_info) ;
+
+static void uty_file_write_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time uty_file_write_timeout(vio_timer timer,
+ void* action_info) ;
+
+static cmd_return_code_t uty_fifo_command_line(vio_vf vf, cmd_action action) ;
/*------------------------------------------------------------------------------
+ * Open file for input, to be read as commands.
+ *
+ * If could not open, issues message to the vio.
*
+ * If opens OK, save the current context in the current vin (before pushing
+ * the new vin).
*
+ * Returns: CMD_SUCCESS -- all set to go
+ * CMD_WARNING -- failed to open
+ *
+ * If "blocking" vf, this can be called from any thread, otherwise must be the
+ * cli thread -- see uty_vfd_new().
*/
extern cmd_return_code_t
-uty_file_read_open(vty_io vio, qstring name, bool reflect)
+uty_file_read_open(vty_io vio, qstring name, cmd_context context)
{
- const char* name_str ;
- int fd ;
- vio_vf vf ;
+ cmd_return_code_t ret ;
+ qpath path ;
+ const char* pns ;
+ int fd ;
+ vio_vf vf ;
vfd_io_type_t iot ;
- name_str = qs_make_string(name) ;
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+
+ assert(vio->vin != NULL) ; /* Not expected to be vin_base */
+
+ /* Now is the time to complete the name, if required. */
- iot = vfd_io_read | vfd_io_blocking ; /* TODO blocking */
+ path = uty_cmd_path_name_complete(NULL, qs_string(name), context) ;
+ pns = qpath_string(path) ;
/* Do the basic file open. */
- fd = uty_vfd_file_open(name_str, iot) ;
+ iot = vfd_io_read ;
+
+ fd = uty_vfd_file_open(pns, iot) ;
if (fd < 0)
{
- uty_out(vio, "%% Could not open input file %s\n", name_str) ;
+ uty_out(vio, "%% Could not open input file %s\n", pns) ;
- return CMD_WARNING ;
+ ret = CMD_WARNING ; /* TODO add errno to message ? */
}
+ else
+ {
+ /* We have a file, so now save context and update dir "here" */
+ uty_vin_new_context(vio, context, path) ;
- /* OK -- now push the new input onto the vin_stack. */
- vf = uty_vf_new(vio, name_str, fd, vfd_file, iot) ;
- uty_vin_open(vio, vf, VIN_FILE, NULL, NULL, 16 * 1024) ;
+ /* OK -- now push the new input onto the vin_stack. */
+ vf = uty_vf_new(vio, pns, fd, vfd_file, iot) ;
+ uty_vin_push(vio, vf, VIN_FILE, uty_file_read_ready,
+ uty_file_read_timeout, 16 * 1024) ;
+ uty_vf_set_read_timeout(vf, 30) ;
- vf->parse_type = cmd_parse_strict ;
- vf->reflect_enabled = reflect ;
+ ret = CMD_SUCCESS ;
+ } ;
- return CMD_SUCCESS ;
+ qpath_free(path) ;
+ return ret ;
} ;
/*------------------------------------------------------------------------------
+ * Open file for output.
+ *
+ * If could not open, issues message to the vio.
*
+ * Returns: CMD_SUCCESS -- all set to go
+ * CMD_WARNING -- failed to open
*
+ * If "blocking" vf, this can be called from any thread, otherwise must be the
+ * cli thread -- see uty_vfd_new().
*/
extern cmd_return_code_t
-uty_file_write_open(vty_io vio, qstring name, bool append)
+uty_file_write_open(vty_io vio, qstring name, bool append, cmd_context context)
{
- const char* name_str ;
+ cmd_return_code_t ret ;
+ qpath path ;
+ const char* pns ;
int fd ;
vio_vf vf ;
vfd_io_type_t iot ;
- iot = vfd_io_write | vfd_io_blocking ; /* TODO blocking */
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
- if (append)
- iot |= vfd_io_append ;
+ /* Now is the time to complete the name, if required. */
- name_str = qs_make_string(name) ;
+ path = uty_cmd_path_name_complete(NULL, qs_string(name), context) ;
+ pns = qpath_string(path) ;
/* Do the basic file open. */
- fd = uty_vfd_file_open(name_str, iot) ;
+ iot = vfd_io_write | (append ? vfd_io_append : 0) ;
+
+ fd = uty_vfd_file_open(pns, iot) ;
if (fd < 0)
{
- uty_out(vio, "%% Could not open output file %s\n", name_str) ;
+ uty_out(vio, "%% Could not open output file %s\n", pns) ;
- return CMD_WARNING ;
+ ret = CMD_WARNING ; /* TODO add errno to message ? */
}
+ else
+ {
+ /* OK -- now push the new output onto the vout_stack. */
+ vf = uty_vf_new(vio, pns, fd, vfd_file, iot) ;
+ uty_vout_push(vio, vf, VOUT_FILE, uty_file_write_ready,
+ uty_file_write_timeout, 16 * 1024) ;
+ ret = CMD_SUCCESS ;
+ } ;
- /* OK -- now push the new input onto the vin_stack. */
- vf = uty_vf_new(vio, name_str, fd, vfd_file, iot) ;
- uty_vout_open(vio, vf, VOUT_FILE, NULL, NULL, 16 * 1024) ;
-
- vf->out_enabled = true ;
-
- return CMD_SUCCESS ;
+ qpath_free(path) ;
+ return ret ;
} ;
-/*==============================================================================
+/*------------------------------------------------------------------------------
* Command line fetch from a file or pipe.
*
- * Returns: CMD_SUCCESS -- have another command line ready to go
- * CMD_WAITING -- do not have a command line at the moment
- * CMD_EOF -- ran into EOF
- * CMD_IO_ERROR -- ran into an I/O error
+ * Returns: CMD_SUCCESS -- have another command line ready to go
+ * CMD_WAITING -- would not wait for input <=> non-blocking
+ * CMD_EOF -- ran into EOF
+ * CMD_IO_ERROR -- ran into an I/O error
+ * CMD_IO_TIMEOUT -- could wait no longer <=> blocking
+ *
+ * In "non-blocking" state, on CMD_WAITING sets read ready, which will
+ * vty_cmd_signal() the command loop to come round again.
+ *
+ * In "blocking" state, will not return until have something, or there is
+ * nothing to be had, or times out.
+ *
+ * This can be called in any thread.
+ *
+ * NB: the vin_state will become vf_eof the first time that CMD_EOF is
+ * returned.
*/
extern cmd_return_code_t
-uty_file_fetch_command_line(vio_vf vf, qstring* line)
+uty_file_fetch_command_line(vio_vf vf, cmd_action action)
{
- assert(vf->vin_state == vf_open) ;
+ VTY_ASSERT_LOCKED() ; /* In any thread */
+
+ assert((vf->vin_type == VIN_FILE) || (vf->vin_type == VIN_CONFIG)) ;
+ assert((vf->vin_state == vf_open) || (vf->vin_state == vf_eof)) ;
+
+ while (1)
+ {
+ cmd_return_code_t ret ;
+
+ /* Try to complete line straight from the buffer.
+ *
+ * If buffer is empty and have seen eof on the input, signal CMD_EOF.
+ */
+ ret = uty_fifo_command_line(vf, action) ;
+
+ if (ret != CMD_WAITING)
+ return ret ;
+
+ /* Could not complete line and exhausted contents of fifo */
+ while (1)
+ {
+ qps_mini_t qm ;
+ int get ;
+
+ get = vio_fifo_read_nb(vf->ibuf, vio_vfd_fd(vf->vfd), 1) ;
+
+ if (get > 0)
+ break ; /* loop back to uty_fifo_command_line() */
+
+ if (get == -1)
+ return CMD_IO_ERROR ; /* register error */
+
+ if (get == -2)
+ {
+ /* Hit end of file set the vf into vf_eof.
+ *
+ * NB: does not know has hit eof until tries to read and nothing
+ * is returned.
+ *
+ * Loop back so that uty_fifo_command_line() can reconsider, now
+ * that we know that there is no more data to come -- it may
+ * signal CMD_SUCCESS (non-empty final line) or CMD_EOF.
+ */
+ assert(vio_fifo_empty(vf->ibuf)) ;
+
+ vf->vin_state = vf_eof ;
+
+ break ; /* loop back to uty_fifo_command_line() */
+ } ;
+
+ /* Would block -- for non-blocking return CMD_WAITING, for
+ * blocking we block here with a timeout, and when there is more
+ * to read, loop back to
+ */
+ assert(get == 0) ;
+
+ if (!vf->blocking)
+ {
+ uty_vf_set_read(vf, on) ;
+ return CMD_WAITING ;
+ } ;
+
+ /* Implement blocking I/O, with timeout */
+ qps_mini_set(qm, vio_vfd_fd(vf->vfd), qps_read_mnum,
+ vf->read_timeout) ;
+ if (qps_mini_wait(qm, NULL, false) == 0)
+ return CMD_IO_TIMEOUT ; /* TODO vf_timeout ?? */
+
+ /* Loop back to vio_fifo_read_nb() */
+ }
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Try to complete a command line for the given vf, from the current input
+ * fifo -- performs NO I/O
+ *
+ * Returns: CMD_SUCCESS -- have a command line.
+ * CMD_EOF -- there is no more
+ * CMD_WAITING -- waiting for more
+ *
+ * If vf->vin_state == vf_eof, will return CMD_SUCCESS if have last part line
+ * in hand. Otherwise will return CMD_EOF, any number of times.
+ */
+static cmd_return_code_t
+uty_fifo_command_line(vio_vf vf, cmd_action action)
+{
+ VTY_ASSERT_LOCKED() ; /* In any thread */
if (vf->line_complete)
{
@@ -142,47 +367,42 @@ uty_file_fetch_command_line(vio_vf vf, qstring* line)
while (1)
{
- char* s, * p, * q, * e ;
+ char* s, * p, * e ;
size_t have ;
ulen len ;
+ bool eol ;
+ /* Get what we can from the fifo */
s = vio_fifo_get(vf->ibuf, &have) ;
- /* If nothing in hand, try and get some more.
- *
- * Either exits or loops back to set s & have.
- */
+ /* If fifo is empty, may be last line before eof, eof or waiting */
if (have == 0)
{
- int get ;
-
- get = vio_fifo_read_nb(vf->ibuf, vio_vfd_fd(vf->vfd), 100) ;
-
- if (get > 0)
- continue ; /* loop back */
-
- if (get == 0)
- return CMD_WAITING ; /* need to set read ready ! */
-
- if (get == -1)
- ; /* register error */
+ if (vf->vin_state == vf_eof)
+ {
+ if (qs_len_nn(vf->cl) > 0)
+ break ; /* have non-empty last line */
+ else
+ return CMD_EOF ;
+ } ;
- if (get == -2)
- return (qs_len_nn(vf->cl) > 0) ? CMD_SUCCESS : CMD_EOF ;
+ return CMD_WAITING ;
} ;
+ assert(vf->vin_state != vf_eof) ; /* not empty => not eof */
+
/* Try to find a '\n' -- converting all other control chars to ' '
*
* When we find '\n' step back across any trailing ' ' (which includes
* any control chars before the '\n').
*
- * This means that we cope with "\r\n" line terminators. But not anything
- * more exotic.
+ * This means that we cope with "\r\n" line terminators. But not
+ * anything more exotic.
*/
p = s ;
e = s + have ; /* have != 0 */
- q = NULL ;
+ eol = false ;
while (p < e)
{
if (*p++ < 0x20)
@@ -195,9 +415,7 @@ uty_file_fetch_command_line(vio_vf vf, qstring* line)
++vf->line_step ; /* got a '\n' */
- q = p ; /* point just past '\n' */
- do --q ; while ((q > s) && (*(q-1) == ' ')) ;
- /* discard trailing "spaces" */
+ eol = true ;
break ;
} ;
} ;
@@ -212,28 +430,30 @@ uty_file_fetch_command_line(vio_vf vf, qstring* line)
*
* Loops back to try to get some more form the fifo.
*/
- if (q == NULL)
+ if (!eol)
{
- qs_append_n(vf->cl, s, p - s) ;
+ qs_append_str_n(vf->cl, s, p - s) ;
continue ;
} ;
+ /* Have an eol. Step back across the '\n' and any trailing spaces
+ * we have in hand.
+ */
+ do --p ; while ((p > s) && (*(p-1) == ' ')) ;
+
/* If we have nothing so far, set alias to point at what we have in
* the fifo. Otherwise, append to what we have.
*
* End up with: s = start of entire line, so far.
* p = end of entire line so far.
*/
- len = q - s ; /* length to add */
+ len = p - s ; /* length to add */
if (qs_len_nn(vf->cl) == 0)
- {
- qs_set_alias_n(vf->cl, s, len) ;
- p = q ;
- }
+ qs_set_alias_n(vf->cl, s, len) ;
else
{
if (len != 0)
- qs_append_n(vf->cl, s, len) ;
+ qs_append_str_n(vf->cl, s, len) ;
s = qs_char_nn(vf->cl) ;
p = s + qs_len_nn(vf->cl) ;
@@ -265,10 +485,10 @@ uty_file_fetch_command_line(vio_vf vf, qstring* line)
* Note that this rule deals with the case of the continuation line
* being empty... e.g. ....\\\ n n -- where n is '\n'
*/
- q = p ;
- do --q ; while ((q > s) && (*(q-1) == '\\')) ;
+ e = p ;
+ do --p ; while ((p > s) && (*(p-1) == '\\')) ;
- if (((p - q) & 1) == 0)
+ if (((e - p) & 1) == 0)
break ; /* even => no continuation => success */
qs_set_len_nn(vf->cl, p - s - 1) ; /* strip odd '\' */
@@ -276,47 +496,1433 @@ uty_file_fetch_command_line(vio_vf vf, qstring* line)
continue ; /* loop back to fetch more */
} ;
- /* Success have a line in hand */
+ /* Success: have a line in hand */
vf->line_complete = true ;
- *line = vf->cl ;
+ action->to_do = cmd_do_command ;
+ action->line = vf->cl ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * File or pipe is ready to read -- this is the call-back planted in the
+ * vf->vfd.
+ *
+ * The command_queue processing can continue.
+ *
+ * Note that the read_ready state and any time out are automatically
+ * disabled when they go off.
+ */
+static void
+uty_file_read_ready(vio_vfd vfd, void* action_info)
+{
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->vfd == vfd) ;
+
+ uty_cmd_signal(vf->vio, CMD_SUCCESS) ;
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * File or pipe has timed out, waiting to read -- this is the call-back planted
+ * in the vf->vfd.
+ *
+ * ????
+ */
+static vty_timer_time
+uty_file_read_timeout(vio_timer timer, void* action_info)
+{
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->vfd->write_timer == timer) ;
+
+//cq_continue(vf->vio->vty) ; TODO
+
+ VTY_UNLOCK() ;
+
+ return 0 ; /* Do not restart timer */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Command output push to a file.
+ *
+ * Until the file becomes vf_closing, will not write the end_lump of the fifo,
+ * so all output is in units of the fifo size -- which should be "chunky".
+ *
+ * Although it is unlikely to happen, if blocking, will block if cannot
+ * completely write away what is required, or enable write ready.
+ *
+ * If final, will write as much as possible, but not block and will ignore
+ * any errors -- but the return code will be an error return code.
+ *
+ * Returns: CMD_SUCCESS -- written everything
+ * CMD_WAITING -- could not write everything (<=> non-blocking)
+ * CMD_IO_ERROR -- ran into an I/O error
+ * CMD_IO_TIMEOUT -- not going to wait any longer (<=> blocking)
+ *
+ * In "non-blocking" state, on CMD_WAITING the caller will have to take steps
+ * to come back later.
+ *
+ * In "blocking" state, will not return until have written everything there is,
+ * away, or cannot continue.
+ *
+ * This can be called in any thread.
+ */
+extern cmd_return_code_t
+uty_file_out_push(vio_vf vf, bool final)
+{
+ bool all ;
+
+ VTY_ASSERT_LOCKED() ; /* In any thread */
+ assert((vf->vout_state == vf_open) || (vf->vout_state == vf_closing)) ;
+ assert((vf->vout_type == VOUT_FILE) || (vf->vout_type == VOUT_CONFIG)) ;
+
+ all = (vf->vout_state == vf_closing) ;
+ /* empty buffers if closing */
+ while (1)
+ {
+ qps_mini_t qm ;
+ int n ;
+
+ n = vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), all || final) ;
+
+ if (n < 0)
+ return CMD_IO_ERROR ; /* TODO */
+
+ if ((n == 0) || final) /* all gone (or as much as can go) */
+ return CMD_SUCCESS ;
+
+ /* Cannot write everything away without waiting */
+ if (!vf->blocking)
+ {
+ uty_vf_set_write(vf, on) ;
+ return CMD_WAITING ;
+ } ;
+
+ /* Implement blocking I/O, with timeout */
+ qps_mini_set(qm, vio_vfd_fd(vf->vfd), qps_write_mnum, vf->write_timeout) ;
+ if (qps_mini_wait(qm, NULL, false) == 0)
+ return CMD_IO_TIMEOUT ;
+
+ /* Loop back to vio_fifo_write_nb() */
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * File or pipe is ready to write -- this is the call-back planted in the
+ * vf->vfd.
+ *
+ * Note that the write_ready state and any time out are automatically
+ * disabled when they go off.
+ *
+ * Since we have gone to all the trouble of going through pselect, might as
+ * well write everything away. This works while the file is being closed, too.
+ */
+static void
+uty_file_write_ready(vio_vfd vfd, void* action_info)
+{
+ cmd_return_code_t ret ;
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->vfd == vfd) ;
+
+ ret = uty_file_out_push(vf, false) ; /* re-enable write ready if required */
+
+ if (ret != CMD_WAITING)
+ uty_cmd_signal(vf->vio, ret) ;
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * File or pipe is ready to write -- this is the call-back planted in the
+ * vf->vfd.
+ *
+ * Note that the read_ready state and any time out are automatically
+ * disabled when they go off.
+ */
+static vty_timer_time
+uty_file_write_timeout(vio_timer timer, void* action_info)
+{
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->vfd->write_timer == timer) ;
+
+//uty_file_out_push(vf) ; // TODO ???????
+
+ VTY_UNLOCK() ;
+
+ return 0 ; /* Do not restart timer */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Tidy up after input file has been closed.
+ *
+ * Nothing further required -- input comes to a halt.
+ */
+extern cmd_return_code_t
+uty_file_read_close(vio_vf vf, bool final)
+{
return CMD_SUCCESS ;
} ;
/*------------------------------------------------------------------------------
- * Command output push to a file or pipe.
+ * Flush output buffer and close.
+ *
+ * See uty_file_out_push()
*
- * Returns: CMD_SUCCESS -- done
- * CMD_IO_ERROR -- ran into an I/O error
+ * Returns: CMD_SUCCESS -- buffers are empty, or final
+ * CMD_WAITING -- waiting for I/O to complete
+ * CMD_xxx TODO
*/
extern cmd_return_code_t
-uty_file_out_push(vio_vf vf)
+uty_file_write_close(vio_vf vf, bool final, bool base)
{
- assert(vf->vout_state == vf_open) ;
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+ assert(vf->vout_state == vf_closing) ;
+ assert((vf->vout_type == VOUT_FILE) || (vf->vout_type == VOUT_CONFIG)) ;
- if (vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), false) >= 0)
- return CMD_SUCCESS ;
+ return uty_file_out_push(vf, final) ;
+} ;
+
+/*==============================================================================
+ * Shell pipe stuff
+ *
+ */
+typedef int pipe_pair_t[2] ;
+typedef int* pipe_pair ;
+
+enum pipe_half
+{
+ in_fd = 0,
+ out_fd = 1,
+} ;
+typedef enum pipe_half pipe_half_t ;
+
+CONFIRM(STDIN_FILENO == 0) ;
+CONFIRM(STDOUT_FILENO == 1) ;
+CONFIRM(STDERR_FILENO == 2) ;
+
+enum
+{
+ stdin_fd = STDIN_FILENO,
+ stdout_fd = STDOUT_FILENO,
+ stderr_fd = STDERR_FILENO,
+
+ stds = 3
+} ;
+
+enum pipe_id
+{
+ in_pipe,
+ out_pipe,
+ ret_pipe,
+
+ pipe_count
+} ;
+typedef enum pipe_id pipe_id_t ;
+
+typedef pipe_pair_t pipe_set[pipe_count] ;
+
+static pid_t uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes,
+ pipe_id_t type) ;
+static bool uty_pipe_pair(vty_io vio, const char* cmd_str,
+ const char* what, pipe_set pipes,
+ pipe_id_t id,
+ pipe_half_t half) ;
+static bool uty_pipe_fork_fail(vty_io vio, const char* cmd_str,
+ const char* what, pipe_set pipes,
+ pipe_pair pair,
+ const char* action) ;
+static void uty_pipe_close_half_pipe(pipe_pair pair, pipe_half_t half) ;
+static void uty_pipe_open_complete(vio_vf vf, pid_t pid, int ret_fd,
+ vio_vf slave) ;
+static bool uty_pipe_exec_prepare(vty_io vio, pipe_set pipes) ;
+
+static cmd_return_code_t uty_pipe_return_close(vio_vf vf, bool final) ;
+static cmd_return_code_t uty_pipe_return_empty(vio_vf vf, bool final) ;
+static cmd_return_code_t uty_pipe_collect_child(vio_vf vf, bool final) ;
+static cmd_return_code_t uty_pipe_release_slave(vio_vf vf, bool final) ;
+
+static cmd_return_code_t uty_pipe_shovel(vio_vf vf, bool final, bool closing) ;
+
+static void uty_pipe_read_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time uty_pipe_read_timeout(vio_timer timer,
+ void* action_info) ;
+static void uty_pipe_return_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time uty_pipe_return_timeout(vio_timer timer,
+ void* action_info) ;
+static void uty_pipe_write_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time uty_pipe_write_timeout(vio_timer timer,
+ void* action_info) ;
+
+/*------------------------------------------------------------------------------
+ * Open pipe whose child's stdout is going to be read and executed as commands.
+ *
+ * The child's stderr is read, separately, and that is sent to the current
+ * vout.
+ *
+ * If could not open, issues message to the vio.
+ *
+ * The new vin has two vio_fd's, the standard one to read commands, and the
+ * other (the pr_vfd) to collect any stderr output (the "return") from the
+ * child, which is sent to the current vout.
+ *
+ * The current vout is made the "slave" of the new vin "master". For the
+ * return the pr_vd reads into the "slave" obuf.
+ *
+ * There are three tricky bits:
+ *
+ * - while reading from the main vfd, also reads from the return vfd.
+ *
+ * Up to eof on the main vfd, reads from the return vfd whenever tries
+ * to fetch a command. Once a command is fetched, will not shovel anything
+ * further into the slave obuf until the next command is fetched. This
+ * means that return stuff is output *between* commands.
+ *
+ * - in "blocking" state, will not block on the return unless blocks on the
+ * main vfd, or the main vfd is at eof.
+ *
+ * in "non-blocking" state, will set read ready on the main vfd, until that
+ * reaches eof, in which case sets read ready on the return vfd (if that
+ * has not yet reached eof).
+ *
+ * - the close operation will not succeed until the return vfd reaches EOF,
+ * or is a "final" close.
+ *
+ * If could not open, issues message to the vio.
+ *
+ * Returns: CMD_SUCCESS -- all set to go
+ * CMD_WARNING -- failed to open
+ */
+extern cmd_return_code_t
+uty_pipe_read_open(vty_io vio, qstring command, cmd_context context)
+{
+ pipe_set pipes ;
+ const char* cmd_str ;
+ vio_vf vf ;
+ pid_t child ;
+
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+
+ assert(vio->vin != NULL) ; /* Not expected to be vin_base */
+
+ cmd_str = qs_make_string(command) ;
+
+ /* Fork it */
+ child = uty_pipe_fork(vio, cmd_str, pipes, in_pipe) ;
+
+ if (child < 0)
+ return CMD_WARNING ;
+
+ /* We have a pipe, so now save context */
+ uty_vin_new_context(vio, context, NULL) ;
+
+ /* OK -- now push the new input onto the vin_stack. */
+ vf = uty_vf_new(vio, cmd_str, pipes[in_pipe][in_fd], vfd_pipe, vfd_io_read) ;
+ uty_vin_push(vio, vf, VIN_PIPE, uty_pipe_read_ready,
+ uty_pipe_read_timeout, 4 * 1024) ;
+
+ /* And the err_pair is set as the return from the child */
+ uty_pipe_open_complete(vf, child, pipes[ret_pipe][in_fd], vio->vout) ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Open pipe which is going to be written to (or not, if shell_only), and
+ * what the pipe returns will be output to the current vout.
+ *
+ * The new vout becomes the "master", and has two vio_vfd's, one for output to
+ * the pipe (this is NULL if shell_only) and the other for the pipe return.
+ * The pipe return reads directly into the "slave" obuf.
+ *
+ * There are three tricky bits:
+ *
+ * - while writing to the output vfd, also reads from the return vfd.
+ *
+ * This is to avoid any possibility that the child process blocks because
+ * it cannot write to the return pipe.
+ *
+ * - when closing the master pipe, it must remain open up to the time it
+ * gets eof on the return pipe.
+ *
+ * This means that the all return output is collected before any further
+ * output to the slave can be done.
+ *
+ * - in "blocking" state, reads from the return only when writes to the
+ * pipe, and when closing the pipe.
+ *
+ * in "non-blocking" state, reads from the return and pushes to the slave
+ * under pselect... so sucks and blows at its own rate.
+ *
+ * Returns: CMD_SUCCESS -- all set to go
+ * CMD_WARNING -- failed to open -- message sent to vio.
+ */
+extern cmd_return_code_t
+uty_pipe_write_open(vty_io vio, qstring command, bool shell_only)
+{
+ pipe_set pipes ;
+ const char* cmd_str ;
+ pid_t child ;
+ vio_vf vf ;
+
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+
+ cmd_str = qs_make_string(command) ;
+
+ /* Do the basic file open. */
+ child = uty_pipe_fork(vio, cmd_str, pipes, shell_only ? ret_pipe : out_pipe) ;
+ if (child < 0)
+ return CMD_WARNING ;
+
+ /* OK -- now push the new output onto the vout_stack. */
+
+ if (shell_only)
+ {
+ vf = uty_vf_new(vio, cmd_str, -1, vfd_none, vfd_io_none) ;
+ uty_vout_push(vio, vf, VOUT_SHELL_ONLY, NULL, NULL, 0) ;
+ }
else
- return CMD_IO_ERROR ;
+ {
+ vf = uty_vf_new(vio, cmd_str, pipes[out_pipe][out_fd],
+ vfd_pipe, vfd_io_write) ;
+ uty_vout_push(vio, vf, VOUT_PIPE, uty_pipe_write_ready,
+ uty_pipe_write_timeout, 4 * 1024) ;
+ } ;
+
+ /* Record the child pid and set up the pr_vf and enslave vout_next */
+
+ uty_pipe_open_complete(vf, child, pipes[ret_pipe][in_fd], vf->vout_next) ;
+
+ vf->pr_only = shell_only ;
+
+ /* If not blocking start up the return pipe reader. */
+
+ if (!vf->blocking)
+ vio_vfd_set_read(vf->pr_vfd, on, 0) ; /* TODO timeout */
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Complete the opening of a pipe.
+ *
+ * All pipes have a return. For in_pipes this is the child's stderr, for
+ * out_pipes this is the child's combined stdout/stderr.
+ *
+ * VIN_PIPE sucks the return in between reading command lines.
+ *
+ * VOUT_PIPE/VOUT_SHELL_ONLY suck the return at the same time as writing to
+ * the child.
+ *
+ * Sets pr_only false.
+ */
+static void
+uty_pipe_open_complete(vio_vf vf, pid_t pid, int ret_fd, vio_vf slave)
+{
+ vfd_io_type_t iot ;
+
+ vf->child = uty_child_register(pid, vf) ;
+
+ /* And configure the pipe return vfd. */
+ iot = vfd_io_read | (vf->blocking ? vfd_io_blocking : 0) ;
+
+ vf->pr_vfd = vio_vfd_new(ret_fd, vfd_pipe, iot, vf) ;
+ vf->pr_state = vf_open ;
+
+ vf->pr_only = false ;
+
+ if (!vf->blocking)
+ vio_vfd_set_read_action(vf->pr_vfd, uty_pipe_return_ready) ;
+
+ /* Configure master/slave relationship. */
+ slave->pr_master = vf ;
+ vf->pr_slave = slave ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Fork fork fork
+ *
+ * Set up pipes according to type of fork:
+ *
+ * in_pipe -- input from child's stdout as main fd
+ * input from child's stderr as return fd
+ *
+ * out_pipe -- output to child's stdin as main fd
+ * input from child's stderr & stdout as return fd
+ *
+ * ret_pipe -- nothing for main fd
+ * input from child's stderr & stdout as return fd
+ *
+ * vfork to create child process and then:
+ *
+ * -- in parent close the unused part(s) of the pair(s).
+ *
+ * -- in child, close all unused fds, and move relevant part(s) of pair(s)
+ * to stdin, stdout and stderr, sort out signal, set pwd then exec sh -c.
+ */
+static pid_t
+uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes, pipe_id_t type)
+{
+ pid_t child ;
+ int id ;
+
+ /* Set pipes empty */
+ for (id = 0 ; id < pipe_count ; ++id)
+ {
+ pipes[id][in_fd] = -1 ;
+ pipes[id][out_fd] = -1 ;
+ } ;
+
+ /* Open as many pipes as are required. */
+ if (type == in_pipe)
+ if (!uty_pipe_pair(vio, cmd_str, "input pipe", pipes, in_pipe, in_fd))
+ return -1 ;
+
+ if (type == out_pipe)
+ if (!uty_pipe_pair(vio, cmd_str, "output pipe", pipes, out_pipe, out_fd))
+ return -1 ;
+
+ if (!uty_pipe_pair(vio, cmd_str, "return pipe", pipes, ret_pipe, in_fd))
+ return -1 ;
+
+ /* Off to the races */
+
+ child = vfork() ;
+
+ if (child == 0) /* In child */
+ {
+
+ /* Prepare all file descriptors and then execute */
+ if (uty_pipe_exec_prepare(vio, pipes))
+ execl("/bin/bash", "bash", "-c", cmd_str, NULL) ; /* does not return */
+ else
+ exit(0x80 | errno) ;
+ }
+ else if (child > 0) /* In parent -- success */
+ {
+ /* close the pipe fds we do not need */
+ uty_pipe_close_half_pipe(pipes[in_pipe], out_fd) ;
+ uty_pipe_close_half_pipe(pipes[out_pipe], in_fd) ;
+ uty_pipe_close_half_pipe(pipes[ret_pipe], out_fd) ;
+ }
+ else if (child < 0) /* In parent -- failed */
+ uty_pipe_fork_fail(vio, cmd_str, "vfork", pipes, NULL, "child") ;
+
+ return child ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Open a pipe pair -- generate suitable error message if failed and close
+ * any early pipes that have been opened.
+ */
+static bool
+uty_pipe_pair(vty_io vio, const char* cmd_str,
+ const char* what, pipe_set pipes,
+ pipe_id_t id,
+ pipe_half_t half)
+{
+ pipe_pair pair ;
+
+ pair = pipes[id] ;
+
+ if (pipe(pair) < 0)
+ return uty_pipe_fork_fail(vio, cmd_str, what, pipes, pair, "open") ;
+
+ if (set_nonblocking(pair[half]) < 0)
+ return uty_pipe_fork_fail(vio, cmd_str, what, pipes, pair,
+ "set non-blocking") ;
+
+ return true ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Open a pipe pair -- generate suitable error message if failed and close
+ * any early pipes that have been opened.
+ */
+static bool
+uty_pipe_fork_fail(vty_io vio, const char* cmd_str,
+ const char* what, pipe_set pipes,
+ pipe_pair pair,
+ const char* action)
+{
+ int err = errno ;
+ int id ;
+
+ /* Close anything that has been opened */
+ for (id = 0 ; id < pipe_count ; ++id)
+ {
+ if (pipes[id] == pair)
+ continue ; /* ignore if just failed to open */
+
+ uty_pipe_close_half_pipe(pipes[id], in_fd) ;
+ uty_pipe_close_half_pipe(pipes[id], out_fd) ;
+ } ;
+
+ uty_out(vio, "%% Failed to %s %s for %s\n", action, what, cmd_str) ;
+
+ errno = err ;
+
+ return false ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Close half of a pipe pair, if it is open.
+ */
+static void
+uty_pipe_close_half_pipe(pipe_pair pair, pipe_half_t half)
+{
+ if (pair[half] >= 0)
+ {
+ close(pair[half]) ;
+ pair[half] = -1 ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * In the child process... prepare to exec the shell.
+ *
+ * Discard all fd's except for the those required for the shell command.
+ *
+ * Arrange for the pipe fd's to be stdin/stdout/stderr as required. The pairs
+ * are named wrt to the parent process:
+ *
+ * in_pipe -- if present, is: stdout for the child
+ * out_pipe -- if present, is: stdin for the child
+ * ret_pipe -- if present, is: stderr for the child
+ * and: stdout for the child, if no in_pipe
+ *
+ * Set current directory.
+ *
+ * Reset all signals to default state and unmask everything.
+ */
+static bool
+uty_pipe_exec_prepare(vty_io vio, pipe_set pipes)
+{
+ int std[stds] ;
+ int fd ;
+
+ /* Assign fds to child's std[] */
+
+ std[stdin_fd] = pipes[out_pipe][in_fd] ; /* stdin for child */
+ std[stdout_fd] = pipes[in_pipe][out_fd] ; /* stdout for child */
+ std[stderr_fd] = pipes[ret_pipe][out_fd] ; /* stderr for child */
+
+ /* Mark everything to be closed on exec */
+ for (fd = 0 ; fd <= 1024 ; ++fd) /* TODO -- max_fd */
+ {
+ int fd_flags ;
+ fd_flags = fcntl(fd, F_GETFD, 0) ;
+ if (fd_flags >= 0)
+ fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC) ;
+ } ;
+
+ /* Now dup anything of what we keep to ensure is above 2, making sure that
+ * the dup remains FD_CLOEXEC.
+ *
+ * This is highly unlikely, so no real extra work. It simplifies the next
+ * step which moves fds to the required position, and clears the FD_CLOEXEC
+ * flag on the duplicate.
+ */
+ for (fd = 0 ; fd < stds ; ++fd)
+ {
+ if ((std[fd] >= 0) && (std[fd] < stds))
+ if ((std[fd] = fcntl(std[fd], F_DUPFD, stds)) < 0)
+ return false ;
+ } ;
+
+ /* Now dup2 to the required location -- destination is NOT FD_CLOEXEC.
+ */
+ for (fd = 0 ; fd < stds ; ++fd)
+ {
+ if (std[fd] >= 0)
+ if (dup2(std[fd], fd) != fd)
+ return false ;
+ } ;
+
+ /* Finally (for fds) if we don't have a stdout for the child,
+ * dup stderr to stdout.
+ */
+ if ((std[stdout_fd] < 0) && (std[stderr_fd] >= 0))
+ if (dup2(stderr_fd, stdout_fd) != stdout_fd)
+ return false ;
+
+ /* Clear down all the signals magic. */
+ quagga_signal_reset() ;
+
+ /* Set the working directory */
+
+ if (qpath_setcwd(vio->vty->exec->context->dir_cd) != 0)
+ return false ;
+
+ return true ; /* coming, ready or not */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Command line fetch from a pipe and (for blocking) shovel return into
+ * slave and push.
+ *
+ * In "non-blocking" state, on CMD_WAITING may be waiting for read ready on
+ * this vf, or (indirectly) write ready on the slave.
+ *
+ * In "blocking" state, will not return until have something, or there is
+ * nothing to be had.
+ *
+ * Returns: CMD_SUCCESS -- have another command line ready to go
+ * CMD_WAITING -- would not wait for input <=> non-blocking
+ * CMD_EOF -- ran into EOF -- on input AND return
+ * CMD_IO_ERROR -- ran into an I/O error
+ * CMD_IO_TIMEOUT -- could wait no longer <=> blocking
+ *
+ * If returns CMD_EOF will have vf->vin_state == vf_eof. Further, will not
+ * have vf_eof until returns CMD_EOF (the first time).
+ *
+ * This can be called in any thread.
+ *
+ * NB: once this has signalled CMD_EOF (on the main input) the close must deal
+ * with sucking up any remaining return.
+ */
+extern cmd_return_code_t
+uty_pipe_fetch_command_line(vio_vf vf, cmd_action action)
+{
+ VTY_ASSERT_LOCKED() ; /* In any thread */
+
+ assert(vf->vin_type == VIN_PIPE) ;
+ assert((vf->vin_state == vf_open) || (vf->vin_state == vf_eof)) ;
+
+ /* TODO police the state of the two vfs ? */
+
+ while (1) /* so blocking stuff can loop round */
+ {
+ cmd_return_code_t ret ;
+ int get ;
+ qps_mini_t qm ;
+
+ /* Try to complete line straight from the buffer.
+ *
+ * If buffer is empty and have seen eof on the input, signal CMD_EOF.
+ */
+ ret = uty_fifo_command_line(vf, action) ;
+
+ if ((ret == CMD_SUCCESS) || (ret == CMD_EOF))
+ return ret ;
+
+ /* Worry about the return.
+ *
+ * If the child is outputting to both stderr and stdout, we have no
+ * way of telling the order the data was sent in -- but we treat
+ * stderr as a higher priority !
+ *
+ * Expect only CMD_SUCCESS or CMD_WAITING (non-blocking), CMD_IO_ERROR
+ * or CMD_IO_TIMEOUT (blocking).
+ */
+ if (vf->blocking && (vf->pr_state == vf_open))
+ {
+ ret = uty_pipe_shovel(vf, false, false) ; /* not final or closing */
+
+ if ((ret != CMD_SUCCESS) && (ret != CMD_EOF))
+ return ret ; /* cannot continue */
+ } ;
+
+ /* Need more from the main input. */
+
+ get = vio_fifo_read_nb(vf->ibuf, vio_vfd_fd(vf->vfd), 100) ;
+
+ if (get > 0)
+ continue ; /* loop back */
+
+ if (get == -1)
+ return CMD_IO_ERROR ; /* register error */
+
+ if (get == -2) /* EOF met immediately */
+ {
+ vf->vin_state = vf_eof ;
+ continue ; /* loop back -- deals with possible
+ final line and the return. */
+ } ;
+
+ assert (get == 0) ;
+
+ /* We get here if main input is not yet at eof. */
+ assert ((vf->vin_state == vf_open) || (vf->pr_state == vf_open)) ;
+
+ if (!vf->blocking)
+ {
+ uty_vf_set_read(vf, on) ;
+ return CMD_WAITING ;
+ } ;
+
+ /* Implement blocking I/O, with timeout */
+ qps_mini_set(qm, (vf->vin_state == vf_open) ? vio_vfd_fd(vf->vfd)
+ : -1,
+ qps_read_mnum, vf->read_timeout) ;
+ if (vf->pr_state == vf_open)
+ qps_mini_add(qm, vio_vfd_fd(vf->pr_vfd), qps_read_mnum) ;
+
+ if (qps_mini_wait(qm, NULL, false) == 0)
+ return CMD_IO_TIMEOUT ;
+
+ continue ; /* loop back */
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Command output push to a pipe and (for blocking) shovel return into
+ * slave and push.
+ *
+ * Does not push to the pipe while there is return input to be read. For
+ * blocking I/O may block in the slave output. For non-blocking I/O, will
+ * return if would block in the slave output.
+ *
+ * Until the file becomes vf_closing, will not write the end_lump of the fifo,
+ * so all output is in units of the fifo size -- which should be "chunky".
+ *
+ * Although it is unlikely to happen, if blocking, will block if cannot
+ * completely write away what is required, or enable write ready.
+ *
+ * If final, will do final shovel from return to slave and also attempt to
+ * empty any output buffer -- will not wait or block, and ignores errors.
+ *
+ * Returns: CMD_SUCCESS -- written everything
+ * CMD_WAITING -- could not write everything (<=> non-blocking)
+ * CMD_IO_ERROR -- ran into an I/O error
+ * CMD_IO_TIMEOUT -- not going to wait any longer (<=> blocking)
+ *
+ * In "non-blocking" state, on CMD_WAITING the slave output will put the
+ * master return pr_vfd into read ready state when it sees write ready, or will
+ * here set the pr_vfd into read ready state.
+ *
+ * In "blocking" state, will not return until have written everything there is,
+ * away, or cannot continue.
+ *
+ * This can be called in any thread.
+ */
+extern cmd_return_code_t
+uty_pipe_out_push(vio_vf vf, bool final)
+{
+ VTY_ASSERT_LOCKED() ; /* In any thread */
+ assert((vf->vout_state == vf_open) || (vf->vout_state == vf_closing)) ;
+ assert((vf->vout_type == VOUT_PIPE) || (vf->vout_type == VOUT_SHELL_ONLY)) ;
+
+ /* If blocking, see if there is anything in the return, and shovel.
+ *
+ * For non-blocking, the pselect process looks after this.
+ */
+ if (vf->blocking && (vf->pr_state == vf_open))
+ {
+ cmd_return_code_t ret ;
+
+ ret = uty_pipe_shovel(vf, final, false) ; /* not closing */
+
+ if ((ret != CMD_SUCCESS) && (ret != CMD_EOF) && !final)
+ return ret ;
+ } ;
+
+ /* Now write away everything we can -- nothing if pr_only.
+ */
+ if (!vf->pr_only)
+ {
+ bool all ;
+
+ all = (vf->vout_state == vf_closing) ;
+
+ while (1)
+ {
+ qps_mini_t qm ;
+ int put ;
+
+ put = vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), all) ;
+
+ if (put < 0)
+ return CMD_IO_ERROR ; /* TODO */
+
+ if ((put == 0) || final) /* all gone or final */
+ break ;
+
+ /* Cannot write everything away without waiting */
+ if (!vf->blocking)
+ {
+ uty_vf_set_write(vf, on) ;
+ return CMD_WAITING ;
+ } ;
+
+ /* Implement blocking I/O, with timeout */
+ qps_mini_set(qm, vio_vfd_fd(vf->vfd), qps_write_mnum,
+ vf->write_timeout) ;
+ if (qps_mini_wait(qm, NULL, false) == 0)
+ return CMD_IO_TIMEOUT ;
+
+ /* Loop back to vio_fifo_write_nb() */
+ } ;
+ } ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Shovel from return to slave, pushing the slave as we go.
+ *
+ * For VIN_PIPE: this is called between command line reads, when a command
+ * line read needs more input. Wants to suck the return dry before proceeding
+ * to read any more command lines. Is also called when the vin is closed.
+ *
+ * For VOUT_PIPE: for blocking vfs this is called each time the output is
+ * pushed -- to keep the process moving. And when the vout is closed, to
+ * complete the process. For non-blocking vfs, TODO
+ *
+ * For VOUT_SHELL_ONLY: same as VOUT_PIPE.
+ *
+ * For blocking, keeps going until can read no more from the return. The slave
+ * output may block, and could time out.
+ *
+ * With non-blocking, reads from the return and pushes at the slave. If the
+ * slave obuf is not emptied by the push, the slave will set the master
+ * read ready, when the slave goes write-ready.
+ *
+ * For "final" does not attempt to read anything from the return, but sets it
+ * vf_eof.
+ *
+ * For "closing" (if not "final"), if blocking will block on the return, until
+ * get eof (or time out).
+ *
+ * Returns: CMD_SUCCESS -- shovelled everything, but not hit EOF
+ * if "closing" <=> non-blocking
+ * CMD_EOF -- shovelled everything and is now at EOF
+ * CMD_WAITING -- waiting for slave write-ready <=> non-blocking
+ * CMD_IO_ERROR -- ran into an I/O error
+ * CMD_IO_TIMEOUT -- could wait no longer <=> blocking
+ *
+ * This can be called in any thread.
+ */
+static cmd_return_code_t
+uty_pipe_shovel(vio_vf vf, bool final, bool closing)
+{
+ vio_vf slave ;
+
+ VTY_ASSERT_LOCKED() ; /* In any thread */
+
+ // TODO other pr_state ??? vf_closed/vf_closing/vf_error ??
+
+ /* Suck and blow -- may block in uty_cmd_out_push()
+ *
+ * Note that we do not push the slave if there is nothing new from the
+ * return -- there shouldn't be anything pending in the slave obuf.
+ */
+ slave = vf->pr_slave ;
+ assert(vf == slave->pr_master) ;
+
+ while (!final && (vf->pr_state == vf_open))
+ {
+ cmd_return_code_t ret ;
+ int get ;
+
+ get = vio_fifo_read_nb(slave->obuf, vio_vfd_fd(vf->pr_vfd), 10) ;
+
+ if (get == 0)
+ {
+ qps_mini_t qm ;
+
+ if (!closing || !vf->blocking)
+ return CMD_SUCCESS ; /* quit if now dry */
+
+ /* Implement blocking I/O, with timeout */
+ qps_mini_set(qm, vio_vfd_fd(vf->pr_vfd), qps_read_mnum,
+ vf->read_timeout) ;
+ if (qps_mini_wait(qm, NULL, false) == 0)
+ return CMD_IO_TIMEOUT ;
+ }
+
+ else if (get >= 0)
+ {
+ ret = uty_cmd_out_push(slave, final) ; /* may block etc. */
+
+ if (ret != CMD_SUCCESS)
+ return ret ;
+ }
+
+ else if (get == -1)
+ return CMD_IO_ERROR ; /* register error TODO */
+
+ else
+ {
+ assert (get == -2) ; /* eof on the return */
+ break ;
+ } ;
+ } ;
+
+ vf->pr_state = vf_eof ; /* Set EOF TODO: willy nilly ? */
+
+ return CMD_EOF ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Pipe is ready to read -- this is the call-back planted in the vf->vfd, of
+ * type VIN_PIPE.
+ *
+ * Can restart the command_queue.
+ *
+ * Note that the read_ready state and any time out are automatically
+ * disabled when they go off.
+ */
+static void
+uty_pipe_read_ready(vio_vfd vfd, void* action_info)
+{
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->vfd == vfd) ;
+
+ uty_cmd_signal(vf->vio, CMD_SUCCESS) ;
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Pipe has timed out, waiting to read -- this is the call-back planted in the
+ * vf->vfd, of type VIN_PIPE.
+ *
+ * ????
+ */
+static vty_timer_time
+uty_pipe_read_timeout(vio_timer timer, void* action_info)
+{
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->vfd->read_timer == timer) ;
+
+// TODO .... signal time out
+
+ VTY_UNLOCK() ;
+
+ return 0 ; /* Do not restart timer */
} ;
/*------------------------------------------------------------------------------
- * Tidy up after input file has been closed
+ * Pipe return is ready to read -- this is the call-back planted in the
+ * vf->pr_vfd:
+ *
+ * VIN_PIPE: used only when closing the vin, and are waiting for
+ * the return to be drained.
+ *
+ * VOUT_PIPE: used to continually shovel from the return to the
+ * slave -- including while closing.
+ *
+ * VOUT_SHELL_ONLY: used to continually shovel from the return to the
+ * slave -- which happens while closing.
+ *
+ * If all is well, and more return input is expected, re-enables read ready.
+ *
+ * If uty_pipe_shovel() returns CMD_WAITING, then is waiting for the slave
+ * output buffer to clear. The slave will kick uty_pipe_return_slave_ready().
+ *
+ * For all other returns, kick the command loop, which if it is waiting is TODO
+ */
+static void
+uty_pipe_return_ready(vio_vfd vfd, void* action_info)
+{
+ cmd_return_code_t ret ;
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->pr_vfd == vfd) ;
+
+ ret = uty_pipe_shovel(vf, false, false) ; /* not final or closing */
+ /* TODO -- errors !! */
+ if (ret == CMD_SUCCESS)
+ vio_vfd_set_read(vf->pr_vfd, on, 0) ; /* expect more */
+ else if (ret != CMD_WAITING)
+ uty_cmd_signal(vf->vio, (ret == CMD_EOF) ? CMD_SUCCESS
+ : ret) ; /* in case TODO */
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Pipe return slave is ready to write.
+ *
+ * This is called by the pipe return slave, to prompt the master when the slave
+ * has been waiting to be able to write away its output buffer.
+ *
+ * This will set read ready on the return, to keep the process of shovelling
+ * from return to slave going.
*/
extern void
-uty_file_read_close(vio_vf vf)
+uty_pipe_return_slave_ready(vio_vf slave)
{
- return ;
+ vio_vf vf ;
+
+ vf = slave->pr_master ;
+ assert(vf->pr_slave == slave) ;
+
+ vio_vfd_set_read(vf->pr_vfd, on, 0) ;
} ;
/*------------------------------------------------------------------------------
- * Flush output buffer and close.
+ * Pipe return has timed out -- this is the call-back planted in the
+ * vf->pr_vfd of either a VIN_PIPE or a VOUT_PIPE ????
*
- * Returns: true <=> buffer (now) empty
+ * ????
*/
-extern bool
-uty_file_write_close(vio_vf vf, bool final)
+static vty_timer_time
+uty_pipe_return_timeout(vio_timer timer, void* action_info)
{
- return vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), true) == 0 ;
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->pr_vfd->read_timer == timer) ;
+
+//cq_continue(vf->vio->vty) ; TODO
+
+ VTY_UNLOCK() ;
+
+ return 0 ; /* Do not restart timer */
} ;
+
+/*------------------------------------------------------------------------------
+ * Pipe output is ready to write -- this is the call-back planted in the
+ * vf->vfd of a VOUT_PIPE
+ *
+ * Note that the write_ready state and any time out are automatically
+ * disabled when they go off.
+ */
+static void
+uty_pipe_write_ready(vio_vfd vfd, void* action_info)
+{
+ cmd_return_code_t ret ;
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->vfd == vfd) ;
+
+ ret = uty_pipe_out_push(vf, false) ; /* not final */
+
+ if (ret != CMD_WAITING)
+ uty_cmd_signal(vf->vio, ret) ;
+
+ if ((ret == CMD_SUCCESS) && (vf->pr_master != NULL))
+ uty_pipe_return_slave_ready(vf) ;
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Pipe output has timed out -- this is the call-back planted in the
+ * vf->vfd of a VOUT_PIPE
+ *
+ * ????
+ */
+static vty_timer_time
+uty_pipe_write_timeout(vio_timer timer, void* action_info)
+{
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->vfd->write_timer == timer) ;
+
+// TODO
+
+ VTY_UNLOCK() ;
+
+ return 0 ; /* Do not restart timer */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Tidy up after input pipe has been read closed.
+ *
+ * Nothing needs to be done with the main input, but do need to close the
+ * return, collect the child and release the slave.
+ */
+extern cmd_return_code_t
+uty_pipe_read_close(vio_vf vf, bool final)
+{
+ return uty_pipe_return_close(vf, final) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Close VOUT_PIPE or VOUT_SHELL_ONLY.
+ *
+ * For not-final, wish to flush output buffer. If final will attempt to empty
+ * the output buffer -- but will not wait or block, and ignores errors.
+ *
+ * Must then close the return, collect the child and release the slave.
+ *
+ * Returns: CMD_SUCCESS -- pushed what there was, or final
+ * CMD_WAITING -- would have blocked <=> non-blocking
+ * CMD_IO_ERROR -- ran into an I/O error
+ * CMD_IO_TIMEOUT -- could wait no longer <=> blocking
+ */
+extern cmd_return_code_t
+uty_pipe_write_close(vio_vf vf, bool final, bool base, bool shell_only)
+{
+ cmd_return_code_t ret ;
+
+ VTY_ASSERT_CAN_CLOSE_VF(vf) ;
+
+ /* If the main vfd is still there, keep pushing.
+ *
+ * Close it if manage to empty out buffers (or final) -- signals end to the
+ * child process.
+ */
+ if (!vf->pr_only)
+ {
+ ret = uty_pipe_out_push(vf, final) ;
+
+ if ((ret != CMD_SUCCESS) && !final)
+ return ret ;
+
+ /* Now need to close the output vfd to signal to the child that we
+ * are done.
+ *
+ * Sets pr_only so that if does go CMD_WAITING, then future call of
+ * uty_pipe_out_push() will not attempt any I/O ! vf->vfd is set NULL,
+ * which will be ignored by any future vio_vfd_close().
+ */
+ vf->vfd = vio_vfd_close(vf->vfd) ;
+ vf->pr_only = true ; /* only the return is left */
+ } ;
+
+ /* If the return is still open, try to empty and close that. */
+ return uty_pipe_return_close(vf, final) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Close the return on a pipe.
+ *
+ * If not final, may need to drain the return.
+ *
+ * Need to collect the child to complete the process. Once the child is
+ * collected (or have given up waiting, will send a message to the slave if
+ * not completed with return code == 0.
+ *
+ * Once any message has been pushed to the slave, can release the slave
+ * and the close process will be complete.
+ *
+ * If final, then will not block and will ignore errors. Return code reflects
+ * the last operation.
+ *
+ * Returns: CMD_SUCCESS -- closed and child collected
+ * CMD_WAITING
+ * CMD_IO_ERROR
+ * CMD_IO_TIMEOUT
+ * CMD_xxxx -- child error(s)
+ */
+static cmd_return_code_t
+uty_pipe_return_close(vio_vf vf, bool final)
+{
+ cmd_return_code_t ret ;
+
+ VTY_ASSERT_CAN_CLOSE_VF(vf) ;
+
+ /* If the return is still open, try to empty and close that. */
+ if (vf->pr_state != vf_closed)
+ {
+ ret = uty_pipe_return_empty(vf, final) ;
+
+ if (!final)
+ {
+ if (ret == CMD_SUCCESS)
+ {
+ vio_vfd_set_read(vf->pr_vfd, on, 0) ; /* TODO timeout */
+ return CMD_WAITING ;
+ } ;
+
+ if (ret != CMD_EOF)
+ return ret ;
+ } ;
+ } ;
+
+ /* If not already collected, collect the child. */
+ if (vf->child != NULL)
+ {
+ ret = uty_pipe_collect_child(vf, final) ;
+
+ if ((ret != CMD_SUCCESS) && !final)
+ return ret ;
+ } ;
+
+ /* Finally, release the slave */
+ return uty_pipe_release_slave(vf, final) ;
+}
+
+/*------------------------------------------------------------------------------
+ * If the return is still open, shovel from return to slave.
+ *
+ * If final will not block or wait, and ignores errors -- return code reflects
+ * the last operation.
+ *
+ * If blocking, will block until return hits EOF or timeout.
+ *
+ * Returns: CMD_SUCCESS -- shovelled what there was, may be more to come
+ * <=> non-blocking
+ * CMD_EOF -- shovelled what there was, is now *closed*
+ * CMD_WAITING -- waiting for slave write-ready <=> non-blocking
+ * CMD_IO_ERROR -- ran into an I/O error
+ * CMD_IO_TIMEOUT -- could wait no longer <=> blocking
+ */
+static cmd_return_code_t
+uty_pipe_return_empty(vio_vf vf, bool final)
+{
+ cmd_return_code_t ret ;
+
+ // worry about pr_state ??
+
+ ret = uty_pipe_shovel(vf, final, true) ; /* closing ! */
+
+ if ((ret == CMD_EOF) || final)
+ {
+ vf->pr_vfd = vio_vfd_close(vf->pr_vfd) ;
+ vf->pr_state = vf_closed ;
+ } ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Collect child.
+ *
+ * When does collect, or times out, will dismiss the child and set vf->child
+ * to NULL -- which indicates that the child is done with.
+ *
+ * Returns: CMD_SUCCESS -- child dismissed, vf->child == NULL
+ * CMD_WAITING -- waiting to collect child <=> non-blocking
+ *
+ */
+static cmd_return_code_t
+uty_pipe_collect_child(vio_vf vf, bool final)
+{
+ vio_vf slave ;
+
+ assert(vf->child != NULL) ;
+
+ /* Collect -- blocking or not blocking */
+ if (!vf->child->collected && !vf->child->overdue)
+ {
+ /* If we are !blocking and !final, leave the child collection up to
+ * the SIGCHLD system.
+ */
+ if (!vf->blocking && !final)
+ {
+ uty_child_awaited(vf->child, 6) ;
+ return CMD_WAITING ;
+ } ;
+
+ /* If we are blocking or final, try to collect the child here and
+ * now -- if not final may block here.
+ */
+ uty_child_collect(vf->child, 6, final) ;
+ } ;
+
+ /* If child is overdue, or did not terminate cleanly, write message to
+ * slave... which we then have to push...
+ */
+ slave = vf->pr_slave ;
+ assert((slave != NULL) && (vf == slave->pr_master)) ;
+
+ if (!vf->child->collected)
+ {
+ vio_fifo_printf(slave->obuf, "%% child process still running\n") ;
+ }
+ else if (WIFEXITED(vf->child->report))
+ {
+ int status = WEXITSTATUS(vf->child->report) ;
+ if (status != 0)
+ vio_fifo_printf(slave->obuf,
+ "%% child process ended normally, but with status = %d\n",
+ status) ;
+ }
+ else if (WIFSIGNALED(vf->child->report))
+ {
+ int signal = WTERMSIG(vf->child->report) ;
+ vio_fifo_printf(slave->obuf,
+ "%% child process terminated by signal = %d\n", signal) ;
+ }
+ else
+ {
+ vio_fifo_printf(slave->obuf,
+ "%% child process ended in unknown state = %d\n",
+ vf->child->report) ;
+ } ;
+
+ /* Can now dismiss the child. */
+ vf->child = uty_child_dismiss(vf->child, false) ; /* not final */
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Push output to slave a final time -- to shift any final message about state
+ * of child on closing.
+ *
+ * Then release slave.
+ */
+static cmd_return_code_t
+uty_pipe_release_slave(vio_vf vf, bool final)
+{
+ cmd_return_code_t ret ;
+ vio_vf slave ;
+
+ ret = CMD_SUCCESS ;
+
+ slave = vf->pr_slave ;
+ if (slave != NULL)
+ {
+ assert(vf == slave->pr_master) ;
+
+ ret = uty_cmd_out_push(slave, final) ; /* may block etc. */
+
+ if ((ret != CMD_SUCCESS) && !final)
+ return ret ;
+
+ slave->pr_master = NULL ; /* release slave */
+ vf->pr_slave = NULL ;
+ } ;
+
+ return ret ;
+} ;
+
+/*==============================================================================
+ * stdout and stderr
+ *
+ *
+ */
+
diff --git a/lib/vty_io_file.h b/lib/vty_io_file.h
index ab852e08..5bb5a607 100644
--- a/lib/vty_io_file.h
+++ b/lib/vty_io_file.h
@@ -26,6 +26,7 @@
#include "misc.h"
#include "vty_io.h"
+#include "command_parse.h"
/*==============================================================================
* Here are structures and other definitions which are shared by:
@@ -38,16 +39,32 @@
/*==============================================================================
* Functions
*/
+extern vty vty_config_read_open(int fd, const char* name, bool full_lex) ;
+extern cmd_return_code_t uty_config_read_close(vio_vf vf, bool final) ;
extern cmd_return_code_t uty_file_read_open(vty_io vio, qstring name,
- bool reflect) ;
+ cmd_context context) ;
extern cmd_return_code_t uty_file_write_open(vty_io vio, qstring name,
- bool append) ;
+ bool append, cmd_context context) ;
-extern cmd_return_code_t uty_file_fetch_command_line(vio_vf vf, qstring* line) ;
-extern cmd_return_code_t uty_file_out_push(vio_vf vf) ;
+extern cmd_return_code_t uty_file_fetch_command_line(vio_vf vf,
+ cmd_action action) ;
+extern cmd_return_code_t uty_file_out_push(vio_vf vf, bool final) ;
-extern void uty_file_read_close(vio_vf vf) ;
-extern bool uty_file_write_close(vio_vf vf, bool final) ;
+extern cmd_return_code_t uty_file_read_close(vio_vf vf, bool final) ;
+extern cmd_return_code_t uty_file_write_close(vio_vf vf, bool final, bool base) ;
-#endif /* _ZEBRA_VTY_IO_FILE_H */
+
+extern cmd_return_code_t uty_pipe_read_open(vty_io vio, qstring command,
+ cmd_context context) ;
+extern cmd_return_code_t uty_pipe_write_open(vty_io vio, qstring command,
+ bool shell_only) ;
+extern cmd_return_code_t uty_pipe_fetch_command_line(vio_vf vf,
+ cmd_action action) ;
+extern cmd_return_code_t uty_pipe_out_push(vio_vf vf, bool final) ;
+extern void uty_pipe_return_slave_ready(vio_vf slave) ;
+extern cmd_return_code_t uty_pipe_read_close(vio_vf vf, bool final) ;
+extern cmd_return_code_t uty_pipe_write_close(vio_vf vf, bool final, bool base,
+ bool shell_only) ;
+
+#endif
diff --git a/lib/vty_io_shell.c b/lib/vty_io_shell.c
index 7b24b871..0b3cb5c4 100644
--- a/lib/vty_io_shell.c
+++ b/lib/vty_io_shell.c
@@ -106,7 +106,7 @@ uty_serv_vtysh(const char *path)
path_len = strlen(path) + 1 ;
if (path_len >= (int)sizeof(sa_un.sun_path))
{
- uzlog(NULL, LOG_ERR, "path too long for unix stream socket: '%s'", path);
+ zlog(NULL, LOG_ERR, "path too long for unix stream socket: '%s'", path);
return -1 ;
} ;
@@ -117,7 +117,7 @@ uty_serv_vtysh(const char *path)
sock = socket (AF_UNIX, SOCK_STREAM, 0);
if (sock < 0)
{
- uzlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s",
+ zlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s",
errtoa(errno, 0).str) ;
return -1 ;
}
@@ -137,7 +137,7 @@ uty_serv_vtysh(const char *path)
ret = bind (sock, (struct sockaddr *) &sa_un, sa_len) ;
if (ret < 0)
- uzlog(NULL, LOG_ERR, "Cannot bind path %s: %s", path, errtoa(errno, 0).str);
+ zlog(NULL, LOG_ERR, "Cannot bind path %s: %s", path, errtoa(errno, 0).str);
if (ret >= 0)
ret = set_nonblocking(sock);
@@ -146,7 +146,7 @@ uty_serv_vtysh(const char *path)
{
ret = listen (sock, 5);
if (ret < 0)
- uzlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock,
+ zlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock,
errtoa(errno, 0).str) ;
} ;
@@ -156,7 +156,7 @@ uty_serv_vtysh(const char *path)
{
/* set group of socket */
if ( chown (path, -1, ids.gid_vty) )
- uzlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s",
+ zlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s",
errtoa(errno, 0).str) ;
}
@@ -196,7 +196,7 @@ uty_accept_shell_serv (vty_listener listener)
if (sock_fd < 0)
{
- uzlog (NULL, LOG_WARNING, "can't accept vty shell socket : %s",
+ zlog (NULL, LOG_WARNING, "can't accept vty shell socket : %s",
errtoa(errno, 0).str) ;
return -1;
}
@@ -216,7 +216,7 @@ uty_accept_shell_serv (vty_listener listener)
uty_new_shell_serv(sock_fd) ;
/* Log new VTY */
- uzlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock_fd);
+ zlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock_fd);
return 0;
}
diff --git a/lib/vty_io_term.c b/lib/vty_io_term.c
index 0f8e834d..2c9cb643 100644
--- a/lib/vty_io_term.c
+++ b/lib/vty_io_term.c
@@ -26,10 +26,13 @@
#include "vty_local.h"
#include "vty_io.h"
#include "vty_io_term.h"
+#include "vty_io_file.h"
#include "vty_cli.h"
#include "vty_command.h"
#include "vio_fifo.h"
+#include "log_local.h"
+
#include "qstring.h"
#include "keystroke.h"
@@ -75,12 +78,21 @@
* Opening and closing VTY_TERMINAL type
*/
-static void uty_term_ready(vio_vfd vfd, void* action_info) ;
-static vty_timer_time uty_term_read_timeout(vio_timer_t* timer,
+typedef enum {
+ utw_error = 0,
+ utw_done = BIT(0), /* all possible is done */
+ utw_stopped = BIT(1),
+ utw_blocked = BIT(2), /* I/O blocked */
+ utw_paused = utw_blocked | utw_stopped,
+} utw_ret_t ;
+
+static void uty_term_read_ready(vio_vfd vfd, void* action_info) ;
+static void uty_term_write_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time uty_term_read_timeout(vio_timer timer,
void* action_info) ;
-static vty_timer_time uty_term_write_timeout(vio_timer_t* timer,
+static vty_timer_time uty_term_write_timeout(vio_timer timer,
void* action_info) ;
-static vty_readiness_t uty_term_write(vio_vf vf) ;
+static utw_ret_t uty_term_write(vio_vf vf) ;
static void uty_term_will_echo(vty_cli cli) ;
static void uty_term_will_suppress_go_ahead(vty_cli cli) ;
@@ -96,34 +108,38 @@ static void uty_term_dont_lflow_ahead(vty_cli cli) ;
static void
uty_term_open(int sock_fd, union sockunion *su)
{
+ node_type_t node ;
vty vty ;
vty_io vio ;
vio_vf vf ;
- VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
-
- /* Allocate new vty structure and set up default values. */
- vty = uty_new(VTY_TERMINAL) ;
- vio = vty->vio ;
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
- /* The initial vty->node depends on a number of vty type things, so
- * we set that now.
+ /* The initial vty->node will be authentication, unless the host does not
+ * require that, in which case it may be a number of other things.
*
- * This completes the initialisation of the vty object, except that the
- * execution and vio objects are largely empty.
+ * Note that setting NULL_NODE at this point will cause the terminal to be
+ * closed very quickly, after issuing suitable message.
*/
+ node = (host.password != NULL) ? AUTH_NODE : NULL_NODE ;
+
if (host.no_password_check)
{
- if (host.restricted_mode)
- vio->vty->node = RESTRICTED_NODE;
- else if (host.advanced)
- vio->vty->node = ENABLE_NODE;
+ if (host.restricted_mode)
+ node = RESTRICTED_NODE;
+ else if (host.advanced && (host.enable == NULL))
+ node = ENABLE_NODE;
else
- vio->vty->node = VIEW_NODE;
- }
- else
- vio->vty->node = AUTH_NODE;
+ node = VIEW_NODE;
+ } ;
+
+ /* Allocate new vty structure and set up default values.
+ *
+ * This completes the initialisation of the vty object, except that the
+ * execution and vio objects are largely empty.
+ */
+ vty = uty_new(VTY_TERMINAL, node) ;
+ vio = vty->vio ;
/* Complete the initialisation of the vty_io object.
*
@@ -134,7 +150,6 @@ uty_term_open(int sock_fd, union sockunion *su)
*
* - parse_type -- default = cmd_parse_standard
* - reflect_enabled -- default = false
- * - out_enabled -- default = true iff vfd_io_write
*
* Are OK, except that we want the read_timeout set to the current EXEC
* timeout value.
@@ -143,10 +158,10 @@ uty_term_open(int sock_fd, union sockunion *su)
*/
vf = uty_vf_new(vio, sutoa(su).str, sock_fd, vfd_socket, vfd_io_read_write) ;
- uty_vin_open( vio, vf, VIN_TERM, uty_term_ready,
+ uty_vin_push( vio, vf, VIN_TERM, uty_term_read_ready,
uty_term_read_timeout,
0) ; /* no ibuf required */
- uty_vout_open(vio, vf, VOUT_TERM, uty_term_ready,
+ uty_vout_push(vio, vf, VOUT_TERM, uty_term_write_ready,
uty_term_write_timeout,
4096) ; /* obuf is required */
@@ -155,9 +170,6 @@ uty_term_open(int sock_fd, union sockunion *su)
/* Set up the CLI object & initialise */
vf->cli = uty_cli_new(vf) ;
- /* When we get here the VTY is set up and all ready to go. */
- uty_cmd_prepare(vio) ;
-
/* Issue Telnet commands/escapes to be a good telnet citizen -- not much
* real negotiating going on -- just a statement of intentions !
*/
@@ -171,90 +183,221 @@ uty_term_open(int sock_fd, union sockunion *su)
/* Say hello */
vty_hello(vty);
- /* If need password, issue prompt or give up if no password to check
- * against !
+ /* If about to authenticate, issue friendly message.
+ *
+ * If cannot authenticate, issue an error message.
+ */
+ if (vty->node == AUTH_NODE)
+ vty_out(vty, "User Access Verification\n") ;
+ else if (vty->node == NULL_NODE)
+ vty_out(vty, "%% Cannot continue because no password is set\n") ;
+
+ /* Enter the command loop. */
+ uty_cmd_loop_enter(vio) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Command line fetch from a VTY_TERMINAL.
+ *
+ * Fetching a command line <=> the previous command has completed.
+ *
+ * Returns: CMD_SUCCESS -- have another command line ready to go
+ * CMD_WAITING -- would not wait for input <=> non-blocking
+ * CMD_EOF -- ??????
+ *
+ * This can be called in any thread.
+ *
+ * NB: this does not signal CMD_EOF TODO ????
+ */
+extern cmd_return_code_t
+uty_term_fetch_command_line(vio_vf vf, cmd_action action, cmd_context context)
+{
+ return uty_cli_want_command(vf->cli, action, context) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Showing error context for the VTY_TERMINAL command line.
+ *
+ * If the stack is at level == 1, then the last full line displayed will be
+ * the line in which the error occurred (unless have monitor output, which
+ * there is little we can do about). So there is no further output required.
+ * The command line is indented by the current prompt.
+ *
+ * If the stack is at level > 1, then may or may not have had output separating
+ * the command line from the current position, so we output the command line
+ * to provide context.
+ *
+ * Returns: indent position of command line
+ */
+extern uint
+uty_term_show_error_context(vio_vf vf, vio_fifo ebuf, uint depth)
+{
+ if (depth == 1)
+ return uty_cli_prompt_len(vf->cli) ;
+
+ vio_fifo_printf(ebuf, "%% in command line:\n") ;
+ vio_fifo_printf(ebuf, " %s\n", qs_make_string(vf->cli->clx)) ;
+
+ return 2 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Push output to the terminal.
+ *
+ * Returns: CMD_SUCCESS -- all buffers are empty, or final
+ * CMD_WAITING -- all buffers are not empty
+ * CMD_IO_ERROR -- failed -- final or not.
+ *
+ * This can be called in any thread. If "final" will not turn on any
+ * read/write ready stuff.
+ */
+extern cmd_return_code_t
+uty_term_out_push(vio_vf vf, bool final)
+{
+ vty_cli cli = vf->cli ;
+ utw_ret_t done ;
+
+ /* If have something in the obuf that needs to be written, then if not already
+ * out_active, make sure the command line is clear, and set out_active.
*/
- if (vty->node == AUTH_NODE)
+ if (!cli->out_active && !vio_fifo_empty(vf->obuf))
{
- if (host.password != NULL)
- vty_out(vty, "\nUser Access Verification\n\n");
- else
- uty_close(vio, false, qs_set(NULL, "vty password is not set."));
+ uty_cli_wipe(cli, 0) ;
+ cli->out_active = true ;
+ vio_lc_counter_reset(cli->olc) ;
+ } ;
+
+ /* Give the terminal writing a shove.
+ *
+ * If final, keep pushing while succeeds in writing without blocking.
+ */
+ if (final)
+ cli->flush = cli->out_active ; /* make sure empty everything */
+
+ do
+ {
+ if (final)
+ vio_lc_counter_reset(cli->olc) ;
+
+ done = uty_term_write(vf) ;
+ }
+ while (final && (done == utw_paused)) ;
+
+ if (!final)
+ {
+ if (done == utw_error)
+ return CMD_IO_ERROR ; /* TODO */
+
+ if ((done & utw_blocked) != 0)
+ {
+ confirm((utw_paused & utw_blocked) != 0) ;
+
+ uty_term_set_readiness(vf, write_ready) ;
+ return CMD_WAITING ;
+ } ;
} ;
- /* Push the output to date and start the CLI */
- uty_cmd_out_push(vio) ;
- uty_cli_start(vf->cli, vty->node) ;
+ return CMD_SUCCESS ;
} ;
/*------------------------------------------------------------------------------
- * Close the reading side of VTY_TERMINAL, and close down CLI as far as
+ * The read side of the vfd has been closed. Close down CLI as far as
* possible, given that output may be continuing.
*
* Expects to be called once only for the VTY_TERMINAL.
+ *
+ * There is no difference between final and not-final close in this case.
+ *
+ * Note that this is only closed when the VTY_TERMINAL is forcibly closed, or
+ * when the user quits.
+ *
+ * Returns: CMD_SUCCESS -- all is quiet.
*/
-extern void
-uty_term_read_close(vio_vf vf)
+extern cmd_return_code_t
+uty_term_read_close(vio_vf vf, bool final)
{
vty_io vio ;
/* Get the vio and ensure that we are all straight */
vio = vf->vio ;
assert((vio->vin == vio->vin_base) && (vio->vin == vf)) ;
+ assert(vio->vin->vin_state == vf_closing) ;
- /* Do the file side of things
- *
- * Note that half closing the file sets a new timeout, sets read off
- * and write on.
+ /*
*/
uty_set_monitor(vio, 0) ;
+ /* Close the CLI as far as possible, leaving output side intact.
+ *
+ * Can generate some final output, which will be dealt with as the output
+ * side is closed.
+ */
uty_cli_close(vf->cli, false) ;
- /* Log closing of VTY_TERM */
+ /* Log closing of VTY_TERMINAL
+ */
assert(vio->vty->type == VTY_TERMINAL) ;
- uzlog (NULL, LOG_INFO, "Vty connection (fd %d) close", vio_vfd_fd(vf->vfd)) ;
+ zlog (NULL, LOG_INFO, "Vty connection (fd %d) close", vio_vfd_fd(vf->vfd)) ;
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Try to output the close reason to the given VOUT_TERM.
+ *
+ * If there is anything pending to be output, discard it, first. The obuf has
+ * already been cleared.
+ *
+ * This will be pushed out when the VOUT_TERM is finally closed.
+ */
+extern void
+uty_term_close_reason(vio_vf vf, const char* reason)
+{
+ vio_lc_clear(vf->cli->olc) ;
+ vio_fifo_clear(vf->cli->cbuf, true) ;
+
+ uty_cli_out(vf->cli, "%% %s%s", reason, uty_cli_newline) ;
} ;
/*------------------------------------------------------------------------------
* Close the writing side of VTY_TERMINAL.
*
- * Pushes any buffered stuff to output and
+ * Assumes that the read side has been closed already, and so this is the last
+ * thing to be closed.
+ *
+ * Kicks the output side:
*
+ * if final, will push as much as possible until would block.
+ *
+ * if not final, will push another tranche and let the uty_term_ready() keep
+ * pushing until buffers empty and can uty_cmd_signal().
+ *
+ * Returns: CMD_SUCCESS => all written (or final) and CLI closed.
+ * CMD_WAITING => write ready running to empty the buffers
+ * CMD_IO_ERROR =>
+ * others TODO
*/
-extern bool
-uty_term_write_close(vio_vf vf, bool final)
+extern cmd_return_code_t
+uty_term_write_close(vio_vf vf, bool final, bool base)
{
+ cmd_return_code_t ret ;
vty_io vio ;
- vty_readiness_t ready ;
-
- /* Get the vio and ensure that we are all straight */
- vio = vf->vio ;
- assert((vio->vin == vio->vin_base) && (vio->vin == vf)) ;
- /* Do the file side of things
+ /* Get the vio and ensure that we are all straight
*
- * Note that half closing the file sets a new timeout, sets read off
- * and write on.
+ * Can only be the vout_base and must also be the vin_base, and the vin_base
+ * must now be closed.
*/
- uty_set_monitor(vio, 0) ;
-
- vf->cli->out_active = true ; /* force the issue */
-
- do
- {
- vf->cli->out_done = false ;
- ready = uty_term_write(vf) ;
- } while ((ready != write_ready) && vf->cli->out_active) ;
-
- final = final || !vf->cli->out_active ;
+ vio = vf->vio ;
+ assert((vio->vout == vio->vout_base) && (vio->vout == vf)) ;
+ assert((vio->vin == vio->vin_base) && (vio->vin->vin_state == vf_closed)) ;
- if (!final)
- uty_term_set_readiness(vf, ready) ;
+ ret = uty_term_out_push(vf, final) ;
- vf->cli = uty_cli_close(vf->cli, final) ;
+ if (final)
+ vf->cli = uty_cli_close(vf->cli, final) ;
- return final ;
+ return ret ;
} ;
/*==============================================================================
@@ -272,6 +415,8 @@ uty_term_write_close(vio_vf vf, bool final)
* not set both at once.
*/
+static void uty_term_ready(vio_vf vf) ;
+
/*------------------------------------------------------------------------------
* Set read/write readiness -- for VIN_TERM/VOUT_TERM
*
@@ -282,12 +427,41 @@ uty_term_set_readiness(vio_vf vf, vty_readiness_t ready)
{
VTY_ASSERT_LOCKED() ;
- uty_vf_set_read(vf, (ready == read_ready)) ;
- uty_vf_set_write(vf, (ready >= write_ready)) ;
+ if ((ready & write_ready) != 0)
+ uty_vf_set_write(vf, on) ;
+ else if ((ready & read_ready) != 0)
+ uty_vf_set_read(vf, on) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Terminal read ready
+ */
+static void
+uty_term_read_ready(vio_vfd vfd, void* action_info)
+{
+ vio_vf vf = action_info ;
+
+ assert(vfd == vf->vfd) ;
+
+ vf->cli->paused = false ; /* read ready clears paused */
+ uty_term_ready(vf) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Terminal write ready
+ */
+static void
+uty_term_write_ready(vio_vfd vfd, void* action_info)
+{
+ vio_vf vf = action_info ;
+
+ assert(vfd == vf->vfd) ;
+
+ uty_term_ready(vf) ;
} ;
/*------------------------------------------------------------------------------
- * So there is only one, common, uty_term_ready function, which:
+ * Terminal, something is ready -- read, write or no longer paused.
*
* 1. attempts to clear any output it can.
*
@@ -323,24 +497,142 @@ uty_term_set_readiness(vio_vf vf, vty_readiness_t ready)
* Resets the timer because something happened.
*/
static void
-uty_term_ready(vio_vfd vfd, void* action_info)
+uty_term_ready(vio_vf vf)
{
vty_readiness_t ready ;
+ utw_ret_t done ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* Start by trying to write away any outstanding stuff, and then another
+ * tranche of any outstanding output.
+ */
+ ready = not_ready ;
+
+ if (!vf->cli->more_enabled)
+ vio_lc_counter_reset(vf->cli->olc) ; /* do one tranche */
+
+ done = uty_term_write(vf) ;
+
+ while (done != utw_error)
+ {
+ utw_ret_t done_before ;
+ done_before = done ;
+
+ /* Kick the CLI, which may advance either because there is more input,
+ * or because some output has now completed, or for any other reason.
+ *
+ * This may return write_ready, which is a proxy for CLI ready, and
+ * MUST be honoured, even (especially) if the output buffers are empty.
+ */
+ ready = uty_cli(vf->cli) ;
+
+ /* Now try to write away any new output which may have been generated
+ * by the CLI.
+ */
+ done = uty_term_write(vf) ;
+
+ if (done == done_before)
+ break ; /* quit if no change in response */
+ } ;
+
+ if (done == utw_error)
+ ; /* TODO !! */
+
+ if ((done & utw_blocked) != 0)
+ {
+ confirm((utw_paused & utw_blocked) != 0) ;
+ ready |= write_ready ;
+ } ;
+
+ if ((done != utw_blocked) && (done != utw_error))
+ {
+ /* Since is not output blocked, tell master that is now ready. */
+ if (vf->pr_master != NULL)
+ uty_pipe_return_slave_ready(vf) ;
+ } ;
+
+ uty_term_set_readiness(vf, ready) ;
+
+ /* Signal the command loop if out_active and the buffers empty out.
+ */
+ if (done == utw_done)
+ uty_cmd_signal(vf->vio, CMD_SUCCESS) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Read timer has expired.
+ *
+ * If closing, then this is curtains -- have waited long enough !
+ *
+ * TODO .... sort out the VTY_TERMINAL time-out & death-watch timeout
+ *
+ * Otherwise, half close the VTY and leave it to the death-watch to sweep up.
+ */
+static vty_timer_time
+uty_term_read_timeout(vio_timer timer, void* action_info)
+{
+ vio_vf vf = action_info ;
+
+ assert(timer == vf->vfd->read_timer) ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ vf->vin_state = vf_timed_out ;
+ keystroke_stream_set_eof(vf->cli->key_stream, true) ;
+
+ vf->cli->paused = false ;
+
+ uty_term_ready(vf) ;
+
+ return 0 ;
+ } ;
+/*------------------------------------------------------------------------------
+ * Write timer has expired.
+ *
+ * If closing, then this is curtains -- have waited long enough !
+ *
+ * TODO .... sort out the VTY_TERMINAL time-out & death-watch timeout
+ *
+ * Otherwise, half close the VTY and leave it to the death-watch to sweep up.
+ */
+static vty_timer_time
+uty_term_write_timeout(vio_timer timer, void* action_info)
+{
vio_vf vf = action_info ;
- assert(vfd == vf->vfd) ;
+ assert(timer == vf->vfd->read_timer) ;
VTY_ASSERT_LOCKED() ;
- uty_term_write(vf) ; /* try to clear outstanding stuff */
- do
+ vf->cli->paused = false ;
+
+//uty_close(vio, true, qs_set(NULL, "Timed out")) ; TODO
+
+ return 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Timeout on the cli->paused timer -- clear paused and treat as CLI ready.
+ */
+extern void
+vty_term_pause_timeout(qtimer qtr, void* timer_info, qtime_mono_t when)
+{
+ vty_cli cli ;
+
+ VTY_LOCK() ;
+
+ cli = timer_info ;
+ assert(cli->pause_timer == qtr) ;
+
+ if (cli->paused)
{
- ready = uty_cli(vf->cli) ; /* do any CLI work... */
- ready |= uty_term_write(vf) ; /* ...and any output that generates */
- } while (ready >= now_ready) ;
+ cli->paused = false ;
+ uty_term_ready(cli->vf) ;
+ } ;
- uty_term_set_readiness(vf, ready) ;
+ VTY_UNLOCK() ;
} ;
/*==============================================================================
@@ -362,7 +654,7 @@ uty_term_ready(vio_vfd vfd, void* action_info)
*
* Returns: 0 => nothing available
* > 0 => read at least one byte
- * -1 => EOF (or not open, or failed)
+ * -1 => EOF (or not open, or failed, or timed out, ...)
*/
extern int
uty_term_read(vio_vf vf, keystroke steal)
@@ -397,7 +689,7 @@ uty_term_read(vio_vf vf, keystroke steal)
* cli->cbuf -- command line -- reflects the status of the command line
*
* vf->obuf -- command output -- which is written to the file only while
- * out_active.
+ * out_active, and not blocked in more_wait.
*
* The cli output takes precedence.
*
@@ -405,351 +697,256 @@ uty_term_read(vio_vf vf, keystroke steal)
* "--more--" mechanism.
*/
-static int uty_write_lc(vio_vf vf, vio_fifo vff, vio_line_control lc) ;
-static int uty_write_fifo_lc(vio_vf vf, vio_fifo vff, vio_line_control lc) ;
+static utw_ret_t uty_term_write_lc(vio_line_control lc, vio_vf vf,
+ vio_fifo vff) ;
/*------------------------------------------------------------------------------
- * Write as much as possible of what there is.
- *
- * If not cmd_in_progress, clears cli_blocked if both FIFOs are, or become,
- * empty.
- *
- * Note that if !write_open, or becomes !write_open, then the FIFOs are empty
- * and all output instantly successful.
- *
- * Sets write on if prevented from writing everything available for output
- * by write() threatening to block.
+ * Have some (more) monitor output to send to the vty.
*
- * Returns: write_ready if should now set write on
- * now_ready if should loop back and try again
- * not_ready otherwise
+ * Make sure the command line is clear, then claim screen for monitor output
+ * and attempt to empty out buffers.
*/
-static vty_readiness_t
-uty_term_write(vio_vf vf)
+extern void
+uty_term_mon_write(vio_vf vf)
{
- vty_cli cli = vf->cli ;
- int ret ;
+ utw_ret_t done ;
- VTY_ASSERT_LOCKED() ;
+ uty_cli_pre_monitor(vf->cli) ; /* make sure in a fit state */
- ret = -1 ;
- while (vf->vout_state == vf_open)
- {
- /* Any outstanding line control output takes precedence */
- ret = uty_write_lc(vf, vf->obuf, cli->olc) ;
- if (ret != 0)
- break ;
+ done = uty_term_write(vf) ; /* TODO -- errors !! */
- /* Next: empty out the cli output */
- ret = vio_fifo_write_nb(cli->cbuf, vio_vfd_fd(vf->vfd), true) ;
- if (ret != 0)
- break ;
-
- /* Finished now if not allowed to progress the command stuff */
- if (!cli->out_active)
- return not_ready ; /* done all can do */
-
- /* If there is something in the command buffer, do that */
- if (!vio_fifo_empty(vf->obuf))
- {
-#if 0
- if (cli->out_done)
- break ; /* ...but not if done once */
-
- cli->out_done = true ; /* done this once */
-#endif
- assert(!cli->more_wait) ;
-
- ret = uty_write_fifo_lc(vf, vf->obuf, cli->olc) ;
- if (ret != 0)
- {
- if (ret < 0)
- break ; /* failed */
-
- if (!cli->more_wait)
- return write_ready ; /* done a tranche */
-
- /* Moved into "--more--" state
- *
- * * the "--more--" prompt is ready to be written, so do that
- * now
- *
- * * if that completes, then want to run the CLI *now* to
- * perform the first stage of the "--more--" process.
- */
- ret = vio_fifo_write_nb(cli->cbuf, vio_vfd_fd(vf->vfd), true) ;
- if (ret != 0)
- break ;
-
- return now_ready ;
- } ;
- } ;
-
- /* Exciting stuff: there is nothing left to output...
- *
- * ... watch out for half closed state.
- */
-#if 0
- if (vio->closing)
- {
- if (vio->close_reason != NULL)
- {
- if (cli->drawn || cli->dirty)
- uty_out(vio, "\n") ;
- uty_out(vio, "%% %s\n", vio->close_reason) ;
-
- vio->close_reason = NULL ; /* MUST discard now... */
- continue ; /* ... and write away */
- } ;
-
- if (!vio->closed) /* avoid recursion */
- uty_close(vio) ;
-
- return not_ready ; /* it's all over */
- } ;
-#endif
-
- if (uty_cli_draw_if_required(cli))
- continue ; /* do that now. */
-
- /* There really is nothing left to output */
- cli->out_active = false ;
+ if ((done & utw_blocked) != 0)
+ {
+ confirm((utw_paused & utw_blocked) != 0) ;
- return not_ready ;
+ uty_term_set_readiness(vf, write_ready) ;
} ;
-
- /* Arrives here if there is more to do, or failed (or was !write_open) */
-
- if (ret > 0)
- return write_ready ;
-
- if (ret == 0) /* just in case */
- return not_ready ;
-
- /* If is write_open, then report the error
- *
- * If still read_open, let the reader pick up and report the error, when it
- * has finished anything it has buffered.
- */
- if (vf->vout_state == vf_open)
- uty_vf_error(vf, "write", errno) ;
-
- /* For whatever reason, is no longer write_open -- clear all buffers.
- */
- vio_fifo_clear(vf->obuf, true) ; /* throw away cli stuff */
- uty_cli_out_clear(cli) ; /* throw away cmd stuff */
-
- cli->out_active = false ;
-
- return not_ready ; /* NB: NOT blocked by I/O */
} ;
/*------------------------------------------------------------------------------
- * Write as much as possible -- for "monitor" output.
+ * Write as much as possible of what there is.
*
- * Outputs only:
+ * Move to more_wait if required. TODO
*
- * a. outstanding line control stuff.
+ * If is cli->flush, then when all buffers are emptied out, clears itself and
+ * the out_active flag.
*
- * b. contents of CLI buffer
+ * Returns:
*
- * And:
+ * utw_error -- I/O error -- see errno (utw_error = -1)
*
- * a. does not report any errors.
+ * utw_blocked -- write blocked -- some write operation would block
*
- * b. does not change anything except the state of the buffers.
+ * utw_paused -- have written as much as line control allows in one go.
*
- * In particular, for the qpthreaded world, does not attempt to change
- * the state of the qfile or any other "thread private" structures.
+ * NB: this does NOT mean is now in more_wait, it means that
+ * to write more it is necessary to clear the line
+ * control pause state.
*
- * Returns: > 0 => blocked
- * 0 => all gone
- * < 0 => failed (or !write_open)
- */
-#if 0
-static int
-uty_write_monitor(vio_vf vf)
-{
- VTY_ASSERT_LOCKED() ;
-
- if (!vf->write_open)
- return -1 ;
-
- if (vf->olc != NULL)
- {
- int ret ;
- ret = uty_write_lc(vf, vf->obuf, vf->olc) ;
-
- if (ret != 0)
- return ret ;
- } ;
-
- return vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf), true) ;
-} ;
-#endif
-
-/*------------------------------------------------------------------------------
- * Write the given FIFO to output -- subject to line control.
+ * utw_stopped -- have done as much as can do -- no more output is possible
+ * until some external event changes things.
+ *
+ * This implies that any pending output has completed, in
+ * particular the line control iovec and the cli->cbuf are
+ * both empty.
*
- * Note that even if no "--more--" is set, will have set some height, so
- * that does not attempt to empty the FIFO completely all in one go.
+ * This state includes:
*
- * If the line control becomes "paused", it is time to enter "--more--" state
- * -- unless the FIFO is empty (or "--more--" is not enabled).
+ * * !out_active -- if there is something in the vf->obuf,
+ * we are not yet ready to output it.
*
- * NB: expects that the sock is write_open
+ * * more_wait -- waiting for user
*
- * Returns: > 0 => blocked or completed one tranche (more to go)
- * 0 => all gone
- * < 0 => failed
+ * utw_done -- have written everything can find, unless more output
+ * arrives, there is no more to do.
+ *
+ * If was cli->flush the all output really has gone, as
+ * well as any incomplete line. Also the out_active and
+ * the flush flags will have been cleared.
+ *
+ * If was not cli->flush, the out_active state persists,
+ * and there may be an incomplete line still pending.
*/
-static int
-uty_write_fifo_lc(vio_vf vf, vio_fifo vff, vio_line_control lc)
+static utw_ret_t
+uty_term_write(vio_vf vf)
{
- int ret ;
- char* src ;
- size_t have ;
- vty_cli cli ;
+ vty_cli cli = vf->cli ;
+ utw_ret_t ret ;
+ int did ;
+ size_t have, take ;
+ char* src ;
- cli = vf->cli ;
+ VTY_ASSERT_LOCKED() ;
- /* Collect another line_control height's worth of output.
- *
- * Expect the line control to be empty at this point, but it does not have
- * to be.
+ /* If the vout is neither vf_open, not vf_closing, discard all buffered
+ * output, and return all done.
*/
- vio_lc_set_pause(lc) ; /* clears lc->paused */
-
- vio_fifo_set_hold_mark(vff) ;
-
- src = vio_fifo_get(vff, &have) ;
- while ((src != NULL) && (!lc->paused))
+ if ((vf->vout_state != vf_open) && (vf->vout_state != vf_closing))
{
- size_t take ;
+ vio_fifo_clear(vf->obuf, false) ;
+ vio_fifo_clear(cli->cbuf, false) ;
+ vio_lc_clear(cli->olc) ;
- if (src == NULL)
- break ;
-
- take = vio_lc_append(lc, src, have) ;
- src = vio_fifo_step_get(vff, &have, take) ;
- } ;
+ cli->out_active = false ;
+ cli->flush = false ;
- cli->dirty = (lc->col != 0) ;
+ cli->more_wait = false ;
+ cli->more_enter = false ;
- /* Write the contents of the line control */
- ret = uty_write_lc(vf, vff, lc) ;
+ return utw_done ;
+ } ;
- if (ret < 0)
- return ret ; /* give up now if failed. */
+ /* Any outstanding line control output takes precedence */
+ ret = uty_term_write_lc(cli->olc, vf, vf->obuf) ;
+ if (ret != utw_done)
+ return ret ; /* utw_blocked or utw_error */
- if ((ret == 0) && vio_fifo_empty(vff))
- return 0 ; /* FIFO and line control empty */
+ /* Next: empty out the cli output */
+ did = vio_fifo_write_nb(cli->cbuf, vio_vfd_fd(vf->vfd), true) ;
+ if (did != 0)
+ return (did < 0) ? utw_error : utw_blocked ;
- /* If should now do "--more--", now is the time to prepare for that.
+ /* Next: if there is monitor output to deal with, deal with it.
*
- * Entering more state issues a new prompt in the CLI buffer, which can
- * be written once line control write completes.
+ * Note that the mon_active flag is set under VTY_LOCK(), so do not
+ * need to LOG_LOCK() to discover whether there is anything to do.
*
- * The "--more--" cli will not do anything until the CLI buffer has
- * cleared.
+ * But the vio->mbuf is filled under LOG_LOCK(), so need to write it
+ * under the same.
*/
- if (lc->paused && cli->more_enabled)
- uty_cli_enter_more_wait(cli) ;
+ if (cli->mon_active)
+ {
+ LOG_LOCK() ;
- return 1 ; /* FIFO or line control, not empty */
-} ;
+ did = vio_fifo_write_nb(vf->vio->mbuf, vio_vfd_fd(vf->vfd), true) ;
-/*------------------------------------------------------------------------------
- * Write contents of line control (if any).
- *
- * NB: expects that the sock is write_open
- *
- * NB: does nothing other than write() and buffer management.
- *
- * Returns: > 0 => blocked
- * 0 => all gone
- * < 0 => failed
- */
-static int
-uty_write_lc(vio_vf vf, vio_fifo vff, vio_line_control lc)
-{
- int ret ;
+ LOG_UNLOCK() ;
- ret = vio_lc_write_nb(vio_vfd_fd(vf->vfd), lc) ;
+ if (did != 0)
+ return (did < 0) ? utw_error : utw_blocked ;
- if (ret <= 0)
- vio_fifo_clear_hold_mark(vff) ; /* finished with FIFO contents */
+ uty_cli_post_monitor(vf->cli) ;
+ } ;
- return ret ;
-} ;
+ /* If not out_active, or in more_wait, then we are stopped, waiting for some
+ * external event to move things on.
+ */
+ if (cli->flush)
+ assert(cli->out_active) ; /* makes no sense, otherwise */
+ if (!cli->out_active || cli->more_wait)
+ return utw_stopped ;
-#if 0
+ /* Push the output fifo and any complete line fragments that may be buffered
+ * in hand in the line control -- this will stop if the line counter becomes
+ * exhausted.
+ *
+ * Note that this arranges for vio_lc_append() to be called at least once,
+ * even if the fifo is empty -- this deals with any parts of a complete
+ * line that may be held in the line control due to counter exhaustion.
+ *
+ * If the fifo is or becomes empty, then if is cli->flush, flush out any
+ * incomplete line which may be held in the line control -- in effect,
+ * cli->flush is a phantom '\n' at the end of the output fifo !
+ */
+ vio_fifo_set_hold_mark(vf->obuf) ; /* released in uty_term_write_lc() */
-/*------------------------------------------------------------------------------
- * Start command output -- clears down the line control.
- *
- * Requires that that current line is empty -- restarts the line control
- * on the basis that is at column 0.
- */
-extern void
-uty_cmd_output_start(vio_vf vf)
-{
- if (vf->olc != NULL)
- vio_lc_clear(vf->olc) ;
+ src = vio_fifo_get(vf->obuf, &have) ;
+ while (1)
+ {
+ take = vio_lc_append(cli->olc, src, have) ;
- vio_fifo_set_hold_mark(vf->obuf) ; /* mark to keep until all gone */
-} ;
+ if (take == 0)
+ break ;
-#endif
+ src = vio_fifo_step_get(vf->obuf, &have, take) ;
+ if (have == 0)
+ break ;
+ } ;
+ if ((have == 0) && (cli->flush))
+ vio_lc_flush(cli->olc) ;
+ ret = uty_term_write_lc(cli->olc, vf, vf->obuf) ;
+ if (ret != utw_done)
+ return ret ; /* utw_blocked or utw_error */
-/*==============================================================================
- * Timer actions for VTY_TERMINAL
- */
+ /* If arrive here, then:
+ *
+ * * no output is blocked and no errors have occurred.
+ *
+ * * the cli->cbuf is empty.
+ *
+ * * the line control iovec buffer is empty.
+ *
+ * If the fifo is not empty or there is a some part of a complete line in
+ * hand, then the line counter must be exhausted.
+ */
+ if ((have != 0) || vio_lc_have_complete_line_in_hand(cli->olc))
+ {
+ assert(vio_lc_counter_is_exhausted(cli->olc)) ;
-/*------------------------------------------------------------------------------
- * Read timer has expired.
- *
- * If closing, then this is curtains -- have waited long enough !
- *
- * TODO .... sort out the VTY_TERMINAL time-out & death-watch timeout
- *
- * Otherwise, half close the VTY and leave it to the death-watch to sweep up.
- */
-static vty_timer_time
-uty_term_read_timeout(vio_timer_t* timer, void* action_info)
-{
- vty_io vio = action_info ;
+ if (cli->more_enabled)
+ {
+ uty_cli_enter_more_wait(cli) ;
+ return utw_stopped ;
+ }
+ else
+ return utw_paused ; /* artificial block */
+ } ;
- VTY_ASSERT_LOCKED() ;
+ /* Exciting stuff: there is nothing left to output...
+ *
+ * ...with the sole possible exception of an incomplete line buffered
+ * in the line control, which can do nothing about until there is more
+ * to be output, or the output is flushed...
+ *
+ * ...if not cli->flush, we are stopped, waiting for something else to
+ * happen.
+ */
+ assert(!cli->more_wait && !cli->more_enter) ;
- uty_close(vio, true, qs_set(NULL, "Timed out")) ;
+ if (cli->flush)
+ {
+ /* Even more exciting: is cli->flush !
+ *
+ * This means that any incomplete line must have been flushed, above.
+ * So all buffers MUST be empty.
+ */
+ assert(vio_fifo_empty(vf->obuf) && vio_lc_is_empty(cli->olc)) ;
- return 0 ;
- } ;
+ cli->out_active = false ;
+ cli->flush = false ;
+ } ;
+
+ return utw_done ;
+} ;
/*------------------------------------------------------------------------------
- * Write timer has expired.
+ * Write contents of line control iovec buffer (if any).
*
- * If closing, then this is curtains -- have waited long enough !
+ * NB: expects that the vf is write_open
*
- * TODO .... sort out the VTY_TERMINAL time-out & death-watch timeout
+ * NB: does nothing other than write() and buffer management.
*
- * Otherwise, half close the VTY and leave it to the death-watch to sweep up.
+ * Returns: utw_blocked => blocked
+ * utw_done => all gone (may still have stuff "in hand")
+ * utw_error => failed
*/
-static vty_timer_time
-uty_term_write_timeout(vio_timer_t* timer, void* action_info)
+static utw_ret_t
+uty_term_write_lc(vio_line_control lc, vio_vf vf, vio_fifo vff)
{
- vty_io vio = action_info ;
+ int did ;
- VTY_ASSERT_LOCKED() ;
+ did = vio_lc_write_nb(vio_vfd_fd(vf->vfd), lc) ;
- uty_close(vio, true, qs_set(NULL, "Timed out")) ;
+ if (did > 0)
+ return utw_blocked ;
- return 0 ;
+ vio_fifo_clear_hold_mark(vff) ; /* finished with FIFO contents */
+
+ return (did == 0) ? utw_done : utw_error ;
} ;
/*==============================================================================
@@ -778,7 +975,7 @@ uty_term_open_listeners(const char *addr, unsigned short port)
n = uty_term_listen_simple(addr, port);
if (n == 0)
- uzlog(NULL, LOG_ERR, "could not open any VTY_TERMINAL listeners") ;
+ zlog(NULL, LOG_ERR, "could not open any VTY_TERMINAL listeners") ;
} ;
/*------------------------------------------------------------------------------
@@ -875,7 +1072,7 @@ uty_term_listen_simple(const char *addr, unsigned short port)
if (ret == 0)
sa = &su_addr.sa ;
else
- uzlog(NULL, LOG_ERR, "bad address %s, cannot listen for VTY", addr);
+ zlog(NULL, LOG_ERR, "bad address %s, cannot listen for VTY", addr);
} ;
/* Try for AF_INET */
@@ -896,7 +1093,7 @@ uty_term_listen_simple(const char *addr, unsigned short port)
/* If not used the address... something wrong */
if (sa != NULL)
- uzlog(NULL, LOG_ERR, "could not use address %s, to listen for VTY", addr);
+ zlog(NULL, LOG_ERR, "could not use address %s, to listen for VTY", addr);
/* Done */
return n ;
@@ -1003,7 +1200,7 @@ uty_term_accept(int sock_listen)
if (sock_fd < 0)
{
if (sock_fd == -1)
- uzlog (NULL, LOG_WARNING, "can't accept vty socket : %s",
+ zlog (NULL, LOG_WARNING, "can't accept vty socket : %s",
errtoa(errno, 0).str) ;
return ;
}
@@ -1046,7 +1243,7 @@ uty_term_accept(int sock_listen)
if (ret != 0)
{
- uzlog (NULL, LOG_INFO, "Vty connection refused from %s", sutoa(&su).str) ;
+ zlog (NULL, LOG_INFO, "Vty connection refused from %s", sutoa(&su).str) ;
close (sock_fd);
return ;
} ;
@@ -1056,14 +1253,14 @@ uty_term_accept(int sock_listen)
ret = setsockopt (sock_fd, IPPROTO_TCP, TCP_NODELAY,
(void*)&on, sizeof (on));
if (ret < 0)
- uzlog (NULL, LOG_INFO, "can't set sockopt to socket %d: %s",
+ zlog (NULL, LOG_INFO, "can't set sockopt to socket %d: %s",
sock_fd, errtoa(errno, 0).str) ;
/* All set -- create the VTY_TERMINAL and set it going */
uty_term_open(sock_fd, &su);
/* Log new VTY */
- uzlog (NULL, LOG_INFO, "Vty connection from %s (fd %d)", sutoa(&su).str,
+ zlog (NULL, LOG_INFO, "Vty connection from %s (fd %d)", sutoa(&su).str,
sock_fd) ;
return ;
@@ -1315,7 +1512,7 @@ uty_telnet_command(vio_vf vf, keystroke stroke, bool callback)
case to_NAWS:
if (left != 4)
{
- uzlog(NULL, LOG_WARNING,
+ zlog(NULL, LOG_WARNING,
"RFC 1073 violation detected: telnet NAWS option "
"should send %d characters, but we received %d",
(3 + 4 + 2), (3 + left + 2)) ;
@@ -1352,148 +1549,3 @@ uty_telnet_command(vio_vf vf, keystroke stroke, bool callback)
return dealt_with ;
} ;
-/*==============================================================================
- * Output to vty which are set to "monitor".
- *
- * This is VERY TRICKY.
- *
- * If not running qpthreaded, then the objective is to get the message away
- * immediately -- do not wish it to be delayed in any way by the thread
- * system.
- *
- * So proceed as follows:
- *
- * a. wipe command line -- which adds output to the CLI buffer
- *
- * b. write the CLI buffer to the sock and any outstanding line control.
- *
- * c. write the monitor output.
- *
- * If that does not complete, put the tail end to the CLI buffer.
- *
- * d. restore any command line -- which adds output to the CLI buffer
- *
- * e. write the CLI buffer to the sock
- *
- * If that all succeeds, nothing has changed as far as the VTY stuff is
- * concerned -- except that possibly some CLI output was sent before it got
- * round to it.
- *
- * Note that step (b) will deal with any output hanging around from an
- * earlier step (e). If cannot complete that, then does not add fuel to the
- * fire -- but the message will be discarded.
- *
- * If that fails, or does not complete, then can set write on, to signal that
- * there is some output in the CLI buffer that needs to be sent, or some
- * error to be dealt with.
- *
- * The output should be tidy.
- *
- * To cut down the clutter, step (d) is performed only if the command line
- * is not empty (or if in cli_more_wait). Once a the user has started to enter
- * a command, the prompt and the command will remain visible.
- *
- * When logging an I/O error for a vty that happens to be a monitor, the
- * monitor-ness has already been turned off. The monitor output code does not
- * attempt to log any errors, sets write on so that the error will be picked
- * up that way.
- *
- * However, in the event of an assertion failure, it is possible that an
- * assertion will fail inside the monitor output. The monitor_busy flag
- * prevents disaster. It is also left set if I/O fails in monitor output, so
- * will not try to use the monitor again.
- *
- * Note that an assertion which is false for all vty monitors will recurse
- * through all the monitors, setting each one busy, in turn !
- *
-
-
- * TODO: sort out write on in the qpthreads world ??
- *
- * The problem is that the qpselect structure is designed to be accessed ONLY
- * within the thread to which it belongs. This makes it impossible for the
- * monitor output to set/clear read/write on the vty sock... so some way
- * around this is required.
- */
-
-/*------------------------------------------------------------------------------
- * Output logging information to all vty which are set to "monitor".
- */
-extern void
-uty_log(struct logline* ll, struct zlog *zl, int priority,
- const char *format, va_list va)
-{
- vty_io vio ;
-
- VTY_ASSERT_LOCKED() ;
-
- vio = sdl_head(vio_monitors_base) ;
-
- if (vio == NULL)
- return ; /* go no further if no "monitor" vtys */
-
- /* Prepare line for output. */
- uvzlog_line(ll, zl, priority, format, va, llt_crlf) ; /* with crlf */
-
- /* write to all known "monitor" vty
- *
- */
- while (vio != NULL)
- {
-#if 0
- if (!vio->monitor_busy)
- {
- int ret ;
-
- vio->monitor_busy = 1 ; /* close the door */
-
- uty_cli_pre_monitor(vio, ll->len - 2) ; /* claim the console */
-
- ret = uty_write_monitor(vio) ;
- if (ret == 0)
- {
- ret = write_nb(vio_vfd_fd(vf->vfd), ll->line, ll->len) ;
-
- if (ret >= 0)
- {
- ret = uty_cli_post_monitor(vio, ll->line + ret,
- ll->len - ret) ;
- if (ret > 0)
- ret = uty_write_monitor(vio) ;
- } ;
- } ;
-
- if (ret != 0)
- /* need to prod */ ;
-
- if (ret >= 0)
- vio->monitor_busy = 0 ;
- } ;
-#endif
- vio = sdl_next(vio, mon_list) ;
- } ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Async-signal-safe version of vty_log for fixed strings.
- *
- * This is last gasp operation.
- */
-void
-vty_log_fixed (const char *buf, size_t len)
-{
- vty_io vio ;
-
- /* Write to all known "monitor" vty
- *
- * Forget all the niceties -- about to die in any case.
- */
- vio = sdl_head(vio_monitors_base) ;
- while (vio != NULL)
- {
- write(vio_vfd_fd(vio->vout_base->vfd), buf, len) ;
- write(vio_vfd_fd(vio->vout_base->vfd), "\r\n", 2) ;
-
- vio = sdl_next(vio, mon_list) ;
- } ;
-} ;
diff --git a/lib/vty_io_term.h b/lib/vty_io_term.h
index f1823a92..d8999b98 100644
--- a/lib/vty_io_term.h
+++ b/lib/vty_io_term.h
@@ -39,6 +39,7 @@
#include "thread.h"
#include "command_local.h"
#include "qstring.h"
+#include "qtimers.h"
/*==============================================================================
* Here are structures and other definitions which are shared by:
@@ -54,13 +55,20 @@
extern void uty_term_new(vty_io vio, int sock_fd) ;
-extern void uty_term_read_close(vio_vf vf) ;
-extern bool uty_term_write_close(vio_vf vf, bool final) ;
+extern cmd_return_code_t uty_term_fetch_command_line(vio_vf vf,
+ cmd_action action, cmd_context context) ;
+extern cmd_return_code_t uty_term_out_push(vio_vf vf, bool final) ;
+extern uint uty_term_show_error_context(vio_vf vf, vio_fifo ebuf, uint depth) ;
+extern cmd_return_code_t uty_term_read_close(vio_vf vf, bool final) ;
+extern void uty_term_close_reason(vio_vf vf, const char* reason) ;
+extern cmd_return_code_t uty_term_write_close(vio_vf vf, bool final, bool base);
extern int uty_term_read(vio_vf vf, keystroke steal) ;
extern void uty_term_set_readiness(vio_vf vf, vty_readiness_t ready) ;
+extern qtimer_action vty_term_pause_timeout ;
+extern void uty_term_mon_write(vio_vf vf) ;
extern bool uty_telnet_command(vio_vf, keystroke stroke, bool callback) ;
diff --git a/lib/vty_io_vsh.c b/lib/vty_io_vsh.c
index 7b24b871..0b3cb5c4 100644
--- a/lib/vty_io_vsh.c
+++ b/lib/vty_io_vsh.c
@@ -106,7 +106,7 @@ uty_serv_vtysh(const char *path)
path_len = strlen(path) + 1 ;
if (path_len >= (int)sizeof(sa_un.sun_path))
{
- uzlog(NULL, LOG_ERR, "path too long for unix stream socket: '%s'", path);
+ zlog(NULL, LOG_ERR, "path too long for unix stream socket: '%s'", path);
return -1 ;
} ;
@@ -117,7 +117,7 @@ uty_serv_vtysh(const char *path)
sock = socket (AF_UNIX, SOCK_STREAM, 0);
if (sock < 0)
{
- uzlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s",
+ zlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s",
errtoa(errno, 0).str) ;
return -1 ;
}
@@ -137,7 +137,7 @@ uty_serv_vtysh(const char *path)
ret = bind (sock, (struct sockaddr *) &sa_un, sa_len) ;
if (ret < 0)
- uzlog(NULL, LOG_ERR, "Cannot bind path %s: %s", path, errtoa(errno, 0).str);
+ zlog(NULL, LOG_ERR, "Cannot bind path %s: %s", path, errtoa(errno, 0).str);
if (ret >= 0)
ret = set_nonblocking(sock);
@@ -146,7 +146,7 @@ uty_serv_vtysh(const char *path)
{
ret = listen (sock, 5);
if (ret < 0)
- uzlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock,
+ zlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock,
errtoa(errno, 0).str) ;
} ;
@@ -156,7 +156,7 @@ uty_serv_vtysh(const char *path)
{
/* set group of socket */
if ( chown (path, -1, ids.gid_vty) )
- uzlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s",
+ zlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s",
errtoa(errno, 0).str) ;
}
@@ -196,7 +196,7 @@ uty_accept_shell_serv (vty_listener listener)
if (sock_fd < 0)
{
- uzlog (NULL, LOG_WARNING, "can't accept vty shell socket : %s",
+ zlog (NULL, LOG_WARNING, "can't accept vty shell socket : %s",
errtoa(errno, 0).str) ;
return -1;
}
@@ -216,7 +216,7 @@ uty_accept_shell_serv (vty_listener listener)
uty_new_shell_serv(sock_fd) ;
/* Log new VTY */
- uzlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock_fd);
+ zlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock_fd);
return 0;
}
diff --git a/lib/vty_local.h b/lib/vty_local.h
index 97e5786c..ff389baf 100644
--- a/lib/vty_local.h
+++ b/lib/vty_local.h
@@ -24,14 +24,18 @@
#ifndef _ZEBRA_VTY_LOCAL_H
#define _ZEBRA_VTY_LOCAL_H
+#include "misc.h"
#include "vty_common.h" /* First and foremost */
#include "command_common.h"
+//#include "log_local.h" /* NB: */
+
#include "vargs.h"
#include "qpthreads.h"
#include "qpnexus.h"
#include "thread.h"
+#include "qpath.h"
/*==============================================================================
* This is for access to some things in vty.c which are not required
@@ -61,56 +65,82 @@
/*==============================================================================
* Variables in vty.c -- used in any of the family
*/
-extern struct vty_io* vio_list_base ;
-extern struct vty_io* vio_monitors_base ;
+extern struct vty_io* vio_live_list ;
+extern struct vty_io* vio_monitor_list ;
extern struct vty_io* vio_death_watch ;
+extern struct vio_child* vio_childer_list ;
+
extern struct thread_master* vty_master ;
extern bool vty_nexus ;
+extern bool vty_multi_nexus ;
extern qpn_nexus vty_cli_nexus ;
extern qpn_nexus vty_cmd_nexus ;
+extern qpt_mutex_t vty_child_signal_mutex ;
+extern qpn_nexus vty_child_signal_nexus ;
+
/*==============================================================================
* To make vty qpthread safe we use a single mutex.
*
- * vty and log recurse through each other, so the same mutex is used
- * for both, i.e. they are treated as being part of the same monitor.
- *
- * A recursive mutex is used. This simplifies the calling from log to vty and
- * back again. It also allows for the vty internals to call each other.
+ * A recursive mutex is used, which allows for the vty internals to call
+ * each other.
*
* There are some "uty" functions which assume the mutex is locked.
*
* vty is closely bound to the command handling -- the main vty structure
* contains the context in which commands are parsed and executed.
+ *
+ * vty also interacts with logging functions. Note that where it needs to
+ * LOG_LOCK() and VTY_LOCK() it will acquire the VTY_LOCK() *first*.
*/
extern qpt_mutex_t vty_mutex ;
-#ifdef VTY_DEBUG /* Can be forced from outside */
-# if VTY_DEBUG
-# define VTY_DEBUG 1 /* Force 1 or 0 */
-#else
-# define VTY_DEBUG 0
+/*------------------------------------------------------------------------------
+ * Sort out VTY_DEBUG.
+ *
+ * Set to 1 if defined, but blank.
+ * Set to QDEBUG if not defined.
+ *
+ * Force to 0 if VTY_NO_DEBUG is defined and not zero.
+ *
+ * So: defaults to same as QDEBUG, but no matter what QDEBUG is set to:
+ *
+ * * can set VTY_DEBUG == 0 to turn off debug
+ * * or set VTY_DEBUG != 0 to turn on debug
+ * * or set VTY_NO_DEBUG != 0 to force debug off
+ */
+
+#ifdef VTY_DEBUG /* If defined, make it 1 or 0 */
+# if IS_BLANK_OPTION(VTY_DEBUG)
+# undef VTY_DEBUG
+# define VTY_DEBUG 1
# endif
-#else
-# ifdef QDEBUG
-# define VTY_DEBUG 1 /* Follow QDEBUG */
-#else
+#else /* If not defined, follow QDEBUG */
+# define VTY_DEBUG QDEBUG
+#endif
+
+#ifdef VTY_NO_DEBUG /* Override, if defined */
+# if IS_NOT_ZERO_OPTION(VTY_NO_DEBUG)
+# undef VTY_DEBUG
# define VTY_DEBUG 0
# endif
#endif
enum { vty_debug = VTY_DEBUG } ;
+/*------------------------------------------------------------------------------
+ * Locking and related functions.
+ */
extern int vty_lock_count ;
Inline void
VTY_LOCK(void) /* if is qpthreads_enabled, lock vty_mutex */
{
- qpt_mutex_lock(&vty_mutex) ;
+ qpt_mutex_lock(vty_mutex) ;
if (vty_debug)
++vty_lock_count ;
} ;
@@ -120,7 +150,7 @@ VTY_UNLOCK(void) /* if is qpthreads_enabled, unlock vty_mutex */
{
if (vty_debug)
--vty_lock_count ;
- qpt_mutex_unlock(&vty_mutex) ;
+ qpt_mutex_unlock(vty_mutex) ;
} ;
Inline bool /* true => is (effectively) cli thread */
@@ -129,6 +159,12 @@ vty_is_cli_thread(void)
return !qpthreads_enabled || qpt_thread_is_self(vty_cli_nexus->thread_id) ;
} ;
+Inline bool /* true => running with more than one pthread */
+vty_is_multi_pthreaded(void)
+{
+ return !qpthreads_enabled && (vty_cli_nexus != vty_cmd_nexus) ;
+} ;
+
/* For debug (and documentation) purposes, will VTY_ASSERT_LOCKED where that
* is required.
*
@@ -138,7 +174,6 @@ vty_is_cli_thread(void)
* code which is called before qpthreads are started up, or which will never
* run qpthreaded !
*/
-#if VTY_DEBUG
extern int vty_assert_fail ;
@@ -155,44 +190,45 @@ VTY_ASSERT_FAILED(void)
Inline void
VTY_ASSERT_LOCKED(void)
{
- if ((vty_lock_count == 0) && (qpthreads_enabled))
- VTY_ASSERT_FAILED() ;
+ if (vty_debug)
+ if ((vty_lock_count == 0) && (qpthreads_enabled))
+ VTY_ASSERT_FAILED() ;
} ;
Inline void
VTY_ASSERT_CLI_THREAD(void)
{
- if (!vty_is_cli_thread())
- VTY_ASSERT_FAILED() ;
+ if (vty_debug)
+ if (!vty_is_cli_thread())
+ VTY_ASSERT_FAILED() ;
} ;
-#else
-
-#define VTY_ASSERT_LOCKED()
-#define VTY_ASSERT_CLI_THREAD()
-
-#endif
+Inline void
+VTY_ASSERT_CLI_THREAD_LOCKED(void)
+{
+ if (vty_debug)
+ {
+ VTY_ASSERT_CLI_THREAD() ;
+ VTY_ASSERT_LOCKED() ;
+ } ;
+} ;
/*==============================================================================
* Functions in vty.c -- used in any of the family
*/
+extern void vty_hello (vty vty);
+
extern int vty_out (vty vty, const char* format, ...) PRINTF_ATTRIBUTE(2, 3);
+extern int vty_write(struct vty *vty, const void* buf, int n) ;
extern int vty_out_indent(vty vty, int indent) ;
extern void vty_out_clear(vty vty) ;
+extern cmd_return_code_t vty_cat_file(vty vty, qpath path, const char* desc) ;
-extern void vty_set_lines(vty vty, int lines);
+extern void vty_time_print (struct vty *, int);
-extern bool vty_close(vty vty, bool final, qstring reason) ;
+extern void vty_set_lines(vty vty, int lines);
extern void vty_open_config_write(vty vty, int fd) ;
-extern int vty_close_config_write(vty vty) ;
-
-extern void vty_log_fixed (const char *buf, size_t len);
-
-struct logline ; /* forward reference */
-struct zlog ; /* forward reference */
-
-extern void uty_log (struct logline* ll, struct zlog *zl, int priority,
- const char *format, va_list va);
+extern cmd_return_code_t vty_close_config_write(struct vty* vty, bool final) ;
#endif /* _ZEBRA_VTY_LOCAL_H */
diff --git a/lib/vty_log.c b/lib/vty_log.c
new file mode 100644
index 00000000..edb3291b
--- /dev/null
+++ b/lib/vty_log.c
@@ -0,0 +1,258 @@
+/* VTY interface to logging
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "misc.h"
+
+#include "vty_log.h"
+#include "vty_local.h"
+#include "vty_io_term.h"
+#include "log_local.h"
+#include "list_util.h"
+#include "vio_fifo.h"
+#include "mqueue.h"
+
+/*==============================================================================
+ * This supports the "vty monitor" facility -- which reflects logging
+ * information to one or more VTY_TERMINAL vty.
+ *
+ * There are a number of issues:
+ *
+ * a) output of logging information should not be held up any longer than
+ * is absolutely necessary.
+ *
+ * b) console may be busy doing other things, so logging information needs
+ * to be buffered.
+ *
+ * c) zlog() et al, hold the LOG_LOCK(), which is at a lower level than the
+ * VTY_LOCK().
+ *
+ * d) may have one or more monitor vty, possibly at different levels of
+ * message.
+ *
+ * e) must avoid logging error messages for given vty on that vty !
+ *
+ *
+ *
+ *
+ */
+static vio_fifo monitor_buffer = NULL ;
+static uint monitor_count = 0 ;
+
+static bool mon_kicked = false ;
+static mqueue_block mon_mqb = NULL ;
+
+static void vty_mon_action(mqueue_block mqb, mqb_flag_t flag) ;
+
+/*------------------------------------------------------------------------------
+ * Initialise the vty monitor facility.
+ *
+ */
+extern void
+uty_init_monitor(void)
+{
+ LOG_LOCK() ;
+
+ vio_monitor_list = NULL ;
+ monitor_buffer = NULL ;
+
+ mon_kicked = false ;
+ mon_mqb = mqb_init_new(NULL, vty_mon_action, &mon_mqb) ;
+
+ LOG_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set/Clear "monitor" state:
+ *
+ * set: if VTY_TERM and not already "monitor" (and write_open !)
+ * clear: if is "monitor"
+ */
+extern void
+uty_set_monitor(vty_io vio, bool on)
+{
+ int level ;
+ int count ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ level = 0 ;
+ count = 0 ;
+
+ LOG_LOCK() ;
+
+ if (on && !vio->monitor)
+ {
+ if (vio->vty->type == VTY_TERMINAL)
+ {
+ vio->monitor = true ;
+
+ vio->maxlvl = INT_MAX ; /* pro tem TODO */
+ vio->mon_kick = false ;
+
+ if (vio->mbuf == NULL)
+ vio->mbuf = vio_fifo_init_new(NULL, 8 * 1024) ;
+
+ sdl_push(vio_monitor_list, vio, mon_list) ;
+
+ count = +1 ;
+ } ;
+ }
+ else if (!on && vio->monitor)
+ {
+ vio->monitor = false ;
+
+ vio->maxlvl = INT_MAX ; /* pro tem TODO */
+ vio->mon_kick = false ;
+
+ sdl_del(vio_monitor_list, vio, mon_list) ;
+ count = -1 ;
+ }
+
+ if (count != 0)
+ uzlog_add_monitor(NULL, count) ;
+
+ monitor_count += count ;
+
+ LOG_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Put logging message to all suitable monitors.
+ *
+ * All we can do here is to shovel stuff into buffers and then kick the VTY
+ * to do something. If running multi-nexus, then the kick takes the form of
+ * a message sent to the cli nexus, otherwise can call the message action
+ * function here and now.
+ *
+ * NB: expect incoming line to NOT include '\n' or any other line ending.
+ */
+extern void
+vty_log(int priority, const char* line, uint len)
+{
+ vty_io vio ;
+ bool kick ;
+
+ LOG_ASSERT_LOCKED() ;
+
+ vio = vio_monitor_list ;
+ kick = false ;
+ while (vio != NULL)
+ {
+ if (priority <= vio->maxlvl)
+ {
+ vio_fifo_put_bytes(vio->mbuf, line, len) ;
+ vio_fifo_put_bytes(vio->mbuf, "\r\n", 2) ;
+
+ vio->mon_kick = kick = true ;
+ }
+ else
+ vio->mon_kick = false ;
+
+ vio = sdl_next(vio, mon_list) ;
+ } ;
+
+ if (kick)
+ {
+ if (vty_multi_nexus)
+ {
+ if (!mon_kicked)
+ {
+ mon_kicked = true ;
+ mqueue_enqueue(vty_cli_nexus->queue, mon_mqb, mqb_ordinary) ;
+ } ;
+ }
+ else
+ vty_mon_action(NULL, mqb_action) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Put logging message to all suitable monitors.
+ *
+ * All we can do here is to shovel stuff into buffers and then boot the VTY
+ * to do something.
+ *
+ * NB: expect incoming line to NOT include '\n' or any other line ending.
+ */
+static void
+vty_mon_action(mqueue_block mqb, mqb_flag_t flag)
+{
+ VTY_LOCK() ;
+ LOG_LOCK() ; /* IN THIS ORDER !!! */
+
+ mon_kicked = false ; /* If anything else happens, need to kick again */
+
+ if (flag == mqb_action)
+ {
+ vty_io vio ;
+
+ vio = vio_monitor_list ;
+ while (vio != NULL)
+ {
+ assert(vio->vout_base->vout_type == VOUT_TERM) ;
+
+ if (vio->mon_kick)
+ {
+ vio->mon_kick = false ;
+ uty_term_mon_write(vio->vout_base) ;
+ } ;
+
+ vio = sdl_next(vio, mon_list) ;
+ } ;
+ }
+ else
+ mqb_free(mqb) ; /* Suicide */
+
+ LOG_UNLOCK() ;
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Async-signal-safe version of vty_log for fixed strings.
+ *
+ * This is last gasp operation.
+ */
+extern void
+vty_log_fixed (const char *buf, uint len)
+{
+ vty_io vio ;
+
+ /* Write to all known "monitor" vty
+ *
+ * Forget all the niceties -- about to die in any case.
+ */
+ vio = sdl_head(vio_monitor_list) ;
+ while (vio != NULL)
+ {
+ write(vio_vfd_fd(vio->vout_base->vfd), buf, len) ;
+ write(vio_vfd_fd(vio->vout_base->vfd), "\r\n", 2) ;
+
+ vio = sdl_next(vio, mon_list) ;
+ } ;
+} ;
+
+
+
+
+
+
diff --git a/lib/vty_log.h b/lib/vty_log.h
new file mode 100644
index 00000000..4a0614a6
--- /dev/null
+++ b/lib/vty_log.h
@@ -0,0 +1,41 @@
+/* VTY interface to logging -- header
+ * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ *
+ * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Zebra; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef _ZEBRA_VTY_LOG_H
+#define _ZEBRA_VTY_LOG_H
+
+#include "misc.h"
+#include "vty_io.h"
+
+/*==============================================================================
+ */
+
+extern void uty_init_monitor(void) ;
+extern void uty_terminate_monitor(void) ;
+
+extern void uty_set_monitor(vty_io vio, bool on) ;
+
+extern void vty_log(int priority, const char* line, uint len) ;
+extern void vty_log_fixed(const char* buf, uint len) ;
+
+#endif /* _ZEBRA_VTY_LOG_H */
diff --git a/lib/vty_pipe.c b/lib/vty_pipe.c
index 3276085c..ad86e30c 100644
--- a/lib/vty_pipe.c
+++ b/lib/vty_pipe.c
@@ -188,2681 +188,309 @@
*
*
*/
-
-/*==============================================================================
- *
- */
-
#if 0
-
-
-
-
-
-
-
-
-
-
-
-static char* vty_host_name = NULL ;
-int vty_host_name_set = 0 ;
-
-static void uty_new_host_name(const char* name) ;
-
-/*------------------------------------------------------------------------------
- * Update vty_host_name as per "hostname" or "no hostname" command
- */
-extern void
-uty_set_host_name(const char* name)
-{
- VTY_ASSERT_LOCKED() ;
-
- vty_host_name_set = (name != NULL) ;
-
- if (vty_host_name_set)
- uty_new_host_name(name) ;
- else
- uty_check_host_name() ;
-} ;
-
-/*------------------------------------------------------------------------------
- * If vty_host_name is set, free it.
- */
-extern void
-uty_free_host_name(void)
-{
- if (vty_host_name != NULL)
- XFREE (MTYPE_HOST, vty_host_name) ; /* sets vty_host_name = NULL */
-} ;
-
-/*------------------------------------------------------------------------------
- * If the host name is not set by command, see if the actual host name has
- * changed, and if so change it.
- *
- * This is done periodically in case the actual host name changes !
- */
-extern void
-uty_check_host_name(void)
-{
- struct utsname names ;
-
- VTY_ASSERT_LOCKED() ;
-
- if (vty_host_name_set)
- return ; /* nothing to do if set by command */
-
- uname (&names) ;
-
- if ((vty_host_name == NULL) || (strcmp(vty_host_name, names.nodename) != 0))
- uty_new_host_name(names.nodename) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Set new vty_host_name and run along list of VTYs to mark the change.
- */
-static void
-uty_new_host_name(const char* name)
-{
- vty_io vio ;
-
- VTY_ASSERT_LOCKED() ;
-
- uty_free_host_name() ;
- vty_host_name = XSTRDUP(MTYPE_HOST, name) ;
-
- vio = vio_list_base ;
- while (vio != NULL)
- {
- vio->cli_prompt_set = 0 ;
- vio = sdl_next(vio, vio_list) ;
- } ;
-} ;
-
/*==============================================================================
- * General mechanism for command execution.
- *
- * Command execution is driven by select/pselect -- which means that the
- * processing of commands is multiplexed with all other activity. In the
- * following:
- *
- * -- read_ready and write_ready are events signalled by select/pselect
- *
- * -- setting read or write on, means enabling the file for select/pselect to
- * consider it for read_ready or write_ready, respectively.
- *
- * State of the CLI:
- *
- * cli_blocked -- the CLI is unable to process any further keystrokes.
- *
- * cmd_in_progress -- a command has been dispatched and has not yet
- * completed (may have been queued).
- *
- * cmd_out_enabled -- the command FIFO is may be emptied.
- *
- * This is set when a command completes, and cleared when
- * everything is written away.
- *
- * cli_more_wait -- is in "--more--" wait state
- *
- * The following are the valid combinations:
- *
- * blkd : cip : o_en : m_wt :
- * -----:------:------:------:--------------------------------------------
- * 0 : 0 : 0 : 0 : collecting a new command
- * 0 : 1 : 0 : 0 : command dispatched
- * 1 : 1 : 0 : 0 : waiting for (queued) command to complete
- * 1 : 0 : 1 : 0 : waiting for command output to complete
- * 1 : 0 : 0 : 1 : waiting for "--more--" to be written away
- * 0 : 0 : 0 : 1 : waiting for "--more--" response
- * 1 : 1 : 1 : 0 : waiting for command to complete, after the
- * CLI has been closed
- *
- * There are two output FIFOs:
- *
- * 1. for the CLI itself -- uty_cli_out and friends
- *
- * 2. for output generated by commands -- vty_out and friends.
- *
- * The CLI FIFO is emptied whenever possible, in preference to the command
- * FIFO. The command FIFO is emptied when cmd_out_enabled. While
- * cmd_in_progress is also !cmd_out_enabled -- so that all the output from a
- * given command is collected together before being sent to the file.
- *
- * Note that only sets read on when the keystroke stream is empty and has not
- * yet hit eof. The CLI process is driven mostly by write_ready -- which
- * invokes read_ready.
- *
- * Note also that after each command dispatch the CLI processor exits, to be
- * re-entered again on write_ready/read_ready -- so does one command line at
- * a time, yielding the processor after each one.
- *
- * Note that select/pselect treat a socket which is at "EOF", or has seen an
- * error, or has been half closed, etc. as readable and writable. This means
- * that the CLI will continue to move forward even after the socket is no
- * longer delivering any data.
- *
- *------------------------------------------------------------------------------
- * The "--more--" handling.
- *
- * This is largely buried in the output handling.
- *
- * While cmd_in_progress is true cmd_out_enabled will be false. When the
- * command completes:
- *
- * * cmd_in_progress is cleared
- *
- * * cmd_out_enabled is set
- *
- * * cli_blocked will be set
- *
- * * the line_control structure is reset
- *
- * * the output process is kicked off by setting write on
- *
- * The output process used the line_control structure to manage the output, and
- * occasionally enter the trivial "--more--" CLI. This is invisible to the
- * main CLI. (See the cli_more_wait flag and its handling.)
- *
- * When all the output has completed the CLI will be kicked, which will see
- * that the output buffer is now empty, and it can proceed.
- *
- * It is expected that the output will end with a newline -- so that when the
- * CLI is kicked, the cursor will be at the start of an empty line.
- *
- * This mechanism means that the command output FIFO only ever contains the
- * output from the last command executed.
- *
- * If the user decides to abandon output at the "--more--" prompt, then the
- * contents of the command output FIFO are discarded.
- *
- *------------------------------------------------------------------------------
- * The qpthreads/qpnexus extension.
- *
- * When running in qnexus mode, many commands are not executed directly in the
- * CLI, but are queued for execution by the main "routeing" nexus.
- *
- * The parsing of commands is context sensitive. The context depends may
- * change during the execution of a command. So... it is not possible to
- * dispatch a command until the previous one has completed.
- *
- * In qnexus mode, when a command is queued, the CLI does not go cli_blocked,
- * even if some output has already been generated. This allows a further
- * command to be entered while the previous one is executed. However, if the
- * command is dispatched before the previous one completes, then the cli will
- * block.
- *
- * While the previous command is executing, the current command line has a
- * minimal prompt -- to show that the context is not known. When the previous
- * command completes, the command line is redrawn in the new context.
- *
- *------------------------------------------------------------------------------
- * Command line drawn state.
- *
- * When the cli_drawn flag is set, the current console line contains the
- * current prompt and the user input to date. The cursor is positioned where
- * the user last placed it.
- *
- * The command line can be "wiped" -- see uty_cli_wipe() -- which removes all
- * output and prompt, and leaves the console at the start of an empty line
- * where the command line used to be.
- *
- * On entry to the CLI, it will draw the command line again if it has been
- * wiped.
- *
- * This mechanism is used to support the partial command line that may be
- * entered while a queued command executes.
- *
- * It is also used for the command help/completion system.
- *
- * It is also used to support the "monitor" output.
- */
-
-/*==============================================================================
- * The CLI
- */
-
-#define CONTROL(X) ((X) - '@')
-
-static void uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret) ;
-static enum vty_readiness uty_cli_standard(vty_io vio) ;
-static enum vty_readiness uty_cli_more_wait(vty_io vio) ;
-static void uty_cli_draw(vty_io vio) ;
-static void uty_cli_draw_this(vty_io vio, enum node_type node) ;
-static void uty_cli_wipe(vty_io vio, int len) ;
-
-static void uty_will_echo (vty_io vio) ;
-static void uty_will_suppress_go_ahead (vty_io vio) ;
-static void uty_dont_linemode (vty_io vio) ;
-static void uty_do_window_size (vty_io vio) ;
-static void uty_dont_lflow_ahead (vty_io vio) ;
-
-/*------------------------------------------------------------------------------
- * Initialise CLI.
- *
- * It is assumed that the following have been initialised, empty or zero:
*
- * cli_prompt_for_node
- * cl
- * clx
- * cli_vbuf
- * cli_obuf
- *
- * cli_drawn
- * cli_dirty
- *
- * cli_prompt_set
- *
- * cli_blocked
- * cmd_in_progress
- * cmd_out_enabled
- * cli_wait_more
- *
- * cli_more_enabled
- *
- * Sets the CLI such that there is apparently a command in progress, so that
- * further initialisation (in particular hello messages and the like) is
- * treated as a "start up command".
- *
- * Sends a suitable set of Telnet commands to start the process.
*/
-extern void
-uty_cli_init(vty_io vio)
-{
- assert(vio->type == VTY_TERM) ;
-
- vio->cmd_in_progress = 1 ;
- vio->cli_blocked = 1 ;
-
- vio->cli_do = cli_do_nothing ;
-
- /* Setting up terminal. */
- uty_will_echo (vio);
- uty_will_suppress_go_ahead (vio);
- uty_dont_linemode (vio);
- uty_do_window_size (vio);
- if (0)
- uty_dont_lflow_ahead (vio) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Start the CLI.
- *
- * All start-up operations are complete -- so the "command" is now complete.
- *
- * Returns: write_ready -- so the first event is a write event, to flush
- * any output to date.
- */
-extern enum vty_readiness
-uty_cli_start(vty_io vio)
-{
- uty_cli_cmd_complete(vio, CMD_SUCCESS) ;
- return write_ready ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Close the CLI
- *
- * Note that if any command is revoked, then will clear cmd_in_progress and
- * set cmd_out_enabled -- so any output can now clear.
- */
-extern void
-uty_cli_close(vty_io vio)
-{
- VTY_ASSERT_LOCKED() ;
- assert(vio->type == VTY_TERM) ;
-
- cq_revoke(vio->vty) ;
-
- vio->cli_blocked = 1 ; /* don't attempt any more */
- vio->cmd_out_enabled = 1 ; /* allow output to clear */
-} ;
-/*------------------------------------------------------------------------------
- * CLI for VTY_TERM
- *
- * Do nothing at all if half closed.
- *
- * Otherwise do: standard CLI
- * or: "--more--" CLI
- *
- * NB: on return, requires that an attempt is made to write away anything that
- * may be ready for that.
- */
-extern enum vty_readiness
-uty_cli(vty_io vio)
-{
- VTY_ASSERT_LOCKED() ;
- assert(vio->type == VTY_TERM) ;
+#include "misc.h"
- if (vio->half_closed)
- return not_ready ; /* Nothing more if half closed */
+#include "command_local.h"
- /* Standard or "--more--" CLI ? */
- if (vio->cli_more_wait)
- return uty_cli_more_wait(vio) ;
- else
- return uty_cli_standard(vio) ;
-} ;
+#include "vty_io_pipe.h"
+#include "vty_io_file.h"
+#include "vty_io_basic.h"
/*==============================================================================
- * The Standard CLI
- */
-
-static enum cli_do uty_cli_process(vty_io vio, enum node_type node) ;
-static void uty_cli_response(vty_io vio, enum cli_do cli_do) ;
-static bool uty_cli_dispatch(vty_io vio) ;
-
-/*------------------------------------------------------------------------------
- * Standard CLI for VTY_TERM -- if not blocked, runs until:
- *
- * * runs out of keystrokes
- * * executes a command
- *
- * Note that this executes at most one command each time it is called. This
- * is to allow for a modicum of sharing of the system. For real keyboard input
- * this will make no difference at all !
- *
- * Returns: not_ready blocked and was blocked when entered
- * write_ready if there is anything in the keystroke stream
- * read_ready otherwise
- */
-static enum vty_readiness
-uty_cli_standard(vty_io vio)
-{
- VTY_ASSERT_LOCKED() ;
- assert(vio->type == VTY_TERM) ;
-
- /* cli_blocked is set when is waiting for a command, or its output to
- * complete -- unless either of those has happened, is still blocked.
- *
- * NB: in both these cases, assumes that other forces are at work to
- * keep things moving.
- */
- if (vio->cli_blocked)
- {
- assert(vio->cmd_in_progress || vio->cmd_out_enabled) ;
-
- if (vio->cmd_in_progress)
- {
- assert(!vio->cmd_out_enabled) ;
- return not_ready ;
- } ;
-
- if (!vio_fifo_empty(&vio->cmd_obuf))
- return not_ready ;
-
- vio->cli_blocked = 0 ;
- vio->cmd_out_enabled = 0 ;
- } ;
-
- /* If there is nothing pending, then can run the CLI until there is
- * something to do, or runs out of input.
- *
- * If there is something to do, that is because a previous command has
- * now completed, which may have wiped the pending command or changed
- * the required prompt.
- */
- if (vio->cli_do == cli_do_nothing)
- vio->cli_do = uty_cli_process(vio, vio->vty->node) ;
- else
- uty_cli_draw_this(vio, vio->vty->node) ;
-
- /* If have something to do, do it. */
- if (vio->cli_do != cli_do_nothing)
- {
- /* Reflect immediate response */
- uty_cli_response(vio, vio->cli_do) ;
-
- /* If command not already in progress, dispatch this one, which may
- * set the CLI blocked.
- *
- * Otherwise is now blocked until queued command completes.
- */
- if (!vio->cmd_in_progress)
- vio->cli_blocked = uty_cli_dispatch(vio) ;
- else
- vio->cli_blocked = 1 ;
- } ;
-
- /* Use write_ready as a proxy for read_ready on the keystroke stream.
- *
- * Also, if the command line is not drawn, then return write_ready, so
- * that
- *
- * Note that if has just gone cli_blocked, still returns ready. This is
- * defensive: at worst will generate one unnecessary read_ready/write_ready
- * event.
- */
- if (keystroke_stream_empty(vio->key_stream))
- return read_ready ;
- else
- return write_ready ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Dispatch the current vio->cli_do -- queueing it if necessary.
- *
- * Requires that are NOT blocked and NO command is queued.
- *
- * Expects to be on new blank line, and when returns will be on new, blank
- * line.
- *
- * Returns: true <=> command completed and output is pending
- * false => command has been queued and is now in progress
- *
- * Generally sets vio->cl_do = cli_do_nothing and clears vio->cl to empty.
- *
- * Can set vio->cl_do = and vio->cl to be a follow-on command.
- */
-static bool
-uty_cli_dispatch(vty_io vio)
-{
- qstring_t tmp ;
- enum cli_do cli_do ;
- enum cmd_return_code ret ;
-
- struct vty* vty = vio->vty ;
-
- VTY_ASSERT_LOCKED() ;
- assert(!vio->cli_blocked && !vio->cmd_in_progress) ;
-
- /* Set vio->clx to the command about to execute.
- *
- * Clear vio->cl and vio->cl_do.
- */
- vio->cmd_in_progress = 1 ; /* => vty->buf is valid */
- vio->cmd_out_enabled = 0 ; /* => collect all output */
-
- tmp = vio->clx ; /* swap clx and cl */
- vio->clx = vio->cl ;
- vio->cl = tmp ;
-
- qs_term(&vio->clx) ; /* ensure string is terminated */
- vty->buf = qs_chars(&vio->clx) ; /* terminated command line */
- cli_do = vio->cli_do ; /* current operation */
-
- vio->cli_do = cli_do_nothing ; /* clear */
- qs_clear(vio->cl) ; /* set cl empty (with '\0') */
-
- /* Reset the command output FIFO and line_control */
- assert(vio_fifo_empty(&vio->cmd_obuf)) ;
- uty_out_clear(vio) ; /* clears FIFO and line control */
-
- /* Dispatch command */
- if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE))
- {
- /* AUTH_NODE and AUTH_ENABLE_NODE are unique */
- ret = uty_auth(vty, vty->buf, cli_do) ;
- }
- else
- {
- /* All other nodes... */
- switch (cli_do)
- {
- case cli_do_nothing:
- ret = CMD_SUCCESS ;
- break ;
-
- case cli_do_command:
- ret = uty_dispatch_command(vty) ;
- break ;
-
- case cli_do_ctrl_c:
- ret = uty_stop_input(vty) ;
- break ;
-
- case cli_do_ctrl_d:
- ret = uty_down_level(vty) ;
- break ;
-
- case cli_do_ctrl_z:
- ret = uty_command(vty) ;
- if (ret == CMD_QUEUED)
- vio->cli_do = cli_do_ctrl_z ; /* defer the ^Z action */
- else
- ret = uty_end_config(vty) ; /* do the ^Z now */
- break ;
-
- case cli_do_eof:
- ret = uty_cmd_close(vio->vty, "End") ;
- break ;
-
- default:
- zabort("unknown cli_do_xxx value") ;
- } ;
- } ;
-
- if (ret == CMD_QUEUED)
- {
- uty_cli_draw(vio) ; /* draw the prompt */
- return false ; /* command not complete */
- }
- else
- {
- uty_cli_cmd_complete(vio, ret) ;
- return true ; /* command complete */
- } ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Queued command has completed.
- *
- * Note that sets write on whether there is anything in the output buffer
- * or not... write_ready will kick read_ready.
- */
-extern void
-vty_queued_result(struct vty *vty, enum cmd_return_code ret)
-{
- vty_io vio ;
-
- VTY_LOCK() ;
-
- vio = vty->vio ;
-
- if (!vio->closed)
- {
- uty_cli_wipe(vio, 0) ; /* wipe any partly constructed line */
-
- /* Do the command completion actions that were deferred because the
- * command was queued.
- *
- * Return of CMD_QUEUED => command was revoked before being executed.
- * However interesting that might be... frankly don't care.
- */
- uty_cli_cmd_complete(vio, ret) ;
-
- /* Kick the socket -- to write away any outstanding output, and
- * re-enter the CLI when that's done.
- */
- uty_sock_set_readiness(&vio->sock, write_ready) ;
- }
- else
- {
- /* If the VTY is closed, the only reason it still exists is because
- * there was cmd_in_progress.
- */
- vio->cmd_in_progress = 0 ;
-
- uty_close(vio) ; /* Final close */
- } ;
-
- VTY_UNLOCK() ;
-}
-
-/*------------------------------------------------------------------------------
- * Command has completed, so:
- *
- * * clear cmd_in_progress
- * * set cmd_out_enabled -- so any output can now proceed
- * * set cli_blocked -- waiting for output to complete
- * * and prepare the line control for output
- *
- * If the return is CMD_CLOSE, then also now does the required half close.
- *
- * Note that apart from CMD_CLOSE, don't really care what the return was. Any
- * diagnostics or other action must be dealt with elsewhere (as part of the
- * command execution.
- *
- * Note that everything proceeds as if there is some output. So after every
- * command goes through at least one write_ready event.
+ * VTY File Output
*
- * This ensures some multiplexing at the command level.
+ * This is for input and output of configuration files and piped stuff.
*
- * It also means that the decision about whether there is anything to output
- * is left to the output code.
+ * When reading the configuration (and piped stuff in the configuration) I/O
+ * is blocking... nothing else can run while this is going on. Otherwise,
+ * all I/O is non-blocking.
*/
-static void
-uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret)
-{
- VTY_ASSERT_LOCKED() ;
-
- assert(vio->cmd_in_progress && !vio->cmd_out_enabled) ;
- if (ret == CMD_CLOSE)
- uty_half_close(vio, NULL) ;
- vio->cmd_in_progress = 0 ; /* command complete */
- vio->cmd_out_enabled = 1 ; /* enable the output */
- vio->cli_blocked = 1 ; /* now blocked waiting for output */
-
- vio->vty->buf = NULL ; /* finished with command line */
-
- uty_cmd_output_start(vio) ; /* reset line control (belt & braces) */
-} ;
/*==============================================================================
- * The "--more--" CLI
- *
- * While command output is being cleared from its FIFO, the CLI is cli_blocked.
- *
- * When the output side signals that "--more--" is required, it sets the
- * cli_more_wait flag and clears the cmd_out_enabled flag.
- *
- * The first stage of handling "--more--" is to suck the input dry, so that
- * (as far as is reasonably possible) does not steal a keystroke as the
- * "--more--" response which was typed before the prompt was issued.
- *
- * The cli_blocked flag indicates that the CLI is in this first stage.
- */
-
-/*------------------------------------------------------------------------------
- * Change the CLI to the "--more--" CLI.
- *
- * Outputs the new prompt line.
+ * Prototypes.
*/
-extern void
-uty_cli_enter_more_wait(vty_io vio)
-{
- VTY_ASSERT_LOCKED() ;
-
- assert(vio->cli_blocked && vio->cmd_out_enabled && !vio->cli_more_wait) ;
-
- uty_cli_wipe(vio, 0) ; /* make absolutely sure that command line is
- wiped before change the CLI state */
-
- vio->cmd_out_enabled = 0 ; /* stop output pro tem */
- vio->cli_more_wait = 1 ; /* new state */
-
- uty_cli_draw(vio) ; /* draw the "--more--" */
-} ;
/*------------------------------------------------------------------------------
- * Exit the "--more--" CLI.
*
- * Wipes the "--more--" prompt.
*
- * This is used when the user responds to the prompt.
- *
- * It is also used when the vty is "half-closed". In this case, it is (just)
- * possible that the '--more--' prompt is yet to be completely written away,
- * so:
- *
- * * assert that is either: !vio->cli_blocked (most of the time it will)
- * or: !vio_fifo_empty(&vio->cli_obuf)
- *
- * * note that can wipe the prompt even though it hasn't been fully
- * written away yet. (The effect is to append the wipe action to the
- * cli_obuf !)
*/
-extern void
-uty_cli_exit_more_wait(vty_io vio)
+extern cmd_return_code_t
+uty_file_read_open(vty_io vio, qstring name, bool reflect)
{
- VTY_ASSERT_LOCKED() ;
+ const char* name_str ;
+ int fd ;
+ vio_vf vf ;
+ vfd_io_type_t iot ;
- assert( (!vio->cli_blocked || !vio_fifo_empty(&vio->cli_obuf))
- && !vio->cmd_out_enabled && vio->cli_more_wait) ;
+ name_str = qs_make_string(name) ;
- uty_cli_wipe(vio, 0) ; /* wipe the prompt ('--more--')
- before changing the CLI state */
+ iot = vfd_io_read | vfd_io_blocking ; /* TODO blocking */
- vio->cli_blocked = 1 ; /* back to blocked waiting for output */
- vio->cli_more_wait = 0 ; /* exit more_wait */
- vio->cmd_out_enabled = 1 ; /* re-enable output */
-} ;
+ /* Do the basic file open. */
+ fd = uty_vfd_file_open(name_str, iot) ;
-/*------------------------------------------------------------------------------
- * Handle the "--more--" state.
- *
- * Deals with the first stage if cli_blocked.
- *
- * Tries to steal a keystroke, and when succeeds wipes the "--more--"
- * prompt and exits cli_more_wait -- and may cancel all outstanding output.
- *
- * EOF on input causes immediate exit from cli_more_state.
- *
- * Returns: read_ready -- waiting to steal a keystroke
- * now_ready -- just left cli_more_wait
- * not_ready -- otherwise
- */
-static enum vty_readiness
-uty_cli_more_wait(vty_io vio)
-{
- struct keystroke steal ;
-
- VTY_ASSERT_LOCKED() ;
-
- /* Deal with the first stage of "--more--" */
- if (vio->cli_blocked)
+ if (fd < 0)
{
- int get ;
-
- /* If the CLI buffer is not yet empty, then is waiting for the
- * initial prompt to clear, so nothing to be done here.
- */
- if (!vio_fifo_empty(&vio->cli_obuf))
- return not_ready ;
-
- vio->cli_blocked = 0 ;
-
- /* empty the input buffer into the keystroke stream */
- do
- {
- get = uty_read(vio, NULL) ;
- } while (get > 0) ;
- } ;
+ uty_out(vio, "%% Could not open input file %s\n", name_str) ;
- /* Go through the "--more--" process, unless no longer write_open (!) */
- if (vio->sock.write_open)
- {
- /* The read fetches a reasonable lump from the I/O -- so if there
- * is a complete keystroke available, expect to get it.
- *
- * If no complete keystroke available to steal, returns ks_null.
- *
- * If has hit EOF (or error etc), returns knull_eof.
- */
- uty_read(vio, &steal) ;
-
- /* If nothing stolen, make sure prompt is drawn and wait for more
- * input.
- */
- if ((steal.type == ks_null) && (steal.value != knull_eof))
- {
- if (uty_cli_draw_if_required(vio)) /* "--more--" if req. */
- return write_ready ;
- else
- return read_ready ;
- } ;
-
- /* Stolen a keystroke -- a (very) few terminate all output */
- if (steal.type == ks_char)
- {
- switch (steal.value)
- {
- case CONTROL('C'):
- case 'q':
- case 'Q':
- uty_out_clear(vio) ;
- break;
-
- default: /* everything else, thrown away */
- break ;
- } ;
- } ;
- } ;
-
- /* End of "--more--" process
- *
- * Wipe out the prompt and update state.
- *
- * Return write_ready to tidy up the screen and, unless cleared, write
- * some more.
- */
- uty_cli_exit_more_wait(vio) ;
-
- return now_ready ;
-} ;
-
-/*==============================================================================
- * CLI VTY output
- *
- * This is buffered separately from the general (command) VTY output above.
- *
- * Has a dedicated buffer in the struct vty, which is flushed regularly during
- * command processing.
- *
- * It is expected that can flush straight to the file, since this is running at
- * CLI speed. However, if the CLI is being driven by something other than a
- * keyboard, or "monitor" output has filled the buffers, then may need to
- * have intermediate buffering.
- *
- * No actual I/O takes place here-- all "output" is to vio->cli_obuf
- * and/or vio->cli_ex_obuf
- *
- * The "cli_echo" functions discard the output if vio->cli_echo_suppress != 0.
- * This is used while passwords are entered and to allow command line changes
- * to be made while the line is not visible.
- */
-
-enum { cli_rep_count = 32 } ;
-
-typedef const char cli_rep_char[cli_rep_count] ;
-
-static const char telnet_backspaces[] =
- { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
- 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
- 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
- 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08
- } ;
-CONFIRM(sizeof(telnet_backspaces) == sizeof(cli_rep_char)) ;
-
-static const char telnet_spaces[] =
- { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
- ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
- ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
- ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '
- } ;
-CONFIRM(sizeof(telnet_spaces) == sizeof(cli_rep_char)) ;
+ return CMD_WARNING ;
+ }
-static const char* telnet_newline = "\r\n" ;
+ /* OK -- now push the new input onto the vin_stack. */
+ vf = uty_vf_new(vio, name_str, fd, vfd_file, iot) ;
+ uty_vin_push(vio, vf, VIN_FILE, NULL, NULL, 16 * 1024) ;
-static void uty_cli_write(vty_io vio, const char *this, int len) ;
-static void uty_cli_write_n(vty_io vio, cli_rep_char chars, int n) ;
+ vf->parse_type = cmd_parse_strict ;
+ vf->reflect_enabled = reflect ;
-/*------------------------------------------------------------------------------
- * CLI VTY output -- cf fprintf()
- */
-static void
-uty_cli_out(vty_io vio, const char *format, ...)
-{
- VTY_ASSERT_LOCKED() ;
-
- if (vio->sock.write_open)
- {
- va_list args ;
-
- va_start (args, format);
- vio_fifo_vprintf(&vio->cli_obuf, format, args) ;
- va_end(args);
- } ;
+ return CMD_SUCCESS ;
} ;
/*------------------------------------------------------------------------------
- * CLI VTY output -- echo user input
*
- * Do nothing if echo suppressed (eg in AUTH_NODE) or not write_open
- */
-static void
-uty_cli_echo(vty_io vio, const char *this, size_t len)
-{
- VTY_ASSERT_LOCKED() ;
-
- if (vio->cli_echo_suppress || !vio->sock.write_open)
- return ;
-
- uty_cli_write(vio, this, len) ;
-}
-
-/*------------------------------------------------------------------------------
- * CLI VTY output -- echo 'n' characters using a cli_rep_char string
*
- * Do nothing if echo suppressed (eg in AUTH_NODE)
*/
-static void
-uty_cli_echo_n(vty_io vio, cli_rep_char chars, int n)
+extern cmd_return_code_t
+uty_file_write_open(vty_io vio, qstring name, bool append)
{
- VTY_ASSERT_LOCKED() ;
+ const char* name_str ;
+ int fd ;
+ vio_vf vf ;
+ vfd_io_type_t iot ;
- if (vio->cli_echo_suppress || !vio->sock.write_open)
- return ;
+ iot = vfd_io_write | vfd_io_blocking ; /* TODO blocking */
- uty_cli_write_n(vio, chars, n) ;
-}
+ if (append)
+ iot |= vfd_io_append ;
-/*------------------------------------------------------------------------------
- * CLI VTY output -- cf write()
- */
-inline static void
-uty_cli_write(vty_io vio, const char *this, int len)
-{
- VTY_ASSERT_LOCKED() ;
+ name_str = qs_make_string(name) ;
- if (vio->sock.write_open)
- vio_fifo_put(&vio->cli_obuf, this, len) ;
-} ;
+ /* Do the basic file open. */
+ fd = uty_vfd_file_open(name_str, iot) ;
-/*------------------------------------------------------------------------------
- * CLI VTY output -- write 'n' characters using a cli_rep_char string
- */
-static void
-uty_cli_write_n(vty_io vio, cli_rep_char chars, int n)
-{
- int len ;
-
- len = sizeof(cli_rep_char) ;
- while (n > 0)
+ if (fd < 0)
{
- if (n < len)
- len = n ;
- uty_cli_write(vio, chars, len) ;
- n -= len ;
- } ;
-} ;
+ uty_out(vio, "%% Could not open output file %s\n", name_str) ;
-/*------------------------------------------------------------------------------
- * CLI VTY output -- write given string
- *
- * Returns length of string.
- */
-static int
-uty_cli_write_s(vty_io vio, const char *str)
-{
- int len ;
+ return CMD_WARNING ;
+ }
- len = strlen(str) ;
- if (len != 0)
- uty_cli_write(vio, str, len) ;
+ /* OK -- now push the new input onto the vin_stack. */
+ vf = uty_vf_new(vio, name_str, fd, vfd_file, iot) ;
+ uty_vout_push(vio, vf, VOUT_FILE, NULL, NULL, 16 * 1024) ;
- return len ;
+ return CMD_SUCCESS ;
} ;
/*==============================================================================
- * Standard Messages
- */
-
-/*------------------------------------------------------------------------------
- * Send newline to the console.
+ * Command line fetch from a file or pipe.
*
- * Clears the cli_drawn and the cli_dirty flags.
+ * Returns: CMD_SUCCESS -- have another command line ready to go
+ * CMD_WAITING -- do not have a command line at the moment
+ * CMD_EOF -- ran into EOF
+ * CMD_IO_ERROR -- ran into an I/O error
*/
-static void
-uty_cli_out_newline(vty_io vio)
+extern cmd_return_code_t
+uty_file_fetch_command_line(vio_vf vf, qstring* line)
{
- uty_cli_write(vio, telnet_newline, 2) ;
- vio->cli_drawn = 0 ;
- vio->cli_dirty = 0 ;
-} ;
+ assert(vf->vin_state == vf_open) ;
-/*------------------------------------------------------------------------------
- * Wipe 'n' characters.
- *
- * If 'n' < 0, wipes characters backwards and moves cursor back.
- * 'n' > 0, wipes characters forwards, leaving cursor where it is
- */
-static void
-uty_cli_out_wipe_n(vty_io vio, int n)
-{
- if (n < 0)
+ if (vf->line_complete)
{
- n = abs(n) ;
- uty_cli_write_n(vio, telnet_backspaces, n);
- } ;
-
- if (n > 0)
- {
- uty_cli_write_n(vio, telnet_spaces, n) ;
- uty_cli_write_n(vio, telnet_backspaces, n) ;
- } ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Send response to the given cli_do
- *
- * If no command is in progress, then will send newline to signal that the
- * command is about to be dispatched.
- *
- * If command is in progress, then leaves cursor on '^' to signal that is now
- * waiting for previous command to complete.
- */
-static const char* cli_response [2][cli_do_count] =
-{
- { /* when not waiting for previous command to complete */
- [cli_do_command] = "",
- [cli_do_ctrl_c] = "^C",
- [cli_do_ctrl_d] = "^D",
- [cli_do_ctrl_z] = "^Z",
- [cli_do_eof] = "^*"
- },
- { /* when waiting for a previous command to complete */
- [cli_do_command] = "^",
- [cli_do_ctrl_c] = "^C",
- [cli_do_ctrl_d] = "^D",
- [cli_do_ctrl_z] = "^Z",
- [cli_do_eof] = "^*"
- }
-} ;
-
-static void
-uty_cli_response(vty_io vio, enum cli_do cli_do)
-{
- const char* str ;
- int len ;
-
- if ((cli_do == cli_do_nothing) || (vio->half_closed))
- return ;
+ vio_fifo_set_hold_mark(vf->ibuf) ; /* advance hold */
- str = (cli_do < cli_do_count)
- ? cli_response[vio->cmd_in_progress ? 1 : 0][cli_do] : NULL ;
- assert(str != NULL) ;
+ vf->line_complete = false ;
+ vf->line_number += vf->line_step ;
- len = uty_cli_write_s(vio, str) ;
-
- if (vio->cmd_in_progress)
- {
- vio->cli_extra_len = len ;
- uty_cli_write_n(vio, telnet_backspaces, len) ;
- }
- else
- {
- uty_cli_out_newline(vio) ;
+ qs_set_len_nn(vf->cl, 0) ;
+ vf->line_step = 0 ;
} ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Send various messages with trailing newline.
- */
-static void
-uty_cli_out_CMD_ERR_AMBIGUOUS(vty_io vio)
-{
- uty_cli_write_s(vio, "% " MSG_CMD_ERR_AMBIGUOUS ".") ;
- uty_cli_out_newline(vio) ;
-} ;
-
-static void
-uty_cli_out_CMD_ERR_NO_MATCH(vty_io vio)
-{
- uty_cli_write_s(vio, "% " MSG_CMD_ERR_NO_MATCH ".") ;
- uty_cli_out_newline(vio) ;
-} ;
-
-/*==============================================================================
- * Command line draw and wipe
- */
-
-/*------------------------------------------------------------------------------
- * Wipe the current console line -- if any.
- */
-static void
-uty_cli_wipe(vty_io vio, int len)
-{
- int a ;
- int b ;
-
- if (!vio->cli_drawn)
- return ; /* quit if already wiped */
-
- assert(vio->cl.cp <= vio->cl.len) ;
-
- /* Establish how much ahead and how much behind the cursor */
- a = vio->cli_extra_len ;
- b = vio->cli_prompt_len ;
-
- if (!vio->cli_echo_suppress && !vio->cli_more_wait)
- {
- a += vio->cl.len - vio->cl.cp ;
- b += vio->cl.cp ;
- } ;
-
- /* Wipe anything ahead of the current position and ahead of new len */
- if ((a + b) > len)
- uty_cli_out_wipe_n(vio, +a) ;
-
- /* Wipe anything behind current position, but ahead of new len */
- if (b > len)
+ while (1)
{
- uty_cli_out_wipe_n(vio, -(b - len)) ;
- b = len ; /* moved the cursor back */
- } ;
+ char* s, * p, * q, * e ;
+ size_t have ;
+ ulen len ;
- /* Back to the beginning of the line */
- uty_cli_write_n(vio, telnet_backspaces, b) ;
+ s = vio_fifo_get(vf->ibuf, &have) ;
- /* Nothing there any more */
- vio->cli_drawn = 0 ;
- vio->cli_dirty = 0 ;
-} ;
+ /* If nothing in hand, try and get some more.
+ *
+ * Either exits or loops back to set s & have.
+ */
+ if (have == 0)
+ {
+ int get ;
-/*------------------------------------------------------------------------------
- * If not currently drawn, draw prompt etc according to the current state
- * and node.
- *
- * See uty_cli_draw().
- */
-extern bool
-uty_cli_draw_if_required(vty_io vio)
-{
- if (vio->cli_drawn)
- return false ;
+ get = vio_fifo_read_nb(vf->ibuf, vio_vfd_fd(vf->vfd), 100) ;
- uty_cli_draw(vio) ;
+ if (get > 0)
+ continue ; /* loop back */
- return true ;
-} ;
+ if (get == 0)
+ return CMD_WAITING ; /* need to set read ready ! */
-/*------------------------------------------------------------------------------
- * Draw prompt etc for the current vty node.
- *
- * See uty_cli_draw_this()
- */
-static void
-uty_cli_draw(vty_io vio)
-{
- uty_cli_draw_this(vio, vio->vty->node) ;
-} ;
+ if (get == -1)
+ ; /* register error */
-/*------------------------------------------------------------------------------
- * Draw prompt and entire command line, leaving current position where it
- * should be.
- *
- * If command line is currently drawn, this wipes and redraws.
- *
- * Otherwise, assumes is positioned at start of an empty line.
- *
- * Draws prompt according to the given 'node', except:
- *
- * * if is half_closed, draw nothing -- wipes the current line
- *
- * * if is cli_more_wait, draw the "--more--" prompt
- *
- * * if is cmd_in_progress, draw the vestigial prompt.
- *
- * By the time the current command completes, the node may have changed, so
- * the current prompt may be invalid.
- *
- * Sets: cli_drawn = true
- * cli_dirty = false
- * cli_prompt_len = length of prompt used
- * cli_extra_len = 0
- * cli_echo_suppress = (AUTH_NODE or AUTH_ENABLE_NODE)
- */
-static void
-uty_cli_draw_this(vty_io vio, enum node_type node)
-{
- const char* prompt ;
- size_t l_len ;
- int p_len ;
-
- if (vio->cli_dirty)
- uty_cli_out_newline(vio) ; /* clears cli_dirty and cli_drawn */
+ if (get == -2)
+ return (qs_len_nn(vf->cl) > 0) ? CMD_SUCCESS : CMD_EOF ;
+ } ;
- /* Sort out what the prompt is. */
- if (vio->half_closed)
- {
- prompt = "" ;
- p_len = 0 ;
- l_len = 0 ;
- }
- else if (vio->cli_more_wait)
- {
- prompt = "--more--" ;
- p_len = strlen(prompt) ;
- l_len = 0 ;
- }
- else if (vio->cmd_in_progress)
- {
- /* If there is a queued command, the prompt is a minimal affair. */
- prompt = "~ " ;
- p_len = strlen(prompt) ;
- l_len = vio->cl.len ;
- }
- else
- {
- /* The prompt depends on the node, and is expected to include the
- * host name.
+ /* Try to find a '\n' -- converting all other control chars to ' '
*
- * Caches the prompt so doesn't re-evaluate it every time.
+ * When we find '\n' step back across any trailing ' ' (which includes
+ * any control chars before the '\n').
*
- * If the host name changes, the cli_prompt_set flag is cleared.
+ * This means that we cope with "\r\n" line terminators. But not anything
+ * more exotic.
*/
- if (!vio->cli_prompt_set || (node != vio->cli_prompt_node))
- {
- const char* prompt ;
-
- if (vty_host_name == NULL)
- uty_check_host_name() ; /* should never be required */
+ p = s ;
+ e = s + have ; /* have != 0 */
+ q = NULL ;
- prompt = cmd_prompt(node) ;
- if (prompt == NULL)
+ while (p < e)
+ {
+ if (*p++ < 0x20)
{
- zlog_err("vty %s has node %d", uty_get_name(vio), node) ;
- prompt = "%s ???: " ;
- } ;
+ if (*(p-1) != '\n')
+ {
+ *(p-1) = ' ' ; /* everything other than '\n' */
+ continue ;
+ } ;
- qs_printf(vio->cli_prompt_for_node, prompt, vty_host_name);
+ ++vf->line_step ; /* got a '\n' */
- vio->cli_prompt_node = node ;
- vio->cli_prompt_set = 1 ;
+ q = p ; /* point just past '\n' */
+ do --q ; while ((q > s) && (*(q-1) == ' ')) ;
+ /* discard trailing "spaces" */
+ break ;
+ } ;
} ;
- prompt = vio->cli_prompt_for_node.body ;
- p_len = vio->cli_prompt_for_node.len ;
- l_len = vio->cl.len ;
- } ;
-
- /* Now, if line is currently drawn, time to wipe it */
- if (vio->cli_drawn)
- uty_cli_wipe(vio, p_len + l_len) ;
-
- /* Set about writing the prompt and the line */
- vio->cli_drawn = 1 ;
- vio->cli_extra_len = 0 ;
- vio->cli_echo_suppress = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ;
-
- vio->cli_prompt_len = p_len ;
-
- uty_cli_write(vio, prompt, p_len) ;
-
- if (l_len != 0)
- {
- uty_cli_write(vio, qs_chars(&vio->cl), l_len) ;
- if (vio->cl.cp < l_len)
- uty_cli_write_n(vio, telnet_backspaces, l_len - vio->cl.cp) ;
- } ;
-} ;
-
-/*==============================================================================
- * Monitor output.
- *
- * To prepare for monitor output, wipe as much as is necessary for the
- * monitor line to appear correctly.
- *
- * After monitor output, may need to do two things:
- *
- * * if the output was incomplete, place the rump in the CLI buffer,
- * so that:
- *
- * a. don't mess up the console with partial lines
- *
- * b. don't lose part of a message
- *
- * c. act as a brake on further monitor output -- cannot do any more
- * until the last, part, line is dealt with.
- *
- * * restore the command line, unless it is empty !
- */
-
- /*-----------------------------------------------------------------------------
- * Prepare for new monitor output line.
- *
- * Wipe any existing command line.
- */
-extern void
-uty_cli_pre_monitor(vty_io vio, size_t len)
-{
- VTY_ASSERT_LOCKED() ;
-
- uty_cli_wipe(vio, len) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Recover from monitor line output.
- *
- * If monitor line failed to complete, append the rump to the CLI buffer.
- *
- * If have a non-empty command line, or is cli_more_wait, redraw the command
- * line.
- *
- * Returns: 0 => rump was empty and no command line stuff written
- * > 0 => rump not empty or some command line stuff written
- */
-extern int
-uty_cli_post_monitor(vty_io vio, const char* buf, size_t len)
-{
- VTY_ASSERT_LOCKED() ;
-
- if (len != 0)
- uty_cli_write(vio, buf, len) ;
-
- if (vio->cli_more_wait || (vio->cl.len != 0))
- {
- uty_cli_draw(vio) ;
- ++len ;
- } ;
-
- return len ;
-} ;
-
-/*==============================================================================
- * Command line processing loop
- */
-
-static bool uty_telnet_command(vty_io vio, keystroke stroke, bool callback) ;
-static int uty_cli_insert (vty_io vio, const char* chars, int n) ;
-static int uty_cli_overwrite (vty_io vio, char* chars, int n) ;
-static int uty_cli_word_overwrite (vty_io vio, char *str) ;
-static int uty_cli_forwards(vty_io vio, int n) ;
-static int uty_cli_backwards(vty_io vio, int n) ;
-static int uty_cli_del_forwards(vty_io vio, int n) ;
-static int uty_cli_del_backwards(vty_io vio, int n) ;
-static int uty_cli_bol (vty_io vio) ;
-static int uty_cli_eol (vty_io vio) ;
-static int uty_cli_word_forwards_delta(vty_io vio) ;
-static int uty_cli_word_forwards(vty_io vio) ;
-static int uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) ;
-static int uty_cli_word_backwards_pure (vty_io vio) ;
-static int uty_cli_word_backwards (vty_io vio) ;
-static int uty_cli_del_word_forwards(vty_io vio) ;
-static int uty_cli_del_word_backwards(vty_io vio) ;
-static int uty_cli_del_to_eol (vty_io vio) ;
-static int uty_cli_clear_line(vty_io vio) ;
-static int uty_cli_transpose_chars(vty_io vio) ;
-static void uty_cli_history_use(vty_io vio, int step) ;
-static void uty_cli_next_line(vty_io vio) ;
-static void uty_cli_previous_line (vty_io vio) ;
-static void uty_cli_complete_command (vty_io vio, enum node_type node) ;
-static void uty_cli_describe_command (vty_io vio, enum node_type node) ;
-
-/*------------------------------------------------------------------------------
- * Fetch next keystroke, reading from the file if required.
- */
-static inline bool
-uty_cli_get_keystroke(vty_io vio, keystroke stroke)
-{
- if (keystroke_get(vio->key_stream, stroke))
- return 1 ;
-
- uty_read(vio, NULL) ; /* not stealing */
-
- return keystroke_get(vio->key_stream, stroke) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Process keystrokes until run out of input, or get something to cli_do.
- *
- * If required, draw the prompt and command line.
- *
- * Process keystrokes until run out of stuff to do, or have a "command line"
- * that must now be executed.
- *
- * Processes the contents of the keystroke stream. If exhausts that, will set
- * ready to read and return. (To give some "sharing".)
- *
- * Returns: cli_do_xxxx
- *
- * When returns the cl is '\0' terminated.
- */
-static enum cli_do
-uty_cli_process(vty_io vio, enum node_type node)
-{
- struct keystroke stroke ;
- uint8_t u ;
- int auth ;
- enum cli_do ret ;
-
- auth = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ;
-
- /* Now process as much as possible of what there is */
- ret = cli_do_nothing ;
- while (1)
- {
- if (!vio->cli_drawn)
- uty_cli_draw_this(vio, node) ;
-
- if (!uty_cli_get_keystroke(vio, &stroke))
- {
- ret = (stroke.value == knull_eof) ? cli_do_eof : cli_do_nothing ;
- break ;
- } ;
+ /* Step past what have just consumed -- we have a hold_mark, so
+ * stuff is still in the fifo.
+ */
+ vio_fifo_step(vf->ibuf, p - s) ;
- if (stroke.flags != 0)
+ /* If not found '\n', then we have a line fragment that needs to be
+ * appended to any previous line fragments.
+ *
+ * Loops back to try to get some more form the fifo.
+ */
+ if (q == NULL)
{
- /* TODO: deal with broken keystrokes */
+ qs_append_str_n(vf->cl, s, p - s) ;
continue ;
} ;
- switch (stroke.type)
- {
- /* Straightforward character -----------------------------------*/
- /* Note: only interested in 8-bit characters ! */
- case ks_char:
- u = (uint8_t)stroke.value ;
-
- switch (stroke.value)
- {
- case CONTROL('A'):
- uty_cli_bol (vio);
- break;
-
- case CONTROL('B'):
- uty_cli_backwards(vio, 1);
- break;
-
- case CONTROL('C'):
- ret = cli_do_ctrl_c ; /* Exit on ^C ..................*/
- break ;
-
- case CONTROL('D'):
- if (vio->cl.len == 0) /* if at start of empty line */
- ret = cli_do_ctrl_d ; /* Exit on ^D ..................*/
- else
- uty_cli_del_forwards(vio, 1);
- break;
-
- case CONTROL('E'):
- uty_cli_eol (vio);
- break;
-
- case CONTROL('F'):
- uty_cli_forwards(vio, 1);
- break;
-
- case CONTROL('H'):
- case 0x7f:
- uty_cli_del_backwards(vio, 1);
- break;
-
- case CONTROL('K'):
- uty_cli_del_to_eol (vio);
- break;
-
- case CONTROL('N'):
- uty_cli_next_line (vio);
- break;
-
- case CONTROL('P'):
- uty_cli_previous_line (vio);
- break;
-
- case CONTROL('T'):
- uty_cli_transpose_chars (vio);
- break;
-
- case CONTROL('U'):
- uty_cli_clear_line(vio);
- break;
-
- case CONTROL('W'):
- uty_cli_del_word_backwards (vio);
- break;
-
- case CONTROL('Z'):
- ret = cli_do_ctrl_z ; /* Exit on ^Z ..................*/
- break;
-
- case '\n':
- case '\r':
- ret = cli_do_command ; /* Exit on CR or LF.............*/
- break ;
-
- case '\t':
- if (auth)
- break ;
- else
- uty_cli_complete_command (vio, node);
- break;
-
- case '?':
- if (auth)
- uty_cli_insert (vio, (char*)&u, 1);
- else
- uty_cli_describe_command (vio, node);
- break;
-
- default:
- if ((stroke.value >= 0x20) && (stroke.value < 0x7F))
- uty_cli_insert (vio, (char*)&u, 1) ;
- break;
- }
- break ;
-
- /* ESC X -------------------------------------------------------------*/
- case ks_esc:
- switch (stroke.value)
- {
- case 'b':
- uty_cli_word_backwards (vio);
- break;
-
- case 'f':
- uty_cli_word_forwards (vio);
- break;
-
- case 'd':
- uty_cli_del_word_forwards (vio);
- break;
-
- case CONTROL('H'):
- case 0x7f:
- uty_cli_del_word_backwards (vio);
- break;
-
- default:
- break;
- } ;
- break ;
-
- /* ESC [ X -----------------------------------------------------------*/
- case ks_csi:
- if (stroke.len != 0)
- break ; /* only recognise 3 byte sequences */
-
- switch (stroke.value)
- {
- case ('A'):
- uty_cli_previous_line (vio);
- break;
-
- case ('B'):
- uty_cli_next_line (vio);
- break;
-
- case ('C'):
- uty_cli_forwards(vio, 1);
- break;
-
- case ('D'):
- uty_cli_backwards(vio, 1);
- break;
- default:
- break ;
- } ;
- break ;
-
- /* Telnet Command ----------------------------------------------------*/
- case ks_iac:
- uty_telnet_command(vio, &stroke, false) ;
- break ;
-
- /* Unknown -----------------------------------------------------------*/
- default:
- zabort("unknown keystroke type") ;
- } ;
-
- /* After each keystroke..... */
-
- if (ret != cli_do_nothing)
+ /* If we have nothing so far, set alias to point at what we have in
+ * the fifo. Otherwise, append to what we have.
+ *
+ * End up with: s = start of entire line, so far.
+ * p = end of entire line so far.
+ */
+ len = q - s ; /* length to add */
+ if (qs_len_nn(vf->cl) == 0)
{
- uty_cli_eol (vio) ; /* go to the end of the line */
- break ; /* stop processing */
- } ;
- } ;
-
- /* Tidy up and return where got to. */
-
- qs_term(&vio->cl) ; /* add '\0' */
-
- return ret ;
-} ;
-
-/*==============================================================================
- * Command line operations
- */
-
-/*------------------------------------------------------------------------------
- * Insert 'n' characters at current position in the command line
- *
- * Returns number of characters inserted -- ie 'n'
- */
-static int
-uty_cli_insert (vty_io vio, const char* chars, int n)
-{
- int after ;
-
- VTY_ASSERT_LOCKED() ;
-
- assert((vio->cl.cp <= vio->cl.len) && (n >= 0)) ;
-
- if (n <= 0)
- return n ; /* avoid trouble */
-
- after = qs_insert(vio->cl, chars, n) ;
-
- uty_cli_echo(vio, qs_cp_char(vio->cl), after) ;
-
- if ((after - n) != 0)
- uty_cli_echo_n(vio, telnet_backspaces, after - n) ;
-
- vio->cl.cp += n ;
-
- return n ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Overstrike 'n' characters at current position in the command line
- *
- * Move current position forwards.
- *
- * Returns number of characters inserted -- ie 'n'
- */
-static int
-uty_cli_overwrite (vty_io vio, char* chars, int n)
-{
- VTY_ASSERT_LOCKED() ;
-
- assert((vio->cl.cp <= vio->cl.len) && (n >= 0)) ;
-
- if (n > 0)
- {
- qs_replace(vio->cl, n, chars, n) ;
- uty_cli_echo(vio, chars, n) ;
-
- vio->cl.cp += n ;
- } ;
-
- return n ;
-}
-
-/*------------------------------------------------------------------------------
- * Insert a word into vty interface with overwrite mode.
- *
- * NB: Assumes result will then be the end of the line.
- *
- * Returns number of characters inserted -- ie length of string
- */
-static int
-uty_cli_word_overwrite (vty_io vio, char *str)
-{
- int n ;
- VTY_ASSERT_LOCKED() ;
-
- n = uty_cli_overwrite(vio, str, strlen(str)) ;
-
- vio->cl.len = vio->cl.cp ;
-
- return n ;
-}
+ qs_set_alias_n(vf->cl, s, len) ;
+ p = q ;
+ }
+ else
+ {
+ if (len != 0)
+ qs_append_str_n(vf->cl, s, len) ;
-/*------------------------------------------------------------------------------
- * Forward 'n' characters -- stop at end of line.
- *
- * Returns number of characters actually moved
- */
-static int
-uty_cli_forwards(vty_io vio, int n)
-{
- int have ;
- VTY_ASSERT_LOCKED() ;
+ s = qs_char_nn(vf->cl) ;
+ p = s + qs_len_nn(vf->cl) ;
- have = vio->cl.len - vio->cl.cp ;
- if (have < n)
- n = have ;
+ if ((len == 0) && (p > s) && (*(p-1) == ' '))
+ {
+ /* Have an empty end of line section, and the last character
+ * of what we have so far is ' ', so need now to trim trailing
+ * spaces off the stored stuff.
+ */
+ do --p ; while ((p > s) && (*(p-1) == ' ')) ;
- assert(n >= 0) ;
+ qs_set_len_nn(vf->cl, p - s) ;
+ } ;
+ } ;
- if (n > 0)
- {
- uty_cli_echo(vio, qs_cp_char(vio->cl), n) ;
- vio->cl.cp += n ;
- } ;
+ /* Now worry about we have a trailing '\'. */
- return n ;
-}
+ if ((p == s) || (*(p-1) != '\\'))
+ break ; /* no \ => no continuation => success */
-/*------------------------------------------------------------------------------
- * Backwards 'n' characters -- stop at start of line.
- *
- * Returns number of characters actually moved
- */
-static int
-uty_cli_backwards(vty_io vio, int n)
-{
- VTY_ASSERT_LOCKED() ;
+ /* Have a trailing '\'.
+ *
+ * If there are an odd number of '\', strip the last one and loop
+ * round to collect the continuation.
+ *
+ * If there are an even number of '\', then this is not a continuation.
+ *
+ * Note that this rule deals with the case of the continuation line
+ * being empty... e.g. ....\\\ n n -- where n is '\n'
+ */
+ q = p ;
+ do --q ; while ((q > s) && (*(q-1) == '\\')) ;
- if ((int)vio->cl.cp < n)
- n = vio->cl.cp ;
+ if (((p - q) & 1) == 0)
+ break ; /* even => no continuation => success */
- assert(n >= 0) ;
+ qs_set_len_nn(vf->cl, p - s - 1) ; /* strip odd '\' */
- if (n > 0)
- {
- uty_cli_echo_n(vio, telnet_backspaces, n) ;
- vio->cl.cp -= n ;
+ continue ; /* loop back to fetch more */
} ;
- return n ;
-}
-
-/*------------------------------------------------------------------------------
- * Delete 'n' characters -- forwards -- stop at end of line.
- *
- * Returns number of characters actually deleted.
- */
-static int
-uty_cli_del_forwards(vty_io vio, int n)
-{
- int after ;
- int have ;
-
- VTY_ASSERT_LOCKED() ;
+ /* Success have a line in hand */
- have = vio->cl.len - vio->cl.cp ;
- if (have < n)
- n = have ; /* cannot delete more than have */
+ vf->line_complete = true ;
+ *line = vf->cl ;
- assert(n >= 0) ;
-
- if (n <= 0)
- return 0 ;
-
- after = qs_delete(vio->cl, n) ;
-
- if (after > 0)
- uty_cli_echo(vio, qs_cp_char(vio->cl), after) ;
-
- uty_cli_echo_n(vio, telnet_spaces, n) ;
- uty_cli_echo_n(vio, telnet_backspaces, after + n) ;
-
- return n ;
-}
-
-/*------------------------------------------------------------------------------
- * Delete 'n' characters before the point.
- *
- * Returns number of characters actually deleted.
- */
-static int
-uty_cli_del_backwards(vty_io vio, int n)
-{
- return uty_cli_del_forwards(vio, uty_cli_backwards(vio, n)) ;
-}
-
-/*------------------------------------------------------------------------------
- * Move to the beginning of the line.
- *
- * Returns number of characters moved over.
- */
-static int
-uty_cli_bol (vty_io vio)
-{
- return uty_cli_backwards(vio, vio->cl.cp) ;
+ return CMD_SUCCESS ;
} ;
/*------------------------------------------------------------------------------
- * Move to the end of the line.
+ * Command output push to a file or pipe.
*
- * Returns number of characters moved over
+ * Returns: CMD_SUCCESS -- done
+ * CMD_IO_ERROR -- ran into an I/O error
*/
-static int
-uty_cli_eol (vty_io vio)
+extern cmd_return_code_t
+uty_file_out_push(vio_vf vf)
{
- return uty_cli_forwards(vio, vio->cl.len - vio->cl.cp) ;
-} ;
+ assert(vf->vout_state == vf_open) ;
-/*------------------------------------------------------------------------------
- * Forward word delta -- distance to start of next word.
- *
- * Return number of characters to step over to reach next word.
- *
- * Steps over non-space characters and then any spaces.
- */
-static int
-uty_cli_word_forwards_delta(vty_io vio)
-{
- char* cp ;
- char* tp ;
- char* ep ;
-
- VTY_ASSERT_LOCKED() ; ;
-
- assert(vio->cl.cp <= vio->cl.len) ;
-
- cp = qs_cp_char(vio->cl) ;
- ep = qs_ep_char(vio->cl) ;
-
- tp = cp ;
-
- while ((tp < ep) && (*tp != ' '))
- ++tp ;
-
- while ((tp < ep) && (*tp == ' '))
- ++tp ;
-
- return tp - cp ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Forward word -- move to start of next word.
- *
- * Moves past any non-spaces, then past any spaces.
- */
-static int
-uty_cli_word_forwards(vty_io vio)
-{
- return uty_cli_forwards(vio, uty_cli_word_forwards_delta(vio)) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Backward word delta -- distance to start of next word, back.
- *
- * Return number of characters to step over to reach next word.
- *
- * If "eat_spaces", starts by stepping over spaces.
- * Steps back until next (backwards) character is space, or hits start of line.
- */
-static int
-uty_cli_word_backwards_delta(vty_io vio, int eat_spaces)
-{
- char* cp ;
- char* tp ;
- char* sp ;
-
- VTY_ASSERT_LOCKED() ; ;
-
- assert(vio->cl.cp <= vio->cl.len) ;
-
- cp = qs_cp_char(vio->cl) ;
- sp = qs_chars(vio->cl) ;
-
- tp = cp ;
-
- if (eat_spaces)
- while ((tp > sp) && (*(tp - 1) == ' '))
- --tp ;
-
- while ((tp > sp) && (*(tp - 1) != ' '))
- --tp ;
-
- return cp - tp ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Backward word, but not trailing spaces.
- *
- * Move back until next (backwards) character is space or start of line.
- *
- * Returns number of characters stepped over.
- */
-static int
-uty_cli_word_backwards_pure (vty_io vio)
-{
- return uty_cli_backwards(vio, uty_cli_word_backwards_delta(vio, 0)) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Backward word -- move to start of previous word.
- *
- * Moves past any spaces, then move back until next (backwards) character is
- * space or start of line.
- *
- * Returns number of characters stepped over.
- */
-static int
-uty_cli_word_backwards (vty_io vio)
-{
- return uty_cli_backwards(vio, uty_cli_word_backwards_delta(vio, 1)) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Delete to end of word -- forwards.
- *
- * Deletes any leading spaces, then deletes upto next space or end of line.
- *
- * Returns number of characters deleted.
- */
-static int
-uty_cli_del_word_forwards(vty_io vio)
-{
- return uty_cli_del_forwards(vio, uty_cli_word_forwards_delta(vio)) ;
-}
-
-/*------------------------------------------------------------------------------
- * Delete to start of word -- backwards.
- *
- * Deletes any trailing spaces, then deletes upto next space or start of line.
- *
- * Returns number of characters deleted.
- */
-static int
-uty_cli_del_word_backwards(vty_io vio)
-{
- return uty_cli_del_backwards(vio, uty_cli_word_backwards_delta(vio, 1)) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Kill rest of line from current point.
- *
- * Returns number of characters deleted.
- */
-static int
-uty_cli_del_to_eol (vty_io vio)
-{
- return uty_cli_del_forwards(vio, vio->cl.len - vio->cl.cp) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Kill line from the beginning.
- *
- * Returns number of characters deleted.
- */
-static int
-uty_cli_clear_line(vty_io vio)
-{
- uty_cli_bol(vio) ;
- return uty_cli_del_to_eol(vio) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Transpose chars before or at the point.
- *
- * Return number of characters affected.
- */
-static int
-uty_cli_transpose_chars(vty_io vio)
-{
- char chars[2] ;
- char* cp ;
-
- VTY_ASSERT_LOCKED() ;
-
- /* Give up if < 2 characters or at start of line. */
- if ((vio->cl.len < 2) || (vio->cl.cp < 1))
- return 0 ;
-
- /* Move back to first of characters to exchange */
- if (vio->cl.cp == vio->cl.len)
- uty_cli_backwards(vio, 2) ;
+ if (vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), false) >= 0)
+ return CMD_SUCCESS ;
else
- uty_cli_backwards(vio, 1) ;
-
- /* Pick up in the new order */
- cp = qs_cp_char(vio->cl) ;
- chars[1] = *cp++ ;
- chars[0] = *cp ;
-
- /* And overwrite */
- return uty_cli_overwrite(vio, chars, 2) ;
+ return CMD_IO_ERROR ;
} ;
-/*==============================================================================
- * Command line history handling
- */
-
/*------------------------------------------------------------------------------
- * Add given command line to the history buffer.
- *
- * This is inserting the vty->buf line into the history.
+ * Tidy up after input file has been closed
*/
extern void
-uty_cli_hist_add (vty_io vio, const char* cmd_line)
-{
- qstring prev_line ;
- qstring_t line ;
- int prev_index ;
-
- VTY_ASSERT_LOCKED() ;
-
- /* Construct a dummy qstring for the given command line */
- qs_dummy(&line, cmd_line, 1) ; /* set cursor to the end */
-
- /* make sure have a suitable history vector */
- vector_set_min_length(vio->hist, VTY_MAXHIST) ;
-
- /* find the previous command line in the history */
- prev_index = vio->hindex - 1 ;
- if (prev_index < 0)
- prev_index = VTY_MAXHIST - 1 ;
-
- prev_line = vector_get_item(vio->hist, prev_index) ;
-
- /* If the previous line is NULL, that means the history is empty.
- *
- * If the previous line is essentially the same as the current line,
- * replace it with the current line -- so that the latest whitespace
- * version is saved.
- *
- * Either way, replace the the previous line entry by moving hindex
- * back !
- */
- if ((prev_line == NULL) || (qs_cmp_sig(prev_line, line) == 0))
- vio->hindex = prev_index ;
- else
- prev_line = vector_get_item(vio->hist, vio->hindex) ;
-
- /* Now replace the hindex entry */
- vector_set_item(vio->hist, vio->hindex, qs_copy(prev_line, line)) ;
-
- /* Advance to the near future and reset the history pointer */
- vio->hindex++;
- if (vio->hindex == VTY_MAXHIST)
- vio->hindex = 0;
-
- vio->hp = vio->hindex;
-} ;
-
-/*------------------------------------------------------------------------------
- * Replace command line by current history.
- *
- * This function is called from vty_next_line and vty_previous_line.
- *
- * Step +1 is towards the present
- * -1 is into the past
- */
-static void
-uty_cli_history_use(vty_io vio, int step)
+uty_file_read_close(vio_vf vf)
{
- int index ;
- unsigned old_len ;
- unsigned after ;
- unsigned back ;
- qstring hist ;
-
- VTY_ASSERT_LOCKED() ;
-
- assert((step == +1) || (step == -1)) ;
-
- index = vio->hp ;
-
- /* Special case of being at the insertion point */
- if (index == vio->hindex)
- {
- if (step > 0)
- return ; /* already in the present */
-
- /* before stepping back from the present, take a copy of the
- * current command line -- so can get back to it.
- */
- hist = vector_get_item(&vio->hist, vio->hindex) ;
- vector_set_item(vio->hist, vio->hindex, qs_copy(hist, vio->cl)) ;
- } ;
-
- /* Advance or retreat */
- index += step ;
- if (index < 0)
- index = VTY_MAXHIST - 1 ;
- else if (index >= VTY_MAXHIST)
- index = 0 ;
-
- hist = vector_get_item(vio->hist, index) ;
-
- /* If moving backwards in time, may not move back to the insertion
- * point (that would be wrapping round to the present) and may not
- * move back to a NULL entry (that would be going back before '.').
- */
- if (step < 0)
- if ((hist == NULL) || (index == vio->hindex))
- return ;
-
- /* Now, if arrived at the insertion point, this is returning to the
- * present, which is fine.
- */
- vio->hp = index;
-
- /* Move back to the start of the current line */
- uty_cli_bol(vio) ;
-
- /* Get previous line from history buffer and echo that */
- old_len = vio->cl.len ;
- qs_copy(vio->cl, hist) ;
-
- /* Sort out wiping out any excess and setting the cursor position */
- if (old_len > vio->cl.len)
- after = old_len - vio->cl.len ;
- else
- after = 0 ;
-
- back = after ;
- if (vio->cl.len > vio->cl.cp)
- back += (vio->cl.len - vio->cl.cp) ;
-
- if (vio->cl.len > 0)
- uty_cli_echo(vio, vio->cl.body, vio->cl.len) ;
-
- if (after > 0)
- uty_cli_echo_n(vio, telnet_spaces, after) ;
-
- if (back > 0)
- uty_cli_echo_n(vio, telnet_backspaces, back) ;
-
return ;
} ;
/*------------------------------------------------------------------------------
- * Use next history line, if any.
- */
-static void
-uty_cli_next_line(vty_io vio)
-{
- uty_cli_history_use(vio, +1) ;
-}
-
-/*------------------------------------------------------------------------------
- * Use previous history line, if any.
- */
-static void
-uty_cli_previous_line (vty_io vio)
-{
- uty_cli_history_use(vio, -1) ;
-}
-
-/*==============================================================================
- * Command Completion and Command Description
+ * Flush output buffer and close.
*
- */
-static void uty_cli_describe_show(vty_io vio, vector describe) ;
-static void uty_cli_describe_fold (vty_io vio, int cmd_width,
- unsigned int desc_width, struct desc *desc) ;
-static void uty_cli_describe_line(vty_io vio, int cmd_width, const char* cmd,
- const char* str) ;
-
-static vector uty_cli_cmd_prepare(vty_io vio, int help) ;
-
-/*------------------------------------------------------------------------------
- * Command completion
- */
-static void
-uty_cli_complete_command (vty_io vio, enum node_type node)
-{
- unsigned i ;
- int ret ;
- int len ;
- int n ;
- vector matched ;
- vector vline ;
-
- VTY_ASSERT_LOCKED() ;
-
- /* Try and match the tokenised command line */
- vline = uty_cli_cmd_prepare(vio, 1) ;
- matched = cmd_complete_command (vline, node, &ret);
- cmd_free_strvec (vline);
-
- /* Show the result. */
- switch (ret)
- {
- case CMD_ERR_AMBIGUOUS:
- uty_cli_out_newline(vio) ; /* clears cli_drawn */
- uty_cli_out_CMD_ERR_AMBIGUOUS(vio) ;
- break ;
-
- case CMD_ERR_NO_MATCH:
- uty_cli_out_newline(vio) ; /* clears cli_drawn */
- uty_cli_out_CMD_ERR_NO_MATCH(vio) ;
- break ;
-
- case CMD_COMPLETE_FULL_MATCH:
- uty_cli_eol (vio) ;
- uty_cli_word_backwards_pure (vio);
- uty_cli_word_overwrite (vio, vector_get_item(matched, 0));
- uty_cli_insert(vio, " ", 1);
- break ;
-
- case CMD_COMPLETE_MATCH:
- uty_cli_eol (vio) ;
- uty_cli_word_backwards_pure (vio);
- uty_cli_word_overwrite (vio, vector_get_item(matched, 0));
- break ;
-
- case CMD_COMPLETE_LIST_MATCH:
- len = 6 ;
- for (i = 0; i < vector_end(matched); i++)
- {
- int sl = strlen((char*)vector_get_item(matched, i)) ;
- if (len < sl)
- len = sl ;
- } ;
-
- n = vio->width ;
- if (n == 0)
- n = 60 ;
- n = n / (len + 2) ;
- if (n == 0)
- n = 1 ;
-
- for (i = 0; i < vector_end(matched); i++)
- {
- if ((i % n) == 0)
- uty_cli_out_newline(vio) ; /* clears cli_drawn */
- uty_cli_out(vio, "%-*s ", len, (char*)vector_get_item(matched, i));
- }
- uty_cli_out_newline(vio) ;
-
- break;
-
- case CMD_COMPLETE_ALREADY:
- default:
- break;
- } ;
-
- cmd_free_strvec(matched);
-} ;
-
-/*------------------------------------------------------------------------------
- * Command Description
- */
-static void
-uty_cli_describe_command (vty_io vio, enum node_type node)
-{
- int ret;
- vector vline;
- vector describe;
-
- VTY_ASSERT_LOCKED() ;
-
- /* Try and match the tokenised command line */
- vline = uty_cli_cmd_prepare(vio, 1) ;
- describe = cmd_describe_command (vline, node, &ret);
- cmd_free_strvec (vline);
-
- uty_cli_out_newline(vio); /* clears cli_drawn */
-
- /* Deal with result. */
- switch (ret)
- {
- case CMD_ERR_AMBIGUOUS:
- uty_cli_out_CMD_ERR_AMBIGUOUS(vio) ;
- break ;
-
- case CMD_ERR_NO_MATCH:
- uty_cli_out_CMD_ERR_NO_MATCH(vio) ;
- break ;
-
- default:
- uty_cli_describe_show(vio, describe) ;
- break ;
- } ;
-
- if (describe != NULL)
- vector_free (describe);
-}
-
-/*------------------------------------------------------------------------------
- * Show the command description.
- *
- * Generates lines of the form:
- *
- * word description text
- *
- * Where the word field is adjusted to suit the longest word, and the
- * description text is wrapped if required (if the width of the console is
- * known) so that get:
- *
- * word description ..................................
- * .............text
- *
- * If one of the options is '<cr>', that is always shown last.
- */
-static void
-uty_cli_describe_show(vty_io vio, vector describe)
-{
- unsigned int i, cmd_width, desc_width;
- struct desc *desc, *cr_item ;
-
- /* Get width of the longest "word" */
- cmd_width = 0;
- for (i = 0; i < vector_active (describe); i++)
- if ((desc = vector_slot (describe, i)) != NULL)
- {
- unsigned int len;
-
- if (desc->cmd[0] == '\0')
- continue;
-
- len = strlen (desc->cmd);
- if (desc->cmd[0] == '.')
- len--;
-
- if (cmd_width < len)
- cmd_width = len;
- }
-
- /* Set width of description string. */
- desc_width = vio->width - (cmd_width + 6);
-
- /* Print out description. */
- cr_item = NULL ; /* put <cr> last if it appears */
-
- for (i = 0; i < vector_active (describe); i++)
- if ((desc = vector_slot (describe, i)) != NULL)
- {
- if (desc->cmd[0] == '\0')
- continue;
-
- if (strcmp (desc->cmd, cr_string) == 0)
- {
- cr_item = desc;
- continue;
- }
-
- uty_cli_describe_fold (vio, cmd_width, desc_width, desc);
- }
-
- if (cr_item != NULL)
- uty_cli_describe_fold (vio, cmd_width, desc_width, cr_item);
-} ;
-
-/*------------------------------------------------------------------------------
- * Show one word and the description, folding the description as required.
- */
-static void
-uty_cli_describe_fold (vty_io vio, int cmd_width,
- unsigned int desc_width, struct desc *desc)
-{
- char *buf;
- const char *cmd, *p;
- int pos;
-
- VTY_ASSERT_LOCKED() ;
-
- cmd = desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd;
- p = desc->str ;
-
- /* If have a sensible description width */
- if (desc_width > 20)
- {
- buf = XCALLOC (MTYPE_TMP, strlen (desc->str) + 1);
-
- while (strlen (p) > desc_width)
- {
- /* move back to first space */
- for (pos = desc_width; pos > 0; pos--)
- if (*(p + pos) == ' ')
- break;
-
- /* if did not find a space, break at width */
- if (pos == 0)
- pos = desc_width ;
-
- strncpy (buf, p, pos);
- buf[pos] = '\0';
- uty_cli_describe_line(vio, cmd_width, cmd, buf) ;
-
- cmd = ""; /* for 2nd and subsequent lines */
-
- p += pos ; /* step past what just wrote */
- while (*p == ' ')
- ++p ; /* skip spaces */
- } ;
-
- XFREE (MTYPE_TMP, buf);
- } ;
-
- uty_cli_describe_line(vio, cmd_width, cmd, p) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Show one description line.
- */
-static void
-uty_cli_describe_line(vty_io vio, int cmd_width, const char* cmd,
- const char* str)
-{
- if (str != NULL)
- uty_cli_out (vio, " %-*s %s", cmd_width, cmd, str) ;
- else
- uty_cli_out (vio, " %-s", cmd) ;
- uty_cli_out_newline(vio) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Prepare "vline" token array for command handler.
- *
- * For "help" (command completion/description), if the command line is empty,
- * or ends in ' ', adds an empty token to the end of the token array.
- */
-static vector
-uty_cli_cmd_prepare(vty_io vio, int help)
-{
- vector vline ;
-
- vline = cmd_make_strvec(qs_term(&vio->cl)) ;
-
- /* Note that if there is a vector of tokens, then there is at least one
- * token, so can guarantee that vio->cl.len >= 1 !
- */
- if (help)
- if ((vline == NULL) || isspace(*qs_chars_at(&vio->cl, vio->cl.len - 1)))
- vline = cmd_add_to_strvec(vline, "") ;
-
- return vline ;
-} ;
-
-/*==============================================================================
- * VTY telnet stuff
- */
-
-#define TELNET_OPTION_DEBUG 0 /* 0 to turn off */
-
-static const char* telnet_commands[256] =
-{
- [tn_IAC ] = "IAC",
- [tn_DONT ] = "DONT",
- [tn_DO ] = "DO",
- [tn_WONT ] = "WONT",
- [tn_WILL ] = "WILL",
- [tn_SB ] = "SB",
- [tn_GA ] = "GA",
- [tn_EL ] = "EL",
- [tn_EC ] = "EC",
- [tn_AYT ] = "AYT",
- [tn_AO ] = "AO",
- [tn_IP ] = "IP",
- [tn_BREAK] = "BREAK",
- [tn_DM ] = "DM",
- [tn_NOP ] = "NOP",
- [tn_SE ] = "SE",
- [tn_EOR ] = "EOR",
- [tn_ABORT] = "ABORT",
- [tn_SUSP ] = "SUSP",
- [tn_EOF ] = "EOF",
-} ;
-
-static const char* telnet_options[256] =
-{
- [to_BINARY] = "BINARY", /* 8-bit data path */
- [to_ECHO] = "ECHO", /* echo */
- [to_RCP] = "RCP", /* prepare to reconnect */
- [to_SGA] = "SGA", /* suppress go ahead */
- [to_NAMS] = "NAMS", /* approximate message size */
- [to_STATUS] = "STATUS", /* give status */
- [to_TM] = "TM", /* timing mark */
- [to_RCTE] = "RCTE", /* remote controlled tx and echo */
- [to_NAOL] = "NAOL", /* neg. about output line width */
- [to_NAOP] = "NAOP", /* neg. about output page size */
- [to_NAOCRD] = "NAOCRD", /* neg. about CR disposition */
- [to_NAOHTS] = "NAOHTS", /* neg. about horizontal tabstops */
- [to_NAOHTD] = "NAOHTD", /* neg. about horizontal tab disp. */
- [to_NAOFFD] = "NAOFFD", /* neg. about formfeed disposition */
- [to_NAOVTS] = "NAOVTS", /* neg. about vertical tab stops */
- [to_NAOVTD] = "NAOVTD", /* neg. about vertical tab disp. */
- [to_NAOLFD] = "NAOLFD", /* neg. about output LF disposition */
- [to_XASCII] = "XASCII", /* extended ascii character set */
- [to_LOGOUT] = "LOGOUT", /* force logout */
- [to_BM] = "BM", /* byte macro */
- [to_DET] = "DET", /* data entry terminal */
- [to_SUPDUP] = "SUPDUP", /* supdup protocol */
- [to_SUPDUPOUTPUT] = "SUPDUPOUTPUT",/* supdup output */
- [to_SNDLOC] = "SNDLOC", /* send location */
- [to_TTYPE] = "TTYPE", /* terminal type */
- [to_EOR] = "EOR", /* end or record */
- [to_TUID] = "TUID", /* TACACS user identification */
- [to_OUTMRK] = "OUTMRK", /* output marking */
- [to_TTYLOC] = "TTYLOC", /* terminal location number */
- [to_3270REGIME] = "3270REGIME", /* 3270 regime */
- [to_X3PAD] = "X3PAD", /* X.3 PAD */
- [to_NAWS] = "NAWS", /* window size */
- [to_TSPEED] = "TSPEED", /* terminal speed */
- [to_LFLOW] = "LFLOW", /* remote flow control */
- [to_LINEMODE] = "LINEMODE", /* Linemode option */
- [to_XDISPLOC] = "XDISPLOC", /* X Display Location */
- [to_OLD_ENVIRON] = "OLD_ENVIRON", /* Old - Environment variables */
- [to_AUTHENTICATION] = "AUTHENTICATION", /* Authenticate */
- [to_ENCRYPT] = "ENCRYPT", /* Encryption option */
- [to_NEW_ENVIRON] = "NEW_ENVIRON", /* New - Environment variables */
- [to_EXOPL] = "EXOPL", /* extended-options-list */
-} ;
-
-/*------------------------------------------------------------------------------
- * For debug. Put string or value as decimal.
- */
-static void
-uty_cli_out_dec(vty_io vio, const char* str, unsigned char u)
-{
- if (str != NULL)
- uty_cli_out(vio, "%s ", str) ;
- else
- uty_cli_out(vio, "%d ", (int)u) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * For debug. Put string or value as hex.
- */
-static void
-uty_cli_out_hex(vty_io vio, const char* str, unsigned char u)
-{
- if (str != NULL)
- uty_cli_out(vio, "%s ", str) ;
- else
- uty_cli_out(vio, "0x%02x ", (unsigned)u) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Send telnet: "WILL TELOPT_ECHO"
- */
-static void
-uty_will_echo (vty_io vio)
-{
- unsigned char cmd[] = { tn_IAC, tn_WILL, to_ECHO };
- VTY_ASSERT_LOCKED() ;
- uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd));
-}
-
-/*------------------------------------------------------------------------------
- * Send telnet: "suppress Go-Ahead"
- */
-static void
-uty_will_suppress_go_ahead (vty_io vio)
-{
- unsigned char cmd[] = { tn_IAC, tn_WILL, to_SGA };
- VTY_ASSERT_LOCKED() ;
- uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd));
-}
-
-/*------------------------------------------------------------------------------
- * Send telnet: "don't use linemode"
- */
-static void
-uty_dont_linemode (vty_io vio)
-{
- unsigned char cmd[] = { tn_IAC, tn_DONT, to_LINEMODE };
- VTY_ASSERT_LOCKED() ;
- uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd));
-}
-
-/*------------------------------------------------------------------------------
- * Send telnet: "Use window size"
- */
-static void
-uty_do_window_size (vty_io vio)
-{
- unsigned char cmd[] = { tn_IAC, tn_DO, to_NAWS };
- VTY_ASSERT_LOCKED() ;
- uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd));
-}
-
-/*------------------------------------------------------------------------------
- * Send telnet: "don't use lflow" -- not currently used
- */
-static void
-uty_dont_lflow_ahead (vty_io vio)
-{
- unsigned char cmd[] = { tn_IAC, tn_DONT, to_LFLOW };
- VTY_ASSERT_LOCKED() ;
- uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd));
-}
-
-/*------------------------------------------------------------------------------
- * The keystroke iac callback function.
- *
- * This deals with IAC sequences that should be dealt with as soon as they
- * are read -- not stored in the keystroke stream for later processing.
+ * Returns: true <=> buffer (now) empty
*/
extern bool
-uty_cli_iac_callback(keystroke_iac_callback_args)
+uty_file_write_close(vio_vf vf, bool final)
{
- return uty_telnet_command((vty_io)context, stroke, true) ;
+ return vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), true) == 0 ;
} ;
-
-/*------------------------------------------------------------------------------
- * Process incoming Telnet Option(s)
- *
- * May be called during keystroke iac callback, or when processing CLI
- * keystrokes.
- *
- * In particular: get telnet window size.
- *
- * Returns: true <=> dealt with, for:
- *
- * * telnet window size.
- */
-static bool
-uty_telnet_command(vty_io vio, keystroke stroke, bool callback)
-{
- uint8_t* p ;
- uint8_t o ;
- int left ;
- bool dealt_with ;
-
- /* Echo to the other end if required */
- if (TELNET_OPTION_DEBUG)
- {
- uty_cli_wipe(vio, 0) ;
-
- p = stroke->buf ;
- left = stroke->len ;
-
- uty_cli_out_hex(vio, telnet_commands[tn_IAC], tn_IAC) ;
-
- if (left-- > 0)
- uty_cli_out_dec(vio, telnet_commands[*p], *p) ;
- ++p ;
-
- if (left-- > 0)
- uty_cli_out_dec(vio, telnet_options[*p], *p) ;
- ++p ;
-
- if (left > 0)
- {
- while(left-- > 0)
- uty_cli_out_hex(vio, NULL, *p++) ;
-
- if (stroke->flags & kf_truncated)
- uty_cli_out(vio, "... ") ;
-
- if (!(stroke->flags & kf_broken))
- {
- uty_cli_out_hex(vio, telnet_commands[tn_IAC], tn_IAC) ;
- uty_cli_out_hex(vio, telnet_commands[tn_SE], tn_SE) ;
- }
- } ;
-
- if (stroke->flags & kf_broken)
- uty_cli_out (vio, "BROKEN") ;
-
- uty_cli_out (vio, "\r\n") ;
- } ;
-
- /* Process the telnet command */
- dealt_with = false ;
-
- if (stroke->flags != 0)
- return dealt_with ; /* go no further if broken */
-
- p = stroke->buf ;
- left = stroke->len ;
-
- passert(left >= 1) ; /* must be if not broken ! */
- passert(stroke->value == *p) ; /* or something is wrong */
-
- ++p ; /* step past X of IAC X */
- --left ;
-
- /* Decode the one command that is interesting -- "NAWS" */
- switch (stroke->value)
- {
- case tn_SB:
- passert(left > 0) ; /* or parser failed */
-
- o = *p++ ; /* the option byte */
- --left ;
- switch(o)
- {
- case to_NAWS:
- if (left != 4)
- {
- uzlog(NULL, LOG_WARNING,
- "RFC 1073 violation detected: telnet NAWS option "
- "should send %d characters, but we received %d",
- (3 + 4 + 2), (3 + left + 2)) ;
- }
- else
- {
- vio->width = *p++ << 8 ;
- vio->width += *p++ ;
- vio->height = *p++ << 8 ;
- vio->height += *p ;
-
- if (TELNET_OPTION_DEBUG)
- uty_cli_out(vio, "TELNET NAWS window size received: "
- "width %d, height %d%s",
- vio->width, vio->height, telnet_newline) ;
- uty_set_height(vio) ;
-
- dealt_with = true ;
- } ;
- break ;
-
- default: /* no other IAC SB <option> */
- break ;
- } ;
- break ;
-
- default: /* no other IAC X */
- break ;
- } ;
-
- return dealt_with ;
-} ;
-
#endif
diff --git a/lib/zassert.h b/lib/zassert.h
index 9f2465f7..a12464fe 100644
--- a/lib/zassert.h
+++ b/lib/zassert.h
@@ -6,6 +6,7 @@
#define _QUAGGA_ASSERT_H
#include "confirm.h"
+#include "misc.h"
extern void _zlog_assert_failed (const char *assertion, const char *file,
unsigned int line, const char *function)
@@ -44,11 +45,14 @@ extern void _zlog_abort_err (const char *mess, int err, const char *file,
/* NDEBUG time assert() */
#ifndef NDEBUG
-#define dassert(EX) zassert(EX)
+# define dassert(EX) zassert(EX)
#else
-#define dassert(EX)
+# define dassert(EX)
#endif
+/* Assert iff QDEBUG */
+#define qassert(EX) if (qdebug) zassert(EX)
+
/* Abort with message */
#define zabort(MS) _zlog_abort_mess(MS, __FILE__, __LINE__, __ASSERT_FUNCTION)
diff --git a/lib/zconfig.h b/lib/zconfig.h
index 052bfe99..afde09c6 100644
--- a/lib/zconfig.h
+++ b/lib/zconfig.h
@@ -22,7 +22,13 @@ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
#define _ZEBRA_CONFIG_H
#ifdef HAVE_CONFIG_H
+
#include "config.h"
+
+#if _GNU_SOURCE
+#include <features.h>
+#endif
+
#endif /* HAVE_CONFIG_H */
#endif /* _ZEBRA_CONFIG_H */