diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..729fd9d --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,6 @@ +[build] +target = "x86_64-unknown-uefi" + +[unstable] +build-std = ["core", "compiler_builtins"] +build-std-features = ["compiler-builtins-mem"] diff --git a/.gitignore b/.gitignore index 3f69aab..9139273 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +Cargo.lock /target # Infrastructure diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 0ec45c1..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,393 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "anyhow" -version = "1.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" - -[[package]] -name = "bit_field" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" - -[[package]] -name = "bitflags" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "edera-sprout-bls" -version = "0.0.28" -dependencies = [ - "anyhow", -] - -[[package]] -name = "edera-sprout-boot" -version = "0.0.28" -dependencies = [ - "anyhow", - "edera-sprout-bls", - "edera-sprout-build", - "edera-sprout-config", - "edera-sprout-eficore", - "edera-sprout-parsing", - "hex", - "jaarg", - "log", - "sha2", - "toml", - "uefi", - "uefi-raw", -] - -[[package]] -name = "edera-sprout-build" -version = "0.0.28" - -[[package]] -name = "edera-sprout-config" -version = "0.0.28" -dependencies = [ - "serde", -] - -[[package]] -name = "edera-sprout-eficore" -version = "0.0.28" -dependencies = [ - "anyhow", - "bitflags", - "log", - "shlex", - "spin", - "uefi", - "uefi-raw", -] - -[[package]] -name = "edera-sprout-parsing" -version = "0.0.28" -dependencies = [ - "hex", - "sha2", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "jaarg" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534d589df1ef528a238f4bc4b1db081a1280f3aedf2695fd8971e9853a7fa4f6" - -[[package]] -name = "libc" -version = "0.2.180" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "ptr_meta" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9a0cf95a1196af61d4f1cbdab967179516d9a4a4312af1f31948f8f6224a79" -dependencies = [ - "ptr_meta_derive", -] - -[[package]] -name = "ptr_meta_derive" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "quote" -version = "1.0.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_spanned" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" -dependencies = [ - "serde_core", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "spin" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" -dependencies = [ - "lock_api", -] - -[[package]] -name = "syn" -version = "2.0.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "toml" -version = "0.9.11+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" -dependencies = [ - "serde_core", - "serde_spanned", - "toml_datetime", - "toml_parser", - "winnow", -] - -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_parser" -version = "1.0.6+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" -dependencies = [ - "winnow", -] - -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "ucs2" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79298e11f316400c57ec268f3c2c29ac3c4d4777687955cd3d4f3a35ce7eba" -dependencies = [ - "bit_field", -] - -[[package]] -name = "uefi" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fe9058b73ee2b6559524af9e33199c13b2485ddbf3ad1181b68051cdc50c17" -dependencies = [ - "bitflags", - "cfg-if", - "log", - "ptr_meta", - "ucs2", - "uefi-macros", - "uefi-raw", - "uguid", -] - -[[package]] -name = "uefi-macros" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4687412b5ac74d245d5bfb1733ede50c31be19bf8a4b6a967a29b451bab49e67" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "uefi-raw" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f64fe59e11af447d12fd60a403c74106eb104309f34b4c6dbce6e927d97da9d" -dependencies = [ - "bitflags", - "uguid", -] - -[[package]] -name = "uguid" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8352f8c05e47892e7eaf13b34abd76a7f4aeaf817b716e88789381927f199c" - -[[package]] -name = "unicode-ident" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "winnow" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" diff --git a/Cargo.toml b/Cargo.toml index dc864e4..f3a7769 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,18 +64,22 @@ features = ["alloc", "global_allocator", "panic_handler"] # which don't handle the UEFI target properly. [profile.dev] opt-level = 2 +panic = "abort" [profile.release] lto = "thin" strip = "symbols" +panic = "abort" [profile.release-debuginfo] inherits = "release" strip = "none" debug = 1 +panic = "abort" [profile.dev-fast] inherits = "dev" strip = "debuginfo" debug = 0 opt-level = 2 +panic = "abort" diff --git a/crates/boot/src/actions/chainload.rs b/crates/boot/src/actions/chainload.rs index 2998ea5..8b4d14b 100644 --- a/crates/boot/src/actions/chainload.rs +++ b/crates/boot/src/actions/chainload.rs @@ -2,9 +2,10 @@ use crate::context::SproutContext; use crate::phases::before_handoff; use alloc::boxed::Box; use alloc::rc::Rc; +use alloc::vec::Vec; use anyhow::{Context, Result, bail}; use edera_sprout_config::actions::chainload::ChainloadConfiguration; -use edera_sprout_parsing::{combine_options, empty_is_none}; +use edera_sprout_parsing::combine_options; use eficore::bootloader_interface::BootloaderInterface; use eficore::loader::source::ImageSource; use eficore::loader::{ImageLoadRequest, ImageLoader}; @@ -62,25 +63,37 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfigurat .set_load_options(options.as_ptr() as *const u8, options.num_bytes() as u32); } - // Stamp the initrd path, if provided. - let initrd = configuration - .linux_initrd - .as_ref() - .map(|item| context.stamp(item)); - // The initrd can be None or empty, so we need to collapse that into a single Option. - let initrd = empty_is_none(initrd); - // If an initrd is provided, register it with the EFI stack. let mut initrd_handle = None; - if let Some(linux_initrd) = initrd { - let content = eficore::path::read_file_contents( - Some(context.root().loaded_image_path()?), - &linux_initrd, - ) - .context("unable to read linux initrd")?; - let handle = - MediaLoaderHandle::register(LINUX_EFI_INITRD_MEDIA_GUID, content.into_boxed_slice()) - .context("unable to register linux initrd")?; + + // Only proceed if the configuration actually contains initrd paths. + if !configuration.initrd.is_empty() { + // Initialize a master buffer to hold the combined bytes of all initrd files. + let mut content = Vec::new(); + + // Iterate through each path provided in the configuration. + for item in &configuration.initrd { + // "Stamp" the path to replace variables (like disk labels) with real values. + let stamped_path = context.stamp(item); + + // Read the file contents from the EFI partition. + let mut segment = eficore::path::read_file_contents( + Some(context.root().loaded_image_path()?), + &stamped_path, + ).context("unable to read linux initrd segment")?; + + // Append this segment's bytes to the end of our master buffer. + // The kernel will see these as a continuous stream of CPIO archives. + content.append(&mut segment); + } + + // Register the full, concatenated buffer with the EFI stack using the Linux-specific GUID. + // This allows the Linux EFI stub to discover the initrd in memory. + let handle = MediaLoaderHandle::register( + LINUX_EFI_INITRD_MEDIA_GUID, + content.into_boxed_slice() + ).context("unable to register initrd")?; + initrd_handle = Some(handle); } diff --git a/crates/boot/src/actions/edera.rs b/crates/boot/src/actions/edera.rs index 363b8a4..9b1f8f6 100644 --- a/crates/boot/src/actions/edera.rs +++ b/crates/boot/src/actions/edera.rs @@ -5,7 +5,7 @@ use alloc::{format, vec}; use anyhow::{Context, Result}; use edera_sprout_config::actions::chainload::ChainloadConfiguration; use edera_sprout_config::actions::edera::EderaConfiguration; -use edera_sprout_parsing::{build_xen_config, combine_options, empty_is_none}; +use edera_sprout_parsing::{build_xen_config, combine_options}; use eficore::media_loader::{ MediaLoaderHandle, constants::xen::{ @@ -74,12 +74,16 @@ pub fn edera(context: Rc, configuration: &EderaConfiguration) -> // Create a vector of media loaders to drop them only after this function completes. let mut media_loaders = vec![config, kernel]; - // Register the initrd if it is provided. - if let Some(initrd) = empty_is_none(configuration.initrd.as_ref()) { - let initrd = - register_media_loader_file(&context, XEN_EFI_RAMDISK_MEDIA_GUID, "initrd", initrd) - .context("unable to register initrd media loader")?; - media_loaders.push(initrd); + // Register each initrd segment, filtering out any empty strings or placeholders. + for initrd_path in configuration.initrd.iter().filter(|s| !s.is_empty()) { + let handle = register_media_loader_file( + &context, + XEN_EFI_RAMDISK_MEDIA_GUID, + "initrd", + initrd_path + ).context("unable to register initrd segment media loader")?; + + media_loaders.push(handle); } // Chainload to the Xen EFI stub. @@ -88,7 +92,7 @@ pub fn edera(context: Rc, configuration: &EderaConfiguration) -> &ChainloadConfiguration { path: configuration.xen.clone(), options: vec![], - linux_initrd: None, + initrd: vec![], }, ) .context("unable to chainload to xen"); diff --git a/crates/boot/src/autoconfigure/bls.rs b/crates/boot/src/autoconfigure/bls.rs index d4ab931..98f63f4 100644 --- a/crates/boot/src/autoconfigure/bls.rs +++ b/crates/boot/src/autoconfigure/bls.rs @@ -86,7 +86,7 @@ pub fn scan( let chainload = ChainloadConfiguration { path: format!("{}\\$chainload", root), options: vec!["$options".to_string()], - linux_initrd: Some(format!("{}\\$initrd", root)), + initrd: alloc::vec![format!("{}\\$initrd", root)], }; // Insert the chainload action into the configuration. diff --git a/crates/boot/src/autoconfigure/linux.rs b/crates/boot/src/autoconfigure/linux.rs index d7ec70c..0eab818 100644 --- a/crates/boot/src/autoconfigure/linux.rs +++ b/crates/boot/src/autoconfigure/linux.rs @@ -231,7 +231,7 @@ pub fn scan( let chainload = ChainloadConfiguration { path: "$kernel".to_string(), options: vec!["$linux-options".to_string()], - linux_initrd: Some("$initrd".to_string()), + initrd: alloc::vec!["$initrd".to_string()], }; // Insert the chainload action into the configuration. diff --git a/crates/config/src/actions/chainload.rs b/crates/config/src/actions/chainload.rs index 55c7f00..e6c0c17 100644 --- a/crates/config/src/actions/chainload.rs +++ b/crates/config/src/actions/chainload.rs @@ -1,6 +1,32 @@ use alloc::string::String; use alloc::vec::Vec; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; + +// Naming the return type vastly improves readability +type DeResult<'de, D, T> = core::result::Result>::Error>; + +// Type of vector for a list of strings +type StringList = Vec; + +// Helper function to allow "linux-initrd" to be either a single string +// or a list of strings in the TOML file. +fn string_or_vec<'de, D>(deserializer: D) -> DeResult<'de, D, StringList> +where + D: Deserializer<'de>, { + + #[derive(Deserialize)] + #[serde(untagged)] + enum StringOrVec { + String(String), + Vec(Vec), + } + + match StringOrVec::deserialize(deserializer)? { + StringOrVec::String(s) => Ok(alloc::vec![s]), + // Wrap the vector in Ok() to match the return type + StringOrVec::Vec(v) => Ok(v), + } + } /// The configuration of the chainload action. #[derive(Serialize, Deserialize, Debug, Default, Clone)] @@ -8,14 +34,14 @@ 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, + /// The options to pass to the image. /// The options are concatenated by a space and then passed to the EFI application. #[serde(default)] pub options: Vec, - /// 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")] - pub linux_initrd: Option, + + /// The path(s) to the initrd to use for the image. + /// Supports both a single string or an array of strings. + #[serde(default, rename = "linux-initrd", deserialize_with = "string_or_vec")] + pub initrd: Vec, } diff --git a/crates/config/src/actions/edera.rs b/crates/config/src/actions/edera.rs index 6577610..879fa12 100644 --- a/crates/config/src/actions/edera.rs +++ b/crates/config/src/actions/edera.rs @@ -2,9 +2,11 @@ use alloc::string::String; use alloc::vec::Vec; use serde::{Deserialize, Serialize}; -/// 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. +type StringList = Vec; + +// 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, Debug, Default, Clone)] pub struct EderaConfiguration { /// The path to the Xen hypervisor EFI image. @@ -13,7 +15,7 @@ pub struct EderaConfiguration { pub kernel: String, /// The path to the initrd to load for dom0. #[serde(default)] - pub initrd: Option, + pub initrd: StringList, /// The options to pass to the kernel. #[serde(default, rename = "kernel-options")] pub kernel_options: Vec, diff --git a/crates/parsing/src/lib.rs b/crates/parsing/src/lib.rs index ba0bb38..9606b85 100644 --- a/crates/parsing/src/lib.rs +++ b/crates/parsing/src/lib.rs @@ -84,8 +84,8 @@ pub fn build_matrix(input: &BTreeMap>) -> Vec>(options: impl Iterator) -> String { options - .flat_map(|item| empty_is_none(Some(item))) .map(|item| item.as_ref().to_string()) + .filter(|item| !item.is_empty()) .collect::>() .join(" ") } @@ -96,11 +96,6 @@ pub fn unique_hash(input: &str) -> String { hex::encode(Sha256::digest(input.as_bytes())) } -/// Filter a string-like Option `input` such that an empty string is [None]. -pub fn empty_is_none>(input: Option) -> Option { - input.filter(|input| !input.as_ref().is_empty()) -} - /// Build a Xen EFI stub configuration file from pre-stamped `xen_options` and `kernel_options`. /// The returned string is in the Xen ini-like config file format. pub fn build_xen_config(xen_options: &str, kernel_options: &str) -> String { @@ -287,21 +282,6 @@ mod tests { assert_eq!(result, ""); } - #[test] - fn empty_is_none_returns_none_for_empty_string() { - assert!(empty_is_none(Some("")).is_none()); - } - - #[test] - fn empty_is_none_returns_some_for_nonempty_string() { - assert_eq!(empty_is_none(Some("x")), Some("x")); - } - - #[test] - fn empty_is_none_passthrough_on_none() { - assert!(empty_is_none(None::<&str>).is_none()); - } - #[test] fn unique_hash_is_deterministic() { assert_eq!(unique_hash("hello"), unique_hash("hello"));