2025-10-20 18:17:29 -07:00
|
|
|
use crate::options::SproutOptions;
|
2026-09-26 20:22:54 -07:00
|
|
|
use alloc::format;
|
|
|
|
|
use alloc::string::{String, ToString};
|
2025-11-03 02:04:21 -05:00
|
|
|
use alloc::vec::Vec;
|
2025-10-19 20:30:09 -07:00
|
|
|
use anyhow::{Context, Result, bail};
|
2026-09-26 20:22:54 -07:00
|
|
|
use core::fmt::Display;
|
2025-11-03 02:04:21 -05:00
|
|
|
use core::ops::Deref;
|
2025-11-02 23:28:31 -05:00
|
|
|
use edera_sprout_config::{RootConfiguration, latest_version};
|
2025-11-03 20:47:21 -05:00
|
|
|
use eficore::platform::tpm::PlatformTpm;
|
2026-09-26 20:22:54 -07:00
|
|
|
use log::{info, warn};
|
|
|
|
|
use serde_ignored::Path;
|
2025-10-19 20:23:55 -07:00
|
|
|
use toml::Value;
|
|
|
|
|
use uefi::proto::device_path::LoadedImageDevicePath;
|
|
|
|
|
|
2025-10-20 18:17:29 -07:00
|
|
|
/// Loads the raw configuration from the sprout config file as data.
|
|
|
|
|
fn load_raw_config(options: &SproutOptions) -> Result<Vec<u8>> {
|
2025-10-20 00:06:46 -07:00
|
|
|
// Open the LoadedImageDevicePath protocol to get the path to the current image.
|
2025-10-19 20:23:55 -07:00
|
|
|
let current_image_device_path_protocol =
|
|
|
|
|
uefi::boot::open_protocol_exclusive::<LoadedImageDevicePath>(uefi::boot::image_handle())
|
|
|
|
|
.context("unable to get loaded image device path")?;
|
2025-10-20 00:06:46 -07:00
|
|
|
// Acquire the device path as a boxed device path.
|
2025-10-19 20:23:55 -07:00
|
|
|
let path = current_image_device_path_protocol.deref().to_boxed();
|
2025-10-20 18:17:29 -07:00
|
|
|
|
2025-10-27 00:20:42 -04:00
|
|
|
info!("configuration file: {}", options.config);
|
2025-10-20 18:17:29 -07:00
|
|
|
|
|
|
|
|
// Read the contents of the sprout config file.
|
2025-11-03 20:47:21 -05:00
|
|
|
let content = eficore::path::read_file_contents(Some(&path), &options.config)
|
2025-10-20 18:17:29 -07:00
|
|
|
.context("unable to read sprout config file")?;
|
2025-10-31 02:35:58 -04:00
|
|
|
|
|
|
|
|
// Measure the sprout.toml into the TPM, if needed and possible.
|
2025-10-31 02:45:15 -04:00
|
|
|
PlatformTpm::log_event(
|
|
|
|
|
PlatformTpm::PCR_BOOT_LOADER_CONFIG,
|
|
|
|
|
&content,
|
|
|
|
|
"sprout: configuration file",
|
|
|
|
|
)
|
|
|
|
|
.context("unable to measure the sprout.toml file into the TPM")?;
|
2025-10-31 02:35:58 -04:00
|
|
|
|
2025-10-20 18:17:29 -07:00
|
|
|
// Return the contents of the sprout config file.
|
2025-10-19 20:23:55 -07:00
|
|
|
Ok(content)
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-26 20:22:54 -07:00
|
|
|
/// Format the configuration key `path` with dots, like "actions.linux.chainload.path".
|
|
|
|
|
fn key_path(path: &Path) -> String {
|
|
|
|
|
// Joins the `parent` path and the `key` with a dot, unless the parent is the root.
|
|
|
|
|
fn join(parent: &Path, key: impl Display) -> String {
|
|
|
|
|
match parent {
|
|
|
|
|
Path::Root => key.to_string(),
|
|
|
|
|
parent => format!("{}.{}", key_path(parent), key),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match path {
|
|
|
|
|
Path::Root => String::new(),
|
|
|
|
|
Path::Seq { parent, index } => join(parent, index),
|
|
|
|
|
Path::Map { parent, key } => join(parent, key),
|
|
|
|
|
// Optional values and wrapper types don't add a key of their own.
|
|
|
|
|
Path::Some { parent }
|
|
|
|
|
| Path::NewtypeStruct { parent }
|
|
|
|
|
| Path::NewtypeVariant { parent } => key_path(parent),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Warn about each declaration in `config` that configures more than one kind.
|
|
|
|
|
/// For example, an action with both chainload and print configured only runs chainload.
|
|
|
|
|
fn warn_multiple_kinds(config: &RootConfiguration) {
|
|
|
|
|
for (name, action) in &config.actions {
|
|
|
|
|
let kinds = [
|
|
|
|
|
action.chainload.is_some(),
|
|
|
|
|
action.print.is_some(),
|
|
|
|
|
action.edera.is_some(),
|
|
|
|
|
];
|
|
|
|
|
if kinds.iter().filter(|kind| **kind).count() > 1 {
|
|
|
|
|
warn!(
|
|
|
|
|
"action {} configures more than one action, only one is used",
|
|
|
|
|
name
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (name, generator) in &config.generators {
|
|
|
|
|
let kinds = [
|
|
|
|
|
generator.matrix.is_some(),
|
|
|
|
|
generator.bls.is_some(),
|
|
|
|
|
generator.list.is_some(),
|
|
|
|
|
];
|
|
|
|
|
if kinds.iter().filter(|kind| **kind).count() > 1 {
|
|
|
|
|
warn!(
|
|
|
|
|
"generator {} configures more than one generator, only one is used",
|
|
|
|
|
name
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Loads the [RootConfiguration] for Sprout.
|
2025-10-20 18:17:29 -07:00
|
|
|
pub fn load(options: &SproutOptions) -> Result<RootConfiguration> {
|
|
|
|
|
// Load the raw configuration from the sprout config file.
|
|
|
|
|
let content = load_raw_config(options)?;
|
2025-10-20 00:06:46 -07:00
|
|
|
// Parse the raw configuration into a toml::Value which can represent any TOML file.
|
2025-10-20 18:17:29 -07:00
|
|
|
let value: Value = toml::from_slice(&content).context("unable to parse sprout config file")?;
|
2025-10-19 20:23:55 -07:00
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
// Check the version of the configuration without parsing the full configuration.
|
2025-10-19 20:23:55 -07:00
|
|
|
let version = value
|
|
|
|
|
.get("version")
|
|
|
|
|
.cloned()
|
|
|
|
|
.unwrap_or_else(|| Value::Integer(latest_version() as i64));
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
// Parse the version into an u32.
|
2025-10-19 20:23:55 -07:00
|
|
|
let version: u32 = version
|
|
|
|
|
.try_into()
|
|
|
|
|
.context("unable to get configuration version")?;
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
// Check if the version is supported.
|
2025-10-19 20:23:55 -07:00
|
|
|
if version != latest_version() {
|
|
|
|
|
bail!("unsupported configuration version: {}", version);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
// If the version is supported, parse the full configuration.
|
2026-09-26 20:22:54 -07:00
|
|
|
// Keys that aren't part of the configuration, such as misspelled keys, are ignored.
|
|
|
|
|
// Warn about each one, as the configuration may not do what was intended.
|
|
|
|
|
let config: RootConfiguration = serde_ignored::deserialize(value, |path| {
|
|
|
|
|
warn!("ignoring unknown configuration key: {}", key_path(&path));
|
|
|
|
|
})
|
|
|
|
|
.context("unable to parse sprout.toml file")?;
|
|
|
|
|
|
|
|
|
|
// Warn about declarations that configure more than one kind, as only one is used.
|
|
|
|
|
warn_multiple_kinds(&config);
|
2025-10-20 00:06:46 -07:00
|
|
|
|
|
|
|
|
// Return the parsed configuration.
|
2025-10-19 20:23:55 -07:00
|
|
|
Ok(config)
|
|
|
|
|
}
|