From 7d913b3ab44855c8a74865784336461d78ec8df7 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sat, 3 Oct 2026 17:36:49 -0700 Subject: [PATCH] fix(boot): load loader.conf before one-shot variables and normalize the boot count path --- crates/bls/src/lib.rs | 32 ++++++++++++++++++++++ crates/boot/src/main.rs | 17 +++++------- crates/eficore/src/bootloader_interface.rs | 7 ++++- 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/crates/bls/src/lib.rs b/crates/bls/src/lib.rs index 52cea83..0543351 100644 --- a/crates/bls/src/lib.rs +++ b/crates/bls/src/lib.rs @@ -252,6 +252,20 @@ impl BootCounter { } } +/// Converts an EFI `path` to the form used by the bootloader interface: separated by forward +/// slashes, with a leading slash and no repeated or trailing separators. +pub fn boot_path(path: &str) -> String { + let mut result = String::new(); + for part in path.split(['\\', '/']).filter(|part| !part.is_empty()) { + result.push('/'); + result.push_str(part); + } + if result.is_empty() { + result.push('/'); + } + result +} + /// Decides which entries are the default. Each item of `entries` is `(is_default, is_bad)`, /// and the result says whether each entry is the default afterwards. /// Bad entries, which have no boot counter tries left, are never the default while a good entry @@ -583,6 +597,24 @@ mod tests { assert!(!entry.is_bad()); } + #[test] + fn boot_path_uses_forward_slashes_and_a_leading_slash() { + assert_eq!( + boot_path("\\loader\\entries\\foo+2-1.conf"), + "/loader/entries/foo+2-1.conf" + ); + assert_eq!( + boot_path("loader/entries/foo.conf"), + "/loader/entries/foo.conf" + ); + } + + #[test] + fn boot_path_collapses_repeated_separators() { + assert_eq!(boot_path("\\a/\\b//c"), "/a/b/c"); + assert_eq!(boot_path(""), "/"); + } + #[test] fn loader_conf_empty_has_no_settings() { let conf = LoaderConf::parse(""); diff --git a/crates/boot/src/main.rs b/crates/boot/src/main.rs index cad4d6f..5ffbbb9 100644 --- a/crates/boot/src/main.rs +++ b/crates/boot/src/main.rs @@ -333,6 +333,12 @@ fn run() -> Result<()> { .context("unable to set entries in bootloader interface"), ); + // Load the loader.conf of the partition Sprout was loaded from. + let loader_conf = load_loader_conf(&context).context("unable to load loader.conf")?; + for warning in &loader_conf.warnings { + warn!("{}", warning); + } + // Acquire the timeouts from the bootloader interface. let bootloader_interface_timeouts = advisory( BootloaderInterface::get_timeouts().context("unable to get bootloader interface timeouts"), @@ -350,12 +356,6 @@ fn run() -> Result<()> { .context("unable to get bootloader interface oneshot entry"), ); - // Load the loader.conf of the partition Sprout was loaded from. - let loader_conf = load_loader_conf(&context).context("unable to load loader.conf")?; - for warning in &loader_conf.warnings { - warn!("{}", warning); - } - // If --boot is specified, boot that entry immediately. let mut force_boot_entry = context.root().options().boot.clone(); // If --force-menu is specified, show the boot menu regardless of the value of --boot. @@ -576,10 +576,7 @@ fn run() -> Result<()> { Ok(path) => { info!("updated boot counter of entry {}: {}", entry.name(), path); // Tell the system where the counter is, so it can mark the boot as good. - let path = format!( - "/{}", - path.to_string().replace('\\', "/").trim_start_matches('/') - ); + let path = edera_sprout_bls::boot_path(&path.to_string()); advisory( BootloaderInterface::set_boot_count_path(&path) .context("unable to set boot count path in bootloader interface"), diff --git a/crates/eficore/src/bootloader_interface.rs b/crates/eficore/src/bootloader_interface.rs index a3ca694..3b008b3 100644 --- a/crates/eficore/src/bootloader_interface.rs +++ b/crates/eficore/src/bootloader_interface.rs @@ -295,8 +295,13 @@ impl BootloaderInterface { // Attempt to acquire the value of the LoaderConfigTimeout variable. // The specification says that a value of 0 means that the menu should be hidden. + // A failure to read it is only a warning, as the one-shot variable is already consumed. let direct = Self::get_timeout_value("LoaderConfigTimeout", false) - .context("unable to check for LoaderConfigTimeout variable")? + .context("unable to check for LoaderConfigTimeout variable") + .unwrap_or_else(|error| { + warn!("{:#}", error); + None + }) .map(|direct| match direct { BootloaderInterfaceTimeout::Timeout(0) => BootloaderInterfaceTimeout::MenuHidden, other => other,