mirror of
https://github.com/edera-dev/sprout.git
synced 2026-11-04 17:42:20 +00:00
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.
This commit is contained in:
1 parent
d41bcaa292
commit
5b345c82d1
4 files changed
+109
-23
No files matched your search
@@ -8,7 +8,7 @@ use alloc::vec::Vec;
|
|||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
use core::time::Duration;
|
use core::time::Duration;
|
||||||
use eficore::framebuffer::Framebuffer;
|
use eficore::framebuffer::Framebuffer;
|
||||||
use uefi::boot::{OpenProtocolAttributes, OpenProtocolParams, ScopedProtocol};
|
use uefi::boot::{OpenProtocolParams, ScopedProtocol};
|
||||||
use uefi::proto::ProtocolPointer;
|
use uefi::proto::ProtocolPointer;
|
||||||
use uefi::proto::console::gop::{BltPixel, GraphicsOutput};
|
use uefi::proto::console::gop::{BltPixel, GraphicsOutput};
|
||||||
use uefi::proto::console::pointer::{AbsolutePointer, Pointer};
|
use uefi::proto::console::pointer::{AbsolutePointer, Pointer};
|
||||||
@@ -99,24 +99,6 @@ struct Mouse {
|
|||||||
absolute: Option<ScopedProtocol<AbsolutePointer>>,
|
absolute: Option<ScopedProtocol<AbsolutePointer>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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<P: ProtocolPointer + ?Sized>(handle: Handle) -> uefi::Result<ScopedProtocol<P>> {
|
|
||||||
// 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::<P>(
|
|
||||||
OpenProtocolParams {
|
|
||||||
handle,
|
|
||||||
agent: uefi::boot::image_handle(),
|
|
||||||
controller: None,
|
|
||||||
},
|
|
||||||
OpenProtocolAttributes::GetProtocol,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Open the pointer protocol `P`, preferring the one on the console input handle.
|
/// 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.
|
/// 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.
|
/// Otherwise, the first device that has the protocol is used.
|
||||||
@@ -125,10 +107,10 @@ fn open_pointer<P: ProtocolPointer + ?Sized>() -> Option<ScopedProtocol<P>> {
|
|||||||
let console = uefi::table::system_table_raw()
|
let console = uefi::table::system_table_raw()
|
||||||
.and_then(|table| unsafe { Handle::from_ptr(table.as_ref().stdin_handle) });
|
.and_then(|table| unsafe { Handle::from_ptr(table.as_ref().stdin_handle) });
|
||||||
console
|
console
|
||||||
.and_then(|handle| open_shared::<P>(handle).ok())
|
.and_then(|handle| eficore::handle::open_shared::<P>(handle).ok())
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
uefi::boot::get_handle_for_protocol::<P>()
|
uefi::boot::get_handle_for_protocol::<P>()
|
||||||
.and_then(open_shared::<P>)
|
.and_then(eficore::handle::open_shared::<P>)
|
||||||
.ok()
|
.ok()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -407,7 +389,7 @@ impl Screen {
|
|||||||
for handle in handles {
|
for handle in handles {
|
||||||
// Opening it exclusively would disconnect the firmware's text console from the
|
// Opening it exclusively would disconnect the firmware's text console from the
|
||||||
// display, and anything printed after the menu may not show up.
|
// display, and anything printed after the menu may not show up.
|
||||||
let Ok(gop) = open_shared::<GraphicsOutput>(handle) else {
|
let Ok(gop) = eficore::handle::open_shared::<GraphicsOutput>(handle) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let (width, height) = gop.current_mode_info().resolution();
|
let (width, height) = gop.current_mode_info().resolution();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use anyhow::{Context, Result};
|
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::{Guid, Handle};
|
||||||
use uefi_raw::Status;
|
use uefi_raw::Status;
|
||||||
|
|
||||||
@@ -24,3 +25,21 @@ pub fn find_handle(protocol: &Guid) -> Result<Option<Handle>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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<P: ProtocolPointer + ?Sized>(handle: Handle) -> uefi::Result<ScopedProtocol<P>> {
|
||||||
|
// 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::<P>(
|
||||||
|
OpenProtocolParams {
|
||||||
|
handle,
|
||||||
|
agent: uefi::boot::image_handle(),
|
||||||
|
controller: None,
|
||||||
|
},
|
||||||
|
OpenProtocolAttributes::GetProtocol,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,9 @@ pub mod platform;
|
|||||||
/// Secure Boot support.
|
/// Secure Boot support.
|
||||||
pub mod secure;
|
pub mod secure;
|
||||||
|
|
||||||
|
/// Finding the Extended Boot Loader Partition.
|
||||||
|
pub mod xbootldr;
|
||||||
|
|
||||||
/// Support for the shim loader application that enables Secure Boot.
|
/// Support for the shim loader application that enables Secure Boot.
|
||||||
pub mod shim;
|
pub mod shim;
|
||||||
|
|
||||||
|
|||||||
@@ -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: <https://uapi-group.org/specifications/specs/boot_loader_specification/>
|
||||||
|
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<Option<Box<DevicePath>>> {
|
||||||
|
// 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::<SimpleFileSystem>().context("unable to find the filesystems")?;
|
||||||
|
|
||||||
|
let mut found: Option<(u32, Box<DevicePath>)> = None;
|
||||||
|
for handle in handles {
|
||||||
|
let Ok(candidate_path) = open_shared::<DevicePath>(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::<PartitionInfo>(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))
|
||||||
|
}
|
||||||
Reference in new issue
Block a user