aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Disseldorp <ddiss@suse.de>2021-05-29 01:10:11 +0200
committerHarald Hoyer <harald@hoyer.xyz>2021-11-24 11:14:54 +0100
commit94fc50262f5e6c28d92782dc231fbb6c61855954 (patch)
tree29cf282b46e5796832b6015c62c8f1a5b97174fa
parent3a0f423309c85dc42de4cf6b47ba482dc01d64cf (diff)
downloaddracut-94fc50262f5e6c28d92782dc231fbb6c61855954.tar.gz
feat(cpio): add rust argument parsing library from crosvm
Crosvm's rust argument library is very small and simple, while still providing helpful functionality. It will be consumed by dracut-cpio in a subsequent commit. The unmodified, BSD licensed argument.rs source is lifted as-is from https://chromium.googlesource.com/chromiumos/platform/crosvm (release-R92-13982.B b6ae6517aeef9ae1e3a39c55b52f9ac6de8edb31). The one-line crosvm.rs wrapper is needed to ensure that crosvm::argument imports continue to work. Signed-off-by: David Disseldorp <ddiss@suse.de>
-rw-r--r--src/dracut-cpio/third_party/crosvm/Cargo.toml8
-rw-r--r--src/dracut-cpio/third_party/crosvm/LICENSE27
-rw-r--r--src/dracut-cpio/third_party/crosvm/argument.rs549
-rw-r--r--src/dracut-cpio/third_party/crosvm/crosvm.rs2
4 files changed, 586 insertions, 0 deletions
diff --git a/src/dracut-cpio/third_party/crosvm/Cargo.toml b/src/dracut-cpio/third_party/crosvm/Cargo.toml
new file mode 100644
index 00000000..a56ff2db
--- /dev/null
+++ b/src/dracut-cpio/third_party/crosvm/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "crosvm"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+
+[lib]
+path = "crosvm.rs"
diff --git a/src/dracut-cpio/third_party/crosvm/LICENSE b/src/dracut-cpio/third_party/crosvm/LICENSE
new file mode 100644
index 00000000..8bafca30
--- /dev/null
+++ b/src/dracut-cpio/third_party/crosvm/LICENSE
@@ -0,0 +1,27 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/dracut-cpio/third_party/crosvm/argument.rs b/src/dracut-cpio/third_party/crosvm/argument.rs
new file mode 100644
index 00000000..64be1822
--- /dev/null
+++ b/src/dracut-cpio/third_party/crosvm/argument.rs
@@ -0,0 +1,549 @@
+// 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
+//!
+//! ```
+//! # use crosvm::argument::{Argument, Error, print_help, set_arguments};
+//! # let args: std::slice::Iter<String> = [].iter();
+//! 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 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: String::from("this value for `cpus` needs to be integer"),
+//! }
+//! })?;
+//! }
+//! "unmount" => println!("gonna unmount"),
+//! "help" => return Err(Error::PrintHelp),
+//! _ => unreachable!(),
+//! }
+//! 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::{self, Display};
+use std::result;
+
+/// An error with argument parsing.
+#[derive(Debug)]
+pub enum Error {
+ /// There was a syntax error with the argument.
+ Syntax(String),
+ /// The argument's name is unused.
+ UnknownArgument(String),
+ /// The argument was required.
+ ExpectedArgument(String),
+ /// The argument's given value is invalid.
+ InvalidValue { value: String, expected: String },
+ /// The argument was already given and none more are expected.
+ TooManyArguments(String),
+ /// The argument expects a value.
+ ExpectedValue(String),
+ /// The argument does not expect a value.
+ UnexpectedValue(String),
+ /// The help information was requested
+ PrintHelp,
+}
+
+impl Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::Error::*;
+
+ match self {
+ Syntax(s) => write!(f, "syntax error: {}", s),
+ UnknownArgument(s) => write!(f, "unknown argument: {}", s),
+ ExpectedArgument(s) => write!(f, "expected argument: {}", s),
+ InvalidValue { value, expected } => {
+ write!(f, "invalid value {:?}: {}", value, expected)
+ }
+ TooManyArguments(s) => write!(f, "too many arguments: {}", s),
+ ExpectedValue(s) => write!(f, "expected parameter value: {}", s),
+ UnexpectedValue(s) => write!(f, "unexpected parameter value: {}", s),
+ PrintHelp => write!(f, "help was requested"),
+ }
+ }
+}
+
+/// Result of a argument parsing.
+pub type Result<T> = result::Result<T, Error>;
+
+#[derive(Debug, PartialEq)]
+pub enum ArgumentValueMode {
+ /// Specifies that an argument requires a value and that an error should be generated if
+ /// no value is provided during parsing.
+ Required,
+
+ /// Specifies that an argument does not allow a value and that an error should be returned
+ /// if a value is provided during parsing.
+ Disallowed,
+
+ /// Specifies that an argument may have a value during parsing but is not required to.
+ Optional,
+}
+
+/// Information about an argument expected from the command line.
+///
+/// # Examples
+///
+/// To indicate a flag style argument:
+///
+/// ```
+/// # use crosvm::argument::Argument;
+/// Argument::short_flag('f', "flag", "enable awesome mode");
+/// ```
+///
+/// To indicate a parameter style argument that expects a value:
+///
+/// ```
+/// # use crosvm::argument::Argument;
+/// // "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:
+///
+/// ```
+/// # use crosvm::argument::Argument;
+/// Argument::flag("verbose", "this option is hard to type quickly");
+/// ```
+///
+/// To indicate a positional argument:
+///
+/// ```
+/// # use crosvm::argument::Argument;
+/// Argument::positional("VALUES", "these are positional arguments");
+/// ```
+pub struct Argument {
+ /// The name of the value to display in the usage information.
+ pub value: Option<&'static str>,
+ /// Specifies how values should be handled for this this argument.
+ pub value_mode: ArgumentValueMode,
+ /// 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),
+ value_mode: ArgumentValueMode::Required,
+ short: None,
+ long: "",
+ help,
+ }
+ }
+
+ pub fn value(long: &'static str, value: &'static str, help: &'static str) -> Argument {
+ Argument {
+ value: Some(value),
+ value_mode: ArgumentValueMode::Required,
+ short: None,
+ long,
+ help,
+ }
+ }
+
+ pub fn short_value(
+ short: char,
+ long: &'static str,
+ value: &'static str,
+ help: &'static str,
+ ) -> Argument {
+ Argument {
+ value: Some(value),
+ value_mode: ArgumentValueMode::Required,
+ short: Some(short),
+ long,
+ help,
+ }
+ }
+
+ pub fn flag(long: &'static str, help: &'static str) -> Argument {
+ Argument {
+ value: None,
+ value_mode: ArgumentValueMode::Disallowed,
+ short: None,
+ long,
+ help,
+ }
+ }
+
+ pub fn short_flag(short: char, long: &'static str, help: &'static str) -> Argument {
+ Argument {
+ value: None,
+ value_mode: ArgumentValueMode::Disallowed,
+ short: Some(short),
+ long,
+ help,
+ }
+ }
+
+ pub fn flag_or_value(long: &'static str, value: &'static str, help: &'static str) -> Argument {
+ Argument {
+ value: Some(value),
+ value_mode: ArgumentValueMode::Optional,
+ short: None,
+ long,
+ help,
+ }
+ }
+}
+
+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();
+ loop {
+ let mut arg_consumed = true;
+ s = match s {
+ State::Top => {
+ if arg == "--" {
+ State::Positional
+ } else if arg.starts_with("--") {
+ let param = arg.trim_start_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 {
+ State::Value {
+ name: param.to_owned(),
+ }
+ }
+ } 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 } => {
+ if arg.starts_with('-') {
+ arg_consumed = false;
+ f(&name, None)?;
+ } else if let Err(e) = f(&name, Some(&arg)) {
+ arg_consumed = false;
+ f(&name, None).map_err(|_| e)?;
+ }
+ State::Top
+ }
+ };
+
+ if arg_consumed {
+ break;
+ }
+ }
+ }
+
+ // If we ran out of arguments while parsing the last parameter, which may be either a
+ // value parameter or a flag, try to parse it as a flag. This will produce "missing value"
+ // error if the parameter is in fact a value parameter, which is the desired outcome.
+ match s {
+ State::Value { name } => f(&name, None),
+ _ => 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_none() && arg.value_mode == ArgumentValueMode::Required {
+ return Err(Error::ExpectedValue(arg.long.to_owned()));
+ }
+ if value.is_some() && arg.value_mode == ArgumentValueMode::Disallowed {
+ return Err(Error::UnexpectedValue(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(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: String::from("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());
+ let not_match_res = set_arguments(
+ ["-c", "5", "--cpus"].iter(),
+ &arguments[..],
+ |name, value| {
+ assert_eq!(name, "cpus");
+ assert_eq!(value, Some("5"));
+ Ok(())
+ },
+ );
+ assert!(not_match_res.is_err());
+ }
+
+ #[test]
+ fn flag_or_value() {
+ let run_case = |args| -> Option<String> {
+ let arguments = [
+ Argument::positional("FILES", "files to operate on"),
+ Argument::flag_or_value("gpu", "[2D|3D]", "Enable or configure gpu"),
+ Argument::flag("foo", "Enable foo."),
+ Argument::value("bar", "stuff", "Configure bar."),
+ ];
+
+ let mut gpu_value: Option<String> = None;
+ let match_res =
+ set_arguments(args, &arguments[..], |name: &str, value: Option<&str>| {
+ match name {
+ "" => assert_eq!(value.unwrap(), "file1"),
+ "foo" => assert!(value.is_none()),
+ "bar" => assert_eq!(value.unwrap(), "stuff"),
+ "gpu" => match value {
+ Some(v) => match v {
+ "2D" | "3D" => {
+ gpu_value = Some(v.to_string());
+ }
+ _ => {
+ return Err(Error::InvalidValue {
+ value: v.to_string(),
+ expected: String::from("2D or 3D"),
+ })
+ }
+ },
+ None => {
+ gpu_value = None;
+ }
+ },
+ _ => unreachable!(),
+ };
+ Ok(())
+ });
+
+ assert!(match_res.is_ok());
+ gpu_value
+ };
+
+ // Used as flag and followed by positional
+ assert_eq!(run_case(["--gpu", "file1"].iter()), None);
+ // Used as flag and followed by flag
+ assert_eq!(run_case(["--gpu", "--foo", "file1",].iter()), None);
+ // Used as flag and followed by value
+ assert_eq!(run_case(["--gpu", "--bar=stuff", "file1"].iter()), None);
+
+ // Used as value and followed by positional
+ assert_eq!(run_case(["--gpu=2D", "file1"].iter()).unwrap(), "2D");
+ // Used as value and followed by flag
+ assert_eq!(run_case(["--gpu=2D", "--foo"].iter()).unwrap(), "2D");
+ // Used as value and followed by value
+ assert_eq!(
+ run_case(["--gpu=2D", "--bar=stuff", "file1"].iter()).unwrap(),
+ "2D"
+ );
+ }
+}
diff --git a/src/dracut-cpio/third_party/crosvm/crosvm.rs b/src/dracut-cpio/third_party/crosvm/crosvm.rs
new file mode 100644
index 00000000..ddccbfee
--- /dev/null
+++ b/src/dracut-cpio/third_party/crosvm/crosvm.rs
@@ -0,0 +1,2 @@
+// ensure that existing crosvm::argument imports work without modification...
+pub mod argument;