david/ipxe
david
/
ipxe
Archived
1
0
Fork 0

[dhcp] Split PXE menuing code out of dhcp.c

The DHCP client code now implements only the mechanism of the DHCP and
PXE Boot Server protocols.  Boot Server Discovery can be initiated
manually using the "pxebs" command.  The menuing code is separated out
into a user-level function on a par with boot_root_path(), and is
entered in preference to a normal filename boot if the DHCP vendor
class is "PXEClient" and the PXE boot menu option exists.
This commit is contained in:
Michael Brown 2009-01-31 07:36:05 +00:00
parent d2b0081740
commit e65afc4b10
17 changed files with 1379 additions and 973 deletions

View File

@ -82,7 +82,7 @@ struct pxe_dhcp_packet_creator {
static struct pxe_dhcp_packet_creator pxe_dhcp_packet_creators[] = {
[CACHED_INFO_DHCPDISCOVER] = { create_fakedhcpdiscover },
[CACHED_INFO_DHCPACK] = { create_fakedhcpack },
[CACHED_INFO_BINL] = { create_fakeproxydhcpack },
[CACHED_INFO_BINL] = { create_fakepxebsack },
};
/* The case in which the caller doesn't supply a buffer is really

View File

@ -26,6 +26,7 @@
#include <assert.h>
#include <getopt.h>
#include <gpxe/netdevice.h>
#include <gpxe/in.h>
#include <gpxe/command.h>
#include <usr/dhcpmgmt.h>
@ -60,7 +61,7 @@ static int dhcp_exec ( int argc, char **argv ) {
{ "help", 0, NULL, 'h' },
{ NULL, 0, NULL, 0 },
};
const char *name;
const char *netdev_txt;
struct net_device *netdev;
int c;
int rc;
@ -82,14 +83,16 @@ static int dhcp_exec ( int argc, char **argv ) {
dhcp_syntax ( argv );
return 1;
}
name = argv[optind];
netdev_txt = argv[optind];
/* Perform DHCP */
netdev = find_netdev ( name );
/* Parse arguments */
netdev = find_netdev ( netdev_txt );
if ( ! netdev ) {
printf ( "No such interface: %s\n", name );
printf ( "No such interface: %s\n", netdev_txt );
return 1;
}
/* Perform DHCP */
if ( ( rc = dhcp ( netdev ) ) != 0 ) {
printf ( "Could not configure %s: %s\n", netdev->name,
strerror ( rc ) );
@ -99,10 +102,96 @@ static int dhcp_exec ( int argc, char **argv ) {
return 0;
}
/**
* "pxebs" command syntax message
*
* @v argv Argument list
*/
static void pxebs_syntax ( char **argv ) {
printf ( "Usage:\n"
" %s <interface> <discovery_ip> <server_type>\n"
"\n"
"Perform PXE Boot Server discovery\n",
argv[0] );
}
/**
* The "pxebs" command
*
* @v argc Argument count
* @v argv Argument list
* @ret rc Exit code
*/
static int pxebs_exec ( int argc, char **argv ) {
static struct option longopts[] = {
{ "help", 0, NULL, 'h' },
{ NULL, 0, NULL, 0 },
};
const char *netdev_txt;
const char *pxe_server_txt;
const char *pxe_type_txt;
struct net_device *netdev;
struct in_addr pxe_server;
unsigned int pxe_type;
char *end;
int c;
int rc;
/* Parse options */
while ( ( c = getopt_long ( argc, argv, "h", longopts, NULL ) ) >= 0 ){
switch ( c ) {
case 'h':
/* Display help text */
default:
/* Unrecognised/invalid option */
pxebs_syntax ( argv );
return 1;
}
}
/* Need exactly one interface name remaining after the options */
if ( optind != ( argc - 3 ) ) {
pxebs_syntax ( argv );
return 1;
}
netdev_txt = argv[optind];
pxe_server_txt = argv[ optind + 1 ];
pxe_type_txt = argv[ optind + 2 ];
/* Parse arguments */
netdev = find_netdev ( netdev_txt );
if ( ! netdev ) {
printf ( "No such interface: %s\n", netdev_txt );
return 1;
}
if ( inet_aton ( pxe_server_txt, &pxe_server ) == 0 ) {
printf ( "Bad discovery IP address: %s\n", pxe_server_txt );
return 1;
}
pxe_type = strtoul ( pxe_type_txt, &end, 0 );
if ( *end ) {
printf ( "Bad server type: %s\n", pxe_type_txt );
return 1;
}
/* Perform Boot Server Discovery */
if ( ( rc = pxebs ( netdev, pxe_server, pxe_type ) ) != 0 ) {
printf ( "Could not discover boot server on %s: %s\n",
netdev->name, strerror ( rc ) );
return 1;
}
return 0;
}
/** DHCP management commands */
struct command dhcp_commands[] __command = {
{
.name = "dhcp",
.exec = dhcp_exec,
},
{
.name = "pxebs",
.exec = pxebs_exec,
},
};

View File

@ -15,7 +15,7 @@ static void ansiscr_reset ( struct _curses_screen *scr ) {
scr->attrs = 0;
scr->curs_x = 0;
scr->curs_y = 0;
printf ( "\033[0m\033[2J\033[1;1H" );
printf ( "\033[0m" );
}
static void ansiscr_movetoyx ( struct _curses_screen *scr,

View File

@ -18,7 +18,7 @@ WINDOW *initscr ( void ) {
stdscr->scr->init( stdscr->scr );
stdscr->height = LINES;
stdscr->width = COLS;
erase();
move ( 0, 0 );
return stdscr;
}
@ -29,7 +29,7 @@ WINDOW *initscr ( void ) {
int endwin ( void ) {
attrset ( 0 );
color_set ( 0, NULL );
erase();
mvprintw ( ( LINES - 1 ), 0, "\n" );
stdscr->scr->exit( stdscr->scr );
return OK;
}

View File

@ -12,12 +12,12 @@
#include <gpxe/list.h>
#include <gpxe/refcnt.h>
#include <gpxe/tables.h>
#include <gpxe/uuid.h>
#include <gpxe/netdevice.h>
struct net_device;
struct job_interface;
struct dhcp_options;
struct dhcp_packet;
struct dhcp_pxe_boot_menu_item;
/** BOOTP/DHCP server port */
#define BOOTPS_PORT 67
@ -88,12 +88,53 @@ struct dhcp_pxe_boot_menu_item;
/** PXE boot menu */
#define DHCP_PXE_BOOT_MENU DHCP_ENCAP_OPT ( DHCP_VENDOR_ENCAP, 9 )
/** PXE boot menu */
struct dhcp_pxe_boot_menu {
/** "Type" */
uint16_t type;
/** Description length */
uint8_t desc_len;
/** Description */
char desc[0];
} __attribute__ (( packed ));
/** PXE boot menu prompt */
#define DHCP_PXE_BOOT_MENU_PROMPT DHCP_ENCAP_OPT ( DHCP_VENDOR_ENCAP, 10 )
/** PXE boot menu prompt */
struct dhcp_pxe_boot_menu_prompt {
/** Timeout
*
* A value of 0 means "time out immediately and select first
* boot item, without displaying the prompt". A value of 255
* means "display menu immediately with no timeout". Any
* other value means "display prompt, wait this many seconds
* for keypress, if key is F8, display menu, otherwise select
* first boot item".
*/
uint8_t timeout;
/** Prompt to press F8 */
char prompt[0];
} __attribute__ (( packed ));
/** PXE boot menu item */
#define DHCP_PXE_BOOT_MENU_ITEM DHCP_ENCAP_OPT ( DHCP_VENDOR_ENCAP, 71 )
/** PXE boot menu item */
struct dhcp_pxe_boot_menu_item {
/** "Type"
*
* This field actually identifies the specific boot server (or
* cluster of boot servers offering identical boot files).
*/
uint16_t type;
/** "Layer"
*
* Just don't ask.
*/
uint16_t layer;
} __attribute__ (( packed ));
/** Requested IP address */
#define DHCP_REQUESTED_ADDRESS 50
@ -140,6 +181,14 @@ struct dhcp_pxe_boot_menu_item;
/** Client identifier */
#define DHCP_CLIENT_ID 61
/** Client identifier */
struct dhcp_client_id {
/** Link-layer protocol */
uint8_t ll_proto;
/** Link-layer address */
uint8_t ll_addr[MAX_LL_ADDR_LEN];
} __attribute__ (( packed ));
/** TFTP server name
*
* This option replaces the fixed "sname" field, when that field is
@ -163,6 +212,16 @@ struct dhcp_pxe_boot_menu_item;
/** UUID client identifier */
#define DHCP_CLIENT_UUID 97
/** UUID client identifier */
struct dhcp_client_uuid {
/** Identifier type */
uint8_t type;
/** UUID */
union uuid uuid;
} __attribute__ (( packed ));
#define DHCP_CLIENT_UUID_TYPE 0
/** Etherboot-specific encapsulated options
*
* This encapsulated options field is used to contain all options
@ -213,7 +272,7 @@ struct dhcp_pxe_boot_menu_item;
/** Skip PXE DHCP protocol extensions such as ProxyDHCP
*
* If set to a non-zero value, gPXE will not wait for ProxyDHCP offers
* and will ignore any PXE-specific DHCP offers that it receives.
* and will ignore any PXE-specific DHCP options that it receives.
*/
#define DHCP_EB_NO_PXEDHCP DHCP_ENCAP_OPT ( DHCP_EB_ENCAP, 0xb0 )
@ -230,6 +289,16 @@ struct dhcp_pxe_boot_menu_item;
*/
#define DHCP_EB_BUS_ID DHCP_ENCAP_OPT ( DHCP_EB_ENCAP, 0xb1 )
/** Network device descriptor */
struct dhcp_netdev_desc {
/** Bus type ID */
uint8_t type;
/** Vendor ID */
uint16_t vendor;
/** Device ID */
uint16_t device;
} __attribute__ (( packed ));
/** BIOS drive number
*
* This is the drive number for a drive emulated via INT 13. 0x80 is
@ -480,13 +549,13 @@ struct dhcphdr {
*/
#define DHCP_MIN_LEN 552
/** Maximum time that we will wait for ProxyDHCP responses */
#define PROXYDHCP_WAIT_TIME ( 2 * TICKS_PER_SEC )
/** Timeouts for sending DHCP packets */
#define DHCP_MIN_TIMEOUT ( 1 * TICKS_PER_SEC )
#define DHCP_MAX_TIMEOUT ( 10 * TICKS_PER_SEC )
/** Maximum time that we will wait for ProxyDHCP responses */
#define PROXYDHCP_MAX_TIMEOUT ( 2 * TICKS_PER_SEC )
/** Settings block name used for DHCP responses */
#define DHCP_SETTINGS_NAME "dhcp"
@ -494,19 +563,18 @@ struct dhcphdr {
#define PROXYDHCP_SETTINGS_NAME "proxydhcp"
/** Setting block name used for BootServerDHCP responses */
#define BSDHCP_SETTINGS_NAME "bs"
#define PXEBS_SETTINGS_NAME "pxebs"
extern int dhcp_create_packet ( struct dhcp_packet *dhcppkt,
struct net_device *netdev, uint8_t msgtype,
struct dhcp_options *options,
const void *options, size_t options_len,
void *data, size_t max_len );
extern int dhcp_create_request ( struct dhcp_packet *dhcppkt,
struct net_device *netdev,
unsigned int msgtype, struct in_addr ciaddr,
struct in_addr server,
struct in_addr requested_ip,
struct dhcp_pxe_boot_menu_item *menu_item,
void *data, size_t max_len );
extern int start_dhcp ( struct job_interface *job, struct net_device *netdev );
extern int start_pxebs ( struct job_interface *job, struct net_device *netdev,
struct in_addr pxe_server, unsigned int pxe_type );
#endif /* _GPXE_DHCP_H */

View File

@ -9,27 +9,54 @@
#include <gpxe/dhcp.h>
#include <gpxe/dhcpopts.h>
#include <gpxe/refcnt.h>
/**
* A DHCP packet
*
*/
struct dhcp_packet {
/** Reference counter */
struct refcnt refcnt;
/** The DHCP packet contents */
struct dhcphdr *dhcphdr;
/** Maximum length of the DHCP packet buffer */
size_t max_len;
/** Used length of the DHCP packet buffer */
size_t len;
/** DHCP option blocks */
/** DHCP options */
struct dhcp_options options;
/** Settings interface */
struct settings settings;
};
/**
* Increment reference count on DHCP packet
*
* @v dhcppkt DHCP packet
* @ret dhcppkt DHCP packet
*/
static inline __attribute__ (( always_inline )) struct dhcp_packet *
dhcppkt_get ( struct dhcp_packet *dhcppkt ) {
ref_get ( &dhcppkt->refcnt );
return dhcppkt;
}
/**
* Decrement reference count on DHCP packet
*
* @v dhcppkt DHCP packet
*/
static inline __attribute__ (( always_inline )) void
dhcppkt_put ( struct dhcp_packet *dhcppkt ) {
ref_put ( &dhcppkt->refcnt );
}
extern int dhcppkt_store ( struct dhcp_packet *dhcppkt, unsigned int tag,
const void *data, size_t len );
extern int dhcppkt_fetch ( struct dhcp_packet *dhcppkt, unsigned int tag,
void *data, size_t len );
extern void dhcppkt_init ( struct dhcp_packet *dhcppkt,
void *data, size_t len );
struct dhcphdr *data, size_t len );
#endif /* _GPXE_DHCPPKT_H */

View File

@ -167,6 +167,7 @@
#define ERRFILE_smbios ( ERRFILE_OTHER | 0x00120000 )
#define ERRFILE_smbios_settings ( ERRFILE_OTHER | 0x00130000 )
#define ERRFILE_efi_smbios ( ERRFILE_OTHER | 0x00140000 )
#define ERRFILE_pxemenu ( ERRFILE_OTHER | 0x00150000 )
/** @} */

View File

@ -15,7 +15,7 @@ extern int create_fakedhcpdiscover ( struct net_device *netdev,
void *data, size_t max_len );
extern int create_fakedhcpack ( struct net_device *netdev,
void *data, size_t max_len );
extern int create_fakeproxydhcpack ( struct net_device *netdev,
void *data, size_t max_len );
extern int create_fakepxebsack ( struct net_device *netdev,
void *data, size_t max_len );
#endif /* _GPXE_FAKEDHCP_H */

View File

@ -304,4 +304,16 @@ static inline int delete_named_setting ( const char *name ) {
return storef_named_setting ( name, NULL );
}
/**
* Check existence of setting
*
* @v settings Settings block, or NULL to search all blocks
* @v setting Setting to fetch
* @ret exists Setting exists
*/
static inline int setting_exists ( struct settings *settings,
struct setting *setting ) {
return ( fetch_setting_len ( settings, setting ) >= 0 );
}
#endif /* _GPXE_SETTINGS_H */

View File

@ -7,9 +7,15 @@
*
*/
#include <gpxe/in.h>
struct net_device;
extern int shutdown_exit_flags;
extern void autoboot ( void );
extern int boot_next_server_and_filename ( struct in_addr next_server,
const char *filename );
extern int boot_root_path ( const char *root_path );
extern int pxe_menu_boot ( struct net_device *netdev );
#endif /* _USR_AUTOBOOT_H */

View File

@ -9,6 +9,8 @@
struct net_device;
int dhcp ( struct net_device *netdev );
extern int dhcp ( struct net_device *netdev );
extern int pxebs ( struct net_device *netdev, struct in_addr pxe_server,
unsigned int pxe_type );
#endif /* _USR_DHCPMGMT_H */

View File

@ -32,6 +32,12 @@
*
*/
/****************************************************************************
*
* DHCP packet raw interface
*
*/
/**
* Calculate used length of an IPv4 field within a DHCP packet
*
@ -193,21 +199,79 @@ int dhcppkt_fetch ( struct dhcp_packet *dhcppkt, unsigned int tag,
return dhcpopt_fetch ( &dhcppkt->options, tag, data, len );
}
/**
* Initialise prepopulated DHCP packet
/****************************************************************************
*
* @v dhcppkt Uninitialised DHCP packet
* @v data Memory for DHCP packet data
* @v max_len Length of memory for DHCP packet data
* DHCP packet settings interface
*
* The memory content must already be filled with valid DHCP options.
* A zeroed block counts as a block of valid DHCP options.
*/
void dhcppkt_init ( struct dhcp_packet *dhcppkt, void *data, size_t len ) {
/**
* Store value of DHCP setting
*
* @v settings Settings block
* @v setting Setting to store
* @v data Setting data, or NULL to clear setting
* @v len Length of setting data
* @ret rc Return status code
*/
static int dhcppkt_settings_store ( struct settings *settings,
struct setting *setting,
const void *data, size_t len ) {
struct dhcp_packet *dhcppkt =
container_of ( settings, struct dhcp_packet, settings );
return dhcppkt_store ( dhcppkt, setting->tag, data, len );
}
/**
* Fetch value of DHCP setting
*
* @v settings Settings block, or NULL to search all blocks
* @v setting Setting to fetch
* @v data Buffer to fill with setting data
* @v len Length of buffer
* @ret len Length of setting data, or negative error
*/
static int dhcppkt_settings_fetch ( struct settings *settings,
struct setting *setting,
void *data, size_t len ) {
struct dhcp_packet *dhcppkt =
container_of ( settings, struct dhcp_packet, settings );
return dhcppkt_fetch ( dhcppkt, setting->tag, data, len );
}
/** DHCP settings operations */
static struct settings_operations dhcppkt_settings_operations = {
.store = dhcppkt_settings_store,
.fetch = dhcppkt_settings_fetch,
};
/****************************************************************************
*
* Constructor
*
*/
/**
* Initialise DHCP packet
*
* @v dhcppkt DHCP packet structure to fill in
* @v data DHCP packet raw data
* @v max_len Length of raw data buffer
*
* Initialise a DHCP packet structure from a data buffer containing a
* DHCP packet.
*/
void dhcppkt_init ( struct dhcp_packet *dhcppkt, struct dhcphdr *data,
size_t len ) {
dhcppkt->dhcphdr = data;
dhcppkt->max_len = len;
dhcpopt_init ( &dhcppkt->options, &dhcppkt->dhcphdr->options,
( len - offsetof ( struct dhcphdr, options ) ) );
dhcppkt->len = ( offsetof ( struct dhcphdr, options ) +
dhcppkt->options.len );
settings_init ( &dhcppkt->settings,
&dhcppkt_settings_operations, &dhcppkt->refcnt,
DHCP_SETTINGS_NAME, 0 );
}

View File

@ -108,12 +108,11 @@ static int copy_settings ( struct dhcp_packet *dest,
int create_fakedhcpdiscover ( struct net_device *netdev,
void *data, size_t max_len ) {
struct dhcp_packet dhcppkt;
struct in_addr dummy_addr = { 0 };
struct in_addr ciaddr = { 0 };
int rc;
if ( ( rc = dhcp_create_request ( &dhcppkt, netdev, DHCPDISCOVER,
dummy_addr, dummy_addr, dummy_addr,
NULL, data, max_len ) ) != 0 ) {
ciaddr, data, max_len ) ) != 0 ) {
DBG ( "Could not create DHCPDISCOVER: %s\n",
strerror ( rc ) );
return rc;
@ -138,7 +137,7 @@ int create_fakedhcpack ( struct net_device *netdev,
int rc;
/* Create base DHCPACK packet */
if ( ( rc = dhcp_create_packet ( &dhcppkt, netdev, DHCPACK, NULL,
if ( ( rc = dhcp_create_packet ( &dhcppkt, netdev, DHCPACK, NULL, 0,
data, max_len ) ) != 0 ) {
DBG ( "Could not create DHCPACK: %s\n", strerror ( rc ) );
return rc;
@ -164,7 +163,7 @@ int create_fakedhcpack ( struct net_device *netdev,
}
/**
* Create ProxyDHCPACK packet
* Create fake PXE Boot Server ACK packet
*
* @v netdev Network device
* @v data Buffer for DHCP packet
@ -173,43 +172,43 @@ int create_fakedhcpack ( struct net_device *netdev,
*
* Used by external code.
*/
int create_fakeproxydhcpack ( struct net_device *netdev,
void *data, size_t max_len ) {
int create_fakepxebsack ( struct net_device *netdev,
void *data, size_t max_len ) {
struct dhcp_packet dhcppkt;
struct settings *settings;
struct settings *bs_settings;
struct settings *proxy_settings;
struct settings *pxebs_settings;
int rc;
/* Identify ProxyDHCP settings */
settings = find_settings ( PROXYDHCP_SETTINGS_NAME );
/* No ProxyDHCP settings => use normal DHCPACK */
if ( ! settings )
/* Identify available settings */
proxy_settings = find_settings ( PROXYDHCP_SETTINGS_NAME );
pxebs_settings = find_settings ( PXEBS_SETTINGS_NAME );
if ( ( ! proxy_settings ) && ( ! pxebs_settings ) ) {
/* No PXE boot server; return the regular DHCPACK */
return create_fakedhcpack ( netdev, data, max_len );
}
/* Create base DHCPACK packet */
if ( ( rc = dhcp_create_packet ( &dhcppkt, netdev, DHCPACK, NULL,
if ( ( rc = dhcp_create_packet ( &dhcppkt, netdev, DHCPACK, NULL, 0,
data, max_len ) ) != 0 ) {
DBG ( "Could not create ProxyDHCPACK: %s\n",
DBG ( "Could not create PXE BS ACK: %s\n",
strerror ( rc ) );
return rc;
}
/* Merge in ProxyDHCP options */
if ( ( rc = copy_settings ( &dhcppkt, settings ) ) != 0 ) {
DBG ( "Could not set ProxyDHCPACK settings: %s\n",
if ( proxy_settings &&
( ( rc = copy_settings ( &dhcppkt, proxy_settings ) ) != 0 ) ) {
DBG ( "Could not copy ProxyDHCP settings: %s\n",
strerror ( rc ) );
return rc;
}
/* Merge in BootServerDHCP options, if present */
bs_settings = find_settings ( BSDHCP_SETTINGS_NAME );
if ( bs_settings ) {
if ( ( rc = copy_settings ( &dhcppkt, bs_settings ) ) != 0 ) {
DBG ( "Could not set BootServerDHCPACK settings: "
"%s\n", strerror ( rc ) );
return rc;
}
if ( pxebs_settings &&
( ( rc = copy_settings ( &dhcppkt, pxebs_settings ) ) != 0 ) ) {
DBG ( "Could not copy PXE BS settings: %s\n",
strerror ( rc ) );
return rc;
}
return 0;

File diff suppressed because it is too large Load Diff

View File

@ -89,8 +89,8 @@ static int boot_embedded_image ( void ) {
* @v filename Boot filename
* @ret rc Return status code
*/
static int boot_next_server_and_filename ( struct in_addr next_server,
const char *filename ) {
int boot_next_server_and_filename ( struct in_addr next_server,
const char *filename ) {
struct uri *uri;
struct image *image;
char buf[ 23 /* tftp://xxx.xxx.xxx.xxx/ */ + strlen(filename) + 1 ];
@ -167,6 +167,7 @@ int boot_root_path ( const char *root_path ) {
* @ret rc Return status code
*/
static int netboot ( struct net_device *netdev ) {
struct setting tmp_setting = { .name = NULL };
char buf[256];
struct in_addr next_server;
int rc;
@ -194,6 +195,16 @@ static int netboot ( struct net_device *netdev ) {
if ( rc != ENOENT )
return rc;
/* Try PXE menu boot, if we have PXE menu options */
tmp_setting.tag = DHCP_VENDOR_CLASS_ID;
fetch_string_setting ( NULL, &tmp_setting, buf, sizeof ( buf ) );
tmp_setting.tag = DHCP_PXE_BOOT_MENU;
if ( ( strcmp ( buf, "PXEClient" ) == 0 ) &&
setting_exists ( NULL, &tmp_setting ) ) {
printf ( "Booting from PXE menu\n" );
return pxe_menu_boot ( netdev );
}
/* Try to download and boot whatever we are given as a filename */
fetch_ipv4_setting ( NULL, &next_server_setting, &next_server );
fetch_string_setting ( NULL, &filename_setting, buf, sizeof ( buf ) );

View File

@ -46,3 +46,17 @@ int dhcp ( struct net_device *netdev ) {
return rc;
}
int pxebs ( struct net_device *netdev, struct in_addr pxe_server,
unsigned int pxe_type ) {
int rc;
/* Perform PXE Boot Server Discovery */
printf ( "PXEBS (%s %s type %d)",
netdev->name, inet_ntoa ( pxe_server ), pxe_type );
if ( ( rc = start_pxebs ( &monojob, netdev, pxe_server,
pxe_type ) ) == 0 )
rc = monojob_wait ( "" );
return rc;
}

336
src/usr/pxemenu.c Normal file
View File

@ -0,0 +1,336 @@
/*
* 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.
*/
#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/vsprintf.h>
#include <gpxe/keys.h>
#include <gpxe/timer.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 {
/** Boot Server address */
struct in_addr server;
/** 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 tmp_setting = { .name = NULL };
struct in_addr server;
struct dhcp_pxe_boot_menu_prompt prompt = { .timeout = 0 };
uint8_t raw_menu[256];
int raw_menu_len;
struct dhcp_pxe_boot_menu *raw_menu_item;
void *raw_menu_end;
unsigned int num_menu_items;
unsigned int i;
int rc;
/* Fetch relevant settings */
tmp_setting.tag = DHCP_PXE_BOOT_SERVER_MCAST;
server.s_addr = INADDR_BROADCAST;
fetch_ipv4_setting ( NULL, &tmp_setting, &server );
tmp_setting.tag = DHCP_PXE_BOOT_MENU_PROMPT;
fetch_setting ( NULL, &tmp_setting, &prompt, sizeof ( prompt ) );
tmp_setting.tag = DHCP_PXE_BOOT_MENU;
memset ( raw_menu, 0, sizeof ( raw_menu ) );
if ( ( raw_menu_len = fetch_setting ( NULL, &tmp_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 );
/* 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 */ );
if ( ! *menu ) {
DBG ( "Could not allocate PXE boot menu\n" );
return -ENOMEM;
}
/* Fill in parsed menu */
(*menu)->server = server;
(*menu)->timeout =
( ( prompt.timeout == 0xff ) ? -1 : prompt.timeout );
(*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 = ntohs ( 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 );
}
return 0;
}
/**
* Draw PXE boot menu item
*
* @v menu PXE boot menu
* @v index Index of item to draw
*/
static void pxe_menu_draw_item ( struct pxe_menu *menu,
unsigned int index ) {
int selected = ( menu->selection == index );
char buf[COLS+1];
char *tmp = buf;
ssize_t remaining = sizeof ( buf );
size_t len;
unsigned int row;
/* Prepare space-padded row content */
len = ssnprintf ( tmp, remaining, " %c. %s",
( 'A' + index ), menu->items[index].desc );
tmp += len;
remaining -= len;
if ( selected && ( menu->timeout > 0 ) ) {
len = ssnprintf ( tmp, remaining, " (%d)", menu->timeout );
tmp += len;
remaining -= len;
}
for ( ; remaining > 1 ; tmp++, remaining-- )
*tmp = ' ';
*tmp = '\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
*/
int pxe_menu_select ( struct pxe_menu *menu ) {
unsigned long start = currticks();
unsigned long now;
unsigned long elapsed;
unsigned int old_selection;
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 ) );
while ( 1 ) {
/* Decrease timeout if necessary */
if ( menu->timeout > 0 ) {
now = currticks();
elapsed = ( now - start );
if ( elapsed >= TICKS_PER_SEC ) {
start = now;
menu->timeout--;
pxe_menu_draw_item ( menu, menu->selection );
}
}
/* Select current item if we have timed out */
if ( menu->timeout == 0 )
break;
/* Check for keyboard input */
if ( ! iskey() )
continue;
key = getkey();
/* Any keyboard input cancels the timeout */
menu->timeout = -1;
pxe_menu_draw_item ( menu, menu->selection );
/* Act upon key */
old_selection = menu->selection;
if ( ( key == CR ) || ( key == LF ) ) {
break;
} else if ( key == CTRL_C ) {
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;
menu->timeout = 0;
}
pxe_menu_draw_item ( menu, old_selection );
pxe_menu_draw_item ( menu, menu->selection );
}
/* Shut down UI */
endwin();
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;
struct in_addr pxe_server;
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_select ( menu ) ) != 0 ) {
free ( menu );
return rc;
}
pxe_server = menu->server;
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_server, 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 );
}