diff options
Diffstat (limited to 'lib/vty_command.c')
-rw-r--r-- | lib/vty_command.c | 1624 |
1 files changed, 1037 insertions, 587 deletions
diff --git a/lib/vty_command.c b/lib/vty_command.c index a63f6f0c..d195f193 100644 --- a/lib/vty_command.c +++ b/lib/vty_command.c @@ -39,63 +39,97 @@ #include "qstring.h" /*============================================================================== - * Variables etc. + * vty_command.c contains functions used by the command processing, where + * that interacts with the vty -- in particular vty I/O. + * + * There are two command loops -- cmd_read_config() and cq_process(). Each + * command loop appears to be a thread of control sucking in command lines, + * parsing and executing them. In the process, input and output pipes are + * opened and closed, and the vty stack grows and shrinks. + * + * For cmd_read_config() -- see command_execute.c -- the command loop is, + * indeed, a thread of control and all the I/O operations are "blocking", so + * that any waiting required is done inside the loop. + * + * For cq_process() -- see command_queue.c -- things are a lot more + * complicated: first, I/O is non-blocking, so I/O operations may return + * CMD_WAITING and the command loop must exit and be able to restart, when + * the I/O completes in the background; second, in a multi-pthread environment + * some operations must be performed in the CLI pthread while others must be + * performed in the command (the Routing Engine, generally) pthread. (It would + * be easier if each cq_process instance were a separate pthread, but we here + * implement it as a form of co-routine, driven by messages.) + * + * The I/O is structured so that all output either completes before the I/O + * operation returns, or will be autonomously completed by the background + * pselect() process. Only when waiting for input does the loop need to exit + * waiting for I/O. + * + * So a command loop takes the general form: + * + * loop: fetch command line + * parse command line + * loop if empty or all comment + * reflect command line + * deal with any pipe open actions + * dispatch command (if any) + * push command output to output + * loop + * + * hiatus: deal with issue + * loop if OK and not waiting for input + * exit command loop + * + * In the loop, if any operation receives a return code it cannot immediately + * deal with, it jumps to the "hiatius". For everything except the command + * line fetch this will be some sort of error -- either I/O or command -- or + * some external event closing down the loop. For the command line fetch + * this may be because must now wait for input, or because the current input + * has reached EOF and must now be closed (which may pop things off the + * vty stack). In any event, once the issue is dealt with, can leave the + * hiatus and return to the top of the loop. + * + * Note that in hiatus the command loop may be waiting for some output I/O + * to complete -- e.g. while closing an output pipe. + * + * So, most obviously for cq_process(), the loop is a co-routine which exits + * and is re-entered at the hiatus. When the loop does exit, it has either + * come to a dead stop (for a number of reasons) or it is waiting for input. + * + * The state of the command loop is vty->vio->state. The main states are: + * + * vc_running -- somewhere in the command loop, doing things. + * + * vc_waiting -- waiting for I/O + * + * When some event (such as some input arriving) occurs it is signalled to + * the command loop by uty_cmd_signal(), where the value of the signal is + * a CMD_XXXX code. If the loop is in vc_waiting state, the loop can be + * re-entered (at the hiatus point) with the return code. Otherwise, what + * happens depends on the state and the return code -- see uty_cmd_signal(). + * + * Functions called by the various steps in the command loop will check for + * a pending signal, and will force a jump to hiatus -- using CMD_HIATUS return + * code. (This is how the command loop may be "interrupted" by, for example + * an I/O error detected in the pselect() process.) */ /*------------------------------------------------------------------------------ * Prototypes */ - -static void uty_cmd_loop_prepare(vty_io vio) ; +static bool uty_cmd_loop_prepare(vty_io vio) ; +static void uty_cmd_stopping(vty_io vio, bool exeunt) ; static cmd_return_code_t uty_cmd_hiatus(vty_io vio, cmd_return_code_t ret) ; static cmd_return_code_t vty_cmd_auth(vty vty, node_type_t* p_next_node) ; +static void uty_cmd_out_cancel(vio_vf vf, bool base) ; static uint uty_show_error_context(vio_fifo ebuf, vio_vf vf) ; static uint uty_cmd_failed(vty_io vio, cmd_return_code_t ret) ; static void uty_cmd_prepare(vty_io vio) ; -static void uty_cmd_config_lock(vty vty) ; +static bool uty_cmd_config_lock(vty vty) ; static void uty_cmd_config_lock_check(struct vty *vty, node_type_t node) ; /*============================================================================== - * There are two command loops -- cmd_read_config() and cq_process(). These - * functions support those command loops: TODO update !! - * - * * uty_cmd_prepare() - * - * After opening or closing a vin/vout object, the state of the execution - * context must be set to reflect the current top of the vin/vout stack. - * - * * uty_cmd_dispatch_line() - * - * This is for command lines which come from "push" sources, eg the - * Telnet VTY CLI or the VTYSH. - * - * Hands command line over to the vty_cli_nexus message queue. - * - * NB: from this point onwards, the vio is vio->cmd_running ! TODO - * - * * vty_cmd_fetch_line() - * - * This is for command lines which are "pulled" by one of the command - * execution loops, eg files and pipes. If not in cmd_read_config()) - * can return CMD_WAITING. - * - * * vty_cmd_open_in_pipe_file() - * * vty_cmd_open_in_pipe_shell() - * * vty_cmd_open_out_pipe_file() - * * vty_cmd_open_out_pipe_shell() - * - * These do the I/O side of the required opens, pushing the new vty_vf - * onto the vin/vout stack. - * - * * vty_cmd_success() - * - * When a command returns success, this is called either to push all - * buffered output to the current vout, or to discard all buffered - * output (which is what cmd_read_config() does). - * - * If required, pops any vout(s). - * - * + * Starting up, communicating with and closing down a command loop. */ /*------------------------------------------------------------------------------ @@ -103,32 +137,47 @@ static void uty_cmd_config_lock_check(struct vty *vty, node_type_t node) ; * * Initialise exec object, and copy required settings from the current vin * and vout. + * + * Returns: true <=> acquired or did not need config symbol of power + * or: false <=> needed but could not acquire symbol of power */ -extern void -vty_cmd_loop_prepare(vty vty) +extern bool +vty_cmd_config_loop_prepare(vty vty) { + bool ok ; + VTY_LOCK() ; assert(vty->type == VTY_CONFIG_READ) ; - uty_cmd_loop_prepare(vty->vio) ; /* by vty->type & vty->node */ + ok = uty_cmd_loop_prepare(vty->vio) ; /* by vty->type & vty->node */ + VTY_UNLOCK() ; + + return ok ; } ; /*------------------------------------------------------------------------------ * Enter the command_queue command loop. */ extern void -uty_cmd_loop_enter(vty_io vio) +uty_cmd_queue_loop_enter(vty_io vio) { + bool ok ; + VTY_ASSERT_CLI_THREAD_LOCKED() ; assert(vio->vty->type == VTY_TERMINAL) ; - uty_cmd_loop_prepare(vio) ; /* by vty->type & vty->node */ + ok = uty_cmd_loop_prepare(vio) ; /* by vty->type & vty->node */ + + if (!ok) + uty_out(vio, "%% unable to start in config mode\n") ; - cq_loop_enter(vio->vty, vio->vty->node != NULL_NODE ? CMD_SUCCESS - : CMD_CLOSE) ; + qassert(vio->state == vc_running) ; + cq_loop_enter(vio->vty, (vio->vty->node == NULL_NODE) ? CMD_CLOSE + : ok ? CMD_SUCCESS + : CMD_WARNING) ; } ; /*------------------------------------------------------------------------------ @@ -136,88 +185,123 @@ uty_cmd_loop_enter(vty_io vio) * * Initialise cmd_exec object, and its cmd_context -- given vty->type and * vty->node. + * + * Returns: true <=> acquired or did not need config symbol of power + * or: false <=> needed but could not acquire symbol of power */ -static void +static bool uty_cmd_loop_prepare(vty_io vio) { + bool ok ; + VTY_ASSERT_LOCKED() ; assert(vio->vty->exec == NULL) ; - assert(vio->state == vc_null) ; vio->vty->exec = cmd_exec_new(vio->vty) ; vio->state = vc_running ; + ok = true ; if (vio->vty->node > MAX_NON_CONFIG_NODE) - uty_cmd_config_lock(vio->vty) ; /* TODO cannot fail !? */ + { + ok = uty_cmd_config_lock(vio->vty) ; + if (!ok) + vio->vty->node = ENABLE_NODE ; + } ; uty_cmd_prepare(vio) ; + + return ok ; +} ; + +/*------------------------------------------------------------------------------ + * When entering command loop, or after opening or closing a vin/vout object, + * update the vty->exec context. + * + * Output to the vout_base is suppressed for reading of configuration files. + * + * Reflection of the command line depends on the current context, and on the + * state of output suppression. + */ +static void +uty_cmd_prepare(vty_io vio) +{ + cmd_exec exec = vio->vty->exec ; + + exec->reflect = exec->context->reflect_enabled ; + exec->out_suppress = (vio->vty->type == VTY_CONFIG_READ) + && (vio->vout_depth == 1) + && !exec->reflect ; } ; /*------------------------------------------------------------------------------ * Signal to the command loop that some I/O has completed -- successfully, or - * with some I/O error (including time out). + * with some I/O error (including time out), or otherwise. * - * If the vio is in vc_waiting state -- so will be exec_hiatus -- send message - * so that command loop continues. + * Accepts the following return codes: * - * Otherwise, if is vc_running and have CMD_IO_ERROR, set the vc_io_error_trap, - * so that the next interaction with the vty (other than output) will signal - * a pending error. + * CMD_SUCCESS -- if vc_waiting, passed in + * otherwise, ignored * - * Accepts: + * CMD_WAITING -- ignored * - * CMD_SUCCESS -- if vc_waiting, passed in. - * otherwise, ignored + * CMD_IO_ERROR -- if vc_waiting, passed in + * if vc_running, set signal, unless already CMD_STOP + * otherwise, ignored * - * CMD_WAITING -- ignored + * CMD_CANCEL -- if vc_waiting, passed in + * if vc_running, set signal, unless already CMD_STOP + * or CMD_IO_ERROR + * otherwise, ignored * - * CMD_IO_ERROR -- if vc_waiting, passed in - * if vc_running, set vc_io_error_trap + * NB: if sets CMD_CANCEL signal, sets vio->cancel. * - * CMD_CLOSE -- if vc_waiting, passed in - * if vc_running, set vc_close_trap - * if vc_io_error_trap, set vc_close_trap + * if passes CMD_CANCEL in, sets vio->cancel. */ extern void uty_cmd_signal(vty_io vio, cmd_return_code_t ret) { VTY_ASSERT_LOCKED() ; - if (ret == CMD_WAITING) - return ; - - assert((ret == CMD_SUCCESS) || (ret == CMD_IO_ERROR) || (ret == CMD_CLOSE)) ; + qassert( (ret == CMD_SUCCESS) || (ret == CMD_WAITING) + || (ret == CMD_IO_ERROR) + || (ret == CMD_CANCEL) ) ; switch (vio->state) { - case vc_null: - zabort("invalid vc_null") ; - break ; + case vc_running: + if ((ret == CMD_SUCCESS) || (ret == CMD_WAITING)) + break ; /* Ignored */ - case vc_running: /* ignore CMD_SUCCESS */ - if (ret == CMD_IO_ERROR) - vio->state = vc_io_error_trap ; - if (ret == CMD_CLOSE) - vio->state = vc_close_trap ; - break ; + if (vio->signal == CMD_STOP) + break ; /* Cannot override CMD_STOP */ - case vc_waiting: /* pass in the return code continue */ - vio->state = vc_running ; - cq_continue(vio->vty, ret) ; - break ; + if (ret != CMD_IO_ERROR) + { + qassert(ret == CMD_CANCEL) ; + + if (vio->signal == CMD_IO_ERROR) + break ; /* Cannot override CMD_IO_ERROR */ + + vio->cancel = true ; + } ; + + vio->signal = ret ; - case vc_io_error_trap: /* ignore CMD_SUCCESS or duplicate */ - if (ret == CMD_CLOSE) /* CMD_IO_ERROR. */ - vio->state = vc_close_trap ; break ; - case vc_close_trap: /* ignore CMD_SUCCESS, CMD_IO_ERROR, */ - case vc_stopped: /* and duplicate/too late CMD_CLOSE. */ + case vc_waiting: /* pass in the return code */ + if (ret != CMD_WAITING) + { + vio->state = vc_running ; + if (ret == CMD_CANCEL) + vio->cancel = true ; + + cq_continue(vio->vty, ret) ; + } ; break ; - case vc_closed: - zabort("invalid vc_closed") ; + case vc_stopped: /* ignore everything */ break ; default: @@ -227,64 +311,141 @@ uty_cmd_signal(vty_io vio, cmd_return_code_t ret) } ; /*------------------------------------------------------------------------------ - * Reset the command loop. + * Stop the command loop. * - * This is used by uty_close() to try to shut down the command loop. + * If the loop is vc_null, it has never started, so is stopped. * - * Does nothing if already closed or never got going. + * If is non-blocking then revoke any in-flight message (or legacy thread + * event), *and* if does that, then the command loop may be stopped. * - * "curtains" means that the program is being terminated, so no message or - * event handling is running any more, and all threads other than the main - * thread have stopped. This means that whatever the state of the command - * loop, we can terminate it now. Revokes any outstanding messages/events, - * in order to tidy up. + * The loop may then be: * - * If not curtains, then push a CMD_CLOSE into the command loop, to bring it - * to a shuddering halt. + * vc_running -- for multi-pthread world this means that the + * command loop is executing, and must be sent a + * CMD_STOP signal. (Is not waiting for a message, + * because we just tried revoking.) Because this + * function is in the vty_cli_nexus, the command loop + * *must* be running in the vty_cmd_nexus. * - * Will leave the vio->state: + * for single-pthread (or legacy thread), this should + * be impossible -- this code cannot run at the same + * time as the command loop. However, we send a + * CMD_STOP signal. * - * vc_running -- a CMD_CLOSE has been passed in (was vc_waiting). - * Will not be the case if "curtains". + * vc_waiting -- can stop the command loop now. Setting vc_stopped + * turns off any further signals to the command loop. * - * vc_close_trap -- command loop is running, but will stop as soon as it - * sees this trap. - * Will not be the case if "curtains". + * vc_stopped -- already stopped (or never started). * - * vc_stopped -- command loop is stopped - * Will be the case if "curtains" (unless vc_null or - * vc_closed). - * Otherwise will only be the case if the loop had already - * stopped. + * Note: we still revoke any in-flight messages. + * This deals with the case of the command loop + * picking up a CMD_STOP signal in the vty_cmd_nexus, + * but the message transferring the command loop to + * the vty_cli_nexus has not been picked up yet. * - * vc_null -- never been kissed - * vc_closed -- was already closed + * Note: if the command loop is exiting normally, + * then it will already be vc_stopped -- see + * vty_cmd_loop_exit() * - * No other states are possible. + * NB: if this is "curtains" then this *should* find the command loop already + * vc_stopped -- see uty_close() -- but will force the issue. + * + * Returns: true <=> the command loop is vc_stopped + * false => the command loop is running, but a CMD_STOP signal has + * been set. + */ +extern bool +uty_cmd_loop_stop(vty_io vio, bool curtains) +{ + VTY_ASSERT_CLI_THREAD_LOCKED() ; + + if (vio->blocking || !cq_revoke(vio->vty)) + { + if ((vio->state == vc_running) && !curtains) + { + uty_set_monitor(vio, off) ; /* instantly */ + vio->signal = CMD_STOP ; + + return false ; + } ; + + qassert(vio->state != vc_running) ; + } ; + + uty_cmd_stopping(vio, true) ; /* -> vc_stopped */ + + return true ; +} ; + +/*------------------------------------------------------------------------------ + * Set the command loop stopped -- forced exit. */ extern void -uty_cmd_loop_close(vty_io vio, bool curtains) +vty_cmd_set_stopped(vty vty) +{ + VTY_LOCK() ; + uty_cmd_stopping(vty->vio, true) ; /* forced exit */ + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * The command loop is stopping or closing. + * + * We drop the config symbol of power and effectively discard all input. + * Stops any log monitoring, immediately. + * + * This is done as soon as a CMD_CLOSE, CMD_STOP or CMD_STOP signal is seen, so + * that the symbol of power is not held while a vty is closing its vio stack, + * or while the command loop is being transferred from the vty_cmd_nexus to the + * vty_cli_nexus. + * + * If exeunt, set vc_stopped -- otherwise leave vc_running to tidy up. + * + * This can be called any number of times. + */ +static void +uty_cmd_stopping(vty_io vio, bool exeunt) { VTY_ASSERT_LOCKED() ; - if ((vio->state == vc_null) || (vio->state == vc_closed)) - return ; + uty_set_monitor(vio, off) ; /* instantly */ - if (curtains) - { - if (!vio->blocking) - cq_revoke(vio->vty) ; /* collect any outstanding message */ + vio->vin_true_depth = 0 ; /* all stop */ + uty_cmd_config_lock_check(vio->vty, NULL_NODE) ; - vio->state = vc_stopped ; - } + if (exeunt) + vio->state = vc_stopped ; /* don't come back */ else - uty_cmd_signal(vio, CMD_CLOSE) ; + qassert(vio->state == vc_running) ; } ; /*------------------------------------------------------------------------------ - * Exit command loop. + * If we have a CMD_STOP on our hands, then drop the config symbol of power. * - * Final close of the VTY, giving a reason, if required. + * This is done so that on SIGHUP the symbol of power can be acquired to read + * the configuration file, without an interlock to wait for closing then + * current vty. + */ +extern void +vty_cmd_check_stop(vty vty, cmd_return_code_t ret) +{ + VTY_LOCK() ; + + if ((ret == CMD_STOP) || (vty->vio->signal == CMD_STOP)) + { + vty->vio->signal = CMD_STOP ; /* make sure signal set */ + + uty_cmd_stopping(vty->vio, false) ; /* not exit, yet */ + } ; + + VTY_UNLOCK() ; +} ; + +/*------------------------------------------------------------------------------ + * Exit command loop, with final close of the VTY. + * + * NB: on exit the VTY has been release, so do NOT attempt to touch the VTY + * or any of its components. */ extern void vty_cmd_loop_exit(vty vty) @@ -293,20 +454,150 @@ vty_cmd_loop_exit(vty vty) VTY_ASSERT_CAN_CLOSE(vty) ; - /* Make sure no longer holding the config symbol of power */ - uty_cmd_config_lock_check(vty, NULL_NODE) ; + uty_cmd_stopping(vty->vio, true) ; /* exit -- set vc_stopping */ + uty_close(vty->vio) ; /* down close the vty */ + + VTY_UNLOCK() ; +} ; - /* Can now close the vty */ - vty->vio->state = vc_stopped ; - uty_close(vty->vio, NULL, false) ; /* not curtains */ +/*============================================================================== + * Command line fetch. + * + * Will read command lines from the current input, until that signals EOF. + * + * Before attempting to read, will check for a vio->signal, and that the input + * and output stack depths do not require attention. The CMD_HIATUS return + * code signals that something needs to be dealt with before any input is + * attempted. + * + * NB: all closing of inputs and/or outputs is done in the hiatus, below. + */ + +/*------------------------------------------------------------------------------ + * Fetch the next command line to be executed. + * + * Returns: CMD_SUCCESS => OK -- have a line ready to be processed. + * + * vty->exec->line points at the line + * vty->exec->to_do says what to do with it + * + * or: CMD_WAITING => OK -- but waiting for command line to arrive + * <=> non-blocking + * + * or: CMD_EOF => OK -- but nothing to fetch from the current vin + * + * Need to close the current vin and pop vin/vout + * as necessary. + * + * or: CMD_HIATUS => OK -- but need to close one or more vin/vout + * to adjust stack. + * + * or: CMD_CANCEL => cancel vio->signal detected. + * + * or: CMD_IO_ERROR => failed (here or signal) -- I/O error or timeout. + * + * or: CMD_STOP => stop vio->signal detected. + * + * NB: can be called from any thread -- because does no closing of files or + * anything other than read/write. + */ +extern cmd_return_code_t +vty_cmd_fetch_line(vty vty) +{ + cmd_return_code_t ret ; + vty_io vio ; + vio_vf vf ; + cmd_exec exec ; + + VTY_LOCK() ; + + vio = vty->vio ; /* once locked */ + exec = vty->exec ; + + cmd_action_clear(exec->action) ; /* tidy */ + + vf = vio->vin ; + + ret = vio->signal ; + + if (ret == CMD_SUCCESS) + { + if ( (vio->vin_depth < vio->vout_depth) || + (vio->vin_depth > vio->vin_true_depth) ) + ret = CMD_HIATUS ; + else + { + qassert(vio->vin_depth == vio->vin_true_depth) ; + qassert(vio->vin_depth != 0) ; + + switch (vf->vin_state) + { + case vf_closed: + zabort("invalid vf->vin_state (vf_closed)") ; + break ; + + case vf_open: + switch (vf->vin_type) + { + case VIN_NONE: + zabort("invalid VIN_NONE") ; + break ; + + case VIN_TERM: + ret = uty_term_fetch_command_line(vf, exec->action, + exec->context) ; + break ; + + case VIN_VTYSH: + zabort("invalid VIN_VTYSH") ; + break ; + + case VIN_DEV_NULL: + ret = CMD_EOF ; + break ; + + case VIN_FILE: + case VIN_CONFIG: + ret = uty_file_fetch_command_line(vf, exec->action) ; + break ; + + case VIN_PIPE: + ret = uty_pipe_fetch_command_line(vf, exec->action) ; + break ; + + default: + zabort("unknown vin_type") ; + } ; + + break ; + + case vf_end: /* Should be dealt with in hiatus... */ + ret = CMD_HIATUS ; /* ...so go (back) there ! */ + break ; + + default: + zabort("unknown vf->vin_state") ; + break ; + } ; + } ; + } ; VTY_UNLOCK() ; + + return ret ; } ; +/*============================================================================== + * Special command handling functions. + */ + /*------------------------------------------------------------------------------ * Handle a "special" command -- anything not cmd_do_command. * * These "commands" are related to VTY_TERMINAL CLI only. + * + * Returns: CMD_SUCCESS -- OK, carry on + * CMD_CLOSE -- bring everything to an orderly stop */ extern cmd_return_code_t vty_cmd_special(vty vty) @@ -330,6 +621,7 @@ vty_cmd_special(vty vty) switch (to_do & cmd_do_mask) { case cmd_do_nothing: + case cmd_do_ctrl_c: break ; case cmd_do_eof: @@ -360,7 +652,6 @@ vty_cmd_special(vty vty) zabort("invalid cmd_do_ctrl_d") ; break ; - case cmd_do_ctrl_c: case cmd_do_ctrl_z: next_node = cmd_node_end_to(vty->node) ; break ; @@ -380,6 +671,7 @@ vty_cmd_special(vty vty) vty->exec->context->node = next_node ; vty_cmd_config_lock_check(vty, next_node) ; } ; + return ret ; } ; @@ -478,12 +770,12 @@ vty_cmd_can_auth_enable(vty vty) * Significant iff there is no enable password, when it sets ENABLE_NODE * as the start up node (if no_password_check) or post AUTH_NODE node. * - * 4. host.password -- set by "password xxx" + * 4. host.password -- set by "password xx" * * Unless no_password_check, if there is no password, you cannot start * a vty. * - * 5. host.enable -- set by "enable password xxx" + * 5. host.enable -- set by "enable password xx" * * If this is set, then must authenticate against it for vty to reach * ENABLE_NODE. @@ -524,7 +816,7 @@ vty_cmd_auth(vty vty, node_type_t* p_next_node) pass = false ; - VTY_LOCK() ; /* while access host.xxx */ + VTY_LOCK() ; /* while access host.xx */ switch (vty->node) { @@ -649,126 +941,39 @@ vty_cmd_auth(vty vty, node_type_t* p_next_node) return ret ; } ; -/*------------------------------------------------------------------------------ - * Fetch the next command line to be executed. +/*============================================================================== + * Hiatus handling. * - * Returns: CMD_SUCCESS => OK -- have a line ready to be processed. + * The hiatus must deal with a number of slightly different things: * - * vty->exec->line points at the line - * vty->exec->to_do says what to do with it + * * closing of inputs and/or outputs, and adjusting the stacks. * - * or: CMD_WAITING => OK -- but waiting for command line to arrive - * <=> non-blocking + * All pipe closing is done here. * - * or: CMD_EOF => OK -- but nothing to fetch from the current vin + * * command errors * - * Need to close the current vin and pop vin/vout - * as necessary. + * * cancel * - * or: CMD_HIATUS => OK -- but need to close one or more vin/vout - * to adjust stack. + * * I/O errors and time-outs * - * or: any other return code from the current vin when it attempts to - * fetch another command line -- including I/O error or timeout. + * * closing of vty altogether, either: * - * NB: can be called from any thread -- because does no closing of files or - * anything other than read/write. + * CMD_CLOSE -- which closes everything, completing all I/O + * + * CMD_STOP -- which stops the command loop, and lets uty_close() + * close everything "final". * - * TODO -- dealing with states other than vf_open !! + * Note that while closing, for non-blocking vio, may return from the hiatus + * CMD_WAITING, and the hiatus will be called again (possibly a number of + * times) until all necessary closes and related I/O are complete. */ -extern cmd_return_code_t -vty_cmd_fetch_line(vty vty) -{ - cmd_return_code_t ret ; - vty_io vio ; - vio_vf vf ; - cmd_exec exec ; - - VTY_LOCK() ; - - vio = vty->vio ; /* once locked */ - exec = vty->exec ; - - cmd_action_clear(exec->action) ; /* tidy */ - - vf = vio->vin ; - - ret = CMD_SUCCESS ; /* assume all is well */ - - /* Worry about all the things that stop us from being able to fetch the - * next command line. - */ - if ( (vty->vio->state != vc_running) - || (vio->vin_depth < vio->vout->depth_mark) - || (vio->vin_depth > vio->real_depth) ) - ret = CMD_HIATUS ; - else - { - switch (vf->vin_state) - { - case vf_open: - case vf_eof: - case vf_timed_out: - switch (vf->vin_type) - { - case VIN_NONE: - zabort("invalid VIN_NONE") ; - break ; - - case VIN_TERM: - ret = uty_term_fetch_command_line(vf, exec->action, - exec->context) ; - break ; - - case VIN_VTYSH: - zabort("invalid VIN_VTYSH") ; - break ; - - case VIN_DEV_NULL: - ret = CMD_EOF ; - break ; - - case VIN_FILE: - case VIN_CONFIG: - ret = uty_file_fetch_command_line(vf, exec->action) ; - break ; - - case VIN_PIPE: - ret = uty_pipe_fetch_command_line(vf, exec->action) ; - break ; - - default: - zabort("unknown vin_type") ; - } ; - break ; - - case vf_closed: /* TODO treat closed as EOF ? */ - ret = CMD_EOF ; - break ; - - case vf_error: - ret = CMD_IO_ERROR ; - break ; - - case vf_closing: - assert(!vf->blocking) ; - ret = CMD_WAITING ; - break ; - - default: - zabort("invalid vf->vin_state") ; - break ; - } ; - } ; - - VTY_UNLOCK() ; - - return ret ; -} ; /*------------------------------------------------------------------------------ * Deal with return code at the "exec_hiatus" point in the command loop. * + * May be entering the hiatus because a signal has been detected, which may + * override the given return code. Any signal is then cleared. + * * The command_queue command loop runs until something happens that it * cannot immediately deal with, at which point it enters "exec_hiatus", and * this function is called. The command loop will deal with CMD_SUCCESS and @@ -781,11 +986,24 @@ vty_cmd_fetch_line(vty vty) * * - the vio->state needs to be checked. * + * CMD_STOP -- stop the command loop and exit, closing vty + * * CMD_EOF -- from vty_cmd_fetch_line() => current vin has hit eof, - * and must be closed and popped. + * or an end/exit command has forced the issue. + * + * The vin_real_depth must be reduced, and the top vin + * then closed. + * + * CMD_CLOSE -- from a command return => must close the entire vty. + * + * CMD_CLOSE causes the vty to be closed in an orderly + * fashion, dealing with all pending I/O, including + * all pipe return etc. + * + * Once everything has been closed down to the * - * CMD_CLOSE -- from a command return (or otherwise) => must close - * and pop the current vin (same as CMD_EOF, really). + * CMD_CANCEL -- cancel all output & close everything except the + * vin/vout base. * * CMD_WAITING -- from vty_cmd_fetch_line() or elsewhere => must go to * vc_waiting and the command loop MUST exit. @@ -808,11 +1026,16 @@ vty_cmd_fetch_line(vty vty) * Pending output will be pushed out, and pipe return stuff will be sucked * in and blown out, until the return signals EOF. * - * * I/O errors will cause all levels of TODO ... depending on error location ?? + * * I/O errors and timeouts in any level of the stack other than vin_base + * and vout_base will cause everything except the vin_base and vout_base + * to be closed. * - * I/O errors also cause all closes to be "final", so pending output is - * attempted -- but will be abandoned if would block. Also, any further - * I/O errors will be discarded. + * I/O errors and time-outs in vin_base cause everything to be closed, but + * will try to put error messages and outstanding stuff to vout_base. + * + * I/O errors and time-outs in vout_base cause everything to be closed. + * + * Does the standard close... so will try to flush all outstanding stuff. * * This function will return: * @@ -825,150 +1048,208 @@ vty_cmd_fetch_line(vty vty) * * state == vc_waiting * - * CMD_EOF => OK -- but nothing more to fetch, close the vty and exit - * command loop. - * <=> the vin_base is now closed. + * CMD_STOP => OK -- all done: close the vty and exit command loop. * * state == vc_stopped * - * CMD_IO_ERROR => some error has occurred while closing stuff. + * If CMD_STOP is passed in, or a CMD_STOP signal + * is picked up, then has done nothing with the + * stacks -- uty_close() will do close "final". * - * state == vc_io_error_trap + * Otherwise, will have completing all pending stuff, + * and closed everything except the vout_base. * - * And nothing else. + * And nothing else, except: * - * The CMD_IO_ERROR is returned so that errors are not hidden inside here. - * At some point vty_cmd_hiatus() must be called again to deal with the - * error. + * CMD_IO_ERROR => an I/O error or time-out has been hit, probably while + * trying to close something. + * + * This error should be sent straight back to this + * function -- but is passed out so that any error is not + * hidden -- see cmd_read_config(). * * When the command loop has gone vc_waiting, the I/O side of things can wake * it up by uty_cmd_signal(), which passes in a return code. When the * command loop runs it will call this function to handle the new return code. * If CMD_SUCCESS is passed in, will continue trying to adjust the vin/vout - * stacks. + * stacks, if required. * * The configuration reader command loop also uses vty_cmd_hiatus() to handle - * all return codes. However, it will exit the command loop at the slightest + * all return codes. However, it will exit the command loop at the first * hint of trouble. * - * All this TODO (after discussion of error handling) - * NB: can be called from any thread if !blocking, otherwise MUST be cli thread. */ extern cmd_return_code_t vty_cmd_hiatus(vty vty, cmd_return_code_t ret) { + vty_io vio ; + VTY_LOCK() ; VTY_ASSERT_CAN_CLOSE(vty) ; - ret = uty_cmd_hiatus(vty->vio, ret) ; + vio = vty->vio ; + + qassert(vio->state == vc_running) ; + + ret = uty_cmd_hiatus(vio, ret) ; + + switch (ret) + { + case CMD_SUCCESS: /* ready to continue */ + case CMD_IO_ERROR: /* for information */ + break ; + + case CMD_WAITING: + qassert(!vio->blocking) ; + vio->state = vc_waiting ; + break ; + + case CMD_STOP: /* exit */ + uty_cmd_stopping(vio, true) ; /* vio->state -> vc_stopped */ + break ; + + default: + zabort("invalid return code from uty_cmd_hiatus") ; + } ; VTY_UNLOCK() ; + return ret ; } ; /*------------------------------------------------------------------------------ - * Inside of vty_cmd_hiatus() -- can return at any time. + * Inside of vty_cmd_hiatus() -- see above. */ static cmd_return_code_t uty_cmd_hiatus(vty_io vio, cmd_return_code_t ret) { - /* (0) worry about state of the vio. - * - * We expect it to generally be be vc_running or vc_waiting, otherwise: + /* (1) Handle any vio->signal. * - * vc_io_error_trap => an I/O error has been posted asynchronously + * If there is a signal it overrides most return codes, except: * - * set state to vc_running and return code to - * the pending CMD_IO_ERROR. + * CMD_CANCEL -- trumped by: CMD_STOP, CMD_CLOSE or CMD_IO_ERROR * - * vc_close_trap => the vty has been reset asynchronously + * CMD_IO_ERROR -- trumped by: CMD_STOP or CMD_CLOSE * - * set state to vc_running and return code to - * the pending CMD_CLOSE. + * CMD_STOP -- trumps everything * - * vc_stopped ) - * vc_closed ) invalid -- cannot be here in this state - * vc_null ) + * The vio_signal is now cleared (unless was CMD_STOP). */ - switch (vio->state) + switch (vio->signal) { - case vc_null: - case vc_stopped: - case vc_closed: - zabort("invalid vc_xxxx") ; + case CMD_SUCCESS: break ; - case vc_running: - case vc_waiting: + case CMD_CANCEL: + if ((ret != CMD_STOP) && (ret != CMD_CLOSE) && (ret != CMD_IO_ERROR)) + ret = CMD_CANCEL ; + + vio->signal = CMD_SUCCESS ; break ; - case vc_io_error_trap: - ret = CMD_IO_ERROR ; /* convert pending IO error */ + case CMD_IO_ERROR: + if ((ret != CMD_STOP) && (ret != CMD_CLOSE)) + ret = CMD_IO_ERROR ; + + vio->signal = CMD_SUCCESS ; break ; - case vc_close_trap: - ret = CMD_CLOSE ; /* convert pending close */ + case CMD_STOP: + ret = CMD_STOP ; break ; default: - zabort("unknown vio->state") ; - break ; + zabort("Invalid vio->signal value") ; } ; - vio->state = vc_running ; /* running in hiatus */ - - /* (1) Handle the return code. + /* (2) Handle the return code/signal + * + * Deal here with the return codes that signify success, or signify + * success but some vin and/or vout need to be closed. * - * Deal here with the return codes that signify success, or signify - * success but some vin and/or vout need to be closed. + * Call uty_cmd_failed() to deal with return codes that signify some + * sort of failure. A failure generally means closing all the way to + * the vin_/vout_base, or possibly completely. * - * Call uty_cmd_failed() to deal with return codes that signify some - * sort of failure. A failure generally means closing all the way to - * the vin_/vout_base, or possibly completely. + * CMD_WAITING is immediately returned * - * Note that CMD_WAITING is immediately returned ! + * CMD_STOP, whether passed in or from vio->signal is also immediately + * returned. */ switch (ret) - { - case CMD_SUCCESS: - case CMD_EMPTY: - case CMD_HIATUS: - break ; + { + case CMD_SUCCESS: + case CMD_EMPTY: + case CMD_HIATUS: + ret = CMD_SUCCESS ; /* OK */ + break ; - case CMD_WAITING: - assert(!vio->blocking) ; - vio->state = vc_waiting ; - return ret ; /* <<< exit here on CMD_WAITING */ + case CMD_STOP: + return CMD_STOP ; /* <<< ...exit here on CMD_STOP */ - case CMD_EOF: - uty_out_accept(vio) ; /* accept any buffered remarks. */ - assert(vio->real_depth > 0) ; - --vio->real_depth ; - break ; + case CMD_WAITING: + return CMD_WAITING ; /* <<< ...exit here on CMD_WAITING */ - case CMD_CLOSE: - uty_out_accept(vio) ; /* accept any buffered remarks. */ - vio->real_depth = 0 ; /* which it may already be */ - break ; + case CMD_EOF: + uty_out_accept(vio) ; /* accept any buffered stuff. */ - default: - /* If not any of the above, must be an error of some kind: - * - * * set real_depth to close all pipes and files. - * - * Depending on the type of vin_base/vout_base and the type of - * error, may or may not leave the bas I/O open. - * - * * create error messages in the vout_base. - */ - vio->real_depth = uty_cmd_failed(vio, ret) ; - break ; - } ; + qassert(vio->vin_true_depth > 0) ; + --vio->vin_true_depth ; /* cause vin stack to pop */ + + ret = CMD_SUCCESS ; /* OK */ + break ; + + case CMD_CANCEL: + qassert(vio->vin_true_depth > 0) ; + vio->vin_true_depth = 1 ; /* cause vin stack to pop */ + vio->cancel = true ; /* suppress output */ + + ret = CMD_SUCCESS ; /* OK */ + break ; - ret = CMD_SUCCESS ; /* OK, so far */ + case CMD_CLOSE: + uty_out_accept(vio) ; /* accept any buffered stuff. */ + + vio->vin_true_depth = 0 ; /* cause vin stack to close */ + + ret = CMD_SUCCESS ; /* OK */ + break ; + + default: /* some sort of error */ + break ; + } ; + + /* If the return code is (still) not CMD_SUCCESS, then must be an error + * of some kind, for which we now construct a suitable error message, and + * update the vin_true_depth as required. + */ + if (ret != CMD_SUCCESS) + { + uint depth ; + + depth = uty_cmd_failed(vio, ret) ; + if (depth < vio->vin_true_depth) + vio->vin_true_depth = depth ; + + ret = CMD_SUCCESS ; /* Is now OK. */ + } ; + + /* Have established the (new) vio->vin_true_depth, so now need to make + * sure that the stack conforms to that. + * + * Adjusting the stack may take a while. So, if the vin_true_depth is 0, + * now is a good time to give up the config symbol of power. (This is + * for SIGHUP, which closes all vty before reading the configuration.) + * + * Note that because vin_true_depth is zero, could not fetch any further + * command lines or attempt to execute any commands, and don't care + * whether own the symbol of power or not. + */ + if (vio->vin_true_depth == 0) + uty_cmd_stopping(vio, false) ; /* not exit, yet */ - /* (2) Do we need to close one or more vin, or are we waiting for one to + /* (3) Do we need to close one or more vin, or are we waiting for one to * close ? * * The close will immediately close the input, and discard anything @@ -980,179 +1261,121 @@ uty_cmd_hiatus(vty_io vio, cmd_return_code_t ret) * * For non-blocking vio, close operations may return CMD_WAITING * (eg: VIN_PIPE where the child stderr is not yet at EOF, or the - * child completion status has not yet been collected). Where a - * close operation does not complete, the vf is marked vf_closing, - * and the stack stays at its current level. + * child completion status has not yet been collected). * - * For hard errors will do "final" close, which immediately closes the - * input (and any pipe return) discarding any buffered input. Any errors - * that occur in the process are discarded. + * Where a close does not succeed, exit here and expect to come back + * later to complete the operation. */ - while ((vio->vin_depth > vio->real_depth) && (ret == CMD_SUCCESS)) - ret = uty_vin_pop(vio, vio->err_hard, vio->vty->exec->context) ; + while (vio->vin_depth > vio->vin_true_depth) + { + ret = uty_vin_pop(vio, vio->vty->exec->context, false) ; /* not final */ + if (ret != CMD_SUCCESS) + return ret ; + } ; + + qassert(vio->vin_depth == vio->vin_true_depth) ; - /* (3) And, do we need to close one or more vout, or are we waiting for - * one to close ? If so: + /* (4) Do we need to close one or more vout, or are we waiting for + * one to close ? * - * Any output after the current end_mark is discarded. Note that we - * do not actually close the vout_base. + * Note that we do not close the vout_base here -- that is left open + * to the final minute, in case any parting messages are to be sent. * - * In all cases we push any remaining output and collect any remaining - * pipe return, and collect child termination condition. + * Any remaining output is pushed and any remaining pipe return is + * shovelled through, and any child process is collected, along + * with its termination condition. * * For blocking vio, close operations will either complete or fail. * * For non-blocking vio, close operations may return CMD_WAITING * (eg: where the output buffers have not yet been written away). - * Where a close operation does not complete, the vf is marked - * vf_closing, and the stack stays at its current level. - * - * For hard errors will attempt to write away anything which is, - * pending, but will stop if would block and on any error, and close - * the output -- discarding any remaining output and any errors. - * - * NB: when we reach the vout_base, turn off the hard error -- so - * never "final" close the vout TODO error handling. * - * Also: if we are at, or we reach, the vout_base, and there is an error - * message in hand, now is the time to move that to the obuf and - * push it. + * Where a close does not succeed, exit here and expect to come back + * later to complete the operation. */ - while (ret == CMD_SUCCESS) + while ((vio->vin_depth < vio->vout_depth) && (vio->vout_depth > 1)) { - assert(vio->vout->depth_mark >= vio->vout_depth) ; - - if (vio->vout_depth == 1) - { - assert(vio->vout->depth_mark == 1) ; - - vio->err_hard = false ; - - if (vio->ebuf != NULL) - { - vio_fifo_copy(vio->obuf, vio->ebuf) ; - vio->ebuf = vio_fifo_free(vio->ebuf) ; - - ret = uty_cmd_out_push(vio->vout, vio->err_hard) ; + if (vio->cancel) + uty_cmd_out_cancel(vio->vout, false) ; /* stop output & pipe return + * not vout_base */ - if (ret != CMD_SUCCESS) - break ; - } ; - } ; + ret = uty_vout_pop(vio, false) ; /* not final */ - if (vio->vout_depth == 0) - assert(vio->vout->depth_mark == 0) ; - - if (vio->vin_depth < vio->vout->depth_mark) - ret = uty_vout_pop(vio, vio->err_hard) ; - else - break ; + if (ret != CMD_SUCCESS) + return ret ; } ; - /* (4) Quit now if not successful on stack adjustment + /* (5) If we are now at the vout_base, then: + * + * If there is anything in the pipe stderr return, copy that to the + * obuff -- unless vio->cancel. + * + * If there is an error message in hand, now is the time to move that to + * the obuf and clear the error message buffer. (If the vout_base has + * failed, then the error message is going nowhere, but there's nothing + * we can do about that -- the error has been logged in any case.) + * + * Push any outstanding output (including any error message) to the + * vout_base. + * + * If the vty is about to be closed, this step ensures that all output + * is tidily dealt with, before uty_close() performs its "final" close. */ - if (ret != CMD_SUCCESS) + if (vio->vout_depth == 1) { - if (ret == CMD_WAITING) + if (vio->cancel) { - assert(!vio->blocking) ; - vio->state = vc_waiting ; - return ret ; /* <<< exit here on CMD_WAITING */ + /* Once we have cleared the output buffer etc., clear the cancel + * flag and output "^C" to show what has happened. + */ + uty_cmd_out_cancel(vio->vout, true) ; /* stop output & pipe return + * is vout_base */ + uty_out(vio, " ^C\n") ; } ; - if (ret == CMD_IO_ERROR) + if (!vio_fifo_empty(vio->ps_buf)) { - vio->state = vc_io_error_trap ; - return ret ; + if (!vio->cancel) + vio_fifo_copy(vio->obuf, vio->ps_buf) ; + vio_fifo_clear(vio->ps_buf, true) ; /* clear any marks too */ } ; - zabort("invalid return code") ; - } ; - - /* (5) Having dealt with closing of files and adjustment of stack, may - * now be EOF on the vin_base. - */ - if (vio->real_depth == 0) - { - vio->state = vc_stopped ; - return CMD_EOF ; - } ; + vio->cancel = false ; - /* (6) All ready now to continue processing commands. - */ - uty_cmd_prepare(vio) ; /* update vty->exec state */ + if (!vio_fifo_empty(vio->ebuf)) + { + vio_fifo_copy(vio->obuf, vio->ebuf) ; + vio_fifo_clear(vio->ebuf, true) ; /* clear any marks too */ + } ; - return CMD_SUCCESS ; -} ; + ret = uty_cmd_out_push(vio->vout, false) ; -/*------------------------------------------------------------------------------ - * When entering command loop, or after opening or closing a vin/vout object, - * update the vty->exec context. - * - * Output to the vout_base is suppressed for reading of configuration files. - * - * Reflection of the command line depends on the current context, and on the - * state of output suppression. - */ -static void -uty_cmd_prepare(vty_io vio) -{ - cmd_exec exec = vio->vty->exec ; + if (ret != CMD_SUCCESS) + return ret ; /* CMD_WAITING or CMD_IO_ERROR */ + } ; - exec->out_suppress = (vio->vty->type == VTY_CONFIG_READ) && - (vio->vout_depth == 1) ; - exec->reflect = exec->context->reflect_enabled && !exec->out_suppress ; -} ; + /* (6) Stacks are straight. + * + * If there is no input left, the command loop must now stop, close the + * vty and exit. + * + * Otherwise, prepare to execute commands at the, presumed new, stack + * depth. + */ + qassert(ret == CMD_SUCCESS) ; -/*------------------------------------------------------------------------------ - * Set the full_lex flag for further commands, and for any further pipes. - */ -extern void -vty_cmd_set_full_lex(vty vty, bool full_lex) -{ - VTY_LOCK() ; + if (vio->vin_depth == 0) + return CMD_STOP ; - vty->exec->context->full_lex = full_lex ; + uty_cmd_prepare(vio) ; - VTY_UNLOCK() ; + return CMD_SUCCESS ; } ; -/*------------------------------------------------------------------------------ - * Reflect the command line to the current vio->obuf. - * - * Advances the end_mark past the reflected line, so that output (in particular - * error stuff) is separate. - * - * NB: pushes the output, so that if the command takes a long time to process, - * it is visible while it proceeds. +/*============================================================================== + * Opening of pipes and adjustment of stacks. */ -extern cmd_return_code_t -vty_cmd_reflect_line(vty vty) -{ - cmd_return_code_t ret ; - - VTY_LOCK() ; - - if (vty->vio->state != vc_running) - ret = CMD_HIATUS ; - else - { - vio_fifo obuf ; - qstring line ; - - obuf = vty->vio->obuf ; /* once locked */ - line = vty->exec->action->line ; - - vio_fifo_put_bytes(obuf, qs_char_nn(line), qs_len_nn(line)) ; - vio_fifo_put_byte(obuf, '\n') ; - - ret = uty_cmd_out_push(vty->vio->vout, false) ; /* not final */ - } ; - - VTY_UNLOCK() ; - - return ret ; -} ; +static void uty_cmd_command_path(qstring name, cmd_context context) ; /*------------------------------------------------------------------------------ * Open the given file as an in pipe, if possible. @@ -1170,9 +1393,9 @@ uty_cmd_open_in_pipe_file(vty_io vio, cmd_context context, VTY_ASSERT_LOCKED() ; - if (vio->state != vc_running) - ret = CMD_HIATUS ; - else + ret = vio->signal ; /* signal can interrupt */ + + if (ret == CMD_SUCCESS) { ret = uty_file_read_open(vio, name, context) ; @@ -1204,10 +1427,11 @@ uty_cmd_open_in_pipe_shell(vty_io vio, cmd_context context, qstring command, VTY_ASSERT_LOCKED() ; - if (vio->state != vc_running) - ret = CMD_HIATUS ; - else + ret = vio->signal ; /* signal can interrupt */ + + if (ret == CMD_SUCCESS) { + uty_cmd_command_path(command, context) ; ret = uty_pipe_read_open(vio, command, context) ; if (ret == CMD_SUCCESS) @@ -1229,16 +1453,18 @@ uty_cmd_open_in_pipe_shell(vty_io vio, cmd_context context, qstring command, */ extern cmd_return_code_t uty_cmd_open_out_pipe_file(vty_io vio, cmd_context context, qstring name, - cmd_pipe_type_t type) + cmd_pipe_type_t type, bool after) { cmd_return_code_t ret ; - if (vio->state != vc_running) - ret = CMD_HIATUS ; - else + VTY_ASSERT_LOCKED() ; + + ret = vio->signal ; /* signal can interrupt */ + + if (ret == CMD_SUCCESS) { ret = uty_file_write_open(vio, name, - ((type & cmd_pipe_append) != 0), context) ; + ((type & cmd_pipe_append) != 0), context, after) ; if (ret == CMD_SUCCESS) uty_cmd_prepare(vio) ; } ; @@ -1253,16 +1479,19 @@ uty_cmd_open_out_pipe_file(vty_io vio, cmd_context context, qstring name, */ extern cmd_return_code_t uty_cmd_open_out_pipe_shell(vty_io vio, cmd_context context, qstring command, - cmd_pipe_type_t type) + cmd_pipe_type_t type, bool after) { cmd_return_code_t ret ; - if (vio->state != vc_running) - ret = CMD_HIATUS ; - else + VTY_ASSERT_LOCKED() ; + + ret = vio->signal ; /* signal can interrupt */ + + if (ret == CMD_SUCCESS) { + uty_cmd_command_path(command, context) ; ret = uty_pipe_write_open(vio, command, - ((type & cmd_pipe_shell_only) !=0)) ; + ((type & cmd_pipe_shell_cmd) != 0), after) ; if (ret == CMD_SUCCESS) uty_cmd_prepare(vio) ; @@ -1277,19 +1506,19 @@ uty_cmd_open_out_pipe_shell(vty_io vio, cmd_context context, qstring command, * Puts error messages to vty if fails. */ extern cmd_return_code_t -uty_cmd_open_out_dev_null(vty_io vio) +uty_cmd_open_out_dev_null(vty_io vio, bool after) { cmd_return_code_t ret ; vio_vf vf ; VTY_ASSERT_LOCKED() ; - if (vio->state != vc_running) - ret = CMD_HIATUS ; - else + ret = vio->signal ; /* signal can interrupt */ + + if (ret == CMD_SUCCESS) { vf = uty_vf_new(vio, "dev_null", -1, vfd_none, vfd_io_none) ; - uty_vout_push(vio, vf, VOUT_DEV_NULL, NULL, NULL, 0) ; + uty_vout_push(vio, vf, VOUT_DEV_NULL, NULL, NULL, 0, after) ; uty_cmd_prepare(vio) ; @@ -1312,18 +1541,141 @@ uty_cmd_path_name_complete(qpath dst, const char* name, cmd_context context) if (*name != '~') dst = qpath_copy(dst, context->dir_cd) ; - else if ((*(name + 1) == '/') || (*(name + 1) == '\0')) - dst = qpath_copy(dst, context->dir_home) ; - else if ((*(name + 1) == '.') && - ( (*(name + 2) == '/') || (*(name + 2) == '\0')) ) - dst = qpath_copy(dst, context->dir_here) ; else - return qpath_set(dst, name) ; /* no idea... probably an error */ + { + /* Have a leading '~' -- deal with: + * + * "~~/???" or "~~\0", which for Quagga -> configuration directory + * + * "~./???" or "~.\0", which for Quagga -> "here" (same as enclosing + * pipe) + * + * "~/???" or "~\0", which -> HOME environment variable + * or initial working directory + * for login. + * + * "~user/???" or "~user\0", which -> initial working directory + * for given user + */ + if ((*(name + 1) == '~') && + ( (*(name + 2) == '/') || (*(name + 2) == '\0')) ) + dst = qpath_copy(dst, context->dir_home) ; + else if ((*(name + 1) == '.') && + ( (*(name + 2) == '/') || (*(name + 2) == '\0')) ) + dst = qpath_copy(dst, context->dir_here) ; + else + { + qpath was = dst ; + + dst = qpath_get_home(dst, name + 1) ; + + /* If didn't get a home, return the original name + */ + if (dst == NULL) + return qpath_set(was, name) ; + } ; + } ; return qpath_append_str(dst, name) ; /* create the full path */ } ; /*------------------------------------------------------------------------------ + * If the given qstring starts with a '~' directory or is a relative path, + * then now is the time to complete it. + */ +static void +uty_cmd_command_path(qstring command, cmd_context context) +{ + const char* p, * s ; + qstring cmd ; + qpath qp ; + + s = p = qs_string(command) ; + + if ((*p == '/') || (*p == '\0')) + return ; /* absolute path or empty ! */ + + do + { + ++p ; + if (*p <= ' ') + return ; /* no path involved */ + } + while (*p != '/') ; /* look for '/' */ + + do + ++p ; + while (*p > ' ') ; /* look for end */ + + cmd = qs_set_n(NULL, s, p - s) ; + qp = uty_cmd_path_name_complete(NULL, qs_string(cmd), context) ; + + qs_set_cp_nn(command, 0) ; + qs_replace(command, p - s, qpath_string(qp), qpath_len(qp)) ; + + qs_free(cmd) ; + qpath_free(qp) ; +} ; + +/*============================================================================== + * Output before and after command execution. + * + * All output goes to a fifo, after a fifo "end mark". After reflecting a + * command and after completing a command, all outstanding output is pushed + * out -- advancing the end mark past all output to date. + */ + +/*------------------------------------------------------------------------------ + * Reflect the command line to the current vio->obuf. + * + * Advances the end_mark past the reflected line, so that output (in particular + * error stuff) is separate. + * + * NB: pushes the output, so that if the command takes a long time to process, + * it is visible while it proceeds. + * + * Returns: CMD_SUCCESS -- all buffers are empty + * CMD_WAITING -- all buffers are not empty + * CMD_IO_ERROR -- error or time-out + * CMD_HIATUS -- the vty is not in vc_running state. + * + * This can be called in any thread. + * + * Note that CMD_WAITING requires no further action from the caller, the + * background pselect process will complete the output and may signal the + * result via uty_cmd_signal() (CMD_SUCCESS or CMD_IO_ERROR). + */ +extern cmd_return_code_t +vty_cmd_reflect_line(vty vty) +{ + cmd_return_code_t ret ; + vty_io vio ; + + VTY_LOCK() ; + vio = vty->vio ; /* once locked */ + + ret = vio->signal ; /* signal can interrupt */ + + if (ret == CMD_SUCCESS) + { + vio_fifo obuf ; + qstring line ; + + obuf = vio->obuf ; + line = vty->exec->action->line ; + + vio_fifo_put_bytes(obuf, qs_char_nn(line), qs_len_nn(line)) ; + vio_fifo_put_byte(obuf, '\n') ; + + ret = uty_cmd_out_push(vio->vout, false) ; /* not final */ + } ; + + VTY_UNLOCK() ; + + return ret ; +} ; + +/*------------------------------------------------------------------------------ * Command has completed successfully. * * An output generated by the command is now pushed unless exec->out_suppress, @@ -1338,11 +1690,9 @@ vty_cmd_success(vty vty) VTY_LOCK() ; vio = vty->vio ; /* once locked */ - ret = CMD_SUCCESS ; + ret = vio->signal ; /* signal can interrupt */ - if (vio->state != vc_running) - ret = CMD_HIATUS ; - else + if (ret == CMD_SUCCESS) { if (!vio_fifo_tail_empty(vio->obuf)) { @@ -1361,19 +1711,24 @@ vty_cmd_success(vty vty) /*------------------------------------------------------------------------------ * If there is anything after the end_mark, push it to be written, now. * + * This is used by configuration file output, which outputs to the fifo and + * pushes every now and then. + * * See uty_cmd_out_push() below. */ extern cmd_return_code_t vty_cmd_out_push(vty vty) { cmd_return_code_t ret ; + vty_io vio ; VTY_LOCK() ; + vio = vty->vio ; /* once locked */ - if (vty->vio->state != vc_running) - ret = CMD_HIATUS ; - else - ret = uty_cmd_out_push(vty->vio->vout, false) ; /* not final */ + ret = vio->signal ; /* signal can interrupt */ + + if (ret == CMD_SUCCESS) + ret = uty_cmd_out_push(vio->vout, false) ; /* not final */ VTY_UNLOCK() ; @@ -1381,22 +1736,36 @@ vty_cmd_out_push(vty vty) } ; /*------------------------------------------------------------------------------ - * If there is anything after the end_mark, push it to be written, now. + * If there is anything after the end_mark, advance the end mark and attempt to + * write away contents of the buffer. + * + * For non-blocking vf, will write as much as possible here, and anything left + * will be left to the pselect() process, unless "final". * - * For most outputs we kick the I/O side so that the pselect processing deals - * with the required write. For VOUT_STDERR and VOUT_STDOUT, output goes - * directly to standard I/O. + * For blocking vf, may block here, unless "final". * - * Advances the end_mark past the stuff pushed. + * If "final", will attempt to write etc., but will not block and may turn + * off the pselect() processing of this vf. "final" is used when a pipe of + * some kind is being closed "final", and the slave output is being pushed. * * NB: takes no notice of vf->out_suppress, which applies only to buffered * output present when successfully complete a command -- vty_cmd_success(). * - * TODO: worry about closing state ! - * TODO: need a vf level one of these. + * Returns: CMD_SUCCESS -- done everything possible + * CMD_WAITING -- not "final" => waiting for output to complete + * <=> not vf->blocking + * "final" => would have waited *or* blocked, + * but did not. + * CMD_IO_ERROR -- error or time-out (may be "final") + * + * This can be called in any thread. + * + * Note that CMD_WAITING requires no further action from the caller, the + * background pselect process will complete the output and may signal the + * result via uty_cmd_signal() (CMD_SUCCESS or CMD_IO_ERROR). */ extern cmd_return_code_t -uty_cmd_out_push(vio_vf vf, bool final) /* TODO: write errors & blocking */ +uty_cmd_out_push(vio_vf vf, bool final) { cmd_return_code_t ret ; @@ -1409,7 +1778,6 @@ uty_cmd_out_push(vio_vf vf, bool final) /* TODO: write errors & blocking */ switch (vf->vout_state) { case vf_open: - case vf_closing: switch (vf->vout_type) { case VOUT_NONE: @@ -1417,15 +1785,19 @@ uty_cmd_out_push(vio_vf vf, bool final) /* TODO: write errors & blocking */ break ; case VOUT_TERM: - ret = uty_term_out_push(vf, final) ; + /* Note that we ignore "final" -- the VOUT_TERM runs until + * it is closed. + */ + ret = uty_term_out_push(vf, false) ; break ; case VOUT_VTYSH: - /* Kick the writer */ + /* Kick the writer */ break ; case VOUT_FILE: - ret = uty_file_out_push(vf, final) ; + /* push everything if the vty is being closed. */ + ret = uty_file_out_push(vf, final, vf->vio->vin_depth == 0) ; break ; case VOUT_PIPE: @@ -1433,20 +1805,27 @@ uty_cmd_out_push(vio_vf vf, bool final) /* TODO: write errors & blocking */ break ; case VOUT_CONFIG: - ret = uty_file_out_push(vf, final) ; /* treat as file */ + /* push everything if the vty is being closed. */ + ret = uty_file_out_push(vf, final, vf->vio->vin_depth == 0) ; break ; case VOUT_DEV_NULL: - case VOUT_SHELL_ONLY: - vio_fifo_clear(vf->obuf, false) ; /* keep end mark */ + case VOUT_SH_CMD: + vio_fifo_clear(vf->obuf, false) ; /* keep end mark */ break ; case VOUT_STDOUT: - vio_fifo_fwrite(vf->obuf, stdout) ; // TODO errors + if (vf->vio->cancel) + vio_fifo_clear(vf->obuf, false) ; /* keep end mark */ + else + vio_fifo_fwrite(vf->obuf, stdout) ; // TODO errors break ; case VOUT_STDERR: - vio_fifo_fwrite(vf->obuf, stderr) ; // TODO errors + if (vf->vio->cancel) + vio_fifo_clear(vf->obuf, false) ; /* keep end mark */ + else + vio_fifo_fwrite(vf->obuf, stderr) ; // TODO errors break ; default: @@ -1455,16 +1834,9 @@ uty_cmd_out_push(vio_vf vf, bool final) /* TODO: write errors & blocking */ break ; case vf_closed: - case vf_timed_out: - break ; /* immediate success ! */ - - case vf_eof: - zabort("vf->vout cannot be vf_eof") ; - break ; - - case vf_error: - ret = CMD_IO_ERROR ; - break ; + case vf_end: + vio_fifo_clear(vf->obuf, false) ; /* keep end mark */ + break ; /* immediate success ! */ default: zabort("unknown vf->vout_state") ; @@ -1475,38 +1847,73 @@ uty_cmd_out_push(vio_vf vf, bool final) /* TODO: write errors & blocking */ } ; /*------------------------------------------------------------------------------ + * Bring output and any pipe return to a sudden halt. + */ +static void +uty_cmd_out_cancel(vio_vf vf, bool base) +{ + VTY_ASSERT_LOCKED() ; + + /* Dump contents of obuf and if not base: force vf_end (if vf_open) + */ + vio_fifo_clear(vf->obuf, false) ; + + if (!base && (vf->vout_state == vf_open)) + vf->vout_state = vf_end ; + + /* If there is a pipe return, close that down, too. + */ + if (vf->pr_state == vf_open) + uty_pipe_return_cancel(vf) ; +} ; + +/*============================================================================== + * Error handling + */ + +/*------------------------------------------------------------------------------ * Dealing with error of some kind. * - * The current vio->obuf will have an end_mark. After the end_mark will be - * any output generated since the start of the current command (or any out_push - * since then). For command errors, that output is expected to be error - * messages associated with the error. + * In general any error causes the vin/vout stack to be closed either + * completely or down to the base vin/vout. vio->err_depth contains the + * default depth to close back to. An I/O error in either vin_base or + * vout_base will set the err_depth to 0. * - * A new "ebuf" fifo is created, and the location of the error is written to - * that fifo. Once the contents of the "ebuf" are output, the command line in - * which the error occurred should be the last thing output. + * The vio->ebuf contains all error messages collected so far for the vio, + * and will be output to the vout_base when the stack has been closed to + * that point. The vio->ebuf will then be emptied. * - * Any other error message is then appended to the ebuf. + * For command and parser errors: * - * For command errors, the tail of the vio->obuf (stuff after the end_mark) is - * moved to the ebuf. + * The current vio->obuf will have an end_mark. After the end_mark will be + * any output generated since the start of the current command (or any + * out_push since then). For command errors, that output is expected to be + * messages associated with the error. * - * Deals with: + * The location of the error is written to the vio->ebuf, and then the + * contents of the vio->obuf are moved to the end the vio->ebuf, possibly + * with other diagnostic information. * - * CMD_WARNING = TODO - * CMD_ERROR => + * For I/O errors: * - * CMD_ERR_PARSING, parser: general parser error - * CMD_ERR_NO_MATCH, parser: command/argument not recognised - * CMD_ERR_AMBIGUOUS, parser: more than on command matches - * CMD_ERR_INCOMPLETE, + * The contents of vio->obuf are left untouched -- the closing of the + * stack will do what it can with those. * - * CMD_IO_ERROR I/O -- failed :-( - * CMD_IO_TIMEOUT I/O -- timed out :-( + * The vio->ebuf will already contain the required error message(s). The + * vio->err_depth will have been set to close as far as vin_base/vout_base, + * or to close the vty completely. * - * In any event, need to separate out any output from the failing command, - * so that can report error location and type, before showing the error - * message(s). + * Deals with: + * + * CMD_WARNING command: general failed or not fully succeeded + * CMD_ERROR command: definitely failed + * + * CMD_ERR_PARSING parser: general parser error + * CMD_ERR_NO_MATCH parser: command/argument not recognised + * CMD_ERR_AMBIGUOUS parser: more than on command matches + * CMD_ERR_INCOMPLETE + * + * CMD_IO_ERROR I/O: error or time-out * * NB: does not expect to see all the possible CMD_XXX return codes (see * below), but treats all as a form of error ! @@ -1514,54 +1921,61 @@ uty_cmd_out_push(vio_vf vf, bool final) /* TODO: write errors & blocking */ static uint uty_cmd_failed(vty_io vio, cmd_return_code_t ret) { - ulen indent ; + ulen indent ; + uint depth ; VTY_ASSERT_LOCKED() ; - /* Process the vin stack to generate the error location(s) */ - if (vio->ebuf != NULL) - vio_fifo_clear(vio->ebuf, true) ; - else - vio->ebuf = vio_fifo_new(1000) ; - - indent = uty_show_error_context(vio->ebuf, vio->vin) ; + /* Stack depth to close back to. + * + * This could be overridden by the return code type. + */ + depth = vio->err_depth ; /* Now any additional error message if required */ + uty_cmd_get_ebuf(vio) ; + switch (ret) { case CMD_WARNING: + uty_show_error_context(vio->ebuf, vio->vin) ; + if (vio_fifo_tail_empty(vio->obuf)) vio_fifo_printf(vio->ebuf, "%% WARNING: non-specific warning\n") ; break ; case CMD_ERROR: + uty_show_error_context(vio->ebuf, vio->vin) ; + if (vio_fifo_tail_empty(vio->obuf)) vio_fifo_printf(vio->ebuf, "%% ERROR: non-specific error\n") ; break ; case CMD_ERR_PARSING: + indent = uty_show_error_context(vio->ebuf, vio->vin) ; cmd_get_parse_error(vio->ebuf, vio->vty->exec->parsed, indent) ; break ; case CMD_ERR_NO_MATCH: + uty_show_error_context(vio->ebuf, vio->vin) ; vio_fifo_printf(vio->ebuf, "%% Unknown command.\n") ; break; case CMD_ERR_AMBIGUOUS: + uty_show_error_context(vio->ebuf, vio->vin) ; vio_fifo_printf(vio->ebuf, "%% Ambiguous command.\n"); break; case CMD_ERR_INCOMPLETE: + uty_show_error_context(vio->ebuf, vio->vin) ; vio_fifo_printf(vio->ebuf, "%% Command incomplete.\n"); break; - case CMD_IO_ERROR: - break ; - - case CMD_IO_TIMEOUT: + case CMD_IO_ERROR: /* Diagnostic already posted to ebuf */ break ; default: + zlog_err("%s: unexpected return code (%d).", __func__, (int)ret) ; vio_fifo_printf(vio->ebuf, "%% Unexpected return code (%d).\n", (int)ret); break ; } ; @@ -1572,8 +1986,8 @@ uty_cmd_failed(vty_io vio, cmd_return_code_t ret) vio_fifo_copy_tail(vio->ebuf, vio->obuf) ; vio_fifo_back_to_end_mark(vio->obuf, true) ; - /* Decide what stack level to close back to. */ - return (vio->vty->type == VTY_TERMINAL) ? 1 : 0 ; // TODO I/O error ?? + /* Return what stack depth to close back to. */ + return depth ; } ; /*------------------------------------------------------------------------------ @@ -1650,10 +2064,29 @@ uty_show_error_context(vio_fifo ebuf, vio_vf vf) return indent ; } ; +/*------------------------------------------------------------------------------ + * If there is no vio->ebuf, make one + */ +extern vio_fifo +uty_cmd_get_ebuf(vty_io vio) +{ + VTY_ASSERT_LOCKED() ; + + if (vio->ebuf == NULL) + vio->ebuf = vio_fifo_new(1000) ; + + return vio->ebuf ; +} ; + /*============================================================================== * Configuration node/state handling * * At most one VTY may hold the configuration symbol of power at any time. + * + * Only at vin_depth == 1 may the symbol of power be acquired, and only at + * vin_depth <= 1 will the symbol of power be released. Inter alia, this + * means that the restoration of command context when an input pipe finishes + * does not have to worry about recovering or releasing the symbol of power. */ /*------------------------------------------------------------------------------ @@ -1664,41 +2097,54 @@ uty_show_error_context(vio_fifo ebuf, vio_vf vf) extern cmd_return_code_t vty_cmd_config_lock (vty vty) { + bool locked ; + VTY_LOCK() ; - uty_cmd_config_lock(vty) ; + locked = uty_cmd_config_lock(vty) ; VTY_UNLOCK() ; if (vty->config) return CMD_SUCCESS ; - vty_out (vty, "VTY configuration is locked by other VTY\n") ; + if (locked) + vty_out(vty, "VTY configuration is locked by other VTY\n") ; + else + vty_out(vty, "VTY configuration is not available\n") ; + return CMD_WARNING ; } ; /*------------------------------------------------------------------------------ * Attempt to gain the configuration symbol of power -- may already own it ! * - * Returns: true <=> now own the symbol of power (or already did). + * NB: cannot do this at any vin level except 1 ! */ -static void +static bool uty_cmd_config_lock (vty vty) { + VTY_ASSERT_LOCKED() ; + if (!host.config) /* If nobody owns the lock... */ { - host.config = true ; /* ...rope it... */ - vty->config = true ; + if (vty->vio->vin_depth == 1) + { + host.config = true ; /* ...rope it... */ + vty->config = true ; - do - ++host.config_brand ; /* ...update the brand... */ - while (host.config_brand == 0) ; + do + ++host.config_brand ; /* ...update the brand... */ + while (host.config_brand == 0) ; - vty->config_brand = host.config_brand ; /* ...brand it. */ + vty->config_brand = host.config_brand ; /* ...brand it. */ + } ; } else /* Somebody owns the lock... */ { if (vty->config) /* ...if we think it is us, check brand */ assert(host.config_brand == vty->config_brand) ; } ; + + return host.config ; } /*------------------------------------------------------------------------------ @@ -1717,10 +2163,11 @@ vty_cmd_config_lock_check(vty vty, node_type_t node) * Check that given node and ownership of configuration symbol of power * are mutually consistent. * - * If node > MAX_NON_CONFIG_NODE, must own the symbol of power. + * If node > MAX_NON_CONFIG_NODE, must own the symbol of power (unless + * vio->vin_true_depth == 0, in which case the node is irrelevant). * * If node <= MAX_NON_CONFIG_NODE, will release symbol of power, if own it, - * PROVIDED is at vin_depth <= 1 !! + * PROVIDED is at vin_true_depth <= 1 !! */ static void uty_cmd_config_lock_check(vty vty, node_type_t node) @@ -1730,11 +2177,11 @@ uty_cmd_config_lock_check(vty vty, node_type_t node) if (vty->config) { /* We think we own it, so we better had */ - assert(host.config) ; - assert(host.config_brand == vty->config_brand) ; + qassert(host.config) ; + qassert(host.config_brand == vty->config_brand) ; /* If no longer need it, release */ - if ((node <= MAX_NON_CONFIG_NODE) && (vty->vio->vin_depth <= 1)) + if ((node <= MAX_NON_CONFIG_NODE) && (vty->vio->vin_true_depth <= 1)) { host.config = false ; vty->config = false ; @@ -1744,9 +2191,12 @@ uty_cmd_config_lock_check(vty vty, node_type_t node) { /* We don't think we own it, so we had better not */ if (host.config) - assert(host.config_brand != vty->config_brand) ; + qassert(host.config_brand != vty->config_brand) ; - /* Also, node had better not require that we do. */ - assert(node <= MAX_NON_CONFIG_NODE) ; + /* Also, node had better not require that we do, noting that + * the node is irrelevant if the vin_true_depth is 0. + */ + if (vty->vio->vin_true_depth > 0) + qassert(node <= MAX_NON_CONFIG_NODE) ; } ; } ; |