diff --git a/Makefile b/Makefile index 14f952c2..cc472b09 100644 --- a/Makefile +++ b/Makefile @@ -11,11 +11,10 @@ AUTO_TEST ?= none BOOT_LOADER ?= grub BOOT_PROTOCOL ?= multiboot2 BUILD_SYSCALL_TEST ?= 0 -EMULATE_IOMMU ?= 0 ENABLE_KVM ?= 1 EXTRA_BLOCKLISTS_DIRS ?= "" INTEL_TDX ?= 0 -QEMU_MACHINE ?= q35 +SCHEMA ?= "" RELEASE_MODE ?= 0 SKIP_GRUB_MENU ?= 1 SYSCALL_TEST_DIR ?= /tmp @@ -27,13 +26,13 @@ CARGO_OSDK_ARGS := --arch=$(ARCH) ifeq ($(AUTO_TEST), syscall) BUILD_SYSCALL_TEST := 1 -CARGO_OSDK_ARGS += --kcmd_args="SYSCALL_TEST_DIR=$(SYSCALL_TEST_DIR)" -CARGO_OSDK_ARGS += --kcmd_args="EXTRA_BLOCKLISTS_DIRS=$(EXTRA_BLOCKLISTS_DIRS)" -CARGO_OSDK_ARGS += --init_args="/opt/syscall_test/run_syscall_test.sh" +CARGO_OSDK_ARGS += --kcmd_args+="SYSCALL_TEST_DIR=$(SYSCALL_TEST_DIR)" +CARGO_OSDK_ARGS += --kcmd_args+="EXTRA_BLOCKLISTS_DIRS=$(EXTRA_BLOCKLISTS_DIRS)" +CARGO_OSDK_ARGS += --init_args+="/opt/syscall_test/run_syscall_test.sh" else ifeq ($(AUTO_TEST), regression) -CARGO_OSDK_ARGS += --init_args="/regression/run_regression_test.sh" +CARGO_OSDK_ARGS += --init_args+="/regression/run_regression_test.sh" else ifeq ($(AUTO_TEST), boot) -CARGO_OSDK_ARGS += --init_args="/regression/boot_hello.sh" +CARGO_OSDK_ARGS += --init_args+="/regression/boot_hello.sh" endif ifeq ($(RELEASE_MODE), 1) @@ -44,26 +43,21 @@ ifeq ($(INTEL_TDX), 1) CARGO_OSDK_ARGS += --features intel_tdx endif -CARGO_OSDK_ARGS += --boot.loader="$(BOOT_LOADER)" -CARGO_OSDK_ARGS += --boot.protocol="$(BOOT_PROTOCOL)" -CARGO_OSDK_ARGS += --qemu.machine="$(QEMU_MACHINE)" +CARGO_OSDK_ARGS += --bootloader="$(BOOT_LOADER)" +CARGO_OSDK_ARGS += --boot_protocol="$(BOOT_PROTOCOL)" -ifeq ($(QEMU_MACHINE), microvm) -CARGO_OSDK_ARGS += --select microvm +ifneq ($(SCHEMA), "") +CARGO_OSDK_ARGS += --schema $(SCHEMA) endif # To test the linux-efi-handover64 boot protocol, we need to use Debian's # GRUB release, which is installed in /usr/bin in our Docker image. ifeq ($(BOOT_PROTOCOL), linux-efi-handover64) -CARGO_OSDK_ARGS += --boot.grub-mkrescue=/usr/bin/grub-mkrescue -endif - -ifeq ($(EMULATE_IOMMU), 1) -CARGO_OSDK_ARGS += --select iommu +CARGO_OSDK_ARGS += --grub-mkrescue=/usr/bin/grub-mkrescue endif ifeq ($(ENABLE_KVM), 1) -CARGO_OSDK_ARGS += --qemu.args="--enable-kvm" +CARGO_OSDK_ARGS += --qemu_args+="--enable-kvm" endif # Pass make variables to all subdirectory makes diff --git a/OSDK.toml b/OSDK.toml index 5aabc823..dfce63d2 100644 --- a/OSDK.toml +++ b/OSDK.toml @@ -1,3 +1,7 @@ +[project] +type = "kernel" + +[run] kcmd_args = [ "SHELL=/bin/sh", "LOGNAME=root", @@ -8,20 +12,16 @@ kcmd_args = [ ] init_args = ["sh", "-l"] initramfs = "regression/build/initramfs.cpio.gz" - -[boot] -protocol = "multiboot2" -loader = "grub" +boot_protocol = "multiboot2" +bootloader = "grub" ovmf = "/root/ovmf/release" -opensbi = "/root/opensbi-virt-firmware" - -[qemu.'cfg(arch="x86_64")'] -machine = "q35" drive_files = [ ["regression/build/ext2.img", "if=none,format=raw,id=x0"], ["regression/build/exfat.img", "if=none,format=raw,id=x1"], ] -args = [ +qemu_args = [ + "-machine q35,kernel-irqchip=split", + "-cpu Icelake-Server,+x2apic", "--no-reboot", "-m 2G", "-nographic", @@ -40,13 +40,36 @@ args = [ "-device virtconsole,chardev=mux", ] -[qemu.'cfg(arch="x86_64", select="iommu")'] -machine = "q35" +[test] +boot_protocol = "multiboot" +bootloader = "qemu" +qemu_args = [ + "-machine q35,kernel-irqchip=split", + "-cpu Icelake-Server,+x2apic", + "--no-reboot", + "-m 2G", + "-nographic", + "-serial chardev:mux", + "-monitor chardev:mux", + "-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log", + "-display none", + "-device isa-debug-exit,iobase=0xf4,iosize=0x04", + "-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080", + "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off", + "-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off", + "-device virtio-serial-pci,disable-legacy=on,disable-modern=off", + "-device virtconsole,chardev=mux", +] + + +['cfg(arch="x86_64", schema="iommu")'.run] drive_files = [ ["regression/build/ext2.img", "if=none,format=raw,id=x0"], ["regression/build/exfat.img", "if=none,format=raw,id=x1"], ] -args = [ +qemu_args = [ + "-machine q35,kernel-irqchip=split", + "-cpu Icelake-Server,+x2apic", "--no-reboot", "-m 2G", "-nographic", @@ -67,13 +90,15 @@ args = [ "-device ioh3420,id=pcie.0,chassis=1", ] -[qemu.'cfg(arch="x86_64", select="microvm")'] -machine = "microvm" +['cfg(arch="x86_64", schema="microvm")'.run] +bootloader = "qemu" drive_files = [ ["regression/build/ext2.img", "if=none,format=raw,id=x0"], ["regression/build/exfat.img", "if=none,format=raw,id=x1"], ] -args = [ +qemu_args = [ + "-machine microvm,rtc=on", + "-cpu Icelake-Server,+x2apic", "--no-reboot", "-m 2G", "-nographic", @@ -94,9 +119,9 @@ args = [ "-device virtconsole,chardev=mux", ] -[qemu.'cfg(arch="riscv64")'] -machine = "virt" -args = [ +['cfg(arch="riscv64")'.run] +qemu_args = [ + "-machine virt", "--no-reboot", "-m 2G", "-nographic", diff --git a/docs/src/osdk/guide/create-project.md b/docs/src/osdk/guide/create-project.md index f793da5a..1dc1b1c3 100644 --- a/docs/src/osdk/guide/create-project.md +++ b/docs/src/osdk/guide/create-project.md @@ -13,7 +13,7 @@ Creating a new kernel project is simple. You only need to execute the following command: ```bash -cargo osdk new --kernel myos +cargo osdk new --type kernel myos ``` ## Creating a new library project diff --git a/docs/src/osdk/guide/run-project.md b/docs/src/osdk/guide/run-project.md index ccbe99f1..134a8221 100644 --- a/docs/src/osdk/guide/run-project.md +++ b/docs/src/osdk/guide/run-project.md @@ -8,7 +8,7 @@ Suppose you have created a new kernel project named `myos` and you are in the project directory: ```bash -cargo osdk new --kernel myos && cd myos +cargo osdk new --type kernel myos && cd myos ``` ## Build the project diff --git a/docs/src/osdk/guide/work-in-workspace.md b/docs/src/osdk/guide/work-in-workspace.md index 5e89ec5b..f083777a 100644 --- a/docs/src/osdk/guide/work-in-workspace.md +++ b/docs/src/osdk/guide/work-in-workspace.md @@ -24,7 +24,7 @@ Then, add the following content to `Cargo.toml`: The two projects can be created using the following commands: ```bash -cargo osdk new --kernel myos +cargo osdk new --type kernel myos cargo osdk new mymodule ``` diff --git a/docs/src/osdk/reference/commands/new.md b/docs/src/osdk/reference/commands/new.md index d0cc8683..05f24293 100644 --- a/docs/src/osdk/reference/commands/new.md +++ b/docs/src/osdk/reference/commands/new.md @@ -17,7 +17,7 @@ cargo osdk new [OPTIONS] ## Options -`--kernel`: +`--type kernel`: Use the kernel template. If this option is not set, the library template will be used by default. @@ -27,7 +27,7 @@ the library template will be used by default. - Create a new kernel named `myos`: ```bash -cargo osdk new --kernel myos +cargo osdk new --type kernel myos ``` - Create a new library named `mymodule`: diff --git a/osdk/Cargo.lock b/osdk/Cargo.lock index a130625d..16bcf7f7 100644 --- a/osdk/Cargo.lock +++ b/osdk/Cargo.lock @@ -80,12 +80,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitflags" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" - [[package]] name = "block-buffer" version = "0.10.4" @@ -143,7 +137,6 @@ dependencies = [ "sha2", "syn", "toml", - "which", ] [[package]] @@ -239,12 +232,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" -[[package]] -name = "either" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" - [[package]] name = "env_filter" version = "0.1.0" @@ -274,16 +261,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" -[[package]] -name = "errno" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" -dependencies = [ - "libc", - "windows-sys", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -306,15 +283,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys", -] - [[package]] name = "humantime" version = "2.1.0" @@ -353,18 +321,12 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" name = "linux-bzimage-builder" version = "0.1.0" dependencies = [ - "bitflags 1.3.2", + "bitflags", "bytemuck", "serde", "xmas-elf", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" - [[package]] name = "log" version = "0.4.20" @@ -377,12 +339,6 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - [[package]] name = "predicates" version = "3.1.0" @@ -457,19 +413,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" -[[package]] -name = "rustix" -version = "0.38.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" -dependencies = [ - "bitflags 2.4.2", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - [[package]] name = "ryu" version = "1.0.17" @@ -618,19 +561,6 @@ dependencies = [ "libc", ] -[[package]] -name = "which" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fa5e0c10bf77f44aac573e498d1a82d5fbd5e91f6fc0a99e7be4b38e85e101c" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", - "windows-sys", -] - [[package]] name = "windows-sys" version = "0.52.0" diff --git a/osdk/Cargo.toml b/osdk/Cargo.toml index 9df0715a..2cab2707 100644 --- a/osdk/Cargo.toml +++ b/osdk/Cargo.toml @@ -21,7 +21,6 @@ serde_json = "1.0.111" sha2 = "0.10.8" syn = { version = "2.0.52", features = ["extra-traits", "full", "parsing", "printing"] } toml = { version = "0.8.8", features = ["preserve_order"] } -which = "6.0.0" [dev-dependencies] assert_cmd = "2.0.13" diff --git a/osdk/README.md b/osdk/README.md index 75ae9faa..9cc374ac 100644 --- a/osdk/README.md +++ b/osdk/README.md @@ -56,7 +56,7 @@ Here we provide a simple demo to demonstrate how to create and run a simple kern With `cargo-osdk`, a kernel project can be created by one command ```bash -cargo osdk new --kernel my-first-os +cargo osdk new --type kernel my-first-os ``` Then, you can run the kernel with diff --git a/osdk/src/arch.rs b/osdk/src/arch.rs index 39b776df..ac8a0bf3 100644 --- a/osdk/src/arch.rs +++ b/osdk/src/arch.rs @@ -10,7 +10,7 @@ use std::fmt::{self, Display, Formatter}; /// element of the target triple, but akin to the "target_arch" cfg /// of Cargo: /// -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] pub enum Arch { Aarch64, X86_64, @@ -41,7 +41,7 @@ impl Arch { } } - pub fn to_str(&self) -> &'static str { + pub fn to_str(self) -> &'static str { match self { Arch::Aarch64 => "aarch64", Arch::RiscV64 => "riscv64", diff --git a/osdk/src/bundle/mod.rs b/osdk/src/bundle/mod.rs index 785b04a2..cc0e9319 100644 --- a/osdk/src/bundle/mod.rs +++ b/osdk/src/bundle/mod.rs @@ -6,6 +6,7 @@ pub mod vm_image; use bin::AsterBin; use file::{BundleFile, Initramfs}; +use std::process; use vm_image::AsterVmImage; use std::{ @@ -15,10 +16,10 @@ use std::{ }; use crate::{ + arch::Arch, cli::CargoArgs, config_manager::{ - boot::Boot, - qemu::{Qemu, QemuMachine}, + action::{ActionSettings, Bootloader}, RunConfig, }, error::Errno, @@ -38,34 +39,33 @@ pub struct Bundle { /// The osdk bundle artifact manifest that stores as `bundle.toml`. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct BundleManifest { - pub kcmd_args: Vec, pub initramfs: Option, pub aster_bin: Option, pub vm_image: Option, - pub boot: Boot, - pub qemu: Qemu, + pub settings: ActionSettings, pub cargo_args: CargoArgs, pub last_modified: SystemTime, } impl Bundle { /// This function creates a new `Bundle` without adding any files. - pub fn new( - path: impl AsRef, - kcmd_args: Vec, - boot: Boot, - qemu: Qemu, - cargo_args: CargoArgs, - ) -> Self { + pub fn new(path: impl AsRef, settings: ActionSettings, cargo_args: CargoArgs) -> Self { std::fs::create_dir_all(path.as_ref()).unwrap(); + let initramfs = if let Some(ref initramfs) = settings.initramfs { + if !initramfs.exists() { + error_msg!("initramfs file not found: {}", initramfs.display()); + process::exit(Errno::BuildCrate as _); + } + Some(Initramfs::new(initramfs).copy_to(&path)) + } else { + None + }; let mut created = Self { manifest: BundleManifest { - kcmd_args, - initramfs: None, + initramfs, aster_bin: None, vm_image: None, - boot, - qemu, + settings, cargo_args, last_modified: SystemTime::now(), }, @@ -113,16 +113,14 @@ impl Bundle { // Compare the manifest with the run configuration. // TODO: This pairwise comparison will result in some false negatives. We may // fix it by pondering upon each fields with more care. - if self.manifest.kcmd_args != config.manifest.kcmd_args - || self.manifest.boot != config.manifest.boot - || self.manifest.qemu != config.manifest.qemu + if self.manifest.settings != config.settings || self.manifest.cargo_args != config.cargo_args { return false; } // Compare the initramfs. - match (&self.manifest.initramfs, &config.manifest.initramfs) { + match (&self.manifest.initramfs, &config.settings.initramfs) { (Some(initramfs), Some(initramfs_path)) => { let config_initramfs = Initramfs::new(initramfs_path); if initramfs.sha256sum() != config_initramfs.sha256sum() { @@ -147,42 +145,46 @@ impl Bundle { error_msg!("The bundle is not compatible with the run configuration"); std::process::exit(Errno::RunBundle as _); } - let mut qemu_cmd = Command::new(config.manifest.qemu.path.clone().unwrap()); + let mut qemu_cmd = Command::new(config.settings.qemu_exe.clone().unwrap_or_else(|| { + PathBuf::from(match config.arch { + Arch::Aarch64 => "qemu-system-aarch64", + Arch::RiscV64 => "qemu-system-riscv64", + Arch::X86_64 => "qemu-system-x86_64", + }) + })); // FIXME: Arguments like "-m 2G" sould be separated into "-m" and "2G". This // is a dirty hack to make it work. Anything like space in the paths will // break this. - for arg in &config.manifest.qemu.args { + for arg in &config.settings.qemu_args { for part in arg.split_whitespace() { qemu_cmd.arg(part); } } - match config.manifest.qemu.machine { - QemuMachine::Microvm => { - qemu_cmd.arg("-machine").arg("microvm,rtc=on"); + match config.settings.bootloader { + Some(Bootloader::Qemu) => { let Some(ref aster_bin) = self.manifest.aster_bin else { - error_msg!("Kernel ELF binary is required for Microvm"); + error_msg!("Kernel ELF binary is required for direct QEMU booting"); std::process::exit(Errno::RunBundle as _); }; qemu_cmd .arg("-kernel") .arg(self.path.join(aster_bin.path())); - let Some(ref initramfs) = config.manifest.initramfs else { - error_msg!("Initramfs is required for Microvm"); - std::process::exit(Errno::RunBundle as _); + if let Some(ref initramfs) = config.settings.initramfs { + qemu_cmd.arg("-initrd").arg(initramfs); + } else { + info!("No initramfs specified"); }; - qemu_cmd.arg("-initrd").arg(initramfs); qemu_cmd .arg("-append") - .arg(config.manifest.kcmd_args.join(" ")); + .arg(config.settings.combined_kcmd_args().join(" ")); } - QemuMachine::Q35 => { - qemu_cmd.arg("-machine").arg("q35,kernel-irqchip=split"); + Some(Bootloader::Grub) => { 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("-cdrom").arg(self.path.join(vm_image.path())); - if let Some(ovmf) = &config.manifest.boot.ovmf { + if let Some(ovmf) = &config.settings.ovmf { qemu_cmd.arg("-drive").arg(format!( "if=pflash,format=raw,unit=0,readonly=on,file={}", ovmf.join("OVMF_CODE.fd").display() @@ -193,31 +195,13 @@ 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); + None => { + error_msg!("Bootloader is required for QEMU booting"); + std::process::exit(Errno::RunBundle as _); } }; - qemu_cmd.arg("-cpu").arg("Icelake-Server,+x2apic"); - for drive_file in &config.manifest.qemu.drive_files { + for drive_file in &config.settings.drive_files { qemu_cmd.arg("-drive").arg(format!( "file={},{}", drive_file.path.display(), @@ -256,15 +240,6 @@ impl Bundle { self.write_manifest_to_fs(); } - /// Copy the initramfs into the bundle. - pub fn add_initramfs(&mut self, initramfs: Initramfs) { - if self.manifest.initramfs.is_some() { - panic!("initramfs already exists"); - } - self.manifest.initramfs = Some(initramfs.copy_to(&self.path)); - self.write_manifest_to_fs(); - } - fn write_manifest_to_fs(&mut self) { self.manifest.last_modified = SystemTime::now(); let manifest_file_content = toml::to_string(&self.manifest).unwrap(); diff --git a/osdk/src/cli.rs b/osdk/src/cli.rs index a4ff6df7..faa1f7a7 100644 --- a/osdk/src/cli.rs +++ b/osdk/src/cli.rs @@ -11,8 +11,8 @@ use crate::{ execute_new_command, execute_run_command, execute_test_command, }, config_manager::{ - boot::{BootLoader, BootProtocol}, - qemu::QemuMachine, + action::{BootProtocol, Bootloader}, + manifest::ProjectType, BuildConfig, DebugConfig, RunConfig, TestConfig, }, }; @@ -96,8 +96,13 @@ pub struct ForwardedArguments { #[derive(Debug, Parser)] pub struct NewArgs { - #[arg(long, default_value = "false", help = "Use the kernel template")] - pub kernel: bool, + #[arg( + long = "type", + short = 't', + default_value = "library", + help = "The type of the project to create" + )] + pub type_: ProjectType, #[arg(name = "name", required = true)] pub crate_name: String, } @@ -189,7 +194,7 @@ pub struct CargoArgs { conflicts_with = "profile" )] pub release: bool, - #[arg(long, value_name = "FEATURES", help = "List of features to activate")] + #[arg(long, value_name = "FEATURES", help = "List of features to activate", value_delimiter = ',', num_args = 1..)] pub features: Vec, } @@ -198,57 +203,60 @@ 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", - value_name = "SELECTION" + long = "schema", + help = "Select the specific configuration schema provided in the OSDK manifest", + value_name = "SCHEMA" )] - pub select: Option, + pub schema: Option, #[arg( - long = "kcmd_args", - help = "Command line arguments for guest kernel", + long = "kcmd_args+", + require_equals = true, + help = "Extra or overriding command line arguments for guest kernel", value_name = "ARGS" )] pub kcmd_args: Vec, #[arg( - long = "init_args", - help = "Command line arguments for init process", + long = "init_args+", + require_equals = true, + help = "Extra command line arguments for init process", value_name = "ARGS" )] pub init_args: Vec, #[arg(long, help = "Path of initramfs", value_name = "PATH")] pub initramfs: Option, - #[arg(long = "boot.ovmf", help = "Path of OVMF", value_name = "PATH")] - pub boot_ovmf: Option, + #[arg(long = "ovmf", help = "Path of OVMF", value_name = "PATH")] + pub ovmf: Option, + #[arg(long = "opensbi", help = "Path of OpenSBI", value_name = "PATH")] + pub opensbi: Option, #[arg( - long = "boot.loader", + long = "bootloader", help = "Loader for booting the kernel", - value_name = "LOADER" + value_name = "BOOTLOADER" )] - pub boot_loader: Option, + pub bootloader: Option, #[arg( - long = "boot.grub-mkrescue", + long = "grub-mkrescue", help = "Path of grub-mkrescue", value_name = "PATH" )] - pub boot_grub_mkrescue: Option, + pub grub_mkrescue: Option, #[arg( - long = "boot.protocol", + long = "boot_protocol", help = "Protocol for booting the kernel", - value_name = "PROTOCOL" + value_name = "BOOT_PROTOCOL" )] pub boot_protocol: Option, - #[arg(long = "qemu.path", help = "Path of QEMU", value_name = "PATH")] - pub qemu_path: Option, #[arg( - long = "qemu.machine", - help = "QEMU machine type", - value_name = "MACHINE" + long = "qemu_exe", + help = "The QEMU executable file", + value_name = "FILE" )] - pub qemu_machine: Option, + pub qemu_exe: Option, #[arg( - long = "qemu.args", - help = "Arguments for running QEMU", + long = "qemu_args+", + require_equals = true, + help = "Extra arguments or overriding arguments for running QEMU", value_name = "ARGS" )] - pub qemu_args: Vec, + pub qemu_args_add: Vec, } diff --git a/osdk/src/commands/build/bin.rs b/osdk/src/commands/build/bin.rs index 43fda2dc..f531aca2 100644 --- a/osdk/src/commands/build/bin.rs +++ b/osdk/src/commands/build/bin.rs @@ -14,7 +14,7 @@ use crate::{ bin::{AsterBin, AsterBinType, AsterBzImageMeta, AsterElfMeta}, file::BundleFile, }, - config_manager::boot::BootProtocol, + config_manager::action::BootProtocol, util::get_current_crate_info, }; @@ -153,7 +153,6 @@ fn install_setup_with_arch( cmd.arg("install").arg("linux-bzimage-setup"); cmd.arg("--force"); cmd.arg("--root").arg(install_dir.as_ref()); - // TODO: Use the latest revision when modifications on the `osdk` branch is merged. cmd.arg("--git").arg(crate::util::ASTER_GIT_LINK); cmd.arg("--rev").arg(crate::util::ASTER_GIT_REV); cmd.arg("--target").arg(match arch { diff --git a/osdk/src/commands/build/grub.rs b/osdk/src/commands/build/grub.rs index 30c4b36a..6187f7e7 100644 --- a/osdk/src/commands/build/grub.rs +++ b/osdk/src/commands/build/grub.rs @@ -12,7 +12,7 @@ use crate::{ file::BundleFile, vm_image::{AsterGrubIsoImageMeta, AsterVmImage, AsterVmImageType}, }, - config_manager::{boot::BootProtocol, BuildConfig}, + config_manager::{action::BootProtocol, BuildConfig}, util::get_current_crate_info, }; @@ -24,7 +24,7 @@ pub fn create_bootdev_image( ) -> AsterVmImage { let target_name = get_current_crate_info().name; let iso_root = &target_dir.as_ref().join("iso_root"); - let protocol = &config.manifest.boot.protocol; + let protocol = &config.settings.boot_protocol; // Clear or make the iso dir. if iso_root.exists() { @@ -43,10 +43,15 @@ pub fn create_bootdev_image( // Make the kernel image and place it in the boot directory. match protocol { - BootProtocol::LinuxLegacy32 | BootProtocol::LinuxEfiHandover64 => { - make_install_bzimage(iso_root.join("boot"), &target_dir, aster_bin, protocol); + Some(BootProtocol::LinuxLegacy32) | Some(BootProtocol::LinuxEfiHandover64) => { + make_install_bzimage( + iso_root.join("boot"), + &target_dir, + aster_bin, + &protocol.clone().unwrap(), + ); } - BootProtocol::Multiboot | BootProtocol::Multiboot2 => { + _ => { // Copy the kernel image to the boot directory. let target_path = iso_root.join("boot").join(&target_name); fs::copy(aster_bin.path(), target_path).unwrap(); @@ -60,17 +65,21 @@ pub fn create_bootdev_image( None }; let grub_cfg = generate_grub_cfg( - &config.manifest.kcmd_args.join(" "), + &config.settings.combined_kcmd_args().join(" "), true, initramfs_in_image, - protocol, + &protocol.clone().unwrap_or(BootProtocol::Multiboot2), ); let grub_cfg_path = iso_root.join("boot").join("grub").join("grub.cfg"); fs::write(grub_cfg_path, grub_cfg).unwrap(); // Make the boot device CDROM image using `grub-mkrescue`. let iso_path = &target_dir.as_ref().join(target_name.to_string() + ".iso"); - let grub_mkrescue_bin = &config.manifest.boot.grub_mkrescue.clone().unwrap(); + let grub_mkrescue_bin = &config + .settings + .grub_mkrescue + .clone() + .unwrap_or_else(|| PathBuf::from("grub-mkrescue")); let mut grub_mkrescue_cmd = std::process::Command::new(grub_mkrescue_bin.as_os_str()); grub_mkrescue_cmd .arg(iso_root.as_os_str()) diff --git a/osdk/src/commands/build/mod.rs b/osdk/src/commands/build/mod.rs index 643aee6f..250c2c2b 100644 --- a/osdk/src/commands/build/mod.rs +++ b/osdk/src/commands/build/mod.rs @@ -13,11 +13,10 @@ use crate::{ base_crate::new_base_crate, bundle::{ bin::{AsterBin, AsterBinType, AsterElfMeta}, - file::Initramfs, Bundle, }, cli::CargoArgs, - config_manager::{qemu::QemuMachine, BuildConfig}, + config_manager::{action::Bootloader, BuildConfig}, error::Errno, error_msg, util::{get_current_crate_info, get_target_directory}, @@ -79,20 +78,10 @@ pub fn do_build( } let mut bundle = Bundle::new( &bundle_path, - config.manifest.kcmd_args.clone(), - config.manifest.boot.clone(), - config.manifest.qemu.clone(), + config.settings.clone(), config.cargo_args.clone(), ); - if let Some(ref initramfs) = config.manifest.initramfs { - if !initramfs.exists() { - error_msg!("initramfs file not found: {}", initramfs.display()); - process::exit(Errno::BuildCrate as _); - } - bundle.add_initramfs(Initramfs::new(initramfs)); - }; - info!("Building kernel ELF"); let aster_elf = build_kernel_elf( &config.arch, @@ -101,20 +90,17 @@ pub fn do_build( rustflags, ); - if matches!(config.manifest.qemu.machine, QemuMachine::Microvm) { + if matches!(config.settings.bootloader, Some(Bootloader::Qemu)) { let stripped_elf = strip_elf_for_qemu(&osdk_target_directory, &aster_elf); bundle.consume_aster_bin(stripped_elf); } - // TODO: A boot device is required if we use GRUB. Actually you can boot - // a multiboot kernel with Q35 machine directly without a bootloader. - // We are currently ignoring this case. - if matches!(config.manifest.qemu.machine, QemuMachine::Q35) { + if matches!(config.settings.bootloader, Some(Bootloader::Grub)) { info!("Building boot device image"); let bootdev_image = grub::create_bootdev_image( &osdk_target_directory, &aster_elf, - config.manifest.initramfs.as_ref(), + config.settings.initramfs.as_ref(), config, ); bundle.consume_vm_image(bootdev_image); @@ -134,7 +120,7 @@ fn build_kernel_elf( 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 + // Asterinas does not support PIC yet. rustflags.extend(vec![ &env_rustflags, &rustc_linker_script_arg, diff --git a/osdk/src/commands/new/kernel.OSDK.toml.template b/osdk/src/commands/new/kernel.OSDK.toml.template new file mode 100644 index 00000000..b5f711d8 --- /dev/null +++ b/osdk/src/commands/new/kernel.OSDK.toml.template @@ -0,0 +1,18 @@ +[project] +type = "kernel" + +[run] +bootloader = "grub" +ovmf = "/usr/share/OVMF" +qemu_args = [ + "-machine q35,kernel-irqchip=split", + "-cpu Icelake-Server,+x2apic", + "--no-reboot", + "-m 2G", + "-nographic", + "-serial chardev:mux", + "-monitor chardev:mux", + "-chardev stdio,id=mux,mux=on,signal=off", + "-display none", + "-device isa-debug-exit,iobase=0xf4,iosize=0x04", +] diff --git a/osdk/src/commands/new/lib.OSDK.toml.template b/osdk/src/commands/new/lib.OSDK.toml.template new file mode 100644 index 00000000..d3549d8c --- /dev/null +++ b/osdk/src/commands/new/lib.OSDK.toml.template @@ -0,0 +1,17 @@ +[project] +type = "library" + +[test] +bootloader = "qemu" +qemu_args = [ + "-machine q35,kernel-irqchip=split", + "-cpu Icelake-Server,+x2apic", + "--no-reboot", + "-m 2G", + "-nographic", + "-serial chardev:mux", + "-monitor chardev:mux", + "-chardev stdio,id=mux,mux=on,signal=off", + "-display none", + "-device isa-debug-exit,iobase=0xf4,iosize=0x04", +] diff --git a/osdk/src/commands/new/mod.rs b/osdk/src/commands/new/mod.rs index 11ba22a8..ac8f1134 100644 --- a/osdk/src/commands/new/mod.rs +++ b/osdk/src/commands/new/mod.rs @@ -4,6 +4,7 @@ use std::{fs, path::PathBuf, process, str::FromStr}; use crate::{ cli::NewArgs, + config_manager::manifest::ProjectType, error::Errno, error_msg, util::{aster_crate_dep, cargo_new_lib, get_cargo_metadata}, @@ -13,13 +14,9 @@ pub fn execute_new_command(args: &NewArgs) { cargo_new_lib(&args.crate_name); let cargo_metadata = get_cargo_metadata(Some(&args.crate_name), None::<&[&str]>).unwrap(); add_manifest_dependencies(&cargo_metadata, &args.crate_name); - create_osdk_manifest(&cargo_metadata); + create_osdk_manifest(&cargo_metadata, &args.type_); exclude_osdk_base(&cargo_metadata); - if args.kernel { - write_kernel_template(&cargo_metadata, &args.crate_name); - } else { - write_library_template(&cargo_metadata, &args.crate_name); - } + write_src_template(&cargo_metadata, &args.crate_name, &args.type_); add_rust_toolchain(&cargo_metadata); } @@ -83,7 +80,7 @@ fn exclude_osdk_base(metadata: &serde_json::Value) { fs::write(workspace_manifest_path, content).unwrap(); } -fn create_osdk_manifest(cargo_metadata: &serde_json::Value) { +fn create_osdk_manifest(cargo_metadata: &serde_json::Value, type_: &ProjectType) { let osdk_manifest_path = { let workspace_root = get_workspace_root(cargo_metadata); PathBuf::from(workspace_root).join("OSDK.toml") @@ -95,42 +92,34 @@ fn create_osdk_manifest(cargo_metadata: &serde_json::Value) { } // Create `OSDK.toml` for the workspace - // FIXME: we need ovmf for grub-efi, the user may not have it. - // The apt OVMF repo installs to `/usr/share/OVMF` - fs::write( - osdk_manifest_path, - r#" -[boot] -ovmf = "/usr/share/OVMF" -protocol = "multiboot" -[qemu] -machine = "q35" -args = [ - "--no-reboot", - "-m 2G", - "-nographic", - "-serial chardev:mux", - "-monitor chardev:mux", - "-chardev stdio,id=mux,mux=on,signal=off", - "-display none", - "-device isa-debug-exit,iobase=0xf4,iosize=0x04", -] -"#, - ) - .unwrap(); -} - -/// Write the default content of `src/kernel.rs`, with contents in provided template. -fn write_kernel_template(cargo_metadata: &serde_json::Value, crate_name: &str) { - let src_path = get_src_path(cargo_metadata, crate_name); - let contents = include_str!("kernel.template"); - fs::write(src_path, contents).unwrap(); + let contents = match type_ { + ProjectType::Kernel => { + include_str!("kernel.OSDK.toml.template") + } + ProjectType::Library => { + include_str!("lib.OSDK.toml.template") + } + ProjectType::Module => { + todo!() + } + }; + fs::write(osdk_manifest_path, contents).unwrap(); } /// Write the default content of `src/lib.rs`, with contents in provided template. -fn write_library_template(cargo_metadata: &serde_json::Value, crate_name: &str) { +fn write_src_template(cargo_metadata: &serde_json::Value, crate_name: &str, type_: &ProjectType) { let src_path = get_src_path(cargo_metadata, crate_name); - let contents = include_str!("lib.template"); + let contents = match type_ { + ProjectType::Kernel => { + include_str!("kernel.template") + } + ProjectType::Library => { + include_str!("lib.template") + } + ProjectType::Module => { + todo!() + } + }; fs::write(src_path, contents).unwrap(); } diff --git a/osdk/src/commands/run.rs b/osdk/src/commands/run.rs index 77fd1d46..c5f74760 100644 --- a/osdk/src/commands/run.rs +++ b/osdk/src/commands/run.rs @@ -28,7 +28,7 @@ pub fn execute_run_command(config: &RunConfig) { let existing_bundle = Bundle::load(&default_bundle_directory); let config = RunConfig { - manifest: { + settings: { if config.gdb_server_args.is_gdb_enabled { let qemu_gdb_args: Vec<_> = { let gdb_stub_addr = config.gdb_server_args.gdb_server_addr.as_str(); @@ -55,14 +55,14 @@ pub fn execute_run_command(config: &RunConfig) { let qemu_gdb_args: Vec<_> = qemu_gdb_args .into_iter() - .filter(|arg| !config.manifest.qemu.args.iter().any(|x| x == arg)) + .filter(|arg| !config.settings.qemu_args.iter().any(|x| x == arg)) .map(|x| x.to_string()) .collect(); - let mut manifest = config.manifest.clone(); - manifest.qemu.args.extend(qemu_gdb_args); - manifest + let mut settings = config.settings.clone(); + settings.qemu_args.extend(qemu_gdb_args); + settings } else { - config.manifest.clone() + config.settings.clone() } }, ..config.clone() @@ -95,8 +95,8 @@ pub fn execute_run_command(config: &RunConfig) { } let required_build_config = BuildConfig { - arch: config.arch.clone(), - manifest: config.manifest.clone(), + arch: config.arch, + settings: config.settings.clone(), cargo_args: config.cargo_args.clone(), }; diff --git a/osdk/src/commands/test.rs b/osdk/src/commands/test.rs index 077352b9..dc55b533 100644 --- a/osdk/src/commands/test.rs +++ b/osdk/src/commands/test.rs @@ -56,8 +56,8 @@ 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(), + arch: config.arch, + settings: config.settings.clone(), cargo_args: config.cargo_args.clone(), }; let original_dir = std::env::current_dir().unwrap(); @@ -73,8 +73,8 @@ 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(), + arch: config.arch, + settings: required_build_config.settings.clone(), cargo_args: required_build_config.cargo_args.clone(), gdb_server_args: GdbServerArgs::default(), }; diff --git a/osdk/src/config_manager/action.rs b/osdk/src/config_manager/action.rs new file mode 100644 index 00000000..db4b1ba6 --- /dev/null +++ b/osdk/src/config_manager/action.rs @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MPL-2.0 + +use std::{path::PathBuf, process}; + +use clap::ValueEnum; + +use super::{qemu, unix_args::apply_kv_array}; + +use crate::{config_manager::OsdkArgs, error::Errno, error_msg}; + +/// The settings for an action (running or testing). +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ActionSettings { + /// Command line arguments for the guest kernel + #[serde(default)] + pub kcmd_args: Vec, + /// Command line arguments for the guest init process + #[serde(default)] + pub init_args: Vec, + /// The path of initramfs + pub initramfs: Option, + pub bootloader: Option, + pub boot_protocol: Option, + /// The path of `grub_mkrecue`. Only be `Some(_)` if `loader` is `Bootloader::grub` + pub grub_mkrescue: Option, + /// 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, + /// QEMU's available machines appended with various machine configurations + pub qemu_machine: Option, + /// The additional arguments for running QEMU, except `-cpu` and `-machine` + #[serde(default)] + pub qemu_args: Vec, + /// The additional drive files attaching to QEMU + #[serde(default)] + pub drive_files: Vec, + /// The path of qemu + pub qemu_exe: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ValueEnum)] +#[serde(rename_all = "kebab-case")] +pub enum Bootloader { + Grub, + Qemu, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ValueEnum)] +#[serde(rename_all = "kebab-case")] +pub enum BootProtocol { + LinuxEfiHandover64, + LinuxLegacy32, + Multiboot, + Multiboot2, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct DriveFile { + pub path: PathBuf, + pub append: String, +} + +impl ActionSettings { + pub fn canonicalize_paths(&mut self, cur_dir: impl AsRef) { + macro_rules! canonicalize_path { + ($path:expr) => {{ + let path = if $path.is_relative() { + cur_dir.as_ref().join($path) + } else { + $path.clone() + }; + path.canonicalize().unwrap_or_else(|_| { + error_msg!("File specified but not found: {:#?}", path); + process::exit(Errno::ParseMetadata as _); + }) + }}; + } + macro_rules! canonicalize_optional_path { + ($path:expr) => { + if let Some(path_inner) = &$path { + Some(canonicalize_path!(path_inner)) + } else { + None + } + }; + } + self.initramfs = canonicalize_optional_path!(self.initramfs); + self.grub_mkrescue = canonicalize_optional_path!(self.grub_mkrescue); + self.ovmf = canonicalize_optional_path!(self.ovmf); + self.qemu_exe = canonicalize_optional_path!(self.qemu_exe); + self.opensbi = canonicalize_optional_path!(self.opensbi); + for drive_file in &mut self.drive_files { + drive_file.path = canonicalize_path!(&drive_file.path); + } + } + + pub fn apply_cli_args(&mut self, args: &OsdkArgs) { + macro_rules! apply { + ($item:expr, $arg:expr) => { + if let Some(arg) = $arg.clone() { + $item = Some(arg); + } + }; + } + + apply!(self.initramfs, &args.initramfs); + apply!(self.ovmf, &args.ovmf); + apply!(self.opensbi, &args.opensbi); + apply!(self.grub_mkrescue, &args.grub_mkrescue); + apply!(self.bootloader, &args.bootloader); + apply!(self.boot_protocol, &args.boot_protocol); + apply!(self.qemu_exe, &args.qemu_exe); + + apply_kv_array(&mut self.kcmd_args, &args.kcmd_args, "=", &[]); + for init_arg in &args.init_args { + for seperated_arg in init_arg.split(' ') { + self.init_args.push(seperated_arg.to_string()); + } + } + + qemu::apply_qemu_args_addition(&mut self.qemu_args, &args.qemu_args_add); + } + + pub fn combined_kcmd_args(&self) -> Vec { + let mut kcmd_args = self.kcmd_args.clone(); + kcmd_args.push("--".to_owned()); + kcmd_args.extend(self.init_args.clone()); + kcmd_args + } +} diff --git a/osdk/src/config_manager/boot.rs b/osdk/src/config_manager/boot.rs deleted file mode 100644 index 50388ddd..00000000 --- a/osdk/src/config_manager/boot.rs +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -use std::{path::PathBuf, process}; - -use crate::{error::Errno, error_msg}; - -/// Arguments for creating bootdev image and how to boot with vmm. -#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub struct Boot { - #[serde(default)] - pub loader: BootLoader, - #[serde(default)] - pub protocol: BootProtocol, - /// The path of `grub_mkrecue`. Only be `Some(_)` if `loader` is `BootLoader::grub`. - pub grub_mkrescue: Option, - /// 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)] -#[serde(rename_all = "kebab-case")] -pub enum BootLoader { - #[default] - Grub, - Qemu, -} - -impl<'a> From<&'a str> for BootLoader { - fn from(value: &'a str) -> Self { - match value { - "grub" => Self::Grub, - "qemu" => Self::Qemu, - _ => { - error_msg!("`{}` is not a valid option for `boot.loader`. Allowed options are `grub` and `qemu`.",value); - process::exit(Errno::ParseMetadata as _); - } - } - } -} - -#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum BootProtocol { - LinuxEfiHandover64, - LinuxLegacy32, - Multiboot, - #[default] - Multiboot2, -} - -impl<'a> From<&'a str> for BootProtocol { - fn from(value: &'a str) -> Self { - match value { - "linux-efi-handover64" => Self::LinuxEfiHandover64, - "linux-legacy32" => Self::LinuxLegacy32, - "multiboot" => Self::Multiboot, - "multiboot2" => Self::Multiboot2, - _ => { - error_msg!("`{}` is not a valid option for `boot.protocol`. Allowed options are `linux-efi-handover64`, `linux-legacy32`, `multiboot`, `multiboot2`", value); - process::exit(Errno::ParseMetadata as _); - } - } - } -} diff --git a/osdk/src/config_manager/cfg.rs b/osdk/src/config_manager/cfg.rs index 3431d5c4..cbd396c0 100644 --- a/osdk/src/config_manager/cfg.rs +++ b/osdk/src/config_manager/cfg.rs @@ -33,8 +33,7 @@ impl CfgParseError { } } -/// This is to allow constructions like `Cfg::from([("arch", "foo"), ("select", "bar")])`. -/// Making things easier for testing. +/// This allows literal constructions like `Cfg::from([("arch", "foo"), ("schema", "bar")])`. impl From<[(K, V); N]> for Cfg where K: Into, @@ -50,7 +49,7 @@ where } impl Cfg { - pub fn new() -> Self { + pub fn empty() -> Self { Self(BTreeMap::new()) } @@ -77,17 +76,8 @@ impl Cfg { 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); + pub fn map(&self) -> &BTreeMap { + &self.0 } } @@ -110,10 +100,10 @@ mod test { #[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(); + let cfg = Cfg::from([("arch", "x86_64"), ("schema", "foo")]); + let cfg1 = Cfg::from_str("cfg(arch = \"x86_64\", schema=\"foo\", )").unwrap(); + let cfg2 = Cfg::from_str("cfg(arch=\"x86_64\",schema=\"foo\")").unwrap(); + let cfg3 = Cfg::from_str("cfg( arch=\"x86_64\", schema=\"foo\" )").unwrap(); assert_eq!(cfg, cfg1); assert_eq!(cfg, cfg2); assert_eq!(cfg, cfg3); @@ -121,17 +111,17 @@ mod test { #[test] fn test_cfg_display() { - let cfg = Cfg::from([("arch", "x86_64"), ("select", "foo")]); + let cfg = Cfg::from([("arch", "x86_64"), ("schema", "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_string, "cfg(arch=\"x86_64\", schema=\"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(); + assert!(Cfg::from_str("fg(,,,,arch=\"x86_64 \", schema=\"foo\")").is_err()); + assert!(Cfg::from_str("cfg(arch=\"x86_64\", schema=\"foo\"").is_err()); + assert!(Cfg::from_str("cfgarch=x86_64,,, schema=\"foo\") ").is_err()); } } diff --git a/osdk/src/config_manager/manifest.rs b/osdk/src/config_manager/manifest.rs index edd4fff2..fd6b37f3 100644 --- a/osdk/src/config_manager/manifest.rs +++ b/osdk/src/config_manager/manifest.rs @@ -1,163 +1,256 @@ // SPDX-License-Identifier: MPL-2.0 -use std::{ - path::{Path, PathBuf}, - process, -}; +use std::{collections::BTreeMap, fmt, path::Path, process}; -use serde::Deserialize; +use clap::ValueEnum; +use serde::{de, Deserialize, Deserializer, Serialize}; -use super::{ - boot::Boot, - qemu::{CfgQemu, Qemu}, -}; -use crate::config_manager::cfg::Cfg; -use crate::{error::Errno, error_msg}; +use super::{action::ActionSettings, cfg::Cfg}; -/// The osdk manifest from configuration file and command line arguments. +use crate::{config_manager::Arch, error::Errno, error_msg}; + +/// The settings for the actions summarized from the command line arguments +/// and the configuration file `OSDK.toml`. #[derive(Debug, Clone)] pub struct OsdkManifest { - pub kcmd_args: Vec, - pub initramfs: Option, - pub boot: Boot, - pub qemu: Qemu, + pub project: Project, + pub run: Option, + pub test: Option, } -impl OsdkManifest { - pub fn from_toml_manifest( - toml_manifest: TomlManifest, - arch: Option, - selection: Option, - ) -> Self { - let TomlManifest { - mut kcmd_args, - mut init_args, - initramfs, - boot, - qemu, - } = toml_manifest; - let CfgQemu { default, cfg_map } = qemu; +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Project { + #[serde(rename(serialize = "type", deserialize = "type"))] + pub type_: ProjectType, +} - let Some(cfg_map) = cfg_map else { - return Self { - kcmd_args, - initramfs, - boot, - qemu: default, - }; - }; - - 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 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 args_matches.len() > 1 { - error_msg!("Multiple CFGs matched using the command line arguments"); - process::exit(Errno::ParseMetadata as _); - } else if args_matches.len() == 1 { - qemu_args = Some(args_matches.remove(0)); - } else if args_matches.is_empty() { - qemu_args = Some(default); - } - - check_args("kcmd_args", &kcmd_args); - check_args("init_args", &init_args); - - kcmd_args.push("--".to_string()); - kcmd_args.append(&mut init_args); - - OsdkManifest { - kcmd_args, - initramfs, - boot, - qemu: qemu_args.unwrap(), - } - } - - pub fn check_canonicalize_all_paths(&mut self, manifest_file_dir: impl AsRef) { - macro_rules! canonicalize_path { - ($path:expr) => {{ - let path = if $path.is_relative() { - manifest_file_dir.as_ref().join($path) - } else { - $path.clone() - }; - path.canonicalize().unwrap_or_else(|_| { - error_msg!("File specified but not found: {:#?}", path); - process::exit(Errno::ParseMetadata as _); - }) - }}; - } - macro_rules! canonicalize_optional_path { - ($path:expr) => { - if let Some(path_inner) = &$path { - Some(canonicalize_path!(path_inner)) - } else { - None - } - }; - } - self.initramfs = canonicalize_optional_path!(self.initramfs); - self.boot.grub_mkrescue = canonicalize_optional_path!(self.boot.grub_mkrescue); - self.boot.ovmf = canonicalize_optional_path!(self.boot.ovmf); - self.qemu.path = canonicalize_optional_path!(self.qemu.path); - for drive_file in &mut self.qemu.drive_files { - drive_file.path = canonicalize_path!(&drive_file.path); - } - } +#[derive(Debug, Clone, Serialize, Deserialize, ValueEnum, PartialEq, Eq)] +#[serde(rename_all = "kebab-case")] +pub enum ProjectType { + Kernel, + #[value(alias("lib"))] + Library, + Module, } /// The osdk manifest from configuration file `OSDK.toml`. -#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone)] pub struct TomlManifest { - /// Command line arguments for guest kernel - #[serde(default)] - pub kcmd_args: Vec, - #[serde(default)] - pub init_args: Vec, - /// The path of initramfs - pub initramfs: Option, - #[serde(default)] - pub boot: Boot, - #[serde(default)] - pub qemu: CfgQemu, + pub project: Project, + cfg_map: BTreeMap, } -fn check_args(arg_name: &str, args: &[String]) { - for arg in args { - if arg.as_str() == "--" { - error_msg!("`{}` cannot have `--` as argument", arg_name); +impl TomlManifest { + /// Get the action manifest given the architecture and the schema from the command line arguments. + /// + /// If any entry in the `OSDK.toml` manifest doesn't specify an architecture, we regard it matching + /// all the architectures. + pub fn get_osdk_manifest( + &self, + path_of_self: impl AsRef, + arch: Arch, + schema: Option, + ) -> OsdkManifest { + let filtered_by_arch = self.cfg_map.iter().filter(|(cfg, _)| { + if let Some(got) = cfg.map().get("arch") { + got == &arch.to_string() + } else { + true + } + }); + + let filtered_by_schema = if let Some(schema) = schema { + filtered_by_arch + .filter(|(cfg, _)| { + if let Some(got) = cfg.map().get("schema") { + got == &schema + } else { + false + } + }) + .collect::>() + } else { + filtered_by_arch + .filter(|(cfg, _)| cfg == &&Cfg::empty()) + .collect::>() + }; + + let filtered = filtered_by_schema; + if filtered.len() > 1 { + error_msg!("Multiple entries in OSDK.toml match the given architecture and schema"); process::exit(Errno::ParseMetadata as _); } + if filtered.is_empty() { + error_msg!("No entry in OSDK.toml matches the given architecture and schema"); + process::exit(Errno::ParseMetadata as _); + } + let final_cfg_args = filtered.first().unwrap().1; + let mut run = final_cfg_args.run.clone(); + if let Some(run_inner) = &mut run { + run_inner.canonicalize_paths(&path_of_self); + } + let mut test = final_cfg_args.test.clone(); + if let Some(test_inner) = &mut test { + test_inner.canonicalize_paths(&path_of_self); + } + OsdkManifest { + project: self.project.clone(), + run, + test, + } + } +} + +/// A inner adapter for `TomlManifest` to allow the `cfg` field to be optional. +/// The fields should be identical to `TomlManifest` except the `cfg` field. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +struct CfgArgs { + pub run: Option, + pub test: Option, +} + +impl CfgArgs { + pub fn try_accept(&mut self, another: CfgArgs) { + if another.run.is_some() { + if self.run.is_some() { + error_msg!("Duplicate `run` field in OSDK.toml"); + process::exit(Errno::ParseMetadata as _); + } + self.run = another.run; + } + if another.test.is_some() { + if self.test.is_some() { + error_msg!("Duplicate `test` field in OSDK.toml"); + process::exit(Errno::ParseMetadata as _); + } + self.test = another.test; + } + } +} + +impl<'de> Deserialize<'de> for TomlManifest { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + enum Field { + Project, + Run, + Test, + Cfg(Cfg), + } + + impl<'de> Deserialize<'de> for Field { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct FieldVisitor; + + impl<'de> de::Visitor<'de> for FieldVisitor { + type Value = Field; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("`project`, `run`, `test` or cfg") + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + match v { + "project" => Ok(Field::Project), + "run" => Ok(Field::Run), + "test" => Ok(Field::Test), + v => Ok(Field::Cfg(Cfg::from_str(v).unwrap_or_else(|e| { + error_msg!("Error parsing cfg: {}", e); + process::exit(Errno::ParseMetadata as _); + }))), + } + } + } + + deserializer.deserialize_identifier(FieldVisitor) + } + } + + struct TomlManifestVisitor; + + impl<'de> de::Visitor<'de> for TomlManifestVisitor { + type Value = TomlManifest; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("struct TomlManifest") + } + + fn visit_map(self, mut map: A) -> Result + where + A: de::MapAccess<'de>, + { + let mut project: Option = None; + let default_cfg = Cfg::empty(); + let mut cfg_map = BTreeMap::::new(); + + while let Some(key) = map.next_key()? { + match key { + Field::Project => { + let value = map.next_value()?; + project = Some(value); + } + Field::Run => { + let value: ActionSettings = map.next_value()?; + cfg_map + .entry(default_cfg.clone()) + .and_modify(|v| { + v.try_accept(CfgArgs { + run: Some(value.clone()), + test: None, + }) + }) + .or_insert(CfgArgs { + run: Some(value.clone()), + test: None, + }); + } + Field::Test => { + let value: ActionSettings = map.next_value()?; + cfg_map + .entry(default_cfg.clone()) + .and_modify(|v| { + v.try_accept(CfgArgs { + run: None, + test: Some(value.clone()), + }) + }) + .or_insert(CfgArgs { + run: None, + test: Some(value.clone()), + }); + } + Field::Cfg(cfg) => { + let value: CfgArgs = map.next_value()?; + cfg_map + .entry(cfg) + .and_modify(|v| v.try_accept(value.clone())) + .or_insert(value.clone()); + } + } + } + + Ok(TomlManifest { + project: project.unwrap_or_else(|| { + error_msg!("`project` field is required in OSDK.toml"); + process::exit(Errno::ParseMetadata as _); + }), + cfg_map, + }) + } + } + + deserializer.deserialize_struct( + "TomlManifest", + &["run", "test", "cfg"], + TomlManifestVisitor, + ) } } diff --git a/osdk/src/config_manager/mod.rs b/osdk/src/config_manager/mod.rs index 5090fecc..8d25616e 100644 --- a/osdk/src/config_manager/mod.rs +++ b/osdk/src/config_manager/mod.rs @@ -5,54 +5,47 @@ //! arguments if the arguments is missing, e.g., the path of QEMU. The final configuration is stored in `BuildConfig`, //! `RunConfig` and `TestConfig`. These `*Config` are used for `build`, `run` and `test` subcommand. -pub mod boot; +pub mod action; pub mod cfg; pub mod manifest; pub mod qemu; +pub mod unix_args; + +use action::ActionSettings; #[cfg(test)] mod test; use std::{fs, path::PathBuf, process}; -use indexmap::{IndexMap, IndexSet}; -use which::which; - -use self::{ - boot::BootLoader, - manifest::{OsdkManifest, TomlManifest}, -}; +use self::manifest::{OsdkManifest, TomlManifest}; use crate::{ arch::{get_default_arch, Arch}, cli::{BuildArgs, CargoArgs, DebugArgs, GdbServerArgs, OsdkArgs, RunArgs, TestArgs}, error::Errno, error_msg, util::get_cargo_metadata, - warn_msg, }; -fn get_final_manifest(cargo_args: &CargoArgs, osdk_args: &OsdkArgs) -> OsdkManifest { - let mut manifest = load_osdk_manifest(cargo_args, osdk_args.select.as_ref()); - apply_cli_args(&mut manifest, osdk_args); - try_fill_system_configs(&mut manifest); - manifest -} - /// Configurations for build subcommand #[derive(Debug)] pub struct BuildConfig { pub arch: Arch, - pub manifest: OsdkManifest, + pub settings: ActionSettings, pub cargo_args: CargoArgs, } impl BuildConfig { pub fn parse(args: &BuildArgs) -> Self { - let arch = args.osdk_args.arch.clone().unwrap_or_else(get_default_arch); + let arch = args.osdk_args.arch.unwrap_or_else(get_default_arch); let cargo_args = parse_cargo_args(&args.cargo_args); + let mut manifest = load_osdk_manifest(&args.cargo_args, &args.osdk_args); + if let Some(run) = manifest.run.as_mut() { + run.apply_cli_args(&args.osdk_args); + } Self { arch, - manifest: get_final_manifest(&cargo_args, &args.osdk_args), + settings: manifest.run.unwrap(), cargo_args, } } @@ -62,18 +55,22 @@ impl BuildConfig { #[derive(Debug, Clone)] pub struct RunConfig { pub arch: Arch, - pub manifest: OsdkManifest, + pub settings: ActionSettings, pub cargo_args: CargoArgs, pub gdb_server_args: GdbServerArgs, } impl RunConfig { pub fn parse(args: &RunArgs) -> Self { - let arch = args.osdk_args.arch.clone().unwrap_or_else(get_default_arch); + let arch = args.osdk_args.arch.unwrap_or_else(get_default_arch); let cargo_args = parse_cargo_args(&args.cargo_args); + let mut manifest = load_osdk_manifest(&args.cargo_args, &args.osdk_args); + if let Some(run) = manifest.run.as_mut() { + run.apply_cli_args(&args.osdk_args); + } Self { arch, - manifest: get_final_manifest(&cargo_args, &args.osdk_args), + settings: manifest.run.unwrap(), cargo_args, gdb_server_args: args.gdb_server_args.clone(), } @@ -99,27 +96,32 @@ impl DebugConfig { #[derive(Debug)] pub struct TestConfig { pub arch: Arch, - pub manifest: OsdkManifest, + pub settings: ActionSettings, pub cargo_args: CargoArgs, pub test_name: Option, } impl TestConfig { pub fn parse(args: &TestArgs) -> Self { - let arch = args.osdk_args.arch.clone().unwrap_or_else(get_default_arch); + let arch = args.osdk_args.arch.unwrap_or_else(get_default_arch); let cargo_args = parse_cargo_args(&args.cargo_args); + let manifest = load_osdk_manifest(&args.cargo_args, &args.osdk_args); + // Use run settings if test settings are not provided + let mut test = if let Some(test) = manifest.test { + test + } else { + manifest.run.unwrap() + }; + test.apply_cli_args(&args.osdk_args); Self { arch, - manifest: get_final_manifest(&cargo_args, &args.osdk_args), + settings: test, cargo_args, test_name: args.test_name.clone(), } } } -/// FIXME: I guess OSDK manifest is definitely NOT per workspace. It's per crate. When you cannot -/// find a manifest per crate, find it in the upper levels. -/// I don't bother to do it now, just fix the relpaths. fn load_osdk_manifest(cargo_args: &CargoArgs, osdk_args: &OsdkArgs) -> OsdkManifest { let feature_strings = get_feature_strings(cargo_args); let cargo_metadata = get_cargo_metadata(None::<&str>, Some(&feature_strings)).unwrap(); @@ -130,34 +132,41 @@ fn load_osdk_manifest(cargo_args: &CargoArgs, osdk_args: &OsdkArgs) -> OsdkManif .as_str() .unwrap(), ); - let manifest_path = workspace_root.join("OSDK.toml"); - let Ok(contents) = fs::read_to_string(&manifest_path) else { - error_msg!( - "Cannot read file {}", - manifest_path.to_string_lossy().to_string() - ); - process::exit(Errno::GetMetadata as _); + // Search for OSDK.toml in the current directory. If not, dive into the workspace root. + let manifest_path = PathBuf::from("OSDK.toml"); + let (contents, manifest_path) = if let Ok(contents) = fs::read_to_string("OSDK.toml") { + (contents, manifest_path) + } else { + let manifest_path = workspace_root.join("OSDK.toml"); + let Ok(contents) = fs::read_to_string(&manifest_path) else { + error_msg!( + "Cannot read file {}", + manifest_path.to_string_lossy().to_string() + ); + process::exit(Errno::GetMetadata as _); + }; + (contents, manifest_path) }; let toml_manifest: TomlManifest = toml::from_str(&contents).unwrap_or_else(|err| { + let span = err.span().unwrap(); + let wider_span = + (span.start as isize - 20).max(0) as usize..(span.end + 20).min(contents.len()); error_msg!( - "Cannot parse TOML file, {}:\n{}:\n {}", + "Cannot parse TOML file, {}. {}:{:?}:\n {}", err.message(), manifest_path.to_string_lossy().to_string(), - &contents[err.span().unwrap()], + span, + &contents[wider_span], ); process::exit(Errno::ParseMetadata as _); }); - let mut osdk_manifest = OsdkManifest::from_toml_manifest( - toml_manifest, - osdk_args - .arch - .as_ref() - .map(|s| s.to_string()), - osdk_args.select.as_ref().map(|s| s.to_string()), + let osdk_manifest = toml_manifest.get_osdk_manifest( + workspace_root, + osdk_args.arch.unwrap_or_else(get_default_arch), + osdk_args.schema.as_ref().map(|s| s.to_string()), ); - osdk_manifest.check_canonicalize_all_paths(workspace_root); osdk_manifest } @@ -188,195 +197,10 @@ fn parse_cargo_args(cargo_args: &CargoArgs) -> CargoArgs { } } -pub fn get_feature_strings(cargo_args: &CargoArgs) -> Vec { +fn get_feature_strings(cargo_args: &CargoArgs) -> Vec { cargo_args .features .iter() .map(|feature| format!("--features={}", feature)) .collect() } - -pub fn try_fill_system_configs(manifest: &mut OsdkManifest) { - if manifest.qemu.path.is_none() { - if let Ok(path) = which("qemu-system-x86_64") { - trace!("system qemu path: {:?}", path); - manifest.qemu.path = Some(path); - } else { - warn_msg!("Cannot find qemu-system-x86_64 in your system. ") - } - } - - if manifest.boot.grub_mkrescue.is_none() && manifest.boot.loader == BootLoader::Grub { - if let Ok(path) = which("grub-mkrescue") { - trace!("system grub-mkrescue path: {:?}", path); - manifest.boot.grub_mkrescue = Some(path); - } else { - warn_msg!("Cannot find grub-mkrescue in your system.") - } - } -} - -pub fn apply_cli_args(manifest: &mut OsdkManifest, args: &OsdkArgs) { - let mut init_args = split_kcmd_args(&mut manifest.kcmd_args); - apply_kv_array(&mut manifest.kcmd_args, &args.kcmd_args, "=", &[]); - init_args.append(&mut args.init_args.clone()); - - manifest.kcmd_args.push("--".to_string()); - for init_arg in init_args { - for seperated_arg in init_arg.split(' ') { - manifest.kcmd_args.push(seperated_arg.to_string()); - } - } - - apply_option(&mut manifest.initramfs, &args.initramfs); - apply_option(&mut manifest.boot.ovmf, &args.boot_ovmf); - apply_option(&mut manifest.boot.grub_mkrescue, &args.boot_grub_mkrescue); - apply_item(&mut manifest.boot.loader, &args.boot_loader); - apply_item(&mut manifest.boot.protocol, &args.boot_protocol); - apply_option(&mut manifest.qemu.path, &args.qemu_path); - apply_item(&mut manifest.qemu.machine, &args.qemu_machine); - - // check qemu_args - for arg in manifest.qemu.args.iter() { - qemu::check_qemu_arg(arg); - } - for arg in args.qemu_args.iter() { - qemu::check_qemu_arg(arg); - } - - apply_kv_array( - &mut manifest.qemu.args, - &args.qemu_args, - " ", - qemu::MULTI_VALUE_KEYS, - ); -} - -fn apply_item<'a, T: From<&'a str> + Clone>(item: &mut T, arg: &Option) { - let Some(arg) = arg.clone() else { - return; - }; - - *item = arg; -} - -fn apply_option<'a, T: From<&'a str> + Clone>(item: &mut Option, arg: &Option) { - let Some(arg) = arg.clone() else { - return; - }; - - *item = Some(arg); -} - -pub fn apply_kv_array( - array: &mut Vec, - args: &Vec, - seperator: &str, - multi_value_keys: &[&str], -) { - let multi_value_keys = { - let mut inferred_keys = infer_multi_value_keys(array, seperator); - for key in multi_value_keys { - inferred_keys.insert(key.to_string()); - } - inferred_keys - }; - - debug!("multi value keys: {:?}", multi_value_keys); - - // We use IndexMap to keep key orders - let mut key_strings = IndexMap::new(); - let mut multi_value_key_strings: IndexMap> = IndexMap::new(); - for item in array.drain(..) { - // Each key-value string has two patterns: - // 1. Seperated by separator: key value / key=value - if let Some(key) = get_key(&item, seperator) { - if multi_value_keys.contains(&key) { - if let Some(v) = multi_value_key_strings.get_mut(&key) { - v.push(item); - } else { - let v = vec![item]; - multi_value_key_strings.insert(key, v); - } - continue; - } - - key_strings.insert(key, item); - continue; - } - // 2. Only key, no value - key_strings.insert(item.clone(), item); - } - - for arg in args { - if let Some(key) = get_key(arg, seperator) { - if multi_value_keys.contains(&key) { - if let Some(v) = multi_value_key_strings.get_mut(&key) { - v.push(arg.to_owned()); - } else { - let v = vec![arg.to_owned()]; - multi_value_key_strings.insert(key, v); - } - continue; - } - - key_strings.insert(key, arg.to_owned()); - continue; - } - - key_strings.insert(arg.to_owned(), arg.to_owned()); - } - - *array = key_strings.into_iter().map(|(_, value)| value).collect(); - - for (_, mut values) in multi_value_key_strings { - array.append(&mut values); - } -} - -fn infer_multi_value_keys(array: &Vec, seperator: &str) -> IndexSet { - let mut multi_val_keys = IndexSet::new(); - - let mut occured_keys = IndexSet::new(); - for item in array { - let Some(key) = get_key(item, seperator) else { - continue; - }; - - if occured_keys.contains(&key) { - multi_val_keys.insert(key); - } else { - occured_keys.insert(key); - } - } - - multi_val_keys -} - -pub fn get_key(item: &str, seperator: &str) -> Option { - let split = item.split(seperator).collect::>(); - let len = split.len(); - if len > 2 || len == 0 { - error_msg!("`{}` is an invalid argument.", item); - process::exit(Errno::ParseMetadata as _); - } - - if len == 1 { - return None; - } - - let key = split.first().unwrap(); - - Some(key.to_string()) -} - -fn split_kcmd_args(kcmd_args: &mut Vec) -> Vec { - let seperator = "--"; - let index = kcmd_args.iter().position(|item| item.as_str() == seperator); - let Some(index) = index else { - return Vec::new(); - }; - let mut init_args = kcmd_args.split_off(index); - init_args.remove(0); - init_args -} diff --git a/osdk/src/config_manager/qemu.rs b/osdk/src/config_manager/qemu.rs index ba176321..8133aacf 100644 --- a/osdk/src/config_manager/qemu.rs +++ b/osdk/src/config_manager/qemu.rs @@ -1,181 +1,39 @@ // SPDX-License-Identifier: MPL-2.0 -use std::{collections::BTreeMap, fmt, path::PathBuf, process}; +//! A module about QEMU arguments. -use serde::{ - de::{self, Visitor}, - Deserialize, Deserializer, -}; +use std::process; + +use super::unix_args::{apply_kv_array, get_key}; -use super::cfg::Cfg; -use super::get_key; use crate::{error::Errno, error_msg}; -/// Arguments for creating bootdev image and how to boot with vmm. -#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Qemu { - /// The additional arguments for running qemu, except `-cpu` and `-machine`. - #[serde(default)] - pub args: Vec, - /// The additional drive files - #[serde(default)] - pub drive_files: Vec, - /// The `-machine` argument for running qemu. - #[serde(default)] - pub machine: QemuMachine, - /// The path of qemu. - #[serde(default)] - pub path: Option, -} - -#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct DriveFile { - #[serde(default)] - pub path: PathBuf, - #[serde(default)] - pub append: String, -} - -#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)] -pub struct CfgQemu { - pub default: Qemu, - pub cfg_map: Option>, -} - -impl CfgQemu { - pub fn new(default: Qemu, cfg_map: Option>) -> Self { - Self { default, cfg_map } +pub fn apply_qemu_args_addition(target: &mut Vec, args: &Vec) { + // check qemu_args + for arg in target.iter() { + check_qemu_arg(arg); } -} - -impl<'de> Deserialize<'de> for CfgQemu { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - enum Field { - Path, - Args, - Machine, - DriveFiles, - Cfg(Cfg), - } - - impl<'de> Deserialize<'de> for Field { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct FieldVisitor; - - impl<'de> Visitor<'de> for FieldVisitor { - type Value = Field; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("`path`, `args`, `machine`, `drive_files` or cfg") - } - - fn visit_str(self, v: &str) -> Result - where - E: de::Error, - { - match v { - "args" => Ok(Field::Args), - "machine" => Ok(Field::Machine), - "path" => Ok(Field::Path), - "drive_files" => Ok(Field::DriveFiles), - v => Ok(Field::Cfg(Cfg::from_str(v).unwrap_or_else(|e| { - error_msg!("Error parsing cfg: {}", e); - process::exit(Errno::ParseMetadata as _); - }))), - } - } - } - - deserializer.deserialize_identifier(FieldVisitor) - } - } - - struct CfgQemuVisitor; - - impl<'de> Visitor<'de> for CfgQemuVisitor { - type Value = CfgQemu; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("struct CfgQemu") - } - - fn visit_map(self, mut map: A) -> Result - where - A: de::MapAccess<'de>, - { - let mut default = Qemu::default(); - let mut cfg_map = BTreeMap::::new(); - - while let Some(key) = map.next_key()? { - match key { - Field::Args => { - default.args = map.next_value()?; - } - Field::Machine => { - default.machine = map.next_value()?; - } - Field::Path => { - default.path = map.next_value()?; - } - Field::DriveFiles => { - default.drive_files = map.next_value()?; - } - Field::Cfg(cfg) => { - let qemu_args = map.next_value()?; - cfg_map.insert(cfg, qemu_args); - } - } - } - - Ok(CfgQemu::new(default, Some(cfg_map))) - } - } - - deserializer.deserialize_struct("CfgQemu", &["default", "cfg"], CfgQemuVisitor) + for arg in args.iter() { + check_qemu_arg(arg); } -} -#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum QemuMachine { - Microvm, - #[default] - Q35, - Virt, -} - -impl<'a> From<&'a str> for QemuMachine { - fn from(value: &'a str) -> Self { - match value { - "microvm" => Self::Microvm, - "q35" => Self::Q35, - "virt" => Self::Virt, - _ => { - error_msg!("{} is not a valid option for `qemu.machine`", value); - process::exit(Errno::ParseMetadata as _); - } - } - } + apply_kv_array(target, args, " ", MULTI_VALUE_KEYS); } // Below are keys in qemu arguments. The key list is not complete. /// Keys with multiple values -pub const MULTI_VALUE_KEYS: &[&str] = &["-device", "-chardev", "-object", "-netdev", "-drive"]; +const MULTI_VALUE_KEYS: &[&str] = &[ + "-device", "-chardev", "-object", "-netdev", "-drive", "-cdrom", +]; /// Keys with only single value -pub const SINGLE_VALUE_KEYS: &[&str] = &["-m", "-serial", "-monitor", "-display"]; +const SINGLE_VALUE_KEYS: &[&str] = &["-cpu", "-machine", "-m", "-serial", "-monitor", "-display"]; /// Keys with no value -pub const NO_VALUE_KEYS: &[&str] = &["--no-reboot", "-nographic", "-enable-kvm"]; +const NO_VALUE_KEYS: &[&str] = &["--no-reboot", "-nographic", "-enable-kvm"]; /// Keys are not allowed to set in configuration files and command line -pub const NOT_ALLOWED_TO_SET_KEYS: &[&str] = &["-cpu", "-machine", "-kernel", "-initrd", "-cdrom"]; +const NOT_ALLOWED_TO_SET_KEYS: &[&str] = &["-kernel", "-initrd"]; -pub fn check_qemu_arg(arg: &str) { +fn check_qemu_arg(arg: &str) { let key = if let Some(key) = get_key(arg, " ") { key } else { diff --git a/osdk/src/config_manager/test/OSDK.toml.conditional b/osdk/src/config_manager/test/OSDK.toml.conditional deleted file mode 100644 index 1ccbc6e1..00000000 --- a/osdk/src/config_manager/test/OSDK.toml.conditional +++ /dev/null @@ -1,16 +0,0 @@ -[qemu] -args = [ - "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off", - "-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off", - "-device virtio-serial-pci,disable-legacy=on,disable-modern=off" -] - -[qemu.'cfg(arch="x86_64", select="intel_tdx")'] - -[qemu.'cfg(select="iommu")'] -args = [ - "-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on", - "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on", - "-device intel-iommu,intremap=on,device-iotlb=on", - "-device ioh3420,id=pcie.0,chassis=1", -] diff --git a/osdk/src/config_manager/test/OSDK.toml.empty b/osdk/src/config_manager/test/OSDK.toml.empty deleted file mode 100644 index e69de29b..00000000 diff --git a/osdk/src/config_manager/test/OSDK.toml.full b/osdk/src/config_manager/test/OSDK.toml.full index 644d6d9c..ab52922e 100644 --- a/osdk/src/config_manager/test/OSDK.toml.full +++ b/osdk/src/config_manager/test/OSDK.toml.full @@ -1,20 +1,110 @@ -kcmd_args = ["init=/bin/busybox", "path=/usr/local/bin"] +[project] +type = "kernel" + +[run] +kcmd_args = [ + "SHELL=/bin/sh", + "LOGNAME=root", + "HOME=/", + "USER=root", + "PATH=/bin:/benchmark", + "init=/usr/bin/busybox", +] init_args = ["sh", "-l"] - -[boot] -loader = "grub" -protocol = "multiboot2" -grub-mkrescue = "/usr/bin/grub-mkrescue" -ovmf = "/usr/bin/ovmf" - -[qemu] -machine = "q35" -args = [ - "-enable-kvm", - "-m 2G", - "-s /tmp/aster.sock", +initramfs = "/usr/bin/bash" +boot_protocol = "multiboot2" +bootloader = "grub" +ovmf = "/usr/bin/bash" +opensbi = "/usr/bin/bash" +drive_files = [ + ["/usr/bin/bash", "if=none,format=raw,id=x0"], + ["/usr/bin/bash", "if=none,format=raw,id=x1"], +] +qemu_args = [ + "-machine q35,kernel-irqchip=split", + "-cpu Icelake-Server,+x2apic", "--no-reboot", + "-m 2G", "-nographic", - "-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off", + "-serial chardev:mux", + "-monitor chardev:mux", + "-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log", + "-display none", + "-device isa-debug-exit,iobase=0xf4,iosize=0x04", + "-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap", + "-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080", + "-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,serial=vext2,disable-legacy=on,disable-modern=off", + "-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off", "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off", -] \ No newline at end of file + "-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off", + "-device virtio-serial-pci,disable-legacy=on,disable-modern=off", + "-device virtconsole,chardev=mux", +] + +['cfg(arch="x86_64", schema="iommu")'.run] +drive_files = [ + ["/usr/bin/bash", "if=none,format=raw,id=x0"], + ["/usr/bin/bash", "if=none,format=raw,id=x1"], +] +qemu_args = [ + "-machine q35,kernel-irqchip=split", + "-cpu Icelake-Server,+x2apic", + "--no-reboot", + "-m 2G", + "-nographic", + "-serial chardev:mux", + "-monitor chardev:mux", + "-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log", + "-display none", + "-device isa-debug-exit,iobase=0xf4,iosize=0x04", + "-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap", + "-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080", + "-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,serial=vext2,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on", + "-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on", + "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on", + "-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on", + "-device virtio-serial-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on", + "-device virtconsole,chardev=mux", + "-device intel-iommu,intremap=on,device-iotlb=on", + "-device ioh3420,id=pcie.0,chassis=1", +] + +['cfg(arch="x86_64", schema="microvm")'.run] +bootloader = "qemu" +drive_files = [ + ["/usr/bin/bash", "if=none,format=raw,id=x0"], + ["/usr/bin/bash", "if=none,format=raw,id=x1"], +] +qemu_args = [ + "-machine microvm,rtc=on", + "-cpu Icelake-Server,+x2apic", + "--no-reboot", + "-m 2G", + "-nographic", + "-serial chardev:mux", + "-monitor chardev:mux", + "-chardev stdio,id=mux,mux=on,signal=off,logfile=qemu.log", + "-display none", + "-device isa-debug-exit,iobase=0xf4,iosize=0x04", + "-object filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap", + "-netdev user,id=net01,hostfwd=tcp::36788-:22,hostfwd=tcp::55834-:8080", + "-nodefaults", + "-no-user-config", + "-device virtio-blk-device,drive=x0,serial=vext2", + "-device virtio-blk-device,drive=x1,serial=vexfat", + "-device virtio-keyboard-device", + "-device virtio-net-device,netdev=net01", + "-device virtio-serial-device", + "-device virtconsole,chardev=mux", +] + +['cfg(schema="intel_tdx")'.run] +qemu_exe = "/usr/bin/bash" + +['cfg(arch="riscv64")'.run] +qemu_args = [ + "-machine virt", + "--no-reboot", + "-m 2G", + "-nographic", +] diff --git a/osdk/src/config_manager/test/mod.rs b/osdk/src/config_manager/test/mod.rs index 3e211cfb..d7f5c457 100644 --- a/osdk/src/config_manager/test/mod.rs +++ b/osdk/src/config_manager/test/mod.rs @@ -1,176 +1,43 @@ // SPDX-License-Identifier: MPL-2.0 use super::*; -use crate::config_manager::cfg::Cfg; #[test] -fn split_kcmd_args_test() { - let mut kcmd_args = ["init=/bin/sh", "--", "sh", "-l"] - .iter() - .map(ToString::to_string) - .collect(); - let init_args = split_kcmd_args(&mut kcmd_args); - let expected_kcmd_args: Vec<_> = ["init=/bin/sh"].iter().map(ToString::to_string).collect(); - assert_eq!(kcmd_args, expected_kcmd_args); - let expecetd_init_args: Vec<_> = ["sh", "-l"].iter().map(ToString::to_string).collect(); - assert_eq!(init_args, expecetd_init_args); - - let mut kcmd_args = ["init=/bin/sh", "--"] - .iter() - .map(ToString::to_string) - .collect(); - let init_args = split_kcmd_args(&mut kcmd_args); - let expected_kcmd_args: Vec<_> = ["init=/bin/sh"].iter().map(ToString::to_string).collect(); - assert_eq!(kcmd_args, expected_kcmd_args); - let expecetd_init_args: Vec = Vec::new(); - assert_eq!(init_args, expecetd_init_args); - - let mut kcmd_args = ["init=/bin/sh", "shell=/bin/sh"] - .iter() - .map(ToString::to_string) - .collect(); - let init_args = split_kcmd_args(&mut kcmd_args); - let expected_kcmd_args: Vec<_> = ["init=/bin/sh", "shell=/bin/sh"] - .iter() - .map(ToString::to_string) - .collect(); - assert_eq!(kcmd_args, expected_kcmd_args); - let expecetd_init_args: Vec = Vec::new(); - assert_eq!(init_args, expecetd_init_args); -} - -#[test] -fn get_key_test() { - let string1 = "init=/bin/init"; - let key = get_key(string1, "=").unwrap(); - assert_eq!(key.as_str(), "init"); - - let string2 = "-m 2G"; - let key = get_key(string2, " ").unwrap(); - assert_eq!(key.as_str(), "-m"); - - let string3 = "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off"; - let key = get_key(string3, " ").unwrap(); - assert_eq!(key.as_str(), "-device"); - - let string4 = "-device"; - assert!(get_key(string4, " ").is_none()); -} - -#[test] -fn apply_kv_array_test() { - let qemu_args = &[ - "-enable-kvm", - "-m 2G", - "-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off", - "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off", - ]; - - let args = &["-m 100G", "-device ioh3420,id=pcie.0,chassis=1"]; - - let expected = &[ - "-enable-kvm", - "-m 100G", - "-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off", - "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off", - "-device ioh3420,id=pcie.0,chassis=1", - ]; - - let mut array = qemu_args.iter().map(ToString::to_string).collect(); - let args = args.iter().map(ToString::to_string).collect(); - apply_kv_array(&mut array, &args, " ", &["-device"]); - - let expected: Vec<_> = expected.iter().map(ToString::to_string).collect(); - assert_eq!(expected, array); -} - -#[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 deserialize_osdk_manifest() { - let content = include_str!("OSDK.toml.empty"); - let osdk_manifest: TomlManifest = toml::from_str(content).unwrap(); - assert!(osdk_manifest == TomlManifest::default()); - +fn deserialize_toml_manifest() { let content = include_str!("OSDK.toml.full"); - let osdk_manifest: TomlManifest = toml::from_str(content).unwrap(); - assert!(osdk_manifest.boot.grub_mkrescue.unwrap() == PathBuf::from("/usr/bin/grub-mkrescue")); -} - -#[test] -fn serialize_osdk_manifest() { - let manifest = TomlManifest::default(); - let contents = toml::to_string(&manifest).unwrap(); - fs::write("OSDK.toml", contents).unwrap(); - fs::remove_file("OSDK.toml").unwrap(); -} - -#[test] -fn deserialize_conditional_osdk_manifest() { - let content = include_str!("OSDK.toml.conditional"); - let manifest: TomlManifest = toml::from_str(content).unwrap(); - println!("manifest = {:?}", manifest); + let toml_manifest: TomlManifest = toml::from_str(content).unwrap(); + assert!(toml_manifest.project.type_ == manifest::ProjectType::Kernel); } #[test] fn conditional_manifest() { let toml_manifest: TomlManifest = { - let content = include_str!("OSDK.toml.conditional"); + let content = include_str!("OSDK.toml.full"); toml::from_str(content).unwrap() }; + let arch = crate::arch::Arch::X86_64; - assert!(toml_manifest.qemu.cfg_map.is_some()); - assert!(toml_manifest - .qemu - .cfg_map - .as_ref() - .unwrap() - .contains_key(&Cfg::from([("arch", "x86_64"), ("select", "intel_tdx")]))); - assert!(toml_manifest - .qemu - .cfg_map - .as_ref() - .unwrap() - .contains_key(&Cfg::from([("select", "iommu")]))); - - // Default selection - let arch = None; - let selection: Option = None; - let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), arch, selection); - assert!(manifest.qemu.args.contains(&String::from( - "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off" + // Default schema + let schema: Option = None; + let manifest = toml_manifest.get_osdk_manifest(PathBuf::from("/"), arch, schema); + assert!(manifest.run.unwrap().qemu_args.contains(&String::from( + "-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off", ))); // Iommu - let arch = None; - let selection: Option = Some("iommu".to_owned()); - let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), arch, selection); + let schema: Option = Some("iommu".to_owned()); + let manifest = toml_manifest.get_osdk_manifest(PathBuf::from("/"), arch, schema); assert!(manifest - .qemu - .args + .run + .unwrap() + .qemu_args .contains(&String::from("-device ioh3420,id=pcie.0,chassis=1"))); // Tdx - let arch = Some("x86_64".to_owned()); - let selection: Option = Some("intel_tdx".to_owned()); - let manifest = OsdkManifest::from_toml_manifest(toml_manifest.clone(), arch, selection); - assert!(manifest.qemu.args.is_empty()); + let schema: Option = Some("intel_tdx".to_owned()); + let manifest = toml_manifest.get_osdk_manifest(PathBuf::from("/"), arch, schema); + assert_eq!( + manifest.run.unwrap().qemu_exe.unwrap(), + PathBuf::from("/usr/bin/bash") + ); } diff --git a/osdk/src/config_manager/unix_args.rs b/osdk/src/config_manager/unix_args.rs new file mode 100644 index 00000000..5d8eef47 --- /dev/null +++ b/osdk/src/config_manager/unix_args.rs @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! This module contains utilities for manipulating common Unix command-line arguments. + +use std::process; + +use indexmap::{IndexMap, IndexSet}; + +use crate::{error::Errno, error_msg}; + +/// Apply key-value pairs to an array of strings. +/// +/// The provided arguments will be appended to the array if the key is not already present or if the key is a multi-value key. +/// Otherwise, the value will be updated. +pub fn apply_kv_array( + array: &mut Vec, + args: &Vec, + seperator: &str, + multi_value_keys: &[&str], +) { + let multi_value_keys = { + let mut inferred_keys = infer_multi_value_keys(array, seperator); + for key in multi_value_keys { + inferred_keys.insert(key.to_string()); + } + inferred_keys + }; + + debug!("multi value keys: {:?}", multi_value_keys); + + // We use IndexMap to keep key orders + let mut key_strings = IndexMap::new(); + let mut multi_value_key_strings: IndexMap> = IndexMap::new(); + for item in array.drain(..) { + // Each key-value string has two patterns: + // 1. Seperated by separator: key value / key=value + if let Some(key) = get_key(&item, seperator) { + if multi_value_keys.contains(&key) { + if let Some(v) = multi_value_key_strings.get_mut(&key) { + v.push(item); + } else { + let v = vec![item]; + multi_value_key_strings.insert(key, v); + } + continue; + } + + key_strings.insert(key, item); + continue; + } + // 2. Only key, no value + key_strings.insert(item.clone(), item); + } + + for arg in args { + if let Some(key) = get_key(arg, seperator) { + if multi_value_keys.contains(&key) { + if let Some(v) = multi_value_key_strings.get_mut(&key) { + v.push(arg.to_owned()); + } else { + let v = vec![arg.to_owned()]; + multi_value_key_strings.insert(key, v); + } + continue; + } + + key_strings.insert(key, arg.to_owned()); + continue; + } + + key_strings.insert(arg.to_owned(), arg.to_owned()); + } + + *array = key_strings.into_iter().map(|(_, value)| value).collect(); + + for (_, mut values) in multi_value_key_strings { + array.append(&mut values); + } +} + +fn infer_multi_value_keys(array: &Vec, seperator: &str) -> IndexSet { + let mut multi_val_keys = IndexSet::new(); + + let mut occured_keys = IndexSet::new(); + for item in array { + let Some(key) = get_key(item, seperator) else { + continue; + }; + + if occured_keys.contains(&key) { + multi_val_keys.insert(key); + } else { + occured_keys.insert(key); + } + } + + multi_val_keys +} + +pub fn get_key(item: &str, seperator: &str) -> Option { + let split = item.split(seperator).collect::>(); + let len = split.len(); + if len > 2 || len == 0 { + error_msg!("`{}` is an invalid argument.", item); + process::exit(Errno::ParseMetadata as _); + } + + if len == 1 { + return None; + } + + let key = split.first().unwrap(); + + Some(key.to_string()) +} + +#[cfg(test)] +pub mod test { + use super::*; + + #[test] + fn test_get_key() { + let string1 = "init=/bin/init"; + let key = get_key(string1, "=").unwrap(); + assert_eq!(key.as_str(), "init"); + + let string2 = "-m 2G"; + let key = get_key(string2, " ").unwrap(); + assert_eq!(key.as_str(), "-m"); + + let string3 = "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off"; + let key = get_key(string3, " ").unwrap(); + assert_eq!(key.as_str(), "-device"); + + let string4 = "-device"; + assert!(get_key(string4, " ").is_none()); + } + + #[test] + fn test_apply_kv_array() { + let qemu_args = &[ + "-enable-kvm", + "-m 2G", + "-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off", + "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off", + ]; + + let args = &["-m 100G", "-device ioh3420,id=pcie.0,chassis=1"]; + + let expected = &[ + "-enable-kvm", + "-m 100G", + "-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off", + "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off", + "-device ioh3420,id=pcie.0,chassis=1", + ]; + + let mut array = qemu_args.iter().map(ToString::to_string).collect(); + let args = args.iter().map(ToString::to_string).collect(); + apply_kv_array(&mut array, &args, " ", &["-device"]); + + let expected: Vec<_> = expected.iter().map(ToString::to_string).collect(); + assert_eq!(expected, array); + } +} diff --git a/osdk/src/util.rs b/osdk/src/util.rs index 13714565..fb075dca 100644 --- a/osdk/src/util.rs +++ b/osdk/src/util.rs @@ -14,7 +14,7 @@ use quote::ToTokens; /// FIXME: We should publish the asterinas crates to a public registry /// and use the published version in the generated Cargo.toml. pub const ASTER_GIT_LINK: &str = "https://github.com/asterinas/asterinas"; -pub const ASTER_GIT_REV: &str = "d390365"; +pub const ASTER_GIT_REV: &str = "1069006"; pub fn aster_crate_dep(crate_name: &str) -> String { format!( "{} = {{ git = \"{}\", rev = \"{}\" }}", diff --git a/osdk/tests/commands/new.rs b/osdk/tests/commands/new.rs index 706267e9..bae21c41 100644 --- a/osdk/tests/commands/new.rs +++ b/osdk/tests/commands/new.rs @@ -14,7 +14,7 @@ fn create_kernel_in_workspace() { remove_dir_all(WORKSPACE_NAME).unwrap(); } create_workspace(WORKSPACE_NAME, &[KERNEL_NAME]); - let mut cargo_osdk = cargo_osdk(["new", "--kernel", KERNEL_NAME]); + let mut cargo_osdk = cargo_osdk(["new", "--type", "kernel", KERNEL_NAME]); cargo_osdk.current_dir(WORKSPACE_NAME); let output = cargo_osdk.output().unwrap(); assert_success(&output); @@ -51,7 +51,7 @@ fn create_two_crates_in_workspace() { add_member_to_workspace(WORKSPACE_NAME, KERNEL_NAME); // Create kernel crate - let mut command = cargo_osdk(["new", "--kernel", KERNEL_NAME]); + let mut command = cargo_osdk(["new", "--type", "kernel", KERNEL_NAME]); command.current_dir(WORKSPACE_NAME); let output = command.output().unwrap(); assert_success(&output); diff --git a/osdk/tests/examples_in_book/create_os_projects.rs b/osdk/tests/examples_in_book/create_os_projects.rs index 3c31dab4..93e3a1b9 100644 --- a/osdk/tests/examples_in_book/create_os_projects.rs +++ b/osdk/tests/examples_in_book/create_os_projects.rs @@ -15,7 +15,7 @@ fn create_a_kernel_project() { fs::remove_dir_all(&kernel_path).unwrap(); } - cargo_osdk(&["new", "--kernel", kernel]) + cargo_osdk(&["new", "--type", "kernel", kernel]) .current_dir(workdir) .unwrap(); diff --git a/osdk/tests/examples_in_book/test_and_run_projects.rs b/osdk/tests/examples_in_book/test_and_run_projects.rs index 19a0118e..b3dc8dd3 100644 --- a/osdk/tests/examples_in_book/test_and_run_projects.rs +++ b/osdk/tests/examples_in_book/test_and_run_projects.rs @@ -15,7 +15,7 @@ fn create_and_run_kernel() { fs::remove_dir_all(&os_dir).unwrap(); } - let mut command = cargo_osdk(&["new", "--kernel", os_name]); + let mut command = cargo_osdk(&["new", "--type", "kernel", os_name]); command.current_dir(work_dir); command.ok().unwrap(); diff --git a/osdk/tests/examples_in_book/work_in_workspace.rs b/osdk/tests/examples_in_book/work_in_workspace.rs index 2b465cd5..f9fe7f2a 100644 --- a/osdk/tests/examples_in_book/work_in_workspace.rs +++ b/osdk/tests/examples_in_book/work_in_workspace.rs @@ -29,7 +29,9 @@ fn work_in_workspace() { // Create a kernel project and a library project let kernel = "myos"; let module = "mymodule"; - cargo_osdk(&["new", "--kernel", kernel]).ok().unwrap(); + cargo_osdk(&["new", "--type", "kernel", kernel]) + .ok() + .unwrap(); cargo_osdk(&["new", module]).ok().unwrap(); // Add a test function to mymodule/src/lib.rs diff --git a/osdk/tests/bin.rs b/osdk/tests/integration.rs similarity index 94% rename from osdk/tests/bin.rs rename to osdk/tests/integration.rs index 46493233..5827a9e4 100644 --- a/osdk/tests/bin.rs +++ b/osdk/tests/integration.rs @@ -7,5 +7,4 @@ mod cli; mod commands; mod examples_in_book; -mod integration; mod util; diff --git a/osdk/tests/integration/OSDK.toml.conditional b/osdk/tests/integration/OSDK.toml.conditional deleted file mode 100644 index 791e6143..00000000 --- a/osdk/tests/integration/OSDK.toml.conditional +++ /dev/null @@ -1,16 +0,0 @@ -[qemu] -args = [ - "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off", - "-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off", - "-device virtio-serial-pci,disable-legacy=on,disable-modern=off" -] - -[qemu.'cfg(select="intel_tdx")'] - -[qemu.'cfg(select="iommu")'] -args = [ - "-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on", - "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on", - "-device intel-iommu,intremap=on,device-iotlb=on", - "-device ioh3420,id=pcie.0,chassis=1", -] diff --git a/osdk/tests/integration/mod.rs b/osdk/tests/integration/mod.rs deleted file mode 100644 index 40018629..00000000 --- a/osdk/tests/integration/mod.rs +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -#![allow(unused)] - -use std::{ - fs, - path::{Path, PathBuf}, -}; - -use crate::util::{assert_success, cargo_osdk, create_workspace}; - -#[test] -fn build_with_default_manifest() { - let workspace = "/tmp/workspace_foo"; - if Path::new(workspace).exists() { - fs::remove_dir_all(workspace).unwrap(); - } - - let kernel_name: &str = "foo_os"; - create_workspace(workspace, &[kernel_name]); - create_osdk_kernel(kernel_name, workspace); - cargo_osdk_build(PathBuf::from(workspace).join(kernel_name), &[]); - - fs::remove_dir_all(workspace).unwrap(); -} - -#[test] -fn build_with_conditional_manifest() { - let workspace = "/tmp/workspace_bar"; - if Path::new(workspace).exists() { - fs::remove_dir_all(workspace).unwrap(); - } - - let kernel_name: &str = "bar_os"; - create_workspace(workspace, &[kernel_name]); - create_osdk_kernel_with_features(kernel_name, &["intel_tdx", "iommu"], workspace); - let contents = include_str!("OSDK.toml.conditional"); - let path = PathBuf::from(workspace).join("OSDK.toml"); - fs::write(path, contents).unwrap(); - - cargo_osdk_build( - PathBuf::from(workspace).join(kernel_name), - &["--profile", "release", "--features", "iommu"], - ); - - fs::remove_dir_all(workspace).unwrap(); -} - -fn create_osdk_kernel(name: &str, current_dir: &str) { - let output = cargo_osdk(&["new", "--kernel", name]) - .current_dir(current_dir) - .output() - .unwrap(); - assert_success(&output); -} - -fn create_osdk_kernel_with_features(name: &str, features: &[&str], current_dir: &str) { - create_osdk_kernel(name, current_dir); - let manifest_path = PathBuf::from(current_dir).join(name).join("Cargo.toml"); - let contents = fs::read_to_string(&manifest_path).unwrap(); - let mut manifest: toml::Table = toml::from_str(&contents).unwrap(); - - let mut features_table = toml::Table::new(); - for feature in features { - features_table.insert(feature.to_string(), toml::Value::Array(Vec::new())); - } - manifest.insert("features".to_string(), toml::Value::Table(features_table)); - - fs::write(&manifest_path, manifest.to_string()).unwrap(); -} - -fn cargo_osdk_build>(current_dir: P, args: &[&str]) { - let mut command = cargo_osdk(&["build"]); - command.args(args); - command.current_dir(current_dir); - let output = command.output().unwrap(); - assert_success(&output); -} diff --git a/osdk/tests/util/mod.rs b/osdk/tests/util/mod.rs index e0c642c1..6f456d35 100644 --- a/osdk/tests/util/mod.rs +++ b/osdk/tests/util/mod.rs @@ -59,10 +59,6 @@ pub fn create_workspace(workspace_name: &str, members: &[&str]) { let content = table.to_string(); fs::write(manefest_path, content).unwrap(); - // Create OSDK.toml - let osdk_manifest_path = PathBuf::from(workspace_name).join("OSDK.toml"); - fs::write(osdk_manifest_path, "").unwrap(); - // Create rust-toolchain.toml which is synced with the Asterinas' toolchain let rust_toolchain_path = PathBuf::from(workspace_name).join("rust-toolchain.toml"); let content = include_str!("../../../rust-toolchain.toml");