diff --git a/src/actions/chainload.rs b/src/actions/chainload.rs index b14015f..8223058 100644 --- a/src/actions/chainload.rs +++ b/src/actions/chainload.rs @@ -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, 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 diff --git a/src/extractors/filesystem_device_match.rs b/src/extractors/filesystem_device_match.rs index 6a6a75a..5bfb0be 100644 --- a/src/extractors/filesystem_device_match.rs +++ b/src/extractors/filesystem_device_match.rs @@ -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, } -/// 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> { - // Open the partition info protocol for this handle. - let partition_info = uefi::boot::open_protocol_exclusive::(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, @@ -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::(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::(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; diff --git a/src/integrations.rs b/src/integrations.rs new file mode 100644 index 0000000..286bf58 --- /dev/null +++ b/src/integrations.rs @@ -0,0 +1,2 @@ +/// Implements support for the bootloader interface specification. +pub mod bootloader_interface; diff --git a/src/integrations/bootloader_interface.rs b/src/integrations/bootloader_interface.rs new file mode 100644 index 0000000..5d43b76 --- /dev/null +++ b/src/integrations/bootloader_interface.rs @@ -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>(_entries: impl Iterator) -> 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(()) + } +} diff --git a/src/main.rs b/src/main.rs index 40caa9e..bbe8f8d 100644 --- a/src/main.rs +++ b/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); diff --git a/src/utils.rs b/src/utils.rs index e988b8c..936a508 100644 --- a/src/utils.rs +++ b/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>(options: impl Iterator) -> 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> { + // Clone the path so we can pass it to the UEFI stack. + let path = path.to_boxed(); + let result = uefi::boot::locate_device_path::(&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::(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) + } +}