diff --git a/Makefile b/Makefile index e0ff2ab2..6962e702 100644 --- a/Makefile +++ b/Makefile @@ -97,7 +97,7 @@ KTEST_TESTABLE := \ all: build install_osdk: - @cargo install cargo-osdk --path osdk --force + @cargo install cargo-osdk --path osdk build: @make --no-print-directory -C regression diff --git a/osdk/Cargo.lock b/osdk/Cargo.lock index a1d50f5d..0db13c92 100644 --- a/osdk/Cargo.lock +++ b/osdk/Cargo.lock @@ -86,6 +86,15 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bstr" version = "1.9.1" @@ -131,10 +140,17 @@ dependencies = [ "regex", "serde", "serde_json", + "sha2", "toml", "which", ] +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "4.5.1" @@ -181,12 +197,41 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "difflib" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -238,6 +283,16 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -460,6 +515,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "strsim" version = "0.11.0" @@ -518,6 +584,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -530,6 +602,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/osdk/Cargo.toml b/osdk/Cargo.toml index 0113bf49..3d837e48 100644 --- a/osdk/Cargo.toml +++ b/osdk/Cargo.toml @@ -18,6 +18,7 @@ log = "0.4.20" regex = "1.10.3" serde = { version = "1.0.195", features = ["derive"] } serde_json = "1.0.111" +sha2 = "0.10.8" toml = { version = "0.8.8", features = ["preserve_order"] } which = "6.0.0" diff --git a/osdk/README.md b/osdk/README.md index 533354cc..8bb5756c 100644 --- a/osdk/README.md +++ b/osdk/README.md @@ -15,15 +15,25 @@ OSDK (short for Operating System Development Kit) is designed to simplify the de Currenly, `cargo-osdk` only supports x86_64 ubuntu system. -`cargo-osdk` requires the following tools to be installed: +To run a kernel with QEMU, `cargo-osdk` requires the following tools to be installed: - Rust >= 1.75.0 -- Gcc compiler +- cargo-binutils +- gcc +- qemu-system-x86_64 +- grub-mkrescue +- ovmf +- xorriso About how to install Rust, you can refer to the [official site](https://www.rust-lang.org/tools/install). -Gcc compiler can be installed by +After installing Rust, you can install Cargo tools by ```bash -apt install build-essential +cargo install cargo-binutils +``` + +Other tools can be installed by +```bash +apt install build-essential grub2-common qemu-system-x86 ovmf xorriso ``` #### Install @@ -44,17 +54,6 @@ cargo install --force cargo-osdk Here we provide a simple demo to demonstrate how to create and run a simple kernel with `cargo-osdk`. -Suppose you are on a x86_64 ubuntu machine, to run a kernel with QEMU, the following tools should be installed: -- qemu-system-x86_64 -- grub-mkrescue -- ovmf -- xorriso - -If these tools are missing, they can be installed by -```bash -apt install grub2-common qemu-system-x86 ovmf xorriso -``` - With `cargo-osdk`, a kernel project can be created by one command ```bash cargo osdk new --kernel my-first-os @@ -105,15 +104,17 @@ ovmf = "/usr/bin/ovmf" # <7> path = "/usr/bin/qemu-system-x86_64" # <8> machine = "q35" # <9> args = [ # <10> - "--enable-kvm", + "-enable-kvm", "-m 2G", - "--device virtio-keyboard-pci,disable-legacy=on,disable-modern=off" + "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off" ] [qemu.'cfg(feature="iommu")'] # <11> path = "/usr/local/sbin/qemu-kvm" # <8> machine = "q35" # <9> args = [ # <10> + "-enable-kvm", + "-m 2G", "-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on", "-device intel-iommu,intremap=on,device-iotlb=on" ] @@ -145,18 +146,7 @@ Optional. Default is `q35`. The allowed values are `q35` and `microvm`. 10. Additional arguments passed to QEMU. Optional. The default value is empty. -Each argument should be in the form `KEY VALUE` (separated by space), or `KEY` if no value is required. Some keys can appear multiple times (e.g., `--device`, `--netdev`), while other keys can appear at most once. Certain keys, such as `-cpu` and `-machine`, are not allowed to be set here as they may conflict with the internal settings of `cargo-osdk`. +Each argument should be in the form `KEY VALUE` (separated by space), or `KEY` if no value is required. Some keys can appear multiple times (e.g., `-device`, `-netdev`), while other keys can appear at most once. Certain keys, such as `-cpu` and `-machine`, are not allowed to be set here as they may conflict with the internal settings of `cargo-osdk`. 11. Conditional QEMU settings. Optional. The default value is empty. -Conditional QEMU settings allow for a condition to be specified after `qemu`. Currently, `cargo-osdk` only supports the condition `cfg(feature="FEATURE")`, which activates the QEMU settings only if the `FEATURE` is set. The `FEATURE` must be defined in the project's `Cargo.toml`. At most one conditional setting can be activated at a time. If multiple conditional settings can be activated simultaneously, `cargo-osdk` will report an error. In the future, `cargo-osdk` will support all possible conditions that [Rust conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html) supports. - - -### The framekernel architecture - -The architecture divides the OS development into two distinct realms: the safe world and the unsafe world. In the safe world, only safe Rust code is allowed, while the unsafe world can tap into the power of the unsafe keyword. At the heart of the unsafe world lies `aster-frame`, a compact framework with limited functionalities. It encapsulates essential OS operations such as booting, physical memory management, context switching, and more. - -With `aster-frame` as the foundation, higher-level OS functionalities like process management, file systems, network protocols, and even device drivers can be built upon it using only safe Rust. This segregation ensures that critical operations are handled securely in the unsafe realm while allowing for the development of complex and feature-rich OS components. - -In addition to OS functionalities, `aster-frame` also provides development utilities, including kernel mode unit test support. The shared base of crates built on `aster-frame` allows for easy reuse and facilitates the creation of sophisticated operating systems with rich features. - -Overall, this architectural approach promotes safety and modularity, empowering developers to build robust and advanced OS systems using Rust. +Conditional QEMU settings allow for a condition to be specified after `qemu`. Currently, `cargo-osdk` only supports the condition `cfg(feature="FEATURE")`, which activates the QEMU settings only if the `FEATURE` is set. The `FEATURE` must be defined in the project's `Cargo.toml`. At most one conditional setting can be activated at a time. If multiple conditional settings can be activated simultaneously, `cargo-osdk` will report an error. In the future, `cargo-osdk` will support all possible conditions that [Rust conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html) supports. \ No newline at end of file diff --git a/osdk/src/bin.rs b/osdk/src/bin.rs deleted file mode 100644 index b748088d..00000000 --- a/osdk/src/bin.rs +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -use std::path::PathBuf; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct AsterBin { - pub path: PathBuf, - pub typ: AsterBinType, - pub version: String, - pub sha256sum: String, - pub stripped: bool, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum AsterBinType { - Elf(AsterElfMeta), - BzImage(AsterBzImageMeta), -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct AsterElfMeta { - pub has_linux_header: bool, - pub has_pvh_header: bool, - pub has_multiboot_header: bool, - pub has_multiboot2_header: bool, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct AsterBzImageMeta { - pub support_legacy32_boot: bool, - pub support_efi_boot: bool, - pub support_efi_handover: bool, -} diff --git a/osdk/src/bundle/bin.rs b/osdk/src/bundle/bin.rs new file mode 100644 index 00000000..4d6642df --- /dev/null +++ b/osdk/src/bundle/bin.rs @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MPL-2.0 + +use std::{ + fs, + path::{Path, PathBuf}, +}; + +use super::file::BundleFile; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct AsterBin { + path: PathBuf, + typ: AsterBinType, + version: String, + sha256sum: String, + stripped: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum AsterBinType { + Elf(AsterElfMeta), + BzImage(AsterBzImageMeta), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct AsterElfMeta { + pub has_linux_header: bool, + pub has_pvh_header: bool, + pub has_multiboot_header: bool, + pub has_multiboot2_header: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct AsterBzImageMeta { + pub support_legacy32_boot: bool, + pub support_efi_boot: bool, + pub support_efi_handover: bool, +} + +impl BundleFile for AsterBin { + fn path(&self) -> &PathBuf { + &self.path + } + + fn sha256sum(&self) -> &String { + &self.sha256sum + } +} + +impl AsterBin { + pub fn new(path: impl AsRef, typ: AsterBinType, version: String, stripped: bool) -> Self { + let created = Self { + path: path.as_ref().to_path_buf(), + typ, + version, + sha256sum: String::new(), + stripped, + }; + Self { + sha256sum: created.calculate_sha256sum(), + ..created + } + } + + pub fn version(&self) -> &String { + &self.version + } + + pub fn stripped(&self) -> bool { + self.stripped + } + + /// Move the binary to the `base` directory and convert the path to a relative path. + pub fn move_to(self, base: impl AsRef) -> Self { + let file_name = self.path.file_name().unwrap(); + let copied_path = base.as_ref().join(file_name); + fs::copy(&self.path, copied_path).unwrap(); + fs::remove_file(&self.path).unwrap(); + Self { + path: PathBuf::from(file_name), + typ: self.typ, + version: self.version, + sha256sum: self.sha256sum, + stripped: self.stripped, + } + } +} diff --git a/osdk/src/bundle/file.rs b/osdk/src/bundle/file.rs new file mode 100644 index 00000000..f95062c3 --- /dev/null +++ b/osdk/src/bundle/file.rs @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MPL-2.0 + +use std::{ + fs, io, + path::{Path, PathBuf}, +}; + +use sha2::{Digest, Sha256}; + +/// A trait for files in a bundle. The file in a bundle should have it's digest and be validatable. +pub trait BundleFile { + fn path(&self) -> &PathBuf; + + fn sha256sum(&self) -> &String; + + fn calculate_sha256sum(&self) -> String { + let mut file = fs::File::open(self.path()).unwrap(); + let mut hasher = Sha256::new(); + let _n = io::copy(&mut file, &mut hasher).unwrap(); + format!("{:x}", hasher.finalize()) + } + + fn validate(&self) -> bool { + self.sha256sum() == &self.calculate_sha256sum() + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Initramfs { + path: PathBuf, + sha256sum: String, +} + +impl BundleFile for Initramfs { + fn path(&self) -> &PathBuf { + &self.path + } + + fn sha256sum(&self) -> &String { + &self.sha256sum + } +} + +impl Initramfs { + pub fn new(path: impl AsRef) -> Self { + let created = Self { + path: path.as_ref().to_path_buf(), + sha256sum: String::new(), + }; + Self { + sha256sum: created.calculate_sha256sum(), + ..created + } + } + + /// Move the initramfs to the `base` directory and convert the path to a relative path. + pub fn copy_to(self, base: impl AsRef) -> Self { + let name = self.path.file_name().unwrap(); + let dest = base.as_ref().join(name); + fs::copy(&self.path, dest).unwrap(); + Self { + path: PathBuf::from(name), + ..self + } + } +} diff --git a/osdk/src/bundle.rs b/osdk/src/bundle/mod.rs similarity index 58% rename from osdk/src/bundle.rs rename to osdk/src/bundle/mod.rs index fa0fc04a..fdbcdf66 100644 --- a/osdk/src/bundle.rs +++ b/osdk/src/bundle/mod.rs @@ -1,12 +1,20 @@ // SPDX-License-Identifier: MPL-2.0 +pub mod bin; +pub mod file; +pub mod vm_image; + +use bin::AsterBin; +use file::{BundleFile, Initramfs}; +use vm_image::AsterVmImage; + use std::{ path::{Path, PathBuf}, process::Command, + time::SystemTime, }; use crate::{ - bin::AsterBin, cli::CargoArgs, config_manager::{ boot::Boot, @@ -15,7 +23,6 @@ use crate::{ }, error::Errno, error_msg, - vm_image::AsterVmImage, }; /// The osdk bundle artifact that stores as `bundle` directory. @@ -28,39 +35,111 @@ pub struct Bundle { path: PathBuf, } +/// 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 cargo_args: CargoArgs, + pub last_modified: SystemTime, +} + impl Bundle { - pub fn new(manifest: BundleManifest, path: impl AsRef) -> Self { + /// 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 { std::fs::create_dir_all(path.as_ref()).unwrap(); - let created = Self { - manifest, + let mut created = Self { + manifest: BundleManifest { + kcmd_args, + initramfs: None, + aster_bin: None, + vm_image: None, + boot, + qemu, + cargo_args, + last_modified: SystemTime::now(), + }, path: path.as_ref().to_path_buf(), }; - created.write_manifest_content(); + created.write_manifest_to_fs(); created } - // FIXME: the load function should be used when implementing build cache, but it is not - // implemented yet. - #[allow(dead_code)] - pub fn load(path: impl AsRef) -> Self { + // Load the bundle from the file system. If the bundle does not exist or have inconsistencies, + // it will return `None`. + pub fn load(path: impl AsRef) -> Option { let manifest_file_path = path.as_ref().join("bundle.toml"); - let manifest_file_content = std::fs::read_to_string(manifest_file_path).unwrap(); - let manifest: BundleManifest = toml::from_str(&manifest_file_content).unwrap(); - // TODO: check integrity of the loaded bundle. - Self { + let manifest_file_content = std::fs::read_to_string(manifest_file_path).ok()?; + let manifest: BundleManifest = toml::from_str(&manifest_file_content).ok()?; + + let original_dir = std::env::current_dir().unwrap(); + std::env::set_current_dir(&path).unwrap(); + + if let Some(aster_bin) = &manifest.aster_bin { + if !aster_bin.validate() { + return None; + } + } + if let Some(vm_image) = &manifest.vm_image { + if !vm_image.validate() { + return None; + } + } + if let Some(initramfs) = &manifest.initramfs { + if !initramfs.validate() { + return None; + } + } + + std::env::set_current_dir(original_dir).unwrap(); + + Some(Self { manifest, path: path.as_ref().to_path_buf(), - } + }) } pub fn can_run_with_config(&self, config: &RunConfig) -> bool { + // 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. - self.manifest.kcmd_args == config.manifest.kcmd_args - && self.manifest.initramfs == config.manifest.initramfs - && self.manifest.boot == config.manifest.boot - && self.manifest.qemu == config.manifest.qemu - && self.manifest.cargo_args == config.cargo_args + if self.manifest.kcmd_args != config.manifest.kcmd_args + || self.manifest.boot != config.manifest.boot + || self.manifest.qemu != config.manifest.qemu + || self.manifest.cargo_args != config.cargo_args + { + return false; + } + + // Compare the initramfs. + match (&self.manifest.initramfs, &config.manifest.initramfs) { + (Some(initramfs), Some(initramfs_path)) => { + let config_initramfs = Initramfs::new(initramfs_path); + if initramfs.sha256sum() != config_initramfs.sha256sum() { + return false; + } + } + (None, None) => {} + _ => { + return false; + } + }; + + true + } + + pub fn last_modified_time(&self) -> SystemTime { + self.manifest.last_modified } pub fn run(&self, config: &RunConfig) { @@ -84,7 +163,9 @@ impl Bundle { error_msg!("Kernel ELF binary is required for Microvm"); std::process::exit(Errno::RunBundle as _); }; - qemu_cmd.arg("-kernel").arg(self.path.join(&aster_bin.path)); + 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 _); @@ -100,7 +181,7 @@ impl Bundle { 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)); + qemu_cmd.arg("-cdrom").arg(self.path.join(vm_image.path())); if let Some(ovmf) = &config.manifest.boot.ovmf { qemu_cmd.arg("-drive").arg(format!( "if=pflash,format=raw,unit=0,readonly=on,file={}", @@ -136,61 +217,37 @@ impl Bundle { } } - pub fn add_vm_image(&mut self, vm_image: &AsterVmImage) { + /// Move the vm_image into the bundle. + pub fn consume_vm_image(&mut self, vm_image: AsterVmImage) { if self.manifest.vm_image.is_some() { panic!("vm_image already exists"); } - let file_name = vm_image.path.file_name().unwrap(); - let copied_path = self.path.join(file_name); - std::fs::copy(&vm_image.path, copied_path).unwrap(); - self.manifest.vm_image = Some(AsterVmImage { - path: file_name.into(), - typ: vm_image.typ.clone(), - aster_version: vm_image.aster_version.clone(), - sha256sum: vm_image.sha256sum.clone(), - }); - self.write_manifest_content(); + self.manifest.vm_image = Some(vm_image.move_to(&self.path)); + self.write_manifest_to_fs(); } - pub fn add_aster_bin(&mut self, aster_bin: &AsterBin) { + /// Move the aster_bin into the bundle. + pub fn consume_aster_bin(&mut self, aster_bin: AsterBin) { if self.manifest.aster_bin.is_some() { panic!("aster_bin already exists"); } - let file_name = aster_bin.path.file_name().unwrap(); - let copied_path = self.path.join(file_name); - std::fs::copy(&aster_bin.path, copied_path).unwrap(); - self.manifest.aster_bin = Some(AsterBin { - path: file_name.into(), - typ: aster_bin.typ.clone(), - version: aster_bin.version.clone(), - sha256sum: aster_bin.sha256sum.clone(), - stripped: aster_bin.stripped, - }); - self.write_manifest_content(); + self.manifest.aster_bin = Some(aster_bin.move_to(&self.path)); + self.write_manifest_to_fs(); } - fn write_manifest_content(&self) { + /// 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(); let manifest_file_path = self.path.join("bundle.toml"); std::fs::write(manifest_file_path, manifest_file_content).unwrap(); } } - -/// The osdk bundle artifact manifest that stores as `bundle.toml`. -#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct BundleManifest { - #[serde(default)] - pub kcmd_args: Vec, - #[serde(default)] - pub initramfs: Option, - #[serde(default)] - pub aster_bin: Option, - #[serde(default)] - pub vm_image: Option, - #[serde(default)] - pub boot: Boot, - #[serde(default)] - pub qemu: Qemu, - #[serde(default)] - pub cargo_args: CargoArgs, -} diff --git a/osdk/src/bundle/vm_image.rs b/osdk/src/bundle/vm_image.rs new file mode 100644 index 00000000..c6afbc97 --- /dev/null +++ b/osdk/src/bundle/vm_image.rs @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MPL-2.0 + +use std::{ + fs, + path::{Path, PathBuf}, +}; + +use super::file::BundleFile; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct AsterVmImage { + path: PathBuf, + typ: AsterVmImageType, + aster_version: String, + sha256sum: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum AsterVmImageType { + GrubIso(AsterGrubIsoImageMeta), + // TODO: add more vm image types such as qcow2, etc. +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct AsterGrubIsoImageMeta { + pub grub_version: String, +} + +impl BundleFile for AsterVmImage { + fn path(&self) -> &PathBuf { + &self.path + } + + fn sha256sum(&self) -> &String { + &self.sha256sum + } +} + +impl AsterVmImage { + pub fn new(path: impl AsRef, typ: AsterVmImageType, aster_version: String) -> Self { + let created = Self { + path: path.as_ref().to_path_buf(), + typ, + aster_version, + sha256sum: String::new(), + }; + Self { + sha256sum: created.calculate_sha256sum(), + ..created + } + } + + /// Move the binary to the `base` directory and convert the path to a relative path. + pub fn move_to(self, base: impl AsRef) -> Self { + let file_name = self.path.file_name().unwrap(); + let copied_path = base.as_ref().join(file_name); + fs::copy(&self.path, copied_path).unwrap(); + fs::remove_file(&self.path).unwrap(); + Self { + path: PathBuf::from(file_name), + typ: self.typ, + aster_version: self.aster_version, + sha256sum: self.sha256sum, + } + } +} diff --git a/osdk/src/commands/build/bin.rs b/osdk/src/commands/build/bin.rs index a330ffde..706370c0 100644 --- a/osdk/src/commands/build/bin.rs +++ b/osdk/src/commands/build/bin.rs @@ -10,7 +10,10 @@ use std::{ use linux_bzimage_builder::{legacy32_rust_target_json, make_bzimage, BzImageType}; use crate::{ - bin::{AsterBin, AsterBinType, AsterBzImageMeta, AsterElfMeta}, + bundle::{ + bin::{AsterBin, AsterBinType, AsterBzImageMeta, AsterElfMeta}, + file::BundleFile, + }, config_manager::boot::BootProtocol, utils::get_current_crate_info, }; @@ -52,30 +55,35 @@ pub fn make_install_bzimage( let install_path = install_dir.as_ref().join(target_name); info!("Building bzImage"); println!("install_path: {:?}", install_path); - make_bzimage(&install_path, image_type, &aster_elf.path, &setup_bin); + make_bzimage(&install_path, image_type, aster_elf.path(), &setup_bin); - AsterBin { - path: install_path, - typ: AsterBinType::BzImage(AsterBzImageMeta { + AsterBin::new( + &install_path, + AsterBinType::BzImage(AsterBzImageMeta { support_legacy32_boot: matches!(protocol, BootProtocol::LinuxLegacy32), support_efi_boot: false, support_efi_handover: matches!(protocol, BootProtocol::LinuxEfiHandover64), }), - version: aster_elf.version.clone(), - sha256sum: "TODO".to_string(), - stripped: aster_elf.stripped, - } + aster_elf.version().clone(), + aster_elf.stripped(), + ) } pub fn strip_elf_for_qemu(install_dir: impl AsRef, elf: &AsterBin) -> AsterBin { let stripped_elf_path = { - let elf_name = elf.path.file_name().unwrap().to_str().unwrap().to_string(); + let elf_name = elf + .path() + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string(); install_dir.as_ref().join(elf_name + ".stripped.elf") }; // We use rust-strip to reduce the kernel image size. let status = Command::new("rust-strip") - .arg(&elf.path) + .arg(elf.path()) .arg("-o") .arg(stripped_elf_path.as_os_str()) .status(); @@ -112,18 +120,17 @@ pub fn strip_elf_for_qemu(install_dir: impl AsRef, elf: &AsterBin) -> Aste file.write_all(&bytes).unwrap(); file.flush().unwrap(); - AsterBin { - path: stripped_elf_path, - typ: AsterBinType::Elf(AsterElfMeta { + AsterBin::new( + &stripped_elf_path, + AsterBinType::Elf(AsterElfMeta { has_linux_header: false, has_pvh_header: false, has_multiboot_header: true, has_multiboot2_header: true, }), - version: elf.version.clone(), - sha256sum: "TODO".to_string(), - stripped: true, - } + elf.version().clone(), + true, + ) } enum SetupInstallArch { diff --git a/osdk/src/commands/build/grub.rs b/osdk/src/commands/build/grub.rs index bdf90a34..e4a55fbe 100644 --- a/osdk/src/commands/build/grub.rs +++ b/osdk/src/commands/build/grub.rs @@ -7,10 +7,13 @@ use std::{ use super::bin::make_install_bzimage; use crate::{ - bin::AsterBin, + bundle::{ + bin::AsterBin, + file::BundleFile, + vm_image::{AsterGrubIsoImageMeta, AsterVmImage, AsterVmImageType}, + }, config_manager::{boot::BootProtocol, BuildConfig}, utils::get_current_crate_info, - vm_image::{AsterGrubIsoImageMeta, AsterVmImage, AsterVmImageType}, }; pub fn create_bootdev_image( @@ -46,7 +49,7 @@ pub fn create_bootdev_image( 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(); + fs::copy(aster_bin.path(), target_path).unwrap(); } }; @@ -77,14 +80,13 @@ pub fn create_bootdev_image( panic!("Failed to run {:#?}.", grub_mkrescue_cmd); } - AsterVmImage { - path: iso_path.clone(), - typ: AsterVmImageType::GrubIso(AsterGrubIsoImageMeta { + AsterVmImage::new( + iso_path, + AsterVmImageType::GrubIso(AsterGrubIsoImageMeta { grub_version: get_grub_mkrescue_version(grub_mkrescue_bin), }), - aster_version: aster_bin.version.clone(), - sha256sum: "TODO".to_string(), - } + aster_bin.version().clone(), + ) } fn generate_grub_cfg( diff --git a/osdk/src/commands/build/mod.rs b/osdk/src/commands/build/mod.rs index 2187e658..eefe1bb2 100644 --- a/osdk/src/commands/build/mod.rs +++ b/osdk/src/commands/build/mod.rs @@ -14,8 +14,11 @@ use bin::strip_elf_for_qemu; use super::utils::{cargo, COMMON_CARGO_ARGS, DEFAULT_TARGET_RELPATH}; use crate::{ base_crate::new_base_crate, - bin::{AsterBin, AsterBinType, AsterElfMeta}, - bundle::{Bundle, BundleManifest}, + bundle::{ + bin::{AsterBin, AsterBinType, AsterElfMeta}, + file::Initramfs, + Bundle, + }, cli::CargoArgs, config_manager::{qemu::QemuMachine, BuildConfig}, error::Errno, @@ -70,30 +73,31 @@ pub fn do_build( cargo_target_directory: impl AsRef, config: &BuildConfig, ) -> Bundle { + if bundle_path.as_ref().exists() { + std::fs::remove_dir_all(&bundle_path).unwrap(); + } + let mut bundle = Bundle::new( + &bundle_path, + config.manifest.kcmd_args.clone(), + config.manifest.boot.clone(), + config.manifest.qemu.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)); }; - let mut bundle = Bundle::new( - BundleManifest { - kcmd_args: config.manifest.kcmd_args.clone(), - initramfs: config.manifest.initramfs.clone(), - aster_bin: None, - vm_image: None, - boot: config.manifest.boot.clone(), - qemu: config.manifest.qemu.clone(), - cargo_args: config.cargo_args.clone(), - }, - &bundle_path, - ); + info!("Building kernel ELF"); let aster_elf = build_kernel_elf(&config.cargo_args, &cargo_target_directory); if matches!(config.manifest.qemu.machine, QemuMachine::Microvm) { let stripped_elf = strip_elf_for_qemu(&osdk_target_directory, &aster_elf); - bundle.add_aster_bin(&stripped_elf); + bundle.consume_aster_bin(stripped_elf); } // TODO: A boot device is required if we use GRUB. Actually you can boot @@ -107,7 +111,7 @@ pub fn do_build( config.manifest.initramfs.as_ref(), config, ); - bundle.add_vm_image(&bootdev_image); + bundle.consume_vm_image(bootdev_image); } bundle @@ -141,16 +145,15 @@ fn build_kernel_elf(args: &CargoArgs, cargo_target_directory: impl AsRef) } .join(get_current_crate_info().name); - AsterBin { - path: aster_bin_path, - typ: AsterBinType::Elf(AsterElfMeta { + AsterBin::new( + aster_bin_path, + AsterBinType::Elf(AsterElfMeta { has_linux_header: false, has_pvh_header: false, has_multiboot_header: true, has_multiboot2_header: true, }), - version: get_current_crate_info().version, - sha256sum: "TODO".to_string(), - stripped: false, - } + get_current_crate_info().version, + false, + ) } diff --git a/osdk/src/commands/new/kernel.template b/osdk/src/commands/new/kernel.template index 074b2b1e..bc073831 100644 --- a/osdk/src/commands/new/kernel.template +++ b/osdk/src/commands/new/kernel.template @@ -14,9 +14,10 @@ fn kernel_main() -> ! { } #[cfg(ktest)] -mod test { +mod tests { #[ktest] - fn trivial_test() { - assert_eq!(1 + 1, 2); + fn it_works() { + let memory_regions = aster_frame::boot::memory_regions(); + assert!(!memory_regions.is_empty()); } } diff --git a/osdk/src/commands/new/mod.rs b/osdk/src/commands/new/mod.rs index 00ca1cad..3040888f 100644 --- a/osdk/src/commands/new/mod.rs +++ b/osdk/src/commands/new/mod.rs @@ -30,10 +30,8 @@ pub fn execute_new_command(args: &NewArgs) { /// OSDK assumes that the toolchain used by the kernel should be same same as the toolchain /// specified in the asterinas workspace. -macro_rules! aster_rust_toolchain { - () => { - include_str!("../../../../rust-toolchain.toml") - }; +fn aster_rust_toolchain() -> &'static str { + include_str!("../../../../rust-toolchain.toml") } fn add_manifest_dependencies(cargo_metadata: &serde_json::Value, crate_name: &str) { @@ -126,7 +124,7 @@ fn add_rust_toolchain(cargo_metadata: &serde_json::Value) { return; } - let contents = aster_rust_toolchain!(); + let contents = aster_rust_toolchain(); fs::write(rust_toolchain_path, contents).unwrap(); } @@ -177,7 +175,7 @@ fn get_package_metadata<'a>( fn check_rust_toolchain(toolchain: &toml::Table) { let expected = { - let contents = aster_rust_toolchain!(); + let contents = aster_rust_toolchain(); toml::Table::from_str(contents).unwrap() }; diff --git a/osdk/src/commands/run.rs b/osdk/src/commands/run.rs index dae6ca16..7da55a77 100644 --- a/osdk/src/commands/run.rs +++ b/osdk/src/commands/run.rs @@ -1,9 +1,15 @@ // SPDX-License-Identifier: MPL-2.0 +use std::{ + path::{Path, PathBuf}, + time::{Duration, SystemTime}, +}; + use super::{build::create_base_and_build, utils::DEFAULT_TARGET_RELPATH}; use crate::{ + bundle::Bundle, config_manager::{BuildConfig, RunConfig}, - utils::{get_current_crate_info, get_target_directory}, + utils::{get_cargo_metadata, get_current_crate_info, get_target_directory}, }; pub fn execute_run_command(config: &RunConfig) { @@ -11,13 +17,34 @@ pub fn execute_run_command(config: &RunConfig) { let osdk_target_directory = ws_target_directory.join(DEFAULT_TARGET_RELPATH); let target_name = get_current_crate_info().name; let default_bundle_directory = osdk_target_directory.join(target_name); + let existing_bundle = Bundle::load(&default_bundle_directory); + + // If the source is not since modified and the last build is recent, we can reuse the existing bundle. + if let Some(existing_bundle) = existing_bundle { + if existing_bundle.can_run_with_config(config) { + if let Ok(built_since) = + SystemTime::now().duration_since(existing_bundle.last_modified_time()) + { + if built_since < Duration::from_secs(600) { + let workspace_root = { + let meta = get_cargo_metadata(None::<&str>, None::<&[&str]>).unwrap(); + PathBuf::from(meta.get("workspace_root").unwrap().as_str().unwrap()) + }; + if get_last_modified_time(workspace_root) < existing_bundle.last_modified_time() + { + existing_bundle.run(config); + return; + } + } + } + } + } let required_build_config = BuildConfig { manifest: config.manifest.clone(), cargo_args: config.cargo_args.clone(), }; - // TODO: Check if the bundle is already built and compatible with the run configuration. let bundle = create_base_and_build( default_bundle_directory, &osdk_target_directory, @@ -27,3 +54,21 @@ pub fn execute_run_command(config: &RunConfig) { bundle.run(config); } + +fn get_last_modified_time(path: impl AsRef) -> SystemTime { + let mut last_modified = SystemTime::UNIX_EPOCH; + for entry in std::fs::read_dir(path).unwrap() { + let entry = entry.unwrap(); + if entry.file_name() == "target" { + continue; + } + + let metadata = entry.metadata().unwrap(); + if metadata.is_dir() { + last_modified = std::cmp::max(last_modified, get_last_modified_time(&entry.path())); + } else { + last_modified = std::cmp::max(last_modified, metadata.modified().unwrap()); + } + } + last_modified +} diff --git a/osdk/src/main.rs b/osdk/src/main.rs index 90747b53..fcc797d1 100644 --- a/osdk/src/main.rs +++ b/osdk/src/main.rs @@ -8,14 +8,12 @@ extern crate log; extern crate serde; mod base_crate; -mod bin; mod bundle; mod cli; mod commands; mod config_manager; mod error; mod utils; -mod vm_image; fn main() { // init logger diff --git a/osdk/src/vm_image.rs b/osdk/src/vm_image.rs deleted file mode 100644 index 92ce254a..00000000 --- a/osdk/src/vm_image.rs +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -use std::path::PathBuf; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct AsterVmImage { - pub path: PathBuf, - pub typ: AsterVmImageType, - pub aster_version: String, - pub sha256sum: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum AsterVmImageType { - GrubIso(AsterGrubIsoImageMeta), - // TODO: add more vm image types such as qcow2, etc. -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct AsterGrubIsoImageMeta { - pub grub_version: String, -}