Home | History | Annotate | Download | only in usr
      1 /*
      2  * Copyright (C) 2009 Michael Brown <mbrown (at) fensystems.co.uk>.
      3  *
      4  * This program is free software; you can redistribute it and/or
      5  * modify it under the terms of the GNU General Public License as
      6  * published by the Free Software Foundation; either version 2 of the
      7  * License, or any later version.
      8  *
      9  * This program is distributed in the hope that it will be useful, but
     10  * WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     12  * General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU General Public License
     15  * along with this program; if not, write to the Free Software
     16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     17  */
     18 
     19 FILE_LICENCE ( GPL2_OR_LATER );
     20 
     21 #include <stdint.h>
     22 #include <stdlib.h>
     23 #include <stdio.h>
     24 #include <string.h>
     25 #include <errno.h>
     26 #include <ctype.h>
     27 #include <byteswap.h>
     28 #include <curses.h>
     29 #include <console.h>
     30 #include <gpxe/dhcp.h>
     31 #include <gpxe/keys.h>
     32 #include <gpxe/timer.h>
     33 #include <gpxe/process.h>
     34 #include <usr/dhcpmgmt.h>
     35 #include <usr/autoboot.h>
     36 
     37 /** @file
     38  *
     39  * PXE Boot Menus
     40  *
     41  */
     42 
     43 /* Colour pairs */
     44 #define CPAIR_NORMAL	1
     45 #define CPAIR_SELECT	2
     46 
     47 /** A PXE boot menu item */
     48 struct pxe_menu_item {
     49 	/** Boot Server type */
     50 	unsigned int type;
     51 	/** Description */
     52 	char *desc;
     53 };
     54 
     55 /**
     56  * A PXE boot menu
     57  *
     58  * This structure encapsulates the menu information provided via DHCP
     59  * options.
     60  */
     61 struct pxe_menu {
     62 	/** Prompt string (optional) */
     63 	const char *prompt;
     64 	/** Timeout (in seconds)
     65 	 *
     66 	 * Negative indicates no timeout (i.e. wait indefinitely)
     67 	 */
     68 	int timeout;
     69 	/** Number of menu items */
     70 	unsigned int num_items;
     71 	/** Selected menu item */
     72 	unsigned int selection;
     73 	/** Menu items */
     74 	struct pxe_menu_item items[0];
     75 };
     76 
     77 /**
     78  * Parse and allocate PXE boot menu
     79  *
     80  * @v menu		PXE boot menu to fill in
     81  * @ret rc		Return status code
     82  *
     83  * It is the callers responsibility to eventually free the allocated
     84  * boot menu.
     85  */
     86 static int pxe_menu_parse ( struct pxe_menu **menu ) {
     87 	struct setting pxe_boot_menu_prompt_setting =
     88 		{ .tag = DHCP_PXE_BOOT_MENU_PROMPT };
     89 	struct setting pxe_boot_menu_setting =
     90 		{ .tag = DHCP_PXE_BOOT_MENU };
     91 	uint8_t raw_menu[256];
     92 	int raw_prompt_len;
     93 	int raw_menu_len;
     94 	struct dhcp_pxe_boot_menu *raw_menu_item;
     95 	struct dhcp_pxe_boot_menu_prompt *raw_menu_prompt;
     96 	void *raw_menu_end;
     97 	unsigned int num_menu_items;
     98 	unsigned int i;
     99 	int rc;
    100 
    101 	/* Fetch raw menu */
    102 	memset ( raw_menu, 0, sizeof ( raw_menu ) );
    103 	if ( ( raw_menu_len = fetch_setting ( NULL, &pxe_boot_menu_setting,
    104 					      raw_menu,
    105 					      sizeof ( raw_menu ) ) ) < 0 ) {
    106 		rc = raw_menu_len;
    107 		DBG ( "Could not retrieve raw PXE boot menu: %s\n",
    108 		      strerror ( rc ) );
    109 		return rc;
    110 	}
    111 	if ( raw_menu_len >= ( int ) sizeof ( raw_menu ) ) {
    112 		DBG ( "Raw PXE boot menu too large for buffer\n" );
    113 		return -ENOSPC;
    114 	}
    115 	raw_menu_end = ( raw_menu + raw_menu_len );
    116 
    117 	/* Fetch raw prompt length */
    118 	raw_prompt_len = fetch_setting_len ( NULL,
    119 					     &pxe_boot_menu_prompt_setting );
    120 	if ( raw_prompt_len < 0 )
    121 		raw_prompt_len = 0;
    122 
    123 	/* Count menu items */
    124 	num_menu_items = 0;
    125 	raw_menu_item = ( ( void * ) raw_menu );
    126 	while ( 1 ) {
    127 		if ( ( ( ( void * ) raw_menu_item ) +
    128 		       sizeof ( *raw_menu_item ) ) > raw_menu_end )
    129 			break;
    130 		if ( ( ( ( void * ) raw_menu_item ) +
    131 		       sizeof ( *raw_menu_item ) +
    132 		       raw_menu_item->desc_len ) > raw_menu_end )
    133 			break;
    134 		num_menu_items++;
    135 		raw_menu_item = ( ( ( void * ) raw_menu_item ) +
    136 				  sizeof ( *raw_menu_item ) +
    137 				  raw_menu_item->desc_len );
    138 	}
    139 
    140 	/* Allocate space for parsed menu */
    141 	*menu = zalloc ( sizeof ( **menu ) +
    142 			 ( num_menu_items * sizeof ( (*menu)->items[0] ) ) +
    143 			 raw_menu_len + 1 /* NUL */ +
    144 			 raw_prompt_len + 1 /* NUL */ );
    145 	if ( ! *menu ) {
    146 		DBG ( "Could not allocate PXE boot menu\n" );
    147 		return -ENOMEM;
    148 	}
    149 
    150 	/* Fill in parsed menu */
    151 	(*menu)->num_items = num_menu_items;
    152 	raw_menu_item = ( ( ( void * ) (*menu) ) + sizeof ( **menu ) +
    153 			  ( num_menu_items * sizeof ( (*menu)->items[0] ) ) );
    154 	memcpy ( raw_menu_item, raw_menu, raw_menu_len );
    155 	for ( i = 0 ; i < num_menu_items ; i++ ) {
    156 		(*menu)->items[i].type = le16_to_cpu ( raw_menu_item->type );
    157 		(*menu)->items[i].desc = raw_menu_item->desc;
    158 		/* Set type to 0; this ensures that the description
    159 		 * for the previous menu item is NUL-terminated.
    160 		 * (Final item is NUL-terminated anyway.)
    161 		 */
    162 		raw_menu_item->type = 0;
    163 		raw_menu_item = ( ( ( void * ) raw_menu_item ) +
    164 				  sizeof ( *raw_menu_item ) +
    165 				  raw_menu_item->desc_len );
    166 	}
    167 	if ( raw_prompt_len ) {
    168 		raw_menu_prompt = ( ( ( void * ) raw_menu_item ) +
    169 				    1 /* NUL */ );
    170 		fetch_setting ( NULL, &pxe_boot_menu_prompt_setting,
    171 				raw_menu_prompt, raw_prompt_len );
    172 		(*menu)->timeout =
    173 			( ( raw_menu_prompt->timeout == 0xff ) ?
    174 			  -1 : raw_menu_prompt->timeout );
    175 		(*menu)->prompt = raw_menu_prompt->prompt;
    176 	} else {
    177 		(*menu)->timeout = -1;
    178 	}
    179 
    180 	return 0;
    181 }
    182 
    183 /**
    184  * Draw PXE boot menu item
    185  *
    186  * @v menu		PXE boot menu
    187  * @v index		Index of item to draw
    188  * @v selected		Item is selected
    189  */
    190 static void pxe_menu_draw_item ( struct pxe_menu *menu,
    191 				 unsigned int index, int selected ) {
    192 	char buf[COLS+1];
    193 	size_t len;
    194 	unsigned int row;
    195 
    196 	/* Prepare space-padded row content */
    197 	len = snprintf ( buf, sizeof ( buf ), " %c. %s",
    198 			 ( 'A' + index ), menu->items[index].desc );
    199 	while ( len < ( sizeof ( buf ) - 1 ) )
    200 		buf[len++] = ' ';
    201 	buf[ sizeof ( buf ) - 1 ] = '\0';
    202 
    203 	/* Draw row */
    204 	row = ( LINES - menu->num_items + index );
    205 	color_set ( ( selected ? CPAIR_SELECT : CPAIR_NORMAL ), NULL );
    206 	mvprintw ( row, 0, "%s", buf );
    207 	move ( row, 1 );
    208 }
    209 
    210 /**
    211  * Make selection from PXE boot menu
    212  *
    213  * @v menu		PXE boot menu
    214  * @ret rc		Return status code
    215  */
    216 static int pxe_menu_select ( struct pxe_menu *menu ) {
    217 	int key;
    218 	unsigned int key_selection;
    219 	unsigned int i;
    220 	int rc = 0;
    221 
    222 	/* Initialise UI */
    223 	initscr();
    224 	start_color();
    225 	init_pair ( CPAIR_NORMAL, COLOR_WHITE, COLOR_BLACK );
    226 	init_pair ( CPAIR_SELECT, COLOR_BLACK, COLOR_WHITE );
    227 	color_set ( CPAIR_NORMAL, NULL );
    228 
    229 	/* Draw initial menu */
    230 	for ( i = 0 ; i < menu->num_items ; i++ )
    231 		printf ( "\n" );
    232 	for ( i = 0 ; i < menu->num_items ; i++ )
    233 		pxe_menu_draw_item ( menu, ( menu->num_items - i - 1 ), 0 );
    234 
    235 	while ( 1 ) {
    236 
    237 		/* Highlight currently selected item */
    238 		pxe_menu_draw_item ( menu, menu->selection, 1 );
    239 
    240 		/* Wait for keyboard input */
    241 		while ( ! iskey() )
    242 			step();
    243 		key = getkey();
    244 
    245 		/* Unhighlight currently selected item */
    246 		pxe_menu_draw_item ( menu, menu->selection, 0 );
    247 
    248 		/* Act upon key */
    249 		if ( ( key == CR ) || ( key == LF ) ) {
    250 			pxe_menu_draw_item ( menu, menu->selection, 1 );
    251 			break;
    252 		} else if ( ( key == CTRL_C ) || ( key == ESC ) ) {
    253 			rc = -ECANCELED;
    254 			break;
    255 		} else if ( key == KEY_UP ) {
    256 			if ( menu->selection > 0 )
    257 				menu->selection--;
    258 		} else if ( key == KEY_DOWN ) {
    259 			if ( menu->selection < ( menu->num_items - 1 ) )
    260 				menu->selection++;
    261 		} else if ( ( key < KEY_MIN ) &&
    262 			    ( ( key_selection = ( toupper ( key ) - 'A' ) )
    263 			      < menu->num_items ) ) {
    264 			menu->selection = key_selection;
    265 			pxe_menu_draw_item ( menu, menu->selection, 1 );
    266 			break;
    267 		}
    268 	}
    269 
    270 	/* Shut down UI */
    271 	endwin();
    272 
    273 	return rc;
    274 }
    275 
    276 /**
    277  * Prompt for (and make selection from) PXE boot menu
    278  *
    279  * @v menu		PXE boot menu
    280  * @ret rc		Return status code
    281  */
    282 static int pxe_menu_prompt_and_select ( struct pxe_menu *menu ) {
    283 	unsigned long start = currticks();
    284 	unsigned long now;
    285 	unsigned long elapsed;
    286 	size_t len = 0;
    287 	int key;
    288 	int rc = 0;
    289 
    290 	/* Display menu immediately, if specified to do so */
    291 	if ( menu->timeout < 0 ) {
    292 		if ( menu->prompt )
    293 			printf ( "%s\n", menu->prompt );
    294 		return pxe_menu_select ( menu );
    295 	}
    296 
    297 	/* Display prompt, if specified */
    298 	if ( menu->prompt )
    299 		printf ( "%s", menu->prompt );
    300 
    301 	/* Wait for timeout, if specified */
    302 	while ( menu->timeout > 0 ) {
    303 		if ( ! len )
    304 			len = printf ( " (%d)", menu->timeout );
    305 		if ( iskey() ) {
    306 			key = getkey();
    307 			if ( key == KEY_F8 ) {
    308 				/* Display menu */
    309 				printf ( "\n" );
    310 				return pxe_menu_select ( menu );
    311 			} else if ( ( key == CTRL_C ) || ( key == ESC ) ) {
    312 				/* Abort */
    313 				rc = -ECANCELED;
    314 				break;
    315 			} else {
    316 				/* Stop waiting */
    317 				break;
    318 			}
    319 		}
    320 		now = currticks();
    321 		elapsed = ( now - start );
    322 		if ( elapsed >= TICKS_PER_SEC ) {
    323 			menu->timeout -= 1;
    324 			do {
    325 				printf ( "\b \b" );
    326 			} while ( --len );
    327 			start = now;
    328 		}
    329 	}
    330 
    331 	/* Return with default option selected */
    332 	printf ( "\n" );
    333 	return rc;
    334 }
    335 
    336 /**
    337  * Boot using PXE boot menu
    338  *
    339  * @ret rc		Return status code
    340  *
    341  * Note that a success return status indicates that a PXE boot menu
    342  * item has been selected, and that the DHCP session should perform a
    343  * boot server request/ack.
    344  */
    345 int pxe_menu_boot ( struct net_device *netdev ) {
    346 	struct pxe_menu *menu;
    347 	unsigned int pxe_type;
    348 	struct settings *pxebs_settings;
    349 	struct in_addr next_server;
    350 	char filename[256];
    351 	int rc;
    352 
    353 	/* Parse and allocate boot menu */
    354 	if ( ( rc = pxe_menu_parse ( &menu ) ) != 0 )
    355 		return rc;
    356 
    357 	/* Make selection from boot menu */
    358 	if ( ( rc = pxe_menu_prompt_and_select ( menu ) ) != 0 ) {
    359 		free ( menu );
    360 		return rc;
    361 	}
    362 	pxe_type = menu->items[menu->selection].type;
    363 
    364 	/* Free boot menu */
    365 	free ( menu );
    366 
    367 	/* Return immediately if local boot selected */
    368 	if ( ! pxe_type )
    369 		return 0;
    370 
    371 	/* Attempt PXE Boot Server Discovery */
    372 	if ( ( rc = pxebs ( netdev, pxe_type ) ) != 0 )
    373 		return rc;
    374 
    375 	/* Attempt boot */
    376 	pxebs_settings = find_settings ( PXEBS_SETTINGS_NAME );
    377 	assert ( pxebs_settings );
    378 	fetch_ipv4_setting ( pxebs_settings, &next_server_setting,
    379 			     &next_server );
    380 	fetch_string_setting ( pxebs_settings, &filename_setting,
    381 			       filename, sizeof ( filename ) );
    382 	return boot_next_server_and_filename ( next_server, filename );
    383 }
    384