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 */
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, SHADOW_NORMAL
},
64 { "border", "30;44", 0x40000000, 0x00000000, SHADOW_NORMAL
},
65 { "title", "1;36;44", 0xc00090f0, 0x00000000, SHADOW_NORMAL
},
66 { "unsel", "37;44", 0x90ffffff, 0x00000000, SHADOW_NORMAL
},
67 { "hotkey", "1;37;44", 0xffffffff, 0x00000000, SHADOW_NORMAL
},
68 { "sel", "7;37;40", 0xe0000000, 0x20ff8000, SHADOW_ALL
},
69 { "hotsel", "1;7;37;40", 0xe0400000, 0x20ff8000, SHADOW_ALL
},
70 { "scrollbar", "30;44", 0x40000000, 0x00000000, SHADOW_NORMAL
},
71 { "tabmsg", "31;40", 0x90ffff00, 0x00000000, SHADOW_NORMAL
},
72 { "cmdmark", "1;36;40", 0xc000ffff, 0x00000000, SHADOW_NORMAL
},
73 { "cmdline", "37;40", 0xc0ffffff, 0x00000000, SHADOW_NORMAL
},
74 { "pwdborder", "30;47", 0x80ffffff, 0x20ffffff, SHADOW_NORMAL
},
75 { "pwdheader", "31;47", 0x80ff8080, 0x20ffffff, SHADOW_NORMAL
},
76 { "pwdentry", "30;47", 0x80ffffff, 0x20ffffff, SHADOW_NORMAL
},
77 { "timeout_msg", "37;40", 0x80ffffff, 0x00000000, SHADOW_NORMAL
},
78 { "timeout", "1;37;40", 0xc0ffffff, 0x00000000, SHADOW_NORMAL
},
79 { "help", "37;40", 0xc0ffffff, 0x00000000, SHADOW_NORMAL
},
82 #define NCOLORS (sizeof default_color_table/sizeof(struct color_table))
84 struct menu_parameter mparm
[] = {
87 { "passwordmargin", 3 },
92 { "passwordrow", 11 },
95 { "helpmsgendrow", -1 },
99 #define WIDTH mparm[0].value
100 #define MARGIN mparm[1].value
101 #define PASSWD_MARGIN mparm[2].value
102 #define MENU_ROWS mparm[3].value
103 #define TABMSG_ROW mparm[4].value
104 #define CMDLINE_ROW mparm[5].value
105 #define END_ROW mparm[6].value
106 #define PASSWD_ROW mparm[7].value
107 #define TIMEOUT_ROW mparm[8].value
108 #define HELPMSG_ROW mparm[9].value
109 #define HELPMSGEND_ROW mparm[10].value
112 install_default_color_table(void)
115 const struct color_table
*dp
;
116 struct color_table
*cp
;
117 static struct color_table color_table
[NCOLORS
];
119 dp
= default_color_table
;
122 for (i
= 0; i
< NCOLORS
; i
++) {
124 free((void *)cp
->ansi
);
127 cp
->ansi
= strdup(dp
->ansi
);
133 console_color_table
= color_table
;
134 console_color_table_size
= NCOLORS
;
138 pad_line(const char *text
, int align
, int width
)
140 static char buffer
[MAX_CMDLINE_LEN
];
143 if ( width
>= (int) sizeof buffer
)
144 return NULL
; /* Can't do it */
150 memset(buffer
, ' ', width
);
152 p
= ((width
-n
)*align
)>>1;
153 memcpy(buffer
+p
, text
, n
);
158 /* Display an entry, with possible hotkey highlight. Assumes
159 that the current attribute is the non-hotkey one, and will
160 guarantee that as an exit condition as well. */
162 display_entry(const struct menu_entry
*entry
, const char *attrib
,
163 const char *hotattrib
, int width
)
165 const char *p
= entry
->displayname
;
171 if ( *p
&& ((unsigned char)*p
& ~0x20) == entry
->hotkey
) {
172 fputs(hotattrib
, stdout
);
174 fputs(attrib
, stdout
);
189 draw_row(int y
, int sel
, int top
, int sbtop
, int sbbot
)
193 printf("\033[%d;%dH\1#01\016x\017%s ",
194 y
, MARGIN
+1, (i
== sel
) ? "\1#05" : "\1#03");
196 if ( i
>= nentries
) {
197 fputs(pad_line("", 0, WIDTH
-2*MARGIN
-4), stdout
);
199 display_entry(&menu_entries
[i
],
200 (i
== sel
) ? "\1#05" : "\1#03",
201 (i
== sel
) ? "\1#06" : "\1#04",
205 if ( nentries
<= MENU_ROWS
) {
206 printf(" \1#01\016x\017");
207 } else if ( sbtop
> 0 ) {
208 if ( y
>= sbtop
&& y
<= sbbot
)
209 printf(" \1#07\016a\017");
211 printf(" \1#01\016x\017");
213 putchar(' '); /* Don't modify the scrollbar */
218 passwd_compare(const char *passwd
, const char *entry
)
222 unsigned char sha1
[20], pwdsha1
[20];
224 if ( passwd
[0] != '$' ) /* Plaintext passwd, yuck! */
225 return !strcmp(entry
, passwd
);
227 if ( strncmp(passwd
, "$4$", 3) )
228 return 0; /* Only SHA-1 passwds supported */
232 if ( (p
= strchr(passwd
+3, '$')) ) {
233 SHA1Update(&ctx
, (void *)passwd
+3, p
-(passwd
+3));
236 p
= passwd
+3; /* Assume no salt */
239 SHA1Update(&ctx
, (void *)entry
, strlen(entry
));
240 SHA1Final(sha1
, &ctx
);
242 memset(pwdsha1
, 0, 20);
243 unbase64(pwdsha1
, 20, p
);
245 return !memcmp(sha1
, pwdsha1
, 20);
248 static jmp_buf timeout_jump
;
250 static int mygetkey(clock_t timeout
)
257 return get_key(stdin
, timeout
);
260 tto
= min(totaltimeout
, INT_MAX
);
261 to
= timeout
? min(tto
, timeout
) : tto
;
264 key
= get_key(stdin
, to
);
265 t
= times(NULL
) - t0
;
267 if ( totaltimeout
<= t
)
268 longjmp(timeout_jump
, 1);
272 if ( key
!= KEY_NONE
)
285 ask_passwd(const char *menu_entry
)
287 char user_passwd
[WIDTH
], *p
;
292 printf("\033[%d;%dH\1#11\016l", PASSWD_ROW
, PASSWD_MARGIN
+1);
293 for ( x
= 2 ; x
<= WIDTH
-2*PASSWD_MARGIN
-1 ; x
++ )
296 printf("k\033[%d;%dHx", PASSWD_ROW
+1, PASSWD_MARGIN
+1);
297 for ( x
= 2 ; x
<= WIDTH
-2*PASSWD_MARGIN
-1 ; x
++ )
300 printf("x\033[%d;%dHm", PASSWD_ROW
+2, PASSWD_MARGIN
+1);
301 for ( x
= 2 ; x
<= WIDTH
-2*PASSWD_MARGIN
-1 ; x
++ )
304 printf("j\017\033[%d;%dH\1#12 %s \033[%d;%dH\1#13",
305 PASSWD_ROW
, (WIDTH
-(strlen(messages
[MSG_PASSPROMPT
].msg
)+2))/2,
306 messages
[MSG_PASSPROMPT
].msg
, PASSWD_ROW
+1, PASSWD_MARGIN
+3);
308 /* Actually allow user to type a password, then compare to the SHA1 */
323 p
= user_passwd
; /* No password entered */
330 if ( p
> user_passwd
) {
337 while ( p
> user_passwd
) {
344 if ( key
>= ' ' && key
<= 0xFF &&
345 (p
-user_passwd
) < WIDTH
-2*PASSWD_MARGIN
-5 ) {
353 if ( p
== user_passwd
)
354 return 0; /* No password entered */
358 return (menu_master_passwd
&& passwd_compare(menu_master_passwd
, user_passwd
))
359 || (menu_entry
&& passwd_compare(menu_entry
, user_passwd
));
364 draw_menu(int sel
, int top
, int edit_line
)
367 int sbtop
= 0, sbbot
= 0;
370 if ( nentries
> MENU_ROWS
) {
371 int sblen
= MENU_ROWS
*MENU_ROWS
/nentries
;
372 sbtop
= (MENU_ROWS
-sblen
+1)*top
/(nentries
-MENU_ROWS
+1);
373 sbbot
= sbtop
+ sblen
- 1;
375 sbtop
+= 4; sbbot
+= 4; /* Starting row of scrollbar */
378 printf("\033[1;%dH\1#01\016l", MARGIN
+1);
379 for ( x
= 2 ; x
<= WIDTH
-2*MARGIN
-1 ; x
++ )
382 printf("k\033[2;%dH\1#01x\017\1#02 %s \1#01\016x",
384 pad_line(messages
[MSG_TITLE
].msg
, 1, WIDTH
-2*MARGIN
-4));
386 printf("\033[3;%dH\1#01t", MARGIN
+1);
387 for ( x
= 2 ; x
<= WIDTH
-2*MARGIN
-1 ; x
++ )
389 fputs("u\017", stdout
);
391 for ( y
= 4 ; y
< 4+MENU_ROWS
; y
++ )
392 draw_row(y
, sel
, top
, sbtop
, sbbot
);
394 printf("\033[%d;%dH\1#01\016m", y
, MARGIN
+1);
395 for ( x
= 2 ; x
<= WIDTH
-2*MARGIN
-1 ; x
++ )
397 fputs("j\017", stdout
);
399 if ( edit_line
&& allowedit
&& !menu_master_passwd
)
400 tabmsg
= messages
[MSG_TAB
].msg
;
402 tabmsg
= messages
[MSG_NOTAB
].msg
;
404 printf("\1#08\033[%d;1H%s", TABMSG_ROW
, pad_line(tabmsg
, 1, WIDTH
));
405 printf("\1#00\033[%d;1H", END_ROW
);
411 fputs("\033e\033%@\033)0\033(B\1#00\033[?25l\033[2J", stdout
);
415 display_help(const char *text
)
422 printf("\1#00\033[%d;1H", HELPMSG_ROW
);
424 printf("\1#16\033[%d;1H", HELPMSG_ROW
);
427 for (p
= text
, row
= HELPMSG_ROW
; *p
&& row
<= HELPMSGEND_ROW
; p
++) {
435 printf("\033[K\033[%d;1H", ++row
);
442 fputs("\033[K", stdout
);
444 while (row
<= HELPMSGEND_ROW
) {
445 printf("\033[K\033[%d;1H", ++row
);
450 edit_cmdline(char *input
, int top
)
452 static char cmdline
[MAX_CMDLINE_LEN
];
453 int key
, len
, prev_len
, cursor
;
454 int redraw
= 1; /* We enter with the menu already drawn */
456 strncpy(cmdline
, input
, MAX_CMDLINE_LEN
);
457 cmdline
[MAX_CMDLINE_LEN
-1] = '\0';
459 len
= cursor
= strlen(cmdline
);
464 /* Clear and redraw whole screen */
465 /* Enable ASCII on G0 and DEC VT on G1; do it in this order
466 to avoid confusing the Linux console */
468 draw_menu(-1, top
, 1);
473 /* Redraw the command line */
474 printf("\033[?25l\033[%d;1H\1#09> \1#10%s",
475 CMDLINE_ROW
, pad_line(cmdline
, 0, prev_len
));
476 printf("\1#10\033[%d;3H%s\033[?25h",
477 CMDLINE_ROW
, pad_line(cmdline
, 0, cursor
));
500 memmove(cmdline
+cursor
-1, cmdline
+cursor
, len
-cursor
+1);
509 if ( cursor
< len
) {
510 memmove(cmdline
+cursor
, cmdline
+cursor
+1, len
-cursor
);
526 int prevcursor
= cursor
;
528 while ( cursor
&& my_isspace(cmdline
[cursor
-1]) )
531 while ( cursor
&& !my_isspace(cmdline
[cursor
-1]) )
534 memmove(cmdline
+cursor
, cmdline
+prevcursor
, len
-prevcursor
+1);
535 len
-= (cursor
-prevcursor
);
550 if ( cursor
< len
) {
551 putchar(cmdline
[cursor
++]);
556 if ( cursor
< len
) {
557 cmdline
[len
= cursor
] = '\0';
572 if ( cursor
!= len
) {
579 if ( key
>= ' ' && key
<= 0xFF && len
< MAX_CMDLINE_LEN
-1 ) {
580 if ( cursor
== len
) {
582 cmdline
[++len
] = '\0';
587 memmove(cmdline
+cursor
+1, cmdline
+cursor
, len
-cursor
+1);
588 cmdline
[cursor
++] = key
;
601 uint8_t shift_bits
= *(uint8_t *)0x417;
603 return !!(shift_bits
& 0x5d); /* Caps/Scroll/Alt/Shift */
611 volatile int entry
= defentry
, prev_entry
= -1;
612 int top
= 0, prev_top
= -1;
613 int clear
= 1, to_clear
;
614 const char *cmdline
= NULL
;
615 volatile clock_t key_timeout
, timeout_left
, this_timeout
;
617 /* Note: for both key_timeout and timeout == 0 means no limit */
618 timeout_left
= key_timeout
= timeout
;
620 /* If we're in shiftkey mode, exit immediately unless a shift key is pressed */
621 if ( shiftkey
&& !shift_is_held() ) {
622 return menu_entries
[defentry
].cmdline
;
625 /* Handle both local and global timeout */
626 if ( setjmp(timeout_jump
) ) {
629 if ( top
< 0 || top
< entry
-MENU_ROWS
+1 )
630 top
= max(0, entry
-MENU_ROWS
+1);
631 else if ( top
> entry
|| top
> max(0,nentries
-MENU_ROWS
) )
632 top
= min(entry
, max(0,nentries
-MENU_ROWS
));
634 draw_menu(ontimeout
? -1 : entry
, top
, 1);
635 cmdline
= ontimeout
? ontimeout
: menu_entries
[entry
].cmdline
;
642 else if ( entry
>= nentries
)
645 if ( top
< 0 || top
< entry
-MENU_ROWS
+1 )
646 top
= max(0, entry
-MENU_ROWS
+1);
647 else if ( top
> entry
|| top
> max(0,nentries
-MENU_ROWS
) )
648 top
= min(entry
, max(0,nentries
-MENU_ROWS
));
650 /* Start with a clear screen */
652 /* Clear and redraw whole screen */
653 /* Enable ASCII on G0 and DEC VT on G1; do it in this order
654 to avoid confusing the Linux console */
657 prev_entry
= prev_top
= -1;
660 if ( top
!= prev_top
) {
661 draw_menu(entry
, top
, 1);
662 display_help(menu_entries
[entry
].helptext
);
663 } else if ( entry
!= prev_entry
) {
664 draw_row(prev_entry
-top
+4, entry
, top
, 0, 0);
665 draw_row(entry
-top
+4, entry
, top
, 0, 0);
666 display_help(menu_entries
[entry
].helptext
);
669 prev_entry
= entry
; prev_top
= top
;
671 /* Cursor movement cancels timeout */
672 if ( entry
!= defentry
)
677 int tol
= timeout_left
/CLK_TCK
;
679 const char *tp
= messages
[MSG_AUTOBOOT
].msg
;
683 while ((size_t)(tq
-buf
) < (sizeof buf
-16) && (tc
= *tp
)) {
685 nnc
= sprintf(tq
, "\1#15%d\1#14", tol
);
687 nc
+= nnc
-8; /* 8 formatting characters */
696 printf("\033[%d;%dH\1#14 %s ", TIMEOUT_ROW
, 1+((WIDTH
-nc
)>>1), buf
);
702 this_timeout
= min(min(key_timeout
, timeout_left
), CLK_TCK
);
703 key
= mygetkey(this_timeout
);
705 if ( key
!= KEY_NONE
) {
706 timeout_left
= key_timeout
;
708 printf("\033[%d;1H\1#00\033[K", TIMEOUT_ROW
);
712 case KEY_NONE
: /* Timeout */
713 /* This is somewhat hacky, but this at least lets the user
714 know what's going on, and still deals with "phantom inputs"
715 e.g. on serial ports.
717 Warning: a timeout will boot the default entry without any
720 if ( timeout_left
<= this_timeout
)
721 longjmp(timeout_jump
, 1);
723 timeout_left
-= this_timeout
;
733 key_timeout
= 0; /* Cancels timeout */
734 if ( menu_entries
[entry
].passwd
) {
736 done
= ask_passwd(menu_entries
[entry
].passwd
);
740 cmdline
= menu_entries
[entry
].cmdline
;
754 if ( entry
< nentries
-1 ) {
756 if ( entry
>= top
+MENU_ROWS
)
795 entry
= nentries
- 1;
796 top
= max(0, nentries
-MENU_ROWS
);
803 key_timeout
= 0; /* Cancels timeout */
804 draw_row(entry
-top
+4, -1, top
, 0, 0);
806 if ( menu_master_passwd
) {
807 ok
= ask_passwd(NULL
);
809 draw_menu(-1, top
, 0);
811 /* Erase [Tab] message and help text*/
812 printf("\033[%d;1H\1#00\033[K", TABMSG_ROW
);
817 cmdline
= edit_cmdline(menu_entries
[entry
].cmdline
, top
);
819 clear
= 1; /* In case we hit [Esc] and done is null */
821 draw_row(entry
-top
+4, entry
, top
, 0, 0);
825 case KEY_CTRL('C'): /* Ctrl-C */
826 case KEY_ESC
: /* Esc */
832 draw_row(entry
-top
+4, -1, top
, 0, 0);
834 if ( menu_master_passwd
)
835 done
= ask_passwd(NULL
);
839 if ( key
> 0 && key
< 0xFF ) {
840 key
&= ~0x20; /* Upper case */
841 if ( menu_hotkeys
[key
] ) {
843 entry
= menu_hotkeys
[key
] - menu_entries
;
844 /* Should we commit at this point? */
851 printf("\033[?25h"); /* Show cursor */
853 /* Return the label name so localboot and ipappend work */
859 execute(const char *cmdline
, enum kernel_type type
)
864 char *q
= __com32
.cs_bounce
;
865 const char *kernel
, *args
;
867 memset(&ireg
, 0, sizeof ireg
);
871 while ( *p
&& !my_isspace(*p
) ) {
877 while ( *p
&& my_isspace(*p
) )
882 if (kernel
[0] == '.' && type
== KT_NONE
) {
883 /* It might be a type specifier */
884 enum kernel_type type
= KT_NONE
;
885 for (pp
= kernel_types
; *pp
; pp
++, type
++) {
886 if (!strcmp(kernel
+1, *pp
)) {
887 execute(p
, type
); /* Strip the type specifier and retry */
892 if (type
== KT_LOCALBOOT
) {
893 ireg
.eax
.w
[0] = 0x0014; /* Local boot */
894 ireg
.edx
.w
[0] = strtoul(kernel
, NULL
, 0);
896 if (type
< KT_KERNEL
)
899 ireg
.eax
.w
[0] = 0x0016; /* Run kernel image */
900 ireg
.esi
.w
[0] = OFFS(kernel
);
901 ireg
.ds
= SEG(kernel
);
902 ireg
.ebx
.w
[0] = OFFS(args
);
904 ireg
.edx
.l
= type
-KT_KERNEL
;
905 /* ireg.ecx.l = 0; */ /* We do ipappend "manually" */
908 __intcall(0x22, &ireg
, NULL
);
910 /* If this returns, something went bad; return to menu */
913 printf("\n\033[0m>>> %s\n", cmdline
);
918 int menu_main(int argc
, char *argv
[])
928 install_default_color_table();
929 if (getscreensize(1, &rows
, &cols
)) {
930 /* Unknown screen size? */
936 parse_configs(argv
+1);
938 /* If anyone has specified negative parameters, consider them
939 relative to the bottom row of the screen. */
940 for (i
= 0; mparm
[i
].name
; i
++)
941 if (mparm
[i
].value
< 0)
942 mparm
[i
].value
= max(mparm
[i
].value
+rows
, 0);
945 draw_background(menu_background
);
948 fputs("No LABEL entries found in configuration file!\n", stdout
);
949 return 1; /* Error! */
953 cmdline
= run_menu();
955 printf("\033[?25h\033[%d;1H\033[0m", END_ROW
);
959 execute(cmdline
, KT_NONE
);
961 execute(onerror
, KT_NONE
);
966 console_prepare(); /* If we're looping... */