implement support for filesystem extractor mechanism

This commit is contained in:
2025-10-13 00:55:11 -07:00
parent aba53c0d2b
commit 7a63e0325b
10 changed files with 237 additions and 28 deletions

View File

@@ -1,9 +1,12 @@
version = 1 version = 1
[extractors.boot.filesystem]
item = "\\EFI\\BOOT\\kernel.efi"
[actions.chainload-kernel] [actions.chainload-kernel]
chainload.path = "\\EFI\\BOOT\\kernel.efi" chainload.path = "$boot\\EFI\\BOOT\\kernel.efi"
chainload.options = ["console=hvc0"] chainload.options = ["console=hvc0"]
chainload.linux-initrd = "\\initramfs" chainload.linux-initrd = "$boot\\initramfs"
[entries.kernel] [entries.kernel]
title = "Boot Linux" title = "Boot Linux"

View File

@@ -20,22 +20,20 @@ pub struct ChainloadConfiguration {
pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfiguration) -> Result<()> { pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfiguration) -> Result<()> {
let sprout_image = uefi::boot::image_handle(); let sprout_image = uefi::boot::image_handle();
let image_device_path_protocol = let _image_device_path_protocol =
uefi::boot::open_protocol_exclusive::<LoadedImageDevicePath>(sprout_image) uefi::boot::open_protocol_exclusive::<LoadedImageDevicePath>(sprout_image)
.context("unable to open loaded image device path protocol")?; .context("unable to open loaded image device path protocol")?;
let mut full_path = utils::device_path_root(&image_device_path_protocol)?; let resolved = utils::resolve_path(
context.root().loaded_image_path()?,
full_path.push_str(&context.stamp(&configuration.path)); &context.stamp(&configuration.path),
)
info!("path: {}", full_path); .context("failed to resolve chainload path")?;
let device_path = utils::text_to_device_path(&full_path)?;
let image = uefi::boot::load_image( let image = uefi::boot::load_image(
sprout_image, sprout_image,
uefi::boot::LoadImageSource::FromDevicePath { uefi::boot::LoadImageSource::FromDevicePath {
device_path: &device_path, device_path: &resolved.full_path,
boot_policy: uefi::proto::BootPolicy::ExactMatch, boot_policy: uefi::proto::BootPolicy::ExactMatch,
}, },
) )
@@ -77,8 +75,8 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
let mut initrd_handle = None; let mut initrd_handle = None;
if let Some(ref linux_initrd) = configuration.linux_initrd { if let Some(ref linux_initrd) = configuration.linux_initrd {
let initrd_path = context.stamp(linux_initrd); let initrd_path = context.stamp(linux_initrd);
let content = let content = utils::read_file_contents(context.root().loaded_image_path()?, &initrd_path)
utils::read_file_contents(&initrd_path).context("failed to read linux initrd")?; .context("failed to read linux initrd")?;
initrd_handle = Some( initrd_handle = Some(
register_linux_initrd(content.into_boxed_slice()) register_linux_initrd(content.into_boxed_slice())
.context("failed to register linux initrd")?, .context("failed to register linux initrd")?,

View File

@@ -96,7 +96,7 @@ fn draw(image: DynamicImage) -> Result<()> {
pub fn splash(context: Rc<SproutContext>, configuration: &SplashConfiguration) -> Result<()> { pub fn splash(context: Rc<SproutContext>, configuration: &SplashConfiguration) -> Result<()> {
let image = context.stamp(&configuration.image); let image = context.stamp(&configuration.image);
let image = read_file_contents(&image)?; let image = read_file_contents(context.root().loaded_image_path()?, &image)?;
let image = ImageReader::with_format(Cursor::new(image), ImageFormat::Png) let image = ImageReader::with_format(Cursor::new(image), ImageFormat::Png)
.decode() .decode()
.context("failed to decode splash image")?; .context("failed to decode splash image")?;

View File

@@ -1,11 +1,14 @@
use crate::actions::ActionDeclaration; use crate::actions::ActionDeclaration;
use crate::drivers::DriverDeclaration; use crate::drivers::DriverDeclaration;
use crate::extractors::ExtractorDeclaration;
use crate::generators::GeneratorDeclaration; use crate::generators::GeneratorDeclaration;
use crate::utils; use crate::utils;
use anyhow::Context; use anyhow::Context;
use anyhow::Result; use anyhow::Result;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::ops::Deref;
use uefi::proto::device_path::LoadedImageDevicePath;
#[derive(Serialize, Deserialize, Default, Clone)] #[derive(Serialize, Deserialize, Default, Clone)]
pub struct RootConfiguration { pub struct RootConfiguration {
@@ -16,6 +19,8 @@ pub struct RootConfiguration {
#[serde(default)] #[serde(default)]
pub drivers: BTreeMap<String, DriverDeclaration>, pub drivers: BTreeMap<String, DriverDeclaration>,
#[serde(default)] #[serde(default)]
pub extractors: BTreeMap<String, ExtractorDeclaration>,
#[serde(default)]
pub actions: BTreeMap<String, ActionDeclaration>, pub actions: BTreeMap<String, ActionDeclaration>,
#[serde(default)] #[serde(default)]
pub entries: BTreeMap<String, EntryDeclaration>, pub entries: BTreeMap<String, EntryDeclaration>,
@@ -49,8 +54,13 @@ pub struct PhaseConfiguration {
} }
pub fn load() -> Result<RootConfiguration> { pub fn load() -> Result<RootConfiguration> {
let content = let current_image_device_path_protocol =
utils::read_file_contents("sprout.toml").context("unable to read sprout.toml file")?; uefi::boot::open_protocol_exclusive::<LoadedImageDevicePath>(uefi::boot::image_handle())
.context("failed to get loaded image device path")?;
let path = current_image_device_path_protocol.deref().to_boxed();
let content = utils::read_file_contents(&path, "sprout.toml")
.context("unable to read sprout.toml file")?;
let config: RootConfiguration = let config: RootConfiguration =
toml::from_slice(&content).context("unable to parse sprout.toml file")?; toml::from_slice(&content).context("unable to parse sprout.toml file")?;
Ok(config) Ok(config)

View File

@@ -1,15 +1,22 @@
use crate::actions::ActionDeclaration; use crate::actions::ActionDeclaration;
use anyhow::Result;
use anyhow::anyhow;
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use std::rc::Rc; use std::rc::Rc;
use uefi::proto::device_path::DevicePath;
#[derive(Default)] #[derive(Default)]
pub struct RootContext { pub struct RootContext {
actions: BTreeMap<String, ActionDeclaration>, actions: BTreeMap<String, ActionDeclaration>,
loaded_image_path: Option<Box<DevicePath>>,
} }
impl RootContext { impl RootContext {
pub fn new() -> Self { pub fn new(loaded_image_device_path: Box<DevicePath>) -> Self {
Default::default() RootContext {
actions: BTreeMap::new(),
loaded_image_path: Some(loaded_image_device_path),
}
} }
pub fn actions(&self) -> &BTreeMap<String, ActionDeclaration> { pub fn actions(&self) -> &BTreeMap<String, ActionDeclaration> {
@@ -19,6 +26,12 @@ impl RootContext {
pub fn actions_mut(&mut self) -> &mut BTreeMap<String, ActionDeclaration> { pub fn actions_mut(&mut self) -> &mut BTreeMap<String, ActionDeclaration> {
&mut self.actions &mut self.actions
} }
pub fn loaded_image_path(&self) -> Result<&DevicePath> {
self.loaded_image_path
.as_deref()
.ok_or_else(|| anyhow!("no loaded image path"))
}
} }
pub struct SproutContext { pub struct SproutContext {

20
src/extractors.rs Normal file
View File

@@ -0,0 +1,20 @@
use crate::context::SproutContext;
use crate::extractors::filesystem::FileSystemExtractorConfiguration;
use anyhow::{Result, bail};
use serde::{Deserialize, Serialize};
use std::rc::Rc;
pub mod filesystem;
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct ExtractorDeclaration {
pub filesystem: Option<FileSystemExtractorConfiguration>,
}
pub fn extract(context: Rc<SproutContext>, extractor: &ExtractorDeclaration) -> Result<String> {
if let Some(filesystem) = &extractor.filesystem {
filesystem::extract(context, filesystem)
} else {
bail!("unknown extractor configuration");
}
}

View File

@@ -0,0 +1,66 @@
use crate::context::SproutContext;
use crate::utils;
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::ops::Deref;
use std::rc::Rc;
use uefi::CString16;
use uefi::fs::{FileSystem, Path};
use uefi::proto::device_path::DevicePath;
use uefi::proto::media::file::{File, FileSystemVolumeLabel};
use uefi::proto::media::fs::SimpleFileSystem;
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct FileSystemExtractorConfiguration {
pub label: Option<String>,
pub item: Option<String>,
}
pub fn extract(
context: Rc<SproutContext>,
device: &FileSystemExtractorConfiguration,
) -> Result<String> {
let handles = uefi::boot::find_handles::<SimpleFileSystem>()
.context("failed to find filesystem handles")?;
for handle in handles {
let mut filesystem = uefi::boot::open_protocol_exclusive::<SimpleFileSystem>(handle)
.context("failed to open filesystem protocol")?;
if let Some(ref label) = device.label {
let want_label = CString16::try_from(context.stamp(label).as_str())
.context("failed to convert label to CString16")?;
let mut root = filesystem
.open_volume()
.context("failed to open filesystem volume")?;
let label = root
.get_boxed_info::<FileSystemVolumeLabel>()
.context("failed to get filesystem volume label")?;
if label.volume_label() != want_label {
continue;
}
}
if let Some(ref item) = device.item {
let want_item = CString16::try_from(context.stamp(item).as_str())
.context("failed to convert item to CString16")?;
let mut filesystem = FileSystem::new(filesystem);
let metadata = filesystem.metadata(Path::new(&want_item));
if metadata.is_err() {
continue;
}
let metadata = metadata?;
if !(metadata.is_directory() || metadata.is_regular_file()) {
continue;
}
}
let path = uefi::boot::open_protocol_exclusive::<DevicePath>(handle)
.context("failed to open filesystem device path")?;
let path = path.deref();
return utils::device_path_root(path).context("failed to get device path root");
}
Ok(String::new())
}

View File

@@ -21,6 +21,6 @@ pub fn generate(
if let Some(matrix) = &generator.matrix { if let Some(matrix) = &generator.matrix {
matrix::generate(context, matrix) matrix::generate(context, matrix)
} else { } else {
bail!("unknown action configuration"); bail!("unknown generator configuration");
} }
} }

View File

@@ -4,12 +4,17 @@ use crate::config::PhaseConfiguration;
use crate::context::{RootContext, SproutContext}; use crate::context::{RootContext, SproutContext};
use anyhow::{Context, Result, bail}; use anyhow::{Context, Result, bail};
use log::info; use log::info;
use std::collections::BTreeMap;
use std::ops::Deref;
use std::rc::Rc; use std::rc::Rc;
use uefi::proto::device_path::LoadedImageDevicePath;
use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly};
pub mod actions; pub mod actions;
pub mod config; pub mod config;
pub mod context; pub mod context;
pub mod drivers; pub mod drivers;
pub mod extractors;
pub mod generators; pub mod generators;
pub mod setup; pub mod setup;
pub mod utils; pub mod utils;
@@ -37,7 +42,19 @@ fn main() -> Result<()> {
bail!("unsupported configuration version: {}", config.version); bail!("unsupported configuration version: {}", config.version);
} }
let mut root = RootContext::new(); let mut root = {
let current_image_device_path_protocol = uefi::boot::open_protocol_exclusive::<
LoadedImageDevicePath,
>(uefi::boot::image_handle())
.context("failed to get loaded image device path")?;
let loaded_image_path = current_image_device_path_protocol.deref().to_boxed();
info!(
"loaded image path: {}",
loaded_image_path.to_string(DisplayOnly(false), AllowShortcuts(false))?
);
RootContext::new(loaded_image_path)
};
root.actions_mut().extend(config.actions.clone()); root.actions_mut().extend(config.actions.clone());
let mut context = SproutContext::new(root); let mut context = SproutContext::new(root);
@@ -46,6 +63,17 @@ fn main() -> Result<()> {
drivers::load(context.clone(), &config.drivers).context("failed to load drivers")?; drivers::load(context.clone(), &config.drivers).context("failed to load drivers")?;
let mut extracted = BTreeMap::new();
for (name, extractor) in &config.extractors {
let value = extractors::extract(context.clone(), extractor)
.context(format!("failed to extract value {}", name))?;
info!("extracted value {}: {}", name, value);
extracted.insert(name.clone(), value);
}
let mut context = context.fork();
context.insert(&extracted);
let context = context.freeze();
phase(context.clone(), &config.phases.startup)?; phase(context.clone(), &config.phases.startup)?;
let mut all_entries = Vec::new(); let mut all_entries = Vec::new();

View File

@@ -1,9 +1,10 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use uefi::CString16; use std::ops::Deref;
use uefi::fs::{FileSystem, Path}; use uefi::fs::{FileSystem, Path};
use uefi::proto::device_path::text::{AllowShortcuts, DevicePathFromText, DisplayOnly}; use uefi::proto::device_path::text::{AllowShortcuts, DevicePathFromText, DisplayOnly};
use uefi::proto::device_path::{DevicePath, PoolDevicePath}; use uefi::proto::device_path::{DevicePath, PoolDevicePath};
use uefi::proto::media::fs::SimpleFileSystem; use uefi::proto::media::fs::SimpleFileSystem;
use uefi::{CString16, Handle};
pub mod framebuffer; pub mod framebuffer;
pub mod linux_media_initrd; pub mod linux_media_initrd;
@@ -43,14 +44,84 @@ pub fn device_path_root(path: &DevicePath) -> Result<String> {
Ok(path) Ok(path)
} }
pub fn read_file_contents(path: &str) -> Result<Vec<u8>> { pub fn device_path_subpath(path: &DevicePath) -> Result<String> {
let fs = uefi::boot::open_protocol_exclusive::<SimpleFileSystem>( let path = path
uefi::boot::get_handle_for_protocol::<SimpleFileSystem>() .node_iter()
.context("no filesystem protocol")?, .filter_map(|item| {
) let item = item.to_string(DisplayOnly(false), AllowShortcuts(false));
if item
.as_ref()
.map(|item| item.to_string().contains("("))
.unwrap_or(false)
{
None
} else {
Some(item.unwrap_or_default())
}
})
.map(|item| item.to_string())
.collect::<Vec<_>>()
.join("\\");
Ok(path)
}
pub struct ResolvedPath {
pub root_path: Box<DevicePath>,
pub sub_path: Box<DevicePath>,
pub full_path: Box<DevicePath>,
pub filesystem_handle: Handle,
}
pub fn resolve_path(default_root_path: &DevicePath, input: &str) -> Result<ResolvedPath> {
let mut path = text_to_device_path(input).context("failed to convert text to path")?;
let path_has_device = path
.node_iter()
.next()
.map(|it| {
it.to_string(DisplayOnly(false), AllowShortcuts(false))
.unwrap_or_default()
})
.map(|it| it.to_string().contains("("))
.unwrap_or(false);
if !path_has_device {
let mut input = input.to_string();
if !input.starts_with("\\") {
input.insert(0, '\\');
}
input.insert_str(
0,
device_path_root(default_root_path)
.context("failed to get loaded image device root")?
.as_str(),
);
path = text_to_device_path(input.as_str()).context("failed to convert text to path")?;
}
let path = path.to_boxed();
let root = device_path_root(path.as_ref()).context("failed to convert root to path")?;
let root_path = text_to_device_path(root.as_str())
.context("failed to convert root to path")?
.to_boxed();
let mut root_path = root_path.as_ref();
let handle = uefi::boot::locate_device_path::<SimpleFileSystem>(&mut root_path)
.context("failed to locate filesystem device path")?;
let subpath = device_path_subpath(path.deref()).context("failed to get device subpath")?;
Ok(ResolvedPath {
root_path: root_path.to_boxed(),
sub_path: text_to_device_path(subpath.as_str())?.to_boxed(),
full_path: path,
filesystem_handle: handle,
})
}
pub fn read_file_contents(default_root_path: &DevicePath, input: &str) -> Result<Vec<u8>> {
let resolved = resolve_path(default_root_path, input)?;
let fs = uefi::boot::open_protocol_exclusive::<SimpleFileSystem>(resolved.filesystem_handle)
.context("unable to open filesystem protocol")?; .context("unable to open filesystem protocol")?;
let mut fs = FileSystem::new(fs); let mut fs = FileSystem::new(fs);
let path = CString16::try_from(path).context("unable to convert path to CString16")?; let path = resolved
.sub_path
.to_string(DisplayOnly(false), AllowShortcuts(false))?;
let content = fs.read(Path::new(&path)); let content = fs.read(Path::new(&path));
content.context("unable to read file contents") content.context("unable to read file contents")
} }