From 8f2bb5415708f41eb109cf2a4ad3545f4caef76d Mon Sep 17 00:00:00 2001 From: Carlos Venegas Date: Mon, 24 May 2021 17:01:35 +0000 Subject: [PATCH] main: Handle errors gracefully in main() Handle calls to `CLI`, `VMM::try_from()` and `vmm.run()`. This commit adds anyhow and thiserror crates to handle errors. The `anyhow` crate is used in the main binary to add extra context and give a pretty view of error trace. The create `thiserror` was used to easily derive `std::error::Error` for errors provided by libs. The new crates pulls the following extra crates: ```diff +name = "proc-macro2" +name = "quote" +name = "syn" +name = "thiserror-impl" +name = "unicode-xid" ``` New error format: examples: ```sh $ vmm-reference --kernel \ path=/path/vmlinuz-5.10.25 Error: Failed to create VMM from configurations Caused by: Error issuing an ioctl to KVM. $ echo $? 1 ``` ```sh $ vmm-reference --kernel path Error: Failed to parse CLI options Caused by: Failed to parse cli Invalid input for kernel: Missing required argument: path ``` ``` $vmm-reference Error: Failed to parse CLI options Caused by: Failed to parse cli error: The following required arguments were not provided: --kernel USAGE: vmm-reference [OPTIONS] --kernel For more information try --help ``` Fixes: #99 Signed-off-by: Carlos Venegas --- Cargo.lock | 64 ++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/api/Cargo.toml | 1 + src/api/src/lib.rs | 23 +++++++++++------ src/main.rs | 57 +++++++++++++++++++++++------------------ src/vmm/Cargo.toml | 1 + src/vmm/src/lib.rs | 21 ++++++++++++++- 7 files changed, 134 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c5f9602..91dd6f3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,11 +9,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "anyhow" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" + [[package]] name = "api" version = "0.1.0" dependencies = [ "clap", + "thiserror", "vmm", ] @@ -139,12 +146,41 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "proc-macro2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "syn" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -154,12 +190,38 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-width" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + [[package]] name = "utils" version = "0.1.0" @@ -230,6 +292,7 @@ dependencies = [ "kvm-ioctls", "libc", "linux-loader", + "thiserror", "utils", "vm-device", "vm-memory", @@ -242,6 +305,7 @@ dependencies = [ name = "vmm-reference" version = "0.1.0" dependencies = [ + "anyhow", "api", "vmm", ] diff --git a/Cargo.toml b/Cargo.toml index 412c58ec..1949e030 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" license = "Apache-2.0 OR BSD-3-Clause" [dependencies] +anyhow = "1.0" vmm = { path = "src/vmm" } api = { path = "src/api" } diff --git a/src/api/Cargo.toml b/src/api/Cargo.toml index 37c1594f..d3b39eef 100644 --- a/src/api/Cargo.toml +++ b/src/api/Cargo.toml @@ -6,5 +6,6 @@ edition = "2018" [dependencies] clap = "2.33.3" +thiserror = "1.0" vmm = { path = "../vmm" } diff --git a/src/api/src/lib.rs b/src/api/src/lib.rs index 79cee637..5af9f92a 100644 --- a/src/api/src/lib.rs +++ b/src/api/src/lib.rs @@ -9,8 +9,17 @@ use std::result; use clap::{App, Arg}; +use thiserror::Error; use vmm::VMMConfig; +#[derive(Error, Debug)] +/// Cli api errors. +pub enum CliError { + /// Failed to parse CLI. + #[error("Failed to parse cli {0}")] + Parse(String), +} + /// Command line parser. pub struct CLI; @@ -20,7 +29,7 @@ impl CLI { /// # Arguments /// /// * `cmdline_args` - command line arguments passed to the application. - pub fn launch(cmdline_args: Vec<&str>) -> result::Result { + pub fn launch(cmdline_args: Vec<&str>) -> result::Result { let mut app = App::new(cmdline_args[0].to_string()) .arg( Arg::with_name("memory") @@ -59,21 +68,19 @@ impl CLI { let mut help_msg_buf: Vec = vec![]; // If the write fails, we'll just have an empty help message. let _ = app.write_long_help(&mut help_msg_buf); - let help_msg = String::from_utf8_lossy(&help_msg_buf); - let matches = app.get_matches_from_safe(cmdline_args).map_err(|e| { - eprintln!("{}", help_msg); - format!("Invalid command line arguments: {}", e) - })?; + let matches = app + .get_matches_from_safe(cmdline_args) + .map_err(|e| CliError::Parse(format!("{}", e)))?; - Ok(VMMConfig::builder() + VMMConfig::builder() .memory_config(matches.value_of("memory")) .kernel_config(matches.value_of("kernel")) .vcpu_config(matches.value_of("vcpu")) .net_config(matches.value_of("net")) .block_config(matches.value_of("block")) .build() - .map_err(|e| format!("{:?}", e))?) + .map_err(|e| CliError::Parse(format!("{}", e))) } } diff --git a/src/main.rs b/src/main.rs index 573f75bf..d69e56c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,37 +1,44 @@ // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause -#[cfg(target_arch = "x86_64")] -use std::convert::TryFrom; -#[cfg(target_arch = "x86_64")] -use std::env; #[cfg(target_arch = "x86_64")] -use api::CLI; -#[cfg(target_arch = "x86_64")] -use vmm::VMM; +mod main { + use std::convert::TryFrom; + use std::env; + + use api::CLI; + use vmm::VMM; -fn main() { - #[cfg(target_arch = "x86_64")] - { - match CLI::launch( + use anyhow::Context; + + pub type Result = anyhow::Result; + + pub fn run() -> Result<()> { + let vmm_config = CLI::launch( env::args() .collect::>() .iter() .map(|s| s.as_str()) .collect(), - ) { - Ok(vmm_config) => { - let mut vmm = - VMM::try_from(vmm_config).expect("Failed to create VMM from configurations"); - // For now we are just unwrapping here, in the future we might use a nicer way of - // handling errors such as pretty printing them. - vmm.run().unwrap(); - } - Err(e) => { - eprintln!("Failed to parse command line options. {}", e); - } - } + ) + .context("Failed to parse CLI options")?; + + let mut vmm = + VMM::try_from(vmm_config).context("Failed to create VMM from configurations")?; + + vmm.run().context("failed to run VMM") + } +} + +#[cfg(target_arch = "aarch64")] +mod main { + pub type Result = std::result::Result; + pub fn run() -> Result<()> { + println!("Reference VMM under construction!"); + Ok(()) } - #[cfg(target_arch = "aarch64")] - println!("Reference VMM under construction!") +} + +fn main() -> main::Result<()> { + main::run() } diff --git a/src/vmm/Cargo.toml b/src/vmm/Cargo.toml index 2366c899..0a0d26b2 100644 --- a/src/vmm/Cargo.toml +++ b/src/vmm/Cargo.toml @@ -5,6 +5,7 @@ authors = ["rust-vmm AWS maintainers "] edition = "2018" [dependencies] +thiserror = "1.0" event-manager = "0.2.1" kvm-bindings = { version = "0.4.0", features = ["fam-wrappers"] } diff --git a/src/vmm/src/lib.rs b/src/vmm/src/lib.rs index 7b302c54..cff44b73 100644 --- a/src/vmm/src/lib.rs +++ b/src/vmm/src/lib.rs @@ -49,6 +49,8 @@ use serial::SerialWrapper; use vm_vcpu::vcpu::{cpuid::filter_cpuid, VcpuState}; use vm_vcpu::vm::{self, ExitHandler, KvmVm, VmState}; +use thiserror::Error; + mod boot; mod config; @@ -81,41 +83,58 @@ pub enum MemoryError { } /// VMM errors. -#[derive(Debug)] +#[derive(Error, Debug)] pub enum Error { /// Failed to create block device. + #[error("Failed to create block device {0:?}.")] Block(block::Error), /// Failed to write boot parameters to guest memory. + #[error("Failed to write boot parameters to guest memory {0:?}.")] BootConfigure(configurator::Error), /// Error configuring boot parameters. + #[error("Error configuring boot parameters {0:?}.")] BootParam(boot::Error), /// Error configuring the kernel command line. + #[error("Error configuring the kernel command line {0:?}.")] Cmdline(cmdline::Error), /// Error setting up devices. + #[error("Error setting up devices {0:?}.")] Device(serial::Error), /// Event management error. + #[error("Event management error {0}.")] EventManager(event_manager::Error), /// I/O error. + #[error("I/O error {0}.")] IO(io::Error), /// Failed to load kernel. + #[error("Failed to load kernel {0}.")] KernelLoad(loader::Error), /// Failed to create net device. + #[error("Failed to create net device {0:?}.")] Net(net::Error), /// Address stored in the rip registry does not fit in guest memory. + #[error("Address stored in the rip registry does not fit in guest memory.")] RipOutOfGuestMemory, /// Invalid KVM API version. + #[error("Invalid KVM API version {0}.")] KvmApiVersion(i32), /// Unsupported KVM capability. + #[error("Unsupported KVM capability {0:?}.")] KvmCap(Cap), /// Error issuing an ioctl to KVM. + #[error("Error issuing an ioctl to KVM {0:?}.")] KvmIoctl(kvm_ioctls::Error), /// Memory error. + #[error("Memory error {0:?}.")] Memory(MemoryError), /// Invalid number of vCPUs specified. + #[error("Invalid number of vCPUs specified {0:?}.")] VcpuNumber(u8), /// VM errors. + #[error("VM errors {0:?}.")] Vm(vm::Error), /// Exit event errors. + #[error("Exit event errors {0:?}.")] ExitEvent(io::Error), }