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