aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Luke <chrisy@flirble.org>2016-04-25 13:48:54 -0400
committerDave Barach <openvpp@barachs.net>2016-04-27 13:32:48 +0000
commit0aca5eb1b88524fc8e988deb6488d14c092b0137 (patch)
treebd38ae395a66b02227a22f044cf0e2ddd857183d
parentb3656ea0b847ef7bd1a19d2915cdd5938f32b314 (diff)
Debug/Telnet CLI enhancements
A fairly comprehensive re-work of the built-in debug and telnet CLI to add various command line editing features and to add command history to the debug CLI. This may seem like a large change but a good amount of it is merely reworking the existing CLI code (which changed its indent level). The features this patch enables include: - Enable history in the debug CLI. - Put both stdin and telnet connections in char-by-char mode. - Echo from the server, not the client, for more control. - Add a mostly no-op but fairly complete Telnet protocol processor. - Perform control code parsing on the input byte stream to match strings of both control codes and ANSI/VT100 escape sequences. - Up/down keys scroll through the history (like ^P/^N). - Do CRLF output cooking (\n -> \r\n) for connections that need it. - Left/right cursor movements, insert/erase at cursor. - Home/end cursor jumps. - Jump left/right word at a time (Ctrl-left/right). - Negotiate the terminal type from Telnet clients. (well, the code doesn’t really negotiate, it demands it, but the client is led to believe it was a negotiation) - Read terminal type from TERM variable for the local debug CLI. - Delete from cursor to end of line (^K). Delete char-right (^D/Del). - Clear screen (^L) and repaint prompt/current command (on non-ANSI terminals it just newlines and repaints the line). Change-Id: Id274b56ccfd4cc8c19ddc0f478890f21f284262a Signed-off-by: Chris Luke <chrisy@flirble.org>
-rw-r--r--vlib/vlib/unix/cli.c1275
-rw-r--r--vlib/vlib/unix/unix.h10
2 files changed, 1063 insertions, 222 deletions
diff --git a/vlib/vlib/unix/cli.c b/vlib/vlib/unix/cli.c
index 2e6e0593280..809be73331e 100644
--- a/vlib/vlib/unix/cli.c
+++ b/vlib/vlib/unix/cli.c
@@ -39,13 +39,36 @@
#include <vlib/vlib.h>
#include <vlib/unix/unix.h>
+#include <vppinfra/timer.h>
+#include <ctype.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <termios.h>
#include <unistd.h>
#include <arpa/telnet.h>
+/* ANSI Escape code. */
+#define ESC "\x1b"
+
+/* ANSI Control Sequence Introducer. */
+#define CSI ESC "["
+
+/* ANSI sequences. */
+#define ANSI_CLEAR CSI "2J" CSI "1;1H"
+
+/** Maximum depth into a byte stream from which to compile a Telnet
+ * protocol message. This is a saftey measure. */
+#define UNIX_CLI_MAX_DEPTH_TELNET 16
+
+/** Default CLI history depth if not configured in startup.conf */
+#define UNIX_CLI_DEFAULT_HISTORY 50
+
+/** Unix standard in */
+#define UNIX_CLI_STDIN_FD 0
+
+
+/** Unix CLI session. */
typedef struct {
u32 unix_file_index;
@@ -64,6 +87,18 @@ typedef struct {
u8 * search_key;
int search_mode;
+ /* Position of the insert cursor on the current input line */
+ u32 cursor;
+
+ /* Set if the CRLF mode wants CR + LF */
+ u8 crlf_mode;
+
+ /* Can we do ANSI output? */
+ u8 ansi_capable;
+
+ /* Has the session started? */
+ u8 started;
+
u32 process_node_index;
} unix_cli_file_t;
@@ -74,6 +109,118 @@ unix_cli_file_free (unix_cli_file_t * f)
vec_free (f->input_vector);
}
+/* CLI actions */
+typedef enum {
+ UNIX_CLI_PARSE_ACTION_NOACTION = 0,
+ UNIX_CLI_PARSE_ACTION_CRLF,
+ UNIX_CLI_PARSE_ACTION_TAB,
+ UNIX_CLI_PARSE_ACTION_ERASE,
+ UNIX_CLI_PARSE_ACTION_ERASERIGHT,
+ UNIX_CLI_PARSE_ACTION_UP,
+ UNIX_CLI_PARSE_ACTION_DOWN,
+ UNIX_CLI_PARSE_ACTION_LEFT,
+ UNIX_CLI_PARSE_ACTION_RIGHT,
+ UNIX_CLI_PARSE_ACTION_HOME,
+ UNIX_CLI_PARSE_ACTION_END,
+ UNIX_CLI_PARSE_ACTION_WORDLEFT,
+ UNIX_CLI_PARSE_ACTION_WORDRIGHT,
+ UNIX_CLI_PARSE_ACTION_ERASELINELEFT,
+ UNIX_CLI_PARSE_ACTION_ERASELINERIGHT,
+ UNIX_CLI_PARSE_ACTION_CLEAR,
+ UNIX_CLI_PARSE_ACTION_REVSEARCH,
+ UNIX_CLI_PARSE_ACTION_FWDSEARCH,
+ UNIX_CLI_PARSE_ACTION_HISTORY,
+ UNIX_CLI_PARSE_ACTION_YANK,
+ UNIX_CLI_PARSE_ACTION_TELNETIAC,
+
+ UNIX_CLI_PARSE_ACTION_PARTIALMATCH,
+ UNIX_CLI_PARSE_ACTION_NOMATCH
+} unix_cli_parse_action_t;
+
+/** \brief Mapping of input buffer strings to action values.
+ * @note This won't work as a hash since we need to be able to do
+ * partial matches on the string.
+ */
+typedef struct {
+ u8 *input; /**< Input string to match. */
+ u32 len; /**< Length of input without final NUL. */
+ unix_cli_parse_action_t action; /**< Action to take when matched. */
+} unix_cli_parse_actions_t;
+
+/** \brief Given a capital ASCII letter character return a NUL terminated
+ * string with the control code for that letter.
+ * \example CTL('A') returns { 0x01, 0x00 } as a u8[].
+ */
+#define CTL(c) (u8[]){ (c) - '@', 0 }
+
+#define _(a,b) { .input = (u8 *)(a), .len = sizeof(a) - 1, .action = (b) }
+static unix_cli_parse_actions_t unix_cli_parse_strings[] = {
+ /* Line handling */
+ _( "\r\n", UNIX_CLI_PARSE_ACTION_CRLF ), /* Must be before '\r' */
+ _( "\n", UNIX_CLI_PARSE_ACTION_CRLF ),
+ _( "\r\0", UNIX_CLI_PARSE_ACTION_CRLF ), /* Telnet does this */
+ _( "\r", UNIX_CLI_PARSE_ACTION_CRLF ),
+
+ /* Unix shell control codes */
+ _( CTL('B'), UNIX_CLI_PARSE_ACTION_LEFT ),
+ _( CTL('F'), UNIX_CLI_PARSE_ACTION_RIGHT ),
+ _( CTL('P'), UNIX_CLI_PARSE_ACTION_UP ),
+ _( CTL('N'), UNIX_CLI_PARSE_ACTION_DOWN ),
+ _( CTL('A'), UNIX_CLI_PARSE_ACTION_HOME ),
+ _( CTL('E'), UNIX_CLI_PARSE_ACTION_END ),
+ _( CTL('D'), UNIX_CLI_PARSE_ACTION_ERASERIGHT ),
+ _( CTL('U'), UNIX_CLI_PARSE_ACTION_ERASELINELEFT ),
+ _( CTL('K'), UNIX_CLI_PARSE_ACTION_ERASELINERIGHT ),
+ _( CTL('Y'), UNIX_CLI_PARSE_ACTION_YANK ),
+ _( CTL('L'), UNIX_CLI_PARSE_ACTION_CLEAR ),
+ _( ESC "b", UNIX_CLI_PARSE_ACTION_WORDLEFT ), /* Alt-B */
+ _( ESC "f", UNIX_CLI_PARSE_ACTION_WORDRIGHT ), /* Alt-F */
+ _( "\b", UNIX_CLI_PARSE_ACTION_ERASE ), /* ^H */
+ _( "\x7f", UNIX_CLI_PARSE_ACTION_ERASE ), /* Backspace */
+ _( "\t", UNIX_CLI_PARSE_ACTION_TAB ), /* ^I */
+
+ /* VT100 Normal mode - Broadest support */
+ _( CSI "A", UNIX_CLI_PARSE_ACTION_UP ),
+ _( CSI "B", UNIX_CLI_PARSE_ACTION_DOWN ),
+ _( CSI "C", UNIX_CLI_PARSE_ACTION_RIGHT ),
+ _( CSI "D", UNIX_CLI_PARSE_ACTION_LEFT ),
+ _( CSI "H", UNIX_CLI_PARSE_ACTION_HOME ),
+ _( CSI "F", UNIX_CLI_PARSE_ACTION_END ),
+ _( CSI "3~", UNIX_CLI_PARSE_ACTION_ERASERIGHT ), /* Delete */
+ _( CSI "1;5D", UNIX_CLI_PARSE_ACTION_WORDLEFT ), /* C-Left */
+ _( CSI "1;5C", UNIX_CLI_PARSE_ACTION_WORDRIGHT ),/* C-Right */
+
+ /* VT100 Application mode - Some Gnome Terminal functions use these */
+ _( ESC "OA", UNIX_CLI_PARSE_ACTION_UP ),
+ _( ESC "OB", UNIX_CLI_PARSE_ACTION_DOWN ),
+ _( ESC "OC", UNIX_CLI_PARSE_ACTION_RIGHT ),
+ _( ESC "OD", UNIX_CLI_PARSE_ACTION_LEFT ),
+ _( ESC "OH", UNIX_CLI_PARSE_ACTION_HOME ),
+ _( ESC "OF", UNIX_CLI_PARSE_ACTION_END ),
+
+ /* ANSI X3.41-1974 - sent by Microsoft Telnet and PuTTY */
+ _( CSI "1~", UNIX_CLI_PARSE_ACTION_HOME ),
+ _( CSI "4~", UNIX_CLI_PARSE_ACTION_END ),
+
+ /* Emacs-ish history search */
+ _( CTL('S'), UNIX_CLI_PARSE_ACTION_FWDSEARCH ),
+ _( CTL('R'), UNIX_CLI_PARSE_ACTION_REVSEARCH ),
+
+ /* TODO: replace with 'history' command? */
+ _( "?", UNIX_CLI_PARSE_ACTION_HISTORY ),
+
+ /* Other protocol things */
+ _( "\xff", UNIX_CLI_PARSE_ACTION_TELNETIAC ), /* IAC */
+ _( "\0", UNIX_CLI_PARSE_ACTION_NOACTION ), /* NUL */
+ _( NULL, UNIX_CLI_PARSE_ACTION_NOMATCH )
+};
+#undef _
+
+typedef enum {
+ UNIX_CLI_PROCESS_EVENT_READ_READY,
+ UNIX_CLI_PROCESS_EVENT_QUIT,
+} unix_cli_process_event_type_t;
+
typedef struct {
/* Prompt string for CLI. */
u8 * cli_prompt;
@@ -88,6 +235,64 @@ typedef struct {
static unix_cli_main_t unix_cli_main;
+/**
+ * \brief Search for a byte sequence in the action list.
+ *
+ * Searches unix_cli_parse_actions[] for a match with the bytes in \c input
+ * of maximum length \c ilen . When a match is made \c *matched indicates how
+ * many bytes were matched. Returns a value from the enum
+ * \c unix_cli_parse_action_t to indicate whether no match was found, a
+ * partial match was found or a complete match was found and what action,
+ * if any, should be taken.
+ *
+ * @param input String fragment to search for.
+ * @param ilen Length of the string in 'input'.
+ * @param matched Pointer to an integer that will contain the number of
+ * bytes matched when a complete match is found.
+ *
+ * @return Action from \v unix_cli_parse_action_t that the string fragment
+ * matches.
+ * \c UNIX_CLI_PARSE_ACTION_PARTIALMATCH is returned when the whole
+ * input string matches the start of at least one action.
+ * \c UNIX_CLI_PARSE_ACTION_NOMATCH is returned when there is no
+ * match at all.
+ */
+static unix_cli_parse_action_t
+unix_cli_match_action(u8 *input, u32 ilen, i32 *matched)
+{
+ unix_cli_parse_actions_t *a = unix_cli_parse_strings;
+ u8 partial = 0;
+
+ while (a->input)
+ {
+ if (ilen >= a->len)
+ {
+ /* see if the start of the input buffer exactly matches the current
+ * action string. */
+ if (memcmp(input, a->input, a->len) == 0)
+ {
+ *matched = a->len;
+ return a->action;
+ }
+ }
+ else
+ {
+ /* if the first ilen characters match, flag this as a partial -
+ * meaning keep collecting bytes in case of a future match */
+ if (memcmp(input, a->input, ilen) == 0)
+ partial = 1;
+ }
+
+ /* check next action */
+ a ++;
+ }
+
+ return partial ?
+ UNIX_CLI_PARSE_ACTION_PARTIALMATCH :
+ UNIX_CLI_PARSE_ACTION_NOMATCH;
+}
+
+
static void
unix_cli_add_pending_output (unix_file_t * uf,
unix_cli_file_t * cf,
@@ -123,7 +328,113 @@ unix_cli_del_pending_output (unix_file_t * uf,
}
}
-/* VLIB cli output function. */
+/** \brief A bit like strchr with a buffer length limit.
+ * Search a buffer for the first instance of a character up to the limit of
+ * the buffer length. If found then return the position of that character.
+ *
+ * The key departure from strchr is that if the character is not found then
+ * return the buffer length.
+ *
+ * @param chr The byte value to search for.
+ * @param str The buffer in which to search for the value.
+ * @param len The depth into the buffer to search.
+ *
+ * @return The index of the first occurence of \c chr. If \c chr is not
+ * found then \c len instead.
+ */
+always_inline word unix_vlib_findchr(u8 chr, u8 *str, word len)
+{
+ word i = 0;
+ for (i = 0; i < len; i++, str++)
+ {
+ if (*str == chr)
+ return i;
+ }
+ return len;
+}
+
+/** \brief Send a buffer to the CLI stream if possible, enqueue it otherwise.
+ * Attempts to write given buffer to the file descriptor of the given
+ * Unix CLI session. If that session already has data in the output buffer
+ * or if the write attempt tells us to try again later then the given buffer
+ * is appended to the pending output buffer instead.
+ *
+ * This is typically called only from \c unix_vlib_cli_output_cooked since
+ * that is where CRLF handling occurs or from places where we explicitly do
+ * not want cooked handling.
+ *
+ * @param cf Unix CLI session of the desired stream to write to.
+ * @param uf The Unix file structure of the desired stream to write to.
+ * @param buffer Pointer to the buffer that needs to be written.
+ * @param buffer_bytes The number of bytes from \c buffer to write.
+ */
+static void unix_vlib_cli_output_raw(unix_cli_file_t * cf,
+ unix_file_t * uf,
+ u8 * buffer,
+ uword buffer_bytes)
+{
+ int n = 0;
+
+ if (vec_len (cf->output_vector) == 0)
+ n = write (uf->file_descriptor, buffer, buffer_bytes);
+
+ if (n < 0 && errno != EAGAIN)
+ {
+ clib_unix_warning ("write");
+ }
+ else if ((word) n < (word) buffer_bytes)
+ {
+ /* We got EAGAIN or we already have stuff in the buffer;
+ * queue up whatever didn't get sent for later. */
+ if (n < 0) n = 0;
+ unix_cli_add_pending_output (uf, cf, buffer + n, buffer_bytes - n);
+ }
+}
+
+/** \brief Process a buffer for CRLF handling before outputting it to the CLI.
+ *
+ * @param cf Unix CLI session of the desired stream to write to.
+ * @param uf The Unix file structure of the desired stream to write to.
+ * @param buffer Pointer to the buffer that needs to be written.
+ * @param buffer_bytes The number of bytes from \c buffer to write.
+ */
+static void unix_vlib_cli_output_cooked(unix_cli_file_t * cf,
+ unix_file_t * uf,
+ u8 * buffer,
+ uword buffer_bytes)
+{
+ word end = 0, start = 0;
+
+ while (end < buffer_bytes)
+ {
+ if (cf->crlf_mode)
+ {
+ /* iterate the line on \n's so we can insert a \r before it */
+ end = unix_vlib_findchr('\n',
+ buffer + start,
+ buffer_bytes - start) + start;
+ }
+ else
+ {
+ /* otherwise just send the whole buffer */
+ end = buffer_bytes;
+ }
+
+ unix_vlib_cli_output_raw(cf, uf, buffer + start, end - start);
+
+ if (cf->crlf_mode)
+ {
+ if (end < buffer_bytes)
+ {
+ unix_vlib_cli_output_raw(cf, uf, (u8 *)"\r\n", 2);
+ end ++; /* skip the \n that we already sent */
+ }
+ start = end;
+ }
+ }
+}
+
+/** \brief VLIB CLI output function. */
static void unix_vlib_cli_output (uword cli_file_index,
u8 * buffer,
uword buffer_bytes)
@@ -132,245 +443,721 @@ static void unix_vlib_cli_output (uword cli_file_index,
unix_cli_main_t * cm = &unix_cli_main;
unix_cli_file_t * cf;
unix_file_t * uf;
- int n;
-
+
cf = pool_elt_at_index (cm->cli_file_pool, cli_file_index);
uf = pool_elt_at_index (um->file_pool, cf->unix_file_index);
- n = 0;
- if (vec_len (cf->output_vector) == 0)
- n = write (uf->file_descriptor, buffer, buffer_bytes);
- if (n < 0 && errno != EAGAIN)
- clib_unix_warning ("write");
- else if ((word) n < (word) buffer_bytes)
- {
- if (n < 0) n = 0;
- unix_cli_add_pending_output (uf, cf, buffer + n, buffer_bytes - n);
- }
+ unix_vlib_cli_output_cooked(cf, uf, buffer, buffer_bytes);
}
-static int unix_cli_line_edit (unix_main_t * um, unix_cli_file_t * cf)
+/** \brief Identify whether a terminal type is ANSI capable. */
+static u8 unix_cli_terminal_type(u8 * term, uword len)
{
+ /* This may later be better done as a hash of some sort. */
+#define _(a) do { \
+ if (strncasecmp(a, (char *)term, (size_t)len) == 0) return 1; \
+ } while(0)
+
+ _("xterm");
+ _("xterm-color");
+ _("xterm-256color"); /* iTerm on Mac */
+ _("screen");
+ _("ansi"); /* Microsoft Telnet */
+#undef _
+
+ return 0;
+}
+
+/** \brief Emit initial prompt on a connection. */
+static void unix_cli_file_welcome(unix_cli_main_t * cm, unix_cli_file_t * cf)
+{
+ unix_main_t * um = &unix_main;
unix_file_t * uf = pool_elt_at_index (um->file_pool, cf->unix_file_index);
- u8 * prev;
- int i, j, delta;
- for (i = 0; i < vec_len (cf->input_vector); i++)
+ /*
+ * Put the first bytes directly into the buffer so that further output is
+ * queued until everything is ready. (oterwise initial prompt can appear
+ * mid way through VPP initialization)
+ */
+ unix_cli_add_pending_output (uf, cf,
+ cm->cli_prompt,
+ vec_len (cm->cli_prompt));
+
+ cf->started = 1;
+}
+
+/** \brief A failsafe triggered on a timer to ensure we send the prompt
+ * to telnet sessions that fail to negotiate the terminal type. */
+static void unix_cli_file_welcome_timer(any arg, f64 delay)
+{
+ unix_cli_main_t * cm = &unix_cli_main;
+ unix_cli_file_t * cf;
+ (void)delay;
+
+ /* Check the connection didn't close already */
+ if (pool_is_free_index (cm->cli_file_pool, (uword)arg))
+ return;
+
+ cf = pool_elt_at_index (cm->cli_file_pool, (uword)arg);
+
+ if (!cf->started)
+ unix_cli_file_welcome(cm, cf);
+}
+
+/** \brief A mostly no-op Telnet state machine.
+ * Process Telnet command bytes in a way that ensures we're mostly
+ * transparent to the Telnet protocol. That is, it's mostly a no-op.
+ *
+ * @return -1 if we need more bytes, otherwise a positive integer number of
+ * bytes to consume from the input_vector, not including the initial
+ * IAC byte.
+ */
+static i32 unix_cli_process_telnet(unix_main_t * um,
+ unix_cli_file_t * cf,
+ unix_file_t * uf,
+ u8 * input_vector,
+ uword len)
+{
+ /* Input_vector starts at IAC byte.
+ * See if we have a complete message; if not, return -1 so we wait for more.
+ * if we have a complete message, consume those bytes from the vector.
+ */
+ i32 consume = 0;
+
+ if (len == 1)
+ return -1; /* want more bytes */
+
+ switch (input_vector[1])
{
- switch (cf->input_vector[i])
+ case IAC:
+ /* two IAC's in a row means to pass through 0xff.
+ * since that makes no sense here, just consume it.
+ */
+ consume = 1;
+ break;
+
+ case WILL:
+ case WONT:
+ case DO:
+ case DONT:
+ /* Expect 3 bytes */
+ if (vec_len(input_vector) < 3)
+ return -1; /* want more bytes */
+
+ consume = 2;
+ break;
+
+ case SB:
{
- case 0:
- continue;
-
- case '?':
- /* Erase the current command (if any) plus ?*/
- for (j = 0; j < (vec_len (cf->current_command)+1); j++)
- unix_cli_add_pending_output (uf, cf, (u8 *) "\b \b", 3);
-
- unix_cli_add_pending_output (uf, cf, (u8 *) "\r\nHistory:\r\n", 12);
-
- for (j = 0; j < vec_len (cf->command_history); j++)
+ /* Sub option - search ahead for IAC SE to end it */
+ i32 i;
+ for (i = 3; i < len && i < UNIX_CLI_MAX_DEPTH_TELNET; i++)
{
- unix_cli_add_pending_output (uf, cf, cf->command_history[j],
- vec_len(cf->command_history[j]));
- unix_cli_add_pending_output (uf, cf, (u8 *) "\r\n", 2);
+ if (input_vector[i - 1] == IAC && input_vector[i] == SE)
+ {
+ /* We have a complete message; see if we care about it */
+ switch (input_vector[2])
+ {
+ case TELOPT_TTYPE:
+ if (input_vector[3] != 0)
+ break;
+ /* See if the terminal type is ANSI capable */
+ cf->ansi_capable =
+ unix_cli_terminal_type(input_vector + 4, i - 5);
+ /* If session not started, we can release the pause */
+ if (!cf->started)
+ /* Send the welcome banner and initial prompt */
+ unix_cli_file_welcome(&unix_cli_main, cf);
+ break;
+
+ default:
+ break;
+ }
+ /* Consume it all */
+ consume = i;
+ break;
+ }
}
- goto crlf;
- /* ^R - reverse search */
- case 'R' - '@':
- case 'S' - '@':
- if (cf->search_mode == 0)
- {
- /* Erase the current command (if any) plus ^R */
- for (j = 0; j < (vec_len (cf->current_command)+2); j++)
- unix_cli_add_pending_output (uf, cf, (u8 *) "\b \b", 3);
-
- vec_reset_length (cf->search_key);
- vec_reset_length (cf->current_command);
- if (cf->input_vector[i] == 'R' - '@')
- cf->search_mode = -1;
- else
- cf->search_mode = 1;
- }
- else
- {
- if (cf->input_vector[i] == 'R' - '@')
- cf->search_mode = -1;
- else
- cf->search_mode = 1;
+ if (i == UNIX_CLI_MAX_DEPTH_TELNET)
+ consume = 1; /* hit max search depth, advance one byte */
+
+ if (consume == 0)
+ return -1; /* want more bytes */
- cf->excursion += cf->search_mode;
- unix_cli_add_pending_output (uf, cf, (u8 *) "\b \b", 3);
- goto search_again;
- }
break;
+ }
- /* ^U - line-kill */
- case 'U'-'@':
- /* Erase the command, plus ^U */
- for (j = 0; j < (vec_len (cf->current_command)+2); j++)
- unix_cli_add_pending_output (uf, cf, (u8 *) "\b \b", 3);
- vec_reset_length (cf->current_command);
- cf->search_mode = 0;
- continue;
+ case GA:
+ case EL:
+ case EC:
+ case AO:
+ case IP:
+ case BREAK:
+ case DM:
+ case NOP:
+ case SE:
+ case EOR:
+ case ABORT:
+ case SUSP:
+ case xEOF:
+ /* Simple one-byte messages */
+ consume = 1;
+ break;
+
+ case AYT:
+ /* Are You There - trigger a visible response */
+ consume = 1;
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "fd.io VPP\n", 10);
+ break;
+
+ default:
+ /* Unknown command! Eat the IAC byte */
+ break;
+ }
- /* ^P - previous, ^N - next */
- case 'P' - '@':
- case 'N' - '@':
- cf->search_mode = 0;
- /* Erase the command, plus ^P */
- for (j = 0; j < (vec_len (cf->current_command)+2); j++)
- unix_cli_add_pending_output (uf, cf, (u8 *) "\b \b", 3);
+ return consume;
+}
+
+/** \brief Process actionable input.
+ * Based on the \c action process the input; this typically involves
+ * searching the command history or editing the current command line.
+ */
+static int unix_cli_line_process_one(unix_cli_main_t * cm,
+ unix_main_t * um,
+ unix_cli_file_t * cf,
+ unix_file_t * uf,
+ u8 input,
+ unix_cli_parse_action_t action)
+{
+ u8 * prev;
+ int j, delta;
+
+ switch (action)
+ {
+ case UNIX_CLI_PARSE_ACTION_NOACTION:
+ break;
+
+ case UNIX_CLI_PARSE_ACTION_HISTORY:
+ /* Erase the current command (if any)*/
+ for (j = cf->cursor; j < (vec_len (cf->current_command)); j++)
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1);
+ for (j = 0; j < (vec_len (cf->current_command)); j++)
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b \b", 3);
+
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\nHistory:\n", 10);
+
+ for (j = 0; j < vec_len (cf->command_history); j++)
+ {
+ unix_vlib_cli_output_cooked (cf, uf, cf->command_history[j],
+ vec_len(cf->command_history[j]));
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1);
+ }
+ goto crlf;
+
+ case UNIX_CLI_PARSE_ACTION_REVSEARCH:
+ case UNIX_CLI_PARSE_ACTION_FWDSEARCH:
+ if (cf->search_mode == 0)
+ {
+ /* Erase the current command (if any) */
+ for (j = 0; j < (vec_len (cf->current_command)); j++)
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b \b", 3);
+
+ vec_reset_length (cf->search_key);
vec_reset_length (cf->current_command);
- if (vec_len (cf->command_history))
- {
- if (cf->input_vector[i] == 'P' - '@')
- delta = -1;
- else
- delta = 1;
+ if (action == UNIX_CLI_PARSE_ACTION_REVSEARCH)
+ cf->search_mode = -1;
+ else
+ cf->search_mode = 1;
+ cf->cursor = 0;
+ }
+ else
+ {
+ if (action == UNIX_CLI_PARSE_ACTION_REVSEARCH)
+ cf->search_mode = -1;
+ else
+ cf->search_mode = 1;
- cf->excursion += delta;
+ cf->excursion += cf->search_mode;
+ goto search_again;
+ }
+ break;
+
+ case UNIX_CLI_PARSE_ACTION_ERASELINELEFT:
+ /* Erase the command from the cursor to the start */
+
+ /* Shimmy forwards to the new end of line position */
+ delta = vec_len (cf->current_command) - cf->cursor;
+ for (j = cf->cursor; j > delta; j--)
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
+ /* Zap from here to the end of what is currently displayed */
+ for (; j < (vec_len (cf->current_command)); j++)
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1);
+ /* Get back to the start of the line */
+ for (j = 0; j < (vec_len (cf->current_command)); j++)
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
+
+ j = vec_len(cf->current_command) - cf->cursor;
+ memmove(cf->current_command,
+ cf->current_command + cf->cursor,
+ j);
+ _vec_len(cf->current_command) = j;
+
+ /* Print the new contents */
+ unix_vlib_cli_output_cooked (cf, uf, cf->current_command, j);
+ /* Shimmy back to the start */
+ for (j = 0; j < (vec_len (cf->current_command)); j++)
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
+ cf->cursor = 0;
+
+ cf->search_mode = 0;
+ break;
+
+ case UNIX_CLI_PARSE_ACTION_ERASELINERIGHT:
+ /* Erase the command from the cursor to the end */
+
+ /* Zap from cursor to end of what is currently displayed */
+ for (j = cf->cursor; j < (vec_len (cf->current_command)); j++)
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1);
+ /* Get back to where we were */
+ for (j = cf->cursor; j < (vec_len (cf->current_command)); j++)
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
+
+ /* Truncate the line at the cursor */
+ _vec_len(cf->current_command) = cf->cursor;
+
+ cf->search_mode = 0;
+ break;
+
+ case UNIX_CLI_PARSE_ACTION_LEFT:
+ if (cf->cursor > 0)
+ {
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
+ cf->cursor --;
+ }
- if (cf->excursion > (i32) vec_len (cf->command_history) -1)
- cf->excursion = 0;
- else if (cf->excursion < 0)
- cf->excursion = vec_len (cf->command_history) -1;
+ cf->search_mode = 0;
+ break;
+
+ case UNIX_CLI_PARSE_ACTION_RIGHT:
+ if (cf->cursor < vec_len(cf->current_command))
+ {
+ /* have to emit the character under the cursor */
+ unix_vlib_cli_output_cooked (cf, uf, cf->current_command + cf->cursor, 1);
+ cf->cursor ++;
+ }
- prev = cf->command_history [cf->excursion];
- vec_validate (cf->current_command, vec_len(prev)-1);
+ cf->search_mode = 0;
+ break;
+
+ case UNIX_CLI_PARSE_ACTION_UP:
+ case UNIX_CLI_PARSE_ACTION_DOWN:
+ cf->search_mode = 0;
+ /* Erase the command */
+ for (j = cf->cursor; j < (vec_len (cf->current_command)); j++)
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1);
+ for (j = 0; j < (vec_len (cf->current_command)); j++)
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b \b", 3);
+ vec_reset_length (cf->current_command);
+ if (vec_len (cf->command_history))
+ {
+ if (action == UNIX_CLI_PARSE_ACTION_UP)
+ delta = -1;
+ else
+ delta = 1;
+
+ cf->excursion += delta;
+
+ if (cf->excursion > (i32) vec_len (cf->command_history) -1)
+ cf->excursion = 0;
+ else if (cf->excursion < 0)
+ cf->excursion = vec_len (cf->command_history) -1;
+
+ prev = cf->command_history [cf->excursion];
+ vec_validate (cf->current_command, vec_len(prev)-1);
+
+ clib_memcpy (cf->current_command, prev, vec_len(prev));
+ _vec_len (cf->current_command) = vec_len(prev);
+ unix_vlib_cli_output_cooked (cf, uf, cf->current_command,
+ vec_len (cf->current_command));
+ cf->cursor = vec_len(cf->current_command);
- clib_memcpy (cf->current_command, prev, vec_len(prev));
- _vec_len (cf->current_command) = vec_len(prev);
- unix_cli_add_pending_output (uf, cf, cf->current_command,
- vec_len (cf->current_command));
- break;
- }
break;
+ }
+ break;
- case 0x7f:
- case 'H' - '@':
- for (j = 0; j < 2; j++)
- unix_cli_add_pending_output (uf, cf, (u8 *) "\b \b", 3);
- if (vec_len (cf->current_command))
+ case UNIX_CLI_PARSE_ACTION_HOME:
+ if (vec_len (cf->current_command) && cf->cursor > 0)
+ {
+ while (cf->cursor)
{
- unix_cli_add_pending_output (uf, cf, (u8 *) "\b \b", 3);
- _vec_len (cf->current_command)--;
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
+ cf->cursor --;
}
- cf->search_mode = 0;
- cf->excursion = 0;
- cf->search_mode = 0;
- vec_reset_length (cf->search_key);
- break;
+ }
+
+ cf->search_mode = 0;
+ break;
- case '\r':
- case '\n':
- crlf:
- vec_add1 (cf->current_command, '\r');
- vec_add1 (cf->current_command, '\n');
- unix_cli_add_pending_output (uf, cf, (u8 *) "\b\b \b\b\r\n", 8);
+ case UNIX_CLI_PARSE_ACTION_END:
+ if (vec_len (cf->current_command) &&
+ cf->cursor < vec_len(cf->current_command))
+ {
+ unix_vlib_cli_output_cooked (cf, uf,
+ cf->current_command + cf->cursor,
+ vec_len(cf->current_command) - cf->cursor);
+ cf->cursor = vec_len(cf->current_command);
+ }
- vec_validate (cf->input_vector, vec_len(cf->current_command)-1);
- clib_memcpy (cf->input_vector, cf->current_command,
- vec_len(cf->current_command));
- _vec_len(cf->input_vector) = _vec_len (cf->current_command);
+ cf->search_mode = 0;
+ break;
- if (vec_len(cf->command_history) >= cf->history_limit)
+ case UNIX_CLI_PARSE_ACTION_WORDLEFT:
+ if (vec_len (cf->current_command) && cf->cursor > 0)
+ {
+ j = cf->cursor;
+
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
+ j --;
+
+ while (j && isspace(cf->current_command[j]))
{
- vec_free (cf->command_history[0]);
- vec_delete (cf->command_history, 1, 0);
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
+ j --;
}
- /* Don't add blank lines to the cmd history */
- if (vec_len (cf->current_command) > 2)
+ while (j && !isspace(cf->current_command[j]))
{
- _vec_len (cf->current_command) -= 2;
- vec_add1 (cf->command_history, cf->current_command);
- cf->current_command = 0;
+ if (isspace(cf->current_command[j - 1]))
+ break;
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
+ j --;
}
- else
- vec_reset_length (cf->current_command);
- cf->excursion = 0;
- cf->search_mode = 0;
- vec_reset_length (cf->search_key);
- return 0;
- /* telnet "mode character" blort, echo but don't process. */
- case 0xff:
- unix_cli_add_pending_output (uf, cf, cf->input_vector + i,
- 6);
- i += 6;
- continue;
+ cf->cursor = j;
+ }
- default:
- if (cf->search_mode)
- {
- int j, k, limit, offset;
- u8 * item;
+ cf->search_mode = 0;
+ break;
- vec_add1 (cf->search_key, cf->input_vector[i]);
+ case UNIX_CLI_PARSE_ACTION_WORDRIGHT:
+ if (vec_len (cf->current_command) &&
+ cf->cursor < vec_len(cf->current_command))
+ {
+ int e = vec_len(cf->current_command);
+ j = cf->cursor;
+ while (j < e && !isspace(cf->current_command[j]))
+ j ++;
+ while (j < e && isspace(cf->current_command[j]))
+ j ++;
+ unix_vlib_cli_output_cooked (cf, uf,
+ cf->current_command + cf->cursor,
+ j - cf->cursor);
+ cf->cursor = j;
+ }
+
+ cf->search_mode = 0;
+ break;
+
+
+ case UNIX_CLI_PARSE_ACTION_ERASE:
+ if (vec_len (cf->current_command))
+ {
+ if (cf->cursor == vec_len(cf->current_command))
+ {
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b \b", 3);
+ _vec_len (cf->current_command)--;
+ cf->cursor --;
+ }
+ else if (cf->cursor > 0)
+ {
+ /* shift everything at & to the right of the cursor left by 1 */
+ j = vec_len (cf->current_command) - cf->cursor;
+ memmove (cf->current_command + cf->cursor - 1,
+ cf->current_command + cf->cursor,
+ j);
+ _vec_len (cf->current_command)--;
+ cf->cursor --;
+ /* redraw the rest of the line */
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
+ unix_vlib_cli_output_cooked (cf, uf,
+ cf->current_command + cf->cursor, j);
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) " \b\b", 3);
+ /* and shift the terminal cursor back where it should be */
+ while (-- j)
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
+ }
+ }
+ cf->search_mode = 0;
+ cf->excursion = 0;
+ vec_reset_length (cf->search_key);
+ break;
- search_again:
- for (j = 0; j < vec_len(cf->command_history); j++)
+ case UNIX_CLI_PARSE_ACTION_ERASERIGHT:
+ if (vec_len (cf->current_command))
+ {
+ if (cf->cursor < vec_len(cf->current_command))
+ {
+ /* shift everything to the right of the cursor left by 1 */
+ j = vec_len (cf->current_command) - cf->cursor - 1;
+ memmove (cf->current_command + cf->cursor,
+ cf->current_command + cf->cursor + 1,
+ j);
+ _vec_len (cf->current_command)--;
+ /* redraw the rest of the line */
+ unix_vlib_cli_output_cooked (cf, uf,
+ cf->current_command + cf->cursor, j);
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) " \b", 2);
+ /* and shift the terminal cursor back where it should be */
+ if (j)
{
- if (cf->excursion > (i32) vec_len (cf->command_history) -1)
- cf->excursion = 0;
- else if (cf->excursion < 0)
- cf->excursion = vec_len (cf->command_history) -1;
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
+ while (-- j)
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
+ }
+ }
+ }
+ else if (input == 'D' - '@')
+ {
+ /* ^D with no command entered = quit */
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "quit\n", 5);
+ vlib_process_signal_event (um->vlib_main,
+ vlib_current_process (um->vlib_main),
+ UNIX_CLI_PROCESS_EVENT_QUIT,
+ cf - cm->cli_file_pool);
+ }
+ cf->search_mode = 0;
+ cf->excursion = 0;
+ vec_reset_length (cf->search_key);
+ break;
+
+ case UNIX_CLI_PARSE_ACTION_CLEAR:
+ /* If we're in ANSI mode, clear the screen.
+ * Then redraw the prompt and any existing command input, then put
+ * the cursor back where it was in that line.
+ */
+ if (cf->ansi_capable)
+ unix_vlib_cli_output_cooked (cf, uf,
+ (u8 *) ANSI_CLEAR,
+ sizeof(ANSI_CLEAR)-1);
+ else
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1);
+
+ unix_vlib_cli_output_raw (cf, uf,
+ cm->cli_prompt,
+ vec_len (cm->cli_prompt));
+ unix_vlib_cli_output_raw (cf, uf,
+ cf->current_command,
+ vec_len (cf->current_command));
+ for (j = cf->cursor; j < vec_len(cf->current_command); j++)
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
+
+ break;
+
+ case UNIX_CLI_PARSE_ACTION_CRLF:
+ crlf:
+ vec_add1 (cf->current_command, '\r');
+ vec_add1 (cf->current_command, '\n');
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1);
+
+ vec_validate (cf->input_vector, vec_len(cf->current_command)-1);
+ clib_memcpy (cf->input_vector, cf->current_command,
+ vec_len(cf->current_command));
+ _vec_len(cf->input_vector) = _vec_len (cf->current_command);
+
+ if (vec_len(cf->command_history) >= cf->history_limit)
+ {
+ vec_free (cf->command_history[0]);
+ vec_delete (cf->command_history, 1, 0);
+ }
+ /* Don't add blank lines to the cmd history */
+ if (vec_len (cf->current_command) > 2)
+ {
+ _vec_len (cf->current_command) -= 2;
+ vec_add1 (cf->command_history, cf->current_command);
+ cf->current_command = 0;
+ }
+ else
+ vec_reset_length (cf->current_command);
+ cf->excursion = 0;
+ cf->search_mode = 0;
+ vec_reset_length (cf->search_key);
+ cf->cursor = 0;
+
+ return 0;
+
+
+ default:
+ if (cf->search_mode && isprint(input))
+ {
+ int k, limit, offset;
+ u8 * item;
+
+ vec_add1 (cf->search_key, input);
+
+ search_again:
+ for (j = 0; j < vec_len(cf->command_history); j++)
+ {
+ if (cf->excursion > (i32) vec_len (cf->command_history) -1)
+ cf->excursion = 0;
+ else if (cf->excursion < 0)
+ cf->excursion = vec_len (cf->command_history) -1;
- item = cf->command_history[cf->excursion];
+ item = cf->command_history[cf->excursion];
- limit = (vec_len(cf->search_key) > vec_len (item)) ?
- vec_len(item) : vec_len (cf->search_key);
+ limit = (vec_len(cf->search_key) > vec_len (item)) ?
+ vec_len(item) : vec_len (cf->search_key);
- for (offset = 0; offset <= vec_len(item) - limit; offset++)
+ for (offset = 0; offset <= vec_len(item) - limit; offset++)
+ {
+ for (k = 0; k < limit; k++)
{
- for (k = 0; k < limit; k++)
- {
- if (item[k+offset] != cf->search_key[k])
- goto next_offset;
- }
- goto found_at_offset;
-
- next_offset:
- ;
+ if (item[k+offset] != cf->search_key[k])
+ goto next_offset;
}
- goto next;
+ goto found_at_offset;
+
+ next_offset:
+ ;
+ }
+ goto next;
+
+ found_at_offset:
+ for (j = 0; j < vec_len (cf->current_command); j++)
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b \b", 3);
+
+ vec_validate (cf->current_command, vec_len(item)-1);
- found_at_offset:
- for (j = 0; j < vec_len (cf->current_command)+1; j++)
- unix_cli_add_pending_output (uf, cf, (u8 *) "\b \b", 3);
+ clib_memcpy (cf->current_command, item, vec_len(item));
+ _vec_len (cf->current_command) = vec_len(item);
+ unix_vlib_cli_output_cooked (cf, uf, cf->current_command,
+ vec_len (cf->current_command));
+ cf->cursor = vec_len (cf->current_command);
+ goto found;
- vec_validate (cf->current_command, vec_len(item)-1);
+ next:
+ cf->excursion += cf->search_mode;
+ }
- clib_memcpy (cf->current_command, item, vec_len(item));
- _vec_len (cf->current_command) = vec_len(item);
- unix_cli_add_pending_output (uf, cf, cf->current_command,
- vec_len (cf->current_command));
- goto found;
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *)"\nNo match...", 12);
+ vec_reset_length (cf->search_key);
+ vec_reset_length (cf->current_command);
+ cf->search_mode = 0;
+ cf->cursor = 0;
+ goto crlf;
+ }
+ else
+ {
+ if (isprint(input)) /* skip any errant control codes */
+ {
+ if (cf->cursor == vec_len(cf->current_command))
+ {
+ /* Append to end */
+ vec_add1 (cf->current_command, input);
+ cf->cursor ++;
- next:
- cf->excursion += cf->search_mode;
+ /* Echo the character back to the client */
+ unix_vlib_cli_output_raw (cf, uf, &input, 1);
+ }
+ else
+ {
+ /* Insert at cursor: resize +1 byte, move everything over */
+ j = vec_len (cf->current_command) - cf->cursor;
+ vec_add1 (cf->current_command, (u8)'A');
+ memmove (cf->current_command + cf->cursor + 1,
+ cf->current_command + cf->cursor,
+ j);
+ cf->current_command[cf->cursor] = input;
+ /* Redraw the line */
+ j ++;
+ unix_vlib_cli_output_raw (cf, uf,
+ cf->current_command + cf->cursor, j);
+ /* Put terminal cursor back */
+ while (-- j)
+ unix_vlib_cli_output_raw (cf, uf, (u8 *)"\b", 1);
+ cf->cursor ++;
}
-
- unix_cli_add_pending_output (uf, cf, (u8 *)"\r\nno match..", 12);
- vec_reset_length (cf->search_key);
- vec_reset_length (cf->current_command);
- cf->search_mode = 0;
- goto crlf;
}
- else
- vec_add1 (cf->current_command, cf->input_vector[i]);
+ }
+
+ found:
+
+ break;
+ }
+ return 1;
+}
- found:
+/** \brief Process input bytes on a stream to provide line editing and
+ * command history in the CLI. */
+static int unix_cli_line_edit (unix_cli_main_t * cm,
+ unix_main_t * um,
+ unix_cli_file_t * cf)
+{
+ unix_file_t * uf = pool_elt_at_index (um->file_pool, cf->unix_file_index);
+ int i;
+ for (i = 0; i < vec_len (cf->input_vector); i++)
+ {
+ unix_cli_parse_action_t action;
+ /* See if the input buffer is some sort of control code */
+ i32 matched = 0;
+
+ action = unix_cli_match_action(&cf->input_vector[i],
+ vec_len (cf->input_vector) - i, &matched);
+
+ switch (action)
+ {
+ case UNIX_CLI_PARSE_ACTION_PARTIALMATCH:
+ if (i)
+ {
+ /* There was a partial match which means we need more bytes
+ * than the input buffer currently has.
+ * Since the bytes before here have been processed, shift
+ * the current contents to the start of the input buffer.
+ */
+ int j = vec_len (cf->input_vector) - i;
+ memmove(cf->input_vector, cf->input_vector + i, j);
+ _vec_len(cf->input_vector) = j;
+ }
+ return 1; /* wait for more */
+
+ case UNIX_CLI_PARSE_ACTION_TELNETIAC:
+ /* process telnet options */
+ matched = unix_cli_process_telnet(um, cf, uf,
+ cf->input_vector + i, vec_len(cf->input_vector) - i);
+ if (matched < 0)
+ {
+ if (i)
+ {
+ /* There was a partial match which means we need more bytes
+ * than the input buffer currently has.
+ * Since the bytes before here have been processed, shift
+ * the current contents to the start of the input buffer.
+ */
+ int j = vec_len (cf->input_vector) - i;
+ memmove(cf->input_vector, cf->input_vector + i, j);
+ _vec_len(cf->input_vector) = j;
+ }
+ return 1; /* wait for more */
+ }
break;
+
+ default:
+ /* process the action */
+ if (!unix_cli_line_process_one(cm, um, cf, uf,
+ cf->input_vector[i], action))
+ return 0; /* CRLF found */
}
+
+ i += matched;
}
+
vec_reset_length(cf->input_vector);
return 1;
}
-static void unix_cli_process_input (unix_cli_main_t * cm, uword cli_file_index)
+/** \brief Process input to a CLI session. */
+static void unix_cli_process_input (unix_cli_main_t * cm,
+ uword cli_file_index)
{
unix_main_t * um = &unix_main;
unix_file_t * uf;
@@ -383,7 +1170,7 @@ static void unix_cli_process_input (unix_cli_main_t * cm, uword cli_file_index)
goto done;
/* Line edit, echo, etc. */
- if (cf->has_history && unix_cli_line_edit (um, cf))
+ if (cf->has_history && unix_cli_line_edit (cm, um, cf))
return;
if (um->log_fd)
@@ -426,7 +1213,7 @@ done:
/* Prompt. */
uf = pool_elt_at_index (um->file_pool, cf->unix_file_index);
- unix_cli_add_pending_output (uf, cf,
+ unix_vlib_cli_output_raw (cf, uf,
cm->cli_prompt,
vec_len (cm->cli_prompt));
}
@@ -442,7 +1229,7 @@ static void unix_cli_kill (unix_cli_main_t * cm, uword cli_file_index)
uf = pool_elt_at_index (um->file_pool, cf->unix_file_index);
/* Quit/EOF on stdin means quit program. */
- if (uf->file_descriptor == 0)
+ if (uf->file_descriptor == UNIX_CLI_STDIN_FD)
clib_longjmp (&um->vlib_main->main_loop_exit, VLIB_MAIN_LOOP_EXIT_CLI);
vec_free (cf->current_command);
@@ -459,11 +1246,6 @@ static void unix_cli_kill (unix_cli_main_t * cm, uword cli_file_index)
pool_put (cm->cli_file_pool, cf);
}
-typedef enum {
- UNIX_CLI_PROCESS_EVENT_READ_READY,
- UNIX_CLI_PROCESS_EVENT_QUIT,
-} unix_cli_process_event_type_t;
-
static uword
unix_cli_process (vlib_main_t * vm,
vlib_node_runtime_t * rt,
@@ -568,7 +1350,7 @@ static u32 unix_cli_file_add (unix_cli_main_t * cm, char * name, int fd)
{
unix_main_t * um = &unix_main;
unix_cli_file_t * cf;
- unix_file_t * uf, template = {0};
+ unix_file_t template = {0};
vlib_main_t * vm = um->vlib_main;
vlib_node_t * n;
@@ -615,12 +1397,6 @@ static u32 unix_cli_file_add (unix_cli_main_t * cm, char * name, int fd)
cf->output_vector = 0;
cf->input_vector = 0;
- uf = pool_elt_at_index (um->file_pool, cf->unix_file_index);
-
- /* Prompt. */
- unix_cli_add_pending_output (uf, cf,
- cm->cli_prompt, vec_len (cm->cli_prompt));
-
vlib_start_process (vm, n->runtime_index);
return cf - cm->cli_file_pool;
}
@@ -653,28 +1429,39 @@ static clib_error_t * unix_cli_listen_read_ready (unix_file_t * uf)
/* if we're supposed to run telnet session in character mode (default) */
if (um->cli_line_mode == 0)
{
- u8 charmode_option[6];
+ /*
+ * Set telnet client character mode, echo on, suppress "go-ahead".
+ * Technically these should be negotiated, but this works.
+ */
+ u8 charmode_option[] = {
+ IAC, WONT, TELOPT_LINEMODE, /* server will do char-by-char */
+ IAC, DONT, TELOPT_LINEMODE, /* client should do char-by-char */
+ IAC, WILL, TELOPT_SGA, /* server willl supress GA */
+ IAC, DO, TELOPT_SGA, /* client should supress Go Ahead */
+ IAC, WILL, TELOPT_ECHO, /* server will do echo */
+ IAC, DONT, TELOPT_ECHO, /* client should not echo */
+ IAC, DO, TELOPT_TTYPE, /* client should tell us its term type */
+ IAC, SB, TELOPT_TTYPE, 1, IAC, SE, /* now tell me ttype */
+ };
+ /* Enable history on this CLI */
cf->has_history = 1;
- cf->history_limit = um->cli_history_limit ? um->cli_history_limit : 50;
+ cf->history_limit = um->cli_history_limit ?
+ um->cli_history_limit :
+ UNIX_CLI_DEFAULT_HISTORY;
- /*
- * Set telnet client character mode, echo on, suppress "go-ahead"
- * Empirically, this sequence works. YMMV.
- */
+ /* We need CRLF */
+ cf->crlf_mode = 1;
- /* Tell the client no linemode, echo */
- charmode_option[0] = IAC;
- charmode_option[1] = DONT;
- charmode_option[2] = TELOPT_LINEMODE;
- charmode_option[3] = IAC;
- charmode_option[4] = DO;
- charmode_option[5] = TELOPT_SGA;
-
uf = pool_elt_at_index (um->file_pool, cf->unix_file_index);
-
- unix_cli_add_pending_output (uf, cf, charmode_option,
+
+ /* Send the telnet options */
+ unix_vlib_cli_output_raw (cf, uf, charmode_option,
ARRAY_LEN(charmode_option));
+
+ /* In case the client doesn't negotiate terminal type, use
+ * a timer to kick off the initial prompt. */
+ timer_call (unix_cli_file_welcome_timer, cf_index, 1);
}
return error;
@@ -685,8 +1472,12 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input)
{
unix_main_t * um = &unix_main;
unix_cli_main_t * cm = &unix_cli_main;
- int flags, standard_input_fd;
+ int flags;
clib_error_t * error = 0;
+ unix_cli_file_t * cf;
+ u32 cf_index;
+ struct termios tio;
+ u8 * term;
/* We depend on unix flags being set. */
if ((error = vlib_call_config_function (vm, unix_config)))
@@ -694,14 +1485,44 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input)
if (um->flags & UNIX_FLAG_INTERACTIVE)
{
- standard_input_fd = 0;
-
/* Set stdin to be non-blocking. */
- if ((flags = fcntl (standard_input_fd, F_GETFL, 0)) < 0)
- flags = 0;
- fcntl (standard_input_fd, F_SETFL, flags | O_NONBLOCK);
+ if ((flags = fcntl (UNIX_CLI_STDIN_FD, F_GETFL, 0)) < 0)
+ flags = 0;
+ fcntl (UNIX_CLI_STDIN_FD, F_SETFL, flags | O_NONBLOCK);
+
+ cf_index = unix_cli_file_add (cm, "stdin", UNIX_CLI_STDIN_FD);
+ cf = pool_elt_at_index (cm->cli_file_pool, cf_index);
+
+ /* If stdin is a tty and we are using chacracter mode, enable
+ * history on the CLI and set the tty line discipline accordingly. */
+ if (isatty(UNIX_CLI_STDIN_FD) && um->cli_line_mode == 0)
+ {
+ cf->has_history = 1;
+ cf->history_limit = um->cli_history_limit ?
+ um->cli_history_limit :
+ UNIX_CLI_DEFAULT_HISTORY;
+
+ /* Save the original tty state so we can restore it later */
+ tcgetattr(UNIX_CLI_STDIN_FD, &um->tio_stdin);
+ um->tio_isset = 1;
+
+ /* Tweak the tty settings */
+ tio = um->tio_stdin;
+ /* echo off, canonical mode off, ext'd input processing off */
+ tio.c_lflag &= ~(ECHO | ICANON | IEXTEN);
+ tio.c_cc[VMIN] = 1; /* 1 byte at a time */
+ tio.c_cc[VTIME] = 0; /* no timer */
+ tcsetattr(UNIX_CLI_STDIN_FD, TCSAFLUSH, &tio);
+
+ /* See if we can do ANSI/VT100 output */
+ term = (u8 *)getenv("TERM");
+ if (term != NULL)
+ cf->ansi_capable = unix_cli_terminal_type(term,
+ strlen((char *)term));
+ }
- unix_cli_file_add (cm, "stdin", standard_input_fd);
+ /* Send banner and initial prompt */
+ unix_cli_file_welcome(cm, cf);
}
/* If we have socket config, LISTEN, otherwise, don't */
@@ -731,6 +1552,20 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input)
VLIB_CONFIG_FUNCTION (unix_cli_config, "unix-cli");
+static clib_error_t *
+unix_cli_exit (vlib_main_t * vm)
+{
+ unix_main_t * um = &unix_main;
+
+ /* If stdin is a tty and we saved the tty state, reset the tty state */
+ if (isatty(UNIX_CLI_STDIN_FD) && um->tio_isset)
+ tcsetattr(UNIX_CLI_STDIN_FD, TCSAFLUSH, &um->tio_stdin);
+
+ return 0;
+}
+
+VLIB_MAIN_LOOP_EXIT_FUNCTION (unix_cli_exit);
+
void vlib_unix_cli_set_prompt (char * prompt)
{
char * fmt = (prompt[strlen(prompt)-1] == ' ') ? "%s" : "%s ";
diff --git a/vlib/vlib/unix/unix.h b/vlib/vlib/unix/unix.h
index 44a8853e356..269b9df5975 100644
--- a/vlib/vlib/unix/unix.h
+++ b/vlib/vlib/unix/unix.h
@@ -41,6 +41,7 @@
#define included_unix_unix_h
#include <vppinfra/socket.h>
+#include <termios.h>
struct unix_file;
typedef clib_error_t * (unix_file_function_t) (struct unix_file * f);
@@ -102,10 +103,15 @@ typedef struct {
/* CLI log file. GIGO. */
u8 *log_filename;
int log_fd;
- /* Don't put telnet connections into character mode */
+ /* Don't put CLI connections into character mode */
int cli_line_mode;
+
+ /* Maximum amount of command line history to keep per session */
u32 cli_history_limit;
-
+
+ /* Store the original state of stdin when it's a tty */
+ struct termios tio_stdin;
+ int tio_isset;
} unix_main_t;
/* Global main structure. */