1
0
mirror of git://projects.qi-hardware.com/xburst-tools.git synced 2024-11-01 10:15:19 +02:00
xburst-tools/shell.c
2010-12-10 15:14:43 +03:00

515 lines
9.8 KiB
C

/*
* JzBoot: an USB bootloader for JZ series of Ingenic(R) microprocessors.
* Copyright (C) 2010 Sergey Gridassov <grindars@gmail.com>,
* Peter Zotov <whitequark@whitequark.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <stdio.h>
#ifdef WITH_READLINE
#include <readline/readline.h>
#include <readline/history.h>
#endif
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include "shell_internal.h"
#include "debug.h"
#include "ingenic.h"
static void shell_update_cmdset(uint32_t cmdset, void *arg);
static void shell_progress(int action, int value, int max, void *arg);
static const ingenic_callbacks_t shell_callbacks = {
shell_update_cmdset,
shell_progress
};
static const struct {
int set;
const char *name;
const shell_command_t *commands;
} cmdsets[] = {
{ CMDSET_SPL, "SPL", spl_cmdset },
{ CMDSET_USBBOOT, "USBBoot", usbboot_cmdset },
{ 0, NULL, NULL }
};
shell_context_t *shell_init(void *ingenic) {
#ifdef WITH_READLINE
rl_initialize();
#endif
debug(LEVEL_DEBUG, "Initializing shell\n");
shell_context_t *ctx = malloc(sizeof(shell_context_t));
memset(ctx, 0, sizeof(shell_context_t));
ctx->device = ingenic;
ingenic_set_callbacks(ingenic, &shell_callbacks, ctx);
shell_update_cmdset(ingenic_cmdset(ingenic), ctx);
return ctx;
}
int shell_enumerate_commands(shell_context_t *ctx, int (*callback)(shell_context_t *ctx, const shell_command_t *cmd, void *arg), void *arg) {
for(int i = 0; builtin_cmdset[i].cmd != NULL; i++) {
int ret = callback(ctx, builtin_cmdset + i, arg);
if(ret != 0)
return ret;
}
if(ctx->set_cmds)
for(int i = 0; ctx->set_cmds[i].cmd != NULL; i++) {
int ret = callback(ctx, ctx->set_cmds + i, arg);
if(ret != 0)
return ret;
}
return 0;
}
static int shell_run_function(shell_context_t *ctx, const shell_command_t *cmd, void *arg) {
shell_run_data_t *data = arg;
if(strcmp(cmd->cmd, data->argv[0]) == 0) {
int invalid = 0;
if(cmd->args == NULL && data->argc != 1)
invalid = 1;
else if(cmd->args) {
char *dup = strdup(cmd->args), *save = dup, *ptrptr = NULL;
int pos = 1;
int max_tokens = 1;
for(char *token = strtok_r(dup, " ", &ptrptr); token; token = strtok_r(NULL, " ", &ptrptr)) {
if(strcmp(token, "...") == 0) {
max_tokens = -1;
break;
}
max_tokens++;
if(data->argc - 1 < pos) {
if(*token == '[') {
break;
} else if(*token == '<') {
invalid = 1;
break;
}
}
pos++;
}
if(max_tokens != -1 && data->argc > max_tokens)
invalid = 1;
free(save);
}
if(invalid) {
if(cmd->args)
fprintf(stderr, "Usage: %s %s\n", cmd->cmd, cmd->args);
else
fprintf(stderr, "Usage: %s\n", cmd->cmd);
errno = EINVAL;
return -1;
} else {
int ret = cmd->handler(ctx, data->argc, data->argv);
if(ret == 0)
return 1;
else
return ret;
}
} else
return 0;
}
int shell_run(shell_context_t *ctx, int argc, char *argv[]) {
shell_run_data_t data = { argc, argv };
int ret = shell_enumerate_commands(ctx, shell_run_function, &data);
if(ret == 0) {
debug(LEVEL_ERROR, "Bad command '%s'\n", argv[0]);
errno = EINVAL;
return -1;
} else if(ret == 1) {
return 0;
} else
return ret;
}
int shell_execute(shell_context_t *ctx, const char *cmd) {
yyscan_t scanner;
if(yylex_init_extra(ctx, &scanner) == -1)
return -1;
ctx->line = strdup(cmd);
char *ptr = ctx->line;
int token;
int state = STATE_WANTSTR;
int argc = 0;
char **argv = NULL;
int fret = 0;
do {
int noway = 0;
token = yylex(scanner);
if((token == TOK_SEPARATOR || token == TOK_COMMENT || token == 0)) {
if(argc > 0) {
int ret = shell_run(ctx, argc, argv);
for(int i = 0; i < argc; i++) {
free(argv[i]);
}
free(argv);
argv = NULL;
argc = 0;
if(ret == -1) {
fret = -1;
break;
}
}
state = STATE_WANTSTR;
} else {
switch(state) {
case STATE_WANTSTR:
if(token == TOK_SPACE) {
state = STATE_WANTSTR;
} else if(token == TOK_STRING) {
int oargc = argc++;
argv = realloc(argv, sizeof(char *) * argc);
argv[oargc] = ctx->strval;
state = STATE_WANTSPACE;
} else {
noway = 1;
}
break;
case STATE_WANTSPACE:
if(token == TOK_STRING) {
free(ctx->strval);
noway = 1;
} else if(token == TOK_SPACE) {
state = STATE_WANTSTR;
} else
noway = 1;
}
if(noway) {
debug(LEVEL_ERROR, "No way from state %d by token %d\n", state, token);
for(int i = 0; i < argc; i++)
free(argv[i]);
free(argv);
fret = -1;
break;
}
}
} while(token && token != TOK_COMMENT);
free(ptr);
yylex_destroy(scanner);
return fret;
}
int shell_pull(shell_context_t *ctx, char *buf, int maxlen) {
size_t len = strlen(ctx->line);
if(len < maxlen)
maxlen = len;
memcpy(buf, ctx->line, maxlen);
ctx->line += maxlen;
return maxlen;
}
void shell_fini(shell_context_t *ctx) {
free(ctx);
}
int shell_source(shell_context_t *ctx, const char *filename) {
ctx->shell_exit = 0;
FILE *file = fopen(filename, "r");
if(file == NULL) {
return -1;
}
char *line;
while((line = fgets(ctx->linebuf, sizeof(ctx->linebuf), file)) && !ctx->shell_exit) {
if(shell_execute(ctx, line) == -1) {
fclose(file);
return -1;
}
}
fclose(file);
return 0;
}
#ifdef WITH_READLINE
static shell_context_t *completion_context;
static char **completion_matches;
static int completion_matches_count = 0;
static int shell_completion_filler(shell_context_t *ctx, const shell_command_t *cmd, void *arg) {
const char *part = arg;
size_t len = strlen(part), cmdlen = strlen(cmd->cmd);
if(cmdlen >= len && memcmp(part, cmd->cmd, len) == 0) {
int idx = completion_matches_count++;
completion_matches = realloc(completion_matches, sizeof(char **) * completion_matches_count);
completion_matches[idx] = strdup(cmd->cmd);
}
return 0;
}
static char *shell_completion(const char *partial, int state) {
static int completion_pass = 0, completion_matches_offset = 0;
if(state == 0) {
if(completion_matches) {
for(int i = 0; i < completion_matches_count; i++)
if(completion_matches[i])
free(completion_matches[i]);
free(completion_matches);
completion_matches = NULL;
completion_matches_count = 0;
}
completion_pass = 0;
char *tmp = rl_line_buffer;
while(isspace(*tmp)) tmp++;
int not_first = 0;
for(; *tmp; tmp++) {
for(const char *sep = rl_basic_word_break_characters; *sep; sep++) {
if(*tmp == *sep) {
not_first = 1;
break;
}
}
if(not_first)
break;
}
if(not_first) {
completion_pass = 1;
return rl_filename_completion_function(partial, state);
} else {
shell_enumerate_commands(completion_context, shell_completion_filler, (void *) partial);
completion_matches_offset = 0;
}
}
if(completion_pass) {
return rl_filename_completion_function(partial, state);
} else if(completion_matches_offset == completion_matches_count) {
return NULL;
} else {
char *val = completion_matches[completion_matches_offset];
completion_matches[completion_matches_offset++] = NULL;
return val;
}
}
#endif
void shell_interactive(shell_context_t *ctx) {
ctx->shell_exit = 0;
#ifndef WITH_READLINE
char *line;
while(!ctx->shell_exit) {
fputs("jzboot> ", stdout);
fflush(stdout);
line = fgets(ctx->linebuf, sizeof(ctx->linebuf), stdin);
if(line == NULL)
break;
shell_execute(ctx, line);
}
#else
rl_completion_entry_function = shell_completion;
completion_context = ctx;
rl_set_signals();
rl_filename_quote_characters = "\" ";
while(!ctx->shell_exit) {
char *line = readline("jzboot> ");
if(line == NULL) {
break;
}
add_history(line);
shell_execute(ctx, line);
free(line);
}
rl_clear_signals();
#endif
}
static void shell_update_cmdset(uint32_t cmdset, void *arg) {
shell_context_t *ctx = arg;
ctx->set_cmds = NULL;
for(int i = 0; cmdsets[i].name != NULL; i++) {
if(cmdsets[i].set == cmdset) {
printf("Shell: using command set '%s', run 'help' for command list. CPU: %04X\n", cmdsets[i].name, ingenic_type(ctx->device));
ctx->set_cmds = cmdsets[i].commands;
return;
}
}
debug(LEVEL_ERROR, "Shell: unknown cmdset %u\n", cmdset);
}
void *shell_device(shell_context_t *ctx) {
return ctx->device;
}
void shell_exit(shell_context_t *ctx, int val) {
ctx->shell_exit = val;
}
static void shell_progress(int action, int value, int max, void *arg) {
shell_context_t *ctx = arg;
if(isatty(STDOUT_FILENO)) {
struct winsize size;
int progress, percent;
if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) == -1)
return;
int bar_size = size.ws_col - 6;
switch(action) {
case PROGRESS_INIT:
ctx->prev_progress = -1;
case PROGRESS_FINI:
putchar('\r');
for(int i = 0; i < size.ws_col; i++)
putchar(' ');
putchar('\r');
fflush(stdout);
break;
case PROGRESS_UPDATE:
progress = value * bar_size / max;
percent = value * 100 / max;
if(progress != ctx->prev_progress) {
fputs("\r|", stdout);
for(int i = 0; i < progress; i++) {
putchar('=');
}
for(int i = progress; i < bar_size; i++)
putchar(' ');
printf("|%3d%%", percent);
fflush(stdout);
ctx->prev_progress = progress;
}
break;
}
}
}