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