From 24a9e2c727758dcfdc3f07143b78d3190015a834 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sat, 4 Oct 2025 23:12:01 -0700 Subject: [PATCH] initial actions, entries, generators mechanism! --- hack/configs/kernel.sprout.toml | 16 +- hack/configs/shell-kernel.sprout.toml | 5 - src/actions.rs | 15 ++ .../chainloader.rs => actions/chainload.rs} | 16 +- src/config.rs | 66 +++++++- src/context.rs | 146 ++++++++++++++++++ src/generators.rs | 16 ++ src/generators/matrix.rs | 50 ++++++ src/main.rs | 64 +++++++- src/modules.rs | 11 -- 10 files changed, 373 insertions(+), 32 deletions(-) delete mode 100644 hack/configs/shell-kernel.sprout.toml create mode 100644 src/actions.rs rename src/{modules/chainloader.rs => actions/chainload.rs} (83%) create mode 100644 src/context.rs create mode 100644 src/generators.rs create mode 100644 src/generators/matrix.rs delete mode 100644 src/modules.rs diff --git a/hack/configs/kernel.sprout.toml b/hack/configs/kernel.sprout.toml index 5a96e10..33256cd 100644 --- a/hack/configs/kernel.sprout.toml +++ b/hack/configs/kernel.sprout.toml @@ -1,2 +1,14 @@ -[[modules]] -chainloader = { path = "\\EFI\\BOOT\\KERNEL.EFI", options = ["tty=hvc0"] } +version = 1 + +[values] +default-options = "tty=hvc0" + +[actions.chainload-kernel] +chainload.path = "$path" +chainload.options = ["$default-options"] + +[generators.kernels.matrix] +entry.title = "Boot Kernel $name" +entry.values.path = "\\EFI\\BOOT\\$name" +entry.actions = ["chainload-kernel"] +values.name = ["kernel.efi"] diff --git a/hack/configs/shell-kernel.sprout.toml b/hack/configs/shell-kernel.sprout.toml deleted file mode 100644 index 43fb14c..0000000 --- a/hack/configs/shell-kernel.sprout.toml +++ /dev/null @@ -1,5 +0,0 @@ -[[modules]] -chainloader = { path = "\\EFI\\BOOT\\SHELL.EFI" } - -[[modules]] -chainloader = { path = "\\EFI\\BOOT\\KERNEL.EFI", options = ["tty=hvc0"] } diff --git a/src/actions.rs b/src/actions.rs new file mode 100644 index 0000000..ecefef5 --- /dev/null +++ b/src/actions.rs @@ -0,0 +1,15 @@ +use crate::config::ActionDeclaration; +use crate::context::Context; +use std::rc::Rc; + +pub mod chainload; + +pub fn execute(context: Rc, action: &ActionDeclaration) { + let context = context.finalize().freeze(); + + if let Some(chainload) = &action.chainload { + chainload::chainload(context, chainload); + } else { + panic!("unknown action configuration"); + } +} diff --git a/src/modules/chainloader.rs b/src/actions/chainload.rs similarity index 83% rename from src/modules/chainloader.rs rename to src/actions/chainload.rs index 5535971..c96ce6e 100644 --- a/src/modules/chainloader.rs +++ b/src/actions/chainload.rs @@ -1,18 +1,21 @@ -use crate::config::ChainloaderConfiguration; +use crate::config::ChainloadConfiguration; +use crate::context::Context; use crate::utils; use log::info; +use std::rc::Rc; use uefi::CString16; use uefi::proto::device_path::LoadedImageDevicePath; use uefi::proto::loaded_image::LoadedImage; -pub fn chainloader(configuration: ChainloaderConfiguration) { +pub fn chainload(context: Rc, configuration: &ChainloadConfiguration) { let sprout_image = uefi::boot::image_handle(); let image_device_path_protocol = uefi::boot::open_protocol_exclusive::(sprout_image) .expect("unable to open loaded image device path protocol"); let mut full_path = utils::device_path_root(&image_device_path_protocol); - full_path.push_str(&configuration.path); + + full_path.push_str(&context.stamp(&configuration.path)); info!("path={}", full_path); @@ -30,7 +33,12 @@ pub fn chainloader(configuration: ChainloaderConfiguration) { let mut loaded_image_protocol = uefi::boot::open_protocol_exclusive::(image) .expect("unable to open loaded image protocol"); - let options = configuration.options.join(" "); + let options = configuration + .options + .iter() + .map(|item| context.stamp(item)) + .collect::>() + .join(" "); if !options.is_empty() { let options = Box::new( CString16::try_from(&options[..]) diff --git a/src/config.rs b/src/config.rs index ec45c68..34264b5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,20 +1,68 @@ use crate::utils; use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; -#[derive(Serialize, Deserialize, Default)] +#[derive(Serialize, Deserialize, Default, Clone)] pub struct RootConfiguration { + #[serde(default = "default_version")] + pub version: u32, #[serde(default)] - pub modules: Vec, + pub values: BTreeMap, + #[serde(default)] + pub actions: BTreeMap, + #[serde(default)] + pub entries: BTreeMap, + #[serde(default)] + pub generators: BTreeMap, + #[serde(default)] + pub phases: PhasesConfiguration, } -#[derive(Serialize, Deserialize, Default)] -pub struct ModuleConfiguration { +#[derive(Serialize, Deserialize, Default, Clone)] +pub struct ActionDeclaration { #[serde(default)] - pub chainloader: Option, + pub chainload: Option, } -#[derive(Serialize, Deserialize, Default)] -pub struct ChainloaderConfiguration { +#[derive(Serialize, Deserialize, Default, Clone)] +pub struct EntryDeclaration { + pub title: String, + #[serde(default)] + pub actions: Vec, + #[serde(default)] + pub values: BTreeMap, +} + +#[derive(Serialize, Deserialize, Default, Clone)] +pub struct GeneratorDeclaration { + #[serde(default)] + pub matrix: Option, +} + +#[derive(Serialize, Deserialize, Default, Clone)] +pub struct PhasesConfiguration { + #[serde(default)] + pub startup: Vec, +} + +#[derive(Serialize, Deserialize, Default, Clone)] +pub struct PhaseConfiguration { + #[serde(default)] + pub actions: Vec, + #[serde(default)] + pub values: BTreeMap, +} + +#[derive(Serialize, Deserialize, Default, Clone)] +pub struct MatrixConfiguration { + #[serde(default)] + pub entry: EntryDeclaration, + #[serde(default)] + pub values: BTreeMap>, +} + +#[derive(Serialize, Deserialize, Default, Clone)] +pub struct ChainloadConfiguration { pub path: String, #[serde(default)] pub options: Vec, @@ -24,3 +72,7 @@ pub fn load() -> RootConfiguration { let content = utils::read_file_contents("sprout.toml"); toml::from_slice(&content).expect("unable to parse sprout.toml file") } + +pub fn default_version() -> u32 { + 1 +} diff --git a/src/context.rs b/src/context.rs new file mode 100644 index 0000000..cad804d --- /dev/null +++ b/src/context.rs @@ -0,0 +1,146 @@ +use crate::config::{ActionDeclaration, EntryDeclaration}; +use std::collections::{BTreeMap, BTreeSet}; +use std::rc::Rc; + +#[derive(Default)] +pub struct RootContext { + actions: BTreeMap, + entries: BTreeMap, EntryDeclaration)>, +} + +impl RootContext { + pub fn new() -> Self { + Default::default() + } + + pub fn actions(&self) -> &BTreeMap { + &self.actions + } + + pub fn actions_mut(&mut self) -> &mut BTreeMap { + &mut self.actions + } + + pub fn entries(&self) -> &BTreeMap, EntryDeclaration)> { + &self.entries + } + + pub fn entries_mut(&mut self) -> &mut BTreeMap, EntryDeclaration)> { + &mut self.entries + } +} + +pub struct Context { + root: Rc, + parent: Option>, + values: BTreeMap, +} + +impl Context { + pub fn new(root: RootContext) -> Self { + Self { + root: Rc::new(root), + parent: None, + values: BTreeMap::new(), + } + } + + pub fn root(&self) -> &RootContext { + self.root.as_ref() + } + + pub fn get(&self, key: impl AsRef) -> Option<&String> { + self.values.get(key.as_ref()).or_else(|| { + self.parent + .as_ref() + .and_then(|parent| parent.get(key.as_ref())) + }) + } + + pub fn all_keys(&self) -> Vec { + let mut keys = BTreeSet::new(); + + for key in self.values.keys() { + keys.insert(key.clone()); + } + + if let Some(parent) = &self.parent { + keys.extend(parent.all_keys()); + } + keys.into_iter().collect() + } + + pub fn all_values(&self) -> BTreeMap { + let mut values = BTreeMap::new(); + for key in self.all_keys() { + values.insert(key.clone(), self.get(key).cloned().unwrap_or_default()); + } + values + } + + pub fn set(&mut self, key: impl AsRef, value: impl ToString) { + self.values + .insert(key.as_ref().to_string(), value.to_string()); + } + + pub fn insert(&mut self, values: &BTreeMap) { + for (key, value) in values { + self.values.insert(key.clone(), value.clone()); + } + } + + pub fn fork(self: &Rc) -> Self { + Self { + root: self.root.clone(), + parent: Some(self.clone()), + values: BTreeMap::new(), + } + } + + pub fn freeze(self) -> Rc { + Rc::new(self) + } + + pub fn finalize(&self) -> Context { + let mut current_values = self.all_values(); + + loop { + let mut did_change = false; + let mut values = BTreeMap::new(); + for (key, value) in ¤t_values { + let (changed, result) = Self::stamp_values(¤t_values, value); + if changed { + did_change = true; + } + values.insert(key.clone(), result); + } + current_values = values; + + if !did_change { + break; + } + } + Self { + root: self.root.clone(), + parent: None, + values: current_values, + } + } + + fn stamp_values(values: &BTreeMap, text: impl AsRef) -> (bool, String) { + let mut result = text.as_ref().to_string(); + let mut did_change = false; + for (key, value) in values { + let next_result = result.replace(&format!("${key}"), value); + if result != next_result { + did_change = true; + } + result = next_result; + } + (did_change, result) + } + + pub fn stamp(&self, text: impl AsRef) -> String { + Self::stamp_values(&self.all_values(), text.as_ref()).1 + } +} diff --git a/src/generators.rs b/src/generators.rs new file mode 100644 index 0000000..5858122 --- /dev/null +++ b/src/generators.rs @@ -0,0 +1,16 @@ +use crate::config::{EntryDeclaration, GeneratorDeclaration}; +use crate::context::Context; +use std::rc::Rc; + +pub mod matrix; + +pub fn generate( + context: Rc, + generator: &GeneratorDeclaration, +) -> Vec<(Rc, EntryDeclaration)> { + if let Some(matrix) = &generator.matrix { + matrix::generate(context, matrix) + } else { + panic!("unknown action configuration"); + } +} diff --git a/src/generators/matrix.rs b/src/generators/matrix.rs new file mode 100644 index 0000000..08c4224 --- /dev/null +++ b/src/generators/matrix.rs @@ -0,0 +1,50 @@ +use crate::config::{EntryDeclaration, MatrixConfiguration}; +use crate::context::Context; +use std::collections::BTreeMap; +use std::rc::Rc; + +fn build_matrix(input: &BTreeMap>) -> Vec> { + let items: Vec<(String, Vec)> = input.clone().into_iter().collect(); + let mut result: Vec> = vec![BTreeMap::new()]; + + for (key, values) in items { + let mut new_result = Vec::new(); + + for combination in &result { + for value in &values { + let mut new_combination = combination.clone(); + new_combination.insert(key.clone(), value.clone()); + new_result.push(new_combination); + } + } + + result = new_result; + } + + result.into_iter().filter(|item| !item.is_empty()).collect() +} + +pub fn generate( + context: Rc, + matrix: &MatrixConfiguration, +) -> Vec<(Rc, EntryDeclaration)> { + let combinations = build_matrix(&matrix.values); + let mut entries = Vec::new(); + + for combination in combinations { + let mut context = context.fork(); + context.insert(&combination); + let context = context.freeze(); + + let mut entry = matrix.entry.clone(); + entry.title = context.stamp(&entry.title); + entry.actions = entry + .actions + .into_iter() + .map(|action| context.stamp(action)) + .collect(); + entries.push((context, entry)); + } + + entries +} diff --git a/src/main.rs b/src/main.rs index 0b7ed06..4012122 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,72 @@ #![feature(uefi_std)] + +use crate::config::PhaseConfiguration; +use crate::context::{Context, RootContext}; +use std::rc::Rc; + +pub mod actions; pub mod config; -pub mod modules; +pub mod context; +pub mod generators; pub mod setup; pub mod utils; +fn phase(context: Rc, phase: &[PhaseConfiguration]) { + for item in phase { + let mut context = context.fork(); + context.insert(&item.values); + let context = context.freeze(); + + for action in item.actions.iter() { + let Some(action) = context.root().actions().get(action) else { + panic!("unknown action: {}", action); + }; + + actions::execute(context.clone(), action); + } + } +} + fn main() { setup::init(); let config = config::load(); - for module in config.modules { - modules::execute(module); + let mut root = RootContext::new(); + root.actions_mut().extend(config.actions.clone()); + + let mut context = Context::new(root); + context.insert(&config.values); + let context = context.freeze(); + + phase(context.clone(), &config.phases.startup); + + let mut all_entries = Vec::new(); + + for (_name, entry) in config.entries { + all_entries.push((context.clone(), entry)); + } + + for (_name, generator) in config.generators { + let context = context.fork().freeze(); + + for entry in generators::generate(context.clone(), &generator) { + all_entries.push(entry); + } + } + + println!("{} entries", all_entries.len()); + for (index, (context, entry)) in all_entries.iter().enumerate() { + let mut context = context.fork(); + context.insert(&entry.values); + let context = context.finalize().freeze(); + + println!("Entry {}:", index + 1); + println!(" Title: {}", entry.title); + println!(" Actions: {:?}", entry.actions); + println!(" Values: {:?}", context.all_values()); + } + + loop { + std::thread::sleep(std::time::Duration::from_secs(5)); } } diff --git a/src/modules.rs b/src/modules.rs deleted file mode 100644 index 7bdee76..0000000 --- a/src/modules.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::config::ModuleConfiguration; - -pub mod chainloader; - -pub fn execute(module: ModuleConfiguration) { - if let Some(chainloader) = module.chainloader { - chainloader::chainloader(chainloader); - } else { - panic!("unknown module configuration"); - } -}