]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[autoboot] Connect SAN disk during a filename boot, if applicable
authorMichael Brown <mcb30@ipxe.org>
Thu, 27 Jan 2011 18:48:47 +0000 (18:48 +0000)
committerMichael Brown <mcb30@ipxe.org>
Thu, 27 Jan 2011 20:41:27 +0000 (20:41 +0000)
For performing installations direct to a SAN target, it can be very
useful to hook a SAN disk and then proceed to perform a filename boot.
For example, the user may wish to hook the (empty) SAN installation
disk and then boot into the OS installer via TFTP.  This provides an
alternative mechanism to using "keep-san" and relying on the BIOS to
fall through to boot from the installation media, which is unreliable
on many BIOSes.

When a root-path is specified in addition to a boot filename, attempt
to hook the root-path as a SAN disk before booting from the specified
filename.  Since the root-path may be used for non-SAN purposes
(e.g. an NFS root mount point), ignore the root-path if it contains a
URI scheme that we do not support.

Originally-implemented-by: Jarrod Johnson <jarrod.b.johnson@gmail.com>
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/hci/commands/sanboot_cmd.c
src/include/ipxe/errfile.h
src/include/usr/autoboot.h
src/include/usr/imgmgmt.h
src/usr/autoboot.c
src/usr/imgmgmt.c
src/usr/pxemenu.c

index 1f11cc2d0106ab53da2c4c68af60c74384d9a586..61bc5463eeb7d39270bf83c6e37f32964fc1ff18 100644 (file)
 
 #include <stdio.h>
 #include <string.h>
+#include <errno.h>
 #include <getopt.h>
 #include <ipxe/command.h>
 #include <ipxe/parseopt.h>
+#include <ipxe/uri.h>
 #include <usr/autoboot.h>
 
 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 */
index f3a8efb0331fae00f9fa6ef23e6d7b993eead378..24d5b310cd76592d082cea9a529480336881fac5 100644 (file)
@@ -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 )
 
 /** @} */
 
index 32763beee85fb7bb0a590561f2e8bd22111f5e61..a608b3dce7a850541f25317c69ba15396ff60b00 100644 (file)
@@ -11,12 +11,14 @@ FILE_LICENCE ( GPL2_OR_LATER );
 
 #include <ipxe/in.h>
 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 );
 
index 0c8c8cf72283a0a9efedacae1c24e84030f68101..0beab51393793c5e08bfee34967e46cb7df7f67b 100644 (file)
@@ -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 );
index df152e3ac7142bd0fb3f3bb44172f1607f970cc2..e7d6787a7b35a6bdd1003c462368abf00ea17cf4 100644 (file)
@@ -27,6 +27,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #include <ipxe/image.h>
 #include <ipxe/sanboot.h>
 #include <ipxe/uri.h>
+#include <ipxe/open.h>
 #include <ipxe/init.h>
 #include <usr/ifmgmt.h>
 #include <usr/route.h>
@@ -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;
 }
 
 /**
index 5e7629a609be620b484950367a3de0a63b9fb026..e958bc199c5dac7ebfc26c31f683ab46b91f3dd3 100644 (file)
@@ -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;
index bbd7670c584ec9b44caebd074d18362df4f54902..a9ea1f044c015cdefe5b35dcc7272b8cfd88a0ff 100644 (file)
@@ -31,6 +31,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #include <ipxe/keys.h>
 #include <ipxe/timer.h>
 #include <ipxe/process.h>
+#include <ipxe/uri.h>
 #include <usr/dhcpmgmt.h>
 #include <usr/autoboot.h>
 
@@ -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;
 }