diff --git a/crates/boot/src/autoconfigure.rs b/crates/boot/src/autoconfigure.rs index 28e69cb..68b48e3 100644 --- a/crates/boot/src/autoconfigure.rs +++ b/crates/boot/src/autoconfigure.rs @@ -1,8 +1,11 @@ +use alloc::boxed::Box; use anyhow::{Context, Result}; use edera_sprout_config::RootConfiguration; use log::warn; +use uefi::Handle; use uefi::fs::FileSystem; use uefi::proto::device_path::DevicePath; +use uefi::proto::loaded_image::LoadedImage; use uefi::proto::media::fs::SimpleFileSystem; /// bls: autodetect and configure BLS-enabled filesystems. @@ -16,13 +19,47 @@ pub mod linux; /// windows: autodetect and configure Windows boot configurations. pub mod windows; +/// Find the filesystems that systemd-boot reads BLS entries from: the partition that Sprout +/// was loaded from, and the extended boot loader partition of the same disk. +fn strict_bls_sources(loaded_image_path: &DevicePath) -> (Option, Option) { + let own = eficore::handle::open_shared::(uefi::boot::image_handle()) + .ok() + .and_then(|image| image.device()); + let xbootldr = match eficore::xbootldr::find_xbootldr(loaded_image_path) { + Ok(Some(path)) => uefi::boot::locate_device_path::(&mut &*path).ok(), + Ok(None) => None, + Err(error) => { + warn!("unable to find the xbootldr partition: {:#}", error); + None + } + }; + (own, xbootldr) +} + /// Generate a [RootConfiguration] based on the environment. /// Intakes a `config` to use as the basis of the autoconfiguration. -pub fn autoconfigure(config: &mut RootConfiguration) -> Result<()> { +/// With `strict`, only the partition Sprout was loaded from and the extended boot loader +/// partition of its disk are read for BLS entries, as one generator, like systemd-boot does. +pub fn autoconfigure( + config: &mut RootConfiguration, + strict: bool, + loaded_image_path: &DevicePath, +) -> Result<()> { // Find all the filesystems that are on the system. let filesystem_handles = uefi::boot::find_handles::().context("unable to scan filesystems")?; + // The partitions that BLS entries are read from in strict mode. + let (own_handle, xbootldr_handle) = if strict { + strict_bls_sources(loaded_image_path) + } else { + (None, None) + }; + // The device path of the partition of Sprout, which names the strict generator. + let mut own_root: Option> = None; + // Whether either of the partitions has BLS entries, in strict mode. + let mut strict_found = false; + // For each filesystem that was detected, scan it for supported autoconfig mechanisms. for handle in filesystem_handles { // Acquire the device path root for the filesystem. @@ -51,15 +88,37 @@ pub fn autoconfigure(config: &mut RootConfiguration) -> Result<()> { // A scan that fails is skipped, as a filesystem that can't be read should not // prevent autoconfiguring the other filesystems. - // Scan the filesystem for BLS supported configurations. - let bls_found = match bls::scan(&mut filesystem, &root, config) { - Ok(found) => found, - Err(error) => { - warn!( - "unable to scan filesystem for bls configurations: {:#}", - error - ); - false + // Scan the filesystem for BLS supported configurations. In strict mode, only the + // partitions that systemd-boot reads get entries, and they get a single generator. + // The other filesystems that have BLS are still not scanned for Linux configurations. + let is_source = Some(handle) == own_handle || Some(handle) == xbootldr_handle; + if strict && Some(handle) == own_handle { + own_root = Some(root.to_boxed()); + } + let bls_found = if strict { + match bls::detect(&mut filesystem) { + Ok(found) => { + strict_found |= found && is_source; + found + } + Err(error) => { + warn!( + "unable to scan filesystem for bls configurations: {:#}", + error + ); + false + } + } + } else { + match bls::scan(&mut filesystem, &root, config) { + Ok(found) => found, + Err(error) => { + warn!( + "unable to scan filesystem for bls configurations: {:#}", + error + ); + false + } } }; @@ -80,5 +139,14 @@ pub fn autoconfigure(config: &mut RootConfiguration) -> Result<()> { } } + // In strict mode, one generator reads both partitions, with the root of Sprout's partition. + if strict + && strict_found + && let Some(root) = own_root + && let Err(error) = bls::add_generator(config, &root, true) + { + warn!("unable to add the bls generator: {:#}", error); + } + Ok(()) } diff --git a/crates/boot/src/autoconfigure/bls.rs b/crates/boot/src/autoconfigure/bls.rs index 3ee9de3..c6bcc33 100644 --- a/crates/boot/src/autoconfigure/bls.rs +++ b/crates/boot/src/autoconfigure/bls.rs @@ -18,12 +18,8 @@ use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly}; /// by the BLS generator to chainload entries. const BLS_CHAINLOAD_ACTION_PREFIX: &str = "bls-chainload-"; -/// Scan the specified `filesystem` for BLS configurations. -pub fn scan( - filesystem: &mut FileSystem, - root: &DevicePath, - config: &mut RootConfiguration, -) -> Result { +/// Detect whether the `filesystem` has a BLS supported configuration. +pub fn detect(filesystem: &mut FileSystem) -> Result { // BLS has a loader.conf file that can specify its own auto-entries mechanism. let bls_loader_conf_path = Path::new(cstr16!("\\loader\\loader.conf")); // BLS also has an entries directory that can specify explicit entries. @@ -31,17 +27,6 @@ pub fn scan( // BLS Type #2 entries are unified kernel images in the EFI/Linux directory. let bls_uki_path = Path::new(cstr16!("\\EFI\\Linux")); - // Convert the device path root to a string we can use in the configuration. - let mut root = root - .to_string16(DisplayOnly(false), AllowShortcuts(false)) - .context("unable to convert device root to string")? - .to_string(); - // Add a trailing forward-slash to the root to ensure the device root is completed. - root.push('/'); - - // Generate a unique hash of the root path. - let root_unique_hash = unique_hash(&root); - // Whether we have a loader.conf file. let has_loader_conf = filesystem .try_exists(bls_loader_conf_path) @@ -86,9 +71,26 @@ pub fn scan( // Detect if a BLS supported configuration is on this filesystem. // We check loader.conf, the entries directory and unified kernel images, as only one of // them is required. - if !(has_loader_conf || has_entries_dir || has_ukis) { - return Ok(false); - } + Ok(has_loader_conf || has_entries_dir || has_ukis) +} + +/// Add the BLS generator and its action for the partition with the device path `root`. +/// In `strict` mode, the generator also reads the extended boot loader partition of the disk. +pub fn add_generator( + config: &mut RootConfiguration, + root: &DevicePath, + strict: bool, +) -> Result<()> { + // Convert the device path root to a string we can use in the configuration. + let mut root = root + .to_string16(DisplayOnly(false), AllowShortcuts(false)) + .context("unable to convert device root to string")? + .to_string(); + // Add a trailing forward-slash to the root to ensure the device root is completed. + root.push('/'); + + // Generate a unique hash of the root path. + let root_unique_hash = unique_hash(&root); // Generate a unique name for the BLS chainload action. let chainload_action_name = format!("{}{}", BLS_CHAINLOAD_ACTION_PREFIX, root_unique_hash,); @@ -103,8 +105,9 @@ pub fn scan( path: format!("{}\\loader", root), uki_path: None, // Every filesystem is scanned, including the Extended Boot Loader Partition, which - // gets a generator of its own. - xbootldr: false, + // gets a generator of its own, unless strict mode reads it with the generator of the + // partition that Sprout was loaded from. + xbootldr: strict, pin_names: true, }; @@ -121,15 +124,18 @@ pub fn scan( // BLS will provide these values to us. // Every initrd slot is chained in order. Unused slots stamp to the root of the // filesystem, which the chainload action skips. + // The files of an entry are on the partition of the entry, which is the root of the + // generator for most entries, and the extended boot loader partition for some in strict mode. + let entry_root = if strict { "$entry-root" } else { &root }; let chainload = ChainloadConfiguration { - path: format!("{}\\$chainload", root), + path: format!("{}\\$chainload", entry_root), options: vec!["$options".to_string()], linux_initrd: None, linux_initrd_chain: (0..BLS_INITRD_SLOTS) - .map(|slot| format!("{}\\$initrd-{}", root, slot)) + .map(|slot| format!("{}\\$initrd-{}", entry_root, slot)) .collect(), // An unset devicetree stamps to the root of the filesystem, which the action skips. - devicetree: Some(format!("{}\\$devicetree", root)), + devicetree: Some(format!("{}\\$devicetree", entry_root)), }; // Insert the chainload action into the configuration. @@ -141,6 +147,18 @@ pub fn scan( }, ); - // We had a BLS supported configuration, so return true. + Ok(()) +} + +/// Scan the specified `filesystem` for BLS configurations. +pub fn scan( + filesystem: &mut FileSystem, + root: &DevicePath, + config: &mut RootConfiguration, +) -> Result { + if !detect(filesystem)? { + return Ok(false); + } + add_generator(config, root, false)?; Ok(true) } diff --git a/crates/boot/src/main.rs b/crates/boot/src/main.rs index 6ed425e..333035e 100644 --- a/crates/boot/src/main.rs +++ b/crates/boot/src/main.rs @@ -230,7 +230,12 @@ fn run(reboot_on_error: &mut bool) -> Result<()> { // If --autoconfigure is specified or the loaded configuration has autoconfigure enabled, // trigger the autoconfiguration mechanism. if context.root().options().autoconfigure || config.options.autoconfigure { - autoconfigure::autoconfigure(&mut config).context("unable to autoconfigure")?; + autoconfigure::autoconfigure( + &mut config, + context.root().options().bls_strict_mode, + context.root().loaded_image_path()?, + ) + .context("unable to autoconfigure")?; } // Unload the context so that it can be modified.