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-12 20:22:24 -07:00
|
|
|
use crate::utils::linux_media_initrd::{register_linux_initrd, unregister_linux_initrd};
|
2025-10-11 14:35:29 -07:00
|
|
|
use anyhow::{Context, Result, bail};
|
2025-10-01 19:15:42 -07:00
|
|
|
use log::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;
|
|
|
|
|
use uefi::proto::device_path::LoadedImageDevicePath;
|
2025-10-01 21:30:43 -07:00
|
|
|
use uefi::proto::loaded_image::LoadedImage;
|
2025-10-01 16:45:04 -07:00
|
|
|
|
2025-10-11 14:11:31 -07:00
|
|
|
#[derive(Serialize, Deserialize, Default, Clone)]
|
|
|
|
|
pub struct ChainloadConfiguration {
|
|
|
|
|
pub path: String,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub options: Vec<String>,
|
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-11 14:35:29 -07:00
|
|
|
pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfiguration) -> Result<()> {
|
2025-10-01 16:45:04 -07:00
|
|
|
let sprout_image = uefi::boot::image_handle();
|
|
|
|
|
let image_device_path_protocol =
|
|
|
|
|
uefi::boot::open_protocol_exclusive::<LoadedImageDevicePath>(sprout_image)
|
2025-10-11 14:35:29 -07:00
|
|
|
.context("unable to open loaded image device path protocol")?;
|
2025-10-01 16:45:04 -07:00
|
|
|
|
2025-10-11 14:35:29 -07:00
|
|
|
let mut full_path = utils::device_path_root(&image_device_path_protocol)?;
|
2025-10-04 23:12:01 -07:00
|
|
|
|
|
|
|
|
full_path.push_str(&context.stamp(&configuration.path));
|
2025-10-01 16:45:04 -07:00
|
|
|
|
2025-10-12 17:45:16 -07:00
|
|
|
info!("path: {}", full_path);
|
2025-10-01 16:45:04 -07:00
|
|
|
|
2025-10-11 14:35:29 -07:00
|
|
|
let device_path = utils::text_to_device_path(&full_path)?;
|
2025-10-01 16:45:04 -07:00
|
|
|
|
|
|
|
|
let image = uefi::boot::load_image(
|
|
|
|
|
sprout_image,
|
|
|
|
|
uefi::boot::LoadImageSource::FromDevicePath {
|
|
|
|
|
device_path: &device_path,
|
|
|
|
|
boot_policy: uefi::proto::BootPolicy::ExactMatch,
|
|
|
|
|
},
|
|
|
|
|
)
|
2025-10-11 14:35:29 -07:00
|
|
|
.context("failed to load image")?;
|
2025-10-01 19:15:42 -07:00
|
|
|
|
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-04 23:12:01 -07:00
|
|
|
let options = configuration
|
|
|
|
|
.options
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|item| context.stamp(item))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(" ");
|
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-12 18:06:57 -07: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-12 20:22:24 -07:00
|
|
|
let mut initrd_handle = None;
|
|
|
|
|
if let Some(ref linux_initrd) = configuration.linux_initrd {
|
|
|
|
|
let initrd_path = context.stamp(linux_initrd);
|
|
|
|
|
let content =
|
|
|
|
|
utils::read_file_contents(&initrd_path).context("failed to read linux initrd")?;
|
|
|
|
|
initrd_handle = Some(
|
|
|
|
|
register_linux_initrd(content.into_boxed_slice())
|
|
|
|
|
.context("failed to register linux initrd")?,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-02 00:24:19 -07:00
|
|
|
let (base, size) = loaded_image_protocol.info();
|
2025-10-12 17:45:16 -07:00
|
|
|
info!("loaded image: base={:#x} size={:#x}", base.addr(), size);
|
2025-10-12 20:22:24 -07:00
|
|
|
let result = uefi::boot::start_image(image).context("failed to start image");
|
|
|
|
|
if let Some(initrd_handle) = initrd_handle {
|
|
|
|
|
unregister_linux_initrd(initrd_handle).context("failed to unregister linux initrd")?;
|
|
|
|
|
}
|
|
|
|
|
result.context("failed to start image")?;
|
2025-10-12 18:06:57 -07:00
|
|
|
drop(options_holder);
|
2025-10-11 14:35:29 -07:00
|
|
|
Ok(())
|
2025-10-01 16:45:04 -07:00
|
|
|
}
|