blob: 519c200e1292671eda0712bea263538ec56438e2 [file] [log] [blame]
/*
* Copyright (C) 2009 Michael Brown <mbrown@fensystems.co.uk>.
*
* 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; either version 2 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
FILE_LICENCE ( GPL2_OR_LATER );
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <byteswap.h>
#include <curses.h>
#include <console.h>
#include <gpxe/dhcp.h>
#include <gpxe/keys.h>
#include <gpxe/timer.h>
#include <gpxe/process.h>
#include <usr/dhcpmgmt.h>
#include <usr/autoboot.h>
/** @file
*
* PXE Boot Menus
*
*/
/* Colour pairs */
#define CPAIR_NORMAL 1
#define CPAIR_SELECT 2
/** A PXE boot menu item */
struct pxe_menu_item {
/** Boot Server type */
unsigned int type;
/** Description */
char *desc;
};
/**
* A PXE boot menu
*
* This structure encapsulates the menu information provided via DHCP
* options.
*/
struct pxe_menu {
/** Prompt string (optional) */
const char *prompt;
/** Timeout (in seconds)
*
* Negative indicates no timeout (i.e. wait indefinitely)
*/
int timeout;
/** Number of menu items */
unsigned int num_items;
/** Selected menu item */
unsigned int selection;
/** Menu items */
struct pxe_menu_item items[0];
};
/**
* Parse and allocate PXE boot menu
*
* @v menu PXE boot menu to fill in
* @ret rc Return status code
*
* It is the callers responsibility to eventually free the allocated
* boot menu.
*/
static int pxe_menu_parse ( struct pxe_menu **menu ) {
struct setting pxe_boot_menu_prompt_setting =
{ .tag = DHCP_PXE_BOOT_MENU_PROMPT };
struct setting pxe_boot_menu_setting =
{ .tag = DHCP_PXE_BOOT_MENU };
uint8_t raw_menu[256];
int raw_prompt_len;
int raw_menu_len;
struct dhcp_pxe_boot_menu *raw_menu_item;
struct dhcp_pxe_boot_menu_prompt *raw_menu_prompt;
void *raw_menu_end;
unsigned int num_menu_items;
unsigned int i;
int rc;
/* Fetch raw menu */
memset ( raw_menu, 0, sizeof ( raw_menu ) );
if ( ( raw_menu_len = fetch_setting ( NULL, &pxe_boot_menu_setting,
raw_menu,
sizeof ( raw_menu ) ) ) < 0 ) {
rc = raw_menu_len;
DBG ( "Could not retrieve raw PXE boot menu: %s\n",
strerror ( rc ) );
return rc;
}
if ( raw_menu_len >= ( int ) sizeof ( raw_menu ) ) {
DBG ( "Raw PXE boot menu too large for buffer\n" );
return -ENOSPC;
}
raw_menu_end = ( raw_menu + raw_menu_len );
/* Fetch raw prompt length */
raw_prompt_len = fetch_setting_len ( NULL,
&pxe_boot_menu_prompt_setting );
if ( raw_prompt_len < 0 )
raw_prompt_len = 0;
/* Count menu items */
num_menu_items = 0;
raw_menu_item = ( ( void * ) raw_menu );
while ( 1 ) {
if ( ( ( ( void * ) raw_menu_item ) +
sizeof ( *raw_menu_item ) ) > raw_menu_end )
break;
if ( ( ( ( void * ) raw_menu_item ) +
sizeof ( *raw_menu_item ) +
raw_menu_item->desc_len ) > raw_menu_end )
break;
num_menu_items++;
raw_menu_item = ( ( ( void * ) raw_menu_item ) +
sizeof ( *raw_menu_item ) +
raw_menu_item->desc_len );
}
/* Allocate space for parsed menu */
*menu = zalloc ( sizeof ( **menu ) +
( num_menu_items * sizeof ( (*menu)->items[0] ) ) +
raw_menu_len + 1 /* NUL */ +
raw_prompt_len + 1 /* NUL */ );
if ( ! *menu ) {
DBG ( "Could not allocate PXE boot menu\n" );
return -ENOMEM;
}
/* Fill in parsed menu */
(*menu)->num_items = num_menu_items;
raw_menu_item = ( ( ( void * ) (*menu) ) + sizeof ( **menu ) +
( num_menu_items * sizeof ( (*menu)->items[0] ) ) );
memcpy ( raw_menu_item, raw_menu, raw_menu_len );
for ( i = 0 ; i < num_menu_items ; i++ ) {
(*menu)->items[i].type = le16_to_cpu ( raw_menu_item->type );
(*menu)->items[i].desc = raw_menu_item->desc;
/* Set type to 0; this ensures that the description
* for the previous menu item is NUL-terminated.
* (Final item is NUL-terminated anyway.)
*/
raw_menu_item->type = 0;
raw_menu_item = ( ( ( void * ) raw_menu_item ) +
sizeof ( *raw_menu_item ) +
raw_menu_item->desc_len );
}
if ( raw_prompt_len ) {
raw_menu_prompt = ( ( ( void * ) raw_menu_item ) +
1 /* NUL */ );
fetch_setting ( NULL, &pxe_boot_menu_prompt_setting,
raw_menu_prompt, raw_prompt_len );
(*menu)->timeout =
( ( raw_menu_prompt->timeout == 0xff ) ?
-1 : raw_menu_prompt->timeout );
(*menu)->prompt = raw_menu_prompt->prompt;
} else {
(*menu)->timeout = -1;
}
return 0;
}
/**
* Draw PXE boot menu item
*
* @v menu PXE boot menu
* @v index Index of item to draw
* @v selected Item is selected
*/
static void pxe_menu_draw_item ( struct pxe_menu *menu,
unsigned int index, int selected ) {
char buf[COLS+1];
size_t len;
unsigned int row;
/* Prepare space-padded row content */
len = snprintf ( buf, sizeof ( buf ), " %c. %s",
( 'A' + index ), menu->items[index].desc );
while ( len < ( sizeof ( buf ) - 1 ) )
buf[len++] = ' ';
buf[ sizeof ( buf ) - 1 ] = '\0';
/* Draw row */
row = ( LINES - menu->num_items + index );
color_set ( ( selected ? CPAIR_SELECT : CPAIR_NORMAL ), NULL );
mvprintw ( row, 0, "%s", buf );
move ( row, 1 );
}
/**
* Make selection from PXE boot menu
*
* @v menu PXE boot menu
* @ret rc Return status code
*/
static int pxe_menu_select ( struct pxe_menu *menu ) {
int key;
unsigned int key_selection;
unsigned int i;
int rc = 0;
/* Initialise UI */
initscr();
start_color();
init_pair ( CPAIR_NORMAL, COLOR_WHITE, COLOR_BLACK );
init_pair ( CPAIR_SELECT, COLOR_BLACK, COLOR_WHITE );
color_set ( CPAIR_NORMAL, NULL );
/* Draw initial menu */
for ( i = 0 ; i < menu->num_items ; i++ )
printf ( "\n" );
for ( i = 0 ; i < menu->num_items ; i++ )
pxe_menu_draw_item ( menu, ( menu->num_items - i - 1 ), 0 );
while ( 1 ) {
/* Highlight currently selected item */
pxe_menu_draw_item ( menu, menu->selection, 1 );
/* Wait for keyboard input */
while ( ! iskey() )
step();
key = getkey();
/* Unhighlight currently selected item */
pxe_menu_draw_item ( menu, menu->selection, 0 );
/* Act upon key */
if ( ( key == CR ) || ( key == LF ) ) {
pxe_menu_draw_item ( menu, menu->selection, 1 );
break;
} else if ( ( key == CTRL_C ) || ( key == ESC ) ) {
rc = -ECANCELED;
break;
} else if ( key == KEY_UP ) {
if ( menu->selection > 0 )
menu->selection--;
} else if ( key == KEY_DOWN ) {
if ( menu->selection < ( menu->num_items - 1 ) )
menu->selection++;
} else if ( ( key < KEY_MIN ) &&
( ( key_selection = ( toupper ( key ) - 'A' ) )
< menu->num_items ) ) {
menu->selection = key_selection;
pxe_menu_draw_item ( menu, menu->selection, 1 );
break;
}
}
/* Shut down UI */
endwin();
return rc;
}
/**
* Prompt for (and make selection from) PXE boot menu
*
* @v menu PXE boot menu
* @ret rc Return status code
*/
static int pxe_menu_prompt_and_select ( struct pxe_menu *menu ) {
unsigned long start = currticks();
unsigned long now;
unsigned long elapsed;
size_t len = 0;
int key;
int rc = 0;
/* Display menu immediately, if specified to do so */
if ( menu->timeout < 0 ) {
if ( menu->prompt )
printf ( "%s\n", menu->prompt );
return pxe_menu_select ( menu );
}
/* Display prompt, if specified */
if ( menu->prompt )
printf ( "%s", menu->prompt );
/* Wait for timeout, if specified */
while ( menu->timeout > 0 ) {
if ( ! len )
len = printf ( " (%d)", menu->timeout );
if ( iskey() ) {
key = getkey();
if ( key == KEY_F8 ) {
/* Display menu */
printf ( "\n" );
return pxe_menu_select ( menu );
} else if ( ( key == CTRL_C ) || ( key == ESC ) ) {
/* Abort */
rc = -ECANCELED;
break;
} else {
/* Stop waiting */
break;
}
}
now = currticks();
elapsed = ( now - start );
if ( elapsed >= TICKS_PER_SEC ) {
menu->timeout -= 1;
do {
printf ( "\b \b" );
} while ( --len );
start = now;
}
}
/* Return with default option selected */
printf ( "\n" );
return rc;
}
/**
* Boot using PXE boot menu
*
* @ret rc Return status code
*
* Note that a success return status indicates that a PXE boot menu
* item has been selected, and that the DHCP session should perform a
* boot server request/ack.
*/
int pxe_menu_boot ( struct net_device *netdev ) {
struct pxe_menu *menu;
unsigned int pxe_type;
struct settings *pxebs_settings;
struct in_addr next_server;
char filename[256];
int rc;
/* Parse and allocate boot menu */
if ( ( rc = pxe_menu_parse ( &menu ) ) != 0 )
return rc;
/* Make selection from boot menu */
if ( ( rc = pxe_menu_prompt_and_select ( menu ) ) != 0 ) {
free ( menu );
return rc;
}
pxe_type = menu->items[menu->selection].type;
/* Free boot menu */
free ( menu );
/* Return immediately if local boot selected */
if ( ! pxe_type )
return 0;
/* Attempt PXE Boot Server Discovery */
if ( ( rc = pxebs ( netdev, pxe_type ) ) != 0 )
return rc;
/* Attempt boot */
pxebs_settings = find_settings ( PXEBS_SETTINGS_NAME );
assert ( pxebs_settings );
fetch_ipv4_setting ( pxebs_settings, &next_server_setting,
&next_server );
fetch_string_setting ( pxebs_settings, &filename_setting,
filename, sizeof ( filename ) );
return boot_next_server_and_filename ( next_server, filename );
}