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