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