From 0384c1d050057035d892e2b52f825a4be4bc73a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Sun, 5 Dec 2021 22:20:36 +0100 Subject: [PATCH] Rework CLI interface with structopt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Have structs for args to modularize arg handling * Improve argument docs * Improve error messages on faulty args * Colorize help output * Rename `--disable-hugepages` to `--no-thp` * Corresponds to QEMU's `-redhat-disable-THP` * Remove `HERMIT_HUGEPAGES` * Replace `--mergeable` with `--ksm` * Corresponds to QEMU's `-redhat-disable-KSM` * Remove `HERMIT_MERGEABLE` * Remove `HERMIT_VERBOSE` * Rename `-c, --cpus` to `-c, --cpu-count` * Rename `HERMIT_CPUS` to `HERMIT_CPU_COUNT` * Rename `-s, --gdb_port` to `-s, --gdb-port` * Rename `-m, --memsize` to `-m, --memory-size` * Rename `HERMIT_MEM` to `HERMIT_MEMORY_SIZE` * Disable `--nic` since networking is currently not supported ``` uhyve 0.0.29 Stefan Lankes , Martin Kröning , Jens Breitbart , Jonathan Klimt A minimal hypervisor for RustyHermit USAGE: uhyve [OPTIONS] [KERNEL_ARGS]... ARGS: The kernel to execute ... Arguments to forward to the kernel OPTIONS: -h, --help Print help information -s, --gdb-port GDB server port Starts a GDB server on the provided port and waits for a connection. [env: HERMIT_GDB_PORT=] -v, --verbose Print kernel messages -V, --version Print version information MEMORY: --ksm Kernel Samepage Merging Advise the kernel to enable Kernel Samepage Merging [KSM] on the virtual RAM. [KSM]: https://www.kernel.org/doc/html/latest/admin-guide/mm/ksm.html -m, --memory-size Guest RAM size [env: HERMIT_MEMORY_SIZE=] [default: "64.00 MiB"] --no-thp No Transparent Hugepages Don't advise the kernel to enable Transparent Hugepages [THP] on the virtual RAM. [THP]: https://www.kernel.org/doc/html/latest/admin-guide/mm/transhuge.html CPU: -a, --affinity Bind guest vCPUs to host cpus A list of host CPU numbers onto which the guest vCPUs should be bound to obtain performance benefits. List items may be single numbers or inclusive ranges. List items may be separated with commas or spaces. # Examples * `--affinity "0 1 2"` * `--affinity 0-1,2` -c, --cpu-count Number of guest CPUs [env: HERMIT_CPU_COUNT=] [default: 1] ``` ``` uhyve 0.0.29 Stefan Lankes Martin Kröning Jens Breitbart Jonathan Klimt USAGE: uhyve [FLAGS] [OPTIONS] [ARGUMENTS]... FLAGS: --disable-hugepages Disable the usage of huge pages --mergeable Enable kernel feature to merge same pages -v, --verbose Print also kernel messages -h, --help Prints help information -V, --version Prints version information OPTIONS: -c, --cpus Number of guest processors [env: HERMIT_CPUS=] -a, --affinity A list of CPUs delimited by commas onto which the virtual CPUs should be bound. This may improve performance. -s, --gdb_port Enables GDB-Stub on given port [env: HERMIT_GDB_PORT=] -m, --memsize Memory size of the guest [env: HERMIT_MEM=] --nic Name of the network interface [env: HERMIT_NETIF=] ARGS: Sets path to the kernel ... Arguments of the unikernel ``` --- Cargo.lock | 102 +++++++-- Cargo.toml | 6 +- src/bin/uhyve.rs | 583 +++++++++++++++++++++++++++++------------------ src/lib.rs | 1 - src/utils.rs | 46 ---- 5 files changed, 446 insertions(+), 292 deletions(-) delete mode 100644 src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index fe5e75b0..f8256413 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,15 +11,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "assert_fs" version = "1.0.6" @@ -129,13 +120,39 @@ version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ - "ansi_term", + "bitflags", + "textwrap 0.11.0", + "unicode-width", +] + +[[package]] +name = "clap" +version = "3.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b34190c12bd1d613deba77e1cc13e68eaf4a0d51e389dbd485b7bfe15a47c0" +dependencies = [ "atty", "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", "strsim", - "textwrap", - "unicode-width", - "vec_map", + "termcolor", + "textwrap 0.14.2", +] + +[[package]] +name = "clap_derive" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "517358c28fcef6607bf6f76108e02afad7e82297d132a6b846dcc1fc3efcd153" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -158,7 +175,7 @@ checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" dependencies = [ "atty", "cast", - "clap", + "clap 2.34.0", "criterion-plot", "csv", "itertools", @@ -394,6 +411,12 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -606,6 +629,15 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "os_str_bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +dependencies = [ + "memchr", +] + [[package]] name = "paste" version = "1.0.6" @@ -679,6 +711,30 @@ dependencies = [ "termtree", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.36" @@ -929,9 +985,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" @@ -982,6 +1038,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "textwrap" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" + [[package]] name = "thiserror" version = "1.0.30" @@ -1039,7 +1101,7 @@ dependencies = [ "burst", "byte-unit", "byteorder", - "clap", + "clap 3.0.8", "core_affinity", "criterion", "either", @@ -1086,10 +1148,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cf7d77f457ef8dfa11e4cd5933c5ddb5dc52a94664071951219a97710f0a32b" [[package]] -name = "vec_map" -version = "0.8.2" +name = "version_check" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "virtio-bindings" diff --git a/Cargo.toml b/Cargo.toml index 257c3c0d..5dba48a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,8 +45,8 @@ xhypervisor = { git = "https://github.com/RWTH-OS/xhypervisor.git", branch = "aa [dependencies] bitflags = "1.3" byteorder = "1.4" -byte-unit = "4.0" -clap = "2.34" +byte-unit = { version = "4.0", default-features = false, features = ["std"] } +clap = { version = "3", features = ["derive", "env"] } core_affinity = "0.5" either = "1.6" env_logger = "0.9" @@ -57,6 +57,7 @@ goblin = { version = "0.4", default-features = false, features = ["elf64", "elf3 lazy_static = "1.4" libc = "0.2" log = "0.4" +mac_address = "1.1" rustc-serialize = "0.3" thiserror = "1.0" @@ -66,7 +67,6 @@ rftrace-frontend = { version = "0.1", optional = true } [target.'cfg(target_os = "linux")'.dependencies] kvm-bindings = "0.5" kvm-ioctls = "0.10" -mac_address = "1.1" nix = "0.23" tun-tap = { version = "0.1", default-features = false } virtio-bindings = { version = "0.1", features = ["virtio-v4_14_0"] } diff --git a/src/bin/uhyve.rs b/src/bin/uhyve.rs index 71612dd8..f51e5438 100644 --- a/src/bin/uhyve.rs +++ b/src/bin/uhyve.rs @@ -1,24 +1,22 @@ #![warn(rust_2018_idioms)] -#[macro_use] -extern crate log; -#[macro_use] -extern crate clap; - -use std::collections::HashSet; -use std::env; +use std::ffi::OsString; +use std::net::Ipv4Addr; +use std::num::{NonZeroU32, ParseIntError, TryFromIntError}; +use std::ops::RangeInclusive; use std::path::PathBuf; +use std::process; use std::str::FromStr; +use std::{fmt, iter}; -use uhyvelib::utils; -use uhyvelib::vm; -use uhyvelib::Uhyve; - -use byte_unit::Byte; -use clap::{App, Arg}; +use byte_unit::{AdjustedByte, Byte, ByteError}; +use clap::{App, ErrorKind, IntoApp, Parser}; +use core_affinity::CoreId; +use either::Either; +use mac_address::MacAddress; +use thiserror::Error; -const MINIMAL_GUEST_SIZE: usize = 16 * 1024 * 1024; -const DEFAULT_GUEST_SIZE: usize = 64 * 1024 * 1024; +use uhyvelib::{vm, Uhyve}; #[cfg(feature = "instrument")] fn setup_trace() { @@ -44,224 +42,365 @@ fn setup_trace() { } } -// Note that we end main with `std::process::exit` to set the return value and -// as a result destructors are not run and cleanup may not happen. -fn main() { - #[cfg(feature = "instrument")] - setup_trace(); +#[derive(Parser, Debug)] +#[clap(version, author, about)] +struct Args { + /// Print kernel messages + #[clap(short, long)] + verbose: bool, - env_logger::init(); + #[clap(flatten, help_heading = "MEMORY")] + memory_args: MemoryArgs, - let matches = App::new("uhyve") - .version(crate_version!()) - .setting(clap::AppSettings::TrailingVarArg) - .setting(clap::AppSettings::AllowLeadingHyphen) - .author(crate_authors!("\n")) - .about("A minimal hypervisor for RustyHermit") - .arg( - Arg::with_name("VERBOSE") - .short("v") - .long("verbose") - .help("Print also kernel messages"), - ) - .arg( - Arg::with_name("DISABLE_HUGEPAGE") - .long("disable-hugepages") - .help("Disable the usage of huge pages"), - ) - .arg( - Arg::with_name("MERGEABLE") - .long("mergeable") - .help("Enable kernel feature to merge same pages"), - ) - .arg( - Arg::with_name("MEM") - .short("m") - .long("memsize") - .value_name("MEM") - .help("Memory size of the guest") - .takes_value(true) - .env("HERMIT_MEM"), - ) - .arg( - Arg::with_name("CPUS") - .short("c") - .long("cpus") - .value_name("CPUS") - .help("Number of guest processors") - .takes_value(true) - .env("HERMIT_CPUS"), - ) - .arg( - Arg::with_name("CPU_AFFINITY") - .short("a") - .long("affinity") - .value_name("cpulist") - .help("CPU Affinity of guest CPUs on Host") - .long_help( - "A list of CPUs delimited by commas onto which - the virtual CPUs should be bound. This may improve - performance. - ", - ), - ) - .arg( - Arg::with_name("GDB_PORT") - .short("s") - .long("gdb_port") - .value_name("GDB_PORT") - .help("Enables GDB-Stub on given port") - .takes_value(true) - .env("HERMIT_GDB_PORT"), - ) - .arg( - Arg::with_name("NETIF") - .long("nic") - .value_name("NETIF") - .help("Name of the network interface") - .takes_value(true) - .env("HERMIT_NETIF"), - ) - /*.arg( - Arg::with_name("IP") - .long("ip") - .value_name("IP") - .help("IP address of the guest") - .takes_value(true) - .env("HERMIT_IP"), - ) - .arg( - Arg::with_name("GATEWAY") - .long("gateway") - .value_name("GATEWAY") - .help("Gateway address") - .takes_value(true) - .env("HERMIT_GATEWAY"), - ) - .arg( - Arg::with_name("MASK") - .long("mask") - .value_name("MASK") - .help("Network mask") - .takes_value(true) - .env("HERMIT_MASK"), - ) - .arg( - Arg::with_name("MAC") - .long("mac") - .value_name("MAC") - .help("MAC address of the network interface") - .takes_value(true) - .env("HERMIT_MASK"), - )*/ - .arg( - Arg::with_name("KERNEL") - .help("Sets path to the kernel") - .required(true) - .index(1), - ) - .arg( - Arg::with_name("ARGUMENTS") - .help("Arguments of the unikernel") - .required(false) - .multiple(true) - .max_values(255), - ) - .get_matches(); - - let path = PathBuf::from_str( - matches - .value_of("KERNEL") - .expect("Expect path to the kernel!"), - ) - .expect("Invalid kernel path"); - let mem_size: usize = matches - .value_of("MEM") - .map(|s| { - let mem = Byte::from_str(s) - .expect("Invalid MEM specified") - .get_bytes() - .try_into() - .unwrap(); - if mem < MINIMAL_GUEST_SIZE { - warn!( - "Resize guest memory to {}", - Byte::from_bytes(MINIMAL_GUEST_SIZE.try_into().unwrap()) - ); - MINIMAL_GUEST_SIZE - } else { - mem + #[clap(flatten, help_heading = "CPU")] + cpu_args: CpuArgs, + + /// GDB server port + /// + /// Starts a GDB server on the provided port and waits for a connection. + #[clap(short = 's', long, env = "HERMIT_GDB_PORT")] + gdb_port: Option, + + // #[clap(flatten, help_heading = "NETWORK")] + #[clap(skip)] + network_args: NetworkArgs, + + /// The kernel to execute + #[clap(parse(from_os_str))] + kernel: PathBuf, + + /// Arguments to forward to the kernel + #[clap(parse(from_os_str))] + kernel_args: Vec, +} + +#[derive(Debug, Clone, Copy)] +pub struct GuestMemorySize(Byte); + +impl GuestMemorySize { + const fn minimum() -> Byte { + Byte::from_bytes(16 * 1024 * 1024) + } + + pub fn get(self) -> usize { + self.0.get_bytes().try_into().unwrap() + } +} + +impl Default for GuestMemorySize { + fn default() -> Self { + Self(Byte::from_bytes(64 * 1024 * 1024)) + } +} + +impl fmt::Display for GuestMemorySize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.get_appropriate_unit(true).fmt(f) + } +} + +#[derive(Error, Debug)] +#[error("invalid amount of guest memory (minimum: {}, found {0})", GuestMemorySize::minimum().get_appropriate_unit(true))] +pub struct InvalidGuestMemorySizeError(AdjustedByte); + +impl TryFrom for GuestMemorySize { + type Error = InvalidGuestMemorySizeError; + + fn try_from(value: Byte) -> Result { + if value >= Self::minimum() { + Ok(Self(value)) + } else { + let value = value.get_appropriate_unit(true); + Err(InvalidGuestMemorySizeError(value)) + } + } +} + +#[derive(Error, Debug)] +pub enum ParseByteError { + #[error(transparent)] + Parse(#[from] ByteError), + + #[error(transparent)] + InvalidMemorySize(#[from] InvalidGuestMemorySizeError), +} + +impl FromStr for GuestMemorySize { + type Err = ParseByteError; + + fn from_str(s: &str) -> Result { + let requested = Byte::from_str(s)?; + let memory_size = requested.try_into()?; + Ok(memory_size) + } +} + +#[derive(Parser, Debug)] +struct MemoryArgs { + /// Guest RAM size + #[clap(short = 'm', long, default_value_t, env = "HERMIT_MEMORY_SIZE")] + memory_size: GuestMemorySize, + + /// No Transparent Hugepages + /// + /// Don't advise the kernel to enable Transparent Hugepages [THP] on the virtual RAM. + /// + /// [THP]: https://www.kernel.org/doc/html/latest/admin-guide/mm/transhuge.html + #[clap(long)] + no_thp: bool, + + /// Kernel Samepage Merging + /// + /// Advise the kernel to enable Kernel Samepage Merging [KSM] on the virtual RAM. + /// + /// [KSM]: https://www.kernel.org/doc/html/latest/admin-guide/mm/ksm.html + #[clap(long)] + ksm: bool, +} + +#[derive(Debug, Clone, Copy)] +pub struct CpuCount(NonZeroU32); + +impl CpuCount { + pub fn get(self) -> u32 { + self.0.get() + } +} + +impl Default for CpuCount { + fn default() -> Self { + let default = 1.try_into().unwrap(); + Self(default) + } +} + +impl fmt::Display for CpuCount { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl TryFrom for CpuCount { + type Error = TryFromIntError; + + fn try_from(value: u32) -> Result { + value.try_into().map(Self) + } +} + +impl FromStr for CpuCount { + type Err = ParseIntError; + + fn from_str(s: &str) -> Result { + let count = s.parse()?; + Ok(Self(count)) + } +} + +#[derive(Debug, Clone)] +struct Affinity(Vec); + +impl Affinity { + fn parse_ranges_iter<'a>( + ranges: impl IntoIterator + 'a, + ) -> impl Iterator> + 'a { + struct ParsedRange(RangeInclusive); + + impl FromStr for ParsedRange { + type Err = ParseIntError; + + fn from_str(s: &str) -> Result { + let range = match s.split_once('-') { + Some((start, end)) => start.parse()?..=end.parse()?, + None => { + let idx = s.parse()?; + idx..=idx + } + }; + Ok(Self(range)) } - }) - .unwrap_or(DEFAULT_GUEST_SIZE); - let num_cpus = matches - .value_of("CPUS") - .and_then(|cpus| cpus.parse().ok()) - .unwrap_or(1); - - let cpu_affinity = matches.values_of("CPU_AFFINITY").map(|affinity| { - let parsed_affinity = utils::parse_ranges(affinity) - .collect::, _>>() - .expect("Invalid parameters passed for CPU_AFFINITY"); - - // According to https://github.com/Elzair/core_affinity_rs/issues/3 - // on linux this gives a list of CPUs the process is allowed to run on - // (as opposed to all CPUs available on the system as the docs suggest) - let core_ids = core_affinity::get_core_ids() - .expect("Dependency core_affinity failed to find any available CPUs") + } + + ranges .into_iter() - .filter(|core_id| parsed_affinity.contains(&core_id.id)) - .collect::>(); - assert_eq!(core_ids.len(), num_cpus as usize); - core_ids - }); - - let ip = None; //matches.value_of("IP").or(None); - let gateway = None; // matches.value_of("GATEWAY").or(None); - let mask = None; //matches.value_of("MASK").or(None); - let nic = None; //matches.value_of("NETIF").or(None); - - let mut mergeable = envmnt::is_or("HERMIT_MERGEABLE", false); - if matches.is_present("MERGEABLE") { - mergeable = true; + .map(ParsedRange::from_str) + .flat_map(|range| match range { + Ok(range) => Either::Left(range.0.map(Ok)), + Err(err) => Either::Right(iter::once(Err(err))), + }) } - // per default we use huge page to improve the performance, - // if the kernel supports transparent hugepages - let hugepage_default = true; - info!("Default hugepages set to: {}", hugepage_default); - // HERMIT_HUGEPAGES overrides the default we detected. - let mut hugepage = envmnt::is_or("HERMIT_HUGEPAGE", hugepage_default); - if matches.is_present("DISABLE_HUGEPAGE") { - hugepage = false; + + fn parse_ranges(ranges: &str) -> Result, ParseIntError> { + Self::parse_ranges_iter(ranges.split([' ', ','].as_slice())).collect() } - let mut verbose = envmnt::is_or("HERMIT_VERBOSE", false); - if matches.is_present("VERBOSE") { - verbose = true; +} + +#[derive(Error, Debug)] +enum ParseAffinityError { + #[error(transparent)] + Parse(#[from] ParseIntError), + + #[error( + "Available cores: {available_cores:?}, requested affinities: {requested_affinities:?}" + )] + InvalidValue { + available_cores: Vec, + requested_affinities: Vec, + }, +} + +impl FromStr for Affinity { + type Err = ParseAffinityError; + + fn from_str(s: &str) -> Result { + let available_cores = core_affinity::get_core_ids() + .unwrap() + .into_iter() + .map(|core_id| core_id.id) + .collect::>(); + + let requested_affinities = Self::parse_ranges(s)?; + + if !requested_affinities + .iter() + .all(|affinity| available_cores.contains(affinity)) + { + return Err(ParseAffinityError::InvalidValue { + available_cores, + requested_affinities, + }); + } + + let core_ids = requested_affinities + .into_iter() + .map(|affinity| CoreId { id: affinity }) + .collect(); + Ok(Self(core_ids)) } - let gdbport = matches - .value_of("GDB_PORT") - .map(|p| p.parse::().expect("Could not parse gdb port")) - .or_else(|| { - env::var("HERMIT_GDB_PORT") - .ok() - .map(|p| p.parse::().expect("Could not parse gdb port")) - }); +} + +#[derive(Parser, Debug, Clone)] +struct CpuArgs { + /// Number of guest CPUs + #[clap(short, long, default_value_t, env = "HERMIT_CPU_COUNT")] + cpu_count: CpuCount, + + /// Bind guest vCPUs to host cpus + /// + /// A list of host CPU numbers onto which the guest vCPUs should be bound to obtain performance benefits. + /// List items may be single numbers or inclusive ranges. + /// List items may be separated with commas or spaces. + /// + /// # Examples + /// + /// * `--affinity "0 1 2"` + /// + /// * `--affinity 0-1,2` + #[clap(short, long, name = "CPUs")] + affinity: Option, +} + +impl CpuArgs { + fn get_affinity(self, app: &mut App<'_>) -> Option> { + self.affinity.map(|affinity| { + let affinity_num_vals = affinity.0.len(); + let cpus_num_vals = self.cpu_count.get().try_into().unwrap(); + if affinity_num_vals != cpus_num_vals { + let affinity_arg = app + .get_arguments() + .find(|arg| arg.get_name() == "affinity") + .unwrap(); + let cpus_arg = app + .get_arguments() + .find(|arg| arg.get_name() == "cpus") + .unwrap(); + let verb = if affinity_num_vals > 1 { "were" } else { "was" }; + let message = format!( + "The argument '{affinity_arg}' requires {cpus_num_vals} values (matching '{cpus_arg}'), but {affinity_num_vals} {verb} provided", + affinity_arg = affinity_arg, + cpus_num_vals = cpus_num_vals, + cpus_arg = cpus_arg, + affinity_num_vals = affinity_num_vals, + verb = verb, + ); + app.error(ErrorKind::WrongNumberOfValues, message).exit() + } else { + affinity.0 + } + }) + } +} + +#[derive(Parser, Debug, Default)] +struct NetworkArgs { + /// Guest IP address + #[clap(long, env = "HERMIT_IP")] + ip: Option, + + /// Guest gateway address + #[clap(long, env = "HERMIT_GATEWAY")] + gateway: Option, + + /// Guest network mask + #[clap(long, env = "HERMIT_MASK")] + mask: Option, + + /// Name of the network interface + #[clap(long, env = "HERMIT_NETIF")] + nic: Option, + + /// MAC address of the network interface + #[clap(long, env = "HERMIT_MAC")] + _mac: Option, +} + +fn run_uhyve() -> i32 { + #[cfg(feature = "instrument")] + setup_trace(); + + env_logger::init(); + + let mut app = Args::into_app(); + let Args { + verbose, + memory_args, + cpu_args, + gdb_port, + network_args: NetworkArgs { + ip, + gateway, + mask, + nic, + _mac, + }, + kernel, + kernel_args: _kernel_args, + } = Args::parse(); + let cpu_count = cpu_args.cpu_count; + let affinity = cpu_args.get_affinity(&mut app); + + let ip = ip.map(|ip| ip.to_string()); + let gateway = gateway.map(|ip| ip.to_string()); + let mask = mask.map(|ip| ip.to_string()); let params = vm::Parameter { - mem_size, - num_cpus, + mem_size: memory_args.memory_size.get(), + num_cpus: cpu_count.get(), verbose, - hugepage, - mergeable, - ip, - gateway, - mask, - nic, - gdbport, + hugepage: !memory_args.no_thp, + mergeable: memory_args.ksm, + ip: ip.as_deref(), + gateway: gateway.as_deref(), + mask: mask.as_deref(), + nic: nic.as_deref(), + gdbport: gdb_port, }; - let code = Uhyve::new(path, ¶ms) + Uhyve::new(kernel, ¶ms) .expect("Unable to create VM! Is the hypervisor interface (e.g. KVM) activated?") - .run(cpu_affinity); - std::process::exit(code); + .run(affinity) +} + +fn main() { + process::exit(run_uhyve()) } diff --git a/src/lib.rs b/src/lib.rs index d804b0bf..3173c8cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,6 @@ pub mod macos; pub use macos as os; #[cfg(target_os = "linux")] pub mod shared_queue; -pub mod utils; pub mod vm; pub use arch::*; diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index 32777722..00000000 --- a/src/utils.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! Utilities for the binary frontend. -//! -//! These functions are used to parse command line arguments or determining defaults. - -use std::{iter, num::ParseIntError}; - -use either::Either; - -/// Parses ranges from strings into discrete steps. -pub fn parse_ranges<'a>( - ranges: impl IntoIterator + 'a, -) -> impl Iterator> + 'a { - ranges - .into_iter() - .map(|range| { - let range = match range.split_once('-') { - Some((start, end)) => start.parse()?..=end.parse()?, - None => { - let idx = range.parse()?; - idx..=idx - } - }; - Ok(range) - }) - .flat_map(|range| match range { - Ok(range) => Either::Left(range.map(Ok)), - Err(err) => Either::Right(iter::once(Err(err))), - }) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_cpu_affinity() { - assert_eq!( - parse_ranges(["8-10", "5", "3", "7-9"]) - .collect::, _>>() - .unwrap(), - [8, 9, 10, 5, 3, 7, 8, 9] - ); - - parse_ranges(["-1-2", "-5"]).for_each(|res| assert!(res.is_err())); - } -}