#include <vlib/vlib.h>
#include <vlib/unix/unix.h>
-#include <vppinfra/timer.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include <arpa/telnet.h>
#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <limits.h>
+#include <netinet/tcp.h>
+#include <math.h>
/** ANSI escape code. */
#define ESC "\x1b"
#define ANSI_RESTCURSOR CSI "u"
/** Maximum depth into a byte stream from which to compile a Telnet
- * protocol message. This is a saftey measure. */
+ * protocol message. This is a safety measure. */
#define UNIX_CLI_MAX_DEPTH_TELNET 24
-/** Unix standard in */
-#define UNIX_CLI_STDIN_FD 0
-
+/** 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
typedef struct
{
/** The file index held by unix.c */
- u32 unix_file_index;
+ u32 clib_file_index;
/** Vector of output pending write to file descriptor. */
u8 *output_vector;
/** 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;
/** 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.
UNIX_CLI_PARSE_ACTION_WORDRIGHT, /**< Jump cursor to start of right word */
UNIX_CLI_PARSE_ACTION_ERASELINELEFT, /**< Erase line to left of cursor */
UNIX_CLI_PARSE_ACTION_ERASELINERIGHT, /**< Erase line to right & including cursor */
+ UNIX_CLI_PARSE_ACTION_ERASEWORDLEFT, /**< Erase word left */
UNIX_CLI_PARSE_ACTION_CLEAR, /**< Clear the terminal */
UNIX_CLI_PARSE_ACTION_REVSEARCH, /**< Search backwards in command history */
UNIX_CLI_PARSE_ACTION_FWDSEARCH, /**< Search forwards in command history */
_(CTL ('D'), UNIX_CLI_PARSE_ACTION_ERASERIGHT),
_(CTL ('U'), UNIX_CLI_PARSE_ACTION_ERASELINELEFT),
_(CTL ('K'), UNIX_CLI_PARSE_ACTION_ERASELINERIGHT),
+ _(CTL ('W'), UNIX_CLI_PARSE_ACTION_ERASEWORDLEFT),
_(CTL ('Y'), UNIX_CLI_PARSE_ACTION_YANK),
_(CTL ('L'), UNIX_CLI_PARSE_ACTION_CLEAR),
_(ESC "b", UNIX_CLI_PARSE_ACTION_WORDLEFT), /* Alt-B */
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
{
/** 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 */
* are available to be sent.
*/
static void
-unix_cli_add_pending_output (unix_file_t * uf,
+unix_cli_add_pending_output (clib_file_t * uf,
unix_cli_file_t * cf,
u8 * buffer, uword buffer_bytes)
{
- unix_main_t *um = &unix_main;
+ 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)
- um->file_update (uf, UNIX_FILE_UPDATE_MODIFY);
+ fm->file_update (uf, UNIX_FILE_UPDATE_MODIFY);
}
}
* that no more bytes are available to be sent.
*/
static void
-unix_cli_del_pending_output (unix_file_t * uf,
+unix_cli_del_pending_output (clib_file_t * uf,
unix_cli_file_t * cf, uword n_bytes)
{
- unix_main_t *um = &unix_main;
+ 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)
- um->file_update (uf, UNIX_FILE_UPDATE_MODIFY);
+ fm->file_update (uf, UNIX_FILE_UPDATE_MODIFY);
}
}
* @param str The buffer in which to search for the value.
* @param len The depth into the buffer to search.
*
- * @return The index of the first occurence of \c chr. If \c chr is not
+ * @return The index of the first occurrence of \c chr. If \c chr is not
* found then \c len instead.
*/
always_inline word
*/
static void
unix_vlib_cli_output_raw (unix_cli_file_t * cf,
- unix_file_t * uf, u8 * buffer, uword buffer_bytes)
+ 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)
- n = write (uf->file_descriptor, buffer, buffer_bytes);
+ {
+ 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)
{
- clib_unix_warning ("write");
+ 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)
{
*/
static void
unix_vlib_cli_output_cooked (unix_cli_file_t * cf,
- unix_file_t * uf,
+ clib_file_t * uf,
u8 * buffer, uword buffer_bytes)
{
word end = 0, start = 0;
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, unix_file_t * uf)
+unix_cli_cli_prompt (unix_cli_file_t * cf, clib_file_t * uf)
{
unix_cli_main_t *cm = &unix_cli_main;
- unix_vlib_cli_output_raw (cf, uf, cm->cli_prompt, vec_len (cm->cli_prompt));
+ 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, unix_file_t * uf)
+unix_cli_pager_prompt (unix_cli_file_t * cf, clib_file_t * uf)
{
u8 *prompt;
u32 h;
/** @brief Output a pager "skipping" message */
static void
-unix_cli_pager_message (unix_cli_file_t * cf, unix_file_t * uf,
+unix_cli_pager_message (unix_cli_file_t * cf, clib_file_t * uf,
char *message, char *postfix)
{
u8 *prompt;
/** @brief Erase the printed pager prompt */
static void
-unix_cli_pager_prompt_erase (unix_cli_file_t * cf, unix_file_t * uf)
+unix_cli_pager_prompt_erase (unix_cli_file_t * cf, clib_file_t * uf)
{
if (cf->ansi_capable)
{
/** @brief Uses an ANSI escape sequence to move the cursor */
static void
-unix_cli_ansi_cursor (unix_cli_file_t * cf, unix_file_t * uf, u16 x, u16 y)
+unix_cli_ansi_cursor (unix_cli_file_t * cf, clib_file_t * uf, u16 x, u16 y)
{
u8 *str;
* @param uf Unix file of the CLI session.
*/
static void
-unix_cli_pager_redraw (unix_cli_file_t * cf, unix_file_t * uf)
+unix_cli_pager_redraw (unix_cli_file_t * cf, clib_file_t * uf)
{
unix_cli_pager_index_t *pi = NULL;
u8 *line = NULL;
*
* If instead @c line is @c NULL then @c len_or_index is taken to mean the
* index of an existing line in the pager buffer; this simply means that the
- * input line does not need to be cloned since we alreayd have it. This is
+ * input line does not need to be cloned since we already have it. This is
* typical if we are reindexing the pager buffer.
*
* @param cf The CLI session whose pager we are adding to.
static void
unix_cli_pager_add_line (unix_cli_file_t * cf, u8 * line, word len_or_index)
{
- u8 *p;
+ u8 *p = NULL;
word i, j, k;
word line_index, len;
u32 width = cf->width;
{
/* Use a line already in the pager buffer */
line_index = len_or_index;
- p = cf->pager_vector[line_index];
+ if (cf->pager_vector != NULL)
+ p = cf->pager_vector[line_index];
len = vec_len (p);
}
else
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;
- unix_file_t *uf;
+ clib_file_t *uf;
cf = pool_elt_at_index (cm->cli_file_pool, cli_file_index);
- uf = pool_elt_at_index (um->file_pool, cf->unix_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)
{
* terminal sequences; @c 0 otherwise.
*/
static u8
-unix_cli_terminal_type (u8 * term, uword len)
+unix_cli_terminal_type_ansi (u8 * term, uword len)
{
/* This may later be better done as a hash of some sort. */
#define _(a) do { \
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;
- unix_file_t *uf = pool_elt_at_index (um->file_pool, cf->unix_file_index);
+ 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
/* Prompt. */
unix_cli_cli_prompt (cf, uf);
- cf->started = 1;
}
-/** @brief A failsafe triggered on a timer to ensure we send the prompt
- * to telnet sessions that fail to negotiate the terminal type. */
-static void
-unix_cli_file_welcome_timer (any arg, f64 delay)
+/**
+ * @brief A failsafe manager that ensures CLI sessions issue an initial
+ * prompt if TELNET negotiation fails.
+ */
+static uword
+unix_cli_new_session_process (vlib_main_t * vm, vlib_node_runtime_t * rt,
+ vlib_frame_t * f)
{
unix_cli_main_t *cm = &unix_cli_main;
- unix_cli_file_t *cf;
- (void) delay;
+ uword event_type, *event_data = 0;
+ f64 wait = 10.0;
- /* Check the connection didn't close already */
- if (pool_is_free_index (cm->cli_file_pool, (uword) arg))
- return;
+ while (1)
+ {
+ if (vec_len (cm->new_sessions) > 0)
+ wait = vlib_process_wait_for_event_or_clock (vm, wait);
+ else
+ vlib_process_wait_for_event (vm);
- cf = pool_elt_at_index (cm->cli_file_pool, (uword) arg);
+ event_type = vlib_process_get_events (vm, &event_data);
- if (!cf->started)
- unix_cli_file_welcome (cm, cf);
+ 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.
static i32
unix_cli_process_telnet (unix_main_t * um,
unix_cli_file_t * cf,
- unix_file_t * uf, u8 * input_vector, uword len)
+ 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.
case DO:
case DONT:
/* Expect 3 bytes */
- if (vec_len (input_vector) < 3)
+ if (len < 3)
return -1; /* want more bytes */
consume = 2;
case TELOPT_TTYPE:
if (input_vector[3] != 0)
break;
- /* See if the terminal type is ANSI capable */
- cf->ansi_capable =
- unix_cli_terminal_type (input_vector + 4, i - 5);
+ {
+ /* 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 */
/* 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_line_process_one (unix_cli_main_t * cm,
unix_main_t * um,
unix_cli_file_t * cf,
- unix_file_t * uf,
+ clib_file_t * uf,
u8 input, unix_cli_parse_action_t action)
{
u8 *prev;
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);
+ 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 == UNIX_CLI_PARSE_ACTION_REVSEARCH)
cf->search_mode = -1;
else
cf->search_mode = 1;
- cf->cursor = 0;
}
else
{
case UNIX_CLI_PARSE_ACTION_ERASELINELEFT:
/* Erase the command from the cursor to the start */
- /* Shimmy forwards to the new end of line position */
+ j = cf->cursor;
+ /* Shimmy backwards 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);
+ for (; cf->cursor > delta; cf->cursor--)
+ unix_vlib_cli_output_cursor_left (cf, uf);
/* Zap from here to the end of what is currently displayed */
- for (; j < (vec_len (cf->current_command)); j++)
+ for (; cf->cursor < vec_len (cf->current_command); cf->cursor++)
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);
+ for (; cf->cursor > 0; cf->cursor--)
+ unix_vlib_cli_output_cursor_left (cf, uf);
- j = vec_len (cf->current_command) - cf->cursor;
- memmove (cf->current_command, cf->current_command + cf->cursor, j);
- _vec_len (cf->current_command) = j;
+ /* Delete the desired text from the command */
+ memmove (cf->current_command, cf->current_command + j, delta);
+ _vec_len (cf->current_command) = delta;
/* Print the new contents */
- unix_vlib_cli_output_cooked (cf, uf, cf->current_command, j);
+ unix_vlib_cli_output_cooked (cf, uf, cf->current_command, delta);
+ cf->cursor = delta; /* for backspace tracking */
+
/* 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;
+ for (; cf->cursor > 0; cf->cursor--)
+ unix_vlib_cli_output_cursor_left (cf, uf);
cf->search_mode = 0;
break;
case UNIX_CLI_PARSE_ACTION_ERASELINERIGHT:
/* Erase the command from the cursor to the end */
+ j = cf->cursor;
/* Zap from cursor to end of what is currently displayed */
- for (j = cf->cursor; j < (vec_len (cf->current_command)); j++)
+ for (; cf->cursor < (vec_len (cf->current_command)); cf->cursor++)
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);
+ for (; cf->cursor > j; cf->cursor--)
+ unix_vlib_cli_output_cursor_left (cf, uf);
/* Truncate the line at the cursor */
_vec_len (cf->current_command) = cf->cursor;
cf->search_mode = 0;
break;
+ case UNIX_CLI_PARSE_ACTION_ERASEWORDLEFT:
+ /* calculate num of caracter to be erased */
+ delta = 0;
+ while (cf->cursor > delta
+ && cf->current_command[cf->cursor - delta - 1] == ' ')
+ delta++;
+ while (cf->cursor > delta
+ && cf->current_command[cf->cursor - delta - 1] != ' ')
+ delta++;
+
+ if (vec_len (cf->current_command))
+ {
+ if (cf->cursor > 0)
+ {
+ /* move cursor left delta times */
+ for (j = delta; j > 0; j--, cf->cursor--)
+ unix_vlib_cli_output_cursor_left (cf, uf);
+ save = cf->current_command + cf->cursor;
+
+ /* redraw remainder of line */
+ memmove (cf->current_command + cf->cursor,
+ cf->current_command + cf->cursor + delta,
+ _vec_len (cf->current_command) - cf->cursor - delta);
+ unix_vlib_cli_output_cooked (cf, uf,
+ cf->current_command + cf->cursor,
+ _vec_len (cf->current_command) -
+ cf->cursor);
+ cf->cursor += _vec_len (cf->current_command) - cf->cursor;
+
+ /* print delta amount of blank spaces,
+ * then finally fix the cursor position */
+ for (j = delta; j > 0; j--, cf->cursor--)
+ unix_vlib_cli_output_cursor_left (cf, uf);
+ for (j = delta; j > 0; j--, cf->cursor++)
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1);
+ for (; (cf->current_command + cf->cursor) > save; cf->cursor--)
+ unix_vlib_cli_output_cursor_left (cf, uf);
+ _vec_len (cf->current_command) -= delta;
+ }
+ }
+ cf->search_mode = 0;
+ cf->excursion = 0;
+ vec_reset_length (cf->search_key);
+ break;
+
case UNIX_CLI_PARSE_ACTION_LEFT:
if (cf->cursor > 0)
{
- unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
+ unix_vlib_cli_output_cursor_left (cf, uf);
cf->cursor--;
}
break;
cf->search_mode = 0;
/* Erase the command */
- for (j = cf->cursor; j < (vec_len (cf->current_command)); j++)
+ for (; cf->cursor < vec_len (cf->current_command); cf->cursor++)
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);
+ 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->current_command);
if (vec_len (cf->command_history))
{
unix_vlib_cli_output_cooked (cf, uf, cf->current_command,
vec_len (cf->current_command));
}
- cf->cursor = vec_len (cf->current_command);
-
- break;
}
+ 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--;
- }
+ for (; cf->cursor > 0; cf->cursor--)
+ unix_vlib_cli_output_cursor_left (cf, uf);
}
cf->search_mode = 0;
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--;
+ unix_vlib_cli_output_cursor_left (cf, uf);
+ cf->cursor--;
- while (j && isspace (cf->current_command[j]))
+ while (cf->cursor && isspace (cf->current_command[cf->cursor]))
{
- unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
- j--;
+ unix_vlib_cli_output_cursor_left (cf, uf);
+ cf->cursor--;
}
- while (j && !isspace (cf->current_command[j]))
+ while (cf->cursor && !isspace (cf->current_command[cf->cursor]))
{
- if (isspace (cf->current_command[j - 1]))
+ if (isspace (cf->current_command[cf->cursor - 1]))
break;
- unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
- j--;
+ unix_vlib_cli_output_cursor_left (cf, uf);
+ cf->cursor--;
}
- cf->cursor = j;
}
cf->search_mode = 0;
{
if (cf->cursor == vec_len (cf->current_command))
{
- unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b \b", 3);
- _vec_len (cf->current_command)--;
+ unix_vlib_cli_output_cursor_left (cf, uf);
+ cf->cursor--;
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1);
+ cf->cursor++;
+ unix_vlib_cli_output_cursor_left (cf, uf);
cf->cursor--;
+ _vec_len (cf->current_command)--;
}
else if (cf->cursor > 0)
{
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_cursor_left (cf, uf);
+ cf->cursor--;
unix_vlib_cli_output_cooked (cf, uf,
cf->current_command + cf->cursor,
j);
- unix_vlib_cli_output_cooked (cf, uf, (u8 *) " \b\b", 3);
+ cf->cursor += j;
+ /* erase last char */
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1);
+ cf->cursor++;
+
/* and shift the terminal cursor back where it should be */
+ j += 2; /* account for old string length and offset position */
while (--j)
- unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
+ {
+ unix_vlib_cli_output_cursor_left (cf, uf);
+ cf->cursor--;
+ }
}
}
cf->search_mode = 0;
unix_vlib_cli_output_cooked (cf, uf,
cf->current_command + cf->cursor,
j);
- unix_vlib_cli_output_cooked (cf, uf, (u8 *) " \b", 2);
+ cf->cursor += j;
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1);
+ cf->cursor++;
+ unix_vlib_cli_output_cursor_left (cf, uf);
+ cf->cursor--;
/* and shift the terminal cursor back where it should be */
if (j)
{
- unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
+ unix_vlib_cli_output_cursor_left (cf, uf);
+ cf->cursor--;
while (--j)
- unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
+ {
+ unix_vlib_cli_output_cursor_left (cf, uf);
+ cf->cursor--;
+ }
}
}
}
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);
+ unix_vlib_cli_output_cooked (cf, uf,
+ cf->current_command,
+ vec_len (cf->current_command));
+ j = cf->cursor;
+ cf->cursor = vec_len (cf->current_command);
+ for (; cf->cursor > j; cf->cursor--)
+ unix_vlib_cli_output_cursor_left (cf, uf);
break;
vlib_cli_get_possible_completions (cf->current_command);
if (vec_len (possible_commands) == 1)
{
- u32 j = cf->cursor;
u8 *completed = possible_commands[0];
+ j = cf->cursor;
/* find the last word of current_command */
while (j >= 1 && !isspace (cf->current_command[j - 1]))
{
+ unix_vlib_cli_output_cursor_left (cf, uf);
+ cf->cursor--;
j--;
- unix_vlib_cli_output_raw (cf, uf, (u8 *) "\b", 1);
}
_vec_len (cf->current_command) = j;
vec_append (cf->current_command, completed);
/* echo to the terminal */
- unix_vlib_cli_output_raw (cf, uf, completed, vec_len (completed));
+ unix_vlib_cli_output_cooked (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);
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1);
}
cf->cursor = vec_len (cf->current_command);
{
u8 **possible_command;
uword max_command_len = 0, min_command_len = ~0;
- u32 i, j;
+ u32 i;
vec_foreach (possible_command, possible_commands)
{
if (vec_len (*possible_command) > max_command_len)
- {
- max_command_len = vec_len (*possible_command);
- }
+ max_command_len = vec_len (*possible_command);
if (vec_len (*possible_command) < min_command_len)
- {
- min_command_len = vec_len (*possible_command);
- }
+ min_command_len = vec_len (*possible_command);
}
unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1);
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));
+ unix_vlib_cli_output_cooked (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);
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1);
}
i += max_command_len + 2;
}
/* rewrite prompt */
unix_cli_cli_prompt (cf, uf);
- unix_vlib_cli_output_raw (cf, uf, cf->current_command,
- vec_len (cf->current_command));
+ unix_vlib_cli_output_cooked (cf, uf, cf->current_command,
+ vec_len (cf->current_command));
/* count length of last word */
j = cf->cursor;
{
u8 common = '\0';
int stop = 0;
+
vec_foreach (possible_command, possible_commands)
{
if (common == '\0')
break;
}
}
+
if (!stop)
{
vec_add1 (cf->current_command, common);
cf->cursor++;
- unix_vlib_cli_output_raw (cf, uf, (u8 *) & common, 1);
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) & common, 1);
}
else
{
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);
- }
+ unix_vlib_cli_output_cooked (cf, uf, save, vec_len (save));
+ cf->cursor += vec_len (save);
+ for (j = 0; j < vec_len (save); j++, cf->cursor--)
+ unix_vlib_cli_output_cursor_left (cf, uf);
vec_append (cf->current_command, save);
vec_free (save);
}
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))
+ if (cf->height + cf->pager_start <= vec_len (cf->pager_index))
{
u8 *line = NULL;
unix_cli_pager_index_t *pi = NULL;
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))
+ if (cf->height + cf->pager_start <= vec_len (cf->pager_index))
{
u8 *line;
unix_cli_pager_index_t *pi;
{
/* no-op for now */
}
- else if (cf->has_history && cf->search_mode && isprint (input))
+ else if (cf->has_history && cf->search_mode != 0 && isprint (input))
{
int k, limit, offset;
u8 *item;
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);
+ 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_validate (cf->current_command, vec_len (item) - 1);
clib_memcpy (cf->current_command, item, vec_len (item));
cf->cursor++;
/* Echo the character back to the client */
- unix_vlib_cli_output_raw (cf, uf, &input, 1);
+ unix_vlib_cli_output_cooked (cf, uf, &input, 1);
}
else
{
cf->current_command[cf->cursor] = input;
/* Redraw the line */
j++;
- unix_vlib_cli_output_raw (cf, uf,
- cf->current_command + cf->cursor, j);
+ unix_vlib_cli_output_cooked (cf, uf,
+ cf->current_command + cf->cursor,
+ j);
+ cf->cursor += j;
+ j--;
/* Put terminal cursor back */
- while (--j)
- unix_vlib_cli_output_raw (cf, uf, (u8 *) "\b", 1);
- cf->cursor++;
+ for (; j > 0; j--, cf->cursor--)
+ unix_vlib_cli_output_cursor_left (cf, uf);
}
}
else
/** @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, unix_cli_file_t * cf)
+unix_cli_line_edit (unix_cli_main_t * cm, unix_main_t * um,
+ clib_file_main_t * fm, unix_cli_file_t * cf)
{
- unix_file_t *uf = pool_elt_at_index (um->file_pool, cf->unix_file_index);
+ 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++)
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)
{
- /* 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.
*/
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))
unix_cli_process_input (unix_cli_main_t * cm, uword cli_file_index)
{
unix_main_t *um = &unix_main;
- unix_file_t *uf;
+ 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 */
else
{
/* Line edit, echo, etc. */
- if (unix_cli_line_edit (cm, um, cf))
+ if (unix_cli_line_edit (cm, um, fm, cf))
/* want more input */
return;
}
lv = format (lv, "%U[%d]: %v",
format_timeval, 0 /* current bat-time */ ,
0 /* current bat-format */ ,
- cli_file_index, cf->input_vector);
- {
- int rv __attribute__ ((unused)) =
- write (um->log_fd, lv, vec_len (lv));
- }
+ cli_file_index, cf->current_command);
+ int rv __attribute__ ((unused)) = write (um->log_fd, lv, vec_len (lv));
}
- /* Copy our input command to a new string */
+ /* Build an unformat structure around our command */
unformat_init_vector (&input, cf->current_command);
/* Remove leading white space from input. */
(void) unformat (&input, "");
- cm->current_input_file_index = cli_file_index;
cf->pager_start = 0; /* start a new pager session */
if (unformat_check_input (&input) != UNFORMAT_END_OF_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 (um->file_pool, cf->unix_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);
+ {
+ vec_reset_length (cf->input_vector);
+ cf->current_command = 0;
+ }
else
- vec_reset_length (cf->current_command);
+ {
+ vec_reset_length (cf->current_command);
+ }
if (cf->no_pager == 2)
{
/* 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.
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;
- unix_file_t *uf;
+ clib_file_t *uf;
int i;
+ /* Validate cli_file_index */
+ if (pool_is_free_index (cm->cli_file_pool, cli_file_index))
+ return;
+
cf = pool_elt_at_index (cm->cli_file_pool, cli_file_index);
- uf = pool_elt_at_index (um->file_pool, cf->unix_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 == UNIX_CLI_STDIN_FD)
+ 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->command_history);
- unix_file_del (um, uf);
+ clib_file_del (fm, uf);
unix_cli_file_free (cf);
pool_put (cm->cli_file_pool, cf);
/** Called when a CLI session file descriptor can be written to without
* blocking. */
static clib_error_t *
-unix_cli_write_ready (unix_file_t * uf)
+unix_cli_write_ready (clib_file_t * uf)
{
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);
/* Flush output vector. */
- n = write (uf->file_descriptor,
- cf->output_vector, vec_len (cf->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)
- return clib_error_return_unix (0, "write");
+ {
+ 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);
/** Called when a CLI session file descriptor has data to be read. */
static clib_error_t *
-unix_cli_read_ready (unix_file_t * uf)
+unix_cli_read_ready (clib_file_t * uf)
{
unix_main_t *um = &unix_main;
unix_cli_main_t *cm = &unix_cli_main;
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.
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;
- unix_file_t template = { 0 };
+ clib_file_t template = { 0 };
vlib_main_t *vm = um->vlib_main;
- vlib_node_t *n;
+ vlib_node_t *n = 0;
+ u8 *file_desc = 0;
+
+ file_desc = format (0, "%s", name);
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);
+ int i;
+ vlib_main_t *this_vlib_main;
+ u8 *old_name = 0;
- /* 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;
+ /*
+ * Nodes are bulk-copied, so node name pointers are shared.
+ * Find the cli node in all graph replicas, and give all of them
+ * the same new name.
+ * Then, throw away the old shared name-vector.
+ */
+ for (i = 0; i < vec_len (vlib_mains); i++)
+ {
+ this_vlib_main = vlib_mains[i];
+ if (this_vlib_main == 0)
+ continue;
+ n = vlib_get_node (this_vlib_main,
+ cm->unused_cli_process_node_indices[l - 1]);
+ old_name = n->name;
+ n->name = (u8 *) name;
+ }
+ vec_free (old_name);
vlib_node_set_state (vm, n->index, VLIB_NODE_STATE_POLLING);
static vlib_node_registration_t r = {
.function = unix_cli_process,
.type = VLIB_NODE_TYPE_PROCESS,
- .process_log2_n_stack_bytes = 16,
+ .process_log2_n_stack_bytes = 18,
};
r.name = name;
+
+ vlib_worker_thread_barrier_sync (vm);
+
vlib_register_node (vm, &r);
vec_free (name);
n = vlib_get_node (vm, r.index);
+ vlib_worker_thread_node_runtime_update ();
+ vlib_worker_thread_barrier_release (vm);
}
pool_get (cm->cli_file_pool, cf);
- memset (cf, 0, sizeof (*cf));
+ clib_memset (cf, 0, sizeof (*cf));
template.read_function = unix_cli_read_ready;
template.write_function = unix_cli_write_ready;
+ template.error_function = unix_cli_error_detected;
template.file_descriptor = fd;
template.private_data = cf - cm->cli_file_pool;
+ template.description = file_desc;
cf->process_node_index = n->index;
- cf->unix_file_index = unix_file_add (um, &template);
+ cf->clib_file_index = clib_file_add (fm, &template);
cf->output_vector = 0;
cf->input_vector = 0;
/** Telnet listening socket has a new connection. */
static clib_error_t *
-unix_cli_listen_read_ready (unix_file_t * uf)
+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;
clib_error_t *error;
unix_cli_file_t *cf;
u32 cf_index;
+ int one;
error = clib_socket_accept (s, &client);
if (error)
return error;
+ /* Disable Nagle, ignore any errors doing so eg on PF_LOCAL socket */
+ one = 1;
+ (void) setsockopt (client.fd, IPPROTO_TCP, TCP_NODELAY,
+ (void *) &one, sizeof (one));
+
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) */
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;
/* Setup the pager */
cf->no_pager = um->cli_no_pager;
- uf = pool_elt_at_index (um->file_pool, cf->unix_file_index);
+ /* Default terminal dimensions, should the terminal
+ * fail to provide any.
+ */
+ cf->width = UNIX_CLI_DEFAULT_TERMINAL_WIDTH;
+ cf->height = UNIX_CLI_DEFAULT_TERMINAL_HEIGHT;
/* 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);
+ if (cm->new_session_process_node_index == ~0)
+ {
+ /* Create thw new session deadline process */
+ cm->new_session_process_node_index =
+ vlib_process_create (um->vlib_main, "unix-cli-new-session",
+ unix_cli_new_session_process,
+ 16 /* log2_n_stack_bytes */ );
+ }
+
+ /* In case the client doesn't negotiate terminal type, register
+ * our session with a process that will emit the prompt if
+ * a deadline passes */
+ vlib_process_signal_event (um->vlib_main,
+ cm->new_session_process_node_index,
+ UNIX_CLI_NEW_SESSION_EVENT_ADD, cf_index);
+
}
return error;
static void
unix_cli_resize_interrupt (int signum)
{
- 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 = pool_elt_at_index (cm->cli_file_pool,
cm->stdin_cli_file_index);
- unix_file_t *uf = pool_elt_at_index (um->file_pool, cf->unix_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 (UNIX_CLI_STDIN_FD, TIOCGWINSZ, &ws) < 0)
+ 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 == 0)
+ cf->width = UNIX_CLI_DEFAULT_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 == 0)
+ cf->height = UNIX_CLI_DEFAULT_TERMINAL_HEIGHT;
/* Reindex the pager buffer */
unix_cli_pager_reindex (cf);
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;
if (um->flags & UNIX_FLAG_INTERACTIVE)
{
/* Set stdin to be non-blocking. */
- if ((flags = fcntl (UNIX_CLI_STDIN_FD, F_GETFL, 0)) < 0)
+ if ((flags = fcntl (STDIN_FILENO, F_GETFL, 0)) < 0)
flags = 0;
- (void) fcntl (UNIX_CLI_STDIN_FD, F_SETFL, flags | O_NONBLOCK);
+ (void) fcntl (STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
- cf_index = unix_cli_file_add (cm, "stdin", UNIX_CLI_STDIN_FD);
+ 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 (UNIX_CLI_STDIN_FD) && um->cli_line_mode == 0)
+ if (isatty (STDIN_FILENO) && um->cli_line_mode == 0)
{
/* Capture terminal resize events */
- memset (&sa, 0, sizeof (sa));
+ clib_memset (&sa, 0, sizeof (sa));
sa.sa_handler = unix_cli_resize_interrupt;
if (sigaction (SIGWINCH, &sa, 0) < 0)
clib_panic ("sigaction");
/* Retrieve the current terminal size */
- ioctl (UNIX_CLI_STDIN_FD, TIOCGWINSZ, &ws);
- cf->width = ws.ws_col;
- cf->height = ws.ws_row;
+ if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) == 0)
+ {
+ cf->width = ws.ws_col;
+ cf->height = ws.ws_row;
+ }
if (cf->width == 0 || cf->height == 0)
- /* We have a tty, but no size. Stick to line mode. */
- goto notty;
+ {
+ /*
+ * We have a tty, but no size. Use defaults.
+ * vpp "unix interactive" inside emacs + gdb ends up here.
+ */
+ cf->width = UNIX_CLI_DEFAULT_TERMINAL_WIDTH;
+ cf->height = UNIX_CLI_DEFAULT_TERMINAL_HEIGHT;
+ }
/* Setup the history */
cf->history_limit = um->cli_history_limit;
/* 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 (UNIX_CLI_STDIN_FD, &um->tio_stdin);
+ 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);
+ /* disable XON/XOFF, so ^S invokes the history search */
+ tio.c_iflag &= ~(IXON | IXOFF);
tio.c_cc[VMIN] = 1; /* 1 byte at a time */
tio.c_cc[VTIME] = 0; /* no timer */
- tcsetattr (UNIX_CLI_STDIN_FD, TCSAFLUSH, &tio);
+ tio.c_cc[VSTOP] = _POSIX_VDISABLE; /* not ^S */
+ tio.c_cc[VSTART] = _POSIX_VDISABLE; /* not ^Q */
+ tcsetattr (STDIN_FILENO, TCSAFLUSH, &tio);
/* See if we can do ANSI/VT100 output */
term = (u8 *) getenv ("TERM");
if (term != NULL)
- cf->ansi_capable = unix_cli_terminal_type (term,
- strlen ((char *)
- term));
+ {
+ 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
{
- notty:
- /* No tty, so make sure these things are off */
- cf->no_pager = 1;
- cf->history_limit = 0;
- cf->has_history = 0;
- cf->line_mode = 1;
+ /* No tty, so make sure the session doesn't have tty-like features */
+ unix_cli_set_session_noninteractive (cf);
}
/* Send banner and initial prompt */
if (s->config && s->config[0] != 0)
{
/* CLI listen. */
- unix_file_t template = { 0 };
+ 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 = SOCKET_IS_SERVER; /* listen, don't connect */
+ 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)
template.read_function = unix_cli_listen_read_ready;
template.file_descriptor = s->fd;
+ template.description = format (0, "cli listener %s", s->config);
- unix_file_add (um, &template);
+ clib_file_add (fm, &template);
}
/* Set CLI prompt. */
unix_main_t *um = &unix_main;
/* If stdin is a tty and we saved the tty state, reset the tty state */
- if (isatty (UNIX_CLI_STDIN_FD) && um->tio_isset)
- tcsetattr (UNIX_CLI_STDIN_FD, TCSAFLUSH, &um->tio_stdin);
+ if (isatty (STDIN_FILENO) && um->tio_isset)
+ tcsetattr (STDIN_FILENO, TCSAFLUSH, &um->tio_stdin);
return 0;
}
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),
};
/* *INDENT-ON* */
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (unix_cli_q_command, static) = {
+ .path = "q",
+ .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_init_unix_file (&sub_input, fd);
+ unformat_init_clib_file (&sub_input, fd);
vlib_cli_input (vm, &sub_input, 0, 0);
unformat_free (&sub_input);
done:
- if (fd > 0)
+ if (fd >= 0)
close (fd);
vec_free (file_name);
}
/*?
- * Executes a sequence of CLI commands which are read from a file.
- *
- * If a command is unrecognised or otherwise invalid then the usual CLI
+ * 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.
+ *
+ * The VPP code is indifferent to the file location. However, if SELinux
+ * is enabled, then the file needs to have an SELinux label the VPP
+ * process is allowed to access. For example, if a file is created in
+ * '<em>/usr/share/vpp/</em>', it will be allowed. However, files manually
+ * created in '/tmp/' or '/home/<user>/' will not be accessible by the VPP
+ * process when SELinux is enabled.
+ *
+ * @cliexpar
+ * Sample file:
+ * @clistart
+ * <b><em>$ cat /usr/share/vpp/scripts/gigup.txt</em></b>
+ * set interface state GigabitEthernet0/8/0 up
+ * set interface state GigabitEthernet0/9/0 up
+ * @cliend
+ * Example of how to execute a set of CLI commands from a file:
+ * @cliexcmd{exec /usr/share/vpp/scripts/gigup.txt}
?*/
/* *INDENT-OFF* */
VLIB_CLI_COMMAND (cli_exec, static) = {
.path = "exec",
- .short_help = "Execute commands from file",
+ .short_help = "exec <filename>",
.function = unix_cli_exec,
.is_mp_safe = 1,
};
/* *INDENT-OFF* */
VLIB_CLI_COMMAND (cli_unix_show_errors, static) = {
- .path = "show unix-errors",
+ .path = "show unix errors",
.short_help = "Show Unix system call error history",
.function = unix_show_errors,
};
/* *INDENT-ON* */
+/** CLI command to show various unix error statistics. */
+static clib_error_t *
+unix_show_files (vlib_main_t * vm,
+ unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+ clib_error_t *error = 0;
+ clib_file_main_t *fm = &file_main;
+ clib_file_t *f;
+ char path[PATH_MAX];
+ u8 *s = 0;
+
+ vlib_cli_output (vm, "%3s %6s %12s %12s %12s %-32s %s", "FD", "Thread",
+ "Read", "Write", "Error", "File Name", "Description");
+
+ /* *INDENT-OFF* */
+ pool_foreach (f, fm->file_pool,(
+ {
+ int rv;
+ s = format (s, "/proc/self/fd/%d%c", f->file_descriptor, 0);
+ rv = readlink((char *) s, path, PATH_MAX - 1);
+
+ path[rv < 0 ? 0 : rv] = 0;
+
+ vlib_cli_output (vm, "%3d %6d %12d %12d %12d %-32s %v",
+ f->file_descriptor, f->polling_thread_index,
+ f->read_events, f->write_events, f->error_events,
+ path, f->description);
+ vec_reset_length (s);
+ }));
+ /* *INDENT-ON* */
+ vec_free (s);
+
+ return error;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (cli_unix_show_files, static) = {
+ .path = "show unix files",
+ .short_help = "Show Unix files in use",
+ .function = unix_show_files,
+};
+/* *INDENT-ON* */
+
/** CLI command to show session command history. */
static clib_error_t *
unix_cli_show_history (vlib_main_t * vm,
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);
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 ? "" :
* Terminal width: 123
* Terminal height: 48
* ANSI capable: yes
+ * Interactive: yes
* History enabled: yes
* History limit: 50
* Pager enabled: yes
};
/* *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_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 _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;
- cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index);
-
while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
{
if (unformat (line_input, "on"))
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;
- cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index);
-
while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
{
if (unformat (line_input, "on"))
goto done;
}
- /* If we reduced history size, or turned it off, purge the history */
- limit = cf->has_history ? cf->history_limit : 0;
+ }
- while (cf->command_history && vec_len (cf->command_history) >= limit)
- {
- vec_free (cf->command_history[0]);
- vec_delete (cf->command_history, 1, 0);
- }
+ /* If we reduced history size, or turned it off, purge the history */
+ limit = cf->has_history ? cf->history_limit : 0;
+ if (limit < vec_len (cf->command_history))
+ {
+ u32 i;
+
+ /* How many items to remove from the start of history */
+ limit = vec_len (cf->command_history) - limit;
+
+ for (i = 0; i < limit; i++)
+ vec_free (cf->command_history[i]);
+
+ vec_delete (cf->command_history, limit, 0);
}
done:
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"))
};
/* *INDENT-ON* */
+
+#define MAX_CLI_WAIT 86400
+/** CLI command to wait <sec> seconds. Useful for exec script. */
+static clib_error_t *
+unix_wait_cmd (vlib_main_t * vm,
+ unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+ unformat_input_t _line_input, *line_input = &_line_input;
+ f64 sec = 1.0;
+
+ if (!unformat_user (input, unformat_line_input, line_input))
+ return 0;
+
+ while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (line_input, "%f", &sec))
+ ;
+ else
+ return clib_error_return (0, "unknown parameter: `%U`",
+ format_unformat_error, input);
+ }
+
+ if (sec <= 0 || sec > MAX_CLI_WAIT || floor (sec * 1000) / 1000 != sec)
+ return clib_error_return (0,
+ "<sec> must be a positive value and less than 86400 (one day) with no more than msec precision.");
+
+ vlib_process_wait_for_event_or_clock (vm, sec);
+ vlib_cli_output (vm, "waited %.3f sec.", sec);
+
+ unformat_free (line_input);
+ return 0;
+}
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (cli_unix_wait_cmd, static) = {
+ .path = "wait",
+ .short_help = "wait <sec>",
+ .function = unix_wait_cmd,
+};
+/* *INDENT-ON* */
+
static clib_error_t *
unix_cli_init (vlib_main_t * vm)
{
+ unix_cli_main_t *cm = &unix_cli_main;
+
+ /* Breadcrumb to indicate the new session process
+ * has not been started */
+ cm->new_session_process_node_index = ~0;
+
return 0;
}