1 /* ----------------------------------------------------------------------- *
3 * Copyright 2004-2006 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 */
39 int (*draw_background
)(const char *filename
);
42 * The color/attribute indexes (\1#XX) are as follows
44 * 00 - screen Rest of the screen
45 * 01 - border Border area
46 * 02 - title Title bar
47 * 03 - unsel Unselected menu item
48 * 04 - hotkey Unselected hotkey
49 * 05 - sel Selection bar
50 * 06 - hotsel Selected hotkey
51 * 07 - scrollbar Scroll bar
52 * 08 - tabmsg Press [Tab] message
53 * 09 - cmdmark Command line marker
54 * 10 - cmdline Command line
55 * 11 - pwdborder Password box border
56 * 12 - pwdheader Password box header
57 * 13 - pwdentry Password box contents
58 * 14 - timeout_msg Timeout message
59 * 15 - timeout Timeout counter
62 static const struct color_table default_color_table
[] = {
63 { "screen", "37;40", 0x80ffffff, 0x00000000 },
64 { "border", "30;44", 0x40000000, 0x00000000 },
65 { "title", "1;36;44", 0xc00090f0, 0x00000000 },
66 { "unsel", "37;44", 0x90ffffff, 0x00000000 },
67 { "hotkey", "1;37;44", 0xffffffff, 0x00000000 },
68 { "sel", "7;37;40", 0xcf101010, 0x20ff8000 },
69 { "hotsel", "7;37;40", 0xff353535, 0x20ff8000 },
70 { "scrollbar", "30;44", 0x40000000, 0x00000000 },
71 { "tabmsg", "31;40", 0x90ffff00, 0x00000000 },
72 { "cmdmark", "1;36;40", 0xc000ffff, 0x00000000 },
73 { "cmdline", "37;40", 0xc0ffffff, 0x00000000 },
74 { "pwdborder", "30;47", 0x80ffffff, 0x20ffffff },
75 { "pwdheader", "31;47", 0x80ff8080, 0x20ffffff },
76 { "pwdentry", "30;47", 0x80ffffff, 0x20ffffff },
77 { "timeout_msg", "37;40", 0x80ffffff, 0x00000000 },
78 { "timeout", "1;37;40", 0xc0ffffff, 0x00000000 },
81 #define NCOLORS (sizeof default_color_table/sizeof(struct color_table))
83 struct menu_parameter mparm
[] = {
86 { "passwordmargin", 3 },
91 { "passwordrow", 11 },
96 #define WIDTH mparm[0].value
97 #define MARGIN mparm[1].value
98 #define PASSWD_MARGIN mparm[2].value
99 #define MENU_ROWS mparm[3].value
100 #define TABMSG_ROW mparm[4].value
101 #define CMDLINE_ROW mparm[5].value
102 #define END_ROW mparm[6].value
103 #define PASSWD_ROW mparm[7].value
104 #define TIMEOUT_ROW mparm[8].value
107 install_default_color_table(void)
110 const struct color_table
*dp
;
111 struct color_table
*cp
;
112 static struct color_table color_table
[NCOLORS
];
114 dp
= default_color_table
;
117 for (i
= 0; i
< NCOLORS
; i
++) {
119 free((void *)cp
->ansi
);
122 cp
->ansi
= strdup(dp
->ansi
);
123 cp
->argb_fg
= dp
->argb_fg
;
124 cp
->argb_bg
= dp
->argb_bg
;
130 console_color_table
= color_table
;
131 console_color_table_size
= NCOLORS
;
135 pad_line(const char *text
, int align
, int width
)
137 static char buffer
[MAX_CMDLINE_LEN
];
140 if ( width
>= (int) sizeof buffer
)
141 return NULL
; /* Can't do it */
147 memset(buffer
, ' ', width
);
149 p
= ((width
-n
)*align
)>>1;
150 memcpy(buffer
+p
, text
, n
);
155 /* Display an entry, with possible hotkey highlight. Assumes
156 that the current attribute is the non-hotkey one, and will
157 guarantee that as an exit condition as well. */
159 display_entry(const struct menu_entry
*entry
, const char *attrib
,
160 const char *hotattrib
, int width
)
162 const char *p
= entry
->displayname
;
168 if ( *p
&& ((unsigned char)*p
& ~0x20) == entry
->hotkey
) {
169 fputs(hotattrib
, stdout
);
171 fputs(attrib
, stdout
);
186 draw_row(int y
, int sel
, int top
, int sbtop
, int sbbot
)
190 printf("\033[%d;%dH\1#01\016x\017%s ",
191 y
, MARGIN
+1, (i
== sel
) ? "\1#05" : "\1#03");
193 if ( i
>= nentries
) {
194 fputs(pad_line("", 0, WIDTH
-2*MARGIN
-4), stdout
);
196 display_entry(&menu_entries
[i
],
197 (i
== sel
) ? "\1#05" : "\1#03",
198 (i
== sel
) ? "\1#06" : "\1#04",
202 if ( nentries
<= MENU_ROWS
) {
203 printf(" \1#01\016x\017");
204 } else if ( sbtop
> 0 ) {
205 if ( y
>= sbtop
&& y
<= sbbot
)
206 printf(" \1#07\016a\017");
208 printf(" \1#01\016x\017");
210 putchar(' '); /* Don't modify the scrollbar */
215 passwd_compare(const char *passwd
, const char *entry
)
219 unsigned char sha1
[20], pwdsha1
[20];
221 if ( passwd
[0] != '$' ) /* Plaintext passwd, yuck! */
222 return !strcmp(entry
, passwd
);
224 if ( strncmp(passwd
, "$4$", 3) )
225 return 0; /* Only SHA-1 passwds supported */
229 if ( (p
= strchr(passwd
+3, '$')) ) {
230 SHA1Update(&ctx
, (void *)passwd
+3, p
-(passwd
+3));
233 p
= passwd
+3; /* Assume no salt */
236 SHA1Update(&ctx
, (void *)entry
, strlen(entry
));
237 SHA1Final(sha1
, &ctx
);
239 memset(pwdsha1
, 0, 20);
240 unbase64(pwdsha1
, 20, p
);
242 return !memcmp(sha1
, pwdsha1
, 20);
245 static jmp_buf timeout_jump
;
247 static int mygetkey(clock_t timeout
)
254 return get_key(stdin
, timeout
);
257 tto
= min(totaltimeout
, INT_MAX
);
258 to
= timeout
? min(tto
, timeout
) : tto
;
261 key
= get_key(stdin
, to
);
262 t
= times(NULL
) - t0
;
264 if ( totaltimeout
<= t
)
265 longjmp(timeout_jump
, 1);
269 if ( key
!= KEY_NONE
)
282 ask_passwd(const char *menu_entry
)
284 static const char title
[] = "Password required";
285 char user_passwd
[WIDTH
], *p
;
290 printf("\033[%d;%dH\1#11\016l", PASSWD_ROW
, PASSWD_MARGIN
+1);
291 for ( x
= 2 ; x
<= WIDTH
-2*PASSWD_MARGIN
-1 ; x
++ )
294 printf("k\033[%d;%dHx", PASSWD_ROW
+1, PASSWD_MARGIN
+1);
295 for ( x
= 2 ; x
<= WIDTH
-2*PASSWD_MARGIN
-1 ; x
++ )
298 printf("x\033[%d;%dHm", PASSWD_ROW
+2, PASSWD_MARGIN
+1);
299 for ( x
= 2 ; x
<= WIDTH
-2*PASSWD_MARGIN
-1 ; x
++ )
302 printf("j\017\033[%d;%dH\1#12 %s \033[%d;%dH\1#13",
303 PASSWD_ROW
, (WIDTH
-((int)sizeof(title
)+1))/2,
304 title
, PASSWD_ROW
+1, PASSWD_MARGIN
+3);
306 /* Actually allow user to type a password, then compare to the SHA1 */
321 p
= user_passwd
; /* No password entered */
328 if ( p
> user_passwd
) {
335 while ( p
> user_passwd
) {
342 if ( key
>= ' ' && key
<= 0xFF &&
343 (p
-user_passwd
) < WIDTH
-2*PASSWD_MARGIN
-5 ) {
351 if ( p
== user_passwd
)
352 return 0; /* No password entered */
356 return (menu_master_passwd
&& passwd_compare(menu_master_passwd
, user_passwd
))
357 || (menu_entry
&& passwd_compare(menu_entry
, user_passwd
));
362 draw_menu(int sel
, int top
, int edit_line
)
365 int sbtop
= 0, sbbot
= 0;
367 if ( nentries
> MENU_ROWS
) {
368 int sblen
= MENU_ROWS
*MENU_ROWS
/nentries
;
369 sbtop
= (MENU_ROWS
-sblen
+1)*top
/(nentries
-MENU_ROWS
+1);
370 sbbot
= sbtop
+ sblen
- 1;
372 sbtop
+= 4; sbbot
+= 4; /* Starting row of scrollbar */
375 printf("\033[1;%dH\1#01\016l", MARGIN
+1);
376 for ( x
= 2 ; x
<= WIDTH
-2*MARGIN
-1 ; x
++ )
379 printf("k\033[2;%dH\1#01x\017\1#02 %s \1#01\016x",
381 pad_line(menu_title
, 1, WIDTH
-2*MARGIN
-4));
383 printf("\033[3;%dH\1#01t", MARGIN
+1);
384 for ( x
= 2 ; x
<= WIDTH
-2*MARGIN
-1 ; x
++ )
386 fputs("u\017", stdout
);
388 for ( y
= 4 ; y
< 4+MENU_ROWS
; y
++ )
389 draw_row(y
, sel
, top
, sbtop
, sbbot
);
391 printf("\033[%d;%dH\1#01\016m", y
, MARGIN
+1);
392 for ( x
= 2 ; x
<= WIDTH
-2*MARGIN
-1 ; x
++ )
394 fputs("j\017", stdout
);
396 if ( edit_line
&& allowedit
&& !menu_master_passwd
)
397 printf("\1#08\033[%d;1H%s", TABMSG_ROW
,
398 pad_line("Press [Tab] to edit options", 1, WIDTH
));
400 printf("\1#00\033[%d;1H", END_ROW
);
406 fputs("\033e\033%@\033)0\033(B\1#00\033[?25l\033[2J", stdout
);
410 edit_cmdline(char *input
, int top
)
412 static char cmdline
[MAX_CMDLINE_LEN
];
413 int key
, len
, prev_len
, cursor
;
414 int redraw
= 1; /* We enter with the menu already drawn */
416 strncpy(cmdline
, input
, MAX_CMDLINE_LEN
);
417 cmdline
[MAX_CMDLINE_LEN
-1] = '\0';
419 len
= cursor
= strlen(cmdline
);
424 /* Clear and redraw whole screen */
425 /* Enable ASCII on G0 and DEC VT on G1; do it in this order
426 to avoid confusing the Linux console */
428 draw_menu(-1, top
, 1);
433 /* Redraw the command line */
434 printf("\033[?25l\033[%d;1H\1#09> \1#10%s",
435 CMDLINE_ROW
, pad_line(cmdline
, 0, prev_len
));
436 printf("\1#10\033[%d;3H%s\033[?25h",
437 CMDLINE_ROW
, pad_line(cmdline
, 0, cursor
));
460 memmove(cmdline
+cursor
-1, cmdline
+cursor
, len
-cursor
+1);
469 if ( cursor
< len
) {
470 memmove(cmdline
+cursor
, cmdline
+cursor
+1, len
-cursor
);
486 int prevcursor
= cursor
;
488 while ( cursor
&& my_isspace(cmdline
[cursor
-1]) )
491 while ( cursor
&& !my_isspace(cmdline
[cursor
-1]) )
494 memmove(cmdline
+cursor
, cmdline
+prevcursor
, len
-prevcursor
+1);
495 len
-= (cursor
-prevcursor
);
510 if ( cursor
< len
) {
511 putchar(cmdline
[cursor
++]);
516 if ( cursor
< len
) {
517 cmdline
[len
= cursor
] = '\0';
532 if ( cursor
!= len
) {
539 if ( key
>= ' ' && key
<= 0xFF && len
< MAX_CMDLINE_LEN
-1 ) {
540 if ( cursor
== len
) {
542 cmdline
[++len
] = '\0';
547 memmove(cmdline
+cursor
+1, cmdline
+cursor
, len
-cursor
+1);
548 cmdline
[cursor
++] = key
;
561 uint8_t shift_bits
= *(uint8_t *)0x417;
563 return !!(shift_bits
& 0x5d); /* Caps/Scroll/Alt/Shift */
571 volatile int entry
= defentry
, prev_entry
= -1;
572 int top
= 0, prev_top
= -1;
573 int clear
= 1, to_clear
;
574 const char *cmdline
= NULL
;
575 volatile clock_t key_timeout
, timeout_left
, this_timeout
;
577 /* Note: for both key_timeout and timeout == 0 means no limit */
578 timeout_left
= key_timeout
= timeout
;
580 /* If we're in shiftkey mode, exit immediately unless a shift key is pressed */
581 if ( shiftkey
&& !shift_is_held() ) {
582 return menu_entries
[defentry
].cmdline
;
585 /* Handle both local and global timeout */
586 if ( setjmp(timeout_jump
) ) {
589 if ( top
< 0 || top
< entry
-MENU_ROWS
+1 )
590 top
= max(0, entry
-MENU_ROWS
+1);
591 else if ( top
> entry
|| top
> max(0,nentries
-MENU_ROWS
) )
592 top
= min(entry
, max(0,nentries
-MENU_ROWS
));
594 draw_menu(ontimeout
? -1 : entry
, top
, 1);
595 cmdline
= ontimeout
? ontimeout
: menu_entries
[entry
].cmdline
;
602 else if ( entry
>= nentries
)
605 if ( top
< 0 || top
< entry
-MENU_ROWS
+1 )
606 top
= max(0, entry
-MENU_ROWS
+1);
607 else if ( top
> entry
|| top
> max(0,nentries
-MENU_ROWS
) )
608 top
= min(entry
, max(0,nentries
-MENU_ROWS
));
610 /* Start with a clear screen */
612 /* Clear and redraw whole screen */
613 /* Enable ASCII on G0 and DEC VT on G1; do it in this order
614 to avoid confusing the Linux console */
617 prev_entry
= prev_top
= -1;
620 if ( top
!= prev_top
) {
621 draw_menu(entry
, top
, 1);
622 } else if ( entry
!= prev_entry
) {
623 draw_row(prev_entry
-top
+4, entry
, top
, 0, 0);
624 draw_row(entry
-top
+4, entry
, top
, 0, 0);
627 prev_entry
= entry
; prev_top
= top
;
629 /* Cursor movement cancels timeout */
630 if ( entry
!= defentry
)
634 int tol
= timeout_left
/CLK_TCK
;
635 int nc
= snprintf(NULL
, 0, " Automatic boot in %d seconds ", tol
);
636 printf("\033[%d;%dH\1#14 Automatic boot in \1#15%d\1#14 seconds ",
637 TIMEOUT_ROW
, 1+((WIDTH
-nc
)>>1), tol
);
643 this_timeout
= min(min(key_timeout
, timeout_left
), CLK_TCK
);
644 key
= mygetkey(this_timeout
);
646 if ( key
!= KEY_NONE
) {
647 timeout_left
= key_timeout
;
649 printf("\033[%d;1H\1#00\033[K", TIMEOUT_ROW
);
653 case KEY_NONE
: /* Timeout */
654 /* This is somewhat hacky, but this at least lets the user
655 know what's going on, and still deals with "phantom inputs"
656 e.g. on serial ports.
658 Warning: a timeout will boot the default entry without any
661 if ( timeout_left
<= this_timeout
)
662 longjmp(timeout_jump
, 1);
664 timeout_left
-= this_timeout
;
674 key_timeout
= 0; /* Cancels timeout */
675 if ( menu_entries
[entry
].passwd
) {
677 done
= ask_passwd(menu_entries
[entry
].passwd
);
681 cmdline
= menu_entries
[entry
].cmdline
;
695 if ( entry
< nentries
-1 ) {
697 if ( entry
>= top
+MENU_ROWS
)
736 entry
= nentries
- 1;
737 top
= max(0, nentries
-MENU_ROWS
);
744 key_timeout
= 0; /* Cancels timeout */
745 draw_row(entry
-top
+4, -1, top
, 0, 0);
747 if ( menu_master_passwd
) {
748 ok
= ask_passwd(NULL
);
750 draw_menu(-1, top
, 0);
752 /* Erase [Tab] message */
753 printf("\033[%d;1H\1#00\033[K", TABMSG_ROW
);
757 cmdline
= edit_cmdline(menu_entries
[entry
].cmdline
, top
);
759 clear
= 1; /* In case we hit [Esc] and done is null */
761 draw_row(entry
-top
+4, entry
, top
, 0, 0);
765 case KEY_CTRL('C'): /* Ctrl-C */
766 case KEY_ESC
: /* Esc */
772 draw_row(entry
-top
+4, -1, top
, 0, 0);
774 if ( menu_master_passwd
)
775 done
= ask_passwd(NULL
);
779 if ( key
> 0 && key
< 0xFF ) {
780 key
&= ~0x20; /* Upper case */
781 if ( menu_hotkeys
[key
] ) {
783 entry
= menu_hotkeys
[key
] - menu_entries
;
784 /* Should we commit at this point? */
791 printf("\033[?25h"); /* Show cursor */
793 /* Return the label name so localboot and ipappend work */
799 execute(const char *cmdline
)
804 char *q
= __com32
.cs_bounce
;
805 const char *kernel
, *args
;
807 memset(&ireg
, 0, sizeof ireg
);
811 while ( *p
&& !my_isspace(*p
) ) {
817 while ( *p
&& my_isspace(*p
) )
822 if ( !strcmp(kernel
, ".localboot") ) {
823 ireg
.eax
.w
[0] = 0x0014; /* Local boot */
824 ireg
.edx
.w
[0] = strtoul(args
, NULL
, 0);
826 ireg
.eax
.w
[0] = 0x0016; /* Run kernel image */
827 ireg
.esi
.w
[0] = OFFS(kernel
);
828 ireg
.ds
= SEG(kernel
);
829 ireg
.ebx
.w
[0] = OFFS(args
);
831 /* ireg.ecx.l = 0; */ /* We do ipappend "manually" */
832 /* ireg.edx.l = 0; */
835 __intcall(0x22, &ireg
, NULL
);
837 /* If this returns, something went bad; return to menu */
840 printf("\n\033[0m>>> %s\n", cmdline
);
845 int menu_main(int argc
, char *argv
[])
851 install_default_color_table();
852 fputs("\1#00", stdout
);
854 parse_config(argv
[1]);
856 if (draw_background
&& menu_background
)
857 draw_background(menu_background
);
860 fputs("No LABEL entries found in configuration file!\n", stdout
);
861 return 1; /* Error! */
865 cmdline
= run_menu();
867 printf("\033[?25h\033[%d;1H\033[0m", END_ROW
);