From fc239ea54d111228d2d9af92d785bf5d28a88483 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sat, 11 Oct 2025 14:35:29 -0700 Subject: [PATCH] introduce the use of anyhow to no longer use panic --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/actions.rs | 21 +++++++++++---------- src/actions/chainload.rs | 22 ++++++++++++---------- src/actions/print.rs | 6 ++++-- src/actions/splash.rs | 25 ++++++++++++++----------- src/config.rs | 11 ++++++++--- src/context.rs | 12 ++++++------ src/generators.rs | 10 ++++++---- src/generators/matrix.rs | 9 +++++---- src/main.rs | 29 +++++++++++++++++------------ src/setup.rs | 8 +++++--- src/utils.rs | 38 +++++++++++++++++++++----------------- src/utils/framebuffer.rs | 6 ++++-- 14 files changed, 121 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0454c52..dbbf330 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,12 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + [[package]] name = "autocfg" version = "1.5.0" @@ -247,6 +253,7 @@ version = "0.3.7" name = "sprout" version = "0.1.0" dependencies = [ + "anyhow", "image", "log", "serde", diff --git a/Cargo.toml b/Cargo.toml index fdd39e6..f15dd1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +anyhow = "1.0.100" toml = "0.9.7" log = "0.4.28" diff --git a/src/actions.rs b/src/actions.rs index b13ebb3..870331c 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -1,4 +1,5 @@ -use crate::context::Context; +use crate::context::SproutContext; +use anyhow::{Result, bail}; use serde::{Deserialize, Serialize}; use std::rc::Rc; @@ -19,25 +20,25 @@ pub struct ActionDeclaration { pub splash: Option, } -pub fn execute(context: Rc, name: impl AsRef) { +pub fn execute(context: Rc, name: impl AsRef) -> Result<()> { let Some(action) = context.root().actions().get(name.as_ref()) else { - panic!("unknown action: {}", name.as_ref()); + bail!("unknown action '{}'", name.as_ref()); }; let context = context.finalize().freeze(); if let Some(chainload) = &action.chainload { - chainload::chainload(context.clone(), chainload); - return; + chainload::chainload(context.clone(), chainload)?; + return Ok(()); } else if let Some(print) = &action.print { - print::print(context.clone(), print); - return; + print::print(context.clone(), print)?; + return Ok(()); } #[cfg(feature = "splash")] if let Some(splash) = &action.splash { - splash::splash(context.clone(), splash); - return; + splash::splash(context.clone(), splash)?; + return Ok(()); } - panic!("unknown action configuration"); + bail!("unknown action configuration"); } diff --git a/src/actions/chainload.rs b/src/actions/chainload.rs index cba5a06..0896bbd 100644 --- a/src/actions/chainload.rs +++ b/src/actions/chainload.rs @@ -1,5 +1,6 @@ -use crate::context::Context; +use crate::context::SproutContext; use crate::utils; +use anyhow::{Context, Result, bail}; use log::info; use serde::{Deserialize, Serialize}; use std::rc::Rc; @@ -14,19 +15,19 @@ pub struct ChainloadConfiguration { pub options: Vec, } -pub fn chainload(context: Rc, configuration: &ChainloadConfiguration) { +pub fn chainload(context: Rc, configuration: &ChainloadConfiguration) -> Result<()> { let sprout_image = uefi::boot::image_handle(); let image_device_path_protocol = uefi::boot::open_protocol_exclusive::(sprout_image) - .expect("unable to open loaded image device path protocol"); + .context("unable to open loaded image device path protocol")?; - let mut full_path = utils::device_path_root(&image_device_path_protocol); + let mut full_path = utils::device_path_root(&image_device_path_protocol)?; full_path.push_str(&context.stamp(&configuration.path)); info!("path={}", full_path); - let device_path = utils::text_to_device_path(&full_path); + let device_path = utils::text_to_device_path(&full_path)?; let image = uefi::boot::load_image( sprout_image, @@ -35,10 +36,10 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfiguration) { boot_policy: uefi::proto::BootPolicy::ExactMatch, }, ) - .expect("failed to load image"); + .context("failed to load image")?; let mut loaded_image_protocol = uefi::boot::open_protocol_exclusive::(image) - .expect("unable to open loaded image protocol"); + .context("unable to open loaded image protocol")?; let options = configuration .options @@ -49,12 +50,12 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfiguration) { if !options.is_empty() { let options = Box::new( CString16::try_from(&options[..]) - .expect("unable to convert chainloader options to CString16"), + .context("unable to convert chainloader options to CString16")?, ); info!("options={}", options); if options.num_bytes() > u32::MAX as usize { - panic!("chainloader options too large"); + bail!("chainloader options too large"); } // SAFETY: options size is checked to validate it is safe to pass. @@ -68,5 +69,6 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfiguration) { let (base, size) = loaded_image_protocol.info(); info!("loaded image base={:#x} size={:#x}", base.addr(), size); - uefi::boot::start_image(image).expect("failed to start image"); + uefi::boot::start_image(image).context("failed to start image")?; + Ok(()) } diff --git a/src/actions/print.rs b/src/actions/print.rs index 8259808..984404d 100644 --- a/src/actions/print.rs +++ b/src/actions/print.rs @@ -1,4 +1,5 @@ -use crate::context::Context; +use crate::context::SproutContext; +use anyhow::Result; use serde::{Deserialize, Serialize}; use std::rc::Rc; @@ -8,6 +9,7 @@ pub struct PrintConfiguration { pub text: String, } -pub fn print(context: Rc, configuration: &PrintConfiguration) { +pub fn print(context: Rc, configuration: &PrintConfiguration) -> Result<()> { println!("{}", context.stamp(&configuration.text)); + Ok(()) } diff --git a/src/actions/splash.rs b/src/actions/splash.rs index c61c697..adbabde 100644 --- a/src/actions/splash.rs +++ b/src/actions/splash.rs @@ -1,6 +1,7 @@ -use crate::context::Context; +use crate::context::SproutContext; use crate::utils::framebuffer::Framebuffer; use crate::utils::read_file_contents; +use anyhow::{Context, Result}; use image::imageops::{FilterType, resize}; use image::math::Rect; use image::{DynamicImage, ImageBuffer, ImageFormat, ImageReader, Rgba}; @@ -22,11 +23,11 @@ pub fn default_splash_time() -> u32 { 5 } -fn setup_graphics() -> ScopedProtocol { +fn setup_graphics() -> Result> { let gop_handle = uefi::boot::get_handle_for_protocol::() - .expect("failed to get graphics output"); + .context("failed to get graphics output")?; uefi::boot::open_protocol_exclusive::(gop_handle) - .expect("failed to open graphics output") + .context("failed to open graphics output") } fn fit_to_frame(image: &DynamicImage, frame: Rect) -> Rect { @@ -67,8 +68,8 @@ fn resize_to_fit(image: &DynamicImage, frame: Rect) -> ImageBuffer, Vec resize(&image, frame.width, frame.height, FilterType::Lanczos3) } -fn draw(image: DynamicImage) { - let mut gop = setup_graphics(); +fn draw(image: DynamicImage) -> Result<()> { + let mut gop = setup_graphics()?; let (width, height) = gop.current_mode_info().resolution(); let display_frame = Rect { x: 0, @@ -89,15 +90,17 @@ fn draw(image: DynamicImage) { fb.blue = pixel[2]; } - framebuffer.blit(&mut gop); + framebuffer.blit(&mut gop)?; + Ok(()) } -pub fn splash(context: Rc, configuration: &SplashConfiguration) { +pub fn splash(context: Rc, configuration: &SplashConfiguration) -> Result<()> { let image = context.stamp(&configuration.image); - let image = read_file_contents(&image); + let image = read_file_contents(&image)?; let image = ImageReader::with_format(Cursor::new(image), ImageFormat::Png) .decode() - .expect("failed to decode splash image"); - draw(image); + .context("failed 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 9f26b65..6759b38 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,8 @@ use crate::actions::ActionDeclaration; use crate::generators::GeneratorDeclaration; use crate::utils; +use anyhow::Context; +use anyhow::Result; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -43,9 +45,12 @@ pub struct PhaseConfiguration { pub values: BTreeMap, } -pub fn load() -> RootConfiguration { - let content = utils::read_file_contents("sprout.toml"); - toml::from_slice(&content).expect("unable to parse sprout.toml file") +pub fn load() -> Result { + let content = + utils::read_file_contents("sprout.toml").context("unable to read sprout.toml file")?; + let config: RootConfiguration = + toml::from_slice(&content).context("unable to parse sprout.toml file")?; + Ok(config) } pub fn latest_version() -> u32 { diff --git a/src/context.rs b/src/context.rs index ded6c63..eacfd9d 100644 --- a/src/context.rs +++ b/src/context.rs @@ -21,13 +21,13 @@ impl RootContext { } } -pub struct Context { +pub struct SproutContext { root: Rc, - parent: Option>, + parent: Option>, values: BTreeMap, } -impl Context { +impl SproutContext { pub fn new(root: RootContext) -> Self { Self { root: Rc::new(root), @@ -80,7 +80,7 @@ impl Context { } } - pub fn fork(self: &Rc) -> Self { + pub fn fork(self: &Rc) -> Self { Self { root: self.root.clone(), parent: Some(self.clone()), @@ -88,11 +88,11 @@ impl Context { } } - pub fn freeze(self) -> Rc { + pub fn freeze(self) -> Rc { Rc::new(self) } - pub fn finalize(&self) -> Context { + pub fn finalize(&self) -> SproutContext { let mut current_values = self.all_values(); loop { diff --git a/src/generators.rs b/src/generators.rs index 4e7c300..647b3da 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -1,6 +1,8 @@ use crate::config::EntryDeclaration; -use crate::context::Context; +use crate::context::SproutContext; use crate::generators::matrix::MatrixConfiguration; +use anyhow::Result; +use anyhow::bail; use serde::{Deserialize, Serialize}; use std::rc::Rc; @@ -13,12 +15,12 @@ pub struct GeneratorDeclaration { } pub fn generate( - context: Rc, + context: Rc, generator: &GeneratorDeclaration, -) -> Vec<(Rc, EntryDeclaration)> { +) -> Result, EntryDeclaration)>> { if let Some(matrix) = &generator.matrix { matrix::generate(context, matrix) } else { - panic!("unknown action configuration"); + bail!("unknown action configuration"); } } diff --git a/src/generators/matrix.rs b/src/generators/matrix.rs index cf5cace..9499b77 100644 --- a/src/generators/matrix.rs +++ b/src/generators/matrix.rs @@ -1,5 +1,6 @@ use crate::config::EntryDeclaration; -use crate::context::Context; +use crate::context::SproutContext; +use anyhow::Result; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::rc::Rc; @@ -34,9 +35,9 @@ fn build_matrix(input: &BTreeMap>) -> Vec, + context: Rc, matrix: &MatrixConfiguration, -) -> Vec<(Rc, EntryDeclaration)> { +) -> Result, EntryDeclaration)>> { let combinations = build_matrix(&matrix.values); let mut entries = Vec::new(); @@ -55,5 +56,5 @@ pub fn generate( entries.push((context, entry)); } - entries + Ok(entries) } diff --git a/src/main.rs b/src/main.rs index 0e45322..86b4154 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ #![feature(uefi_std)] use crate::config::PhaseConfiguration; -use crate::context::{Context, RootContext}; +use crate::context::{RootContext, SproutContext}; +use anyhow::{Context, Result, bail}; use log::info; use std::rc::Rc; @@ -12,35 +13,37 @@ pub mod generators; pub mod setup; pub mod utils; -fn phase(context: Rc, phase: &[PhaseConfiguration]) { +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); + actions::execute(context.clone(), action) + .context(format!("failed to execute action '{}'", action))?; } } + Ok(()) } -fn main() { - setup::init(); +fn main() -> Result<()> { + setup::init()?; - let config = config::load(); + let config = config::load()?; - if config.version > config::latest_version() { - panic!("unsupported configuration version: {}", config.version); + if config.version != config::latest_version() { + bail!("unsupported configuration version: {}", config.version); } let mut root = RootContext::new(); root.actions_mut().extend(config.actions.clone()); - let mut context = Context::new(root); + let mut context = SproutContext::new(root); context.insert(&config.values); let context = context.freeze(); - phase(context.clone(), &config.phases.startup); + phase(context.clone(), &config.phases.startup)?; let mut all_entries = Vec::new(); @@ -51,7 +54,7 @@ fn main() { for (_name, generator) in config.generators { let context = context.fork().freeze(); - for entry in generators::generate(context.clone(), &generator) { + for entry in generators::generate(context.clone(), &generator)? { all_entries.push(entry); } } @@ -77,6 +80,8 @@ fn main() { for action in &entry.actions { let action = context.stamp(action); - actions::execute(context.clone(), action); + actions::execute(context.clone(), &action) + .context(format!("failed to execute action '{}'", action))?; } + Ok(()) } diff --git a/src/setup.rs b/src/setup.rs index ea787be..6059b7a 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,6 +1,7 @@ +use anyhow::{Context, Result}; use std::os::uefi as uefi_std; -pub fn init() { +pub fn init() -> Result<()> { let system_table = uefi_std::env::system_table(); let image_handle = uefi_std::env::image_handle(); @@ -10,9 +11,10 @@ pub fn init() { unsafe { uefi::table::set_system_table(system_table.as_ptr().cast()); let handle = uefi::Handle::from_ptr(image_handle.as_ptr().cast()) - .expect("unable to resolve image handle"); + .context("unable to resolve image handle")?; uefi::boot::set_image_handle(handle); } - uefi::helpers::init().expect("failed to initialize uefi"); + uefi::helpers::init().context("failed to initialize uefi")?; + Ok(()) } diff --git a/src/utils.rs b/src/utils.rs index 9037654..bfda015 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,4 @@ +use anyhow::{Context, Result}; use uefi::CString16; use uefi::fs::{FileSystem, Path}; use uefi::proto::device_path::text::{AllowShortcuts, DevicePathFromText, DisplayOnly}; @@ -6,28 +7,30 @@ use uefi::proto::media::fs::SimpleFileSystem; pub mod framebuffer; -pub fn text_to_device_path(path: &str) -> PoolDevicePath { - let path = CString16::try_from(path).expect("unable to convert path to CString16"); +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::( uefi::boot::get_handle_for_protocol::() - .expect("no device path from text protocol"), + .context("no device path from text protocol")?, ) - .expect("unable to open device path from text protocol"); + .context("unable to open device path from text protocol")?; device_path_from_text .convert_text_to_device_path(&path) - .expect("unable to convert text to device path") + .context("unable to convert text to device path") } -pub fn device_path_root(path: &DevicePath) -> String { +pub fn device_path_root(path: &DevicePath) -> Result { let mut path = path .node_iter() .filter_map(|item| { - let item = item - .to_string(DisplayOnly(false), AllowShortcuts(false)) - .expect("unable to convert device path to string"); - if item.to_string().contains("(") { - Some(item) + let item = item.to_string(DisplayOnly(false), AllowShortcuts(false)); + if item + .as_ref() + .map(|item| item.to_string().contains("(")) + .unwrap_or(false) + { + Some(item.unwrap_or_default()) } else { None } @@ -36,16 +39,17 @@ pub fn device_path_root(path: &DevicePath) -> String { .collect::>() .join("/"); path.push('/'); - path + Ok(path) } -pub fn read_file_contents(path: &str) -> Vec { +pub fn read_file_contents(path: &str) -> Result> { let fs = uefi::boot::open_protocol_exclusive::( - uefi::boot::get_handle_for_protocol::().expect("no filesystem protocol"), + uefi::boot::get_handle_for_protocol::() + .context("no filesystem protocol")?, ) - .expect("unable to open filesystem protocol"); + .context("unable to open filesystem protocol")?; let mut fs = FileSystem::new(fs); - let path = CString16::try_from(path).expect("unable to convert path to CString16"); + let path = CString16::try_from(path).context("unable to convert path to CString16")?; let content = fs.read(Path::new(&path)); - content.expect("unable to read file contents") + content.context("unable to read file contents") } diff --git a/src/utils/framebuffer.rs b/src/utils/framebuffer.rs index c65132f..b10c9f7 100644 --- a/src/utils/framebuffer.rs +++ b/src/utils/framebuffer.rs @@ -1,3 +1,4 @@ +use anyhow::{Context, Result}; use uefi::proto::console::gop::{BltOp, BltPixel, BltRegion, GraphicsOutput}; pub struct Framebuffer { @@ -19,13 +20,14 @@ impl Framebuffer { self.pixels.get_mut(y * self.width + x) } - pub fn blit(&self, gop: &mut GraphicsOutput) { + pub fn blit(&self, gop: &mut GraphicsOutput) -> Result<()> { gop.blt(BltOp::BufferToVideo { buffer: &self.pixels, src: BltRegion::Full, dest: (0, 0), dims: (self.width, self.height), }) - .expect("failed to blit framebuffer"); + .context("failed to blit framebuffer")?; + Ok(()) } }