From 7afda3aee55bb25a274c3d1da48589a82d8c6d3b Mon Sep 17 00:00:00 2001 From: Chris Luke Date: Mon, 25 Apr 2016 13:49:22 -0400 Subject: CLI output pager Buffers output from CLI commands as it arrives and allows the terminal to view this buffer one page at a time. - View of the buffer is moved with space/enter/up/down/page-up/page-down/ home/end. - At the end of the CLI command if less than one page of output was generated then the pager is not engaged. - 'q' to quit the pager, or scroll off the bottom. - Pager prompt displays the current view aperture line numbers and total number of lines buffered. - Can be disabled at runtime with "no-cli-pager" in the unix configuration. - The number of lines that will be stored in the buffer is limited to prevent excessive memory use; this limit is configurable with "cli-pager-buffer-limit" in the unix configuration. - Both these options can also be set in the console with "set terminal pager [on|off] [limit ]". Limitations: - Does not yet implement a search function. - Whilst the terminal size is detected and tracked, changing the terminal size when the pager is being used will not cause a redraw of the page. - Lines that wrap the right most column are not yet handled gracefully. Change-Id: I69548c1464eff79c53e122668f25758266daf1c2 Signed-off-by: Chris Luke --- vlib/vlib/unix/cli.c | 807 ++++++++++++++++++++++++++++++++++++++++++++------ vlib/vlib/unix/main.c | 16 + vlib/vlib/unix/unix.h | 6 + 3 files changed, 744 insertions(+), 85 deletions(-) diff --git a/vlib/vlib/unix/cli.c b/vlib/vlib/unix/cli.c index c6b8cd2b449..91dfd117caa 100644 --- a/vlib/vlib/unix/cli.c +++ b/vlib/vlib/unix/cli.c @@ -45,8 +45,10 @@ #include #include #include +#include #include #include +#include /* ANSI Escape code. */ #define ESC "\x1b" @@ -55,21 +57,22 @@ #define CSI ESC "[" /* ANSI sequences. */ -#define ANSI_CLEAR CSI "2J" CSI "1;1H" -#define ANSI_RESET CSI "0m" -#define ANSI_BOLD CSI "1m" -#define ANSI_DIM CSI "2m" -#define ANSI_DRED ANSI_DIM CSI "31m" -#define ANSI_BRED ANSI_BOLD CSI "31m" -#define ANSI_CLEAR CSI "2J" CSI "1;1H" +#define ANSI_CLEAR CSI "2J" CSI "1;1H" +#define ANSI_RESET CSI "0m" +#define ANSI_BOLD CSI "1m" +#define ANSI_DIM CSI "2m" +#define ANSI_DRED ANSI_DIM CSI "31m" +#define ANSI_BRED ANSI_BOLD CSI "31m" +#define ANSI_CLEAR CSI "2J" CSI "1;1H" +#define ANSI_CLEARLINE CSI "2K" +#define ANSI_SCROLLDN CSI "1T" +#define ANSI_SAVECURSOR CSI "s" +#define ANSI_RESTCURSOR CSI "u" /** 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 @@ -127,6 +130,9 @@ typedef struct { /* Position of the insert cursor on the current input line */ u32 cursor; + /* Line mode or char mode */ + u8 line_mode; + /* Set if the CRLF mode wants CR + LF */ u8 crlf_mode; @@ -136,14 +142,44 @@ typedef struct { /* Has the session started? */ u8 started; + /* Disable the pager? */ + u8 no_pager; + + /* Pager buffer */ + u8 ** pager_vector; + + /* Lines currently displayed */ + u32 pager_lines; + + /* Line number of top of page */ + u32 pager_start; + + /* Terminal size */ + u32 width, height; + u32 process_node_index; } unix_cli_file_t; +always_inline void +unix_cli_pager_reset (unix_cli_file_t *f) +{ + u8 ** p; + + f->pager_lines = f->pager_start = 0; + vec_foreach (p, f->pager_vector) + { + vec_free(*p); + } + vec_free(f->pager_vector); + f->pager_vector = 0; +} + always_inline void unix_cli_file_free (unix_cli_file_t * f) { vec_free (f->output_vector); vec_free (f->input_vector); + unix_cli_pager_reset(f); } /* CLI actions */ @@ -169,6 +205,17 @@ typedef enum { UNIX_CLI_PARSE_ACTION_YANK, UNIX_CLI_PARSE_ACTION_TELNETIAC, + UNIX_CLI_PARSE_ACTION_PAGER_CRLF, + UNIX_CLI_PARSE_ACTION_PAGER_QUIT, + UNIX_CLI_PARSE_ACTION_PAGER_NEXT, + UNIX_CLI_PARSE_ACTION_PAGER_DN, + UNIX_CLI_PARSE_ACTION_PAGER_UP, + UNIX_CLI_PARSE_ACTION_PAGER_TOP, + UNIX_CLI_PARSE_ACTION_PAGER_BOTTOM, + UNIX_CLI_PARSE_ACTION_PAGER_PGDN, + UNIX_CLI_PARSE_ACTION_PAGER_PGUP, + UNIX_CLI_PARSE_ACTION_PAGER_SEARCH, + UNIX_CLI_PARSE_ACTION_PARTIALMATCH, UNIX_CLI_PARSE_ACTION_NOMATCH } unix_cli_parse_action_t; @@ -247,6 +294,41 @@ static unix_cli_parse_actions_t unix_cli_parse_strings[] = { _( "\0", UNIX_CLI_PARSE_ACTION_NOACTION ), /* NUL */ _( NULL, UNIX_CLI_PARSE_ACTION_NOMATCH ) }; +static unix_cli_parse_actions_t unix_cli_parse_pager[] = { + /* Line handling */ + _( "\r\n", UNIX_CLI_PARSE_ACTION_PAGER_CRLF ), /* Must be before '\r' */ + _( "\n", UNIX_CLI_PARSE_ACTION_PAGER_CRLF ), + _( "\r\0", UNIX_CLI_PARSE_ACTION_PAGER_CRLF ), /* Telnet does this */ + _( "\r", UNIX_CLI_PARSE_ACTION_PAGER_CRLF ), + + /* Pager commands */ + _( " ", UNIX_CLI_PARSE_ACTION_PAGER_NEXT ), + _( "q", UNIX_CLI_PARSE_ACTION_PAGER_QUIT ), + _( "/", UNIX_CLI_PARSE_ACTION_PAGER_SEARCH ), + + /* VT100 */ + _( CSI "A", UNIX_CLI_PARSE_ACTION_PAGER_UP ), + _( CSI "B", UNIX_CLI_PARSE_ACTION_PAGER_DN ), + _( CSI "H", UNIX_CLI_PARSE_ACTION_PAGER_TOP ), + _( CSI "F", UNIX_CLI_PARSE_ACTION_PAGER_BOTTOM ), + + /* VT100 Application mode */ + _( ESC "OA", UNIX_CLI_PARSE_ACTION_PAGER_UP ), + _( ESC "OB", UNIX_CLI_PARSE_ACTION_PAGER_DN ), + _( ESC "OH", UNIX_CLI_PARSE_ACTION_PAGER_TOP ), + _( ESC "OF", UNIX_CLI_PARSE_ACTION_PAGER_BOTTOM ), + + /* ANSI X3.41-1974 */ + _( CSI "1~", UNIX_CLI_PARSE_ACTION_PAGER_TOP ), + _( CSI "4~", UNIX_CLI_PARSE_ACTION_PAGER_BOTTOM ), + _( CSI "5~", UNIX_CLI_PARSE_ACTION_PAGER_PGUP ), + _( CSI "6~", UNIX_CLI_PARSE_ACTION_PAGER_PGDN ), + + /* 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 { @@ -262,6 +344,9 @@ typedef struct { u32 * unused_cli_process_node_indices; + /* The session index of the stdin cli */ + u32 stdin_cli_file_index; + /* File pool index of current input. */ u32 current_input_file_index; } unix_cli_main_t; @@ -278,6 +363,7 @@ static unix_cli_main_t unix_cli_main; * partial match was found or a complete match was found and what action, * if any, should be taken. * + * @param a Actions list to search within. * @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 @@ -291,9 +377,9 @@ static unix_cli_main_t unix_cli_main; * match at all. */ static unix_cli_parse_action_t -unix_cli_match_action(u8 *input, u32 ilen, i32 *matched) +unix_cli_match_action(unix_cli_parse_actions_t *a, + u8 *input, u32 ilen, i32 *matched) { - unix_cli_parse_actions_t *a = unix_cli_parse_strings; u8 partial = 0; while (a->input) @@ -467,6 +553,81 @@ static void unix_vlib_cli_output_cooked(unix_cli_file_t * cf, } } +/** \brief Output the CLI prompt */ +static void unix_cli_cli_prompt(unix_cli_file_t * cf, unix_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)); +} + +/** \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) +{ + u8 * prompt; + + prompt = format(0, "\r%s-- more -- (%d-%d/%d)%s", + cf->ansi_capable ? ANSI_BOLD : "", + cf->pager_start + 1, + cf->pager_start + cf->height, + cf->pager_lines, + cf->ansi_capable ? ANSI_RESET: ""); + + unix_vlib_cli_output_cooked(cf, uf, prompt, vec_len(prompt)); + + vec_free(prompt); +} + +/** \brief Output a pager "skipping" message */ +static void unix_cli_pager_message(unix_cli_file_t * cf, unix_file_t * uf, + char *message, char *postfix) +{ + u8 * prompt; + + prompt = format(0, "\r%s-- %s --%s%s", + cf->ansi_capable ? ANSI_BOLD : "", + message, + cf->ansi_capable ? ANSI_RESET: "", + postfix); + + unix_vlib_cli_output_cooked(cf, uf, prompt, vec_len(prompt)); + + vec_free(prompt); +} + +/** \brief Erase the printed pager prompt */ +static void unix_cli_pager_prompt_erase(unix_cli_file_t * cf, unix_file_t * uf) +{ + if (cf->ansi_capable) + { + unix_vlib_cli_output_cooked(cf, uf, (u8 *)"\r", 1); + unix_vlib_cli_output_cooked(cf, uf, + (u8 *)ANSI_CLEARLINE, sizeof(ANSI_CLEARLINE) - 1); + } + else + { + int i; + + unix_vlib_cli_output_cooked(cf, uf, (u8 *)"\r", 1); + for (i = 0; i < cf->width - 1; i ++) + unix_vlib_cli_output_cooked(cf, uf, (u8 *)" ", 1); + unix_vlib_cli_output_cooked(cf, uf, (u8 *)"\r", 1); + } +} + +/** \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) +{ + u8 * str; + + str = format(0, "%s%d;%dH", CSI, y, x); + + unix_vlib_cli_output_cooked(cf, uf, str, vec_len(str)); + + vec_free(str); +} + /** \brief VLIB CLI output function. */ static void unix_vlib_cli_output (uword cli_file_index, u8 * buffer, @@ -480,7 +641,74 @@ static void unix_vlib_cli_output (uword cli_file_index, cf = pool_elt_at_index (cm->cli_file_pool, cli_file_index); uf = pool_elt_at_index (um->file_pool, cf->unix_file_index); - unix_vlib_cli_output_cooked(cf, uf, buffer, buffer_bytes); + if (cf->no_pager || um->cli_pager_buffer_limit == 0 || cf->height == 0) + { + unix_vlib_cli_output_cooked(cf, uf, buffer, buffer_bytes); + } + else + { + /* At each \n add the line to the pager buffer. + * If we've not yet displayed a whole page in this command then + * also output the line. + * If we have displayed a whole page then display a pager prompt + * and count the lines we've buffered. + */ + u8 * p = buffer; + u8 * line; + uword i, len = buffer_bytes; + + while (len) + { + i = unix_vlib_findchr('\n', p, len); + if (i < len) + i ++; /* include the '\n' */ + + /* make a vec out of this substring */ + line = vec_new(u8, i); + memcpy(line, p, i); + + /* store in pager buffer */ + vec_add1(cf->pager_vector, line); + cf->pager_lines ++; + + if (cf->pager_lines < cf->height) + { + /* output this line */ + unix_vlib_cli_output_cooked(cf, uf, p, i); + /* TODO: stop if line longer than cf->width and would + * overflow cf->height */ + } + else + { + /* Display the pager prompt every 10 lines */ + if (!(cf->pager_lines % 10)) + unix_cli_pager_prompt(cf, uf); + } + + p += i; + len -= i; + } + + /* Check if we went over the pager buffer limit */ + if (cf->pager_lines > um->cli_pager_buffer_limit) + { + /* Stop using the pager for the remainder of this CLI command */ + cf->no_pager = 2; + + /* If we likely printed the prompt, erase it */ + if (cf->pager_lines > cf->height - 1) + unix_cli_pager_prompt_erase (cf, uf); + + /* Dump out the contents of the buffer */ + for (i = cf->pager_start + (cf->height - 1); + i < cf->pager_lines; i ++) + unix_vlib_cli_output_cooked (cf, uf, + cf->pager_vector[i], + vec_len(cf->pager_vector[i])); + + unix_cli_pager_reset (cf); + } + } } /** \brief Identify whether a terminal type is ANSI capable. */ @@ -538,9 +766,7 @@ static void unix_cli_file_welcome(unix_cli_main_t * cm, unix_cli_file_t * cf) } /* Prompt. */ - unix_vlib_cli_output_raw (cf, uf, - cm->cli_prompt, - vec_len (cm->cli_prompt)); + unix_cli_cli_prompt (cf, uf); cf->started = 1; } @@ -629,6 +855,14 @@ static i32 unix_cli_process_telnet(unix_main_t * um, unix_cli_file_welcome(&unix_cli_main, cf); break; + case TELOPT_NAWS: + /* Window size */ + if (i != 8) /* check message is correct size */ + break; + cf->width = clib_net_to_host_u16(*((u16 *)(input_vector + 3))); + cf->height = clib_net_to_host_u16(*((u16 *)(input_vector + 5))); + break; + default: break; } @@ -699,6 +933,8 @@ static int unix_cli_line_process_one(unix_cli_main_t * cm, case UNIX_CLI_PARSE_ACTION_REVSEARCH: case UNIX_CLI_PARSE_ACTION_FWDSEARCH: + if (!cf->has_history || !cf->history_limit) + break; if (cf->search_mode == 0) { /* Erase the current command (if any) */ @@ -794,6 +1030,8 @@ static int unix_cli_line_process_one(unix_cli_main_t * cm, case UNIX_CLI_PARSE_ACTION_UP: case UNIX_CLI_PARSE_ACTION_DOWN: + if (!cf->has_history || !cf->history_limit) + break; cf->search_mode = 0; /* Erase the command */ for (j = cf->cursor; j < (vec_len (cf->current_command)); j++) @@ -995,6 +1233,157 @@ static int unix_cli_line_process_one(unix_cli_main_t * cm, break; + case UNIX_CLI_PARSE_ACTION_TAB: + case UNIX_CLI_PARSE_ACTION_YANK: + /* TODO */ + break; + + + case UNIX_CLI_PARSE_ACTION_PAGER_QUIT: + pager_quit: + unix_cli_pager_prompt_erase (cf, uf); + unix_cli_pager_reset (cf); + unix_cli_cli_prompt (cf, uf); + break; + + 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 < cf->pager_lines) + { + u8 * line; + int m = cf->pager_start + (cf->height - 1); + unix_cli_pager_prompt_erase (cf, uf); + for (j = m; + j < cf->pager_lines && cf->pager_start < m; + j ++, cf->pager_start ++) + { + line = cf->pager_vector[j]; + unix_vlib_cli_output_cooked (cf, uf, line, vec_len(line)); + } + unix_cli_pager_prompt (cf, uf); + } + else + { + if (action == UNIX_CLI_PARSE_ACTION_PAGER_NEXT) + /* no more in buffer, exit, but only if it was */ + goto pager_quit; + } + break; + + 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 < cf->pager_lines - (cf->height - 1)) + { + u8 * line; + unix_cli_pager_prompt_erase (cf, uf); + line = cf->pager_vector[cf->pager_start + (cf->height - 1)]; + unix_vlib_cli_output_cooked (cf, uf, line, vec_len(line)); + cf->pager_start ++; + unix_cli_pager_prompt (cf, uf); + } + else + { + if (action == UNIX_CLI_PARSE_ACTION_PAGER_CRLF) + /* no more in buffer, exit, but only if it was */ + goto pager_quit; + } + + break; + + case UNIX_CLI_PARSE_ACTION_PAGER_UP: + /* scroll the page back one line */ + if (cf->pager_start > 0) + { + u8 * line; + cf->pager_start --; + if (cf->ansi_capable) + { + unix_cli_pager_prompt_erase (cf, uf); + unix_vlib_cli_output_cooked (cf, uf, (u8 *)ANSI_SCROLLDN, sizeof(ANSI_SCROLLDN) - 1); + unix_vlib_cli_output_cooked (cf, uf, (u8 *)ANSI_SAVECURSOR, sizeof(ANSI_SAVECURSOR) - 1); + unix_cli_ansi_cursor(cf, uf, 1, 1); + unix_vlib_cli_output_cooked (cf, uf, (u8 *)ANSI_CLEARLINE, sizeof(ANSI_CLEARLINE) - 1); + unix_vlib_cli_output_cooked (cf, uf, cf->pager_vector[cf->pager_start], vec_len(cf->pager_vector[cf->pager_start])); + unix_vlib_cli_output_cooked (cf, uf, (u8 *)ANSI_RESTCURSOR, sizeof(ANSI_RESTCURSOR) - 1); + unix_cli_pager_prompt_erase (cf, uf); + unix_cli_pager_prompt (cf, uf); + } + else + { + int m = cf->pager_start + (cf->height - 1); + unix_cli_pager_prompt_erase (cf, uf); + for (j = cf->pager_start; j < cf->pager_lines && j < m; j ++) + { + line = cf->pager_vector[j]; + unix_vlib_cli_output_cooked (cf, uf, line, vec_len(line)); + } + unix_cli_pager_prompt (cf, uf); + } + } + break; + + case UNIX_CLI_PARSE_ACTION_PAGER_TOP: + /* back to the first page of the buffer */ + if (cf->pager_start > 0) + { + u8 * line; + cf->pager_start = 0; + int m = cf->pager_start + (cf->height - 1); + unix_cli_pager_prompt_erase (cf, uf); + for (j = cf->pager_start; j < cf->pager_lines && j < m; j ++) + { + line = cf->pager_vector[j]; + unix_vlib_cli_output_cooked (cf, uf, line, vec_len(line)); + } + unix_cli_pager_prompt (cf, uf); + } + break; + + case UNIX_CLI_PARSE_ACTION_PAGER_BOTTOM: + /* skip to the last page of the buffer */ + if (cf->pager_start < cf->pager_lines - (cf->height - 1)) + { + u8 * line; + cf->pager_start = cf->pager_lines - (cf->height - 1); + unix_cli_pager_prompt_erase (cf, uf); + unix_cli_pager_message (cf, uf, "skipping", "\n"); + for (j = cf->pager_start; j < cf->pager_lines; j ++) + { + line = cf->pager_vector[j]; + unix_vlib_cli_output_cooked (cf, uf, line, vec_len(line)); + } + unix_cli_pager_prompt (cf, uf); + } + break; + + case UNIX_CLI_PARSE_ACTION_PAGER_PGUP: + /* wander back one page in the buffer */ + if (cf->pager_start > 0) + { + u8 * line; + int m; + if (cf->pager_start >= cf->height) + cf->pager_start -= cf->height - 1; + else + cf->pager_start = 1; + m = cf->pager_start + cf->height - 1; + unix_cli_pager_prompt_erase (cf, uf); + for (j = cf->pager_start; j < cf->pager_lines && j < m; j ++) + { + line = cf->pager_vector[j]; + unix_vlib_cli_output_cooked (cf, uf, line, vec_len(line)); + } + unix_cli_pager_prompt (cf, uf); + } + break; + + case UNIX_CLI_PARSE_ACTION_PAGER_SEARCH: + /* search forwards in the buffer */ + break; + + case UNIX_CLI_PARSE_ACTION_CRLF: crlf: vec_add1 (cf->current_command, '\r'); @@ -1006,31 +1395,36 @@ static int unix_cli_line_process_one(unix_cli_main_t * cm, vec_len(cf->current_command)); _vec_len(cf->input_vector) = _vec_len (cf->current_command); - if (vec_len(cf->command_history) >= cf->history_limit) + if (cf->has_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) - { - /* Don't duplicate the previous command */ - _vec_len (cf->current_command) -= 2; - j = vec_len(cf->command_history); - if (j == 0 || - (vec_len (cf->current_command) != vec_len (cf->command_history[j - 1]) || - memcmp(cf->current_command, cf->command_history[j - 1], - vec_len (cf->current_command)) != 0)) + if (cf->command_history && vec_len(cf->command_history) >= cf->history_limit) { - vec_add1 (cf->command_history, cf->current_command); - cf->current_command = 0; - cf->command_number ++; + 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) + { + /* Don't duplicate the previous command */ + _vec_len (cf->current_command) -= 2; + j = vec_len(cf->command_history); + if (j == 0 || + (vec_len (cf->current_command) != vec_len (cf->command_history[j - 1]) || + memcmp(cf->current_command, cf->command_history[j - 1], + vec_len (cf->current_command)) != 0)) + { + vec_add1 (cf->command_history, cf->current_command); + cf->current_command = 0; + cf->command_number ++; + } + else + vec_reset_length (cf->current_command); } else vec_reset_length (cf->current_command); } - else - vec_reset_length (cf->current_command); + else /* history disabled */ + vec_reset_length (cf->current_command); cf->excursion = 0; cf->search_mode = 0; vec_reset_length (cf->search_key); @@ -1038,9 +1432,13 @@ static int unix_cli_line_process_one(unix_cli_main_t * cm, return 0; - - default: - if (cf->search_mode && isprint(input)) + case UNIX_CLI_PARSE_ACTION_PARTIALMATCH: + case UNIX_CLI_PARSE_ACTION_NOMATCH: + if (cf->pager_lines) + { + /* no-op for now */ + } + else if (cf->has_history && cf->search_mode && isprint(input)) { int k, limit, offset; u8 * item; @@ -1098,43 +1496,47 @@ static int unix_cli_line_process_one(unix_cli_main_t * cm, cf->cursor = 0; goto crlf; } - else + else if (isprint(input)) /* skip any errant control codes */ { - if (isprint(input)) /* skip any errant control codes */ + if (cf->cursor == vec_len(cf->current_command)) { - if (cf->cursor == vec_len(cf->current_command)) - { - /* Append to end */ - vec_add1 (cf->current_command, input); - cf->cursor ++; + /* Append to end */ + vec_add1 (cf->current_command, input); + cf->cursor ++; - /* 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 ++; - } + /* 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 ++; + } + } + else + { + /* no-op - not printable or otherwise not actionable */ } found: break; + + case UNIX_CLI_PARSE_ACTION_TELNETIAC: + break; } return 1; } @@ -1153,8 +1555,12 @@ static int unix_cli_line_edit (unix_cli_main_t * cm, unix_cli_parse_action_t action; /* See if the input buffer is some sort of control code */ i32 matched = 0; + unix_cli_parse_actions_t *a; - action = unix_cli_match_action(&cf->input_vector[i], + /* If we're in the pager mode, search the pager actions */ + a = cf->pager_lines ? unix_cli_parse_pager : unix_cli_parse_strings; + + action = unix_cli_match_action(a, &cf->input_vector[i], vec_len (cf->input_vector) - i, &matched); switch (action) @@ -1223,7 +1629,7 @@ static void unix_cli_process_input (unix_cli_main_t * cm, goto done; /* Line edit, echo, etc. */ - if (cf->has_history && unix_cli_line_edit (cm, um, cf)) + if (!cf->line_mode && unix_cli_line_edit (cm, um, cf)) return; if (um->log_fd) @@ -1248,12 +1654,15 @@ static void unix_cli_process_input (unix_cli_main_t * cm, (void) unformat (&input, ""); cm->current_input_file_index = cli_file_index; + cf->pager_lines = 0; /* start a new pager session */ + cf->pager_start = 0; if (unformat_check_input (&input) != UNFORMAT_END_OF_INPUT) vlib_cli_input (um->vlib_main, &input, unix_vlib_cli_output, cli_file_index); /* 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); /* Zero buffer since otherwise unformat_free will call vec_free on it. */ input.buffer = 0; @@ -1264,11 +1673,26 @@ static void unix_cli_process_input (unix_cli_main_t * cm, done: _vec_len (cf->input_vector) = 0; - /* Prompt. */ - uf = pool_elt_at_index (um->file_pool, cf->unix_file_index); - unix_vlib_cli_output_raw (cf, uf, - cm->cli_prompt, - vec_len (cm->cli_prompt)); + if (cf->no_pager == 2) + { + /* Pager was programmatically disabled */ + unix_cli_pager_message (cf, uf, "pager buffer overflowed", "\n"); + cf->no_pager = um->cli_no_pager; + } + + if (cf->pager_lines == 0 || cf->pager_lines < cf->height) + { + /* There was no need for the pager */ + unix_cli_pager_reset (cf); + + /* Prompt. */ + unix_cli_cli_prompt (cf, uf); + } + else + { + /* Display the pager prompt */ + unix_cli_pager_prompt(cf, uf); + } } static void unix_cli_kill (unix_cli_main_t * cm, uword cli_file_index) @@ -1495,17 +1919,23 @@ static clib_error_t * unix_cli_listen_read_ready (unix_file_t * uf) 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 */ + IAC, DO, TELOPT_NAWS, /* client should tell us its window sz */ + IAC, SB, TELOPT_NAWS, 1, IAC, SE, /* now tell me window size */ }; /* Enable history on this CLI */ - cf->has_history = 1; - cf->history_limit = um->cli_history_limit ? - um->cli_history_limit : - UNIX_CLI_DEFAULT_HISTORY; + cf->history_limit = um->cli_history_limit; + cf->has_history = cf->history_limit != 0; + + /* Make sure this session is in line mode */ + cf->line_mode = 0; /* We need CRLF */ cf->crlf_mode = 1; + /* Setup the pager */ + cf->no_pager = um->cli_no_pager; + uf = pool_elt_at_index (um->file_pool, cf->unix_file_index); /* Send the telnet options */ @@ -1520,6 +1950,21 @@ static clib_error_t * unix_cli_listen_read_ready (unix_file_t * uf) return error; } +static void +unix_cli_resize_interrupt (int signum) +{ + 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); + struct winsize ws; + (void)signum; + + /* Terminal resized, fetch the new size */ + ioctl(UNIX_CLI_STDIN_FD, TIOCGWINSZ, &ws); + cf->width = ws.ws_col; + cf->height = ws.ws_row; +} + static clib_error_t * unix_cli_config (vlib_main_t * vm, unformat_input_t * input) { @@ -1530,6 +1975,8 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input) unix_cli_file_t * cf; u32 cf_index; struct termios tio; + struct sigaction sa; + struct winsize ws; u8 * term; /* We depend on unix flags being set. */ @@ -1545,15 +1992,36 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input) cf_index = unix_cli_file_add (cm, "stdin", UNIX_CLI_STDIN_FD); 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) { - cf->has_history = 1; - cf->history_limit = um->cli_history_limit ? - um->cli_history_limit : - UNIX_CLI_DEFAULT_HISTORY; + /* Capture terminal resize events */ + sa.sa_handler = unix_cli_resize_interrupt; + sa.sa_flags = 0; + if (sigaction (SIGWINCH, &sa, 0) < 0) + clib_panic ("sigaction"); + + /* Retrieve the current terminal size */ + ioctl(UNIX_CLI_STDIN_FD, 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; + + /* Setup the history */ + cf->history_limit = um->cli_history_limit; + cf->has_history = cf->history_limit != 0; + + /* Setup the pager */ + cf->no_pager = um->cli_no_pager; + + /* 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); @@ -1573,6 +2041,15 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input) cf->ansi_capable = unix_cli_terminal_type(term, strlen((char *)term)); } + 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; + } /* Send banner and initial prompt */ unix_cli_file_welcome(cm, cf); @@ -1792,11 +2269,15 @@ unix_cli_show_history (vlib_main_t * vm, cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index); - i = 1 + cf->command_number - vec_len(cf->command_history); - - for (j = 0; j < vec_len (cf->command_history); j++) + if (cf->has_history && cf->history_limit) + { + i = 1 + cf->command_number - vec_len(cf->command_history); + for (j = 0; j < vec_len (cf->command_history); j++) + vlib_cli_output (vm, "%d %v\n", i + j, cf->command_history[j]); + } + else { - vlib_cli_output (vm, "%d %v\n", i + j, cf->command_history[j]); + vlib_cli_output (vm, "History not enabled.\n"); } return 0; @@ -1808,6 +2289,162 @@ VLIB_CLI_COMMAND (cli_unix_cli_show_history, static) = { .function = unix_cli_show_history, }; +static clib_error_t * +unix_cli_show_terminal (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; + unix_cli_file_t * cf; + vlib_node_t * n; + + cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index); + n = vlib_get_node (vm, cf->process_node_index); + + vlib_cli_output (vm, "Terminal name: %v\n", n->name); + vlib_cli_output (vm, "Terminal mode: %s\n", cf->line_mode ? + "line-by-line" : + "char-by-char"); + vlib_cli_output (vm, "Terminal width: %d\n", cf->width); + 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, "History enabled: %s%s\n", + cf->has_history ? "yes" : "no", + !cf->has_history || cf->history_limit ? "" : " (disabled by history limit)"); + if (cf->has_history) + vlib_cli_output (vm, "History limit: %d\n", cf->history_limit); + vlib_cli_output (vm, "Pager enabled: %s%s%s\n", + cf->no_pager ? "no" : "yes", + cf->no_pager || cf->height ? "" : " (disabled by terminal height)", + cf->no_pager || um->cli_pager_buffer_limit ? "" : " (disabled by buffer limit)"); + if (!cf->no_pager) + vlib_cli_output (vm, "Pager limit: %d\n", um->cli_pager_buffer_limit); + vlib_cli_output (vm, "CRLF mode: %s\n", cf->crlf_mode ? "CR+LF" : "LF"); + + return 0; +} + +VLIB_CLI_COMMAND (cli_unix_cli_show_terminal, static) = { + .path = "show terminal", + .short_help = "Show current session terminal settings", + .function = unix_cli_show_terminal, +}; + +static clib_error_t * +unix_cli_set_terminal_pager (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; + unix_cli_file_t * cf; + unformat_input_t _line_input, * line_input = &_line_input; + + 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")) + cf->no_pager = 0; + else if (unformat (line_input, "off")) + cf->no_pager = 1; + else if (unformat (line_input, "limit %u", &um->cli_pager_buffer_limit)) + vlib_cli_output (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); + } + + unformat_free(line_input); + + return 0; +} + +VLIB_CLI_COMMAND (cli_unix_cli_set_terminal_pager, static) = { + .path = "set terminal pager", + .short_help = "set terminal pager [on|off] [limit ]", + .function = unix_cli_set_terminal_pager, +}; + +static clib_error_t * +unix_cli_set_terminal_history (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; + unformat_input_t _line_input, * line_input = &_line_input; + u32 limit; + + 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")) + cf->has_history = 1; + else if (unformat (line_input, "off")) + cf->has_history = 0; + else if (unformat (line_input, "limit %u", &cf->history_limit)) + ; + else + return clib_error_return (0, "unknown parameter: `%U`", + format_unformat_error, line_input); + + /* If we reduced history size, or turned it off, purge the history */ + limit = cf->has_history ? cf->history_limit : 0; + + while (cf->command_history && vec_len(cf->command_history) >= limit) + { + vec_free (cf->command_history[0]); + vec_delete (cf->command_history, 1, 0); + } + } + + unformat_free(line_input); + + return 0; +} + +VLIB_CLI_COMMAND (cli_unix_cli_set_terminal_history, static) = { + .path = "set terminal history", + .short_help = "set terminal history [on|off] [limit ]", + .function = unix_cli_set_terminal_history, +}; + +static clib_error_t * +unix_cli_set_terminal_ansi (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; + + cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index); + + if (unformat (input, "on")) + cf->ansi_capable = 1; + else if (unformat (input, "off")) + cf->ansi_capable = 0; + else + return clib_error_return (0, "unknown parameter: `%U`", + format_unformat_error, input); + + return 0; +} + +VLIB_CLI_COMMAND (cli_unix_cli_set_terminal_ansi, static) = { + .path = "set terminal ansi", + .short_help = "set terminal ansi [on|off]", + .function = unix_cli_set_terminal_ansi, +}; static clib_error_t * unix_cli_init (vlib_main_t * vm) diff --git a/vlib/vlib/unix/main.c b/vlib/vlib/unix/main.c index 1d8bd282c09..9049644f5b3 100644 --- a/vlib/vlib/unix/main.c +++ b/vlib/vlib/unix/main.c @@ -47,6 +47,13 @@ #include #include +/** Default CLI pager limit is not configured in startup.conf */ +#define UNIX_CLI_DEFAULT_PAGER_LIMIT 100000 + +/** Default CLI history depth if not configured in startup.conf */ +#define UNIX_CLI_DEFAULT_HISTORY 50 + + unix_main_t unix_main; static clib_error_t * @@ -299,6 +306,10 @@ unix_config (vlib_main_t * vm, unformat_input_t * input) unix_main_t * um = &unix_main; clib_error_t * error = 0; + /* Defaults */ + um->cli_pager_buffer_limit = UNIX_CLI_DEFAULT_PAGER_LIMIT; + um->cli_history_limit = UNIX_CLI_DEFAULT_HISTORY; + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { char * cli_prompt; @@ -314,6 +325,11 @@ unix_config (vlib_main_t * vm, unformat_input_t * input) um->cli_line_mode = 1; else if (unformat (input, "cli-no-banner")) um->cli_no_banner = 1; + else if (unformat (input, "cli-no-pager")) + um->cli_no_pager = 1; + else if (unformat (input, "cli-pager-buffer-limit %d", + &um->cli_pager_buffer_limit)) + ; else if (unformat (input, "cli-history-limit %d", &um->cli_history_limit)) ; else if (unformat (input, "full-coredump")) diff --git a/vlib/vlib/unix/unix.h b/vlib/vlib/unix/unix.h index 20aec675ee0..2922b4e2a68 100644 --- a/vlib/vlib/unix/unix.h +++ b/vlib/vlib/unix/unix.h @@ -113,6 +113,12 @@ typedef struct { /* Suppress the welcome banner at CLI session start */ int cli_no_banner; + /* Maximum pager buffer size */ + u32 cli_pager_buffer_limit; + + /* Suppress the pager */ + int cli_no_pager; + /* Store the original state of stdin when it's a tty */ struct termios tio_stdin; int tio_isset; -- cgit 1.2.3-korg