bootmgr_rs_core/boot/
loader.rs

1//! Boot loading re-exports
2//!
3//! This mainly provides the function [`load_boot_option`], which will redirect [`Config`]s to the respective boot loaders
4//! depending on the action set. It is essentially a wrapper around running the `run()` method on the [`Config`]'s action field.
5
6use alloc::string::String;
7
8use thiserror::Error;
9use uefi::Handle;
10
11use crate::{BootResult, config::Config};
12
13pub mod efi;
14pub mod tftp;
15
16/// An `Error` that may result from loading an image.
17#[derive(Error, Debug)]
18pub enum LoadError {
19    /// A [`Config`] did not have a [`Handle`] when required.
20    #[error("Config \"{0}\" attempted to boot without a handle")]
21    ConfigMissingHandle(String),
22
23    /// A [`Config`] did not have an EFI defined when required.
24    #[error("Config \"{0}\" attempted to boot without an EFI executable")]
25    ConfigMissingEfi(String),
26
27    /// Failed to parse a string as an IP address.
28    #[error("Failed to parse as IP address")]
29    IpParse(#[from] core::net::AddrParseError),
30
31    /// The HTTP response did not have a valid content-length header.
32    #[error("Nonexistent or invalid content length header found in address \"{0}\"")]
33    InvalidContentLen(String),
34}
35
36/// Loads a boot option given a [`Config`].
37///
38/// It simply delegates to [`super::action::BootAction::run`].
39///
40/// # Errors
41///
42/// May return an `Error` if any of the actions fail.
43///
44/// # Example
45///
46/// ```no_run
47/// // this example starts the fallback boot loader on the same partition as the image handle.
48///
49/// use bootmgr_rs_core::{boot::loader::load_boot_option, config::builder::ConfigBuilder};
50/// use uefi::{
51///     boot,
52///     proto::{
53///         device_path::DevicePath,
54///         loaded_image::LoadedImage,
55///         media::fs::SimpleFileSystem
56///     }
57/// };
58///
59/// let handle = {
60///     let loaded_image =
61///         boot::open_protocol_exclusive::<LoadedImage>(boot::image_handle()).expect("Failed to open LoadedImage protocol on image");
62///     let device_handle = loaded_image.device().expect("Image was not loaded from a filesystem");
63///     let device_path = boot::open_protocol_exclusive::<DevicePath>(device_handle).expect("Failed to get device path from image filesystem");
64///     boot::locate_device_path::<SimpleFileSystem>(&mut &*device_path).expect("Failed to get SimpleFileSystem protocol from image filesystem")
65/// }; // so that the handle will be able to be opened for loading the boot option
66///
67/// let config = ConfigBuilder::new("foo.bar", ".bar").efi_path("/efi/boot/bootx64.efi").fs_handle(handle).build();
68///
69/// let image = load_boot_option(&config).expect("Failed to load boot option");
70///
71/// boot::start_image(image).expect("Failed to start image");
72/// ```
73pub fn load_boot_option(config: &Config) -> BootResult<Handle> {
74    config.action.run(config)
75}
76
77/// Get an EFI path from a [`Config`].
78///
79/// # Errors
80///
81/// May return an `Error` if the [`Config`] does not contain an EFI path.
82fn get_efi(config: &Config) -> Result<&String, LoadError> {
83    config
84        .efi_path
85        .as_deref()
86        .ok_or_else(|| LoadError::ConfigMissingEfi(config.filename.clone()))
87}
88
89#[cfg(test)]
90mod tests {
91    use crate::{boot::action::BootAction, error::BootError};
92
93    use super::*;
94
95    #[test]
96    fn test_missing_handle() {
97        let config = Config {
98            fs_handle: None,
99            action: BootAction::BootEfi,
100            ..Default::default()
101        };
102        assert!(matches!(
103            load_boot_option(&config),
104            Err(BootError::LoadError(LoadError::ConfigMissingHandle(_)))
105        ));
106    }
107}