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