diff --git a/hack/dev/configs/kernel.sprout.toml b/hack/dev/configs/kernel.sprout.toml index edde4ca..4decd42 100644 --- a/hack/dev/configs/kernel.sprout.toml +++ b/hack/dev/configs/kernel.sprout.toml @@ -1,9 +1,12 @@ version = 1 +[extractors.boot.filesystem] +item = "\\EFI\\BOOT\\kernel.efi" + [actions.chainload-kernel] -chainload.path = "\\EFI\\BOOT\\kernel.efi" +chainload.path = "$boot\\EFI\\BOOT\\kernel.efi" chainload.options = ["console=hvc0"] -chainload.linux-initrd = "\\initramfs" +chainload.linux-initrd = "$boot\\initramfs" [entries.kernel] title = "Boot Linux" diff --git a/src/actions/chainload.rs b/src/actions/chainload.rs index 764a978..7b1c62b 100644 --- a/src/actions/chainload.rs +++ b/src/actions/chainload.rs @@ -20,22 +20,20 @@ pub struct ChainloadConfiguration { pub fn chainload(context: Rc, configuration: &ChainloadConfiguration) -> Result<()> { let sprout_image = uefi::boot::image_handle(); - let image_device_path_protocol = + let _image_device_path_protocol = uefi::boot::open_protocol_exclusive::(sprout_image) .context("unable to open loaded image device path protocol")?; - let mut full_path = utils::device_path_root(&image_device_path_protocol)?; - - full_path.push_str(&context.stamp(&configuration.path)); - - info!("path: {}", full_path); - - let device_path = utils::text_to_device_path(&full_path)?; + let resolved = utils::resolve_path( + context.root().loaded_image_path()?, + &context.stamp(&configuration.path), + ) + .context("failed to resolve chainload path")?; let image = uefi::boot::load_image( sprout_image, uefi::boot::LoadImageSource::FromDevicePath { - device_path: &device_path, + device_path: &resolved.full_path, boot_policy: uefi::proto::BootPolicy::ExactMatch, }, ) @@ -77,8 +75,8 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfigurat let mut initrd_handle = None; if let Some(ref linux_initrd) = configuration.linux_initrd { let initrd_path = context.stamp(linux_initrd); - let content = - utils::read_file_contents(&initrd_path).context("failed to read linux initrd")?; + let content = utils::read_file_contents(context.root().loaded_image_path()?, &initrd_path) + .context("failed to read linux initrd")?; initrd_handle = Some( register_linux_initrd(content.into_boxed_slice()) .context("failed to register linux initrd")?, diff --git a/src/actions/splash.rs b/src/actions/splash.rs index adbabde..53abf09 100644 --- a/src/actions/splash.rs +++ b/src/actions/splash.rs @@ -96,7 +96,7 @@ fn draw(image: DynamicImage) -> Result<()> { pub fn splash(context: Rc, configuration: &SplashConfiguration) -> Result<()> { 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) .decode() .context("failed to decode splash image")?; diff --git a/src/config.rs b/src/config.rs index 9d22e4f..057b2de 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,11 +1,14 @@ use crate::actions::ActionDeclaration; use crate::drivers::DriverDeclaration; +use crate::extractors::ExtractorDeclaration; use crate::generators::GeneratorDeclaration; use crate::utils; use anyhow::Context; use anyhow::Result; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +use std::ops::Deref; +use uefi::proto::device_path::LoadedImageDevicePath; #[derive(Serialize, Deserialize, Default, Clone)] pub struct RootConfiguration { @@ -16,6 +19,8 @@ pub struct RootConfiguration { #[serde(default)] pub drivers: BTreeMap, #[serde(default)] + pub extractors: BTreeMap, + #[serde(default)] pub actions: BTreeMap, #[serde(default)] pub entries: BTreeMap, @@ -49,8 +54,13 @@ pub struct PhaseConfiguration { } pub fn load() -> Result { - let content = - utils::read_file_contents("sprout.toml").context("unable to read sprout.toml file")?; + let current_image_device_path_protocol = + uefi::boot::open_protocol_exclusive::(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 = toml::from_slice(&content).context("unable to parse sprout.toml file")?; Ok(config) diff --git a/src/context.rs b/src/context.rs index eacfd9d..c6964d3 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,15 +1,22 @@ use crate::actions::ActionDeclaration; +use anyhow::Result; +use anyhow::anyhow; use std::collections::{BTreeMap, BTreeSet}; use std::rc::Rc; +use uefi::proto::device_path::DevicePath; #[derive(Default)] pub struct RootContext { actions: BTreeMap, + loaded_image_path: Option>, } impl RootContext { - pub fn new() -> Self { - Default::default() + pub fn new(loaded_image_device_path: Box) -> Self { + RootContext { + actions: BTreeMap::new(), + loaded_image_path: Some(loaded_image_device_path), + } } pub fn actions(&self) -> &BTreeMap { @@ -19,6 +26,12 @@ impl RootContext { pub fn actions_mut(&mut self) -> &mut BTreeMap { &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 { diff --git a/src/extractors.rs b/src/extractors.rs new file mode 100644 index 0000000..6deeee9 --- /dev/null +++ b/src/extractors.rs @@ -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, +} + +pub fn extract(context: Rc, extractor: &ExtractorDeclaration) -> Result { + if let Some(filesystem) = &extractor.filesystem { + filesystem::extract(context, filesystem) + } else { + bail!("unknown extractor configuration"); + } +} diff --git a/src/extractors/filesystem.rs b/src/extractors/filesystem.rs new file mode 100644 index 0000000..3f36a87 --- /dev/null +++ b/src/extractors/filesystem.rs @@ -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, + pub item: Option, +} + +pub fn extract( + context: Rc, + device: &FileSystemExtractorConfiguration, +) -> Result { + let handles = uefi::boot::find_handles::() + .context("failed to find filesystem handles")?; + for handle in handles { + let mut filesystem = uefi::boot::open_protocol_exclusive::(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::() + .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::(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()) +} diff --git a/src/generators.rs b/src/generators.rs index 647b3da..478e409 100644 --- a/src/generators.rs +++ b/src/generators.rs @@ -21,6 +21,6 @@ pub fn generate( if let Some(matrix) = &generator.matrix { matrix::generate(context, matrix) } else { - bail!("unknown action configuration"); + bail!("unknown generator configuration"); } } diff --git a/src/main.rs b/src/main.rs index 6f71d8a..61ee1d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,12 +4,17 @@ use crate::config::PhaseConfiguration; use crate::context::{RootContext, SproutContext}; use anyhow::{Context, Result, bail}; use log::info; +use std::collections::BTreeMap; +use std::ops::Deref; use std::rc::Rc; +use uefi::proto::device_path::LoadedImageDevicePath; +use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly}; pub mod actions; pub mod config; pub mod context; pub mod drivers; +pub mod extractors; pub mod generators; pub mod setup; pub mod utils; @@ -37,7 +42,19 @@ fn main() -> Result<()> { 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()); let mut context = SproutContext::new(root); @@ -46,6 +63,17 @@ fn main() -> Result<()> { 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)?; let mut all_entries = Vec::new(); diff --git a/src/utils.rs b/src/utils.rs index 05f3557..4be87a4 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,9 +1,10 @@ use anyhow::{Context, Result}; -use uefi::CString16; +use std::ops::Deref; use uefi::fs::{FileSystem, Path}; use uefi::proto::device_path::text::{AllowShortcuts, DevicePathFromText, DisplayOnly}; use uefi::proto::device_path::{DevicePath, PoolDevicePath}; use uefi::proto::media::fs::SimpleFileSystem; +use uefi::{CString16, Handle}; pub mod framebuffer; pub mod linux_media_initrd; @@ -43,14 +44,84 @@ pub fn device_path_root(path: &DevicePath) -> Result { Ok(path) } -pub fn read_file_contents(path: &str) -> Result> { - let fs = uefi::boot::open_protocol_exclusive::( - uefi::boot::get_handle_for_protocol::() - .context("no filesystem protocol")?, - ) - .context("unable to open filesystem protocol")?; +pub fn device_path_subpath(path: &DevicePath) -> Result { + let path = path + .node_iter() + .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::>() + .join("\\"); + Ok(path) +} + +pub struct ResolvedPath { + pub root_path: Box, + pub sub_path: Box, + pub full_path: Box, + pub filesystem_handle: Handle, +} + +pub fn resolve_path(default_root_path: &DevicePath, input: &str) -> Result { + 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::(&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> { + let resolved = resolve_path(default_root_path, input)?; + let fs = uefi::boot::open_protocol_exclusive::(resolved.filesystem_handle) + .context("unable to open filesystem protocol")?; 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)); content.context("unable to read file contents") }