mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-19 14:00:18 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
a10a5cb342
|
|||
|
fdc5f0e1d2
|
|||
| f60cf4b365 | |||
|
0ca9ff4fec
|
|||
|
1799419bfa
|
|||
|
facd2000a5
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -61,7 +61,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "edera-sprout"
|
||||
version = "0.0.10"
|
||||
version = "0.0.11"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"image",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "edera-sprout"
|
||||
description = "Modern UEFI bootloader"
|
||||
license = "Apache-2.0"
|
||||
version = "0.0.10"
|
||||
version = "0.0.11"
|
||||
homepage = "https://sprout.edera.dev"
|
||||
repository = "https://github.com/edera-dev/sprout"
|
||||
edition = "2024"
|
||||
|
||||
29
README.md
29
README.md
@@ -61,6 +61,7 @@ The boot menu mechanism is very rudimentary.
|
||||
- [x] Windows boot support via chainload
|
||||
- [x] Load Linux initrd from disk
|
||||
- [x] Basic boot menu
|
||||
- [x] BLS autoconfiguration support
|
||||
|
||||
### Roadmap
|
||||
|
||||
@@ -100,6 +101,8 @@ Sprout supports some command line options that can be combined to modify behavio
|
||||
$ sprout.efi --config=\path\to\config.toml
|
||||
# Boot a specific entry, bypassing the menu.
|
||||
$ sprout.efi --boot="Boot Xen"
|
||||
# Autoconfigure Sprout, without loading a configuration file.
|
||||
$ sprout.efi --autoconfigure
|
||||
```
|
||||
|
||||
### Boot Linux from ESP
|
||||
@@ -134,27 +137,11 @@ version = 1
|
||||
[drivers.ext4]
|
||||
path = "\\sprout\\drivers\\ext4.efi"
|
||||
|
||||
# extract the full path of the first filesystem
|
||||
# that contains \loader as a directory
|
||||
# into the value called "boot"
|
||||
[extractors.boot.filesystem-device-match]
|
||||
has-item = "\\loader"
|
||||
|
||||
# use the sprout bls module to scan a bls
|
||||
# directory for entries and load them as boot
|
||||
# entries in sprout, using the entry template
|
||||
# as specified here. the bls action below will
|
||||
# be passed the extracted values from bls.
|
||||
[generators.boot.bls]
|
||||
path = "$boot\\loader"
|
||||
entry.title = "$title"
|
||||
entry.actions = ["bls"]
|
||||
|
||||
# the action that is used for each bls entry above.
|
||||
[actions.bls]
|
||||
chainload.path = "$boot\\$chainload"
|
||||
chainload.options = ["$options"]
|
||||
chainload.linux-initrd = "$boot\\$initrd"
|
||||
# global options.
|
||||
[defaults]
|
||||
# enable autoconfiguration by detecting bls enabled
|
||||
# filesystems and generating boot entries for them.
|
||||
autoconfigure = true
|
||||
```
|
||||
|
||||
[Edera]: https://edera.dev
|
||||
|
||||
@@ -39,27 +39,11 @@ version = 1
|
||||
[drivers.ext4]
|
||||
path = "\\sprout\\drivers\\ext4.efi"
|
||||
|
||||
# extract the full path of the first filesystem
|
||||
# that contains \loader as a directory
|
||||
# into the value called "boot"
|
||||
[extractors.boot.filesystem-device-match]
|
||||
has-item = "\\loader"
|
||||
|
||||
# use the sprout bls module to scan a bls
|
||||
# directory for entries and load them as boot
|
||||
# entries in sprout, using the entry template
|
||||
# as specified here. the bls action below will
|
||||
# be passed the extracted values from bls.
|
||||
[generators.boot.bls]
|
||||
path = "$boot\\loader"
|
||||
entry.title = "$title"
|
||||
entry.actions = ["bls"]
|
||||
|
||||
# the action that is used for each bls entry above.
|
||||
[actions.bls]
|
||||
chainload.path = "$boot\\$chainload"
|
||||
chainload.options = ["$options"]
|
||||
chainload.linux-initrd = "$boot\\$initrd"
|
||||
# global options.
|
||||
[defaults]
|
||||
# enable autoconfiguration by detecting bls enabled
|
||||
# filesystems and generating boot entries for them.
|
||||
autoconfigure = true
|
||||
```
|
||||
|
||||
## Step 3, Option 1: Configure GRUB to load Sprout (recommended)
|
||||
|
||||
@@ -65,13 +65,8 @@ set -- "${@}" \
|
||||
-drive "if=pflash,file=${FINAL_DIR}/ovmf-boot.fd,format=raw,readonly=on" \
|
||||
-device nvme,drive=disk1,serial=cafebabe
|
||||
|
||||
if [ "${DISK_BOOT}" = "1" ]; then
|
||||
set -- "${@}" \
|
||||
-drive "if=none,file=${FINAL_DIR}/sprout.img,format=raw,id=disk1,readonly=on"
|
||||
else
|
||||
set -- "${@}" \
|
||||
-drive "if=none,file=fat:rw:${FINAL_DIR}/efi,format=raw,id=disk1"
|
||||
fi
|
||||
set -- "${@}" \
|
||||
-drive "if=none,file=${FINAL_DIR}/sprout.img,format=raw,id=disk1,readonly=on"
|
||||
|
||||
set -- "${@}" -name "sprout ${TARGET_ARCH}"
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ COPY xen.efi /work/XEN.EFI
|
||||
COPY xen.cfg /work/XEN.CFG
|
||||
COPY initramfs /work/INITRAMFS
|
||||
COPY edera-splash.png /work/EDERA-SPLASH.PNG
|
||||
COPY bls.conf /work/BLS.CONF
|
||||
RUN truncate -s128MiB sprout.img && \
|
||||
parted --script sprout.img mklabel gpt > /dev/null 2>&1 && \
|
||||
parted --script sprout.img mkpart primary fat32 1MiB 100% > /dev/null 2>&1 && \
|
||||
@@ -20,6 +21,8 @@ RUN truncate -s128MiB sprout.img && \
|
||||
mkfs.vfat -F32 -n EFI sprout.img && \
|
||||
mmd -i sprout.img ::/EFI && \
|
||||
mmd -i sprout.img ::/EFI/BOOT && \
|
||||
mmd -i sprout.img ::/LOADER && \
|
||||
mmd -i sprout.img ::/LOADER/ENTRIES && \
|
||||
mcopy -i sprout.img ${EFI_NAME}.EFI ::/EFI/BOOT/ && \
|
||||
mcopy -i sprout.img KERNEL.EFI ::/EFI/BOOT/ && \
|
||||
mcopy -i sprout.img SHELL.EFI ::/EFI/BOOT/ && \
|
||||
@@ -28,6 +31,7 @@ RUN truncate -s128MiB sprout.img && \
|
||||
mcopy -i sprout.img SPROUT.TOML ::/ && \
|
||||
mcopy -i sprout.img EDERA-SPLASH.PNG ::/ && \
|
||||
mcopy -i sprout.img INITRAMFS ::/ && \
|
||||
mcopy -i sprout.img BLS.CONF ::/LOADER/ENTRIES/ && \
|
||||
mv sprout.img /sprout.img
|
||||
|
||||
FROM scratch AS final
|
||||
|
||||
@@ -108,6 +108,7 @@ if [ "${SKIP_SPROUT_BUILD}" != "1" ]; then
|
||||
cp "hack/dev/configs/${SPROUT_CONFIG_NAME}.sprout.toml" "${FINAL_DIR}/sprout.toml"
|
||||
cp "hack/dev/configs/xen.cfg" "${FINAL_DIR}/xen.cfg"
|
||||
cp "hack/dev/assets/edera-splash.png" "${FINAL_DIR}/edera-splash.png"
|
||||
cp "hack/dev/configs/bls.conf" "${FINAL_DIR}/bls.conf"
|
||||
|
||||
mkdir -p "${FINAL_DIR}/efi/EFI/BOOT"
|
||||
cp "${FINAL_DIR}/sprout.efi" "${FINAL_DIR}/efi/EFI/BOOT/${EFI_NAME}.EFI"
|
||||
|
||||
4
hack/dev/configs/autoconfigure.sprout.toml
Normal file
4
hack/dev/configs/autoconfigure.sprout.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
version = 1
|
||||
|
||||
[defaults]
|
||||
autoconfigure = true
|
||||
4
hack/dev/configs/bls.conf
Normal file
4
hack/dev/configs/bls.conf
Normal file
@@ -0,0 +1,4 @@
|
||||
title Boot Linux
|
||||
linux /efi/boot/kernel.efi
|
||||
options console=hvc0
|
||||
initrd /initramfs
|
||||
@@ -19,7 +19,7 @@ pub mod splash;
|
||||
/// 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, 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
|
||||
|
||||
@@ -10,7 +10,7 @@ use uefi::CString16;
|
||||
use uefi::proto::loaded_image::LoadedImage;
|
||||
|
||||
/// The configuration of the chainload action.
|
||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||
#[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.
|
||||
@@ -99,10 +99,6 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
|
||||
initrd_handle = Some(handle);
|
||||
}
|
||||
|
||||
// Retrieve the base and size of the loaded image to display.
|
||||
let (base, size) = loaded_image_protocol.info();
|
||||
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
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::{
|
||||
/// 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, Default, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
pub struct EderaConfiguration {
|
||||
/// The path to the Xen hypervisor EFI image.
|
||||
pub xen: String,
|
||||
|
||||
@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// The configuration of the print action.
|
||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
pub struct PrintConfiguration {
|
||||
/// The text to print to the console.
|
||||
#[serde(default)]
|
||||
|
||||
@@ -15,7 +15,7 @@ use uefi::proto::console::gop::GraphicsOutput;
|
||||
const DEFAULT_SPLASH_TIME: u32 = 0;
|
||||
|
||||
/// The configuration of the splash action.
|
||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
pub struct SplashConfiguration {
|
||||
/// The path to the image to display.
|
||||
/// Currently, only PNG images are supported.
|
||||
|
||||
129
src/autoconfigure.rs
Normal file
129
src/autoconfigure.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
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 anyhow::{Context, Result};
|
||||
use uefi::cstr16;
|
||||
use uefi::fs::{FileSystem, Path};
|
||||
use uefi::proto::device_path::DevicePath;
|
||||
use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly};
|
||||
use uefi::proto::media::fs::SimpleFileSystem;
|
||||
|
||||
/// The name prefix of the BLS chainload action that will be used
|
||||
/// by the BLS generator to chainload entries.
|
||||
const BLS_CHAINLOAD_ACTION_PREFIX: &str = "bls-chainload-";
|
||||
|
||||
/// Scan the specified `filesystem` for BLS configurations.
|
||||
fn scan_for_bls(
|
||||
filesystem: &mut FileSystem,
|
||||
root: &DevicePath,
|
||||
config: &mut RootConfiguration,
|
||||
) -> Result<bool> {
|
||||
// BLS has a loader.conf file that can specify its own auto-entries mechanism.
|
||||
let bls_loader_conf_path = Path::new(cstr16!("\\loader\\loader.conf"));
|
||||
// BLS also has an entries directory that can specify explicit entries.
|
||||
let bls_entries_path = Path::new(cstr16!("\\loader\\entries"));
|
||||
|
||||
// Convert the device path root to a string we can use in the configuration.
|
||||
let mut root = root
|
||||
.to_string(DisplayOnly(false), AllowShortcuts(false))
|
||||
.context("unable to convert device root to string")?
|
||||
.to_string();
|
||||
// Add a trailing slash to the root to ensure the path is valid.
|
||||
root.push('/');
|
||||
|
||||
// Whether we have a loader.conf file.
|
||||
let has_loader_conf = filesystem
|
||||
.try_exists(bls_loader_conf_path)
|
||||
.context("unable to check for BLS loader.conf file")?;
|
||||
|
||||
// Whether we have an entries directory.
|
||||
// We actually iterate the entries to see if there are any.
|
||||
let has_entries_dir = filesystem
|
||||
.read_dir(bls_entries_path)
|
||||
.ok()
|
||||
.and_then(|mut iterator| iterator.next())
|
||||
.map(|entry| entry.is_ok())
|
||||
.unwrap_or(false);
|
||||
|
||||
// Detect if a BLS supported configuration is on this filesystem.
|
||||
// We check both loader.conf and entries directory as only one of them is required.
|
||||
if !(has_loader_conf || has_entries_dir) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Generate a unique name for the BLS chainload action.
|
||||
let chainload_action_name = format!("{}{}", BLS_CHAINLOAD_ACTION_PREFIX, root);
|
||||
|
||||
// BLS is now detected, generate a configuration for it.
|
||||
let generator = BlsConfiguration {
|
||||
entry: EntryDeclaration {
|
||||
title: "$title".to_string(),
|
||||
actions: vec![chainload_action_name.clone()],
|
||||
..Default::default()
|
||||
},
|
||||
path: format!("{}\\loader", root),
|
||||
};
|
||||
|
||||
// Generate a unique name for the BLS generator and insert the generator into the configuration.
|
||||
config.generators.insert(
|
||||
format!("autoconfigure-bls-{}", root),
|
||||
GeneratorDeclaration {
|
||||
bls: Some(generator),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
// Generate a chainload configuration for BLS.
|
||||
// BLS will provide these values to us.
|
||||
let chainload = ChainloadConfiguration {
|
||||
path: format!("{}\\$chainload", root),
|
||||
options: vec!["$options".to_string()],
|
||||
linux_initrd: Some(format!("{}\\$initrd", root)),
|
||||
};
|
||||
|
||||
// Insert the chainload action into the configuration.
|
||||
config.actions.insert(
|
||||
chainload_action_name,
|
||||
ActionDeclaration {
|
||||
chainload: Some(chainload),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
// We had a BLS supported configuration, so return true.
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Generate a [RootConfiguration] based on the environment.
|
||||
/// Intakes a `config` to use as the basis of the autoconfiguration.
|
||||
pub fn autoconfigure(config: &mut RootConfiguration) -> Result<()> {
|
||||
// Find all the filesystems that are on the system.
|
||||
let filesystem_handles =
|
||||
uefi::boot::find_handles::<SimpleFileSystem>().context("unable to scan filesystems")?;
|
||||
|
||||
// For each filesystem that was detected, scan it for supported autoconfig mechanisms.
|
||||
for handle in filesystem_handles {
|
||||
// Acquire the device path root for the filesystem.
|
||||
let root = {
|
||||
uefi::boot::open_protocol_exclusive::<DevicePath>(handle)
|
||||
.context("unable to get root for filesystem")?
|
||||
.to_boxed()
|
||||
};
|
||||
|
||||
// Open the filesystem that was detected.
|
||||
let filesystem = uefi::boot::open_protocol_exclusive::<SimpleFileSystem>(handle)
|
||||
.context("unable to open filesystem")?;
|
||||
|
||||
// Trade the filesystem protocol for the uefi filesystem helper.
|
||||
let mut filesystem = FileSystem::new(filesystem);
|
||||
|
||||
// Scan the filesystem for BLS supported configurations.
|
||||
// If we find any, we will add a BLS generator to the configuration.
|
||||
scan_for_bls(&mut filesystem, &root, config).context("unable to scan filesystem")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -18,7 +18,7 @@ pub const LATEST_VERSION: u32 = 1;
|
||||
pub const DEFAULT_MENU_TIMEOUT_SECONDS: u64 = 10;
|
||||
|
||||
/// The Sprout configuration format.
|
||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||
#[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
|
||||
@@ -66,7 +66,7 @@ pub struct RootConfiguration {
|
||||
}
|
||||
|
||||
/// Default configuration for Sprout, used when the corresponding options are not specified.
|
||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
pub struct DefaultsConfiguration {
|
||||
/// The entry to boot without showing the boot menu.
|
||||
/// If not specified, a boot menu is shown.
|
||||
@@ -74,6 +74,8 @@ pub struct DefaultsConfiguration {
|
||||
/// 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.
|
||||
pub autoconfigure: bool,
|
||||
}
|
||||
|
||||
fn latest_version() -> u32 {
|
||||
|
||||
@@ -83,6 +83,11 @@ impl SproutContext {
|
||||
self.root.as_ref()
|
||||
}
|
||||
|
||||
/// Access the root context to modify it, if possible.
|
||||
pub fn root_mut(&mut self) -> Option<&mut RootContext> {
|
||||
Rc::get_mut(&mut self.root)
|
||||
}
|
||||
|
||||
/// Retrieve the value specified by `key` from this context or its parents.
|
||||
/// Returns `None` if the value is not found.
|
||||
pub fn get(&self, key: impl AsRef<str>) -> Option<&String> {
|
||||
@@ -235,4 +240,10 @@ impl SproutContext {
|
||||
pub fn stamp(&self, text: impl AsRef<str>) -> String {
|
||||
Self::stamp_values(&self.all_values(), text.as_ref()).1
|
||||
}
|
||||
|
||||
/// Unloads a [SproutContext] back into an owned context. This
|
||||
/// may not succeed if something else is holding onto the value.
|
||||
pub fn unload(self: Rc<SproutContext>) -> Option<SproutContext> {
|
||||
Rc::into_inner(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use uefi::proto::device_path::LoadedImageDevicePath;
|
||||
/// 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, 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.
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::rc::Rc;
|
||||
///
|
||||
/// Entries are the user-facing concept of Sprout, making it possible
|
||||
/// to run a set of actions with a specific context.
|
||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||
#[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.
|
||||
|
||||
@@ -10,7 +10,7 @@ pub mod filesystem_device_match;
|
||||
/// Declares an extractor configuration.
|
||||
/// Extractors allow calculating values at runtime
|
||||
/// using built-in sprout modules.
|
||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||
#[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
|
||||
|
||||
@@ -20,7 +20,7 @@ use uefi_raw::Status;
|
||||
///
|
||||
/// This function only requires one of the criteria to match.
|
||||
/// The fallback value can be used to provide a value if none is found.
|
||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
pub struct FilesystemDeviceMatchExtractor {
|
||||
/// Matches a filesystem that has the specified label.
|
||||
#[serde(default, rename = "has-label")]
|
||||
|
||||
@@ -12,7 +12,7 @@ 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, Debug, Default, Clone)]
|
||||
pub struct GeneratorDeclaration {
|
||||
/// Matrix generator configuration.
|
||||
/// Matrix allows you to specify multiple value-key values as arrays.
|
||||
|
||||
@@ -20,7 +20,7 @@ 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, Default, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
pub struct BlsConfiguration {
|
||||
/// The entry to use for as a template.
|
||||
pub entry: EntryDeclaration,
|
||||
@@ -86,7 +86,7 @@ pub fn generate(context: Rc<SproutContext>, bls: &BlsConfiguration) -> Result<Ve
|
||||
let name = entry.file_name().to_string();
|
||||
|
||||
// Ignore files that are not .conf files.
|
||||
if !name.ends_with(".conf") {
|
||||
if !name.to_lowercase().ends_with(".conf") {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::rc::Rc;
|
||||
/// Matrix generator configuration.
|
||||
/// The matrix generator produces multiple entries based
|
||||
/// on input values multiplicatively.
|
||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
pub struct MatrixConfiguration {
|
||||
/// The template entry to use for each generated entry.
|
||||
#[serde(default)]
|
||||
|
||||
45
src/main.rs
45
src/main.rs
@@ -1,12 +1,13 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![feature(uefi_std)]
|
||||
|
||||
use crate::config::RootConfiguration;
|
||||
use crate::context::{RootContext, SproutContext};
|
||||
use crate::entries::BootableEntry;
|
||||
use crate::options::SproutOptions;
|
||||
use crate::options::parser::OptionsRepresentable;
|
||||
use crate::phases::phase;
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{Context, Result, bail};
|
||||
use log::info;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::Deref;
|
||||
@@ -17,6 +18,9 @@ use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly};
|
||||
/// actions: Code that can be configured and executed by Sprout.
|
||||
pub mod actions;
|
||||
|
||||
/// autoconfigure: Autoconfigure Sprout based on the detected environment.
|
||||
pub mod autoconfigure;
|
||||
|
||||
/// config: Sprout configuration mechanism.
|
||||
pub mod config;
|
||||
|
||||
@@ -60,10 +64,16 @@ fn main() -> Result<()> {
|
||||
// Parse the options to the sprout executable.
|
||||
let options = SproutOptions::parse().context("unable to parse options")?;
|
||||
|
||||
// Load the configuration of sprout.
|
||||
// At this point, the configuration has been validated and the specified
|
||||
// version is checked to ensure compatibility.
|
||||
let config = config::loader::load(&options)?;
|
||||
// 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)?
|
||||
};
|
||||
|
||||
// Load the root context.
|
||||
// This is done in a block to ensure the release of the LoadedImageDevicePath protocol.
|
||||
@@ -98,6 +108,31 @@ fn main() -> Result<()> {
|
||||
// Load all configured drivers.
|
||||
drivers::load(context.clone(), &config.drivers).context("unable to load drivers")?;
|
||||
|
||||
// If --autoconfigure is specified or the loaded configuration has autoconfigure enabled,
|
||||
// trigger the autoconfiguration mechanism.
|
||||
if context.root().options().autoconfigure || config.defaults.autoconfigure {
|
||||
autoconfigure::autoconfigure(&mut config).context("unable to autoconfigure")?;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Refreeze the context to ensure that further operations can share the context.
|
||||
let context = context.freeze();
|
||||
|
||||
// Run all the extractors declared in the configuration.
|
||||
let mut extracted = BTreeMap::new();
|
||||
for (name, extractor) in &config.extractors {
|
||||
|
||||
@@ -11,6 +11,8 @@ const DEFAULT_CONFIG_PATH: &str = "\\sprout.toml";
|
||||
/// The parsed options of sprout.
|
||||
#[derive(Debug)]
|
||||
pub struct SproutOptions {
|
||||
/// Configures Sprout automatically based on the environment.
|
||||
pub autoconfigure: bool,
|
||||
/// Path to a configuration file to load.
|
||||
pub config: String,
|
||||
/// Entry to boot without showing the boot menu.
|
||||
@@ -25,6 +27,7 @@ pub struct SproutOptions {
|
||||
impl Default for SproutOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
autoconfigure: false,
|
||||
config: DEFAULT_CONFIG_PATH.to_string(),
|
||||
boot: None,
|
||||
force_menu: false,
|
||||
@@ -86,6 +89,11 @@ impl OptionsRepresentable for SproutOptions {
|
||||
|
||||
for (key, value) in options {
|
||||
match key.as_str() {
|
||||
"autoconfigure" => {
|
||||
// Enable autoconfiguration.
|
||||
result.autoconfigure = true;
|
||||
}
|
||||
|
||||
"config" => {
|
||||
// The configuration file to load.
|
||||
result.config = value.context("--config option requires a value")?;
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::rc::Rc;
|
||||
|
||||
/// Configures the various phases of the boot process.
|
||||
/// This allows hooking various phases to run actions.
|
||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
pub struct PhasesConfiguration {
|
||||
/// The early phase is run before drivers are loaded.
|
||||
#[serde(default)]
|
||||
@@ -23,7 +23,7 @@ pub struct PhasesConfiguration {
|
||||
/// Configures a single phase of the boot process.
|
||||
/// There can be multiple phase configurations that are
|
||||
/// executed sequentially.
|
||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||
pub struct PhaseConfiguration {
|
||||
/// The actions to run when the phase is executed.
|
||||
#[serde(default)]
|
||||
|
||||
Reference in New Issue
Block a user