summaryrefslogtreecommitdiffstats
path: root/lib/vty_cli.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vty_cli.c')
-rw-r--r--lib/vty_cli.c3473
1 files changed, 1883 insertions, 1590 deletions
diff --git a/lib/vty_cli.c b/lib/vty_cli.c
index bdaf1128..a6a276e8 100644
--- a/lib/vty_cli.c
+++ b/lib/vty_cli.c
@@ -20,103 +20,403 @@
* Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
+#include "misc.h"
+#include <ctype.h>
-#include <zebra.h>
-
-#include "keystroke.h"
-#include "vty.h"
-#include "uty.h"
+#include "vty_local.h"
+#include "vty_command.h"
#include "vty_cli.h"
+#include "vty_io_term.h"
#include "vty_io.h"
#include "vio_lines.h"
+#include "vio_fifo.h"
+
+#include "keystroke.h"
-#include "command.h"
+#include "command_common.h"
+#include "command_parse.h"
#include "command_execute.h"
#include "command_queue.h"
#include "memory.h"
/*==============================================================================
- * Host name handling
+ * This is the CLI which is part of a VIN_TERM vf.
+ *
+ * With a few exceptions, the externs here are called by vty_io_term.
+ */
+
+/*------------------------------------------------------------------------------
+ * Essential stuff.
+ */
+#define TELNET_NEWLINE "\r\n"
+static const char* telnet_newline = TELNET_NEWLINE ;
+ const char* uty_cli_newline = TELNET_NEWLINE ;
+
+/*==============================================================================
+ * Construct and destroy CLI object
+ */
+
+static bool uty_cli_callback(keystroke_callback_args) ;
+static void uty_cli_update_more(vty_cli cli) ;
+
+static void uty_cli_cancel(vty_cli cli) ;
+
+/*------------------------------------------------------------------------------
+ * Construct and initialise a new CLI object -- never embedded.
*
- * The host name is used in the command line prompt. The name used is either
- * the name set by "hostname" command, or the current machine host name.
+ * The CLI is started up in the state it is in waiting for a command to
+ * complete. This means that all start-up messages etc. are handled as the
+ * output from an implied start command. When the command loop is entered,
+ * will find its way to uty_cli_want_command().
*
- * Static variables -- under the VTY_LOCK !
+ * Note that the vty_io_term stuff sends out a bunch of telnet commands, which
+ * will be buffered in the CLI buffer. That will be output, before the
+ * start-up messages etc. on uty_cli_start().
*/
+extern vty_cli
+uty_cli_new(vio_vf vf)
+{
+ vty_cli cli ;
+
+ cli = XCALLOC(MTYPE_VTY_CLI, sizeof(struct vty_cli)) ;
+
+ /* Zeroising has initialised:
+ *
+ * vf = NULL -- set below
+ *
+ * hist = NULL -- set up when first used.
+ * hp = 0 -- hp == h_now => in the present ...
+ * h_now = 0 -- ... see uty_cli_hist_add()
+ * h_repeat = false -- ... see uty_cli_hist_add()
+ *
+ * width = 0 -- unknown width ) Telnet window size
+ * height = 0 -- unknown height )
+ *
+ * lines = 0 -- set below
+ * lines_set = false -- set below
+ *
+ * monitor = false -- not a "monitor"
+ * monitor_busy = false -- so not busy either
+ *
+ * key_stream = X -- set below
+ *
+ * drawn = false
+ *
+ * tilde_prompt = false -- tilde prompt is not drawn
+ * tilde_enabled = false -- set below if ! multi-threaded
+ *
+ * prompt_len = 0 -- not drawn, in any case
+ * extra_len = 0 -- not drawn, in any case
+ *
+ * prompt_node = NULL_NODE -- so not set !
+ * prompt_gen = 0 -- not a generation number
+ * prompt_for_node = NULL -- not set, yet
+ *
+ * dispatched = false -- see below
+ * in_progress = false -- see below
+ * blocked = false -- see below
+ * paused = false
+ *
+ * mon_active = false
+ * out_active = false
+ *
+ * more_wait = false
+ * more_enter = false
+ *
+ * more_enabled = false -- not in "--More--" state
+ *
+ * pause_timer = NULL -- set below if multi-threaded
+ *
+ * context = NULL -- see below
+ * auth_context = false -- set by uty_cli_want_command()
+ *
+ * parsed = NULL -- see below
+ * to_do = cmd_do_nothing
+ * cl = NULL qstring -- set below
+ * cls = NULL qstring -- set below
+ * clx = NULL qstring -- set below
+ * dispatch = all zeros -- nothing to dispatch
+ *
+ * cbuf = NULL -- see below
+ *
+ * olc = NULL -- see below
+ */
+ confirm(NULL_NODE == 0) ; /* prompt_node & node */
+ confirm(cmd_do_nothing == 0) ; /* to_do */
+ confirm(CMD_ACTION_ALL_ZEROS) ; /* dispatch */
-static char* vty_host_name = NULL ;
-int vty_host_name_set = 0 ;
+ cli->vf = vf ;
-static void uty_new_host_name(const char* name) ;
+ /* Allocate and initialise a keystroke stream TODO: CSI ?? */
+ cli->key_stream = keystroke_stream_new('\0', "\x03", uty_cli_callback, cli) ;
+
+ /* Set up cl, cls and clx qstrings, the command line output fifo and
+ * the output line control.
+ */
+ cli->cl = qs_new(120) ; /* reasonable line length */
+ cli->cls = qs_new(120) ;
+ cli->clx = qs_new(120) ;
+
+ cli->cbuf = vio_fifo_new(1000) ;
+
+ cli->olc = vio_lc_new(0 , 0, telnet_newline) ;
+
+ /* Make an empty context object and an empty parsed object */
+ cli->context = cmd_context_new() ;
+ cli->parsed = cmd_parsed_new() ;
+
+ /* Use global 'lines' setting, as default -- but 'lines_set' false. */
+ uty_cli_set_lines(cli, host.lines, false) ;
+ uty_cli_update_more(cli) ; /* and update the olc etc to suit. */
+
+ /* Enable "~ " prompt and pause timer if multi-threaded
+ *
+ * TODO decide whether to ditch the '~' prompt, given the priority given
+ * to commands.
+ */
+ if (vty_nexus)
+ {
+ cli->pause_timer = qtimer_init_new(NULL, vty_cli_nexus->pile,
+ vty_term_pause_timeout, cli) ;
+#if 0
+ cli->tilde_enabled = vty_multi_nexus ;
+#endif
+ } ;
+
+ /* Ready to be started -- paused, out_active & more_wait are false.
+ *
+ * Is started by the first call of uty_cli_want_command(), which (inter alia)
+ * set the cli->context so CLI knows how to prompt etc.
+ */
+ cli->dispatched = true ;
+ cli->in_progress = true ;
+ cli->blocked = true ;
+
+ return cli ;
+} ;
/*------------------------------------------------------------------------------
- * Update vty_host_name as per "hostname" or "no hostname" command
+ * Close CLI object, if any -- may be called more than once.
+ *
+ * Shuts down and discards anything to do with the input.
+ *
+ * Revokes anything that can be revoked, which may allow output to proceed
+ * and the vty to clear itself down.
+ *
+ * If dispatched and not final, keeps the cbuf, the olc and clx if dispatched.
+ * If not final, keeps the cbuf and the olc.
+ *
+ * Destroys self if final.
+ *
+ * Expects other code to:
+ *
+ * - close down command loop and empty the vin/vout stacks.
+ *
+ * - turn of any monitor status.
+ *
+ * - half close the telnet connection.
*/
-extern void
-uty_set_host_name(const char* name)
+extern vty_cli
+uty_cli_close(vty_cli cli, bool final)
{
- VTY_ASSERT_LOCKED() ;
+ if (cli == NULL)
+ return NULL ;
- vty_host_name_set = (name != NULL) ;
+ VTY_ASSERT_CLI_THREAD_LOCKED() ;
- if (vty_host_name_set)
- uty_new_host_name(name) ;
- else
- uty_check_host_name() ;
+ /* Bring as much of the command handling to a stop as possible, and
+ * turn off "---more--" etc. so that output can proceed.
+ */
+ cli->more_enabled = false ;
+ cli->lines = 0 ; /* so will not be re-enabled */
+
+ uty_cli_cancel(cli) ; /* " ^C\r\n" unless believed blank */
+
+ cli->more_wait = false ; /* exit more_wait (if was in it) */
+ cli->more_enter = false ; /* and so cannot be this */
+
+ cli->blocked = true ; /* do not renter the CLI */
+ cli->out_active = true ; /* if there is any output, it can go */
+
+ /* Ream out the history. */
+ {
+ qstring line ;
+ while ((line = vector_ream(cli->hist, free_it)) != NULL)
+ qs_reset(line, free_it) ;
+ cli->hist = NULL ;
+ } ;
+
+ /* Empty the keystroke handling. */
+ cli->key_stream = keystroke_stream_free(cli->key_stream) ;
+
+ /* Can discard active command line if not dispatched */
+ if (!cli->dispatched || final)
+ cli->clx = qs_reset(cli->clx, free_it) ;
+
+ /* Can discard context and parsed objects */
+ cli->context = cmd_context_free(cli->context, true) ; /* its a copy */
+ cli->parsed = cmd_parsed_free(cli->parsed) ;
+
+ /* Discard any pause_timer, and suppress */
+ cli->pause_timer = qtimer_free(cli->pause_timer) ;
+ cli->paused = false ;
+#if 0
+ cli->tilde_enabled = false ;
+#endif
+
+ /* If final, free the CLI object. */
+ if (final)
+ {
+ cli->prompt_for_node = qs_free(cli->prompt_for_node) ;
+ cli->cl = qs_free(cli->cl) ;
+ cli->cls = qs_free(cli->cls) ;
+
+ cli->cbuf = vio_fifo_free(cli->cbuf) ;
+ cli->olc = vio_lc_free(cli->olc) ;
+
+ XFREE(MTYPE_VTY_CLI, cli) ; /* sets cli = NULL */
+ } ;
+
+ return cli ;
} ;
/*------------------------------------------------------------------------------
- * If vty_host_name is set, free it.
+ * The keystroke callback function.
+ *
+ * This deals with interrupts and IAC sequences that should be dealt with as
+ * soon as they are read -- not stored in the keystroke stream for later
+ * processing.
+ *
+ * Returns: true <=> dealt with keystroke, do not store in keystroke stream
*/
-extern void
-uty_free_host_name(void)
+static bool
+uty_cli_callback(keystroke_callback_args /* void* context, keystroke stroke */)
{
- if (vty_host_name != NULL)
- XFREE (MTYPE_HOST, vty_host_name) ; /* sets vty_host_name = NULL */
+ vty_cli cli = context ;
+
+ switch (stroke->type)
+ {
+ case ks_char: /* character -- uint32_t */
+ if ((cli->in_progress || cli->out_active) && !cli->more_wait)
+ {
+ vty_io vio = cli->vf->vio ;
+
+ uty_cmd_signal(vio, CMD_CANCEL) ; /* ^C */
+
+ return true ;
+ } ;
+
+ return false ;
+
+ case ks_iac: /* Telnet command */
+ return uty_telnet_command(cli->vf, stroke, true) ;
+
+ case ks_esc: /* ESC xx */
+ case ks_csi: /* ESC [ ... or CSI ... */
+ zabort("invalid interrupt keystroke type") ;
+
+ default:
+ zabort("unknown keystroke type") ;
+ } ;
} ;
/*------------------------------------------------------------------------------
- * If the host name is not set by command, see if the actual host name has
- * changed, and if so change it.
+ * Set the cli->lines, explicitly or implicitly, and update the "--more--"
+ * state to suit.
*
- * This is done periodically in case the actual host name changes !
+ * lines < 0 => use whatever telnet has said, or no "--more--"
+ * lines = 0 => no "--more--"
+ * lines > 0 => use
*/
extern void
-uty_check_host_name(void)
+uty_cli_set_lines(vty_cli cli, int lines, bool explicit)
{
- struct utsname names ;
-
- VTY_ASSERT_LOCKED() ;
+ cli->lines = lines ;
+ cli->lines_set = explicit ;
- if (vty_host_name_set)
- return ; /* nothing to do if set by command */
+ uty_cli_update_more(cli) ;
+} ;
- uname (&names) ;
+/*------------------------------------------------------------------------------
+ * Set the cli->lines, explicitly or implicitly, and update the "--more--"
+ * state to suit.
+ *
+ * lines < 0 => use whatever telnet has said, or no "--more--"
+ * lines = 0 => no "--more--"
+ * lines > 0 =>
+ */
+extern void
+uty_cli_set_window(vty_cli cli, int width, int height)
+{
+ cli->width = width ;
+ cli->height = height ;
- if ((vty_host_name == NULL) || (strcmp(vty_host_name, names.nodename) != 0))
- uty_new_host_name(names.nodename) ;
+ uty_cli_update_more(cli) ;
} ;
/*------------------------------------------------------------------------------
- * Set new vty_host_name and run along list of VTYs to mark the change.
+ * Update the "--more--" state, after changing one or more of:
+ *
+ * cli->lines
+ * cli->lines_set
+ * cli->width
+ * cli->height
+ *
+ * Set the effective height for line control, if required, which may enable the
+ * "--more--" output handling.
+ *
+ * If not, want some limit on the amount of stuff output at a time.
+ *
+ * Sets the line control window width and height.
+ * Sets cli_more_enabled if "--more--" is enabled.
*/
static void
-uty_new_host_name(const char* name)
+uty_cli_update_more(vty_cli cli)
{
- vty_io vio ;
-
- VTY_ASSERT_LOCKED() ;
+ bool on ;
- uty_free_host_name() ;
- vty_host_name = XSTRDUP(MTYPE_HOST, name) ;
+ on = false ; /* default state */
- vio = vio_list_base ;
- while (vio != NULL)
+ if (cli->vf->vout_state == vf_open)
{
- vio->cli_prompt_set = 0 ;
- vio = sdl_next(vio, vio_list) ;
+ int height ;
+
+ height = 0 ; /* default state: no "--more--" */
+
+ if ((cli->width) != 0)
+ {
+ /* If window size is known, use lines or given height */
+ if (cli->lines >= 0)
+ height = cli->lines ;
+ else
+ {
+ /* Window height, leaving one line from previous "page"
+ * and one line for the "--more--" -- if at all possible
+ */
+ height = cli->height - 2 ;
+ if (height < 1)
+ height = 1 ;
+ } ;
+ }
+ else
+ {
+ /* If window size not known, use lines if that has been set
+ * explicitly for this terminal.
+ */
+ if (cli->lines_set)
+ height = cli->lines ;
+ } ;
+
+ if (height > 0)
+ on = true ; /* have a defined height */
+ else
+ height = 200 ; /* but no "--more--" */
+
+ vio_lc_set_window(cli->olc, cli->width, height) ;
} ;
+
+ cli->more_enabled = on ;
} ;
/*==============================================================================
@@ -131,32 +431,63 @@ uty_new_host_name(const char* name)
* -- setting read or write on, means enabling the file for select/pselect to
* consider it for read_ready or write_ready, respectively.
*
+ * All command actions are dispatched via the command queue -- commands are
+ * not executed inside the CLI code. [For legacy threads the event queue is
+ * used. If that is too slow, a priority event queue will have to be
+ * invented.]
+ *
* State of the CLI:
*
- * cli_blocked -- the CLI is unable to process any further keystrokes.
+ * dispatched -- a command line has been dispatched, and is waiting for
+ * (dsp) command loop to fetch it.
+ *
+ * in_progress -- a command has been taken by the command loop, and is still
+ * (inp) running -- or at least no further command has been
+ * fetched by the command loop.
+ *
+ * The command may be an in_pipe, so there may be many
+ * commands to be completed before the CLI level command is.
+ *
+ * or: the CLI has been closed.
+ *
+ * blocked -- is in_progress and a further command is now ready to be
+ * (bkd) dispatched.
*
- * cmd_in_progress -- a command has been dispatched and has not yet
- * completed (may have been queued).
+ * or: the CLI has been closed.
*
- * cmd_out_enabled -- the command FIFO is may be emptied.
+ * out_active -- the command output FIFO is being emptied.
+ * (oa)
+ * This is set when output is pushed, and cleared when
+ * everything is written away and in_progress is not set.
+ * When it is set, any current command line is wiped.
*
- * This is set when a command completes, and cleared when
- * everything is written away.
+ * Note that what this flag does is prevent the CLI from
+ * running until the output completes, and in particular
+ * prevents it from writing anything to the CLI buffer.
*
- * cli_more_wait -- is in "--more--" wait state
+ * more_wait -- is in "--more--" wait state. => out_active !
+ * (mwt)
+ * The "--more--" CLI uses the CLI output buffer to draw
+ * and undraw the "--more--" prompt. The buffer will
+ * otherwise be empty because is out_active.
+ *
+ * more_enter -- is in the process of entering the "--more--" wait state,
+ * (men) waiting to write the "--more--" prompt and prepare the
+ * keystroke input.
*
* The following are the valid combinations:
*
- * blkd : cip : o_en : m_wt :
- * -----:------:------:------:--------------------------------------------
- * 0 : 0 : 0 : 0 : collecting a new command
- * 0 : 1 : 0 : 0 : command dispatched
- * 1 : 1 : 0 : 0 : waiting for (queued) command to complete
- * 1 : 0 : 1 : 0 : waiting for command output to complete
- * 1 : 0 : 0 : 1 : waiting for "--more--" to be written away
- * 0 : 0 : 0 : 1 : waiting for "--more--" response
- * 1 : 1 : 1 : 0 : waiting for command to complete, after the
- * CLI has been closed
+ * dsp:inp:bkd: oa:mwt:men:
+ * ---:---:---:---:---:---:-----------------------------------------
+ * 0 : 0 : 0 : 0 : 0 : 0 : collecting a new command
+ * 1 : 0 : 0 : 0 : 0 : 0 : waiting for command to be fetched
+ * 1 : 1 : 0 : X : 0 : 0 : command fetched and running
+ * 1 : 1 : 1 : X : 0 : 0 : waiting for command to complete
+ * 0 : 0 : 0 : 1 : 0 : 0 : waiting for command output to finish
+ * 1 : X : X : 1 : 1 : 1 : waiting for "--more--" to start
+ * 1 : X : X : 1 : 1 : 0 : waiting for "--more--" response
+ * 1 : 1 : 1 : 1 : 0 : 0 : waiting for command to complete,
+ * after the CLI has been closed
*
* There are two output FIFOs:
*
@@ -165,56 +496,43 @@ uty_new_host_name(const char* name)
* 2. for output generated by commands -- vty_out and friends.
*
* The CLI FIFO is emptied whenever possible, in preference to the command
- * FIFO. The command FIFO is emptied when cmd_out_enabled. While
- * cmd_in_progress is also !cmd_out_enabled -- so that all the output from a
- * given command is collected together before being sent to the file.
+ * FIFO. The command FIFO is emptied when out_active. While a command is
+ * in_progress all its output is collected in the command output buffer,
+ * to be written away when the command completes, or if it pushes the output
+ * explicitly. Note that where the CLI level command is an in_pipe, the first
+ * output will set out_active, and that will persist until returns to the CLI
+ * level. Between commands in the pipe, the output will be pushed, so will
+ * output stuff more or less as it is generated.
+ *
+ * It is expected that each command's output will end with a newline. However,
+ * the line control imposes some discipline, and holds on to incomplete lines
+ * until a newline arrives, or the output is because in_progress is false -- so
+ * that when the CLI is kicked, the cursor will be at the start of an empty
+ * line.
*
- * Note that only sets read on when the keystroke stream is empty and has not
- * yet hit eof. The CLI process is driven mostly by write_ready -- which
- * invokes read_ready.
+ * Note that is read ready until the keystroke stream hits eof. So any input
+ * will be hoovered up as soon as it is available. The CLI process is driven
+ * mostly by write_ready, except for when all output is complete and the
+ * input keystroke buffer has been emptied.
*
* Note also that after each command dispatch the CLI processor exits, to be
* re-entered again on write_ready/read_ready -- so does one command line at
* a time, yielding the processor after each one.
*
- * Note that select/pselect treat a socket which is at "EOF", or has seen an
- * error, or has been half closed, etc. as readable and writable. This means
- * that the CLI will continue to move forward even after the socket is no
- * longer delivering any data.
- *
*------------------------------------------------------------------------------
* The "--more--" handling.
*
* This is largely buried in the output handling.
*
- * While cmd_in_progress is true cmd_out_enabled will be false. When the
- * command completes:
- *
- * * cmd_in_progress is cleared
- *
- * * cmd_out_enabled is set
- *
- * * cli_blocked will be set
- *
- * * the line_control structure is reset
- *
- * * the output process is kicked off by setting write on
+ * When command is pushed to written away, out_active will be set (and any
+ * command line will be wiped). The output process is then kicked.
*
* The output process used the line_control structure to manage the output, and
* occasionally enter the trivial "--more--" CLI. This is invisible to the
- * main CLI. (See the cli_more_wait flag and its handling.)
- *
- * When all the output has completed the CLI will be kicked, which will see
- * that the output buffer is now empty, and it can proceed.
- *
- * It is expected that the output will end with a newline -- so that when the
- * CLI is kicked, the cursor will be at the start of an empty line.
- *
- * This mechanism means that the command output FIFO only ever contains the
- * output from the last command executed.
+ * main CLI. (See the more_wait and more_enter flags and their handling.)
*
* If the user decides to abandon output at the "--more--" prompt, then the
- * contents of the command output FIFO are discarded.
+ * then contents of the command output FIFO are discarded.
*
*------------------------------------------------------------------------------
* The qpthreads/qpnexus extension.
@@ -262,104 +580,17 @@ uty_new_host_name(const char* name)
* The CLI
*/
-#define CONTROL(X) ((X) - '@')
-
-static void uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret) ;
-static enum vty_readiness uty_cli_standard(vty_io vio) ;
-static enum vty_readiness uty_cli_more_wait(vty_io vio) ;
-static void uty_cli_draw(vty_io vio) ;
-static void uty_cli_draw_this(vty_io vio, enum node_type node) ;
-static void uty_cli_wipe(vty_io vio, int len) ;
-
-static void uty_will_echo (vty_io vio) ;
-static void uty_will_suppress_go_ahead (vty_io vio) ;
-static void uty_dont_linemode (vty_io vio) ;
-static void uty_do_window_size (vty_io vio) ;
-static void uty_dont_lflow_ahead (vty_io vio) ;
-
-/*------------------------------------------------------------------------------
- * Initialise CLI.
- *
- * It is assumed that the following have been initialised, empty or zero:
- *
- * cli_prompt_for_node
- * cl
- * clx
- * cli_vbuf
- * cli_obuf
- *
- * cli_drawn
- * cli_dirty
- *
- * cli_prompt_set
- *
- * cli_blocked
- * cmd_in_progress
- * cmd_out_enabled
- * cli_wait_more
- *
- * cli_more_enabled
- *
- * Sets the CLI such that there is apparently a command in progress, so that
- * further initialisation (in particular hello messages and the like) is
- * treated as a "start up command".
- *
- * Sends a suitable set of Telnet commands to start the process.
- */
-extern void
-uty_cli_init(vty_io vio)
-{
- assert(vio->type == VTY_TERM) ;
-
- vio->cmd_in_progress = 1 ;
- vio->cli_blocked = 1 ;
+#define CONTROL(X) ((X) & 0x1F)
- vio->cli_do = cli_do_nothing ;
-
- /* Setting up terminal. */
- uty_will_echo (vio);
- uty_will_suppress_go_ahead (vio);
- uty_dont_linemode (vio);
- uty_do_window_size (vio);
- if (0)
- uty_dont_lflow_ahead (vio) ;
-} ;
+static enum vty_readiness uty_cli_standard(vty_cli cli) ;
+static cmd_do_t uty_cli_auth(vty_cli cli) ;
+static enum vty_readiness uty_cli_more_wait(vty_cli cli) ;
+static void uty_cli_draw(vty_cli cli) ;
/*------------------------------------------------------------------------------
- * Start the CLI.
+ * CLI for VTY_TERMINAL
*
- * All start-up operations are complete -- so the "command" is now complete.
- *
- * Returns: write_ready -- so the first event is a write event, to flush
- * any output to date.
- */
-extern enum vty_readiness
-uty_cli_start(vty_io vio)
-{
- uty_cli_cmd_complete(vio, CMD_SUCCESS) ;
- return write_ready ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Close the CLI
- *
- * Note that if any command is revoked, then will clear cmd_in_progress and
- * set cmd_out_enabled -- so any output can now clear.
- */
-extern void
-uty_cli_close(vty_io vio)
-{
- VTY_ASSERT_LOCKED() ;
- assert(vio->type == VTY_TERM) ;
-
- cq_revoke(vio->vty) ;
-
- vio->cli_blocked = 1 ; /* don't attempt any more */
- vio->cmd_out_enabled = 1 ; /* allow output to clear */
-} ;
-
-/*------------------------------------------------------------------------------
- * CLI for VTY_TERM
+ * Called by uty_term_ready(), so driven by read/write ready.
*
* Do nothing at all if half closed.
*
@@ -369,29 +600,34 @@ uty_cli_close(vty_io vio)
* NB: on return, requires that an attempt is made to write away anything that
* may be ready for that.
*/
-extern enum vty_readiness
-uty_cli(vty_io vio)
+extern vty_readiness_t
+uty_cli(vty_cli cli)
{
VTY_ASSERT_LOCKED() ;
- assert((vio->type == VTY_TERM) || (vio->real_type == VTY_TERM)) ;
- if (vio->half_closed)
- return not_ready ; /* Nothing more if half closed */
+ if (cli->vf->vin_state == vf_closed)
+ return not_ready ; /* Nothing more from CLI if closed */
/* Standard or "--more--" CLI ? */
- if (vio->cli_more_wait)
- return uty_cli_more_wait(vio) ;
+ if (cli->more_wait)
+ return uty_cli_more_wait(cli) ;
else
- return uty_cli_standard(vio) ;
+ return uty_cli_standard(cli) ;
} ;
/*==============================================================================
* The Standard CLI
*/
-static enum cli_do uty_cli_process(vty_io vio, enum node_type node) ;
-static void uty_cli_response(vty_io vio, enum cli_do cli_do) ;
-static bool uty_cli_dispatch(vty_io vio) ;
+static cmd_do_t uty_cli_process(vty_cli cli) ;
+static void uty_cli_response(vty_cli cli, cmd_do_t cmd_do) ;
+
+static bool uty_cli_dispatch(vty_cli cli) ;
+static cmd_do_t uty_cli_command(vty_cli cli) ;
+static void uty_cli_hist_add (vty_cli cli, qstring clx) ;
+
+static void uty_cli_pause_start(vty_cli cli) ;
+static void uty_cli_goto_end_if_drawn(vty_cli cli) ;
/*------------------------------------------------------------------------------
* Standard CLI for VTY_TERM -- if not blocked, runs until:
@@ -407,282 +643,316 @@ static bool uty_cli_dispatch(vty_io vio) ;
* write_ready if there is anything in the keystroke stream
* read_ready otherwise
*/
-static enum vty_readiness
-uty_cli_standard(vty_io vio)
+static vty_readiness_t
+uty_cli_standard(vty_cli cli)
{
+ bool need_prompt ;
+
VTY_ASSERT_LOCKED() ;
- /* cli_blocked is set when is waiting for a command, or its output to
- * complete -- unless either of those has happened, is still blocked.
+ assert(!cli->more_wait) ; /* cannot be here in more_wait state ! */
+
+ /* cli_blocked is set when is waiting for a command, or some output to
+ * complete.
*
* NB: in both these cases, assumes that other forces are at work to
* keep things moving.
*/
- if (vio->cli_blocked)
- {
- assert(vio->cmd_in_progress || vio->cmd_out_enabled) ;
-
- if (vio->cmd_in_progress)
- {
- assert(!vio->cmd_out_enabled) ;
- return not_ready ;
- } ;
+ if (cli->blocked || cli->out_active || cli->paused || cli->mon_active)
+ return not_ready ;
- if (!vio_fifo_empty(&vio->cmd_obuf))
- return not_ready ;
-
- vio->cli_blocked = 0 ;
- vio->cmd_out_enabled = 0 ;
- } ;
-
- /* If there is nothing pending, then can run the CLI until there is
- * something to do, or runs out of input.
+ /* Make sure that the command line is drawn.
*
- * If there is something to do, that is because a previous command has
- * now completed, which may have wiped the pending command or changed
- * the required prompt.
+ * If there is nothing pending, then can run the CLI until there is
+ * something to do, or runs out of input.
*/
- if (vio->cli_do == cli_do_nothing)
- vio->cli_do = uty_cli_process(vio, vio->vty->node) ;
- else
- uty_cli_draw_this(vio, vio->vty->node) ;
+ if (!cli->drawn)
+ uty_cli_draw(cli) ;
+
+ if (cli->to_do == cmd_do_nothing)
+ cli->to_do = cli->auth_context ? uty_cli_auth(cli)
+ : uty_cli_process(cli) ;
+
+ /* If have something to do, do it if we can. */
+ need_prompt = false ;
- /* If have something to do, do it. */
- if (vio->cli_do != cli_do_nothing)
+ if (cli->to_do != cmd_do_nothing)
{
- /* Reflect immediate response */
- uty_cli_response(vio, vio->cli_do) ;
+ /* Reflect immediate response */
+ uty_cli_response(cli, cli->to_do) ;
/* If command not already in progress, dispatch this one, which may
* set the CLI blocked.
*
* Otherwise is now blocked until queued command completes.
*/
- if (!vio->cmd_in_progress)
- vio->cli_blocked = uty_cli_dispatch(vio) ;
+ if (cli->dispatched)
+ cli->blocked = true ; /* waiting for previous */
else
- vio->cli_blocked = 1 ;
+ need_prompt = uty_cli_dispatch(cli) ; /* dispatch latest */
} ;
- /* Use write_ready as a proxy for read_ready on the keystroke stream.
+ /* If blocked, must wait for some other event to continue in CLI.
*
- * Also, if the command line is not drawn, then return write_ready, so
- * that
+ * Note that will be blocked if have just dispatched a command, and is
+ * not "tilde_enabled" -- which is the case if single threaded, and may be
+ * so for other reasons.
*
- * Note that if has just gone cli_blocked, still returns ready. This is
- * defensive: at worst will generate one unnecessary read_ready/write_ready
- * event.
+ * If the keystroke stream is not empty, use write_ready as a proxy for
+ * CLI ready -- no point doing anything until any buffered output has been
+ * written away.
+ *
+ * If command prompt has been redrawn, need to kick writer to deal with
+ * that -- will reenter to then process any keystrokes.
*/
- if (keystroke_stream_empty(vio->key_stream))
- return read_ready ;
- else
+ assert(!cli->paused) ;
+
+ if (cli->blocked)
+ return not_ready ;
+
+ if (!keystroke_stream_empty(cli->key_stream) || need_prompt)
return write_ready ;
+
+ /* The keystroke stream is empty, but CLI is not blocked.
+ *
+ * If a command is dispatched, and output is not active, and the command
+ * line is not drawn, then go to paused state, so that will delay output
+ * of the prompt slightly (or until a keystroke arrives, or the current
+ * command completes, or something else happens).
+ *
+ * Note that if a command has been dispatched, and is !tilde_enabled, then
+ * will now be blocked, so won't be here.
+ */
+ if (cli->dispatched && !cli->out_active && !cli->drawn)
+ uty_cli_pause_start(cli) ;
+
+ return read_ready ;
} ;
/*------------------------------------------------------------------------------
- * Dispatch the current vio->cli_do -- queueing it if necessary.
+ * Dispatch the current cli->to_do -- queueing it if necessary.
*
* Requires that are NOT blocked and NO command is queued.
*
* Expects to be on new blank line, and when returns will be on new, blank
* line.
*
- * Returns: true <=> command completed and output is pending
- * false => command has been queued and is now in progress
+ * Generally sets cli->to_do = cmd_do_nothing and clears cli->cl to empty.
*
- * Generally sets vio->cl_do = cli_do_nothing and clears vio->cl to empty.
+ * Can set cli->cl_do = and cli->cl to be a follow-on command.
*
- * Can set vio->cl_do = and vio->cl to be a follow-on command.
+ * Returns: true <=> nothing, in fact, to do: prompt has been redrawn.
*/
static bool
-uty_cli_dispatch(vty_io vio)
+uty_cli_dispatch(vty_cli cli)
{
- qstring_t tmp ;
- enum cli_do cli_do ;
- enum cmd_return_code ret ;
+ qstring tmp ;
+ cmd_do_t to_do_now ;
- struct vty* vty = vio->vty ;
+ vty_io vio = cli->vf->vio ;
VTY_ASSERT_LOCKED() ;
- assert(!vio->cli_blocked && !vio->cmd_in_progress) ;
- /* Set vio->clx to the command about to execute.
+ /* About to dispatch a command, so must be in the following state. */
+ qassert(!cli->dispatched && !cli->in_progress
+ && !cli->blocked && !cli->out_active) ;
+ qassert(cli->context->node == vio->vty->exec->context->node) ;
+
+ /* Set cli->clx to the command about to execute & pick up cli->to_do.
*
- * Clear vio->cl and vio->cl_do.
+ * Clear cli->cl and cli->to_do.
*/
- vio->cmd_in_progress = 1 ; /* => vty->buf is valid */
- vio->cmd_out_enabled = 0 ; /* => collect all output */
-
- tmp = vio->clx ; /* swap clx and cl */
- vio->clx = vio->cl ;
- vio->cl = tmp ;
+ tmp = cli->clx ; /* swap clx and cl */
+ cli->clx = cli->cl ;
+ cli->cl = tmp ;
- qs_term(&vio->clx) ; /* ensure string is terminated */
- vty->buf = qs_chars(&vio->clx) ; /* terminated command line */
- cli_do = vio->cli_do ; /* current operation */
+ to_do_now = cli->to_do ; /* current operation */
- vio->cli_do = cli_do_nothing ; /* clear */
- qs_clear(&vio->cl) ; /* set cl empty (with '\0') */
+ cli->to_do = cmd_do_nothing ; /* clear */
+ qs_clear(cli->cl) ; /* set cl empty */
- /* Reset the command output FIFO and line_control */
- assert(vio_fifo_empty(&vio->cmd_obuf)) ;
- uty_out_clear(vio) ; /* clears FIFO and line control */
-
- /* Dispatch command */
- if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE))
- {
- /* AUTH_NODE and AUTH_ENABLE_NODE are unique */
- ret = uty_auth(vty, vty->buf, cli_do) ;
- }
+ /* Dispatch command */
+ if (cli->auth_context)
+ to_do_now |= cmd_do_auth ;
else
{
- /* All other nodes... */
- switch (cli_do)
+ /* All other nodes... */
+ switch (to_do_now)
{
- case cli_do_nothing:
- ret = CMD_SUCCESS ;
+ case cmd_do_nothing:
+ case cmd_do_ctrl_c:
+ case cmd_do_eof:
+ case cmd_do_timed_out:
break ;
- case cli_do_command:
- ret = uty_command(vty) ;
+ case cmd_do_command:
+ to_do_now = uty_cli_command(cli) ;
break ;
- case cli_do_ctrl_c:
- ret = uty_stop_input(vty) ;
+ case cmd_do_ctrl_d:
+ zabort("invalid cmd_do_ctrl_d") ;
break ;
- case cli_do_ctrl_d:
- ret = uty_down_level(vty) ;
- break ;
+ case cmd_do_ctrl_z:
+ to_do_now = uty_cli_command(cli) ;
- case cli_do_ctrl_z:
- ret = uty_command(vty) ;
- if (ret == CMD_QUEUED)
- vio->cli_do = cli_do_ctrl_z ; /* defer the ^Z action */
+ if (to_do_now == cmd_do_nothing)
+ to_do_now = cmd_do_ctrl_z ;
else
- ret = uty_end_config(vty) ; /* do the ^Z now */
- break ;
+ cli->to_do = cmd_do_ctrl_z ; /* defer the ^Z */
- case cli_do_eof:
- ret = uty_cmd_close(vio->vty, "End") ;
break ;
default:
- zabort("unknown cli_do_xxx value") ;
+ zabort("unknown cmd_do_xxx value") ;
} ;
} ;
- if (ret == CMD_QUEUED)
+ cli->hp = cli->h_now ; /* in any event, back to the present */
+
+ if (to_do_now != cmd_do_nothing)
{
- uty_cli_draw(vio) ; /* draw the prompt */
- return false ; /* command not complete */
+ cmd_action_set(cli->dispatch, to_do_now, cli->clx) ;
+ cli->dispatched = true ;
+
+ uty_cmd_signal(vio, CMD_SUCCESS) ;
+
+ cli->blocked = (to_do_now != cmd_do_command)
+#if 0
+ || !cli->tilde_enabled
+#endif
+ ;
}
else
{
- uty_cli_cmd_complete(vio, ret) ;
- return true ; /* command complete */
+ cmd_action_clear(cli->dispatch) ;
+ cli->dispatched = false ;
+
+ uty_cli_draw(cli) ;
} ;
+
+ return !cli->dispatched ;
} ;
/*------------------------------------------------------------------------------
- * Queued command has completed.
+ * Check if command is empty, if not add to history
*
- * Note that sets write on whether there is anything in the output buffer
- * or not... write_ready will kick read_ready.
+ * Returns: cmd_do_nothing -- empty command line
+ * cmd_do_command -- command ready to be executed (added to history)
*/
-extern void
-vty_queued_result(struct vty *vty, enum cmd_return_code ret)
+static cmd_do_t
+uty_cli_command(vty_cli cli)
{
- vty_io vio ;
+ if (cmd_is_empty(cli->clx))
+ return cmd_do_nothing ; /* easy when nothing to do ! */
- VTY_LOCK() ;
+ /* Add not empty command to history */
+ uty_cli_hist_add(cli, cli->clx) ;
- vio = vty->vio ;
-
- if (!vio->closed)
- {
- uty_cli_wipe(vio, 0) ; /* wipe any partly constructed line */
-
- /* Do the command completion actions that were deferred because the
- * command was queued.
- *
- * Return of CMD_QUEUED => command was revoked before being executed.
- * However interesting that might be... frankly don't care.
- */
- uty_cli_cmd_complete(vio, ret) ;
-
- /* Kick the socket -- to write away any outstanding output, and
- * re-enter the CLI when that's done.
- */
- uty_sock_set_readiness(&vio->sock, write_ready) ;
- }
- else
- {
- /* If the VTY is closed, the only reason it still exists is because
- * there was cmd_in_progress.
- */
- vio->cmd_in_progress = 0 ;
-
- uty_close(vio) ; /* Final close */
- } ;
-
- VTY_UNLOCK() ;
-}
+ return cmd_do_command ;
+} ;
/*------------------------------------------------------------------------------
- * Command has completed, so:
+ * Want another command line from the CLI.
+ *
+ * If in_progress this <=> any previous command has completed, and output may
+ * be active writing the results away. Will:
*
- * * clear cmd_in_progress
- * * set cmd_out_enabled -- so any output can now proceed
- * * set cli_blocked -- waiting for output to complete
- * * and prepare the line control for output
+ * * clear cli->dispatched
+ * * clear cli->in_progress
+ * * clear cli->blocked
*
- * If the return is CMD_CLOSE, then also now does the required half close.
+ * May be in more_wait state -- so avoids touching that.
*
- * Note that apart from CMD_CLOSE, don't really care what the return was. Any
- * diagnostics or other action must be dealt with elsewhere (as part of the
- * command execution.
+ * If not in_progress, then if dispatched, we have a new command ready to pass
+ * to the command loop -- which we do here, and set cli->in_progress.
*
- * Note that everything proceeds as if there is some output. So after every
- * command goes through at least one write_ready event.
+ * Returns: CMD_SUCCESS -- the given action has been set to next command
+ * or: CMD_WAITING -- no command available (yet)
*
- * This ensures some multiplexing at the command level.
+ * Note that for the CLI eof and read time-out are handled as cmd_do_eof and
+ * cmd_do_timed_out -- so will float through as CMD_SUCCESS and be processed
+ * as commands.
*
- * It also means that the decision about whether there is anything to output
- * is left to the output code.
+ * Write I/O errors and time-outs are signalled by uty_vf_error(), and
+ * therefore caught in the command loop.
+ *
+ * Read I/O errors are signalled by uty_vf_error(). Read timeout is treated
+ * as above.
*/
-static void
-uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret)
+extern cmd_return_code_t
+uty_cli_want_command(vty_cli cli, cmd_action action, cmd_context context)
{
VTY_ASSERT_LOCKED() ;
- assert(vio->cmd_in_progress && !vio->cmd_out_enabled) ;
+ if (cli->in_progress)
+ {
+ /* Previous command has completed
+ *
+ * Make sure state reflects the fact that we are now waiting for a
+ * command.
+ */
+ cli->dispatched = false ; /* no longer have a dispatched command */
+ cli->in_progress = false ; /* command complete */
+ cli->blocked = false ; /* releases possibly blocked command */
+ cli->paused = false ; /* override paused state */
+
+ *cli->context = *context ; /* make sure is up to date */
+ cli->auth_context = ( (cli->context->node == AUTH_NODE)
+ || (cli->context->node == AUTH_ENABLE_NODE) ) ;
+
+ /* If the output is owned by command output, then when the buffers
+ * empty, and in_progress is seen to be false, out_active will be
+ * cleared.
+ *
+ * If the output side is not owned by command output, rewrite the
+ * command line, so that prompt is up to date and visible.
+ *
+ * In any case, kick write_ready to ensure output clears and prompt is
+ * written and so on.
+ */
+ if (!cli->out_active)
+ uty_cli_draw(cli) ;
- if (ret == CMD_CLOSE)
- uty_half_close(vio, NULL) ;
+ uty_term_set_readiness(cli->vf, write_ready) ;
+ }
+ else if (cli->dispatched)
+ {
+ /* New command has been dispatched -- can now pass that to the
+ * command loop -- setting it in_progress.
+ */
+ assert(cli->dispatch->to_do != cmd_do_nothing) ;
+ cmd_action_take(action, cli->dispatch) ;
- vio->cmd_in_progress = 0 ; /* command complete */
- vio->cmd_out_enabled = 1 ; /* enable the output */
- vio->cli_blocked = 1 ; /* now blocked waiting for output */
+ cli->in_progress = true ;
+ } ;
- vio->vty->buf = NULL ; /* finished with command line */
+ return cli->in_progress ? CMD_SUCCESS : CMD_WAITING ;
+} ;
- uty_cmd_output_start(vio) ; /* reset line control (belt & braces) */
+/*------------------------------------------------------------------------------
+ * Start pause timer and set paused.
+ */
+static void
+uty_cli_pause_start(vty_cli cli)
+{
+ qtimer_set(cli->pause_timer, qt_add_monotonic(QTIME(0.2)), NULL) ;
+ cli->paused = true ;
} ;
/*==============================================================================
* The "--more--" CLI
*
- * While command output is being cleared from its FIFO, the CLI is cli_blocked.
+ * While command output is being cleared from its FIFO, the CLI is blocked.
*
* When the output side signals that "--more--" is required, it sets the
- * cli_more_wait flag and clears the cmd_out_enabled flag.
+ * more_wait and more_enter flags.
*
* The first stage of handling "--more--" is to suck the input dry, so that
* (as far as is reasonably possible) does not steal a keystroke as the
* "--more--" response which was typed before the prompt was issued.
*
- * The cli_blocked flag indicates that the CLI is in this first stage.
+ * The more_enter flag indicates that the CLI is in this first stage.
*/
/*------------------------------------------------------------------------------
@@ -691,146 +961,211 @@ uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret)
* Outputs the new prompt line.
*/
extern void
-uty_cli_enter_more_wait(vty_io vio)
+uty_cli_enter_more_wait(vty_cli cli)
{
VTY_ASSERT_LOCKED() ;
- assert(vio->cli_blocked && vio->cmd_out_enabled && !vio->cli_more_wait) ;
+ qassert(cli->out_active && !cli->more_wait && !cli->drawn) ;
- uty_cli_wipe(vio, 0) ; /* make absolutely sure that command line is
- wiped before change the CLI state */
-
- vio->cmd_out_enabled = 0 ; /* stop output pro tem */
- vio->cli_more_wait = 1 ; /* new state */
-
- uty_cli_draw(vio) ; /* draw the "--more--" */
+ cli->more_wait = true ; /* new state */
+ cli->more_enter = true ; /* drawing the "--more--" etc. */
} ;
/*------------------------------------------------------------------------------
- * Exit the "--more--" CLI.
+ * Handle the "--more--" state.
*
- * Wipes the "--more--" prompt.
+ * If is paused or the monitor is active, do nothing -- those override the
+ * "--more--" state.
*
- * This is used when the user responds to the prompt.
+ * If more_enter is set, then need to make sure the "--more--" prompt is
+ * written, and that any keystrokes which arrived before the prompt is
+ * written are taken into the keystroke stream -- so not stolen.
*
- * It is also used when the vty is "half-closed". In this case, it is (just)
- * possible that the '--more--' prompt is yet to be completely written away,
- * so:
+ * Note that the "--more--" state may be interrupted by monitor output,
+ * which, once complete, sets more_enter again.
*
- * * assert that is either: !vio->cli_blocked (most of the time it will)
- * or: !vio_fifo_empty(&vio->cli_obuf)
- *
- * * note that can wipe the prompt even though it hasn't been fully
- * written away yet. (The effect is to append the wipe action to the
- * cli_obuf !)
- */
-extern void
-uty_cli_exit_more_wait(vty_io vio)
-{
- VTY_ASSERT_LOCKED() ;
-
- assert( (!vio->cli_blocked || !vio_fifo_empty(&vio->cli_obuf))
- && !vio->cmd_out_enabled && vio->cli_more_wait) ;
-
- uty_cli_wipe(vio, 0) ; /* wipe the prompt ('--more--')
- before changing the CLI state */
-
- vio->cli_blocked = 1 ; /* back to blocked waiting for output */
- vio->cli_more_wait = 0 ; /* exit more_wait */
- vio->cmd_out_enabled = 1 ; /* re-enable output */
-} ;
-
-/*------------------------------------------------------------------------------
- * Handle the "--more--" state.
- *
- * Deals with the first stage if cli_blocked.
- *
- * Tries to steal a keystroke, and when succeeds wipes the "--more--"
- * prompt and exits cli_more_wait -- and may cancel all outstanding output.
+ * If more_enter is not set, tries to steal a keystroke, and if succeeds wipes
+ * the "--more--" prompt and exits cli_more_wait -- and may cancel all
+ * outstanding output.
*
* EOF on input causes immediate exit from cli_more_state.
*
- * Returns: read_ready -- waiting to steal a keystroke
- * now_ready -- just left cli_more_wait
- * not_ready -- otherwise
+ * Returns: read_ready -- waiting to steal a keystroke
+ * write_ready -- waiting to draw or undraw the "--more--" prompt
+ * not_ready -- otherwise
*/
-static enum vty_readiness
-uty_cli_more_wait(vty_io vio)
+static vty_readiness_t
+uty_cli_more_wait(vty_cli cli)
{
- struct keystroke steal ;
+ keystroke_t steal ;
+ bool cancel, stolen ;
VTY_ASSERT_LOCKED() ;
+ assert(cli->more_wait) ; /* must be in more_wait state ! */
+
+ if (cli->paused || cli->mon_active)
+ return not_ready ;
+
/* Deal with the first stage of "--more--" */
- if (vio->cli_blocked)
+ if (cli->more_enter)
{
int get ;
+ if (!cli->drawn)
+ uty_cli_draw(cli) ; /* draw the "--more--" */
+
/* If the CLI buffer is not yet empty, then is waiting for the
* initial prompt to clear, so nothing to be done here.
*/
- if (!vio_fifo_empty(&vio->cli_obuf))
- return not_ready ;
+ if (!vio_fifo_empty(cli->cbuf))
+ return write_ready ;
- vio->cli_blocked = 0 ;
+ cli->more_enter = false ;
/* empty the input buffer into the keystroke stream */
do
{
- get = uty_read(vio, NULL) ;
+ get = uty_term_read(cli->vf) ;
} while (get > 0) ;
} ;
- /* Go through the "--more--" process, unless no longer write_open (!) */
- if (vio->sock.write_open)
- {
- /* The read fetches a reasonable lump from the I/O -- so if there
- * is a complete keystroke available, expect to get it.
- *
- * If no complete keystroke available to steal, returns ks_null.
- *
- * If has hit EOF (or error etc), returns knull_eof.
- */
- uty_read(vio, &steal) ;
+ /* Try to get a stolen keystroke. If the keystroke stream has hit
+ * EOF (for any reason, including error or timed-out), will get a ks_null
+ * stolen keystroke.
+ *
+ * If nothing to be stolen exit.
+ */
+ stolen = keystroke_steal(cli->key_stream, steal) ;
- /* If nothing stolen, make sure prompt is drawn and wait for more
- * input.
- */
- if ((steal.type == ks_null) && (steal.value != knull_eof))
- {
- if (uty_cli_draw_if_required(vio)) /* "--more--" if req. */
- return write_ready ;
- else
- return read_ready ;
- } ;
+ if (!stolen)
+ return read_ready ;
- /* Stolen a keystroke -- a (very) few terminate all output */
- if (steal.type == ks_char)
- {
- switch (steal.value)
+ /* Something has been stolen, so exit "--more--" state, and continue
+ * or cancel.
+ */
+ cancel = false ;
+ switch(steal->type)
+ {
+ case ks_null: /* at EOF for whatever reason */
+ cancel = true ;
+ break ;
+
+ case ks_char:
+ switch (steal->value)
{
case CONTROL('C'):
+ case CONTROL('Z'):
case 'q':
case 'Q':
- uty_out_clear(vio) ;
+ cancel = true ;
break;
- default: /* everything else, thrown away */
+ default:
break ;
} ;
- } ;
+ break ;
+
+ case ks_esc:
+ cancel = steal->value == '\x1B' ;
+ break ;
+
+ default:
+ break ;
} ;
/* End of "--more--" process
*
- * Wipe out the prompt and update state.
+ * If cancelling, make sure is at end of "--more--" prompt, and then
+ * clear the "drawn". The cancel process will append a " ^C\n" !
+ *
+ * If not cancelling, wipe out the prompt and update state.
*
* Return write_ready to tidy up the screen and, unless cleared, write
* some more.
*/
- uty_cli_exit_more_wait(vio) ;
+ if (cancel)
+ {
+ vio_fifo_clear(cli->vf->obuf, false) ;
+ vio_lc_clear(cli->olc) ; /* clear & reset counter */
- return now_ready ;
+ uty_cli_goto_end_if_drawn(cli) ;
+ cli->drawn = false ;
+
+ qassert(cli->out_active) ;
+
+ uty_cmd_signal(cli->vf->vio, CMD_CANCEL) ; /* ^C */
+ }
+ else
+ {
+ vio_lc_counter_reset(cli->olc) ;
+ uty_cli_wipe(cli, 0) ;
+ } ;
+
+ cli->more_wait = false ; /* exit more_wait */
+ cli->more_enter = false ; /* tidy */
+
+ return write_ready ;
+} ;
+
+/*==============================================================================
+ * Monitor output.
+ *
+ * To prepare for monitor output, wipe as much as is necessary for the
+ * monitor line to appear correctly.
+ *
+ * After monitor output, may need to do two things:
+ *
+ * * if the output was incomplete, place the rump in the CLI buffer,
+ * so that:
+ *
+ * a. don't mess up the console with partial lines
+ *
+ * b. don't lose part of a message
+ *
+ * c. act as a brake on further monitor output -- cannot do any more
+ * until the last, part, line is dealt with.
+ *
+ * * restore the command line, unless it is empty !
+ */
+
+ /*-----------------------------------------------------------------------------
+ * Prepare for new monitor output line.
+ *
+ * Wipe any existing command line.
+ */
+extern void
+uty_cli_pre_monitor(vty_cli cli)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ uty_cli_wipe(cli, 0) ;
+
+ cli->mon_active = true ; /* block cli & enable empty of fifo */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Recover from monitor line output.
+ *
+ * If monitor line failed to complete, append the rump to the CLI buffer.
+ *
+ * If have a non-empty command line, or is cli_more_wait, redraw the command
+ * line.
+ *
+ * Returns: 0 => rump was empty and no command line stuff written
+ * > 0 => rump not empty or some command line stuff written
+ */
+extern void
+uty_cli_post_monitor(vty_cli cli)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ uty_cli_pause_start(cli) ; /* do not draw prompt immediately */
+
+ cli->more_enter = cli->more_wait ;
+ /* revert to more_enter state */
+
+ cli->mon_active = false ; /* unblock cli & turn off output */
} ;
/*==============================================================================
@@ -838,27 +1173,22 @@ uty_cli_more_wait(vty_io vio)
*
* This is buffered separately from the general (command) VTY output above.
*
- * Has a dedicated buffer in the struct vty, which is flushed regularly during
- * command processing.
+ * Has a dedicated buffer in the struct vty_cli, which is flushed regularly
+ * during command processing.
*
* It is expected that can flush straight to the file, since this is running at
* CLI speed. However, if the CLI is being driven by something other than a
- * keyboard, or "monitor" output has filled the buffers, then may need to
- * have intermediate buffering.
+ * keyboard then may need to have intermediate buffering.
*
- * No actual I/O takes place here-- all "output" is to vio->cli_obuf
- * and/or vio->cli_ex_obuf
- *
- * The "cli_echo" functions discard the output if vio->cli_echo_suppress != 0.
- * This is used while passwords are entered and to allow command line changes
- * to be made while the line is not visible.
+ * No actual I/O takes place here-- all "output" is to cli->cbuf
*/
enum { cli_rep_count = 32 } ;
typedef const char cli_rep_char[cli_rep_count] ;
+typedef const char cli_rep[] ;
-static const char telnet_backspaces[] =
+static cli_rep telnet_backspaces =
{ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
@@ -866,86 +1196,46 @@ static const char telnet_backspaces[] =
} ;
CONFIRM(sizeof(telnet_backspaces) == sizeof(cli_rep_char)) ;
-static const char telnet_spaces[] =
- { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
- ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
- ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
- ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '
- } ;
-CONFIRM(sizeof(telnet_spaces) == sizeof(cli_rep_char)) ;
+/* 12345678901234567890123456789012 */
+static cli_rep telnet_spaces = " " ;
+static cli_rep telnet_dots = "................................" ;
+static cli_rep telnet_stars = "********************************" ;
-static const char* telnet_newline = "\r\n" ;
+CONFIRM(sizeof(telnet_spaces) == (sizeof(cli_rep_char) + 1)) ;
+CONFIRM(sizeof(telnet_dots) == (sizeof(cli_rep_char) + 1)) ;
+CONFIRM(sizeof(telnet_stars) == (sizeof(cli_rep_char) + 1)) ;
-static void uty_cli_write(vty_io vio, const char *this, int len) ;
-static void uty_cli_write_n(vty_io vio, cli_rep_char chars, int n) ;
+static void uty_cli_write_n(vty_cli cli, cli_rep_char chars, int n) ;
/*------------------------------------------------------------------------------
* CLI VTY output -- cf fprintf()
*/
-static void
-uty_cli_out(vty_io vio, const char *format, ...)
-{
- VTY_ASSERT_LOCKED() ;
-
- if (vio->sock.write_open)
- {
- va_list args ;
-
- va_start (args, format);
- vio_fifo_vprintf(&vio->cli_obuf, format, args) ;
- va_end(args);
- } ;
-} ;
-
-/*------------------------------------------------------------------------------
- * CLI VTY output -- echo user input
- *
- * Do nothing if echo suppressed (eg in AUTH_NODE) or not write_open
- */
-static void
-uty_cli_echo(vty_io vio, const char *this, size_t len)
+extern void
+uty_cli_out(vty_cli cli, const char *format, ...)
{
- VTY_ASSERT_LOCKED() ;
-
- if (vio->cli_echo_suppress || !vio->sock.write_open)
- return ;
+ va_list args ;
- uty_cli_write(vio, this, len) ;
-}
-
-/*------------------------------------------------------------------------------
- * CLI VTY output -- echo 'n' characters using a cli_rep_char string
- *
- * Do nothing if echo suppressed (eg in AUTH_NODE)
- */
-static void
-uty_cli_echo_n(vty_io vio, cli_rep_char chars, int n)
-{
VTY_ASSERT_LOCKED() ;
- if (vio->cli_echo_suppress || !vio->sock.write_open)
- return ;
-
- uty_cli_write_n(vio, chars, n) ;
-}
+ va_start (args, format);
+ vio_fifo_vprintf(cli->cbuf, format, args) ;
+ va_end(args);
+} ;
/*------------------------------------------------------------------------------
* CLI VTY output -- cf write()
*/
-inline static void
-uty_cli_write(vty_io vio, const char *this, int len)
+extern void
+uty_cli_write(vty_cli cli, const char *this, int len)
{
- VTY_ASSERT_LOCKED() ;
-
- if (vio->sock.write_open)
- vio_fifo_put(&vio->cli_obuf, this, len) ;
+ vio_fifo_put_bytes(cli->cbuf, this, len) ;
} ;
/*------------------------------------------------------------------------------
* CLI VTY output -- write 'n' characters using a cli_rep_char string
*/
static void
-uty_cli_write_n(vty_io vio, cli_rep_char chars, int n)
+uty_cli_write_n(vty_cli cli, cli_rep_char chars, int n)
{
int len ;
@@ -954,7 +1244,7 @@ uty_cli_write_n(vty_io vio, cli_rep_char chars, int n)
{
if (n < len)
len = n ;
- uty_cli_write(vio, chars, len) ;
+ uty_cli_write(cli, chars, len) ;
n -= len ;
} ;
} ;
@@ -965,32 +1255,33 @@ uty_cli_write_n(vty_io vio, cli_rep_char chars, int n)
* Returns length of string.
*/
static int
-uty_cli_write_s(vty_io vio, const char *str)
+uty_cli_write_s(vty_cli cli, const char *str)
{
int len ;
len = strlen(str) ;
if (len != 0)
- uty_cli_write(vio, str, len) ;
+ uty_cli_write(cli, str, len) ;
return len ;
} ;
/*==============================================================================
- * Standard Messages
+ * Prompts and responses
*/
/*------------------------------------------------------------------------------
* Send newline to the console.
*
- * Clears the cli_drawn and the cli_dirty flags.
+ * Clears the cli_drawn flag.
*/
-static void
-uty_cli_out_newline(vty_io vio)
+extern void
+uty_cli_out_newline(vty_cli cli)
{
- uty_cli_write(vio, telnet_newline, 2) ;
- vio->cli_drawn = 0 ;
- vio->cli_dirty = 0 ;
+ uty_cli_goto_end_if_drawn(cli) ;
+
+ uty_cli_write(cli, telnet_newline, 2) ;
+ cli->drawn = false ;
} ;
/*------------------------------------------------------------------------------
@@ -1000,23 +1291,56 @@ uty_cli_out_newline(vty_io vio)
* 'n' > 0, wipes characters forwards, leaving cursor where it is
*/
static void
-uty_cli_out_wipe_n(vty_io vio, int n)
+uty_cli_out_wipe_n(vty_cli cli, int n)
{
if (n < 0)
{
n = abs(n) ;
- uty_cli_write_n(vio, telnet_backspaces, n);
+ uty_cli_write_n(cli, telnet_backspaces, n);
} ;
if (n > 0)
{
- uty_cli_write_n(vio, telnet_spaces, n) ;
- uty_cli_write_n(vio, telnet_backspaces, n) ;
+ uty_cli_write_n(cli, telnet_spaces, n) ;
+ uty_cli_write_n(cli, telnet_backspaces, n) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * If the command line is drawn, then show that it has been cancelled.
+ *
+ * Clears cli->drawn.
+ */
+static void
+uty_cli_cancel(vty_cli cli)
+{
+ if (cli->drawn)
+ {
+ uty_cli_goto_end_if_drawn(cli) ;
+
+ uty_cli_write_s(cli, " ^C" TELNET_NEWLINE) ;
+
+ cli->drawn = false ;
} ;
} ;
/*------------------------------------------------------------------------------
- * Send response to the given cli_do
+ * If the command line is drawn and the cursor is not at the end of the line,
+ * move the physical cursor to the end of the line.
+ *
+ * Assumes about to issue newline.
+ */
+static void
+uty_cli_goto_end_if_drawn(vty_cli cli)
+{
+ ulen after ;
+
+ if (cli->drawn && ( (after = qs_after_cp_nn(cli->cl)) != 0 ))
+ uty_cli_write(cli, qs_cp_char_nn(cli->cl), after) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Send response to the given cmd_do
*
* If no command is in progress, then will send newline to signal that the
* command is about to be dispatched.
@@ -1024,141 +1348,105 @@ uty_cli_out_wipe_n(vty_io vio, int n)
* If command is in progress, then leaves cursor on '^' to signal that is now
* waiting for previous command to complete.
*/
-static const char* cli_response [2][cli_do_count] =
+static const char* cli_response [2][cmd_do_count] =
{
{ /* when not waiting for previous command to complete */
- [cli_do_command] = "",
- [cli_do_ctrl_c] = "^C",
- [cli_do_ctrl_d] = "^D",
- [cli_do_ctrl_z] = "^Z",
- [cli_do_eof] = "^*"
+ [cmd_do_command] = "",
+ [cmd_do_ctrl_c] = "^C",
+ [cmd_do_ctrl_d] = "^D",
+ [cmd_do_ctrl_z] = "^Z",
+ [cmd_do_eof] = "^*",
+ [cmd_do_timed_out] = "^!"
},
{ /* when waiting for a previous command to complete */
- [cli_do_command] = "^",
- [cli_do_ctrl_c] = "^C",
- [cli_do_ctrl_d] = "^D",
- [cli_do_ctrl_z] = "^Z",
- [cli_do_eof] = "^*"
+ [cmd_do_command] = "^",
+ [cmd_do_ctrl_c] = "^C",
+ [cmd_do_ctrl_d] = "^D",
+ [cmd_do_ctrl_z] = "^Z",
+ [cmd_do_eof] = "^*",
+ [cmd_do_timed_out] = "^!"
}
} ;
static void
-uty_cli_response(vty_io vio, enum cli_do cli_do)
+uty_cli_response(vty_cli cli, cmd_do_t to_do)
{
const char* str ;
int len ;
- if ((cli_do == cli_do_nothing) || (vio->half_closed))
- return ;
+ assert((to_do != cmd_do_nothing) && cli->drawn
+ && (qs_cp_nn(cli->cl) == qs_len_nn(cli->cl))) ;
- str = (cli_do < cli_do_count)
- ? cli_response[vio->cmd_in_progress ? 1 : 0][cli_do] : NULL ;
+ str = (to_do < cmd_do_count)
+ ? cli_response[cli->dispatched ? 1 : 0][to_do] : NULL ;
assert(str != NULL) ;
- len = uty_cli_write_s(vio, str) ;
+ len = uty_cli_write_s(cli, str) ;
- if (vio->cmd_in_progress)
+ if (cli->dispatched)
{
- vio->cli_extra_len = len ;
- uty_cli_write_n(vio, telnet_backspaces, len) ;
+ cli->extra_len = len ;
+ uty_cli_write_n(cli, telnet_backspaces, len) ;
}
else
{
- uty_cli_out_newline(vio) ;
+ uty_cli_out_newline(cli) ;
} ;
} ;
/*------------------------------------------------------------------------------
- * Send various messages with trailing newline.
+ * Current prompt length
*/
-
-static void
-uty_cli_out_CMD_ERR_AMBIGUOUS(vty_io vio)
+extern ulen
+uty_cli_prompt_len(vty_cli cli)
{
- uty_cli_write_s(vio, "% " MSG_CMD_ERR_AMBIGUOUS ".") ;
- uty_cli_out_newline(vio) ;
+ return cli->prompt_len ;
} ;
-static void
-uty_cli_out_CMD_ERR_NO_MATCH(vty_io vio)
-{
- uty_cli_write_s(vio, "% " MSG_CMD_ERR_NO_MATCH ".") ;
- uty_cli_out_newline(vio) ;
-} ;
-
-/*==============================================================================
- * Command line draw and wipe
- */
-
/*------------------------------------------------------------------------------
* Wipe the current console line -- if any.
+ *
+ * If it is known that some characters will immediately be written, then
+ * passing the number of them as "len" will reduce the number of characters
+ * that have to be sent.
*/
-static void
-uty_cli_wipe(vty_io vio, int len)
+extern void
+uty_cli_wipe(vty_cli cli, int len)
{
int a ;
int b ;
- if (!vio->cli_drawn)
+ if (!cli->drawn)
return ; /* quit if already wiped */
- assert(vio->cl.cp <= vio->cl.len) ;
+ assert(qs_cp_nn(cli->cl) <= qs_len_nn(cli->cl)) ;
/* Establish how much ahead and how much behind the cursor */
- a = vio->cli_extra_len ;
- b = vio->cli_prompt_len ;
+ a = cli->extra_len ;
+ b = cli->prompt_len ;
- if (!vio->cli_echo_suppress && !vio->cli_more_wait)
+ if (!cli->more_wait)
{
- a += vio->cl.len - vio->cl.cp ;
- b += vio->cl.cp ;
+ a += qs_len_nn(cli->cl) - qs_cp_nn(cli->cl) ;
+ b += qs_cp_nn(cli->cl) ;
} ;
/* Wipe anything ahead of the current position and ahead of new len */
if ((a + b) > len)
- uty_cli_out_wipe_n(vio, +a) ;
+ uty_cli_out_wipe_n(cli, +a) ;
/* Wipe anything behind current position, but ahead of new len */
if (b > len)
{
- uty_cli_out_wipe_n(vio, -(b - len)) ;
+ uty_cli_out_wipe_n(cli, -(b - len)) ;
b = len ; /* moved the cursor back */
} ;
/* Back to the beginning of the line */
- uty_cli_write_n(vio, telnet_backspaces, b) ;
+ uty_cli_write_n(cli, telnet_backspaces, b) ;
/* Nothing there any more */
- vio->cli_drawn = 0 ;
- vio->cli_dirty = 0 ;
-} ;
-
-/*------------------------------------------------------------------------------
- * If not currently drawn, draw prompt etc according to the current state
- * and node.
- *
- * See uty_cli_draw().
- */
-extern bool
-uty_cli_draw_if_required(vty_io vio)
-{
- if (vio->cli_drawn)
- return false ;
-
- uty_cli_draw(vio) ;
-
- return true ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Draw prompt etc for the current vty node.
- *
- * See uty_cli_draw_this()
- */
-static void
-uty_cli_draw(vty_io vio)
-{
- uty_cli_draw_this(vio, vio->vty->node) ;
+ cli->drawn = false ;
} ;
/*------------------------------------------------------------------------------
@@ -1171,50 +1459,45 @@ uty_cli_draw(vty_io vio)
*
* Draws prompt according to the given 'node', except:
*
- * * if is half_closed, draw nothing -- wipes the current line
+ * * if is closing, draw nothing -- wipes the current line
*
- * * if is cli_more_wait, draw the "--more--" prompt
+ * * if is more_wait, draw the "--more--" prompt
*
- * * if is cmd_in_progress, draw the vestigial prompt.
+ * * if is dispatched, draw the vestigial prompt.
*
* By the time the current command completes, the node may have changed, so
* the current prompt may be invalid.
*
- * Sets: cli_drawn = true
- * cli_dirty = false
- * cli_prompt_len = length of prompt used
- * cli_extra_len = 0
- * cli_echo_suppress = (AUTH_NODE or AUTH_ENABLE_NODE)
+ * Sets: cli->drawn = true
+ * cli->prompt_len = length of prompt used
+ * cli->extra_len = 0
*/
static void
-uty_cli_draw_this(vty_io vio, enum node_type node)
+uty_cli_draw(vty_cli cli)
{
const char* prompt ;
size_t l_len ;
int p_len ;
- if (vio->cli_dirty)
- uty_cli_out_newline(vio) ; /* clears cli_dirty and cli_drawn */
-
/* Sort out what the prompt is. */
- if (vio->half_closed)
+ if (cli->vf->vin_state != vf_open)
{
prompt = "" ;
p_len = 0 ;
l_len = 0 ;
}
- else if (vio->cli_more_wait)
+ else if (cli->more_wait)
{
prompt = "--more--" ;
p_len = strlen(prompt) ;
l_len = 0 ;
}
- else if (vio->cmd_in_progress)
+ else if (cli->dispatched)
{
/* If there is a queued command, the prompt is a minimal affair. */
prompt = "~ " ;
p_len = strlen(prompt) ;
- l_len = vio->cl.len ;
+ l_len = qs_len_nn(cli->cl) ;
}
else
{
@@ -1225,316 +1508,234 @@ uty_cli_draw_this(vty_io vio, enum node_type node)
*
* If the host name changes, the cli_prompt_set flag is cleared.
*/
- if (!vio->cli_prompt_set || (node != vio->cli_prompt_node))
+ node_type_t node = cli->context->node ;
+
+ if ((node != cli->prompt_node) || (host.name_gen != cli->prompt_gen))
{
const char* prompt ;
- if (vty_host_name == NULL)
- uty_check_host_name() ; /* should never be required */
-
prompt = cmd_prompt(node) ;
if (prompt == NULL)
{
- zlog_err("vty %s has node %d", uty_get_name(vio), node) ;
+ zlog_err("vty %s has node %d", uty_get_name(cli->vf->vio), node) ;
prompt = "%s ???: " ;
} ;
- qs_printf(&vio->cli_prompt_for_node, prompt, vty_host_name);
+ cli->prompt_for_node =
+ qs_printf(cli->prompt_for_node, prompt, host.name) ;
- vio->cli_prompt_node = node ;
- vio->cli_prompt_set = 1 ;
+ cli->prompt_node = node ;
+ cli->prompt_gen = host.name_gen ;
} ;
- prompt = vio->cli_prompt_for_node.body ;
- p_len = vio->cli_prompt_for_node.len ;
- l_len = vio->cl.len ;
+ prompt = qs_char(cli->prompt_for_node) ;
+ p_len = qs_len(cli->prompt_for_node) ;
+ l_len = qs_len_nn(cli->cl) ;
} ;
/* Now, if line is currently drawn, time to wipe it */
- if (vio->cli_drawn)
- uty_cli_wipe(vio, p_len + l_len) ;
+ if (cli->drawn)
+ uty_cli_wipe(cli, p_len + l_len) ;
/* Set about writing the prompt and the line */
- vio->cli_drawn = 1 ;
- vio->cli_extra_len = 0 ;
- vio->cli_echo_suppress = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ;
+ cli->drawn = true ;
+ cli->extra_len = false ;
- vio->cli_prompt_len = p_len ;
+ cli->prompt_len = p_len ;
- uty_cli_write(vio, prompt, p_len) ;
+ uty_cli_write(cli, prompt, p_len) ;
if (l_len != 0)
{
- uty_cli_write(vio, qs_chars(&vio->cl), l_len) ;
- if (vio->cl.cp < l_len)
- uty_cli_write_n(vio, telnet_backspaces, l_len - vio->cl.cp) ;
- } ;
-} ;
-
-/*==============================================================================
- * Monitor output.
- *
- * To prepare for monitor output, wipe as much as is necessary for the
- * monitor line to appear correctly.
- *
- * After monitor output, may need to do two things:
- *
- * * if the output was incomplete, place the rump in the CLI buffer,
- * so that:
- *
- * a. don't mess up the console with partial lines
- *
- * b. don't lose part of a message
- *
- * c. act as a brake on further monitor output -- cannot do any more
- * until the last, part, line is dealt with.
- *
- * * restore the command line, unless it is empty !
- */
-
- /*-----------------------------------------------------------------------------
- * Prepare for new monitor output line.
- *
- * Wipe any existing command line.
- */
-extern void
-uty_cli_pre_monitor(vty_io vio, size_t len)
-{
- VTY_ASSERT_LOCKED() ;
-
- uty_cli_wipe(vio, len) ;
-} ;
-
-/*------------------------------------------------------------------------------
- * Recover from monitor line output.
- *
- * If monitor line failed to complete, append the rump to the CLI buffer.
- *
- * If have a non-empty command line, or is cli_more_wait, redraw the command
- * line.
- *
- * Returns: 0 => rump was empty and no command line stuff written
- * > 0 => rump not empty or some command line stuff written
- */
-extern int
-uty_cli_post_monitor(vty_io vio, const char* buf, size_t len)
-{
- VTY_ASSERT_LOCKED() ;
-
- if (len != 0)
- uty_cli_write(vio, buf, len) ;
+ if (cli->auth_context)
+ uty_cli_write_n(cli, telnet_stars, l_len) ;
+ else
+ uty_cli_write(cli, qs_char(cli->cl), l_len) ;
- if (vio->cli_more_wait || (vio->cl.len != 0))
- {
- uty_cli_draw(vio) ;
- ++len ;
+ if (qs_cp_nn(cli->cl) < l_len)
+ uty_cli_write_n(cli, telnet_backspaces, l_len - qs_cp_nn(cli->cl)) ;
} ;
-
- return len ;
} ;
/*==============================================================================
* Command line processing loop
*/
+static cmd_do_t uty_cli_get_keystroke(vty_cli cli, keystroke stroke) ;
+static void uty_cli_update_line(vty_cli cli, uint rc) ;
-static bool uty_telnet_command(vty_io vio, keystroke stroke, bool callback) ;
-static int uty_cli_insert (vty_io vio, const char* chars, int n) ;
-static int uty_cli_overwrite (vty_io vio, char* chars, int n) ;
-static int uty_cli_word_overwrite (vty_io vio, char *str) ;
-static int uty_cli_forwards(vty_io vio, int n) ;
-static int uty_cli_backwards(vty_io vio, int n) ;
-static int uty_cli_del_forwards(vty_io vio, int n) ;
-static int uty_cli_del_backwards(vty_io vio, int n) ;
-static int uty_cli_bol (vty_io vio) ;
-static int uty_cli_eol (vty_io vio) ;
-static int uty_cli_word_forwards_delta(vty_io vio) ;
-static int uty_cli_word_forwards(vty_io vio) ;
-static int uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) ;
-static int uty_cli_word_backwards_pure (vty_io vio) ;
-static int uty_cli_word_backwards (vty_io vio) ;
-static int uty_cli_del_word_forwards(vty_io vio) ;
-static int uty_cli_del_word_backwards(vty_io vio) ;
-static int uty_cli_del_to_eol (vty_io vio) ;
-static int uty_cli_clear_line(vty_io vio) ;
-static int uty_cli_transpose_chars(vty_io vio) ;
-static void uty_cli_history_use(vty_io vio, int step) ;
-static void uty_cli_next_line(vty_io vio) ;
-static void uty_cli_previous_line (vty_io vio) ;
-static void uty_cli_complete_command (vty_io vio, enum node_type node) ;
-static void uty_cli_describe_command (vty_io vio, enum node_type node) ;
+static void uty_cli_insert (qstring cl, const char* chars, int n) ;
+static int uty_cli_forwards(qstring cl, int n) ;
+static int uty_cli_backwards(qstring cl, int n) ;
+static void uty_cli_del_forwards(qstring cl, int n) ;
+static void uty_cli_del_backwards(qstring cl, int n) ;
+static void uty_cli_bol(qstring cl) ;
+static void uty_cli_eol(qstring cl) ;
+static int uty_cli_word_forwards_delta(qstring cl) ;
+static void uty_cli_word_forwards(qstring cl) ;
+static int uty_cli_word_backwards_delta(qstring cl) ;
+static void uty_cli_word_backwards(qstring cl) ;
+static void uty_cli_del_word_forwards(qstring cl) ;
+static void uty_cli_del_word_backwards(qstring cl) ;
+static void uty_cli_del_to_eol(qstring cl) ;
+static void uty_cli_clear_line(qstring cl) ;
+static void uty_cli_transpose_chars(qstring cl) ;
+static void uty_cli_complete_command (vty_cli cli) ;
+static void uty_cli_describe_command (vty_cli cli) ;
-/*------------------------------------------------------------------------------
- * Fetch next keystroke, reading from the file if required.
- */
-static inline bool
-uty_cli_get_keystroke(vty_io vio, keystroke stroke)
+enum hist_step
{
- if (keystroke_get(vio->key_stream, stroke))
- return 1 ;
-
- uty_read(vio, NULL) ; /* not stealing */
+ hist_previous = -1,
+ hist_next = +1,
+};
- return keystroke_get(vio->key_stream, stroke) ;
-} ;
+static void uty_cli_hist_use(vty_cli cli, enum hist_step) ;
/*------------------------------------------------------------------------------
- * Process keystrokes until run out of input, or get something to cli_do.
+ * Process keystrokes until run out of input, or get something to cmd_do.
*
- * If required, draw the prompt and command line.
+ * Expects the command line to have been drawn, so that what is in cli->cl
+ * is what is on the screen.
*
* Process keystrokes until run out of stuff to do, or have a "command line"
- * that must now be executed.
+ * that must now be executed. Updates cli->cl.
*
* Processes the contents of the keystroke stream. If exhausts that, will set
* ready to read and return. (To give some "sharing".)
*
- * Returns: cli_do_xxxx
+ * Returns: cmd_do_xxxx
*
- * When returns the cl is '\0' terminated.
+ * Note that will return cmd_do_eof or cmd_do_timed_out any number of times.
*/
-static enum cli_do
-uty_cli_process(vty_io vio, enum node_type node)
+static cmd_do_t
+uty_cli_process(vty_cli cli)
{
- struct keystroke stroke ;
- uint8_t u ;
- int auth ;
- enum cli_do ret ;
+ keystroke_t stroke ;
+ uint8_t u ;
+ cmd_do_t to_do ;
+
+ qs_copy(cli->cls, cli->cl) ; /* current screen line */
- auth = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ;
+ assert(cli->drawn) ;
/* Now process as much as possible of what there is */
- ret = cli_do_nothing ;
- while (1)
+ do
{
- if (!vio->cli_drawn)
- uty_cli_draw_this(vio, node) ;
+ to_do = uty_cli_get_keystroke(cli, stroke) ;
- if (!uty_cli_get_keystroke(vio, &stroke))
- {
- ret = (stroke.value == knull_eof) ? cli_do_eof : cli_do_nothing ;
- break ;
- } ;
-
- if (stroke.flags != 0)
- {
- /* TODO: deal with broken keystrokes */
- continue ;
- } ;
+ if (to_do != cmd_do_keystroke)
+ break ;
- switch (stroke.type)
+ switch (stroke->type)
{
/* Straightforward character -----------------------------------*/
/* Note: only interested in 8-bit characters ! */
case ks_char:
- u = (uint8_t)stroke.value ;
+ u = (uint8_t)stroke->value ;
- switch (stroke.value)
+ switch (stroke->value)
{
case CONTROL('A'):
- uty_cli_bol (vio);
+ uty_cli_bol(cli->cl) ;
break;
case CONTROL('B'):
- uty_cli_backwards(vio, 1);
+ uty_cli_backwards(cli->cl, 1);
break;
case CONTROL('C'):
- ret = cli_do_ctrl_c ; /* Exit on ^C ..................*/
+ to_do = cmd_do_ctrl_c ; /* Exit on ^C ..................*/
break ;
case CONTROL('D'):
- if (vio->cl.len == 0) /* if at start of empty line */
- ret = cli_do_ctrl_d ; /* Exit on ^D ..................*/
- else
- uty_cli_del_forwards(vio, 1);
+ uty_cli_del_forwards(cli->cl, 1);
break;
case CONTROL('E'):
- uty_cli_eol (vio);
+ uty_cli_eol(cli->cl);
break;
case CONTROL('F'):
- uty_cli_forwards(vio, 1);
+ uty_cli_forwards(cli->cl, 1);
break;
case CONTROL('H'):
case 0x7f:
- uty_cli_del_backwards(vio, 1);
+ uty_cli_del_backwards(cli->cl, 1);
break;
case CONTROL('K'):
- uty_cli_del_to_eol (vio);
+ uty_cli_del_to_eol(cli->cl);
break;
case CONTROL('N'):
- uty_cli_next_line (vio);
+ uty_cli_hist_use(cli, hist_next) ;
break;
case CONTROL('P'):
- uty_cli_previous_line (vio);
+ uty_cli_hist_use(cli, hist_previous) ;
break;
case CONTROL('T'):
- uty_cli_transpose_chars (vio);
+ uty_cli_transpose_chars(cli->cl) ;
break;
case CONTROL('U'):
- uty_cli_clear_line(vio);
+ uty_cli_clear_line(cli->cl) ;
break;
case CONTROL('W'):
- uty_cli_del_word_backwards (vio);
+ uty_cli_del_word_backwards(cli->cl) ;
break;
case CONTROL('Z'):
- ret = cli_do_ctrl_z ; /* Exit on ^Z ..................*/
+ to_do = cmd_do_ctrl_z ; /* Exit on ^Z ..................*/
break;
case '\n':
case '\r':
- ret = cli_do_command ; /* Exit on CR or LF.............*/
+ to_do = cmd_do_command ; /* Exit on CR or LF.............*/
break ;
case '\t':
- if (auth)
- break ;
+ if (cmd_token_position(cli->parsed, cli->cl))
+ uty_cli_insert (cli->cl, " ", 1) ;
else
- uty_cli_complete_command (vio, node);
+ uty_cli_complete_command(cli);
break;
case '?':
- if (auth)
- uty_cli_insert (vio, (char*)&u, 1);
+ if (cmd_token_position(cli->parsed, cli->cl))
+ uty_cli_insert(cli->cl, (char*)&u, 1) ;
else
- uty_cli_describe_command (vio, node);
+ uty_cli_describe_command(cli);
break;
default:
- if ((stroke.value >= 0x20) && (stroke.value < 0x7F))
- uty_cli_insert (vio, (char*)&u, 1) ;
+ if ((stroke->value >= 0x20) && (stroke->value < 0x7F))
+ uty_cli_insert(cli->cl, (char*)&u, 1) ;
break;
}
break ;
/* ESC X -------------------------------------------------------------*/
case ks_esc:
- switch (stroke.value)
+ switch (stroke->value)
{
case 'b':
- uty_cli_word_backwards (vio);
+ uty_cli_word_backwards(cli->cl);
break;
case 'f':
- uty_cli_word_forwards (vio);
+ uty_cli_word_forwards(cli->cl);
break;
case 'd':
- uty_cli_del_word_forwards (vio);
+ uty_cli_del_word_forwards(cli->cl);
break;
case CONTROL('H'):
case 0x7f:
- uty_cli_del_word_backwards (vio);
+ uty_cli_del_word_backwards(cli->cl);
break;
default:
@@ -1542,36 +1743,39 @@ uty_cli_process(vty_io vio, enum node_type node)
} ;
break ;
- /* ESC [ X -----------------------------------------------------------*/
+ /* ESC [ ... ---------------------------------------------------------*/
case ks_csi:
- if (stroke.len != 0)
- break ; /* only recognise 3 byte sequences */
-
- switch (stroke.value)
- {
- case ('A'):
- uty_cli_previous_line (vio);
- break;
-
- case ('B'):
- uty_cli_next_line (vio);
- break;
-
- case ('C'):
- uty_cli_forwards(vio, 1);
- break;
-
- case ('D'):
- uty_cli_backwards(vio, 1);
- break;
- default:
- break ;
- } ;
- break ;
-
- /* Telnet Command ----------------------------------------------------*/
- case ks_iac:
- uty_telnet_command(vio, &stroke, false) ;
+ if (stroke->len == 0)
+ {
+ /* ESC [ X */
+ switch (stroke->value)
+ {
+ case ('A'): /* up arrow */
+ uty_cli_hist_use(cli, hist_previous) ;
+ break;
+
+ case ('B'): /* down arrow */
+ uty_cli_hist_use(cli, hist_next) ;
+ break;
+
+ case ('C'): /* right arrow */
+ uty_cli_forwards(cli->cl, 1) ;
+ break;
+
+ case ('D'): /* left arrow */
+ uty_cli_backwards(cli->cl, 1) ;
+ break;
+
+ default:
+ break ;
+ } ;
+ }
+ else if (stroke->len == 1)
+ {
+ /* ESC [ 3 ~ only ! */
+ if ((stroke->value == '~') && (stroke->buf[0] == '3'))
+ uty_cli_del_forwards(cli->cl, 1) ;
+ } ;
break ;
/* Unknown -----------------------------------------------------------*/
@@ -1579,99 +1783,306 @@ uty_cli_process(vty_io vio, enum node_type node)
zabort("unknown keystroke type") ;
} ;
- /* After each keystroke..... */
-
- if (ret != cli_do_nothing)
- {
- uty_cli_eol (vio) ; /* go to the end of the line */
- break ; /* stop processing */
- } ;
- } ;
+ }
+ while (to_do == cmd_do_keystroke) ;
/* Tidy up and return where got to. */
- qs_term(&vio->cl) ; /* add '\0' */
+ if (to_do != cmd_do_nothing)
+ uty_cli_eol(cli->cl) ; /* go to the end of the line */
- return ret ;
-} ;
+ uty_cli_update_line(cli, qs_cp_nn(cli->cl)) ;
-/*==============================================================================
- * Command line operations
- */
+ return to_do ;
+} ;
/*------------------------------------------------------------------------------
- * Insert 'n' characters at current position in the command line
+ * Update the command line to reflect the difference between old line and the
+ * new line.
*
- * Returns number of characters inserted -- ie 'n'
+ * cli->cls is the old line (currently on screen)
+ * cli->cl is the new line (possible changed in some way)
+ *
+ * Leave the screen cursor at the given required cursor position.
*/
-static int
-uty_cli_insert (vty_io vio, const char* chars, int n)
+static void
+uty_cli_update_line(vty_cli cli, uint rc)
{
- int after ;
+ const char* np ; /* new line */
+ const char* sp ; /* screen line */
- VTY_ASSERT_LOCKED() ;
+ ulen nl ; /* new length */
+ ulen sl ; /* screen length */
- assert((vio->cl.cp <= vio->cl.len) && (n >= 0)) ;
+ ulen sc ; /* screen cursor */
- if (n <= 0)
- return n ; /* avoid trouble */
+ ulen s, l ;
- after = qs_insert(&vio->cl, chars, n) ;
+ assert(cli->drawn) ;
- uty_cli_echo(vio, qs_cp_char(&vio->cl), after + n) ;
+ np = qs_char_nn(cli->cl) ;
+ nl = qs_len_nn(cli->cl) ;
- if (after != 0)
- uty_cli_echo_n(vio, telnet_backspaces, after) ;
+ sp = qs_char_nn(cli->cls) ;
+ sl = qs_len_nn(cli->cls) ;
+ sc = qs_cp_nn(cli->cls) ;
- vio->cl.cp += n ;
+ /* Find how many characters are the same. */
+ l = (nl <= sl) ? nl : sl ;
+ s = 0 ;
+ while ((s < l) && (*(np + s) == *(sp + s)))
+ ++s ;
- return n ;
+ /* If the screen and new are different lengths, or the strings are not
+ * the same, then need to draw stuff to correct what is on the screen.
+ * That will leave the screen cursor at the end of the new line, after
+ * any spaces required to wipe out excess characters.
+ *
+ * Note that sc is the current cursor position on the screen, and we keep
+ * that up to date as we draw stuff.
+ */
+ if ((nl != sl) || (s != nl))
+ {
+ /* Move back if the screen cursor is beyond the same section */
+ if (sc > s)
+ {
+ uty_cli_write_n(cli, telnet_backspaces, sc - s) ;
+ sc = s ;
+ } ;
+
+ /* Write from cursor to the end of the new line. */
+ uty_cli_write(cli, np + sc, nl - sc) ;
+ sc = nl ;
+
+ /* If the old line was longer, need to wipe out old stuff */
+ if (sl > nl)
+ {
+ uty_cli_write_n(cli, telnet_spaces, sl - nl) ;
+ sc = sl ;
+ } ;
+ } ;
+
+ /* Now move cursor to the required cursor position */
+ if (sc > rc)
+ uty_cli_write_n(cli, telnet_backspaces, sc - rc) ;
+ else if (sc < rc) /* => lines unchanged, but cursor moved */
+ uty_cli_write(cli, np + sc, rc - sc) ;
} ;
/*------------------------------------------------------------------------------
- * Overstrike 'n' characters at current position in the command line
+ * For password: process keystrokes until run out of input, or get something
+ * to cmd_do.
+ *
+ * Similar to uty_cli_auth, except accepts a limited number of keystrokes.
*
- * Move current position forwards.
+ * Does not accept cursor moves, so does not do forwards delete, and ^D means
+ * exit.
*
- * Returns number of characters inserted -- ie 'n'
+ * Returns: cmd_do_xxxx
+ *
+ * Note that will return cmd_do_eof or cmd_do_timed_out any number of times.
*/
-static int
-uty_cli_overwrite (vty_io vio, char* chars, int n)
+static cmd_do_t
+uty_cli_auth(vty_cli cli)
{
- VTY_ASSERT_LOCKED() ;
+ keystroke_t stroke ;
+ uint8_t u ;
+ cmd_do_t to_do ;
+ int olen, nlen ;
- assert((vio->cl.cp <= vio->cl.len) && (n >= 0)) ;
+ /* For auth command lines, cursor is always at the end */
+ assert(qs_cp_nn(cli->cl) == qs_len_nn(cli->cl)) ;
- if (n > 0)
+ olen = qs_len_nn(cli->cl) ;
+
+ /* Now process as much as possible of what there is */
+ do
{
- qs_replace(&vio->cl, chars, n) ;
- uty_cli_echo(vio, chars, n) ;
+ to_do = uty_cli_get_keystroke(cli, stroke) ;
- vio->cl.cp += n ;
- } ;
+ if (to_do != cmd_do_keystroke)
+ break ;
- return n ;
-}
+ switch (stroke->type)
+ {
+ /* Straightforward character ---------------------------------*/
+ /* Note: only interested in 8-bit characters ! */
+ case ks_char:
+ u = (uint8_t)stroke->value ;
+
+ switch (stroke->value)
+ {
+ case CONTROL('C'):
+ to_do = cmd_do_ctrl_c ; /* Exit on ^C */
+ break ;
+
+ case CONTROL('D'):
+ to_do = cmd_do_ctrl_d ; /* Exit on ^D */
+ break;
+
+ case CONTROL('H'):
+ case 0x7F:
+ uty_cli_del_backwards(cli->cl, 1);
+ break;
+
+ case CONTROL('U'):
+ case CONTROL('W'):
+ uty_cli_clear_line(cli->cl);
+ break;
+
+ case CONTROL('Z'):
+ to_do = cmd_do_ctrl_z ; /* Exit on ^Z */
+ break;
+
+ case '\n':
+ case '\r':
+ to_do = cmd_do_command ; /* Exit on CR or LF */
+ break ;
+
+ default:
+ if ((stroke->value >= 0x20) && (stroke->value < 0x7F))
+ uty_cli_insert(cli->cl, (char*)&u, 1) ;
+ break;
+ }
+ break ;
+
+ /* ESC X -----------------------------------------------------*/
+ case ks_esc:
+ switch (stroke->value)
+ {
+ case CONTROL('H'):
+ case 0x7f:
+ uty_cli_clear_line(cli->cl);
+ break;
+
+ default:
+ break;
+ } ;
+ break ;
+
+ /* ESC [ ... ---------------------------------------------------*/
+ case ks_csi:
+ break ;
+
+ /* Unknown -----------------------------------------------------*/
+ default:
+ zabort("unknown keystroke type") ;
+ } ;
+ }
+ while (to_do == cmd_do_keystroke) ;
+
+ /* Tidy up and return where got to. */
+
+ nlen = qs_len_nn(cli->cl) ;
+
+ if (nlen < olen)
+ uty_cli_out_wipe_n(cli, nlen - olen) ;
+ else if (nlen > olen)
+ uty_cli_write_n(cli, telnet_stars, nlen - olen) ;
+
+ return to_do ;
+} ;
/*------------------------------------------------------------------------------
- * Insert a word into vty interface with overwrite mode.
+ * Fetch next keystroke, reading from the file if required.
*
- * NB: Assumes result will then be the end of the line.
+ * Returns: cmd_do_keystroke: have a keystroke -- stroke != ks_null
+ * cmd_do_eof : eof in keystream -- stroke == knull_eof
+ * cmd_do_timed_out: timed_ keystream -- stroke == knull_timed_out
+ * cmd_do_nothing : nothing available -- stroke == knull_not_eof
*
- * Returns number of characters inserted -- ie length of string
+ * Note that will return cmd_do_eof or cmd_do_timed_out any number of times.
+ *
+ * Note that any I/O error is signalled to the command loop, and is passed
+ * out of here as "nothing available". If the command loop is running, it
+ * will see the CMD_IO_ERROR signal and deal with the error.
*/
-static int
-uty_cli_word_overwrite (vty_io vio, char *str)
+static cmd_do_t
+uty_cli_get_keystroke(vty_cli cli, keystroke stroke)
{
- int n ;
- VTY_ASSERT_LOCKED() ;
+ qassert(cli->vf->vin_state == vf_open) ;
- n = uty_cli_overwrite(vio, str, strlen(str)) ;
+ while (1)
+ {
+ if (keystroke_get(cli->key_stream, stroke))
+ {
+ if (stroke->flags != 0)
+ {
+ /* TODO: deal with broken keystrokes */
+ } ;
- vio->cl.len = vio->cl.cp ;
+ if (stroke->type != ks_iac)
+ return cmd_do_keystroke ; /* have a keystroke */
- return n ;
-}
+ /* Deal with telnet command, so invisible to upper level */
+ uty_telnet_command(cli->vf, stroke, false) ;
+ }
+ else
+ {
+ int get ;
+
+ qassert(stroke->type == ks_null) ;
+
+ switch (stroke->value)
+ {
+ case knull_not_eof:
+ break ;
+
+ case knull_eof:
+ return cmd_do_eof ;
+
+ case knull_timed_out:
+ return cmd_do_timed_out ;
+
+ default:
+ zabort("unknown knull_xxx") ;
+ break ;
+ } ;
+
+ get = uty_term_read(cli->vf) ; /* sets eof in key_stream
+ if hit eof or error */
+ if ((get == 0) || (get == -1))
+ return cmd_do_nothing ;
+ } ;
+ } ;
+} ;
+
+/*==============================================================================
+ * Command line operations.
+ *
+ * These all affect the given command line, only. The effect on the screen
+ * is taken care of elsewhere -- see uty_cli_update_line().
+ */
+
+/*------------------------------------------------------------------------------
+ * Insert 'n' characters at current position in the command line, leaving
+ * cursor after the inserted characters.
+ */
+static void
+uty_cli_insert(qstring cl, const char* chars, int n)
+{
+ qassert((qs_cp_nn(cl) <= qs_len_nn(cl)) && (n >= 0)) ;
+
+ if (n > 0)
+ {
+ qs_insert(cl, chars, n) ;
+ qs_move_cp_nn(cl, n) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Replace 'm' characters at the current position, by 'n' characters and leave
+ * cursor at the end of the inserted characters.
+ */
+static void
+uty_cli_replace(qstring cl, int m, const char* chars, int n)
+{
+ qassert((qs_cp_nn(cl) <= qs_len_nn(cl)) && (n >= 0) && (m >= 0)) ;
+
+ qs_replace(cl, m, chars, n) ;
+
+ qs_move_cp_nn(cl, n) ;
+} ;
/*------------------------------------------------------------------------------
* Forward 'n' characters -- stop at end of line.
@@ -1679,25 +2090,20 @@ uty_cli_word_overwrite (vty_io vio, char *str)
* Returns number of characters actually moved
*/
static int
-uty_cli_forwards(vty_io vio, int n)
+uty_cli_forwards(qstring cl, int n)
{
int have ;
- VTY_ASSERT_LOCKED() ;
- have = vio->cl.len - vio->cl.cp ;
+ have = qs_after_cp_nn(cl) ;
if (have < n)
n = have ;
- assert(n >= 0) ;
+ qassert(n >= 0) ;
- if (n > 0)
- {
- uty_cli_echo(vio, qs_cp_char(&vio->cl), n) ;
- vio->cl.cp += n ;
- } ;
+ qs_move_cp_nn(cl, n) ;
return n ;
-}
+} ;
/*------------------------------------------------------------------------------
* Backwards 'n' characters -- stop at start of line.
@@ -1705,88 +2111,74 @@ uty_cli_forwards(vty_io vio, int n)
* Returns number of characters actually moved
*/
static int
-uty_cli_backwards(vty_io vio, int n)
+uty_cli_backwards(qstring cl, int n)
{
- VTY_ASSERT_LOCKED() ;
+ if ((int)qs_cp_nn(cl) < n)
+ n = qs_cp_nn(cl) ;
- if ((int)vio->cl.cp < n)
- n = vio->cl.cp ;
+ qassert(n >= 0) ;
- assert(n >= 0) ;
-
- if (n > 0)
- {
- uty_cli_echo_n(vio, telnet_backspaces, n) ;
- vio->cl.cp -= n ;
- } ;
+ qs_move_cp_nn(cl, -n) ;
return n ;
-}
+} ;
+
+/*------------------------------------------------------------------------------
+ * Move forwards (if n > 0) or backwards (if n < 0) -- stop at start or end of
+ * line.
+ */
+static void
+uty_cli_move(qstring cl, int n)
+{
+ if (n < 0)
+ uty_cli_backwards(cl, -n) ;
+ else if (n > 0)
+ uty_cli_forwards(cl, +n) ;
+} ;
/*------------------------------------------------------------------------------
* Delete 'n' characters -- forwards -- stop at end of line.
- *
- * Returns number of characters actually deleted.
*/
-static int
-uty_cli_del_forwards(vty_io vio, int n)
+static void
+uty_cli_del_forwards(qstring cl, int n)
{
- int after ;
int have ;
- VTY_ASSERT_LOCKED() ;
-
- have = vio->cl.len - vio->cl.cp ;
+ have = qs_after_cp_nn(cl) ;
if (have < n)
n = have ; /* cannot delete more than have */
- assert(n >= 0) ;
-
- if (n <= 0)
- return 0 ;
+ qassert(n >= 0) ;
- after = qs_delete(&vio->cl, n) ;
-
- if (after > 0)
- uty_cli_echo(vio, qs_cp_char(&vio->cl), after) ;
-
- uty_cli_echo_n(vio, telnet_spaces, n) ;
- uty_cli_echo_n(vio, telnet_backspaces, after + n) ;
-
- return n ;
+ if (n > 0)
+ qs_delete(cl, n) ;
}
/*------------------------------------------------------------------------------
- * Delete 'n' characters before the point.
- *
- * Returns number of characters actually deleted.
+ * Delete 'n' characters before the point -- stopping at start of line.
*/
-static int
-uty_cli_del_backwards(vty_io vio, int n)
+static void
+uty_cli_del_backwards(qstring cl, int n)
{
- return uty_cli_del_forwards(vio, uty_cli_backwards(vio, n)) ;
+ uty_cli_del_forwards(cl, uty_cli_backwards(cl, n)) ;
}
/*------------------------------------------------------------------------------
* Move to the beginning of the line.
- *
- * Returns number of characters moved over.
*/
-static int
-uty_cli_bol (vty_io vio)
+static void
+uty_cli_bol(qstring cl)
{
- return uty_cli_backwards(vio, vio->cl.cp) ;
+ qs_set_cp_nn(cl, 0) ;
} ;
/*------------------------------------------------------------------------------
* Move to the end of the line.
- *
- * Returns number of characters moved over
*/
-static int
-uty_cli_eol (vty_io vio)
+static void
+uty_cli_eol(qstring cl)
{
- return uty_cli_forwards(vio, vio->cl.len - vio->cl.cp) ;
+ qs_set_cp_nn(cl, qs_len_nn(cl)) ;
} ;
/*------------------------------------------------------------------------------
@@ -1797,18 +2189,16 @@ uty_cli_eol (vty_io vio)
* Steps over non-space characters and then any spaces.
*/
static int
-uty_cli_word_forwards_delta(vty_io vio)
+uty_cli_word_forwards_delta(qstring cl)
{
char* cp ;
char* tp ;
char* ep ;
- VTY_ASSERT_LOCKED() ; ;
+ cp = qs_cp_char(cl) ;
+ ep = qs_ep_char(cl) ;
- assert(vio->cl.cp <= vio->cl.len) ;
-
- cp = qs_cp_char(&vio->cl) ;
- ep = qs_ep_char(&vio->cl) ;
+ assert(cp <= ep) ;
tp = cp ;
@@ -1826,10 +2216,10 @@ uty_cli_word_forwards_delta(vty_io vio)
*
* Moves past any non-spaces, then past any spaces.
*/
-static int
-uty_cli_word_forwards(vty_io vio)
+static void
+uty_cli_word_forwards(qstring cl)
{
- return uty_cli_forwards(vio, uty_cli_word_forwards_delta(vio)) ;
+ uty_cli_forwards(cl, uty_cli_word_forwards_delta(cl)) ;
} ;
/*------------------------------------------------------------------------------
@@ -1837,28 +2227,25 @@ uty_cli_word_forwards(vty_io vio)
*
* Return number of characters to step over to reach next word.
*
- * If "eat_spaces", starts by stepping over spaces.
- * Steps back until next (backwards) character is space, or hits start of line.
+ * Steps back over spaces, and then until next (backwards) character is space,
+ * or hits start of line.
*/
static int
-uty_cli_word_backwards_delta(vty_io vio, int eat_spaces)
+uty_cli_word_backwards_delta(qstring cl)
{
char* cp ;
char* tp ;
char* sp ;
- VTY_ASSERT_LOCKED() ; ;
-
- assert(vio->cl.cp <= vio->cl.len) ;
+ assert(qs_cp_nn(cl) <= qs_len_nn(cl)) ;
- cp = qs_cp_char(&vio->cl) ;
- sp = qs_chars(&vio->cl) ;
+ cp = qs_cp_char(cl) ;
+ sp = qs_char(cl) ;
tp = cp ;
- if (eat_spaces)
- while ((tp > sp) && (*(tp - 1) == ' '))
- --tp ;
+ while ((tp > sp) && (*(tp - 1) == ' '))
+ --tp ;
while ((tp > sp) && (*(tp - 1) != ' '))
--tp ;
@@ -1867,143 +2254,170 @@ uty_cli_word_backwards_delta(vty_io vio, int eat_spaces)
} ;
/*------------------------------------------------------------------------------
- * Backward word, but not trailing spaces.
- *
- * Move back until next (backwards) character is space or start of line.
- *
- * Returns number of characters stepped over.
- */
-static int
-uty_cli_word_backwards_pure (vty_io vio)
-{
- return uty_cli_backwards(vio, uty_cli_word_backwards_delta(vio, 0)) ;
-} ;
-
-/*------------------------------------------------------------------------------
* Backward word -- move to start of previous word.
*
* Moves past any spaces, then move back until next (backwards) character is
* space or start of line.
- *
- * Returns number of characters stepped over.
*/
-static int
-uty_cli_word_backwards (vty_io vio)
+static void
+uty_cli_word_backwards(qstring cl)
{
- return uty_cli_backwards(vio, uty_cli_word_backwards_delta(vio, 1)) ;
+ uty_cli_backwards(cl, uty_cli_word_backwards_delta(cl)) ;
} ;
/*------------------------------------------------------------------------------
* Delete to end of word -- forwards.
*
* Deletes any leading spaces, then deletes upto next space or end of line.
- *
- * Returns number of characters deleted.
*/
-static int
-uty_cli_del_word_forwards(vty_io vio)
+static void
+uty_cli_del_word_forwards(qstring cl)
{
- return uty_cli_del_forwards(vio, uty_cli_word_forwards_delta(vio)) ;
+ uty_cli_del_forwards(cl, uty_cli_word_forwards_delta(cl)) ;
}
/*------------------------------------------------------------------------------
* Delete to start of word -- backwards.
*
* Deletes any trailing spaces, then deletes upto next space or start of line.
- *
- * Returns number of characters deleted.
*/
-static int
-uty_cli_del_word_backwards(vty_io vio)
+static void
+uty_cli_del_word_backwards(qstring cl)
{
- return uty_cli_del_backwards(vio, uty_cli_word_backwards_delta(vio, 1)) ;
+ uty_cli_del_backwards(cl, uty_cli_word_backwards_delta(cl)) ;
} ;
/*------------------------------------------------------------------------------
* Kill rest of line from current point.
- *
- * Returns number of characters deleted.
*/
-static int
-uty_cli_del_to_eol (vty_io vio)
+static void
+uty_cli_del_to_eol(qstring cl)
{
- return uty_cli_del_forwards(vio, vio->cl.len - vio->cl.cp) ;
+ qs_set_len_nn(cl, qs_cp_nn(cl)) ;
} ;
/*------------------------------------------------------------------------------
* Kill line from the beginning.
- *
- * Returns number of characters deleted.
*/
-static int
-uty_cli_clear_line(vty_io vio)
+static void
+uty_cli_clear_line(qstring cl)
{
- uty_cli_bol(vio) ;
- return uty_cli_del_to_eol(vio) ;
+ qs_set_cp_nn(cl, 0) ;
+ qs_set_len_nn(cl, 0) ;
} ;
/*------------------------------------------------------------------------------
- * Transpose chars before or at the point.
+ * Transpose current character with previous one, and step forward one.
*
- * Return number of characters affected.
+ * If at end of line, transpose the last two characters.
*/
-static int
-uty_cli_transpose_chars(vty_io vio)
+static void
+uty_cli_transpose_chars(qstring cl)
{
- char chars[2] ;
+ char ch ;
char* cp ;
- VTY_ASSERT_LOCKED() ;
-
/* Give up if < 2 characters or at start of line. */
- if ((vio->cl.len < 2) || (vio->cl.cp < 1))
- return 0 ;
+ if ((qs_len_nn(cl) < 2) || (qs_cp_nn(cl) < 1))
+ return ;
- /* Move back to first of characters to exchange */
- if (vio->cl.cp == vio->cl.len)
- uty_cli_backwards(vio, 2) ;
- else
- uty_cli_backwards(vio, 1) ;
+ /* If we are not at the end, step past the second character */
+ if (qs_after_cp_nn(cl) == 0)
+ qs_move_cp_nn(cl, 1) ;
- /* Pick up in the new order */
- cp = qs_cp_char(&vio->cl) ;
- chars[1] = *cp++ ;
- chars[0] = *cp ;
+ /* Get address of first character */
+ cp = qs_cp_char(cl) - 2 ;
- /* And overwrite */
- return uty_cli_overwrite(vio, chars, 2) ;
+ /* swap characters */
+ ch = *(cp + 1) ;
+ *(cp + 1) = *cp ;
+ *cp = ch ;
} ;
/*==============================================================================
* Command line history handling
+ *
+ * cli->hist is vector of qstrings (created on demand)
+ * cli->h_now is index of the present time
+ * cli->hp is index of most recent line read back
+ *
+ * cli->hist is initialised empty, with h_now == hp == 0.
+ *
+ * On first use it is set to VTY_MAX_HIST entries, and its size never changes.
+ * Before VTY_MAX_HIST lines have been inserted, a NULL entry signals the end
+ * of history (to date).
+ *
+ * h_now is incremented after a line is inserted (and wraps round). So
+ * stepping +1 moves towards the present (down) and -1 moves towards the past
+ * (up).
+ *
+ * hp == h_now means we are in the present.
+ *
+ * Cannot step forwards from hp == h_now (into the future, which is the
+ * same as the oldest thing we can remember !).
+ *
+ * Before stepping backwards from hp == hp_now, sets hp_now to be a copy of
+ * the current line (complete with cp), so can return to the present.
+ *
+ * Cannot step backwards to hp == hp_now -- that would be to wrap round from
+ * the ancient past to the now time.
+ *
+ * When storing a line in the history, replaces the last line stored if that
+ * is the same as the new line, apart from whitespace.
+ */
+
+static inline uint
+hp_next(uint hp)
+{
+ return (hp != (VTY_HIST_COUNT - 1)) ? hp + 1 : 0 ;
+} ;
+
+static inline uint
+hp_prev(uint hp)
+{
+ return (hp != 0) ? hp - 1 : VTY_HIST_COUNT - 1 ;
+} ;
+
+static inline uint
+hp_step(uint hp, enum hist_step step)
+{
+ if (step == hist_previous)
+ return hp_prev(hp) ;
+
+ if (step == hist_next)
+ return hp_next(hp) ;
+
+ zabort("invalid hist_step") ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Create cli->hist.
*/
+static void
+uty_cli_hist_make(vty_cli cli)
+{
+ cli->hist = vector_init_new(cli->hist, VTY_HIST_COUNT) ;
+ vector_set_min_length(cli->hist, VTY_HIST_COUNT) ;
+} ;
/*------------------------------------------------------------------------------
* Add given command line to the history buffer.
*
- * This is inserting the vty->buf line into the history.
+ * The 'cp' stored with the line is set to be the end of the line, so that is
+ * all ready for when the stored line is used.
+ *
+ * Resets hp == h_now.
*/
-extern void
-uty_cli_hist_add (vty_io vio, const char* cmd_line)
+static void
+uty_cli_hist_add (vty_cli cli, qstring clx)
{
- qstring prev_line ;
- qstring_t line ;
- int prev_index ;
+ qstring hist_line ;
+ int h_prev ;
VTY_ASSERT_LOCKED() ;
- /* Construct a dummy qstring for the given command line */
- qs_dummy(&line, cmd_line, 1) ; /* set cursor to the end */
-
- /* make sure have a suitable history vector */
- vector_set_min_length(&vio->hist, VTY_MAXHIST) ;
-
- /* find the previous command line in the history */
- prev_index = vio->hindex - 1 ;
- if (prev_index < 0)
- prev_index = VTY_MAXHIST - 1 ;
-
- prev_line = vector_get_item(&vio->hist, prev_index) ;
+ if (cli->hist == NULL)
+ uty_cli_hist_make(cli) ; /* create if required */
/* If the previous line is NULL, that means the history is empty.
*
@@ -2011,23 +2425,35 @@ uty_cli_hist_add (vty_io vio, const char* cmd_line)
* replace it with the current line -- so that the latest whitespace
* version is saved.
*
- * Either way, replace the the previous line entry by moving hindex
- * back !
+ * In both those cases, replace the the previous line entry by moving
+ * h_now back to it -- leaving hist_line pointing at it.
+ *
+ * Otherwise, leave cli->h_now and point hist_line at the most ancient
+ * line in history.
*/
- if ((prev_line == NULL) || (qs_cmp_sig(prev_line, &line) == 0))
- vio->hindex = prev_index ;
- else
- prev_line = vector_get_item(&vio->hist, vio->hindex) ;
+ h_prev = hp_prev(cli->h_now) ;
- /* Now replace the hindex entry */
- vector_set_item(&vio->hist, vio->hindex, qs_copy(prev_line, &line)) ;
+ hist_line = vector_get_item(cli->hist, h_prev) ;
+
+ if ((hist_line == NULL) || (qs_cmp_sig(hist_line, clx) == 0))
+ {
+ cli->h_now = h_prev ;
+ cli->h_repeat = true ; /* latest history is a repeat */
+ }
+ else
+ {
+ hist_line = vector_get_item(cli->hist, cli->h_now) ;
+ cli->h_repeat = false ; /* latest history is novel */
+ } ;
- /* Advance to the near future and reset the history pointer */
- vio->hindex++;
- if (vio->hindex == VTY_MAXHIST)
- vio->hindex = 0;
+ /* Now replace the h_now entry -- setting 'cp' to end of line
+ */
+ hist_line = qs_copy(hist_line, clx) ;
+ qs_set_cp_nn(hist_line, qs_len_nn(hist_line)) ;
+ vector_set_item(cli->hist, cli->h_now, hist_line) ;
- vio->hp = vio->hindex;
+ /* Advance history */
+ cli->hp = cli->h_now = hp_next(cli->h_now) ;
} ;
/*------------------------------------------------------------------------------
@@ -2035,236 +2461,344 @@ uty_cli_hist_add (vty_io vio, const char* cmd_line)
*
* This function is called from vty_next_line and vty_previous_line.
*
- * Step +1 is towards the present
- * -1 is into the past
+ * Step -1 is into the past (up)
+ * +1 is towards the present (down)
+ *
+ * NB: assumes line will be updated by uty_cli_update_line()
*/
static void
-uty_cli_history_use(vty_io vio, int step)
+uty_cli_hist_use(vty_cli cli, enum hist_step step)
{
- int index ;
- unsigned old_len ;
- unsigned after ;
- unsigned back ;
- qstring hist ;
-
- VTY_ASSERT_LOCKED() ;
+ uint hp ;
+ qstring hist_line ;
assert((step == +1) || (step == -1)) ;
- index = vio->hp ;
+ if (cli->hist == NULL)
+ uty_cli_hist_make(cli) ; /* create if required */
- /* Special case of being at the insertion point */
- if (index == vio->hindex)
+ hp = cli->hp ;
+
+ /* Special case of being at the insertion point (the present)
+ *
+ * Cannot step forwards from the present.
+ *
+ * Before stepping back from the present, take a copy of the current
+ * command line -- so can get back to it.
+ *
+ * Note that the 'cp' is stored with the line. So if return to the present,
+ * the cursor returns to its current position. (When lines are added to
+ * the history, the cursor is set to the end of the line.)
+ */
+ if (hp == cli->h_now)
{
if (step > 0)
return ; /* already in the present */
- /* before stepping back from the present, take a copy of the
- * current command line -- so can get back to it.
- */
- hist = vector_get_item(&vio->hist, vio->hindex) ;
- vector_set_item(&vio->hist, vio->hindex, qs_copy(hist, &vio->cl)) ;
+ hist_line = vector_get_item(cli->hist, cli->h_now) ;
+ vector_set_item(cli->hist, cli->h_now, qs_copy(hist_line, cli->cl)) ;
} ;
- /* Advance or retreat */
- index += step ;
- if (index < 0)
- index = VTY_MAXHIST - 1 ;
- else if (index >= VTY_MAXHIST)
- index = 0 ;
+ /* Advance or retreat and get history line. */
+ hp = hp_step(hp, step) ;
- hist = vector_get_item(&vio->hist, index) ;
+ hist_line = vector_get_item(cli->hist, hp) ;
- /* If moving backwards in time, may not move back to the insertion
+ /* If moving backwards in time, may not move back to the h_now
* point (that would be wrapping round to the present) and may not
* move back to a NULL entry (that would be going back before '.').
*/
if (step < 0)
- if ((hist == NULL) || (index == vio->hindex))
+ if ((hist_line == NULL) || (hp == cli->h_now))
return ;
- /* Now, if arrived at the insertion point, this is returning to the
- * present, which is fine.
- */
- vio->hp = index;
+ /* Update the history pointer and copy history line to current line. */
+ cli->hp = hp ;
+ qs_copy(cli->cl, hist_line) ;
+} ;
- /* Move back to the start of the current line */
- uty_cli_bol(vio) ;
+/*------------------------------------------------------------------------------
+ * Show the contents of the history
+ */
+extern void
+uty_cli_hist_show(vty_cli cli)
+{
+ uint hp ;
+ uint h_end ;
- /* Get previous line from history buffer and echo that */
- old_len = vio->cl.len ;
- qs_copy(&vio->cl, hist) ;
+ VTY_ASSERT_LOCKED() ;
- /* Sort out wiping out any excess and setting the cursor position */
- if (old_len > vio->cl.len)
- after = old_len - vio->cl.len ;
- else
- after = 0 ;
+ if (cli->hist == NULL)
+ return ; /* if no history */
- back = after ;
- if (vio->cl.len > vio->cl.cp)
- back += (vio->cl.len - vio->cl.cp) ;
+ /* We start with the oldest thing we can remember, which means that
+ * we start by stepping "forwards" from "now".
+ *
+ * Until the history buffer fills, there will be a number of NULL entries
+ * between "now" and the oldest thing in the history.
+ *
+ * We do not show the "now" entry, which is not part of history.
+ *
+ * We do not show the entry before "now", because that is the current
+ * executing command, unless that was a repeat of the command before !
+ */
+ hp = cli->h_now ;
- if (vio->cl.len > 0)
- uty_cli_echo(vio, vio->cl.body, vio->cl.len) ;
+ h_end = cli->h_repeat ? hp : hp_prev(hp) ;
- if (after > 0)
- uty_cli_echo_n(vio, telnet_spaces, after) ;
+ while (1)
+ {
+ qstring line ;
- if (back > 0)
- uty_cli_echo_n(vio, telnet_backspaces, back) ;
+ hp = hp_next(hp) ;
- return ;
-} ;
+ if (hp == h_end)
+ break ; /* reached end of history */
-/*------------------------------------------------------------------------------
- * Use next history line, if any.
- */
-static void
-uty_cli_next_line(vty_io vio)
-{
- uty_cli_history_use(vio, +1) ;
-}
+ line = vector_get_item(cli->hist, hp) ;
-/*------------------------------------------------------------------------------
- * Use previous history line, if any.
- */
-static void
-uty_cli_previous_line (vty_io vio)
-{
- uty_cli_history_use(vio, -1) ;
-}
+ if (line != NULL)
+ uty_out(cli->vf->vio, " %s\n", qs_make_string(line));
+ } ;
+} ;
/*==============================================================================
* Command Completion and Command Description
*
+ * Any changes to the command line are made to cli->cl. If the command line
+ * is redrawn, updates cli->cls. Otherwise, the screen may need updating to
+ * reflect differences between cli->cl and cli->cls.
*/
-static void uty_cli_describe_show(vty_io vio, vector describe) ;
-static void uty_cli_describe_fold (vty_io vio, int cmd_width,
- unsigned int desc_width, struct desc *desc) ;
-static void uty_cli_describe_line(vty_io vio, int cmd_width, const char* cmd,
- const char* str) ;
+static uint uty_cli_help_parse(vty_cli cli) ;
+
+static void uty_cli_complete_keyword(vty_cli cli, elstring keyword) ;
+static void uty_cli_complete_list(vty_cli cli, vector item_v) ;
-static vector uty_cli_cmd_prepare(vty_io vio, int help) ;
+static void uty_cli_describe_list(vty_cli cli, vector item_v) ;
+static void uty_cli_describe_line(vty_cli cli, uint str_width, const char* str,
+ ulen str_len, const char* doc, ulen doc_len) ;
+static uint uty_cli_width_to_use(vty_cli cli) ;
+
+static void uty_cli_help_message(vty_cli cli, const char* msg) ;
+static void uty_cli_help_newline(vty_cli cli) ;
+static void uty_cli_help_finish(vty_cli cli) ;
/*------------------------------------------------------------------------------
* Command completion
+ *
+ * Requires that cmd_token_position() has been called to tokenise the line and
+ * establish which token the cursor is in. Must NOT call this if the cursor
+ * is in a "special" place.
+ *
+ * This is called from inside "uty_cli_process()".
*/
static void
-uty_cli_complete_command (vty_io vio, enum node_type node)
+uty_cli_complete_command (vty_cli cli)
{
- unsigned i ;
- int ret ;
- int len ;
- int n ;
- vector matched ;
- vector vline ;
+ uint n_items ;
+ cmd_parsed parsed ;
+ cmd_item item ;
VTY_ASSERT_LOCKED() ;
- /* Try and match the tokenised command line */
- vline = uty_cli_cmd_prepare(vio, 1) ;
- matched = cmd_complete_command (vline, node, &ret);
- cmd_free_strvec (vline);
+ parsed = cli->parsed ;
- /* Show the result. */
- switch (ret)
+ /* Establish what items may be present at the current token position. */
+ n_items = uty_cli_help_parse(cli) ;
+
+ if (n_items > 1) /* render list of alternatives */
+ uty_cli_complete_list(cli, parsed->item_v) ;
+ else if (n_items == 1)
{
- case CMD_ERR_AMBIGUOUS:
- uty_cli_out_newline(vio) ; /* clears cli_drawn */
- uty_cli_out_CMD_ERR_AMBIGUOUS(vio) ;
- break ;
+ /* One possible item -- one or more possible commands */
+ item = vector_get_item(parsed->item_v, 0) ;
- case CMD_ERR_NO_MATCH:
- uty_cli_out_newline(vio) ; /* clears cli_drawn */
- uty_cli_out_CMD_ERR_NO_MATCH(vio) ;
- break ;
+ switch (item->type)
+ {
+ case item_null:
+ zabort("invalid item_null") ;
- case CMD_COMPLETE_FULL_MATCH:
- uty_cli_eol (vio) ;
- uty_cli_word_backwards_pure (vio);
- uty_cli_word_overwrite (vio, vector_get_item(matched, 0));
- uty_cli_insert(vio, " ", 1);
- break ;
+ case item_eol:
- case CMD_COMPLETE_MATCH:
- uty_cli_eol (vio) ;
- uty_cli_word_backwards_pure (vio);
- uty_cli_word_overwrite (vio, vector_get_item(matched, 0));
- break ;
+ case item_option_word:
- case CMD_COMPLETE_LIST_MATCH:
- len = 6 ;
- for (i = 0; i < vector_end(matched); i++)
- {
- int sl = strlen((char*)vector_get_item(matched, i)) ;
- if (len < sl)
- len = sl ;
- } ;
+ case item_vararg:
- n = vio->width ;
- if (n == 0)
- n = 60 ;
- n = n / (len + 2) ;
- if (n == 0)
- n = 1 ;
+ case item_word:
- for (i = 0; i < vector_end(matched); i++)
- {
- if ((i % n) == 0)
- uty_cli_out_newline(vio) ; /* clears cli_drawn */
- uty_cli_out(vio, "%-*s ", len, (char*)vector_get_item(matched, i));
- }
- uty_cli_out_newline(vio) ;
+ case item_ipv6_prefix:
+ case item_ipv6_address:
+ case item_ipv4_prefix:
+ case item_ipv4_address:
- break;
+ case item_range:
+ uty_cli_describe_list(cli, parsed->item_v) ;
+ break ;
- case CMD_COMPLETE_ALREADY:
- default:
- break;
+ case item_keyword:
+ uty_cli_complete_keyword(cli, item->str) ;
+ break ;
+
+ default:
+ zabort("unknown item type") ;
+ } ;
} ;
- cmd_free_strvec(matched);
+ /* If necessary, redraw the command line */
+ uty_cli_help_finish(cli) ;
} ;
/*------------------------------------------------------------------------------
* Command Description
*/
static void
-uty_cli_describe_command (vty_io vio, enum node_type node)
+uty_cli_describe_command (vty_cli cli)
{
- int ret;
- vector vline;
- vector describe;
+ uint n_items ;
VTY_ASSERT_LOCKED() ;
- /* Try and match the tokenised command line */
- vline = uty_cli_cmd_prepare(vio, 1) ;
- describe = cmd_describe_command (vline, node, &ret);
- cmd_free_strvec (vline);
+ /* Establish what items may be present at the current token position. */
+ n_items = uty_cli_help_parse(cli) ;
+
+ if (n_items > 0) /* render list of possibilities */
+ uty_cli_describe_list(cli, cli->parsed->item_v) ;
+
+ /* If necessary, redraw the command line */
+ uty_cli_help_finish(cli) ;
+} ;
- uty_cli_out_newline(vio); /* clears cli_drawn */
+/*------------------------------------------------------------------------------
+ * Parse for command completion and command description.
+ *
+ * Requires that cmd_token_position() has been called to tokenise the line and
+ * establish which token the cursor is in. Must NOT call this if the cursor
+ * is in a "special" place.
+ *
+ * Deal with all cases which yield no items at all.
+ *
+ * Returns: number of items to consider.
+ */
+static uint
+uty_cli_help_parse(vty_cli cli)
+{
+ const char* msg ;
+ cmd_return_code_t ret ;
+ uint n_items ;
- /* Deal with result. */
- switch (ret)
+ /* The preflight checks avoid getting into trouble doing command completion
+ * on a line with comment
+ */
+ msg = cmd_help_preflight(cli->parsed) ;
+ if (msg != NULL)
{
- case CMD_ERR_AMBIGUOUS:
- uty_cli_out_CMD_ERR_AMBIGUOUS(vio) ;
- break ;
+ uty_cli_help_message(cli, msg) ;
+ return 0 ;
+ } ;
- case CMD_ERR_NO_MATCH:
- uty_cli_out_CMD_ERR_NO_MATCH(vio) ;
- break ;
+ /* Now see what the cmd_completion can come up with. */
+ ret = cmd_completion(cli->parsed, cli->context) ;
- default:
- uty_cli_describe_show(vio, describe) ;
- break ;
+ if (ret == CMD_ERR_PARSING)
+ {
+ if (cli->parsed->eloc >= 0)
+ {
+ uint eloc = cli->prompt_len + cli->parsed->eloc ;
+
+ uty_cli_help_newline(cli) ; /* clears cli_drawn etc. */
+ uty_cli_write_n(cli, telnet_dots, eloc) ;
+ uty_cli_write_s(cli, "^") ;
+ } ;
+
+ uty_cli_help_message(cli, qs_make_string(cli->parsed->emess)) ;
+
+ return 0 ;
} ;
- if (describe != NULL)
- vector_free (describe);
-}
+ /* Will now have 0, 1 or more items which match at the current
+ * cursor token.
+ */
+ n_items = vector_length(cli->parsed->item_v) ;
+
+ if (n_items == 0)
+ uty_cli_help_message(cli, "command not recognised") ;
+
+ return n_items ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Can complete a keyword.
+ */
+static void
+uty_cli_complete_keyword(vty_cli cli, elstring keyword)
+{
+ int pre, rep, ins, mov ;
+
+ cmd_complete_keyword(cli->parsed, &pre, &rep, &ins, &mov) ;
+
+ uty_cli_move(cli->cl, pre) ; /* move to start of token */
+ uty_cli_replace(cli->cl, rep, els_body_nn(keyword), els_len_nn(keyword)) ;
+
+ assert(ins <= 2) ;
+ if (ins > 0)
+ uty_cli_insert(cli->cl, " ", ins) ;
+
+ uty_cli_move(cli->cl, mov) ;
+
+ return ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Show the command completions -- usually more than one.
+ *
+ * Generates a table of possible items, with fixed width entries, depending
+ * on the longest option.
+ *
+ * NB: the items will have been sorted before we get here. Inter alia, that
+ * ensures that any <cr> is shown last.
+ */
+static void
+uty_cli_complete_list(vty_cli cli, vector item_v)
+{
+ uint i, str_width, n ;
+
+ str_width = 6 ;
+ for (i = 0 ; i < vector_length(item_v) ; ++i)
+ {
+ cmd_item item ;
+
+ item = vector_get_item(item_v, i) ;
+
+ if (str_width < els_len_nn(item->str))
+ str_width = els_len_nn(item->str) ;
+ } ;
+
+ n = uty_cli_width_to_use(cli) / (str_width + 2) ;
+
+ if (n == 0)
+ n = 1 ;
+
+ for (i = 0 ; i < vector_length(item_v) ; ++i)
+ {
+ cmd_item item ;
+ ulen str_len ;
+ ulen pad ;
+
+ item = vector_get_item(item_v, i) ;
+
+ if ((i % n) == 0)
+ uty_cli_out_newline(cli) ; /* clears cli_drawn */
+
+ str_len = els_len_nn(item->str) ;
+ uty_cli_write(cli, els_body_nn(item->str), str_len) ;
+
+ pad = (str_len < str_width) ? str_width - str_len : 0 ;
+
+ uty_cli_write_n(cli, telnet_spaces, pad + 2) ;
+ } ;
+
+ uty_cli_help_newline(cli) ;
+} ;
/*------------------------------------------------------------------------------
* Show the command description.
@@ -2280,426 +2814,185 @@ uty_cli_describe_command (vty_io vio, enum node_type node)
* word description ..................................
* .............text
*
- * If one of the options is '<cr>', that is always shown last.
+ * NB: the items will have been sorted before we get here. Inter alia, that
+ * ensures that any <cr> is shown last.
*/
static void
-uty_cli_describe_show(vty_io vio, vector describe)
+uty_cli_describe_list(vty_cli cli, vector item_v)
{
- unsigned int i, cmd_width, desc_width;
- struct desc *desc, *desc_cr ;
+ uint i, str_width, doc_width, width ;
/* Get width of the longest "word" */
- cmd_width = 0;
- for (i = 0; i < vector_active (describe); i++)
- if ((desc = vector_slot (describe, i)) != NULL)
- {
- unsigned int len;
-
- if (desc->cmd[0] == '\0')
- continue;
-
- len = strlen (desc->cmd);
- if (desc->cmd[0] == '.')
- len--;
+ str_width = 0;
+ for (i = 0 ; i < vector_length(item_v) ; ++i)
+ {
+ cmd_item item ;
+ uint len ;
- if (cmd_width < len)
- cmd_width = len;
- }
+ item = vector_get_item(item_v, i) ;
- /* Set width of description string. */
- desc_width = vio->width - (cmd_width + 6);
+ len = els_len_nn(item->str) ;
+ if (*((char*)els_body_nn(item->str)) == '.')
+ len--;
- /* Print out description. */
- desc_cr = NULL ; /* put <cr> last if it appears */
+ if (len > str_width)
+ str_width = len ;
+ } ;
- for (i = 0; i < vector_active (describe); i++)
- if ((desc = vector_slot (describe, i)) != NULL)
- {
- if (desc->cmd[0] == '\0')
- continue;
+ /* Set width of description string.
+ *
+ * Format is:
+ *
+ * __wo.....rd__description...
+ * ...continues
+ *
+ * The width of the word part has been established above as the width of the
+ * widest word.
+ *
+ * There are two spaces on either side of the word, so we here calculate the
+ * width of the description part
+ */
+ width = uty_cli_width_to_use(cli) ;
- if (strcmp (desc->cmd, command_cr) == 0)
- {
- desc_cr = desc;
- continue;
- }
+ if (width > ((str_width + 6) + 20))
+ doc_width = width - (str_width + 6) ;
+ else
+ doc_width = 0 ;
- uty_cli_describe_fold (vio, cmd_width, desc_width, desc);
- }
+ /* Print out description. */
+ for (i = 0 ; i < vector_length(item_v) ; ++i)
+ {
+ cmd_item item ;
+ const char* str, * dp, * ep ;
+ ulen str_len ;
- if (desc_cr != NULL)
- uty_cli_describe_fold (vio, cmd_width, desc_width, desc_cr);
-} ;
+ item = vector_get_item(item_v, i) ;
-/*------------------------------------------------------------------------------
- * Show one word and the description, folding the description as required.
- */
-static void
-uty_cli_describe_fold (vty_io vio, int cmd_width,
- unsigned int desc_width, struct desc *desc)
-{
- char *buf;
- const char *cmd, *p;
- int pos;
+ str = els_body_nn(item->str) ;
+ str_len = els_len_nn(item->str) ;
+ if (*str == '.')
+ {
+ ++str ;
+ --str_len ;
+ } ;
- VTY_ASSERT_LOCKED() ;
+ dp = item->doc ;
+ ep = dp + strlen(dp) ;
- cmd = desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd;
- p = desc->str ;
+ /* If have a sensible description width */
+ if (doc_width > 20)
+ {
+ while ((ep - dp) > doc_width)
+ {
+ const char* np ;
- /* If have a sensible description width */
- if (desc_width > 20)
- {
- buf = XCALLOC (MTYPE_TMP, strlen (desc->str) + 1);
+ np = dp + doc_width ; /* target next position */
- while (strlen (p) > desc_width)
- {
- /* move back to first space */
- for (pos = desc_width; pos > 0; pos--)
- if (*(p + pos) == ' ')
- break;
+ while ((np > dp) && (*np != ' '))
+ --np ; /* seek back to ' ' */
- /* if did not find a space, break at width */
- if (pos == 0)
- pos = desc_width ;
+ if (np == dp) /* if no space... */
+ np = dp + doc_width ; /* ...force break */
- strncpy (buf, p, pos);
- buf[pos] = '\0';
- uty_cli_describe_line(vio, cmd_width, cmd, buf) ;
+ uty_cli_describe_line(cli, str_width, str, str_len, dp, np - dp) ;
- cmd = ""; /* for 2nd and subsequent lines */
+ str_len = 0 ; /* for 2nd and subsequent lines */
- p += pos ; /* step past what just wrote */
- while (*p == ' ')
- ++p ; /* skip spaces */
+ dp = np ; /* step past what just wrote */
+ while (*dp == ' ')
+ ++dp ; /* skip spaces */
+ } ;
} ;
- XFREE (MTYPE_TMP, buf);
+ uty_cli_describe_line(cli, str_width, str, str_len, dp, ep - dp) ;
} ;
- uty_cli_describe_line(vio, cmd_width, cmd, p) ;
+ uty_cli_help_newline(cli) ;
} ;
/*------------------------------------------------------------------------------
* Show one description line.
*/
static void
-uty_cli_describe_line(vty_io vio, int cmd_width, const char* cmd,
- const char* str)
+uty_cli_describe_line(vty_cli cli, uint str_width, const char* str,
+ ulen str_len, const char* doc, ulen doc_len)
{
- if (str != NULL)
- uty_cli_out (vio, " %-*s %s", cmd_width, cmd, str) ;
- else
- uty_cli_out (vio, " %-s", cmd) ;
- uty_cli_out_newline(vio) ;
-} ;
+ if ((str_len == 0) && (doc_len == 0))
+ return ; /* quit if nothing to say */
-/*------------------------------------------------------------------------------
- * Prepare "vline" token array for command handler.
- *
- * For "help" (command completion/description), if the command line is empty,
- * or ends in ' ', adds an empty token to the end of the token array.
- */
-static vector
-uty_cli_cmd_prepare(vty_io vio, int help)
-{
- vector vline ;
-
- vline = cmd_make_strvec(qs_term(&vio->cl)) ;
-
- /* Note that if there is a vector of tokens, then there is at least one
- * token, so can guarantee that vio->cl.len >= 1 !
- */
- if (help)
- if ((vline == NULL) || isspace(*qs_chars_at(&vio->cl, vio->cl.len - 1)))
- vline = cmd_add_to_strvec(vline, "") ;
-
- return vline ;
-} ;
+ uty_cli_help_newline(cli) ;
-/*==============================================================================
- * VTY telnet stuff
- */
-
-#define TELNET_OPTION_DEBUG 0 /* 0 to turn off */
-
-static const char* telnet_commands[256] =
-{
- [tn_IAC ] = "IAC",
- [tn_DONT ] = "DONT",
- [tn_DO ] = "DO",
- [tn_WONT ] = "WONT",
- [tn_WILL ] = "WILL",
- [tn_SB ] = "SB",
- [tn_GA ] = "GA",
- [tn_EL ] = "EL",
- [tn_EC ] = "EC",
- [tn_AYT ] = "AYT",
- [tn_AO ] = "AO",
- [tn_IP ] = "IP",
- [tn_BREAK] = "BREAK",
- [tn_DM ] = "DM",
- [tn_NOP ] = "NOP",
- [tn_SE ] = "SE",
- [tn_EOR ] = "EOR",
- [tn_ABORT] = "ABORT",
- [tn_SUSP ] = "SUSP",
- [tn_EOF ] = "EOF",
-} ;
-
-static const char* telnet_options[256] =
-{
- [to_BINARY] = "BINARY", /* 8-bit data path */
- [to_ECHO] = "ECHO", /* echo */
- [to_RCP] = "RCP", /* prepare to reconnect */
- [to_SGA] = "SGA", /* suppress go ahead */
- [to_NAMS] = "NAMS", /* approximate message size */
- [to_STATUS] = "STATUS", /* give status */
- [to_TM] = "TM", /* timing mark */
- [to_RCTE] = "RCTE", /* remote controlled tx and echo */
- [to_NAOL] = "NAOL", /* neg. about output line width */
- [to_NAOP] = "NAOP", /* neg. about output page size */
- [to_NAOCRD] = "NAOCRD", /* neg. about CR disposition */
- [to_NAOHTS] = "NAOHTS", /* neg. about horizontal tabstops */
- [to_NAOHTD] = "NAOHTD", /* neg. about horizontal tab disp. */
- [to_NAOFFD] = "NAOFFD", /* neg. about formfeed disposition */
- [to_NAOVTS] = "NAOVTS", /* neg. about vertical tab stops */
- [to_NAOVTD] = "NAOVTD", /* neg. about vertical tab disp. */
- [to_NAOLFD] = "NAOLFD", /* neg. about output LF disposition */
- [to_XASCII] = "XASCII", /* extended ascii character set */
- [to_LOGOUT] = "LOGOUT", /* force logout */
- [to_BM] = "BM", /* byte macro */
- [to_DET] = "DET", /* data entry terminal */
- [to_SUPDUP] = "SUPDUP", /* supdup protocol */
- [to_SUPDUPOUTPUT] = "SUPDUPOUTPUT",/* supdup output */
- [to_SNDLOC] = "SNDLOC", /* send location */
- [to_TTYPE] = "TTYPE", /* terminal type */
- [to_EOR] = "EOR", /* end or record */
- [to_TUID] = "TUID", /* TACACS user identification */
- [to_OUTMRK] = "OUTMRK", /* output marking */
- [to_TTYLOC] = "TTYLOC", /* terminal location number */
- [to_3270REGIME] = "3270REGIME", /* 3270 regime */
- [to_X3PAD] = "X3PAD", /* X.3 PAD */
- [to_NAWS] = "NAWS", /* window size */
- [to_TSPEED] = "TSPEED", /* terminal speed */
- [to_LFLOW] = "LFLOW", /* remote flow control */
- [to_LINEMODE] = "LINEMODE", /* Linemode option */
- [to_XDISPLOC] = "XDISPLOC", /* X Display Location */
- [to_OLD_ENVIRON] = "OLD_ENVIRON", /* Old - Environment variables */
- [to_AUTHENTICATION] = "AUTHENTICATION", /* Authenticate */
- [to_ENCRYPT] = "ENCRYPT", /* Encryption option */
- [to_NEW_ENVIRON] = "NEW_ENVIRON", /* New - Environment variables */
- [to_EXOPL] = "EXOPL", /* extended-options-list */
-} ;
-
-/*------------------------------------------------------------------------------
- * For debug. Put string or value as decimal.
- */
-static void
-uty_cli_out_dec(vty_io vio, const char* str, unsigned char u)
-{
- if (str != NULL)
- uty_cli_out(vio, "%s ", str) ;
+ if (str_len > 0)
+ {
+ uty_cli_write(cli, " ", 2) ;
+ uty_cli_write(cli, str, str_len) ;
+ }
else
- uty_cli_out(vio, "%d ", (int)u) ;
-} ;
+ str_width += 2 ;
-/*------------------------------------------------------------------------------
- * For debug. Put string or value as hex.
- */
-static void
-uty_cli_out_hex(vty_io vio, const char* str, unsigned char u)
-{
- if (str != NULL)
- uty_cli_out(vio, "%s ", str) ;
- else
- uty_cli_out(vio, "0x%02x ", (unsigned)u) ;
+ if (doc_len > 0)
+ {
+ ulen pad ;
+ pad = (str_len < str_width) ? str_width - str_len : 0 ;
+ uty_cli_write_n(cli, telnet_spaces, pad + 2) ;
+ uty_cli_write(cli, doc, doc_len) ;
+ } ;
} ;
/*------------------------------------------------------------------------------
- * Send telnet: "WILL TELOPT_ECHO"
- */
-static void
-uty_will_echo (vty_io vio)
-{
- unsigned char cmd[] = { tn_IAC, tn_WILL, to_ECHO };
- VTY_ASSERT_LOCKED() ;
- uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd));
-}
-
-/*------------------------------------------------------------------------------
- * Send telnet: "suppress Go-Ahead"
+ * Return the actual or assumed console width.
+ *
+ * If we know the width we use it. Otherwise just assume something reasonable.
*/
-static void
-uty_will_suppress_go_ahead (vty_io vio)
+static uint
+uty_cli_width_to_use(vty_cli cli)
{
- unsigned char cmd[] = { tn_IAC, tn_WILL, to_SGA };
- VTY_ASSERT_LOCKED() ;
- uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd));
-}
+ return (cli->width == 0) ? 60 : cli->width ;
+} ;
/*------------------------------------------------------------------------------
- * Send telnet: "don't use linemode"
+ * Move to new line, issue message and leave on new line.
+ *
+ * Deals with udating the command line if we are currently on it.
*/
static void
-uty_dont_linemode (vty_io vio)
+uty_cli_help_message(vty_cli cli, const char* msg)
{
- unsigned char cmd[] = { tn_IAC, tn_DONT, to_LINEMODE };
- VTY_ASSERT_LOCKED() ;
- uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd));
-}
+ uty_cli_help_newline(cli) ; /* clears cli->drawn etc. */
+ uty_cli_write_s(cli, "% ") ;
+ uty_cli_write_s(cli, msg) ;
+ uty_cli_write(cli, telnet_newline, 2) ;
+} ;
/*------------------------------------------------------------------------------
- * Send telnet: "Use window size"
+ * If the command line is drawn, make sure it is up to date, leaving cursor
+ * at the end of the line, and then issue newline.
+ *
+ * Clears cli->drawn.
*/
static void
-uty_do_window_size (vty_io vio)
+uty_cli_help_newline(vty_cli cli)
{
- unsigned char cmd[] = { tn_IAC, tn_DO, to_NAWS };
- VTY_ASSERT_LOCKED() ;
- uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd));
-}
+ if (cli->drawn)
+ uty_cli_update_line(cli, qs_len_nn(cli->cl)) ;
-/*------------------------------------------------------------------------------
- * Send telnet: "don't use lflow" -- not currently used
- */
-static void
-uty_dont_lflow_ahead (vty_io vio)
-{
- unsigned char cmd[] = { tn_IAC, tn_DONT, to_LFLOW };
- VTY_ASSERT_LOCKED() ;
- uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd));
-}
+ uty_cli_write(cli, telnet_newline, 2) ;
-/*------------------------------------------------------------------------------
- * The keystroke iac callback function.
- *
- * This deals with IAC sequences that should be dealt with as soon as they
- * are read -- not stored in the keystroke stream for later processing.
- */
-extern bool
-uty_cli_iac_callback(keystroke_iac_callback_args)
-{
- return uty_telnet_command((vty_io)context, stroke, true) ;
+ cli->drawn = false ;
} ;
/*------------------------------------------------------------------------------
- * Process incoming Telnet Option(s)
- *
- * May be called during keystroke iac callback, or when processing CLI
- * keystrokes.
- *
- * In particular: get telnet window size.
- *
- * Returns: true <=> dealt with, for:
+ * If the command line help has "undrawn" the command line, then redraw it now
+ * and make a new copy to cli->cls.
*
- * * telnet window size.
+ * Sets cli->drawn
*/
-static bool
-uty_telnet_command(vty_io vio, keystroke stroke, bool callback)
+static void
+uty_cli_help_finish(vty_cli cli)
{
- uint8_t* p ;
- uint8_t o ;
- int left ;
- bool dealt_with ;
-
- /* Echo to the other end if required */
- if (TELNET_OPTION_DEBUG)
+ if (!cli->drawn)
{
- uty_cli_wipe(vio, 0) ;
-
- p = stroke->buf ;
- left = stroke->len ;
-
- uty_cli_out_hex(vio, telnet_commands[tn_IAC], tn_IAC) ;
-
- if (left-- > 0)
- uty_cli_out_dec(vio, telnet_commands[*p], *p) ;
- ++p ;
-
- if (left-- > 0)
- uty_cli_out_dec(vio, telnet_options[*p], *p) ;
- ++p ;
-
- if (left > 0)
- {
- while(left-- > 0)
- uty_cli_out_hex(vio, NULL, *p++) ;
-
- if (stroke->flags & kf_truncated)
- uty_cli_out(vio, "... ") ;
-
- if (!(stroke->flags & kf_broken))
- {
- uty_cli_out_hex(vio, telnet_commands[tn_IAC], tn_IAC) ;
- uty_cli_out_hex(vio, telnet_commands[tn_SE], tn_SE) ;
- }
- } ;
-
- if (stroke->flags & kf_broken)
- uty_cli_out (vio, "BROKEN") ;
-
- uty_cli_out (vio, "\r\n") ;
+ uty_cli_draw(cli) ;
+ qs_copy(cli->cls, cli->cl) ;
} ;
-
- /* Process the telnet command */
- dealt_with = false ;
-
- if (stroke->flags != 0)
- return dealt_with ; /* go no further if broken */
-
- p = stroke->buf ;
- left = stroke->len ;
-
- passert(left >= 1) ; /* must be if not broken ! */
- passert(stroke->value == *p) ; /* or something is wrong */
-
- ++p ; /* step past X of IAC X */
- --left ;
-
- /* Decode the one command that is interesting -- "NAWS" */
- switch (stroke->value)
- {
- case tn_SB:
- passert(left > 0) ; /* or parser failed */
-
- o = *p++ ; /* the option byte */
- --left ;
- switch(o)
- {
- case to_NAWS:
- if (left != 4)
- {
- uzlog(NULL, LOG_WARNING,
- "RFC 1073 violation detected: telnet NAWS option "
- "should send %d characters, but we received %d",
- (3 + 4 + 2), (3 + left + 2)) ;
- }
- else
- {
- vio->width = *p++ << 8 ;
- vio->width += *p++ ;
- vio->height = *p++ << 8 ;
- vio->height += *p ;
-
- if (TELNET_OPTION_DEBUG)
- uty_cli_out(vio, "TELNET NAWS window size received: "
- "width %d, height %d%s",
- vio->width, vio->height, telnet_newline) ;
- uty_set_height(vio) ;
-
- dealt_with = true ;
- } ;
- break ;
-
- default: /* no other IAC SB <option> */
- break ;
- } ;
- break ;
-
- default: /* no other IAC X */
- break ;
- } ;
-
- return dealt_with ;
} ;