pxemenu.c

Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2009 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00017  */
00018 
00019 FILE_LICENCE ( GPL2_OR_LATER );
00020 
00021 #include <stdint.h>
00022 #include <stdlib.h>
00023 #include <stdio.h>
00024 #include <string.h>
00025 #include <errno.h>
00026 #include <ctype.h>
00027 #include <byteswap.h>
00028 #include <curses.h>
00029 #include <console.h>
00030 #include <gpxe/dhcp.h>
00031 #include <gpxe/keys.h>
00032 #include <gpxe/timer.h>
00033 #include <gpxe/process.h>
00034 #include <usr/dhcpmgmt.h>
00035 #include <usr/autoboot.h>
00036 
00037 /** @file
00038  *
00039  * PXE Boot Menus
00040  *
00041  */
00042 
00043 /* Colour pairs */
00044 #define CPAIR_NORMAL    1
00045 #define CPAIR_SELECT    2
00046 
00047 /** A PXE boot menu item */
00048 struct pxe_menu_item {
00049         /** Boot Server type */
00050         unsigned int type;
00051         /** Description */
00052         char *desc;
00053 };
00054 
00055 /**
00056  * A PXE boot menu
00057  *
00058  * This structure encapsulates the menu information provided via DHCP
00059  * options.
00060  */
00061 struct pxe_menu {
00062         /** Prompt string (optional) */
00063         const char *prompt;
00064         /** Timeout (in seconds)
00065          *
00066          * Negative indicates no timeout (i.e. wait indefinitely)
00067          */
00068         int timeout;
00069         /** Number of menu items */
00070         unsigned int num_items;
00071         /** Selected menu item */
00072         unsigned int selection;
00073         /** Menu items */
00074         struct pxe_menu_item items[0];
00075 };
00076 
00077 /**
00078  * Parse and allocate PXE boot menu
00079  *
00080  * @v menu              PXE boot menu to fill in
00081  * @ret rc              Return status code
00082  *
00083  * It is the callers responsibility to eventually free the allocated
00084  * boot menu.
00085  */
00086 static int pxe_menu_parse ( struct pxe_menu **menu ) {
00087         struct setting pxe_boot_menu_prompt_setting =
00088                 { .tag = DHCP_PXE_BOOT_MENU_PROMPT };
00089         struct setting pxe_boot_menu_setting =
00090                 { .tag = DHCP_PXE_BOOT_MENU };
00091         uint8_t raw_menu[256];
00092         int raw_prompt_len;
00093         int raw_menu_len;
00094         struct dhcp_pxe_boot_menu *raw_menu_item;
00095         struct dhcp_pxe_boot_menu_prompt *raw_menu_prompt;
00096         void *raw_menu_end;
00097         unsigned int num_menu_items;
00098         unsigned int i;
00099         int rc;
00100 
00101         /* Fetch raw menu */
00102         memset ( raw_menu, 0, sizeof ( raw_menu ) );
00103         if ( ( raw_menu_len = fetch_setting ( NULL, &pxe_boot_menu_setting,
00104                                               raw_menu,
00105                                               sizeof ( raw_menu ) ) ) < 0 ) {
00106                 rc = raw_menu_len;
00107                 DBG ( "Could not retrieve raw PXE boot menu: %s\n",
00108                       strerror ( rc ) );
00109                 return rc;
00110         }
00111         if ( raw_menu_len >= ( int ) sizeof ( raw_menu ) ) {
00112                 DBG ( "Raw PXE boot menu too large for buffer\n" );
00113                 return -ENOSPC;
00114         }
00115         raw_menu_end = ( raw_menu + raw_menu_len );
00116 
00117         /* Fetch raw prompt length */
00118         raw_prompt_len = fetch_setting_len ( NULL,
00119                                              &pxe_boot_menu_prompt_setting );
00120         if ( raw_prompt_len < 0 )
00121                 raw_prompt_len = 0;
00122 
00123         /* Count menu items */
00124         num_menu_items = 0;
00125         raw_menu_item = ( ( void * ) raw_menu );
00126         while ( 1 ) {
00127                 if ( ( ( ( void * ) raw_menu_item ) +
00128                        sizeof ( *raw_menu_item ) ) > raw_menu_end )
00129                         break;
00130                 if ( ( ( ( void * ) raw_menu_item ) +
00131                        sizeof ( *raw_menu_item ) +
00132                        raw_menu_item->desc_len ) > raw_menu_end )
00133                         break;
00134                 num_menu_items++;
00135                 raw_menu_item = ( ( ( void * ) raw_menu_item ) +
00136                                   sizeof ( *raw_menu_item ) +
00137                                   raw_menu_item->desc_len );
00138         }
00139 
00140         /* Allocate space for parsed menu */
00141         *menu = zalloc ( sizeof ( **menu ) +
00142                          ( num_menu_items * sizeof ( (*menu)->items[0] ) ) +
00143                          raw_menu_len + 1 /* NUL */ +
00144                          raw_prompt_len + 1 /* NUL */ );
00145         if ( ! *menu ) {
00146                 DBG ( "Could not allocate PXE boot menu\n" );
00147                 return -ENOMEM;
00148         }
00149 
00150         /* Fill in parsed menu */
00151         (*menu)->num_items = num_menu_items;
00152         raw_menu_item = ( ( ( void * ) (*menu) ) + sizeof ( **menu ) +
00153                           ( num_menu_items * sizeof ( (*menu)->items[0] ) ) );
00154         memcpy ( raw_menu_item, raw_menu, raw_menu_len );
00155         for ( i = 0 ; i < num_menu_items ; i++ ) {
00156                 (*menu)->items[i].type = le16_to_cpu ( raw_menu_item->type );
00157                 (*menu)->items[i].desc = raw_menu_item->desc;
00158                 /* Set type to 0; this ensures that the description
00159                  * for the previous menu item is NUL-terminated.
00160                  * (Final item is NUL-terminated anyway.)
00161                  */
00162                 raw_menu_item->type = 0;
00163                 raw_menu_item = ( ( ( void * ) raw_menu_item ) +
00164                                   sizeof ( *raw_menu_item ) +
00165                                   raw_menu_item->desc_len );
00166         }
00167         if ( raw_prompt_len ) {
00168                 raw_menu_prompt = ( ( ( void * ) raw_menu_item ) +
00169                                     1 /* NUL */ );
00170                 fetch_setting ( NULL, &pxe_boot_menu_prompt_setting,
00171                                 raw_menu_prompt, raw_prompt_len );
00172                 (*menu)->timeout =
00173                         ( ( raw_menu_prompt->timeout == 0xff ) ?
00174                           -1 : raw_menu_prompt->timeout );
00175                 (*menu)->prompt = raw_menu_prompt->prompt;
00176         } else {
00177                 (*menu)->timeout = -1;
00178         }
00179 
00180         return 0;
00181 }
00182 
00183 /**
00184  * Draw PXE boot menu item
00185  *
00186  * @v menu              PXE boot menu
00187  * @v index             Index of item to draw
00188  * @v selected          Item is selected
00189  */
00190 static void pxe_menu_draw_item ( struct pxe_menu *menu,
00191                                  unsigned int index, int selected ) {
00192         char buf[COLS+1];
00193         size_t len;
00194         unsigned int row;
00195 
00196         /* Prepare space-padded row content */
00197         len = snprintf ( buf, sizeof ( buf ), " %c. %s",
00198                          ( 'A' + index ), menu->items[index].desc );
00199         while ( len < ( sizeof ( buf ) - 1 ) )
00200                 buf[len++] = ' ';
00201         buf[ sizeof ( buf ) - 1 ] = '\0';
00202 
00203         /* Draw row */
00204         row = ( LINES - menu->num_items + index );
00205         color_set ( ( selected ? CPAIR_SELECT : CPAIR_NORMAL ), NULL );
00206         mvprintw ( row, 0, "%s", buf );
00207         move ( row, 1 );
00208 }
00209 
00210 /**
00211  * Make selection from PXE boot menu
00212  *
00213  * @v menu              PXE boot menu
00214  * @ret rc              Return status code
00215  */
00216 static int pxe_menu_select ( struct pxe_menu *menu ) {
00217         int key;
00218         unsigned int key_selection;
00219         unsigned int i;
00220         int rc = 0;
00221 
00222         /* Initialise UI */
00223         initscr();
00224         start_color();
00225         init_pair ( CPAIR_NORMAL, COLOR_WHITE, COLOR_BLACK );
00226         init_pair ( CPAIR_SELECT, COLOR_BLACK, COLOR_WHITE );
00227         color_set ( CPAIR_NORMAL, NULL );
00228 
00229         /* Draw initial menu */
00230         for ( i = 0 ; i < menu->num_items ; i++ )
00231                 printf ( "\n" );
00232         for ( i = 0 ; i < menu->num_items ; i++ )
00233                 pxe_menu_draw_item ( menu, ( menu->num_items - i - 1 ), 0 );
00234 
00235         while ( 1 ) {
00236 
00237                 /* Highlight currently selected item */
00238                 pxe_menu_draw_item ( menu, menu->selection, 1 );
00239 
00240                 /* Wait for keyboard input */
00241                 while ( ! iskey() )
00242                         step();
00243                 key = getkey();
00244 
00245                 /* Unhighlight currently selected item */
00246                 pxe_menu_draw_item ( menu, menu->selection, 0 );
00247 
00248                 /* Act upon key */
00249                 if ( ( key == CR ) || ( key == LF ) ) {
00250                         pxe_menu_draw_item ( menu, menu->selection, 1 );
00251                         break;
00252                 } else if ( ( key == CTRL_C ) || ( key == ESC ) ) {
00253                         rc = -ECANCELED;
00254                         break;
00255                 } else if ( key == KEY_UP ) {
00256                         if ( menu->selection > 0 )
00257                                 menu->selection--;
00258                 } else if ( key == KEY_DOWN ) {
00259                         if ( menu->selection < ( menu->num_items - 1 ) )
00260                                 menu->selection++;
00261                 } else if ( ( key < KEY_MIN ) &&
00262                             ( ( key_selection = ( toupper ( key ) - 'A' ) )
00263                               < menu->num_items ) ) {
00264                         menu->selection = key_selection;
00265                         pxe_menu_draw_item ( menu, menu->selection, 1 );
00266                         break;
00267                 }
00268         }
00269 
00270         /* Shut down UI */
00271         endwin();
00272 
00273         return rc;
00274 }
00275 
00276 /**
00277  * Prompt for (and make selection from) PXE boot menu
00278  *
00279  * @v menu              PXE boot menu
00280  * @ret rc              Return status code
00281  */
00282 static int pxe_menu_prompt_and_select ( struct pxe_menu *menu ) {
00283         unsigned long start = currticks();
00284         unsigned long now;
00285         unsigned long elapsed;
00286         size_t len = 0;
00287         int key;
00288         int rc = 0;
00289 
00290         /* Display menu immediately, if specified to do so */
00291         if ( menu->timeout < 0 ) {
00292                 if ( menu->prompt )
00293                         printf ( "%s\n", menu->prompt );
00294                 return pxe_menu_select ( menu );
00295         }
00296 
00297         /* Display prompt, if specified */
00298         if ( menu->prompt )
00299                 printf ( "%s", menu->prompt );
00300 
00301         /* Wait for timeout, if specified */
00302         while ( menu->timeout > 0 ) {
00303                 if ( ! len )
00304                         len = printf ( " (%d)", menu->timeout );
00305                 if ( iskey() ) {
00306                         key = getkey();
00307                         if ( key == KEY_F8 ) {
00308                                 /* Display menu */
00309                                 printf ( "\n" );
00310                                 return pxe_menu_select ( menu );
00311                         } else if ( ( key == CTRL_C ) || ( key == ESC ) ) {
00312                                 /* Abort */
00313                                 rc = -ECANCELED;
00314                                 break;
00315                         } else {
00316                                 /* Stop waiting */
00317                                 break;
00318                         }
00319                 }
00320                 now = currticks();
00321                 elapsed = ( now - start );
00322                 if ( elapsed >= TICKS_PER_SEC ) {
00323                         menu->timeout -= 1;
00324                         do {
00325                                 printf ( "\b \b" );
00326                         } while ( --len );
00327                         start = now;
00328                 }
00329         }
00330 
00331         /* Return with default option selected */
00332         printf ( "\n" );
00333         return rc;
00334 }
00335 
00336 /**
00337  * Boot using PXE boot menu
00338  *
00339  * @ret rc              Return status code
00340  *
00341  * Note that a success return status indicates that a PXE boot menu
00342  * item has been selected, and that the DHCP session should perform a
00343  * boot server request/ack.
00344  */
00345 int pxe_menu_boot ( struct net_device *netdev ) {
00346         struct pxe_menu *menu;
00347         unsigned int pxe_type;
00348         struct settings *pxebs_settings;
00349         struct in_addr next_server;
00350         char filename[256];
00351         int rc;
00352 
00353         /* Parse and allocate boot menu */
00354         if ( ( rc = pxe_menu_parse ( &menu ) ) != 0 )
00355                 return rc;
00356 
00357         /* Make selection from boot menu */
00358         if ( ( rc = pxe_menu_prompt_and_select ( menu ) ) != 0 ) {
00359                 free ( menu );
00360                 return rc;
00361         }
00362         pxe_type = menu->items[menu->selection].type;
00363 
00364         /* Free boot menu */
00365         free ( menu );
00366 
00367         /* Return immediately if local boot selected */
00368         if ( ! pxe_type )
00369                 return 0;
00370 
00371         /* Attempt PXE Boot Server Discovery */
00372         if ( ( rc = pxebs ( netdev, pxe_type ) ) != 0 )
00373                 return rc;
00374 
00375         /* Attempt boot */
00376         pxebs_settings = find_settings ( PXEBS_SETTINGS_NAME );
00377         assert ( pxebs_settings );
00378         fetch_ipv4_setting ( pxebs_settings, &next_server_setting,
00379                              &next_server );
00380         fetch_string_setting ( pxebs_settings, &filename_setting,
00381                                filename, sizeof ( filename ) );
00382         return boot_next_server_and_filename ( next_server, filename );
00383 }

Generated on Tue Apr 6 20:01:11 2010 for gPXE by  doxygen 1.5.7.1