Skip to main content

plex_boot/core/
bootables.rs

1//! Abstractions for different types of boot targets.
2//!
3//! Provides the generic definitions for boot targets (e.g., standard EFI executables
4//! and ISO images) and how they are displayed and executed within the application.
5
6use crate::core::app::AppResult;
7use crate::core::app::{App, AppCtx, DisplayEntry};
8use crate::error::AppError;
9use crate::path::{DiskManager, PathReference};
10use alloc::borrow::ToOwned as _;
11use alloc::string::{String, ToString};
12use uefi::CString16;
13use uefi::boot::LoadImageSource;
14use uefi::cstr16;
15use uefi::proto::device_path::PoolDevicePath;
16use uefi::proto::loaded_image::LoadedImage;
17
18#[derive(Debug)]
19/// Represents any bootable target that can be executed by the bootloader.
20pub enum BootTarget {
21    /// A generic EFI executable boot target.
22    Generic(GenericBootTarget),
23    /// A boot target representing a bootable ISO file.
24    #[cfg(feature = "iso")]
25    Iso(crate::iso::IsoBootTarget),
26}
27
28/// note: this is a two-way implementation, to allow decisions in the
29/// future whether we want to model all targets as enum or use dyn dispatch.
30impl BootTarget {
31    fn boot(&self, handle: uefi::Handle, dm: &DiskManager) -> Result<(), AppError> {
32        match self {
33            BootTarget::Generic(target) => target.boot(handle, dm),
34            #[cfg(feature = "iso")]
35            BootTarget::Iso(target) => target.boot(handle, dm),
36        }
37    }
38}
39
40impl App for BootTarget {
41    fn run(&mut self, ctx: &mut AppCtx) -> AppResult {
42        match self.boot(ctx.handle, ctx.disk_manager) {
43            Ok(_) => AppResult::Booted,
44            Err(e) => AppResult::Error(e),
45        }
46    }
47}
48
49impl DisplayEntry for BootTarget {
50    fn display_options(&self) -> DisplayOptions {
51        match self {
52            BootTarget::Generic(target) => target.display_options(),
53            #[cfg(feature = "iso")]
54            BootTarget::Iso(target) => target.display_options(),
55        }
56    }
57}
58
59/// Options for rendering a boot entry in the user interface.
60pub struct DisplayOptions {
61    /// The text label to display for this entry.
62    pub label: String,
63}
64
65/// A generic EFI executable + cmd chain-loadable target.
66///
67/// For example, the Linux kernel can be booted into directly via EFI stub, if compiled with `CONFIG_EFI_STUB=y`, which
68/// is a very common default. Windows is also easily chain-loaded.
69#[derive(Debug)]
70pub struct GenericBootTarget {
71    /// Display label for the boot menu
72    label: String,
73    /// Path to executable. current limitation is that this path is relative
74    /// to the root the bootloader is loaded from.
75    executable: CString16,
76    /// Command options to be passed to LoadedImage::SetLoadOptions
77    options: CString16,
78}
79
80impl GenericBootTarget {
81    /// Creates a new `GenericBootTarget` from the provided label, executable path, and options.
82    pub fn new(
83        label: impl AsRef<str>,
84        executable: impl AsRef<str>,
85        options: impl AsRef<str>,
86    ) -> Self {
87        Self {
88            label: label.as_ref().to_string(),
89            executable: CString16::try_from(executable.as_ref())
90                .unwrap_or(cstr16!("failed to parse").to_owned()),
91            options: CString16::try_from(options.as_ref())
92                .unwrap_or(cstr16!("failed to parse").to_owned()),
93        }
94    }
95
96    fn boot(&self, handle: uefi::Handle, dm: &DiskManager) -> Result<(), AppError> {
97        let pathref = PathReference::parse(self.executable.to_string().as_str())?;
98        let img_path = dm.resolve_path(&pathref)?;
99
100        log::debug!(
101            "Loading image from resolved path: {}",
102            path_to_string(&img_path)
103        );
104
105        let src = LoadImageSource::FromDevicePath {
106            device_path: &img_path,
107            boot_policy: Default::default(),
108        };
109
110        let loaded_image_handle = uefi::boot::load_image(handle, src)?;
111        let mut loaded_img =
112            uefi::boot::open_protocol_exclusive::<LoadedImage>(loaded_image_handle)?;
113
114        unsafe {
115            loaded_img.set_load_options(
116                self.options.as_ptr() as *const u8,
117                self.options.num_bytes() as u32,
118            );
119        }
120
121        Ok(uefi::boot::start_image(loaded_image_handle)?)
122    }
123
124    fn display_options(&self) -> DisplayOptions {
125        DisplayOptions {
126            label: self.label.clone(),
127        }
128    }
129}
130
131fn path_to_string(path: &PoolDevicePath) -> CString16 {
132    path.to_string(
133        uefi::proto::device_path::text::DisplayOnly(true),
134        uefi::proto::device_path::text::AllowShortcuts(true),
135    )
136    .unwrap_or(cstr16!("failed to parse.").into())
137}