This repository has been archived on 2023-08-20. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files

632 lines
14 KiB
C

#ident "$Id: menu.c,v 1.19 2005/04/06 09:53:28 hpa Exp $"
/* ----------------------------------------------------------------------- *
*
* Copyright 2004-2005 H. Peter Anvin - All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, Inc., 53 Temple Place Ste 330,
* Boston MA 02111-1307, USA; either version 2 of the License, or
* (at your option) any later version; incorporated herein by reference.
*
* ----------------------------------------------------------------------- */
/*
* menu.c
*
* Simple menu system which displays a list and allows the user to select
* a command line and/or edit it.
*/
#define _GNU_SOURCE /* Needed for asprintf() on Linux */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <consoles.h>
#include <getkey.h>
#include <minmax.h>
#include <time.h>
#include <sys/times.h>
#include <unistd.h>
#include <sha1.h>
#include <base64.h>
#ifdef __COM32__
#include <com32.h>
#endif
#include "menu.h"
#ifndef CLK_TCK
# define CLK_TCK sysconf(_SC_CLK_TCK)
#endif
struct menu_attrib {
const char *border; /* Border area */
const char *title; /* Title bar */
const char *unsel; /* Unselected menu item */
const char *hotkey; /* Unselected hotkey */
const char *sel; /* Selected */
const char *hotsel; /* Selected hotkey */
const char *scrollbar; /* Scroll bar */
const char *tabmsg; /* Press [Tab] message */
const char *cmdmark; /* Command line marker */
const char *cmdline; /* Command line */
const char *screen; /* Rest of the screen */
const char *pwdborder; /* Password box border */
const char *pwdheader; /* Password box header */
const char *pwdentry; /* Password box contents */
};
static const struct menu_attrib default_attrib = {
.border = "\033[0;30;44m",
.title = "\033[1;36;44m",
.unsel = "\033[0;37;44m",
.hotkey = "\033[1;37;44m",
.sel = "\033[0;7;37;40m",
.hotsel = "\033[1;7;37;40m",
.scrollbar = "\033[0;30;44m",
.tabmsg = "\033[0;31;40m",
.cmdmark = "\033[1;36;40m",
.cmdline = "\033[0;37;40m",
.screen = "\033[0;37;40m",
.pwdborder = "\033[0;30;47m",
.pwdheader = "\033[0;31;47m",
.pwdentry = "\033[0;30;47m",
};
static const struct menu_attrib *menu_attrib = &default_attrib;
#define WIDTH 80
#define MARGIN 10
#define PASSWD_MARGIN 3
#define MENU_ROWS 12
#define TABMSG_ROW 18
#define CMDLINE_ROW 20
#define END_ROW 24
#define PASSWD_ROW 11
static char *
pad_line(const char *text, int align, int width)
{
static char buffer[256];
int n, p;
if ( width >= (int) sizeof buffer )
return NULL; /* Can't do it */
n = strlen(text);
if ( n >= width )
n = width;
memset(buffer, ' ', width);
buffer[width] = 0;
p = ((width-n)*align)>>1;
memcpy(buffer+p, text, n);
return buffer;
}
/* Display an entry, with possible hotkey highlight. Assumes
that the current attribute is the non-hotkey one, and will
guarantee that as an exit condition as well. */
static void
display_entry(const struct menu_entry *entry, const char *attrib,
const char *hotattrib, int width)
{
const char *p = entry->displayname;
while ( width ) {
if ( *p ) {
if ( *p == '^' ) {
p++;
if ( *p && ((unsigned char)*p & ~0x20) == entry->hotkey ) {
fputs(hotattrib, stdout);
putchar(*p++);
fputs(attrib, stdout);
width--;
}
} else {
putchar(*p++);
width--;
}
} else {
putchar(' ');
width--;
}
}
}
static void
draw_row(int y, int sel, int top, int sbtop, int sbbot)
{
int i = (y-4)+top;
printf("\033[%d;%dH%s\016x\017%s ",
y, MARGIN+1, menu_attrib->border,
(i == sel) ? menu_attrib->sel : menu_attrib->unsel);
if ( i >= nentries ) {
fputs(pad_line("", 0, WIDTH-2*MARGIN-4), stdout);
} else {
display_entry(&menu_entries[i],
(i == sel) ? menu_attrib->sel : menu_attrib->unsel,
(i == sel) ? menu_attrib->hotsel : menu_attrib->hotkey,
WIDTH-2*MARGIN-4);
}
if ( nentries <= MENU_ROWS ) {
printf(" %s\016x\017", menu_attrib->border);
} else if ( sbtop > 0 ) {
if ( y >= sbtop && y <= sbbot )
printf(" %s\016a\017", menu_attrib->scrollbar);
else
printf(" %s\016x\017", menu_attrib->border);
} else {
putchar(' '); /* Don't modify the scrollbar */
}
}
static int
passwd_compare(const char *passwd, const char *entry)
{
const char *p;
SHA1_CTX ctx;
unsigned char sha1[20], pwdsha1[20];
if ( passwd[0] != '$' ) /* Plaintext passwd, yuck! */
return !strcmp(entry, passwd);
if ( strncmp(passwd, "$4$", 3) )
return 0; /* Only SHA-1 passwds supported */
SHA1Init(&ctx);
if ( (p = strchr(passwd+3, '$')) ) {
SHA1Update(&ctx, passwd+3, p-(passwd+3));
p++;
} else {
p = passwd+3; /* Assume no salt */
}
SHA1Update(&ctx, entry, strlen(entry));
SHA1Final(sha1, &ctx);
memset(pwdsha1, 0, 20);
unbase64(pwdsha1, 20, p);
return !memcmp(sha1, pwdsha1, 20);
}
static int
ask_passwd(const char *menu_entry)
{
static const char title[] = "Password required";
char user_passwd[WIDTH], *p;
int done;
int key;
int x;
printf("\033[%d;%dH%s\016l", PASSWD_ROW, PASSWD_MARGIN+1,
menu_attrib->pwdborder);
for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
putchar('q');
printf("k\033[%d;%dHx", PASSWD_ROW+1, PASSWD_MARGIN+1);
for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
putchar(' ');
printf("x\033[%d;%dHm", PASSWD_ROW+2, PASSWD_MARGIN+1);
for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
putchar('q');
printf("j\017\033[%d;%dH%s %s \033[%d;%dH%s",
PASSWD_ROW, (WIDTH-((int)sizeof(title)+1))/2,
menu_attrib->pwdheader, title,
PASSWD_ROW+1, PASSWD_MARGIN+3, menu_attrib->pwdentry);
/* Actually allow user to type a password, then compare to the SHA1 */
done = 0;
p = user_passwd;
while ( !done ) {
key = get_key(stdin, 0);
switch ( key ) {
case KEY_ENTER:
case KEY_CTRL('J'):
done = 1;
break;
case KEY_ESC:
case KEY_CTRL('C'):
p = user_passwd; /* No password entered */
done = 1;
break;
case KEY_BACKSPACE:
case KEY_DEL:
case KEY_DELETE:
if ( p > user_passwd ) {
printf("\b \b");
p--;
}
break;
case KEY_CTRL('U'):
while ( p > user_passwd ) {
printf("\b \b");
p--;
}
break;
default:
if ( key >= ' ' && key <= 0xFF &&
(p-user_passwd) < WIDTH-2*PASSWD_MARGIN-5 ) {
*p++ = key;
putchar('*');
}
break;
}
}
if ( p == user_passwd )
return 0; /* No password entered */
*p = '\0';
return (menu_master_passwd && passwd_compare(menu_master_passwd, user_passwd))
|| (menu_entry && passwd_compare(menu_entry, user_passwd));
}
static void
draw_menu(int sel, int top)
{
int x, y;
int sbtop = 0, sbbot = 0;
if ( nentries > MENU_ROWS ) {
int sblen = MENU_ROWS*MENU_ROWS/nentries;
sbtop = (MENU_ROWS-sblen+1)*top/(nentries-MENU_ROWS+1);
sbbot = sbtop + sblen - 1;
sbtop += 4; sbbot += 4; /* Starting row of scrollbar */
}
printf("\033[1;%dH%s\016l", MARGIN+1, menu_attrib->border);
for ( x = 2 ; x <= WIDTH-2*MARGIN-1 ; x++ )
putchar('q');
printf("k\033[2;%dH%sx\017%s %s %s\016x",
MARGIN+1,
menu_attrib->border,
menu_attrib->title,
pad_line(menu_title, 1, WIDTH-2*MARGIN-4),
menu_attrib->border);
printf("\033[3;%dH%st", MARGIN+1, menu_attrib->border);
for ( x = 2 ; x <= WIDTH-2*MARGIN-1 ; x++ )
putchar('q');
fputs("u\017", stdout);
for ( y = 4 ; y < 4+MENU_ROWS ; y++ )
draw_row(y, sel, top, sbtop, sbbot);
printf("\033[%d;%dH%s\016m", y, MARGIN+1, menu_attrib->border);
for ( x = 2 ; x <= WIDTH-2*MARGIN-1 ; x++ )
putchar('q');
fputs("j\017", stdout);
if ( allowedit && !menu_master_passwd )
printf("%s\033[%d;1H%s", menu_attrib->tabmsg, TABMSG_ROW,
pad_line("Press [Tab] to edit options", 1, WIDTH));
printf("%s\033[%d;1H", menu_attrib->screen, END_ROW);
}
static const char *
edit_cmdline(char *input, int top)
{
static char cmdline[MAX_CMDLINE_LEN];
int key, len;
int redraw = 1; /* We enter with the menu already drawn */
strncpy(cmdline, input, MAX_CMDLINE_LEN);
cmdline[MAX_CMDLINE_LEN-1] = '\0';
len = strlen(cmdline);
for (;;) {
if ( redraw > 1 ) {
/* Clear and redraw whole screen */
/* Enable ASCII on G0 and DEC VT on G1; do it in this order
to avoid confusing the Linux console */
printf("\033e\033%%@\033)0\033(B%s\033[?25l\033[2J", menu_attrib->screen);
draw_menu(-1, top);
}
if ( redraw > 0 ) {
/* Redraw the command line */
printf("\033[?25h\033[%d;1H%s> %s%s",
CMDLINE_ROW, menu_attrib->cmdmark,
menu_attrib->cmdline, pad_line(cmdline, 0, MAX_CMDLINE_LEN-1));
printf("%s\033[%d;3H%s",
menu_attrib->cmdline, CMDLINE_ROW, cmdline);
redraw = 0;
}
key = get_key(stdin, 0);
/* FIX: should handle arrow keys and edit-in-middle */
switch( key ) {
case KEY_CTRL('L'):
redraw = 2;
break;
case KEY_ENTER:
case KEY_CTRL('J'):
return cmdline;
case KEY_ESC:
case KEY_CTRL('C'):
return NULL;
case KEY_BACKSPACE:
case KEY_DEL:
case KEY_DELETE:
if ( len ) {
cmdline[--len] = '\0';
redraw = 1;
}
break;
case KEY_CTRL('U'):
if ( len ) {
len = 0;
cmdline[len] = '\0';
redraw = 1;
}
break;
case KEY_CTRL('W'):
if ( len ) {
int wasbs = (cmdline[len-1] <= ' ');
while ( len && (cmdline[len-1] <= ' ' || !wasbs) ) {
len--;
wasbs = wasbs || (cmdline[len-1] <= ' ');
}
cmdline[len] = '\0';
redraw = 1;
}
break;
default:
if ( key >= ' ' && key <= 0xFF && len < MAX_CMDLINE_LEN-1 ) {
cmdline[len] = key;
cmdline[++len] = '\0';
putchar(key);
}
break;
}
}
}
static void
clear_screen(void)
{
printf("\033e\033%%@\033)0\033(B%s\033[?25l\033[2J", menu_attrib->screen);
}
static const char *
run_menu(void)
{
int key;
int done = 0;
int entry = defentry, prev_entry = -1;
int top = 0, prev_top = -1;
int clear = 1;
const char *cmdline = NULL;
clock_t key_timeout;
/* Convert timeout from deciseconds to clock ticks */
/* Note: for both key_timeout and timeout == 0 means no limit */
key_timeout = (clock_t)(CLK_TCK*timeout+9)/10;
while ( !done ) {
if ( entry < 0 )
entry = 0;
else if ( entry >= nentries )
entry = nentries-1;
if ( top < 0 || top < entry-MENU_ROWS+1 )
top = max(0, entry-MENU_ROWS+1);
else if ( top > entry || top > max(0,nentries-MENU_ROWS) )
top = min(entry, max(0,nentries-MENU_ROWS));
/* Start with a clear screen */
if ( clear ) {
/* Clear and redraw whole screen */
/* Enable ASCII on G0 and DEC VT on G1; do it in this order
to avoid confusing the Linux console */
clear_screen();
clear = 0;
prev_entry = prev_top = -1;
}
if ( top != prev_top ) {
draw_menu(entry, top);
} else if ( entry != prev_entry ) {
draw_row(prev_entry-top+4, entry, top, 0, 0);
draw_row(entry-top+4, entry, top, 0, 0);
}
prev_entry = entry; prev_top = top;
key = get_key(stdin, key_timeout);
switch ( key ) {
case KEY_NONE: /* Timeout */
/* This is somewhat hacky, but this at least lets the user
know what's going on, and still deals with "phantom inputs"
e.g. on serial ports.
Warning: a timeout will boot the default entry without any
password! */
if ( entry != defentry )
entry = defentry;
else {
cmdline = menu_entries[defentry].label;
done = 1;
}
break;
case KEY_CTRL('L'):
clear = 1;
break;
case KEY_ENTER:
case KEY_CTRL('J'):
if ( menu_entries[entry].passwd ) {
clear = 1;
done = ask_passwd(menu_entries[entry].passwd);
} else {
done = 1;
}
cmdline = menu_entries[entry].label;
break;
case 'P':
case 'p':
case KEY_UP:
if ( entry > 0 ) {
entry--;
if ( entry < top )
top -= MENU_ROWS;
}
break;
case 'N':
case 'n':
case KEY_DOWN:
if ( entry < nentries-1 ) {
entry++;
if ( entry >= top+MENU_ROWS )
top += MENU_ROWS;
}
break;
case KEY_CTRL('P'):
case KEY_PGUP:
case KEY_LEFT:
entry -= MENU_ROWS;
top -= MENU_ROWS;
break;
case KEY_CTRL('N'):
case KEY_PGDN:
case KEY_RIGHT:
case ' ':
entry += MENU_ROWS;
top += MENU_ROWS;
break;
case '-':
entry--;
top--;
break;
case '+':
entry++;
top++;
break;
case KEY_CTRL('A'):
case KEY_HOME:
top = entry = 0;
break;
case KEY_CTRL('E'):
case KEY_END:
entry = nentries - 1;
top = max(0, nentries-MENU_ROWS);
break;
case KEY_TAB:
if ( allowedit ) {
int ok = 1;
draw_row(entry-top+4, -1, top, 0, 0);
if ( menu_master_passwd ) {
ok = ask_passwd(NULL);
clear_screen();
draw_menu(-1, top);
}
if ( ok ) {
cmdline = edit_cmdline(menu_entries[entry].cmdline, top);
done = !!cmdline;
clear = 1; /* In case we hit [Esc] and done is null */
} else {
draw_row(entry-top+4, entry, top, 0, 0);
}
}
break;
case KEY_CTRL('C'): /* Ctrl-C */
case KEY_ESC: /* Esc */
if ( allowedit ) {
done = 1;
clear = 1;
draw_row(entry-top+4, -1, top, 0, 0);
if ( menu_master_passwd )
done = ask_passwd(NULL);
}
break;
default:
if ( key > 0 && key < 0xFF ) {
key &= ~0x20; /* Upper case */
if ( menu_hotkeys[key] ) {
entry = menu_hotkeys[key] - menu_entries;
/* Should we commit at this point? */
}
}
break;
}
}
printf("\033[?25h"); /* Show cursor */
/* Return the label name so localboot and ipappend work */
return cmdline;
}
static void __attribute__((noreturn))
execute(const char *cmdline)
{
#ifdef __COM32__
static com32sys_t ireg;
strcpy(__com32.cs_bounce, cmdline);
ireg.eax.w[0] = 0x0003; /* Run command */
ireg.ebx.w[0] = OFFS(__com32.cs_bounce);
ireg.es = SEG(__com32.cs_bounce);
__intcall(0x22, &ireg, NULL);
exit(255); /* Shouldn't return */
#else
/* For testing... */
printf("\n>>> %s\n", cmdline);
exit(0);
#endif
}
int main(int argc, char *argv[])
{
const char *cmdline = NULL;
int err = 0;
(void)argc;
console_ansi_raw();
parse_config(argv[1]);
if ( !nentries ) {
fputs("No LABEL entries found in configuration file!\n", stdout);
err = 1;
} else {
cmdline = run_menu();
}
printf("\033[?25h\033[%d;1H\033[0m", END_ROW);
if ( cmdline )
execute(cmdline);
else
return err;
}