summaryrefslogtreecommitdiffstats
path: root/lib/vty_io.c
diff options
context:
space:
mode:
authorChris Hall <chris.hall@highwayman.com>2011-07-21 19:53:02 +0100
committerChris Hall <chris.hall@highwayman.com>2011-07-21 19:53:02 +0100
commit56da2a1c9b6361e302b7a39fe2740561a9012d88 (patch)
tree6b6543532133a0c618d0f4ec70a87cf3f96caf30 /lib/vty_io.c
parente535bc959729262480a9702e71334002edee3f8c (diff)
downloadquagga-56da2a1c9b6361e302b7a39fe2740561a9012d88.tar.bz2
quagga-56da2a1c9b6361e302b7a39fe2740561a9012d88.tar.xz
Update pipework and improve memory reporting.
Improve error handling for all new pipework inputs and outputs. Change behaviour of ^C from VTY Terminal, so that will interrupt output and terminate all running pipes -- including running shell commands. In pipe commands, recognise "~/..." and "~user/..." home directory forms. Changed "~/" to mean the usual home for the current user. "~~/" now means the configuration file directory. Introduced "shdir DIR" command to show what is (currently) what. Changed "<|" so that if the command has a path, it is expanded using Quagga's rules (including "~~/" and "~./") and the "here" directory is set to that path. Fixed collection of stderr output from all pipes so that is separate from stdout output, and is always sent to the base output (eg VTY Terminal). Increase amount of information about the heap that "show mem" shows -- particularly if the "memory_tracker" is enabled. Tested and applied resulting fixes.
Diffstat (limited to 'lib/vty_io.c')
-rw-r--r--lib/vty_io.c1560
1 files changed, 917 insertions, 643 deletions
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 ;
+ } ;
} ;
/*==============================================================================