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