crosvm: argument parsing without clap

This removes the clap dependency by replacing that functionality with a
custom written parser. Binary size is reduced by about 60% in optimized
and stripped mode.

TEST=cargo run -- run -h
BUG=None

Change-Id: I2eaf6fcff121ab16613c444693d95fdf3ad04da3
Reviewed-on: https://chromium-review.googlesource.com/636011
Commit-Ready: Zach Reizner <zachr@chromium.org>
Tested-by: Zach Reizner <zachr@chromium.org>
Reviewed-by: Dylan Reid <dgreid@chromium.org>
This commit is contained in:
Zach Reizner 2017-08-26 18:05:48 -07:00 committed by chrome-bot
parent e932102386
commit efe957849b
3 changed files with 698 additions and 188 deletions

View file

@ -21,9 +21,5 @@ vhost = { path = "vhost" }
virtio_sys = { path = "virtio_sys" }
data_model = { path = "data_model" }
[dependencies.clap]
version = "*"
default-features = false
[target.'cfg(target_arch = "x86_64")'.dependencies]
x86_64 = { path = "x86_64" }

410
src/argument.rs Normal file
View file

@ -0,0 +1,410 @@
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Handles argument parsing.
//!
//! # Example
//!
//! ```
//! const ARGUMENTS: &'static [Argument] = &[
//! Argument::positional("FILES", "files to operate on"),
//! Argument::short_value('p', "program", "PROGRAM", "Program to apply to each file"),
//! Argument::short_value('c', "cpus", "N", "Number of CPUs to use. (default: 1)"),
//! Argument::flag("unmount", "Unmount the root"),
//! Argument::short_flag('h', "help", "Print help message."),
//! ];
//!
//! let match_res = set_arguments(args, ARGUMENTS, |name, value| {
//! match name {
//! "" => println!("positional arg! {}", value.unwrap()),
//! "program" => println!("gonna use program {}", value.unwrap()),
//! "cpus" => {
//! let v: u32 = value.unwrap().parse().map_err(|_| {
//! Error::InvalidValue {
//! value: value.unwrap().to_owned(),
//! expected: "this value for `cpus` needs to be integer",
//! }
//! })?;
//! }
//! "unmount" => println!("gonna unmount"),
//! "help" => return Err(Error::PrintHelp),
//! _ => unreachable!(),
//! }
//! }
//!
//! match match_res {
//! Ok(_) => println!("running with settings"),
//! Err(Error::PrintHelp) => print_help("best_program", "FILES", ARGUMENTS),
//! Err(e) => println!("{}", e),
//! }
//! ```
use std::fmt;
use std::result;
/// An error with argument parsing.
pub enum Error {
/// There was a syntax error with the argument.
Syntax(String),
/// The argumen's name is unused.
UnknownArgument(String),
/// The argument was required.
ExpectedArgument(String),
/// The argument's given value is invalid.
InvalidValue {
value: String,
expected: &'static str,
},
/// The argument was already given and none more are expected.
TooManyArguments(String),
/// The argument expects a value.
ExpectedValue(String),
/// The help information was requested
PrintHelp,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Error::Syntax(ref s) => write!(f, "syntax error: {}", s),
&Error::UnknownArgument(ref s) => write!(f, "unknown argument: {}", s),
&Error::ExpectedArgument(ref s) => write!(f, "expected argument: {}", s),
&Error::InvalidValue {
ref value,
expected,
} => write!(f, "invalid value {:?}: {}", value, expected),
&Error::TooManyArguments(ref s) => write!(f, "too many arguments: {}", s),
&Error::ExpectedValue(ref s) => write!(f, "expected parameter value: {}", s),
&Error::PrintHelp => write!(f, "help was requested"),
}
}
}
/// Result of a argument parsing.
pub type Result<T> = result::Result<T, Error>;
/// Information about an argument expected from the command line.
///
/// # Examples
///
/// To indicate a flag style argument:
///
/// ```
/// Argument::short_flag('f', "flag", "enable awesome mode")
/// ```
///
/// To indicate a parameter style argument that expects a value:
///
/// ```
/// // "VALUE" and "NETMASK" are placeholder values displayed in the help message for these
/// // arguments.
/// Argument::short_value('v', "val", "VALUE", "how much do you value this usage information")
/// Argument::value("netmask", "NETMASK", "hides your netface")
/// ```
///
/// To indicate an argument with no short version:
///
/// ```
/// Argument::flag("verbose", "this option is hard to type quickly")
/// ```
///
/// To indicate a positional argument:
///
/// ```
/// Argument::positional("VALUES", "these are positional arguments")
/// ```
#[derive(Default)]
pub struct Argument {
/// The name of the value to display in the usage information. Use None to indicate that there
/// is no value expected for this argument.
pub value: Option<&'static str>,
/// Optional single character shortened argument name.
pub short: Option<char>,
/// The long name of this argument.
pub long: &'static str,
/// Helpfuly usage information for this argument to display to the user.
pub help: &'static str,
}
impl Argument {
pub fn positional(value: &'static str, help: &'static str) -> Argument {
Argument {
value: Some(value),
long: "",
help: help,
..Default::default()
}
}
pub fn value(long: &'static str, value: &'static str, help: &'static str) -> Argument {
Argument {
value: Some(value),
long: long,
help: help,
..Default::default()
}
}
pub fn short_value(short: char,
long: &'static str,
value: &'static str,
help: &'static str)
-> Argument {
Argument {
value: Some(value),
short: Some(short),
long: long,
help: help,
}
}
pub fn flag(long: &'static str, help: &'static str) -> Argument {
Argument {
long: long,
help: help,
..Default::default()
}
}
pub fn short_flag(short: char, long: &'static str, help: &'static str) -> Argument {
Argument {
short: Some(short),
long: long,
help: help,
..Default::default()
}
}
}
fn parse_arguments<I, R, F>(args: I, mut f: F) -> Result<()>
where I: Iterator<Item = R>,
R: AsRef<str>,
F: FnMut(&str, Option<&str>) -> Result<()>
{
enum State {
// Initial state at the start and after finishing a single argument/value.
Top,
// The remaining arguments are all positional.
Positional,
// The next string is the value for the argument `name`.
Value { name: String },
}
let mut s = State::Top;
for arg in args {
let arg = arg.as_ref();
s = match s {
State::Top => {
if arg == "--" {
State::Positional
} else if arg.starts_with("--") {
let param = arg.trim_left_matches('-');
if param.contains('=') {
let mut iter = param.splitn(2, '=');
let name = iter.next().unwrap();
let value = iter.next().unwrap();
if name.is_empty() {
return Err(Error::Syntax("expected parameter name before `=`"
.to_owned()));
}
if value.is_empty() {
return Err(Error::Syntax("expected parameter value after `=`"
.to_owned()));
}
f(name, Some(value))?;
State::Top
} else {
if let Err(e) = f(param, None) {
if let Error::ExpectedValue(_) = e {
State::Value { name: param.to_owned() }
} else {
return Err(e);
}
} else {
State::Top
}
}
} else if arg.starts_with("-") {
if arg.len() == 1 {
return Err(Error::Syntax("expected argument short name after `-`"
.to_owned()));
}
let name = &arg[1..2];
let value = if arg.len() > 2 { Some(&arg[2..]) } else { None };
if let Err(e) = f(name, value) {
if let Error::ExpectedValue(_) = e {
State::Value { name: name.to_owned() }
} else {
return Err(e);
}
} else {
State::Top
}
} else {
f("", Some(&arg))?;
State::Positional
}
}
State::Positional => {
f("", Some(&arg))?;
State::Positional
}
State::Value { name } => {
f(&name, Some(&arg))?;
State::Top
}
};
}
Ok(())
}
/// Parses the given `args` against the list of know arguments `arg_list` and calls `f` with each
/// present argument and value if required.
///
/// This function guarantees that only valid long argument names from `arg_list` are sent to the
/// callback `f`. It is also guaranteed that if an arg requires a value (i.e.
/// `arg.value.is_some()`), the value will be `Some` in the callbacks arguments. If the callback
/// returns `Err`, this function will end parsing and return that `Err`.
///
/// See the [module level](index.html) example for a usage example.
pub fn set_arguments<I, R, F>(args: I, arg_list: &[Argument], mut f: F) -> Result<()>
where I: Iterator<Item = R>,
R: AsRef<str>,
F: FnMut(&str, Option<&str>) -> Result<()>
{
parse_arguments(args, |name, value| {
let mut matches = None;
for arg in arg_list {
if let Some(short) = arg.short {
if name.len() == 1 && name.starts_with(short) {
if value.is_some() != arg.value.is_some() {
return Err(Error::ExpectedValue(short.to_string()));
}
matches = Some(arg.long);
}
}
if matches.is_none() && arg.long == name {
if value.is_some() != arg.value.is_some() {
return Err(Error::ExpectedValue(arg.long.to_owned()));
}
matches = Some(arg.long);
}
}
match matches {
Some(long) => f(long, value),
None => Err(Error::UnknownArgument(name.to_owned())),
}
})
}
/// Prints command line usage information to stdout.
///
/// Usage information is printed according to the help fields in `args` with a leading usage line.
/// The usage line is of the format "`program_name` [ARGUMENTS] `required_arg`".
pub fn print_help(program_name: &str, required_arg: &str, args: &[Argument]) {
println!("Usage: {} {}{}\n",
program_name,
if args.is_empty() { "" } else { "[ARGUMENTS] " },
required_arg);
if args.is_empty() {
return;
}
println!("Argument{}:", if args.len() > 1 { "s" } else { "" });
for arg in args {
match arg.short {
Some(ref s) => print!(" -{}, ", s),
None => print!(" "),
}
if arg.long.is_empty() {
print!(" ");
} else {
print!("--");
}
print!("{:<12}", arg.long);
if let Some(v) = arg.value {
if arg.long.is_empty() {
print!(" ");
} else {
print!("=");
}
print!("{:<10}", v);
} else {
print!("{:<11}", "");
}
println!("{}", arg.help);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_help() {
let arguments = [Argument::short_flag('h', "help", "Print help message.")];
let match_res = set_arguments(["-h"].iter(), &arguments[..], |name, _| {
match name {
"help" => return Err(Error::PrintHelp),
_ => unreachable!(),
};
});
match match_res {
Err(Error::PrintHelp) => {}
_ => unreachable!(),
}
}
#[test]
fn mixed_args() {
let arguments =
[Argument::positional("FILES", "files to operate on"),
Argument::short_value('p', "program", "PROGRAM", "Program to apply to each file"),
Argument::short_value('c', "cpus", "N", "Number of CPUs to use. (default: 1)"),
Argument::flag("unmount", "Unmount the root"),
Argument::short_flag('h', "help", "Print help message.")];
let mut unmount = false;
let match_res = set_arguments(["--cpus", "3", "--program", "hello", "--unmount", "file"]
.iter(),
&arguments[..],
|name, value| {
match name {
"" => assert_eq!(value.unwrap(), "file"),
"program" => assert_eq!(value.unwrap(), "hello"),
"cpus" => {
let c: u32 = value
.unwrap()
.parse()
.map_err(|_| {
Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: "this value for `cpus` needs to be integer",
}
})?;
assert_eq!(c, 3);
}
"unmount" => unmount = true,
"help" => return Err(Error::PrintHelp),
_ => unreachable!(),
};
Ok(())
});
assert!(match_res.is_ok());
assert!(unmount);
}
#[test]
fn name_value_pair() {
let arguments =
[Argument::short_value('c', "cpus", "N", "Number of CPUs to use. (default: 1)")];
let match_res = set_arguments(["-c", "5", "--cpus", "5", "-c5", "--cpus=5"].iter(),
&arguments[..],
|name, value| {
assert_eq!(name, "cpus");
assert_eq!(value, Some("5"));
Ok(())
});
assert!(match_res.is_ok());
}
}

View file

@ -4,7 +4,6 @@
//! Runs a virtual machine under KVM
extern crate clap;
extern crate libc;
extern crate io_jail;
extern crate kvm;
@ -12,13 +11,15 @@ extern crate kvm;
extern crate x86_64;
extern crate kernel_loader;
extern crate byteorder;
#[macro_use] extern crate sys_util;
#[macro_use]
extern crate sys_util;
extern crate net_sys;
extern crate net_util;
extern crate vhost;
extern crate virtio_sys;
extern crate data_model;
pub mod argument;
pub mod hw;
pub mod kernel_cmdline;
pub mod vm_control;
@ -37,18 +38,18 @@ use std::sync::{Arc, Mutex, Barrier};
use std::thread::{spawn, sleep, JoinHandle};
use std::time::Duration;
use clap::{Arg, App, SubCommand};
use io_jail::Minijail;
use kvm::*;
use sys_util::{GuestAddress, GuestMemory, EventFd, TempDir, Terminal, Poller, Pollable, Scm,
register_signal_handler, Killable, SignalFd, getpid, kill_process_group, reap_child,
syslog};
use argument::{Argument, set_arguments, print_help};
use device_manager::*;
use vm_control::{VmRequest, VmResponse};
enum Error {
OpenKernel(PathBuf, std::io::Error),
Socket(std::io::Error),
Disk(std::io::Error),
BlockDeviceNew(sys_util::Error),
@ -56,8 +57,6 @@ enum Error {
VhostNetDeviceNew(hw::virtio::vhost::Error),
NetDeviceNew(hw::virtio::NetError),
NetDeviceRootSetup(sys_util::Error),
MacAddressNeedsNetConfig,
NetMissingConfig,
DeviceJail(io_jail::Error),
DevicePivotRoot(io_jail::Error),
RegisterBlock(device_manager::Error),
@ -100,6 +99,7 @@ impl std::convert::From<sys_util::Error> for Error {
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Error::OpenKernel(ref p, ref e) => write!(f, "failed to open kernel image {:?}: {}", p, e),
&Error::Socket(ref e) => write!(f, "failed to create socket: {}", e),
&Error::Disk(ref e) => write!(f, "failed to load disk image: {}", e),
&Error::BlockDeviceNew(ref e) => write!(f, "failed to create block device: {:?}", e),
@ -112,8 +112,6 @@ impl fmt::Display for Error {
&Error::NetDeviceRootSetup(ref e) => {
write!(f, "failed to create root directory for a net device: {:?}", e)
}
&Error::MacAddressNeedsNetConfig => write!(f, "MAC address can only be specified when host IP and netmask are provided"),
&Error::NetMissingConfig => write!(f, "networking requires both host IP and netmask specified"),
&Error::DeviceJail(ref e) => write!(f, "failed to jail device: {:?}", e),
&Error::DevicePivotRoot(ref e) => write!(f, "failed to pivot root device: {:?}", e),
&Error::RegisterNet(ref e) => write!(f, "error registering net device: {:?}", e),
@ -157,22 +155,23 @@ impl Drop for UnlinkUnixDatagram {
}
}
struct DiskOption<'a> {
path: &'a str,
struct DiskOption {
path: PathBuf,
writable: bool,
}
struct Config<'a> {
disks: Vec<DiskOption<'a>>,
#[derive(Default)]
struct Config {
disks: Vec<DiskOption>,
vcpu_count: Option<u32>,
memory: Option<usize>,
kernel_image: File,
params: Option<String>,
kernel_path: PathBuf,
params: String,
host_ip: Option<net::Ipv4Addr>,
netmask: Option<net::Ipv4Addr>,
mac_address: Option<String>,
vhost_net: bool,
socket_path: Option<String>,
socket_path: Option<PathBuf>,
multiprocess: bool,
warn_unknown_ports: bool,
}
@ -231,24 +230,13 @@ fn wait_all_children() -> bool {
}
fn run_config(cfg: Config) -> Result<()> {
if cfg.mac_address.is_some() &&
(cfg.netmask.is_none() || cfg.host_ip.is_none()) {
return Err(Error::MacAddressNeedsNetConfig);
}
if cfg.netmask.is_some() != cfg.host_ip.is_some() {
return Err(Error::NetMissingConfig);
}
let kernel_image = File::open(cfg.kernel_path.as_path())
.map_err(|e| Error::OpenKernel(cfg.kernel_path.clone(), e))?;
let mut control_sockets = Vec::new();
if let Some(ref path) = cfg.socket_path {
let path = Path::new(path);
let control_socket = if path.is_dir() {
UnixDatagram::bind(path.join(format!("crosvm-{}.sock", getpid())))
} else {
UnixDatagram::bind(path)
}
.map_err(|e| Error::Socket(e))?;
let control_socket = UnixDatagram::bind(path).map_err(|e| Error::Socket(e))?;
control_sockets.push(UnlinkUnixDatagram(control_socket));
}
@ -334,14 +322,14 @@ fn run_config(cfg: Config) -> Result<()> {
}
}
if let Some(params) = cfg.params {
if !cfg.params.is_empty() {
cmdline
.insert_str(params)
.insert_str(cfg.params)
.map_err(|e| Error::Cmdline(e))?;
}
run_kvm(device_manager.vm_requests,
cfg.kernel_image,
kernel_image,
&CString::new(cmdline).unwrap(),
cfg.vcpu_count.unwrap_or(1),
guest_mem,
@ -668,163 +656,279 @@ fn run_control(mut vm: Vm,
Ok(())
}
fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::Result<()> {
match name {
"" => {
if !cfg.kernel_path.as_os_str().is_empty() {
return Err(argument::Error::TooManyArguments("expected exactly one kernel path"
.to_owned()));
} else {
let kernel_path = PathBuf::from(value.unwrap());
if !kernel_path.exists() {
return Err(argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: "this kernel path does not exist",
});
}
cfg.kernel_path = kernel_path;
}
}
"params" => {
if cfg.params.ends_with(|c| !char::is_whitespace(c)) {
cfg.params.push(' ');
}
cfg.params.push_str(&value.unwrap());
}
"cpus" => {
if cfg.vcpu_count.is_some() {
return Err(argument::Error::TooManyArguments("`cpus` already given".to_owned()));
}
cfg.vcpu_count =
Some(value
.unwrap()
.parse()
.map_err(|_| {
argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: "this value for `cpus` needs to be integer",
}
})?)
}
"mem" => {
if cfg.memory.is_some() {
return Err(argument::Error::TooManyArguments("`mem` already given".to_owned()));
}
cfg.memory =
Some(value
.unwrap()
.parse()
.map_err(|_| {
argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: "this value for `mem` needs to be integer",
}
})?)
}
"root" | "disk" | "rwdisk" => {
let disk_path = PathBuf::from(value.unwrap());
if !disk_path.exists() {
return Err(argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: "this disk path does not exist",
});
}
if name == "root" {
if cfg.disks.len() >= 26 {
return Err(argument::Error::TooManyArguments("ran out of letters for to assign to root disk".to_owned()));
}
let white = if cfg.params.ends_with(|c| !char::is_whitespace(c)) {
" "
} else {
""
};
cfg.params
.push_str(&format!("{}root=/dev/vd{} ro",
white,
char::from('a' as u8 + cfg.disks.len() as u8)));
}
cfg.disks
.push(DiskOption {
path: disk_path,
writable: name.starts_with("rw"),
});
}
"host_ip" => {
if cfg.host_ip.is_some() {
return Err(argument::Error::TooManyArguments("`host_ip` already given".to_owned()));
}
cfg.host_ip =
Some(value
.unwrap()
.parse()
.map_err(|_| {
argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: "`host_ip` needs to be in the form \"x.x.x.x\"",
}
})?)
}
"netmask" => {
if cfg.netmask.is_some() {
return Err(argument::Error::TooManyArguments("`netmask` already given".to_owned()));
}
cfg.netmask =
Some(value
.unwrap()
.parse()
.map_err(|_| {
argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: "`netmask` needs to be in the form \"x.x.x.x\"",
}
})?)
}
"mac" => {
if cfg.mac_address.is_some() {
return Err(argument::Error::TooManyArguments("`mac` already given".to_owned()));
}
cfg.mac_address = Some(value.unwrap().to_owned());
}
"socket" => {
if cfg.socket_path.is_some() {
return Err(argument::Error::TooManyArguments("`socket` already given".to_owned()));
}
let mut socket_path = PathBuf::from(value.unwrap());
if socket_path.is_dir() {
socket_path.push(format!("crosvm-{}.sock", getpid()));
}
if socket_path.exists() {
return Err(argument::Error::InvalidValue {
value: socket_path.to_string_lossy().into_owned(),
expected: "this socket path already exists",
});
}
cfg.socket_path = Some(socket_path);
}
"multiprocess" => {
cfg.multiprocess = true;
}
"help" => return Err(argument::Error::PrintHelp),
_ => unreachable!(),
}
Ok(())
}
fn run_vm(args: std::env::Args) {
let arguments =
&[Argument::positional("KERNEL", "bzImage of kernel to run"),
Argument::short_value('p',
"params",
"PARAMS",
"Extra kernel command line arguments. Can be given more than once."),
Argument::short_value('c', "cpus", "N", "Number of VCPUs. (default: 1)"),
Argument::short_value('m',
"mem",
"N",
"Amount of guest memory in MiB. (default: 256)"),
Argument::short_value('r',
"root",
"PATH",
"Path to a root disk image. Like `--disk` but adds appropriate kernel command line option."),
Argument::short_value('d', "disk", "PATH", "Path to a disk image."),
Argument::value("rwdisk", "PATH", "Path to a writable disk image."),
Argument::value("host_ip",
"IP",
"IP address to assign to host tap interface."),
Argument::value("netmask", "NETMASK", "Netmask for VM subnet."),
Argument::value("mac", "MAC", "MAC address for VM."),
Argument::short_value('s',
"socket",
"PATH",
"Path to put the control socket. If PATH is a directory, a name will be generated."),
Argument::short_flag('u', "multiprocess", "Run each device in a child process."),
Argument::short_flag('h', "help", "Print help message.")];
let mut cfg = Config::default();
let match_res = set_arguments(args, &arguments[..], |name, value| set_argument(&mut cfg, name, value)).and_then(|_| {
if cfg.kernel_path.as_os_str().is_empty() {
return Err(argument::Error::ExpectedArgument("`KERNEL`".to_owned()));
}
if cfg.host_ip.is_some() || cfg.netmask.is_some() || cfg.mac_address.is_some() {
if cfg.host_ip.is_none() {
return Err(argument::Error::ExpectedArgument("`host_ip` missing from network config".to_owned()));
}
if cfg.netmask.is_none() {
return Err(argument::Error::ExpectedArgument("`netmask` missing from network config".to_owned()));
}
if cfg.mac_address.is_none() {
return Err(argument::Error::ExpectedArgument("`mac` missing from network config".to_owned()));
}
}
Ok(())
});
match match_res {
Ok(_) => {
match run_config(cfg) {
Ok(_) => println!("crosvm has exited normally"),
Err(e) => println!("{}", e),
}
}
Err(argument::Error::PrintHelp) => print_help("crosvm run", "KERNEL", &arguments[..]),
Err(e) => println!("{}", e),
}
}
fn stop_vms(args: std::env::Args) {
let mut scm = Scm::new(1);
if args.len() == 0 {
print_help("crosvm stop", "VM_SOCKET...", &[]);
println!("Stops the crosvm instance listening on each `VM_SOCKET` given.");
}
for socket_path in args {
match UnixDatagram::unbound().and_then(|s| {
s.connect(&socket_path)?;
Ok(s)
}) {
Ok(s) => {
if let Err(e) = VmRequest::Exit.send(&mut scm, &s) {
error!("failed to send stop request to socket at '{}': {:?}",
socket_path,
e);
}
}
Err(e) => error!("failed to connect to socket at '{}': {}", socket_path, e),
}
}
}
fn print_usage() {
print_help("crosvm", "[stop|run]", &[]);
println!("Commands:");
println!(" stop - Stops crosvm instances via their control sockets.");
println!(" run - Start a new crosvm instance.");
}
fn main() {
if let Err(e) = syslog::init() {
println!("failed to initiailize syslog: {:?}", e);
return;
}
let matches = App::new("crosvm")
.version("0.1.0")
.author("The Chromium OS Authors")
.about("Runs a virtual machine under KVM")
.subcommand(SubCommand::with_name("stop").arg(Arg::with_name("socket")
.required(true)
.multiple(true)
.index(1)
.value_name("PATH")
.help("path of the control sockets")))
.subcommand(SubCommand::with_name("run")
.arg(Arg::with_name("disk")
.short("d")
.long("disk")
.value_name("FILE")
.help("disk image")
.multiple(true)
.number_of_values(1)
.takes_value(true))
.arg(Arg::with_name("writable")
.short("w")
.long("writable")
.value_name("FILE")
.help("make disk image writable")
.multiple(true)
.number_of_values(1)
.takes_value(true))
.arg(Arg::with_name("cpus")
.short("c")
.long("cpus")
.value_name("N")
.help("number of VCPUs")
.takes_value(true))
.arg(Arg::with_name("memory")
.short("m")
.long("mem")
.value_name("N")
.help("amount of guest memory in MiB")
.takes_value(true))
.arg(Arg::with_name("params")
.short("p")
.long("params")
.value_name("params")
.help("extra kernel command line arguments")
.takes_value(true))
.arg(Arg::with_name("multiprocess")
.short("u")
.long("multiprocess")
.help("run the devices in a child process"))
.arg(Arg::with_name("host_ip")
.long("host_ip")
.value_name("HOST_IP")
.help("IP address to assign to host tap interface"))
.arg(Arg::with_name("netmask")
.long("netmask")
.value_name("NETMASK")
.help("netmask for VM subnet"))
.arg(Arg::with_name("mac")
.long("mac")
.value_name("MAC")
.help("mac address for VM"))
.arg(Arg::with_name("vhost_net")
.long("vhost_net")
.help("use vhost_net for networking"))
.arg(Arg::with_name("socket")
.short("s")
.long("socket")
.value_name("PATH")
.help("Path to put the control socket. If PATH is a directory, a name will be generated.")
.takes_value(true))
.arg(Arg::with_name("warn-unknown-ports")
.long("warn-unknown-ports")
.help("warn when an the VM uses an unknown port"))
.arg(Arg::with_name("KERNEL")
.required(true)
.index(1)
.help("bzImage of kernel to run")))
.get_matches();
match matches.subcommand() {
("stop", Some(matches)) => {
let mut scm = Scm::new(1);
for socket_path in matches.values_of("socket").unwrap() {
match UnixDatagram::unbound().and_then(|s| {
s.connect(socket_path)?;
Ok(s)
}) {
Ok(s) => {
if let Err(e) = VmRequest::Exit.send(&mut scm, &s) {
println!("failed to send stop request to socket at '{}': {:?}",
socket_path,
e);
}
}
Err(e) => println!("failed to connect to socket at '{}': {}", socket_path, e),
}
}
}
("run", Some(matches)) => {
let mut disks = Vec::new();
matches.values_of("disk").map(|paths| {
disks.extend(paths.map(|ref p| {
DiskOption {
path: p,
writable: false,
}
}))
});
if let Some(write_paths) = matches.values_of("writable") {
for path in write_paths {
disks.iter_mut().find(|ref mut d| d.path == path).map(
|ref mut d| d.writable = true,
);
}
}
let config = Config {
disks: disks,
vcpu_count: matches.value_of("cpus").and_then(|v| v.parse().ok()),
memory: matches.value_of("memory").and_then(|v| v.parse().ok()),
kernel_image: File::open(matches.value_of("KERNEL").unwrap())
.expect("Expected kernel image path to be valid"),
params: matches.value_of("params").map(|s| s.to_string()),
multiprocess: matches.is_present("multiprocess"),
host_ip: matches.value_of("host_ip").and_then(|v| v.parse().ok()),
netmask: matches.value_of("netmask").and_then(|v| v.parse().ok()),
mac_address: matches.value_of("mac").map(|s| s.to_string()),
vhost_net: matches.is_present("vhost_net"),
socket_path: matches.value_of("socket").map(|s| s.to_string()),
warn_unknown_ports: matches.is_present("warn-unknown-ports"),
};
match run_config(config) {
Ok(_) => println!("crosvm has exited normally"),
Err(e) => println!("{}", e),
}
// Reap exit status from any child device processes. At this point,
// all devices should have been dropped in the main process and
// told to shutdown. Try over a period of 100ms, since it may
// take some time for the processes to shut down.
if !wait_all_children() {
// We gave them a chance, and it's too late.
warn!("not all child processes have exited; sending SIGKILL");
if let Err(e) = kill_process_group() {
// We're now at the mercy of the OS to clean up after us.
warn!("unable to kill all child processes: {:?}", e);
}
}
// WARNING: Any code added after this point is not guaranteed to run
// since we may forcibly kill this process (and its children) above.
}
_ => {}
let mut args = std::env::args();
if args.next().is_none() {
error!("expected executable name");
return;
}
match args.next().as_ref().map(|a| a.as_ref()) {
None => print_usage(),
Some("stop") => {
stop_vms(args);
}
Some("run") => {
run_vm(args);
}
Some(c) => {
println!("invalid subcommand: {:?}", c);
print_usage();
}
}
// Reap exit status from any child device processes. At this point, all devices should have been
// dropped in the main process and told to shutdown. Try over a period of 100ms, since it may
// take some time for the processes to shut down.
if !wait_all_children() {
// We gave them a chance, and it's too late.
warn!("not all child processes have exited; sending SIGKILL");
if let Err(e) = kill_process_group() {
// We're now at the mercy of the OS to clean up after us.
warn!("unable to kill all child processes: {:?}", e);
}
}
// WARNING: Any code added after this point is not guaranteed to run
// since we may forcibly kill this process (and its children) above.
}