Merge pull request #2046 from ricardoaiglesias/timeout-clap

timeout: Moved argument parsing to clap
This commit is contained in:
Sylvestre Ledru 2021-04-07 23:19:36 +02:00 committed by GitHub
commit 717b875b5d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 137 additions and 78 deletions

1
Cargo.lock generated
View file

@ -2416,6 +2416,7 @@ dependencies = [
name = "uu_timeout" name = "uu_timeout"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"clap",
"getopts", "getopts",
"libc", "libc",
"uucore", "uucore",

View file

@ -80,7 +80,8 @@ fn print_version() {
fn print_usage(opts: &Options) { fn print_usage(opts: &Options) {
let brief = "Run COMMAND, with modified buffering operations for its standard streams\n \ let brief = "Run COMMAND, with modified buffering operations for its standard streams\n \
Mandatory arguments to long options are mandatory for short options too."; 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 \ This option is invalid with standard input.\n\n \
If MODE is '0' the corresponding stream will be unbuffered.\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 \ Otherwise MODE is a number which may be followed by one of the following:\n\n \

View file

@ -15,11 +15,13 @@ edition = "2018"
path = "src/timeout.rs" path = "src/timeout.rs"
[dependencies] [dependencies]
clap = "2.33"
getopts = "0.2.18" getopts = "0.2.18"
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["parse_time", "process", "signals"] } 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" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]] [[bin]]
name = "timeout" name = "timeout"
path = "src/main.rs" path = "src/main.rs"

View file

@ -10,99 +10,154 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
extern crate clap;
use clap::{App, AppSettings, Arg};
use std::io::ErrorKind; use std::io::ErrorKind;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::time::Duration; use std::time::Duration;
use uucore::process::ChildExt; use uucore::process::ChildExt;
use uucore::signals::signal_by_name_or_value;
static NAME: &str = "timeout";
static VERSION: &str = env!("CARGO_PKG_VERSION"); 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; const ERR_EXIT_STATUS: i32 = 125;
pub fn uumain(args: impl uucore::Args) -> i32 { pub mod options {
let args = args.collect_str(); 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";
let mut opts = getopts::Options::new(); pub static COMMAND: &str = "command";
opts.optflag( pub static ARGS: &str = "args";
"",
"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!(
"{} {}
Usage:
{} [OPTION] DURATION COMMAND [ARG]...
{}",
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;
} }
},
struct Config {
foreground: bool,
kill_after: Duration,
signal: usize,
duration: Duration,
preserve_status: bool,
command: String,
command_args: Vec<String>,
}
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), None => Duration::new(0, 0),
}; };
let signal = match matches.opt_str("signal") {
Some(sigstr) => match uucore::signals::signal_by_name_or_value(&sigstr) { let duration: Duration =
Some(sig) => sig, uucore::parse_time::from_str(options.value_of(options::DURATION).unwrap()).unwrap();
None => {
show_error!("invalid signal '{}'", sigstr); let preserve_status: bool = options.is_present(options::PRESERVE_STATUS);
return ERR_EXIT_STATUS; let foreground = options.is_present(options::FOREGROUND);
}
}, let command: String = options.value_of(options::COMMAND).unwrap().to_string();
None => uucore::signals::signal_by_name_or_value("TERM").unwrap(),
let command_args: Vec<String> = match options.values_of(options::ARGS) {
Some(values) => values.map(|x| x.to_owned()).collect(),
None => vec![],
}; };
let duration = match uucore::parse_time::from_str(&matches.free[0]) {
Ok(time) => time, Config {
Err(f) => {
show_error!("{}", f);
return ERR_EXIT_STATUS;
}
};
return timeout(
&matches.free[1],
&matches.free[2..],
duration,
signal,
kill_after,
foreground, foreground,
status, kill_after,
); signal,
duration,
preserve_status,
command,
command_args,
}
}
} }
0 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( fn timeout(
cmdname: &str, cmdname: &str,
args: &[String], args: &[String],
@ -126,10 +181,10 @@ fn timeout(
Err(err) => { Err(err) => {
show_error!("failed to execute process: {}", err); show_error!("failed to execute process: {}", err);
if err.kind() == ErrorKind::NotFound { if err.kind() == ErrorKind::NotFound {
// XXX: not sure which to use // FIXME: not sure which to use
return 127; return 127;
} else { } else {
// XXX: this may not be 100% correct... // FIXME: this may not be 100% correct...
return 126; return 126;
} }
} }