Root/jzboot/src/shell.c

1/*
2 * JzBoot: an USB bootloader for JZ series of Ingenic(R) microprocessors.
3 * Copyright (C) 2010 Sergey Gridassov <grindars@gmail.com>,
4 * Peter Zotov <whitequark@whitequark.org>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <string.h>
21#include <stdio.h>
22#ifdef HAVE_LIBREADLINE
23#include <readline/readline.h>
24#include <readline/history.h>
25#endif
26#include <stdlib.h>
27#include <unistd.h>
28#include <errno.h>
29#include <sys/ioctl.h>
30
31#include "shell_internal.h"
32#include "debug.h"
33#include "ingenic.h"
34
35static void shell_update_cmdset(uint32_t cmdset, void *arg);
36static void shell_progress(int action, int value, int max, void *arg);
37
38static const ingenic_callbacks_t shell_callbacks = {
39    shell_update_cmdset,
40    shell_progress
41};
42
43static const struct {
44    int set;
45    const char *name;
46    const shell_command_t *commands;
47} cmdsets[] = {
48    { CMDSET_SPL, "SPL", spl_cmdset },
49    { CMDSET_USBBOOT, "USBBoot", usbboot_cmdset },
50    { 0, NULL, NULL }
51};
52
53shell_context_t *shell_init(void *ingenic) {
54#ifdef HAVE_LIBREADLINE
55    rl_initialize();
56#endif
57
58    debug(LEVEL_DEBUG, "Initializing shell\n");
59
60    shell_context_t *ctx = malloc(sizeof(shell_context_t));
61    memset(ctx, 0, sizeof(shell_context_t));
62    ctx->device = ingenic;
63
64    ingenic_set_callbacks(ingenic, &shell_callbacks, ctx);
65
66    shell_update_cmdset(ingenic_cmdset(ingenic), ctx);
67
68    return ctx;
69}
70
71int shell_enumerate_commands(shell_context_t *ctx, int (*callback)(shell_context_t *ctx, const shell_command_t *cmd, void *arg), void *arg) {
72    for(int i = 0; builtin_cmdset[i].cmd != NULL; i++) {
73        int ret = callback(ctx, builtin_cmdset + i, arg);
74
75        if(ret != 0)
76            return ret;
77    }
78
79    if(ctx->set_cmds)
80        for(int i = 0; ctx->set_cmds[i].cmd != NULL; i++) {
81            int ret = callback(ctx, ctx->set_cmds + i, arg);
82
83            if(ret != 0)
84                return ret;
85        }
86
87    return 0;
88}
89
90static int shell_run_function(shell_context_t *ctx, const shell_command_t *cmd, void *arg) {
91    shell_run_data_t *data = arg;
92
93    if(strcmp(cmd->cmd, data->argv[0]) == 0) {
94        int invalid = 0;
95
96        if(cmd->args == NULL && data->argc != 1)
97            invalid = 1;
98        else if(cmd->args) {
99            char *dup = strdup(cmd->args), *save = dup, *ptrptr = NULL;
100
101            int pos = 1;
102            int max_tokens = 1;
103
104            for(char *token = strtok_r(dup, " ", &ptrptr); token; token = strtok_r(NULL, " ", &ptrptr)) {
105                if(strcmp(token, "...") == 0) {
106                    max_tokens = -1;
107
108                    break;
109                }
110
111                max_tokens++;
112
113                if(data->argc - 1 < pos) {
114                    if(*token == '[') {
115                        break;
116                    } else if(*token == '<') {
117                        invalid = 1;
118
119                        break;
120                    }
121                }
122
123                pos++;
124            }
125
126            if(max_tokens != -1 && data->argc > max_tokens)
127                invalid = 1;
128
129
130            free(save);
131        }
132
133        if(invalid) {
134            if(cmd->args)
135                fprintf(stderr, "Usage: %s %s\n", cmd->cmd, cmd->args);
136            else
137                fprintf(stderr, "Usage: %s\n", cmd->cmd);
138
139            errno = EINVAL;
140
141            return -1;
142
143        } else {
144            int ret = cmd->handler(ctx, data->argc, data->argv);
145
146            if(ret == 0)
147                return 1;
148            else
149                return ret;
150        }
151    } else
152        return 0;
153}
154
155int shell_run(shell_context_t *ctx, int argc, char *argv[]) {
156    shell_run_data_t data = { argc, argv };
157
158    int ret = shell_enumerate_commands(ctx, shell_run_function, &data);
159
160    if(ret == 0) {
161        debug(LEVEL_ERROR, "Bad command '%s'\n", argv[0]);
162
163        errno = EINVAL;
164        return -1;
165
166    } else if(ret == 1) {
167        return 0;
168    } else
169        return ret;
170}
171
172int shell_execute(shell_context_t *ctx, const char *cmd) {
173    yyscan_t scanner;
174    if(yylex_init_extra(ctx, &scanner) == -1)
175        return -1;
176
177    ctx->line = strdup(cmd);
178    char *ptr = ctx->line;
179
180    int token;
181    int state = STATE_WANTSTR;
182    int argc = 0;
183    char **argv = NULL;
184    int fret = 0;
185
186    do {
187        int noway = 0;
188
189        token = yylex(scanner);
190
191        if((token == TOK_SEPARATOR || token == TOK_COMMENT || token == 0)) {
192            if(argc > 0) {
193                int ret = shell_run(ctx, argc, argv);
194
195                for(int i = 0; i < argc; i++) {
196                    free(argv[i]);
197                }
198
199                free(argv);
200
201                argv = NULL;
202                argc = 0;
203
204                if(ret == -1) {
205                    fret = -1;
206
207                    break;
208                }
209            }
210
211            state = STATE_WANTSTR;
212        } else {
213            switch(state) {
214            case STATE_WANTSTR:
215                if(token == TOK_SPACE) {
216                    state = STATE_WANTSTR;
217                } else if(token == TOK_STRING) {
218                    int oargc = argc++;
219
220                    argv = realloc(argv, sizeof(char *) * argc);
221
222                    argv[oargc] = ctx->strval;
223
224                    state = STATE_WANTSPACE;
225                } else {
226                    noway = 1;
227                }
228
229                break;
230
231            case STATE_WANTSPACE:
232                if(token == TOK_STRING) {
233                    free(ctx->strval);
234
235                    noway = 1;
236                } else if(token == TOK_SPACE) {
237                    state = STATE_WANTSTR;
238                } else
239                    noway = 1;
240            }
241
242            if(noway) {
243                debug(LEVEL_ERROR, "No way from state %d by token %d\n", state, token);
244
245                for(int i = 0; i < argc; i++)
246                    free(argv[i]);
247
248                free(argv);
249
250                fret = -1;
251
252                break;
253            }
254        }
255
256    } while(token && token != TOK_COMMENT);
257
258    free(ptr);
259
260    yylex_destroy(scanner);
261
262    return fret;
263}
264
265int shell_pull(shell_context_t *ctx, char *buf, int maxlen) {
266    size_t len = strlen(ctx->line);
267
268    if(len < maxlen)
269        maxlen = len;
270
271    memcpy(buf, ctx->line, maxlen);
272
273    ctx->line += maxlen;
274
275    return maxlen;
276}
277
278void shell_fini(shell_context_t *ctx) {
279    free(ctx);
280}
281
282int shell_source(shell_context_t *ctx, const char *filename) {
283    ctx->shell_exit = 0;
284
285    FILE *file = fopen(filename, "r");
286
287    if(file == NULL) {
288        return -1;
289    }
290
291    char *line;
292
293    while((line = fgets(ctx->linebuf, sizeof(ctx->linebuf), file)) && !ctx->shell_exit) {
294        if(shell_execute(ctx, line) == -1) {
295            fclose(file);
296
297            return -1;
298        }
299    }
300
301    fclose(file);
302
303    return 0;
304}
305
306#ifdef HAVE_LIBREADLINE
307static shell_context_t *completion_context;
308static char **completion_matches;
309static int completion_matches_count = 0;
310
311static int shell_completion_filler(shell_context_t *ctx, const shell_command_t *cmd, void *arg) {
312    const char *part = arg;
313
314    size_t len = strlen(part), cmdlen = strlen(cmd->cmd);
315
316    if(cmdlen >= len && memcmp(part, cmd->cmd, len) == 0) {
317        int idx = completion_matches_count++;
318
319        completion_matches = realloc(completion_matches, sizeof(char **) * completion_matches_count);
320        completion_matches[idx] = strdup(cmd->cmd);
321    }
322    
323    return 0;
324}
325
326static char *shell_completion(const char *partial, int state) {
327    static int completion_pass = 0, completion_matches_offset = 0;
328
329    if(state == 0) {
330        if(completion_matches) {
331            for(int i = 0; i < completion_matches_count; i++)
332                if(completion_matches[i])
333                    free(completion_matches[i]);
334
335            free(completion_matches);
336        
337            completion_matches = NULL;
338            completion_matches_count = 0;
339        }
340
341        completion_pass = 0;
342
343        char *tmp = rl_line_buffer;
344
345        while(isspace(*tmp)) tmp++;
346
347        int not_first = 0;
348
349        for(; *tmp; tmp++) {
350            for(const char *sep = rl_basic_word_break_characters; *sep; sep++) {
351                if(*tmp == *sep) {
352                    not_first = 1;
353
354                    break;
355                }
356            }
357
358            if(not_first)
359                break;
360        }
361
362        if(not_first) {
363            completion_pass = 1;
364
365            return rl_filename_completion_function(partial, state);
366        } else {
367            shell_enumerate_commands(completion_context, shell_completion_filler, (void *) partial);
368
369            completion_matches_offset = 0;
370        }
371    }
372
373    if(completion_pass) {
374        return rl_filename_completion_function(partial, state);
375
376    } else if(completion_matches_offset == completion_matches_count) {
377        return NULL;
378    } else {
379        char *val = completion_matches[completion_matches_offset];
380
381        completion_matches[completion_matches_offset++] = NULL;
382
383        return val;
384    }
385}
386#endif
387
388void shell_interactive(shell_context_t *ctx) {
389    ctx->shell_exit = 0;
390
391#ifndef WITH_READLINE
392    char *line;
393
394    while(!ctx->shell_exit) {
395        fputs("jzboot> ", stdout);
396        fflush(stdout);
397
398        line = fgets(ctx->linebuf, sizeof(ctx->linebuf), stdin);
399
400        if(line == NULL)
401            break;
402
403        shell_execute(ctx, line);
404    }
405#else
406    rl_completion_entry_function = shell_completion;
407    completion_context = ctx;
408
409    rl_set_signals();
410
411    rl_filename_quote_characters = "\" ";
412    while(!ctx->shell_exit) {
413        char *line = readline("jzboot> ");
414
415        if(line == NULL) {
416            break;
417        }
418
419        add_history(line);
420
421        shell_execute(ctx, line);
422
423        free(line);
424    }
425
426    rl_clear_signals();
427#endif
428}
429
430static void shell_update_cmdset(uint32_t cmdset, void *arg) {
431    shell_context_t *ctx = arg;
432
433    ctx->set_cmds = NULL;
434
435    for(int i = 0; cmdsets[i].name != NULL; i++) {
436        if(cmdsets[i].set == cmdset) {
437            printf("Shell: using command set '%s', run 'help' for command list. CPU: %04X\n", cmdsets[i].name, ingenic_type(ctx->device));
438
439            ctx->set_cmds = cmdsets[i].commands;
440
441            return;
442        }
443    }
444
445    debug(LEVEL_ERROR, "Shell: unknown cmdset %u\n", cmdset);
446}
447
448void *shell_device(shell_context_t *ctx) {
449    return ctx->device;
450}
451
452void shell_exit(shell_context_t *ctx, int val) {
453    ctx->shell_exit = val;
454}
455
456static void shell_progress(int action, int value, int max, void *arg) {
457    shell_context_t *ctx = arg;
458
459    if(isatty(STDOUT_FILENO)) {
460        struct winsize size;
461
462        int progress, percent;
463
464        if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) == -1)
465            return;
466
467        int bar_size = size.ws_col - 6;
468
469        switch(action) {
470        case PROGRESS_INIT:
471            ctx->prev_progress = -1;
472        
473
474        case PROGRESS_FINI:
475            putchar('\r');
476
477            for(int i = 0; i < size.ws_col; i++)
478                putchar(' ');
479
480            putchar('\r');
481
482            fflush(stdout);
483
484            break;
485
486        case PROGRESS_UPDATE:
487            progress = value * bar_size / max;
488            percent = value * 100 / max;
489
490            if(progress != ctx->prev_progress) {
491                fputs("\r|", stdout);
492
493                for(int i = 0; i < progress; i++) {
494                    putchar('=');
495                }
496
497                for(int i = progress; i < bar_size; i++)
498                    putchar(' ');
499
500                printf("|%3d%%", percent);
501
502                fflush(stdout);
503
504                ctx->prev_progress = progress;
505
506            }
507
508        
509            break;
510
511        }
512    }
513}
514
515

Archive Download this file



interactive