summaryrefslogtreecommitdiffstats
path: root/lib/vty_io.c
diff options
context:
space:
mode:
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 ;
+ } ;
} ;
/*==============================================================================