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