/* ----------------------------------------------------------------------- *
 *
 *   Copyright 2004-2009 H. Peter Anvin - All Rights Reserved
 *   Copyright 2009-2013 Intel Corporation; author: H. Peter Anvin
 *
 *   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, Inc., 51 Franklin St, Fifth Floor,
 *   Boston MA 02110-1301, USA; either version 2 of the License, or
 *   (at your option) any later version; incorporated herein by reference.
 *
 * ----------------------------------------------------------------------- */

#include <sys/io.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <minmax.h>
#include <alloca.h>
#include <inttypes.h>
#include <colortbl.h>
#include <com32.h>
#include <syslinux/adv.h>
#include <syslinux/config.h>
#include <dprintf.h>
#include <ctype.h>
#include <bios.h>
#include <core.h>
#include <fs.h>
#include <syslinux/pxe_api.h>

#include "menu.h"
#include "config.h"
#include "getkey.h"
#include "core.h"
#include "fs.h"

const struct menu_parameter mparm[NPARAMS] = {
    [P_WIDTH] = {"width", 0},
    [P_MARGIN] = {"margin", 10},
    [P_PASSWD_MARGIN] = {"passwordmargin", 3},
    [P_MENU_ROWS] = {"rows", 12},
    [P_TABMSG_ROW] = {"tabmsgrow", 18},
    [P_CMDLINE_ROW] = {"cmdlinerow", 18},
    [P_END_ROW] = {"endrow", -1},
    [P_PASSWD_ROW] = {"passwordrow", 11},
    [P_TIMEOUT_ROW] = {"timeoutrow", 20},
    [P_HELPMSG_ROW] = {"helpmsgrow", 22},
    [P_HELPMSGEND_ROW] = {"helpmsgendrow", -1},
    [P_HSHIFT] = {"hshift", 0},
    [P_VSHIFT] = {"vshift", 0},
    [P_HIDDEN_ROW] = {"hiddenrow", -2},
};

/* Must match enum kernel_type */
static const char *const kernel_types[] = {
    "none",
    "localboot",
    "kernel",
    "linux",
    "boot",
    "bss",
    "pxe",
    "fdimage",
    "comboot",
    "com32",
    "config",
    NULL
};

short uappendlen = 0;		//bytes in append= command
short ontimeoutlen = 0;		//bytes in ontimeout command
short onerrorlen = 0;		//bytes in onerror command
short forceprompt = 0;		//force prompt
short noescape = 0;		//no escape
short nocomplete = 0;		//no label completion on TAB key
short allowimplicit = 1;	//allow implicit kernels
short allowoptions = 1;		//user-specified options allowed
short includelevel = 1;		//nesting level
short defaultlevel = 0;		//the current level of default
short vkernel = 0;		//have we seen any "label" statements?
extern short NoHalt;		//idle.c

const char *onerror = NULL;	//"onerror" command line
const char *ontimeout = NULL;	//"ontimeout" command line

__export const char *default_cmd = NULL;	//"default" command line

/* Empty refstring */
const char *empty_string;

/* Root menu, starting menu, hidden menu, and list of all menus */
struct menu *root_menu, *start_menu, *hide_menu, *menu_list, *default_menu;

/* These are global parameters regardless of which menu we're displaying */
int shiftkey = 0;		/* Only display menu if shift key pressed */
int hiddenmenu = 0;
long long totaltimeout = 0;
unsigned int kbdtimeout = 0;

/* Keep track of global default */
static int has_ui = 0;		/* DEFAULT only counts if UI is found */
extern const char *globaldefault;
static bool menusave = false;	/* True if there is any "menu save" */

/* Linked list of all entires, hidden or not; used by unlabel() */
static struct menu_entry *all_entries;
static struct menu_entry **all_entries_end = &all_entries;

static const struct messages messages[MSG_COUNT] = {
    [MSG_AUTOBOOT] = {"autoboot", "Automatic boot in # second{,s}..."},
    [MSG_TAB] = {"tabmsg", "Press [Tab] to edit options"},
    [MSG_NOTAB] = {"notabmsg", ""},
    [MSG_PASSPROMPT] = {"passprompt", "Password required"},
};

#define astrdup(x) ({ char *__x = (x); \
                      size_t __n = strlen(__x) + 1; \
                      char *__p = alloca(__n); \
                      if ( __p ) memcpy(__p, __x, __n); \
                      __p; })

/*
 * Search the list of all menus for a specific label
 */
static struct menu *find_menu(const char *label)
{
    struct menu *m;

    for (m = menu_list; m; m = m->next) {
	if (!strcmp(label, m->label))
	    return m;
    }

    return NULL;
}

#define MAX_LINE 4096

/* Strip ^ from a string, returning a new reference to the same refstring
   if none present */
static const char *strip_caret(const char *str)
{
    const char *p, *r;
    char *q;
    int carets = 0;

    p = str;
    for (;;) {
	p = strchr(p, '^');
	if (!p)
	    break;
	carets++;
	p++;
    }

    if (!carets)
	return refstr_get(str);

    r = q = refstr_alloc(strlen(str) - carets);
    for (p = str; *p; p++)
	if (*p != '^')
	    *q++ = *p;

    *q = '\0';			/* refstr_alloc() already did this... */

    return r;
}

/* Check to see if we are at a certain keyword (case insensitive) */
/* Returns a pointer to the first character past the keyword */
static char *looking_at(char *line, const char *kwd)
{
    char *p = line;
    const char *q = kwd;

    while (*p && *q && ((*p ^ *q) & ~0x20) == 0) {
	p++;
	q++;
    }

    if (*q)
	return NULL;		/* Didn't see the keyword */

    return my_isspace(*p) ? p : NULL;	/* Must be EOL or whitespace */
}

static struct menu *new_menu(struct menu *parent,
			     struct menu_entry *parent_entry, const char *label)
{
    struct menu *m = calloc(1, sizeof(struct menu));
    int i;
	
	//dprintf("enter: menu_label = %s", label);

    m->label = label;
    m->title = refstr_get(empty_string);

    if (parent) {
	/* Submenu */
	m->parent = parent;
	m->parent_entry = parent_entry;
	parent_entry->action = MA_SUBMENU;
	parent_entry->submenu = m;

	for (i = 0; i < MSG_COUNT; i++)
	    m->messages[i] = refstr_get(parent->messages[i]);

	memcpy(m->mparm, parent->mparm, sizeof m->mparm);

	m->allowedit = parent->allowedit;
	m->timeout = parent->timeout;
	m->save = parent->save;

	m->ontimeout = refstr_get(parent->ontimeout);
	m->onerror = refstr_get(parent->onerror);
	m->menu_master_passwd = refstr_get(parent->menu_master_passwd);
	m->menu_background = refstr_get(parent->menu_background);

	m->color_table = copy_color_table(parent->color_table);

	for (i = 0; i < 12; i++) {
	    m->fkeyhelp[i].textname = refstr_get(parent->fkeyhelp[i].textname);
	    m->fkeyhelp[i].background =
		refstr_get(parent->fkeyhelp[i].background);
	}
    } else {
	/* Root menu */
	for (i = 0; i < MSG_COUNT; i++)
	    m->messages[i] = refstrdup(messages[i].defmsg);
	for (i = 0; i < NPARAMS; i++)
	    m->mparm[i] = mparm[i].value;

	m->allowedit = true;	/* Allow edits of the command line */
	m->color_table = default_color_table();
    }

    m->next = menu_list;
    menu_list = m;

    return m;
}

struct labeldata {
    const char *label;
    const char *kernel;
    enum kernel_type type;
    const char *append;
    const char *initrd;
    const char *menulabel;
    const char *passwd;
    char *helptext;
    unsigned int ipappend;
    unsigned int menuhide;
    unsigned int menudefault;
    unsigned int menuseparator;
    unsigned int menudisabled;
    unsigned int menuindent;
    enum menu_action action;
    int save;
    struct menu *submenu;
};

/* Menu currently being parsed */
static struct menu *current_menu;

static void clear_label_data(struct labeldata *ld)
{
    refstr_put(ld->label);
    refstr_put(ld->kernel);
    refstr_put(ld->append);
    refstr_put(ld->initrd);
    refstr_put(ld->menulabel);
    refstr_put(ld->passwd);

    memset(ld, 0, sizeof *ld);
}

static struct menu_entry *new_entry(struct menu *m)
{
    struct menu_entry *me;

    //dprintf("enter, call from menu %s", m->label);

    if (m->nentries >= m->nentries_space) {
	if (!m->nentries_space)
	    m->nentries_space = 1;
	else
	    m->nentries_space <<= 1;

	m->menu_entries = realloc(m->menu_entries, m->nentries_space *
				  sizeof(struct menu_entry *));
    }

    me = calloc(1, sizeof(struct menu_entry));
    me->menu = m;
    me->entry = m->nentries;
    m->menu_entries[m->nentries++] = me;
    *all_entries_end = me;
    all_entries_end = &me->next;

    return me;
}

static void consider_for_hotkey(struct menu *m, struct menu_entry *me)
{
    const char *p = strchr(me->displayname, '^');

    if (me->action != MA_DISABLED) {
	if (p && p[1]) {
	    unsigned char hotkey = p[1] & ~0x20;
	    if (!m->menu_hotkeys[hotkey]) {
		me->hotkey = hotkey;
		m->menu_hotkeys[hotkey] = me;
	    }
	}
    }
}

/*
 * Copy a string, converting whitespace characters to underscores
 * and compacting them.  Return a pointer to the final null.
 */
static char *copy_sysappend_string(char *dst, const char *src)
{
    bool was_space = true;	/* Kill leading whitespace */
    char *end = dst;
    char c;

    while ((c = *src++)) {
	if (my_isspace(c)) {
	    if (!was_space)
		*dst++ = '_';
	    was_space = true;
	} else {
	    *dst++ = c;
	    end = dst;
	    was_space = false;
	}
    }
    *end = '\0';
    return end;
}

static void record(struct menu *m, struct labeldata *ld, const char *append)
{
	int i;
	struct menu_entry *me;
	const struct syslinux_ipappend_strings *ipappend;

	if (!ld->label)
		return;			/* Nothing defined */

	/* Hidden entries are recorded on a special "hidden menu" */
	if (ld->menuhide)
		m = hide_menu;

	char ipoptions[4096], *ipp;
	const char *a;
	char *s;

	me = new_entry(m);

	me->displayname = ld->menulabel
	    ? refstr_get(ld->menulabel) : refstr_get(ld->label);
	me->label = refstr_get(ld->label);
	me->passwd = refstr_get(ld->passwd);
	me->helptext = ld->helptext;
	me->hotkey = 0;
	me->action = ld->action ? ld->action : MA_CMD;
	me->save = ld->save ? (ld->save > 0) : m->save;

	if (ld->menuindent) {
	    const char *dn;

	    rsprintf(&dn, "%*s%s", ld->menuindent, "", me->displayname);
	    refstr_put(me->displayname);
	    me->displayname = dn;
	}

	if (ld->menuseparator) {
	    refstr_put(me->displayname);
	    me->displayname = refstr_get(empty_string);
	}

	if (ld->menuseparator || ld->menudisabled) {
	    me->action = MA_DISABLED;
	    refstr_put(me->label);
	    me->label = NULL;
	    refstr_put(me->passwd);
	    me->passwd = NULL;
	}

	if (ld->menulabel)
	    consider_for_hotkey(m, me);

	switch (me->action) {
	case MA_CMD:
	    ipp = ipoptions;
	    *ipp = '\0';

	    if (ld->initrd)
		ipp += sprintf(ipp, " initrd=%s", ld->initrd);

	    if (ld->ipappend) {
		ipappend = syslinux_ipappend_strings();
		for (i = 0; i < ipappend->count; i++) {
		    if ((ld->ipappend & (1U << i)) &&
			ipappend->ptr[i] && ipappend->ptr[i][0]) {
			*ipp++ = ' ';
			ipp = copy_sysappend_string(ipp, ipappend->ptr[i]);
		    }
		}
	    }

	    a = ld->append;
	    if (!a)
		a = append;
	    if (!a || (a[0] == '-' && !a[1]))
		a = "";
	    s = a[0] ? " " : "";

	    if (ld->type == KT_KERNEL)
		rsprintf(&me->cmdline, "%s%s%s%s", ld->kernel, s, a, ipoptions);
	    else
		rsprintf(&me->cmdline, ".%s %s%s%s%s",
			 kernel_types[ld->type], ld->kernel, s, a, ipoptions);
		dprintf("type = %s, cmd = %s", kernel_types[ld->type], me->cmdline);
	    break;

	case MA_GOTO_UNRES:
	case MA_EXIT_UNRES:
	    me->cmdline = refstr_get(ld->kernel);
	    break;

	case MA_GOTO:
	case MA_EXIT:
	    me->submenu = ld->submenu;
	    break;

	default:
	    break;
	}

	if (ld->menudefault && me->action == MA_CMD)
	    m->defentry = m->nentries - 1;

    clear_label_data(ld);
}

static struct menu *begin_submenu(const char *tag)
{
    struct menu_entry *me;

    if (!tag[0])
	tag = NULL;

    me = new_entry(current_menu);
    me->displayname = refstrdup(tag);
    return new_menu(current_menu, me, refstr_get(me->displayname));
}

static struct menu *end_submenu(void)
{
    return current_menu->parent ? current_menu->parent : current_menu;
}

void print_labels(const char *prefix, size_t len)
{
    struct menu_entry *me;

    printf("\n");
    for (me = all_entries; me; me = me->next ) {
	if (!me->label)
	    continue;

	if (!strncmp(prefix, me->label, len))
	    printf(" %s", me->label);
    }
    printf("\n");
}

struct menu_entry *find_label(const char *str)
{
    const char *p;
    struct menu_entry *me;
    int pos;

    p = str;
    while (*p && !my_isspace(*p))
	p++;

    /* p now points to the first byte beyond the kernel name */
    pos = p - str;

    for (me = all_entries; me; me = me->next) {
	if (!strncmp(str, me->label, pos) && !me->label[pos])
	    return me;
    }

    return NULL;
}

static const char *unlabel(const char *str)
{
    /* Convert a CLI-style command line to an executable command line */
    const char *p;
    const char *q;
    struct menu_entry *me;
    int pos;

    p = str;
    while (*p && !my_isspace(*p))
	p++;

    /* p now points to the first byte beyond the kernel name */
    pos = p - str;

    for (me = all_entries; me; me = me->next) {
	if (!strncmp(str, me->label, pos) && !me->label[pos]) {
	    /* Found matching label */
	    rsprintf(&q, "%s%s", me->cmdline, p);
	    refstr_put(str);
	    return q;
	}
    }

    return str;
}

static const char *__refdup_word(char *p, char **ref)
{
    char *sp = p;
    char *ep = sp;

    while (*ep && !my_isspace(*ep))
	ep++;

    if (ref)
	*ref = ep;
    return refstrndup(sp, ep - sp);
}

static const char *refdup_word(char **p)
{
    return __refdup_word(*p, p);
}

int my_isxdigit(char c)
{
    unsigned int uc = c;

    return (uc - '0') < 10 || ((uc | 0x20) - 'a') < 6;
}

unsigned int hexval(char c)
{
    unsigned char uc = c | 0x20;
    unsigned int v;

    v = uc - '0';
    if (v < 10)
	return v;

    return uc - 'a' + 10;
}

unsigned int hexval2(const char *p)
{
    return (hexval(p[0]) << 4) + hexval(p[1]);
}

uint32_t parse_argb(char **p)
{
    char *sp = *p;
    char *ep;
    uint32_t argb;
    size_t len, dl;

    if (*sp == '#')
	sp++;

    ep = sp;

    while (my_isxdigit(*ep))
	ep++;

    *p = ep;
    len = ep - sp;

    switch (len) {
    case 3:			/* #rgb */
	argb =
	    0xff000000 +
	    (hexval(sp[0]) * 0x11 << 16) +
	    (hexval(sp[1]) * 0x11 << 8) + (hexval(sp[2]) * 0x11);
	break;
    case 4:			/* #argb */
	argb =
	    (hexval(sp[0]) * 0x11 << 24) +
	    (hexval(sp[1]) * 0x11 << 16) +
	    (hexval(sp[2]) * 0x11 << 8) + (hexval(sp[3]) * 0x11);
	break;
    case 6:			/* #rrggbb */
    case 9:			/* #rrrgggbbb */
    case 12:			/* #rrrrggggbbbb */
	dl = len / 3;
	argb =
	    0xff000000 +
	    (hexval2(sp + 0) << 16) +
	    (hexval2(sp + dl) << 8) + hexval2(sp + dl * 2);
	break;
    case 8:			/* #aarrggbb */
	/* #aaarrrgggbbb is indistinguishable from #rrrrggggbbbb,
	   assume the latter is a more common format */
    case 16:			/* #aaaarrrrggggbbbb */
	dl = len / 4;
	argb =
	    (hexval2(sp + 0) << 24) +
	    (hexval2(sp + dl) << 16) +
	    (hexval2(sp + dl * 2) << 8) + hexval2(sp + dl * 3);
	break;
    default:
	argb = 0xffff0000;	/* Bright red (error indication) */
	break;
    }

    return argb;
}

/*
 * Parser state.  This is global so that including multiple
 * files work as expected, which is that everything works the
 * same way as if the files had been concatenated together.
 */
//static const char *append = NULL;
extern const char *append;
extern uint16_t PXERetry;
static struct labeldata ld;

static int parse_main_config(const char *filename);

static char *is_kernel_type(char *cmdstr, enum kernel_type *type)
{
    const char *const *p;
    char *q;
    enum kernel_type t = KT_NONE;

    for (p = kernel_types; *p; p++, t++) {
	if ((q = looking_at(cmdstr, *p))) {
	    *type = t;
	    return q;
	}
    }

    return NULL;
}

static char *is_message_name(char *cmdstr, enum message_number *msgnr)
{
    char *q;
    enum message_number i;

    for (i = 0; i < MSG_COUNT; i++) {
	if ((q = looking_at(cmdstr, messages[i].name))) {
	    *msgnr = i;
	    return q;
	}
    }

    return NULL;
}

extern void get_msg_file(char *);

void cat_help_file(int key)
{
	struct menu *cm = current_menu;
	int fkey;

	switch (key) {
	case KEY_F1:
		fkey = 0;
		break;
	case KEY_F2:
		fkey = 1;
		break;
	case KEY_F3:
		fkey = 2;
		break;
	case KEY_F4:
		fkey = 3;
		break;
	case KEY_F5:
		fkey = 4;
		break;
	case KEY_F6:
		fkey = 5;
		break;
	case KEY_F7:
		fkey = 6;
		break;
	case KEY_F8:
		fkey = 7;
		break;
	case KEY_F9:
		fkey = 8;
		break;
	case KEY_F10:
		fkey = 9;
		break;
	case KEY_F11:
		fkey = 10;
		break;
	case KEY_F12:
		fkey = 11;
		break;
	default:
		fkey = -1;
		break;
	}

	if (fkey == -1)
		return;

	if (cm->fkeyhelp[fkey].textname) {
		printf("\n");
		get_msg_file((char *)cm->fkeyhelp[fkey].textname);
	}
}

static char *is_fkey(char *cmdstr, int *fkeyno)
{
    char *q;
    int no;

    if ((cmdstr[0] | 0x20) != 'f')
	return NULL;

    no = strtoul(cmdstr + 1, &q, 10);
    if (!my_isspace(*q))
	return NULL;

    if (no < 0 || no > 12)
	return NULL;

    *fkeyno = (no == 0) ? 10 : no - 1;
    return q;
}

extern uint8_t FlowIgnore;
extern uint8_t FlowInput;
extern uint8_t FlowOutput;
extern uint16_t SerialPort;
extern uint16_t BaudDivisor;
static uint8_t SerialNotice = 1;

#define DEFAULT_BAUD	9600
#define BAUD_DIVISOR	115200

extern void sirq_cleanup_nowipe(void);
extern void sirq_install(void);
extern void write_serial_str(char *);

extern void loadfont(const char *);
extern void loadkeys(const char *);

extern char syslinux_banner[];
extern char copyright_str[];

/*
 * PATH-based lookup
 *
 * Each entry in the PATH directive is separated by a colon, e.g.
 *
 *     PATH /bar:/bin/foo:/baz/bar/bin
 */
static int parse_path(char *p)
{
    struct path_entry *entry;
    const char *str;

    while (*p) {
	char *c = p;

	/* Find the next directory */
	while (*c && *c != ':')
	    c++;

	str = refstrndup(p, c - p);
	if (!str)
	    goto bail;

	entry = path_add(str);
	refstr_put(str);

	if (!entry)
	    goto bail;

	if (!*c++)
	    break;
	p = c;
    }

    return 0;

bail:
    return -1;
}

static void parse_config_file(FILE * f);

static void do_include_menu(char *str, struct menu *m)
{
    const char *file;
    char *p;
    FILE *f;
    int fd;

    p = skipspace(str);
    file = refdup_word(&p);
    p = skipspace(p);

    fd = open(file, O_RDONLY);
    if (fd < 0)
	goto put;

    f = fdopen(fd, "r");
    if (!f)
	goto bail;

    if (*p) {
	record(m, &ld, append);
	m = current_menu = begin_submenu(p);
    }

    parse_config_file(f);

    if (*p) {
	record(m, &ld, append);
	m = current_menu = end_submenu();
    }

bail:
    close(fd);
put:
    refstr_put(file);

}

static void do_include(char *str)
{
    const char *file;
    char *p;
    FILE *f;
    int fd;

    p = skipspace(str);
    file = refdup_word(&p);

    fd = open(file, O_RDONLY);
    if (fd < 0)
	goto put;

    f = fdopen(fd, "r");
    if (f)
	parse_config_file(f);

    close(fd);
put:
    refstr_put(file);
}

static void parse_config_file(FILE * f)
{
    char line[MAX_LINE], *p, *ep, ch;
    enum kernel_type type;
    enum message_number msgnr;
    int fkeyno;
    struct menu *m = current_menu;

    while (fgets(line, sizeof line, f)) {
	p = strchr(line, '\r');
	if (p)
	    *p = '\0';
	p = strchr(line, '\n');
	if (p)
	    *p = '\0';

	p = skipspace(line);

	if (looking_at(p, "menu")) {

	    p = skipspace(p + 4);

	    if (looking_at(p, "label")) {
			if (ld.label) {
				refstr_put(ld.menulabel);
				ld.menulabel = refstrdup(skipspace(p + 5));
			} else if (m->parent_entry) {
				refstr_put(m->parent_entry->displayname);
				m->parent_entry->displayname = refstrdup(skipspace(p + 5));
				consider_for_hotkey(m->parent, m->parent_entry);
				if (!m->title[0]) {
				/* MENU LABEL -> MENU TITLE on submenu */
				refstr_put(m->title);
				m->title = strip_caret(m->parent_entry->displayname);
				}
			}
			} else if (looking_at(p, "title")) {
			refstr_put(m->title);
			m->title = refstrdup(skipspace(p + 5));
			if (m->parent_entry) {
				/* MENU TITLE -> MENU LABEL on submenu */
				if (m->parent_entry->displayname == m->label) {
				refstr_put(m->parent_entry->displayname);
				m->parent_entry->displayname = refstr_get(m->title);
				}
			}
	    } else if (looking_at(p, "default")) {
		if (ld.label) {
		    ld.menudefault = 1;
		} else if (m->parent_entry) {
		    m->parent->defentry = m->parent_entry->entry;
		}
	    } else if (looking_at(p, "hide")) {
		ld.menuhide = 1;
	    } else if (looking_at(p, "passwd")) {
		if (ld.label) {
		    refstr_put(ld.passwd);
		    ld.passwd = refstrdup(skipspace(p + 6));
		} else if (m->parent_entry) {
		    refstr_put(m->parent_entry->passwd);
		    m->parent_entry->passwd = refstrdup(skipspace(p + 6));
		}
	    } else if (looking_at(p, "shiftkey")) {
		shiftkey = 1;
	    } else if (looking_at(p, "save")) {
		menusave = true;
		if (ld.label)
		    ld.save = 1;
		else
		    m->save = true;
	    } else if (looking_at(p, "nosave")) {
		if (ld.label)
		    ld.save = -1;
		else
		    m->save = false;
	    } else if (looking_at(p, "onerror")) {
		refstr_put(m->onerror);
		m->onerror = refstrdup(skipspace(p + 7));
		onerrorlen = strlen(m->onerror);
		refstr_put(onerror);
		onerror = refstrdup(m->onerror);
	    } else if (looking_at(p, "master")) {
		p = skipspace(p + 6);
		if (looking_at(p, "passwd")) {
		    refstr_put(m->menu_master_passwd);
		    m->menu_master_passwd = refstrdup(skipspace(p + 6));
		}
	    } else if ((ep = looking_at(p, "include"))) {
		do_include_menu(ep, m);
	    } else if ((ep = looking_at(p, "background"))) {
		p = skipspace(ep);
		refstr_put(m->menu_background);
		m->menu_background = refdup_word(&p);
	    } else if ((ep = looking_at(p, "hidden"))) {
		hiddenmenu = 1;
	    } else if ((ep = is_message_name(p, &msgnr))) {
		refstr_put(m->messages[msgnr]);
		m->messages[msgnr] = refstrdup(skipspace(ep));
	    } else if ((ep = looking_at(p, "color")) ||
		       (ep = looking_at(p, "colour"))) {
		int i;
		struct color_table *cptr;
		p = skipspace(ep);
		cptr = m->color_table;
		for (i = 0; i < menu_color_table_size; i++) {
		    if ((ep = looking_at(p, cptr->name))) {
			p = skipspace(ep);
			if (*p) {
			    if (looking_at(p, "*")) {
				p++;
			    } else {
				refstr_put(cptr->ansi);
				cptr->ansi = refdup_word(&p);
			    }

			    p = skipspace(p);
			    if (*p) {
				if (looking_at(p, "*"))
				    p++;
				else
				    cptr->argb_fg = parse_argb(&p);

				p = skipspace(p);
				if (*p) {
				    if (looking_at(p, "*"))
					p++;
				    else
					cptr->argb_bg = parse_argb(&p);

				    /* Parse a shadow mode */
				    p = skipspace(p);
				    ch = *p | 0x20;
				    if (ch == 'n')	/* none */
					cptr->shadow = SHADOW_NONE;
				    else if (ch == 's')	/* std, standard */
					cptr->shadow = SHADOW_NORMAL;
				    else if (ch == 'a')	/* all */
					cptr->shadow = SHADOW_ALL;
				    else if (ch == 'r')	/* rev, reverse */
					cptr->shadow = SHADOW_REVERSE;
				}
			    }
			}
			break;
		    }
		    cptr++;
		}
	    } else if ((ep = looking_at(p, "msgcolor")) ||
		       (ep = looking_at(p, "msgcolour"))) {
		unsigned int fg_mask = MSG_COLORS_DEF_FG;
		unsigned int bg_mask = MSG_COLORS_DEF_BG;
		enum color_table_shadow shadow = MSG_COLORS_DEF_SHADOW;

		p = skipspace(ep);
		if (*p) {
		    if (!looking_at(p, "*"))
			fg_mask = parse_argb(&p);

		    p = skipspace(p);
		    if (*p) {
			if (!looking_at(p, "*"))
			    bg_mask = parse_argb(&p);

			p = skipspace(p);
			switch (*p | 0x20) {
			case 'n':
			    shadow = SHADOW_NONE;
			    break;
			case 's':
			    shadow = SHADOW_NORMAL;
			    break;
			case 'a':
			    shadow = SHADOW_ALL;
			    break;
			case 'r':
			    shadow = SHADOW_REVERSE;
			    break;
			default:
			    /* go with default */
			    break;
			}
		    }
		}
		set_msg_colors_global(m->color_table, fg_mask, bg_mask, shadow);
	    } else if (looking_at(p, "separator")) {
		record(m, &ld, append);
		ld.label = refstr_get(empty_string);
		ld.menuseparator = 1;
		record(m, &ld, append);
	    } else if (looking_at(p, "disable") || looking_at(p, "disabled")) {
		ld.menudisabled = 1;
	    } else if (looking_at(p, "indent")) {
		ld.menuindent = atoi(skipspace(p + 6));
	    } else if (looking_at(p, "begin")) {
		record(m, &ld, append);
		m = current_menu = begin_submenu(skipspace(p + 5));
	    } else if (looking_at(p, "end")) {
		record(m, &ld, append);
		m = current_menu = end_submenu();
	    } else if (looking_at(p, "quit")) {
		if (ld.label)
		    ld.action = MA_QUIT;
	    } else if (looking_at(p, "goto")) {
		if (ld.label) {
		    ld.action = MA_GOTO_UNRES;
		    refstr_put(ld.kernel);
		    ld.kernel = refstrdup(skipspace(p + 4));
		}
	    } else if (looking_at(p, "exit")) {
		p = skipspace(p + 4);
		if (ld.label && m->parent) {
		    if (*p) {
			/* This is really just a goto, except for the marker */
			ld.action = MA_EXIT_UNRES;
			refstr_put(ld.kernel);
			ld.kernel = refstrdup(p);
		    } else {
			ld.action = MA_EXIT;
			ld.submenu = m->parent;
		    }
		}
	    } else if (looking_at(p, "start")) {
		start_menu = m;
	    } else {
		/* Unknown, check for layout parameters */
		enum parameter_number mp;
		for (mp = 0; mp < NPARAMS; mp++) {
		    if ((ep = looking_at(p, mparm[mp].name))) {
			m->mparm[mp] = atoi(skipspace(ep));
			break;
		    }
		}
	    }
	}
	/* feng: menu handling end */	
	else if (looking_at(p, "text")) {

		/* loop till we fined the "endtext" */
	    enum text_cmd {
		TEXT_UNKNOWN,
		TEXT_HELP
	    } cmd = TEXT_UNKNOWN;
	    int len = ld.helptext ? strlen(ld.helptext) : 0;
	    int xlen;

	    p = skipspace(p + 4);

	    if (looking_at(p, "help"))
		cmd = TEXT_HELP;

	    while (fgets(line, sizeof line, f)) {
		p = skipspace(line);
		if (looking_at(p, "endtext"))
		    break;

		xlen = strlen(line);

		switch (cmd) {
		case TEXT_UNKNOWN:
		    break;
		case TEXT_HELP:
		    ld.helptext = realloc(ld.helptext, len + xlen + 1);
		    memcpy(ld.helptext + len, line, xlen + 1);
		    len += xlen;
		    break;
		}
	    }
	} else if ((ep = is_fkey(p, &fkeyno))) {
	    p = skipspace(ep);
	    if (m->fkeyhelp[fkeyno].textname) {
		refstr_put(m->fkeyhelp[fkeyno].textname);
		m->fkeyhelp[fkeyno].textname = NULL;
	    }
	    if (m->fkeyhelp[fkeyno].background) {
		refstr_put(m->fkeyhelp[fkeyno].background);
		m->fkeyhelp[fkeyno].background = NULL;
	    }

	    refstr_put(m->fkeyhelp[fkeyno].textname);
	    m->fkeyhelp[fkeyno].textname = refdup_word(&p);
	    if (*p) {
		p = skipspace(p);
		m->fkeyhelp[fkeyno].background = refdup_word(&p);
	    }
	} else if ((ep = looking_at(p, "include"))) {
	    do_include(ep);
	} else if (looking_at(p, "append")) {
	    const char *a = refstrdup(skipspace(p + 6));
	    if (ld.label) {
		refstr_put(ld.append);
		ld.append = a;
	    } else {
		refstr_put(append);
		append = a;
	    }
	    //dprintf("we got a append: %s", a);
	} else if (looking_at(p, "initrd")) {
	    const char *a = refstrdup(skipspace(p + 6));
	    if (ld.label) {
		refstr_put(ld.initrd);
		ld.initrd = a;
	    } else {
		/* Ignore */
	    }
	} else if (looking_at(p, "label")) {
	    p = skipspace(p + 5);
	    /* when first time see "label", it will not really record anything */
	    record(m, &ld, append);
	    ld.label = __refdup_word(p, NULL);
	    ld.kernel = __refdup_word(p, NULL);
	    /* feng: this is the default type for all */
	    ld.type = KT_KERNEL;
	    ld.passwd = NULL;
	    ld.append = NULL;
	    ld.initrd = NULL;
	    ld.menulabel = NULL;
	    ld.helptext = NULL;
	    ld.ipappend = SysAppends;
	    ld.menudefault = ld.menuhide = ld.menuseparator =
		ld.menudisabled = ld.menuindent = 0;
	} else if ((ep = is_kernel_type(p, &type))) {
	    if (ld.label) {
		refstr_put(ld.kernel);
		ld.kernel = refstrdup(skipspace(ep));
		ld.type = type;
		//dprintf("got a kernel: %s, type = %d", ld.kernel, ld.type);
	    }
	} else if (looking_at(p, "timeout")) {
	    kbdtimeout = (atoi(skipspace(p + 7)) * CLK_TCK + 9) / 10;
	} else if (looking_at(p, "totaltimeout")) {
	    totaltimeout = (atoll(skipspace(p + 13)) * CLK_TCK + 9) / 10;
	} else if (looking_at(p, "ontimeout")) {
	    ontimeout = refstrdup(skipspace(p + 9));
	    ontimeoutlen = strlen(ontimeout);
	} else if (looking_at(p, "allowoptions")) {
	    allowoptions = !!atoi(skipspace(p + 12));
	} else if ((ep = looking_at(p, "ipappend")) ||
		   (ep = looking_at(p, "sysappend"))) {
	    uint32_t s = strtoul(skipspace(ep), NULL, 0);
	    if (ld.label)
		ld.ipappend = s;
	    else
		SysAppends = s;
	} else if (looking_at(p, "default")) {
	    /* default could be a kernel image or another label */
	    refstr_put(globaldefault);
	    globaldefault = refstrdup(skipspace(p + 7));

	    /*
	     * On the chance that "default" is actually a kernel image
	     * and not a label, store a copy of it, but only if we
	     * haven't seen a "ui" command. "ui" commands take
	     * precendence over "default" commands.
	     */
	    if (defaultlevel < LEVEL_UI) {
		defaultlevel = LEVEL_DEFAULT;
		refstr_put(default_cmd);
		default_cmd = refstrdup(globaldefault);
	    }
	} else if (looking_at(p, "ui")) {
	    has_ui = 1;
	    defaultlevel = LEVEL_UI;
	    refstr_put(default_cmd);
	    default_cmd = refstrdup(skipspace(p + 2));
	}
	
	/*
	 * subset 1:  pc_opencmd 
	 * display/font/kbdmap are rather similar, open a file then do sth
	 */
	else if (looking_at(p, "display")) {
		const char *filename;
		char *dst = KernelName;
		size_t len = FILENAME_MAX - 1;

		filename = refstrdup(skipspace(p + 7));

		while (len-- && not_whitespace(*filename))
			*dst++ = *filename++;
		*dst = '\0';

		get_msg_file(KernelName);
		refstr_put(filename);
	} else if (looking_at(p, "font")) {
		const char *filename;
		char *dst = KernelName;
		size_t len = FILENAME_MAX - 1;

		filename = refstrdup(skipspace(p + 4));

		while (len-- && not_whitespace(*filename))
			*dst++ = *filename++;
		*dst = '\0';

		loadfont(KernelName);
		refstr_put(filename);
	} else if (looking_at(p, "kbdmap")) {
		const char *filename;

		filename = refstrdup(skipspace(p + 6));
		loadkeys(filename);
		refstr_put(filename);
	}
	/*
	 * subset 2:  pc_setint16
	 * set a global flag
	 */
	else if (looking_at(p, "implicit")) {
		allowimplicit = atoi(skipspace(p + 8));
	} else if (looking_at(p, "prompt")) {
		forceprompt = atoi(skipspace(p + 6));
	} else if (looking_at(p, "console")) {
		DisplayCon = atoi(skipspace(p + 7));
	} else if (looking_at(p, "allowoptions")) {
		allowoptions = atoi(skipspace(p + 12));
	} else if (looking_at(p, "noescape")) {
		noescape = atoi(skipspace(p + 8));
	} else if (looking_at(p, "nocomplete")) {
		nocomplete = atoi(skipspace(p + 10));
	} else if (looking_at(p, "nohalt")) {
		NoHalt = atoi(skipspace(p + 8));
	} else if (looking_at(p, "onerror")) {
		refstr_put(m->onerror);
		m->onerror = refstrdup(skipspace(p + 7));
		onerrorlen = strlen(m->onerror);
		refstr_put(onerror);
		onerror = refstrdup(m->onerror);
	}

	else if (looking_at(p, "pxeretry"))
		PXERetry = atoi(skipspace(p + 8));

	/* serial setting, bps, flow control */
	else if (looking_at(p, "serial")) {
		uint16_t port, flow;
		uint32_t baud;

		p = skipspace(p + 6);
		port = strtoul(p, &p, 0);

		while (isalnum(*p))
			p++;
		p = skipspace(p);

		/* Default to no flow control */
		FlowOutput = 0;
		FlowInput = 0;

		baud = DEFAULT_BAUD;
		if (isalnum(*p)) {
			uint8_t ignore;

			/* setup baud */
			baud = atoi(p);
			while (isalnum(*p))
				p++;
			p = skipspace(p);

			ignore = 0;
			flow = 0;
			if (isalnum(*p)) {
				/* flow control */
				flow = strtoul(p, NULL, 0);
				ignore = ((flow & 0x0F00) >> 4);
			}

			FlowIgnore = ignore;
			flow = ((flow & 0xff) << 8) | (flow & 0xff);
			flow &= 0xF00B;
			FlowOutput = (flow & 0xff);
			FlowInput = ((flow & 0xff00) >> 8);
		}

		/*
		 * Parse baud
		 */
		if (baud < 75) {
			/* < 75 baud == bogus */
			SerialPort = 0;
			continue;
		}

		baud = BAUD_DIVISOR / baud;
		baud &= 0xffff;
		BaudDivisor = baud;

		port = get_serial_port(port);
		SerialPort = port;

		/*
		 * Begin code to actually set up the serial port
		 */
		sirq_cleanup_nowipe();

		outb(0x83, port + 3); /* Enable DLAB */
		io_delay();

		outb((baud & 0xff), port); /* write divisor to LS */
		io_delay();

		outb(((baud & 0xff00) >> 8), port + 1); /* write to MS */
		io_delay();

		outb(0x03, port + 3); /* Disable DLAB */
		io_delay();

		/*
		 * Read back LCR (detect missing hw). If nothing here
		 * we'll read 00 or FF.
		 */
		if (inb(port + 3) != 0x03) {
			/* Assume serial port busted */
			SerialPort = 0;
			continue;
		}

		outb(0x01, port + 2); /* Enable FIFOs if present */
		io_delay();

		/* Disable FIFO if unusable */
		if (inb(port + 2) < 0x0C0) {
			outb(0, port + 2);
			io_delay();
		}

		/* Assert bits in MCR */
		outb(FlowOutput, port + 4);
		io_delay();

		/* Enable interrupts if requested */
		if (FlowOutput & 0x8)
			sirq_install();

		/* Show some life */
		if (SerialNotice != 0) {
			SerialNotice = 0;

			write_serial_str(syslinux_banner);
			write_serial_str(copyright_str);
		}

	} else if (looking_at(p, "say")) {
		printf("%s\n", p+4);
	} else if (looking_at(p, "path")) {
		if (parse_path(skipspace(p + 4)))
			printf("Failed to parse PATH\n");
	} else if (looking_at(p, "sendcookies")) {
		const union syslinux_derivative_info *sdi;

		p += strlen("sendcookies");
		sdi = syslinux_derivative_info();

		if (sdi->c.filesystem == SYSLINUX_FS_PXELINUX) {
			SendCookies = strtoul(skipspace(p), NULL, 10);
			http_bake_cookies();
		}
	}
    }
}

static int parse_main_config(const char *filename)
{
	const char *mode = "r";
	FILE *f;
	int fd;

	if (!filename)
		fd = open_config();
	else
		fd = open(filename, O_RDONLY);

	if (fd < 0)
		return fd;

	if (config_cwd[0]) {
		if (chdir(config_cwd) < 0)
			printf("Failed to chdir to %s\n", config_cwd);
		config_cwd[0] = '\0';
	}

	f = fdopen(fd, mode);
	parse_config_file(f);

	/*
	 * Update ConfigName so that syslinux_config_file() returns
	 * the filename we just opened. filesystem-specific
	 * open_config() implementations are expected to update
	 * ConfigName themselves.
	 */
	if (filename)
	    strcpy(ConfigName, filename);

	return 0;
}

static void resolve_gotos(void)
{
    struct menu_entry *me;
    struct menu *m;

    for (me = all_entries; me; me = me->next) {
	if (me->action == MA_GOTO_UNRES || me->action == MA_EXIT_UNRES) {
	    m = find_menu(me->cmdline);
	    refstr_put(me->cmdline);
	    me->cmdline = NULL;
	    if (m) {
		me->submenu = m;
		me->action--;	/* Drop the _UNRES */
	    } else {
		me->action = MA_DISABLED;
	    }
	}
    }
}

void parse_configs(char **argv)
{
    const char *filename;
    struct menu *m;
    struct menu_entry *me;
    dprintf("enter");

    empty_string = refstrdup("");

    /* feng: reset current menu_list and entry list */
    menu_list = NULL;
    all_entries = NULL;

    /* Initialize defaults for the root and hidden menus */
    hide_menu = new_menu(NULL, NULL, refstrdup(".hidden"));
    root_menu = new_menu(NULL, NULL, refstrdup(".top"));
    start_menu = root_menu;

    /* Other initialization */
    memset(&ld, 0, sizeof(struct labeldata));

    /* Actually process the files */
    current_menu = root_menu;

    if (!argv || !*argv) {
	if (parse_main_config(NULL) < 0) {
	    printf("WARNING: No configuration file found\n");
	    return;
	}
    } else {
	while ((filename = *argv++)) {
		dprintf("Parsing config: %s", filename);
	    parse_main_config(filename);
	}
    }

    /* On final EOF process the last label statement */
    record(current_menu, &ld, append);

    /* Common postprocessing */
    resolve_gotos();

    /* Handle global default */
    //if (has_ui && globaldefault) {
    if (globaldefault) {
	dprintf("gloabldefault = %s", globaldefault);
	me = find_label(globaldefault);
	if (me && me->menu != hide_menu) {
	    me->menu->defentry = me->entry;
	    start_menu = me->menu;
	    default_menu = me->menu;
	}
    }

    /* If "menu save" is active, let the ADV override the global default */
    if (menusave) {
	size_t len;
	const char *lbl = syslinux_getadv(ADV_MENUSAVE, &len);
	char *lstr;
	if (lbl && len) {
	    lstr = refstr_alloc(len);
	    memcpy(lstr, lbl, len);	/* refstr_alloc() adds the final null */
	    me = find_label(lstr);
	    if (me && me->menu != hide_menu) {
		me->menu->defentry = me->entry;
		start_menu = me->menu;
	    }
	    refstr_put(lstr);
	}
    }

    /* Final per-menu initialization, with all labels known */
    for (m = menu_list; m; m = m->next) {
	m->curentry = m->defentry;	/* All menus start at their defaults */

	if (m->ontimeout)
	    m->ontimeout = unlabel(m->ontimeout);
	if (m->onerror)
	    m->onerror = unlabel(m->onerror);
    }
}
