diff --git a/Cargo.lock b/Cargo.lock index 41a384ea4..9c73674c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2416,6 +2416,7 @@ dependencies = [ name = "uu_timeout" version = "0.0.6" dependencies = [ + "clap", "getopts", "libc", "uucore", diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index cfd3b74cf..c35e996f2 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -45,7 +45,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .short("b") .help( "count using bytes rather than columns (meaning control characters \ - such as newline are not treated specially)", + such as newline are not treated specially)", ) .takes_value(false), ) diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 67ed9a838..a61ba967b 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -80,7 +80,8 @@ fn print_version() { fn print_usage(opts: &Options) { let brief = "Run COMMAND, with modified buffering operations for its standard streams\n \ Mandatory arguments to long options are mandatory for short options too."; - let explanation = "If MODE is 'L' the corresponding stream will be line buffered.\n \ + let explanation = + "If MODE is 'L' the corresponding stream will be line buffered.\n \ This option is invalid with standard input.\n\n \ If MODE is '0' the corresponding stream will be unbuffered.\n\n \ Otherwise MODE is a number which may be followed by one of the following:\n\n \ diff --git a/src/uu/timeout/Cargo.toml b/src/uu/timeout/Cargo.toml index 51ac0bc0e..206a98c08 100644 --- a/src/uu/timeout/Cargo.toml +++ b/src/uu/timeout/Cargo.toml @@ -15,11 +15,13 @@ edition = "2018" path = "src/timeout.rs" [dependencies] +clap = "2.33" getopts = "0.2.18" libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["parse_time", "process", "signals"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } + [[bin]] name = "timeout" path = "src/main.rs" diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index 0dd7c2016..23e2ec842 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -10,99 +10,154 @@ #[macro_use] extern crate uucore; +extern crate clap; + +use clap::{App, AppSettings, Arg}; use std::io::ErrorKind; use std::process::{Command, Stdio}; use std::time::Duration; use uucore::process::ChildExt; +use uucore::signals::signal_by_name_or_value; -static NAME: &str = "timeout"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Start COMMAND, and kill it if still running after DURATION."; + +fn get_usage() -> String { + format!("{0} [OPTION]... [FILE]...", executable!()) +} const ERR_EXIT_STATUS: i32 = 125; -pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); +pub mod options { + pub static FOREGROUND: &str = "foreground"; + pub static KILL_AFTER: &str = "kill-after"; + pub static SIGNAL: &str = "signal"; + pub static VERSION: &str = "version"; + pub static PRESERVE_STATUS: &str = "preserve-status"; - let program = args[0].clone(); + // Positional args. + pub static DURATION: &str = "duration"; + pub static COMMAND: &str = "command"; + pub static ARGS: &str = "args"; +} - let mut opts = getopts::Options::new(); - opts.optflag( - "", - "preserve-status", - "exit with the same status as COMMAND, even when the command times out", - ); - opts.optflag("", "foreground", "when not running timeout directly from a shell prompt, allow COMMAND to read from the TTY and get TTY signals; in this mode, children of COMMAND will not be timed out"); - opts.optopt("k", "kill-after", "also send a KILL signal if COMMAND is still running this long after the initial signal was sent", "DURATION"); - opts.optflag("s", "signal", "specify the signal to be sent on timeout; SIGNAL may be a name like 'HUP' or a number; see 'kill -l' for a list of signals"); - opts.optflag("h", "help", "display this help and exit"); - opts.optflag("V", "version", "output version information and exit"); - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(f) => crash!(ERR_EXIT_STATUS, "{}", f), - }; - if matches.opt_present("help") { - print!( - "{} {} +struct Config { + foreground: bool, + kill_after: Duration, + signal: usize, + duration: Duration, + preserve_status: bool, -Usage: - {} [OPTION] DURATION COMMAND [ARG]... + command: String, + command_args: Vec, +} -{}", - NAME, - VERSION, - program, - &opts.usage("Start COMMAND, and kill it if still running after DURATION.") - ); - } else if matches.opt_present("version") { - println!("{} {}", NAME, VERSION); - } else if matches.free.len() < 2 { - show_error!("missing an argument"); - show_error!("for help, try '{0} --help'", program); - return ERR_EXIT_STATUS; - } else { - let status = matches.opt_present("preserve-status"); - let foreground = matches.opt_present("foreground"); - let kill_after = match matches.opt_str("kill-after") { - Some(tstr) => match uucore::parse_time::from_str(&tstr) { - Ok(time) => time, - Err(f) => { - show_error!("{}", f); - return ERR_EXIT_STATUS; +impl Config { + fn from(options: clap::ArgMatches) -> Config { + let signal = match options.value_of(options::SIGNAL) { + Some(signal_) => { + let signal_result = signal_by_name_or_value(&signal_); + match signal_result { + None => { + unreachable!("invalid signal '{}'", signal_); + } + Some(signal_value) => signal_value, } - }, + } + _ => uucore::signals::signal_by_name_or_value("TERM").unwrap(), + }; + + let kill_after: Duration = match options.value_of(options::KILL_AFTER) { + Some(time) => uucore::parse_time::from_str(&time).unwrap(), None => Duration::new(0, 0), }; - let signal = match matches.opt_str("signal") { - Some(sigstr) => match uucore::signals::signal_by_name_or_value(&sigstr) { - Some(sig) => sig, - None => { - show_error!("invalid signal '{}'", sigstr); - return ERR_EXIT_STATUS; - } - }, - None => uucore::signals::signal_by_name_or_value("TERM").unwrap(), - }; - let duration = match uucore::parse_time::from_str(&matches.free[0]) { - Ok(time) => time, - Err(f) => { - show_error!("{}", f); - return ERR_EXIT_STATUS; - } - }; - return timeout( - &matches.free[1], - &matches.free[2..], - duration, - signal, - kill_after, - foreground, - status, - ); - } - 0 + let duration: Duration = + uucore::parse_time::from_str(options.value_of(options::DURATION).unwrap()).unwrap(); + + let preserve_status: bool = options.is_present(options::PRESERVE_STATUS); + let foreground = options.is_present(options::FOREGROUND); + + let command: String = options.value_of(options::COMMAND).unwrap().to_string(); + + let command_args: Vec = match options.values_of(options::ARGS) { + Some(values) => values.map(|x| x.to_owned()).collect(), + None => vec![], + }; + + Config { + foreground, + kill_after, + signal, + duration, + preserve_status, + command, + command_args, + } + } } +pub fn uumain(args: impl uucore::Args) -> i32 { + let args = args.collect_str(); + let usage = get_usage(); + + let app = App::new("timeout") + .version(VERSION) + .usage(&usage[..]) + .about(ABOUT) + .arg( + Arg::with_name(options::FOREGROUND) + .long(options::FOREGROUND) + .help("when not running timeout directly from a shell prompt, allow COMMAND to read from the TTY and get TTY signals; in this mode, children of COMMAND will not be timed out") + ) + .arg( + Arg::with_name(options::KILL_AFTER) + .short("k") + .takes_value(true)) + .arg( + Arg::with_name(options::PRESERVE_STATUS) + .long(options::PRESERVE_STATUS) + .help("exit with the same status as COMMAND, even when the command times out") + ) + .arg( + Arg::with_name(options::SIGNAL) + .short("s") + .long(options::SIGNAL) + .help("specify the signal to be sent on timeout; SIGNAL may be a name like 'HUP' or a number; see 'kill -l' for a list of signals") + .takes_value(true) + ) + .arg( + Arg::with_name(options::DURATION) + .index(1) + .required(true) + ) + .arg( + Arg::with_name(options::COMMAND) + .index(2) + .required(true) + ) + .arg( + Arg::with_name(options::ARGS).multiple(true) + ) + .setting(AppSettings::TrailingVarArg); + + let matches = app.get_matches_from(args); + + let config = Config::from(matches); + timeout( + &config.command, + &config.command_args, + config.duration, + config.signal, + config.kill_after, + config.foreground, + config.preserve_status, + ) +} + +/// TODO: Improve exit codes, and make them consistent with the GNU Coreutil +/// exit codes. + fn timeout( cmdname: &str, args: &[String], @@ -126,10 +181,10 @@ fn timeout( Err(err) => { show_error!("failed to execute process: {}", err); if err.kind() == ErrorKind::NotFound { - // XXX: not sure which to use + // FIXME: not sure which to use return 127; } else { - // XXX: this may not be 100% correct... + // FIXME: this may not be 100% correct... return 126; } }