pg: exec file fixes
[vpp.git] / src / vlib / unix / cli.c
index 22f56c7..7b9a231 100644 (file)
@@ -61,6 +61,7 @@
 #include <limits.h>
 #include <netinet/tcp.h>
 #include <math.h>
+#include <vppinfra/macros.h>
 
 /** ANSI escape code. */
 #define ESC "\x1b"
@@ -91,7 +92,7 @@
 
 /** Maximum depth into a byte stream from which to compile a Telnet
  * protocol message. This is a safety measure. */
-#define UNIX_CLI_MAX_DEPTH_TELNET 24
+#define UNIX_CLI_MAX_DEPTH_TELNET 32
 
 /** Maximum terminal width we will accept */
 #define UNIX_CLI_MAX_TERMINAL_WIDTH 512
@@ -241,6 +242,8 @@ typedef struct
    */
   u8 cursor_direction;
 
+  /** Macro tables for this session */
+  clib_macro_main_t macro_main;
 } unix_cli_file_t;
 
 /** Resets the pager buffer and other data.
@@ -293,6 +296,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 +363,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 */
@@ -444,13 +449,6 @@ static unix_cli_parse_actions_t unix_cli_parse_pager[] = {
 
 #undef _
 
-/** CLI session events. */
-typedef enum
-{
-  UNIX_CLI_PROCESS_EVENT_READ_READY,  /**< A file descriptor has data to be read. */
-  UNIX_CLI_PROCESS_EVENT_QUIT,       /**< A CLI session wants to close. */
-} unix_cli_process_event_type_t;
-
 /** CLI session telnet negotiation timer events. */
 typedef enum
 {
@@ -489,11 +487,33 @@ typedef struct
 
   /** List of new sessions */
   unix_cli_new_session_t *new_sessions;
+
+  /** system default macro table */
+  clib_macro_main_t macro_main;
+
 } unix_cli_main_t;
 
 /** CLI global state */
 static unix_cli_main_t unix_cli_main;
 
+/** Return the macro main / tables we should use for this session
+ */
+static clib_macro_main_t *
+get_macro_main (void)
+{
+  unix_cli_main_t *cm = &unix_cli_main;
+  vlib_main_t *vm = vlib_get_main ();
+  vlib_process_t *cp = vlib_get_current_process (vm);
+  unix_cli_file_t *cf;
+
+  if (pool_is_free_index (cm->cli_file_pool, cp->output_function_arg))
+    return (&cm->macro_main);
+
+  cf = pool_elt_at_index (cm->cli_file_pool, cp->output_function_arg);
+
+  return (&cf->macro_main);
+}
+
 /**
  * @brief Search for a byte sequence in the action list.
  *
@@ -1236,9 +1256,9 @@ unix_cli_file_welcome (unix_cli_main_t * cm, unix_cli_file_t * cf)
    */
   unix_cli_add_pending_output (uf, cf, (u8 *) "\r", 1);
 
-  if (!um->cli_no_banner)
+  if (!um->cli_no_banner && (um->flags & UNIX_FLAG_NOBANNER) == 0)
     {
-      if (cf->ansi_capable)
+      if (cf->ansi_capable && (um->flags & UNIX_FLAG_NOCOLOR) == 0)
        {
          banner = unix_cli_banner_color;
          len = ARRAY_LEN (unix_cli_banner_color);
@@ -1616,6 +1636,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)
        {
@@ -2448,6 +2513,7 @@ unix_cli_line_edit (unix_cli_main_t * cm, unix_main_t * um,
          if (cf->line_mode)
            {
              vec_delete (cf->input_vector, i, 0);
+             vec_free (cf->current_command);
              cf->current_command = cf->input_vector;
              return 0;
            }
@@ -2510,9 +2576,31 @@ more:
                   format_timeval, 0 /* current bat-time */ ,
                   0 /* current bat-format */ ,
                   cli_file_index, cf->current_command);
+      if ((vec_len (cf->current_command) > 0) &&
+         (cf->current_command[vec_len (cf->current_command) - 1] != '\n'))
+       lv = format (lv, "\n");
       int rv __attribute__ ((unused)) = write (um->log_fd, lv, vec_len (lv));
     }
 
+  /* Run the command through the macro processor */
+  if (vec_len (cf->current_command))
+    {
+      u8 *expanded;
+      vec_validate (cf->current_command, vec_len (cf->current_command));
+      cf->current_command[vec_len (cf->current_command) - 1] = 0;
+      /* The macro expander expects proper C-strings, not vectors */
+      expanded = (u8 *) clib_macro_eval (&cf->macro_main,
+                                        (i8 *) cf->current_command,
+                                        1 /* complain */ ,
+                                        0 /* level */ ,
+                                        8 /* max_level */ );
+      /* Macro processor NULL terminates the return */
+      _vec_len (expanded) -= 1;
+      vec_reset_length (cf->current_command);
+      vec_append (cf->current_command, expanded);
+      vec_free (expanded);
+    }
+
   /* Build an unformat structure around our command */
   unformat_init_vector (&input, cf->current_command);
 
@@ -2613,10 +2701,12 @@ unix_cli_kill (unix_cli_main_t * cm, uword cli_file_index)
     vec_free (cf->command_history[i]);
 
   vec_free (cf->command_history);
+  vec_free (cf->input_vector);
 
   clib_file_del (fm, uf);
 
   unix_cli_file_free (cf);
+  clib_macro_free (&cf->macro_main);
   pool_put (cm->cli_file_pool, cf);
 }
 
@@ -2796,9 +2886,9 @@ unix_cli_file_add (unix_cli_main_t * cm, char *name, int fd)
        * the same new name.
        * Then, throw away the old shared name-vector.
        */
-      for (i = 0; i < vec_len (vlib_mains); i++)
+      for (i = 0; i < vlib_get_n_threads (); i++)
        {
-         this_vlib_main = vlib_mains[i];
+         this_vlib_main = vlib_get_main_by_index (i);
          if (this_vlib_main == 0)
            continue;
          n = vlib_get_node (this_vlib_main,
@@ -2817,7 +2907,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;
@@ -2834,6 +2924,7 @@ unix_cli_file_add (unix_cli_main_t * cm, char *name, int fd)
 
   pool_get (cm->cli_file_pool, cf);
   clib_memset (cf, 0, sizeof (*cf));
+  clib_macro_init (&cf->macro_main);
 
   template.read_function = unix_cli_read_ready;
   template.write_function = unix_cli_write_ready;
@@ -2846,6 +2937,8 @@ unix_cli_file_add (unix_cli_main_t * cm, char *name, int fd)
   cf->clib_file_index = clib_file_add (fm, &template);
   cf->output_vector = 0;
   cf->input_vector = 0;
+  vec_validate (cf->current_command, 0);
+  _vec_len (cf->current_command) = 0;
 
   vlib_start_process (vm, n->runtime_index);
 
@@ -3042,9 +3135,11 @@ unix_cli_config (vlib_main_t * vm, unformat_input_t * input)
            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)
            {
@@ -3185,6 +3280,23 @@ vlib_unix_cli_set_prompt (char *prompt)
   cm->cli_prompt = format (0, fmt, prompt);
 }
 
+static unix_cli_file_t *
+unix_cli_file_if_exists (unix_cli_main_t * cm)
+{
+  if (!cm->cli_file_pool)
+    return 0;
+  return pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index);
+}
+
+static unix_cli_file_t *
+unix_cli_file_if_interactive (unix_cli_main_t * cm)
+{
+  unix_cli_file_t *cf;
+  if ((cf = unix_cli_file_if_exists (cm)) && !cf->is_interactive)
+    return 0;
+  return cf;
+}
+
 /** CLI command to quit the terminal session.
  * @note If this is a stdin session then this will
  *       shutdown VPP also.
@@ -3194,8 +3306,10 @@ 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);
+  unix_cli_file_t *cf;
+
+  if (!(cf = unix_cli_file_if_exists (cm)))
+    return clib_error_return (0, "invalid session");
 
   /* Cosmetic: suppress the final prompt from appearing before we die */
   cf->is_interactive = 0;
@@ -3238,11 +3352,16 @@ unix_cli_exec (vlib_main_t * vm,
   char *file_name;
   int fd;
   unformat_input_t sub_input;
+  unformat_input_t _line_input, *line_input = &_line_input;
   clib_error_t *error;
-
+  unix_cli_main_t *cm = &unix_cli_main;
+  unix_cli_file_t *cf;
+  u8 *file_data = 0;
   file_name = 0;
   fd = -1;
   error = 0;
+  struct stat s;
+
 
   if (!unformat (input, "%s", &file_name))
     {
@@ -3259,29 +3378,73 @@ unix_cli_exec (vlib_main_t * vm,
     }
 
   /* Make sure its a regular file. */
-  {
-    struct stat s;
+  if (fstat (fd, &s) < 0)
+    {
+      error = clib_error_return_unix (0, "failed to stat `%s'", file_name);
+      goto done;
+    }
 
-    if (fstat (fd, &s) < 0)
-      {
-       error = clib_error_return_unix (0, "failed to stat `%s'", file_name);
-       goto done;
-      }
+  if (!(S_ISREG (s.st_mode) || S_ISLNK (s.st_mode)))
+    {
+      error = clib_error_return (0, "not a regular file `%s'", file_name);
+      goto done;
+    }
 
-    if (!(S_ISREG (s.st_mode) || S_ISLNK (s.st_mode)))
-      {
-       error = clib_error_return (0, "not a regular file `%s'", file_name);
-       goto done;
-      }
-  }
+  /* Read the file */
+  vec_validate (file_data, s.st_size);
+
+  if (read (fd, file_data, s.st_size) != s.st_size)
+    {
+      error = clib_error_return_unix (0, "Failed to read %d bytes from '%s'",
+                                     s.st_size, file_name);
+      vec_free (file_data);
+      goto done;
+    }
+
+  /* The macro expander expects a c string... */
+  vec_add1 (file_data, 0);
+
+  unformat_init_vector (&sub_input, file_data);
+
+  /* Run the file contents through the macro processor */
+  if (vec_len (sub_input.buffer) > 1)
+    {
+      u8 *expanded;
+      clib_macro_main_t *mm = 0;
+
+      /* Initial config process? Use the global macro table. */
+      if (pool_is_free_index
+         (cm->cli_file_pool, cm->current_input_file_index))
+       mm = &cm->macro_main;
+      else
+       {
+         /* Otherwise, use the per-cli-process macro table */
+         cf = pool_elt_at_index (cm->cli_file_pool,
+                                 cm->current_input_file_index);
+         mm = &cf->macro_main;
+       }
 
-  unformat_init_clib_file (&sub_input, fd);
+      expanded = (u8 *) clib_macro_eval (mm,
+                                        (i8 *) sub_input.buffer,
+                                        1 /* complain */ ,
+                                        0 /* level */ ,
+                                        8 /* max_level */ );
+      /* Macro processor NULL terminates the return */
+      _vec_len (expanded) -= 1;
+      vec_reset_length (sub_input.buffer);
+      vec_append (sub_input.buffer, expanded);
+      vec_free (expanded);
+    }
 
-  vlib_cli_input (vm, &sub_input, 0, 0);
+  while (unformat_user (&sub_input, unformat_line_input, line_input))
+    {
+      vlib_cli_input (vm, line_input, 0, 0);
+      unformat_free (line_input);
+    }
   unformat_free (&sub_input);
 
 done:
-  if (fd > 0)
+  if (fd >= 0)
     close (fd);
   vec_free (file_name);
 
@@ -3410,7 +3573,7 @@ unix_show_files (vlib_main_t * vm,
                   "Read", "Write", "Error", "File Name", "Description");
 
   /* *INDENT-OFF* */
-  pool_foreach (f, fm->file_pool,(
+  pool_foreach (f, fm->file_pool)
    {
       int rv;
       s = format (s, "/proc/self/fd/%d%c", f->file_descriptor, 0);
@@ -3423,7 +3586,7 @@ unix_show_files (vlib_main_t * vm,
                       f->read_events, f->write_events, f->error_events,
                       path, f->description);
       vec_reset_length (s);
-    }));
+    }
   /* *INDENT-ON* */
   vec_free (s);
 
@@ -3447,9 +3610,7 @@ unix_cli_show_history (vlib_main_t * vm,
   unix_cli_file_t *cf;
   int i, j;
 
-  cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index);
-
-  if (!cf->is_interactive)
+  if (!(cf = unix_cli_file_if_interactive (cm)))
     return clib_error_return (0, "invalid for non-interactive sessions");
 
   if (cf->has_history && cf->history_limit)
@@ -3487,7 +3648,9 @@ unix_cli_show_terminal (vlib_main_t * vm,
   unix_cli_file_t *cf;
   vlib_node_t *n;
 
-  cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index);
+  if (!(cf = unix_cli_file_if_exists (cm)))
+    return clib_error_return (0, "invalid session");
+
   n = vlib_get_node (vm, cf->process_node_index);
 
   vlib_cli_output (vm, "Terminal name:   %v\n", n->name);
@@ -3563,7 +3726,7 @@ unix_cli_show_cli_sessions (vlib_main_t * vm,
 
 #define fl(x, y) ( (x) ? toupper((y)) : tolower((y)) )
   /* *INDENT-OFF* */
-  pool_foreach (cf, cm->cli_file_pool, ({
+  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,
@@ -3576,7 +3739,7 @@ unix_cli_show_cli_sessions (vlib_main_t * vm,
                     fl (cf->line_mode, 'l'),
                     fl (cf->has_epipe, 'p'),
                     fl (cf->ansi_capable, 'a'));
-  }));
+  }
   /* *INDENT-ON* */
 #undef fl
 
@@ -3639,9 +3802,7 @@ 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)
+  if (!(cf = unix_cli_file_if_interactive (cm)))
     return clib_error_return (0, "invalid for non-interactive sessions");
 
   if (!unformat_user (input, unformat_line_input, line_input))
@@ -3698,9 +3859,7 @@ 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)
+  if (!(cf = unix_cli_file_if_interactive (cm)))
     return clib_error_return (0, "invalid for non-interactive sessions");
 
   if (!unformat_user (input, unformat_line_input, line_input))
@@ -3768,9 +3927,7 @@ unix_cli_set_terminal_ansi (vlib_main_t * vm,
   unix_cli_main_t *cm = &unix_cli_main;
   unix_cli_file_t *cf;
 
-  cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index);
-
-  if (!cf->is_interactive)
+  if (!(cf = unix_cli_file_if_interactive (cm)))
     return clib_error_return (0, "invalid for non-interactive sessions");
 
   if (unformat (input, "on"))
@@ -3800,6 +3957,161 @@ 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 *
+echo_cmd (vlib_main_t * vm,
+         unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  unformat_input_t _line_input, *line_input = &_line_input;
+
+  /* Get a line of input. */
+  if (!unformat_user (input, unformat_line_input, line_input))
+    {
+      vlib_cli_output (vm, "");
+      return 0;
+    }
+
+  vlib_cli_output (vm, "%v", line_input->buffer);
+
+  unformat_free (line_input);
+  return 0;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (cli_unix_echo_cmd, static) = {
+  .path = "echo",
+  .short_help = "echo <rest-of-line>",
+  .function = echo_cmd,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+define_cmd_fn (vlib_main_t * vm,
+              unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  u8 *macro_name;
+  unformat_input_t _line_input, *line_input = &_line_input;
+  clib_macro_main_t *mm = get_macro_main ();
+  clib_error_t *error;
+
+  if (!unformat (input, "%s", &macro_name))
+    return clib_error_return (0, "missing variable name...");
+
+  /* Remove white space */
+  (void) unformat (input, "");
+
+  /* Get a line of input. */
+  if (!unformat_user (input, unformat_line_input, line_input))
+    {
+      error = clib_error_return (0, "missing value for '%s'...", macro_name);
+      vec_free (macro_name);
+      return error;
+    }
+  /* the macro expander expects c-strings, not vectors... */
+  vec_add1 (line_input->buffer, 0);
+  clib_macro_set_value (mm, (char *) macro_name, (char *) line_input->buffer);
+  vec_free (macro_name);
+  unformat_free (line_input);
+  return 0;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (define_cmd, static) = {
+  .path = "define",
+  .short_help = "define <variable-name> <value>",
+  .function = define_cmd_fn,
+};
+
+/* *INDENT-ON* */
+
+static clib_error_t *
+undefine_cmd_fn (vlib_main_t * vm,
+                unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  u8 *macro_name;
+  clib_macro_main_t *mm = get_macro_main ();
+
+  if (!unformat (input, "%s", &macro_name))
+    return clib_error_return (0, "missing variable name...");
+
+  if (clib_macro_unset (mm, (char *) macro_name))
+    vlib_cli_output (vm, "%s wasn't set...", macro_name);
+
+  vec_free (macro_name);
+  return 0;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (undefine_cmd, static) = {
+  .path = "undefine",
+  .short_help = "undefine <variable-name>",
+  .function = undefine_cmd_fn,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+show_macro_cmd_fn (vlib_main_t * vm,
+                  unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  clib_macro_main_t *mm = get_macro_main ();
+  int evaluate = 1;
+
+  if (unformat (input, "noevaluate %=", &evaluate, 0))
+    ;
+  else if (unformat (input, "noeval %=", &evaluate, 0))
+    ;
+
+  vlib_cli_output (vm, "%U", format_clib_macro_main, mm, evaluate);
+  return 0;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (show_macro, static) = {
+  .path = "show macro",
+  .short_help = "show macro [noevaluate]",
+  .function = show_macro_cmd_fn,
+};
+/* *INDENT-ON* */
+
 static clib_error_t *
 unix_cli_init (vlib_main_t * vm)
 {
@@ -3808,6 +4120,7 @@ unix_cli_init (vlib_main_t * vm)
   /* Breadcrumb to indicate the new session process
    * has not been started */
   cm->new_session_process_node_index = ~0;
+  clib_macro_init (&cm->macro_main);
 
   return 0;
 }