iBoot/sys/menu.c

503 lines
12 KiB
C
Raw Permalink Normal View History

2023-07-08 13:03:17 -07:00
/*
* Copyright (C) 2007-2008 Apple Inc. All rights reserved.
* Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
*
* This document is the property of Apple Computer, Inc.
* It is considered confidential and proprietary.
*
* This document may not be reproduced or transmitted in any form,
* in whole or in part, without the express written permission of
* Apple Computer, Inc.
*/
#include <stdio.h>
#include <string.h>
#include <debug.h>
#include <sys.h>
#include <sys/menu.h>
#include <sys/task.h>
#include <platform.h>
#include <platform/memmap.h>
#include <drivers/power.h>
#include <lib/env.h>
#if WITH_MENU
#if !WITH_SIMPLE_MENU
// 0x1b 0x5b 0x41 uparrow
// 0x1b 0x5b 0x42 downarrow
// 0x1b 0x5b 0x43 rightarrow
// 0x1b 0x5b 0x44 leftarrow
#define HISTORY_SIZE 16
static char (*history)[256];
static uint32_t curr_history_pos = 0;
#define kTokenBufSize (1024)
#define kMaxMenuArgs 64
utime_t gMenuLastActivityTime;
static char *token_buf;
static char *console_prompt;
struct task_event gMenuTaskReadyEvent = EVENT_STATIC_INIT(gMenuTaskReadyEvent, false, 0);
static char *get_line(const char *prompt, char *buf, int buf_size)
{
int i;
int escape;
uint32_t history_pos = curr_history_pos;
uint32_t saved_history_pos = curr_history_pos;
do_prompt:
puts(prompt);
history[curr_history_pos][0] = 0;
i = 0;
escape = 0;
for (;;) {
char c = getchar();
// printf("c = 0x%x (%c)\n", c, c);
/* Update activity time on any new input. */
gMenuLastActivityTime = system_time();
switch (c) {
case '\n':
escape = 0;
putchar('\n');
if (i != 0) {
buf[i] = 0;
/* save the history, if it's different from the last command */
if (strcmp(buf, history[(curr_history_pos - 1) % HISTORY_SIZE]) != 0) {
strlcpy(history[curr_history_pos], buf, sizeof(history[curr_history_pos]));
curr_history_pos = (curr_history_pos + 1) % HISTORY_SIZE;
}
return buf;
} else {
goto do_prompt;
}
break;
case 0x8: // backspace
case 0x7f: // del
escape = 0;
if (i > 0) {
i--;
putchar(0x8);
putchar(0x20);
putchar(0x8);
}
break;
case 0x1b:
escape = 1;
break;
case 0x5b:
if (escape > 0)
escape = 2;
break;
case 0x41: case 0x42:
if (escape < 2) {
goto regchar;
} else {
// up/down arrow
char *new_history;
if (c == 0x41) {
int temp = history_pos;
history_pos = (history_pos - 1) % HISTORY_SIZE;
if (history_pos == saved_history_pos) {
// printf("%d %d\n", history_pos, saved_history_pos);
// we've wrapped around
history_pos = temp;
break;
}
if (strlen(history[history_pos]) == 0) {
// printf("%p %d, len 0\n", history[history_pos], history_pos);
// we've walked before the start of history
history_pos = temp;
break;
}
} else {
if (history_pos == saved_history_pos)
break; // can't go forward past top of the history
history_pos = (history_pos + 1) % HISTORY_SIZE;
}
new_history = history[history_pos];
/* wipe out the line */
while(i > 0) {
putchar(0x8);
putchar(' ');
putchar(0x8);
i--;
}
/* output the new line */
for (i=0; new_history[i] != 0; i++) {
buf[i] = new_history[i];
putchar(new_history[i]);
}
}
escape = 0;
break;
case 0x43: case 0x44:
if (escape < 2) {
goto regchar;
} else {
// left & right arrow
escape = 0;
}
break;
default:
regchar:
escape = 0;
if (i < (buf_size - 1)) {
buf[i++] = c;
putchar(c);
}
}
}
}
/*
* chop a string into multiple tokens, each pointed to by the tokens array.
* if an unescaped ';' is hit, stop tokenizing and return the remainder of
* the line in *line_remainder. If no ';' is hit, return null in *line_remainder.
*/
static int tokenize(const char *buf, char **tokens, int token_count, const char **line_remainder)
{
bool done;
bool done_after;
int inpos = 0;
int outpos = 0;
int outpos_token_start = 0;
int token = 0;
char last_char = 0;
enum {
INITIAL,
CONSUME_WHITESPACE,
TOKEN_START,
TOKEN,
QUOTED_TOKEN,
FINISH_TOKEN,
#if WITH_ENV
ENV_TOKEN,
FINISH_ENV_TOKEN,
#endif
} state = INITIAL;
*line_remainder = NULL;
done = done_after = false;
while (!done) {
/* end of input buffer, finish off whatever token we're in the middle of and exit */
if ((buf[inpos] == 0) || (outpos >= (kTokenBufSize - 1))) {
done = true;
if (state == TOKEN || state == QUOTED_TOKEN || state == FINISH_TOKEN)
state = FINISH_TOKEN;
#if WITH_ENV
else if (state == ENV_TOKEN)
state = FINISH_ENV_TOKEN;
#endif
else
break;
}
if (done_after)
done = true;
// printf("tok: in %d (%c) out %d tok %d state %d\n",
// inpos, buf[inpos], outpos, token, state);
switch (state) {
case INITIAL:
case CONSUME_WHITESPACE:
/* eat whitespace until we see the start of a token */
if (isspace(buf[inpos])) {
last_char = buf[inpos];
inpos++;
continue;
}
state = TOKEN_START;
break;
case TOKEN_START:
/* start of a token, examine the first character to see if we need to do anything special */
outpos_token_start = outpos;
if (buf[inpos] == '#') {
/* comment, ignore the rest of the line */
last_char = buf[inpos];
done = true;
break;
#if WITH_ENV
} else if (buf[inpos] == '$') {
/* environment variable */
last_char = buf[inpos];
inpos++;
state = ENV_TOKEN;
tokens[token] = &token_buf[outpos];
#endif
} else if (buf[inpos] == ';') {
/* if we hit a unescaped ';', it's the end of the line */
*line_remainder = &buf[inpos+1];
done = true;
break;
} else if (buf[inpos] == '"') {
/* quoted token */
last_char = buf[inpos];
inpos++;
state = QUOTED_TOKEN;
tokens[token] = &token_buf[outpos];
} else {
state = TOKEN;
tokens[token] = &token_buf[outpos];
}
break;
case TOKEN:
/* consume characters until we hit whitespace */
if (isspace(buf[inpos])) {
/* whitespace, null terminate current token and switch to whitespace consume state */
last_char = buf[inpos];
state = FINISH_TOKEN;
break;
} else if (buf[inpos] == ';') {
/* if we hit a unescaped ';', it's the end of the line */
if (last_char != '\\') {
*line_remainder = &buf[inpos+1];
state = FINISH_TOKEN;
done_after = true;
break;
}
}
last_char = buf[inpos];
if (buf[inpos] != '\\')
token_buf[outpos++] = buf[inpos];
inpos++;
break;
case QUOTED_TOKEN:
/* consume characters until we hit terminating quote */
if (buf[inpos] == '"') {
/* terminating quote, null terminate current token and switch to whitespace consume state */
inpos++; // consume the quote
state = FINISH_TOKEN;
break;
}
last_char = buf[inpos];
token_buf[outpos++] = buf[inpos++];
break;
case FINISH_TOKEN:
/* finish whatever token we're dealing with */
token_buf[outpos++] = 0;
if (++token < token_count)
state = CONSUME_WHITESPACE;
else {
*line_remainder = &buf[inpos];
done = true;
}
break;
#if WITH_ENV
case ENV_TOKEN:
/* consume characters until we hit whitespace */
if (isspace(buf[inpos])) {
/* whitespace, we've now got the environment variable we want to read */
last_char = buf[inpos];
state = FINISH_ENV_TOKEN;
break;
}
last_char = buf[inpos];
token_buf[outpos++] = buf[inpos++];
break;
case FINISH_ENV_TOKEN: {
/* the current token now holds the environment variable we want to read */
const char *env;
token_buf[outpos++] = 0;
/* read in the environment variable */
env = env_get(tokens[token]);
if (!env) {
printf("\ncould not read environment var '%s'\n", tokens[token]);
return -1;
}
/* copy the value of the environment variable on top of the current token */
strlcpy(&token_buf[outpos_token_start], env, kTokenBufSize - outpos_token_start);
outpos = outpos_token_start + strlen(token_buf + outpos_token_start) + 1;
if (++token < token_count)
state = CONSUME_WHITESPACE;
else {
*line_remainder = &buf[inpos];
done = true;
}
break;
}
#endif
default:
panic("tokenize: got into an invalid state\n");
}
}
return token;
}
bool process_command_line(char *line, int* ret_val)
{
char *tokens[kMaxMenuArgs];
struct cmd_arg args[kMaxMenuArgs];
int token_count;
int ret = 0;
const struct cmd_menu_item *menu_entry = NULL;
void **menu_cursor;
int argnum;
/* don't allow ourselves to be called before the menu task has started */
if (NULL == token_buf)
return(false);
/* Update activity; we might have come from outside the get_line() loop */
gMenuLastActivityTime = system_time();
while(line) {
memset(tokens, 0, sizeof(tokens));
memset(args, 0, sizeof(args));
token_count = tokenize(line, tokens, sizeof(tokens)/sizeof(char *), (const char **)&line);
if (token_count <= 0)
continue;
// printf("buf %p, line %p\n", buf, line);
// for(i=0; i < token_count; i++)
// printf("\t%d: '%s'\n", i, tokens[i]);
/* if the command starts with '?', only execute it if the last command returned a positive error */
if (tokens[0][0] == '?') {
tokens[0]++;
if (ret < 0)
continue;
}
menu_entry = NULL;
LINKER_SET_FOREACH(menu_cursor, menu) {
menu_entry = (const struct cmd_menu_item *)*menu_cursor;
if (!strcmp(menu_entry->cmd, tokens[0])) {
/* parse the arglist */
for (argnum = 0; argnum < kMaxMenuArgs; argnum++) {
/* are we done? */
if (argnum >= token_count)
break;
args[argnum].n = strtol(tokens[argnum], NULL, 0);
args[argnum].u = strtoul(tokens[argnum], NULL, 0);
args[argnum].h = strtol(tokens[argnum], NULL, 16);
if (!strcmp(tokens[argnum], "true"))
args[argnum].b = true;
else if (!strcmp(tokens[argnum], "false"))
args[argnum].b = false;
else
args[argnum].b = args[argnum].n ? true : false;
args[argnum].str = tokens[argnum];
}
/* call the routine */
ret = menu_entry->func(argnum, args);
if (ret_val)
*ret_val = ret;
#if WITH_ENV
/* stick the result in an environment variable */
env_set_uint("?", ret, 0);
#endif
break;
} else {
menu_entry = NULL;
}
}
}
return (menu_entry == NULL);
}
int menu_task(void *arg)
{
char *buf;
/* allocate the history buffer */
history = calloc(1, HISTORY_SIZE * sizeof(*history));
/* allocate the line buffer */
buf = malloc(512);
/* allocate the token buffer */
token_buf = malloc(kTokenBufSize);
/* signal others waiting for us */
event_signal(&gMenuTaskReadyEvent);
menu_prompt(NULL);
for (;;) {
/* prompt */
puts("\x1b[m");
get_line(console_prompt, buf, 512);
if (process_command_line(buf, NULL)) {
printf("\x1b[1m?SYNTAX ERROR\n");
}
}
return 0;
}
char *menu_prompt(char *new_prompt)
{
char *ret;
ret = console_prompt;
if (NULL != new_prompt) {
console_prompt = new_prompt;
} else {
console_prompt = "] ";
}
return(ret);
}
static int do_help(int argc, struct cmd_arg *args)
{
const struct cmd_menu_item *menu_entry;
void **menu_cursor;
printf("command list:\n");
LINKER_SET_FOREACH(menu_cursor, menu) {
menu_entry = (const struct cmd_menu_item *)*menu_cursor;
if (menu_entry->help)
printf(" %-16.16s %s\n", menu_entry->cmd, menu_entry->help);
}
return 0;
}
MENU_COMMAND_DEVELOPMENT(help, do_help, NULL, NULL);
#if 0
static int do_argtest(int argc, struct cmd_arg *args)
{
int i;
printf("argtest, %d args:\n", argc);
for (i=0; i<argc; i++) {
printf("%d: n %d u %u h %x str %p (%s)\n", i, args[i].n, args[i].u, args[i].h, args[i].str, args[i].str);
}
return 0;
}
MENU_COMMAND_DEBUG(argtest, do_argtest, NULL, NULL);
#endif
#endif /* !WITH_SIMPLE_MENU */
#endif /* WITH_MENU */