2025-10-11 14:35:29 -07:00
|
|
|
use crate::context::SproutContext;
|
2025-10-02 00:24:19 -07:00
|
|
|
use crate::utils;
|
2025-10-14 18:11:41 -07:00
|
|
|
use crate::utils::media_loader::MediaLoaderHandle;
|
2025-10-18 23:49:00 -07:00
|
|
|
use crate::utils::media_loader::constants::linux::LINUX_EFI_INITRD_MEDIA_GUID;
|
2025-10-11 14:35:29 -07:00
|
|
|
use anyhow::{Context, Result, bail};
|
2025-10-27 00:20:42 -04:00
|
|
|
use log::{error, info};
|
2025-10-11 14:11:31 -07:00
|
|
|
use serde::{Deserialize, Serialize};
|
2025-10-04 23:12:01 -07:00
|
|
|
use std::rc::Rc;
|
2025-10-02 00:24:19 -07:00
|
|
|
use uefi::CString16;
|
2025-10-01 21:30:43 -07:00
|
|
|
use uefi::proto::loaded_image::LoadedImage;
|
2025-10-01 16:45:04 -07:00
|
|
|
|
2025-10-19 21:44:05 -07:00
|
|
|
/// The configuration of the chainload action.
|
2025-10-27 03:37:09 -04:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
2025-10-11 14:11:31 -07:00
|
|
|
pub struct ChainloadConfiguration {
|
2025-10-19 21:44:05 -07:00
|
|
|
/// The path to the image to chainload.
|
|
|
|
|
/// This can be a Linux EFI stub (vmlinuz usually) or a standard EFI executable.
|
2025-10-11 14:11:31 -07:00
|
|
|
pub path: String,
|
2025-10-19 21:44:05 -07:00
|
|
|
/// The options to pass to the image.
|
|
|
|
|
/// The options are concatenated by a space and then passed to the EFI application.
|
2025-10-11 14:11:31 -07:00
|
|
|
#[serde(default)]
|
|
|
|
|
pub options: Vec<String>,
|
2025-10-19 21:44:05 -07:00
|
|
|
/// An optional path to a Linux initrd.
|
|
|
|
|
/// This uses the [LINUX_EFI_INITRD_MEDIA_GUID] mechanism to load the initrd into the EFI stack.
|
|
|
|
|
/// For Linux, you can also use initrd=\path\to\initrd as an option, but this option is
|
|
|
|
|
/// generally better and safer as it can support additional load options in the future.
|
2025-10-12 20:22:24 -07:00
|
|
|
#[serde(default, rename = "linux-initrd")]
|
|
|
|
|
pub linux_initrd: Option<String>,
|
2025-10-11 14:11:31 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-19 21:44:05 -07:00
|
|
|
/// Executes the chainload action using the specified `configuration` inside the provided `context`.
|
2025-10-11 14:35:29 -07:00
|
|
|
pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfiguration) -> Result<()> {
|
2025-10-19 21:44:05 -07:00
|
|
|
// Retrieve the current image handle of sprout.
|
2025-10-01 16:45:04 -07:00
|
|
|
let sprout_image = uefi::boot::image_handle();
|
|
|
|
|
|
2025-10-19 21:44:05 -07:00
|
|
|
// Resolve the path to the image to chainload.
|
2025-10-13 00:55:11 -07:00
|
|
|
let resolved = utils::resolve_path(
|
|
|
|
|
context.root().loaded_image_path()?,
|
|
|
|
|
&context.stamp(&configuration.path),
|
|
|
|
|
)
|
2025-10-14 12:47:33 -07:00
|
|
|
.context("unable to resolve chainload path")?;
|
2025-10-01 16:45:04 -07:00
|
|
|
|
2025-10-19 21:44:05 -07:00
|
|
|
// Load the image to chainload.
|
2025-10-01 16:45:04 -07:00
|
|
|
let image = uefi::boot::load_image(
|
|
|
|
|
sprout_image,
|
|
|
|
|
uefi::boot::LoadImageSource::FromDevicePath {
|
2025-10-13 00:55:11 -07:00
|
|
|
device_path: &resolved.full_path,
|
2025-10-01 16:45:04 -07:00
|
|
|
boot_policy: uefi::proto::BootPolicy::ExactMatch,
|
|
|
|
|
},
|
|
|
|
|
)
|
2025-10-14 12:47:33 -07:00
|
|
|
.context("unable to load image")?;
|
2025-10-01 19:15:42 -07:00
|
|
|
|
2025-10-19 21:44:05 -07:00
|
|
|
// Open the LoadedImage protocol of the image to chainload.
|
2025-10-02 00:24:19 -07:00
|
|
|
let mut loaded_image_protocol = uefi::boot::open_protocol_exclusive::<LoadedImage>(image)
|
2025-10-11 14:35:29 -07:00
|
|
|
.context("unable to open loaded image protocol")?;
|
2025-10-01 19:15:42 -07:00
|
|
|
|
2025-10-27 17:44:30 -04:00
|
|
|
// Stamp and combine the options to pass to the image.
|
|
|
|
|
let options =
|
|
|
|
|
utils::combine_options(configuration.options.iter().map(|item| context.stamp(item)));
|
2025-10-12 18:06:57 -07:00
|
|
|
|
2025-10-19 21:44:05 -07:00
|
|
|
// Pass the options to the image, if any are provided.
|
|
|
|
|
// The holder must drop at the end of this function to ensure the options are not leaked,
|
|
|
|
|
// and the holder here ensures it outlives the if block here, as a pointer has to be
|
2025-10-27 16:16:09 -04:00
|
|
|
// passed to the image.
|
|
|
|
|
// SAFETY: The options outlive the usage of the image, and the image is not used after this.
|
2025-10-12 18:06:57 -07:00
|
|
|
let mut options_holder: Option<Box<CString16>> = None;
|
2025-10-02 00:24:19 -07:00
|
|
|
if !options.is_empty() {
|
|
|
|
|
let options = Box::new(
|
|
|
|
|
CString16::try_from(&options[..])
|
2025-10-11 14:35:29 -07:00
|
|
|
.context("unable to convert chainloader options to CString16")?,
|
2025-10-02 00:24:19 -07:00
|
|
|
);
|
2025-10-12 17:45:16 -07:00
|
|
|
|
2025-10-27 00:20:42 -04:00
|
|
|
info!("options: {}", options);
|
2025-10-02 00:24:19 -07:00
|
|
|
|
|
|
|
|
if options.num_bytes() > u32::MAX as usize {
|
2025-10-11 14:35:29 -07:00
|
|
|
bail!("chainloader options too large");
|
2025-10-02 00:24:19 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-12 18:06:57 -07:00
|
|
|
// SAFETY: option size is checked to validate it is safe to pass.
|
|
|
|
|
// Additionally, the pointer is allocated and retained on heap, which makes
|
|
|
|
|
// passing the `options` pointer safe to the next image.
|
2025-10-02 00:24:19 -07:00
|
|
|
unsafe {
|
|
|
|
|
loaded_image_protocol
|
|
|
|
|
.set_load_options(options.as_ptr() as *const u8, options.num_bytes() as u32);
|
|
|
|
|
}
|
2025-10-12 18:06:57 -07:00
|
|
|
options_holder = Some(options);
|
2025-10-02 00:24:19 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-27 16:27:39 -04:00
|
|
|
// The initrd can be None or empty, so we need to collapse that into a single Option.
|
2025-10-27 17:44:30 -04:00
|
|
|
let initrd = utils::empty_is_none(configuration.linux_initrd.as_ref());
|
2025-10-27 16:27:39 -04:00
|
|
|
|
|
|
|
|
// If an initrd is provided, register it with the EFI stack.
|
2025-10-12 20:22:24 -07:00
|
|
|
let mut initrd_handle = None;
|
2025-10-27 17:44:30 -04:00
|
|
|
if let Some(linux_initrd) = initrd {
|
|
|
|
|
// Stamp the path to the initrd.
|
|
|
|
|
let linux_initrd = context.stamp(linux_initrd);
|
|
|
|
|
let content = utils::read_file_contents(context.root().loaded_image_path()?, &linux_initrd)
|
2025-10-14 12:47:33 -07:00
|
|
|
.context("unable to read linux initrd")?;
|
2025-10-18 23:49:00 -07:00
|
|
|
let handle =
|
2025-10-14 18:11:41 -07:00
|
|
|
MediaLoaderHandle::register(LINUX_EFI_INITRD_MEDIA_GUID, content.into_boxed_slice())
|
2025-10-18 23:49:00 -07:00
|
|
|
.context("unable to register linux initrd")?;
|
|
|
|
|
initrd_handle = Some(handle);
|
2025-10-12 20:22:24 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-19 21:44:05 -07:00
|
|
|
// 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
|
|
|
|
|
// after the optional initrd has been unregistered.
|
2025-10-27 16:16:09 -04:00
|
|
|
let result = uefi::boot::start_image(image);
|
2025-10-19 21:44:05 -07:00
|
|
|
|
|
|
|
|
// Unregister the initrd if it was registered.
|
2025-10-18 23:26:05 -07:00
|
|
|
if let Some(initrd_handle) = initrd_handle
|
|
|
|
|
&& let Err(error) = initrd_handle.unregister()
|
|
|
|
|
{
|
|
|
|
|
error!("unable to unregister linux initrd: {}", error);
|
2025-10-12 20:22:24 -07:00
|
|
|
}
|
2025-10-19 21:44:05 -07:00
|
|
|
|
|
|
|
|
// Assert there was no error starting the image.
|
2025-10-14 12:47:33 -07:00
|
|
|
result.context("unable to start image")?;
|
2025-10-19 21:44:05 -07:00
|
|
|
// Explicitly drop the option holder to clarify the lifetime.
|
2025-10-12 18:06:57 -07:00
|
|
|
drop(options_holder);
|
2025-10-19 21:44:05 -07:00
|
|
|
|
|
|
|
|
// Return control to sprout.
|
2025-10-11 14:35:29 -07:00
|
|
|
Ok(())
|
2025-10-01 16:45:04 -07:00
|
|
|
}
|