From 2bf4013938e1c8a0df72e716e21c6e984679053a Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Mon, 27 Oct 2025 19:47:21 -0400 Subject: [PATCH] feat(autoconfigure): improved linux support and windows support --- src/actions/chainload.rs | 9 ++-- src/autoconfigure.rs | 7 ++++ src/autoconfigure/linux.rs | 21 +++++++--- src/autoconfigure/windows.rs | 80 ++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + 5 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 src/autoconfigure/windows.rs diff --git a/src/actions/chainload.rs b/src/actions/chainload.rs index f5466d4..7cbce92 100644 --- a/src/actions/chainload.rs +++ b/src/actions/chainload.rs @@ -85,14 +85,17 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfigurat options_holder = Some(options); } + // Stamp the initrd path, if provided. + let initrd = configuration + .linux_initrd + .as_ref() + .map(|item| context.stamp(item)); // The initrd can be None or empty, so we need to collapse that into a single Option. - let initrd = utils::empty_is_none(configuration.linux_initrd.as_ref()); + let initrd = utils::empty_is_none(initrd); // If an initrd is provided, register it with the EFI stack. let mut initrd_handle = None; if let Some(linux_initrd) = initrd { - // Stamp the path to the initrd. - let linux_initrd = context.stamp(linux_initrd); let content = utils::read_file_contents(context.root().loaded_image_path()?, &linux_initrd) .context("unable to read linux initrd")?; let handle = diff --git a/src/autoconfigure.rs b/src/autoconfigure.rs index d78c683..92e17f2 100644 --- a/src/autoconfigure.rs +++ b/src/autoconfigure.rs @@ -12,6 +12,9 @@ pub mod bls; /// on BLS-enabled filesystems as it may make duplicate entries. pub mod linux; +/// windows: autodetect and configure Windows boot configurations. +pub mod windows; + /// 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<()> { @@ -44,6 +47,10 @@ pub fn autoconfigure(config: &mut RootConfiguration) -> Result<()> { linux::scan(&mut filesystem, &root, config) .context("unable to scan for linux configurations")?; } + + // Always look for Windows configurations. + windows::scan(&mut filesystem, &root, config) + .context("unable to scan for windows configurations")?; } Ok(()) diff --git a/src/autoconfigure/linux.rs b/src/autoconfigure/linux.rs index d471484..ab7cd41 100644 --- a/src/autoconfigure/linux.rs +++ b/src/autoconfigure/linux.rs @@ -6,6 +6,7 @@ use crate::generators::GeneratorDeclaration; use crate::generators::list::ListConfiguration; use crate::utils; use anyhow::{Context, Result}; +use log::info; use std::collections::BTreeMap; use uefi::CString16; use uefi::fs::{FileSystem, Path}; @@ -23,7 +24,7 @@ const SCAN_LOCATIONS: &[&str] = &["/boot", "/"]; const KERNEL_PREFIXES: &[&str] = &["vmlinuz"]; /// Prefixes of initramfs files to match to. -const INITRAMFS_PREFIXES: &[&str] = &["initramfs", "initrd"]; +const INITRAMFS_PREFIXES: &[&str] = &["initramfs", "initrd", "initrd.img"]; /// Pair of kernel and initramfs. /// This is what scanning a directory is meant to find. @@ -158,7 +159,7 @@ pub fn scan( // Kernel pairs are detected, generate a list configuration for it. let generator = ListConfiguration { entry: EntryDeclaration { - title: "Boot Linux $kernel".to_string(), + title: "Boot Linux $name".to_string(), actions: vec![chainload_action_name.clone()], ..Default::default() }, @@ -166,8 +167,14 @@ pub fn scan( .into_iter() .map(|pair| { BTreeMap::from_iter(vec![ - ("kernel".to_string(), pair.kernel), - ("initrd".to_string(), pair.initramfs.unwrap_or_default()), + ("name".to_string(), pair.kernel.clone()), + ("kernel".to_string(), format!("{}{}", root, pair.kernel)), + ( + "initrd".to_string(), + pair.initramfs + .map(|initramfs| format!("{}{}", root, initramfs)) + .unwrap_or_default(), + ), ]) }) .collect(), @@ -194,9 +201,9 @@ pub fn scan( // Note that we don't need an extra \\ in the paths here. // The root already contains a trailing slash. let chainload = ChainloadConfiguration { - path: format!("{}$kernel", root), + path: "$kernel".to_string(), options: vec!["$linux-options".to_string()], - linux_initrd: Some(format!("{}$initrd", root)), + linux_initrd: Some("$initrd".to_string()), }; // Insert the chainload action into the configuration. @@ -208,6 +215,8 @@ pub fn scan( }, ); + info!("{:?}", config); + // We had a Linux kernel, so return true to indicate something was found. Ok(true) } diff --git a/src/autoconfigure/windows.rs b/src/autoconfigure/windows.rs new file mode 100644 index 0000000..a1651b2 --- /dev/null +++ b/src/autoconfigure/windows.rs @@ -0,0 +1,80 @@ +use crate::actions::ActionDeclaration; +use crate::actions::chainload::ChainloadConfiguration; +use crate::config::RootConfiguration; +use crate::entries::EntryDeclaration; +use crate::utils; +use anyhow::{Context, Result}; +use uefi::CString16; +use uefi::fs::{FileSystem, Path}; +use uefi::proto::device_path::DevicePath; +use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly}; + +/// The name prefix of the Windows chainload action that will be used to boot Windows. +const WINDOWS_CHAINLOAD_ACTION_PREFIX: &str = "windows-chainload-"; + +/// Windows boot manager path. +const BOOTMGR_FW_PATH: &str = "\\EFI\\Microsoft\\Boot\\bootmgfw.efi"; + +/// Scan the specified `filesystem` for Windows configurations. +pub fn scan( + filesystem: &mut FileSystem, + root: &DevicePath, + config: &mut RootConfiguration, +) -> Result { + // Convert the boot manager firmware path to a path. + let bootmgr_fw_path = + CString16::try_from(BOOTMGR_FW_PATH).context("unable to convert path to CString16")?; + let bootmgr_fw_path = Path::new(&bootmgr_fw_path); + + // Check if the boot manager firmware path exists, if it doesn't, return false. + if !filesystem + .try_exists(bootmgr_fw_path) + .context("unable to check if bootmgr firmware path exists")? + { + return Ok(false); + } + + // Convert the device path root to a string we can use in the configuration. + 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('/'); + + // Generate a unique hash of the root path. + let root_unique_hash = utils::unique_hash(&root); + + // Generate a unique name for the Windows chainload action. + let chainload_action_name = format!("{}{}", WINDOWS_CHAINLOAD_ACTION_PREFIX, root_unique_hash,); + + // Generate an entry name for Windows. + let entry_name = format!("autoconfigure-windows-{}", root_unique_hash,); + + // Create an entry for Windows and insert it into the configuration. + let entry = EntryDeclaration { + title: "Boot Windows".to_string(), + actions: vec![chainload_action_name.clone()], + values: Default::default(), + }; + config.entries.insert(entry_name, entry); + + // Generate a chainload configuration for Windows. + let chainload = ChainloadConfiguration { + path: format!("{}{}", root, bootmgr_fw_path), + options: vec![], + ..Default::default() + }; + + // Insert the chainload action into the configuration. + config.actions.insert( + chainload_action_name, + ActionDeclaration { + chainload: Some(chainload), + ..Default::default() + }, + ); + + // We have a Windows boot entry, so return true to indicate something was found. + Ok(true) +} diff --git a/src/main.rs b/src/main.rs index d31158a..d029ed3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -248,6 +248,7 @@ fn main() -> Result<()> { for (index, stack) in error.chain().enumerate() { error!("[{}]: {}", index, stack); } + uefi::boot::stall(Duration::from_secs(10)); } // Sprout doesn't necessarily guarantee anything was booted.