diff options
Diffstat (limited to 'lib/vty_io.c')
-rw-r--r-- | lib/vty_io.c | 1532 |
1 files changed, 1119 insertions, 413 deletions
diff --git a/lib/vty_io.c b/lib/vty_io.c index 4b50c526..dadfd091 100644 --- a/lib/vty_io.c +++ b/lib/vty_io.c @@ -44,6 +44,7 @@ #include <arpa/telnet.h> #include <sys/un.h> /* for VTYSH */ #include <sys/socket.h> +#include <wait.h> #define VTYSH_DEBUG 0 @@ -88,8 +89,10 @@ uty_out(vty_io vio, const char *format, ...) /* Watch-dog timer. */ static vio_timer_t vty_watch_dog ; -static vty_timer_time uty_watch_dog_bark(vio_timer_t* timer, void* info) ; -static bool uty_death_watch_scan(bool final) ; +static vty_timer_time uty_watch_dog_bark(vio_timer timer, void* info) ; +static void uty_death_watch_scan(bool final) ; + +static vty_io uty_dispose(vty_io vio) ; /*------------------------------------------------------------------------------ * Initialise watch dog -- at start-up time. @@ -97,7 +100,7 @@ static bool uty_death_watch_scan(bool final) ; extern void uty_watch_dog_init(void) { - vio_timer_init(&vty_watch_dog, NULL, NULL) ; /* empty */ + vio_timer_init_new(vty_watch_dog, NULL, NULL) ; /* empty */ } ; /*------------------------------------------------------------------------------ @@ -106,8 +109,8 @@ uty_watch_dog_init(void) extern void uty_watch_dog_start(void) { - vio_timer_init(&vty_watch_dog, uty_watch_dog_bark, NULL) ; - vio_timer_set(&vty_watch_dog, VTY_WATCH_DOG_INTERVAL) ; + vio_timer_init_new(vty_watch_dog, uty_watch_dog_bark, NULL) ; + vio_timer_set(vty_watch_dog, VTY_WATCH_DOG_INTERVAL) ; } ; /*------------------------------------------------------------------------------ @@ -118,7 +121,7 @@ uty_watch_dog_start(void) extern void uty_watch_dog_stop(void) { - vio_timer_reset(&vty_watch_dog) ; + vio_timer_reset(vty_watch_dog, keep_it) ; uty_death_watch_scan(true) ; /* scan the death-watch list */ } @@ -126,7 +129,7 @@ uty_watch_dog_stop(void) * Watch dog vio_timer action */ static vty_timer_time -uty_watch_dog_bark(vio_timer_t* timer, void* info) +uty_watch_dog_bark(vio_timer timer, void* info) { cmd_host_name(true) ; /* check for host name change */ @@ -136,49 +139,40 @@ uty_watch_dog_bark(vio_timer_t* timer, void* info) } ; /*------------------------------------------------------------------------------ - * Scan the death watch list. - * - * A vty can finally be freed if it is closed and there is no command running. + * Process the death watch list -- anything on the list can be disposed of. * - * At curtains the command running state is forced off. + * At curtains... */ -static bool -uty_death_watch_scan(bool curtains) +static void +uty_death_watch_scan(bool final) { - vty_io vio ; - vty_io next ; + VTY_ASSERT_CLI_THREAD() ; - next = vio_death_watch ; - while (next != NULL) + /* Dispose of anything on the death watch list. */ + + while (vio_death_watch != NULL) { - vio = next ; - next = sdl_next(vio, vio_list) ; + vty vty ; + vty_io vio ; - /* If this is curtains, override cmd_running ! */ - if (curtains) - vio->cmd_running = false ; + vio = vio_death_watch ; + vio_death_watch = sdl_next(vio, vio_list) ; /* take off death watch */ - if (uty_close(vio, curtains, NULL)) - { - vty vty = vio->vty ; + vty = vio->vty ; - sdl_del(vio_death_watch, vio, vio_list) ; + vty->vio = uty_dispose(vty->vio) ; + assert(vty->exec == NULL) ; - cmd_exec_free(vty->exec) ; - XFREE(MTYPE_VTY, vty->vio) ; - XFREE(MTYPE_VTY, vty) ; - } ; + XFREE(MTYPE_VTY, vty) ; } ; - - return (vio_death_watch == NULL) ; } ; /*============================================================================== * Prototypes. */ - -static void uty_vf_read_close(vio_vf vf) ; -static bool uty_vf_write_close(vio_vf vf, bool final) ; +static void uty_vout_close_reason(vio_vf vf, const char* reason) ; +static cmd_return_code_t uty_vf_read_close(vio_vf vf, bool final) ; +static cmd_return_code_t uty_vf_write_close(vio_vf vf, bool final) ; static vio_vf uty_vf_free(vio_vf vf) ; /*============================================================================== @@ -191,30 +185,28 @@ static vio_vf uty_vf_free(vio_vf vf) ; * * Caller must complete the initialisation of the vty_io, which means: * - * * constructing a suitable vio_vf and doing uty_vin_open() to set the + * * constructing a suitable vio_vf and doing uty_vin_push() to set the * vin_base. * * All vty_io MUST have a vin_base, even if it is /dev/null. * - * * constructing a suitable vio_vf and doing uty_vout_open() to set the - * vout_base. + * * constructing a suitable vio_vf and doing uty_vout_push() to set the + * vout_base and the vio->obuf. * * All vty_io MUST have a vout_base, even if it is /dev/null. * - * * setting the vio->obuf to the vout_base obuf... TODO ??? + * * setting vio->cli, if required * - * * setting vio->cli, if required. + * * etc. * - * Caller must also set the initial vty->node, if any. + * No "exec" is allocated. That is done when the command loop is entered. */ extern vty -uty_new(vty_type_t type) +uty_new(vty_type_t type, node_type_t node) { vty vty ; vty_io vio ; - bool execution ; - VTY_ASSERT_LOCKED() ; /* Basic allocation */ @@ -224,20 +216,22 @@ uty_new(vty_type_t type) * * type = X -- set to actual type, below * - * node = NULL_NODE -- set to something sensible elsewhere + * node = X -- set to actual node, below * * index = NULL -- nothing, yet * index_sub = NULL -- nothing, yet * - * config = false -- not in configure mode + * config = false -- not owner of configuration symbol of power + * config_brand = 0 -- none, yet * - * execution = X -- set below + * exec = NULL -- execution state set up when required * vio = X -- set below */ confirm(NULL_NODE == 0) ; confirm(QSTRING_INIT_ALL_ZEROS) ; vty->type = type ; + vty->node = node ; vio = XCALLOC(MTYPE_VTY, sizeof(struct vty_io)) ; @@ -251,57 +245,37 @@ uty_new(vty_type_t type) * vin_base = NULL -- empty input stack * vin_depth = 0 -- no stacked vin's, yet * + * real_depth = 0 -- nothing stacked, yet + * * vout = NULL -- empty output stack * vout_base = NULL -- empty output stack * vout_depth = 0 -- no stacked vout's, yet * - * vout_closing = NULL -- nothing closing yet + * depth_mark = 0 -- no stacked vin/vout, yet + * + * err_hard = false -- no error at all, yet + * ebuf = NULL -- no error at all, yet * * vio_list = NULLs -- not on the vio_list, yet * mon_list = NULLs -- not on the monitors list * * blocking = X -- set below: false unless VTY_CONFIG_READ - * cmd_running = false -- no commands running, yet * - * state = X -- set vf_open, below. + * state = vc_null -- not started vty command loop * - * close_reason = NULL -- not closed, yet + * close_reason = NULL -- none set * - * obuf = NULL -- no output buffer, yet + * obuf = NULL -- no output buffer, yet */ + confirm(vc_null == 0) ; vty->vio = vio ; vio->vty = vty ; vio->blocking = (type == VTY_CONFIG_READ) ; - vio->state = vf_open ; - - /* Create and initialise the command execution environment (if any) */ - execution = true ; - - switch(type) - { - case VTY_TERMINAL: - case VTY_SHELL_SERVER: - case VTY_SHELL_CLIENT: - case VTY_CONFIG_READ: - execution = true ; - break ; - - case VTY_STDOUT: - case VTY_STDERR: - execution = false ; - break ; - - default: - zabort("unknown vty type") ; - } ; - - if (execution) - vty->exec = cmd_exec_new(vty) ; /* Place on list of known vio/vty */ - sdl_push(vio_list_base, vio, vio_list) ; + sdl_push(vio_live_list, vio, vio_list) ; return vty; } ; @@ -316,13 +290,13 @@ uty_new(vty_type_t type) * * Sets the read ready action and the read timer timeout action. * - * NB: a uty_cmd_prepare() is required before command processing can continue. + * NB: is usually called from the cli thread, but may be called from the cmd + * thread for vf which is blocking ! * - * That is not done here because the vin state may not be complete (in - * particular the vin->parse_type and vin->reflect_enabled !). + * NB: a uty_cmd_prepare() is required before command processing can continue. */ extern void -uty_vin_open(vty_io vio, vio_vf vf, vio_in_type_t type, +uty_vin_push(vty_io vio, vio_vf vf, vio_in_type_t type, vio_vfd_action* read_action, vio_timer_action* read_timer_action, usize ibuf_size) @@ -330,34 +304,58 @@ uty_vin_open(vty_io vio, vio_vf vf, vio_in_type_t type, vf->vin_type = type ; vf->vin_state = vf_open ; - if (type < VIN_SPECIALS) + assert(type != VIN_NONE) ; + + if ((type < VIN_SPECIALS) && (!vf->blocking)) { vio_vfd_set_read_action(vf->vfd, read_action) ; vio_vfd_set_read_timeout_action(vf->vfd, read_timer_action) ; } ; ssl_push(vio->vin, vf, vin_next) ; + vio->real_depth = ++vio->vin_depth ; + if (vio->vin_base == NULL) { - assert(vio->vin_depth == 0) ; + assert(vio->vin_depth == 1) ; vio->vin_base = vf ; - } - else - { - assert(type != VIN_NONE) ; - ++vio->vin_depth ; } ; if (ibuf_size != 0) { vf->ibuf = vio_fifo_init_new(NULL, ibuf_size) ; - + vf->cl = qs_init_new(NULL, 120) ; vf->line_complete = true ; vf->line_step = 1 ; } ; } ; /*------------------------------------------------------------------------------ + * Save the given context in the current top of the vin stack. + * + * This is done when a new pipe is opened, so that: + * + * a) saves the current context in the current vin (the new pipe has + * not yet been pushed) so that uty_vin_pop() can restore this context + * after closing the then top of the stack. + * + * b) can update context for about to be run vin, eg: + * + * - dir_here -- if required + * + * - can_enable + * + * So the top of the vin stack does not contain the current context, that is + * in the vty->exec ! + */ +extern void +uty_vin_new_context(vty_io vio, cmd_context context, qpath file_here) +{ + assert(vio->vin->context == NULL) ; + vio->vin->context = cmd_context_new_save(context, file_here) ; +} ; + +/*------------------------------------------------------------------------------ * Push a new vf to the vio->vout stack, and set write stuff. * * Sets the vf->vout_type and set vf->write_open. @@ -366,6 +364,15 @@ uty_vin_open(vty_io vio, vio_vf vf, vio_in_type_t type, * * Initialises an output buffer and sets an end_mark. * + * The depth_mark is set to the current vio->depth_mark + 1. This is the + * vin_depth below which the vout should be closed. Before a command line + * is fetched (and hence after the previous command line has completed) the + * vout->depth_mark is checked. If it is > the current vin_depth, then + * the vout is closed before a command line can be fetched. + * + * NB: is usually called from the cli thread, but may be called from the cmd + * thread for vf which is blocking ! + * * NB: VOUT_DEV_NULL, VOUT_STDOUT and VOUT_STDERR are special. * * The write_action and the write_timer_action are ignored. @@ -377,41 +384,41 @@ uty_vin_open(vty_io vio, vio_vf vf, vio_in_type_t type, * if it is later to be discarded. * * NB: a uty_cmd_prepare() is required before command processing can continue. - * - * That is not done here because the vout state may not be complete (in - * particular the vout->out_enabled !). */ extern void -uty_vout_open(vty_io vio, vio_vf vf, vio_out_type_t type, - vio_vfd_action* write_action, - vio_timer_action* write_timer_action, - usize obuf_size) +uty_vout_push(vty_io vio, vio_vf vf, vio_out_type_t type, + vio_vfd_action* write_action, + vio_timer_action* write_timer_action, + usize obuf_size) { + VTY_ASSERT_LOCKED() ; + vf->vout_type = type ; vf->vout_state = vf_open ; - if (type < VOUT_SPECIALS) + assert(type != VOUT_NONE) ; + + if ((type < VOUT_SPECIALS) && (!vf->blocking)) { vio_vfd_set_write_action(vf->vfd, write_action) ; vio_vfd_set_write_timeout_action(vf->vfd, write_timer_action) ; } ; ssl_push(vio->vout, vf, vout_next) ; + ++vio->vout_depth ; + if (vio->vout_base == NULL) { - assert(vio->vout_depth == 0) ; + assert(vio->vout_depth == 1) ; vio->vout_base = vf ; - } - else - { - assert(type != VOUT_NONE) ; - ++vio->vout_depth ; } ; vf->obuf = vio_fifo_init_new(NULL, obuf_size) ; vio_fifo_set_end_mark(vf->obuf) ; - vio->obuf = vio->vout->obuf ; + vf->depth_mark = vio->depth_mark + 1 ; + + vio->obuf = vf->obuf ; } ; /*------------------------------------------------------------------------------ @@ -434,33 +441,6 @@ uty_set_timeout(vty_io vio, vty_timer_time timeout) } ; /*------------------------------------------------------------------------------ - * Set/Clear "monitor" state: - * - * set: if VTY_TERM and not already "monitor" (and write_open !) - * clear: if is "monitor" - */ -extern void -uty_set_monitor(vty_io vio, bool on) -{ - VTY_ASSERT_LOCKED() ; -#if 0 - if (on && !vio->monitor) - { - if ((vio->vty->type == VTY_TERMINAL) && !vio->closing) - { - vio->monitor = 1 ; - sdl_push(vio_monitors_base, vio, mon_list) ; - } ; - } - else if (!on && vio->monitor) - { - vio->monitor = 0 ; - sdl_del(vio_monitors_base, vio, mon_list) ; - } -#endif -} ; - -/*------------------------------------------------------------------------------ * Return "name" of VTY. * * The name of the base vin, or (failing that) the base vout. @@ -478,257 +458,391 @@ uty_get_name(vty_io vio) } ; /*------------------------------------------------------------------------------ - * Close all vin excluding the vin_base. - * - * This is done on close and when there is a command error. - */ -extern void -uty_vin_close_stack(vty_io vio) -{ - while (vio->vin != vio->vin_base) - uty_vin_close(vio) ; -} ; - -/*------------------------------------------------------------------------------ - * Close all vout except for the vout_base. - * - * This is done on close and when there is a command error. - * - * Should only be "final" on a call from the watch-dog ! - */ -extern void -uty_vout_close_stack(vty_io vio, bool final) -{ - while (vio->vout != vio->vout_base) - uty_vout_close(vio, final) ; -} ; - -/*------------------------------------------------------------------------------ - * Close VTY -- may be called any number of times ! + * Close VTY -- final. * - * If no reason for the close has already been set, sets the given reason. - * The close reason is reported at the end of the output to the vout_base. + * Two forms: "curtains" and "not-curtains". * - * Once a VTY has been closed, it will provide no further input. However, until - * "final" close, it will continue to output anything which is pending at the - * time of the close, which includes: (a) anything in the output buffer, - * (b) any further output from a then running command and (c) anything that an - * out_pipe may be in the process of returning. + * At "curtains" the system is being terminated, and all message and event + * handling has stopped. Can assume that a vty is not in any command loop, + * so can be killed off here and now. * - * The main complication is that in a multi-threaded world there may be a - * vio->cmd_running. The close shuts down everything on the input stack, so - * any active command loop will come to a halt as soon as the current command - * does complete. The output stack is preserved, but will be closed on - * completion of the command. + * At "not-curtains" messages and event handling is still running. It is + * possible that a vty is running a command in the cmd_thread, so cannot + * completely close things down -- must leave enough for the command loop + * to continue to work, up to the point that the closing of the vty is + * detected. * - * Closing a VTY places it on the death-watch list. Once any pending output - * has been dealt with and any cmd_running has completed, then the death-watch - * will apply the coup de grace. + * For everything that is closed, uses "final" close, which stops things + * instantly -- will not block, or set any read/write ready, or continue the + * command loop, or take any notice of errors. * - * Getting the death-watch to finally close the VTY also allows the vty to be - * closed from somewhere deep (e.g. when there is an I/O error) without - * destroying the VTY and upsetting code that might not realise the VTY has - * vanished. + * This close is called by: * - * The close sequence is: + * * uty_reset() -- SIGHUP -- !curtains * - * 1. if a close reason has not already been set, set any given one. + * Will close "final" everything except vout_base. * - * 2. if not already closing -- ie this is the first call of uty_close() + * If the command loop is not already stopped, it is signalled to stop + * as soon as possible. When it does it will return here via + * vty_cmd_loop_exit(). * - * a. transfer to death-watch list & set closing. + * If command loop has already stopped, then will proceed to complete + * close -- see below. * - * b. turn off any monitor state + * * uty_reset() -- SIGTERM -- curtains * - * c. close down the input/read side completely. + * Will close "final" everything except vout_base. * - * Empties the vin stack down to vin_base, closing all input. + * The command loop will be set stopped, and will proceed to complete + * close -- see below. * - * Close the vin_base. The vin_base is left !read_open. + * * vty_cmd_loop_exit() -- when the command loop has stopped -- !curtains * - * All read-only vio_vf (except the vin_base) are freed. + * The command loop may have stopped in response to a vc_close_trap, + * which means that have been here before, and the vty is pretty much + * closed already. * - * Note that everything is read closed before anything is write - * closed. + * The command loop may have stopped: * - * A vio_vf is read closed once and only once. + * - because has reached the end of the vin_base input + * - vin_base has timed out + * - there was a command error, on non-interactive vty + * - there was an I/O error * - * The vin_base is closed only by this function. Until the final - * uty_close, there is always at least the vin_base, even if it is - * read_closed. + * In all these cases, the stack will already have been closed final or + * otherwise, except for vout_base, and all output will have been pushed. * - * 3. try to close everything on the vout_closing list. + * vout_base will have been closed, but not "final", so will be sitting + * in vf_closing state. * - * even if cmd_running + * So the vio is ready for complete close. * + * For complete close any remaining vf are closed final, and the close reason + * is output to the vout_base, if any and if possible. * + * The vty is then closed and placed on death watch to be finally reaped. */ -extern bool -uty_close(vty_io vio, bool final, qstring reason) +extern void +uty_close(vty_io vio, const char* reason, bool curtains) { - vio_vf vf_next ; + VTY_ASSERT_CAN_CLOSE(vio->vty) ; - VTY_ASSERT_LOCKED() ; + /* Stamp on any monitor output instantly. */ + uty_set_monitor(vio, off) ; - /* quit if already closed */ - if (vio->state == vf_closed) - return true ; + /* Save the close reason for later, unless one is already set. */ + if ((reason != NULL) && (vio->close_reason == NULL)) + vio->close_reason = XSTRDUP(MTYPE_TMP, reason) ; - /* Set the close reason, if not already set. */ - if (reason != NULL) - { - if (vio->close_reason == NULL) - vio->close_reason = reason ; - else - qs_reset(reason, free_it) ; - } ; + /* Close the command loop -- if not already stopped (or closed !) + * + * If command loop is not already stopped, the if "curtains" will stop it + * instantly, otherwise will signal to the command loop to close, soonest. + */ + uty_cmd_loop_close(vio, curtains) ; - /* If not already closing, set closing and transfer to the death watch - * list -- turn off any "monitor" status immediately. + /* Close all vin including the vin_base. + * + * Note that the vin_base is closed, but is still on the vin stack. */ - if (vio->state == vf_open) - { - vio->state = vf_closing ; + do + uty_vin_pop(vio, true, NULL) ; /* final close, discard context */ + while (vio->vin != vio->vin_base) ; - /* Transfer to the death-watch */ - sdl_del(vio_list_base, vio, vio_list) ; - sdl_push(vio_death_watch, vio, vio_list) ; + /* Close all the vout excluding the vout_base. */ + while (vio->vout != vio->vout_base) + uty_vout_pop(vio, true) ; /* final close */ + + /* If command loop is still running, this is as far as can go. + * + * The command loop will hit the vc_close_trap or execute CMD_CLOSE, and + * that will cause this function to be called again, in vc_stopped state. + * + * Save the close_reason for later. + */ + if ((vio->state == vc_running) || (vio->state == vc_close_trap)) + return ; - /* TODO turn off any "monitor" IMMEDIATELY. */ + /* If the vout_base is not closed, try to output the close reason, + * if any. + */ + if ((vio->close_reason != NULL) && (vio->vout_base->vout_state != vf_closed)) + uty_vout_close_reason(vio->vout_base, vio->close_reason) ; - /* Flush the vin stack. - * - * Where possible this will revoke commands bring command processing to - * as sudden a halt as possible -- though may still be processing - * commands. - */ - uty_vin_close_stack(vio) ; + /* Now final close the vout_base. + * + * Note that the vout_base will be closed, but on the vout stack with an + * empty obuf... just in case TODO ? + */ + uty_vout_pop(vio, true) ; /* final close */ + + assert(vio->obuf == vio->vout_base->obuf) ; + vio_fifo_clear(vio->obuf, true) ; /* and discard any marks */ + + /* All should now be very quiet indeed. */ + if (vty_debug) + { assert(vio->vin == vio->vin_base) ; - uty_vf_read_close(vio->vin) ; + assert(vio->vin_depth == 0) ; + assert(vio->real_depth == 0) ; + + assert(vio->vout == vio->vout_base) ; + assert(vio->vout_depth == 0) ; + + assert(vio->vin->vin_state == vf_closed) ; + assert(vio->vout->vout_state == vf_closed) ; } ; - /* If there is anything on the vout_closing list, give it a shove in case - * that manages to close anything -- which it will do if "final". + /* Can dispose of these now -- leave vin/vout for final disposition */ + vio->vty->exec = cmd_exec_free(vio->vty->exec) ; + vio->ebuf = vio_fifo_reset(vio->ebuf, free_it) ; + + /* Command loop is not running, so can place on death watch for final + * disposition. */ - vf_next = vio->vout_closing ; - while (vf_next != NULL) + if (vio->state == vc_stopped) { - vio_vf vf = vf_next ; - vf_next = ssl_next(vf, vout_next) ; + vio->state = vc_closed ; - uty_vf_write_close(vf, final) ; /* removes from vout_closing if - is now completely closed. */ + sdl_del(vio_live_list, vio, vio_list) ; + sdl_push(vio_death_watch, vio, vio_list) ; } ; - /* If is vio->cmd_running, this is as far as we can go this time. */ - if (vio->cmd_running) - return false ; + assert(vio->state == vc_closed) ; /* thank you and good night */ +} ; - /* Make sure no longer holding the config symbol of power */ - uty_config_unlock(vio->vty, NULL_NODE) ; +/*------------------------------------------------------------------------------ + * Dispose unwanted vty. + * + * Called from deathwatch -- must already be removed from deathwatch list. + */ +static vty_io +uty_dispose(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; - /* Flush the vout stack. - * - * If cannot completely close a vf, places it on the vout_closing list, - * except for the vout_base. - */ - uty_vout_close_stack(vio, final) ; - assert(vio->vout == vio->vout_base) ; - uty_vf_write_close(vio->vout, final) ; + assert(vio->state == vc_closed) ; - /* See if have now successfully closed everything. */ - if ((vio->vout_closing != NULL) || (vio->vout_base->vout_state != vf_closed)) - return false ; /* something is still open */ + /* Stop pointing at vout_base obuf */ + vio->obuf = NULL ; - /* Empty everything out of the vio and return the good news. - * - * NB: retains vio->vty & the vio->vio_list for death-watch. - */ - assert((vio->vin == vio->vin_base) && (vio->vin_depth == 0)) ; + /* Clear out vout and vin (may be the same) */ + assert(vio->vin == vio->vin_base) ; + vio->vin_base = uty_vf_free(vio->vin_base) ; - if (vio->vin != vio->vout) - vio->vin = uty_vf_free(vio->vin) ; - else - vio->vin = NULL ; - vio->vin_base = NULL ; + assert(vio->vout == vio->vout_base) ; + if (vio->vout != vio->vin) + vio->vout_base = uty_vf_free(vio->vout_base) ; - assert((vio->vout == vio->vout_base) && (vio->vout_depth == 0)) ; + vio->vin = NULL ; + vio->vout = NULL ; - vio->vout = uty_vf_free(vio->vout) ; + /* Remainder of contents of the vio */ + vio->ebuf = vio_fifo_reset(vio->ebuf, free_it) ; - vio->close_reason = qs_reset(vio->close_reason, free_it) ; + /* Really cannot be a monitor any more ! */ + assert(!vio->monitor) ; + vio->mbuf = vio_fifo_reset(vio->mbuf, free_it) ; - vio->obuf = NULL ; + XFREE(MTYPE_VTY, vio) ; - vio->state = vf_closed ; - return true ; + return NULL ; } ; /*------------------------------------------------------------------------------ - * Pop and close the top of the vin stack -- MUST NOT be vin_base. + * Close top of the vin stack and pop when done -- see uty_vf_read_close(). * - * Read closes the vio_vfd -- if the vio_vfd was read-only, this will fully - * close it, and the vio_vfd will have been freed. + * If succeeds in closing, unless is vin_base, pops the vin stack and if this + * is read-only will free the vio_vf and all its contents. (So if this is + * vin_base, it is left on the stack, but vf_closed/vf_closing.) * - * If this is read-only will free the vio_vf and all its contents. + * If the given context is not NULL, having popped the vin stack, the context + * in the new top of stack is restored to the given context. * - * NB: a uty_cmd_prepare() is required before command processing can continue. + * On final close, will completely close the input, even if errors occur (and + * no errors are posted). * - * That is not done here because this may be called from outside the - * command loop -- in particular by uty_close(). + * Returns: CMD_SUCCESS -- input completely closed + * CMD_WAITING -- waiting for input to close <=> not-blocking + * (not if final) + * CMD_IO_ERROR -- error or timeout + * + * NB: a uty_cmd_prepare() is required before command processing can continue. */ -extern void -uty_vin_close(vty_io vio) +extern cmd_return_code_t +uty_vin_pop(vty_io vio, bool final, cmd_context context) { - vio_vf vf ; + cmd_return_code_t ret ; + + VTY_ASSERT_LOCKED() ; + + ret = uty_vf_read_close(vio->vin, final) ; + + if ((ret == CMD_SUCCESS) || final) + { + assert(vio->vin->vin_state == vf_closed) ; + + if (vio->vin_depth > 1) + { + vio_vf vf ; - vf = vio->vin ; + vf = ssl_pop(&vf, vio->vin, vin_next) ; + --vio->vin_depth ; - assert(vf != vio->vin_base) ; - assert(vio->vin_depth > 0) ; + if (vf->vout_state == vf_closed) + uty_vf_free(vf) ; + } + else + { + assert(vio->vin == vio->vin_base) ; + vio->vin_depth = 0 ; /* may already have been closed */ + } ; - ssl_del_head(vio->vin, vin_next) ; - --vio->vin_depth ; + if (vio->real_depth > vio->vin_depth) + vio->real_depth = vio->vin_depth ; - uty_vf_read_close(vf) ; + if (vio->vin->context != NULL) + { + if (context != NULL) + vio->vin->context = cmd_context_restore(context, vio->vin->context); + else + vio->vin->context = cmd_context_free(vio->vin->context, false) ; + /* Not a copy */ + } ; + } ; + + return ret ; } ; /*------------------------------------------------------------------------------ - * Pop and close the top of the vout stack -- MUST NOT be vout_base. + * Close top of the vout stack and pop when done -- see uty_vf_write_close(). + * + * If is vout_base, does not completely close, unless is "final" -- if not + * final, CMD_SUCCESS means that the output buffers are empty and the + * vout_depth has been reduced, but the vio_vf is still writeable, held in + * vf_closing state. * - * Moves the vio_vf to the vout_closing list. + * Unless is vout_base, pops the vout stack. If this is write-only will free + * the vio_vf and all its contents. * - * Before closing, push any outstanding output. + * If this is vout_base, does not actually close the vfd and does not close + * the vout side of the vf. This leaves an active vio->obuf (inter alia.) + * + * Before closing, discard anything after end_mark, then push any outstanding + * output. * * Unless "final", the close is soft, that is, if there is any output still * outstanding does not actually close the vout. * * If there is no outstanding output (or if final) will completely close the - * vf and free it. + * vf and free it (except for vout_base). + * + * Returns: CMD_SUCCESS -- input completely closed + * CMD_WAITING -- waiting for input to close <=> not-blocking + * CMD_IO_ERROR -- error or timeout * * NB: a uty_cmd_prepare() is required before command processing can continue. * * That is not done here because this may be called from outside the * command loop -- in particular by uty_close(). + * + * However, ensures that vio->obuf is up to date ! */ -extern void -uty_vout_close(vty_io vio, bool final) +extern cmd_return_code_t +uty_vout_pop(vty_io vio, bool final) +{ + cmd_return_code_t ret ; + + VTY_ASSERT_LOCKED() ; + + ret = uty_vf_write_close(vio->vout, final) ; + + if ((ret == CMD_SUCCESS) || final) + { + if (vio->vout_depth > 1) + { + vio_vf vf ; + + vf = ssl_pop(&vf, vio->vout, vout_next) ; + --vio->vout_depth ; + + uty_vf_free(vf) ; + } + else + { + assert(vio->vout == vio->vout_base) ; + if (final) + assert(vio->vout->vout_state == vf_closed) ; + + vio->vout_depth = 0 ; /* may already have been closed */ + + assert(vio->vin_depth == 0) ; + vio->vout->depth_mark = 0 ; /* align with the end stop */ + } ; + + vio->obuf = vio->vout->obuf ; + } ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ + * Try to output the close reason to the vout_base. + * + * This is the final act for the vout_base. Will attempt to output unless + * the thing is completely closed. + * + * Should by now not be any buffered output -- but if there is, that is + * discarded before the close reason is output. + * + * Any actual output may be done when the vout_base is finally closed. + */ +static void +uty_vout_close_reason(vio_vf vf, const char* reason) { - vio_vf vf ; + if ((vf->vout_state == vf_closed) || (reason == NULL) || (*reason == '\0')) + return ; + + vio_fifo_clear(vf->obuf, true) ; /* clear any markers, too */ + + if (vf->vout_type < VOUT_SPECIALS) + assert(vf->vfd != NULL) ; /* make sure */ + + switch(vf->vout_type) + { + case VOUT_NONE: + zabort("invalid VOUT_NONE") ; + break ; - vf = vio->vout ; + case VOUT_TERM: + uty_term_close_reason(vf, reason) ; + break ; - assert(vf != vio->vout_base) ; - assert(vio->vout_depth > 0) ; + case VOUT_VTYSH: + break ; + + case VOUT_FILE: + case VOUT_PIPE: + case VOUT_SHELL_ONLY: + break ; + + case VOUT_CONFIG: + break ; - ssl_del_head(vio->vout, vout_next) ; - --vio->vout_depth ; + case VOUT_DEV_NULL: + break ; - ssl_push(vio->vout_closing, vf, vout_next) ; + case VOUT_STDOUT: + fprintf(stdout, "%% %s\n", reason) ; + break ; - vio->obuf = vio->vout->obuf ; + case VOUT_STDERR: + fprintf(stderr, "%% %s\n", reason) ; + break ; - uty_vf_write_close(vf, final) ; + default: + zabort("unknown VOUT type") ; + } ; } ; /*============================================================================== @@ -742,7 +856,7 @@ uty_vout_close(vty_io vio, bool final) * * This leaves most things unset/NULL/false. Caller will need to: * - * - uty_vin_open() and/or uty_vout_open() + * - uty_vin_push() and/or uty_vout_push() * * - once those are done, the following optional items remain to be set * if they are required: @@ -750,23 +864,30 @@ uty_vout_close(vty_io vio, bool final) * read_timeout -- default = 0 => no timeout * write_timeout -- default = 0 => no timeout * - * parse_type -- default = cmd_parse_standard - * reflect_enabled -- default = false - * out_enabled -- default = true iff vfd_io_write - * - * If vio->blocking, adds vfd_io_blocking to the io_type. + * - for pipes the child state needs to be set, and for out pipes the return + * state needs to be set in this vio_vf (the master) and in the next + * vout (the slave). * * NB: if there is no fd for this vio_vf, it should be set to -1, and the - * type (recommend vfd_none) and io_type are ignored -- except for - * vfd_io_write and the out_enabled state. + * type (recommend vfd_none) and io_type are ignored. * * A VTY_STDOUT or a VTY_STDERR (output only, to the standard I/O) can - * be set up without an fd, and with/without out_enabled. + * be set up without an fd. * * A VTY_CONFIG_READ can be set up with the fd of the input file, but * MUST be type = vfd_file and io_type = vfd_io_read -- because the fd - * is for input only. This will set out_enabled false (as required). - * The required VOUT_STDERR will be set by uty_vout_open(). + * is for input only. The required VOUT_STDERR will be set by + * uty_vout_push(). + * + * NB: if the parent vio is blocking, then the vf will be blocking, and so + * will any vfd. An individual vf may be set blocking by setting + * vfd_io_blocking in the io_type. + * + * The vty stuff opens all files, pipes etc. non-blocking. A non-blocking + * vf simulates blocking by local pselect. As far as the vfd level is + * concerned, once the file, pipe etc. is open, there is no difference + * between blocking and non-blocking except that non-blocking vfd are not + * allowed to set read/write ready. */ extern vio_vf uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type, @@ -776,9 +897,6 @@ uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type, VTY_ASSERT_LOCKED() ; - if (vio->blocking) - io_type |= vfd_io_blocking ; - vf = XCALLOC (MTYPE_VTY, sizeof(struct vio_vf)) ; /* Zeroising the structure has set: @@ -786,49 +904,61 @@ uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type, * vio = X -- set below * name = X -- set below * - * vin_type = VIN_NONE -- see uty_vin_open() - * vin_state = vf_closed -- see uty_vin_open() - * vin_next = NULL -- see uty_vin_open() + * vin_type = VIN_NONE -- see uty_vin_push() + * vin_state = vf_closed -- see uty_vin_push() + * vin_next = NULL -- see uty_vin_push() * - * cli = NULL -- no CLI, yet + * context = NULL -- see uty_vin_new_context() * - * ibuf = NULL -- none, yet -- see uty_vin_open() + * cli = NULL -- no CLI, yet * - * cl = zeros -- empty qstring (embedded) - * line_complete = false -- see uty_vout_open() + * ibuf = NULL -- none, yet -- see uty_vin_push() + * cl = NULL -- none, yet -- see uty_vin_push() + * line_complete = false -- see uty_vin_push() * line_number = 0 -- nothing yet - * line_step = 0 -- see uty_vout_open() + * line_step = 0 -- see uty_vin_push() * - * parse_type = cmd_parse_standard - * reflect_enabled = false + * vout_type = VOUT_NONE -- see uty_vout_push() + * vout_state = vf_closed -- see uty_vout_push() + * vout_next = NULL -- see uty_vout_push() * - * vout_type = VOUT_NONE -- see uty_vout_open() - * vout_state = vf_closed -- see uty_vout_open() - * vout_next = NULL -- see uty_vout_open() + * pr_master = NULL -- none -- see uty_pipe_write_open() * - * obuf = NULL -- none, yet -- see uty_vout_open() + * obuf = NULL -- none -- see uty_vout_push() * - * out_enabled = X -- set below: true iff vfd_io_write + * depth_mark = 0 -- see uty_vout_push() * * vfd = NULL -- no vfd, yet * - * blocking = false -- default is non-blocking I/O - * closing = false -- definitely not + * blocking = X -- see below + * closing = false -- not on the closing list, yet. * * error_seen = 0 -- no error seen, yet * - * read_on = false - * write_on = false - * * read_timeout = 0 -- none * write_timeout = 0 -- none + * + * child = 0 -- none ) + * terminated = false -- not ) -- see uty_pipe_read/write_open() + * term_status = X -- none ) + * + * pr_state = vf_closed -- no pipe return vfd + * -- see uty_pipe_read/write_open() + * pr_vfd = NULL -- no vfd -- see uty_pipe_read/write_open() + * + * pr_slave = NULL -- none -- see uty_pipe_read/write_open() + * + * pr_only = false -- see uty_pipe_read/write_open() */ confirm((VIN_NONE == 0) && (VOUT_NONE == 0)) ; confirm(vf_closed == 0) ; confirm(QSTRING_INIT_ALL_ZEROS) ; - confirm(cmd_parse_standard == 0) ; - vf->vio = vio ; + if (vio->blocking) + io_type |= vfd_io_blocking ; /* inherit blocking state */ + + vf->vio = vio ; + vf->blocking = (io_type & vfd_io_blocking) != 0 ; if (name != NULL) vf->name = XSTRDUP(MTYPE_VTY_NAME, name) ; @@ -836,35 +966,58 @@ uty_vf_new(vty_io vio, const char* name, int fd, vfd_type_t type, if (fd >= 0) vf->vfd = vio_vfd_new(fd, type, io_type, vf) ; - vf->out_enabled = ((io_type & vfd_io_write) != 0) ; - return vf ; } ; /*------------------------------------------------------------------------------ - * Close the read side of the given vio_vf. + * Close the read side of the given vio_vf -- if can. * * Read closes the vio_vfd -- if the vio_vfd was read-only, this will fully * close it, and the vio_vfd will have been freed. * - * If this is read-only (ie vout_type == VOUT_NONE) will free the vio_vf as - * well as the vio_vfd -- unless this is the vin_base. + * For not-final close, if there is any other related I/O, then waits until + * that has completed. For example, with a VIN_PIPE: * - * Note that read_close is effective immediately, there is no delay as there is - * with write_close -- so no need for a "final" option. + * * waits for any return input to complete, and pushes it to the relevant + * vout. + * + * * waits to collect the child, and its termination state. + * + * This means that a not-final close may return errors. For not-blocking vf + * may return waiting state. For blocking vf, may block and may later return + * timeout error. + * + * For a final close, if there is any related I/O then will attempt to complete + * it -- but will give up if would block. I/O errors on final close are + * ignored. A final close may be called in terminating state, so does not + * do any "vty_cmd_signal". + * + * Returns: CMD_SUCCESS -- is all closed + * CMD_WAITING -- cannot close at the moment <=> non-blocking + * (not if "final") + * CMD_IO_ERROR -- something went wrong + * + * NB: on "final" close returns CMD_SUCCESS no matter what happened, and all + * input will have been closed down, and the vf closed. */ -static void -uty_vf_read_close(vio_vf vf) +static cmd_return_code_t +uty_vf_read_close(vio_vf vf, bool final) { - assert(vf->vin_state != vf_closed) ; + cmd_return_code_t ret ; + + VTY_ASSERT_CAN_CLOSE_VF(vf) ; + + ret = CMD_SUCCESS ; + + if (vf->vin_state == vf_closed) + return ret ; /* quit if already closed */ + + vf->vin_state = vf_closing ; /* TODO wipes out error etc ? */ /* Do the vfd level read close and mark the vf no longer read_open */ if (vf->vin_type < VIN_SPECIALS) vf->vfd = vio_vfd_read_close(vf->vfd) ; - vf->vin_state = vf_closed ; - vf->read_on = off ; - /* Now the vin_type specific clean up. */ switch(vf->vin_type) { @@ -873,60 +1026,126 @@ uty_vf_read_close(vio_vf vf) break ; case VIN_TERM: - uty_term_read_close(vf) ; + ret = uty_term_read_close(vf, final) ; break ; case VIN_VTYSH: + zabort("tba VIN_VTYSH") ; + break ; + + case VIN_CONFIG: + ret = uty_config_read_close(vf, final) ; break ; case VIN_FILE: - uty_file_read_close(vf) ; + ret = uty_file_read_close(vf, final) ; break ; case VIN_PIPE: - break ; - - case VIN_CONFIG: + ret = uty_pipe_read_close(vf, final) ; break ; case VIN_DEV_NULL: + ret = CMD_SUCCESS ; break ; default: zabort("unknown VIN type") ; } ; - if ((vf->vout_type == VOUT_NONE) && (vf != vf->vio->vin_base)) - uty_vf_free(vf) ; + if ((ret == CMD_SUCCESS) || final) + { + vf->vin_state = vf_closed ; + assert(vf->pr_state == vf_closed) ; + } ; + + return ret ; } ; /*------------------------------------------------------------------------------ * Close the write side of the given vio_vf, if can. * - * Pushes any outstanding output -- non-blocking if not a blocking vty. + * Discards anything beyond the current end_mark, and clears the end_mark. + * + * Pushes any outstanding output, and if is pipe will attempt to collect + * the child. On not-final close, if cannot complete everything, will return + * CMD_WAITING for non-blocking, or block (and may time out). On final close, + * will do as much as possible without blocking, and will then close even if + * there is outstanding output or child has not been collected. + * + * For not-final close, for example, with a VOUT_PIPE: * - * The vio_vf MUST either be on the vout_closing list or be vout_base. - * The vio_vf MUST be write_open and MUST NOT be read_open. + * * waits for any return input to complete, and pushes it to the relevant + * vout. * - * If output is empty, or if final, close the vf, then if it is on the - * vout_closing list, remove and free it. + * * waits to collect the child, and its termination state. * - * Returns whether output was empty or not. + * This means that a not-final close may return errors. + * + * For a final close, if there is any related I/O then will attempt to complete + * it -- but will give up if would block. I/O errors on final close are + * ignored. A final close may be called in terminating state, so does not + * do any "vty_cmd_signal". + * + * If this is the vout_base, unless "final", does NOT actually close the vf or + * the vfd -- so the vout_base will still work -- but is marked vf_closing ! + * The effect is, essentially, to try to empty out any buffers, but not to + * do anything that would prevent further output. This is used so that a + * command loop can close the vout_base in the usual way, waiting until all + * output is flushed, but when uty_close() is finally called, it can output + * any close reason there is to hand. + * + * Returns: CMD_SUCCESS -- is all closed + * CMD_WAITING -- cannot close at the moment <=> non-blocking + * (not if "final") + * CMD_IO_ERROR -- something went wrong + * + * NB: on "final" all output will have been closed down, and the vfd closed, + * no matter what the return value says. + * + * NB: must not have open vins at this or a higher level in the stack. + * + * NB: does not at this stage discard the obuf. */ -static bool +static cmd_return_code_t uty_vf_write_close(vio_vf vf, bool final) { - bool empty ; + cmd_return_code_t ret ; + bool base ; - assert((vf->vout_state != vf_closed) && (vf->vin_state == vf_closed)) ; + VTY_ASSERT_CAN_CLOSE_VF(vf) ; - /* Worry about remaining return input from vout pipe TODO */ + ret = CMD_SUCCESS ; - /* Worry about appending the close reason to the vout_base TODO */ + if (vf->vout_state == vf_closed) + return ret ; /* quit if already closed */ - /* Now the vout_type specific clean up. */ - empty = false ; + vf->vout_state = vf_closing ; /* TODO wipes out error etc ? */ + base = (vf == vf->vio->vout_base) ; + + /* Must close vin before closing vout at the same level. + * + * Note that cannot currently be a pipe return slave, because if was + * slave to a VOUT_PIPE/VOUT_SHELL_ONLY that vout must have been closed + * already, and if was slave to a VIN_PIPE, then that too will have been + * closed already (because of the above). + */ + assert( (vf->vio->vin_depth < vf->vio->vout->depth_mark) + || (vf->vio->vout_depth ==0) ) ; + assert(vf->pr_master == NULL) ; + + /* If there is anything in the obuf beyond the end_mark, then it is + * assumed to be surplus to requirements, and we clear the end_mark. + */ + vio_fifo_back_to_end_mark(vf->obuf, false) ; + + /* The vout_type specific close functions will attempt to write + * everything away. + * + * If "final", will only keep going until blocks -- at which point will + * bring everything to a shuddering halt. + */ switch(vf->vout_type) { case VOUT_NONE: @@ -934,50 +1153,54 @@ uty_vf_write_close(vio_vf vf, bool final) break ; case VOUT_TERM: - empty = uty_term_write_close(vf, final) ; + ret = uty_term_write_close(vf, final, base) ; break ; case VOUT_VTYSH: break ; case VOUT_FILE: - empty = uty_file_write_close(vf, final) ; + ret = uty_file_write_close(vf, final, base) ; break ; case VOUT_PIPE: + case VOUT_SHELL_ONLY: + ret = uty_pipe_write_close(vf, final, base, + vf->vout_type == VOUT_SHELL_ONLY) ; break ; case VOUT_CONFIG: + ret = uty_file_write_close(vf, final, base) ; /* treat as file */ break ; case VOUT_DEV_NULL: case VOUT_STDOUT: case VOUT_STDERR: - empty = true ; + ret = CMD_SUCCESS ; break ; default: zabort("unknown VOUT type") ; } ; - if (empty || final) + assert(vf->vin_state == vf_closed) ; + + if (((ret == CMD_SUCCESS) && !base) || final) { - /* Do the vfd level close and mark the vf no longer write_open */ + + assert(vf->vio->obuf == vf->obuf) ; + assert(vio_fifo_empty(vf->obuf)) ; + + if (vf->vout_type < VOUT_SPECIALS) vf->vfd = vio_vfd_close(vf->vfd) ; else assert(vf->vfd == NULL) ; vf->vout_state = vf_closed ; - vf->write_on = off ; - - if (ssl_del(vf->vio->vout_closing, vf, vout_next)) - uty_vf_free(vf) ; - else - assert(vf == vf->vio->vout_base) ; } ; - return empty ; + return ret ; } ; /*------------------------------------------------------------------------------ @@ -992,27 +1215,27 @@ uty_vf_write_close(vio_vf vf, bool final) static vio_vf uty_vf_free(vio_vf vf) { + assert((vf->vin_state == vf_closed) && (vf->vout_state == vf_closed) + && (vf->pr_state == vf_closed)) ; + XFREE(MTYPE_VTY_NAME, vf->name) ; - vf->ibuf = vio_fifo_reset(vf->ibuf, free_it) ; - vf->obuf = vio_fifo_reset(vf->obuf, free_it) ; + assert(vf->cli == NULL) ; + + vf->ibuf = vio_fifo_reset(vf->ibuf, free_it) ; + vf->cl = qs_reset(vf->cl, free_it) ; + vf->obuf = vio_fifo_reset(vf->obuf, free_it) ; - qs_reset(vf->cl, keep_it) ; + vf->context = cmd_context_free(vf->context, false) ; /* not a copy */ - vf->cli = uty_cli_close(vf->cli, true) ; - vf->vfd = vio_vfd_close(vf->vfd) ; /* for completeness */ + vf->vfd = vio_vfd_close(vf->vfd) ; /* for completeness */ + vf->pr_vfd = vio_vfd_close(vf->pr_vfd) ; /* for completeness */ XFREE(MTYPE_VTY, vf) ; return NULL ; } ; -/*------------------------------------------------------------------------------ - * - * - */ - - @@ -1032,7 +1255,7 @@ uty_vf_error(vio_vf vf, const char* what, int err) VTY_ASSERT_CLI_THREAD() ; /* can no longer be a monitor ! *before* any logging ! */ - uty_set_monitor(vio, 0) ; + uty_set_monitor(vio, off) ; /* if this is the first error, log it */ if (vf->error_seen == 0) @@ -1041,13 +1264,16 @@ uty_vf_error(vio_vf vf, const char* what, int err) vf->error_seen = err ; - uzlog(NULL, LOG_WARNING, "%s: %s failed on fd %d: %s", + zlog(NULL, LOG_WARNING, "%s: %s failed on fd %d: %s", type, what, vio_vfd_fd(vf->vfd), errtoa(err, 0).str) ; } ; return -1 ; } ; + + + /*------------------------------------------------------------------------------ * Set required read ready state. Applies the current read timeout. * @@ -1056,31 +1282,31 @@ uty_vf_error(vio_vf vf, const char* what, int err) * Does nothing if: vf->vin_state == vf_closed * or: vf->vfd == NULL * - * Returns new state of vf->read_on + * NB: must NOT be a "blocking" vf */ -extern on_off_b +extern void uty_vf_set_read(vio_vf vf, on_off_b how) { if (vf->vin_state != vf_open) how = off ; - return vf->read_on = (vf->vin_state != vf_closed) - ? vio_vfd_set_read(vf->vfd, how, vf->read_timeout) - : off ; + if (vf->vin_state != vf_closed) + vio_vfd_set_read(vf->vfd, how, vf->read_timeout) ; } ; /*------------------------------------------------------------------------------ - * Set required read ready timeout -- if already read_on, restart it. + * Set required read ready timeout -- if already read on, restart it. * - * Returns new state of vf->read_on + * If this is a blocking vf, will set the timeout value, but since can never + * be "read on", will never attempt to restart any timer ! */ -extern on_off_b +extern void uty_vf_set_read_timeout(vio_vf vf, vty_timer_time read_timeout) { vf->read_timeout = read_timeout ; - return vf->read_on ? uty_vf_set_read(vf, on) - : off ; + if (!vf->blocking) + uty_vf_set_read(vf, on) ; } ; /*------------------------------------------------------------------------------ @@ -1091,31 +1317,511 @@ uty_vf_set_read_timeout(vio_vf vf, vty_timer_time read_timeout) * Does nothing if: vf->vout_state == vf_closed * or: vf->vfd == NULL * - * Returns new state of vf->write_on + * NB: must NOT be a "blocking" vf */ -extern on_off_b +extern void uty_vf_set_write(vio_vf vf, on_off_b how) { - if (vf->vout_state != vf_open) + if ((vf->vout_state != vf_open) && (vf->vout_state != vf_closing)) how = off ; - return vf->write_on = (vf->vout_state != vf_closed) - ? vio_vfd_set_write(vf->vfd, how, vf->write_timeout) - : off ; + if (vf->vout_state != vf_closed) + vio_vfd_set_write(vf->vfd, how, vf->write_timeout) ; } ; /*------------------------------------------------------------------------------ - * Set required write ready timeout -- if already write_on, restart it. + * Set required write ready timeout -- if already write on, restart it. * - * Returns new state of vf->write_on + * If this is a blocking vf, will set the timeout value, but since can never + * be "write on", will never attempt to restart any timer ! */ -extern on_off_b +extern void uty_vf_set_write_timeout(vio_vf vf, vty_timer_time write_timeout) { vf->write_timeout = write_timeout ; - return vf->write_on ? uty_vf_set_write(vf, on) - : off ; + if (!vf->blocking) + uty_vf_set_write(vf, on) ; +} ; + +/*============================================================================== + * Child care. + * + * Management of vio_child objects and the vio_childer_list. + * + * When child is registered it is placed on the vio_childer_list. It remains + * there until it is "collected", that is until a waitpid() has returned a + * "report" for the child. + * + * When a parent waits for a child to be collected, it may be in one of three + * states: + * + * TODO + * + * When a child is collected, or becomes overdue, the parent is signalled (if + * required) and the child->awaited state is cleared. + * + * When a parent "dismisses" a child, if it has not yet been collected it is + * "smacked" -- kill(SIGTERM) -- but kept on the register until it is + * collected. When a child which has been collected is dismissed it is freed. + * + * At "curtains" the register may contain children which have been dismissed, + * but not yet collected. Should NOT contain any children with living parents. + * All children remaining on the register are smacked, and all with no living + * parents are freed. (This could leave children on the register, but avoids + * the possibility of a dangling reference from a parent.) + */ +static void uty_child_collected(vio_child child, int report) ; +static vty_timer_time vty_child_overdue(vio_timer timer, void* action_info) ; +static void uty_child_signal_parent(vio_child child) ; +static vio_child uty_child_free(vio_child child) ; + +/*------------------------------------------------------------------------------ + * Set vty_child_signal_nexus() -- if required. + * + * Note that this is set/cleared under VTY_LOCK() and its own mutex. This + * allows it to be read under its own mutex and/or VTY_LOCK(). + */ +extern void +uty_child_signal_nexus_set(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + assert(vio->blocking) ; + + if (!vty_is_cli_thread()) + { + qpt_mutex_lock(vty_child_signal_mutex) ; + + vty_child_signal_nexus = qpn_find_self() ; + + qpt_mutex_unlock(vty_child_signal_mutex) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * If there is a nexus to signal, clear the indicator and signal the + * associated thread. + */ +extern void +vty_child_signal_nexus_signal(void) +{ + qpt_mutex_lock(vty_child_signal_mutex) ; + + if (vty_child_signal_nexus != NULL) + qpt_thread_signal(vty_child_signal_nexus->thread_id, + vty_child_signal_nexus->pselect_signal) ; + + qpt_mutex_unlock(vty_child_signal_mutex) ; +} + +/*------------------------------------------------------------------------------ + * Set vty_child_signal_nexus() -- if required. + * + * Note that this is set/cleared under VTY_LOCK() and its own mutex. This + * allows it to be read under its own mutex and/or VTY_LOCK(). + */ +extern void +uty_child_signal_nexus_clear(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + assert(vio->blocking) ; + + if (!vty_is_cli_thread()) + { + qpt_mutex_lock(vty_child_signal_mutex) ; + + vty_child_signal_nexus = NULL ; + + qpt_mutex_unlock(vty_child_signal_mutex) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * New child. + * + * NB: must register child promptly (under VTY_LOCK, at same time as fork) + * in order to avoid missing the SIG_CHLD ! + */ +extern vio_child +uty_child_register(pid_t pid, vio_vf parent) +{ + vio_child child ; + + VTY_ASSERT_LOCKED() ; + + child = XCALLOC(MTYPE_VTY, sizeof(struct vio_child)) ; + + /* Zeroising has set: + * + * list -- NULLs -- added to vio_childer_list, below + * + * parent -- NULL -- parent vf set, below + * + * pid -- X -- child pid set below + * collected -- false -- not yet collected + * report -- X -- not relevant until collected + * + * awaited -- false -- not waiting for child + * overdue -- false -- child not overdue + * timer -- NULL -- no waiting timer set + */ + + child->parent = parent ; + child->pid = pid ; + + sdl_push(vio_childer_list, child, list) ; + + return child ; +} ; + +/*------------------------------------------------------------------------------ + * Set waiting for child to be collected. + * + * This is for !vf->blocking: set timer and leave, waiting for SIGCHLD event. + */ +extern void +uty_child_awaited(vio_child child, vty_timer_time timeout) +{ + VTY_ASSERT_CLI_THREAD_LOCKED() ; + + assert(child->parent != NULL) ; + assert(!child->parent->blocking) ; + + child->awaited = true ; + + if (child->timer == NULL) + child->timer = vio_timer_init_new(NULL, vty_child_overdue, child) ; + + vio_timer_set(child->timer, timeout) ; +} ; + +/*------------------------------------------------------------------------------ + * See if parent can collect child -- directly. + * + * This is for vf->blocking, + * + * If can, will collect now -- marking collected and taking off the + * vio_childer_list. + * + * If cannot immediately collect, if final mark overdue and return. + * + * Otherwise wait for up to timeout seconds for a suitable SIGCHLD or related + * wake-up signal. + * + * NB: this blocks with the VTY_LOCK() in its hands !! But this is only + * required for configuration file reading... and timeout is limited. + * + * Returns: true <=> collected + * false => timed out or final + */ +extern bool +uty_child_collect(vio_child child, vty_timer_time timeout, bool final) +{ + bool first ; + + VTY_ASSERT_LOCKED() ; + + assert(child->parent != NULL) ; + assert(child->parent->blocking) ; + assert(child->timer == NULL) ; + + assert(child->pid > 0) ; + + first = true ; + while (1) + { + pid_t pid ; + int report ; + + qps_mini_t qm ; + sigset_t* sig_mask = NULL ; + + if (child->collected) + return true ; /* have collected */ + + pid = waitpid(child->pid, &report, WNOHANG) ; + + if (pid == child->pid) + { + /* Collected the child */ + uty_child_collected(child, report) ; + + return true ; /* have collected */ + } ; + + if (pid != 0) + { + int err = 0 ; + + /* With the exception of EINTR, all other returns are, essentially, + * impossible... + * + * (1) ECHLD means that the given pid is not a child... + * ...which is impossible if not collected -- but treat as + * "overdue". + * + * (2) only other known error is EINVAL -- invalid options... + * absolutely impossible. + * + * (3) some pid other than the given child is an invalid response ! + */ + if (pid < 0) + { + if (errno == EINTR) + continue ; + + err = errno ; + + zlog_err("waitpid(%d) returned %s", child->pid, + errtoa(err, 0).str) ; + } + else + zlog_err("waitpid(%d) returned pid=%d", child->pid, pid) ; + + if (err != ECHILD) + zabort("impossible return from waitpid()") ; + + final = true ; /* treat ECHLD as last straw ! */ + } ; + + /* Waiting for child. */ + if (final) + { + child->overdue = true ; + return false ; /* overdue */ + } ; + + /* Need to wait -- if this is the first time through, prepare for + * that. + */ + if (first) + { + qps_mini_set(qm, -1, 0, 6) ; + + if (vty_is_cli_thread()) + sig_mask = NULL ; + else + sig_mask = vty_child_signal_nexus->pselect_mask ; + + first = false ; + } ; + + /* Wait on pselect. */ + if (qps_mini_wait(qm, sig_mask, true) == 0) + final = true ; /* timed out => now final */ + } ; +} ; + +/*------------------------------------------------------------------------------ + * Dismiss child -- if not collected, smack but leave to be collected in + * due course (or swept up at "curtains"). + */ +extern vio_child +uty_child_dismiss(vio_child child, bool final) +{ + VTY_ASSERT_LOCKED() ; + + if (child != NULL) + { + if (!child->collected) + { + assert(child->pid > 0) ; + + kill(child->pid, SIGKILL) ; /* hasten the end */ + + if (final) + { + assert(child->parent == NULL) ; + sdl_del(vio_childer_list, child, list) ; + child->collected = true ; /* forceably */ + } ; + + child->overdue = true ; /* too late for parent */ + } ; + + child->parent = NULL ; /* orphan from now on */ + child->awaited = false ; /* nobody waiting */ + + if (child->collected) + uty_child_free(child) ; + } ; + + return NULL ; +} ; + +/*------------------------------------------------------------------------------ + * At "curtains" -- empty out anything left in the child register. + * + * The only children that can be left are dismissed children that have yet to + * be collected. + */ +extern void +vty_child_close_register(void) +{ + while (vio_childer_list != NULL) + uty_child_dismiss(vio_childer_list, true) ; /* final */ +} ; + +/*------------------------------------------------------------------------------ + * See whether any children are ready for collection, and check each one + * against the register. + * + * Perform waitpid( , , WNOHANG) until no child is returned, and process + * each one against the register. + * + * The is done when a SIGCHLD is route through the event mechanism. + * + * If another SIGCHLD occurs while this is being done, that will later cause + * another call of this function -- at worst it may find that the child in + * question has already been collected. + * + * This is also done when about to block waiting for a child. + * + * Set any children that can be collected, collected and signal to any parents + * that their children are now ready. + */ +extern void +uty_sigchld(void) +{ + VTY_ASSERT_CLI_THREAD_LOCKED() ; + + while (1) + { + vio_child child ; + pid_t pid ; + int report ; + + pid = waitpid(-1, &report, WNOHANG) ; + + if (pid == 0) + break ; + + if (pid < 0) + { + if (errno == EINTR) + continue ; /* loop on "Interrupted" */ + + if (errno != ECHILD) /* returns ECHLD if no children */ + zlog_err("waitpid(-1) returned %s", errtoa(errno, 0).str) ; + + break ; + } ; + + child = vio_childer_list ; + while (1) + { + if (child == NULL) + { + zlog_err("waitpid(-1) returned pid %d, which is not registered", + pid) ; + break ; + } ; + + if (child->pid == pid) + { + /* Have collected child. + * + * Remove from the vio_childer_list, set collected flag. + * + * We can leave any timer object -- if it goes off it will be + * ignored, because the child is no longer awaited. Timer will + * be discarded when the child is dismissed/freed. + * + * If no parent, can free child object now. + */ + uty_child_collected(child, report) ; + + if (child->parent == NULL) + uty_child_free(child) ; + else if (child->awaited) + uty_child_signal_parent(child) ; + + break ; + } ; + + child = sdl_next(child, list) ; + } ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Set the child collected and set the report. + * + * Remove from the vio_childer_list -- is now either back in the hands of the + * parent, or ready to be freed. + */ +static void +uty_child_collected(vio_child child, int report) +{ + assert(!child->collected) ; /* can only collect once */ + + sdl_del(vio_childer_list, child, list) ; + + child->collected = true ; + child->report = report ; +} ; + +/*------------------------------------------------------------------------------ + * Set child as overdue -- vio_timer action routine. + * + * NB: the timer may go off after the child has been collected, but before the + * parent has got round to stopping the timer. + */ +static vty_timer_time +vty_child_overdue(vio_timer timer, void* action_info) +{ + vio_child child ; + + VTY_LOCK() ; + + child = action_info ; + assert(timer == child->timer) ; + + if (child->awaited) + { + child->overdue = true ; + uty_child_signal_parent(child) ; + } ; + + VTY_UNLOCK() ; + + return 0 ; /* stop timer */ +} ; + +/*------------------------------------------------------------------------------ + * Signal that child is ready -- collected or overdue. + * + * Must be "awaited" -- so not "blocking" + */ +static void +uty_child_signal_parent(vio_child child) +{ + assert(child->awaited && (child->parent != NULL)) ; + + assert(!child->parent->vio->blocking) ; + + child->awaited = false ; + uty_cmd_signal(child->parent->vio, CMD_SUCCESS) ; +} ; + +/*------------------------------------------------------------------------------ + * Free the child -- caller must ensure that any parent has disowned the child, + * and that it is collected (so not on the vio_childer_list). + */ +static vio_child +uty_child_free(vio_child child) +{ + if (child != NULL) + { + assert(child->collected && (child->parent == NULL)) ; + + child->timer = vio_timer_reset(child->timer, free_it) ; + XFREE(MTYPE_VTY, child) ; /* sets child = NULL */ + } ; + + return child ; } ; /*============================================================================== |