diff --git a/src/main.rs b/src/main.rs index f383f156d..b835b6a34 100755 --- a/src/main.rs +++ b/src/main.rs @@ -9,16 +9,26 @@ extern crate vmm; extern crate clap; use clap::{App, Arg}; - -use std::path::PathBuf; - -use vmm::vm::*; +use std::process; +use vmm::config; fn main() { let cmd_arguments = App::new("cloud-hypervisor") .version(crate_version!()) .author(crate_authors!()) .about("Launch a cloud-hypervisor VMM.") + .arg( + Arg::with_name("cpus") + .long("cpus") + .help("Number of virtual CPUs") + .default_value(config::DEFAULT_VCPUS), + ) + .arg( + Arg::with_name("memory") + .long("memory") + .help("Amount of RAM (in MiB)") + .default_value(config::DEFAULT_MEMORY), + ) .arg( Arg::with_name("kernel") .long("kernel") @@ -41,82 +51,68 @@ fn main() { .arg( Arg::with_name("net") .long("net") - .help("Network parameters \"tap=,ip=,mask=,mac=\"") - .takes_value(true), - ) - .arg( - Arg::with_name("cpus") - .long("cpus") - .help("Number of virtual CPUs") - .takes_value(true), - ) - .arg( - Arg::with_name("memory") - .long("memory") - .help("Amount of RAM (in MB)") + .help( + "Network parameters \"tap=,\ + ip=,mask=,mac=\"", + ) .takes_value(true), ) .arg( Arg::with_name("rng") .long("rng") .help("Path to entropy source") - .default_value("/dev/urandom"), + .default_value(config::DEFAULT_RNG_SOURCE), ) .get_matches(); - let kernel_arg = cmd_arguments - .value_of("kernel") - .map(PathBuf::from) - .expect("Missing argument: kernel"); - let kernel_path = kernel_arg.as_path(); + // These .unwrap()s cannot fail as there is a default value defined + let cpus = cmd_arguments.value_of("cpus").unwrap(); + let memory = cmd_arguments.value_of("memory").unwrap(); - let disk_paths: Vec = cmd_arguments + let kernel = cmd_arguments + .value_of("kernel") + .expect("Missing argument: kernel"); + + let cmdline = cmd_arguments.value_of("cmdline"); + + let disks: Vec<&str> = cmd_arguments .values_of("disk") - .expect("Missing arguments on disk") - .map(PathBuf::from) + .expect("Missing argument: disk. Provide at least one") .collect(); - let cmdline = cmd_arguments - .value_of("cmdline") - .map(std::string::ToString::to_string) - .unwrap_or_else(String::new); + let net = cmd_arguments.value_of("net"); - let net_params = cmd_arguments - .value_of("net") - .map(std::string::ToString::to_string); + // This .unwrap() cannot fail as there is a default value defined + let rng = cmd_arguments.value_of("rng").unwrap(); - let rng_path = match cmd_arguments.occurrences_of("rng") { - 0 => None, - _ => Some(cmd_arguments.value_of("rng").unwrap().to_string()), + let vm_config = match config::VmConfig::parse(config::VmParams { + cpus, + memory, + kernel, + cmdline, + disks, + rng, + net, + }) { + Ok(config) => config, + Err(e) => { + println!("Failed parsing parameters {:?}", e); + process::exit(1); + } }; - let vcpus = cmd_arguments - .value_of("cpus") - .and_then(|c| c.parse::().ok()) - .unwrap_or(DEFAULT_VCPUS); - - let memory = cmd_arguments - .value_of("memory") - .and_then(|m| m.parse::().ok()) - .unwrap_or(DEFAULT_MEMORY); - println!( - "Cloud Hypervisor Guest\n\tvCPUs: {}\n\tMemory: {} MB\n\tKernel: {:?}\n\tKernel cmdline: {}\n\tDisk(s): {:?}", - vcpus, memory, kernel_path, cmdline, disk_paths, + "Cloud Hypervisor Guest\n\tvCPUs: {}\n\tMemory: {} MB\ + \n\tKernel: {:?}\n\tKernel cmdline: {}\n\tDisk(s): {:?}", + u8::from(&vm_config.cpus), + u64::from(&vm_config.memory), + vm_config.kernel.path, + vm_config.cmdline.args.as_str(), + vm_config.disks, ); - let vm_config = VmConfig::new( - kernel_path, - disk_paths, - rng_path, - cmdline, - net_params, - vcpus, - memory, - ) - .unwrap(); - if let Err(e) = vmm::boot_kernel(vm_config) { println!("Guest boot failed: {}", e); + process::exit(1); } } diff --git a/vmm/src/config.rs b/vmm/src/config.rs new file mode 100644 index 000000000..abb3fae8b --- /dev/null +++ b/vmm/src/config.rs @@ -0,0 +1,225 @@ +// Copyright © 2019 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +use linux_loader::cmdline::Cmdline; +use net_util::MacAddr; +use std::convert::From; +use std::net::AddrParseError; +use std::net::Ipv4Addr; +use std::path::Path; +use std::result; +use vm_memory::GuestAddress; + +pub const DEFAULT_VCPUS: &str = "1"; +pub const DEFAULT_MEMORY: &str = "512"; +pub const DEFAULT_RNG_SOURCE: &str = "/dev/urandom"; +const CMDLINE_OFFSET: GuestAddress = GuestAddress(0x20000); + +/// Errors associated with VM configuration parameters. +#[derive(Debug)] +pub enum Error<'a> { + /// Failed parsing cpus parameters. + ParseCpusParams(std::num::ParseIntError), + /// Failed parsing memory parameters. + ParseMemoryParams(std::num::ParseIntError), + /// Failed parsing kernel parameters. + ParseKernelParams, + /// Failed parsing kernel command line parameters. + ParseCmdlineParams, + /// Failed parsing disks parameters. + ParseDisksParams, + /// Failed parsing random number generator parameters. + ParseRngParams, + /// Failed parsing network ip parameter. + ParseNetIpParam(AddrParseError), + /// Failed parsing network mask parameter. + ParseNetMaskParam(AddrParseError), + /// Failed parsing network mac parameter. + ParseNetMacParam(&'a str), +} +pub type Result<'a, T> = result::Result>; + +pub struct VmParams<'a> { + pub cpus: &'a str, + pub memory: &'a str, + pub kernel: &'a str, + pub cmdline: Option<&'a str>, + pub disks: Vec<&'a str>, + pub rng: &'a str, + pub net: Option<&'a str>, +} + +pub struct CpusConfig(pub u8); + +impl CpusConfig { + pub fn parse(cpus: &str) -> Result { + Ok(CpusConfig( + cpus.parse::().map_err(Error::ParseCpusParams)?, + )) + } +} + +impl From<&CpusConfig> for u8 { + fn from(val: &CpusConfig) -> Self { + val.0 + } +} + +pub struct MemoryConfig(pub u64); + +impl MemoryConfig { + pub fn parse(memory: &str) -> Result { + Ok(MemoryConfig( + memory.parse::().map_err(Error::ParseMemoryParams)?, + )) + } +} + +impl From<&MemoryConfig> for u64 { + fn from(val: &MemoryConfig) -> Self { + val.0 + } +} + +pub struct KernelConfig<'a> { + pub path: &'a Path, +} + +impl<'a> KernelConfig<'a> { + pub fn parse(kernel: &'a str) -> Result { + Ok(KernelConfig { + path: Path::new(kernel), + }) + } +} + +pub struct CmdlineConfig { + pub args: Cmdline, + pub offset: GuestAddress, +} + +impl CmdlineConfig { + pub fn parse(cmdline: Option<&str>) -> Result { + let cmdline_str = cmdline + .map(std::string::ToString::to_string) + .unwrap_or_else(String::new); + let mut args = Cmdline::new(arch::CMDLINE_MAX_SIZE); + args.insert_str(cmdline_str).unwrap(); + + Ok(CmdlineConfig { + args, + offset: CMDLINE_OFFSET, + }) + } +} + +#[derive(Debug)] +pub struct DiskConfig<'a> { + pub path: &'a Path, +} + +impl<'a> DiskConfig<'a> { + pub fn parse(disk: &'a str) -> Result { + Ok(DiskConfig { + path: Path::new(disk), + }) + } +} + +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, + pub mask: Ipv4Addr, + pub mac: MacAddr, +} + +impl<'a> NetConfig<'a> { + pub fn parse(net: Option<&'a str>) -> Result> { + if net.is_none() { + return Ok(None); + } + + // Split the parameters based on the comma delimiter + let params_list: Vec<&str> = net.unwrap().split(',').collect(); + + let mut tap_str: &str = ""; + let mut ip_str: &str = ""; + let mut mask_str: &str = ""; + let mut mac_str: &str = ""; + + for param in params_list.iter() { + if param.starts_with("tap=") { + tap_str = ¶m[4..]; + } else if param.starts_with("ip=") { + ip_str = ¶m[3..]; + } else if param.starts_with("mask=") { + mask_str = ¶m[5..]; + } else if param.starts_with("mac=") { + mac_str = ¶m[4..]; + } + } + + let mut tap: Option<&str> = None; + let mut ip: Ipv4Addr = Ipv4Addr::new(192, 168, 249, 1); + let mut mask: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0);; + let mut mac: MacAddr = MacAddr::local_random(); + + if !tap_str.is_empty() { + tap = Some(tap_str); + } + if !ip_str.is_empty() { + ip = ip_str.parse().map_err(Error::ParseNetIpParam)?; + } + if !mask_str.is_empty() { + mask = mask_str.parse().map_err(Error::ParseNetMaskParam)?; + } + if !mac_str.is_empty() { + mac = MacAddr::parse_str(mac_str).map_err(Error::ParseNetMacParam)?; + } + + Ok(Some(NetConfig { tap, ip, mask, mac })) + } +} + +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>, +} + +impl<'a> VmConfig<'a> { + pub fn parse(vm_params: VmParams<'a>) -> Result { + let mut disks: Vec = Vec::new(); + for disk in vm_params.disks.iter() { + disks.push(DiskConfig::parse(disk)?); + } + + Ok(VmConfig { + cpus: CpusConfig::parse(vm_params.cpus)?, + memory: MemoryConfig::parse(vm_params.memory)?, + 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)?, + }) + } +} diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 51a26c501..dd4037614 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -11,9 +11,11 @@ use kvm_ioctls::*; use std::fmt::{self, Display}; use std::result; +pub mod config; pub mod vm; -use self::vm::{Vm, VmConfig}; +use self::config::VmConfig; +use self::vm::Vm; /// Errors associated with VM management #[derive(Debug)] diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index f19357a6a..17ad7ebc5 100755 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -21,20 +21,18 @@ extern crate vm_memory; extern crate vm_virtio; extern crate vmm_sys_util; +use crate::config::VmConfig; use kvm_bindings::{kvm_pit_config, kvm_userspace_memory_region, KVM_PIT_SPEAKER_DUMMY}; use kvm_ioctls::*; use libc::{c_void, siginfo_t, EFD_NONBLOCK}; -use linux_loader::cmdline; use linux_loader::loader::KernelLoader; -use net_util::{MacAddr, Tap}; +use net_util::Tap; use pci::{PciConfigIo, PciDevice, PciInterruptPin, PciRoot}; use qcow::{self, ImageType, QcowFile}; use std::ffi::CString; use std::fs::{File, OpenOptions}; use std::io::{self, stdout}; -use std::net::Ipv4Addr; use std::os::unix::io::{AsRawFd, RawFd}; -use std::path::{Path, PathBuf}; use std::sync::{Arc, Barrier, Mutex}; use std::{result, str, thread}; use vm_allocator::SystemAllocator; @@ -48,9 +46,6 @@ use vmm_sys_util::terminal::Terminal; use vmm_sys_util::EventFd; const VCPU_RTSIG_OFFSET: i32 = 0; -pub const DEFAULT_VCPUS: u8 = 1; -pub const DEFAULT_MEMORY: GuestUsize = 512; -const CMDLINE_OFFSET: GuestAddress = GuestAddress(0x20000); const X86_64_IRQ_BASE: u32 = 5; // CPUID feature bits @@ -288,98 +283,6 @@ impl Vcpu { } } -pub struct VmConfig<'a> { - kernel_path: &'a Path, - disk_paths: Vec, - rng_path: Option, - cmdline: cmdline::Cmdline, - cmdline_addr: GuestAddress, - net_params: Option, - memory_size: GuestUsize, - vcpu_count: u8, -} - -impl<'a> VmConfig<'a> { - pub fn new( - kernel_path: &'a Path, - disk_paths: Vec, - rng_path: Option, - cmdline_str: String, - net_params: Option, - vcpus: u8, - memory_size: GuestUsize, - ) -> Result { - let mut cmdline = cmdline::Cmdline::new(arch::CMDLINE_MAX_SIZE); - cmdline.insert_str(cmdline_str).unwrap(); - - Ok(VmConfig { - kernel_path, - disk_paths, - rng_path, - cmdline, - cmdline_addr: CMDLINE_OFFSET, - net_params, - memory_size, - vcpu_count: vcpus, - }) - } -} - -#[derive(Debug)] -struct NetParams<'a> { - tap_if_name: Option<&'a str>, - ip_addr: Ipv4Addr, - net_mask: Ipv4Addr, - mac_addr: MacAddr, -} - -fn parse_net_params(net_params: &str) -> Result { - // Split the parameters based on the comma delimiter - let params_list: Vec<&str> = net_params.split(',').collect(); - - let mut tap: &str = ""; - let mut ip: &str = ""; - let mut mask: &str = ""; - let mut mac: &str = ""; - - for param in params_list.iter() { - if param.starts_with("tap=") { - tap = ¶m[4..]; - } else if param.starts_with("ip=") { - ip = ¶m[3..]; - } else if param.starts_with("mask=") { - mask = ¶m[5..]; - } else if param.starts_with("mac=") { - mac = ¶m[4..]; - } - } - - let mut tap_if_name: Option<&str> = None; - let mut ip_addr: Ipv4Addr = "192.168.249.1".parse().unwrap(); - let mut net_mask: Ipv4Addr = "255.255.255.0".parse().unwrap(); - let mut mac_addr: MacAddr = MacAddr::local_random(); - - if !tap.is_empty() { - tap_if_name = Some(tap); - } - if !ip.is_empty() { - ip_addr = ip.parse().unwrap(); - } - if !mask.is_empty() { - net_mask = mask.parse().unwrap(); - } - if !mac.is_empty() { - mac_addr = MacAddr::parse_str(mac).unwrap(); - } - - Ok(NetParams { - tap_if_name, - ip_addr, - net_mask, - mac_addr, - }) -} - struct DeviceManager { io_bus: devices::Bus, mmio_bus: devices::Bus, @@ -420,12 +323,12 @@ impl DeviceManager { let mut pci_root = PciRoot::new(None); - for disk_path in &vm_cfg.disk_paths { + for disk_cfg in &vm_cfg.disks { // Open block device path let raw_img: File = OpenOptions::new() .read(true) .write(true) - .open(disk_path.as_path()) + .open(disk_cfg.path) .map_err(DeviceManagerError::Disk)?; // Add virtio-blk @@ -434,14 +337,14 @@ impl DeviceManager { let block = match image_type { ImageType::Raw => { let raw_img = vm_virtio::RawFile::new(raw_img); - let dev = vm_virtio::Block::new(raw_img, disk_path.to_path_buf(), false) + let dev = vm_virtio::Block::new(raw_img, disk_cfg.path.to_path_buf(), false) .map_err(DeviceManagerError::CreateVirtioBlock)?; Box::new(dev) as Box } ImageType::Qcow2 => { let qcow_img = QcowFile::from(raw_img).map_err(DeviceManagerError::QcowDeviceCreate)?; - let dev = vm_virtio::Block::new(qcow_img, disk_path.to_path_buf(), false) + let dev = vm_virtio::Block::new(qcow_img, disk_cfg.path.to_path_buf(), false) .map_err(DeviceManagerError::CreateVirtioBlock)?; Box::new(dev) as Box } @@ -458,37 +361,32 @@ impl DeviceManager { } // Add virtio-net if required - if let Some(net_params) = &vm_cfg.net_params { - if let Ok(net_params) = parse_net_params(net_params) { - let mut virtio_net_device: vm_virtio::Net; + if let Some(net_cfg) = &vm_cfg.net { + let mut virtio_net_device: vm_virtio::Net; - if let Some(tap_if_name) = net_params.tap_if_name { - let tap = Tap::open_named(tap_if_name).map_err(DeviceManagerError::OpenTap)?; - virtio_net_device = - vm_virtio::Net::new_with_tap(tap, Some(&net_params.mac_addr)) - .map_err(DeviceManagerError::CreateVirtioNet)?; - } else { - virtio_net_device = vm_virtio::Net::new( - net_params.ip_addr, - net_params.net_mask, - Some(&net_params.mac_addr), - ) + if let Some(tap_if_name) = net_cfg.tap { + let tap = Tap::open_named(tap_if_name).map_err(DeviceManagerError::OpenTap)?; + virtio_net_device = vm_virtio::Net::new_with_tap(tap, Some(&net_cfg.mac)) .map_err(DeviceManagerError::CreateVirtioNet)?; - } - - DeviceManager::add_virtio_pci_device( - Box::new(virtio_net_device), - memory.clone(), - allocator, - vm_fd, - &mut pci_root, - &mut mmio_bus, - )?; + } else { + virtio_net_device = + vm_virtio::Net::new(net_cfg.ip, net_cfg.mask, Some(&net_cfg.mac)) + .map_err(DeviceManagerError::CreateVirtioNet)?; } + + DeviceManager::add_virtio_pci_device( + Box::new(virtio_net_device), + memory.clone(), + allocator, + vm_fd, + &mut pci_root, + &mut mmio_bus, + )?; } // Add virtio-rng if required - if let Some(rng_path) = &vm_cfg.rng_path { + if let Some(rng_path) = vm_cfg.rng.src.to_str() { + println!("VIRTIO_RNG PATH {}", rng_path); let virtio_rng_device = vm_virtio::Rng::new(rng_path).map_err(DeviceManagerError::CreateVirtioRng)?; @@ -652,11 +550,11 @@ pub struct Vm<'a> { impl<'a> Vm<'a> { pub fn new(kvm: &Kvm, config: VmConfig<'a>) -> Result { - let kernel = File::open(&config.kernel_path).map_err(Error::KernelFile)?; + let kernel = File::open(&config.kernel.path).map_err(Error::KernelFile)?; let fd = kvm.create_vm().map_err(Error::VmCreate)?; // Init guest memory - let arch_mem_regions = arch::arch_memory_regions(config.memory_size << 20); + 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)?; guest_memory @@ -718,7 +616,7 @@ impl<'a> Vm<'a> { .add_event(&device_manager.exit_evt, EpollDispatch::Exit) .map_err(Error::EpollError)?; - let vcpus = Vec::with_capacity(config.vcpu_count as usize); + let vcpus = Vec::with_capacity(u8::from(&config.cpus) as usize); Ok(Vm { fd, @@ -734,7 +632,7 @@ impl<'a> Vm<'a> { pub fn load_kernel(&mut self) -> Result { let cmdline_cstring = - CString::new(self.config.cmdline.clone()).map_err(|_| Error::CmdLine)?; + CString::new(self.config.cmdline.args.clone()).map_err(|_| Error::CmdLine)?; let entry_addr = linux_loader::loader::Elf::load( &self.memory, None, @@ -745,16 +643,16 @@ impl<'a> Vm<'a> { linux_loader::loader::load_cmdline( &self.memory, - self.config.cmdline_addr, + self.config.cmdline.offset, &cmdline_cstring, ) .map_err(|_| Error::CmdLine)?; - let vcpu_count = self.config.vcpu_count; + let vcpu_count = u8::from(&self.config.cpus); arch::configure_system( &self.memory, - self.config.cmdline_addr, + self.config.cmdline.offset, cmdline_cstring.to_bytes().len() + 1, vcpu_count, ) @@ -818,7 +716,7 @@ impl<'a> Vm<'a> { pub fn start(&mut self, entry_addr: GuestAddress) -> Result<()> { self.devices.register_devices()?; - let vcpu_count = self.config.vcpu_count; + let vcpu_count = u8::from(&self.config.cpus); // let vcpus: Vec> = Vec::with_capacity(vcpu_count as usize); let vcpu_thread_barrier = Arc::new(Barrier::new((vcpu_count + 1) as usize));