mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-19 13:50:16 +00:00
feat(integrations): implement initial bootloader interface touchpoints
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
use crate::context::SproutContext;
|
use crate::context::SproutContext;
|
||||||
|
use crate::integrations::bootloader_interface::BootloaderInterface;
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
use crate::utils::media_loader::MediaLoaderHandle;
|
use crate::utils::media_loader::MediaLoaderHandle;
|
||||||
use crate::utils::media_loader::constants::linux::LINUX_EFI_INITRD_MEDIA_GUID;
|
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);
|
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.
|
// Start the loaded image.
|
||||||
// This call might return, or it may pass full control to another image that will never return.
|
// 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
|
// 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::device_path::DevicePath;
|
||||||
use uefi::proto::media::file::{File, FileSystemVolumeLabel};
|
use uefi::proto::media::file::{File, FileSystemVolumeLabel};
|
||||||
use uefi::proto::media::fs::SimpleFileSystem;
|
use uefi::proto::media::fs::SimpleFileSystem;
|
||||||
use uefi::proto::media::partition::PartitionInfo;
|
use uefi::{CString16, Guid};
|
||||||
use uefi::{CString16, Guid, Handle};
|
|
||||||
use uefi_raw::Status;
|
|
||||||
|
|
||||||
/// The filesystem device match extractor.
|
/// The filesystem device match extractor.
|
||||||
/// This extractor finds a filesystem using some search criteria and returns
|
/// This extractor finds a filesystem using some search criteria and returns
|
||||||
@@ -41,48 +39,6 @@ pub struct FilesystemDeviceMatchExtractor {
|
|||||||
pub fallback: Option<String>,
|
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.
|
/// Extract a filesystem device path using the specified `context` and `extractor` configuration.
|
||||||
pub fn extract(
|
pub fn extract(
|
||||||
context: Rc<SproutContext>,
|
context: Rc<SproutContext>,
|
||||||
@@ -106,30 +62,49 @@ pub fn extract(
|
|||||||
// This defines whether a match has been found.
|
// This defines whether a match has been found.
|
||||||
let mut has_match = false;
|
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.
|
// Check if the partition info matches partition uuid criteria.
|
||||||
if let Some(ref partition_info) = partition_info
|
if let Some(ref has_partition_uuid) = extractor.has_partition_uuid {
|
||||||
&& 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)
|
let parsed_uuid = Guid::from_str(has_partition_uuid)
|
||||||
.map_err(|e| anyhow!("unable to parse has-partition-uuid: {}", e))?;
|
.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;
|
continue;
|
||||||
}
|
}
|
||||||
has_match = true;
|
has_match = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the partition info matches partition type uuid criteria.
|
// Check if the partition info matches partition type uuid criteria.
|
||||||
if let Some(ref partition_info) = partition_info
|
if let Some(ref has_partition_type_uuid) = extractor.has_partition_type_uuid {
|
||||||
&& 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)
|
let parsed_uuid = Guid::from_str(has_partition_type_uuid)
|
||||||
.map_err(|e| anyhow!("unable to parse has-partition-type-uuid: {}", e))?;
|
.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;
|
continue;
|
||||||
}
|
}
|
||||||
has_match = true;
|
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::config::RootConfiguration;
|
||||||
use crate::context::{RootContext, SproutContext};
|
use crate::context::{RootContext, SproutContext};
|
||||||
use crate::entries::BootableEntry;
|
use crate::entries::BootableEntry;
|
||||||
|
use crate::integrations::bootloader_interface::BootloaderInterface;
|
||||||
use crate::options::SproutOptions;
|
use crate::options::SproutOptions;
|
||||||
use crate::options::parser::OptionsRepresentable;
|
use crate::options::parser::OptionsRepresentable;
|
||||||
use crate::phases::phase;
|
use crate::phases::phase;
|
||||||
|
use crate::utils::PartitionGuidForm;
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
@@ -41,6 +43,9 @@ pub mod generators;
|
|||||||
/// menu: Display a boot menu to select an entry to boot.
|
/// menu: Display a boot menu to select an entry to boot.
|
||||||
pub mod menu;
|
pub mod menu;
|
||||||
|
|
||||||
|
/// integrations: Code that interacts with other systems.
|
||||||
|
pub mod integrations;
|
||||||
|
|
||||||
/// phases: Hooks into specific parts of the boot process.
|
/// phases: Hooks into specific parts of the boot process.
|
||||||
pub mod phases;
|
pub mod phases;
|
||||||
|
|
||||||
@@ -55,6 +60,10 @@ pub mod utils;
|
|||||||
|
|
||||||
/// Run Sprout, returning an error if one occurs.
|
/// Run Sprout, returning an error if one occurs.
|
||||||
fn run() -> Result<()> {
|
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.
|
// Parse the options to the sprout executable.
|
||||||
let options = SproutOptions::parse().context("unable to parse options")?;
|
let options = SproutOptions::parse().context("unable to parse options")?;
|
||||||
|
|
||||||
@@ -69,17 +78,31 @@ fn run() -> Result<()> {
|
|||||||
config::loader::load(&options)?
|
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.
|
// 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::<
|
let current_image_device_path_protocol = uefi::boot::open_protocol_exclusive::<
|
||||||
LoadedImageDevicePath,
|
LoadedImageDevicePath,
|
||||||
>(uefi::boot::image_handle())
|
>(uefi::boot::image_handle())
|
||||||
.context("unable to get loaded image device path")?;
|
.context("unable to get loaded image device path")?;
|
||||||
let loaded_image_path = current_image_device_path_protocol.deref().to_boxed();
|
current_image_device_path_protocol.deref().to_boxed()
|
||||||
RootContext::new(loaded_image_path, options)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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.
|
// Insert the configuration actions into the root context.
|
||||||
root.actions_mut().extend(config.actions.clone());
|
root.actions_mut().extend(config.actions.clone());
|
||||||
|
|
||||||
@@ -200,6 +223,21 @@ fn run() -> Result<()> {
|
|||||||
entry.mark_default();
|
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.
|
// Execute the late phase.
|
||||||
phase(context.clone(), &config.phases.late).context("unable to execute 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")?
|
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.
|
// Execute all the actions for the selected entry.
|
||||||
for action in &entry.declaration().actions {
|
for action in &entry.declaration().actions {
|
||||||
let action = entry.context().stamp(action);
|
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::text::{AllowShortcuts, DevicePathFromText, DisplayOnly};
|
||||||
use uefi::proto::device_path::{DevicePath, PoolDevicePath};
|
use uefi::proto::device_path::{DevicePath, PoolDevicePath};
|
||||||
use uefi::proto::media::fs::SimpleFileSystem;
|
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.
|
/// Support code for the EFI framebuffer.
|
||||||
pub mod 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 {
|
pub fn unique_hash(input: &str) -> String {
|
||||||
sha256::digest(input.as_bytes())
|
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