|
|
1.1 root 1: /*
2: * Copyright (C) 2009 Michael Brown <[email protected]>.
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 <ipxe/console.h>
30: #include <ipxe/dhcp.h>
31: #include <ipxe/keys.h>
32: #include <ipxe/timer.h>
33: #include <ipxe/uri.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: key = getkey ( 0 );
242:
243: /* Unhighlight currently selected item */
244: pxe_menu_draw_item ( menu, menu->selection, 0 );
245:
246: /* Act upon key */
247: if ( ( key == CR ) || ( key == LF ) ) {
248: pxe_menu_draw_item ( menu, menu->selection, 1 );
249: break;
250: } else if ( ( key == CTRL_C ) || ( key == ESC ) ) {
251: rc = -ECANCELED;
252: break;
253: } else if ( key == KEY_UP ) {
254: if ( menu->selection > 0 )
255: menu->selection--;
256: } else if ( key == KEY_DOWN ) {
257: if ( menu->selection < ( menu->num_items - 1 ) )
258: menu->selection++;
259: } else if ( ( key < KEY_MIN ) &&
260: ( ( key_selection = ( toupper ( key ) - 'A' ) )
261: < menu->num_items ) ) {
262: menu->selection = key_selection;
263: pxe_menu_draw_item ( menu, menu->selection, 1 );
264: break;
265: }
266: }
267:
268: /* Shut down UI */
269: endwin();
270:
271: return rc;
272: }
273:
274: /**
275: * Prompt for (and make selection from) PXE boot menu
276: *
277: * @v menu PXE boot menu
278: * @ret rc Return status code
279: */
280: static int pxe_menu_prompt_and_select ( struct pxe_menu *menu ) {
281: unsigned long start = currticks();
282: unsigned long now;
283: unsigned long elapsed;
284: size_t len = 0;
285: int key;
286: int rc = 0;
287:
288: /* Display menu immediately, if specified to do so */
289: if ( menu->timeout < 0 ) {
290: if ( menu->prompt )
291: printf ( "%s\n", menu->prompt );
292: return pxe_menu_select ( menu );
293: }
294:
295: /* Display prompt, if specified */
296: if ( menu->prompt )
297: printf ( "%s", menu->prompt );
298:
299: /* Wait for timeout, if specified */
300: while ( menu->timeout > 0 ) {
301: if ( ! len )
302: len = printf ( " (%d)", menu->timeout );
303: if ( iskey() ) {
304: key = getkey ( 0 );
305: if ( key == KEY_F8 ) {
306: /* Display menu */
307: printf ( "\n" );
308: return pxe_menu_select ( menu );
309: } else if ( ( key == CTRL_C ) || ( key == ESC ) ) {
310: /* Abort */
311: rc = -ECANCELED;
312: break;
313: } else {
314: /* Stop waiting */
315: break;
316: }
317: }
318: now = currticks();
319: elapsed = ( now - start );
320: if ( elapsed >= TICKS_PER_SEC ) {
321: menu->timeout -= 1;
322: do {
323: printf ( "\b \b" );
324: } while ( --len );
325: start = now;
326: }
327: }
328:
329: /* Return with default option selected */
330: printf ( "\n" );
331: return rc;
332: }
333:
334: /**
335: * Boot using PXE boot menu
336: *
337: * @ret rc Return status code
338: *
339: * Note that a success return status indicates that a PXE boot menu
340: * item has been selected, and that the DHCP session should perform a
341: * boot server request/ack.
342: */
343: int pxe_menu_boot ( struct net_device *netdev ) {
344: struct pxe_menu *menu;
345: unsigned int pxe_type;
346: struct settings *pxebs_settings;
347: struct uri *uri;
348: int rc;
349:
350: /* Parse and allocate boot menu */
351: if ( ( rc = pxe_menu_parse ( &menu ) ) != 0 )
352: return rc;
353:
354: /* Make selection from boot menu */
355: if ( ( rc = pxe_menu_prompt_and_select ( menu ) ) != 0 ) {
356: free ( menu );
357: return rc;
358: }
359: pxe_type = menu->items[menu->selection].type;
360:
361: /* Free boot menu */
362: free ( menu );
363:
364: /* Return immediately if local boot selected */
365: if ( ! pxe_type )
366: return 0;
367:
368: /* Attempt PXE Boot Server Discovery */
369: if ( ( rc = pxebs ( netdev, pxe_type ) ) != 0 )
370: return rc;
371:
372: /* Fetch next server and filename */
373: pxebs_settings = find_settings ( PXEBS_SETTINGS_NAME );
374: assert ( pxebs_settings );
375: uri = fetch_next_server_and_filename ( pxebs_settings );
376: if ( ! uri )
377: return -ENOMEM;
378:
379: /* Attempt boot */
380: rc = uriboot ( uri, NULL );
381: uri_put ( uri );
382: return rc;
383: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.