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