[comboot] Allow for tail recursion of COMBOOT images

Multi-level menus via COMBOOT rely on the COMBOOT program being able
to exit and invoke a new COMBOOT program (the next menu).  This works,
but rapidly (within about five iterations) runs out of space in gPXE's
internal stack, since each new image is executed in a new function
context.

Fix by allowing tail recursion between images; an image can now
specify a replacement image for itself, and image_exec() will perform
the necessary tail recursion.
This commit is contained in:
Michael Brown
2009-02-17 00:47:35 +00:00
parent 349868b8fd
commit 8904cd55f1
8 changed files with 178 additions and 113 deletions

View File

@@ -35,6 +35,8 @@
#include <gpxe/process.h>
#include <gpxe/serial.h>
#include <gpxe/init.h>
#include <gpxe/image.h>
#include <usr/imgmgmt.h>
/** The "SYSLINUX" version string */
static char __data16_array ( syslinux_version, [] ) = "gPXE " VERSION;
@@ -67,10 +69,8 @@ extern void int22_wrapper ( void );
/* setjmp/longjmp context buffer used to return after loading an image */
jmp_buf comboot_return;
/* Command line to execute when returning via comboot_return
* with COMBOOT_RETURN_RUN_KERNEL
*/
char *comboot_kernel_cmdline;
/* Replacement image when exiting with COMBOOT_EXIT_RUN_KERNEL */
struct image *comboot_replacement_image;
/* Mode flags set by INT 22h AX=0017h */
static uint16_t comboot_graphics_mode = 0;
@@ -154,58 +154,81 @@ void comboot_force_text_mode ( void ) {
/**
* Run the kernel specified in comboot_kernel_cmdline
* Fetch kernel and optional initrd
*/
void comboot_run_kernel ( )
{
char *initrd;
comboot_force_text_mode ( );
DBG ( "COMBOOT: executing image '%s'\n", comboot_kernel_cmdline );
static int comboot_fetch_kernel ( char *kernel_file, char *cmdline ) {
struct image *kernel;
struct image *initrd = NULL;
char *initrd_file;
int rc;
/* Find initrd= parameter, if any */
if ( ( initrd = strstr ( comboot_kernel_cmdline, "initrd=" ) ) ) {
char old_char = '\0';
char *initrd_end = strchr( initrd, ' ' );
if ( ( initrd_file = strstr ( cmdline, "initrd=" ) ) != NULL ) {
char *initrd_end;
/* Replace space after end of parameter
* with a nul terminator if this is not
* the last parameter
*/
if ( initrd_end ) {
old_char = *initrd_end;
/* skip "initrd=" */
initrd_file += 7;
/* Find terminating space, if any, and replace with NUL */
initrd_end = strchr ( initrd_file, ' ' );
if ( initrd_end )
*initrd_end = '\0';
DBG ( "COMBOOT: fetching initrd '%s'\n", initrd_file );
/* Allocate and fetch initrd */
initrd = alloc_image();
if ( ! initrd ) {
DBG ( "COMBOOT: could not allocate initrd\n" );
rc = -ENOMEM;
goto err_alloc_initrd;
}
if ( ( rc = imgfetch ( initrd, initrd_file,
register_image ) ) != 0 ) {
DBG ( "COMBOOT: could not fetch initrd: %s\n",
strerror ( rc ) );
goto err_fetch_initrd;
}
/* Replace = with space to get 'initrd filename'
* command suitable for system()
*/
initrd[6] = ' ';
DBG( "COMBOOT: loading initrd '%s'\n", initrd );
system ( initrd );
/* Restore space after parameter */
if ( initrd_end ) {
*initrd_end = old_char;
}
/* Restore = */
initrd[6] = '=';
/* Restore space after initrd name, if applicable */
if ( initrd_end )
*initrd_end = ' ';
}
/* Load kernel */
DBG ( "COMBOOT: loading kernel '%s'\n", comboot_kernel_cmdline );
system ( comboot_kernel_cmdline );
DBG ( "COMBOOT: fetching kernel '%s'\n", kernel_file );
free ( comboot_kernel_cmdline );
/* Allocate and fetch kernel */
kernel = alloc_image();
if ( ! kernel ) {
DBG ( "COMBOOT: could not allocate kernel\n" );
rc = -ENOMEM;
goto err_alloc_kernel;
}
if ( ( rc = imgfetch ( kernel, kernel_file,
register_image ) ) != 0 ) {
DBG ( "COMBOOT: could not fetch kernel: %s\n",
strerror ( rc ) );
goto err_fetch_kernel;
}
if ( ( rc = image_set_cmdline ( kernel, cmdline ) ) != 0 ) {
DBG ( "COMBOOT: could not set kernel command line: %s\n",
strerror ( rc ) );
goto err_set_cmdline;
}
/* Boot */
system ( "boot" );
/* Store kernel as replacement image */
comboot_replacement_image = kernel;
DBG ( "COMBOOT: back from executing command\n" );
return 0;
err_set_cmdline:
err_fetch_kernel:
image_put ( kernel );
err_alloc_kernel:
err_fetch_initrd:
image_put ( initrd );
err_alloc_initrd:
return rc;
}
@@ -213,7 +236,7 @@ void comboot_run_kernel ( )
* Terminate program interrupt handler
*/
static __asmcall void int20 ( struct i386_all_regs *ix86 __unused ) {
longjmp ( comboot_return, COMBOOT_RETURN_EXIT );
longjmp ( comboot_return, COMBOOT_EXIT );
}
@@ -226,7 +249,7 @@ static __asmcall void int21 ( struct i386_all_regs *ix86 ) {
switch ( ix86->regs.ah ) {
case 0x00:
case 0x4C: /* Terminate program */
longjmp ( comboot_return, COMBOOT_RETURN_EXIT );
longjmp ( comboot_return, COMBOOT_EXIT );
break;
case 0x01: /* Get Key with Echo */
@@ -323,17 +346,15 @@ static __asmcall void int22 ( struct i386_all_regs *ix86 ) {
char cmd[len + 1];
copy_from_user ( cmd, cmd_u, 0, len + 1 );
DBG ( "COMBOOT: executing command '%s'\n", cmd );
comboot_kernel_cmdline = strdup ( cmd );
DBG ( "COMBOOT: returning to run image...\n" );
longjmp ( comboot_return, COMBOOT_RETURN_RUN_KERNEL );
system ( cmd );
DBG ( "COMBOOT: exiting after executing command...\n" );
longjmp ( comboot_return, COMBOOT_EXIT_COMMAND );
}
break;
case 0x0004: /* Run default command */
/* FIXME: just exit for now */
longjmp ( comboot_return, COMBOOT_RETURN_EXIT );
longjmp ( comboot_return, COMBOOT_EXIT_COMMAND );
break;
case 0x0005: /* Force text mode */
@@ -518,21 +539,21 @@ static __asmcall void int22 ( struct i386_all_regs *ix86 ) {
userptr_t cmd_u = real_to_user ( ix86->segs.es, ix86->regs.bx );
int file_len = strlen_user ( file_u, 0 );
int cmd_len = strlen_user ( cmd_u, 0 );
char file[file_len + 1 + cmd_len + 7 + 1];
char file[file_len + 1];
char cmd[cmd_len + 1];
memcpy( file, "kernel ", 7 );
copy_from_user ( file + 7, file_u, 0, file_len + 1 );
copy_from_user ( file, file_u, 0, file_len + 1 );
copy_from_user ( cmd, cmd_u, 0, cmd_len + 1 );
strcat ( file, " " );
strcat ( file, cmd );
DBG ( "COMBOOT: run kernel image '%s'\n", file );
comboot_kernel_cmdline = strdup ( file );
DBG ( "COMBOOT: returning to run image...\n" );
longjmp ( comboot_return, COMBOOT_RETURN_RUN_KERNEL );
DBG ( "COMBOOT: run kernel %s %s\n", file, cmd );
comboot_fetch_kernel ( file, cmd );
/* Technically, we should return if we
* couldn't load the kernel, but it's not safe
* to do that since we have just overwritten
* part of the COMBOOT program's memory space.
*/
DBG ( "COMBOOT: exiting to run kernel...\n" );
longjmp ( comboot_return, COMBOOT_EXIT_RUN_KERNEL );
}
break;