Files
sprout/src/main.rs

144 lines
5.2 KiB
Rust

#![doc = include_str!("../README.md")]
#![feature(uefi_std)]
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 uefi::proto::device_path::LoadedImageDevicePath;
use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly};
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;
/// 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()?;
// 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()?;
// 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)
};
// 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")?;
// Pick the first entry from the list of final entries until a boot menu is implemented.
let Some((context, entry)) = final_entries.first() else {
bail!("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(())
}