VPP-221 CLI auto-documentation infrastructure
[vpp.git] / vlib / vlib / unix / cli.c
1 /*
2  * Copyright (c) 2015 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 /*
16  * cli.c: Unix stdin/socket CLI.
17  *
18  * Copyright (c) 2008 Eliot Dresselhaus
19  *
20  * Permission is hereby granted, free of charge, to any person obtaining
21  * a copy of this software and associated documentation files (the
22  * "Software"), to deal in the Software without restriction, including
23  * without limitation the rights to use, copy, modify, merge, publish,
24  * distribute, sublicense, and/or sell copies of the Software, and to
25  * permit persons to whom the Software is furnished to do so, subject to
26  * the following conditions:
27  *
28  * The above copyright notice and this permission notice shall be
29  * included in all copies or substantial portions of the Software.
30  *
31  *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
32  *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
33  *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
34  *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
35  *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
36  *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
37  *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
38  */
39 /**
40  * @file
41  * @brief Unix stdin/socket command line interface.
42  * Provides a command line interface so humans can interact with VPP.
43  * This is predominantly a debugging and testing mechanism.
44  */
45 /*? %%clicmd:group_label Debug CLI %% ?*/
46
47 #include <vlib/vlib.h>
48 #include <vlib/unix/unix.h>
49 #include <vppinfra/timer.h>
50
51 #include <ctype.h>
52 #include <fcntl.h>
53 #include <sys/stat.h>
54 #include <termios.h>
55 #include <signal.h>
56 #include <unistd.h>
57 #include <arpa/telnet.h>
58 #include <sys/ioctl.h>
59
60 /** ANSI escape code. */
61 #define ESC "\x1b"
62
63 /** ANSI Control Sequence Introducer. */
64 #define CSI ESC "["
65
66 /** ANSI clear screen. */
67 #define ANSI_CLEAR      CSI "2J" CSI "1;1H"
68 /** ANSI reset color settings. */
69 #define ANSI_RESET      CSI "0m"
70 /** ANSI Start bold text. */
71 #define ANSI_BOLD       CSI "1m"
72 /** ANSI Stop bold text. */
73 #define ANSI_DIM        CSI "2m"
74 /** ANSI Start dark red text. */
75 #define ANSI_DRED       ANSI_DIM CSI "31m"
76 /** ANSI Start bright red text. */
77 #define ANSI_BRED       ANSI_BOLD CSI "31m"
78 /** ANSI clear line cursor is on. */
79 #define ANSI_CLEARLINE  CSI "2K"
80 /** ANSI scroll screen down one line. */
81 #define ANSI_SCROLLDN   CSI "1T"
82 /** ANSI save cursor position. */
83 #define ANSI_SAVECURSOR CSI "s"
84 /** ANSI restore cursor position if previously saved. */
85 #define ANSI_RESTCURSOR CSI "u"
86
87 /** Maximum depth into a byte stream from which to compile a Telnet
88  * protocol message. This is a saftey measure. */
89 #define UNIX_CLI_MAX_DEPTH_TELNET 24
90
91 /** Unix standard in */
92 #define UNIX_CLI_STDIN_FD 0
93
94
95 /** A CLI banner line. */
96 typedef struct
97 {
98   u8 *line;     /**< The line to print. */
99   u32 length;   /**< The length of the line without terminating NUL. */
100 } unix_cli_banner_t;
101
102 #define _(a) { .line = (u8 *)(a), .length = sizeof(a) - 1 }
103 /** Plain welcome banner. */
104 static unix_cli_banner_t unix_cli_banner[] = {
105   _("    _______    _        _   _____  ___ \n"),
106   _(" __/ __/ _ \\  (_)__    | | / / _ \\/ _ \\\n"),
107   _(" _/ _// // / / / _ \\   | |/ / ___/ ___/\n"),
108   _(" /_/ /____(_)_/\\___/   |___/_/  /_/    \n"),
109   _("\n")
110 };
111
112 /** ANSI color welcome banner. */
113 static unix_cli_banner_t unix_cli_banner_color[] = {
114   _(ANSI_BRED "    _______    _     " ANSI_RESET "   _   _____  ___ \n"),
115   _(ANSI_BRED " __/ __/ _ \\  (_)__ " ANSI_RESET "   | | / / _ \\/ _ \\\n"),
116   _(ANSI_BRED " _/ _// // / / / _ \\" ANSI_RESET "   | |/ / ___/ ___/\n"),
117   _(ANSI_BRED " /_/ /____(_)_/\\___/" ANSI_RESET "   |___/_/  /_/    \n"),
118   _("\n")
119 };
120
121 #undef _
122
123 /** Pager line index */
124 typedef struct
125 {
126   /** Index into pager_vector */
127   u32 line;
128
129   /** Offset of the string in the line */
130   u32 offset;
131
132   /** Length of the string in the line */
133   u32 length;
134 } unix_cli_pager_index_t;
135
136
137 /** Unix CLI session. */
138 typedef struct
139 {
140   /** The file index held by unix.c */
141   u32 unix_file_index;
142
143   /** Vector of output pending write to file descriptor. */
144   u8 *output_vector;
145
146   /** Vector of input saved by Unix input node to be processed by
147      CLI process. */
148   u8 *input_vector;
149
150   /** This session has command history. */
151   u8 has_history;
152   /** Array of vectors of commands in the history. */
153   u8 **command_history;
154   /** The command currently pointed at by the history cursor. */
155   u8 *current_command;
156   /** How far from the end of the history array the user has browsed. */
157   i32 excursion;
158
159   /** Maximum number of history entries this session will store. */
160   u32 history_limit;
161
162   /** Current command line counter */
163   u32 command_number;
164
165   /** The string being searched for in the history. */
166   u8 *search_key;
167   /** If non-zero then the CLI is searching in the history array.
168    * - @c -1 means search backwards.
169    * - @c 1 means search forwards.
170    */
171   int search_mode;
172
173   /** Position of the insert cursor on the current input line */
174   u32 cursor;
175
176   /** Line mode or char mode */
177   u8 line_mode;
178
179   /** Set if the CRLF mode wants CR + LF */
180   u8 crlf_mode;
181
182   /** Can we do ANSI output? */
183   u8 ansi_capable;
184
185   /** Has the session started? */
186   u8 started;
187
188   /** Disable the pager? */
189   u8 no_pager;
190
191   /** Pager buffer */
192   u8 **pager_vector;
193
194   /** Index of line fragments in the pager buffer */
195   unix_cli_pager_index_t *pager_index;
196
197   /** Line number of top of page */
198   u32 pager_start;
199
200   /** Terminal width */
201   u32 width;
202
203   /** Terminal height */
204   u32 height;
205
206   /** Process node identifier */
207   u32 process_node_index;
208 } unix_cli_file_t;
209
210 /** Resets the pager buffer and other data.
211  * @param f The CLI session whose pager needs to be reset.
212  */
213 always_inline void
214 unix_cli_pager_reset (unix_cli_file_t * f)
215 {
216   u8 **p;
217
218   f->pager_start = 0;
219
220   vec_free (f->pager_index);
221   f->pager_index = 0;
222
223   vec_foreach (p, f->pager_vector)
224   {
225     vec_free (*p);
226   }
227   vec_free (f->pager_vector);
228   f->pager_vector = 0;
229 }
230
231 /** Release storage used by a CLI session.
232  * @param f The CLI session whose storage needs to be released.
233  */
234 always_inline void
235 unix_cli_file_free (unix_cli_file_t * f)
236 {
237   vec_free (f->output_vector);
238   vec_free (f->input_vector);
239   unix_cli_pager_reset (f);
240 }
241
242 /** CLI actions */
243 typedef enum
244 {
245   UNIX_CLI_PARSE_ACTION_NOACTION = 0,   /**< No action */
246   UNIX_CLI_PARSE_ACTION_CRLF,           /**< Carriage return, newline or enter */
247   UNIX_CLI_PARSE_ACTION_TAB,            /**< Tab key */
248   UNIX_CLI_PARSE_ACTION_ERASE,          /**< Erase cursor left */
249   UNIX_CLI_PARSE_ACTION_ERASERIGHT,     /**< Erase cursor right */
250   UNIX_CLI_PARSE_ACTION_UP,             /**< Up arrow */
251   UNIX_CLI_PARSE_ACTION_DOWN,           /**< Down arrow */
252   UNIX_CLI_PARSE_ACTION_LEFT,           /**< Left arrow */
253   UNIX_CLI_PARSE_ACTION_RIGHT,          /**< Right arrow */
254   UNIX_CLI_PARSE_ACTION_HOME,           /**< Home key (jump to start of line) */
255   UNIX_CLI_PARSE_ACTION_END,            /**< End key (jump to end of line) */
256   UNIX_CLI_PARSE_ACTION_WORDLEFT,       /**< Jump cursor to start of left word */
257   UNIX_CLI_PARSE_ACTION_WORDRIGHT,      /**< Jump cursor to start of right word */
258   UNIX_CLI_PARSE_ACTION_ERASELINELEFT,  /**< Erase line to left of cursor */
259   UNIX_CLI_PARSE_ACTION_ERASELINERIGHT, /**< Erase line to right & including cursor */
260   UNIX_CLI_PARSE_ACTION_CLEAR,          /**< Clear the terminal */
261   UNIX_CLI_PARSE_ACTION_REVSEARCH,      /**< Search backwards in command history */
262   UNIX_CLI_PARSE_ACTION_FWDSEARCH,      /**< Search forwards in command history */
263   UNIX_CLI_PARSE_ACTION_YANK,           /**< Undo last erase action */
264   UNIX_CLI_PARSE_ACTION_TELNETIAC,      /**< Telnet control code */
265
266   UNIX_CLI_PARSE_ACTION_PAGER_CRLF,     /**< Enter pressed (CR, CRLF, LF, etc) */
267   UNIX_CLI_PARSE_ACTION_PAGER_QUIT,     /**< Exit the pager session */
268   UNIX_CLI_PARSE_ACTION_PAGER_NEXT,     /**< Scroll to next page */
269   UNIX_CLI_PARSE_ACTION_PAGER_DN,       /**< Scroll to next line */
270   UNIX_CLI_PARSE_ACTION_PAGER_UP,       /**< Scroll to previous line */
271   UNIX_CLI_PARSE_ACTION_PAGER_TOP,      /**< Scroll to first line */
272   UNIX_CLI_PARSE_ACTION_PAGER_BOTTOM,   /**< Scroll to last line */
273   UNIX_CLI_PARSE_ACTION_PAGER_PGDN,     /**< Scroll to next page */
274   UNIX_CLI_PARSE_ACTION_PAGER_PGUP,     /**< Scroll to previous page */
275   UNIX_CLI_PARSE_ACTION_PAGER_REDRAW,   /**< Clear and redraw the page on the terminal */
276   UNIX_CLI_PARSE_ACTION_PAGER_SEARCH,   /**< Search the pager buffer */
277
278   UNIX_CLI_PARSE_ACTION_PARTIALMATCH,   /**< Action parser found a partial match */
279   UNIX_CLI_PARSE_ACTION_NOMATCH         /**< Action parser did not find any match */
280 } unix_cli_parse_action_t;
281
282 /** @brief Mapping of input buffer strings to action values.
283  * @note This won't work as a hash since we need to be able to do
284  *       partial matches on the string.
285  */
286 typedef struct
287 {
288   u8 *input;                        /**< Input string to match. */
289   u32 len;                          /**< Length of input without final NUL. */
290   unix_cli_parse_action_t action;   /**< Action to take when matched. */
291 } unix_cli_parse_actions_t;
292
293 /** @brief Given a capital ASCII letter character return a @c NUL terminated
294  * string with the control code for that letter.
295  *
296  * @param c An ASCII character.
297  * @return A @c NUL terminated string of type @c u8[].
298  *
299  * @par Example
300  *     @c CTL('A') returns <code>{ 0x01, 0x00 }</code> as a @c u8[].
301  */
302 #define CTL(c) (u8[]){ (c) - '@', 0 }
303
304 #define _(a,b) { .input = (u8 *)(a), .len = sizeof(a) - 1, .action = (b) }
305 /**
306  * Patterns to match on a CLI input stream.
307  * @showinitializer
308  */
309 static unix_cli_parse_actions_t unix_cli_parse_strings[] = {
310   /* Line handling */
311   _("\r\n", UNIX_CLI_PARSE_ACTION_CRLF),        /* Must be before '\r' */
312   _("\n", UNIX_CLI_PARSE_ACTION_CRLF),
313   _("\r\0", UNIX_CLI_PARSE_ACTION_CRLF),        /* Telnet does this */
314   _("\r", UNIX_CLI_PARSE_ACTION_CRLF),
315
316   /* Unix shell control codes */
317   _(CTL ('B'), UNIX_CLI_PARSE_ACTION_LEFT),
318   _(CTL ('F'), UNIX_CLI_PARSE_ACTION_RIGHT),
319   _(CTL ('P'), UNIX_CLI_PARSE_ACTION_UP),
320   _(CTL ('N'), UNIX_CLI_PARSE_ACTION_DOWN),
321   _(CTL ('A'), UNIX_CLI_PARSE_ACTION_HOME),
322   _(CTL ('E'), UNIX_CLI_PARSE_ACTION_END),
323   _(CTL ('D'), UNIX_CLI_PARSE_ACTION_ERASERIGHT),
324   _(CTL ('U'), UNIX_CLI_PARSE_ACTION_ERASELINELEFT),
325   _(CTL ('K'), UNIX_CLI_PARSE_ACTION_ERASELINERIGHT),
326   _(CTL ('Y'), UNIX_CLI_PARSE_ACTION_YANK),
327   _(CTL ('L'), UNIX_CLI_PARSE_ACTION_CLEAR),
328   _(ESC "b", UNIX_CLI_PARSE_ACTION_WORDLEFT),   /* Alt-B */
329   _(ESC "f", UNIX_CLI_PARSE_ACTION_WORDRIGHT),  /* Alt-F */
330   _("\b", UNIX_CLI_PARSE_ACTION_ERASE), /* ^H */
331   _("\x7f", UNIX_CLI_PARSE_ACTION_ERASE),       /* Backspace */
332   _("\t", UNIX_CLI_PARSE_ACTION_TAB),   /* ^I */
333
334   /* VT100 Normal mode - Broadest support */
335   _(CSI "A", UNIX_CLI_PARSE_ACTION_UP),
336   _(CSI "B", UNIX_CLI_PARSE_ACTION_DOWN),
337   _(CSI "C", UNIX_CLI_PARSE_ACTION_RIGHT),
338   _(CSI "D", UNIX_CLI_PARSE_ACTION_LEFT),
339   _(CSI "H", UNIX_CLI_PARSE_ACTION_HOME),
340   _(CSI "F", UNIX_CLI_PARSE_ACTION_END),
341   _(CSI "3~", UNIX_CLI_PARSE_ACTION_ERASERIGHT),        /* Delete */
342   _(CSI "1;5D", UNIX_CLI_PARSE_ACTION_WORDLEFT),        /* C-Left */
343   _(CSI "1;5C", UNIX_CLI_PARSE_ACTION_WORDRIGHT),       /* C-Right */
344
345   /* VT100 Application mode - Some Gnome Terminal functions use these */
346   _(ESC "OA", UNIX_CLI_PARSE_ACTION_UP),
347   _(ESC "OB", UNIX_CLI_PARSE_ACTION_DOWN),
348   _(ESC "OC", UNIX_CLI_PARSE_ACTION_RIGHT),
349   _(ESC "OD", UNIX_CLI_PARSE_ACTION_LEFT),
350   _(ESC "OH", UNIX_CLI_PARSE_ACTION_HOME),
351   _(ESC "OF", UNIX_CLI_PARSE_ACTION_END),
352
353   /* ANSI X3.41-1974 - sent by Microsoft Telnet and PuTTY */
354   _(CSI "1~", UNIX_CLI_PARSE_ACTION_HOME),
355   _(CSI "4~", UNIX_CLI_PARSE_ACTION_END),
356
357   /* Emacs-ish history search */
358   _(CTL ('S'), UNIX_CLI_PARSE_ACTION_FWDSEARCH),
359   _(CTL ('R'), UNIX_CLI_PARSE_ACTION_REVSEARCH),
360
361   /* Other protocol things */
362   _("\xff", UNIX_CLI_PARSE_ACTION_TELNETIAC),   /* IAC */
363   _("\0", UNIX_CLI_PARSE_ACTION_NOACTION),      /* NUL */
364   _(NULL, UNIX_CLI_PARSE_ACTION_NOMATCH)
365 };
366
367 /**
368  * Patterns to match when a CLI session is in the pager.
369  * @showinitializer
370  */
371 static unix_cli_parse_actions_t unix_cli_parse_pager[] = {
372   /* Line handling */
373   _("\r\n", UNIX_CLI_PARSE_ACTION_PAGER_CRLF),  /* Must be before '\r' */
374   _("\n", UNIX_CLI_PARSE_ACTION_PAGER_CRLF),
375   _("\r\0", UNIX_CLI_PARSE_ACTION_PAGER_CRLF),  /* Telnet does this */
376   _("\r", UNIX_CLI_PARSE_ACTION_PAGER_CRLF),
377
378   /* Pager commands */
379   _(" ", UNIX_CLI_PARSE_ACTION_PAGER_NEXT),
380   _("q", UNIX_CLI_PARSE_ACTION_PAGER_QUIT),
381   _(CTL ('L'), UNIX_CLI_PARSE_ACTION_PAGER_REDRAW),
382   _(CTL ('R'), UNIX_CLI_PARSE_ACTION_PAGER_REDRAW),
383   _("/", UNIX_CLI_PARSE_ACTION_PAGER_SEARCH),
384
385   /* VT100 */
386   _(CSI "A", UNIX_CLI_PARSE_ACTION_PAGER_UP),
387   _(CSI "B", UNIX_CLI_PARSE_ACTION_PAGER_DN),
388   _(CSI "H", UNIX_CLI_PARSE_ACTION_PAGER_TOP),
389   _(CSI "F", UNIX_CLI_PARSE_ACTION_PAGER_BOTTOM),
390
391   /* VT100 Application mode */
392   _(ESC "OA", UNIX_CLI_PARSE_ACTION_PAGER_UP),
393   _(ESC "OB", UNIX_CLI_PARSE_ACTION_PAGER_DN),
394   _(ESC "OH", UNIX_CLI_PARSE_ACTION_PAGER_TOP),
395   _(ESC "OF", UNIX_CLI_PARSE_ACTION_PAGER_BOTTOM),
396
397   /* ANSI X3.41-1974 */
398   _(CSI "1~", UNIX_CLI_PARSE_ACTION_PAGER_TOP),
399   _(CSI "4~", UNIX_CLI_PARSE_ACTION_PAGER_BOTTOM),
400   _(CSI "5~", UNIX_CLI_PARSE_ACTION_PAGER_PGUP),
401   _(CSI "6~", UNIX_CLI_PARSE_ACTION_PAGER_PGDN),
402
403   /* Other protocol things */
404   _("\xff", UNIX_CLI_PARSE_ACTION_TELNETIAC),   /* IAC */
405   _("\0", UNIX_CLI_PARSE_ACTION_NOACTION),      /* NUL */
406   _(NULL, UNIX_CLI_PARSE_ACTION_NOMATCH)
407 };
408
409 #undef _
410
411 /** CLI session events. */
412 typedef enum
413 {
414   UNIX_CLI_PROCESS_EVENT_READ_READY,  /**< A file descriptor has data to be read. */
415   UNIX_CLI_PROCESS_EVENT_QUIT,        /**< A CLI session wants to close. */
416 } unix_cli_process_event_type_t;
417
418 /** CLI global state. */
419 typedef struct
420 {
421   /** Prompt string for CLI. */
422   u8 *cli_prompt;
423
424   /** Vec pool of CLI sessions. */
425   unix_cli_file_t *cli_file_pool;
426
427   /** Vec pool of unused session indices. */
428   u32 *unused_cli_process_node_indices;
429
430   /** The session index of the stdin cli */
431   u32 stdin_cli_file_index;
432
433   /** File pool index of current input. */
434   u32 current_input_file_index;
435 } unix_cli_main_t;
436
437 /** CLI global state */
438 static unix_cli_main_t unix_cli_main;
439
440 /**
441  * @brief Search for a byte sequence in the action list.
442  *
443  * Searches the @ref unix_cli_parse_actions_t list in @a a for a match with
444  * the bytes in @a input of maximum length @a ilen bytes.
445  * When a match is made @a *matched indicates how many bytes were matched.
446  * Returns a value from the enum @ref unix_cli_parse_action_t to indicate
447  * whether no match was found, a partial match was found or a complete
448  * match was found and what action, if any, should be taken.
449  *
450  * @param[in]  a        Actions list to search within.
451  * @param[in]  input    String fragment to search for.
452  * @param[in]  ilen     Length of the string in 'input'.
453  * @param[out] matched  Pointer to an integer that will contain the number
454  *                      of bytes matched when a complete match is found.
455  *
456  * @return Action from @ref unix_cli_parse_action_t that the string fragment
457  *         matches.
458  *         @ref UNIX_CLI_PARSE_ACTION_PARTIALMATCH is returned when the
459  *         whole input string matches the start of at least one action.
460  *         @ref UNIX_CLI_PARSE_ACTION_NOMATCH is returned when there is no
461  *         match at all.
462  */
463 static unix_cli_parse_action_t
464 unix_cli_match_action (unix_cli_parse_actions_t * a,
465                        u8 * input, u32 ilen, i32 * matched)
466 {
467   u8 partial = 0;
468
469   while (a->input)
470     {
471       if (ilen >= a->len)
472         {
473           /* see if the start of the input buffer exactly matches the current
474            * action string. */
475           if (memcmp (input, a->input, a->len) == 0)
476             {
477               *matched = a->len;
478               return a->action;
479             }
480         }
481       else
482         {
483           /* if the first ilen characters match, flag this as a partial -
484            * meaning keep collecting bytes in case of a future match */
485           if (memcmp (input, a->input, ilen) == 0)
486             partial = 1;
487         }
488
489       /* check next action */
490       a++;
491     }
492
493   return partial ?
494     UNIX_CLI_PARSE_ACTION_PARTIALMATCH : UNIX_CLI_PARSE_ACTION_NOMATCH;
495 }
496
497
498 /** Add bytes to the output vector and then flagg the I/O system that bytes
499  * are available to be sent.
500  */
501 static void
502 unix_cli_add_pending_output (unix_file_t * uf,
503                              unix_cli_file_t * cf,
504                              u8 * buffer, uword buffer_bytes)
505 {
506   unix_main_t *um = &unix_main;
507
508   vec_add (cf->output_vector, buffer, buffer_bytes);
509   if (vec_len (cf->output_vector) > 0)
510     {
511       int skip_update = 0 != (uf->flags & UNIX_FILE_DATA_AVAILABLE_TO_WRITE);
512       uf->flags |= UNIX_FILE_DATA_AVAILABLE_TO_WRITE;
513       if (!skip_update)
514         um->file_update (uf, UNIX_FILE_UPDATE_MODIFY);
515     }
516 }
517
518 /** Delete all bytes from the output vector and flag the I/O system
519  * that no more bytes are available to be sent.
520  */
521 static void
522 unix_cli_del_pending_output (unix_file_t * uf,
523                              unix_cli_file_t * cf, uword n_bytes)
524 {
525   unix_main_t *um = &unix_main;
526
527   vec_delete (cf->output_vector, n_bytes, 0);
528   if (vec_len (cf->output_vector) <= 0)
529     {
530       int skip_update = 0 == (uf->flags & UNIX_FILE_DATA_AVAILABLE_TO_WRITE);
531       uf->flags &= ~UNIX_FILE_DATA_AVAILABLE_TO_WRITE;
532       if (!skip_update)
533         um->file_update (uf, UNIX_FILE_UPDATE_MODIFY);
534     }
535 }
536
537 /** @brief A bit like strchr with a buffer length limit.
538  * Search a buffer for the first instance of a character up to the limit of
539  * the buffer length. If found then return the position of that character.
540  *
541  * The key departure from strchr is that if the character is not found then
542  * return the buffer length.
543  *
544  * @param chr The byte value to search for.
545  * @param str The buffer in which to search for the value.
546  * @param len The depth into the buffer to search.
547  *
548  * @return The index of the first occurence of \c chr. If \c chr is not
549  *          found then \c len instead.
550  */
551 always_inline word
552 unix_vlib_findchr (u8 chr, u8 * str, word len)
553 {
554   word i = 0;
555   for (i = 0; i < len; i++, str++)
556     {
557       if (*str == chr)
558         return i;
559     }
560   return len;
561 }
562
563 /** @brief Send a buffer to the CLI stream if possible, enqueue it otherwise.
564  * Attempts to write given buffer to the file descriptor of the given
565  * Unix CLI session. If that session already has data in the output buffer
566  * or if the write attempt tells us to try again later then the given buffer
567  * is appended to the pending output buffer instead.
568  *
569  * This is typically called only from \c unix_vlib_cli_output_cooked since
570  * that is where CRLF handling occurs or from places where we explicitly do
571  * not want cooked handling.
572  *
573  * @param cf Unix CLI session of the desired stream to write to.
574  * @param uf The Unix file structure of the desired stream to write to.
575  * @param buffer Pointer to the buffer that needs to be written.
576  * @param buffer_bytes The number of bytes from \c buffer to write.
577  */
578 static void
579 unix_vlib_cli_output_raw (unix_cli_file_t * cf,
580                           unix_file_t * uf, u8 * buffer, uword buffer_bytes)
581 {
582   int n = 0;
583
584   if (vec_len (cf->output_vector) == 0)
585     n = write (uf->file_descriptor, buffer, buffer_bytes);
586
587   if (n < 0 && errno != EAGAIN)
588     {
589       clib_unix_warning ("write");
590     }
591   else if ((word) n < (word) buffer_bytes)
592     {
593       /* We got EAGAIN or we already have stuff in the buffer;
594        * queue up whatever didn't get sent for later. */
595       if (n < 0)
596         n = 0;
597       unix_cli_add_pending_output (uf, cf, buffer + n, buffer_bytes - n);
598     }
599 }
600
601 /** @brief Process a buffer for CRLF handling before outputting it to the CLI.
602  *
603  * @param cf Unix CLI session of the desired stream to write to.
604  * @param uf The Unix file structure of the desired stream to write to.
605  * @param buffer Pointer to the buffer that needs to be written.
606  * @param buffer_bytes The number of bytes from \c buffer to write.
607  */
608 static void
609 unix_vlib_cli_output_cooked (unix_cli_file_t * cf,
610                              unix_file_t * uf,
611                              u8 * buffer, uword buffer_bytes)
612 {
613   word end = 0, start = 0;
614
615   while (end < buffer_bytes)
616     {
617       if (cf->crlf_mode)
618         {
619           /* iterate the line on \n's so we can insert a \r before it */
620           end = unix_vlib_findchr ('\n',
621                                    buffer + start,
622                                    buffer_bytes - start) + start;
623         }
624       else
625         {
626           /* otherwise just send the whole buffer */
627           end = buffer_bytes;
628         }
629
630       unix_vlib_cli_output_raw (cf, uf, buffer + start, end - start);
631
632       if (cf->crlf_mode)
633         {
634           if (end < buffer_bytes)
635             {
636               unix_vlib_cli_output_raw (cf, uf, (u8 *) "\r\n", 2);
637               end++;            /* skip the \n that we already sent */
638             }
639           start = end;
640         }
641     }
642 }
643
644 /** @brief Output the CLI prompt */
645 static void
646 unix_cli_cli_prompt (unix_cli_file_t * cf, unix_file_t * uf)
647 {
648   unix_cli_main_t *cm = &unix_cli_main;
649
650   unix_vlib_cli_output_raw (cf, uf, cm->cli_prompt, vec_len (cm->cli_prompt));
651 }
652
653 /** @brief Output a pager prompt and show number of buffered lines */
654 static void
655 unix_cli_pager_prompt (unix_cli_file_t * cf, unix_file_t * uf)
656 {
657   u8 *prompt;
658   u32 h;
659
660   h = cf->pager_start + (cf->height - 1);
661   if (h > vec_len (cf->pager_index))
662     h = vec_len (cf->pager_index);
663
664   prompt = format (0, "\r%s-- more -- (%d-%d/%d)%s",
665                    cf->ansi_capable ? ANSI_BOLD : "",
666                    cf->pager_start + 1,
667                    h,
668                    vec_len (cf->pager_index),
669                    cf->ansi_capable ? ANSI_RESET : "");
670
671   unix_vlib_cli_output_cooked (cf, uf, prompt, vec_len (prompt));
672
673   vec_free (prompt);
674 }
675
676 /** @brief Output a pager "skipping" message */
677 static void
678 unix_cli_pager_message (unix_cli_file_t * cf, unix_file_t * uf,
679                         char *message, char *postfix)
680 {
681   u8 *prompt;
682
683   prompt = format (0, "\r%s-- %s --%s%s",
684                    cf->ansi_capable ? ANSI_BOLD : "",
685                    message, cf->ansi_capable ? ANSI_RESET : "", postfix);
686
687   unix_vlib_cli_output_cooked (cf, uf, prompt, vec_len (prompt));
688
689   vec_free (prompt);
690 }
691
692 /** @brief Erase the printed pager prompt */
693 static void
694 unix_cli_pager_prompt_erase (unix_cli_file_t * cf, unix_file_t * uf)
695 {
696   if (cf->ansi_capable)
697     {
698       unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\r", 1);
699       unix_vlib_cli_output_cooked (cf, uf,
700                                    (u8 *) ANSI_CLEARLINE,
701                                    sizeof (ANSI_CLEARLINE) - 1);
702     }
703   else
704     {
705       int i;
706
707       unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\r", 1);
708       for (i = 0; i < cf->width - 1; i++)
709         unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1);
710       unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\r", 1);
711     }
712 }
713
714 /** @brief Uses an ANSI escape sequence to move the cursor */
715 static void
716 unix_cli_ansi_cursor (unix_cli_file_t * cf, unix_file_t * uf, u16 x, u16 y)
717 {
718   u8 *str;
719
720   str = format (0, "%s%d;%dH", CSI, y, x);
721
722   unix_vlib_cli_output_cooked (cf, uf, str, vec_len (str));
723
724   vec_free (str);
725 }
726
727 /** Redraw the currently displayed page of text.
728  * @param cf CLI session to redraw the pager buffer of.
729  * @param uf Unix file of the CLI session.
730  */
731 static void
732 unix_cli_pager_redraw (unix_cli_file_t * cf, unix_file_t * uf)
733 {
734   unix_cli_pager_index_t *pi = NULL;
735   u8 *line = NULL;
736   word i;
737
738   /* No active pager? Do nothing. */
739   if (!vec_len (cf->pager_index))
740     return;
741
742   if (cf->ansi_capable)
743     {
744       /* If we have ANSI, send the clear screen sequence */
745       unix_vlib_cli_output_cooked (cf, uf,
746                                    (u8 *) ANSI_CLEAR,
747                                    sizeof (ANSI_CLEAR) - 1);
748     }
749   else
750     {
751       /* Otherwise make sure we're on a blank line */
752       unix_cli_pager_prompt_erase (cf, uf);
753     }
754
755   /* (Re-)send the current page of content */
756   for (i = 0; i < cf->height - 1 &&
757        i + cf->pager_start < vec_len (cf->pager_index); i++)
758     {
759       pi = &cf->pager_index[cf->pager_start + i];
760       line = cf->pager_vector[pi->line] + pi->offset;
761
762       unix_vlib_cli_output_cooked (cf, uf, line, pi->length);
763     }
764   /* if the last line didn't end in newline, add a newline */
765   if (pi && line[pi->length - 1] != '\n')
766     unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1);
767
768   unix_cli_pager_prompt (cf, uf);
769 }
770
771 /** @brief Process and add a line to the pager index.
772  * In normal operation this function will take the given character string
773  * found in @c line and with length @c len_or_index and iterates the over the
774  * contents, adding each line of text discovered within it to the
775  * pager index. Lines are identified by newlines ("<code>\\n</code>") and by
776  * strings longer than the width of the terminal.
777  *
778  * If instead @c line is @c NULL then @c len_or_index is taken to mean the
779  * index of an existing line in the pager buffer; this simply means that the
780  * input line does not need to be cloned since we alreayd have it. This is
781  * typical if we are reindexing the pager buffer.
782  *
783  * @param cf           The CLI session whose pager we are adding to.
784  * @param line         The string of text to be indexed into the pager buffer.
785  *                     If @c line is @c NULL then the mode of operation
786  *                     changes slightly; see the description above.
787  * @param len_or_index If @c line is a pointer to a string then this parameter
788  *                     indicates the length of that string; Otherwise this
789  *                     value provides the index in the pager buffer of an
790  *                     existing string to be indexed.
791  */
792 static void
793 unix_cli_pager_add_line (unix_cli_file_t * cf, u8 * line, word len_or_index)
794 {
795   u8 *p;
796   word i, j, k;
797   word line_index, len;
798   u32 width = cf->width;
799   unix_cli_pager_index_t *pi;
800
801   if (line == NULL)
802     {
803       /* Use a line already in the pager buffer */
804       line_index = len_or_index;
805       p = cf->pager_vector[line_index];
806       len = vec_len (p);
807     }
808   else
809     {
810       len = len_or_index;
811       /* Add a copy of the raw string to the pager buffer */
812       p = vec_new (u8, len);
813       clib_memcpy (p, line, len);
814
815       /* store in pager buffer */
816       line_index = vec_len (cf->pager_vector);
817       vec_add1 (cf->pager_vector, p);
818     }
819
820   i = 0;
821   while (i < len)
822     {
823       /* Find the next line, or run to terminal width, or run to EOL */
824       int l = len - i;
825       j = unix_vlib_findchr ((u8) '\n', p, l < width ? l : width);
826
827       if (j < l && p[j] == '\n')        /* incl \n */
828         j++;
829
830       /* Add the line to the index */
831       k = vec_len (cf->pager_index);
832       vec_validate (cf->pager_index, k);
833       pi = &cf->pager_index[k];
834
835       pi->line = line_index;
836       pi->offset = i;
837       pi->length = j;
838
839       i += j;
840       p += j;
841     }
842 }
843
844 /** @brief Reindex entire pager buffer.
845  * Resets the current pager index and then re-adds the lines in the pager
846  * buffer to the index.
847  *
848  * Additionally this function attempts to retain the current page start
849  * line offset by searching for the same top-of-screen line in the new index.
850  *
851  * @param cf The CLI session whose pager buffer should be reindexed.
852  */
853 static void
854 unix_cli_pager_reindex (unix_cli_file_t * cf)
855 {
856   word i, old_line, old_offset;
857   unix_cli_pager_index_t *pi;
858
859   /* If there is nothing in the pager buffer then make sure the index
860    * is empty and move on.
861    */
862   if (cf->pager_vector == 0)
863     {
864       vec_reset_length (cf->pager_index);
865       return;
866     }
867
868   /* Retain a pointer to the current page start line so we can
869    * find it later
870    */
871   pi = &cf->pager_index[cf->pager_start];
872   old_line = pi->line;
873   old_offset = pi->offset;
874
875   /* Re-add the buffered lines to the index */
876   vec_reset_length (cf->pager_index);
877   vec_foreach_index (i, cf->pager_vector)
878   {
879     unix_cli_pager_add_line (cf, NULL, i);
880   }
881
882   /* Attempt to re-locate the previously stored page start line */
883   vec_foreach_index (i, cf->pager_index)
884   {
885     pi = &cf->pager_index[i];
886
887     if (pi->line == old_line &&
888         (pi->offset <= old_offset || pi->offset + pi->length > old_offset))
889       {
890         /* Found it! */
891         cf->pager_start = i;
892         break;
893       }
894   }
895
896   /* In case the start line was not found (rare), ensure the pager start
897    * index is within bounds
898    */
899   if (cf->pager_start >= vec_len (cf->pager_index))
900     {
901       if (!cf->height || vec_len (cf->pager_index) < (cf->height - 1))
902         cf->pager_start = 0;
903       else
904         cf->pager_start = vec_len (cf->pager_index) - (cf->height - 1);
905     }
906 }
907
908 /** VLIB CLI output function.
909  *
910  * If the terminal has a pager configured then this function takes care
911  * of collating output into the pager buffer; ensuring only the first page
912  * is displayed and any lines in excess of the first page are buffered.
913  *
914  * If the maximum number of index lines in the buffer is exceeded then the
915  * pager is cancelled and the contents of the current buffer are sent to the
916  * terminal.
917  *
918  * If there is no pager configured then the output is sent directly to the
919  * terminal.
920  *
921  * @param cli_file_index Index of the CLI session where this output is
922  *                       directed.
923  * @param buffer         String of printabe bytes to be output.
924  * @param buffer_bytes   The number of bytes in @c buffer to be output.
925  */
926 static void
927 unix_vlib_cli_output (uword cli_file_index, u8 * buffer, uword buffer_bytes)
928 {
929   unix_main_t *um = &unix_main;
930   unix_cli_main_t *cm = &unix_cli_main;
931   unix_cli_file_t *cf;
932   unix_file_t *uf;
933
934   cf = pool_elt_at_index (cm->cli_file_pool, cli_file_index);
935   uf = pool_elt_at_index (um->file_pool, cf->unix_file_index);
936
937   if (cf->no_pager || um->cli_pager_buffer_limit == 0 || cf->height == 0)
938     {
939       unix_vlib_cli_output_cooked (cf, uf, buffer, buffer_bytes);
940     }
941   else
942     {
943       word row = vec_len (cf->pager_index);
944       u8 *line;
945       unix_cli_pager_index_t *pi;
946
947       /* Index and add the output lines to the pager buffer. */
948       unix_cli_pager_add_line (cf, buffer, buffer_bytes);
949
950       /* Now iterate what was added to display the lines.
951        * If we reach the bottom of the page, display a prompt.
952        */
953       while (row < vec_len (cf->pager_index))
954         {
955           if (row < cf->height - 1)
956             {
957               /* output this line */
958               pi = &cf->pager_index[row];
959               line = cf->pager_vector[pi->line] + pi->offset;
960               unix_vlib_cli_output_cooked (cf, uf, line, pi->length);
961
962               /* if the last line didn't end in newline, and we're at the
963                * bottom of the page, add a newline */
964               if (line[pi->length - 1] != '\n' && row == cf->height - 2)
965                 unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1);
966             }
967           else
968             {
969               /* Display the pager prompt every 10 lines */
970               if (!(row % 10))
971                 unix_cli_pager_prompt (cf, uf);
972             }
973           row++;
974         }
975
976       /* Check if we went over the pager buffer limit */
977       if (vec_len (cf->pager_index) > um->cli_pager_buffer_limit)
978         {
979           /* Stop using the pager for the remainder of this CLI command */
980           cf->no_pager = 2;
981
982           /* If we likely printed the prompt, erase it */
983           if (vec_len (cf->pager_index) > cf->height - 1)
984             unix_cli_pager_prompt_erase (cf, uf);
985
986           /* Dump out the contents of the buffer */
987           for (row = cf->pager_start + (cf->height - 1);
988                row < vec_len (cf->pager_index); row++)
989             {
990               pi = &cf->pager_index[row];
991               line = cf->pager_vector[pi->line] + pi->offset;
992               unix_vlib_cli_output_cooked (cf, uf, line, pi->length);
993             }
994
995           unix_cli_pager_reset (cf);
996         }
997     }
998 }
999
1000 /** Identify whether a terminal type is ANSI capable.
1001  *
1002  * Compares the string given in @c term with a list of terminal types known
1003  * to support ANSI escape sequences.
1004  *
1005  * This list contains, for example, @c xterm, @c screen and @c ansi.
1006  *
1007  * @param term A string with a terminal type in it.
1008  * @param len The length of the string in @c term.
1009  *
1010  * @return @c 1 if the terminal type is recognized as supporting ANSI
1011  *         terminal sequences; @c 0 otherwise.
1012  */
1013 static u8
1014 unix_cli_terminal_type (u8 * term, uword len)
1015 {
1016   /* This may later be better done as a hash of some sort. */
1017 #define _(a) do { \
1018     if (strncasecmp(a, (char *)term, (size_t)len) == 0) return 1; \
1019   } while(0)
1020
1021   _("xterm");
1022   _("xterm-color");
1023   _("xterm-256color");          /* iTerm on Mac */
1024   _("screen");
1025   _("ansi");                    /* Microsoft Telnet */
1026 #undef _
1027
1028   return 0;
1029 }
1030
1031 /** @brief Emit initial welcome banner and prompt on a connection. */
1032 static void
1033 unix_cli_file_welcome (unix_cli_main_t * cm, unix_cli_file_t * cf)
1034 {
1035   unix_main_t *um = &unix_main;
1036   unix_file_t *uf = pool_elt_at_index (um->file_pool, cf->unix_file_index);
1037   unix_cli_banner_t *banner;
1038   int i, len;
1039
1040   /*
1041    * Put the first bytes directly into the buffer so that further output is
1042    * queued until everything is ready. (oterwise initial prompt can appear
1043    * mid way through VPP initialization)
1044    */
1045   unix_cli_add_pending_output (uf, cf, (u8 *) "\r", 1);
1046
1047   if (!um->cli_no_banner)
1048     {
1049       if (cf->ansi_capable)
1050         {
1051           banner = unix_cli_banner_color;
1052           len = ARRAY_LEN (unix_cli_banner_color);
1053         }
1054       else
1055         {
1056           banner = unix_cli_banner;
1057           len = ARRAY_LEN (unix_cli_banner);
1058         }
1059
1060       for (i = 0; i < len; i++)
1061         {
1062           unix_vlib_cli_output_cooked (cf, uf,
1063                                        banner[i].line, banner[i].length);
1064         }
1065     }
1066
1067   /* Prompt. */
1068   unix_cli_cli_prompt (cf, uf);
1069
1070   cf->started = 1;
1071 }
1072
1073 /** @brief A failsafe triggered on a timer to ensure we send the prompt
1074  * to telnet sessions that fail to negotiate the terminal type. */
1075 static void
1076 unix_cli_file_welcome_timer (any arg, f64 delay)
1077 {
1078   unix_cli_main_t *cm = &unix_cli_main;
1079   unix_cli_file_t *cf;
1080   (void) delay;
1081
1082   /* Check the connection didn't close already */
1083   if (pool_is_free_index (cm->cli_file_pool, (uword) arg))
1084     return;
1085
1086   cf = pool_elt_at_index (cm->cli_file_pool, (uword) arg);
1087
1088   if (!cf->started)
1089     unix_cli_file_welcome (cm, cf);
1090 }
1091
1092 /** @brief A mostly no-op Telnet state machine.
1093  * Process Telnet command bytes in a way that ensures we're mostly
1094  * transparent to the Telnet protocol. That is, it's mostly a no-op.
1095  *
1096  * @return -1 if we need more bytes, otherwise a positive integer number of
1097  *          bytes to consume from the input_vector, not including the initial
1098  *          IAC byte.
1099  */
1100 static i32
1101 unix_cli_process_telnet (unix_main_t * um,
1102                          unix_cli_file_t * cf,
1103                          unix_file_t * uf, u8 * input_vector, uword len)
1104 {
1105   /* Input_vector starts at IAC byte.
1106    * See if we have a complete message; if not, return -1 so we wait for more.
1107    * if we have a complete message, consume those bytes from the vector.
1108    */
1109   i32 consume = 0;
1110
1111   if (len == 1)
1112     return -1;                  /* want more bytes */
1113
1114   switch (input_vector[1])
1115     {
1116     case IAC:
1117       /* two IAC's in a row means to pass through 0xff.
1118        * since that makes no sense here, just consume it.
1119        */
1120       consume = 1;
1121       break;
1122
1123     case WILL:
1124     case WONT:
1125     case DO:
1126     case DONT:
1127       /* Expect 3 bytes */
1128       if (vec_len (input_vector) < 3)
1129         return -1;              /* want more bytes */
1130
1131       consume = 2;
1132       break;
1133
1134     case SB:
1135       {
1136         /* Sub option - search ahead for IAC SE to end it */
1137         i32 i;
1138         for (i = 3; i < len && i < UNIX_CLI_MAX_DEPTH_TELNET; i++)
1139           {
1140             if (input_vector[i - 1] == IAC && input_vector[i] == SE)
1141               {
1142                 /* We have a complete message; see if we care about it */
1143                 switch (input_vector[2])
1144                   {
1145                   case TELOPT_TTYPE:
1146                     if (input_vector[3] != 0)
1147                       break;
1148                     /* See if the terminal type is ANSI capable */
1149                     cf->ansi_capable =
1150                       unix_cli_terminal_type (input_vector + 4, i - 5);
1151                     /* If session not started, we can release the pause */
1152                     if (!cf->started)
1153                       /* Send the welcome banner and initial prompt */
1154                       unix_cli_file_welcome (&unix_cli_main, cf);
1155                     break;
1156
1157                   case TELOPT_NAWS:
1158                     /* Window size */
1159                     if (i != 8) /* check message is correct size */
1160                       break;
1161                     cf->width =
1162                       clib_net_to_host_u16 (*((u16 *) (input_vector + 3)));
1163                     cf->height =
1164                       clib_net_to_host_u16 (*((u16 *) (input_vector + 5)));
1165                     /* reindex pager buffer */
1166                     unix_cli_pager_reindex (cf);
1167                     /* redraw page */
1168                     unix_cli_pager_redraw (cf, uf);
1169                     break;
1170
1171                   default:
1172                     break;
1173                   }
1174                 /* Consume it all */
1175                 consume = i;
1176                 break;
1177               }
1178           }
1179
1180         if (i == UNIX_CLI_MAX_DEPTH_TELNET)
1181           consume = 1;          /* hit max search depth, advance one byte */
1182
1183         if (consume == 0)
1184           return -1;            /* want more bytes */
1185
1186         break;
1187       }
1188
1189     case GA:
1190     case EL:
1191     case EC:
1192     case AO:
1193     case IP:
1194     case BREAK:
1195     case DM:
1196     case NOP:
1197     case SE:
1198     case EOR:
1199     case ABORT:
1200     case SUSP:
1201     case xEOF:
1202       /* Simple one-byte messages */
1203       consume = 1;
1204       break;
1205
1206     case AYT:
1207       /* Are You There - trigger a visible response */
1208       consume = 1;
1209       unix_vlib_cli_output_cooked (cf, uf, (u8 *) "fd.io VPP\n", 10);
1210       break;
1211
1212     default:
1213       /* Unknown command! Eat the IAC byte */
1214       break;
1215     }
1216
1217   return consume;
1218 }
1219
1220 /** @brief Process actionable input.
1221  * Based on the \c action process the input; this typically involves
1222  * searching the command history or editing the current command line.
1223  */
1224 static int
1225 unix_cli_line_process_one (unix_cli_main_t * cm,
1226                            unix_main_t * um,
1227                            unix_cli_file_t * cf,
1228                            unix_file_t * uf,
1229                            u8 input, unix_cli_parse_action_t action)
1230 {
1231   u8 *prev;
1232   int j, delta;
1233
1234   switch (action)
1235     {
1236     case UNIX_CLI_PARSE_ACTION_NOACTION:
1237       break;
1238
1239     case UNIX_CLI_PARSE_ACTION_REVSEARCH:
1240     case UNIX_CLI_PARSE_ACTION_FWDSEARCH:
1241       if (!cf->has_history || !cf->history_limit)
1242         break;
1243       if (cf->search_mode == 0)
1244         {
1245           /* Erase the current command (if any) */
1246           for (j = 0; j < (vec_len (cf->current_command)); j++)
1247             unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b \b", 3);
1248
1249           vec_reset_length (cf->search_key);
1250           vec_reset_length (cf->current_command);
1251           if (action == UNIX_CLI_PARSE_ACTION_REVSEARCH)
1252             cf->search_mode = -1;
1253           else
1254             cf->search_mode = 1;
1255           cf->cursor = 0;
1256         }
1257       else
1258         {
1259           if (action == UNIX_CLI_PARSE_ACTION_REVSEARCH)
1260             cf->search_mode = -1;
1261           else
1262             cf->search_mode = 1;
1263
1264           cf->excursion += cf->search_mode;
1265           goto search_again;
1266         }
1267       break;
1268
1269     case UNIX_CLI_PARSE_ACTION_ERASELINELEFT:
1270       /* Erase the command from the cursor to the start */
1271
1272       /* Shimmy forwards to the new end of line position */
1273       delta = vec_len (cf->current_command) - cf->cursor;
1274       for (j = cf->cursor; j > delta; j--)
1275         unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
1276       /* Zap from here to the end of what is currently displayed */
1277       for (; j < (vec_len (cf->current_command)); j++)
1278         unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1);
1279       /* Get back to the start of the line */
1280       for (j = 0; j < (vec_len (cf->current_command)); j++)
1281         unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
1282
1283       j = vec_len (cf->current_command) - cf->cursor;
1284       memmove (cf->current_command, cf->current_command + cf->cursor, j);
1285       _vec_len (cf->current_command) = j;
1286
1287       /* Print the new contents */
1288       unix_vlib_cli_output_cooked (cf, uf, cf->current_command, j);
1289       /* Shimmy back to the start */
1290       for (j = 0; j < (vec_len (cf->current_command)); j++)
1291         unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
1292       cf->cursor = 0;
1293
1294       cf->search_mode = 0;
1295       break;
1296
1297     case UNIX_CLI_PARSE_ACTION_ERASELINERIGHT:
1298       /* Erase the command from the cursor to the end */
1299
1300       /* Zap from cursor to end of what is currently displayed */
1301       for (j = cf->cursor; j < (vec_len (cf->current_command)); j++)
1302         unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1);
1303       /* Get back to where we were */
1304       for (j = cf->cursor; j < (vec_len (cf->current_command)); j++)
1305         unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
1306
1307       /* Truncate the line at the cursor */
1308       _vec_len (cf->current_command) = cf->cursor;
1309
1310       cf->search_mode = 0;
1311       break;
1312
1313     case UNIX_CLI_PARSE_ACTION_LEFT:
1314       if (cf->cursor > 0)
1315         {
1316           unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
1317           cf->cursor--;
1318         }
1319
1320       cf->search_mode = 0;
1321       break;
1322
1323     case UNIX_CLI_PARSE_ACTION_RIGHT:
1324       if (cf->cursor < vec_len (cf->current_command))
1325         {
1326           /* have to emit the character under the cursor */
1327           unix_vlib_cli_output_cooked (cf, uf,
1328                                        cf->current_command + cf->cursor, 1);
1329           cf->cursor++;
1330         }
1331
1332       cf->search_mode = 0;
1333       break;
1334
1335     case UNIX_CLI_PARSE_ACTION_UP:
1336     case UNIX_CLI_PARSE_ACTION_DOWN:
1337       if (!cf->has_history || !cf->history_limit)
1338         break;
1339       cf->search_mode = 0;
1340       /* Erase the command */
1341       for (j = cf->cursor; j < (vec_len (cf->current_command)); j++)
1342         unix_vlib_cli_output_cooked (cf, uf, (u8 *) " ", 1);
1343       for (j = 0; j < (vec_len (cf->current_command)); j++)
1344         unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b \b", 3);
1345       vec_reset_length (cf->current_command);
1346       if (vec_len (cf->command_history))
1347         {
1348           if (action == UNIX_CLI_PARSE_ACTION_UP)
1349             delta = -1;
1350           else
1351             delta = 1;
1352
1353           cf->excursion += delta;
1354
1355           if (cf->excursion == vec_len (cf->command_history))
1356             {
1357               /* down-arrowed to last entry - want a blank line */
1358               _vec_len (cf->current_command) = 0;
1359             }
1360           else if (cf->excursion < 0)
1361             {
1362               /* up-arrowed over the start to the end, want a blank line */
1363               cf->excursion = vec_len (cf->command_history);
1364               _vec_len (cf->current_command) = 0;
1365             }
1366           else
1367             {
1368               if (cf->excursion > (i32) vec_len (cf->command_history) - 1)
1369                 /* down-arrowed past end - wrap to start */
1370                 cf->excursion = 0;
1371
1372               /* Print the command at the current position */
1373               prev = cf->command_history[cf->excursion];
1374               vec_validate (cf->current_command, vec_len (prev) - 1);
1375
1376               clib_memcpy (cf->current_command, prev, vec_len (prev));
1377               _vec_len (cf->current_command) = vec_len (prev);
1378               unix_vlib_cli_output_cooked (cf, uf, cf->current_command,
1379                                            vec_len (cf->current_command));
1380             }
1381           cf->cursor = vec_len (cf->current_command);
1382
1383           break;
1384         }
1385       break;
1386
1387     case UNIX_CLI_PARSE_ACTION_HOME:
1388       if (vec_len (cf->current_command) && cf->cursor > 0)
1389         {
1390           while (cf->cursor)
1391             {
1392               unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
1393               cf->cursor--;
1394             }
1395         }
1396
1397       cf->search_mode = 0;
1398       break;
1399
1400     case UNIX_CLI_PARSE_ACTION_END:
1401       if (vec_len (cf->current_command) &&
1402           cf->cursor < vec_len (cf->current_command))
1403         {
1404           unix_vlib_cli_output_cooked (cf, uf,
1405                                        cf->current_command + cf->cursor,
1406                                        vec_len (cf->current_command) -
1407                                        cf->cursor);
1408           cf->cursor = vec_len (cf->current_command);
1409         }
1410
1411       cf->search_mode = 0;
1412       break;
1413
1414     case UNIX_CLI_PARSE_ACTION_WORDLEFT:
1415       if (vec_len (cf->current_command) && cf->cursor > 0)
1416         {
1417           j = cf->cursor;
1418
1419           unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
1420           j--;
1421
1422           while (j && isspace (cf->current_command[j]))
1423             {
1424               unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
1425               j--;
1426             }
1427           while (j && !isspace (cf->current_command[j]))
1428             {
1429               if (isspace (cf->current_command[j - 1]))
1430                 break;
1431               unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
1432               j--;
1433             }
1434
1435           cf->cursor = j;
1436         }
1437
1438       cf->search_mode = 0;
1439       break;
1440
1441     case UNIX_CLI_PARSE_ACTION_WORDRIGHT:
1442       if (vec_len (cf->current_command) &&
1443           cf->cursor < vec_len (cf->current_command))
1444         {
1445           int e = vec_len (cf->current_command);
1446           j = cf->cursor;
1447           while (j < e && !isspace (cf->current_command[j]))
1448             j++;
1449           while (j < e && isspace (cf->current_command[j]))
1450             j++;
1451           unix_vlib_cli_output_cooked (cf, uf,
1452                                        cf->current_command + cf->cursor,
1453                                        j - cf->cursor);
1454           cf->cursor = j;
1455         }
1456
1457       cf->search_mode = 0;
1458       break;
1459
1460
1461     case UNIX_CLI_PARSE_ACTION_ERASE:
1462       if (vec_len (cf->current_command))
1463         {
1464           if (cf->cursor == vec_len (cf->current_command))
1465             {
1466               unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b \b", 3);
1467               _vec_len (cf->current_command)--;
1468               cf->cursor--;
1469             }
1470           else if (cf->cursor > 0)
1471             {
1472               /* shift everything at & to the right of the cursor left by 1 */
1473               j = vec_len (cf->current_command) - cf->cursor;
1474               memmove (cf->current_command + cf->cursor - 1,
1475                        cf->current_command + cf->cursor, j);
1476               _vec_len (cf->current_command)--;
1477               cf->cursor--;
1478               /* redraw the rest of the line */
1479               unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
1480               unix_vlib_cli_output_cooked (cf, uf,
1481                                            cf->current_command + cf->cursor,
1482                                            j);
1483               unix_vlib_cli_output_cooked (cf, uf, (u8 *) " \b\b", 3);
1484               /* and shift the terminal cursor back where it should be */
1485               while (--j)
1486                 unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
1487             }
1488         }
1489       cf->search_mode = 0;
1490       cf->excursion = 0;
1491       vec_reset_length (cf->search_key);
1492       break;
1493
1494     case UNIX_CLI_PARSE_ACTION_ERASERIGHT:
1495       if (vec_len (cf->current_command))
1496         {
1497           if (cf->cursor < vec_len (cf->current_command))
1498             {
1499               /* shift everything to the right of the cursor left by 1 */
1500               j = vec_len (cf->current_command) - cf->cursor - 1;
1501               memmove (cf->current_command + cf->cursor,
1502                        cf->current_command + cf->cursor + 1, j);
1503               _vec_len (cf->current_command)--;
1504               /* redraw the rest of the line */
1505               unix_vlib_cli_output_cooked (cf, uf,
1506                                            cf->current_command + cf->cursor,
1507                                            j);
1508               unix_vlib_cli_output_cooked (cf, uf, (u8 *) " \b", 2);
1509               /* and shift the terminal cursor back where it should be */
1510               if (j)
1511                 {
1512                   unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
1513                   while (--j)
1514                     unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
1515                 }
1516             }
1517         }
1518       else if (input == 'D' - '@')
1519         {
1520           /* ^D with no command entered = quit */
1521           unix_vlib_cli_output_cooked (cf, uf, (u8 *) "quit\n", 5);
1522           vlib_process_signal_event (um->vlib_main,
1523                                      vlib_current_process (um->vlib_main),
1524                                      UNIX_CLI_PROCESS_EVENT_QUIT,
1525                                      cf - cm->cli_file_pool);
1526         }
1527       cf->search_mode = 0;
1528       cf->excursion = 0;
1529       vec_reset_length (cf->search_key);
1530       break;
1531
1532     case UNIX_CLI_PARSE_ACTION_CLEAR:
1533       /* If we're in ANSI mode, clear the screen.
1534        * Then redraw the prompt and any existing command input, then put
1535        * the cursor back where it was in that line.
1536        */
1537       if (cf->ansi_capable)
1538         unix_vlib_cli_output_cooked (cf, uf,
1539                                      (u8 *) ANSI_CLEAR,
1540                                      sizeof (ANSI_CLEAR) - 1);
1541       else
1542         unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1);
1543
1544       unix_vlib_cli_output_raw (cf, uf,
1545                                 cm->cli_prompt, vec_len (cm->cli_prompt));
1546       unix_vlib_cli_output_raw (cf, uf,
1547                                 cf->current_command,
1548                                 vec_len (cf->current_command));
1549       for (j = cf->cursor; j < vec_len (cf->current_command); j++)
1550         unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b", 1);
1551
1552       break;
1553
1554     case UNIX_CLI_PARSE_ACTION_TAB:
1555     case UNIX_CLI_PARSE_ACTION_YANK:
1556       /* TODO */
1557       break;
1558
1559
1560     case UNIX_CLI_PARSE_ACTION_PAGER_QUIT:
1561     pager_quit:
1562       unix_cli_pager_prompt_erase (cf, uf);
1563       unix_cli_pager_reset (cf);
1564       unix_cli_cli_prompt (cf, uf);
1565       break;
1566
1567     case UNIX_CLI_PARSE_ACTION_PAGER_NEXT:
1568     case UNIX_CLI_PARSE_ACTION_PAGER_PGDN:
1569       /* show next page of the buffer */
1570       if (cf->height + cf->pager_start < vec_len (cf->pager_index))
1571         {
1572           u8 *line = NULL;
1573           unix_cli_pager_index_t *pi = NULL;
1574
1575           int m = cf->pager_start + (cf->height - 1);
1576           unix_cli_pager_prompt_erase (cf, uf);
1577           for (j = m;
1578                j < vec_len (cf->pager_index) && cf->pager_start < m;
1579                j++, cf->pager_start++)
1580             {
1581               pi = &cf->pager_index[j];
1582               line = cf->pager_vector[pi->line] + pi->offset;
1583               unix_vlib_cli_output_cooked (cf, uf, line, pi->length);
1584             }
1585           /* if the last line didn't end in newline, add a newline */
1586           if (pi && line[pi->length - 1] != '\n')
1587             unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1);
1588           unix_cli_pager_prompt (cf, uf);
1589         }
1590       else
1591         {
1592           if (action == UNIX_CLI_PARSE_ACTION_PAGER_NEXT)
1593             /* no more in buffer, exit, but only if it was <space> */
1594             goto pager_quit;
1595         }
1596       break;
1597
1598     case UNIX_CLI_PARSE_ACTION_PAGER_DN:
1599     case UNIX_CLI_PARSE_ACTION_PAGER_CRLF:
1600       /* display the next line of the buffer */
1601       if (cf->pager_start < vec_len (cf->pager_index) - (cf->height - 1))
1602         {
1603           u8 *line;
1604           unix_cli_pager_index_t *pi;
1605
1606           unix_cli_pager_prompt_erase (cf, uf);
1607           pi = &cf->pager_index[cf->pager_start + (cf->height - 1)];
1608           line = cf->pager_vector[pi->line] + pi->offset;
1609           unix_vlib_cli_output_cooked (cf, uf, line, pi->length);
1610           cf->pager_start++;
1611           /* if the last line didn't end in newline, add a newline */
1612           if (line[pi->length - 1] != '\n')
1613             unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1);
1614           unix_cli_pager_prompt (cf, uf);
1615         }
1616       else
1617         {
1618           if (action == UNIX_CLI_PARSE_ACTION_PAGER_CRLF)
1619             /* no more in buffer, exit, but only if it was <enter> */
1620             goto pager_quit;
1621         }
1622
1623       break;
1624
1625     case UNIX_CLI_PARSE_ACTION_PAGER_UP:
1626       /* scroll the page back one line */
1627       if (cf->pager_start > 0)
1628         {
1629           u8 *line = NULL;
1630           unix_cli_pager_index_t *pi = NULL;
1631
1632           cf->pager_start--;
1633           if (cf->ansi_capable)
1634             {
1635               pi = &cf->pager_index[cf->pager_start];
1636               line = cf->pager_vector[pi->line] + pi->offset;
1637               unix_cli_pager_prompt_erase (cf, uf);
1638               unix_vlib_cli_output_cooked (cf, uf, (u8 *) ANSI_SCROLLDN,
1639                                            sizeof (ANSI_SCROLLDN) - 1);
1640               unix_vlib_cli_output_cooked (cf, uf, (u8 *) ANSI_SAVECURSOR,
1641                                            sizeof (ANSI_SAVECURSOR) - 1);
1642               unix_cli_ansi_cursor (cf, uf, 1, 1);
1643               unix_vlib_cli_output_cooked (cf, uf, (u8 *) ANSI_CLEARLINE,
1644                                            sizeof (ANSI_CLEARLINE) - 1);
1645               unix_vlib_cli_output_cooked (cf, uf, line, pi->length);
1646               unix_vlib_cli_output_cooked (cf, uf, (u8 *) ANSI_RESTCURSOR,
1647                                            sizeof (ANSI_RESTCURSOR) - 1);
1648               unix_cli_pager_prompt_erase (cf, uf);
1649               unix_cli_pager_prompt (cf, uf);
1650             }
1651           else
1652             {
1653               int m = cf->pager_start + (cf->height - 1);
1654               unix_cli_pager_prompt_erase (cf, uf);
1655               for (j = cf->pager_start;
1656                    j < vec_len (cf->pager_index) && j < m; j++)
1657                 {
1658                   pi = &cf->pager_index[j];
1659                   line = cf->pager_vector[pi->line] + pi->offset;
1660                   unix_vlib_cli_output_cooked (cf, uf, line, pi->length);
1661                 }
1662               /* if the last line didn't end in newline, add a newline */
1663               if (pi && line[pi->length - 1] != '\n')
1664                 unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1);
1665               unix_cli_pager_prompt (cf, uf);
1666             }
1667         }
1668       break;
1669
1670     case UNIX_CLI_PARSE_ACTION_PAGER_TOP:
1671       /* back to the first page of the buffer */
1672       if (cf->pager_start > 0)
1673         {
1674           u8 *line = NULL;
1675           unix_cli_pager_index_t *pi = NULL;
1676
1677           cf->pager_start = 0;
1678           int m = cf->pager_start + (cf->height - 1);
1679           unix_cli_pager_prompt_erase (cf, uf);
1680           for (j = cf->pager_start; j < vec_len (cf->pager_index) && j < m;
1681                j++)
1682             {
1683               pi = &cf->pager_index[j];
1684               line = cf->pager_vector[pi->line] + pi->offset;
1685               unix_vlib_cli_output_cooked (cf, uf, line, pi->length);
1686             }
1687           /* if the last line didn't end in newline, add a newline */
1688           if (pi && line[pi->length - 1] != '\n')
1689             unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1);
1690           unix_cli_pager_prompt (cf, uf);
1691         }
1692       break;
1693
1694     case UNIX_CLI_PARSE_ACTION_PAGER_BOTTOM:
1695       /* skip to the last page of the buffer */
1696       if (cf->pager_start < vec_len (cf->pager_index) - (cf->height - 1))
1697         {
1698           u8 *line = NULL;
1699           unix_cli_pager_index_t *pi = NULL;
1700
1701           cf->pager_start = vec_len (cf->pager_index) - (cf->height - 1);
1702           unix_cli_pager_prompt_erase (cf, uf);
1703           unix_cli_pager_message (cf, uf, "skipping", "\n");
1704           for (j = cf->pager_start; j < vec_len (cf->pager_index); j++)
1705             {
1706               pi = &cf->pager_index[j];
1707               line = cf->pager_vector[pi->line] + pi->offset;
1708               unix_vlib_cli_output_cooked (cf, uf, line, pi->length);
1709             }
1710           /* if the last line didn't end in newline, add a newline */
1711           if (pi && line[pi->length - 1] != '\n')
1712             unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1);
1713           unix_cli_pager_prompt (cf, uf);
1714         }
1715       break;
1716
1717     case UNIX_CLI_PARSE_ACTION_PAGER_PGUP:
1718       /* wander back one page in the buffer */
1719       if (cf->pager_start > 0)
1720         {
1721           u8 *line = NULL;
1722           unix_cli_pager_index_t *pi = NULL;
1723           int m;
1724
1725           if (cf->pager_start >= cf->height)
1726             cf->pager_start -= cf->height - 1;
1727           else
1728             cf->pager_start = 0;
1729           m = cf->pager_start + cf->height - 1;
1730           unix_cli_pager_prompt_erase (cf, uf);
1731           for (j = cf->pager_start; j < vec_len (cf->pager_index) && j < m;
1732                j++)
1733             {
1734               pi = &cf->pager_index[j];
1735               line = cf->pager_vector[pi->line] + pi->offset;
1736               unix_vlib_cli_output_cooked (cf, uf, line, pi->length);
1737             }
1738           /* if the last line didn't end in newline, add a newline */
1739           if (pi && line[pi->length - 1] != '\n')
1740             unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1);
1741           unix_cli_pager_prompt (cf, uf);
1742         }
1743       break;
1744
1745     case UNIX_CLI_PARSE_ACTION_PAGER_REDRAW:
1746       /* Redraw the current pager screen */
1747       unix_cli_pager_redraw (cf, uf);
1748       break;
1749
1750     case UNIX_CLI_PARSE_ACTION_PAGER_SEARCH:
1751       /* search forwards in the buffer */
1752       break;
1753
1754
1755     case UNIX_CLI_PARSE_ACTION_CRLF:
1756     crlf:
1757       unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1);
1758
1759       if (cf->has_history && cf->history_limit)
1760         {
1761           if (cf->command_history
1762               && vec_len (cf->command_history) >= cf->history_limit)
1763             {
1764               vec_free (cf->command_history[0]);
1765               vec_delete (cf->command_history, 1, 0);
1766             }
1767           /* Don't add blank lines to the cmd history */
1768           if (vec_len (cf->current_command))
1769             {
1770               /* Don't duplicate the previous command */
1771               j = vec_len (cf->command_history);
1772               if (j == 0 ||
1773                   (vec_len (cf->current_command) !=
1774                    vec_len (cf->command_history[j - 1])
1775                    || memcmp (cf->current_command, cf->command_history[j - 1],
1776                               vec_len (cf->current_command)) != 0))
1777                 {
1778                   /* copy the command to the history */
1779                   u8 *c = 0;
1780                   vec_append (c, cf->current_command);
1781                   vec_add1 (cf->command_history, c);
1782                   cf->command_number++;
1783                 }
1784             }
1785           cf->excursion = vec_len (cf->command_history);
1786         }
1787
1788       cf->search_mode = 0;
1789       vec_reset_length (cf->search_key);
1790       cf->cursor = 0;
1791
1792       return 0;
1793
1794     case UNIX_CLI_PARSE_ACTION_PARTIALMATCH:
1795     case UNIX_CLI_PARSE_ACTION_NOMATCH:
1796       if (vec_len (cf->pager_index))
1797         {
1798           /* no-op for now */
1799         }
1800       else if (cf->has_history && cf->search_mode && isprint (input))
1801         {
1802           int k, limit, offset;
1803           u8 *item;
1804
1805           vec_add1 (cf->search_key, input);
1806
1807         search_again:
1808           for (j = 0; j < vec_len (cf->command_history); j++)
1809             {
1810               if (cf->excursion > (i32) vec_len (cf->command_history) - 1)
1811                 cf->excursion = 0;
1812               else if (cf->excursion < 0)
1813                 cf->excursion = vec_len (cf->command_history) - 1;
1814
1815               item = cf->command_history[cf->excursion];
1816
1817               limit = (vec_len (cf->search_key) > vec_len (item)) ?
1818                 vec_len (item) : vec_len (cf->search_key);
1819
1820               for (offset = 0; offset <= vec_len (item) - limit; offset++)
1821                 {
1822                   for (k = 0; k < limit; k++)
1823                     {
1824                       if (item[k + offset] != cf->search_key[k])
1825                         goto next_offset;
1826                     }
1827                   goto found_at_offset;
1828
1829                 next_offset:
1830                   ;
1831                 }
1832               goto next;
1833
1834             found_at_offset:
1835               for (j = 0; j < vec_len (cf->current_command); j++)
1836                 unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\b \b", 3);
1837
1838               vec_validate (cf->current_command, vec_len (item) - 1);
1839               clib_memcpy (cf->current_command, item, vec_len (item));
1840               _vec_len (cf->current_command) = vec_len (item);
1841
1842               unix_vlib_cli_output_cooked (cf, uf, cf->current_command,
1843                                            vec_len (cf->current_command));
1844               cf->cursor = vec_len (cf->current_command);
1845               goto found;
1846
1847             next:
1848               cf->excursion += cf->search_mode;
1849             }
1850
1851           unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\nNo match...", 12);
1852           vec_reset_length (cf->search_key);
1853           vec_reset_length (cf->current_command);
1854           cf->search_mode = 0;
1855           cf->cursor = 0;
1856           goto crlf;
1857         }
1858       else if (isprint (input)) /* skip any errant control codes */
1859         {
1860           if (cf->cursor == vec_len (cf->current_command))
1861             {
1862               /* Append to end */
1863               vec_add1 (cf->current_command, input);
1864               cf->cursor++;
1865
1866               /* Echo the character back to the client */
1867               unix_vlib_cli_output_raw (cf, uf, &input, 1);
1868             }
1869           else
1870             {
1871               /* Insert at cursor: resize +1 byte, move everything over */
1872               j = vec_len (cf->current_command) - cf->cursor;
1873               vec_add1 (cf->current_command, (u8) 'A');
1874               memmove (cf->current_command + cf->cursor + 1,
1875                        cf->current_command + cf->cursor, j);
1876               cf->current_command[cf->cursor] = input;
1877               /* Redraw the line */
1878               j++;
1879               unix_vlib_cli_output_raw (cf, uf,
1880                                         cf->current_command + cf->cursor, j);
1881               /* Put terminal cursor back */
1882               while (--j)
1883                 unix_vlib_cli_output_raw (cf, uf, (u8 *) "\b", 1);
1884               cf->cursor++;
1885             }
1886         }
1887       else
1888         {
1889           /* no-op - not printable or otherwise not actionable */
1890         }
1891
1892     found:
1893
1894       break;
1895
1896     case UNIX_CLI_PARSE_ACTION_TELNETIAC:
1897       break;
1898     }
1899   return 1;
1900 }
1901
1902 /** @brief Process input bytes on a stream to provide line editing and
1903  * command history in the CLI. */
1904 static int
1905 unix_cli_line_edit (unix_cli_main_t * cm,
1906                     unix_main_t * um, unix_cli_file_t * cf)
1907 {
1908   unix_file_t *uf = pool_elt_at_index (um->file_pool, cf->unix_file_index);
1909   int i;
1910
1911   for (i = 0; i < vec_len (cf->input_vector); i++)
1912     {
1913       unix_cli_parse_action_t action;
1914       i32 matched = 0;
1915       unix_cli_parse_actions_t *a;
1916
1917       /* If we're in the pager mode, search the pager actions */
1918       a =
1919         vec_len (cf->pager_index) ? unix_cli_parse_pager :
1920         unix_cli_parse_strings;
1921
1922       /* See if the input buffer is some sort of control code */
1923       action = unix_cli_match_action (a, &cf->input_vector[i],
1924                                       vec_len (cf->input_vector) - i,
1925                                       &matched);
1926
1927       switch (action)
1928         {
1929         case UNIX_CLI_PARSE_ACTION_PARTIALMATCH:
1930           if (i)
1931             {
1932               /* There was a partial match which means we need more bytes
1933                * than the input buffer currently has.
1934                * Since the bytes before here have been processed, shift
1935                * the remaining contents to the start of the input buffer.
1936                */
1937               vec_delete (cf->input_vector, i, 0);
1938             }
1939           return 1;             /* wait for more */
1940
1941         case UNIX_CLI_PARSE_ACTION_TELNETIAC:
1942           /* process telnet options */
1943           matched = unix_cli_process_telnet (um, cf, uf,
1944                                              cf->input_vector + i,
1945                                              vec_len (cf->input_vector) - i);
1946           if (matched < 0)
1947             {
1948               if (i)
1949                 {
1950                   /* There was a partial match which means we need more bytes
1951                    * than the input buffer currently has.
1952                    * Since the bytes before here have been processed, shift
1953                    * the remaining contents to the start of the input buffer.
1954                    */
1955                   vec_delete (cf->input_vector, i, 0);
1956                 }
1957               return 1;         /* wait for more */
1958             }
1959           break;
1960
1961         default:
1962           /* process the action */
1963           if (!unix_cli_line_process_one (cm, um, cf, uf,
1964                                           cf->input_vector[i], action))
1965             {
1966               /* CRLF found. Consume the bytes from the input_vector */
1967               vec_delete (cf->input_vector, i + matched, 0);
1968               /* And tell our caller to execute cf->input_command */
1969               return 0;
1970             }
1971         }
1972
1973       i += matched;
1974     }
1975
1976   vec_reset_length (cf->input_vector);
1977   return 1;
1978 }
1979
1980 /** @brief Process input to a CLI session. */
1981 static void
1982 unix_cli_process_input (unix_cli_main_t * cm, uword cli_file_index)
1983 {
1984   unix_main_t *um = &unix_main;
1985   unix_file_t *uf;
1986   unix_cli_file_t *cf = pool_elt_at_index (cm->cli_file_pool, cli_file_index);
1987   unformat_input_t input;
1988   int vlib_parse_eval (u8 *);
1989
1990 more:
1991   /* Try vlibplex first.  Someday... */
1992   if (0 && vlib_parse_eval (cf->input_vector) == 0)
1993     goto done;
1994
1995   if (cf->line_mode)
1996     {
1997       /* just treat whatever we got as a complete line of input */
1998       cf->current_command = cf->input_vector;
1999     }
2000   else
2001     {
2002       /* Line edit, echo, etc. */
2003       if (unix_cli_line_edit (cm, um, cf))
2004         /* want more input */
2005         return;
2006     }
2007
2008   if (um->log_fd)
2009     {
2010       static u8 *lv;
2011       vec_reset_length (lv);
2012       lv = format (lv, "%U[%d]: %v",
2013                    format_timeval, 0 /* current bat-time */ ,
2014                    0 /* current bat-format */ ,
2015                    cli_file_index, cf->input_vector);
2016       {
2017         int rv __attribute__ ((unused)) =
2018           write (um->log_fd, lv, vec_len (lv));
2019       }
2020     }
2021
2022   /* Copy our input command to a new string */
2023   unformat_init_vector (&input, cf->current_command);
2024
2025   /* Remove leading white space from input. */
2026   (void) unformat (&input, "");
2027
2028   cm->current_input_file_index = cli_file_index;
2029   cf->pager_start = 0;          /* start a new pager session */
2030
2031   if (unformat_check_input (&input) != UNFORMAT_END_OF_INPUT)
2032     vlib_cli_input (um->vlib_main, &input, unix_vlib_cli_output,
2033                     cli_file_index);
2034
2035   /* Zero buffer since otherwise unformat_free will call vec_free on it. */
2036   input.buffer = 0;
2037
2038   unformat_free (&input);
2039
2040   /* Re-fetch pointer since pool may have moved. */
2041   cf = pool_elt_at_index (cm->cli_file_pool, cli_file_index);
2042   uf = pool_elt_at_index (um->file_pool, cf->unix_file_index);
2043
2044 done:
2045   /* reset vector; we'll re-use it later  */
2046   if (cf->line_mode)
2047     vec_reset_length (cf->input_vector);
2048   else
2049     vec_reset_length (cf->current_command);
2050
2051   if (cf->no_pager == 2)
2052     {
2053       /* Pager was programmatically disabled */
2054       unix_cli_pager_message (cf, uf, "pager buffer overflowed", "\n");
2055       cf->no_pager = um->cli_no_pager;
2056     }
2057
2058   if (vec_len (cf->pager_index) == 0
2059       || vec_len (cf->pager_index) < cf->height)
2060     {
2061       /* There was no need for the pager */
2062       unix_cli_pager_reset (cf);
2063
2064       /* Prompt. */
2065       unix_cli_cli_prompt (cf, uf);
2066     }
2067   else
2068     {
2069       /* Display the pager prompt */
2070       unix_cli_pager_prompt (cf, uf);
2071     }
2072
2073   /* Any residual data in the input vector? */
2074   if (vec_len (cf->input_vector))
2075     goto more;
2076 }
2077
2078 /** Destroy a CLI session.
2079  * @note If we destroy the @c stdin session this additionally signals
2080  *       the shutdown of VPP.
2081  */
2082 static void
2083 unix_cli_kill (unix_cli_main_t * cm, uword cli_file_index)
2084 {
2085   unix_main_t *um = &unix_main;
2086   unix_cli_file_t *cf;
2087   unix_file_t *uf;
2088   int i;
2089
2090   cf = pool_elt_at_index (cm->cli_file_pool, cli_file_index);
2091   uf = pool_elt_at_index (um->file_pool, cf->unix_file_index);
2092
2093   /* Quit/EOF on stdin means quit program. */
2094   if (uf->file_descriptor == UNIX_CLI_STDIN_FD)
2095     clib_longjmp (&um->vlib_main->main_loop_exit, VLIB_MAIN_LOOP_EXIT_CLI);
2096
2097   vec_free (cf->current_command);
2098   vec_free (cf->search_key);
2099
2100   for (i = 0; i < vec_len (cf->command_history); i++)
2101     vec_free (cf->command_history[i]);
2102
2103   vec_free (cf->command_history);
2104
2105   unix_file_del (um, uf);
2106
2107   unix_cli_file_free (cf);
2108   pool_put (cm->cli_file_pool, cf);
2109 }
2110
2111 /** Handle system events. */
2112 static uword
2113 unix_cli_process (vlib_main_t * vm,
2114                   vlib_node_runtime_t * rt, vlib_frame_t * f)
2115 {
2116   unix_cli_main_t *cm = &unix_cli_main;
2117   uword i, *data = 0;
2118
2119   while (1)
2120     {
2121       unix_cli_process_event_type_t event_type;
2122       vlib_process_wait_for_event (vm);
2123       event_type = vlib_process_get_events (vm, &data);
2124
2125       switch (event_type)
2126         {
2127         case UNIX_CLI_PROCESS_EVENT_READ_READY:
2128           for (i = 0; i < vec_len (data); i++)
2129             unix_cli_process_input (cm, data[i]);
2130           break;
2131
2132         case UNIX_CLI_PROCESS_EVENT_QUIT:
2133           /* Kill this process. */
2134           for (i = 0; i < vec_len (data); i++)
2135             unix_cli_kill (cm, data[i]);
2136           goto done;
2137         }
2138
2139       if (data)
2140         _vec_len (data) = 0;
2141     }
2142
2143 done:
2144   vec_free (data);
2145
2146   vlib_node_set_state (vm, rt->node_index, VLIB_NODE_STATE_DISABLED);
2147
2148   /* Add node index so we can re-use this process later. */
2149   vec_add1 (cm->unused_cli_process_node_indices, rt->node_index);
2150
2151   return 0;
2152 }
2153
2154 /** Called when a CLI session file descriptor can be written to without
2155  * blocking. */
2156 static clib_error_t *
2157 unix_cli_write_ready (unix_file_t * uf)
2158 {
2159   unix_cli_main_t *cm = &unix_cli_main;
2160   unix_cli_file_t *cf;
2161   int n;
2162
2163   cf = pool_elt_at_index (cm->cli_file_pool, uf->private_data);
2164
2165   /* Flush output vector. */
2166   n = write (uf->file_descriptor,
2167              cf->output_vector, vec_len (cf->output_vector));
2168
2169   if (n < 0 && errno != EAGAIN)
2170     return clib_error_return_unix (0, "write");
2171
2172   else if (n > 0)
2173     unix_cli_del_pending_output (uf, cf, n);
2174
2175   return /* no error */ 0;
2176 }
2177
2178 /** Called when a CLI session file descriptor has data to be read. */
2179 static clib_error_t *
2180 unix_cli_read_ready (unix_file_t * uf)
2181 {
2182   unix_main_t *um = &unix_main;
2183   unix_cli_main_t *cm = &unix_cli_main;
2184   unix_cli_file_t *cf;
2185   uword l;
2186   int n, n_read, n_try;
2187
2188   cf = pool_elt_at_index (cm->cli_file_pool, uf->private_data);
2189
2190   n = n_try = 4096;
2191   while (n == n_try)
2192     {
2193       l = vec_len (cf->input_vector);
2194       vec_resize (cf->input_vector, l + n_try);
2195
2196       n = read (uf->file_descriptor, cf->input_vector + l, n_try);
2197
2198       /* Error? */
2199       if (n < 0 && errno != EAGAIN)
2200         return clib_error_return_unix (0, "read");
2201
2202       n_read = n < 0 ? 0 : n;
2203       _vec_len (cf->input_vector) = l + n_read;
2204     }
2205
2206   if (!(n < 0))
2207     vlib_process_signal_event (um->vlib_main,
2208                                cf->process_node_index,
2209                                (n_read == 0
2210                                 ? UNIX_CLI_PROCESS_EVENT_QUIT
2211                                 : UNIX_CLI_PROCESS_EVENT_READ_READY),
2212                                /* event data */ uf->private_data);
2213
2214   return /* no error */ 0;
2215 }
2216
2217 /** Store a new CLI session.
2218  * @param name The name of the session.
2219  * @param fd   The file descriptor for the session I/O.
2220  * @return The session ID.
2221  */
2222 static u32
2223 unix_cli_file_add (unix_cli_main_t * cm, char *name, int fd)
2224 {
2225   unix_main_t *um = &unix_main;
2226   unix_cli_file_t *cf;
2227   unix_file_t template = { 0 };
2228   vlib_main_t *vm = um->vlib_main;
2229   vlib_node_t *n;
2230
2231   name = (char *) format (0, "unix-cli-%s", name);
2232
2233   if (vec_len (cm->unused_cli_process_node_indices) > 0)
2234     {
2235       uword l = vec_len (cm->unused_cli_process_node_indices);
2236
2237       /* Find node and give it new name. */
2238       n = vlib_get_node (vm, cm->unused_cli_process_node_indices[l - 1]);
2239       vec_free (n->name);
2240       n->name = (u8 *) name;
2241
2242       vlib_node_set_state (vm, n->index, VLIB_NODE_STATE_POLLING);
2243
2244       _vec_len (cm->unused_cli_process_node_indices) = l - 1;
2245     }
2246   else
2247     {
2248       static vlib_node_registration_t r = {
2249         .function = unix_cli_process,
2250         .type = VLIB_NODE_TYPE_PROCESS,
2251         .process_log2_n_stack_bytes = 16,
2252       };
2253
2254       r.name = name;
2255       vlib_register_node (vm, &r);
2256       vec_free (name);
2257
2258       n = vlib_get_node (vm, r.index);
2259     }
2260
2261   pool_get (cm->cli_file_pool, cf);
2262   memset (cf, 0, sizeof (*cf));
2263
2264   template.read_function = unix_cli_read_ready;
2265   template.write_function = unix_cli_write_ready;
2266   template.file_descriptor = fd;
2267   template.private_data = cf - cm->cli_file_pool;
2268
2269   cf->process_node_index = n->index;
2270   cf->unix_file_index = unix_file_add (um, &template);
2271   cf->output_vector = 0;
2272   cf->input_vector = 0;
2273
2274   vlib_start_process (vm, n->runtime_index);
2275
2276   vlib_process_t *p = vlib_get_process_from_node (vm, n);
2277   p->output_function = unix_vlib_cli_output;
2278   p->output_function_arg = cf - cm->cli_file_pool;
2279
2280   return cf - cm->cli_file_pool;
2281 }
2282
2283 /** Telnet listening socket has a new connection. */
2284 static clib_error_t *
2285 unix_cli_listen_read_ready (unix_file_t * uf)
2286 {
2287   unix_main_t *um = &unix_main;
2288   unix_cli_main_t *cm = &unix_cli_main;
2289   clib_socket_t *s = &um->cli_listen_socket;
2290   clib_socket_t client;
2291   char *client_name;
2292   clib_error_t *error;
2293   unix_cli_file_t *cf;
2294   u32 cf_index;
2295
2296   error = clib_socket_accept (s, &client);
2297   if (error)
2298     return error;
2299
2300   client_name = (char *) format (0, "%U%c", format_sockaddr, &client.peer, 0);
2301
2302   cf_index = unix_cli_file_add (cm, client_name, client.fd);
2303   cf = pool_elt_at_index (cm->cli_file_pool, cf_index);
2304
2305   /* No longer need CLIB version of socket. */
2306   clib_socket_free (&client);
2307
2308   vec_free (client_name);
2309
2310   /* if we're supposed to run telnet session in character mode (default) */
2311   if (um->cli_line_mode == 0)
2312     {
2313       /*
2314        * Set telnet client character mode, echo on, suppress "go-ahead".
2315        * Technically these should be negotiated, but this works.
2316        */
2317       u8 charmode_option[] = {
2318         IAC, WONT, TELOPT_LINEMODE,     /* server will do char-by-char */
2319         IAC, DONT, TELOPT_LINEMODE,     /* client should do char-by-char */
2320         IAC, WILL, TELOPT_SGA,  /* server willl supress GA */
2321         IAC, DO, TELOPT_SGA,    /* client should supress Go Ahead */
2322         IAC, WILL, TELOPT_ECHO, /* server will do echo */
2323         IAC, DONT, TELOPT_ECHO, /* client should not echo */
2324         IAC, DO, TELOPT_TTYPE,  /* client should tell us its term type */
2325         IAC, SB, TELOPT_TTYPE, 1, IAC, SE,      /* now tell me ttype */
2326         IAC, DO, TELOPT_NAWS,   /* client should tell us its window sz */
2327         IAC, SB, TELOPT_NAWS, 1, IAC, SE,       /* now tell me window size */
2328       };
2329
2330       /* Enable history on this CLI */
2331       cf->history_limit = um->cli_history_limit;
2332       cf->has_history = cf->history_limit != 0;
2333
2334       /* Make sure this session is in line mode */
2335       cf->line_mode = 0;
2336
2337       /* We need CRLF */
2338       cf->crlf_mode = 1;
2339
2340       /* Setup the pager */
2341       cf->no_pager = um->cli_no_pager;
2342
2343       uf = pool_elt_at_index (um->file_pool, cf->unix_file_index);
2344
2345       /* Send the telnet options */
2346       unix_vlib_cli_output_raw (cf, uf, charmode_option,
2347                                 ARRAY_LEN (charmode_option));
2348
2349       /* In case the client doesn't negotiate terminal type, use
2350        * a timer to kick off the initial prompt. */
2351       timer_call (unix_cli_file_welcome_timer, cf_index, 1);
2352     }
2353
2354   return error;
2355 }
2356
2357 /** The system terminal has informed us that the window size
2358  * has changed.
2359  */
2360 static void
2361 unix_cli_resize_interrupt (int signum)
2362 {
2363   unix_main_t *um = &unix_main;
2364   unix_cli_main_t *cm = &unix_cli_main;
2365   unix_cli_file_t *cf = pool_elt_at_index (cm->cli_file_pool,
2366                                            cm->stdin_cli_file_index);
2367   unix_file_t *uf = pool_elt_at_index (um->file_pool, cf->unix_file_index);
2368   struct winsize ws;
2369   (void) signum;
2370
2371   /* Terminal resized, fetch the new size */
2372   if (ioctl (UNIX_CLI_STDIN_FD, TIOCGWINSZ, &ws) < 0)
2373     {
2374       /* "Should never happen..." */
2375       clib_unix_warning ("TIOCGWINSZ");
2376       /* We can't trust ws.XXX... */
2377       return;
2378     }
2379   cf->width = ws.ws_col;
2380   cf->height = ws.ws_row;
2381
2382   /* Reindex the pager buffer */
2383   unix_cli_pager_reindex (cf);
2384
2385   /* Redraw the page */
2386   unix_cli_pager_redraw (cf, uf);
2387 }
2388
2389 /** Handle configuration directives in the @em unix section. */
2390 static clib_error_t *
2391 unix_cli_config (vlib_main_t * vm, unformat_input_t * input)
2392 {
2393   unix_main_t *um = &unix_main;
2394   unix_cli_main_t *cm = &unix_cli_main;
2395   int flags;
2396   clib_error_t *error = 0;
2397   unix_cli_file_t *cf;
2398   u32 cf_index;
2399   struct termios tio;
2400   struct sigaction sa;
2401   struct winsize ws;
2402   u8 *term;
2403
2404   /* We depend on unix flags being set. */
2405   if ((error = vlib_call_config_function (vm, unix_config)))
2406     return error;
2407
2408   if (um->flags & UNIX_FLAG_INTERACTIVE)
2409     {
2410       /* Set stdin to be non-blocking. */
2411       if ((flags = fcntl (UNIX_CLI_STDIN_FD, F_GETFL, 0)) < 0)
2412         flags = 0;
2413       (void) fcntl (UNIX_CLI_STDIN_FD, F_SETFL, flags | O_NONBLOCK);
2414
2415       cf_index = unix_cli_file_add (cm, "stdin", UNIX_CLI_STDIN_FD);
2416       cf = pool_elt_at_index (cm->cli_file_pool, cf_index);
2417       cm->stdin_cli_file_index = cf_index;
2418
2419       /* If stdin is a tty and we are using chacracter mode, enable
2420        * history on the CLI and set the tty line discipline accordingly. */
2421       if (isatty (UNIX_CLI_STDIN_FD) && um->cli_line_mode == 0)
2422         {
2423           /* Capture terminal resize events */
2424           memset (&sa, 0, sizeof (sa));
2425           sa.sa_handler = unix_cli_resize_interrupt;
2426           if (sigaction (SIGWINCH, &sa, 0) < 0)
2427             clib_panic ("sigaction");
2428
2429           /* Retrieve the current terminal size */
2430           ioctl (UNIX_CLI_STDIN_FD, TIOCGWINSZ, &ws);
2431           cf->width = ws.ws_col;
2432           cf->height = ws.ws_row;
2433
2434           if (cf->width == 0 || cf->height == 0)
2435             /* We have a tty, but no size. Stick to line mode. */
2436             goto notty;
2437
2438           /* Setup the history */
2439           cf->history_limit = um->cli_history_limit;
2440           cf->has_history = cf->history_limit != 0;
2441
2442           /* Setup the pager */
2443           cf->no_pager = um->cli_no_pager;
2444
2445           /* We're going to be in char by char mode */
2446           cf->line_mode = 0;
2447
2448           /* Save the original tty state so we can restore it later */
2449           tcgetattr (UNIX_CLI_STDIN_FD, &um->tio_stdin);
2450           um->tio_isset = 1;
2451
2452           /* Tweak the tty settings */
2453           tio = um->tio_stdin;
2454           /* echo off, canonical mode off, ext'd input processing off */
2455           tio.c_lflag &= ~(ECHO | ICANON | IEXTEN);
2456           tio.c_cc[VMIN] = 1;   /* 1 byte at a time */
2457           tio.c_cc[VTIME] = 0;  /* no timer */
2458           tcsetattr (UNIX_CLI_STDIN_FD, TCSAFLUSH, &tio);
2459
2460           /* See if we can do ANSI/VT100 output */
2461           term = (u8 *) getenv ("TERM");
2462           if (term != NULL)
2463             cf->ansi_capable = unix_cli_terminal_type (term,
2464                                                        strlen ((char *)
2465                                                                term));
2466         }
2467       else
2468         {
2469         notty:
2470           /* No tty, so make sure these things are off */
2471           cf->no_pager = 1;
2472           cf->history_limit = 0;
2473           cf->has_history = 0;
2474           cf->line_mode = 1;
2475         }
2476
2477       /* Send banner and initial prompt */
2478       unix_cli_file_welcome (cm, cf);
2479     }
2480
2481   /* If we have socket config, LISTEN, otherwise, don't */
2482   clib_socket_t *s = &um->cli_listen_socket;
2483   if (s->config && s->config[0] != 0)
2484     {
2485       /* CLI listen. */
2486       unix_file_t template = { 0 };
2487
2488       s->flags = SOCKET_IS_SERVER;      /* listen, don't connect */
2489       error = clib_socket_init (s);
2490
2491       if (error)
2492         return error;
2493
2494       template.read_function = unix_cli_listen_read_ready;
2495       template.file_descriptor = s->fd;
2496
2497       unix_file_add (um, &template);
2498     }
2499
2500   /* Set CLI prompt. */
2501   if (!cm->cli_prompt)
2502     cm->cli_prompt = format (0, "VLIB: ");
2503
2504   return 0;
2505 }
2506
2507 VLIB_CONFIG_FUNCTION (unix_cli_config, "unix-cli");
2508
2509 /** Called when VPP is shutting down, this restores the system
2510  * terminal state if previously saved.
2511  */
2512 static clib_error_t *
2513 unix_cli_exit (vlib_main_t * vm)
2514 {
2515   unix_main_t *um = &unix_main;
2516
2517   /* If stdin is a tty and we saved the tty state, reset the tty state */
2518   if (isatty (UNIX_CLI_STDIN_FD) && um->tio_isset)
2519     tcsetattr (UNIX_CLI_STDIN_FD, TCSAFLUSH, &um->tio_stdin);
2520
2521   return 0;
2522 }
2523
2524 VLIB_MAIN_LOOP_EXIT_FUNCTION (unix_cli_exit);
2525
2526 /** Set the CLI prompt.
2527  * @param prompt The C string to set the prompt to.
2528  * @note This setting is global; it impacts all current
2529  *       and future CLI sessions.
2530  */
2531 void
2532 vlib_unix_cli_set_prompt (char *prompt)
2533 {
2534   char *fmt = (prompt[strlen (prompt) - 1] == ' ') ? "%s" : "%s ";
2535   unix_cli_main_t *cm = &unix_cli_main;
2536   if (cm->cli_prompt)
2537     vec_free (cm->cli_prompt);
2538   cm->cli_prompt = format (0, fmt, prompt);
2539 }
2540
2541 /** CLI command to quit the terminal session.
2542  * @note If this is a stdin session then this will
2543  *       shutdown VPP also.
2544  */
2545 static clib_error_t *
2546 unix_cli_quit (vlib_main_t * vm,
2547                unformat_input_t * input, vlib_cli_command_t * cmd)
2548 {
2549   unix_cli_main_t *cm = &unix_cli_main;
2550
2551   vlib_process_signal_event (vm,
2552                              vlib_current_process (vm),
2553                              UNIX_CLI_PROCESS_EVENT_QUIT,
2554                              cm->current_input_file_index);
2555   return 0;
2556 }
2557
2558 /*?
2559  * Terminates the current CLI session.
2560  *
2561  * If VPP is running in @em interactive mode and this is the console session
2562  * (that is, the session on @c stdin) then this will also terminate VPP.
2563 ?*/
2564 /* *INDENT-OFF* */
2565 VLIB_CLI_COMMAND (unix_cli_quit_command, static) = {
2566   .path = "quit",
2567   .short_help = "Exit CLI",
2568   .function = unix_cli_quit,
2569 };
2570 /* *INDENT-ON* */
2571
2572 /** CLI command to execute a VPP command script. */
2573 static clib_error_t *
2574 unix_cli_exec (vlib_main_t * vm,
2575                unformat_input_t * input, vlib_cli_command_t * cmd)
2576 {
2577   char *file_name;
2578   int fd;
2579   unformat_input_t sub_input;
2580   clib_error_t *error;
2581
2582   file_name = 0;
2583   fd = -1;
2584   error = 0;
2585
2586   if (!unformat (input, "%s", &file_name))
2587     {
2588       error = clib_error_return (0, "expecting file name, got `%U'",
2589                                  format_unformat_error, input);
2590       goto done;
2591     }
2592
2593   fd = open (file_name, O_RDONLY);
2594   if (fd < 0)
2595     {
2596       error = clib_error_return_unix (0, "failed to open `%s'", file_name);
2597       goto done;
2598     }
2599
2600   /* Make sure its a regular file. */
2601   {
2602     struct stat s;
2603
2604     if (fstat (fd, &s) < 0)
2605       {
2606         error = clib_error_return_unix (0, "failed to stat `%s'", file_name);
2607         goto done;
2608       }
2609
2610     if (!(S_ISREG (s.st_mode) || S_ISLNK (s.st_mode)))
2611       {
2612         error = clib_error_return (0, "not a regular file `%s'", file_name);
2613         goto done;
2614       }
2615   }
2616
2617   unformat_init_unix_file (&sub_input, fd);
2618
2619   vlib_cli_input (vm, &sub_input, 0, 0);
2620   unformat_free (&sub_input);
2621
2622 done:
2623   if (fd > 0)
2624     close (fd);
2625   vec_free (file_name);
2626
2627   return error;
2628 }
2629
2630 /*?
2631  * Executes a sequence of CLI commands which are read from a file.
2632  *
2633  * If a command is unrecognised or otherwise invalid then the usual CLI
2634  * feedback will be generated, however execution of subsequent commands
2635  * from the file will continue.
2636 ?*/
2637 /* *INDENT-OFF* */
2638 VLIB_CLI_COMMAND (cli_exec, static) = {
2639   .path = "exec",
2640   .short_help = "Execute commands from file",
2641   .function = unix_cli_exec,
2642   .is_mp_safe = 1,
2643 };
2644 /* *INDENT-ON* */
2645
2646 /** CLI command to show various unix error statistics. */
2647 static clib_error_t *
2648 unix_show_errors (vlib_main_t * vm,
2649                   unformat_input_t * input, vlib_cli_command_t * cmd)
2650 {
2651   unix_main_t *um = &unix_main;
2652   clib_error_t *error = 0;
2653   int i, n_errors_to_show;
2654   unix_error_history_t *unix_errors = 0;
2655
2656   n_errors_to_show = 1 << 30;
2657
2658   if (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
2659     {
2660       if (!unformat (input, "%d", &n_errors_to_show))
2661         {
2662           error =
2663             clib_error_return (0,
2664                                "expecting integer number of errors to show, got `%U'",
2665                                format_unformat_error, input);
2666           goto done;
2667         }
2668     }
2669
2670   n_errors_to_show =
2671     clib_min (ARRAY_LEN (um->error_history), n_errors_to_show);
2672
2673   i =
2674     um->error_history_index >
2675     0 ? um->error_history_index - 1 : ARRAY_LEN (um->error_history) - 1;
2676
2677   while (n_errors_to_show > 0)
2678     {
2679       unix_error_history_t *eh = um->error_history + i;
2680
2681       if (!eh->error)
2682         break;
2683
2684       vec_add1 (unix_errors, eh[0]);
2685       n_errors_to_show -= 1;
2686       if (i == 0)
2687         i = ARRAY_LEN (um->error_history) - 1;
2688       else
2689         i--;
2690     }
2691
2692   if (vec_len (unix_errors) == 0)
2693     vlib_cli_output (vm, "no Unix errors so far");
2694   else
2695     {
2696       vlib_cli_output (vm, "%Ld total errors seen", um->n_total_errors);
2697       for (i = vec_len (unix_errors) - 1; i >= 0; i--)
2698         {
2699           unix_error_history_t *eh = vec_elt_at_index (unix_errors, i);
2700           vlib_cli_output (vm, "%U: %U",
2701                            format_time_interval, "h:m:s:u", eh->time,
2702                            format_clib_error, eh->error);
2703         }
2704       vlib_cli_output (vm, "%U: time now",
2705                        format_time_interval, "h:m:s:u", vlib_time_now (vm));
2706     }
2707
2708 done:
2709   vec_free (unix_errors);
2710   return error;
2711 }
2712
2713 /* *INDENT-OFF* */
2714 VLIB_CLI_COMMAND (cli_unix_show_errors, static) = {
2715   .path = "show unix-errors",
2716   .short_help = "Show Unix system call error history",
2717   .function = unix_show_errors,
2718 };
2719 /* *INDENT-ON* */
2720
2721 /** CLI command to show session command history. */
2722 static clib_error_t *
2723 unix_cli_show_history (vlib_main_t * vm,
2724                        unformat_input_t * input, vlib_cli_command_t * cmd)
2725 {
2726   unix_cli_main_t *cm = &unix_cli_main;
2727   unix_cli_file_t *cf;
2728   int i, j;
2729
2730   cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index);
2731
2732   if (cf->has_history && cf->history_limit)
2733     {
2734       i = 1 + cf->command_number - vec_len (cf->command_history);
2735       for (j = 0; j < vec_len (cf->command_history); j++)
2736         vlib_cli_output (vm, "%d  %v\n", i + j, cf->command_history[j]);
2737     }
2738   else
2739     {
2740       vlib_cli_output (vm, "History not enabled.\n");
2741     }
2742
2743   return 0;
2744 }
2745
2746 /*?
2747  * Displays the command history for the current session, if any.
2748 ?*/
2749 /* *INDENT-OFF* */
2750 VLIB_CLI_COMMAND (cli_unix_cli_show_history, static) = {
2751   .path = "history",
2752   .short_help = "Show current session command history",
2753   .function = unix_cli_show_history,
2754 };
2755 /* *INDENT-ON* */
2756
2757 /** CLI command to show terminal status. */
2758 static clib_error_t *
2759 unix_cli_show_terminal (vlib_main_t * vm,
2760                         unformat_input_t * input, vlib_cli_command_t * cmd)
2761 {
2762   unix_main_t *um = &unix_main;
2763   unix_cli_main_t *cm = &unix_cli_main;
2764   unix_cli_file_t *cf;
2765   vlib_node_t *n;
2766
2767   cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index);
2768   n = vlib_get_node (vm, cf->process_node_index);
2769
2770   vlib_cli_output (vm, "Terminal name:   %v\n", n->name);
2771   vlib_cli_output (vm, "Terminal mode:   %s\n", cf->line_mode ?
2772                    "line-by-line" : "char-by-char");
2773   vlib_cli_output (vm, "Terminal width:  %d\n", cf->width);
2774   vlib_cli_output (vm, "Terminal height: %d\n", cf->height);
2775   vlib_cli_output (vm, "ANSI capable:    %s\n",
2776                    cf->ansi_capable ? "yes" : "no");
2777   vlib_cli_output (vm, "History enabled: %s%s\n",
2778                    cf->has_history ? "yes" : "no", !cf->has_history
2779                    || cf->history_limit ? "" :
2780                    " (disabled by history limit)");
2781   if (cf->has_history)
2782     vlib_cli_output (vm, "History limit:   %d\n", cf->history_limit);
2783   vlib_cli_output (vm, "Pager enabled:   %s%s%s\n",
2784                    cf->no_pager ? "no" : "yes",
2785                    cf->no_pager
2786                    || cf->height ? "" : " (disabled by terminal height)",
2787                    cf->no_pager
2788                    || um->cli_pager_buffer_limit ? "" :
2789                    " (disabled by buffer limit)");
2790   if (!cf->no_pager)
2791     vlib_cli_output (vm, "Pager limit:     %d\n", um->cli_pager_buffer_limit);
2792   vlib_cli_output (vm, "CRLF mode:       %s\n",
2793                    cf->crlf_mode ? "CR+LF" : "LF");
2794
2795   return 0;
2796 }
2797
2798 /*?
2799  * Displays various information about the state of the current terminal
2800  * session.
2801  *
2802  * @cliexpar
2803  * @cliexstart{show terminal}
2804  * Terminal name:   unix-cli-stdin
2805  * Terminal mode:   char-by-char
2806  * Terminal width:  123
2807  * Terminal height: 48
2808  * ANSI capable:    yes
2809  * History enabled: yes
2810  * History limit:   50
2811  * Pager enabled:   yes
2812  * Pager limit:     100000
2813  * CRLF mode:       LF
2814  * @cliexend
2815 ?*/
2816 /* *INDENT-OFF* */
2817 VLIB_CLI_COMMAND (cli_unix_cli_show_terminal, static) = {
2818   .path = "show terminal",
2819   .short_help = "Show current session terminal settings",
2820   .function = unix_cli_show_terminal,
2821 };
2822 /* *INDENT-ON* */
2823
2824 /** CLI command to set terminal pager settings. */
2825 static clib_error_t *
2826 unix_cli_set_terminal_pager (vlib_main_t * vm,
2827                              unformat_input_t * input,
2828                              vlib_cli_command_t * cmd)
2829 {
2830   unix_main_t *um = &unix_main;
2831   unix_cli_main_t *cm = &unix_cli_main;
2832   unix_cli_file_t *cf;
2833   unformat_input_t _line_input, *line_input = &_line_input;
2834
2835   if (!unformat_user (input, unformat_line_input, line_input))
2836     return 0;
2837
2838   cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index);
2839
2840   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
2841     {
2842       if (unformat (line_input, "on"))
2843         cf->no_pager = 0;
2844       else if (unformat (line_input, "off"))
2845         cf->no_pager = 1;
2846       else if (unformat (line_input, "limit %u", &um->cli_pager_buffer_limit))
2847         vlib_cli_output (vm,
2848                          "Pager limit set to %u lines; note, this is global.\n",
2849                          um->cli_pager_buffer_limit);
2850       else
2851         return clib_error_return (0, "unknown parameter: `%U`",
2852                                   format_unformat_error, line_input);
2853     }
2854
2855   unformat_free (line_input);
2856
2857   return 0;
2858 }
2859
2860 /*?
2861  * Enables or disables the terminal pager for this session. Generally
2862  * this defaults to enabled.
2863  *
2864  * Additionally allows the pager buffer size to be set; though note that
2865  * this value is set globally and not per session.
2866 ?*/
2867 /* *INDENT-OFF* */
2868 VLIB_CLI_COMMAND (cli_unix_cli_set_terminal_pager, static) = {
2869   .path = "set terminal pager",
2870   .short_help = "set terminal pager [on|off] [limit <lines>]",
2871   .function = unix_cli_set_terminal_pager,
2872 };
2873 /* *INDENT-ON* */
2874
2875 /** CLI command to set terminal history settings. */
2876 static clib_error_t *
2877 unix_cli_set_terminal_history (vlib_main_t * vm,
2878                                unformat_input_t * input,
2879                                vlib_cli_command_t * cmd)
2880 {
2881   unix_cli_main_t *cm = &unix_cli_main;
2882   unix_cli_file_t *cf;
2883   unformat_input_t _line_input, *line_input = &_line_input;
2884   u32 limit;
2885
2886   if (!unformat_user (input, unformat_line_input, line_input))
2887     return 0;
2888
2889   cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index);
2890
2891   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
2892     {
2893       if (unformat (line_input, "on"))
2894         cf->has_history = 1;
2895       else if (unformat (line_input, "off"))
2896         cf->has_history = 0;
2897       else if (unformat (line_input, "limit %u", &cf->history_limit))
2898         ;
2899       else
2900         return clib_error_return (0, "unknown parameter: `%U`",
2901                                   format_unformat_error, line_input);
2902
2903       /* If we reduced history size, or turned it off, purge the history */
2904       limit = cf->has_history ? cf->history_limit : 0;
2905
2906       while (cf->command_history && vec_len (cf->command_history) >= limit)
2907         {
2908           vec_free (cf->command_history[0]);
2909           vec_delete (cf->command_history, 1, 0);
2910         }
2911     }
2912
2913   unformat_free (line_input);
2914
2915   return 0;
2916 }
2917
2918 /*?
2919  * Enables or disables the command history function of the current
2920  * terminal. Generally this defaults to enabled.
2921  *
2922  * This command also allows the maximum size of the history buffer for
2923  * this session to be altered.
2924 ?*/
2925 /* *INDENT-OFF* */
2926 VLIB_CLI_COMMAND (cli_unix_cli_set_terminal_history, static) = {
2927   .path = "set terminal history",
2928   .short_help = "set terminal history [on|off] [limit <lines>]",
2929   .function = unix_cli_set_terminal_history,
2930 };
2931 /* *INDENT-ON* */
2932
2933 /** CLI command to set terminal ANSI settings. */
2934 static clib_error_t *
2935 unix_cli_set_terminal_ansi (vlib_main_t * vm,
2936                             unformat_input_t * input,
2937                             vlib_cli_command_t * cmd)
2938 {
2939   unix_cli_main_t *cm = &unix_cli_main;
2940   unix_cli_file_t *cf;
2941
2942   cf = pool_elt_at_index (cm->cli_file_pool, cm->current_input_file_index);
2943
2944   if (unformat (input, "on"))
2945     cf->ansi_capable = 1;
2946   else if (unformat (input, "off"))
2947     cf->ansi_capable = 0;
2948   else
2949     return clib_error_return (0, "unknown parameter: `%U`",
2950                               format_unformat_error, input);
2951
2952   return 0;
2953 }
2954
2955 /*?
2956  * Enables or disables the use of ANSI control sequences by this terminal.
2957  * The default will vary based on terminal detection at the start of the
2958  * session.
2959  *
2960  * ANSI control sequences are used in a small number of places to provide,
2961  * for example, color text output and to control the cursor in the pager.
2962 ?*/
2963 /* *INDENT-OFF* */
2964 VLIB_CLI_COMMAND (cli_unix_cli_set_terminal_ansi, static) = {
2965   .path = "set terminal ansi",
2966   .short_help = "set terminal ansi [on|off]",
2967   .function = unix_cli_set_terminal_ansi,
2968 };
2969 /* *INDENT-ON* */
2970
2971 static clib_error_t *
2972 unix_cli_init (vlib_main_t * vm)
2973 {
2974   return 0;
2975 }
2976
2977 VLIB_INIT_FUNCTION (unix_cli_init);
2978
2979 /*
2980  * fd.io coding-style-patch-verification: ON
2981  *
2982  * Local Variables:
2983  * eval: (c-set-style "gnu")
2984  * End:
2985  */