/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** 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 safety measure. */ #define UNIX_CLI_MAX_DEPTH_TELNET 24 /** Maximum terminal width we will accept */ #define UNIX_CLI_MAX_TERMINAL_WIDTH 512 /** Maximum terminal height we will accept */ #define UNIX_CLI_MAX_TERMINAL_HEIGHT 512 /** Default terminal height */ #define UNIX_CLI_DEFAULT_TERMINAL_HEIGHT 24 /** Default terminal width */ #define UNIX_CLI_DEFAULT_TERMINAL_WIDTH 80 /** A CLI banner line. */ typedef struct { 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; /** The current direction of cursor travel. * This is important since when advancing left-to-right, at the * right hand edge of the console the terminal typically defers * wrapping the cursor to the next line until a character is * actually displayed. * This messes up our heuristic for whether to use ANSI to return * the cursor to the end of the line and instead we have to * nudge the cursor to the next line. * A Value of @c 0 means we're advancing left-to-right; @c 1 means * the opposite. */ u8 cursor_direction; } unix_cli_file_t; /** Resets the pager buffer and other data. * @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 { 0x01, 0x00 } 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 session telnet negotiation timer events. */ typedef enum { UNIX_CLI_NEW_SESSION_EVENT_ADD, /**< Add a CLI session to the new session list */ } unix_cli_timeout_event_type_t; /** Each new session is stored on a list with a deadline after which * a prompt is issued, in case the session TELNET negotiation fails to * complete. */ typedef struct { uword cf_index; /**< Session index of the new session. */ f64 deadline; /**< Deadline after which the new session must have a prompt. */ } unix_cli_new_session_t; /** CLI global state. */ typedef struct { /** 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; /** New session process node identifier */ u32 new_session_process_node_index; /** List of new sessions */ unix_cli_new_session_t *new_sessions; } unix_cli_main_t; /** CLI global state */ 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 occurrence 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; } } /* Use the last character to determine the last direction of the cursor. */ if (buffer_bytes > 0) cf->cursor_direction = (buffer[buffer_bytes - 1] == (u8) '\b'); } /** @brief Moves the terminal cursor one character to the left, with * special handling when it reaches the left edge of the terminal window. * * Ordinarily we can simply send a '\b' to move the cursor left, however * most terminals will not reverse-wrap to the end of the previous line * if the cursor is in the left-most column. To counter this we must * check the cursor position + prompt length modulo terminal width and * if available use some other means, such as ANSI terminal escape * sequences, to move the cursor. * * @param cf Unix CLI session of the desired stream to write to. * @param uf The Unix file structure of the desired stream to write to. */ static void unix_vlib_cli_output_cursor_left (unix_cli_file_t * cf, clib_file_t * uf) { unix_cli_main_t *cm = &unix_cli_main; static u8 *ansi = 0; /* assumes no reentry */ u32 position; if (!cf->is_interactive || !cf->ansi_capable || !cf->width) { /* No special handling for dumb terminals */ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1); return; } position = ((u32) vec_len (cm->cli_prompt) + cf->cursor) % cf->width; if (position != 0) { /* No special handling required if we're not at the left edge */ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1); return; } if (!cf->cursor_direction) { /* Special handling for when we are at the left edge but * the cursor was going left-to-right, but in this situation * xterm-like terminals actually hide the cursor off the right * edge. A \b here seems to jump one char too many, so let's * force the cursor onto the next line instead. */ if (cf->cursor < vec_len (cf->current_command)) unix_vlib_cli_output_cooked (cf, uf, &cf->current_command[cf->cursor], 1); else unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1); unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\r", 1); } /* Relocate the cursor at the right hand edge one line above */ ansi = format (ansi, CSI "A" CSI "%dC", cf->width - 1); unix_vlib_cli_output_cooked (cf, uf, ansi, vec_len (ansi)); vec_reset_length (ansi); /* keep the vec around for next time */ cf->cursor_direction = 1; /* going backwards now */ } /** @brief Output the CLI prompt */ 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 ("\\n") 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 already 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 = NULL; 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; if (cf->pager_vector != NULL) 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 manager that ensures CLI sessions issue an initial * prompt if TELNET negotiation fails. */ static uword unix_cli_new_session_process (vlib_main_t * vm, vlib_node_runtime_t * rt, vlib_frame_t * f) { unix_cli_main_t *cm = &unix_cli_main; uword event_type, *event_data = 0; f64 wait = 10.0; while (1) { if (vec_len (cm->new_sessions) > 0) wait = vlib_process_wait_for_event_or_clock (vm, wait); else vlib_process_wait_for_event (vm); event_type = vlib_process_get_events (vm, &event_data); switch (event_type) { case ~0: /* no events => timeout */ break; case UNIX_CLI_NEW_SESSION_EVENT_ADD: { /* Add an identifier to the new session list */ unix_cli_new_session_t ns; ns.cf_index = event_data[0]; ns.deadline = vlib_time_now (vm) + 1.0; vec_add1 (cm->new_sessions, ns); if (wait > 0.1) wait = 0.1; /* force a re-evaluation soon, but not too soon */ } break; default: clib_warning ("BUG: unknown event type 0x%wx", event_type); break; } vec_reset_length (event_data); if (vlib_process_suspend_time_is_zero (wait)) { /* Scan the list looking for expired deadlines. * Emit needed prompts and remove from the list. * While scanning, look for the nearest new deadline * for the next iteration. * Since the list is ordered with newest sessions first * we can make assumptions about expired sessions being * contiguous at the beginning and the next deadline is the * next entry on the list, if any. */ f64 now = vlib_time_now (vm); unix_cli_new_session_t *nsp; word index = 0; wait = INFINITY; vec_foreach (nsp, cm->new_sessions) { if (vlib_process_suspend_time_is_zero (nsp->deadline - now)) { /* Deadline reached */ unix_cli_file_t *cf; /* Mark the highwater */ index++; /* Check the connection didn't close already */ if (pool_is_free_index (cm->cli_file_pool, nsp->cf_index)) continue; cf = pool_elt_at_index (cm->cli_file_pool, nsp->cf_index); if (!cf->started) unix_cli_file_welcome (cm, cf); } else { wait = nsp->deadline - now; break; } } if (index) { /* We have sessions to remove */ vec_delete (cm->new_sessions, index, 0); } } } return 0; } /** @brief A mostly no-op Telnet state machine. * Process Telnet command bytes in a way that ensures we're mostly * transparent to the Telnet protocol. That is, it's mostly a no-op. * * @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 (len < 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 == 0) cf->width = UNIX_CLI_DEFAULT_TERMINAL_WIDTH; cf->height = clib_net_to_host_u16 (*((u16 *) (input_vector + 5))); if (cf->height > UNIX_CLI_MAX_TERMINAL_HEIGHT) cf->height = UNIX_CLI_MAX_TERMINAL_HEIGHT; if (cf->height == 0) cf->height = UNIX_CLI_DEFAULT_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 (; cf->cursor > 0; cf->cursor--) { unix_vlib_cli_output_cursor_left (cf, uf); unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1); unix_vlib_cli_output_cursor_left (cf, uf); } vec_reset_length (cf->search_key); vec_reset_length (cf->current_command); if (action }
/*
 *------------------------------------------------------------------
 * Copyright (c) 2017 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 <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <stddef.h>
#include <assert.h>

#include <vpp-api/vapi/vapi_dbg.h>
#include <vpp-api/vapi/vapi.h>
#include <vpp-api/vapi/vapi_internal.h>
#include <vppinfra/types.h>
#include <vppinfra/pool.h>
#include <vlib/vlib.h>
#include <vlibapi/api_common.h>
#include <vlibmemory/memory_client.h>

#include <vapi/memclnt.api.vapi.h>
#include <vapi/vlib.api.vapi.h>

/* we need to use control pings for some stuff and because we're forced to put
 * the code in headers, we need a way to be able to grab the ids of these
 * messages - so declare them here as extern */
vapi_msg_id_t vapi_msg_id_control_ping = 0;
vapi_msg_id_t vapi_msg_id_control_ping_reply = 0;

DEFINE_VAPI_MSG_IDS_MEMCLNT_API_JSON;
DEFINE_VAPI_MSG_IDS_VLIB_API_JSON;

struct
{
  size_t count;
  vapi_message_desc_t **msgs;
  size_t max_len_name_with_crc;
} __vapi_metadata;

typedef struct
{
  u32 context;
  vapi_cb_t callback;
  void *callback_ctx;
  bool is_dump;
} vapi_req_t;

static const u32 context_counter_mask = (1 << 31);

typedef struct
{
  vapi_error_e (*cb) (vapi_ctx_t ctx, void *callback_ctx, vapi_msg_id_t id,
		      void *payload);
  void *ctx;
} vapi_generic_cb_with_ctx;

typedef struct
{
  vapi_error_e (*cb) (vapi_ctx_t ctx, void *callback_ctx, void *payload);
  void *ctx;
} vapi_event_cb_with_ctx;

struct vapi_ctx_s
{
  vapi_mode_e mode;
  int requests_size;		/* size of the requests array (circular queue) */
  int requests_start;		/* index of first request */
  int requests_count;		/* number of used slots */
  vapi_req_t *requests;
  u32 context_counter;
  vapi_generic_cb_with_ctx generic_cb;
  vapi_event_cb_with_ctx *event_cbs;
  u16 *vapi_msg_id_t_to_vl_msg_id;
  u16 vl_msg_id_max;
  vapi_msg_id_t *vl_msg_id_to_vapi_msg_t;
  bool connected;
  bool handle_keepalives;
  pthread_mutex_t requests_mutex;
};

u32
vapi_gen_req_context (vapi_ctx_t ctx)
{
  ++ctx->context_counter;
  ctx->context_counter %= context_counter_mask;
  return ctx->context_counter | context_counter_mask;
}

size_t
vapi_get_request_count (vapi_ctx_t ctx)
{
  return ctx->requests_count;
}

bool
vapi_requests_full (vapi_ctx_t ctx)
{
  return (ctx->requests_count == ctx->requests_size);
}

bool
vapi_requests_empty (vapi_ctx_t ctx)
{
  return (0 == ctx->requests_count);
}

static int
vapi_requests_end (vapi_ctx_t ctx)
{
  return (ctx->requests_start + ctx->requests_count) % ctx->requests_size;
}

void
vapi_store_request (vapi_ctx_t ctx, u32 context, bool is_dump,
		    vapi_cb_t callback, void *callback_ctx)
{
  assert (!vapi_requests_full (ctx));
  /* if the mutex is not held, bad things will happen */
  assert (0 != pthread_mutex_trylock (&ctx->requests_mutex));
  const int requests_end = vapi_requests_end (ctx);
  vapi_req_t *slot = &ctx->requests[requests_end];
  slot->is_dump = is_dump;
  slot->context = context;
  slot->callback = callback;
  slot->callback_ctx = callback_ctx;
  VAPI_DBG ("stored@%d: context:%x (start is @%d)", requests_end, context,
	    ctx->requests_start);
  ++ctx->requests_count;
  assert (!vapi_requests_empty (ctx));
}

#if VAPI_DEBUG_ALLOC
struct to_be_freed_s;
struct to_be_freed_s
{
  void *v;
  struct to_be_freed_s *next;
};

static struct to_be_freed_s *to_be_freed = NULL;

void
vapi_add_to_be_freed (void *v)
{
  struct to_be_freed_s *prev = NULL;
  struct to_be_freed_s *tmp;
  tmp = to_be_freed;
  while (tmp && tmp->v)
    {
      prev = tmp;
      tmp = tmp->next;
    }
  if (!tmp)
    {
      if (!prev)
	{
	  tmp = to_be_freed = calloc (1, sizeof (*to_be_freed));
	}
      else
	{
	  tmp = prev->next = calloc (1, sizeof (*to_be_freed));
	}
    }
  VAPI_DBG ("To be freed %p", v);
  tmp->v = v;
}

void
vapi_trace_free (void *v)
{
  struct to_be_freed_s *tmp = to_be_freed;
  while (tmp && tmp->v != v)
    {
      tmp = tmp->next;
    }
  if (tmp && tmp->v == v)
    {
      VAPI_DBG ("Freed %p", v);
      tmp->v = NULL;
    }
  else
    {
      VAPI_ERR ("Trying to free untracked pointer %p", v);
      abort ();
    }
}

void
vapi_to_be_freed_validate ()
{
  struct to_be_freed_s *tmp = to_be_freed;
  while (tmp)
    {
      if (tmp->v)
	{
	  VAPI_ERR ("Unfreed msg %p!", tmp->v);
	}
      tmp = tmp->next;
    }
}

#endif

void *
vapi_msg_alloc (vapi_ctx_t ctx, size_t size)
{
  if (!ctx->connected)
    {
      return NULL;
    }
  void *rv = vl_msg_api_alloc_or_null (size);
  if (rv)
    {
      clib_memset (rv, 0, size);
    }
  return rv;
}

void
vapi_msg_free (vapi_ctx_t ctx, void *msg)
{
  if (!ctx->connected)
    {
      return;
    }
#if VAPI_DEBUG_ALLOC
  vapi_trace_free (msg);
#endif
  vl_msg_api_free (msg);
}

vapi_msg_id_t
vapi_lookup_vapi_msg_id_t (vapi_ctx_t ctx, u16 vl_msg_id)
{
  if (vl_msg_id <= ctx->vl_msg_id_max)
    {
      return ctx->vl_msg_id_to_vapi_msg_t[vl_msg_id];
    }
  return VAPI_INVALID_MSG_ID;
}

vapi_error_e
vapi_ctx_alloc (vapi_ctx_t * result)
{
  vapi_ctx_t ctx = calloc (1, sizeof (struct vapi_ctx_s));
  if (!ctx)
    {
      return VAPI_ENOMEM;
    }
  ctx->context_counter = 0;
  ctx->vapi_msg_id_t_to_vl_msg_id =
    malloc (__vapi_metadata.count *
	    sizeof (*ctx->vapi_msg_id_t_to_vl_msg_id));
  if (!ctx->vapi_msg_id_t_to_vl_msg_id)
    {
      goto fail;
    }
  clib_memset (ctx->vapi_msg_id_t_to_vl_msg_id, ~0,
	       __vapi_metadata.count *
	       sizeof (*ctx->vapi_msg_id_t_to_vl_msg_id));
  ctx->event_cbs = calloc (__vapi_metadata.count, sizeof (*ctx->event_cbs));
  if (!ctx->event_cbs)
    {
      goto fail;
    }
  pthread_mutex_init (&ctx->requests_mutex, NULL);
  *result = ctx;
  return VAPI_OK;
fail:
  vapi_ctx_free (ctx);
  return VAPI_ENOMEM;
}

void
vapi_ctx_free (vapi_ctx_t ctx)
{
  assert (!ctx->connected);
  free (ctx->requests);
  free (ctx->vapi_msg_id_t_to_vl_msg_id);
  free (ctx->event_cbs);
  free (ctx->vl_msg_id_to_vapi_msg_t);
  pthread_mutex_destroy (&ctx->requests_mutex);
  free (ctx);
}

bool
vapi_is_msg_available (vapi_ctx_t ctx, vapi_msg_id_t id)
{
  return vapi_lookup_vl_msg_id (ctx, id) != UINT16_MAX;
}

vapi_error_e
vapi_connect (vapi_ctx_t ctx, const char *name,
	      const char *chroot_prefix,
	      int max_outstanding_requests,
	      int response_queue_size, vapi_mode_e mode,
	      bool handle_keepalives)
{
  if (response_queue_size <= 0 || max_outstanding_requests <= 0)
    {
      return VAPI_EINVAL;
    }
  if (!clib_mem_get_per_cpu_heap () && !clib_mem_init (0, 1024 * 1024 * 32))
    {
      return VAPI_ENOMEM;
    }
  ctx->requests_size = max_outstanding_requests;
  const size_t size = ctx->requests_size * sizeof (*ctx->requests);
  void *tmp = realloc (ctx->requests, size);
  if (!tmp)
    {
      return VAPI_ENOMEM;
    }
  ctx->requests = tmp;
  clib_memset (ctx->requests, 0, size);
  /* coverity[MISSING_LOCK] - 177211 requests_mutex is not needed here */
  ctx->requests_start = ctx->requests_count = 0;
  if (chroot_prefix)
    {
      VAPI_DBG ("set memory root path `%s'", chroot_prefix);
      vl_set_memory_root_path ((char *) chroot_prefix);
    }
  static char api_map[] = "/vpe-api";
  VAPI_DBG ("client api map `%s'", api_map);
  if ((vl_client_api_map (api_map)) < 0)
    {
      return VAPI_EMAP_FAIL;
    }
  VAPI_DBG ("connect client `%s'", name);
  if (vl_client_connect ((char *) name, 0, response_queue_size) < 0)
    {
      vl_client_api_unmap ();
      return VAPI_ECON_FAIL;
    }
#if VAPI_DEBUG_CONNECT
  VAPI_DBG ("start probing messages");
#endif
  int rv;
  int i;
  for (i = 0; i < __vapi_metadata.count; ++i)
    {
      vapi_message_desc_t *m = __vapi_metadata.msgs[i];
      u8 scratch[m->name_with_crc_len + 1];
      memcpy (scratch, m->name_with_crc, m->name_with_crc_len + 1);
      u32 id = vl_msg_api_get_msg_index (scratch);
      if (VAPI_INVALID_MSG_ID != id)
	{
	  if (id > UINT16_MAX)
	    {
	      VAPI_ERR ("Returned vl_msg_id `%u' > UINT16MAX `%u'!", id,
			UINT16_MAX);
	      rv = VAPI_EINVAL;
	      goto fail;
	    }
	  if (id > ctx->vl_msg_id_max)
	    {
	      vapi_msg_id_t *tmp = realloc (ctx->vl_msg_id_to_vapi_msg_t,
					    sizeof
					    (*ctx->vl_msg_id_to_vapi_msg_t) *
					    (id + 1));
	      if (!tmp)
		{
		  rv = VAPI_ENOMEM;
		  goto fail;
		}
	      ctx->vl_msg_id_to_vapi_msg_t = tmp;
	      ctx->vl_msg_id_max = id;
	    }
	  ctx->vl_msg_id_to_vapi_msg_t[id] = m->id;
	  ctx->vapi_msg_id_t_to_vl_msg_id[m->id] = id;
#if VAPI_DEBUG_CONNECT
	  VAPI_DBG ("Message `%s' has vl_msg_id `%u'", m->name_with_crc,
		    (unsigned) id);
#endif
	}
      else
	{
	  ctx->vapi_msg_id_t_to_vl_msg_id[m->id] = UINT16_MAX;
	  VAPI_DBG ("Message `%s' not available", m->name_with_crc);
	}
    }
#if VAPI_DEBUG_CONNECT
  VAPI_DBG ("finished probing messages");
#endif
  if (!vapi_is_msg_available (ctx, vapi_msg_id_control_ping) ||
      !vapi_is_msg_available (ctx, vapi_msg_id_control_ping_reply))
    {
      VAPI_ERR
	("control ping or control ping reply not available, cannot connect");
      rv = VAPI_EINCOMPATIBLE;
      goto fail;
    }
  ctx->mode = mode;
  ctx->connected = true;
  if (vapi_is_msg_available (ctx, vapi_msg_id_memclnt_keepalive))
    {
      ctx->handle_keepalives = handle_keepalives;
    }
  else
    {
      ctx->handle_keepalives = false;
    }
  return VAPI_OK;
fail:
  vl_client_disconnect ();
  vl_client_api_unmap ();
  return rv;
}

vapi_error_e
vapi_disconnect (vapi_ctx_t ctx)
{
  if (!ctx->connected)
    {
      return VAPI_EINVAL;
    }
  vl_client_disconnect ();
  vl_client_api_unmap ();
#if VAPI_DEBUG_ALLOC
  vapi_to_be_freed_validate ();
#endif
  ctx->connected = false;
  return VAPI_OK;
}

vapi_error_e
vapi_get_fd (vapi_ctx_t ctx, int *fd)
{
  return VAPI_ENOTSUP;
}

vapi_error_e
vapi_send (vapi_ctx_t ctx, void *msg)
{
  vapi_error_e rv = VAPI_OK;
  if (!ctx || !msg || !ctx->connected)
    {
      rv = VAPI_EINVAL;
      goto out;
    }
  int tmp;
  svm_queue_t *q = vlibapi_get_main ()->shmem_hdr->vl_input_queue;
#if VAPI_DEBUG
  unsigned msgid = be16toh (*(u16 *) msg);
  if (msgid <= ctx->vl_msg_id_max)
    {
      vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid];
      if (id < __vapi_metadata.count)
	{
	  VAPI_DBG ("send msg@%p:%u[%s]", msg, msgid,
		    __vapi_metadata.msgs[id]->name);
	}
      else
	{
	  VAPI_DBG ("send msg@%p:%u[UNKNOWN]", msg, msgid);
	}
    }
  else
    {
      VAPI_DBG ("send msg@%p:%u[UNKNOWN]", msg, msgid);
    }
#endif
  tmp = svm_queue_add (q, (u8 *) & msg,
		       VAPI_MODE_BLOCKING == ctx->mode ? 0 : 1);
  if (tmp < 0)
    {
      rv = VAPI_EAGAIN;
    }
  else
    VL_MSG_API_POISON (msg);
out:
  VAPI_DBG ("vapi_send() rv = %d", rv);
  return rv;
}

vapi_error_e
vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2)
{
  vapi_error_e rv = VAPI_OK;
  if (!ctx || !msg1 || !msg2 || !ctx->connected)
    {
      rv = VAPI_EINVAL;
      goto out;
    }
  svm_queue_t *q = vlibapi_get_main ()->shmem_hdr->vl_input_queue;
#if VAPI_DEBUG
  unsigned msgid1 = be16toh (*(u16 *) msg1);
  unsigned msgid2 = be16toh (*(u16 *) msg2);
  const char *name1 = "UNKNOWN";
  const char *name2 = "UNKNOWN";
  if (msgid1 <= ctx->vl_msg_id_max)
    {
      vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid1];
      if (id < __vapi_metadata.count)
	{
	  name1 = __vapi_metadata.msgs[id]->name;
	}
    }
  if (msgid2 <= ctx->vl_msg_id_max)
    {
      vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid2];
      if (id < __vapi_metadata.count)
	{
	  name2 = __vapi_metadata.msgs[id]->name;
	}
    }
  VAPI_DBG ("send two: %u[%s], %u[%s]", msgid1, name1, msgid2, name2);
#endif
  int tmp = svm_queue_add2 (q, (u8 *) & msg1, (u8 *) & msg2,
			    VAPI_MODE_BLOCKING == ctx->mode ? 0 : 1);
  if (tmp < 0)
    {
      rv = VAPI_EAGAIN;
    }
  else
    VL_MSG_API_POISON (msg1);
out:
  VAPI_DBG ("vapi_send() rv = %d", rv);
  return rv;
}

vapi_error_e
vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size,
	   svm_q_conditional_wait_t cond, u32 time)
{
  if (!ctx || !ctx->connected || !msg || !msg_size)
    {
      return VAPI_EINVAL;
    }
  vapi_error_e rv = VAPI_OK;
  api_main_t *am = vlibapi_get_main ();
  uword data;

  if (am->our_pid == 0)
    {
      return VAPI_EINVAL;
    }

  svm_queue_t *q = am->vl_input_queue;
again:
  VAPI_DBG ("doing shm queue sub");

  int tmp = svm_queue_sub (q, (u8 *) & data, cond, time);

  if (tmp == 0)
    {
      VL_MSG_API_UNPOISON ((void *) data);
#if VAPI_DEBUG_ALLOC
      vapi_add_to_be_freed ((void *) data);
#endif
      msgbuf_t *msgbuf =
	(msgbuf_t *) ((u8 *) data - offsetof (msgbuf_t, data));
      if (!msgbuf->data_len)
	{
	  vapi_msg_free (