mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-28 11:53:24 +00:00
Implement OSDK functionalities and opt-in OSDK for asterinas
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
bc9bce9dea
commit
f97d0f1260
191
osdk/src/bundle.rs
Normal file
191
osdk/src/bundle.rs
Normal file
@ -0,0 +1,191 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use crate::bin::AsterBin;
|
||||
use crate::cli::CargoArgs;
|
||||
use crate::config_manager::{
|
||||
boot::Boot,
|
||||
qemu::{Qemu, QemuMachine},
|
||||
RunConfig,
|
||||
};
|
||||
use crate::vm_image::AsterVmImage;
|
||||
use crate::{error::Errno, error_msg};
|
||||
|
||||
/// The osdk bundle artifact that stores as `bundle` directory.
|
||||
///
|
||||
/// This `Bundle` struct is used to track a bundle on a filesystem. Every modification to the bundle
|
||||
/// would result in file system writes. But the bundle will not be removed from the file system when
|
||||
/// the `Bundle` is dropped.
|
||||
pub struct Bundle {
|
||||
manifest: BundleManifest,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl Bundle {
|
||||
pub fn new(manifest: BundleManifest, path: impl AsRef<Path>) -> Self {
|
||||
std::fs::create_dir_all(path.as_ref()).unwrap();
|
||||
let created = Self {
|
||||
manifest,
|
||||
path: path.as_ref().to_path_buf(),
|
||||
};
|
||||
created.write_manifest_content();
|
||||
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<Path>) -> Self {
|
||||
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 {
|
||||
manifest,
|
||||
path: path.as_ref().to_path_buf(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_run_with_config(&self, config: &RunConfig) -> bool {
|
||||
// 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
|
||||
}
|
||||
|
||||
pub fn run(&self, config: &RunConfig) {
|
||||
if !self.can_run_with_config(config) {
|
||||
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());
|
||||
// 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 part in arg.split_whitespace() {
|
||||
qemu_cmd.arg(part);
|
||||
}
|
||||
}
|
||||
match config.manifest.qemu.machine {
|
||||
QemuMachine::Microvm => {
|
||||
qemu_cmd.arg("-machine").arg("microvm");
|
||||
let Some(ref aster_bin) = self.manifest.aster_bin else {
|
||||
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));
|
||||
let Some(ref initramfs) = config.manifest.initramfs else {
|
||||
error_msg!("Initramfs is required for Microvm");
|
||||
std::process::exit(Errno::RunBundle as _);
|
||||
};
|
||||
qemu_cmd.arg("-initrd").arg(initramfs);
|
||||
qemu_cmd
|
||||
.arg("-append")
|
||||
.arg(config.manifest.kcmd_args.join(" "));
|
||||
}
|
||||
QemuMachine::Q35 => {
|
||||
qemu_cmd.arg("-machine").arg("q35,kernel-irqchip=split");
|
||||
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 {
|
||||
qemu_cmd.arg("-drive").arg(format!(
|
||||
"if=pflash,format=raw,unit=0,readonly=on,file={}",
|
||||
ovmf.join("OVMF_CODE.fd").display()
|
||||
));
|
||||
qemu_cmd.arg("-drive").arg(format!(
|
||||
"if=pflash,format=raw,unit=1,file={}",
|
||||
ovmf.join("OVMF_VARS.fd").display()
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
qemu_cmd.arg("-cpu").arg("Icelake-Server,+x2apic");
|
||||
|
||||
for drive_file in &config.manifest.qemu.drive_files {
|
||||
qemu_cmd.arg("-drive").arg(format!(
|
||||
"file={},{}",
|
||||
drive_file.path.display(),
|
||||
drive_file.append,
|
||||
));
|
||||
}
|
||||
|
||||
let exit_status = qemu_cmd.status().unwrap();
|
||||
if !exit_status.success() {
|
||||
// FIXME: Exit code manipulation is not needed when using non-x86 QEMU
|
||||
let qemu_exit_code = exit_status.code().unwrap();
|
||||
let kernel_exit_code = qemu_exit_code >> 1;
|
||||
match kernel_exit_code {
|
||||
0x10 /*aster_frame::QemuExitCode::Success*/ => { std::process::exit(0); },
|
||||
0x20 /*aster_frame::QemuExitCode::Failed*/ => { std::process::exit(1); },
|
||||
_ /* unknown, e.g., a triple fault */ => { std::process::exit(2) },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_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();
|
||||
}
|
||||
|
||||
pub fn add_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.clone(),
|
||||
});
|
||||
self.write_manifest_content();
|
||||
}
|
||||
|
||||
fn write_manifest_content(&self) {
|
||||
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<String>,
|
||||
#[serde(default)]
|
||||
pub initramfs: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub aster_bin: Option<AsterBin>,
|
||||
#[serde(default)]
|
||||
pub vm_image: Option<AsterVmImage>,
|
||||
#[serde(default)]
|
||||
pub boot: Boot,
|
||||
#[serde(default)]
|
||||
pub qemu: Qemu,
|
||||
#[serde(default)]
|
||||
pub cargo_args: CargoArgs,
|
||||
}
|
Reference in New Issue
Block a user