mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-19 15:20:17 +00:00
add more documentation to some actions and configurations
This commit is contained in:
@@ -3,32 +3,56 @@ use anyhow::{Result, bail};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
/// EFI chainloader action.
|
||||||
pub mod chainload;
|
pub mod chainload;
|
||||||
|
/// Edera hypervisor action.
|
||||||
pub mod edera;
|
pub mod edera;
|
||||||
|
/// EFI console print action.
|
||||||
pub mod print;
|
pub mod print;
|
||||||
|
|
||||||
|
/// Splash screen action.
|
||||||
#[cfg(feature = "splash")]
|
#[cfg(feature = "splash")]
|
||||||
pub mod splash;
|
pub mod splash;
|
||||||
|
|
||||||
|
/// 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, Default, Clone)]
|
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||||
pub struct ActionDeclaration {
|
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)]
|
#[serde(default)]
|
||||||
pub chainload: Option<chainload::ChainloadConfiguration>,
|
pub chainload: Option<chainload::ChainloadConfiguration>,
|
||||||
|
/// Print a string to the EFI console.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub print: Option<print::PrintConfiguration>,
|
pub print: Option<print::PrintConfiguration>,
|
||||||
|
/// Show an image as a fullscreen splash screen.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[cfg(feature = "splash")]
|
#[cfg(feature = "splash")]
|
||||||
pub splash: Option<splash::SplashConfiguration>,
|
pub splash: Option<splash::SplashConfiguration>,
|
||||||
|
/// 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")]
|
#[serde(default, rename = "edera")]
|
||||||
pub edera: Option<edera::EderaConfiguration>,
|
pub edera: Option<edera::EderaConfiguration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Execute the action specified by `name` which should be stored in the
|
||||||
|
/// root context of the provided `context`. This function may not return
|
||||||
|
/// if the provided action executes an operating system or an EFI application
|
||||||
|
/// that does not return control to sprout.
|
||||||
pub fn execute(context: Rc<SproutContext>, name: impl AsRef<str>) -> Result<()> {
|
pub fn execute(context: Rc<SproutContext>, name: impl AsRef<str>) -> Result<()> {
|
||||||
|
// Retrieve the action from the root context.
|
||||||
let Some(action) = context.root().actions().get(name.as_ref()) else {
|
let Some(action) = context.root().actions().get(name.as_ref()) else {
|
||||||
bail!("unknown action '{}'", name.as_ref());
|
bail!("unknown action '{}'", name.as_ref());
|
||||||
};
|
};
|
||||||
|
// Finalize the context and freeze it.
|
||||||
let context = context.finalize().freeze();
|
let context = context.finalize().freeze();
|
||||||
|
|
||||||
|
// Execute the action.
|
||||||
if let Some(chainload) = &action.chainload {
|
if let Some(chainload) = &action.chainload {
|
||||||
chainload::chainload(context.clone(), chainload)?;
|
chainload::chainload(context.clone(), chainload)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -45,5 +69,7 @@ pub fn execute(context: Rc<SproutContext>, name: impl AsRef<str>) -> Result<()>
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we reach here, we don't know how to execute the action that was configured.
|
||||||
|
// This is likely unreachable, but we should still return an error just in case.
|
||||||
bail!("unknown action configuration");
|
bail!("unknown action configuration");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,24 +9,37 @@ 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, Default, Clone)]
|
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||||
pub struct ChainloadConfiguration {
|
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,
|
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)]
|
#[serde(default)]
|
||||||
pub options: Vec<String>,
|
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")]
|
#[serde(default, rename = "linux-initrd")]
|
||||||
pub linux_initrd: Option<String>,
|
pub linux_initrd: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
let sprout_image = uefi::boot::image_handle();
|
let sprout_image = uefi::boot::image_handle();
|
||||||
|
|
||||||
|
// Resolve the path to the image to chainload.
|
||||||
let resolved = utils::resolve_path(
|
let resolved = utils::resolve_path(
|
||||||
context.root().loaded_image_path()?,
|
context.root().loaded_image_path()?,
|
||||||
&context.stamp(&configuration.path),
|
&context.stamp(&configuration.path),
|
||||||
)
|
)
|
||||||
.context("unable to resolve chainload path")?;
|
.context("unable to resolve chainload path")?;
|
||||||
|
|
||||||
|
// Load the image to chainload.
|
||||||
let image = uefi::boot::load_image(
|
let image = uefi::boot::load_image(
|
||||||
sprout_image,
|
sprout_image,
|
||||||
uefi::boot::LoadImageSource::FromDevicePath {
|
uefi::boot::LoadImageSource::FromDevicePath {
|
||||||
@@ -36,9 +49,11 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
|
|||||||
)
|
)
|
||||||
.context("unable to load image")?;
|
.context("unable to load image")?;
|
||||||
|
|
||||||
|
// Open the LoadedImage protocol of the image to chainload.
|
||||||
let mut loaded_image_protocol = uefi::boot::open_protocol_exclusive::<LoadedImage>(image)
|
let mut loaded_image_protocol = uefi::boot::open_protocol_exclusive::<LoadedImage>(image)
|
||||||
.context("unable to open loaded image protocol")?;
|
.context("unable to open loaded image protocol")?;
|
||||||
|
|
||||||
|
// Stamp and concatenate the options to pass to the image.
|
||||||
let options = configuration
|
let options = configuration
|
||||||
.options
|
.options
|
||||||
.iter()
|
.iter()
|
||||||
@@ -46,6 +61,10 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(" ");
|
.join(" ");
|
||||||
|
|
||||||
|
// Pass the options to the image, if any are provided.
|
||||||
|
// The holder must drop at the end of this function to ensure the options are not leaked,
|
||||||
|
// and the holder here ensures it outlives the if block here, as a pointer has to be
|
||||||
|
// passed to the image. This has been hand-validated to be safe.
|
||||||
let mut options_holder: Option<Box<CString16>> = None;
|
let mut options_holder: Option<Box<CString16>> = None;
|
||||||
if !options.is_empty() {
|
if !options.is_empty() {
|
||||||
let options = Box::new(
|
let options = Box::new(
|
||||||
@@ -80,15 +99,28 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
|
|||||||
initrd_handle = Some(handle);
|
initrd_handle = Some(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrieve the base and size of the loaded image to display.
|
||||||
let (base, size) = loaded_image_protocol.info();
|
let (base, size) = loaded_image_protocol.info();
|
||||||
info!("loaded image: base={:#x} size={:#x}", base.addr(), size);
|
info!("loaded image: base={:#x} size={:#x}", base.addr(), size);
|
||||||
|
|
||||||
|
// Start the loaded image.
|
||||||
|
// This call might return, or it may pass full control to another image that will never return.
|
||||||
|
// Capture the result to ensure we can return an error if the image fails to start, but only
|
||||||
|
// after the optional initrd has been unregistered.
|
||||||
let result = uefi::boot::start_image(image).context("unable to start image");
|
let result = uefi::boot::start_image(image).context("unable to start image");
|
||||||
|
|
||||||
|
// Unregister the initrd if it was registered.
|
||||||
if let Some(initrd_handle) = initrd_handle
|
if let Some(initrd_handle) = initrd_handle
|
||||||
&& let Err(error) = initrd_handle.unregister()
|
&& let Err(error) = initrd_handle.unregister()
|
||||||
{
|
{
|
||||||
error!("unable to unregister linux initrd: {}", error);
|
error!("unable to unregister linux initrd: {}", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assert there was no error starting the image.
|
||||||
result.context("unable to start image")?;
|
result.context("unable to start image")?;
|
||||||
|
// Explicitly drop the option holder to clarify the lifetime.
|
||||||
drop(options_holder);
|
drop(options_holder);
|
||||||
|
|
||||||
|
// Return control to sprout.
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,15 @@ use anyhow::Result;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
/// The configuration of the print action.
|
||||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||||
pub struct PrintConfiguration {
|
pub struct PrintConfiguration {
|
||||||
|
/// The text to print to the console.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub text: String,
|
pub text: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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<()> {
|
||||||
println!("{}", context.stamp(&configuration.text));
|
println!("{}", context.stamp(&configuration.text));
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -35,12 +35,26 @@ pub struct RootConfiguration {
|
|||||||
/// inside the sprout context.
|
/// inside the sprout context.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub extractors: BTreeMap<String, ExtractorDeclaration>,
|
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)]
|
#[serde(default)]
|
||||||
pub actions: BTreeMap<String, ActionDeclaration>,
|
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)]
|
#[serde(default)]
|
||||||
pub entries: BTreeMap<String, EntryDeclaration>,
|
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)]
|
#[serde(default)]
|
||||||
pub generators: BTreeMap<String, GeneratorDeclaration>,
|
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)]
|
#[serde(default)]
|
||||||
pub phases: PhasesConfiguration,
|
pub phases: PhasesConfiguration,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,14 @@ use std::rc::Rc;
|
|||||||
use uefi::boot::SearchType;
|
use uefi::boot::SearchType;
|
||||||
use uefi::proto::device_path::LoadedImageDevicePath;
|
use uefi::proto::device_path::LoadedImageDevicePath;
|
||||||
|
|
||||||
|
/// 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, Default, Clone)]
|
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||||
pub struct DriverDeclaration {
|
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,
|
pub path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,14 +10,33 @@ use std::rc::Rc;
|
|||||||
pub mod bls;
|
pub mod bls;
|
||||||
pub mod matrix;
|
pub mod matrix;
|
||||||
|
|
||||||
|
/// Declares a generator configuration.
|
||||||
|
/// Generators allow generating entries at runtime based on a set of data.
|
||||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||||
pub struct GeneratorDeclaration {
|
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)]
|
#[serde(default)]
|
||||||
pub matrix: Option<MatrixConfiguration>,
|
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)]
|
#[serde(default)]
|
||||||
pub bls: Option<BlsConfiguration>,
|
pub bls: Option<BlsConfiguration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Runs the generator specified by the `generator` option.
|
||||||
|
/// It uses the specified `context` as the parent context for
|
||||||
|
/// the generated entries, injecting more values if needed.
|
||||||
pub fn generate(
|
pub fn generate(
|
||||||
context: Rc<SproutContext>,
|
context: Rc<SproutContext>,
|
||||||
generator: &GeneratorDeclaration,
|
generator: &GeneratorDeclaration,
|
||||||
|
|||||||
@@ -37,9 +37,11 @@ pub struct PhaseConfiguration {
|
|||||||
pub fn phase(context: Rc<SproutContext>, phase: &[PhaseConfiguration]) -> Result<()> {
|
pub fn phase(context: Rc<SproutContext>, phase: &[PhaseConfiguration]) -> Result<()> {
|
||||||
for item in phase {
|
for item in phase {
|
||||||
let mut context = context.fork();
|
let mut context = context.fork();
|
||||||
|
// Insert the values into the context.
|
||||||
context.insert(&item.values);
|
context.insert(&item.values);
|
||||||
let context = context.freeze();
|
let context = context.freeze();
|
||||||
|
|
||||||
|
// Execute all the actions in this phase configuration.
|
||||||
for action in item.actions.iter() {
|
for action in item.actions.iter() {
|
||||||
actions::execute(context.clone(), action)
|
actions::execute(context.clone(), action)
|
||||||
.context(format!("unable to execute action '{}'", action))?;
|
.context(format!("unable to execute action '{}'", action))?;
|
||||||
|
|||||||
Reference in New Issue
Block a user