From 5b345c82d1f581b97d83272229861f00ac4e20b9 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sat, 3 Oct 2026 19:50:45 -0700 Subject: [PATCH] feat(eficore): find the extended boot loader partition on the disk of a partition Look for a filesystem on the same disk whose GPT partition type is the XBOOTLDR type, using the partition info protocol. open_shared moves to the handle module so the graphical menu and the discovery share it. --- crates/boot/src/menu/graphical.rs | 26 ++-------- crates/eficore/src/handle.rs | 21 +++++++- crates/eficore/src/lib.rs | 3 ++ crates/eficore/src/xbootldr.rs | 82 +++++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 23 deletions(-) create mode 100644 crates/eficore/src/xbootldr.rs diff --git a/crates/boot/src/menu/graphical.rs b/crates/boot/src/menu/graphical.rs index ebee1d1..e0e22ba 100644 --- a/crates/boot/src/menu/graphical.rs +++ b/crates/boot/src/menu/graphical.rs @@ -8,7 +8,7 @@ use alloc::vec::Vec; use anyhow::{Context, Result, bail}; use core::time::Duration; use eficore::framebuffer::Framebuffer; -use uefi::boot::{OpenProtocolAttributes, OpenProtocolParams, ScopedProtocol}; +use uefi::boot::{OpenProtocolParams, ScopedProtocol}; use uefi::proto::ProtocolPointer; use uefi::proto::console::gop::{BltPixel, GraphicsOutput}; use uefi::proto::console::pointer::{AbsolutePointer, Pointer}; @@ -99,24 +99,6 @@ struct Mouse { absolute: Option>, } -/// Open the protocol `P` on `handle` without taking it from the firmware. -/// An exclusive open makes the firmware disconnect every driver that is using the protocol, -/// and on some firmware that never returns. -fn open_shared(handle: Handle) -> uefi::Result> { - // SAFETY: The protocols opened this way are only used from this thread, and the firmware - // keeps them installed for as long as the menu is open. - unsafe { - uefi::boot::open_protocol::

( - OpenProtocolParams { - handle, - agent: uefi::boot::image_handle(), - controller: None, - }, - OpenProtocolAttributes::GetProtocol, - ) - } -} - /// Open the pointer protocol `P`, preferring the one on the console input handle. /// That one belongs to the console splitter, which combines every device and is never removed. /// Otherwise, the first device that has the protocol is used. @@ -125,10 +107,10 @@ fn open_pointer() -> Option> { let console = uefi::table::system_table_raw() .and_then(|table| unsafe { Handle::from_ptr(table.as_ref().stdin_handle) }); console - .and_then(|handle| open_shared::

(handle).ok()) + .and_then(|handle| eficore::handle::open_shared::

(handle).ok()) .or_else(|| { uefi::boot::get_handle_for_protocol::

() - .and_then(open_shared::

) + .and_then(eficore::handle::open_shared::

) .ok() }) } @@ -407,7 +389,7 @@ impl Screen { for handle in handles { // Opening it exclusively would disconnect the firmware's text console from the // display, and anything printed after the menu may not show up. - let Ok(gop) = open_shared::(handle) else { + let Ok(gop) = eficore::handle::open_shared::(handle) else { continue; }; let (width, height) = gop.current_mode_info().resolution(); diff --git a/crates/eficore/src/handle.rs b/crates/eficore/src/handle.rs index ed0ca1e..c69b616 100644 --- a/crates/eficore/src/handle.rs +++ b/crates/eficore/src/handle.rs @@ -1,5 +1,6 @@ use anyhow::{Context, Result}; -use uefi::boot::SearchType; +use uefi::boot::{OpenProtocolAttributes, OpenProtocolParams, ScopedProtocol, SearchType}; +use uefi::proto::ProtocolPointer; use uefi::{Guid, Handle}; use uefi_raw::Status; @@ -24,3 +25,21 @@ pub fn find_handle(protocol: &Guid) -> Result> { } } } + +/// Open the protocol `P` on `handle` without taking it from the firmware. +/// An exclusive open makes the firmware disconnect every driver that is using the protocol, +/// which for a disk or a partition would remove the filesystem Sprout is running from. +pub fn open_shared(handle: Handle) -> uefi::Result> { + // SAFETY: The protocols opened this way are only used from this thread, and the firmware + // keeps them installed for as long as they are open. + unsafe { + uefi::boot::open_protocol::

( + OpenProtocolParams { + handle, + agent: uefi::boot::image_handle(), + controller: None, + }, + OpenProtocolAttributes::GetProtocol, + ) + } +} diff --git a/crates/eficore/src/lib.rs b/crates/eficore/src/lib.rs index 3819196..de0ee96 100644 --- a/crates/eficore/src/lib.rs +++ b/crates/eficore/src/lib.rs @@ -27,6 +27,9 @@ pub mod platform; /// Secure Boot support. pub mod secure; +/// Finding the Extended Boot Loader Partition. +pub mod xbootldr; + /// Support for the shim loader application that enables Secure Boot. pub mod shim; diff --git a/crates/eficore/src/xbootldr.rs b/crates/eficore/src/xbootldr.rs new file mode 100644 index 0000000..04f86bd --- /dev/null +++ b/crates/eficore/src/xbootldr.rs @@ -0,0 +1,82 @@ +use crate::handle::open_shared; +use alloc::boxed::Box; +use alloc::vec::Vec; +use anyhow::{Context, Result}; +use uefi::proto::device_path::{DevicePath, DevicePathNode, DeviceSubType, DeviceType}; +use uefi::proto::media::fs::SimpleFileSystem; +use uefi::proto::media::partition::PartitionInfo; +use uefi::{Guid, guid}; + +/// The GPT partition type of the Extended Boot Loader Partition. +/// Reference: +pub const XBOOTLDR_PARTITION_TYPE: Guid = guid!("bc13c2ff-59e6-4262-a352-b275fd6f7172"); + +/// Whether `node` is the node of a partition on a hard drive. +fn is_hard_drive_node(node: &DevicePathNode) -> bool { + node.full_type() == (DeviceType::MEDIA, DeviceSubType::MEDIA_HARD_DRIVE) +} + +/// The device nodes of `path`, without the file path nodes that follow the device. +fn device_nodes(path: &DevicePath) -> Vec<&DevicePathNode> { + path.node_iter() + .filter(|node| node.full_type() != (DeviceType::MEDIA, DeviceSubType::MEDIA_FILE_PATH)) + .collect() +} + +/// Find the Extended Boot Loader Partition on the same disk as the partition at `path`, +/// which is usually the EFI system partition that Sprout was loaded from. Only a partition +/// that has a filesystem the firmware can read is found. Returns the device path of its +/// filesystem, or None if there is no such partition, such as on a disk without a GPT. +pub fn find_xbootldr(path: &DevicePath) -> Result>> { + // The partition is the last hard drive node. The disk is everything before it. + let own = device_nodes(path); + let Some(partition_index) = own.iter().rposition(|node| is_hard_drive_node(node)) else { + // There is no partition, such as when booting from an optical disc or the network. + return Ok(None); + }; + let disk = &own[..partition_index]; + let own_partition = own[partition_index]; + + let handles = + uefi::boot::find_handles::().context("unable to find the filesystems")?; + + let mut found: Option<(u32, Box)> = None; + for handle in handles { + let Ok(candidate_path) = open_shared::(handle) else { + continue; + }; + let nodes = device_nodes(&candidate_path); + + // The candidate must be a partition of the same disk, and not the partition itself. + if nodes.len() != disk.len() + 1 + || nodes[..disk.len()] != *disk + || !is_hard_drive_node(nodes[disk.len()]) + || nodes[disk.len()] == own_partition + { + continue; + } + + // The partition type is only available for a GPT partition. + let Ok(info) = open_shared::(handle) else { + continue; + }; + let Some(entry) = info.gpt_partition_entry() else { + continue; + }; + // The entry is packed, so the field is copied before it is compared. + let partition_type = entry.partition_type_guid.0; + if partition_type != XBOOTLDR_PARTITION_TYPE { + continue; + } + + // There should only be one, but if there are several, use the first partition. + let number = <&uefi::proto::device_path::media::HardDrive>::try_from(nodes[disk.len()]) + .map(|drive| drive.partition_number()) + .unwrap_or(u32::MAX); + if found.as_ref().is_none_or(|(best, _)| number < *best) { + found = Some((number, candidate_path.to_boxed())); + } + } + + Ok(found.map(|(_, path)| path)) +}