diff --git a/.gitignore b/.gitignore index 0ed877a55..f6dd2427f 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,3 @@ virtio-net.pcap # vscode launch config file .vscode/launch.json -.vscode/launch.bak diff --git a/Makefile b/Makefile index 505699ebe..14f952c2b 100644 --- a/Makefile +++ b/Makefile @@ -1,26 +1,29 @@ # SPDX-License-Identifier: MPL-2.0 +# Project-wide options. +ARCH ?= x86_64 +# End of project-wide options. + # The Makefile provides a way to run arbitrary tests in the kernel # mode using the kernel command line. # Here are the options for the auto test feature. AUTO_TEST ?= none BOOT_LOADER ?= grub BOOT_PROTOCOL ?= multiboot2 -QEMU_MACHINE ?= q35 BUILD_SYSCALL_TEST ?= 0 EMULATE_IOMMU ?= 0 ENABLE_KVM ?= 1 +EXTRA_BLOCKLISTS_DIRS ?= "" INTEL_TDX ?= 0 +QEMU_MACHINE ?= q35 +RELEASE_MODE ?= 0 SKIP_GRUB_MENU ?= 1 SYSCALL_TEST_DIR ?= /tmp -EXTRA_BLOCKLISTS_DIRS ?= "" -RELEASE_MODE ?= 0 -GDB_TCP_PORT ?= 1234 # End of auto test features. CARGO_OSDK := ~/.cargo/bin/cargo-osdk -CARGO_OSDK_ARGS := +CARGO_OSDK_ARGS := --arch=$(ARCH) ifeq ($(AUTO_TEST), syscall) BUILD_SYSCALL_TEST := 1 @@ -140,12 +143,12 @@ else ifeq ($(AUTO_TEST), boot) endif .PHONY: gdb_server -gdb_server: initramfs $(CARGO_OSDK) - @cargo osdk run $(CARGO_OSDK_ARGS) -G --vsc --gdb-server-addr :$(GDB_TCP_PORT) +gdb_server: build + @cd kernel && cargo osdk run $(CARGO_OSDK_ARGS) -G --vsc --gdb-server-addr :1234 .PHONY: gdb_client gdb_client: $(CARGO_OSDK) - @cd kernel && cargo osdk debug $(CARGO_OSDK_ARGS) --remote :$(GDB_TCP_PORT) + @cd kernel && cargo osdk debug $(CARGO_OSDK_ARGS) --remote :1234 .PHONY: test test: diff --git a/OSDK.toml b/OSDK.toml index ad9838b0f..5aabc823e 100644 --- a/OSDK.toml +++ b/OSDK.toml @@ -13,8 +13,9 @@ initramfs = "regression/build/initramfs.cpio.gz" protocol = "multiboot2" loader = "grub" ovmf = "/root/ovmf/release" +opensbi = "/root/opensbi-virt-firmware" -[qemu] +[qemu.'cfg(arch="x86_64")'] machine = "q35" drive_files = [ ["regression/build/ext2.img", "if=none,format=raw,id=x0"], @@ -39,7 +40,7 @@ args = [ "-device virtconsole,chardev=mux", ] -[qemu.'cfg(select="iommu")'] +[qemu.'cfg(arch="x86_64", select="iommu")'] machine = "q35" drive_files = [ ["regression/build/ext2.img", "if=none,format=raw,id=x0"], @@ -66,7 +67,7 @@ args = [ "-device ioh3420,id=pcie.0,chassis=1", ] -[qemu.'cfg(select="microvm")'] +[qemu.'cfg(arch="x86_64", select="microvm")'] machine = "microvm" drive_files = [ ["regression/build/ext2.img", "if=none,format=raw,id=x0"], @@ -92,3 +93,11 @@ args = [ "-device virtio-serial-device", "-device virtconsole,chardev=mux", ] + +[qemu.'cfg(arch="riscv64")'] +machine = "virt" +args = [ + "--no-reboot", + "-m 2G", + "-nographic", +] diff --git a/docs/src/kernel/advanced-instructions.md b/docs/src/kernel/advanced-instructions.md index 4c0695c04..949ccff99 100644 --- a/docs/src/kernel/advanced-instructions.md +++ b/docs/src/kernel/advanced-instructions.md @@ -74,10 +74,6 @@ Start a GDB-enabled VM of Asterinas with OSDK and wait for debugging connection: make gdb_server ``` -The server will listen at the default address specified in `Makefile`, i.e., a local TCP port `:1234`. -Change the address in `Makefile` for your convenience, -and check `cargo osdk run -h` for more details about the address. - Two options are provided to interact with the debug server. - A GDB client: start a GDB client in another terminal. diff --git a/docs/src/osdk/reference/commands/run.md b/docs/src/osdk/reference/commands/run.md index 049ac5984..e7296b9a1 100644 --- a/docs/src/osdk/reference/commands/run.md +++ b/docs/src/osdk/reference/commands/run.md @@ -23,7 +23,7 @@ Options related with debugging: Requires [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb). - `--gdb-server-addr `: The network address on which the GDB server listens, it can be either a path for the UNIX domain socket or a TCP port on an IP address. -[default: `.aster-gdb-socket`(a local UNIX socket)] +[default: .aster-gdb-socket] See [Debug Command](debug.md) to interact with the GDB server in terminal. diff --git a/osdk/Cargo.lock b/osdk/Cargo.lock index b326d69a7..a130625d9 100644 --- a/osdk/Cargo.lock +++ b/osdk/Cargo.lock @@ -138,7 +138,6 @@ dependencies = [ "linux-bzimage-builder", "log", "quote", - "regex", "serde", "serde_json", "sha2", diff --git a/osdk/Cargo.toml b/osdk/Cargo.toml index 4910ac0d8..9df0715a0 100644 --- a/osdk/Cargo.toml +++ b/osdk/Cargo.toml @@ -16,7 +16,6 @@ lazy_static = "1.4.0" linux-bzimage-builder = { path = "../framework/libs/linux-bzimage/builder" } log = "0.4.20" quote = "1.0.35" -regex = "1.10.3" serde = { version = "1.0.195", features = ["derive"] } serde_json = "1.0.111" sha2 = "0.10.8" diff --git a/osdk/src/arch.rs b/osdk/src/arch.rs new file mode 100644 index 000000000..39b776df0 --- /dev/null +++ b/osdk/src/arch.rs @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MPL-2.0 + +use clap::{builder::PossibleValue, ValueEnum}; +use std::fmt::{self, Display, Formatter}; + +/// Supported architectures. +/// +/// The target triple for each architecture is fixed and shall not +/// be assigned by the user. This is also different from the first +/// element of the target triple, but akin to the "target_arch" cfg +/// of Cargo: +/// +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum Arch { + Aarch64, + X86_64, + RiscV64, +} + +impl ValueEnum for Arch { + fn value_variants<'a>() -> &'a [Self] { + &[Arch::Aarch64, Arch::RiscV64, Arch::X86_64] + } + + fn to_possible_value(&self) -> Option { + match self { + Arch::Aarch64 => Some(PossibleValue::new(self.to_str())), + Arch::RiscV64 => Some(PossibleValue::new(self.to_str())), + Arch::X86_64 => Some(PossibleValue::new(self.to_str())), + } + } +} + +impl Arch { + /// Get the target triple for the architecture. + pub fn triple(&self) -> String { + match self { + Arch::Aarch64 => "aarch64-unknown-none".to_owned(), + Arch::RiscV64 => "riscv64gc-unknown-none-elf".to_owned(), + Arch::X86_64 => "x86_64-unknown-none".to_owned(), + } + } + + pub fn to_str(&self) -> &'static str { + match self { + Arch::Aarch64 => "aarch64", + Arch::RiscV64 => "riscv64", + Arch::X86_64 => "x86_64", + } + } +} + +impl Display for Arch { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_str()) + } +} + +/// Get the default architecture implied by the host rustc's default architecture. +pub fn get_default_arch() -> Arch { + let output = std::process::Command::new("rustc") + .arg("-vV") + .output() + .expect("Failed to run rustc to get the host target"); + let output = + std::str::from_utf8(&output.stdout).expect("`rustc -vV` didn't return utf8 output"); + + let field = "host: "; + let host = output + .lines() + .find(|l| l.starts_with(field)) + .map(|l| &l[field.len()..]) + .expect("`rustc -vV` didn't give a line for host") + .to_string(); + + match host.split('-').next() { + Some(host_arch) => match host_arch { + "aarch64" => Arch::Aarch64, + "riscv64gc" => Arch::RiscV64, + "x86_64" => Arch::X86_64, + _ => panic!("The host has an unsupported native architecture"), + }, + None => panic!("`rustc -vV` gave a host with unknown format"), + } +} diff --git a/osdk/src/base_crate/mod.rs b/osdk/src/base_crate/mod.rs index 9ebfeda08..36152423c 100644 --- a/osdk/src/base_crate/mod.rs +++ b/osdk/src/base_crate/mod.rs @@ -57,9 +57,18 @@ pub fn new_base_crate( let original_dir = std::env::current_dir().unwrap(); std::env::set_current_dir(&base_crate_path).unwrap(); - // Add linker.ld file - let linker_ld = include_str!("x86_64.ld.template"); - fs::write("x86_64.ld", linker_ld).unwrap(); + // Add linker script files + macro_rules! include_linker_script { + ([$($linker_script:literal),+]) => {$( + fs::write( + base_crate_path.as_ref().join($linker_script), + include_str!(concat!($linker_script, ".template")) + ).unwrap(); + )+}; + } + // TODO: currently just x86_64 works; add support for other architectures + // here when the framework is ready + include_linker_script!(["x86_64.ld"]); // Overrite the main.rs file let main_rs = include_str!("main.rs.template"); diff --git a/osdk/src/bundle/mod.rs b/osdk/src/bundle/mod.rs index a57edfa9d..785b04a2b 100644 --- a/osdk/src/bundle/mod.rs +++ b/osdk/src/bundle/mod.rs @@ -193,6 +193,27 @@ impl Bundle { )); } } + QemuMachine::Virt => { + qemu_cmd.arg("-machine").arg("virt"); + let Some(ref vm_image) = self.manifest.vm_image else { + error_msg!("VM image is required for QEMU booting"); + std::process::exit(Errno::RunBundle as _); + }; + qemu_cmd.arg("-kernel").arg(self.path.join(vm_image.path())); + let Some(ref initramfs) = config.manifest.initramfs else { + error_msg!("Initramfs is required for Virt machine"); + std::process::exit(Errno::RunBundle as _); + }; + qemu_cmd.arg("-initrd").arg(initramfs); + qemu_cmd + .arg("-append") + .arg(config.manifest.kcmd_args.join(" ")); + let Some(ref opensbi) = self.manifest.boot.opensbi else { + error_msg!("OpenSBI is required for Virt machine"); + std::process::exit(Errno::RunBundle as _); + }; + qemu_cmd.arg("-bios").arg(opensbi); + } }; qemu_cmd.arg("-cpu").arg("Icelake-Server,+x2apic"); diff --git a/osdk/src/cli.rs b/osdk/src/cli.rs index 54b2857bc..a4ff6df70 100644 --- a/osdk/src/cli.rs +++ b/osdk/src/cli.rs @@ -5,6 +5,7 @@ use std::path::PathBuf; use clap::{crate_version, Args, Parser}; use crate::{ + arch::Arch, commands::{ execute_build_command, execute_debug_command, execute_forwarded_command, execute_new_command, execute_run_command, execute_test_command, @@ -190,16 +191,12 @@ pub struct CargoArgs { pub release: bool, #[arg(long, value_name = "FEATURES", help = "List of features to activate")] pub features: Vec, - #[arg( - long = "config", - help = "Override a configuration value", - value_name = "KEY=VALUE" - )] - pub override_configs: Vec, } #[derive(Debug, Args)] pub struct OsdkArgs { + #[arg(long, value_name = "ARCH", help = "The architecture to build for")] + pub arch: Option, #[arg( long = "select", help = "Select the specific configuration provided in the OSDK manifest", diff --git a/osdk/src/commands/build/mod.rs b/osdk/src/commands/build/mod.rs index dd1e128c8..643aee6f8 100644 --- a/osdk/src/commands/build/mod.rs +++ b/osdk/src/commands/build/mod.rs @@ -3,12 +3,13 @@ mod bin; mod grub; -use std::{path::Path, process}; +use std::{ffi::OsString, path::Path, process}; use bin::strip_elf_for_qemu; -use super::util::{cargo, profile_name_adapter, COMMON_CARGO_ARGS, DEFAULT_TARGET_RELPATH}; +use super::util::{cargo, COMMON_CARGO_ARGS, DEFAULT_TARGET_RELPATH}; use crate::{ + arch::Arch, base_crate::new_base_crate, bundle::{ bin::{AsterBin, AsterBinType, AsterElfMeta}, @@ -93,7 +94,12 @@ pub fn do_build( }; info!("Building kernel ELF"); - let aster_elf = build_kernel_elf(&config.cargo_args, &cargo_target_directory, rustflags); + let aster_elf = build_kernel_elf( + &config.arch, + &config.cargo_args, + &cargo_target_directory, + rustflags, + ); if matches!(config.manifest.qemu.machine, QemuMachine::Microvm) { let stripped_elf = strip_elf_for_qemu(&osdk_target_directory, &aster_elf); @@ -118,19 +124,20 @@ pub fn do_build( } fn build_kernel_elf( - args: &CargoArgs, + arch: &Arch, + cargo_args: &CargoArgs, cargo_target_directory: impl AsRef, rustflags: &[&str], ) -> AsterBin { - let target = "x86_64-unknown-none"; + let target_os_string = OsString::from(&arch.triple()); + let rustc_linker_script_arg = format!("-C link-arg=-T{}.ld", arch); let env_rustflags = std::env::var("RUSTFLAGS").unwrap_or_default(); let mut rustflags = Vec::from(rustflags); // We disable RELRO and PIC here because they cause link failures rustflags.extend(vec![ &env_rustflags, - "-C link-arg=-Tx86_64.ld", - "-C code-model=kernel", + &rustc_linker_script_arg, "-C relocation-model=static", "-Z relro-level=off", // We do not really allow unwinding except for kernel testing. However, we need to specify @@ -142,26 +149,25 @@ fn build_kernel_elf( command.env_remove("RUSTUP_TOOLCHAIN"); command.env("RUSTFLAGS", rustflags.join(" ")); command.arg("build"); - command.arg("--target").arg(target); + command.arg("--target").arg(&target_os_string); command .arg("--target-dir") .arg(cargo_target_directory.as_ref()); command.args(COMMON_CARGO_ARGS); - command.arg("--profile=".to_string() + &args.profile); - for override_config in &args.override_configs { - command.arg("--config").arg(override_config); - } - + command.arg("--profile=".to_string() + &cargo_args.profile); let status = command.status().unwrap(); if !status.success() { error_msg!("Cargo build failed"); process::exit(Errno::ExecuteCommand as _); } - let aster_bin_path = cargo_target_directory.as_ref().join(target); - let aster_bin_path = aster_bin_path - .join(profile_name_adapter(&args.profile)) - .join(get_current_crate_info().name); + let aster_bin_path = cargo_target_directory.as_ref().join(&target_os_string); + let aster_bin_path = if cargo_args.profile == "dev" { + aster_bin_path.join("debug") + } else { + aster_bin_path.join(&cargo_args.profile) + } + .join(get_current_crate_info().name); AsterBin::new( aster_bin_path, diff --git a/osdk/src/commands/debug.rs b/osdk/src/commands/debug.rs index 52019beb2..5f646513d 100644 --- a/osdk/src/commands/debug.rs +++ b/osdk/src/commands/debug.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 -use crate::commands::util::{bin_file_name, profile_name_adapter}; +use crate::commands::util::{bin_file_name, profile_adapter}; use crate::config_manager::DebugConfig; use crate::util::get_target_directory; @@ -9,7 +9,7 @@ use std::process::Command; pub fn execute_debug_command(config: &DebugConfig) { let DebugConfig { cargo_args, remote } = config; - let profile = profile_name_adapter(&cargo_args.profile); + let profile = profile_adapter(&cargo_args.profile); let file_path = get_target_directory() .join("x86_64-unknown-none") .join(profile) diff --git a/osdk/src/commands/mod.rs b/osdk/src/commands/mod.rs index ab3470b57..4d01b362e 100644 --- a/osdk/src/commands/mod.rs +++ b/osdk/src/commands/mod.rs @@ -14,6 +14,8 @@ pub use self::{ run::execute_run_command, test::execute_test_command, }; +use crate::arch::get_default_arch; + /// Execute the forwarded cargo command with args containing the subcommand and its arguments. pub fn execute_forwarded_command(subcommand: &str, args: &Vec) -> ! { let mut cargo = util::cargo(); @@ -21,7 +23,7 @@ pub fn execute_forwarded_command(subcommand: &str, args: &Vec) -> ! { .arg(subcommand) .args(util::COMMON_CARGO_ARGS) .arg("--target") - .arg("x86_64-unknown-none") + .arg(get_default_arch().triple()) .args(args); let status = cargo.status().expect("Failed to execute cargo"); std::process::exit(status.code().unwrap_or(1)); diff --git a/osdk/src/commands/run.rs b/osdk/src/commands/run.rs index d5f5880f6..77fd1d46c 100644 --- a/osdk/src/commands/run.rs +++ b/osdk/src/commands/run.rs @@ -60,42 +60,16 @@ pub fn execute_run_command(config: &RunConfig) { .collect(); let mut manifest = config.manifest.clone(); manifest.qemu.args.extend(qemu_gdb_args); - - // FIXME: Disable KVM from QEMU args in debug mode. - // Currently, the QEMU GDB server does not work properly with KVM enabled. - let args_num = manifest.qemu.args.len(); - manifest.qemu.args.retain(|x| !x.contains("kvm")); - if manifest.qemu.args.len() != args_num { - println!( - "[WARNING] KVM is forced to be disabled in GDB server currently. \ - Options related with KVM are ignored." - ); - } - manifest } else { config.manifest.clone() } }, - cargo_args: { - fn is_release_profile(cfg: &RunConfig) -> bool { - cfg.cargo_args.profile == "release" || cfg.cargo_args.release - } - if config.gdb_server_args.is_gdb_enabled && is_release_profile(config) { - let mut cargo_args = config.cargo_args.clone(); - cargo_args - .override_configs - .push("profile.release.debug=true".to_owned()); - cargo_args - } else { - config.cargo_args.clone() - } - }, ..config.clone() }; let _vsc_launch_file = config.gdb_server_args.vsc_launch_file.then(|| { vsc::check_gdb_config(&config.gdb_server_args); - let profile = super::util::profile_name_adapter(&config.cargo_args.profile); + let profile = super::util::profile_adapter(&config.cargo_args.profile); vsc::VscLaunchConfig::new(profile, &config.gdb_server_args.gdb_server_addr) }); @@ -121,9 +95,11 @@ pub fn execute_run_command(config: &RunConfig) { } let required_build_config = BuildConfig { + arch: config.arch.clone(), manifest: config.manifest.clone(), cargo_args: config.cargo_args.clone(), }; + let bundle = create_base_and_build( default_bundle_directory, &osdk_target_directory, diff --git a/osdk/src/commands/test.rs b/osdk/src/commands/test.rs index bbaa9e665..077352b98 100644 --- a/osdk/src/commands/test.rs +++ b/osdk/src/commands/test.rs @@ -56,6 +56,7 @@ pub static KTEST_CRATE_WHITELIST: Option<&[&str]> = Some(&{:#?}); let target_name = get_current_crate_info().name; let default_bundle_directory = osdk_target_directory.join(target_name); let required_build_config = BuildConfig { + arch: config.arch.clone(), manifest: config.manifest.clone(), cargo_args: config.cargo_args.clone(), }; @@ -72,6 +73,7 @@ pub static KTEST_CRATE_WHITELIST: Option<&[&str]> = Some(&{:#?}); std::env::set_current_dir(original_dir).unwrap(); let required_run_config = RunConfig { + arch: config.arch.clone(), manifest: required_build_config.manifest.clone(), cargo_args: required_build_config.cargo_args.clone(), gdb_server_args: GdbServerArgs::default(), diff --git a/osdk/src/commands/util.rs b/osdk/src/commands/util.rs index 036998be0..d6f2647ff 100644 --- a/osdk/src/commands/util.rs +++ b/osdk/src/commands/util.rs @@ -15,7 +15,7 @@ pub fn cargo() -> Command { Command::new("cargo") } -pub fn profile_name_adapter(profile: &str) -> &str { +pub fn profile_adapter(profile: &str) -> &str { match profile { "dev" => "debug", _ => profile, diff --git a/osdk/src/config_manager/boot.rs b/osdk/src/config_manager/boot.rs index 14ae7dd99..50388ddd0 100644 --- a/osdk/src/config_manager/boot.rs +++ b/osdk/src/config_manager/boot.rs @@ -14,8 +14,10 @@ pub struct Boot { pub protocol: BootProtocol, /// The path of `grub_mkrecue`. Only be `Some(_)` if `loader` is `BootLoader::grub`. pub grub_mkrescue: Option, - /// The path of ovmf. Only be `Some(_)` if `protocol` is `BootProtocol::LinuxEfiHandover64`. + /// The path of OVMF binaries. Only required if `protocol` is `BootProtocol::LinuxEfiHandover64`. pub ovmf: Option, + /// The path of OpenSBI binaries. Only required for RISC-V. + pub opensbi: Option, } #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/osdk/src/config_manager/cfg.rs b/osdk/src/config_manager/cfg.rs new file mode 100644 index 000000000..3431d5c42 --- /dev/null +++ b/osdk/src/config_manager/cfg.rs @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! A module for handling configurations. + +use std::{ + collections::BTreeMap, + fmt::{self, Display}, +}; + +/// A configuration that looks like "cfg(k1=v1, k2=v2, ...)". +#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Serialize)] +pub struct Cfg(BTreeMap); + +#[derive(Debug)] +pub struct CfgParseError(String); + +impl fmt::Display for CfgParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Failed to parse cfg: {}", self.0) + } +} + +impl serde::ser::StdError for CfgParseError {} +impl serde::de::Error for CfgParseError { + fn custom(msg: T) -> Self { + Self(msg.to_string()) + } +} + +impl CfgParseError { + pub fn new(s: &str) -> Self { + Self(s.to_string()) + } +} + +/// This is to allow constructions like `Cfg::from([("arch", "foo"), ("select", "bar")])`. +/// Making things easier for testing. +impl From<[(K, V); N]> for Cfg +where + K: Into, + V: Into, +{ + fn from(array: [(K, V); N]) -> Self { + let mut cfg = BTreeMap::new(); + for (k, v) in array.into_iter() { + cfg.insert(k.into(), v.into()); + } + Self(cfg) + } +} + +impl Cfg { + pub fn new() -> Self { + Self(BTreeMap::new()) + } + + pub fn from_str(s: &str) -> Result { + let s = s.trim(); + + // Match the leading "cfg(" and trailing ")" + if !s.starts_with("cfg(") || !s.ends_with(')') { + return Err(CfgParseError::new(s)); + } + let s = &s[4..s.len() - 1]; + + let mut cfg = BTreeMap::new(); + for kv in s.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) { + let kv: Vec<_> = kv.split('=').collect(); + if kv.len() != 2 { + return Err(CfgParseError::new(s)); + } + cfg.insert( + kv[0].trim().to_string(), + kv[1].trim().trim_matches('\"').to_string(), + ); + } + Ok(Self(cfg)) + } + + pub fn check_allowed(&self, allowed_keys: &[&str]) -> bool { + for (k, _) in self.0.iter() { + if allowed_keys.iter().all(|&key| k != key) { + return false; + } + } + true + } + + pub fn insert(&mut self, k: String, v: String) { + self.0.insert(k, v); + } +} + +impl Display for Cfg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "cfg(")?; + for (i, (k, v)) in self.0.iter().enumerate() { + write!(f, "{}=\"{}\"", k, v)?; + if i != self.0.len() - 1 { + write!(f, ", ")?; + } + } + write!(f, ")") + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_cfg_from_str() { + let cfg = Cfg::from([("arch", "x86_64"), ("select", "foo")]); + let cfg1 = Cfg::from_str("cfg(arch = \"x86_64\", select=\"foo\", )").unwrap(); + let cfg2 = Cfg::from_str("cfg(arch=\"x86_64\",select=\"foo\")").unwrap(); + let cfg3 = Cfg::from_str("cfg( arch=\"x86_64\", select=\"foo\" )").unwrap(); + assert_eq!(cfg, cfg1); + assert_eq!(cfg, cfg2); + assert_eq!(cfg, cfg3); + } + + #[test] + fn test_cfg_display() { + let cfg = Cfg::from([("arch", "x86_64"), ("select", "foo")]); + let cfg_string = cfg.to_string(); + let cfg_back = Cfg::from_str(&cfg_string).unwrap(); + assert_eq!(cfg_string, "cfg(arch=\"x86_64\", select=\"foo\")"); + assert_eq!(cfg, cfg_back); + } + + #[test] + fn test_bad_cfg_strings() { + Cfg::from_str("cfg(,,,,arch=\"x86_64 \", select=\"foo\")").is_err(); + Cfg::from_str("cfg(arch=\"x86_64\", select=\"foo\"").is_err(); + Cfg::from_str("cfg(arch=x86_64,,, select=\"foo\") ").is_err(); + } +} diff --git a/osdk/src/config_manager/manifest.rs b/osdk/src/config_manager/manifest.rs index 5631069ed..edd4fff2d 100644 --- a/osdk/src/config_manager/manifest.rs +++ b/osdk/src/config_manager/manifest.rs @@ -5,13 +5,13 @@ use std::{ process, }; -use regex::Regex; use serde::Deserialize; use super::{ boot::Boot, qemu::{CfgQemu, Qemu}, }; +use crate::config_manager::cfg::Cfg; use crate::{error::Errno, error_msg}; /// The osdk manifest from configuration file and command line arguments. @@ -24,9 +24,10 @@ pub struct OsdkManifest { } impl OsdkManifest { - pub fn from_toml_manifest>( + pub fn from_toml_manifest( toml_manifest: TomlManifest, - selection: Option, + arch: Option, + selection: Option, ) -> Self { let TomlManifest { mut kcmd_args, @@ -35,9 +36,9 @@ impl OsdkManifest { boot, qemu, } = toml_manifest; - let CfgQemu { default, cfg } = qemu; + let CfgQemu { default, cfg_map } = qemu; - let Some(cfg) = cfg else { + let Some(cfg_map) = cfg_map else { return Self { kcmd_args, initramfs, @@ -46,32 +47,46 @@ impl OsdkManifest { }; }; - for cfg in cfg.keys() { - check_cfg(cfg); + for cfg in cfg_map.keys() { + const ALLOWED_KEYS: &[&str] = &["arch", "select"]; + if !cfg.check_allowed(ALLOWED_KEYS) { + error_msg!("cfg {:#?} is not allowed to be used in `OSDK.toml`", cfg); + process::exit(Errno::ParseMetadata as _); + } } let mut qemu_args = None; - let mut selected_args: Vec<_> = if let Some(sel) = selection { - cfg.into_iter() - .filter_map(|(cfg, args)| { - if cfg.contains(sel.as_ref()) { - Some(args) - } else { - None - } - }) - .collect() - } else { + let mut args_matches: Vec<_> = if arch.is_none() && selection.is_none() { vec![] + } else { + let mut need_cfg = Cfg::new(); + if let Some(arch) = arch { + need_cfg.insert("arch".to_string(), arch); + } + if let Some(selection) = selection { + need_cfg.insert("select".to_string(), selection); + } + cfg_map + .into_iter() + .filter_map( + |(cfg, args)| { + if need_cfg == cfg { + Some(args) + } else { + None + } + }, + ) + .collect() }; - if selected_args.len() > 1 { - error_msg!("Multiple selections are not allowed"); + if args_matches.len() > 1 { + error_msg!("Multiple CFGs matched using the command line arguments"); process::exit(Errno::ParseMetadata as _); - } else if selected_args.len() == 1 { - qemu_args = Some(selected_args.remove(0)); - } else if selected_args.is_empty() { + } else if args_matches.len() == 1 { + qemu_args = Some(args_matches.remove(0)); + } else if args_matches.is_empty() { qemu_args = Some(default); } @@ -146,28 +161,3 @@ fn check_args(arg_name: &str, args: &[String]) { } } } - -/// Check cfg that is in the form that we can accept -fn check_cfg(cfg: &str) { - if SELECT_REGEX.captures(cfg).is_none() { - error_msg!("{} is not allowed to be used after `qemu` in `OSDK.toml`. Currently we only allow cfgs like `cfg(select=\"foo\")`", cfg); - process::exit(Errno::ParseMetadata as _); - } -} - -lazy_static::lazy_static! { - pub static ref SELECT_REGEX: Regex = Regex::new(r#"cfg\(select="(?P