1 /*
2  * Arm SCP/MCP Software
3  * Copyright (c) 2020-2021, 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         status = fwk_io_putch(fwk_io_stdout, string[index]);
528         if (status != FWK_SUCCESS)
529             return status;
530     }
531     return status;
532 }
533 
cli_snprintf(char * s,char * smax,const char * fmt,...)534 void cli_snprintf(char *s, char *smax, const char *fmt, ...)
535 {
536     va_list args;
537 
538     va_start(args, fmt);
539     cli_snprintf_arg(s, smax, fmt, &args);
540     va_end(args);
541 
542     return;
543 }
544 
cli_getline(char * buffer,uint32_t buffer_size,char ** argbuf,uint32_t argbuf_size,uint32_t cursor_position)545 uint32_t cli_getline(
546     char *buffer,
547     uint32_t buffer_size,
548     char **argbuf,
549     uint32_t argbuf_size,
550     uint32_t cursor_position)
551 {
552     char c = 0;
553     uint32_t index = 0;
554     int32_t status = FWK_SUCCESS;
555 
556     /* Validate parameters. */
557     if (buffer == NULL ||
558         buffer_size == 0 ||
559         cursor_position >= cli_terminal_width ||
560         argbuf == NULL ||
561         argbuf_size == 0)
562         return FWK_E_PARAM;
563 
564     /* Zero out buffer and argbuf. */
565     memset(buffer, 0, buffer_size);
566     memset(argbuf, 0, argbuf_size * sizeof(char **));
567 
568     /* Print prompt arrow. */
569     status = cli_print(CLI_PROMPT);
570     if (status != FWK_SUCCESS)
571         return status;
572 
573     /* Increment cursor position since we just printed an arrow and space. */
574     cursor_position = (cursor_position + 2) % cli_terminal_width;
575 
576     /*
577      * Loop will terminate when the user presses enter or when an error is
578      * generated. This loop will have negligible impact on system performance.
579      */
580     while (1) {
581         /* Grab a character from the UART. */
582         do {
583             status = fwk_io_getch(fwk_io_stdin, &c);
584         } while (status == FWK_PENDING);
585 
586         if (status != FWK_SUCCESS)
587             return status;
588 
589         /*
590          * Ignore non-printing characters except for a few we care about.
591          * 0x00 - 0x1F are non-printing characters.
592          * 0x7F - 0xFF are non-printing characters.
593          * Carriage return: \r, 0x0D
594          * Newline:         \n, 0x0C
595          * Backspace:       \b, 0x08
596          * Delete:              0x7F
597          */
598         if ((c <= 0x1F || c >= 0x7F) && c != '\r' && c != '\n' && c != '\b' &&
599             c != 0x7F)
600             continue;
601 
602 
603         /* If backspace (0x08) or delete (0x7F) character received. */
604         if (c == '\b' || c == 0x7F) {
605             /* Only accept backspace presses if we're not at the beginning of
606              * the string. */
607             if (index != 0) {
608                 status = cli_backspace(&cursor_position, cli_terminal_width);
609                 if (status != FWK_SUCCESS)
610                     return status;
611 
612                 /* Decrement index and set old last character to null. */
613                 index = index - 1;
614                 buffer[index] = 0;
615             }
616             continue;
617         }
618 
619         /* If newline received. */
620         if (c == '\n' || c == '\r') {
621             /* Making sure the rest of the buffer is zero. */
622             memset(&(buffer[index]), 0, buffer_size - index);
623             status = cli_print("\n");
624             if (status != FWK_SUCCESS)
625                 return status;
626             break;
627         }
628 
629         /* Echo received character to console. */
630         do {
631             status = fwk_io_putch(fwk_io_stdout, c);
632         } while (status == FWK_PENDING);
633 
634         if (status != FWK_SUCCESS)
635             return status;
636 
637         cursor_position = (cursor_position + 1) % cli_terminal_width;
638 
639         /* Incrementing indices. */
640         buffer[index] = c;
641         index = index + 1;
642         if (index >= buffer_size) {
643             /*
644              * Add null termination in case the user doesn't check return codes
645              * and tries to use the buffer.
646              */
647             buffer[index - 1] = 0;
648             return FWK_E_NOMEM;
649         }
650 
651         /* Add new null terminator. */
652         buffer[index] = 0;
653     }
654 
655     status = cli_split(argbuf, argbuf_size, buffer, index, " ");
656     if (status != FWK_SUCCESS)
657         return status;
658 
659     return FWK_SUCCESS;
660 }
661 
662 /*****************************************************************************/
663 /* Private Function Definitions                                              */
664 /*****************************************************************************/
665 
cli_main(void const * argument)666 static void cli_main(void const *argument)
667 {
668     int32_t status;
669     uint32_t last_history_index = CLI_CONFIG_HISTORY_LENGTH - 1;
670     uint32_t command_length = 0;
671     bool cli_exit = false;
672 
673     /* Thread was started successfully, set state to CLI_RUNNING. */
674     cli_state = CLI_RUNNING;
675 
676     /* Initialize command history buffer pointers. */
677     for (uint32_t i = 0; i < CLI_CONFIG_HISTORY_LENGTH; i++)
678         cli_history[i] = &cli_history_buffer[i * CLI_CONFIG_COMMAND_BUF_SIZE];
679 
680     /* Loop forever. */
681     while (1) {
682         /* Printing prompt text. */
683         cli_printf(NONE, cli_prompt);
684 
685         /* Zero out input buffer. */
686         memset(cli_input_buffer, 0, CLI_CONFIG_COMMAND_BUF_SIZE);
687 
688         /* Get command from terminal UART. */
689         status = cli_get_command(
690             cli_input_buffer,
691             CLI_CONFIG_COMMAND_BUF_SIZE - 1,
692             &command_length,
693             cli_history,
694             history_index,
695             CLI_CONFIG_HISTORY_LENGTH,
696             &cli_exit);
697         if (status != FWK_SUCCESS) {
698             cli_error_handler(status);
699             continue;
700         }
701 
702         /* Ctrl+d was pressed to exit cli */
703         if (cli_exit) {
704             cli_state = CLI_READY;
705             return;
706         }
707 
708         /* Make sure command string is not empty. */
709         if (cli_input_buffer[0] == 0)
710             continue;
711 
712 
713         /* Update history buffer if command is different than the last one. */
714         if (cli_strncmp(
715                 cli_input_buffer,
716                 cli_history[last_history_index],
717                 CLI_CONFIG_COMMAND_BUF_SIZE) != 0) {
718             strncpy(
719                 cli_history[history_index],
720                 cli_input_buffer,
721                 CLI_CONFIG_COMMAND_BUF_SIZE);
722             history_index = (history_index + 1) % CLI_CONFIG_HISTORY_LENGTH;
723             last_history_index =
724                 (last_history_index + 1) % CLI_CONFIG_HISTORY_LENGTH;
725         }
726 
727         /* Splitting up command into individual argument strings. */
728         status = cli_split(
729             cli_args,
730             CLI_CONFIG_MAX_NUM_ARGUMENTS,
731             cli_input_buffer,
732             command_length,
733             " ");
734         if (status != FWK_SUCCESS) {
735             cli_error_handler(status);
736             continue;
737         }
738 
739         /* If the user didn't type any valid arguments, don't process it. */
740         if (cli_args[0] == 0)
741             continue;
742 
743         /* Dispatching command for processing. */
744         status = cli_command_dispatch(cli_args);
745         if (status != FWK_SUCCESS)
746             cli_error_handler(status);
747     }
748 }
749 
cli_format(cli_option_et options,char * buffer,uint32_t size)750 uint32_t cli_format(cli_option_et options, char *buffer, uint32_t size)
751 {
752     int32_t status = FWK_SUCCESS;
753     static char tmp_buf[10] = { 0 };
754 
755     if (buffer != NULL)
756         /* Add a null terminator before we do anything. */
757         buffer[0] = '\0';
758 
759     if (automation_mode)
760         return status;
761 
762     /* If no options given, send SGR default sequence to remove formatting. */
763     if (options == NONE) {
764         if (buffer != NULL) {
765             if (size < 5)
766                 return FWK_E_NOMEM;
767             memcpy(buffer, "\x1B[0m", 5);
768             buffer = &(buffer[4]);
769             size = size - 4;
770         } else
771             return cli_print("\x1B[0m");
772     }
773     /* Clear terminal window. */
774     if (options & CLEAR_DISPLAY) {
775         if (buffer != NULL) {
776             if (size < 5)
777                 return FWK_E_NOMEM;
778             memcpy(buffer, "\x1B[2J", 5);
779             buffer = &(buffer[4]);
780             size = size - 4;
781         } else
782             status = cli_print("\x1B[2J");
783 
784         if (status != FWK_SUCCESS)
785             return status;
786     }
787     /* Reset cursor. */
788     if (options & RESET_CURSOR) {
789         if (buffer != NULL) {
790             if (size < 7)
791                 return FWK_E_NOMEM;
792 
793             memcpy(buffer, "\x1B[0;0f", 7);
794             buffer = &(buffer[6]);
795             size = size - 6;
796         } else
797             status = cli_print("\x1B[0;0f");
798 
799         if (status != FWK_SUCCESS)
800             return status;
801     }
802     /* SGR settings. */
803     if (options & CLI_ALL_MASK) {
804         if (buffer != NULL) {
805             if (size < 3)
806                 return FWK_E_NOMEM;
807             memcpy(buffer, "\x1B[", 3);
808             buffer = &(buffer[2]);
809             size = size - 2;
810         } else {
811             status = cli_print("\x1B[");
812             if (status != FWK_SUCCESS)
813                 return status;
814         }
815         /* Bold/bright. */
816         if (options & BOLD) {
817             if (buffer != NULL) {
818                 if (size < 3)
819                     return FWK_E_NOMEM;
820 
821                 memcpy(buffer, ";1", 3);
822                 buffer = &(buffer[2]);
823                 size = size - 2;
824             } else {
825                 status = cli_print(";1");
826                 if (status != FWK_SUCCESS)
827                     return status;
828             }
829         }
830         /* Underlining. */
831         if (options & UNDERLINE) {
832             if (buffer != NULL) {
833                 if (size < 3)
834                     return FWK_E_NOMEM;
835                 memcpy(buffer, ";4", 3);
836                 buffer = &(buffer[2]);
837                 size = size - 2;
838             } else {
839                 status = cli_print(";4");
840                 if (status != FWK_SUCCESS)
841                     return status;
842             }
843         }
844         /* Background color. */
845         if (options & CLI_BG_COLOR_MASK) {
846             if (buffer != NULL) {
847                 if (size < 4)
848                     return FWK_E_NOMEM;
849 
850                 cli_snprintf(
851                     tmp_buf,
852                     tmp_buf + 10,
853                     (const char *)";%d",
854                     (uint16_t)(
855                         (options & CLI_BG_COLOR_MASK) >> CLI_BG_COLOR_SHIFT));
856                 memcpy(buffer, tmp_buf, 4);
857                 buffer = &(buffer[3]);
858                 size = size - 3;
859             } else {
860                 status = cli_printf(
861                     0,
862                     ";%d",
863                     (uint16_t)(
864                         (options & CLI_BG_COLOR_MASK) >> CLI_BG_COLOR_SHIFT));
865                 if (status != FWK_SUCCESS)
866                     return status;
867             }
868         }
869         /* Foreground color. */
870         if (options & CLI_TEXT_COLOR_MASK) {
871             if (buffer != NULL) {
872                 if (size < 4)
873                     return FWK_E_NOMEM;
874                 cli_snprintf(
875                     tmp_buf,
876                     tmp_buf + 10,
877                     (const char *)";%d",
878                     (char)((options & CLI_TEXT_COLOR_MASK) >>
879                         CLI_TEXT_COLOR_SHIFT));
880                 memcpy(buffer, tmp_buf, 4);
881                 buffer = &(buffer[3]);
882                 size = size - 3;
883             } else {
884                 status = cli_printf(
885                     0,
886                     ";%d",
887                     (char)((options & CLI_TEXT_COLOR_MASK) >>
888                         CLI_TEXT_COLOR_SHIFT));
889                 if (status != FWK_SUCCESS)
890                     return status;
891             }
892         }
893         if (buffer != NULL) {
894             if (size < 2)
895                 return FWK_E_NOMEM;
896             memcpy(buffer, "m", 2);
897         } else {
898             status = cli_print("m");
899             if (status != FWK_SUCCESS)
900                 return status;
901         }
902     }
903 
904     return FWK_SUCCESS;
905 }
906 
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)907 static uint32_t cli_get_command(
908     char *buffer,
909     uint32_t buffer_size,
910     uint32_t *command_length,
911     char **history,
912     uint32_t history_index,
913     uint32_t history_size,
914     bool *exit)
915 {
916     int32_t status = FWK_SUCCESS;
917     uint32_t index = 0;
918     uint32_t cursor_index = cli_prompt_size;
919     char c = 0;
920     static char escape[8] = { 0 };
921     uint32_t escape_index = 0;
922     uint32_t history_oldest = history_index;
923     bool flag_escape_sequence = false;
924 
925     (void)escape;
926 
927     /* Checking parameters. */
928     if (buffer == 0 || buffer_size == 0 || history == 0 ||
929         command_length == 0)
930         return FWK_E_PARAM;
931 
932     if (!automation_mode) {
933         /* Getting terminal window size. */
934         /* Saving cursor position. */
935         cli_print("\x1B[s");
936         /* Moving cursor to bottom right position. */
937         cli_print("\x1B[999;999f");
938         /* Requesting new cursor position. */
939         cli_print("\x1B[6n");
940         /* Restoring old cursor position. */
941         cli_print("\x1B[u");
942     }
943 
944     while (1) {
945         /* Get character from UART. */
946         do {
947             status = fwk_io_getch(fwk_io_stdin, &c);
948         } while (status == FWK_PENDING);
949 
950         if (status != FWK_SUCCESS)
951             return status;
952 
953         /* If we received a Ctrl+C press, go to debug mode. */
954         if (c == '\x03') {
955             *command_length = 0;
956             return cli_debug_output();
957         }
958 
959         /* If we received a Ctrl+d press, exit cli. */
960         if (c == '\x04') {
961             *exit = true;
962             return FWK_SUCCESS;
963         }
964 
965         /* Ignoring non-printing characters except for a few we care about. */
966         if (c < 0x20 && c != '\r' && c != '\n' && c != '\b') {
967             if (c == 0x1B)
968                 flag_escape_sequence = true;
969             continue;
970         }
971 
972         /* Dealing with escape sequences. */
973         if (flag_escape_sequence == true) {
974             escape[escape_index] = c;
975             escape[escape_index + 1] = 0;
976             escape_index = escape_index + 1;
977 
978             /* Escape sequences end with a letter. */
979             if ((c > 0x40 && c < 0x5B) || (c > 0x60 && c < 0x7B)) {
980                 flag_escape_sequence = false;
981                 escape_index = 0;
982 
983                 /* Up arrow press. */
984                 if (c == 'A') {
985                     if (((history_oldest + 1) % history_size) !=
986                         history_index) {
987                         /* Rewind history index by 1. */
988                         if (history_index == 0)
989                             history_index = history_size - 1;
990                         else
991                             history_index = history_index - 1;
992 
993                         /* If command entry is empty then stop and restore index
994                          * value. */
995                         if (history[history_index][0] == 0) {
996                             history_index = (history_index + 1) % history_size;
997                         } else {
998                             /* Erasing currently entered command. */
999                             for (; index > 0; index--) {
1000                                 status = cli_backspace(
1001                                     &cursor_index, cli_terminal_width);
1002                                 if (status != FWK_SUCCESS)
1003                                     return status;
1004                             }
1005 
1006                             /* Copying history command from history buffer. */
1007                             strncpy(
1008                                 buffer, history[history_index], buffer_size);
1009 
1010                             /* Printing history command to screen. */
1011                             while (buffer[index] != 0) {
1012                                 status =
1013                                     fwk_io_putch(fwk_io_stdout, buffer[index]);
1014                                 if (status != FWK_SUCCESS)
1015                                     return status;
1016                                 index = index + 1;
1017                                 cursor_index =
1018                                     (cursor_index + 1) % cli_terminal_width;
1019                             }
1020                         }
1021                     }
1022                 }
1023 
1024                 /* Down arrow press. */
1025                 if (c == 'B') {
1026                     if (history_index != history_oldest) {
1027                         /* Getting index of history item to load. */
1028                         history_index = (history_index + 1) % history_size;
1029 
1030                         /* Erasing everything in the currently entered command.
1031                          */
1032                         for (; index > 0; index--) {
1033                             status = cli_backspace(
1034                                 &cursor_index, cli_terminal_width);
1035                             if (status != FWK_SUCCESS)
1036                                 return status;
1037                         }
1038 
1039                         /* If we're back to the current command start fresh and
1040                          * zero buffer. */
1041                         if (history_index == history_oldest)
1042                             memset(buffer, 0, buffer_size);
1043                         else {
1044                             /* Copying history command from history buffer. */
1045                             strncpy(
1046                                 buffer, history[history_index], buffer_size);
1047 
1048                             /* Printing history command to screen. */
1049                             while (buffer[index] != 0) {
1050                                 status =
1051                                     fwk_io_putch(fwk_io_stdout, buffer[index]);
1052                                 if (status != FWK_SUCCESS)
1053                                     return status;
1054 
1055                                 index = index + 1;
1056                                 cursor_index =
1057                                     (cursor_index + 1) % cli_terminal_width;
1058                             }
1059                         }
1060                     }
1061                 }
1062 
1063                 /* Handling cursor position response sequence. */
1064                 if (c == 'R') {
1065                     uint32_t i = 0;
1066                     for (i = 0; escape[i] != '['; i++)
1067                         ;
1068                     i++;
1069                     cli_terminal_height = strtoul(&escape[i], NULL, 0);
1070                     for (; escape[i] != ';'; i++)
1071                         ;
1072                     i++;
1073                     cli_terminal_width = strtoul(&escape[i], NULL, 0);
1074                 }
1075             }
1076             continue;
1077         }
1078 
1079         /* If backspace (0x08) or delete (0x7F) character received. */
1080         if (c == 0x08 || c == 0x7F) {
1081             /* Only accept backspace presses if we're not at the beginning of
1082              * the string. */
1083             if (index != 0) {
1084                 status = cli_backspace(&cursor_index, cli_terminal_width);
1085                 if (status != FWK_SUCCESS)
1086                     return status;
1087                 index = index - 1;
1088                 buffer[index] = 0;
1089             }
1090             continue;
1091         }
1092 
1093         /* If newline received. */
1094         if (c == '\n' || c == '\r') {
1095             /* Making sure the rest of the buffer is zero. */
1096             memset(&(buffer[index]), 0, buffer_size - index);
1097             *command_length = index;
1098             return cli_print("\n");
1099         }
1100 
1101         /* Printing received character to console. */
1102         status = fwk_io_putch(fwk_io_stdout, c);
1103         if (status != FWK_SUCCESS)
1104             return status;
1105 
1106         /* Incrementing indices. */
1107         buffer[index] = c;
1108         index = index + 1;
1109         if (index >= buffer_size)
1110             return FWK_E_NOMEM;
1111         buffer[index] = 0;
1112         cursor_index = (cursor_index + 1) % cli_terminal_width;
1113     }
1114 }
1115 
cli_backspace(uint32_t * cursor,uint32_t width)1116 static uint32_t cli_backspace(uint32_t *cursor, uint32_t width)
1117 {
1118     uint32_t status = FWK_SUCCESS;
1119 
1120     /* If cursor is at the first position of a line. */
1121     if (*cursor == 0) {
1122         status = cli_printf(0, "\x1B[A\x1B[%dC ", width - 1);
1123         if (status != FWK_SUCCESS)
1124             return status;
1125         *cursor = width - 1;
1126     } else {
1127         /* For compatibility, print back, space, back. */
1128         status = cli_print("\x1B[D \x1B[D");
1129         if (status != FWK_SUCCESS)
1130             return status;
1131         *cursor = *cursor - 1;
1132     }
1133 
1134     return FWK_SUCCESS;
1135 }
1136 
cli_split(char ** argbuf,uint32_t argbuf_size,char * command,uint32_t command_length,const char * whitespace)1137 static uint32_t cli_split(
1138     char **argbuf,
1139     uint32_t argbuf_size,
1140     char *command,
1141     uint32_t command_length,
1142     const char *whitespace)
1143 {
1144     uint32_t index = 0;
1145     uint32_t argbuf_ctr = 0;
1146     bool flag_paren = false;
1147     bool flag_last_was_whitespace = true;
1148 
1149     /* Checking pointers. */
1150     if (argbuf == 0 || command == 0 || argbuf_size == 0 || whitespace == 0)
1151         return FWK_E_PARAM;
1152 
1153     argbuf[0] = 0;
1154 
1155     for (index = 0; index < command_length; index++) {
1156         /* If whitespace is encountered outside of parenthesis, change it to
1157          * null and set flag. */
1158         if (strchr(whitespace, command[index]) != 0 && flag_paren == false) {
1159             command[index] = 0;
1160             flag_last_was_whitespace = true;
1161             continue;
1162         }
1163 
1164         /* Handle parenthesis. */
1165         if (command[index] == '\"') {
1166             /* If we've reached the end of an arg in parenthesis, reset flag. */
1167             if (flag_paren == true) {
1168                 flag_paren = false;
1169                 command[index] = 0;
1170                 flag_last_was_whitespace = true;
1171                 continue;
1172             }
1173 
1174             /* If we receive an opening parenthesis preceded by a whitespace
1175              * character, mark null and enter parenthesis processing. */
1176             if (flag_paren == false && flag_last_was_whitespace == true) {
1177                 flag_paren = true;
1178                 command[index] = 0;
1179                 continue;
1180             }
1181         }
1182 
1183         /* First regular character after whitespace encountered. */
1184         if ((strchr(whitespace, command[index]) == 0 || flag_paren == true) &&
1185             flag_last_was_whitespace == true) {
1186             flag_last_was_whitespace = false;
1187             if (argbuf_ctr + 1 >= argbuf_size)
1188                 return FWK_E_NOMEM;
1189             argbuf[argbuf_ctr] = &command[index];
1190             argbuf[argbuf_ctr + 1] = 0;
1191             argbuf_ctr = argbuf_ctr + 1;
1192         }
1193     }
1194 
1195     /* If the user forgot to close their parenthesis, return an error. */
1196     if (flag_paren == true)
1197         return FWK_E_PARAM;
1198 
1199     return FWK_SUCCESS;
1200 }
1201 
cli_command_dispatch(char ** args)1202 static uint32_t cli_command_dispatch(char **args)
1203 {
1204     uint32_t index;
1205     uint32_t status;
1206     uint32_t num_args = 0;
1207     struct fwk_slist *node = NULL;
1208     struct command_ctx *cc = NULL;
1209     bool command_found;
1210 
1211     /* Checking pointer. */
1212     if (args == 0)
1213         return FWK_E_PARAM;
1214 
1215     /* Special case command: help. */
1216     if (cli_strncmp(args[0], "help", 5) == 0) {
1217         FWK_LIST_FOR_EACH(&cli_commands_list, node,
1218             struct command_ctx, list_node, cc) {
1219                 cli_printf(NONE, "%s\n", cc->cmd.command);
1220                 cli_print(cc->cmd.help);
1221                 cli_print("\n");
1222         }
1223 
1224         cli_printf(NONE, "help\n");
1225         cli_print("  Displays this information.\n");
1226         cli_printf(NONE, "Ctrl+C\n");
1227         cli_print("  Displays debug output from running threads.\n");
1228         cli_printf(NONE, "Ctrl+d\n");
1229         cli_print("  Exit the CLI.\n");
1230         return FWK_SUCCESS;
1231     }
1232 
1233     if (cli_strncmp(args[0], "AUTO", 4) == 0) {
1234         cli_printf(NONE, "AUTO Mode ON\n");
1235         automation_mode = 1;
1236         return FWK_SUCCESS;
1237     }
1238 
1239     if (cli_strncmp(args[0], "OTUA", 4) == 0) {
1240         cli_printf(NONE, "AUTO Mode OFF\n");
1241         automation_mode = 0;
1242         return FWK_SUCCESS;
1243     }
1244 
1245     /* Searching for command handler. */
1246     /* Using strcmp here because each entry in args is guaranteed to be null
1247      * terminated by cli_split. */
1248     command_found = false;
1249     FWK_LIST_FOR_EACH(&cli_commands_list, node,
1250         struct command_ctx, list_node, cc) {
1251         if (strcmp(args[0], cc->cmd.command) == 0) {
1252             command_found = true;
1253             break;
1254         }
1255     }
1256     if (!command_found)
1257         return FWK_E_SUPPORT;
1258 
1259     /* Handler found, if -h or --help is given just print it's help string and
1260      * return. */
1261     if (cc->cmd.ignore_help_flag == false) {
1262         for (index = 1;
1263              (args[index] != 0) && (cli_strncmp(args[index], "-h", 3) != 0) &&
1264              (cli_strncmp(args[index], "--help", 7) != 0);
1265              index++)
1266             ;
1267 
1268         if ((args[index] != 0 || cc->cmd.handler == 0) &&
1269             cc->cmd.help != 0) {
1270             status = cli_print(cc->cmd.help);
1271             if (status != FWK_SUCCESS)
1272                 return status;
1273 
1274             /* Print a newline since help strings shouldn't have newlines at the
1275              * end. */
1276             cli_print("\n");
1277             return status;
1278         }
1279     }
1280 
1281     /* Counting arguments. */
1282     for (num_args = 0; args[num_args] != 0; num_args++)
1283         ;
1284 
1285     /* Calling command handler. */
1286     /* args is incremented so the command just gets the arguments and not the
1287      * name of itself. */
1288     if (cc->cmd.handler != 0)
1289         return cc->cmd.handler(num_args, args);
1290     else
1291         return FWK_E_PARAM;
1292 }
1293 
cli_debug_output(void)1294 static uint32_t cli_debug_output(void)
1295 {
1296     uint32_t fifo_status;
1297     char c = 0;
1298 
1299     cli_printf(
1300         0,
1301         "\nNow showing debug console output, to return to the command line, "
1302         "press Ctrl+C.\n");
1303     while (1) {
1304         /* Looking for Ctrl+C press. */
1305         if (fwk_io_getch(fwk_io_stdin, &c) == FWK_SUCCESS) {
1306             if (c == '\x03') {
1307                 cli_printf(
1308                     0,
1309                     "\nNow showing command line, to return to debug output, "
1310                     "press Ctrl+C.\n");
1311                 return FWK_SUCCESS;
1312             }
1313         }
1314 
1315         /* Read from print FIFO. */
1316         fifo_status = fifo_get(&cli_print_fifo, &c);
1317         if (fifo_status == FWK_SUCCESS) {
1318             overflow = false;
1319             fwk_io_putch(fwk_io_stdout, c);
1320         } else
1321             /* If no characters are available, let other stuff run. */
1322             cli_platform_delay_ms(0);
1323     }
1324 }
1325 
cli_error_handler(uint32_t status)1326 static void cli_error_handler(uint32_t status)
1327 {
1328     if (status == FWK_SUCCESS)
1329         return;
1330 
1331     cli_printf(NONE, "CONSOLE ERROR: %s\n", fwk_status_str(status));
1332 }
1333 
cli_val2str(char ** outstr,char * smax,uint32_t value,uint32_t base,int32_t fill)1334 static void cli_val2str(
1335     char **outstr,
1336     char *smax,
1337     uint32_t value,
1338     uint32_t base,
1339     int32_t fill)
1340 {
1341     /* Just need enough space to store 64 bit decimal integer */
1342     unsigned char str[20] = { 0 };
1343     int i = 0;
1344 
1345     do {
1346         str[i++] = "0123456789ABCDEF"[value % base];
1347     } while (value /= base);
1348 
1349     while (--fill >= i) {
1350         **outstr = '0';
1351         *outstr = *outstr + 1;
1352         if (*outstr >= smax)
1353             return;
1354     }
1355 
1356     while (--i >= 0) {
1357         **outstr = str[i];
1358         *outstr = *outstr + 1;
1359         if (*outstr >= smax)
1360             return;
1361     }
1362 }
1363 
cli_snprintf_arg(char * s,char * smax,const char * fmt,va_list * args)1364 static void cli_snprintf_arg(
1365     char *s,
1366     char *smax,
1367     const char *fmt,
1368     va_list *args)
1369 {
1370     int bit64;
1371     int fill;
1372     int64_t num = 0;
1373     uint64_t unum = 0;
1374     char *str = NULL;
1375     uint32_t most_significant = 0;
1376     char c = 0;
1377 
1378     while (*fmt) {
1379         if (*fmt == '%') {
1380             fmt++;
1381             bit64 = 0;
1382             fill = 0;
1383         /* Check the format specifier */
1384 loop:
1385             switch (*fmt) {
1386             case 'c':
1387                 c = (char)va_arg(*args, int32_t);
1388                 *s++ = c;
1389                 if (s >= smax) {
1390                     s[-1] = 0;
1391                     return;
1392                 }
1393                 break;
1394             case 'i': /* Specifiers i and d do the same thing. */
1395             case 'd':
1396                 if (bit64) {
1397                     *s = 0;
1398                     return;
1399                 }
1400 
1401                 num = va_arg(*args, int32_t);
1402 
1403                 if (num < 0) {
1404                     *s++ = '-';
1405                     if (s >= smax) {
1406                         s[-1] = 0;
1407                         return;
1408                     }
1409                     unum = (unsigned long int)-num;
1410                 } else {
1411                     unum = (unsigned long int)num;
1412                 }
1413 
1414                 cli_val2str(&s, smax, unum, 10, fill);
1415                 if (s >= smax) {
1416                     s[-1] = 0;
1417                     return;
1418                 }
1419                 break;
1420             case 's':
1421                 str = va_arg(*args, char *);
1422                 while (*str) {
1423                     *s++ = *str++;
1424                     if (s >= smax) {
1425                         s[-1] = 0;
1426                         return;
1427                     }
1428                 }
1429                 break;
1430             case 'x': /* All hex prints use uppercase hex digits. */
1431             case 'X':
1432                 if (bit64) {
1433                     unum = va_arg(*args, uint64_t);
1434                     most_significant = (uint32_t)(unum >> 32);
1435                     if (most_significant) {
1436                         cli_val2str(
1437                             &s,
1438                             smax,
1439                             most_significant,
1440                             16,
1441                             (fill >= 8) ? fill - 8 : 0);
1442                         if (s >= smax) {
1443                             s[-1] = 0;
1444                             return;
1445                         }
1446                         cli_val2str(&s, smax, unum, 16, 8);
1447                         if (s >= smax) {
1448                             s[-1] = 0;
1449                             return;
1450                         }
1451                     } else {
1452                         cli_val2str(&s, smax, unum, 16, fill);
1453                         if (s >= smax) {
1454                             s[-1] = 0;
1455                             return;
1456                         }
1457                     }
1458                 } else {
1459                     unum = va_arg(*args, uint32_t);
1460                     cli_val2str(&s, smax, unum, 16, fill);
1461                     if (s >= smax) {
1462                         s[-1] = 0;
1463                         return;
1464                     }
1465                 }
1466                 break;
1467             case 'l':
1468                 bit64 = 1;
1469                 fmt++;
1470                 goto loop;
1471             case 'u':
1472                 if (bit64) {
1473                     *s = 0;
1474                     return;
1475                 }
1476 
1477                 unum = va_arg(*args, uint32_t);
1478 
1479                 cli_val2str(&s, smax, unum, 10, fill);
1480                 if (s >= smax) {
1481                     s[-1] = 0;
1482                     return;
1483                 }
1484                 break;
1485             case '0':
1486                 fmt++;
1487                 /* Make sure we have a number for fill length. */
1488                 if (((*fmt) < '0') || ((*fmt) > '9')) {
1489                     *s = 0;
1490                     return;
1491                 }
1492                 fill = strtoul((char *)fmt, (char **)&fmt, 0);
1493                 goto loop;
1494             default:
1495                 /* Exit on any other format specifier */
1496                 *s = 0;
1497                 return;
1498             }
1499             fmt++;
1500             continue;
1501         }
1502 
1503         *s++ = *fmt++;
1504         if (s == smax) {
1505             s[-1] = 0;
1506             return;
1507         }
1508     }
1509     *s = 0;
1510 }
1511