X-Git-Url: https://gerrit.fd.io/r/gitweb?p=vpp.git;a=blobdiff_plain;f=src%2Fvlib%2Funix%2Fcli.c;h=f9d6b40c4d5e9b89c2b35943834674b7fd56a742;hp=69fca6ec7bc02e28571a3cd04475ebf6a1f62b87;hb=8feeaff56;hpb=7cd468a3d7dee7d6c92f69a0bb7061ae208ec727 diff --git a/src/vlib/unix/cli.c b/src/vlib/unix/cli.c index 69fca6ec7bc..f9d6b40c4d5 100644 --- a/src/vlib/unix/cli.c +++ b/src/vlib/unix/cli.c @@ -57,6 +57,10 @@ #include #include #include +#include +#include +#include +#include /** ANSI escape code. */ #define ESC "\x1b" @@ -86,12 +90,17 @@ #define ANSI_RESTCURSOR CSI "u" /** Maximum depth into a byte stream from which to compile a Telnet - * protocol message. This is a saftey measure. */ + * protocol message. This is a safety measure. */ #define UNIX_CLI_MAX_DEPTH_TELNET 24 -/** Unix standard in */ -#define UNIX_CLI_STDIN_FD 0 - +/** Maximum terminal width we will accept */ +#define UNIX_CLI_MAX_TERMINAL_WIDTH 512 +/** Maximum terminal height we will accept */ +#define UNIX_CLI_MAX_TERMINAL_HEIGHT 512 +/** Default terminal height */ +#define UNIX_CLI_DEFAULT_TERMINAL_HEIGHT 24 +/** Default terminal width */ +#define UNIX_CLI_DEFAULT_TERMINAL_WIDTH 80 /** A CLI banner line. */ typedef struct @@ -139,7 +148,7 @@ typedef struct typedef struct { /** The file index held by unix.c */ - u32 unix_file_index; + u32 clib_file_index; /** Vector of output pending write to file descriptor. */ u8 *output_vector; @@ -189,6 +198,18 @@ typedef struct /** Disable the pager? */ u8 no_pager; + /** Whether the session is interactive or not. + * Controls things like initial banner, the CLI prompt etc. */ + u8 is_interactive; + + /** Whether the session is attached to a socket. */ + u8 is_socket; + + /** If EPIPE has been detected, prevent further write-related + * activity on the descriptor. + */ + u8 has_epipe; + /** Pager buffer */ u8 **pager_vector; @@ -206,6 +227,20 @@ typedef struct /** Process node identifier */ u32 process_node_index; + + /** The current direction of cursor travel. + * This is important since when advancing left-to-right, at the + * right hand edge of the console the terminal typically defers + * wrapping the cursor to the next line until a character is + * actually displayed. + * This messes up our heuristic for whether to use ANSI to return + * the cursor to the end of the line and instead we have to + * nudge the cursor to the next line. + * A Value of @c 0 means we're advancing left-to-right; @c 1 means + * the opposite. + */ + u8 cursor_direction; + } unix_cli_file_t; /** Resets the pager buffer and other data. @@ -500,11 +535,11 @@ unix_cli_match_action (unix_cli_parse_actions_t * a, * are available to be sent. */ static void -unix_cli_add_pending_output (unix_file_t * uf, +unix_cli_add_pending_output (clib_file_t * uf, unix_cli_file_t * cf, u8 * buffer, uword buffer_bytes) { - unix_main_t *um = &unix_main; + clib_file_main_t *fm = &file_main; vec_add (cf->output_vector, buffer, buffer_bytes); if (vec_len (cf->output_vector) > 0) @@ -512,7 +547,7 @@ unix_cli_add_pending_output (unix_file_t * uf, int skip_update = 0 != (uf->flags & UNIX_FILE_DATA_AVAILABLE_TO_WRITE); uf->flags |= UNIX_FILE_DATA_AVAILABLE_TO_WRITE; if (!skip_update) - um->file_update (uf, UNIX_FILE_UPDATE_MODIFY); + fm->file_update (uf, UNIX_FILE_UPDATE_MODIFY); } } @@ -520,10 +555,10 @@ unix_cli_add_pending_output (unix_file_t * uf, * that no more bytes are available to be sent. */ static void -unix_cli_del_pending_output (unix_file_t * uf, +unix_cli_del_pending_output (clib_file_t * uf, unix_cli_file_t * cf, uword n_bytes) { - unix_main_t *um = &unix_main; + clib_file_main_t *fm = &file_main; vec_delete (cf->output_vector, n_bytes, 0); if (vec_len (cf->output_vector) <= 0) @@ -531,7 +566,7 @@ unix_cli_del_pending_output (unix_file_t * uf, int skip_update = 0 == (uf->flags & UNIX_FILE_DATA_AVAILABLE_TO_WRITE); uf->flags &= ~UNIX_FILE_DATA_AVAILABLE_TO_WRITE; if (!skip_update) - um->file_update (uf, UNIX_FILE_UPDATE_MODIFY); + fm->file_update (uf, UNIX_FILE_UPDATE_MODIFY); } } @@ -546,7 +581,7 @@ unix_cli_del_pending_output (unix_file_t * uf, * @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 + * @return The index of the first occurrence of \c chr. If \c chr is not * found then \c len instead. */ always_inline word @@ -578,16 +613,37 @@ unix_vlib_findchr (u8 chr, u8 * str, word len) */ static void unix_vlib_cli_output_raw (unix_cli_file_t * cf, - unix_file_t * uf, u8 * buffer, uword buffer_bytes) + clib_file_t * uf, u8 * buffer, uword buffer_bytes) { int n = 0; + if (cf->has_epipe) /* don't try writing anything */ + return; + if (vec_len (cf->output_vector) == 0) - n = write (uf->file_descriptor, buffer, buffer_bytes); + { + if (cf->is_socket) + /* If it's a socket we use MSG_NOSIGNAL to prevent SIGPIPE */ + n = send (uf->file_descriptor, buffer, buffer_bytes, MSG_NOSIGNAL); + else + n = write (uf->file_descriptor, buffer, buffer_bytes); + } if (n < 0 && errno != EAGAIN) { - clib_unix_warning ("write"); + if (errno == EPIPE) + { + /* connection closed on us */ + unix_main_t *um = &unix_main; + cf->has_epipe = 1; + vlib_process_signal_event (um->vlib_main, cf->process_node_index, + UNIX_CLI_PROCESS_EVENT_QUIT, + uf->private_data); + } + else + { + clib_unix_warning ("write"); + } } else if ((word) n < (word) buffer_bytes) { @@ -608,7 +664,7 @@ unix_vlib_cli_output_raw (unix_cli_file_t * cf, */ static void unix_vlib_cli_output_cooked (unix_cli_file_t * cf, - unix_file_t * uf, + clib_file_t * uf, u8 * buffer, uword buffer_bytes) { word end = 0, start = 0; @@ -640,20 +696,85 @@ unix_vlib_cli_output_cooked (unix_cli_file_t * cf, start = end; } } + + /* Use the last character to determine the last direction of the cursor. */ + if (buffer_bytes > 0) + cf->cursor_direction = (buffer[buffer_bytes - 1] == (u8) '\b'); +} + +/** @brief Moves the terminal cursor one character to the left, with + * special handling when it reaches the left edge of the terminal window. + * + * Ordinarily we can simply send a '\b' to move the cursor left, however + * most terminals will not reverse-wrap to the end of the previous line + * if the cursor is in the left-most column. To counter this we must + * check the cursor position + prompt length modulo terminal width and + * if available use some other means, such as ANSI terminal escape + * sequences, to move the cursor. + * + * @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. + */ +static void +unix_vlib_cli_output_cursor_left (unix_cli_file_t * cf, clib_file_t * uf) +{ + unix_cli_main_t *cm = &unix_cli_main; + static u8 *ansi = 0; /* assumes no reentry */ + u32 position; + + if (!cf->is_interactive || !cf->ansi_capable || !cf->width) + { + /* No special handling for dumb terminals */ + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1); + return; + } + + position = ((u32) vec_len (cm->cli_prompt) + cf->cursor) % cf->width; + + if (position != 0) + { + /* No special handling required if we're not at the left edge */ + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1); + return; + } + + if (!cf->cursor_direction) + { + /* Special handling for when we are at the left edge but + * the cursor was going left-to-right, but in this situation + * xterm-like terminals actually hide the cursor off the right + * edge. A \b here seems to jump one char too many, so let's + * force the cursor onto the next line instead. + */ + if (cf->cursor < vec_len (cf->current_command)) + unix_vlib_cli_output_cooked (cf, uf, &cf->current_command[cf->cursor], + 1); + else + unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1); + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\r", 1); + } + + /* Relocate the cursor at the right hand edge one line above */ + ansi = format (ansi, CSI "A" CSI "%dC", cf->width - 1); + unix_vlib_cli_output_cooked (cf, uf, ansi, vec_len (ansi)); + vec_reset_length (ansi); /* keep the vec around for next time */ + cf->cursor_direction = 1; /* going backwards now */ } /** @brief Output the CLI prompt */ static void -unix_cli_cli_prompt (unix_cli_file_t * cf, unix_file_t * uf) +unix_cli_cli_prompt (unix_cli_file_t * cf, clib_file_t * uf) { unix_cli_main_t *cm = &unix_cli_main; - unix_vlib_cli_output_raw (cf, uf, cm->cli_prompt, vec_len (cm->cli_prompt)); + if (cf->is_interactive) /* Only interactive sessions get a prompt */ + unix_vlib_cli_output_raw (cf, uf, cm->cli_prompt, + vec_len (cm->cli_prompt)); } /** @brief Output a pager prompt and show number of buffered lines */ static void -unix_cli_pager_prompt (unix_cli_file_t * cf, unix_file_t * uf) +unix_cli_pager_prompt (unix_cli_file_t * cf, clib_file_t * uf) { u8 *prompt; u32 h; @@ -676,7 +797,7 @@ unix_cli_pager_prompt (unix_cli_file_t * cf, unix_file_t * uf) /** @brief Output a pager "skipping" message */ static void -unix_cli_pager_message (unix_cli_file_t * cf, unix_file_t * uf, +unix_cli_pager_message (unix_cli_file_t * cf, clib_file_t * uf, char *message, char *postfix) { u8 *prompt; @@ -692,7 +813,7 @@ unix_cli_pager_message (unix_cli_file_t * cf, unix_file_t * uf, /** @brief Erase the printed pager prompt */ static void -unix_cli_pager_prompt_erase (unix_cli_file_t * cf, unix_file_t * uf) +unix_cli_pager_prompt_erase (unix_cli_file_t * cf, clib_file_t * uf) { if (cf->ansi_capable) { @@ -714,7 +835,7 @@ unix_cli_pager_prompt_erase (unix_cli_file_t * cf, unix_file_t * uf) /** @brief Uses an ANSI escape sequence to move the cursor */ static void -unix_cli_ansi_cursor (unix_cli_file_t * cf, unix_file_t * uf, u16 x, u16 y) +unix_cli_ansi_cursor (unix_cli_file_t * cf, clib_file_t * uf, u16 x, u16 y) { u8 *str; @@ -730,7 +851,7 @@ unix_cli_ansi_cursor (unix_cli_file_t * cf, unix_file_t * uf, u16 x, u16 y) * @param uf Unix file of the CLI session. */ static void -unix_cli_pager_redraw (unix_cli_file_t * cf, unix_file_t * uf) +unix_cli_pager_redraw (unix_cli_file_t * cf, clib_file_t * uf) { unix_cli_pager_index_t *pi = NULL; u8 *line = NULL; @@ -778,7 +899,7 @@ unix_cli_pager_redraw (unix_cli_file_t * cf, unix_file_t * uf) * * If instead @c line is @c NULL then @c len_or_index is taken to mean the * index of an existing line in the pager buffer; this simply means that the - * input line does not need to be cloned since we alreayd have it. This is + * input line does not need to be cloned since we already have it. This is * typical if we are reindexing the pager buffer. * * @param cf The CLI session whose pager we are adding to. @@ -793,7 +914,7 @@ unix_cli_pager_redraw (unix_cli_file_t * cf, unix_file_t * uf) static void unix_cli_pager_add_line (unix_cli_file_t * cf, u8 * line, word len_or_index) { - u8 *p; + u8 *p = NULL; word i, j, k; word line_index, len; u32 width = cf->width; @@ -803,7 +924,8 @@ unix_cli_pager_add_line (unix_cli_file_t * cf, u8 * line, word len_or_index) { /* Use a line already in the pager buffer */ line_index = len_or_index; - p = cf->pager_vector[line_index]; + if (cf->pager_vector != NULL) + p = cf->pager_vector[line_index]; len = vec_len (p); } else @@ -928,12 +1050,13 @@ static void unix_vlib_cli_output (uword cli_file_index, u8 * buffer, uword buffer_bytes) { unix_main_t *um = &unix_main; + clib_file_main_t *fm = &file_main; unix_cli_main_t *cm = &unix_cli_main; unix_cli_file_t *cf; - unix_file_t *uf; + clib_file_t *uf; cf = pool_elt_at_index (cm->cli_file_pool, cli_file_index); - uf = pool_elt_at_index (um->file_pool, cf->unix_file_index); + uf = pool_elt_at_index (fm->file_pool, cf->clib_file_index); if (cf->no_pager || um->cli_pager_buffer_limit == 0 || cf->height == 0) { @@ -1012,7 +1135,7 @@ unix_vlib_cli_output (uword cli_file_index, u8 * buffer, uword buffer_bytes) * terminal sequences; @c 0 otherwise. */ static u8 -unix_cli_terminal_type (u8 * term, uword len) +unix_cli_terminal_type_ansi (u8 * term, uword len) { /* This may later be better done as a hash of some sort. */ #define _(a) do { \ @@ -1023,21 +1146,68 @@ unix_cli_terminal_type (u8 * term, uword len) _("xterm-color"); _("xterm-256color"); /* iTerm on Mac */ _("screen"); + _("screen-256color"); /* Screen and tmux */ _("ansi"); /* Microsoft Telnet */ #undef _ return 0; } +/** Identify whether a terminal type is non-interactive. + * + * Compares the string given in @c term with a list of terminal types known + * to be non-interactive, as send by tools such as @c vppctl . + * + * This list contains, for example, @c vppctl. + * + * @param term A string with a terminal type in it. + * @param len The length of the string in @c term. + * + * @return @c 1 if the terminal type is recognized as being non-interactive; + * @c 0 otherwise. + */ +static u8 +unix_cli_terminal_type_noninteractive (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) + + _("vppctl"); +#undef _ + + return 0; +} + +/** Set a session to be non-interactive. */ +static void +unix_cli_set_session_noninteractive (unix_cli_file_t * cf) +{ + /* Non-interactive sessions don't get these */ + cf->is_interactive = 0; + cf->no_pager = 1; + cf->history_limit = 0; + cf->has_history = 0; + cf->line_mode = 1; +} + /** @brief Emit initial welcome banner and 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); + clib_file_main_t *fm = &file_main; + clib_file_t *uf = pool_elt_at_index (fm->file_pool, cf->clib_file_index); unix_cli_banner_t *banner; int i, len; + /* Mark the session as started if we get here */ + cf->started = 1; + + if (!(cf->is_interactive)) /* No banner for non-interactive sessions */ + return; + /* * Put the first bytes directly into the buffer so that further output is * queued until everything is ready. (oterwise initial prompt can appear @@ -1068,7 +1238,6 @@ unix_cli_file_welcome (unix_cli_main_t * cm, unix_cli_file_t * cf) /* Prompt. */ unix_cli_cli_prompt (cf, uf); - cf->started = 1; } /** @brief A failsafe triggered on a timer to ensure we send the prompt @@ -1101,7 +1270,7 @@ unix_cli_file_welcome_timer (any arg, f64 delay) static i32 unix_cli_process_telnet (unix_main_t * um, unix_cli_file_t * cf, - unix_file_t * uf, u8 * input_vector, uword len) + clib_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. @@ -1126,7 +1295,7 @@ unix_cli_process_telnet (unix_main_t * um, case DO: case DONT: /* Expect 3 bytes */ - if (vec_len (input_vector) < 3) + if (len < 3) return -1; /* want more bytes */ consume = 2; @@ -1146,9 +1315,20 @@ unix_cli_process_telnet (unix_main_t * um, 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); + { + /* See if the the terminal type is recognized */ + u8 *term = input_vector + 4; + uword len = i - 5; + + /* See if the terminal type is ANSI capable */ + cf->ansi_capable = + unix_cli_terminal_type_ansi (term, len); + + /* See if the terminal type indicates non-interactive */ + if (unix_cli_terminal_type_noninteractive (term, len)) + unix_cli_set_session_noninteractive (cf); + } + /* If session not started, we can release the pause */ if (!cf->started) /* Send the welcome banner and initial prompt */ @@ -1159,10 +1339,21 @@ unix_cli_process_telnet (unix_main_t * um, /* Window size */ if (i != 8) /* check message is correct size */ break; + cf->width = clib_net_to_host_u16 (*((u16 *) (input_vector + 3))); + if (cf->width > UNIX_CLI_MAX_TERMINAL_WIDTH) + cf->width = UNIX_CLI_MAX_TERMINAL_WIDTH; + if (cf->width == 0) + cf->width = UNIX_CLI_DEFAULT_TERMINAL_WIDTH; + cf->height = clib_net_to_host_u16 (*((u16 *) (input_vector + 5))); + if (cf->height > UNIX_CLI_MAX_TERMINAL_HEIGHT) + cf->height = UNIX_CLI_MAX_TERMINAL_HEIGHT; + if (cf->height == 0) + cf->height = UNIX_CLI_DEFAULT_TERMINAL_HEIGHT; + /* reindex pager buffer */ unix_cli_pager_reindex (cf); /* redraw page */ @@ -1226,10 +1417,12 @@ 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, + clib_file_t * uf, u8 input, unix_cli_parse_action_t action) { u8 *prev; + u8 *save = 0; + u8 **possible_commands; int j, delta; switch (action) @@ -1244,16 +1437,20 @@ unix_cli_line_process_one (unix_cli_main_t * cm, 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); + for (; cf->cursor > 0; cf->cursor--) + { + unix_vlib_cli_output_cursor_left (cf, uf); + unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1); + unix_vlib_cli_output_cursor_left (cf, uf); + } vec_reset_length (cf->search_key); vec_reset_length (cf->current_command); + if (action == UNIX_CLI_PARSE_ACTION_REVSEARCH) cf->search_mode = -1; else cf->search_mode = 1; - cf->cursor = 0; } else { @@ -1270,27 +1467,29 @@ unix_cli_line_process_one (unix_cli_main_t * cm, case UNIX_CLI_PARSE_ACTION_ERASELINELEFT: /* Erase the command from the cursor to the start */ - /* Shimmy forwards to the new end of line position */ + j = cf->cursor; + /* Shimmy backwards 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); + for (; cf->cursor > delta; cf->cursor--) + unix_vlib_cli_output_cursor_left (cf, uf); /* Zap from here to the end of what is currently displayed */ - for (; j < (vec_len (cf->current_command)); j++) + for (; cf->cursor < vec_len (cf->current_command); cf->cursor++) 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); + for (; cf->cursor > 0; cf->cursor--) + unix_vlib_cli_output_cursor_left (cf, uf); - j = vec_len (cf->current_command) - cf->cursor; - memmove (cf->current_command, cf->current_command + cf->cursor, j); - _vec_len (cf->current_command) = j; + /* Delete the desired text from the command */ + memmove (cf->current_command, cf->current_command + j, delta); + _vec_len (cf->current_command) = delta; /* Print the new contents */ - unix_vlib_cli_output_cooked (cf, uf, cf->current_command, j); + unix_vlib_cli_output_cooked (cf, uf, cf->current_command, delta); + cf->cursor = delta; /* for backspace tracking */ + /* 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; + for (; cf->cursor > 0; cf->cursor--) + unix_vlib_cli_output_cursor_left (cf, uf); cf->search_mode = 0; break; @@ -1298,12 +1497,13 @@ unix_cli_line_process_one (unix_cli_main_t * cm, case UNIX_CLI_PARSE_ACTION_ERASELINERIGHT: /* Erase the command from the cursor to the end */ + j = cf->cursor; /* Zap from cursor to end of what is currently displayed */ - for (j = cf->cursor; j < (vec_len (cf->current_command)); j++) + for (; cf->cursor < (vec_len (cf->current_command)); cf->cursor++) 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); + for (; cf->cursor > j; cf->cursor--) + unix_vlib_cli_output_cursor_left (cf, uf); /* Truncate the line at the cursor */ _vec_len (cf->current_command) = cf->cursor; @@ -1314,7 +1514,7 @@ unix_cli_line_process_one (unix_cli_main_t * cm, case UNIX_CLI_PARSE_ACTION_LEFT: if (cf->cursor > 0) { - unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1); + unix_vlib_cli_output_cursor_left (cf, uf); cf->cursor--; } @@ -1339,10 +1539,14 @@ unix_cli_line_process_one (unix_cli_main_t * cm, break; cf->search_mode = 0; /* Erase the command */ - for (j = cf->cursor; j < (vec_len (cf->current_command)); j++) + for (; cf->cursor < vec_len (cf->current_command); cf->cursor++) 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); + for (; cf->cursor > 0; cf->cursor--) + { + unix_vlib_cli_output_cursor_left (cf, uf); + unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1); + unix_vlib_cli_output_cursor_left (cf, uf); + } vec_reset_length (cf->current_command); if (vec_len (cf->command_history)) { @@ -1379,20 +1583,15 @@ unix_cli_line_process_one (unix_cli_main_t * cm, unix_vlib_cli_output_cooked (cf, uf, cf->current_command, vec_len (cf->current_command)); } - cf->cursor = vec_len (cf->current_command); - - break; } + cf->cursor = vec_len (cf->current_command); break; case UNIX_CLI_PARSE_ACTION_HOME: if (vec_len (cf->current_command) && cf->cursor > 0) { - while (cf->cursor) - { - unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1); - cf->cursor--; - } + for (; cf->cursor > 0; cf->cursor--) + unix_vlib_cli_output_cursor_left (cf, uf); } cf->search_mode = 0; @@ -1415,25 +1614,22 @@ unix_cli_line_process_one (unix_cli_main_t * cm, 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--; + unix_vlib_cli_output_cursor_left (cf, uf); + cf->cursor--; - while (j && isspace (cf->current_command[j])) + while (cf->cursor && isspace (cf->current_command[cf->cursor])) { - unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1); - j--; + unix_vlib_cli_output_cursor_left (cf, uf); + cf->cursor--; } - while (j && !isspace (cf->current_command[j])) + while (cf->cursor && !isspace (cf->current_command[cf->cursor])) { - if (isspace (cf->current_command[j - 1])) + if (isspace (cf->current_command[cf->cursor - 1])) break; - unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1); - j--; + unix_vlib_cli_output_cursor_left (cf, uf); + cf->cursor--; } - cf->cursor = j; } cf->search_mode = 0; @@ -1464,9 +1660,13 @@ unix_cli_line_process_one (unix_cli_main_t * cm, { if (cf->cursor == vec_len (cf->current_command)) { - unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b \b", 3); - _vec_len (cf->current_command)--; + unix_vlib_cli_output_cursor_left (cf, uf); cf->cursor--; + unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1); + cf->cursor++; + unix_vlib_cli_output_cursor_left (cf, uf); + cf->cursor--; + _vec_len (cf->current_command)--; } else if (cf->cursor > 0) { @@ -1475,16 +1675,25 @@ unix_cli_line_process_one (unix_cli_main_t * cm, 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_cursor_left (cf, uf); + cf->cursor--; unix_vlib_cli_output_cooked (cf, uf, cf->current_command + cf->cursor, j); - unix_vlib_cli_output_cooked (cf, uf, (u8 *) " \b\b", 3); + cf->cursor += j; + /* erase last char */ + unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1); + cf->cursor++; + /* and shift the terminal cursor back where it should be */ + j += 2; /* account for old string length and offset position */ while (--j) - unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1); + { + unix_vlib_cli_output_cursor_left (cf, uf); + cf->cursor--; + } } } cf->search_mode = 0; @@ -1506,13 +1715,21 @@ unix_cli_line_process_one (unix_cli_main_t * cm, unix_vlib_cli_output_cooked (cf, uf, cf->current_command + cf->cursor, j); - unix_vlib_cli_output_cooked (cf, uf, (u8 *) " \b", 2); + cf->cursor += j; + unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1); + cf->cursor++; + unix_vlib_cli_output_cursor_left (cf, uf); + cf->cursor--; /* and shift the terminal cursor back where it should be */ if (j) { - unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1); + unix_vlib_cli_output_cursor_left (cf, uf); + cf->cursor--; while (--j) - unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1); + { + unix_vlib_cli_output_cursor_left (cf, uf); + cf->cursor--; + } } } } @@ -1544,15 +1761,167 @@ unix_cli_line_process_one (unix_cli_main_t * cm, 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); + unix_vlib_cli_output_cooked (cf, uf, + cf->current_command, + vec_len (cf->current_command)); + j = cf->cursor; + cf->cursor = vec_len (cf->current_command); + for (; cf->cursor > j; cf->cursor--) + unix_vlib_cli_output_cursor_left (cf, uf); break; case UNIX_CLI_PARSE_ACTION_TAB: + if (cf->cursor < vec_len (cf->current_command)) + { + /* if we are in the middle of a line, complete only if + * the cursor points to whitespace */ + if (isspace (cf->current_command[cf->cursor])) + { + /* save and clear any input that is after the cursor */ + vec_resize (save, vec_len (cf->current_command) - cf->cursor); + clib_memcpy (save, cf->current_command + cf->cursor, + vec_len (cf->current_command) - cf->cursor); + _vec_len (cf->current_command) = cf->cursor; + } + else + { + unix_vlib_cli_output_raw (cf, uf, (u8 *) "\a", 1); + break; + } + } + possible_commands = + vlib_cli_get_possible_completions (cf->current_command); + if (vec_len (possible_commands) == 1) + { + u8 *completed = possible_commands[0]; + j = cf->cursor; + + /* find the last word of current_command */ + while (j >= 1 && !isspace (cf->current_command[j - 1])) + { + unix_vlib_cli_output_cursor_left (cf, uf); + cf->cursor--; + j--; + } + _vec_len (cf->current_command) = j; + + /* replace it with the newly expanded command */ + vec_append (cf->current_command, completed); + + /* echo to the terminal */ + unix_vlib_cli_output_cooked (cf, uf, completed, + vec_len (completed)); + + /* add one trailing space if needed */ + if (vec_len (save) == 0) + { + vec_add1 (cf->current_command, ' '); + unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1); + } + + cf->cursor = vec_len (cf->current_command); + + } + else if (vec_len (possible_commands) >= 2) + { + u8 **possible_command; + uword max_command_len = 0, min_command_len = ~0; + u32 i; + + vec_foreach (possible_command, possible_commands) + { + if (vec_len (*possible_command) > max_command_len) + max_command_len = vec_len (*possible_command); + if (vec_len (*possible_command) < min_command_len) + min_command_len = vec_len (*possible_command); + } + + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1); + + i = 0; + vec_foreach (possible_command, possible_commands) + { + if (i + max_command_len >= cf->width) + { + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1); + i = 0; + } + unix_vlib_cli_output_cooked (cf, uf, *possible_command, + vec_len (*possible_command)); + for (j = vec_len (*possible_command); j < max_command_len + 2; + j++) + { + unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1); + } + i += max_command_len + 2; + } + + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1); + + /* rewrite prompt */ + unix_cli_cli_prompt (cf, uf); + unix_vlib_cli_output_cooked (cf, uf, cf->current_command, + vec_len (cf->current_command)); + + /* count length of last word */ + j = cf->cursor; + i = 0; + while (j >= 1 && !isspace (cf->current_command[j - 1])) + { + j--; + i++; + } + + /* determine smallest common command */ + for (; i < min_command_len; i++) + { + u8 common = '\0'; + int stop = 0; + + vec_foreach (possible_command, possible_commands) + { + if (common == '\0') + { + common = (*possible_command)[i]; + } + else if (common != (*possible_command)[i]) + { + stop = 1; + break; + } + } + + if (!stop) + { + vec_add1 (cf->current_command, common); + cf->cursor++; + unix_vlib_cli_output_cooked (cf, uf, (u8 *) & common, 1); + } + else + { + break; + } + } + } + else + { + unix_vlib_cli_output_raw (cf, uf, (u8 *) "\a", 1); + } + + if (vec_len (save) > 0) + { + /* restore remaining input if tab was hit in the middle of a line */ + unix_vlib_cli_output_cooked (cf, uf, save, vec_len (save)); + cf->cursor += vec_len (save); + for (j = 0; j < vec_len (save); j++, cf->cursor--) + unix_vlib_cli_output_cursor_left (cf, uf); + vec_append (cf->current_command, save); + vec_free (save); + } + vec_free (possible_commands); + + break; case UNIX_CLI_PARSE_ACTION_YANK: /* TODO */ break; @@ -1568,7 +1937,7 @@ unix_cli_line_process_one (unix_cli_main_t * cm, case UNIX_CLI_PARSE_ACTION_PAGER_NEXT: case UNIX_CLI_PARSE_ACTION_PAGER_PGDN: /* show next page of the buffer */ - if (cf->height + cf->pager_start < vec_len (cf->pager_index)) + if (cf->height + cf->pager_start <= vec_len (cf->pager_index)) { u8 *line = NULL; unix_cli_pager_index_t *pi = NULL; @@ -1599,7 +1968,7 @@ unix_cli_line_process_one (unix_cli_main_t * cm, case UNIX_CLI_PARSE_ACTION_PAGER_DN: case UNIX_CLI_PARSE_ACTION_PAGER_CRLF: /* display the next line of the buffer */ - if (cf->pager_start < vec_len (cf->pager_index) - (cf->height - 1)) + if (cf->height + cf->pager_start <= vec_len (cf->pager_index)) { u8 *line; unix_cli_pager_index_t *pi; @@ -1798,7 +2167,7 @@ unix_cli_line_process_one (unix_cli_main_t * cm, { /* no-op for now */ } - else if (cf->has_history && cf->search_mode && isprint (input)) + else if (cf->has_history && cf->search_mode != 0 && isprint (input)) { int k, limit, offset; u8 *item; @@ -1833,8 +2202,12 @@ unix_cli_line_process_one (unix_cli_main_t * cm, 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); + for (; cf->cursor > 0; cf->cursor--) + { + unix_vlib_cli_output_cursor_left (cf, uf); + unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1); + unix_vlib_cli_output_cursor_left (cf, uf); + } vec_validate (cf->current_command, vec_len (item) - 1); clib_memcpy (cf->current_command, item, vec_len (item)); @@ -1865,7 +2238,7 @@ unix_cli_line_process_one (unix_cli_main_t * cm, cf->cursor++; /* Echo the character back to the client */ - unix_vlib_cli_output_raw (cf, uf, &input, 1); + unix_vlib_cli_output_cooked (cf, uf, &input, 1); } else { @@ -1877,12 +2250,14 @@ unix_cli_line_process_one (unix_cli_main_t * cm, cf->current_command[cf->cursor] = input; /* Redraw the line */ j++; - unix_vlib_cli_output_raw (cf, uf, - cf->current_command + cf->cursor, j); + unix_vlib_cli_output_cooked (cf, uf, + cf->current_command + cf->cursor, + j); + cf->cursor += j; + j--; /* Put terminal cursor back */ - while (--j) - unix_vlib_cli_output_raw (cf, uf, (u8 *) "\b", 1); - cf->cursor++; + for (; j > 0; j--, cf->cursor--) + unix_vlib_cli_output_cursor_left (cf, uf); } } else @@ -1903,10 +2278,10 @@ unix_cli_line_process_one (unix_cli_main_t * cm, /** @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_cli_line_edit (unix_cli_main_t * cm, unix_main_t * um, + clib_file_main_t * fm, unix_cli_file_t * cf) { - unix_file_t *uf = pool_elt_at_index (um->file_pool, cf->unix_file_index); + clib_file_t *uf = pool_elt_at_index (fm->file_pool, cf->clib_file_index); int i; for (i = 0; i < vec_len (cf->input_vector); i++) @@ -1946,10 +2321,12 @@ unix_cli_line_edit (unix_cli_main_t * cm, vec_len (cf->input_vector) - i); if (matched < 0) { + /* There was a partial match which means we need more bytes + * than the input buffer currently has. + */ 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 remaining contents to the start of the input buffer. */ @@ -1960,6 +2337,16 @@ unix_cli_line_edit (unix_cli_main_t * cm, break; default: + /* If telnet option processing switched us to line mode, get us + * out of here! + */ + if (cf->line_mode) + { + vec_delete (cf->input_vector, i, 0); + cf->current_command = cf->input_vector; + return 0; + } + /* process the action */ if (!unix_cli_line_process_one (cm, um, cf, uf, cf->input_vector[i], action)) @@ -1983,16 +2370,20 @@ 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; + clib_file_main_t *fm = &file_main; + clib_file_t *uf; unix_cli_file_t *cf = pool_elt_at_index (cm->cli_file_pool, cli_file_index); unformat_input_t input; int vlib_parse_eval (u8 *); + cm->current_input_file_index = cli_file_index; + more: /* Try vlibplex first. Someday... */ if (0 && vlib_parse_eval (cf->input_vector) == 0) goto done; + if (cf->line_mode) { /* just treat whatever we got as a complete line of input */ @@ -2001,7 +2392,7 @@ more: else { /* Line edit, echo, etc. */ - if (unix_cli_line_edit (cm, um, cf)) + if (unix_cli_line_edit (cm, um, fm, cf)) /* want more input */ return; } @@ -2013,20 +2404,16 @@ more: lv = format (lv, "%U[%d]: %v", format_timeval, 0 /* current bat-time */ , 0 /* current bat-format */ , - cli_file_index, cf->input_vector); - { - int rv __attribute__ ((unused)) = - write (um->log_fd, lv, vec_len (lv)); - } + cli_file_index, cf->current_command); + int rv __attribute__ ((unused)) = write (um->log_fd, lv, vec_len (lv)); } - /* Copy our input command to a new string */ + /* Build an unformat structure around our command */ unformat_init_vector (&input, cf->current_command); /* Remove leading white space from input. */ (void) unformat (&input, ""); - cm->current_input_file_index = cli_file_index; cf->pager_start = 0; /* start a new pager session */ if (unformat_check_input (&input) != UNFORMAT_END_OF_INPUT) @@ -2040,14 +2427,19 @@ more: /* Re-fetch pointer since pool may have moved. */ cf = pool_elt_at_index (cm->cli_file_pool, cli_file_index); - uf = pool_elt_at_index (um->file_pool, cf->unix_file_index); + uf = pool_elt_at_index (fm->file_pool, cf->clib_file_index); done: /* reset vector; we'll re-use it later */ if (cf->line_mode) - vec_reset_length (cf->input_vector); + { + vec_reset_length (cf->input_vector); + cf->current_command = 0; + } else - vec_reset_length (cf->current_command); + { + vec_reset_length (cf->current_command); + } if (cf->no_pager == 2) { @@ -2074,6 +2466,15 @@ done: /* Any residual data in the input vector? */ if (vec_len (cf->input_vector)) goto more; + + /* For non-interactive sessions send a NUL byte. + * Specifically this is because vppctl needs to see some traffic in + * order to move on to closing the session. Commands with no output + * would thus cause vppctl to hang indefinitely in non-interactive mode + * since there is also no prompt sent after the command completes. + */ + if (!cf->is_interactive) + unix_vlib_cli_output_raw (cf, uf, (u8 *) "\0", 1); } /** Destroy a CLI session. @@ -2084,15 +2485,20 @@ static void unix_cli_kill (unix_cli_main_t * cm, uword cli_file_index) { unix_main_t *um = &unix_main; + clib_file_main_t *fm = &file_main; unix_cli_file_t *cf; - unix_file_t *uf; + clib_file_t *uf; int i; + /* Validate cli_file_index */ + if (pool_is_free_index (cm->cli_file_pool, cli_file_index)) + return; + cf = pool_elt_at_index (cm->cli_file_pool, cli_file_index); - uf = pool_elt_at_index (um->file_pool, cf->unix_file_index); + uf = pool_elt_at_index (fm->file_pool, cf->clib_file_index); /* Quit/EOF on stdin means quit program. */ - if (uf->file_descriptor == UNIX_CLI_STDIN_FD) + if (uf->file_descriptor == STDIN_FILENO) clib_longjmp (&um->vlib_main->main_loop_exit, VLIB_MAIN_LOOP_EXIT_CLI); vec_free (cf->current_command); @@ -2103,7 +2509,7 @@ unix_cli_kill (unix_cli_main_t * cm, uword cli_file_index) vec_free (cf->command_history); - unix_file_del (um, uf); + clib_file_del (fm, uf); unix_cli_file_free (cf); pool_put (cm->cli_file_pool, cf); @@ -2155,7 +2561,7 @@ done: /** Called when a CLI session file descriptor can be written to without * blocking. */ static clib_error_t * -unix_cli_write_ready (unix_file_t * uf) +unix_cli_write_ready (clib_file_t * uf) { unix_cli_main_t *cm = &unix_cli_main; unix_cli_file_t *cf; @@ -2164,11 +2570,30 @@ unix_cli_write_ready (unix_file_t * uf) cf = pool_elt_at_index (cm->cli_file_pool, uf->private_data); /* Flush output vector. */ - n = write (uf->file_descriptor, - cf->output_vector, vec_len (cf->output_vector)); + if (cf->is_socket) + /* If it's a socket we use MSG_NOSIGNAL to prevent SIGPIPE */ + n = send (uf->file_descriptor, + cf->output_vector, vec_len (cf->output_vector), MSG_NOSIGNAL); + else + n = write (uf->file_descriptor, + cf->output_vector, vec_len (cf->output_vector)); if (n < 0 && errno != EAGAIN) - return clib_error_return_unix (0, "write"); + { + if (errno == EPIPE) + { + /* connection closed on us */ + unix_main_t *um = &unix_main; + cf->has_epipe = 1; + vlib_process_signal_event (um->vlib_main, cf->process_node_index, + UNIX_CLI_PROCESS_EVENT_QUIT, + uf->private_data); + } + else + { + return clib_error_return_unix (0, "write"); + } + } else if (n > 0) unix_cli_del_pending_output (uf, cf, n); @@ -2178,7 +2603,7 @@ unix_cli_write_ready (unix_file_t * uf) /** Called when a CLI session file descriptor has data to be read. */ static clib_error_t * -unix_cli_read_ready (unix_file_t * uf) +unix_cli_read_ready (clib_file_t * uf) { unix_main_t *um = &unix_main; unix_cli_main_t *cm = &unix_cli_main; @@ -2215,6 +2640,24 @@ unix_cli_read_ready (unix_file_t * uf) return /* no error */ 0; } +/** Called when a CLI session file descriptor has an error condition. */ +static clib_error_t * +unix_cli_error_detected (clib_file_t * uf) +{ + unix_main_t *um = &unix_main; + unix_cli_main_t *cm = &unix_cli_main; + unix_cli_file_t *cf; + + cf = pool_elt_at_index (cm->cli_file_pool, uf->private_data); + cf->has_epipe = 1; /* prevent writes while the close is pending */ + vlib_process_signal_event (um->vlib_main, + cf->process_node_index, + UNIX_CLI_PROCESS_EVENT_QUIT, + /* event data */ uf->private_data); + + return /* no error */ 0; +} + /** Store a new CLI session. * @param name The name of the session. * @param fd The file descriptor for the session I/O. @@ -2224,21 +2667,41 @@ static u32 unix_cli_file_add (unix_cli_main_t * cm, char *name, int fd) { unix_main_t *um = &unix_main; + clib_file_main_t *fm = &file_main; unix_cli_file_t *cf; - unix_file_t template = { 0 }; + clib_file_t template = { 0 }; vlib_main_t *vm = um->vlib_main; - vlib_node_t *n; + vlib_node_t *n = 0; + u8 *file_desc = 0; + + file_desc = format (0, "%s", name); name = (char *) format (0, "unix-cli-%s", name); if (vec_len (cm->unused_cli_process_node_indices) > 0) { uword l = vec_len (cm->unused_cli_process_node_indices); + int i; + vlib_main_t *this_vlib_main; + u8 *old_name = 0; - /* Find node and give it new name. */ - n = vlib_get_node (vm, cm->unused_cli_process_node_indices[l - 1]); - vec_free (n->name); - n->name = (u8 *) name; + /* + * Nodes are bulk-copied, so node name pointers are shared. + * Find the cli node in all graph replicas, and give all of them + * the same new name. + * Then, throw away the old shared name-vector. + */ + for (i = 0; i < vec_len (vlib_mains); i++) + { + this_vlib_main = vlib_mains[i]; + if (this_vlib_main == 0) + continue; + n = vlib_get_node (this_vlib_main, + cm->unused_cli_process_node_indices[l - 1]); + old_name = n->name; + n->name = (u8 *) name; + } + vec_free (old_name); vlib_node_set_state (vm, n->index, VLIB_NODE_STATE_POLLING); @@ -2253,22 +2716,29 @@ unix_cli_file_add (unix_cli_main_t * cm, char *name, int fd) }; r.name = name; + + vlib_worker_thread_barrier_sync (vm); + vlib_register_node (vm, &r); vec_free (name); n = vlib_get_node (vm, r.index); + vlib_worker_thread_node_runtime_update (); + vlib_worker_thread_barrier_release (vm); } pool_get (cm->cli_file_pool, cf); - memset (cf, 0, sizeof (*cf)); + clib_memset (cf, 0, sizeof (*cf)); template.read_function = unix_cli_read_ready; template.write_function = unix_cli_write_ready; + template.error_function = unix_cli_error_detected; template.file_descriptor = fd; template.private_data = cf - cm->cli_file_pool; + template.description = file_desc; cf->process_node_index = n->index; - cf->unix_file_index = unix_file_add (um, &template); + cf->clib_file_index = clib_file_add (fm, &template); cf->output_vector = 0; cf->input_vector = 0; @@ -2283,9 +2753,10 @@ unix_cli_file_add (unix_cli_main_t * cm, char *name, int fd) /** Telnet listening socket has a new connection. */ static clib_error_t * -unix_cli_listen_read_ready (unix_file_t * uf) +unix_cli_listen_read_ready (clib_file_t * uf) { unix_main_t *um = &unix_main; + clib_file_main_t *fm = &file_main; unix_cli_main_t *cm = &unix_cli_main; clib_socket_t *s = &um->cli_listen_socket; clib_socket_t client; @@ -2293,19 +2764,25 @@ unix_cli_listen_read_ready (unix_file_t * uf) clib_error_t *error; unix_cli_file_t *cf; u32 cf_index; + int one; error = clib_socket_accept (s, &client); if (error) return error; + /* Disable Nagle, ignore any errors doing so eg on PF_LOCAL socket */ + one = 1; + (void) setsockopt (client.fd, IPPROTO_TCP, TCP_NODELAY, + (void *) &one, sizeof (one)); + client_name = (char *) format (0, "%U%c", format_sockaddr, &client.peer, 0); cf_index = unix_cli_file_add (cm, client_name, client.fd); cf = pool_elt_at_index (cm->cli_file_pool, cf_index); + cf->is_socket = 1; /* No longer need CLIB version of socket. */ clib_socket_free (&client); - vec_free (client_name); /* if we're supposed to run telnet session in character mode (default) */ @@ -2332,6 +2809,9 @@ unix_cli_listen_read_ready (unix_file_t * uf) cf->history_limit = um->cli_history_limit; cf->has_history = cf->history_limit != 0; + /* This is an interactive session until we decide otherwise */ + cf->is_interactive = 1; + /* Make sure this session is in line mode */ cf->line_mode = 0; @@ -2341,9 +2821,14 @@ unix_cli_listen_read_ready (unix_file_t * uf) /* Setup the pager */ cf->no_pager = um->cli_no_pager; - uf = pool_elt_at_index (um->file_pool, cf->unix_file_index); + /* Default terminal dimensions, should the terminal + * fail to provide any. + */ + cf->width = UNIX_CLI_DEFAULT_TERMINAL_WIDTH; + cf->height = UNIX_CLI_DEFAULT_TERMINAL_HEIGHT; /* Send the telnet options */ + uf = pool_elt_at_index (fm->file_pool, cf->clib_file_index); unix_vlib_cli_output_raw (cf, uf, charmode_option, ARRAY_LEN (charmode_option)); @@ -2361,24 +2846,34 @@ unix_cli_listen_read_ready (unix_file_t * uf) static void unix_cli_resize_interrupt (int signum) { - unix_main_t *um = &unix_main; + clib_file_main_t *fm = &file_main; unix_cli_main_t *cm = &unix_cli_main; unix_cli_file_t *cf = pool_elt_at_index (cm->cli_file_pool, cm->stdin_cli_file_index); - unix_file_t *uf = pool_elt_at_index (um->file_pool, cf->unix_file_index); + clib_file_t *uf = pool_elt_at_index (fm->file_pool, cf->clib_file_index); struct winsize ws; (void) signum; /* Terminal resized, fetch the new size */ - if (ioctl (UNIX_CLI_STDIN_FD, TIOCGWINSZ, &ws) < 0) + if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) < 0) { /* "Should never happen..." */ clib_unix_warning ("TIOCGWINSZ"); /* We can't trust ws.XXX... */ return; } + cf->width = ws.ws_col; + if (cf->width > UNIX_CLI_MAX_TERMINAL_WIDTH) + cf->width = UNIX_CLI_MAX_TERMINAL_WIDTH; + if (cf->width == 0) + cf->width = UNIX_CLI_DEFAULT_TERMINAL_WIDTH; + cf->height = ws.ws_row; + if (cf->height > UNIX_CLI_MAX_TERMINAL_HEIGHT) + cf->height = UNIX_CLI_MAX_TERMINAL_HEIGHT; + if (cf->height == 0) + cf->height = UNIX_CLI_DEFAULT_TERMINAL_HEIGHT; /* Reindex the pager buffer */ unix_cli_pager_reindex (cf); @@ -2392,6 +2887,7 @@ static clib_error_t * unix_cli_config (vlib_main_t * vm, unformat_input_t * input) { unix_main_t *um = &unix_main; + clib_file_main_t *fm = &file_main; unix_cli_main_t *cm = &unix_cli_main; int flags; clib_error_t *error = 0; @@ -2409,32 +2905,38 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input) if (um->flags & UNIX_FLAG_INTERACTIVE) { /* Set stdin to be non-blocking. */ - if ((flags = fcntl (UNIX_CLI_STDIN_FD, F_GETFL, 0)) < 0) + if ((flags = fcntl (STDIN_FILENO, F_GETFL, 0)) < 0) flags = 0; - (void) fcntl (UNIX_CLI_STDIN_FD, F_SETFL, flags | O_NONBLOCK); + (void) fcntl (STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); - cf_index = unix_cli_file_add (cm, "stdin", UNIX_CLI_STDIN_FD); + cf_index = unix_cli_file_add (cm, "stdin", STDIN_FILENO); cf = pool_elt_at_index (cm->cli_file_pool, cf_index); cm->stdin_cli_file_index = 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) + if (isatty (STDIN_FILENO) && um->cli_line_mode == 0) { /* Capture terminal resize events */ - memset (&sa, 0, sizeof (sa)); + clib_memset (&sa, 0, sizeof (sa)); sa.sa_handler = unix_cli_resize_interrupt; if (sigaction (SIGWINCH, &sa, 0) < 0) clib_panic ("sigaction"); /* Retrieve the current terminal size */ - ioctl (UNIX_CLI_STDIN_FD, TIOCGWINSZ, &ws); + ioctl (STDIN_FILENO, TIOCGWINSZ, &ws); cf->width = ws.ws_col; cf->height = ws.ws_row; if (cf->width == 0 || cf->height == 0) - /* We have a tty, but no size. Stick to line mode. */ - goto notty; + { + /* + * We have a tty, but no size. Use defaults. + * vpp "unix interactive" inside emacs + gdb ends up here. + */ + cf->width = UNIX_CLI_DEFAULT_TERMINAL_WIDTH; + cf->height = UNIX_CLI_DEFAULT_TERMINAL_HEIGHT; + } /* Setup the history */ cf->history_limit = um->cli_history_limit; @@ -2443,36 +2945,42 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input) /* Setup the pager */ cf->no_pager = um->cli_no_pager; + /* This is an interactive session until we decide otherwise */ + cf->is_interactive = 1; + /* We're going to be in char by char mode */ cf->line_mode = 0; /* Save the original tty state so we can restore it later */ - tcgetattr (UNIX_CLI_STDIN_FD, &um->tio_stdin); + tcgetattr (STDIN_FILENO, &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); + /* disable XON/XOFF, so ^S invokes the history search */ + tio.c_iflag &= ~(IXON | IXOFF); tio.c_cc[VMIN] = 1; /* 1 byte at a time */ tio.c_cc[VTIME] = 0; /* no timer */ - tcsetattr (UNIX_CLI_STDIN_FD, TCSAFLUSH, &tio); + tio.c_cc[VSTOP] = _POSIX_VDISABLE; /* not ^S */ + tio.c_cc[VSTART] = _POSIX_VDISABLE; /* not ^Q */ + tcsetattr (STDIN_FILENO, 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)); + { + int len = strlen ((char *) term); + cf->ansi_capable = unix_cli_terminal_type_ansi (term, len); + if (unix_cli_terminal_type_noninteractive (term, len)) + unix_cli_set_session_noninteractive (cf); + } } else { - notty: - /* No tty, so make sure these things are off */ - cf->no_pager = 1; - cf->history_limit = 0; - cf->has_history = 0; - cf->line_mode = 1; + /* No tty, so make sure the session doesn't have tty-like features */ + unix_cli_set_session_noninteractive (cf); } /* Send banner and initial prompt */ @@ -2484,9 +2992,25 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input) if (s->config && s->config[0] != 0) { /* CLI listen. */ - unix_file_t template = { 0 }; + clib_file_t template = { 0 }; + + /* mkdir of file socketu, only under /run */ + if (strncmp (s->config, "/run", 4) == 0) + { + u8 *tmp = format (0, "%s", s->config); + int i = vec_len (tmp); + while (i && tmp[--i] != '/') + ; - s->flags = SOCKET_IS_SERVER; /* listen, don't connect */ + tmp[i] = 0; + + if (i) + vlib_unix_recursive_mkdir ((char *) tmp); + vec_free (tmp); + } + + s->flags = CLIB_SOCKET_F_IS_SERVER | /* listen, don't connect */ + CLIB_SOCKET_F_ALLOW_GROUP_WRITE; /* PF_LOCAL socket only */ error = clib_socket_init (s); if (error) @@ -2494,8 +3018,9 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input) template.read_function = unix_cli_listen_read_ready; template.file_descriptor = s->fd; + template.description = format (0, "cli listener %s", s->config); - unix_file_add (um, &template); + clib_file_add (fm, &template); } /* Set CLI prompt. */ @@ -2519,8 +3044,8 @@ 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); + if (isatty (STDIN_FILENO) && um->tio_isset) + tcsetattr (STDIN_FILENO, TCSAFLUSH, &um->tio_stdin); return 0; } @@ -2551,6 +3076,12 @@ unix_cli_quit (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { unix_cli_main_t *cm = &unix_cli_main; + unix_cli_file_t *cf = pool_elt_at_index (cm->cli_file_pool, + cm->current_input_file_index); + + /* Cosmetic: suppress the final prompt from appearing before we die */ + cf->is_interactive = 0; + cf->started = 1; vlib_process_signal_event (vm, vlib_current_process (vm), @@ -2573,6 +3104,14 @@ VLIB_CLI_COMMAND (unix_cli_quit_command, static) = { }; /* *INDENT-ON* */ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (unix_cli_q_command, static) = { + .path = "q", + .short_help = "Exit CLI", + .function = unix_cli_quit, +}; +/* *INDENT-ON* */ + /** CLI command to execute a VPP command script. */ static clib_error_t * unix_cli_exec (vlib_main_t * vm, @@ -2618,7 +3157,7 @@ unix_cli_exec (vlib_main_t * vm, } } - unformat_init_unix_file (&sub_input, fd); + unformat_init_clib_file (&sub_input, fd); vlib_cli_input (vm, &sub_input, 0, 0); unformat_free (&sub_input); @@ -2632,16 +3171,32 @@ done: } /*? - * Executes a sequence of CLI commands which are read from a file. - * - * If a command is unrecognised or otherwise invalid then the usual CLI + * Executes a sequence of CLI commands which are read from a file. If + * a command is unrecognised or otherwise invalid then the usual CLI * feedback will be generated, however execution of subsequent commands * from the file will continue. + * + * The VPP code is indifferent to the file location. However, if SELinux + * is enabled, then the file needs to have an SELinux label the VPP + * process is allowed to access. For example, if a file is created in + * '/usr/share/vpp/', it will be allowed. However, files manually + * created in '/tmp/' or '/home//' will not be accessible by the VPP + * process when SELinux is enabled. + * + * @cliexpar + * Sample file: + * @clistart + * $ cat /usr/share/vpp/scripts/gigup.txt + * set interface state GigabitEthernet0/8/0 up + * set interface state GigabitEthernet0/9/0 up + * @cliend + * Example of how to execute a set of CLI commands from a file: + * @cliexcmd{exec /usr/share/vpp/scripts/gigup.txt} ?*/ /* *INDENT-OFF* */ VLIB_CLI_COMMAND (cli_exec, static) = { .path = "exec", - .short_help = "Execute commands from file", + .short_help = "exec ", .function = unix_cli_exec, .is_mp_safe = 1, }; @@ -2716,12 +3271,55 @@ done: /* *INDENT-OFF* */ VLIB_CLI_COMMAND (cli_unix_show_errors, static) = { - .path = "show unix-errors", + .path = "show unix errors", .short_help = "Show Unix system call error history", .function = unix_show_errors, }; /* *INDENT-ON* */ +/** CLI command to show various unix error statistics. */ +static clib_error_t * +unix_show_files (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + clib_error_t *error = 0; + clib_file_main_t *fm = &file_main; + clib_file_t *f; + char path[PATH_MAX]; + u8 *s = 0; + + vlib_cli_output (vm, "%3s %6s %12s %12s %12s %-32s %s", "FD", "Thread", + "Read", "Write", "Error", "File Name", "Description"); + + /* *INDENT-OFF* */ + pool_foreach (f, fm->file_pool,( + { + int rv; + s = format (s, "/proc/self/fd/%d%c", f->file_descriptor, 0); + rv = readlink((char *) s, path, PATH_MAX - 1); + + path[rv < 0 ? 0 : rv] = 0; + + vlib_cli_output (vm, "%3d %6d %12d %12d %12d %-32s %v", + f->file_descriptor, f->polling_thread_index, + f->read_events, f->write_events, f->error_events, + path, f->description); + vec_reset_length (s); + })); + /* *INDENT-ON* */ + vec_free (s); + + return error; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (cli_unix_show_files, static) = { + .path = "show unix files", + .short_help = "Show Unix files in use", + .function = unix_show_files, +}; +/* *INDENT-ON* */ + /** CLI command to show session command history. */ static clib_error_t * unix_cli_show_history (vlib_main_t * vm, @@ -2733,6 +3331,9 @@ unix_cli_show_history (vlib_main_t * vm, cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index); + if (!cf->is_interactive) + return clib_error_return (0, "invalid for non-interactive sessions"); + if (cf->has_history && cf->history_limit) { i = 1 + cf->command_number - vec_len (cf->command_history); @@ -2778,6 +3379,8 @@ unix_cli_show_terminal (vlib_main_t * vm, vlib_cli_output (vm, "Terminal height: %d\n", cf->height); vlib_cli_output (vm, "ANSI capable: %s\n", cf->ansi_capable ? "yes" : "no"); + vlib_cli_output (vm, "Interactive: %s\n", + cf->is_interactive ? "yes" : "no"); vlib_cli_output (vm, "History enabled: %s%s\n", cf->has_history ? "yes" : "no", !cf->has_history || cf->history_limit ? "" : @@ -2810,6 +3413,7 @@ unix_cli_show_terminal (vlib_main_t * vm, * Terminal width: 123 * Terminal height: 48 * ANSI capable: yes + * Interactive: yes * History enabled: yes * History limit: 50 * Pager enabled: yes @@ -2825,6 +3429,86 @@ VLIB_CLI_COMMAND (cli_unix_cli_show_terminal, static) = { }; /* *INDENT-ON* */ +/** CLI command to display a list of CLI sessions. */ +static clib_error_t * +unix_cli_show_cli_sessions (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + unix_cli_main_t *cm = &unix_cli_main; + clib_file_main_t *fm = &file_main; + unix_cli_file_t *cf; + clib_file_t *uf; + vlib_node_t *n; + + vlib_cli_output (vm, "%-5s %-5s %-20s %s", "PNI", "FD", "Name", "Flags"); + +#define fl(x, y) ( (x) ? toupper((y)) : tolower((y)) ) + /* *INDENT-OFF* */ + pool_foreach (cf, cm->cli_file_pool, ({ + uf = pool_elt_at_index (fm->file_pool, cf->clib_file_index); + n = vlib_get_node (vm, cf->process_node_index); + vlib_cli_output (vm, + "%-5d %-5d %-20v %c%c%c%c%c\n", + cf->process_node_index, + uf->file_descriptor, + n->name, + fl (cf->is_interactive, 'i'), + fl (cf->is_socket, 's'), + fl (cf->line_mode, 'l'), + fl (cf->has_epipe, 'p'), + fl (cf->ansi_capable, 'a')); + })); + /* *INDENT-ON* */ +#undef fl + + return 0; +} + +/*? + * Displays a summary of all the current CLI sessions. + * + * Typically used to diagnose connection issues with the CLI + * socket. + * + * @cliexpar + * @cliexstart{show cli-sessions} + * PNI FD Name Flags + * 343 0 unix-cli-stdin IslpA + * 344 7 unix-cli-local:20 ISlpA + * 346 8 unix-cli-local:21 iSLpa + * @cliexend + + * In this example we have the debug console of the running process + * on stdin/out, we have an interactive socket session and we also + * have a non-interactive socket session. + * + * Fields: + * + * - @em PNI: Process node index. + * - @em FD: Unix file descriptor. + * - @em Name: Name of the session. + * - @em Flags: Various flags that describe the state of the session. + * + * @em Flags have the following meanings; lower-case typically negates + * upper-case: + * + * - @em I Interactive session. + * - @em S Connected by socket. + * - @em s Not a socket, likely stdin. + * - @em L Line-by-line mode. + * - @em l Char-by-char mode. + * - @em P EPIPE detected on connection; it will close soon. + * - @em A ANSI-capable terminal. +?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (cli_unix_cli_show_cli_sessions, static) = { + .path = "show cli-sessions", + .short_help = "Show current CLI sessions", + .function = unix_cli_show_cli_sessions, +}; +/* *INDENT-ON* */ + /** CLI command to set terminal pager settings. */ static clib_error_t * unix_cli_set_terminal_pager (vlib_main_t * vm, @@ -2835,12 +3519,16 @@ unix_cli_set_terminal_pager (vlib_main_t * vm, unix_cli_main_t *cm = &unix_cli_main; unix_cli_file_t *cf; unformat_input_t _line_input, *line_input = &_line_input; + clib_error_t *error = 0; + + cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index); + + if (!cf->is_interactive) + return clib_error_return (0, "invalid for non-interactive sessions"); if (!unformat_user (input, unformat_line_input, line_input)) return 0; - cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index); - while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) { if (unformat (line_input, "on")) @@ -2852,13 +3540,17 @@ unix_cli_set_terminal_pager (vlib_main_t * vm, "Pager limit set to %u lines; note, this is global.\n", um->cli_pager_buffer_limit); else - return clib_error_return (0, "unknown parameter: `%U`", - format_unformat_error, line_input); + { + error = clib_error_return (0, "unknown parameter: `%U`", + format_unformat_error, line_input); + goto done; + } } +done: unformat_free (line_input); - return 0; + return error; } /*? @@ -2886,12 +3578,16 @@ unix_cli_set_terminal_history (vlib_main_t * vm, unix_cli_file_t *cf; unformat_input_t _line_input, *line_input = &_line_input; u32 limit; + clib_error_t *error = 0; + + cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index); + + if (!cf->is_interactive) + return clib_error_return (0, "invalid for non-interactive sessions"); if (!unformat_user (input, unformat_line_input, line_input)) return 0; - cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index); - while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) { if (unformat (line_input, "on")) @@ -2901,8 +3597,11 @@ unix_cli_set_terminal_history (vlib_main_t * vm, else if (unformat (line_input, "limit %u", &cf->history_limit)) ; else - return clib_error_return (0, "unknown parameter: `%U`", - format_unformat_error, line_input); + { + error = clib_error_return (0, "unknown parameter: `%U`", + format_unformat_error, line_input); + goto done; + } /* If we reduced history size, or turned it off, purge the history */ limit = cf->has_history ? cf->history_limit : 0; @@ -2914,9 +3613,10 @@ unix_cli_set_terminal_history (vlib_main_t * vm, } } +done: unformat_free (line_input); - return 0; + return error; } /*? @@ -2945,6 +3645,9 @@ unix_cli_set_terminal_ansi (vlib_main_t * vm, cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index); + if (!cf->is_interactive) + return clib_error_return (0, "invalid for non-interactive sessions"); + if (unformat (input, "on")) cf->ansi_capable = 1; else if (unformat (input, "off"))