syslinux-3.08-2 sources from FC4
[bootcd.git] / syslinux / com32 / modules / menu.c
1 #ident "$Id: menu.c,v 1.19 2005/04/06 09:53:28 hpa Exp $"
2 /* ----------------------------------------------------------------------- *
3  *   
4  *   Copyright 2004-2005 H. Peter Anvin - All Rights Reserved
5  *
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.
11  *
12  * ----------------------------------------------------------------------- */
13
14 /*
15  * menu.c
16  *
17  * Simple menu system which displays a list and allows the user to select
18  * a command line and/or edit it.
19  */
20
21 #define _GNU_SOURCE             /* Needed for asprintf() on Linux */
22 #include <string.h>
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <consoles.h>
26 #include <getkey.h>
27 #include <minmax.h>
28 #include <time.h>
29 #include <sys/times.h>
30 #include <unistd.h>
31 #include <sha1.h>
32 #include <base64.h>
33 #ifdef __COM32__
34 #include <com32.h>
35 #endif
36
37 #include "menu.h"
38
39 #ifndef CLK_TCK
40 # define CLK_TCK sysconf(_SC_CLK_TCK)
41 #endif
42
43 struct menu_attrib {
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 */
58 };
59
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",
75 };
76
77 static const struct menu_attrib *menu_attrib = &default_attrib;
78
79 #define WIDTH           80
80 #define MARGIN          10
81 #define PASSWD_MARGIN   3
82 #define MENU_ROWS       12
83 #define TABMSG_ROW      18
84 #define CMDLINE_ROW     20
85 #define END_ROW         24
86 #define PASSWD_ROW      11
87
88 static char *
89 pad_line(const char *text, int align, int width)
90 {
91   static char buffer[256];
92   int n, p;
93
94   if ( width >= (int) sizeof buffer )
95     return NULL;                /* Can't do it */
96
97   n = strlen(text);
98   if ( n >= width )
99     n = width;
100
101   memset(buffer, ' ', width);
102   buffer[width] = 0;
103   p = ((width-n)*align)>>1;
104   memcpy(buffer+p, text, n);
105
106   return buffer;
107 }
108
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. */
112 static void
113 display_entry(const struct menu_entry *entry, const char *attrib,
114               const char *hotattrib, int width)
115 {
116   const char *p = entry->displayname;
117
118   while ( width ) {
119     if ( *p ) {
120       if ( *p == '^' ) {
121         p++;
122         if ( *p && ((unsigned char)*p & ~0x20) == entry->hotkey ) {
123           fputs(hotattrib, stdout);
124           putchar(*p++);
125           fputs(attrib, stdout);
126           width--;
127         }
128       } else {
129         putchar(*p++);
130         width--;
131       }
132     } else {
133       putchar(' ');
134       width--;
135     }
136   }
137 }
138
139 static void
140 draw_row(int y, int sel, int top, int sbtop, int sbbot)
141 {
142   int i = (y-4)+top;
143   
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);
147   
148   if ( i >= nentries ) {
149     fputs(pad_line("", 0, WIDTH-2*MARGIN-4), stdout);
150   } else {
151     display_entry(&menu_entries[i],
152                   (i == sel) ? menu_attrib->sel : menu_attrib->unsel,
153                   (i == sel) ? menu_attrib->hotsel : menu_attrib->hotkey,
154                   WIDTH-2*MARGIN-4);
155   }
156
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);
162     else
163       printf(" %s\016x\017", menu_attrib->border);
164   } else {
165     putchar(' ');               /* Don't modify the scrollbar */
166   }
167 }
168
169 static int
170 passwd_compare(const char *passwd, const char *entry)
171 {
172   const char *p;
173   SHA1_CTX ctx;
174   unsigned char sha1[20], pwdsha1[20];
175
176   if ( passwd[0] != '$' )       /* Plaintext passwd, yuck! */
177     return !strcmp(entry, passwd);
178
179   if ( strncmp(passwd, "$4$", 3) )
180     return 0;                   /* Only SHA-1 passwds supported */
181
182   SHA1Init(&ctx);
183
184   if ( (p = strchr(passwd+3, '$')) ) {
185     SHA1Update(&ctx, passwd+3, p-(passwd+3));
186     p++;
187   } else {
188     p = passwd+3;               /* Assume no salt */
189   }
190
191   SHA1Update(&ctx, entry, strlen(entry));
192   SHA1Final(sha1, &ctx);
193
194   memset(pwdsha1, 0, 20);
195   unbase64(pwdsha1, 20, p);
196
197   return !memcmp(sha1, pwdsha1, 20);
198 }
199
200 static int
201 ask_passwd(const char *menu_entry)
202 {
203   static const char title[] = "Password required";
204   char user_passwd[WIDTH], *p;
205   int done;
206   int key;
207   int x;
208
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++ )
212     putchar('q');
213   
214   printf("k\033[%d;%dHx", PASSWD_ROW+1, PASSWD_MARGIN+1);
215   for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
216     putchar(' ');
217
218   printf("x\033[%d;%dHm", PASSWD_ROW+2, PASSWD_MARGIN+1);
219   for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
220     putchar('q');
221   
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);
226
227   /* Actually allow user to type a password, then compare to the SHA1 */
228   done = 0;
229   p = user_passwd;
230
231   while ( !done ) {
232     key = get_key(stdin, 0);
233
234     switch ( key ) {
235     case KEY_ENTER:
236     case KEY_CTRL('J'):
237       done = 1;
238       break;
239
240     case KEY_ESC:
241     case KEY_CTRL('C'):
242       p = user_passwd;          /* No password entered */
243       done = 1;
244       break;
245
246     case KEY_BACKSPACE:
247     case KEY_DEL:
248     case KEY_DELETE:
249       if ( p > user_passwd ) {
250         printf("\b \b");
251         p--;
252       }
253       break;
254
255     case KEY_CTRL('U'):
256       while ( p > user_passwd ) {
257         printf("\b \b");
258         p--;
259       }
260       break;
261
262     default:
263       if ( key >= ' ' && key <= 0xFF &&
264            (p-user_passwd) < WIDTH-2*PASSWD_MARGIN-5 ) {
265         *p++ = key;
266         putchar('*');
267       }
268       break;
269     }
270   }
271
272   if ( p == user_passwd )
273     return 0;                   /* No password entered */
274
275   *p = '\0';
276       
277   return (menu_master_passwd && passwd_compare(menu_master_passwd, user_passwd))
278     || (menu_entry && passwd_compare(menu_entry, user_passwd));
279 }
280
281
282 static void
283 draw_menu(int sel, int top)
284 {
285   int x, y;
286   int sbtop = 0, sbbot = 0;
287   
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;
292     
293     sbtop += 4;  sbbot += 4;    /* Starting row of scrollbar */
294   }
295   
296   printf("\033[1;%dH%s\016l", MARGIN+1, menu_attrib->border);
297   for ( x = 2 ; x <= WIDTH-2*MARGIN-1 ; x++ )
298     putchar('q');
299   
300   printf("k\033[2;%dH%sx\017%s %s %s\016x",
301          MARGIN+1,
302          menu_attrib->border,
303          menu_attrib->title,
304          pad_line(menu_title, 1, WIDTH-2*MARGIN-4),
305          menu_attrib->border);
306   
307   printf("\033[3;%dH%st", MARGIN+1, menu_attrib->border);
308   for ( x = 2 ; x <= WIDTH-2*MARGIN-1 ; x++ )
309     putchar('q');
310   fputs("u\017", stdout);
311   
312   for ( y = 4 ; y < 4+MENU_ROWS ; y++ )
313     draw_row(y, sel, top, sbtop, sbbot);
314
315   printf("\033[%d;%dH%s\016m", y, MARGIN+1, menu_attrib->border);
316   for ( x = 2 ; x <= WIDTH-2*MARGIN-1 ; x++ )
317     putchar('q');
318   fputs("j\017", stdout);
319
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));
323
324   printf("%s\033[%d;1H", menu_attrib->screen, END_ROW);
325 }
326
327 static const char *
328 edit_cmdline(char *input, int top)
329 {
330   static char cmdline[MAX_CMDLINE_LEN];
331   int key, len;
332   int redraw = 1;               /* We enter with the menu already drawn */
333
334   strncpy(cmdline, input, MAX_CMDLINE_LEN);
335   cmdline[MAX_CMDLINE_LEN-1] = '\0';
336
337   len = strlen(cmdline);
338
339   for (;;) {
340     if ( redraw > 1 ) {
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);
345       draw_menu(-1, top);
346     }
347
348     if ( redraw > 0 ) {
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);
355       redraw = 0;
356     }
357
358     key = get_key(stdin, 0);
359
360     /* FIX: should handle arrow keys and edit-in-middle */
361
362     switch( key ) {
363     case KEY_CTRL('L'):
364       redraw = 2;
365       break;
366     case KEY_ENTER:
367     case KEY_CTRL('J'):
368       return cmdline;
369     case KEY_ESC:
370     case KEY_CTRL('C'):
371       return NULL;
372     case KEY_BACKSPACE:
373     case KEY_DEL:
374     case KEY_DELETE:
375       if ( len ) {
376         cmdline[--len] = '\0';
377         redraw = 1;
378       }
379       break;
380     case KEY_CTRL('U'):
381       if ( len ) {
382         len = 0;
383         cmdline[len] = '\0';
384         redraw = 1;
385       }
386       break;
387     case KEY_CTRL('W'):
388       if ( len ) {
389         int wasbs = (cmdline[len-1] <= ' ');
390         while ( len && (cmdline[len-1] <= ' ' || !wasbs) ) {
391           len--;
392           wasbs = wasbs || (cmdline[len-1] <= ' ');
393         }
394         cmdline[len] = '\0';
395         redraw = 1;
396       }
397       break;
398     default:
399       if ( key >= ' ' && key <= 0xFF && len < MAX_CMDLINE_LEN-1 ) {
400         cmdline[len] = key;
401         cmdline[++len] = '\0';
402         putchar(key);
403       }
404       break;
405     }
406   }
407 }
408
409 static void
410 clear_screen(void)
411 {
412   printf("\033e\033%%@\033)0\033(B%s\033[?25l\033[2J", menu_attrib->screen);
413 }
414
415 static const char *
416 run_menu(void)
417 {
418   int key;
419   int done = 0;
420   int entry = defentry, prev_entry = -1;
421   int top = 0, prev_top = -1;
422   int clear = 1;
423   const char *cmdline = NULL;
424   clock_t key_timeout;
425
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;
429
430   while ( !done ) {
431     if ( entry < 0 )
432       entry = 0;
433     else if ( entry >= nentries )
434       entry = nentries-1;
435
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));
440
441     /* Start with a clear screen */
442     if ( clear ) {
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 */
446       clear_screen();
447       clear = 0;
448       prev_entry = prev_top = -1;
449     }
450
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);
456     }
457
458     prev_entry = entry;  prev_top = top;
459
460     key = get_key(stdin, key_timeout);
461     switch ( key ) {
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.
466
467          Warning: a timeout will boot the default entry without any
468          password! */
469       if ( entry != defentry )
470         entry = defentry;
471       else {
472         cmdline = menu_entries[defentry].label;
473         done = 1;
474       }
475       break;
476     case KEY_CTRL('L'):
477       clear = 1;
478       break;
479     case KEY_ENTER:
480     case KEY_CTRL('J'):
481       if ( menu_entries[entry].passwd ) {
482         clear = 1;
483         done = ask_passwd(menu_entries[entry].passwd);
484       } else {
485         done = 1;
486       }
487       cmdline = menu_entries[entry].label;
488       break;
489     case 'P':
490     case 'p':
491     case KEY_UP:
492       if ( entry > 0 ) {
493         entry--;
494         if ( entry < top )
495           top -= MENU_ROWS;
496       }
497       break;
498     case 'N':
499     case 'n':
500     case KEY_DOWN:
501       if ( entry < nentries-1 ) {
502         entry++;
503         if ( entry >= top+MENU_ROWS )
504           top += MENU_ROWS;
505       }
506       break;
507     case KEY_CTRL('P'):
508     case KEY_PGUP:
509     case KEY_LEFT:
510       entry -= MENU_ROWS;
511       top   -= MENU_ROWS;
512       break;
513     case KEY_CTRL('N'):
514     case KEY_PGDN:
515     case KEY_RIGHT:
516     case ' ':
517       entry += MENU_ROWS;
518       top   += MENU_ROWS;
519       break;
520     case '-':
521       entry--;
522       top--;
523       break;
524     case '+':
525       entry++;
526       top++;
527       break;
528     case KEY_CTRL('A'):
529     case KEY_HOME:
530       top = entry = 0;
531       break;
532     case KEY_CTRL('E'):
533     case KEY_END:
534       entry = nentries - 1;
535       top = max(0, nentries-MENU_ROWS);
536       break;
537     case KEY_TAB:
538       if ( allowedit ) {
539         int ok = 1;
540
541         draw_row(entry-top+4, -1, top, 0, 0);
542
543         if ( menu_master_passwd ) {
544           ok = ask_passwd(NULL);
545           clear_screen();
546           draw_menu(-1, top);
547         }
548         
549         if ( ok ) {
550           cmdline = edit_cmdline(menu_entries[entry].cmdline, top);
551           done = !!cmdline;
552           clear = 1;            /* In case we hit [Esc] and done is null */
553         } else {
554           draw_row(entry-top+4, entry, top, 0, 0);
555         }
556       }
557       break;
558     case KEY_CTRL('C'):         /* Ctrl-C */
559     case KEY_ESC:               /* Esc */
560       if ( allowedit ) {
561         done = 1;
562         clear = 1;
563         
564         draw_row(entry-top+4, -1, top, 0, 0);
565
566         if ( menu_master_passwd )
567           done = ask_passwd(NULL);
568       }
569       break;
570     default:
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? */
576         }
577       }
578       break;
579     }
580   }
581
582   printf("\033[?25h");          /* Show cursor */
583
584   /* Return the label name so localboot and ipappend work */
585   return cmdline;
586 }
587
588
589 static void __attribute__((noreturn))
590 execute(const char *cmdline)
591 {
592 #ifdef __COM32__
593   static com32sys_t ireg;
594
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 */
601 #else
602   /* For testing... */
603   printf("\n>>> %s\n", cmdline);
604   exit(0);
605 #endif
606 }
607
608 int main(int argc, char *argv[])
609 {
610   const char *cmdline = NULL;
611   int err = 0;
612
613   (void)argc;
614
615   console_ansi_raw();
616
617   parse_config(argv[1]);
618
619   if ( !nentries ) {
620     fputs("No LABEL entries found in configuration file!\n", stdout);
621     err = 1;
622   } else {
623     cmdline = run_menu();
624   }
625
626   printf("\033[?25h\033[%d;1H\033[0m", END_ROW);
627   if ( cmdline )
628     execute(cmdline);
629   else
630     return err;
631 }