mirror of
https://github.com/uutils/coreutils
synced 2024-11-17 02:08:09 +00:00
Merge pull request #2046 from ricardoaiglesias/timeout-clap
timeout: Moved argument parsing to clap
This commit is contained in:
commit
717b875b5d
5 changed files with 137 additions and 78 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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 \
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue