| /* ----------------------------------------------------------------------- * |
| * |
| * 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 (c <= ' ' && c == '\x7f') { |
| 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 = atoi(p); |
| |
| 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 = atoi(p); |
| 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); |
| } |
| } |