introduce the use of anyhow to no longer use panic

This commit is contained in:
2025-10-11 14:35:29 -07:00
parent 449eb85ab8
commit fc239ea54d
14 changed files with 121 additions and 84 deletions

7
Cargo.lock generated
View File

@@ -8,6 +8,12 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "anyhow"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.5.0" version = "1.5.0"
@@ -247,6 +253,7 @@ version = "0.3.7"
name = "sprout" name = "sprout"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"image", "image",
"log", "log",
"serde", "serde",

View File

@@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
anyhow = "1.0.100"
toml = "0.9.7" toml = "0.9.7"
log = "0.4.28" log = "0.4.28"

View File

@@ -1,4 +1,5 @@
use crate::context::Context; use crate::context::SproutContext;
use anyhow::{Result, bail};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::rc::Rc; use std::rc::Rc;
@@ -19,25 +20,25 @@ pub struct ActionDeclaration {
pub splash: Option<splash::SplashConfiguration>, pub splash: Option<splash::SplashConfiguration>,
} }
pub fn execute(context: Rc<Context>, name: impl AsRef<str>) { pub fn execute(context: Rc<SproutContext>, name: impl AsRef<str>) -> Result<()> {
let Some(action) = context.root().actions().get(name.as_ref()) else { 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(); let context = context.finalize().freeze();
if let Some(chainload) = &action.chainload { if let Some(chainload) = &action.chainload {
chainload::chainload(context.clone(), chainload); chainload::chainload(context.clone(), chainload)?;
return; return Ok(());
} else if let Some(print) = &action.print { } else if let Some(print) = &action.print {
print::print(context.clone(), print); print::print(context.clone(), print)?;
return; return Ok(());
} }
#[cfg(feature = "splash")] #[cfg(feature = "splash")]
if let Some(splash) = &action.splash { if let Some(splash) = &action.splash {
splash::splash(context.clone(), splash); splash::splash(context.clone(), splash)?;
return; return Ok(());
} }
panic!("unknown action configuration"); bail!("unknown action configuration");
} }

View File

@@ -1,5 +1,6 @@
use crate::context::Context; use crate::context::SproutContext;
use crate::utils; use crate::utils;
use anyhow::{Context, Result, bail};
use log::info; use log::info;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::rc::Rc; use std::rc::Rc;
@@ -14,19 +15,19 @@ pub struct ChainloadConfiguration {
pub options: Vec<String>, pub options: Vec<String>,
} }
pub fn chainload(context: Rc<Context>, configuration: &ChainloadConfiguration) { pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfiguration) -> Result<()> {
let sprout_image = uefi::boot::image_handle(); let sprout_image = uefi::boot::image_handle();
let image_device_path_protocol = let image_device_path_protocol =
uefi::boot::open_protocol_exclusive::<LoadedImageDevicePath>(sprout_image) uefi::boot::open_protocol_exclusive::<LoadedImageDevicePath>(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)); full_path.push_str(&context.stamp(&configuration.path));
info!("path={}", full_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( let image = uefi::boot::load_image(
sprout_image, sprout_image,
@@ -35,10 +36,10 @@ pub fn chainload(context: Rc<Context>, configuration: &ChainloadConfiguration) {
boot_policy: uefi::proto::BootPolicy::ExactMatch, 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::<LoadedImage>(image) let mut loaded_image_protocol = uefi::boot::open_protocol_exclusive::<LoadedImage>(image)
.expect("unable to open loaded image protocol"); .context("unable to open loaded image protocol")?;
let options = configuration let options = configuration
.options .options
@@ -49,12 +50,12 @@ pub fn chainload(context: Rc<Context>, configuration: &ChainloadConfiguration) {
if !options.is_empty() { if !options.is_empty() {
let options = Box::new( let options = Box::new(
CString16::try_from(&options[..]) CString16::try_from(&options[..])
.expect("unable to convert chainloader options to CString16"), .context("unable to convert chainloader options to CString16")?,
); );
info!("options={}", options); info!("options={}", options);
if options.num_bytes() > u32::MAX as usize { 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. // SAFETY: options size is checked to validate it is safe to pass.
@@ -68,5 +69,6 @@ pub fn chainload(context: Rc<Context>, configuration: &ChainloadConfiguration) {
let (base, size) = loaded_image_protocol.info(); let (base, size) = loaded_image_protocol.info();
info!("loaded image base={:#x} size={:#x}", base.addr(), size); 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(())
} }

View File

@@ -1,4 +1,5 @@
use crate::context::Context; use crate::context::SproutContext;
use anyhow::Result;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::rc::Rc; use std::rc::Rc;
@@ -8,6 +9,7 @@ pub struct PrintConfiguration {
pub text: String, pub text: String,
} }
pub fn print(context: Rc<Context>, configuration: &PrintConfiguration) { pub fn print(context: Rc<SproutContext>, configuration: &PrintConfiguration) -> Result<()> {
println!("{}", context.stamp(&configuration.text)); println!("{}", context.stamp(&configuration.text));
Ok(())
} }

View File

@@ -1,6 +1,7 @@
use crate::context::Context; use crate::context::SproutContext;
use crate::utils::framebuffer::Framebuffer; use crate::utils::framebuffer::Framebuffer;
use crate::utils::read_file_contents; use crate::utils::read_file_contents;
use anyhow::{Context, Result};
use image::imageops::{FilterType, resize}; use image::imageops::{FilterType, resize};
use image::math::Rect; use image::math::Rect;
use image::{DynamicImage, ImageBuffer, ImageFormat, ImageReader, Rgba}; use image::{DynamicImage, ImageBuffer, ImageFormat, ImageReader, Rgba};
@@ -22,11 +23,11 @@ pub fn default_splash_time() -> u32 {
5 5
} }
fn setup_graphics() -> ScopedProtocol<GraphicsOutput> { fn setup_graphics() -> Result<ScopedProtocol<GraphicsOutput>> {
let gop_handle = uefi::boot::get_handle_for_protocol::<GraphicsOutput>() let gop_handle = uefi::boot::get_handle_for_protocol::<GraphicsOutput>()
.expect("failed to get graphics output"); .context("failed to get graphics output")?;
uefi::boot::open_protocol_exclusive::<GraphicsOutput>(gop_handle) uefi::boot::open_protocol_exclusive::<GraphicsOutput>(gop_handle)
.expect("failed to open graphics output") .context("failed to open graphics output")
} }
fn fit_to_frame(image: &DynamicImage, frame: Rect) -> Rect { fn fit_to_frame(image: &DynamicImage, frame: Rect) -> Rect {
@@ -67,8 +68,8 @@ fn resize_to_fit(image: &DynamicImage, frame: Rect) -> ImageBuffer<Rgba<u8>, Vec
resize(&image, frame.width, frame.height, FilterType::Lanczos3) resize(&image, frame.width, frame.height, FilterType::Lanczos3)
} }
fn draw(image: DynamicImage) { fn draw(image: DynamicImage) -> Result<()> {
let mut gop = setup_graphics(); let mut gop = setup_graphics()?;
let (width, height) = gop.current_mode_info().resolution(); let (width, height) = gop.current_mode_info().resolution();
let display_frame = Rect { let display_frame = Rect {
x: 0, x: 0,
@@ -89,15 +90,17 @@ fn draw(image: DynamicImage) {
fb.blue = pixel[2]; fb.blue = pixel[2];
} }
framebuffer.blit(&mut gop); framebuffer.blit(&mut gop)?;
Ok(())
} }
pub fn splash(context: Rc<Context>, configuration: &SplashConfiguration) { pub fn splash(context: Rc<SproutContext>, configuration: &SplashConfiguration) -> Result<()> {
let image = context.stamp(&configuration.image); 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) let image = ImageReader::with_format(Cursor::new(image), ImageFormat::Png)
.decode() .decode()
.expect("failed to decode splash image"); .context("failed to decode splash image")?;
draw(image); draw(image)?;
std::thread::sleep(Duration::from_secs(configuration.time as u64)); std::thread::sleep(Duration::from_secs(configuration.time as u64));
Ok(())
} }

View File

@@ -1,6 +1,8 @@
use crate::actions::ActionDeclaration; use crate::actions::ActionDeclaration;
use crate::generators::GeneratorDeclaration; use crate::generators::GeneratorDeclaration;
use crate::utils; use crate::utils;
use anyhow::Context;
use anyhow::Result;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::BTreeMap; use std::collections::BTreeMap;
@@ -43,9 +45,12 @@ pub struct PhaseConfiguration {
pub values: BTreeMap<String, String>, pub values: BTreeMap<String, String>,
} }
pub fn load() -> RootConfiguration { pub fn load() -> Result<RootConfiguration> {
let content = utils::read_file_contents("sprout.toml"); let content =
toml::from_slice(&content).expect("unable to parse sprout.toml file") 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 { pub fn latest_version() -> u32 {

View File

@@ -21,13 +21,13 @@ impl RootContext {
} }
} }
pub struct Context { pub struct SproutContext {
root: Rc<RootContext>, root: Rc<RootContext>,
parent: Option<Rc<Context>>, parent: Option<Rc<SproutContext>>,
values: BTreeMap<String, String>, values: BTreeMap<String, String>,
} }
impl Context { impl SproutContext {
pub fn new(root: RootContext) -> Self { pub fn new(root: RootContext) -> Self {
Self { Self {
root: Rc::new(root), root: Rc::new(root),
@@ -80,7 +80,7 @@ impl Context {
} }
} }
pub fn fork(self: &Rc<Context>) -> Self { pub fn fork(self: &Rc<SproutContext>) -> Self {
Self { Self {
root: self.root.clone(), root: self.root.clone(),
parent: Some(self.clone()), parent: Some(self.clone()),
@@ -88,11 +88,11 @@ impl Context {
} }
} }
pub fn freeze(self) -> Rc<Context> { pub fn freeze(self) -> Rc<SproutContext> {
Rc::new(self) Rc::new(self)
} }
pub fn finalize(&self) -> Context { pub fn finalize(&self) -> SproutContext {
let mut current_values = self.all_values(); let mut current_values = self.all_values();
loop { loop {

View File

@@ -1,6 +1,8 @@
use crate::config::EntryDeclaration; use crate::config::EntryDeclaration;
use crate::context::Context; use crate::context::SproutContext;
use crate::generators::matrix::MatrixConfiguration; use crate::generators::matrix::MatrixConfiguration;
use anyhow::Result;
use anyhow::bail;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::rc::Rc; use std::rc::Rc;
@@ -13,12 +15,12 @@ pub struct GeneratorDeclaration {
} }
pub fn generate( pub fn generate(
context: Rc<Context>, context: Rc<SproutContext>,
generator: &GeneratorDeclaration, generator: &GeneratorDeclaration,
) -> Vec<(Rc<Context>, EntryDeclaration)> { ) -> Result<Vec<(Rc<SproutContext>, EntryDeclaration)>> {
if let Some(matrix) = &generator.matrix { if let Some(matrix) = &generator.matrix {
matrix::generate(context, matrix) matrix::generate(context, matrix)
} else { } else {
panic!("unknown action configuration"); bail!("unknown action configuration");
} }
} }

View File

@@ -1,5 +1,6 @@
use crate::config::EntryDeclaration; use crate::config::EntryDeclaration;
use crate::context::Context; use crate::context::SproutContext;
use anyhow::Result;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::rc::Rc; use std::rc::Rc;
@@ -34,9 +35,9 @@ fn build_matrix(input: &BTreeMap<String, Vec<String>>) -> Vec<BTreeMap<String, S
} }
pub fn generate( pub fn generate(
context: Rc<Context>, context: Rc<SproutContext>,
matrix: &MatrixConfiguration, matrix: &MatrixConfiguration,
) -> Vec<(Rc<Context>, EntryDeclaration)> { ) -> Result<Vec<(Rc<SproutContext>, EntryDeclaration)>> {
let combinations = build_matrix(&matrix.values); let combinations = build_matrix(&matrix.values);
let mut entries = Vec::new(); let mut entries = Vec::new();
@@ -55,5 +56,5 @@ pub fn generate(
entries.push((context, entry)); entries.push((context, entry));
} }
entries Ok(entries)
} }

View File

@@ -1,7 +1,8 @@
#![feature(uefi_std)] #![feature(uefi_std)]
use crate::config::PhaseConfiguration; use crate::config::PhaseConfiguration;
use crate::context::{Context, RootContext}; use crate::context::{RootContext, SproutContext};
use anyhow::{Context, Result, bail};
use log::info; use log::info;
use std::rc::Rc; use std::rc::Rc;
@@ -12,35 +13,37 @@ pub mod generators;
pub mod setup; pub mod setup;
pub mod utils; pub mod utils;
fn phase(context: Rc<Context>, phase: &[PhaseConfiguration]) { fn phase(context: Rc<SproutContext>, phase: &[PhaseConfiguration]) -> Result<()> {
for item in phase { for item in phase {
let mut context = context.fork(); let mut context = context.fork();
context.insert(&item.values); context.insert(&item.values);
let context = context.freeze(); let context = context.freeze();
for action in item.actions.iter() { 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() { fn main() -> Result<()> {
setup::init(); setup::init()?;
let config = config::load(); let config = config::load()?;
if config.version > config::latest_version() { if config.version != config::latest_version() {
panic!("unsupported configuration version: {}", config.version); bail!("unsupported configuration version: {}", config.version);
} }
let mut root = RootContext::new(); let mut root = RootContext::new();
root.actions_mut().extend(config.actions.clone()); root.actions_mut().extend(config.actions.clone());
let mut context = Context::new(root); let mut context = SproutContext::new(root);
context.insert(&config.values); context.insert(&config.values);
let context = context.freeze(); let context = context.freeze();
phase(context.clone(), &config.phases.startup); phase(context.clone(), &config.phases.startup)?;
let mut all_entries = Vec::new(); let mut all_entries = Vec::new();
@@ -51,7 +54,7 @@ fn main() {
for (_name, generator) in config.generators { for (_name, generator) in config.generators {
let context = context.fork().freeze(); 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); all_entries.push(entry);
} }
} }
@@ -77,6 +80,8 @@ fn main() {
for action in &entry.actions { for action in &entry.actions {
let action = context.stamp(action); let action = context.stamp(action);
actions::execute(context.clone(), action); actions::execute(context.clone(), &action)
.context(format!("failed to execute action '{}'", action))?;
} }
Ok(())
} }

View File

@@ -1,6 +1,7 @@
use anyhow::{Context, Result};
use std::os::uefi as uefi_std; use std::os::uefi as uefi_std;
pub fn init() { pub fn init() -> Result<()> {
let system_table = uefi_std::env::system_table(); let system_table = uefi_std::env::system_table();
let image_handle = uefi_std::env::image_handle(); let image_handle = uefi_std::env::image_handle();
@@ -10,9 +11,10 @@ pub fn init() {
unsafe { unsafe {
uefi::table::set_system_table(system_table.as_ptr().cast()); uefi::table::set_system_table(system_table.as_ptr().cast());
let handle = uefi::Handle::from_ptr(image_handle.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::boot::set_image_handle(handle);
} }
uefi::helpers::init().expect("failed to initialize uefi"); uefi::helpers::init().context("failed to initialize uefi")?;
Ok(())
} }

View File

@@ -1,3 +1,4 @@
use anyhow::{Context, Result};
use uefi::CString16; use uefi::CString16;
use uefi::fs::{FileSystem, Path}; use uefi::fs::{FileSystem, Path};
use uefi::proto::device_path::text::{AllowShortcuts, DevicePathFromText, DisplayOnly}; use uefi::proto::device_path::text::{AllowShortcuts, DevicePathFromText, DisplayOnly};
@@ -6,28 +7,30 @@ use uefi::proto::media::fs::SimpleFileSystem;
pub mod framebuffer; pub mod framebuffer;
pub fn text_to_device_path(path: &str) -> PoolDevicePath { pub fn text_to_device_path(path: &str) -> Result<PoolDevicePath> {
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 device_path_from_text = uefi::boot::open_protocol_exclusive::<DevicePathFromText>( let device_path_from_text = uefi::boot::open_protocol_exclusive::<DevicePathFromText>(
uefi::boot::get_handle_for_protocol::<DevicePathFromText>() uefi::boot::get_handle_for_protocol::<DevicePathFromText>()
.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 device_path_from_text
.convert_text_to_device_path(&path) .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<String> {
let mut path = path let mut path = path
.node_iter() .node_iter()
.filter_map(|item| { .filter_map(|item| {
let item = item let item = item.to_string(DisplayOnly(false), AllowShortcuts(false));
.to_string(DisplayOnly(false), AllowShortcuts(false)) if item
.expect("unable to convert device path to string"); .as_ref()
if item.to_string().contains("(") { .map(|item| item.to_string().contains("("))
Some(item) .unwrap_or(false)
{
Some(item.unwrap_or_default())
} else { } else {
None None
} }
@@ -36,16 +39,17 @@ pub fn device_path_root(path: &DevicePath) -> String {
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("/"); .join("/");
path.push('/'); path.push('/');
path Ok(path)
} }
pub fn read_file_contents(path: &str) -> Vec<u8> { pub fn read_file_contents(path: &str) -> Result<Vec<u8>> {
let fs = uefi::boot::open_protocol_exclusive::<SimpleFileSystem>( let fs = uefi::boot::open_protocol_exclusive::<SimpleFileSystem>(
uefi::boot::get_handle_for_protocol::<SimpleFileSystem>().expect("no filesystem protocol"), uefi::boot::get_handle_for_protocol::<SimpleFileSystem>()
.context("no filesystem protocol")?,
) )
.expect("unable to open filesystem protocol"); .context("unable to open filesystem protocol")?;
let mut fs = FileSystem::new(fs); 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)); let content = fs.read(Path::new(&path));
content.expect("unable to read file contents") content.context("unable to read file contents")
} }

View File

@@ -1,3 +1,4 @@
use anyhow::{Context, Result};
use uefi::proto::console::gop::{BltOp, BltPixel, BltRegion, GraphicsOutput}; use uefi::proto::console::gop::{BltOp, BltPixel, BltRegion, GraphicsOutput};
pub struct Framebuffer { pub struct Framebuffer {
@@ -19,13 +20,14 @@ impl Framebuffer {
self.pixels.get_mut(y * self.width + x) 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 { gop.blt(BltOp::BufferToVideo {
buffer: &self.pixels, buffer: &self.pixels,
src: BltRegion::Full, src: BltRegion::Full,
dest: (0, 0), dest: (0, 0),
dims: (self.width, self.height), dims: (self.width, self.height),
}) })
.expect("failed to blit framebuffer"); .context("failed to blit framebuffer")?;
Ok(())
} }
} }