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}