diff --git a/src/actions/chainload.rs b/src/actions/chainload.rs index 7b1c62b..2a23f2d 100644 --- a/src/actions/chainload.rs +++ b/src/actions/chainload.rs @@ -28,7 +28,7 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfigurat context.root().loaded_image_path()?, &context.stamp(&configuration.path), ) - .context("failed to resolve chainload path")?; + .context("unable to resolve chainload path")?; let image = uefi::boot::load_image( sprout_image, @@ -37,7 +37,7 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfigurat boot_policy: uefi::proto::BootPolicy::ExactMatch, }, ) - .context("failed to load image")?; + .context("unable to load image")?; let mut loaded_image_protocol = uefi::boot::open_protocol_exclusive::(image) .context("unable to open loaded image protocol")?; @@ -76,20 +76,20 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfigurat if let Some(ref linux_initrd) = configuration.linux_initrd { let initrd_path = context.stamp(linux_initrd); let content = utils::read_file_contents(context.root().loaded_image_path()?, &initrd_path) - .context("failed to read linux initrd")?; + .context("unable to read linux initrd")?; initrd_handle = Some( register_linux_initrd(content.into_boxed_slice()) - .context("failed to register linux initrd")?, + .context("unable to register linux initrd")?, ); } let (base, size) = loaded_image_protocol.info(); info!("loaded image: base={:#x} size={:#x}", base.addr(), size); - let result = uefi::boot::start_image(image).context("failed to start image"); + let result = uefi::boot::start_image(image).context("unable to start image"); if let Some(initrd_handle) = initrd_handle { - unregister_linux_initrd(initrd_handle).context("failed to unregister linux initrd")?; + unregister_linux_initrd(initrd_handle).context("unable to unregister linux initrd")?; } - result.context("failed to start image")?; + result.context("unable to start image")?; drop(options_holder); Ok(()) } diff --git a/src/actions/splash.rs b/src/actions/splash.rs index 7c0426c..d02d0c8 100644 --- a/src/actions/splash.rs +++ b/src/actions/splash.rs @@ -25,9 +25,9 @@ pub fn default_splash_time() -> u32 { fn setup_graphics() -> Result> { let gop_handle = uefi::boot::get_handle_for_protocol::() - .context("failed to get graphics output")?; + .context("unable to get graphics output")?; uefi::boot::open_protocol_exclusive::(gop_handle) - .context("failed to open graphics output") + .context("unable to open graphics output") } fn fit_to_frame(image: &DynamicImage, frame: Rect) -> Rect { @@ -99,7 +99,7 @@ pub fn splash(context: Rc, configuration: &SplashConfiguration) - let image = read_file_contents(context.root().loaded_image_path()?, &image)?; let image = ImageReader::with_format(Cursor::new(image), ImageFormat::Png) .decode() - .context("failed to decode splash image")?; + .context("unable to decode splash image")?; draw(image)?; std::thread::sleep(Duration::from_secs(configuration.time as u64)); Ok(()) diff --git a/src/config.rs b/src/config.rs index a786a6d..923c570 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,9 @@ use crate::actions::ActionDeclaration; use crate::drivers::DriverDeclaration; +use crate::entries::EntryDeclaration; use crate::extractors::ExtractorDeclaration; use crate::generators::GeneratorDeclaration; +use crate::phases::PhasesConfiguration; use crate::utils; use anyhow::Context; use anyhow::Result; @@ -30,37 +32,14 @@ pub struct RootConfiguration { pub phases: PhasesConfiguration, } -#[derive(Serialize, Deserialize, Default, Clone)] -pub struct EntryDeclaration { - pub title: String, - #[serde(default)] - pub actions: Vec, - #[serde(default)] - pub values: BTreeMap, -} - -#[derive(Serialize, Deserialize, Default, Clone)] -pub struct PhasesConfiguration { - #[serde(default)] - pub early: Vec, - #[serde(default)] - pub startup: Vec, - #[serde(default)] - pub late: Vec, -} - -#[derive(Serialize, Deserialize, Default, Clone)] -pub struct PhaseConfiguration { - #[serde(default)] - pub actions: Vec, - #[serde(default)] - pub values: BTreeMap, +pub fn latest_version() -> u32 { + 1 } pub fn load() -> Result { let current_image_device_path_protocol = uefi::boot::open_protocol_exclusive::(uefi::boot::image_handle()) - .context("failed to get loaded image device path")?; + .context("unable to get loaded image device path")?; let path = current_image_device_path_protocol.deref().to_boxed(); let content = utils::read_file_contents(&path, "sprout.toml") @@ -69,7 +48,3 @@ pub fn load() -> Result { toml::from_slice(&content).context("unable to parse sprout.toml file")?; Ok(config) } - -pub fn latest_version() -> u32 { - 1 -} diff --git a/src/drivers.rs b/src/drivers.rs index a9665d2..6358807 100644 --- a/src/drivers.rs +++ b/src/drivers.rs @@ -34,16 +34,16 @@ fn load_driver(context: Rc, driver: &DriverDeclaration) -> Result boot_policy: uefi::proto::BootPolicy::ExactMatch, }, ) - .context("failed to load image")?; + .context("unable to load image")?; - uefi::boot::start_image(image).context("failed to start driver image")?; + uefi::boot::start_image(image).context("unable to start driver image")?; Ok(()) } fn reconnect() -> Result<()> { let handles = uefi::boot::locate_handle_buffer(SearchType::AllHandles) - .context("failed to locate handles buffer")?; + .context("unable to locate handles buffer")?; for handle in handles.iter() { // ignore result as there is nothing we can do if it doesn't work. @@ -63,10 +63,10 @@ pub fn load( info!("loading drivers"); for (name, driver) in drivers { - load_driver(context.clone(), driver).context(format!("failed to load driver: {}", name))?; + load_driver(context.clone(), driver).context(format!("unable to load driver: {}", name))?; } - reconnect().context("failed to reconnect drivers")?; + reconnect().context("unable to reconnect drivers")?; info!("loaded drivers"); Ok(()) } diff --git a/src/entries.rs b/src/entries.rs new file mode 100644 index 0000000..4353c12 --- /dev/null +++ b/src/entries.rs @@ -0,0 +1,11 @@ +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +#[derive(Serialize, Deserialize, Default, Clone)] +pub struct EntryDeclaration { + pub title: String, + #[serde(default)] + pub actions: Vec, + #[serde(default)] + pub values: BTreeMap, +} diff --git a/src/extractors/filesystem_device_match.rs b/src/extractors/filesystem_device_match.rs index 4c34546..f44de50 100644 --- a/src/extractors/filesystem_device_match.rs +++ b/src/extractors/filesystem_device_match.rs @@ -32,7 +32,7 @@ pub fn extract( extractor: &FilesystemDeviceMatchExtractor, ) -> Result { let handles = uefi::boot::find_handles::() - .context("failed to find filesystem handles")?; + .context("unable to find filesystem handles")?; for handle in handles { let mut has_match = false; @@ -55,7 +55,7 @@ pub fn extract( { None } else { - Err(error).context("failed to open filesystem partition info")?; + Err(error).context("unable to open filesystem partition info")?; None } } @@ -66,7 +66,7 @@ pub fn extract( && let Some(ref has_partition_uuid) = extractor.has_partition_uuid { let parsed_uuid = Guid::from_str(has_partition_uuid) - .map_err(|e| anyhow!("failed to parse has-partition-uuid: {}", e))?; + .map_err(|e| anyhow!("unable to parse has-partition-uuid: {}", e))?; if partition_uuid != parsed_uuid { continue; } @@ -77,7 +77,7 @@ pub fn extract( && let Some(ref has_partition_type_uuid) = extractor.has_partition_type_uuid { let parsed_uuid = Guid::from_str(has_partition_type_uuid) - .map_err(|e| anyhow!("failed to parse has-partition-type-uuid: {}", e))?; + .map_err(|e| anyhow!("unable to parse has-partition-type-uuid: {}", e))?; if partition_type_guid != parsed_uuid { continue; } @@ -85,17 +85,17 @@ pub fn extract( } let mut filesystem = uefi::boot::open_protocol_exclusive::(handle) - .context("failed to open filesystem protocol")?; + .context("unable to open filesystem protocol")?; if let Some(ref label) = extractor.has_label { let want_label = CString16::try_from(context.stamp(label).as_str()) - .context("failed to convert label to CString16")?; + .context("unable to convert label to CString16")?; let mut root = filesystem .open_volume() - .context("failed to open filesystem volume")?; + .context("unable to open filesystem volume")?; let label = root .get_boxed_info::() - .context("failed to get filesystem volume label")?; + .context("unable to get filesystem volume label")?; if label.volume_label() != want_label { continue; @@ -105,7 +105,7 @@ pub fn extract( if let Some(ref item) = extractor.has_item { let want_item = CString16::try_from(context.stamp(item).as_str()) - .context("failed to convert item to CString16")?; + .context("unable to convert item to CString16")?; let mut filesystem = FileSystem::new(filesystem); let metadata = filesystem.metadata(Path::new(&want_item)); @@ -125,9 +125,9 @@ pub fn extract( } let path = uefi::boot::open_protocol_exclusive::(handle) - .context("failed to open filesystem device path")?; + .context("unable to open filesystem device path")?; let path = path.deref(); - return utils::device_path_root(path).context("failed to get device path root"); + return utils::device_path_root(path).context("unable to get device path root"); } if let Some(fallback) = &extractor.fallback { diff --git a/src/generators.rs b/src/generators.rs index 4770468..8e293d2 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -1,5 +1,5 @@ -use crate::config::EntryDeclaration; use crate::context::SproutContext; +use crate::entries::EntryDeclaration; use crate::generators::bls::BlsConfiguration; use crate::generators::matrix::MatrixConfiguration; use anyhow::Result; diff --git a/src/generators/bls.rs b/src/generators/bls.rs index 03392e2..9ea5cbc 100644 --- a/src/generators/bls.rs +++ b/src/generators/bls.rs @@ -1,7 +1,5 @@ -mod entry; - -use crate::config::EntryDeclaration; use crate::context::SproutContext; +use crate::entries::EntryDeclaration; use crate::generators::bls::entry::BlsEntry; use crate::utils; use anyhow::{Context, Result}; @@ -13,6 +11,8 @@ use uefi::fs::{FileSystem, Path}; use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly}; use uefi::proto::media::fs::SimpleFileSystem; +mod entry; + #[derive(Serialize, Deserialize, Default, Clone)] pub struct BlsConfiguration { pub entry: EntryDeclaration, @@ -32,19 +32,19 @@ pub fn generate( let path = context.stamp(&bls.path); let resolved = utils::resolve_path(context.root().loaded_image_path()?, &path) - .context("failed to resolve bls path")?; + .context("unable to resolve bls path")?; let fs = uefi::boot::open_protocol_exclusive::(resolved.filesystem_handle) - .context("failed to open bls filesystem")?; + .context("unable to open bls filesystem")?; let mut fs = FileSystem::new(fs); let sub_text_path = resolved .sub_path .to_string(DisplayOnly(false), AllowShortcuts(false)) - .context("failed to convert subpath to string")?; + .context("unable to convert subpath to string")?; let entries_path = Path::new(&sub_text_path); let entries_iter = fs .read_dir(entries_path) - .context("failed to read bls entries")?; + .context("unable to read bls entries")?; for entry in entries_iter { let entry = entry?; @@ -57,13 +57,13 @@ pub fn generate( } let full_entry_path = CString16::try_from(format!("{}\\{}", sub_text_path, name).as_str()) - .context("failed to construct full entry path")?; + .context("unable to construct full entry path")?; let full_entry_path = Path::new(&full_entry_path); let content = fs .read(full_entry_path) - .context("failed to read bls file")?; - let content = String::from_utf8(content).context("failed to read bls entry as utf8")?; - let entry = BlsEntry::from_str(&content).context("failed to parse bls entry")?; + .context("unable to read bls file")?; + let content = String::from_utf8(content).context("unable to read bls entry as utf8")?; + let entry = BlsEntry::from_str(&content).context("unable to parse bls entry")?; if !entry.is_valid() { continue; diff --git a/src/generators/matrix.rs b/src/generators/matrix.rs index 9499b77..e243d62 100644 --- a/src/generators/matrix.rs +++ b/src/generators/matrix.rs @@ -1,5 +1,5 @@ -use crate::config::EntryDeclaration; use crate::context::SproutContext; +use crate::entries::EntryDeclaration; use anyhow::Result; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; diff --git a/src/main.rs b/src/main.rs index 1fa553a..599b2d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,11 @@ #![feature(uefi_std)] -use crate::config::PhaseConfiguration; use crate::context::{RootContext, SproutContext}; +use crate::phases::phase; use anyhow::{Context, Result, bail}; use log::info; use std::collections::BTreeMap; use std::ops::Deref; -use std::rc::Rc; use uefi::proto::device_path::LoadedImageDevicePath; use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly}; @@ -14,25 +13,13 @@ pub mod actions; pub mod config; pub mod context; pub mod drivers; +pub mod entries; pub mod extractors; pub mod generators; +pub mod phases; pub mod setup; pub mod utils; -fn phase(context: Rc, phase: &[PhaseConfiguration]) -> Result<()> { - for item in phase { - let mut context = context.fork(); - context.insert(&item.values); - let context = context.freeze(); - - for action in item.actions.iter() { - actions::execute(context.clone(), action) - .context(format!("failed to execute action '{}'", action))?; - } - } - Ok(()) -} - fn main() -> Result<()> { setup::init()?; @@ -46,7 +33,7 @@ fn main() -> Result<()> { let current_image_device_path_protocol = uefi::boot::open_protocol_exclusive::< LoadedImageDevicePath, >(uefi::boot::image_handle()) - .context("failed to get loaded image device path")?; + .context("unable to get loaded image device path")?; let loaded_image_path = current_image_device_path_protocol.deref().to_boxed(); info!( "loaded image path: {}", @@ -61,14 +48,14 @@ fn main() -> Result<()> { context.insert(&config.values); let context = context.freeze(); - phase(context.clone(), &config.phases.early).context("failed to execute early phase")?; + phase(context.clone(), &config.phases.early).context("unable to execute early phase")?; - drivers::load(context.clone(), &config.drivers).context("failed to load drivers")?; + drivers::load(context.clone(), &config.drivers).context("unable to load drivers")?; let mut extracted = BTreeMap::new(); for (name, extractor) in &config.extractors { let value = extractors::extract(context.clone(), extractor) - .context(format!("failed to extract value {}", name))?; + .context(format!("unable to extract value {}", name))?; info!("extracted value {}: {}", name, value); extracted.insert(name.clone(), value); } @@ -76,7 +63,7 @@ fn main() -> Result<()> { context.insert(&extracted); let context = context.freeze(); - phase(context.clone(), &config.phases.startup).context("failed to execute startup phase")?; + phase(context.clone(), &config.phases.startup).context("unable to execute startup phase")?; let mut all_entries = Vec::new(); @@ -107,7 +94,7 @@ fn main() -> Result<()> { info!(" entry {}: {}", index + 1, title); } - phase(context.clone(), &config.phases.late).context("failed to execute late phase")?; + phase(context.clone(), &config.phases.late).context("unable to execute late phase")?; let index = 1; @@ -116,7 +103,7 @@ fn main() -> Result<()> { for action in &entry.actions { let action = context.stamp(action); actions::execute(context.clone(), &action) - .context(format!("failed to execute action '{}'", action))?; + .context(format!("unable to execute action '{}'", action))?; } Ok(()) } diff --git a/src/phases.rs b/src/phases.rs new file mode 100644 index 0000000..4381096 --- /dev/null +++ b/src/phases.rs @@ -0,0 +1,38 @@ +use crate::actions; +use crate::context::SproutContext; +use anyhow::Context; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; +use std::rc::Rc; + +#[derive(Serialize, Deserialize, Default, Clone)] +pub struct PhasesConfiguration { + #[serde(default)] + pub early: Vec, + #[serde(default)] + pub startup: Vec, + #[serde(default)] + pub late: Vec, +} + +#[derive(Serialize, Deserialize, Default, Clone)] +pub struct PhaseConfiguration { + #[serde(default)] + pub actions: Vec, + #[serde(default)] + pub values: BTreeMap, +} + +pub fn phase(context: Rc, phase: &[PhaseConfiguration]) -> anyhow::Result<()> { + for item in phase { + let mut context = context.fork(); + context.insert(&item.values); + let context = context.freeze(); + + for action in item.actions.iter() { + actions::execute(context.clone(), action) + .context(format!("unable to execute action '{}'", action))?; + } + } + Ok(()) +} diff --git a/src/setup.rs b/src/setup.rs index 6059b7a..f40282e 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,6 +1,10 @@ use anyhow::{Context, Result}; use std::os::uefi as uefi_std; +/// Initializes the UEFI environment. +/// +/// This fetches the system table and current image handle from uefi_std and injects +/// them into the uefi crate. pub fn init() -> Result<()> { let system_table = uefi_std::env::system_table(); let image_handle = uefi_std::env::image_handle(); @@ -15,6 +19,6 @@ pub fn init() -> Result<()> { uefi::boot::set_image_handle(handle); } - uefi::helpers::init().context("failed to initialize uefi")?; + uefi::helpers::init().context("unable to initialize uefi")?; Ok(()) } diff --git a/src/utils.rs b/src/utils.rs index 4be87a4..b9ebeb3 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -9,6 +9,8 @@ use uefi::{CString16, Handle}; pub mod framebuffer; pub mod linux_media_initrd; +/// Parses the input [path] as a [DevicePath]. +/// Uses the [DevicePathFromText] protocol exclusively, and will fail if it cannot acquire the protocol. pub fn text_to_device_path(path: &str) -> Result { let path = CString16::try_from(path).context("unable to convert path to CString16")?; let device_path_from_text = uefi::boot::open_protocol_exclusive::( @@ -22,6 +24,9 @@ pub fn text_to_device_path(path: &str) -> Result { .context("unable to convert text to device path") } +/// Grabs the root part of the [path]. +/// For example, given "PciRoot(0x0)/Pci(0x4,0x0)/NVMe(0x1,00-00-00-00-00-00-00-00)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)/\EFI\BOOT\BOOTX64.efi" +/// it will give "PciRoot(0x0)/Pci(0x4,0x0)/NVMe(0x1,00-00-00-00-00-00-00-00)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)" pub fn device_path_root(path: &DevicePath) -> Result { let mut path = path .node_iter() @@ -44,6 +49,9 @@ pub fn device_path_root(path: &DevicePath) -> Result { Ok(path) } +/// Grabs the part of the [path] after the root. +/// For example, given "PciRoot(0x0)/Pci(0x4,0x0)/NVMe(0x1,00-00-00-00-00-00-00-00)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)/\EFI\BOOT\BOOTX64.efi" +/// it will give "\EFI\BOOT\BOOTX64.efi" pub fn device_path_subpath(path: &DevicePath) -> Result { let path = path .node_iter() @@ -65,15 +73,27 @@ pub fn device_path_subpath(path: &DevicePath) -> Result { Ok(path) } +/// Represents the components of a resolved path. pub struct ResolvedPath { + /// The root path of the resolved path. This is the device itself. + /// For example, "PciRoot(0x0)/Pci(0x4,0x0)/NVMe(0x1,00-00-00-00-00-00-00-00)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)/" pub root_path: Box, + /// The subpath of the resolved path. This is the path to the file. + /// For example, "\EFI\BOOT\BOOTX64.efi" pub sub_path: Box, + /// The full path of the resolved path. This is the safest path to use. + /// For example, "PciRoot(0x0)/Pci(0x4,0x0)/NVMe(0x1,00-00-00-00-00-00-00-00)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)/\EFI\BOOT\BOOTX64.efi" pub full_path: Box, + /// The handle of the filesystem containing the path. + /// This can be used to acquire a [SimpleFileSystem] protocol to read the file. pub filesystem_handle: Handle, } +/// Resolve a path specified by [input] to its various components. +/// Uses [default_root_path] as the base root if one is not specified in the path. +/// Returns [ResolvedPath] which contains the resolved components. pub fn resolve_path(default_root_path: &DevicePath, input: &str) -> Result { - let mut path = text_to_device_path(input).context("failed to convert text to path")?; + let mut path = text_to_device_path(input).context("unable to convert text to path")?; let path_has_device = path .node_iter() .next() @@ -91,21 +111,21 @@ pub fn resolve_path(default_root_path: &DevicePath, input: &str) -> Result(&mut root_path) - .context("failed to locate filesystem device path")?; - let subpath = device_path_subpath(path.deref()).context("failed to get device subpath")?; + .context("unable to locate filesystem device path")?; + let subpath = device_path_subpath(path.deref()).context("unable to get device subpath")?; Ok(ResolvedPath { root_path: root_path.to_boxed(), sub_path: text_to_device_path(subpath.as_str())?.to_boxed(), @@ -114,6 +134,13 @@ pub fn resolve_path(default_root_path: &DevicePath, input: &str) -> Result Result> { let resolved = resolve_path(default_root_path, input)?; let fs = uefi::boot::open_protocol_exclusive::(resolved.filesystem_handle) diff --git a/src/utils/framebuffer.rs b/src/utils/framebuffer.rs index b10c9f7..efcb60d 100644 --- a/src/utils/framebuffer.rs +++ b/src/utils/framebuffer.rs @@ -27,7 +27,7 @@ impl Framebuffer { dest: (0, 0), dims: (self.width, self.height), }) - .context("failed to blit framebuffer")?; + .context("unable to blit framebuffer")?; Ok(()) } } diff --git a/src/utils/linux_media_initrd.rs b/src/utils/linux_media_initrd.rs index 02a32fa..fdef147 100644 --- a/src/utils/linux_media_initrd.rs +++ b/src/utils/linux_media_initrd.rs @@ -100,6 +100,9 @@ fn already_registered() -> Result { Ok(false) } +/// Registers the provided [data] with the UEFI stack as a Linux initrd. +/// This uses a special device path that Linux EFI stub will look at +/// to load the initrd from. pub fn register_linux_initrd(data: Box<[u8]>) -> Result { let path = LinuxMediaInitrdProtocol::device_path(); let path = Box::leak(path); @@ -149,6 +152,8 @@ pub fn register_linux_initrd(data: Box<[u8]>) -> Result }) } +/// Unregisters a Linux initrd from the UEFI stack. +/// This will free the memory allocated by the initrd. pub fn unregister_linux_initrd(handle: LinuxMediaInitrdHandle) -> Result<()> { if !already_registered()? { return Ok(());