From 9733fcdb434080d4d3357d784d6b3cdeac9f5980 Mon Sep 17 00:00:00 2001 From: David van Moolenbroek Date: Thu, 11 Oct 2012 00:09:19 +0200 Subject: [PATCH] bootloader: usability improvements - add "edit" menu option, to edit menu commands before executing them; - add "menu" boot command, to return to the menu from the prompt; - provide more line editing features when getting input; - fix a few potential buffer overflows as a side effect. --- etc/boot.cfg.default | 1 + man/man5/boot.cfg.5 | 3 + releasetools/release.functions | 3 + sys/arch/i386/stand/lib/bootmenu.c | 55 ++++++++++--- sys/arch/i386/stand/lib/conio.S | 27 ++++++- sys/arch/i386/stand/lib/libi386.h | 4 + sys/arch/i386/stand/lib/menuutils.c | 119 +++++++++++++++++++++++++++- sys/arch/i386/stand/lib/pcio.c | 12 ++- 8 files changed, 202 insertions(+), 22 deletions(-) diff --git a/etc/boot.cfg.default b/etc/boot.cfg.default index 25113d6ca..dfc971704 100644 --- a/etc/boot.cfg.default +++ b/etc/boot.cfg.default @@ -4,4 +4,5 @@ default=2 menu=Start MINIX 3:load_mods /boot/minix_default/mod*;multiboot /boot/minix_default/kernel rootdevname=$rootdevname menu=Start latest MINIX 3:load_mods /boot/minix_latest/mod*;multiboot /boot/minix_latest/kernel rootdevname=$rootdevname menu=Start latest MINIX 3 in single user mode:load_mods /boot/minix_latest/mod*;multiboot /boot/minix_latest/kernel rootdevname=$rootdevname bootopts=-s +menu=Edit menu option:edit menu=Drop to boot prompt:prompt diff --git a/man/man5/boot.cfg.5 b/man/man5/boot.cfg.5 index 119ce33a9..63a235579 100644 --- a/man/man5/boot.cfg.5 +++ b/man/man5/boot.cfg.5 @@ -130,6 +130,9 @@ Each command is executed just as though the user had typed it in and so can be any valid command that would be accepted at the normal boot prompt. In addition, +.Dq Ic edit +can be used to put the menu in editing mode, allowing the user to modify the +next selected option before executing it, and .Dq Ic prompt can be used to drop to the normal boot prompt. .It Sy timeout diff --git a/releasetools/release.functions b/releasetools/release.functions index 2719f0af3..0ef40c18d 100644 --- a/releasetools/release.functions +++ b/releasetools/release.functions @@ -61,6 +61,7 @@ banner=Welcome to the MINIX 3 installation CD banner================================================================================ banner= menu=Regular MINIX 3:multiboot /kernel bootcd=1 cdproberoot=1 rootdevname=ram disable=inet +menu=Edit menu option:edit menu=Drop to boot prompt:prompt clear=1 timeout=10 @@ -106,6 +107,7 @@ hdemu_root_changes() cat >$RELEASEMNTDIR/boot.cfg <$RELEASEMNTDIR/boot.cfg < "); + editline(input, sizeof(input), ic); + ic = input; + } + if (!strcmp(ic, "edit") && ((boot_params.bp_flags & X86_BP_FLAGS_PASSWORD) == 0 || check_password((char *)boot_params.bp_password))) { - printf("type \"?\" or \"help\" for help.\n"); - bootmenu(); /* does not return */ + editing = 1; + bootconf.timeout = -1; + } else if (!strcmp(ic, "prompt") && + ((boot_params.bp_flags & X86_BP_FLAGS_PASSWORD) == 0 || + check_password((char *)boot_params.bp_password))) { + printf("type \"?\" or \"help\" for help, " + "or \"menu\" to return to the menu.\n"); + prompt(1); + showmenu(); + editing = 0; + bootconf.timeout = -1; } else { - ic = bootconf.command[choice]; /* Split command string at ; into separate commands */ do { + /* + * This must support inline editing, since ic + * may also point to input. + */ oc = input; /* Look for ; separator */ for (; *ic && *ic != COMMAND_SEPARATOR; ic++) *oc++ = *ic; - if (*input == '\0') + if (*ic == COMMAND_SEPARATOR) + ic++; + if (oc == input) continue; /* Strip out any trailing spaces */ oc--; for (; *oc == ' ' && oc > input; oc--); *++oc = '\0'; - if (*ic == COMMAND_SEPARATOR) - ic++; /* Stop silly command strings like ;;; */ if (*input != '\0') docommand(input); diff --git a/sys/arch/i386/stand/lib/conio.S b/sys/arch/i386/stand/lib/conio.S index dd04a790f..3f62ec15f 100644 --- a/sys/arch/i386/stand/lib/conio.S +++ b/sys/arch/i386/stand/lib/conio.S @@ -45,9 +45,30 @@ ENTRY(conputc) call _C_LABEL(prot_to_real) # enter real mode .code16 + cmp $0x08, %al # backspace? + jne print_char + + # Multiline backspace support. + push %ax + movb $0x3, %ah # get cursor position + xorw %bx, %bx + int $0x10 + pop %ax + testb %dl, %dl # cursor on first column? + jnz print_char + testb %dh, %dh # cursor not on first row? + jz print_char + decb %dh # move up one row + movb $0x4f, %dl # move to last column + xorw %bx, %bx + movb $0x02, %ah # set cursor position + jmp do_int + +print_char: movw $1,%bx - movb $0x0e,%ah + movb $0x0e,%ah # print character movb %al, %cl +do_int: int $0x10 calll _C_LABEL(real_to_prot) # back to protected mode @@ -68,12 +89,12 @@ ENTRY(congetc) movb $0x0,%ah int $0x16 - movb %al,%bl + movw %ax,%bx calll _C_LABEL(real_to_prot) # back to protected mode .code32 - movb %bl, 28(%esp) + movw %bx, 28(%esp) popa ret diff --git a/sys/arch/i386/stand/lib/libi386.h b/sys/arch/i386/stand/lib/libi386.h index 2fb0ec5cb..6f28e6274 100644 --- a/sys/arch/i386/stand/lib/libi386.h +++ b/sys/arch/i386/stand/lib/libi386.h @@ -91,7 +91,9 @@ struct bootblk_command { void (*c_fn)(char *); }; void bootmenu(void); +void prompt(int); void docommand(char *); +void editline(char *, size_t, char *); /* in "user code": */ void command_help(char *); @@ -114,6 +116,8 @@ int coniskey(void); __compactcall void conputc(int); void conclr(void); +int getchar_ex(void); + int getextmem2(int *); __compactcall int getextmemps2(void *); int getmementry(int *, int *); diff --git a/sys/arch/i386/stand/lib/menuutils.c b/sys/arch/i386/stand/lib/menuutils.c index 92904ac70..f613e3054 100644 --- a/sys/arch/i386/stand/lib/menuutils.c +++ b/sys/arch/i386/stand/lib/menuutils.c @@ -62,23 +62,136 @@ docommand(char *arg) } void -bootmenu(void) +prompt(int allowreturn) { char input[80]; for (;;) { char *c = input; - input[0] = '\0'; printf("> "); - gets(input); + editline(input, sizeof(input), NULL); /* * Skip leading whitespace. */ while (*c == ' ') c++; + if (allowreturn && !strcmp(c, "menu")) + break; if (*c) docommand(c); } } + +void +bootmenu(void) +{ + prompt(0); +} + +/* Derived from libsa gets(). */ +void +editline(char *buf, size_t size, char *input) +{ + int c, i, pos, len = 0; + + /* If an initial input has been given, copy and print this first. */ + if (input != NULL) { + while (*input && len < size - 1) + putchar(buf[len++] = *input++); + } + pos = len; + + for (;;) { + c = getchar_ex(); + switch (c & 0177) { + case '\0': + switch (c) { + case 0x4b00: /* Left arrow: move cursor to left. */ + if (pos > 0) { + putchar('\b'); + pos--; + } + break; + case 0x4d00: /* Right arrow: move cursor to right. */ + if (pos < len) putchar(buf[pos++]); + break; + } + break; + case 'b' & 037: /* Ctrl+B: move cursor to left. */ + if (pos > 0) { + putchar('\b'); + pos--; + } + break; + case 'f' & 037: /* Ctrl+F: move cursor to right. */ + if (pos < len) putchar(buf[pos++]); + break; + case 'a' & 037: /* Ctrl+A: move cursor to start of line. */ + for ( ; pos > 0; pos--) putchar('\b'); + break; + case 'e' & 037: /* Ctrl+E: move cursor to end of line. */ + for ( ; pos < len; pos++) putchar(buf[pos]); + break; + case '\n': /* Enter: return line. */ + case '\r': + for ( ; pos < len; pos++) putchar(buf[pos]); + buf[len] = '\0'; + putchar('\n'); + return; +#if HASH_ERASE + case '#': +#endif + case '\b': /* Backspace: erase character before cursor. */ + case '\177': + if (pos > 0) { + pos--; + len--; + putchar('\b'); + for (i = pos; i < len; i++) + putchar(buf[i] = buf[i + 1]); + putchar(' '); + for (i = pos; i < len; i++) putchar('\b'); + putchar('\b'); + } + break; + case 'r' & 037: /* Ctrl+R: reprint line. */ + putchar('\n'); + for (i = 0; i < len; i++) putchar(buf[i]); + for (i = len; i > pos; i--) putchar('\b'); + break; +#if AT_ERASE + case '@': +#endif + case 'u' & 037: /* Ctrl+U: clear entire line. */ + case 'w' & 037: + for ( ; pos > 0; pos--) putchar('\b'); + for ( ; pos < len; pos++) putchar(' '); + for ( ; pos > 0; pos--) putchar('\b'); + len = 0; + break; + case '\a': /* Ctrl+G: sound bell but do not store character. */ + putchar(c); + break; + case '\t': /* Tab: convert to single space. */ + c = ' '; + /*FALLTHROUGH*/ + default: /* Insert character at cursor position. */ + if (len < size - 1) { + for (i = len; i > pos; i--) + buf[i] = buf[i - 1]; + buf[pos] = c; + pos++; + len++; + putchar(c); + for (i = pos; i < len; i++) putchar(buf[i]); + for (i = pos; i < len; i++) putchar('\b'); + } else { + putchar('\a'); + } + break; + } + } + /*NOTREACHED*/ +} diff --git a/sys/arch/i386/stand/lib/pcio.c b/sys/arch/i386/stand/lib/pcio.c index d03e4466b..f45724451 100644 --- a/sys/arch/i386/stand/lib/pcio.c +++ b/sys/arch/i386/stand/lib/pcio.c @@ -259,7 +259,7 @@ putchar(int c) } int -getchar(void) +getchar_ex(void) { int c; #ifdef SUPPORT_SERIAL @@ -272,9 +272,9 @@ getchar(void) c = congetc(); #ifdef CONSOLE_KEYMAP { - char *cp = strchr(CONSOLE_KEYMAP, c); + char *cp = strchr(CONSOLE_KEYMAP, c & 0xff); if (cp != 0 && cp[1] != 0) - c = cp[1]; + c = cp[1] | (c & 0xff00); } #endif return c; @@ -302,6 +302,12 @@ getchar(void) #endif /* SUPPORT_SERIAL */ } +int +getchar(void) +{ + return getchar_ex() & 0xff; +} + int iskey(int intr) {