diff options
Diffstat (limited to 'vlib/vlib/unix/cli.c')
-rw-r--r-- | vlib/vlib/unix/cli.c | 900 |
1 files changed, 900 insertions, 0 deletions
diff --git a/vlib/vlib/unix/cli.c b/vlib/vlib/unix/cli.c new file mode 100644 index 00000000000..3cb13fc8550 --- /dev/null +++ b/vlib/vlib/unix/cli.c @@ -0,0 +1,900 @@ +/* + * Copyright (c) 2015 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * cli.c: Unix stdin/socket CLI. + * + * Copyright (c) 2008 Eliot Dresselhaus + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <vlib/vlib.h> +#include <vlib/unix/unix.h> + +#include <fcntl.h> +#include <sys/stat.h> +#include <termios.h> +#include <unistd.h> +#include <arpa/telnet.h> + +typedef struct { + u32 unix_file_index; + + /* Vector of output pending write to file descriptor. */ + u8 * output_vector; + + /* Vector of input saved by Unix input node to be processed by + CLI process. */ + u8 * input_vector; + + u8 has_history; + u8 ** command_history; + u8 * current_command; + i32 excursion; + u32 history_limit; + u8 * search_key; + int search_mode; + + u32 process_node_index; +} unix_cli_file_t; + +always_inline void +unix_cli_file_free (unix_cli_file_t * f) +{ + vec_free (f->output_vector); + vec_free (f->input_vector); +} + +typedef struct { + /* Prompt string for CLI. */ + u8 * cli_prompt; + + unix_cli_file_t * cli_file_pool; + + u32 * unused_cli_process_node_indices; + + /* File pool index of current input. */ + u32 current_input_file_index; +} unix_cli_main_t; + +static unix_cli_main_t unix_cli_main; + +static void +unix_cli_add_pending_output (unix_file_t * uf, + unix_cli_file_t * cf, + u8 * buffer, + uword buffer_bytes) +{ + unix_main_t * um = &unix_main; + + vec_add (cf->output_vector, buffer, buffer_bytes); + if (vec_len (cf->output_vector) > 0) + { + 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); + } +} + +static void +unix_cli_del_pending_output (unix_file_t * uf, + unix_cli_file_t * cf, + uword n_bytes) +{ + unix_main_t * um = &unix_main; + + vec_delete (cf->output_vector, n_bytes, 0); + if (vec_len (cf->output_vector) <= 0) + { + 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); + } +} + +/* VLIB cli output function. */ +static void unix_vlib_cli_output (uword cli_file_index, + u8 * buffer, + uword buffer_bytes) +{ + unix_main_t * um = &unix_main; + unix_cli_main_t * cm = &unix_cli_main; + unix_cli_file_t * cf; + unix_file_t * uf; + int n; + + cf = pool_elt_at_index (cm->cli_file_pool, cli_file_index); + uf = pool_elt_at_index (um->file_pool, cf->unix_file_index); + n = 0; + if (vec_len (cf->output_vector) == 0) + n = write (uf->file_descriptor, buffer, buffer_bytes); + if (n < 0 && errno != EAGAIN) + clib_unix_warning ("write"); + + else if ((word) n < (word) buffer_bytes) + { + if (n < 0) n = 0; + unix_cli_add_pending_output (uf, cf, buffer + n, buffer_bytes - n); + } +} + +static int unix_cli_line_edit (unix_main_t * um, unix_cli_file_t * cf) +{ + unix_file_t * uf = pool_elt_at_index (um->file_pool, cf->unix_file_index); + u8 * prev; + int i, j, delta; + + for (i = 0; i < vec_len (cf->input_vector); i++) + { + switch (cf->input_vector[i]) + { + case 0: + continue; + + case '?': + /* Erase the current command (if any) plus ?*/ + for (j = 0; j < (vec_len (cf->current_command)+1); j++) + unix_cli_add_pending_output (uf, cf, (u8 *) "\b \b", 3); + + unix_cli_add_pending_output (uf, cf, (u8 *) "\r\nHistory:\r\n", 12); + + for (j = 0; j < vec_len (cf->command_history); j++) + { + unix_cli_add_pending_output (uf, cf, cf->command_history[j], + vec_len(cf->command_history[j])); + unix_cli_add_pending_output (uf, cf, (u8 *) "\r\n", 2); + } + goto crlf; + + /* ^R - reverse search */ + case 'R' - '@': + case 'S' - '@': + if (cf->search_mode == 0) + { + /* Erase the current command (if any) plus ^R */ + for (j = 0; j < (vec_len (cf->current_command)+2); j++) + unix_cli_add_pending_output (uf, cf, (u8 *) "\b \b", 3); + + vec_reset_length (cf->search_key); + vec_reset_length (cf->current_command); + if (cf->input_vector[i] == 'R' - '@') + cf->search_mode = -1; + else + cf->search_mode = 1; + } + else + { + if (cf->input_vector[i] == 'R' - '@') + cf->search_mode = -1; + else + cf->search_mode = 1; + + cf->excursion += cf->search_mode; + unix_cli_add_pending_output (uf, cf, (u8 *) "\b \b", 3); + goto search_again; + } + break; + + /* ^U - line-kill */ + case 'U'-'@': + /* Erase the command, plus ^U */ + for (j = 0; j < (vec_len (cf->current_command)+2); j++) + unix_cli_add_pending_output (uf, cf, (u8 *) "\b \b", 3); + vec_reset_length (cf->current_command); + cf->search_mode = 0; + continue; + + /* ^P - previous, ^N - next */ + case 'P' - '@': + case 'N' - '@': + cf->search_mode = 0; + /* Erase the command, plus ^P */ + for (j = 0; j < (vec_len (cf->current_command)+2); j++) + unix_cli_add_pending_output (uf, cf, (u8 *) "\b \b", 3); + vec_reset_length (cf->current_command); + if (vec_len (cf->command_history)) + { + if (cf->input_vector[i] == 'P' - '@') + delta = -1; + else + delta = 1; + + cf->excursion += delta; + + if (cf->excursion > (i32) vec_len (cf->command_history) -1) + cf->excursion = 0; + else if (cf->excursion < 0) + cf->excursion = vec_len (cf->command_history) -1; + + prev = cf->command_history [cf->excursion]; + vec_validate (cf->current_command, vec_len(prev)-1); + + memcpy (cf->current_command, prev, vec_len(prev)); + _vec_len (cf->current_command) = vec_len(prev); + unix_cli_add_pending_output (uf, cf, cf->current_command, + vec_len (cf->current_command)); + break; + } + break; + + case 0x7f: + case 'H' - '@': + for (j = 0; j < 2; j++) + unix_cli_add_pending_output (uf, cf, (u8 *) "\b \b", 3); + if (vec_len (cf->current_command)) + { + unix_cli_add_pending_output (uf, cf, (u8 *) "\b \b", 3); + _vec_len (cf->current_command)--; + } + cf->search_mode = 0; + cf->excursion = 0; + cf->search_mode = 0; + vec_reset_length (cf->search_key); + break; + + case '\r': + case '\n': + crlf: + vec_add1 (cf->current_command, '\r'); + vec_add1 (cf->current_command, '\n'); + unix_cli_add_pending_output (uf, cf, (u8 *) "\b\b \b\b\r\n", 8); + + vec_validate (cf->input_vector, vec_len(cf->current_command)-1); + memcpy (cf->input_vector, cf->current_command, + vec_len(cf->current_command)); + _vec_len(cf->input_vector) = _vec_len (cf->current_command); + + if (vec_len(cf->command_history) >= cf->history_limit) + { + vec_free (cf->command_history[0]); + vec_delete (cf->command_history, 1, 0); + } + /* Don't add blank lines to the cmd history */ + if (vec_len (cf->current_command) > 2) + { + _vec_len (cf->current_command) -= 2; + vec_add1 (cf->command_history, cf->current_command); + cf->current_command = 0; + } + else + vec_reset_length (cf->current_command); + cf->excursion = 0; + cf->search_mode = 0; + vec_reset_length (cf->search_key); + return 0; + + /* telnet "mode character" blort, echo but don't process. */ + case 0xff: + unix_cli_add_pending_output (uf, cf, cf->input_vector + i, + 6); + i += 6; + continue; + + default: + if (cf->search_mode) + { + int j, k, limit, offset; + u8 * item; + + vec_add1 (cf->search_key, cf->input_vector[i]); + + search_again: + for (j = 0; j < vec_len(cf->command_history); j++) + { + if (cf->excursion > (i32) vec_len (cf->command_history) -1) + cf->excursion = 0; + else if (cf->excursion < 0) + cf->excursion = vec_len (cf->command_history) -1; + + item = cf->command_history[cf->excursion]; + + limit = (vec_len(cf->search_key) > vec_len (item)) ? + vec_len(item) : vec_len (cf->search_key); + + for (offset = 0; offset <= vec_len(item) - limit; offset++) + { + for (k = 0; k < limit; k++) + { + if (item[k+offset] != cf->search_key[k]) + goto next_offset; + } + goto found_at_offset; + + next_offset: + ; + } + goto next; + + found_at_offset: + for (j = 0; j < vec_len (cf->current_command)+1; j++) + unix_cli_add_pending_output (uf, cf, (u8 *) "\b \b", 3); + + vec_validate (cf->current_command, vec_len(item)-1); + + memcpy (cf->current_command, item, vec_len(item)); + _vec_len (cf->current_command) = vec_len(item); + unix_cli_add_pending_output (uf, cf, cf->current_command, + vec_len (cf->current_command)); + goto found; + + next: + cf->excursion += cf->search_mode; + } + + unix_cli_add_pending_output (uf, cf, (u8 *)"\r\nno match..", 12); + vec_reset_length (cf->search_key); + vec_reset_length (cf->current_command); + cf->search_mode = 0; + goto crlf; + } + else + vec_add1 (cf->current_command, cf->input_vector[i]); + + found: + + break; + } + } + vec_reset_length(cf->input_vector); + return 1; +} + +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; + 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 *); + + /* Try vlibplex first. Someday... */ + if (0 && vlib_parse_eval (cf->input_vector) == 0) + goto done; + + /* Line edit, echo, etc. */ + if (cf->has_history && unix_cli_line_edit (um, cf)) + return; + + if (um->log_fd) + { + static u8 * lv; + vec_reset_length (lv); + 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)); + } + } + + unformat_init_vector (&input, cf->input_vector); + + /* Remove leading white space from input. */ + (void) unformat (&input, ""); + + cm->current_input_file_index = cli_file_index; + + 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); + + /* Zero buffer since otherwise unformat_free will call vec_free on it. */ + input.buffer = 0; + + unformat_free (&input); + + /* Re-use input vector. */ +done: + _vec_len (cf->input_vector) = 0; + + /* Prompt. */ + uf = pool_elt_at_index (um->file_pool, cf->unix_file_index); + unix_cli_add_pending_output (uf, cf, + cm->cli_prompt, + vec_len (cm->cli_prompt)); +} + +static void unix_cli_kill (unix_cli_main_t * cm, uword cli_file_index) +{ + unix_main_t * um = &unix_main; + unix_cli_file_t * cf; + unix_file_t * uf; + int i; + + cf = pool_elt_at_index (cm->cli_file_pool, cli_file_index); + uf = pool_elt_at_index (um->file_pool, cf->unix_file_index); + + /* Quit/EOF on stdin means quit program. */ + if (uf->file_descriptor == 0) + clib_longjmp (&um->vlib_main->main_loop_exit, VLIB_MAIN_LOOP_EXIT_CLI); + + vec_free (cf->current_command); + vec_free (cf->search_key); + + for (i = 0; i < vec_len (cf->command_history); i++) + vec_free (cf->command_history[i]); + + vec_free (cf->command_history); + + unix_file_del (um, uf); + + unix_cli_file_free (cf); + pool_put (cm->cli_file_pool, cf); +} + +typedef enum { + UNIX_CLI_PROCESS_EVENT_READ_READY, + UNIX_CLI_PROCESS_EVENT_QUIT, +} unix_cli_process_event_type_t; + +static uword +unix_cli_process (vlib_main_t * vm, + vlib_node_runtime_t * rt, + vlib_frame_t * f) +{ + unix_cli_main_t * cm = &unix_cli_main; + uword i, * data = 0; + + while (1) + { + unix_cli_process_event_type_t event_type; + vlib_process_wait_for_event (vm); + event_type = vlib_process_get_events (vm, &data); + + switch (event_type) + { + case UNIX_CLI_PROCESS_EVENT_READ_READY: + for (i = 0; i < vec_len (data); i++) + unix_cli_process_input (cm, data[i]); + break; + + case UNIX_CLI_PROCESS_EVENT_QUIT: + /* Kill this process. */ + for (i = 0; i < vec_len (data); i++) + unix_cli_kill (cm, data[i]); + goto done; + } + + if (data) + _vec_len (data) = 0; + } + + done: + vec_free (data); + + vlib_node_set_state (vm, rt->node_index, VLIB_NODE_STATE_DISABLED); + + /* Add node index so we can re-use this process later. */ + vec_add1 (cm->unused_cli_process_node_indices, rt->node_index); + + return 0; +} + +static clib_error_t * unix_cli_write_ready (unix_file_t * uf) +{ + unix_cli_main_t * cm = &unix_cli_main; + unix_cli_file_t * cf; + int n; + + 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 (n < 0 && errno != EAGAIN) + return clib_error_return_unix (0, "write"); + + else if (n > 0) + unix_cli_del_pending_output (uf, cf, n); + + return /* no error */ 0; +} + +static clib_error_t * unix_cli_read_ready (unix_file_t * uf) +{ + unix_main_t * um = &unix_main; + unix_cli_main_t * cm = &unix_cli_main; + unix_cli_file_t * cf; + uword l; + int n, n_read, n_try; + + cf = pool_elt_at_index (cm->cli_file_pool, uf->private_data); + + n = n_try = 4096; + while (n == n_try) { + l = vec_len (cf->input_vector); + vec_resize (cf->input_vector, l + n_try); + + n = read (uf->file_descriptor, cf->input_vector + l, n_try); + + /* Error? */ + if (n < 0 && errno != EAGAIN) + return clib_error_return_unix (0, "read"); + + n_read = n < 0 ? 0 : n; + _vec_len (cf->input_vector) = l + n_read; + } + + if (! (n < 0)) + vlib_process_signal_event (um->vlib_main, + cf->process_node_index, + (n_read == 0 + ? UNIX_CLI_PROCESS_EVENT_QUIT + : UNIX_CLI_PROCESS_EVENT_READ_READY), + /* event data */ uf->private_data); + + return /* no error */ 0; +} + +static u32 unix_cli_file_add (unix_cli_main_t * cm, char * name, int fd) +{ + unix_main_t * um = &unix_main; + unix_cli_file_t * cf; + unix_file_t * uf, template = {0}; + vlib_main_t * vm = um->vlib_main; + vlib_node_t * n; + + 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); + + /* 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; + + vlib_node_set_state (vm, n->index, VLIB_NODE_STATE_POLLING); + + _vec_len (cm->unused_cli_process_node_indices) = l - 1; + } + else + { + static vlib_node_registration_t r = { + .function = unix_cli_process, + .type = VLIB_NODE_TYPE_PROCESS, + .process_log2_n_stack_bytes = 14, + }; + + r.name = name; + vlib_register_node (vm, &r); + vec_free (name); + + n = vlib_get_node (vm, r.index); + } + + pool_get (cm->cli_file_pool, cf); + memset (cf, 0, sizeof (*cf)); + + template.read_function = unix_cli_read_ready; + template.write_function = unix_cli_write_ready; + template.file_descriptor = fd; + template.private_data = cf - cm->cli_file_pool; + + cf->process_node_index = n->index; + cf->unix_file_index = unix_file_add (um, &template); + cf->output_vector = 0; + cf->input_vector = 0; + + uf = pool_elt_at_index (um->file_pool, cf->unix_file_index); + + /* Prompt. */ + unix_cli_add_pending_output (uf, cf, + cm->cli_prompt, vec_len (cm->cli_prompt)); + + vlib_start_process (vm, n->runtime_index); + return cf - cm->cli_file_pool; +} + +static clib_error_t * unix_cli_listen_read_ready (unix_file_t * uf) +{ + unix_main_t * um = &unix_main; + unix_cli_main_t * cm = &unix_cli_main; + clib_socket_t * s = &um->cli_listen_socket; + clib_socket_t client; + char * client_name; + clib_error_t * error; + unix_cli_file_t * cf; + u32 cf_index; + + error = clib_socket_accept (s, &client); + if (error) + return error; + + 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); + + /* 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) */ + if (um->cli_line_mode == 0) + { + u8 charmode_option[6]; + + cf->has_history = 1; + cf->history_limit = um->cli_history_limit ? um->cli_history_limit : 50; + + /* + * Set telnet client character mode, echo on, suppress "go-ahead" + * Empirically, this sequence works. YMMV. + */ + + /* Tell the client no linemode, echo */ + charmode_option[0] = IAC; + charmode_option[1] = DONT; + charmode_option[2] = TELOPT_LINEMODE; + charmode_option[3] = IAC; + charmode_option[4] = DO; + charmode_option[5] = TELOPT_SGA; + + uf = pool_elt_at_index (um->file_pool, cf->unix_file_index); + + unix_cli_add_pending_output (uf, cf, charmode_option, + ARRAY_LEN(charmode_option)); + } + + return error; +} + +static clib_error_t * +unix_cli_config (vlib_main_t * vm, unformat_input_t * input) +{ + unix_main_t * um = &unix_main; + unix_cli_main_t * cm = &unix_cli_main; + int flags, standard_input_fd; + clib_error_t * error; + + /* We depend on unix flags being set. */ + if ((error = vlib_call_config_function (vm, unix_config))) + return error; + + if (um->flags & UNIX_FLAG_INTERACTIVE) + { + standard_input_fd = 0; + + /* Set stdin to be non-blocking. */ + if ((flags = fcntl (standard_input_fd, F_GETFL, 0)) < 0) + flags = 0; + fcntl (standard_input_fd, F_SETFL, flags | O_NONBLOCK); + + unix_cli_file_add (cm, "stdin", standard_input_fd); + } + + { + /* CLI listen. */ + clib_socket_t * s = &um->cli_listen_socket; + unix_file_t template = {0}; + + s->flags = SOCKET_IS_SERVER; /* listen, don't connect */ + + error = clib_socket_init (s); + if (error) + return error; + + template.read_function = unix_cli_listen_read_ready; + template.file_descriptor = s->fd; + + unix_file_add (um, &template); + } + + /* Set CLI prompt. */ + if (! cm->cli_prompt) + cm->cli_prompt = format (0, "VLIB: "); + + return 0; +} + +VLIB_CONFIG_FUNCTION (unix_cli_config, "unix-cli"); + +void vlib_unix_cli_set_prompt (char * prompt) +{ + char * fmt = (prompt[strlen(prompt)-1] == ' ') ? "%s" : "%s "; + unix_cli_main_t * cm = &unix_cli_main; + if (cm->cli_prompt) + vec_free (cm->cli_prompt); + cm->cli_prompt = format (0, fmt, prompt); +} + +static clib_error_t * +unix_cli_quit (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + unix_cli_main_t * cm = &unix_cli_main; + + vlib_process_signal_event (vm, + vlib_current_process (vm), + UNIX_CLI_PROCESS_EVENT_QUIT, + cm->current_input_file_index); + return 0; +} + +VLIB_CLI_COMMAND (unix_cli_quit_command, static) = { + .path = "quit", + .short_help = "Exit CLI", + .function = unix_cli_quit, +}; + +static clib_error_t * +unix_cli_exec (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + char * file_name; + int fd; + unformat_input_t sub_input; + clib_error_t * error; + + file_name = 0; + fd = -1; + error = 0; + + if (! unformat (input, "%s", &file_name)) + { + error = clib_error_return (0, "expecting file name, got `%U'", + format_unformat_error, input); + goto done; + } + + fd = open (file_name, O_RDONLY); + if (fd < 0) + { + error = clib_error_return_unix (0, "failed to open `%s'", file_name); + goto done; + } + + /* Make sure its a regular file. */ + { + struct stat s; + + if (fstat (fd, &s) < 0) + { + error = clib_error_return_unix (0, "failed to stat `%s'", file_name); + goto done; + } + + if (! (S_ISREG (s.st_mode) || S_ISLNK (s.st_mode))) + { + error = clib_error_return (0, "not a regular file `%s'", file_name); + goto done; + } + } + + unformat_init_unix_file (&sub_input, fd); + + vlib_cli_input (vm, &sub_input, 0, 0); + unformat_free (&sub_input); + + done: + if (fd > 0) + close (fd); + vec_free (file_name); + + return error; +} + +VLIB_CLI_COMMAND (cli_exec, static) = { + .path = "exec", + .short_help = "Execute commands from file", + .function = unix_cli_exec, +}; + +static clib_error_t * +unix_show_errors (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + unix_main_t * um = &unix_main; + clib_error_t * error = 0; + int i, n_errors_to_show; + unix_error_history_t * unix_errors = 0; + + n_errors_to_show = 1 << 30; + + if (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (! unformat (input, "%d", &n_errors_to_show)) + { + error = clib_error_return (0, "expecting integer number of errors to show, got `%U'", + format_unformat_error, input); + goto done; + } + } + + n_errors_to_show = clib_min (ARRAY_LEN (um->error_history), n_errors_to_show); + + i = um->error_history_index > 0 ? um->error_history_index - 1 : ARRAY_LEN (um->error_history) - 1; + + while (n_errors_to_show > 0) + { + unix_error_history_t * eh = um->error_history + i; + + if (! eh->error) + break; + + vec_add1 (unix_errors, eh[0]); + n_errors_to_show -= 1; + if (i == 0) + i = ARRAY_LEN (um->error_history) - 1; + else + i--; + } + + if (vec_len (unix_errors) == 0) + vlib_cli_output (vm, "no Unix errors so far"); + else + { + vlib_cli_output (vm, "%Ld total errors seen", um->n_total_errors); + for (i = vec_len (unix_errors) - 1; i >= 0; i--) + { + unix_error_history_t * eh = vec_elt_at_index (unix_errors, i); + vlib_cli_output (vm, "%U: %U", + format_time_interval, "h:m:s:u", eh->time, + format_clib_error, eh->error); + } + vlib_cli_output (vm, "%U: time now", + format_time_interval, "h:m:s:u", vlib_time_now (vm)); + } + + done: + vec_free (unix_errors); + return error; +} + +VLIB_CLI_COMMAND (cli_unix_show_errors, static) = { + .path = "show unix-errors", + .short_help = "Show Unix system call error history", + .function = unix_show_errors, +}; + +static clib_error_t * +unix_cli_init (vlib_main_t * vm) +{ + return 0; +} + +VLIB_INIT_FUNCTION (unix_cli_init); |