diff --git a/src/hci/commands/sanboot_cmd.c b/src/hci/commands/sanboot_cmd.c index 1f11cc2d01..61bc5463ee 100644 --- a/src/hci/commands/sanboot_cmd.c +++ b/src/hci/commands/sanboot_cmd.c @@ -18,9 +18,11 @@ #include #include +#include #include #include #include +#include #include FILE_LICENCE ( GPL2_OR_LATER ); @@ -52,23 +54,33 @@ static struct command_descriptor sanboot_cmd = static int sanboot_exec ( int argc, char **argv ) { struct sanboot_options opts; const char *root_path; + struct uri *uri; int rc; /* Parse options */ if ( ( rc = parse_options ( argc, argv, &sanboot_cmd, &opts ) ) != 0 ) - return rc; + goto err_parse_options; /* Parse root path */ root_path = argv[optind]; + uri = parse_uri ( root_path ); + if ( ! uri ) { + rc = -ENOMEM; + goto err_parse_uri; + } /* Boot from root path */ - if ( ( rc = boot_root_path ( root_path ) ) != 0 ) { + if ( ( rc = uriboot ( NULL, uri ) ) != 0 ) { printf ( "Could not boot from %s: %s\n", root_path, strerror ( rc ) ); - return rc; + goto err_uriboot; } - return 0; + err_uriboot: + uri_put ( uri ); + err_parse_uri: + err_parse_options: + return rc; } /** SAN commands */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index f3a8efb033..24d5b310cd 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -235,6 +235,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define ERRFILE_ifmgmt_cmd ( ERRFILE_OTHER | 0x001d0000 ) #define ERRFILE_fcmgmt_cmd ( ERRFILE_OTHER | 0x001e0000 ) #define ERRFILE_gdbstub_cmd ( ERRFILE_OTHER | 0x001f0000 ) +#define ERRFILE_sanboot_cmd ( ERRFILE_OTHER | 0x00200000 ) /** @} */ diff --git a/src/include/usr/autoboot.h b/src/include/usr/autoboot.h index 32763beee8..a608b3dce7 100644 --- a/src/include/usr/autoboot.h +++ b/src/include/usr/autoboot.h @@ -11,12 +11,14 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include struct net_device; +struct uri; +struct settings; +extern int uriboot ( struct uri *filename, struct uri *root_path ); +extern struct uri * +fetch_next_server_and_filename ( struct settings *settings ); extern int netboot ( struct net_device *netdev ); extern int autoboot ( void ); -extern int boot_next_server_and_filename ( struct in_addr next_server, - const char *filename ); -extern int boot_root_path ( const char *root_path ); extern int pxe_menu_boot ( struct net_device *netdev ); diff --git a/src/include/usr/imgmgmt.h b/src/include/usr/imgmgmt.h index 0c8c8cf722..0beab51393 100644 --- a/src/include/usr/imgmgmt.h +++ b/src/include/usr/imgmgmt.h @@ -11,6 +11,8 @@ FILE_LICENCE ( GPL2_OR_LATER ); struct image; +extern int imgdownload ( struct image *image, struct uri *uri, + int ( * image_register ) ( struct image *image ) ); extern int imgfetch ( struct image *image, const char *uri_string, int ( * image_register ) ( struct image *image ) ); extern int imgload ( struct image *image ); diff --git a/src/usr/autoboot.c b/src/usr/autoboot.c index df152e3ac7..e7d6787a7b 100644 --- a/src/usr/autoboot.c +++ b/src/usr/autoboot.c @@ -27,6 +27,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include #include #include #include @@ -57,30 +58,21 @@ static struct net_device * find_boot_netdev ( void ) { } /** - * Boot using next-server and filename + * Parse next-server and filename into a URI * - * @v filename Boot filename - * @ret rc Return status code + * @v next_server Next-server address + * @v filename Filename + * @ret uri URI, or NULL on failure */ -int boot_next_server_and_filename ( struct in_addr next_server, - const char *filename ) { +static struct uri * parse_next_server_and_filename ( struct in_addr next_server, + const char *filename ) { struct uri *uri; - struct image *image; - char buf[ 23 /* tftp://xxx.xxx.xxx.xxx/ */ + - ( 3 * strlen(filename) ) /* completely URI-encoded */ - + 1 /* NUL */ ]; - int filename_is_absolute; - int rc; + struct uri *tmp; - /* Construct URI */ + /* Parse filename */ uri = parse_uri ( filename ); - if ( ! uri ) { - printf ( "Could not parse \"%s\"\n", filename ); - rc = -ENOMEM; - goto err_parse_uri; - } - filename_is_absolute = uri_is_absolute ( uri ); - uri_put ( uri ); + if ( ! uri ) + return NULL; /* Construct a tftp:// URI for the filename, if applicable. * We can't just rely on the current working URI, because the @@ -88,41 +80,17 @@ int boot_next_server_and_filename ( struct in_addr next_server, * filenames with and without initial slashes, which is * significant for TFTP. */ - if ( ! filename_is_absolute ) { - snprintf ( buf, sizeof ( buf ), "tftp://%s/", - inet_ntoa ( next_server ) ); - uri_encode ( filename, buf + strlen ( buf ), - sizeof ( buf ) - strlen ( buf ), URI_PATH ); - filename = buf; + if ( ! uri_is_absolute ( uri ) ) { + tmp = uri; + tmp->scheme = "tftp"; + tmp->host = inet_ntoa ( next_server ); + uri = uri_dup ( tmp ); + uri_put ( tmp ); + if ( ! uri ) + return NULL; } - /* Download and boot image */ - image = alloc_image(); - if ( ! image ) { - printf ( "Could not allocate image\n" ); - rc = -ENOMEM; - goto err_alloc_image; - } - if ( ( rc = imgfetch ( image, filename, - register_and_autoload_image ) ) != 0 ) { - printf ( "Could not fetch image: %s\n", strerror ( rc ) ); - goto err_imgfetch; - } - if ( ( rc = imgexec ( image ) ) != 0 ) { - printf ( "Could not execute image: %s\n", strerror ( rc ) ); - goto err_imgexec; - } - - /* Drop image reference */ - image_put ( image ); - return 0; - - err_imgexec: - err_imgfetch: - image_put ( image ); - err_alloc_image: - err_parse_uri: - return rc; + return uri; } /** The "keep-san" setting */ @@ -142,71 +110,99 @@ struct setting skip_san_boot_setting __setting = { }; /** - * Boot using root path + * Boot from filename and root-path URIs * + * @v filename Filename * @v root_path Root path * @ret rc Return status code */ -int boot_root_path ( const char *root_path ) { - struct uri *uri; +int uriboot ( struct uri *filename, struct uri *root_path ) { + struct image *image; int drive; int rc; - /* Parse URI */ - uri = parse_uri ( root_path ); - if ( ! uri ) { - printf ( "Could not parse \"%s\"\n", root_path ); + /* Allocate image */ + image = alloc_image(); + if ( ! image ) { + printf ( "Could not allocate image\n" ); rc = -ENOMEM; - goto err_parse_uri; + goto err_alloc_image; + } + + /* Treat empty URIs as absent */ + if ( filename && ( ! filename->path ) ) + filename = NULL; + if ( root_path && ( ! uri_is_absolute ( root_path ) ) ) + root_path = NULL; + + /* If we have both a filename and a root path, ignore an + * unsupported URI scheme in the root path, since it may + * represent an NFS root. + */ + if ( filename && root_path && + ( xfer_uri_opener ( root_path->scheme ) == NULL ) ) { + printf ( "Ignoring unsupported root path\n" ); + root_path = NULL; } - /* Hook SAN device */ - if ( ( drive = san_hook ( uri, 0 ) ) < 0 ) { - rc = drive; - printf ( "Could not open SAN device: %s\n", - strerror ( rc ) ); - goto err_open; + /* Hook SAN device, if applicable */ + if ( root_path ) { + drive = san_hook ( root_path, 0 ); + if ( drive < 0 ) { + rc = drive; + printf ( "Could not open SAN device: %s\n", + strerror ( rc ) ); + goto err_san_hook; + } + printf ( "Registered as SAN device %#02x\n", drive ); + } else { + drive = -ENODEV; } - printf ( "Registered as SAN device %#02x\n", drive ); - /* Describe SAN device */ - if ( ( rc = san_describe ( drive ) ) != 0 ) { + /* Describe SAN device, if applicable */ + if ( ( drive >= 0 ) && ( ( rc = san_describe ( drive ) ) != 0 ) ) { printf ( "Could not describe SAN device %#02x: %s\n", drive, strerror ( rc ) ); - goto err_describe; + goto err_san_describe; } - /* Boot from SAN device */ - if ( fetch_intz_setting ( NULL, &skip_san_boot_setting) != 0 ) { - printf ( "Skipping boot from SAN device %#02x\n", drive ); + /* Attempt filename or SAN boot as applicable */ + if ( filename ) { + if ( ( rc = imgdownload ( image, filename, + register_and_autoexec_image ) ) !=0){ + printf ( "Could not chain image: %s\n", + strerror ( rc ) ); + } + } else if ( root_path ) { + if ( fetch_intz_setting ( NULL, &skip_san_boot_setting) == 0 ) { + printf ( "Booting from SAN device %#02x\n", drive ); + rc = san_boot ( drive ); + printf ( "Boot from SAN device %#02x failed: %s\n", + drive, strerror ( rc ) ); + } else { + printf ( "Skipping boot from SAN device %#02x\n", + drive ); + rc = 0; + } } else { - printf ( "Booting from SAN device %#02x\n", drive ); - rc = san_boot ( drive ); - printf ( "Boot from SAN device %#02x failed: %s\n", - drive, strerror ( rc ) ); + printf ( "No filename or root path specified\n" ); + rc = -ENOENT; } - /* Leave drive registered, if instructed to do so */ - if ( fetch_intz_setting ( NULL, &keep_san_setting ) != 0 ) { - printf ( "Preserving connection to SAN device %#02x\n", - drive ); - goto err_keep_san; + err_san_describe: + /* Unhook SAN device, if applicable */ + if ( drive >= 0 ) { + if ( fetch_intz_setting ( NULL, &keep_san_setting ) == 0 ) { + printf ( "Unregistering SAN device %#02x\n", drive ); + san_unhook ( drive ); + } else { + printf ( "Preserving connection to SAN device %#02x\n", + drive ); + } } - - /* Unhook SAN deivce */ - printf ( "Unregistering SAN device %#02x\n", drive ); - san_unhook ( drive ); - - /* Drop URI reference */ - uri_put ( uri ); - - return 0; - - err_keep_san: - err_describe: - err_open: - uri_put ( uri ); - err_parse_uri: + err_san_hook: + image_put ( image ); + err_alloc_image: return rc; } @@ -227,12 +223,53 @@ static void close_all_netdevs ( void ) { } /** - * Boot from a network device + * Fetch next-server and filename settings into a URI * - * @v netdev Network device - * @ret rc Return status code + * @v settings Settings block + * @ret uri URI, or NULL on failure */ -int netboot ( struct net_device *netdev ) { +struct uri * fetch_next_server_and_filename ( struct settings *settings ) { + struct in_addr next_server; + char filename[256]; + + /* Fetch next-server setting */ + fetch_ipv4_setting ( settings, &next_server_setting, &next_server ); + if ( next_server.s_addr ) + printf ( "Next server: %s\n", inet_ntoa ( next_server ) ); + + /* Fetch filename setting */ + fetch_string_setting ( settings, &filename_setting, + filename, sizeof ( filename ) ); + if ( filename[0] ) + printf ( "Filename: %s\n", filename ); + + return parse_next_server_and_filename ( next_server, filename ); +} + +/** + * Fetch root-path setting into a URI + * + * @v settings Settings block + * @ret uri URI, or NULL on failure + */ +static struct uri * fetch_root_path ( struct settings *settings ) { + char root_path[256]; + + /* Fetch root-path setting */ + fetch_string_setting ( settings, &root_path_setting, + root_path, sizeof ( root_path ) ); + if ( root_path[0] ) + printf ( "Root path: %s\n", root_path ); + + return parse_uri ( root_path ); +} + +/** + * Check whether or not we have a usable PXE menu + * + * @ret have_menu A usable PXE menu is present + */ +static int have_pxe_menu ( void ) { struct setting vendor_class_id_setting = { .tag = DHCP_VENDOR_CLASS_ID }; struct setting pxe_discovery_control_setting @@ -240,8 +277,28 @@ int netboot ( struct net_device *netdev ) { struct setting pxe_boot_menu_setting = { .tag = DHCP_PXE_BOOT_MENU }; char buf[256]; - struct in_addr next_server; unsigned int pxe_discovery_control; + + fetch_string_setting ( NULL, &vendor_class_id_setting, + buf, sizeof ( buf ) ); + pxe_discovery_control = + fetch_uintz_setting ( NULL, &pxe_discovery_control_setting ); + + return ( ( strcmp ( buf, "PXEClient" ) == 0 ) && + setting_exists ( NULL, &pxe_boot_menu_setting ) && + ( ! ( ( pxe_discovery_control & PXEBS_SKIP ) && + setting_exists ( NULL, &filename_setting ) ) ) ); +} + +/** + * Boot from a network device + * + * @v netdev Network device + * @ret rc Return status code + */ +int netboot ( struct net_device *netdev ) { + struct uri *filename; + struct uri *root_path; int rc; /* Close all other network devices */ @@ -249,44 +306,42 @@ int netboot ( struct net_device *netdev ) { /* Open device and display device status */ if ( ( rc = ifopen ( netdev ) ) != 0 ) - return rc; + goto err_ifopen; ifstat ( netdev ); /* Configure device via DHCP */ if ( ( rc = dhcp ( netdev ) ) != 0 ) - return rc; + goto err_dhcp; route(); /* Try PXE menu boot, if applicable */ - fetch_string_setting ( NULL, &vendor_class_id_setting, - buf, sizeof ( buf ) ); - pxe_discovery_control = - fetch_uintz_setting ( NULL, &pxe_discovery_control_setting ); - if ( ( strcmp ( buf, "PXEClient" ) == 0 ) && - setting_exists ( NULL, &pxe_boot_menu_setting ) && - ( ! ( ( pxe_discovery_control & PXEBS_SKIP ) && - setting_exists ( NULL, &filename_setting ) ) ) ) { + if ( have_pxe_menu() ) { printf ( "Booting from PXE menu\n" ); - return pxe_menu_boot ( netdev ); + rc = pxe_menu_boot ( netdev ); + goto err_pxe_menu_boot; } - /* Try to download and boot whatever we are given as a filename */ - fetch_ipv4_setting ( NULL, &next_server_setting, &next_server ); - fetch_string_setting ( NULL, &filename_setting, buf, sizeof ( buf ) ); - if ( buf[0] ) { - printf ( "Booting from filename \"%s\"\n", buf ); - return boot_next_server_and_filename ( next_server, buf ); - } - - /* No filename; try the root path */ - fetch_string_setting ( NULL, &root_path_setting, buf, sizeof ( buf ) ); - if ( buf[0] ) { - printf ( "Booting from root path \"%s\"\n", buf ); - return boot_root_path ( buf ); - } - - printf ( "No filename or root path specified\n" ); - return -ENOENT; + /* Fetch next server, filename and root path */ + filename = fetch_next_server_and_filename ( NULL ); + if ( ! filename ) + goto err_filename; + root_path = fetch_root_path ( NULL ); + if ( ! root_path ) + goto err_root_path; + + /* Boot using next server, filename and root path */ + if ( ( rc = uriboot ( filename, root_path ) ) != 0 ) + goto err_uriboot; + + err_uriboot: + uri_put ( root_path ); + err_root_path: + uri_put ( filename ); + err_filename: + err_pxe_menu_boot: + err_dhcp: + err_ifopen: + return rc; } /** diff --git a/src/usr/imgmgmt.c b/src/usr/imgmgmt.c index 5e7629a609..e958bc199c 100644 --- a/src/usr/imgmgmt.c +++ b/src/usr/imgmgmt.c @@ -36,24 +36,21 @@ FILE_LICENCE ( GPL2_OR_LATER ); */ /** - * Fetch an image + * Download an image * - * @v uri_string URI as a string (e.g. "http://www.nowhere.com/vmlinuz") - * @v name Name for image, or NULL - * @v register_image Image registration routine + * @v image Image + * @v uri URI + * @v image_register Action to take upon a successful download * @ret rc Return status code */ -int imgfetch ( struct image *image, const char *uri_string, - int ( * image_register ) ( struct image *image ) ) { - char uri_string_redacted[ strlen ( uri_string ) + 3 /* "***" */ - + 1 /* NUL */ ]; - struct uri *uri; +int imgdownload ( struct image *image, struct uri *uri, + int ( * image_register ) ( struct image *image ) ) { + size_t len = ( unparse_uri ( NULL, 0, uri, URI_ALL ) + 1 ); + char uri_string_redacted[len]; const char *password; int rc; - if ( ! ( uri = parse_uri ( uri_string ) ) ) - return -ENOMEM; - + /* Set image URI */ image_set_uri ( image, uri ); /* Redact password portion of URI, if necessary */ @@ -64,9 +61,35 @@ int imgfetch ( struct image *image, const char *uri_string, uri, URI_ALL ); uri->password = password; + /* Create downloader */ if ( ( rc = create_downloader ( &monojob, image, image_register, - LOCATION_URI, uri ) ) == 0 ) - rc = monojob_wait ( uri_string_redacted ); + LOCATION_URI, uri ) ) != 0 ) + return rc; + + /* Wait for download to complete */ + if ( ( rc = monojob_wait ( uri_string_redacted ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Fetch an image + * + * @v image Image + * @v uri_string URI as a string (e.g. "http://www.nowhere.com/vmlinuz") + * @v image_register Action to take upon a successful fetch + * @ret rc Return status code + */ +int imgfetch ( struct image *image, const char *uri_string, + int ( * image_register ) ( struct image *image ) ) { + struct uri *uri; + int rc; + + if ( ! ( uri = parse_uri ( uri_string ) ) ) + return -ENOMEM; + + rc = imgdownload ( image, uri, image_register ); uri_put ( uri ); return rc; diff --git a/src/usr/pxemenu.c b/src/usr/pxemenu.c index bbd7670c58..a9ea1f044c 100644 --- a/src/usr/pxemenu.c +++ b/src/usr/pxemenu.c @@ -31,6 +31,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include #include #include @@ -346,8 +347,7 @@ int pxe_menu_boot ( struct net_device *netdev ) { struct pxe_menu *menu; unsigned int pxe_type; struct settings *pxebs_settings; - struct in_addr next_server; - char filename[256]; + struct uri *uri; int rc; /* Parse and allocate boot menu */ @@ -372,12 +372,15 @@ int pxe_menu_boot ( struct net_device *netdev ) { if ( ( rc = pxebs ( netdev, pxe_type ) ) != 0 ) return rc; - /* Attempt boot */ + /* Fetch next server and filename */ pxebs_settings = find_settings ( PXEBS_SETTINGS_NAME ); assert ( pxebs_settings ); - fetch_ipv4_setting ( pxebs_settings, &next_server_setting, - &next_server ); - fetch_string_setting ( pxebs_settings, &filename_setting, - filename, sizeof ( filename ) ); - return boot_next_server_and_filename ( next_server, filename ); + uri = fetch_next_server_and_filename ( pxebs_settings ); + if ( ! uri ) + return -ENOMEM; + + /* Attempt boot */ + rc = uriboot ( uri, NULL ); + uri_put ( uri ); + return rc; }