Typos. A bunch of typos I've been collecting.
[vpp.git] / src / vlib / unix / cli.c
index 0cf4ed3..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"
@@ -225,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.
@@ -565,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
@@ -680,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 */
@@ -820,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.
@@ -1216,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;
@@ -1358,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
        {
@@ -1384,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;
@@ -1412,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;
@@ -1428,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--;
        }
 
@@ -1453,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))
        {
@@ -1500,11 +1590,8 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
     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;
@@ -1527,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;
@@ -1576,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)
            {
@@ -1587,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;
@@ -1618,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--;
+                   }
                }
            }
        }
@@ -1656,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;
 
@@ -1687,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;
 
@@ -1702,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);
@@ -1718,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);
@@ -1742,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;
          }
@@ -1756,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;
@@ -1773,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')
@@ -1785,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
                {
@@ -1805,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);
        }
@@ -1831,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;
@@ -1862,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;
@@ -2061,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;
@@ -2096,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));
@@ -2128,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
            {
@@ -2140,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
@@ -2378,6 +2490,10 @@ 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);
 
@@ -2555,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);
 
@@ -2581,20 +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);
@@ -2623,11 +2764,17 @@ 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);
@@ -2771,7 +2918,7 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input)
       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");
@@ -2812,8 +2959,12 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input)
          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 */
+         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 */
@@ -2867,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);
     }
@@ -2952,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,
@@ -3111,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,
@@ -3232,7 +3435,6 @@ unix_cli_show_cli_sessions (vlib_main_t * vm,
                            unformat_input_t * input,
                            vlib_cli_command_t * cmd)
 {
-  //unix_main_t *um = &unix_main;
   unix_cli_main_t *cm = &unix_cli_main;
   clib_file_main_t *fm = &file_main;
   unix_cli_file_t *cf;