From 2ede30b6d3500dfa5e62c7f91c3661c97dad3c8e Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Wed, 22 May 2019 13:06:49 -0700 Subject: [PATCH] vmm: Add virtio-fs support to the VMM The user can now share some files and directories with the guest by providing the corresponding vhost-user socket. The virtiofsd daemon should be started by the user before to start the VM. Signed-off-by: Sebastien Boeuf --- src/main.rs | 15 ++++++- vmm/src/config.rs | 103 +++++++++++++++++++++++++++++++++++++++------- vmm/src/vm.rs | 49 +++++++++++++++++++++- 3 files changed, 150 insertions(+), 17 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8dc7db2a9..815f1feb3 100755 --- a/src/main.rs +++ b/src/main.rs @@ -63,6 +63,16 @@ fn main() { .help("Path to entropy source") .default_value(config::DEFAULT_RNG_SOURCE), ) + .arg( + Arg::with_name("fs") + .long("fs") + .help( + "virtio-fs parameters \"tag=,\ + sock=,num_queues=,\ + queue_size=\"", + ) + .takes_value(true), + ) .get_matches(); // These .unwrap()s cannot fail as there is a default value defined @@ -85,14 +95,17 @@ fn main() { // This .unwrap() cannot fail as there is a default value defined let rng = cmd_arguments.value_of("rng").unwrap(); + let fs = cmd_arguments.value_of("fs"); + let vm_config = match config::VmConfig::parse(config::VmParams { cpus, memory, kernel, cmdline, disks, - rng, net, + rng, + fs, }) { Ok(config) => config, Err(e) => { diff --git a/vmm/src/config.rs b/vmm/src/config.rs index abb3fae8b..c956f5b00 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -38,6 +38,14 @@ pub enum Error<'a> { ParseNetMaskParam(AddrParseError), /// Failed parsing network mac parameter. ParseNetMacParam(&'a str), + /// Failed parsing fs tag parameter. + ParseFsTagParam, + /// Failed parsing fs socket path parameter. + ParseFsSockParam, + /// Failed parsing fs number of queues parameter. + ParseFsNumQueuesParam(std::num::ParseIntError), + /// Failed parsing fs queue size parameter. + ParseFsQueueSizeParam(std::num::ParseIntError), } pub type Result<'a, T> = result::Result>; @@ -47,8 +55,9 @@ pub struct VmParams<'a> { pub kernel: &'a str, pub cmdline: Option<&'a str>, pub disks: Vec<&'a str>, - pub rng: &'a str, pub net: Option<&'a str>, + pub rng: &'a str, + pub fs: Option<&'a str>, } pub struct CpusConfig(pub u8); @@ -128,18 +137,6 @@ impl<'a> DiskConfig<'a> { } } -pub struct RngConfig<'a> { - pub src: &'a Path, -} - -impl<'a> RngConfig<'a> { - pub fn parse(rng: &'a str) -> Result { - Ok(RngConfig { - src: Path::new(rng), - }) - } -} - pub struct NetConfig<'a> { pub tap: Option<&'a str>, pub ip: Ipv4Addr, @@ -195,14 +192,89 @@ impl<'a> NetConfig<'a> { } } +pub struct RngConfig<'a> { + pub src: &'a Path, +} + +impl<'a> RngConfig<'a> { + pub fn parse(rng: &'a str) -> Result { + Ok(RngConfig { + src: Path::new(rng), + }) + } +} + +pub struct FsConfig<'a> { + pub tag: &'a str, + pub sock: &'a Path, + pub num_queues: usize, + pub queue_size: u16, +} + +impl<'a> FsConfig<'a> { + pub fn parse(fs: Option<&'a str>) -> Result> { + if fs.is_none() { + return Ok(None); + } + + // Split the parameters based on the comma delimiter + let params_list: Vec<&str> = fs.unwrap().split(',').collect(); + + let mut tag: &str = ""; + let mut sock: &str = ""; + let mut num_queues_str: &str = ""; + let mut queue_size_str: &str = ""; + + for param in params_list.iter() { + if param.starts_with("tag=") { + tag = ¶m[4..]; + } else if param.starts_with("sock=") { + sock = ¶m[5..]; + } else if param.starts_with("num_queues=") { + num_queues_str = ¶m[11..]; + } else if param.starts_with("queue_size=") { + queue_size_str = ¶m[11..]; + } + } + + let mut num_queues: usize = 1; + let mut queue_size: u16 = 1024; + + if tag.is_empty() { + return Err(Error::ParseFsTagParam); + } + if sock.is_empty() { + return Err(Error::ParseFsSockParam); + } + if !num_queues_str.is_empty() { + num_queues = num_queues_str + .parse() + .map_err(Error::ParseFsNumQueuesParam)?; + } + if !queue_size_str.is_empty() { + queue_size = queue_size_str + .parse() + .map_err(Error::ParseFsQueueSizeParam)?; + } + + Ok(Some(FsConfig { + tag, + sock: Path::new(sock), + num_queues, + queue_size, + })) + } +} + pub struct VmConfig<'a> { pub cpus: CpusConfig, pub memory: MemoryConfig, pub kernel: KernelConfig<'a>, pub cmdline: CmdlineConfig, pub disks: Vec>, - pub rng: RngConfig<'a>, pub net: Option>, + pub rng: RngConfig<'a>, + pub fs: Option>, } impl<'a> VmConfig<'a> { @@ -218,8 +290,9 @@ impl<'a> VmConfig<'a> { kernel: KernelConfig::parse(vm_params.kernel)?, cmdline: CmdlineConfig::parse(vm_params.cmdline)?, disks, - rng: RngConfig::parse(vm_params.rng)?, net: NetConfig::parse(vm_params.net)?, + rng: RngConfig::parse(vm_params.rng)?, + fs: FsConfig::parse(vm_params.fs)?, }) } } diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index 7196d5f86..b37cb4476 100755 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -28,6 +28,7 @@ use kvm_bindings::{ KVM_PIT_SPEAKER_DUMMY, }; use kvm_ioctls::*; +use libc::O_TMPFILE; use libc::{c_void, siginfo_t, EFD_NONBLOCK}; use linux_loader::loader::KernelLoader; use net_util::Tap; @@ -38,10 +39,12 @@ use qcow::{self, ImageType, QcowFile}; use std::ffi::CString; use std::fs::{File, OpenOptions}; use std::io::{self, stdout}; +use std::os::unix::fs::OpenOptionsExt; use std::os::unix::io::{AsRawFd, RawFd}; use std::sync::{Arc, Barrier, Mutex}; use std::{result, str, thread}; use vm_allocator::SystemAllocator; +use vm_memory::guest_memory::FileOffset; use vm_memory::{ Address, Bytes, Error as MmapError, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion, GuestUsize, @@ -150,6 +153,12 @@ pub enum Error { /// Memory is overflow MemOverflow, + + /// Failed to create shared file. + SharedFileCreate(io::Error), + + /// Failed to set shared file length. + SharedFileSetLen(io::Error), } pub type Result = result::Result; @@ -171,6 +180,9 @@ pub enum DeviceManagerError { /// Cannot create virtio-rng device CreateVirtioRng(io::Error), + /// Cannot create virtio-fs device + CreateVirtioFs(vm_virtio::fs::Error), + /// Failed parsing disk image format DetectImageType(qcow::Error), @@ -582,6 +594,25 @@ impl DeviceManager { )?; } + // Add virtio-fs if required + if let Some(fs_cfg) = &vm_cfg.fs { + if let Some(fs_sock) = fs_cfg.sock.to_str() { + let virtio_fs_device = + vm_virtio::Fs::new(fs_sock, fs_cfg.tag, fs_cfg.num_queues, fs_cfg.queue_size) + .map_err(DeviceManagerError::CreateVirtioFs)?; + + DeviceManager::add_virtio_pci_device( + Box::new(virtio_fs_device), + memory.clone(), + allocator, + vm_fd, + &mut pci_root, + &mut mmio_bus, + &interrupt_info, + )?; + } + } + let pci = Arc::new(Mutex::new(PciConfigIo::new(pci_root))); Ok(DeviceManager { @@ -813,7 +844,23 @@ impl<'a> Vm<'a> { // Init guest memory let arch_mem_regions = arch::arch_memory_regions(u64::from(&config.memory) << 20); - let guest_memory = GuestMemoryMmap::new(&arch_mem_regions).map_err(Error::GuestMemory)?; + + let mut mem_regions = Vec::<(GuestAddress, usize, Option)>::new(); + for region in arch_mem_regions.iter() { + let file = OpenOptions::new() + .read(true) + .write(true) + .custom_flags(O_TMPFILE) + .open("/dev/shm") + .map_err(Error::SharedFileCreate)?; + + file.set_len(region.1 as u64) + .map_err(Error::SharedFileSetLen)?; + + mem_regions.push((region.0, region.1, Some(FileOffset::new(file, 0)))); + } + + let guest_memory = GuestMemoryMmap::with_files(&mem_regions).map_err(Error::GuestMemory)?; guest_memory .with_regions(|index, region| {