Add support for Unix-standard MD5 password
[profile/ivi/syslinux.git] / com32 / modules / menumain.c
1 /* ----------------------------------------------------------------------- *
2  *
3  *   Copyright 2004-2007 H. Peter Anvin - All Rights Reserved
4  *
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.
10  *
11  * ----------------------------------------------------------------------- */
12
13 /*
14  * menumain.c
15  *
16  * Simple menu system which displays a list and allows the user to select
17  * a command line and/or edit it.
18  */
19
20 #define _GNU_SOURCE             /* Needed for asprintf() on Linux */
21 #include <ctype.h>
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 <setjmp.h>
29 #include <limits.h>
30 #include <sha1.h>
31 #include <md5.h>
32 #include <base64.h>
33 #include <colortbl.h>
34 #include <com32.h>
35
36 #include "menu.h"
37
38 /*
39  * The color/attribute indexes (\1#X, \2#XX, \3#XXX) are as follows
40  *
41  * 00 - screen          Rest of the screen
42  * 01 - border          Border area
43  * 02 - title           Title bar
44  * 03 - unsel           Unselected menu item
45  * 04 - hotkey          Unselected hotkey
46  * 05 - sel             Selection bar
47  * 06 - hotsel          Selected hotkey
48  * 07 - scrollbar       Scroll bar
49  * 08 - tabmsg          Press [Tab] message
50  * 09 - cmdmark         Command line marker
51  * 10 - cmdline         Command line
52  * 11 - pwdborder       Password box border
53  * 12 - pwdheader       Password box header
54  * 13 - pwdentry        Password box contents
55  * 14 - timeout_msg     Timeout message
56  * 15 - timeout         Timeout counter
57  * 16 - help            Current entry help text
58  */
59
60 static const struct color_table default_color_table[] = {
61   { "screen",      "37;40",     0x80ffffff, 0x00000000, SHADOW_NORMAL },
62   { "border",      "30;44",     0x40000000, 0x00000000, SHADOW_NORMAL },
63   { "title",       "1;36;44",   0xc00090f0, 0x00000000, SHADOW_NORMAL },
64   { "unsel",       "37;44",     0x90ffffff, 0x00000000, SHADOW_NORMAL },
65   { "hotkey",      "1;37;44",   0xffffffff, 0x00000000, SHADOW_NORMAL },
66   { "sel",         "7;37;40",   0xe0000000, 0x20ff8000, SHADOW_ALL },
67   { "hotsel",      "1;7;37;40", 0xe0400000, 0x20ff8000, SHADOW_ALL },
68   { "scrollbar",   "30;44",     0x40000000, 0x00000000, SHADOW_NORMAL },
69   { "tabmsg",      "31;40",     0x90ffff00, 0x00000000, SHADOW_NORMAL },
70   { "cmdmark",     "1;36;40",   0xc000ffff, 0x00000000, SHADOW_NORMAL },
71   { "cmdline",     "37;40",     0xc0ffffff, 0x00000000, SHADOW_NORMAL },
72   { "pwdborder",   "30;47",     0x80ffffff, 0x20ffffff, SHADOW_NORMAL },
73   { "pwdheader",   "31;47",     0x80ff8080, 0x20ffffff, SHADOW_NORMAL },
74   { "pwdentry",    "30;47",     0x80ffffff, 0x20ffffff, SHADOW_NORMAL },
75   { "timeout_msg", "37;40",     0x80ffffff, 0x00000000, SHADOW_NORMAL },
76   { "timeout",     "1;37;40",   0xc0ffffff, 0x00000000, SHADOW_NORMAL },
77   { "help",        "37;40",     0xc0ffffff, 0x00000000, SHADOW_NORMAL },
78 };
79
80 #define NCOLORS (sizeof default_color_table/sizeof(struct color_table))
81 const int message_base_color = NCOLORS;
82
83 struct menu_parameter mparm[] = {
84   { "width", 80 },
85   { "margin", 10 },
86   { "passwordmargin", 3 },
87   { "rows", 12 },
88   { "tabmsgrow", 18 },
89   { "cmdlinerow", 18 },
90   { "endrow", -1 },
91   { "passwordrow", 11 },
92   { "timeoutrow", 20 },
93   { "helpmsgrow", 22 },
94   { "helpmsgendrow", -1 },
95   { "hshift", 0 },
96   { "vshift", 0 },
97   { "hiddenrow", -2 },
98   { NULL, 0 }
99 };
100
101 #define WIDTH           mparm[0].value
102 #define MARGIN          mparm[1].value
103 #define PASSWD_MARGIN   mparm[2].value
104 #define MENU_ROWS       mparm[3].value
105 #define TABMSG_ROW      (mparm[4].value+VSHIFT)
106 #define CMDLINE_ROW     (mparm[5].value+VSHIFT)
107 #define END_ROW         mparm[6].value
108 #define PASSWD_ROW      (mparm[7].value+VSHIFT)
109 #define TIMEOUT_ROW     (mparm[8].value+VSHIFT)
110 #define HELPMSG_ROW     (mparm[9].value+VSHIFT)
111 #define HELPMSGEND_ROW  mparm[10].value
112 #define HSHIFT          mparm[11].value
113 #define VSHIFT          mparm[12].value
114 #define HIDDEN_ROW      mparm[13].value
115
116 void set_msg_colors_global(unsigned int fg, unsigned int bg,
117                            enum color_table_shadow shadow)
118 {
119   struct color_table *cp = console_color_table+message_base_color;
120   unsigned int i;
121   unsigned int fga, bga;
122   unsigned int fgh, bgh;
123   unsigned int fg_idx, bg_idx;
124   unsigned int fg_rgb, bg_rgb;
125
126   static const unsigned int pc2rgb[8] =
127     { 0x000000, 0x0000ff, 0x00ff00, 0x00ffff, 0xff0000, 0xff00ff, 0xffff00,
128       0xffffff };
129
130   /* Converting PC RGBI to sensible RGBA values is an "interesting"
131      proposition.  This algorithm may need plenty of tweaking. */
132
133   fga = fg & 0xff000000;
134   fgh = ((fg >> 1) & 0xff000000) | 0x80000000;
135
136   bga = bg & 0xff000000;
137   bgh = ((bg >> 1) & 0xff000000) | 0x80000000;
138
139   for (i = 0; i < 256; i++) {
140     fg_idx = i & 15;
141     bg_idx = i >> 4;
142
143     fg_rgb = pc2rgb[fg_idx & 7] & fg;
144     bg_rgb = pc2rgb[bg_idx & 7] & bg;
145
146     if (fg_idx & 8) {
147       /* High intensity foreground */
148       fg_rgb |= fgh;
149     } else {
150       fg_rgb |= fga;
151     }
152
153     if (bg_idx == 0) {
154       /* Default black background, assume transparent */
155       bg_rgb = 0;
156     } else if (bg_idx & 8) {
157       bg_rgb |= bgh;
158     } else {
159       bg_rgb |= bga;
160     }
161
162     cp->argb_fg = fg_rgb;
163     cp->argb_bg = bg_rgb;
164     cp->shadow = shadow;
165     cp++;
166   }
167 }
168
169 static void
170 install_default_color_table(void)
171 {
172   unsigned int i;
173   const struct color_table *dp;
174   struct color_table *cp;
175   static struct color_table color_table[NCOLORS+256];
176   static const int pc2ansi[8] = {0, 4, 2, 6, 1, 5, 3, 7};
177
178   dp = default_color_table;
179   cp = color_table;
180
181   for (i = 0; i < NCOLORS; i++) {
182     if (cp->ansi)
183       free((void *)cp->ansi);
184
185     *cp = *dp;
186     cp->ansi = strdup(dp->ansi);
187
188     cp++;
189     dp++;
190   }
191
192   for (i = 0; i < 256; i++) {
193     if (!cp->name)
194       asprintf((char **)&cp->name, "msg%02x", i);
195
196     if (cp->ansi)
197       free((void *)cp->ansi);
198
199     asprintf((char **)&cp->ansi, "%s3%d;4%d", (i & 8) ? "1;" : "",
200              pc2ansi[i & 7], pc2ansi[(i >> 4) & 7]);
201
202     cp++;
203   }
204
205   console_color_table = color_table;
206   console_color_table_size = NCOLORS+256;
207
208   set_msg_colors_global(MSG_COLORS_DEF_FG, MSG_COLORS_DEF_BG,
209                         MSG_COLORS_DEF_SHADOW);
210 }
211
212 static char *
213 pad_line(const char *text, int align, int width)
214 {
215   static char buffer[MAX_CMDLINE_LEN];
216   int n, p;
217
218   if ( width >= (int) sizeof buffer )
219     return NULL;                /* Can't do it */
220
221   n = strlen(text);
222   if ( n >= width )
223     n = width;
224
225   memset(buffer, ' ', width);
226   buffer[width] = 0;
227   p = ((width-n)*align)>>1;
228   memcpy(buffer+p, text, n);
229
230   return buffer;
231 }
232
233 /* Display an entry, with possible hotkey highlight.  Assumes
234    that the current attribute is the non-hotkey one, and will
235    guarantee that as an exit condition as well. */
236 static void
237 display_entry(const struct menu_entry *entry, const char *attrib,
238               const char *hotattrib, int width)
239 {
240   const char *p = entry->displayname;
241
242   while ( width ) {
243     if ( *p ) {
244       if ( *p == '^' ) {
245         p++;
246         if ( *p && ((unsigned char)*p & ~0x20) == entry->hotkey ) {
247           fputs(hotattrib, stdout);
248           putchar(*p++);
249           fputs(attrib, stdout);
250           width--;
251         }
252       } else {
253         putchar(*p++);
254         width--;
255       }
256     } else {
257       putchar(' ');
258       width--;
259     }
260   }
261 }
262
263 static void
264 draw_row(int y, int sel, int top, int sbtop, int sbbot)
265 {
266   int i = (y-4-VSHIFT)+top;
267
268   printf("\033[%d;%dH\1#1\016x\017%s ",
269          y, MARGIN+1+HSHIFT, (i == sel) ? "\1#5" : "\1#3");
270
271   if ( i >= nentries ) {
272     fputs(pad_line("", 0, WIDTH-2*MARGIN-4), stdout);
273   } else {
274     display_entry(&menu_entries[i],
275                   (i == sel) ? "\1#5" : "\1#3",
276                   (i == sel) ? "\1#6" : "\1#4",
277                   WIDTH-2*MARGIN-4);
278   }
279
280   if ( nentries <= MENU_ROWS ) {
281     printf(" \1#1\016x\017");
282   } else if ( sbtop > 0 ) {
283     if ( y >= sbtop && y <= sbbot )
284       printf(" \1#7\016a\017");
285     else
286       printf(" \1#1\016x\017");
287   } else {
288     putchar(' ');               /* Don't modify the scrollbar */
289   }
290 }
291
292 static int passwd_compare_sha1(const char *passwd, const char *entry)
293 {
294   const char *p;
295   SHA1_CTX ctx;
296   unsigned char sha1[20], pwdsha1[20];
297
298   if ( (p = strchr(passwd+3, '$')) ) {
299     SHA1Update(&ctx, (void *)passwd+3, p-(passwd+3));
300     p++;
301   } else {
302     p = passwd+3;               /* Assume no salt */
303   }
304
305   SHA1Init(&ctx);
306
307   SHA1Update(&ctx, (void *)entry, strlen(entry));
308   SHA1Final(sha1, &ctx);
309
310   memset(pwdsha1, 0, 20);
311   unbase64(pwdsha1, 20, p);
312
313   return !memcmp(sha1, pwdsha1, 20);
314 }
315
316 static int passwd_compare_md5(const char *passwd, const char *entry)
317 {
318   const char *crypted = crypt_md5(entry, passwd+3);
319   int len = strlen(crypted);
320
321   return !strncmp(crypted, passwd, len) &&
322     (passwd[len] == '\0' || passwd[len] == '$');
323 }
324
325 static int
326 passwd_compare(const char *passwd, const char *entry)
327 {
328   if ( passwd[0] != '$' )       /* Plaintext passwd, yuck! */
329     return !strcmp(entry, passwd);
330   else if ( !strncmp(passwd, "$4$", 3) )
331     return passwd_compare_sha1(passwd, entry);
332   else if ( !strncmp(passwd, "$1$", 3) )
333     return passwd_compare_md5(passwd, entry);
334   else
335     return 0;                   /* Invalid encryption algorithm */
336 }
337
338 static jmp_buf timeout_jump;
339
340 int mygetkey(clock_t timeout)
341 {
342   clock_t t0, t;
343   clock_t tto, to;
344   int key;
345
346   if ( !totaltimeout )
347     return get_key(stdin, timeout);
348
349   for (;;) {
350     tto = min(totaltimeout, INT_MAX);
351     to = timeout ? min(tto, timeout) : tto;
352
353     t0 = times(NULL);
354     key = get_key(stdin, to);
355     t = times(NULL) - t0;
356
357     if ( totaltimeout <= t )
358       longjmp(timeout_jump, 1);
359
360     totaltimeout -= t;
361
362     if ( key != KEY_NONE )
363       return key;
364
365     if ( timeout ) {
366       if ( timeout <= t )
367         return KEY_NONE;
368
369       timeout -= t;
370     }
371   }
372 }
373
374 static int
375 ask_passwd(const char *menu_entry)
376 {
377   char user_passwd[WIDTH], *p;
378   int done;
379   int key;
380   int x;
381
382   printf("\033[%d;%dH\2#11\016l", PASSWD_ROW, PASSWD_MARGIN+1);
383   for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
384     putchar('q');
385
386   printf("k\033[%d;%dHx", PASSWD_ROW+1, PASSWD_MARGIN+1);
387   for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
388     putchar(' ');
389
390   printf("x\033[%d;%dHm", PASSWD_ROW+2, PASSWD_MARGIN+1);
391   for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
392     putchar('q');
393
394   printf("j\017\033[%d;%dH\2#12 %s \033[%d;%dH\2#13",
395          PASSWD_ROW, (WIDTH-(strlen(messages[MSG_PASSPROMPT].msg)+2))/2,
396          messages[MSG_PASSPROMPT].msg, PASSWD_ROW+1, PASSWD_MARGIN+3);
397
398   /* Actually allow user to type a password, then compare to the SHA1 */
399   done = 0;
400   p = user_passwd;
401
402   while ( !done ) {
403     key = mygetkey(0);
404
405     switch ( key ) {
406     case KEY_ENTER:
407     case KEY_CTRL('J'):
408       done = 1;
409       break;
410
411     case KEY_ESC:
412     case KEY_CTRL('C'):
413       p = user_passwd;          /* No password entered */
414       done = 1;
415       break;
416
417     case KEY_BACKSPACE:
418     case KEY_DEL:
419     case KEY_DELETE:
420       if ( p > user_passwd ) {
421         printf("\b \b");
422         p--;
423       }
424       break;
425
426     case KEY_CTRL('U'):
427       while ( p > user_passwd ) {
428         printf("\b \b");
429         p--;
430       }
431       break;
432
433     default:
434       if ( key >= ' ' && key <= 0xFF &&
435            (p-user_passwd) < WIDTH-2*PASSWD_MARGIN-5 ) {
436         *p++ = key;
437         putchar('*');
438       }
439       break;
440     }
441   }
442
443   if ( p == user_passwd )
444     return 0;                   /* No password entered */
445
446   *p = '\0';
447
448   return (menu_master_passwd && passwd_compare(menu_master_passwd, user_passwd))
449     || (menu_entry && passwd_compare(menu_entry, user_passwd));
450 }
451
452
453 static void
454 draw_menu(int sel, int top, int edit_line)
455 {
456   int x, y;
457   int sbtop = 0, sbbot = 0;
458   const char *tabmsg;
459   int tabmsg_len;
460
461   if ( nentries > MENU_ROWS ) {
462     int sblen = MENU_ROWS*MENU_ROWS/nentries;
463     sbtop = (MENU_ROWS-sblen+1)*top/(nentries-MENU_ROWS+1);
464     sbbot = sbtop + sblen - 1;
465
466     sbtop += 4;  sbbot += 4;    /* Starting row of scrollbar */
467   }
468
469   printf("\033[%d;%dH\1#1\016l", VSHIFT+1, HSHIFT+MARGIN+1);
470   for ( x = 2+HSHIFT ; x <= (WIDTH-2*MARGIN-1)+HSHIFT ; x++ )
471     putchar('q');
472
473   printf("k\033[%d;%dH\1#1x\017\1#2 %s \1#1\016x",
474          VSHIFT+2,
475          HSHIFT+MARGIN+1,
476          pad_line(messages[MSG_TITLE].msg, 1, WIDTH-2*MARGIN-4));
477
478   printf("\033[%d;%dH\1#1t", VSHIFT+3, HSHIFT+MARGIN+1);
479   for ( x = 2+HSHIFT ; x <= (WIDTH-2*MARGIN-1)+HSHIFT ; x++ )
480     putchar('q');
481   fputs("u\017", stdout);
482
483   for ( y = 4+VSHIFT ; y < 4+VSHIFT+MENU_ROWS ; y++ )
484     draw_row(y, sel, top, sbtop, sbbot);
485
486   printf("\033[%d;%dH\1#1\016m", y, HSHIFT+MARGIN+1);
487   for ( x = 2+HSHIFT ; x <= (WIDTH-2*MARGIN-1)+HSHIFT ; x++ )
488     putchar('q');
489   fputs("j\017", stdout);
490
491   if ( edit_line && allowedit && !menu_master_passwd )
492     tabmsg = messages[MSG_TAB].msg;
493   else
494     tabmsg = messages[MSG_NOTAB].msg;
495
496   tabmsg_len = strlen(tabmsg);
497
498   printf("\1#8\033[%d;%dH%s",
499          TABMSG_ROW, 1+HSHIFT+((WIDTH-tabmsg_len)>>1), tabmsg);
500   printf("\1#0\033[%d;1H", END_ROW);
501 }
502
503 static void
504 clear_screen(void)
505 {
506   fputs("\033e\033%@\033)0\033(B\1#0\033[?25l\033[2J", stdout);
507 }
508
509 static void
510 display_help(const char *text)
511 {
512   int row;
513   const char *p;
514
515   if (!text) {
516     text = "";
517     printf("\1#0\033[%d;1H", HELPMSG_ROW);
518   } else {
519     printf("\2#16\033[%d;1H", HELPMSG_ROW);
520   }
521
522   for (p = text, row = HELPMSG_ROW; *p && row <= HELPMSGEND_ROW; p++) {
523     switch (*p) {
524     case '\r':
525     case '\f':
526     case '\v':
527     case '\033':
528       break;
529     case '\n':
530       printf("\033[K\033[%d;1H", ++row);
531       break;
532     default:
533       putchar(*p);
534     }
535   }
536
537   fputs("\033[K", stdout);
538
539   while (row <= HELPMSGEND_ROW) {
540     printf("\033[K\033[%d;1H", ++row);
541   }
542 }
543
544 static void show_fkey(int key)
545 {
546   int fkey;
547
548   while (1) {
549     switch (key) {
550     case KEY_F1:  fkey =  0;  break;
551     case KEY_F2:  fkey =  1;  break;
552     case KEY_F3:  fkey =  2;  break;
553     case KEY_F4:  fkey =  3;  break;
554     case KEY_F5:  fkey =  4;  break;
555     case KEY_F6:  fkey =  5;  break;
556     case KEY_F7:  fkey =  6;  break;
557     case KEY_F8:  fkey =  7;  break;
558     case KEY_F9:  fkey =  8;  break;
559     case KEY_F10: fkey =  9;  break;
560     case KEY_F11: fkey = 10;  break;
561     case KEY_F12: fkey = 11;  break;
562     default: fkey = -1; break;
563     }
564
565     if (fkey == -1)
566       break;
567
568     if (fkeyhelp[fkey].textname)
569       key = show_message_file(fkeyhelp[fkey].textname,
570                               fkeyhelp[fkey].background);
571     else
572       break;
573   }
574 }
575
576 static const char *
577 edit_cmdline(char *input, int top)
578 {
579   static char cmdline[MAX_CMDLINE_LEN];
580   int key, len, prev_len, cursor;
581   int redraw = 1;               /* We enter with the menu already drawn */
582
583   strncpy(cmdline, input, MAX_CMDLINE_LEN);
584   cmdline[MAX_CMDLINE_LEN-1] = '\0';
585
586   len = cursor = strlen(cmdline);
587   prev_len = 0;
588
589   for (;;) {
590     if ( redraw > 1 ) {
591       /* Clear and redraw whole screen */
592       /* Enable ASCII on G0 and DEC VT on G1; do it in this order
593          to avoid confusing the Linux console */
594       clear_screen();
595       draw_menu(-1, top, 1);
596       prev_len = 0;
597     }
598
599     if ( redraw > 0 ) {
600       /* Redraw the command line */
601       printf("\033[?25l\033[%d;1H\1#9> \2#10%s",
602              CMDLINE_ROW, pad_line(cmdline, 0, max(len, prev_len)));
603       printf("\2#10\033[%d;3H%s\033[?25h",
604              CMDLINE_ROW, pad_line(cmdline, 0, cursor));
605       prev_len = len;
606       redraw = 0;
607     }
608
609     key = mygetkey(0);
610
611     switch( key ) {
612     case KEY_CTRL('L'):
613       redraw = 2;
614       break;
615
616     case KEY_ENTER:
617     case KEY_CTRL('J'):
618       return cmdline;
619
620     case KEY_ESC:
621     case KEY_CTRL('C'):
622       return NULL;
623
624     case KEY_BACKSPACE:
625     case KEY_DEL:
626       if ( cursor ) {
627         memmove(cmdline+cursor-1, cmdline+cursor, len-cursor+1);
628         len--;
629         cursor--;
630         redraw = 1;
631       }
632       break;
633
634     case KEY_CTRL('D'):
635     case KEY_DELETE:
636       if ( cursor < len ) {
637         memmove(cmdline+cursor, cmdline+cursor+1, len-cursor);
638         len--;
639         redraw = 1;
640       }
641       break;
642
643     case KEY_CTRL('U'):
644       if ( len ) {
645         len = cursor = 0;
646         cmdline[len] = '\0';
647         redraw = 1;
648       }
649       break;
650
651     case KEY_CTRL('W'):
652       if ( cursor ) {
653         int prevcursor = cursor;
654
655         while ( cursor && my_isspace(cmdline[cursor-1]) )
656           cursor--;
657
658         while ( cursor && !my_isspace(cmdline[cursor-1]) )
659           cursor--;
660
661         memmove(cmdline+cursor, cmdline+prevcursor, len-prevcursor+1);
662         len -= (cursor-prevcursor);
663         redraw = 1;
664       }
665       break;
666
667     case KEY_LEFT:
668     case KEY_CTRL('B'):
669       if ( cursor ) {
670         cursor--;
671         redraw = 1;
672       }
673       break;
674
675     case KEY_RIGHT:
676     case KEY_CTRL('F'):
677       if ( cursor < len ) {
678         putchar(cmdline[cursor++]);
679       }
680       break;
681
682     case KEY_CTRL('K'):
683       if ( cursor < len ) {
684         cmdline[len = cursor] = '\0';
685         redraw = 1;
686       }
687       break;
688
689     case KEY_HOME:
690     case KEY_CTRL('A'):
691       if ( cursor ) {
692         cursor = 0;
693         redraw = 1;
694       }
695       break;
696
697     case KEY_END:
698     case KEY_CTRL('E'):
699       if ( cursor != len ) {
700         cursor = len;
701         redraw = 1;
702       }
703       break;
704
705     case KEY_F1:
706     case KEY_F2:
707     case KEY_F3:
708     case KEY_F4:
709     case KEY_F5:
710     case KEY_F6:
711     case KEY_F7:
712     case KEY_F8:
713     case KEY_F9:
714     case KEY_F10:
715     case KEY_F11:
716     case KEY_F12:
717       show_fkey(key);
718       redraw = 1;
719       break;
720
721     default:
722       if ( key >= ' ' && key <= 0xFF && len < MAX_CMDLINE_LEN-1 ) {
723         if ( cursor == len ) {
724           cmdline[len] = key;
725           cmdline[++len] = '\0';
726           cursor++;
727           putchar(key);
728           prev_len++;
729         } else {
730           memmove(cmdline+cursor+1, cmdline+cursor, len-cursor+1);
731           cmdline[cursor++] = key;
732           len++;
733           redraw = 1;
734         }
735       }
736       break;
737     }
738   }
739 }
740
741 static inline int
742 shift_is_held(void)
743 {
744   uint8_t shift_bits = *(uint8_t *)0x417;
745
746   return !!(shift_bits & 0x5d); /* Caps/Scroll/Alt/Shift */
747 }
748
749 static void
750 print_timeout_message(int tol, int row, const char *msg)
751 {
752   char buf[256];
753   int nc = 0, nnc;
754   const char *tp = msg;
755   char tc;
756   char *tq = buf;
757
758   while ((size_t)(tq-buf) < (sizeof buf-16) && (tc = *tp)) {
759     if (tc == '#') {
760       nnc = sprintf(tq, "\2#15%d\2#14", tol);
761       tq += nnc;
762       nc += nnc-8;              /* 8 formatting characters */
763     } else {
764       *tq++ = tc;
765       nc++;
766     }
767     tp++;
768   }
769   *tq = '\0';
770
771   printf("\033[%d;%dH\2#14 %s ", row, HSHIFT+1+((WIDTH-nc-2)>>1), buf);
772 }
773
774 static const char *
775 do_hidden_menu(void)
776 {
777   int key;
778   int timeout_left, this_timeout;
779
780   clear_screen();
781
782   if ( !setjmp(timeout_jump) ) {
783     timeout_left = timeout;
784
785     while (!timeout || timeout_left) {
786       int tol = timeout_left/CLK_TCK;
787
788       print_timeout_message(tol, HIDDEN_ROW, messages[MSG_AUTOBOOT].msg);
789
790       this_timeout = min(timeout_left, CLK_TCK);
791       key = mygetkey(this_timeout);
792
793       if (key != KEY_NONE)
794         return NULL;            /* Key pressed */
795
796       timeout_left -= this_timeout;
797     }
798   }
799
800   return menu_entries[defentry].cmdline; /* Default entry */
801 }
802
803 static const char *
804 run_menu(void)
805 {
806   int key;
807   int done = 0;
808   volatile int entry = defentry, prev_entry = -1;
809   int top = 0, prev_top = -1;
810   int clear = 1, to_clear;
811   const char *cmdline = NULL;
812   volatile clock_t key_timeout, timeout_left, this_timeout;
813
814   /* Note: for both key_timeout and timeout == 0 means no limit */
815   timeout_left = key_timeout = timeout;
816
817   /* If we're in shiftkey mode, exit immediately unless a shift key is pressed */
818   if ( shiftkey && !shift_is_held() ) {
819     return menu_entries[defentry].cmdline;
820   }
821
822   /* Handle hiddenmenu */
823   if ( hiddenmenu ) {
824     cmdline = do_hidden_menu();
825     if (cmdline)
826       return cmdline;
827
828     /* Otherwise display the menu now; the timeout has already been
829        cancelled, since the user pressed a key. */
830     hiddenmenu = 0;
831     key_timeout = 0;
832   }
833
834   /* Handle both local and global timeout */
835   if ( setjmp(timeout_jump) ) {
836     entry = defentry;
837
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));
842
843     draw_menu(ontimeout ? -1 : entry, top, 1);
844     cmdline = ontimeout ? ontimeout : menu_entries[entry].cmdline;
845     done = 1;
846   }
847
848   while ( !done ) {
849     if ( entry < 0 )
850       entry = 0;
851     else if ( entry >= nentries )
852       entry = nentries-1;
853
854     if ( top < 0 || top < entry-MENU_ROWS+1 )
855       top = max(0, entry-MENU_ROWS+1);
856     else if ( top > entry || top > max(0,nentries-MENU_ROWS) )
857       top = min(entry, max(0,nentries-MENU_ROWS));
858
859     /* Start with a clear screen */
860     if ( clear ) {
861       /* Clear and redraw whole screen */
862       /* Enable ASCII on G0 and DEC VT on G1; do it in this order
863          to avoid confusing the Linux console */
864       clear_screen();
865       clear = 0;
866       prev_entry = prev_top = -1;
867     }
868
869     if ( top != prev_top ) {
870       draw_menu(entry, top, 1);
871       display_help(menu_entries[entry].helptext);
872     } else if ( entry != prev_entry ) {
873       draw_row(prev_entry-top+4+VSHIFT, entry, top, 0, 0);
874       draw_row(entry-top+4+VSHIFT, entry, top, 0, 0);
875       display_help(menu_entries[entry].helptext);
876     }
877
878     prev_entry = entry;  prev_top = top;
879
880     /* Cursor movement cancels timeout */
881     if ( entry != defentry )
882       key_timeout = 0;
883
884     if ( key_timeout ) {
885       int tol = timeout_left/CLK_TCK;
886       print_timeout_message(tol, TIMEOUT_ROW, messages[MSG_AUTOBOOT].msg);
887       to_clear = 1;
888     } else {
889       to_clear = 0;
890     }
891
892     this_timeout = min(min(key_timeout, timeout_left), CLK_TCK);
893     key = mygetkey(this_timeout);
894
895     if ( key != KEY_NONE ) {
896       timeout_left = key_timeout;
897       if ( to_clear )
898         printf("\033[%d;1H\1#0\033[K", TIMEOUT_ROW);
899     }
900
901     switch ( key ) {
902     case KEY_NONE:              /* Timeout */
903       /* This is somewhat hacky, but this at least lets the user
904          know what's going on, and still deals with "phantom inputs"
905          e.g. on serial ports.
906
907          Warning: a timeout will boot the default entry without any
908          password! */
909       if ( key_timeout ) {
910         if ( timeout_left <= this_timeout )
911           longjmp(timeout_jump, 1);
912
913         timeout_left -= this_timeout;
914       }
915       break;
916
917     case KEY_CTRL('L'):
918       clear = 1;
919       break;
920
921     case KEY_ENTER:
922     case KEY_CTRL('J'):
923       key_timeout = 0;          /* Cancels timeout */
924       if ( menu_entries[entry].passwd ) {
925         clear = 1;
926         done = ask_passwd(menu_entries[entry].passwd);
927       } else {
928         done = 1;
929       }
930       cmdline = menu_entries[entry].cmdline;
931       break;
932
933     case KEY_UP:
934     case KEY_CTRL('P'):
935       if ( entry > 0 ) {
936         entry--;
937         if ( entry < top )
938           top -= MENU_ROWS;
939       }
940       break;
941
942     case KEY_DOWN:
943     case KEY_CTRL('N'):
944       if ( entry < nentries-1 ) {
945         entry++;
946         if ( entry >= top+MENU_ROWS )
947           top += MENU_ROWS;
948       }
949       break;
950
951     case KEY_PGUP:
952     case KEY_LEFT:
953     case KEY_CTRL('B'):
954     case '<':
955       entry -= MENU_ROWS;
956       top   -= MENU_ROWS;
957       break;
958
959     case KEY_PGDN:
960     case KEY_RIGHT:
961     case KEY_CTRL('F'):
962     case '>':
963     case ' ':
964       entry += MENU_ROWS;
965       top   += MENU_ROWS;
966       break;
967
968     case '-':
969       entry--;
970       top--;
971       break;
972
973     case '+':
974       entry++;
975       top++;
976       break;
977
978     case KEY_CTRL('A'):
979     case KEY_HOME:
980       top = entry = 0;
981       break;
982
983     case KEY_CTRL('E'):
984     case KEY_END:
985       entry = nentries - 1;
986       top = max(0, nentries-MENU_ROWS);
987       break;
988
989     case KEY_F1:
990     case KEY_F2:
991     case KEY_F3:
992     case KEY_F4:
993     case KEY_F5:
994     case KEY_F6:
995     case KEY_F7:
996     case KEY_F8:
997     case KEY_F9:
998     case KEY_F10:
999     case KEY_F11:
1000     case KEY_F12:
1001       show_fkey(key);
1002       clear = 1;
1003       break;
1004
1005     case KEY_TAB:
1006       if ( allowedit ) {
1007         int ok = 1;
1008
1009         key_timeout = 0;        /* Cancels timeout */
1010         draw_row(entry-top+4+VSHIFT, -1, top, 0, 0);
1011
1012         if ( menu_master_passwd ) {
1013           ok = ask_passwd(NULL);
1014           clear_screen();
1015           draw_menu(-1, top, 0);
1016         } else {
1017           /* Erase [Tab] message and help text*/
1018           printf("\033[%d;1H\1#0\033[K", TABMSG_ROW);
1019           display_help(NULL);
1020         }
1021
1022         if ( ok ) {
1023           cmdline = edit_cmdline(menu_entries[entry].cmdline, top);
1024           done = !!cmdline;
1025           clear = 1;            /* In case we hit [Esc] and done is null */
1026         } else {
1027           draw_row(entry-top+4+VSHIFT, entry, top, 0, 0);
1028         }
1029       }
1030       break;
1031     case KEY_CTRL('C'):         /* Ctrl-C */
1032     case KEY_ESC:               /* Esc */
1033       if ( allowedit ) {
1034         done = 1;
1035         clear = 1;
1036         key_timeout = 0;
1037
1038         draw_row(entry-top+4+VSHIFT, -1, top, 0, 0);
1039
1040         if ( menu_master_passwd )
1041           done = ask_passwd(NULL);
1042       }
1043       break;
1044     default:
1045       if ( key > 0 && key < 0xFF ) {
1046         key &= ~0x20;           /* Upper case */
1047         if ( menu_hotkeys[key] ) {
1048           key_timeout = 0;
1049           entry = menu_hotkeys[key] - menu_entries;
1050           /* Should we commit at this point? */
1051         }
1052       }
1053       break;
1054     }
1055   }
1056
1057   printf("\033[?25h");          /* Show cursor */
1058
1059   /* Return the label name so localboot and ipappend work */
1060   return cmdline;
1061 }
1062
1063
1064 static void
1065 execute(const char *cmdline, enum kernel_type type)
1066 {
1067   com32sys_t ireg;
1068   const char *p, **pp;
1069   char *q = __com32.cs_bounce;
1070   const char *kernel, *args;
1071
1072   memset(&ireg, 0, sizeof ireg);
1073
1074   kernel = q;
1075   p = cmdline;
1076   while ( *p && !my_isspace(*p) ) {
1077     *q++ = *p++;
1078   }
1079   *q++ = '\0';
1080
1081   args = q;
1082   while ( *p && my_isspace(*p) )
1083     p++;
1084
1085   strcpy(q, p);
1086
1087   if (kernel[0] == '.' && type == KT_NONE) {
1088     /* It might be a type specifier */
1089     enum kernel_type type = KT_NONE;
1090     for (pp = kernel_types; *pp; pp++, type++) {
1091       if (!strcmp(kernel+1, *pp)) {
1092         execute(p, type);       /* Strip the type specifier and retry */
1093       }
1094     }
1095   }
1096
1097   if (type == KT_LOCALBOOT) {
1098     ireg.eax.w[0] = 0x0014;     /* Local boot */
1099     ireg.edx.w[0] = strtoul(kernel, NULL, 0);
1100   } else {
1101     if (type < KT_KERNEL)
1102       type = KT_KERNEL;
1103
1104     ireg.eax.w[0] = 0x0016;     /* Run kernel image */
1105     ireg.esi.w[0] = OFFS(kernel);
1106     ireg.ds       = SEG(kernel);
1107     ireg.ebx.w[0] = OFFS(args);
1108     ireg.es       = SEG(args);
1109     ireg.edx.l    = type-KT_KERNEL;
1110     /* ireg.ecx.l    = 0; */            /* We do ipappend "manually" */
1111   }
1112
1113   __intcall(0x22, &ireg, NULL);
1114
1115   /* If this returns, something went bad; return to menu */
1116 }
1117
1118 int menu_main(int argc, char *argv[])
1119 {
1120   const char *cmdline;
1121   int rows, cols;
1122   int i;
1123
1124   (void)argc;
1125
1126   console_prepare();
1127
1128   install_default_color_table();
1129   if (getscreensize(1, &rows, &cols)) {
1130     /* Unknown screen size? */
1131     rows = 24;
1132     cols = 80;
1133   }
1134
1135   WIDTH = cols;
1136   parse_configs(argv+1);
1137
1138   /* If anyone has specified negative parameters, consider them
1139      relative to the bottom row of the screen. */
1140   for (i = 0; mparm[i].name; i++)
1141     if (mparm[i].value < 0)
1142       mparm[i].value = max(mparm[i].value+rows, 0);
1143
1144   draw_background(menu_background);
1145
1146   if ( !nentries ) {
1147     fputs("No LABEL entries found in configuration file!\n", stdout);
1148     return 1;                   /* Error! */
1149   }
1150
1151   for(;;) {
1152     cmdline = run_menu();
1153
1154     printf("\033[?25h\033[%d;1H\033[0m", END_ROW);
1155     console_cleanup();
1156
1157     if ( cmdline ) {
1158       execute(cmdline, KT_NONE);
1159       if ( onerror )
1160         execute(onerror, KT_NONE);
1161     } else {
1162       return 0;                 /* Exit */
1163     }
1164
1165     console_prepare();          /* If we're looping... */
1166   }
1167 }