summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bgpd/bgp_connection.c2
-rw-r--r--bgpd/bgp_main.c3
-rw-r--r--bgpd/bgp_vty.c37
-rw-r--r--bgpd/bgpd.h4
-rw-r--r--lib/Makefile.am2
-rw-r--r--lib/command.c118
-rw-r--r--lib/command_common.h31
-rw-r--r--lib/command_execute.c125
-rw-r--r--lib/command_parse.c2
-rw-r--r--lib/command_parse.h2
-rw-r--r--lib/command_queue.c186
-rw-r--r--lib/keystroke.c1093
-rw-r--r--lib/keystroke.h57
-rw-r--r--lib/log.c212
-rw-r--r--lib/log.h183
-rw-r--r--lib/log_common.h79
-rw-r--r--lib/log_local.h37
-rw-r--r--lib/mem_tracker.c91
-rw-r--r--lib/memory.c3470
-rw-r--r--lib/memory.h132
-rw-r--r--lib/memtypes.awk13
-rw-r--r--lib/misc.h5
-rw-r--r--lib/pthread_safe.c160
-rw-r--r--lib/pthread_safe.h8
-rw-r--r--lib/qfstring.c6
-rw-r--r--lib/qfstring.h88
-rw-r--r--lib/qiovec.c31
-rw-r--r--lib/qiovec.h1
-rw-r--r--lib/qlib_init.c77
-rw-r--r--lib/qlib_init.h10
-rw-r--r--lib/qpath.c432
-rw-r--r--lib/qpath.h2
-rw-r--r--lib/qpnexus.h1
-rw-r--r--lib/qpselect.c10
-rw-r--r--lib/qpselect.h2
-rw-r--r--lib/qstring.h20
-rw-r--r--lib/qtimers.c7
-rw-r--r--lib/sigevent.c36
-rw-r--r--lib/thread.c2
-rw-r--r--lib/vector.c2
-rw-r--r--lib/vio_fifo.c206
-rw-r--r--lib/vio_fifo.h25
-rw-r--r--lib/vio_lines.c2
-rw-r--r--lib/vio_lines.h12
-rw-r--r--lib/vty.c246
-rw-r--r--lib/vty_cli.c525
-rw-r--r--lib/vty_cli.h23
-rw-r--r--lib/vty_command.c1624
-rw-r--r--lib/vty_command.h20
-rw-r--r--lib/vty_common.h5
-rw-r--r--lib/vty_io.c1560
-rw-r--r--lib/vty_io.h204
-rw-r--r--lib/vty_io_basic.c119
-rw-r--r--lib/vty_io_basic.h18
-rw-r--r--lib/vty_io_file.c2057
-rw-r--r--lib/vty_io_file.h14
-rw-r--r--lib/vty_io_term.c489
-rw-r--r--lib/vty_io_term.h4
-rw-r--r--lib/vty_local.h13
-rw-r--r--lib/vty_log.c93
-rw-r--r--lib/vty_log.h1
-rw-r--r--lib/vty_pipe.c4
-rw-r--r--lib/zassert.h2
-rw-r--r--ospfd/ospf_spf.c168
-rw-r--r--tests/Makefile.am6
-rw-r--r--tests/test-qpath.c490
-rw-r--r--tests/test-vector.c304
67 files changed, 10438 insertions, 4575 deletions
diff --git a/bgpd/bgp_connection.c b/bgpd/bgp_connection.c
index e2916705..cdd37848 100644
--- a/bgpd/bgp_connection.c
+++ b/bgpd/bgp_connection.c
@@ -348,7 +348,7 @@ bgp_connection_free(bgp_connection connection)
/* Free any components which still exist */
connection->qf = qps_file_free(connection->qf) ;
connection->hold_timer = qtimer_free(connection->hold_timer) ;
- connection->keepalive_timer = qtimer_free(connection->hold_timer) ;
+ connection->keepalive_timer = qtimer_free(connection->keepalive_timer) ;
bgp_notify_unset(&connection->notification) ;
bgp_open_state_unset(&connection->open_recv) ;
diff --git a/bgpd/bgp_main.c b/bgpd/bgp_main.c
index 82db783a..38fbb7e4 100644
--- a/bgpd/bgp_main.c
+++ b/bgpd/bgp_main.c
@@ -340,6 +340,7 @@ bgp_exit (int status)
cmd_terminate ();
vty_terminate ();
+
if (zclient)
zclient_free (zclient);
if (zlookup)
@@ -717,8 +718,6 @@ routing_background(void)
/*------------------------------------------------------------------------------
* SIGHUP: message sent to Routeing engine and the action it then takes.
- *
- * TODO: should SIGHUP be a priority message (!)
*/
static void
sighup_enqueue(void)
diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c
index ed7e0e03..0015e043 100644
--- a/bgpd/bgp_vty.c
+++ b/bgpd/bgp_vty.c
@@ -6642,52 +6642,55 @@ DEFUN (show_bgp_memory,
BGP_STR
"Global BGP memory statistics\n")
{
+ mem_stats_t mst[1] ;
char memstrbuf[MTYPE_MEMSTR_LEN];
unsigned long count;
+ mem_get_stats(mst) ;
+
/* RIB related usage stats */
- count = mtype_stats_alloc (MTYPE_BGP_NODE);
+ count = mem_get_alloc(mst, MTYPE_BGP_NODE);
vty_out (vty, "%ld RIB nodes, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct bgp_node)),
VTY_NEWLINE);
- count = mtype_stats_alloc (MTYPE_BGP_ROUTE);
+ count = mem_get_alloc(mst, MTYPE_BGP_ROUTE);
vty_out (vty, "%ld BGP routes, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct bgp_info)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_BGP_ROUTE_EXTRA)))
+ if ((count = mem_get_alloc(mst, MTYPE_BGP_ROUTE_EXTRA)))
vty_out (vty, "%ld BGP route ancillaries, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct bgp_info_extra)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_BGP_STATIC)))
+ if ((count = mem_get_alloc(mst, MTYPE_BGP_STATIC)))
vty_out (vty, "%ld Static routes, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct bgp_static)),
VTY_NEWLINE);
/* Adj-In/Out */
- if ((count = mtype_stats_alloc (MTYPE_BGP_ADJ_IN)))
+ if ((count = mem_get_alloc(mst, MTYPE_BGP_ADJ_IN)))
vty_out (vty, "%ld Adj-In entries, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct bgp_adj_in)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_BGP_ADJ_OUT)))
+ if ((count = mem_get_alloc(mst, MTYPE_BGP_ADJ_OUT)))
vty_out (vty, "%ld Adj-Out entries, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct bgp_adj_out)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_BGP_NEXTHOP_CACHE)))
+ if ((count = mem_get_alloc(mst, MTYPE_BGP_NEXTHOP_CACHE)))
vty_out (vty, "%ld Nexthop cache entries, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct bgp_nexthop_cache)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_BGP_DAMP_INFO)))
+ if ((count = mem_get_alloc(mst, MTYPE_BGP_DAMP_INFO)))
vty_out (vty, "%ld Dampening entries, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct bgp_damp_info)),
@@ -6699,7 +6702,7 @@ DEFUN (show_bgp_memory,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof(struct attr)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_ATTR_EXTRA)))
+ if ((count = mem_get_alloc(mst, MTYPE_ATTR_EXTRA)))
vty_out (vty, "%ld BGP extra attributes, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof(struct attr_extra)),
@@ -6715,7 +6718,7 @@ DEFUN (show_bgp_memory,
count * sizeof (struct aspath)),
VTY_NEWLINE);
- count = mtype_stats_alloc (MTYPE_AS_SEG);
+ count = mem_get_alloc(mst, MTYPE_AS_SEG);
vty_out (vty, "%ld BGP AS-PATH segments, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct assegment)),
@@ -6727,43 +6730,43 @@ DEFUN (show_bgp_memory,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct community)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_ECOMMUNITY)))
+ if ((count = mem_get_alloc(mst, MTYPE_ECOMMUNITY)))
vty_out (vty, "%ld BGP community entries, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct ecommunity)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_CLUSTER)))
+ if ((count = mem_get_alloc(mst, MTYPE_CLUSTER)))
vty_out (vty, "%ld Cluster lists, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct cluster_list)),
VTY_NEWLINE);
/* Peer related usage */
- count = mtype_stats_alloc (MTYPE_BGP_PEER);
+ count = mem_get_alloc(mst, MTYPE_BGP_PEER);
vty_out (vty, "%ld peers, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct peer)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_PEER_GROUP)))
+ if ((count = mem_get_alloc(mst, MTYPE_PEER_GROUP)))
vty_out (vty, "%ld peer groups, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct peer_group)),
VTY_NEWLINE);
/* Other */
- if ((count = mtype_stats_alloc (MTYPE_HASH)))
+ if ((count = mem_get_alloc(mst, MTYPE_HASH)))
vty_out (vty, "%ld hash tables, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct hash)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_HASH_BACKET)))
+ if ((count = mem_get_alloc(mst, MTYPE_HASH_BACKET)))
vty_out (vty, "%ld hash buckets, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (struct hash_backet)),
VTY_NEWLINE);
- if ((count = mtype_stats_alloc (MTYPE_BGP_REGEXP)))
+ if ((count = mem_get_alloc(mst, MTYPE_BGP_REGEXP)))
vty_out (vty, "%ld compiled regexes, using %s of memory%s", count,
mtype_memstr (memstrbuf, sizeof (memstrbuf),
count * sizeof (regex_t)),
diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h
index 00dd6804..3a657456 100644
--- a/bgpd/bgpd.h
+++ b/bgpd/bgpd.h
@@ -445,9 +445,9 @@ bgp_clock(void)
* Clock time calculated now may differ from any logged Wall Clock times !!
*/
Inline time_t
-bgp_wall_clock(time_t bgp_time)
+bgp_wall_clock(time_t mono)
{
- return time(NULL) + (bgp_time - bgp_clock()) ;
+ return time(NULL) - (bgp_clock() - mono) ;
} ;
/*------------------------------------------------------------------------------
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 2d41399c..0f965b07 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -33,7 +33,7 @@ pkginclude_HEADERS = \
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 \
+ keystroke.h linklist.h list_util.h log.h log_common.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 \
diff --git a/lib/command.c b/lib/command.c
index f9747a33..ee855360 100644
--- a/lib/command.c
+++ b/lib/command.c
@@ -30,7 +30,6 @@ Boston, MA 02111-1307, USA. */
#include <sys/utsname.h>
#include "memory.h"
-#include "log.h"
#include "thread.h"
#include "vector.h"
#include "qstring.h"
@@ -41,9 +40,11 @@ Boston, MA 02111-1307, USA. */
#include "command_parse.h"
#include "command_execute.h"
#include "command_queue.h"
+#include "log_local.h"
#include "vty_local.h"
#include "vty_command.h"
#include "vty_io.h"
+#include "vty_log.h"
#include "network.h"
/* Vector of cmd_node, one for each known node, built during daemon
@@ -1097,7 +1098,7 @@ DEFUN (config_write_file,
}
/* Make vty for configuration file. */
- vty_open_config_write(vty, fd) ;
+ vty_config_write_open(vty, fd) ;
vty_out (vty, "!\n! Zebra configuration saved from vty\n! ");
vty_time_print (vty, 1);
@@ -1119,7 +1120,7 @@ DEFUN (config_write_file,
} ;
} ;
- ret = vty_close_config_write(vty, (retw != CMD_SUCCESS)) ;
+ ret = vty_config_write_close(vty) ;
if ((ret != CMD_SUCCESS) || (retw != CMD_SUCCESS))
{
@@ -1505,7 +1506,9 @@ static cmd_return_code_t
set_host_lines(int lines)
{
VTY_LOCK() ;
+
host.lines = lines ;
+
VTY_UNLOCK() ;
return CMD_SUCCESS ;
} ;
@@ -1556,6 +1559,14 @@ DEFUN_HID_CALL (do_echo,
/*==============================================================================
* Logging configuration.
+ *
+ * Each VTY has its own logging level for monitor logging, and its own
+ * enable/disable. When logging is enabled, the current monitor logging
+ * level is set for the VTY.
+ *
+ * The monitor logging level is a bit special -- setting this level affects
+ * the current VTY (if it is a VTY_TERMINAL) and any future VTY. It also
+ * affects the level which will be written away to any configuration file.
*/
DEFUN_CALL (config_logmsg,
@@ -1583,51 +1594,43 @@ DEFUN_CALL (show_logging,
SHOW_STR
"Show current logging configuration\n")
{
+ int lvl ;
+
vty_out (vty, "Syslog logging: ");
- if (zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG) == ZLOG_DISABLED)
- vty_out (vty, "disabled");
+ if ((lvl = zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG)) == ZLOG_DISABLED)
+ vty_out (vty, "disabled\n");
else
- vty_out (vty, "level %s, facility %s, ident %s",
- zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_SYSLOG)],
- facility_name(zlog_get_facility(NULL)), zlog_get_ident(NULL));
- vty_out (vty, "%s", VTY_NEWLINE);
+ vty_out (vty, "level %s, facility %s, ident %s\n", zlog_priority[lvl],
+ facility_name(zlog_get_facility(NULL)), zlog_get_ident(NULL)) ;
vty_out (vty, "Stdout logging: ");
- if (zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT) == ZLOG_DISABLED)
- vty_out (vty, "disabled");
+ if ((lvl = zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT)) == ZLOG_DISABLED)
+ vty_out (vty, "disabled\n");
else
- vty_out (vty, "level %s",
- zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_STDOUT)]);
- vty_out (vty, "%s", VTY_NEWLINE);
+ vty_out (vty, "level %s\n", zlog_priority[lvl]) ;
vty_out (vty, "Monitor logging: ");
- if (zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR) == ZLOG_DISABLED)
- vty_out (vty, "disabled");
+ if ((lvl = zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR)) == ZLOG_DISABLED)
+ vty_out (vty, "disabled\n");
else
- vty_out (vty, "level %s",
- zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_MONITOR)]);
- vty_out (vty, "%s", VTY_NEWLINE);
+ vty_out (vty, "level %s\n", zlog_priority[lvl]);
vty_out (vty, "File logging: ");
- if ((zlog_get_maxlvl(NULL, ZLOG_DEST_FILE) == ZLOG_DISABLED) ||
- !zlog_is_file(NULL))
- vty_out (vty, "disabled");
+ if (((lvl = zlog_get_maxlvl(NULL, ZLOG_DEST_FILE)) == ZLOG_DISABLED) ||
+ !zlog_is_file(NULL))
+ vty_out (vty, "disabled\n");
else
{
char * filename = zlog_get_filename(NULL);
- vty_out (vty, "level %s, filename %s",
- zlog_priority[zlog_get_maxlvl(NULL, ZLOG_DEST_FILE)],
- filename);
+ vty_out (vty, "level %s, filename %s\n", zlog_priority[lvl], filename) ;
free(filename);
}
- vty_out (vty, "%s", VTY_NEWLINE);
- vty_out (vty, "Protocol name: %s%s",
- zlog_get_proto_name(NULL), VTY_NEWLINE);
- vty_out (vty, "Record priority: %s%s",
- (zlog_get_record_priority(NULL) ? "enabled" : "disabled"), VTY_NEWLINE);
- vty_out (vty, "Timestamp precision: %d%s",
- zlog_get_timestamp_precision(NULL), VTY_NEWLINE);
+ vty_out (vty, "Protocol name: %s\n", zlog_get_proto_name(NULL));
+ vty_out (vty, "Record priority: %s\n",
+ (zlog_get_record_priority(NULL) ? "enabled" : "disabled")) ;
+ vty_out (vty, "Timestamp precision: %d\n",
+ zlog_get_timestamp_precision(NULL)) ;
return CMD_SUCCESS;
}
@@ -1675,7 +1678,9 @@ DEFUN_CALL (config_log_monitor,
"Logging control\n"
"Set terminal line (monitor) logging level\n")
{
- zlog_set_level (NULL, ZLOG_DEST_MONITOR, zlog_get_default_lvl(NULL));
+ int level = zlog_get_default_lvl(NULL) ;
+ zlog_set_level (NULL, ZLOG_DEST_MONITOR, level) ;
+ vty_set_monitor_level(vty, level) ;
return CMD_SUCCESS;
}
@@ -1690,7 +1695,9 @@ DEFUN_CALL (config_log_monitor_level,
if ((level = level_match(argv[0])) == ZLOG_DISABLED)
return CMD_ERR_NO_MATCH;
+
zlog_set_level (NULL, ZLOG_DEST_MONITOR, level);
+ vty_set_monitor_level(vty, level) ;
return CMD_SUCCESS;
}
@@ -1703,6 +1710,7 @@ DEFUN_CALL (no_config_log_monitor,
"Logging level\n")
{
zlog_set_level (NULL, ZLOG_DEST_MONITOR, ZLOG_DISABLED);
+ vty_set_monitor_level(vty, ZLOG_DISABLED) ;
return CMD_SUCCESS;
}
@@ -2033,8 +2041,8 @@ DEFUN_CALL (no_banner_motd,
return CMD_SUCCESS;
}
-/*==============================================================================
- * Current directory handling
+/*------------------------------------------------------------------------------
+ * Set current directory
*/
DEFUN_CALL (do_chdir,
chdir_cmd,
@@ -2069,6 +2077,41 @@ DEFUN_CALL (do_chdir,
} ;
/*------------------------------------------------------------------------------
+ * Show given directory path
+ */
+DEFUN_CALL (do_shdir,
+ shdir_cmd,
+ "shdir DIR",
+ "Show directory\n"
+ "Directory to show\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)
+ vty_out(vty, "%s\n", qpath_string(path)) ;
+ else
+ {
+ vty_out(vty, "%% %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
@@ -2119,7 +2162,7 @@ DEFUN_CALL (lexical_level,
level = strtol(argv[0], NULL, 0) ;
- vty_cmd_set_full_lex(vty, (level != 0)) ;
+ vty->exec->context->full_lex = (level != 0) ;
return CMD_SUCCESS;
}
@@ -2199,6 +2242,7 @@ cmd_init (bool terminal)
install_element (VIEW_NODE, &show_logging_cmd);
install_element (VIEW_NODE, &echo_cmd);
install_element (VIEW_NODE, &chdir_cmd);
+ install_element (VIEW_NODE, &shdir_cmd);
install_element (RESTRICTED_NODE, &config_list_cmd);
install_element (RESTRICTED_NODE, &config_exit_cmd);
@@ -2211,6 +2255,7 @@ cmd_init (bool terminal)
install_element (RESTRICTED_NODE, &config_terminal_no_length_cmd);
install_element (RESTRICTED_NODE, &echo_cmd);
install_element (RESTRICTED_NODE, &chdir_cmd);
+ install_element (RESTRICTED_NODE, &shdir_cmd);
}
if (terminal)
@@ -2227,6 +2272,7 @@ cmd_init (bool terminal)
install_element (ENABLE_NODE, &show_version_cmd);
install_element (ENABLE_NODE, &lexical_level_cmd);
install_element (ENABLE_NODE, &chdir_cmd);
+ install_element (ENABLE_NODE, &shdir_cmd);
if (terminal)
{
diff --git a/lib/command_common.h b/lib/command_common.h
index 3621a4b6..4dd26753 100644
--- a/lib/command_common.h
+++ b/lib/command_common.h
@@ -42,7 +42,7 @@ enum node_type
ENABLE_NODE, /* aka privileged EXEC */
MIN_DO_SHORTCUT_NODE = ENABLE_NODE,
- /* May not "do xxx" at any node lower */
+ /* May not "do xx" at any node lower */
MAX_NON_CONFIG_NODE = ENABLE_NODE,
/* May not be higher than this without owning
* the configuration symbol of power */
@@ -117,8 +117,10 @@ enum cmd_return_code
/* Return codes suitable for command execution functions */
- CMD_WARNING = 1,
- CMD_ERROR,
+ CMD_WARNING = 1, /* command: not 100% successful */
+ CMD_ERROR, /* command: failed badly */
+
+ CMD_CLOSE, /* command: finish up and close vty */
/* Return codes from the command parser */
@@ -129,25 +131,26 @@ enum cmd_return_code
CMD_ERR_AMBIGUOUS, /* parser: more than on command matches */
CMD_ERR_INCOMPLETE,
- CMD_CLOSE, /* command: used by "exit" */
+ /* Return codes used in command loop */
+
+ CMD_HIATUS, /* loop: something requires attention */
+ CMD_STOP, /* loop: stop and close vty (final) */
+ CMD_CANCEL, /* loop: stop and close down to base
+ * vin/vout and discard output. */
+ /* Return codes from I/O layers */
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 :-( */
+ CMD_IO_ERROR, /* I/O: error or time-out */
/* For the chop ???? */
-
- CMD_COMPLETE_FULL_MATCH, /* cmd_completion returns */
- CMD_COMPLETE_MATCH,
- CMD_COMPLETE_LIST_MATCH,
- CMD_COMPLETE_ALREADY,
+//CMD_COMPLETE_FULL_MATCH, /* cmd_completion returns */
+//CMD_COMPLETE_MATCH,
+//CMD_COMPLETE_LIST_MATCH,
+//CMD_COMPLETE_ALREADY,
CMD_SUCCESS_DAEMON, /* parser: success & command is for vtysh ? */
diff --git a/lib/command_execute.c b/lib/command_execute.c
index cc457ba3..cf04bb31 100644
--- a/lib/command_execute.c
+++ b/lib/command_execute.c
@@ -94,6 +94,7 @@ cmd_exec_new(vty vty)
context->dir_cd = qpath_dup(host.cwd) ;
context->dir_home = qpath_dup(host.config_dir) ;
+ context->dir_here = qpath_dup(host.config_dir) ;
switch (vty->type)
{
@@ -107,8 +108,6 @@ cmd_exec_new(vty vty)
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:
@@ -123,8 +122,6 @@ cmd_exec_new(vty vty)
context->can_enable = true ;
context->can_auth_enable = false ;
- context->dir_here = qpath_dup(context->dir_cd) ;
-
break ;
case VTY_CONFIG_READ:
@@ -137,8 +134,6 @@ cmd_exec_new(vty vty)
context->can_enable = true ;
context->can_auth_enable = false ;
- context->dir_here = qpath_dup(context->dir_home) ;
-
break ;
default:
@@ -190,7 +185,7 @@ cmd_context_new_save(cmd_context context, qpath file_here)
} ;
/* The inheritance of can_enable is tricky. Will not bequeath can_enable
- * is is not already in ENABLE_NODE or better !
+ * is is not currently in ENABLE_NODE or better !
*/
if (context->node <= ENABLE_NODE)
context->can_enable = false ;
@@ -205,6 +200,10 @@ cmd_context_new_save(cmd_context context, qpath file_here)
*
* Has to free the directories in the context being restored to.
*
+ * Note that can restore any node it likes: if it was enabled, that's fine; if
+ * it was in some config mode, will still have the symbol of power because
+ * only at vin_depth <= 1 is the symbol of power actually released.
+ *
* Returns NULL.
*/
extern cmd_context
@@ -239,10 +238,10 @@ cmd_context_free(cmd_context context, bool copy)
qpath_free(context->dir_here) ;
} ;
- XFREE(MTYPE_CMD_EXEC, context) ; /* sets context = NULL */
+ XFREE(MTYPE_CMD_EXEC, context) ;
} ;
- return context ;
+ return NULL ;
} ;
/*------------------------------------------------------------------------------
@@ -253,8 +252,19 @@ cmd_exec_free(cmd_exec exec)
{
if (exec != NULL)
{
- exec->parsed = cmd_parsed_free(exec->parsed) ;
+ exec->vty = NULL ; /* no longer required. */
+
exec->context = cmd_context_free(exec->context, false) ; /* not a copy */
+ cmd_action_clear(exec->action) ;
+ exec->parsed = cmd_parsed_free(exec->parsed) ;
+
+ if (vty_nexus)
+ exec->cq.mqb = mqb_free(exec->cq.mqb) ;
+ else if (exec->cq.thread != NULL)
+ {
+ thread_cancel(exec->cq.thread) ;
+ exec->cq.thread = NULL ;
+ } ;
XFREE(MTYPE_CMD_EXEC, exec) ;
} ;
@@ -266,50 +276,6 @@ cmd_exec_free(cmd_exec exec)
*
*/
/*------------------------------------------------------------------------------
- * Parse and execute a command.
- *
- * The command is given by vty->buf and vty->node.
- *
- * Uses vty->parsed.
- *
- * -- use exact/completion parsing, as required.
- *
- * -- parse in current node and in ancestors, as required
- *
- * If does not find in any ancestor, return error from current node.
- *
- * -- implement the "do" shortcut, as required
- *
- * If qpthreads_enabled, then may queue the command rather than execute it
- * here.
- *
- * The vty->node may be changed during the execution of the command, and may
- * be returned changed once the command has completed.
- *
- * NB: expects to have free run of everything in the vty structure (except
- * the contents of the vty_io sub-structure) until the command completes.
- */
-#if 0
-extern enum cmd_return_code
-cmd_execute_command(struct vty *vty,
- cmd_parse_type_t type, struct cmd_command **cmd)
-{
- enum cmd_return_code ret ;
-
- /* Try to parse in vty->node or, if required, ancestors thereof. */
- ret = cmd_parse_command(vty->exec->parsed, vty->line, vty->node, type) ;
-
- if (cmd != NULL)
- *cmd = vty->exec->parsed->cmd ; /* for vtysh */
-
- if (ret == CMD_SUCCESS)
- ret = cmd_dispatch(vty, cmd_may_queue) ;
-
- return ret ;
-} ;
-#endif
-
-/*------------------------------------------------------------------------------
* Read configuration from file.
*
* In the qpthreads world this assumes that it is running with the vty
@@ -339,7 +305,7 @@ cmd_read_config(struct vty *vty, cmd_command first_cmd, bool ignore_warning)
{
cmd_exec exec = vty->exec ;
cmd_parsed parsed = exec->parsed ;
- cmd_return_code_t ret;
+ cmd_return_code_t ret, hret ;
ret = CMD_SUCCESS ; /* so far, so good */
@@ -361,11 +327,10 @@ cmd_read_config(struct vty *vty, cmd_command first_cmd, bool ignore_warning)
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_STOP)
+ return CMD_SUCCESS ; /* the expected outcome ! */
+
if (ret != CMD_SUCCESS)
break ;
} ;
@@ -439,18 +404,21 @@ cmd_read_config(struct vty *vty, cmd_command first_cmd, bool ignore_warning)
* 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_CLOSE is also impossible -- will have been given to vty_cmd_hiatus(),
+ * which never returns it.
*
* 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) ;
+ qassert(ret != CMD_SUCCESS) ;
+ qassert(ret != CMD_EMPTY) ;
+ qassert(ret != CMD_EOF) ;
+ qassert(ret != CMD_CLOSE) ;
+ qassert(ret != CMD_WAITING) ;
- vty_cmd_hiatus(vty, ret) ;
+ hret = ret ;
+ do
+ hret = vty_cmd_hiatus(vty, hret) ;
+ while (hret == CMD_IO_ERROR) ;
return ret ;
} ;
@@ -473,13 +441,13 @@ cmd_open_pipes(vty vty)
cmd_parsed parsed = exec->parsed ;
cmd_return_code_t ret ;
vty_io vio ;
- bool sync_depth ;
+ bool after ;
VTY_LOCK() ;
vio = vty->vio ;
ret = CMD_SUCCESS ;
- sync_depth = false ; /* assuming no in pipe */
+ after = false ; /* assuming no in pipe */
/* Deal with any in pipe stuff */
if ((parsed->parts & cmd_part_in_pipe) != 0)
@@ -500,7 +468,7 @@ cmd_open_pipes(vty vty)
qs_free(args) ;
- sync_depth = true ;
+ after = true ;
} ;
/* Deal with any out pipe stuff */
@@ -513,18 +481,15 @@ cmd_open_pipes(vty vty)
if ((parsed->out_pipe & cmd_pipe_file) != 0)
ret = uty_cmd_open_out_pipe_file(vio, exec->context, args,
- parsed->out_pipe) ;
+ parsed->out_pipe, after) ;
else if ((parsed->out_pipe & cmd_pipe_shell) != 0)
ret = uty_cmd_open_out_pipe_shell(vio, exec->context, args,
- parsed->out_pipe) ;
+ parsed->out_pipe, after) ;
else if ((parsed->out_pipe & cmd_pipe_dev_null) != 0)
- ret = uty_cmd_open_out_dev_null(vio) ;
+ ret = uty_cmd_open_out_dev_null(vio, after) ;
else
zabort("invalid out pipe state") ;
- if (sync_depth && (ret == CMD_SUCCESS))
- uty_vout_sync_depth(vio) ;
-
qs_free(args) ;
} ;
@@ -540,7 +505,7 @@ cmd_open_pipes(vty vty)
* 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
+ * CMD_EOF -- close the current input
*
* NB: the distinction between CMD_WARNING and CMD_ERROR is that CMD_WARNING
* may be ignored when reading a configuration file.
@@ -575,7 +540,7 @@ cmd_execute(vty vty)
* 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 the new node is NULL_NODE, then treat as CMD_EOF.
*/
if (context->node != parsed->nnode)
{
@@ -583,7 +548,7 @@ cmd_execute(vty vty)
vty_cmd_config_lock_check(vty, context->node) ;
if (context->node == NULL_NODE)
- ret = CMD_CLOSE ;
+ ret = CMD_EOF ;
} ;
/* The command should (no longer) change the vty->node, but if it does,
@@ -596,7 +561,7 @@ cmd_execute(vty vty)
{
/* Enforce restrictions on return codes. */
assert((ret == CMD_WARNING) || (ret == CMD_ERROR)
- || (ret == CMD_CLOSE)) ;
+ || (ret == CMD_EOF)) ;
} ;
return ret ;
diff --git a/lib/command_parse.c b/lib/command_parse.c
index 81e4ad74..9204db70 100644
--- a/lib/command_parse.c
+++ b/lib/command_parse.c
@@ -1955,7 +1955,7 @@ cmd_parse_out_pipe(cmd_parsed parsed, cmd_token t)
switch (cpp_getch(p))
{
case '|':
- parsed->out_pipe = cmd_pipe_shell | cmd_pipe_shell_only ;
+ parsed->out_pipe = cmd_pipe_shell | cmd_pipe_shell_cmd ;
break ;
case '>':
diff --git a/lib/command_parse.h b/lib/command_parse.h
index d2c53385..ee219f84 100644
--- a/lib/command_parse.h
+++ b/lib/command_parse.h
@@ -485,7 +485,7 @@ enum cmd_pipe_type /* bit significant */
cmd_pipe_append = BIT(4), /* >> */
/* For out shell pipes */
- cmd_pipe_shell_only = BIT(4), /* | at start of line */
+ cmd_pipe_shell_cmd = BIT(4), /* | at start of line */
} ;
typedef enum cmd_pipe_type cmd_pipe_type_t ;
diff --git a/lib/command_queue.c b/lib/command_queue.c
index 7946d0e8..b80b2c9f 100644
--- a/lib/command_queue.c
+++ b/lib/command_queue.c
@@ -33,78 +33,77 @@
/*==============================================================================
* This command loop processes commands for VTY_TERMINAL and VTY_SHELL_SERVER.
*
- * Commands appear from those sources, driven by socket I/O. Once a command
- * line is ready it is passed out of the I/O pselect/select driven code
- * as a message, and enters the command loop.
+ * The command loop is a form of co-routine, which is "called" by sending a
+ * message (or by legacy threads events). In the multi-pthread world, the
+ * command loop may run in either the vty_cli_nexus or the vty_cmd_nexus, and
+ * may be passed from one to the other by sending a message.
*
- * If pipes are involved that is all dealt with in the command loop, which
- * runs until the vin/vout stacks return to 0 -- the VTY_TERMINAL and
- * VTY_SHELL_SERVER.
+ * The command loop is usually in one of three states:
*
- * There are further issues: fix to current state TODO
+ * vc_running -- the command loop is running, doing things.
*
- * 1) in the qpthreads world, commands are parsed in the CLI thread, but most
- * are executed in the Routing thread. So parsed commands are passed, by
- * message between threads.
+ * In this state the vty and the vty->exec are in the hands
+ * of the command loop.
*
- * 2) input is expected to be non-blocking -- so the command loop will
- * exit if a command line cannot be delivered immediately, and will be
- * returned to later.
+ * The vty->vio is always accessed under VTY_LOCK().
*
- * 3) while a VTY is in the command loop it is marked vio->cmd_running. TODO
+ * Note that one of the things the command loop may be
+ * doing is waiting for a message to be picked up (or
+ * a legacy thread event to be dispatched).
*
- * 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().
+ * vc_waiting -- the command loop is waiting for something to happen
+ * (typically some I/O), and will stay there until a
+ * message is sent (or a legacy threads event).
*
- * 4) opening pipes is done in the CLI thread, in case of any possible
- * blocking.
+ * To be waiting the command loop must have entered "hiatus"
+ * and then exited.
*
- * 5) all output is to fifo buffers -- when output is pushed the CLI side
- * is kicked to manage all output via pselect/select.
+ * When a command loop is or has been stopped, to goes to vc_stopped state.
*
- * In vty_io_basic() it is possible to set read/write ready and associated
- * timeouts while running in the CMD thread, but that too depends on the
- * passing of messages to the CLI thread.
+ * There are further issues:
*
- * The smooth running of the command handling depends on the continued
- * running of the CLI thread.
+ * * In the mult-pthreaded world, commands may be parsed in either pthread,
+ * but most are executed in the Routing thread. So may switch pthreads
+ * after parsing a command -- by sending a message.
*
- * 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: TODO
+ * * opening pipes is done in the vty_cli_nexus, in case of any possible
+ * blocking and because the pselect() stuff is all done in the
+ * vty_cli_nexus.
*
- * - on the vty_cli_nexus queue (or the combined queue)
- * - executing in the vty_cli_nexus (or the single "both" nexus)
- * - on the vty_cmd_nexus queue (if different)
- * - executing in the vty_cmd_nexus (if different)
- *
- * Or, in the legacy threads world:
+ * * all output is via fifo buffers -- when output is pushed, as much as
+ * possible is done immediately, but if necessary the pselect() process
+ * (in the vty_cli_nexus) is left to keep things moving.
*
- * - on the event queue
- * - executing
+ * In vty_io_basic() it is possible to set read/write ready and associated
+ * timeouts while running in the vty_cmd_nexus, but note that this is done
+ * by sending messages to the CLI thread (if multi-pthreaded).
*
- * Where there is only one pthread (and in the legacy threads world) things are
- * easy.
+ * The smooth running of the command handling depends on the continued
+ * running of the CLI thread.
*
+ * To close a VTY must (eventually) arrange for the state to not be vc_running.
+ * While a vty is vc_running, the command loop may be in either nexus, and
+ * may be:
*
- * This revoke runs in the CLI thread, and will catch the message if it is
- * on either queue, and vty_revoke() will deal with it -- still in the CLI
- * thread.
+ * - on the vty_cli_nexus queue (or the combined queue)
+ * - executing cq_process() in the vty_cli_nexus (or the single "both" nexus)
+ * - on the vty_cmd_nexus queue (if different)
+ * - executing cq_process() in the vty_cmd_nexus (if different)
*
- * The command cannot be running in the CLI thread, since that is where we
- * are !
+ * where being on the queue means that there is a message waiting to be
+ * picked up on that queue, which will end up calling cq_process().
*
- * That leaves the command running in the CMD thread. That will run to
- * completion... the VTY may be closed in the meantime, which will shut down
- * the reading side, so the command loop will come to a halt quite quickly.
- * Note, however, that the smooth running of this process requires the CLI
- * thread and its messages to be
+ * Or, in the legacy threads world:
*
+ * - on the event queue -- waiting for event handler to call cq_process()
+ * - executing cq_process()
*
+ * To bring the command loop to a stop will call cq_revoke to revoke any
+ * pending message or pending legacy event. If that succeeds, then the
+ * command loop can be stopped immediately. If not, then vc_running implies
+ * it is executing in cq_process() in one of the nexuses.
*/
-
-
-
/*------------------------------------------------------------------------------
* Prototypes
*/
@@ -127,7 +126,7 @@ cq_loop_enter(vty vty, cmd_return_code_t ret)
{
VTY_ASSERT_CLI_THREAD() ;
- assert(vty->exec->state == exec_null) ;
+ qassert(vty->exec->state == exec_null) ;
cq_enqueue(vty, vty_cli_nexus, exec_done_cmd, ret) ;
} ;
@@ -140,7 +139,7 @@ cq_continue(vty vty, cmd_return_code_t ret)
{
VTY_ASSERT_CLI_THREAD() ;
- assert(vty->exec->state == exec_hiatus) ;
+ qassert(vty->exec->state == exec_hiatus) ;
cq_enqueue(vty, vty_cli_nexus, exec_hiatus, ret) ;
} ;
@@ -150,6 +149,8 @@ cq_continue(vty vty, cmd_return_code_t ret)
*
* Will execute in the current exec->state, passing in the given return
* code.
+ *
+ * Note that the vio->state *must* be vc_running.
*/
static void
cq_enqueue(vty vty, qpn_nexus dst, cmd_exec_state_t state,
@@ -157,7 +158,7 @@ cq_enqueue(vty vty, qpn_nexus dst, cmd_exec_state_t state,
{
cmd_exec exec = vty->exec ;
- assert((dst == vty_cli_nexus) || (dst == vty_cmd_nexus)) ;
+ qassert((dst == vty_cli_nexus) || (dst == vty_cmd_nexus)) ;
exec->locus = dst ;
exec->state = state ;
@@ -175,7 +176,7 @@ cq_enqueue(vty vty, qpn_nexus dst, cmd_exec_state_t state,
}
else
{
- assert(vty_cli_nexus == vty_cmd_nexus) ;
+ qassert(vty_cli_nexus == vty_cmd_nexus) ;
exec->cq.thread = thread_add_event(vty_master, cq_thread, vty, 0) ;
} ;
} ;
@@ -183,7 +184,11 @@ 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.... TODO
+ * Note that if the message is revoked, then the state is set vc_stopped.
+ * We revoke when stopping a command loop -- see cq_revoke, below. If the
+ * message is revoked at any other time then the command loop is, indeed,
+ * stopped -- the I/O side may plow on until buffers empty, but the
+ * command loop will not cq_continue(). At
*/
static void
cq_action(mqueue_block mqb, mqb_flag_t flag)
@@ -194,33 +199,35 @@ cq_action(mqueue_block mqb, mqb_flag_t flag)
vty = mqb_get_arg0(mqb);
- assert(vty->exec->cq.mqb == mqb) ;
+ qassert(vty->exec->cq.mqb == mqb) ;
if (flag == mqb_action)
- return cq_process(vty, vty->exec->state, vty->exec->ret) ;
+ cq_process(vty, vty->exec->state, vty->exec->ret) ;
/* do not touch vty on way out */
+ else
+ {
+ /* Revoke action. */
+ mqb_free(mqb) ;
+ vty->exec->cq.mqb = NULL ;
- /* Revoke action. */
- mqb_free(mqb) ;
- vty->exec->cq.mqb = NULL ;
+ vty_cmd_set_stopped(vty) ; /* enforced stop of loop */
+ } ;
} ;
/*------------------------------------------------------------------------------
* Deal with command message -- in the legacy threads world.
- *
- * Note that if the command is revoked.... TODO
*/
static int
cq_thread(struct thread* thread)
{
vty vty = THREAD_ARG(thread) ;
- assert(vty->exec->cq.thread == thread) ;
+ qassert(vty->exec->cq.thread == thread) ;
vty->exec->cq.thread = NULL ;
cq_process(vty, vty->exec->state, vty->exec->ret) ;
- /* do not touch vty on way out */
+ /* do not touch vty on way out */
return 0 ;
} ;
@@ -248,7 +255,7 @@ cq_thread(struct thread* thread)
*
* 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_STOP => close the vty and leave command loop
* CMD_IO_ERROR => loop back and deal with the error
*
* The cq_loop_enter() and cq_continue() operations send a message (to the
@@ -261,7 +268,11 @@ cq_thread(struct thread* thread)
* 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.
+ * and/or write ready state -- which may generate messages to the
+ * vty_cli_nexus.
+ *
+ * NB: on exit from cq_process the VTY may have been closed down and released,
+ * so do NOT depend on its existence.
*/
static void
cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret)
@@ -341,7 +352,7 @@ cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret)
ret = vty_cmd_reflect_line(vty) ;
if ((ret != CMD_SUCCESS) && (ret != CMD_WAITING))
- break ;
+ break ; /* CMD_IO_ERROR or CMD_HIATUS */
} ;
fall_through ;
@@ -447,20 +458,28 @@ cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret)
/*--------------------------------------------------------------------
* Hiatus state -- some return code to be dealt with !
+ *
+ * If we are not in the vty_cli_nexus, then must pass the problem
+ * to the vty_cli_nexus. If the return code is CMD_STOP, or there
+ * is a CMD_STOP signal, then drop the config symbol of power -- see
+ * uty_close().
*/
case exec_hiatus:
if (exec->locus != vty_cli_nexus)
- return cq_enqueue(vty, vty_cli_nexus, exec_hiatus, ret) ;
+ {
+ vty_cmd_check_stop(vty, ret) ;
+ 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.
+ /* Let vty_cmd_hiatus() deal with the return code and/or any
+ * stop/error/etc trap, and adjust the stack as required.
*/
ret = vty_cmd_hiatus(vty, ret) ;
if (ret == CMD_SUCCESS)
- break ;
+ break ; /* back to exec_fetch */
if (ret == CMD_WAITING)
{
@@ -468,7 +487,10 @@ cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret)
return ; /* <<< DONE, pro tem */
} ;
- if (ret == CMD_EOF)
+ if (ret == CMD_IO_ERROR)
+ continue ; /* give error back to hiatus */
+
+ if (ret == CMD_STOP)
{
exec->state = exec_stopped ;
vty_cmd_loop_exit(vty) ;
@@ -476,9 +498,6 @@ cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret)
return ; /* <<< DONE, permanently */
} ;
- if (ret == CMD_IO_ERROR)
- continue ;
-
zabort("invalid return from vty_cmd_hiatus()") ;
} ;
@@ -524,7 +543,13 @@ cq_process(vty vty, cmd_exec_state_t state, cmd_return_code_t ret)
* Note that the revoke does not affect any vty_cli_nexus messages associated
* with the vty_io_basic operations.
*
+ * If succeeds in revoking, then will have set the state to vc_stopped, and
+ * will have released the message block or legacy thread object.
+ *
* Returns: true <=> have revoked a pending message/event
+ *
+ * NB: if no command loop has ever been started for this vty, then will not
+ * find anything to revoke.
*/
extern bool
cq_revoke(vty vty)
@@ -541,7 +566,16 @@ cq_revoke(vty vty)
ret = mqueue_revoke(vty_cmd_nexus->queue, vty, 1) ;
}
else
- ret = thread_cancel_event(vty_master, vty) ;
+ {
+ ret = thread_cancel_event(vty_master, vty) ;
+
+ if (ret != 0)
+ {
+ /* Make sure in same state as after mqueue_revoke. */
+ vty_cmd_set_stopped(vty) ;
+ vty->exec->cq.thread = NULL ;
+ } ;
+ } ;
/* If revoked message/event, the command loop is stopped. */
if (ret != 0)
diff --git a/lib/keystroke.c b/lib/keystroke.c
index 2b5d8128..e4dc2540 100644
--- a/lib/keystroke.c
+++ b/lib/keystroke.c
@@ -250,15 +250,17 @@
enum stream_state
{
- kst_null, /* nothing special (but see iac) */
+ kst_between, /* between keystrokes */
kst_char, /* collecting a multi-byte character */
kst_cr, /* collecting '\r''\0' or '\r''\n' */
kst_esc, /* collecting an ESC sequence */
kst_csi, /* collecting an ESC '[' or CSI sequence */
+ kst_iac, /* seen an IAC -- previous state pushed. */
kst_iac_option, /* waiting for option (just seen IAC X) */
kst_iac_sub, /* waiting for IAC SE */
+ kst_iac_sub_iac, /* seen an IAC in iac_sub state */
} ;
typedef enum stream_state stream_state_t ;
@@ -270,26 +272,37 @@ struct keystroke_state
} ;
typedef struct keystroke_state keystroke_state_t ;
+typedef enum
+{
+ steal_none = 0, /* not stealing */
+ steal_next, /* steal next complete kestroke */
+ steal_this, /* steal this keystroke when complete */
+ steal_done /* have a stolen keystroke in hand */
+} steal_state_t ;
+
+enum { max_cntrl = 0x1F } ;
+
struct keystroke_stream
{
vio_fifo fifo ; /* the keystrokes */
- keystroke_callback* iac_callback ;
- void* iac_callback_context ;
+ keystroke_callback* callback ;
+ void* callback_context ;
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 */
+ steal_state_t steal ; /* whether/how/if stealing */
+ keystroke_t stolen ; /* what has been stolen */
keystroke_state_t in ; /* current keystroke being collected */
keystroke_state_t pushed_in ;
/* keystroke interrupted by IAC */
+
+ bool interrupt[max_cntrl + 1] ;
} ;
/* Buffering of keystrokes */
@@ -299,28 +312,26 @@ enum { keystroke_buffer_len = 2000 } ; /* should be plenty ! */
/*------------------------------------------------------------------------------
* Prototypes
*/
-static void keystroke_in_push(keystroke_stream stream, uint8_t u) ;
+static void keystroke_in_push(keystroke_stream stream) ;
static void keystroke_in_pop(keystroke_stream stream) ;
-inline static bool keystroke_set_null(keystroke_stream stream,
- keystroke stroke) ;
-inline static uint8_t keystroke_get_byte(keystroke_stream stream) ;
+inline static void keystroke_set_null(keystroke stroke,
+ keystroke_stream stream) ;
+inline static void keystroke_set_char(keystroke stroke, uint8_t u) ;
+inline static void keystroke_set_esc(keystroke stroke, keystroke_type_t type,
+ keystroke_stream stream) ;
+
+
inline static void keystroke_add_raw(keystroke_stream stream, uint8_t u) ;
-static void keystroke_put_char(keystroke_stream stream, uint32_t u) ;
-inline static void keystroke_put_esc(keystroke_stream stream, uint8_t u,
- int len) ;
-inline static void keystroke_put_csi(keystroke_stream stream, uint8_t u) ;
-static void keystroke_put_iac_one(keystroke_stream stream, uint8_t u);
-static void keystroke_put_iac_long(keystroke_stream stream, bool broken) ;
-static void keystroke_clear_iac(keystroke_stream stream) ;
-static void keystroke_steal_char(keystroke steal, keystroke_stream stream,
- uint8_t u) ;
-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, keystroke_compound_t type,
- bool broken, uint8_t* bytes, int len) ;
+
+static void keystroke_put_char(keystroke_stream stream, uint8_t u) ;
+static void keystroke_put_esc(keystroke_stream stream, uint8_t u,
+ keystroke_flags_t flags) ;
+static void keystroke_put_iac(keystroke_stream stream, keystroke_flags_t flags);
+
+static void keystroke_put(keystroke_stream stream,
+ keystroke_type_t type, keystroke_flags_t flags,
+ uint8_t* bytes, uint len) ;
/*==============================================================================
* Creating and freeing keystroke streams and keystroke stream buffers.
@@ -331,20 +342,23 @@ static void keystroke_put(keystroke_stream stream, keystroke_compound_t type,
*
* Can set CSI character value. '\0' => none. (As does '\x1B' !)
*
- * The callback function is called when an IAC sequence is seen, the callback
- * is:
+ * Can set any control character (other than '\0', '\r', '\n', ESC or CSI)
+ * to be an "interrupt" character.
+ *
+ * The callback function is called when an IAC sequence or an "interrupt"
+ * control character is seen. The callback is:
*
* bool callback(void* context, keystroke stroke)
*
* see: #define keystroke_iac_callback_args
* and: typedef for keystroke_callback
*
- * The callback must return true iff the IAC sequence has been dealt with, and
+ * The callback must return true iff the keystroke has been dealt with, and
* should NOT be stored for later processing.
*/
extern keystroke_stream
-keystroke_stream_new(uint8_t csi_char, keystroke_callback* iac_callback,
- void* iac_callback_context)
+keystroke_stream_new(uint8_t csi_char, const char* interrupts,
+ keystroke_callback* callback, void* callback_context)
{
keystroke_stream stream ;
@@ -352,29 +366,51 @@ keystroke_stream_new(uint8_t csi_char, keystroke_callback* iac_callback,
/* Zeroising the structure sets:
*
- * iac_callback = NULL -- none
- * iac_callback_context = NULL -- none
+ * fifo = NULL -- see below
+ *
+ * callback = NULL -- see below
+ * callback_context = NULL -- see below
+ *
+ * CSI = '\0' -- see below
*
* 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
+ * steal = steal_none -- no stealing going on
+ * stolen = all zeros not significant until steal_done
+ *
+ * in.state = kst_between
* in.len = 0 -- nothing in the buffer
*
* pushed_in.state ) ditto
* pushed_in.len )
+ *
+ * interrupt = all false
*/
- confirm(kst_null == 0) ;
+ confirm(kst_between == 0) ;
+ confirm(steal_none == 0) ;
- stream->iac_callback = iac_callback ;
- stream->iac_callback_context = iac_callback_context ;
+ stream->callback = callback ;
+ stream->callback_context = callback_context ;
stream->fifo = vio_fifo_new(keystroke_buffer_len) ;
stream->CSI = (csi_char != '\0') ? csi_char : 0x1B ;
+ if (interrupts != NULL)
+ {
+ while (*interrupts != '\0')
+ {
+ if ((*interrupts > max_cntrl) || (*interrupts == '\r')
+ || (*interrupts == '\n')
+ || (*interrupts == '\x1B')
+ || (*interrupts == stream->CSI))
+ zabort("invalid 'interrupt' character") ;
+
+ stream->interrupt[(uint8_t)*interrupts++] = true ;
+ } ;
+ } ;
+
return stream ;
} ;
@@ -403,6 +439,7 @@ keystroke_stream_free(keystroke_stream stream)
/*------------------------------------------------------------------------------
* See if given keystroke stream is empty
*
+ * case kst_iac_option:
* May or may not be at "EOF", see below.
*
* Returns: true <=> is empty
@@ -418,14 +455,12 @@ keystroke_stream_empty(keystroke_stream stream)
*
* * keystroke stream is empty
*
- * * there is no partial keystroke in construction
- *
* * EOF has been signalled by a suitable call of keystroke_input().
*
- * Returns: true <=> is at EOF
+ * Returns: true <=> is at EOF (or is NULL !)
*/
extern bool
-keystroke_stream_eof(keystroke_stream stream)
+keystroke_stream_at_eof(keystroke_stream stream)
{
/* Note that when EOF is signalled, any partial keystroke in construction
* is converted to a broken keystroke and placed in the stream.
@@ -435,6 +470,21 @@ keystroke_stream_eof(keystroke_stream stream)
} ;
/*------------------------------------------------------------------------------
+ * See if given keystroke stream has seen "EOF" yet, that is:
+ *
+ * * EOF has been signalled by a suitable call of keystroke_input().
+ *
+ * NB: the keystroke stream may not be empty.
+ *
+ * Returns: true <=> is at EOF (or is NULL !)
+ */
+extern bool
+keystroke_stream_met_eof(keystroke_stream stream)
+{
+ return (stream == NULL) || stream->eof_met ;
+} ;
+
+/*------------------------------------------------------------------------------
* Set keystroke stream to "EOF", that is:
*
* * discard contents of the stream, including any partial keystroke.
@@ -449,42 +499,184 @@ keystroke_stream_set_eof(keystroke_stream stream, bool timed_out)
stream->eof_met = true ; /* essential information */
stream->timed_out = timed_out ; /* variant of eof */
- stream->steal_this = false ; /* keep tidy */
- stream->iac = false ;
- stream->in.state = kst_null ;
- stream->pushed_in.state = kst_null ;
+ stream->steal = steal_none ; /* keep tidy */
+ stream->in.state = kst_between ;
+ stream->pushed_in.state = kst_between ;
} ;
/*==============================================================================
- * Input raw bytes to given keyboard stream.
- *
- * To steal the next keystroke, pass 'steal' = address of a keystroke structure.
- * Otherwise, pass NULL.
- *
- * Note: when trying to steal, will complete any partial keystroke before
- * stealing the next one. May exit from here:
- *
- * a. without having completed the partial keystroke.
+ * Getting and stealing keystrokes.
+ */
+
+/*------------------------------------------------------------------------------
+ * Get next keystroke from keystroke stream
*
- * b. without having completed the keystroke to be stolen.
+ * Returns: true => have a stroke type != ks_null
+ * false => stroke type is ks_null (may be EOF).
+ */
+extern bool
+keystroke_get(keystroke_stream stream, keystroke stroke)
+{
+ int b ;
+
+ /* Get first byte and deal with FIFO empty response */
+ b = vio_fifo_get_byte(stream->fifo) ;
+
+ if (b < 0)
+ {
+ keystroke_set_null(stroke, stream) ;
+ return false ;
+ } ;
+
+ /* Fetch first byte and deal with the simple character case */
+ if ((b & kf_compound) == 0) /* Simple character ? */
+ {
+ keystroke_set_char(stroke, b) ;
+ return true ;
+ } ;
+
+ /* Sex the compound keystroke */
+
+ stroke->type = b & kf_type_mask ;
+ stroke->value = 0 ;
+ stroke->flags = b & kf_flag_mask ;
+ stroke->len = vio_fifo_get_byte(stream->fifo) ; /* -1 <=> end */
+
+ /* Fetch what we need to the stroke buffer */
+ if (stroke->len > 0)
+ {
+ uint get ;
+
+ assert(stroke->len <= keystroke_max_len) ;
+
+ get = vio_fifo_get_bytes(stream->fifo, stroke->buf, stroke->len) ;
+
+ assert(get == stroke->len) ;
+ }
+ else
+ assert(stroke->len == 0) ;
+
+ /* Complete the process, depending on the type */
+ switch (stroke->type)
+ {
+ case ks_null:
+ zabort("ks_null found in FIFO") ;
+
+ case ks_char:
+ /* If character is well formed, set its value */
+ if (stroke->flags == 0)
+ {
+ uint i ;
+ assert((stroke->len > 0) && (stroke->len <= 4)) ;
+ for (i = 0 ; i < stroke->len ; ++i)
+ stroke->value = (stroke->value << 8) + stroke->buf[i] ;
+
+ /* NB: to do UTF-8 would need to create UTF form here */
+
+ } ;
+ break ;
+
+ case ks_esc:
+ /* If have ESC X, set value = X */
+ if (stroke->len == 1)
+ stroke->value = stroke->buf[0] ;
+ else
+ assert(stroke->len == 0) ;
+ break ;
+
+ case ks_csi:
+ /* If have the final X, set value = X */
+ /* Null terminate the parameters */
+ if (stroke->len != 0)
+ {
+ --stroke->len ;
+ stroke->value = stroke->buf[stroke->len] ;
+ } ;
+ stroke->buf[stroke->len] = '\0' ;
+ break ;
+
+ case ks_iac:
+ /* If have the command byte after IAC, set value */
+ if (stroke->len > 0)
+ stroke->value = stroke->buf[0] ;
+ break ;
+
+ default:
+ zabort("unknown keystroke type") ;
+ } ;
+
+ return true ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Steal keystroke from keystroke stream
*
- * State (b) is remembered by the keystroke_stream.
+ * The first time this is called, sets the stream into "steal_next" state. As
+ * keystroke input is read -- see keystroke_input() -- the next keystroke to
+ * arrive will be stolen. The first time that this is called after a keystroke
+ * has been stolen, the stolen keystroke will be returned, and the stealing
+ * state reset.
*
- * Caller may have to call several times with steal != NULL to get a
- * keystroke.
+ * Returns: true => have a stolen keystroke type (may be EOF)
+ * false => nothing yet
+ */
+extern bool
+keystroke_steal(keystroke_stream stream, keystroke stroke)
+{
+ /* At EOF we immediately "steal" an eof or timed out ks_null.
+ */
+ if (stream->eof_met)
+ {
+ keystroke_set_null(stroke, stream) ;
+ return true ;
+ } ;
+
+ /* If have a stolen keystroke in hand -- return that and clear
+ * stealing state.
+ */
+ if (stream->steal == steal_done)
+ {
+ *stroke = *stream->stolen ;
+ stream->steal = steal_none ;
+ return true ;
+ } ;
+
+ /* Otherwise, if not already stealing, set stealing state.
+ *
+ * In any event, do not have a stolen keystroke, yet.
+ */
+ if (stream->steal == steal_none)
+ stream->steal = steal_next ;
+
+ return false ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Clear any keystroke stealing state.
*
- * 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.
+ * If keystroke has been stolen, it will be lost.
+ */
+extern void
+keystroke_steal_clear(keystroke_stream stream)
+{
+ stream->steal = steal_none ;
+} ;
+
+/*==============================================================================
+ * Reading of bytes into keystroke stream.
+ */
+
+/*------------------------------------------------------------------------------
+ * Input raw bytes to given keystroke stream.
*
* Note that never steals broken or truncated keystrokes, or IAC.
*
- * Passing len == 0 and ptr == NULL signals EOF to the keystroke_stream.
+ * Passing len < 0 signals EOF to the keystroke_stream.
*
* Updates the stream and returns updated raw
*/
extern void
-keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len,
- keystroke steal)
+keystroke_input(keystroke_stream stream, uint8_t* ptr, int len)
{
uint8_t* end ;
@@ -493,14 +685,12 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len,
* Any partial keystroke is converted into a broken keystroke and placed
* at the end of the stream.
*
- * Note that this occurs before any attempt to steal a keystroke -- so can
- * never steal a broken keystroke.
+ * Note that neither steals no calls back broken keystrokes.
*/
- if ((len == 0) && (ptr == NULL))
+ if (len < 0)
{
stream->eof_met = true ;
stream->timed_out = false ;
- stream->steal_this = false ;
/* Loop to deal with any pending IAC and partial keystroke.
*
@@ -510,85 +700,49 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len,
* A partial IAC sequence may have pushed a partial real keystroke
* sequence -- so loop until have dealt with that.
*/
- do
+ while (stream->in.state != kst_between)
{
switch (stream->in.state)
{
- case kst_null: /* not expecting anything, unless iac */
- keystroke_clear_iac(stream) ;
- break ;
-
- case kst_cr: /* expecting something after CR */
- keystroke_clear_iac(stream) ;
-
- stream->in.len = 0 ;
+ case kst_cr: /* expecting something after CR */
keystroke_add_raw(stream, '\r') ;
- keystroke_put(stream, ks_char, true,
+ keystroke_put(stream, ks_char, kf_broken,
stream->in.raw, stream->in.len) ;
break ;
- case kst_esc: /* expecting rest of escape */
- keystroke_clear_iac(stream) ;
-
- keystroke_put_esc(stream, '\0', 0) ;
- break ;
-
+ case kst_esc: /* expecting rest of escape */
case kst_csi:
- keystroke_clear_iac(stream) ;
-
- keystroke_put_csi(stream, '\0') ;
+ keystroke_put_esc(stream, '\0', kf_broken) ;
break ;
- case kst_iac_option: /* expecting rest of IAC */
- assert(!stream->iac) ;
- /* fall through */
+ /* For incomplete IAC sequences, insert a broken sequence.
+ *
+ * Note that where have just seen an IAC itself, we insert that
+ * now at the end of the sequence.
+ */
+ case kst_iac: /* expecting something after IAC */
+ case kst_iac_sub_iac:
+ keystroke_add_raw(stream, tn_IAC) ;
+ fall_through ;
+
+ case kst_iac_option: /* expecting rest of IAC XX */
case kst_iac_sub:
- keystroke_put_iac_long(stream, true) ;
+ keystroke_put_iac(stream, kf_broken) ;
- /* For kst_iac_sub, an incomplete IAC could be anything, so
- * don't include in the broken IAC, but don't lose it
- * either.
- */
- keystroke_clear_iac(stream) ;
break ;
- case kst_char: /* TBD */
+ case kst_between: /* Cannot be this */
+ case kst_char: /* TBD */
zabort("impossible keystroke stream state") ;
default:
zabort("unknown keystroke stream state") ;
} ;
- assert(!stream->iac) ; /* must have dealt with this */
-
- keystroke_in_pop(stream) ; /* pops kst_null, when all done */
-
- } while (stream->in.state != kst_null) ;
+ keystroke_in_pop(stream) ; /* pops kst_between, when all done */
+ } ;
} ;
- /* Update the stealing state
- *
- * steal != NULL => want to steal a keystroke
- *
- * If do not wish to steal now, must clear any
- * remembered steal_this state.
- *
- * stream->steal_this => steal the next keystroke to complete.
- *
- * If want to steal a keystroke, this is set if
- * currently "between" keystrokes, or later when
- * reach that condition.
- *
- * Once set, this is remembered across calls to
- * keystroke_input(), while still wish to steal.
- */
- if (steal == NULL)
- stream->steal_this = false; /* not now required */
- else
- stream->steal_this = (stream->in.state == kst_null) ;
- /* want to and can can steal the next
- keystroke that completes */
-
/* Once EOF has been signalled, do not expect to receive any further input.
*
* However, keystroke_stream_set_eof() can set the stream artificially at
@@ -612,192 +766,219 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len,
*
* stream->iac means that the last thing seen was an IAC.
*
- * First IAC sets the flag. On the next byte:
- *
- * * if is IAC, clears stream->iac, and lets the escaped IAC value
- * through for further processing -- NOT in IAC state.
- *
- * * if is not IAC, will be let through for further processing, in
- * IAC state.
+ * First IAC changes state as required to process the next byte in
+ * suitable (sub) state.
*/
- if ((u == tn_IAC) && (stream->in.state != kst_iac_option))
+ if (u == tn_IAC)
{
- if (stream->iac)
- stream->iac = false ; /* IAC IAC => single IAC byte value */
- else
- {
- stream->iac = true ; /* seen an IAC */
- continue ; /* wait for next character */
- } ;
- } ;
-
- /* If stream->iac, then need to worry about IAC XX
- *
- * Note that IAC sequences are entirely invisible to the general
- * stream of keystrokes. So... IAC sequences may appear in the middle
- * of multi-byte general keystroke objects.
- *
- * If this is not a simple 2 byte IAC, then must put whatever was
- * collecting to one side, and deal with the IAC.
- *
- * Note: not interested in stealing an IAC object.
- */
- if (stream->iac)
- {
- stream->iac = false ; /* expect will eat the IAC XX */
-
switch (stream->in.state)
{
- case kst_null:
+ /* An IAC may interrupt any ordinary keystroke sequence.
+ *
+ * Switch to kst_iac state and continue by fetching the next byte,
+ * if any.
+ */
+ case kst_between:
case kst_cr:
case kst_esc:
case kst_csi:
- if (u < tn_SB)
- /* This is a simple IAC XX, one byte IAC */
- keystroke_put_iac_one(stream, u) ;
- else
- /* This is a multi-byte IAC, so push whatever real
- * keystroke sequence is currently on preparation, and
- * set into kst_iac_option state.
- */
- keystroke_in_push(stream, u) ;
- break ;
+ keystroke_in_push(stream) ; /* enter kst_iac state */
+ continue ; /* loop back */
- case kst_iac_sub:
- assert(stream->in.raw[0] == tn_SB) ;
+ /* An IAC in kst_iac state is an escaped IAC value, so pop the
+ * state and proceed with the tn_IAC value.
+ */
+ case kst_iac:
+ keystroke_in_pop(stream) ;
+ break ;
- if (u != tn_SE)
- {
- --ptr ; /* put back the XX */
- stream->iac = true ; /* put back the IAC */
- } ;
+ /* An IAC in kst_iac_option state is actually an EXOPL, so
+ * proceed with the EXOPL value.
+ */
+ case kst_iac_option:
+ confirm((uint)tn_IAC == (uint)to_EXOPL) ;
+ break ;
- keystroke_put_iac_long(stream, (u != tn_SE)) ;
- keystroke_in_pop(stream) ;
+ /* An IAC in kst_iac_sub state may be IAC IAC or IAC SE below.
+ *
+ * Switch to kst_iac_sub_iac state and continue by fetching the
+ * next byte, if any.
+ */
+ case kst_iac_sub:
+ stream->in.state = kst_iac_sub_iac ;
+ continue ; /* loop back */
+
+ /* An IAC in kst_iac state is an escaped IAC value, so return
+ * to kst_iac_sub state and proceed with the tn_IAC value.
+ */
+ case kst_iac_sub_iac:
+ stream->in.state = kst_iac_sub ;
break ;
case kst_char: /* TBD */
- case kst_iac_option:
zabort("impossible keystroke stream state") ;
default:
zabort("unknown keystroke stream state") ;
} ;
-
- continue ;
} ;
- /* No IAC complications... proceed per current state */
+ /* Dealt with an IAC, proceed with byte per current state.
+ */
switch (stream->in.state)
{
- case kst_null: /* Expecting anything */
- stream->steal_this = (steal != NULL) ;
-
- if (u == '\r')
- stream->in.state = kst_cr ;
- else if (u == 0x1B)
- stream->in.state = kst_esc ;
- else if (u == stream->CSI) /* NB: CSI == 0x1B => no CSI */
+ /* In kst_between state we are between keystrokes, and may get anything.
+ *
+ * If waiting to steal the next keystroke, this is the (start of the)
+ * next keystroke.
+ */
+ case kst_between:
+ if (stream->steal == steal_next)
+ stream->steal = steal_this ;
+
+ if (u <= max_cntrl)
{
- stream->in.len = 0 ;
- stream->in.state = kst_csi ;
- }
- else
- {
- /* Won't steal NUL */
- if (!stream->steal_this || (u == '\0'))
- keystroke_put_char(stream, u) ;
- else
+ stream->in.len = 0 ; /* make sure */
+
+ if (u == '\0') /* discard NUL */
+ break ;
+
+ if (u == '\r')
+ {
+ stream->in.state = kst_cr ;
+ break ;
+ } ;
+
+ if (u == 0x1B)
+ {
+ stream->in.state = kst_esc ;
+ break ;
+ } ;
+
+ if (u == stream->CSI) /* NB: CSI == 0x1B => no CSI */
{
- keystroke_steal_char(steal, stream, u) ;
- stream->steal_this = false ;
- steal = NULL ;
+ stream->in.state = kst_csi ;
+ break ;
} ;
- stream->in.state = kst_null ;
+ if (stream->interrupt[u] && (stream->callback != NULL))
+ {
+ keystroke_t stroke ;
+
+ keystroke_set_char(stroke, u) ;
+
+ if ((*stream->callback)(stream->callback_context, stroke))
+ break ;
+ } ;
} ;
+
+ /* Simple single byte character: put or steal -- no state change.
+ */
+ keystroke_put_char(stream, u) ;
+
break ;
- case kst_char: /* TBD */
+ /* Multi-byte character handling -- TBD
+ */
+ case kst_char:
zabort("impossible keystroke stream state") ;
+ /* Byte following a '\r'
+ */
case kst_cr: /* expecting something after CR */
- if ((u != '\n') && (u != '\r'))
- {
- if (u != '\0')
- {
- --ptr ; /* put back the duff XX */
- stream->iac = (u == tn_IAC) ;
- /* re=escape if is IAC */
- } ;
- u = '\r' ;
- } ;
+ stream->in.state = kst_between ;
- if (!stream->steal_this)
- keystroke_put_char(stream, u) ;
- else
+ switch (u)
{
- keystroke_steal_char(steal, stream, u) ;
- stream->steal_this = false ;
- steal = NULL ;
+ case '\0':
+ u = '\r' ; /* treat '\r''\0' as '\r' (as expected) */
+
+ case '\r': /* treat '\r''\r' as '\r' (why not) */
+ case '\n': /* treat '\r''\n' as '\n' */
+ break ;
+
+ default:
+ --ptr ; /* put back the duff XX */
+ if (u == tn_IAC)
+ keystroke_in_push(stream) ;
+ /* re-enter kst_iac state */
+ u = '\r' ; /* treat '\r'XX as '\r' */
} ;
- stream->in.state = kst_null ;
+ keystroke_put_char(stream, u) ; /* put or steal */
+
break ;
- case kst_esc: /* Expecting XX after ESC */
+ /* Byte following ESC
+ */
+ case kst_esc:
if (u == '[')
- {
- stream->in.len = 0 ;
- stream->in.state = kst_csi ;
- }
+ stream->in.state = kst_csi ;
else
{
- if (!stream->steal_this)
- keystroke_put_esc(stream, u, 1) ;
- else
- {
- keystroke_steal_esc(steal, stream, u) ;
- stream->steal_this = false ;
- steal = NULL ;
- } ;
-
- stream->in.state = kst_null ;
+ keystroke_put_esc(stream, u, kf_null) ;
+ stream->in.state = kst_between ;
} ;
break ;
- case kst_csi: /* Expecting ... after ESC [ or CSI */
+ /* Byte(s) following CSI or ESC '['
+ */
+ case kst_csi:
if ((u >= 0x20) && (u <= 0x3F))
keystroke_add_raw(stream, u) ;
else
{
- bool ok ;
+ keystroke_flags_t flags = kf_null ;
- ok = stream->in.len < keystroke_max_len ;
- /* have room for terminator */
+ stream->in.state = kst_between ; /* done */
+
+ if (stream->in.len >= keystroke_max_len)
+ {
+ flags |= kf_truncated ;
+ stream->in.len = keystroke_max_len - 1 ;
+ } ;
if ((u < 0x40) || (u > 0x7E))
{
--ptr ; /* put back the duff XX */
- stream->iac = (u == tn_IAC) ;
- /* re=escape if is IAC */
+ if (u == tn_IAC)
+ keystroke_in_push(stream) ;
+ /* re-enter kst_iac state */
u = '\0' ;
- ok = false ; /* broken */
+ flags |= kf_broken ;
} ;
- if (!stream->steal_this || !ok)
- keystroke_put_csi(stream, u) ;
- else
- {
- keystroke_steal_csi(steal, stream, u) ;
- stream->steal_this = false ;
- steal = NULL ;
- } ;
- stream->in.state = kst_null ;
+ keystroke_put_esc(stream, u, flags) ;
} ;
break ;
- case kst_iac_option: /* Expecting <option> after IAC XX */
+ /* Byte following IAC -- where that byte is not itself IAC
+ */
+ case kst_iac: /* IAC XX -- process the XX */
+ keystroke_add_raw(stream, u) ;
+
+ if (u < tn_SB)
+ {
+ /* This is a simple IAC XX, one byte IAC
+ */
+ keystroke_put_iac(stream, kf_null) ;
+ keystroke_in_pop(stream) ;
+ }
+ else
+ {
+ /* This is a multi-byte IAC, so set into kst_iac_option state.
+ */
+ stream->in.state = kst_iac_option ;
+ } ;
+ break ;
+
+ /* Byte following IAC XX -- expecting <option>
+ *
+ * If the XX was SB -> kst_iac_sub state: IAC SB <option> .... IAC SE
+ *
+ * Otherwise, this is a 3 byte IAC: IAC XX <option>
+ */
+ case kst_iac_option:
assert(stream->in.len == 1) ;
keystroke_add_raw(stream, u) ;
@@ -805,28 +986,55 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len,
stream->in.state = kst_iac_sub ;
else
{
- keystroke_put_iac_long(stream, false) ;
+ keystroke_put_iac(stream, kf_null) ;
keystroke_in_pop(stream) ;
} ;
break ;
- case kst_iac_sub: /* Expecting sub stuff */
- assert(stream->in.raw[0]== tn_SB) ;
+ /* Bytes following IAC SB <option> ...
+ *
+ * This IAC is terminated by IAC SE. If an IAC is seen, the next byte
+ * is dealt with in kst_iac_sub_iac state.
+ */
+ case kst_iac_sub:
+ assert(stream->in.raw[0] == tn_SB) ;
keystroke_add_raw(stream, u) ;
break ;
+ /* in kst_iac_sub_iac state, expect:
+ *
+ * IAC -- escaped 0xFF value -- dealt with already.
+ *
+ * SE -- end of kst_iac_sub state
+ *
+ * Nothing else.
+ *
+ * So here the IAC is terminated, broken if don't have an SE, and the
+ * stack popped.
+ *
+ * Then if was broken, put back the XX to be processed in kst_iac state.
+ */
+ case kst_iac_sub_iac:
+ assert(stream->in.raw[0] == tn_SB) ;
+
+ keystroke_put_iac(stream, ((u == tn_SE) ? kf_null : kf_broken)) ;
+ keystroke_in_pop(stream) ;
+
+ if (u != tn_SE)
+ {
+ --ptr ; /* put back the XX */
+ keystroke_in_push(stream) ; /* enter kst_iac state */
+ } ;
+ break ;
+
+
default:
zabort("unknown keystroke stream state") ;
}
} ;
- assert(ptr == end) ;
- /* If did not steal a keystroke, return a ks_null -- which may be
- * a knull_eof.
- */
- if (steal != NULL)
- keystroke_set_null(stream, steal) ;
+ assert(ptr == end) ;
} ;
/*------------------------------------------------------------------------------
@@ -834,141 +1042,36 @@ keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len,
* sequences transparently.
*/
-/* Push current state and set new current state for start of IAC option
- * sequence.
+/* Push current state and set new current state to process byte after IAC.
*/
static void
-keystroke_in_push(keystroke_stream stream, uint8_t u)
+keystroke_in_push(keystroke_stream stream)
{
- assert(stream->pushed_in.state == kst_null) ;
-
- stream->pushed_in = stream->in ;
+ assert(stream->pushed_in.state == kst_between) ;
- stream->in.len = 1 ;
- stream->in.raw[0] = u ;
+ stream->pushed_in = stream->in ;
- stream->in.state = kst_iac_option ;
+ stream->in.state = kst_iac ;
+ stream->in.len = 0 ;
} ;
-/* Pop the pushed state and clear the pushed state to kst_null */
+/* Pop the pushed state and clear the pushed state to kst_between (so further
+ * pop will return that)
+ */
static void
keystroke_in_pop(keystroke_stream stream)
{
stream->in = stream->pushed_in ;
- stream->pushed_in.state = kst_null ;
+ stream->pushed_in.state = kst_between ;
stream->pushed_in.len = 0 ;
} ;
-/*==============================================================================
- * Fetch next keystroke from keystroke stream
- *
- * Returns: true => have a stroke type != ks_null
- * false => stroke type is ks_null (may be EOF).
- */
-extern bool
-keystroke_get(keystroke_stream stream, keystroke stroke)
-{
- int b ;
- uint8_t* p ;
- uint8_t* e ;
-
- /* Get first byte and deal with FIFO empty response */
- b = vio_fifo_get_byte(stream->fifo) ;
-
- if (b < 0)
- return keystroke_set_null(stream, stroke) ;
-
- /* Fetch first byte and deal with the simple character case */
- if ((b & kf_compound) == 0) /* Simple character ? */
- {
- stroke->type = ks_char ;
- stroke->value = b ;
-
- stroke->flags = 0 ;
- stroke->len = 1 ;
- stroke->buf[0] = b ;
-
- return true ;
- } ;
-
- /* Sex the compound keystroke */
-
- stroke->type = b & kf_type_mask ;
- stroke->value = 0 ;
- stroke->flags = b & kf_flag_mask ;
- stroke->len = keystroke_get_byte(stream) ;
-
- assert(stroke->len <= keystroke_max_len) ;
-
- /* Fetch what we need to the stroke buffer */
- p = stroke->buf ;
- e = p + stroke->len ;
-
- while (p < e)
- *p++ = keystroke_get_byte(stream) ;
-
- p = stroke->buf ;
-
- /* Complete the process, depending on the type */
- switch (stroke->type)
- {
- case ks_null:
- zabort("ks_null found in FIFO") ;
-
- case ks_char:
- /* If character is well formed, set its value */
- if (stroke->flags == 0)
- {
- assert((stroke->len > 0) && (stroke->len <= 4)) ;
- while (p < e)
- stroke->value = (stroke->value << 8) + *p++ ;
-
- /* NB: to do UTF-8 would need to create UTF form here */
-
- } ;
- break ;
-
- case ks_esc:
- /* If have ESC X, set value = X */
- if (stroke->len == 1)
- stroke->value = *p ;
- else
- assert(stroke->len == 0) ;
- break ;
-
- case ks_csi:
- /* If have the final X, set value = X */
- /* Null terminate the parameters */
- if (stroke->len != 0)
- {
- --e ;
- stroke->value = *e ;
- --stroke->len ;
- } ;
- *e = '\0' ;
- break ;
-
- case ks_iac:
- /* If have the command byte after IAC, set value */
- if (stroke->len > 0)
- stroke->value = *p ;
- break ;
-
- default:
- zabort("unknown keystroke type") ;
- } ;
-
- return true ;
-} ;
-
/*------------------------------------------------------------------------------
* Set given keystroke to ks_null -- and set to knull_eof if stream->eof_met
- *
- * Returns: 0
*/
-inline static bool
-keystroke_set_null(keystroke_stream stream, keystroke stroke)
+inline static void
+keystroke_set_null(keystroke stroke, keystroke_stream stream)
{
stroke->type = ks_null ;
stroke->value = stream->eof_met ? (stream->timed_out ? knull_timed_out
@@ -976,24 +1079,58 @@ keystroke_set_null(keystroke_stream stream, keystroke stroke)
: knull_not_eof ;
stroke->flags = 0 ;
stroke->len = 0 ;
+} ;
- return false ;
+/*------------------------------------------------------------------------------
+ * Set given keystroke to simple character
+ */
+inline static void
+keystroke_set_char(keystroke stroke, uint8_t u)
+{
+ stroke->type = ks_char ;
+ stroke->value = u ;
+
+ stroke->flags = 0 ;
+ stroke->len = 1 ;
+ stroke->buf[0] = u ;
} ;
/*------------------------------------------------------------------------------
- * Fetch 2nd or subsequent byte of keystroke.
- *
- * NB: it is impossible for partial keystrokes to be written, so this treats
- * buffer empty as a FATAL error.
+ * Set given keystroke to given escape
*/
-inline static uint8_t
-keystroke_get_byte(keystroke_stream stream)
+inline static void
+keystroke_set_esc(keystroke stroke, keystroke_type_t type,
+ keystroke_stream stream)
{
- int b = vio_fifo_get_byte(stream->fifo) ;
+ int len ;
- passert(b >= 0) ;
+ len = stream->in.len - 1 ; /* exclude the escape terminator */
+ assert((len >= 0) && (len < keystroke_max_len)) ;
+
+ if (len > 0)
+ memcpy(stroke->buf, stream->in.raw, len) ;
+ stroke->buf[len] = '\0' ;
- return b ;
+ stroke->type = type ;
+ stroke->value = stream->in.raw[len] ; /* last character */
+ stroke->flags = 0 ;
+ stroke->len = len ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set given keystroke to given escape
+ */
+inline static void
+keystroke_set_iac(keystroke stroke, keystroke_stream stream)
+{
+ assert((stream->in.len >= 1) && (stream->in.len <= keystroke_max_len)) ;
+
+ memcpy(stroke->buf, stream->in.raw, stream->in.len) ;
+
+ stroke->type = ks_iac ;
+ stroke->value = stream->in.raw[0] ;
+ stroke->flags = 0 ;
+ stroke->len = stream->in.len ;
} ;
/*==============================================================================
@@ -1002,6 +1139,8 @@ keystroke_get_byte(keystroke_stream stream)
/*------------------------------------------------------------------------------
* If possible, add character to the stream->in.raw[] buffer
+ *
+ * Note that the stream->in.len continues counting beyond the end of the buffer.
*/
static inline void
keystroke_add_raw(keystroke_stream stream, uint8_t u)
@@ -1013,122 +1152,66 @@ keystroke_add_raw(keystroke_stream stream, uint8_t u)
} ;
/*------------------------------------------------------------------------------
- * Store simple character value
+ * Store simple 8 bit character value, or steal it
*/
static void
-keystroke_put_char(keystroke_stream stream, uint32_t u)
+keystroke_put_char(keystroke_stream stream, uint8_t u)
{
- if (u < kf_compound)
- vio_fifo_put_byte(stream->fifo, (uint8_t)u) ;
+ if (stream->steal == steal_this)
+ {
+ keystroke_set_char(stream->stolen, u) ;
+ stream->steal = steal_done ;
+ }
else
{
- uint8_t buf[4] ;
- uint8_t* p ;
-
- p = buf + 4 ;
-
- do
- {
- *(--p) = u & 0xFF ;
- u >>= 8 ;
- }
- while (u != 0) ;
-
- keystroke_put(stream, ks_char, false, p, (buf + 4) - p) ;
+ if (u < kf_compound)
+ vio_fifo_put_byte(stream->fifo, u) ;
+ else
+ keystroke_put(stream, ks_char, kf_null, &u, 1) ;
} ;
} ;
/*------------------------------------------------------------------------------
- * Store simple ESC. Is broken if length (after ESC) == 0 !
- */
-static void
-keystroke_put_esc(keystroke_stream stream, uint8_t u, int len)
-{
- keystroke_put(stream, ks_esc, (len == 0), &u, len) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Store CSI.
- *
- * Plants the last character of the CSI in the buffer, even if has to overwrite
- * the existing last character -- the sequence is truncated, but this way at
- * least the end of the sequence is preserved.
- *
- * Is broken if u == '\0'. May also be truncated !
+ * Store simple ESC or CSI, or steal it (but won't steal if broken/truncated)
*/
static void
-keystroke_put_csi(keystroke_stream stream, uint8_t u)
+keystroke_put_esc(keystroke_stream stream, uint8_t u, keystroke_flags_t flags)
{
- int l ;
+ keystroke_type_t type ;
- l = stream->in.len++ ;
+ keystroke_add_raw(stream, u) ;
- if (l >= keystroke_max_len)
- l = keystroke_max_len - 1 ;
+ type = (stream->in.state == kst_esc) ? ks_esc : ks_csi ;
- stream->in.raw[l] = u ; /* plant terminator */
-
- keystroke_put(stream, ks_csi, (u == '\0'), stream->in.raw, stream->in.len) ;
+ if ((stream->steal == steal_this)
+ && ((flags & (kf_broken | kf_truncated)) == 0))
+ {
+ keystroke_set_esc(stream->stolen, type, stream) ;
+ stream->steal = steal_done ;
+ }
+ else
+ keystroke_put(stream, type, flags, stream->in.raw, stream->in.len) ;
} ;
/*------------------------------------------------------------------------------
* Store IAC -- if not broken, send it via any call-back.
*/
static void
-keystroke_put_iac(keystroke_stream stream, bool broken, uint8_t* bytes, int len)
+keystroke_put_iac(keystroke_stream stream, keystroke_flags_t flags)
{
bool dealt_with = false ;
- if (!broken && (stream->iac_callback != NULL))
+ if ((stream->callback != NULL) && ((flags & (kf_broken | kf_truncated)) == 0))
{
- struct keystroke stroke ;
-
- assert((len >= 1) && (bytes != NULL) && (len <= keystroke_max_len)) ;
-
- stroke.type = ks_iac ;
- stroke.value = bytes[0] ;
- stroke.flags = 0 ;
- stroke.len = len ;
+ keystroke_t stroke ;
- memcpy(&stroke.buf, bytes, len) ;
+ keystroke_set_iac(stroke, stream) ;
- dealt_with = (*stream->iac_callback)(stream->iac_callback_context,
- &stroke) ;
+ dealt_with = (*stream->callback)(stream->callback_context, stroke) ;
} ;
if (!dealt_with)
- keystroke_put(stream, ks_iac, broken, bytes, len) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Store one byte IAC.
- */
-static void
-keystroke_put_iac_one(keystroke_stream stream, uint8_t u)
-{
- keystroke_put_iac(stream, false, &u, 1) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Store long IAC. Is broken if says it is.
- */
-static void
-keystroke_put_iac_long(keystroke_stream stream, bool broken)
-{
- keystroke_put_iac(stream, broken, stream->in.raw, stream->in.len) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * If in IAC state, issue broken IAC and clear state.
- */
-static void
-keystroke_clear_iac(keystroke_stream stream)
-{
- if (stream->iac)
- {
- keystroke_put_iac(stream, true, NULL, 0) ;
- stream->iac = 0 ;
- } ;
+ keystroke_put(stream, ks_iac, flags, stream->in.raw, stream->in.len) ;
} ;
/*------------------------------------------------------------------------------
@@ -1137,74 +1220,22 @@ keystroke_clear_iac(keystroke_stream stream)
* If len == 0, bytes may be NULL
*/
static void
-keystroke_put(keystroke_stream stream, keystroke_compound_t type, bool broken,
- uint8_t* bytes, int len)
+keystroke_put(keystroke_stream stream, keystroke_type_t type,
+ keystroke_flags_t flags,
+ uint8_t* bytes, uint len)
{
if (len > keystroke_max_len)
{
len = keystroke_max_len ;
- type |= kf_truncated ;
+ flags |= kf_truncated ;
} ;
- vio_fifo_put_byte(stream->fifo,
- kf_compound | (broken ? kf_broken : 0) | type) ;
+ vio_fifo_put_byte(stream->fifo, kf_compound | flags | type) ;
vio_fifo_put_byte(stream->fifo, len) ;
if (len > 0)
vio_fifo_put_bytes(stream->fifo, (void*)bytes, len) ;
} ;
-/*------------------------------------------------------------------------------
- * Steal character value -- cannot be broken
- */
-static void
-keystroke_steal_char(keystroke steal, keystroke_stream stream, uint8_t u)
-{
- steal->type = ks_char ;
- steal->value = u ;
- steal->flags = 0 ;
- steal->len = 1 ;
- steal->buf[0] = u ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Steal simple escape -- cannot be broken
- */
-static void
-keystroke_steal_esc(keystroke steal, keystroke_stream stream, uint8_t u)
-{
- steal->type = ks_esc ;
- steal->value = u ;
- steal->flags = 0 ;
- steal->len = 1 ;
- steal->buf[0] = u ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Steal CSI escape -- cannot be broken or truncated.
- *
- * In the stream-in.raw buffer the last character is the escape terminator,
- * after the escape parameters.
- *
- * In keystroke buffer the escape parameters are '\0' terminated, and the
- * escape terminator is the keystroke value.
- */
-static void
-keystroke_steal_csi(keystroke steal, keystroke_stream stream, uint8_t u)
-{
- int len ;
-
- len = stream->in.len ; /* excludes the escape terminator */
- assert((len < keystroke_max_len) && (u >= 0x40) && (u <= 0x7E)) ;
-
- steal->type = ks_esc ;
- steal->value = u ;
- steal->flags = 0 ;
- steal->len = len ;
-
- memcpy(steal->buf, stream->in.raw, len) ;
- steal->buf[len] = '\0' ;
-} ;
-
/*==============================================================================
*/
diff --git a/lib/keystroke.h b/lib/keystroke.h
index 70aa120b..b04a2e20 100644
--- a/lib/keystroke.h
+++ b/lib/keystroke.h
@@ -35,7 +35,7 @@
enum { keystroke_max_len = 100 } ;
-enum keystroke_type
+typedef enum keystroke_type
{
ks_null = 0, /* nothing, nada, bupkis... */
ks_char, /* character -- uint32_t */
@@ -46,19 +46,21 @@ enum keystroke_type
ks_type_count,
ks_type_reserved = 0x0F,
-} ;
-typedef enum keystroke_type keystroke_type_t ;
+} keystroke_type_t ;
+
CONFIRM(ks_type_count <= ks_type_reserved) ;
-enum keystroke_null
+typedef enum keystroke_null
{
knull_not_eof,
knull_eof,
knull_timed_out,
-};
+} keystroke_null_t ;
-enum keystroke_flags
+typedef enum keystroke_flags
{
+ kf_null = 0,
+
kf_compound = 0x80, /* marker on all compound characters */
kf_reserved = 0x40,
@@ -69,8 +71,7 @@ enum keystroke_flags
kf_flag_mask = 0x70, /* flags for the keystroke */
kf_type_mask = 0x0F, /* extraction of type */
-} ;
-typedef enum keystroke_type keystroke_flags_t ;
+} keystroke_flags_t ;
/* The keystroke type and flags are designed so that they can be packed
* together as a single byte, the first of a "compound character".
@@ -95,8 +96,8 @@ struct keystroke
} ;
typedef struct keystroke keystroke_t[1] ;
-#define keystroke_iac_callback_args void* context, keystroke stroke
-typedef bool (keystroke_callback)(keystroke_iac_callback_args) ;
+#define keystroke_callback_args void* context, keystroke stroke
+typedef bool (keystroke_callback)(keystroke_callback_args) ;
/* Telnet commands/options */
enum tn_Command
@@ -174,25 +175,21 @@ enum tn_Option
/*==============================================================================
* Functions
*/
-extern keystroke_stream
-keystroke_stream_new(uint8_t csi_char, keystroke_callback* iac_callback,
- void* iac_callback_context) ;
-
-extern void
-keystroke_stream_set_eof(keystroke_stream stream, bool timed_out) ;
-
-extern keystroke_stream
-keystroke_stream_free(keystroke_stream stream) ;
-
-extern bool
-keystroke_stream_empty(keystroke_stream stream) ;
-extern bool
-keystroke_stream_eof(keystroke_stream stream) ;
-
-extern void
-keystroke_input(keystroke_stream stream, uint8_t* ptr, size_t len,
- keystroke steal) ;
-extern bool
-keystroke_get(keystroke_stream stream, keystroke stroke) ;
+extern keystroke_stream keystroke_stream_new(uint8_t csi_char,
+ const char* interrupts,
+ keystroke_callback* callback, void* callback_context) ;
+
+extern void keystroke_stream_set_eof(keystroke_stream stream, bool timed_out) ;
+
+extern keystroke_stream keystroke_stream_free(keystroke_stream stream) ;
+
+extern bool keystroke_get(keystroke_stream stream, keystroke stroke) ;
+extern bool keystroke_steal(keystroke_stream stream, keystroke stroke) ;
+extern void keystroke_steal_clear(keystroke_stream stream) ;
+extern bool keystroke_stream_empty(keystroke_stream stream) ;
+extern bool keystroke_stream_at_eof(keystroke_stream stream) ;
+extern bool keystroke_stream_met_eof(keystroke_stream stream) ;
+
+extern void keystroke_input(keystroke_stream stream, uint8_t* ptr, int len) ;
#endif /* _ZEBRA_KEYSTROKE_H */
diff --git a/lib/log.c b/lib/log.c
index 0c76fe95..d4feb75c 100644
--- a/lib/log.c
+++ b/lib/log.c
@@ -41,36 +41,68 @@
#include "sigevent.h"
/*------------------------------------------------------------------------------
- * Prototypes
+ * Logging is managed using struct zlog, which represents a set of logging
+ * destinations, which are output to in parallel, each with its own log
+ * priority.
+ *
+ * Notionally, there may be more than one strct zlog, but in practice there is
+ * only one and that is the one pointed at by zlog_default.
*/
-static void zlog_abort (const char *mess) __attribute__ ((noreturn)) ;
-static void uzlog_backtrace(int priority);
+struct zlog
+{
+ 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 monitor_lvl ; /* last monitor level specified */
+ 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 */
+ char *filename; /* for ZLOG_DEST_FILE */
+
+ int facility; /* as per syslog facility */
+ int syslog_options; /* 2nd arg to openlog */
+
+ int timestamp_precision; /* # of digits of subsecond precision */
+ bool record_priority; /* should messages logged through stdio
+ include the priority of the message? */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Tables of protocol and log priority names.
+ */
const char *zlog_proto_names[] =
{
- "NONE",
- "DEFAULT",
- "ZEBRA",
- "RIP",
- "BGP",
- "OSPF",
- "RIPNG",
- "OSPF6",
- "ISIS",
- "MASC",
+ [ZLOG_NONE] = "NONE",
+ [ZLOG_DEFAULT] = "DEFAULT",
+ [ZLOG_ZEBRA] = "ZEBRA",
+ [ZLOG_RIP] = "RIP",
+ [ZLOG_BGP] = "BGP",
+ [ZLOG_OSPF] = "OSPF",
+ [ZLOG_RIPNG] = "RIPNG",
+ [ZLOG_OSPF6] = "OSPF6",
+ [ZLOG_ISIS] = "ISIS",
+ [ZLOG_MASC] = "MASC",
NULL,
-};
+} ;
const char *zlog_priority[] =
{
- "emergencies",
- "alerts",
- "critical",
- "errors",
- "warnings",
- "notifications",
- "informational",
- "debugging",
+ [LOG_EMERG] = "emergencies",
+ [LOG_ALERT] = "alerts",
+ [LOG_CRIT] = "critical",
+ [LOG_ERR] = "errors",
+ [LOG_WARNING] = "warnings",
+ [LOG_NOTICE] = "notifications",
+ [LOG_INFO] = "informational",
+ [LOG_DEBUG] = "debugging",
NULL,
};
@@ -81,13 +113,19 @@ struct zlog* zlog_default = NULL;
struct zlog* zlog_list = NULL ;
-static volatile sig_atomic_t max_maxlvl = INT_MAX ;
+static volatile sig_atomic_t max_maxlvl = ZLOG_DISABLED ;
qpt_mutex_t log_mutex ;
int log_lock_count = 0 ;
int log_assert_fail = 0 ;
+/*------------------------------------------------------------------------------
+ * Prototypes
+ */
+static void zlog_abort (const char *mess) __attribute__ ((noreturn)) ;
+static void uzlog_backtrace(int priority);
+
/*==============================================================================
* Log locking and relationship with VTY.
*
@@ -155,10 +193,7 @@ log_finish(void)
/*==============================================================================
* 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.
*
@@ -182,6 +217,19 @@ static void uquagga_timestamp(qf_str qfs, int timestamp_precision) ;
/*==============================================================================
* The main logging function.
+ *
+ * This writes the logging information directly to all logging destinations,
+ * except the vty_log(), so that it is externally visible as soon as possible.
+ * This assumes that syslog and file output is essentially instantaneous, and
+ * will not block.
+ *
+ * Does not attempt to pick up or report any I/O errors.
+ *
+ * For vty_log(), any logging is buffered and dealt with by the VTY handler.
+ *
+ * So, having acquired the LOG_LOCK() the logging will proceed without
+ * requiring any further locks. Indeed, apart from vty_log() and
+ * uquagga_timestamp() (TODO) the process is async_signal_safe, even.
*/
extern void
zlog (struct zlog *zl, int priority, const char *format, ...)
@@ -196,28 +244,33 @@ zlog (struct zlog *zl, int priority, const char *format, ...)
* 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:
+ * The max_maxlvl is sig_atomic_t and volatile, and we assume that:
+ *
+ * writing to max_maxlvl is atomic wrt all forms of interrupt,
+ * and wrt any processor cache invalidation.
*
- * 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).
+ * That is:
*
- * b) reading max_maxlvl will either collect the state before some
- * change to the logging levels, or after.
+ * the variable cannot be read in a partly written state (with,
+ * say, some bytes of the old value and some of the new).
*
- * If passes the initial test, immediately acquires the LOG_LOCK().
+ * Hence, reading max_maxlvl will either collect the state before some
+ * change to the logging levels, or after.
*
- * So, if the logging facilities are being changed, then:
+ * If the given priority > max_maxlvl, this function exits, otherwise it
+ * immediately acquires the LOG_LOCK().
*
- * 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.
+ * So, if the logging facilities are being changed, then:
*
- * 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().
+ * a) if the level is being increased, so the current priority would
+ * would pass, then that change is just too late for this logging
+ * operation.
*
- * NB: max_maxlvl is statically initialised to INT_MAX !
+ * b) if the level is about to be reduced, then will get past the
+ * initial test, but after acquiring the LOG_LOCK(), will find that
+ * there is no logging to be done after all.
+ *
+ * NB: max_maxlvl is statically initialised to ZLOG_DISABLED.
*/
if (priority > max_maxlvl)
return ;
@@ -308,6 +361,9 @@ zlog (struct zlog *zl, int priority, const char *format, ...)
* Preparation of line to send to logging: file, stdout or "monitor" terminals.
*
* Line ends in '\n', but no terminating '\0'.
+ *
+ * TODO: apart from uquagga_timestamp, this is all async_signal_safe.
+ *
*/
static void
uvzlog_line(logline ll, struct zlog *zl, int priority,
@@ -374,8 +430,10 @@ uvzlog_line(logline ll, struct zlog *zl, int priority,
*
* 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
- *
+ * Time stamp is rendered in the form:
+ */
+#define TIMESTAMP_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 !)
*
@@ -383,8 +441,9 @@ uvzlog_line(logline ll, struct zlog *zl, int priority,
*
* So the maximum time stamp is 19 + 1 + 6 = 26. Adding the trailing '\n', and
* rounding up for good measure -- buffer size = 32.
-
-
+ */
+CONFIRM(timestamp_buffer_len >= 32) ;
+/*
* Returns: number of characters in buffer, not including trailing '\0'.
*
* NB: does no rounding.
@@ -940,10 +999,11 @@ openzlog (const char *progname, zlog_proto_t protocol,
zl->facility = syslog_facility;
zl->syslog_options = syslog_flags;
- /* Set default logging levels. */
+ /* Set initial logging levels. */
for (i = 0 ; i < ZLOG_DEST_COUNT ; ++i)
- zl->maxlvl[i] = ZLOG_DISABLED ;
+ zl->maxlvl[i] = ZLOG_DISABLED ;
+ zl->monitor_lvl = LOG_DEBUG ;
zl->default_lvl = LOG_DEBUG ;
openlog (progname, syslog_flags, zl->facility);
@@ -951,7 +1011,6 @@ openzlog (const char *progname, zlog_proto_t protocol,
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 ;
@@ -983,7 +1042,6 @@ closezlog (struct zlog *zl)
zl->syslog = false ;
zl->file_fd = -1 ;
zl->stdout_fd = -1 ;
- zl->monitors = 0 ; /* TODO... state of VTY ?? */
uzlog_set_effective_level(zl) ;
@@ -995,7 +1053,17 @@ closezlog (struct zlog *zl)
/*------------------------------------------------------------------------------
* Set new logging level for the given destination.
*
+ * If the log_level is ZLOG_DISABLED, then the destination is, effectively,
+ * disabled.
+ *
+ * Note that for file logging need to use zlog_set_file() to set a file in
+ * the first place and zlog_reset_file() to actually close a file destination.
+ *
* Update the effective maxlvl for this zlog, and the max_maxlvl for all zlog.
+ *
+ * Note that for monitor the sets the separate zl->monitor_lvl. The entry
+ * in the zl->maxlvl[] vector is the maximum of all *active* monitors, not
+ * the current configured level.
*/
extern void
zlog_set_level (struct zlog *zl, zlog_dest_t dest, int level)
@@ -1004,12 +1072,17 @@ zlog_set_level (struct zlog *zl, zlog_dest_t dest, int level)
if ((zl = zlog_actual(zl)) != NULL)
{
- zl->maxlvl[dest] = level ;
- uzlog_set_effective_level(zl) ;
+ if (dest != ZLOG_DEST_MONITOR)
+ {
+ zl->maxlvl[dest] = level ;
+ uzlog_set_effective_level(zl) ;
+ }
+ else
+ zl->monitor_lvl = level ;
}
LOG_UNLOCK() ;
-}
+} ;
/*------------------------------------------------------------------------------
* Set new log file: name and level.
@@ -1150,18 +1223,18 @@ zlog_rotate (struct zlog *zl)
}
/*------------------------------------------------------------------------------
- * Increment or decrement the number of monitor terminals.
+ * Set the current maximum monitor level and the effective level (the same in
+ * this case).
*/
extern void
-uzlog_add_monitor(struct zlog *zl, int count)
+uzlog_set_monitor(struct zlog *zl, int level)
{
if ((zl = zlog_actual(zl)) != NULL)
{
- zl->monitors += count ;
+ zl->maxlvl[ZLOG_DEST_MONITOR] = level ;
uzlog_set_effective_level(zl) ;
} ;
-}
-
+} ;
/*------------------------------------------------------------------------------
* get the current default level
@@ -1222,6 +1295,10 @@ zlog_set_default_lvl_dest (struct zlog *zl, int level)
/*------------------------------------------------------------------------------
* Get the current level for the given destination.
+ *
+ * Note that for monitor the gets the separate zl->monitor_lvl. The entry
+ * in the zl->maxlvl[] vector is the maximum of all *active* monitors, not
+ * the current configured level.
*/
extern int
zlog_get_maxlvl (struct zlog *zl, zlog_dest_t dest)
@@ -1231,11 +1308,26 @@ zlog_get_maxlvl (struct zlog *zl, zlog_dest_t dest)
LOG_LOCK() ;
zl = zlog_actual(zl) ;
- level = (zl != NULL) ? zl->maxlvl[dest] : ZLOG_DISABLED ;
+ if (zl == NULL)
+ level = ZLOG_DISABLED ;
+ else
+ level = (dest != ZLOG_DEST_MONITOR) ? zl->maxlvl[dest] : zl->monitor_lvl ;
LOG_UNLOCK() ;
return level;
-}
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get the current monitor level
+ */
+extern int
+uzlog_get_monitor_lvl(struct zlog *zl)
+{
+ LOG_ASSERT_LOCKED() ;
+
+ zl = zlog_actual(zl) ;
+ return (zl == NULL) ? ZLOG_DISABLED : zl->monitor_lvl ;
+} ;
/*------------------------------------------------------------------------------
* Get the current facility setting for syslog
@@ -1423,7 +1515,7 @@ uzlog_set_effective_level(struct zlog *zl)
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) ;
+ uzlog_set_emaxlvl(zl, ZLOG_DEST_MONITOR, true) ;
confirm(ZLOG_DEST_COUNT == 4) ;
/* Scan all known logging streams, and re-establish max_maxlvl.
diff --git a/lib/log.h b/lib/log.h
index 30a81a28..5fb2f163 100644
--- a/lib/log.h
+++ b/lib/log.h
@@ -29,6 +29,7 @@
#include <signal.h>
#include <syslog.h>
#include <stdio.h>
+#include "log_common.h"
#include "vargs.h"
#include "pthread_safe.h"
@@ -48,88 +49,32 @@
* please use LOG_ERR instead.
*/
-typedef enum
-{
- ZLOG_NONE,
- ZLOG_DEFAULT,
- ZLOG_ZEBRA,
- ZLOG_RIP,
- ZLOG_BGP,
- ZLOG_OSPF,
- ZLOG_RIPNG,
- ZLOG_OSPF6,
- ZLOG_ISIS,
- ZLOG_MASC
-} zlog_proto_t;
-
-/* If maxlvl is set to ZLOG_DISABLED, then no messages will be sent
- * to that logging destination.
+/*------------------------------------------------------------------------------
+ * Pointer to default logging structure.
+ *
+ * Each daemon initialises this very early in the morning (before processing
+ * command line arguments) by:
+ *
+ * zlog_default = openzlog(....) ;
*/
-enum { ZLOG_DISABLED = LOG_EMERG - 1 } ;
-
-typedef enum
-{
- ZLOG_DEST_SYSLOG = 0,
- ZLOG_DEST_FILE,
- ZLOG_DEST_STDOUT,
- ZLOG_DEST_MONITOR,
-
- ZLOG_DEST_COUNT /* Number of destinations */
-} zlog_dest_t;
-
-struct zlog
-{
- 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 */
-
- 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 */
-extern void zlog_init_r(void);
-extern void zlog_destroy_r(void);
-
-/* Default logging structure. */
-extern struct zlog *zlog_default;
+extern struct zlog* zlog_default;
-/* Open zlog function */
-extern struct zlog *openzlog (const char *progname, zlog_proto_t protocol,
+/*------------------------------------------------------------------------------
+ * zlog opening etc.
+ */
+extern struct zlog* openzlog (const char *progname, zlog_proto_t protocol,
int syslog_options, int syslog_facility);
-
-/* Close zlog function. */
extern void closezlog (struct zlog *zl);
+extern void zlog_set_level(struct zlog *zl, zlog_dest_t, int log_level);
+extern int zlog_rotate (struct zlog *);
-/* Generic function for zlog. */
+/*------------------------------------------------------------------------------
+ * Functions and macros for logging.
+ */
extern void zlog (struct zlog *zl, int priority, const char *format, ...)
PRINTF_ATTRIBUTE(3, 4);
-
-/* Handy zlog functions. */
+/* 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__)
@@ -137,7 +82,8 @@ extern void zlog (struct zlog *zl, int priority, const char *format, ...)
#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. */
+/* For bgpd's peer oriented log.
+ */
#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__)
@@ -145,37 +91,17 @@ extern void zlog (struct zlog *zl, int priority, const char *format, ...)
#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 uzlog_set_file
- or zlog_reset_file instead). */
-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);
-/* Disable file logging. */
-extern int zlog_reset_file(struct zlog *zl);
-
-/* Rotate log. */
-extern int zlog_rotate (struct zlog *);
+/*------------------------------------------------------------------------------
+ * Message and other lookups.
+ */
+struct message /* For message lookup. */
+{
+ int key;
+ const char *str;
+};
-/* getters & setters */
-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 void zlog_set_facility (struct zlog *zl, int facility);
-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 bool zlog_is_file (struct zlog *zl);
extern const char * zlog_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)")
extern const char *lookup (const struct message *, int);
@@ -186,9 +112,12 @@ extern const char *mes_lookup (const struct message *meslist,
extern const char *zlog_priority[];
extern const char *zlog_proto_names[];
-/* Safe version of strerror -- never returns NULL. */
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, siginfo_t *siginfo,
void *program_counter) ;
@@ -205,8 +134,9 @@ 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);
-/* Defines for use in command construction: */
-
+/*------------------------------------------------------------------------------
+ * Defines for use in command construction
+ */
#define LOG_LEVELS "(emergencies|alerts|critical|errors|" \
"warnings|notifications|informational|debugging)"
@@ -220,26 +150,27 @@ extern void zlog_backtrace_sigsafe(int priority, void *program_counter);
"Informational messages\n" \
"Debugging messages\n"
-#define LOG_FACILITIES "(kern|user|mail|daemon|auth|syslog|lpr|news|uucp|cron|local0|local1|local2|local3|local4|local5|local6|local7)"
+#define LOG_FACILITIES "(kern|user|mail|daemon|auth|syslog|lpr|news|uucp|" \
+ "cron|local0|local1|local2|local3|local4|local5|local6|local7)"
#define LOG_FACILITY_DESC \
- "Kernel\n" \
- "User process\n" \
- "Mail system\n" \
- "System daemons\n" \
- "Authorization system\n" \
- "Syslog itself\n" \
- "Line printer system\n" \
- "USENET news\n" \
- "Unix-to-Unix copy system\n" \
- "Cron/at facility\n" \
- "Local use\n" \
- "Local use\n" \
- "Local use\n" \
- "Local use\n" \
- "Local use\n" \
- "Local use\n" \
- "Local use\n" \
- "Local use\n"
+ "Kernel\n" \
+ "User process\n" \
+ "Mail system\n" \
+ "System daemons\n" \
+ "Authorization system\n" \
+ "Syslog itself\n" \
+ "Line printer system\n" \
+ "USENET news\n" \
+ "Unix-to-Unix copy system\n" \
+ "Cron/at facility\n" \
+ "Local use\n" \
+ "Local use\n" \
+ "Local use\n" \
+ "Local use\n" \
+ "Local use\n" \
+ "Local use\n" \
+ "Local use\n" \
+ "Local use\n"
#endif /* _ZEBRA_LOG_H */
diff --git a/lib/log_common.h b/lib/log_common.h
new file mode 100644
index 00000000..dc0463c5
--- /dev/null
+++ b/lib/log_common.h
@@ -0,0 +1,79 @@
+/* Logging definitions used in log.h and log_local.h
+ * 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_COMMON_H
+#define _ZEBRA_LOG_COMMON_H
+
+#include "misc.h"
+#include <syslog.h>
+
+/*==============================================================================
+ * This is for things which are required in log.h for external use, and in
+ * log_local.h for use within the log/command/vty family.
+ */
+
+/*------------------------------------------------------------------------------
+ * The protocols known to the logging system.
+ */
+typedef enum
+{
+ ZLOG_NONE,
+ ZLOG_DEFAULT,
+ ZLOG_ZEBRA,
+ ZLOG_RIP,
+ ZLOG_BGP,
+ ZLOG_OSPF,
+ ZLOG_RIPNG,
+ ZLOG_OSPF6,
+ ZLOG_ISIS,
+ ZLOG_MASC
+} zlog_proto_t;
+
+/*------------------------------------------------------------------------------
+ * If maxlvl is set to ZLOG_DISABLED, then no messages will be sent
+ * to that logging destination.
+ *
+ * Note that logging levels with higher priority have lower numbers. So, this
+ * may (well) be -ve.
+ */
+enum { ZLOG_DISABLED = LOG_EMERG - 1 } ;
+
+/*------------------------------------------------------------------------------
+ * The logging destinations supported.
+ */
+typedef enum
+{
+ ZLOG_DEST_SYSLOG = 0,
+ ZLOG_DEST_FILE,
+ ZLOG_DEST_STDOUT,
+ ZLOG_DEST_MONITOR,
+
+ ZLOG_DEST_COUNT /* Number of destinations */
+} zlog_dest_t;
+
+/*------------------------------------------------------------------------------
+ *
+ */
+struct zlog ;
+
+enum { timestamp_buffer_len = 32 } ;
+
+#endif /* _ZEBRA_LOG_COMMON_H */
diff --git a/lib/log_local.h b/lib/log_local.h
index 8e38bf48..d96f9f8b 100644
--- a/lib/log_local.h
+++ b/lib/log_local.h
@@ -24,6 +24,7 @@
#include "misc.h"
+#include "log_common.h"
#include "qpthreads.h"
/*==============================================================================
@@ -133,23 +134,35 @@ LOG_ASSERT_LOCKED(void)
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);
+extern size_t quagga_timestamp(int timestamp_precision /* # subsecond digits */,
+ char *buf, size_t buflen);
+
+extern void uzlog_set_monitor(struct zlog *zl, int level) ;
+extern int uzlog_get_monitor_lvl(struct zlog *zl) ;
+
+extern int zlog_set_file(struct zlog *zl, const char *filename, int log_level);
+extern int zlog_reset_file(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 void zlog_set_facility (struct zlog *zl, int facility);
+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 bool zlog_is_file (struct zlog *zl);
+
+
#endif /* _ZEBRA_LOG_LOCAL_H */
diff --git a/lib/mem_tracker.c b/lib/mem_tracker.c
index b468ad2a..f188a13a 100644
--- a/lib/mem_tracker.c
+++ b/lib/mem_tracker.c
@@ -20,10 +20,25 @@
*/
#include "vty.h"
+#include <sys/mman.h>
/*==============================================================================
* Memory Tracker
*/
+typedef struct mem_region mem_region_t ;
+typedef struct mem_region* mem_region ;
+struct mem_region
+{
+ void* base ;
+ void* limit ;
+
+ uint64_t allocated ;
+
+ uint64_t used ;
+ uint64_t overhead ;
+} ;
+
+typedef struct mem_descriptor mem_descriptor_t ;
typedef struct mem_descriptor* mem_descriptor ;
struct mem_descriptor
{
@@ -34,7 +49,7 @@ struct mem_descriptor
uint32_t size ; /* LS Type is encoded as MS 4 bits */
} ;
-typedef uint32_t md_index ;
+typedef uint32_t md_index_t ;
enum
{
@@ -72,13 +87,13 @@ static struct mem_type_tracker
static mem_descriptor mem_page_table[md_page_count] ;
static mem_descriptor mem_free_descriptors ;
-static md_index mem_next_index ;
+static md_index_t mem_next_index ;
static struct mem_tracker mem ;
uint32_t mem_base_count ;
-md_index* mem_bases ;
+md_index_t* mem_bases ;
inline static void
mem_md_set_type(mem_descriptor md, enum MTYPE mtype)
@@ -97,7 +112,7 @@ mem_md_set_type(mem_descriptor md, enum MTYPE mtype)
} ;
inline static void
-mem_md_set_next(mem_descriptor md, md_index next)
+mem_md_set_next(mem_descriptor md, md_index_t next)
{
md->next = (md->next & ~md_next_mask) | (next & md_next_mask) ;
} ;
@@ -116,7 +131,7 @@ mem_md_type(mem_descriptor md)
| ( (md->size >> md_size_bits) & md_size_type_mask ) ;
} ;
-inline static md_index
+inline static md_index_t
mem_md_next(mem_descriptor md)
{
return md->next & md_next_mask ;
@@ -129,7 +144,7 @@ mem_md_size(mem_descriptor md)
} ;
inline static mem_descriptor
-mem_md_ptr(md_index mdi)
+mem_md_ptr(md_index_t mdi)
{
mem_descriptor page ;
@@ -143,7 +158,7 @@ mem_md_ptr(md_index mdi)
static void mem_md_make_bases(void) ;
-inline static md_index*
+inline static md_index_t*
mem_md_base(void* address)
{
if (mem_bases == NULL)
@@ -155,23 +170,32 @@ mem_md_base(void* address)
static void
mem_md_make_bases(void)
{
- md_index* bases_was = mem_bases ;
- uint32_t count_was = mem_base_count ;
+ md_index_t* bases_was = mem_bases ;
+ uint32_t count_was = mem_base_count ;
mem_base_count += 256 * 1024 ;
mem_base_count |= 1 ;
- mem_bases = calloc(mem_base_count, sizeof(md_index)) ;
+#ifdef HAVE_MMAP
+ mem_bases = mmap(NULL, mem_base_count * sizeof(md_index_t),
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) ;
+ passert(mem_bases != MAP_FAILED) ;
+#else
+ mem_bases = malloc(mem_base_count * sizeof(md_index_t)) ;
passert(mem_bases != NULL) ;
+#endif
+
+ memset(mem_bases, 0, mem_base_count * sizeof(md_index_t)) ;
if (bases_was == NULL)
passert(count_was == 0) ;
else
{
- md_index* base = bases_was ;
- md_index* new_base ;
- md_index this ;
- md_index next ;
+ md_index_t* base = bases_was ;
+ md_index_t* new_base ;
+ md_index_t this ;
+ md_index_t next ;
mem_descriptor md ;
while (count_was)
@@ -190,7 +214,14 @@ mem_md_make_bases(void)
--count_was ;
} ;
+#ifdef HAVE_MMAP
+ {
+ int rc = munmap(bases_was, count_was * sizeof(md_index_t)) ;
+ passert(rc >= 0) ;
+ } ;
+#else
free(bases_was) ;
+#endif
} ;
} ;
@@ -198,17 +229,25 @@ static void
mem_md_make_descriptors(void)
{
mem_descriptor md ;
- md_index mdi ;
+ md_index_t mdi ;
mdi = mem_next_index ;
passert(mdi < md_index_max) ;
- mem_free_descriptors
- = mem_page_table[(mdi >> md_i_index_bits) & md_page_mask]
- = calloc(md_i_index_count, sizeof(struct mem_descriptor)) ;
-
+#ifdef HAVE_MMAP
+ mem_free_descriptors = mmap(NULL, md_i_index_count * sizeof(mem_descriptor_t),
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) ;
+ passert(mem_free_descriptors != MAP_FAILED) ;
+#else
+ mem_free_descriptors = malloc(md_i_index_count * sizeof(mem_descriptor_t)) ;
passert(mem_free_descriptors != NULL) ;
+#endif
+
+ memset(mem_free_descriptors, 0, md_i_index_count * sizeof(mem_descriptor_t)) ;
+ mem_page_table[(mdi >> md_i_index_bits) & md_page_mask]
+ = mem_free_descriptors ;
mem_next_index += md_i_index_count ;
if (mdi == 0)
@@ -232,9 +271,9 @@ inline static void
mem_md_malloc(enum MTYPE mtype, void* address, size_t size, const char* name)
{
mem_tracker mtt ;
- md_index* base ;
+ md_index_t* base ;
mem_descriptor md ;
- md_index mdi ;
+ md_index_t mdi ;
passert(size <= md_size_max) ;
@@ -286,9 +325,9 @@ inline static void
mem_md_free(enum MTYPE mtype, void* address)
{
mem_tracker mtt ;
- md_index* base ;
+ md_index_t* base ;
mem_descriptor md, prev_md ;
- md_index this, next ;
+ md_index_t this, next ;
if (address == NULL)
return ;
@@ -344,9 +383,9 @@ mem_md_realloc(enum MTYPE mtype, void* old_address, void* new_address,
size_t size, const char* name)
{
mem_tracker mtt ;
- md_index* base ;
+ md_index_t* base ;
mem_descriptor md, prev_md ;
- md_index this, next ;
+ md_index_t this, next ;
if (old_address == NULL)
{
@@ -534,7 +573,7 @@ show_memory_tracker_summary(struct vty *vty)
LOCK ;
overhead = (sizeof(struct mem_descriptor) * mem_next_index)
- + (sizeof(md_index) * mem_base_count)
+ + (sizeof(md_index_t) * mem_base_count)
+ (sizeof(mem_descriptor) * md_page_count) ;
mt = mem ; /* copy the overall memory information */
diff --git a/lib/memory.c b/lib/memory.c
index ef8551cd..efd3a83f 100644
--- a/lib/memory.c
+++ b/lib/memory.c
@@ -26,12 +26,145 @@
#include <malloc.h>
#endif /* !HAVE_STDLIB_H || HAVE_MALLINFO */
+#include "qlib_init.h"
#include "log.h"
#include "memory.h"
#include "qpthreads.h"
-/* Needs to be qpthread safe. The system malloc etc are already
- * thread safe, but we need to protect the stats
+#include "vty.h"
+#include "command.h"
+#include "qfstring.h"
+
+/* HAVE_MMAP specifies that can do mmap() and mmunmap() -- below */
+#define HAVE_MMAP 1
+
+/* HAVE_MEM_REGIONS -- pro tem ! */
+#if defined(GNU_LINUX) && !defined(NO_MEM_REGIONS)
+#define HAVE_MEM_REGIONS
+#endif
+
+#ifdef HAVE_MMAP
+#include <sys/mman.h>
+#endif
+
+#ifdef HAVE_MEM_REGIONS
+
+#undef HAVE_MEM_REGIONS
+#define HAVE_MEM_REGIONS 1
+
+ enum { memory_regions = HAVE_MEM_REGIONS } ;
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <unistd.h>
+
+#else
+ enum { memory_regions = 0 } ;
+#endif
+
+
+/*==============================================================================
+ * Here we wrap the usual malloc()/calloc()/realloc()/free()/strdup() functions
+ * with various amounts of statics gathering.
+ *
+ * When memory is allocated/deallocated the type of that memory must be
+ * specified -- see memtypes.c.
+ *
+ * The basic statistics gathering counts, for each memory type, the number of
+ * objects which have been allocated and not yet freed -- so knows the current
+ * number of allocated objects. (The counts depend on realloc() and free()
+ * being given the same memory type as the original allocation.)
+ *
+ *------------------------------------------------------------------------------
+ * The "memory_tracker"
+ *
+ * The (debug) "memory_tracker" statistics keep the address, size and type
+ * for every allocated object, and the name of the function which allocated
+ * the object (or last realloc()'d) it. This adds 24..32 bytes per object,
+ * so is not cheap ! The number of alloc/realloc/free operations for each
+ * type of memory is also recorded, as are the peak number of objects and the
+ * peak memory allocation. These statistics allow:
+ *
+ * * tracking of size of allocated memory
+ *
+ * * the malloc() overhead for allocated memory
+ * (assuming the mem_tracker_start() function can establish this.
+ *
+ * * checking that realloc() and free()
+ *
+ * a. specify the address of a previously allocated object.
+ *
+ * b. specify the same type as the original allocation
+ *
+ * * reporting of the total activity -- alloc/realloc/free -- by memory type
+ *
+ * * reporting of peak object counts and size -- by memory type.
+ *
+ * The extra data saved by the memory_tracker is put (if at all possible) into
+ * lumps of memory separate from malloc() -- allocated by mmap().
+ *
+ *------------------------------------------------------------------------------
+ * The "memory_logger"
+ *
+ * The (debug) "memory_logger" outputs a logging message for every memory
+ * operation.
+ *
+ * The "memory_logger" overrides the "memory tracker".
+ *
+ *------------------------------------------------------------------------------
+ * mallinfo()
+ *
+ * This is a GNU extension in glibc. Sadly, the mallinfo data structure is
+ * defined in terms of "int", and there seem to be no plans to extend that to
+ * support larger amounts of memory -- beyond 4G.
+ *
+ *------------------------------------------------------------------------------
+ * mmap() and munmap()
+ *
+ * These are Posix facilities. Most implementations support (MAP_ANON or
+ * MAP_ANONYMOUS), though that is not Posix. The Posix posix_typed+mem_open()
+ * can be used if MAP_ANON or MAP_ANONYMOUS is not supported.
+ *
+ * Late model malloc() uses mmap(), so memory may be allocated across a number
+ * of distinct regions.
+ *
+ * The "memory_tracker" will use mmap() allocated regions, if at all possible,
+ * for all the tracked data.
+ *
+ *------------------------------------------------------------------------------
+ * Memory Regions
+ *
+ * With Linux /proc/self/maps provides a list of all regions of memory. Also,
+ * /proc/self/pagemap can be used to discover which pages in each region are
+ * in memory and which are swapped out (and which are neither).
+ *
+ * Using these it is possible to provide some information about total memory
+ * usage -- partly replacing mallinfo().
+ *
+ * With memory_tracker, can work out which regions form part of the heap, and
+ * how much of each region is in use, taken up by overhead, or currently
+ * free.
+ *
+ * Without memory_tracker there is no way to tell which regions are part of
+ * the heap, apart from the initial heap region. So the summary information
+ * may show a number of "Anon" regions, some of which are, in fact, heap.
+ *
+ * The extra data saved by the memory_regions is put (if at all possible) into
+ * lumps of memory separate from malloc() -- allocated by mmap().
+ *
+ *------------------------------------------------------------------------------
+ * malloc() overhead
+ *
+ * There is an attempt to establish the amount of redtape on each malloc()
+ * allocation, the minimum size of a malloc() block and the unit of allocation
+ * after that. This is used by the memory_tracker to identify the space in
+ * the heap which is "overhead".
+ */
+
+/*------------------------------------------------------------------------------
+ * Need to be qpthread safe. The system malloc etc are already thread safe,
+ * but we need to protect the statistics.
*/
static qpt_mutex_t memory_mutex;
@@ -40,57 +173,380 @@ static qpt_mutex_t memory_mutex;
static void log_memstats(int log_priority);
-static const struct message mstr [] =
+/*------------------------------------------------------------------------------
+ * Basic statistics
+ */
+static mem_stats_t mstats ; /* zeroised in memory_start() */
+
+static size_t mem_pagesize = 0 ; /* set in memory_start() */
+
+/*------------------------------------------------------------------------------
+ * Showing stuff
+ */
+QFB_T(num_str, 30) ;
+
+/*------------------------------------------------------------------------------
+ * Read in the mem_type_map array: MTYPE_XXX -> "MTYPE_XXX"
+ */
+#define MEM_MTYPE_MAP_REQUIRED 1
+#include "memtypes.h"
+CONFIRM((sizeof(mem_mtype_map) / sizeof(char*)) == MTYPE_MAX) ;
+
+static inline const char*
+mem_mtype_name(mtype_t mtype)
{
- { MTYPE_THREAD, "thread" },
- { MTYPE_THREAD_MASTER, "thread_master" },
- { MTYPE_VECTOR, "vector" },
- { MTYPE_VECTOR_BODY, "vector_index" },
- { MTYPE_IF, "interface" },
- { 0, NULL },
-};
+ return (mtype < MTYPE_MAX) ? mem_mtype_map[mtype] : "*unknown mtype*" ;
+}
/*------------------------------------------------------------------------------
- * Include the memory tracker, if required.
+ * Include the optional memory tracker -- MEMORY_TRACKER.
*
- * 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.
+ * The memory tracker is always part of the source -- so kept up to date.
+ * We depend on dead code removal to eliminate overhead when the tracker is
+ * not used.
*/
-typedef struct mem_tracker* mem_tracker ;
-struct mem_tracker
+typedef struct mem_tracker_item* mem_tracker_item ;
+typedef struct mem_tracker_item mem_tracker_item_t ;
+
+struct mem_tracker_item
{
- uint64_t malloc_count ;
- uint64_t realloc_count ;
- uint64_t free_count ;
+ ulong malloc_count ; /* number of mallocs */
+ ulong realloc_count ; /* number of reallocs */
+ ulong free_count ; /* number of frees */
- uint32_t tracked_count ;
- size_t tracked_size ;
+ ulong item_count ; /* number of existing items */
+ size_t total_size ; /* total size of existing items */
- uint32_t tracked_max_count ;
- size_t tracked_max_size ;
+ ulong peak_item_count ; /* peak count of items */
+ size_t peak_total_size ; /* peak size of items */
} ;
-static void
-mem_tracker_zeroise(struct mem_tracker* mem)
+/*------------------------------------------------------------------------------
+ * Memory Region Handling -- based on /proc/self/maps
+ * and /proc/self/pagemap
+ */
+enum mem_region_flags
{
- memset(mem, 0, sizeof(struct mem_tracker)) ;
+ mrf_read = BIT(0),
+ mrf_write = BIT(1),
+ mrf_execute = BIT(2),
+ mrf_private = BIT(3),
+ mrf_shared = BIT(4),
} ;
+typedef enum mem_region_flags mem_region_flags_t ;
-#include "mem_tracker.c"
+enum mem_region_type
+{
+ mrt_anon,
+ mrt_named,
+ mrt_heap,
+ mrt_stack,
+ mrt_vdso,
+ mrt_vsyscall,
+ mrt_other,
+
+ mrt_type_count,
+} ;
+typedef enum mem_region_type mem_region_type_t ;
-/*==============================================================================
- * Keeping track of number of allocated objects of given type
+static const char mrf_type_chars[mrt_type_count] =
+{
+ [mrt_anon] = 'A',
+ [mrt_named] = '=',
+ [mrt_heap] = 'H',
+ [mrt_stack] = 'S',
+ [mrt_vdso] = 'v',
+ [mrt_vsyscall] = 'w',
+ [mrt_other] = '~',
+} ;
+
+enum mem_region_sex
+{
+ mrx_code = 0, /* mrt_named & mrf_read & !mrf_write & mrf_execute */
+ /* mrt_vdso & mrf_read & !mrf_write & mrf_execute */
+ /* mrt_vsyscall & mrf_read & !mrf_write & mrf_execute */
+ mrx_const, /* mrt_named & mrf_read & !mrf_write & !mrf_execute */
+ mrx_data, /* mrt_named & mrf_read & mrf_write & !mrf_execute */
+ mrx_reserved, /* mrt_name & !mrf_read & !mrf_write & !mrf_execute */
+ mrx_gap, /* mrt_anon & !mrf_read & !mrf_write & !mrf_execute */
+ mrx_exotic, /* anything else */
+
+ /* Note that the stack and anon regions immediately precede the heap stuff */
+
+ mrx_stack, /* mrt_stack & mrf_read & mrf_write & !mrf_execute */
+ mrx_anon, /* mrt_anon & mrf_read & mrf_write & !mrf_execute */
+
+ mrx_rest_total, /* subtotal of all not heap */
+
+ mrx_heap, /* start of heap information */
+
+ mrx_heap_main = mrx_heap,
+ /* mrt_heap & mrf_read & mrf_write & !mrf_execute */
+ mrx_heap_extra, /* mrx_anon & seen by memory_tracker */
+
+ mrx_heap_total, /* subtotal of heap_main and heap extra */
+
+ mrx_grand_total, /* total of everything */
+
+ mrx_sex_count, /* number of mrx items */
+
+ mrx_heap_count = mrx_heap_total - mrx_heap,
+ /* number of heap type items */
+} ;
+
+typedef enum mem_region_sex mem_region_sex_t ;
+
+static const char* mrx_sex_names[mrx_sex_count] =
+{
+ [mrx_heap_main] = "Heap ",
+ [mrx_heap_extra] = "Extra ",
+
+ [mrx_code] = "Code ",
+ [mrx_const] = "Const ",
+ [mrx_data] = "Data ",
+ [mrx_reserved] = "Resrvd",
+ [mrx_gap] = "Gap ",
+ [mrx_exotic] = "Exotic",
+ [mrx_stack] = "Stack ",
+ [mrx_anon] = "Anon ",
+
+ [mrx_heap_total] = "++++++",
+ [mrx_rest_total] = "++++++",
+ [mrx_grand_total] = " Total",
+} ;
+
+enum { mem_region_max_name_len = 127 } ;
+
+typedef struct mem_region mem_region_t ;
+typedef struct mem_region* mem_region ;
+typedef const struct mem_region* mem_region_c ;
+
+struct mem_region
+{
+ uintptr_t base ;
+ uintptr_t limit ;
+
+ mem_region_flags_t flags ;
+ mem_region_type_t type ;
+ mem_region_sex_t sex ;
+
+ size_t offset ;
+
+ uint dev_major ;
+ uint dev_minor ;
+ ullong inode ;
+
+ char name[mem_region_max_name_len + 1] ;
+
+ size_t present ;
+ size_t swapped ;
+
+ uintptr_t min_address ;
+ uintptr_t max_address ;
+
+ size_t count ;
+ size_t used ;
+ size_t overhead ;
+} ;
+
+typedef struct mem_region_summary mem_region_summary_t ;
+typedef struct mem_region_summary* mem_region_summary ;
+
+struct mem_region_summary
+{
+ mem_region_sex_t sex ;
+
+ size_t size ;
+ size_t present ;
+ size_t swapped ;
+
+ size_t count ;
+ size_t used ;
+ size_t overhead ;
+ size_t unused ;
+ size_t data ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * memory_tracker definitions
+ */
+typedef struct mem_descriptor mem_descriptor_t ;
+typedef struct mem_descriptor* mem_descriptor ;
+struct mem_descriptor
+{
+ void* addr ;
+ const char* name ;
+
+ uint32_t next ; /* MS Type is encoded as MS 4 bits */
+ uint32_t size ; /* LS Type is encoded as MS 4 bits */
+} ;
+
+typedef uint32_t md_index_t ;
+
+enum
+{
+ md_next_bits = 28, /* up to 256M allocated objects */
+ md_next_mask = (1 << md_next_bits) - 1,
+
+ md_index_max = md_next_mask + 1,
+
+ md_size_bits = 28, /* up to 256M individual item */
+ md_size_mask = (1 << md_size_bits) - 1,
+
+ md_size_max = md_size_mask,
+
+ md_next_type_bits = 32 - md_next_bits,
+ md_next_type_mask = (1 << md_next_type_bits) - 1,
+ md_size_type_bits = 32 - md_size_bits,
+ md_size_type_mask = (1 << md_size_type_bits) - 1,
+
+ md_i_index_bits = 16,
+ md_i_index_count = 1 << md_i_index_bits,
+ md_i_index_mask = md_i_index_count - 1,
+
+ md_page_bits = md_next_bits - md_i_index_bits,
+ md_page_count = 1 << md_page_bits,
+ md_page_mask = md_page_count - 1,
+} ;
+
+CONFIRM(MTYPE_MAX < (1 << (md_next_type_bits + md_size_type_bits))) ;
+
+/* The structure in which all mem_tracker stuff is held.
+ *
+ * Note that for the total memory, does not bother to keep:
+ *
+ * malloc_count
+ * realloc_count
+ * free_count
+ *
+ * up to date on every alloc/free -- those can be produced by adding up the
+ * typ items.
+ *
+ * Does keep:
+ *
+ * item_count
+ * total_size
+ *
+ * up to date on every alloc/free, so that can maintain:
+ *
+ * peak_item_count
+ * peak_total_size
+ */
+typedef struct mem_tracker_data mem_tracker_data_t ;
+typedef struct mem_tracker_data* mem_tracker_data ;
+
+struct mem_tracker_data
+{
+ mem_tracker_item_t tot[1] ; /* Total memory */
+ mem_tracker_item_t typ[MTYPE_MAX] ; /* Per memory type */
+
+ size_t tracker_overhead ; /* Bytes used by tracker */
+} ;
+
+/* All the mem_tracker data is kept together to facilitate copying of same
+ * to take snapshot to output from.
*/
+static mem_tracker_data_t mem_mt_data ;
+
+static mem_descriptor* mem_page_table = NULL ;
+static mem_descriptor mem_free_descriptors = NULL ;
+static md_index_t mem_next_index = 0 ;
+
+static uint32_t mem_base_count = 0 ;
+static md_index_t* mem_bases = NULL ;
+
+/*------------------------------------------------------------------------------
+ * In order to measure heap usage, we try to establish the overhead and
+ * padding in the blocks returned by malloc() -- see mem_alloc_discover().
+ *
+ * Have to make a number of assumptions:
+ *
+ * a. overhead comprises:
+ *
+ * * a fixed amount of "red-tape"
+ *
+ * * a minimum allocation
+ *
+ * * a fixed unit of allocation for anything above minimum
+ *
+ * Note that this assumes that malloc() does *not* for larger blocks
+ * allocate larger amounts of unused space at the end of a block.
+ * (Which would be a way of avoiding moving memory around on some
+ * realloc() requests -- but those are rare.)
+ *
+ * b. at start time:
+ *
+ * * malloc() will allocate blocks at the "edge" of the heap.
+ *
+ * * free() will release blocks, which will be immediately be
+ * reused.
+ *
+ * This appears reasonable, but... who can tell ? The code that tries to
+ * establish these parameters is quite careful to check that its conclusions
+ * are consistent -- but there is limited intelligence here.
+ */
+bool mem_alloc_known = false ; /* set true when think know what
+ the overhead & padding is. */
+
+uintptr_t mem_alloc_first = 0 ; /* address of first allocation */
+size_t mem_alloc_min_size = 0 ; /* minimum size block */
+size_t mem_alloc_redtape = 0 ; /* redtape per block */
+size_t mem_alloc_min_max = 0 ; /* maximum allocation in minimum
+ size block */
+size_t mem_alloc_unit = 0 ; /* unit beyond minimum size */
+
+size_t mem_alloc_add = 0 ; /* see mem_alloc_padding() */
+
+char* mem_alloc_trial_addr = NULL ; /* final trial results */
+size_t mem_alloc_trial_size = 0 ;
+size_t mem_alloc_trial_alloc = 0 ;
+
+enum
+{
+ ma_not_tried,
+
+ ma_not_a_before_b,
+ ma_not_increasing_size,
+ ma_unit_not_power_2,
+ ma_address_not_n_unit,
+ ma_size_min_not_n_unit,
+ ma_trial_failed,
+
+} mem_alloc_failed = ma_not_tried ;
-static struct mstat
+/*------------------------------------------------------------------------------
+ * Extra counting performed by optional logger -- MEMORY_LOGGER
+ *
+ * The memory logger is always part of the source -- so kept up to date.
+ * We depend on dead code removal to eliminate overhead when the tracker is
+ * not used.
+ */
+static struct /* TODO: establish what this is for ! */
{
- struct
- {
- char *name ;
- long alloc ;
- } mt[MTYPE_MAX] ;
-} mstat ;
+ const char *name;
+ ulong alloc;
+ ulong t_malloc;
+ ulong c_malloc;
+ ulong t_calloc;
+ ulong c_calloc;
+ ulong t_realloc;
+ ulong t_free;
+ ulong c_strdup;
+} mlog_stat [MTYPE_MAX];
+
+/*==============================================================================
+ * Function definitions.
+ */
+inline static void mem_mt_malloc(mtype_t mtype, void* address,
+ size_t size, const char* name) ;
+inline static void mem_mt_free(mtype_t mtype, void* address) ;
+inline static void mem_mt_realloc(mtype_t mtype, void* old_address,
+ void* new_address,
+ size_t new_size, const char* name) ;
+static void mem_mt_get_stats(mem_tracker_data mtd, bool locked) ;
+
+static void mtype_log (const char *func, void *memory, mtype_t mtype,
+ const char *file, int line) ;
/*==============================================================================
* Memory allocation functions.
@@ -98,12 +554,15 @@ static struct mstat
* NB: failure to allocate is FATAL -- so no need to test return value.
*/
-/* Fatal memory allocation error occured. */
+/*------------------------------------------------------------------------------
+ * Fatal memory allocation error occured.
+ */
static void __attribute__ ((noreturn))
-zerror (const char *fname, int type, size_t size)
+zerror (const char *fname, mtype_t mtype, size_t size)
{
- zlog_err ("%s : can't allocate memory for `%s' size %d: %s\n",
- fname, lookup (mstr, type), (int) size, errtoa(errno, 0).str);
+ zlog_err ("%s : can't allocate memory for '%s'(%d) size %zd: %s\n",
+ fname, mem_mtype_name(mtype), mtype, size, errtoa(errno, 0).str) ;
+
log_memstats(LOG_WARNING);
/* N.B. It might be preferable to call zlog_backtrace_sigsafe here, since
that function should definitely be safe in an OOM condition. But
@@ -111,7 +570,7 @@ zerror (const char *fname, int type, size_t size)
this time... */
zlog_backtrace(LOG_WARNING);
zabort_abort();
-}
+} ;
/*------------------------------------------------------------------------------
* Memory allocation.
@@ -122,8 +581,8 @@ zerror (const char *fname, int type, size_t size)
*
* NB: If memory cannot be allocated, aborts execution.
*/
-void *
-zmalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME)
+extern void *
+zmalloc (mtype_t mtype, size_t size MEMORY_EXTRA_ARGS)
{
void *memory;
@@ -138,23 +597,33 @@ zmalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME)
}
else
{
- mstat.mt[mtype].alloc++;
+ mstats.alloc[mtype]++;
+
+ if (memory_logger)
+ {
+ mlog_stat[mtype].c_malloc++;
+ mlog_stat[mtype].t_malloc++;
+ } ;
+
if (memory_tracker)
- mem_md_malloc(mtype, memory, size, MEMORY_TRACKER_NAME_ARG) ;
+ mem_mt_malloc(mtype, memory, size, MEMORY_TRACKER_ARG) ;
UNLOCK ;
+
+ if (memory_logger)
+ mtype_log("zmalloc", memory, mtype, MEMORY_LOGGING_ARGS) ;
} ;
- return memory;
-}
+ return memory ;
+} ;
/*------------------------------------------------------------------------------
* Memory allocation zeroising the allocated area.
*
* As zmalloc, plus zeroises the allocated memory.
*/
-void *
-zcalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME)
+extern void *
+zcalloc (mtype_t mtype, size_t size MEMORY_EXTRA_ARGS)
{
void *memory;
@@ -169,10 +638,21 @@ zcalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME)
}
else
{
- mstat.mt[mtype].alloc++;
+ mstats.alloc[mtype]++;
+
+ if (memory_logger)
+ {
+ mlog_stat[mtype].c_calloc++;
+ mlog_stat[mtype].t_calloc++;
+ } ;
+
if (memory_tracker)
- mem_md_malloc(mtype, memory, size, MEMORY_TRACKER_NAME_ARG) ;
+ mem_mt_malloc(mtype, memory, size, MEMORY_TRACKER_ARG) ;
+
UNLOCK ;
+
+ if (memory_logger)
+ mtype_log("zcalloc", memory, mtype, MEMORY_LOGGING_ARGS) ;
} ;
return memory;
@@ -192,7 +672,7 @@ zcalloc (enum MTYPE mtype, size_t size MEMORY_TRACKER_NAME)
* NB: If memory cannot be allocated, aborts execution.
*/
void *
-zrealloc (enum MTYPE mtype, void *ptr, size_t size MEMORY_TRACKER_NAME)
+zrealloc (mtype_t mtype, void *ptr, size_t size MEMORY_EXTRA_ARGS)
{
void *memory;
@@ -207,10 +687,20 @@ zrealloc (enum MTYPE mtype, void *ptr, size_t size MEMORY_TRACKER_NAME)
else
{
if (ptr == NULL)
- mstat.mt[mtype].alloc++;
+ mstats.alloc[mtype]++;
+
+ if (memory_logger)
+ {
+ mlog_stat[mtype].t_realloc++;
+ } ;
+
if (memory_tracker)
- mem_md_realloc(mtype, ptr, memory, size, MEMORY_TRACKER_NAME_ARG) ;
+ mem_mt_realloc(mtype, ptr, memory, size, MEMORY_TRACKER_ARG) ;
+
UNLOCK ;
+
+ if (memory_logger)
+ mtype_log("zrealloc", memory, mtype, MEMORY_LOGGING_ARGS) ;
} ;
return memory;
@@ -225,21 +715,27 @@ zrealloc (enum MTYPE mtype, void *ptr, size_t size MEMORY_TRACKER_NAME)
* Does nothing if the given pointer is NULL.
*/
void
-zfree (enum MTYPE mtype, void *ptr)
+zfree (mtype_t mtype, void *ptr MEMORY_EXTRA_ARGS)
{
if (ptr != NULL)
{
LOCK ;
- assert(mstat.mt[mtype].alloc > 0) ;
+ assert(mstats.alloc[mtype] > 0) ;
+ mstats.alloc[mtype]--;
+
+ if (memory_logger)
+ mlog_stat[mtype].t_free++;
- mstat.mt[mtype].alloc--;
if (memory_tracker)
- mem_md_free(mtype, ptr) ;
+ mem_mt_free(mtype, ptr) ;
free (ptr);
UNLOCK ;
+
+ if (memory_logger)
+ mtype_log("zfree", ptr, mtype, MEMORY_LOGGING_ARGS) ;
} ;
} ;
@@ -251,7 +747,7 @@ zfree (enum MTYPE mtype, void *ptr)
* NB: If memory cannot be allocated, aborts execution.
*/
char *
-zstrdup (enum MTYPE mtype, const char *str MEMORY_TRACKER_NAME)
+zstrdup (mtype_t mtype, const char *str MEMORY_EXTRA_ARGS)
{
void *dup;
@@ -265,303 +761,1913 @@ zstrdup (enum MTYPE mtype, const char *str MEMORY_TRACKER_NAME)
}
else
{
- mstat.mt[mtype].alloc++;
+ mstats.alloc[mtype]++;
+
+ if (memory_logger)
+ mlog_stat[mtype].c_strdup++;
+
if (memory_tracker)
- mem_md_malloc(mtype, dup, strlen(str)+1, MEMORY_TRACKER_NAME_ARG) ;
+ mem_mt_malloc(mtype, dup, strlen(str)+1, MEMORY_TRACKER_ARG) ;
UNLOCK ;
+
+ if (memory_logger)
+ mtype_log("zstrdup", dup, mtype, MEMORY_LOGGING_ARGS) ;
} ;
return dup;
}
/*==============================================================================
- * Memory allocation with built in logging
+ * Memory logging function -- called if MEMORY_LOGGER enabled
*/
+static void
+mtype_log (const char *func, void *memory, mtype_t mtype,
+ const char *file, int line)
+{
+ zlog_debug ("%s: %s(%d) %p %s %d", func, mem_mtype_name(mtype), mtype,
+ memory, file, line) ;
+} ;
-#ifdef MEMORY_LOG
+/*==============================================================================
+ * Support for memory reporting functions
+ */
-static struct
+/*------------------------------------------------------------------------------
+ * Pick up copy of the current basic statistics
+ *
+ * Done under lock in order to get a single, consistent snap shot.
+ */
+extern void
+mem_get_stats(mem_stats_t* mst)
{
- const char *name;
- long alloc;
- unsigned long t_malloc;
- unsigned long c_malloc;
- unsigned long t_calloc;
- unsigned long c_calloc;
- unsigned long t_realloc;
- unsigned long t_free;
- unsigned long c_strdup;
-} mlog_stat [MTYPE_MAX];
+ LOCK ;
+ *mst = mstats ;
+ UNLOCK ;
+} ;
-static void
-mtype_log (char *func, void *memory, const char *file, int line,
- enum MTYPE type)
+/*------------------------------------------------------------------------------
+ * Get value of alloc count for given mtype
+ *
+ * For use with a copy of statistics picjed up already.
+ */
+extern ulong
+mem_get_alloc(mem_stats_t* mst, mtype_t mtype)
{
- zlog_debug ("%s: %s %p %s %d", func, lookup (mstr, type), memory, file, line);
-}
+ return mst->alloc[mtype] ;
+} ;
-void *
-mtype_zmalloc (const char *file, int line, int type, size_t size)
+/*------------------------------------------------------------------------------
+ * Get individual allocation counter.
+ *
+ * To get all the counters at once, see mem_get_stats().
+ */
+extern ulong
+mtype_stats_alloc(mtype_t mtype)
{
- void *memory;
+ ulong alloc ;
+ LOCK ;
+ alloc = mstats.alloc[mtype] ;
+ UNLOCK ;
+ return alloc ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Convert given byte count to 4 figures + scale
+ *
+ * "0 bytes", "2048 bytes", "110KiB", "500MiB", "11GiB", etc.
+ *
+ * The pointer returned may be NULL (indicating an error)
+ * or point to the given buffer, or point to static storage.
+ */
+extern const char *
+mtype_memstr(char *buf, size_t len, ulong bytes)
+{
+ uint t, g, m, k;
- LOCK
- mlog_stat[type].c_malloc++;
- mlog_stat[type].t_malloc++;
- UNLOCK
+ /* easy cases */
+ if (!bytes)
+ return "0 bytes";
+ if (bytes == 1)
+ return "1 byte";
- memory = zmalloc (type, size);
- mtype_log ("zmalloc", memory, file, line, type);
+ if (sizeof (ulong) >= 8)
+ /* Hacked to make it not warn on ILP32 machines
+ * Shift will always be 40 at runtime. See below too */
+ t = bytes >> (sizeof (ulong) >= 8 ? 40 : 0);
+ else
+ t = 0;
- return memory;
-}
+ g = bytes >> 30;
+ m = bytes >> 20;
+ k = bytes >> 10;
-void *
-mtype_zcalloc (const char *file, int line, enum MTYPE type, size_t size)
-{
- void *memory;
+ if (t > 10)
+ {
+ /* The shift will always be 39 at runtime.
+ * Just hacked to make it not warn on 'smaller' machines.
+ * Static compiler analysis should mean no extra code
+ */
+ if (bytes & (1UL << (sizeof (ulong) >= 8 ? 39 : 0)))
+ t++;
+ snprintf (buf, len, "%4u TiB", t);
+ }
+ else if (g > 10)
+ {
+ if (bytes & (1 << 29))
+ g++;
+ snprintf (buf, len, "%u GiB", g);
+ }
+ else if (m > 10)
+ {
+ if (bytes & (1 << 19))
+ m++;
+ snprintf (buf, len, "%u MiB", m);
+ }
+ else if (k > 10)
+ {
+ if (bytes & (1 << 9))
+ k++;
+ snprintf (buf, len, "%d KiB", k);
+ }
+ else
+ snprintf (buf, len, "%lu bytes", bytes);
- LOCK
- mlog_stat[type].c_calloc++;
- mlog_stat[type].t_calloc++;
- UNLOCK
+ return buf;
+} ;
- memory = zcalloc (type, size);
- mtype_log ("xcalloc", memory, file, line, type);
+/*==============================================================================
+ * Establishing (if can) the malloc() parameters.
+ */
- return memory;
-}
+static qfb_gen_t mem_alloc_unknown_message(void) ;
-void *
-mtype_zrealloc (const char *file, int line, enum MTYPE type, void *ptr,
- size_t size)
+static inline size_t
+mem_alloc_padding(size_t s)
{
- void *memory;
+ size_t sp ;
+ sp = (s <= mem_alloc_min_max) ? 0 : ((s - mem_alloc_min_max + mem_alloc_add)
+ & ~mem_alloc_add) ;
+ return sp + mem_alloc_min_max - s ;
+} ;
- /* Realloc need before allocated pointer. */
- LOCK
- mlog_stat[type].t_realloc++;
- UNLOCK
+/*------------------------------------------------------------------------------
+ * This tries to find out how malloc() system manages memory, overhead-wise.
+ */
+static void
+mem_alloc_discover(void)
+{
+ enum { show = false } ;
+
+ size_t alloc ;
+ FILE* out = show ? stdout : NULL ;
+
+ /* Make sure the statics are initialised -- cleared down to zero */
+ mem_page_table = NULL ;
+ mem_free_descriptors = NULL ;
+ mem_next_index = 0 ;
+
+ mem_base_count = 0 ;
+ mem_bases = NULL ;
+
+ /* Try to establish the memory allocation parameters */
+ mem_alloc_known = false ;
+ mem_alloc_failed = ma_not_tried ;
+
+ mem_alloc_first = (uintptr_t)((char*)malloc(1)) ;
+ free((void*)mem_alloc_first) ;
+
+ mem_alloc_min_size = 0 ;
+ mem_alloc_redtape = 0 ;
+ mem_alloc_min_max = 0 ;
+ mem_alloc_unit = 0 ;
+
+ if (show)
+ fprintf(out, "first malloc(1):%p\n", (void*)mem_alloc_first) ;
+
+ /* In this loop we allocate blocks 'a' and then 'b' of the same size,
+ * increasing from 1 by 1 each time round the loop.
+ *
+ * We assume (at a minimum) that at the edge of the heap 'b' will immediately
+ * follow 'a'.
+ *
+ * So, we expect to find the actual size of each allocation (including
+ * redtape and padding) as 'b - a'. After allocating 1 byte, expect the
+ * size to remain the same until we allocate the minimum allocation + 1,
+ * at which point we expect the size to increase by the allocation unit.
+ *
+ * Note that we do not require 'a' to be allocated at the same address
+ * each time -- only that if one is allocated using reclaimed space, then
+ * both are. [A common approach is for released space to remain in the units
+ * it was originally allocated in -- so, not coalesce free space, at least
+ * not until there is pressure to do so. If malloc() does this, allocating
+ * 'a' and 'b' the same size means that 'a' and 'b' will fit in their
+ * first locations until the minimum allocation is exceeded, at which point
+ * they will neither of them fit there.]
+ *
+ * We leave this loop:
+ *
+ * * with mem_alloc_unit != 0 if all has gone well so far.
+ *
+ * or mem_alloc_unit == 0 if got something unexpected
+ * mem_alloc_failed == indication of what went wrong.
+ *
+ * * mem_alloc_min_size
+ *
+ * * mem_alloc_min_max
+ */
+ alloc = 0 ;
+
+ while (1)
+ {
+ char* a, * b ;
+ size_t size ;
+ uint i ;
- memory = zrealloc (type, ptr, size);
+ ++alloc ;
- mtype_log ("xrealloc", memory, file, line, type);
+ a = malloc(alloc) ;
+ b = malloc(alloc) ;
- return memory;
-}
+ if (b < a)
+ {
+ mem_alloc_failed = ma_not_a_before_b ;
+ break ;
+ } ;
-/* Important function. */
-void
-mtype_zfree (const char *file, int line, enum MTYPE type, void *ptr)
+ size = b - a ;
+ for (i = 0 ; i < alloc ; ++i)
+ *(a + i) = i ;
+
+ free(b) ;
+ free(a) ;
+
+ if (show)
+ {
+ fprintf(out, "alloc:%3zd a:%p b:%p size=%zd\n",
+ alloc, a, b, size) ;
+ a = (char*)mem_alloc_first ;
+ while (a < b)
+ {
+ fprintf(out, " %p: %016llX\n", a, (ullong)(*(uint64_t*)a)) ;
+ a += 8 ;
+ } ;
+ } ;
+
+ if (mem_alloc_min_size == 0)
+ mem_alloc_min_size = size ; /* capture first size */
+
+ if (mem_alloc_min_size == size)
+ mem_alloc_min_max = alloc ; /* increases until size changes */
+ else
+ {
+ /* As soon as the size changes we stop increasing the allocation
+ * request and exit the loop.
+ *
+ * If the size increased, we assume it has done so by the minimum
+ * allocation unit.
+ */
+ if (mem_alloc_min_size < size)
+ mem_alloc_unit = size - mem_alloc_min_size ;
+ else
+ mem_alloc_failed = ma_not_increasing_size ;
+ break ;
+ } ;
+ } ;
+
+ if (show)
+ fprintf(out, "size_min=%zd alloc_min=%zd alloc_unit=%zd\n",
+ mem_alloc_min_size, mem_alloc_min_max, mem_alloc_unit) ;
+
+ if (mem_alloc_unit != 0)
+ {
+ size_t x ;
+
+ /* Assume all is well. */
+ mem_alloc_redtape = mem_alloc_min_size - mem_alloc_min_max ;
+ mem_alloc_add = mem_alloc_unit - 1 ;
+
+ x = ((mem_alloc_unit ^ (mem_alloc_unit - 1)) >> 1) + 1 ;
+ if (x != mem_alloc_unit)
+ mem_alloc_failed = ma_unit_not_power_2 ;
+ else if ((mem_alloc_first % mem_alloc_unit) != 0)
+ mem_alloc_failed = ma_address_not_n_unit ;
+ else if ((mem_alloc_min_size % mem_alloc_unit) != 0)
+ mem_alloc_failed = ma_size_min_not_n_unit ;
+ else
+ {
+ char* b ;
+
+ mem_alloc_trial_alloc = 31415 ;
+ mem_alloc_trial_addr = malloc(mem_alloc_trial_alloc) ;
+ b = malloc(mem_alloc_trial_alloc) ;
+ free(b) ;
+ free(mem_alloc_trial_addr) ;
+
+ mem_alloc_trial_size = b - mem_alloc_trial_addr ;
+
+ if (mem_alloc_trial_size !=
+ (mem_alloc_redtape + mem_alloc_trial_alloc
+ + mem_alloc_padding(mem_alloc_trial_alloc)))
+ mem_alloc_failed = ma_trial_failed ;
+ else if (((uintptr_t)mem_alloc_trial_addr % mem_alloc_unit) != 0)
+ mem_alloc_failed = ma_trial_failed ;
+ else if ((mem_alloc_trial_size % mem_alloc_unit) != 0)
+ mem_alloc_failed = ma_trial_failed ;
+ else
+ mem_alloc_known = true ;
+ } ;
+ } ;
+
+ if (!mem_alloc_known)
+ mem_alloc_add = 0 ;
+
+ if (show)
+ {
+ if (mem_alloc_known)
+ fprintf(out,
+ "Allocation parameters: redtape=%zu min_max_alloc=%zu alloc unit=%zu\n",
+ mem_alloc_redtape, mem_alloc_min_max, mem_alloc_unit) ;
+ else
+ fprintf(out, "Allocation parameters not known: %s\n",
+ mem_alloc_unknown_message().str) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Message explaining why allocation parameters are not known
+ *
+ * Message expected to follow something like: "allocation parameters unknown: "
+ */
+static qfb_gen_t
+mem_alloc_unknown_message(void)
{
- LOCK
- mlog_stat[type].t_free++;
- UNLOCK
+ QFB_QFS(qfb_gen, qfs) ;
- mtype_log ("xfree", ptr, file, line, type);
+ switch (mem_alloc_failed)
+ {
+ case ma_not_tried:
+ qfs_append(qfs, "not yet tried to establish parameters") ;
+ break ;
+
+ case ma_not_a_before_b:
+ qfs_append(qfs, "malloc() allocated 'a' at higher address than 'b'") ;
+ break ;
+
+ case ma_not_increasing_size:
+ qfs_append(qfs,
+ "malloc() apparently allocated less space for larger request") ;
+ break ;
+
+ case ma_unit_not_power_2:
+ qfs_printf(qfs, "mem_alloc_unit=%zd is not power of 2\n",
+ mem_alloc_unit) ;
+ break ;
+
+ case ma_address_not_n_unit:
+ qfs_printf(qfs,
+ "first address in heap (%p) is not multiple of alloc_unit=%zd",
+ (void*)mem_alloc_first, mem_alloc_unit) ;
+ break ;
+
+ case ma_size_min_not_n_unit:
+ qfs_printf(qfs, "minimum size (%zd) is not multiple of alloc_unit=%zd",
+ mem_alloc_min_size, mem_alloc_unit) ;
+ break ;
+
+ case ma_trial_failed:
+ qfs_printf(qfs,
+ "parameters trial failed: malloc(%zu) gave %zu bytes at %p",
+ mem_alloc_trial_alloc, mem_alloc_trial_size, mem_alloc_trial_addr) ;
+ break ;
+
+ default:
+ qfs_printf(qfs, "for unknown reason %d", mem_alloc_failed) ;
+ break ;
+ } ;
- zfree (type, ptr);
-}
+ qfs_term(qfs) ;
-char *
-mtype_zstrdup (const char *file, int line, enum MTYPE type, const char *str)
+ return qfb_gen ;
+} ;
+
+/*==============================================================================
+ * Memory Region Mapping
+ *
+ * Uses a mmaped area to hold:
+ *
+ * 1. array of mem_region_t objects, one for each region in /proc/self/maps
+ *
+ * 2. array of mem_region pointers, for all regions in ascending address
+ * order.
+ *
+ * 3. array of mem_region pointers, for all "malloc" regions in ascending
+ * address order -- this is used when memory tracker maps current
+ * allocations to the memory map.
+ */
+static void* mem_region_map_base = NULL ; /* mmaped area base */
+static size_t mem_region_map_size = 0 ; /* mmaped area size */
+
+static uint mem_region_count = 0 ; /* current count */
+static uint mem_region_heap_count = 0 ; /* current count, heap */
+static uint mem_region_max_count = 0 ; /* current limit */
+
+static mem_region_t* mem_regions = NULL ; /* known regions */
+static mem_region* mem_region_v = NULL ; /* all regions */
+static mem_region* mem_region_h = NULL ; /* heap regions */
+
+static mem_region mem_seek_region(mem_region vec[], uint count, uintptr_t addr);
+static FILE* mem_open_maps(void) ;
+static bool mem_read_region(mem_region reg, FILE* maps) ;
+static int mem_open_pagemap(void) ;
+static uint mem_read_pagemap(uint64_t* pm, int pagemap, uintptr_t v,
+ uint count) ;
+static qfb_gen_t mem_form_region(mem_region reg) ;
+
+static void mem_sort_regions(mem_region* base, uint count) ;
+static int mem_cmp_region(const cvp* a, const cvp* b) ;
+static int mem_in_region(cvp k, const cvp* v) ;
+
+/*------------------------------------------------------------------------------
+ * Get an up to date map of all regions -- assumes lock held.
+ *
+ * Reads the /proc/self/maps file and fills in the mem_regions array, if
+ * required, will create a new mmaped area if runs out of space.
+ *
+ * Fill in and sort the mem_region_v.
+ *
+ * Empty the mem_region_h.
+ */
+static void
+mem_map_regions(void)
{
- char *memory;
+ FILE* maps ;
+ pid_t euid ;
- LOCK
- mlog_stat[type].c_strdup++;
- UNLOCK
+ euid = geteuid() ;
+ seteuid(getuid()) ;
- memory = zstrdup (type, str);
+ maps = mem_open_maps() ;
- mtype_log ("xstrdup", memory, file, line, type);
+ mem_region_count = 0 ; /* current count */
+ mem_region_heap_count = 0 ; /* current count, heap */
- return memory;
-}
-#endif
+ if (maps != NULL)
+ {
+ bool alloc = (mem_region_max_count == 0) ;
-/*==============================================================================
- * Showing memory allocation
+ while (1)
+ {
+ size_t map_size, vec_size, tot_size ;
+ mem_region_t reg[1] ;
+
+ if (alloc)
+ {
+ if (mem_region_map_base != NULL)
+ mem_munmap(mem_region_map_base, mem_region_map_size) ;
+
+ mem_region_max_count = ((mem_region_max_count + 64) | 0xFF) + 1 ;
+ } ;
+
+ map_size = ((mem_region_max_count * sizeof(mem_region_t)) | 0xFF) + 1;
+ vec_size = ((mem_region_max_count * sizeof(mem_region)) | 0xFF) + 1;
+
+ tot_size = map_size + (vec_size * 2) ;
+
+ if (alloc)
+ {
+ mem_region_map_base = mem_mmap(tot_size) ; /* zero fills */
+ mem_region_map_size = tot_size ;
+ }
+ else
+ {
+ assert(mem_region_map_size == tot_size) ;
+
+ memset(mem_region_map_base, 0, mem_region_map_size) ;
+ } ;
+
+ mem_regions = (mem_region)mem_region_map_base ;
+ mem_region_v = (mem_region*)((char*)mem_region_map_base + map_size) ;
+ mem_region_h = (mem_region*)((char*)mem_region_map_base + map_size
+ + vec_size) ;
+
+ while (mem_read_region(reg, maps))
+ {
+ if (mem_region_count < mem_region_max_count)
+ {
+ mem_regions[mem_region_count] = *reg ;
+ mem_region_v[mem_region_count] =
+ &mem_regions[mem_region_count] ;
+ } ;
+ ++mem_region_count ;
+ } ;
+
+ /* OK if had enough memory for complete map */
+ if (mem_region_count <= mem_region_max_count)
+ break ;
+
+ /* Page map did not fit -- alloc new memory according to what we
+ * now know is required.
+ */
+ mem_region_max_count = mem_region_count ;
+ mem_region_count = 0 ; /* reset count */
+
+ fseek(maps, 0, SEEK_SET) ;
+ alloc = true ;
+ } ;
+
+ fclose(maps) ;
+ } ;
+
+ seteuid(euid) ;
+
+ mem_sort_regions(mem_region_v, mem_region_count) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Find how much is in memory and how much is swapped out -- assumes lock held.
+ *
+ * Assumes the regions have just been updated.
*/
+static void
+mem_weigh_regions(void)
+{
+ enum { show = false } ;
-/* Looking up memory status from vty interface. */
-#include "vector.h"
-#include "vty.h"
-#include "command.h"
+ int pagemap ;
+ pid_t euid ;
+
+ euid = geteuid() ;
+ seteuid(getuid()) ;
+
+ pagemap = mem_open_pagemap() ;
+ if (pagemap >= 0)
+ {
+ enum { pm_entries = 4096 / sizeof(uint64_t) } ;
+
+ uint64_t pm[pm_entries] ;
+ uint i ;
+
+ for (i = 0 ; i < mem_region_count ; ++i)
+ {
+ mem_region reg ;
+ uintptr_t v ;
+ uint n, j ;
+
+ reg = mem_region_v[i] ;
+
+ v = reg->base ;
+ assert((v % mem_pagesize) == 0) ;
+
+ reg->present = 0 ;
+ reg->swapped = 0 ;
+
+ n = 0 ;
+ j = 0 ;
+ while (v < reg->limit)
+ {
+ uint64_t pme ;
+
+ if (j == n)
+ {
+ n = mem_read_pagemap(pm, pagemap, v, pm_entries) ;
+ j = 0 ;
+ if (n == 0)
+ break ;
+ } ;
+
+ pme = pm[j] ;
+
+ if (((pme & (3llu << 62)) != 0))
+ {
+ assert(mem_pagesize == (1llu << ((pme >> 55) & 0x3F))) ;
+
+ if ((pme & (1llu << 63)) != 0)
+ reg->present += mem_pagesize ;
+ else
+ reg->swapped += mem_pagesize ;
+ } ;
+
+ j += 1 ;
+ v += mem_pagesize ;
+ } ;
+
+ if (show)
+ fputs(mem_form_region(reg).str, stdout) ;
+ } ;
+
+ close(pagemap) ;
+ } ;
+
+ seteuid(euid) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Account for tracked memory object in the map of regions -- assume lock held.
+ */
static void
-log_memstats(int pri)
+mem_account_region(uintptr_t address, size_t size)
{
- struct mstat mst ;
- struct mlist *ml;
+ mem_region reg ;
- LOCK ;
- mst = mstat ;
- UNLOCK ;
+ reg = mem_seek_region(mem_region_h, mem_region_heap_count, address) ;
- for (ml = mlists; ml->list; ml++)
+ if (reg == NULL)
{
- struct memory_list *m;
+ reg = mem_seek_region(mem_region_v, mem_region_count, address) ;
+ assert(reg != NULL) ;
- zlog (NULL, pri, "Memory utilization in module %s:", ml->name);
- for (m = ml->list; m->index >= 0; m++)
+ mem_region_h[mem_region_heap_count++] = reg ;
+ mem_sort_regions(mem_region_h, mem_region_heap_count) ;
+ } ;
+
+ reg->count += 1 ;
+ reg->used += size ;
+ reg->overhead += mem_alloc_redtape + mem_alloc_padding(size) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Count maximum number of summary regions
+ */
+static uint
+mem_count_summary_regions(void)
+{
+ uint i, h ;
+
+ h = 0 ;
+ for (i = 0 ; i < mem_region_heap_count ; ++i)
+ {
+ if (mem_region_h[i]->sex == mrx_anon)
{
- unsigned long alloc = mst.mt[m->index].alloc ;
- if (m->index && alloc)
- zlog (NULL, pri, " %-30s: %10ld", m->format, alloc);
- }
- }
-}
+ mem_region_h[i]->sex = mrx_heap_extra ;
+ ++h ;
+ } ;
+ } ;
-void
-log_memstats_stderr (const char *prefix)
+ for (i = 0 ; i < mem_region_count ; ++i)
+ {
+ if (mem_region_v[i]->sex == mrx_heap_main)
+ ++h ;
+ } ;
+
+ return (mrx_sex_count - mrx_heap_count) + h ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Fill in the region summary
+ *
+ * Constructs an individual entry for each mrx_heap_main or mrx_heap_extra, and
+ * summary entries for each non-heap sex.
+ *
+ * If there is only one heap entry -- which is most likely if memory_tracker is
+ * not enabled -- then does not construct a heap subtotal entry.
+ *
+ * Note that for heap entries, the count is the count is the number of known
+ * items allocated in that heap region -- this is only known with mem_tracker.
+ *
+ * For all other entries it is the number of regions. For the grand total,
+ * the count is returned zero -- cannot add count of regions to count of
+ * items and get anything sensible.
+ */
+static void
+mem_summarize_regions(mem_region_summary_t sum[], uint n)
{
- struct mstat mst ;
- struct mlist *ml;
- struct memory_list *m;
- int i;
- int j = 0;
+ uint i, h, nh, it ;
- LOCK ;
- mst = mstat ;
- UNLOCK ;
+ memset(sum, 0, sizeof(mem_region_summary_t) * n) ;
+ nh = n - (mrx_sex_count - mrx_heap_count) ;
+ h = 0 ;
- for (ml = mlists; ml->list; ml++)
+ for (i = 0 ; i < mem_region_count ; ++i)
{
- i = 0;
- for (m = ml->list; m->index >= 0; m++)
+ mem_region reg ;
+ uint is, ist ;
+
+ reg = mem_region_v[i] ;
+
+ is = reg->sex ;
+
+ if ((is == mrx_heap_main) || (is == mrx_heap_extra))
{
- unsigned long alloc = mst.mt[m->index].alloc ;
- if (m->index && alloc)
+ assert(h < nh) ;
+
+ is = mrx_heap + h++ ;
+ ist = n - 2 ;
+ sum[ist].sex = mrx_heap_total ;
+
+ if (memory_tracker)
{
- if (!i)
- fprintf (stderr,
- "%s: memstats: Current memory utilization in module %s:\n",
- prefix,
- ml->name);
- fprintf (stderr,
- "%s: memstats: %-30s: %10ld%s\n",
- prefix,
- m->format,
- alloc,
- alloc < 0 ? " (REPORT THIS BUG!)" : "");
- i = j = 1;
- }
+ sum[is].count = reg->count ;
+ sum[is].used = reg->used ;
+ sum[is].overhead = reg->overhead ;
+ sum[is].unused = (reg->limit - reg->base)
+ - (reg->used + reg->overhead) ;
+
+ sum[ist].count += sum[is].count ;
+ sum[ist].used += sum[is].used;
+ sum[ist].overhead += sum[is].overhead ;
+ sum[ist].unused += sum[is].unused ;
+ } ;
+
+ if ((mem_alloc_first >= reg->base) && (mem_alloc_first <= reg->limit))
+ {
+ sum[is].data = mem_alloc_first - reg->base ;
+ sum[ist].data = sum[is].data ;
+ } ;
}
+ else
+ {
+ ist = mrx_rest_total ;
+ sum[ist].sex = mrx_rest_total ;
+
+ sum[is].count += 1 ;
+ sum[ist].count += 1 ;
+ } ;
+
+ sum[is].sex = reg->sex ;
+ sum[is].size += (reg->limit - reg->base) ;
+ sum[is].present += reg->present ;
+ sum[is].swapped += reg->swapped ;
+
+ sum[ist].size += (reg->limit - reg->base) ;
+ sum[ist].present += reg->present ;
+ sum[ist].swapped += reg->swapped ;
+ } ;
+
+ /* Construct the grand totals by copying in the heap subtotal and then
+ * adding the rest. Setting the count to be the count of regions.
+ */
+
+ it = n - 1 ;
+ sum[it] = sum[n - 2] ; /* copy heap subtotals to total */
+ sum[it].sex = mrx_grand_total ;
+
+ if (nh == 1) /* wipe out heap subtotals if only one */
+ memset(&sum[n - 2], 0, sizeof(mem_region_summary_t)) ;
+
+ sum[it].count = 0 ; /* grand total count is meaningless */
+ sum[it].size += sum[mrx_rest_total].size ;
+ sum[it].present += sum[mrx_rest_total].present ;
+ sum[it].swapped += sum[mrx_rest_total].swapped ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Seek region for given address amongst the known regions.
+ *
+ * Returns known region, or NULL.
+ */
+static mem_region
+mem_seek_region(mem_region vec[], uint count, uintptr_t addr)
+{
+ mem_region* p_reg ;
+
+ if (count <= 4)
+ {
+ uint i ;
+ for (i = 0 ; i < count ; ++i)
+ {
+ mem_region reg = vec[i] ;
+ if ((reg->base <= addr) && (addr < reg->limit))
+ return reg ;
+ } ;
+
+ return NULL ;
+ } ;
+
+ typedef int bsearch_cmp(const void*, const void*) ;
+ p_reg = bsearch(&addr, vec, count, sizeof(mem_region),
+ (bsearch_cmp*)mem_in_region) ;
+ return (p_reg != NULL) ? *p_reg : NULL ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Open /proc/self/maps if we can
+ */
+static FILE*
+mem_open_maps(void)
+{
+ FILE* maps = fopen("/proc/self/maps", "r") ;
+
+ if (maps == NULL)
+ zlog_err("%s: failed to open '/proc/self/maps': %s",
+ __func__, errtoa(errno, 0).str) ;
+ return maps ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Read next entry from /proc/<pid>/maps
+ *
+ * Each entry takes the form:
+ *
+ * base limit perm offset dev inode name
+ * 00400000-0040b000 r-xp 00000000 08:03 262262 /bin/cat
+ *
+ * It is assumed that all values are hex except the inode.
+ *
+ * Zeroises the mem_region object and sets:
+ *
+ * base - base of region
+ * limit - last address + 1 of region
+ * flags - as permissions and the name of the region
+ * offset - offset within device (if any)
+ * dev_major
+ * dev_minor
+ * inode
+ * name - which may be [heap], [stack] etc. or path to mapped file.
+ */
+static bool
+mem_read_region(mem_region reg, FILE* maps)
+{
+ int ch, i ;
+ size_t a, b ;
+
+ memset(reg, 0, sizeof(mem_region_t)) ;
+
+ /* Chew up leading white-space -- spaces/tabs/newlines etc. */
+ while (isspace(ch = fgetc(maps))) ;
+ if (ch == EOF)
+ return false ;
+ ungetc(ch, maps) ;
+
+ /* Base and limit */
+ i = fscanf(maps, "%zx-%zx", &a, &b) ;
+
+ if ((i != 2) || (a > b))
+ {
+ zlog_err("%s: invalid base or limit (%d)", __func__, i) ;
+ return false ;
+ } ;
+
+ confirm(sizeof(size_t) >= sizeof(uintptr_t)) ;
+
+ reg->base = a ;
+ reg->limit = b ;
+
+ /* Flags */
+ while (isblank(ch = fgetc(maps))) ;
+
+ do
+ {
+ switch (ch)
+ {
+ case '-':
+ break ;
+
+ case 'r':
+ reg->flags |= mrf_read ;
+ break ;
+
+ case 'w':
+ reg->flags |= mrf_write ;
+ break ;
+
+ case 'x':
+ reg->flags |= mrf_execute ;
+ break ;
+
+ case 'p':
+ reg->flags |= mrf_private ;
+ break ;
+
+ case 's':
+ reg->flags |= mrf_shared ;
+ break ;
+
+ default:
+ zlog_err("%s: invalid permission '%c'", __func__, ch) ;
+ return false ;
+ } ;
}
+ while (!isblank(ch = fgetc(maps))) ;
- if (j)
- fprintf (stderr,
- "%s: memstats: NOTE: If configuration exists, utilization may be "
- "expected.\n",
- prefix);
+ /* offset, device and inode */
+ i = fscanf(maps, "%zx %x:%x %llu", &reg->offset,
+ &reg->dev_major, &reg->dev_minor,
+ &reg->inode) ;
+ if (i != 4)
+ {
+ zlog_err("%s: invalid offset, device or inode (%d)", __func__, i) ;
+ return false ;
+ } ;
+
+ /* Skip spaces and then pick up the name (if any) */
+ while (isblank(ch = fgetc(maps))) ;
+
+ i = 0 ;
+ while ((ch != '\n') && (ch != EOF))
+ {
+ if (i < (mem_region_max_name_len + 1))
+ reg->name[i++] = ch ;
+
+ ch = fgetc(maps) ;
+ } ;
+
+ if (i == (mem_region_max_name_len + 1))
+ {
+ i -= 4 ;
+ while (i < mem_region_max_name_len)
+ reg->name[i++] = '.' ;
+ } ;
+
+ reg->name[i] = '\0' ;
+
+ /* Establish type */
+ if (i == 0)
+ reg->type = mrt_anon ;
+ else if (reg->name[0] == '[')
+ {
+ if (strcmp(reg->name, "[heap]") == 0)
+ reg->type = mrt_heap ;
+ else if (strcmp(reg->name, "[stack]") == 0)
+ reg->type = mrt_stack ;
+ else if (strcmp(reg->name, "[vdso]") == 0)
+ reg->type = mrt_vdso ;
+ else if (strcmp(reg->name, "[vsyscall]") == 0)
+ reg->type = mrt_vsyscall ;
+ else
+ reg->type = mrt_other ;
+ }
else
- fprintf (stderr,
- "%s: memstats: No remaining tracked memory utilization.\n",
- prefix);
-}
+ reg->type = mrt_named ;
+
+ /* Establish sex
+ *
+ * Note that mrx_heap_extra is only discovered if/when the mem_tracker data
+ * is scanned to see if anything has been malloced in the region.
+ */
+ reg->sex = mrx_exotic ;
+
+ switch (reg->flags & (mrf_read | mrf_write | mrf_execute))
+ {
+ case mrf_read | 0 | mrf_execute:
+ if ((reg->type == mrt_named) || (reg->type == mrt_vdso)
+ || (reg->type == mrt_vsyscall))
+ reg->sex = mrx_code ;
+ break ;
+
+ case mrf_read:
+ if (reg->type == mrt_named)
+ reg->sex = mrx_const ;
+ break ;
+
+ case mrf_read | mrf_write:
+ if (reg->type == mrt_named)
+ reg->sex = mrx_data ;
+ else if (reg->type == mrt_heap)
+ reg->sex = mrx_heap_main ;
+ else if (reg->type == mrt_anon)
+ reg->sex = mrx_anon ;
+ else if (reg->type == mrt_stack)
+ reg->sex = mrx_stack ;
+ break ;
+
+ case 0:
+ if (reg->type == mrt_named)
+ reg->sex = mrx_reserved ;
+ else if (reg->type == mrt_anon)
+ reg->sex = mrx_gap ;
+ break ;
+
+ default: /* mrf_read | mrf_write | mrf_execute
+ 0 | mrf_write | mrf_execute
+ 0 | mrf_write | 0
+ 0 | 0 | mrf_execute */
+ break ;
+ } ;
+
+ return true ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Open /proc/self/pagemap if we can
+ */
+static int
+mem_open_pagemap(void)
+{
+ int pagemap = open("/proc/self/pagemap", O_RDONLY) ;
+
+ if (pagemap < 0)
+ zlog_err("%s: failed to open '/proc/self/pagemap': %s",
+ __func__, errtoa(errno, 0).str) ;
+ return pagemap ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Read set of entries from /proc/self/pagemap.
+ */
+static uint
+mem_read_pagemap(uint64_t* pm, int pagemap, uintptr_t v, uint count)
+{
+ off_t off ;
+ int n ;
+
+ off = (v / mem_pagesize) * sizeof(uint64_t) ;
+ if (lseek(pagemap, off, SEEK_SET) < 0)
+ {
+ zlog_err("%s: failed to seek to %llX in '/proc/self/pagemap': %s",
+ __func__, (long long)off, errtoa(errno, 0).str) ;
+ return 0 ;
+ } ;
+
+ n = read(pagemap, pm, sizeof(uint64_t) * count) ;
+
+ if (n < 0)
+ {
+ zlog_err("%s: failed reading at %llX in '/proc/self/pagemap': %s",
+ __func__, (long long)off, errtoa(errno, 0).str) ;
+ n = 0 ;
+ } ;
+
+ assert((n % sizeof(uint64_t)) == 0) ;
+
+ return n / sizeof(uint64_t) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Construct string describing a memory region
+ */
+static qfb_gen_t
+mem_form_region(mem_region reg)
+{
+ QFB_QFS(qfb_gen, qfs) ;
+
+ size_t a, b, s ;
+ int ch ;
+
+ confirm(sizeof(size_t) >= sizeof(uintptr_t)) ;
+
+ a = reg->base >> 32 ;
+ b = reg->base & 0xFFFFFFFF ;
+ s = reg->limit - reg->base ;
+
+ qfs_printf(qfs, "%08zX_%08zX %010zX %010zX %010zX ", a, b, s,
+ reg->present, reg->swapped) ;
+
+ qfs_printf(qfs, "%c", ((reg->flags & mrf_read) != 0) ? 'r' : '-') ;
+ qfs_printf(qfs, "%c", ((reg->flags & mrf_write) != 0) ? 'w' : '-') ;
+ qfs_printf(qfs, "%c", ((reg->flags & mrf_execute) != 0) ? 'x' : '-') ;
+ qfs_printf(qfs, "%c", ((reg->flags & mrf_private) != 0)
+ ? ( ((reg->flags & mrf_shared) != 0) ? 'Q' : 'p' )
+ : ( ((reg->flags & mrf_shared) != 0) ? 's' : '-' )) ;
+
+ ch = (reg->type < mrt_type_count) ? mrf_type_chars[reg->type] : '\0' ;
+ if (ch == '\0')
+ ch = '?' ;
+
+ qfs_printf(qfs, " %08zX %02X:%02X %8lld %c %s\n", reg->offset,
+ reg->dev_major, reg->dev_minor,
+ reg->inode, ch, reg->name) ;
+ qfs_term(qfs) ;
+
+ return qfb_gen ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Sort given vector of pointers to regions, and then make sure they are
+ * in order (!) and that the limit >= base for each one.
+ */
static void
-show_memory_type_vty (struct vty *vty, const char* name,
- struct mem_tracker* mt, long int alloc, int sep)
+mem_sort_regions(mem_region* base, uint count)
{
- if (sep)
- vty_out (vty, "-----------------------------%s", VTY_NEWLINE) ;
+ uint i ;
+ uintptr_t ll ;
+
+ typedef int qsort_cmp(const void*, const void*) ;
+ qsort(base, count, sizeof(mem_region), (qsort_cmp*)mem_cmp_region) ;
+
+ ll = (uintptr_t)NULL ;
+ for (i = 0 ; i < count ; ++i)
+ {
+ assert(base[i]->base >= ll) ;
+ ll = base[i]->limit ;
+ assert(base[i]->base <= ll) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Compare two regions for qsort
+ *
+ * Don't think it likely, but if bases are equal, sorts by limit. Allows for
+ * empty region(s) ?
+ */
+static int
+mem_cmp_region(const cvp* a, const cvp* b)
+{
+ mem_region_c ra = *(const mem_region_c*)a ;
+ mem_region_c rb = *(const mem_region_c*)b ;
- vty_out (vty, "%-30s:", name) ;
+ if (ra->base != rb->base)
+ return (ra->base < rb->base) ? -1 : +1 ;
- if (memory_tracker)
- show_memory_tracker_detail(vty, mt, alloc) ;
- else
- vty_out (vty, " %10ld", alloc) ;
+ if (ra->limit != rb->limit)
+ return (ra->limit < rb->limit) ? -1 : +1 ;
- vty_out (vty, "%s", VTY_NEWLINE);
+ return 0 ;
} ;
+/*------------------------------------------------------------------------------
+ * Compare two regions for bsearch
+ */
static int
-show_memory_vty (struct vty *vty, struct memory_list *m, struct mlist* ml,
- int needsep)
+mem_in_region(cvp k, const cvp* v)
{
- int notempty = 0 ;
+ const uintptr_t kv = *(const uintptr_t*)k ;
+ const mem_region_c rv = *(const mem_region_c*)v ;
- long int alloc ;
+ if (rv->base > kv)
+ return -1 ;
- struct mstat mst ;
- struct mem_tracker mem_tot ;
- struct mem_tracker mem_one ;
- struct mem_tracker* mt ;
+ if (kv >= rv->limit)
+ return +1 ;
- struct mem_type_tracker mem_tt ;
+ return 0 ;
+} ;
- LOCK ;
+/*==============================================================================
+ * Manipulation of the tracking data structures.
+ */
+static void mem_mt_make_bases(void) ;
+
+/*------------------------------------------------------------------------------
+ * Set memory type in the given mem_descriptor
+ */
+inline static void
+mem_mt_set_type(mem_descriptor md, enum MTYPE mtype)
+{
+ uint32_t t_ms ;
+ uint32_t t_ls ;
- mst = mstat ;
- mem_tt = mem_type_tracker ;
+ t_ms = mtype >> md_size_type_bits ;
+ t_ls = mtype ;
- UNLOCK ;
+ t_ms = (t_ms & md_next_type_mask) << md_next_bits ;
+ t_ls = (t_ls & md_size_type_mask) << md_size_bits ;
+
+ md->next = (md->next & md_next_mask) | t_ms ;
+ md->size = (md->size & md_size_mask) | t_ls ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the next index in the given mem_descriptor
+ */
+inline static void
+mem_mt_set_next(mem_descriptor md, md_index_t next)
+{
+ md->next = (md->next & ~md_next_mask) | (next & md_next_mask) ;
+} ;
- mem_tracker_zeroise(&mem_tot) ;
- mem_tracker_zeroise(&mem_one) ;
+/*------------------------------------------------------------------------------
+ * Set the item size in the given mem_descriptor
+ */
+inline static void
+mem_mt_set_size(mem_descriptor md, size_t size)
+{
+ md->size = (md->size & ~md_size_mask) | (size & md_size_mask) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get the item type from the given mem_descriptor
+ */
+inline static uint8_t
+mem_mt_type(mem_descriptor md)
+{
+ return ( (md->next >> (md_next_bits - md_size_type_bits))
+ & (md_next_type_mask << md_size_type_bits) )
+ | ( (md->size >> md_size_bits) & md_size_type_mask ) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get the next index from the given mem_descriptor
+ */
+inline static md_index_t
+mem_mt_next(mem_descriptor md)
+{
+ return md->next & md_next_mask ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Get the item size from the given mem_descriptor
+ */
+inline static size_t
+mem_mt_size(mem_descriptor md)
+{
+ return md->size & md_size_mask ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Map item index to mem_descriptor
+ */
+inline static mem_descriptor
+mem_mt_ptr(md_index_t mdi)
+{
+ mem_descriptor page ;
+
+ if (mdi == 0)
+ return NULL ;
+
+ page = mem_page_table[(mdi >> md_i_index_bits) & md_page_mask] ;
+ passert(page != NULL) ;
+ return page + (mdi & md_i_index_mask) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Map item address to address of base entry in the hash table.
+ */
+inline static md_index_t*
+mem_mt_base(void* address)
+{
+ if (mem_bases == NULL)
+ mem_mt_make_bases() ;
+
+ return mem_bases + ((uintptr_t)address % mem_base_count) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Make new set of bases for the hash table -- if a set already exists,
+ * rearrange the hash table and then discard the old bases.
+ */
+static void
+mem_mt_make_bases(void)
+{
+ md_index_t* bases_was = mem_bases ;
+ uint32_t count_was = mem_base_count ;
+
+ size_t size_was = count_was * sizeof(md_index_t) ;
+ size_t size_now ;
- if ((m == NULL) && (ml != NULL))
- m = (ml++)->list ;
+ mem_base_count += 256 * 1024 ;
+ mem_base_count |= 1 ;
- while (m != NULL)
+ size_now = mem_base_count * sizeof(md_index_t) ;
+
+ mem_bases = mem_mmap(size_now) ; /* zero fills */
+
+ mem_mt_data.tracker_overhead += size_now - size_was ;
+
+ if (bases_was == NULL)
+ passert(count_was == 0) ;
+ else
{
- if (m->index <= 0)
+ md_index_t* base = bases_was ;
+ md_index_t* new_base ;
+ md_index_t this ;
+ md_index_t next ;
+ mem_descriptor md ;
+
+ while (count_was)
{
- needsep = notempty ;
- if (m->index < 0)
+ next = *base++ ;
+ while (next != 0)
{
- if (ml == NULL)
- m = NULL ;
- else
- m = (ml++)->list ;
- }
+ this = next ;
+ md = mem_mt_ptr(this) ;
+ next = mem_mt_next(md) ;
+
+ new_base = mem_mt_base(md->addr) ;
+ mem_mt_set_next(md, *new_base) ;
+ *new_base = this ;
+ } ;
+ --count_was ;
+ } ;
+
+ mem_munmap(bases_was, size_was) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Make new page full of empty memory descriptors, and place them all on the
+ * free descriptors list.
+ */
+static void
+mem_mt_make_descriptors(void)
+{
+ mem_descriptor md ;
+ md_index_t mdi ;
+ size_t desc_size ;
+ size_t total_size ;
+
+ mdi = mem_next_index ;
+ passert(mdi < md_index_max) ;
+
+ desc_size = md_i_index_count * sizeof(mem_descriptor_t) ;
+ total_size = desc_size ;
+
+ if (mem_page_table == NULL)
+ {
+ desc_size = (desc_size | 0xFF) + 1 ;
+ total_size = desc_size + (md_page_count * sizeof(mem_descriptor)) ;
+ } ;
+
+ mem_mt_data.tracker_overhead += total_size ;
+
+ mem_free_descriptors = mem_mmap(total_size) ; /* zero fills */
+
+ if (mem_page_table == NULL)
+ mem_page_table = (mem_descriptor*)((char*)mem_free_descriptors + desc_size);
+
+ mem_page_table[(mdi >> md_i_index_bits) & md_page_mask]
+ = mem_free_descriptors ;
+ mem_next_index += md_i_index_count ;
+
+ if (mdi == 0)
+ {
+ ++mem_free_descriptors ; /* don't use index == 0 */
+ ++mdi ;
+ } ;
+
+ md = mem_free_descriptors ;
+ while (mdi < mem_next_index)
+ {
+ md->addr = md + 1 ; /* point at next entry */
+ md->next = mdi ; /* set to point at self */
+ ++md ;
+ ++mdi ;
+ } ;
+ (md-1)->addr = NULL ; /* set end of list */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Walk the memory_tracker descriptors, to account for all tracked memory
+ * by region -- assumes lock held.
+ */
+static void
+mem_mt_region_accounting(void)
+{
+ uint i ;
+
+ for (i = 0 ; i < mem_base_count ; ++i)
+ {
+ md_index_t this ;
+ this = mem_bases[i] ;
+ while (this != 0)
+ {
+ mem_descriptor md ;
+ md = mem_mt_ptr(this) ;
+ mem_account_region((uintptr_t)md->addr, mem_mt_size(md)) ;
+ this = mem_mt_next(md) ;
+ } ;
+ } ;
+} ;
+
+/*==============================================================================
+ * Tracking of malloc/realloc/free -- calloc treated as malloc.
+ */
+
+/*------------------------------------------------------------------------------
+ * memory_tracker malloc() extension.
+ */
+inline static void
+mem_mt_malloc(mtype_t mtype, void* address, size_t size, const char* name)
+{
+ mem_tracker_item typ ;
+ md_index_t* base ;
+ mem_descriptor md ;
+ md_index_t mdi ;
+
+ passert(size <= md_size_max) ;
+ passert(mtype < MTYPE_MAX) ;
+
+ if (mem_free_descriptors == NULL)
+ mem_mt_make_descriptors() ;
+
+ md = mem_free_descriptors ;
+ mem_free_descriptors = md->addr ;
+ mdi = md->next ;
+
+ if (mem_mt_data.tot->item_count >= (mem_base_count * 4))
+ mem_mt_make_bases() ;
+
+ base = mem_mt_base(address) ;
+
+ md->addr = address ;
+ md->name = name ;
+ md->size = size ;
+ md->next = *base ;
+ mem_mt_set_type(md, mtype) ;
+
+ *base = mdi ;
+
+ /* Keep track of total number of all items and the total size of same,
+ * and the peak value of those.
+ */
+ mem_mt_data.tot->item_count += 1 ;
+ mem_mt_data.tot->total_size += size ;
+
+ if (mem_mt_data.tot->peak_item_count < mem_mt_data.tot->item_count)
+ mem_mt_data.tot->peak_item_count = mem_mt_data.tot->item_count ;
+
+ if (mem_mt_data.tot->peak_total_size < mem_mt_data.tot->total_size)
+ mem_mt_data.tot->peak_total_size = mem_mt_data.tot->total_size ;
+
+ /* Now all data for items of this type.
+ */
+ typ = &(mem_mt_data.typ[mtype]) ;
+
+ typ->malloc_count += 1 ;
+ typ->item_count += 1 ;
+ typ->total_size += size ;
+
+ if (typ->peak_item_count < typ->item_count)
+ typ->peak_item_count = typ->item_count ;
+
+ if (typ->peak_total_size < typ->total_size)
+ typ->peak_total_size = typ->total_size ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * memory_tracker free() extension.
+ */
+inline static void
+mem_mt_free(mtype_t mtype, void* address)
+{
+ mem_tracker_item typ ;
+ md_index_t* base ;
+ mem_descriptor md, prev_md ;
+ md_index_t this, next ;
+
+ passert(mtype < MTYPE_MAX) ;
+
+ if (address == NULL)
+ return ;
+
+ base = mem_mt_base(address) ;
+
+ prev_md = NULL ;
+ this = *base ;
+ while (this != 0)
+ {
+ md = mem_mt_ptr(this) ;
+ next = mem_mt_next(md) ;
+
+ if (md->addr == address)
+ {
+ size_t size = mem_mt_size(md) ;
+
+ if (mem_mt_type(md) != mtype)
+ zabort("memory type mismatch in free") ;
+
+ mem_mt_data.tot->item_count -= 1 ;
+ mem_mt_data.tot->total_size -= size ;
+
+ typ = &(mem_mt_data.typ[mtype]) ;
+
+ typ->free_count += 1 ;
+ typ->item_count -= 1 ;
+ typ->total_size -= size ;
+
+ if (prev_md == NULL)
+ *base = next ;
else
- ++m ;
+ mem_mt_set_next(prev_md, next) ;
+
+ md->addr = mem_free_descriptors ;
+ mem_free_descriptors = md ;
+ md->next = this ;
+
+ return ;
}
else
{
- alloc = mst.mt[m->index].alloc ;
- if (memory_tracker)
- mt = &(mem_tt.mt[m->index]) ;
+ prev_md = md ;
+ this = next ;
+ } ;
+ } ;
+
+ zabort("Failed to find memory being freed") ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * memory_tracker realloc() extension.
+ */
+inline static void
+mem_mt_realloc(mtype_t mtype, void* old_address, void* new_address,
+ size_t new_size, const char* name)
+{
+ mem_tracker_item typ ;
+ md_index_t* base ;
+ mem_descriptor md, prev_md ;
+ md_index_t this, next ;
+
+ if (old_address == NULL)
+ {
+ mem_mt_malloc(mtype, new_address, new_size, name) ;
+ return ;
+ } ;
+
+ passert(new_size <= md_size_max) ;
+ passert(mtype < MTYPE_MAX) ;
+
+ base = mem_mt_base(old_address) ;
+
+ prev_md = NULL ;
+ this = *base ;
+ while (this != 0)
+ {
+ md = mem_mt_ptr(this) ;
+ next = mem_mt_next(md) ;
+
+ if (md->addr == old_address)
+ {
+ size_t old_size = mem_mt_size(md) ;
+
+ if (mem_mt_type(md) != mtype)
+ zabort("memory type mismatch in realloc") ;
+
+ mem_mt_data.tot->total_size += new_size - old_size ;
+
+ if (mem_mt_data.tot->peak_total_size
+ < mem_mt_data.tot->total_size)
+ mem_mt_data.tot->peak_total_size
+ = mem_mt_data.tot->total_size ;
+
+ typ = &(mem_mt_data.typ[mtype]) ;
+
+ typ->realloc_count += 1 ;
+ typ->total_size += new_size - old_size ;
+
+ if (typ->peak_total_size < typ->total_size)
+ typ->peak_total_size = typ->total_size ;
+
+ md->name = name ;
+ mem_mt_set_size(md, new_size) ;
+
+ if (old_address == new_address)
+ return ;
+
+ if (prev_md == NULL)
+ *base = next ;
+ else
+ mem_mt_set_next(prev_md, next) ;
+
+ base = mem_mt_base(new_address) ;
+ mem_mt_set_next(md, *base) ;
+ *base = this ;
+
+ md->addr = new_address ;
+
+ return ;
+ }
+ else
+ {
+ prev_md = md ;
+ this = next ;
+ } ;
+ } ;
+
+ zabort("Failed to find memory being realloced") ;
+} ;
+
+/*==============================================================================
+ * Memory Tracker Display
+ */
+
+/*------------------------------------------------------------------------------
+ * Get copy of current statistics in mem_tracker_data form.
+ *
+ * This acquires the lock (if required), copies the data and releases the
+ * lock.
+ *
+ * If not mem_tracker enabled, then takes the standard mstat stuff and
+ * creates a (largely empty) mem_tracker data set.
+ *
+ * If mem_tracker enabled, takes the mem_tracker_data and the mstat stuff.
+ * Then checks that the counts are the same, and constructs the totals for:
+ *
+ * malloc_count
+ * realloc_count
+ * free_count
+ */
+static void
+mem_mt_get_stats(mem_tracker_data mtd, bool locked)
+{
+ mem_stats_t mst ;
+ mtype_t mtype ;
+ ulong item_count ;
+ size_t total_size ;
+
+ if (!locked)
+ LOCK ;
+
+ mst = mstats ;
+ if (memory_tracker)
+ *mtd = mem_mt_data ;
+
+ UNLOCK ;
+
+ if (!memory_tracker)
+ {
+ memset(mtd, 0, sizeof(mem_tracker_data_t)) ;
+
+ item_count = 0 ;
+
+ for (mtype = 0 ; mtype < MTYPE_MAX ; ++mtype)
+ item_count += (mtd->typ[mtype].item_count = mst.alloc[mtype]) ;
+
+ mtd->tot->item_count = item_count ;
+ } ;
+
+ mtd->tot->malloc_count = 0 ;
+ mtd->tot->realloc_count = 0 ;
+ mtd->tot->free_count = 0 ;
+
+ item_count = 0 ;
+ total_size = 0 ;
+
+ for (mtype = 0 ; mtype < MTYPE_MAX ; ++mtype)
+ {
+ mtd->tot->malloc_count += mtd->typ[mtype].malloc_count ;
+ mtd->tot->realloc_count += mtd->typ[mtype].realloc_count ;
+ mtd->tot->free_count += mtd->typ[mtype].free_count ;
+
+ item_count += mtd->typ[mtype].item_count ;
+ total_size += mtd->typ[mtype].total_size ;
+ } ;
+
+ assert(item_count == mtd->tot->item_count) ;
+
+ if (memory_tracker)
+ {
+ assert(total_size == mtd->tot->total_size) ;
+ assert(item_count == mtd->tot->malloc_count - mtd->tot->free_count) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Creation of "friendly" counts and sizes display.
+ */
+
+enum { scale_max = 4 } ;
+
+static const char* scale_d_tags [] =
+{
+ [0] = " " ,
+ [1] = "'k",
+ [2] = "'m",
+ [3] = "'g",
+ [4] = "'t",
+} ;
+
+static const char* scale_b_tags [] =
+{
+ [0] = " " ,
+ [1] = "'K",
+ [2] = "'M",
+ [3] = "'G",
+ [4] = "'T",
+} ;
+
+static const ulong p10 [] =
+{
+ [ 0] = 1ul,
+ [ 1] = 10ul,
+ [ 2] = 100ul,
+ [ 3] = 1000ul,
+ [ 4] = 10000ul,
+ [ 5] = 100000ul,
+ [ 6] = 1000000ul,
+ [ 7] = 10000000ul,
+ [ 8] = 100000000ul,
+ [ 9] = 1000000000ul,
+ [10] = 10000000000ul,
+ [11] = 100000000000ul,
+ [12] = 1000000000000ul,
+ [13] = 10000000000000ul,
+ [14] = 100000000000000ul,
+ [15] = 1000000000000000ul /* 10^((4*3) + 3) */
+} ;
+
+static const ulong q10 [] =
+{
+ [ 0] = 1ul / 2,
+ [ 1] = 10ul / 2,
+ [ 2] = 100ul / 2,
+ [ 3] = 1000ul / 2,
+ [ 4] = 10000ul / 2,
+ [ 5] = 100000ul / 2,
+ [ 6] = 1000000ul / 2,
+ [ 7] = 10000000ul / 2,
+ [ 8] = 100000000ul / 2,
+ [ 9] = 1000000000ul / 2,
+ [10] = 10000000000ul / 2,
+ [11] = 100000000000ul / 2,
+ [12] = 1000000000000ul / 2,
+ [13] = 10000000000000ul / 2,
+ [14] = 100000000000000ul / 2,
+ [15] = 1000000000000000ul / 2, /* 10^((4*3) + 3) / 2 */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Form string for number, with commas and "d" decimal digits, followed by
+ * the given tag -- where d = 0..4
+ *
+ * So: val=1234567, d=2, tag="k" -> "12,345.67k".
+ * val=1234, d=0, tag="" -> "1,234"
+ */
+static num_str_t
+mem_form_commas(ulong v, const char* tag, uint d, uint f)
+{
+ QFB_QFS(num_str, qfs) ;
+
+ if (d == 0)
+ qfs_unsigned(qfs, v, pf_commas, 0, 0) ;
+ else
+ {
+ qfs_unsigned(qfs, v, 0, 0, 0) ;
+ qfs_append(qfs, ".") ;
+ qfs_unsigned(qfs, f, pf_zeros, d, 0) ;
+ } ;
+
+ qfs_append(qfs, tag) ;
+ qfs_term(qfs) ;
+
+ return num_str ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Form count with 4 significant digits, up to multiples of trillions.
+ */
+static num_str_t
+mem_form_count(ulong val)
+{
+ uint i, d ;
+ ldiv_t r ;
+
+ i = 0 ;
+ d = 0 ;
+
+ while ((val >= p10[i + 4]) && (i < (scale_max * 3)))
+ i += 3 ;
+
+ if (i == 0)
+ {
+ r.quot = val ;
+ r.rem = 0 ;
+ }
+ else
+ {
+ if (val < p10[i + 1])
+ d = 3 ;
+ else if (val < p10[i + 2])
+ d = 2 ;
+ else if (val < p10[i + 3])
+ d = 1 ;
+
+ /* Scale down to required number of decimals and round.
+ *
+ * If is thousands, then i = 3, if value = 10,000 (smallest possible)
+ * then d == 2. So divide by 5 (q10[3 - 2]) to make ls bit the rounding
+ * bit, add one and shift off the rounding bit.
+ *
+ * The result should be 1000..9999, unless value is greater than our
+ * ability to scale, or has rounded up one decade.
+ */
+ val = ((val / q10[i - d]) + 1) >> 1 ;
+
+ qassert(val >= 1000) ;
+
+ if (val > 9999)
+ {
+ if (d == 0)
+ {
+ if (i < (scale_max * 3))
+ {
+ qassert(val == 10000) ;
+
+ val = 1000 ;
+ d = 2 ;
+ i += 3 ;
+ } ;
+ }
else
- mt = &mem_one ;
- mt->tracked_count = alloc ;
-
- mem_tot.malloc_count += mt->malloc_count ;
- mem_tot.free_count += mt->free_count ;
- mem_tot.realloc_count += mt->realloc_count ;
- mem_tot.tracked_count += mt->tracked_count ;
- mem_tot.tracked_max_count += mt->tracked_max_count ;
- mem_tot.tracked_size += mt->tracked_size ;
- mem_tot.tracked_max_size += mt->tracked_max_size ;
-
- if (alloc || mt->tracked_count)
{
- show_memory_type_vty(vty, m->format, mt, alloc, needsep) ;
- needsep = 0 ;
- notempty = 1 ;
+ qassert(val == 10000) ;
+
+ val = 1000 ;
+ d -= 1 ;
} ;
+ } ;
- ++m ;
- } ;
+ r = ldiv(val, p10[d]) ;
} ;
- show_memory_type_vty(vty, "Total", &mem_tot, mem_tot.tracked_count, notempty);
+ return mem_form_commas(r.quot, scale_d_tags[i / 3], d, r.rem) ;
+} ;
- return 1 ;
+/*------------------------------------------------------------------------------
+ * Form count with 4 significant digits, up to multiples of Terabytes.
+ *
+ * So: 1023
+ */
+static num_str_t
+mem_form_byte_count(ulong val)
+{
+ ulong v ;
+ uint i, d, f ;
+
+ i = 0 ;
+ d = 0 ;
+ v = val ;
+ f = 0 ;
+
+ while ((v >= 1024) && (i < scale_max))
+ {
+ v >>= 10 ; /* find power of 1024 scale */
+ i += 1 ;
+ } ;
+
+ if (i > 0)
+ {
+ if (v < 10)
+ d = 3 ; /* number of decimals expected */
+ else if (v < 100)
+ d = 2 ;
+ else if (v < 1000)
+ d = 1 ;
+
+ /* Scale up to the required number of decimals, shift down so that
+ * only ms bit of fraction is left, round and shift off rounding bit.
+ *
+ * If d != 0, then will scale up by 10, 100 or 1000. The largest value
+ * to be scaled up is 1000 * (2^(scale_max * 10)) - 1, which will be
+ * multiplied by 10. The maximum scaled value is, therefore, bound to
+ * be < 2^(((scale_max + 1) * 10) + 4)
+ */
+ confirm((sizeof(ulong) * 8) >= (((scale_max + 1) * 10) + 4)) ;
+
+ v = (((val * p10[d]) >> ((i * 10) - 1)) + 1) >> 1 ;
+
+ qassert(v >= 1000) ;
+
+ if (d == 0)
+ {
+ if ((v == 1024) && (i < scale_max))
+ {
+ v = 1000 ; /* rounded up to next power of 1024 */
+ d = 3 ;
+ i += 1 ;
+ }
+ }
+ else
+ {
+ if (v >= 10000)
+ {
+ qassert(v == 10000) ;
+
+ v = 1000 ; /* rounded up to one less decimals */
+ d -= 1 ;
+ } ;
+ } ;
+
+ f = v % p10[d] ;
+ v = v / p10[d] ;
+ } ;
+
+ return mem_form_commas(v, scale_b_tags[i], d, f) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Show summary of memory_tracker on vty.
+ */
+static void
+show_memory_tracker_summary(vty vty, mem_tracker_data mtd)
+{
+ vty_out (vty, "Memory Tracker Statistics:\n");
+ vty_out (vty, " Current memory allocated: %13s\n",
+ mem_form_byte_count(mtd->tot->total_size).str) ;
+ vty_out (vty, " Current allocated objects: %'11lu\n",
+ mtd->tot->item_count);
+ vty_out (vty, " Maximum memory allocated: %13s\n",
+ mem_form_byte_count(mtd->tot->peak_total_size).str) ;
+ vty_out (vty, " Maximum allocated objects: %'11lu\n",
+ mtd->tot->peak_item_count) ;
+ vty_out (vty, " malloc/calloc call count: %13s\n",
+ mem_form_count (mtd->tot->malloc_count).str);
+ vty_out (vty, " realloc_call_count: %13s\n",
+ mem_form_count (mtd->tot->realloc_count).str);
+ vty_out (vty, " free call count: %13s\n",
+ mem_form_count (mtd->tot->free_count).str);
+ vty_out (vty, " Memory Tracker overhead: %13s\n",
+ mem_form_byte_count(mtd->tracker_overhead).str);
} ;
+/*==============================================================================
+ * Showing memory allocation
+ */
#ifdef HAVE_MALLINFO
-static int
-show_memory_mallinfo (struct vty *vty)
+static void
+show_memory_mallinfo (struct vty *vty, mem_tracker_data mtd)
{
- struct mallinfo minfo = mallinfo();
+ struct mallinfo minfo ;
char buf[MTYPE_MEMSTR_LEN];
+ LOCK ;
+
+ minfo = mallinfo() ;
+ mem_mt_get_stats(mtd, true) ; /* Get a snapshot of all data & UNLOCK */
+
+
vty_out (vty, "System allocator statistics:%s", VTY_NEWLINE);
vty_out (vty, " Total heap allocated: %s%s",
mtype_memstr (buf, MTYPE_MEMSTR_LEN, minfo.arena),
@@ -581,22 +2687,353 @@ show_memory_mallinfo (struct vty *vty)
vty_out (vty, " Free ordinary blocks: %s%s",
mtype_memstr (buf, MTYPE_MEMSTR_LEN, minfo.fordblks),
VTY_NEWLINE);
- vty_out (vty, " Ordinary blocks: %ld%s",
- (unsigned long)minfo.ordblks,
+ vty_out (vty, " Ordinary blocks: %lu%s",
+ (ulong)minfo.ordblks,
VTY_NEWLINE);
- vty_out (vty, " Small blocks: %ld%s",
- (unsigned long)minfo.smblks,
+ vty_out (vty, " Small blocks: %lu%s",
+ (ulong)minfo.smblks,
VTY_NEWLINE);
- vty_out (vty, " Holding blocks: %ld%s",
- (unsigned long)minfo.hblks,
+ vty_out (vty, " Holding blocks: %lu%s",
+ (ulong)minfo.hblks,
VTY_NEWLINE);
vty_out (vty, "(see system documentation for 'mallinfo' for meaning)%s",
VTY_NEWLINE);
+}
+#endif /* HAVE_MALLINFO */
+
+/*------------------------------------------------------------------------------
+ * Log the number of each type of memory currently thought to be allocated.
+ */
+static void
+log_memstats(int pri)
+{
+ mem_stats_t mst ;
+ struct mlist *ml;
+
+ mem_get_stats(&mst) ;
+
+ for (ml = mlists; ml->list; ml++)
+ {
+ struct memory_list *m;
- return 1;
+ zlog (NULL, pri, "Memory utilization in module %s:", ml->name);
+ for (m = ml->list; m->index >= 0; m++)
+ {
+ if ((m->index != 0) && (mst.alloc[m->index] != 0))
+ zlog (NULL, pri, " %-30s: %10lu", m->format, mst.alloc[m->index]) ;
+ } ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Output the number of each type of memory currently thought to be allocated,
+ * to stderr.
+ */
+extern void
+log_memstats_stderr (const char *prefix)
+{
+ mem_stats_t mst ;
+ struct mlist *ml;
+ struct memory_list *m;
+ bool seen_something ;
+
+ mem_get_stats(&mst) ;
+
+ seen_something = false ;
+ for (ml = mlists; ml->list; ml++)
+ {
+ bool first = true ;
+ for (m = ml->list; m->index >= 0; m++)
+ {
+ if ((m->index != 0) && (mst.alloc[m->index] != 0))
+ {
+ if (first)
+ {
+ fprintf (stderr,
+ "%s: memstats: Current memory utilization in module %s:\n",
+ prefix,
+ ml->name) ;
+ first = false ;
+ } ;
+ fprintf (stderr,
+ "%s: memstats: %-30s: %10lu\n",
+ prefix,
+ m->format,
+ mst.alloc[m->index]) ;
+ seen_something = true ;
+ } ;
+ } ;
+ } ;
+
+ if (seen_something)
+ fprintf (stderr,
+ "%s: memstats: NOTE: If configuration exists, utilization may be "
+ "expected.\n",
+ prefix);
+ else
+ fprintf (stderr,
+ "%s: memstats: No remaining tracked memory utilization.\n",
+ prefix);
}
+
+/*------------------------------------------------------------------------------
+ * Show all regions -- to vty.
+ *
+ * Acquires the lock while making the region map, and returns the other
+ * statistics, gathered while the lock is held.
+ */
+static void
+mem_show_region_info(vty vty, mem_tracker_data mtd)
+{
+ uint i, n ;
+
+ /* Start by making sure known regions are up to date, and then taking a
+ * local copy of them.
+ *
+ * This uses C99 variable length arrays !
+ */
+ LOCK ;
+
+ mem_map_regions() ;
+ mem_weigh_regions() ;
+
+ if (memory_tracker)
+ mem_mt_region_accounting() ;
+
+ n = mem_count_summary_regions() ;
+
+ mem_region_summary_t sum[n] ;
+ mem_summarize_regions(sum, n) ;
+
+ mem_mt_get_stats(mtd, true) ; /* Get a snapshot of all data and UNLOCK */
+
+ /* Now can show what we have
+ */
+ /* 12345678901234567890123456789012345678901234 */
+ vty_out(vty, "Region Size Present Swapped Count ") ;
+ if (memory_tracker)
+ /* 567890123456789012345678901 */
+ vty_out(vty, " Used Overhd Unused ") ;
+ /* 234567890 */
+ vty_out(vty, " Static\n") ;
+
+ for (i = 0 ; i < n ; i++)
+ {
+ mem_region_summary rs = &sum[i] ;
+ const char* name ;
+
+ if (rs->size == 0)
+ continue ; /* Ignore absent or trivial entries */
+
+ name = (rs->sex < mrx_sex_count) ? mrx_sex_names[rs->sex] : NULL ;
+ if (name == NULL)
+ name = "????" ;
+
+ /* 12--7:90-789-678-5 */
+ vty_out(vty, " %-6s: %8s %8s %8s",
+ name,
+ mem_form_byte_count(rs->size).str,
+ mem_form_byte_count(rs->present).str,
+ mem_form_byte_count(rs->swapped).str
+ ) ;
+
+ if (rs->sex != mrx_grand_total)
+ {
+ /* 67-4 */
+ vty_out(vty, " %8s", ((rs->count != 0) || memory_tracker)
+ ? mem_form_count(rs->count).str
+ : "- ") ;
+
+ if (memory_tracker && (rs->sex >= mrx_heap))
+ /* 56-345-234-1 */
+ vty_out (vty, " %8s %8s %8s",
+ mem_form_byte_count(rs->used).str,
+ mem_form_byte_count(rs->overhead).str,
+ mem_form_byte_count(rs->unused).str) ;
+
+ if (rs->data != 0)
+ /* 23-0 */
+ vty_out(vty, " %8s", mem_form_byte_count(rs->data).str) ;
+ } ;
+
+ vty_out(vty, "\n") ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Show memory summary, if any is available:
+ *
+ * If have "memory_regions", show that. Otherwise, if HAVE_MALLINFO, show
+ * that (for whatever it is worth.
+ *
+ * If we have memory_tracker, show that.
+ *
+ * Show what we know about the memory overhead, if anything.
+ *
+ * Returns true if has output any form of summary.
+ *
+ * Copies the memory_tracker or standard statistics to the given
+ * mem_tracker_data structure -- so all information is gathered under the lock,
+ * in one go.
+ */
+static bool
+show_memory_summary_info(vty vty, mem_tracker_data mtd)
+{
+ bool needsep = false ;
+
+ if (memory_regions)
+ {
+ /* Show what we know about all memory regions & get other statistics */
+ mem_show_region_info(vty, mtd) ;
+ needsep = true ;
+ }
+ else
+ {
+ /* If we have mallinfo() show that & get other statistics.
+ *
+ * In any event, get other statistics.
+ */
+#ifdef HAVE_MALLINFO
+ show_memory_mallinfo(vty, mtd) ;
+ needsep = true ;
+#else
+ mem_mt_get_stats(mtd) ; /* Get a snapshot of all data */
#endif /* HAVE_MALLINFO */
+ } ;
+
+ if (memory_tracker)
+ {
+ show_memory_tracker_summary(vty, mtd) ;
+ needsep = true ;
+
+ if (mem_alloc_known)
+ vty_out(vty,
+ "malloc() minimum block %zu with %zu redtape and allocation unit %zu\n",
+ mem_alloc_min_size, mem_alloc_redtape, mem_alloc_unit) ;
+ else
+ vty_out(vty, "malloc() parameters unknown: %s\n",
+ mem_alloc_unknown_message().str) ;
+ } ;
+
+ return needsep ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Show counts for for given type of memory.
+ *
+ * Shows basic allocation count or full memory_tracker statistics.
+ */
+static void
+show_memory_type_vty (vty vty, const char* name, mem_tracker_item mt, bool sep)
+{
+ if (sep)
+ {
+ /* 123456789012345678901234567890123456789012 */
+ vty_out (vty, "------------------------------:------Count") ;
+
+ if (memory_tracker)
+ {
+ /* 123456789012 */
+ vty_out(vty, "---Size--"
+ "--Peak-Count"
+ "-P-Size--"
+ "-malloc--"
+ "realloc--"
+ "---free--") ;
+ } ;
+
+ vty_out (vty, "\n") ;
+ } ;
+
+ vty_out (vty, "%-30s:%'11lu", name, mt->item_count) ;
+
+ if (memory_tracker)
+ {
+ vty_out(vty, "%9s", mem_form_byte_count(mt->total_size).str) ;
+ vty_out(vty, "%'12lu", mt->peak_item_count) ;
+ vty_out(vty, "%9s", mem_form_byte_count(mt->peak_total_size).str) ;
+ vty_out(vty, "%9s", mem_form_count(mt->malloc_count).str) ;
+ vty_out(vty, "%9s", mem_form_count(mt->realloc_count).str) ;
+ vty_out(vty, "%9s", mem_form_count(mt->free_count).str) ;
+
+ } ;
+
+ vty_out (vty, "\n") ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Show counts for all memory of types in the given memory_list.
+ *
+ * Shows everything for NULL memory_list, preceeded by summary.
+ *
+ * For basic statistics shows anything with non-zero count. For memory_tracker
+ * shows anything with non-zero count or peak count.
+ */
+static int
+show_memory_vty (vty vty, struct memory_list *ml)
+{
+ mem_tracker_data_t mtd[1] ;
+ mem_tracker_item mt ;
+
+ struct mlist* mll = NULL ;
+
+ bool needsep = false ;
+ bool notempty = false ;
+
+ /* ml == NULL => "show memory all" */
+ if (ml == NULL)
+ {
+ needsep = show_memory_summary_info(vty, mtd) ;
+
+ mll = mlists ;
+ }
+ else
+ mem_mt_get_stats(mtd, false) ; /* Get a snapshot of all data */
+
+ /* Work along the current list, when reach end, step to next or fin */
+ while (1)
+ {
+ if (ml == NULL) /* Step to next list */
+ {
+ if (mll != NULL)
+ ml = (mll++)->list ;
+ if (ml == NULL)
+ break ;
+ } ;
+
+ if (ml->index <= 0) /* Deal with separator/terminator */
+ {
+ needsep |= notempty ;
+ notempty = false ;
+
+ if (ml->index < 0)
+ ml = NULL ;
+ else
+ ++ml ;
+
+ continue ;
+ } ;
+
+ mt = &(mtd->typ[ml->index]) ;
+
+ if ((mt->item_count != 0) || (mt->peak_item_count != 0))
+ {
+ show_memory_type_vty(vty, ml->format, mt, needsep) ;
+ needsep = false ;
+ notempty = true ;
+ } ;
+ ++ml ;
+ } ;
+
+ show_memory_type_vty(vty, "Total", mtd->tot, needsep || notempty) ;
+
+ return 1 ;
+} ;
+
+/*==============================================================================
+ * The memory CLI commands
+ */
DEFUN_CALL (show_memory_summary,
show_memory_summary_cmd,
@@ -605,23 +3042,12 @@ 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 ;
+ mem_tracker_data_t mtd[1] ;
-# ifdef HAVE_MALLINFO
- show_memory_mallinfo (vty);
-# endif /* HAVE_MALLINFO */
+ show_memory_summary_info(vty, mtd) ;
- LOCK ;
- for (mtype = 1 ; mtype < MTYPE_MAX ; ++mtype)
- alloc += mstat.mt[mtype].alloc ;
- UNLOCK
- vty_out(vty, "%ld items allocated%s", alloc, VTY_NEWLINE) ;
- } ;
+ if (!memory_tracker)
+ vty_out(vty, "%'lu items allocated\n", mtd->tot->item_count) ;
return CMD_SUCCESS;
}
@@ -633,16 +3059,7 @@ DEFUN_CALL (show_memory_all,
"Memory statistics\n"
"All memory statistics\n")
{
- int needsep = 0;
-
-#ifdef HAVE_MALLINFO
- needsep |= show_memory_mallinfo (vty);
-#endif /* HAVE_MALLINFO */
-
- if (memory_tracker)
- needsep |= show_memory_tracker_summary(vty) ;
-
- show_memory_vty (vty, NULL, mlists, needsep);
+ show_memory_vty (vty, NULL) ;
return CMD_SUCCESS;
}
@@ -660,7 +3077,7 @@ DEFUN_CALL (show_memory_lib,
"Memory statistics\n"
"Library memory\n")
{
- show_memory_vty (vty, memory_list_lib, NULL, 0);
+ show_memory_vty (vty, memory_list_lib);
return CMD_SUCCESS;
}
@@ -671,7 +3088,7 @@ DEFUN_CALL (show_memory_zebra,
"Memory statistics\n"
"Zebra memory\n")
{
- show_memory_vty (vty, memory_list_zebra, NULL, 0);
+ show_memory_vty (vty, memory_list_zebra);
return CMD_SUCCESS;
}
@@ -682,7 +3099,7 @@ DEFUN_CALL (show_memory_rip,
"Memory statistics\n"
"RIP memory\n")
{
- show_memory_vty (vty, memory_list_rip, NULL, 0);
+ show_memory_vty (vty, memory_list_rip);
return CMD_SUCCESS;
}
@@ -693,7 +3110,7 @@ DEFUN_CALL (show_memory_ripng,
"Memory statistics\n"
"RIPng memory\n")
{
- show_memory_vty (vty, memory_list_ripng, NULL, 0);
+ show_memory_vty (vty, memory_list_ripng);
return CMD_SUCCESS;
}
@@ -704,7 +3121,7 @@ DEFUN_CALL (show_memory_bgp,
"Memory statistics\n"
"BGP memory\n")
{
- show_memory_vty (vty, memory_list_bgp, NULL, 0);
+ show_memory_vty (vty, memory_list_bgp);
return CMD_SUCCESS;
}
@@ -715,7 +3132,7 @@ DEFUN_CALL (show_memory_ospf,
"Memory statistics\n"
"OSPF memory\n")
{
- show_memory_vty (vty, memory_list_ospf, NULL, 0);
+ show_memory_vty (vty, memory_list_ospf);
return CMD_SUCCESS;
}
@@ -726,7 +3143,7 @@ DEFUN_CALL (show_memory_ospf6,
"Memory statistics\n"
"OSPF6 memory\n")
{
- show_memory_vty (vty, memory_list_ospf6, NULL, 0);
+ show_memory_vty (vty, memory_list_ospf6);
return CMD_SUCCESS;
}
@@ -737,24 +3154,60 @@ DEFUN_CALL (show_memory_isis,
"Memory statistics\n"
"ISIS memory\n")
{
- show_memory_vty (vty, memory_list_isis, NULL, 0);
+ show_memory_vty (vty, memory_list_isis);
return CMD_SUCCESS;
}
-/* Second state initialisation if qpthreaded */
-void
+/*==============================================================================
+ * Initialisation and close down
+ */
+
+static void bd_form_test(void) ;
+
+/*------------------------------------------------------------------------------
+ * First stage initialisation
+ */
+extern void
+memory_start(void)
+{
+#ifdef MCHECK
+ #include <mcheck.h>
+ mcheck(NULL) ; /* start now !! */
+#endif
+
+ memset(&mstats, 0, sizeof(mem_stats_t)) ;
+ memset(&mem_mt_data, 0, sizeof(mem_tracker_data_t)) ;
+ memset(mlog_stat, 0, sizeof(mlog_stat)) ;
+
+ mem_pagesize = qlib_pagesize ;
+
+ mem_alloc_discover() ;
+
+ if (false)
+ bd_form_test() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Second stage initialisation if qpthreaded
+ */
+extern void
memory_init_r (void)
{
qpt_mutex_init(memory_mutex, qpt_mutex_quagga);
}
-/* Finished with module */
-void
+/*------------------------------------------------------------------------------
+ * Finished with module
+ */
+extern void
memory_finish (void)
{
qpt_mutex_destroy(memory_mutex, 0);
}
+/*------------------------------------------------------------------------------
+ * Set up of memory reporting commands
+ */
void
memory_init (void)
{
@@ -793,78 +3246,377 @@ memory_init (void)
install_element (ENABLE_NODE, &show_memory_isis_cmd);
}
-/* Stats querying from users */
-/* Return a pointer to a human friendly string describing
- * the byte count passed in. E.g:
- * "0 bytes", "2048 bytes", "110kB", "500MiB", "11GiB", etc.
- * Up to 4 significant figures will be given.
- * The pointer returned may be NULL (indicating an error)
- * or point to the given buffer, or point to static storage.
+#undef UNLOCK
+#undef LOCK
+
+/*==============================================================================
+ * Support for mmap of anonymous memory regions
+ *
+ * These wrappers allow for the (perhaps obscure, now) absence of MAP_ANON/
+ * MAP_ANONYMOUS. They also check for failure returns, and crash with
+ * suitable messages.
+ *
+ * In the (extremely) unlikely event of there being no mmap, uses malloc().
+ *
+ * Note that the memory allocated is *not* included in the memory statistics.
+ */
+
+/*------------------------------------------------------------------------------
+ * mmap allocation of memory.
+ *
+ * Rounds allocation up to multiple of page size (unless using malloc()) and
+ * zeroises.
*/
-const char *
-mtype_memstr (char *buf, size_t len, unsigned long bytes)
+extern void*
+mem_mmap(size_t size)
{
- unsigned int t, g, m, k;
+ void* p ;
+ int err = 0 ;
- /* easy cases */
- if (!bytes)
- return "0 bytes";
- if (bytes == 1)
- return "1 byte";
+#if HAVE_MMAP
- if (sizeof (unsigned long) >= 8)
- /* Hacked to make it not warn on ILP32 machines
- * Shift will always be 40 at runtime. See below too */
- t = bytes >> (sizeof (unsigned long) >= 8 ? 40 : 0);
- else
- t = 0;
- g = bytes >> 30;
- m = bytes >> 20;
- k = bytes >> 10;
+ size = ((size + mem_pagesize - 1) / mem_pagesize) * mem_pagesize ;
- if (t > 10)
+# if (defined(MAP_ANON) || defined(MAP_ANONYMOUS)) && 0
+ p = mmap(NULL, size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | (MAP_ANONYMOUS |0) | (MAP_ANON |0), -1, 0) ;
+ err = errno ;
+# else
+ int fd ;
+
+ fd = open("/dev/zero", O_RDWR) ;
+
+ if (fd >= 0)
{
- /* The shift will always be 39 at runtime.
- * Just hacked to make it not warn on 'smaller' machines.
- * Static compiler analysis should mean no extra code
- */
- if (bytes & (1UL << (sizeof (unsigned long) >= 8 ? 39 : 0)))
- t++;
- snprintf (buf, len, "%4d TiB", t);
+ p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0) ;
+ err = errno ;
}
- else if (g > 10)
+ else
{
- if (bytes & (1 << 29))
- g++;
- snprintf (buf, len, "%d GiB", g);
- }
- else if (m > 10)
+ p = MAP_FAILED ;
+ err = errno ;
+ } ;
+
+ close(fd) ;
+
+ if (p == MAP_FAILED)
{
- if (bytes & (1 << 19))
- m++;
- snprintf (buf, len, "%d MiB", m);
- }
- else if (k > 10)
+ zlog_err ("%s : failed to mmap %zd bytes: %s\n",
+ __func__, size, errtoa(err, 0).str) ;
+ zabort_abort();
+ } ;
+# endif
+
+#else
+ p = malloc(size) ;
+ err = errno ;
+
+ if (p == NULL)
{
- if (bytes & (1 << 9))
- k++;
- snprintf (buf, len, "%d KiB", k);
- }
+ zlog_err ("%s : failed to malloc %zd bytes: %s\n",
+ __func__, size, errtoa(err, 0).str) ;
+ zabort_abort();
+ } ;
+#endif
+
+ memset(p, 0, size) ;
+
+ return p ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * mmap allocation of memory.
+ *
+ * Rounds allocation up to multiple of page sizes and zeroises.
+ */
+extern void
+mem_munmap(void* p, size_t size)
+{
+#if HAVE_MMAP
+ int rc ;
+
+ size = ((size + mem_pagesize - 1) / mem_pagesize) * mem_pagesize ;
+ rc = munmap(p, size) ;
+
+ if (rc < 0)
+ {
+ zlog_err ("%s : failed to munmap %zd bytes @ %p: %s\n",
+ __func__, size, p, errtoa(errno, 0).str) ;
+ zabort_abort();
+ } ;
+#else
+ free(p) ;
+#endif
+} ;
+
+/*==============================================================================
+ * Testing the number formatting
+ */
+static ulong t_val(double v, ulong s) ;
+static void b_test(ulong v) ;
+static void d_test(ulong v) ;
+static void c_val(char* e, ulong u, uint d, const char* t) ;
+
+static void
+bd_form_test(void)
+{
+ ulong s ;
+ int g = 0 ;
+
+ fprintf(stderr, "Group %d\n", g) ;
+ b_test(0 ) ;
+ b_test(1 ) ;
+ b_test(9 ) ;
+ b_test(10 ) ;
+ b_test(99 ) ;
+ b_test(100 ) ;
+ b_test(999 ) ;
+ b_test(1023 ) ;
+ b_test(1024 ) ;
+ b_test(1025 ) ;
+ b_test(9999 ) ;
+ b_test(10000) ;
+ b_test(10007) ;
+
+ s = 1 ;
+ for (g = 1 ; g <= 5 ; ++g)
+ {
+ fprintf(stderr, "Group %d\n", g) ;
+ s *= 1024ul ;
+ b_test(t_val(.31414, s) ) ;
+ b_test(t_val(.31415, s) ) ;
+ b_test(t_val(.31416, s) ) ;
+ b_test(t_val(.99995, s) - 1) ;
+ b_test(t_val(.99995, s) ) ;
+ b_test(t_val(.99995, s) + 1) ;
+ b_test(t_val(1.2345, s) ) ;
+ b_test(t_val(9.9995, s) - 1) ;
+ b_test(t_val(9.9995, s) ) ;
+ b_test(t_val(9.9995, s) + 1) ;
+ b_test(t_val(3.1415, s) - 1) ;
+ b_test(t_val(3.1415, s) ) ;
+ b_test(t_val(3.1416, s) ) ;
+ b_test(t_val(99.995, s) - 1) ;
+ b_test(t_val(99.995, s) ) ;
+ b_test(t_val(99.995, s) + 1) ;
+ b_test(t_val(314.15, s) - 1) ;
+ b_test(t_val(314.15, s) ) ;
+ b_test(t_val(314.16, s) ) ;
+ b_test(t_val(999.95, s) - 1) ;
+ b_test(t_val(999.95, s) ) ;
+ b_test(t_val(999.95, s) + 1) ;
+ b_test(t_val(1023.5, s) - 1) ;
+ b_test(t_val(1023.5, s) ) ;
+ b_test(t_val(1023.5, s) + 1) ;
+ b_test(t_val(3141.5, s) - 1) ;
+ b_test(t_val(3141.5, s) ) ;
+ b_test(t_val(3141.6, s) ) ;
+ b_test(t_val(9999.5, s) - 1) ;
+ b_test(t_val(9999.5, s) ) ;
+ b_test(t_val(9999.5, s) + 1) ;
+ } ;
+
+ g = 0 ;
+ fprintf(stderr, "Group %d\n", g) ;
+ d_test(0 ) ;
+ d_test(1 ) ;
+ d_test(9 ) ;
+ d_test(10 ) ;
+ d_test(11 ) ;
+ d_test(99 ) ;
+ d_test(100 ) ;
+ d_test(999 ) ;
+ d_test(1000 ) ;
+ d_test(1001 ) ;
+ d_test(9999 ) ;
+ d_test(10000) ;
+ d_test(10001) ;
+
+ s = 1 ;
+ for (g = 1 ; g <= 4 ; ++g)
+ {
+ fprintf(stderr, "Group %d\n", g) ;
+ s *= 1000ul ;
+ d_test(t_val(1.2345, s) - 1) ;
+ d_test(t_val(1.2345, s) ) ;
+ d_test(t_val(9.9995, s) - 1) ;
+ d_test(t_val(9.9995, s) ) ;
+ d_test(t_val(21.235, s) - 1) ;
+ d_test(t_val(21.235, s) ) ;
+ d_test(t_val(99.995, s) - 1) ;
+ d_test(t_val(99.995, s) ) ;
+ d_test(t_val(312.35, s) - 1) ;
+ d_test(t_val(312.35, s) ) ;
+ d_test(t_val(999.95, s) - 1) ;
+ d_test(t_val(999.95, s) ) ;
+ d_test(t_val(4321.5, s) - 1) ;
+ d_test(t_val(4321.5, s) ) ;
+ d_test(t_val(9999.5, s) - 1) ;
+ d_test(t_val(9999.5, s) ) ;
+ } ;
+
+ g = 5 ;
+ fprintf(stderr, "Group %d\n", g) ;
+ s *= 1000ul ;
+ d_test(t_val(1.2345, s) - 1) ;
+ d_test(t_val(1.2345, s) ) ;
+ d_test(t_val(9.9995, s) - 1) ;
+ d_test(t_val(9.9995, s) ) ;
+ d_test(t_val(21.2345, s) - 1) ;
+ d_test(t_val(21.2345, s) ) ;
+ d_test(t_val(99.9995, s) - 1) ;
+ d_test(t_val(99.9995, s) ) ;
+ d_test(t_val(312.6785, s) - 1) ;
+ d_test(t_val(312.6785, s) ) ;
+ d_test(t_val(999.9995, s) - 1) ;
+ d_test(t_val(999.9995, s) ) ;
+ d_test(t_val(4321.5675, s) - 1) ;
+ d_test(t_val(4321.5675, s) ) ;
+ d_test(t_val(9999.9995, s) - 1) ;
+ d_test(t_val(9999.9995, s) ) ;
+} ;
+
+static ulong
+t_val(double v, ulong s)
+{
+ return v * s ;
+} ;
+
+static void
+b_test(ulong v)
+{
+ num_str_t g ;
+ char e[50] ;
+ ulong u ;
+ uint i ;
+ uint d ;
+
+ d = 0 ;
+ u = v ;
+ i = 0 ;
+ if (u >= 1024)
+ {
+ uint n ;
+
+ while ((u >= 1024) && (i < scale_max))
+ {
+ u >>= 10 ;
+ i += 1 ;
+ }
+ n = i * 10 ;
+
+ u = v ;
+ while (u < (1000ul << n))
+ {
+ u *= 10 ;
+ d += 1 ;
+ } ;
+
+ u = ((u >> (n - 1)) + 1) >> 1 ;
+
+ if (d == 0)
+ {
+ if ((u == 1024) && (i < scale_max))
+ {
+ u = 1000 ;
+ i += 1 ;
+ d = 3 ;
+ } ;
+ }
+ else
+ {
+ assert(u <= 10000) ;
+ if (u == 10000)
+ {
+ u = 1000 ;
+ d -= 1 ;
+ } ;
+ } ;
+ } ;
+
+ c_val(e, u, d, scale_b_tags[i]) ;
+
+ g = mem_form_byte_count(v) ;
+
+ fprintf(stderr, "%16lx %'20lu -> '%7s'", v, v, g.str) ;
+
+ if (strcmp(e, g.str) == 0)
+ fputs(" -- OK\n", stderr) ;
else
- snprintf (buf, len, "%ld bytes", bytes);
+ fprintf(stderr, " *** expect: '%7s'\n", e) ;
+} ;
- return buf;
-}
+static void
+d_test(ulong v)
+{
+ num_str_t g ;
+ char e[50] ;
+ ulong u ;
+ uint i ;
+ uint n ;
+ uint d ;
+
+ d = 0 ;
+ i = 0 ;
+ u = v ;
+ n = (v == 0) ? 0 : 1 ;
+
+ while (u >= 10)
+ {
+ u /= 10 ;
+ n += 1 ;
+ } ;
-unsigned long
-mtype_stats_alloc (enum MTYPE type)
+ u = v ;
+
+ if (n > 4)
+ {
+ if (n > ((scale_max * 3) + 3))
+ {
+ u = (u + q10[scale_max * 3]) / p10[scale_max * 3] ;
+ i = scale_max ;
+ }
+ else
+ {
+ u = (u + q10[n - 4]) / p10[n - 4] ;
+
+ if (u > 9999)
+ {
+ u /= 10 ;
+ n += 1 ;
+ } ;
+
+ i = (n - 2) / 3 ;
+ d = (i * 3) + 4 - n ;
+ } ;
+ } ;
+
+ c_val(e, u, d, scale_d_tags[i]) ;
+
+ g = mem_form_count(v) ;
+
+ fprintf(stderr, "%'20lu -> '%7s'", v, g.str) ;
+
+ if (strcmp(e, g.str) == 0)
+ fputs(" -- OK\n", stderr) ;
+ else
+ fprintf(stderr, " *** expect: '%7s'\n", e) ;
+} ;
+
+static void
+c_val(char* e, ulong u, uint d, const char* t)
{
- unsigned long result;
- LOCK
- result = mstat.mt[type].alloc;
- UNLOCK
- return result;
-}
+ if (d != 0)
+ sprintf(e, "%lu.%0*lu%s", u / p10[d], (int)d, u % p10[d], t) ;
+ else
+ {
+ if (u < 1000)
+ sprintf(e, "%lu%s", u, t) ;
+ else if (u < 1000000)
+ sprintf(e, "%lu,%03lu%s", u / 1000, u % 1000, t) ;
+ else
+ sprintf(e, "%lu,%03lu,%03lu%s", u / 1000000, (u / 1000) % 1000,
+ u % 1000, t) ;
+ } ;
+} ;
-#undef UNLOCK
-#undef LOCK
diff --git a/lib/memory.h b/lib/memory.h
index 265a8226..5e5af698 100644
--- a/lib/memory.h
+++ b/lib/memory.h
@@ -50,24 +50,33 @@ extern struct mlist mlists[]; /* all classes of memory */
typedef enum MTYPE mtype_t ;
/*------------------------------------------------------------------------------
+ * Basic counting -- count of allocated objects of each type of memory
+ *
+ */
+struct mem_stats /* wrap in struct so can copy trivially */
+{
+ ulong alloc[MTYPE_MAX] ;
+} ;
+
+typedef struct mem_stats mem_stats_t ;
+
+/*------------------------------------------------------------------------------
* Option for logging memory operations.
*/
-#ifdef MEMORY_LOG
-#define XMALLOC(mtype, size) \
- mtype_zmalloc (__FILE__, __LINE__, (mtype), (size))
-#define XCALLOC(mtype, size) \
- mtype_zcalloc (__FILE__, __LINE__, (mtype), (size))
-#define XREALLOC(mtype, ptr, size) \
- mtype_zrealloc (__FILE__, __LINE__, (mtype), (ptr), (size))
-#define XFREE(mtype, ptr) \
- do { \
- mtype_zfree (__FILE__, __LINE__, (mtype), (ptr)); \
- ptr = NULL; } \
- while (0)
-#define XSTRDUP(mtype, str) \
- mtype_zstrdup (__FILE__, __LINE__, (mtype), (str))
+#ifdef MEMORY_LOGGER /* Takes precedence over MEMORY_TRACKER */
+
+#undef MEMORY_LOGGER
+#define MEMORY_LOGGER 1
+
+#undef MEMORY_TRACKER
+#define MEMORY_TRACKER 0
+
#else
+#define MEMORY_LOGGER 0
+
+#endif
+
/*------------------------------------------------------------------------------
* Sort out MEMORY_TRACKER -- option to keep track of memory allocation.
*
@@ -82,7 +91,6 @@ typedef enum MTYPE mtype_t ;
* * 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
@@ -99,68 +107,84 @@ typedef enum MTYPE mtype_t ;
# endif
#endif
-enum { memory_tracker = MEMORY_TRACKER } ;
+/*------------------------------------------------------------------------------
+ * Argument macros to make everything look much the same
+ */
-#if MEMORY_TRACKER
-#define MEMORY_TRACKER_NAME_ARG name
-#define MEMORY_TRACKER_NAME , const char* MEMORY_TRACKER_NAME_ARG
-#define MEMORY_TRACKER_FUNC , __func__
+#if MEMORY_LOGGER
+#define MEMORY_EXTRA_DATA , __FILE__, __LINE__
+#define MEMORY_EXTRA_ARGS , const char* file, int line
+#define MEMORY_LOGGING_ARGS file, line
+#elif MEMORY_TRACKER
+#define MEMORY_EXTRA_DATA , __func__
+#define MEMORY_EXTRA_ARGS , const char* func
+#define MEMORY_TRACKER_ARG func
#else
-#define MEMORY_TRACKER_NAME_ARG "*dummy*"
-#define MEMORY_TRACKER_NAME
-#define MEMORY_TRACKER_FUNC
+#define MEMORY_EXTRA_DATA
+#define MEMORY_EXTRA_ARGS
#endif
+#ifndef MEMORY_LOGGING_ARGS
+#define MEMORY_LOGGING_ARGS "*dummy*", -1
+#endif
+#ifndef MEMORY_TRACKER_ARG
+#define MEMORY_TRACKER_ARG "*dummy*"
+#endif
+
+enum
+{
+ memory_logger = MEMORY_LOGGER,
+ memory_tracker = MEMORY_TRACKER,
+} ;
+
/*------------------------------------------------------------------------------
* The macros used for all Quagga dynamic memory.
*/
#define XMALLOC(mtype, size) zmalloc ((mtype), (size) \
- MEMORY_TRACKER_FUNC)
+ MEMORY_EXTRA_DATA)
#define XCALLOC(mtype, size) zcalloc ((mtype), (size) \
- MEMORY_TRACKER_FUNC)
+ MEMORY_EXTRA_DATA)
#define XREALLOC(mtype, ptr, size) zrealloc ((mtype), (ptr), (size) \
- MEMORY_TRACKER_FUNC)
-#define XFREE(mtype, ptr) do { zfree ((mtype), (ptr)); \
+ MEMORY_EXTRA_DATA)
+#define XFREE(mtype, ptr) do { zfree ((mtype), (ptr) \
+ MEMORY_EXTRA_DATA); \
ptr = NULL; } while (0)
#define XSTRDUP(mtype, str) zstrdup ((mtype), (str) \
- MEMORY_TRACKER_FUNC)
-
-#endif /* MEMORY_LOG */
+ MEMORY_EXTRA_DATA)
#define SIZE(t,n) (sizeof(t) * (n))
-/* Prototypes of memory function. */
-extern void *zmalloc (enum MTYPE type, size_t size MEMORY_TRACKER_NAME);
-extern void *zcalloc (enum MTYPE type, size_t size MEMORY_TRACKER_NAME);
-extern void *zrealloc (enum MTYPE type, void *ptr, size_t size
- MEMORY_TRACKER_NAME);
-extern void zfree (enum MTYPE type, void *ptr);
-extern char *zstrdup (enum MTYPE type, const char *str MEMORY_TRACKER_NAME);
-
-extern void *mtype_zmalloc (const char *file, int line, enum MTYPE type,
- size_t size);
-
-extern void *mtype_zcalloc (const char *file, int line, enum MTYPE type,
- size_t size);
-
-extern void *mtype_zrealloc (const char *file, int line, enum MTYPE type,
- void *ptr, size_t size);
+/*------------------------------------------------------------------------------
+ * Prototypes of memory functions
+ */
+extern void *zmalloc (mtype_t mtype, size_t size MEMORY_EXTRA_ARGS) ;
+extern void *zcalloc (mtype_t mtype, size_t size MEMORY_EXTRA_ARGS) ;
+extern void *zrealloc (mtype_t mtype, void *ptr, size_t size
+ MEMORY_EXTRA_ARGS) ;
+extern void zfree (mtype_t mtype, void *ptr MEMORY_EXTRA_ARGS) ;
+extern char *zstrdup (mtype_t mtype, const char *str MEMORY_EXTRA_ARGS) ;
-extern void mtype_zfree (const char *file, int line, enum MTYPE type,
- void *ptr);
+extern void* mem_mmap(size_t size) ;
+extern void mem_munmap(void* p, size_t size) ;
-extern char *mtype_zstrdup (const char *file, int line, enum MTYPE type,
- const char *str);
+extern void memory_start(void) ;
extern void memory_init (void);
extern void memory_init_r (void);
extern void memory_finish (void);
extern void log_memstats_stderr (const char *);
-/* return number of allocations outstanding for the type */
-extern unsigned long mtype_stats_alloc (enum MTYPE);
+/* Return number of allocations outstanding for all memory types */
+extern void mem_get_stats(mem_stats_t* mst) ;
-/* Human friendly string for given byte count */
+/* Return number of allocations outstanding for the given memory type */
+extern ulong mem_get_alloc(mem_stats_t* mst, mtype_t mtype) ;
+
+/* Return number of allocations outstanding for the given memory type */
+extern ulong mtype_stats_alloc(mtype_t mtype) ;
+
+/* Human friendly string for given byte count */
#define MTYPE_MEMSTR_LEN 20
-extern const char *mtype_memstr (char *, size_t, unsigned long);
+extern const char *mtype_memstr (char *, size_t, ulong);
+
#endif /* _ZEBRA_MEMORY_H */
diff --git a/lib/memtypes.awk b/lib/memtypes.awk
index a8004977..ac111b69 100644
--- a/lib/memtypes.awk
+++ b/lib/memtypes.awk
@@ -65,4 +65,17 @@ END {
printf (mlistformat "\n", mlists[i]);
}
printf (footer);
+
+ printf ("\n") ;
+ printf ("#ifdef MEM_MTYPE_MAP_REQUIRED\n") ;
+ printf ("static const char* const mem_mtype_map[] = {\n") ;
+
+ printf (" [%-30d] = \"*MTYPE = 0*\",\n", 0) ;
+
+ for (i = 0; i < tcount; i++) {
+ printf (" [%-30s] = \"%s\",\n", mtype[i], mtype[i]);
+ } ;
+
+ printf ("} ;\n") ;
+ printf ("#endif /* MEM_MTYPE_MAP_REQUIRED */\n") ;
}
diff --git a/lib/misc.h b/lib/misc.h
index f5f9d9f6..8a0efb9a 100644
--- a/lib/misc.h
+++ b/lib/misc.h
@@ -112,6 +112,9 @@ typedef int slen ;
typedef unsigned long ulong ;
+typedef long long llong ;
+typedef unsigned long long ullong ;
+
/* cvp == const void* -- ptr to constant void
* cvp* == void * const* -- ptr to ptr to constant void
* const cvp* == void const* const* -- ptr to constant ptr to constant void
@@ -138,7 +141,7 @@ typedef const void* cvp ;
/* If QDEBUG is defined, make QDEBUG_NAME and set QDEBUG
*
- * Numeric value for QDEBUG: undefined => 0
+ * Numeric value for QDEBUG: undefined => 0
* defined, blank => 1
* defined, 0 => 0
* defined, other => other
diff --git a/lib/pthread_safe.c b/lib/pthread_safe.c
index 4a698216..336f686e 100644
--- a/lib/pthread_safe.c
+++ b/lib/pthread_safe.c
@@ -47,16 +47,25 @@
#include "qfstring.h"
#include "errno_names.h"
-/* prototypes */
-static void destructor(void* data);
-static char * thread_buff(void);
-
+/*==============================================================================
+ * Initialisation, close down and local variables.
+ *
+ * Note that the "thread_safe" mutex is recursive, so one thread safe function
+ * can call another, if required.
+ */
static pthread_key_t tsd_key;
static const int buff_size = 1024;
+static qpt_mutex thread_safe = NULL ;
+
static const char ellipsis[] = "..." ;
-/* Module initialization, before any threads have been created */
+static void destructor(void* data);
+static char * thread_buff(void);
+
+/*------------------------------------------------------------------------------
+ * Module initialization, before any threads have been created
+ */
void
safe_init_r(void)
{
@@ -66,50 +75,36 @@ safe_init_r(void)
status = pthread_key_create(&tsd_key, destructor);
if (status != 0)
zabort("Can't create thread specific data key");
- }
+
+ qassert(thread_safe == NULL) ;
+
+ thread_safe = qpt_mutex_init_new(NULL, qpt_mutex_recursive) ;
+ } ;
}
-/* Clean up */
+/*------------------------------------------------------------------------------
+ * Clean up
+ */
void
safe_finish(void)
{
if (qpthreads_enabled)
- pthread_key_delete(tsd_key);
-}
+ {
+ pthread_key_delete(tsd_key) ;
-/* called when thread terminates, clean up */
+ thread_safe = qpt_mutex_destroy(thread_safe, free_it) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * called when thread terminates, clean up
+ */
static void
destructor(void* data)
{
XFREE(MTYPE_TSD, data);
}
-/* Thread safe version of strerror. Never returns NULL.
- * Contents of result remains intact until another call of
- * a safe_ function.
- */
-const char *
-safe_strerror(int errnum)
-{
- static const char * unknown = "Unknown error";
- if (qpthreads_enabled)
- {
- char * buff = thread_buff();
- int ret = strerror_r(errnum, buff, buff_size);
-
- return (ret >= 0)
- ? buff
- : unknown;
- }
- else
- {
- const char *s = strerror(errnum);
- return (s != NULL)
- ? s
- : unknown;
- }
-}
-
/*==============================================================================
* Alternative error number handling.
*
@@ -311,14 +306,14 @@ errtox(strerror_t* st, int err, uint len, uint want)
if (ql != 0)
qfs_append(qfs, q) ;
-
- /* '\0' terminate -- if has overflowed, replace last few characters
- * by "..." -- noting that sizeof("...") includes the '\0'.
- */
- if (qfs_term(qfs) != 0)
- qfs_term_string(qfs, ellipsis, sizeof(ellipsis)) ;
} ;
+ /* '\0' terminate -- if has overflowed, replace last few characters
+ * by "..." -- noting that sizeof("...") includes the '\0'.
+ */
+ if (qfs_term(qfs) != 0)
+ qfs_term_string(qfs, ellipsis, sizeof(ellipsis)) ;
+
/* Put back errno */
errno = errno_saved ;
} ;
@@ -490,9 +485,83 @@ eaitox(strerror_t* st, int eai, int err, uint len, uint want)
errno = errno_saved ;
} ;
-/*============================================================================*/
+/*==============================================================================
+ * Miscellaneous thread-safe functions
+ */
+
+/*------------------------------------------------------------------------------
+ * getenv_r -- fetch environment variable into the given buffer.
+ *
+ * If buffer is not long enough, fetches as much as can and '\0' terminates.
+ *
+ * Returns: -1 => not found -- buffer set empty
+ * >= 0 == length of environment variable
+ *
+ * NB: this is NOT signal safe. If need value of environment variable in
+ * a signal action -- make OTHER arrangements !!
+ */
+extern int
+getenv_r(const char* name, char* buf, int buf_len)
+{
+ char* val ;
+ int len ;
+ int cl ;
+
+ qpt_mutex_lock(thread_safe) ;
-/* Thread safe version of inet_ntoa. Never returns NULL.
+ val = getenv(name) ;
+ if (val == NULL)
+ {
+ len = -1 ;
+ cl = 0 ;
+ }
+ else
+ {
+ len = strlen(val) ;
+ cl = (len < buf_len) ? len : buf_len - 1 ;
+ } ;
+
+ if (buf_len > 0)
+ {
+ if (cl > 0)
+ memcpy(buf, val, cl) ;
+ buf[cl] = '\0' ;
+ } ;
+
+ qpt_mutex_unlock(thread_safe) ;
+
+ return len ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Thread safe version of strerror. Never returns NULL.
+ * Contents of result remains intact until another call of
+ * a safe_ function.
+ */
+const char *
+safe_strerror(int errnum)
+{
+ static const char * unknown = "Unknown error";
+ if (qpthreads_enabled)
+ {
+ char * buff = thread_buff();
+ int ret = strerror_r(errnum, buff, buff_size);
+
+ return (ret >= 0)
+ ? buff
+ : unknown;
+ }
+ else
+ {
+ const char *s = strerror(errnum);
+ return (s != NULL)
+ ? s
+ : unknown;
+ }
+}
+
+/*------------------------------------------------------------------------------
+ * Thread safe version of inet_ntoa. Never returns NULL.
* Contents of result remains intact until another call of
* a safe_ function.
*/
@@ -511,7 +580,8 @@ safe_inet_ntoa (struct in_addr in)
: unknown;
}
-/* Return the thread's buffer, create it if necessary.
+/*------------------------------------------------------------------------------
+ * Return the thread's buffer, create it if necessary.
* (pthread Thread Specific Data)
*/
static char *
diff --git a/lib/pthread_safe.h b/lib/pthread_safe.h
index b7faba1a..f3974081 100644
--- a/lib/pthread_safe.h
+++ b/lib/pthread_safe.h
@@ -27,13 +27,11 @@
typedef struct strerror strerror_t ;
struct strerror
{
- char str[121] ; /* cannot imagine anything as big */
+ char str[120] ; /* cannot imagine anything as big */
} ;
extern void safe_init_r(void);
extern void safe_finish(void);
-extern const char * safe_strerror(int errnum);
-extern const char * safe_inet_ntoa (struct in_addr in);
extern strerror_t errtoa(int err, uint len) ;
extern strerror_t errtoname(int err, uint len) ;
@@ -43,4 +41,8 @@ extern strerror_t eaitoa(int eai, int err, uint len) ;
extern strerror_t eaitoname(int eai, int err, uint len) ;
extern strerror_t eaitostr(int eai, int err, uint len) ;
+extern int getenv_r(const char* name, char* buf, int buf_len) ;
+extern const char * safe_strerror(int errnum);
+extern const char * safe_inet_ntoa (struct in_addr in);
+
#endif /* PTHREAD_SAFE_H_ */
diff --git a/lib/qfstring.c b/lib/qfstring.c
index 2d5f69cc..9f2e2e9c 100644
--- a/lib/qfstring.c
+++ b/lib/qfstring.c
@@ -625,6 +625,7 @@ qfs_number(qf_str qfs, uintmax_t val, int sign, enum pf_flags flags,
while (t--)
*cp++ = *cq++ ;
*cp++ = comma ;
+ t = interval ;
} ;
assert(len == (e - p)) ;
@@ -1043,7 +1044,10 @@ qfs_arg_string(qf_str qfs, const char* src, enum pf_flags flags,
flags &= ~pf_precision ;
} ;
- len = (src != NULL) ? strlen(src) : 0 ;
+ len = 0 ;
+ if (src != NULL)
+ while (*(src + len) != '\0') ++len ;
+
if (((precision > 0) || (flags & pf_precision)) && (len > precision))
len = precision ;
diff --git a/lib/qfstring.h b/lib/qfstring.h
index f6eb1807..03204c3d 100644
--- a/lib/qfstring.h
+++ b/lib/qfstring.h
@@ -209,4 +209,90 @@ qfs_strlen(const char* str)
return s - str ;
}
-#endif /* _ZEBRA_QSTRING_H */
+/*==============================================================================
+ * Fixed Size String Buffers
+ *
+ * This supports the common case of a function whose task is to construct a
+ * (small) string of known maximum length, which will promptly be output
+ * or something similar.
+ *
+ * This scheme removes the need for the caller to construct a small buffer
+ * and pass it to the string constructor. The "magic" is to make the callee
+ * return a struct containing the result. So the callee is, for example:
+ *
+ * foo_t make_foo(...) { ... } ;
+ *
+ * where foo_t is a struct, with a "str" element large enough for all known
+ * foo. So the caller can, for example:
+ *
+ * printf("...%s...", ..., make_foo(...).str, ...) ;
+ *
+ * All the fiddling around with buffers and buffer sizes is hidden from the
+ * caller. And, since the buffer is implicitly on the stack, this is thread
+ * safe (and async-signal-safe, provided make_foo() is).
+ *
+ * The macro: qfb_t(name, len) declares a fixed length buffer type. So:
+ *
+ * QFB_T(foo, 79) ;
+ *
+ * declares:
+ *
+ * typedef struct { char str[79 + 1] ; } foo_t ;
+ *
+ * NB: the length given *excludes* the terminating '\0' ;
+ *
+ * NB: the type declared has the "_t" added *automatically*.
+ *
+ * Having declared a suitable type, function(s) can be declared to return
+ * a string in a value of that type.
+ *
+ * A string generating function can use the buffer directly, for example:
+ *
+ * foo_t make_foo(...)
+ * {
+ * foo_t foo ;
+ *
+ * ... foo.str is the address of the string buffer
+ * ... sizeof(foo.str) is its length *including* the '\0'
+ *
+ * return foo ;
+ * } ;
+ *
+ * The qfstring facilities may be used to construct the string, and to
+ * facilitate that, the macro: qfb_qfs(buf, qfs) declares the buffer and a
+ * qf_str_t and initialises same, thus:
+ *
+ * QFB_QFS(foo, foo_qfs) ;
+ *
+ * declares:
+ *
+ * foo_t foo ;
+ * qf_str_t foo_qfs = { ...initialised for empty foo... } ;
+ *
+ * So the string generator can use foo_qfs and qfstring facilities to fill in
+ * the string in foo, and then return foo (having terminated it) as above.
+ *
+ * So... with two macros we reduce the amount of fiddling about required to
+ * do something reasonably simple.
+ *
+ * NB: it is quite possible that the compiler will allocate two buffers, one
+ * in the caller's stack frame and one in the callee's, and returning the
+ * value will involve copying from one to the other.
+ */
+#define QFB_T(name, len) \
+ typedef struct { char str[((len) | 7) + 1] ; } name##_t
+
+#define QFB_QFS(qfb, qfs) \
+ qfb##_t qfb ; \
+ qf_str_t qfs = { { .str = qfb.str, \
+ .ptr = qfb.str, \
+ .end = qfb.str + sizeof(qfb.str), \
+ .offset = 0, \
+ .overflow = 0 } }
+
+/* And, finally, a "standard" qfb for general use: qfb_gen_t ! */
+
+enum { qfb_gen_len = 200 } ; /* More than enough for most purposes ! */
+QFB_T(qfb_gen, qfb_gen_len) ;
+
+#endif /* _ZEBRA_QFSTRING_H */
diff --git a/lib/qiovec.c b/lib/qiovec.c
index cc24503e..9492731a 100644
--- a/lib/qiovec.c
+++ b/lib/qiovec.c
@@ -23,23 +23,24 @@
#include <errno.h>
#include "qdebug_nb.h"
+#include "qlib_init.h"
#include "memory.h"
#include "miyagi.h"
#include "qiovec.h"
/*==============================================================================
- * IOV_MAX is defined by POSIX to appear in <limits.h>.
- *
- * This does not appear unless "zconfig.h" is included, first...
- * ...but although it compiles OK, Eclipse cannot see where the value has
- * come from.
+ * We collect the _SC_IOV_MAX very early in the morning, but in any case
+ * only use up to 100 entries -- in order not to let a single operation hog
+ * the cpu.
*/
-#ifdef IOV_MAX /* Stops Eclipse whinging */
-#if IOV_MAX < 64 /* check for a reasonable value */
-#error IOV_MAX < 64
-#endif
-#endif
+static int qiov_max ;
+
+extern void
+qiovec_start_up(void)
+{
+ qiov_max = (qlib_iov_max < 100) ? qlib_iov_max : 100 ;
+}
/*==============================================================================
* Initialise, allocate and reset qiovec
@@ -287,7 +288,7 @@ qiovec_write_nb(int fd, qiovec qiov)
l = iovec_write_nb(fd, (struct iovec*)(&qiov->vec[qiov->i_get]), n) ;
- if (l == 0)
+ if (l <= 0)
{
qiov->writing = false ;
qiov->i_get = qiov->i_put = 0 ;
@@ -342,11 +343,14 @@ iovec_write_nb(int fd, struct iovec p_iov[], int n)
while (n > 0)
{
ssize_t ret ;
+ int m ;
+
+ m = (n < qiov_max) ? n : qiov_max ;
if (qdebug_nb)
- ret = writev_qdebug_nb(fd, p_iov, (n < IOV_MAX ? n : IOV_MAX)) ;
+ ret = writev_qdebug_nb(fd, p_iov, m) ;
else
- ret = writev (fd, p_iov, (n < IOV_MAX ? n : IOV_MAX)) ;
+ ret = writev (fd, p_iov, m) ;
if (ret > 0)
{
@@ -378,7 +382,6 @@ 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 */
} ;
} ;
diff --git a/lib/qiovec.h b/lib/qiovec.h
index 0cbf0364..5c7890bf 100644
--- a/lib/qiovec.h
+++ b/lib/qiovec.h
@@ -79,6 +79,7 @@ enum
/*==============================================================================
* Functions
*/
+extern void qiovec_start_up(void) ;
extern qiovec qiovec_init_new(qiovec qiov) ;
extern qiovec qiovec_reset(qiovec qiov, bool free_structure) ;
diff --git a/lib/qlib_init.c b/lib/qlib_init.c
index 757a9e65..1d641e26 100644
--- a/lib/qlib_init.c
+++ b/lib/qlib_init.c
@@ -19,6 +19,10 @@
* Boston, MA 02111-1307, USA.
*/
#include "misc.h"
+
+#include <errno.h>
+#include <stdio.h>
+
#include "qlib_init.h"
#include "zassert.h"
#include "memory.h"
@@ -30,6 +34,7 @@
#include "mqueue.h"
#include "pthread_safe.h"
#include "log_local.h"
+#include "qiovec.h"
/*==============================================================================
* Quagga Library Initialise/Closedown
@@ -44,6 +49,8 @@
*
* this is expected to be called before the program does anything at all.
*
+ * Collects a small number of useful system parameters -- see below.
+ *
* This performs all initialisation required to support asserts, logging,
* basic I/O (but not the remote console), trap signals... and so on.
*
@@ -57,21 +64,82 @@
* This performs all initialisation required to support socket I/O,
* thread handling, timers, and so on.
*
- * In particular, at this stage the system is set into Pthread Mode, if
- * required. No pthreads may be started before this. Up to this point
- * the system operates in non-Pthread Mode -- all mutexes are implicitly
- * free.
+ * NB: at this stage the system is set into pthread mode, if required.
+ *
+ * No pthreads may be started before this. Up to this point
+ * the system operates in non-pthread mode -- all mutexes are
+ * implicitly free.
*
* There is one stage of closedown. This is expected to be called last, and
* is passed the exit code.
*
+ *==============================================================================
+ * System parameters:
*
+ * iov_max -- _SC_IOV_MAX
+ *
+ * open_max -- _SC_OPEN_MAX
*/
+int qlib_iov_max ;
+int qlib_open_max ;
+int qlib_pagesize ;
+
+struct
+{
+ int* p_var ;
+ int sc ;
+ const char* name ;
+ long min ;
+ long max ;
+} qlib_vars[] =
+{
+ { .p_var = &qlib_iov_max, .sc = _SC_IOV_MAX, .name = "_SC_IOV_MAX",
+ .min = 16, .max = INT_MAX },
+ { .p_var = &qlib_open_max, .sc = _SC_OPEN_MAX, .name = "_SC_OPEN_MAX",
+ .min = 256, .max = INT_MAX },
+ { .p_var = &qlib_pagesize, .sc = _SC_PAGESIZE, .name = "_SC_PAGESIZE",
+ .min = 256, .max = (INT_MAX >> 1) + 1 },
+ { .p_var = NULL }
+} ;
+
extern void
qlib_init_first_stage(void)
{
+ int i ;
+
+ for (i = 0 ; qlib_vars[i].p_var != NULL ; ++i)
+ {
+ long val ;
+
+ errno = 0 ;
+ val = sysconf(qlib_vars[i].sc) ;
+
+ if (val == -1)
+ {
+ if (errno == 0)
+ val = INT_MAX ;
+ else
+ {
+ fprintf(stderr, "Failed to sysconf(%s): %s\n",
+ qlib_vars[i].name, errtoa(errno, 0).str) ;
+ exit(1) ;
+ } ;
+ } ;
+
+ if ((val < qlib_vars[i].min) || (val > qlib_vars[i].max))
+ {
+ fprintf(stderr, "sysconf(%s) = %ld: which is < %ld or > %ld\n",
+ qlib_vars[i].name, val, qlib_vars[i].min, qlib_vars[i].max) ;
+ exit(1) ;
+ } ;
+
+ *(qlib_vars[i].p_var) = (int)val ;
+ } ;
+
qps_start_up() ;
+ memory_start() ;
+ qiovec_start_up() ;
}
extern void
@@ -87,7 +155,6 @@ qlib_init_second_stage(bool pthreads)
safe_init_r();
}
-
extern void
qexit(int exit_code)
{
diff --git a/lib/qlib_init.h b/lib/qlib_init.h
index 6c6c1a01..71ccc162 100644
--- a/lib/qlib_init.h
+++ b/lib/qlib_init.h
@@ -39,4 +39,14 @@ extern void qlib_init_second_stage(bool pthreads) ;
extern void qexit(int exit_code) ;
+/*==============================================================================
+ * System parameters, set at qlib_init_first_stage() time.
+ */
+
+int qlib_iov_max ; /* Maximum length of iovec vector */
+
+int qlib_open_max ; /* Maximum number of file descriptors */
+
+int qlib_pagesize ; /* Size of system page */
+
#endif /* _ZEBRA_QLIB_INIT_H */
diff --git a/lib/qpath.c b/lib/qpath.c
index 28757f11..955f307f 100644
--- a/lib/qpath.c
+++ b/lib/qpath.c
@@ -19,13 +19,15 @@
* Boston, MA 02111-1307, USA.
*/
#include "misc.h"
-#include "qpath.h"
-#include "qstring.h"
-
-#include "zassert.h"
#include <unistd.h>
#include <errno.h>
+#include <pwd.h>
+
+#include "qpath.h"
+#include "qstring.h"
+#include "memory.h"
+#include "pthread_safe.h"
/*==============================================================================
* Some primitive path handling, based on qstrings.
@@ -41,11 +43,9 @@
* So this code reduces runs of '/' to single '/' -- except for the special
* case.
*
- * This code also replaces "/./" by "/".
- *
- *
+ * This code also replaces "/./" by "/", and removes xxxx/.. ! Does not strip
+ * trailing '/'.
*/
-
static void qpath_reduce(qpath qp) ;
/*------------------------------------------------------------------------------
@@ -284,6 +284,149 @@ qpath_stat_is_directory(qpath qp)
: err ;
} ;
+/*------------------------------------------------------------------------------
+ * Get home directory for the given user.
+ *
+ * The name may be terminated by '\0' or '/'.
+ *
+ * If the qp path is NULL, creates a new qpath if required.
+ *
+ * Returns: qpath => OK
+ * NULL => errno == 0 => user not found ) existing qp set empty.
+ * errno != 0 => some error )
+ */
+extern qpath
+qpath_get_home(qpath qp, const char* name)
+{
+ qpath dst ;
+ const char* p ;
+
+ bool done ;
+ qpath home ;
+ int err ;
+
+ /* Prepare for action.
+ */
+ p = name ;
+
+ while ((*p != '\0') && (*p != '/'))
+ ++p ; /* find terminator */
+
+ dst = qpath_set_n(qp, name, p - name) ;
+ /* set user name */
+
+ done = false ; /* no result, yet */
+ home = NULL ; /* empty result, so far */
+ err = 0 ; /* no error, yet */
+
+ /* If the name is empty, attempt to get the HOME environment variable,
+ * failing that set the user name to the getlogin_r() name.
+ */
+ if ((p - name) == 0)
+ {
+ int l, s ;
+
+ while (1)
+ {
+ s = qs_size_nn(dst->path) ;
+ l = getenv_r("HOME", qs_char_nn(dst->path), s) ;
+
+ if (l < s)
+ break ;
+
+ qs_new_size(dst->path, l) ;
+ } ;
+
+ if (l >= 0)
+ {
+ qs_set_len_nn(dst->path, l) ;
+ home = dst ; /* Successfully fetched HOME */
+ done = true ;
+ }
+ else
+ {
+ while (1)
+ {
+ s = qs_size_nn(dst->path) ;
+ qassert(s > 0) ;
+ err = getlogin_r(qs_char_nn(dst->path), s) ;
+
+ if (err != ERANGE)
+ break ;
+
+ qs_new_size(dst->path, s * 2) ;
+ } ;
+
+ if (err == 0)
+ {
+ qs_set_strlen_nn(dst->path) ;
+
+ if (qs_len_nn(dst->path) == 0)
+ done = true ;
+ }
+ else
+ done = true ;
+ } ;
+ } ;
+
+ /* If name was empty, or not found "HOME", then proceed to getpwd_r
+ */
+ if (!done)
+ {
+ struct passwd pwd_s ;
+ struct passwd* pwd ;
+
+ char* scratch ;
+ uint scratch_size ;
+ char buffer[200] ; /* should be plenty */
+
+ scratch = buffer ;
+ scratch_size = sizeof(buffer) ;
+
+ while (1)
+ {
+ err = getpwnam_r(qpath_string(dst),
+ &pwd_s, scratch, scratch_size, &pwd) ;
+ if (err == EINTR)
+ continue ;
+
+ if (err != ERANGE)
+ break ;
+
+ scratch_size *= 2 ;
+ if (scratch == buffer)
+ scratch = XMALLOC(MTYPE_TMP, scratch_size) ;
+ else
+ scratch = XREALLOC(MTYPE_TMP, scratch, scratch_size) ;
+ } ;
+
+ done = true ;
+
+ if (pwd != NULL)
+ {
+ qpath_set(dst, pwd->pw_dir) ;
+ home = dst ;
+ } ;
+
+ if (scratch != buffer)
+ XFREE(MTYPE_TMP, scratch) ;
+ } ;
+
+ /* Complete result
+ */
+ if (home == NULL)
+ {
+ if (qp != NULL)
+ qs_set_len_nn(qp->path, 0) ; /* chop existing path */
+ else
+ qpath_free(dst) ; /* discard temporary */
+
+ errno = err ; /* as promised */
+ } ;
+
+ return home ;
+} ;
+
/*==============================================================================
* Path editing functions
*
@@ -333,6 +476,22 @@ qpath_shave(qpath qp)
return qp ;
} ;
+/*------------------------------------------------------------------------------
+ * See if path ends '/'.
+ */
+extern bool
+qpath_has_trailing_slash(qpath qp)
+{
+ pp_t p ;
+
+ if (qp == NULL)
+ return false ;
+
+ qs_pp_nn(p, qp->path) ;
+
+ return (p->e > p->p) && (*(p->e - 1) == '/') ;
+} ;
+
#if 0
/*==============================================================================
@@ -1112,14 +1271,37 @@ qpath_is_atom(qpath qp)
/*------------------------------------------------------------------------------
* Reduce multiple '/' to single '/' (except for exactly "//" at start).
*
- * Reduce "/./" to "/".
+ * Reduce "zzz/./zzz" to "zzz/zzz" -- s/\/\.(\/|\0)/$1/g
+ * "zzz/./" to "zzz/"
+ * "zzz/." to "zzz/"
+ * "/./" to "/"
+ * "/." to "/"
+ *
+ * where zzz is anything, including '/'
+ *
+ * Reduce "zzz/aaa/../zzz" to "zzz/zzz" -- s/\.*([^/.]+\.*)+\/\.\.\/?//g
+ * "aaa/../zzz" to "zzz"
+ * "/aaa/../zzz" to "/zzz"
+ * "/aaa/../" to "/"
+ * "aaa/../" to ""
+ * "aaa/.." to ""
+ *
+ * where aaa is anything except '/'
+ *
+ * NB:
+ *
+ * NB: does not strip trailing '/' (including '/' of trailing "/." or "/..")
+ *
+ * does not reduce leading "./" or single "." to nothing.
*/
static void
qpath_reduce(qpath qp)
{
- char* sp ;
- char* p ;
- char* q ;
+ char* sp ;
+ char* p ;
+ char* q ;
+ char ch ;
+ bool eat ;
sp = qs_make_string(qp->path) ;
p = sp ;
@@ -1134,79 +1316,213 @@ qpath_reduce(qpath qp)
/* Scan to see if there is anything that needs to be fixed.
*
- * Looking for "//" and "/./".
+ * Looking for "//", "/./", "/.\0", "/../" or "/..\0".
+ *
+ * NB: does not consider that "." needs to be fixed, nor "./", and does not
+ * strip trailing "/".
*/
while (1)
{
- if (*p == '\0')
- return ; /* scanned to end */
+ ch = *p++ ;
- if (*p++ != '/') /* scanning for '/' */
+ if (ch == '\0')
+ return ; /* scanned to end */
+
+ if (ch != '/') /* scanning for '/' */
continue ;
- if (*p == '/')
- break ; /* second '/' */
+ ch = *p++ ; /* get char after '/' */
+
+ if (ch == '\0')
+ return ; /* scanned to end */
+
+ if (ch == '/')
+ break ; /* second '/' */
+
+ if (ch != '.')
+ continue ; /* not "//" and not "/." */
- if (*p != '.')
- continue ; /* not "//" and not "/." */
+ ch = *p ; /* get char after "/." */
- if (*(p+1) == '/')
- break ; /* found "/./" */
+ if ((ch == '/') || (ch == '\0'))
+ break ; /* found "/./" or "/.\0" */
+
+ if (ch != '.')
+ continue ; /* not "/..", either */
+
+ ch = *(p+1) ; /* get char after "/.." */
+
+ if ((ch == '/') || (ch == '\0'))
+ break ; /* found "/../" or "/..\0" */
} ;
/* Rats... there is something to be fixed.
*
- * *p is second '/' of "//" or '.' of "/./".
+ * Enter the reduction loop ready to eat the first item.
*/
- q = p ; /* keep the first '/' */
+ --p ; /* back to second '/' or '.' */
+ q = p ; /* keep the first '/' */
- while (*p != '\0')
- {
- ++p ; /* step past '.' or second '/' */
+ eat = true ;
- /* Step past any number of '/' and any number of "./". */
- while (*p != '\0')
+ while (1)
+ {
+ if (eat)
{
- while (*p == '/') /* eat any number of these */
+ eat = false ;
+
+ /* At this point:
+ *
+ * *p is '/', which is second of "//???"
+ * or (first) '.' of "./???", ".\0", "../???" or "..\0"
+ *
+ * *q is start of path or just after a '/'
+ *
+ * NB: after the first time through, p != q (or not necessarily).
+ */
+ qassert((*p == '/') || (*p == '.')) ;
+ if (*p == '.')
+ qassert((*(p+1) == '\0') || (*(p+1) == '/') || (*(p+1) == '.')) ;
+
+ qassert((q == sp) || (*(q-1) == '/')) ;
+
+ if ((*p == '.') && (*(p+1) == '.'))
+ {
+ char* sq = q ; /* in case find leading '~' */
+
+ qassert((*(p+2) == '\0') || (*(p+2) == '/')) ;
+
+ /* Deal with "../???" or "..\0"
+ *
+ * Before can strip the "..", have to verify that we have
+ * something to step back over.
+ *
+ * Can eat the ".." unless : is at start of path
+ * have nothing before the '/'
+ * (only) have '/' before the '/'
+ * only have "." before the '/'
+ * only have ".." before the '/'
+ * have "/.." before the '/'
+ *
+ * Note: can only have '/' before the '/' at the start.
+ *
+ * If cannot eat the '..', go back to the top of the loop, with
+ * eat == false, and process as an ordinary item.
+ */
+ if (q <= (sp + 1))
+ continue ; /* at start or
+ * nothing before the '/' */
+ if (*(q-2) == '/')
+ continue ; /* '/' before the '/' */
+
+ if (*(q-2) == '.')
+ {
+ /* Have '.' before the '/' */
+ if (q == (sp + 2))
+ continue ; /* only '.' before the '/' */
+
+ if (*(q-3) == '.')
+ {
+ /* Have ".." before the '/' */
+ if (q == (sp + 3))
+ continue ; /* only ".." before the '/' */
+
+ if (*(q-4) == '/')
+ continue ; /* "/.." before the '/' */
+ } ;
+ } ;
+
+ /* Eat the preceding item, including the final '/'
+ *
+ * *p == "../" or "..\0" to eat
+ * *q == just after '/' at end of item to eat
+ */
+ do /* starts with q just past '/' */
+ --q ;
+ while ((q > sp) && (*(q-1) != '/')) ;
+
+ /* Very special case: if have reduced the path to nothing,
+ * but path started with '~' then need to undo everything,
+ * and treate the '..' as ordinary item.
+ *
+ * Copes with ~fred/.. !!
+ */
+ if ((q == sp) && (*q == '~'))
+ {
+ q = sq ; /* put back item */
+ continue ; /* keep the '..' */
+ } ;
+
+ /* Eat the '..' and stop now if nothing more to come.
+ */
+ p += 2 ;
+
+ if (*p == '\0')
+ break ;
+
+ qassert(*p == '/') ;
+ } ;
+
+ /* Now discard '.' or '/' and any number of following '/'.
+ *
+ * *p == the '.' or first '/' to be discarded
+ *
+ * Ends up at first character after a '/' -- which may be anything,
+ * including a possible '.'
+ */
+ do
++p ;
-
- if (*p != '.') /* done if not '.' */
- break ;
-
- if (*(p+1) != '/') /* done if not "./" */
- break ;
-
- p += 2 ; /* Step past "./" */
+ while (*p == '/') ;
+ }
+ else
+ {
+ /* Ordinary item (or '..') to copy across -- eat == false.
+ *
+ * Copying stuff, until get to '\0' or copy a '/'.
+ */
+ do
+ {
+ ch = *p ;
+
+ if (ch == '\0')
+ break ;
+
+ ++p ;
+ *q++ = ch ;
+ }
+ while (ch != '/') ;
} ;
- /* Here we have *p which is not '/' and not "./", so unless is '\0'
- * there is at least one character to move across.
+ /* Have processed to '\0' and/or just past a '/'
*
- * Copying stuff, until get to '\0' or find "//" or "/./"
+ * Decide whether to continue, and if so whether to eat what follows
+ * or copy it across.
*/
- while (*p != '\0')
- {
- *q++ = *p ; /* copy non-'\0' */
+ ch = *p ;
- if (*p++ != '/')
- continue ; /* keep going if wasn't '/' */
+ if (ch == '\0')
+ break ;
- if (*p == '/')
- break ; /* second '/' */
+ qassert(!eat) ;
- if (*p != '.')
- continue ; /* not "//" and not "/." */
-
- if (*(p+1) == '/')
- break ; /* found "/./" */
+ if (ch == '/')
+ eat = true ; /* second '/' after '/' */
+ else if (ch == '.')
+ {
+ ch = *(p+1) ;
+ if ((ch == '/') || (ch == '\0'))
+ eat = true ; /* "./???" or ".\0" after '/' */
+ else if (ch == '.')
+ {
+ ch = *(p+2) ;
+ if ((ch == '/') || (ch == '\0'))
+ eat = true ; /* "../???" or "..\0" after '/' */
+ } ;
} ;
} ;
- /* Adjust the length and terminate */
-
+ /* 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 23b710a6..9b0509d7 100644
--- a/lib/qpath.h
+++ b/lib/qpath.h
@@ -85,12 +85,14 @@ Inline qpath qpath_dup(const qpath qp) ;
Inline qpath qpath_dup_str(const char* src) ;
extern qpath qpath_getcwd(qpath dst) ;
+extern qpath qpath_get_home(qpath qp, const char* name) ;
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_shave(qpath qp) ;
+extern bool qpath_has_trailing_slash(qpath qp) ;
extern qpath qpath_append(qpath dst, const qpath src) ;
extern qpath qpath_append_qs(qpath dst, const qstring src) ;
diff --git a/lib/qpnexus.h b/lib/qpnexus.h
index 1aeefc04..38dcbebc 100644
--- a/lib/qpnexus.h
+++ b/lib/qpnexus.h
@@ -57,7 +57,6 @@ enum
/*==============================================================================
* Data Structures.
*/
-
typedef int qpn_hook_function(void) ; /* dispatch of tasks */
typedef int qpn_init_function(void) ; /* start/stop work */
diff --git a/lib/qpselect.c b/lib/qpselect.c
index 3fb80daf..3332db4f 100644
--- a/lib/qpselect.c
+++ b/lib/qpselect.c
@@ -1432,6 +1432,8 @@ qps_selection_validate(qps_selection qps)
* Miniature pselect -- for where want to wait for a small number of things.
*/
+uint qps_mini_timeout_debug = 0 ;
+
/*------------------------------------------------------------------------------
* Initialise a qps_mini and set one fd in the given mode.
*
@@ -1461,6 +1463,14 @@ 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)
{
+ if (qdebug && (qps_mini_timeout_debug > 0))
+ {
+ if (qps_mini_timeout_debug == 1)
+ fd = -1 ;
+
+ --qps_mini_timeout_debug ;
+ } ;
+
if (fd >= 0)
{
FD_SET(fd, &(qm->sets[mode].fdset)) ;
diff --git a/lib/qpselect.h b/lib/qpselect.h
index 37bf8680..fc9037ce 100644
--- a/lib/qpselect.h
+++ b/lib/qpselect.h
@@ -255,4 +255,6 @@ extern qps_mini qps_mini_set(qps_mini qm, int fd, qps_mnum_t mode,
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) ;
+extern uint qps_mini_timeout_debug ;
+
#endif /* _ZEBRA_QPSELECT_H */
diff --git a/lib/qstring.h b/lib/qstring.h
index 536105d0..b9813bc7 100644
--- a/lib/qstring.h
+++ b/lib/qstring.h
@@ -518,16 +518,16 @@ qs_make_string(qstring qs)
/*------------------------------------------------------------------------------
* Return pointer to string value.
*
- * If possible, writes '\0' at 'len' in order to return a terminated string.
+ * Writes '\0' at 'len' in order to return a terminated string, if required.
*
* If qs == NULL or body == NULL, or 'len' == 0 returns pointer to constant
* empty '\0' terminated string (ie "").
*
* NB: if 'len' is beyond the current 'size' of the of the qstring, then
- * does NOT write '\0' (does NOT extend the string).
+ * will extend the string.
*
- * NB: if string is an alias, this returns its address, whether it is
- * terminated or not.
+ * NB: if string is an alias, and that is not '\0' terminated, will make a
+ * copy, before writing '\0' at end.
*
* NB: In any event, the string should not be changed or reset until this
* pointer has been discarded !
@@ -542,8 +542,16 @@ qs_string(qstring qs)
|| ((p = qs_char_nn(qs)) == NULL) )
return "" ;
- if (len < qs->size) /* for alias, qs_size == 0 */
- *(p + len) = '\0' ;
+ if (len >= qs->size) /* for alias, qs_size == 0 */
+ {
+ if (qs->alias && (*(p + len) == '\0'))
+ return p ;
+
+ qs_make_to_size(qs, len, len) ; /* extend and/or copy alias */
+ p = qs_char_nn(qs) ;
+ } ;
+
+ *(p + len) = '\0' ;
return p ;
} ;
diff --git a/lib/qtimers.c b/lib/qtimers.c
index 3af79d7b..ad8da395 100644
--- a/lib/qtimers.c
+++ b/lib/qtimers.c
@@ -406,7 +406,7 @@ qtimer_pile_verify(qtimer_pile qtp)
vector_index_t i ;
vector_length_t e ;
qtimer qtr ;
- bool seen ;
+ bool seen = false ;
assert(qtp != NULL) ;
@@ -427,7 +427,10 @@ qtimer_pile_verify(qtimer_pile qtp)
assert(qtr != NULL) ;
if (qtr == qtp->implicit_unset)
- seen = 1 ;
+ {
+ assert(!seen) ;
+ seen = true ;
+ } ;
assert(qtr->active) ;
diff --git a/lib/sigevent.c b/lib/sigevent.c
index c7c1e134..5dfb67cf 100644
--- a/lib/sigevent.c
+++ b/lib/sigevent.c
@@ -147,8 +147,8 @@ 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 int signal_set_set(sigset_t* set, sig_handler* handler, bool required) ;
+static int signal_set(int signo, sig_handler* handler, bool required) ;
static void __attribute__ ((noreturn))
core_handler(int signo, siginfo_t *info, void *context) ;
@@ -362,11 +362,11 @@ signal_init (struct thread_master *m, int sigc,
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) ;
+ signal_set_set(core_signals, core_handler, false) ;
+ signal_set_set(exit_signals, exit_handler, false) ;
+ signal_set_set(ignore_signals, NULL, false) ;
+ signal_set_set(qsig_signals, quagga_signal_handler, true) ;
+ signal_set_set(qsig_interrupts, quagga_interrupt_handler, true) ;
/* If using a timer thread to scan for signal events, start that now.
*/
@@ -695,7 +695,7 @@ quagga_signal_reset(void)
* Returns: < 0 => failed -- value is - failing signo !
*/
static int
-signal_set_set(sigset_t* set, sig_handler* handler)
+signal_set_set(sigset_t* set, sig_handler* handler, bool required)
{
int signo ;
@@ -707,7 +707,7 @@ signal_set_set(sigset_t* set, sig_handler* handler)
if (s < 0)
break ;
if (s > 0)
- if (signal_set(signo, handler) < 0)
+ if (signal_set(signo, handler, required) < 0)
return -signo ;
} ;
@@ -727,10 +727,26 @@ signal_set_set(sigset_t* set, sig_handler* handler)
#endif
static int
-signal_set(int signo, sig_handler* handler)
+signal_set(int signo, sig_handler* handler, bool required)
{
struct sigaction act[1] ;
+ /* If a signal handler is already set for the given signal then we leave
+ * that as it is -- unless this is one of the "required" signals, used by
+ * Quagga.
+ */
+ sigaction(signo, NULL, act) ;
+
+ if ((act->sa_handler != SIG_DFL) && (act->sa_handler != SIG_IGN))
+ {
+ if (!required)
+ return 0 ;
+ } ;
+
+ /* Set our handler */
+
+ memset(act, 0, sizeof(struct sigaction)) ;
+
if (handler == NULL)
{
act->sa_handler = SIG_IGN ;
diff --git a/lib/thread.c b/lib/thread.c
index b9fd603c..7f8ff5f6 100644
--- a/lib/thread.c
+++ b/lib/thread.c
@@ -840,7 +840,7 @@ funcname_thread_add_write (struct thread_master *m,
* THREAD_TIMER or a THREAD_BACKGROUND thread, then thread->u.qtr points
* at the qtimer.
*
- * AND, conversely, if there is no qtimer, then thread->u.ptr == NULL.
+ * AND, conversely, if there is no qtimer, then thread->u.qtr == NULL.
*/
/*------------------------------------------------------------------------------
diff --git a/lib/vector.c b/lib/vector.c
index 74f61913..83d0397c 100644
--- a/lib/vector.c
+++ b/lib/vector.c
@@ -482,7 +482,7 @@ vector_move_item(vector v, vector_index_t i_dst, vector_index_t i_src)
*
* Move items and extend vector as required.
*/
-extern void
+void
vector_move_item_here(vector v, vector_index_t i_dst, int rider,
vector_index_t i_src)
{
diff --git a/lib/vio_fifo.c b/lib/vio_fifo.c
index 89e42add..0612c465 100644
--- a/lib/vio_fifo.c
+++ b/lib/vio_fifo.c
@@ -71,7 +71,7 @@
* The following are expected to be true:
*
* * p_start == &get_ptr => no hold mark
- * &hold_ptr => have hold mark
+ * &hold_ptr => hold mark is set
*
* * put_ptr == get_ptr => FIFO empty -- unless *p_start != get_ptr.
*
@@ -95,8 +95,8 @@
* get_ptr < *p_get_end => data exists in the current get_lump
* get_ptr > *p_get_end => broken
*
- * * p_end == &end_ptr -- when end mark set
- * == &put_ptr -- when no end mark
+ * * p_end == &put_ptr => no end mark
+ * == &end_ptr => end mark is set
*
* Note that:
*
@@ -105,7 +105,7 @@
*
* When get_ptr reaches *p_get_end, however, must move to the next lump,
* if possible, or collapse the pointers if have hit the put_ptr. Keeping
- * the get_ptr honest in this way: (a) ensures that will always put at
+ * the get_ptr honest in this way: (a) ensures that will always get from
* the beginning of a lump if possible; (b) simplifies the handling of
* hold_ptr et al (because get_ptr is never in the ambiguous position
* at the end of one lump, which is the same as the start of the next).
@@ -163,7 +163,7 @@
inline static void vio_fifo_set_get_ptr(vio_fifo vff, vio_fifo_lump lump,
char* ptr) ;
inline static void vio_fifo_set_get_end(vio_fifo vff) ;
-inline static void vio_fifo_release_upto(vio_fifo vff, vio_fifo_lump upto) ;
+inline static void vio_fifo_release_up_to(vio_fifo vff, vio_fifo_lump upto) ;
static void vio_fifo_release_lump(vio_fifo vff, vio_fifo_lump lump) ;
/*------------------------------------------------------------------------------
@@ -189,6 +189,10 @@ vio_fifo_have_end_mark(vio_fifo vff)
*
* Preserves and hold mark or end mark -- so no need to change p_start or p_end.
*
+ * Resets the hold_ptr and the end_ptr whether there is a hold and/or end mark
+ * or not -- saves testing for whether to do it or not. These values are
+ * only significant if p_start or p_end/p_get_end point at them.
+ *
* HOWEVER: does not set p_get_end -- so if get_lump or end_lump or p_end have
* changed, then must also call vio_fifo_set_get_end().
*
@@ -257,7 +261,7 @@ vio_fifo_sync_get(vio_fifo vff)
vio_fifo_set_get_ptr(vff, get_lump, get_lump->data) ;
if (!vio_fifo_have_hold_mark(vff))
- vio_fifo_release_upto(vff, get_lump) ;
+ vio_fifo_release_up_to(vff, get_lump) ;
} ;
} ;
@@ -268,7 +272,7 @@ inline static void
vio_fifo_set_get_ptr(vio_fifo vff, vio_fifo_lump lump, char* ptr)
{
vff->get_lump = lump ;
- vff->get_ptr = lump->data ;
+ vff->get_ptr = ptr ;
vio_fifo_set_get_end(vff) ;
} ;
@@ -287,18 +291,36 @@ vio_fifo_set_get_end(vio_fifo vff)
} ;
/*------------------------------------------------------------------------------
- * Release all lumps upto (but excluding) the given lump.
+ * Release all lumps up to (but excluding) the given lump.
*
* NB: takes no notice of hold_ptr or anything else.
*/
inline static void
-vio_fifo_release_upto(vio_fifo vff, vio_fifo_lump upto)
+vio_fifo_release_up_to(vio_fifo vff, vio_fifo_lump to)
{
vio_fifo_lump lump ;
- while (ddl_head(vff->base) != upto)
+ while (ddl_head(vff->base) != to)
vio_fifo_release_lump(vff, ddl_pop(&lump, vff->base, list)) ;
} ;
+/*------------------------------------------------------------------------------
+ * Release all lumps back to (but excluding) the given lump.
+ *
+ * Reset vff->put_end to be the end of the to->lump.
+ *
+ * NB: takes no notice of hold_ptr or anything else.
+ */
+inline static void
+vio_fifo_release_back_to(vio_fifo vff, vio_fifo_lump to)
+{
+ vio_fifo_lump lump ;
+ do
+ vio_fifo_release_lump(vff, ddl_crop(&lump, vff->base, list)) ;
+ while (to != ddl_tail(vff->base)) ;
+
+ vff->put_end = to->end ;
+} ;
+
/*==============================================================================
* Initialisation, allocation and freeing of FIFO and lumps thereof.
*/
@@ -445,7 +467,7 @@ vio_fifo_clear(vio_fifo vff, bool clear_marks)
vff->get_lump = lump ; /* before releasing */
vff->end_lump = lump ;
- vio_fifo_release_upto(vff, lump) ;
+ vio_fifo_release_up_to(vff, lump) ;
vio_fifo_reset_ptrs(vff) ;
@@ -726,6 +748,137 @@ vio_fifo_read_nb(vio_fifo vff, int fd, ulen request)
return total ;
} ;
+/*------------------------------------------------------------------------------
+ * Strip trailing whitespace and, if required, insert '\n' if result is not
+ * empty and does not now end in '\n'
+ *
+ * Strips anything 0x00..0x20 except for '\n'.
+ *
+ * Returns: 0..n -- number of bytes read
+ * -1 => failed -- see errno
+ * -2 => EOF met immediately
+ */
+extern void
+vio_fifo_trim(vio_fifo vff, bool term)
+{
+ vio_fifo_lump lump ;
+ char* p ;
+ char* s ;
+ char* end_ptr ;
+ bool end_ptr_passed ;
+ char ch ;
+
+ /* Position at end of fifo, and establish how far back in current lump
+ * can discard whitespace.
+ */
+ lump = ddl_tail(vff->base) ;
+ p = vff->put_ptr ;
+ s = (lump == vff->get_lump) ? vff->get_ptr : lump->data ;
+
+ if (vio_fifo_have_end_mark(vff))
+ end_ptr = vff->end_ptr ;
+ else
+ end_ptr = NULL ;
+ end_ptr_passed = false ;
+
+ /* Track backwards, until reach get_ptr or hit '\n' or something which is
+ * not 0x00..0x20.
+ */
+ ch = '\0' ;
+ while (1)
+ {
+ if (s == p)
+ {
+ /* At the start of the current lump and/or at the get_ptr.
+ *
+ * If at the get_ptr, then cannot track any further back, but if
+ * there is a hold mark, there may be something before the get_ptr.
+ */
+ if (lump == vff->get_lump)
+ {
+ qassert(p == vff->get_ptr) ; /* hit get_ptr */
+
+ ch = '\0' ;
+ if (p == *vff->p_start)
+ break ; /* hit start of fifo */
+
+ if (p != lump->data)
+ qassert(p > lump->data) ;
+ else
+ {
+ qassert(lump != ddl_head(vff->base)) ;
+ s = ddl_prev(lump, list)->end ;
+ } ;
+
+ ch = *(s - 1) ;
+
+ if (ch != '\n')
+ ch = '\xFF' ; /* anything but '\0' */
+
+ break ;
+ } ;
+
+ lump = ddl_prev(lump, list) ;
+
+ p = lump->end ;
+ s = (lump == vff->get_lump) ? vff->get_ptr : lump->data ;
+
+ continue ;
+ } ;
+
+ qassert(p > s) ;
+
+ ch = *(p-1) ;
+
+ if ((ch > 0x20) || (ch == '\n'))
+ break ;
+
+ if (p == end_ptr)
+ end_ptr_passed = true ; /* stepped past the end mark */
+
+ --p ;
+ } ;
+
+ /* Decide what to do now.
+ *
+ * ch == '\0' <=> p points at end of empty fifo !
+ * ch == '\n' <=> p points after a '\n'.
+ * otherwise <=> p points after non-whitespace, or is at get_ptr and
+ * something other than '\n' precedes it.
+ *
+ * lump points at the lump that 'p' is in.
+ *
+ * If stepped back past the end_ptr, now is the time to set the end_ptr
+ * to the current position.
+ */
+ if (end_ptr_passed)
+ {
+ qassert(end_ptr != NULL) ;
+
+ vff->end_lump = lump ;
+ vff->end_ptr = p ;
+ } ;
+
+ /* If have stepped back across one or more lumps, trim now excess lumps off
+ * the end.
+ *
+ * Note, it is (just) possible that in inserting a '\n', that this will
+ * trim off a lump that needs to be re-instated -- but it is more
+ * straightforward to do this, and the odds are pretty slim.
+ */
+ if (lump != ddl_tail(vff->base))
+ vio_fifo_release_back_to(vff, lump) ;
+
+ /* set the (new) put_ptr.
+ */
+ vff->put_ptr = p ;
+
+ /* If we need to add a terminator, do that now.
+ */
+ if (term && (ch != '\n') && (ch != '\0'))
+ vio_fifo_put_byte(vff, '\n') ;
+} ;
+
/*==============================================================================
* Copy operations -- from one FIFO to another.
*/
@@ -936,14 +1089,7 @@ vio_fifo_back_to_end_mark(vio_fifo vff, bool keep)
assert(vio_fifo_have_end_mark(vff)) ;
if (vff->end_lump != ddl_tail(vff->base))
- {
- vio_fifo_lump lump ;
- do
- vio_fifo_release_lump(vff, ddl_crop(&lump, vff->base, list)) ;
- while (vff->end_lump != ddl_tail(vff->base)) ;
-
- vff->put_end = vff->end_lump->end ;
- } ;
+ vio_fifo_release_back_to(vff, vff->end_lump) ;
if (*vff->p_start == vff->end_ptr)
vio_fifo_reset_ptrs(vff) ;
@@ -967,6 +1113,8 @@ vio_fifo_back_to_end_mark(vio_fifo vff, bool keep)
/*------------------------------------------------------------------------------
* Get upto 'n' bytes -- steps past the bytes fetched.
*
+ * Stops at current end of FIFO (and not before).
+ *
* Returns: number of bytes got -- may be zero.
*/
extern ulen
@@ -1123,7 +1271,7 @@ vio_fifo_skip_to_end(vio_fifo vff)
extern void
vio_fifo_set_hold_mark(vio_fifo vff)
{
- vio_fifo_release_upto(vff, vff->get_lump) ;
+ vio_fifo_release_up_to(vff, vff->get_lump) ;
if (vff->get_ptr == vff->put_ptr)
vio_fifo_reset_ptrs(vff) ;
@@ -1149,7 +1297,7 @@ vio_fifo_set_hold_mark(vio_fifo vff)
extern void
vio_fifo_clear_hold_mark(vio_fifo vff)
{
- vio_fifo_release_upto(vff, vff->get_lump) ;
+ vio_fifo_release_up_to(vff, vff->get_lump) ;
if (vff->get_ptr == vff->put_ptr)
vio_fifo_reset_ptrs(vff) ;
@@ -1346,14 +1494,14 @@ vio_fifo_verify(vio_fifo vff)
*/
if (*vff->p_start == vff->put_ptr)
{
- if ( (tail != head)
- || (vff->get_lump != head)
- || (vff->end_lump != head)
- || (vff->get_ptr != head->data)
- || (vff->put_ptr != head->data)
- || (vff->put_end != head->end)
- || !( (vff->hold_ptr == NULL) || (vff->hold_ptr == head->data) )
- || !( (vff->end_ptr == NULL) || (vff->end_ptr == head->data) )
+ if ( (tail != head)
+ || (vff->get_lump != head)
+ || (vff->end_lump != head)
+ || (vff->get_ptr != head->data)
+ || (vff->put_ptr != head->data)
+ || (vff->put_end != head->end)
+ || !( (vff->p_start != &vff->hold_ptr) || (vff->hold_ptr == head->data) )
+ || !( (vff->p_end != &vff->end_ptr) || (vff->end_ptr == head->data) )
)
zabort("pointers not valid for empty fifo") ;
} ;
diff --git a/lib/vio_fifo.h b/lib/vio_fifo.h
index 03437ae4..44f2cf47 100644
--- a/lib/vio_fifo.h
+++ b/lib/vio_fifo.h
@@ -86,28 +86,28 @@ struct vio_fifo
{
struct dl_base_pair(vio_fifo_lump) base ;
- char** p_start ; /* -> hold_ptr/get_ptr */
+ char** p_start ; /* -> hold_ptr/get_ptr */
- char* hold_ptr ; /* implicitly in the head lump */
- /* NULL <=> no hold_ptr */
+ char* hold_ptr ; /* p_start == &hold_ptr <=> hold mark set */
+ /* implicitly in the head lump, if set */
- vio_fifo_lump get_lump ; /* head lump unless "hold_ptr" */
+ vio_fifo_lump get_lump ; /* head lump unless "hold_ptr" */
char* get_ptr ;
- char** p_get_end ; /* -> lump->end/end_ptr/put_ptr */
- char** p_end ; /* -> end_ptr/put_ptr */
+ char** p_get_end ; /* -> lump->end/end_ptr/put_ptr */
+ char** p_end ; /* -> end_ptr/put_ptr */
- vio_fifo_lump end_lump ; /* tail lump unless "end_ptr" */
- char* end_ptr ; /* NULL <=> no end_ptr */
+ vio_fifo_lump end_lump ; /* tail lump unless "end_ptr" */
+ char* end_ptr ; /* p_end == &end_ptr <=> end mark set */
- char* put_ptr ; /* implicitly in the tail lump */
+ char* put_ptr ; /* implicitly in the tail lump */
char* put_end ;
- ulen size ; /* set when initialised */
+ ulen size ; /* set when initialised */
- vio_fifo_lump spare ; /* may be "own_lump" */
+ vio_fifo_lump spare ; /* may be "own_lump" */
- vio_fifo_lump_t own_lump[] ; /* embedded lump */
+ vio_fifo_lump_t own_lump[] ; /* embedded lump */
} ;
typedef struct vio_fifo vio_fifo_t[1] ; /* embedded */
@@ -141,6 +141,7 @@ 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, ulen request) ;
+extern void vio_fifo_trim(vio_fifo vff, bool term) ;
extern ulen vio_fifo_get_bytes(vio_fifo vff, void* dst, ulen n) ;
Inline int vio_fifo_get_byte(vio_fifo vff) ;
diff --git a/lib/vio_lines.c b/lib/vio_lines.c
index 2a6a89d1..cb5bf26c 100644
--- a/lib/vio_lines.c
+++ b/lib/vio_lines.c
@@ -446,7 +446,7 @@ vio_lc_flush(vio_line_control lc)
if (item->len == 0)
{
- assert(qiovec_empty(lc->fragments)) ;
+ qassert(qiovec_empty(lc->fragments)) ;
return false ; /* it was all whitespace which has all
been discarded. */
} ;
diff --git a/lib/vio_lines.h b/lib/vio_lines.h
index 6a3d13e9..be62f143 100644
--- a/lib/vio_lines.h
+++ b/lib/vio_lines.h
@@ -82,6 +82,7 @@ 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) ;
+Inline bool vio_lc_pending(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) ;
@@ -172,4 +173,15 @@ vio_lc_is_empty(vio_line_control lc)
return qiovec_empty(lc->qiov) && qiovec_empty(lc->fragments) ;
} ;
+/*------------------------------------------------------------------------------
+ * Is there something pending, still to be written, in the qiov ?
+ *
+ * NB: ignoring anything that there may be in hand.
+ */
+Inline bool
+vio_lc_pending(vio_line_control lc)
+{
+ return lc->qiov->writing ;
+} ;
+
#endif /* _ZEBRA_VIO_LINES_H */
diff --git a/lib/vty.c b/lib/vty.c
index e6dcd6a8..831ce721 100644
--- a/lib/vty.c
+++ b/lib/vty.c
@@ -57,8 +57,8 @@
* vty -- level visible from outside the vty/command/log family
* and within those families.
*
- * vty_common.h -- definitions ...
- * vty_local.h
+ * vty_common.h -- definitions used by both external and internal users
+ * vty_local.h -- definitions used within the family only
*
* vty_io -- top level of the vio handling
*
@@ -76,12 +76,10 @@
* thread/select worlds.
*
* vio_lines -- for terminal: handles width, CRLF, line counting etc.
- * vio_fifo --
- * qiovec
- *
+ * vio_fifo -- for all vty types, indefinite size buffer
+ * qiovec -- using writev() to output contents of buffers
*/
-
/*==============================================================================
* Variables etc. (see vty_local.h)
*/
@@ -119,9 +117,6 @@ vty_io vio_live_list = NULL ;
/* List of all vty which are in monitor state. */
vty_io vio_monitor_list = NULL ;
-/* List of all vty which are on death watch */
-vty_io vio_death_watch = NULL ;
-
/* List of child processes in our care */
vio_child vio_childer_list = NULL ;
@@ -139,7 +134,7 @@ char integrate_default[] = SYSCONFDIR INTEGRATE_DEFAULT_CONFIG ;
/*------------------------------------------------------------------------------
* Prototypes
*/
-static void uty_reset (bool final, const char* why) ;
+static void uty_reset (const char* why, bool curtains) ;
static void uty_init_commands (void) ;
//static bool vty_terminal (struct vty *);
@@ -186,7 +181,6 @@ vty_init (struct thread_master *master_thread)
vty_master = master_thread; /* Local pointer to the master thread */
vio_live_list = NULL ; /* no VTYs yet */
- vio_death_watch = NULL ;
vio_childer_list = NULL ;
vty_nexus = false ; /* not running qnexus-wise */
@@ -196,8 +190,6 @@ vty_init (struct thread_master *master_thread)
vty_child_signal_nexus = NULL ; /* none, yet */
- uty_watch_dog_init() ; /* empty watch dog */
-
uty_init_monitor() ;
uty_init_commands() ; /* install nodes */
@@ -290,22 +282,26 @@ extern void
vty_reset()
{
vty_reset_because("Reset") ;
-}
+} ;
/*------------------------------------------------------------------------------
- * Reset all VTY status
- *
- * This is done in response to SIGHUP/SIGINT/SIGTERM -- and runs in the
- * CLI thread (if there is one).
+ * Reset all VTY.
*
- * Closes all VTY, leaving the death watch to tidy up once all output and any
- * command in progress have completed.
+ * This is done in response to SIGHUP/SIGINT -- and runs in the CLI pthread
+ * (if there is one).
*
- * Closes all listening sockets.
+ * Stops any command loop and closes all VTY, "final". Issues the "why"
+ * message just before closing the base output.
*
- * TODO: revoke ?
+ * Note that if a command loop is currently executing a command in the
+ * Routing Engine pthread, then when that completes the command loop will stop
+ * and close the VTY. So completion of the close depends on the CLI pthread
+ * continuing to run and consume messages. The Routing Engine will act on
+ * the SIGHUP/SIGINT/SIGTERM when it consumes a message sent to it, so any
+ * currently executing command will complete, before any further action is
+ * taken.
*
- * NB: old code discarded all output and hard closed all the VTY...
+ * Closes all listening sockets.
*/
extern void
vty_reset_because(const char* why)
@@ -317,15 +313,16 @@ vty_reset_because(const char* why)
{
assert(vty_init_state == vty_init_started) ;
- uty_reset(false, why) ; /* not final ! */
+ uty_reset(why, false) ; /* not curtains */
vty_init_state = vty_init_reset ;
} ;
+
VTY_UNLOCK() ;
-}
+} ;
/*------------------------------------------------------------------------------
- * Restart the VTY, following a vty_reset().
+ * Restart the VTY, following a vty_reset()/vty_reset_because().
*
* This starts the listeners for VTY_TERMINAL and VTY_SHELL_SERVER, again.
*
@@ -444,7 +441,7 @@ vty_terminate (void)
assert( (vty_init_state > vty_init_pending)
&& (vty_init_state < vty_init_terminated) ) ;
- uty_reset(true, "Shut down") ; /* final reset */
+ uty_reset("Shut down", true) ; /* curtains */
vty_child_close_register() ;
@@ -457,35 +454,71 @@ vty_terminate (void)
}
/*------------------------------------------------------------------------------
- * Reset -- for SIGHUP or at final curtain.
+ * Reset -- for SIGHUP/SIGINT or at final curtain.
+ *
+ * Closes listeners and resets the vty timeout and access lists.
+ *
+ * Closes all vty "final" -- see uty_close(). Before can close the vty must
+ * stop any related command loop -- see uty_cmd_loop_stop():
+ *
+ * * at "not-curtains" this is called in the *CLI* pthread, with all message
+ * and event handling is still running.
+ *
+ * This is called *before* a message is sent to the Routing Engine pthread
+ * to inform it that a SIGHUP/SIGINT/SIGTERM has been seen.
+ *
+ * It is possible that the command loop for a given vty cannot be stopped
+ * immediately and must be left to continue, until it sees the CMD_STOP
+ * vio->signal which will be set.
+ *
+ * * at "curtains" the system is being terminated, all threads have been
+ * joined, so is implicity in the CLI thread, but all message and event
+ * handling has stopped
+ *
+ * At "curtains" it is always possible to stop any command loop.
*
- * For SIGHUP is called via vty_reset_because(), and is sitting in the
- * vty_cli_nexus (if pthreaded) with the message queues still running.
+ * This is part of vty_terminate -- see above.
*
- * For final curtain will
+ * Close down is then a two step process:
*
+ * 1. vty_reset()/vty_reset_because() -- which immediately closes everything
+ * that does not have to wait for a command loop to stop, and anything
+ * that remains will be closed, automatically, as soon as possible.
*
- * is called by vty_terminate(), by which time all qnexus
- * have been shut down, so no message queues and no timers etc, are running.
+ * 2. vty_terminate() -- which sweeps up and closes anything that remains
+ * to be closed, and which the automatic mechanism has not got to yet.
*
+ * So, for each vty, if uty_cmd_loop_stop() does stop the command loop, then:
*
- * Closes listeners.
+ * * the close of the vty proceeds immediately.
*
- * Revokes any outstanding commands and close (SIGHUP) or close_final
- * (curtains) all VTY.
+ * Otherwise:
*
+ * * the CMD_STOP vio->signal has been set and the vty is left open.
*
+ * Note that the command loop *must* be running in the vty_cmd_nexus
+ * -- see uty_cmd_loop_stop().
*
- * Resets the vty timeout and access lists.
+ * * nothing further will happen until the Routing Engine collects the
+ * CMD_STOP vio->signal, and sends a message to pass the command loop
+ * back to the CLI pthread, to deal with the CMD_STOP.
*
- * When reach final reset it should not be possible for there to be any
- * commands still in progress. If there are, they are simply left on the
- * death-watch list... there is no pressing need to do anything more radical,
- * and the presence of anything on the death watch is grounds for some debug
- * activity !
+ * Before passing the command loop back, the Routing Engine will release
+ * the config symbol of power. So, if the Routing Engine goes on to
+ * re-read the configuration, it does not have to wait for the CLI to get
+ * round to stopping the vty.
+ *
+ * * when the CLI pthread receives the command loop back, it will collect the
+ * CMD_STOP (again) stop the command loop and close the vty.
+ *
+ * * when the Routing Engine picks up any SIGHUP/SIGTERM/etc message, it
+ * will either re-read the configuration or vty_terminate().
+ *
+ * The Routing Engine will not do this until after it has come out
+ * of the command loop, after dealing with the vio->signal, as above.
*/
static void
-uty_reset (bool curtains, const char* why)
+uty_reset (const char* why, bool curtains)
{
vty_io vio ;
vty_io next ;
@@ -500,7 +533,16 @@ uty_reset (bool curtains, const char* why)
vio = next ;
next = sdl_next(vio, vio_list) ;
- uty_close(vio, why, curtains) ;
+ /* Save the close reason for later, unless one is already set. */
+ if ((why != NULL) && (vio->close_reason == NULL))
+ vio->close_reason = XSTRDUP(MTYPE_TMP, why) ;
+
+ /* Stop the command loop -- if not already stopped.
+ *
+ * If command loop is running, it will be signalled to stop, soonest.
+ */
+ if (uty_cmd_loop_stop(vio, curtains))
+ uty_close(vio) ;
} ;
host.vty_timeout_val = VTY_TIMEOUT_DEFAULT;
@@ -512,7 +554,7 @@ uty_reset (bool curtains, const char* why)
/* sets host.vty_ipv6_accesslist_name = NULL */
if (curtains)
- uty_watch_dog_stop() ; /* and final death watch run */
+ uty_watch_dog_stop() ;
} ;
/*==============================================================================
@@ -680,6 +722,8 @@ vty_hello (struct vty *vty)
/*------------------------------------------------------------------------------
* "cat" file to vty
+ *
+ *
*/
extern cmd_return_code_t
vty_cat_file(vty vty, qpath path, const char* desc)
@@ -687,7 +731,7 @@ 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) ;
+ fd = uty_fd_file_open(qpath_string(path), vfd_io_read | vfd_io_blocking, 0) ;
if (fd < 0)
{
@@ -697,7 +741,7 @@ vty_cat_file(vty vty, qpath path, const char* desc)
return CMD_WARNING;
} ;
- enum { buffer_size = 64 * 1024 } ;
+ enum { buffer_size = 32 * 1024 } ;
buf = XMALLOC(MTYPE_TMP, buffer_size) ;
while (1)
@@ -717,6 +761,7 @@ vty_cat_file(vty vty, qpath path, const char* desc)
} ;
} ;
+ XFREE(MTYPE_TMP, buf) ;
close(fd) ;
return CMD_SUCCESS;
@@ -849,7 +894,7 @@ vty_read_config_first_cmd_special(const char *config_file,
name = qpath_string(path) ;
/* try to open the configuration file */
- conf_fd = uty_vfd_file_open(name, vfd_io_read) ;
+ conf_fd = uty_fd_file_open(name, vfd_io_read, 0) ;
if (conf_fd < 0)
{
@@ -908,8 +953,8 @@ vty_use_backup_config (qpath path)
sav_fd = -1 ;
tmp_fd = -1 ;
- sav_fd = uty_vfd_file_open(qpath_string(temp),
- vfd_io_read | vfd_io_blocking) ;
+ sav_fd = uty_fd_file_open(qpath_string(temp),
+ vfd_io_read | vfd_io_blocking, 0) ;
/* construct a temporary file and copy "<fullpath.sav>" to it. */
qpath_extend_str(temp, ".XXXXXX") ;
@@ -950,7 +995,7 @@ vty_use_backup_config (qpath path)
qpath_free(temp) ; /* done with the qpath */
if (ok)
- return uty_vfd_file_open(qpath_string(path), vfd_io_read) ;
+ return uty_fd_file_open(qpath_string(path), vfd_io_read, 0) ;
errno = err ;
return -1 ;
@@ -977,95 +1022,42 @@ vty_use_backup_config (qpath path)
* so all commands are executed directly.
*/
static void
-vty_read_config_file (int fd, const char* name, cmd_command first_cmd,
+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 ;
qtime_t taking ;
- /* Set up configuration file reader VTY -- which buffers all output */
- vty = vty_open(VTY_CONFIG_READ);
- vty->node = CONFIG_NODE;
-
- /* Make sure we have a suitable buffer, and set vty->buf to point at
- * it -- same like other command execution.
- */
- qs_need(&vty->vio->clx, VTY_BUFSIZ) ;
- vty->buf = qs_chars(&vty->vio->clx) ;
-
- taking = qt_get_monotonic() ;
-
- /* Execute configuration file */
- ret = config_from_file (vty, confp, first_cmd, &vty->vio->clx,
- ignore_warnings) ;
-
- taking = (qt_get_monotonic() - taking) / (QTIME_SECOND / 1000) ;
-
- zlog_info("Finished reading configuration in %d.%dsecs%s",
- (int)(taking / 1000), (int)(taking % 1000),
- (ret == CMD_SUCCESS) ? "." : " -- FAILED") ;
-
- VTY_LOCK() ;
-
vty = vty_config_read_open(fd, name, full_lex) ;
- vty_cmd_loop_prepare(vty) ;
-
- taking = qt_get_monotonic() ;
+ if (vty_cmd_config_loop_prepare(vty))
+ {
+ taking = qt_get_monotonic() ;
- ret = cmd_read_config(vty, first_cmd, ignore_warnings) ;
+ ret = cmd_read_config(vty, first_cmd, ignore_warnings) ;
- taking = (qt_get_monotonic() - taking) / (QTIME_SECOND / 1000) ;
+ taking = (qt_get_monotonic() - taking) / (QTIME_SECOND / 1000) ;
- zlog_info("Finished reading configuration '%s' in %d.%d secs%s",
+ zlog_info("Finished reading configuration '%s' in %d.%d secs%s",
name, (int)(taking / 1000), (int)(taking % 1000),
(ret == CMD_SUCCESS) ? "." : " -- FAILED") ;
+ }
+ else
+ {
+ /* This should be impossible -- before reading the configuration,
+ * all other vty are closed.
+ */
+ zlog_err("%s: Failed to gain config capability !", __func__) ;
+ ret = CMD_ERROR ;
+ } ;
- vty_cmd_loop_exit(vty) ;
+ vty_cmd_loop_exit(vty) ; /* the vty is released */
if (ret != CMD_SUCCESS)
exit(1) ;
} ;
-/*------------------------------------------------------------------------------
- * Push the given fd as the VOUT_CONFIG.
- *
- * 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)
-{
- vty_io vio ;
- vio_vf vf ;
-
- VTY_LOCK() ;
-
- vio = vty->vio ;
-
- 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 pop the VOUT_CONFIG.
- */
-extern cmd_return_code_t
-vty_close_config_write(struct vty* vty, bool final)
-{
- cmd_return_code_t ret ;
- VTY_LOCK() ;
-
- ret = uty_vout_pop(vty->vio, final) ;
-
- VTY_UNLOCK() ;
-
- return ret ;
-} ;
-
/*==============================================================================
* Commands
*
@@ -1391,7 +1383,7 @@ vty_config_write (struct vty *vty)
{
vty_io vio ;
- VTY_LOCK() ; /* while accessing the host.xxx */
+ VTY_LOCK() ; /* while accessing the host.xx */
vio = vty->vio ;
@@ -1483,8 +1475,11 @@ vty_is_shell_client (struct vty *vty)
#endif
-void
-vty_set_lines(struct vty *vty, int lines)
+/*------------------------------------------------------------------------------
+ * When terminal length is set, change the current CLI setting, if required.
+ */
+extern void
+vty_set_lines(vty vty, int lines)
{
VTY_LOCK() ;
@@ -1592,7 +1587,8 @@ DEFUN (delay_secs,
a = (rand() % 4) + 1 ;
} ;
- *q++ = '\n' ;
+ if (n != 0)
+ *q++ = '\n' ;
*q++ = '\0' ;
vty_out(vty, buf) ;
diff --git a/lib/vty_cli.c b/lib/vty_cli.c
index cf85fb6c..83f156ef 100644
--- a/lib/vty_cli.c
+++ b/lib/vty_cli.c
@@ -40,6 +40,12 @@
#include "memory.h"
+/*==============================================================================
+ * This is the CLI which is part of a VIN_TERM vf.
+ *
+ * With a few exceptions, the externs here are called by vty_io_term.
+ */
+
/*------------------------------------------------------------------------------
* Essential stuff.
*/
@@ -49,11 +55,9 @@ static const char* telnet_newline = TELNET_NEWLINE ;
/*==============================================================================
* Construct and destroy CLI object
- *
- *
- *
*/
-static bool uty_cli_iac_callback(keystroke_iac_callback_args) ;
+
+static bool uty_cli_callback(keystroke_callback_args) ;
static void uty_cli_update_more(vty_cli cli) ;
static void uty_cli_cancel(vty_cli cli) ;
@@ -79,67 +83,64 @@ uty_cli_new(vio_vf vf)
/* Zeroising has initialised:
*
- * vf = NULL -- set below
- *
- * 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()
+ * vf = NULL -- set below
*
- * width = 0 -- unknown width ) Telnet window size
- * height = 0 -- unknown height )
+ * 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()
*
- * lines = 0 -- set below
- * lines_set = false -- set below
+ * width = 0 -- unknown width ) Telnet window size
+ * height = 0 -- unknown height )
*
- * monitor = false -- not a "monitor"
- * monitor_busy = false -- so not busy either
+ * lines = 0 -- set below
+ * lines_set = false -- set below
*
- * v_timeout = ???
+ * monitor = false -- not a "monitor"
+ * monitor_busy = false -- so not busy either
*
- * key_stream = X -- set below
+ * key_stream = X -- set below
*
- * drawn = false
- * dirty = false
+ * drawn = false
*
- * tilde_prompt = false -- tilde prompt is not drawn
- * tilde_enabled = false -- set below if ! multi-threaded
+ * 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
+ * prompt_len = 0 -- not drawn, in any case
+ * extra_len = 0 -- not drawn, in any case
*
- * prompt_node = NULL_NODE -- so not set !
- * prompt_gen = 0 -- not a generation number
- * prompt_for_node = NULL -- not set, yet
+ * prompt_node = NULL_NODE -- so not set !
+ * prompt_gen = 0 -- not a generation number
+ * prompt_for_node = NULL -- not set, yet
*
- * dispatched = false -- see below
- * in_progress = false -- see below
- * blocked = false -- see below
- * paused = false
+ * dispatched = false -- see below
+ * in_progress = false -- see below
+ * blocked = false -- see below
+ * paused = false
*
- * out_active = false
- * flush = false
+ * mon_active = false
+ * out_active = false
*
- * more_wait = false
- * more_enter = false
+ * more_wait = false
+ * more_enter = false
*
- * more_enabled = false -- not in "--More--" state
+ * more_enabled = false -- not in "--More--" state
*
- * pause_timer = NULL -- set below if multi-threaded
+ * pause_timer = NULL -- set below if multi-threaded
*
- * context = NULL -- see below
- * context_auth = false -- set by uty_cli_want_command()
+ * context = NULL -- see below
+ * auth_context = 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 = NULL -- see below
+ * to_do = cmd_do_nothing
+ * cl = NULL qstring -- set below
+ * cls = NULL qstring -- set below
+ * clx = NULL qstring -- set below
+ * dispatch = all zeros -- nothing to dispatch
*
- * cbuf = NULL -- see below
+ * cbuf = NULL -- see below
*
- * olc = NULL -- see below
+ * olc = NULL -- see below
*/
confirm(NULL_NODE == 0) ; /* prompt_node & node */
confirm(cmd_do_nothing == 0) ; /* to_do */
@@ -148,9 +149,11 @@ uty_cli_new(vio_vf vf)
cli->vf = vf ;
/* Allocate and initialise a keystroke stream TODO: CSI ?? */
- cli->key_stream = keystroke_stream_new('\0', uty_cli_iac_callback, cli) ;
+ cli->key_stream = keystroke_stream_new('\0', "\x03", uty_cli_callback, cli) ;
- /* Set up cl and clx qstrings and the command line output fifo */
+ /* Set up cl, cls and clx qstrings, the command line output fifo and
+ * the output line control.
+ */
cli->cl = qs_new(120) ; /* reasonable line length */
cli->cls = qs_new(120) ;
cli->clx = qs_new(120) ;
@@ -167,15 +170,19 @@ uty_cli_new(vio_vf vf)
uty_cli_set_lines(cli, host.lines, false) ;
uty_cli_update_more(cli) ; /* and update the olc etc to suit. */
- /* Enable "~ " prompt and pause timer if multi-threaded */
+ /* Enable "~ " prompt and pause timer if multi-threaded
+ *
+ * TODO decide whether to ditch the '~' prompt, given the priority given
+ * to commands.
+ */
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 ;
+ cli->tilde_enabled = vty_multi_nexus && false ;
} ;
- /* Ready to be started -- paused, out_active, flush & more_wait are false.
+ /* Ready to be started -- paused, out_active & 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.
@@ -229,7 +236,6 @@ uty_cli_close(vty_cli cli, bool final)
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. */
{
@@ -242,18 +248,18 @@ uty_cli_close(vty_cli cli, bool final)
/* Empty the keystroke handling. */
cli->key_stream = keystroke_stream_free(cli->key_stream) ;
- /* Can discard active command line if not dispatched TODO ?? */
+ /* Can discard active command line if not dispatched */
if (!cli->dispatched || final)
cli->clx = qs_reset(cli->clx, free_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) ;
+ 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 ;
+ cli->tilde_enabled = false ;
/* If final, free the CLI object. */
if (final)
@@ -272,15 +278,43 @@ uty_cli_close(vty_cli cli, bool final)
} ;
/*------------------------------------------------------------------------------
- * The keystroke iac callback function.
+ * The keystroke callback function.
+ *
+ * This deals with interrupts and IAC sequences that should be dealt with as
+ * soon as they are read -- not stored in the keystroke stream for later
+ * processing.
*
- * 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 <=> dealt with keystroke, do not store in keystroke stream
*/
static bool
-uty_cli_iac_callback(keystroke_iac_callback_args)
+uty_cli_callback(keystroke_callback_args /* void* context, keystroke stroke */)
{
- return uty_telnet_command(((vty_cli)context)->vf, stroke, true) ;
+ vty_cli cli = context ;
+
+ switch (stroke->type)
+ {
+ case ks_char: /* character -- uint32_t */
+ if ((cli->in_progress || cli->out_active) && !cli->more_wait)
+ {
+ vty_io vio = cli->vf->vio ;
+
+ uty_cmd_signal(vio, CMD_CANCEL) ; /* ^C */
+
+ return true ;
+ } ;
+
+ return false ;
+
+ case ks_iac: /* Telnet command */
+ return uty_telnet_command(cli->vf, stroke, true) ;
+
+ case ks_esc: /* ESC xx */
+ case ks_csi: /* ESC [ ... or CSI ... */
+ zabort("invalid interrupt keystroke type") ;
+
+ default:
+ zabort("unknown keystroke type") ;
+ } ;
} ;
/*------------------------------------------------------------------------------
@@ -407,8 +441,8 @@ uty_cli_update_more(vty_cli cli)
* (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.
+ * The command may be an in_pipe, so there may be many
+ * commands to be completed before the CLI level command is.
*
* or: the CLI has been closed.
*
@@ -420,18 +454,13 @@ uty_cli_update_more(vty_cli cli)
* out_active -- the command output FIFO is being emptied.
* (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.
+ * everything is written away and in_progress is not 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
@@ -444,16 +473,16 @@ uty_cli_update_more(vty_cli cli)
*
* The following are the valid combinations:
*
- * 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,
+ * dsp:inp:bkd: oa:mwt:men:
+ * ---:---:---:---:---:---:-----------------------------------------
+ * 0 : 0 : 0 : 0 : 0 : 0 : collecting a new command
+ * 1 : 0 : 0 : 0 : 0 : 0 : waiting for command to be fetched
+ * 1 : 1 : 0 : X : 0 : 0 : command fetched and running
+ * 1 : 1 : 1 : X : 0 : 0 : waiting for command to complete
+ * 0 : 0 : 0 : 1 : 0 : 0 : waiting for command output to finish
+ * 1 : X : X : 1 : 1 : 1 : waiting for "--more--" to start
+ * 1 : X : X : 1 : 1 : 0 : waiting for "--more--" response
+ * 1 : 1 : 1 : 1 : 0 : 0 : waiting for command to complete,
* after the CLI has been closed
*
* There are two output FIFOs:
@@ -466,20 +495,21 @@ uty_cli_update_more(vty_cli cli)
* 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, or if it pushes the output
- * explicitly. Note that where the CLI level command is a pipe-in, the first
+ * explicitly. Note that where the CLI level command is an in_pipe, 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.
- *
+ * until a newline arrives, or the output is because in_progress is false -- 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
- * invokes read_ready.
+ * Note that is read ready until the keystroke stream hits eof. So any input
+ * will be hoovered up as soon as it is available. The CLI process is driven
+ * mostly by write_ready, except for when all output is complete and the
+ * input keystroke buffer has been emptied.
*
* 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
@@ -593,6 +623,7 @@ 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) ;
+static void uty_cli_goto_end_if_drawn(vty_cli cli) ;
/*------------------------------------------------------------------------------
* Standard CLI for VTY_TERM -- if not blocked, runs until:
@@ -660,11 +691,12 @@ uty_cli_standard(vty_cli cli)
/* 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.
+ * not "tilde_enabled" -- which is the case if single threaded, and may be
+ * so for other reasons.
*
* If the keystroke stream is not empty, use write_ready as a proxy for
- * CLI ready -- no point doing anything until any buffered.
+ * CLI ready -- no point doing anything until any buffered output has been
+ * written away.
*
* If command prompt has been redrawn, need to kick writer to deal with
* that -- will reenter to then process any keystrokes.
@@ -685,7 +717,7 @@ uty_cli_standard(vty_cli cli)
* command completes, or something else happens).
*
* Note that if a command has been dispatched, and is !tilde_enabled, then
- * will now be blocked.
+ * will now be blocked, so won't be here.
*/
if (cli->dispatched && !cli->out_active && !cli->drawn)
uty_cli_pause_start(cli) ;
@@ -718,7 +750,7 @@ uty_cli_dispatch(vty_cli cli)
VTY_ASSERT_LOCKED() ;
/* About to dispatch a command, so must be in the following state. */
- assert(!cli->dispatched && !cli->in_progress
+ qassert(!cli->dispatched && !cli->in_progress
&& !cli->blocked && !cli->out_active) ;
qassert(cli->context->node == vio->vty->exec->context->node) ;
@@ -735,15 +767,12 @@ uty_cli_dispatch(vty_cli cli)
cli->to_do = cmd_do_nothing ; /* clear */
qs_clear(cli->cl) ; /* set cl empty */
- /* Reset the command output FIFO and line_control TODO */
-//uty_out_clear(cli->vio) ; /* clears FIFO and line control */
-
- /* Dispatch command */
+ /* Dispatch command */
if (cli->auth_context)
to_do_now |= cmd_do_auth ;
else
{
- /* All other nodes... */
+ /* All other nodes... */
switch (to_do_now)
{
case cmd_do_nothing:
@@ -827,8 +856,21 @@ uty_cli_command(vty_cli cli)
*
* May be in more_wait state -- so avoids touching that.
*
- * If not in_progress, then if dispatched, that is a new command ready to pass
+ * If not in_progress, then if dispatched, we have a new command ready to pass
* to the command loop -- which we do here, and set cli->in_progress.
+ *
+ * Returns: CMD_SUCCESS -- the given action has been set to next command
+ * or: CMD_WAITING -- no command available (yet)
+ *
+ * Note that for the CLI eof and read time-out are handled as cmd_do_eof and
+ * cmd_do_timed_out -- so will float through as CMD_SUCCESS and be processed
+ * as commands.
+ *
+ * Write I/O errors and time-outs are signalled by uty_vf_error(), and
+ * therefore caught in the command loop.
+ *
+ * Read I/O errors are signalled by uty_vf_error(). Read timeout is treated
+ * as above.
*/
extern cmd_return_code_t
uty_cli_want_command(vty_cli cli, cmd_action action, cmd_context context)
@@ -851,18 +893,17 @@ uty_cli_want_command(vty_cli cli, cmd_action action, cmd_context context)
cli->auth_context = ( (cli->context->node == AUTH_NODE)
|| (cli->context->node == AUTH_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 is owned by command output, then when the buffers
+ * empty, and in_progress is seen to be false, out_active will be
+ * cleared.
*
- * If the output side is not owned by command output, wipe any temporary
- * prompt.
+ * If the output side is not owned by command output, rewrite the
+ * command line, so that prompt is up to date and visible.
*
* 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
+ if (!cli->out_active)
uty_cli_draw(cli) ;
uty_term_set_readiness(cli->vf, write_ready) ;
@@ -916,7 +957,7 @@ uty_cli_enter_more_wait(vty_cli cli)
{
VTY_ASSERT_LOCKED() ;
- assert(cli->out_active && !cli->more_wait && !cli->drawn) ;
+ qassert(cli->out_active && !cli->more_wait && !cli->drawn) ;
cli->more_wait = true ; /* new state */
cli->more_enter = true ; /* drawing the "--more--" etc. */
@@ -925,10 +966,19 @@ uty_cli_enter_more_wait(vty_cli cli)
/*------------------------------------------------------------------------------
* Handle the "--more--" state.
*
- * Deals with the first stage if cli_blocked.
+ * If is paused or the monitor is active, do nothing -- those override the
+ * "--more--" state.
+ *
+ * If more_enter is set, then need to make sure the "--more--" prompt is
+ * written, and that any keystrokes which arrived before the prompt is
+ * written are taken into the keystroke stream -- so not stolen.
*
- * Tries to steal a keystroke, and when succeeds wipes the "--more--"
- * prompt and exits cli_more_wait -- and may cancel all outstanding output.
+ * Note that the "--more--" state may be interrupted by monitor output,
+ * which, once complete, sets more_enter again.
+ *
+ * If more_enter is not set, tries to steal a keystroke, and if 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.
*
@@ -940,7 +990,7 @@ static vty_readiness_t
uty_cli_more_wait(vty_cli cli)
{
keystroke_t steal ;
- bool cancel ;
+ bool cancel, stolen ;
VTY_ASSERT_LOCKED() ;
@@ -954,7 +1004,8 @@ uty_cli_more_wait(vty_cli cli)
{
int get ;
- uty_cli_draw_if_required(cli) ; /* draw the "--more--" */
+ if (!cli->drawn)
+ uty_cli_draw(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.
@@ -967,52 +1018,36 @@ uty_cli_more_wait(vty_cli cli)
/* empty the input buffer into the keystroke stream */
do
{
- get = uty_term_read(cli->vf, NULL) ;
+ get = uty_term_read(cli->vf) ;
} while (get > 0) ;
-
- return read_ready ;
} ;
- /* Go through the "--more--" process, unless closing */
- /* The read fetches a reasonable lump from the I/O -- so if there
- * is a complete keystroke available, expect to get it.
+ /* Try to get a stolen keystroke. If the keystroke stream has hit
+ * EOF (for any reason, including error or timed-out), will get a ks_null
+ * stolen keystroke.
*
- * If no complete keystroke available to steal, returns ks_null.
- *
- * If has hit EOF or timeout (or error etc), returns knull_eof.
+ * If nothing to be stolen exit.
*/
- uty_term_read(cli->vf, steal) ;
+ stolen = keystroke_steal(cli->key_stream, 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.
+ if (!stolen)
+ return read_ready ;
+
+ /* Something has been stolen, so exit "--more--" state, and continue
+ * or cancel.
*/
cancel = false ;
switch(steal->type)
{
- 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 ;
-
- case knull_eof:
- case knull_timed_out:
- cancel = true ;
- break ;
-
- default:
- break ;
- } ;
+ case ks_null: /* at EOF for whatever reason */
+ cancel = true ;
break ;
case ks_char:
switch (steal->value)
{
case CONTROL('C'):
+ case CONTROL('Z'):
case 'q':
case 'Q':
cancel = true ;
@@ -1023,22 +1058,35 @@ uty_cli_more_wait(vty_cli cli)
} ;
break ;
+ case ks_esc:
+ cancel = steal->value == '\x1B' ;
+ break ;
+
default:
break ;
} ;
/* End of "--more--" process
*
- * Wipe out the prompt (unless "cancel") and update state.
+ * If cancelling, make sure is at end of "--more--" prompt, and then
+ * clear the "drawn". The cancel process will append a " ^C\n" !
+ *
+ * If not cancelling, wipe out the prompt and update state.
*
* Return write_ready to tidy up the screen and, unless cleared, write
* some more.
*/
if (cancel)
{
- uty_out_clear(cli->vf->vio) ;
- vio_lc_clear(cli->olc) ; /* clear & reset counter */
- uty_cli_cancel(cli) ;
+ vio_fifo_clear(cli->vf->obuf, false) ;
+ vio_lc_clear(cli->olc) ; /* clear & reset counter */
+
+ uty_cli_goto_end_if_drawn(cli) ;
+ cli->drawn = false ;
+
+ qassert(cli->out_active) ;
+
+ uty_cmd_signal(cli->vf->vio, CMD_CANCEL) ; /* ^C */
}
else
{
@@ -1046,8 +1094,8 @@ uty_cli_more_wait(vty_cli cli)
uty_cli_wipe(cli, 0) ;
} ;
- cli->more_wait = false ; /* exit more_wait */
- cli->more_enter = false ; /* tidy */
+ cli->more_wait = false ; /* exit more_wait */
+ cli->more_enter = false ; /* tidy */
return write_ready ;
} ;
@@ -1147,6 +1195,7 @@ static cli_rep telnet_stars = "********************************" ;
CONFIRM(sizeof(telnet_spaces) == (sizeof(cli_rep_char) + 1)) ;
CONFIRM(sizeof(telnet_dots) == (sizeof(cli_rep_char) + 1)) ;
+CONFIRM(sizeof(telnet_stars) == (sizeof(cli_rep_char) + 1)) ;
static void uty_cli_write_n(vty_cli cli, cli_rep_char chars, int n) ;
@@ -1166,15 +1215,6 @@ uty_cli_out(vty_cli cli, const char *format, ...)
} ;
/*------------------------------------------------------------------------------
- * Completely empty the cli command buffer
- */
-extern void
-uty_cli_out_clear(vty_cli cli)
-{
- vio_fifo_clear(cli->cbuf, true) ;
-} ;
-
-/*------------------------------------------------------------------------------
* CLI VTY output -- cf write()
*/
extern void
@@ -1221,12 +1261,11 @@ uty_cli_write_s(vty_cli cli, const char *str)
/*==============================================================================
* Prompts and responses
*/
-static void uty_cli_goto_end_if_drawn(vty_cli cli) ;
/*------------------------------------------------------------------------------
* Send newline to the console.
*
- * Clears the cli_drawn and the cli_dirty flags.
+ * Clears the cli_drawn flag.
*/
extern void
uty_cli_out_newline(vty_cli cli)
@@ -1235,7 +1274,6 @@ uty_cli_out_newline(vty_cli cli)
uty_cli_write(cli, telnet_newline, 2) ;
cli->drawn = false ;
- cli->dirty = false ;
} ;
/*------------------------------------------------------------------------------
@@ -1263,22 +1301,19 @@ 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
+ * Clears cli->drawn.
*/
static void
uty_cli_cancel(vty_cli cli)
{
- if (cli->drawn || cli->dirty)
+ if (cli->drawn)
{
uty_cli_goto_end_if_drawn(cli) ;
+
uty_cli_write_s(cli, " ^C" TELNET_NEWLINE) ;
- } ;
- cli->drawn = false ;
- cli->dirty = false ;
+ cli->drawn = false ;
+ } ;
} ;
/*------------------------------------------------------------------------------
@@ -1404,24 +1439,6 @@ uty_cli_wipe(vty_cli cli, int len)
/* Nothing there any more */
cli->drawn = false ;
- cli->dirty = false ;
-} ;
-
-/*------------------------------------------------------------------------------
- * 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_cli cli)
-{
- if (cli->drawn)
- return false ;
-
- uty_cli_draw(cli) ;
-
- return true ;
} ;
/*------------------------------------------------------------------------------
@@ -1444,7 +1461,6 @@ uty_cli_draw_if_required(vty_cli cli)
* the current prompt may be invalid.
*
* Sets: cli->drawn = true
- * cli->dirty = false
* cli->prompt_len = length of prompt used
* cli->extra_len = 0
*/
@@ -1455,9 +1471,6 @@ uty_cli_draw(vty_cli cli)
size_t l_len ;
int p_len ;
- if (cli->dirty)
- uty_cli_out_newline(cli) ; /* clears cli_dirty and cli_drawn */
-
/* Sort out what the prompt is. */
if (cli->vf->vin_state != vf_open)
{
@@ -1572,15 +1585,18 @@ 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.
*
- * Expects the command line to have been drawn.
+ * Expects the command line to have been drawn, so that what is in cli->cl
+ * is what is on the screen.
*
* Process keystrokes until run out of stuff to do, or have a "command line"
- * that must now be executed.
+ * that must now be executed. Updates cli->cl.
*
* Processes the contents of the keystroke stream. If exhausts that, will set
* ready to read and return. (To give some "sharing".)
*
* Returns: cmd_do_xxxx
+ *
+ * Note that will return cmd_do_eof or cmd_do_timed_out any number of times.
*/
static cmd_do_t
uty_cli_process(vty_cli cli)
@@ -1589,7 +1605,7 @@ uty_cli_process(vty_cli cli)
uint8_t u ;
cmd_do_t to_do ;
- qs_copy(cli->cls, cli->cl) ;
+ qs_copy(cli->cls, cli->cl) ; /* current screen line */
assert(cli->drawn) ;
@@ -1776,6 +1792,9 @@ uty_cli_process(vty_cli cli)
* Update the command line to reflect the difference between old line and the
* new line.
*
+ * cli->cls is the old line (currently on screen)
+ * cli->cl is the new line (possible changed in some way)
+ *
* Leave the screen cursor at the given required cursor position.
*/
static void
@@ -1836,10 +1855,9 @@ uty_cli_update_line(vty_cli cli, uint rc)
} ;
/* Now move cursor to the required cursor position */
- if (sc > rc)
+ if (sc > rc)
uty_cli_write_n(cli, telnet_backspaces, sc - rc) ;
-
- if (sc < rc) /* => lines unchanged, but cursor moved */
+ else if (sc < rc) /* => lines unchanged, but cursor moved */
uty_cli_write(cli, np + sc, rc - sc) ;
} ;
@@ -1849,7 +1867,12 @@ uty_cli_update_line(vty_cli cli, uint rc)
*
* Similar to uty_cli_auth, except accepts a limited number of keystrokes.
*
+ * Does not accept cursor moves, so does not do forwards delete, and ^D means
+ * exit.
+ *
* Returns: cmd_do_xxxx
+ *
+ * Note that will return cmd_do_eof or cmd_do_timed_out any number of times.
*/
static cmd_do_t
uty_cli_auth(vty_cli cli)
@@ -1944,9 +1967,9 @@ uty_cli_auth(vty_cli cli)
nlen = qs_len_nn(cli->cl) ;
- if (nlen < olen)
+ if (nlen < olen)
uty_cli_out_wipe_n(cli, nlen - olen) ;
- if (nlen > olen)
+ else if (nlen > olen)
uty_cli_write_n(cli, telnet_stars, nlen - olen) ;
return to_do ;
@@ -1955,11 +1978,22 @@ uty_cli_auth(vty_cli cli)
/*------------------------------------------------------------------------------
* Fetch next keystroke, reading from the file if required.
*
- * Returns: cmd_do_t
+ * Returns: cmd_do_keystroke: have a keystroke -- stroke != ks_null
+ * cmd_do_eof : eof in keystream -- stroke == knull_eof
+ * cmd_do_timed_out: timed_ keystream -- stroke == knull_timed_out
+ * cmd_do_nothing : nothing available -- stroke == knull_not_eof
+ *
+ * Note that will return cmd_do_eof or cmd_do_timed_out any number of times.
+ *
+ * Note that any I/O error is signalled to the command loop, and is passed
+ * out of here as "nothing available". If the command loop is running, it
+ * will see the CMD_IO_ERROR signal and deal with the error.
*/
static cmd_do_t
uty_cli_get_keystroke(vty_cli cli, keystroke stroke)
{
+ qassert(cli->vf->vin_state == vf_open) ;
+
while (1)
{
if (keystroke_get(cli->key_stream, stroke))
@@ -1967,7 +2001,7 @@ uty_cli_get_keystroke(vty_cli cli, keystroke stroke)
if (stroke->flags != 0)
{
/* TODO: deal with broken keystrokes */
- }
+ } ;
if (stroke->type != ks_iac)
return cmd_do_keystroke ; /* have a keystroke */
@@ -1979,7 +2013,7 @@ uty_cli_get_keystroke(vty_cli cli, keystroke stroke)
{
int get ;
- assert(stroke->type == ks_null) ;
+ qassert(stroke->type == ks_null) ;
switch (stroke->value)
{
@@ -1997,28 +2031,29 @@ uty_cli_get_keystroke(vty_cli cli, keystroke stroke)
break ;
} ;
- get = uty_term_read(cli->vf, NULL) ; /* sets eof in key_stream
+ get = uty_term_read(cli->vf) ; /* sets eof in key_stream
if hit eof or error */
- if (get <= 0)
+ if ((get == 0) || (get == -1))
return cmd_do_nothing ;
} ;
} ;
} ;
/*==============================================================================
- * Command line operations
+ * Command line operations.
+ *
+ * These all affect the given command line, only. The effect on the screen
+ * is taken care of elsewhere -- see uty_cli_update_line().
*/
/*------------------------------------------------------------------------------
* Insert 'n' characters at current position in the command line, leaving
* cursor after the inserted characters.
- *
- * NB: assumes line will be updated by uty_cli_update_line()
*/
static void
uty_cli_insert(qstring cl, const char* chars, int n)
{
- assert((qs_cp_nn(cl) <= qs_len_nn(cl)) && (n >= 0)) ;
+ qassert((qs_cp_nn(cl) <= qs_len_nn(cl)) && (n >= 0)) ;
if (n > 0)
{
@@ -2030,13 +2065,11 @@ uty_cli_insert(qstring cl, const char* chars, int 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)) ;
+ qassert((qs_cp_nn(cl) <= qs_len_nn(cl)) && (n >= 0) && (m >= 0)) ;
qs_replace(cl, m, chars, n) ;
@@ -2047,8 +2080,6 @@ uty_cli_replace(qstring cl, int m, const char* chars, int 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(qstring cl, int n)
@@ -2059,7 +2090,7 @@ uty_cli_forwards(qstring cl, int n)
if (have < n)
n = have ;
- assert(n >= 0) ;
+ qassert(n >= 0) ;
qs_move_cp_nn(cl, n) ;
@@ -2070,8 +2101,6 @@ uty_cli_forwards(qstring cl, 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(qstring cl, int n)
@@ -2079,7 +2108,7 @@ uty_cli_backwards(qstring cl, int n)
if ((int)qs_cp_nn(cl) < n)
n = qs_cp_nn(cl) ;
- assert(n >= 0) ;
+ qassert(n >= 0) ;
qs_move_cp_nn(cl, -n) ;
@@ -2089,23 +2118,18 @@ uty_cli_backwards(qstring cl, int n)
/*------------------------------------------------------------------------------
* Move forwards (if n > 0) or backwards (if n < 0) -- stop at start or end of
* line.
- *
- * NB: assumes line will be updated by uty_cli_update_line()
*/
static void
uty_cli_move(qstring cl, int n)
{
- if (n < 0)
+ if (n < 0)
uty_cli_backwards(cl, -n) ;
-
- if (n > 0)
+ else if (n > 0)
uty_cli_forwards(cl, +n) ;
} ;
/*------------------------------------------------------------------------------
* Delete 'n' characters -- forwards -- stop at end of line.
- *
- * NB: assumes line will be updated by uty_cli_update_line()
*/
static void
uty_cli_del_forwards(qstring cl, int n)
@@ -2116,16 +2140,14 @@ uty_cli_del_forwards(qstring cl, int n)
if (have < n)
n = have ; /* cannot delete more than have */
- assert(n >= 0) ;
+ qassert(n >= 0) ;
if (n > 0)
qs_delete(cl, n) ;
}
/*------------------------------------------------------------------------------
- * Delete 'n' characters before the point.
- *
- * NB: assumes line will be updated by uty_cli_update_line()
+ * Delete 'n' characters before the point -- stopping at start of line.
*/
static void
uty_cli_del_backwards(qstring cl, int n)
@@ -2135,8 +2157,6 @@ uty_cli_del_backwards(qstring cl, int n)
/*------------------------------------------------------------------------------
* Move to the beginning of the line.
- *
- * NB: assumes line will be updated by uty_cli_update_line()
*/
static void
uty_cli_bol(qstring cl)
@@ -2146,8 +2166,6 @@ uty_cli_bol(qstring cl)
/*------------------------------------------------------------------------------
* Move to the end of the line.
- *
- * NB: assumes line will be updated by uty_cli_update_line()
*/
static void
uty_cli_eol(qstring cl)
@@ -2176,10 +2194,10 @@ uty_cli_word_forwards_delta(qstring cl)
tp = cp ;
- while ((tp < ep) && (*tp != ' ')) /* step over spaces */
+ while ((tp < ep) && (*tp != ' '))
++tp ;
- while ((tp < ep) && (*tp == ' ')) /* step to space */
+ while ((tp < ep) && (*tp == ' '))
++tp ;
return tp - cp ;
@@ -2189,8 +2207,6 @@ uty_cli_word_forwards_delta(qstring cl)
* 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 void
uty_cli_word_forwards(qstring cl)
@@ -2203,8 +2219,8 @@ uty_cli_word_forwards(qstring cl)
*
* 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.
+ * Steps back over spaces, and then until next (backwards) character is space,
+ * or hits start of line.
*/
static int
uty_cli_word_backwards_delta(qstring cl)
@@ -2234,8 +2250,6 @@ uty_cli_word_backwards_delta(qstring cl)
*
* Moves past any spaces, then move back until next (backwards) character is
* space or start of line.
- *
- * NB: assumes line will be updated by uty_cli_update_line()
*/
static void
uty_cli_word_backwards(qstring cl)
@@ -2247,8 +2261,6 @@ uty_cli_word_backwards(qstring cl)
* Delete to end of word -- forwards.
*
* Deletes any leading spaces, then deletes upto next space or end of line.
- *
- * NB: assumes line will be updated by uty_cli_update_line()
*/
static void
uty_cli_del_word_forwards(qstring cl)
@@ -2260,8 +2272,6 @@ uty_cli_del_word_forwards(qstring cl)
* Delete to start of word -- backwards.
*
* Deletes any trailing spaces, then deletes upto next space or start of line.
- *
- * NB: assumes line will be updated by uty_cli_update_line()
*/
static void
uty_cli_del_word_backwards(qstring cl)
@@ -2271,8 +2281,6 @@ uty_cli_del_word_backwards(qstring cl)
/*------------------------------------------------------------------------------
* Kill rest of line from current point.
- *
- * NB: assumes line will be updated by uty_cli_update_line()
*/
static void
uty_cli_del_to_eol(qstring cl)
@@ -2282,8 +2290,6 @@ uty_cli_del_to_eol(qstring cl)
/*------------------------------------------------------------------------------
* Kill line from the beginning.
- *
- * NB: assumes line will be updated by uty_cli_update_line()
*/
static void
uty_cli_clear_line(qstring cl)
@@ -2293,9 +2299,9 @@ uty_cli_clear_line(qstring cl)
} ;
/*------------------------------------------------------------------------------
- * Transpose chars before or at the point.
+ * Transpose current character with previous one, and step forward one.
*
- * NB: assumes line will be updated by uty_cli_update_line()
+ * If at end of line, transpose the last two characters.
*/
static void
uty_cli_transpose_chars(qstring cl)
@@ -2389,7 +2395,8 @@ uty_cli_hist_make(vty_cli cli)
/*------------------------------------------------------------------------------
* Add given command line to the history buffer.
*
- * This is inserting the vty->buf line into the history.
+ * The 'cp' stored with the line is set to be the end of the line, so that is
+ * all ready for when the stored line is used.
*
* Resets hp == h_now.
*/
@@ -2431,10 +2438,7 @@ uty_cli_hist_add (vty_cli cli, qstring clx)
cli->h_repeat = false ; /* latest history is novel */
} ;
- /* Now replace the h_now entry
- *
- * Note that the line inserted in the history has it's 'cp' set to the end of
- * the line -- so that it is there when it comes back out again.
+ /* Now replace the h_now entry -- setting 'cp' to end of line
*/
hist_line = qs_copy(hist_line, clx) ;
qs_set_cp_nn(hist_line, qs_len_nn(hist_line)) ;
@@ -2471,12 +2475,12 @@ uty_cli_hist_use(vty_cli cli, enum hist_step step)
*
* Cannot step forwards from the present.
*
- * before stepping back from the present, take a copy of the current
+ * 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.)
+ * the history, the cursor is set to the end of the line.)
*/
if (hp == cli->h_now)
{
@@ -2553,6 +2557,9 @@ uty_cli_hist_show(vty_cli cli)
/*==============================================================================
* Command Completion and Command Description
*
+ * Any changes to the command line are made to cli->cl. If the command line
+ * is redrawn, updates cli->cls. Otherwise, the screen may need updating to
+ * reflect differences between cli->cl and cli->cls.
*/
static uint uty_cli_help_parse(vty_cli cli) ;
@@ -2576,8 +2583,6 @@ static void uty_cli_help_finish(vty_cli cli) ;
* 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)
@@ -2782,7 +2787,8 @@ uty_cli_complete_list(vty_cli cli, vector item_v)
pad = (str_len < str_width) ? str_width - str_len : 0 ;
uty_cli_write_n(cli, telnet_spaces, pad + 2) ;
- }
+ } ;
+
uty_cli_help_newline(cli) ;
} ;
@@ -2954,7 +2960,7 @@ uty_cli_help_message(vty_cli cli, const char* msg)
* 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.
+ * Clears cli->drawn.
*/
static void
uty_cli_help_newline(vty_cli cli)
@@ -2965,7 +2971,6 @@ uty_cli_help_newline(vty_cli cli)
uty_cli_write(cli, telnet_newline, 2) ;
cli->drawn = false ;
- cli->dirty = false ;
} ;
/*------------------------------------------------------------------------------
diff --git a/lib/vty_cli.h b/lib/vty_cli.h
index 78ccc900..e366ecdf 100644
--- a/lib/vty_cli.h
+++ b/lib/vty_cli.h
@@ -63,9 +63,6 @@ struct vty_cli
bool monitor ;
bool monitor_busy ;
- /* Terminal timeout in seconds -- 0 => none */
- vty_timer_time v_timeout ;
-
/* The incoming stuff */
keystroke_stream key_stream ;
@@ -79,9 +76,7 @@ struct vty_cli
* 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.
+ * tilde_enabled <=> do the "~ " one command line ahead.
*
* If drawn is true, the following are valid:
*
@@ -98,7 +93,6 @@ struct vty_cli
* NB: echo_suppress is only used for password entry.
*/
bool drawn ;
- bool dirty ;
bool tilde_prompt ;
bool tilde_enabled ;
@@ -106,8 +100,6 @@ struct vty_cli
int prompt_len ;
int extra_len ;
- bool echo_suppress ;
-
/* "cache" for prompt -- when node or host name changes, prompt does */
node_type_t prompt_node ;
name_gen_t prompt_gen ;
@@ -130,14 +122,7 @@ struct vty_cli
* 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.
+ * Flag is cleared when obuf is empty, and is !in_progress.
*
* more_wait -- is in "--more--" wait state
* more_enter -- more_wait and waiting for "--more--" prompt to be
@@ -150,7 +135,6 @@ struct vty_cli
bool mon_active ;
bool out_active ;
- bool flush ;
bool more_wait ;
bool more_enter ;
@@ -220,7 +204,6 @@ extern cmd_return_code_t uty_cli_want_command(vty_cli cli, cmd_action action,
extern void uty_cli_out(vty_cli cli, const char *format, ...)
PRINTF_ATTRIBUTE(2, 3) ;
extern void uty_cli_out_newline(vty_cli cli) ;
-extern void uty_cli_out_clear(vty_cli cli) ;
extern void uty_cli_write(vty_cli cli, const char *this, int len) ;
extern void uty_cli_wipe(vty_cli cli, int len) ;
@@ -229,8 +212,6 @@ extern void uty_cli_set_window(vty_cli cli, int width, int height) ;
extern void uty_cli_enter_more_wait(vty_cli cli) ;
extern void uty_cli_exit_more_wait(vty_cli cli) ;
-extern bool uty_cli_draw_if_required(vty_cli cli) ;
-
extern void uty_cli_pre_monitor(vty_cli cli) ;
extern void uty_cli_post_monitor(vty_cli cli) ;
diff --git a/lib/vty_command.c b/lib/vty_command.c
index a63f6f0c..d195f193 100644
--- a/lib/vty_command.c
+++ b/lib/vty_command.c
@@ -39,63 +39,97 @@
#include "qstring.h"
/*==============================================================================
- * Variables etc.
+ * vty_command.c contains functions used by the command processing, where
+ * that interacts with the vty -- in particular vty I/O.
+ *
+ * There are two command loops -- cmd_read_config() and cq_process(). Each
+ * command loop appears to be a thread of control sucking in command lines,
+ * parsing and executing them. In the process, input and output pipes are
+ * opened and closed, and the vty stack grows and shrinks.
+ *
+ * For cmd_read_config() -- see command_execute.c -- the command loop is,
+ * indeed, a thread of control and all the I/O operations are "blocking", so
+ * that any waiting required is done inside the loop.
+ *
+ * For cq_process() -- see command_queue.c -- things are a lot more
+ * complicated: first, I/O is non-blocking, so I/O operations may return
+ * CMD_WAITING and the command loop must exit and be able to restart, when
+ * the I/O completes in the background; second, in a multi-pthread environment
+ * some operations must be performed in the CLI pthread while others must be
+ * performed in the command (the Routing Engine, generally) pthread. (It would
+ * be easier if each cq_process instance were a separate pthread, but we here
+ * implement it as a form of co-routine, driven by messages.)
+ *
+ * The I/O is structured so that all output either completes before the I/O
+ * operation returns, or will be autonomously completed by the background
+ * pselect() process. Only when waiting for input does the loop need to exit
+ * waiting for I/O.
+ *
+ * So a command loop takes the general form:
+ *
+ * loop: fetch command line
+ * parse command line
+ * loop if empty or all comment
+ * reflect command line
+ * deal with any pipe open actions
+ * dispatch command (if any)
+ * push command output to output
+ * loop
+ *
+ * hiatus: deal with issue
+ * loop if OK and not waiting for input
+ * exit command loop
+ *
+ * In the loop, if any operation receives a return code it cannot immediately
+ * deal with, it jumps to the "hiatius". For everything except the command
+ * line fetch this will be some sort of error -- either I/O or command -- or
+ * some external event closing down the loop. For the command line fetch
+ * this may be because must now wait for input, or because the current input
+ * has reached EOF and must now be closed (which may pop things off the
+ * vty stack). In any event, once the issue is dealt with, can leave the
+ * hiatus and return to the top of the loop.
+ *
+ * Note that in hiatus the command loop may be waiting for some output I/O
+ * to complete -- e.g. while closing an output pipe.
+ *
+ * So, most obviously for cq_process(), the loop is a co-routine which exits
+ * and is re-entered at the hiatus. When the loop does exit, it has either
+ * come to a dead stop (for a number of reasons) or it is waiting for input.
+ *
+ * The state of the command loop is vty->vio->state. The main states are:
+ *
+ * vc_running -- somewhere in the command loop, doing things.
+ *
+ * vc_waiting -- waiting for I/O
+ *
+ * When some event (such as some input arriving) occurs it is signalled to
+ * the command loop by uty_cmd_signal(), where the value of the signal is
+ * a CMD_XXXX code. If the loop is in vc_waiting state, the loop can be
+ * re-entered (at the hiatus point) with the return code. Otherwise, what
+ * happens depends on the state and the return code -- see uty_cmd_signal().
+ *
+ * Functions called by the various steps in the command loop will check for
+ * a pending signal, and will force a jump to hiatus -- using CMD_HIATUS return
+ * code. (This is how the command loop may be "interrupted" by, for example
+ * an I/O error detected in the pselect() process.)
*/
/*------------------------------------------------------------------------------
* Prototypes
*/
-
-static void uty_cmd_loop_prepare(vty_io vio) ;
+static bool uty_cmd_loop_prepare(vty_io vio) ;
+static void uty_cmd_stopping(vty_io vio, bool exeunt) ;
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 void uty_cmd_out_cancel(vio_vf vf, bool base) ;
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 bool 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: TODO update !!
- *
- * * uty_cmd_prepare()
- *
- * After opening or closing a vin/vout object, the state of the execution
- * context must be set to reflect the current top of the vin/vout stack.
- *
- * * uty_cmd_dispatch_line()
- *
- * This is for command lines which come from "push" sources, eg the
- * Telnet VTY CLI or the VTYSH.
- *
- * Hands command line over to the vty_cli_nexus message queue.
- *
- * NB: from this point onwards, the vio is vio->cmd_running ! TODO
- *
- * * vty_cmd_fetch_line()
- *
- * This is for command lines which are "pulled" by one of the command
- * execution loops, eg files and pipes. If not in cmd_read_config())
- * can return CMD_WAITING.
- *
- * * vty_cmd_open_in_pipe_file()
- * * vty_cmd_open_in_pipe_shell()
- * * vty_cmd_open_out_pipe_file()
- * * vty_cmd_open_out_pipe_shell()
- *
- * These do the I/O side of the required opens, pushing the new vty_vf
- * onto the vin/vout stack.
- *
- * * vty_cmd_success()
- *
- * When a command returns success, this is called either to push all
- * buffered output to the current vout, or to discard all buffered
- * output (which is what cmd_read_config() does).
- *
- * If required, pops any vout(s).
- *
- *
+ * Starting up, communicating with and closing down a command loop.
*/
/*------------------------------------------------------------------------------
@@ -103,32 +137,47 @@ static void uty_cmd_config_lock_check(struct vty *vty, node_type_t node) ;
*
* Initialise exec object, and copy required settings from the current vin
* and vout.
+ *
+ * Returns: true <=> acquired or did not need config symbol of power
+ * or: false <=> needed but could not acquire symbol of power
*/
-extern void
-vty_cmd_loop_prepare(vty vty)
+extern bool
+vty_cmd_config_loop_prepare(vty vty)
{
+ bool ok ;
+
VTY_LOCK() ;
assert(vty->type == VTY_CONFIG_READ) ;
- uty_cmd_loop_prepare(vty->vio) ; /* by vty->type & vty->node */
+ ok = uty_cmd_loop_prepare(vty->vio) ; /* by vty->type & vty->node */
+
VTY_UNLOCK() ;
+
+ return ok ;
} ;
/*------------------------------------------------------------------------------
* Enter the command_queue command loop.
*/
extern void
-uty_cmd_loop_enter(vty_io vio)
+uty_cmd_queue_loop_enter(vty_io vio)
{
+ bool ok ;
+
VTY_ASSERT_CLI_THREAD_LOCKED() ;
assert(vio->vty->type == VTY_TERMINAL) ;
- uty_cmd_loop_prepare(vio) ; /* by vty->type & vty->node */
+ ok = uty_cmd_loop_prepare(vio) ; /* by vty->type & vty->node */
+
+ if (!ok)
+ uty_out(vio, "%% unable to start in config mode\n") ;
- cq_loop_enter(vio->vty, vio->vty->node != NULL_NODE ? CMD_SUCCESS
- : CMD_CLOSE) ;
+ qassert(vio->state == vc_running) ;
+ cq_loop_enter(vio->vty, (vio->vty->node == NULL_NODE) ? CMD_CLOSE
+ : ok ? CMD_SUCCESS
+ : CMD_WARNING) ;
} ;
/*------------------------------------------------------------------------------
@@ -136,88 +185,123 @@ uty_cmd_loop_enter(vty_io vio)
*
* Initialise cmd_exec object, and its cmd_context -- given vty->type and
* vty->node.
+ *
+ * Returns: true <=> acquired or did not need config symbol of power
+ * or: false <=> needed but could not acquire symbol of power
*/
-static void
+static bool
uty_cmd_loop_prepare(vty_io vio)
{
+ bool ok ;
+
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 ;
+ ok = true ;
if (vio->vty->node > MAX_NON_CONFIG_NODE)
- uty_cmd_config_lock(vio->vty) ; /* TODO cannot fail !? */
+ {
+ ok = uty_cmd_config_lock(vio->vty) ;
+ if (!ok)
+ vio->vty->node = ENABLE_NODE ;
+ } ;
uty_cmd_prepare(vio) ;
+
+ return ok ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * 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->reflect = exec->context->reflect_enabled ;
+ exec->out_suppress = (vio->vty->type == VTY_CONFIG_READ)
+ && (vio->vout_depth == 1)
+ && !exec->reflect ;
} ;
/*------------------------------------------------------------------------------
* Signal to the command loop that some I/O has completed -- successfully, or
- * with some I/O error (including time out).
+ * with some I/O error (including time out), or otherwise.
*
- * If the vio is in vc_waiting state -- so will be exec_hiatus -- send message
- * so that command loop continues.
+ * Accepts the following return codes:
*
- * 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.
+ * CMD_SUCCESS -- if vc_waiting, passed in
+ * otherwise, ignored
*
- * Accepts:
+ * CMD_WAITING -- ignored
*
- * CMD_SUCCESS -- if vc_waiting, passed in.
- * otherwise, ignored
+ * CMD_IO_ERROR -- if vc_waiting, passed in
+ * if vc_running, set signal, unless already CMD_STOP
+ * otherwise, ignored
*
- * CMD_WAITING -- ignored
+ * CMD_CANCEL -- if vc_waiting, passed in
+ * if vc_running, set signal, unless already CMD_STOP
+ * or CMD_IO_ERROR
+ * otherwise, ignored
*
- * CMD_IO_ERROR -- if vc_waiting, passed in
- * if vc_running, set vc_io_error_trap
+ * NB: if sets CMD_CANCEL signal, sets vio->cancel.
*
- * CMD_CLOSE -- if vc_waiting, passed in
- * if vc_running, set vc_close_trap
- * if vc_io_error_trap, set vc_close_trap
+ * if passes CMD_CANCEL in, sets vio->cancel.
*/
extern void
uty_cmd_signal(vty_io vio, cmd_return_code_t ret)
{
VTY_ASSERT_LOCKED() ;
- if (ret == CMD_WAITING)
- return ;
-
- assert((ret == CMD_SUCCESS) || (ret == CMD_IO_ERROR) || (ret == CMD_CLOSE)) ;
+ qassert( (ret == CMD_SUCCESS) || (ret == CMD_WAITING)
+ || (ret == CMD_IO_ERROR)
+ || (ret == CMD_CANCEL) ) ;
switch (vio->state)
{
- case vc_null:
- zabort("invalid vc_null") ;
- break ;
+ case vc_running:
+ if ((ret == CMD_SUCCESS) || (ret == CMD_WAITING))
+ break ; /* Ignored */
- 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 ;
+ if (vio->signal == CMD_STOP)
+ break ; /* Cannot override CMD_STOP */
- case vc_waiting: /* pass in the return code continue */
- vio->state = vc_running ;
- cq_continue(vio->vty, ret) ;
- break ;
+ if (ret != CMD_IO_ERROR)
+ {
+ qassert(ret == CMD_CANCEL) ;
+
+ if (vio->signal == CMD_IO_ERROR)
+ break ; /* Cannot override CMD_IO_ERROR */
+
+ vio->cancel = true ;
+ } ;
+
+ vio->signal = ret ;
- 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. */
+ case vc_waiting: /* pass in the return code */
+ if (ret != CMD_WAITING)
+ {
+ vio->state = vc_running ;
+ if (ret == CMD_CANCEL)
+ vio->cancel = true ;
+
+ cq_continue(vio->vty, ret) ;
+ } ;
break ;
- case vc_closed:
- zabort("invalid vc_closed") ;
+ case vc_stopped: /* ignore everything */
break ;
default:
@@ -227,64 +311,141 @@ uty_cmd_signal(vty_io vio, cmd_return_code_t ret)
} ;
/*------------------------------------------------------------------------------
- * Reset the command loop.
+ * Stop the command loop.
*
- * This is used by uty_close() to try to shut down the command loop.
+ * If the loop is vc_null, it has never started, so is stopped.
*
- * Does nothing if already closed or never got going.
+ * If is non-blocking then revoke any in-flight message (or legacy thread
+ * event), *and* if does that, then the command loop may be stopped.
*
- * "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.
+ * The loop may then be:
*
- * If not curtains, then push a CMD_CLOSE into the command loop, to bring it
- * to a shuddering halt.
+ * vc_running -- for multi-pthread world this means that the
+ * command loop is executing, and must be sent a
+ * CMD_STOP signal. (Is not waiting for a message,
+ * because we just tried revoking.) Because this
+ * function is in the vty_cli_nexus, the command loop
+ * *must* be running in the vty_cmd_nexus.
*
- * Will leave the vio->state:
+ * for single-pthread (or legacy thread), this should
+ * be impossible -- this code cannot run at the same
+ * time as the command loop. However, we send a
+ * CMD_STOP signal.
*
- * vc_running -- a CMD_CLOSE has been passed in (was vc_waiting).
- * Will not be the case if "curtains".
+ * vc_waiting -- can stop the command loop now. Setting vc_stopped
+ * turns off any further signals to the command loop.
*
- * 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 -- already stopped (or never started).
*
- * 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.
+ * Note: we still revoke any in-flight messages.
+ * This deals with the case of the command loop
+ * picking up a CMD_STOP signal in the vty_cmd_nexus,
+ * but the message transferring the command loop to
+ * the vty_cli_nexus has not been picked up yet.
*
- * vc_null -- never been kissed
- * vc_closed -- was already closed
+ * Note: if the command loop is exiting normally,
+ * then it will already be vc_stopped -- see
+ * vty_cmd_loop_exit()
*
- * No other states are possible.
+ * NB: if this is "curtains" then this *should* find the command loop already
+ * vc_stopped -- see uty_close() -- but will force the issue.
+ *
+ * Returns: true <=> the command loop is vc_stopped
+ * false => the command loop is running, but a CMD_STOP signal has
+ * been set.
+ */
+extern bool
+uty_cmd_loop_stop(vty_io vio, bool curtains)
+{
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+
+ if (vio->blocking || !cq_revoke(vio->vty))
+ {
+ if ((vio->state == vc_running) && !curtains)
+ {
+ uty_set_monitor(vio, off) ; /* instantly */
+ vio->signal = CMD_STOP ;
+
+ return false ;
+ } ;
+
+ qassert(vio->state != vc_running) ;
+ } ;
+
+ uty_cmd_stopping(vio, true) ; /* -> vc_stopped */
+
+ return true ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set the command loop stopped -- forced exit.
*/
extern void
-uty_cmd_loop_close(vty_io vio, bool curtains)
+vty_cmd_set_stopped(vty vty)
+{
+ VTY_LOCK() ;
+ uty_cmd_stopping(vty->vio, true) ; /* forced exit */
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * The command loop is stopping or closing.
+ *
+ * We drop the config symbol of power and effectively discard all input.
+ * Stops any log monitoring, immediately.
+ *
+ * This is done as soon as a CMD_CLOSE, CMD_STOP or CMD_STOP signal is seen, so
+ * that the symbol of power is not held while a vty is closing its vio stack,
+ * or while the command loop is being transferred from the vty_cmd_nexus to the
+ * vty_cli_nexus.
+ *
+ * If exeunt, set vc_stopped -- otherwise leave vc_running to tidy up.
+ *
+ * This can be called any number of times.
+ */
+static void
+uty_cmd_stopping(vty_io vio, bool exeunt)
{
VTY_ASSERT_LOCKED() ;
- if ((vio->state == vc_null) || (vio->state == vc_closed))
- return ;
+ uty_set_monitor(vio, off) ; /* instantly */
- if (curtains)
- {
- if (!vio->blocking)
- cq_revoke(vio->vty) ; /* collect any outstanding message */
+ vio->vin_true_depth = 0 ; /* all stop */
+ uty_cmd_config_lock_check(vio->vty, NULL_NODE) ;
- vio->state = vc_stopped ;
- }
+ if (exeunt)
+ vio->state = vc_stopped ; /* don't come back */
else
- uty_cmd_signal(vio, CMD_CLOSE) ;
+ qassert(vio->state == vc_running) ;
} ;
/*------------------------------------------------------------------------------
- * Exit command loop.
+ * If we have a CMD_STOP on our hands, then drop the config symbol of power.
*
- * Final close of the VTY, giving a reason, if required.
+ * This is done so that on SIGHUP the symbol of power can be acquired to read
+ * the configuration file, without an interlock to wait for closing then
+ * current vty.
+ */
+extern void
+vty_cmd_check_stop(vty vty, cmd_return_code_t ret)
+{
+ VTY_LOCK() ;
+
+ if ((ret == CMD_STOP) || (vty->vio->signal == CMD_STOP))
+ {
+ vty->vio->signal = CMD_STOP ; /* make sure signal set */
+
+ uty_cmd_stopping(vty->vio, false) ; /* not exit, yet */
+ } ;
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Exit command loop, with final close of the VTY.
+ *
+ * NB: on exit the VTY has been release, so do NOT attempt to touch the VTY
+ * or any of its components.
*/
extern void
vty_cmd_loop_exit(vty vty)
@@ -293,20 +454,150 @@ vty_cmd_loop_exit(vty vty)
VTY_ASSERT_CAN_CLOSE(vty) ;
- /* Make sure no longer holding the config symbol of power */
- uty_cmd_config_lock_check(vty, NULL_NODE) ;
+ uty_cmd_stopping(vty->vio, true) ; /* exit -- set vc_stopping */
+ uty_close(vty->vio) ; /* down close the vty */
+
+ VTY_UNLOCK() ;
+} ;
- /* Can now close the vty */
- vty->vio->state = vc_stopped ;
- uty_close(vty->vio, NULL, false) ; /* not curtains */
+/*==============================================================================
+ * Command line fetch.
+ *
+ * Will read command lines from the current input, until that signals EOF.
+ *
+ * Before attempting to read, will check for a vio->signal, and that the input
+ * and output stack depths do not require attention. The CMD_HIATUS return
+ * code signals that something needs to be dealt with before any input is
+ * attempted.
+ *
+ * NB: all closing of inputs and/or outputs is done in the hiatus, below.
+ */
+
+/*------------------------------------------------------------------------------
+ * Fetch the next command line to be executed.
+ *
+ * Returns: CMD_SUCCESS => OK -- have a line ready to be processed.
+ *
+ * vty->exec->line points at the line
+ * vty->exec->to_do says what to do with it
+ *
+ * or: CMD_WAITING => OK -- but waiting for command line to arrive
+ * <=> non-blocking
+ *
+ * or: CMD_EOF => OK -- but nothing to fetch from the current vin
+ *
+ * Need to close the current vin and pop vin/vout
+ * as necessary.
+ *
+ * or: CMD_HIATUS => OK -- but need to close one or more vin/vout
+ * to adjust stack.
+ *
+ * or: CMD_CANCEL => cancel vio->signal detected.
+ *
+ * or: CMD_IO_ERROR => failed (here or signal) -- I/O error or timeout.
+ *
+ * or: CMD_STOP => stop vio->signal detected.
+ *
+ * NB: can be called from any thread -- because does no closing of files or
+ * anything other than read/write.
+ */
+extern cmd_return_code_t
+vty_cmd_fetch_line(vty vty)
+{
+ cmd_return_code_t ret ;
+ vty_io vio ;
+ vio_vf vf ;
+ cmd_exec exec ;
+
+ VTY_LOCK() ;
+
+ vio = vty->vio ; /* once locked */
+ exec = vty->exec ;
+
+ cmd_action_clear(exec->action) ; /* tidy */
+
+ vf = vio->vin ;
+
+ ret = vio->signal ;
+
+ if (ret == CMD_SUCCESS)
+ {
+ if ( (vio->vin_depth < vio->vout_depth) ||
+ (vio->vin_depth > vio->vin_true_depth) )
+ ret = CMD_HIATUS ;
+ else
+ {
+ qassert(vio->vin_depth == vio->vin_true_depth) ;
+ qassert(vio->vin_depth != 0) ;
+
+ switch (vf->vin_state)
+ {
+ case vf_closed:
+ zabort("invalid vf->vin_state (vf_closed)") ;
+ break ;
+
+ case vf_open:
+ 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_end: /* Should be dealt with in hiatus... */
+ ret = CMD_HIATUS ; /* ...so go (back) there ! */
+ break ;
+
+ default:
+ zabort("unknown vf->vin_state") ;
+ break ;
+ } ;
+ } ;
+ } ;
VTY_UNLOCK() ;
+
+ return ret ;
} ;
+/*==============================================================================
+ * Special command handling functions.
+ */
+
/*------------------------------------------------------------------------------
* Handle a "special" command -- anything not cmd_do_command.
*
* These "commands" are related to VTY_TERMINAL CLI only.
+ *
+ * Returns: CMD_SUCCESS -- OK, carry on
+ * CMD_CLOSE -- bring everything to an orderly stop
*/
extern cmd_return_code_t
vty_cmd_special(vty vty)
@@ -330,6 +621,7 @@ vty_cmd_special(vty vty)
switch (to_do & cmd_do_mask)
{
case cmd_do_nothing:
+ case cmd_do_ctrl_c:
break ;
case cmd_do_eof:
@@ -360,7 +652,6 @@ vty_cmd_special(vty vty)
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 ;
@@ -380,6 +671,7 @@ vty_cmd_special(vty vty)
vty->exec->context->node = next_node ;
vty_cmd_config_lock_check(vty, next_node) ;
} ;
+
return ret ;
} ;
@@ -478,12 +770,12 @@ vty_cmd_can_auth_enable(vty 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"
+ * 4. host.password -- set by "password xx"
*
* Unless no_password_check, if there is no password, you cannot start
* a vty.
*
- * 5. host.enable -- set by "enable password xxx"
+ * 5. host.enable -- set by "enable password xx"
*
* If this is set, then must authenticate against it for vty to reach
* ENABLE_NODE.
@@ -524,7 +816,7 @@ vty_cmd_auth(vty vty, node_type_t* p_next_node)
pass = false ;
- VTY_LOCK() ; /* while access host.xxx */
+ VTY_LOCK() ; /* while access host.xx */
switch (vty->node)
{
@@ -649,126 +941,39 @@ vty_cmd_auth(vty vty, node_type_t* p_next_node)
return ret ;
} ;
-/*------------------------------------------------------------------------------
- * Fetch the next command line to be executed.
+/*==============================================================================
+ * Hiatus handling.
*
- * Returns: CMD_SUCCESS => OK -- have a line ready to be processed.
+ * The hiatus must deal with a number of slightly different things:
*
- * vty->exec->line points at the line
- * vty->exec->to_do says what to do with it
+ * * closing of inputs and/or outputs, and adjusting the stacks.
*
- * or: CMD_WAITING => OK -- but waiting for command line to arrive
- * <=> non-blocking
+ * All pipe closing is done here.
*
- * or: CMD_EOF => OK -- but nothing to fetch from the current vin
+ * * command errors
*
- * Need to close the current vin and pop vin/vout
- * as necessary.
+ * * cancel
*
- * or: CMD_HIATUS => OK -- but need to close one or more vin/vout
- * to adjust stack.
+ * * I/O errors and time-outs
*
- * or: any other return code from the current vin when it attempts to
- * fetch another command line -- including I/O error or timeout.
+ * * closing of vty altogether, either:
*
- * NB: can be called from any thread -- because does no closing of files or
- * anything other than read/write.
+ * CMD_CLOSE -- which closes everything, completing all I/O
+ *
+ * CMD_STOP -- which stops the command loop, and lets uty_close()
+ * close everything "final".
*
- * TODO -- dealing with states other than vf_open !!
+ * Note that while closing, for non-blocking vio, may return from the hiatus
+ * CMD_WAITING, and the hiatus will be called again (possibly a number of
+ * times) until all necessary closes and related I/O are complete.
*/
-extern cmd_return_code_t
-vty_cmd_fetch_line(vty vty)
-{
- cmd_return_code_t ret ;
- vty_io vio ;
- vio_vf vf ;
- cmd_exec exec ;
-
- VTY_LOCK() ;
-
- vio = vty->vio ; /* once locked */
- exec = vty->exec ;
-
- cmd_action_clear(exec->action) ; /* tidy */
-
- vf = vio->vin ;
-
- ret = CMD_SUCCESS ; /* assume all is well */
-
- /* 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)
- {
- 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 ;
- } ;
- } ;
-
- VTY_UNLOCK() ;
-
- return ret ;
-} ;
/*------------------------------------------------------------------------------
* Deal with return code at the "exec_hiatus" point in the command loop.
*
+ * May be entering the hiatus because a signal has been detected, which may
+ * override the given return code. Any signal is then cleared.
+ *
* 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
@@ -781,11 +986,24 @@ vty_cmd_fetch_line(vty vty)
*
* - the vio->state needs to be checked.
*
+ * CMD_STOP -- stop the command loop and exit, closing vty
+ *
* CMD_EOF -- from vty_cmd_fetch_line() => current vin has hit eof,
- * and must be closed and popped.
+ * or an end/exit command has forced the issue.
+ *
+ * The vin_real_depth must be reduced, and the top vin
+ * then closed.
+ *
+ * CMD_CLOSE -- from a command return => must close the entire vty.
+ *
+ * CMD_CLOSE causes the vty to be closed in an orderly
+ * fashion, dealing with all pending I/O, including
+ * all pipe return etc.
+ *
+ * Once everything has been closed down to the
*
- * CMD_CLOSE -- from a command return (or otherwise) => must close
- * and pop the current vin (same as CMD_EOF, really).
+ * CMD_CANCEL -- cancel all output & close everything except the
+ * vin/vout base.
*
* CMD_WAITING -- from vty_cmd_fetch_line() or elsewhere => must go to
* vc_waiting and the command loop MUST exit.
@@ -808,11 +1026,16 @@ vty_cmd_fetch_line(vty vty)
* 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 and timeouts in any level of the stack other than vin_base
+ * and vout_base will cause everything except the vin_base and vout_base
+ * to be closed.
*
- * 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.
+ * I/O errors and time-outs in vin_base cause everything to be closed, but
+ * will try to put error messages and outstanding stuff to vout_base.
+ *
+ * I/O errors and time-outs in vout_base cause everything to be closed.
+ *
+ * Does the standard close... so will try to flush all outstanding stuff.
*
* This function will return:
*
@@ -825,150 +1048,208 @@ vty_cmd_fetch_line(vty vty)
*
* state == vc_waiting
*
- * CMD_EOF => OK -- but nothing more to fetch, close the vty and exit
- * command loop.
- * <=> the vin_base is now closed.
+ * CMD_STOP => OK -- all done: close the vty and exit command loop.
*
* state == vc_stopped
*
- * CMD_IO_ERROR => some error has occurred while closing stuff.
+ * If CMD_STOP is passed in, or a CMD_STOP signal
+ * is picked up, then has done nothing with the
+ * stacks -- uty_close() will do close "final".
*
- * state == vc_io_error_trap
+ * Otherwise, will have completing all pending stuff,
+ * and closed everything except the vout_base.
*
- * And nothing else.
+ * And nothing else, except:
*
- * 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.
+ * CMD_IO_ERROR => an I/O error or time-out has been hit, probably while
+ * trying to close something.
+ *
+ * This error should be sent straight back to this
+ * function -- but is passed out so that any error is not
+ * hidden -- see cmd_read_config().
*
* 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.
+ * stacks, if required.
*
* 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
+ * all return codes. However, it will exit the command loop at the first
* hint of trouble.
*
- * All this TODO (after discussion of error handling)
-
* 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_io vio ;
+
VTY_LOCK() ;
VTY_ASSERT_CAN_CLOSE(vty) ;
- ret = uty_cmd_hiatus(vty->vio, ret) ;
+ vio = vty->vio ;
+
+ qassert(vio->state == vc_running) ;
+
+ ret = uty_cmd_hiatus(vio, ret) ;
+
+ switch (ret)
+ {
+ case CMD_SUCCESS: /* ready to continue */
+ case CMD_IO_ERROR: /* for information */
+ break ;
+
+ case CMD_WAITING:
+ qassert(!vio->blocking) ;
+ vio->state = vc_waiting ;
+ break ;
+
+ case CMD_STOP: /* exit */
+ uty_cmd_stopping(vio, true) ; /* vio->state -> vc_stopped */
+ break ;
+
+ default:
+ zabort("invalid return code from uty_cmd_hiatus") ;
+ } ;
VTY_UNLOCK() ;
+
return ret ;
} ;
/*------------------------------------------------------------------------------
- * Inside of vty_cmd_hiatus() -- can return at any time.
+ * Inside of vty_cmd_hiatus() -- see above.
*/
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:
+ /* (1) Handle any vio->signal.
*
- * vc_io_error_trap => an I/O error has been posted asynchronously
+ * If there is a signal it overrides most return codes, except:
*
- * set state to vc_running and return code to
- * the pending CMD_IO_ERROR.
+ * CMD_CANCEL -- trumped by: CMD_STOP, CMD_CLOSE or CMD_IO_ERROR
*
- * vc_close_trap => the vty has been reset asynchronously
+ * CMD_IO_ERROR -- trumped by: CMD_STOP or CMD_CLOSE
*
- * set state to vc_running and return code to
- * the pending CMD_CLOSE.
+ * CMD_STOP -- trumps everything
*
- * vc_stopped )
- * vc_closed ) invalid -- cannot be here in this state
- * vc_null )
+ * The vio_signal is now cleared (unless was CMD_STOP).
*/
- switch (vio->state)
+ switch (vio->signal)
{
- case vc_null:
- case vc_stopped:
- case vc_closed:
- zabort("invalid vc_xxxx") ;
+ case CMD_SUCCESS:
break ;
- case vc_running:
- case vc_waiting:
+ case CMD_CANCEL:
+ if ((ret != CMD_STOP) && (ret != CMD_CLOSE) && (ret != CMD_IO_ERROR))
+ ret = CMD_CANCEL ;
+
+ vio->signal = CMD_SUCCESS ;
break ;
- case vc_io_error_trap:
- ret = CMD_IO_ERROR ; /* convert pending IO error */
+ case CMD_IO_ERROR:
+ if ((ret != CMD_STOP) && (ret != CMD_CLOSE))
+ ret = CMD_IO_ERROR ;
+
+ vio->signal = CMD_SUCCESS ;
break ;
- case vc_close_trap:
- ret = CMD_CLOSE ; /* convert pending close */
+ case CMD_STOP:
+ ret = CMD_STOP ;
break ;
default:
- zabort("unknown vio->state") ;
- break ;
+ zabort("Invalid vio->signal value") ;
} ;
- vio->state = vc_running ; /* running in hiatus */
-
- /* (1) Handle the return code.
+ /* (2) Handle the return code/signal
+ *
+ * Deal here with the return codes that signify success, or signify
+ * success but some vin and/or vout need to be closed.
*
- * 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.
*
- * 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.
+ * CMD_WAITING is immediately returned
*
- * Note that CMD_WAITING is immediately returned !
+ * CMD_STOP, whether passed in or from vio->signal is also immediately
+ * returned.
*/
switch (ret)
- {
- case CMD_SUCCESS:
- case CMD_EMPTY:
- case CMD_HIATUS:
- break ;
+ {
+ case CMD_SUCCESS:
+ case CMD_EMPTY:
+ case CMD_HIATUS:
+ ret = CMD_SUCCESS ; /* OK */
+ break ;
- case CMD_WAITING:
- assert(!vio->blocking) ;
- vio->state = vc_waiting ;
- return ret ; /* <<< exit here on CMD_WAITING */
+ case CMD_STOP:
+ return CMD_STOP ; /* <<< ...exit here on CMD_STOP */
- case CMD_EOF:
- uty_out_accept(vio) ; /* accept any buffered remarks. */
- assert(vio->real_depth > 0) ;
- --vio->real_depth ;
- break ;
+ case CMD_WAITING:
+ return CMD_WAITING ; /* <<< ...exit here on CMD_WAITING */
- case CMD_CLOSE:
- uty_out_accept(vio) ; /* accept any buffered remarks. */
- vio->real_depth = 0 ; /* which it may already be */
- break ;
+ case CMD_EOF:
+ uty_out_accept(vio) ; /* accept any buffered stuff. */
- 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 ;
- } ;
+ qassert(vio->vin_true_depth > 0) ;
+ --vio->vin_true_depth ; /* cause vin stack to pop */
+
+ ret = CMD_SUCCESS ; /* OK */
+ break ;
+
+ case CMD_CANCEL:
+ qassert(vio->vin_true_depth > 0) ;
+ vio->vin_true_depth = 1 ; /* cause vin stack to pop */
+ vio->cancel = true ; /* suppress output */
+
+ ret = CMD_SUCCESS ; /* OK */
+ break ;
- ret = CMD_SUCCESS ; /* OK, so far */
+ case CMD_CLOSE:
+ uty_out_accept(vio) ; /* accept any buffered stuff. */
+
+ vio->vin_true_depth = 0 ; /* cause vin stack to close */
+
+ ret = CMD_SUCCESS ; /* OK */
+ break ;
+
+ default: /* some sort of error */
+ break ;
+ } ;
+
+ /* If the return code is (still) not CMD_SUCCESS, then must be an error
+ * of some kind, for which we now construct a suitable error message, and
+ * update the vin_true_depth as required.
+ */
+ if (ret != CMD_SUCCESS)
+ {
+ uint depth ;
+
+ depth = uty_cmd_failed(vio, ret) ;
+ if (depth < vio->vin_true_depth)
+ vio->vin_true_depth = depth ;
+
+ ret = CMD_SUCCESS ; /* Is now OK. */
+ } ;
+
+ /* Have established the (new) vio->vin_true_depth, so now need to make
+ * sure that the stack conforms to that.
+ *
+ * Adjusting the stack may take a while. So, if the vin_true_depth is 0,
+ * now is a good time to give up the config symbol of power. (This is
+ * for SIGHUP, which closes all vty before reading the configuration.)
+ *
+ * Note that because vin_true_depth is zero, could not fetch any further
+ * command lines or attempt to execute any commands, and don't care
+ * whether own the symbol of power or not.
+ */
+ if (vio->vin_true_depth == 0)
+ uty_cmd_stopping(vio, false) ; /* not exit, yet */
- /* (2) Do we need to close one or more vin, or are we waiting for one to
+ /* (3) 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
@@ -980,179 +1261,121 @@ uty_cmd_hiatus(vty_io vio, cmd_return_code_t ret)
*
* 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.
+ * child completion status has not yet been collected).
*
- * 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.
+ * Where a close does not succeed, exit here and expect to come back
+ * later to complete the operation.
*/
- while ((vio->vin_depth > vio->real_depth) && (ret == CMD_SUCCESS))
- ret = uty_vin_pop(vio, vio->err_hard, vio->vty->exec->context) ;
+ while (vio->vin_depth > vio->vin_true_depth)
+ {
+ ret = uty_vin_pop(vio, vio->vty->exec->context, false) ; /* not final */
+ if (ret != CMD_SUCCESS)
+ return ret ;
+ } ;
+
+ qassert(vio->vin_depth == vio->vin_true_depth) ;
- /* (3) And, do we need to close one or more vout, or are we waiting for
- * one to close ? If so:
+ /* (4) Do we need to close one or more vout, or are we waiting for
+ * one to close ?
*
- * Any output after the current end_mark is discarded. Note that we
- * do not actually close the vout_base.
+ * Note that we do not close the vout_base here -- that is left open
+ * to the final minute, in case any parting messages are to be sent.
*
- * In all cases we push any remaining output and collect any remaining
- * pipe return, and collect child termination condition.
+ * Any remaining output is pushed and any remaining pipe return is
+ * shovelled through, and any child process is collected, along
+ * with its 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.
+ * Where a close does not succeed, exit here and expect to come back
+ * later to complete the operation.
*/
- while (ret == CMD_SUCCESS)
+ while ((vio->vin_depth < vio->vout_depth) && (vio->vout_depth > 1))
{
- 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)
- {
- vio_fifo_copy(vio->obuf, vio->ebuf) ;
- vio->ebuf = vio_fifo_free(vio->ebuf) ;
-
- ret = uty_cmd_out_push(vio->vout, vio->err_hard) ;
+ if (vio->cancel)
+ uty_cmd_out_cancel(vio->vout, false) ; /* stop output & pipe return
+ * not vout_base */
- if (ret != CMD_SUCCESS)
- break ;
- } ;
- } ;
+ ret = uty_vout_pop(vio, false) ; /* not final */
- if (vio->vout_depth == 0)
- assert(vio->vout->depth_mark == 0) ;
-
- if (vio->vin_depth < vio->vout->depth_mark)
- ret = uty_vout_pop(vio, vio->err_hard) ;
- else
- break ;
+ if (ret != CMD_SUCCESS)
+ return ret ;
} ;
- /* (4) Quit now if not successful on stack adjustment
+ /* (5) If we are now at the vout_base, then:
+ *
+ * If there is anything in the pipe stderr return, copy that to the
+ * obuff -- unless vio->cancel.
+ *
+ * If there is an error message in hand, now is the time to move that to
+ * the obuf and clear the error message buffer. (If the vout_base has
+ * failed, then the error message is going nowhere, but there's nothing
+ * we can do about that -- the error has been logged in any case.)
+ *
+ * Push any outstanding output (including any error message) to the
+ * vout_base.
+ *
+ * If the vty is about to be closed, this step ensures that all output
+ * is tidily dealt with, before uty_close() performs its "final" close.
*/
- if (ret != CMD_SUCCESS)
+ if (vio->vout_depth == 1)
{
- if (ret == CMD_WAITING)
+ if (vio->cancel)
{
- assert(!vio->blocking) ;
- vio->state = vc_waiting ;
- return ret ; /* <<< exit here on CMD_WAITING */
+ /* Once we have cleared the output buffer etc., clear the cancel
+ * flag and output "^C" to show what has happened.
+ */
+ uty_cmd_out_cancel(vio->vout, true) ; /* stop output & pipe return
+ * is vout_base */
+ uty_out(vio, " ^C\n") ;
} ;
- if (ret == CMD_IO_ERROR)
+ if (!vio_fifo_empty(vio->ps_buf))
{
- vio->state = vc_io_error_trap ;
- return ret ;
+ if (!vio->cancel)
+ vio_fifo_copy(vio->obuf, vio->ps_buf) ;
+ vio_fifo_clear(vio->ps_buf, true) ; /* clear any marks too */
} ;
- 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 ;
- } ;
+ vio->cancel = false ;
- /* (6) All ready now to continue processing commands.
- */
- uty_cmd_prepare(vio) ; /* update vty->exec state */
+ if (!vio_fifo_empty(vio->ebuf))
+ {
+ vio_fifo_copy(vio->obuf, vio->ebuf) ;
+ vio_fifo_clear(vio->ebuf, true) ; /* clear any marks too */
+ } ;
- return CMD_SUCCESS ;
-} ;
+ ret = uty_cmd_out_push(vio->vout, false) ;
-/*------------------------------------------------------------------------------
- * 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 ;
+ if (ret != CMD_SUCCESS)
+ return ret ; /* CMD_WAITING or CMD_IO_ERROR */
+ } ;
- exec->out_suppress = (vio->vty->type == VTY_CONFIG_READ) &&
- (vio->vout_depth == 1) ;
- exec->reflect = exec->context->reflect_enabled && !exec->out_suppress ;
-} ;
+ /* (6) Stacks are straight.
+ *
+ * If there is no input left, the command loop must now stop, close the
+ * vty and exit.
+ *
+ * Otherwise, prepare to execute commands at the, presumed new, stack
+ * depth.
+ */
+ qassert(ret == CMD_SUCCESS) ;
-/*------------------------------------------------------------------------------
- * 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() ;
+ if (vio->vin_depth == 0)
+ return CMD_STOP ;
- vty->exec->context->full_lex = full_lex ;
+ uty_cmd_prepare(vio) ;
- VTY_UNLOCK() ;
+ return CMD_SUCCESS ;
} ;
-/*------------------------------------------------------------------------------
- * Reflect the command line to the current vio->obuf.
- *
- * 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.
+/*==============================================================================
+ * Opening of pipes and adjustment of stacks.
*/
-extern cmd_return_code_t
-vty_cmd_reflect_line(vty vty)
-{
- cmd_return_code_t ret ;
-
- VTY_LOCK() ;
-
- 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') ;
-
- ret = uty_cmd_out_push(vty->vio->vout, false) ; /* not final */
- } ;
-
- VTY_UNLOCK() ;
-
- return ret ;
-} ;
+static void uty_cmd_command_path(qstring name, cmd_context context) ;
/*------------------------------------------------------------------------------
* Open the given file as an in pipe, if possible.
@@ -1170,9 +1393,9 @@ uty_cmd_open_in_pipe_file(vty_io vio, cmd_context context,
VTY_ASSERT_LOCKED() ;
- if (vio->state != vc_running)
- ret = CMD_HIATUS ;
- else
+ ret = vio->signal ; /* signal can interrupt */
+
+ if (ret == CMD_SUCCESS)
{
ret = uty_file_read_open(vio, name, context) ;
@@ -1204,10 +1427,11 @@ uty_cmd_open_in_pipe_shell(vty_io vio, cmd_context context, qstring command,
VTY_ASSERT_LOCKED() ;
- if (vio->state != vc_running)
- ret = CMD_HIATUS ;
- else
+ ret = vio->signal ; /* signal can interrupt */
+
+ if (ret == CMD_SUCCESS)
{
+ uty_cmd_command_path(command, context) ;
ret = uty_pipe_read_open(vio, command, context) ;
if (ret == CMD_SUCCESS)
@@ -1229,16 +1453,18 @@ uty_cmd_open_in_pipe_shell(vty_io vio, cmd_context context, qstring command,
*/
extern cmd_return_code_t
uty_cmd_open_out_pipe_file(vty_io vio, cmd_context context, qstring name,
- cmd_pipe_type_t type)
+ cmd_pipe_type_t type, bool after)
{
cmd_return_code_t ret ;
- if (vio->state != vc_running)
- ret = CMD_HIATUS ;
- else
+ VTY_ASSERT_LOCKED() ;
+
+ ret = vio->signal ; /* signal can interrupt */
+
+ if (ret == CMD_SUCCESS)
{
ret = uty_file_write_open(vio, name,
- ((type & cmd_pipe_append) != 0), context) ;
+ ((type & cmd_pipe_append) != 0), context, after) ;
if (ret == CMD_SUCCESS)
uty_cmd_prepare(vio) ;
} ;
@@ -1253,16 +1479,19 @@ uty_cmd_open_out_pipe_file(vty_io vio, cmd_context context, qstring name,
*/
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_pipe_type_t type, bool after)
{
cmd_return_code_t ret ;
- if (vio->state != vc_running)
- ret = CMD_HIATUS ;
- else
+ VTY_ASSERT_LOCKED() ;
+
+ ret = vio->signal ; /* signal can interrupt */
+
+ if (ret == CMD_SUCCESS)
{
+ uty_cmd_command_path(command, context) ;
ret = uty_pipe_write_open(vio, command,
- ((type & cmd_pipe_shell_only) !=0)) ;
+ ((type & cmd_pipe_shell_cmd) != 0), after) ;
if (ret == CMD_SUCCESS)
uty_cmd_prepare(vio) ;
@@ -1277,19 +1506,19 @@ uty_cmd_open_out_pipe_shell(vty_io vio, cmd_context context, qstring command,
* Puts error messages to vty if fails.
*/
extern cmd_return_code_t
-uty_cmd_open_out_dev_null(vty_io vio)
+uty_cmd_open_out_dev_null(vty_io vio, bool after)
{
cmd_return_code_t ret ;
vio_vf vf ;
VTY_ASSERT_LOCKED() ;
- if (vio->state != vc_running)
- ret = CMD_HIATUS ;
- else
+ ret = vio->signal ; /* signal can interrupt */
+
+ if (ret == CMD_SUCCESS)
{
vf = uty_vf_new(vio, "dev_null", -1, vfd_none, vfd_io_none) ;
- uty_vout_push(vio, vf, VOUT_DEV_NULL, NULL, NULL, 0) ;
+ uty_vout_push(vio, vf, VOUT_DEV_NULL, NULL, NULL, 0, after) ;
uty_cmd_prepare(vio) ;
@@ -1312,18 +1541,141 @@ uty_cmd_path_name_complete(qpath dst, const char* name, cmd_context context)
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 */
+ {
+ /* Have a leading '~' -- deal with:
+ *
+ * "~~/???" or "~~\0", which for Quagga -> configuration directory
+ *
+ * "~./???" or "~.\0", which for Quagga -> "here" (same as enclosing
+ * pipe)
+ *
+ * "~/???" or "~\0", which -> HOME environment variable
+ * or initial working directory
+ * for login.
+ *
+ * "~user/???" or "~user\0", which -> initial working directory
+ * for given user
+ */
+ if ((*(name + 1) == '~') &&
+ ( (*(name + 2) == '/') || (*(name + 2) == '\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
+ {
+ qpath was = dst ;
+
+ dst = qpath_get_home(dst, name + 1) ;
+
+ /* If didn't get a home, return the original name
+ */
+ if (dst == NULL)
+ return qpath_set(was, name) ;
+ } ;
+ } ;
return qpath_append_str(dst, name) ; /* create the full path */
} ;
/*------------------------------------------------------------------------------
+ * If the given qstring starts with a '~' directory or is a relative path,
+ * then now is the time to complete it.
+ */
+static void
+uty_cmd_command_path(qstring command, cmd_context context)
+{
+ const char* p, * s ;
+ qstring cmd ;
+ qpath qp ;
+
+ s = p = qs_string(command) ;
+
+ if ((*p == '/') || (*p == '\0'))
+ return ; /* absolute path or empty ! */
+
+ do
+ {
+ ++p ;
+ if (*p <= ' ')
+ return ; /* no path involved */
+ }
+ while (*p != '/') ; /* look for '/' */
+
+ do
+ ++p ;
+ while (*p > ' ') ; /* look for end */
+
+ cmd = qs_set_n(NULL, s, p - s) ;
+ qp = uty_cmd_path_name_complete(NULL, qs_string(cmd), context) ;
+
+ qs_set_cp_nn(command, 0) ;
+ qs_replace(command, p - s, qpath_string(qp), qpath_len(qp)) ;
+
+ qs_free(cmd) ;
+ qpath_free(qp) ;
+} ;
+
+/*==============================================================================
+ * Output before and after command execution.
+ *
+ * All output goes to a fifo, after a fifo "end mark". After reflecting a
+ * command and after completing a command, all outstanding output is pushed
+ * out -- advancing the end mark past all output to date.
+ */
+
+/*------------------------------------------------------------------------------
+ * Reflect the command line to the current vio->obuf.
+ *
+ * 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.
+ *
+ * Returns: CMD_SUCCESS -- all buffers are empty
+ * CMD_WAITING -- all buffers are not empty
+ * CMD_IO_ERROR -- error or time-out
+ * CMD_HIATUS -- the vty is not in vc_running state.
+ *
+ * This can be called in any thread.
+ *
+ * Note that CMD_WAITING requires no further action from the caller, the
+ * background pselect process will complete the output and may signal the
+ * result via uty_cmd_signal() (CMD_SUCCESS or CMD_IO_ERROR).
+ */
+extern cmd_return_code_t
+vty_cmd_reflect_line(vty vty)
+{
+ cmd_return_code_t ret ;
+ vty_io vio ;
+
+ VTY_LOCK() ;
+ vio = vty->vio ; /* once locked */
+
+ ret = vio->signal ; /* signal can interrupt */
+
+ if (ret == CMD_SUCCESS)
+ {
+ vio_fifo obuf ;
+ qstring line ;
+
+ obuf = vio->obuf ;
+ line = vty->exec->action->line ;
+
+ vio_fifo_put_bytes(obuf, qs_char_nn(line), qs_len_nn(line)) ;
+ vio_fifo_put_byte(obuf, '\n') ;
+
+ ret = uty_cmd_out_push(vio->vout, false) ; /* not final */
+ } ;
+
+ VTY_UNLOCK() ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
* Command has completed successfully.
*
* An output generated by the command is now pushed unless exec->out_suppress,
@@ -1338,11 +1690,9 @@ vty_cmd_success(vty vty)
VTY_LOCK() ;
vio = vty->vio ; /* once locked */
- ret = CMD_SUCCESS ;
+ ret = vio->signal ; /* signal can interrupt */
- if (vio->state != vc_running)
- ret = CMD_HIATUS ;
- else
+ if (ret == CMD_SUCCESS)
{
if (!vio_fifo_tail_empty(vio->obuf))
{
@@ -1361,19 +1711,24 @@ vty_cmd_success(vty vty)
/*------------------------------------------------------------------------------
* If there is anything after the end_mark, push it to be written, now.
*
+ * This is used by configuration file output, which outputs to the fifo and
+ * pushes every now and then.
+ *
* See uty_cmd_out_push() below.
*/
extern cmd_return_code_t
vty_cmd_out_push(vty vty)
{
cmd_return_code_t ret ;
+ vty_io vio ;
VTY_LOCK() ;
+ vio = vty->vio ; /* once locked */
- if (vty->vio->state != vc_running)
- ret = CMD_HIATUS ;
- else
- ret = uty_cmd_out_push(vty->vio->vout, false) ; /* not final */
+ ret = vio->signal ; /* signal can interrupt */
+
+ if (ret == CMD_SUCCESS)
+ ret = uty_cmd_out_push(vio->vout, false) ; /* not final */
VTY_UNLOCK() ;
@@ -1381,22 +1736,36 @@ vty_cmd_out_push(vty vty)
} ;
/*------------------------------------------------------------------------------
- * If there is anything after the end_mark, push it to be written, now.
+ * If there is anything after the end_mark, advance the end mark and attempt to
+ * write away contents of the buffer.
+ *
+ * For non-blocking vf, will write as much as possible here, and anything left
+ * will be left to the pselect() process, unless "final".
*
- * For most outputs we kick the I/O side so that the pselect processing deals
- * with the required write. For VOUT_STDERR and VOUT_STDOUT, output goes
- * directly to standard I/O.
+ * For blocking vf, may block here, unless "final".
*
- * Advances the end_mark past the stuff pushed.
+ * If "final", will attempt to write etc., but will not block and may turn
+ * off the pselect() processing of this vf. "final" is used when a pipe of
+ * some kind is being closed "final", and the slave output is being pushed.
*
* 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.
+ * Returns: CMD_SUCCESS -- done everything possible
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * This can be called in any thread.
+ *
+ * Note that CMD_WAITING requires no further action from the caller, the
+ * background pselect process will complete the output and may signal the
+ * result via uty_cmd_signal() (CMD_SUCCESS or CMD_IO_ERROR).
*/
extern cmd_return_code_t
-uty_cmd_out_push(vio_vf vf, bool final) /* TODO: write errors & blocking */
+uty_cmd_out_push(vio_vf vf, bool final)
{
cmd_return_code_t ret ;
@@ -1409,7 +1778,6 @@ uty_cmd_out_push(vio_vf vf, bool final) /* TODO: write errors & blocking */
switch (vf->vout_state)
{
case vf_open:
- case vf_closing:
switch (vf->vout_type)
{
case VOUT_NONE:
@@ -1417,15 +1785,19 @@ uty_cmd_out_push(vio_vf vf, bool final) /* TODO: write errors & blocking */
break ;
case VOUT_TERM:
- ret = uty_term_out_push(vf, final) ;
+ /* Note that we ignore "final" -- the VOUT_TERM runs until
+ * it is closed.
+ */
+ ret = uty_term_out_push(vf, false) ;
break ;
case VOUT_VTYSH:
- /* Kick the writer */
+ /* Kick the writer */
break ;
case VOUT_FILE:
- ret = uty_file_out_push(vf, final) ;
+ /* push everything if the vty is being closed. */
+ ret = uty_file_out_push(vf, final, vf->vio->vin_depth == 0) ;
break ;
case VOUT_PIPE:
@@ -1433,20 +1805,27 @@ uty_cmd_out_push(vio_vf vf, bool final) /* TODO: write errors & blocking */
break ;
case VOUT_CONFIG:
- ret = uty_file_out_push(vf, final) ; /* treat as file */
+ /* push everything if the vty is being closed. */
+ ret = uty_file_out_push(vf, final, vf->vio->vin_depth == 0) ;
break ;
case VOUT_DEV_NULL:
- case VOUT_SHELL_ONLY:
- vio_fifo_clear(vf->obuf, false) ; /* keep end mark */
+ case VOUT_SH_CMD:
+ vio_fifo_clear(vf->obuf, false) ; /* keep end mark */
break ;
case VOUT_STDOUT:
- vio_fifo_fwrite(vf->obuf, stdout) ; // TODO errors
+ if (vf->vio->cancel)
+ vio_fifo_clear(vf->obuf, false) ; /* keep end mark */
+ else
+ vio_fifo_fwrite(vf->obuf, stdout) ; // TODO errors
break ;
case VOUT_STDERR:
- vio_fifo_fwrite(vf->obuf, stderr) ; // TODO errors
+ if (vf->vio->cancel)
+ vio_fifo_clear(vf->obuf, false) ; /* keep end mark */
+ else
+ vio_fifo_fwrite(vf->obuf, stderr) ; // TODO errors
break ;
default:
@@ -1455,16 +1834,9 @@ uty_cmd_out_push(vio_vf vf, bool final) /* TODO: write errors & blocking */
break ;
case vf_closed:
- case vf_timed_out:
- break ; /* immediate success ! */
-
- case vf_eof:
- zabort("vf->vout cannot be vf_eof") ;
- break ;
-
- case vf_error:
- ret = CMD_IO_ERROR ;
- break ;
+ case vf_end:
+ vio_fifo_clear(vf->obuf, false) ; /* keep end mark */
+ break ; /* immediate success ! */
default:
zabort("unknown vf->vout_state") ;
@@ -1475,38 +1847,73 @@ uty_cmd_out_push(vio_vf vf, bool final) /* TODO: write errors & blocking */
} ;
/*------------------------------------------------------------------------------
+ * Bring output and any pipe return to a sudden halt.
+ */
+static void
+uty_cmd_out_cancel(vio_vf vf, bool base)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ /* Dump contents of obuf and if not base: force vf_end (if vf_open)
+ */
+ vio_fifo_clear(vf->obuf, false) ;
+
+ if (!base && (vf->vout_state == vf_open))
+ vf->vout_state = vf_end ;
+
+ /* If there is a pipe return, close that down, too.
+ */
+ if (vf->pr_state == vf_open)
+ uty_pipe_return_cancel(vf) ;
+} ;
+
+/*==============================================================================
+ * Error handling
+ */
+
+/*------------------------------------------------------------------------------
* Dealing with error of some kind.
*
- * 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.
+ * In general any error causes the vin/vout stack to be closed either
+ * completely or down to the base vin/vout. vio->err_depth contains the
+ * default depth to close back to. An I/O error in either vin_base or
+ * vout_base will set the err_depth to 0.
*
- * 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.
+ * The vio->ebuf contains all error messages collected so far for the vio,
+ * and will be output to the vout_base when the stack has been closed to
+ * that point. The vio->ebuf will then be emptied.
*
- * Any other error message is then appended to the ebuf.
+ * For command and parser errors:
*
- * For command errors, the tail of the vio->obuf (stuff after the end_mark) is
- * moved to the ebuf.
+ * 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
+ * messages associated with the error.
*
- * Deals with:
+ * The location of the error is written to the vio->ebuf, and then the
+ * contents of the vio->obuf are moved to the end the vio->ebuf, possibly
+ * with other diagnostic information.
*
- * CMD_WARNING = TODO
- * CMD_ERROR =>
+ * For I/O errors:
*
- * 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,
+ * The contents of vio->obuf are left untouched -- the closing of the
+ * stack will do what it can with those.
*
- * CMD_IO_ERROR I/O -- failed :-(
- * CMD_IO_TIMEOUT I/O -- timed out :-(
+ * The vio->ebuf will already contain the required error message(s). The
+ * vio->err_depth will have been set to close as far as vin_base/vout_base,
+ * or to close the vty completely.
*
- * 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
- * message(s).
+ * Deals with:
+ *
+ * CMD_WARNING command: general failed or not fully succeeded
+ * CMD_ERROR command: definitely failed
+ *
+ * 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: error or time-out
*
* NB: does not expect to see all the possible CMD_XXX return codes (see
* below), but treats all as a form of error !
@@ -1514,54 +1921,61 @@ uty_cmd_out_push(vio_vf vf, bool final) /* TODO: write errors & blocking */
static uint
uty_cmd_failed(vty_io vio, cmd_return_code_t ret)
{
- ulen indent ;
+ ulen indent ;
+ uint depth ;
VTY_ASSERT_LOCKED() ;
- /* Process the vin stack to generate the error location(s) */
- if (vio->ebuf != NULL)
- vio_fifo_clear(vio->ebuf, true) ;
- else
- vio->ebuf = vio_fifo_new(1000) ;
-
- indent = uty_show_error_context(vio->ebuf, vio->vin) ;
+ /* Stack depth to close back to.
+ *
+ * This could be overridden by the return code type.
+ */
+ depth = vio->err_depth ;
/* Now any additional error message if required */
+ uty_cmd_get_ebuf(vio) ;
+
switch (ret)
{
case CMD_WARNING:
+ uty_show_error_context(vio->ebuf, vio->vin) ;
+
if (vio_fifo_tail_empty(vio->obuf))
vio_fifo_printf(vio->ebuf, "%% WARNING: non-specific warning\n") ;
break ;
case CMD_ERROR:
+ uty_show_error_context(vio->ebuf, vio->vin) ;
+
if (vio_fifo_tail_empty(vio->obuf))
vio_fifo_printf(vio->ebuf, "%% ERROR: non-specific error\n") ;
break ;
case CMD_ERR_PARSING:
+ indent = uty_show_error_context(vio->ebuf, vio->vin) ;
cmd_get_parse_error(vio->ebuf, vio->vty->exec->parsed, indent) ;
break ;
case CMD_ERR_NO_MATCH:
+ uty_show_error_context(vio->ebuf, vio->vin) ;
vio_fifo_printf(vio->ebuf, "%% Unknown command.\n") ;
break;
case CMD_ERR_AMBIGUOUS:
+ uty_show_error_context(vio->ebuf, vio->vin) ;
vio_fifo_printf(vio->ebuf, "%% Ambiguous command.\n");
break;
case CMD_ERR_INCOMPLETE:
+ uty_show_error_context(vio->ebuf, vio->vin) ;
vio_fifo_printf(vio->ebuf, "%% Command incomplete.\n");
break;
- case CMD_IO_ERROR:
- break ;
-
- case CMD_IO_TIMEOUT:
+ case CMD_IO_ERROR: /* Diagnostic already posted to ebuf */
break ;
default:
+ zlog_err("%s: unexpected return code (%d).", __func__, (int)ret) ;
vio_fifo_printf(vio->ebuf, "%% Unexpected return code (%d).\n", (int)ret);
break ;
} ;
@@ -1572,8 +1986,8 @@ uty_cmd_failed(vty_io vio, cmd_return_code_t ret)
vio_fifo_copy_tail(vio->ebuf, vio->obuf) ;
vio_fifo_back_to_end_mark(vio->obuf, true) ;
- /* Decide what stack level to close back to. */
- return (vio->vty->type == VTY_TERMINAL) ? 1 : 0 ; // TODO I/O error ??
+ /* Return what stack depth to close back to. */
+ return depth ;
} ;
/*------------------------------------------------------------------------------
@@ -1650,10 +2064,29 @@ uty_show_error_context(vio_fifo ebuf, vio_vf vf)
return indent ;
} ;
+/*------------------------------------------------------------------------------
+ * If there is no vio->ebuf, make one
+ */
+extern vio_fifo
+uty_cmd_get_ebuf(vty_io vio)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if (vio->ebuf == NULL)
+ vio->ebuf = vio_fifo_new(1000) ;
+
+ return vio->ebuf ;
+} ;
+
/*==============================================================================
* Configuration node/state handling
*
* At most one VTY may hold the configuration symbol of power at any time.
+ *
+ * Only at vin_depth == 1 may the symbol of power be acquired, and only at
+ * vin_depth <= 1 will the symbol of power be released. Inter alia, this
+ * means that the restoration of command context when an input pipe finishes
+ * does not have to worry about recovering or releasing the symbol of power.
*/
/*------------------------------------------------------------------------------
@@ -1664,41 +2097,54 @@ uty_show_error_context(vio_fifo ebuf, vio_vf vf)
extern cmd_return_code_t
vty_cmd_config_lock (vty vty)
{
+ bool locked ;
+
VTY_LOCK() ;
- uty_cmd_config_lock(vty) ;
+ locked = uty_cmd_config_lock(vty) ;
VTY_UNLOCK() ;
if (vty->config)
return CMD_SUCCESS ;
- vty_out (vty, "VTY configuration is locked by other VTY\n") ;
+ if (locked)
+ vty_out(vty, "VTY configuration is locked by other VTY\n") ;
+ else
+ vty_out(vty, "VTY configuration is not available\n") ;
+
return CMD_WARNING ;
} ;
/*------------------------------------------------------------------------------
* Attempt to gain the configuration symbol of power -- may already own it !
*
- * Returns: true <=> now own the symbol of power (or already did).
+ * NB: cannot do this at any vin level except 1 !
*/
-static void
+static bool
uty_cmd_config_lock (vty vty)
{
+ VTY_ASSERT_LOCKED() ;
+
if (!host.config) /* If nobody owns the lock... */
{
- host.config = true ; /* ...rope it... */
- vty->config = true ;
+ if (vty->vio->vin_depth == 1)
+ {
+ host.config = true ; /* ...rope it... */
+ vty->config = true ;
- do
- ++host.config_brand ; /* ...update the brand... */
- while (host.config_brand == 0) ;
+ do
+ ++host.config_brand ; /* ...update the brand... */
+ while (host.config_brand == 0) ;
- vty->config_brand = host.config_brand ; /* ...brand it. */
+ 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) ;
} ;
+
+ return host.config ;
}
/*------------------------------------------------------------------------------
@@ -1717,10 +2163,11 @@ vty_cmd_config_lock_check(vty vty, node_type_t node)
* 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.
+ * If node > MAX_NON_CONFIG_NODE, must own the symbol of power (unless
+ * vio->vin_true_depth == 0, in which case the node is irrelevant).
*
* If node <= MAX_NON_CONFIG_NODE, will release symbol of power, if own it,
- * PROVIDED is at vin_depth <= 1 !!
+ * PROVIDED is at vin_true_depth <= 1 !!
*/
static void
uty_cmd_config_lock_check(vty vty, node_type_t node)
@@ -1730,11 +2177,11 @@ uty_cmd_config_lock_check(vty vty, node_type_t node)
if (vty->config)
{
/* We think we own it, so we better had */
- assert(host.config) ;
- assert(host.config_brand == vty->config_brand) ;
+ qassert(host.config) ;
+ qassert(host.config_brand == vty->config_brand) ;
/* If no longer need it, release */
- if ((node <= MAX_NON_CONFIG_NODE) && (vty->vio->vin_depth <= 1))
+ if ((node <= MAX_NON_CONFIG_NODE) && (vty->vio->vin_true_depth <= 1))
{
host.config = false ;
vty->config = false ;
@@ -1744,9 +2191,12 @@ uty_cmd_config_lock_check(vty vty, node_type_t node)
{
/* We don't think we own it, so we had better not */
if (host.config)
- assert(host.config_brand != vty->config_brand) ;
+ qassert(host.config_brand != vty->config_brand) ;
- /* Also, node had better not require that we do. */
- assert(node <= MAX_NON_CONFIG_NODE) ;
+ /* Also, node had better not require that we do, noting that
+ * the node is irrelevant if the vin_true_depth is 0.
+ */
+ if (vty->vio->vin_true_depth > 0)
+ qassert(node <= MAX_NON_CONFIG_NODE) ;
} ;
} ;
diff --git a/lib/vty_command.h b/lib/vty_command.h
index 38c95604..1f66f7a7 100644
--- a/lib/vty_command.h
+++ b/lib/vty_command.h
@@ -30,11 +30,14 @@
#include "vty_local.h"
#include "vty_io.h"
-extern void vty_cmd_loop_prepare(vty vty) ;
-extern void uty_cmd_loop_enter(vty_io vio) ;
+extern bool vty_cmd_config_loop_prepare(vty vty) ;
+extern void uty_cmd_queue_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 bool uty_cmd_loop_stop(vty_io vio, bool curtains) ;
+
extern void vty_cmd_loop_exit(vty vty) ;
+extern void vty_cmd_set_stopped(vty vty) ;
+extern void vty_cmd_check_stop(vty vty, cmd_return_code_t ret) ;
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) ;
@@ -44,21 +47,22 @@ 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 vty_cmd_set_full_lex(vty vty, bool full_lex) ;
-
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) ;
+ cmd_context context, qstring name, cmd_pipe_type_t type,
+ bool after) ;
+extern cmd_return_code_t uty_cmd_open_out_dev_null(vty_io vio, bool after) ;
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_context context, qstring command, cmd_pipe_type_t type,
+ bool after) ;
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 vio_fifo uty_cmd_get_ebuf(vty_io vio) ;
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) ;
diff --git a/lib/vty_common.h b/lib/vty_common.h
index 04429d47..c9331261 100644
--- a/lib/vty_common.h
+++ b/lib/vty_common.h
@@ -33,7 +33,10 @@
*
* vty.h -- which is used by all "external" code.
*
- * command_local.h -- which is used by all "internal" code.
+ * vty_local.h -- which is used by all "internal" code on the I/O side.
+ *
+ * command_local.h -- which is used by all "internal" code on the command
+ * processing side.
*
* This allows some things not to be published to "external" code.
*/
diff --git a/lib/vty_io.c b/lib/vty_io.c
index 619f8129..33947342 100644
--- a/lib/vty_io.c
+++ b/lib/vty_io.c
@@ -83,29 +83,17 @@ uty_out(vty_io vio, const char *format, ...)
*
* * for changes to the host name, which should be reflected in the
* prompt for any terminals.
- *
- * * the death watch list
*/
-/* Watch-dog timer. */
+/* Watch-dog timer */
static vio_timer_t vty_watch_dog ;
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.
- */
-extern void
-uty_watch_dog_init(void)
-{
- vio_timer_init_new(vty_watch_dog, NULL, NULL) ; /* empty */
-} ;
/*------------------------------------------------------------------------------
* Start watch dog -- before a VTY is created.
+ *
+ * The host name is set during initialisation, so no need to check it here.
*/
extern void
uty_watch_dog_start(void)
@@ -116,15 +104,12 @@ uty_watch_dog_start(void)
/*------------------------------------------------------------------------------
* Stop watch dog timer -- at close down.
- *
- * Final run along the death-watch
*/
extern void
uty_watch_dog_stop(void)
{
vio_timer_reset(vty_watch_dog, keep_it) ;
- uty_death_watch_scan(true) ; /* scan the death-watch list */
-}
+} ;
/*------------------------------------------------------------------------------
* Watch dog vio_timer action
@@ -134,40 +119,9 @@ uty_watch_dog_bark(vio_timer timer, void* info)
{
cmd_host_name(true) ; /* check for host name change */
- uty_death_watch_scan(false) ; /* scan the death-watch list */
-
return VTY_WATCH_DOG_INTERVAL ;
} ;
-/*------------------------------------------------------------------------------
- * Process the death watch list -- anything on the list can be disposed of.
- *
- * At curtains...
- */
-static void
-uty_death_watch_scan(bool final)
-{
- VTY_ASSERT_CLI_THREAD() ;
-
- /* Dispose of anything on the death watch list. */
-
- while (vio_death_watch != NULL)
- {
- vty vty ;
- vty_io vio ;
-
- vio = vio_death_watch ;
- vio_death_watch = sdl_next(vio, vio_list) ; /* take off death watch */
-
- vty = vio->vty ;
-
- vty->vio = uty_dispose(vty->vio) ;
- assert(vty->exec == NULL) ;
-
- XFREE(MTYPE_VTY, vty) ;
- } ;
-} ;
-
/*==============================================================================
* Prototypes.
*/
@@ -181,8 +135,20 @@ static vio_vf uty_vf_free(vio_vf vf) ;
*/
/*------------------------------------------------------------------------------
- * Allocate new vty structure, including empty vty_io and empty execution
- * structures.
+ * Allocate new vty structure, including empty vty_io but no exec structure.
+ *
+ * Sets:
+ *
+ * * vio->err_depth = 1 if VTY_TERMINAL
+ * = 0 otherwise
+ *
+ * So on everthing except VTY_TERMINAL, any error
+ * will close the entire VTY. On VTY_TERMINAL, an
+ * error will close down to the vin_base/vout_base
+ * (unless error is in one of those).
+ *
+ * * vio->blocking = true if VTY_CONFIG_READ
+ * = false otherwise
*
* Caller must complete the initialisation of the vty_io, which means:
*
@@ -200,7 +166,7 @@ static vio_vf uty_vf_free(vio_vf vf) ;
*
* * etc.
*
- * No "exec" is allocated. That is done when the command loop is entered.
+ * "exec" is allocated only when the command loop is entered.
*/
extern vty
uty_new(vty_type_t type, node_type_t node)
@@ -212,8 +178,7 @@ uty_new(vty_type_t type, node_type_t node)
/* Basic allocation */
- vty = XCALLOC(MTYPE_VTY, sizeof(struct vty)) ;
- /* Zeroising the vty structure has set:
+ /* Zeroising the vty structure will set:
*
* type = X -- set to actual type, below
*
@@ -228,50 +193,54 @@ uty_new(vty_type_t type, node_type_t node)
* exec = NULL -- execution state set up when required
* vio = X -- set below
*/
- confirm(NULL_NODE == 0) ;
- confirm(QSTRING_INIT_ALL_ZEROS) ;
+ vty = XCALLOC(MTYPE_VTY, sizeof(struct vty)) ;
vty->type = type ;
vty->node = node ;
- vio = XCALLOC(MTYPE_VTY, sizeof(struct vty_io)) ;
-
- /* Zeroising the vty_io structure has set:
- *
- * vty = X -- set to point to parent vty, below
- *
- * name = NULL -- no name, yet TODO ???
- *
- * vin = NULL -- empty input stack
- * vin_base = NULL -- empty input stack
- * vin_depth = 0 -- no stacked vin's, yet
+ /* Zeroising the vty_io structure will set:
*
- * real_depth = 0 -- nothing stacked, yet
+ * vty = X -- set to point to parent vty, below
+ * vio_list = NULLs -- not on the vio_list, yet
*
- * vout = NULL -- empty output stack
- * vout_base = NULL -- empty output stack
- * vout_depth = 0 -- no stacked vout's, yet
+ * vin = NULL -- empty input stack
+ * vin_base = NULL -- empty input stack
+ * vin_depth = 0 -- no stacked vin's, yet
+ * vin_true_depth = 0 -- ditto
*
- * err_hard = false -- no error at all, yet
- * ebuf = NULL -- no error at all, yet
+ * vout = NULL -- empty output stack
+ * vout_base = NULL -- empty output stack
+ * vout_depth = 0 -- no stacked vout's, yet
*
- * vio_list = NULLs -- not on the vio_list, yet
- * mon_list = NULLs -- not on the monitors list
+ * ebuf = NULL -- no error at all, yet
+ * err_depth = 0 -- see below: zero unless VTY_TERMINAL
*
- * blocking = X -- set below: false unless VTY_CONFIG_READ
+ * blocking = false -- set below: false unless VTY_CONFIG_READ
+ * state = vc_stopped -- not started vty command loop
+ * signal = CMD_SUCCESS -- OK (null signal)
+ * close_reason = NULL -- none set
*
- * state = vc_null -- not started vty command loop
+ * ps_buf = NULL -- no pipe stderr return buffer, yet
*
- * close_reason = NULL -- none set
+ * obuf = NULL -- no output buffer, yet
*
- * obuf = NULL -- no output buffer, yet
+ * mon_list = NULLs -- not on the monitors list
+ * monitor = false -- not a monitor
+ * mon_kick = false
+ * maxlvl = 0
+ * mbuf = NULL
*/
- confirm(vc_null == 0) ;
+ vio = XCALLOC(MTYPE_VTY, sizeof(struct vty_io)) ;
+
+ confirm(vc_stopped == 0) ;
+ confirm(CMD_SUCCESS == 0) ;
vty->vio = vio ;
vio->vty = vty ;
- vio->blocking = (type == VTY_CONFIG_READ) ;
+ vio->err_depth = (type == VTY_TERMINAL) ? 1 : 0 ;
+
+ vio->blocking = (type == VTY_CONFIG_READ) ;
/* Place on list of known vio/vty */
sdl_push(vio_live_list, vio, vio_list) ;
@@ -280,19 +249,173 @@ uty_new(vty_type_t type, node_type_t node)
} ;
/*------------------------------------------------------------------------------
+ * Set timeout value.
+ *
+ * This is only ever called when a command (eg exec-timeout) sets a new
+ * time out value -- which applies only to VIN_TERM and VTY_VTYSH.
+ */
+extern void
+uty_set_timeout(vty_io vio, vty_timer_time timeout)
+{
+ vio_in_type_t vt ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ vt = vio->vin_base->vin_type ;
+
+ if ((vt == VIN_TERM) || (vt == VIN_VTYSH))
+ uty_vf_set_read_timeout(vio->vin_base, timeout) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Return "name" of VTY.
+ *
+ * The name of the base vin, or (failing that) the base vout.
+ */
+extern const char*
+uty_get_name(vty_io vio)
+{
+ const char* name ;
+
+ name = vio->vin_base->name ;
+ if (name == NULL)
+ name = vio->vout_base->name ;
+
+ return (name != NULL) ? name : "?" ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Close VTY -- final.
+ *
+ * Turns off any log monitoring immediately.
+ *
+ * Close "final" means: terminate any input immediately, but attempt to flush
+ * any pending output (and any pipe return) but give up if would block or gets
+ * any error.
+ *
+ * To call this the command loop must be vc_stopped. When a command loop exits
+ * normally, it completes all pending I/O and closes the stacks down to, but
+ * not including vout_base. So there is not much to do here. When a vty is
+ * reset, once the command loop has been stopped, this function will close
+ * everything.
+ *
+ * Once the vio stacks are all closed, except for the vout_base, makes some
+ * effort to issue any "close reason" message, then closes the vout_base.
+ *
+ * The vty can then be closed.
+ *
+ * NB: releases the vty, the vio, the exec and all related objects.
+ *
+ * On return DO NOT attempt to do anything with the one-time VTY.
+ */
+extern void
+uty_close(vty_io vio)
+{
+ vty vty = vio->vty ;
+
+ VTY_ASSERT_CAN_CLOSE(vio->vty) ;
+
+ qassert(vio->state == vc_stopped) ;
+
+ /* The vio is about to be closed, so take off the live list and make
+ * sure that is not a log monitor.
+ */
+ uty_set_monitor(vio, off) ;
+ sdl_del(vio_live_list, vio, vio_list) ;
+
+ /* Close all vin including the vin_base.
+ *
+ * Note that the vin_base is closed, but is still on the vin stack.
+ */
+ do
+ uty_vin_pop(vio, NULL, true) ; /* final close, discard context */
+ while (vio->vin != vio->vin_base) ;
+
+ /* Close all the vout excluding the vout_base.
+ */
+ while (vio->vout != vio->vout_base)
+ uty_vout_pop(vio, true) ; /* final close */
+
+ /* If the vout_base is not closed, try to output the close reason,
+ * if any -- note that this will attempt to output, even if some
+ * earlier output has failed.
+ */
+ uty_vout_close_reason(vio->vout_base, vio->close_reason) ;
+
+ /* Now final close the vout_base.
+ */
+ uty_vout_pop(vio, true) ; /* final close */
+
+ /* All should now be very quiet indeed. */
+ if (vty_debug)
+ {
+ assert(vio->vin == vio->vin_base) ;
+ assert(vio->vin_depth == 0) ;
+ assert(vio->vin_true_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) ;
+
+ assert(vty->vio == vio) ;
+ } ;
+
+ /* Release what remains of the vio. */
+ vio->vty = NULL ; /* no longer required. */
+
+ vio->ebuf = vio_fifo_free(vio->ebuf) ;
+ vio->obuf = NULL ; /* about to discard the vout_base */
+ vio->ps_buf = vio_fifo_free(vio->ps_buf) ;
+
+ XFREE(MTYPE_TMP, vio->close_reason) ;
+
+ if (vio->vin != vio->vout)
+ vio->vin = uty_vf_free(vio->vin) ;
+ else
+ vio->vin = NULL ;
+
+ vio->vout = uty_vf_free(vio->vout) ;
+
+ vio->vin_base = NULL ;
+ vio->vout_base = NULL ;
+
+ assert(!vio->monitor) ;
+ vio->mbuf = vio_fifo_free(vio->mbuf) ;
+
+ /* Release the vio and the exec (if any) */
+ XFREE(MTYPE_VTY, vty->vio) ;
+ vty->exec = cmd_exec_free(vty->exec) ;
+
+ /* Finally, release the VTY itself. */
+ XFREE(MTYPE_VTY, vty) ;
+} ;
+
+/*==============================================================================
+ * Pushing and popping vf objects on the vio->vin_stack and vio->vout_stack.
+ */
+
+/*------------------------------------------------------------------------------
* Add a new vf to the vio->vin stack, and set read stuff.
*
- * Sets the vf->vin_type and set vf->read_open.
+ * Sets the given vf->vin_type and set vf->vin_state = vf_open.
*
* Initialises an input buffer if required, and sets line_complete and
* line_step so that first attempt to fetch a line will give line 1.
*
- * Sets the read ready action and the read timer timeout action.
+ * Sets the read ready action and the read timer timeout action, if required.
+ * If the vf is "blocking", then these actions are not required. Also,
+ * if the vin_type is one of the "specials", then these actions are not
+ * required -- noting that the vin_type may be "special" while the vout_type
+ * is a non-blocking ordinary output.
*
* NB: is usually called from the cli thread, but may be called from the cmd
- * thread for vf which is blocking !
+ * thread for vf which is blocking, or for a "special" vin_type !
*
* NB: a uty_cmd_prepare() is required before command processing can continue.
+ *
+ * NB: see uty_vout_push() for notes on pushing a vin and a vout together.
*/
extern void
uty_vin_push(vty_io vio, vio_vf vf, vio_in_type_t type,
@@ -303,20 +426,20 @@ uty_vin_push(vty_io vio, vio_vf vf, vio_in_type_t type,
vf->vin_type = type ;
vf->vin_state = vf_open ;
- assert(type != VIN_NONE) ;
+ qassert(type != VIN_NONE) ;
- if ((type < VIN_SPECIALS) && (!vf->blocking))
+ if ((!vf->blocking) && (vf->vin_type < VIN_SPECIALS))
{
vio_vfd_set_read_action(vf->vfd, read_action) ;
vio_vfd_set_read_timeout_action(vf->vfd, read_timer_action) ;
} ;
ssl_push(vio->vin, vf, vin_next) ;
- vio->real_depth = ++vio->vin_depth ;
+ vio->vin_true_depth = ++vio->vin_depth ;
if (vio->vin_base == NULL)
{
- assert(vio->vin_depth == 1) ;
+ qassert(vio->vin_depth == 1) ;
vio->vin_base = vf ;
} ;
@@ -350,32 +473,45 @@ uty_vin_push(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)
{
- assert(vio->vin->context == NULL) ;
+ qassert(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.
+ * Sets the vf->vout_type and set vf->vout_state = vf_open.
*
- * Sets the write ready action and the write timer timeout action.
+ * Sets the write ready action and the write timer timeout action, if required.
+ * If the vf is "blocking", then these actions are not required. Also,
+ * if the vout_type is one of the "specials", then these actions are not
+ * required -- noting that the vout_type may be "special" while the vin_type
+ * is a non-blocking ordinary input.
*
* Initialises an output buffer and sets an end_mark.
*
- * The depth_mark is set to the current vio->vin_depth + 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.
+ * The new vout_depth depends on whether a vin and vout are being opened
+ * together, and if so, in what order they are pushed:
+ *
+ * * for a vout being opened on its own (e.g. VOUT_CONFIG_WRITE) the
+ * vout_depth is set to the current vin_depth + 1. This means that
+ * when the current command completes, vout_depth > vin_depth, so the
+ * vout will automatically be closed.
+ *
+ * * for a vout being opened at the same time as a vin, the vout_depth
+ * is set to the same as the *new* vin_depth. This means that the
+ * vout will not be closed until the vin is.
+ *
+ * If the vout is opened before the vin, the new vout_depth will be
+ * vin_depth + 1.
*
- * NB: where a vin and vout are opened together, so the vout should NOT
- * be closed until after the vin, need to call uty_vout_sync_depth()
- * *both* the vin and the vout are pushed, in order to set the correct
- * depth_mark.
+ * If the vout is opened after the vin, the new vout_depth will be
+ * vin_depth.
+ *
+ * ...hence the "after" argument.
*
* NB: is usually called from the cli thread, but may be called from the cmd
- * thread for vf which is blocking !
+ * thread for vf which is blocking, or for a "special" vout_type !
*
* NB: VOUT_DEV_NULL, VOUT_STDOUT and VOUT_STDERR are special.
*
@@ -393,278 +529,46 @@ 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)
+ usize obuf_size,
+ bool after)
{
VTY_ASSERT_LOCKED() ;
vf->vout_type = type ;
vf->vout_state = vf_open ;
- assert(type != VOUT_NONE) ;
+ qassert(type != VOUT_NONE) ;
- if ((type < VOUT_SPECIALS) && (!vf->blocking))
+ if ((!vf->blocking) && (vf->vout_type < VOUT_SPECIALS))
{
vio_vfd_set_write_action(vf->vfd, write_action) ;
vio_vfd_set_write_timeout_action(vf->vfd, write_timer_action) ;
} ;
ssl_push(vio->vout, vf, vout_next) ;
- ++vio->vout_depth ;
-
if (vio->vout_base == NULL)
{
- assert(vio->vout_depth == 1) ;
+ qassert(vio->vout_depth == 0) ;
vio->vout_base = vf ;
} ;
vf->obuf = vio_fifo_new(obuf_size) ;
vio_fifo_set_end_mark(vf->obuf) ;
- vf->depth_mark = vio->vin_depth + 1 ;
-
- vio->obuf = vf->obuf ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Synchronise vout->depth_mark to current vin_depth.
- *
- * This *must* be called after a vin and vout have been opened together, and
- * they are intended to be at the same depth. This applies when the base
- * vin/vout are opened, and when an in pipe and an out pipe are present together
- * on a command line.
- */
-extern void
-uty_vout_sync_depth(vty_io vio)
-{
- vio->vout->depth_mark = vio->vin_depth ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Set timeout value.
- *
- * This is only ever called when a command (eg exec-timeout) sets a new
- * time out value -- which applies only to VIN_TERM and VTY_VTYSH.
- */
-extern void
-uty_set_timeout(vty_io vio, vty_timer_time timeout)
-{
- vio_in_type_t vt ;
-
- VTY_ASSERT_LOCKED() ;
-
- vt = vio->vin_base->vin_type ;
-
- if ((vt == VIN_TERM) || (vt == VIN_VTYSH))
- uty_vf_set_read_timeout(vio->vin_base, timeout) ;
-} ;
+ vf->depth_mark = vio->vout_depth ; /* remember *old* depth */
-/*------------------------------------------------------------------------------
- * Return "name" of VTY.
- *
- * The name of the base vin, or (failing that) the base vout.
- */
-extern const char*
-uty_get_name(vty_io vio)
-{
- const char* name ;
-
- name = vio->vin_base->name ;
- if (name == NULL)
- name = vio->vout_base->name ;
-
- return (name != NULL) ? name : "?" ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Close VTY -- final.
- *
- * Two forms: "curtains" and "not-curtains".
- *
- * 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.
- *
- * 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.
- *
- * 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.
- *
- * This close is called by:
- *
- * * uty_reset() -- SIGHUP -- !curtains
- *
- * Will close "final" everything except vout_base.
- *
- * 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().
- *
- * If command loop has already stopped, then will proceed to complete
- * close -- see below.
- *
- * * uty_reset() -- SIGTERM -- curtains
- *
- * Will close "final" everything except vout_base.
- *
- * The command loop will be set stopped, and will proceed to complete
- * close -- see below.
- *
- * * vty_cmd_loop_exit() -- when the command loop has stopped -- !curtains
- *
- * 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.
- *
- * The command loop may have stopped:
- *
- * - 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
- *
- * 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.
- *
- * vout_base will have been closed, but not "final", so will be sitting
- * in vf_closing state.
- *
- * 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 void
-uty_close(vty_io vio, const char* reason, bool curtains)
-{
- VTY_ASSERT_CAN_CLOSE(vio->vty) ;
-
- /* Stamp on any monitor output instantly. */
- uty_set_monitor(vio, off) ;
-
- /* 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) ;
-
- /* 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) ;
-
- /* Close all vin including the vin_base.
- *
- * Note that the vin_base is closed, but is still on the vin stack.
- */
- do
- uty_vin_pop(vio, true, NULL) ; /* final close, discard context */
- while (vio->vin != vio->vin_base) ;
-
- /* 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 ;
-
- /* 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) ;
-
- /* 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)
+ if (after)
{
- assert(vio->vin == vio->vin_base) ;
- 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) ;
- } ;
-
- /* Can dispose of these now -- leave vin/vout for final disposition */
- vio->vty->exec = cmd_exec_free(vio->vty->exec) ;
- vio->ebuf = vio_fifo_free(vio->ebuf) ;
-
- /* Command loop is not running, so can place on death watch for final
- * disposition.
- */
- if (vio->state == vc_stopped)
+ qassert(vio->vout_depth < vio->vin_depth) ;
+ vio->vout_depth = vio->vin_depth ; /* set new depth */
+ }
+ else
{
- vio->state = vc_closed ;
-
- sdl_del(vio_live_list, vio, vio_list) ;
- sdl_push(vio_death_watch, vio, vio_list) ;
+ qassert(vio->vout_depth <= vio->vin_depth) ;
+ vio->vout_depth = vio->vin_depth + 1 ; /* set new depth */
} ;
- assert(vio->state == vc_closed) ; /* thank you and good night */
-} ;
-
-/*------------------------------------------------------------------------------
- * Dispose unwanted vty.
- *
- * Called from deathwatch -- must already be removed from deathwatch list.
- */
-static vty_io
-uty_dispose(vty_io vio)
-{
- VTY_ASSERT_LOCKED() ;
-
- assert(vio->state == vc_closed) ;
-
- /* Stop pointing at vout_base obuf */
- vio->obuf = NULL ;
-
- /* Clear out vout and vin (may be the same) */
- assert(vio->vin == vio->vin_base) ;
- vio->vin_base = uty_vf_free(vio->vin_base) ;
-
- assert(vio->vout == vio->vout_base) ;
- if (vio->vout != vio->vin)
- vio->vout_base = uty_vf_free(vio->vout_base) ;
-
- vio->vin = NULL ;
- vio->vout = NULL ;
-
- /* Remainder of contents of the vio */
- vio->ebuf = vio_fifo_free(vio->ebuf) ;
-
- /* Really cannot be a monitor any more ! */
- assert(!vio->monitor) ;
- vio->mbuf = vio_fifo_free(vio->mbuf) ;
-
- XFREE(MTYPE_VTY, vio) ;
-
- return NULL ;
+ vio->obuf = vf->obuf ;
} ;
/*------------------------------------------------------------------------------
@@ -677,18 +581,19 @@ uty_dispose(vty_io vio)
* 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.
*
- * On final close, will completely close the input, even if errors occur (and
- * no errors are posted).
+ * On final close, will not wait for I/O, will completely close the input,
+ * even if errors occur (and no errors are posted) and will return CMD_SUCCESS.
*
- * Returns: CMD_SUCCESS -- input completely closed
- * CMD_WAITING -- waiting for input to close <=> not-blocking
- * (not if final)
- * CMD_IO_ERROR -- error or timeout
+ * Returns: CMD_SUCCESS -- input completely closed and popped
+ * CMD_WAITING -- waiting for input to close <=> non-blocking
+ * <=> not "final"
+ * CMD_IO_ERROR -- error or timeout <=> try again
+ * <=> not "final"
*
* NB: a uty_cmd_prepare() is required before command processing can continue.
*/
extern cmd_return_code_t
-uty_vin_pop(vty_io vio, bool final, cmd_context context)
+uty_vin_pop(vty_io vio, cmd_context context, bool final)
{
cmd_return_code_t ret ;
@@ -716,8 +621,8 @@ uty_vin_pop(vty_io vio, bool final, cmd_context context)
vio->vin_depth = 0 ; /* may already have been closed */
} ;
- if (vio->real_depth > vio->vin_depth)
- vio->real_depth = vio->vin_depth ;
+ if (vio->vin_true_depth > vio->vin_depth)
+ vio->vin_true_depth = vio->vin_depth ;
if (vio->vin->context != NULL)
{
@@ -750,14 +655,16 @@ uty_vin_pop(vty_io vio, bool final, cmd_context context)
* output.
*
* Unless "final", the close is soft, that is, if there is any output still
- * outstanding does not actually close the vout.
+ * outstanding does not close the vout.
*
* If there is no outstanding output (or if final) will completely close the
* 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
+ * Returns: CMD_SUCCESS -- output completely closed and popped
+ * CMD_WAITING -- waiting for input to close <=> non-blocking
+ * <=> not "final"
+ * CMD_IO_ERROR -- error or timeout <=> try again
+ * <=> not "final"
*
* NB: a uty_cmd_prepare() is required before command processing can continue.
*
@@ -775,32 +682,32 @@ uty_vout_pop(vty_io vio, bool final)
ret = uty_vf_write_close(vio->vout, final) ;
- if ((ret == CMD_SUCCESS) || final)
- {
- if (vio->vout_depth > 1)
- {
- vio_vf vf ;
+ if (ret != CMD_SUCCESS)
+ return ret ;
- vf = ssl_pop(&vf, vio->vout, vout_next) ;
- --vio->vout_depth ;
+ if (vio->vout_depth > 1)
+ {
+ vio_vf vf ;
- uty_vf_free(vf) ;
- }
- else
- {
- assert(vio->vout == vio->vout_base) ;
- if (final)
- assert(vio->vout->vout_state == vf_closed) ;
+ vf = ssl_pop(&vf, vio->vout, vout_next) ;
- vio->vout_depth = 0 ; /* may already have been closed */
+ qassert(vf->depth_mark < vio->vout_depth) ;
+ vio->vout_depth = vf->depth_mark ;
- assert(vio->vin_depth == 0) ;
- vio->vout->depth_mark = 0 ; /* align with the end stop */
- } ;
+ uty_vf_free(vf) ;
+ }
+ else
+ {
+ assert(vio->vout == vio->vout_base) ;
+ if (final)
+ assert(vio->vout->vout_state == vf_closed) ;
- vio->obuf = vio->vout->obuf ;
+ qassert(vio->vout->depth_mark == 0) ;
+ vio->vout_depth = 0 ; /* may already be */
} ;
+ vio->obuf = vio->vout->obuf ;
+
return ret ;
} ;
@@ -841,7 +748,7 @@ uty_vout_close_reason(vio_vf vf, const char* reason)
case VOUT_FILE:
case VOUT_PIPE:
- case VOUT_SHELL_ONLY:
+ case VOUT_SH_CMD:
break ;
case VOUT_CONFIG:
@@ -897,15 +804,18 @@ uty_vout_close_reason(vio_vf vf, const char* reason)
* 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.
+ * NB: if the parent vio is blocking, then the vf will be vfd_io_ps_blocking,
+ * and so will any vfd. An individual vf may be set blocking by setting
+ * vfd_io_blocking or vfd_io_ps_blocking in the io_type.
+ *
+ * For vfd_io_ps_blocking, the vfd stuff handles all files, pipes etc.
+ * non-blocking. The 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 blocking
+ * vfd (of either type) are not allowed to set read/write ready.
*
- * 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.
+ * NB: the name is XSTRDUP() into the vio -- so the caller is responsible for
+ * disposing of its copy, if required.
*/
extern vio_vf
uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type,
@@ -940,43 +850,39 @@ uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type,
* vout_state = vf_closed -- see uty_vout_push()
* vout_next = NULL -- see uty_vout_push()
*
- * pr_master = NULL -- none -- see uty_pipe_write_open()
- *
* obuf = NULL -- none -- see uty_vout_push()
*
* depth_mark = 0 -- see uty_vout_push()
*
- * vfd = NULL -- no vfd, yet
- *
* blocking = X -- see below
- * closing = false -- not on the closing list, yet.
*
- * error_seen = 0 -- no error seen, yet
+ * vfd = NULL -- no vfd, yet
*
* read_timeout = 0 -- none
* write_timeout = 0 -- none
*
- * child = 0 -- none )
- * terminated = false -- not ) -- see uty_pipe_read/write_open()
- * term_status = X -- none )
+ * child = NULL -- none -- see uty_pipe_read/write_open()
*
* 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_vfd = NULL -- no vfd -- ditto
+ * pr_timeout = 0 -- none -- ditto
*
- * pr_only = false -- see uty_pipe_read/write_open()
+ * ps_state = vf_closed -- no pipe stderr return vfd
+ * -- see uty_pipe_read/write_open()
+ * ps_vfd = NULL -- no vfd -- ditto
+ * ps_timeout = 0 -- none -- ditto
+ * ps_buf = NULL -- none, yet
*/
confirm((VIN_NONE == 0) && (VOUT_NONE == 0)) ;
confirm(vf_closed == 0) ;
confirm(QSTRING_INIT_ALL_ZEROS) ;
if (vio->blocking)
- io_type |= vfd_io_blocking ; /* inherit blocking state */
+ io_type |= vfd_io_ps_blocking ; /* inherit blocking state */
vf->vio = vio ;
- vf->blocking = (io_type & vfd_io_blocking) != 0 ;
+ vf->blocking = (io_type & (vfd_io_blocking | vfd_io_ps_blocking)) != 0 ;
if (name != NULL)
vf->name = XSTRDUP(MTYPE_VTY_NAME, name) ;
@@ -1007,16 +913,13 @@ uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type,
*
* 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
+ * ignored. Final close always returns CMD_SUCCESS.
*
- * NB: on "final" close returns CMD_SUCCESS no matter what happened, and all
- * input will have been closed down, and the vf closed.
+ * Returns: CMD_SUCCESS -- closed
+ * CMD_WAITING -- waiting to complete close <=> non-blocking
+ * <=> not "final"
+ * CMD_IO_ERROR -- error or timeout <=> try again
+ * <=> not "final"
*/
static cmd_return_code_t
uty_vf_read_close(vio_vf vf, bool final)
@@ -1030,7 +933,10 @@ uty_vf_read_close(vio_vf vf, bool final)
if (vf->vin_state == vf_closed)
return ret ; /* quit if already closed */
- vf->vin_state = vf_closing ; /* TODO wipes out error etc ? */
+ if (vf->vin_state == vf_open)
+ vf->vin_state = vf_end ; /* don't try to read any more ! */
+ else
+ qassert(vf->vin_state == vf_end) ;
/* Do the vfd level read close and mark the vf no longer read_open */
if (vf->vin_type < VIN_SPECIALS)
@@ -1075,6 +981,8 @@ uty_vf_read_close(vio_vf vf, bool final)
{
vf->vin_state = vf_closed ;
assert(vf->pr_state == vf_closed) ;
+
+ ret = CMD_SUCCESS ;
} ;
return ret ;
@@ -1102,24 +1010,21 @@ uty_vf_read_close(vio_vf vf, bool final)
*
* 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".
+ * ignored. Will close the vfd and return CMD_SUCCESS.
*
* 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 vfd -- so the vout_base will still work and is left vf_open.
* 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.
+ * Returns: CMD_SUCCESS -- closed
+ * CMD_WAITING -- waiting to complete close <=> non-blocking
+ * <=> not "final"
+ * CMD_IO_ERROR -- error or timeout <=> try again
+ * <=> not "final"
*
* NB: must not have open vins at this or a higher level in the stack.
*
@@ -1136,22 +1041,20 @@ uty_vf_write_close(vio_vf vf, bool final)
ret = CMD_SUCCESS ;
if (vf->vout_state == vf_closed)
- return ret ; /* quit if already closed */
-
- vf->vout_state = vf_closing ; /* TODO wipes out error etc ? */
+ return ret ; /* quit if already closed */
base = (vf == vf->vio->vout_base) ;
- /* Must close vin before closing vout at the same level.
+ /* Must always 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).
+ * slave to a VOUT_PIPE/VOUT_SH_CMD that vout must have been closed
+ * already.
*/
- assert( (vf->vio->vin_depth < vf->vio->vout->depth_mark)
- || (vf->vio->vout_depth ==0) ) ;
- assert(vf->pr_master == NULL) ;
+ qassert( (vf->vio->vin_depth < vf->vio->vout_depth)
+ || ((vf->vio->vin_depth == 0) && (vf->vio->vout_depth == 0)) ) ;
+ qassert(vf->vin_state == vf_closed) ;
+ qassert(vf->vio->obuf == vf->obuf) ;
/* 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.
@@ -1161,8 +1064,10 @@ uty_vf_write_close(vio_vf vf, bool final)
/* The vout_type specific close functions will attempt to write
* everything away.
*
- * If "final", will only keep going until blocks -- at which point will
+ * If "final", will only keep going until would block -- at which point will
* bring everything to a shuddering halt.
+ *
+ * NB: at this point vout_state is not vf_closed.
*/
switch(vf->vout_type)
{
@@ -1171,24 +1076,20 @@ uty_vf_write_close(vio_vf vf, bool final)
break ;
case VOUT_TERM:
- ret = uty_term_write_close(vf, final, base) ;
+ ret = uty_term_write_close(vf, final) ;
break ;
case VOUT_VTYSH:
break ;
case VOUT_FILE:
- ret = uty_file_write_close(vf, final, base) ;
+ case VOUT_CONFIG:
+ ret = uty_file_write_close(vf, final) ;
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 */
+ case VOUT_SH_CMD:
+ ret = uty_pipe_write_close(vf, final) ;
break ;
case VOUT_DEV_NULL:
@@ -1201,21 +1102,16 @@ uty_vf_write_close(vio_vf vf, bool final)
zabort("unknown VOUT type") ;
} ;
- assert(vf->vin_state == vf_closed) ;
-
if (((ret == CMD_SUCCESS) && !base) || final)
{
-
- 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 ;
+
+ ret = CMD_SUCCESS ;
} ;
return ret ;
@@ -1234,7 +1130,8 @@ 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)) ;
+ && (vf->pr_state == vf_closed)
+ && (vf->ps_state == vf_closed)) ;
XFREE(MTYPE_VTY_NAME, vf->name) ;
@@ -1248,49 +1145,18 @@ uty_vf_free(vio_vf vf)
vf->vfd = vio_vfd_close(vf->vfd) ; /* for completeness */
vf->pr_vfd = vio_vfd_close(vf->pr_vfd) ; /* for completeness */
+ vf->ps_vfd = vio_vfd_close(vf->ps_vfd) ; /* for completeness */
+
+ vf->ps_buf = vio_fifo_free(vf->ps_buf) ;
XFREE(MTYPE_VTY, vf) ;
return NULL ;
} ;
-
-
-
-/*------------------------------------------------------------------------------
- * Dealing with an I/O error on VTY socket
- *
- * If this is the first error for this VTY, produce suitable log message.
- *
- * If is a "monitor", turn that off, *before* issuing log message.
+/*==============================================================================
+ * vio_vf level read/write ready and timeout setting
*/
-extern int
-uty_vf_error(vio_vf vf, const char* what, int err)
-{
- vty_io vio = vf->vio ;
-
- VTY_ASSERT_LOCKED() ;
- VTY_ASSERT_CLI_THREAD() ;
-
- /* can no longer be a monitor ! *before* any logging ! */
- uty_set_monitor(vio, off) ;
-
- /* if this is the first error, log it */
- if (vf->error_seen == 0)
- {
- const char* type = "?" ; /* TODO */
-
-
- vf->error_seen = err ;
- 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.
@@ -1340,7 +1206,7 @@ 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 how)
{
- if ((vf->vout_state != vf_open) && (vf->vout_state != vf_closing))
+ if (vf->vout_state != vf_open)
how = off ;
if (vf->vout_state != vf_closed)
@@ -1363,6 +1229,299 @@ uty_vf_set_write_timeout(vio_vf vf, vty_timer_time write_timeout)
} ;
/*==============================================================================
+ * I/O error and timeout reporting.
+ *
+ * Posts error information and locus to the vio, which is signalled by a
+ * CMD_IO_ERROR return code.
+ */
+
+/*------------------------------------------------------------------------------
+ * Dealing with an I/O error or time-out on the given vio_vf:
+ *
+ * * sets the vf->vin_state, vf->vout_state, vf->pr_state or vf->ps_state to
+ * vf_end
+ *
+ * Note that this does not signal the error -- it means that any further
+ * I/O on this vin/vout is to be avoided.
+ *
+ * Errors and timeouts in either vin or vout also force any pipe return
+ * and/or pipe stderr return to vf_end.
+ *
+ * Errors and timeouts in either pipe return or pipe stderr return also
+ * force all of vin, vout, pipe return and pipe stderr return to vf_end.
+ *
+ * * if vio is a "monitor", turn that off, *before* issuing log message
+ * wrt to either the vin_base or the vout_base.
+ *
+ * * produce suitable log message.
+ *
+ * * insert suitable message in vio->ebuf
+ *
+ * * set the vio->err_depth to 0 if this is an error in vin_base or
+ * vout_base, or to no more than 1 for errors anywhere else.
+ *
+ * * signals CMD_IO_ERROR to the command loop -- which is how the pselect()
+ * process communicates with the command loop.
+ *
+ * Returns: CMD_IO_ERROR -- which may be returned to the command loop, as
+ * well as the vio->signal.
+ */
+extern cmd_return_code_t
+uty_vf_error(vio_vf vf, vio_err_type_t err_type, int err)
+{
+ vty_io vio = vf->vio ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* Set the error level and, if required, turn off any log monitoring *before*
+ * issuing any logging message.
+ */
+ if ((vf == vio->vin_base) || (vf == vio->vout_base))
+ {
+ vio->err_depth = 0 ;
+ uty_set_monitor(vio, off) ;
+ }
+ else
+ {
+ if (vio->err_depth > 1)
+ vio->err_depth = 1 ;
+ } ;
+
+ /* Set vf->vin_state, vf->vout_state or vf->pr_state, as required.
+ */
+ switch (err_type)
+ {
+ case verr_none:
+ zabort("verr_io_none invalid") ;
+ break ;
+
+ case verr_io_vin:
+ case verr_to_vin:
+ qassert(vf->vin_state != vf_closed) ;
+
+ vf->vin_state = vf_end ;
+ break ;
+
+ case verr_io_vout:
+ case verr_to_vout:
+ qassert(vf->vout_state != vf_closed) ;
+
+ vf->vout_state = vf_end ;
+ break ;
+
+ case verr_io_pr:
+ case verr_to_pr:
+ qassert(vf->pr_state != vf_closed) ;
+ uty_pipe_return_stop(vf) ;
+ break ;
+
+ case verr_io_ps:
+ case verr_to_ps:
+ qassert(vf->ps_state != vf_closed) ;
+ uty_pipe_return_stop(vf) ;
+ break ;
+
+ default:
+ zabort("unknown verr_xxxx") ;
+ } ;
+
+ /* If there is a pipe return (still active), then stop it now -- no point
+ * continuing after main vin/vout has failed.
+ *
+ * Ditto pipe stderr return.
+ */
+ if (vf->pr_state == vf_open)
+ vf->pr_state = vf_end ;
+
+ if (vf->ps_state == vf_open)
+ vf->ps_state = vf_end ;
+
+ /* Log the error and add an error message to the vio->ebuf.
+ */
+ zlog_warn("%s", uty_error_message(vf, err_type, err, true).str) ;
+
+ vio_fifo_printf(uty_cmd_get_ebuf(vio), "\n%s\n",
+ uty_error_message(vf, err_type, err, false).str) ;
+
+ /* Signal to the command loop, if required, and return CMD_IO_ERROR.
+ *
+ * One or both will be collected in the command loop "hiatus" and dealt
+ * with -- it does not matter if both arrive.
+ */
+ uty_cmd_signal(vio, CMD_IO_ERROR) ;
+
+ return CMD_IO_ERROR ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Construct error message for given I/O or time-out error
+ */
+extern verr_mess_t
+uty_error_message(vio_vf vf, vio_err_type_t err_type, int err, bool log)
+{
+ QFB_QFS(verr_mess, qfs) ;
+
+ const char* name ;
+ const char* where ;
+ const char* what ;
+ bool vout ;
+ bool io ;
+ int fd ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ vout = false ;
+ fd = -1 ;
+ what = NULL ;
+
+ switch (err_type & verr_mask)
+ {
+ case verr_vin:
+ vout = false ;
+ what = "read" ;
+ if (log && (vf->vfd != NULL))
+ fd = vio_vfd_fd(vf->vfd) ;
+ break ;
+
+ case verr_vout:
+ vout = true ;
+ what = "write" ;
+ if (log && (vf->vfd != NULL))
+ fd = vio_vfd_fd(vf->vfd) ;
+ break ;
+
+ case verr_pr:
+ vout = true ;
+ what = "pipe return" ;
+ if (log)
+ fd = vio_vfd_fd(vf->pr_vfd) ;
+ break ;
+
+ case verr_ps:
+ vout = (vf->vout_state != vf_closed) ;
+ what = "stderr return" ;
+ if (log)
+ fd = vio_vfd_fd(vf->ps_vfd) ;
+ break ;
+ } ;
+
+ name = vf->name ;
+ where = NULL ;
+
+ io = (err_type & verr_to) == 0 ;
+ confirm((verr_to != 0) && (verr_io == 0)) ;
+
+ if (vout)
+ {
+ switch (vf->vout_type)
+ {
+ case VOUT_NONE:
+ zabort("VOUT_NONE invalid") ;
+ break ;
+
+ case VOUT_TERM:
+ where = "Terminal" ;
+ if (!log)
+ name = NULL ;
+ break ;
+
+ case VOUT_VTYSH:
+ where = "VTY Shell" ;
+ if (!log)
+ name = NULL ;
+ break ;
+
+ case VOUT_FILE:
+ where = "File" ;
+ break ;
+
+ case VOUT_PIPE:
+ where = "Pipe" ;
+ break ;
+
+ case VOUT_CONFIG:
+ where = "Configuration file" ;
+ break ;
+
+ case VOUT_DEV_NULL:
+ where = "/dev/null" ;
+ break ;
+
+ case VOUT_SH_CMD:
+ where = "Command" ;
+ break ;
+
+ case VOUT_STDOUT:
+ where = "stdout" ;
+ break ;
+
+ case VOUT_STDERR:
+ where = "stderr" ;
+ break ;
+
+ default:
+ zabort("unknown vout_type") ;
+ break ;
+ } ;
+ }
+ else
+ {
+ switch (vf->vin_type)
+ {
+ case VIN_NONE:
+ zabort("VIN_NONE invalid") ;
+ break ;
+
+ case VIN_TERM:
+ where = "Terminal" ;
+ if (!log)
+ name = NULL ;
+ break ;
+
+ case VIN_VTYSH:
+ where = "VTY Shell" ;
+ if (!log)
+ fd = vio_vfd_fd(vf->vfd) ;
+ break ;
+
+ case VIN_FILE:
+ where = "File" ;
+ break ;
+
+ case VIN_PIPE:
+ where = "Pipe" ;
+ break ;
+
+ case VIN_CONFIG:
+ where = "Configuration file" ;
+ break ;
+
+ case VIN_DEV_NULL:
+ where = "/dev/null" ;
+ break ;
+
+ default:
+ zabort("unknown vin_type") ;
+ break ;
+ } ;
+ } ;
+
+ qfs_printf(qfs, "%s %s %s", where, what, io ? "I/O error" : "time-out") ;
+
+ if (name != NULL)
+ qfs_printf(qfs, " '%s'", name) ;
+
+ if (fd >= 0)
+ qfs_printf(qfs, " (fd=%d)", fd) ;
+
+ if (io && (err != 0))
+ qfs_printf(qfs, ": %s", errtoa(err, 0).str) ;
+
+ qfs_term(qfs) ;
+ return verr_mess ;
+} ;
+
+/*==============================================================================
* Child care.
*
* Management of vio_child objects and the vio_childer_list.
@@ -1371,13 +1530,29 @@ uty_vf_set_write_timeout(vio_vf vf, vty_timer_time write_timeout)
* 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:
+ * When SIGCHLD events go off, the return code for the child is collected,
+ * and saved until the pipe I/O wants it.
+ *
+ * When pipe I/O is closing a pipe and requires the return code from the
+ * child, if the child has not already been collected (after a SIGCHLD),
+ * then:
+ *
+ * * for blocking vf (configuration reading), uty_child_collect() is used,
+ * in which a mini-pselect is used to wait and time-out. A SIGCHLD will
+ * wake up the CLI pthread (or the only thread if not multi-pthreaded)
+ * and, if required, that will wake the waiting pthread by sending a
+ * Quagga SIG_INTERRUPT to it.
+ *
+ * * for non-blocking vf, utf_child_awaited() is used, in which a time-out
+ * timer is set, in case the SIGCHLD does not arrive in good time. When
+ * the child is collected or the timer goes off, a uty_cmd_signal() is
+ * sent.
*
- * TODO
+ * When the parent sees that the child is "collected" or "overdue", it can
+ * examine any report and then dismiss the child.
*
- * When a child is collected, or becomes overdue, the parent is signalled (if
- * required) and the child->awaited state is cleared.
+ * NB: time-out while waiting to collect a child is not treated as an error,
+ * here -- so no uty_vf_error() is signalled.
*
* 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
@@ -1388,14 +1563,49 @@ uty_vf_set_write_timeout(vio_vf vf, vty_timer_time write_timeout)
* 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.)
+
+ * The state of a vio_child object includes:
+ *
+ * parent -- pointer to the parent vf, if any -- set when the child is
+ * registered.
+ *
+ * A NULL parent pointer <=> the child is an orphan.
+ *
+ * At "curtains" all children are orphaned.
+ *
+ * collected -- this is set true when the child termination code is picked
+ * up (by uty_waitpid). It is forced true at "curtains".
+ *
+ * When a child is collected it is removed from the register.
+ *
+ * Once removed from the register, a child is the responsibility
+ * of its parent, if any.
+ *
+ * When an orphan is removed from the register it can be freed.
+ *
+ * awaited -- this is true iff the parent is non-blocking and is now
+ * waiting to collect the child.
+ *
+ * "awaited" <=> there is a timer running.
+ *
+ * When the timer goes off "awaited" is cleared, but the timer
+ * still exists (but is not running).
+ *
+ * overdue -- this is set true if times out waiting for child to be
+ * collected, or can wait no longer ("final" close).
*/
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) ;
+static void uty_child_free(vio_child child) ;
+static pid_t uty_waitpid(pid_t for_pid, int* p_report) ;
/*------------------------------------------------------------------------------
- * Set vty_child_signal_nexus() -- if required.
+ * Set the vty_child_signal_nexus() -- if required.
+ *
+ * The SIGCHLD signal will wake up the cli thread. For blocking I/O (reading
+ * configuration file) may need to wake up another thread, for which the
+ * SIG_INTERRUPT signal is used.
*
* 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().
@@ -1405,7 +1615,7 @@ uty_child_signal_nexus_set(vty_io vio)
{
VTY_ASSERT_LOCKED() ;
- assert(vio->blocking) ;
+ qassert(vio->blocking) ;
if (!vty_is_cli_thread())
{
@@ -1418,8 +1628,7 @@ uty_child_signal_nexus_set(vty_io vio)
} ;
/*------------------------------------------------------------------------------
- * If there is a nexus to signal, clear the indicator and signal the
- * associated thread.
+ * If there is a nexus to signal, do that.
*/
extern void
vty_child_signal_nexus_signal(void)
@@ -1434,7 +1643,7 @@ vty_child_signal_nexus_signal(void)
}
/*------------------------------------------------------------------------------
- * Set vty_child_signal_nexus() -- if required.
+ * Clear the 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().
@@ -1485,7 +1694,6 @@ uty_child_register(pid_t pid, vio_vf parent)
* overdue -- false -- child not overdue
* timer -- NULL -- no waiting timer set
*/
-
child->parent = parent ;
child->pid = pid ;
@@ -1495,30 +1703,74 @@ uty_child_register(pid_t pid, vio_vf parent)
} ;
/*------------------------------------------------------------------------------
- * Set waiting for child to be collected.
+ * Set waiting for child to be collected -- if not already set.
+ *
+ * This is for !vf->blocking.
*
- * This is for !vf->blocking: set timer and leave, waiting for SIGCHLD event.
+ * If not already waiting for child, set timer.
+ * If is already waiting, leave existing timer running.
*/
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) ;
+ qassert(child->parent != NULL) ;
+ qassert(!child->parent->blocking) ;
- child->awaited = true ;
+ if (child->awaited)
+ {
+ qassert(child->timer != NULL) ;
+ }
+ else
+ {
+ child->awaited = true ;
- if (child->timer == NULL)
- child->timer = vio_timer_init_new(NULL, vty_child_overdue, child) ;
+ if (child->timer == NULL)
+ child->timer = vio_timer_init_new(NULL, vty_child_overdue, child) ;
- vio_timer_set(child->timer, timeout) ;
+ vio_timer_set(child->timer, timeout) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Clear waiting for child to be collected, if that is set, and discard
+ * any timer.
+ *
+ * NB: child may already be orphaned.
+ *
+ * NB: timer is not discarded when goes off, so will still exist, even
+ * though is no longer "awaited".
+ *
+ * If has a timer, then must be in the CLI thread -- which will be, because
+ * all !vf->blocking child handling is done in the CLI thread.
+ */
+static void
+uty_child_not_awaited(vio_child child)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if (child->awaited) /* "awaited" => not orphan & timer running */
+ {
+ qassert(child->parent != NULL) ;
+ qassert(!child->parent->blocking) ;
+ qassert(child->timer != NULL) ;
+
+ child->awaited = false ;
+ } ;
+
+ if (child->timer != NULL) /* => in CLI thread */
+ {
+ VTY_ASSERT_CLI_THREAD() ;
+
+ child->timer = vio_timer_reset(child->timer, free_it) ;
+ } ;
} ;
/*------------------------------------------------------------------------------
* See if parent can collect child -- directly.
*
- * This is for vf->blocking,
+ * This is for vf->blocking, or for "final" !vf->blocking.
*
* If can, will collect now -- marking collected and taking off the
* vio_childer_list.
@@ -1528,9 +1780,6 @@ uty_child_awaited(vio_child child, vty_timer_time timeout)
* 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
*/
@@ -1541,71 +1790,35 @@ uty_child_collect(vio_child child, vty_timer_time timeout, bool final)
VTY_ASSERT_LOCKED() ;
- assert(child->parent != NULL) ;
- assert(child->parent->blocking) ;
- assert(child->timer == NULL) ;
+ qassert(child->parent != NULL) ;
+ qassert(child->parent->blocking || final) ;
+ qassert(child->pid > 0) ;
- assert(child->pid > 0) ;
+ uty_child_not_awaited(child) ; /* clear flag & timer */
first = true ;
while (1)
{
- pid_t pid ;
- int report ;
+ pid_t pid ;
+ int report ;
qps_mini_t qm ;
sigset_t* sig_mask = NULL ;
+ /* If already collected, or succeed in collecting, we are done. */
if (child->collected)
- return true ; /* have collected */
+ return true ; /* already collected */
- pid = waitpid(child->pid, &report, WNOHANG) ;
+ pid = uty_waitpid(child->pid, &report) ;
if (pid == child->pid)
{
- /* Collected the child */
- uty_child_collected(child, report) ;
-
- return true ; /* have collected */
+ uty_child_collected(child, report) ; /* not orphan */
+ return true ; /* 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)
+ /* If "final" or got an error, mark child overdue and give up */
+ if (final || (pid < 0))
{
child->overdue = true ;
return false ; /* overdue */
@@ -1633,53 +1846,56 @@ uty_child_collect(vio_child child, vty_timer_time timeout, bool final)
} ;
/*------------------------------------------------------------------------------
- * Dismiss child -- if not collected, smack but leave to be collected in
- * due course (or swept up at "curtains").
+ * Dismiss child
+ *
+ * If already collected, free the child.
+ *
+ * If not collected, smack but leave to be collected in due course (or free
+ * now at "curtains").
+ *
+ * This may be called in any thread -- but must be CLI thread if there is
+ * a timer, that is: must be CLI thread if this is/was non-blocking.
+ *
+ * When the register is closed, is done in CLI thread.
+ *
+ * NB: child will have been freed if was collected or this is "curtains".
*/
-extern vio_child
-uty_child_dismiss(vio_child child, bool final)
+extern void
+uty_child_dismiss(vio_child child, bool curtains)
{
VTY_ASSERT_LOCKED() ;
- if (child != NULL)
- {
- if (!child->collected)
- {
- assert(child->pid > 0) ;
+ uty_child_not_awaited(child) ;
- kill(child->pid, SIGKILL) ; /* hasten the end */
-
- if (final)
- {
- assert(child->parent == NULL) ;
- sdl_del(vio_childer_list, child, list) ;
- child->collected = true ; /* forceably */
- } ;
+ if (child->parent != NULL)
+ {
+ child->parent->child = NULL ;
+ child->parent = NULL ; /* orphan from now on */
+ } ;
- child->overdue = true ; /* too late for parent */
- } ;
+ if (child->collected)
+ uty_child_free(child) ;
+ else
+ {
+ qassert(child->pid > 0) ;
- child->parent = NULL ; /* orphan from now on */
- child->awaited = false ; /* nobody waiting */
+ kill(child->pid, SIGKILL) ; /* hasten the end */
- if (child->collected)
- uty_child_free(child) ;
+ if (curtains)
+ uty_child_collected(child, 0) ; /* orphan: so free it */
} ;
-
- 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.
+ * At "curtains" -- dismiss any children left in the register.
*/
extern void
vty_child_close_register(void)
{
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+
while (vio_childer_list != NULL)
- uty_child_dismiss(vio_childer_list, true) ; /* final */
+ uty_child_dismiss(vio_childer_list, true) ; /* curtains */
} ;
/*------------------------------------------------------------------------------
@@ -1689,11 +1905,11 @@ vty_child_close_register(void)
* 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.
+ * The is done when a SIGCHLD is routed 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.
+ * If another SIGCHLD occurs while this is being done, that will later cause
+ * another call of this function -- which may find that there are no children
+ * to be collected.
*
* This is also done when about to block waiting for a child.
*
@@ -1711,22 +1927,11 @@ uty_sigchld(void)
pid_t pid ;
int report ;
- pid = waitpid(-1, &report, WNOHANG) ;
+ pid = uty_waitpid((pid_t)-1, &report) ;
- if (pid == 0)
+ 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)
{
@@ -1739,23 +1944,16 @@ uty_sigchld(void)
if (child->pid == pid)
{
- /* Have collected child.
+ /* Remove child from register and set "collected". Turn off
+ * any timer and clear "awaited".
*
- * Remove from the vio_childer_list, set collected flag.
+ * If child is not orphaned: set report and signal parent if
+ * required.
*
- * 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.
+ * Otherwise: can free the child now.
*/
uty_child_collected(child, report) ;
- if (child->parent == NULL)
- uty_child_free(child) ;
- else if (child->awaited)
- uty_child_signal_parent(child) ;
-
break ;
} ;
@@ -1765,27 +1963,42 @@ uty_sigchld(void)
} ;
/*------------------------------------------------------------------------------
- * Set the child collected and set the report.
+ * Have collected a child.
+ *
+ * * if a parent is waiting, signal them
+ *
+ * * clear any awaited state and discard any timer.
*
- * Remove from the vio_childer_list -- is now either back in the hands of the
- * parent, or ready to be freed.
+ * * remove from the register
+ *
+ * * if the child has a parent, set the child's report (the parent is now
+ * responsible for the child).
+ *
+ * otherwise, can free the child now.
*/
static void
uty_child_collected(vio_child child, int report)
{
- assert(!child->collected) ; /* can only collect once */
+ qassert(!child->collected) ; /* can only collect once */
+
+ if (child->timer != NULL)
+ VTY_ASSERT_CLI_THREAD() ; /* must be to clear timer */
+
+ if (child->awaited)
+ uty_child_signal_parent(child) ;
+ uty_child_not_awaited(child) ; /* clear flag and timer */
+ child->collected = true ; /* remove from register */
sdl_del(vio_childer_list, child, list) ;
- child->collected = true ;
- child->report = report ;
+ if (child->parent == NULL)
+ uty_child_free(child) ;
+ else
+ 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)
@@ -1793,14 +2006,16 @@ vty_child_overdue(vio_timer timer, void* action_info)
vio_child child ;
VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
child = action_info ;
assert(timer == child->timer) ;
- if (child->awaited)
+ if (child->awaited) /* ignore if no longer awaited */
{
+ uty_child_signal_parent(child) ; /* clears "awaited" */
+
child->overdue = true ;
- uty_child_signal_parent(child) ;
} ;
VTY_UNLOCK() ;
@@ -1809,37 +2024,96 @@ vty_child_overdue(vio_timer timer, void* action_info)
} ;
/*------------------------------------------------------------------------------
- * Signal that child is ready -- collected or overdue.
+ * Signal that child is ready -- collected or overdue -- clears "awaited".
*
* Must be "awaited" -- so not "blocking"
*/
static void
uty_child_signal_parent(vio_child child)
{
- assert(child->awaited && (child->parent != NULL)) ;
+ qassert(child->awaited) ;
+ qassert(child->parent != NULL) ;
+ qassert(!child->parent->vio->blocking) ;
- assert(!child->parent->vio->blocking) ;
+ uty_cmd_signal(child->parent->vio, CMD_SUCCESS) ;
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).
+ * Free the child -- caller must ensure that any parent has dismissed the child,
+ * and that it is collected (so not on the vio_childer_list) and that there
+ * is no timer running.
*/
-static vio_child
+static void
uty_child_free(vio_child child)
{
+ VTY_ASSERT_LOCKED() ;
+
if (child != NULL)
{
- assert(child->collected && (child->parent == NULL)) ;
+ qassert(child->collected) ;
+ qassert(child->parent == NULL) ;
+ qassert(child->timer == NULL) ;
- child->timer = vio_timer_reset(child->timer, free_it) ;
- XFREE(MTYPE_VTY, child) ; /* sets child = NULL */
+ XFREE(MTYPE_VTY, child) ;
} ;
+} ;
- return child ;
+/*------------------------------------------------------------------------------
+ * Wrapper for waitpid() -- deal with and log any errors.
+ */
+static pid_t
+uty_waitpid(pid_t for_pid, int* p_report)
+{
+ pid_t pid ;
+
+ while (1)
+ {
+ pid = waitpid(for_pid, p_report, WNOHANG) ;
+
+ if (pid == 0)
+ return pid ; /* nothing to be had */
+
+ if (pid > 0) /* got an answer */
+ {
+ if ((for_pid < 0) || (pid == for_pid))
+ return pid ;
+
+ /* This is absolutely impossible. If for_pid is > 0, then
+ * the only valid response > 0 is for_pid !!
+ *
+ * Don't know what to do with this, but treating it as a
+ * "nothing to be had" return seems safe.
+ */
+ zlog_err("waitpid(%d) returned pid=%d", for_pid, pid) ;
+
+ return -1 ;
+ } ;
+
+ if (errno == EINTR)
+ continue ; /* loop on "Interrupted" */
+
+ /* Got an error other than EINTR, which is almost impossible...
+ *
+ * (1) ECHILD means that the given pid is not a child or there are
+ * no children.
+ *
+ * This is possible if this is called following a SIGCHLD, and
+ * all children have been collected between the signal and the
+ * uty_sigchld().
+ *
+ * Note that only call uty_waitpid() for a specific child if it
+ * is known to not yet have been collected.
+ *
+ * (2) only other known error is EINVAL -- invalid options...
+ * absolutely impossible.
+ */
+ if ((errno != ECHILD) || (for_pid > 0))
+ zlog_err("waitpid(%d) returned %s", for_pid, errtoa(errno, 0).str) ;
+
+ return -1 ;
+ } ;
} ;
/*==============================================================================
diff --git a/lib/vty_io.h b/lib/vty_io.h
index 59ffb808..5f550837 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 "qfstring.h"
#include "list_util.h"
/*==============================================================================
@@ -72,8 +73,6 @@ enum vio_in_type /* Command input */
VIN_CONFIG, /* config file */
- VIN_PIPE_RETURN, /* */
-
/* The VIN types >= VIN_SPECIALS do not have an associated fd.
*
* These can coexist with a VOUT which does have an associated fd.
@@ -106,7 +105,7 @@ 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_SH_CMD, /* pipe for shell command (no actual output) */
VOUT_STDOUT, /* stdout */
VOUT_STDERR, /* stderr */
@@ -124,22 +123,15 @@ enum vf_state
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_closing, /* for a vout: open, but in process of closing.
- * May still output stuff. */
-
- 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_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 */
+ vf_end, /* for a vin: EOF has been reached or input has
+ * been terminated, or an error or timeout has
+ * been met.
+ *
+ * for a vout: output has been terminated, or an
+ * error or tineout has been met.
+ *
+ * The vfd may have been closed -- but in any
+ * case no further vfd I/O should be attempted. */
} ;
typedef enum vf_state vf_state_t ;
@@ -219,33 +211,32 @@ struct vio_vf
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) */
uint depth_mark ; /* depth of this vout */
- /* I/O */
+ /* General I/O */
- vio_vfd vfd ; /* vty_io_basic "file descriptor" */
-
- bool blocking ; /* using blocking I/O (config read) */
+ bool blocking ; /* using blocking I/O (eg config read) */
- int error_seen ; /* non-zero => failed */
+ vio_vfd vfd ; /* vty_io_basic "file descriptor" */
vty_timer_time read_timeout ;
vty_timer_time write_timeout ;
- /* Pipe extras */
+ /* Pipe extras -- child and pipe returns */
vio_child child ; /* state of child */
- vf_state_t pr_state ; /* iff pipe */
- vio_vfd pr_vfd ; /* if pr_state == vf_open */
+ vf_state_t pr_state ; /* iff VOUT_PIPE/VOUT_SH_CMD */
+ vio_vfd pr_vfd ; /* if pr_state != vf_closed */
+ vty_timer_time pr_timeout ; /* set once closing pipe return */
- vio_vf pr_slave ; /* sending return stuff to there */
+ vf_state_t ps_state ; /* stderr for all pipe types */
+ vio_vfd ps_vfd ; /* if ps_state != vf_closed */
+ vty_timer_time ps_timeout ; /* set once closing pipe return */
- bool pr_only ; /* no vfd */
+ vio_fifo ps_buf ; /* to be moved to vio->ps_buf */
} ;
enum vty_readiness /* bit significant */
@@ -261,69 +252,95 @@ typedef enum vty_readiness vty_readiness_t ;
*/
enum vc_state
{
- vc_null = 0, /* the command loop has not yet been entered. */
+ vc_stopped, /* the command loop has stopped, and will not run
+ * again.
+ *
+ * or, the command loop has never started. */
+
+ vc_waiting, /* the command loop is waiting for I/O.
+ * command queue command loop only */
vc_running, /* the command loop is running, and the vty is
* in its hands. */
+} ;
+typedef enum vc_state vc_state_t ;
+
+/*------------------------------------------------------------------------------
+ * I/O and time-out error types
+ */
+enum vio_err_type
+{
+ verr_none = 0,
- vc_io_error_trap, /* the command loop is running, but an I/O
- * error has been posted. */
+ verr_vin = 1,
+ verr_vout = 2,
+ verr_pr = 3, /* pipe return (read) */
+ verr_ps = 4, /* pipe stderr return (read) */
- vc_close_trap, /* the command loop is running, but the vty is
- * reset and must be closed */
+ verr_mask = BIT(4) - 1,
- vc_waiting, /* the command loop is waiting for I/O. */
+ verr_io = BIT(4) * 0,
+ verr_to = BIT(4) * 1,
- vc_stopped, /* the command loop has stopped, the vty is due
- to be closed (loop cannot run again) */
+ verr_io_vin = verr_vin | verr_io,
+ verr_io_vout = verr_vout | verr_io,
+ verr_io_pr = verr_pr | verr_io,
+ verr_io_ps = verr_ps | verr_io,
- vc_closed, /* the command loop has finished and the vty
- * has been closed. */
+ verr_to_vin = verr_vin | verr_to,
+ verr_to_vout = verr_vout | verr_to,
+ verr_to_pr = verr_pr | verr_to,
+ verr_to_ps = verr_ps | verr_to,
} ;
-typedef enum vc_state vc_state_t ;
+typedef enum vio_err_type vio_err_type_t ;
+
+QFB_T(verr_mess, 200) ;
/*------------------------------------------------------------------------------
* The vty_io structure
*
- * The main elements of the vty_io object are the vin and vout stacks.
+ * The main elements of the vty_io (aka vio) object are the vin and vout stacks.
*
- * One of the challenges is managing the closing of VTY objects. First,
- * cannot close and free a VTY object which is in the hands of a command
- * function, or which is queued to or from a command function. Second,
- * do not wish to completely close an output until have given it a chance
- * to clear any buffered output.
+ * The first entry in the vin/vout stacks is the "base" and is a bit special.
+ * This entry is at stack depth 1. Stack depth 0 is reserved for all closed,
+ * or about to be.
*
+ * The vin_depth counts the number of command inputs which have been opened
+ * and pushed on the stack.
*
+ * The vout_depth reflects the vin_depth at which the vout was opened, and
+ * will be:
*
- * "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.
+ * * vout_depth == vin_depth + 1
*
- * "blocking" is set for configuration reading VTY, so that everything is
- * done with blocking I/O.
+ * this is true after a command line such as:
*
- * "closing" means that the vty has been closed (!), but a command
- * and or output may still be active:
+ * ...some command... > some_file
*
- * - if is a socket, will have shut_down the read side (half-closed)
+ * When the command completes, and all output is pushed out, then the
+ * vout will be closed and the vout_depth reduced.
*
- * - any further attempts to read will give "eof"
+ * * vout_depth == vin_depth
*
- * - there may be a command in execution -- see "cmd_running". TODO
+ * this is true when a vty is set up (and the depths will be 1), and also
+ * if vin and a vout are opened together, as in:
*
- * - further writing will be honoured.
+ * < some_file > some_other_file
*
- * - the write side may still be active, attempting to empty out any
- * pending output.
+ * When the vin reaches eof (or fails) and is closed, then the vout_depth
+ * will be vin_depth + 1, which triggers the closing of the vout.
*
- * "closed" means the vty has been fully and finally closed.
+ * * vout_depth < vin_depth
*
- * - any further attempts to write will be ignored, but return instant
- * success.
+ * This is true when one or vins have been opened and are stacked on top
+ * of each other. As the vins are closed, the vin_depth reduces until
+ * it hits the vout_depth, as above.
*
- * - the file/socket has been fully closed.
+ * When a vout is opened, the then current vout_depth is stored in the
+ * vf->depth_mark, and restored from there when the vout is closed.
*
- * - the VTY and all attached structures can be reaped by the death_watch.
+ * The vin_depth drives the closing of vouts. The vin_true_depth drives the
+ * closing of vins.
*/
struct vty_cli ; /* forward reference -- vty_cli.h is
*not* included, because that refers
@@ -333,22 +350,27 @@ struct vty_io /* typedef appears above */
{
vty vty ; /* the related vty */
+ /* List of all vty_io objects */
+ struct dl_list_pair(vty_io) vio_list ;
+
+ /* The vin/vout stacks */
+
vio_vf vin ; /* vin stack */
vio_vf vin_base ;
uint vin_depth ;
-
- uint real_depth ; /* less than vin_depth when closing */
+ uint vin_true_depth ; /* less than vin_depth when closing */
vio_vf vout ; /* vout stack */
vio_vf vout_base ;
uint vout_depth ;
+ bool cancel ;
+
/* 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 ;
+ int err_depth ; /* on error, close stack to this depth */
/* State
*
@@ -357,11 +379,16 @@ struct vty_io /* typedef appears above */
*
* "state" as described above.
*/
- bool blocking ; /* => all I/O is blocking. */
+ bool blocking ; /* => all I/O is blocking. */
+
+ vc_state_t state ; /* command loop state */
+ cmd_return_code_t signal ; /* signal sent to command loop */
- vc_state_t state ;
+ char* close_reason ; /* MTYPE_TMP (if any) */
- char* close_reason ; /* MTYPE_TMP (if any) */
+ /* Pipe stderr return buffer.
+ */
+ vio_fifo ps_buf ;
/* For ease of output, pointer to current vout->obuf
*
@@ -375,15 +402,14 @@ struct vty_io /* typedef appears above */
* With the exception of the "monitor" flag, need the LOG_MUTEX in order
* to change any of this.
*/
- bool monitor ; /* is in monitor state */
+ struct dl_list_pair(vty_io) mon_list ;
- bool mon_kick ; /* vty needs a kick */
- int maxlvl ; /* message level wish to see */
+ bool monitor ; /* is in monitor state */
- vio_fifo mbuf ; /* monitor output pending */
+ bool mon_kick ; /* vty needs a kick */
+ int maxlvl ; /* message level wish to see */
- /* List of all vty_io that are in monitor state */
- struct dl_list_pair(vty_io) mon_list ;
+ vio_fifo mbuf ; /* monitor output pending */
} ;
/*==============================================================================
@@ -425,7 +451,7 @@ Inline void uty_out_accept(vty_io vio) ;
Inline void uty_out_reject(vty_io vio) ;
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_close(vty_io vio) ;
extern void uty_set_timeout(vty_io vio, vty_timer_time timeout) ;
@@ -438,10 +464,10 @@ extern void uty_vin_push(vty_io vio, vio_vf vf, vio_in_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 void uty_vout_sync_depth(vty_io vio) ;
-extern cmd_return_code_t uty_vin_pop(vty_io vio, bool final,
- cmd_context context) ;
+ usize obuf_size,
+ bool after) ;
+extern cmd_return_code_t uty_vin_pop(vty_io vio, cmd_context context,
+ bool final) ;
extern cmd_return_code_t uty_vout_pop(vty_io vio, bool final) ;
@@ -451,14 +477,17 @@ 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 cmd_return_code_t uty_vf_error(vio_vf vf, vio_err_type_t err_type,
+ int err) ;
+extern verr_mess_t uty_error_message(vio_vf vf, vio_err_type_t err_type,
+ int err, bool log) ;
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_child_dismiss(vio_child child, bool final) ;
extern void uty_sigchld(void) ;
extern void uty_child_signal_nexus_set(vty_io vio) ;
@@ -471,7 +500,6 @@ extern void uty_open_listeners(const char *addr, unsigned short port,
extern void uty_add_listener(int fd, vio_vfd_accept* accept) ;
extern void uty_close_listeners(void) ;
-extern void uty_watch_dog_init(void) ;
extern void uty_watch_dog_start(void) ;
extern void uty_watch_dog_stop(void) ;
diff --git a/lib/vty_io_basic.c b/lib/vty_io_basic.c
index 1d40c210..bf86b17b 100644
--- a/lib/vty_io_basic.c
+++ b/lib/vty_io_basic.c
@@ -21,14 +21,17 @@
* 02111-1307, USA.
*/
#include "misc.h"
+
+#include <sys/socket.h>
+#include <fcntl.h>
+
#include "vty_io_basic.h"
#include "vty_local.h"
+#include "log.h"
+#include "pthread_safe.h"
#include "memory.h"
-#include <sys/socket.h>
-#include <fcntl.h>
-
/*==============================================================================
* Base level open operations -- files and pipes
*
@@ -37,14 +40,14 @@
/*------------------------------------------------------------------------------
* Try to open the given file for the given type of I/O.
*
- * vfd_io_write => create if does not exist (mode 0600)
+ * vfd_io_write => create if does not exist (with given cmode)
*
* vfd_io_append => set O_APPEND
* otherwise => truncate file if it does exist
*
* vfd_io_read => fail if does not exist
*
- * vfd_io_read_write => create if does not exist (mode 0600)
+ * vfd_io_read_write => create if does not exist (with given cmode)
*
* vfd_io_append => set O_APPEND
* otherwise => leave file as is
@@ -55,11 +58,19 @@
*
* Returns: if >= 0 -- the fd of the open file
* < 0 -- failed to open file -- see errno
+ *
+ * NB: "vfd_io_blocking" really means that the I/O will be blocking.
+ *
+ * "vfd_io_ps_blocking", used elsewhere, means that the actual I/O is
+ * non-blocking, but blocking, with time-out, is simulated by pselect.
*/
extern int
-uty_vfd_file_open(const char* name, vfd_io_type_t io_type)
+uty_fd_file_open(const char* name, vfd_io_type_t io_type, mode_t cmode)
{
- int oflag ;
+ mode_t um ;
+ int oflag ;
+ int fd ;
+ int err ;
oflag = 0 ;
if ((io_type & vfd_io_read_write) == vfd_io_read_write)
@@ -75,12 +86,43 @@ uty_vfd_file_open(const char* name, vfd_io_type_t io_type)
oflag |= O_APPEND ;
else if ((io_type & vfd_io_read) == 0)
oflag |= O_TRUNC ;
- } ;
+
+ if (cmode == 0)
+ cmode = S_IRUSR | S_IWUSR ;
+
+ cmode &= (S_IRWXU | S_IRWXG | S_IRWXO) ;
+ }
+ else
+ cmode = 0 ;
if ((io_type & vfd_io_blocking) == 0)
oflag |= O_NONBLOCK ;
- return open(name, oflag, S_IRUSR | S_IWUSR) ; /* TODO umask etc ? */
+ if (cmode != 0)
+ um = umask((S_IRWXU | S_IRWXG | S_IRWXO) & ~cmode) ;
+ else
+ um = (mode_t)0 ;
+
+ while (1)
+ {
+ fd = open(name, oflag, cmode) ;
+ if (fd >= 0)
+ break ;
+
+ err = errno ;
+ if (err == EINTR)
+ continue ;
+
+ break ;
+ } ;
+
+ if (cmode != 0)
+ umask(um) ;
+
+ if (fd < 0)
+ errno = err ;
+
+ return fd ;
} ;
/*==============================================================================
@@ -135,8 +177,14 @@ vio_vfd_do_write_action(vio_vfd vfd)
* 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.
+ * Sets the blocking_vf state if the io_type is vfd_io_ps_blocking (where the
+ * I/O is, in fact, non-blocking, but the vf level simulates blocking) or if
+ * the io_type is vfd_io_blocking (where the I/O really is blocking).
+ *
+ * The blocking_vf state disallows any attempt to set read/write ready/timeout,
+ * but enables open/close when not in cli thread.
+ *
+ * Must be in CLI thread if is neither vfd_io_ps_blocking nor vfd_io_blocking.
*/
extern vio_vfd
vio_vfd_new(int fd, vfd_type_t type, vfd_io_type_t io_type, void* action_info)
@@ -181,7 +229,7 @@ vio_vfd_new(int fd, vfd_type_t type, vfd_io_type_t io_type, void* action_info)
vfd->type = type ;
vfd->io_type = io_type ;
- vfd->blocking_vf = (io_type & vfd_io_blocking) != 0 ;
+ vfd->blocking_vf = (io_type & (vfd_io_blocking | vfd_io_ps_blocking)) != 0 ;
if (!vfd->blocking_vf)
{
@@ -303,7 +351,12 @@ vio_vfd_read_close(vio_vfd vfd)
if (vfd->fd >= 0)
{
if (vfd->type == vfd_socket)
- shutdown(vfd->fd, SHUT_RD) ; /* ignore errors TODO */
+ {
+ int rc = shutdown(vfd->fd, SHUT_RD) ;
+ if (rc < 0)
+ zlog_err("%s: shutdown() failed, fd=%d: %s", __func__,
+ vfd->fd, errtoa(errno, 0).str) ;
+ } ;
vio_vfd_set_read(vfd, off, 0) ;
vfd->io_type ^= vfd_io_read ; /* now write only ! */
} ;
@@ -343,7 +396,21 @@ vio_vfd_close(vio_vfd vfd)
/* Close the underlying fd, if any */
if (vfd->fd >= 0)
- close(vfd->fd) ; /* ignores errors TODO */
+ {
+ while(1)
+ {
+ int rc = close(vfd->fd) ;
+
+ if (rc == 0)
+ break ;
+
+ if (errno == EINTR)
+ continue ;
+
+ zlog_err("%s: close() failed, fd=%d: %s", __func__, vfd->fd,
+ errtoa(errno, 0).str) ;
+ } ;
+ } ;
/* Clear out the vfd then free it */
@@ -419,12 +486,12 @@ vio_vfd_set_read(vio_vfd vfd, on_off_b on, vty_timer_time timeout)
if (vty_nexus)
qps_enable_mode(vfd->f.qf, qps_read_mnum,
- vio_vfd_qps_read_action) ;
+ vio_vfd_qps_read_action) ;
else
{
if (vfd->f.thread.read == NULL)
vfd->f.thread.read = thread_add_read(vty_master,
- vio_vfd_thread_read_action, vfd, vfd->fd) ;
+ vio_vfd_thread_read_action, vfd, vfd->fd) ;
} ;
vio_timer_set(vfd->read_timer, timeout) ;
@@ -641,15 +708,16 @@ vio_vfd_thread_write_action(struct thread *thread)
* Message handling, so that other threads can signal for output to be
* dispatched !
*
- * There is one message block per vfd. It is only every touched under the
+ * There is one message block per vfd. It is only ever touched under the
* vty_mutex.
*
* Once it is dispatched it is marked 'active'. Can still be changed, but no
* further dispatch is required. When it has been dequeued and processed,
* it is marked inactive.
*
- * If the vfd is closed while the message is active, it is marked to close,
- * which it will do when it is dequeued and actioned.
+ * If the vfd is closed while the message is active, the pointer from the mqb
+ * to the vfd is set NULL, and when the message is dequeued and actioned,
+ * the mqb is destroyed.
*/
static void vio_vfd_mqb_action(mqueue_block mqb, mqb_flag_t flag) ;
@@ -830,7 +898,7 @@ vio_timer_init_new(vio_timer timer, vio_timer_action* action, void* action_info)
confirm(VIO_TIMER_INIT_ZERO) ;
/* active -- 0, false
- * squelch -- 0, false
+ * cancel -- 0, false
* t -- NULL, no qtr and no thread
*/
@@ -862,7 +930,7 @@ vio_timer_set_info(vio_timer timer, void* action_info)
/*------------------------------------------------------------------------------
* Squelch the given timer -- if it goes off, do not call the action routine,
- * and leave the rimer inactive.
+ * and leave the timer inactive.
*
* Used when doing read/write ready from not-cli thread.
*/
@@ -876,7 +944,9 @@ vio_timer_squelch(vio_timer timer)
} ;
/*------------------------------------------------------------------------------
- * Reset vio_timer structure. Stops any timer and releases all memory.
+ * Reset vio_timer structure.
+ *
+ * Stops any timer and releases all memory, if required.
*/
extern vio_timer
vio_timer_reset(vio_timer timer, free_keep_b free_structure)
@@ -890,7 +960,8 @@ vio_timer_reset(vio_timer timer, free_keep_b free_structure)
if (timer->t.anon != NULL)
{
if (vty_nexus)
- qtimer_free(timer->t.qtr) ;
+ qtimer_free(timer->t.qtr) ; /* stop and discard qtimer even
+ * if keeping vio_timer */
else
thread_cancel(timer->t.thread) ;
@@ -901,7 +972,7 @@ vio_timer_reset(vio_timer timer, free_keep_b free_structure)
timer->squelch = false ;
if (free_structure)
- XFREE(MTYPE_VTY, timer) ; /* sets timer = NULL */
+ XFREE(MTYPE_VTY, timer) ; /* sets timer = NULL */
} ;
return timer ;
diff --git a/lib/vty_io_basic.h b/lib/vty_io_basic.h
index 9d174fdd..cbeb9fed 100644
--- a/lib/vty_io_basic.h
+++ b/lib/vty_io_basic.h
@@ -54,14 +54,16 @@ typedef enum vfd_type vfd_type_t ;
enum vfd_io_type /* NB: *bit*significant* */
{
- vfd_io_none = 0,
+ vfd_io_none = 0,
- vfd_io_read = BIT(0),
- vfd_io_write = BIT(1),
- vfd_io_read_write = vfd_io_read | vfd_io_write,
+ vfd_io_read = BIT(0),
+ vfd_io_write = BIT(1),
+ vfd_io_read_write = vfd_io_read | vfd_io_write,
- vfd_io_append = BIT(2),
- vfd_io_blocking = BIT(3),
+ vfd_io_append = BIT(2),
+ vfd_io_blocking = BIT(3),
+
+ vfd_io_ps_blocking = BIT(4),
} ;
typedef enum vfd_io_type vfd_io_type_t ;
@@ -183,8 +185,8 @@ struct vio_listener
/*==============================================================================
* Functions
*/
-
-extern int uty_vfd_file_open(const char* name, vfd_io_type_t io_type) ;
+extern int uty_fd_file_open(const char* name, vfd_io_type_t io_type,
+ mode_t cmode) ;
extern vio_vfd vio_vfd_new(int fd, vfd_type_t type,
vfd_io_type_t io_type, void* action_info) ;
diff --git a/lib/vty_io_file.c b/lib/vty_io_file.c
index b98dd694..d1d1a50a 100644
--- a/lib/vty_io_file.c
+++ b/lib/vty_io_file.c
@@ -32,22 +32,58 @@
#include "vty_command.h"
#include "network.h"
#include "sigevent.h"
+#include "pthread_safe.h"
+#include "qlib_init.h"
+
+/*==============================================================================
+ * Here we handle:
+ *
+ * * VIN_CONFIG/VOUT_CONFIG -- configuration file read/write
+ *
+ * * VIN_FILE/VOUT_FILE -- file pipe read/write
+ *
+ * * VIN_PIPE/VOUT_PIPE -- shell command pipe read/write
+ */
+
+#define PIPEFILE_MODE CONFIGFILE_MASK
+
+enum
+{
+ file_timeout = 20, /* for file read/write */
+
+ pipe_timeout = 30, /* for pipe read/write */
+
+ child_timeout = 10, /* for collecting child process */
+
+ config_buffer_size = 64 * 1024, /* for config reading */
+ file_buffer_size = 16 * 1024, /* for other file read/write */
+
+ pipe_buffer_size = 4 * 1024, /* for pipe read/write */
+} ;
/*==============================================================================
* VTY Configuration file I/O -- VIN_CONFIG and VOUT_CONFIG.
*
+ * The creation of configuration files is handled elsewhere, as is the actual
+ * opening of input configuration files.
*
+ * Once open, VIN_CONFIG and VOUT_CONFIG are read/written in the same way as
+ * VIN_FILE and VOUT_FILE.
*
+ * These files are handled as "blocking" because the parent vio is marked as
+ * "blocking".
*/
/*------------------------------------------------------------------------------
* Set up VTY on which to read configuration file -- using already open fd.
*
- * Sets TODO
+ * Sets the vout_base to be VOUT_STDERR.
*
- * 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.
+ * NB: sets up a blocking vio -- so that the vin_base, vout_base and all files
+ * and pipes will block waiting for I/O to complete (or to time out).
+ *
+ * NB: the name is XSTRDUP() into the vio -- so the caller is responsible for
+ * disposing of its copy, if required.
*/
extern vty
vty_config_read_open(int fd, const char* name, bool full_lex)
@@ -68,14 +104,17 @@ vty_config_read_open(int fd, const char* name, bool full_lex)
/* Create the vin_base/vout_base vf
*
- * NB: don't need to specify vfd_io_blocking, because the vio is set blocking.
+ * NB: don't need to specify vfd_io_ps_blocking, because the vio is set
+ * blocking.
*/
- vf = uty_vf_new(vio, name, fd, vfd_file, vfd_io_read) ;
+ qassert(vio->blocking) ;
- uty_vin_push( vio, vf, VIN_CONFIG, NULL, NULL, 64 * 1024) ;
- uty_vout_push(vio, vf, VOUT_STDERR, NULL, NULL, 4 * 1024) ;
+ vf = uty_vf_new(vio, name, fd, vfd_file, vfd_io_read) ;
- uty_vout_sync_depth(vio) ; /* vin & vout are at same level */
+ uty_vin_push( vio, vf, VIN_CONFIG, NULL, NULL, config_buffer_size) ;
+ vf->read_timeout = file_timeout ;
+ uty_vout_push(vio, vf, VOUT_STDERR, NULL, NULL, pipe_buffer_size, true) ;
+ vf->write_timeout = file_timeout ;
/* Deal with the possibility that while reading the configuration file, may
* use a pipe, and therefore may block waiting to collect a child process.
@@ -104,15 +143,61 @@ uty_config_read_close(vio_vf vf, bool final)
{
VTY_ASSERT_LOCKED() ;
+ qassert(vf->vin_state == vf_end) ;
+
uty_child_signal_nexus_clear(vf->vio) ;
return CMD_SUCCESS ;
} ;
+/*------------------------------------------------------------------------------
+ * Push the given fd as the VOUT_CONFIG.
+ *
+ * Note that the fd is assumed to be genuine I/O blocking, so this is a
+ * "blocking" vf, and so can be opened and closed in the cmd thread.
+ */
+extern void
+vty_config_write_open(vty vty, int fd)
+{
+ vty_io vio ;
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+
+ vio = vty->vio ;
+
+ 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, file_buffer_size, false) ;
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Write away any pending stuff, and pop the VOUT_CONFIG.
+ *
+ * This is a blocking vf -- so any outstanding output will complete here.
+ */
+extern cmd_return_code_t
+vty_config_write_close(struct vty* vty)
+{
+ cmd_return_code_t ret ;
+ VTY_LOCK() ;
+
+ qassert(vty->vio->vout->vout_type == VOUT_CONFIG) ;
+
+ ret = uty_vout_pop(vty->vio, false) ;
+
+ VTY_UNLOCK() ;
+
+ return ret ;
+} ;
+
/*==============================================================================
* VTY File I/O -- VIN_FILE and VOUT_FILE
+ * also read/write/close for VIN_CONFIG and VOUT_CONFIG.
*
- * This is for input and output of configuration files and piped stuff.
+ * This is for input and output of configuration and piped files.
*
* When reading the configuration (and piped stuff in the configuration) I/O
* is blocking... nothing else can run while this is going on. Otherwise,
@@ -120,18 +205,18 @@ uty_config_read_close(vio_vf vf, bool final)
* 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,
+static void vty_file_read_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time vty_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,
+static void vty_file_write_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time vty_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.
+ * Open file for input, to be read as commands -- VIN_FILE.
*
* If could not open, issues message to the vio.
*
@@ -154,7 +239,7 @@ uty_file_read_open(vty_io vio, qstring name, cmd_context context)
vio_vf vf ;
vfd_io_type_t iot ;
- VTY_ASSERT_CLI_THREAD_LOCKED() ;
+ VTY_ASSERT_LOCKED() ;
assert(vio->vin != NULL) ; /* Not expected to be vin_base */
@@ -163,27 +248,30 @@ uty_file_read_open(vty_io vio, qstring name, cmd_context context)
path = uty_cmd_path_name_complete(NULL, qs_string(name), context) ;
pns = qpath_string(path) ;
- /* Do the basic file open. */
+ /* Do the basic file open. May be vfd_io_ps_blocking, but we don't care
+ * about that here.
+ */
iot = vfd_io_read ;
- fd = uty_vfd_file_open(pns, iot) ;
+ fd = uty_fd_file_open(pns, iot, (mode_t)0) ; /* cmode not required */
+ /* If failed, report.
+ * If OK save context & update "here" then create and push the vin.
+ */
if (fd < 0)
{
- uty_out(vio, "%% Could not open input file %s\n", pns) ;
-
- ret = CMD_WARNING ; /* TODO add errno to message ? */
+ uty_out(vio, "%% Could not open input file %s: %s\n", pns,
+ errtoa(errno, 0).str) ;
+ ret = CMD_WARNING ;
}
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, 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) ;
+ uty_vin_push(vio, vf, VIN_FILE, vty_file_read_ready,
+ vty_file_read_timeout, file_buffer_size) ;
+ vf->read_timeout = file_timeout ;
ret = CMD_SUCCESS ;
} ;
@@ -193,10 +281,13 @@ uty_file_read_open(vty_io vio, qstring name, cmd_context context)
} ;
/*------------------------------------------------------------------------------
- * Open file for output.
+ * Open file for output -- VOUT_FILE.
*
* If could not open, issues message to the vio.
*
+ * "after" <=> this vout is being opened after a vin being opened at the same
+ * time.
+ *
* Returns: CMD_SUCCESS -- all set to go
* CMD_WARNING -- failed to open
*
@@ -204,7 +295,8 @@ uty_file_read_open(vty_io vio, qstring name, cmd_context context)
* cli thread -- see uty_vfd_new().
*/
extern cmd_return_code_t
-uty_file_write_open(vty_io vio, qstring name, bool append, cmd_context context)
+uty_file_write_open(vty_io vio, qstring name, bool append, cmd_context context,
+ bool after)
{
cmd_return_code_t ret ;
qpath path ;
@@ -213,30 +305,36 @@ uty_file_write_open(vty_io vio, qstring name, bool append, cmd_context context)
vio_vf vf ;
vfd_io_type_t iot ;
- VTY_ASSERT_CLI_THREAD_LOCKED() ;
+ VTY_ASSERT_LOCKED() ;
/* Now is the time to complete the name, if required. */
path = uty_cmd_path_name_complete(NULL, qs_string(name), context) ;
pns = qpath_string(path) ;
- /* Do the basic file open. */
+ /* Do the basic file open. May be vfd_io_ps_blocking, but we don't care
+ * about that here.
+ */
iot = vfd_io_write | (append ? vfd_io_append : 0) ;
- fd = uty_vfd_file_open(pns, iot) ;
+ fd = uty_fd_file_open(pns, iot, PIPEFILE_MODE) ;
+ /* If failed, report.
+ * If OK, create and push the vout.
+ */
if (fd < 0)
{
- uty_out(vio, "%% Could not open output file %s\n", pns) ;
-
- ret = CMD_WARNING ; /* TODO add errno to message ? */
+ uty_out(vio, "%% Could not open output file %s: %s\n", pns,
+ errtoa(errno, 0).str) ;
+ ret = CMD_WARNING ;
}
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) ;
+ uty_vout_push(vio, vf, VOUT_FILE, vty_file_write_ready,
+ vty_file_write_timeout, file_buffer_size, after) ;
+ vf->write_timeout = file_timeout ;
+
ret = CMD_SUCCESS ;
} ;
@@ -245,13 +343,12 @@ uty_file_write_open(vty_io vio, qstring name, bool append, cmd_context context)
} ;
/*------------------------------------------------------------------------------
- * Command line fetch from a file or pipe.
+ * Command line fetch from a file -- VIN_FILE or VIN_CONFIG, in vf_open state.
*
* 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
+ * CMD_EOF -- ran into EOF (and nothing else)
+ * CMD_IO_ERROR -- ran into an I/O error or time-out
*
* In "non-blocking" state, on CMD_WAITING sets read ready, which will
* vty_cmd_signal() the command loop to come round again.
@@ -261,16 +358,23 @@ uty_file_write_open(vty_io vio, qstring name, bool append, cmd_context context)
*
* This can be called in any thread.
*
- * NB: the vin_state will become vf_eof the first time that CMD_EOF is
- * returned.
+ * NB: the vin_state is set to vf_end when CMD_EOF is returned, and this
+ * code may not be called again.
+ *
+ * NB: the vin_state is set to vf_end when CMD_IO_ERROR is returned, and
+ * this code may not be called again.
+ *
+ * When an error occurs it is signalled to the command loop. This function
+ * is called from the command loop -- so, in fact, the CMD_IO_ERROR return
+ * code does the trick.
*/
extern cmd_return_code_t
uty_file_fetch_command_line(vio_vf vf, cmd_action action)
{
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)) ;
+ qassert((vf->vin_type == VIN_FILE) || (vf->vin_type == VIN_CONFIG)) ;
+ qassert(vf->vin_state == vf_open) ;
while (1)
{
@@ -297,11 +401,11 @@ uty_file_fetch_command_line(vio_vf vf, cmd_action action)
break ; /* loop back to uty_fifo_command_line() */
if (get == -1)
- return CMD_IO_ERROR ; /* register error */
+ return uty_vf_error(vf, verr_io_vin, errno) ;
if (get == -2)
{
- /* Hit end of file set the vf into vf_eof.
+ /* Hit end of file, so set the vf into vf_end.
*
* NB: does not know has hit eof until tries to read and nothing
* is returned.
@@ -310,18 +414,18 @@ uty_file_fetch_command_line(vio_vf vf, cmd_action action)
* 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)) ;
+ qassert(vio_fifo_empty(vf->ibuf)) ;
- vf->vin_state = vf_eof ;
+ vf->vin_state = vf_end ;
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
+ * to read, loop back to vio_fifo_read_nb().
*/
- assert(get == 0) ;
+ qassert(get == 0) ;
if (!vf->blocking)
{
@@ -332,23 +436,27 @@ uty_file_fetch_command_line(vio_vf vf, cmd_action action)
/* 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 ?? */
+ if (qps_mini_wait(qm, NULL, false) != 0)
+ continue ; /* loop back to vio_fifo_read_nb() */
- /* Loop back to vio_fifo_read_nb() */
- }
+ return uty_vf_error(vf, verr_to_vin, 0) ;
+ } ;
} ;
} ;
/*------------------------------------------------------------------------------
* Try to complete a command line for the given vf, from the current input
- * fifo -- performs NO I/O
+ * fifo -- performs NO I/O.
+ *
+ * Expects that most of the time will be able to set the vf->cl to point
+ * directly at a command line in the fifo -- but at the edge of fifo buffers
+ * (and if get continuation lines) will copy line fragments to the vf->cl.
*
* 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
+ * If vf->vin_state == vf_end, 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
@@ -380,7 +488,7 @@ uty_fifo_command_line(vio_vf vf, cmd_action action)
/* If fifo is empty, may be last line before eof, eof or waiting */
if (have == 0)
{
- if (vf->vin_state == vf_eof)
+ if (vf->vin_state == vf_end)
{
if (qs_len_nn(vf->cl) > 0)
break ; /* have non-empty last line */
@@ -391,7 +499,7 @@ uty_fifo_command_line(vio_vf vf, cmd_action action)
return CMD_WAITING ;
} ;
- assert(vf->vin_state != vf_eof) ; /* not empty => not eof */
+ qassert(vf->vin_state != vf_end) ; /* not empty => not eof */
/* Try to find a '\n' -- converting all other control chars to ' '
*
@@ -472,7 +580,7 @@ uty_fifo_command_line(vio_vf vf, cmd_action action)
} ;
} ;
- /* Now worry about we have a trailing '\'. */
+ /* Now worry about possible trailing '\'. */
if ((p == s) || (*(p-1) != '\\'))
break ; /* no \ => no continuation => success */
@@ -509,16 +617,17 @@ uty_fifo_command_line(vio_vf vf, cmd_action action)
} ;
/*------------------------------------------------------------------------------
- * File or pipe is ready to read -- this is the call-back planted in the
- * vf->vfd.
+ * File is ready to read -- call-back for VIN_FILE.
+ *
+ * This is used if the VIN_FILE is non-blocking.
*
- * The command_queue processing can continue.
+ * Signals command loop, so may continue if was waiting.
*
* 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)
+vty_file_read_ready(vio_vfd vfd, void* action_info)
{
vio_vf vf ;
@@ -528,19 +637,23 @@ uty_file_read_ready(vio_vfd vfd, void* action_info)
vf = action_info ;
assert(vf->vfd == vfd) ;
+ /* If the vin is no longer open then read ready should have been turned
+ * off -- but kicking the command loop will not hurt.
+ */
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.
+ * File has timed out, waiting to read -- call-back for VIN_FILE.
+ *
+ * This is used if the VIN_FILE is non-blocking.
*
- * ????
+ * Signals a timeout error to the command loop.
*/
static vty_timer_time
-uty_file_read_timeout(vio_timer timer, void* action_info)
+vty_file_read_timeout(vio_timer timer, void* action_info)
{
vio_vf vf ;
@@ -548,9 +661,9 @@ uty_file_read_timeout(vio_timer timer, void* action_info)
VTY_ASSERT_CLI_THREAD() ;
vf = action_info ;
- assert(vf->vfd->write_timer == timer) ;
+ assert(vf->vfd->read_timer == timer) ;
-//cq_continue(vf->vio->vty) ; TODO
+ uty_vf_error(vf, verr_to_vin, 0) ; /* signals command loop */
VTY_UNLOCK() ;
@@ -558,82 +671,99 @@ uty_file_read_timeout(vio_timer timer, void* action_info)
} ;
/*------------------------------------------------------------------------------
- * Command output push to a file.
+ * Command output push to a file -- VOUT_FILE or VOUT_CONFIG -- vf_open.
*
- * 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".
+ * Unless all || final this will not write the end_lump of the fifo, so 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.
+ * any errors (but will return 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)
+ * If an error occurred earlier, then returns immediately (CMD_SUCCESS).
*
- * In "non-blocking" state, on CMD_WAITING the caller will have to take steps
- * to come back later.
+ * Returns: CMD_SUCCESS -- done everything possible
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
*
- * In "blocking" state, will not return until have written everything there is,
- * away, or cannot continue.
+ * If "non-blocking", on CMD_WAITING the pselect() background process
+ * will complete the output and signal the result via uty_cmd_signal().
+ *
+ * If "blocking", will not return until have written away everything there is,
+ * or cannot continue.
*
* This can be called in any thread.
*/
extern cmd_return_code_t
-uty_file_out_push(vio_vf vf, bool final)
+uty_file_out_push(vio_vf vf, bool final, bool all)
{
- 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)
+ qassert((vf->vout_type == VOUT_FILE) || (vf->vout_type == VOUT_CONFIG)) ;
+ qassert(vf->vout_state == vf_open) ;
+
+ /* If squelching, dump anything we have in the obuf.
+ *
+ * Otherwise, write contents away.
+ */
+ if (vf->vio->cancel)
+ vio_fifo_clear(vf->obuf, false) ; /* keep end mark */
+ else
{
- qps_mini_t qm ;
- int n ;
+ while (1)
+ {
+ qps_mini_t qm ;
+ int n ;
- n = vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), all || final) ;
+ 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)
+ return uty_vf_error(vf, verr_io_vout, errno) ;
- if ((n == 0) || final) /* all gone (or as much as can go) */
- return CMD_SUCCESS ;
+ if (n == 0)
+ break ; /* all gone */
- /* Cannot write everything away without waiting */
- if (!vf->blocking)
- {
- uty_vf_set_write(vf, on) ;
- return CMD_WAITING ;
- } ;
+ /* Cannot write everything away without waiting */
+ if (!vf->blocking)
+ {
+ if (!final)
+ uty_vf_set_write(vf, on) ;
+ return CMD_WAITING ;
+ } ;
+
+ /* Implement blocking I/O, with timeout */
+ if (final)
+ 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 ;
+ qps_mini_set(qm, vio_vfd_fd(vf->vfd), qps_write_mnum,
+ vf->write_timeout) ;
+ if (qps_mini_wait(qm, NULL, false) != 0)
+ continue ; /* Loop back to vio_fifo_write_nb() */
- /* Loop back to vio_fifo_write_nb() */
+ return uty_vf_error(vf, verr_to_vout, 0) ;
+ } ;
} ;
+
+ return CMD_SUCCESS ;
} ;
/*------------------------------------------------------------------------------
- * File or pipe is ready to write -- this is the call-back planted in the
- * vf->vfd.
+ * File is ready to write -- call-back for VOUT_FILE.
+ *
+ * This is used if the VOUT_FILE is non-blocking.
+ *
+ * Signals command loop, so may continue if was waiting.
*
* 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)
+vty_file_write_ready(vio_vfd vfd, void* action_info)
{
cmd_return_code_t ret ;
vio_vf vf ;
@@ -644,23 +774,32 @@ uty_file_write_ready(vio_vfd vfd, void* action_info)
vf = action_info ;
assert(vf->vfd == vfd) ;
- ret = uty_file_out_push(vf, false) ; /* re-enable write ready if required */
+ /* If the vout is no longer open then write ready should have been turned
+ * off -- but kicking the command loop will not hurt.
+ */
+ if (vf->vout_state == vf_open)
+ {
+ /* Push, not final and not all -- re-enables write ready if required */
+ ret = uty_file_out_push(vf, false, false) ;
+ }
+ else
+ ret = CMD_SUCCESS ;
if (ret != CMD_WAITING)
- uty_cmd_signal(vf->vio, ret) ;
+ uty_cmd_signal(vf->vio, ret) ; /* CMD_SUCCESS or CMD_IO_ERROR */
VTY_UNLOCK() ;
} ;
/*------------------------------------------------------------------------------
- * File or pipe is ready to write -- this is the call-back planted in the
- * vf->vfd.
+ * File has timed out, waiting to write -- call-back for VOUT_FILE.
*
- * Note that the read_ready state and any time out are automatically
- * disabled when they go off.
+ * This is used if the VOUT_FILE is non-blocking.
+ *
+ * Signals a timeout error to the command loop.
*/
static vty_timer_time
-uty_file_write_timeout(vio_timer timer, void* action_info)
+vty_file_write_timeout(vio_timer timer, void* action_info)
{
vio_vf vf ;
@@ -670,7 +809,7 @@ uty_file_write_timeout(vio_timer timer, void* action_info)
vf = action_info ;
assert(vf->vfd->write_timer == timer) ;
-//uty_file_out_push(vf) ; // TODO ???????
+ uty_vf_error(vf, verr_to_vout, 0) ; /* signals command loop */
VTY_UNLOCK() ;
@@ -678,76 +817,279 @@ uty_file_write_timeout(vio_timer timer, void* action_info)
} ;
/*------------------------------------------------------------------------------
- * Tidy up after input file has been closed.
+ * Tidy up after input file has been closed -- VIN_FILE.
*
* Nothing further required -- input comes to a halt.
+ *
+ * Returns: CMD_SUCCESS -- at all times
*/
extern cmd_return_code_t
uty_file_read_close(vio_vf vf, bool final)
{
+ VTY_ASSERT_LOCKED() ;
+
+ qassert(vf->vin_type == VIN_FILE) ;
+
return CMD_SUCCESS ;
} ;
/*------------------------------------------------------------------------------
- * Flush output buffer and close.
+ * Flush output buffer ready for close -- VOUT_FILE and VOUT_CONFIG.
*
* See uty_file_out_push()
*
- * Returns: CMD_SUCCESS -- buffers are empty, or final
- * CMD_WAITING -- waiting for I/O to complete
- * CMD_xxx TODO
+ * Returns: CMD_SUCCESS -- done everything possible
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
*/
extern cmd_return_code_t
-uty_file_write_close(vio_vf vf, bool final, bool base)
+uty_file_write_close(vio_vf vf, bool final)
{
VTY_ASSERT_CAN_CLOSE_VF(vf) ;
- assert(vf->vout_state == vf_closing) ;
- assert((vf->vout_type == VOUT_FILE) || (vf->vout_type == VOUT_CONFIG)) ;
- return uty_file_out_push(vf, final) ;
+ qassert((vf->vout_type == VOUT_FILE) || (vf->vout_type == VOUT_CONFIG)) ;
+
+ if (vf->vout_state == vf_open)
+ return uty_file_out_push(vf, final, true) ; /* write all */
+ else
+ return CMD_SUCCESS ;
} ;
/*==============================================================================
- * Shell pipe stuff
+ * VTY Pipe I/O -- VIN_PIPE, VOUT_PIPE & VOUT_SH_CMD
+ *
+ * This is for input/output from/to shell commands.
+ *
+ * This is complicated by (a) the existence of up to 3 streams of data, and
+ * (b) the extra step of collecting the child and its return code on close.
+ *
+ * The three streams of data, from the perspective of the shell command, are:
+ *
+ * stdin -- input from Quagga (if any)
+ *
+ * stdout -- output to Quagga -- may be treated as command lines
+ *
+ * stderr -- output to Quagga -- assumed to be diagnostic information
+ *
+ * There are three variants of pipe I/O:
+ *
+ * * VIN_PIPE -- command line: <| shell command
+ *
+ * stdin -- none
+ *
+ * stdout -- read by Quagga as command lines
+ *
+ * stderr -- collected by Quagga and output (eventually) to the base
+ * vout.
+ *
+ * * VOUT_PIPE -- command line: .... >| shell command
+ *
+ * stdin -- the Quagga command(s) output
+ *
+ * stdout -- collected by Quagga and output to the next vout.
+ *
+ * stderr -- collected by Quagga and output (eventually) to the base
+ * vout.
+ *
+ * * VOUT_SH_CMD -- command line: | shell_command
+ *
+ * stdin -- none
+ *
+ * stdout -- collected by Quagga and output to the next vout.
+ *
+ * stderr -- collected by Quagga and output (eventually) to the base
+ * vout.
+ *
+ * When Quagga is collecting stdout for output, that is known as the
+ * "pipe return", and is expected to be the result of the command.
+ *
+ * In all cases the stderr is assumed to be diagnostic information, which is
+ * known as the "pipe stderr return". This is collected in the vf->ps_buf.
+ * When the pipe is closed, any stderr return is then appended to the
+ * vio->ps_buf. When the output stack is closed down to the base vout, the
+ * contents of the vio->ps_buf are transferred to the vio->obuf. This means
+ * that:
+ *
+ * 1. all pipe stderr return for a given pipe is collected and output
+ * all together to the base vout (eg the VTY_TERMINAL). The output is
+ * bracketted so:
+ *
+ * %[--- 'name':
+ * ....
+ * ....
+ * %---]
+ *
+ * to give context.
+ *
+ * 2. the pipe stderr return is not mixed up with any other pipe or other
+ * command output, and in particular cannot be piped to another pipe.
+ *
+ * 3. but... if VOUT_PIPE are stacked on top of each other, it may be a
+ * while before the pipe stderr return stuff reaches the base vout.
+ *
+ * The problem here is that if one VOUT_PIPE is stacked above another,
+ * the lower pipe may or may not be in the middle of something when
+ * the upper pipe is closed -- so to avoid a mix-up, must wait until
+ * the all lower pipes close before the pipe stderr is finally output
+ * (along with all other pending stderr return).
+ *
+ * While the main vin/vout is open, the pipe I/O is handled as follows:
+ *
+ * * VIN_PIPE
+ *
+ * The shell command's stdout is connected to the vf's vin, and is read
+ * as command lines, much as VIN_FILE. A read timeout is set, to deal
+ * with shell commands which take an unreasonable time.
+ *
+ * The shell command's stderr is connected to the vf's pipe stderr
+ * return. For non-blocking, that is read into the vf->ps_buf, by the
+ * pselect() process. For blocking, that is read in parallel with the
+ * main vin. No timeout is set until the main vin is closed.
+ *
+ * * VOUT_PIPE
+ *
+ * The shell command's stdin is connected to the vf's vout, and is
+ * written to much like VOUT_FILE. A write timeout is set, to deal
+ * with shell commands which take an unreasonable time.
+ *
+ * The shell command's stdout is connected to the vf's pipe return.
+ * For non-blocking vf, the pipe return is read autonomously under
+ * pselect() and pushed to the next vout. For blocking vf, the pipe
+ * return is polled whenever the (main) vout is written to, and any
+ * available input is pushed to the slave vout. No timeout is set
+ * until the main vout is closed.
+ *
+ * The shell command's stderr is connected to the vf's pipe stderr
+ * return. For non-blocking, that is read into the vf->ps_buf, by the
+ * pselect() process. For blocking, that is read in parallel with the
+ * pipe return. No timeout is set until the main vout is closed.
+ *
+ * * VOUT_SH_CMD
*
+ * The shell command's stdin is set empty, and the vf's vout set to
+ * vf_closed.
+ *
+ * Otherwise, this is the same as VOUT_PIPE.
+ *
+ * The closing of a VIN_PIPE/VOUT_PIPE/VOUT_SH_CMD is a little involved:
+ *
+ * 0. close the main vin/vout.
+ *
+ * for a VIN_PIPE the vin_state will be:
+ *
+ * vf_end -- eof met or forced by an early close, or error or timeout
+ * occurred
+ *
+ * So phase 0 finishes immediately.
+ *
+ * for a VOUT_PIPE the vout_state will be:
+ *
+ * vf_open -- until all output completes or hits error or timeout
+ * vf_end -- nothing more to be output, or error or timeout occurred
+ *
+ * If vf_open, must wait until no longer vf_open, ie until all pending
+ * output completes, or hits and error or timeout.
+ *
+ * As soon as is no longer vf_open, must close the vfd to signal to the
+ * child that is now at eof.
+ *
+ * for a VOUT_SH_CMD the vout_state will be:
+ *
+ * vf_end -- nothing to be output.
+ *
+ * So phase 0 finishes immediately.
+ *
+ * 1. collect remaining input and close the pipe return.
+ *
+ * for a VIN_PIPE there is no pipe return.
+ *
+ * for a VOUT_PIPE or VOUT_SH_CMD, must empty the pipe return and then
+ * close it -- stopping immediately if gets error or time-out on either
+ * the pipe return or the slave vout.
+ *
+ * Note that up to this point no time-out has been set on the pipe return,
+ * since there is no need for there to be any input. But expect now to
+ * see at least eof in a reasonable time.
+ *
+ * For non-blocking vf, the pr_vfd is set read ready. Then for all
+ * non-blocking pipes, the remaining pipe return input proceeds in the
+ * pselect() process.
+ *
+ * For blocking pipes, the remaining pipe return must complete (or
+ * time-out) during the close operation.
+ *
+ * 2. collect remaining input and close the pipe stderr return.
+ *
+ * For non-blocking vf, the ps_vfd is set read ready. Then for all
+ * non-blocking pipes, the remaining pipe return input proceeds in the
+ * pselect() process -- complete with time-out.
+ *
+ * For blocking pipes, the remaining pipe return must complete (or
+ * time-out) during the close operation.
+ *
+ * 3. once all input has been collected and pr_vfd and ps_vfd are closed,
+ * need to collect the child, so that we can check the return code.
+ *
+ * This may time out.
+ *
+ * If the return code is not 0, or anything else happens, a diagnostic
+ * message is appended to the vf->rbuf.
+ *
+ * For non-blocking vf the close may return CMD_WAITING, and must be called
+ * again later to progress the close.
+ *
+ * For "final" close will not block or return CMD_WAITING, but complete the
+ * process as quickly as possible while outputting as much as possible.
*/
typedef int pipe_pair_t[2] ;
typedef int* pipe_pair ;
-enum pipe_half
+typedef enum pipe_half
{
in_fd = 0,
out_fd = 1,
-} ;
-typedef enum pipe_half pipe_half_t ;
+
+} pipe_half_t ;
CONFIRM(STDIN_FILENO == 0) ;
CONFIRM(STDOUT_FILENO == 1) ;
CONFIRM(STDERR_FILENO == 2) ;
-enum
+typedef enum std_id
{
stdin_fd = STDIN_FILENO,
stdout_fd = STDOUT_FILENO,
stderr_fd = STDERR_FILENO,
stds = 3
-} ;
-enum pipe_id
+} std_id_t ;
+
+typedef enum pipe_id
{
in_pipe,
out_pipe,
- ret_pipe,
+ err_pipe,
pipe_count
-} ;
-typedef enum pipe_id pipe_id_t ;
+
+} pipe_id_t ;
+
+typedef enum pipe_type
+{
+ vin_pipe,
+ vout_pipe,
+ vout_sh_cmd,
+
+} pipe_type_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) ;
+ pipe_type_t type) ;
static bool uty_pipe_pair(vty_io vio, const char* cmd_str,
const char* what, pipe_set pipes,
pipe_id_t id,
@@ -757,34 +1099,32 @@ static bool uty_pipe_fork_fail(vty_io vio, const char* cmd_str,
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 void uty_pipe_open_complete(vio_vf vf, pid_t pid, int pr_fd, int ps_fd) ;
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 cmd_return_code_t uty_pipe_shovel(vio_vf vf, bool final) ;
+static cmd_return_code_t uty_pipe_stderr_suck(vio_vf vf, bool final) ;
+static vio_fifo uty_pipe_ps_buf(vio_vf vf) ;
-static void uty_pipe_read_ready(vio_vfd vfd, void* action_info) ;
-static vty_timer_time uty_pipe_read_timeout(vio_timer timer,
+static void vty_pipe_read_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time vty_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,
+static void vty_pipe_return_ready(vio_vfd vfd, void* action_info) ;
+static void vty_pipe_stderr_return_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time vty_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,
+static vty_timer_time vty_pipe_stderr_return_timeout(vio_timer timer,
+ void* action_info) ;
+static void vty_pipe_write_ready(vio_vfd vfd, void* action_info) ;
+static vty_timer_time vty_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.
+ * Open VIN_PIPE: pipe whose child's stdout is read and executed as commands.
*
- * If could not open, issues message to the vio.
+ * The child's stderr is read, separately, as the pipe return, and sent to the
+ * current vout.
*
* 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
@@ -793,29 +1133,10 @@ static vty_timer_time uty_pipe_write_timeout(vio_timer timer,
* 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
+ * CMD_WARNING -- failed to open -- message sent to vio.
*/
extern cmd_return_code_t
uty_pipe_read_open(vty_io vio, qstring command, cmd_context context)
@@ -824,105 +1145,110 @@ uty_pipe_read_open(vty_io vio, qstring command, cmd_context context)
const char* cmd_str ;
vio_vf vf ;
pid_t child ;
+ qpath dir ;
- VTY_ASSERT_CLI_THREAD_LOCKED() ;
+ VTY_ASSERT_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) ;
+ child = uty_pipe_fork(vio, cmd_str, pipes, vin_pipe) ;
if (child < 0)
return CMD_WARNING ;
/* We have a pipe, so now save context */
- uty_vin_new_context(vio, context, NULL) ;
+ dir = NULL ;
+
+ if (*cmd_str == '/')
+ {
+ const char* p ;
+ p = cmd_str ;
+ while (*p > ' ')
+ ++p ;
+ dir = qpath_set_n(NULL, cmd_str, p - cmd_str) ;
+ } ;
+
+ uty_vin_new_context(vio, context, dir) ;
+
+ if (dir != NULL)
+ qpath_free(dir) ;
/* 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) ;
+ uty_vin_push(vio, vf, VIN_PIPE, vty_pipe_read_ready,
+ vty_pipe_read_timeout, pipe_buffer_size) ;
+ vf->read_timeout = pipe_timeout ;
/* 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) ;
+ uty_pipe_open_complete(vf, child, -1, pipes[err_pipe][in_fd]) ;
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.
+ * Open VOUT_PIPE or VOUT_SH_CMD: pipe which is going to be written to
+ * (or not, if shell_cmd), where 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.
+ * If could not open, issues message to the vio.
*
* 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)
+uty_pipe_write_open(vty_io vio, qstring command, bool shell_cmd, bool after)
{
pipe_set pipes ;
const char* cmd_str ;
pid_t child ;
vio_vf vf ;
- VTY_ASSERT_CLI_THREAD_LOCKED() ;
+ VTY_ASSERT_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) ;
+ child = uty_pipe_fork(vio, cmd_str, pipes, shell_cmd ? vout_sh_cmd
+ : vout_pipe) ;
if (child < 0)
return CMD_WARNING ;
- /* OK -- now push the new output onto the vout_stack. */
-
- if (shell_only)
+ /* OK -- now push the new output onto the vout_stack.
+ *
+ * Note that for VOUT_SH_CMD no vfd is set up, and the vout_state is
+ * immediately set to vf_end.
+ */
+ if (shell_cmd)
{
vf = uty_vf_new(vio, cmd_str, -1, vfd_none, vfd_io_none) ;
- uty_vout_push(vio, vf, VOUT_SHELL_ONLY, NULL, NULL, 0) ;
+ uty_vout_push(vio, vf, VOUT_SH_CMD, NULL, NULL, 0, after) ;
+ vf->vout_state = vf_end ;
}
else
{
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) ;
+ uty_vout_push(vio, vf, VOUT_PIPE, vty_pipe_write_ready,
+ vty_pipe_write_timeout, pipe_buffer_size, after) ;
+ vf->write_timeout = pipe_timeout ;
} ;
/* 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 */
+ uty_pipe_open_complete(vf, child, pipes[in_pipe][in_fd],
+ pipes[err_pipe][in_fd]) ;
+ /* Until eof (or error or timeout) on the vin, neither wait for or timeout
+ * the pipe return or the pipe stderr return.
+ */
+ qassert(vf->pr_timeout == 0) ;
+ qassert(vf->ps_timeout == 0) ;
return CMD_SUCCESS ;
} ;
@@ -931,38 +1257,60 @@ uty_pipe_write_open(vty_io vio, qstring command, bool shell_only)
* 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.
+ * out_pipes this is the child's stdout and its 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.
+ * For non-blocking, sets up the pipe return and the pipe stderr return ready
+ * and timeout actions, but leaves pr_timeout == 0 and ps_timeout == 0
*/
static void
-uty_pipe_open_complete(vio_vf vf, pid_t pid, int ret_fd, vio_vf slave)
+uty_pipe_open_complete(vio_vf vf, pid_t pid, int pr_fd, int ps_fd)
{
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) ;
+ iot = vfd_io_read | (vf->blocking ? vfd_io_ps_blocking : 0) ;
- vf->pr_vfd = vio_vfd_new(ret_fd, vfd_pipe, iot, vf) ;
- vf->pr_state = vf_open ;
+ /* If there is a pipe return, set up vfd and for non-blocking prepare the
+ * read ready and read timeout actions.
+ *
+ * Note that do not at this stage set a timeout value, but do set the return
+ * read ready, to proceed asynchronously.
+ */
+ if (pr_fd >= 0)
+ {
+ vf->pr_vfd = vio_vfd_new(pr_fd, vfd_pipe, iot, vf) ;
+ vf->pr_state = vf_open ;
- vf->pr_only = false ;
+ qassert(vf->pr_timeout == 0) ;
- if (!vf->blocking)
- vio_vfd_set_read_action(vf->pr_vfd, uty_pipe_return_ready) ;
+ if (!vf->blocking)
+ {
+ vio_vfd_set_read_action(vf->pr_vfd, vty_pipe_return_ready) ;
+ vio_vfd_set_read_timeout_action(vf->pr_vfd, vty_pipe_return_timeout) ;
+
+ vio_vfd_set_read(vf->pr_vfd, on, vf->pr_timeout) ;
+ } ;
+ } ;
+
+ /* Set up vfd for pipe stderr return and for non-blocking prepare the
+ * read ready and read timeout actions.
+ *
+ * Note that do not at this stage set a timeout value, or set the return
+ * read ready.
+ */
+ vf->ps_vfd = vio_vfd_new(ps_fd, vfd_pipe, iot, vf) ;
+ vf->ps_state = vf_open ;
- vio_vfd_set_read_timeout_action(vf->pr_vfd, uty_pipe_return_timeout) ;
+ qassert(vf->ps_timeout == 0) ;
- /* Configure master/slave relationship. */
- slave->pr_master = vf ;
- vf->pr_slave = slave ;
+ if (!vf->blocking)
+ {
+ vio_vfd_set_read_action(vf->ps_vfd, vty_pipe_stderr_return_ready) ;
+ vio_vfd_set_read_timeout_action(vf->ps_vfd, vty_pipe_stderr_return_timeout) ;
+
+ vio_vfd_set_read(vf->ps_vfd, on, vf->ps_timeout) ;
+ } ;
} ;
/*------------------------------------------------------------------------------
@@ -971,13 +1319,15 @@ uty_pipe_open_complete(vio_vf vf, pid_t pid, int ret_fd, vio_vf slave)
* 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
+ * input from child's stderr as stderr return fd
*
* out_pipe -- output to child's stdin as main fd
- * input from child's stderr & stdout as return fd
+ * input from child's stdout as return fd
+ * input from child's stderr as stderr return fd
*
- * ret_pipe -- nothing for main fd
- * input from child's stderr & stdout as return fd
+ * err_pipe -- nothing for main fd
+ * input from child's stdout as return fd
+ * input from child's stderr as stderr return fd
*
* vfork to create child process and then:
*
@@ -985,9 +1335,14 @@ uty_pipe_open_complete(vio_vf vf, pid_t pid, int ret_fd, vio_vf slave)
*
* -- 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.
+ *
+ * Returns: > 0 -- OK, this is child pid
+ * < 0 -- Failed
+ *
+ * NB: only returns in the parent process -- exec's in the child.
*/
static pid_t
-uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes, pipe_id_t type)
+uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes, pipe_type_t type)
{
pid_t child ;
int id ;
@@ -1000,15 +1355,19 @@ uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes, pipe_id_t type)
} ;
/* Open as many pipes as are required. */
- if (type == in_pipe)
+ if (type == vin_pipe)
if (!uty_pipe_pair(vio, cmd_str, "input pipe", pipes, in_pipe, in_fd))
return -1 ;
- if (type == out_pipe)
+ if ((type == vout_pipe) || (type == vout_sh_cmd))
+ if (!uty_pipe_pair(vio, cmd_str, "return pipe", pipes, in_pipe, in_fd))
+ return -1 ;
+
+ if (type == vout_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))
+ if (!uty_pipe_pair(vio, cmd_str, "stderr pipe", pipes, err_pipe, in_fd))
return -1 ;
/* Off to the races */
@@ -1017,7 +1376,6 @@ uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes, pipe_id_t type)
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 */
@@ -1029,7 +1387,7 @@ uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes, pipe_id_t type)
/* 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) ;
+ uty_pipe_close_half_pipe(pipes[err_pipe], out_fd) ;
}
else if (child < 0) /* In parent -- failed */
uty_pipe_fork_fail(vio, cmd_str, "vfork", pipes, NULL, "child") ;
@@ -1039,7 +1397,9 @@ uty_pipe_fork(vty_io vio, const char* cmd_str, pipe_set pipes, pipe_id_t type)
/*------------------------------------------------------------------------------
* Open a pipe pair -- generate suitable error message if failed and close
- * any early pipes that have been opened.
+ * any earlier pipes that have been opened.
+ *
+ * Returns: true <=> success
*/
static bool
uty_pipe_pair(vty_io vio, const char* cmd_str,
@@ -1055,15 +1415,17 @@ uty_pipe_pair(vty_io vio, const char* cmd_str,
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 uty_pipe_fork_fail(vio, cmd_str, what, pipes, NULL,
+ "set non-blocking for") ;
return true ;
} ;
/*------------------------------------------------------------------------------
- * Open a pipe pair -- generate suitable error message if failed and close
- * any early pipes that have been opened.
+ * Failed to open pipe: generate suitable error message and close any earlier
+ * pipes that have been opened.
+ *
+ * Returns: false
*/
static bool
uty_pipe_fork_fail(vty_io vio, const char* cmd_str,
@@ -1114,27 +1476,30 @@ uty_pipe_close_half_pipe(pipe_pair pair, pipe_half_t half)
*
* 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
+ * err_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.
+ *
+ * Returns: true <=> good to go
+ * false => some sort of error -- see errno
*/
static bool
uty_pipe_exec_prepare(vty_io vio, pipe_set pipes)
{
- int std[stds] ;
- int fd ;
+ 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 */
+ std[stderr_fd] = pipes[err_pipe][out_fd] ; /* stderr for child */
/* Mark everything to be closed on exec */
- for (fd = 0 ; fd <= 1024 ; ++fd) /* TODO -- max_fd */
+ for (fd = 0 ; fd < qlib_open_max ; ++fd)
{
int fd_flags ;
fd_flags = fcntl(fd, F_GETFD, 0) ;
@@ -1152,7 +1517,7 @@ uty_pipe_exec_prepare(vty_io vio, pipe_set pipes)
for (fd = 0 ; fd < stds ; ++fd)
{
if ((std[fd] >= 0) && (std[fd] < stds))
- if ((std[fd] = fcntl(std[fd], F_DUPFD, stds)) < 0)
+ if ((std[fd] = fcntl(std[fd], F_DUPFD_CLOEXEC, stds)) < 0)
return false ;
} ;
@@ -1184,38 +1549,45 @@ uty_pipe_exec_prepare(vty_io vio, pipe_set pipes)
} ;
/*------------------------------------------------------------------------------
- * Command line fetch from a pipe and (for blocking) shovel return into
- * slave and push.
+ * Command line fetch from a pipe -- VIN_PIPE in vf_open state.
+ *
+ * Before attempting to fetch command line, will shovel any return into the
+ * slave and push. This means that any return is output between command lines.
*
* 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
+ * nothing to be had, or times out.
*
- * If returns CMD_EOF will have vf->vin_state == vf_eof. Further, will not
- * have vf_eof until returns CMD_EOF (the first time).
+ * Returns: CMD_SUCCESS -- have another command line ready to go
+ * CMD_WAITING -- waiting for input not output <=> non-blocking
+ * CMD_EOF -- ran into EOF -- on input
+ * CMD_IO_ERROR -- ran into an I/O error or time-out
*
* 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.
+ * NB: the vin_state is set to vf_end when CMD_EOF is returned, and this
+ * code may not be called again.
+ *
+ * Signals CMD_EOF on the main input. If this occurs before EOF on the
+ * return input, any remaining return input must be dealt with before
+ * the vf is finally closed -- see uty_pipe_read_close().
+ *
+ * NB: the vout_state is set to vf_end when CMD_IO_ERROR is returned, and
+ * this code may not be called again.
+ *
+ * When an error occurs it is signalled to the command loop. This function
+ * is called from the command loop -- so, in fact, the CMD_IO_ERROR
+ * return code does the trick.
*/
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 ? */
+ qassert(vf->vin_type == VIN_PIPE) ;
+ qassert(vf->vin_state == vf_open) ;
while (1) /* so blocking stuff can loop round */
{
@@ -1229,47 +1601,46 @@ uty_pipe_fetch_command_line(vio_vf vf, cmd_action action)
*/
ret = uty_fifo_command_line(vf, action) ;
- if ((ret == CMD_SUCCESS) || (ret == CMD_EOF))
+ if (ret != CMD_WAITING)
return ret ;
- /* Worry about the return.
+ /* If blocking, worry about the stderr return -- just to keep I/O moving.
*
- * 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).
+ * Expect only CMD_SUCCESS or CMD_IO_ERROR.
*/
- if (vf->blocking && (vf->pr_state == vf_open))
+ if (vf->blocking && (vf->ps_state == vf_open))
{
- ret = uty_pipe_shovel(vf, false, false) ; /* not final or closing */
+ ret = uty_pipe_stderr_suck(vf, false) ; /* not final */
- if ((ret != CMD_SUCCESS) && (ret != CMD_EOF))
- return ret ; /* cannot continue */
- } ;
+ qassert((ret == CMD_SUCCESS) || (ret == CMD_IO_ERROR)) ;
- /* Need more from the main input. */
+ if (ret != CMD_SUCCESS)
+ 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 */
+ return uty_vf_error(vf, verr_io_vin, errno) ;
if (get == -2) /* EOF met immediately */
{
- vf->vin_state = vf_eof ;
+ vf->vin_state = vf_end ;
continue ; /* loop back -- deals with possible
final line and the return. */
} ;
- assert (get == 0) ;
+ qassert(get == 0) ;
- /* We get here if main input is not yet at eof. */
- assert ((vf->vin_state == vf_open) || (vf->pr_state == vf_open)) ;
+ /* We get here if main input is not yet at eof, but has nothing
+ * to read at the moment.
+ */
+ qassert(vf->vin_state == vf_open) ;
if (!vf->blocking)
{
@@ -1277,17 +1648,20 @@ uty_pipe_fetch_command_line(vio_vf vf, cmd_action action)
return CMD_WAITING ;
} ;
- /* Implement blocking I/O, with timeout */
+ /* Implement blocking I/O, with timeout
+ *
+ * Note that waits for both the main vin and the pipe return.
+ */
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 ;
+ if (qps_mini_wait(qm, NULL, false) != 0)
+ continue ; /* loop back */
- continue ; /* loop back */
+ return uty_vf_error(vf, verr_to_vin, 0) ;
} ;
} ;
@@ -1295,27 +1669,25 @@ uty_pipe_fetch_command_line(vio_vf vf, cmd_action action)
* 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.
+ * For blocking, does not push to the pipe while there is return input to be
+ * read and pushed to the slave. For non-blocking the return input/output is
+ * handled autonomously.
*
* 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)
+ * Returns: CMD_SUCCESS -- done everything possible
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
*
* 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.
+ * here set the pr_vfd into read ready state. This requires no further action
+ * from the caller, the background pselect process will complete the output and
+ * may signal the result via uty_cmd_signal().
*
* In "blocking" state, will not return until have written everything there is,
* away, or cannot continue.
@@ -1326,58 +1698,91 @@ 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.
+ qassert((vf->vout_type == VOUT_PIPE) || (vf->vout_type == VOUT_SH_CMD)) ;
+
+ if (vf->vout_state != vf_open)
+ return CMD_SUCCESS ; /* Get out if going nowhere */
+
+ /* If blocking, keep the stderr return moving.
*/
- if (vf->blocking && (vf->pr_state == vf_open))
+ if (vf->blocking && (vf->ps_state == vf_open))
{
cmd_return_code_t ret ;
- ret = uty_pipe_shovel(vf, final, false) ; /* not closing */
+ ret = uty_pipe_stderr_suck(vf, false) ; /* not final */
- if ((ret != CMD_SUCCESS) && (ret != CMD_EOF) && !final)
- return ret ;
+ qassert((ret == CMD_SUCCESS) || (ret == CMD_IO_ERROR)) ;
+
+ if (ret != CMD_SUCCESS)
+ return ret ; /* cannot continue */
} ;
- /* Now write away everything we can -- nothing if pr_only.
+ /* If squelching, dump anything we have in the obuf.
+ *
+ * Otherwise, write contents away.
*/
- if (!vf->pr_only)
+ if (vf->vio->cancel)
+ vio_fifo_clear(vf->obuf, false) ; /* keep end mark */
+ else
{
- 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 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 ;
- if (put < 0)
- return CMD_IO_ERROR ; /* TODO */
+ ret = uty_pipe_shovel(vf, final) ;
+
+ if (ret != CMD_SUCCESS)
+ return ret ; /* cannot continue */
+ } ;
- if ((put == 0) || final) /* all gone or final */
+ if (vf->vout_state != vf_open)
+ break ; /* Quit if error has stopped the main vout */
+
+ /* Now write to the main vout, blocking if required.
+ */
+ put = vio_fifo_write_nb(vf->obuf, vio_vfd_fd(vf->vfd), true) ;
+
+ if (put == 0) /* all gone */
break ;
- /* Cannot write everything away without waiting */
+ if (put < 0)
+ return uty_vf_error(vf, verr_io_vout, errno) ;
+
+ /* Cannot write everything away without waiting
+ */
if (!vf->blocking)
{
- uty_vf_set_write(vf, on) ;
+ if (!final)
+ uty_vf_set_write(vf, on) ;
return CMD_WAITING ;
} ;
- /* Implement blocking I/O, with timeout */
+ /* Implement blocking I/O, with timeout
+ *
+ * Note that waits for both the main vout and the pipe return.
+ */
+ if (final)
+ return CMD_WAITING ;
+
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 ;
+ vf->write_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)
+ continue ; /* Loop back */
- /* Loop back to vio_fifo_write_nb() */
+ return uty_vf_error(vf, verr_to_vout, 0) ;
} ;
} ;
@@ -1385,112 +1790,282 @@ uty_pipe_out_push(vio_vf vf, bool final)
} ;
/*------------------------------------------------------------------------------
- * Shovel from return to slave, pushing the slave as we go.
+ * Shovel from pipe return to slave, pushing the slave as we go.
+ *
+ * While the main vout is open, this function is used:
+ *
+ * * VOUT_PIPE: for blocking vf, this is called each time the output is
+ * pushed -- to keep any returned stuff moving. Does not
+ * block reading the return, but may block writing to the
+ * slave.
+ *
+ * for non-blocking vf, this is called by the pselect
+ * process, which keeps the return moving autonomously,
+ * or may be called "final".
+ *
+ * Note that pr_timeout == 0 while the main vout is open.
*
- * 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.
+ * * VOUT_SH_CMD: the main vin/vout is closed from the get go.
*
- * 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
+ * All pipe return is handled by the close process.
*
- * For VOUT_SHELL_ONLY: same as VOUT_PIPE.
+ * When the main vin/vout is closed:
*
- * For blocking, keeps going until can read no more from the return. The slave
- * output may block, and could time out.
+ * * VOUT_PIPE & VOUT_SH_CMD:
*
- * 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 blocking vf, this is called by the close function,
+ * to suck up any remaining return, and push it to the slave.
+ * May block reading the return and/or the slave.
*
- * For "final" does not attempt to read anything from the return, but sets it
- * vf_eof.
+ * for non-blocking vf, this is called by the pselect
+ * process, which keeps the return moving autonomously, or
+ * may be called "final".
*
- * For "closing" (if not "final"), if blocking will block on the return, until
- * get eof (or time out).
+ * Note that pr_timeout != 0 once the main vout is closed.
*
- * 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
+ * Returns: CMD_SUCCESS -- done everything possible
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * NB: if runs into I/O error or time-out on the slave output, then sets
+ * vf_end on the TODO
*
* This can be called in any thread.
*/
static cmd_return_code_t
-uty_pipe_shovel(vio_vf vf, bool final, bool closing)
+uty_pipe_shovel(vio_vf vf, bool final)
{
- vio_vf slave ;
-
VTY_ASSERT_LOCKED() ; /* In any thread */
- // TODO other pr_state ??? vf_closed/vf_closing/vf_error ??
+ /* The pipe return MUST still be open, but may be cancelling all I/O
+ * or the slave may already be in error or otherwise not open.
+ */
+ qassert(vf->pr_state == vf_open) ;
+ qassert((vf->vout_type == VOUT_PIPE) || (vf->vout_type == VOUT_SH_CMD)) ;
+
+ if ((vf->vio->cancel) || (vf->vout_next->vout_state != vf_open))
+ return CMD_SUCCESS ;
/* 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))
+ while (1)
{
cmd_return_code_t ret ;
int get ;
- get = vio_fifo_read_nb(slave->obuf, vio_vfd_fd(vf->pr_vfd), 10) ;
+ get = vio_fifo_read_nb(vf->vout_next->obuf, vio_vfd_fd(vf->pr_vfd), 10) ;
- if (get == 0)
+ if (get == 0) /* Nothing there, but not EOF */
{
qps_mini_t qm ;
- if (!closing || !vf->blocking)
- return CMD_SUCCESS ; /* quit if now dry */
+ /* Nothing there to read.
+ *
+ * Returns if not blocking, if no timeout is set or if is "final".
+ *
+ * NB: before blocking on the pipe return, poll the pipe stderr
+ * return to keep that moving -- so child cannot stall trying
+ * to output to its stderr !
+ */
+ if (!vf->blocking || (vf->pr_timeout == 0) || final)
+ break ; /* do not block reading */
+
+ if (vf->ps_state == vf_open)
+ {
+ ret = uty_pipe_stderr_suck(vf, false) ; /* not final */
+
+ qassert((ret == CMD_SUCCESS) || (ret == CMD_IO_ERROR)) ;
+
+ if (ret != CMD_SUCCESS)
+ return ret ; /* cannot continue */
+ } ;
- /* 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 ;
+ vf->pr_timeout) ;
+ if (qps_mini_wait(qm, NULL, false) != 0)
+ continue ; /* loop back */
+
+ return uty_vf_error(vf, verr_to_pr, 0) ; /* CMD_IO_ERROR */
}
- else if (get >= 0)
+ else if (get > 0) /* Read something */
{
- ret = uty_cmd_out_push(slave, final) ; /* may block etc. */
+ ret = uty_cmd_out_push(vf->vout_next, final) ; /* may block */
- if (ret != CMD_SUCCESS)
- return ret ;
+ if (ret == CMD_SUCCESS)
+ continue ; /* Loop back if emptied buffer */
+
+ if (ret == CMD_IO_ERROR)
+ uty_pipe_return_stop(vf) ;
+ /* No point continuing */
+ else
+ {
+ /* Is CMD_WAITING on the slave output.
+ *
+ * If we are "final":
+ *
+ * for blocking vf that means would have blocked, but didn't.
+ *
+ * for non-blocking vf, that means could not empty the buffer.
+ *
+ * If not "final":
+ *
+ * cannot be a blocking vf !
+ *
+ * for non-blocking vf, the output buffer will be serviced,
+ * in due course and can leave it up to the pselect() process
+ * to read anything more -- no need to do so now.
+ *
+ * ...in all cases don't want to try to input or output any more,
+ * so give up and return CMD_WAITING.
+ */
+ qassert(ret == CMD_WAITING) ;
+ } ;
+
+ return ret ; /* CMD_WAITING or CMD_IO_ERROR */
}
- else if (get == -1)
- return CMD_IO_ERROR ; /* register error TODO */
+ else if (get == -1) /* Hit error */
+ {
+ return uty_vf_error(vf, verr_io_pr, errno) ; /* CMD_IO_ERROR */
+ }
else
{
- assert (get == -2) ; /* eof on the return */
- break ;
+ assert (get == -2) ; /* eof on the return */
+
+ vf->pr_state = vf_end ;
+ break ; /* quit: OK */
} ;
} ;
- vf->pr_state = vf_eof ; /* Set EOF TODO: willy nilly ? */
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Suck the pipe stderr return to vf->ps_buf.
+ *
+ * While the main vin/vout is open or the pipe return is open, this function
+ * is used:
+ *
+ * for blocking vf, this is called each time the output is pushed, or the
+ * input is read, or the pipe return is read -- to keep any returned stuff
+ * moving. Does not block reading the stderr return.
+ *
+ * for non-blocking vf, this is called by the pselect which keeps the stderr
+ * return moving autonomously.
+ *
+ * Note that ps_timeout == 0 under these conditions.
+ *
+ * When the main vin/vout and the pipe return are closed:
+ *
+ * for blocking vf, this is called by the close function, to suck up any
+ * remaining stderr return, which may block.
+ *
+ * for non-blocking vf, this is called by the pselect process, which keeps
+ * the stderr return moving autonomously, or may be called "final".
+ *
+ * Note that ps_timeout != 0 under these conditions.
+ *
+ * Returns: CMD_SUCCESS -- done everything possible
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * This can be called in any thread.
+ */
+static cmd_return_code_t
+uty_pipe_stderr_suck(vio_vf vf, bool final)
+{
+ VTY_ASSERT_LOCKED() ; /* In any thread */
+
+ /* The pipe return MUST still be open, but may be cancelling all I/O
+ * or the slave may already be in error or otherwise not open.
+ */
+ qassert(vf->ps_state == vf_open) ;
+
+ /* Suck
+ */
+ while (1)
+ {
+ int get ;
+
+ if (vf->ps_buf == NULL)
+ {
+ char buf[100] ;
+ get = read_nb(vio_vfd_fd(vf->ps_vfd), buf, sizeof(buf)) ;
+
+ if (get > 0)
+ vio_fifo_put_bytes(uty_pipe_ps_buf(vf), buf, get) ;
+ }
+ else
+ get = vio_fifo_read_nb(vf->ps_buf, vio_vfd_fd(vf->ps_vfd), 10) ;
+
+ if (get == 0) /* Nothing there, but not EOF */
+ {
+ qps_mini_t qm ;
+
+ /* Nothing there to read.
+ *
+ * Returns if not blocking, if no timeout is set or if is "final".
+ *
+ * NB: before blocking on the pipe return, poll the pipe stderr
+ * return to keep that moving -- so child cannot stall trying
+ * to output to its stderr !
+ */
+ if (!vf->blocking || (vf->ps_timeout == 0) || final)
+ break ; /* do not block reading */
+
+ qps_mini_set(qm, vio_vfd_fd(vf->ps_vfd), qps_read_mnum,
+ vf->ps_timeout) ;
+ if (qps_mini_wait(qm, NULL, false) != 0)
+ continue ; /* loop back */
+
+ return uty_vf_error(vf, verr_to_ps, 0) ; /* CMD_IO_ERROR */
+ }
+
+ else if (get > 0) /* Read something */
+ {
+ continue ;
+ }
- return CMD_EOF ;
+ else if (get == -1) /* Hit error on stderr return */
+ {
+ return uty_vf_error(vf, verr_io_ps, errno) ; /* CMD_IO_ERROR */
+ }
+
+ else
+ {
+ assert (get == -2) ; /* eof on the stderr return */
+
+ vf->ps_state = vf_end ;
+ break ; /* quit: OK */
+ } ;
+ } ;
+
+ return CMD_SUCCESS ;
} ;
/*------------------------------------------------------------------------------
- * Pipe is ready to read -- this is the call-back planted in the vf->vfd, of
- * type VIN_PIPE.
+ * Pipe is ready to read -- call-back for VIN_PIPE.
*
- * Can restart the command_queue.
+ * This is used if the VIN_PIPE is non-blocking.
+ *
+ * Signals command loop, so may continue if was waiting.
*
* 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)
+vty_pipe_read_ready(vio_vfd vfd, void* action_info)
{
vio_vf vf ;
@@ -1500,19 +2075,23 @@ uty_pipe_read_ready(vio_vfd vfd, void* action_info)
vf = action_info ;
assert(vf->vfd == vfd) ;
+ /* If the vin is no longer vf_open then read ready should have been turned
+ * off -- but kicking the command loop will not hurt.
+ */
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.
+ * Pipe has timed out, waiting to read -- call-back for VIN_PIPE.
+ *
+ * This is used if the VIN_PIPE is non-blocking.
*
- * ????
+ * Signals a timeout error to the command loop.
*/
static vty_timer_time
-uty_pipe_read_timeout(vio_timer timer, void* action_info)
+vty_pipe_read_timeout(vio_timer timer, void* action_info)
{
vio_vf vf ;
@@ -1522,7 +2101,7 @@ uty_pipe_read_timeout(vio_timer timer, void* action_info)
vf = action_info ;
assert(vf->vfd->read_timer == timer) ;
-// TODO .... signal time out
+ uty_vf_error(vf, verr_to_vin, 0) ; /* signals command loop */
VTY_UNLOCK() ;
@@ -1530,27 +2109,22 @@ uty_pipe_read_timeout(vio_timer timer, void* action_info)
} ;
/*------------------------------------------------------------------------------
- * 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.
+ * Pipe return is ready to read -- call-back for VOUT_PIPE and VOUT_SH_CMD.
*
- * VOUT_SHELL_ONLY: used to continually shovel from the return to the
- * slave -- which happens while closing.
+ * This is used if the VOUT_PIPE/VOUT_SH_CMD is non-blocking.
*
- * If all is well, and more return input is expected, re-enables read ready.
+ * Shovels any available return input to the output. If required, the shoveller
+ * will set read ready when runs out of input.
*
- * 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().
+ * Signals to the command loop iff hits eof or an error or time-out. (Also
+ * signals to the command loop if is not open... just in case command loop is
+ * waiting.)
*
- * For all other returns, kick the command loop, which if it is waiting is TODO
+ * Note that the read_ready state and any time out are automatically
+ * disabled when they go off.
*/
static void
-uty_pipe_return_ready(vio_vfd vfd, void* action_info)
+vty_pipe_return_ready(vio_vfd vfd, void* action_info)
{
cmd_return_code_t ret ;
vio_vf vf ;
@@ -1561,45 +2135,82 @@ uty_pipe_return_ready(vio_vfd vfd, void* action_info)
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 */
+ /* If the pipe return is no longer open then read ready should have been
+ * turned off -- but kicking the command loop will not hurt.
+ *
+ * Note that uty_pipe_shovel() returns CMD_WAITING, unless hits eof
+ * (CMD_SUCCESS) or gets an I/O error or timeout (CMD_IO_ERROR).
+ */
+ if (vf->pr_state == vf_open)
+ ret = uty_pipe_shovel(vf, false) ; /* not final */
+ else
+ ret = CMD_SUCCESS ;
+
+ if (vf->pr_state == vf_open)
+ vio_vfd_set_read(vf->pr_vfd, on, vf->pr_timeout) ;
+ else if (vf->ps_state != vf_open)
+ uty_cmd_signal(vf->vio, ret) ; /* CMD_SUCCESS or CMD_IO_ERROR */
VTY_UNLOCK() ;
} ;
/*------------------------------------------------------------------------------
- * Pipe return slave is ready to write.
+ * Pipe stderr return is ready to read -- call-back for VIN_PIPE, VOUT_PIPE and
+ * VOUT_SH_CMD.
+ *
+ * This is used if the VIN_PIPE/VOUT_PIPE/VOUT_SH_CMD is non-blocking.
*
- * 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.
+ * Shovels any available return input to the output. If required, the shoveller
+ * will set read ready when runs out of input.
*
- * This will set read ready on the return, to keep the process of shovelling
- * from return to slave going.
+ * Signals to the command loop iff hits eof or an error or time-out. (Also
+ * signals to the command loop if is not open... just in case command loop is
+ * waiting.)
+ *
+ * Note that the read_ready state and any time out are automatically
+ * disabled when they go off.
*/
-extern void
-uty_pipe_return_slave_ready(vio_vf slave)
+static void
+vty_pipe_stderr_return_ready(vio_vfd vfd, void* action_info)
{
- vio_vf vf ;
+ cmd_return_code_t ret ;
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->ps_vfd == vfd) ;
+
+ /* If the pipe stderr return is no longer open then read ready should have
+ * been turned off -- but kicking the command loop will not hurt.
+ *
+ * Note that uty_pipe_shovel() returns CMD_WAITING, unless hits eof
+ * (CMD_SUCCESS) or gets an I/O error or timeout (CMD_IO_ERROR).
+ */
+ if (vf->ps_state == vf_open)
+ ret = uty_pipe_stderr_suck(vf, false) ; /* not final */
+ else
+ ret = CMD_SUCCESS ;
- vf = slave->pr_master ;
- assert(vf->pr_slave == slave) ;
+ if (vf->ps_state == vf_open)
+ vio_vfd_set_read(vf->ps_vfd, on, vf->ps_timeout) ;
+ else if (vf->pr_state != vf_open)
+ uty_cmd_signal(vf->vio, ret) ; /* CMD_SUCCESS or CMD_IO_ERROR */
- vio_vfd_set_read(vf->pr_vfd, on, 0) ;
+ VTY_UNLOCK() ;
} ;
/*------------------------------------------------------------------------------
- * 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 ????
+ * Pipe return has timed out, waiting to read -- call-back for VIN_PIPE,
+ * VOUT_PIPE and VOUT_SH_CMD.
*
- * ????
+ * This is used if the VIN_PIPE/VOUT_PIPE/VOUT_SH_CMD is non-blocking.
+ *
+ * Signals a timeout error to the command loop.
*/
static vty_timer_time
-uty_pipe_return_timeout(vio_timer timer, void* action_info)
+vty_pipe_return_timeout(vio_timer timer, void* action_info)
{
vio_vf vf ;
@@ -1609,7 +2220,10 @@ uty_pipe_return_timeout(vio_timer timer, void* action_info)
vf = action_info ;
assert(vf->pr_vfd->read_timer == timer) ;
-//cq_continue(vf->vio->vty) ; TODO
+ if (vf->ps_state == vf_open)
+ vio_vfd_set_read(vf->ps_vfd, off, 0) ;
+
+ uty_vf_error(vf, verr_to_ps, 0) ; /* signals command loop */
VTY_UNLOCK() ;
@@ -1617,14 +2231,46 @@ uty_pipe_return_timeout(vio_timer timer, void* action_info)
} ;
/*------------------------------------------------------------------------------
- * Pipe output is ready to write -- this is the call-back planted in the
- * vf->vfd of a VOUT_PIPE
+ * Pipe return has timed out, waiting to read -- call-back for VIN_PIPE,
+ * VOUT_PIPE and VOUT_SH_CMD.
+ *
+ * This is used if the VIN_PIPE/VOUT_PIPE/VOUT_SH_CMD is non-blocking.
+ *
+ * Signals a timeout error to the command loop.
+ */
+static vty_timer_time
+vty_pipe_stderr_return_timeout(vio_timer timer, void* action_info)
+{
+ vio_vf vf ;
+
+ VTY_LOCK() ;
+ VTY_ASSERT_CLI_THREAD() ;
+
+ vf = action_info ;
+ assert(vf->ps_vfd->read_timer == timer) ;
+
+ if (vf->pr_state == vf_open)
+ vio_vfd_set_read(vf->pr_vfd, off, 0) ;
+
+ uty_vf_error(vf, verr_to_pr, 0) ; /* signals command loop */
+
+ VTY_UNLOCK() ;
+
+ return 0 ; /* Do not restart timer */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Pipe is ready to write -- call-back for VOUT_PIPE.
+ *
+ * This is used if the VOUT_PIPE is non-blocking.
+ *
+ * Signals command loop, so may continue if was waiting.
*
* 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)
+vty_pipe_write_ready(vio_vfd vfd, void* action_info)
{
cmd_return_code_t ret ;
vio_vf vf ;
@@ -1635,25 +2281,31 @@ uty_pipe_write_ready(vio_vfd vfd, void* action_info)
vf = action_info ;
assert(vf->vfd == vfd) ;
- ret = uty_pipe_out_push(vf, false) ; /* not final */
+ /* If the vout is no longer vf_open then write ready should have been turned
+ * off -- but kicking the command loop will not hurt.
+ */
+ if (vf->vout_state == vf_open)
+ {
+ ret = uty_pipe_out_push(vf, false) ; /* not final */
+ }
+ else
+ ret = CMD_SUCCESS ;
if (ret != CMD_WAITING)
- uty_cmd_signal(vf->vio, ret) ;
-
- if ((ret == CMD_SUCCESS) && (vf->pr_master != NULL))
- uty_pipe_return_slave_ready(vf) ;
+ uty_cmd_signal(vf->vio, ret) ; /* CMD_SUCCESS or CMD_IO_ERROR */
VTY_UNLOCK() ;
} ;
/*------------------------------------------------------------------------------
- * Pipe output has timed out -- this is the call-back planted in the
- * vf->vfd of a VOUT_PIPE
+ * Pipe has timed out, waiting to write -- call-back for VOUT_PIPE.
+ *
+ * This is used if the VOUT_PIPE is non-blocking.
*
- * ????
+ * Signals a timeout error to the command loop.
*/
static vty_timer_time
-uty_pipe_write_timeout(vio_timer timer, void* action_info)
+vty_pipe_write_timeout(vio_timer timer, void* action_info)
{
vio_vf vf ;
@@ -1663,7 +2315,7 @@ uty_pipe_write_timeout(vio_timer timer, void* action_info)
vf = action_info ;
assert(vf->vfd->write_timer == timer) ;
-// TODO
+ uty_vf_error(vf, verr_to_vout, 0) ; /* signals command loop */
VTY_UNLOCK() ;
@@ -1671,257 +2323,434 @@ uty_pipe_write_timeout(vio_timer timer, void* action_info)
} ;
/*------------------------------------------------------------------------------
- * 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.
+ * Complete the close of a VIN_PIPE after the vfd has been read closed.
+ *
+ * Nothing needs to be done with the main input, but must close the return,
+ * collect the child and release the slave.
+ *
+ * Returns: CMD_SUCCESS -- done everything possible, the return is done with
+ * (and the vfd closed) and the child has been
+ * collected (or is overdue).
+ * CMD_WAITING -- not "final" => waiting for return I/O to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * NB: if "final", whatever the return code, the pipe return is closed and the
+ * child dismissed.
+ *
+ * If returns CMD_WAITING (and not "final") then the command loop will be
+ * signalled when it is time to call this function again to progress the
+ * close operation.
*/
extern cmd_return_code_t
uty_pipe_read_close(vio_vf vf, bool final)
{
+ VTY_ASSERT_LOCKED() ;
+
+ qassert(vf->vin_type == VIN_PIPE) ;
+ qassert(vf->vin_state == vf_end) ;
+
return uty_pipe_return_close(vf, final) ;
} ;
/*------------------------------------------------------------------------------
- * Close VOUT_PIPE or VOUT_SHELL_ONLY.
+ * Close VOUT_PIPE or VOUT_SH_CMD.
*
* 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
+ * Returns: CMD_SUCCESS -- done everything possible, the vout and the
+ * return are done with (and the vfds closed) and
+ * the child has been collected (or is overdue).
+ * CMD_WAITING -- not "final" => waiting for output or return I/O
+ * to complete <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * NB: if "final", whatever the return code, the pipe return is closed and the
+ * child dismissed.
+ *
+ * If returns CMD_WAITING (and not "final") then the command loop will be
+ * signalled when it is time to call this function again to progress the
+ * close operation.
*/
extern cmd_return_code_t
-uty_pipe_write_close(vio_vf vf, bool final, bool base, bool shell_only)
+uty_pipe_write_close(vio_vf vf, bool final)
{
- 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.
+ qassert((vf->vout_type == VOUT_PIPE) || (vf->vout_type == VOUT_SH_CMD)) ;
+
+ /* If the main vfd is still there, keep pushing (which, for blocking vf,
+ * will keep shovelling from pipe return to slave).
*/
- if (!vf->pr_only)
+ if (vf->vout_state == vf_open)
{
+ cmd_return_code_t ret ;
+
ret = uty_pipe_out_push(vf, final) ;
- if ((ret != CMD_SUCCESS) && !final)
+ if ((ret == CMD_WAITING) && !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 */
+ vf->vout_state = vf_end ; /* no further output */
} ;
+ /* Now need to close the output vfd to signal to the child that we
+ * are done -- note that closing an already closed vfd does nothing.
+ */
+ vf->vfd = vio_vfd_close(vf->vfd) ;
+
/* 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.
+ * Close the return on a VIN_PIPE, VOUT_PIPE or a VOUT_SH_CMD.
+ *
+ * This is the second stage of closing a pipe, after the vin or the main vout
+ * has reached vf_end, so all main I/O is complete (or failed).
+ *
+ * If returns CMD_WAITING from here, may be called again, any number of times,
+ * to attempt to complete the process.
+ *
+ * If "final" then will close after making efforts to complete I/O and collect
+ * child, short of blocking or waiting.
+ *
+ * Phase 1: if the return is vf_open:
+ *
+ * Set the read time out for the return (want any remaining return stuff, and
+ * the eof) which (for non-blocking) indicates is now prepared to block.
+ *
+ * If non-blocking:
+ *
+ * if not "final", set the read ready timeout (if not already set), and
+ * leave CMD_WAITING.
+ *
+ * if "final", kill the read ready stuff, and shovel once "final", just
+ * in case there is stuff there.
+ *
+ * If blocking:
+ *
+ * Shovel stuff from return to slave output. If not "final", may block and
+ * may time-out. If "final" will not block, but may return CMD_WAITING.
+ *
+ * Once return is all dealt with (or fails) then close the vfd and set the
+ * pipe return vf_closed.
+ *
+ * Phase 2: if the stderr return is vf_open:
+ *
+ * Set the read time out for the stderr return (want any remaining stderr
+ * return stuff, and the eof) which (for non-blocking) indicates is now
+ * prepared to block.
+ *
+ * Suck stderr return. If "final", do not block or wait for either input or
+ * output. If not final, may block and may time-out or may return
+ * CMD_WAITING.
+ *
+ * Once return is all dealt with (or fails) then close the vfd and set the
+ * pipe return vf_closed.
+ *
+ * If non-blocking:
+ *
+ * if not "final", set the read ready timeout (if not already set), and
+ * leave CMD_WAITING.
+ *
+ * if "final", kill the read ready stuff, and shovel once "final", just
+ * in case there is stuff there.
+ *
+ * If blocking:
+ *
+ * Shovel stuff from return to slave output. If not "final", may block and
+ * may time-out. If "final" will not block, but may return CMD_WAITING.
+ *
+ * Once return is all dealt with (or fails) then close the vfd and set the
+ * pipe return vf_closed.
+ *
+ * Phase 3: collect the child return code and deal with it:
+ *
+ * If the child has yet to be collected:
+ *
+ * if "final", collect child immediately or set overdue.
+ *
+ * if not "final":
+ *
+ * if non-blocking, return CMD_WAITING and wait for the SIGCHLD to do
+ * the business or to time out.
+ *
+ * if blocking, collect child or time out and set overdue.
*
- * If not final, may need to drain the return.
+ * If the child is not collected, or has not terminated cleanly, output
+ * diagnostic to the pipe stderr 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.
+ * If the child is not collected, the SIGCHLD handling will tidy up in the
+ * background.
*
- * Once any message has been pushed to the slave, can release the slave
- * and the close process will be complete.
+ * In any case, dismiss child.
*
- * If final, then will not block and will ignore errors. Return code reflects
- * the last operation.
+ * Phase 4: transfer the pipe stderr return to the main vio pipe stderr return.
*
- * Returns: CMD_SUCCESS -- closed and child collected
- * CMD_WAITING
- * CMD_IO_ERROR
- * CMD_IO_TIMEOUT
- * CMD_xxxx -- child error(s)
+ * This leaves it up to the vout_base to deal with the pipe stderr return,
+ * in its own time -- e.g when the output stack closes.
+ *
+ * When this (eventually) returns CMD_SUCCESS, all pipe return has been
+ * sucked up and has cleared the slave vout buffer, and the child has been
+ * collected -- unless something has gone wrong, and part of that has been
+ * short-circuited.
+ *
+ * Returns: CMD_SUCCESS -- closed and child collected
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * <=> not vf->blocking
+ * "final" => would have waited *or* blocked,
+ * but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * NB: if "final", whatever the return code, the pipe return is closed and the
+ * child dismissed.
*/
static cmd_return_code_t
uty_pipe_return_close(vio_vf vf, bool final)
{
- cmd_return_code_t ret ;
+ vty_io vio ;
VTY_ASSERT_CAN_CLOSE_VF(vf) ;
- /* If the return is still open, try to empty and close that. */
- if (vf->pr_state != vf_closed)
+ vio = vf->vio ;
+
+ /* Phase 1: if return is still open, try to empty it -- subject to "final".
+ * Sets timeout and will now block.
+ *
+ * If not blocking but "final", turn off read ready and any timeout,
+ * and shovel one last time.
+ *
+ * When return is all done, close it (which the child may see) and
+ * mark it vf_closed.
+ */
+ if (vf->pr_state == vf_open)
{
- ret = uty_pipe_return_empty(vf, final) ;
+ cmd_return_code_t ret ;
+ bool set_timeout ;
+
+ set_timeout = (vf->pr_timeout == 0) ;
+ vf->pr_timeout = pipe_timeout ;
- if (!final)
+ if (!vf->blocking)
{
- if (ret == CMD_SUCCESS)
+ if (!final)
{
- vio_vfd_set_read(vf->pr_vfd, on, 0) ; /* TODO timeout */
- return CMD_WAITING ;
+ if (set_timeout)
+ vio_vfd_set_read(vf->pr_vfd, on, vf->pr_timeout) ;
+
+ return CMD_WAITING ; /* in hands of pselect() */
} ;
- if (ret != CMD_EOF)
- return ret ;
+ vio_vfd_set_read(vf->pr_vfd, off, 0) ;
} ;
- } ;
- /* If not already collected, collect the child. */
- if (vf->child != NULL)
- {
- ret = uty_pipe_collect_child(vf, final) ;
+ ret = uty_pipe_shovel(vf, final) ;
- if ((ret != CMD_SUCCESS) && !final)
+ if ((ret == CMD_WAITING) && !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)
+ if (vf->pr_state != vf_closed)
{
vf->pr_vfd = vio_vfd_close(vf->pr_vfd) ;
vf->pr_state = vf_closed ;
} ;
- return ret ;
-} ;
+ /* Phase 2: if stderr return is still open, try to empty it -- subject to
+ * "final". Sets timeout and will now block.
+ *
+ * If not blocking but "final", turn off read ready and any timeout,
+ * and suck one last time.
+ *
+ * When return is all done, close it (which the child may see) and
+ * mark it vf_closed.
+ */
+ if (vf->ps_state == vf_open)
+ {
+ cmd_return_code_t ret ;
+ bool set_timeout ;
-/*------------------------------------------------------------------------------
- * 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 ;
+ set_timeout = (vf->ps_timeout == 0) ;
+ vf->ps_timeout = pipe_timeout ;
- assert(vf->child != NULL) ;
+ if (!vf->blocking)
+ {
+ if (!final)
+ {
+ if (set_timeout)
+ vio_vfd_set_read(vf->ps_vfd, on, vf->pr_timeout) ;
- /* Collect -- blocking or not blocking */
- if (!vf->child->collected && !vf->child->overdue)
+ return CMD_WAITING ; /* in hands of pselect() */
+ } ;
+
+ vio_vfd_set_read(vf->pr_vfd, off, 0) ;
+ } ;
+
+ ret = uty_pipe_stderr_suck(vf, final) ;
+
+ if ((ret == CMD_WAITING) && !final)
+ return ret ;
+ } ;
+
+ if (vf->ps_state != vf_closed)
{
- /* If we are !blocking and !final, leave the child collection up to
- * the SIGCHLD system.
+ vf->ps_vfd = vio_vfd_close(vf->ps_vfd) ;
+ vf->ps_state = vf_closed ;
+ } ;
+
+ /* Phase 3: if not already collected, collect the 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.
+ *
+ * Note that may write diagnostic message to slave, so in last phase we
+ * push the slave output to clear that out before releasing the slave.
+ */
+ if (vf->child != NULL)
+ {
+ /* Collect -- blocking or not blocking */
+ if (!vf->child->collected && !vf->child->overdue)
+ {
+ /* If we are blocking or "final" or vio->cancel, try to collect the
+ * child here and now -- if not final or vio->cancel may block here.
+ *
+ * For non-blocking and not "final", leave the child collection up to
+ * the SIGCHLD system.
+ */
+ if (vf->blocking || final || vf->vio->cancel)
+ {
+ uty_child_collect(vf->child, child_timeout,
+ final || vf->vio->cancel) ;
+ }
+ else
+ {
+ uty_child_awaited(vf->child, child_timeout) ;
+ return CMD_WAITING ;
+ } ;
+ } ;
+
+ /* If child is overdue, or did not terminate cleanly, write message to
+ * the pipe stderr return.
*/
- if (!vf->blocking && !final)
+ if (!vf->child->collected)
{
- uty_child_awaited(vf->child, 6) ;
- return CMD_WAITING ;
+ vio_fifo_printf(uty_pipe_ps_buf(vf),
+ "%% child process still running\n") ;
+ }
+ else if (WIFEXITED(vf->child->report))
+ {
+ int status = WEXITSTATUS(vf->child->report) ;
+ if (status != 0)
+ vio_fifo_printf(uty_pipe_ps_buf(vf),
+ "%% 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(uty_pipe_ps_buf(vf),
+ "%% child process terminated by signal = %d\n", signal) ;
+ }
+ else
+ {
+ vio_fifo_printf(uty_pipe_ps_buf(vf),
+ "%% child process ended in unknown state = %d\n",
+ vf->child->report) ;
} ;
- /* If we are blocking or final, try to collect the child here and
- * now -- if not final may block here.
+ /* Can now dismiss the child -- if not collected, is left on the register.
*/
- uty_child_collect(vf->child, 6, final) ;
+ uty_child_dismiss(vf->child, false) ; /* not curtains */
} ;
- /* If child is overdue, or did not terminate cleanly, write message to
- * slave... which we then have to push...
+ /* Phase 4: if there is anything in the pipe stderr return buffer, finish
+ * it, and transfer to the vio->ps_buf (unless vio->cancel).
*/
- slave = vf->pr_slave ;
- assert((slave != NULL) && (vf == slave->pr_master)) ;
-
- if (!vf->child->collected)
+ if (vf->ps_buf != NULL)
{
- 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) ;
- } ;
+ vio_fifo_trim(vf->ps_buf, true) ; /* trim trailing whitespace
+ * and '\n' terminate */
+ if (!vio_fifo_empty(vf->ps_buf) && !vio->cancel)
+ {
+ const char* what ;
+
+ if (vf->vin_type == VIN_PIPE)
+ what = "<|" ;
+ else if (vf->vout_type == VOUT_PIPE)
+ what = ">|" ;
+ else
+ what = "|" ;
- /* Can now dismiss the child. */
- vf->child = uty_child_dismiss(vf->child, false) ; /* not final */
+ if (vio->ps_buf == NULL)
+ vio->ps_buf = vio_fifo_new(1024) ;
+
+ vio_fifo_printf(vio->ps_buf, "%%--- in '%s %s':\n", what, vf->name) ;
+ vio_fifo_copy(vio->ps_buf, vf->ps_buf) ;
+ } ;
+
+ vf->ps_buf = vio_fifo_free(vf->ps_buf) ;
+ } ;
return CMD_SUCCESS ;
} ;
/*------------------------------------------------------------------------------
- * Push output to slave a final time -- to shift any final message about state
- * of child on closing.
+ * Stop pipe return and/or pipe stderr return.
*
- * Then release slave.
+ * Then if either vin and/or vout is open, set to vf_end to terminate I/O.
+ *
+ * There is no point continuing with anything once either return is broken.
*/
-static cmd_return_code_t
-uty_pipe_release_slave(vio_vf vf, bool final)
+extern void
+uty_pipe_return_stop(vio_vf vf)
{
- cmd_return_code_t ret ;
- vio_vf slave ;
+ if (vf->pr_state == vf_open)
+ vf->pr_state = vf_end ;
- ret = CMD_SUCCESS ;
+ if (vf->ps_state == vf_open)
+ vf->ps_state = vf_end ;
- slave = vf->pr_slave ;
- if (slave != NULL)
- {
- assert(vf == slave->pr_master) ;
+ if (vf->vin_state == vf_open)
+ vf->vin_state = vf_end ;
- ret = uty_cmd_out_push(slave, final) ; /* may block etc. */
+ if (vf->vout_state == vf_open)
+ vf->vout_state = vf_end ;
+} ;
- if ((ret != CMD_SUCCESS) && !final)
- return ret ;
+/*------------------------------------------------------------------------------
+ * Cancel any further input from the pipe return.
+ *
+ * Note that does not touch anything buffered ready to be output in the
+ * slave -- that must be dealt with separately.
+ */
+extern void
+uty_pipe_return_cancel(vio_vf vf)
+{
+ qassert( (vf->vin_type == VIN_PIPE) || (vf->vout_type == VOUT_PIPE)
+ || (vf->vout_type == VOUT_SH_CMD) ) ;
+ qassert(vf->pr_state == vf_open) ;
- slave->pr_master = NULL ; /* release slave */
- vf->pr_slave = NULL ;
- } ;
+ vf->pr_state = vf_end ;
+} ;
- return ret ;
+/*------------------------------------------------------------------------------
+ * Get pipe stderr return buffer for the vf -- make one if none yet exists.
+ */
+static vio_fifo
+uty_pipe_ps_buf(vio_vf vf)
+{
+ if (vf->ps_buf == NULL)
+ vf->ps_buf = vio_fifo_new(1024) ;
+
+ return vf->ps_buf ;
} ;
/*==============================================================================
diff --git a/lib/vty_io_file.h b/lib/vty_io_file.h
index 5bb5a607..701be801 100644
--- a/lib/vty_io_file.h
+++ b/lib/vty_io_file.h
@@ -45,26 +45,26 @@ 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,
cmd_context context) ;
extern cmd_return_code_t uty_file_write_open(vty_io vio, qstring name,
- bool append, cmd_context context) ;
+ bool append, cmd_context context, bool after) ;
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 cmd_return_code_t uty_file_out_push(vio_vf vf, bool final, bool all) ;
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) ;
+extern cmd_return_code_t uty_file_write_close(vio_vf vf, bool final) ;
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) ;
+ bool shell_cmd, bool after) ;
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) ;
+extern cmd_return_code_t uty_pipe_write_close(vio_vf vf, bool final) ;
+extern void uty_pipe_return_stop(vio_vf vf) ;
+extern void uty_pipe_return_cancel(vio_vf vf) ;
#endif
diff --git a/lib/vty_io_term.c b/lib/vty_io_term.c
index 857e457a..61f35761 100644
--- a/lib/vty_io_term.c
+++ b/lib/vty_io_term.c
@@ -54,16 +54,9 @@
* is closed either on command, or on timeout, or when the daemon is reset
* or terminated.
*
- *
- *
- *
+ * All VIN_TERM and VOUT_TERM I/O is non-blocking.
*/
-
-
-
-
-
/*==============================================================================
* If possible, will use getaddrinfo() to find all the things to listen on.
*/
@@ -77,20 +70,26 @@
* Opening and closing VTY_TERMINAL type
*/
-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 timer,
void* action_info) ;
+
+typedef enum { /* see uty_term_write() */
+ utw_null = 0,
+
+ utw_error,
+
+ utw_done,
+
+ utw_blocked,
+ utw_paused,
+ utw_more_enter,
+
+} utw_ret_t ;
+
static utw_ret_t uty_term_write(vio_vf vf) ;
static void uty_term_will_echo(vty_cli cli) ;
@@ -162,11 +161,11 @@ uty_term_open(int sock_fd, union sockunion *su)
0) ; /* no ibuf required */
uty_vout_push(vio, vf, VOUT_TERM, uty_term_write_ready,
uty_term_write_timeout,
- 4096) ; /* obuf is required */
-
- uty_vout_sync_depth(vio) ; /* vin & vout are at same level */
+ 4096, /* obuf is required */
+ true) ; /* after buddy vin */
- vf->read_timeout = host.vty_timeout_val ; /* current EXEC timeout */
+ vf->read_timeout = host.vty_timeout_val ; /* current EXEC timeout */
+ vf->write_timeout = 30 ; /* something reasonable */
/* Set up the CLI object & initialise */
vf->cli = uty_cli_new(vf) ;
@@ -194,25 +193,33 @@ uty_term_open(int sock_fd, union sockunion *su)
vty_out(vty, "%% Cannot continue because no password is set\n") ;
/* Enter the command loop. */
- uty_cmd_loop_enter(vio) ;
+ uty_cmd_queue_loop_enter(vio) ;
} ;
/*------------------------------------------------------------------------------
- * Command line fetch from a VTY_TERMINAL.
+ * Command line fetch from a VTY_TERMINAL -- in vf_open state.
*
* 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 -- ??????
+ * Returns: CMD_SUCCESS -- have another command line ready to go
+ * or: CMD_WAITING -- would not wait for input
*
* This can be called in any thread.
*
- * NB: this does not signal CMD_EOF TODO ????
+ * Note that does not signal CMD_EOF because that is handled for the
+ * VTY_TERMINAL by the cmd_do_eof "special command".
+ *
+ * Note that this does no actual I/O, all that is done in the pselect() process,
+ * while a command line is collected in the CLI. So does not here return
+ * CMD_IO_ERROR -- any errors are dealt with by signalling the command loop.
*/
extern cmd_return_code_t
uty_term_fetch_command_line(vio_vf vf, cmd_action action, cmd_context context)
{
+ VTY_ASSERT_LOCKED() ;
+
+ qassert(vf->vin_state == vf_open) ;
+
return uty_cli_want_command(vf->cli, action, context) ;
} ;
@@ -243,14 +250,18 @@ uty_term_show_error_context(vio_vf vf, vio_fifo ebuf, uint depth)
} ;
/*------------------------------------------------------------------------------
- * Push output to the terminal.
+ * Push output to the terminal -- always not vf->blocking !
*
- * Returns: CMD_SUCCESS -- all buffers are empty
- * CMD_WAITING -- all buffers are not empty
- * CMD_IO_ERROR -- failed -- final or not.
+ * Returns: CMD_SUCCESS -- done everything possible
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * "final" => would have waited but did not.
+ * CMD_IO_ERROR -- error or time-out (may be "final")
+ *
+ * This can be called in any thread.
*
- * This can be called in any thread. If "final" will not turn on any
- * read/write ready stuff.
+ * Note that CMD_WAITING requires no further action from the caller, the
+ * background pselect process will complete the output and may signal the
+ * result via uty_cmd_signal().
*/
extern cmd_return_code_t
uty_term_out_push(vio_vf vf, bool final)
@@ -258,8 +269,17 @@ 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.
+ qassert(vf->vout_state == vf_open) ;
+ qassert(!vf->blocking) ;
+
+ /* If squelching, dump anything we have in the obuf.
+ */
+ if (vf->vio->cancel)
+ vio_fifo_clear(vf->obuf, false) ; /* keep end mark */
+
+ /* 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 (!cli->out_active && !vio_fifo_empty(vf->obuf))
{
@@ -271,34 +291,39 @@ uty_term_out_push(vio_vf vf, bool final)
/* Give the terminal writing a shove.
*
* If final, keep pushing while succeeds in writing without blocking.
+ *
+ * Note that is only called "final" when closing the vout, by which time
+ * the "--more--" handling has been turned off and any output has been
+ * released.
*/
- 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)
+ done = uty_term_write(vf) ;
+ else
{
- if (done == utw_error)
- return CMD_IO_ERROR ; /* TODO */
-
- if ((done & utw_blocked) != 0)
+ do
{
- confirm((utw_paused & utw_blocked) != 0) ;
-
- uty_term_set_readiness(vf, write_ready) ;
- return CMD_WAITING ;
- } ;
+ vio_lc_counter_reset(cli->olc) ;
+ done = uty_term_write(vf) ;
+ } while (done == utw_paused) ;
} ;
- return CMD_SUCCESS ;
+ /* Deal with the result
+ *
+ * If required and if not final make sure that write ready is set, so
+ * that the pselect() process can pursue the issue.
+ *
+ * Return code depends on utw_xxx
+ */
+ if (done == utw_done)
+ return CMD_SUCCESS ; /* don't set write ready */
+
+ if (done == utw_error)
+ return CMD_IO_ERROR ; /* don't set write ready */
+
+ if (!final)
+ uty_term_set_readiness(vf, write_ready) ;
+
+ return CMD_WAITING ; /* waiting for write ready */
} ;
/*------------------------------------------------------------------------------
@@ -319,14 +344,14 @@ uty_term_read_close(vio_vf vf, bool final)
{
vty_io vio ;
+ qassert(vf->vin_state == vf_end) ;
+
/* 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) ;
+ qassert((vio->vin == vio->vin_base) && (vio->vin == vf)) ;
- /*
- */
- uty_set_monitor(vio, 0) ;
+ /* Kill monitor state */
+ uty_set_monitor(vio, off) ;
/* Close the CLI as far as possible, leaving output side intact.
*
@@ -335,11 +360,6 @@ uty_term_read_close(vio_vf vf, bool final)
*/
uty_cli_close(vf->cli, false) ;
- /* Log closing of VTY_TERMINAL
- */
- assert(vio->vty->type == VTY_TERMINAL) ;
- zlog (NULL, LOG_INFO, "Vty connection (fd %d) close", vio_vfd_fd(vf->vfd)) ;
-
return CMD_SUCCESS ;
} ;
@@ -355,7 +375,8 @@ 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) ;
+
+ vf->cli->mon_active = false ; /* stamp on any monitor output */
uty_cli_out(vf->cli, "%% %s%s", reason, uty_cli_newline) ;
} ;
@@ -364,56 +385,77 @@ uty_term_close_reason(vio_vf vf, const char* reason)
* Close the writing side of VTY_TERMINAL.
*
* Assumes that the read side has been closed already, and so this is the last
- * thing to be closed.
+ * thing to be closed. Any monitor state was turned off earlier when the read
+ * side was closed.
*
* Kicks the output side:
*
- * if final, will push as much as possible until would block.
+ * if final, will push as much as possible until all gone, would block or
+ * gets error. In any event, closes the cli, final.
*
* 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
+ * Returns: CMD_SUCCESS => all written,
+ * or cannot write anything (more),
+ * or final and wrote what could
+ * CMD_WAITING -- not "final" => waiting for output to complete
+ * "final" => would have waited but did not.
+ *
+ * NB: if an error occurs while writing, that will have been logged, but there
+ * is nothing more to be done about it here -- so does not return
+ * CMD_IO_ERROR.
*/
extern cmd_return_code_t
-uty_term_write_close(vio_vf vf, bool final, bool base)
+uty_term_write_close(vio_vf vf, bool final)
{
cmd_return_code_t ret ;
vty_io vio ;
+ VTY_ASSERT_LOCKED() ;
+
/* Get the vio and ensure that we are all straight
*
* Can only be the vout_base and must also be the vin_base, and the vin_base
* must now be closed.
*/
vio = vf->vio ;
- assert((vio->vout == vio->vout_base) && (vio->vout == vf)) ;
- assert((vio->vin == vio->vin_base) && (vio->vin->vin_state == vf_closed)) ;
+ qassert((vio->vout == vio->vout_base) && (vio->vout == vf)) ;
+ qassert((vio->vin == vio->vin_base) && (vio->vin->vin_state == vf_closed)) ;
+
+ ret = CMD_SUCCESS ;
+
+ if (vf->vout_state == vf_open)
+ {
+ ret = uty_term_out_push(vf, final) ;
- ret = uty_term_out_push(vf, final) ;
+ if (ret != CMD_WAITING)
+ ret = CMD_SUCCESS ;
+ } ;
if (final)
- vf->cli = uty_cli_close(vf->cli, final) ;
+ {
+ vf->cli = uty_cli_close(vf->cli, final) ;
+
+ qassert(vio->vty->type == VTY_TERMINAL) ;
+ zlog (NULL, LOG_INFO, "Vty connection (fd %d) close",
+ vio_vfd_fd(vf->vfd)) ;
+ } ;
return ret ;
} ;
/*==============================================================================
- * Action routines for VIN_TERM/VOUT_TERM type vin/vout objects
- */
-
-/*==============================================================================
* Readiness and the VIN_TERM type vin.
*
* For TERM stuff the driving force is write ready. This is used to prompt the
* VOUT_TERM when there is outstanding output (obviously), but also if there
* is buffered input in the keystroke stream.
*
- * The VIN_TERM uses read ready only when it doesn't set write ready. Does
- * not set both at once.
+ * The VIN_TERM is read ready permanently, until eof is met. Note that the
+ * read timeout is reset each time uty_term_set_readiness is called. When
+ * eof is met, the VIN_TERM is read closed, which prevents any further setting
+ * of read ready and its timeout.
*/
static void uty_term_ready(vio_vf vf) ;
@@ -421,17 +463,17 @@ static void uty_term_ready(vio_vf vf) ;
/*------------------------------------------------------------------------------
* Set read/write readiness -- for VIN_TERM/VOUT_TERM
*
- * Note that sets only one of read or write, and sets write for preference.
+ * Is permanently read-ready (until eof or no longer vin_state == vf_open).
*/
extern void
uty_term_set_readiness(vio_vf vf, vty_readiness_t ready)
{
VTY_ASSERT_LOCKED() ;
- if ((ready & write_ready) != 0)
+ if ((ready & write_ready) != 0)
uty_vf_set_write(vf, on) ;
- else if ((ready & read_ready) != 0)
- uty_vf_set_read(vf, on) ;
+
+ uty_vf_set_read(vf, on) ;
} ;
/*------------------------------------------------------------------------------
@@ -442,9 +484,11 @@ uty_term_read_ready(vio_vfd vfd, void* action_info)
{
vio_vf vf = action_info ;
- assert(vfd == vf->vfd) ;
+ qassert(vfd == vf->vfd) ;
vf->cli->paused = false ; /* read ready clears paused */
+
+ uty_term_read(vf) ;
uty_term_ready(vf) ;
} ;
@@ -456,7 +500,7 @@ uty_term_write_ready(vio_vfd vfd, void* action_info)
{
vio_vf vf = action_info ;
- assert(vfd == vf->vfd) ;
+ qassert(vfd == vf->vfd) ;
uty_term_ready(vf) ;
} ;
@@ -501,25 +545,25 @@ static void
uty_term_ready(vio_vf vf)
{
vty_readiness_t ready ;
- utw_ret_t done ;
- bool signal ;
+ utw_ret_t done, done_before ;
VTY_ASSERT_LOCKED() ;
- /* Start by trying to write away any outstanding stuff, and then another
- * tranche of any outstanding output.
+ /* Will attempt to write away any pending stuff, then for each call of
+ * uty_term_ready, put out another tranche of output (unless in '--more--'
+ * state).
*/
- ready = not_ready ;
-
if (!vf->cli->more_enabled)
vio_lc_counter_reset(vf->cli->olc) ; /* do one tranche */
- done = uty_term_write(vf) ;
- signal = ((done == utw_done) || (done == utw_stopped)) ;
-
- while (done != utw_error)
+ /* Now loop kicking the CLI and the output, until stops changing.
+ *
+ * This is because the CLI may generate more to write, and writing stuff
+ * away may release the CLI.
+ */
+ done = utw_null ;
+ do
{
- utw_ret_t done_before ;
done_before = done ;
/* Kick the CLI, which may advance either because there is more input,
@@ -532,59 +576,45 @@ uty_term_ready(vio_vf vf)
/* Now try to write away any new output which may have been generated
* by the CLI.
+ *
+ * Note that when enters "--more--" will return utw_more_enter, once,
+ * which causes a loop back to uty_cli(), which will start the process.
+ * When comes through here a second time, will return utw_done, once
+ * any prompt etc has been output.
*/
done = uty_term_write(vf) ;
- if (done == done_before)
- break ; /* quit if no change in response */
-
- if ((done == utw_done) || (done == utw_stopped))
- signal = true ;
- } ;
-
- if (done == utw_error)
- ; /* TODO !! */
+ if (done == utw_error)
+ return ; /* quit if in error */
- if ((done & utw_blocked) != 0)
- {
- confirm((utw_paused & utw_blocked) != 0) ;
- ready |= write_ready ;
- } ;
+ } while (done != done_before) ;
- 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) ;
- } ;
+ if (done != utw_done) /* isn't utw_error, either */
+ ready |= write_ready ;
uty_term_set_readiness(vf, ready) ;
- /* Signal the command loop if out_active and the buffers empty out.
+ /* Signal the command loop if not waiting for write any more.
*/
- if (signal)
+ if ((ready & write_ready) == 0)
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.
+ * Discard anything in the keystroke stream and set it "eof, timed-out". This
+ * will be picked up by the CLI and a cmd_do_timed-out will float out.
*/
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) ;
+ qassert(timer == vf->vfd->read_timer) ;
VTY_ASSERT_LOCKED() ;
- vf->vin_state = vf_timed_out ;
keystroke_stream_set_eof(vf->cli->key_stream, true) ; /* timed out */
vf->cli->paused = false ;
@@ -597,24 +627,20 @@ uty_term_read_timeout(vio_timer timer, void* action_info)
/*------------------------------------------------------------------------------
* 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.
+ * Signal write timeout error.
*/
static vty_timer_time
uty_term_write_timeout(vio_timer timer, void* action_info)
{
vio_vf vf = action_info ;
- assert(timer == vf->vfd->read_timer) ;
+ qassert(timer == vf->vfd->write_timer) ;
VTY_ASSERT_LOCKED() ;
vf->cli->paused = false ;
-//uty_close(vio, true, qs_set(NULL, "Timed out")) ; TODO
+ uty_vf_error(vf, verr_to_vout, 0) ;
return 0 ;
} ;
@@ -650,38 +676,69 @@ vty_term_pause_timeout(qtimer qtr, void* timer_info, qtime_mono_t when)
/*------------------------------------------------------------------------------
* Read a lump of bytes and shovel into the keystroke stream
*
- * This function is called from the vty_cli to top up the keystroke buffer,
- * or in the stealing of a keystroke to end "--more--" state.
- *
* NB: need not be in the term_ready path. Indeed, when the VTY_TERMINAL is
* initialised, this is called to suck up any telnet preamble.
*
- * Steal keystroke if required -- see keystroke_input()
+ * NB: the terminal is permanently read-ready, so will keep calling this
+ * until all input is hoovered up. For real terminals it is assumed that
+ * reading a lump of bytes this small will immediately empty the input
+ * buffer.
+ *
+ * When reaches EOF on the input eof is set in the keystroke stream, and the
+ * vfd is read closed. Read closing the vfd turns off any timeout and prevents
+ * any further setting of read_ready (to avoid permanent read_ready !). Does
+ * NOT set vf->vin_state to vf_end, because that is not true until the
+ * keystroke stream is empty. Once eof is set in the keystroke stream, this
+ * code will not attempt any further input from the vfd.
*
* Returns: 0 => nothing available
* > 0 => read at least one byte
- * -1 => EOF (or not open, or failed, or timed out, ...)
+ * == -1 => I/O error
+ * == -2 => hit EOF (without reading anything else)
*/
extern int
-uty_term_read(vio_vf vf, keystroke steal)
+uty_term_read(vio_vf vf)
{
+ keystroke_stream stream ;
unsigned char buf[500] ;
int get ;
+ stream = vf->cli->key_stream ;
+
+ if (keystroke_stream_met_eof(stream))
+ return -2 ; /* already seen EOF */
+
if (vf->vin_state != vf_open)
- return -1 ; /* at EOF if not open */
+ {
+ /* If is not vf_open, but also not seen EOF on the keystroke stream,
+ * then now is a good moment to empty out the keystroke stream and
+ * force it to EOF.
+ */
+ keystroke_stream_set_eof(vf->cli->key_stream, false) ;
+ return -2 ;
+ } ;
+ /* OK: read from input and pass result to keystroke stream.
+ */
get = read_nb(vio_vfd_fd(vf->vfd), buf, sizeof(buf)) ;
- if (get >= 0)
- keystroke_input(vf->cli->key_stream, buf, get, steal) ;
- else if (get < 0)
+ /* -1 <=> error, -2 <=> EOF */
+ if (get != 0)
{
if (get == -1)
- uty_vf_error(vf, "read", errno) ;
+ {
+ /* Error: signal to command loop and force key_stream empty */
+ uty_vf_error(vf, verr_io_vin, errno) ;
+ keystroke_stream_set_eof(vf->cli->key_stream, false) ;
+ /* not timed-out */
+ }
+ else
+ {
+ /* Not error. get < 0 => EOF and that is set in key_stream. */
+ keystroke_input(vf->cli->key_stream, buf, get) ;
+ } ;
- keystroke_input(vf->cli->key_stream, NULL, 0, steal) ;
- /* Tell keystroke stream that EOF met */
- get = -1 ;
+ if (get < 0)
+ vio_vfd_read_close(vf->vfd) ;
} ;
return get ;
@@ -719,59 +776,40 @@ uty_term_mon_write(vio_vf vf)
uty_cli_pre_monitor(vf->cli) ; /* make sure in a fit state */
- done = uty_term_write(vf) ; /* TODO -- errors !! */
-
- if ((done & utw_blocked) != 0)
- {
- confirm((utw_paused & utw_blocked) != 0) ;
-
- uty_term_set_readiness(vf, write_ready) ;
- } ;
+ done = uty_term_write(vf) ; /* this calls uty_vf_error() if
+ * there are any errors */
+ if ((done != utw_done) && (done != utw_error))
+ uty_term_set_readiness(vf, write_ready) ;
} ;
/*------------------------------------------------------------------------------
* Write as much as possible of what there is.
*
- * Move to more_wait if required. TODO
+ * Move to more_wait if required.
*
* If is cli->flush, then when all buffers are emptied out, clears itself and
* the out_active flag.
*
* Returns:
*
- * utw_error -- I/O error -- see errno (utw_error = -1)
- *
- * utw_blocked -- write blocked -- some write operation would block
+ * utw_error -- I/O error either now or sometime earlier.
*
- * utw_paused -- have written as much as line control allows in one go.
+ * utw_done -- have written everything can find
*
- * 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.
+ * utw_blocked -- write blocked -- some write operation would block
*
- * utw_stopped -- have done as much as can do -- no more output is possible
- * until some external event changes things.
+ * Need to set write ready & wait for it.
*
- * This implies that any pending output has completed, in
- * particular the line control iovec and the cli->cbuf are
- * both empty.
+ * utw_paused -- have written as much as line control allows in one go.
*
- * This state includes:
+ * Note that is *not* in "--more--" state, this is all to
+ * do with limiting work on each visit.
*
- * * !out_active -- if there is something in the vf->obuf,
- * we are not yet ready to output it.
+ * Need to set write ready & wait for it.
*
- * * more_wait -- waiting for user
+ * utw_more_enter -- has just entered "--more--" state
*
- * 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.
+ * Need either to set write ready, or call the CLI.
*/
static utw_ret_t
uty_term_write(vio_vf vf)
@@ -783,33 +821,44 @@ uty_term_write(vio_vf vf)
VTY_ASSERT_LOCKED() ;
- /* If the vout is neither vf_open, not vf_closing, discard all buffered
+ /* If the vout is neither vf_open nor vf_closing, discard all buffered
* output, and return all done.
*/
- if ((vf->vout_state != vf_open) && (vf->vout_state != vf_closing))
+ if (vf->vout_state != vf_open)
{
+ qassert(vf->vout_state == vf_end) ;
+
vio_fifo_clear(vf->obuf, false) ;
vio_fifo_clear(cli->cbuf, false) ;
vio_lc_clear(cli->olc) ;
cli->out_active = false ;
- cli->flush = false ;
cli->more_wait = false ;
cli->more_enter = false ;
- return utw_done ;
+ return utw_error ;
} ;
/* 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 (vio_lc_pending(cli->olc))
+ {
+ ret = uty_term_write_lc(cli->olc, vf, vf->obuf) ;
+ if (ret != utw_done)
+ return ret ; /* utw_blocked or utw_error */
+ } ;
/* 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 (did > 0)
+ return utw_blocked ;
+
+ if (did < 0)
+ {
+ uty_vf_error(vf, verr_io_vout, errno) ;
+ return utw_error ;
+ } ;
/* Next: if there is monitor output to deal with, deal with it.
*
@@ -827,20 +876,23 @@ uty_term_write(vio_vf vf)
LOG_UNLOCK() ;
- if (did != 0)
- return (did < 0) ? utw_error : utw_blocked ;
+ if (did > 0)
+ return utw_blocked ;
+
+ if (did < 0)
+ {
+ uty_vf_error(vf, verr_io_vout, errno) ;
+ return utw_error ;
+ } ;
uty_cli_post_monitor(vf->cli) ;
} ;
- /* If not out_active, or in more_wait, then we are stopped, waiting for some
+ /* If not out_active, or in more_wait, then we are done, 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 ((!cli->out_active) || (cli->more_wait))
+ return utw_done ; /* can do no more */
/* 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
@@ -870,7 +922,7 @@ uty_term_write(vio_vf vf)
break ;
} ;
- if ((have == 0) && (cli->flush))
+ if ((have == 0) && !cli->in_progress)
vio_lc_flush(cli->olc) ;
ret = uty_term_write_lc(cli->olc, vf, vf->obuf) ;
@@ -890,12 +942,12 @@ uty_term_write(vio_vf vf)
*/
if ((have != 0) || vio_lc_have_complete_line_in_hand(cli->olc))
{
- assert(vio_lc_counter_is_exhausted(cli->olc)) ;
+ qassert(vio_lc_counter_is_exhausted(cli->olc)) ;
if (cli->more_enabled)
{
uty_cli_enter_more_wait(cli) ;
- return utw_stopped ;
+ return utw_more_enter ;
}
else
return utw_paused ; /* artificial block */
@@ -910,22 +962,21 @@ uty_term_write(vio_vf vf)
* ...if not cli->flush, we are stopped, waiting for something else to
* happen.
*/
- assert(!cli->more_wait && !cli->more_enter) ;
+ qassert(!cli->more_wait && !cli->more_enter) ;
- if (!cli->flush)
- return utw_stopped ;
+ if (cli->in_progress)
+ return utw_done ; /* can do no more */
- /* Even more exciting: is cli->flush !
+ /* Even more exciting: is !cli->in_progress !
*
* 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)) ;
+ qassert(vio_fifo_empty(vf->obuf) && vio_lc_is_empty(cli->olc)) ;
cli->out_active = false ;
- cli->flush = false ;
- return utw_done ;
+ return utw_done ; /* absolutely all done */
} ;
/*------------------------------------------------------------------------------
@@ -937,7 +988,7 @@ uty_term_write(vio_vf vf)
*
* Returns: utw_blocked => blocked
* utw_done => all gone (may still have stuff "in hand")
- * utw_error => failed
+ * utw_error => failed -- error posted to uty_vf_error()
*/
static utw_ret_t
uty_term_write_lc(vio_line_control lc, vio_vf vf, vio_fifo vff)
@@ -946,7 +997,9 @@ uty_term_write_lc(vio_line_control lc, vio_vf vf, vio_fifo vff)
did = vio_lc_write_nb(vio_vfd_fd(vf->vfd), lc) ;
- if (did > 0)
+ if (did < 0)
+ uty_vf_error(vf, verr_io_vout, errno) ;
+ else if (did > 0)
return utw_blocked ;
vio_fifo_clear_hold_mark(vff) ; /* finished with FIFO contents */
@@ -1032,7 +1085,7 @@ uty_term_listen_addrinfo(const char *addr, unsigned short port)
if ((ainfo->ai_family != AF_INET) && (ainfo->ai_family != AF_INET6))
continue;
- assert(ainfo->ai_family == ainfo->ai_addr->sa_family) ;
+ qassert(ainfo->ai_family == ainfo->ai_addr->sa_family) ;
ret = uty_term_listen_open(ainfo->ai_family, ainfo->ai_socktype,
ainfo->ai_protocol, ainfo->ai_addr, port) ;
@@ -1498,8 +1551,8 @@ uty_telnet_command(vio_vf vf, keystroke stroke, bool callback)
p = stroke->buf ;
left = stroke->len ;
- passert(left >= 1) ; /* must be if not broken ! */
- passert(stroke->value == *p) ; /* or something is wrong */
+ qassert(left >= 1) ; /* must be if not broken ! */
+ qassert(stroke->value == *p) ; /* or something is wrong */
++p ; /* step past X of IAC X */
--left ;
@@ -1508,7 +1561,7 @@ uty_telnet_command(vio_vf vf, keystroke stroke, bool callback)
switch (stroke->value)
{
case tn_SB:
- passert(left > 0) ; /* or parser failed */
+ qassert(left > 0) ; /* or parser failed */
o = *p++ ; /* the option byte */
--left ;
diff --git a/lib/vty_io_term.h b/lib/vty_io_term.h
index d8999b98..78d37b18 100644
--- a/lib/vty_io_term.h
+++ b/lib/vty_io_term.h
@@ -61,9 +61,9 @@ 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 cmd_return_code_t uty_term_write_close(vio_vf vf, bool final);
-extern int uty_term_read(vio_vf vf, keystroke steal) ;
+extern int uty_term_read(vio_vf vf) ;
extern void uty_term_set_readiness(vio_vf vf, vty_readiness_t ready) ;
extern qtimer_action vty_term_pause_timeout ;
diff --git a/lib/vty_local.h b/lib/vty_local.h
index ff389baf..ed2c5ce4 100644
--- a/lib/vty_local.h
+++ b/lib/vty_local.h
@@ -28,8 +28,6 @@
#include "vty_common.h" /* First and foremost */
#include "command_common.h"
-//#include "log_local.h" /* NB: */
-
#include "vargs.h"
#include "qpthreads.h"
@@ -67,7 +65,6 @@
*/
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 ;
@@ -214,7 +211,7 @@ VTY_ASSERT_CLI_THREAD_LOCKED(void)
} ;
/*==============================================================================
- * Functions in vty.c -- used in any of the family
+ * Functions in vty.c -- used in any of the vty and command family
*/
extern void vty_hello (vty vty);
@@ -224,11 +221,11 @@ 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_time_print (struct vty *, int);
+extern void vty_time_print (struct vty *, int) ;
-extern void vty_set_lines(vty vty, int lines);
+extern void vty_set_lines(vty vty, int lines) ;
-extern void vty_open_config_write(vty vty, int fd) ;
-extern cmd_return_code_t vty_close_config_write(struct vty* vty, bool final) ;
+extern void vty_config_write_open(vty vty, int fd) ;
+extern cmd_return_code_t vty_config_write_close(struct vty* vty) ;
#endif /* _ZEBRA_VTY_LOCAL_H */
diff --git a/lib/vty_log.c b/lib/vty_log.c
index 080f86a3..1f485e89 100644
--- a/lib/vty_log.c
+++ b/lib/vty_log.c
@@ -35,6 +35,8 @@
* This supports the "vty monitor" facility -- which reflects logging
* information to one or more VTY_TERMINAL vty.
*
+ * NB: this *only* only to the base_vout of a VTY_TERMINAL.
+ *
* There are a number of issues:
*
* a) output of logging information should not be held up any longer than
@@ -46,14 +48,16 @@
* c) zlog() et al, hold the LOG_LOCK(), which is at a lower level than the
* VTY_LOCK().
*
+ * MUST NOT require the VTY_LOCK() in order to complete a zlog() operation,
+ * hence the buffering and other mechanisms.
+ *
* 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 !
- *
- *
- *
+ * e) must avoid logging I/O error log messages for given vty on that vty !
*
+ * The I/O error handling turns off log monitoring for the vty if the
+ * vin_base or the vout_base is the locus of the error.
*/
static vio_fifo monitor_buffer = NULL ;
static uint monitor_count = 0 ;
@@ -62,6 +66,7 @@ static bool mon_kicked = false ;
static mqueue_block mon_mqb = NULL ;
static void vty_mon_action(mqueue_block mqb, mqb_flag_t flag) ;
+static void uty_monitor_update(void) ;
/*------------------------------------------------------------------------------
* Initialise the vty monitor facility.
@@ -91,12 +96,12 @@ extern void
uty_set_monitor(vty_io vio, bool on)
{
int level ;
- int count ;
+ int delta ;
VTY_ASSERT_LOCKED() ;
level = 0 ;
- count = 0 ;
+ delta = 0 ;
LOG_LOCK() ;
@@ -106,7 +111,7 @@ uty_set_monitor(vty_io vio, bool on)
{
vio->monitor = true ;
- vio->maxlvl = INT_MAX ; /* pro tem TODO */
+ vio->maxlvl = uzlog_get_monitor_lvl(NULL) ;
vio->mon_kick = false ;
if (vio->mbuf == NULL)
@@ -114,29 +119,85 @@ uty_set_monitor(vty_io vio, bool on)
sdl_push(vio_monitor_list, vio, mon_list) ;
- count = +1 ;
+ delta = +1 ;
} ;
}
else if (!on && vio->monitor)
{
vio->monitor = false ;
- vio->maxlvl = INT_MAX ; /* pro tem TODO */
+ vio->maxlvl = ZLOG_DISABLED ;
vio->mon_kick = false ;
sdl_del(vio_monitor_list, vio, mon_list) ;
- count = -1 ;
+ delta = -1 ;
}
- if (count != 0)
- uzlog_add_monitor(NULL, count) ;
+ monitor_count += delta ;
- monitor_count += count ;
+ if (delta != 0)
+ uty_monitor_update() ; /* tell logging if number of monitors changes */
LOG_UNLOCK() ;
} ;
/*------------------------------------------------------------------------------
+ * If the current VTY is a log monitor, set a new level
+ */
+extern void
+vty_set_monitor_level(vty vty, int level)
+{
+ VTY_LOCK() ;
+
+ if (vty->vio->monitor)
+ {
+ LOG_LOCK() ;
+
+ vty->vio->maxlvl = level ;
+ uty_monitor_update() ;
+
+ LOG_UNLOCK() ;
+ } ;
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Establish the maximum level of all monitors and tell the logging levels.
+ *
+ * This is used when a monitor is enabled or disabled, and when a VTY's monitor
+ * level is changed.
+ */
+static void
+uty_monitor_update(void)
+{
+ int level ;
+ uint count ;
+ vty_io vio ;
+
+ VTY_ASSERT_LOCKED() ;
+ LOG_ASSERT_LOCKED() ;
+
+ vio = vio_monitor_list ;
+ count = 0 ;
+ level = ZLOG_DISABLED ;
+ while (vio != NULL)
+ {
+ ++count ;
+ qassert(vio->vout_base->vout_type == VOUT_TERM) ;
+
+ if (level <= vio->maxlvl)
+ level = vio->maxlvl ;
+
+ vio = sdl_next(vio, mon_list) ;
+ } ;
+
+ qassert(monitor_count == count) ;
+
+ uzlog_set_monitor(NULL, level) ;
+} ;
+
+/*------------------------------------------------------------------------------
* Put logging message to all suitable monitors.
*
* All we can do here is to shovel stuff into buffers and then kick the VTY
@@ -158,6 +219,8 @@ vty_log(int priority, const char* line, uint len)
kick = false ;
while (vio != NULL)
{
+ qassert(vio->vout_base->vout_type == VOUT_TERM) ;
+
if (priority <= vio->maxlvl)
{
vio_fifo_put_bytes(vio->mbuf, line, len) ;
@@ -209,7 +272,7 @@ vty_mon_action(mqueue_block mqb, mqb_flag_t flag)
vio = vio_monitor_list ;
while (vio != NULL)
{
- assert(vio->vout_base->vout_type == VOUT_TERM) ;
+ qassert(vio->vout_base->vout_type == VOUT_TERM) ;
if (vio->mon_kick)
{
@@ -244,6 +307,8 @@ vty_log_fixed (const char *buf, uint len)
vio = sdl_head(vio_monitor_list) ;
while (vio != NULL)
{
+ qassert(vio->vout_base->vout_type == VOUT_TERM) ;
+
write(vio_vfd_fd(vio->vout_base->vfd), buf, len) ;
write(vio_vfd_fd(vio->vout_base->vfd), "\r\n", 2) ;
diff --git a/lib/vty_log.h b/lib/vty_log.h
index 4a0614a6..55fff67f 100644
--- a/lib/vty_log.h
+++ b/lib/vty_log.h
@@ -34,6 +34,7 @@ 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_set_monitor_level(vty vty, int level) ;
extern void vty_log(int priority, const char* line, uint len) ;
extern void vty_log_fixed(const char* buf, uint len) ;
diff --git a/lib/vty_pipe.c b/lib/vty_pipe.c
index ad86e30c..b2c3fd64 100644
--- a/lib/vty_pipe.c
+++ b/lib/vty_pipe.c
@@ -234,7 +234,7 @@ uty_file_read_open(vty_io vio, qstring name, bool reflect)
iot = vfd_io_read | vfd_io_blocking ; /* TODO blocking */
/* Do the basic file open. */
- fd = uty_vfd_file_open(name_str, iot) ;
+ fd = uty_fd_file_open(name_str, iot) ;
if (fd < 0)
{
@@ -273,7 +273,7 @@ uty_file_write_open(vty_io vio, qstring name, bool append)
name_str = qs_make_string(name) ;
/* Do the basic file open. */
- fd = uty_vfd_file_open(name_str, iot) ;
+ fd = uty_fd_file_open(name_str, iot) ;
if (fd < 0)
{
diff --git a/lib/zassert.h b/lib/zassert.h
index 84e2c819..ee36a98e 100644
--- a/lib/zassert.h
+++ b/lib/zassert.h
@@ -51,7 +51,7 @@ extern void _zlog_abort_err (const char *mess, int err, const char *file,
#endif
/* Assert iff QDEBUG */
-#define qassert(EX) if (qdebug) zassert(EX)
+#define qassert(EX) zassert(qdebug && (EX))
/* Abort with message */
#define zabort(MS) _zlog_abort_mess(MS, __FILE__, __LINE__, __ASSERT_FUNCTION)
diff --git a/ospfd/ospf_spf.c b/ospfd/ospf_spf.c
index e41492a5..66f1df2c 100644
--- a/ospfd/ospf_spf.c
+++ b/ospfd/ospf_spf.c
@@ -52,7 +52,7 @@ static void ospf_vertex_free (void *);
* dynamically allocated at begin of ospf_spf_calculate
*/
static struct list vertex_list = { .del = ospf_vertex_free };
-
+
/* Heap related functions, for the managment of the candidates, to
* be used with pqueue. */
static int
@@ -90,7 +90,7 @@ update_stat (void *node , int position)
/* Set the status of the vertex, when its position changes. */
*(v->stat) = position;
}
-
+
static struct vertex_nexthop *
vertex_nexthop_new (void)
{
@@ -112,12 +112,12 @@ ospf_canonical_nexthops_free (struct vertex *root)
{
struct listnode *node, *nnode;
struct vertex *child;
-
+
for (ALL_LIST_ELEMENTS (root->children, node, nnode, child))
{
struct listnode *n2, *nn2;
struct vertex_parent *vp;
-
+
/* router vertices through an attached network each
* have a distinct (canonical / not inherited) nexthop
* which must be freed.
@@ -127,14 +127,14 @@ ospf_canonical_nexthops_free (struct vertex *root)
*/
if (child->type == OSPF_VERTEX_NETWORK)
ospf_canonical_nexthops_free (child);
-
+
/* Free child nexthops pointing back to this root vertex */
for (ALL_LIST_ELEMENTS (child->parents, n2, nn2, vp))
if (vp->parent == root && vp->nexthop)
vertex_nexthop_free (vp->nexthop);
}
-}
-
+}
+
/* TODO: Parent list should be excised, in favour of maintaining only
* vertex_nexthop, with refcounts.
*/
@@ -142,12 +142,12 @@ static struct vertex_parent *
vertex_parent_new (struct vertex *v, int backlink, struct vertex_nexthop *hop)
{
struct vertex_parent *new;
-
+
new = XMALLOC (MTYPE_OSPF_VERTEX_PARENT, sizeof (struct vertex_parent));
-
+
if (new == NULL)
return NULL;
-
+
new->parent = v;
new->backlink = backlink;
new->nexthop = hop;
@@ -159,7 +159,7 @@ vertex_parent_free (void *p)
{
XFREE (MTYPE_OSPF_VERTEX_PARENT, p);
}
-
+
static struct vertex *
ospf_vertex_new (struct ospf_lsa *lsa)
{
@@ -175,9 +175,9 @@ ospf_vertex_new (struct ospf_lsa *lsa)
new->children = list_new ();
new->parents = list_new ();
new->parents->del = vertex_parent_free;
-
+
listnode_add (&vertex_list, new);
-
+
if (IS_DEBUG_OSPF_EVENT)
zlog_debug ("%s: Created %s vertex %s", __func__,
new->type == OSPF_VERTEX_ROUTER ? "Router" : "Network",
@@ -189,28 +189,28 @@ static void
ospf_vertex_free (void *data)
{
struct vertex *v = data;
-
+
if (IS_DEBUG_OSPF_EVENT)
zlog_debug ("%s: Free %s vertex %s", __func__,
v->type == OSPF_VERTEX_ROUTER ? "Router" : "Network",
inet_ntoa (v->lsa->id));
-
+
/* There should be no parents potentially holding references to this vertex
* Children however may still be there, but presumably referenced by other
* vertices
*/
//assert (listcount (v->parents) == 0);
-
+
if (v->children)
list_delete (v->children);
v->children = NULL;
-
+
if (v->parents)
list_delete (v->parents);
v->parents = NULL;
-
+
v->lsa = NULL;
-
+
XFREE (MTYPE_OSPF_VERTEX, v);
}
@@ -232,11 +232,11 @@ ospf_vertex_dump(const char *msg, struct vertex *v,
{
struct listnode *node;
struct vertex_parent *vp;
-
+
for (ALL_LIST_ELEMENTS_RO (v->parents, node, vp))
{
char buf1[BUFSIZ];
-
+
if (vp)
{
zlog_debug ("parent %s backlink %d nexthop %s interface %s",
@@ -251,7 +251,7 @@ ospf_vertex_dump(const char *msg, struct vertex *v,
{
struct listnode *cnode;
struct vertex *cv;
-
+
for (ALL_LIST_ELEMENTS_RO (v->children, cnode, cv))
ospf_vertex_dump(" child:", cv, 0, 0);
}
@@ -264,27 +264,27 @@ ospf_vertex_add_parent (struct vertex *v)
{
struct vertex_parent *vp;
struct listnode *node;
-
+
assert (v && v->parents);
-
+
for (ALL_LIST_ELEMENTS_RO (v->parents, node, vp))
{
assert (vp->parent && vp->parent->children);
-
+
/* No need to add two links from the same parent. */
if (listnode_lookup (vp->parent->children, v) == NULL)
listnode_add (vp->parent->children, v);
}
}
-
+
static void
ospf_spf_init (struct ospf_area *area)
{
struct vertex *v;
-
+
/* Create root node. */
v = ospf_vertex_new (area->router_lsa_self);
-
+
area->spf = v;
/* Reset ABR and ASBR router counts. */
@@ -407,7 +407,7 @@ ospf_spf_flush_parents (struct vertex *w)
{
struct vertex_parent *vp;
struct listnode *ln, *nn;
-
+
/* delete the existing nexthops */
for (ALL_LIST_ELEMENTS (w->parents, ln, nn, vp))
{
@@ -416,9 +416,9 @@ ospf_spf_flush_parents (struct vertex *w)
}
}
-/*
+/*
* Consider supplied next-hop for inclusion to the supplied list of
- * equal-cost next-hops, adjust list as neccessary.
+ * equal-cost next-hops, adjust list as neccessary.
*/
static void
ospf_spf_add_parent (struct vertex *v, struct vertex *w,
@@ -426,11 +426,11 @@ ospf_spf_add_parent (struct vertex *v, struct vertex *w,
unsigned int distance)
{
struct vertex_parent *vp;
-
+
/* we must have a newhop, and a distance */
assert (v && w && newhop);
assert (distance);
-
+
/* IFF w has already been assigned a distance, then we shouldn't get here
* unless callers have determined V(l)->W is shortest / equal-shortest
* path (0 is a special case distance (no distance yet assigned)).
@@ -439,7 +439,7 @@ ospf_spf_add_parent (struct vertex *v, struct vertex *w,
assert (distance <= w->distance);
else
w->distance = distance;
-
+
if (IS_DEBUG_OSPF_EVENT)
{
char buf[2][INET_ADDRSTRLEN];
@@ -447,7 +447,7 @@ ospf_spf_add_parent (struct vertex *v, struct vertex *w,
__func__,
inet_ntop(AF_INET, &v->lsa->id, buf[0], sizeof(buf[0])),
inet_ntop(AF_INET, &w->lsa->id, buf[1], sizeof(buf[1])));
- }
+ }
/* Adding parent for a new, better path: flush existing parents from W. */
if (distance < w->distance)
@@ -458,8 +458,8 @@ ospf_spf_add_parent (struct vertex *v, struct vertex *w,
ospf_spf_flush_parents (w);
w->distance = distance;
}
-
- /* new parent is <= existing parents, add it to parent list */
+
+ /* new parent is <= existing parents, add it to parent list */
vp = vertex_parent_new (v, ospf_lsa_has_link (w->lsa, v->lsa), newhop);
listnode_add (w->parents, vp);
@@ -497,11 +497,11 @@ ospf_nexthop_calculation (struct ospf_area *area, struct vertex *v,
}
if (v == area->spf)
- {
+ {
/* 16.1.1 para 4. In the first case, the parent vertex (V) is the
- root (the calculating router itself). This means that the
+ root (the calculating router itself). This means that the
destination is either a directly connected network or directly
- connected router. The outgoing interface in this case is simply
+ connected router. The outgoing interface in this case is simply
the OSPF interface connecting to the destination network/router.
*/
@@ -511,15 +511,15 @@ ospf_nexthop_calculation (struct ospf_area *area, struct vertex *v,
* l2 will be link from w to v
*/
struct router_lsa_link *l2 = NULL;
-
+
/* we *must* be supplied with the link data */
assert (l != NULL);
-
+
if (IS_DEBUG_OSPF_EVENT)
{
char buf1[BUFSIZ];
char buf2[BUFSIZ];
-
+
zlog_debug("ospf_nexthop_calculation(): considering link "
"type %d link_id %s link_data %s",
l->m[0].type,
@@ -539,13 +539,13 @@ ospf_nexthop_calculation (struct ospf_area *area, struct vertex *v,
provides an IP address of the next hop router.
At this point l is a link from V to W, and V is the
- root ("us"). Find the local interface associated
+ root ("us"). Find the local interface associated
with l (its address is in l->link_data). If it
is a point-to-multipoint interface, then look through
the links in the opposite direction (W to V). If
any of them have an address that lands within the
subnet declared by the PtMP link, then that link
- is a constituent of the PtMP link, and its address is
+ is a constituent of the PtMP link, and its address is
a nexthop address for V.
*/
oi = ospf_if_is_configured (area->ospf, &l->link_data);
@@ -605,17 +605,17 @@ ospf_nexthop_calculation (struct ospf_area *area, struct vertex *v,
else if (l->m[0].type == LSA_LINK_TYPE_VIRTUALLINK)
{
struct ospf_vl_data *vl_data;
-
- /* VLink implementation limitations:
+
+ /* VLink implementation limitations:
* a) vl_data can only reference one nexthop, so no ECMP
- * to backbone through VLinks. Though transit-area
+ * to backbone through VLinks. Though transit-area
* summaries may be considered, and those can be ECMP.
* b) We can only use /one/ VLink, even if multiple ones
* exist this router through multiple transit-areas.
*/
vl_data = ospf_vl_lookup (area->ospf, NULL, l->link_id);
-
- if (vl_data
+
+ if (vl_data
&& CHECK_FLAG (vl_data->flags, OSPF_VL_FLAG_APPROVED))
{
nh = vertex_nexthop_new ();
@@ -667,7 +667,7 @@ ospf_nexthop_calculation (struct ospf_area *area, struct vertex *v,
/* ...For each link in the router-LSA that points back to the
* parent network, the link's Link Data field provides the IP
* address of a next hop router. The outgoing interface to
- * use can then be derived from the next hop IP address (or
+ * use can then be derived from the next hop IP address (or
* it can be inherited from the parent network).
*/
nh = vertex_nexthop_new ();
@@ -679,7 +679,7 @@ ospf_nexthop_calculation (struct ospf_area *area, struct vertex *v,
}
}
/* NB: This code is non-trivial.
- *
+ *
* E.g. it is not enough to know that V connects to the root. It is
* also important that the while above, looping through all links from
* W->V found at least one link, so that we know there is
@@ -708,7 +708,7 @@ ospf_nexthop_calculation (struct ospf_area *area, struct vertex *v,
added = 1;
ospf_spf_add_parent (v, w, vp->nexthop, distance);
}
-
+
return added;
}
@@ -735,13 +735,13 @@ ospf_spf_next (struct vertex *v, struct ospf_area *area,
if (IS_ROUTER_LSA_VIRTUAL ((struct router_lsa *) v->lsa))
area->transit = OSPF_TRANSIT_TRUE;
}
-
+
if (IS_DEBUG_OSPF_EVENT)
zlog_debug ("%s: Next vertex of %s vertex %s",
- __func__,
+ __func__,
v->type == OSPF_VERTEX_ROUTER ? "Router" : "Network",
inet_ntoa(v->lsa->id));
-
+
p = ((u_char *) v->lsa) + OSPF_LSA_HEADER_SIZE + 4;
lim = ((u_char *) v->lsa) + ntohs (v->lsa->length);
@@ -749,7 +749,7 @@ ospf_spf_next (struct vertex *v, struct ospf_area *area,
{
struct vertex *w;
unsigned int distance;
-
+
/* In case of V is Router-LSA. */
if (v->lsa->type == OSPF_ROUTER_LSA)
{
@@ -764,7 +764,7 @@ ospf_spf_next (struct vertex *v, struct ospf_area *area,
calculation. */
if ((type = l->m[0].type) == LSA_LINK_TYPE_STUB)
continue;
-
+
/* Infinite distance links shouldn't be followed, except
* for local links (a stub-routed router still wants to
* calculate tree, so must follow its own links).
@@ -886,7 +886,7 @@ ospf_spf_next (struct vertex *v, struct ospf_area *area,
/* Get the vertex from candidates. */
w = candidate->array[w_lsa->stat];
- /* if D is greater than. */
+ /* if D is greater than. */
if (w->distance < distance)
{
continue;
@@ -894,7 +894,7 @@ ospf_spf_next (struct vertex *v, struct ospf_area *area,
/* equal to. */
else if (w->distance == distance)
{
- /* Found an equal-cost path to W.
+ /* Found an equal-cost path to W.
* Calculate nexthop of to W from V. */
ospf_nexthop_calculation (area, v, w, l, distance);
}
@@ -909,7 +909,7 @@ ospf_spf_next (struct vertex *v, struct ospf_area *area,
if (ospf_nexthop_calculation (area, v, w, l, distance))
/* Decrease the key of the node in the heap.
* trickle-sort it up towards root, just in case this
- * node should now be the new root due the cost change.
+ * node should now be the new root due the cost change.
* (next pqueu_{de,en}queue will fully re-heap the queue).
*/
trickle_up (w_lsa->stat, candidate);
@@ -941,7 +941,7 @@ ospf_spf_dump (struct vertex *v, int i)
if (IS_DEBUG_OSPF_EVENT)
for (ALL_LIST_ELEMENTS_RO (v->parents, nnode, parent))
{
- zlog_debug (" nexthop %p %s %s",
+ zlog_debug (" nexthop %p %s %s",
parent->nexthop,
inet_ntoa (parent->nexthop->router),
parent->nexthop->oi ? IF_NAME(parent->nexthop->oi)
@@ -1003,16 +1003,16 @@ ospf_spf_process_stubs (struct ospf_area *area, struct vertex *v,
{
if (CHECK_FLAG (child->flags, OSPF_VERTEX_PROCESSED))
continue;
-
+
/* the first level of routers connected to the root
- * should have 'parent_is_root' set, including those
+ * should have 'parent_is_root' set, including those
* connected via a network vertex.
*/
if (area->spf == v)
parent_is_root = 1;
else if (v->type == OSPF_VERTEX_ROUTER)
parent_is_root = 0;
-
+
ospf_spf_process_stubs (area, child, rt, parent_is_root);
SET_FLAG (child->flags, OSPF_VERTEX_PROCESSED);
@@ -1114,7 +1114,7 @@ ospf_spf_calculate (struct ospf_area *area, struct route_table *new_table,
{
struct pqueue *candidate;
struct vertex *v;
-
+
if (IS_DEBUG_OSPF_EVENT)
{
zlog_debug ("ospf_spf_calculate: Start");
@@ -1135,11 +1135,11 @@ ospf_spf_calculate (struct ospf_area *area, struct route_table *new_table,
/* RFC2328 16.1. (1). */
/* Initialize the algorithm's data structures. */
-
+
/* This function scans all the LSA database and set the stat field to
* LSA_SPF_NOT_EXPLORED. */
ospf_lsdb_clean_stat (area->lsdb);
- /* Create a new heap for the candidates. */
+ /* Create a new heap for the candidates. */
candidate = pqueue_create();
candidate->cmp = cmp;
candidate->update = update_stat;
@@ -1155,7 +1155,7 @@ ospf_spf_calculate (struct ospf_area *area, struct route_table *new_table,
/* Set Area A's TransitCapability to FALSE. */
area->transit = OSPF_TRANSIT_FALSE;
area->shortcut_capability = 1;
-
+
for (;;)
{
/* RFC2328 16.1. (2). */
@@ -1201,29 +1201,29 @@ ospf_spf_calculate (struct ospf_area *area, struct route_table *new_table,
/* Free candidate queue. */
pqueue_delete (candidate);
-
+
ospf_vertex_dump (__func__, area->spf, 0, 1);
/* Free nexthop information, canonical versions of which are attached
* the first level of router vertices attached to the root vertex, see
* ospf_nexthop_calculation.
*/
ospf_canonical_nexthops_free (area->spf);
-
+
/* Free SPF vertices, but not the list. List has ospf_vertex_free
* as deconstructor.
*/
list_delete_all_node (&vertex_list);
-
+
/* Increment SPF Calculation Counter. */
area->spf_calculation++;
quagga_gettime (QUAGGA_CLK_MONOTONIC, &area->ospf->ts_spf);
if (IS_DEBUG_OSPF_EVENT)
- zlog_debug ("ospf_spf_calculate: Stop. %ld vertices",
+ zlog_debug ("ospf_spf_calculate: Stop. %lu vertices",
mtype_stats_alloc(MTYPE_OSPF_VERTEX));
}
-
+
/* Timer for SPF calculation. */
static int
ospf_spf_calculate_timer (struct thread *thread)
@@ -1252,14 +1252,14 @@ ospf_spf_calculate_timer (struct thread *thread)
*/
if (ospf->backbone && ospf->backbone == area)
continue;
-
+
ospf_spf_calculate (area, new_table, new_rtrs);
}
-
+
/* SPF for backbone, if required */
if (ospf->backbone)
ospf_spf_calculate (ospf->backbone, new_table, new_rtrs);
-
+
ospf_vl_shut_unapproved (ospf);
ospf_ia_routing (ospf, new_table, new_rtrs);
@@ -1313,7 +1313,7 @@ ospf_spf_calculate_schedule (struct ospf *ospf)
/* OSPF instance does not exist. */
if (ospf == NULL)
return;
-
+
/* SPF calculation timer is already scheduled. */
if (ospf->t_spf_calc)
{
@@ -1322,16 +1322,16 @@ ospf_spf_calculate_schedule (struct ospf *ospf)
ospf->t_spf_calc);
return;
}
-
+
/* XXX Monotic timers: we only care about relative time here. */
result = tv_sub (recent_relative_time (), ospf->ts_spf);
-
+
elapsed = (result.tv_sec * 1000) + (result.tv_usec / 1000);
ht = ospf->spf_holdtime * ospf->spf_hold_multiplier;
-
+
if (ht > ospf->spf_max_holdtime)
ht = ospf->spf_max_holdtime;
-
+
/* Get SPF calculation delay time. */
if (elapsed < ht)
{
@@ -1341,7 +1341,7 @@ ospf_spf_calculate_schedule (struct ospf *ospf)
*/
if (ht < ospf->spf_max_holdtime)
ospf->spf_hold_multiplier++;
-
+
/* always honour the SPF initial delay */
if ( (ht - elapsed) < ospf->spf_delay)
delay = ospf->spf_delay;
@@ -1354,7 +1354,7 @@ ospf_spf_calculate_schedule (struct ospf *ospf)
delay = ospf->spf_delay;
ospf->spf_hold_multiplier = 1;
}
-
+
if (IS_DEBUG_OSPF_EVENT)
zlog_debug ("SPF: calculation timer delay = %ld", delay);
diff --git a/tests/Makefile.am b/tests/Makefile.am
index d6e24db7..b4233fb8 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -6,8 +6,8 @@ AM_LDFLAGS = $(PILDFLAGS)
noinst_PROGRAMS = testsig testbuffer testmemory heavy heavywq heavythread \
aspathtest testprivs teststream testbgpcap ecommtest \
- testbgpmpattr testchecksum testvector testsymtab \
- testlistutil
+ testbgpmpattr testchecksum testvector testsymtab \
+ testlistutil testqpath
testsig_SOURCES = test-sig.c
testbuffer_SOURCES = test-buffer.c
@@ -25,6 +25,7 @@ testchecksum_SOURCES = test-checksum.c
testvector_SOURCES = test-vector.c
testsymtab_SOURCES = test-symtab.c
testlistutil_SOURCES = test-list_util.c
+testqpath_SOURCES = test-qpath.c
testsig_LDADD = ../lib/libzebra.la @LIBCAP@
testbuffer_LDADD = ../lib/libzebra.la @LIBCAP@
@@ -42,3 +43,4 @@ testchecksum_LDADD = ../lib/libzebra.la @LIBCAP@
testvector_LDADD = ../lib/libzebra.la @LIBCAP@
testsymtab_LDADD = ../lib/libzebra.la @LIBCAP@
testlistutil_LDADD = ../lib/libzebra.la @LIBCAP@
+testqpath_LDADD = ../lib/libzebra.la @LIBCAP@
diff --git a/tests/test-qpath.c b/tests/test-qpath.c
new file mode 100644
index 00000000..ecf1a0ae
--- /dev/null
+++ b/tests/test-qpath.c
@@ -0,0 +1,490 @@
+#include "misc.h"
+#include "qpath.h"
+#include "qlib_init.h"
+
+#include <stdio.h>
+
+struct thread_master *master ; /* required by lib ! */
+
+/*==============================================================================
+ * qpath torture tests
+ *
+ */
+static int qpath_reduce_testing(void) ;
+
+int
+main(int argc, char **argv)
+{
+ int errors = 0 ;
+
+ qlib_init_first_stage() ;
+
+ fprintf(stdout, "qpath torture tests\n") ;
+
+ errors += qpath_reduce_testing() ;
+
+
+
+ if (errors == 0)
+ fprintf(stdout, "No errors\n") ;
+ else
+ fprintf(stderr, "*** %d errors\n", errors) ;
+} ;
+
+/*==============================================================================
+ * Testing Path Reduction
+ */
+static int qpath_reduce_test(qpath qp, const char* from, const char* to) ;
+
+static int
+qpath_reduce_testing(void)
+{
+ int errors = 0 ;
+ qpath qp ;
+
+ fprintf(stdout, " qpath_reduce() testing") ;
+
+ qp = qpath_init_new(NULL) ;
+
+ /* Trivial Tests */
+
+ errors += qpath_reduce_test(qp, "",
+ "") ;
+ errors += qpath_reduce_test(qp, "a",
+ "a") ;
+ errors += qpath_reduce_test(qp, ".",
+ ".") ;
+ errors += qpath_reduce_test(qp, "/",
+ "/") ;
+ errors += qpath_reduce_test(qp, "/a",
+ "/a") ;
+ errors += qpath_reduce_test(qp, "/.",
+ "/-") ;
+ errors += qpath_reduce_test(qp, "//",
+ "//") ;
+ errors += qpath_reduce_test(qp, "//a",
+ "//a") ;
+ errors += qpath_reduce_test(qp, "//.",
+ "//-") ;
+ errors += qpath_reduce_test(qp, "///",
+ "/--") ;
+ errors += qpath_reduce_test(qp, "///a",
+ "/--a") ;
+ errors += qpath_reduce_test(qp, "///.",
+ "/---") ;
+
+ /* Slightly longer paths */
+
+ errors += qpath_reduce_test(qp, "abc",
+ "abc") ;
+ errors += qpath_reduce_test(qp, "abc.",
+ "abc.") ;
+ errors += qpath_reduce_test(qp, ".abc",
+ ".abc") ;
+ errors += qpath_reduce_test(qp, "/abc",
+ "/abc") ;
+ errors += qpath_reduce_test(qp, "/.abc",
+ "/.abc") ;
+ errors += qpath_reduce_test(qp, "/..abc",
+ "/..abc") ;
+ errors += qpath_reduce_test(qp, "//abc",
+ "//abc") ;
+ errors += qpath_reduce_test(qp, "//abc.",
+ "//abc.") ;
+ errors += qpath_reduce_test(qp, "//.abc",
+ "//.abc") ;
+ errors += qpath_reduce_test(qp, "///abc",
+ "/--abc") ;
+ errors += qpath_reduce_test(qp, "///abc.",
+ "/--abc.") ;
+ errors += qpath_reduce_test(qp, "///..abc",
+ "/--..abc") ;
+
+ errors += qpath_reduce_test(qp, "abc/pqr",
+ "abc/pqr") ;
+ errors += qpath_reduce_test(qp, "abc./pqr/",
+ "abc./pqr/") ;
+ errors += qpath_reduce_test(qp, ".abc/pqr//",
+ ".abc/pqr/-") ;
+ errors += qpath_reduce_test(qp, "/abc/pqr",
+ "/abc/pqr") ;
+ errors += qpath_reduce_test(qp, "/.abc/pqr/",
+ "/.abc/pqr/") ;
+ errors += qpath_reduce_test(qp, "/..abc/pqr///",
+ "/..abc/pqr/--") ;
+ errors += qpath_reduce_test(qp, "//abc/pqr",
+ "//abc/pqr") ;
+ errors += qpath_reduce_test(qp, "//abc./pqr/",
+ "//abc./pqr/") ;
+ errors += qpath_reduce_test(qp, "//.abc/pqr//",
+ "//.abc/pqr/-") ;
+ errors += qpath_reduce_test(qp, "///abc/pqr",
+ "/--abc/pqr") ;
+ errors += qpath_reduce_test(qp, "///abc./pqr/",
+ "/--abc./pqr/") ;
+ errors += qpath_reduce_test(qp, "///..abc/pqr//",
+ "/--..abc/pqr/-") ;
+
+ /* Lots of / and . to get rid of -- NB: does not discard trailing '/' */
+
+ errors += qpath_reduce_test(qp, "/.///./././//",
+ "/------------") ;
+ errors += qpath_reduce_test(qp, "/.a.///./././//",
+ "/.a./----------") ;
+ errors += qpath_reduce_test(qp, "/.///.b/././//",
+ "/----.b/------") ;
+ errors += qpath_reduce_test(qp, "/.a.///./././//.z.",
+ "/.a./----------.z.") ;
+ errors += qpath_reduce_test(qp, "/.///.b/././//z.",
+ "/----.b/------z.") ;
+ errors += qpath_reduce_test(qp, "/a///./././//.z./",
+ "/a/----------.z./") ;
+ errors += qpath_reduce_test(qp, "/.///.b/././//.z./",
+ "/----.b/------.z./") ;
+
+ errors += qpath_reduce_test(qp, "//.///./././//",
+ "//------------") ;
+ errors += qpath_reduce_test(qp, "//.a.///./././//",
+ "//.a./----------") ;
+ errors += qpath_reduce_test(qp, "//.///.b/././//",
+ "//----.b/------") ;
+ errors += qpath_reduce_test(qp, "//a///./././//z",
+ "//a/----------z") ;
+ errors += qpath_reduce_test(qp, "//.///.b/././//.z.",
+ "//----.b/------.z.") ;
+ errors += qpath_reduce_test(qp, "//a///./././//..z/",
+ "//a/----------..z/") ;
+ errors += qpath_reduce_test(qp, "//.///.b/././//z../",
+ "//----.b/------z../") ;
+
+ errors += qpath_reduce_test(qp, "///.///./././//",
+ "/--------------") ;
+ errors += qpath_reduce_test(qp, "///..a///./././//",
+ "/--..a/----------") ;
+ errors += qpath_reduce_test(qp, "///.///.b/././//",
+ "/------.b/------") ;
+ errors += qpath_reduce_test(qp, "///a..///./././//z",
+ "/--a../----------z") ;
+ errors += qpath_reduce_test(qp, "///.///.b/././//z",
+ "/------.b/------z") ;
+ errors += qpath_reduce_test(qp, "///..a..///./././//z/",
+ "/--..a../----------z/") ;
+ errors += qpath_reduce_test(qp, "///.///.b/././//z/",
+ "/------.b/------z/") ;
+
+ /* Assorted trailing '.' */
+
+ errors += qpath_reduce_test(qp, ".",
+ ".") ;
+ errors += qpath_reduce_test(qp, "./",
+ "./") ;
+ errors += qpath_reduce_test(qp, "a././",
+ "a./--") ;
+ errors += qpath_reduce_test(qp, "/.a/./",
+ "/.a/--") ;
+ errors += qpath_reduce_test(qp, "/a./.",
+ "/a./-") ;
+ errors += qpath_reduce_test(qp, "/.",
+ "/-") ;
+ errors += qpath_reduce_test(qp, "/./",
+ "/--") ;
+ errors += qpath_reduce_test(qp, "//.",
+ "//-") ;
+ errors += qpath_reduce_test(qp, "//./",
+ "//--") ;
+ errors += qpath_reduce_test(qp, "///.",
+ "/---") ;
+ errors += qpath_reduce_test(qp, "///.//",
+ "/-----") ;
+
+ /* Possible .. */
+
+ errors += qpath_reduce_test(qp, ".a.a.a./..",
+ "----------") ;
+ errors += qpath_reduce_test(qp, "..aaa../../",
+ "-----------") ;
+ errors += qpath_reduce_test(qp, "..aaa../..",
+ "----------") ;
+ errors += qpath_reduce_test(qp, "..aaa../.././//././/.",
+ "---------------------") ;
+ errors += qpath_reduce_test(qp, "..aaa../../..z",
+ "-----------..z") ;
+ errors += qpath_reduce_test(qp, "..aaa../../..z../",
+ "-----------..z../") ;
+
+ errors += qpath_reduce_test(qp, "..b.b.b../..aaa../..",
+ "..b.b.b../----------") ;
+ errors += qpath_reduce_test(qp, "..bbb../..a.a.a../../",
+ "..bbb../-------------") ;
+ errors += qpath_reduce_test(qp, "..bbb../..a.a.a../.././///./",
+ "..bbb../--------------------") ;
+ errors += qpath_reduce_test(qp, "..bbb../..aaa.././/../..z..",
+ "..bbb../--------------..z..") ;
+ errors += qpath_reduce_test(qp, "..bbb../..aaa.././/../..z../",
+ "..bbb../--------------..z../") ;
+
+ errors += qpath_reduce_test(qp, "/..a.a.a../..",
+ "/------------") ;
+ errors += qpath_reduce_test(qp, "/..a.a.a/../",
+ "/-----------") ;
+ errors += qpath_reduce_test(qp, "/a.a.a../.././///./",
+ "/------------------") ;
+ errors += qpath_reduce_test(qp, "/..aaa/.//../..z",
+ "/------------..z") ;
+ errors += qpath_reduce_test(qp, "/aaa.././/../z/",
+ "/------------z/") ;
+
+ errors += qpath_reduce_test(qp, "//aaa../..",
+ "//--------") ;
+ errors += qpath_reduce_test(qp, "//..aaa/../",
+ "//---------") ;
+ errors += qpath_reduce_test(qp, "//aaa../.././///./",
+ "//----------------") ;
+ errors += qpath_reduce_test(qp, "//aaa.././/../..z",
+ "//------------..z") ;
+ errors += qpath_reduce_test(qp, "//..aaa/.//../z../",
+ "//------------z../") ;
+
+ errors += qpath_reduce_test(qp, "./aaa../..",
+ "./--------") ;
+ errors += qpath_reduce_test(qp, "./..aaa/../",
+ "./---------") ;
+ errors += qpath_reduce_test(qp, "./aaa../.././///./",
+ "./----------------") ;
+ errors += qpath_reduce_test(qp, "./..aaa/.//../z",
+ "./------------z") ;
+ errors += qpath_reduce_test(qp, "./aaa.././/../z/",
+ "./------------z/") ;
+
+ errors += qpath_reduce_test(qp, ".///./aaa./..",
+ "./-----------") ;
+ errors += qpath_reduce_test(qp, "./././.aaa/../",
+ "./------------") ;
+ errors += qpath_reduce_test(qp, "./////.aaa/.././///./",
+ "./-------------------") ;
+ errors += qpath_reduce_test(qp, "././//./aaa././/../z",
+ "./-----------------z") ;
+ errors += qpath_reduce_test(qp, "./..aaa.././/../z/",
+ "./--------------z/") ;
+
+ errors += qpath_reduce_test(qp, "///..aaa../..",
+ "/------------") ;
+ errors += qpath_reduce_test(qp, "///.aaa./../",
+ "/-----------") ;
+ errors += qpath_reduce_test(qp, "///aaa/.././///./",
+ "/----------------") ;
+ errors += qpath_reduce_test(qp, "///..aaa/.//../z",
+ "/--------------z") ;
+ errors += qpath_reduce_test(qp, "///aaa.././/../z/",
+ "/--------------z/") ;
+
+ errors += qpath_reduce_test(qp, ".bbb/..aaa/../..",
+ "----------------") ;
+ errors += qpath_reduce_test(qp, "bbb./aaa../../../",
+ "-----------------") ;
+ errors += qpath_reduce_test(qp, "bbb../aaa../../.././///./",
+ "-------------------------") ;
+ errors += qpath_reduce_test(qp, "..bbb/..aaa/../../z",
+ "------------------z") ;
+ errors += qpath_reduce_test(qp, "bbb../..aaa/../../z/",
+ "------------------z/") ;
+
+ errors += qpath_reduce_test(qp, "bbb./aaa/../.zzz/../qqq/../..",
+ "-----------------------------") ;
+ errors += qpath_reduce_test(qp, "bbb/.aaa/../zzz./../qqq/../../",
+ "------------------------------") ;
+ errors += qpath_reduce_test(qp, "/bbb/aaa/../zzz../../..qqq/../../",
+ "/--------------------------------") ;
+ errors += qpath_reduce_test(qp, "//bbb../aaa/../zzz/../qqq../../../",
+ "//--------------------------------") ;
+ errors += qpath_reduce_test(qp, "./..bbb/aaa/../zzz/../qqq/../../",
+ "./------------------------------") ;
+ errors += qpath_reduce_test(qp, "./bbb/aaa../../zzz/../qqq/../..",
+ "./-----------------------------") ;
+ errors += qpath_reduce_test(qp, "///bbb/aaa/../zzz../../qqq/../../",
+ "/--------------------------------") ;
+
+ errors += qpath_reduce_test(qp, "bbb/aaa/../zzz/../qqq/../../.",
+ "-----------------------------") ;
+ errors += qpath_reduce_test(qp, "bbb/aaa/../zzz/../qqq/../.././",
+ "------------------------------") ;
+ errors += qpath_reduce_test(qp, "/bbb/aaa/../zzz/../qqq/../../",
+ "/----------------------------") ;
+ errors += qpath_reduce_test(qp, "//bbb/aaa/../zzz/../qqq/../../",
+ "//----------------------------") ;
+ errors += qpath_reduce_test(qp, "./bbb/aaa/../zzz/../qqq/../../",
+ "./----------------------------") ;
+ errors += qpath_reduce_test(qp, "./bbb/aaa/../zzz/../qqq/../..",
+ "./---------------------------") ;
+ errors += qpath_reduce_test(qp, "///bbb/aaa/../zzz/../qqq/../../",
+ "/------------------------------") ;
+
+ errors += qpath_reduce_test(qp, "o/aaa../../zzz/../qqq/../../.",
+ "-----------------------------") ;
+ errors += qpath_reduce_test(qp, "o/aaa/../zzz../../qqq/../.././",
+ "------------------------------") ;
+ errors += qpath_reduce_test(qp, "o/bbb/aaa/../zzz/../qqq../../../",
+ "o/------------------------------") ;
+ errors += qpath_reduce_test(qp, "o/bbb/aaa/../zzz/../qqq/../../",
+ "o/----------------------------") ;
+ errors += qpath_reduce_test(qp, "o/bbb/aaa/../zzz/../qqq/../../",
+ "o/----------------------------") ;
+ errors += qpath_reduce_test(qp, "o/bbb/aaa/../zzz/../qqq/../..",
+ "o/---------------------------") ;
+ errors += qpath_reduce_test(qp, "o//bbb/aaa/../zzz/../qqq/../../",
+ "o/-----------------------------") ;
+
+ errors += qpath_reduce_test(qp, "zzz../././/.././o/..",
+ "--------------------") ;
+ errors += qpath_reduce_test(qp, "zzz.././o//.././o/../",
+ "zzz../---------------") ;
+
+ /* Impossible .. */
+
+ errors += qpath_reduce_test(qp, "..",
+ "..") ;
+ errors += qpath_reduce_test(qp, "../",
+ "../") ;
+ errors += qpath_reduce_test(qp, "/..",
+ "/..") ;
+ errors += qpath_reduce_test(qp, "/../",
+ "/../") ;
+ errors += qpath_reduce_test(qp, "//..",
+ "//..") ;
+ errors += qpath_reduce_test(qp, "//../",
+ "//../") ;
+ errors += qpath_reduce_test(qp, "///..",
+ "/--..") ;
+ errors += qpath_reduce_test(qp, "///../",
+ "/--../") ;
+ errors += qpath_reduce_test(qp, "./..",
+ "./..") ;
+ errors += qpath_reduce_test(qp, "./../",
+ "./../") ;
+ errors += qpath_reduce_test(qp, ".///..",
+ "./--..") ;
+ errors += qpath_reduce_test(qp, ".///../",
+ "./--../") ;
+ errors += qpath_reduce_test(qp, ".///..",
+ "./--..") ;
+ errors += qpath_reduce_test(qp, "././/../",
+ "./---../") ;
+
+ errors += qpath_reduce_test(qp, "..///./..",
+ "../----..") ;
+ errors += qpath_reduce_test(qp, "..///./../",
+ "../----../") ;
+ errors += qpath_reduce_test(qp, "/.././///..",
+ "/../-----..") ;
+ errors += qpath_reduce_test(qp, "/..///.//../",
+ "/../-----../") ;
+ errors += qpath_reduce_test(qp, "//..///.//../.",
+ "//../-----../-") ;
+ errors += qpath_reduce_test(qp, "//..//.///.././",
+ "//../-----../--") ;
+ errors += qpath_reduce_test(qp, "///..///.//..",
+ "/--../-----..") ;
+ errors += qpath_reduce_test(qp, "///.././/./../",
+ "/--../-----../") ;
+ errors += qpath_reduce_test(qp, "./../../..",
+ "./../../..") ;
+ errors += qpath_reduce_test(qp, "./../../../",
+ "./../../../") ;
+ errors += qpath_reduce_test(qp, ".///..///.///.///..",
+ "./--../----------..") ;
+ errors += qpath_reduce_test(qp, ".///.././././../",
+ "./--../------../") ;
+ errors += qpath_reduce_test(qp, ".///..///////..",
+ "./--../------..") ;
+ errors += qpath_reduce_test(qp, "././/..//////..////",
+ "./---../-----../---") ;
+
+ errors += qpath_reduce_test(qp, "../././/.",
+ "../------") ;
+ errors += qpath_reduce_test(qp, "../zzz/..",
+ "../------") ;
+ errors += qpath_reduce_test(qp, "/../zzz/.//./../",
+ "/../------------") ;
+ errors += qpath_reduce_test(qp, "/zzz../././/.././..",
+ "/----------------..") ;
+ errors += qpath_reduce_test(qp, "/zzz../././/.././../",
+ "/----------------../") ;
+ errors += qpath_reduce_test(qp, "zzz../././/.././..",
+ "----------------..") ;
+ errors += qpath_reduce_test(qp, "zzz../././/.././../",
+ "----------------../") ;
+ errors += qpath_reduce_test(qp, "/zzz../././/.././..",
+ "/----------------..") ;
+ errors += qpath_reduce_test(qp, "/zzz../././/.././../",
+ "/----------------../") ;
+ errors += qpath_reduce_test(qp, "//zzz../././/.././..",
+ "//----------------..") ;
+ errors += qpath_reduce_test(qp, "//zzz../././/.././../",
+ "//----------------../") ;
+ errors += qpath_reduce_test(qp, "///zzz../././/.././..",
+ "/------------------..") ;
+ errors += qpath_reduce_test(qp, "///zzz../././/.././../",
+ "/------------------../") ;
+
+ /* Finish up */
+
+ qpath_reset(qp, free_it) ;
+
+ if (errors == 0)
+ fprintf(stdout, " -- OK\n") ;
+ else
+ fprintf(stdout, "\n *** %d errors\n", errors) ;
+
+ return errors ;
+} ;
+
+
+static int
+qpath_reduce_test(qpath qp, const char* from, const char* to)
+{
+ const char* r ;
+ char e[100] ;
+ const char* p ;
+ char* q ;
+
+ assert(strlen(to) < sizeof(e)) ;
+ if (strlen(to) != strlen(from))
+ {
+ fprintf(stdout,
+ "\n"
+ " qpath_reduce(%s)\n"
+ " to: '%s' ???", from, to) ;
+ return 1 ;
+ } ;
+
+ p = to ;
+ q = e ;
+
+ while (1)
+ {
+ char ch = *p++ ;
+
+ if ((ch == ' ') || (ch == '-'))
+ continue ;
+
+ *q++ = ch ;
+
+ if (ch == '\0')
+ break ;
+ } ;
+
+ qpath_set(qp, from) ; /* Reduces automajically */
+
+ r = qpath_string(qp) ;
+
+ if (strcmp(r, e) == 0)
+ return 0 ;
+
+ fprintf(stdout,
+ "\n"
+ " qpath_reduce(%s)\n"
+ " returned: '%s'\n"
+ " expected: '%s'", from, r, e) ;
+
+ return 1 ;
+} ;
diff --git a/tests/test-vector.c b/tests/test-vector.c
index 109f9004..02105e01 100644
--- a/tests/test-vector.c
+++ b/tests/test-vector.c
@@ -25,12 +25,11 @@ void test_vector_insert_item(void);
void test_vector_insert_item_here(void);
void test_vector_delete_item(void);
void do_test_insert(const int rider);
-int sort_cmp(const void* const* a, const void* const* b);
+int sort_cmp(void const* const* a, void const* const* b);
void test_vector_sort(void);
void test_vector_bsearch(void);
void test_vector_move_item_here(void);
-void do_test_move_item_here(const int rider, vector_index ins, vector_index src,
- char strings[][10], const vector_index len) ;
+void do_test_move_item_here(const int rider);
void test_vector_part_reverse(void);
void test_vector_copy_here(void);
void test_vector_move_here(void);
@@ -290,9 +289,9 @@ test_vector_set(void)
"vector_active != 1000");
vector_set(v, s1000);
- assert_true(vector_count(v) == len + 1,
+ assert_true(vector_count(v) == (unsigned)len + 1,
"vector_count != 1001");
- assert_true(vector_active(v) == len + 1,
+ assert_true(vector_active(v) == (unsigned)len + 1,
"vector_active != 1001");
assert_true(vector_slot(v,1000) == s1000,
"vector_slot != 1000");
@@ -551,7 +550,7 @@ test_vector_sort(void)
}
int
-sort_cmp(const void* const* a, const void* const* b)
+sort_cmp(void const* const* a, void const* const* b)
{
return strcmp(*a, *b);
}
@@ -606,275 +605,104 @@ test_vector_bsearch(void)
void
test_vector_move_item_here(void)
{
- const uint n = 20 ;
- const uint l = n - 1 ;
- char strings[n][10] ;
- uint i ;
-
- for (i = 0 ; i < n ; ++i)
- sprintf(strings[i], "item=%2u", i) ;
-
- do_test_move_item_here(-1, 8, 16, strings, n);
- do_test_move_item_here( 0, 8, 16, strings, n);
- do_test_move_item_here(+1, 8, 16, strings, n);
-
- do_test_move_item_here(-1, 16, 8, strings, n);
- do_test_move_item_here( 0, 16, 8, strings, n);
- do_test_move_item_here(+1, 16, 8, strings, n);
-
- do_test_move_item_here(-1, 9, 9, strings, n);
- do_test_move_item_here( 0, 9, 9, strings, n);
- do_test_move_item_here(+1, 9, 9, strings, n);
-
- do_test_move_item_here(-1, 10, 9, strings, n);
- do_test_move_item_here( 0, 10, 9, strings, n);
- do_test_move_item_here(+1, 10, 9, strings, n);
-
- do_test_move_item_here(-1, 9, 10, strings, n);
- do_test_move_item_here( 0, 9, 10, strings, n);
- do_test_move_item_here(+1, 9, 10, strings, n);
-
- do_test_move_item_here(-1, 11, 9, strings, n);
- do_test_move_item_here( 0, 11, 9, strings, n);
- do_test_move_item_here(+1, 11, 9, strings, n);
-
- do_test_move_item_here(-1, 9, 11, strings, n);
- do_test_move_item_here( 0, 9, 11, strings, n);
- do_test_move_item_here(+1, 9, 11, strings, n);
-
- do_test_move_item_here(-1, 0, 10, strings, n);
- do_test_move_item_here( 0, 0, 10, strings, n);
- do_test_move_item_here(+1, 0, 10, strings, n);
-
- do_test_move_item_here(-1, 10, 0, strings, n);
- do_test_move_item_here( 0, 10, 0, strings, n);
- do_test_move_item_here(+1, 10, 0, strings, n);
-
- do_test_move_item_here(-1, 0, l, strings, n);
- do_test_move_item_here( 0, 0, l, strings, n);
- do_test_move_item_here(+1, 0, l, strings, n);
-
- do_test_move_item_here(-1, l, 0, strings, n);
- do_test_move_item_here( 0, l, 0, strings, n);
- do_test_move_item_here(+1, l, 0, strings, n);
-
- do_test_move_item_here(-1, 10, l, strings, n);
- do_test_move_item_here( 0, 10, l, strings, n);
- do_test_move_item_here(+1, 10, l, strings, n);
-
- do_test_move_item_here(-1, l, 10, strings, n);
- do_test_move_item_here( 0, l, 10, strings, n);
- do_test_move_item_here(+1, l, 10, strings, n);
-
- do_test_move_item_here(-1, 0, 0, strings, n);
- do_test_move_item_here( 0, 0, 0, strings, n);
- do_test_move_item_here(+1, 0, 0, strings, n);
-
- do_test_move_item_here(-1, 1, 1, strings, n);
- do_test_move_item_here( 0, 1, 1, strings, n);
- do_test_move_item_here(+1, 1, 1, strings, n);
-
- do_test_move_item_here(-1, 0, 1, strings, n);
- do_test_move_item_here( 0, 0, 1, strings, n);
- do_test_move_item_here(+1, 0, 1, strings, n);
-
- do_test_move_item_here(-1, 1, 0, strings, n);
- do_test_move_item_here( 0, 1, 0, strings, n);
- do_test_move_item_here(+1, 1, 0, strings, n);
-
- do_test_move_item_here(-1, 0, 2, strings, n);
- do_test_move_item_here( 0, 0, 2, strings, n);
- do_test_move_item_here(+1, 0, 2, strings, n);
-
- do_test_move_item_here(-1, 2, 0, strings, n);
- do_test_move_item_here( 0, 2, 0, strings, n);
- do_test_move_item_here(+1, 2, 0, strings, n);
-
- do_test_move_item_here(-1, l, l, strings, n);
- do_test_move_item_here( 0, l, l, strings, n);
- do_test_move_item_here(+1, l, l, strings, n);
-
- do_test_move_item_here(-1,l-1, l, strings, n);
- do_test_move_item_here( 0,l-1, l, strings, n);
- do_test_move_item_here(+1,l-1, l, strings, n);
-
- do_test_move_item_here(-1, l,l-1, strings, n);
- do_test_move_item_here( 0, l,l-1, strings, n);
- do_test_move_item_here(+1, l,l-1, strings, n);
+ do_test_move_item_here(-1);
+ do_test_move_item_here(0);
+ do_test_move_item_here(1);
}
void
-do_test_move_item_here(const int rider, vector_index ins, vector_index src,
- char strings[][10], const vector_index len)
+do_test_move_item_here(const int rider)
{
vector v = NULL;
- vector_index i, e, check_end ;
- vector_index hi, lo ;
- char* expect[len] ;
-
+ const vector_length_t len = 100;
+ const vector_index_t ins = 50;
+ const vector_index_t src = 70;
+ vector_index_t i;
+ char buf[10];
+ vector_index_t check_dest = 0;
+ vector_index_t check_src = 0;
+ vector_index_t check_end = 0;
+ int check_shift = 0;
p_vector_item dest_item = NULL;
+
switch(rider)
{
case -1:
- printf("test_vector_move_here before dst=%2d, src=%2d\n", src, ins);
+ printf("test_vector_move_here before\n");
+ check_dest = ins;
+ check_src = src;
+ check_end = len;
+ check_shift = 1;
break;
-
case 0:
- printf("test_vector_move_here at dst=%2d, src=%2d\n", src, ins);
- --check_end ;
+ printf("test_vector_move_here at\n");
+ check_dest = ins;
+ check_src = src - 1;
+ check_end = len - 1;
+ check_shift = 0;
break;
-
case 1:
- printf("test_vector_move_here after dst=%2d, src=%2d\n", src, ins);
+ printf("test_vector_move_here after\n");
+ check_dest = ins + 1;
+ check_src = src;
+ check_end = len;
+ check_shift = 1;
break;
- } ;
-
- /* Build the test vector and perform action */
+ }
v = vector_init_new(v, 0);
for (i = 0; i < len; ++i)
- vector_set_item(v, i, strdup(strings[i]));
+ {
+ sprintf(buf, "%u", i);
+ vector_set_item(v, i, strdup(buf));
+ }
dest_item = vector_get_item(v, ins); /* item to free if rider == 0 */
vector_move_item_here(v, ins, rider, src);
+ assert_true(vector_end(v) == check_end, "vector_end(v) != check_end");
- /* Build the expected result. */
+ /* check contents are correct */
- if (ins <= src)
+ /* from start to insertion point */
+ for (i = 0; i < check_dest - 1; ++i)
{
- lo = ins ;
- hi = src ;
+ sprintf(buf, "%u", i);
+ assert_true(strcmp(vector_get_item(v, i), buf) == 0,
+ "vector_get_item(v, i) != buf");
}
- else
- {
- lo = src ;
- hi = ins ;
- } ;
- check_end = (rider != 0) ? len : len - 1 ;
- i = 0 ;
- e = 0 ;
+ /* at insertion point */
+ sprintf(buf, "%u", src);
+ assert_true(strcmp(vector_get_item(v, check_dest), buf) == 0,
+ "vector_get_item(v, check_dest) != buf");
- while (i < lo)
- expect[i++] = strings[e++] ;
-
- if (lo == hi)
+ /* from insertion point to src */
+ for (i = check_dest + 1; i <= check_src; ++i)
{
- /* Special case -- do nothing if rider != 0
- * drop entry if rider == 0
- */
- if (rider == 0)
- ++e ;
+ sprintf(buf, "%u", i - check_shift);
+ assert_true(strcmp(vector_get_item(v, i), buf) == 0, "vector_get_item(v, i) != buf");
}
- else if (lo == (hi - 1))
+
+ /* from src to end */
+ for (i = check_src + 1; i < check_end; ++i)
{
- /* Special case -- ins and src next to each other !
- *
- * If rider != 0 -- insert ins and src in the required order
- * If rider == 0 -- insert just src
- */
- if (rider < 0)
- {
- expect[i++] = strings[src] ;
- expect[i++] = strings[ins] ;
- }
- else if (rider == 0)
- {
- expect[i++] = strings[src] ;
- }
- else
- {
- expect[i++] = strings[ins] ;
- expect[i++] = strings[src] ;
- } ;
- e += 2 ;
+ sprintf(buf, "%u", i - (check_shift - 1));
+ assert_true(strcmp(vector_get_item(v, i), buf) == 0, "vector_get_item(v, i) != buf");
}
- else
- {
- /* Now we know that ins and src are separated by at least 1 entry.
- */
- uint be ;
-
- be = hi - 1 ;
-
- if (ins < src)
- {
- /* At insertion point, so insert ins and src in the required order
- * or insert juist the src.
- */
- if (rider < 0)
- {
- expect[i++] = strings[src] ;
- expect[i++] = strings[ins] ;
- ++be ;
- }
- else if (rider == 0)
- {
- expect[i++] = strings[src] ;
- }
- else
- {
- expect[i++] = strings[ins] ;
- expect[i++] = strings[src] ;
- ++be ;
- } ;
-
- ++be ;
- } ;
-
- e += 1 ;
-
- while (i < be)
- expect[i++] = strings[e++] ;
-
- if (ins > src)
- {
- /* At insertion point, so insert ins and src in the required order
- * or insert juist the src.
- */
- if (rider < 0)
- {
- expect[i++] = strings[src] ;
- expect[i++] = strings[ins] ;
- }
- else if (rider == 0)
- {
- expect[i++] = strings[src] ;
- }
- else
- {
- expect[i++] = strings[ins] ;
- expect[i++] = strings[src] ;
- } ;
- } ;
-
- e = hi + 1 ;
- } ;
-
- while (i < check_end)
- expect[i++] = strings[e++] ;
-
- /* check contents are correct */
- assert_true(vector_end(v) == check_end, "vector_end(v) != check_end");
- for (i = 0 ; i < check_end ; ++i)
- {
- if (strcmp(vector_get_item(v, i), expect[i]) != 0)
- {
- assert_true(0, "vector_get_item(v, i) != expected") ;
- break ;
- } ;
- } ;
-
- /* free contents */
+ /* free contents */
for (i = 0; i < vector_end(v); ++i)
- free(vector_slot(v, i));
+ {
+ free(vector_slot(v, i));
+ }
if (rider == 0)
- free(dest_item);
+ {
+ free(dest_item);
+ }
vector_free(v);
}