From f844cff86f1bfc96b796b0852d55ef1855d2a2b7 Mon Sep 17 00:00:00 2001 From: Alexandre Sollier Date: Sat, 20 Apr 2024 22:13:08 +0200 Subject: [PATCH] feat(vmm): add support for initramfs (#18) * feat(vmm): add support for initramfs This adds a new command line argument, `--initramfs`, which takes the path of an initramfs archive to load into the memory of the VM. The initramfs is loaded after the kernel in memory, and the boot parameters are updated to tell the kernel the address and size of the loaded initramfs. Signed-off-by: Kuruyia --- src/vmm/src/args.rs | 4 ++++ src/vmm/src/core/kernel.rs | 36 ++++++++++++++++++++++++++++++++++-- src/vmm/src/core/mod.rs | 2 ++ src/vmm/src/core/vmm.rs | 17 ++++++++++++++--- src/vmm/src/main.rs | 2 +- 5 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/vmm/src/args.rs b/src/vmm/src/args.rs index 0c694b8..fcfca2d 100644 --- a/src/vmm/src/args.rs +++ b/src/vmm/src/args.rs @@ -13,6 +13,10 @@ pub struct CliArguments { #[arg(short, long, env)] pub kernel: PathBuf, + /// Path to the cpio archive to use as the initramfs. + #[arg(short, long, env)] + pub initramfs: Option, + /// Number of virtual CPUs assigned to the guest. #[clap(short, long, env, default_value = "1")] pub cpus: u8, diff --git a/src/vmm/src/core/kernel.rs b/src/vmm/src/core/kernel.rs index b9e66ec..c9b82ea 100644 --- a/src/vmm/src/core/kernel.rs +++ b/src/vmm/src/core/kernel.rs @@ -7,10 +7,10 @@ use linux_loader::bootparam::boot_params; use linux_loader::cmdline::Cmdline; use linux_loader::configurator::{linux::LinuxBootConfigurator, BootConfigurator, BootParams}; use linux_loader::loader::{elf::Elf, load_cmdline, KernelLoader, KernelLoaderResult}; -use std::fs::File; +use std::fs::{self, File}; use std::path::PathBuf; use std::result; -use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryMmap}; +use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap}; // x86_64 boot constants. See https://www.kernel.org/doc/Documentation/x86/boot.txt for the full // documentation. @@ -98,6 +98,25 @@ pub fn build_bootparams( Ok(params) } +/// Load the initramfs into guest memory. Returns a tuple containing the address +/// where the initramfs was loaded, and its size. +/// +/// # Arguments +/// +/// * `guest_memory` - guest memory +/// * `start_addr` - the address where to start looking for a place to store the initramfs +/// * `data` - the initramfs data +fn load_initramfs(mem: &GuestMemoryMmap, start_addr: u64, data: Vec) -> Result<(u32, u32)> { + let addr = GuestAddress(start_addr); + + mem.checked_offset(addr, data.len()) + .ok_or(Error::InitramfsLoad)?; + mem.write_slice(data.as_slice(), addr) + .map_err(|_| Error::InitramfsLoad)?; + + Ok((addr.raw_value() as u32, data.len() as u32)) +} + /// Set guest kernel up. /// /// # Arguments @@ -107,6 +126,7 @@ pub fn build_bootparams( pub fn kernel_setup( guest_memory: &GuestMemoryMmap, kernel_path: PathBuf, + initramfs_path: Option, ) -> Result { let mut kernel_image = File::open(kernel_path).map_err(Error::IO)?; let zero_page_addr = GuestAddress(ZEROPG_START); @@ -138,6 +158,18 @@ pub fn kernel_setup( ) .map_err(Error::KernelLoad)?; + // Handle the initramfs. + if let Some(initramfs_path) = initramfs_path { + // Load the initramfs into guest memory. + let initramfs = fs::read(initramfs_path).map_err(Error::IO)?; + let (initramfs_addr, initramfs_size) = + load_initramfs(guest_memory, kernel_load.kernel_end, initramfs)?; + + // Add the initramfs to the boot parameters. + bootparams.hdr.ramdisk_image = initramfs_addr; + bootparams.hdr.ramdisk_size = initramfs_size; + } + // Write the boot parameters in the zeropage. LinuxBootConfigurator::write_bootparams::( &BootParams::new::(&bootparams, zero_page_addr), diff --git a/src/vmm/src/core/mod.rs b/src/vmm/src/core/mod.rs index abdad5e..34531ac 100644 --- a/src/vmm/src/core/mod.rs +++ b/src/vmm/src/core/mod.rs @@ -27,6 +27,8 @@ pub enum Error { Cmdline(linux_loader::cmdline::Error), /// Failed to load kernel. KernelLoad(loader::Error), + /// Failed to load the initramfs. + InitramfsLoad, /// Invalid E820 configuration. E820Configuration, /// Highmem start address is past the guest memory end. diff --git a/src/vmm/src/core/vmm.rs b/src/vmm/src/core/vmm.rs index 5b8e21e..023dbfb 100644 --- a/src/vmm/src/core/vmm.rs +++ b/src/vmm/src/core/vmm.rs @@ -12,7 +12,7 @@ use std::io; use std::net::Ipv4Addr; use std::os::unix::io::AsRawFd; use std::os::unix::prelude::RawFd; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use std::thread; use tracing::info; @@ -214,9 +214,20 @@ impl VMM { /// * `num_vcpus` Number of virtual CPUs /// * `mem_size_mb` Memory size (in MB) /// * `kernel_path` Path to a Linux kernel - pub fn configure(&mut self, num_vcpus: u8, mem_size_mb: u32, kernel_path: &Path) -> Result<()> { + /// * `initramfs_path` Path to an initramfs + pub fn configure( + &mut self, + num_vcpus: u8, + mem_size_mb: u32, + kernel_path: &Path, + initramfs_path: &Option, + ) -> Result<()> { self.configure_memory(mem_size_mb)?; - let kernel_load = kernel::kernel_setup(&self.guest_memory, kernel_path.to_path_buf())?; + let kernel_load = kernel::kernel_setup( + &self.guest_memory, + kernel_path.to_path_buf(), + initramfs_path.clone(), + )?; self.configure_io()?; self.configure_vcpus(num_vcpus, kernel_load)?; diff --git a/src/vmm/src/main.rs b/src/vmm/src/main.rs index 7599367..c0f008d 100644 --- a/src/vmm/src/main.rs +++ b/src/vmm/src/main.rs @@ -30,7 +30,7 @@ fn main() -> Result<(), Error> { let mut vmm = VMM::new(args.network_host_ip, args.network_host_netmask).map_err(Error::VmmNew)?; - vmm.configure(args.cpus, args.memory, &args.kernel) + vmm.configure(args.cpus, args.memory, &args.kernel, &args.initramfs) .map_err(Error::VmmConfigure)?; // Run the VMM