chore(crates): introduce new config crate for sprout configuration

This commit is contained in:
2025-11-02 23:28:31 -05:00
parent ccc75a2e14
commit ed3bfb77c4
39 changed files with 452 additions and 379 deletions

9
Cargo.lock generated
View File

@@ -70,15 +70,22 @@ version = "0.0.17"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags", "bitflags",
"edera-sprout-config",
"hex", "hex",
"log", "log",
"serde",
"sha2", "sha2",
"toml", "toml",
"uefi", "uefi",
"uefi-raw", "uefi-raw",
] ]
[[package]]
name = "edera-sprout-config"
version = "0.0.17"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"

View File

@@ -1,5 +1,6 @@
[workspace] [workspace]
members = [ members = [
"crates/config",
"crates/sprout", "crates/sprout",
] ]
resolver = "3" resolver = "3"

15
crates/config/Cargo.toml Normal file
View File

@@ -0,0 +1,15 @@
[package]
name = "edera-sprout-config"
description = "Sprout Configuration"
license.workspace = true
version.workspace = true
homepage.workspace = true
repository.workspace = true
edition.workspace = true
[dependencies.serde]
workspace = true
features = ["derive"]
[lib]
name = "edera_sprout_config"

View File

@@ -0,0 +1,32 @@
use serde::{Deserialize, Serialize};
/// Configuration for the chainload action.
pub mod chainload;
/// Configuration for the edera action.
pub mod edera;
/// Configuration for the print action.
pub mod print;
/// Declares an action that sprout can execute.
/// Actions allow configuring sprout's internal runtime mechanisms with values
/// that you can specify via other concepts.
///
/// Actions are the main work that Sprout gets done, like booting Linux.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct ActionDeclaration {
/// Chainload to another EFI application.
/// This allows you to load any EFI application, either to boot an operating system
/// or to perform more EFI actions and return to sprout.
#[serde(default)]
pub chainload: Option<chainload::ChainloadConfiguration>,
/// Print a string to the EFI console.
#[serde(default)]
pub print: Option<print::PrintConfiguration>,
/// Boot the Edera hypervisor and the root operating system.
/// This action is an extension on top of the Xen EFI stub that
/// is specific to Edera.
#[serde(default, rename = "edera")]
pub edera: Option<edera::EderaConfiguration>,
}

View File

@@ -0,0 +1,19 @@
use serde::{Deserialize, Serialize};
/// The configuration of the chainload action.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct ChainloadConfiguration {
/// The path to the image to chainload.
/// This can be a Linux EFI stub (vmlinuz usually) or a standard EFI executable.
pub path: String,
/// The options to pass to the image.
/// The options are concatenated by a space and then passed to the EFI application.
#[serde(default)]
pub options: Vec<String>,
/// An optional path to a Linux initrd.
/// This uses the [LINUX_EFI_INITRD_MEDIA_GUID] mechanism to load the initrd into the EFI stack.
/// For Linux, you can also use initrd=\path\to\initrd as an option, but this option is
/// generally better and safer as it can support additional load options in the future.
#[serde(default, rename = "linux-initrd")]
pub linux_initrd: Option<String>,
}

View File

@@ -0,0 +1,21 @@
use serde::{Deserialize, Serialize};
/// The configuration of the edera action which boots the Edera hypervisor.
/// Edera is based on Xen but modified significantly with a Rust stack.
/// Sprout is a component of the Edera stack and provides the boot functionality of Xen.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct EderaConfiguration {
/// The path to the Xen hypervisor EFI image.
pub xen: String,
/// The path to the kernel to boot for dom0.
pub kernel: String,
/// The path to the initrd to load for dom0.
#[serde(default)]
pub initrd: Option<String>,
/// The options to pass to the kernel.
#[serde(default, rename = "kernel-options")]
pub kernel_options: Vec<String>,
/// The options to pass to the Xen hypervisor.
#[serde(default, rename = "xen-options")]
pub xen_options: Vec<String>,
}

View File

@@ -0,0 +1,9 @@
use serde::{Deserialize, Serialize};
/// The configuration of the print action.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct PrintConfiguration {
/// The text to print to the console.
#[serde(default)]
pub text: String,
}

View File

@@ -0,0 +1,12 @@
use serde::{Deserialize, Serialize};
/// Declares a driver configuration.
/// Drivers allow extending the functionality of Sprout.
/// Drivers are loaded at runtime and can provide extra functionality like filesystem support.
/// Drivers are loaded by their name, which is used to reference them in other concepts.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct DriverDeclaration {
/// The filesystem path to the driver.
/// This file should be an EFI executable that can be located and executed.
pub path: String,
}

View File

@@ -0,0 +1,19 @@
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
/// Declares a boot entry to display in the boot menu.
///
/// Entries are the user-facing concept of Sprout, making it possible
/// to run a set of actions with a specific context.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct EntryDeclaration {
/// The title of the entry which will be display in the boot menu.
/// This is the pre-stamped value.
pub title: String,
/// The actions to run when the entry is selected.
#[serde(default)]
pub actions: Vec<String>,
/// The values to insert into the context when the entry is selected.
#[serde(default)]
pub values: BTreeMap<String, String>,
}

View File

@@ -0,0 +1,18 @@
use crate::extractors::filesystem_device_match::FilesystemDeviceMatchExtractor;
use serde::{Deserialize, Serialize};
/// Configuration for the filesystem-device-match extractor.
pub mod filesystem_device_match;
/// Declares an extractor configuration.
/// Extractors allow calculating values at runtime
/// using built-in sprout modules.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct ExtractorDeclaration {
/// The filesystem device match extractor.
/// This extractor finds a filesystem using some search criteria and returns
/// the device root path that can concatenated with subpaths to access files
/// on a particular filesystem.
#[serde(default, rename = "filesystem-device-match")]
pub filesystem_device_match: Option<FilesystemDeviceMatchExtractor>,
}

View File

@@ -0,0 +1,29 @@
use serde::{Deserialize, Serialize};
/// The filesystem device match extractor.
/// This extractor finds a filesystem using some search criteria and returns
/// the device root path that can concatenated with subpaths to access files
/// on a particular filesystem.
/// The fallback value can be used to provide a value if no match is found.
///
/// This extractor requires all the criteria to match. If no criteria is provided,
/// an error is returned.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct FilesystemDeviceMatchExtractor {
/// Matches a filesystem that has the specified label.
#[serde(default, rename = "has-label")]
pub has_label: Option<String>,
/// Matches a filesystem that has the specified item.
/// An item is either a directory or file.
#[serde(default, rename = "has-item")]
pub has_item: Option<String>,
/// Matches a filesystem that has the specified partition UUID.
#[serde(default, rename = "has-partition-uuid")]
pub has_partition_uuid: Option<String>,
/// Matches a filesystem that has the specified partition type UUID.
#[serde(default, rename = "has-partition-type-uuid")]
pub has_partition_type_uuid: Option<String>,
/// The fallback value to use if no filesystem matches the criteria.
#[serde(default)]
pub fallback: Option<String>,
}

View File

@@ -0,0 +1,40 @@
use crate::generators::bls::BlsConfiguration;
use crate::generators::list::ListConfiguration;
use crate::generators::matrix::MatrixConfiguration;
use serde::{Deserialize, Serialize};
/// Configuration for the BLS generator.
pub mod bls;
/// Configuration for the list generator.
pub mod list;
/// Configuration for the matrix generator.
pub mod matrix;
/// Declares a generator configuration.
/// Generators allow generating entries at runtime based on a set of data.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct GeneratorDeclaration {
/// Matrix generator configuration.
/// Matrix allows you to specify multiple value-key values as arrays.
/// This allows multiplying the number of entries by any number of possible
/// configuration options. For example,
/// data.x = ["a", "b"]
/// data.y = ["c", "d"]
/// would generate an entry for each of these combinations:
/// x = a, y = c
/// x = a, y = d
/// x = b, y = c
/// x = b, y = d
#[serde(default)]
pub matrix: Option<MatrixConfiguration>,
/// BLS generator configuration.
/// BLS allows you to pass a filesystem path that contains a set of BLS entries.
/// It will generate a sprout entry for every supported BLS entry.
#[serde(default)]
pub bls: Option<BlsConfiguration>,
/// List generator configuration.
/// Allows you to specify a list of values to generate an entry from.
pub list: Option<ListConfiguration>,
}

View File

@@ -0,0 +1,21 @@
use crate::entries::EntryDeclaration;
use serde::{Deserialize, Serialize};
/// The default path to the BLS directory.
const BLS_TEMPLATE_PATH: &str = "\\loader";
/// The configuration of the BLS generator.
/// The BLS uses the Bootloader Specification to produce
/// entries from an input template.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct BlsConfiguration {
/// The entry to use for as a template.
pub entry: EntryDeclaration,
/// The path to the BLS directory.
#[serde(default = "default_bls_path")]
pub path: String,
}
fn default_bls_path() -> String {
BLS_TEMPLATE_PATH.to_string()
}

View File

@@ -0,0 +1,16 @@
use crate::entries::EntryDeclaration;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
/// List generator configuration.
/// The list generator produces multiple entries based
/// on a set of input maps.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct ListConfiguration {
/// The template entry to use for each generated entry.
#[serde(default)]
pub entry: EntryDeclaration,
/// The values to use as the input for the matrix.
#[serde(default)]
pub values: Vec<BTreeMap<String, String>>,
}

View File

@@ -0,0 +1,16 @@
use crate::entries::EntryDeclaration;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
/// Matrix generator configuration.
/// The matrix generator produces multiple entries based
/// on input values multiplicatively.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct MatrixConfiguration {
/// The template entry to use for each generated entry.
#[serde(default)]
pub entry: EntryDeclaration,
/// The values to use as the input for the matrix.
#[serde(default)]
pub values: BTreeMap<String, Vec<String>>,
}

97
crates/config/src/lib.rs Normal file
View File

@@ -0,0 +1,97 @@
//! Sprout configuration descriptions.
//! This crate provides all the configuration structures for Sprout.
use crate::actions::ActionDeclaration;
use crate::drivers::DriverDeclaration;
use crate::entries::EntryDeclaration;
use crate::extractors::ExtractorDeclaration;
use crate::generators::GeneratorDeclaration;
use crate::phases::PhasesConfiguration;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
pub mod actions;
pub mod drivers;
pub mod entries;
pub mod extractors;
pub mod generators;
pub mod phases;
/// This is the latest version of the sprout configuration format.
/// This must be incremented when the configuration breaks compatibility.
pub const LATEST_VERSION: u32 = 1;
/// The default timeout for the boot menu in seconds.
pub const DEFAULT_MENU_TIMEOUT_SECONDS: u64 = 10;
/// The Sprout configuration format.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct RootConfiguration {
/// The version of the configuration. This should always be declared
/// and be the latest version that is supported. If not specified, it is assumed
/// the configuration is the latest version.
#[serde(default = "latest_version")]
pub version: u32,
/// Default options for Sprout.
#[serde(default)]
pub options: OptionsConfiguration,
/// Values to be inserted into the root sprout context.
#[serde(default)]
pub values: BTreeMap<String, String>,
/// Drivers to load.
/// These drivers provide extra functionality like filesystem support to Sprout.
/// Each driver has a name which uniquely identifies it inside Sprout.
#[serde(default)]
pub drivers: BTreeMap<String, DriverDeclaration>,
/// Declares the extractors that add values to the sprout context that are calculated
/// at runtime. Each extractor has a name which corresponds to the value it will set
/// inside the sprout context.
#[serde(default)]
pub extractors: BTreeMap<String, ExtractorDeclaration>,
/// Declares the actions that can execute operations for sprout.
/// Actions are executable modules in sprout that take in specific structured values.
/// Actions are responsible for ensuring that passed strings are stamped to replace values
/// at runtime.
/// Each action has a name that can be referenced by other base concepts like entries.
#[serde(default)]
pub actions: BTreeMap<String, ActionDeclaration>,
/// Declares the entries that are displayed on the boot menu. These entries are static
/// but can still use values from the sprout context.
#[serde(default)]
pub entries: BTreeMap<String, EntryDeclaration>,
/// Declares the generators that are used to generate entries at runtime.
/// Each generator has its own logic for generating entries, but generally they intake
/// a template entry and stamp that template entry over some values determined at runtime.
/// Each generator has an associated name used to differentiate it across sprout.
#[serde(default)]
pub generators: BTreeMap<String, GeneratorDeclaration>,
/// Configures the various phases of sprout. This allows you to hook into specific parts
/// of the boot process to execute actions, for example, you can show a boot splash during
/// the early phase.
#[serde(default)]
pub phases: PhasesConfiguration,
}
/// Options configuration for Sprout, used when the corresponding options are not specified.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct OptionsConfiguration {
/// The entry to boot without showing the boot menu.
/// If not specified, a boot menu is shown.
#[serde(rename = "default-entry", default)]
pub default_entry: Option<String>,
/// The timeout of the boot menu.
#[serde(rename = "menu-timeout", default = "default_menu_timeout")]
pub menu_timeout: u64,
/// Enables autoconfiguration of Sprout based on the environment.
#[serde(default)]
pub autoconfigure: bool,
}
/// Get the latest version of the Sprout configuration format.
pub fn latest_version() -> u32 {
LATEST_VERSION
}
fn default_menu_timeout() -> u64 {
DEFAULT_MENU_TIMEOUT_SECONDS
}

View File

@@ -0,0 +1,30 @@
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
/// Configures the various phases of the boot process.
/// This allows hooking various phases to run actions.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct PhasesConfiguration {
/// The early phase is run before drivers are loaded.
#[serde(default)]
pub early: Vec<PhaseConfiguration>,
/// The startup phase is run after drivers are loaded, but before entries are displayed.
#[serde(default)]
pub startup: Vec<PhaseConfiguration>,
/// The late phase is run after the entry is chosen, but before the actions are executed.
#[serde(default)]
pub late: Vec<PhaseConfiguration>,
}
/// Configures a single phase of the boot process.
/// There can be multiple phase configurations that are
/// executed sequentially.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct PhaseConfiguration {
/// The actions to run when the phase is executed.
#[serde(default)]
pub actions: Vec<String>,
/// The values to insert into the context when the phase is executed.
#[serde(default)]
pub values: BTreeMap<String, String>,
}

View File

@@ -10,15 +10,12 @@ edition.workspace = true
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
bitflags.workspace = true bitflags.workspace = true
edera-sprout-config.path = "../config"
hex.workspace = true hex.workspace = true
sha2.workspace = true sha2.workspace = true
toml.workspace = true toml.workspace = true
log.workspace = true log.workspace = true
[dependencies.serde]
workspace = true
features = ["derive"]
[dependencies.uefi] [dependencies.uefi]
workspace = true workspace = true
features = ["alloc", "logger"] features = ["alloc", "logger"]

View File

@@ -1,6 +1,5 @@
use crate::context::SproutContext; use crate::context::SproutContext;
use anyhow::{Context, Result, bail}; use anyhow::{Context, Result, bail};
use serde::{Deserialize, Serialize};
use std::rc::Rc; use std::rc::Rc;
/// EFI chainloader action. /// EFI chainloader action.
@@ -10,28 +9,6 @@ pub mod edera;
/// EFI console print action. /// EFI console print action.
pub mod print; pub mod print;
/// Declares an action that sprout can execute.
/// Actions allow configuring sprout's internal runtime mechanisms with values
/// that you can specify via other concepts.
///
/// Actions are the main work that Sprout gets done, like booting Linux.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct ActionDeclaration {
/// Chainload to another EFI application.
/// This allows you to load any EFI application, either to boot an operating system
/// or to perform more EFI actions and return to sprout.
#[serde(default)]
pub chainload: Option<chainload::ChainloadConfiguration>,
/// Print a string to the EFI console.
#[serde(default)]
pub print: Option<print::PrintConfiguration>,
/// Boot the Edera hypervisor and the root operating system.
/// This action is an extension on top of the Xen EFI stub that
/// is specific to Edera.
#[serde(default, rename = "edera")]
pub edera: Option<edera::EderaConfiguration>,
}
/// Execute the action specified by `name` which should be stored in the /// Execute the action specified by `name` which should be stored in the
/// root context of the provided `context`. This function may not return /// root context of the provided `context`. This function may not return
/// if the provided action executes an operating system or an EFI application /// if the provided action executes an operating system or an EFI application

View File

@@ -5,30 +5,12 @@ use crate::utils;
use crate::utils::media_loader::MediaLoaderHandle; use crate::utils::media_loader::MediaLoaderHandle;
use crate::utils::media_loader::constants::linux::LINUX_EFI_INITRD_MEDIA_GUID; use crate::utils::media_loader::constants::linux::LINUX_EFI_INITRD_MEDIA_GUID;
use anyhow::{Context, Result, bail}; use anyhow::{Context, Result, bail};
use edera_sprout_config::actions::chainload::ChainloadConfiguration;
use log::error; use log::error;
use serde::{Deserialize, Serialize};
use std::rc::Rc; use std::rc::Rc;
use uefi::CString16; use uefi::CString16;
use uefi::proto::loaded_image::LoadedImage; use uefi::proto::loaded_image::LoadedImage;
/// The configuration of the chainload action.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct ChainloadConfiguration {
/// The path to the image to chainload.
/// This can be a Linux EFI stub (vmlinuz usually) or a standard EFI executable.
pub path: String,
/// The options to pass to the image.
/// The options are concatenated by a space and then passed to the EFI application.
#[serde(default)]
pub options: Vec<String>,
/// An optional path to a Linux initrd.
/// This uses the [LINUX_EFI_INITRD_MEDIA_GUID] mechanism to load the initrd into the EFI stack.
/// For Linux, you can also use initrd=\path\to\initrd as an option, but this option is
/// generally better and safer as it can support additional load options in the future.
#[serde(default, rename = "linux-initrd")]
pub linux_initrd: Option<String>,
}
/// Executes the chainload action using the specified `configuration` inside the provided `context`. /// Executes the chainload action using the specified `configuration` inside the provided `context`.
pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfiguration) -> Result<()> { pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfiguration) -> Result<()> {
// Retrieve the current image handle of sprout. // Retrieve the current image handle of sprout.

View File

@@ -1,12 +1,7 @@
use std::rc::Rc; use std::rc::Rc;
use anyhow::{Context, Result};
use log::error;
use serde::{Deserialize, Serialize};
use uefi::Guid;
use crate::{ use crate::{
actions::{self, chainload::ChainloadConfiguration}, actions,
context::SproutContext, context::SproutContext,
utils::{ utils::{
self, self,
@@ -18,26 +13,11 @@ use crate::{
}, },
}, },
}; };
use anyhow::{Context, Result};
/// The configuration of the edera action which boots the Edera hypervisor. use edera_sprout_config::actions::chainload::ChainloadConfiguration;
/// Edera is based on Xen but modified significantly with a Rust stack. use edera_sprout_config::actions::edera::EderaConfiguration;
/// Sprout is a component of the Edera stack and provides the boot functionality of Xen. use log::error;
#[derive(Serialize, Deserialize, Debug, Default, Clone)] use uefi::Guid;
pub struct EderaConfiguration {
/// The path to the Xen hypervisor EFI image.
pub xen: String,
/// The path to the kernel to boot for dom0.
pub kernel: String,
/// The path to the initrd to load for dom0.
#[serde(default)]
pub initrd: Option<String>,
/// The options to pass to the kernel.
#[serde(default, rename = "kernel-options")]
pub kernel_options: Vec<String>,
/// The options to pass to the Xen hypervisor.
#[serde(default, rename = "xen-options")]
pub xen_options: Vec<String>,
}
/// Builds a configuration string for the Xen EFI stub using the specified `configuration`. /// Builds a configuration string for the Xen EFI stub using the specified `configuration`.
fn build_xen_config(context: Rc<SproutContext>, configuration: &EderaConfiguration) -> String { fn build_xen_config(context: Rc<SproutContext>, configuration: &EderaConfiguration) -> String {

View File

@@ -1,17 +1,9 @@
use crate::context::SproutContext; use crate::context::SproutContext;
use anyhow::Result; use anyhow::Result;
use edera_sprout_config::actions::print::PrintConfiguration;
use log::info; use log::info;
use serde::{Deserialize, Serialize};
use std::rc::Rc; use std::rc::Rc;
/// The configuration of the print action.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct PrintConfiguration {
/// The text to print to the console.
#[serde(default)]
pub text: String,
}
/// Executes the print action with the specified `configuration` inside the provided `context`. /// Executes the print action with the specified `configuration` inside the provided `context`.
pub fn print(context: Rc<SproutContext>, configuration: &PrintConfiguration) -> Result<()> { pub fn print(context: Rc<SproutContext>, configuration: &PrintConfiguration) -> Result<()> {
info!("{}", context.stamp(&configuration.text)); info!("{}", context.stamp(&configuration.text));

View File

@@ -1,5 +1,5 @@
use crate::config::RootConfiguration;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use edera_sprout_config::RootConfiguration;
use uefi::fs::FileSystem; use uefi::fs::FileSystem;
use uefi::proto::device_path::DevicePath; use uefi::proto::device_path::DevicePath;
use uefi::proto::media::fs::SimpleFileSystem; use uefi::proto::media::fs::SimpleFileSystem;

View File

@@ -1,11 +1,11 @@
use crate::actions::ActionDeclaration;
use crate::actions::chainload::ChainloadConfiguration;
use crate::config::RootConfiguration;
use crate::entries::EntryDeclaration;
use crate::generators::GeneratorDeclaration;
use crate::generators::bls::BlsConfiguration;
use crate::utils; use crate::utils;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use edera_sprout_config::RootConfiguration;
use edera_sprout_config::actions::ActionDeclaration;
use edera_sprout_config::actions::chainload::ChainloadConfiguration;
use edera_sprout_config::entries::EntryDeclaration;
use edera_sprout_config::generators::GeneratorDeclaration;
use edera_sprout_config::generators::bls::BlsConfiguration;
use uefi::cstr16; use uefi::cstr16;
use uefi::fs::{FileSystem, Path}; use uefi::fs::{FileSystem, Path};
use uefi::proto::device_path::DevicePath; use uefi::proto::device_path::DevicePath;

View File

@@ -1,12 +1,12 @@
use crate::actions::ActionDeclaration;
use crate::actions::chainload::ChainloadConfiguration;
use crate::config::RootConfiguration;
use crate::entries::EntryDeclaration;
use crate::generators::GeneratorDeclaration;
use crate::generators::list::ListConfiguration;
use crate::utils; use crate::utils;
use crate::utils::vercmp; use crate::utils::vercmp;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use edera_sprout_config::RootConfiguration;
use edera_sprout_config::actions::ActionDeclaration;
use edera_sprout_config::actions::chainload::ChainloadConfiguration;
use edera_sprout_config::entries::EntryDeclaration;
use edera_sprout_config::generators::GeneratorDeclaration;
use edera_sprout_config::generators::list::ListConfiguration;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use uefi::CString16; use uefi::CString16;
use uefi::fs::{FileSystem, Path, PathBuf}; use uefi::fs::{FileSystem, Path, PathBuf};

View File

@@ -1,9 +1,9 @@
use crate::actions::ActionDeclaration;
use crate::actions::chainload::ChainloadConfiguration;
use crate::config::RootConfiguration;
use crate::entries::EntryDeclaration;
use crate::utils; use crate::utils;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use edera_sprout_config::RootConfiguration;
use edera_sprout_config::actions::ActionDeclaration;
use edera_sprout_config::actions::chainload::ChainloadConfiguration;
use edera_sprout_config::entries::EntryDeclaration;
use uefi::CString16; use uefi::CString16;
use uefi::fs::{FileSystem, Path}; use uefi::fs::{FileSystem, Path};
use uefi::proto::device_path::DevicePath; use uefi::proto::device_path::DevicePath;

View File

@@ -1,89 +1,2 @@
use crate::actions::ActionDeclaration;
use crate::drivers::DriverDeclaration;
use crate::entries::EntryDeclaration;
use crate::extractors::ExtractorDeclaration;
use crate::generators::GeneratorDeclaration;
use crate::phases::PhasesConfiguration;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
/// The configuration loader mechanisms. /// The configuration loader mechanisms.
pub mod loader; pub mod loader;
/// This is the latest version of the sprout configuration format.
/// This must be incremented when the configuration breaks compatibility.
pub const LATEST_VERSION: u32 = 1;
/// The default timeout for the boot menu in seconds.
pub const DEFAULT_MENU_TIMEOUT_SECONDS: u64 = 10;
/// The Sprout configuration format.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct RootConfiguration {
/// The version of the configuration. This should always be declared
/// and be the latest version that is supported. If not specified, it is assumed
/// the configuration is the latest version.
#[serde(default = "latest_version")]
pub version: u32,
/// Default options for Sprout.
#[serde(default)]
pub options: OptionsConfiguration,
/// Values to be inserted into the root sprout context.
#[serde(default)]
pub values: BTreeMap<String, String>,
/// Drivers to load.
/// These drivers provide extra functionality like filesystem support to Sprout.
/// Each driver has a name which uniquely identifies it inside Sprout.
#[serde(default)]
pub drivers: BTreeMap<String, DriverDeclaration>,
/// Declares the extractors that add values to the sprout context that are calculated
/// at runtime. Each extractor has a name which corresponds to the value it will set
/// inside the sprout context.
#[serde(default)]
pub extractors: BTreeMap<String, ExtractorDeclaration>,
/// Declares the actions that can execute operations for sprout.
/// Actions are executable modules in sprout that take in specific structured values.
/// Actions are responsible for ensuring that passed strings are stamped to replace values
/// at runtime.
/// Each action has a name that can be referenced by other base concepts like entries.
#[serde(default)]
pub actions: BTreeMap<String, ActionDeclaration>,
/// Declares the entries that are displayed on the boot menu. These entries are static
/// but can still use values from the sprout context.
#[serde(default)]
pub entries: BTreeMap<String, EntryDeclaration>,
/// Declares the generators that are used to generate entries at runtime.
/// Each generator has its own logic for generating entries, but generally they intake
/// a template entry and stamp that template entry over some values determined at runtime.
/// Each generator has an associated name used to differentiate it across sprout.
#[serde(default)]
pub generators: BTreeMap<String, GeneratorDeclaration>,
/// Configures the various phases of sprout. This allows you to hook into specific parts
/// of the boot process to execute actions, for example, you can show a boot splash during
/// the early phase.
#[serde(default)]
pub phases: PhasesConfiguration,
}
/// Options configuration for Sprout, used when the corresponding options are not specified.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct OptionsConfiguration {
/// The entry to boot without showing the boot menu.
/// If not specified, a boot menu is shown.
#[serde(rename = "default-entry", default)]
pub default_entry: Option<String>,
/// The timeout of the boot menu.
#[serde(rename = "menu-timeout", default = "default_menu_timeout")]
pub menu_timeout: u64,
/// Enables autoconfiguration of Sprout based on the environment.
#[serde(default)]
pub autoconfigure: bool,
}
fn latest_version() -> u32 {
LATEST_VERSION
}
fn default_menu_timeout() -> u64 {
DEFAULT_MENU_TIMEOUT_SECONDS
}

View File

@@ -1,8 +1,8 @@
use crate::config::{RootConfiguration, latest_version};
use crate::options::SproutOptions; use crate::options::SproutOptions;
use crate::platform::tpm::PlatformTpm; use crate::platform::tpm::PlatformTpm;
use crate::utils; use crate::utils;
use anyhow::{Context, Result, bail}; use anyhow::{Context, Result, bail};
use edera_sprout_config::{RootConfiguration, latest_version};
use log::info; use log::info;
use std::ops::Deref; use std::ops::Deref;
use toml::Value; use toml::Value;

View File

@@ -1,8 +1,8 @@
use crate::actions::ActionDeclaration;
use crate::options::SproutOptions; use crate::options::SproutOptions;
use crate::platform::timer::PlatformTimer; use crate::platform::timer::PlatformTimer;
use anyhow::anyhow; use anyhow::anyhow;
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use edera_sprout_config::actions::ActionDeclaration;
use std::cmp::Reverse; use std::cmp::Reverse;
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use std::rc::Rc; use std::rc::Rc;

View File

@@ -2,23 +2,12 @@ use crate::context::SproutContext;
use crate::integrations::shim::{ShimInput, ShimSupport}; use crate::integrations::shim::{ShimInput, ShimSupport};
use crate::utils; use crate::utils;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
pub(crate) use edera_sprout_config::drivers::DriverDeclaration;
use log::info; use log::info;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::rc::Rc; use std::rc::Rc;
use uefi::boot::SearchType; use uefi::boot::SearchType;
/// Declares a driver configuration.
/// Drivers allow extending the functionality of Sprout.
/// Drivers are loaded at runtime and can provide extra functionality like filesystem support.
/// Drivers are loaded by their name, which is used to reference them in other concepts.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct DriverDeclaration {
/// The filesystem path to the driver.
/// This file should be an EFI executable that can be located and executed.
pub path: String,
}
/// Loads the driver specified by the `driver` declaration. /// Loads the driver specified by the `driver` declaration.
fn load_driver(context: Rc<SproutContext>, driver: &DriverDeclaration) -> Result<()> { fn load_driver(context: Rc<SproutContext>, driver: &DriverDeclaration) -> Result<()> {
// Acquire the handle and device path of the loaded image. // Acquire the handle and device path of the loaded image.

View File

@@ -1,25 +1,7 @@
use crate::context::SproutContext; use crate::context::SproutContext;
use serde::{Deserialize, Serialize}; use edera_sprout_config::entries::EntryDeclaration;
use std::collections::BTreeMap;
use std::rc::Rc; use std::rc::Rc;
/// Declares a boot entry to display in the boot menu.
///
/// Entries are the user-facing concept of Sprout, making it possible
/// to run a set of actions with a specific context.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct EntryDeclaration {
/// The title of the entry which will be display in the boot menu.
/// This is the pre-stamped value.
pub title: String,
/// The actions to run when the entry is selected.
#[serde(default)]
pub actions: Vec<String>,
/// The values to insert into the context when the entry is selected.
#[serde(default)]
pub values: BTreeMap<String, String>,
}
/// Represents an entry that is stamped and ready to be booted. /// Represents an entry that is stamped and ready to be booted.
#[derive(Clone)] #[derive(Clone)]
pub struct BootableEntry { pub struct BootableEntry {

View File

@@ -1,25 +1,11 @@
use crate::context::SproutContext; use crate::context::SproutContext;
use crate::extractors::filesystem_device_match::FilesystemDeviceMatchExtractor;
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use serde::{Deserialize, Serialize}; use edera_sprout_config::extractors::ExtractorDeclaration;
use std::rc::Rc; use std::rc::Rc;
/// The filesystem device match extractor. /// The filesystem device match extractor.
pub mod filesystem_device_match; pub mod filesystem_device_match;
/// Declares an extractor configuration.
/// Extractors allow calculating values at runtime
/// using built-in sprout modules.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct ExtractorDeclaration {
/// The filesystem device match extractor.
/// This extractor finds a filesystem using some search criteria and returns
/// the device root path that can concatenated with subpaths to access files
/// on a particular filesystem.
#[serde(default, rename = "filesystem-device-match")]
pub filesystem_device_match: Option<FilesystemDeviceMatchExtractor>,
}
/// Extracts the value using the specified `extractor` under the provided `context`. /// Extracts the value using the specified `extractor` under the provided `context`.
/// The extractor must return a value, and if a value cannot be determined, an error /// The extractor must return a value, and if a value cannot be determined, an error
/// should be returned. /// should be returned.

View File

@@ -1,7 +1,7 @@
use crate::context::SproutContext; use crate::context::SproutContext;
use crate::utils; use crate::utils;
use anyhow::{Context, Result, anyhow, bail}; use anyhow::{Context, Result, anyhow, bail};
use serde::{Deserialize, Serialize}; use edera_sprout_config::extractors::filesystem_device_match::FilesystemDeviceMatchExtractor;
use std::ops::Deref; use std::ops::Deref;
use std::rc::Rc; use std::rc::Rc;
use std::str::FromStr; use std::str::FromStr;
@@ -11,34 +11,6 @@ use uefi::proto::media::file::{File, FileSystemVolumeLabel};
use uefi::proto::media::fs::SimpleFileSystem; use uefi::proto::media::fs::SimpleFileSystem;
use uefi::{CString16, Guid}; use uefi::{CString16, Guid};
/// The filesystem device match extractor.
/// This extractor finds a filesystem using some search criteria and returns
/// the device root path that can concatenated with subpaths to access files
/// on a particular filesystem.
/// The fallback value can be used to provide a value if no match is found.
///
/// This extractor requires all the criteria to match. If no criteria is provided,
/// an error is returned.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct FilesystemDeviceMatchExtractor {
/// Matches a filesystem that has the specified label.
#[serde(default, rename = "has-label")]
pub has_label: Option<String>,
/// Matches a filesystem that has the specified item.
/// An item is either a directory or file.
#[serde(default, rename = "has-item")]
pub has_item: Option<String>,
/// Matches a filesystem that has the specified partition UUID.
#[serde(default, rename = "has-partition-uuid")]
pub has_partition_uuid: Option<String>,
/// Matches a filesystem that has the specified partition type UUID.
#[serde(default, rename = "has-partition-type-uuid")]
pub has_partition_type_uuid: Option<String>,
/// The fallback value to use if no filesystem matches the criteria.
#[serde(default)]
pub fallback: Option<String>,
}
/// Extract a filesystem device path using the specified `context` and `extractor` configuration. /// Extract a filesystem device path using the specified `context` and `extractor` configuration.
pub fn extract( pub fn extract(
context: Rc<SproutContext>, context: Rc<SproutContext>,

View File

@@ -1,43 +1,18 @@
use crate::context::SproutContext; use crate::context::SproutContext;
use crate::entries::BootableEntry; use crate::entries::BootableEntry;
use crate::generators::bls::BlsConfiguration;
use crate::generators::list::ListConfiguration;
use crate::generators::matrix::MatrixConfiguration;
use anyhow::Result; use anyhow::Result;
use anyhow::bail; use anyhow::bail;
use serde::{Deserialize, Serialize}; use edera_sprout_config::generators::GeneratorDeclaration;
use std::rc::Rc; use std::rc::Rc;
/// The BLS generator.
pub mod bls; pub mod bls;
pub mod list;
pub mod matrix;
/// Declares a generator configuration. /// The list generator.
/// Generators allow generating entries at runtime based on a set of data. pub mod list;
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct GeneratorDeclaration { /// The matrix generator.
/// Matrix generator configuration. pub mod matrix;
/// Matrix allows you to specify multiple value-key values as arrays.
/// This allows multiplying the number of entries by any number of possible
/// configuration options. For example,
/// data.x = ["a", "b"]
/// data.y = ["c", "d"]
/// would generate an entry for each of these combinations:
/// x = a, y = c
/// x = a, y = d
/// x = b, y = c
/// x = b, y = d
#[serde(default)]
pub matrix: Option<MatrixConfiguration>,
/// BLS generator configuration.
/// BLS allows you to pass a filesystem path that contains a set of BLS entries.
/// It will generate a sprout entry for every supported BLS entry.
#[serde(default)]
pub bls: Option<BlsConfiguration>,
/// List generator configuration.
/// Allows you to specify a list of values to generate an entry from.
pub list: Option<ListConfiguration>,
}
/// Runs the generator specified by the `generator` option. /// Runs the generator specified by the `generator` option.
/// It uses the specified `context` as the parent context for /// It uses the specified `context` as the parent context for

View File

@@ -1,10 +1,10 @@
use crate::context::SproutContext; use crate::context::SproutContext;
use crate::entries::{BootableEntry, EntryDeclaration}; use crate::entries::BootableEntry;
use crate::generators::bls::entry::BlsEntry; use crate::generators::bls::entry::BlsEntry;
use crate::utils; use crate::utils;
use crate::utils::vercmp; use crate::utils::vercmp;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use serde::{Deserialize, Serialize}; use edera_sprout_config::generators::bls::BlsConfiguration;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::rc::Rc; use std::rc::Rc;
use std::str::FromStr; use std::str::FromStr;
@@ -16,25 +16,6 @@ use uefi::proto::media::fs::SimpleFileSystem;
/// BLS entry parser. /// BLS entry parser.
mod entry; mod entry;
/// The default path to the BLS directory.
const BLS_TEMPLATE_PATH: &str = "\\loader";
/// The configuration of the BLS generator.
/// The BLS uses the Bootloader Specification to produce
/// entries from an input template.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct BlsConfiguration {
/// The entry to use for as a template.
pub entry: EntryDeclaration,
/// The path to the BLS directory.
#[serde(default = "default_bls_path")]
pub path: String,
}
fn default_bls_path() -> String {
BLS_TEMPLATE_PATH.to_string()
}
// TODO(azenla): remove this once variable substitution is implemented. // TODO(azenla): remove this once variable substitution is implemented.
/// This function is used to remove the `tuned_initrd` variable from entry values. /// This function is used to remove the `tuned_initrd` variable from entry values.
/// Fedora uses tuned which adds an initrd that shouldn't be used. /// Fedora uses tuned which adds an initrd that shouldn't be used.

View File

@@ -1,23 +1,9 @@
use crate::context::SproutContext; use crate::context::SproutContext;
use crate::entries::{BootableEntry, EntryDeclaration}; use crate::entries::BootableEntry;
use anyhow::Result; use anyhow::Result;
use serde::{Deserialize, Serialize}; use edera_sprout_config::generators::list::ListConfiguration;
use std::collections::BTreeMap;
use std::rc::Rc; use std::rc::Rc;
/// List generator configuration.
/// The list generator produces multiple entries based
/// on a set of input maps.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct ListConfiguration {
/// The template entry to use for each generated entry.
#[serde(default)]
pub entry: EntryDeclaration,
/// The values to use as the input for the matrix.
#[serde(default)]
pub values: Vec<BTreeMap<String, String>>,
}
/// Generates a set of entries using the specified `list` configuration in the `context`. /// Generates a set of entries using the specified `list` configuration in the `context`.
pub fn generate( pub fn generate(
context: Rc<SproutContext>, context: Rc<SproutContext>,

View File

@@ -1,24 +1,12 @@
use crate::context::SproutContext; use crate::context::SproutContext;
use crate::entries::{BootableEntry, EntryDeclaration}; use crate::entries::BootableEntry;
use crate::generators::list; use crate::generators::list;
use anyhow::Result; use anyhow::Result;
use serde::{Deserialize, Serialize}; use edera_sprout_config::generators::list::ListConfiguration;
use edera_sprout_config::generators::matrix::MatrixConfiguration;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::rc::Rc; use std::rc::Rc;
/// Matrix generator configuration.
/// The matrix generator produces multiple entries based
/// on input values multiplicatively.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct MatrixConfiguration {
/// The template entry to use for each generated entry.
#[serde(default)]
pub entry: EntryDeclaration,
/// The values to use as the input for the matrix.
#[serde(default)]
pub values: BTreeMap<String, Vec<String>>,
}
/// Builds out multiple generations of `input` based on a matrix style. /// Builds out multiple generations of `input` based on a matrix style.
/// For example, if input is: {"x": ["a", "b"], "y": ["c", "d"]} /// For example, if input is: {"x": ["a", "b"], "y": ["c", "d"]}
/// It will produce: /// It will produce:
@@ -61,7 +49,7 @@ pub fn generate(
// Use the list generator to generate entries for each combination. // Use the list generator to generate entries for each combination.
list::generate( list::generate(
context, context,
&list::ListConfiguration { &ListConfiguration {
entry: matrix.entry.clone(), entry: matrix.entry.clone(),
values: combinations, values: combinations,
}, },

View File

@@ -4,7 +4,6 @@
/// The delay to wait for when an error occurs in Sprout. /// The delay to wait for when an error occurs in Sprout.
const DELAY_ON_ERROR: Duration = Duration::from_secs(10); const DELAY_ON_ERROR: Duration = Duration::from_secs(10);
use crate::config::RootConfiguration;
use crate::context::{RootContext, SproutContext}; use crate::context::{RootContext, SproutContext};
use crate::entries::BootableEntry; use crate::entries::BootableEntry;
use crate::integrations::bootloader_interface::{BootloaderInterface, BootloaderInterfaceTimeout}; use crate::integrations::bootloader_interface::{BootloaderInterface, BootloaderInterfaceTimeout};
@@ -16,6 +15,7 @@ use crate::platform::tpm::PlatformTpm;
use crate::secure::SecureBoot; use crate::secure::SecureBoot;
use crate::utils::PartitionGuidForm; use crate::utils::PartitionGuidForm;
use anyhow::{Context, Result, bail}; use anyhow::{Context, Result, bail};
use edera_sprout_config::RootConfiguration;
use log::{error, info, warn}; use log::{error, info, warn};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::ops::Deref; use std::ops::Deref;

View File

@@ -1,38 +1,9 @@
use crate::actions; use crate::actions;
use crate::context::SproutContext; use crate::context::SproutContext;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use serde::{Deserialize, Serialize}; use edera_sprout_config::phases::PhaseConfiguration;
use std::collections::BTreeMap;
use std::rc::Rc; use std::rc::Rc;
/// Configures the various phases of the boot process.
/// This allows hooking various phases to run actions.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct PhasesConfiguration {
/// The early phase is run before drivers are loaded.
#[serde(default)]
pub early: Vec<PhaseConfiguration>,
/// The startup phase is run after drivers are loaded, but before entries are displayed.
#[serde(default)]
pub startup: Vec<PhaseConfiguration>,
/// The late phase is run after the entry is chosen, but before the actions are executed.
#[serde(default)]
pub late: Vec<PhaseConfiguration>,
}
/// Configures a single phase of the boot process.
/// There can be multiple phase configurations that are
/// executed sequentially.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct PhaseConfiguration {
/// The actions to run when the phase is executed.
#[serde(default)]
pub actions: Vec<String>,
/// The values to insert into the context when the phase is executed.
#[serde(default)]
pub values: BTreeMap<String, String>,
}
/// Executes the specified [phase] of the boot process. /// Executes the specified [phase] of the boot process.
/// The value [phase] should be a reference of a specific phase in the [PhasesConfiguration]. /// The value [phase] should be a reference of a specific phase in the [PhasesConfiguration].
/// Any error from the actions is propagated into the [Result] and will interrupt further /// Any error from the actions is propagated into the [Result] and will interrupt further