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