2025-10-11 14:35:29 -07:00
|
|
|
use crate::context::SproutContext;
|
2025-12-20 21:45:35 -08:00
|
|
|
use crate::phases::before_handoff;
|
2025-11-03 02:04:21 -05:00
|
|
|
use alloc::boxed::Box;
|
|
|
|
|
use alloc::rc::Rc;
|
2026-09-26 19:45:47 -07:00
|
|
|
use alloc::vec::Vec;
|
2025-10-11 14:35:29 -07:00
|
|
|
use anyhow::{Context, Result, bail};
|
2025-11-02 23:28:31 -05:00
|
|
|
use edera_sprout_config::actions::chainload::ChainloadConfiguration;
|
2026-09-26 20:19:25 -07:00
|
|
|
use edera_sprout_parsing::{append_initrd, combine_options, empty_is_none};
|
2025-11-03 20:47:21 -05:00
|
|
|
use eficore::bootloader_interface::BootloaderInterface;
|
2025-11-06 11:52:00 -05:00
|
|
|
use eficore::loader::source::ImageSource;
|
|
|
|
|
use eficore::loader::{ImageLoadRequest, ImageLoader};
|
2025-11-03 20:47:21 -05:00
|
|
|
use eficore::media_loader::MediaLoaderHandle;
|
|
|
|
|
use eficore::media_loader::constants::linux::LINUX_EFI_INITRD_MEDIA_GUID;
|
2026-09-26 20:14:57 -07:00
|
|
|
use log::warn;
|
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
|
|
|
|
2026-09-26 20:19:52 -07:00
|
|
|
/// Read the initrd at the stamped `path` relative to the sprout image.
|
2026-09-26 19:45:47 -07:00
|
|
|
/// Provides [None] if the path refers to the root of a filesystem rather than a file.
|
2026-09-26 20:19:52 -07:00
|
|
|
pub fn read_initrd(context: &Rc<SproutContext>, path: &str) -> Result<Option<Vec<u8>>> {
|
2026-09-26 19:45:47 -07:00
|
|
|
let resolved = eficore::path::resolve_path(Some(context.root().loaded_image_path()?), path)
|
2026-09-26 20:19:52 -07:00
|
|
|
.context("unable to resolve initrd path")?;
|
2026-09-26 19:45:47 -07:00
|
|
|
|
|
|
|
|
// A path without a file component refers to the root of the filesystem, not an initrd.
|
|
|
|
|
// This happens when a path template like "$root\\$initrd-0" is stamped with an empty
|
|
|
|
|
// initrd value, such as a BLS entry without an initrd or an unused BLS initrd slot.
|
|
|
|
|
let subpath = eficore::path::device_path_subpath(&resolved.full_path)
|
2026-09-26 20:19:52 -07:00
|
|
|
.context("unable to get initrd subpath")?;
|
2026-09-26 19:45:47 -07:00
|
|
|
if subpath.trim_matches('\\').is_empty() {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-26 20:19:52 -07:00
|
|
|
let content = resolved.read_file().context("unable to read initrd")?;
|
2026-09-26 19:45:47 -07:00
|
|
|
Ok(Some(content))
|
|
|
|
|
}
|
|
|
|
|
|
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<()> {
|
2026-09-26 19:45:47 -07:00
|
|
|
// The initrd can be provided as either a single initrd or a chain, but not both.
|
|
|
|
|
if configuration.linux_initrd.is_some() && !configuration.linux_initrd_chain.is_empty() {
|
|
|
|
|
bail!("linux-initrd and linux-initrd-chain cannot be used together");
|
|
|
|
|
}
|
|
|
|
|
|
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-11-03 20:47:21 -05:00
|
|
|
let resolved = eficore::path::resolve_path(
|
2025-10-30 22:56:01 -04:00
|
|
|
Some(context.root().loaded_image_path()?),
|
2026-01-26 20:13:52 -08:00
|
|
|
context.stamp(&configuration.path),
|
2025-10-13 00:55:11 -07:00
|
|
|
)
|
2025-10-14 12:47:33 -07:00
|
|
|
.context("unable to resolve chainload path")?;
|
2025-10-01 16:45:04 -07:00
|
|
|
|
2025-11-06 11:52:00 -05: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.
|
2025-11-06 11:52:00 -05:00
|
|
|
let image = ImageLoader::load(request)?;
|
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.
|
2026-03-25 15:56:02 -04:00
|
|
|
let options = combine_options(context.stamp_iter(configuration.options.iter()));
|
2025-10-12 18:06:57 -07:00
|
|
|
|
2025-11-02 17:47:36 -05: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
|
|
|
|
2026-01-26 20:13:52 -08:00
|
|
|
// Ensure the chainloader options limit is not exceeded.
|
2025-11-02 17:47:36 -05:00
|
|
|
if options.num_bytes() > u32::MAX as usize {
|
|
|
|
|
bail!("chainloader options too large");
|
|
|
|
|
}
|
2025-10-02 00:24:19 -07:00
|
|
|
|
2026-09-26 18:13:20 -07:00
|
|
|
// 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);
|
|
|
|
|
}
|
2025-10-02 00:24:19 -07:00
|
|
|
}
|
|
|
|
|
|
2026-09-26 19:45:47 -07:00
|
|
|
// Stamp the initrd paths. A single linux-initrd is read the same way as a chain of one.
|
|
|
|
|
let initrd_paths = configuration
|
2025-10-27 19:47:21 -04:00
|
|
|
.linux_initrd
|
2026-09-26 19:45:47 -07:00
|
|
|
.iter()
|
|
|
|
|
.chain(configuration.linux_initrd_chain.iter())
|
2025-10-27 19:47:21 -04:00
|
|
|
.map(|item| context.stamp(item));
|
2025-10-27 16:27:39 -04:00
|
|
|
|
2026-09-26 19:45:47 -07:00
|
|
|
// Read each initrd and concatenate the contents in order.
|
|
|
|
|
// Paths that are empty after stamping are skipped.
|
|
|
|
|
let mut initrd: Option<Vec<u8>> = None;
|
|
|
|
|
for path in initrd_paths {
|
|
|
|
|
let Some(path) = empty_is_none(Some(path)) else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
2026-09-26 20:19:52 -07:00
|
|
|
let Some(content) = read_initrd(&context, &path)? else {
|
2026-09-26 19:45:47 -07:00
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
match initrd.as_mut() {
|
2026-09-26 20:19:25 -07:00
|
|
|
Some(initrd) => append_initrd(initrd, &content),
|
2026-09-26 19:45:47 -07:00
|
|
|
None => initrd = Some(content),
|
2026-09-26 18:13:27 -07:00
|
|
|
}
|
2025-10-12 20:22:24 -07:00
|
|
|
}
|
|
|
|
|
|
2026-09-26 19:45:47 -07:00
|
|
|
// If an initrd was read, register it with the EFI stack.
|
2026-09-26 20:20:00 -07:00
|
|
|
// An empty initrd is not registered, as Linux fails to boot if the initrd it is
|
|
|
|
|
// given can't be loaded, instead of booting without one.
|
2026-09-26 19:45:47 -07:00
|
|
|
let mut initrd_handle = None;
|
2026-09-26 20:20:00 -07:00
|
|
|
if let Some(content) = initrd.filter(|content| !content.is_empty()) {
|
2026-09-26 19:45:47 -07:00
|
|
|
let handle =
|
|
|
|
|
MediaLoaderHandle::register(LINUX_EFI_INITRD_MEDIA_GUID, content.into_boxed_slice())
|
|
|
|
|
.context("unable to register linux initrd")?;
|
|
|
|
|
initrd_handle = Some(handle);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-28 21:05:22 -04:00
|
|
|
// Mark execution of an entry in the bootloader interface.
|
2026-09-26 20:14:57 -07:00
|
|
|
// This is only informational, so it should not prevent booting.
|
|
|
|
|
if let Err(error) = BootloaderInterface::mark_exec(context.root().timer()) {
|
|
|
|
|
warn!(
|
|
|
|
|
"unable to mark execution of boot entry in bootloader interface: {:#}",
|
|
|
|
|
error
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-10-28 21:05:22 -04:00
|
|
|
|
2025-12-20 21:45:35 -08:00
|
|
|
// 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")?;
|
|
|
|
|
|
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-11-06 11:52:00 -05:00
|
|
|
let result = uefi::boot::start_image(*image.handle());
|
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-11-02 17:47:36 -05:00
|
|
|
|
|
|
|
|
// Explicitly drop the options to clarify the lifetime.
|
|
|
|
|
drop(options);
|
2025-10-19 21:44:05 -07:00
|
|
|
|
2025-12-20 22:01:04 -08:00
|
|
|
// Explicitly drop the initrd handle to clarify when it should be unregistered.
|
|
|
|
|
drop(initrd_handle);
|
|
|
|
|
|
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
|
|
|
}
|