misc: fix coverity warnings
[vpp.git] / src / vlib / unix / cli.c
index 42c1374..b0ed9d2 100644 (file)
@@ -47,7 +47,6 @@
 
 #include <vlib/vlib.h>
 #include <vlib/unix/unix.h>
-#include <vppinfra/timer.h>
 
 #include <ctype.h>
 #include <fcntl.h>
@@ -61,6 +60,7 @@
 #include <unistd.h>
 #include <limits.h>
 #include <netinet/tcp.h>
+#include <math.h>
 
 /** ANSI escape code. */
 #define ESC "\x1b"
@@ -293,6 +293,7 @@ typedef enum
   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 */
@@ -359,6 +360,7 @@ static unix_cli_parse_actions_t unix_cli_parse_strings[] = {
   _(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 */
@@ -451,6 +453,21 @@ typedef enum
   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
 {
@@ -468,6 +485,12 @@ 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 */
@@ -581,7 +604,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
@@ -899,7 +922,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.
@@ -1240,25 +1263,109 @@ unix_cli_file_welcome (unix_cli_main_t * cm, unix_cli_file_t * cf)
 
 }
 
-/** @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);
+
+      event_type = vlib_process_get_events (vm, &event_data);
+
+      switch (event_type)
+       {
+       case ~0:                /* no events => timeout */
+         break;
+
+       case UNIX_CLI_NEW_SESSION_EVENT_ADD:
+         {
+           /* Add an identifier to the new session list */
+           unix_cli_new_session_t ns;
+
+           ns.cf_index = event_data[0];
+           ns.deadline = vlib_time_now (vm) + 1.0;
+
+           vec_add1 (cm->new_sessions, ns);
+
+           if (wait > 0.1)
+             wait = 0.1;       /* force a re-evaluation soon, but not too soon */
+         }
+         break;
 
-  cf = pool_elt_at_index (cm->cli_file_pool, (uword) arg);
+       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;
 
-  if (!cf->started)
-    unix_cli_file_welcome (cm, cf);
+         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.
@@ -1511,6 +1618,51 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
       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)
        {
@@ -2712,7 +2864,7 @@ unix_cli_file_add (unix_cli_main_t * cm, char *name, int fd)
       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;
@@ -2728,7 +2880,7 @@ unix_cli_file_add (unix_cli_main_t * cm, char *name, int fd)
     }
 
   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;
@@ -2772,8 +2924,8 @@ unix_cli_listen_read_ready (clib_file_t * uf)
 
   /* Disable Nagle, ignore any errors doing so eg on PF_LOCAL socket */
   one = 1;
-  setsockopt (client.fd, IPPROTO_TCP, TCP_NODELAY,
-             (void *) &one, sizeof (one));
+  (void) setsockopt (client.fd, IPPROTO_TCP, TCP_NODELAY,
+                    (void *) &one, sizeof (one));
 
   client_name = (char *) format (0, "%U%c", format_sockaddr, &client.peer, 0);
 
@@ -2832,9 +2984,22 @@ unix_cli_listen_read_ready (clib_file_t * uf)
       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;
@@ -2918,15 +3083,17 @@ 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");
 
          /* Retrieve the current terminal size */
-         ioctl (STDIN_FILENO, 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)
            {
@@ -3002,7 +3169,7 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input)
          while (i && tmp[--i] != '/')
            ;
 
-         tmp[i] = 0;
+         tmp[i] = '\0';
 
          if (i)
            vlib_unix_recursive_mkdir ((char *) tmp);
@@ -3163,7 +3330,7 @@ unix_cli_exec (vlib_main_t * vm,
   unformat_free (&sub_input);
 
 done:
-  if (fd > 0)
+  if (fd >= 0)
     close (fd);
   vec_free (file_name);
 
@@ -3603,14 +3770,21 @@ unix_cli_set_terminal_history (vlib_main_t * vm,
          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:
@@ -3675,9 +3849,55 @@ VLIB_CLI_COMMAND (cli_unix_cli_set_terminal_ansi, static) = {
 };
 /* *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;
 }