mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-19 15:20:17 +00:00
feat(autoconfigure): initial attempt at bls autoconfiguration
This commit is contained in:
137
src/autoconfigure.rs
Normal file
137
src/autoconfigure.rs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
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 of the BLS chainload action that will be used
|
||||||
|
/// by the BLS generator to chainload entries.
|
||||||
|
const BLS_CHAINLOAD_ACTION: &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 root = root
|
||||||
|
.to_string(DisplayOnly(false), AllowShortcuts(false))
|
||||||
|
.context("unable to convert device root to string")?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
// Whether we have a loader.conf file.
|
||||||
|
let has_loader_conf = filesystem
|
||||||
|
.try_exists(bls_loader_conf_path)
|
||||||
|
.context("unable to check for BLS entries directory")?;
|
||||||
|
|
||||||
|
// Whether we have an entries directory.
|
||||||
|
let has_entries_dir = filesystem
|
||||||
|
.try_exists(bls_entries_path)
|
||||||
|
.context("unable to check for BLS loader.conf file")?;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BLS is now detected, generate a configuration for it.
|
||||||
|
let generator = BlsConfiguration {
|
||||||
|
entry: EntryDeclaration {
|
||||||
|
title: "$title".to_string(),
|
||||||
|
actions: vec![BLS_CHAINLOAD_ACTION.to_string()],
|
||||||
|
..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()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// We had a BLS supported configuration, so return true.
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add the BLS actions to the configuration.
|
||||||
|
/// We should only do this once.
|
||||||
|
fn add_bls_actions(config: &mut RootConfiguration) -> Result<()> {
|
||||||
|
// Generate a chainload configuration for BLS.
|
||||||
|
// BLS will provide these values to us.
|
||||||
|
let chainload = ChainloadConfiguration {
|
||||||
|
path: "$chainload".to_string(),
|
||||||
|
options: vec!["$options".to_string()],
|
||||||
|
linux_initrd: Some("initrd".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Insert the chainload action into the configuration.
|
||||||
|
config.actions.insert(
|
||||||
|
BLS_CHAINLOAD_ACTION.to_string(),
|
||||||
|
ActionDeclaration {
|
||||||
|
chainload: Some(chainload),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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")?;
|
||||||
|
|
||||||
|
// Whether we have any BLS-supported filesystems.
|
||||||
|
// We will key off of this to know if we should add BLS actions.
|
||||||
|
let mut has_any_bls = false;
|
||||||
|
|
||||||
|
// 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, and then
|
||||||
|
// at the end we will add the BLS actions to the configuration.
|
||||||
|
has_any_bls = has_any_bls
|
||||||
|
|| scan_for_bls(&mut filesystem, &root, config).context("unable to scan filesystem")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we had any BLS-supported filesystems, add the BLS actions to the configuration.
|
||||||
|
if has_any_bls {
|
||||||
|
add_bls_actions(config).context("unable to add BLS actions")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -74,6 +74,8 @@ pub struct DefaultsConfiguration {
|
|||||||
/// The timeout of the boot menu.
|
/// The timeout of the boot menu.
|
||||||
#[serde(rename = "menu-timeout", default = "default_menu_timeout")]
|
#[serde(rename = "menu-timeout", default = "default_menu_timeout")]
|
||||||
pub menu_timeout: u64,
|
pub menu_timeout: u64,
|
||||||
|
/// Enables autoconfiguration of Sprout based on the environment.
|
||||||
|
pub autoconfigure: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn latest_version() -> u32 {
|
fn latest_version() -> u32 {
|
||||||
|
|||||||
18
src/main.rs
18
src/main.rs
@@ -1,6 +1,7 @@
|
|||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
#![feature(uefi_std)]
|
#![feature(uefi_std)]
|
||||||
|
|
||||||
|
use crate::config::RootConfiguration;
|
||||||
use crate::context::{RootContext, SproutContext};
|
use crate::context::{RootContext, SproutContext};
|
||||||
use crate::entries::BootableEntry;
|
use crate::entries::BootableEntry;
|
||||||
use crate::options::SproutOptions;
|
use crate::options::SproutOptions;
|
||||||
@@ -17,6 +18,9 @@ use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly};
|
|||||||
/// actions: Code that can be configured and executed by Sprout.
|
/// actions: Code that can be configured and executed by Sprout.
|
||||||
pub mod actions;
|
pub mod actions;
|
||||||
|
|
||||||
|
/// autoconfigure: Autoconfigure Sprout based on the detected environment.
|
||||||
|
pub mod autoconfigure;
|
||||||
|
|
||||||
/// config: Sprout configuration mechanism.
|
/// config: Sprout configuration mechanism.
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
|
||||||
@@ -60,10 +64,16 @@ fn main() -> Result<()> {
|
|||||||
// Parse the options to the sprout executable.
|
// Parse the options to the sprout executable.
|
||||||
let options = SproutOptions::parse().context("unable to parse options")?;
|
let options = SproutOptions::parse().context("unable to parse 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.
|
// Load the configuration of sprout.
|
||||||
// At this point, the configuration has been validated and the specified
|
// At this point, the configuration has been validated and the specified
|
||||||
// version is checked to ensure compatibility.
|
// version is checked to ensure compatibility.
|
||||||
let config = config::loader::load(&options)?;
|
config::loader::load(&options)?
|
||||||
|
};
|
||||||
|
|
||||||
// Load the root context.
|
// Load the root context.
|
||||||
// This is done in a block to ensure the release of the LoadedImageDevicePath protocol.
|
// This is done in a block to ensure the release of the LoadedImageDevicePath protocol.
|
||||||
@@ -98,6 +108,12 @@ fn main() -> Result<()> {
|
|||||||
// Load all configured drivers.
|
// Load all configured drivers.
|
||||||
drivers::load(context.clone(), &config.drivers).context("unable to load 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")?;
|
||||||
|
}
|
||||||
|
|
||||||
// Run all the extractors declared in the configuration.
|
// Run all the extractors declared in the configuration.
|
||||||
let mut extracted = BTreeMap::new();
|
let mut extracted = BTreeMap::new();
|
||||||
for (name, extractor) in &config.extractors {
|
for (name, extractor) in &config.extractors {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ const DEFAULT_CONFIG_PATH: &str = "\\sprout.toml";
|
|||||||
/// The parsed options of sprout.
|
/// The parsed options of sprout.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SproutOptions {
|
pub struct SproutOptions {
|
||||||
|
/// Configures Sprout automatically based on the environment.
|
||||||
|
pub autoconfigure: bool,
|
||||||
/// Path to a configuration file to load.
|
/// Path to a configuration file to load.
|
||||||
pub config: String,
|
pub config: String,
|
||||||
/// Entry to boot without showing the boot menu.
|
/// Entry to boot without showing the boot menu.
|
||||||
@@ -25,6 +27,7 @@ pub struct SproutOptions {
|
|||||||
impl Default for SproutOptions {
|
impl Default for SproutOptions {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
autoconfigure: false,
|
||||||
config: DEFAULT_CONFIG_PATH.to_string(),
|
config: DEFAULT_CONFIG_PATH.to_string(),
|
||||||
boot: None,
|
boot: None,
|
||||||
force_menu: false,
|
force_menu: false,
|
||||||
@@ -86,6 +89,11 @@ impl OptionsRepresentable for SproutOptions {
|
|||||||
|
|
||||||
for (key, value) in options {
|
for (key, value) in options {
|
||||||
match key.as_str() {
|
match key.as_str() {
|
||||||
|
"autoconfigure" => {
|
||||||
|
// Enable autoconfiguration.
|
||||||
|
result.autoconfigure = true;
|
||||||
|
}
|
||||||
|
|
||||||
"config" => {
|
"config" => {
|
||||||
// The configuration file to load.
|
// The configuration file to load.
|
||||||
result.config = value.context("--config option requires a value")?;
|
result.config = value.context("--config option requires a value")?;
|
||||||
|
|||||||
Reference in New Issue
Block a user