2025-10-19 20:23:55 -07:00
|
|
|
#![doc = include_str!("../README.md")]
|
2025-11-03 02:04:21 -05:00
|
|
|
#![no_std]
|
|
|
|
|
#![no_main]
|
|
|
|
|
extern crate alloc;
|
2025-10-30 15:26:44 -04:00
|
|
|
|
2026-03-25 15:25:14 -04:00
|
|
|
use crate::{
|
|
|
|
|
context::{RootContext, SproutContext},
|
|
|
|
|
entries::BootableEntry,
|
|
|
|
|
options::SproutOptions,
|
|
|
|
|
phases::phase,
|
|
|
|
|
};
|
2026-10-03 15:16:05 -07:00
|
|
|
use alloc::{
|
|
|
|
|
collections::{BTreeMap, BTreeSet},
|
|
|
|
|
format,
|
2026-10-03 17:31:16 -07:00
|
|
|
string::{String, ToString},
|
2026-10-03 15:16:05 -07:00
|
|
|
vec::Vec,
|
|
|
|
|
};
|
2025-10-27 03:37:09 -04:00
|
|
|
use anyhow::{Context, Result, bail};
|
2026-03-25 15:25:14 -04:00
|
|
|
use core::{ops::Deref, time::Duration};
|
2026-10-03 17:31:16 -07:00
|
|
|
use edera_sprout_bls::{LoaderConf, LoaderTimeout, compare_versions};
|
|
|
|
|
use edera_sprout_config::{DEFAULT_MENU_TIMEOUT_SECONDS, RootConfiguration};
|
2026-03-25 15:25:14 -04:00
|
|
|
use eficore::{
|
|
|
|
|
bootloader_interface::{BootloaderInterface, BootloaderInterfaceTimeout},
|
|
|
|
|
partition::PartitionGuidForm,
|
|
|
|
|
platform::{timer::PlatformTimer, tpm::PlatformTpm},
|
|
|
|
|
secure::SecureBoot,
|
|
|
|
|
setup,
|
|
|
|
|
};
|
2025-10-30 21:38:49 -04:00
|
|
|
use log::{error, info, warn};
|
2026-03-25 15:25:14 -04:00
|
|
|
use uefi::{entry, proto::device_path::LoadedImageDevicePath};
|
2025-11-03 02:04:21 -05:00
|
|
|
use uefi_raw::Status;
|
2025-10-04 23:12:01 -07:00
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// actions: Code that can be configured and executed by Sprout.
|
2025-10-04 23:12:01 -07:00
|
|
|
pub mod actions;
|
2025-10-20 00:06:46 -07:00
|
|
|
|
2025-10-27 02:38:40 -04:00
|
|
|
/// autoconfigure: Autoconfigure Sprout based on the detected environment.
|
|
|
|
|
pub mod autoconfigure;
|
|
|
|
|
|
2026-10-03 17:19:27 -07:00
|
|
|
/// boot_counter: Consume the boot counter tries of BLS entries.
|
|
|
|
|
pub mod boot_counter;
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// config: Sprout configuration mechanism.
|
2025-10-01 18:25:49 -07:00
|
|
|
pub mod config;
|
2025-10-20 00:06:46 -07:00
|
|
|
|
|
|
|
|
/// context: Stored values that can be cheaply forked and cloned.
|
2025-10-04 23:12:01 -07:00
|
|
|
pub mod context;
|
2025-10-20 00:06:46 -07:00
|
|
|
|
|
|
|
|
/// drivers: EFI drivers to load and provide extra functionality.
|
2025-10-12 22:39:56 -07:00
|
|
|
pub mod drivers;
|
2025-10-20 00:06:46 -07:00
|
|
|
|
|
|
|
|
/// entries: Boot menu entries that have a title and can execute actions.
|
2025-10-14 12:47:33 -07:00
|
|
|
pub mod entries;
|
2025-10-20 00:06:46 -07:00
|
|
|
|
|
|
|
|
/// extractors: Runtime code that can extract values into the Sprout context.
|
2025-10-13 00:55:11 -07:00
|
|
|
pub mod extractors;
|
2025-10-20 00:06:46 -07:00
|
|
|
|
|
|
|
|
/// generators: Runtime code that can generate entries with specific values.
|
2025-10-04 23:12:01 -07:00
|
|
|
pub mod generators;
|
2025-10-20 00:06:46 -07:00
|
|
|
|
2025-10-26 23:59:50 -04:00
|
|
|
/// menu: Display a boot menu to select an entry to boot.
|
|
|
|
|
pub mod menu;
|
|
|
|
|
|
2025-11-03 14:45:48 -05:00
|
|
|
/// options: Parse the options of the Sprout executable.
|
|
|
|
|
pub mod options;
|
2025-10-28 21:05:22 -04:00
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// phases: Hooks into specific parts of the boot process.
|
2025-10-14 12:47:33 -07:00
|
|
|
pub mod phases;
|
2025-10-20 00:06:46 -07:00
|
|
|
|
2025-10-31 15:49:00 -04:00
|
|
|
/// sbat: Secure Boot Attestation section.
|
|
|
|
|
pub mod sbat;
|
|
|
|
|
|
2025-11-03 02:04:21 -05:00
|
|
|
/// The delay to wait for when an error occurs in Sprout.
|
|
|
|
|
const DELAY_ON_ERROR: Duration = Duration::from_secs(10);
|
|
|
|
|
|
2026-09-26 20:14:57 -07:00
|
|
|
/// Unwraps the `result` of a bootloader interface operation, logging the error if one occurs.
|
|
|
|
|
/// The bootloader interface only exchanges information with the operating system, so a
|
|
|
|
|
/// firmware variable that can't be read or written should not prevent booting.
|
|
|
|
|
/// If an error occurs, the default value is provided instead.
|
|
|
|
|
fn advisory<T: Default>(result: Result<T>) -> T {
|
|
|
|
|
result.unwrap_or_else(|error| {
|
|
|
|
|
warn!("{:#}", error);
|
|
|
|
|
T::default()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-03 17:31:16 -07:00
|
|
|
/// Loads the loader.conf of the partition Sprout was loaded from.
|
|
|
|
|
/// A missing loader.conf has no settings.
|
|
|
|
|
fn load_loader_conf(context: &SproutContext) -> Result<LoaderConf> {
|
|
|
|
|
let Ok(content) = eficore::path::read_file_contents(
|
|
|
|
|
Some(context.root().loaded_image_path()?),
|
|
|
|
|
"\\loader\\loader.conf",
|
|
|
|
|
) else {
|
|
|
|
|
return Ok(LoaderConf::default());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Measure the loader.conf into the TPM, as it changes how Sprout boots.
|
|
|
|
|
PlatformTpm::log_event(
|
|
|
|
|
PlatformTpm::PCR_BOOT_LOADER_CONFIG,
|
|
|
|
|
&content,
|
|
|
|
|
"sprout: loader.conf",
|
|
|
|
|
)
|
|
|
|
|
.context("unable to measure the loader.conf file into the TPM")?;
|
|
|
|
|
|
|
|
|
|
let Ok(content) = String::from_utf8(content) else {
|
|
|
|
|
warn!("ignoring loader.conf that is not valid UTF-8");
|
|
|
|
|
return Ok(LoaderConf::default());
|
|
|
|
|
};
|
|
|
|
|
Ok(LoaderConf::parse(&content))
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-27 15:41:29 -04:00
|
|
|
/// Run Sprout, returning an error if one occurs.
|
|
|
|
|
fn run() -> Result<()> {
|
2025-10-30 22:56:01 -04:00
|
|
|
// For safety reasons, we will note that Secure Boot is in beta on Sprout.
|
2025-10-30 18:57:26 -04:00
|
|
|
if SecureBoot::enabled().context("unable to determine Secure Boot status")? {
|
2025-11-03 15:31:44 -05:00
|
|
|
warn!("Sprout Secure Boot is in beta. Some functionality may not work as expected.");
|
2025-10-30 18:57:26 -04:00
|
|
|
}
|
|
|
|
|
|
2025-10-30 02:36:14 -04:00
|
|
|
// Start the platform timer.
|
|
|
|
|
let timer = PlatformTimer::start();
|
|
|
|
|
|
2025-10-28 21:05:22 -04:00
|
|
|
// Mark the initialization of Sprout in the bootloader interface.
|
2026-09-26 20:14:57 -07:00
|
|
|
advisory(
|
|
|
|
|
BootloaderInterface::mark_init(&timer)
|
|
|
|
|
.context("unable to mark initialization in bootloader interface"),
|
|
|
|
|
);
|
2025-10-28 21:05:22 -04:00
|
|
|
|
2025-10-30 11:47:35 -04:00
|
|
|
// Tell the bootloader interface what firmware we are running on.
|
2026-09-26 20:14:57 -07:00
|
|
|
advisory(
|
|
|
|
|
BootloaderInterface::set_firmware_info()
|
|
|
|
|
.context("unable to set firmware info in bootloader interface"),
|
|
|
|
|
);
|
2025-10-30 11:47:35 -04:00
|
|
|
|
2025-10-30 12:50:36 -04:00
|
|
|
// Tell the bootloader interface what loader is being used.
|
2026-09-26 20:14:57 -07:00
|
|
|
advisory(
|
|
|
|
|
BootloaderInterface::set_loader_info()
|
|
|
|
|
.context("unable to set loader info in bootloader interface"),
|
|
|
|
|
);
|
2025-10-30 12:50:36 -04:00
|
|
|
|
2025-10-31 01:30:07 -04:00
|
|
|
// Acquire the number of active PCR banks on the TPM.
|
|
|
|
|
// If no TPM is available, this will return zero.
|
2026-09-26 20:09:08 -07:00
|
|
|
// This is only reported to the bootloader interface, so a TPM that fails to report its
|
|
|
|
|
// PCR banks is treated as having none, rather than preventing boot.
|
|
|
|
|
let active_pcr_banks = PlatformTpm::active_pcr_banks().unwrap_or_else(|error| {
|
|
|
|
|
warn!("unable to determine the active TPM PCR banks: {}", error);
|
|
|
|
|
0
|
|
|
|
|
});
|
2025-10-31 01:30:07 -04:00
|
|
|
// Tell the bootloader interface what the number of active PCR banks is.
|
2026-09-26 20:14:57 -07:00
|
|
|
advisory(
|
|
|
|
|
BootloaderInterface::set_tpm2_active_pcr_banks(active_pcr_banks)
|
|
|
|
|
.context("unable to set tpm2 active PCR banks in bootloader interface"),
|
|
|
|
|
);
|
2025-10-31 01:30:07 -04:00
|
|
|
|
2025-10-20 18:17:29 -07:00
|
|
|
// Parse the options to the sprout executable.
|
2025-10-21 19:12:16 -07:00
|
|
|
let options = SproutOptions::parse().context("unable to parse options")?;
|
2025-10-20 18:17:29 -07:00
|
|
|
|
2025-10-27 02:38:40 -04:00
|
|
|
// If --autoconfigure is specified, we use a stub configuration.
|
|
|
|
|
let mut config = if options.autoconfigure {
|
|
|
|
|
info!("autoconfiguration enabled, configuration file will be ignored");
|
|
|
|
|
RootConfiguration::default()
|
|
|
|
|
} else {
|
|
|
|
|
// Load the configuration of sprout.
|
|
|
|
|
// At this point, the configuration has been validated and the specified
|
|
|
|
|
// version is checked to ensure compatibility.
|
|
|
|
|
config::loader::load(&options)?
|
|
|
|
|
};
|
2025-10-05 00:09:53 -07:00
|
|
|
|
2025-10-28 21:05:22 -04:00
|
|
|
// Grab the sprout.efi loaded image path.
|
2025-10-19 20:23:55 -07:00
|
|
|
// This is done in a block to ensure the release of the LoadedImageDevicePath protocol.
|
2025-10-28 21:05:22 -04:00
|
|
|
let loaded_image_path = {
|
2025-10-13 00:55:11 -07:00
|
|
|
let current_image_device_path_protocol = uefi::boot::open_protocol_exclusive::<
|
|
|
|
|
LoadedImageDevicePath,
|
|
|
|
|
>(uefi::boot::image_handle())
|
2025-10-14 12:47:33 -07:00
|
|
|
.context("unable to get loaded image device path")?;
|
2025-10-28 21:05:22 -04:00
|
|
|
current_image_device_path_protocol.deref().to_boxed()
|
2025-10-13 00:55:11 -07:00
|
|
|
};
|
|
|
|
|
|
2025-10-28 21:05:22 -04:00
|
|
|
// Grab the partition GUID of the ESP that sprout was loaded from.
|
2026-09-26 20:14:57 -07:00
|
|
|
// This is only reported to the bootloader interface.
|
|
|
|
|
let loaded_image_partition_guid = advisory(
|
2025-11-03 20:47:21 -05:00
|
|
|
eficore::partition::partition_guid(&loaded_image_path, PartitionGuidForm::Partition)
|
2026-09-26 20:14:57 -07:00
|
|
|
.context("unable to retrieve loaded image partition guid"),
|
|
|
|
|
);
|
2025-10-28 21:05:22 -04:00
|
|
|
|
|
|
|
|
// Set the partition GUID of the ESP that sprout was loaded from in the bootloader interface.
|
|
|
|
|
if let Some(loaded_image_partition_guid) = loaded_image_partition_guid {
|
|
|
|
|
// Tell the system about the partition GUID.
|
2026-09-26 20:14:57 -07:00
|
|
|
advisory(
|
|
|
|
|
BootloaderInterface::set_partition_guid(&loaded_image_partition_guid)
|
|
|
|
|
.context("unable to set partition guid in bootloader interface"),
|
|
|
|
|
);
|
2025-10-28 21:05:22 -04:00
|
|
|
}
|
|
|
|
|
|
2025-10-30 12:44:07 -04:00
|
|
|
// Tell the bootloader interface what the loaded image path is.
|
2026-09-26 20:14:57 -07:00
|
|
|
advisory(
|
|
|
|
|
BootloaderInterface::set_loader_path(&loaded_image_path)
|
|
|
|
|
.context("unable to set loader path in bootloader interface"),
|
|
|
|
|
);
|
2025-10-30 12:44:07 -04:00
|
|
|
|
2025-10-28 21:05:22 -04:00
|
|
|
// Create the root context.
|
2025-10-30 02:36:14 -04:00
|
|
|
let mut root = RootContext::new(loaded_image_path, timer, options);
|
2025-10-28 21:05:22 -04:00
|
|
|
|
2025-10-19 20:23:55 -07:00
|
|
|
// Insert the configuration actions into the root context.
|
2025-10-04 23:12:01 -07:00
|
|
|
root.actions_mut().extend(config.actions.clone());
|
|
|
|
|
|
2025-10-19 20:23:55 -07:00
|
|
|
// Create a new sprout context with the root context.
|
2025-10-11 14:35:29 -07:00
|
|
|
let mut context = SproutContext::new(root);
|
2025-10-19 20:23:55 -07:00
|
|
|
|
|
|
|
|
// Insert the configuration values into the sprout context.
|
2025-10-04 23:12:01 -07:00
|
|
|
context.insert(&config.values);
|
2025-10-19 20:23:55 -07:00
|
|
|
|
|
|
|
|
// Freeze the sprout context so it can be shared and cheaply cloned.
|
2025-10-04 23:12:01 -07:00
|
|
|
let context = context.freeze();
|
|
|
|
|
|
2025-10-19 20:23:55 -07:00
|
|
|
// Execute the early phase.
|
2025-10-14 12:47:33 -07:00
|
|
|
phase(context.clone(), &config.phases.early).context("unable to execute early phase")?;
|
2025-10-13 16:23:08 -07:00
|
|
|
|
2025-10-19 20:23:55 -07:00
|
|
|
// Load all configured drivers.
|
2025-10-14 12:47:33 -07:00
|
|
|
drivers::load(context.clone(), &config.drivers).context("unable to load drivers")?;
|
2025-10-12 22:39:56 -07:00
|
|
|
|
2025-10-27 02:38:40 -04:00
|
|
|
// If --autoconfigure is specified or the loaded configuration has autoconfigure enabled,
|
|
|
|
|
// trigger the autoconfiguration mechanism.
|
2025-10-27 17:56:38 -04:00
|
|
|
if context.root().options().autoconfigure || config.options.autoconfigure {
|
2025-10-27 02:38:40 -04:00
|
|
|
autoconfigure::autoconfigure(&mut config).context("unable to autoconfigure")?;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-27 03:37:09 -04:00
|
|
|
// Unload the context so that it can be modified.
|
|
|
|
|
let Some(mut context) = context.unload() else {
|
|
|
|
|
bail!("context safety violation while trying to unload context");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Perform root context modification in a block to release the modification when complete.
|
|
|
|
|
{
|
|
|
|
|
// Modify the root context to include the autoconfigured actions.
|
|
|
|
|
let Some(root) = context.root_mut() else {
|
|
|
|
|
bail!("context safety violation while trying to modify root context");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Extend the root context with the autoconfigured actions.
|
|
|
|
|
root.actions_mut().extend(config.actions);
|
2025-10-27 22:43:37 -04:00
|
|
|
|
|
|
|
|
// Insert any modified root values.
|
|
|
|
|
context.insert(&config.values);
|
2025-10-27 03:37:09 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Refreeze the context to ensure that further operations can share the context.
|
|
|
|
|
let context = context.freeze();
|
|
|
|
|
|
2025-10-19 20:23:55 -07:00
|
|
|
// Run all the extractors declared in the configuration.
|
2025-10-13 00:55:11 -07:00
|
|
|
let mut extracted = BTreeMap::new();
|
|
|
|
|
for (name, extractor) in &config.extractors {
|
|
|
|
|
let value = extractors::extract(context.clone(), extractor)
|
2025-10-14 12:47:33 -07:00
|
|
|
.context(format!("unable to extract value {}", name))?;
|
2025-10-27 00:20:42 -04:00
|
|
|
info!("extracted value {}: {}", name, value);
|
2025-10-13 00:55:11 -07:00
|
|
|
extracted.insert(name.clone(), value);
|
|
|
|
|
}
|
|
|
|
|
let mut context = context.fork();
|
2025-10-19 20:23:55 -07:00
|
|
|
// Insert the extracted values into the sprout context.
|
2025-10-13 00:55:11 -07:00
|
|
|
context.insert(&extracted);
|
|
|
|
|
let context = context.freeze();
|
|
|
|
|
|
2025-10-24 19:28:38 -07:00
|
|
|
// Execute the startup phase.
|
2025-10-14 12:47:33 -07:00
|
|
|
phase(context.clone(), &config.phases.startup).context("unable to execute startup phase")?;
|
2025-10-04 23:12:01 -07:00
|
|
|
|
2025-10-24 16:32:48 -07:00
|
|
|
let mut entries = Vec::new();
|
2025-10-04 23:12:01 -07:00
|
|
|
|
2025-10-19 20:23:55 -07:00
|
|
|
// Insert all the static entries from the configuration into the entry list.
|
2025-10-24 16:32:48 -07:00
|
|
|
for (name, entry) in config.entries {
|
2025-10-19 20:23:55 -07:00
|
|
|
// Associate the main context with the static entry.
|
2025-10-24 16:32:48 -07:00
|
|
|
entries.push(BootableEntry::new(
|
|
|
|
|
name,
|
2025-10-24 17:16:42 -07:00
|
|
|
entry.title.clone(),
|
2025-10-24 16:32:48 -07:00
|
|
|
context.clone(),
|
|
|
|
|
entry,
|
|
|
|
|
));
|
2025-10-04 23:12:01 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-19 20:23:55 -07:00
|
|
|
// Run all the generators declared in the configuration.
|
2025-10-24 16:32:48 -07:00
|
|
|
for (name, generator) in config.generators {
|
2025-10-04 23:12:01 -07:00
|
|
|
let context = context.fork().freeze();
|
|
|
|
|
|
2025-10-19 20:23:55 -07:00
|
|
|
// Add all the entries generated by the generator to the entry list.
|
|
|
|
|
// The generator specifies the context associated with the entry.
|
2026-10-03 15:16:05 -07:00
|
|
|
entries.extend(generators::generate(&name, context, &generator)?);
|
|
|
|
|
}
|
2025-10-28 23:23:12 -04:00
|
|
|
|
2026-10-03 15:16:05 -07:00
|
|
|
// Entry names are used to select entries, so duplicates make the selection ambiguous.
|
|
|
|
|
// This can happen when two generators pin names, like two BLS generators reading the
|
|
|
|
|
// same BLS directory.
|
|
|
|
|
let mut seen_names = BTreeSet::new();
|
|
|
|
|
for entry in &entries {
|
|
|
|
|
if !seen_names.insert(entry.name()) {
|
|
|
|
|
warn!(
|
|
|
|
|
"more than one entry is named {}, selecting it by name is ambiguous",
|
|
|
|
|
entry.name()
|
|
|
|
|
);
|
2025-10-04 23:12:01 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-26 20:18:54 -07:00
|
|
|
// Entries whose context can't be finalized are skipped, as one broken entry
|
|
|
|
|
// should not prevent booting any of the other entries.
|
|
|
|
|
entries.retain_mut(|entry| {
|
2025-10-24 17:16:42 -07:00
|
|
|
let mut context = entry.context().fork();
|
2025-10-19 20:23:55 -07:00
|
|
|
// Insert the values from the entry configuration into the
|
|
|
|
|
// sprout context to use with the entry itself.
|
2025-10-24 16:32:48 -07:00
|
|
|
context.insert(&entry.declaration().values);
|
2026-09-26 20:18:54 -07:00
|
|
|
let context = match context.finalize() {
|
|
|
|
|
Ok(context) => context.freeze(),
|
|
|
|
|
Err(error) => {
|
|
|
|
|
warn!(
|
|
|
|
|
"skipping entry {}, unable to finalize context: {:#}",
|
|
|
|
|
entry.name(),
|
|
|
|
|
error
|
|
|
|
|
);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-10-24 16:32:48 -07:00
|
|
|
// Provide the new context to the bootable entry.
|
|
|
|
|
entry.swap_context(context);
|
2026-09-26 20:15:09 -07:00
|
|
|
// Restamp the title and sort key with any values.
|
2025-10-24 16:32:48 -07:00
|
|
|
entry.restamp_title();
|
2026-09-26 20:15:09 -07:00
|
|
|
entry.restamp_sort_key();
|
2025-10-26 23:59:50 -04:00
|
|
|
|
2026-09-26 20:18:54 -07:00
|
|
|
true
|
|
|
|
|
});
|
2025-10-04 23:12:01 -07:00
|
|
|
|
2025-12-21 14:11:26 -08:00
|
|
|
// Sort the entries by their sort key, finalizing the order to show entries. This happens
|
|
|
|
|
// in reverse order so that entries that would come last show up first in the menu.
|
|
|
|
|
entries.sort_by(|a, b| compare_versions(a.sort_key(), b.sort_key()).reverse());
|
|
|
|
|
|
2025-10-28 21:05:22 -04:00
|
|
|
// Tell the bootloader interface what entries are available.
|
2026-09-26 20:14:57 -07:00
|
|
|
advisory(
|
|
|
|
|
BootloaderInterface::set_entries(entries.iter().map(|entry| entry.name()))
|
|
|
|
|
.context("unable to set entries in bootloader interface"),
|
|
|
|
|
);
|
2025-10-28 21:05:22 -04:00
|
|
|
|
2026-10-03 17:31:16 -07:00
|
|
|
// Acquire the timeouts from the bootloader interface.
|
|
|
|
|
let bootloader_interface_timeouts = advisory(
|
|
|
|
|
BootloaderInterface::get_timeouts().context("unable to get bootloader interface timeouts"),
|
2026-09-26 20:14:57 -07:00
|
|
|
);
|
2025-11-01 17:47:41 -04:00
|
|
|
|
2025-11-01 18:04:06 -04:00
|
|
|
// Acquire the default entry from the bootloader interface.
|
2026-09-26 20:14:57 -07:00
|
|
|
let bootloader_interface_default_entry = advisory(
|
|
|
|
|
BootloaderInterface::get_default_entry()
|
|
|
|
|
.context("unable to get bootloader interface default entry"),
|
|
|
|
|
);
|
2025-11-01 18:04:06 -04:00
|
|
|
|
|
|
|
|
// Acquire the oneshot entry from the bootloader interface.
|
2026-09-26 20:14:57 -07:00
|
|
|
let bootloader_interface_oneshot_entry = advisory(
|
|
|
|
|
BootloaderInterface::get_oneshot_entry()
|
|
|
|
|
.context("unable to get bootloader interface oneshot entry"),
|
|
|
|
|
);
|
2025-11-01 18:04:06 -04:00
|
|
|
|
2026-10-03 17:31:16 -07:00
|
|
|
// Load the loader.conf of the partition Sprout was loaded from.
|
|
|
|
|
let loader_conf = load_loader_conf(&context).context("unable to load loader.conf")?;
|
|
|
|
|
for warning in &loader_conf.warnings {
|
|
|
|
|
warn!("{}", warning);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-26 23:59:50 -04:00
|
|
|
// If --boot is specified, boot that entry immediately.
|
2025-11-01 18:04:06 -04:00
|
|
|
let mut force_boot_entry = context.root().options().boot.clone();
|
2025-10-26 23:59:50 -04:00
|
|
|
// If --force-menu is specified, show the boot menu regardless of the value of --boot.
|
2025-11-01 17:47:41 -04:00
|
|
|
let mut force_boot_menu = context.root().options().force_menu;
|
2025-10-26 23:59:50 -04:00
|
|
|
|
2026-10-03 17:31:16 -07:00
|
|
|
// Pick the menu timeout from the first source that specifies one, in this order:
|
|
|
|
|
// the one-shot timeout, --menu-timeout, the configuration, LoaderConfigTimeout, loader.conf.
|
|
|
|
|
let seconds = |seconds: Option<u64>| {
|
|
|
|
|
seconds.map_or(
|
|
|
|
|
BootloaderInterfaceTimeout::Unspecified,
|
|
|
|
|
BootloaderInterfaceTimeout::Timeout,
|
|
|
|
|
)
|
|
|
|
|
};
|
|
|
|
|
let loader_conf_timeout = match loader_conf.timeout {
|
|
|
|
|
None => BootloaderInterfaceTimeout::Unspecified,
|
|
|
|
|
Some(LoaderTimeout::Seconds(0)) | Some(LoaderTimeout::Hidden) => {
|
|
|
|
|
BootloaderInterfaceTimeout::MenuHidden
|
|
|
|
|
}
|
|
|
|
|
Some(LoaderTimeout::Seconds(timeout)) => BootloaderInterfaceTimeout::Timeout(timeout),
|
|
|
|
|
Some(LoaderTimeout::Disabled) => BootloaderInterfaceTimeout::MenuDisabled,
|
|
|
|
|
Some(LoaderTimeout::Force) => BootloaderInterfaceTimeout::MenuForce,
|
|
|
|
|
};
|
|
|
|
|
let timeout = [
|
|
|
|
|
bootloader_interface_timeouts.oneshot,
|
|
|
|
|
seconds(context.root().options().menu_timeout),
|
|
|
|
|
seconds(config.options.menu_timeout),
|
|
|
|
|
bootloader_interface_timeouts.direct,
|
|
|
|
|
loader_conf_timeout,
|
|
|
|
|
]
|
|
|
|
|
.into_iter()
|
|
|
|
|
.find(|timeout| !matches!(timeout, BootloaderInterfaceTimeout::Unspecified))
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
|
|
|
|
// The menu timeout in seconds, if no source specified one.
|
|
|
|
|
let mut menu_timeout = DEFAULT_MENU_TIMEOUT_SECONDS;
|
2025-11-01 17:47:41 -04:00
|
|
|
|
2026-09-26 20:12:42 -07:00
|
|
|
// Whether the boot menu should wait for the user, instead of counting down.
|
|
|
|
|
let mut wait_for_user = false;
|
|
|
|
|
|
2026-10-03 17:31:16 -07:00
|
|
|
// Apply the chosen timeout.
|
|
|
|
|
match timeout {
|
2025-11-01 17:47:41 -04:00
|
|
|
BootloaderInterfaceTimeout::MenuForce => {
|
2026-09-26 20:12:42 -07:00
|
|
|
// Force the boot menu, and wait for the user to choose an entry.
|
2025-11-01 17:47:41 -04:00
|
|
|
force_boot_menu = true;
|
2026-09-26 20:12:42 -07:00
|
|
|
wait_for_user = true;
|
2025-11-01 17:47:41 -04:00
|
|
|
}
|
|
|
|
|
|
2026-09-26 20:13:18 -07:00
|
|
|
BootloaderInterfaceTimeout::MenuForceTimeout(timeout) => {
|
|
|
|
|
// Force the boot menu with the specified timeout.
|
|
|
|
|
force_boot_menu = true;
|
|
|
|
|
menu_timeout = timeout;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 18:38:08 -04:00
|
|
|
BootloaderInterfaceTimeout::MenuHidden | BootloaderInterfaceTimeout::MenuDisabled => {
|
2025-11-01 17:47:41 -04:00
|
|
|
// Hide the boot menu by setting the timeout to zero.
|
|
|
|
|
menu_timeout = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BootloaderInterfaceTimeout::Timeout(timeout) => {
|
|
|
|
|
// Configure the timeout to the specified value.
|
|
|
|
|
menu_timeout = timeout;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BootloaderInterfaceTimeout::Unspecified => {
|
|
|
|
|
// Do nothing.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-03 17:31:16 -07:00
|
|
|
// The loader.conf default entry, where @saved selects the entry of the previous boot.
|
|
|
|
|
let use_saved_entry = loader_conf.default.as_deref() == Some("@saved");
|
|
|
|
|
let loader_conf_default_entry = if use_saved_entry {
|
|
|
|
|
advisory(
|
|
|
|
|
BootloaderInterface::get_last_booted_entry()
|
|
|
|
|
.context("unable to get last booted entry from bootloader interface"),
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
loader_conf.default.clone()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Pick the default entry from the first source that matches an entry, in this order:
|
|
|
|
|
// the configuration, LoaderEntryDefault, loader.conf. A source that matches no entry,
|
|
|
|
|
// such as when the entry was removed, is ignored so that the next source is used.
|
|
|
|
|
for (source, pattern, id_only) in [
|
|
|
|
|
("configuration", config.options.default_entry.clone(), false),
|
|
|
|
|
(
|
|
|
|
|
"bootloader interface",
|
|
|
|
|
bootloader_interface_default_entry,
|
|
|
|
|
false,
|
|
|
|
|
),
|
|
|
|
|
("loader.conf", loader_conf_default_entry, true),
|
|
|
|
|
] {
|
|
|
|
|
let Some(pattern) = pattern else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
let matches = |entry: &BootableEntry| {
|
|
|
|
|
if id_only {
|
|
|
|
|
entry.is_match_id(&pattern)
|
|
|
|
|
} else {
|
|
|
|
|
entry.is_match(&pattern)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if entries.iter().any(matches) {
|
|
|
|
|
// Mark the matching entries as the default and unmark all the others.
|
2026-09-26 20:13:42 -07:00
|
|
|
for entry in &mut entries {
|
2026-10-03 17:31:16 -07:00
|
|
|
if matches(entry) {
|
2026-09-26 20:13:42 -07:00
|
|
|
entry.mark_default();
|
|
|
|
|
} else {
|
|
|
|
|
entry.unmark_default();
|
|
|
|
|
}
|
2025-11-01 18:04:06 -04:00
|
|
|
}
|
2026-10-03 17:31:16 -07:00
|
|
|
break;
|
2025-11-01 18:04:06 -04:00
|
|
|
}
|
2026-10-03 17:31:16 -07:00
|
|
|
warn!(
|
|
|
|
|
"ignoring {} default entry '{}': no matching entry",
|
|
|
|
|
source, pattern
|
|
|
|
|
);
|
2025-11-01 18:04:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply bootloader interface oneshot entry settings.
|
|
|
|
|
// If set, we will force booting the oneshot entry.
|
|
|
|
|
if let Some(ref bootloader_interface_oneshot_entry) = bootloader_interface_oneshot_entry {
|
|
|
|
|
force_boot_entry = Some(bootloader_interface_oneshot_entry.clone());
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-03 17:19:27 -07:00
|
|
|
// Entries that have no boot counter tries left are never picked automatically.
|
|
|
|
|
// If no entries are the default, pick the first usable entry as the default entry.
|
|
|
|
|
let default_flags = edera_sprout_bls::resolve_default_flags(
|
|
|
|
|
&entries
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|entry| (entry.is_default(), entry.is_bad()))
|
|
|
|
|
.collect::<Vec<_>>(),
|
|
|
|
|
);
|
|
|
|
|
for (entry, default) in entries.iter_mut().zip(default_flags) {
|
|
|
|
|
if default {
|
|
|
|
|
entry.mark_default();
|
|
|
|
|
} else {
|
|
|
|
|
entry.unmark_default();
|
|
|
|
|
}
|
2025-11-01 18:04:06 -04:00
|
|
|
}
|
|
|
|
|
|
2025-11-01 17:47:41 -04:00
|
|
|
// Convert the menu timeout to a duration.
|
2026-09-26 20:12:42 -07:00
|
|
|
// A zero timeout boots without showing the menu, so a forced menu with a zero timeout
|
|
|
|
|
// waits for the user instead.
|
|
|
|
|
let menu_timeout = if wait_for_user || (force_boot_menu && menu_timeout == 0) {
|
|
|
|
|
None
|
|
|
|
|
} else {
|
|
|
|
|
Some(Duration::from_secs(menu_timeout))
|
|
|
|
|
};
|
2025-10-24 21:19:38 -07:00
|
|
|
|
2026-09-26 19:31:50 -07:00
|
|
|
// Determine the menu style based on the options or configuration.
|
|
|
|
|
// We prefer the options over the configuration to allow for overriding.
|
|
|
|
|
let menu_style = context
|
|
|
|
|
.root()
|
|
|
|
|
.options()
|
|
|
|
|
.menu_style
|
|
|
|
|
.unwrap_or(config.options.menu_style);
|
|
|
|
|
|
2026-09-26 20:14:01 -07:00
|
|
|
// Find the forced boot entry, unless the boot menu is forced.
|
|
|
|
|
// If the forced boot entry can't be found, such as when it was removed,
|
|
|
|
|
// the boot menu is used instead.
|
|
|
|
|
let forced_entry = force_boot_entry
|
|
|
|
|
.as_ref()
|
|
|
|
|
.filter(|_| !force_boot_menu)
|
|
|
|
|
.and_then(|force_boot_entry| {
|
|
|
|
|
let entry = BootableEntry::find(force_boot_entry, entries.iter());
|
|
|
|
|
if entry.is_none() {
|
|
|
|
|
warn!(
|
|
|
|
|
"unable to find entry '{}', using the boot menu",
|
|
|
|
|
force_boot_entry
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
entry
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Use the forced boot entry if possible, otherwise pick an entry using a boot menu.
|
|
|
|
|
let entry = match forced_entry {
|
|
|
|
|
Some(entry) => entry,
|
2025-10-26 23:59:50 -04:00
|
|
|
// Delegate to the menu to select an entry to boot.
|
2026-09-26 20:14:01 -07:00
|
|
|
None => menu::select(&timer, menu_timeout, menu_style, &entries)
|
|
|
|
|
.context("unable to select entry via boot menu")?,
|
2025-10-19 02:25:55 -07:00
|
|
|
};
|
2025-10-04 23:33:23 -07:00
|
|
|
|
2025-10-28 21:05:22 -04:00
|
|
|
// Tell the bootloader interface what the selected entry is.
|
2026-09-26 20:14:57 -07:00
|
|
|
advisory(
|
|
|
|
|
BootloaderInterface::set_selected_entry(entry.name().to_string())
|
|
|
|
|
.context("unable to set selected entry in bootloader interface"),
|
|
|
|
|
);
|
2025-10-28 21:05:22 -04:00
|
|
|
|
2026-10-03 17:31:16 -07:00
|
|
|
// Save the selected entry for the next boot when loader.conf asks for it.
|
|
|
|
|
if use_saved_entry {
|
|
|
|
|
advisory(
|
|
|
|
|
BootloaderInterface::set_last_booted_entry(entry.name())
|
|
|
|
|
.context("unable to save last booted entry in bootloader interface"),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-26 18:19:29 -07:00
|
|
|
// Execute the late phase, now that the entry is chosen but before its actions are executed.
|
|
|
|
|
phase(context.clone(), &config.phases.late).context("unable to execute late phase")?;
|
|
|
|
|
|
2026-10-03 17:19:27 -07:00
|
|
|
// Consume a try from the boot counter of the selected entry.
|
|
|
|
|
// Failing to do so must not prevent the entry from booting.
|
|
|
|
|
if let Some(target) = entry.boot_counter()
|
|
|
|
|
&& !target.counter.is_bad()
|
|
|
|
|
{
|
|
|
|
|
match target.consume() {
|
2026-10-03 17:31:16 -07:00
|
|
|
Ok(path) => {
|
|
|
|
|
info!("updated boot counter of entry {}: {}", entry.name(), path);
|
|
|
|
|
// Tell the system where the counter is, so it can mark the boot as good.
|
|
|
|
|
let path = format!(
|
|
|
|
|
"/{}",
|
|
|
|
|
path.to_string().replace('\\', "/").trim_start_matches('/')
|
|
|
|
|
);
|
|
|
|
|
advisory(
|
|
|
|
|
BootloaderInterface::set_boot_count_path(&path)
|
|
|
|
|
.context("unable to set boot count path in bootloader interface"),
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-10-03 17:19:27 -07:00
|
|
|
Err(error) => warn!(
|
|
|
|
|
"unable to update boot counter of entry {}: {:#}",
|
|
|
|
|
entry.name(),
|
|
|
|
|
error
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-19 20:23:55 -07:00
|
|
|
// Execute all the actions for the selected entry.
|
2025-10-24 16:32:48 -07:00
|
|
|
for action in &entry.declaration().actions {
|
2025-10-24 17:16:42 -07:00
|
|
|
let action = entry.context().stamp(action);
|
|
|
|
|
actions::execute(entry.context().clone(), &action)
|
2025-10-14 12:47:33 -07:00
|
|
|
.context(format!("unable to execute action '{}'", action))?;
|
2025-10-01 21:30:43 -07:00
|
|
|
}
|
2025-10-19 20:23:55 -07:00
|
|
|
|
2025-10-27 15:41:29 -04:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 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.
|
2025-11-03 02:04:21 -05:00
|
|
|
#[entry]
|
|
|
|
|
fn efi_main() -> Status {
|
2025-10-27 15:41:29 -04:00
|
|
|
// Initialize the basic UEFI environment.
|
2025-11-03 02:04:21 -05:00
|
|
|
// If initialization fails, we will return ABORTED.
|
2025-11-03 14:45:48 -05:00
|
|
|
// NOTE: This function will also initialize the logger.
|
|
|
|
|
// The logger will panic if it is unable to initialize.
|
|
|
|
|
// It is guaranteed that if this returns, the logger is initialized.
|
2025-11-03 02:04:21 -05:00
|
|
|
if let Err(error) = setup::init() {
|
|
|
|
|
error!("unable to initialize environment: {}", error);
|
|
|
|
|
return Status::ABORTED;
|
|
|
|
|
}
|
2025-10-27 15:41:29 -04:00
|
|
|
|
|
|
|
|
// Run Sprout, then handle the error.
|
|
|
|
|
let result = run();
|
|
|
|
|
if let Err(ref error) = result {
|
|
|
|
|
// Print an error trace.
|
2025-11-06 13:04:05 -05:00
|
|
|
error!("sprout encountered an error: {}", error);
|
2025-10-27 15:41:29 -04:00
|
|
|
for (index, stack) in error.chain().enumerate() {
|
|
|
|
|
error!("[{}]: {}", index, stack);
|
|
|
|
|
}
|
2025-10-30 15:26:44 -04:00
|
|
|
// Sleep to allow the user to read the error.
|
|
|
|
|
uefi::boot::stall(DELAY_ON_ERROR);
|
2025-11-03 02:04:21 -05:00
|
|
|
return Status::ABORTED;
|
2025-10-27 15:41:29 -04:00
|
|
|
}
|
|
|
|
|
|
2025-10-19 20:23:55 -07:00
|
|
|
// Sprout doesn't necessarily guarantee anything was booted.
|
|
|
|
|
// If we reach here, we will exit back to whoever called us.
|
2025-11-03 02:04:21 -05:00
|
|
|
Status::SUCCESS
|
2025-10-01 16:45:04 -07:00
|
|
|
}
|