diff options
Diffstat (limited to 'lib/vty_cli.c')
-rw-r--r-- | lib/vty_cli.c | 3370 |
1 files changed, 1825 insertions, 1545 deletions
diff --git a/lib/vty_cli.c b/lib/vty_cli.c index bdaf1128..cf85fb6c 100644 --- a/lib/vty_cli.c +++ b/lib/vty_cli.c @@ -20,103 +20,365 @@ * 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" +/*------------------------------------------------------------------------------ + * Essential stuff. + */ +#define TELNET_NEWLINE "\r\n" +static const char* telnet_newline = TELNET_NEWLINE ; + const char* uty_cli_newline = TELNET_NEWLINE ; + /*============================================================================== - * Host name handling + * Construct and destroy CLI object + * * - * 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. * - * Static variables -- under the VTY_LOCK ! */ +static bool uty_cli_iac_callback(keystroke_iac_callback_args) ; +static void uty_cli_update_more(vty_cli cli) ; -static char* vty_host_name = NULL ; -int vty_host_name_set = 0 ; +static void uty_cli_cancel(vty_cli cli) ; -static void uty_new_host_name(const char* name) ; +/*------------------------------------------------------------------------------ + * Construct and initialise a new CLI object -- never embedded. + * + * 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(). + * + * 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 + * + * v_timeout = ??? + * + * key_stream = X -- set below + * + * drawn = false + * dirty = 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 + * + * out_active = false + * flush = 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 + * context_auth = false -- set by uty_cli_want_command() + * + * parsed = NULL -- see below + * to_do = cmd_do_nothing + * cl = NULL qstring -- set below + * clo = 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 */ + + cli->vf = vf ; + + /* Allocate and initialise a keystroke stream TODO: CSI ?? */ + cli->key_stream = keystroke_stream_new('\0', uty_cli_iac_callback, cli) ; + + /* Set up cl and clx qstrings and the command line output fifo */ + 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 */ + if (vty_nexus) + { + cli->pause_timer = qtimer_init_new(NULL, vty_cli_nexus->pile, + vty_term_pause_timeout, cli) ; + cli->tilde_enabled = vty_multi_nexus ; + } ; + + /* Ready to be started -- paused, out_active, flush & 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 */ + cli->flush = true ; /* all of 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 TODO ?? */ + 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 ; + cli->tilde_enabled = false ; + + /* 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 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 void -uty_free_host_name(void) +static bool +uty_cli_iac_callback(keystroke_iac_callback_args) { - if (vty_host_name != NULL) - XFREE (MTYPE_HOST, vty_host_name) ; /* sets vty_host_name = NULL */ + return uty_telnet_command(((vty_cli)context)->vf, stroke, true) ; } ; /*------------------------------------------------------------------------------ - * 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 ; + bool on ; - VTY_ASSERT_LOCKED() ; + on = false ; /* default state */ - uty_free_host_name() ; - vty_host_name = XSTRDUP(MTYPE_HOST, name) ; - - 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 +393,68 @@ 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. * - * cmd_in_progress -- a command has been dispatched and has not yet - * completed (may have been queued). + * 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. * - * cmd_out_enabled -- the command FIFO is may be emptied. + * The command may be a in_pipe, so there may be many commands + * to be completed before the CLI level command is. * - * This is set when a command completes, and cleared when - * everything is written away. + * or: the CLI has been closed. * - * cli_more_wait -- is in "--more--" wait state + * blocked -- is in_progress and a further command is now ready to be + * (bkd) dispatched. + * + * or: the CLI has been closed. + * + * 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 flush is set. When it + * is set, any current command line is wiped. + * + * 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. + * + * flush -- is set when the CLI is ready for the next command (so + * (fsh) when in_progress is cleared) to cause any incomplete + * command output to be flushed, and to signal that + * out_active should be cleared when all output is complete. + * + * 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:fsh:mwt:men: + * ---:---:---:---:---:---:---:----------------------------------------- + * 0 : 0 : 0 : 0 : 0 : 0 : 0 : collecting a new command + * 1 : 0 : 0 : 0 : 0 : 0 : 0 : waiting for command to be fetched + * 1 : 1 : 0 : X : 0 : 0 : 0 : command fetched and running + * 1 : 1 : 1 : X : 0 : 0 : 0 : waiting for command to complete + * 0 : 0 : 0 : 1 : 1 : 0 : 0 : waiting for command output to finish + * 1 : 1 : X : 1 : X : 1 : 1 : waiting for "--more--" to start + * 1 : 1 : X : 1 : X : 1 : 0 : waiting for "--more--" response + * 1 : 1 : 1 : 1 : 1 : 0 : 0 : waiting for command to complete, + * after the CLI has been closed * * There are two output FIFOs: * @@ -165,9 +463,19 @@ 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 a pipe-in, 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 if flushed. -- 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 @@ -177,44 +485,20 @@ uty_new_host_name(const char* name) * 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 +546,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 ; - - 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) ; -} ; +#define CONTROL(X) ((X) & 0x1F) -/*------------------------------------------------------------------------------ - * Start the CLI. - * - * 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 ; -} ; +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) ; /*------------------------------------------------------------------------------ - * Close the CLI + * CLI for VTY_TERMINAL * - * 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 +566,33 @@ 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) ; /*------------------------------------------------------------------------------ * Standard CLI for VTY_TERM -- if not blocked, runs until: @@ -407,282 +608,302 @@ 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 (cli->blocked || cli->out_active || cli->paused || cli->mon_active) + return not_ready ; - if (vio->cmd_in_progress) - { - assert(!vio->cmd_out_enabled) ; - 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 have something to do, do it. */ - if (vio->cli_do != cli_do_nothing) + 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 (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 + * "tilde_enabled" -- which will be true if single threaded, and may be + * set 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. + * + * 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. + */ + 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. */ + assert(!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 = cli->clx ; /* swap clx and cl */ + cli->clx = cli->cl ; + cli->cl = tmp ; - tmp = vio->clx ; /* swap clx and cl */ - vio->clx = vio->cl ; - vio->cl = tmp ; + to_do_now = cli->to_do ; /* current operation */ - qs_term(&vio->clx) ; /* ensure string is terminated */ - vty->buf = qs_chars(&vio->clx) ; /* terminated command line */ - cli_do = vio->cli_do ; /* current operation */ + cli->to_do = cmd_do_nothing ; /* clear */ + qs_clear(cli->cl) ; /* set cl empty */ - vio->cli_do = cli_do_nothing ; /* clear */ - qs_clear(&vio->cl) ; /* set cl empty (with '\0') */ - - /* Reset the command output FIFO and line_control */ - assert(vio_fifo_empty(&vio->cmd_obuf)) ; - uty_out_clear(vio) ; /* clears FIFO and line control */ + /* Reset the command output FIFO and line_control TODO */ +//uty_out_clear(cli->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) ; - } + if (cli->auth_context) + to_do_now |= cmd_do_auth ; else { /* All other nodes... */ - switch (cli_do) + 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) || !cli->tilde_enabled ; } 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 ; + return cmd_do_command ; +} ; - if (!vio->closed) - { - uty_cli_wipe(vio, 0) ; /* wipe any partly constructed line */ +/*------------------------------------------------------------------------------ + * 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 cli->dispatched + * * clear cli->in_progress + * * clear cli->blocked + * + * May be in more_wait state -- so avoids touching that. + * + * If not in_progress, then if dispatched, that is a new command ready to pass + * to the command loop -- which we do here, and set cli->in_progress. + */ +extern cmd_return_code_t +uty_cli_want_command(vty_cli cli, cmd_action action, cmd_context context) +{ + VTY_ASSERT_LOCKED() ; - /* Do the command completion actions that were deferred because the - * command was queued. + if (cli->in_progress) + { + /* Previous command has completed * - * Return of CMD_QUEUED => command was revoked before being executed. - * However interesting that might be... frankly don't care. + * Make sure state reflects the fact that we are now waiting for a + * command. */ - uty_cli_cmd_complete(vio, ret) ; + 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 */ - /* Kick the socket -- to write away any outstanding output, and - * re-enter the CLI when that's done. + *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 set flush flag, so + * that when buffers empty, the output will be released. + * + * If the output side is not owned by command output, wipe any temporary + * prompt. + * + * In any case, kick write_ready to ensure output clears and prompt is + * written and so on. */ - uty_sock_set_readiness(&vio->sock, write_ready) ; + if (cli->out_active) + cli->flush = true ; + else + uty_cli_draw(cli) ; + + uty_term_set_readiness(cli->vf, write_ready) ; } - else + else if (cli->dispatched) { - /* If the VTY is closed, the only reason it still exists is because - * there was cmd_in_progress. + /* New command has been dispatched -- can now pass that to the + * command loop -- setting it in_progress. */ - vio->cmd_in_progress = 0 ; + assert(cli->dispatch->to_do != cmd_do_nothing) ; + cmd_action_take(action, cli->dispatch) ; - uty_close(vio) ; /* Final close */ + cli->in_progress = true ; } ; - VTY_UNLOCK() ; -} + return cli->in_progress ? CMD_SUCCESS : CMD_WAITING ; +} ; /*------------------------------------------------------------------------------ - * Command has completed, so: - * - * * 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 - * - * If the return is CMD_CLOSE, then also now does the required half close. - * - * 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. - * - * Note that everything proceeds as if there is some output. So after every - * command goes through at least one write_ready event. - * - * This ensures some multiplexing at the command level. - * - * It also means that the decision about whether there is anything to output - * is left to the output code. + * Start pause timer and set paused. */ static void -uty_cli_cmd_complete(vty_io vio, enum cmd_return_code ret) +uty_cli_pause_start(vty_cli cli) { - VTY_ASSERT_LOCKED() ; - - assert(vio->cmd_in_progress && !vio->cmd_out_enabled) ; - - if (ret == CMD_CLOSE) - uty_half_close(vio, NULL) ; - - vio->cmd_in_progress = 0 ; /* command complete */ - vio->cmd_out_enabled = 1 ; /* enable the output */ - vio->cli_blocked = 1 ; /* now blocked waiting for output */ - - vio->vty->buf = NULL ; /* finished with command line */ - - uty_cmd_output_start(vio) ; /* reset line control (belt & braces) */ + 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,53 +912,14 @@ 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) ; - - uty_cli_wipe(vio, 0) ; /* make absolutely sure that command line is - wiped before change the CLI state */ + assert(cli->out_active && !cli->more_wait && !cli->drawn) ; - vio->cmd_out_enabled = 0 ; /* stop output pro tem */ - vio->cli_more_wait = 1 ; /* new state */ - - uty_cli_draw(vio) ; /* draw the "--more--" */ -} ; - -/*------------------------------------------------------------------------------ - * Exit the "--more--" CLI. - * - * Wipes the "--more--" prompt. - * - * This is used when the user responds to the prompt. - * - * 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: - * - * * 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 */ + cli->more_wait = true ; /* new state */ + cli->more_enter = true ; /* drawing the "--more--" etc. */ } ; /*------------------------------------------------------------------------------ @@ -750,87 +932,184 @@ uty_cli_exit_more_wait(vty_io vio) * * 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 ; 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 ; + uty_cli_draw_if_required(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, NULL) ; } while (get > 0) ; + + return read_ready ; } ; - /* Go through the "--more--" process, unless no longer write_open (!) */ - if (vio->sock.write_open) + /* Go through the "--more--" process, unless closing */ + /* 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 timeout (or error etc), returns knull_eof. + */ + uty_term_read(cli->vf, steal) ; + + /* If nothing stolen, make sure prompt is drawn and wait for more + * input. + * + * If anything at all has been stolen, then continue or cancel. + */ + cancel = false ; + switch(steal->type) { - /* 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) ; + case ks_null: + switch(steal->value) + { + case knull_not_eof: + // TODO need to refresh "--more--" in case of monitor ?? + return read_ready; /* <<< exit: no keystroke */ + break ; - /* 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 ; - } ; + case knull_eof: + case knull_timed_out: + cancel = true ; + break ; - /* Stolen a keystroke -- a (very) few terminate all output */ - if (steal.type == ks_char) - { - switch (steal.value) + default: + break ; + } ; + break ; + + case ks_char: + switch (steal->value) { case CONTROL('C'): case 'q': case 'Q': - uty_out_clear(vio) ; + cancel = true ; break; - default: /* everything else, thrown away */ + default: break ; } ; - } ; + break ; + + default: + break ; } ; /* End of "--more--" process * - * Wipe out the prompt and update state. + * Wipe out the prompt (unless "cancel") 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) + { + uty_out_clear(cli->vf->vio) ; + vio_lc_clear(cli->olc) ; /* clear & reset counter */ + uty_cli_cancel(cli) ; + } + 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 */ - return now_ready ; + cli->mon_active = false ; /* unblock cli & turn off output */ } ; /*============================================================================== @@ -838,27 +1117,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 +1140,54 @@ 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)) ; -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, ...) +extern void +uty_cli_out(vty_cli cli, const char *format, ...) { - VTY_ASSERT_LOCKED() ; - - if (vio->sock.write_open) - { - va_list args ; + 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) -{ VTY_ASSERT_LOCKED() ; - if (vio->cli_echo_suppress || !vio->sock.write_open) - return ; - - uty_cli_write(vio, this, len) ; -} + va_start (args, format); + vio_fifo_vprintf(cli->cbuf, format, args) ; + va_end(args); +} ; /*------------------------------------------------------------------------------ - * CLI VTY output -- echo 'n' characters using a cli_rep_char string - * - * Do nothing if echo suppressed (eg in AUTH_NODE) + * Completely empty the cli command buffer */ -static void -uty_cli_echo_n(vty_io vio, cli_rep_char chars, int n) +extern void +uty_cli_out_clear(vty_cli cli) { - VTY_ASSERT_LOCKED() ; - - if (vio->cli_echo_suppress || !vio->sock.write_open) - return ; - - uty_cli_write_n(vio, chars, n) ; -} + vio_fifo_clear(cli->cbuf, true) ; +} ; /*------------------------------------------------------------------------------ * 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 +1196,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 +1207,35 @@ 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 */ +static void uty_cli_goto_end_if_drawn(vty_cli cli) ; /*------------------------------------------------------------------------------ * Send newline to the console. * * Clears the cli_drawn and the cli_dirty flags. */ -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 ; + cli->dirty = false ; } ; /*------------------------------------------------------------------------------ @@ -1000,23 +1245,59 @@ 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) ; } ; } ; /*------------------------------------------------------------------------------ - * Send response to the given cli_do + * If the command line is drawn, then show that it has been cancelled. + * + * If the command line is dirty, then cancel it and start new line. + * + * Sets: cli_drawn = false + * cli_dirty = false + */ +static void +uty_cli_cancel(vty_cli cli) +{ + if (cli->drawn || cli->dirty) + { + uty_cli_goto_end_if_drawn(cli) ; + uty_cli_write_s(cli, " ^C" TELNET_NEWLINE) ; + } ; + + cli->drawn = false ; + cli->dirty = false ; +} ; + +/*------------------------------------------------------------------------------ + * 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,113 +1305,106 @@ 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 ; + cli->drawn = false ; + cli->dirty = false ; } ; /*------------------------------------------------------------------------------ @@ -1140,28 +1414,17 @@ uty_cli_wipe(vty_io vio, int len) * See uty_cli_draw(). */ extern bool -uty_cli_draw_if_required(vty_io vio) +uty_cli_draw_if_required(vty_cli cli) { - if (vio->cli_drawn) + if (cli->drawn) return false ; - uty_cli_draw(vio) ; + uty_cli_draw(cli) ; 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) ; -} ; - -/*------------------------------------------------------------------------------ * Draw prompt and entire command line, leaving current position where it * should be. * @@ -1171,50 +1434,49 @@ 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->dirty = false + * 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 */ + if (cli->dirty) + uty_cli_out_newline(cli) ; /* 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,162 +1487,92 @@ 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 ; + hist_previous = -1, + hist_next = +1, +}; - uty_read(vio, NULL) ; /* not stealing */ - - 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. * * Process keystrokes until run out of stuff to do, or have a "command line" * that must now be executed. @@ -1388,153 +1580,138 @@ uty_cli_get_keystroke(vty_io vio, keystroke stroke) * 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 - * - * When returns the cl is '\0' terminated. + * Returns: cmd_do_xxxx */ -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) ; - 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) ; - - if (!uty_cli_get_keystroke(vio, &stroke)) - { - ret = (stroke.value == knull_eof) ? cli_do_eof : cli_do_nothing ; - break ; - } ; + to_do = uty_cli_get_keystroke(cli, stroke) ; - 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 +1719,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,214 +1759,400 @@ 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' + * 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) ; + + 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. * - * Move current position forwards. + * Similar to uty_cli_auth, except accepts a limited number of keystrokes. * - * Returns number of characters inserted -- ie 'n' + * Returns: cmd_do_xxxx */ -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) ; + 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_t + */ +static cmd_do_t +uty_cli_get_keystroke(vty_cli cli, keystroke stroke) +{ + while (1) + { + if (keystroke_get(cli->key_stream, stroke)) + { + if (stroke->flags != 0) + { + /* TODO: deal with broken keystrokes */ + } + + if (stroke->type != ks_iac) + return cmd_do_keystroke ; /* have a keystroke */ + + /* Deal with telnet command, so invisible to upper level */ + uty_telnet_command(cli->vf, stroke, false) ; + } + else + { + int get ; + + assert(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, NULL) ; /* sets eof in key_stream + if hit eof or error */ + if (get <= 0) + return cmd_do_nothing ; + } ; + } ; +} ; + +/*============================================================================== + * Command line operations + */ + +/*------------------------------------------------------------------------------ + * Insert 'n' characters at current position in the command line, leaving + * cursor after the inserted characters. * - * Returns number of characters inserted -- ie length of string + * NB: assumes line will be updated by uty_cli_update_line() */ -static int -uty_cli_word_overwrite (vty_io vio, char *str) +static void +uty_cli_insert(qstring cl, const char* chars, int n) { - int n ; - VTY_ASSERT_LOCKED() ; + assert((qs_cp_nn(cl) <= qs_len_nn(cl)) && (n >= 0)) ; - n = uty_cli_overwrite(vio, str, strlen(str)) ; + if (n > 0) + { + qs_insert(cl, chars, n) ; + qs_move_cp_nn(cl, n) ; + } ; +} ; - vio->cl.len = vio->cl.cp ; +/*------------------------------------------------------------------------------ + * Replace 'm' characters at the current position, by 'n' characters and leave + * cursor at the end of the inserted characters. + * + * NB: assumes line will be updated by uty_cli_update_line() + */ +static void +uty_cli_replace(qstring cl, int m, const char* chars, int n) +{ + assert((qs_cp_nn(cl) <= qs_len_nn(cl)) && (n >= 0) && (m >= 0)) ; - return n ; -} + qs_replace(cl, m, chars, n) ; + + qs_move_cp_nn(cl, n) ; +} ; /*------------------------------------------------------------------------------ * Forward 'n' characters -- stop at end of line. * * Returns number of characters actually moved + * + * NB: assumes line will be updated by uty_cli_update_line() */ 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) ; - 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. * * Returns number of characters actually moved + * + * NB: assumes line will be updated by uty_cli_update_line() */ static int -uty_cli_backwards(vty_io vio, int n) +uty_cli_backwards(qstring cl, int n) { - VTY_ASSERT_LOCKED() ; - - if ((int)vio->cl.cp < n) - n = vio->cl.cp ; + if ((int)qs_cp_nn(cl) < n) + n = qs_cp_nn(cl) ; 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. + * + * NB: assumes line will be updated by uty_cli_update_line() + */ +static void +uty_cli_move(qstring cl, int n) +{ + if (n < 0) + uty_cli_backwards(cl, -n) ; + + if (n > 0) + uty_cli_forwards(cl, +n) ; +} ; /*------------------------------------------------------------------------------ * Delete 'n' characters -- forwards -- stop at end of line. * - * Returns number of characters actually deleted. + * NB: assumes line will be updated by uty_cli_update_line() */ -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 ; - - 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. + * NB: assumes line will be updated by uty_cli_update_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. + * NB: assumes line will be updated by uty_cli_update_line() */ -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 + * NB: assumes line will be updated by uty_cli_update_line() */ -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,25 +2163,23 @@ 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 ; - while ((tp < ep) && (*tp != ' ')) + while ((tp < ep) && (*tp != ' ')) /* step over spaces */ ++tp ; - while ((tp < ep) && (*tp == ' ')) + while ((tp < ep) && (*tp == ' ')) /* step to space */ ++tp ; return tp - cp ; @@ -1825,11 +2189,13 @@ uty_cli_word_forwards_delta(vty_io vio) * Forward word -- move to start of next word. * * Moves past any non-spaces, then past any spaces. + * + * NB: assumes line will be updated by uty_cli_update_line() */ -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)) ; } ; /*------------------------------------------------------------------------------ @@ -1841,24 +2207,21 @@ uty_cli_word_forwards(vty_io vio) * Steps back 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(qs_cp_nn(cl) <= qs_len_nn(cl)) ; - assert(vio->cl.cp <= vio->cl.len) ; - - 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,30 +2230,17 @@ 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. + * NB: assumes line will be updated by uty_cli_update_line() */ -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)) ; } ; /*------------------------------------------------------------------------------ @@ -1898,12 +2248,12 @@ uty_cli_word_backwards (vty_io vio) * * Deletes any leading spaces, then deletes upto next space or end of line. * - * Returns number of characters deleted. + * NB: assumes line will be updated by uty_cli_update_line() */ -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)) ; } /*------------------------------------------------------------------------------ @@ -1911,99 +2261,148 @@ uty_cli_del_word_forwards(vty_io vio) * * Deletes any trailing spaces, then deletes upto next space or start of line. * - * Returns number of characters deleted. + * NB: assumes line will be updated by uty_cli_update_line() */ -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. + * NB: assumes line will be updated by uty_cli_update_line() */ -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. + * NB: assumes line will be updated by uty_cli_update_line() */ -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. * - * Return number of characters affected. + * NB: assumes line will be updated by uty_cli_update_line() */ -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. + * + * 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 +2410,38 @@ 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) ; - /* Advance to the near future and reset the history pointer */ - vio->hindex++; - if (vio->hindex == VTY_MAXHIST) - vio->hindex = 0; + 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 */ + } ; - vio->hp = vio->hindex; + /* Now replace the h_now entry + * + * Note that the line inserted in the history has it's 'cp' set to the end of + * the line -- so that it is there when it comes back out again. + */ + 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) ; + + /* Advance history */ + cli->hp = cli->h_now = hp_next(cli->h_now) ; } ; /*------------------------------------------------------------------------------ @@ -2035,236 +2449,342 @@ 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 at 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 * */ -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 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 vector uty_cli_cmd_prepare(vty_io vio, int help) ; +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()". + * + * NB: assumes line will be updated by uty_cli_update_line() */ 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) ; +} ; + +/*------------------------------------------------------------------------------ + * 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 ; + + /* The preflight checks avoid getting into trouble doing command completion + * on a line with comment + */ + msg = cmd_help_preflight(cli->parsed) ; + if (msg != NULL) + { + uty_cli_help_message(cli, msg) ; + return 0 ; + } ; - uty_cli_out_newline(vio); /* clears cli_drawn */ + /* Now see what the cmd_completion can come up with. */ + ret = cmd_completion(cli->parsed, cli->context) ; - /* Deal with result. */ - switch (ret) + if (ret == CMD_ERR_PARSING) { - case CMD_ERR_AMBIGUOUS: - uty_cli_out_CMD_ERR_AMBIGUOUS(vio) ; - break ; + 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, "^") ; + } ; - case CMD_ERR_NO_MATCH: - uty_cli_out_CMD_ERR_NO_MATCH(vio) ; - break ; + uty_cli_help_message(cli, qs_make_string(cli->parsed->emess)) ; - default: - uty_cli_describe_show(vio, describe) ; - break ; + 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 +2800,186 @@ 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) ; -} ; - -/*------------------------------------------------------------------------------ - * 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, "") ; + if ((str_len == 0) && (doc_len == 0)) + return ; /* quit if nothing to say */ - 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 and cli->dirty. */ 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 ; + cli->dirty = 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 ; } ; |