1171 lines
27 KiB
C
1171 lines
27 KiB
C
/*
|
|
* Copyright (C) 2001-2003 Hewlett-Packard Co.
|
|
* Contributed by Stephane Eranian <eranian@hpl.hp.com>
|
|
*
|
|
* This file is part of the ELILO, the EFI Linux boot loader.
|
|
*
|
|
* ELILO 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 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* ELILO 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 ELILO; see the file COPYING. If not, write to the Free
|
|
* Software Foundation, 59 Temple Place - Suite 330, Boston, MA
|
|
* 02111-1307, USA.
|
|
*
|
|
* Please check out the elilo.txt for complete documentation on how
|
|
* to use this program.
|
|
*
|
|
* Portions of this file are derived from the LILO/x86
|
|
* Copyright 1992-1997 Werner Almesberger.
|
|
*/
|
|
|
|
#include <efi.h>
|
|
#include <efilib.h>
|
|
#include <efistdarg.h>
|
|
|
|
#include "elilo.h"
|
|
#include "config.h"
|
|
|
|
/*
|
|
* The first default config file is architecture dependent. This is useful
|
|
* in case of network booting where the server is used for both types of
|
|
* architectures.
|
|
*/
|
|
#if defined(CONFIG_ia64)
|
|
#define ELILO_ARCH_DEFAULT_CONFIG L"elilo-ia64.conf"
|
|
#elif defined (CONFIG_ia32)
|
|
#define ELILO_ARCH_DEFAULT_CONFIG L"elilo-ia32.conf"
|
|
#else
|
|
#error "You need to specfy your default arch config file"
|
|
#endif
|
|
|
|
/*
|
|
* last resort config file. Common to all architectures
|
|
*/
|
|
#define ELILO_DEFAULT_CONFIG L"elilo.conf"
|
|
|
|
#define MAX_STRING CMDLINE_MAXLEN
|
|
#define CONFIG_BUFSIZE 512 /* input buffer size */
|
|
|
|
/*
|
|
* maximum number of message files.
|
|
*
|
|
* main message= goes at entry 0, entries [1-12] are used for function keys
|
|
*
|
|
*/
|
|
#define MAX_MESSAGES 13
|
|
|
|
typedef struct boot_image {
|
|
struct boot_image *next;
|
|
CHAR16 label[MAX_STRING];
|
|
CHAR16 kname[FILENAME_MAXLEN];
|
|
CHAR16 options[MAX_STRING];
|
|
CHAR16 initrd[FILENAME_MAXLEN];
|
|
CHAR16 root[FILENAME_MAXLEN];
|
|
CHAR16 fallback[MAX_STRING];
|
|
CHAR16 description[MAX_STRING];
|
|
|
|
UINTN ramdisk;
|
|
UINTN readonly;
|
|
UINTN literal;
|
|
|
|
sys_img_options_t sys_img_opts; /* architecture specific image options */
|
|
} boot_image_t;
|
|
|
|
typedef enum {
|
|
TOK_ERR,
|
|
TOK_EQUAL,
|
|
TOK_STR,
|
|
TOK_EOF
|
|
} token_t;
|
|
|
|
/*
|
|
* global shared options
|
|
* architecture specific global options are private to each architecture
|
|
*/
|
|
typedef struct {
|
|
CHAR16 root[FILENAME_MAXLEN]; /* globally defined root fs */
|
|
CHAR16 initrd[FILENAME_MAXLEN];/* globally defined initrd */
|
|
CHAR16 options[MAX_STRING];
|
|
CHAR16 default_image_name[MAX_STRING];
|
|
CHAR16 message_file[MAX_MESSAGES][FILENAME_MAXLEN];
|
|
CHAR16 chooser[FILENAME_MAXLEN];/* which image chooser to use */
|
|
CHAR16 config_file[FILENAME_MAXLEN];
|
|
boot_image_t *default_image;
|
|
|
|
UINTN readonly;
|
|
|
|
/*
|
|
* options that may affect global elilo options
|
|
*/
|
|
UINTN alt_check;
|
|
UINTN debug;
|
|
UINTN delay;
|
|
UINTN prompt;
|
|
UINTN timeout;
|
|
UINTN verbose;
|
|
UINTN edd30_no_force; /* don't force EDD30 if not set */
|
|
} global_config_t;
|
|
|
|
/*
|
|
* structure used to point to a group of options.
|
|
* Several group for the same category are supported via a linked list.
|
|
*/
|
|
typedef struct _config_option_group {
|
|
struct _config_option_group *next; /* pointer to next group */
|
|
config_option_t *options; /* the table of options for this group */
|
|
UINTN nentries; /* number of entries for this group */
|
|
} config_option_group_t;
|
|
|
|
static option_action_t do_image, do_literal, do_options;
|
|
static INTN check_verbosity(VOID *), check_chooser(VOID *);
|
|
|
|
static global_config_t global_config; /* options shared by all images */
|
|
|
|
/*
|
|
* Core global options: shared by all architectures, all modules
|
|
*/
|
|
static config_option_t global_common_options[]={
|
|
{OPT_STR, OPT_GLOBAL, L"default", NULL, NULL, global_config.default_image_name},
|
|
{OPT_NUM, OPT_GLOBAL, L"timeout", NULL, NULL, &global_config.timeout},
|
|
{OPT_NUM, OPT_GLOBAL, L"delay", NULL, NULL, &global_config.delay},
|
|
{OPT_BOOL, OPT_GLOBAL, L"debug", NULL, NULL, &global_config.debug},
|
|
{OPT_BOOL, OPT_GLOBAL, L"prompt", NULL, NULL, &global_config.prompt},
|
|
{OPT_NUM, OPT_GLOBAL, L"verbose", NULL, check_verbosity, &global_config.verbose},
|
|
{OPT_FILE, OPT_GLOBAL, L"root", NULL, NULL, global_config.root},
|
|
{OPT_BOOL, OPT_GLOBAL, L"read-only", NULL, NULL, &global_config.readonly},
|
|
{OPT_BOOL, OPT_GLOBAL, L"noedd30", NULL, NULL, &global_config.edd30_no_force},
|
|
{OPT_CMD, OPT_GLOBAL, L"append", NULL, NULL, global_config.options},
|
|
{OPT_FILE, OPT_GLOBAL, L"initrd", NULL, NULL, global_config.initrd},
|
|
{OPT_FILE, OPT_GLOBAL, L"image", do_image, NULL, opt_offsetof(kname)},
|
|
{OPT_BOOL, OPT_GLOBAL, L"checkalt", NULL, NULL, &global_config.alt_check},
|
|
{OPT_STR, OPT_GLOBAL, L"chooser", NULL, check_chooser, global_config.chooser},
|
|
{OPT_FILE, OPT_GLOBAL, L"message", NULL, NULL, global_config.message_file[0]},
|
|
{OPT_FILE, OPT_GLOBAL, L"f1", NULL, NULL, global_config.message_file[1]},
|
|
{OPT_FILE, OPT_GLOBAL, L"f2", NULL, NULL, global_config.message_file[2]},
|
|
{OPT_FILE, OPT_GLOBAL, L"f3", NULL, NULL, global_config.message_file[3]},
|
|
{OPT_FILE, OPT_GLOBAL, L"f4", NULL, NULL, global_config.message_file[4]},
|
|
{OPT_FILE, OPT_GLOBAL, L"f5", NULL, NULL, global_config.message_file[5]},
|
|
{OPT_FILE, OPT_GLOBAL, L"f6", NULL, NULL, global_config.message_file[6]},
|
|
{OPT_FILE, OPT_GLOBAL, L"f7", NULL, NULL, global_config.message_file[7]},
|
|
{OPT_FILE, OPT_GLOBAL, L"f8", NULL, NULL, global_config.message_file[8]},
|
|
{OPT_FILE, OPT_GLOBAL, L"f9", NULL, NULL, global_config.message_file[9]},
|
|
{OPT_FILE, OPT_GLOBAL, L"f10", NULL, NULL, global_config.message_file[10]},
|
|
{OPT_FILE, OPT_GLOBAL, L"f11", NULL, NULL, global_config.message_file[11]},
|
|
{OPT_FILE, OPT_GLOBAL, L"f12", NULL, NULL, global_config.message_file[12]}
|
|
};
|
|
|
|
static config_option_t image_common_options[]={
|
|
{OPT_FILE, OPT_IMAGE, L"root", NULL, NULL, opt_offsetof(root)},
|
|
{OPT_BOOL, OPT_IMAGE, L"read-only", NULL, NULL, opt_offsetof(readonly)},
|
|
{OPT_CMD, OPT_IMAGE, L"append", do_options, NULL, opt_offsetof(options)},
|
|
{OPT_CMD, OPT_IMAGE, L"literal", do_literal, NULL, NULL},
|
|
{OPT_FILE, OPT_IMAGE, L"initrd", NULL, NULL, opt_offsetof(initrd)},
|
|
{OPT_STR, OPT_IMAGE, L"label", NULL, NULL, opt_offsetof(label)},
|
|
{OPT_FILE, OPT_IMAGE, L"image", do_image, NULL, opt_offsetof(kname)},
|
|
{OPT_STR, OPT_IMAGE, L"description", NULL, NULL, opt_offsetof(description)},
|
|
};
|
|
|
|
#define OPTION_IS_GLOBAL(p) ((p)->scope == OPT_GLOBAL)
|
|
#define OPTION_IS_IMG_SYS(p) ((p)->scope == OPT_IMAGE_SYS)
|
|
|
|
#define CHAR_EOF (CHAR16)-1 /* Unicode version of EOF */
|
|
#define CHAR_NUM0 L'0'
|
|
#define CHAR_NUM9 L'9'
|
|
|
|
static UINTN line_num;
|
|
static INTN back; /* can go back by one char */
|
|
|
|
static config_option_group_t *global_option_list;
|
|
static config_option_group_t *image_option_list;
|
|
|
|
|
|
static config_option_group_t *current_options;
|
|
|
|
static boot_image_t *image_list, *first_image;
|
|
static boot_image_t *current_img;
|
|
|
|
static INT8 config_buf[CONFIG_BUFSIZE]; /* input buffer: file must be in ASCII! */
|
|
static UINTN buf_max, buf_pos;
|
|
|
|
static fops_fd_t config_fd;
|
|
|
|
static VOID
|
|
config_error(CHAR16 *msg,...)
|
|
{
|
|
va_list ap;
|
|
extern UINTN _IPrint (UINTN, UINTN, SIMPLE_TEXT_OUTPUT_INTERFACE *, CHAR16 *, CHAR8 *, va_list);
|
|
|
|
Print(L"near line %d: ",line_num);
|
|
|
|
va_start(ap,msg);
|
|
_IPrint((UINTN)-1, (UINTN)-1, systab->ConOut, msg, NULL, ap);
|
|
va_end(ap);
|
|
Print(L"\n");
|
|
}
|
|
|
|
/*
|
|
* low level read routine
|
|
* Return:
|
|
*
|
|
* Success:
|
|
* - the next available unicode character
|
|
* Error:
|
|
* - CHAR_EOF : indicating error or EOF
|
|
*
|
|
* XXX: we suppose that the file is in ASCII format!
|
|
*/
|
|
static CHAR16
|
|
getc(VOID)
|
|
{
|
|
EFI_STATUS status;
|
|
|
|
if (buf_pos == 0 || buf_pos == buf_max) {
|
|
buf_max = CONFIG_BUFSIZE;
|
|
status = fops_read(config_fd, config_buf, &buf_max);
|
|
if (EFI_ERROR(status) || buf_max == 0) return CHAR_EOF;
|
|
|
|
buf_pos = 0;
|
|
}
|
|
return (CHAR16)config_buf[buf_pos++];
|
|
}
|
|
|
|
|
|
/*
|
|
* get the next unicode character with a one character
|
|
* rewind buffer.
|
|
*/
|
|
static CHAR16
|
|
next(VOID)
|
|
{
|
|
CHAR16 ch;
|
|
|
|
if (back) {
|
|
ch = back;
|
|
back = 0;
|
|
return ch;
|
|
}
|
|
return getc();
|
|
}
|
|
|
|
/*
|
|
* rewind by one character
|
|
*/
|
|
static VOID
|
|
again(CHAR16 ch)
|
|
{
|
|
if (back) { config_error(L"config: again invoked twice"); }
|
|
back = ch;
|
|
}
|
|
|
|
/*
|
|
* Look for matching option in the current group
|
|
*
|
|
* Return:
|
|
* - pointer to option if found
|
|
* - NULL if not found
|
|
*/
|
|
static config_option_t *
|
|
find_option(config_option_group_t *grp, CHAR16 *str)
|
|
{
|
|
config_option_t *p = NULL;
|
|
config_option_t *end;
|
|
|
|
while(grp) {
|
|
p = grp->options;
|
|
end = grp->options+grp->nentries;
|
|
|
|
while (p != end) {
|
|
if (!StrCmp(str, p->name)) return p;
|
|
p++;
|
|
}
|
|
grp = grp->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* main parser function
|
|
* Return:
|
|
* - a token code representing the kind of element found
|
|
* - TOK_EOF: end-of-file (or error) detected
|
|
* - TOK_STR: if string is found
|
|
* - TOK_EQUAL: for the '=' sign
|
|
* - TOK_ERR: in case of (parsing) error
|
|
*/
|
|
static token_t
|
|
get_token(CHAR16 *str, UINTN maxlen)
|
|
{
|
|
INTN ch, escaped;
|
|
CHAR16 *here;
|
|
|
|
for (;;) {
|
|
while ((ch = next()), ch == ' ' || ch == '\t' || ch == '\n') if (ch == '\n') line_num++;
|
|
|
|
if (ch == CHAR_EOF) return TOK_EOF;
|
|
|
|
if (ch != '#') break;
|
|
|
|
/* skip comment line */
|
|
while ((ch = next()), ch != '\n') if (ch == CHAR_EOF) return TOK_EOF;
|
|
line_num++;
|
|
}
|
|
if (ch == '=') return TOK_EQUAL;
|
|
|
|
if (ch == '"') {
|
|
here = str;
|
|
while (here-str < maxlen) {
|
|
if ((ch = next()) == CHAR_EOF) {
|
|
config_error(L"EOF in quoted string");
|
|
return TOK_ERR;
|
|
}
|
|
if (ch == '"') {
|
|
*here = 0;
|
|
return TOK_STR;
|
|
}
|
|
if (ch == '\\') {
|
|
ch = next();
|
|
if (ch != '"' && ch != '\\' && ch != '\n') {
|
|
config_error(L"Bad use of \\ in quoted string");
|
|
return TOK_ERR;
|
|
}
|
|
if (ch == '\n') continue;
|
|
#if 0
|
|
while ((ch = next()), ch == ' ' || ch == '\t');
|
|
if (!ch) continue;
|
|
again(ch);
|
|
ch = ' ';
|
|
}
|
|
#endif
|
|
}
|
|
if (ch == '\n' || ch == '\t') {
|
|
config_error(L"\\n and \\t are not allowed in quoted strings");
|
|
return TOK_ERR;
|
|
}
|
|
*here++ = ch;
|
|
}
|
|
config_error(L"Quoted string is too long");
|
|
return TOK_ERR;
|
|
}
|
|
|
|
here = str;
|
|
escaped = 0;
|
|
|
|
while (here-str < maxlen) {
|
|
if (escaped) {
|
|
if (ch == CHAR_EOF) {
|
|
config_error(L"\\ precedes EOF");
|
|
return TOK_ERR;
|
|
}
|
|
if (ch == '\n') line_num++;
|
|
else *here++ = ch == '\t' ? ' ' : ch;
|
|
escaped = 0;
|
|
}
|
|
else {
|
|
if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '#' ||
|
|
ch == '=' || ch == CHAR_EOF) {
|
|
again(ch);
|
|
*here = 0;
|
|
return TOK_STR;
|
|
}
|
|
if (!(escaped = (ch == '\\'))) *here++ = ch;
|
|
}
|
|
ch = next();
|
|
}
|
|
config_error(L"Token is too long");
|
|
return TOK_ERR;
|
|
}
|
|
|
|
static INTN
|
|
image_check(boot_image_t *img)
|
|
{
|
|
boot_image_t *b;
|
|
|
|
if (img == NULL) return -1;
|
|
|
|
/* do the obvious first */
|
|
if (img->label[0] == '\0') {
|
|
config_error(L"image has no label");
|
|
return -1;
|
|
}
|
|
|
|
/* image_list points to the */
|
|
for(b=image_list; b; b = b->next) {
|
|
if (img == b) continue;
|
|
if (!StrCmp(img->label, b->label)) {
|
|
config_error(L"image with label %s already defined", img->label);
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static INTN
|
|
global_check(VOID)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static INTN
|
|
final_check(VOID)
|
|
{
|
|
boot_image_t *b;
|
|
|
|
if (global_config.default_image_name[0]) {
|
|
for(b=image_list; b; b = b->next) {
|
|
if (!StrCmp(b->label, global_config.default_image_name)) goto found;
|
|
}
|
|
config_error(L"default image '%s' not defined ", global_config.default_image_name);
|
|
return -1;
|
|
}
|
|
global_config.default_image = first_image;
|
|
return 0;
|
|
found:
|
|
global_config.default_image = b;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* depending on the active set of options
|
|
* adjust the option data pointer to:
|
|
* if global option set:
|
|
* - just the straight value from p->data
|
|
* if image option set:
|
|
* - adjust as offset to image
|
|
* Return:
|
|
* - the adjusted pointer
|
|
*/
|
|
static inline VOID *
|
|
adjust_pointer(config_option_t *p)
|
|
{
|
|
/*
|
|
* adjust pointer
|
|
*/
|
|
if (OPTION_IS_GLOBAL(p)) return p->data;
|
|
|
|
if (OPTION_IS_IMG_SYS(p)) return (VOID *)((UINTN)¤t_img->sys_img_opts + p->data);
|
|
|
|
return (VOID *)((UINTN)current_img + p->data);
|
|
}
|
|
|
|
/*
|
|
* create a new image entry
|
|
*/
|
|
static INTN
|
|
do_image(config_option_t *p, VOID *str)
|
|
{
|
|
boot_image_t *img;
|
|
|
|
/*
|
|
* if we move to a new image from the current one
|
|
* then we need to check for validity of current.
|
|
*
|
|
* if this is the first image, then check the global
|
|
* options.
|
|
*/
|
|
if (current_img) {
|
|
if (image_check(current_img) == -1) return -1;
|
|
} else if (global_check() == -1) return -1;
|
|
|
|
img = (boot_image_t *)alloc(sizeof(boot_image_t), EfiLoaderData);
|
|
if (img == NULL) return -1;
|
|
|
|
Memset(img, 0, sizeof(boot_image_t));
|
|
|
|
DBG_PRT((L"must do image on %s", (CHAR16 *)str));
|
|
|
|
/* copy kernel file name */
|
|
StrCpy(img->kname, str);
|
|
|
|
/* switch to image mode */
|
|
current_options = image_option_list;
|
|
|
|
/* keep track of first image in case no default is defined */
|
|
if (image_list == NULL) first_image = img;
|
|
|
|
/* append to end of image list, so when a chooser asks for the list
|
|
* it gets them in the order they were in in the config file
|
|
*/
|
|
if (image_list == NULL)
|
|
image_list = img;
|
|
else {
|
|
boot_image_t * p = image_list;
|
|
|
|
while (p->next)
|
|
p = p->next;
|
|
p->next = img;
|
|
}
|
|
|
|
/* update current image */
|
|
current_img = img;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* by default all boolean options are defined
|
|
* as false. This function sets the boolean
|
|
* to true
|
|
*/
|
|
static INTN
|
|
do_boolean(config_option_t *p)
|
|
{
|
|
INT8 *buf;
|
|
|
|
buf = adjust_pointer(p);
|
|
|
|
if (p->action) return p->action(p, NULL);
|
|
|
|
/* set the field to true, overwrite if already defined */
|
|
*buf = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* the image option 'literal' requires special handling
|
|
* because it overrides any defined option be it global or
|
|
* local. so we use the option field and record the fact that
|
|
* it should be interpreted as literal
|
|
*/
|
|
static INTN
|
|
do_literal(config_option_t *p, VOID *str)
|
|
{
|
|
/*
|
|
* we know we have a valid current image at this point
|
|
*/
|
|
StrCpy(current_img->options, str);
|
|
|
|
current_img->literal = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static INTN
|
|
do_options(config_option_t *p, VOID *str)
|
|
{
|
|
/* we ignore append if literal is already defined */
|
|
if (current_img->literal) return 0;
|
|
|
|
/*
|
|
* we know we have a valid current image at this point
|
|
*/
|
|
StrCpy(current_img->options, str);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static INTN
|
|
do_numeric(config_option_t *p)
|
|
{
|
|
CHAR16 numstr[MAX_STRING];
|
|
CHAR16 *str;
|
|
token_t tok;
|
|
UINTN *buf;
|
|
UINTN tmp;
|
|
|
|
/*
|
|
* match the '=' sign
|
|
*/
|
|
tok = get_token(numstr, MAX_STRING);
|
|
if (tok != TOK_EQUAL) {
|
|
config_error(L"Option %s expects an equal signal + value", p->name);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* now get the value
|
|
* XXX: = lexer should return TOK_NUM (and we'd be done)
|
|
*/
|
|
tok = get_token(numstr, MAX_STRING);
|
|
if (tok != TOK_STR) {
|
|
config_error(L"Option %s expects a value", p->name);
|
|
return -1;
|
|
}
|
|
str = numstr;
|
|
/*
|
|
* if there is a customized way of doing the operation
|
|
* do it and bypass generic
|
|
*/
|
|
if (p->action) return p->action(p, str);
|
|
|
|
/*
|
|
* no field to update
|
|
*/
|
|
if (p->data == NULL) return 0;
|
|
|
|
buf = (UINTN *)adjust_pointer(p);
|
|
|
|
while (*str && *str >= CHAR_NUM0 && *str <= CHAR_NUM9) str++;
|
|
if (*str) {
|
|
config_error(L"%s is expecting a numeric decimal value", p->name);
|
|
return -1;
|
|
}
|
|
|
|
tmp = Atoi(numstr);
|
|
|
|
if (p->check && p->check(&tmp) == -1) return -1;
|
|
|
|
/*
|
|
* check for multiple definitions in the same context
|
|
* XXX: zero must not be a valid number !
|
|
*/
|
|
if (*buf) {
|
|
config_error(L"option %s is already defined in this context", p->name);
|
|
return -1;
|
|
}
|
|
|
|
*buf = tmp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static INTN
|
|
check_verbosity(VOID *data)
|
|
{
|
|
UINTN *val = (UINTN *)data;
|
|
|
|
if (*val > 5) {
|
|
config_error(L"Verbosity level must be in [0-5] and not %d", *val);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* here we simply check if chooser is compiled in. At this point, the chooser
|
|
* initialization is not done, so we don't know if that chooser will even be
|
|
* useable.
|
|
*/
|
|
static INTN
|
|
check_chooser(VOID *data)
|
|
{
|
|
CHAR16 *chooser = (CHAR16 *)data;
|
|
|
|
if (exist_chooser(chooser) == -1) {
|
|
config_error(L"chooser %s is unknown\n", chooser);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static INTN
|
|
do_string_core(config_option_t *p, CHAR16 *str, UINTN maxlen, CHAR16 *msg)
|
|
{
|
|
token_t tok;
|
|
CHAR16 *buf;
|
|
|
|
/*
|
|
* match the '=' sign
|
|
*/
|
|
tok = get_token(str, maxlen);
|
|
if (tok != TOK_EQUAL) {
|
|
config_error(L"Option %s expects an equal signal + %s", p->name, msg);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* now get the value
|
|
*/
|
|
tok = get_token(str, maxlen);
|
|
if (tok != TOK_STR) {
|
|
config_error(L"Option %s expects %s", p->name, msg);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* if there is a customized way of doing the operation
|
|
* do it and bypass generic
|
|
*/
|
|
if (p->action) return p->action(p, str);
|
|
|
|
/*
|
|
* no field to update
|
|
*/
|
|
if (p->data == NULL) return 0;
|
|
|
|
buf = adjust_pointer(p);
|
|
|
|
if (*buf != CHAR_NULL) {
|
|
config_error(L"'%s' already defined", p->name);
|
|
return -1;
|
|
}
|
|
if (p->check && p->check(str) == -1) return -1;
|
|
|
|
/*
|
|
* initialize field
|
|
*/
|
|
StrCpy(buf, str);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static INTN
|
|
do_string(config_option_t *p)
|
|
{
|
|
CHAR16 str[MAX_STRING];
|
|
|
|
return do_string_core(p, str, MAX_STRING, L"string");
|
|
}
|
|
|
|
static INTN
|
|
do_file(config_option_t *p)
|
|
{
|
|
CHAR16 str[FILENAME_MAXLEN];
|
|
|
|
return do_string_core(p, str, FILENAME_MAXLEN, L"filename");
|
|
}
|
|
|
|
|
|
static INTN
|
|
do_cmd(config_option_t *p)
|
|
{
|
|
CHAR16 str[CMDLINE_MAXLEN];
|
|
return do_string_core(p, str, CMDLINE_MAXLEN, L"kernel options");
|
|
}
|
|
|
|
|
|
INTN
|
|
config_parse(VOID)
|
|
{
|
|
CHAR16 str[MAX_STRING];
|
|
INTN ret = -1;
|
|
token_t tok;
|
|
config_option_t *p;
|
|
|
|
for (;;) {
|
|
tok = get_token(str, MAX_STRING);
|
|
|
|
if (tok == TOK_EOF) break;
|
|
|
|
if (tok == TOK_ERR) return -1;
|
|
|
|
if ( (p = find_option(current_options, str)) == NULL) {
|
|
config_error(L"Unkown option %s", str);
|
|
return -1;
|
|
}
|
|
|
|
/* make sure we trap in case of error */
|
|
ret = -1;
|
|
|
|
switch(p->type) {
|
|
case OPT_BOOL:
|
|
ret = do_boolean(p);
|
|
break;
|
|
case OPT_STR:
|
|
ret = do_string(p);
|
|
break;
|
|
case OPT_NUM:
|
|
ret = do_numeric(p);
|
|
break;
|
|
case OPT_FILE:
|
|
ret = do_file(p);
|
|
break;
|
|
case OPT_CMD:
|
|
ret = do_cmd(p);
|
|
break;
|
|
default:
|
|
config_error(L"Unkown option type %d", p->type);
|
|
}
|
|
if (ret == -1) goto error;
|
|
}
|
|
if (current_img) {
|
|
ret = image_check(current_img);
|
|
} else {
|
|
config_error(L"No image defined !");
|
|
}
|
|
if (ret == 0) ret = final_check();
|
|
error:
|
|
return ret;
|
|
}
|
|
|
|
static VOID
|
|
update_elilo_opt(VOID)
|
|
{
|
|
/*
|
|
* update boolean options first
|
|
* Rule: by default not set unless explcitely requested on command line
|
|
* therefore we can just update from global_config is set there.
|
|
*/
|
|
if (global_config.alt_check) elilo_opt.alt_check = 1;
|
|
|
|
if (global_config.debug) elilo_opt.debug = 1;
|
|
if (global_config.prompt) elilo_opt.prompt = 1;
|
|
|
|
/*
|
|
* update only if not set on command line
|
|
* XXX: rely on the existence of a non-valid value as a marker than
|
|
* the option was not specified on the command line
|
|
*/
|
|
if (global_config.verbose && elilo_opt.verbose == 0)
|
|
elilo_opt.verbose = global_config.verbose;
|
|
|
|
if (global_config.chooser[0] && elilo_opt.chooser[0] == 0)
|
|
StrCpy(elilo_opt.chooser, global_config.chooser);
|
|
|
|
/*
|
|
* if edd30_no_force not set by command line option but via config
|
|
* file, then propagate
|
|
*/
|
|
if (global_config.edd30_no_force && elilo_opt.edd30_no_force == 0)
|
|
elilo_opt.edd30_no_force = 1;
|
|
|
|
/*
|
|
* Difficult case of numeric where 0 can be a valid value
|
|
* XXX: find a better way of handling these options!
|
|
*/
|
|
if (global_config.delay && elilo_opt.delay_set == 0)
|
|
elilo_opt.delay = global_config.delay;
|
|
|
|
/* readjusted by caller if necessary. 0 not a valid value here */
|
|
elilo_opt.timeout = global_config.timeout;
|
|
|
|
/* initrd is updated later when we have an image match */
|
|
}
|
|
|
|
/*
|
|
* When called after read_config(), this function returns the config file
|
|
* used by the loader, or NULL if no file was found.
|
|
*/
|
|
CHAR16 *
|
|
get_config_file(VOID)
|
|
{
|
|
return global_config.config_file[0] ? global_config.config_file : NULL;
|
|
}
|
|
|
|
EFI_STATUS
|
|
read_config(CHAR16 *filename, INTN retry)
|
|
{
|
|
EFI_STATUS status;
|
|
INTN ret;
|
|
|
|
if (filename == NULL) return EFI_INVALID_PARAMETER;
|
|
|
|
VERB_PRT(3, Print(L"trying config file %s\n", filename));
|
|
|
|
StrCpy(global_config.config_file, filename);
|
|
|
|
status = fops_open(filename, &config_fd);
|
|
if (EFI_ERROR(status)) {
|
|
/*
|
|
* if the user explicitely specified a filename and we can't
|
|
* find it, then we must fail.
|
|
*/
|
|
if (elilo_opt.parse_only || retry == 0) {
|
|
VERB_PRT(3, Print(L"cannot open config file %s\n", filename));
|
|
return status;
|
|
}
|
|
/*
|
|
* if not already submitted filename,
|
|
*/
|
|
if (StrCmp(filename, ELILO_ARCH_DEFAULT_CONFIG)) {
|
|
/*
|
|
* try the arch default file, now
|
|
*/
|
|
VERB_PRT(3,Print(L"config file %s not found, trying %s\n",
|
|
filename, ELILO_ARCH_DEFAULT_CONFIG));
|
|
|
|
StrCpy(global_config.config_file,ELILO_ARCH_DEFAULT_CONFIG);
|
|
|
|
status = fops_open(ELILO_ARCH_DEFAULT_CONFIG, &config_fd);
|
|
}
|
|
/*
|
|
* if arch specific did not work, try generic
|
|
*/
|
|
if (EFI_ERROR(status) && StrCmp(filename, ELILO_DEFAULT_CONFIG)) {
|
|
/*
|
|
* try the default file as a last resort
|
|
*/
|
|
VERB_PRT(3,Print(L"config file %s not found, trying %s\n",
|
|
ELILO_ARCH_DEFAULT_CONFIG, ELILO_DEFAULT_CONFIG));
|
|
|
|
StrCpy(global_config.config_file, ELILO_DEFAULT_CONFIG);
|
|
status = fops_open(ELILO_DEFAULT_CONFIG, &config_fd);
|
|
}
|
|
/*
|
|
* if nothing worked, then bail out
|
|
*/
|
|
if (EFI_ERROR(status)) {
|
|
VERB_PRT(3, Print(L"no valid config file found\n"));
|
|
global_config.config_file[0] = CHAR_NULL;
|
|
return status;
|
|
}
|
|
}
|
|
/*
|
|
* start numbering at line 1
|
|
*/
|
|
line_num = 1;
|
|
|
|
ret = config_parse();
|
|
|
|
fops_close(config_fd);
|
|
|
|
DBG_PRT((L"done parsing config file\n"));
|
|
|
|
if (ret != 0) return EFI_INVALID_PARAMETER;
|
|
|
|
update_elilo_opt();
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
VOID
|
|
print_label_list(VOID)
|
|
{
|
|
boot_image_t *img, *dfl = global_config.default_image;
|
|
|
|
if (dfl) Print(L"%s ", dfl->label);
|
|
|
|
for (img = image_list; img; img = img->next) {
|
|
if (img != dfl) Print(L"%s ", img->label);
|
|
}
|
|
}
|
|
|
|
/* Make labels and corresponding descriptions available to choosers. The
|
|
* idea is that the caller passes NULL for 'prev'; the first time, and
|
|
* passes the previously returned value on subsequent calls. The caller
|
|
* repeats until this fn returns NULL.
|
|
*/
|
|
|
|
VOID *
|
|
get_next_description(VOID *prev, CHAR16 **label, CHAR16 **description)
|
|
{
|
|
boot_image_t *img = (boot_image_t *)prev;
|
|
|
|
if (img == NULL)
|
|
img = image_list;
|
|
else
|
|
img = img->next;
|
|
|
|
if (img) {
|
|
*label = img->label;
|
|
*description = img->description;
|
|
return (void *)img;
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* find a description using the label name
|
|
* return NULL if label not found or no description defined
|
|
*/
|
|
CHAR16 *
|
|
find_description(CHAR16 *label)
|
|
{
|
|
boot_image_t *img;
|
|
|
|
/* Attempt to find the image name now */
|
|
for (img = image_list; img; img = img->next) {
|
|
if (StriCmp(img->label, label) == 0) {
|
|
return img->description;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
INTN
|
|
find_label(CHAR16 *label, CHAR16 *kname, CHAR16 *options, CHAR16 *initrd)
|
|
{
|
|
boot_image_t *img;
|
|
|
|
if (label == NULL) {
|
|
if (global_config.default_image == NULL) return -1;
|
|
img = global_config.default_image;
|
|
goto found;
|
|
}
|
|
|
|
options[0] = 0;
|
|
|
|
/* Attempt to find the image name now */
|
|
for (img = image_list; img; img = img->next) {
|
|
if (StriCmp(img->label, label) == 0) {
|
|
goto found;
|
|
}
|
|
}
|
|
/*
|
|
* when the label does not exist, we still propagate the global options
|
|
*/
|
|
if (global_config.root[0]) {
|
|
StrCpy(options, L" root=");
|
|
StrCat(options, global_config.root);
|
|
}
|
|
|
|
if (global_config.options[0]) {
|
|
StrCat(options, L" ");
|
|
StrCat(options, global_config.options);
|
|
}
|
|
if (global_config.readonly) StrCat(options, L" ro");
|
|
|
|
if (global_config.initrd[0]) StrCpy(initrd, global_config.initrd);
|
|
|
|
/* make sure we don't get garbage here */
|
|
elilo_opt.sys_img_opts = NULL;
|
|
|
|
return -1;
|
|
found:
|
|
StrCpy(kname, img->kname);
|
|
|
|
/*
|
|
* literal option overrides any other image-based or global option
|
|
*
|
|
* In any case, the image option has priority over the global option
|
|
*/
|
|
if (img->literal == 0) {
|
|
if (img->root[0] || global_config.root[0]) {
|
|
StrCat(options, L"root=");
|
|
StrCat(options, img->root[0] ? img->root : global_config.root);
|
|
}
|
|
/* XXX: check max length */
|
|
if (img->options[0] || global_config.options[0]) {
|
|
StrCat(options, L" ");
|
|
StrCat(options, img->options[0] ? img->options: global_config.options);
|
|
}
|
|
if (img->readonly || global_config.readonly) {
|
|
StrCat(options, L" ro");
|
|
}
|
|
} else {
|
|
/* literal options replace everything */
|
|
StrCpy(options, img->options);
|
|
}
|
|
|
|
/* per image initrd has precedence over global initrd */
|
|
if (img->initrd[0])
|
|
StrCpy(initrd, img->initrd);
|
|
else if (global_config.initrd[0])
|
|
StrCpy(initrd, global_config.initrd);
|
|
|
|
/*
|
|
* point to architecture dependent options for this image
|
|
*/
|
|
elilo_opt.sys_img_opts = &img->sys_img_opts;
|
|
|
|
DBG_PRT((L"label %s: kname=%s options=%s initrd=%s", img->label, kname, options, initrd));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static VOID
|
|
print_options(config_option_group_t *grp, BOOLEAN first)
|
|
{
|
|
config_option_t *end, *p;
|
|
CHAR16 *str;
|
|
|
|
while (grp) {
|
|
p = grp->options;
|
|
end = grp->options+grp->nentries;
|
|
while (p != end) {
|
|
str = NULL;
|
|
switch(p->type) {
|
|
case OPT_BOOL:
|
|
str = L"%s";
|
|
break;
|
|
case OPT_STR :
|
|
str = L"%s=string";
|
|
break;
|
|
case OPT_FILE :
|
|
str = L"%s=filename";
|
|
break;
|
|
case OPT_CMD :
|
|
str = L"%s=kernel_options";
|
|
break;
|
|
case OPT_NUM :
|
|
str = L"%s=number";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (str && first == FALSE) Print(L", ");
|
|
if (str) Print(str, p->name);
|
|
first = FALSE;
|
|
p++;
|
|
}
|
|
grp = grp->next;
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
print_config_options(VOID)
|
|
{
|
|
Print(L"Global options supported:\n");
|
|
|
|
print_options(global_option_list, TRUE);
|
|
Print(L"\n\n");
|
|
|
|
Print(L"Image options supported:\n");
|
|
print_options(image_option_list, TRUE);
|
|
Print(L"\n");
|
|
}
|
|
|
|
|
|
/*
|
|
* returns a pointer to filename used for message N (which). NULL if it does
|
|
* not exist.
|
|
*/
|
|
CHAR16 *
|
|
get_message_filename(INTN which)
|
|
{
|
|
if (which < 0 || which >= MAX_MESSAGES) return NULL;
|
|
return global_config.message_file[which];
|
|
}
|
|
|
|
INTN
|
|
register_config_options(config_option_t *opt, UINTN n, config_option_group_scope_t group)
|
|
{
|
|
config_option_group_t *newgrp, **grp;
|
|
|
|
if (opt == NULL || n == 0 || (group != OPTIONS_GROUP_GLOBAL && group != OPTIONS_GROUP_IMAGE)) return -1;
|
|
|
|
VERB_PRT(3, Print(L"registering %d options for group %s\n", n, group == OPTIONS_GROUP_GLOBAL ? L"global" : L"image"));
|
|
|
|
newgrp = alloc(sizeof(config_option_group_t), EfiLoaderData);
|
|
if (newgrp == NULL) return -1;
|
|
|
|
grp = group == OPTIONS_GROUP_GLOBAL ? &global_option_list : &image_option_list;
|
|
|
|
if (*grp) while ((*grp)->next) grp = &(*grp)->next;
|
|
|
|
newgrp->options = opt;
|
|
newgrp->next = NULL;
|
|
newgrp->nentries = n;
|
|
|
|
if (*grp) {
|
|
(*grp)->next = newgrp;
|
|
} else {
|
|
*grp = newgrp;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* initialize config options: register global and per image options
|
|
*/
|
|
INTN
|
|
config_init(VOID)
|
|
{
|
|
INTN ret;
|
|
|
|
ret = register_config_options(global_common_options,
|
|
sizeof(global_common_options)/sizeof(config_option_t),
|
|
OPTIONS_GROUP_GLOBAL);
|
|
if (ret == -1) return -1;
|
|
|
|
ret = register_config_options(image_common_options,
|
|
sizeof(image_common_options)/sizeof(config_option_t),
|
|
OPTIONS_GROUP_IMAGE);
|
|
|
|
current_options = global_option_list;
|
|
|
|
return ret;
|
|
}
|
|
|