1 /*
2  * Arm SCP/MCP Software
3  * Copyright (c) 2020-2023, Arm Limited and Contributors. All rights reserved.
4  *
5  * SPDX-License-Identifier: BSD-3-Clause
6  */
7 
8 #include <cli.h>
9 #include <cli_config.h>
10 #include <cli_fifo.h>
11 #include <cli_platform.h>
12 
13 #include <fwk_io.h>
14 #include <fwk_list.h>
15 #include <fwk_mm.h>
16 
17 #include <stdarg.h>
18 #include <stdbool.h>
19 #include <stdint.h>
20 #include <stdlib.h>
21 #include <string.h>
22 
23 /* String holding console prompt. */
24 static char cli_prompt[CLI_CONFIG_PROMPT_BUF_SIZE] = { 0 };
25 static char cli_prompt_size = 0;
26 
27 /* Buffer to store a command as it is typed. */
28 static char cli_input_buffer[CLI_CONFIG_COMMAND_BUF_SIZE] = { 0 };
29 
30 /* Array holding pointers to arguments after a command is parsed. */
31 static char *cli_args[CLI_CONFIG_MAX_NUM_ARGUMENTS] = { 0 };
32 
33 /* Starting history buffer index. */
34 static uint32_t history_index = 0;
35 /* Buffer used to store the command history. */
36 static char *cli_history[CLI_CONFIG_HISTORY_LENGTH] = { 0 };
37 static char cli_history_buffer
38     [CLI_CONFIG_HISTORY_LENGTH * CLI_CONFIG_COMMAND_BUF_SIZE] = { 0 };
39 
40 /* Default terminal size variables, these are updated at each enter press. */
41 static uint32_t cli_terminal_width = CLI_CONFIG_DEFAULT_TERM_W;
42 static uint32_t cli_terminal_height = CLI_CONFIG_DEFAULT_TERM_H;
43 
44 /* Current state of the CLI. */
45 volatile cli_state_et cli_state = CLI_NOT_READY;
46 
47 /* Extern link to buffer holding installed CLI commands.  In cli_commands.h */
48 extern cli_command_st cli_commands[];
49 
50 /* Print buffer structures. */
51 static fifo_st cli_print_fifo = { 0 };
52 static char cli_print_fifo_buffer[CLI_CONFIG_PRINT_BUFFER_SIZE] = { 0 };
53 static bool overflow = false;
54 
strnlen(s,maxlen)55 size_t strnlen(s, maxlen) register const char *s;
56 size_t maxlen;
57 {
58     register const char *e;
59     size_t n;
60 
61     for (e = s, n = 0; *e && n < maxlen; e++, n++)
62         ;
63     return n;
64 }
65 
66 struct command_ctx {
67     cli_command_st cmd;
68     struct fwk_slist_node list_node;
69 };
70 
71 /* A linked list used for extend the existing command at run time */
72 static struct fwk_slist cli_commands_list;
73 
74 /* This array holds the common command available for all platforms */
75 extern cli_command_st cli_commands[];
76 
cli_command_register(cli_command_st new_cmd)77 int cli_command_register(cli_command_st new_cmd)
78 {
79     struct fwk_slist *node = NULL;
80     struct command_ctx *c = NULL;
81 
82     FWK_LIST_FOR_EACH(&cli_commands_list, node,
83         struct command_ctx, list_node, c) {
84         if (strcmp(new_cmd.command, c->cmd.command) == 0)
85             /* Command exist. Skip registration */
86             return FWK_SUCCESS;
87     }
88 
89     c = fwk_mm_calloc(1, sizeof(struct command_ctx));
90     if (c == NULL)
91         return FWK_E_NOMEM;
92 
93     c->cmd = new_cmd;
94     fwk_list_push_tail(&cli_commands_list, &c->list_node);
95 
96     return FWK_SUCCESS;
97 }
98 
cli_command_init(void)99 int cli_command_init(void)
100 {
101     uint32_t index;
102     int status;
103 
104     fwk_list_init(&cli_commands_list);
105 
106     /* Add the common commands to the commands linked list */
107     for (index = 0; cli_commands[index].command != 0; index++) {
108         status = cli_command_register(cli_commands[index]);
109         if (status != FWK_SUCCESS)
110             return status;
111     }
112     return FWK_SUCCESS;
113 }
114 
115 /*
116  * TBA: Replace calls to strncmp with calls to cli_strncmp.
117  * For some reason calls to strncmp crash for seemingly no reason on the
118  * FPGA, so we'll use cli_strncmp until that gets fixed or we move over to
119  * silicon.
120  */
cli_strncmp(const char * s1,const char * s2,uint32_t limit)121 int32_t cli_strncmp(const char *s1, const char *s2, uint32_t limit)
122 {
123     for (uint32_t i = 0; i < limit; i++) {
124         if (s1[i] != s2[i])
125             /*
126              * If first string greater than second, return positive, if second
127              * string greater than first, return negative.
128              */
129             return s1[i] - s2[i];
130         else if (s1[i] == 0)
131             /*
132              * We know characters are equal, so if one of them is equal to zero
133              * both are, so return zero to indicate a match.
134              */
135             return 0;
136     }
137 
138     /* Limit reached, return 0 as first string and second string match. */
139     /* up to the required character limit.                              */
140     return 0;
141 }
142 
143 /*****************************************************************************/
144 /* Private function prototypes.                                              */
145 /*****************************************************************************/
146 
147 /*
148  * cli_main
149  *   Description
150  *     Main loop for CLI thread.  Handles receiving and dispatching commands.
151  *   Parameters
152  *     void const *argument
153  *       Argument passed at initialization, not used.
154  */
155 static void cli_main(void const *argument);
156 
157 /*
158  * cli_format
159  *   Description
160  *     Sends formatting escape sequences to the terminal or writes them into a
161  *     string depending on how it's used.
162  *   Parameters
163  *     cli_option_et options
164  *       Formatting options to use.
165  *     char *buffer
166  *       If this value is not null, escape sequences are placed into this
167  *       buffer instead of being sent to the terminal immediately.
168  *   Return
169  *     uint32_t: FWK_SUCCESS if it works, something else if it
170  *     doesn't.
171  */
172 static uint32_t cli_format(cli_option_et options, char *buffer, uint32_t size);
173 
174 /*
175  * cli_get_command
176  *   Description
177  *     Function called in cli_main that receives bytes from the UART,
178  *     echos them back, and deals with backspace, escape sequences, etc.
179  *     It returns the received command when the enter key is pressed.
180  *   Parameters
181  *     char *buffer
182  *       Buffer in which the received command is placed.
183  *     uint32_t buffer_size
184  *       Total size, in bytes, of the command buffer.
185  *     uint32_t *command_length
186  *       The length of the command from the terminal is placed in this pointer.
187  *     char history[][]
188  *       Pointer to command history buffer.
189  *     uint32_t history_index
190  *       Index of the most recent item in the history buffer.
191  *     uint32_t history_size
192  *       Size of command history buffer.
193  *     bool *exit
194  *       Exit CLI was requested.
195  *   Return
196  *     uint32_t: FWK_SUCCESS if it works, something else if it
197  *     doesn't.
198  */
199 static uint32_t cli_get_command(
200     char *buffer,
201     uint32_t buffer_size,
202     uint32_t *command_length,
203     char **history,
204     uint32_t history_index,
205     uint32_t history_size,
206     bool *exit);
207 
208 /*
209  * cli_backspace
210  *   Description
211  *     Handles backspace characters from keyboard and makes sure lines wrap
212  *     properly.
213  *   Parameters
214  *     uint32_t *cursor
215  *       Pointer to cursor position variable.
216  *     uint32_t width
217  *       Width of terminal window.
218  *   Return
219  *     uint32_t: FWK_SUCCESS if it works, something else if it
220  *     doesn't.
221  */
222 static uint32_t cli_backspace(uint32_t *cursor, uint32_t width);
223 
224 /*
225  * cli_split
226  *   Description
227  *     Takes the command buffer, replaces whitespace characters with null
228  *     characters, and builds an array of pointers to each individual argument.
229  *     Arguments surrounded by parenthesis are taken verbatim, others are split
230  *     up by whitespace.
231  *   Parameters
232  *     char **argbuf
233  *       Buffer in which pointers to individual arguments are placed.  Null
234  *       terminated.
235  *     uint32_t argbuf_size
236  *       Number of entries in argbuf, can be configured with the
237  *       MAX_NUM_ARGUMENTS macro above.
238  *     char *command
239  *       Command string to be parsed.
240  *     uint32_t command_length
241  *       Length of the command.
242  *     char *whitespace
243  *       Null-terminated string of characters to consider whitespace.
244  *   Return
245  *     uint32_t: FWK_SUCCESS if it works, something else if it
246  *     doesn't.
247  */
248 static uint32_t cli_split(
249     char **argbuf,
250     uint32_t argbuf_size,
251     char *command,
252     uint32_t command_length,
253     const char *whitespace);
254 
255 /*
256  * cli_command_dispatch
257  *   Description
258  *     Takes the argument list provided by cli_split and determines which
259  *     command to execute.
260  *   Parameters
261  *     char **args
262  *       Pointer to an array of strings containing the arguments received from
263  *       the terminal.
264  *   Return
265  *     uint32_t: FWK_SUCCESS if it works, something else if it
266  *     doesn't.
267  */
268 static uint32_t cli_command_dispatch(char **args);
269 
270 /*
271  * cli_debug_output
272  *   Description
273  *     Handles the debug printout mode of the CLI.  It runs until a Ctrl+C
274  *     press is received.
275  *   Return
276  *     uint32_t: FWK_SUCCESS if it works, something else if it
277  *     doesn't.
278  */
279 static uint32_t cli_debug_output(void);
280 
281 /*
282  * cli_error_handler
283  *   Description
284  *     Prints information about cli_ret_et return values.
285  *   Parameters
286  *     cli_ret_et status
287  *       Error code to print information about.
288  */
289 static void cli_error_handler(uint32_t status);
290 
291 /*
292  * cli_val2str
293  *   Description
294  *     Converts a number variable to a string representation of the value.
295  *   Parameters
296  *     char **outstr
297  *       Pointer to string in which to put the result.
298  *     char *smax
299  *       Pointer to the address after the last byte in outstr, prevents
300  *       a buffer overflow.
301  *     uint32_t value
302  *       Value to convert to a string.
303  *     uint32_t base
304  *       Base of the result, usually base 2, 10, or 16.
305  *     int32_t fill
306  *       How many characters you want the result to take up.
307  */
308 static void cli_val2str(
309     char **outstr,
310     char *smax,
311     uint32_t value,
312     uint32_t base,
313     int32_t fill);
314 
315 /*
316  * cli_snprintf_arg
317  *   Description
318  *     Same as cli_snprintf, but uses va_list arguments.
319  *   Parameters
320  *     char *s
321  *       Buffer to build new string in.
322  *     char *smax
323  *       s + len(s), points to address after the last character in s.  This
324  *       keeps the function from exceeding the boundaries of the array.
325  *     const char *fmt
326  *       Formatted text to parse.
327  *     va_list *args
328  *       Pointer to initialized va_list structure.
329  */
330 static void cli_snprintf_arg(
331     char *s,
332     char *smax,
333     const char *fmt,
334     va_list *args);
335 
336 volatile uint32_t automation_mode = 0;
337 
338 /*****************************************************************************/
339 /* Public Function Definitions (see cli.h for descriptions)                  */
340 /*****************************************************************************/
341 
cli_init(void)342 uint32_t cli_init(void)
343 {
344     int32_t status = FWK_SUCCESS;
345     char *prompt = CLI_PROMPT;
346 
347     /* This function can only run when current state is CLI_NOT_READY. */
348     if (cli_state != CLI_NOT_READY)
349         return FWK_E_STATE;
350 
351     /* Store CLI prompt text. */
352     if (strlen(prompt) > (CLI_CONFIG_PROMPT_BUF_SIZE - 1))
353         return FWK_E_NOMEM;
354 
355     strncpy(cli_prompt, prompt, CLI_CONFIG_PROMPT_BUF_SIZE);
356     cli_prompt_size = strlen(prompt);
357 
358     /* Initialize print buffer FIFO. */
359     status = fifo_init(
360         &cli_print_fifo, cli_print_fifo_buffer, CLI_CONFIG_PRINT_BUFFER_SIZE);
361     if (status != FWK_SUCCESS)
362         return status;
363 
364     status = cli_command_init();
365     if (status != FWK_SUCCESS)
366         return status;
367 
368     /* Setting state to CLI_READY. */
369     cli_state = CLI_READY;
370 
371     return FWK_SUCCESS;
372 }
373 
cli_start(void)374 uint32_t cli_start(void)
375 {
376     /* This function can only run when current state is CLI_READY. */
377     if (cli_state != CLI_READY)
378         return FWK_E_STATE;
379 
380     /* Attempt to start thread. */
381     cli_main(NULL);
382 
383     return FWK_SUCCESS;
384 }
385 
cli_bprintf(cli_option_et options,const char * format,...)386 uint32_t cli_bprintf(cli_option_et options, const char *format, ...)
387 {
388     va_list arg;
389     uint32_t buffer_index = 0;
390     char scratch_buffer[CLI_CONFIG_SCRATCH_BUFFER_SIZE] = { 0 };
391 
392     /* Check parameters. */
393     if (format == NULL)
394         return FWK_E_PARAM;
395 
396     /* Check CLI state. */
397     if (cli_state == CLI_NOT_READY)
398         return FWK_E_STATE;
399 
400     /* Check if we need any escape sequence formatting. */
401     if (options != 0) {
402         cli_format(options, scratch_buffer, CLI_CONFIG_SCRATCH_BUFFER_SIZE);
403         buffer_index = strnlen(scratch_buffer, CLI_CONFIG_SCRATCH_BUFFER_SIZE);
404     }
405 
406     /* Generated formatted print string. */
407     va_start(arg, format);
408     cli_snprintf_arg(
409         (scratch_buffer + buffer_index),
410         (scratch_buffer + CLI_CONFIG_SCRATCH_BUFFER_SIZE),
411         format,
412         &arg);
413     va_end(arg);
414     buffer_index = strnlen(scratch_buffer, CLI_CONFIG_SCRATCH_BUFFER_SIZE);
415 
416     /* Make sure to return formatting to normal after escape sequences are used.
417      */
418     if (options != NONE) {
419         strncat(
420             scratch_buffer,
421             "\x1B[0m",
422             (CLI_CONFIG_SCRATCH_BUFFER_SIZE - buffer_index));
423         buffer_index = strnlen(scratch_buffer, CLI_CONFIG_SCRATCH_BUFFER_SIZE);
424     }
425 
426     /* Check to make sure we didn't fill up our scratch buffer. */
427     if (buffer_index >= CLI_CONFIG_SCRATCH_BUFFER_SIZE)
428         return FWK_E_NOMEM;
429 
430     /* Send formatted print data to print FIFO. */
431     return cli_bprint(scratch_buffer);
432 }
433 
cli_bprint(const char * string)434 uint32_t cli_bprint(const char *string)
435 {
436     uint32_t i = 0;
437     int32_t status = FWK_SUCCESS;
438 
439     /* Check parameters. */
440     if (string == NULL)
441         return FWK_E_PARAM;
442 
443     /* If not ready, return an error. */
444     if (cli_state == CLI_NOT_READY)
445         return FWK_E_STATE;
446 
447     /* If ready but not running, print directly to UART. */
448     if (cli_state == CLI_READY)
449         return cli_print(string);
450 
451     /* Put data into FIFO. */
452     for (i = 0; ; i++) {
453         if ((string[i] == 0) &&
454             (i >= CLI_CONFIG_SCRATCH_BUFFER_SIZE) &&
455             (status != FWK_SUCCESS))
456             break;
457         status = fifo_put(&cli_print_fifo, (char *)&string[i]);
458     }
459 
460     /* Print an error message if the print buffer is full. */
461     if ((status != FWK_SUCCESS) && (overflow == false)) {
462         overflow = true;
463         cli_print("\x1B[31mCONSOLE ERROR:\x1B[0m Print buffer overflow.\n");
464     }
465 
466     return status;
467 }
468 static char sCLIbuffer[CLI_CONFIG_SCRATCH_BUFFER_SIZE] = { 0 };
469 
470 static const char str_format_buf_too_small[] =
471     "\x1B[31mCONSOLE ERROR:\x1B[0m CLI format print buffer too small.\n";
cli_printf(cli_option_et options,const char * format,...)472 uint32_t cli_printf(cli_option_et options, const char *format, ...)
473 {
474     va_list arg;
475     int32_t status = FWK_SUCCESS;
476 
477     /* Checking pointers. */
478     if (format == 0)
479         return FWK_E_PARAM;
480 
481     /* Applying style options. */
482     if (options != NONE) {
483         status = cli_format(options, 0, 0);
484         if (status != FWK_SUCCESS)
485             return status;
486     }
487 
488     /* Printing formatted text. */
489     va_start(arg, format);
490     cli_snprintf_arg(
491         sCLIbuffer,
492         sCLIbuffer + CLI_CONFIG_SCRATCH_BUFFER_SIZE,
493         (char *)format,
494         &arg);
495     va_end(arg);
496 
497     /* Print format string. */
498     status = cli_print((const char *)sCLIbuffer);
499     if (status != FWK_SUCCESS)
500         return status;
501 
502     /* If style options were used, reset everything to default. */
503     if (options != NONE) {
504         cli_format(NONE, 0, 0);
505         if (status != FWK_SUCCESS)
506             return status;
507     }
508 
509     /* Making sure we didn't try to print too much. */
510     if (strnlen((char *)sCLIbuffer, CLI_CONFIG_SCRATCH_BUFFER_SIZE) >=
511         CLI_CONFIG_SCRATCH_BUFFER_SIZE - 1) {
512         status = cli_print(str_format_buf_too_small);
513         if (status != FWK_SUCCESS)
514             return status;
515         return FWK_E_NOMEM;
516     }
517 
518     return status;
519 }
520 
cli_print(const char * string)521 uint32_t cli_print(const char *string)
522 {
523     uint32_t index = 0;
524     int32_t status = FWK_SUCCESS;
525 
526     for (index = 0; string[index] != 0; index++) {
527         if (string[index] == '\n') {
528             status = fwk_io_putch(fwk_io_stdout, '\r');
529             if (status != FWK_SUCCESS) {
530                 return status;
531             }
532         }
533         status = fwk_io_putch(fwk_io_stdout, string[index]);
534         if (status != FWK_SUCCESS) {
535             return status;
536         }
537     }
538     return status;
539 }
540 
cli_snprintf(char * s,char * smax,const char * fmt,...)541 void cli_snprintf(char *s, char *smax, const char *fmt, ...)
542 {
543     va_list args;
544 
545     va_start(args, fmt);
546     cli_snprintf_arg(s, smax, fmt, &args);
547     va_end(args);
548 
549     return;
550 }
551 
cli_getline(char * buffer,uint32_t buffer_size,char ** argbuf,uint32_t argbuf_size,uint32_t cursor_position)552 uint32_t cli_getline(
553     char *buffer,
554     uint32_t buffer_size,
555     char **argbuf,
556     uint32_t argbuf_size,
557     uint32_t cursor_position)
558 {
559     char c = 0;
560     uint32_t index = 0;
561     int32_t status = FWK_SUCCESS;
562 
563     /* Validate parameters. */
564     if (buffer == NULL ||
565         buffer_size == 0 ||
566         cursor_position >= cli_terminal_width ||
567         argbuf == NULL ||
568         argbuf_size == 0)
569         return FWK_E_PARAM;
570 
571     /* Zero out buffer and argbuf. */
572     memset(buffer, 0, buffer_size);
573     memset(argbuf, 0, argbuf_size * sizeof(char **));
574 
575     /* Print prompt arrow. */
576     status = cli_print(CLI_PROMPT);
577     if (status != FWK_SUCCESS)
578         return status;
579 
580     /* Increment cursor position since we just printed an arrow and space. */
581     cursor_position = (cursor_position + 2) % cli_terminal_width;
582 
583     /*
584      * Loop will terminate when the user presses enter or when an error is
585      * generated. This loop will have negligible impact on system performance.
586      */
587     while (1) {
588         /* Grab a character from the UART. */
589         do {
590             status = fwk_io_getch(fwk_io_stdin, &c);
591         } while (status == FWK_PENDING);
592 
593         if (status != FWK_SUCCESS)
594             return status;
595 
596         /*
597          * Ignore non-printing characters except for a few we care about.
598          * 0x00 - 0x1F are non-printing characters.
599          * 0x7F - 0xFF are non-printing characters.
600          * Carriage return: \r, 0x0D
601          * Newline:         \n, 0x0C
602          * Backspace:       \b, 0x08
603          * Delete:              0x7F
604          */
605         if ((c <= 0x1F || c >= 0x7F) && c != '\r' && c != '\n' && c != '\b' &&
606             c != 0x7F)
607             continue;
608 
609 
610         /* If backspace (0x08) or delete (0x7F) character received. */
611         if (c == '\b' || c == 0x7F) {
612             /* Only accept backspace presses if we're not at the beginning of
613              * the string. */
614             if (index != 0) {
615                 status = cli_backspace(&cursor_position, cli_terminal_width);
616                 if (status != FWK_SUCCESS)
617                     return status;
618 
619                 /* Decrement index and set old last character to null. */
620                 index = index - 1;
621                 buffer[index] = 0;
622             }
623             continue;
624         }
625 
626         /* If newline received. */
627         if (c == '\n' || c == '\r') {
628             /* Making sure the rest of the buffer is zero. */
629             memset(&(buffer[index]), 0, buffer_size - index);
630             status = cli_print("\n");
631             if (status != FWK_SUCCESS)
632                 return status;
633             break;
634         }
635 
636         /* Echo received character to console. */
637         do {
638             status = fwk_io_putch(fwk_io_stdout, c);
639         } while (status == FWK_PENDING);
640 
641         if (status != FWK_SUCCESS)
642             return status;
643 
644         cursor_position = (cursor_position + 1) % cli_terminal_width;
645 
646         /* Incrementing indices. */
647         buffer[index] = c;
648         index = index + 1;
649         if (index >= buffer_size) {
650             /*
651              * Add null termination in case the user doesn't check return codes
652              * and tries to use the buffer.
653              */
654             buffer[index - 1] = 0;
655             return FWK_E_NOMEM;
656         }
657 
658         /* Add new null terminator. */
659         buffer[index] = 0;
660     }
661 
662     status = cli_split(argbuf, argbuf_size, buffer, index, " ");
663     if (status != FWK_SUCCESS)
664         return status;
665 
666     return FWK_SUCCESS;
667 }
668 
669 /*****************************************************************************/
670 /* Private Function Definitions                                              */
671 /*****************************************************************************/
672 
cli_main(void const * argument)673 static void cli_main(void const *argument)
674 {
675     int32_t status;
676     uint32_t last_history_index = CLI_CONFIG_HISTORY_LENGTH - 1;
677     uint32_t command_length = 0;
678     bool cli_exit = false;
679 
680     /* Thread was started successfully, set state to CLI_RUNNING. */
681     cli_state = CLI_RUNNING;
682 
683     /* Initialize command history buffer pointers. */
684     for (uint32_t i = 0; i < CLI_CONFIG_HISTORY_LENGTH; i++)
685         cli_history[i] = &cli_history_buffer[i * CLI_CONFIG_COMMAND_BUF_SIZE];
686 
687     /* Loop forever. */
688     while (1) {
689         /* Printing prompt text. */
690         cli_printf(NONE, cli_prompt);
691 
692         /* Zero out input buffer. */
693         memset(cli_input_buffer, 0, CLI_CONFIG_COMMAND_BUF_SIZE);
694 
695         /* Get command from terminal UART. */
696         status = cli_get_command(
697             cli_input_buffer,
698             CLI_CONFIG_COMMAND_BUF_SIZE - 1,
699             &command_length,
700             cli_history,
701             history_index,
702             CLI_CONFIG_HISTORY_LENGTH,
703             &cli_exit);
704         if (status != FWK_SUCCESS) {
705             cli_error_handler(status);
706             continue;
707         }
708 
709         /* Ctrl+d was pressed to exit cli */
710         if (cli_exit) {
711             cli_state = CLI_READY;
712             return;
713         }
714 
715         /* Make sure command string is not empty. */
716         if (cli_input_buffer[0] == 0)
717             continue;
718 
719 
720         /* Update history buffer if command is different than the last one. */
721         if (cli_strncmp(
722                 cli_input_buffer,
723                 cli_history[last_history_index],
724                 CLI_CONFIG_COMMAND_BUF_SIZE) != 0) {
725             strncpy(
726                 cli_history[history_index],
727                 cli_input_buffer,
728                 CLI_CONFIG_COMMAND_BUF_SIZE);
729             history_index = (history_index + 1) % CLI_CONFIG_HISTORY_LENGTH;
730             last_history_index =
731                 (last_history_index + 1) % CLI_CONFIG_HISTORY_LENGTH;
732         }
733 
734         /* Splitting up command into individual argument strings. */
735         status = cli_split(
736             cli_args,
737             CLI_CONFIG_MAX_NUM_ARGUMENTS,
738             cli_input_buffer,
739             command_length,
740             " ");
741         if (status != FWK_SUCCESS) {
742             cli_error_handler(status);
743             continue;
744         }
745 
746         /* If the user didn't type any valid arguments, don't process it. */
747         if (cli_args[0] == 0)
748             continue;
749 
750         /* Dispatching command for processing. */
751         status = cli_command_dispatch(cli_args);
752         if (status != FWK_SUCCESS)
753             cli_error_handler(status);
754     }
755 }
756 
cli_format(cli_option_et options,char * buffer,uint32_t size)757 uint32_t cli_format(cli_option_et options, char *buffer, uint32_t size)
758 {
759     int32_t status = FWK_SUCCESS;
760     static char tmp_buf[10] = { 0 };
761 
762     if (buffer != NULL)
763         /* Add a null terminator before we do anything. */
764         buffer[0] = '\0';
765 
766     if (automation_mode)
767         return status;
768 
769     /* If no options given, send SGR default sequence to remove formatting. */
770     if (options == NONE) {
771         if (buffer != NULL) {
772             if (size < 5)
773                 return FWK_E_NOMEM;
774             memcpy(buffer, "\x1B[0m", 5);
775             buffer = &(buffer[4]);
776             size = size - 4;
777         } else
778             return cli_print("\x1B[0m");
779     }
780     /* Clear terminal window. */
781     if (options & CLEAR_DISPLAY) {
782         if (buffer != NULL) {
783             if (size < 5)
784                 return FWK_E_NOMEM;
785             memcpy(buffer, "\x1B[2J", 5);
786             buffer = &(buffer[4]);
787             size = size - 4;
788         } else
789             status = cli_print("\x1B[2J");
790 
791         if (status != FWK_SUCCESS)
792             return status;
793     }
794     /* Reset cursor. */
795     if (options & RESET_CURSOR) {
796         if (buffer != NULL) {
797             if (size < 7)
798                 return FWK_E_NOMEM;
799 
800             memcpy(buffer, "\x1B[0;0f", 7);
801             buffer = &(buffer[6]);
802             size = size - 6;
803         } else
804             status = cli_print("\x1B[0;0f");
805 
806         if (status != FWK_SUCCESS)
807             return status;
808     }
809     /* SGR settings. */
810     if (options & CLI_ALL_MASK) {
811         if (buffer != NULL) {
812             if (size < 3)
813                 return FWK_E_NOMEM;
814             memcpy(buffer, "\x1B[", 3);
815             buffer = &(buffer[2]);
816             size = size - 2;
817         } else {
818             status = cli_print("\x1B[");
819             if (status != FWK_SUCCESS)
820                 return status;
821         }
822         /* Bold/bright. */
823         if (options & BOLD) {
824             if (buffer != NULL) {
825                 if (size < 3)
826                     return FWK_E_NOMEM;
827 
828                 memcpy(buffer, ";1", 3);
829                 buffer = &(buffer[2]);
830                 size = size - 2;
831             } else {
832                 status = cli_print(";1");
833                 if (status != FWK_SUCCESS)
834                     return status;
835             }
836         }
837         /* Underlining. */
838         if (options & UNDERLINE) {
839             if (buffer != NULL) {
840                 if (size < 3)
841                     return FWK_E_NOMEM;
842                 memcpy(buffer, ";4", 3);
843                 buffer = &(buffer[2]);
844                 size = size - 2;
845             } else {
846                 status = cli_print(";4");
847                 if (status != FWK_SUCCESS)
848                     return status;
849             }
850         }
851         /* Background color. */
852         if (options & CLI_BG_COLOR_MASK) {
853             if (buffer != NULL) {
854                 if (size < 4)
855                     return FWK_E_NOMEM;
856 
857                 cli_snprintf(
858                     tmp_buf,
859                     tmp_buf + 10,
860                     (const char *)";%d",
861                     (uint16_t)(
862                         (options & CLI_BG_COLOR_MASK) >> CLI_BG_COLOR_SHIFT));
863                 memcpy(buffer, tmp_buf, 4);
864                 buffer = &(buffer[3]);
865                 size = size - 3;
866             } else {
867                 status = cli_printf(
868                     0,
869                     ";%d",
870                     (uint16_t)(
871                         (options & CLI_BG_COLOR_MASK) >> CLI_BG_COLOR_SHIFT));
872                 if (status != FWK_SUCCESS)
873                     return status;
874             }
875         }
876         /* Foreground color. */
877         if (options & CLI_TEXT_COLOR_MASK) {
878             if (buffer != NULL) {
879                 if (size < 4)
880                     return FWK_E_NOMEM;
881                 cli_snprintf(
882                     tmp_buf,
883                     tmp_buf + 10,
884                     (const char *)";%d",
885                     (char)((options & CLI_TEXT_COLOR_MASK) >>
886                         CLI_TEXT_COLOR_SHIFT));
887                 memcpy(buffer, tmp_buf, 4);
888                 buffer = &(buffer[3]);
889                 size = size - 3;
890             } else {
891                 status = cli_printf(
892                     0,
893                     ";%d",
894                     (char)((options & CLI_TEXT_COLOR_MASK) >>
895                         CLI_TEXT_COLOR_SHIFT));
896                 if (status != FWK_SUCCESS)
897                     return status;
898             }
899         }
900         if (buffer != NULL) {
901             if (size < 2)
902                 return FWK_E_NOMEM;
903             memcpy(buffer, "m", 2);
904         } else {
905             status = cli_print("m");
906             if (status != FWK_SUCCESS)
907                 return status;
908         }
909     }
910 
911     return FWK_SUCCESS;
912 }
913 
cli_get_command(char * buffer,uint32_t buffer_size,uint32_t * command_length,char ** history,uint32_t history_index,uint32_t history_size,bool * exit)914 static uint32_t cli_get_command(
915     char *buffer,
916     uint32_t buffer_size,
917     uint32_t *command_length,
918     char **history,
919     uint32_t history_index,
920     uint32_t history_size,
921     bool *exit)
922 {
923     int32_t status = FWK_SUCCESS;
924     uint32_t index = 0;
925     uint32_t cursor_index = cli_prompt_size;
926     char c = 0;
927     static char escape[8] = { 0 };
928     uint32_t escape_index = 0;
929     uint32_t history_oldest = history_index;
930     bool flag_escape_sequence = false;
931 
932     (void)escape;
933 
934     /* Checking parameters. */
935     if (buffer == 0 || buffer_size == 0 || history == 0 ||
936         command_length == 0)
937         return FWK_E_PARAM;
938 
939     if (!automation_mode) {
940         /* Getting terminal window size. */
941         /* Saving cursor position. */
942         cli_print("\x1B[s");
943         /* Moving cursor to bottom right position. */
944         cli_print("\x1B[999;999f");
945         /* Requesting new cursor position. */
946         cli_print("\x1B[6n");
947         /* Restoring old cursor position. */
948         cli_print("\x1B[u");
949     }
950 
951     while (1) {
952         /* Get character from UART. */
953         do {
954             status = fwk_io_getch(fwk_io_stdin, &c);
955         } while (status == FWK_PENDING);
956 
957         if (status != FWK_SUCCESS)
958             return status;
959 
960         /* If we received a Ctrl+C press, go to debug mode. */
961         if (c == '\x03') {
962             *command_length = 0;
963             return cli_debug_output();
964         }
965 
966         /* If we received a Ctrl+d press, exit cli. */
967         if (c == '\x04') {
968             *exit = true;
969             return FWK_SUCCESS;
970         }
971 
972         /* Ignoring non-printing characters except for a few we care about. */
973         if (c < 0x20 && c != '\r' && c != '\n' && c != '\b') {
974             if (c == 0x1B)
975                 flag_escape_sequence = true;
976             continue;
977         }
978 
979         /* Dealing with escape sequences. */
980         if (flag_escape_sequence == true) {
981             escape[escape_index] = c;
982             escape[escape_index + 1] = 0;
983             escape_index = escape_index + 1;
984 
985             /* Escape sequences end with a letter. */
986             if ((c > 0x40 && c < 0x5B) || (c > 0x60 && c < 0x7B)) {
987                 flag_escape_sequence = false;
988                 escape_index = 0;
989 
990                 /* Up arrow press. */
991                 if (c == 'A') {
992                     if (((history_oldest + 1) % history_size) !=
993                         history_index) {
994                         /* Rewind history index by 1. */
995                         if (history_index == 0)
996                             history_index = history_size - 1;
997                         else
998                             history_index = history_index - 1;
999 
1000                         /* If command entry is empty then stop and restore index
1001                          * value. */
1002                         if (history[history_index][0] == 0) {
1003                             history_index = (history_index + 1) % history_size;
1004                         } else {
1005                             /* Erasing currently entered command. */
1006                             for (; index > 0; index--) {
1007                                 status = cli_backspace(
1008                                     &cursor_index, cli_terminal_width);
1009                                 if (status != FWK_SUCCESS)
1010                                     return status;
1011                             }
1012 
1013                             /* Copying history command from history buffer. */
1014                             strncpy(
1015                                 buffer, history[history_index], buffer_size);
1016 
1017                             /* Printing history command to screen. */
1018                             while (buffer[index] != 0) {
1019                                 status =
1020                                     fwk_io_putch(fwk_io_stdout, buffer[index]);
1021                                 if (status != FWK_SUCCESS)
1022                                     return status;
1023                                 index = index + 1;
1024                                 cursor_index =
1025                                     (cursor_index + 1) % cli_terminal_width;
1026                             }
1027                         }
1028                     }
1029                 }
1030 
1031                 /* Down arrow press. */
1032                 if (c == 'B') {
1033                     if (history_index != history_oldest) {
1034                         /* Getting index of history item to load. */
1035                         history_index = (history_index + 1) % history_size;
1036 
1037                         /* Erasing everything in the currently entered command.
1038                          */
1039                         for (; index > 0; index--) {
1040                             status = cli_backspace(
1041                                 &cursor_index, cli_terminal_width);
1042                             if (status != FWK_SUCCESS)
1043                                 return status;
1044                         }
1045 
1046                         /* If we're back to the current command start fresh and
1047                          * zero buffer. */
1048                         if (history_index == history_oldest)
1049                             memset(buffer, 0, buffer_size);
1050                         else {
1051                             /* Copying history command from history buffer. */
1052                             strncpy(
1053                                 buffer, history[history_index], buffer_size);
1054 
1055                             /* Printing history command to screen. */
1056                             while (buffer[index] != 0) {
1057                                 status =
1058                                     fwk_io_putch(fwk_io_stdout, buffer[index]);
1059                                 if (status != FWK_SUCCESS)
1060                                     return status;
1061 
1062                                 index = index + 1;
1063                                 cursor_index =
1064                                     (cursor_index + 1) % cli_terminal_width;
1065                             }
1066                         }
1067                     }
1068                 }
1069 
1070                 /* Handling cursor position response sequence. */
1071                 if (c == 'R') {
1072                     uint32_t i = 0;
1073                     for (i = 0; escape[i] != '['; i++)
1074                         ;
1075                     i++;
1076                     cli_terminal_height = strtoul(&escape[i], NULL, 0);
1077                     for (; escape[i] != ';'; i++)
1078                         ;
1079                     i++;
1080                     cli_terminal_width = strtoul(&escape[i], NULL, 0);
1081                 }
1082             }
1083             continue;
1084         }
1085 
1086         /* If backspace (0x08) or delete (0x7F) character received. */
1087         if (c == 0x08 || c == 0x7F) {
1088             /* Only accept backspace presses if we're not at the beginning of
1089              * the string. */
1090             if (index != 0) {
1091                 status = cli_backspace(&cursor_index, cli_terminal_width);
1092                 if (status != FWK_SUCCESS)
1093                     return status;
1094                 index = index - 1;
1095                 buffer[index] = 0;
1096             }
1097             continue;
1098         }
1099 
1100         /* If newline received. */
1101         if (c == '\n' || c == '\r') {
1102             /* Making sure the rest of the buffer is zero. */
1103             memset(&(buffer[index]), 0, buffer_size - index);
1104             *command_length = index;
1105             return cli_print("\n");
1106         }
1107 
1108         /* Printing received character to console. */
1109         status = fwk_io_putch(fwk_io_stdout, c);
1110         if (status != FWK_SUCCESS)
1111             return status;
1112 
1113         /* Incrementing indices. */
1114         buffer[index] = c;
1115         index = index + 1;
1116         if (index >= buffer_size)
1117             return FWK_E_NOMEM;
1118         buffer[index] = 0;
1119         cursor_index = (cursor_index + 1) % cli_terminal_width;
1120     }
1121 }
1122 
cli_backspace(uint32_t * cursor,uint32_t width)1123 static uint32_t cli_backspace(uint32_t *cursor, uint32_t width)
1124 {
1125     uint32_t status = FWK_SUCCESS;
1126 
1127     /* If cursor is at the first position of a line. */
1128     if (*cursor == 0) {
1129         status = cli_printf(0, "\x1B[A\x1B[%dC ", width - 1);
1130         if (status != FWK_SUCCESS)
1131             return status;
1132         *cursor = width - 1;
1133     } else {
1134         /* For compatibility, print back, space, back. */
1135         status = cli_print("\x1B[D \x1B[D");
1136         if (status != FWK_SUCCESS)
1137             return status;
1138         *cursor = *cursor - 1;
1139     }
1140 
1141     return FWK_SUCCESS;
1142 }
1143 
cli_split(char ** argbuf,uint32_t argbuf_size,char * command,uint32_t command_length,const char * whitespace)1144 static uint32_t cli_split(
1145     char **argbuf,
1146     uint32_t argbuf_size,
1147     char *command,
1148     uint32_t command_length,
1149     const char *whitespace)
1150 {
1151     uint32_t index = 0;
1152     uint32_t argbuf_ctr = 0;
1153     bool flag_paren = false;
1154     bool flag_last_was_whitespace = true;
1155 
1156     /* Checking pointers. */
1157     if (argbuf == 0 || command == 0 || argbuf_size == 0 || whitespace == 0)
1158         return FWK_E_PARAM;
1159 
1160     argbuf[0] = 0;
1161 
1162     for (index = 0; index < command_length; index++) {
1163         /* If whitespace is encountered outside of parenthesis, change it to
1164          * null and set flag. */
1165         if (strchr(whitespace, command[index]) != 0 && flag_paren == false) {
1166             command[index] = 0;
1167             flag_last_was_whitespace = true;
1168             continue;
1169         }
1170 
1171         /* Handle parenthesis. */
1172         if (command[index] == '\"') {
1173             /* If we've reached the end of an arg in parenthesis, reset flag. */
1174             if (flag_paren == true) {
1175                 flag_paren = false;
1176                 command[index] = 0;
1177                 flag_last_was_whitespace = true;
1178                 continue;
1179             }
1180 
1181             /* If we receive an opening parenthesis preceded by a whitespace
1182              * character, mark null and enter parenthesis processing. */
1183             if (flag_paren == false && flag_last_was_whitespace == true) {
1184                 flag_paren = true;
1185                 command[index] = 0;
1186                 continue;
1187             }
1188         }
1189 
1190         /* First regular character after whitespace encountered. */
1191         if ((strchr(whitespace, command[index]) == 0 || flag_paren == true) &&
1192             flag_last_was_whitespace == true) {
1193             flag_last_was_whitespace = false;
1194             if (argbuf_ctr + 1 >= argbuf_size)
1195                 return FWK_E_NOMEM;
1196             argbuf[argbuf_ctr] = &command[index];
1197             argbuf[argbuf_ctr + 1] = 0;
1198             argbuf_ctr = argbuf_ctr + 1;
1199         }
1200     }
1201 
1202     /* If the user forgot to close their parenthesis, return an error. */
1203     if (flag_paren == true)
1204         return FWK_E_PARAM;
1205 
1206     return FWK_SUCCESS;
1207 }
1208 
cli_command_dispatch(char ** args)1209 static uint32_t cli_command_dispatch(char **args)
1210 {
1211     uint32_t index;
1212     uint32_t status;
1213     uint32_t num_args = 0;
1214     struct fwk_slist *node = NULL;
1215     struct command_ctx *cc = NULL;
1216     bool command_found;
1217 
1218     /* Checking pointer. */
1219     if (args == 0)
1220         return FWK_E_PARAM;
1221 
1222     /* Special case command: help. */
1223     if (cli_strncmp(args[0], "help", 5) == 0) {
1224         FWK_LIST_FOR_EACH(&cli_commands_list, node,
1225             struct command_ctx, list_node, cc) {
1226                 cli_printf(NONE, "%s\n", cc->cmd.command);
1227                 cli_print(cc->cmd.help);
1228                 cli_print("\n");
1229         }
1230 
1231         cli_printf(NONE, "help\n");
1232         cli_print("  Displays this information.\n");
1233         cli_printf(NONE, "Ctrl+C\n");
1234         cli_print("  Displays debug output from running threads.\n");
1235         cli_printf(NONE, "Ctrl+d\n");
1236         cli_print("  Exit the CLI.\n");
1237         return FWK_SUCCESS;
1238     }
1239 
1240     if (cli_strncmp(args[0], "AUTO", 4) == 0) {
1241         cli_printf(NONE, "AUTO Mode ON\n");
1242         automation_mode = 1;
1243         return FWK_SUCCESS;
1244     }
1245 
1246     if (cli_strncmp(args[0], "OTUA", 4) == 0) {
1247         cli_printf(NONE, "AUTO Mode OFF\n");
1248         automation_mode = 0;
1249         return FWK_SUCCESS;
1250     }
1251 
1252     /* Searching for command handler. */
1253     /* Using strcmp here because each entry in args is guaranteed to be null
1254      * terminated by cli_split. */
1255     command_found = false;
1256     FWK_LIST_FOR_EACH(&cli_commands_list, node,
1257         struct command_ctx, list_node, cc) {
1258         if (strcmp(args[0], cc->cmd.command) == 0) {
1259             command_found = true;
1260             break;
1261         }
1262     }
1263     if (!command_found)
1264         return FWK_E_SUPPORT;
1265 
1266     /* Handler found, if -h or --help is given just print it's help string and
1267      * return. */
1268     if (cc->cmd.ignore_help_flag == false) {
1269         for (index = 1;
1270              (args[index] != 0) && (cli_strncmp(args[index], "-h", 3) != 0) &&
1271              (cli_strncmp(args[index], "--help", 7) != 0);
1272              index++)
1273             ;
1274 
1275         if ((args[index] != 0 || cc->cmd.handler == 0) &&
1276             cc->cmd.help != 0) {
1277             status = cli_print(cc->cmd.help);
1278             if (status != FWK_SUCCESS)
1279                 return status;
1280 
1281             /* Print a newline since help strings shouldn't have newlines at the
1282              * end. */
1283             cli_print("\n");
1284             return status;
1285         }
1286     }
1287 
1288     /* Counting arguments. */
1289     for (num_args = 0; args[num_args] != 0; num_args++)
1290         ;
1291 
1292     /* Calling command handler. */
1293     /* args is incremented so the command just gets the arguments and not the
1294      * name of itself. */
1295     if (cc->cmd.handler != 0)
1296         return cc->cmd.handler(num_args, args);
1297     else
1298         return FWK_E_PARAM;
1299 }
1300 
cli_debug_output(void)1301 static uint32_t cli_debug_output(void)
1302 {
1303     uint32_t fifo_status;
1304     char c = 0;
1305 
1306     cli_printf(
1307         0,
1308         "\nNow showing debug console output, to return to the command line, "
1309         "press Ctrl+C.\n");
1310     while (1) {
1311         /* Looking for Ctrl+C press. */
1312         if (fwk_io_getch(fwk_io_stdin, &c) == FWK_SUCCESS) {
1313             if (c == '\x03') {
1314                 cli_printf(
1315                     0,
1316                     "\nNow showing command line, to return to debug output, "
1317                     "press Ctrl+C.\n");
1318                 return FWK_SUCCESS;
1319             }
1320         }
1321 
1322         /* Read from print FIFO. */
1323         fifo_status = fifo_get(&cli_print_fifo, &c);
1324         if (fifo_status == FWK_SUCCESS) {
1325             overflow = false;
1326             fwk_io_putch(fwk_io_stdout, c);
1327         } else
1328             /* If no characters are available, let other stuff run. */
1329             cli_platform_delay_ms(0);
1330     }
1331 }
1332 
cli_error_handler(uint32_t status)1333 static void cli_error_handler(uint32_t status)
1334 {
1335     if (status == FWK_SUCCESS)
1336         return;
1337 
1338     cli_printf(NONE, "CONSOLE ERROR: %s\n", fwk_status_str(status));
1339 }
1340 
cli_val2str(char ** outstr,char * smax,uint32_t value,uint32_t base,int32_t fill)1341 static void cli_val2str(
1342     char **outstr,
1343     char *smax,
1344     uint32_t value,
1345     uint32_t base,
1346     int32_t fill)
1347 {
1348     /* Just need enough space to store 64 bit decimal integer */
1349     unsigned char str[20] = { 0 };
1350     int i = 0;
1351 
1352     do {
1353         str[i++] = "0123456789ABCDEF"[value % base];
1354     } while (value /= base);
1355 
1356     while (--fill >= i) {
1357         **outstr = '0';
1358         *outstr = *outstr + 1;
1359         if (*outstr >= smax)
1360             return;
1361     }
1362 
1363     while (--i >= 0) {
1364         **outstr = str[i];
1365         *outstr = *outstr + 1;
1366         if (*outstr >= smax)
1367             return;
1368     }
1369 }
1370 
cli_snprintf_arg(char * s,char * smax,const char * fmt,va_list * args)1371 static void cli_snprintf_arg(
1372     char *s,
1373     char *smax,
1374     const char *fmt,
1375     va_list *args)
1376 {
1377     int bit64;
1378     int fill;
1379     int64_t num = 0;
1380     uint64_t unum = 0;
1381     char *str = NULL;
1382     uint32_t most_significant = 0;
1383     char c = 0;
1384 
1385     while (*fmt) {
1386         if (*fmt == '%') {
1387             fmt++;
1388             bit64 = 0;
1389             fill = 0;
1390         /* Check the format specifier */
1391 loop:
1392             switch (*fmt) {
1393             case 'c':
1394                 c = (char)va_arg(*args, int32_t);
1395                 *s++ = c;
1396                 if (s >= smax) {
1397                     s[-1] = 0;
1398                     return;
1399                 }
1400                 break;
1401             case 'i': /* Specifiers i and d do the same thing. */
1402             case 'd':
1403                 if (bit64) {
1404                     *s = 0;
1405                     return;
1406                 }
1407 
1408                 num = va_arg(*args, int32_t);
1409 
1410                 if (num < 0) {
1411                     *s++ = '-';
1412                     if (s >= smax) {
1413                         s[-1] = 0;
1414                         return;
1415                     }
1416                     unum = (unsigned long int)-num;
1417                 } else {
1418                     unum = (unsigned long int)num;
1419                 }
1420 
1421                 cli_val2str(&s, smax, unum, 10, fill);
1422                 if (s >= smax) {
1423                     s[-1] = 0;
1424                     return;
1425                 }
1426                 break;
1427             case 's':
1428                 str = va_arg(*args, char *);
1429                 while (*str) {
1430                     *s++ = *str++;
1431                     if (s >= smax) {
1432                         s[-1] = 0;
1433                         return;
1434                     }
1435                 }
1436                 break;
1437             case 'x': /* All hex prints use uppercase hex digits. */
1438             case 'X':
1439                 if (bit64) {
1440                     unum = va_arg(*args, uint64_t);
1441                     most_significant = (uint32_t)(unum >> 32);
1442                     if (most_significant) {
1443                         cli_val2str(
1444                             &s,
1445                             smax,
1446                             most_significant,
1447                             16,
1448                             (fill >= 8) ? fill - 8 : 0);
1449                         if (s >= smax) {
1450                             s[-1] = 0;
1451                             return;
1452                         }
1453                         cli_val2str(&s, smax, unum, 16, 8);
1454                         if (s >= smax) {
1455                             s[-1] = 0;
1456                             return;
1457                         }
1458                     } else {
1459                         cli_val2str(&s, smax, unum, 16, fill);
1460                         if (s >= smax) {
1461                             s[-1] = 0;
1462                             return;
1463                         }
1464                     }
1465                 } else {
1466                     unum = va_arg(*args, uint32_t);
1467                     cli_val2str(&s, smax, unum, 16, fill);
1468                     if (s >= smax) {
1469                         s[-1] = 0;
1470                         return;
1471                     }
1472                 }
1473                 break;
1474             case 'l':
1475                 bit64 = 1;
1476                 fmt++;
1477                 goto loop;
1478             case 'u':
1479                 if (bit64) {
1480                     *s = 0;
1481                     return;
1482                 }
1483 
1484                 unum = va_arg(*args, uint32_t);
1485 
1486                 cli_val2str(&s, smax, unum, 10, fill);
1487                 if (s >= smax) {
1488                     s[-1] = 0;
1489                     return;
1490                 }
1491                 break;
1492             case '0':
1493                 fmt++;
1494                 /* Make sure we have a number for fill length. */
1495                 if (((*fmt) < '0') || ((*fmt) > '9')) {
1496                     *s = 0;
1497                     return;
1498                 }
1499                 fill = strtoul((char *)fmt, (char **)&fmt, 0);
1500                 goto loop;
1501             default:
1502                 /* Exit on any other format specifier */
1503                 *s = 0;
1504                 return;
1505             }
1506             fmt++;
1507             continue;
1508         }
1509 
1510         *s++ = *fmt++;
1511         if (s == smax) {
1512             s[-1] = 0;
1513             return;
1514         }
1515     }
1516     *s = 0;
1517 }
1518