1 #ident "$Id: menu.c,v 1.19 2005/04/06 09:53:28 hpa Exp $"
2 /* ----------------------------------------------------------------------- *
4 * Copyright 2004-2005 H. Peter Anvin - All Rights Reserved
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, Inc., 53 Temple Place Ste 330,
9 * Boston MA 02111-1307, USA; either version 2 of the License, or
10 * (at your option) any later version; incorporated herein by reference.
12 * ----------------------------------------------------------------------- */
17 * Simple menu system which displays a list and allows the user to select
18 * a command line and/or edit it.
21 #define _GNU_SOURCE /* Needed for asprintf() on Linux */
29 #include <sys/times.h>
40 # define CLK_TCK sysconf(_SC_CLK_TCK)
44 const char *border; /* Border area */
45 const char *title; /* Title bar */
46 const char *unsel; /* Unselected menu item */
47 const char *hotkey; /* Unselected hotkey */
48 const char *sel; /* Selected */
49 const char *hotsel; /* Selected hotkey */
50 const char *scrollbar; /* Scroll bar */
51 const char *tabmsg; /* Press [Tab] message */
52 const char *cmdmark; /* Command line marker */
53 const char *cmdline; /* Command line */
54 const char *screen; /* Rest of the screen */
55 const char *pwdborder; /* Password box border */
56 const char *pwdheader; /* Password box header */
57 const char *pwdentry; /* Password box contents */
60 static const struct menu_attrib default_attrib = {
61 .border = "\033[0;30;44m",
62 .title = "\033[1;36;44m",
63 .unsel = "\033[0;37;44m",
64 .hotkey = "\033[1;37;44m",
65 .sel = "\033[0;7;37;40m",
66 .hotsel = "\033[1;7;37;40m",
67 .scrollbar = "\033[0;30;44m",
68 .tabmsg = "\033[0;31;40m",
69 .cmdmark = "\033[1;36;40m",
70 .cmdline = "\033[0;37;40m",
71 .screen = "\033[0;37;40m",
72 .pwdborder = "\033[0;30;47m",
73 .pwdheader = "\033[0;31;47m",
74 .pwdentry = "\033[0;30;47m",
77 static const struct menu_attrib *menu_attrib = &default_attrib;
81 #define PASSWD_MARGIN 3
84 #define CMDLINE_ROW 20
89 pad_line(const char *text, int align, int width)
91 static char buffer[256];
94 if ( width >= (int) sizeof buffer )
95 return NULL; /* Can't do it */
101 memset(buffer, ' ', width);
103 p = ((width-n)*align)>>1;
104 memcpy(buffer+p, text, n);
109 /* Display an entry, with possible hotkey highlight. Assumes
110 that the current attribute is the non-hotkey one, and will
111 guarantee that as an exit condition as well. */
113 display_entry(const struct menu_entry *entry, const char *attrib,
114 const char *hotattrib, int width)
116 const char *p = entry->displayname;
122 if ( *p && ((unsigned char)*p & ~0x20) == entry->hotkey ) {
123 fputs(hotattrib, stdout);
125 fputs(attrib, stdout);
140 draw_row(int y, int sel, int top, int sbtop, int sbbot)
144 printf("\033[%d;%dH%s\016x\017%s ",
145 y, MARGIN+1, menu_attrib->border,
146 (i == sel) ? menu_attrib->sel : menu_attrib->unsel);
148 if ( i >= nentries ) {
149 fputs(pad_line("", 0, WIDTH-2*MARGIN-4), stdout);
151 display_entry(&menu_entries[i],
152 (i == sel) ? menu_attrib->sel : menu_attrib->unsel,
153 (i == sel) ? menu_attrib->hotsel : menu_attrib->hotkey,
157 if ( nentries <= MENU_ROWS ) {
158 printf(" %s\016x\017", menu_attrib->border);
159 } else if ( sbtop > 0 ) {
160 if ( y >= sbtop && y <= sbbot )
161 printf(" %s\016a\017", menu_attrib->scrollbar);
163 printf(" %s\016x\017", menu_attrib->border);
165 putchar(' '); /* Don't modify the scrollbar */
170 passwd_compare(const char *passwd, const char *entry)
174 unsigned char sha1[20], pwdsha1[20];
176 if ( passwd[0] != '$' ) /* Plaintext passwd, yuck! */
177 return !strcmp(entry, passwd);
179 if ( strncmp(passwd, "$4$", 3) )
180 return 0; /* Only SHA-1 passwds supported */
184 if ( (p = strchr(passwd+3, '$')) ) {
185 SHA1Update(&ctx, passwd+3, p-(passwd+3));
188 p = passwd+3; /* Assume no salt */
191 SHA1Update(&ctx, entry, strlen(entry));
192 SHA1Final(sha1, &ctx);
194 memset(pwdsha1, 0, 20);
195 unbase64(pwdsha1, 20, p);
197 return !memcmp(sha1, pwdsha1, 20);
201 ask_passwd(const char *menu_entry)
203 static const char title[] = "Password required";
204 char user_passwd[WIDTH], *p;
209 printf("\033[%d;%dH%s\016l", PASSWD_ROW, PASSWD_MARGIN+1,
210 menu_attrib->pwdborder);
211 for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
214 printf("k\033[%d;%dHx", PASSWD_ROW+1, PASSWD_MARGIN+1);
215 for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
218 printf("x\033[%d;%dHm", PASSWD_ROW+2, PASSWD_MARGIN+1);
219 for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
222 printf("j\017\033[%d;%dH%s %s \033[%d;%dH%s",
223 PASSWD_ROW, (WIDTH-((int)sizeof(title)+1))/2,
224 menu_attrib->pwdheader, title,
225 PASSWD_ROW+1, PASSWD_MARGIN+3, menu_attrib->pwdentry);
227 /* Actually allow user to type a password, then compare to the SHA1 */
232 key = get_key(stdin, 0);
242 p = user_passwd; /* No password entered */
249 if ( p > user_passwd ) {
256 while ( p > user_passwd ) {
263 if ( key >= ' ' && key <= 0xFF &&
264 (p-user_passwd) < WIDTH-2*PASSWD_MARGIN-5 ) {
272 if ( p == user_passwd )
273 return 0; /* No password entered */
277 return (menu_master_passwd && passwd_compare(menu_master_passwd, user_passwd))
278 || (menu_entry && passwd_compare(menu_entry, user_passwd));
283 draw_menu(int sel, int top)
286 int sbtop = 0, sbbot = 0;
288 if ( nentries > MENU_ROWS ) {
289 int sblen = MENU_ROWS*MENU_ROWS/nentries;
290 sbtop = (MENU_ROWS-sblen+1)*top/(nentries-MENU_ROWS+1);
291 sbbot = sbtop + sblen - 1;
293 sbtop += 4; sbbot += 4; /* Starting row of scrollbar */
296 printf("\033[1;%dH%s\016l", MARGIN+1, menu_attrib->border);
297 for ( x = 2 ; x <= WIDTH-2*MARGIN-1 ; x++ )
300 printf("k\033[2;%dH%sx\017%s %s %s\016x",
304 pad_line(menu_title, 1, WIDTH-2*MARGIN-4),
305 menu_attrib->border);
307 printf("\033[3;%dH%st", MARGIN+1, menu_attrib->border);
308 for ( x = 2 ; x <= WIDTH-2*MARGIN-1 ; x++ )
310 fputs("u\017", stdout);
312 for ( y = 4 ; y < 4+MENU_ROWS ; y++ )
313 draw_row(y, sel, top, sbtop, sbbot);
315 printf("\033[%d;%dH%s\016m", y, MARGIN+1, menu_attrib->border);
316 for ( x = 2 ; x <= WIDTH-2*MARGIN-1 ; x++ )
318 fputs("j\017", stdout);
320 if ( allowedit && !menu_master_passwd )
321 printf("%s\033[%d;1H%s", menu_attrib->tabmsg, TABMSG_ROW,
322 pad_line("Press [Tab] to edit options", 1, WIDTH));
324 printf("%s\033[%d;1H", menu_attrib->screen, END_ROW);
328 edit_cmdline(char *input, int top)
330 static char cmdline[MAX_CMDLINE_LEN];
332 int redraw = 1; /* We enter with the menu already drawn */
334 strncpy(cmdline, input, MAX_CMDLINE_LEN);
335 cmdline[MAX_CMDLINE_LEN-1] = '\0';
337 len = strlen(cmdline);
341 /* Clear and redraw whole screen */
342 /* Enable ASCII on G0 and DEC VT on G1; do it in this order
343 to avoid confusing the Linux console */
344 printf("\033e\033%%@\033)0\033(B%s\033[?25l\033[2J", menu_attrib->screen);
349 /* Redraw the command line */
350 printf("\033[?25h\033[%d;1H%s> %s%s",
351 CMDLINE_ROW, menu_attrib->cmdmark,
352 menu_attrib->cmdline, pad_line(cmdline, 0, MAX_CMDLINE_LEN-1));
353 printf("%s\033[%d;3H%s",
354 menu_attrib->cmdline, CMDLINE_ROW, cmdline);
358 key = get_key(stdin, 0);
360 /* FIX: should handle arrow keys and edit-in-middle */
376 cmdline[--len] = '\0';
389 int wasbs = (cmdline[len-1] <= ' ');
390 while ( len && (cmdline[len-1] <= ' ' || !wasbs) ) {
392 wasbs = wasbs || (cmdline[len-1] <= ' ');
399 if ( key >= ' ' && key <= 0xFF && len < MAX_CMDLINE_LEN-1 ) {
401 cmdline[++len] = '\0';
412 printf("\033e\033%%@\033)0\033(B%s\033[?25l\033[2J", menu_attrib->screen);
420 int entry = defentry, prev_entry = -1;
421 int top = 0, prev_top = -1;
423 const char *cmdline = NULL;
426 /* Convert timeout from deciseconds to clock ticks */
427 /* Note: for both key_timeout and timeout == 0 means no limit */
428 key_timeout = (clock_t)(CLK_TCK*timeout+9)/10;
433 else if ( entry >= nentries )
436 if ( top < 0 || top < entry-MENU_ROWS+1 )
437 top = max(0, entry-MENU_ROWS+1);
438 else if ( top > entry || top > max(0,nentries-MENU_ROWS) )
439 top = min(entry, max(0,nentries-MENU_ROWS));
441 /* Start with a clear screen */
443 /* Clear and redraw whole screen */
444 /* Enable ASCII on G0 and DEC VT on G1; do it in this order
445 to avoid confusing the Linux console */
448 prev_entry = prev_top = -1;
451 if ( top != prev_top ) {
452 draw_menu(entry, top);
453 } else if ( entry != prev_entry ) {
454 draw_row(prev_entry-top+4, entry, top, 0, 0);
455 draw_row(entry-top+4, entry, top, 0, 0);
458 prev_entry = entry; prev_top = top;
460 key = get_key(stdin, key_timeout);
462 case KEY_NONE: /* Timeout */
463 /* This is somewhat hacky, but this at least lets the user
464 know what's going on, and still deals with "phantom inputs"
465 e.g. on serial ports.
467 Warning: a timeout will boot the default entry without any
469 if ( entry != defentry )
472 cmdline = menu_entries[defentry].label;
481 if ( menu_entries[entry].passwd ) {
483 done = ask_passwd(menu_entries[entry].passwd);
487 cmdline = menu_entries[entry].label;
501 if ( entry < nentries-1 ) {
503 if ( entry >= top+MENU_ROWS )
534 entry = nentries - 1;
535 top = max(0, nentries-MENU_ROWS);
541 draw_row(entry-top+4, -1, top, 0, 0);
543 if ( menu_master_passwd ) {
544 ok = ask_passwd(NULL);
550 cmdline = edit_cmdline(menu_entries[entry].cmdline, top);
552 clear = 1; /* In case we hit [Esc] and done is null */
554 draw_row(entry-top+4, entry, top, 0, 0);
558 case KEY_CTRL('C'): /* Ctrl-C */
559 case KEY_ESC: /* Esc */
564 draw_row(entry-top+4, -1, top, 0, 0);
566 if ( menu_master_passwd )
567 done = ask_passwd(NULL);
571 if ( key > 0 && key < 0xFF ) {
572 key &= ~0x20; /* Upper case */
573 if ( menu_hotkeys[key] ) {
574 entry = menu_hotkeys[key] - menu_entries;
575 /* Should we commit at this point? */
582 printf("\033[?25h"); /* Show cursor */
584 /* Return the label name so localboot and ipappend work */
589 static void __attribute__((noreturn))
590 execute(const char *cmdline)
593 static com32sys_t ireg;
595 strcpy(__com32.cs_bounce, cmdline);
596 ireg.eax.w[0] = 0x0003; /* Run command */
597 ireg.ebx.w[0] = OFFS(__com32.cs_bounce);
598 ireg.es = SEG(__com32.cs_bounce);
599 __intcall(0x22, &ireg, NULL);
600 exit(255); /* Shouldn't return */
603 printf("\n>>> %s\n", cmdline);
608 int main(int argc, char *argv[])
610 const char *cmdline = NULL;
617 parse_config(argv[1]);
620 fputs("No LABEL entries found in configuration file!\n", stdout);
623 cmdline = run_menu();
626 printf("\033[?25h\033[%d;1H\033[0m", END_ROW);