1 /*
2  * Copyright (c) 2008-2009 Travis Geiselbrecht
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining
5  * a copy of this software and associated documentation files
6  * (the "Software"), to deal in the Software without restriction,
7  * including without limitation the rights to use, copy, modify, merge,
8  * publish, distribute, sublicense, and/or sell copies of the Software,
9  * and to permit persons to whom the Software is furnished to do so,
10  * subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be
13  * included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22  */
23 #include <debug.h>
24 #include <trace.h>
25 #include <assert.h>
26 #include <err.h>
27 #include <string.h>
28 #include <ctype.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <kernel/thread.h>
32 #include <kernel/mutex.h>
33 #include <lib/console.h>
34 #if WITH_LIB_ENV
35 #include <lib/env.h>
36 #endif
37 
38 #ifndef CONSOLE_ENABLE_HISTORY
39 #define CONSOLE_ENABLE_HISTORY 1
40 #endif
41 
42 // Whether to enable "repeat" command.
43 #ifndef CONSOLE_ENABLE_REPEAT
44 #define CONSOLE_ENABLE_REPEAT 1
45 #endif
46 
47 #define LINE_LEN 128
48 
49 #define PANIC_LINE_LEN 32
50 
51 #define MAX_NUM_ARGS 16
52 
53 #define HISTORY_LEN 16
54 
55 #define LOCAL_TRACE 0
56 
57 #define WHITESPACE " \t"
58 
59 /* debug buffer */
60 static char *debug_buffer;
61 
62 /* echo commands? */
63 static bool echo = true;
64 
65 /* command processor state */
66 static mutex_t *command_lock;
67 int lastresult;
68 static bool abort_script;
69 
70 #if CONSOLE_ENABLE_HISTORY
71 /* command history stuff */
72 static char *history; // HISTORY_LEN rows of LINE_LEN chars a piece
73 static uint history_next;
74 
75 static void init_history(void);
76 static void add_history(const char *line);
77 static uint start_history_cursor(void);
78 static const char *next_history(uint *cursor);
79 static const char *prev_history(uint *cursor);
80 static void dump_history(void);
81 #endif
82 
83 /* list of installed commands */
84 static cmd_block *command_list = NULL;
85 
86 /* a linear array of statically defined command blocks,
87    defined in the linker script.
88  */
89 extern cmd_block __commands_start[];
90 extern cmd_block __commands_end[];
91 
92 static int cmd_help(int argc, const cmd_args *argv);
93 static int cmd_help_panic(int argc, const cmd_args *argv);
94 static int cmd_echo(int argc, const cmd_args *argv);
95 static int cmd_test(int argc, const cmd_args *argv);
96 #if CONSOLE_ENABLE_HISTORY
97 static int cmd_history(int argc, const cmd_args *argv);
98 #endif
99 #if CONSOLE_ENABLE_REPEAT
100 static int cmd_repeat(int argc, const cmd_args *argv);
101 #endif
102 
103 STATIC_COMMAND_START
104 STATIC_COMMAND("help", "this list", &cmd_help)
105 STATIC_COMMAND_MASKED("help", "this list", &cmd_help_panic, CMD_AVAIL_PANIC)
106 STATIC_COMMAND("echo", NULL, &cmd_echo)
107 #if LK_DEBUGLEVEL > 1
108 STATIC_COMMAND("test", "test the command processor", &cmd_test)
109 #if CONSOLE_ENABLE_HISTORY
110 STATIC_COMMAND("history", "command history", &cmd_history)
111 #endif
112 #if CONSOLE_ENABLE_REPEAT
113 STATIC_COMMAND("repeat", "repeats command multiple times", &cmd_repeat)
114 #endif
115 #endif
116 STATIC_COMMAND_END(help);
117 
console_init(void)118 int console_init(void)
119 {
120     LTRACE_ENTRY;
121 
122     command_lock = calloc(sizeof(mutex_t), 1);
123     mutex_init(command_lock);
124 
125     /* add all the statically defined commands to the list */
126     cmd_block *block;
127     for (block = __commands_start; block != __commands_end; block++) {
128         console_register_commands(block);
129     }
130 
131 #if CONSOLE_ENABLE_HISTORY
132     init_history();
133 #endif
134 
135     return 0;
136 }
137 
138 #if CONSOLE_ENABLE_HISTORY
cmd_history(int argc,const cmd_args * argv)139 static int cmd_history(int argc, const cmd_args *argv)
140 {
141     dump_history();
142     return 0;
143 }
144 
history_line(uint line)145 static inline char *history_line(uint line)
146 {
147     return history + line * LINE_LEN;
148 }
149 
ptrnext(uint ptr)150 static inline uint ptrnext(uint ptr)
151 {
152     return (ptr + 1) % HISTORY_LEN;
153 }
154 
ptrprev(uint ptr)155 static inline uint ptrprev(uint ptr)
156 {
157     return (ptr - 1) % HISTORY_LEN;
158 }
159 
dump_history(void)160 static void dump_history(void)
161 {
162     printf("command history:\n");
163     uint ptr = ptrprev(history_next);
164     int i;
165     for (i=0; i < HISTORY_LEN; i++) {
166         if (history_line(ptr)[0] != 0)
167             printf("\t%s\n", history_line(ptr));
168         ptr = ptrprev(ptr);
169     }
170 }
171 
init_history(void)172 static void init_history(void)
173 {
174     /* allocate and set up the history buffer */
175     history = calloc(1, HISTORY_LEN * LINE_LEN);
176     history_next = 0;
177 }
178 
add_history(const char * line)179 static void add_history(const char *line)
180 {
181     // reject some stuff
182     if (line[0] == 0)
183         return;
184 
185     uint last = ptrprev(history_next);
186     if (strcmp(line, history_line(last)) == 0)
187         return;
188 
189     strlcpy(history_line(history_next), line, LINE_LEN);
190     history_next = ptrnext(history_next);
191 }
192 
start_history_cursor(void)193 static uint start_history_cursor(void)
194 {
195     return ptrprev(history_next);
196 }
197 
next_history(uint * cursor)198 static const char *next_history(uint *cursor)
199 {
200     uint i = ptrnext(*cursor);
201 
202     if (i == history_next)
203         return ""; // can't let the cursor hit the head
204 
205     *cursor = i;
206     return history_line(i);
207 }
208 
prev_history(uint * cursor)209 static const char *prev_history(uint *cursor)
210 {
211     uint i;
212     const char *str = history_line(*cursor);
213 
214     /* if we are already at head, stop here */
215     if (*cursor == history_next)
216         return str;
217 
218     /* back up one */
219     i = ptrprev(*cursor);
220 
221     /* if the next one is gonna be null */
222     if (history_line(i)[0] == '\0')
223         return str;
224 
225     /* update the cursor */
226     *cursor = i;
227     return str;
228 }
229 #endif  // CONSOLE_ENABLE_HISTORY
230 
231 #if CONSOLE_ENABLE_REPEAT
cmd_repeat(int argc,const cmd_args * argv)232 static int cmd_repeat(int argc, const cmd_args* argv)
233 {
234     if (argc < 4) goto usage;
235     int times = argv[1].i;
236     int delay = argv[2].i;
237     if (times <= 0) goto usage;
238     if (delay < 0) goto usage;
239 
240     // Worst case line length with quoting.
241     char line[LINE_LEN + MAX_NUM_ARGS * 3];
242 
243     // Paste together all arguments, and quote them.
244     int idx = 0;
245     for (int i = 3; i < argc; ++i) {
246         if (i != 3) {
247             // Add a space before all args but the first.
248             line[idx++] = ' ';
249         }
250         line[idx++] = '"';
251         for (const char* src = argv[i].str; *src != '\0'; src++) {
252             line[idx++] = *src;
253         }
254         line[idx++] = '"';
255     }
256     line[idx] = '\0';
257 
258     for (int i = 0; i < times; ++i) {
259         printf("[%d/%d]\n", i + 1, times);
260         int result = console_run_script_locked(line);
261         if (result != 0) {
262             printf("terminating repeat loop, command exited with status %d\n",
263                     result);
264             return result;
265         }
266         thread_sleep(delay);
267     }
268     return NO_ERROR;
269 
270 usage:
271     printf("Usage: repeat <times> <delay in ms> <cmd> [args..]\n");
272     return ERR_INVALID_ARGS;
273 }
274 #endif  // CONSOLE_ENABLE_REPEAT
275 
match_command(const char * command,const uint8_t availability_mask)276 static const cmd *match_command(const char *command, const uint8_t availability_mask)
277 {
278     cmd_block *block;
279     size_t i;
280 
281     for (block = command_list; block != NULL; block = block->next) {
282         const cmd *curr_cmd = block->list;
283         for (i = 0; i < block->count; i++) {
284             if ((availability_mask & curr_cmd[i].availability_mask) == 0) {
285                 continue;
286             }
287             if (strcmp(command, curr_cmd[i].cmd_str) == 0) {
288                 return &curr_cmd[i];
289             }
290         }
291     }
292 
293     return NULL;
294 }
295 
read_debug_line(const char ** outbuffer,void * cookie)296 static int read_debug_line(const char **outbuffer, void *cookie)
297 {
298     int pos = 0;
299     int escape_level = 0;
300 #if CONSOLE_ENABLE_HISTORY
301     uint history_cursor = start_history_cursor();
302 #endif
303 
304     char *buffer = debug_buffer;
305 
306     for (;;) {
307         /* loop until we get a char */
308         int c;
309         if ((c = getchar()) < 0)
310             continue;
311 
312 //      TRACEF("c = 0x%hhx\n", c);
313 
314         if (escape_level == 0) {
315             switch (c) {
316                 case '\r':
317                 case '\n':
318                     if (echo)
319                         putchar('\n');
320                     goto done;
321 
322                 case 0x7f: // backspace or delete
323                 case 0x8:
324                     if (pos > 0) {
325                         pos--;
326                         fputs("\b \b", stdout); // wipe out a character
327                     }
328                     break;
329 
330                 case 0x1b: // escape
331                     escape_level++;
332                     break;
333 
334                 default:
335                     buffer[pos++] = c;
336                     if (echo)
337                         putchar(c);
338             }
339         } else if (escape_level == 1) {
340             // inside an escape, look for '['
341             if (c == '[') {
342                 escape_level++;
343             } else {
344                 // we didn't get it, abort
345                 escape_level = 0;
346             }
347         } else { // escape_level > 1
348             switch (c) {
349                 case 67: // right arrow
350                     buffer[pos++] = ' ';
351                     if (echo)
352                         putchar(' ');
353                     break;
354                 case 68: // left arrow
355                     if (pos > 0) {
356                         pos--;
357                         if (echo) {
358                             fputs("\b \b", stdout); // wipe out a character
359                         }
360                     }
361                     break;
362 #if CONSOLE_ENABLE_HISTORY
363                 case 65: // up arrow -- previous history
364                 case 66: // down arrow -- next history
365                     // wipe out the current line
366                     while (pos > 0) {
367                         pos--;
368                         if (echo) {
369                             fputs("\b \b", stdout); // wipe out a character
370                         }
371                     }
372 
373                     if (c == 65)
374                         strlcpy(buffer, prev_history(&history_cursor), LINE_LEN);
375                     else
376                         strlcpy(buffer, next_history(&history_cursor), LINE_LEN);
377                     pos = strlen(buffer);
378                     if (echo)
379                         fputs(buffer, stdout);
380                     break;
381 #endif
382                 default:
383                     break;
384             }
385             escape_level = 0;
386         }
387 
388         /* end of line. */
389         if (pos == (LINE_LEN - 1)) {
390             fputs("\nerror: line too long\n", stdout);
391             pos = 0;
392             goto done;
393         }
394     }
395 
396 done:
397 //  dprintf("returning pos %d\n", pos);
398 
399     // null terminate
400     buffer[pos] = 0;
401 
402 #if CONSOLE_ENABLE_HISTORY
403     // add to history
404     add_history(buffer);
405 #endif
406 
407     // return a pointer to our buffer
408     *outbuffer = buffer;
409 
410     return pos;
411 }
412 
tokenize_command(const char * inbuffer,const char ** continuebuffer,char * buffer,size_t buflen,cmd_args * args,int arg_count)413 static int tokenize_command(const char *inbuffer, const char **continuebuffer, char *buffer, size_t buflen, cmd_args *args, int arg_count)
414 {
415     int inpos;
416     int outpos;
417     int arg;
418     enum {
419         INITIAL = 0,
420         NEXT_FIELD,
421         SPACE,
422         IN_SPACE,
423         TOKEN,
424         IN_TOKEN,
425         QUOTED_TOKEN,
426         IN_QUOTED_TOKEN,
427         VAR,
428         IN_VAR,
429         COMMAND_SEP,
430     } state;
431     char varname[128];
432     int varnamepos;
433 
434     inpos = 0;
435     outpos = 0;
436     arg = 0;
437     varnamepos = 0;
438     state = INITIAL;
439     *continuebuffer = NULL;
440 
441     for (;;) {
442         char c = inbuffer[inpos];
443 
444 //      dprintf(SPEW, "c 0x%hhx state %d arg %d inpos %d pos %d\n", c, state, arg, inpos, outpos);
445 
446         switch (state) {
447             case INITIAL:
448             case NEXT_FIELD:
449                 if (c == '\0')
450                     goto done;
451                 if (isspace(c))
452                     state = SPACE;
453                 else if (c == ';')
454                     state = COMMAND_SEP;
455                 else
456                     state = TOKEN;
457                 break;
458             case SPACE:
459                 state = IN_SPACE;
460                 break;
461             case IN_SPACE:
462                 if (c == '\0')
463                     goto done;
464                 if (c == ';') {
465                     state = COMMAND_SEP;
466                 } else if (!isspace(c)) {
467                     state = TOKEN;
468                 } else {
469                     inpos++; // consume the space
470                 }
471                 break;
472             case TOKEN:
473                 // start of a token
474                 DEBUG_ASSERT(c != '\0');
475                 if (c == '"') {
476                     // start of a quoted token
477                     state = QUOTED_TOKEN;
478                 } else if (c == '$') {
479                     // start of a variable
480                     state = VAR;
481                 } else {
482                     // regular, unquoted token
483                     state = IN_TOKEN;
484                     args[arg].str = &buffer[outpos];
485                 }
486                 break;
487             case IN_TOKEN:
488                 if (c == '\0') {
489                     arg++;
490                     goto done;
491                 }
492                 if (isspace(c) || c == ';') {
493                     arg++;
494                     buffer[outpos] = 0;
495                     outpos++;
496                     /* are we out of tokens? */
497                     if (arg == arg_count)
498                         goto done;
499                     state = NEXT_FIELD;
500                 } else {
501                     buffer[outpos] = c;
502                     outpos++;
503                     inpos++;
504                 }
505                 break;
506             case QUOTED_TOKEN:
507                 // start of a quoted token
508                 DEBUG_ASSERT(c == '"');
509 
510                 state = IN_QUOTED_TOKEN;
511                 args[arg].str = &buffer[outpos];
512                 inpos++; // consume the quote
513                 break;
514             case IN_QUOTED_TOKEN:
515                 if (c == '\0') {
516                     arg++;
517                     goto done;
518                 }
519                 if (c == '"') {
520                     arg++;
521                     buffer[outpos] = 0;
522                     outpos++;
523                     /* are we out of tokens? */
524                     if (arg == arg_count)
525                         goto done;
526 
527                     state = NEXT_FIELD;
528                 }
529                 buffer[outpos] = c;
530                 outpos++;
531                 inpos++;
532                 break;
533             case VAR:
534                 DEBUG_ASSERT(c == '$');
535 
536                 state = IN_VAR;
537                 args[arg].str = &buffer[outpos];
538                 inpos++; // consume the dollar sign
539 
540                 // initialize the place to store the variable name
541                 varnamepos = 0;
542                 break;
543             case IN_VAR:
544                 if (c == '\0' || isspace(c) || c == ';') {
545                     // hit the end of variable, look it up and stick it inline
546                     varname[varnamepos] = 0;
547 #if WITH_LIB_ENV
548                     int rc = env_get(varname, &buffer[outpos], buflen - outpos);
549 #else
550                     (void)varname[0]; // nuke a warning
551                     int rc = -1;
552 #endif
553                     if (rc < 0) {
554                         buffer[outpos++] = '0';
555                         buffer[outpos++] = 0;
556                     } else {
557                         outpos += strlen(&buffer[outpos]) + 1;
558                     }
559                     arg++;
560                     /* are we out of tokens? */
561                     if (arg == arg_count)
562                         goto done;
563 
564                     state = NEXT_FIELD;
565                 } else {
566                     varname[varnamepos] = c;
567                     varnamepos++;
568                     inpos++;
569                 }
570                 break;
571             case COMMAND_SEP:
572                 // we hit a ;, so terminate the command and pass the remainder of the command back in continuebuffer
573                 DEBUG_ASSERT(c == ';');
574 
575                 inpos++; // consume the ';'
576                 *continuebuffer = &inbuffer[inpos];
577                 goto done;
578         }
579     }
580 
581 done:
582     buffer[outpos] = 0;
583     return arg;
584 }
585 
convert_args(int argc,cmd_args * argv)586 static void convert_args(int argc, cmd_args *argv)
587 {
588     int i;
589 
590     for (i = 0; i < argc; i++) {
591         unsigned long u = atoul(argv[i].str);
592         argv[i].u = u;
593         argv[i].p = (void *)u;
594         argv[i].i = atol(argv[i].str);
595 
596         if (!strcmp(argv[i].str, "true") || !strcmp(argv[i].str, "on")) {
597             argv[i].b = true;
598         } else if (!strcmp(argv[i].str, "false") || !strcmp(argv[i].str, "off")) {
599             argv[i].b = false;
600         } else {
601             argv[i].b = (argv[i].u == 0) ? false : true;
602         }
603     }
604 }
605 
606 
command_loop(int (* get_line)(const char **,void *),void * get_line_cookie,bool showprompt,bool locked)607 static status_t command_loop(int (*get_line)(const char **, void *), void *get_line_cookie, bool showprompt, bool locked)
608 {
609     bool exit;
610 #if WITH_LIB_ENV
611     bool report_result;
612 #endif
613     cmd_args *args = NULL;
614     const char *buffer;
615     const char *continuebuffer;
616     char *outbuf = NULL;
617 
618     args = (cmd_args *) malloc (MAX_NUM_ARGS * sizeof(cmd_args));
619     if (unlikely(args == NULL)) {
620         goto no_mem_error;
621     }
622 
623     const size_t outbuflen = 1024;
624     outbuf = malloc(outbuflen);
625     if (unlikely(outbuf == NULL)) {
626         goto no_mem_error;
627     }
628 
629     exit = false;
630     continuebuffer = NULL;
631     while (!exit) {
632         // read a new line if it hadn't been split previously and passed back from tokenize_command
633         if (continuebuffer == NULL) {
634             if (showprompt)
635                 fputs("] ", stdout);
636 
637             int len = get_line(&buffer, get_line_cookie);
638             if (len < 0)
639                 break;
640             if (len == 0)
641                 continue;
642         } else {
643             buffer = continuebuffer;
644         }
645 
646 //      dprintf("line = '%s'\n", buffer);
647 
648         /* tokenize the line */
649         int argc = tokenize_command(buffer, &continuebuffer, outbuf, outbuflen,
650                                     args, MAX_NUM_ARGS);
651         if (argc < 0) {
652             if (showprompt)
653                 printf("syntax error\n");
654             continue;
655         } else if (argc == 0) {
656             continue;
657         }
658 
659 //      dprintf("after tokenize: argc %d\n", argc);
660 //      for (int i = 0; i < argc; i++)
661 //          dprintf("%d: '%s'\n", i, args[i].str);
662 
663         /* convert the args */
664         convert_args(argc, args);
665 
666         /* try to match the command */
667         const cmd *command = match_command(args[0].str, CMD_AVAIL_NORMAL);
668         if (!command) {
669             if (showprompt)
670                 printf("command not found\n");
671             continue;
672         }
673 
674         if (!locked)
675             mutex_acquire(command_lock);
676 
677         abort_script = false;
678         lastresult = command->cmd_callback(argc, args);
679 
680 #if WITH_LIB_ENV
681         bool report_result;
682         env_get_bool("reportresult", &report_result, false);
683         if (report_result) {
684             if (lastresult < 0)
685                 printf("FAIL %d\n", lastresult);
686             else
687                 printf("PASS %d\n", lastresult);
688         }
689 #endif
690 
691 #if WITH_LIB_ENV
692         // stuff the result in an environment var
693         env_set_int("?", lastresult, true);
694 #endif
695 
696         // someone must have aborted the current script
697         if (abort_script)
698             exit = true;
699         abort_script = false;
700 
701         if (!locked)
702             mutex_release(command_lock);
703     }
704 
705     free(outbuf);
706     free(args);
707     return NO_ERROR;
708 
709 no_mem_error:
710     if (outbuf)
711         free(outbuf);
712 
713     if (args)
714         free(args);
715 
716     dprintf(INFO, "%s: not enough memory\n", __func__);
717     return ERR_NO_MEMORY;
718 }
719 
console_abort_script(void)720 void console_abort_script(void)
721 {
722     abort_script = true;
723 }
724 
console_start(void)725 void console_start(void)
726 {
727     debug_buffer = malloc(LINE_LEN);
728 
729     dprintf(INFO, "entering main console loop\n");
730 
731 
732     while (command_loop(&read_debug_line, NULL, true, false) == NO_ERROR)
733         ;
734 
735     dprintf(INFO, "exiting main console loop\n");
736 
737     free (debug_buffer);
738 }
739 
740 struct line_read_struct {
741     const char *string;
742     int pos;
743     char *buffer;
744     size_t buflen;
745 };
746 
fetch_next_line(const char ** buffer,void * cookie)747 static int fetch_next_line(const char **buffer, void *cookie)
748 {
749     struct line_read_struct *lineread = (struct line_read_struct *)cookie;
750 
751     // we're done
752     if (lineread->string[lineread->pos] == 0)
753         return -1;
754 
755     size_t bufpos = 0;
756     while (lineread->string[lineread->pos] != 0) {
757         if (lineread->string[lineread->pos] == '\n') {
758             lineread->pos++;
759             break;
760         }
761         if (bufpos == (lineread->buflen - 1))
762             break;
763         lineread->buffer[bufpos] = lineread->string[lineread->pos];
764         lineread->pos++;
765         bufpos++;
766     }
767     lineread->buffer[bufpos] = 0;
768 
769     *buffer = lineread->buffer;
770 
771     return bufpos;
772 }
773 
console_run_script_etc(const char * string,bool locked)774 static int console_run_script_etc(const char *string, bool locked)
775 {
776     struct line_read_struct lineread;
777 
778     lineread.string = string;
779     lineread.pos = 0;
780     lineread.buffer = malloc(LINE_LEN);
781     lineread.buflen = LINE_LEN;
782 
783     command_loop(&fetch_next_line, (void *)&lineread, false, locked);
784 
785     free(lineread.buffer);
786 
787     return lastresult;
788 }
789 
console_run_script(const char * string)790 int console_run_script(const char *string)
791 {
792     return console_run_script_etc(string, false);
793 }
794 
console_run_script_locked(const char * string)795 int console_run_script_locked(const char *string)
796 {
797     return console_run_script_etc(string, true);
798 }
799 
console_get_command_handler(const char * commandstr)800 console_cmd console_get_command_handler(const char *commandstr)
801 {
802     const cmd *command = match_command(commandstr, CMD_AVAIL_NORMAL);
803 
804     if (command)
805         return command->cmd_callback;
806     else
807         return NULL;
808 }
809 
console_register_commands(cmd_block * block)810 void console_register_commands(cmd_block *block)
811 {
812     DEBUG_ASSERT(block);
813     DEBUG_ASSERT(block->next == NULL);
814 
815     block->next = command_list;
816     command_list = block;
817 }
818 
819 
cmd_help_impl(uint8_t availability_mask)820 static int cmd_help_impl(uint8_t availability_mask)
821 {
822     printf("command list:\n");
823 
824     cmd_block *block;
825     size_t i;
826 
827     for (block = command_list; block != NULL; block = block->next) {
828         const cmd *curr_cmd = block->list;
829         for (i = 0; i < block->count; i++) {
830             if ((availability_mask & curr_cmd[i].availability_mask) == 0) {
831                 // Skip commands that aren't available in the current shell.
832                 continue;
833             }
834             if (curr_cmd[i].help_str)
835                 printf("\t%-16s: %s\n", curr_cmd[i].cmd_str, curr_cmd[i].help_str);
836         }
837     }
838 
839     return 0;
840 }
841 
cmd_help(int argc,const cmd_args * argv)842 static int cmd_help(int argc, const cmd_args *argv)
843 {
844     return cmd_help_impl(CMD_AVAIL_NORMAL);
845 }
846 
cmd_help_panic(int argc,const cmd_args * argv)847 static int cmd_help_panic(int argc, const cmd_args *argv)
848 {
849     return cmd_help_impl(CMD_AVAIL_PANIC);
850 }
851 
cmd_echo(int argc,const cmd_args * argv)852 static int cmd_echo(int argc, const cmd_args *argv)
853 {
854     if (argc > 1)
855         echo = argv[1].b;
856     return NO_ERROR;
857 }
858 
read_line_panic(char * buffer,const size_t len,FILE * panic_fd)859 static void read_line_panic(char *buffer, const size_t len, FILE *panic_fd)
860 {
861     size_t pos = 0;
862 
863     for (;;) {
864         int c;
865         if ((c = getc(panic_fd)) < 0) {
866             continue;
867         }
868 
869         switch (c) {
870             case '\r':
871             case '\n':
872                 fputc('\n', panic_fd);
873                 goto done;
874             case 0x7f: // backspace or delete
875             case 0x8:
876                 if (pos > 0) {
877                     pos--;
878                     fputs("\b \b", panic_fd); // wipe out a character
879                 }
880                 break;
881             default:
882                 buffer[pos++] = c;
883                 fputc(c, panic_fd);
884         }
885         if (pos == (len - 1)) {
886             fputs("\nerror: line too long\n", panic_fd);
887             pos = 0;
888             goto done;
889         }
890     }
891 done:
892     buffer[pos] = 0;
893 }
894 
895 #if ENABLE_PANIC_SHELL
panic_shell_start(void)896 void panic_shell_start(void)
897 {
898     dprintf(INFO, "entering panic shell loop\n");
899     char input_buffer[PANIC_LINE_LEN];
900     cmd_args args[MAX_NUM_ARGS];
901 
902     // panic_fd allows us to do I/O using the polling drivers.
903     // These drivers function even if interrupts are disabled.
904     FILE *panic_fd = get_panic_fd();
905     if (!panic_fd)
906         return;
907 
908     for (;;) {
909         fputs("! ", panic_fd);
910         read_line_panic(input_buffer, PANIC_LINE_LEN, panic_fd);
911 
912         int argc;
913         char *tok = strtok(input_buffer, WHITESPACE);
914         for (argc = 0; argc < MAX_NUM_ARGS; argc++) {
915             if (tok == NULL) {
916                 break;
917             }
918             args[argc].str = tok;
919             tok = strtok(NULL, WHITESPACE);
920         }
921 
922         if (argc == 0) {
923             continue;
924         }
925 
926         convert_args(argc, args);
927 
928         const cmd *command = match_command(args[0].str, CMD_AVAIL_PANIC);
929         if (!command) {
930             fputs("command not found\n", panic_fd);
931             continue;
932         }
933 
934         command->cmd_callback(argc, args);
935     }
936 }
937 #endif
938 
939 #if LK_DEBUGLEVEL > 1
cmd_test(int argc,const cmd_args * argv)940 static int cmd_test(int argc, const cmd_args *argv)
941 {
942     int i;
943 
944     printf("argc %d, argv %p\n", argc, argv);
945     for (i = 0; i < argc; i++)
946         printf("\t%d: str '%s', i %ld, u %#lx, b %d\n", i, argv[i].str, argv[i].i, argv[i].u, argv[i].b);
947 
948     return 0;
949 }
950 #endif
951