summaryrefslogtreecommitdiffstats
path: root/lib/vty_command.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vty_command.c')
-rw-r--r--lib/vty_command.c1785
1 files changed, 1339 insertions, 446 deletions
diff --git a/lib/vty_command.c b/lib/vty_command.c
index 26a9ec6a..0ce31fdf 100644
--- a/lib/vty_command.c
+++ b/lib/vty_command.c
@@ -34,6 +34,7 @@
#include "vty_cli.h"
#include "vio_fifo.h"
#include "vty_io_file.h"
+#include "vty_io_term.h"
#include "list_util.h"
#include "qstring.h"
@@ -44,12 +45,19 @@
/*------------------------------------------------------------------------------
* Prototypes
*/
-static void uty_show_error_location(vio_vf vf) ;
-static void vty_cmd_failed(vty vty, cmd_return_code_t ret) ;
+
+static void uty_cmd_loop_prepare(vty_io vio) ;
+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 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 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:
+ * functions support those command loops: TODO update !!
*
* * uty_cmd_prepare()
*
@@ -63,7 +71,7 @@ static void vty_cmd_failed(vty vty, cmd_return_code_t ret) ;
*
* Hands command line over to the vty_cli_nexus message queue.
*
- * NB: from this point onwards, the vio is vio->cmd_running !
+ * NB: from this point onwards, the vio is vio->cmd_running ! TODO
*
* * vty_cmd_fetch_line()
*
@@ -87,216 +95,1021 @@ static void vty_cmd_failed(vty vty, cmd_return_code_t ret) ;
*
* If required, pops any vout(s).
*
- * * vty_cmd_loop_exit()
- *
- * When a command has completed, successfully or otherwise, this is
- * called to deal with any errors and return control to.
- *
*
+ */
+
+/*------------------------------------------------------------------------------
+ * Prepare to enter the config read command loop.
*
+ * Initialise exec object, and copy required settings from the current vin
+ * and vout.
+ */
+extern void
+vty_cmd_loop_prepare(vty vty)
+{
+ VTY_LOCK() ;
+
+ assert(vty->type == VTY_CONFIG_READ) ;
+
+ uty_cmd_loop_prepare(vty->vio) ; /* by vty->type & vty->node */
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Enter the command_queue command loop.
+ */
+extern void
+uty_cmd_loop_enter(vty_io vio)
+{
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
+
+ assert(vio->vty->type == VTY_TERMINAL) ;
+
+ uty_cmd_loop_prepare(vio) ; /* by vty->type & vty->node */
+
+ cq_loop_enter(vio->vty, vio->vty->node != NULL_NODE ? CMD_SUCCESS
+ : CMD_CLOSE) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Prepare to enter a command loop.
*
+ * Initialise cmd_exec object, and its cmd_context -- given vty->type and
+ * vty->node.
*/
+static void
+uty_cmd_loop_prepare(vty_io vio)
+{
+ 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 ;
+
+ if (vio->vty->node > MAX_NON_CONFIG_NODE)
+ uty_cmd_config_lock(vio->vty) ; /* TODO cannot fail !? */
+
+ uty_cmd_prepare(vio) ;
+} ;
/*------------------------------------------------------------------------------
- * After opening or closing a vin/vout object, update the vty->exec context.
+ * Signal to the command loop that some I/O has completed -- successfully, or
+ * with some I/O error (including time out).
+ *
+ * If the vio is in vc_waiting state -- so will be exec_hiatus -- send message
+ * so that command loop continues.
+ *
+ * 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.
+ *
+ * Accepts:
+ *
+ * CMD_SUCCESS -- if vc_waiting, passed in.
+ * otherwise, ignored
+ *
+ * CMD_WAITING -- ignored
+ *
+ * CMD_IO_ERROR -- if vc_waiting, passed in
+ * if vc_running, set vc_io_error_trap
+ *
+ * CMD_CLOSE -- if vc_waiting, passed in
+ * if vc_running, set vc_close_trap
+ * if vc_io_error_trap, set vc_close_trap
*/
extern void
-uty_cmd_prepare(vty_io vio)
+uty_cmd_signal(vty_io vio, cmd_return_code_t ret)
{
- cmd_exec exec = vio->vty->exec ;
+ VTY_ASSERT_LOCKED() ;
+
+ if (ret == CMD_WAITING)
+ return ;
- exec->parse_type = vio->vin->parse_type ;
+ assert((ret == CMD_SUCCESS) || (ret == CMD_IO_ERROR) || (ret == CMD_CLOSE)) ;
- exec->out_enabled = vio->vout->out_enabled ;
- exec->reflect_enabled = vio->vin->reflect_enabled &&
- vio->vout->out_enabled ;
+ switch (vio->state)
+ {
+ case vc_null:
+ zabort("invalid vc_null") ;
+ break ;
+
+ 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 ;
+
+ case vc_waiting: /* pass in the return code continue */
+ vio->state = vc_running ;
+ cq_continue(vio->vty, ret) ;
+ break ;
+
+ 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. */
+ break ;
+
+ case vc_closed:
+ zabort("invalid vc_closed") ;
+ break ;
+
+ default:
+ zabort("unknown vio->state") ;
+ break ;
+ } ;
} ;
/*------------------------------------------------------------------------------
- * This pushes the command line into the vty_cli_message queue, where it will
- * be parsed and executed etc.
+ * Reset the command loop.
+ *
+ * This is used by uty_close() to try to shut down the command loop.
+ *
+ * Does nothing if already closed or never got going.
+ *
+ * "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.
*
- * NB: from this moment on, vio->cmd_running is true, so even if the vty is
- * closed, it cannot be fully closed and then reaped until the flag
- * is cleared.
+ * If not curtains, then push a CMD_CLOSE into the command loop, to bring it
+ * to a shuddering halt.
*
- * While vio->cmd_running the command side is responsible for the vty
- * and the vty->exec stuff.
+ * Will leave the vio->state:
+ *
+ * vc_running -- a CMD_CLOSE has been passed in (was vc_waiting).
+ * Will not be the case if "curtains".
+ *
+ * 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 -- 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.
+ *
+ * vc_null -- never been kissed
+ * vc_closed -- was already closed
+ *
+ * No other states are possible.
*/
-extern cmd_return_code_t
-uty_cmd_dispatch(vty_io vio, cmd_do_t to_do, qstring line)
+extern void
+uty_cmd_loop_close(vty_io vio, bool curtains)
{
VTY_ASSERT_LOCKED() ;
- vio->cmd_running = true ;
- uty_cmd_prepare(vio) ; /* make sure */
+ if ((vio->state == vc_null) || (vio->state == vc_closed))
+ return ;
- cq_dispatch(vio->vty, to_do, line) ;
+ if (curtains)
+ {
+ if (!vio->blocking)
+ cq_revoke(vio->vty) ; /* collect any outstanding message */
- return CMD_WAITING ;
+ vio->state = vc_stopped ;
+ }
+ else
+ uty_cmd_signal(vio, CMD_CLOSE) ;
} ;
/*------------------------------------------------------------------------------
- * Handle a "special" command -- anything not cmd_do_command
+ * Exit command loop.
+ *
+ * Final close of the VTY, giving a reason, if required.
+ */
+extern void
+vty_cmd_loop_exit(vty vty)
+{
+ VTY_LOCK() ;
+
+ VTY_ASSERT_CAN_CLOSE(vty) ;
+
+ /* Make sure no longer holding the config symbol of power */
+ uty_cmd_config_lock_check(vty, NULL_NODE) ;
+
+ /* Can now close the vty */
+ uty_close(vty->vio, NULL, false) ; /* not curtains */
+
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Handle a "special" command -- anything not cmd_do_command.
+ *
+ * These "commands" are related to VTY_TERMINAL CLI only.
*/
extern cmd_return_code_t
vty_cmd_special(vty vty)
{
cmd_return_code_t ret ;
+ cmd_do_t to_do ;
+ node_type_t next_node ;
ret = CMD_SUCCESS ;
- /* All other nodes... */
- switch (vty->exec->to_do)
- {
- case cmd_do_nothing:
- break ;
- case cmd_do_eof:
- ret = CMD_CLOSE ;
- break ;
+ to_do = vty->exec->action->to_do ;
- case cmd_do_command:
- zabort("invalid cmd_do_command") ;
- break ;
+ /* Note that the next node handling is special here... we establish
+ * the next node explicitly here -- there is no parse operation to preset
+ * what CMD_SUCCESS next node will be.
+ */
- case cmd_do_ctrl_d:
- zabort("invalid cmd_do_ctrl_d") ;
- break ;
+ vty->node = vty->exec->context->node ; /* as per all commands */
+ next_node = vty->exec->context->node ; /* by default. */
- case cmd_do_ctrl_c:
- case cmd_do_ctrl_z:
- ret = cmd_end(vty) ;
- break ;
+ switch (to_do & cmd_do_mask)
+ {
+ case cmd_do_nothing:
+ break ;
+
+ case cmd_do_eof:
+ if (vty->type == VTY_TERMINAL)
+ vty_out(vty, "%% Terminal closed\n") ;
+
+ ret = CMD_CLOSE ;
+ break ;
+
+ case cmd_do_timed_out:
+ if (vty->type == VTY_TERMINAL)
+ vty_out(vty, "%% Terminal timed out\n") ;
+
+ ret = CMD_CLOSE ;
+ break ;
+
+ case cmd_do_command:
+ if ((to_do & cmd_do_auth) != 0)
+ ret = vty_cmd_auth(vty, &next_node) ;
+ else
+ zabort("invalid cmd_do_command") ;
+ break ;
+
+ case cmd_do_ctrl_d:
+ if ((to_do & cmd_do_auth) != 0)
+ next_node = cmd_node_exit_to(vty->node) ;
+ else
+ 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 ;
+
+ default:
+ zabort("unknown or invalid cmd_do") ;
+ } ;
- default: /* must now be cmd_do_auth or cmd_do_auth_enable */
- VTY_LOCK() ;
- ret = uty_cli_auth(vty->vio->vin->cli) ;
- VTY_UNLOCK() ;
- } ;
+ /* Now worry about changing node */
+ if (ret == CMD_CLOSE)
+ next_node = NULL_NODE ;
+ else if (next_node == NULL_NODE)
+ ret = CMD_CLOSE ;
+ if (next_node != vty->exec->context->node)
+ {
+ vty->exec->context->node = next_node ;
+ vty_cmd_config_lock_check(vty, next_node) ;
+ } ;
return ret ;
} ;
/*------------------------------------------------------------------------------
- * Fetch the next command line to be executed.
+ * Check that can enter AUTH_ENABLE_NODE.
+ *
+ * Must be: VTY_TERMINAL
+ *
+ * and: no pipes, in or out -- so talking directly to terminal
+ *
+ * and: be VIEW_NODE if there is no enable password.
+ *
+ * Note that "can_enable" <=> vin_depth == 1 and VTY_TERMINAL (or other VTY
+ * that can authenticate). But it may not => vout_depth == 0.
+ */
+extern cmd_return_code_t
+vty_cmd_can_auth_enable(vty vty)
+{
+ cmd_return_code_t ret ;
+
+ VTY_LOCK() ;
+
+ assert(vty->exec->parsed->nnode == AUTH_ENABLE_NODE) ;
+ assert((vty->exec->context->onode == VIEW_NODE) ||
+ (vty->exec->context->onode == RESTRICTED_NODE)) ;
+
+ ret = CMD_WARNING ;
+
+ if (vty->type != VTY_TERMINAL)
+ uty_out(vty->vio, "%% Wrong VTY type (%d) for 'enable'", vty->type) ;
+ else if ((vty->exec->context->onode != VIEW_NODE)
+ && (host.enable == NULL))
+ uty_out(vty->vio, "%% cannot enable because there is no enable password") ;
+ else if ((vty->vio->vin_depth != 1) || (vty->vio->vout_depth != 1))
+ uty_out(vty->vio,
+ "%% cannot authenticate for 'enable' in a pipe command") ;
+ else
+ {
+ assert(vty->exec->context->can_auth_enable) ;
+ ret = CMD_SUCCESS ;
+ } ;
+
+ VTY_UNLOCK() ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Authentication of vty
+ *
+ * Note that if the AUTH_NODE password fails too many times, the vty is
+ * closed.
+ *
+ * Quagga authentication is a touch complicated. The following appear to be
+ * the rules:
+ *
+ * 1. host.no_password_check -- set by "no login" command
+ *
+ * Means that a VTY_TERMINAL can start without going through any
+ * password check -- including when no host.password is configured.
+ *
+ * Note that this does not affect the authentication for enable, except
+ * at startup of a VTY_TERMINAL...
+ *
+ * When a VTY_TERMINAL starts:
+ *
+ * * if host.restricted_mode -> RESTRICTED_NODE
+ *
+ * * else if host.advanced -> ENABLE_NODE
*
- * If possible, get another command line to execute -- pops pipes as required
- * and reflects command line when have one, if required.
+ * This is whether or not an enable password exists.
*
- * Action depends on the type of the current input:
+ * * otherwise -> VIEW_NODE
*
- * VIN_NONE invalid
+ * So being in RESTRICTED_NODE <=> was host.no_password_check &&
+ * host.restricted_mode when the VTY_TERMINAL was started.
+ *
+ * 2. host.restricted_mode -- set by "anonymous restricted"
+ *
+ * Significant only at VTY_TERMINAL start, and only if no_password_check.
+ *
+ * NB: if the enable password is NULL, there is not much point in
+ * RESTRICTED_NODE, since ENABLE_NODE is but one command away.
+ *
+ * NB: that behaviour is is modified here... if is in RESTRICTED_MODE,
+ * will not authenticate AUTH_ENABLE_NODE if there is no enable
+ * password.
+ *
+ * Note that the check is left to this point, so that is completed
+ * atomically. Elsewhere, will refuse to enter ENABLE_NODE from
+ * RESTRICTED_NODE if no enable password. By the time we get here
+ * it is (just) possible that the situation has changed.
+ *
+ * 3. host.advanced -- set by "service advanced-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"
+ *
+ * Unless no_password_check, if there is no password, you cannot start
+ * a vty.
+ *
+ * 5. host.enable -- set by "enable password xxx"
+ *
+ * If this is set, then must authenticate against it for vty to reach
+ * ENABLE_NODE.
+ *
+ * If it is not set, then can enter ENABLE_NODE at any time.
+ *
+ * If AUTH_ENABLE_NODE fails, falls back to the node we came from -- which has
+ * been planted in the context for this purpose. (If host.restricted_mode has
+ * changed since the vty started, could argue this should change where should
+ * fall back to... but that seems unnecessarily complicated.)
+ *
+ * Returns: CMD_SUCCESS -- OK, one way or another
+ * CMD_WARNING -- with error message sent to output
+ * CMD_CLOSE -- too many password failures
+ */
+static cmd_return_code_t
+vty_cmd_auth(vty vty, node_type_t* p_next_node)
+{
+ char *crypt (const char *, const char *);
+
+ char* passwd = NULL ;
+ bool encrypted = false ;
+ bool enable = false ;
+ bool advanced ;
+ bool pass ;
+ cmd_return_code_t ret ;
+ cmd_exec exec ;
+ cmd_context context ;
+
+ exec = vty->exec ;
+ context = exec->context ;
+
+ /* Select the password we need to check against. */
+ passwd = NULL ;
+ encrypted = false ;
+ enable = false ;
+ advanced = false ;
+
+ pass = false ;
+
+ VTY_LOCK() ; /* while access host.xxx */
+
+ switch (vty->node)
+ {
+ case AUTH_NODE:
+ passwd = host.password ;
+ encrypted = host.password_encrypted ;
+ enable = false ;
+
+ context->onode = NULL_NODE ; /* started from nowhere */
+
+ if (host.advanced && (host.enable == NULL))
+ {
+ context->tnode = ENABLE_NODE ;
+ advanced = true ;
+ }
+ else
+ {
+ context->tnode = VIEW_NODE ;
+ advanced = false ;
+ } ;
+ break ;
+
+ case AUTH_ENABLE_NODE:
+ passwd = host.enable ;
+ encrypted = host.enable_encrypted ;
+ enable = true ;
+ advanced = false ;
+
+ assert((context->onode == VIEW_NODE) ||
+ (context->onode == RESTRICTED_NODE)) ;
+ break ;
+
+ default:
+ zabort("unknown vty->node") ;
+ break ;
+ } ;
+
+ VTY_UNLOCK() ;
+
+ /* Check against selected password (if any) */
+ if (passwd == NULL)
+ {
+ /* Here we reject any attempt to AUTH_NODE against an empty password.
+ *
+ * Otherwise, is dealing with the (largely) theoretical case of
+ * This fails any attempt to AUTH_ENABLE against an empty password
+ * if was in RESTRICTED_NODE.
+ *
+ * This passes the theoretically possible case of enable in VIEW_NODE,
+ * when there was an enable password set when the enable command was
+ * executed, but it has since been unset !
+ */
+ pass = context->onode == VIEW_NODE ;
+ }
+ else
+ {
+ char* candidate = qs_make_string(exec->action->line) ;
+
+ if (encrypted)
+ candidate = crypt(candidate, passwd) ;
+
+ pass = (strcmp(candidate, passwd) == 0) ;
+ } ;
+
+ /* Now worry about the result */
+ ret = CMD_SUCCESS ; /* so far, so good */
+
+ if (pass)
+ {
+ *p_next_node = context->tnode ;
+
+ if (enable || advanced)
+ context->can_enable = true ;
+
+ if (*p_next_node == CONFIG_NODE)
+ {
+ ret = vty_cmd_config_lock(vty) ;
+ if (ret == CMD_WARNING)
+ *p_next_node = ENABLE_NODE ;
+ } ;
+
+ exec->password_failures = 0 ; /* forgive any failures */
+ }
+ else
+ {
+ bool no_more = false ;
+
+ if (passwd == NULL)
+ {
+ /* Cannot possibly authenticate ! */
+ no_more = true ;
+ vty_out(vty, "%% No password is set, cannot authenticate!\n") ;
+ }
+ else
+ {
+ exec->password_failures++ ;
+
+ if (exec->password_failures >= 3)
+ {
+ no_more = true ;
+ vty_out(vty, "%% Bad passwords, too many failures!\n") ;
+
+ exec->password_failures = 0 ; /* allow further attempts */
+ } ;
+ } ;
+
+ if (no_more)
+ {
+ if (!enable)
+ {
+ *p_next_node = NULL_NODE ;
+ ret = CMD_CLOSE ;
+ }
+ else
+ {
+ *p_next_node = context->onode ;
+ ret = CMD_WARNING ;
+ } ;
+ } ;
+ } ;
+
+ return ret ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Fetch the next command line to be executed.
*
- * VIN_TERM ) return CMD_EOF
- * VIN_VTYSH )
+ * Returns: CMD_SUCCESS => OK -- have a line ready to be processed.
*
- * VIN_FILE )
- * VIN_PIPE ) fetch another line if can.
- * VIN_CONFIG )
+ * vty->exec->line points at the line
+ * vty->exec->to_do says what to do with it
*
- * VIN_DEV_NULL nothing: return CMD_EOF
+ * or: CMD_WAITING => OK -- but waiting for command line to arrive
+ * <=> non-blocking
*
- * Returns: CMD_SUCCESS => OK -- have a line ready to be processed.
+ * or: CMD_EOF => OK -- but nothing to fetch from the current vin
*
- * vty->exec->line points at the line.
+ * Need to close the current vin and pop vin/vout
+ * as necessary.
*
- * or: CMD_EOF => nothing to fetch -- note that if pops back to the
- * VIN_TERM or VIN_VTYSH level, then will return
- * CMD_EOF.
+ * or: CMD_HIATUS => OK -- but need to close one or more vin/vout
+ * to adjust stack.
*
- * If was VIN_TERM or VIN_VTYSH, signals CMD_EOF to
- * exit from the command loop.
+ * or: any other return code from the current vin when it attempts to
+ * fetch another command line -- including I/O error or timeout.
*
- * or: CMD_WAITING => OK -- but waiting for command line to arrive.
+ * NB: can be called from any thread -- because does no closing of files or
+ * anything other than read/write.
*
- * or: ....
+ * TODO -- dealing with states other than vf_open !!
*/
extern cmd_return_code_t
vty_cmd_fetch_line(vty vty)
{
cmd_return_code_t ret ;
vty_io vio ;
- qstring* p_line ;
+ vio_vf vf ;
+ cmd_exec exec ;
VTY_LOCK() ;
- vio = vty->vio ; /* once locked */
- p_line = &vty->exec->line ;
+ vio = vty->vio ; /* once locked */
+ exec = vty->exec ;
- *p_line = NULL ;
- vty->exec->to_do = cmd_do_nothing ;
+ cmd_action_clear(exec->action) ; /* tidy */
- while (1)
- {
- vio_vf vf ;
+ vf = vio->vin ;
- vf = vio->vin ;
+ ret = CMD_SUCCESS ; /* assume all is well */
- if (vf->vin_state != vf_open)
+ /* 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)
{
- switch (vf->vin_state)
- {
- case vf_closed:
- case vf_eof:
- ret = CMD_EOF ;
- break ;
+ 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 ;
+ } ;
+ } ;
- case vf_error:
- ret = CMD_IO_ERROR ;
- break ;
+ VTY_UNLOCK() ;
- case vf_closing:
- ret = CMD_CLOSE ;
- break ;
+ return ret ;
+} ;
- default:
- zabort("invalid vf->vin_state") ;
- break ;
- } ;
- break ;
- } ;
+/*------------------------------------------------------------------------------
+ * Deal with return code at the "exec_hiatus" point in the command loop.
+ *
+ * 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
+ * CMD_EMPTY, but otherwise this function must deal with:
+ *
+ * CMD_HIATUS -- something requires attention, eg:
+ *
+ * - the vout_depth > vin_depth, so the vout needs to
+ * be closed and popped.
+ *
+ * - the vio->state needs to be checked.
+ *
+ * CMD_EOF -- from vty_cmd_fetch_line() => current vin has hit eof,
+ * and must be closed and popped.
+ *
+ * CMD_CLOSE -- from a command return (or otherwise) => must close
+ * and pop the current vin (same as CMD_EOF, really).
+ *
+ * CMD_WAITING -- from vty_cmd_fetch_line() or elsewhere => must go to
+ * vc_waiting and the command loop MUST exit.
+ *
+ * CMD_SUCCESS -- see below
+ *
+ * CMD_EMPTY -- should not appear, but is treated as CMD_SUCCESS
+ *
+ * anything else -- treated as a command or I/O or other error.
+ *
+ * The handling of errors depends on the type of error:
+ *
+ * * command errors will cause all levels of the stack other than vin_base
+ * and vout_base to be closed, and a suitable error message output to the
+ * vout_base.
+ *
+ * Inputs are closed without dealing with any further input and discarding
+ * any buffered input.
+ *
+ * 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 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.
+ *
+ * This function will return:
+ *
+ * CMD_SUCCESS => OK -- can try and fetch a command line again.
+ *
+ * state == vc_running
+ *
+ * CMD_WAITING => OK -- but waiting for input to arrive or for something
+ * to be completely closed. => non-blocking
+ *
+ * state == vc_waiting
+ *
+ * CMD_EOF => OK -- but nothing more to fetch, close the vty and exit
+ * command loop.
+ * <=> the vin_base is now closed.
+ *
+ * state == vc_stopped
+ *
+ * CMD_IO_ERROR => some error has occurred while closing stuff.
+ *
+ * state == vc_io_error_trap
+ *
+ * And nothing else.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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
+ * hint of trouble.
+ *
+ * All this TODO (after discussion of error handling)
- switch (vf->vin_type)
- {
- case VIN_NONE:
- zabort("invalid VIN_NONE") ;
- break ;
+ * 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_LOCK() ;
+ VTY_ASSERT_CAN_CLOSE(vty) ;
- case VIN_TERM:
- case VIN_VTYSH:
- case VIN_DEV_NULL:
- ret = CMD_EOF ;
- break ;
+ ret = uty_cmd_hiatus(vty->vio, ret) ;
- /* fall through */
+ VTY_UNLOCK() ;
+ return ret ;
+} ;
- case VIN_FILE:
- case VIN_PIPE:
- case VIN_CONFIG:
- ret = uty_file_fetch_command_line(vf, p_line) ;
+/*------------------------------------------------------------------------------
+ * Inside of vty_cmd_hiatus() -- can return at any time.
+ */
+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:
+ *
+ * vc_io_error_trap => an I/O error has been posted asynchronously
+ *
+ * set state to vc_running and return code to
+ * the pending CMD_IO_ERROR.
+ *
+ * vc_close_trap => the vty has been reset asynchronously
+ *
+ * set state to vc_running and return code to
+ * the pending CMD_CLOSE.
+ *
+ * vc_stopped )
+ * vc_closed ) invalid -- cannot be here in this state
+ * vc_null )
+ */
+ switch (vio->state)
+ {
+ case vc_null:
+ case vc_stopped:
+ case vc_closed:
+ zabort("invalid vc_xxxx") ;
+ break ;
+
+ case vc_running:
+ case vc_waiting:
+ break ;
+
+ case vc_io_error_trap:
+ ret = CMD_IO_ERROR ; /* convert pending IO error */
+ break ;
+
+ case vc_close_trap:
+ ret = CMD_CLOSE ; /* convert pending close */
+ break ;
+
+ default:
+ zabort("unknown vio->state") ;
+ break ;
+ } ;
- if (ret == CMD_SUCCESS)
- {
- vty->exec->to_do = cmd_do_command ;
- }
- else if ((ret == CMD_EOF) && (vio->vin_depth > 0))
+ vio->state = vc_running ; /* running in hiatus */
+
+ /* (1) Handle the return code.
+ *
+ * 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.
+ *
+ * Note that CMD_WAITING is immediately returned !
+ */
+ switch (ret)
+ {
+ case CMD_SUCCESS:
+ case CMD_EMPTY:
+ case CMD_HIATUS:
+ break ;
+
+ case CMD_WAITING:
+ assert(!vio->blocking) ;
+ vio->state = vc_waiting ;
+ return ret ; /* <<< exit here on CMD_WAITING */
+
+ case CMD_EOF:
+ case CMD_CLOSE:
+ uty_out_accept(vio) ; /* accept any buffered remarks. */
+ assert(vio->real_depth > 0) ;
+ --vio->real_depth ;
+ break ;
+
+ 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 ;
+ } ;
+
+ ret = CMD_SUCCESS ; /* OK, so far */
+
+ /* (2) 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
+ * which has been buffered. The only difficulty with closing inputs
+ * is VIN_PIPE, where the "return" input (from the child stderr) may
+ * not yet have finished.
+ *
+ * For blocking vio, close operations will either complete or fail.
+ *
+ * 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.
+ *
+ * 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.
+ */
+ while ((vio->vin_depth > vio->real_depth) && (ret == CMD_SUCCESS))
+ ret = uty_vin_pop(vio, vio->err_hard, vio->vty->exec->context) ;
+
+ /* (3) And, do we need to close one or more vout, or are we waiting for
+ * one to close ? If so:
+ *
+ * Any output after the current end_mark is discarded. Note that we
+ * do not actually close the vout_base.
+ *
+ * In all cases we push any remaining output and collect any remaining
+ * pipe return, and collect child 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.
+ */
+ while (ret == CMD_SUCCESS)
+ {
+ 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)
{
- uty_vin_close(vio) ;
- uty_cmd_prepare(vio) ;
- continue ;
+ vio_fifo_copy(vio->obuf, vio->ebuf) ;
+ vio->ebuf = vio_fifo_reset(vio->ebuf, free_it) ;
+
+ ret = uty_cmd_out_push(vio->vout, vio->err_hard) ;
+
+ if (ret != CMD_SUCCESS)
+ break ;
} ;
- break ;
+ } ;
- default:
- zabort("unknown vin_type") ;
- } ;
+ if (vio->vout_depth == 0)
+ assert(vio->vout->depth_mark == 0) ;
- break ;
+ if (vio->vin_depth < vio->vout->depth_mark)
+ ret = uty_vout_pop(vio, vio->err_hard) ;
+ else
+ break ;
} ;
- VTY_UNLOCK() ;
+ /* (4) Quit now if not successful on stack adjustment
+ */
+ if (ret != CMD_SUCCESS)
+ {
+ if (ret == CMD_WAITING)
+ {
+ assert(!vio->blocking) ;
+ vio->state = vc_waiting ;
+ return ret ; /* <<< exit here on CMD_WAITING */
+ } ;
- return ret ;
+ if (ret == CMD_IO_ERROR)
+ {
+ vio->state = vc_io_error_trap ;
+ return ret ;
+ } ;
+
+ 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 ;
+ } ;
+
+ /* (6) All ready now to continue processing commands.
+ */
+ uty_cmd_prepare(vio) ; /* update vty->exec state */
+
+ return CMD_SUCCESS ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * 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->out_suppress = (vio->vty->type == VTY_CONFIG_READ) &&
+ (vio->vout_depth == 1) ;
+ exec->reflect = exec->context->reflect_enabled && !exec->out_suppress ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * 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() ;
+
+ vty->exec->context->full_lex = full_lex ;
+
+ VTY_UNLOCK() ;
} ;
/*------------------------------------------------------------------------------
@@ -304,127 +1117,249 @@ vty_cmd_fetch_line(vty vty)
*
* 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.
*/
-extern void
+extern cmd_return_code_t
vty_cmd_reflect_line(vty vty)
{
- vio_fifo obuf ;
- qstring line ;
+ cmd_return_code_t ret ;
VTY_LOCK() ;
- obuf = vty->vio->obuf ; /* once locked */
- line = vty->exec->line ;
+ 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') ;
+ vio_fifo_put_bytes(obuf, qs_char_nn(line), qs_len_nn(line)) ;
+ vio_fifo_put_byte(obuf, '\n') ;
- uty_out_accept(vty->vio) ;
+ ret = uty_cmd_out_push(vty->vio->vout, false) ; /* not final */
+ } ;
VTY_UNLOCK() ;
+
+ return ret ;
} ;
/*------------------------------------------------------------------------------
- * Open the given file as in in pipe, if possible.
+ * Set the vio->depth_mark -- about to push vin and/or vout
+ */
+extern void
+uty_cmd_depth_mark(vty_io vio)
+{
+ vio->depth_mark = vio->vin_depth ;
+}
+
+/*------------------------------------------------------------------------------
+ * Open the given file as an in pipe, if possible.
*
* Puts error messages to vty if fails.
+ *
+ * NB: saves the current context to the current vin, before opening and pushing
+ * the new one.
*/
extern cmd_return_code_t
-vty_cmd_open_in_pipe_file(vty vty, qstring name, bool reflect)
+uty_cmd_open_in_pipe_file(vty_io vio, cmd_context context,
+ qstring name, cmd_pipe_type_t type)
{
cmd_return_code_t ret ;
- VTY_LOCK() ;
+ VTY_ASSERT_LOCKED() ;
- ret = uty_file_read_open(vty->vio, name, reflect) ;
+ if (vio->state != vc_running)
+ ret = CMD_HIATUS ;
+ else
+ {
+ ret = uty_file_read_open(vio, name, context) ;
- if (ret == CMD_SUCCESS)
- uty_cmd_prepare(vty->vio) ;
+ if (ret == CMD_SUCCESS)
+ {
+ context->reflect_enabled = (type & cmd_pipe_reflect) != 0 ;
+ context->parse_strict = true ;
- VTY_UNLOCK() ;
+ uty_cmd_prepare(vio) ;
+ } ;
+ } ;
return ret ;
} ;
+/*------------------------------------------------------------------------------
+ * Run the given shell command as an in pipe, if possible.
+ *
+ * Puts error messages to vty if fails.
+ *
+ * NB: saves the current context to the current vin, before opening and pushing
+ * the new one.
+ */
extern cmd_return_code_t
-vty_cmd_open_in_pipe_shell(vty vty)
+uty_cmd_open_in_pipe_shell(vty_io vio, cmd_context context, qstring command,
+ cmd_pipe_type_t type)
{
- return CMD_SUCCESS ;
+ cmd_return_code_t ret ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ if (vio->state != vc_running)
+ ret = CMD_HIATUS ;
+ else
+ {
+ ret = uty_pipe_read_open(vio, command, context) ;
+
+ if (ret == CMD_SUCCESS)
+ {
+ context->reflect_enabled = (type & cmd_pipe_reflect) != 0 ;
+ context->parse_strict = true ;
+
+ uty_cmd_prepare(vio) ;
+ } ;
+ } ;
+
+ return ret ;
} ;
+/*------------------------------------------------------------------------------
+ * Open the given file as an out pipe, if possible.
+ *
+ * Puts error messages to vty if fails.
+ */
extern cmd_return_code_t
-vty_cmd_open_out_pipe_file(vty vty, qstring name, bool append)
+uty_cmd_open_out_pipe_file(vty_io vio, cmd_context context, qstring name,
+ cmd_pipe_type_t type)
{
cmd_return_code_t ret ;
- VTY_LOCK() ;
+ if (vio->state != vc_running)
+ ret = CMD_HIATUS ;
+ else
+ {
+ ret = uty_file_write_open(vio, name,
+ ((type & cmd_pipe_append) != 0), context) ;
+ if (ret == CMD_SUCCESS)
+ uty_cmd_prepare(vio) ;
+ } ;
- ret = uty_file_write_open(vty->vio, name, append) ;
+ return ret ;
+} ;
- if (ret == CMD_SUCCESS)
- uty_cmd_prepare(vty->vio) ;
+/*------------------------------------------------------------------------------
+ * Open the given shell command as an out pipe, if possible.
+ *
+ * Puts error messages to vty if fails.
+ */
+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_return_code_t ret ;
- VTY_UNLOCK() ;
+ if (vio->state != vc_running)
+ ret = CMD_HIATUS ;
+ else
+ {
+ ret = uty_pipe_write_open(vio, command,
+ ((type & cmd_pipe_shell_only) !=0)) ;
+
+ if (ret == CMD_SUCCESS)
+ uty_cmd_prepare(vio) ;
+ } ;
return ret ;
} ;
+/*------------------------------------------------------------------------------
+ * Open "/dev/null" as an out pipe, if possible.
+ *
+ * Puts error messages to vty if fails.
+ */
extern cmd_return_code_t
-vty_cmd_open_out_dev_null(vty vty)
+uty_cmd_open_out_dev_null(vty_io vio)
{
+ cmd_return_code_t ret ;
vio_vf vf ;
- VTY_LOCK() ;
+ VTY_ASSERT_LOCKED() ;
- vf = uty_vf_new(vty->vio, "dev_null", -1, vfd_none, vfd_io_none) ;
- uty_vout_open(vty->vio, vf, VOUT_DEV_NULL, NULL, NULL, 0) ;
+ if (vio->state != vc_running)
+ ret = CMD_HIATUS ;
+ else
+ {
+ vf = uty_vf_new(vio, "dev_null", -1, vfd_none, vfd_io_none) ;
+ uty_vout_push(vio, vf, VOUT_DEV_NULL, NULL, NULL, 0) ;
- vf->out_enabled = false ;
+ uty_cmd_prepare(vio) ;
- VTY_UNLOCK() ;
- return CMD_SUCCESS ;
+ ret = CMD_SUCCESS ;
+ } ;
+
+ return ret ;
} ;
-extern cmd_return_code_t
-vty_cmd_open_out_pipe_shell(vty vty)
+/*------------------------------------------------------------------------------
+ * Complete the given file name, if not rooted.
+ *
+ * Returns: given or new qpath (if given was NULL)
+ */
+extern qpath
+uty_cmd_path_name_complete(qpath dst, const char* name, cmd_context context)
{
- return CMD_SUCCESS ;
+ if (*name == '/')
+ return qpath_set(dst, name) ; /* done if is rooted */
+
+ 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 */
+
+ return qpath_append_str(dst, name) ; /* create the full path */
} ;
/*------------------------------------------------------------------------------
* Command has completed successfully.
*
- * An output generated by the command is now pushed if exec->out_enabled,
+ * An output generated by the command is now pushed unless exec->out_suppress,
* or discarded.
- *
- * If the vin_depth < vout_depth, then close vouts until the depth is equal.
*/
extern cmd_return_code_t
vty_cmd_success(vty vty)
{
+ cmd_return_code_t ret ;
vty_io vio ;
VTY_LOCK() ;
vio = vty->vio ; /* once locked */
- if (!vio_fifo_empty_tail(vio->obuf))
- {
- if (vty->exec->out_enabled)
- uty_cmd_out_push(vio) ;
- else
- uty_out_clear(vio) ;
- } ;
+ ret = CMD_SUCCESS ;
- /* Deal with closing out pipe on individual command */
- while (vio->vout_depth > vio->vin_depth)
+ if (vio->state != vc_running)
+ ret = CMD_HIATUS ;
+ else
{
- uty_vout_close(vio, false) ; /* ignore failures TODO */
- uty_cmd_prepare(vio) ; /* set new state */
+ if (!vio_fifo_tail_empty(vio->obuf))
+ {
+ if (!vty->exec->out_suppress)
+ ret = uty_cmd_out_push(vio->vout, false) ; /* not final */
+ else
+ uty_out_clear(vio) ;
+ } ;
} ;
VTY_UNLOCK() ;
- return CMD_SUCCESS ;
+ return ret ;
} ;
/*------------------------------------------------------------------------------
@@ -432,12 +1367,21 @@ vty_cmd_success(vty vty)
*
* See uty_cmd_out_push() below.
*/
-extern void
+extern cmd_return_code_t
vty_cmd_out_push(vty vty)
{
+ cmd_return_code_t ret ;
+
VTY_LOCK() ;
- uty_cmd_out_push(vty->vio) ;
+
+ if (vty->vio->state != vc_running)
+ ret = CMD_HIATUS ;
+ else
+ ret = uty_cmd_out_push(vty->vio->vout, false) ; /* not final */
+
VTY_UNLOCK() ;
+
+ return ret ;
} ;
/*------------------------------------------------------------------------------
@@ -449,149 +1393,87 @@ vty_cmd_out_push(vty vty)
*
* Advances the end_mark past the stuff pushed.
*
- * NB: takes no notice of vf->out_enabled... this is used when outputting
- * any error messages when reading the configuration file.
+ * 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.
*/
-extern void
-uty_cmd_out_push(vty_io vio)
+extern cmd_return_code_t
+uty_cmd_out_push(vio_vf vf, bool final) /* TODO: write errors & blocking */
{
cmd_return_code_t ret ;
- vio_vf vf ;
- vf = vio->vout ;
+ VTY_ASSERT_LOCKED() ;
- assert(vio->obuf == vf->obuf) ;
+ vio_fifo_step_end_mark(vf->obuf) ; /* advance the end mark */
- uty_out_accept(vio) ; /* advance the end mark */
+ ret = CMD_SUCCESS ;
- if (vf->vout_state != vf_open)
+ switch (vf->vout_state)
{
- switch (vf->vout_state)
- {
- case vf_closed:
- case vf_eof:
- ret = CMD_EOF ; /* TODO what's to do ? */
- break ;
-
- case vf_error:
- ret = CMD_IO_ERROR ;
- break ;
-
- case vf_closing:
- ret = CMD_CLOSE ; /* TODO continue with output ? */
- break ;
-
- default:
- zabort("invalid vf->vout_state") ;
- break ;
- } ;
-
- return ; /* TODO errors ?? */
- } ;
-
- switch (vio->vout->vout_type)
- {
- case VOUT_NONE:
- zabort("invalid vout_none") ;
- break ;
-
- case VOUT_TERM:
- uty_cli_out_push(vf->cli) ;
- break ;
-
- case VOUT_VTYSH:
- /* Kick the writer */
- break ;
-
- case VOUT_FILE:
- uty_file_out_push(vf) ;
- break ;
-
- case VOUT_PIPE:
- /* Kick the writer */
- break ;
-
- case VOUT_CONFIG:
- /* Kick the writer */
- break ;
-
- case VOUT_DEV_NULL:
- uty_out_clear(vio) ; /* clear fifo, keep end mark */
- break ;
-
- case VOUT_STDOUT:
- vio_fifo_fwrite(vio->obuf, stdout) ;
- break ;
-
- case VOUT_STDERR:
- vio_fifo_fwrite(vio->obuf, stderr) ;
- break ;
+ case vf_open:
+ case vf_closing:
+ switch (vf->vout_type)
+ {
+ case VOUT_NONE:
+ zabort("invalid vout_none") ;
+ break ;
- default:
- zabort("unknown vout_type") ;
- } ;
-} ;
+ case VOUT_TERM:
+ ret = uty_term_out_push(vf, final) ;
+ break ;
-/*------------------------------------------------------------------------------
- * The command loop has exited, with the given return code.
- *
- * There are a number of return codes that are treated as success. On any
- * of those, does vty_cmd_success() (which has no effect if already done).
- *
- * If not a success return code, then close down any pipe-work, and create
- * suitable error message.
- *
- * If is any form of success, returns CMD_SUCCESS -- otherwise returns the
- * return code as given.
- */
-extern cmd_return_code_t
-vty_cmd_loop_exit(vty vty, cmd_return_code_t ret)
-{
- bool close ;
+ case VOUT_VTYSH:
+ /* Kick the writer */
+ break ;
- /* Deal with the several names of success and the many names of failure.
- */
- close = false ;
+ case VOUT_FILE:
+ ret = uty_file_out_push(vf, final) ;
+ break ;
- switch(ret)
- {
- case CMD_CLOSE:
- case CMD_EOF:
- close = true ;
- fall_through ;
+ case VOUT_PIPE:
+ ret = uty_pipe_out_push(vf, final) ;
+ break ;
- case CMD_SUCCESS:
- case CMD_EMPTY:
- ret = vty_cmd_success(vty) ;
- break ;
+ case VOUT_CONFIG:
+ ret = uty_file_out_push(vf, final) ; /* treat as file */
+ break ;
- default:
- /* If not CMD_SUCCESS, the treat as some sort of error:
- *
- * * close down all pipes -- leaving just the vin_base/vout_base.
- * * create error messages in the vout_base.
- */
- vty_cmd_failed(vty, ret) ;
- break ;
- } ;
+ case VOUT_DEV_NULL:
+ case VOUT_SHELL_ONLY:
+ vio_fifo_clear(vf->obuf, false) ; /* keep end mark */
+ break ;
- /* They think it's all over... */
+ case VOUT_STDOUT:
+ vio_fifo_fwrite(vf->obuf, stdout) ; // TODO errors
+ break ;
- VTY_LOCK() ;
+ case VOUT_STDERR:
+ vio_fifo_fwrite(vf->obuf, stderr) ; // TODO errors
+ break ;
- vty->vio->cmd_running = false ;
+ default:
+ zabort("unknown vout_type") ;
+ } ;
+ break ;
- if (vty->vio->vin->vin_type == VIN_TERM)
- uty_cli_done_command(vty->vio->vin->cli, vty->node) ;
+ case vf_closed:
+ case vf_timed_out:
+ break ; /* immediate success ! */
- /* ... it is now. */
+ case vf_eof:
+ zabort("vf->vout cannot be vf_eof") ;
+ break ;
- if (close)
- uty_close(vty->vio, false, NULL) ;
+ case vf_error:
+ ret = CMD_IO_ERROR ;
+ break ;
- VTY_UNLOCK() ;
+ default:
+ zabort("unknown vf->vout_state") ;
+ break ;
+ } ;
return ret ;
} ;
@@ -599,19 +1481,32 @@ vty_cmd_loop_exit(vty vty, cmd_return_code_t ret)
/*------------------------------------------------------------------------------
* Dealing with error of some kind.
*
- * Error is reported on the vout_base. Whatever the command has written
- * so far will be appended to the reporting of the error location.
+ * 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.
+ *
+ * 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.
+ *
+ * Any other error message is then appended to the ebuf.
*
- * If there is only one vout, then:
+ * For command errors, the tail of the vio->obuf (stuff after the end_mark) is
+ * moved to the ebuf.
*
- * - if is VOUT_TERM, then need to wipe the command line. The failing
- * line will be the last output line on screen (unless monitor output,
- * which can do nothing about).
+ * Deals with:
*
- * - if is VOUT_VTYSH, then the failing line will be the last output line
- * on the screen.
+ * CMD_WARNING = TODO
+ * CMD_ERROR =>
*
- * - if is VOUT_xxx, then nothing will have been reflected.
+ * 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 -- failed :-(
+ * CMD_IO_TIMEOUT I/O -- timed out :-(
*
* 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
@@ -620,116 +1515,112 @@ vty_cmd_loop_exit(vty vty, cmd_return_code_t ret)
* NB: does not expect to see all the possible CMD_XXX return codes (see
* below), but treats all as a form of error !
*/
-static void
-vty_cmd_failed(vty vty, cmd_return_code_t ret)
+static uint
+uty_cmd_failed(vty_io vio, cmd_return_code_t ret)
{
- vty_io vio ;
- vio_fifo tbuf ;
- ulen eloc ;
+ ulen indent ;
- VTY_LOCK() ;
- vio = vty->vio ; /* once locked */
-
- /* Copy command output to temporary, and remove from the obuf.
- *
- * Any command output should be messages explaining the error, and we want
- * those (a) on the base vout, and (b) after description of where the error
- * occurred.
- */
- tbuf = vio_fifo_copy_tail(NULL, vio->obuf) ;
- vio_fifo_back_to_end_mark(vio->obuf, true) ;
-
- /* Can now close everything in the vout stack, so all further output
- * is to the vout_base.
- */
- uty_vout_close_stack(vio, false) ; /* not "final" */
+ VTY_ASSERT_LOCKED() ;
/* Process the vin stack to generate the error location(s) */
- uty_show_error_location(vio->vin) ;
-
- if (vio->vin->vin_type == VIN_TERM)
- eloc = uty_cli_prompt_len(vio->vin->cli) ;
+ if (vio->ebuf != NULL)
+ vio_fifo_clear(vio->ebuf, true) ;
else
- eloc = 0 ;
+ vio->ebuf = vio_fifo_init_new(NULL, 1000) ;
- /* Can now close everything in the vin stack */
- uty_vin_close_stack(vio) ;
+ indent = uty_show_error_context(vio->ebuf, vio->vin) ;
/* Now any additional error message if required */
switch (ret)
{
- qstring qs ;
-
case CMD_WARNING:
- if (vio_fifo_empty(tbuf))
- uty_out(vio, "%% WARNING: non-specific warning\n") ;
+ if (vio_fifo_tail_empty(vio->obuf))
+ vio_fifo_printf(vio->ebuf, "%% WARNING: non-specific warning\n") ;
break ;
case CMD_ERROR:
- if (vio_fifo_empty(tbuf))
- uty_out(vio, "%% ERROR: non-specific error\n") ;
- break ;
-
- case CMD_IO_ERROR:
+ if (vio_fifo_tail_empty(vio->obuf))
+ vio_fifo_printf(vio->ebuf, "%% ERROR: non-specific error\n") ;
break ;
case CMD_ERR_PARSING:
- qs = qs_set_fill(NULL, eloc + vio->vty->exec->parsed->eloc, "....") ;
- uty_out(vio, "%s^\n%% %s\n", qs_string(qs),
- vio->vty->exec->parsed->emess) ;
- qs_reset(qs, free_it) ;
+ cmd_get_parse_error(vio->ebuf, vio->vty->exec->parsed, indent) ;
break ;
case CMD_ERR_NO_MATCH:
- uty_out(vio, "%% Unknown command.\n") ;
+ vio_fifo_printf(vio->ebuf, "%% Unknown command.\n") ;
break;
case CMD_ERR_AMBIGUOUS:
- uty_out(vio, "%% Ambiguous command.\n");
+ vio_fifo_printf(vio->ebuf, "%% Ambiguous command.\n");
break;
case CMD_ERR_INCOMPLETE:
- uty_out(vio, "%% Command incomplete.\n");
+ vio_fifo_printf(vio->ebuf, "%% Command incomplete.\n");
break;
- case CMD_SUCCESS:
- case CMD_SUCCESS_DAEMON:
- case CMD_CLOSE:
- case CMD_EMPTY:
- case CMD_WAITING:
- case CMD_EOF:
- case CMD_COMPLETE_FULL_MATCH:
- case CMD_COMPLETE_MATCH:
- case CMD_COMPLETE_LIST_MATCH:
- case CMD_COMPLETE_ALREADY:
+ case CMD_IO_ERROR:
+ break ;
+
+ case CMD_IO_TIMEOUT:
+ break ;
+
default:
- uty_out(vio, "%% Unexpected return code (%d).\n", (int)ret) ;
+ vio_fifo_printf(vio->ebuf, "%% Unexpected return code (%d).\n", (int)ret);
break ;
} ;
- /* Now stick the tbuf onto the end of the output & discard tbuf. */
- vio_fifo_copy(vio->obuf, tbuf) ;
- vio_fifo_reset(tbuf, free_it) ;
-
- uty_cmd_out_push(vio) ;
+ /* Now stick the obuf tail onto the end of the ebuf & discard the tail of
+ * the obuf.
+ */
+ vio_fifo_copy_tail(vio->ebuf, vio->obuf) ;
+ vio_fifo_back_to_end_mark(vio->obuf, true) ;
- VTY_UNLOCK() ;
+ /* Decide what stack level to close back to. */
+ return (vio->vty->type == VTY_TERMINAL) ? 1 : 0 ; // TODO I/O error ??
} ;
-static void
-uty_show_error_location(vio_vf vf)
+/*------------------------------------------------------------------------------
+ * In the given fifo, construct message giving the context in which an error
+ * has occurred.
+ *
+ * For file and pipe input (including config file), it is assumed that no
+ * command line has been reflected, so the context is given as:
+ *
+ * % on line 99 of xxxx:
+ * <the command line -- noting small indent>
+ *
+ * For interactive input, if the stack depth is 1, then it is assumed that the
+ * command line is the last complete line output. Otherwise the context is
+ * given as:
+ *
+ * % in command line:
+ * <the command line -- noting small indent>
+ *
+ * The context starts with level 1 of the vin stack, and ends with the current
+ * level.
+ *
+ * Returns: "eloc" -- start of command line at the current level.
+ */
+static uint
+uty_show_error_context(vio_fifo ebuf, vio_vf vf)
{
vio_vf vf_next ;
+ uint indent ;
/* Recurse until hit end of the vin stack */
vf_next = ssl_next(vf, vin_next) ;
if (vf_next != NULL)
- uty_show_error_location(vf_next) ;
+ uty_show_error_context(ebuf, vf_next) ;
else
assert(vf == vf->vio->vin_base) ;
- /* On the way back, add the error location for each vin entry */
+ /* On the way back, add the error location for each vin entry
+ * and establish the location of the start of the command line as shown.
+ */
+ indent = 0 ;
+
switch (vf->vin_type)
{
case VIN_NONE:
@@ -737,20 +1628,20 @@ uty_show_error_location(vio_vf vf)
break ;
case VIN_TERM:
- /* Wipe the current command line */
-// uty_term_show_error_location(vf, loc) ;
+ indent = uty_term_show_error_context(vf, ebuf, vf->vio->vin_depth) ;
break ;
case VIN_VTYSH:
+// eloc = uty_term_show_error_context(vf, ebuf, vf->vio->vin_depth) ;
break ;
case VIN_FILE:
-
case VIN_PIPE:
-
case VIN_CONFIG:
- uty_out(vf->vio, "%% on line %d of %s:\n", vf->line_number, vf->name) ;
- uty_out(vf->vio, "%s\n", qs_make_string(vf->cl)) ;
+ vio_fifo_printf(ebuf, "%% on line %d of %s:\n",
+ vf->line_number, vf->name) ;
+ vio_fifo_printf(ebuf, " %s\n", qs_make_string(vf->cl)) ;
+ indent = 2 ;
break ;
case VIN_DEV_NULL:
@@ -759,6 +1650,8 @@ uty_show_error_location(vio_vf vf)
default:
zabort("unknown vin_type") ;
} ;
+
+ return indent ;
} ;
/*==============================================================================
@@ -768,96 +1661,96 @@ uty_show_error_location(vio_vf vf)
*/
/*------------------------------------------------------------------------------
- * Attempt to gain the configuration symbol of power
- *
- * If succeeds, set the given node.
+ * Attempt to gain the configuration symbol of power -- may already own it !
*
- * Returns: true <=> now own the symbol of power.
+ * Returns: true <=> now own the symbol of power (or already did).
*/
-extern bool
-vty_config_lock (struct vty *vty, enum node_type node)
+extern cmd_return_code_t
+vty_cmd_config_lock (vty vty)
{
- bool result;
-
VTY_LOCK() ;
+ uty_cmd_config_lock(vty) ;
+ VTY_UNLOCK() ;
- if (!host.config)
- {
- vty->config = true ;
- host.config = true ;
- } ;
+ if (vty->config)
+ return CMD_SUCCESS ;
- result = vty->config;
+ vty_out (vty, "VTY configuration is locked by other VTY\n") ;
+ return CMD_WARNING ;
+} ;
- if (result)
- vty->node = node ;
+/*------------------------------------------------------------------------------
+ * Attempt to gain the configuration symbol of power -- may already own it !
+ *
+ * Returns: true <=> now own the symbol of power (or already did).
+ */
+static void
+uty_cmd_config_lock (vty vty)
+{
+ if (!host.config) /* If nobody owns the lock... */
+ {
+ host.config = true ; /* ...rope it... */
+ vty->config = true ;
- VTY_UNLOCK() ;
+ do
+ ++host.config_brand ; /* ...update the brand... */
+ while (host.config_brand == 0) ;
- return result;
+ 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) ;
+ } ;
}
/*------------------------------------------------------------------------------
- * Give back the configuration symbol of power -- if own it.
- *
- * Set the given node -- which must be <= MAX_NON_CONFIG_NODE
+ * Check that given node and ownership of configuration symbol of power...
+ * ...see below.
*/
extern void
-vty_config_unlock (struct vty *vty, enum node_type node)
+vty_cmd_config_lock_check(vty vty, node_type_t node)
{
VTY_LOCK() ;
- uty_config_unlock(vty, node);
+ uty_cmd_config_lock_check(vty, node) ;
VTY_UNLOCK() ;
-}
+} ;
/*------------------------------------------------------------------------------
- * Give back the configuration symbol of power -- if own it.
+ * 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.
*
- * Set the given node -- which must be <= MAX_NON_CONFIG_NODE
+ * If node <= MAX_NON_CONFIG_NODE, will release symbol of power, if own it,
+ * PROVIDED is at vin_depth <= 1 !!
*/
-extern void
-uty_config_unlock (struct vty *vty, node_type_t node)
+static void
+uty_cmd_config_lock_check(vty vty, node_type_t node)
{
VTY_ASSERT_LOCKED() ;
- if (host.config && vty->config)
+ if (vty->config)
{
- vty->config = false ;
- host.config = false ;
- }
-
- assert(node <= MAX_NON_CONFIG_NODE) ;
- vty->node = node ;
-} ;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ /* We think we own it, so we better had */
+ assert(host.config) ;
+ assert(host.config_brand == vty->config_brand) ;
+ /* If no longer need it, release */
+ if ((node <= MAX_NON_CONFIG_NODE) && (vty->vio->vin_depth <= 1))
+ {
+ host.config = false ;
+ vty->config = false ;
+ } ;
+ }
+ else
+ {
+ /* We don't think we own it, so we had better not */
+ if (host.config)
+ assert(host.config_brand != vty->config_brand) ;
-/*------------------------------------------------------------------------------
- * Result of command is to close the input.
- *
- * Posts the reason for the close.
- *
- * Returns: CMD_CLOSE
- */
-extern cmd_return_code_t
-uty_cmd_close(vty_io vio, const char* reason)
-{
- vio->close_reason = qs_set(NULL, reason) ;
- return CMD_CLOSE ;
+ /* Also, node had better not require that we do. */
+ assert(node <= MAX_NON_CONFIG_NODE) ;
+ } ;
} ;
-
-