diff options
Diffstat (limited to 'src/vlib/unix')
-rw-r--r-- | src/vlib/unix/cj.c | 272 | ||||
-rw-r--r-- | src/vlib/unix/cj.h | 79 | ||||
-rw-r--r-- | src/vlib/unix/cli.c | 3468 | ||||
-rw-r--r-- | src/vlib/unix/dir.dox | 28 | ||||
-rw-r--r-- | src/vlib/unix/input.c | 272 | ||||
-rw-r--r-- | src/vlib/unix/main.c | 642 | ||||
-rw-r--r-- | src/vlib/unix/mc_socket.c | 1050 | ||||
-rw-r--r-- | src/vlib/unix/mc_socket.h | 137 | ||||
-rw-r--r-- | src/vlib/unix/plugin.c | 553 | ||||
-rw-r--r-- | src/vlib/unix/plugin.h | 126 | ||||
-rw-r--r-- | src/vlib/unix/unix.h | 168 | ||||
-rw-r--r-- | src/vlib/unix/util.c | 191 |
12 files changed, 6986 insertions, 0 deletions
diff --git a/src/vlib/unix/cj.c b/src/vlib/unix/cj.c new file mode 100644 index 00000000..7c1e9475 --- /dev/null +++ b/src/vlib/unix/cj.c @@ -0,0 +1,272 @@ +/* + *------------------------------------------------------------------ + * cj.c + * + * Copyright (c) 2013 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. + *------------------------------------------------------------------ + */ + +/** + * @file + * Circular joournal diagnostic mechanism. + * + * The @c cj thread-safe circular log buffer scheme is occasionally useful + * when chasing bugs. Calls to it should not be checked in. + */ +/*? %%clicmd:group_label Circular Journal %% ?*/ +/*? %%syscfg:group_label Circular Journal %% ?*/ + +#include <stdio.h> +#include <vlib/vlib.h> + +#include <vlib/unix/cj.h> + +cj_main_t cj_main; + +void +cj_log (u32 type, void *data0, void *data1) +{ + u64 new_tail; + cj_main_t *cjm = &cj_main; + cj_record_t *r; + + if (cjm->enable == 0) + return; + + new_tail = __sync_add_and_fetch (&cjm->tail, 1); + + r = (cj_record_t *) & (cjm->records[new_tail & (cjm->num_records - 1)]); + r->time = vlib_time_now (cjm->vlib_main); + r->thread_index = vlib_get_thread_index (); + r->type = type; + r->data[0] = pointer_to_uword (data0); + r->data[1] = pointer_to_uword (data1); +} + +void +cj_stop (void) +{ + cj_main_t *cjm = &cj_main; + + cjm->enable = 0; +} + + +clib_error_t * +cj_init (vlib_main_t * vm) +{ + cj_main_t *cjm = &cj_main; + + cjm->vlib_main = vm; + return 0; +} + +VLIB_INIT_FUNCTION (cj_init); + +static clib_error_t * +cj_config (vlib_main_t * vm, unformat_input_t * input) +{ + cj_main_t *cjm = &cj_main; + int matched = 0; + int enable = 0; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "records %d", &cjm->num_records)) + matched = 1; + else if (unformat (input, "on")) + enable = 1; + else + return clib_error_return (0, "cj_config: unknown input '%U'", + format_unformat_error, input); + } + + if (matched == 0) + return 0; + + cjm->num_records = max_pow2 (cjm->num_records); + vec_validate (cjm->records, cjm->num_records - 1); + memset (cjm->records, 0xff, cjm->num_records * sizeof (cj_record_t)); + cjm->tail = ~0; + cjm->enable = enable; + + return 0; +} + +/*? + * Configure the circular journal diagnostic mechanism. This is only useful + * if you, the deveoper, have written code to make use of the circular + * journal. + * + * @cfgcmd{records, <number>} + * Configure the number of records to allocate for the circular journal. + * + * @cfgcmd{on} + * Enable the collection of records in the circular journal at the + * earliest opportunity. +?*/ +VLIB_CONFIG_FUNCTION (cj_config, "cj"); + +void +cj_enable_disable (int is_enable) +{ + cj_main_t *cjm = &cj_main; + + if (cjm->num_records) + cjm->enable = is_enable; + else + vlib_cli_output (cjm->vlib_main, "CJ not configured..."); +} + +static inline void +cj_dump_one_record (cj_record_t * r) +{ + fprintf (stderr, "[%d]: %10.6f T%02d %llx %llx\n", + r->thread_index, r->time, r->type, + (long long unsigned int) r->data[0], + (long long unsigned int) r->data[1]); +} + +static void +cj_dump_internal (u8 filter0_enable, u64 filter0, + u8 filter1_enable, u64 filter1) +{ + cj_main_t *cjm = &cj_main; + cj_record_t *r; + u32 i, index; + + if (cjm->num_records == 0) + { + fprintf (stderr, "CJ not configured...\n"); + return; + } + + if (cjm->tail == (u64) ~ 0) + { + fprintf (stderr, "No data collected...\n"); + return; + } + + /* Has the trace wrapped? */ + index = (cjm->tail + 1) & (cjm->num_records - 1); + r = &(cjm->records[index]); + + if (r->thread_index != (u32) ~ 0) + { + /* Yes, dump from tail + 1 to the end */ + for (i = index; i < cjm->num_records; i++) + { + if (filter0_enable && (r->data[0] != filter0)) + goto skip; + if (filter1_enable && (r->data[1] != filter1)) + goto skip; + cj_dump_one_record (r); + skip: + r++; + } + } + /* dump from the beginning through the final tail */ + r = cjm->records; + for (i = 0; i <= cjm->tail; i++) + { + if (filter0_enable && (r->data[0] != filter0)) + goto skip2; + if (filter1_enable && (r->data[1] != filter1)) + goto skip2; + cj_dump_one_record (r); + skip2: + r++; + } +} + +void +cj_dump (void) +{ + cj_dump_internal (0, 0, 0, 0); +} + +void +cj_dump_filter_data0 (u64 filter0) +{ + cj_dump_internal (1 /* enable f0 */ , filter0, 0, 0); +} + +void +cj_dump_filter_data1 (u64 filter1) +{ + cj_dump_internal (0, 0, 1 /* enable f1 */ , filter1); +} + +void +cj_dump_filter_data12 (u64 filter0, u64 filter1) +{ + cj_dump_internal (1, filter0, 1, filter1); +} + +static clib_error_t * +cj_command_fn (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + int is_enable = -1; + int is_dump = -1; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "enable") || unformat (input, "on")) + is_enable = 1; + else if (unformat (input, "disable") || unformat (input, "off")) + is_enable = 0; + else if (unformat (input, "dump")) + is_dump = 1; + else + return clib_error_return (0, "unknown input `%U'", + format_unformat_error, input); + } + + if (is_enable >= 0) + cj_enable_disable (is_enable); + + if (is_dump > 0) + cj_dump (); + + return 0; +} + +/*? + * Enable, disable the collection of diagnostic data into a + * circular journal or dump the circular journal diagnostic data. + * This is only useful if you, the deveoper, have written code to make + * use of the circular journal. + * + * When dumping the data it is formatted and sent to @c stderr of the + * VPP process; when running VPP in <code>unix interactive</code> mode + * this is typically the same place as the Debug CLI. +?*/ + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (cj_command,static) = { + .path = "cj", + .short_help = "cj <enable | disable | dump>", + .function = cj_command_fn, +}; +/* *INDENT-ON* */ + + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vlib/unix/cj.h b/src/vlib/unix/cj.h new file mode 100644 index 00000000..d0a1d46e --- /dev/null +++ b/src/vlib/unix/cj.h @@ -0,0 +1,79 @@ +/* + *------------------------------------------------------------------ + * cj.h + * + * Copyright (c) 2013 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. + *------------------------------------------------------------------ + */ + +#ifndef __included_cj_h__ +#define __included_cj_h__ + +typedef struct +{ + f64 time; + u32 thread_index; + u32 type; + u64 data[2]; +} cj_record_t; + +typedef struct +{ + volatile u64 tail; + cj_record_t *records; + u32 num_records; + volatile u32 enable; + + vlib_main_t *vlib_main; +} cj_main_t; + +void cj_log (u32 type, void *data0, void *data1); + +/* + * Supply in application main, so we can log from any library... + * Declare a weak reference in the library, off you go. + */ + +#define DECLARE_CJ_GLOBAL_LOG \ +void cj_global_log (unsigned type, void * data0, void * data1) \ + __attribute__ ((weak)); \ + \ +unsigned __cj_type; \ +void * __cj_data0; \ +void * __cj_data1; \ + \ +void \ +cj_global_log (unsigned type, void * data0, void * data1) \ +{ \ + __cj_type = type; \ + __cj_data0 = data0; \ + __cj_data1 = data1; \ +} + +#define CJ_GLOBAL_LOG_PROTOTYPE +void +cj_global_log (unsigned type, void *data0, void *data1) +__attribute__ ((weak)); + +void cj_stop (void); + +#endif /* __included_cj_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vlib/unix/cli.c b/src/vlib/unix/cli.c new file mode 100644 index 00000000..be3c813a --- /dev/null +++ b/src/vlib/unix/cli.c @@ -0,0 +1,3468 @@ +/* + * 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. + */ +/** + * @file + * @brief Unix stdin/socket command line interface. + * Provides a command line interface so humans can interact with VPP. + * This is predominantly a debugging and testing mechanism. + */ +/*? %%clicmd:group_label Command line session %% ?*/ +/*? %%syscfg:group_label Command line session %% ?*/ + +#include <vlib/vlib.h> +#include <vlib/unix/unix.h> +#include <vppinfra/timer.h> + +#include <ctype.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <termios.h> +#include <signal.h> +#include <unistd.h> +#include <arpa/telnet.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <unistd.h> + +/** ANSI escape code. */ +#define ESC "\x1b" + +/** ANSI Control Sequence Introducer. */ +#define CSI ESC "[" + +/** ANSI clear screen. */ +#define ANSI_CLEAR CSI "2J" CSI "1;1H" +/** ANSI reset color settings. */ +#define ANSI_RESET CSI "0m" +/** ANSI Start bold text. */ +#define ANSI_BOLD CSI "1m" +/** ANSI Stop bold text. */ +#define ANSI_DIM CSI "2m" +/** ANSI Start dark red text. */ +#define ANSI_DRED ANSI_DIM CSI "31m" +/** ANSI Start bright red text. */ +#define ANSI_BRED ANSI_BOLD CSI "31m" +/** ANSI clear line cursor is on. */ +#define ANSI_CLEARLINE CSI "2K" +/** ANSI scroll screen down one line. */ +#define ANSI_SCROLLDN CSI "1T" +/** ANSI save cursor position. */ +#define ANSI_SAVECURSOR CSI "s" +/** ANSI restore cursor position if previously saved. */ +#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 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 + + +/** A CLI banner line. */ +typedef struct +{ + u8 *line; /**< The line to print. */ + u32 length; /**< The length of the line without terminating NUL. */ +} unix_cli_banner_t; + +#define _(a) { .line = (u8 *)(a), .length = sizeof(a) - 1 } +/** Plain welcome banner. */ +static unix_cli_banner_t unix_cli_banner[] = { + _(" _______ _ _ _____ ___ \n"), + _(" __/ __/ _ \\ (_)__ | | / / _ \\/ _ \\\n"), + _(" _/ _// // / / / _ \\ | |/ / ___/ ___/\n"), + _(" /_/ /____(_)_/\\___/ |___/_/ /_/ \n"), + _("\n") +}; + +/** ANSI color welcome banner. */ +static unix_cli_banner_t unix_cli_banner_color[] = { + _(ANSI_BRED " _______ _ " ANSI_RESET " _ _____ ___ \n"), + _(ANSI_BRED " __/ __/ _ \\ (_)__ " ANSI_RESET " | | / / _ \\/ _ \\\n"), + _(ANSI_BRED " _/ _// // / / / _ \\" ANSI_RESET " | |/ / ___/ ___/\n"), + _(ANSI_BRED " /_/ /____(_)_/\\___/" ANSI_RESET " |___/_/ /_/ \n"), + _("\n") +}; + +#undef _ + +/** Pager line index */ +typedef struct +{ + /** Index into pager_vector */ + u32 line; + + /** Offset of the string in the line */ + u32 offset; + + /** Length of the string in the line */ + u32 length; +} unix_cli_pager_index_t; + + +/** Unix CLI session. */ +typedef struct +{ + /** The file index held by unix.c */ + u32 clib_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; + + /** This session has command history. */ + u8 has_history; + /** Array of vectors of commands in the history. */ + u8 **command_history; + /** The command currently pointed at by the history cursor. */ + u8 *current_command; + /** How far from the end of the history array the user has browsed. */ + i32 excursion; + + /** Maximum number of history entries this session will store. */ + u32 history_limit; + + /** Current command line counter */ + u32 command_number; + + /** The string being searched for in the history. */ + u8 *search_key; + /** If non-zero then the CLI is searching in the history array. + * - @c -1 means search backwards. + * - @c 1 means search forwards. + */ + int search_mode; + + /** 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; + + /** Can we do ANSI output? */ + u8 ansi_capable; + + /** Has the session started? */ + u8 started; + + /** Disable the pager? */ + u8 no_pager; + + /** Whether the session is interactive or not. + * Controls things like initial banner, the CLI prompt etc. */ + u8 is_interactive; + + /** Whether the session is attached to a socket. */ + u8 is_socket; + + /** If EPIPE has been detected, prevent further write-related + * activity on the descriptor. + */ + u8 has_epipe; + + /** Pager buffer */ + u8 **pager_vector; + + /** Index of line fragments in the pager buffer */ + unix_cli_pager_index_t *pager_index; + + /** Line number of top of page */ + u32 pager_start; + + /** Terminal width */ + u32 width; + + /** Terminal height */ + u32 height; + + /** Process node identifier */ + u32 process_node_index; +} unix_cli_file_t; + +/** Resets the pager buffer and other data. + * @param f The CLI session whose pager needs to be reset. + */ +always_inline void +unix_cli_pager_reset (unix_cli_file_t * f) +{ + u8 **p; + + f->pager_start = 0; + + vec_free (f->pager_index); + f->pager_index = 0; + + vec_foreach (p, f->pager_vector) + { + vec_free (*p); + } + vec_free (f->pager_vector); + f->pager_vector = 0; +} + +/** Release storage used by a CLI session. + * @param f The CLI session whose storage needs to be released. + */ +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 */ +typedef enum +{ + UNIX_CLI_PARSE_ACTION_NOACTION = 0, /**< No action */ + UNIX_CLI_PARSE_ACTION_CRLF, /**< Carriage return, newline or enter */ + UNIX_CLI_PARSE_ACTION_TAB, /**< Tab key */ + UNIX_CLI_PARSE_ACTION_ERASE, /**< Erase cursor left */ + UNIX_CLI_PARSE_ACTION_ERASERIGHT, /**< Erase cursor right */ + UNIX_CLI_PARSE_ACTION_UP, /**< Up arrow */ + UNIX_CLI_PARSE_ACTION_DOWN, /**< Down arrow */ + UNIX_CLI_PARSE_ACTION_LEFT, /**< Left arrow */ + UNIX_CLI_PARSE_ACTION_RIGHT, /**< Right arrow */ + UNIX_CLI_PARSE_ACTION_HOME, /**< Home key (jump to start of line) */ + UNIX_CLI_PARSE_ACTION_END, /**< End key (jump to end of line) */ + UNIX_CLI_PARSE_ACTION_WORDLEFT, /**< Jump cursor to start of left word */ + 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_CLEAR, /**< Clear the terminal */ + UNIX_CLI_PARSE_ACTION_REVSEARCH, /**< Search backwards in command history */ + UNIX_CLI_PARSE_ACTION_FWDSEARCH, /**< Search forwards in command history */ + UNIX_CLI_PARSE_ACTION_YANK, /**< Undo last erase action */ + UNIX_CLI_PARSE_ACTION_TELNETIAC, /**< Telnet control code */ + + UNIX_CLI_PARSE_ACTION_PAGER_CRLF, /**< Enter pressed (CR, CRLF, LF, etc) */ + UNIX_CLI_PARSE_ACTION_PAGER_QUIT, /**< Exit the pager session */ + UNIX_CLI_PARSE_ACTION_PAGER_NEXT, /**< Scroll to next page */ + UNIX_CLI_PARSE_ACTION_PAGER_DN, /**< Scroll to next line */ + UNIX_CLI_PARSE_ACTION_PAGER_UP, /**< Scroll to previous line */ + UNIX_CLI_PARSE_ACTION_PAGER_TOP, /**< Scroll to first line */ + UNIX_CLI_PARSE_ACTION_PAGER_BOTTOM, /**< Scroll to last line */ + UNIX_CLI_PARSE_ACTION_PAGER_PGDN, /**< Scroll to next page */ + UNIX_CLI_PARSE_ACTION_PAGER_PGUP, /**< Scroll to previous page */ + UNIX_CLI_PARSE_ACTION_PAGER_REDRAW, /**< Clear and redraw the page on the terminal */ + UNIX_CLI_PARSE_ACTION_PAGER_SEARCH, /**< Search the pager buffer */ + + UNIX_CLI_PARSE_ACTION_PARTIALMATCH, /**< Action parser found a partial match */ + UNIX_CLI_PARSE_ACTION_NOMATCH /**< Action parser did not find any match */ +} unix_cli_parse_action_t; + +/** @brief Mapping of input buffer strings to action values. + * @note This won't work as a hash since we need to be able to do + * partial matches on the string. + */ +typedef struct +{ + u8 *input; /**< Input string to match. */ + u32 len; /**< Length of input without final NUL. */ + unix_cli_parse_action_t action; /**< Action to take when matched. */ +} unix_cli_parse_actions_t; + +/** @brief Given a capital ASCII letter character return a @c NUL terminated + * string with the control code for that letter. + * + * @param c An ASCII character. + * @return A @c NUL terminated string of type @c u8[]. + * + * @par Example + * @c CTL('A') returns <code>{ 0x01, 0x00 }</code> as a @c u8[]. + */ +#define CTL(c) (u8[]){ (c) - '@', 0 } + +#define _(a,b) { .input = (u8 *)(a), .len = sizeof(a) - 1, .action = (b) } +/** + * Patterns to match on a CLI input stream. + * @showinitializer + */ +static unix_cli_parse_actions_t unix_cli_parse_strings[] = { + /* Line handling */ + _("\r\n", UNIX_CLI_PARSE_ACTION_CRLF), /* Must be before '\r' */ + _("\n", UNIX_CLI_PARSE_ACTION_CRLF), + _("\r\0", UNIX_CLI_PARSE_ACTION_CRLF), /* Telnet does this */ + _("\r", UNIX_CLI_PARSE_ACTION_CRLF), + + /* Unix shell control codes */ + _(CTL ('B'), UNIX_CLI_PARSE_ACTION_LEFT), + _(CTL ('F'), UNIX_CLI_PARSE_ACTION_RIGHT), + _(CTL ('P'), UNIX_CLI_PARSE_ACTION_UP), + _(CTL ('N'), UNIX_CLI_PARSE_ACTION_DOWN), + _(CTL ('A'), UNIX_CLI_PARSE_ACTION_HOME), + _(CTL ('E'), UNIX_CLI_PARSE_ACTION_END), + _(CTL ('D'), UNIX_CLI_PARSE_ACTION_ERASERIGHT), + _(CTL ('U'), UNIX_CLI_PARSE_ACTION_ERASELINELEFT), + _(CTL ('K'), UNIX_CLI_PARSE_ACTION_ERASELINERIGHT), + _(CTL ('Y'), UNIX_CLI_PARSE_ACTION_YANK), + _(CTL ('L'), UNIX_CLI_PARSE_ACTION_CLEAR), + _(ESC "b", UNIX_CLI_PARSE_ACTION_WORDLEFT), /* Alt-B */ + _(ESC "f", UNIX_CLI_PARSE_ACTION_WORDRIGHT), /* Alt-F */ + _("\b", UNIX_CLI_PARSE_ACTION_ERASE), /* ^H */ + _("\x7f", UNIX_CLI_PARSE_ACTION_ERASE), /* Backspace */ + _("\t", UNIX_CLI_PARSE_ACTION_TAB), /* ^I */ + + /* VT100 Normal mode - Broadest support */ + _(CSI "A", UNIX_CLI_PARSE_ACTION_UP), + _(CSI "B", UNIX_CLI_PARSE_ACTION_DOWN), + _(CSI "C", UNIX_CLI_PARSE_ACTION_RIGHT), + _(CSI "D", UNIX_CLI_PARSE_ACTION_LEFT), + _(CSI "H", UNIX_CLI_PARSE_ACTION_HOME), + _(CSI "F", UNIX_CLI_PARSE_ACTION_END), + _(CSI "3~", UNIX_CLI_PARSE_ACTION_ERASERIGHT), /* Delete */ + _(CSI "1;5D", UNIX_CLI_PARSE_ACTION_WORDLEFT), /* C-Left */ + _(CSI "1;5C", UNIX_CLI_PARSE_ACTION_WORDRIGHT), /* C-Right */ + + /* VT100 Application mode - Some Gnome Terminal functions use these */ + _(ESC "OA", UNIX_CLI_PARSE_ACTION_UP), + _(ESC "OB", UNIX_CLI_PARSE_ACTION_DOWN), + _(ESC "OC", UNIX_CLI_PARSE_ACTION_RIGHT), + _(ESC "OD", UNIX_CLI_PARSE_ACTION_LEFT), + _(ESC "OH", UNIX_CLI_PARSE_ACTION_HOME), + _(ESC "OF", UNIX_CLI_PARSE_ACTION_END), + + /* ANSI X3.41-1974 - sent by Microsoft Telnet and PuTTY */ + _(CSI "1~", UNIX_CLI_PARSE_ACTION_HOME), + _(CSI "4~", UNIX_CLI_PARSE_ACTION_END), + + /* Emacs-ish history search */ + _(CTL ('S'), UNIX_CLI_PARSE_ACTION_FWDSEARCH), + _(CTL ('R'), UNIX_CLI_PARSE_ACTION_REVSEARCH), + + /* Other protocol things */ + _("\xff", UNIX_CLI_PARSE_ACTION_TELNETIAC), /* IAC */ + _("\0", UNIX_CLI_PARSE_ACTION_NOACTION), /* NUL */ + _(NULL, UNIX_CLI_PARSE_ACTION_NOMATCH) +}; + +/** + * Patterns to match when a CLI session is in the pager. + * @showinitializer + */ +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), + _(CTL ('L'), UNIX_CLI_PARSE_ACTION_PAGER_REDRAW), + _(CTL ('R'), UNIX_CLI_PARSE_ACTION_PAGER_REDRAW), + _("/", 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 _ + +/** CLI session events. */ +typedef enum +{ + UNIX_CLI_PROCESS_EVENT_READ_READY, /**< A file descriptor has data to be read. */ + UNIX_CLI_PROCESS_EVENT_QUIT, /**< A CLI session wants to close. */ +} unix_cli_process_event_type_t; + +/** CLI global state. */ +typedef struct +{ + /** Prompt string for CLI. */ + u8 *cli_prompt; + + /** Vec pool of CLI sessions. */ + unix_cli_file_t *cli_file_pool; + + /** Vec pool of unused session indices. */ + 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; + +/** CLI global state */ +static unix_cli_main_t unix_cli_main; + +/** + * @brief Search for a byte sequence in the action list. + * + * Searches the @ref unix_cli_parse_actions_t list in @a a for a match with + * the bytes in @a input of maximum length @a ilen bytes. + * When a match is made @a *matched indicates how many bytes were matched. + * Returns a value from the enum @ref unix_cli_parse_action_t to indicate + * whether no match was found, a partial match was found or a complete + * match was found and what action, if any, should be taken. + * + * @param[in] a Actions list to search within. + * @param[in] input String fragment to search for. + * @param[in] ilen Length of the string in 'input'. + * @param[out] matched Pointer to an integer that will contain the number + * of bytes matched when a complete match is found. + * + * @return Action from @ref unix_cli_parse_action_t that the string fragment + * matches. + * @ref UNIX_CLI_PARSE_ACTION_PARTIALMATCH is returned when the + * whole input string matches the start of at least one action. + * @ref UNIX_CLI_PARSE_ACTION_NOMATCH is returned when there is no + * match at all. + */ +static unix_cli_parse_action_t +unix_cli_match_action (unix_cli_parse_actions_t * a, + u8 * input, u32 ilen, i32 * matched) +{ + u8 partial = 0; + + while (a->input) + { + if (ilen >= a->len) + { + /* see if the start of the input buffer exactly matches the current + * action string. */ + if (memcmp (input, a->input, a->len) == 0) + { + *matched = a->len; + return a->action; + } + } + else + { + /* if the first ilen characters match, flag this as a partial - + * meaning keep collecting bytes in case of a future match */ + if (memcmp (input, a->input, ilen) == 0) + partial = 1; + } + + /* check next action */ + a++; + } + + return partial ? + UNIX_CLI_PARSE_ACTION_PARTIALMATCH : UNIX_CLI_PARSE_ACTION_NOMATCH; +} + + +/** Add bytes to the output vector and then flagg the I/O system that bytes + * are available to be sent. + */ +static void +unix_cli_add_pending_output (clib_file_t * uf, + unix_cli_file_t * cf, + u8 * buffer, uword buffer_bytes) +{ + clib_file_main_t *fm = &file_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) + fm->file_update (uf, UNIX_FILE_UPDATE_MODIFY); + } +} + +/** Delete all bytes from the output vector and flag the I/O system + * that no more bytes are available to be sent. + */ +static void +unix_cli_del_pending_output (clib_file_t * uf, + unix_cli_file_t * cf, uword n_bytes) +{ + clib_file_main_t *fm = &file_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) + fm->file_update (uf, UNIX_FILE_UPDATE_MODIFY); + } +} + +/** @brief A bit like strchr with a buffer length limit. + * Search a buffer for the first instance of a character up to the limit of + * the buffer length. If found then return the position of that character. + * + * The key departure from strchr is that if the character is not found then + * return the buffer length. + * + * @param chr The byte value to search for. + * @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 + * found then \c len instead. + */ +always_inline word +unix_vlib_findchr (u8 chr, u8 * str, word len) +{ + word i = 0; + for (i = 0; i < len; i++, str++) + { + if (*str == chr) + return i; + } + return len; +} + +/** @brief Send a buffer to the CLI stream if possible, enqueue it otherwise. + * Attempts to write given buffer to the file descriptor of the given + * Unix CLI session. If that session already has data in the output buffer + * or if the write attempt tells us to try again later then the given buffer + * is appended to the pending output buffer instead. + * + * This is typically called only from \c unix_vlib_cli_output_cooked since + * that is where CRLF handling occurs or from places where we explicitly do + * not want cooked handling. + * + * @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. + * @param buffer Pointer to the buffer that needs to be written. + * @param buffer_bytes The number of bytes from \c buffer to write. + */ +static void +unix_vlib_cli_output_raw (unix_cli_file_t * cf, + clib_file_t * uf, u8 * buffer, uword buffer_bytes) +{ + int n = 0; + + if (cf->has_epipe) /* don't try writing anything */ + return; + + if (vec_len (cf->output_vector) == 0) + { + if (cf->is_socket) + /* If it's a socket we use MSG_NOSIGNAL to prevent SIGPIPE */ + n = send (uf->file_descriptor, buffer, buffer_bytes, MSG_NOSIGNAL); + else + n = write (uf->file_descriptor, buffer, buffer_bytes); + } + + if (n < 0 && errno != EAGAIN) + { + if (errno == EPIPE) + { + /* connection closed on us */ + unix_main_t *um = &unix_main; + cf->has_epipe = 1; + vlib_process_signal_event (um->vlib_main, cf->process_node_index, + UNIX_CLI_PROCESS_EVENT_QUIT, + uf->private_data); + } + else + { + clib_unix_warning ("write"); + } + } + else if ((word) n < (word) buffer_bytes) + { + /* We got EAGAIN or we already have stuff in the buffer; + * queue up whatever didn't get sent for later. */ + if (n < 0) + n = 0; + unix_cli_add_pending_output (uf, cf, buffer + n, buffer_bytes - n); + } +} + +/** @brief Process a buffer for CRLF handling before outputting it to the CLI. + * + * @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. + * @param buffer Pointer to the buffer that needs to be written. + * @param buffer_bytes The number of bytes from \c buffer to write. + */ +static void +unix_vlib_cli_output_cooked (unix_cli_file_t * cf, + clib_file_t * uf, + u8 * buffer, uword buffer_bytes) +{ + word end = 0, start = 0; + + while (end < buffer_bytes) + { + if (cf->crlf_mode) + { + /* iterate the line on \n's so we can insert a \r before it */ + end = unix_vlib_findchr ('\n', + buffer + start, + buffer_bytes - start) + start; + } + else + { + /* otherwise just send the whole buffer */ + end = buffer_bytes; + } + + unix_vlib_cli_output_raw (cf, uf, buffer + start, end - start); + + if (cf->crlf_mode) + { + if (end < buffer_bytes) + { + unix_vlib_cli_output_raw (cf, uf, (u8 *) "\r\n", 2); + end++; /* skip the \n that we already sent */ + } + start = end; + } + } +} + +/** @brief Output the CLI prompt */ +static void +unix_cli_cli_prompt (unix_cli_file_t * cf, clib_file_t * uf) +{ + unix_cli_main_t *cm = &unix_cli_main; + + if (cf->is_interactive) /* Only interactive sessions get a prompt */ + unix_vlib_cli_output_raw (cf, uf, cm->cli_prompt, + vec_len (cm->cli_prompt)); +} + +/** @brief Output a pager prompt and show number of buffered lines */ +static void +unix_cli_pager_prompt (unix_cli_file_t * cf, clib_file_t * uf) +{ + u8 *prompt; + u32 h; + + h = cf->pager_start + (cf->height - 1); + if (h > vec_len (cf->pager_index)) + h = vec_len (cf->pager_index); + + prompt = format (0, "\r%s-- more -- (%d-%d/%d)%s", + cf->ansi_capable ? ANSI_BOLD : "", + cf->pager_start + 1, + h, + vec_len (cf->pager_index), + 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, clib_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, clib_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, clib_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); +} + +/** Redraw the currently displayed page of text. + * @param cf CLI session to redraw the pager buffer of. + * @param uf Unix file of the CLI session. + */ +static void +unix_cli_pager_redraw (unix_cli_file_t * cf, clib_file_t * uf) +{ + unix_cli_pager_index_t *pi = NULL; + u8 *line = NULL; + word i; + + /* No active pager? Do nothing. */ + if (!vec_len (cf->pager_index)) + return; + + if (cf->ansi_capable) + { + /* If we have ANSI, send the clear screen sequence */ + unix_vlib_cli_output_cooked (cf, uf, + (u8 *) ANSI_CLEAR, + sizeof (ANSI_CLEAR) - 1); + } + else + { + /* Otherwise make sure we're on a blank line */ + unix_cli_pager_prompt_erase (cf, uf); + } + + /* (Re-)send the current page of content */ + for (i = 0; i < cf->height - 1 && + i + cf->pager_start < vec_len (cf->pager_index); i++) + { + pi = &cf->pager_index[cf->pager_start + i]; + line = cf->pager_vector[pi->line] + pi->offset; + + unix_vlib_cli_output_cooked (cf, uf, line, pi->length); + } + /* if the last line didn't end in newline, add a newline */ + if (pi && line[pi->length - 1] != '\n') + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1); + + unix_cli_pager_prompt (cf, uf); +} + +/** @brief Process and add a line to the pager index. + * In normal operation this function will take the given character string + * found in @c line and with length @c len_or_index and iterates the over the + * contents, adding each line of text discovered within it to the + * pager index. Lines are identified by newlines ("<code>\\n</code>") and by + * strings longer than the width of the terminal. + * + * 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 + * typical if we are reindexing the pager buffer. + * + * @param cf The CLI session whose pager we are adding to. + * @param line The string of text to be indexed into the pager buffer. + * If @c line is @c NULL then the mode of operation + * changes slightly; see the description above. + * @param len_or_index If @c line is a pointer to a string then this parameter + * indicates the length of that string; Otherwise this + * value provides the index in the pager buffer of an + * existing string to be indexed. + */ +static void +unix_cli_pager_add_line (unix_cli_file_t * cf, u8 * line, word len_or_index) +{ + u8 *p; + word i, j, k; + word line_index, len; + u32 width = cf->width; + unix_cli_pager_index_t *pi; + + if (line == NULL) + { + /* Use a line already in the pager buffer */ + line_index = len_or_index; + p = cf->pager_vector[line_index]; + len = vec_len (p); + } + else + { + len = len_or_index; + /* Add a copy of the raw string to the pager buffer */ + p = vec_new (u8, len); + clib_memcpy (p, line, len); + + /* store in pager buffer */ + line_index = vec_len (cf->pager_vector); + vec_add1 (cf->pager_vector, p); + } + + i = 0; + while (i < len) + { + /* Find the next line, or run to terminal width, or run to EOL */ + int l = len - i; + j = unix_vlib_findchr ((u8) '\n', p, l < width ? l : width); + + if (j < l && p[j] == '\n') /* incl \n */ + j++; + + /* Add the line to the index */ + k = vec_len (cf->pager_index); + vec_validate (cf->pager_index, k); + pi = &cf->pager_index[k]; + + pi->line = line_index; + pi->offset = i; + pi->length = j; + + i += j; + p += j; + } +} + +/** @brief Reindex entire pager buffer. + * Resets the current pager index and then re-adds the lines in the pager + * buffer to the index. + * + * Additionally this function attempts to retain the current page start + * line offset by searching for the same top-of-screen line in the new index. + * + * @param cf The CLI session whose pager buffer should be reindexed. + */ +static void +unix_cli_pager_reindex (unix_cli_file_t * cf) +{ + word i, old_line, old_offset; + unix_cli_pager_index_t *pi; + + /* If there is nothing in the pager buffer then make sure the index + * is empty and move on. + */ + if (cf->pager_vector == 0) + { + vec_reset_length (cf->pager_index); + return; + } + + /* Retain a pointer to the current page start line so we can + * find it later + */ + pi = &cf->pager_index[cf->pager_start]; + old_line = pi->line; + old_offset = pi->offset; + + /* Re-add the buffered lines to the index */ + vec_reset_length (cf->pager_index); + vec_foreach_index (i, cf->pager_vector) + { + unix_cli_pager_add_line (cf, NULL, i); + } + + /* Attempt to re-locate the previously stored page start line */ + vec_foreach_index (i, cf->pager_index) + { + pi = &cf->pager_index[i]; + + if (pi->line == old_line && + (pi->offset <= old_offset || pi->offset + pi->length > old_offset)) + { + /* Found it! */ + cf->pager_start = i; + break; + } + } + + /* In case the start line was not found (rare), ensure the pager start + * index is within bounds + */ + if (cf->pager_start >= vec_len (cf->pager_index)) + { + if (!cf->height || vec_len (cf->pager_index) < (cf->height - 1)) + cf->pager_start = 0; + else + cf->pager_start = vec_len (cf->pager_index) - (cf->height - 1); + } +} + +/** VLIB CLI output function. + * + * If the terminal has a pager configured then this function takes care + * of collating output into the pager buffer; ensuring only the first page + * is displayed and any lines in excess of the first page are buffered. + * + * If the maximum number of index lines in the buffer is exceeded then the + * pager is cancelled and the contents of the current buffer are sent to the + * terminal. + * + * If there is no pager configured then the output is sent directly to the + * terminal. + * + * @param cli_file_index Index of the CLI session where this output is + * directed. + * @param buffer String of printabe bytes to be output. + * @param buffer_bytes The number of bytes in @c buffer to be output. + */ +static void +unix_vlib_cli_output (uword cli_file_index, u8 * buffer, uword buffer_bytes) +{ + unix_main_t *um = &unix_main; + clib_file_main_t *fm = &file_main; + unix_cli_main_t *cm = &unix_cli_main; + unix_cli_file_t *cf; + clib_file_t *uf; + + cf = pool_elt_at_index (cm->cli_file_pool, cli_file_index); + uf = pool_elt_at_index (fm->file_pool, cf->clib_file_index); + + if (cf->no_pager || um->cli_pager_buffer_limit == 0 || cf->height == 0) + { + unix_vlib_cli_output_cooked (cf, uf, buffer, buffer_bytes); + } + else + { + word row = vec_len (cf->pager_index); + u8 *line; + unix_cli_pager_index_t *pi; + + /* Index and add the output lines to the pager buffer. */ + unix_cli_pager_add_line (cf, buffer, buffer_bytes); + + /* Now iterate what was added to display the lines. + * If we reach the bottom of the page, display a prompt. + */ + while (row < vec_len (cf->pager_index)) + { + if (row < cf->height - 1) + { + /* output this line */ + pi = &cf->pager_index[row]; + line = cf->pager_vector[pi->line] + pi->offset; + unix_vlib_cli_output_cooked (cf, uf, line, pi->length); + + /* if the last line didn't end in newline, and we're at the + * bottom of the page, add a newline */ + if (line[pi->length - 1] != '\n' && row == cf->height - 2) + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1); + } + else + { + /* Display the pager prompt every 10 lines */ + if (!(row % 10)) + unix_cli_pager_prompt (cf, uf); + } + row++; + } + + /* Check if we went over the pager buffer limit */ + if (vec_len (cf->pager_index) > 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 (vec_len (cf->pager_index) > cf->height - 1) + unix_cli_pager_prompt_erase (cf, uf); + + /* Dump out the contents of the buffer */ + for (row = cf->pager_start + (cf->height - 1); + row < vec_len (cf->pager_index); row++) + { + pi = &cf->pager_index[row]; + line = cf->pager_vector[pi->line] + pi->offset; + unix_vlib_cli_output_cooked (cf, uf, line, pi->length); + } + + unix_cli_pager_reset (cf); + } + } +} + +/** Identify whether a terminal type is ANSI capable. + * + * Compares the string given in @c term with a list of terminal types known + * to support ANSI escape sequences. + * + * This list contains, for example, @c xterm, @c screen and @c ansi. + * + * @param term A string with a terminal type in it. + * @param len The length of the string in @c term. + * + * @return @c 1 if the terminal type is recognized as supporting ANSI + * terminal sequences; @c 0 otherwise. + */ +static u8 +unix_cli_terminal_type_ansi (u8 * term, uword len) +{ + /* This may later be better done as a hash of some sort. */ +#define _(a) do { \ + if (strncasecmp(a, (char *)term, (size_t)len) == 0) return 1; \ + } while(0) + + _("xterm"); + _("xterm-color"); + _("xterm-256color"); /* iTerm on Mac */ + _("screen"); + _("screen-256color"); /* Screen and tmux */ + _("ansi"); /* Microsoft Telnet */ +#undef _ + + return 0; +} + +/** Identify whether a terminal type is non-interactive. + * + * Compares the string given in @c term with a list of terminal types known + * to be non-interactive, as send by tools such as @c vppctl . + * + * This list contains, for example, @c vppctl. + * + * @param term A string with a terminal type in it. + * @param len The length of the string in @c term. + * + * @return @c 1 if the terminal type is recognized as being non-interactive; + * @c 0 otherwise. + */ +static u8 +unix_cli_terminal_type_noninteractive (u8 * term, uword len) +{ + /* This may later be better done as a hash of some sort. */ +#define _(a) do { \ + if (strncasecmp(a, (char *)term, (size_t)len) == 0) return 1; \ + } while(0) + + _("vppctl"); +#undef _ + + return 0; +} + +/** Set a session to be non-interactive. */ +static void +unix_cli_set_session_noninteractive (unix_cli_file_t * cf) +{ + /* Non-interactive sessions don't get these */ + cf->is_interactive = 0; + cf->no_pager = 1; + cf->history_limit = 0; + cf->has_history = 0; + cf->line_mode = 1; +} + +/** @brief Emit initial welcome banner and prompt on a connection. */ +static void +unix_cli_file_welcome (unix_cli_main_t * cm, unix_cli_file_t * cf) +{ + unix_main_t *um = &unix_main; + clib_file_main_t *fm = &file_main; + clib_file_t *uf = pool_elt_at_index (fm->file_pool, cf->clib_file_index); + unix_cli_banner_t *banner; + int i, len; + + /* Mark the session as started if we get here */ + cf->started = 1; + + if (!(cf->is_interactive)) /* No banner for non-interactive sessions */ + return; + + /* + * Put the first bytes directly into the buffer so that further output is + * queued until everything is ready. (oterwise initial prompt can appear + * mid way through VPP initialization) + */ + unix_cli_add_pending_output (uf, cf, (u8 *) "\r", 1); + + if (!um->cli_no_banner) + { + if (cf->ansi_capable) + { + banner = unix_cli_banner_color; + len = ARRAY_LEN (unix_cli_banner_color); + } + else + { + banner = unix_cli_banner; + len = ARRAY_LEN (unix_cli_banner); + } + + for (i = 0; i < len; i++) + { + unix_vlib_cli_output_cooked (cf, uf, + banner[i].line, banner[i].length); + } + } + + /* Prompt. */ + unix_cli_cli_prompt (cf, uf); + +} + +/** @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) +{ + unix_cli_main_t *cm = &unix_cli_main; + unix_cli_file_t *cf; + (void) delay; + + /* Check the connection didn't close already */ + if (pool_is_free_index (cm->cli_file_pool, (uword) arg)) + return; + + cf = pool_elt_at_index (cm->cli_file_pool, (uword) arg); + + if (!cf->started) + unix_cli_file_welcome (cm, cf); +} + +/** @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. + * + * @return -1 if we need more bytes, otherwise a positive integer number of + * bytes to consume from the input_vector, not including the initial + * IAC byte. + */ +static i32 +unix_cli_process_telnet (unix_main_t * um, + unix_cli_file_t * cf, + clib_file_t * uf, u8 * input_vector, uword len) +{ + /* Input_vector starts at IAC byte. + * See if we have a complete message; if not, return -1 so we wait for more. + * if we have a complete message, consume those bytes from the vector. + */ + i32 consume = 0; + + if (len == 1) + return -1; /* want more bytes */ + + switch (input_vector[1]) + { + case IAC: + /* two IAC's in a row means to pass through 0xff. + * since that makes no sense here, just consume it. + */ + consume = 1; + break; + + case WILL: + case WONT: + case DO: + case DONT: + /* Expect 3 bytes */ + if (vec_len (input_vector) < 3) + return -1; /* want more bytes */ + + consume = 2; + break; + + case SB: + { + /* Sub option - search ahead for IAC SE to end it */ + i32 i; + for (i = 3; i < len && i < UNIX_CLI_MAX_DEPTH_TELNET; i++) + { + if (input_vector[i - 1] == IAC && input_vector[i] == SE) + { + /* We have a complete message; see if we care about it */ + switch (input_vector[2]) + { + case TELOPT_TTYPE: + if (input_vector[3] != 0) + break; + { + /* See if the the terminal type is recognized */ + u8 *term = input_vector + 4; + uword len = i - 5; + + /* See if the terminal type is ANSI capable */ + cf->ansi_capable = + unix_cli_terminal_type_ansi (term, len); + + /* See if the terminal type indicates non-interactive */ + if (unix_cli_terminal_type_noninteractive (term, len)) + unix_cli_set_session_noninteractive (cf); + } + + /* If session not started, we can release the pause */ + if (!cf->started) + /* Send the welcome banner and initial prompt */ + 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))); + 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; + + 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; + + /* reindex pager buffer */ + unix_cli_pager_reindex (cf); + /* redraw page */ + unix_cli_pager_redraw (cf, uf); + break; + + default: + break; + } + /* Consume it all */ + consume = i; + break; + } + } + + if (i == UNIX_CLI_MAX_DEPTH_TELNET) + consume = 1; /* hit max search depth, advance one byte */ + + if (consume == 0) + return -1; /* want more bytes */ + + break; + } + + case GA: + case EL: + case EC: + case AO: + case IP: + case BREAK: + case DM: + case NOP: + case SE: + case EOR: + case ABORT: + case SUSP: + case xEOF: + /* Simple one-byte messages */ + consume = 1; + break; + + case AYT: + /* Are You There - trigger a visible response */ + consume = 1; + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "fd.io VPP\n", 10); + break; + + default: + /* Unknown command! Eat the IAC byte */ + break; + } + + return consume; +} + +/** @brief Process actionable input. + * Based on the \c action process the input; this typically involves + * searching the command history or editing the current command line. + */ +static int +unix_cli_line_process_one (unix_cli_main_t * cm, + unix_main_t * um, + unix_cli_file_t * cf, + clib_file_t * uf, + u8 input, unix_cli_parse_action_t action) +{ + u8 *prev; + u8 *save = 0; + u8 **possible_commands; + int j, delta; + + switch (action) + { + case UNIX_CLI_PARSE_ACTION_NOACTION: + break; + + 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) */ + for (j = 0; j < (vec_len (cf->current_command)); j++) + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b \b", 3); + + 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 + { + if (action == UNIX_CLI_PARSE_ACTION_REVSEARCH) + cf->search_mode = -1; + else + cf->search_mode = 1; + + cf->excursion += cf->search_mode; + goto search_again; + } + break; + + case UNIX_CLI_PARSE_ACTION_ERASELINELEFT: + /* Erase the command from the cursor to the start */ + + /* Shimmy forwards 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); + /* Zap from here to the end of what is currently displayed */ + for (; j < (vec_len (cf->current_command)); j++) + 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); + + j = vec_len (cf->current_command) - cf->cursor; + memmove (cf->current_command, cf->current_command + cf->cursor, j); + _vec_len (cf->current_command) = j; + + /* Print the new contents */ + unix_vlib_cli_output_cooked (cf, uf, cf->current_command, j); + /* 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; + + cf->search_mode = 0; + break; + + case UNIX_CLI_PARSE_ACTION_ERASELINERIGHT: + /* Erase the command from the cursor to the end */ + + /* Zap from cursor to end of what is currently displayed */ + for (j = cf->cursor; j < (vec_len (cf->current_command)); j++) + 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); + + /* Truncate the line at the cursor */ + _vec_len (cf->current_command) = cf->cursor; + + cf->search_mode = 0; + break; + + case UNIX_CLI_PARSE_ACTION_LEFT: + if (cf->cursor > 0) + { + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1); + cf->cursor--; + } + + cf->search_mode = 0; + break; + + case UNIX_CLI_PARSE_ACTION_RIGHT: + if (cf->cursor < vec_len (cf->current_command)) + { + /* have to emit the character under the cursor */ + unix_vlib_cli_output_cooked (cf, uf, + cf->current_command + cf->cursor, 1); + cf->cursor++; + } + + cf->search_mode = 0; + break; + + 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++) + 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); + vec_reset_length (cf->current_command); + if (vec_len (cf->command_history)) + { + if (action == UNIX_CLI_PARSE_ACTION_UP) + delta = -1; + else + delta = 1; + + cf->excursion += delta; + + if (cf->excursion == vec_len (cf->command_history)) + { + /* down-arrowed to last entry - want a blank line */ + _vec_len (cf->current_command) = 0; + } + else if (cf->excursion < 0) + { + /* up-arrowed over the start to the end, want a blank line */ + cf->excursion = vec_len (cf->command_history); + _vec_len (cf->current_command) = 0; + } + else + { + if (cf->excursion > (i32) vec_len (cf->command_history) - 1) + /* down-arrowed past end - wrap to start */ + cf->excursion = 0; + + /* Print the command at the current position */ + prev = cf->command_history[cf->excursion]; + vec_validate (cf->current_command, vec_len (prev) - 1); + + clib_memcpy (cf->current_command, prev, vec_len (prev)); + _vec_len (cf->current_command) = vec_len (prev); + unix_vlib_cli_output_cooked (cf, uf, cf->current_command, + vec_len (cf->current_command)); + } + } + cf->cursor = vec_len (cf->current_command); + break; + + case UNIX_CLI_PARSE_ACTION_HOME: + if (vec_len (cf->current_command) && cf->cursor > 0) + { + while (cf->cursor) + { + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1); + cf->cursor--; + } + } + + cf->search_mode = 0; + break; + + case UNIX_CLI_PARSE_ACTION_END: + if (vec_len (cf->current_command) && + cf->cursor < vec_len (cf->current_command)) + { + 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->search_mode = 0; + break; + + 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--; + + while (j && isspace (cf->current_command[j])) + { + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1); + j--; + } + while (j && !isspace (cf->current_command[j])) + { + if (isspace (cf->current_command[j - 1])) + break; + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1); + j--; + } + + cf->cursor = j; + } + + cf->search_mode = 0; + break; + + case UNIX_CLI_PARSE_ACTION_WORDRIGHT: + if (vec_len (cf->current_command) && + cf->cursor < vec_len (cf->current_command)) + { + int e = vec_len (cf->current_command); + j = cf->cursor; + while (j < e && !isspace (cf->current_command[j])) + j++; + while (j < e && isspace (cf->current_command[j])) + j++; + unix_vlib_cli_output_cooked (cf, uf, + cf->current_command + cf->cursor, + j - cf->cursor); + cf->cursor = j; + } + + cf->search_mode = 0; + break; + + + case UNIX_CLI_PARSE_ACTION_ERASE: + if (vec_len (cf->current_command)) + { + if (cf->cursor == vec_len (cf->current_command)) + { + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b \b", 3); + _vec_len (cf->current_command)--; + cf->cursor--; + } + else if (cf->cursor > 0) + { + /* shift everything at & to the right of the cursor left by 1 */ + j = vec_len (cf->current_command) - cf->cursor; + 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_cooked (cf, uf, + cf->current_command + cf->cursor, + j); + unix_vlib_cli_output_cooked (cf, uf, (u8 *) " \b\b", 3); + /* and shift the terminal cursor back where it should be */ + while (--j) + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1); + } + } + cf->search_mode = 0; + cf->excursion = 0; + vec_reset_length (cf->search_key); + break; + + case UNIX_CLI_PARSE_ACTION_ERASERIGHT: + if (vec_len (cf->current_command)) + { + if (cf->cursor < vec_len (cf->current_command)) + { + /* shift everything to the right of the cursor left by 1 */ + j = vec_len (cf->current_command) - cf->cursor - 1; + memmove (cf->current_command + cf->cursor, + cf->current_command + cf->cursor + 1, j); + _vec_len (cf->current_command)--; + /* redraw the rest of the line */ + unix_vlib_cli_output_cooked (cf, uf, + cf->current_command + cf->cursor, + j); + unix_vlib_cli_output_cooked (cf, uf, (u8 *) " \b", 2); + /* and shift the terminal cursor back where it should be */ + if (j) + { + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1); + while (--j) + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1); + } + } + } + else if (input == 'D' - '@') + { + /* ^D with no command entered = quit */ + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "quit\n", 5); + vlib_process_signal_event (um->vlib_main, + vlib_current_process (um->vlib_main), + UNIX_CLI_PROCESS_EVENT_QUIT, + cf - cm->cli_file_pool); + } + cf->search_mode = 0; + cf->excursion = 0; + vec_reset_length (cf->search_key); + break; + + case UNIX_CLI_PARSE_ACTION_CLEAR: + /* If we're in ANSI mode, clear the screen. + * Then redraw the prompt and any existing command input, then put + * the cursor back where it was in that line. + */ + if (cf->ansi_capable) + unix_vlib_cli_output_cooked (cf, uf, + (u8 *) ANSI_CLEAR, + sizeof (ANSI_CLEAR) - 1); + else + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1); + + 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); + + break; + + case UNIX_CLI_PARSE_ACTION_TAB: + if (cf->cursor < vec_len (cf->current_command)) + { + /* if we are in the middle of a line, complete only if + * the cursor points to whitespace */ + if (isspace (cf->current_command[cf->cursor])) + { + /* save and clear any input that is after the cursor */ + vec_resize (save, vec_len (cf->current_command) - cf->cursor); + clib_memcpy (save, cf->current_command + cf->cursor, + vec_len (cf->current_command) - cf->cursor); + _vec_len (cf->current_command) = cf->cursor; + } + else + { + unix_vlib_cli_output_raw (cf, uf, (u8 *) "\a", 1); + break; + } + } + possible_commands = + vlib_cli_get_possible_completions (cf->current_command); + if (vec_len (possible_commands) == 1) + { + u32 j = cf->cursor; + u8 *completed = possible_commands[0]; + + /* find the last word of current_command */ + while (j >= 1 && !isspace (cf->current_command[j - 1])) + { + j--; + unix_vlib_cli_output_raw (cf, uf, (u8 *) "\b", 1); + } + _vec_len (cf->current_command) = j; + + /* replace it with the newly expanded command */ + vec_append (cf->current_command, completed); + + /* echo to the terminal */ + unix_vlib_cli_output_raw (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); + } + + cf->cursor = vec_len (cf->current_command); + + } + else if (vec_len (possible_commands) >= 2) + { + u8 **possible_command; + uword max_command_len = 0, min_command_len = ~0; + u32 i, j; + + vec_foreach (possible_command, possible_commands) + { + if (vec_len (*possible_command) > max_command_len) + { + max_command_len = vec_len (*possible_command); + } + if (vec_len (*possible_command) < min_command_len) + { + min_command_len = vec_len (*possible_command); + } + } + + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1); + + i = 0; + vec_foreach (possible_command, possible_commands) + { + if (i + max_command_len >= cf->width) + { + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1); + i = 0; + } + unix_vlib_cli_output_raw (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); + } + i += max_command_len + 2; + } + + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1); + + /* rewrite prompt */ + unix_cli_cli_prompt (cf, uf); + unix_vlib_cli_output_raw (cf, uf, cf->current_command, + vec_len (cf->current_command)); + + /* count length of last word */ + j = cf->cursor; + i = 0; + while (j >= 1 && !isspace (cf->current_command[j - 1])) + { + j--; + i++; + } + + /* determine smallest common command */ + for (; i < min_command_len; i++) + { + u8 common = '\0'; + int stop = 0; + vec_foreach (possible_command, possible_commands) + { + if (common == '\0') + { + common = (*possible_command)[i]; + } + else if (common != (*possible_command)[i]) + { + stop = 1; + break; + } + } + if (!stop) + { + vec_add1 (cf->current_command, common); + cf->cursor++; + unix_vlib_cli_output_raw (cf, uf, (u8 *) & common, 1); + } + else + { + break; + } + } + } + else + { + unix_vlib_cli_output_raw (cf, uf, (u8 *) "\a", 1); + } + + if (vec_len (save) > 0) + { + /* restore remaining input if tab was hit in the middle of a line */ + unix_vlib_cli_output_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); + } + vec_append (cf->current_command, save); + vec_free (save); + } + vec_free (possible_commands); + + break; + 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 < vec_len (cf->pager_index)) + { + u8 *line = NULL; + unix_cli_pager_index_t *pi = NULL; + + int m = cf->pager_start + (cf->height - 1); + unix_cli_pager_prompt_erase (cf, uf); + for (j = m; + j < vec_len (cf->pager_index) && cf->pager_start < m; + j++, cf->pager_start++) + { + pi = &cf->pager_index[j]; + line = cf->pager_vector[pi->line] + pi->offset; + unix_vlib_cli_output_cooked (cf, uf, line, pi->length); + } + /* if the last line didn't end in newline, add a newline */ + if (pi && line[pi->length - 1] != '\n') + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1); + 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 <space> */ + 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 < vec_len (cf->pager_index) - (cf->height - 1)) + { + u8 *line; + unix_cli_pager_index_t *pi; + + unix_cli_pager_prompt_erase (cf, uf); + pi = &cf->pager_index[cf->pager_start + (cf->height - 1)]; + line = cf->pager_vector[pi->line] + pi->offset; + unix_vlib_cli_output_cooked (cf, uf, line, pi->length); + cf->pager_start++; + /* if the last line didn't end in newline, add a newline */ + if (line[pi->length - 1] != '\n') + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1); + 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 <enter> */ + goto pager_quit; + } + + break; + + case UNIX_CLI_PARSE_ACTION_PAGER_UP: + /* scroll the page back one line */ + if (cf->pager_start > 0) + { + u8 *line = NULL; + unix_cli_pager_index_t *pi = NULL; + + cf->pager_start--; + if (cf->ansi_capable) + { + pi = &cf->pager_index[cf->pager_start]; + line = cf->pager_vector[pi->line] + pi->offset; + 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, line, pi->length); + 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 < vec_len (cf->pager_index) && j < m; j++) + { + pi = &cf->pager_index[j]; + line = cf->pager_vector[pi->line] + pi->offset; + unix_vlib_cli_output_cooked (cf, uf, line, pi->length); + } + /* if the last line didn't end in newline, add a newline */ + if (pi && line[pi->length - 1] != '\n') + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1); + 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 = NULL; + unix_cli_pager_index_t *pi = NULL; + + 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 < vec_len (cf->pager_index) && j < m; + j++) + { + pi = &cf->pager_index[j]; + line = cf->pager_vector[pi->line] + pi->offset; + unix_vlib_cli_output_cooked (cf, uf, line, pi->length); + } + /* if the last line didn't end in newline, add a newline */ + if (pi && line[pi->length - 1] != '\n') + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1); + 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 < vec_len (cf->pager_index) - (cf->height - 1)) + { + u8 *line = NULL; + unix_cli_pager_index_t *pi = NULL; + + cf->pager_start = vec_len (cf->pager_index) - (cf->height - 1); + unix_cli_pager_prompt_erase (cf, uf); + unix_cli_pager_message (cf, uf, "skipping", "\n"); + for (j = cf->pager_start; j < vec_len (cf->pager_index); j++) + { + pi = &cf->pager_index[j]; + line = cf->pager_vector[pi->line] + pi->offset; + unix_vlib_cli_output_cooked (cf, uf, line, pi->length); + } + /* if the last line didn't end in newline, add a newline */ + if (pi && line[pi->length - 1] != '\n') + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1); + 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 = NULL; + unix_cli_pager_index_t *pi = NULL; + int m; + + if (cf->pager_start >= cf->height) + cf->pager_start -= cf->height - 1; + else + cf->pager_start = 0; + m = cf->pager_start + cf->height - 1; + unix_cli_pager_prompt_erase (cf, uf); + for (j = cf->pager_start; j < vec_len (cf->pager_index) && j < m; + j++) + { + pi = &cf->pager_index[j]; + line = cf->pager_vector[pi->line] + pi->offset; + unix_vlib_cli_output_cooked (cf, uf, line, pi->length); + } + /* if the last line didn't end in newline, add a newline */ + if (pi && line[pi->length - 1] != '\n') + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1); + unix_cli_pager_prompt (cf, uf); + } + break; + + case UNIX_CLI_PARSE_ACTION_PAGER_REDRAW: + /* Redraw the current pager screen */ + unix_cli_pager_redraw (cf, uf); + break; + + case UNIX_CLI_PARSE_ACTION_PAGER_SEARCH: + /* search forwards in the buffer */ + break; + + + case UNIX_CLI_PARSE_ACTION_CRLF: + crlf: + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1); + + if (cf->has_history && cf->history_limit) + { + if (cf->command_history + && 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)) + { + /* Don't duplicate the previous command */ + 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)) + { + /* copy the command to the history */ + u8 *c = 0; + vec_append (c, cf->current_command); + vec_add1 (cf->command_history, c); + cf->command_number++; + } + } + cf->excursion = vec_len (cf->command_history); + } + + cf->search_mode = 0; + vec_reset_length (cf->search_key); + cf->cursor = 0; + + return 0; + + case UNIX_CLI_PARSE_ACTION_PARTIALMATCH: + case UNIX_CLI_PARSE_ACTION_NOMATCH: + if (vec_len (cf->pager_index)) + { + /* no-op for now */ + } + else if (cf->has_history && cf->search_mode && isprint (input)) + { + int k, limit, offset; + u8 *item; + + vec_add1 (cf->search_key, input); + + 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); j++) + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b \b", 3); + + vec_validate (cf->current_command, vec_len (item) - 1); + clib_memcpy (cf->current_command, item, vec_len (item)); + _vec_len (cf->current_command) = vec_len (item); + + unix_vlib_cli_output_cooked (cf, uf, cf->current_command, + vec_len (cf->current_command)); + cf->cursor = vec_len (cf->current_command); + goto found; + + next: + cf->excursion += cf->search_mode; + } + + unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\nNo match...", 12); + vec_reset_length (cf->search_key); + vec_reset_length (cf->current_command); + cf->search_mode = 0; + cf->cursor = 0; + goto crlf; + } + else if (isprint (input)) /* skip any errant control codes */ + { + if (cf->cursor == vec_len (cf->current_command)) + { + /* 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++; + } + } + else + { + /* no-op - not printable or otherwise not actionable */ + } + + found: + + break; + + case UNIX_CLI_PARSE_ACTION_TELNETIAC: + break; + } + return 1; +} + +/** @brief Process input bytes on a stream to provide line editing and + * command history in the CLI. */ +static int +unix_cli_line_edit (unix_cli_main_t * cm, unix_main_t * um, + clib_file_main_t * fm, unix_cli_file_t * cf) +{ + clib_file_t *uf = pool_elt_at_index (fm->file_pool, cf->clib_file_index); + int i; + + for (i = 0; i < vec_len (cf->input_vector); i++) + { + unix_cli_parse_action_t action; + i32 matched = 0; + unix_cli_parse_actions_t *a; + + /* If we're in the pager mode, search the pager actions */ + a = + vec_len (cf->pager_index) ? unix_cli_parse_pager : + unix_cli_parse_strings; + + /* See if the input buffer is some sort of control code */ + action = unix_cli_match_action (a, &cf->input_vector[i], + vec_len (cf->input_vector) - i, + &matched); + + switch (action) + { + case UNIX_CLI_PARSE_ACTION_PARTIALMATCH: + if (i) + { + /* There was a partial match which means we need more bytes + * than the input buffer currently has. + * Since the bytes before here have been processed, shift + * the remaining contents to the start of the input buffer. + */ + vec_delete (cf->input_vector, i, 0); + } + return 1; /* wait for more */ + + case UNIX_CLI_PARSE_ACTION_TELNETIAC: + /* process telnet options */ + matched = unix_cli_process_telnet (um, cf, uf, + cf->input_vector + i, + vec_len (cf->input_vector) - i); + if (matched < 0) + { + /* There was a partial match which means we need more bytes + * than the input buffer currently has. + */ + if (i) + { + /* + * Since the bytes before here have been processed, shift + * the remaining contents to the start of the input buffer. + */ + vec_delete (cf->input_vector, i, 0); + } + return 1; /* wait for more */ + } + break; + + default: + /* If telnet option processing switched us to line mode, get us + * out of here! + */ + if (cf->line_mode) + { + vec_delete (cf->input_vector, i, 0); + cf->current_command = cf->input_vector; + return 0; + } + + /* process the action */ + if (!unix_cli_line_process_one (cm, um, cf, uf, + cf->input_vector[i], action)) + { + /* CRLF found. Consume the bytes from the input_vector */ + vec_delete (cf->input_vector, i + matched, 0); + /* And tell our caller to execute cf->input_command */ + return 0; + } + } + + i += matched; + } + + vec_reset_length (cf->input_vector); + return 1; +} + +/** @brief Process input to a CLI session. */ +static void +unix_cli_process_input (unix_cli_main_t * cm, uword cli_file_index) +{ + unix_main_t *um = &unix_main; + clib_file_main_t *fm = &file_main; + clib_file_t *uf; + unix_cli_file_t *cf = pool_elt_at_index (cm->cli_file_pool, cli_file_index); + unformat_input_t input; + int vlib_parse_eval (u8 *); + + cm->current_input_file_index = cli_file_index; + +more: + /* Try vlibplex first. Someday... */ + if (0 && vlib_parse_eval (cf->input_vector) == 0) + goto done; + + + if (cf->line_mode) + { + /* just treat whatever we got as a complete line of input */ + cf->current_command = cf->input_vector; + } + else + { + /* Line edit, echo, etc. */ + if (unix_cli_line_edit (cm, um, fm, cf)) + /* want more input */ + 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->current_command); + int rv __attribute__ ((unused)) = write (um->log_fd, lv, vec_len (lv)); + } + + /* Build an unformat structure around our command */ + unformat_init_vector (&input, cf->current_command); + + /* Remove leading white space from input. */ + (void) unformat (&input, ""); + + cf->pager_start = 0; /* start a new pager session */ + + if (unformat_check_input (&input) != UNFORMAT_END_OF_INPUT) + vlib_cli_input (um->vlib_main, &input, unix_vlib_cli_output, + cli_file_index); + + /* Zero buffer since otherwise unformat_free will call vec_free on it. */ + input.buffer = 0; + + unformat_free (&input); + + /* 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 (fm->file_pool, cf->clib_file_index); + +done: + /* reset vector; we'll re-use it later */ + if (cf->line_mode) + { + vec_reset_length (cf->input_vector); + cf->current_command = 0; + } + else + { + vec_reset_length (cf->current_command); + } + + 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 (vec_len (cf->pager_index) == 0 + || vec_len (cf->pager_index) < 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); + } + + /* Any residual data in the input vector? */ + if (vec_len (cf->input_vector)) + goto more; + + /* For non-interactive sessions send a NUL byte. + * Specifically this is because vppctl needs to see some traffic in + * order to move on to closing the session. Commands with no output + * would thus cause vppctl to hang indefinitely in non-interactive mode + * since there is also no prompt sent after the command completes. + */ + if (!cf->is_interactive) + unix_vlib_cli_output_raw (cf, uf, (u8 *) "\0", 1); +} + +/** Destroy a CLI session. + * @note If we destroy the @c stdin session this additionally signals + * the shutdown of VPP. + */ +static void +unix_cli_kill (unix_cli_main_t * cm, uword cli_file_index) +{ + unix_main_t *um = &unix_main; + clib_file_main_t *fm = &file_main; + unix_cli_file_t *cf; + clib_file_t *uf; + int i; + + cf = pool_elt_at_index (cm->cli_file_pool, cli_file_index); + uf = pool_elt_at_index (fm->file_pool, cf->clib_file_index); + + /* Quit/EOF on stdin means quit program. */ + if (uf->file_descriptor == STDIN_FILENO) + 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); + + clib_file_del (fm, uf); + + unix_cli_file_free (cf); + pool_put (cm->cli_file_pool, cf); +} + +/** Handle system events. */ +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; +} + +/** Called when a CLI session file descriptor can be written to without + * blocking. */ +static clib_error_t * +unix_cli_write_ready (clib_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. */ + if (cf->is_socket) + /* If it's a socket we use MSG_NOSIGNAL to prevent SIGPIPE */ + n = send (uf->file_descriptor, + cf->output_vector, vec_len (cf->output_vector), MSG_NOSIGNAL); + else + n = write (uf->file_descriptor, + cf->output_vector, vec_len (cf->output_vector)); + + if (n < 0 && errno != EAGAIN) + { + if (errno == EPIPE) + { + /* connection closed on us */ + unix_main_t *um = &unix_main; + cf->has_epipe = 1; + vlib_process_signal_event (um->vlib_main, cf->process_node_index, + UNIX_CLI_PROCESS_EVENT_QUIT, + uf->private_data); + } + else + { + return clib_error_return_unix (0, "write"); + } + } + + else if (n > 0) + unix_cli_del_pending_output (uf, cf, n); + + return /* no error */ 0; +} + +/** Called when a CLI session file descriptor has data to be read. */ +static clib_error_t * +unix_cli_read_ready (clib_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; +} + +/** Called when a CLI session file descriptor has an error condition. */ +static clib_error_t * +unix_cli_error_detected (clib_file_t * uf) +{ + unix_main_t *um = &unix_main; + unix_cli_main_t *cm = &unix_cli_main; + unix_cli_file_t *cf; + + cf = pool_elt_at_index (cm->cli_file_pool, uf->private_data); + cf->has_epipe = 1; /* prevent writes while the close is pending */ + vlib_process_signal_event (um->vlib_main, + cf->process_node_index, + UNIX_CLI_PROCESS_EVENT_QUIT, + /* event data */ uf->private_data); + + return /* no error */ 0; +} + +/** Store a new CLI session. + * @param name The name of the session. + * @param fd The file descriptor for the session I/O. + * @return The session ID. + */ +static u32 +unix_cli_file_add (unix_cli_main_t * cm, char *name, int fd) +{ + unix_main_t *um = &unix_main; + clib_file_main_t *fm = &file_main; + unix_cli_file_t *cf; + clib_file_t 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 = 16, + }; + + 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.error_function = unix_cli_error_detected; + template.file_descriptor = fd; + template.private_data = cf - cm->cli_file_pool; + + cf->process_node_index = n->index; + cf->clib_file_index = clib_file_add (fm, &template); + cf->output_vector = 0; + cf->input_vector = 0; + + vlib_start_process (vm, n->runtime_index); + + vlib_process_t *p = vlib_get_process_from_node (vm, n); + p->output_function = unix_vlib_cli_output; + p->output_function_arg = cf - cm->cli_file_pool; + + return cf - cm->cli_file_pool; +} + +/** Telnet listening socket has a new connection. */ +static clib_error_t * +unix_cli_listen_read_ready (clib_file_t * uf) +{ + unix_main_t *um = &unix_main; + clib_file_main_t *fm = &file_main; + unix_cli_main_t *cm = &unix_cli_main; + clib_socket_t *s = &um->cli_listen_socket; + clib_socket_t client; + 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); + cf->is_socket = 1; + + /* No longer need CLIB version of socket. */ + clib_socket_free (&client); + vec_free (client_name); + + /* if we're supposed to run telnet session in character mode (default) */ + if (um->cli_line_mode == 0) + { + /* + * Set telnet client character mode, echo on, suppress "go-ahead". + * Technically these should be negotiated, but this works. + */ + u8 charmode_option[] = { + IAC, WONT, TELOPT_LINEMODE, /* server will do char-by-char */ + IAC, DONT, TELOPT_LINEMODE, /* client should do char-by-char */ + IAC, WILL, TELOPT_SGA, /* server willl supress GA */ + IAC, DO, TELOPT_SGA, /* client should supress Go Ahead */ + IAC, WILL, TELOPT_ECHO, /* server will do echo */ + 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->history_limit = um->cli_history_limit; + cf->has_history = cf->history_limit != 0; + + /* This is an interactive session until we decide otherwise */ + cf->is_interactive = 1; + + /* Make sure this session is in line mode */ + cf->line_mode = 0; + + /* We need CRLF */ + cf->crlf_mode = 1; + + /* Setup the pager */ + cf->no_pager = um->cli_no_pager; + + /* Send the telnet options */ + uf = pool_elt_at_index (fm->file_pool, cf->clib_file_index); + unix_vlib_cli_output_raw (cf, uf, charmode_option, + ARRAY_LEN (charmode_option)); + + /* 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); + } + + return error; +} + +/** The system terminal has informed us that the window size + * has changed. + */ +static void +unix_cli_resize_interrupt (int signum) +{ + clib_file_main_t *fm = &file_main; + unix_cli_main_t *cm = &unix_cli_main; + unix_cli_file_t *cf = pool_elt_at_index (cm->cli_file_pool, + cm->stdin_cli_file_index); + clib_file_t *uf = pool_elt_at_index (fm->file_pool, cf->clib_file_index); + struct winsize ws; + (void) signum; + + /* Terminal resized, fetch the new size */ + if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) < 0) + { + /* "Should never happen..." */ + clib_unix_warning ("TIOCGWINSZ"); + /* We can't trust ws.XXX... */ + return; + } + + cf->width = ws.ws_col; + if (cf->width > UNIX_CLI_MAX_TERMINAL_WIDTH) + cf->width = UNIX_CLI_MAX_TERMINAL_WIDTH; + if (cf->width < UNIX_CLI_MIN_TERMINAL_WIDTH) + cf->width = UNIX_CLI_MIN_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; + + /* Reindex the pager buffer */ + unix_cli_pager_reindex (cf); + + /* Redraw the page */ + unix_cli_pager_redraw (cf, uf); +} + +/** Handle configuration directives in the @em unix section. */ +static clib_error_t * +unix_cli_config (vlib_main_t * vm, unformat_input_t * input) +{ + unix_main_t *um = &unix_main; + clib_file_main_t *fm = &file_main; + unix_cli_main_t *cm = &unix_cli_main; + int flags; + clib_error_t *error = 0; + 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. */ + if ((error = vlib_call_config_function (vm, unix_config))) + return error; + + if (um->flags & UNIX_FLAG_INTERACTIVE) + { + /* Set stdin to be non-blocking. */ + if ((flags = fcntl (STDIN_FILENO, F_GETFL, 0)) < 0) + flags = 0; + (void) fcntl (STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); + + cf_index = unix_cli_file_add (cm, "stdin", STDIN_FILENO); + cf = pool_elt_at_index (cm->cli_file_pool, cf_index); + cm->stdin_cli_file_index = cf_index; + + /* If stdin is a tty and we are using chacracter mode, enable + * history on the CLI and set the tty line discipline accordingly. */ + if (isatty (STDIN_FILENO) && um->cli_line_mode == 0) + { + /* Capture terminal resize events */ + 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 (cf->width == 0 || cf->height == 0) + { + /* + * We have a tty, but no size. Use defaults. + * vpp "unix interactive" inside emacs + gdb ends up here. + */ + cf->width = 80; + cf->height = 24; + } + + /* 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; + + /* This is an interactive session until we decide otherwise */ + cf->is_interactive = 1; + + /* We're going to be in char by char mode */ + cf->line_mode = 0; + + /* Save the original tty state so we can restore it later */ + tcgetattr (STDIN_FILENO, &um->tio_stdin); + um->tio_isset = 1; + + /* Tweak the tty settings */ + tio = um->tio_stdin; + /* echo off, canonical mode off, ext'd input processing off */ + tio.c_lflag &= ~(ECHO | ICANON | IEXTEN); + tio.c_cc[VMIN] = 1; /* 1 byte at a time */ + tio.c_cc[VTIME] = 0; /* no timer */ + tcsetattr (STDIN_FILENO, TCSAFLUSH, &tio); + + /* See if we can do ANSI/VT100 output */ + term = (u8 *) getenv ("TERM"); + if (term != NULL) + { + int len = strlen ((char *) term); + cf->ansi_capable = unix_cli_terminal_type_ansi (term, len); + if (unix_cli_terminal_type_noninteractive (term, len)) + unix_cli_set_session_noninteractive (cf); + } + } + else + { + /* No tty, so make sure the session doesn't have tty-like features */ + unix_cli_set_session_noninteractive (cf); + } + + /* Send banner and initial prompt */ + unix_cli_file_welcome (cm, cf); + } + + /* If we have socket config, LISTEN, otherwise, don't */ + clib_socket_t *s = &um->cli_listen_socket; + if (s->config && s->config[0] != 0) + { + /* CLI listen. */ + clib_file_t template = { 0 }; + + /* mkdir of file socketu, only under /run */ + if (strncmp (s->config, "/run", 4) == 0) + { + u8 *tmp = format (0, "%s", s->config); + int i = vec_len (tmp); + while (i && tmp[--i] != '/') + ; + + tmp[i] = 0; + + if (i) + vlib_unix_recursive_mkdir ((char *) tmp); + vec_free (tmp); + } + + s->flags = CLIB_SOCKET_F_IS_SERVER | /* listen, don't connect */ + CLIB_SOCKET_F_ALLOW_GROUP_WRITE; /* PF_LOCAL socket only */ + error = clib_socket_init (s); + + if (error) + return error; + + template.read_function = unix_cli_listen_read_ready; + template.file_descriptor = s->fd; + + clib_file_add (fm, &template); + } + + /* Set CLI prompt. */ + if (!cm->cli_prompt) + cm->cli_prompt = format (0, "VLIB: "); + + return 0; +} + +/*? + * This module has no configurable parameters. +?*/ +VLIB_CONFIG_FUNCTION (unix_cli_config, "unix-cli"); + +/** Called when VPP is shutting down, this restores the system + * terminal state if previously saved. + */ +static clib_error_t * +unix_cli_exit (vlib_main_t * vm) +{ + unix_main_t *um = &unix_main; + + /* If stdin is a tty and we saved the tty state, reset the tty state */ + if (isatty (STDIN_FILENO) && um->tio_isset) + tcsetattr (STDIN_FILENO, TCSAFLUSH, &um->tio_stdin); + + return 0; +} + +VLIB_MAIN_LOOP_EXIT_FUNCTION (unix_cli_exit); + +/** Set the CLI prompt. + * @param prompt The C string to set the prompt to. + * @note This setting is global; it impacts all current + * and future CLI sessions. + */ +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); +} + +/** CLI command to quit the terminal session. + * @note If this is a stdin session then this will + * shutdown VPP also. + */ +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; + unix_cli_file_t *cf = pool_elt_at_index (cm->cli_file_pool, + cm->current_input_file_index); + + /* Cosmetic: suppress the final prompt from appearing before we die */ + cf->is_interactive = 0; + cf->started = 1; + + vlib_process_signal_event (vm, + vlib_current_process (vm), + UNIX_CLI_PROCESS_EVENT_QUIT, + cm->current_input_file_index); + return 0; +} + +/*? + * Terminates the current CLI session. + * + * If VPP is running in @em interactive mode and this is the console session + * (that is, the session on @c stdin) then this will also terminate VPP. +?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (unix_cli_quit_command, static) = { + .path = "quit", + .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, + 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; +} + +/*? + * 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. +?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (cli_exec, static) = { + .path = "exec", + .short_help = "Execute commands from file", + .function = unix_cli_exec, + .is_mp_safe = 1, +}; +/* *INDENT-ON* */ + +/** CLI command to show various unix error statistics. */ +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; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (cli_unix_show_errors, static) = { + .path = "show unix-errors", + .short_help = "Show Unix system call error history", + .function = unix_show_errors, +}; +/* *INDENT-ON* */ + +/** CLI command to show session command history. */ +static clib_error_t * +unix_cli_show_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; + int i, j; + + cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index); + + if (!cf->is_interactive) + return clib_error_return (0, "invalid for non-interactive sessions"); + + if (cf->has_history && cf->history_limit) + { + i = 1 + cf->command_number - vec_len (cf->command_history); + 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, "History not enabled.\n"); + } + + return 0; +} + +/*? + * Displays the command history for the current session, if any. +?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (cli_unix_cli_show_history, static) = { + .path = "history", + .short_help = "Show current session command history", + .function = unix_cli_show_history, +}; +/* *INDENT-ON* */ + +/** CLI command to show terminal status. */ +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, "Interactive: %s\n", + cf->is_interactive ? "yes" : "no"); + vlib_cli_output (vm, "History enabled: %s%s\n", + cf->has_history ? "yes" : "no", !cf->has_history + || cf->history_limit ? "" : + " (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; +} + +/*? + * Displays various information about the state of the current terminal + * session. + * + * @cliexpar + * @cliexstart{show terminal} + * Terminal name: unix-cli-stdin + * Terminal mode: char-by-char + * Terminal width: 123 + * Terminal height: 48 + * ANSI capable: yes + * Interactive: yes + * History enabled: yes + * History limit: 50 + * Pager enabled: yes + * Pager limit: 100000 + * CRLF mode: LF + * @cliexend +?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (cli_unix_cli_show_terminal, static) = { + .path = "show terminal", + .short_help = "Show current session terminal settings", + .function = unix_cli_show_terminal, +}; +/* *INDENT-ON* */ + +/** CLI command to display a list of CLI sessions. */ +static clib_error_t * +unix_cli_show_cli_sessions (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + //unix_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; + clib_file_t *uf; + vlib_node_t *n; + + vlib_cli_output (vm, "%-5s %-5s %-20s %s", "PNI", "FD", "Name", "Flags"); + +#define fl(x, y) ( (x) ? toupper((y)) : tolower((y)) ) + /* *INDENT-OFF* */ + pool_foreach (cf, cm->cli_file_pool, ({ + uf = pool_elt_at_index (fm->file_pool, cf->clib_file_index); + n = vlib_get_node (vm, cf->process_node_index); + vlib_cli_output (vm, + "%-5d %-5d %-20v %c%c%c%c%c\n", + cf->process_node_index, + uf->file_descriptor, + n->name, + fl (cf->is_interactive, 'i'), + fl (cf->is_socket, 's'), + fl (cf->line_mode, 'l'), + fl (cf->has_epipe, 'p'), + fl (cf->ansi_capable, 'a')); + })); + /* *INDENT-ON* */ +#undef fl + + return 0; +} + +/*? + * Displays a summary of all the current CLI sessions. + * + * Typically used to diagnose connection issues with the CLI + * socket. + * + * @cliexpar + * @cliexstart{show cli-sessions} + * PNI FD Name Flags + * 343 0 unix-cli-stdin IslpA + * 344 7 unix-cli-local:20 ISlpA + * 346 8 unix-cli-local:21 iSLpa + * @cliexend + + * In this example we have the debug console of the running process + * on stdin/out, we have an interactive socket session and we also + * have a non-interactive socket session. + * + * Fields: + * + * - @em PNI: Process node index. + * - @em FD: Unix file descriptor. + * - @em Name: Name of the session. + * - @em Flags: Various flags that describe the state of the session. + * + * @em Flags have the following meanings; lower-case typically negates + * upper-case: + * + * - @em I Interactive session. + * - @em S Connected by socket. + * - @em s Not a socket, likely stdin. + * - @em L Line-by-line mode. + * - @em l Char-by-char mode. + * - @em P EPIPE detected on connection; it will close soon. + * - @em A ANSI-capable terminal. +?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (cli_unix_cli_show_cli_sessions, static) = { + .path = "show cli-sessions", + .short_help = "Show current CLI sessions", + .function = unix_cli_show_cli_sessions, +}; +/* *INDENT-ON* */ + +/** CLI command to set terminal pager settings. */ +static clib_error_t * +unix_cli_set_terminal_pager (vlib_main_t * vm, + 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; + clib_error_t *error = 0; + + cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index); + + if (!cf->is_interactive) + return clib_error_return (0, "invalid for non-interactive sessions"); + + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + 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 + { + error = clib_error_return (0, "unknown parameter: `%U`", + format_unformat_error, line_input); + goto done; + } + } + +done: + unformat_free (line_input); + + return error; +} + +/*? + * Enables or disables the terminal pager for this session. Generally + * this defaults to enabled. + * + * Additionally allows the pager buffer size to be set; though note that + * this value is set globally and not per session. +?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (cli_unix_cli_set_terminal_pager, static) = { + .path = "set terminal pager", + .short_help = "set terminal pager [on|off] [limit <lines>]", + .function = unix_cli_set_terminal_pager, +}; +/* *INDENT-ON* */ + +/** CLI command to set terminal history settings. */ +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; + clib_error_t *error = 0; + + cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index); + + if (!cf->is_interactive) + return clib_error_return (0, "invalid for non-interactive sessions"); + + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + 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 + { + error = clib_error_return (0, "unknown parameter: `%U`", + format_unformat_error, line_input); + goto done; + } + + /* If we reduced history size, or turned it off, purge the history */ + limit = cf->has_history ? cf->history_limit : 0; + + while (cf->command_history && vec_len (cf->command_history) >= limit) + { + vec_free (cf->command_history[0]); + vec_delete (cf->command_history, 1, 0); + } + } + +done: + unformat_free (line_input); + + return error; +} + +/*? + * Enables or disables the command history function of the current + * terminal. Generally this defaults to enabled. + * + * This command also allows the maximum size of the history buffer for + * this session to be altered. +?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (cli_unix_cli_set_terminal_history, static) = { + .path = "set terminal history", + .short_help = "set terminal history [on|off] [limit <lines>]", + .function = unix_cli_set_terminal_history, +}; +/* *INDENT-ON* */ + +/** CLI command to set terminal ANSI settings. */ +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 (!cf->is_interactive) + return clib_error_return (0, "invalid for non-interactive sessions"); + + if (unformat (input, "on")) + cf->ansi_capable = 1; + else if (unformat (input, "off")) + cf->ansi_capable = 0; + else + return clib_error_return (0, "unknown parameter: `%U`", + format_unformat_error, input); + + return 0; +} + +/*? + * Enables or disables the use of ANSI control sequences by this terminal. + * The default will vary based on terminal detection at the start of the + * session. + * + * ANSI control sequences are used in a small number of places to provide, + * for example, color text output and to control the cursor in the pager. +?*/ +/* *INDENT-OFF* */ +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, +}; +/* *INDENT-ON* */ + +static clib_error_t * +unix_cli_init (vlib_main_t * vm) +{ + return 0; +} + +VLIB_INIT_FUNCTION (unix_cli_init); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vlib/unix/dir.dox b/src/vlib/unix/dir.dox new file mode 100644 index 00000000..1380fa56 --- /dev/null +++ b/src/vlib/unix/dir.dox @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2016 Comcast Cable Communications Management, LLC. + * + * 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. + */ + +/* Doxygen directory documentation */ + +/** +@dir +@brief VLIB Unix interface + +VLIB application library Unix interface layer. + +*/ +/*? %%clicmd:group_label Unix Interface %% ?*/ +/*? %%syscfg:group_label Unix Interface %% ?*/ + diff --git a/src/vlib/unix/input.c b/src/vlib/unix/input.c new file mode 100644 index 00000000..ecd31791 --- /dev/null +++ b/src/vlib/unix/input.c @@ -0,0 +1,272 @@ +/* + * 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. + */ +/* + * input.c: Unix file input + * + * 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 <signal.h> +#include <vppinfra/tw_timer_1t_3w_1024sl_ov.h> + +/* FIXME autoconf */ +#define HAVE_LINUX_EPOLL + +#ifdef HAVE_LINUX_EPOLL + +#include <sys/epoll.h> + +typedef struct +{ + int epoll_fd; + struct epoll_event *epoll_events; + + /* Statistics. */ + u64 epoll_files_ready; + u64 epoll_waits; +} linux_epoll_main_t; + +static linux_epoll_main_t linux_epoll_main; + +static void +linux_epoll_file_update (clib_file_t * f, unix_file_update_type_t update_type) +{ + clib_file_main_t *fm = &file_main; + linux_epoll_main_t *em = &linux_epoll_main; + struct epoll_event e; + int op; + + memset (&e, 0, sizeof (e)); + + e.events = EPOLLIN; + if (f->flags & UNIX_FILE_DATA_AVAILABLE_TO_WRITE) + e.events |= EPOLLOUT; + if (f->flags & UNIX_FILE_EVENT_EDGE_TRIGGERED) + e.events |= EPOLLET; + e.data.u32 = f - fm->file_pool; + + op = -1; + + switch (update_type) + { + case UNIX_FILE_UPDATE_ADD: + op = EPOLL_CTL_ADD; + break; + + case UNIX_FILE_UPDATE_MODIFY: + op = EPOLL_CTL_MOD; + break; + + case UNIX_FILE_UPDATE_DELETE: + op = EPOLL_CTL_DEL; + break; + + default: + clib_warning ("unknown update_type %d", update_type); + return; + } + + if (epoll_ctl (em->epoll_fd, op, f->file_descriptor, &e) < 0) + clib_unix_warning ("epoll_ctl"); +} + +static uword +linux_epoll_input (vlib_main_t * vm, + vlib_node_runtime_t * node, vlib_frame_t * frame) +{ + unix_main_t *um = &unix_main; + clib_file_main_t *fm = &file_main; + linux_epoll_main_t *em = &linux_epoll_main; + struct epoll_event *e; + int n_fds_ready; + + { + vlib_node_main_t *nm = &vm->node_main; + u32 ticks_until_expiration; + f64 timeout; + int timeout_ms = 0, max_timeout_ms = 10; + f64 vector_rate = vlib_last_vectors_per_main_loop (vm); + + /* If we're not working very hard, decide how long to sleep */ + if (vector_rate < 2 && vm->api_queue_nonempty == 0 + && nm->input_node_counts_by_state[VLIB_NODE_STATE_POLLING] == 0) + { + ticks_until_expiration = TW (tw_timer_first_expires_in_ticks) + ((TWT (tw_timer_wheel) *) nm->timing_wheel); + + /* Nothing on the fast wheel, sleep 10ms */ + if (ticks_until_expiration == TW_SLOTS_PER_RING) + { + timeout = 10e-3; + timeout_ms = max_timeout_ms; + } + else + { + timeout = (f64) ticks_until_expiration *1e-5; + if (timeout < 1e-3) + timeout_ms = 0; + else + { + timeout_ms = timeout * 1e3; + /* Must be between 1 and 10 ms. */ + timeout_ms = clib_max (1, timeout_ms); + timeout_ms = clib_min (max_timeout_ms, timeout_ms); + } + } + node->input_main_loops_per_call = 0; + } + else /* busy */ + { + /* Don't come back for a respectable number of dispatch cycles */ + node->input_main_loops_per_call = 1024; + } + + /* Allow any signal to wakeup our sleep. */ + { + static sigset_t unblock_all_signals; + n_fds_ready = epoll_pwait (em->epoll_fd, + em->epoll_events, + vec_len (em->epoll_events), + timeout_ms, &unblock_all_signals); + + /* This kludge is necessary to run over absurdly old kernels */ + if (n_fds_ready < 0 && errno == ENOSYS) + { + n_fds_ready = epoll_wait (em->epoll_fd, + em->epoll_events, + vec_len (em->epoll_events), timeout_ms); + } + } + } + + if (n_fds_ready < 0) + { + if (unix_error_is_fatal (errno)) + vlib_panic_with_error (vm, clib_error_return_unix (0, "epoll_wait")); + + /* non fatal error (e.g. EINTR). */ + return 0; + } + + em->epoll_waits += 1; + em->epoll_files_ready += n_fds_ready; + + for (e = em->epoll_events; e < em->epoll_events + n_fds_ready; e++) + { + u32 i = e->data.u32; + clib_file_t *f = pool_elt_at_index (fm->file_pool, i); + clib_error_t *errors[4]; + int n_errors = 0; + + if (PREDICT_TRUE (!(e->events & EPOLLERR))) + { + if (e->events & EPOLLIN) + { + errors[n_errors] = f->read_function (f); + n_errors += errors[n_errors] != 0; + } + if (e->events & EPOLLOUT) + { + errors[n_errors] = f->write_function (f); + n_errors += errors[n_errors] != 0; + } + } + else + { + if (f->error_function) + { + errors[n_errors] = f->error_function (f); + n_errors += errors[n_errors] != 0; + } + else + close (f->file_descriptor); + } + + ASSERT (n_errors < ARRAY_LEN (errors)); + for (i = 0; i < n_errors; i++) + { + unix_save_error (um, errors[i]); + } + } + + return 0; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (linux_epoll_input_node,static) = { + .function = linux_epoll_input, + .type = VLIB_NODE_TYPE_PRE_INPUT, + .name = "unix-epoll-input", +}; +/* *INDENT-ON* */ + +clib_error_t * +linux_epoll_input_init (vlib_main_t * vm) +{ + linux_epoll_main_t *em = &linux_epoll_main; + clib_file_main_t *fm = &file_main; + + /* Allocate some events. */ + vec_resize (em->epoll_events, VLIB_FRAME_SIZE); + + em->epoll_fd = epoll_create (vec_len (em->epoll_events)); + if (em->epoll_fd < 0) + return clib_error_return_unix (0, "epoll_create"); + + fm->file_update = linux_epoll_file_update; + + return 0; +} + +VLIB_INIT_FUNCTION (linux_epoll_input_init); + +#endif /* HAVE_LINUX_EPOLL */ + +static clib_error_t * +unix_input_init (vlib_main_t * vm) +{ + return vlib_call_init_function (vm, linux_epoll_input_init); +} + +VLIB_INIT_FUNCTION (unix_input_init); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vlib/unix/main.c b/src/vlib/unix/main.c new file mode 100644 index 00000000..f286c870 --- /dev/null +++ b/src/vlib/unix/main.c @@ -0,0 +1,642 @@ +/* + * 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. + */ +/* + * main.c: Unix main routine + * + * 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 <vlib/unix/plugin.h> + +#include <signal.h> +#include <sys/ucontext.h> +#include <syslog.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <unistd.h> + +/** 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 + +char *vlib_default_runtime_dir __attribute__ ((weak)); +char *vlib_default_runtime_dir = "vlib"; + +unix_main_t unix_main; +clib_file_main_t file_main; + +static clib_error_t * +unix_main_init (vlib_main_t * vm) +{ + unix_main_t *um = &unix_main; + um->vlib_main = vm; + return vlib_call_init_function (vm, unix_input_init); +} + +VLIB_INIT_FUNCTION (unix_main_init); + +static void +unix_signal_handler (int signum, siginfo_t * si, ucontext_t * uc) +{ + uword fatal = 0; + u8 *msg = 0; + + msg = format (msg, "received signal %U, PC %U", + format_signal, signum, format_ucontext_pc, uc); + + if (signum == SIGSEGV) + msg = format (msg, ", faulting address %p", si->si_addr); + + switch (signum) + { + /* these (caught) signals cause the application to exit */ + case SIGTERM: + if (unix_main.vlib_main->main_loop_exit_set) + { + syslog (LOG_ERR | LOG_DAEMON, "received SIGTERM, exiting..."); + unix_main.vlib_main->main_loop_exit_now = 1; + } + break; + /* fall through */ + case SIGQUIT: + case SIGINT: + case SIGILL: + case SIGBUS: + case SIGSEGV: + case SIGHUP: + case SIGFPE: + fatal = 1; + break; + + /* by default, print a message and continue */ + default: + fatal = 0; + break; + } + + /* Null terminate. */ + vec_add1 (msg, 0); + + if (fatal) + { + syslog (LOG_ERR | LOG_DAEMON, "%s", msg); + os_exit (1); + } + else + clib_warning ("%s", msg); + + vec_free (msg); +} + +static clib_error_t * +setup_signal_handlers (unix_main_t * um) +{ + uword i; + struct sigaction sa; + + for (i = 1; i < 32; i++) + { + memset (&sa, 0, sizeof (sa)); + sa.sa_sigaction = (void *) unix_signal_handler; + sa.sa_flags = SA_SIGINFO; + + switch (i) + { + /* these signals take the default action */ + case SIGABRT: + case SIGKILL: + case SIGSTOP: + case SIGUSR1: + case SIGUSR2: + continue; + + /* ignore SIGPIPE, SIGCHLD */ + case SIGPIPE: + case SIGCHLD: + sa.sa_sigaction = (void *) SIG_IGN; + break; + + /* catch and handle all other signals */ + default: + break; + } + + if (sigaction (i, &sa, 0) < 0) + return clib_error_return_unix (0, "sigaction %U", format_signal, i); + } + + return 0; +} + +static void +unix_error_handler (void *arg, u8 * msg, int msg_len) +{ + unix_main_t *um = arg; + + /* Echo to stderr when interactive. */ + if (um->flags & UNIX_FLAG_INTERACTIVE) + { + CLIB_UNUSED (int r) = write (2, msg, msg_len); + } + else + { + char save = msg[msg_len - 1]; + + /* Null Terminate. */ + msg[msg_len - 1] = 0; + + syslog (LOG_ERR | LOG_DAEMON, "%s", msg); + + msg[msg_len - 1] = save; + } +} + +void +vlib_unix_error_report (vlib_main_t * vm, clib_error_t * error) +{ + unix_main_t *um = &unix_main; + + if (um->flags & UNIX_FLAG_INTERACTIVE || error == 0) + return; + + { + char save; + u8 *msg; + u32 msg_len; + + msg = error->what; + msg_len = vec_len (msg); + + /* Null Terminate. */ + save = msg[msg_len - 1]; + msg[msg_len - 1] = 0; + + syslog (LOG_ERR | LOG_DAEMON, "%s", msg); + + msg[msg_len - 1] = save; + } +} + +static uword +startup_config_process (vlib_main_t * vm, + vlib_node_runtime_t * rt, vlib_frame_t * f) +{ + unix_main_t *um = &unix_main; + u8 *buf = 0; + uword l, n = 1; + + vlib_process_suspend (vm, 2.0); + + while (um->unix_config_complete == 0) + vlib_process_suspend (vm, 0.1); + + if (um->startup_config_filename) + { + unformat_input_t sub_input; + int fd; + struct stat s; + char *fn = (char *) um->startup_config_filename; + + fd = open (fn, O_RDONLY); + if (fd < 0) + { + clib_warning ("failed to open `%s'", fn); + return 0; + } + + if (fstat (fd, &s) < 0) + { + clib_warning ("failed to stat `%s'", fn); + bail: + close (fd); + return 0; + } + + if (!(S_ISREG (s.st_mode) || S_ISLNK (s.st_mode))) + { + clib_warning ("not a regular file: `%s'", fn); + goto bail; + } + + while (n > 0) + { + l = vec_len (buf); + vec_resize (buf, 4096); + n = read (fd, buf + l, 4096); + if (n > 0) + { + _vec_len (buf) = l + n; + if (n < 4096) + break; + } + else + break; + } + if (um->log_fd && vec_len (buf)) + { + u8 *lv = 0; + lv = format (lv, "%U: ***** Startup Config *****\n%v", + format_timeval, 0 /* current bat-time */ , + 0 /* current bat-format */ , + buf); + { + int rv __attribute__ ((unused)) = + write (um->log_fd, lv, vec_len (lv)); + } + vec_reset_length (lv); + lv = format (lv, "%U: ***** End Startup Config *****\n", + format_timeval, 0 /* current bat-time */ , + 0 /* current bat-format */ ); + { + int rv __attribute__ ((unused)) = + write (um->log_fd, lv, vec_len (lv)); + } + vec_free (lv); + } + + if (vec_len (buf)) + { + unformat_init_vector (&sub_input, buf); + vlib_cli_input (vm, &sub_input, 0, 0); + /* frees buf for us */ + unformat_free (&sub_input); + } + close (fd); + } + return 0; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (startup_config_node,static) = { + .function = startup_config_process, + .type = VLIB_NODE_TYPE_PROCESS, + .name = "startup-config-process", +}; +/* *INDENT-ON* */ + +static clib_error_t * +unix_config (vlib_main_t * vm, unformat_input_t * input) +{ + unix_main_t *um = &unix_main; + clib_error_t *error = 0; + gid_t gid; + int pidfd = -1; + + /* 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; + if (unformat (input, "interactive")) + um->flags |= UNIX_FLAG_INTERACTIVE; + else if (unformat (input, "nodaemon")) + um->flags |= UNIX_FLAG_NODAEMON; + else if (unformat (input, "cli-prompt %s", &cli_prompt)) + vlib_unix_cli_set_prompt (cli_prompt); + else + if (unformat (input, "cli-listen %s", &um->cli_listen_socket.config)) + ; + else if (unformat (input, "runtime-dir %s", &um->runtime_dir)) + ; + else if (unformat (input, "cli-line-mode")) + 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, "coredump-size")) + { + uword coredump_size = 0; + if (unformat (input, "unlimited")) + { + coredump_size = RLIM_INFINITY; + } + else + if (!unformat (input, "%U", unformat_memory_size, &coredump_size)) + { + return clib_error_return (0, + "invalid coredump-size parameter `%U'", + format_unformat_error, input); + } + const struct rlimit new_limit = { coredump_size, coredump_size }; + if (0 != setrlimit (RLIMIT_CORE, &new_limit)) + { + clib_unix_warning ("prlimit() failed"); + } + } + else if (unformat (input, "full-coredump")) + { + int fd; + + fd = open ("/proc/self/coredump_filter", O_WRONLY); + if (fd >= 0) + { + if (write (fd, "0x6f\n", 5) != 5) + clib_unix_warning ("coredump filter write failed!"); + close (fd); + } + else + clib_unix_warning ("couldn't open /proc/self/coredump_filter"); + } + else if (unformat (input, "startup-config %s", + &um->startup_config_filename)) + ; + else if (unformat (input, "exec %s", &um->startup_config_filename)) + ; + else if (unformat (input, "log %s", &um->log_filename)) + { + um->log_fd = open ((char *) um->log_filename, + O_CREAT | O_WRONLY | O_APPEND, 0644); + if (um->log_fd < 0) + { + clib_warning ("couldn't open log '%s'\n", um->log_filename); + um->log_fd = 0; + } + else + { + u8 *lv = 0; + lv = format (0, "%U: ***** Start: PID %d *****\n", + format_timeval, 0 /* current bat-time */ , + 0 /* current bat-format */ , + getpid ()); + { + int rv __attribute__ ((unused)) = + write (um->log_fd, lv, vec_len (lv)); + } + vec_free (lv); + } + } + else if (unformat (input, "gid %U", unformat_unix_gid, &gid)) + { + if (setegid (gid) == -1) + return clib_error_return_unix (0, "setegid"); + } + else if (unformat (input, "pidfile %s", &um->pidfile)) + ; + else + return clib_error_return (0, "unknown input `%U'", + format_unformat_error, input); + } + + if (um->runtime_dir == 0) + { + uid_t uid = geteuid (); + if (uid == 00) + um->runtime_dir = format (0, "/run/%s%c", + vlib_default_runtime_dir, 0); + else + um->runtime_dir = format (0, "/run/user/%u/%s%c", uid, + vlib_default_runtime_dir, 0); + } + + error = setup_signal_handlers (um); + if (error) + return error; + + if (um->pidfile) + { + if ((error = vlib_unix_validate_runtime_file (um, + (char *) um->pidfile, + &um->pidfile))) + return error; + + if (((pidfd = open ((char *) um->pidfile, + O_CREAT | O_WRONLY | O_TRUNC, 0644)) < 0)) + { + return clib_error_return_unix (0, "open"); + } + } + + if (!(um->flags & UNIX_FLAG_INTERACTIVE)) + { + openlog (vm->name, LOG_CONS | LOG_PERROR | LOG_PID, LOG_DAEMON); + clib_error_register_handler (unix_error_handler, um); + + if (!(um->flags & UNIX_FLAG_NODAEMON) && daemon ( /* chdir to / */ 0, + /* stdin/stdout/stderr -> /dev/null */ + 0) < 0) + clib_error_return (0, "daemon () fails"); + } + + if (pidfd >= 0) + { + u8 *lv = format (0, "%d", getpid ()); + if (write (pidfd, (char *) lv, vec_len (lv)) != vec_len (lv)) + { + vec_free (lv); + close (pidfd); + return clib_error_return_unix (0, "write"); + } + vec_free (lv); + close (pidfd); + } + + um->unix_config_complete = 1; + + return 0; +} + +/* unix { ... } configuration. */ +/*? + * + * @cfgcmd{interactive} + * Attach CLI to stdin/out and provide a debugging command line interface. + * Implies @c nodaemon. + * + * @cfgcmd{nodaemon} + * Do not fork or background the VPP process. Typically used when invoking + * VPP applications from a process monitor. + * + * @cfgcmd{exec, <filename>} + * @par <code>startup-config <filename></code> + * Read startup operational configuration from @c filename. + * The contents of the file will be performed as though entered at the CLI. + * The two keywords are aliases for the same function; if both are specified, + * only the last will have an effect. + * + * @cfgcmd{log, <filename>} + * Logs the startup configuration and all subsequent CLI commands in + * @c filename. + * Very useful in situations where folks don't remember or can't be bothered + * to include CLI commands in bug reports. + * + * @cfgcmd{pidfile, <filename>} + * Writes the pid of the main thread in @c filename. + * + * @cfgcmd{full-coredump} + * Ask the Linux kernel to dump all memory-mapped address regions, instead + * of just text+data+bss. + * + * @cfgcmd{runtime-dir} + * Define directory where VPP is going to store all runtime files. + * Default is /run/vpp. + * + * @cfgcmd{cli-listen, <address:port>} + * Bind the CLI to listen at the address and port given. @clocalhost + * on TCP port @c 5002, given as <tt>cli-listen localhost:5002</tt>, + * is typical. + * + * @cfgcmd{cli-line-mode} + * Disable character-by-character I/O on stdin. Useful when combined with, + * for example, <tt>emacs M-x gud-gdb</tt>. + * + * @cfgcmd{cli-prompt, <string>} + * Configure the CLI prompt to be @c string. + * + * @cfgcmd{cli-history-limit, <nn>} + * Limit commmand history to @c nn lines. A value of @c 0 + * disables command history. Default value: @c 50 + * + * @cfgcmd{cli-no-banner} + * Disable the login banner on stdin and Telnet connections. + * + * @cfgcmd{cli-no-pager} + * Disable the output pager. + * + * @cfgcmd{cli-pager-buffer-limit, <nn>} + * Limit pager buffer to @c nn lines of output. + * A value of @c 0 disables the pager. Default value: @c 100000 +?*/ +VLIB_EARLY_CONFIG_FUNCTION (unix_config, "unix"); + +static clib_error_t * +unix_exit (vlib_main_t * vm) +{ + /* Close syslog connection. */ + closelog (); + return 0; +} + +VLIB_MAIN_LOOP_EXIT_FUNCTION (unix_exit); + +u8 **vlib_thread_stacks; + +static uword +thread0 (uword arg) +{ + vlib_main_t *vm = (vlib_main_t *) arg; + unformat_input_t input; + int i; + + unformat_init_command_line (&input, (char **) vm->argv); + i = vlib_main (vm, &input); + unformat_free (&input); + + return i; +} + +u8 * +vlib_thread_stack_init (uword thread_index) +{ + vec_validate (vlib_thread_stacks, thread_index); + vlib_thread_stacks[thread_index] = clib_mem_alloc_aligned + (VLIB_THREAD_STACK_SIZE, VLIB_THREAD_STACK_SIZE); + + /* + * Disallow writes to the bottom page of the stack, to + * catch stack overflows. + */ + if (mprotect (vlib_thread_stacks[thread_index], + clib_mem_get_page_size (), PROT_READ) < 0) + clib_unix_warning ("thread stack"); + return vlib_thread_stacks[thread_index]; +} + +int +vlib_unix_main (int argc, char *argv[]) +{ + vlib_main_t *vm = &vlib_global_main; /* one and only time for this! */ + unformat_input_t input; + clib_error_t *e; + int i; + + vm->argv = (u8 **) argv; + vm->name = argv[0]; + vm->heap_base = clib_mem_get_heap (); + ASSERT (vm->heap_base); + + unformat_init_command_line (&input, (char **) vm->argv); + if ((e = vlib_plugin_config (vm, &input))) + { + clib_error_report (e); + return 1; + } + unformat_free (&input); + + i = vlib_plugin_early_init (vm); + if (i) + return i; + + unformat_init_command_line (&input, (char **) vm->argv); + if (vm->init_functions_called == 0) + vm->init_functions_called = hash_create (0, /* value bytes */ 0); + e = vlib_call_all_config_functions (vm, &input, 1 /* early */ ); + if (e != 0) + { + clib_error_report (e); + return 1; + } + unformat_free (&input); + + vlib_thread_stack_init (0); + + __os_thread_index = 0; + + i = clib_calljmp (thread0, (uword) vm, + (void *) (vlib_thread_stacks[0] + + VLIB_THREAD_STACK_SIZE)); + return i; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vlib/unix/mc_socket.c b/src/vlib/unix/mc_socket.c new file mode 100644 index 00000000..3f1cd99d --- /dev/null +++ b/src/vlib/unix/mc_socket.c @@ -0,0 +1,1050 @@ +/* + * mc_socket.c: socket based multicast for vlib mc + * + * Copyright (c) 2010 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. + */ + +#include <vlib/vlib.h> +#include <vlib/unix/mc_socket.h> + +#include <sys/ioctl.h> /* for FIONBIO */ +#include <netinet/tcp.h> /* for TCP_NODELAY */ +#include <net/if.h> /* for struct ifreq */ + +static u8 * +format_socket_peer_id (u8 * s, va_list * args) +{ + u64 peer_id_as_u64 = va_arg (*args, u64); + mc_peer_id_t peer_id; + peer_id.as_u64 = peer_id_as_u64; + u32 a = mc_socket_peer_id_get_address (peer_id); + u32 p = mc_socket_peer_id_get_port (peer_id); + + s = format (s, "%U:%04x", format_network_address, AF_INET, &a, ntohs (p)); + + return s; +} + +typedef void (mc_msg_handler_t) (mc_main_t * mcm, void *msg, + u32 buffer_index); + +always_inline void +msg_handler (mc_main_t * mcm, + u32 buffer_index, u32 handler_frees_buffer, void *_h) +{ + vlib_main_t *vm = mcm->vlib_main; + mc_msg_handler_t *h = _h; + vlib_buffer_t *b = vlib_get_buffer (vm, buffer_index); + void *the_msg = vlib_buffer_get_current (b); + + h (mcm, the_msg, buffer_index); + if (!handler_frees_buffer) + vlib_buffer_free_one (vm, buffer_index); +} + +static uword +append_buffer_index_to_iovec (vlib_main_t * vm, + u32 buffer_index, struct iovec **iovs_return) +{ + struct iovec *i; + vlib_buffer_t *b; + u32 bi = buffer_index; + u32 l = 0; + + while (1) + { + b = vlib_get_buffer (vm, bi); + vec_add2 (*iovs_return, i, 1); + i->iov_base = vlib_buffer_get_current (b); + i->iov_len = b->current_length; + l += i->iov_len; + if (!(b->flags & VLIB_BUFFER_NEXT_PRESENT)) + break; + bi = b->next_buffer; + } + + return l; +} + +static clib_error_t * +sendmsg_helper (mc_socket_main_t * msm, + int socket, struct sockaddr_in *tx_addr, u32 buffer_index) +{ + vlib_main_t *vm = msm->mc_main.vlib_main; + struct msghdr h; + word n_bytes, n_bytes_tx, n_retries; + + memset (&h, 0, sizeof (h)); + h.msg_name = tx_addr; + h.msg_namelen = sizeof (tx_addr[0]); + + if (msm->iovecs) + _vec_len (msm->iovecs) = 0; + + n_bytes = append_buffer_index_to_iovec (vm, buffer_index, &msm->iovecs); + ASSERT (n_bytes <= msm->mc_main.transport.max_packet_size); + if (n_bytes > msm->mc_main.transport.max_packet_size) + clib_error ("sending packet larger than interace MTU %d bytes", n_bytes); + + h.msg_iov = msm->iovecs; + h.msg_iovlen = vec_len (msm->iovecs); + + n_retries = 0; + while ((n_bytes_tx = sendmsg (socket, &h, /* flags */ 0)) != n_bytes + && errno == EAGAIN) + n_retries++; + if (n_bytes_tx != n_bytes) + { + clib_unix_warning ("sendmsg"); + return 0; + } + if (n_retries) + { + ELOG_TYPE_DECLARE (e) = + { + .format = "sendmsg-helper: %d retries",.format_args = "i4",}; + struct + { + u32 retries; + } *ed = 0; + + ed = ELOG_DATA (&vm->elog_main, e); + ed->retries = n_retries; + } + return 0; +} + +static clib_error_t * +tx_buffer (void *transport, mc_transport_type_t type, u32 buffer_index) +{ + mc_socket_main_t *msm = (mc_socket_main_t *) transport; + vlib_main_t *vm = msm->mc_main.vlib_main; + mc_multicast_socket_t *ms = &msm->multicast_sockets[type]; + clib_error_t *error; + error = sendmsg_helper (msm, ms->socket, &ms->tx_addr, buffer_index); + if (type != MC_TRANSPORT_USER_REQUEST_TO_RELAY) + vlib_buffer_free_one (vm, buffer_index); + return error; +} + +static clib_error_t * +tx_ack (void *transport, mc_peer_id_t dest_peer_id, u32 buffer_index) +{ + struct sockaddr_in tx_addr; + mc_socket_main_t *msm = (mc_socket_main_t *) transport; + vlib_main_t *vm = msm->mc_main.vlib_main; + clib_error_t *error; + + memset (&tx_addr, 0, sizeof (tx_addr)); + tx_addr.sin_family = AF_INET; + tx_addr.sin_addr.s_addr = mc_socket_peer_id_get_address (dest_peer_id); + tx_addr.sin_port = mc_socket_peer_id_get_port (dest_peer_id); + + error = sendmsg_helper (msm, msm->ack_socket, &tx_addr, buffer_index); + vlib_buffer_free_one (vm, buffer_index); + return error; +} + +static clib_error_t * +recvmsg_helper (mc_socket_main_t * msm, + int socket, + struct sockaddr_in *rx_addr, + u32 * buffer_index, u32 drop_message) +{ + vlib_main_t *vm = msm->mc_main.vlib_main; + vlib_buffer_t *b; + uword n_left, n_alloc, n_mtu, i, i_rx; + const uword buffer_size = VLIB_BUFFER_DEFAULT_FREE_LIST_BYTES; + word n_bytes_left; + + /* Make sure we have at least a MTU worth of buffers. */ + n_mtu = msm->rx_mtu_n_buffers; + n_left = vec_len (msm->rx_buffers); + if (n_left < n_mtu) + { + uword max_alloc = 8 * n_mtu; + vec_validate (msm->rx_buffers, max_alloc - 1); + n_alloc = + vlib_buffer_alloc (vm, msm->rx_buffers + n_left, max_alloc - n_left); + _vec_len (msm->rx_buffers) = n_left + n_alloc; + } + + ASSERT (vec_len (msm->rx_buffers) >= n_mtu); + vec_validate (msm->iovecs, n_mtu - 1); + + /* Allocate RX buffers from end of rx_buffers. + Turn them into iovecs to pass to readv. */ + i_rx = vec_len (msm->rx_buffers) - 1; + for (i = 0; i < n_mtu; i++) + { + b = vlib_get_buffer (vm, msm->rx_buffers[i_rx - i]); + msm->iovecs[i].iov_base = b->data; + msm->iovecs[i].iov_len = buffer_size; + } + _vec_len (msm->iovecs) = n_mtu; + + { + struct msghdr h; + + memset (&h, 0, sizeof (h)); + if (rx_addr) + { + h.msg_name = rx_addr; + h.msg_namelen = sizeof (rx_addr[0]); + } + h.msg_iov = msm->iovecs; + h.msg_iovlen = vec_len (msm->iovecs); + + n_bytes_left = recvmsg (socket, &h, 0); + if (n_bytes_left < 0) + return clib_error_return_unix (0, "recvmsg"); + } + + if (drop_message) + { + *buffer_index = ~0; + return 0; + } + + *buffer_index = msm->rx_buffers[i_rx]; + while (1) + { + b = vlib_get_buffer (vm, msm->rx_buffers[i_rx]); + + b->flags = 0; + b->current_data = 0; + b->current_length = + n_bytes_left < buffer_size ? n_bytes_left : buffer_size; + + n_bytes_left -= buffer_size; + + if (n_bytes_left <= 0) + break; + + i_rx--; + b->flags |= VLIB_BUFFER_NEXT_PRESENT; + b->next_buffer = msm->rx_buffers[i_rx]; + } + + _vec_len (msm->rx_buffers) = i_rx; + + return 0 /* no error */ ; +} + +static clib_error_t * +mastership_socket_read_ready (clib_file_t * uf) +{ + mc_socket_main_t *msm = (mc_socket_main_t *) uf->private_data; + mc_main_t *mcm = &msm->mc_main; + mc_multicast_socket_t *ms = + &msm->multicast_sockets[MC_TRANSPORT_MASTERSHIP]; + clib_error_t *error; + u32 bi; + + error = recvmsg_helper (msm, ms->socket, /* rx_addr */ 0, &bi, /* drop_message */ + 0); + if (!error) + msg_handler (mcm, bi, + /* handler_frees_buffer */ 0, + mc_msg_master_assert_handler); + + return error; +} + +static clib_error_t * +to_relay_socket_read_ready (clib_file_t * uf) +{ + mc_socket_main_t *msm = (mc_socket_main_t *) uf->private_data; + mc_main_t *mcm = &msm->mc_main; + vlib_main_t *vm = msm->mc_main.vlib_main; + mc_multicast_socket_t *ms_to_relay = + &msm->multicast_sockets[MC_TRANSPORT_USER_REQUEST_TO_RELAY]; + mc_multicast_socket_t *ms_from_relay = + &msm->multicast_sockets[MC_TRANSPORT_USER_REQUEST_FROM_RELAY]; + clib_error_t *error; + u32 bi; + u32 is_master = mcm->relay_state == MC_RELAY_STATE_MASTER; + + /* Not the ordering master? Turf the msg */ + error = recvmsg_helper (msm, ms_to_relay->socket, /* rx_addr */ 0, &bi, + /* drop_message */ !is_master); + + /* If we are the master, number and rebroadcast the msg. */ + if (!error && is_master) + { + vlib_buffer_t *b = vlib_get_buffer (vm, bi); + mc_msg_user_request_t *mp = vlib_buffer_get_current (b); + mp->global_sequence = clib_host_to_net_u32 (mcm->relay_global_sequence); + mcm->relay_global_sequence++; + error = + sendmsg_helper (msm, ms_from_relay->socket, &ms_from_relay->tx_addr, + bi); + vlib_buffer_free_one (vm, bi); + } + + return error; +} + +static clib_error_t * +from_relay_socket_read_ready (clib_file_t * uf) +{ + mc_socket_main_t *msm = (mc_socket_main_t *) uf->private_data; + mc_main_t *mcm = &msm->mc_main; + mc_multicast_socket_t *ms = + &msm->multicast_sockets[MC_TRANSPORT_USER_REQUEST_FROM_RELAY]; + clib_error_t *error; + u32 bi; + + error = recvmsg_helper (msm, ms->socket, /* rx_addr */ 0, &bi, /* drop_message */ + 0); + if (!error) + { + msg_handler (mcm, bi, /* handler_frees_buffer */ 1, + mc_msg_user_request_handler); + } + return error; +} + +static clib_error_t * +join_socket_read_ready (clib_file_t * uf) +{ + mc_socket_main_t *msm = (mc_socket_main_t *) uf->private_data; + mc_main_t *mcm = &msm->mc_main; + vlib_main_t *vm = mcm->vlib_main; + mc_multicast_socket_t *ms = &msm->multicast_sockets[MC_TRANSPORT_JOIN]; + clib_error_t *error; + u32 bi; + + error = recvmsg_helper (msm, ms->socket, /* rx_addr */ 0, &bi, /* drop_message */ + 0); + if (!error) + { + vlib_buffer_t *b = vlib_get_buffer (vm, bi); + mc_msg_join_or_leave_request_t *mp = vlib_buffer_get_current (b); + + switch (clib_host_to_net_u32 (mp->type)) + { + case MC_MSG_TYPE_join_or_leave_request: + msg_handler (mcm, bi, /* handler_frees_buffer */ 0, + mc_msg_join_or_leave_request_handler); + break; + + case MC_MSG_TYPE_join_reply: + msg_handler (mcm, bi, /* handler_frees_buffer */ 0, + mc_msg_join_reply_handler); + break; + + default: + ASSERT (0); + break; + } + } + return error; +} + +static clib_error_t * +ack_socket_read_ready (clib_file_t * uf) +{ + mc_socket_main_t *msm = (mc_socket_main_t *) uf->private_data; + mc_main_t *mcm = &msm->mc_main; + clib_error_t *error; + u32 bi; + + error = recvmsg_helper (msm, msm->ack_socket, /* rx_addr */ 0, &bi, + /* drop_message */ 0); + if (!error) + msg_handler (mcm, bi, /* handler_frees_buffer */ 0, + mc_msg_user_ack_handler); + return error; +} + +static void +catchup_cleanup (mc_socket_main_t * msm, + mc_socket_catchup_t * c, clib_file_main_t * um, + clib_file_t * uf) +{ + hash_unset (msm->catchup_index_by_file_descriptor, uf->file_descriptor); + clib_file_del (um, uf); + vec_free (c->input_vector); + vec_free (c->output_vector); + pool_put (msm->catchups, c); +} + +static mc_socket_catchup_t * +find_catchup_from_file_descriptor (mc_socket_main_t * msm, + int file_descriptor) +{ + uword *p = + hash_get (msm->catchup_index_by_file_descriptor, file_descriptor); + return p ? pool_elt_at_index (msm->catchups, p[0]) : 0; +} + +static clib_error_t * +catchup_socket_read_ready (clib_file_t * uf, int is_server) +{ + clib_file_main_t *um = &file_main; + mc_socket_main_t *msm = (mc_socket_main_t *) uf->private_data; + mc_main_t *mcm = &msm->mc_main; + mc_socket_catchup_t *c = + find_catchup_from_file_descriptor (msm, uf->file_descriptor); + word l, n, is_eof; + + l = vec_len (c->input_vector); + vec_resize (c->input_vector, 4096); + n = + read (uf->file_descriptor, c->input_vector + l, + vec_len (c->input_vector) - l); + is_eof = n == 0; + + if (n < 0) + { + if (errno == EAGAIN) + n = 0; + else + { + catchup_cleanup (msm, c, um, uf); + return clib_error_return_unix (0, "read"); + } + } + + _vec_len (c->input_vector) = l + n; + + if (is_eof && vec_len (c->input_vector) > 0) + { + if (is_server) + { + mc_msg_catchup_request_handler (mcm, (void *) c->input_vector, + c - msm->catchups); + _vec_len (c->input_vector) = 0; + } + else + { + mc_msg_catchup_reply_handler (mcm, (void *) c->input_vector, + c - msm->catchups); + c->input_vector = 0; /* reply handler is responsible for freeing vector */ + catchup_cleanup (msm, c, um, uf); + } + } + + return 0 /* no error */ ; +} + +static clib_error_t * +catchup_server_read_ready (clib_file_t * uf) +{ + return catchup_socket_read_ready (uf, /* is_server */ 1); +} + +static clib_error_t * +catchup_client_read_ready (clib_file_t * uf) +{ + if (MC_EVENT_LOGGING) + { + mc_socket_main_t *msm = (mc_socket_main_t *) uf->private_data; + vlib_main_t *vm = msm->mc_main.vlib_main; + + ELOG_TYPE (e, "catchup_client_read_ready"); + ELOG (&vm->elog_main, e, 0); + } + return catchup_socket_read_ready (uf, /* is_server */ 0); +} + +static clib_error_t * +catchup_socket_write_ready (clib_file_t * uf, int is_server) +{ + clib_file_main_t *um = &file_main; + mc_socket_main_t *msm = (mc_socket_main_t *) uf->private_data; + mc_socket_catchup_t *c = + find_catchup_from_file_descriptor (msm, uf->file_descriptor); + clib_error_t *error = 0; + int n; + + if (c->connect_in_progress) + { + u32 len, value; + + c->connect_in_progress = 0; + len = sizeof (value); + if (getsockopt (c->socket, SOL_SOCKET, SO_ERROR, &value, &len) < 0) + { + error = clib_error_return_unix (0, "getsockopt SO_ERROR"); + goto error_quit; + } + if (value != 0) + { + error = + clib_error_return_code (0, value, CLIB_ERROR_ERRNO_VALID, + "connect fails"); + goto error_quit; + } + } + + while (1) + { + u32 n_this_write; + + n_this_write = + clib_min (vec_len (c->output_vector) - c->output_vector_n_written, + msm->rx_mtu_n_bytes - + 64 /* ip + tcp + option allowance */ ); + + if (n_this_write <= 0) + break; + + do + { + n = write (uf->file_descriptor, + c->output_vector + c->output_vector_n_written, + n_this_write); + } + while (n < 0 && errno == EAGAIN); + + if (n < 0) + { + error = clib_error_return_unix (0, "write"); + goto error_quit; + } + c->output_vector_n_written += n; + } + + if (c->output_vector_n_written >= vec_len (c->output_vector)) + { + if (!is_server) + { + uf->flags &= ~UNIX_FILE_DATA_AVAILABLE_TO_WRITE; + file_main.file_update (uf, UNIX_FILE_UPDATE_MODIFY); + /* Send EOF to other side. */ + shutdown (uf->file_descriptor, SHUT_WR); + return error; + } + else + { + error_quit: + catchup_cleanup (msm, c, um, uf); + } + } + return error; +} + +static clib_error_t * +catchup_server_write_ready (clib_file_t * uf) +{ + return catchup_socket_write_ready (uf, /* is_server */ 1); +} + +static clib_error_t * +catchup_client_write_ready (clib_file_t * uf) +{ + return catchup_socket_write_ready (uf, /* is_server */ 0); +} + +static clib_error_t * +catchup_socket_error_ready (clib_file_t * uf) +{ + clib_file_main_t *um = &file_main; + mc_socket_main_t *msm = (mc_socket_main_t *) uf->private_data; + mc_socket_catchup_t *c = + find_catchup_from_file_descriptor (msm, uf->file_descriptor); + catchup_cleanup (msm, c, um, uf); + return clib_error_return (0, "error"); +} + +static clib_error_t * +catchup_listen_read_ready (clib_file_t * uf) +{ + mc_socket_main_t *msm = (mc_socket_main_t *) uf->private_data; + struct sockaddr_in client_addr; + int client_len; + mc_socket_catchup_t *c; + clib_file_t template = { 0 }; + + pool_get (msm->catchups, c); + memset (c, 0, sizeof (c[0])); + + client_len = sizeof (client_addr); + + /* Acquires the non-blocking attrib from the server socket. */ + c->socket = accept (uf->file_descriptor, + (struct sockaddr *) &client_addr, + (socklen_t *) & client_len); + + if (c->socket < 0) + { + pool_put (msm->catchups, c); + return clib_error_return_unix (0, "accept"); + } + + if (MC_EVENT_LOGGING) + { + mc_main_t *mcm = &msm->mc_main; + vlib_main_t *vm = mcm->vlib_main; + + ELOG_TYPE_DECLARE (e) = + { + .format = "catchup accepted from 0x%lx",.format_args = "i4",}; + struct + { + u32 addr; + } *ed = 0; + + ed = ELOG_DATA (&vm->elog_main, e); + ed->addr = ntohl (client_addr.sin_addr.s_addr); + } + + /* Disable the Nagle algorithm, ship catchup pkts immediately */ + { + int one = 1; + if ((setsockopt (c->socket, IPPROTO_TCP, + TCP_NODELAY, (void *) &one, sizeof (one))) < 0) + { + clib_unix_warning ("catchup socket: set TCP_NODELAY"); + } + } + + template.read_function = catchup_server_read_ready; + template.write_function = catchup_server_write_ready; + template.error_function = catchup_socket_error_ready; + template.file_descriptor = c->socket; + template.private_data = pointer_to_uword (msm); + c->clib_file_index = clib_file_add (&file_main, &template); + hash_set (msm->catchup_index_by_file_descriptor, c->socket, + c - msm->catchups); + + return 0; +} + +/* Return and bind to an unused port. */ +static word +find_and_bind_to_free_port (word sock, word port) +{ + for (; port < 1 << 16; port++) + { + struct sockaddr_in a; + + memset (&a, 0, sizeof (a)); /* Warnings be gone */ + + a.sin_family = PF_INET; + a.sin_addr.s_addr = INADDR_ANY; + a.sin_port = htons (port); + + if (bind (sock, (struct sockaddr *) &a, sizeof (a)) >= 0) + break; + } + + return port < 1 << 16 ? port : -1; +} + +static clib_error_t * +setup_mutlicast_socket (mc_socket_main_t * msm, + mc_multicast_socket_t * ms, + char *type, uword udp_port) +{ + int one = 1; + struct ip_mreq mcast_req; + + if (!msm->multicast_ttl) + msm->multicast_ttl = 1; + + /* mastership (multicast) TX socket */ + if ((ms->socket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) + return clib_error_return_unix (0, "%s socket", type); + + { + u8 ttl = msm->multicast_ttl; + + if ((setsockopt (ms->socket, IPPROTO_IP, + IP_MULTICAST_TTL, (void *) &ttl, sizeof (ttl))) < 0) + return clib_error_return_unix (0, "%s set multicast ttl", type); + } + + if (setsockopt (ms->socket, SOL_SOCKET, SO_REUSEADDR, &one, sizeof (one)) < + 0) + return clib_error_return_unix (0, "%s setsockopt SO_REUSEADDR", type); + + memset (&ms->tx_addr, 0, sizeof (ms->tx_addr)); + ms->tx_addr.sin_family = AF_INET; + ms->tx_addr.sin_addr.s_addr = + htonl (msm->multicast_tx_ip4_address_host_byte_order); + ms->tx_addr.sin_port = htons (udp_port); + + if (bind (ms->socket, (struct sockaddr *) &ms->tx_addr, + sizeof (ms->tx_addr)) < 0) + return clib_error_return_unix (0, "%s bind", type); + + memset (&mcast_req, 0, sizeof (mcast_req)); + mcast_req.imr_multiaddr.s_addr = + htonl (msm->multicast_tx_ip4_address_host_byte_order); + mcast_req.imr_interface.s_addr = msm->if_ip4_address_net_byte_order; + + if ((setsockopt (ms->socket, IPPROTO_IP, + IP_ADD_MEMBERSHIP, (void *) &mcast_req, + sizeof (mcast_req))) < 0) + return clib_error_return_unix (0, "%s IP_ADD_MEMBERSHIP setsockopt", + type); + + if (ioctl (ms->socket, FIONBIO, &one) < 0) + return clib_error_return_unix (0, "%s set FIONBIO", type); + + /* FIXME remove this when we support tx_ready. */ + { + u32 len = 1 << 20; + socklen_t sl = sizeof (len); + if (setsockopt (ms->socket, SOL_SOCKET, SO_SNDBUF, &len, sl) < 0) + clib_unix_error ("setsockopt"); + } + + return 0; +} + +static clib_error_t * +socket_setup (mc_socket_main_t * msm) +{ + int one = 1; + clib_error_t *error; + u32 port; + + if (!msm->base_multicast_udp_port_host_byte_order) + msm->base_multicast_udp_port_host_byte_order = + 0xffff - ((MC_N_TRANSPORT_TYPE + 2 /* ack socket, catchup socket */ ) + - 1); + + port = msm->base_multicast_udp_port_host_byte_order; + + error = setup_mutlicast_socket (msm, + &msm->multicast_sockets + [MC_TRANSPORT_MASTERSHIP], "mastership", + port++); + if (error) + return error; + + error = setup_mutlicast_socket (msm, + &msm->multicast_sockets[MC_TRANSPORT_JOIN], + "join", port++); + if (error) + return error; + + error = setup_mutlicast_socket (msm, + &msm->multicast_sockets + [MC_TRANSPORT_USER_REQUEST_TO_RELAY], + "to relay", port++); + if (error) + return error; + + error = setup_mutlicast_socket (msm, + &msm->multicast_sockets + [MC_TRANSPORT_USER_REQUEST_FROM_RELAY], + "from relay", port++); + if (error) + return error; + + /* ACK rx socket */ + msm->ack_socket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (msm->ack_socket < 0) + return clib_error_return_unix (0, "ack socket"); + + msm->ack_udp_port = find_and_bind_to_free_port (msm->ack_socket, port++); + + if (ioctl (msm->ack_socket, FIONBIO, &one) < 0) + return clib_error_return_unix (0, "ack socket FIONBIO"); + + msm->catchup_server_socket = socket (AF_INET, SOCK_STREAM, 0); + if (msm->catchup_server_socket < 0) + return clib_error_return_unix (0, "catchup server socket"); + + msm->catchup_tcp_port = + find_and_bind_to_free_port (msm->catchup_server_socket, port++); + + if (ioctl (msm->catchup_server_socket, FIONBIO, &one) < 0) + return clib_error_return_unix (0, "catchup server socket FIONBIO"); + + if (listen (msm->catchup_server_socket, 5) < 0) + return clib_error_return_unix (0, "catchup server socket listen"); + + /* epoll setup for multicast mastership socket */ + { + clib_file_t template = { 0 }; + + template.read_function = mastership_socket_read_ready; + template.file_descriptor = + msm->multicast_sockets[MC_TRANSPORT_MASTERSHIP].socket; + template.private_data = (uword) msm; + clib_file_add (&file_main, &template); + + /* epoll setup for multicast to_relay socket */ + template.read_function = to_relay_socket_read_ready; + template.file_descriptor = + msm->multicast_sockets[MC_TRANSPORT_USER_REQUEST_TO_RELAY].socket; + template.private_data = (uword) msm; + clib_file_add (&file_main, &template); + + /* epoll setup for multicast from_relay socket */ + template.read_function = from_relay_socket_read_ready; + template.file_descriptor = + msm->multicast_sockets[MC_TRANSPORT_USER_REQUEST_FROM_RELAY].socket; + template.private_data = (uword) msm; + clib_file_add (&file_main, &template); + + template.read_function = join_socket_read_ready; + template.file_descriptor = + msm->multicast_sockets[MC_TRANSPORT_JOIN].socket; + template.private_data = (uword) msm; + clib_file_add (&file_main, &template); + + /* epoll setup for ack rx socket */ + template.read_function = ack_socket_read_ready; + template.file_descriptor = msm->ack_socket; + template.private_data = (uword) msm; + clib_file_add (&file_main, &template); + + /* epoll setup for TCP catchup server */ + template.read_function = catchup_listen_read_ready; + template.file_descriptor = msm->catchup_server_socket; + template.private_data = (uword) msm; + clib_file_add (&file_main, &template); + } + + return 0; +} + +static void * +catchup_add_pending_output (mc_socket_catchup_t * c, uword n_bytes, + u8 * set_output_vector) +{ + clib_file_t *uf = pool_elt_at_index (file_main.file_pool, + c->clib_file_index); + u8 *result = 0; + + if (set_output_vector) + c->output_vector = set_output_vector; + else + vec_add2 (c->output_vector, result, n_bytes); + if (vec_len (c->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) + file_main.file_update (uf, UNIX_FILE_UPDATE_MODIFY); + } + return result; +} + +static uword +catchup_request_fun (void *transport_main, + u32 stream_index, mc_peer_id_t catchup_peer_id) +{ + mc_socket_main_t *msm = (mc_socket_main_t *) transport_main; + mc_main_t *mcm = &msm->mc_main; + vlib_main_t *vm = mcm->vlib_main; + mc_socket_catchup_t *c; + struct sockaddr_in addr; + clib_file_main_t *um = &file_main; + int one = 1; + + pool_get (msm->catchups, c); + memset (c, 0, sizeof (*c)); + + c->socket = socket (AF_INET, SOCK_STREAM, 0); + if (c->socket < 0) + { + clib_unix_warning ("socket"); + return 0; + } + + if (ioctl (c->socket, FIONBIO, &one) < 0) + { + clib_unix_warning ("FIONBIO"); + return 0; + } + + memset (&addr, 0, sizeof (addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = mc_socket_peer_id_get_address (catchup_peer_id); + addr.sin_port = mc_socket_peer_id_get_port (catchup_peer_id); + + c->connect_in_progress = 1; + + if (MC_EVENT_LOGGING) + { + ELOG_TYPE_DECLARE (e) = + { + .format = "connecting to peer 0x%Lx",.format_args = "i8",}; + struct + { + u64 peer; + } *ed; + ed = ELOG_DATA (&vm->elog_main, e); + ed->peer = catchup_peer_id.as_u64; + } + + if (connect (c->socket, (const void *) &addr, sizeof (addr)) + < 0 && errno != EINPROGRESS) + { + clib_unix_warning ("connect to %U fails", + format_socket_peer_id, catchup_peer_id); + return 0; + } + + { + clib_file_t template = { 0 }; + + template.read_function = catchup_client_read_ready; + template.write_function = catchup_client_write_ready; + template.error_function = catchup_socket_error_ready; + template.file_descriptor = c->socket; + template.private_data = (uword) msm; + c->clib_file_index = clib_file_add (um, &template); + + hash_set (msm->catchup_index_by_file_descriptor, c->socket, + c - msm->catchups); + } + + { + mc_msg_catchup_request_t *mp; + mp = catchup_add_pending_output (c, sizeof (mp[0]), /* set_output_vector */ + 0); + mp->peer_id = msm->mc_main.transport.our_catchup_peer_id; + mp->stream_index = stream_index; + mc_byte_swap_msg_catchup_request (mp); + } + + return c - msm->catchups; +} + +static void +catchup_send_fun (void *transport_main, uword opaque, u8 * data) +{ + mc_socket_main_t *msm = (mc_socket_main_t *) transport_main; + mc_socket_catchup_t *c = pool_elt_at_index (msm->catchups, opaque); + catchup_add_pending_output (c, 0, data); +} + +static int +find_interface_ip4_address (char *if_name, u32 * ip4_address, u32 * mtu) +{ + int fd; + struct ifreq ifr; + struct sockaddr_in *sa; + + /* Dig up our IP address */ + fd = socket (PF_INET, AF_INET, 0); + if (fd < 0) + { + clib_unix_error ("socket"); + return -1; + } + + ifr.ifr_addr.sa_family = AF_INET; + strncpy (ifr.ifr_name, if_name, sizeof (ifr.ifr_name) - 1); + if (ioctl (fd, SIOCGIFADDR, &ifr) < 0) + { + clib_unix_error ("ioctl(SIOCFIGADDR)"); + close (fd); + return -1; + } + + sa = (void *) &ifr.ifr_addr; + clib_memcpy (ip4_address, &sa->sin_addr.s_addr, sizeof (ip4_address[0])); + + if (ioctl (fd, SIOCGIFMTU, &ifr) < 0) + { + close (fd); + return -1; + } + if (mtu) + *mtu = ifr.ifr_mtu - ( /* IP4 header */ 20 + /* UDP header */ 8); + + close (fd); + + return 0; +} + +clib_error_t * +mc_socket_main_init (mc_socket_main_t * msm, char **intfc_probe_list, + int n_intfcs_to_probe) +{ + clib_error_t *error; + mc_main_t *mcm; + u32 mtu; + + mcm = &msm->mc_main; + + /* 239.255.0.7 */ + if (!msm->multicast_tx_ip4_address_host_byte_order) + msm->multicast_tx_ip4_address_host_byte_order = 0xefff0007; + + { + u32 i, a, win; + + win = 0; + if (msm->multicast_interface_name) + { + win = + !find_interface_ip4_address (msm->multicast_interface_name, &a, + &mtu); + } + else + { + for (i = 0; i < n_intfcs_to_probe; i++) + if (!find_interface_ip4_address (intfc_probe_list[i], &a, &mtu)) + { + win = 1; + msm->multicast_interface_name = intfc_probe_list[i]; + break; + } + } + + if (!win) + return clib_error_return (0, "can't find interface ip4 address"); + + msm->if_ip4_address_net_byte_order = a; + } + + msm->rx_mtu_n_bytes = mtu; + msm->rx_mtu_n_buffers = + msm->rx_mtu_n_bytes / VLIB_BUFFER_DEFAULT_FREE_LIST_BYTES; + msm->rx_mtu_n_buffers += + (msm->rx_mtu_n_bytes % VLIB_BUFFER_DEFAULT_FREE_LIST_BYTES) != 0; + + error = socket_setup (msm); + if (error) + return error; + + mcm->transport.our_ack_peer_id = + mc_socket_set_peer_id (msm->if_ip4_address_net_byte_order, + msm->ack_udp_port); + + mcm->transport.our_catchup_peer_id = + mc_socket_set_peer_id (msm->if_ip4_address_net_byte_order, + msm->catchup_tcp_port); + + mcm->transport.tx_buffer = tx_buffer; + mcm->transport.tx_ack = tx_ack; + mcm->transport.catchup_request_fun = catchup_request_fun; + mcm->transport.catchup_send_fun = catchup_send_fun; + mcm->transport.format_peer_id = format_socket_peer_id; + mcm->transport.opaque = msm; + mcm->transport.max_packet_size = mtu; + + mc_main_init (mcm, "socket"); + + return error; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vlib/unix/mc_socket.h b/src/vlib/unix/mc_socket.h new file mode 100644 index 00000000..3686c824 --- /dev/null +++ b/src/vlib/unix/mc_socket.h @@ -0,0 +1,137 @@ +/* + * mc_socket.h: socket based multicast for vlib mc + * + * Copyright (c) 2010 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. + */ + +#ifndef __included_mc_socket_h__ +#define __included_mc_socket_h__ + +#include <vlib/unix/unix.h> +#include <netinet/in.h> + +typedef struct +{ + int socket; + struct sockaddr_in tx_addr; +} mc_multicast_socket_t; + +/* TCP catchup socket */ +typedef struct +{ + int socket; + u32 clib_file_index; + + u8 *input_vector; + u8 *output_vector; + u32 output_vector_n_written; + + u32 connect_in_progress; +} mc_socket_catchup_t; + +typedef struct mc_socket_main_t +{ + mc_main_t mc_main; + + /* Multicast mastership/to-relay/from-relay sockets. */ + mc_multicast_socket_t multicast_sockets[MC_N_TRANSPORT_TYPE]; + + /* Unicast UDP ack sockets */ + int ack_socket; + + /* TCP catchup server socket */ + int catchup_server_socket; + + /* Pool of stream-private catchup sockets */ + mc_socket_catchup_t *catchups; + + uword *catchup_index_by_file_descriptor; + + u32 rx_mtu_n_bytes; + + /* Receive MTU in bytes and VLIB buffers. */ + u32 rx_mtu_n_buffers; + + /* Vector of RX VLIB buffers. */ + u32 *rx_buffers; + /* Vector of scatter/gather descriptors for sending/receiving VLIB buffers + via kernel. */ + struct iovec *iovecs; + + /* IP address of interface to use for multicast. */ + u32 if_ip4_address_net_byte_order; + + u32 ack_udp_port; + u32 catchup_tcp_port; + + /* Interface on which to listen for multicasts. */ + char *multicast_interface_name; + + /* Multicast address to use (e.g. 0xefff0000). + Host byte order. */ + u32 multicast_tx_ip4_address_host_byte_order; + + /* TTL to use for multicasts. */ + u32 multicast_ttl; + + /* Multicast ports for mastership, joins, etc. will be chosen + starting at the given port in host byte order. + A total of MC_N_TRANSPORT_TYPE ports will be used. */ + u32 base_multicast_udp_port_host_byte_order; +} mc_socket_main_t; + +always_inline u32 +mc_socket_peer_id_get_address (mc_peer_id_t i) +{ + u32 a = ((i.as_u8[0] << 24) + | (i.as_u8[1] << 16) | (i.as_u8[2] << 8) | (i.as_u8[3] << 0)); + return clib_host_to_net_u32 (a); +} + +always_inline u32 +mc_socket_peer_id_get_port (mc_peer_id_t i) +{ + return clib_host_to_net_u16 ((i.as_u8[4] << 8) | i.as_u8[5]); +} + +static_always_inline mc_peer_id_t +mc_socket_set_peer_id (u32 address_net_byte_order, u32 port_host_byte_order) +{ + mc_peer_id_t i; + u32 a = ntohl (address_net_byte_order); + u32 p = port_host_byte_order; + i.as_u8[0] = (a >> 24) & 0xff; + i.as_u8[1] = (a >> 16) & 0xff; + i.as_u8[2] = (a >> 8) & 0xff; + i.as_u8[3] = (a >> 0) & 0xff; + i.as_u8[4] = (p >> 8) & 0xff; + i.as_u8[5] = (p >> 0) & 0xff; + i.as_u8[6] = 0; + i.as_u8[7] = 0; + return i; +} + +clib_error_t *mc_socket_main_init (mc_socket_main_t * msm, + char **intfc_probe_list, + int n_intfcs_to_probe); +#endif /* __included_mc_socket_h__ */ + + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vlib/unix/plugin.c b/src/vlib/unix/plugin.c new file mode 100644 index 00000000..c2741aaa --- /dev/null +++ b/src/vlib/unix/plugin.c @@ -0,0 +1,553 @@ +/* + * plugin.c: plugin handling + * + * Copyright (c) 2011 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. + */ + +#include <vlib/unix/plugin.h> +#include <vppinfra/elf.h> +#include <dlfcn.h> +#include <dirent.h> + +plugin_main_t vlib_plugin_main; + +char *vlib_plugin_path __attribute__ ((weak)); +char *vlib_plugin_path = ""; +char *vlib_plugin_app_version __attribute__ ((weak)); +char *vlib_plugin_app_version = ""; + +void * +vlib_get_plugin_symbol (char *plugin_name, char *symbol_name) +{ + plugin_main_t *pm = &vlib_plugin_main; + uword *p; + plugin_info_t *pi; + + if ((p = hash_get_mem (pm->plugin_by_name_hash, plugin_name)) == 0) + return 0; + + pi = vec_elt_at_index (pm->plugin_info, p[0]); + return dlsym (pi->handle, symbol_name); +} + +static char * +str_array_to_vec (char *array, int len) +{ + char c, *r = 0; + int n = 0; + + do + { + c = array[n]; + vec_add1 (r, c); + } + while (c && ++n < len); + + if (c) + vec_add1 (r, 0); + + return r; +} + +static int +load_one_plugin (plugin_main_t * pm, plugin_info_t * pi, int from_early_init) +{ + void *handle; + clib_error_t *error; + elf_main_t em = { 0 }; + elf_section_t *section; + u8 *data; + char *version_required; + vlib_plugin_registration_t *reg; + plugin_config_t *pc = 0; + uword *p; + + if (elf_read_file (&em, (char *) pi->filename)) + return -1; + + error = elf_get_section_by_name (&em, ".vlib_plugin_registration", + §ion); + if (error) + { + clib_warning ("Not a plugin: %s\n", (char *) pi->name); + return -1; + } + + data = elf_get_section_contents (&em, section->index, 1); + reg = (vlib_plugin_registration_t *) data; + + if (vec_len (data) != sizeof (*reg)) + { + clib_warning ("vlib_plugin_registration size mismatch in plugin %s\n", + (char *) pi->name); + goto error; + } + + p = hash_get_mem (pm->config_index_by_name, pi->name); + if (p) + { + pc = vec_elt_at_index (pm->configs, p[0]); + if (pc->is_disabled) + { + clib_warning ("Plugin disabled: %s", pi->name); + goto error; + } + if (reg->default_disabled && pc->is_enabled == 0) + { + clib_warning ("Plugin disabled (default): %s", pi->name); + goto error; + } + } + else if (reg->default_disabled) + { + clib_warning ("Plugin disabled (default): %s", pi->name); + goto error; + } + + version_required = str_array_to_vec ((char *) ®->version_required, + sizeof (reg->version_required)); + + if ((strlen (version_required) > 0) && + (strncmp (vlib_plugin_app_version, version_required, + strlen (version_required)))) + { + clib_warning ("Plugin %s version mismatch: %s != %s", + pi->name, vlib_plugin_app_version, reg->version_required); + if (!(pc && pc->skip_version_check == 1)) + { + vec_free (version_required); + goto error; + } + } + + vec_free (version_required); + vec_free (data); + elf_main_free (&em); + + handle = dlopen ((char *) pi->filename, RTLD_LAZY); + + if (handle == 0) + { + clib_warning ("%s", dlerror ()); + clib_warning ("Failed to load plugin '%s'", pi->name); + os_exit (1); + } + + pi->handle = handle; + + reg = dlsym (pi->handle, "vlib_plugin_registration"); + + if (reg == 0) + { + /* This should never happen unless somebody chagnes registration macro */ + clib_warning ("Missing plugin registration in plugin '%s'", pi->name); + os_exit (1); + } + + pi->reg = reg; + pi->version = str_array_to_vec ((char *) ®->version, + sizeof (reg->version)); + + if (reg->early_init) + { + clib_error_t *(*ei) (vlib_main_t *); + void *h; + + h = dlsym (pi->handle, reg->early_init); + if (h) + { + ei = h; + error = (*ei) (pm->vlib_main); + if (error) + { + clib_error_report (error); + os_exit (1); + } + } + else + clib_warning ("Plugin %s: early init function %s set but not found", + (char *) pi->name, reg->early_init); + } + + if (reg->description) + clib_warning ("Loaded plugin: %s (%s)", pi->name, reg->description); + else + clib_warning ("Loaded plugin: %s", pi->name); + + return 0; +error: + vec_free (data); + elf_main_free (&em); + return -1; +} + +static u8 ** +split_plugin_path (plugin_main_t * pm) +{ + int i; + u8 **rv = 0; + u8 *path = pm->plugin_path; + u8 *this = 0; + + for (i = 0; i < vec_len (pm->plugin_path); i++) + { + if (path[i] != ':') + { + vec_add1 (this, path[i]); + continue; + } + vec_add1 (this, 0); + vec_add1 (rv, this); + this = 0; + } + if (this) + { + vec_add1 (this, 0); + vec_add1 (rv, this); + } + return rv; +} + +static int +plugin_name_sort_cmp (void *a1, void *a2) +{ + plugin_info_t *p1 = a1; + plugin_info_t *p2 = a2; + + return strcmp ((char *) p1->name, (char *) p2->name); +} + +int +vlib_load_new_plugins (plugin_main_t * pm, int from_early_init) +{ + DIR *dp; + struct dirent *entry; + struct stat statb; + uword *p; + plugin_info_t *pi; + u8 **plugin_path; + u32 *load_fail_indices = 0; + int i; + + plugin_path = split_plugin_path (pm); + + for (i = 0; i < vec_len (plugin_path); i++) + { + dp = opendir ((char *) plugin_path[i]); + + if (dp == 0) + continue; + + while ((entry = readdir (dp))) + { + u8 *plugin_name; + u8 *filename; + + if (pm->plugin_name_filter) + { + int j; + for (j = 0; j < vec_len (pm->plugin_name_filter); j++) + if (entry->d_name[j] != pm->plugin_name_filter[j]) + goto next; + } + + filename = format (0, "%s/%s%c", plugin_path[i], entry->d_name, 0); + + /* Only accept .so */ + char *ext = strrchr ((const char *) filename, '.'); + /* unreadable */ + if (!ext || (strcmp (ext, ".so") != 0) || + stat ((char *) filename, &statb) < 0) + { + ignore: + vec_free (filename); + continue; + } + + /* a dir or other things which aren't plugins */ + if (!S_ISREG (statb.st_mode)) + goto ignore; + + plugin_name = format (0, "%s%c", entry->d_name, 0); + /* Have we seen this plugin already? */ + p = hash_get_mem (pm->plugin_by_name_hash, plugin_name); + if (p == 0) + { + /* No, add it to the plugin vector */ + vec_add2 (pm->plugin_info, pi, 1); + pi->name = plugin_name; + pi->filename = filename; + pi->file_info = statb; + hash_set_mem (pm->plugin_by_name_hash, plugin_name, + pi - pm->plugin_info); + } + next: + ; + } + closedir (dp); + vec_free (plugin_path[i]); + } + vec_free (plugin_path); + + + /* + * Sort the plugins by name. This is important. + * API traces contain absolute message numbers. + * Loading plugins in directory (vs. alphabetical) order + * makes trace replay incredibly fragile. + */ + vec_sort_with_function (pm->plugin_info, plugin_name_sort_cmp); + + /* + * Attempt to load the plugins + */ + for (i = 0; i < vec_len (pm->plugin_info); i++) + { + pi = vec_elt_at_index (pm->plugin_info, i); + + if (load_one_plugin (pm, pi, from_early_init)) + { + /* Make a note of any which fail to load */ + vec_add1 (load_fail_indices, i); + hash_unset_mem (pm->plugin_by_name_hash, pi->name); + vec_free (pi->name); + vec_free (pi->filename); + } + } + + /* Remove plugin info vector elements corresponding to load failures */ + if (vec_len (load_fail_indices) > 0) + { + for (i = vec_len (load_fail_indices) - 1; i >= 0; i--) + vec_delete (pm->plugin_info, 1, load_fail_indices[i]); + vec_free (load_fail_indices); + } + + /* Recreate the plugin name hash */ + for (i = 0; i < vec_len (pm->plugin_info); i++) + { + pi = vec_elt_at_index (pm->plugin_info, i); + hash_unset_mem (pm->plugin_by_name_hash, pi->name); + hash_set_mem (pm->plugin_by_name_hash, pi->name, pi - pm->plugin_info); + } + + return 0; +} + +int +vlib_plugin_early_init (vlib_main_t * vm) +{ + plugin_main_t *pm = &vlib_plugin_main; + + if (pm->plugin_path == 0) + pm->plugin_path = format (0, "%s%c", vlib_plugin_path, 0); + + clib_warning ("plugin path %s", pm->plugin_path); + + pm->plugin_by_name_hash = hash_create_string (0, sizeof (uword)); + pm->vlib_main = vm; + + return vlib_load_new_plugins (pm, 1 /* from_early_init */ ); +} + +static clib_error_t * +vlib_plugins_show_cmd_fn (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + plugin_main_t *pm = &vlib_plugin_main; + u8 *s = 0; + u8 *key = 0; + uword value = 0; + int index = 1; + plugin_info_t *pi; + + s = format (s, " Plugin path is: %s\n\n", pm->plugin_path); + s = format (s, " %-41s%-33s%s\n", "Plugin", "Version", "Description"); + + /* *INDENT-OFF* */ + hash_foreach_mem (key, value, pm->plugin_by_name_hash, + { + if (key != 0) + { + pi = vec_elt_at_index (pm->plugin_info, value); + s = format (s, "%3d. %-40s %-32s %s\n", index, key, pi->version, + pi->reg->description ? pi->reg->description : ""); + index++; + } + }); + /* *INDENT-ON* */ + + vlib_cli_output (vm, "%v", s); + vec_free (s); + return 0; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (plugins_show_cmd, static) = +{ + .path = "show plugins", + .short_help = "show loaded plugins", + .function = vlib_plugins_show_cmd_fn, +}; +/* *INDENT-ON* */ + +static clib_error_t * +config_one_plugin (vlib_main_t * vm, char *name, unformat_input_t * input) +{ + plugin_main_t *pm = &vlib_plugin_main; + plugin_config_t *pc; + clib_error_t *error = 0; + uword *p; + int is_enable = 0; + int is_disable = 0; + int skip_version_check = 0; + + if (pm->config_index_by_name == 0) + pm->config_index_by_name = hash_create_string (0, sizeof (uword)); + + p = hash_get_mem (pm->config_index_by_name, name); + + if (p) + { + error = clib_error_return (0, "plugin '%s' already configured", name); + goto done; + } + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "enable")) + is_enable = 1; + else if (unformat (input, "disable")) + is_disable = 1; + else if (unformat (input, "skip-version-check")) + skip_version_check = 1; + else + { + error = clib_error_return (0, "unknown input '%U'", + format_unformat_error, input); + goto done; + } + } + + if (is_enable && is_disable) + { + error = clib_error_return (0, "please specify either enable or disable" + " for plugin '%s'", name); + goto done; + } + + vec_add2 (pm->configs, pc, 1); + hash_set_mem (pm->config_index_by_name, name, pc - pm->configs); + pc->is_enabled = is_enable; + pc->is_disabled = is_disable; + pc->skip_version_check = skip_version_check; + pc->name = name; + +done: + return error; +} + +clib_error_t * +vlib_plugin_config (vlib_main_t * vm, unformat_input_t * input) +{ + plugin_main_t *pm = &vlib_plugin_main; + clib_error_t *error = 0; + unformat_input_t in; + + unformat_init (&in, 0, 0); + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + u8 *s, *v; + if (unformat (input, "%s %v", &s, &v)) + { + if (strncmp ((const char *) s, "plugins", 8) == 0) + { + if (vec_len (in.buffer) > 0) + vec_add1 (in.buffer, ' '); + vec_add (in.buffer, v, vec_len (v)); + } + } + else + { + error = clib_error_return (0, "unknown input '%U'", + format_unformat_error, input); + goto done; + } + + vec_free (v); + vec_free (s); + } +done: + input = ∈ + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + unformat_input_t sub_input; + u8 *s = 0; + if (unformat (input, "path %s", &s)) + pm->plugin_path = s; + else if (unformat (input, "plugin %s %U", &s, + unformat_vlib_cli_sub_input, &sub_input)) + { + error = config_one_plugin (vm, (char *) s, &sub_input); + unformat_free (&sub_input); + if (error) + goto done2; + } + else + { + error = clib_error_return (0, "unknown input '%U'", + format_unformat_error, input); + { + vec_free (s); + goto done2; + } + } + } + +done2: + unformat_free (&in); + return error; +} + +/* discard whole 'plugins' section, as it is already consumed prior to + plugin load */ +static clib_error_t * +plugins_config (vlib_main_t * vm, unformat_input_t * input) +{ + u8 *junk; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "%s", &junk)) + { + vec_free (junk); + return 0; + } + else + return clib_error_return (0, "unknown input '%U'", + format_unformat_error, input); + } + return 0; +} + +VLIB_CONFIG_FUNCTION (plugins_config, "plugins"); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vlib/unix/plugin.h b/src/vlib/unix/plugin.h new file mode 100644 index 00000000..d9801ec4 --- /dev/null +++ b/src/vlib/unix/plugin.h @@ -0,0 +1,126 @@ +/* + * plugin.h: plugin handling + * + * Copyright (c) 2011 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. + */ + +#ifndef __included_plugin_h__ +#define __included_plugin_h__ + +#include <vlib/vlib.h> +#include <vlib/unix/unix.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +/* + * vlib plugin scheme + * + * Almost anything which can be made to work in a vlib unix + * application will also work in a vlib plugin. + * + * The elf-section magic which registers static objects + * works so long as plugins are preset when the vlib unix process + * starts. But wait: there's more... + * + * If an application calls vlib_load_new_plugins() -- possibly after + * changing vlib_plugin_main.plugin_path / vlib_plugin_main.plugin_name_filter, + * -- new plugins will be loaded. That, in turn, allows considerable + * flexibility in terms of adding feature code or fixing bugs without + * requiring the data-plane process to restart. + * + * When the plugin mechanism loads a plugin, it uses dlsym to locate + * and call the plugin's function vlib_plugin_register() if it exists. + * A plugin which expects to be loaded after the vlib application + * starts uses this callback to modify the application. If vlib_plugin_register + * returns non-zero, the plugin mechanism dlclose()'s the plugin. + * + * Applications control the plugin search path and name filter by + * declaring the variables vlib_plugin_path and vlib_plugin_name_filter. + * libvlib.la supplies weak references for these symbols which + * effectively disable the scheme. In order for the elf-section magic to + * work, static plugins must be loaded at the earliest possible moment. + * + * An application can change these parameters at any time and call + * vlib_load_new_plugins(). + */ + +/* *INDENT-OFF* */ +typedef CLIB_PACKED(struct { + u8 default_disabled; + const char version[32]; + const char version_required[32]; + const char *early_init; + const char *description; +}) vlib_plugin_registration_t; +/* *INDENT-ON* */ + +typedef struct +{ + u8 *name; + u8 *filename; + struct stat file_info; + void *handle; + + /* plugin registration */ + vlib_plugin_registration_t *reg; + char *version; +} plugin_info_t; + +typedef struct +{ + char *name; + u8 is_disabled; + u8 is_enabled; + u8 skip_version_check; +} plugin_config_t; + +typedef struct +{ + /* loaded plugin info */ + plugin_info_t *plugin_info; + uword *plugin_by_name_hash; + + /* path and name filter */ + u8 *plugin_path; + u8 *plugin_name_filter; + + /* plugin configs and hash by name */ + plugin_config_t *configs; + uword *config_index_by_name; + + /* usual */ + vlib_main_t *vlib_main; +} plugin_main_t; + +extern plugin_main_t vlib_plugin_main; + +clib_error_t *vlib_plugin_config (vlib_main_t * vm, unformat_input_t * input); +int vlib_plugin_early_init (vlib_main_t * vm); +int vlib_load_new_plugins (plugin_main_t * pm, int from_early_init); +void *vlib_get_plugin_symbol (char *plugin_name, char *symbol_name); + +#define VLIB_PLUGIN_REGISTER() \ + vlib_plugin_registration_t vlib_plugin_registration \ + __attribute__((__section__(".vlib_plugin_registration"))) + +#endif /* __included_plugin_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vlib/unix/unix.h b/src/vlib/unix/unix.h new file mode 100644 index 00000000..4c8566b7 --- /dev/null +++ b/src/vlib/unix/unix.h @@ -0,0 +1,168 @@ +/* + * 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. + */ +/* + * unix.h: Unix specific main state + * + * 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. + */ + +#ifndef included_unix_unix_h +#define included_unix_unix_h + +#include <vppinfra/file.h> +#include <vppinfra/socket.h> +#include <termios.h> + +typedef struct +{ + f64 time; + clib_error_t *error; +} unix_error_history_t; + +typedef struct +{ + /* Back pointer to main structure. */ + vlib_main_t *vlib_main; + + u32 flags; + /* Run interactively or as daemon (background process). */ +#define UNIX_FLAG_INTERACTIVE (1 << 0) +#define UNIX_FLAG_NODAEMON (1 << 1) + + /* CLI listen socket. */ + clib_socket_t cli_listen_socket; + + /* Circular buffer of last unix errors. */ + unix_error_history_t error_history[128]; + u32 error_history_index; + u64 n_total_errors; + + /* startup-config filename */ + u8 *startup_config_filename; + + /* runtime directory path */ + u8 *runtime_dir; + + /* pidfile filename */ + u8 *pidfile; + + /* unix config complete */ + volatile int unix_config_complete; + + /* CLI log file. GIGO. */ + u8 *log_filename; + int log_fd; + + /* Don't put CLI connections into character mode */ + int cli_line_mode; + + /* Maximum amount of command line history to keep per session */ + u32 cli_history_limit; + + /* 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; +} unix_main_t; + +/* Global main structure. */ +extern unix_main_t unix_main; +extern clib_file_main_t file_main; + +always_inline void +unix_save_error (unix_main_t * um, clib_error_t * error) +{ + unix_error_history_t *eh = um->error_history + um->error_history_index; + clib_error_free_vector (eh->error); + eh->error = error; + eh->time = vlib_time_now (um->vlib_main); + um->n_total_errors += 1; + if (++um->error_history_index >= ARRAY_LEN (um->error_history)) + um->error_history_index = 0; +} + +/* Main function for Unix VLIB. */ +int vlib_unix_main (int argc, char *argv[]); + +clib_error_t *unix_physmem_init (vlib_main_t * vm); + +/* Set prompt for CLI. */ +void vlib_unix_cli_set_prompt (char *prompt); + +static inline unix_main_t * +vlib_unix_get_main (void) +{ + return &unix_main; +} + +static inline char * +vlib_unix_get_runtime_dir (void) +{ + return (char *) unix_main.runtime_dir; +} + +/* thread stack array; vec_len = max number of threads */ +extern u8 **vlib_thread_stacks; + +/* utils */ + +clib_error_t *foreach_directory_file (char *dir_name, + clib_error_t * (*f) (void *arg, + u8 * path_name, + u8 * file_name), + void *arg, int scan_dirs); + +clib_error_t *vlib_unix_recursive_mkdir (char *path); + +clib_error_t *vlib_unix_validate_runtime_file (unix_main_t * um, + const char *path, + u8 ** full_path); + +#endif /* included_unix_unix_h */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vlib/unix/util.c b/src/vlib/unix/util.c new file mode 100644 index 00000000..5472751e --- /dev/null +++ b/src/vlib/unix/util.c @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2016 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. + */ +/* + * pci.c: Linux user space PCI bus management. + * + * 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 <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <dirent.h> + +clib_error_t * +foreach_directory_file (char *dir_name, + clib_error_t * (*f) (void *arg, u8 * path_name, + u8 * file_name), void *arg, + int scan_dirs) +{ + DIR *d; + struct dirent *e; + clib_error_t *error = 0; + u8 *s, *t; + + d = opendir (dir_name); + if (!d) + { + if (errno == ENOENT) + return 0; + return clib_error_return_unix (0, "open `%s'", dir_name); + } + + s = t = 0; + while (1) + { + e = readdir (d); + if (!e) + break; + if (scan_dirs) + { + if (e->d_type == DT_DIR + && (!strcmp (e->d_name, ".") || !strcmp (e->d_name, ".."))) + continue; + } + else + { + if (e->d_type == DT_DIR) + continue; + } + + s = format (s, "%s/%s", dir_name, e->d_name); + t = format (t, "%s", e->d_name); + error = f (arg, s, t); + _vec_len (s) = 0; + _vec_len (t) = 0; + + if (error) + break; + } + + vec_free (s); + closedir (d); + + return error; +} + +clib_error_t * +vlib_unix_recursive_mkdir (char *path) +{ + clib_error_t *error = 0; + char *c = 0; + int i = 0; + + while (path[i] != 0) + { + if (c && path[i] == '/') + { + vec_add1 (c, 0); + if ((mkdir (c, 0755)) && (errno != EEXIST)) + { + error = clib_error_return_unix (0, "mkdir '%s'", c); + goto done; + } + _vec_len (c)--; + } + vec_add1 (c, path[i]); + i++; + } + + if ((mkdir (path, 0755)) && (errno != EEXIST)) + { + error = clib_error_return_unix (0, "mkdir '%s'", path); + goto done; + } + +done: + vec_free (c); + + return error; +} + +clib_error_t * +vlib_unix_validate_runtime_file (unix_main_t * um, + const char *path, u8 ** full_path) +{ + u8 *fp = 0; + char *last_slash = 0; + + if (path[0] == '\0') + { + return clib_error_return (0, "path is an empty string"); + } + else if (strncmp (path, "../", 3) == 0 || strstr (path, "/../")) + { + return clib_error_return (0, "'..' not allowed in runtime path"); + } + else if (path[0] == '/') + { + /* Absolute path. Has to start with runtime directory */ + if (strncmp ((char *) um->runtime_dir, path, + strlen ((char *) um->runtime_dir))) + { + return clib_error_return (0, + "file %s is not in runtime directory %s", + path, um->runtime_dir); + } + fp = format (0, "%s%c", path, '\0'); + } + else + { + /* Relative path, just append to runtime */ + fp = format (0, "%s/%s%c", um->runtime_dir, path, '\0'); + } + + /* We don't want to create a directory out of the last file */ + if ((last_slash = strrchr ((char *) fp, '/')) != NULL) + *last_slash = '\0'; + + clib_error_t *error = vlib_unix_recursive_mkdir ((char *) fp); + + if (last_slash != NULL) + *last_slash = '/'; + + if (error) + vec_free (fp); + + *full_path = fp; + return error; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ |