#![doc = include_str!("../README.md")] #![feature(uefi_std)] use crate::context::{RootContext, SproutContext}; use crate::phases::phase; use anyhow::{Context, Result}; use log::info; use std::collections::BTreeMap; use std::ops::Deref; use uefi::proto::device_path::LoadedImageDevicePath; use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly}; /// actions: Code that can be configured and executed by Sprout. pub mod actions; /// config: Sprout configuration mechanism. pub mod config; /// context: Stored values that can be cheaply forked and cloned. pub mod context; /// drivers: EFI drivers to load and provide extra functionality. pub mod drivers; /// entries: Boot menu entries that have a title and can execute actions. pub mod entries; /// extractors: Runtime code that can extract values into the Sprout context. pub mod extractors; /// generators: Runtime code that can generate entries with specific values. pub mod generators; /// phases: Hooks into specific parts of the boot process. pub mod phases; /// setup: Code that initializes the UEFI environment for Sprout. pub mod setup; /// options: Parse the options of the Sprout executable. pub mod options; /// utils: Utility functions that are used by other parts of Sprout. pub mod utils; /// The main entrypoint of sprout. /// It is possible this function will not return if actions that are executed /// exit boot services or do not return control to sprout. fn main() -> Result<()> { // Initialize the basic UEFI environment. setup::init()?; // Parse the options to the sprout executable. let options = options::parser::parse().context("unable to parse options")?; // Load the configuration of sprout. // At this point, the configuration has been validated and the specified // version is checked to ensure compatibility. let config = config::loader::load(&options)?; // Load the root context. // This is done in a block to ensure the release of the LoadedImageDevicePath protocol. let mut root = { let current_image_device_path_protocol = uefi::boot::open_protocol_exclusive::< LoadedImageDevicePath, >(uefi::boot::image_handle()) .context("unable to get loaded image device path")?; let loaded_image_path = current_image_device_path_protocol.deref().to_boxed(); info!( "loaded image path: {}", loaded_image_path.to_string(DisplayOnly(false), AllowShortcuts(false))? ); RootContext::new(loaded_image_path, options) }; // Insert the configuration actions into the root context. root.actions_mut().extend(config.actions.clone()); // Create a new sprout context with the root context. let mut context = SproutContext::new(root); // Insert the configuration values into the sprout context. context.insert(&config.values); // Freeze the sprout context so it can be shared and cheaply cloned. let context = context.freeze(); // Execute the early phase. phase(context.clone(), &config.phases.early).context("unable to execute early phase")?; // Load all configured drivers. drivers::load(context.clone(), &config.drivers).context("unable to load drivers")?; // Run all the extractors declared in the configuration. let mut extracted = BTreeMap::new(); for (name, extractor) in &config.extractors { let value = extractors::extract(context.clone(), extractor) .context(format!("unable to extract value {}", name))?; info!("extracted value {}: {}", name, value); extracted.insert(name.clone(), value); } let mut context = context.fork(); // Insert the extracted values into the sprout context. context.insert(&extracted); let context = context.freeze(); // Execute the late phase. phase(context.clone(), &config.phases.startup).context("unable to execute startup phase")?; let mut staged_entries = Vec::new(); // Insert all the static entries from the configuration into the entry list. for (_name, entry) in config.entries { // Associate the main context with the static entry. staged_entries.push((context.clone(), entry)); } // Run all the generators declared in the configuration. for (_name, generator) in config.generators { let context = context.fork().freeze(); // Add all the entries generated by the generator to the entry list. // The generator specifies the context associated with the entry. for entry in generators::generate(context.clone(), &generator)? { staged_entries.push(entry); } } // Build a list of all the final boot entries. let mut final_entries = Vec::new(); for (context, entry) in staged_entries { let mut context = context.fork(); // Insert the values from the entry configuration into the // sprout context to use with the entry itself. context.insert(&entry.values); let context = context.finalize().freeze(); // Insert the entry configuration into final boot entries with the extended context. final_entries.push((context, entry)); } // TODO(azenla): Implement boot menu here. // For now, we just print all of the entries. info!("entries:"); for (index, (context, entry)) in final_entries.iter().enumerate() { let title = context.stamp(&entry.title); info!(" entry {}: {}", index + 1, title); } // Execute the late phase. phase(context.clone(), &config.phases.late).context("unable to execute late phase")?; // Use the boot option if possible, otherwise pick the first entry. let (context, entry) = if let Some(ref boot) = context.root().options().boot { final_entries .iter() .find(|(_context, entry)| &entry.title == boot) .context(format!("unable to find entry: {boot}"))? } else { final_entries.first().context("no entries found")? }; // Execute all the actions for the selected entry. for action in &entry.actions { let action = context.stamp(action); actions::execute(context.clone(), &action) .context(format!("unable to execute action '{}'", action))?; } // Sprout doesn't necessarily guarantee anything was booted. // If we reach here, we will exit back to whoever called us. Ok(()) }