begin documenting some functions and align error messages

This commit is contained in:
2025-10-14 12:47:33 -07:00
parent 7a0c32c191
commit e90b45f58d
15 changed files with 148 additions and 101 deletions

View File

@@ -28,7 +28,7 @@ pub fn chainload(context: Rc<SproutContext>, 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<SproutContext>, 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::<LoadedImage>(image)
.context("unable to open loaded image protocol")?;
@@ -76,20 +76,20 @@ pub fn chainload(context: Rc<SproutContext>, 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(())
}

View File

@@ -25,9 +25,9 @@ pub fn default_splash_time() -> u32 {
fn setup_graphics() -> Result<ScopedProtocol<GraphicsOutput>> {
let gop_handle = uefi::boot::get_handle_for_protocol::<GraphicsOutput>()
.context("failed to get graphics output")?;
.context("unable to get graphics output")?;
uefi::boot::open_protocol_exclusive::<GraphicsOutput>(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<SproutContext>, 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(())

View File

@@ -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<String>,
#[serde(default)]
pub values: BTreeMap<String, String>,
}
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct PhasesConfiguration {
#[serde(default)]
pub early: Vec<PhaseConfiguration>,
#[serde(default)]
pub startup: Vec<PhaseConfiguration>,
#[serde(default)]
pub late: Vec<PhaseConfiguration>,
}
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct PhaseConfiguration {
#[serde(default)]
pub actions: Vec<String>,
#[serde(default)]
pub values: BTreeMap<String, String>,
pub fn latest_version() -> u32 {
1
}
pub fn load() -> Result<RootConfiguration> {
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 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<RootConfiguration> {
toml::from_slice(&content).context("unable to parse sprout.toml file")?;
Ok(config)
}
pub fn latest_version() -> u32 {
1
}

View File

@@ -34,16 +34,16 @@ fn load_driver(context: Rc<SproutContext>, 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(())
}

11
src/entries.rs Normal file
View File

@@ -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<String>,
#[serde(default)]
pub values: BTreeMap<String, String>,
}

View File

@@ -32,7 +32,7 @@ pub fn extract(
extractor: &FilesystemDeviceMatchExtractor,
) -> Result<String> {
let handles = uefi::boot::find_handles::<SimpleFileSystem>()
.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::<SimpleFileSystem>(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::<FileSystemVolumeLabel>()
.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::<DevicePath>(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 {

View File

@@ -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;

View File

@@ -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::<SimpleFileSystem>(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;

View File

@@ -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;

View File

@@ -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<SproutContext>, 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(())
}

38
src/phases.rs Normal file
View File

@@ -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<PhaseConfiguration>,
#[serde(default)]
pub startup: Vec<PhaseConfiguration>,
#[serde(default)]
pub late: Vec<PhaseConfiguration>,
}
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct PhaseConfiguration {
#[serde(default)]
pub actions: Vec<String>,
#[serde(default)]
pub values: BTreeMap<String, String>,
}
pub fn phase(context: Rc<SproutContext>, 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(())
}

View File

@@ -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(())
}

View File

@@ -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<PoolDevicePath> {
let path = CString16::try_from(path).context("unable to convert path to CString16")?;
let device_path_from_text = uefi::boot::open_protocol_exclusive::<DevicePathFromText>(
@@ -22,6 +24,9 @@ pub fn text_to_device_path(path: &str) -> Result<PoolDevicePath> {
.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<String> {
let mut path = path
.node_iter()
@@ -44,6 +49,9 @@ pub fn device_path_root(path: &DevicePath) -> Result<String> {
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<String> {
let path = path
.node_iter()
@@ -65,15 +73,27 @@ pub fn device_path_subpath(path: &DevicePath) -> Result<String> {
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<DevicePath>,
/// The subpath of the resolved path. This is the path to the file.
/// For example, "\EFI\BOOT\BOOTX64.efi"
pub sub_path: Box<DevicePath>,
/// 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<DevicePath>,
/// 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<ResolvedPath> {
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<Resol
input.insert_str(
0,
device_path_root(default_root_path)
.context("failed to get loaded image device root")?
.context("unable to get loaded image device root")?
.as_str(),
);
path = text_to_device_path(input.as_str()).context("failed to convert text to path")?;
path = text_to_device_path(input.as_str()).context("unable to convert text to path")?;
}
let path = path.to_boxed();
let root = device_path_root(path.as_ref()).context("failed to convert root to path")?;
let root = device_path_root(path.as_ref()).context("unable to convert root to path")?;
let root_path = text_to_device_path(root.as_str())
.context("failed to convert root to path")?
.context("unable to convert root to path")?
.to_boxed();
let mut root_path = root_path.as_ref();
let handle = uefi::boot::locate_device_path::<SimpleFileSystem>(&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<Resol
})
}
/// Read the contents of a file at the location specified with the [input] path.
/// Internally, this uses [resolve_path] to resolve the path to its various components.
/// [resolve_path] is passed the [default_root_path] which should specify a base root.
///
/// This acquires exclusive protocol access to the [SimpleFileSystem] protocol of the resolved
/// filesystem handle, so care must be taken to call this function outside a scope with
/// the filesystem handle protocol acquired.
pub fn read_file_contents(default_root_path: &DevicePath, input: &str) -> Result<Vec<u8>> {
let resolved = resolve_path(default_root_path, input)?;
let fs = uefi::boot::open_protocol_exclusive::<SimpleFileSystem>(resolved.filesystem_handle)

View File

@@ -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(())
}
}

View File

@@ -100,6 +100,9 @@ fn already_registered() -> Result<bool> {
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<LinuxMediaInitrdHandle> {
let path = LinuxMediaInitrdProtocol::device_path();
let path = Box::leak(path);
@@ -149,6 +152,8 @@ pub fn register_linux_initrd(data: Box<[u8]>) -> Result<LinuxMediaInitrdHandle>
})
}
/// 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(());