From facd2000a58d754d8c318e4d11343d6847f26fc7 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Mon, 27 Oct 2025 02:38:40 -0400 Subject: [PATCH 1/3] feat(autoconfigure): initial attempt at bls autoconfiguration --- src/autoconfigure.rs | 137 +++++++++++++++++++++++++++++++++++++++++++ src/config.rs | 2 + src/main.rs | 24 ++++++-- src/options.rs | 8 +++ 4 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 src/autoconfigure.rs diff --git a/src/autoconfigure.rs b/src/autoconfigure.rs new file mode 100644 index 0000000..3cd5c2a --- /dev/null +++ b/src/autoconfigure.rs @@ -0,0 +1,137 @@ +use crate::actions::ActionDeclaration; +use crate::actions::chainload::ChainloadConfiguration; +use crate::config::RootConfiguration; +use crate::entries::EntryDeclaration; +use crate::generators::GeneratorDeclaration; +use crate::generators::bls::BlsConfiguration; +use anyhow::{Context, Result}; +use uefi::cstr16; +use uefi::fs::{FileSystem, Path}; +use uefi::proto::device_path::DevicePath; +use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly}; +use uefi::proto::media::fs::SimpleFileSystem; + +/// The name of the BLS chainload action that will be used +/// by the BLS generator to chainload entries. +const BLS_CHAINLOAD_ACTION: &str = "bls-chainload"; + +/// Scan the specified `filesystem` for BLS configurations. +fn scan_for_bls( + filesystem: &mut FileSystem, + root: &DevicePath, + config: &mut RootConfiguration, +) -> 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. + let bls_entries_path = Path::new(cstr16!("\\loader\\entries")); + + // Convert the device path root to a string we can use in the configuration. + let root = root + .to_string(DisplayOnly(false), AllowShortcuts(false)) + .context("unable to convert device root to string")? + .to_string(); + + // Whether we have a loader.conf file. + let has_loader_conf = filesystem + .try_exists(bls_loader_conf_path) + .context("unable to check for BLS entries directory")?; + + // Whether we have an entries directory. + let has_entries_dir = filesystem + .try_exists(bls_entries_path) + .context("unable to check for BLS loader.conf file")?; + + // Detect if a BLS supported configuration is on this filesystem. + // We check both loader.conf and entries directory as only one of them is required. + if !has_loader_conf && !has_entries_dir { + return Ok(false); + } + + // BLS is now detected, generate a configuration for it. + let generator = BlsConfiguration { + entry: EntryDeclaration { + title: "$title".to_string(), + actions: vec![BLS_CHAINLOAD_ACTION.to_string()], + ..Default::default() + }, + path: format!("{}\\loader", root,), + }; + + // Generate a unique name for the BLS generator and insert the generator into the configuration. + config.generators.insert( + format!("autoconfigure-bls-{}", root), + GeneratorDeclaration { + bls: Some(generator), + ..Default::default() + }, + ); + + // We had a BLS supported configuration, so return true. + Ok(true) +} + +/// Add the BLS actions to the configuration. +/// We should only do this once. +fn add_bls_actions(config: &mut RootConfiguration) -> Result<()> { + // Generate a chainload configuration for BLS. + // BLS will provide these values to us. + let chainload = ChainloadConfiguration { + path: "$chainload".to_string(), + options: vec!["$options".to_string()], + linux_initrd: Some("initrd".to_string()), + }; + + // Insert the chainload action into the configuration. + config.actions.insert( + BLS_CHAINLOAD_ACTION.to_string(), + ActionDeclaration { + chainload: Some(chainload), + ..Default::default() + }, + ); + + Ok(()) +} + +/// 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<()> { + // Find all the filesystems that are on the system. + let filesystem_handles = + uefi::boot::find_handles::().context("unable to scan filesystems")?; + + // Whether we have any BLS-supported filesystems. + // We will key off of this to know if we should add BLS actions. + let mut has_any_bls = 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. + let root = { + uefi::boot::open_protocol_exclusive::(handle) + .context("unable to get root for filesystem")? + .to_boxed() + }; + + // Open the filesystem that was detected. + let filesystem = uefi::boot::open_protocol_exclusive::(handle) + .context("unable to open filesystem")?; + + // Trade the filesystem protocol for the uefi filesystem helper. + let mut filesystem = FileSystem::new(filesystem); + + // Scan the filesystem for BLS supported configurations. + // If we find any, we will add a BLS generator to the configuration, and then + // at the end we will add the BLS actions to the configuration. + has_any_bls = has_any_bls + || scan_for_bls(&mut filesystem, &root, config).context("unable to scan filesystem")?; + } + + // If we had any BLS-supported filesystems, add the BLS actions to the configuration. + if has_any_bls { + add_bls_actions(config).context("unable to add BLS actions")?; + } + + Ok(()) +} diff --git a/src/config.rs b/src/config.rs index 4ad4225..b6a92ad 100644 --- a/src/config.rs +++ b/src/config.rs @@ -74,6 +74,8 @@ pub struct DefaultsConfiguration { /// The timeout of the boot menu. #[serde(rename = "menu-timeout", default = "default_menu_timeout")] pub menu_timeout: u64, + /// Enables autoconfiguration of Sprout based on the environment. + pub autoconfigure: bool, } fn latest_version() -> u32 { diff --git a/src/main.rs b/src/main.rs index af59c42..ddeb524 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ #![doc = include_str!("../README.md")] #![feature(uefi_std)] +use crate::config::RootConfiguration; use crate::context::{RootContext, SproutContext}; use crate::entries::BootableEntry; use crate::options::SproutOptions; @@ -17,6 +18,9 @@ use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly}; /// actions: Code that can be configured and executed by Sprout. pub mod actions; +/// autoconfigure: Autoconfigure Sprout based on the detected environment. +pub mod autoconfigure; + /// config: Sprout configuration mechanism. pub mod config; @@ -60,10 +64,16 @@ fn main() -> Result<()> { // Parse the options to the sprout executable. let options = SproutOptions::parse().context("unable to parse options")?; - // Load the configuration of sprout. - // At this point, the configuration has been validated and the specified - // version is checked to ensure compatibility. - let config = config::loader::load(&options)?; + // If --autoconfigure is specified, we use a stub configuration. + let mut config = if options.autoconfigure { + info!("autoconfiguration enabled, configuration file will be ignored"); + RootConfiguration::default() + } else { + // Load the configuration of sprout. + // At this point, the configuration has been validated and the specified + // version is checked to ensure compatibility. + config::loader::load(&options)? + }; // Load the root context. // This is done in a block to ensure the release of the LoadedImageDevicePath protocol. @@ -98,6 +108,12 @@ fn main() -> Result<()> { // Load all configured drivers. drivers::load(context.clone(), &config.drivers).context("unable to load drivers")?; + // If --autoconfigure is specified or the loaded configuration has autoconfigure enabled, + // trigger the autoconfiguration mechanism. + if context.root().options().autoconfigure || config.defaults.autoconfigure { + autoconfigure::autoconfigure(&mut config).context("unable to autoconfigure")?; + } + // Run all the extractors declared in the configuration. let mut extracted = BTreeMap::new(); for (name, extractor) in &config.extractors { diff --git a/src/options.rs b/src/options.rs index 3f73eb2..cf02bba 100644 --- a/src/options.rs +++ b/src/options.rs @@ -11,6 +11,8 @@ const DEFAULT_CONFIG_PATH: &str = "\\sprout.toml"; /// The parsed options of sprout. #[derive(Debug)] pub struct SproutOptions { + /// Configures Sprout automatically based on the environment. + pub autoconfigure: bool, /// Path to a configuration file to load. pub config: String, /// Entry to boot without showing the boot menu. @@ -25,6 +27,7 @@ pub struct SproutOptions { impl Default for SproutOptions { fn default() -> Self { Self { + autoconfigure: false, config: DEFAULT_CONFIG_PATH.to_string(), boot: None, force_menu: false, @@ -86,6 +89,11 @@ impl OptionsRepresentable for SproutOptions { for (key, value) in options { match key.as_str() { + "autoconfigure" => { + // Enable autoconfiguration. + result.autoconfigure = true; + } + "config" => { // The configuration file to load. result.config = value.context("--config option requires a value")?; From 1799419bfa9ccdef1797c7c2dd7ffd575fd0336a Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Mon, 27 Oct 2025 03:37:09 -0400 Subject: [PATCH 2/3] fix(autoconfigure): apply the actions properly in the root --- hack/dev/boot.sh | 9 ++------ hack/dev/boot/Dockerfile | 4 ++++ hack/dev/build.sh | 1 + hack/dev/configs/autoconfigure.sprout.toml | 4 ++++ hack/dev/configs/bls.conf | 4 ++++ src/actions.rs | 2 +- src/actions/chainload.rs | 6 +----- src/actions/edera.rs | 2 +- src/actions/print.rs | 2 +- src/actions/splash.rs | 2 +- src/autoconfigure.rs | 25 ++++++++++++++-------- src/config.rs | 4 ++-- src/context.rs | 11 ++++++++++ src/drivers.rs | 2 +- src/entries.rs | 2 +- src/extractors.rs | 2 +- src/extractors/filesystem_device_match.rs | 2 +- src/generators.rs | 2 +- src/generators/bls.rs | 4 ++-- src/generators/matrix.rs | 2 +- src/main.rs | 21 +++++++++++++++++- src/phases.rs | 4 ++-- 22 files changed, 79 insertions(+), 38 deletions(-) create mode 100644 hack/dev/configs/autoconfigure.sprout.toml create mode 100644 hack/dev/configs/bls.conf diff --git a/hack/dev/boot.sh b/hack/dev/boot.sh index e52c513..b5f3a21 100755 --- a/hack/dev/boot.sh +++ b/hack/dev/boot.sh @@ -65,13 +65,8 @@ set -- "${@}" \ -drive "if=pflash,file=${FINAL_DIR}/ovmf-boot.fd,format=raw,readonly=on" \ -device nvme,drive=disk1,serial=cafebabe -if [ "${DISK_BOOT}" = "1" ]; then - set -- "${@}" \ - -drive "if=none,file=${FINAL_DIR}/sprout.img,format=raw,id=disk1,readonly=on" -else - set -- "${@}" \ - -drive "if=none,file=fat:rw:${FINAL_DIR}/efi,format=raw,id=disk1" -fi +set -- "${@}" \ + -drive "if=none,file=${FINAL_DIR}/sprout.img,format=raw,id=disk1,readonly=on" set -- "${@}" -name "sprout ${TARGET_ARCH}" diff --git a/hack/dev/boot/Dockerfile b/hack/dev/boot/Dockerfile index 0c0b355..45010f5 100644 --- a/hack/dev/boot/Dockerfile +++ b/hack/dev/boot/Dockerfile @@ -13,6 +13,7 @@ COPY xen.efi /work/XEN.EFI COPY xen.cfg /work/XEN.CFG COPY initramfs /work/INITRAMFS COPY edera-splash.png /work/EDERA-SPLASH.PNG +COPY bls.conf /work/BLS.CONF RUN truncate -s128MiB sprout.img && \ parted --script sprout.img mklabel gpt > /dev/null 2>&1 && \ parted --script sprout.img mkpart primary fat32 1MiB 100% > /dev/null 2>&1 && \ @@ -20,6 +21,8 @@ RUN truncate -s128MiB sprout.img && \ mkfs.vfat -F32 -n EFI sprout.img && \ mmd -i sprout.img ::/EFI && \ mmd -i sprout.img ::/EFI/BOOT && \ + mmd -i sprout.img ::/LOADER && \ + mmd -i sprout.img ::/LOADER/ENTRIES && \ mcopy -i sprout.img ${EFI_NAME}.EFI ::/EFI/BOOT/ && \ mcopy -i sprout.img KERNEL.EFI ::/EFI/BOOT/ && \ mcopy -i sprout.img SHELL.EFI ::/EFI/BOOT/ && \ @@ -28,6 +31,7 @@ RUN truncate -s128MiB sprout.img && \ mcopy -i sprout.img SPROUT.TOML ::/ && \ mcopy -i sprout.img EDERA-SPLASH.PNG ::/ && \ mcopy -i sprout.img INITRAMFS ::/ && \ + mcopy -i sprout.img BLS.CONF ::/LOADER/ENTRIES/ && \ mv sprout.img /sprout.img FROM scratch AS final diff --git a/hack/dev/build.sh b/hack/dev/build.sh index d4acd4e..6506ce5 100755 --- a/hack/dev/build.sh +++ b/hack/dev/build.sh @@ -108,6 +108,7 @@ if [ "${SKIP_SPROUT_BUILD}" != "1" ]; then cp "hack/dev/configs/${SPROUT_CONFIG_NAME}.sprout.toml" "${FINAL_DIR}/sprout.toml" cp "hack/dev/configs/xen.cfg" "${FINAL_DIR}/xen.cfg" cp "hack/dev/assets/edera-splash.png" "${FINAL_DIR}/edera-splash.png" + cp "hack/dev/configs/bls.conf" "${FINAL_DIR}/bls.conf" mkdir -p "${FINAL_DIR}/efi/EFI/BOOT" cp "${FINAL_DIR}/sprout.efi" "${FINAL_DIR}/efi/EFI/BOOT/${EFI_NAME}.EFI" diff --git a/hack/dev/configs/autoconfigure.sprout.toml b/hack/dev/configs/autoconfigure.sprout.toml new file mode 100644 index 0000000..9b7d56f --- /dev/null +++ b/hack/dev/configs/autoconfigure.sprout.toml @@ -0,0 +1,4 @@ +version = 1 + +[defaults] +autoconfigure = true diff --git a/hack/dev/configs/bls.conf b/hack/dev/configs/bls.conf new file mode 100644 index 0000000..ff0017e --- /dev/null +++ b/hack/dev/configs/bls.conf @@ -0,0 +1,4 @@ +title Boot Linux +linux /efi/boot/kernel.efi +options console=hvc0 +initrd /initramfs diff --git a/src/actions.rs b/src/actions.rs index 8bd1031..e20c951 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -19,7 +19,7 @@ pub mod splash; /// that you can specify via other concepts. /// /// Actions are the main work that Sprout gets done, like booting Linux. -#[derive(Serialize, Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct ActionDeclaration { /// Chainload to another EFI application. /// This allows you to load any EFI application, either to boot an operating system diff --git a/src/actions/chainload.rs b/src/actions/chainload.rs index 891ca27..96bff81 100644 --- a/src/actions/chainload.rs +++ b/src/actions/chainload.rs @@ -10,7 +10,7 @@ use uefi::CString16; use uefi::proto::loaded_image::LoadedImage; /// The configuration of the chainload action. -#[derive(Serialize, Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct ChainloadConfiguration { /// The path to the image to chainload. /// This can be a Linux EFI stub (vmlinuz usually) or a standard EFI executable. @@ -99,10 +99,6 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfigurat initrd_handle = Some(handle); } - // Retrieve the base and size of the loaded image to display. - let (base, size) = loaded_image_protocol.info(); - info!("loaded image: base={:#x} size={:#x}", base.addr(), size); - // Start the loaded image. // This call might return, or it may pass full control to another image that will never return. // Capture the result to ensure we can return an error if the image fails to start, but only diff --git a/src/actions/edera.rs b/src/actions/edera.rs index a88b9e1..e9cf1d5 100644 --- a/src/actions/edera.rs +++ b/src/actions/edera.rs @@ -22,7 +22,7 @@ use crate::{ /// The configuration of the edera action which boots the Edera hypervisor. /// Edera is based on Xen but modified significantly with a Rust stack. /// Sprout is a component of the Edera stack and provides the boot functionality of Xen. -#[derive(Serialize, Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct EderaConfiguration { /// The path to the Xen hypervisor EFI image. pub xen: String, diff --git a/src/actions/print.rs b/src/actions/print.rs index 9b1bbe6..1013d46 100644 --- a/src/actions/print.rs +++ b/src/actions/print.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use std::rc::Rc; /// The configuration of the print action. -#[derive(Serialize, Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct PrintConfiguration { /// The text to print to the console. #[serde(default)] diff --git a/src/actions/splash.rs b/src/actions/splash.rs index 991aecf..173ed43 100644 --- a/src/actions/splash.rs +++ b/src/actions/splash.rs @@ -15,7 +15,7 @@ use uefi::proto::console::gop::GraphicsOutput; const DEFAULT_SPLASH_TIME: u32 = 0; /// The configuration of the splash action. -#[derive(Serialize, Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct SplashConfiguration { /// The path to the image to display. /// Currently, only PNG images are supported. diff --git a/src/autoconfigure.rs b/src/autoconfigure.rs index 3cd5c2a..71daabf 100644 --- a/src/autoconfigure.rs +++ b/src/autoconfigure.rs @@ -27,24 +27,30 @@ fn scan_for_bls( let bls_entries_path = Path::new(cstr16!("\\loader\\entries")); // Convert the device path root to a string we can use in the configuration. - let root = root + let mut root = root .to_string(DisplayOnly(false), AllowShortcuts(false)) .context("unable to convert device root to string")? .to_string(); + // Add a trailing slash to the root to ensure the path is valid. + root.push('/'); // Whether we have a loader.conf file. let has_loader_conf = filesystem .try_exists(bls_loader_conf_path) - .context("unable to check for BLS entries directory")?; + .context("unable to check for BLS loader.conf file")?; // Whether we have an entries directory. + // We actually iterate the entries to see if there are any. let has_entries_dir = filesystem - .try_exists(bls_entries_path) - .context("unable to check for BLS loader.conf file")?; + .read_dir(bls_entries_path) + .ok() + .and_then(|mut iterator| iterator.next()) + .map(|entry| entry.is_ok()) + .unwrap_or(false); // Detect if a BLS supported configuration is on this filesystem. // We check both loader.conf and entries directory as only one of them is required. - if !has_loader_conf && !has_entries_dir { + if !(has_loader_conf || has_entries_dir) { return Ok(false); } @@ -55,7 +61,7 @@ fn scan_for_bls( actions: vec![BLS_CHAINLOAD_ACTION.to_string()], ..Default::default() }, - path: format!("{}\\loader", root,), + path: format!("{}\\loader", root), }; // Generate a unique name for the BLS generator and insert the generator into the configuration. @@ -79,7 +85,7 @@ fn add_bls_actions(config: &mut RootConfiguration) -> Result<()> { let chainload = ChainloadConfiguration { path: "$chainload".to_string(), options: vec!["$options".to_string()], - linux_initrd: Some("initrd".to_string()), + linux_initrd: Some("$initrd".to_string()), }; // Insert the chainload action into the configuration. @@ -124,8 +130,9 @@ pub fn autoconfigure(config: &mut RootConfiguration) -> Result<()> { // Scan the filesystem for BLS supported configurations. // If we find any, we will add a BLS generator to the configuration, and then // at the end we will add the BLS actions to the configuration. - has_any_bls = has_any_bls - || scan_for_bls(&mut filesystem, &root, config).context("unable to scan filesystem")?; + let bls_found = + scan_for_bls(&mut filesystem, &root, config).context("unable to scan filesystem")?; + has_any_bls = has_any_bls || bls_found; } // If we had any BLS-supported filesystems, add the BLS actions to the configuration. diff --git a/src/config.rs b/src/config.rs index b6a92ad..a6affc3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -18,7 +18,7 @@ pub const LATEST_VERSION: u32 = 1; pub const DEFAULT_MENU_TIMEOUT_SECONDS: u64 = 10; /// The Sprout configuration format. -#[derive(Serialize, Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct RootConfiguration { /// The version of the configuration. This should always be declared /// and be the latest version that is supported. If not specified, it is assumed @@ -66,7 +66,7 @@ pub struct RootConfiguration { } /// Default configuration for Sprout, used when the corresponding options are not specified. -#[derive(Serialize, Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct DefaultsConfiguration { /// The entry to boot without showing the boot menu. /// If not specified, a boot menu is shown. diff --git a/src/context.rs b/src/context.rs index e299325..6afa693 100644 --- a/src/context.rs +++ b/src/context.rs @@ -83,6 +83,11 @@ impl SproutContext { self.root.as_ref() } + /// Access the root context to modify it, if possible. + pub fn root_mut(&mut self) -> Option<&mut RootContext> { + Rc::get_mut(&mut self.root) + } + /// Retrieve the value specified by `key` from this context or its parents. /// Returns `None` if the value is not found. pub fn get(&self, key: impl AsRef) -> Option<&String> { @@ -235,4 +240,10 @@ impl SproutContext { pub fn stamp(&self, text: impl AsRef) -> String { Self::stamp_values(&self.all_values(), text.as_ref()).1 } + + /// Unloads a [SproutContext] back into an owned context. This + /// may not succeed if something else is holding onto the value. + pub fn unload(self: Rc) -> Option { + Rc::into_inner(self) + } } diff --git a/src/drivers.rs b/src/drivers.rs index f2e0daa..cf02fd1 100644 --- a/src/drivers.rs +++ b/src/drivers.rs @@ -12,7 +12,7 @@ use uefi::proto::device_path::LoadedImageDevicePath; /// Drivers allow extending the functionality of Sprout. /// Drivers are loaded at runtime and can provide extra functionality like filesystem support. /// Drivers are loaded by their name, which is used to reference them in other concepts. -#[derive(Serialize, Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct DriverDeclaration { /// The filesystem path to the driver. /// This file should be an EFI executable that can be located and executed. diff --git a/src/entries.rs b/src/entries.rs index f32f781..d624c77 100644 --- a/src/entries.rs +++ b/src/entries.rs @@ -7,7 +7,7 @@ use std::rc::Rc; /// /// Entries are the user-facing concept of Sprout, making it possible /// to run a set of actions with a specific context. -#[derive(Serialize, Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct EntryDeclaration { /// The title of the entry which will be display in the boot menu. /// This is the pre-stamped value. diff --git a/src/extractors.rs b/src/extractors.rs index 7320958..ae4f325 100644 --- a/src/extractors.rs +++ b/src/extractors.rs @@ -10,7 +10,7 @@ pub mod filesystem_device_match; /// Declares an extractor configuration. /// Extractors allow calculating values at runtime /// using built-in sprout modules. -#[derive(Serialize, Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct ExtractorDeclaration { /// The filesystem device match extractor. /// This extractor finds a filesystem using some search criteria and returns diff --git a/src/extractors/filesystem_device_match.rs b/src/extractors/filesystem_device_match.rs index 54a91ec..dbe2694 100644 --- a/src/extractors/filesystem_device_match.rs +++ b/src/extractors/filesystem_device_match.rs @@ -20,7 +20,7 @@ use uefi_raw::Status; /// /// This function only requires one of the criteria to match. /// The fallback value can be used to provide a value if none is found. -#[derive(Serialize, Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct FilesystemDeviceMatchExtractor { /// Matches a filesystem that has the specified label. #[serde(default, rename = "has-label")] diff --git a/src/generators.rs b/src/generators.rs index 2c11b1a..2632e19 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -12,7 +12,7 @@ pub mod matrix; /// Declares a generator configuration. /// Generators allow generating entries at runtime based on a set of data. -#[derive(Serialize, Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct GeneratorDeclaration { /// Matrix generator configuration. /// Matrix allows you to specify multiple value-key values as arrays. diff --git a/src/generators/bls.rs b/src/generators/bls.rs index d18a99a..9dc8d7d 100644 --- a/src/generators/bls.rs +++ b/src/generators/bls.rs @@ -20,7 +20,7 @@ const BLS_TEMPLATE_PATH: &str = "\\loader"; /// The configuration of the BLS generator. /// The BLS uses the Bootloader Specification to produce /// entries from an input template. -#[derive(Serialize, Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct BlsConfiguration { /// The entry to use for as a template. pub entry: EntryDeclaration, @@ -86,7 +86,7 @@ pub fn generate(context: Rc, bls: &BlsConfiguration) -> Result Result<()> { autoconfigure::autoconfigure(&mut config).context("unable to autoconfigure")?; } + // Unload the context so that it can be modified. + let Some(mut context) = context.unload() else { + bail!("context safety violation while trying to unload context"); + }; + + // Perform root context modification in a block to release the modification when complete. + { + // Modify the root context to include the autoconfigured actions. + let Some(root) = context.root_mut() else { + bail!("context safety violation while trying to modify root context"); + }; + + // Extend the root context with the autoconfigured actions. + root.actions_mut().extend(config.actions); + } + + // Refreeze the context to ensure that further operations can share the context. + let context = context.freeze(); + // Run all the extractors declared in the configuration. let mut extracted = BTreeMap::new(); for (name, extractor) in &config.extractors { diff --git a/src/phases.rs b/src/phases.rs index 68de3c4..a6303e9 100644 --- a/src/phases.rs +++ b/src/phases.rs @@ -7,7 +7,7 @@ use std::rc::Rc; /// Configures the various phases of the boot process. /// This allows hooking various phases to run actions. -#[derive(Serialize, Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct PhasesConfiguration { /// The early phase is run before drivers are loaded. #[serde(default)] @@ -23,7 +23,7 @@ pub struct PhasesConfiguration { /// Configures a single phase of the boot process. /// There can be multiple phase configurations that are /// executed sequentially. -#[derive(Serialize, Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct PhaseConfiguration { /// The actions to run when the phase is executed. #[serde(default)] From 0ca9ff4fec28240d6c9da73dbb786e41b7b8a877 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Mon, 27 Oct 2025 03:45:10 -0400 Subject: [PATCH 3/3] fix(bls-autoconfigure): generate a unique chainload action for each filesystem --- src/autoconfigure.rs | 41 +++++++++++++---------------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/src/autoconfigure.rs b/src/autoconfigure.rs index 71daabf..3f90e53 100644 --- a/src/autoconfigure.rs +++ b/src/autoconfigure.rs @@ -11,9 +11,9 @@ use uefi::proto::device_path::DevicePath; use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly}; use uefi::proto::media::fs::SimpleFileSystem; -/// The name of the BLS chainload action that will be used +/// The name prefix of the BLS chainload action that will be used /// by the BLS generator to chainload entries. -const BLS_CHAINLOAD_ACTION: &str = "bls-chainload"; +const BLS_CHAINLOAD_ACTION_PREFIX: &str = "bls-chainload-"; /// Scan the specified `filesystem` for BLS configurations. fn scan_for_bls( @@ -54,11 +54,14 @@ fn scan_for_bls( return Ok(false); } + // Generate a unique name for the BLS chainload action. + let chainload_action_name = format!("{}{}", BLS_CHAINLOAD_ACTION_PREFIX, root); + // BLS is now detected, generate a configuration for it. let generator = BlsConfiguration { entry: EntryDeclaration { title: "$title".to_string(), - actions: vec![BLS_CHAINLOAD_ACTION.to_string()], + actions: vec![chainload_action_name.clone()], ..Default::default() }, path: format!("{}\\loader", root), @@ -73,31 +76,25 @@ fn scan_for_bls( }, ); - // We had a BLS supported configuration, so return true. - Ok(true) -} - -/// Add the BLS actions to the configuration. -/// We should only do this once. -fn add_bls_actions(config: &mut RootConfiguration) -> Result<()> { // Generate a chainload configuration for BLS. // BLS will provide these values to us. let chainload = ChainloadConfiguration { - path: "$chainload".to_string(), + path: format!("{}\\$chainload", root), options: vec!["$options".to_string()], - linux_initrd: Some("$initrd".to_string()), + linux_initrd: Some(format!("{}\\$initrd", root)), }; // Insert the chainload action into the configuration. config.actions.insert( - BLS_CHAINLOAD_ACTION.to_string(), + chainload_action_name, ActionDeclaration { chainload: Some(chainload), ..Default::default() }, ); - Ok(()) + // We had a BLS supported configuration, so return true. + Ok(true) } /// Generate a [RootConfiguration] based on the environment. @@ -107,10 +104,6 @@ pub fn autoconfigure(config: &mut RootConfiguration) -> Result<()> { let filesystem_handles = uefi::boot::find_handles::().context("unable to scan filesystems")?; - // Whether we have any BLS-supported filesystems. - // We will key off of this to know if we should add BLS actions. - let mut has_any_bls = 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. @@ -128,16 +121,8 @@ pub fn autoconfigure(config: &mut RootConfiguration) -> Result<()> { let mut filesystem = FileSystem::new(filesystem); // Scan the filesystem for BLS supported configurations. - // If we find any, we will add a BLS generator to the configuration, and then - // at the end we will add the BLS actions to the configuration. - let bls_found = - scan_for_bls(&mut filesystem, &root, config).context("unable to scan filesystem")?; - has_any_bls = has_any_bls || bls_found; - } - - // If we had any BLS-supported filesystems, add the BLS actions to the configuration. - if has_any_bls { - add_bls_actions(config).context("unable to add BLS actions")?; + // If we find any, we will add a BLS generator to the configuration. + scan_for_bls(&mut filesystem, &root, config).context("unable to scan filesystem")?; } Ok(())