Files
sprout/crates/boot/src/actions/chainload.rs
T

116 lines
5.0 KiB
Rust
Raw Normal View History

use crate::context::SproutContext;
use crate::phases::before_handoff;
use alloc::boxed::Box;
use alloc::rc::Rc;
use anyhow::{Context, Result, bail};
use edera_sprout_config::actions::chainload::ChainloadConfiguration;
use edera_sprout_parsing::{combine_options, empty_is_none};
use eficore::bootloader_interface::BootloaderInterface;
use eficore::loader::source::ImageSource;
use eficore::loader::{ImageLoadRequest, ImageLoader};
use eficore::media_loader::MediaLoaderHandle;
use eficore::media_loader::constants::linux::LINUX_EFI_INITRD_MEDIA_GUID;
use uefi::CString16;
use uefi::proto::loaded_image::LoadedImage;
2025-10-01 16:45:04 -07:00
/// Executes the chainload action using the specified `configuration` inside the provided `context`.
pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfiguration) -> Result<()> {
// Retrieve the current image handle of sprout.
2025-10-01 16:45:04 -07:00
let sprout_image = uefi::boot::image_handle();
// Resolve the path to the image to chainload.
let resolved = eficore::path::resolve_path(
Some(context.root().loaded_image_path()?),
context.stamp(&configuration.path),
)
.context("unable to resolve chainload path")?;
2025-10-01 16:45:04 -07:00
// Create a new image load request with the current image and the resolved path.
let request = ImageLoadRequest::new(sprout_image, ImageSource::ResolvedPath(&resolved));
// Load the image to chainload using the image loader support module.
2025-10-30 21:38:49 -04:00
// It will determine if the image needs to be loaded via the shim or can be loaded directly.
let image = ImageLoader::load(request)?;
2025-10-01 19:15:42 -07:00
// Stamp and combine the options to pass to the image.
let options = combine_options(context.stamp_iter(configuration.options.iter()));
2025-10-12 18:06:57 -07:00
// Pass the load options to the image.
// If no options are provided, the resulting string will be empty.
// The options are pinned and boxed to ensure that they are valid for the lifetime of this
// function, which ensures the lifetime of the options for the image runtime.
let options = Box::pin(
CString16::try_from(&options[..])
.context("unable to convert chainloader options to CString16")?,
);
2025-10-12 17:45:16 -07:00
// Ensure the chainloader options limit is not exceeded.
if options.num_bytes() > u32::MAX as usize {
bail!("chainloader options too large");
}
// Open the LoadedImage protocol of the image to chainload to pass the load options.
// This is done in a block to release the protocol before the image is started, as the
// image may itself need to open its LoadedImage protocol exclusively.
{
let mut loaded_image_protocol =
uefi::boot::open_protocol_exclusive::<LoadedImage>(*image.handle())
.context("unable to open loaded image protocol")?;
// 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.
unsafe {
loaded_image_protocol
.set_load_options(options.as_ptr() as *const u8, options.num_bytes() as u32);
}
}
// Stamp the initrd path, if provided.
let initrd = configuration
.linux_initrd
.as_ref()
.map(|item| context.stamp(item));
// The initrd can be None or empty, so we need to collapse that into a single Option.
let initrd = empty_is_none(initrd);
// If an initrd is provided, register it with the EFI stack.
let mut initrd_handle = None;
if let Some(linux_initrd) = initrd {
let content = eficore::path::read_file_contents(
Some(context.root().loaded_image_path()?),
&linux_initrd,
)
.context("unable to read linux initrd")?;
2025-10-18 23:49:00 -07:00
let handle =
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);
}
// Mark execution of an entry in the bootloader interface.
BootloaderInterface::mark_exec(context.root().timer())
.context("unable to mark execution of boot entry in bootloader interface")?;
// Since we are about to hand off control to another image, we need to execute the handoff hook.
// This will perform operations like clearing the screen.
before_handoff(&context).context("unable to execute before handoff hook")?;
// 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.
let result = uefi::boot::start_image(*image.handle());
// Assert there was no error starting the image.
result.context("unable to start image")?;
// Explicitly drop the options to clarify the lifetime.
drop(options);
// Explicitly drop the initrd handle to clarify when it should be unregistered.
drop(initrd_handle);
// Return control to sprout.
Ok(())
2025-10-01 16:45:04 -07:00
}