From 5a4bfefb6e1466b2c79988f62fd790526d70faf3 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sun, 4 Oct 2026 10:41:47 -0700 Subject: [PATCH] fix(boot): count tries without using them, add XBOOTLDR once, and warn about dropped initrds An entry that is not counted in strict mode still has its tries, so it can be bad. Strict mode adds the extended boot loader partition to the first BLS generator only. A chainload chain that does not list an initrd that the entry has now warns, as the kernel may not find its root without it, and strict autoconfigure says when it can't find the partition of Sprout. --- crates/boot/src/actions/chainload.rs | 19 +++++++++++++++++++ crates/boot/src/autoconfigure.rs | 18 ++++++++++++------ crates/boot/src/boot_counter.rs | 3 +++ crates/boot/src/generators/bls.rs | 15 +++++++++++---- crates/boot/src/main.rs | 8 ++++++-- 5 files changed, 51 insertions(+), 12 deletions(-) diff --git a/crates/boot/src/actions/chainload.rs b/crates/boot/src/actions/chainload.rs index 2f00106..f147e13 100644 --- a/crates/boot/src/actions/chainload.rs +++ b/crates/boot/src/actions/chainload.rs @@ -1,4 +1,5 @@ use crate::context::SproutContext; +use crate::generators::bls::BLS_INITRD_SLOTS; use crate::phases::before_handoff; use alloc::boxed::Box; use alloc::format; @@ -160,6 +161,24 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfigurat .chain(configuration.linux_initrd_chain.iter()) .map(|item| context.stamp(item)); + // A BLS entry can have more initrds than the chain lists. Those would be dropped without a + // word, and then the kernel may not find its root, so say so. + let chain_length = configuration.linux_initrd_chain.len(); + if chain_length > 0 { + for slot in chain_length..BLS_INITRD_SLOTS { + let placeholder = format!("$initrd-{}", slot); + let value = context.stamp(&placeholder); + if !value.is_empty() && value != placeholder { + warn!( + "the entry has an initrd for slot {}, but linux-initrd-chain only lists {}, \ + so it is not loaded", + slot, chain_length + ); + break; + } + } + } + // Read each initrd and concatenate the contents in order. // Paths that are empty after stamping are skipped. let mut initrd: Option> = None; diff --git a/crates/boot/src/autoconfigure.rs b/crates/boot/src/autoconfigure.rs index 68b48e3..9abab2d 100644 --- a/crates/boot/src/autoconfigure.rs +++ b/crates/boot/src/autoconfigure.rs @@ -140,12 +140,18 @@ pub fn autoconfigure( } // 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); + if strict && strict_found { + match own_root { + Some(root) => { + if let Err(error) = bls::add_generator(config, &root, true) { + warn!("unable to add the bls generator: {:#}", error); + } + } + None => warn!( + "found BLS entries, but not the partition that Sprout was loaded from, \ + so none are used in strict mode" + ), + } } Ok(()) diff --git a/crates/boot/src/boot_counter.rs b/crates/boot/src/boot_counter.rs index 3d8f646..bbb232a 100644 --- a/crates/boot/src/boot_counter.rs +++ b/crates/boot/src/boot_counter.rs @@ -29,6 +29,9 @@ pub struct BootCounterTarget { pub file_name: String, /// The extension of the file name, such as `.conf`, in its original case. pub extension: String, + /// Whether booting the entry uses up a try. An entry whose tries are not used up still + /// has them, so it can be bad. + pub counting: bool, } impl BootCounterTarget { diff --git a/crates/boot/src/generators/bls.rs b/crates/boot/src/generators/bls.rs index ec08763..979b6db 100644 --- a/crates/boot/src/generators/bls.rs +++ b/crates/boot/src/generators/bls.rs @@ -8,6 +8,7 @@ use alloc::{ vec::Vec, }; use anyhow::{Context, Result}; +use core::sync::atomic::{AtomicBool, Ordering as AtomicOrdering}; use core::{cmp::Ordering, str::FromStr}; use edera_sprout_bls::{ BlsEntry, BootCounter, is_reserved_entry_name, profile_id_suffix, sort_bls, strip_extension, @@ -37,6 +38,10 @@ const ARCHITECTURE: &str = "x64"; #[cfg(target_arch = "aarch64")] const ARCHITECTURE: &str = "aa64"; +/// Whether the extended boot loader partition was added by a generator in strict mode, which +/// does it once, so that more than one generator does not list its entries twice. +static STRICT_XBOOTLDR_ADDED: AtomicBool = AtomicBool::new(false); + /// The number of initrd slots set on each BLS entry. /// Entries set `initrd-0` through `initrd-31`, and slots without an initrd are empty. pub const BLS_INITRD_SLOTS: usize = 32; @@ -311,9 +316,7 @@ fn generate_type1( // systemd-boot only counts the tries of the entries it boots as a kernel or as a unified // kernel image, and not those of a plain EFI program. let counted = !(strict && entry.linux.is_none() && entry.uki.is_none()); - if let Some(counter) = boot_counter - && counted - { + if let Some(counter) = boot_counter { boot.set_boot_counter(BootCounterTarget { counter, filesystem: bls_resolved.filesystem_handle, @@ -321,6 +324,7 @@ fn generate_type1( id: name.clone(), file_name, extension, + counting: counted, }); } @@ -446,6 +450,7 @@ fn generate_type2( id: id.clone(), file_name: file_name.clone(), extension: extension.clone(), + counting: true, }); } found.push((entry, boot)); @@ -532,7 +537,9 @@ pub fn generate(context: Rc, bls: &BlsConfiguration) -> Result { let xbootldr_uki = uki_path.as_ref().map(|_| format!("{}\\EFI\\Linux", root)); diff --git a/crates/boot/src/main.rs b/crates/boot/src/main.rs index 333035e..8184c3e 100644 --- a/crates/boot/src/main.rs +++ b/crates/boot/src/main.rs @@ -372,7 +372,7 @@ fn run(reboot_on_error: &mut bool) -> Result<()> { ); // Acquire the entry that was saved by the previous boot. - let last_booted_entry = advisory( + let mut last_booted_entry = advisory( BootloaderInterface::get_last_booted_entry() .context("unable to get last booted entry from bootloader interface"), ); @@ -692,12 +692,14 @@ fn run(reboot_on_error: &mut bool) -> Result<()> { BootloaderInterface::set_last_booted_entry(&id) .context("unable to save last booted entry in bootloader interface"), ); + last_booted_entry = Some(id.clone()); } } else if last_booted_entry.is_some() { advisory( BootloaderInterface::remove_last_booted_entry() .context("unable to remove last booted entry in bootloader interface"), ); + last_booted_entry = None; } } @@ -710,7 +712,9 @@ fn run(reboot_on_error: &mut bool) -> Result<()> { // still be marked as good. // The tries that were left before this boot used one up, if it did. let mut consumed_tries_left = None; - if let Some(target) = entry.boot_counter() { + if let Some(target) = entry.boot_counter() + && target.counting + { match target.consume() { Ok(path) => { info!("updated boot counter of entry {}: {}", entry.name(), path);