911d4e9ac1dc8eb5d9a0230205ae4af4008fe768
[people/lynusvaz/gpxe.git] / src / usr / pxemenu.c
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., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <stdint.h>
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <byteswap.h>
26 #include <curses.h>
27 #include <console.h>
28 #include <gpxe/dhcp.h>
29 #include <gpxe/keys.h>
30 #include <gpxe/timer.h>
31 #include <gpxe/process.h>
32 #include <usr/dhcpmgmt.h>
33 #include <usr/autoboot.h>
34
35 /** @file
36  *
37  * PXE Boot Menus
38  *
39  */
40
41 /* Colour pairs */
42 #define CPAIR_NORMAL    1
43 #define CPAIR_SELECT    2
44
45 /** A PXE boot menu item */
46 struct pxe_menu_item {
47         /** Boot Server type */
48         unsigned int type;
49         /** Description */
50         char *desc;
51 };
52
53 /**
54  * A PXE boot menu
55  *
56  * This structure encapsulates the menu information provided via DHCP
57  * options.
58  */
59 struct pxe_menu {
60         /** Prompt string (optional) */
61         const char *prompt;
62         /** Timeout (in seconds)
63          *
64          * Negative indicates no timeout (i.e. wait indefinitely)
65          */
66         int timeout;
67         /** Number of menu items */
68         unsigned int num_items;
69         /** Selected menu item */
70         unsigned int selection;
71         /** Menu items */
72         struct pxe_menu_item items[0];
73 };
74
75 /**
76  * Parse and allocate PXE boot menu
77  *
78  * @v menu              PXE boot menu to fill in
79  * @ret rc              Return status code
80  *
81  * It is the callers responsibility to eventually free the allocated
82  * boot menu.
83  */
84 static int pxe_menu_parse ( struct pxe_menu **menu ) {
85         struct setting pxe_boot_menu_prompt_setting =
86                 { .tag = DHCP_PXE_BOOT_MENU_PROMPT };
87         struct setting pxe_boot_menu_setting =
88                 { .tag = DHCP_PXE_BOOT_MENU };
89         uint8_t raw_menu[256];
90         int raw_prompt_len;
91         int raw_menu_len;
92         struct dhcp_pxe_boot_menu *raw_menu_item;
93         struct dhcp_pxe_boot_menu_prompt *raw_menu_prompt;
94         void *raw_menu_end;
95         unsigned int num_menu_items;
96         unsigned int i;
97         int rc;
98
99         /* Fetch raw menu */
100         memset ( raw_menu, 0, sizeof ( raw_menu ) );
101         if ( ( raw_menu_len = fetch_setting ( NULL, &pxe_boot_menu_setting,
102                                               raw_menu,
103                                               sizeof ( raw_menu ) ) ) < 0 ) {
104                 rc = raw_menu_len;
105                 DBG ( "Could not retrieve raw PXE boot menu: %s\n",
106                       strerror ( rc ) );
107                 return rc;
108         }
109         if ( raw_menu_len >= ( int ) sizeof ( raw_menu ) ) {
110                 DBG ( "Raw PXE boot menu too large for buffer\n" );
111                 return -ENOSPC;
112         }
113         raw_menu_end = ( raw_menu + raw_menu_len );
114
115         /* Fetch raw prompt length */
116         raw_prompt_len = fetch_setting_len ( NULL,
117                                              &pxe_boot_menu_prompt_setting );
118         if ( raw_prompt_len < 0 )
119                 raw_prompt_len = 0;
120
121         /* Count menu items */
122         num_menu_items = 0;
123         raw_menu_item = ( ( void * ) raw_menu );
124         while ( 1 ) {
125                 if ( ( ( ( void * ) raw_menu_item ) +
126                        sizeof ( *raw_menu_item ) ) > raw_menu_end )
127                         break;
128                 if ( ( ( ( void * ) raw_menu_item ) +
129                        sizeof ( *raw_menu_item ) +
130                        raw_menu_item->desc_len ) > raw_menu_end )
131                         break;
132                 num_menu_items++;
133                 raw_menu_item = ( ( ( void * ) raw_menu_item ) +
134                                   sizeof ( *raw_menu_item ) +
135                                   raw_menu_item->desc_len );
136         }
137
138         /* Allocate space for parsed menu */
139         *menu = zalloc ( sizeof ( **menu ) +
140                          ( num_menu_items * sizeof ( (*menu)->items[0] ) ) +
141                          raw_menu_len + 1 /* NUL */ +
142                          raw_prompt_len + 1 /* NUL */ );
143         if ( ! *menu ) {
144                 DBG ( "Could not allocate PXE boot menu\n" );
145                 return -ENOMEM;
146         }
147
148         /* Fill in parsed menu */
149         (*menu)->num_items = num_menu_items;
150         raw_menu_item = ( ( ( void * ) (*menu) ) + sizeof ( **menu ) +
151                           ( num_menu_items * sizeof ( (*menu)->items[0] ) ) );
152         memcpy ( raw_menu_item, raw_menu, raw_menu_len );
153         for ( i = 0 ; i < num_menu_items ; i++ ) {
154                 (*menu)->items[i].type = ntohs ( raw_menu_item->type );
155                 (*menu)->items[i].desc = raw_menu_item->desc;
156                 /* Set type to 0; this ensures that the description
157                  * for the previous menu item is NUL-terminated.
158                  * (Final item is NUL-terminated anyway.)
159                  */
160                 raw_menu_item->type = 0;
161                 raw_menu_item = ( ( ( void * ) raw_menu_item ) +
162                                   sizeof ( *raw_menu_item ) +
163                                   raw_menu_item->desc_len );
164         }
165         if ( raw_prompt_len ) {
166                 raw_menu_prompt = ( ( ( void * ) raw_menu_item ) +
167                                     1 /* NUL */ );
168                 fetch_setting ( NULL, &pxe_boot_menu_prompt_setting,
169                                 raw_menu_prompt, raw_prompt_len );
170                 (*menu)->timeout =
171                         ( ( raw_menu_prompt->timeout == 0xff ) ?
172                           -1 : raw_menu_prompt->timeout );
173                 (*menu)->prompt = raw_menu_prompt->prompt;
174         } else {
175                 (*menu)->timeout = -1;
176         }
177
178         return 0;
179 }
180
181 /**
182  * Draw PXE boot menu item
183  *
184  * @v menu              PXE boot menu
185  * @v index             Index of item to draw
186  * @v selected          Item is selected
187  */
188 static void pxe_menu_draw_item ( struct pxe_menu *menu,
189                                  unsigned int index, int selected ) {
190         char buf[COLS+1];
191         size_t len;
192         unsigned int row;
193
194         /* Prepare space-padded row content */
195         len = snprintf ( buf, sizeof ( buf ), " %c. %s",
196                          ( 'A' + index ), menu->items[index].desc );
197         while ( len < ( sizeof ( buf ) - 1 ) )
198                 buf[len++] = ' ';
199         buf[ sizeof ( buf ) - 1 ] = '\0';
200
201         /* Draw row */
202         row = ( LINES - menu->num_items + index );
203         color_set ( ( selected ? CPAIR_SELECT : CPAIR_NORMAL ), NULL );
204         mvprintw ( row, 0, "%s", buf );
205         move ( row, 1 );
206 }
207
208 /**
209  * Make selection from PXE boot menu
210  *
211  * @v menu              PXE boot menu
212  * @ret rc              Return status code
213  */
214 static int pxe_menu_select ( struct pxe_menu *menu ) {
215         int key;
216         unsigned int key_selection;
217         unsigned int i;
218         int rc = 0;
219
220         /* Initialise UI */
221         initscr();
222         start_color();
223         init_pair ( CPAIR_NORMAL, COLOR_WHITE, COLOR_BLACK );
224         init_pair ( CPAIR_SELECT, COLOR_BLACK, COLOR_WHITE );
225         color_set ( CPAIR_NORMAL, NULL );
226
227         /* Draw initial menu */
228         for ( i = 0 ; i < menu->num_items ; i++ )
229                 printf ( "\n" );
230         for ( i = 0 ; i < menu->num_items ; i++ )
231                 pxe_menu_draw_item ( menu, ( menu->num_items - i - 1 ), 0 );
232
233         while ( 1 ) {
234
235                 /* Highlight currently selected item */
236                 pxe_menu_draw_item ( menu, menu->selection, 1 );
237
238                 /* Wait for keyboard input */
239                 while ( ! iskey() )
240                         step();
241                 key = getkey();
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 ) {
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();
305                         if ( key == KEY_F8 ) {
306                                 /* Display menu */
307                                 printf ( "\n" );
308                                 return pxe_menu_select ( menu );
309                         } else if ( key == CTRL_C ) {
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 in_addr next_server;
348         char filename[256];
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         /* Attempt boot */
374         pxebs_settings = find_settings ( PXEBS_SETTINGS_NAME );
375         assert ( pxebs_settings );
376         fetch_ipv4_setting ( pxebs_settings, &next_server_setting,
377                              &next_server );
378         fetch_string_setting ( pxebs_settings, &filename_setting,
379                                filename, sizeof ( filename ) );
380         return boot_next_server_and_filename ( next_server, filename );
381 }