X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=src%2Fvlib%2Funix%2Fcli.c;h=b0ed9d2db143ae14668b4a346911770504ea85f6;hb=a6ef36b2c25de47824a1b45e147ab2fbf67c3a33;hp=42c13740e207f4c87be796f3e0ac4351f6290aba;hpb=a9cf6afa3e2cbc881a41ee6930e2a7d1797c8cf0;p=vpp.git diff --git a/src/vlib/unix/cli.c b/src/vlib/unix/cli.c index 42c13740e20..b0ed9d2db14 100644 --- a/src/vlib/unix/cli.c +++ b/src/vlib/unix/cli.c @@ -47,7 +47,6 @@ #include #include -#include #include #include @@ -61,6 +60,7 @@ #include #include #include +#include /** ANSI escape code. */ #define ESC "\x1b" @@ -293,6 +293,7 @@ typedef enum UNIX_CLI_PARSE_ACTION_WORDRIGHT, /**< Jump cursor to start of right word */ UNIX_CLI_PARSE_ACTION_ERASELINELEFT, /**< Erase line to left of cursor */ UNIX_CLI_PARSE_ACTION_ERASELINERIGHT, /**< Erase line to right & including cursor */ + UNIX_CLI_PARSE_ACTION_ERASEWORDLEFT, /**< Erase word left */ UNIX_CLI_PARSE_ACTION_CLEAR, /**< Clear the terminal */ UNIX_CLI_PARSE_ACTION_REVSEARCH, /**< Search backwards in command history */ UNIX_CLI_PARSE_ACTION_FWDSEARCH, /**< Search forwards in command history */ @@ -359,6 +360,7 @@ static unix_cli_parse_actions_t unix_cli_parse_strings[] = { _(CTL ('D'), UNIX_CLI_PARSE_ACTION_ERASERIGHT), _(CTL ('U'), UNIX_CLI_PARSE_ACTION_ERASELINELEFT), _(CTL ('K'), UNIX_CLI_PARSE_ACTION_ERASELINERIGHT), + _(CTL ('W'), UNIX_CLI_PARSE_ACTION_ERASEWORDLEFT), _(CTL ('Y'), UNIX_CLI_PARSE_ACTION_YANK), _(CTL ('L'), UNIX_CLI_PARSE_ACTION_CLEAR), _(ESC "b", UNIX_CLI_PARSE_ACTION_WORDLEFT), /* Alt-B */ @@ -451,6 +453,21 @@ typedef enum UNIX_CLI_PROCESS_EVENT_QUIT, /**< A CLI session wants to close. */ } unix_cli_process_event_type_t; +/** CLI session telnet negotiation timer events. */ +typedef enum +{ + UNIX_CLI_NEW_SESSION_EVENT_ADD, /**< Add a CLI session to the new session list */ +} unix_cli_timeout_event_type_t; + +/** Each new session is stored on a list with a deadline after which + * a prompt is issued, in case the session TELNET negotiation fails to + * complete. */ +typedef struct +{ + uword cf_index; /**< Session index of the new session. */ + f64 deadline; /**< Deadline after which the new session must have a prompt. */ +} unix_cli_new_session_t; + /** CLI global state. */ typedef struct { @@ -468,6 +485,12 @@ typedef struct /** File pool index of current input. */ u32 current_input_file_index; + + /** New session process node identifier */ + u32 new_session_process_node_index; + + /** List of new sessions */ + unix_cli_new_session_t *new_sessions; } unix_cli_main_t; /** CLI global state */ @@ -581,7 +604,7 @@ unix_cli_del_pending_output (clib_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 @@ -899,7 +922,7 @@ unix_cli_pager_redraw (unix_cli_file_t * cf, clib_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. @@ -1240,25 +1263,109 @@ unix_cli_file_welcome (unix_cli_main_t * cm, unix_cli_file_t * cf) } -/** @brief A failsafe triggered on a timer to ensure we send the prompt - * to telnet sessions that fail to negotiate the terminal type. */ -static void -unix_cli_file_welcome_timer (any arg, f64 delay) +/** + * @brief A failsafe manager that ensures CLI sessions issue an initial + * prompt if TELNET negotiation fails. + */ +static uword +unix_cli_new_session_process (vlib_main_t * vm, vlib_node_runtime_t * rt, + vlib_frame_t * f) { unix_cli_main_t *cm = &unix_cli_main; - unix_cli_file_t *cf; - (void) delay; + uword event_type, *event_data = 0; + f64 wait = 10.0; - /* Check the connection didn't close already */ - if (pool_is_free_index (cm->cli_file_pool, (uword) arg)) - return; + while (1) + { + if (vec_len (cm->new_sessions) > 0) + wait = vlib_process_wait_for_event_or_clock (vm, wait); + else + vlib_process_wait_for_event (vm); + + event_type = vlib_process_get_events (vm, &event_data); + + switch (event_type) + { + case ~0: /* no events => timeout */ + break; + + case UNIX_CLI_NEW_SESSION_EVENT_ADD: + { + /* Add an identifier to the new session list */ + unix_cli_new_session_t ns; + + ns.cf_index = event_data[0]; + ns.deadline = vlib_time_now (vm) + 1.0; + + vec_add1 (cm->new_sessions, ns); + + if (wait > 0.1) + wait = 0.1; /* force a re-evaluation soon, but not too soon */ + } + break; - cf = pool_elt_at_index (cm->cli_file_pool, (uword) arg); + default: + clib_warning ("BUG: unknown event type 0x%wx", event_type); + break; + } + + vec_reset_length (event_data); + + if (vlib_process_suspend_time_is_zero (wait)) + { + /* Scan the list looking for expired deadlines. + * Emit needed prompts and remove from the list. + * While scanning, look for the nearest new deadline + * for the next iteration. + * Since the list is ordered with newest sessions first + * we can make assumptions about expired sessions being + * contiguous at the beginning and the next deadline is the + * next entry on the list, if any. + */ + f64 now = vlib_time_now (vm); + unix_cli_new_session_t *nsp; + word index = 0; + + wait = INFINITY; - if (!cf->started) - unix_cli_file_welcome (cm, cf); + vec_foreach (nsp, cm->new_sessions) + { + if (vlib_process_suspend_time_is_zero (nsp->deadline - now)) + { + /* Deadline reached */ + unix_cli_file_t *cf; + + /* Mark the highwater */ + index++; + + /* Check the connection didn't close already */ + if (pool_is_free_index (cm->cli_file_pool, nsp->cf_index)) + continue; + + cf = pool_elt_at_index (cm->cli_file_pool, nsp->cf_index); + + if (!cf->started) + unix_cli_file_welcome (cm, cf); + } + else + { + wait = nsp->deadline - now; + break; + } + } + + if (index) + { + /* We have sessions to remove */ + vec_delete (cm->new_sessions, index, 0); + } + } + } + + return 0; } + /** @brief A mostly no-op Telnet state machine. * Process Telnet command bytes in a way that ensures we're mostly * transparent to the Telnet protocol. That is, it's mostly a no-op. @@ -1511,6 +1618,51 @@ unix_cli_line_process_one (unix_cli_main_t * cm, cf->search_mode = 0; break; + case UNIX_CLI_PARSE_ACTION_ERASEWORDLEFT: + /* calculate num of caracter to be erased */ + delta = 0; + while (cf->cursor > delta + && cf->current_command[cf->cursor - delta - 1] == ' ') + delta++; + while (cf->cursor > delta + && cf->current_command[cf->cursor - delta - 1] != ' ') + delta++; + + if (vec_len (cf->current_command)) + { + if (cf->cursor > 0) + { + /* move cursor left delta times */ + for (j = delta; j > 0; j--, cf->cursor--) + unix_vlib_cli_output_cursor_left (cf, uf); + save = cf->current_command + cf->cursor; + + /* redraw remainder of line */ + memmove (cf->current_command + cf->cursor, + cf->current_command + cf->cursor + delta, + _vec_len (cf->current_command) - cf->cursor - delta); + unix_vlib_cli_output_cooked (cf, uf, + cf->current_command + cf->cursor, + _vec_len (cf->current_command) - + cf->cursor); + cf->cursor += _vec_len (cf->current_command) - cf->cursor; + + /* print delta amount of blank spaces, + * then finally fix the cursor position */ + for (j = delta; j > 0; j--, cf->cursor--) + unix_vlib_cli_output_cursor_left (cf, uf); + for (j = delta; j > 0; j--, cf->cursor++) + unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1); + for (; (cf->current_command + cf->cursor) > save; cf->cursor--) + unix_vlib_cli_output_cursor_left (cf, uf); + _vec_len (cf->current_command) -= delta; + } + } + cf->search_mode = 0; + cf->excursion = 0; + vec_reset_length (cf->search_key); + break; + case UNIX_CLI_PARSE_ACTION_LEFT: if (cf->cursor > 0) { @@ -2712,7 +2864,7 @@ unix_cli_file_add (unix_cli_main_t * cm, char *name, int fd) static vlib_node_registration_t r = { .function = unix_cli_process, .type = VLIB_NODE_TYPE_PROCESS, - .process_log2_n_stack_bytes = 16, + .process_log2_n_stack_bytes = 18, }; r.name = name; @@ -2728,7 +2880,7 @@ unix_cli_file_add (unix_cli_main_t * cm, char *name, int fd) } 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; @@ -2772,8 +2924,8 @@ unix_cli_listen_read_ready (clib_file_t * uf) /* Disable Nagle, ignore any errors doing so eg on PF_LOCAL socket */ one = 1; - setsockopt (client.fd, IPPROTO_TCP, TCP_NODELAY, - (void *) &one, sizeof (one)); + (void) setsockopt (client.fd, IPPROTO_TCP, TCP_NODELAY, + (void *) &one, sizeof (one)); client_name = (char *) format (0, "%U%c", format_sockaddr, &client.peer, 0); @@ -2832,9 +2984,22 @@ unix_cli_listen_read_ready (clib_file_t * uf) unix_vlib_cli_output_raw (cf, uf, charmode_option, ARRAY_LEN (charmode_option)); - /* In case the client doesn't negotiate terminal type, use - * a timer to kick off the initial prompt. */ - timer_call (unix_cli_file_welcome_timer, cf_index, 1); + if (cm->new_session_process_node_index == ~0) + { + /* Create thw new session deadline process */ + cm->new_session_process_node_index = + vlib_process_create (um->vlib_main, "unix-cli-new-session", + unix_cli_new_session_process, + 16 /* log2_n_stack_bytes */ ); + } + + /* In case the client doesn't negotiate terminal type, register + * our session with a process that will emit the prompt if + * a deadline passes */ + vlib_process_signal_event (um->vlib_main, + cm->new_session_process_node_index, + UNIX_CLI_NEW_SESSION_EVENT_ADD, cf_index); + } return error; @@ -2918,15 +3083,17 @@ 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"); /* Retrieve the current terminal size */ - ioctl (STDIN_FILENO, TIOCGWINSZ, &ws); - cf->width = ws.ws_col; - cf->height = ws.ws_row; + if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) == 0) + { + cf->width = ws.ws_col; + cf->height = ws.ws_row; + } if (cf->width == 0 || cf->height == 0) { @@ -3002,7 +3169,7 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input) while (i && tmp[--i] != '/') ; - tmp[i] = 0; + tmp[i] = '\0'; if (i) vlib_unix_recursive_mkdir ((char *) tmp); @@ -3163,7 +3330,7 @@ unix_cli_exec (vlib_main_t * vm, unformat_free (&sub_input); done: - if (fd > 0) + if (fd >= 0) close (fd); vec_free (file_name); @@ -3603,14 +3770,21 @@ unix_cli_set_terminal_history (vlib_main_t * vm, goto done; } - /* 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); - } + /* If we reduced history size, or turned it off, purge the history */ + limit = cf->has_history ? cf->history_limit : 0; + if (limit < vec_len (cf->command_history)) + { + u32 i; + + /* How many items to remove from the start of history */ + limit = vec_len (cf->command_history) - limit; + + for (i = 0; i < limit; i++) + vec_free (cf->command_history[i]); + + vec_delete (cf->command_history, limit, 0); } done: @@ -3675,9 +3849,55 @@ VLIB_CLI_COMMAND (cli_unix_cli_set_terminal_ansi, static) = { }; /* *INDENT-ON* */ + +#define MAX_CLI_WAIT 86400 +/** CLI command to wait seconds. Useful for exec script. */ +static clib_error_t * +unix_wait_cmd (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + unformat_input_t _line_input, *line_input = &_line_input; + f64 sec = 1.0; + + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "%f", &sec)) + ; + else + return clib_error_return (0, "unknown parameter: `%U`", + format_unformat_error, input); + } + + if (sec <= 0 || sec > MAX_CLI_WAIT || floor (sec * 1000) / 1000 != sec) + return clib_error_return (0, + " must be a positive value and less than 86400 (one day) with no more than msec precision."); + + vlib_process_wait_for_event_or_clock (vm, sec); + vlib_cli_output (vm, "waited %.3f sec.", sec); + + unformat_free (line_input); + return 0; +} +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (cli_unix_wait_cmd, static) = { + .path = "wait", + .short_help = "wait ", + .function = unix_wait_cmd, +}; +/* *INDENT-ON* */ + static clib_error_t * unix_cli_init (vlib_main_t * vm) { + unix_cli_main_t *cm = &unix_cli_main; + + /* Breadcrumb to indicate the new session process + * has not been started */ + cm->new_session_process_node_index = ~0; + return 0; }