iPXE
pxemenu.c
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2009 Michael Brown <mbrown@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., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301, USA.
18  *
19  * You can also choose to distribute this program under the terms of
20  * the Unmodified Binary Distribution Licence (as given in the file
21  * COPYING.UBDL), provided that you have satisfied its requirements.
22  */
23 
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25 
26 #include <stdint.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <errno.h>
31 #include <ctype.h>
32 #include <byteswap.h>
33 #include <curses.h>
34 #include <ipxe/console.h>
35 #include <ipxe/dhcp.h>
36 #include <ipxe/keys.h>
37 #include <ipxe/timer.h>
38 #include <ipxe/uri.h>
39 #include <ipxe/ansicol.h>
40 #include <usr/dhcpmgmt.h>
41 #include <usr/autoboot.h>
42 
43 /** @file
44  *
45  * PXE Boot Menus
46  *
47  */
48 
49 /** A PXE boot menu item */
50 struct pxe_menu_item {
51  /** Boot Server type */
52  unsigned int type;
53  /** Description */
54  char *desc;
55 };
56 
57 /**
58  * A PXE boot menu
59  *
60  * This structure encapsulates the menu information provided via DHCP
61  * options.
62  */
63 struct pxe_menu {
64  /** Prompt string (optional) */
65  const char *prompt;
66  /** Timeout (in seconds)
67  *
68  * Negative indicates no timeout (i.e. wait indefinitely)
69  */
70  int timeout;
71  /** Number of menu items */
72  unsigned int num_items;
73  /** Selected menu item */
74  unsigned int selection;
75  /** Menu items */
76  struct pxe_menu_item items[0];
77 };
78 
79 /**
80  * Parse and allocate PXE boot menu
81  *
82  * @v menu PXE boot menu to fill in
83  * @ret rc Return status code
84  *
85  * It is the callers responsibility to eventually free the allocated
86  * boot menu.
87  */
88 static int pxe_menu_parse ( struct pxe_menu **menu ) {
89  struct setting pxe_boot_menu_prompt_setting =
91  struct setting pxe_boot_menu_setting =
92  { .tag = DHCP_PXE_BOOT_MENU };
93  uint8_t raw_menu[256];
94  int raw_prompt_len;
95  int raw_menu_len;
96  struct dhcp_pxe_boot_menu *raw_menu_item;
97  struct dhcp_pxe_boot_menu_prompt *raw_menu_prompt;
98  void *raw_menu_end;
99  unsigned int num_menu_items;
100  unsigned int i;
101  int rc;
102 
103  /* Fetch raw menu */
104  memset ( raw_menu, 0, sizeof ( raw_menu ) );
105  if ( ( raw_menu_len = fetch_raw_setting ( NULL, &pxe_boot_menu_setting,
106  raw_menu,
107  sizeof ( raw_menu ) ) ) < 0 ){
108  rc = raw_menu_len;
109  DBG ( "Could not retrieve raw PXE boot menu: %s\n",
110  strerror ( rc ) );
111  return rc;
112  }
113  if ( raw_menu_len >= ( int ) sizeof ( raw_menu ) ) {
114  DBG ( "Raw PXE boot menu too large for buffer\n" );
115  return -ENOSPC;
116  }
117  raw_menu_end = ( raw_menu + raw_menu_len );
118 
119  /* Fetch raw prompt length */
120  raw_prompt_len =
121  fetch_raw_setting ( NULL, &pxe_boot_menu_prompt_setting,
122  NULL, 0 );
123  if ( raw_prompt_len < 0 )
124  raw_prompt_len = 0;
125 
126  /* Count menu items */
127  num_menu_items = 0;
128  raw_menu_item = ( ( void * ) raw_menu );
129  while ( 1 ) {
130  if ( ( ( ( void * ) raw_menu_item ) +
131  sizeof ( *raw_menu_item ) ) > raw_menu_end )
132  break;
133  if ( ( ( ( void * ) raw_menu_item ) +
134  sizeof ( *raw_menu_item ) +
135  raw_menu_item->desc_len ) > raw_menu_end )
136  break;
137  num_menu_items++;
138  raw_menu_item = ( ( ( void * ) raw_menu_item ) +
139  sizeof ( *raw_menu_item ) +
140  raw_menu_item->desc_len );
141  }
142 
143  /* Allocate space for parsed menu */
144  *menu = zalloc ( sizeof ( **menu ) +
145  ( num_menu_items * sizeof ( (*menu)->items[0] ) ) +
146  raw_menu_len + 1 /* NUL */ +
147  raw_prompt_len + 1 /* NUL */ );
148  if ( ! *menu ) {
149  DBG ( "Could not allocate PXE boot menu\n" );
150  return -ENOMEM;
151  }
152 
153  /* Fill in parsed menu */
154  (*menu)->num_items = num_menu_items;
155  raw_menu_item = ( ( ( void * ) (*menu) ) + sizeof ( **menu ) +
156  ( num_menu_items * sizeof ( (*menu)->items[0] ) ) );
157  memcpy ( raw_menu_item, raw_menu, raw_menu_len );
158  for ( i = 0 ; i < num_menu_items ; i++ ) {
159  (*menu)->items[i].type = le16_to_cpu ( raw_menu_item->type );
160  (*menu)->items[i].desc = raw_menu_item->desc;
161  /* Set type to 0; this ensures that the description
162  * for the previous menu item is NUL-terminated.
163  * (Final item is NUL-terminated anyway.)
164  */
165  raw_menu_item->type = 0;
166  raw_menu_item = ( ( ( void * ) raw_menu_item ) +
167  sizeof ( *raw_menu_item ) +
168  raw_menu_item->desc_len );
169  }
170  if ( raw_prompt_len ) {
171  raw_menu_prompt = ( ( ( void * ) raw_menu_item ) +
172  1 /* NUL */ );
173  fetch_raw_setting ( NULL, &pxe_boot_menu_prompt_setting,
174  raw_menu_prompt, raw_prompt_len );
175  (*menu)->timeout =
176  ( ( raw_menu_prompt->timeout == 0xff ) ?
177  -1 : raw_menu_prompt->timeout );
178  (*menu)->prompt = raw_menu_prompt->prompt;
179  } else {
180  (*menu)->timeout = -1;
181  }
182 
183  return 0;
184 }
185 
186 /**
187  * Draw PXE boot menu item
188  *
189  * @v menu PXE boot menu
190  * @v index Index of item to draw
191  * @v selected Item is selected
192  */
193 static void pxe_menu_draw_item ( struct pxe_menu *menu,
194  unsigned int index, int selected ) {
195  char buf[COLS+1];
196  size_t len;
197  unsigned int row;
198 
199  /* Prepare space-padded row content */
200  len = snprintf ( buf, sizeof ( buf ), " %c. %s",
201  ( 'A' + index ), menu->items[index].desc );
202  while ( len < ( sizeof ( buf ) - 1 ) )
203  buf[len++] = ' ';
204  buf[ sizeof ( buf ) - 1 ] = '\0';
205 
206  /* Draw row */
207  row = ( LINES - menu->num_items + index );
208  color_set ( ( selected ? CPAIR_PXE : CPAIR_DEFAULT ), NULL );
209  mvprintw ( row, 0, "%s", buf );
210  move ( row, 1 );
211 }
212 
213 /**
214  * Make selection from PXE boot menu
215  *
216  * @v menu PXE boot menu
217  * @ret rc Return status code
218  */
219 static int pxe_menu_select ( struct pxe_menu *menu ) {
220  int key;
221  unsigned int key_selection;
222  unsigned int i;
223  int rc = 0;
224 
225  /* Initialise UI */
226  initscr();
227  start_color();
229 
230  /* Draw initial menu */
231  for ( i = 0 ; i < menu->num_items ; i++ )
232  printf ( "\n" );
233  for ( i = 0 ; i < menu->num_items ; i++ )
234  pxe_menu_draw_item ( menu, ( menu->num_items - i - 1 ), 0 );
235 
236  while ( 1 ) {
237 
238  /* Highlight currently selected item */
239  pxe_menu_draw_item ( menu, menu->selection, 1 );
240 
241  /* Wait for keyboard input */
242  key = getkey ( 0 );
243 
244  /* Unhighlight currently selected item */
245  pxe_menu_draw_item ( menu, menu->selection, 0 );
246 
247  /* Act upon key */
248  if ( ( key == CR ) || ( key == LF ) ) {
249  pxe_menu_draw_item ( menu, menu->selection, 1 );
250  break;
251  } else if ( ( key == CTRL_C ) || ( key == ESC ) ) {
252  rc = -ECANCELED;
253  break;
254  } else if ( key == KEY_UP ) {
255  if ( menu->selection > 0 )
256  menu->selection--;
257  } else if ( key == KEY_DOWN ) {
258  if ( menu->selection < ( menu->num_items - 1 ) )
259  menu->selection++;
260  } else if ( ( key < KEY_MIN ) &&
261  ( ( key_selection = ( toupper ( key ) - 'A' ) )
262  < menu->num_items ) ) {
263  menu->selection = key_selection;
264  pxe_menu_draw_item ( menu, menu->selection, 1 );
265  break;
266  }
267  }
268 
269  /* Shut down UI */
270  endwin();
271 
272  return rc;
273 }
274 
275 /**
276  * Prompt for (and make selection from) PXE boot menu
277  *
278  * @v menu PXE boot menu
279  * @ret rc Return status code
280  */
281 static int pxe_menu_prompt_and_select ( struct pxe_menu *menu ) {
282  unsigned long start = currticks();
283  unsigned long now;
284  unsigned long elapsed;
285  size_t len = 0;
286  int key;
287  int rc = 0;
288 
289  /* Display menu immediately, if specified to do so */
290  if ( menu->timeout < 0 ) {
291  if ( menu->prompt )
292  printf ( "%s\n", menu->prompt );
293  return pxe_menu_select ( menu );
294  }
295 
296  /* Display prompt, if specified */
297  if ( menu->prompt )
298  printf ( "%s", menu->prompt );
299 
300  /* Wait for timeout, if specified */
301  while ( menu->timeout > 0 ) {
302  if ( ! len )
303  len = printf ( " (%d)", menu->timeout );
304  if ( iskey() ) {
305  key = getkey ( 0 );
306  if ( key == KEY_F8 ) {
307  /* Display menu */
308  printf ( "\n" );
309  return pxe_menu_select ( menu );
310  } else if ( ( key == CTRL_C ) || ( key == ESC ) ) {
311  /* Abort */
312  rc = -ECANCELED;
313  break;
314  } else {
315  /* Stop waiting */
316  break;
317  }
318  }
319  now = currticks();
320  elapsed = ( now - start );
321  if ( elapsed >= TICKS_PER_SEC ) {
322  menu->timeout -= 1;
323  do {
324  printf ( "\b \b" );
325  } while ( --len );
326  start = now;
327  }
328  }
329 
330  /* Return with default option selected */
331  printf ( "\n" );
332  return rc;
333 }
334 
335 /**
336  * Boot using PXE boot menu
337  *
338  * @ret rc Return status code
339  *
340  * Note that a success return status indicates that a PXE boot menu
341  * item has been selected, and that the DHCP session should perform a
342  * boot server request/ack.
343  */
344 int pxe_menu_boot ( struct net_device *netdev ) {
345  struct pxe_menu *menu;
346  unsigned int pxe_type;
347  struct settings *pxebs_settings;
348  struct uri *uri;
349  int rc;
350 
351  /* Parse and allocate boot menu */
352  if ( ( rc = pxe_menu_parse ( &menu ) ) != 0 )
353  return rc;
354 
355  /* Make selection from boot menu */
356  if ( ( rc = pxe_menu_prompt_and_select ( menu ) ) != 0 ) {
357  free ( menu );
358  return rc;
359  }
360  pxe_type = menu->items[menu->selection].type;
361 
362  /* Free boot menu */
363  free ( menu );
364 
365  /* Return immediately if local boot selected */
366  if ( ! pxe_type )
367  return 0;
368 
369  /* Attempt PXE Boot Server Discovery */
370  if ( ( rc = pxebs ( netdev, pxe_type ) ) != 0 )
371  return rc;
372 
373  /* Fetch next server and filename */
374  pxebs_settings = find_settings ( PXEBS_SETTINGS_NAME );
375  assert ( pxebs_settings );
376  uri = fetch_next_server_and_filename ( pxebs_settings );
377  if ( ! uri )
378  return -ENOMEM;
379 
380  /* Attempt boot */
381  rc = uriboot ( uri, NULL, 0, 0, NULL, URIBOOT_NO_SAN );
382  uri_put ( uri );
383  return rc;
384 }
int getkey(unsigned long timeout)
Get single keypress.
Definition: getkey.c:71
MuCurses header file.
#define DHCP_PXE_BOOT_MENU_PROMPT
PXE boot menu prompt.
Definition: dhcp.h:137
#define URIBOOT_NO_SAN
Definition: autoboot.h:25
struct arbelprm_rc_send_wqe rc
Definition: arbel.h:14
uint8_t timeout
Timeout.
Definition: dhcp.h:150
A PXE boot menu.
Definition: pxemenu.c:63
Dynamic Host Configuration Protocol.
#define TICKS_PER_SEC
Number of ticks per second.
Definition: timer.h:15
int printf(const char *fmt,...)
Write a formatted string to the console.
Definition: vsprintf.c:464
static void uri_put(struct uri *uri)
Decrement URI reference count.
Definition: uri.h:188
Error codes.
PXE boot menu prompt.
Definition: dhcp.h:140
DHCP management.
#define KEY_F8
F8 (for PXE)
Definition: keys.h:78
#define start_color()
Definition: curses.h:396
int endwin(void)
Finalise console environment.
Definition: wininit.c:31
static int pxe_menu_prompt_and_select(struct pxe_menu *menu)
Prompt for (and make selection from) PXE boot menu.
Definition: pxemenu.c:281
#define mvprintw(y, x, fmt,...)
Definition: curses.h:648
int fetch_raw_setting(struct settings *settings, const struct setting *setting, void *data, size_t len)
Fetch value of setting.
Definition: settings.c:807
iPXE timers
struct list_head items
Menu items.
Definition: menu.h:23
Character types.
#define LINES(...)
Define inline lines.
Definition: linebuf_test.c:44
Automatic booting.
Uniform Resource Identifiers.
int uriboot(struct uri *filename, struct uri **root_paths, unsigned int root_path_count, int drive, const char *san_filename, unsigned int flags)
Boot from filename and root-path URIs.
Definition: autoboot.c:125
FILE_LICENCE(GPL2_OR_LATER_OR_UBDL)
#define ECANCELED
Operation canceled.
Definition: errno.h:343
unsigned int selection
Selected menu item.
Definition: pxemenu.c:74
unsigned int num_items
Number of menu items.
Definition: pxemenu.c:72
uint32_t start
Starting offset.
Definition: netvsc.h:12
static int toupper(int character)
Convert character to upper case.
Definition: ctype.h:109
#define KEY_DOWN
Down arrow.
Definition: keys.h:66
uint64_t tag
Setting tag, if applicable.
Definition: settings.h:43
uint16_t type
"Type"
Definition: dhcp.h:129
int timeout
Timeout (in seconds)
Definition: pxemenu.c:70
#define ENOMEM
Not enough space.
Definition: errno.h:534
void * memcpy(void *dest, const void *src, size_t len) __nonnull
#define KEY_MIN
Definition: keys.h:64
#define KEY_UP
Up arrow.
Definition: keys.h:65
#define CPAIR_PXE
PXE selected menu entry.
Definition: ansicol.h:58
WINDOW * initscr(void)
Initialise console environment.
Definition: wininit.c:17
assert((readw(&hdr->flags) &(GTF_reading|GTF_writing))==0)
struct uri * fetch_next_server_and_filename(struct settings *settings)
Fetch next-server and filename settings into a URI.
Definition: autoboot.c:234
static void pxe_menu_draw_item(struct pxe_menu *menu, unsigned int index, int selected)
Draw PXE boot menu item.
Definition: pxemenu.c:193
static struct net_device * netdev
Definition: gdbudp.c:52
#define CTRL_C
Definition: keys.h:20
#define ESC
Escape character.
Definition: ansiesc.h:92
#define PXEBS_SETTINGS_NAME
Setting block name used for BootServerDHCP responses.
Definition: dhcp.h:692
User interaction.
static int pxe_menu_select(struct pxe_menu *menu)
Make selection from PXE boot menu.
Definition: pxemenu.c:219
char * strerror(int errno)
Retrieve string representation of error number.
Definition: strerror.c:78
static void(* free)(struct refcnt *refcnt))
Definition: refcnt.h:54
void * zalloc(size_t size)
Allocate cleared memory.
Definition: malloc.c:624
const char * prompt
Prompt string (optional)
Definition: pxemenu.c:65
char prompt[0]
Prompt to press F8.
Definition: dhcp.h:152
A network device.
Definition: netdevice.h:348
int pxe_menu_boot(struct net_device *netdev)
Boot using PXE boot menu.
Definition: pxemenu.c:344
char desc[0]
Description.
Definition: dhcp.h:133
A settings block.
Definition: settings.h:132
unsigned char uint8_t
Definition: stdint.h:10
#define DHCP_PXE_BOOT_MENU
PXE boot menu.
Definition: dhcp.h:124
unsigned int type
Boot Server type.
Definition: pxemenu.c:52
#define le16_to_cpu(value)
Definition: byteswap.h:112
#define COLS
Definition: curses.h:111
#define LF
Definition: keys.h:47
A setting.
Definition: settings.h:23
#define CPAIR_DEFAULT
Default colour pair.
Definition: ansicol.h:37
#define ENOSPC
No space left on device.
Definition: errno.h:549
uint32_t len
Length.
Definition: ena.h:14
struct pxe_menu_item items[0]
Menu items.
Definition: pxemenu.c:76
static int pxe_menu_parse(struct pxe_menu **menu)
Parse and allocate PXE boot menu.
Definition: pxemenu.c:88
Key definitions.
static int move(int y, int x)
Definition: curses.h:593
struct settings * find_settings(const char *name)
Find settings block.
Definition: settings.c:406
#define CR
Definition: keys.h:48
int snprintf(char *buf, size_t size, const char *fmt,...)
Write a formatted string to a buffer.
Definition: vsprintf.c:382
A Uniform Resource Identifier.
Definition: uri.h:50
#define color_set(cpno, opts)
Definition: curses.h:240
char * desc
Description.
Definition: pxemenu.c:54
unsigned long currticks(void)
Get current system time in ticks.
Definition: timer.c:42
A PXE boot menu item.
Definition: pxemenu.c:50
#define DBG(...)
Print a debugging message.
Definition: compiler.h:498
A menu.
Definition: menu.h:15
PXE boot menu.
Definition: dhcp.h:127
uint64_t index
Index of the first segment within the content.
Definition: pccrc.h:21
#define NULL
NULL pointer (VOID *)
Definition: Base.h:362
String functions.
int pxebs(struct net_device *netdev, unsigned int pxe_type)
Definition: dhcpmgmt.c:41
int iskey(void)
Check for available input on any console.
Definition: console.c:127
uint8_t desc_len
Description length.
Definition: dhcp.h:131
union @375 key
Sense key.
Definition: scsi.h:18
ANSI colours.
void * memset(void *dest, int character, size_t len) __nonnull