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"
|
||||
version = "0.0.6"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"getopts",
|
||||
"libc",
|
||||
"uucore",
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<String>,
|
||||
}
|
||||
|
||||
{}",
|
||||
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<String> = 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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue