blob: f23e690d4807ad0b37da58d91b9ae589d879ffa3 [file] [log] [blame] [raw]
/*
* cfg.c - Handling and parsing of yaboot.conf
*
* Copyright (C) 1995 Werner Almesberger
* 1996 Jakub Jelinek
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "setjmp.h"
#include "stdarg.h"
#include "stdlib.h"
#include "string.h"
#include "types.h"
#include "prom.h"
/* Imported functions */
extern int strcasecmp(const char *s1, const char *s2);
extern int strncasecmp(const char *cs, const char *ct, size_t n);
extern char bootoncelabel[1024];
typedef enum {
cft_strg, cft_flag, cft_end
} CONFIG_TYPE;
typedef struct {
CONFIG_TYPE type;
char *name;
void *data;
} CONFIG;
#define MAX_TOKEN 511
#define MAX_VAR_NAME MAX_TOKEN
#define EOF -1
CONFIG cf_options[] =
{
{cft_strg, "device", NULL},
{cft_strg, "partition", NULL},
{cft_strg, "default", NULL},
{cft_strg, "timeout", NULL},
{cft_strg, "password", NULL},
{cft_flag, "restricted", NULL},
{cft_strg, "message", NULL},
{cft_strg, "root", NULL},
{cft_strg, "ramdisk", NULL},
{cft_flag, "read-only", NULL},
{cft_flag, "read-write", NULL},
{cft_strg, "append", NULL},
{cft_strg, "initrd", NULL},
{cft_flag, "initrd-prompt", NULL},
{cft_strg, "initrd-size", NULL},
{cft_flag, "pause-after", NULL},
{cft_strg, "pause-message", NULL},
{cft_strg, "init-code", NULL},
{cft_strg, "init-message", NULL},
{cft_strg, "fgcolor", NULL},
{cft_strg, "bgcolor", NULL},
{cft_strg, "ptypewarning", NULL},
{cft_end, NULL, NULL}};
CONFIG cf_image[] =
{
{cft_strg, "image", NULL},
{cft_strg, "label", NULL},
{cft_strg, "alias", NULL},
{cft_flag, "single-key", NULL},
{cft_flag, "restricted", NULL},
{cft_strg, "device", NULL},
{cft_strg, "partition", NULL},
{cft_strg, "root", NULL},
{cft_strg, "ramdisk", NULL},
{cft_flag, "read-only", NULL},
{cft_flag, "read-write", NULL},
{cft_strg, "append", NULL},
{cft_strg, "literal", NULL},
{cft_strg, "initrd", NULL},
{cft_flag, "initrd-prompt", NULL},
{cft_strg, "initrd-size", NULL},
{cft_flag, "pause-after", NULL},
{cft_strg, "pause-message", NULL},
{cft_flag, "novideo", NULL},
{cft_strg, "sysmap", NULL},
{cft_end, NULL, NULL}};
static char flag_set;
static char *last_token = NULL, *last_item = NULL, *last_value = NULL;
static int line_num;
static int back = 0; /* can go back by one char */
static char *currp = NULL;
static char *endp = NULL;
static char *file_name = NULL;
static CONFIG *curr_table = cf_options;
static int ignore_entry;
static jmp_buf env;
static struct IMAGES {
CONFIG table[sizeof (cf_image) / sizeof (cf_image[0])];
int obsolete;
struct IMAGES *next;
} *images = NULL;
void cfg_error (char *msg,...)
{
va_list ap;
va_start (ap, msg);
prom_printf ("Config file error: ");
prom_vprintf (msg, ap);
va_end (ap);
prom_printf (" near line %d in file %s\n", line_num, file_name);
longjmp (env, 1);
}
void cfg_warn (char *msg,...)
{
va_list ap;
va_start (ap, msg);
prom_printf ("Config file warning: ");
prom_vprintf (msg, ap);
va_end (ap);
prom_printf (" near line %d in file %s\n", line_num, file_name);
}
inline int getc ()
{
if (currp == endp)
return EOF;
return *currp++;
}
#define next_raw next
static int next (void)
{
int ch;
if (!back)
return getc ();
ch = back;
back = 0;
return ch;
}
static void again (int ch)
{
back = ch;
}
static char *cfg_get_token (void)
{
char buf[MAX_TOKEN + 1];
char *here;
int ch, escaped;
if (last_token) {
here = last_token;
last_token = NULL;
return here;
}
while (1) {
while (ch = next (), ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')
if (ch == '\n' || ch == '\r')
line_num++;
if (ch == EOF || ch == (int)NULL)
return NULL;
if (ch != '#')
break;
while (ch = next_raw (), (ch != '\n' && ch != '\r'))
if (ch == EOF)
return NULL;
line_num++;
}
if (ch == '=')
return strdup ("=");
if (ch == '"') {
here = buf;
while (here - buf < MAX_TOKEN) {
if ((ch = next ()) == EOF)
cfg_error ("EOF in quoted string");
if (ch == '"') {
*here = 0;
return strdup (buf);
}
if (ch == '\\') {
ch = next ();
switch (ch) {
case '"':
case '\\':
break;
case '\n':
case '\r':
while ((ch = next ()), ch == ' ' || ch == '\t');
if (!ch)
continue;
again (ch);
ch = ' ';
break;
case 'n':
ch = '\n';
break;
default:
cfg_error ("Bad use of \\ in quoted string");
}
} else if ((ch == '\n') || (ch == '\r'))
cfg_error ("newline is not allowed in quoted strings");
*here++ = ch;
}
cfg_error ("Quoted string is too long");
return 0; /* not reached */
}
here = buf;
escaped = 0;
while (here - buf < MAX_TOKEN) {
if (escaped) {
if (ch == EOF)
cfg_error ("\\ precedes EOF");
if (ch == '\n')
line_num++;
else
*here++ = ch == '\t' ? ' ' : ch;
escaped = 0;
} else {
if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '#' ||
ch == '=' || ch == EOF) {
again (ch);
*here = 0;
return strdup (buf);
}
if (!(escaped = (ch == '\\')))
*here++ = ch;
}
ch = next ();
}
cfg_error ("Token is too long");
return 0; /* not reached */
}
static void cfg_return_token (char *token)
{
last_token = token;
}
static int cfg_next (char **item, char **value)
{
char *this;
if (last_item) {
*item = last_item;
*value = last_value;
last_item = NULL;
return 1;
}
*value = NULL;
if (!(*item = cfg_get_token ()))
return 0;
if (!strcmp (*item, "="))
cfg_error ("Syntax error");
if (!(this = cfg_get_token ()))
return 1;
if (strcmp (this, "=")) {
cfg_return_token (this);
return 1;
}
if (!(*value = cfg_get_token ()))
cfg_error ("Value expected at EOF");
if (!strcmp (*value, "="))
cfg_error ("Syntax error after %s", *item);
return 1;
}
static char *cfg_get_strg_i (CONFIG * table, char *item)
{
CONFIG *walk;
for (walk = table; walk->type != cft_end; walk++)
if (walk->name && !strcasecmp (walk->name, item))
return walk->data;
return 0;
}
#if 0
// The one and only call to this procedure is commented out
// below, so we don't need this unless we decide to use it again.
static void cfg_return (char *item, char *value)
{
last_item = item;
last_value = value;
}
#endif
static int match_arch(const char *name)
{
static prom_handle root;
static char model[256], *p;
if (!root) {
if (!(root = prom_finddevice("/")))
return 0;
}
if (!model[0]) {
if (!prom_getprop(root, "compatible", model, sizeof(model)))
return 0;
}
for (p = model; *p; p += strlen(p) + 1) {
if (!strcasecmp(p, name))
return 1;
}
return 0;
}
static void check_for_obsolete(const char *label)
{
struct IMAGES *p;
char *cur_label;
/* Make sure our current entry isn't obsolete (ignored) */
for (p = images; p; p = p->next) {
if (curr_table == p->table && p->obsolete)
return;
}
for (p = images; p; p = p->next) {
if (curr_table == p->table)
continue;
cur_label = cfg_get_strg_i (p->table, "label");
if (!cur_label)
cur_label = cfg_get_strg_i (p->table, "image");
if (!cur_label)
continue;
if (!strcasecmp(cur_label, label))
p->obsolete = 1;
}
}
static int cfg_set (char *item, char *value)
{
CONFIG *walk;
if (!strncasecmp (item, "image", 5)) {
struct IMAGES **p = &images;
int ignore = 0;
if (item[5] == '[' && item[strlen(item) - 1] == ']') {
char *s, *q = item;
/* Get rid of braces */
item[strlen(item) - 1] = 0;
item[5] = 0;
for (s = item + 6; q; s = q) {
q = strchr(s, '|');
if (q)
*q++ = 0;
if (match_arch(s))
goto cfg_set_cont;
}
/* This just creates an unused table. It will get ignored */
ignore = 1;
} else if (item[5])
goto cfg_set_redo;
cfg_set_cont:
while (*p)
p = &((*p)->next);
*p = (struct IMAGES *)malloc (sizeof (struct IMAGES));
if (*p == NULL) {
prom_printf("malloc error in cfg_set\n");
return -1;
}
(*p)->next = 0;
(*p)->obsolete = ignore;
curr_table = ((*p)->table);
memcpy (curr_table, cf_image, sizeof (cf_image));
}
cfg_set_redo:
for (walk = curr_table; walk->type != cft_end; walk++) {
if (walk->name && !strcasecmp (walk->name, item)) {
if (value && walk->type != cft_strg)
cfg_warn ("'%s' doesn't have a value", walk->name);
else if (!value && walk->type == cft_strg)
cfg_warn ("Value expected for '%s'", walk->name);
else {
if (!strcasecmp (item, "label"))
check_for_obsolete(value);
if (walk->data)
cfg_warn ("Duplicate entry '%s'", walk->name);
if (walk->type == cft_flag)
walk->data = &flag_set;
else if (walk->type == cft_strg)
walk->data = value;
}
break;
}
}
if (walk->type != cft_end)
return 1;
//cfg_return (item, value);
return 0;
}
static int cfg_reset ()
{
CONFIG *walk;
#if DEBUG
prom_printf("Resetting image table\n");
#endif
line_num = 0;
images = NULL;
curr_table = NULL;
curr_table = cf_options;
for (walk = curr_table; walk->type != cft_end; walk++) {
#if DEBUG
prom_printf("ItemA %s = %s\n", walk->name, walk->data);
#endif
if (walk->data != NULL)
walk->data = NULL;
#if DEBUG
prom_printf("ItemB %s = %s\n\n", walk->name, walk->data);
#endif
}
return 0;
}
int cfg_parse (char *cfg_file, char *buff, int len)
{
char *item, *value;
file_name = cfg_file;
currp = buff;
endp = currp + len;
cfg_reset();
if (setjmp (env))
return -1;
while (1) {
if (!cfg_next (&item, &value))
return 0;
if (!cfg_set (item, value)) {
#if DEBUG
prom_printf("Can't set item %s to value %s\n", item, value);
#endif
}
free (item);
}
}
char *cfg_get_strg (char *image, char *item)
{
struct IMAGES *p;
char *label, *alias;
char *ret;
if (!image)
return cfg_get_strg_i (cf_options, item);
for (p = images; p; p = p->next) {
if (p->obsolete)
continue;
label = cfg_get_strg_i (p->table, "label");
if (!label) {
label = cfg_get_strg_i (p->table, "image");
alias = strrchr (label, '/');
if (alias)
label = alias + 1;
}
alias = cfg_get_strg_i (p->table, "alias");
if (!strcmp (label, image) || (alias && !strcmp (alias, image))) {
ret = cfg_get_strg_i (p->table, item);
if (!ret)
ret = cfg_get_strg_i (cf_options, item);
return ret;
}
}
return 0;
}
int cfg_get_flag (char *image, char *item)
{
return !!cfg_get_strg (image, item);
}
static int printl_count = 0;
static void printlabel (char *label, int defflag)
{
int len = strlen (label);
char a = ' ';
if (!printl_count)
prom_printf ("\n");
switch (defflag) {
case 1: a='*'; break;
case 2: a='&'; break;
default: a=' '; break;
}
prom_printf ("%c %s", a, label);
while (len++ < 25)
prom_putchar (' ');
printl_count++;
if (printl_count == 3)
printl_count = 0;
}
void cfg_print_images (void)
{
struct IMAGES *p;
char *label, *alias;
char *ret = cfg_get_strg_i (cf_options, "default");
int defflag=0;
printl_count = 0;
for (p = images; p; p = p->next) {
if (p->obsolete)
continue;
label = cfg_get_strg_i (p->table, "label");
if (!label) {
label = cfg_get_strg_i (p->table, "image");
alias = strrchr (label, '/');
if (alias)
label = alias + 1;
}
if(!strcmp(bootoncelabel,label))
defflag=2;
else if(!strcmp(ret,label))
defflag=1;
else
defflag=0;
alias = cfg_get_strg_i (p->table, "alias");
printlabel (label, defflag);
if (alias)
printlabel (alias, 0);
}
prom_printf("\n");
}
char *cfg_get_default (void)
{
char *label;
struct IMAGES *p;
char *ret = cfg_get_strg_i (cf_options, "default");
if (ret)
return ret;
if (!images)
return 0;
for (p = images; p && p->obsolete; p = p->next);
if (!p)
return 0;
ret = cfg_get_strg_i (p->table, "label");
if (!ret) {
ret = cfg_get_strg_i (p->table, "image");
label = strrchr (ret, '/');
if (label)
ret = label + 1;
}
return ret;
}
/*
* cfg_set_default_by_mac ()
* return 1 if the default cf_option was changed to label with the MAC addr
* return 0 if not changed
*/
int cfg_set_default_by_mac (char *mac_addr)
{
CONFIG *walk;
struct IMAGES *tmp;
char * label = NULL;
int haslabel = 0;
/* check if there is an image label equal to mac_addr */
for (tmp = images; tmp; tmp = tmp->next) {
label = cfg_get_strg_i (tmp->table, "label");
if (!strcmp(label,mac_addr)){
haslabel = 1;
}
}
if (!haslabel)
return 0;
else {
/*
* if there is an image label equal to mac_addr, change the default
* cf_options to this image label
*/
for (walk = cf_options; walk->type != cft_end; walk++) {
if (!strcasecmp(walk->name,"default")) {
walk->data = mac_addr;
return 1;
}
}
return 0;
}
}
/*
* Local variables:
* c-file-style: "k&r"
* c-basic-offset: 5
* End:
*/