X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=src%2Fvlib%2Funix%2Fcli.c;h=56afbc2089b8fa93bb331ae930310831deaf8c9b;hb=b7b929931a07fbb27b43d5cd105f366c3e29807e;hp=3eb1d9ee86c966d8f1411ac4313c4e43ec56d0f7;hpb=59b2565cd91a67ced650739f36129650830211ac;p=vpp.git diff --git a/src/vlib/unix/cli.c b/src/vlib/unix/cli.c index 3eb1d9ee86c..56afbc2089b 100644 --- a/src/vlib/unix/cli.c +++ b/src/vlib/unix/cli.c @@ -59,6 +59,8 @@ #include #include #include +#include +#include /** ANSI escape code. */ #define ESC "\x1b" @@ -88,18 +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 -/** Minimum terminal width we will accept */ -#define UNIX_CLI_MIN_TERMINAL_WIDTH 1 /** Maximum terminal width we will accept */ #define UNIX_CLI_MAX_TERMINAL_WIDTH 512 -/** Minimum terminal height we will accept */ -#define UNIX_CLI_MIN_TERMINAL_HEIGHT 1 /** 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 @@ -226,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. @@ -681,6 +696,69 @@ 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 */ @@ -836,7 +914,7 @@ unix_cli_pager_redraw (unix_cli_file_t * cf, clib_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; @@ -846,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 @@ -1216,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; @@ -1265,15 +1344,15 @@ unix_cli_process_telnet (unix_main_t * um, 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 < UNIX_CLI_MIN_TERMINAL_WIDTH) - cf->width = UNIX_CLI_MIN_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 < UNIX_CLI_MIN_TERMINAL_HEIGHT) - cf->height = UNIX_CLI_MIN_TERMINAL_HEIGHT; + if (cf->height == 0) + cf->height = UNIX_CLI_DEFAULT_TERMINAL_HEIGHT; /* reindex pager buffer */ unix_cli_pager_reindex (cf); @@ -1358,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 { @@ -1384,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; @@ -1412,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; @@ -1428,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--; } @@ -1453,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)) { @@ -1500,11 +1590,8 @@ unix_cli_line_process_one (unix_cli_main_t * cm, 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; @@ -1527,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; @@ -1576,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) { @@ -1587,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; @@ -1618,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--; + } } } } @@ -1656,11 +1761,13 @@ 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; @@ -1687,14 +1794,15 @@ unix_cli_line_process_one (unix_cli_main_t * cm, vlib_cli_get_possible_completions (cf->current_command); if (vec_len (possible_commands) == 1) { - u32 j = cf->cursor; 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--; - unix_vlib_cli_output_raw (cf, uf, (u8 *) "\b", 1); } _vec_len (cf->current_command) = j; @@ -1702,13 +1810,14 @@ unix_cli_line_process_one (unix_cli_main_t * cm, vec_append (cf->current_command, completed); /* echo to the terminal */ - unix_vlib_cli_output_raw (cf, uf, completed, vec_len (completed)); + 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_raw (cf, uf, (u8 *) " ", 1); + unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1); } cf->cursor = vec_len (cf->current_command); @@ -1718,18 +1827,14 @@ unix_cli_line_process_one (unix_cli_main_t * cm, { u8 **possible_command; uword max_command_len = 0, min_command_len = ~0; - u32 i, j; + u32 i; vec_foreach (possible_command, possible_commands) { if (vec_len (*possible_command) > max_command_len) - { - max_command_len = vec_len (*possible_command); - } + max_command_len = vec_len (*possible_command); if (vec_len (*possible_command) < min_command_len) - { - min_command_len = vec_len (*possible_command); - } + min_command_len = vec_len (*possible_command); } unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1); @@ -1742,12 +1847,12 @@ unix_cli_line_process_one (unix_cli_main_t * cm, unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1); i = 0; } - unix_vlib_cli_output_raw (cf, uf, *possible_command, - vec_len (*possible_command)); + 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_raw (cf, uf, (u8 *) " ", 1); + unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1); } i += max_command_len + 2; } @@ -1756,8 +1861,8 @@ unix_cli_line_process_one (unix_cli_main_t * cm, /* rewrite prompt */ unix_cli_cli_prompt (cf, uf); - unix_vlib_cli_output_raw (cf, uf, cf->current_command, - vec_len (cf->current_command)); + unix_vlib_cli_output_cooked (cf, uf, cf->current_command, + vec_len (cf->current_command)); /* count length of last word */ j = cf->cursor; @@ -1773,6 +1878,7 @@ unix_cli_line_process_one (unix_cli_main_t * cm, { u8 common = '\0'; int stop = 0; + vec_foreach (possible_command, possible_commands) { if (common == '\0') @@ -1785,11 +1891,12 @@ unix_cli_line_process_one (unix_cli_main_t * cm, break; } } + if (!stop) { vec_add1 (cf->current_command, common); cf->cursor++; - unix_vlib_cli_output_raw (cf, uf, (u8 *) & common, 1); + unix_vlib_cli_output_cooked (cf, uf, (u8 *) & common, 1); } else { @@ -1805,11 +1912,10 @@ unix_cli_line_process_one (unix_cli_main_t * cm, if (vec_len (save) > 0) { /* restore remaining input if tab was hit in the middle of a line */ - unix_vlib_cli_output_raw (cf, uf, save, vec_len (save)); - for (j = 0; j < vec_len (save); j++) - { - unix_vlib_cli_output_raw (cf, uf, (u8 *) "\b", 1); - } + 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); } @@ -1831,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; @@ -1862,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; @@ -2061,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; @@ -2096,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)); @@ -2128,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 { @@ -2140,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 @@ -2378,6 +2490,10 @@ unix_cli_kill (unix_cli_main_t * cm, uword cli_file_index) 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 (fm->file_pool, cf->clib_file_index); @@ -2555,18 +2671,37 @@ unix_cli_file_add (unix_cli_main_t * cm, char *name, int fd) unix_cli_file_t *cf; 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); @@ -2581,20 +2716,26 @@ 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->clib_file_index = clib_file_add (fm, &template); @@ -2623,11 +2764,17 @@ unix_cli_listen_read_ready (clib_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); @@ -2674,6 +2821,12 @@ unix_cli_listen_read_ready (clib_file_t * uf) /* Setup the pager */ cf->no_pager = um->cli_no_pager; + /* 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, @@ -2713,14 +2866,14 @@ unix_cli_resize_interrupt (int signum) cf->width = ws.ws_col; if (cf->width > UNIX_CLI_MAX_TERMINAL_WIDTH) cf->width = UNIX_CLI_MAX_TERMINAL_WIDTH; - if (cf->width < UNIX_CLI_MIN_TERMINAL_WIDTH) - cf->width = UNIX_CLI_MIN_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 < UNIX_CLI_MIN_TERMINAL_HEIGHT) - cf->height = UNIX_CLI_MIN_TERMINAL_HEIGHT; + if (cf->height == 0) + cf->height = UNIX_CLI_DEFAULT_TERMINAL_HEIGHT; /* Reindex the pager buffer */ unix_cli_pager_reindex (cf); @@ -2765,7 +2918,7 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input) 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"); @@ -2781,8 +2934,8 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input) * We have a tty, but no size. Use defaults. * vpp "unix interactive" inside emacs + gdb ends up here. */ - cf->width = 80; - cf->height = 24; + cf->width = UNIX_CLI_DEFAULT_TERMINAL_WIDTH; + cf->height = UNIX_CLI_DEFAULT_TERMINAL_HEIGHT; } /* Setup the history */ @@ -2806,8 +2959,12 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input) 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 */ + 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 */ @@ -2861,6 +3018,7 @@ 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); clib_file_add (fm, &template); } @@ -2946,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, @@ -3005,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, }; @@ -3089,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, @@ -3210,7 +3435,6 @@ unix_cli_show_cli_sessions (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { - //unix_main_t *um = &unix_main; unix_cli_main_t *cm = &unix_cli_main; clib_file_main_t *fm = &file_main; unix_cli_file_t *cf;