elilo/config.c

1192 lines
28 KiB
C

/*
* Copyright (C) 2001-2003 Hewlett-Packard Co.
* Contributed by Stephane Eranian <eranian@hpl.hp.com>
* Contributed by Fenghua Yu <fenghua.yu@intel.com>
* Contributed by Bibo Mao <bibo.mao@intel.com>
* Contributed by Chandramouli Narayanan <mouli@linux.intel.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"
#elif defined (CONFIG_x86_64)
#define ELILO_ARCH_DEFAULT_CONFIG L"elilo-x86_64.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 512
#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[CMDLINE_MAXLEN];
CHAR16 initrd[FILENAME_MAXLEN];
CHAR16 vmcode[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 vmcode[FILENAME_MAXLEN];/* globally defined boot-time module */
CHAR16 options[CMDLINE_MAXLEN];
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"vmm", NULL, NULL, global_config.vmcode},
{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_FILE, OPT_IMAGE, L"vmm", NULL, NULL, opt_offsetof(vmcode)},
{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,...)
{
Print(L"near line %d: ",line_num);
IPrint(systab->ConOut, msg);
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;
}
/*
* config files served from pxe/tftpboot windows servers can contain
* extraneous '\r' characters, often the first char in the file, and
* we need to simply skip over those and continue on
*/
ch = getc();
if(ch == '\r')
ch = getc();
return ch;
}
/*
* 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_core(CHAR16 *str, UINTN maxlen, BOOLEAN rhs)
{
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;
/* skip comment line */
if (ch != '#') break;
while ((ch = next()), ch != '\n') if (ch == CHAR_EOF) return TOK_EOF;
line_num++;
}
if (ch == '=' && !rhs) 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 == CHAR_EOF || (ch == '=' && !rhs)) {
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 token_t
get_token(CHAR16 *str, UINTN maxlen)
{
return get_token_core(str, maxlen, FALSE);
}
static token_t
get_token_rhs(CHAR16 *str, UINTN maxlen)
{
return get_token_core(str, maxlen, TRUE);
}
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)&current_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_rhs(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) {
Print(L"Bad Token from elilo config file, parser read: %s\n elilo exiting\n", str);
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)
{
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)) {
VERB_PRT(3, Print(L"cannot open config file %s\n", filename));
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\n", dfl->label);
for (img = image_list; img; img = img->next) {
if (img != dfl) Print(L" %s\n", 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;
}
static void
add_root_to_options(CHAR16 *options, CHAR16 *root, CHAR16 *vmcode)
{
CHAR16 *o, ko[CMDLINE_MAXLEN];
if (vmcode[0]) {
for (o = options; *o; o++) {
if (*o == '-' && *(o+1) == '-')
break;
}
if (! *o) {
/* no separator found, add one */
StrCpy(o, L" -- ");
o++;
}
/* advance past separator and whitespace */
o += 2;
while (*o == ' ') o++;
} else {
o = options;
}
/* insert root param at this point */
StrCpy(ko, o);
StrCpy(o, L"root=");
StrCat(o, root);
StrCat(o, L" ");
StrCat(o, ko);
}
INTN
find_label(CHAR16 *label, CHAR16 *kname, CHAR16 *options, CHAR16 *initrd, CHAR16 *vmcode)
{
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.options[0]) {
StrCat(options, L" ");
StrCat(options, global_config.options);
}
if (global_config.root[0])
add_root_to_options(options, global_config.root, global_config.vmcode);
if (global_config.readonly) StrCat(options, L" ro");
if (global_config.initrd[0]) StrCpy(initrd, global_config.initrd);
if (global_config.vmcode[0]) StrCpy(vmcode, global_config.vmcode);
/* make sure we don't get garbage here */
elilo_opt.sys_img_opts = NULL;
return -1;
found:
StrCpy(kname, img->kname);
/* per image initrd has precedence over global initrd */
if (img->initrd[0])
StrCpy(initrd, img->initrd);
else
StrCpy(initrd, global_config.initrd);
/* per image vmcode has precedence over global vmcode */
if (img->vmcode[0])
StrCpy(vmcode, img->vmcode);
else
StrCpy(vmcode, global_config.vmcode);
/*
* 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) {
/* 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->root[0] || global_config.root[0]) {
add_root_to_options(options, img->root[0]
? img->root : global_config.root, vmcode);
}
if (img->readonly || global_config.readonly) {
StrCat(options, L" ro");
}
} else {
/* literal options replace everything */
StrCpy(options, img->options);
}
/*
* 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 vmcode=%s", img->label, kname, options, initrd, vmcode));
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;
}