summary refs log tree commit diff
diff options
context:
space:
mode:
authorZach Reizner <zachr@google.com>2017-08-26 18:05:48 -0700
committerchrome-bot <chrome-bot@chromium.org>2017-09-02 00:18:25 -0700
commitefe957849b5d645b115f677701ad779d42ef7574 (patch)
tree4911944e10bcfb6581d5191b76ab4d051c8c6d53
parente9321023861f8584df4237ecc4f3c8fbc01f90f3 (diff)
downloadcrosvm-efe957849b5d645b115f677701ad779d42ef7574.tar
crosvm-efe957849b5d645b115f677701ad779d42ef7574.tar.gz
crosvm-efe957849b5d645b115f677701ad779d42ef7574.tar.bz2
crosvm-efe957849b5d645b115f677701ad779d42ef7574.tar.lz
crosvm-efe957849b5d645b115f677701ad779d42ef7574.tar.xz
crosvm-efe957849b5d645b115f677701ad779d42ef7574.tar.zst
crosvm-efe957849b5d645b115f677701ad779d42ef7574.zip
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>
-rw-r--r--Cargo.toml4
-rw-r--r--src/argument.rs410
-rw-r--r--src/main.rs456
3 files changed, 690 insertions, 180 deletions
diff --git a/Cargo.toml b/Cargo.toml
index c0b64ab..57e4f06 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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" }
diff --git a/src/argument.rs b/src/argument.rs
new file mode 100644
index 0000000..03eda0e
--- /dev/null
+++ b/src/argument.rs
@@ -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());
+    }
+}
diff --git a/src/main.rs b/src/main.rs
index 608bdda..85d39a5 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -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 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),
+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;
             }
         }
-        ("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,
-                    );
+        "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)));
             }
-            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"),
-            };
+            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 run_config(config) {
+    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),
+    }
+}
 
-            // 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);
+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),
+        }
+    }
+}
 
-            // WARNING: Any code added after this point is not guaranteed to run
-            // since we may forcibly kill this process (and its children) above.
+
+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 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.
 }