mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-19 10:30:17 +00:00
feat(integrations): implement initial bootloader interface touchpoints
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use crate::context::SproutContext;
|
||||
use crate::integrations::bootloader_interface::BootloaderInterface;
|
||||
use crate::utils;
|
||||
use crate::utils::media_loader::MediaLoaderHandle;
|
||||
use crate::utils::media_loader::constants::linux::LINUX_EFI_INITRD_MEDIA_GUID;
|
||||
@@ -102,6 +103,10 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
|
||||
initrd_handle = Some(handle);
|
||||
}
|
||||
|
||||
// Mark execution of an entry in the bootloader interface.
|
||||
BootloaderInterface::mark_exec()
|
||||
.context("unable to mark execution of boot entry in bootloader interface")?;
|
||||
|
||||
// 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
|
||||
|
||||
@@ -9,9 +9,7 @@ use uefi::fs::{FileSystem, Path};
|
||||
use uefi::proto::device_path::DevicePath;
|
||||
use uefi::proto::media::file::{File, FileSystemVolumeLabel};
|
||||
use uefi::proto::media::fs::SimpleFileSystem;
|
||||
use uefi::proto::media::partition::PartitionInfo;
|
||||
use uefi::{CString16, Guid, Handle};
|
||||
use uefi_raw::Status;
|
||||
use uefi::{CString16, Guid};
|
||||
|
||||
/// The filesystem device match extractor.
|
||||
/// This extractor finds a filesystem using some search criteria and returns
|
||||
@@ -41,48 +39,6 @@ pub struct FilesystemDeviceMatchExtractor {
|
||||
pub fallback: Option<String>,
|
||||
}
|
||||
|
||||
/// Represents the partition UUIDs for a filesystem.
|
||||
struct PartitionIds {
|
||||
/// The UUID of the partition.
|
||||
partition_uuid: Guid,
|
||||
/// The type UUID of the partition.
|
||||
type_uuid: Guid,
|
||||
}
|
||||
|
||||
/// Fetches the partition UUIDs for the specified filesystem handle.
|
||||
fn fetch_partition_uuids(handle: Handle) -> Result<Option<PartitionIds>> {
|
||||
// Open the partition info protocol for this handle.
|
||||
let partition_info = uefi::boot::open_protocol_exclusive::<PartitionInfo>(handle);
|
||||
|
||||
match partition_info {
|
||||
Ok(partition_info) => {
|
||||
// GPT partitions have a unique partition GUID.
|
||||
// MBR does not.
|
||||
if let Some(gpt) = partition_info.gpt_partition_entry() {
|
||||
let uuid = gpt.unique_partition_guid;
|
||||
let type_uuid = gpt.partition_type_guid;
|
||||
Ok(Some(PartitionIds {
|
||||
partition_uuid: uuid,
|
||||
type_uuid: type_uuid.0,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
Err(error) => {
|
||||
// If the filesystem does not have a partition, that is okay.
|
||||
if error.status() == Status::NOT_FOUND || error.status() == Status::UNSUPPORTED {
|
||||
Ok(None)
|
||||
} else {
|
||||
// We should still handle other errors gracefully.
|
||||
Err(error).context("unable to open filesystem partition info")?;
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract a filesystem device path using the specified `context` and `extractor` configuration.
|
||||
pub fn extract(
|
||||
context: Rc<SproutContext>,
|
||||
@@ -106,30 +62,49 @@ pub fn extract(
|
||||
// This defines whether a match has been found.
|
||||
let mut has_match = false;
|
||||
|
||||
// Extract the partition info for this filesystem.
|
||||
// There is no guarantee that the filesystem has a partition.
|
||||
let partition_info =
|
||||
fetch_partition_uuids(handle).context("unable to fetch partition info")?;
|
||||
|
||||
// Check if the partition info matches partition uuid criteria.
|
||||
if let Some(ref partition_info) = partition_info
|
||||
&& let Some(ref has_partition_uuid) = extractor.has_partition_uuid
|
||||
{
|
||||
if let Some(ref has_partition_uuid) = extractor.has_partition_uuid {
|
||||
// Parse the partition uuid from the extractor.
|
||||
let parsed_uuid = Guid::from_str(has_partition_uuid)
|
||||
.map_err(|e| anyhow!("unable to parse has-partition-uuid: {}", e))?;
|
||||
if partition_info.partition_uuid != parsed_uuid {
|
||||
|
||||
// Fetch the root of the device.
|
||||
let root = uefi::boot::open_protocol_exclusive::<DevicePath>(handle)
|
||||
.context("unable to fetch the device path of the filesystem")?
|
||||
.deref()
|
||||
.to_boxed();
|
||||
|
||||
// Fetch the partition uuid for this filesystem.
|
||||
let partition_uuid = utils::partition_guid(&root, utils::PartitionGuidForm::Partition)
|
||||
.context("unable to fetch the partition uuid of the filesystem")?;
|
||||
|
||||
// Compare the partition uuid to the parsed uuid.
|
||||
// If it does not match, continue to the next filesystem.
|
||||
if partition_uuid != Some(parsed_uuid) {
|
||||
continue;
|
||||
}
|
||||
has_match = true;
|
||||
}
|
||||
|
||||
// Check if the partition info matches partition type uuid criteria.
|
||||
if let Some(ref partition_info) = partition_info
|
||||
&& let Some(ref has_partition_type_uuid) = extractor.has_partition_type_uuid
|
||||
{
|
||||
if let Some(ref has_partition_type_uuid) = extractor.has_partition_type_uuid {
|
||||
// Parse the partition type uuid from the extractor.
|
||||
let parsed_uuid = Guid::from_str(has_partition_type_uuid)
|
||||
.map_err(|e| anyhow!("unable to parse has-partition-type-uuid: {}", e))?;
|
||||
if partition_info.type_uuid != parsed_uuid {
|
||||
|
||||
// Fetch the root of the device.
|
||||
let root = uefi::boot::open_protocol_exclusive::<DevicePath>(handle)
|
||||
.context("unable to fetch the device path of the filesystem")?
|
||||
.deref()
|
||||
.to_boxed();
|
||||
|
||||
// Fetch the partition uuid for this filesystem.
|
||||
let partition_type_uuid =
|
||||
utils::partition_guid(&root, utils::PartitionGuidForm::Partition)
|
||||
.context("unable to fetch the partition uuid of the filesystem")?;
|
||||
// Compare the partition type uuid to the parsed uuid.
|
||||
// If it does not match, continue to the next filesystem.
|
||||
if partition_type_uuid != Some(parsed_uuid) {
|
||||
continue;
|
||||
}
|
||||
has_match = true;
|
||||
|
||||
2
src/integrations.rs
Normal file
2
src/integrations.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
/// Implements support for the bootloader interface specification.
|
||||
pub mod bootloader_interface;
|
||||
43
src/integrations/bootloader_interface.rs
Normal file
43
src/integrations/bootloader_interface.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use anyhow::Result;
|
||||
use uefi::Guid;
|
||||
|
||||
/// Bootloader Interface support.
|
||||
pub struct BootloaderInterface;
|
||||
|
||||
impl BootloaderInterface {
|
||||
/// Tell the system that Sprout was initialized at the current time.
|
||||
pub fn mark_init() -> Result<()> {
|
||||
// TODO(azenla): Implement support for LoaderTimeInitUSec here.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tell the system that Sprout is about to execute the boot entry.
|
||||
pub fn mark_exec() -> Result<()> {
|
||||
// TODO(azenla): Implement support for LoaderTimeExecUSec here.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tell the system what the partition GUID of the ESP Sprout was booted from is.
|
||||
pub fn set_partition_guid(_guid: &Guid) -> Result<()> {
|
||||
// TODO(azenla): Implement support for LoaderDevicePartUUID here.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tell the system what boot entries are available.
|
||||
pub fn set_entries<N: AsRef<str>>(_entries: impl Iterator<Item = N>) -> Result<()> {
|
||||
// TODO(azenla): Implement support for LoaderEntries here.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tell the system what the default boot entry is.
|
||||
pub fn set_default_entry(_entry: String) -> Result<()> {
|
||||
// TODO(azenla): Implement support for LoaderEntryDefault here.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tell the system what the selected boot entry is.
|
||||
pub fn set_selected_entry(_entry: String) -> Result<()> {
|
||||
// TODO(azenla): Implement support for LoaderEntrySelected here.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
50
src/main.rs
50
src/main.rs
@@ -4,9 +4,11 @@
|
||||
use crate::config::RootConfiguration;
|
||||
use crate::context::{RootContext, SproutContext};
|
||||
use crate::entries::BootableEntry;
|
||||
use crate::integrations::bootloader_interface::BootloaderInterface;
|
||||
use crate::options::SproutOptions;
|
||||
use crate::options::parser::OptionsRepresentable;
|
||||
use crate::phases::phase;
|
||||
use crate::utils::PartitionGuidForm;
|
||||
use anyhow::{Context, Result, bail};
|
||||
use log::{error, info};
|
||||
use std::collections::BTreeMap;
|
||||
@@ -41,6 +43,9 @@ pub mod generators;
|
||||
/// menu: Display a boot menu to select an entry to boot.
|
||||
pub mod menu;
|
||||
|
||||
/// integrations: Code that interacts with other systems.
|
||||
pub mod integrations;
|
||||
|
||||
/// phases: Hooks into specific parts of the boot process.
|
||||
pub mod phases;
|
||||
|
||||
@@ -55,6 +60,10 @@ pub mod utils;
|
||||
|
||||
/// Run Sprout, returning an error if one occurs.
|
||||
fn run() -> Result<()> {
|
||||
// Mark the initialization of Sprout in the bootloader interface.
|
||||
BootloaderInterface::mark_init()
|
||||
.context("unable to mark initialization in bootloader interface")?;
|
||||
|
||||
// Parse the options to the sprout executable.
|
||||
let options = SproutOptions::parse().context("unable to parse options")?;
|
||||
|
||||
@@ -69,17 +78,31 @@ fn run() -> Result<()> {
|
||||
config::loader::load(&options)?
|
||||
};
|
||||
|
||||
// Load the root context.
|
||||
// Grab the sprout.efi loaded image path.
|
||||
// This is done in a block to ensure the release of the LoadedImageDevicePath protocol.
|
||||
let mut root = {
|
||||
let loaded_image_path = {
|
||||
let current_image_device_path_protocol = uefi::boot::open_protocol_exclusive::<
|
||||
LoadedImageDevicePath,
|
||||
>(uefi::boot::image_handle())
|
||||
.context("unable to get loaded image device path")?;
|
||||
let loaded_image_path = current_image_device_path_protocol.deref().to_boxed();
|
||||
RootContext::new(loaded_image_path, options)
|
||||
current_image_device_path_protocol.deref().to_boxed()
|
||||
};
|
||||
|
||||
// Grab the partition GUID of the ESP that sprout was loaded from.
|
||||
let loaded_image_partition_guid =
|
||||
utils::partition_guid(&loaded_image_path, PartitionGuidForm::Partition)
|
||||
.context("unable to retrieve loaded image partition guid")?;
|
||||
|
||||
// Set the partition GUID of the ESP that sprout was loaded from in the bootloader interface.
|
||||
if let Some(loaded_image_partition_guid) = loaded_image_partition_guid {
|
||||
// Tell the system about the partition GUID.
|
||||
BootloaderInterface::set_partition_guid(&loaded_image_partition_guid)
|
||||
.context("unable to set partition guid in bootloader interface")?;
|
||||
}
|
||||
|
||||
// Create the root context.
|
||||
let mut root = RootContext::new(loaded_image_path, options);
|
||||
|
||||
// Insert the configuration actions into the root context.
|
||||
root.actions_mut().extend(config.actions.clone());
|
||||
|
||||
@@ -200,6 +223,21 @@ fn run() -> Result<()> {
|
||||
entry.mark_default();
|
||||
}
|
||||
|
||||
// Iterate over all the entries and tell the bootloader interface what the entries are.
|
||||
for entry in &entries {
|
||||
// If the entry is the default entry, tell the bootloader interface it is the default.
|
||||
if entry.is_default() {
|
||||
// Tell the bootloader interface what the default entry is.
|
||||
BootloaderInterface::set_default_entry(entry.name().to_string())
|
||||
.context("unable to set default entry in bootloader interface")?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Tell the bootloader interface what entries are available.
|
||||
BootloaderInterface::set_entries(entries.iter().map(|entry| entry.name()))
|
||||
.context("unable to set entries in bootloader interface")?;
|
||||
|
||||
// Execute the late phase.
|
||||
phase(context.clone(), &config.phases.late).context("unable to execute late phase")?;
|
||||
|
||||
@@ -226,6 +264,10 @@ fn run() -> Result<()> {
|
||||
menu::select(menu_timeout, &entries).context("unable to select entry via boot menu")?
|
||||
};
|
||||
|
||||
// Tell the bootloader interface what the selected entry is.
|
||||
BootloaderInterface::set_selected_entry(entry.name().to_string())
|
||||
.context("unable to set selected entry in bootloader interface")?;
|
||||
|
||||
// Execute all the actions for the selected entry.
|
||||
for action in &entry.declaration().actions {
|
||||
let action = entry.context().stamp(action);
|
||||
|
||||
50
src/utils.rs
50
src/utils.rs
@@ -4,7 +4,9 @@ use uefi::fs::{FileSystem, Path};
|
||||
use uefi::proto::device_path::text::{AllowShortcuts, DevicePathFromText, DisplayOnly};
|
||||
use uefi::proto::device_path::{DevicePath, PoolDevicePath};
|
||||
use uefi::proto::media::fs::SimpleFileSystem;
|
||||
use uefi::{CString16, Handle};
|
||||
use uefi::proto::media::partition::PartitionInfo;
|
||||
use uefi::{CString16, Guid, Handle};
|
||||
use uefi_raw::Status;
|
||||
|
||||
/// Support code for the EFI framebuffer.
|
||||
pub mod framebuffer;
|
||||
@@ -181,3 +183,49 @@ pub fn combine_options<T: AsRef<str>>(options: impl Iterator<Item = T>) -> Strin
|
||||
pub fn unique_hash(input: &str) -> String {
|
||||
sha256::digest(input.as_bytes())
|
||||
}
|
||||
|
||||
/// Represents the type of partition GUID that can be retrieved.
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum PartitionGuidForm {
|
||||
Partition,
|
||||
PartitionType,
|
||||
}
|
||||
|
||||
/// Retrieve the partition / partition type GUID of the device root `path`.
|
||||
/// This only works on GPT partitions. If the root is not a GPT partition, None is returned.
|
||||
pub fn partition_guid(path: &DevicePath, form: PartitionGuidForm) -> Result<Option<Guid>> {
|
||||
// Clone the path so we can pass it to the UEFI stack.
|
||||
let path = path.to_boxed();
|
||||
let result = uefi::boot::locate_device_path::<PartitionInfo>(&mut &*path);
|
||||
let handle = match result {
|
||||
Ok(handle) => Ok(Some(handle)),
|
||||
Err(error) => {
|
||||
// If the error is NOT_FOUND or UNSUPPORTED, we can return None.
|
||||
// These are non-fatal errors.
|
||||
if error.status() == Status::NOT_FOUND || error.status() == Status::UNSUPPORTED {
|
||||
Ok(None)
|
||||
} else {
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
.context("unable to locate device path")?;
|
||||
|
||||
// If we have the handle, we can try to open the partition info protocol.
|
||||
if let Some(handle) = handle {
|
||||
// Open the partition info protocol.
|
||||
let partition_info = uefi::boot::open_protocol_exclusive::<PartitionInfo>(handle)
|
||||
.context("unable to open partition info protocol")?;
|
||||
// Find the unique partition GUID.
|
||||
// If this is not a GPT partition, this will produce None.
|
||||
Ok(partition_info
|
||||
.gpt_partition_entry()
|
||||
.map(|entry| match form {
|
||||
// Match the form of the partition GUID.
|
||||
PartitionGuidForm::Partition => entry.unique_partition_guid,
|
||||
PartitionGuidForm::PartitionType => entry.partition_type_guid.0,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user