Typos. A bunch of typos I've been collecting.
[vpp.git] / src / vlib / unix / cli.c
index 1567cc2..f9d6b40 100644 (file)
@@ -59,6 +59,8 @@
 #include <sys/ioctl.h>
 #include <sys/types.h>
 #include <unistd.h>
+#include <limits.h>
+#include <netinet/tcp.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
@@ -191,6 +198,18 @@ typedef struct
   /** 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;
 
@@ -208,6 +227,20 @@ typedef struct
 
   /** 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.
@@ -548,7 +581,7 @@ unix_cli_del_pending_output (clib_file_t * uf,
  * @param str The buffer in which to search for the value.
  * @param len The depth into the buffer to search.
  *
- * @return The index of the first occurence of \c chr. If \c chr is not
+ * @return The index of the first occurrence of \c chr. If \c chr is not
  *          found then \c len instead.
  */
 always_inline word
@@ -584,12 +617,33 @@ unix_vlib_cli_output_raw (unix_cli_file_t * cf,
 {
   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)
     {
@@ -642,6 +696,69 @@ unix_vlib_cli_output_cooked (unix_cli_file_t * cf,
          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 */
@@ -650,7 +767,9 @@ 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 */
@@ -780,7 +899,7 @@ unix_cli_pager_redraw (unix_cli_file_t * cf, clib_file_t * uf)
  *
  * If instead @c line is @c NULL then @c len_or_index is taken to mean the
  * index of an existing line in the pager buffer; this simply means that the
- * input line does not need to be cloned since we alreayd have it. This is
+ * input line does not need to be cloned since we already have it. This is
  * typical if we are reindexing the pager buffer.
  *
  * @param cf           The CLI session whose pager we are adding to.
@@ -795,7 +914,7 @@ unix_cli_pager_redraw (unix_cli_file_t * cf, clib_file_t * uf)
 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;
@@ -805,7 +924,8 @@ unix_cli_pager_add_line (unix_cli_file_t * cf, u8 * line, word len_or_index)
     {
       /* 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
@@ -1015,7 +1135,7 @@ unix_vlib_cli_output (uword cli_file_index, u8 * buffer, uword buffer_bytes)
  *         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 { \
@@ -1033,6 +1153,45 @@ unix_cli_terminal_type (u8 * term, uword len)
   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)
@@ -1043,6 +1202,12 @@ unix_cli_file_welcome (unix_cli_main_t * cm, unix_cli_file_t * cf)
   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
@@ -1073,7 +1238,6 @@ unix_cli_file_welcome (unix_cli_main_t * cm, unix_cli_file_t * cf)
   /* Prompt. */
   unix_cli_cli_prompt (cf, uf);
 
-  cf->started = 1;
 }
 
 /** @brief A failsafe triggered on a timer to ensure we send the prompt
@@ -1131,7 +1295,7 @@ unix_cli_process_telnet (unix_main_t * um,
     case DO:
     case DONT:
       /* Expect 3 bytes */
-      if (vec_len (input_vector) < 3)
+      if (len < 3)
        return -1;              /* want more bytes */
 
       consume = 2;
@@ -1151,9 +1315,20 @@ unix_cli_process_telnet (unix_main_t * um,
                  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 */
@@ -1164,10 +1339,21 @@ unix_cli_process_telnet (unix_main_t * um,
                    /* 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 */
@@ -1251,16 +1437,20 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
       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
        {
@@ -1277,27 +1467,29 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
     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;
@@ -1305,12 +1497,13 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
     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;
@@ -1321,7 +1514,7 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
     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--;
        }
 
@@ -1346,10 +1539,14 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
        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))
        {
@@ -1386,20 +1583,15 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
              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;
@@ -1422,25 +1614,22 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
     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;
@@ -1471,9 +1660,13 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
        {
          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)
            {
@@ -1482,16 +1675,25 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
              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;
@@ -1513,13 +1715,21 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
              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--;
+                   }
                }
            }
        }
@@ -1551,11 +1761,13 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
 
       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;
 
@@ -1582,14 +1794,15 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
        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;
 
@@ -1597,13 +1810,14 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
          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);
@@ -1613,18 +1827,14 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
        {
          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);
@@ -1637,12 +1847,12 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
                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;
          }
@@ -1651,8 +1861,8 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
 
          /* 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;
@@ -1668,6 +1878,7 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
            {
              u8 common = '\0';
              int stop = 0;
+
              vec_foreach (possible_command, possible_commands)
              {
                if (common == '\0')
@@ -1680,11 +1891,12 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
                    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
                {
@@ -1700,11 +1912,10 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
       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);
        }
@@ -1726,7 +1937,7 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
     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;
@@ -1757,7 +1968,7 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
     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;
@@ -1956,7 +2167,7 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
        {
          /* 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;
@@ -1991,8 +2202,12 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
              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));
@@ -2023,7 +2238,7 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
              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
            {
@@ -2035,12 +2250,14 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
              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
@@ -2104,10 +2321,12 @@ unix_cli_line_edit (unix_cli_main_t * cm, unix_main_t * um,
                                             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.
                   */
@@ -2118,6 +2337,16 @@ unix_cli_line_edit (unix_cli_main_t * cm, unix_main_t * um,
          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))
@@ -2147,11 +2376,14 @@ unix_cli_process_input (unix_cli_main_t * cm, uword 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 */
@@ -2172,20 +2404,16 @@ more:
       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)
@@ -2204,9 +2432,14 @@ more:
 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)
     {
@@ -2233,6 +2466,15 @@ done:
   /* 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.
@@ -2248,11 +2490,15 @@ unix_cli_kill (unix_cli_main_t * cm, uword cli_file_index)
   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 (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);
@@ -2324,11 +2570,30 @@ unix_cli_write_ready (clib_file_t * uf)
   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);
@@ -2375,6 +2640,24 @@ unix_cli_read_ready (clib_file_t * uf)
   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.
@@ -2388,18 +2671,37 @@ unix_cli_file_add (unix_cli_main_t * cm, char *name, int fd)
   unix_cli_file_t *cf;
   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);
 
@@ -2414,19 +2716,26 @@ unix_cli_file_add (unix_cli_main_t * cm, char *name, int fd)
       };
 
       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->clib_file_index = clib_file_add (fm, &template);
@@ -2455,19 +2764,25 @@ unix_cli_listen_read_ready (clib_file_t * uf)
   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) */
@@ -2494,6 +2809,9 @@ unix_cli_listen_read_ready (clib_file_t * uf)
       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;
 
@@ -2503,9 +2821,14 @@ unix_cli_listen_read_ready (clib_file_t * uf)
       /* Setup the pager */
       cf->no_pager = um->cli_no_pager;
 
-      uf = pool_elt_at_index (fm->file_pool, cf->clib_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));
 
@@ -2532,15 +2855,25 @@ unix_cli_resize_interrupt (int signum)
   (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);
@@ -2572,32 +2905,38 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input)
   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);
+         ioctl (STDIN_FILENO, TIOCGWINSZ, &ws);
          cf->width = ws.ws_col;
          cf->height = ws.ws_row;
 
          if (cf->width == 0 || cf->height == 0)
-           /* We have a tty, but no size. 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;
@@ -2606,36 +2945,42 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input)
          /* 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 */
@@ -2673,6 +3018,7 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input)
 
       template.read_function = unix_cli_listen_read_ready;
       template.file_descriptor = s->fd;
+      template.description = format (0, "cli listener %s", s->config);
 
       clib_file_add (fm, &template);
     }
@@ -2698,8 +3044,8 @@ unix_cli_exit (vlib_main_t * vm)
   unix_main_t *um = &unix_main;
 
   /* If stdin is a tty and we saved the tty state, reset the tty state */
-  if (isatty (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;
 }
@@ -2730,6 +3076,12 @@ unix_cli_quit (vlib_main_t * vm,
               unformat_input_t * input, vlib_cli_command_t * cmd)
 {
   unix_cli_main_t *cm = &unix_cli_main;
+  unix_cli_file_t *cf = pool_elt_at_index (cm->cli_file_pool,
+                                          cm->current_input_file_index);
+
+  /* Cosmetic: suppress the final prompt from appearing before we die */
+  cf->is_interactive = 0;
+  cf->started = 1;
 
   vlib_process_signal_event (vm,
                             vlib_current_process (vm),
@@ -2752,6 +3104,14 @@ VLIB_CLI_COMMAND (unix_cli_quit_command, static) = {
 };
 /* *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,
@@ -2797,7 +3157,7 @@ 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);
@@ -2811,16 +3171,32 @@ done:
 }
 
 /*?
- * 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,
 };
@@ -2895,12 +3271,55 @@ done:
 
 /* *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,
@@ -2912,6 +3331,9 @@ 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);
@@ -2957,6 +3379,8 @@ unix_cli_show_terminal (vlib_main_t * vm,
   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 ? "" :
@@ -2989,6 +3413,7 @@ unix_cli_show_terminal (vlib_main_t * vm,
  * Terminal width:  123
  * Terminal height: 48
  * ANSI capable:    yes
+ * Interactive:     yes
  * History enabled: yes
  * History limit:   50
  * Pager enabled:   yes
@@ -3004,6 +3429,86 @@ VLIB_CLI_COMMAND (cli_unix_cli_show_terminal, static) = {
 };
 /* *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,
@@ -3016,11 +3521,14 @@ 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"))
@@ -3072,11 +3580,14 @@ unix_cli_set_terminal_history (vlib_main_t * vm,
   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"))
@@ -3134,6 +3645,9 @@ unix_cli_set_terminal_ansi (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 (unformat (input, "on"))
     cf->ansi_capable = 1;
   else if (unformat (input, "off"))