diff --git a/crates/boot/src/autoconfigure/bls.rs b/crates/boot/src/autoconfigure/bls.rs index 25e1240..3ee9de3 100644 --- a/crates/boot/src/autoconfigure/bls.rs +++ b/crates/boot/src/autoconfigure/bls.rs @@ -102,6 +102,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, pin_names: true, }; diff --git a/crates/boot/src/generators/bls.rs b/crates/boot/src/generators/bls.rs index 2a47ef4..56c493e 100644 --- a/crates/boot/src/generators/bls.rs +++ b/crates/boot/src/generators/bls.rs @@ -66,6 +66,7 @@ fn bootable_entry( name: &str, entry: &BlsEntry, initrds: &[String], + entry_root: &str, ) -> BootableEntry { // Produce a new sprout context for the entry with the extracted values. let mut context = context.fork(); @@ -84,6 +85,9 @@ fn bootable_entry( title_base.clone() }; + // The device that the files of the entry are on, for a path like "$entry-root\\$chainload". + // It is empty for the partition that Sprout was loaded from. + context.set("entry-root", entry_root.to_string()); context.set("title-base", title_base); context.set("title", title_full); context.set("chainload", chainload); @@ -132,6 +136,7 @@ fn generate_type1( context: &Rc, bls: &BlsConfiguration, path: &str, + entry_root: &str, ) -> Result> { let mut entries = Vec::new(); @@ -259,7 +264,7 @@ fn generate_type1( continue; } - let mut boot = bootable_entry(context, bls, &name, &entry, &initrds); + let mut boot = bootable_entry(context, bls, &name, &entry, &initrds, entry_root); boot.set_id_suffix(&extension); // Record where the boot counter lives so a try can be consumed when this entry boots. @@ -287,6 +292,7 @@ fn generate_type2( context: &Rc, bls: &BlsConfiguration, uki_path: &str, + entry_root: &str, entries: &[(BlsEntry, BootableEntry)], ) -> Result> { let resolved = eficore::path::resolve_path(Some(context.root().loaded_image_path()?), uki_path) @@ -356,7 +362,7 @@ fn generate_type2( let mut entry = profile.entry; entry.boot_counter = boot_counter; - let mut boot = bootable_entry(context, bls, &name, &entry, &[]); + let mut boot = bootable_entry(context, bls, &name, &entry, &[], entry_root); boot.set_id_suffix(&extension); if let Some(ref suffix) = suffix { boot.set_id_profile(suffix); @@ -392,18 +398,19 @@ fn generate_type2( Ok(found) } -/// Generates entries from the BLS entries directory and the unified kernel image directory -/// using the specified `bls` configuration and `context`. The BLS conversion is best-effort -/// and will ignore any unsupported entries. -pub fn generate(context: Rc, bls: &BlsConfiguration) -> Result> { - // Stamp the path to the BLS directory. - let path = context.stamp(&bls.path); - - let uki_path = bls.uki_path_for(&path); - +/// Generates the entries of one partition, from the BLS entries directory at `bls_path` and the +/// unified kernel image directory at `uki_path`, if there is one. The files of the entries are on +/// the device `entry_root`. +fn generate_partition( + context: &Rc, + bls: &BlsConfiguration, + bls_path: &str, + uki_path: Option<&str>, + entry_root: &str, +) -> Result> { // A problem reading the Type #1 entries only stops the unified kernel images when there // are none to add, so that either kind of entry can still boot. - let mut entries = match generate_type1(&context, bls, &path) { + let mut entries = match generate_type1(context, bls, bls_path, entry_root) { Ok(entries) => entries, Err(error) if uki_path.is_some() => { warn!("unable to generate bls entries: {:#}", error); @@ -415,8 +422,8 @@ pub fn generate(context: Rc, bls: &BlsConfiguration) -> Result entries.extend(found), Err(error) => warn!( "unable to generate unified kernel image entries: {:#}", @@ -424,6 +431,74 @@ pub fn generate(context: Rc, bls: &BlsConfiguration) -> Result) -> Result> { + let Some(path) = eficore::xbootldr::find_xbootldr(context.root().loaded_image_path()?)? else { + return Ok(None); + }; + let mut root = path + .to_string16(DisplayOnly(false), AllowShortcuts(false)) + .context("unable to convert the xbootldr device path to a string")? + .to_string(); + // Add a trailing forward-slash to the root to ensure the device root is completed. + root.push('/'); + Ok(Some(root)) +} + +/// Generates entries from the BLS entries directory and the unified kernel image directory +/// using the specified `bls` configuration and `context`, and from the Extended Boot Loader +/// Partition if that is enabled. The BLS conversion is best-effort and will ignore any +/// unsupported entries. +pub fn generate(context: Rc, bls: &BlsConfiguration) -> Result> { + // Stamp the path to the BLS directory. + let path = context.stamp(&bls.path); + + let uki_path = bls.uki_path_for(&path); + let mut entries = generate_partition( + &context, + bls, + &path, + uki_path.as_deref(), + BlsConfiguration::root_of(&path), + )?; + + // Add the entries of the Extended Boot Loader Partition, which are sorted together with the + // others. A problem with it should not prevent booting from the entries that are found. + if bls.xbootldr { + match xbootldr_root(&context) { + Ok(Some(root)) => { + let xbootldr_uki = uki_path.as_ref().map(|_| format!("{}\\EFI\\Linux", root)); + match generate_partition( + &context, + bls, + &format!("{}\\loader", root), + xbootldr_uki.as_deref(), + &root, + ) { + Ok(found) => { + for (entry, boot) in found { + // An entry that is on both partitions would be ambiguous. + if entries.iter().any(|(_, other)| other.id() == boot.id()) { + warn!( + "xbootldr entry {} has the same id as another entry, skipping", + boot.id() + ); + } else { + entries.push((entry, boot)); + } + } + } + Err(error) => warn!("unable to generate xbootldr entries: {:#}", error), + } + } + Ok(None) => info!("no xbootldr partition was found"), + Err(error) => warn!("unable to find the xbootldr partition: {:#}", error), + } + } // Sort all the entries according to the BLS sort system. entries.sort_by(sort_entries); diff --git a/crates/config/src/generators/bls.rs b/crates/config/src/generators/bls.rs index f69f709..be5d665 100644 --- a/crates/config/src/generators/bls.rs +++ b/crates/config/src/generators/bls.rs @@ -26,6 +26,12 @@ pub struct BlsConfiguration { /// with the same names, so they should not both scan it. #[serde(default, rename = "uki-path")] pub uki_path: Option, + /// Whether to also read the Extended Boot Loader Partition, which is the partition with the + /// XBOOTLDR type on the same disk as the partition Sprout was loaded from. Its entries are + /// sorted with the others, and their paths are on that partition, so an action has to use + /// the `$entry-root` value to find them, such as `$entry-root\\$chainload`. + #[serde(default)] + pub xbootldr: bool, /// Whether generated entries keep the name of the BLS entry file as-is. /// When enabled, the generator name is not prepended, so the entry names match /// the BLS entry ids used by tools like `bootctl set-default`. Variants still append @@ -38,6 +44,12 @@ pub struct BlsConfiguration { } impl BlsConfiguration { + /// The device of the stamped BLS path `bls_path`, which is everything up to the last slash, + /// and which is empty for a path on the partition Sprout was loaded from. + pub fn root_of(bls_path: &str) -> &str { + bls_path.rfind('/').map_or("", |index| &bls_path[..=index]) + } + /// The directory of unified kernel images for the stamped BLS path `bls_path`, /// or None if unified kernel images are disabled. pub fn uki_path_for(&self, bls_path: &str) -> Option { @@ -45,10 +57,7 @@ impl BlsConfiguration { Some("") => None, Some(path) => Some(path.to_string()), // Keep the device of the BLS path, which is everything up to the last slash. - None => { - let device = bls_path.rfind('/').map_or("", |index| &bls_path[..=index]); - Some(format!("{}{}", device, BLS_UKI_DIRECTORY)) - } + None => Some(format!("{}{}", Self::root_of(bls_path), BLS_UKI_DIRECTORY)), } } } @@ -60,6 +69,7 @@ impl Default for BlsConfiguration { entry: Default::default(), path: default_bls_path(), uki_path: None, + xbootldr: false, pin_names: default_pin_names(), } } @@ -77,6 +87,20 @@ fn default_bls_path() -> String { mod tests { use super::*; + #[test] + fn root_is_the_device_of_the_bls_path() { + assert_eq!(BlsConfiguration::root_of("\\loader"), ""); + assert_eq!( + BlsConfiguration::root_of("PciRoot(0x0)/HD(2,GPT,abc)/\\loader"), + "PciRoot(0x0)/HD(2,GPT,abc)/" + ); + } + + #[test] + fn xbootldr_is_off_by_default() { + assert!(!BlsConfiguration::default().xbootldr); + } + #[test] fn uki_path_is_next_to_the_default_bls_path() { let bls = BlsConfiguration::default();