1 /* ----------------------------------------------------------------------- *
3 * Copyright 2004-2007 H. Peter Anvin - All Rights Reserved
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, Inc., 53 Temple Place Ste 330,
8 * Boston MA 02111-1307, USA; either version 2 of the License, or
9 * (at your option) any later version; incorporated herein by reference.
11 * ----------------------------------------------------------------------- */
16 * Simple menu system which displays a list and allows the user to select
17 * a command line and/or edit it.
20 #define _GNU_SOURCE /* Needed for asprintf() on Linux */
38 * The color/attribute indexes (\1#X, \2#XX, \3#XXX) are as follows
40 * 00 - screen Rest of the screen
41 * 01 - border Border area
42 * 02 - title Title bar
43 * 03 - unsel Unselected menu item
44 * 04 - hotkey Unselected hotkey
45 * 05 - sel Selection bar
46 * 06 - hotsel Selected hotkey
47 * 07 - scrollbar Scroll bar
48 * 08 - tabmsg Press [Tab] message
49 * 09 - cmdmark Command line marker
50 * 10 - cmdline Command line
51 * 11 - pwdborder Password box border
52 * 12 - pwdheader Password box header
53 * 13 - pwdentry Password box contents
54 * 14 - timeout_msg Timeout message
55 * 15 - timeout Timeout counter
56 * 16 - help Current entry help text
59 static const struct color_table default_color_table[] = {
60 { "screen", "37;40", 0x80ffffff, 0x00000000, SHADOW_NORMAL },
61 { "border", "30;44", 0x40000000, 0x00000000, SHADOW_NORMAL },
62 { "title", "1;36;44", 0xc00090f0, 0x00000000, SHADOW_NORMAL },
63 { "unsel", "37;44", 0x90ffffff, 0x00000000, SHADOW_NORMAL },
64 { "hotkey", "1;37;44", 0xffffffff, 0x00000000, SHADOW_NORMAL },
65 { "sel", "7;37;40", 0xe0000000, 0x20ff8000, SHADOW_ALL },
66 { "hotsel", "1;7;37;40", 0xe0400000, 0x20ff8000, SHADOW_ALL },
67 { "scrollbar", "30;44", 0x40000000, 0x00000000, SHADOW_NORMAL },
68 { "tabmsg", "31;40", 0x90ffff00, 0x00000000, SHADOW_NORMAL },
69 { "cmdmark", "1;36;40", 0xc000ffff, 0x00000000, SHADOW_NORMAL },
70 { "cmdline", "37;40", 0xc0ffffff, 0x00000000, SHADOW_NORMAL },
71 { "pwdborder", "30;47", 0x80ffffff, 0x20ffffff, SHADOW_NORMAL },
72 { "pwdheader", "31;47", 0x80ff8080, 0x20ffffff, SHADOW_NORMAL },
73 { "pwdentry", "30;47", 0x80ffffff, 0x20ffffff, SHADOW_NORMAL },
74 { "timeout_msg", "37;40", 0x80ffffff, 0x00000000, SHADOW_NORMAL },
75 { "timeout", "1;37;40", 0xc0ffffff, 0x00000000, SHADOW_NORMAL },
76 { "help", "37;40", 0xc0ffffff, 0x00000000, SHADOW_NORMAL },
79 #define NCOLORS (sizeof default_color_table/sizeof(struct color_table))
80 const int message_base_color = NCOLORS;
82 struct menu_parameter mparm[] = {
85 { "passwordmargin", 3 },
90 { "passwordrow", 11 },
93 { "helpmsgendrow", -1 },
100 #define WIDTH mparm[0].value
101 #define MARGIN mparm[1].value
102 #define PASSWD_MARGIN mparm[2].value
103 #define MENU_ROWS mparm[3].value
104 #define TABMSG_ROW (mparm[4].value+VSHIFT)
105 #define CMDLINE_ROW (mparm[5].value+VSHIFT)
106 #define END_ROW mparm[6].value
107 #define PASSWD_ROW (mparm[7].value+VSHIFT)
108 #define TIMEOUT_ROW (mparm[8].value+VSHIFT)
109 #define HELPMSG_ROW (mparm[9].value+VSHIFT)
110 #define HELPMSGEND_ROW mparm[10].value
111 #define HSHIFT mparm[11].value
112 #define VSHIFT mparm[12].value
113 #define HIDDEN_ROW mparm[13].value
115 void set_msg_colors_global(unsigned int fg, unsigned int bg,
116 enum color_table_shadow shadow)
118 struct color_table *cp = console_color_table+message_base_color;
120 unsigned int fga, bga;
121 unsigned int fgh, bgh;
122 unsigned int fg_idx, bg_idx;
123 unsigned int fg_rgb, bg_rgb;
125 static const unsigned int pc2rgb[8] =
126 { 0x000000, 0x0000ff, 0x00ff00, 0x00ffff, 0xff0000, 0xff00ff, 0xffff00,
129 /* Converting PC RGBI to sensible RGBA values is an "interesting"
130 proposition. This algorithm may need plenty of tweaking. */
132 fga = fg & 0xff000000;
133 fgh = ((fg >> 1) & 0xff000000) | 0x80000000;
135 bga = bg & 0xff000000;
136 bgh = ((bg >> 1) & 0xff000000) | 0x80000000;
138 for (i = 0; i < 256; i++) {
142 fg_rgb = pc2rgb[fg_idx & 7] & fg;
143 bg_rgb = pc2rgb[bg_idx & 7] & bg;
146 /* High intensity foreground */
153 /* Default black background, assume transparent */
155 } else if (bg_idx & 8) {
161 cp->argb_fg = fg_rgb;
162 cp->argb_bg = bg_rgb;
169 install_default_color_table(void)
172 const struct color_table *dp;
173 struct color_table *cp;
174 static struct color_table color_table[NCOLORS+256];
175 static const int pc2ansi[8] = {0, 4, 2, 6, 1, 5, 3, 7};
177 dp = default_color_table;
180 for (i = 0; i < NCOLORS; i++) {
182 free((void *)cp->ansi);
185 cp->ansi = strdup(dp->ansi);
191 for (i = 0; i < 256; i++) {
193 asprintf((char **)&cp->name, "msg%02x", i);
196 free((void *)cp->ansi);
198 asprintf((char **)&cp->ansi, "%s3%d;4%d", (i & 8) ? "1;" : "",
199 pc2ansi[i & 7], pc2ansi[(i >> 4) & 7]);
204 console_color_table = color_table;
205 console_color_table_size = NCOLORS+256;
207 set_msg_colors_global(MSG_COLORS_DEF_FG, MSG_COLORS_DEF_BG,
208 MSG_COLORS_DEF_SHADOW);
212 pad_line(const char *text, int align, int width)
214 static char buffer[MAX_CMDLINE_LEN];
217 if ( width >= (int) sizeof buffer )
218 return NULL; /* Can't do it */
224 memset(buffer, ' ', width);
226 p = ((width-n)*align)>>1;
227 memcpy(buffer+p, text, n);
232 /* Display an entry, with possible hotkey highlight. Assumes
233 that the current attribute is the non-hotkey one, and will
234 guarantee that as an exit condition as well. */
236 display_entry(const struct menu_entry *entry, const char *attrib,
237 const char *hotattrib, int width)
239 const char *p = entry->displayname;
245 if ( *p && ((unsigned char)*p & ~0x20) == entry->hotkey ) {
246 fputs(hotattrib, stdout);
248 fputs(attrib, stdout);
263 draw_row(int y, int sel, int top, int sbtop, int sbbot)
265 int i = (y-4-VSHIFT)+top;
267 printf("\033[%d;%dH\1#1\016x\017%s ",
268 y, MARGIN+1+HSHIFT, (i == sel) ? "\1#5" : "\1#3");
270 if ( i >= nentries ) {
271 fputs(pad_line("", 0, WIDTH-2*MARGIN-4), stdout);
273 display_entry(&menu_entries[i],
274 (i == sel) ? "\1#5" : "\1#3",
275 (i == sel) ? "\1#6" : "\1#4",
279 if ( nentries <= MENU_ROWS ) {
280 printf(" \1#1\016x\017");
281 } else if ( sbtop > 0 ) {
282 if ( y >= sbtop && y <= sbbot )
283 printf(" \1#7\016a\017");
285 printf(" \1#1\016x\017");
287 putchar(' '); /* Don't modify the scrollbar */
292 passwd_compare(const char *passwd, const char *entry)
296 unsigned char sha1[20], pwdsha1[20];
298 if ( passwd[0] != '$' ) /* Plaintext passwd, yuck! */
299 return !strcmp(entry, passwd);
301 if ( strncmp(passwd, "$4$", 3) )
302 return 0; /* Only SHA-1 passwds supported */
306 if ( (p = strchr(passwd+3, '$')) ) {
307 SHA1Update(&ctx, (void *)passwd+3, p-(passwd+3));
310 p = passwd+3; /* Assume no salt */
313 SHA1Update(&ctx, (void *)entry, strlen(entry));
314 SHA1Final(sha1, &ctx);
316 memset(pwdsha1, 0, 20);
317 unbase64(pwdsha1, 20, p);
319 return !memcmp(sha1, pwdsha1, 20);
322 static jmp_buf timeout_jump;
324 int mygetkey(clock_t timeout)
331 return get_key(stdin, timeout);
334 tto = min(totaltimeout, INT_MAX);
335 to = timeout ? min(tto, timeout) : tto;
338 key = get_key(stdin, to);
339 t = times(NULL) - t0;
341 if ( totaltimeout <= t )
342 longjmp(timeout_jump, 1);
346 if ( key != KEY_NONE )
359 ask_passwd(const char *menu_entry)
361 char user_passwd[WIDTH], *p;
366 printf("\033[%d;%dH\2#11\016l", PASSWD_ROW, PASSWD_MARGIN+1);
367 for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
370 printf("k\033[%d;%dHx", PASSWD_ROW+1, PASSWD_MARGIN+1);
371 for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
374 printf("x\033[%d;%dHm", PASSWD_ROW+2, PASSWD_MARGIN+1);
375 for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
378 printf("j\017\033[%d;%dH\2#12 %s \033[%d;%dH\2#13",
379 PASSWD_ROW, (WIDTH-(strlen(messages[MSG_PASSPROMPT].msg)+2))/2,
380 messages[MSG_PASSPROMPT].msg, PASSWD_ROW+1, PASSWD_MARGIN+3);
382 /* Actually allow user to type a password, then compare to the SHA1 */
397 p = user_passwd; /* No password entered */
404 if ( p > user_passwd ) {
411 while ( p > user_passwd ) {
418 if ( key >= ' ' && key <= 0xFF &&
419 (p-user_passwd) < WIDTH-2*PASSWD_MARGIN-5 ) {
427 if ( p == user_passwd )
428 return 0; /* No password entered */
432 return (menu_master_passwd && passwd_compare(menu_master_passwd, user_passwd))
433 || (menu_entry && passwd_compare(menu_entry, user_passwd));
438 draw_menu(int sel, int top, int edit_line)
441 int sbtop = 0, sbbot = 0;
445 if ( nentries > MENU_ROWS ) {
446 int sblen = MENU_ROWS*MENU_ROWS/nentries;
447 sbtop = (MENU_ROWS-sblen+1)*top/(nentries-MENU_ROWS+1);
448 sbbot = sbtop + sblen - 1;
450 sbtop += 4; sbbot += 4; /* Starting row of scrollbar */
453 printf("\033[%d;%dH\1#1\016l", VSHIFT+1, HSHIFT+MARGIN+1);
454 for ( x = 2+HSHIFT ; x <= (WIDTH-2*MARGIN-1)+HSHIFT ; x++ )
457 printf("k\033[%d;%dH\1#1x\017\1#2 %s \1#1\016x",
460 pad_line(messages[MSG_TITLE].msg, 1, WIDTH-2*MARGIN-4));
462 printf("\033[%d;%dH\1#1t", VSHIFT+3, HSHIFT+MARGIN+1);
463 for ( x = 2+HSHIFT ; x <= (WIDTH-2*MARGIN-1)+HSHIFT ; x++ )
465 fputs("u\017", stdout);
467 for ( y = 4+VSHIFT ; y < 4+VSHIFT+MENU_ROWS ; y++ )
468 draw_row(y, sel, top, sbtop, sbbot);
470 printf("\033[%d;%dH\1#1\016m", y, HSHIFT+MARGIN+1);
471 for ( x = 2+HSHIFT ; x <= (WIDTH-2*MARGIN-1)+HSHIFT ; x++ )
473 fputs("j\017", stdout);
475 if ( edit_line && allowedit && !menu_master_passwd )
476 tabmsg = messages[MSG_TAB].msg;
478 tabmsg = messages[MSG_NOTAB].msg;
480 tabmsg_len = strlen(tabmsg);
482 printf("\1#8\033[%d;%dH%s",
483 TABMSG_ROW, 1+HSHIFT+((WIDTH-tabmsg_len)>>1), tabmsg);
484 printf("\1#0\033[%d;1H", END_ROW);
490 fputs("\033e\033%@\033)0\033(B\1#0\033[?25l\033[2J", stdout);
494 display_help(const char *text)
501 printf("\1#0\033[%d;1H", HELPMSG_ROW);
503 printf("\2#16\033[%d;1H", HELPMSG_ROW);
506 for (p = text, row = HELPMSG_ROW; *p && row <= HELPMSGEND_ROW; p++) {
514 printf("\033[K\033[%d;1H", ++row);
521 fputs("\033[K", stdout);
523 while (row <= HELPMSGEND_ROW) {
524 printf("\033[K\033[%d;1H", ++row);
528 static void show_fkey(int key)
534 case KEY_F1: fkey = 0; break;
535 case KEY_F2: fkey = 1; break;
536 case KEY_F3: fkey = 2; break;
537 case KEY_F4: fkey = 3; break;
538 case KEY_F5: fkey = 4; break;
539 case KEY_F6: fkey = 5; break;
540 case KEY_F7: fkey = 6; break;
541 case KEY_F8: fkey = 7; break;
542 case KEY_F9: fkey = 8; break;
543 case KEY_F10: fkey = 9; break;
544 case KEY_F11: fkey = 10; break;
545 case KEY_F12: fkey = 11; break;
546 default: fkey = -1; break;
552 if (fkeyhelp[fkey].textname)
553 key = show_message_file(fkeyhelp[fkey].textname,
554 fkeyhelp[fkey].background);
561 edit_cmdline(char *input, int top)
563 static char cmdline[MAX_CMDLINE_LEN];
564 int key, len, prev_len, cursor;
565 int redraw = 1; /* We enter with the menu already drawn */
567 strncpy(cmdline, input, MAX_CMDLINE_LEN);
568 cmdline[MAX_CMDLINE_LEN-1] = '\0';
570 len = cursor = strlen(cmdline);
575 /* Clear and redraw whole screen */
576 /* Enable ASCII on G0 and DEC VT on G1; do it in this order
577 to avoid confusing the Linux console */
579 draw_menu(-1, top, 1);
584 /* Redraw the command line */
585 printf("\033[?25l\033[%d;1H\1#9> \2#10%s",
586 CMDLINE_ROW, pad_line(cmdline, 0, max(len, prev_len)));
587 printf("\2#10\033[%d;3H%s\033[?25h",
588 CMDLINE_ROW, pad_line(cmdline, 0, cursor));
611 memmove(cmdline+cursor-1, cmdline+cursor, len-cursor+1);
620 if ( cursor < len ) {
621 memmove(cmdline+cursor, cmdline+cursor+1, len-cursor);
637 int prevcursor = cursor;
639 while ( cursor && my_isspace(cmdline[cursor-1]) )
642 while ( cursor && !my_isspace(cmdline[cursor-1]) )
645 memmove(cmdline+cursor, cmdline+prevcursor, len-prevcursor+1);
646 len -= (cursor-prevcursor);
661 if ( cursor < len ) {
662 putchar(cmdline[cursor++]);
667 if ( cursor < len ) {
668 cmdline[len = cursor] = '\0';
683 if ( cursor != len ) {
706 if ( key >= ' ' && key <= 0xFF && len < MAX_CMDLINE_LEN-1 ) {
707 if ( cursor == len ) {
709 cmdline[++len] = '\0';
714 memmove(cmdline+cursor+1, cmdline+cursor, len-cursor+1);
715 cmdline[cursor++] = key;
728 uint8_t shift_bits = *(uint8_t *)0x417;
730 return !!(shift_bits & 0x5d); /* Caps/Scroll/Alt/Shift */
734 print_timeout_message(int tol, int row, const char *msg)
738 const char *tp = msg;
742 while ((size_t)(tq-buf) < (sizeof buf-16) && (tc = *tp)) {
744 nnc = sprintf(tq, "\2#15%d\2#14", tol);
746 nc += nnc-8; /* 8 formatting characters */
755 printf("\033[%d;%dH\2#14 %s ", row, HSHIFT+1+((WIDTH-nc-2)>>1), buf);
762 int timeout_left, this_timeout;
766 if ( !setjmp(timeout_jump) ) {
767 timeout_left = timeout;
769 while (!timeout || timeout_left) {
770 int tol = timeout_left/CLK_TCK;
772 print_timeout_message(tol, HIDDEN_ROW, messages[MSG_AUTOBOOT].msg);
774 this_timeout = min(timeout_left, CLK_TCK);
775 key = mygetkey(this_timeout);
778 return NULL; /* Key pressed */
780 timeout_left -= this_timeout;
784 return menu_entries[defentry].cmdline; /* Default entry */
792 volatile int entry = defentry, prev_entry = -1;
793 int top = 0, prev_top = -1;
794 int clear = 1, to_clear;
795 const char *cmdline = NULL;
796 volatile clock_t key_timeout, timeout_left, this_timeout;
798 /* Note: for both key_timeout and timeout == 0 means no limit */
799 timeout_left = key_timeout = timeout;
801 /* If we're in shiftkey mode, exit immediately unless a shift key is pressed */
802 if ( shiftkey && !shift_is_held() ) {
803 return menu_entries[defentry].cmdline;
806 /* Handle hiddenmenu */
808 cmdline = do_hidden_menu();
812 /* Otherwise display the menu now; the timeout has already been
813 cancelled, since the user pressed a key. */
818 /* Handle both local and global timeout */
819 if ( setjmp(timeout_jump) ) {
822 if ( top < 0 || top < entry-MENU_ROWS+1 )
823 top = max(0, entry-MENU_ROWS+1);
824 else if ( top > entry || top > max(0,nentries-MENU_ROWS) )
825 top = min(entry, max(0,nentries-MENU_ROWS));
827 draw_menu(ontimeout ? -1 : entry, top, 1);
828 cmdline = ontimeout ? ontimeout : menu_entries[entry].cmdline;
835 else if ( entry >= nentries )
838 if ( top < 0 || top < entry-MENU_ROWS+1 )
839 top = max(0, entry-MENU_ROWS+1);
840 else if ( top > entry || top > max(0,nentries-MENU_ROWS) )
841 top = min(entry, max(0,nentries-MENU_ROWS));
843 /* Start with a clear screen */
845 /* Clear and redraw whole screen */
846 /* Enable ASCII on G0 and DEC VT on G1; do it in this order
847 to avoid confusing the Linux console */
850 prev_entry = prev_top = -1;
853 if ( top != prev_top ) {
854 draw_menu(entry, top, 1);
855 display_help(menu_entries[entry].helptext);
856 } else if ( entry != prev_entry ) {
857 draw_row(prev_entry-top+4+VSHIFT, entry, top, 0, 0);
858 draw_row(entry-top+4+VSHIFT, entry, top, 0, 0);
859 display_help(menu_entries[entry].helptext);
862 prev_entry = entry; prev_top = top;
864 /* Cursor movement cancels timeout */
865 if ( entry != defentry )
869 int tol = timeout_left/CLK_TCK;
870 print_timeout_message(tol, TIMEOUT_ROW, messages[MSG_AUTOBOOT].msg);
876 this_timeout = min(min(key_timeout, timeout_left), CLK_TCK);
877 key = mygetkey(this_timeout);
879 if ( key != KEY_NONE ) {
880 timeout_left = key_timeout;
882 printf("\033[%d;1H\1#0\033[K", TIMEOUT_ROW);
886 case KEY_NONE: /* Timeout */
887 /* This is somewhat hacky, but this at least lets the user
888 know what's going on, and still deals with "phantom inputs"
889 e.g. on serial ports.
891 Warning: a timeout will boot the default entry without any
894 if ( timeout_left <= this_timeout )
895 longjmp(timeout_jump, 1);
897 timeout_left -= this_timeout;
907 key_timeout = 0; /* Cancels timeout */
908 if ( menu_entries[entry].passwd ) {
910 done = ask_passwd(menu_entries[entry].passwd);
914 cmdline = menu_entries[entry].cmdline;
928 if ( entry < nentries-1 ) {
930 if ( entry >= top+MENU_ROWS )
969 entry = nentries - 1;
970 top = max(0, nentries-MENU_ROWS);
993 key_timeout = 0; /* Cancels timeout */
994 draw_row(entry-top+4+VSHIFT, -1, top, 0, 0);
996 if ( menu_master_passwd ) {
997 ok = ask_passwd(NULL);
999 draw_menu(-1, top, 0);
1001 /* Erase [Tab] message and help text*/
1002 printf("\033[%d;1H\1#0\033[K", TABMSG_ROW);
1007 cmdline = edit_cmdline(menu_entries[entry].cmdline, top);
1009 clear = 1; /* In case we hit [Esc] and done is null */
1011 draw_row(entry-top+4+VSHIFT, entry, top, 0, 0);
1015 case KEY_CTRL('C'): /* Ctrl-C */
1016 case KEY_ESC: /* Esc */
1022 draw_row(entry-top+4+VSHIFT, -1, top, 0, 0);
1024 if ( menu_master_passwd )
1025 done = ask_passwd(NULL);
1029 if ( key > 0 && key < 0xFF ) {
1030 key &= ~0x20; /* Upper case */
1031 if ( menu_hotkeys[key] ) {
1033 entry = menu_hotkeys[key] - menu_entries;
1034 /* Should we commit at this point? */
1041 printf("\033[?25h"); /* Show cursor */
1043 /* Return the label name so localboot and ipappend work */
1049 execute(const char *cmdline, enum kernel_type type)
1052 const char *p, **pp;
1053 char *q = __com32.cs_bounce;
1054 const char *kernel, *args;
1056 memset(&ireg, 0, sizeof ireg);
1060 while ( *p && !my_isspace(*p) ) {
1066 while ( *p && my_isspace(*p) )
1071 if (kernel[0] == '.' && type == KT_NONE) {
1072 /* It might be a type specifier */
1073 enum kernel_type type = KT_NONE;
1074 for (pp = kernel_types; *pp; pp++, type++) {
1075 if (!strcmp(kernel+1, *pp)) {
1076 execute(p, type); /* Strip the type specifier and retry */
1081 if (type == KT_LOCALBOOT) {
1082 ireg.eax.w[0] = 0x0014; /* Local boot */
1083 ireg.edx.w[0] = strtoul(kernel, NULL, 0);
1085 if (type < KT_KERNEL)
1088 ireg.eax.w[0] = 0x0016; /* Run kernel image */
1089 ireg.esi.w[0] = OFFS(kernel);
1090 ireg.ds = SEG(kernel);
1091 ireg.ebx.w[0] = OFFS(args);
1092 ireg.es = SEG(args);
1093 ireg.edx.l = type-KT_KERNEL;
1094 /* ireg.ecx.l = 0; */ /* We do ipappend "manually" */
1097 __intcall(0x22, &ireg, NULL);
1099 /* If this returns, something went bad; return to menu */
1102 int menu_main(int argc, char *argv[])
1104 const char *cmdline;
1112 install_default_color_table();
1113 if (getscreensize(1, &rows, &cols)) {
1114 /* Unknown screen size? */
1120 parse_configs(argv+1);
1122 /* If anyone has specified negative parameters, consider them
1123 relative to the bottom row of the screen. */
1124 for (i = 0; mparm[i].name; i++)
1125 if (mparm[i].value < 0)
1126 mparm[i].value = max(mparm[i].value+rows, 0);
1128 draw_background(menu_background);
1131 fputs("No LABEL entries found in configuration file!\n", stdout);
1132 return 1; /* Error! */
1136 cmdline = run_menu();
1138 printf("\033[?25h\033[%d;1H\033[0m", END_ROW);
1142 execute(cmdline, KT_NONE);
1144 execute(onerror, KT_NONE);
1146 return 0; /* Exit */
1149 console_prepare(); /* If we're looping... */