mirror of
https://github.com/uutils/coreutils
synced 2024-10-06 16:09:08 +00:00
Implement timeout (resolves #364)
This commit is contained in:
parent
439a8cadd1
commit
a38ee8a007
|
@ -187,6 +187,10 @@ path = "tee/tee.rs"
|
|||
name = "test"
|
||||
path = "test/test.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "timeout"
|
||||
path = "timeout/timeout.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "touch"
|
||||
path = "touch/touch.rs"
|
||||
|
|
1
Makefile
1
Makefile
|
@ -93,6 +93,7 @@ UNIX_PROGS := \
|
|||
logname \
|
||||
mkfifo \
|
||||
nohup \
|
||||
timeout \
|
||||
tty \
|
||||
uname \
|
||||
uptime \
|
||||
|
|
|
@ -165,7 +165,6 @@ To do
|
|||
- stty
|
||||
- tail (not all features implemented)
|
||||
- test (not all features implemented)
|
||||
- timeout
|
||||
- unexpand
|
||||
- uniq (in progress)
|
||||
- who
|
||||
|
|
|
@ -193,7 +193,6 @@ pub fn get_groups() -> Result<Vec<gid_t>, int> {
|
|||
}
|
||||
|
||||
pub fn group(possible_pw: Option<c_passwd>, nflag: bool) {
|
||||
|
||||
let groups = match possible_pw {
|
||||
Some(pw) => Ok(get_group_list(pw.pw_name, pw.pw_gid)),
|
||||
None => get_groups(),
|
||||
|
|
|
@ -139,6 +139,19 @@ pub static ALL_SIGNALS:[Signal<'static>, ..31] = [
|
|||
Signal{ name: "USR2", value:31 },
|
||||
];
|
||||
|
||||
pub fn signal_by_name_or_value(signal_name_or_value: &str) -> Option<uint> {
|
||||
if signal_name_or_value == "0" {
|
||||
return Some(0);
|
||||
}
|
||||
for signal in ALL_SIGNALS.iter() {
|
||||
let long_name = format!("SIG{}", signal.name);
|
||||
if signal.name == signal_name_or_value || (signal_name_or_value == signal.value.to_string().as_slice()) || (long_name.as_slice() == signal_name_or_value) {
|
||||
return Some(signal.value);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_signal(num: uint) -> bool {
|
||||
num < ALL_SIGNALS.len()
|
35
src/common/time.rs
Normal file
35
src/common/time.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* This file is part of the uutils coreutils package.
|
||||
*
|
||||
* (c) Arcterus <arcterus@mail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
pub fn from_str(string: &str) -> Result<f64, String> {
|
||||
let len = string.len();
|
||||
if len == 0 {
|
||||
return Err("empty string".to_string())
|
||||
}
|
||||
let slice = string.slice_to(len - 1);
|
||||
let (numstr, times) = match string.char_at(len - 1) {
|
||||
's' | 'S' => (slice, 1u),
|
||||
'm' | 'M' => (slice, 60u),
|
||||
'h' | 'H' => (slice, 60u * 60),
|
||||
'd' | 'D' => (slice, 60u * 60 * 24),
|
||||
val => {
|
||||
if !val.is_alphabetic() {
|
||||
(string, 1)
|
||||
} else if string == "inf" || string == "infinity" {
|
||||
("inf", 1)
|
||||
} else {
|
||||
return Err(format!("invalid time interval '{}'", string))
|
||||
}
|
||||
}
|
||||
};
|
||||
match ::std::from_str::from_str::<f64>(numstr) {
|
||||
Some(m) => Ok(m * times as f64),
|
||||
None => Err(format!("invalid time interval '{}'", string))
|
||||
}
|
||||
}
|
|
@ -71,6 +71,19 @@ macro_rules! crash_if_err(
|
|||
)
|
||||
)
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! return_if_err(
|
||||
($exitcode:expr, $exp:expr) => (
|
||||
match $exp {
|
||||
Ok(m) => m,
|
||||
Err(f) => {
|
||||
show_error!("{}", f);
|
||||
return $exitcode;
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! safe_write(
|
||||
($fd:expr, $($args:expr),+) => (
|
||||
|
|
|
@ -32,11 +32,12 @@ use getopts::{
|
|||
|
||||
use signals::ALL_SIGNALS;
|
||||
|
||||
mod signals;
|
||||
|
||||
#[path = "../common/util.rs"]
|
||||
mod util;
|
||||
|
||||
#[path = "../common/signals.rs"]
|
||||
mod signals;
|
||||
|
||||
static NAME: &'static str = "kill";
|
||||
static VERSION: &'static str = "0.0.1";
|
||||
|
||||
|
@ -183,22 +184,9 @@ fn help(progname: &str, usage: &str) {
|
|||
println!("{}", get_help_text(progname, usage));
|
||||
}
|
||||
|
||||
fn signal_by_name_or_value(signal_name_or_value: &str) -> Option<uint> {
|
||||
if signal_name_or_value == "0" {
|
||||
return Some(0);
|
||||
}
|
||||
for signal in ALL_SIGNALS.iter() {
|
||||
let long_name = format!("SIG{}", signal.name);
|
||||
if signal.name == signal_name_or_value || (signal_name_or_value == signal.value.to_string().as_slice()) || (long_name.as_slice() == signal_name_or_value) {
|
||||
return Some(signal.value);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn kill(signalname: &str, pids: std::vec::Vec<String>) -> int {
|
||||
let mut status = 0;
|
||||
let optional_signal_value = signal_by_name_or_value(signalname);
|
||||
let optional_signal_value = signals::signal_by_name_or_value(signalname);
|
||||
let signal_value = match optional_signal_value {
|
||||
Some(x) => x,
|
||||
None => crash!(EXIT_ERR, "unknown signal name {}", signalname)
|
||||
|
|
|
@ -21,6 +21,9 @@ use std::u64;
|
|||
#[path = "../common/util.rs"]
|
||||
mod util;
|
||||
|
||||
#[path = "../common/time.rs"]
|
||||
mod time;
|
||||
|
||||
static NAME: &'static str = "sleep";
|
||||
|
||||
pub fn uumain(args: Vec<String>) -> int {
|
||||
|
@ -66,43 +69,13 @@ specified by the sum of their values.", opts).as_slice());
|
|||
|
||||
fn sleep(args: Vec<String>) {
|
||||
let sleep_time = args.iter().fold(0.0, |result, arg| {
|
||||
let (arg, suffix_time) = match match_suffix(arg.as_slice()) {
|
||||
let num = match time::from_str(arg.as_slice()) {
|
||||
Ok(m) => m,
|
||||
Err(f) => {
|
||||
crash!(1, "{}", f.to_string())
|
||||
crash!(1, "{}", f)
|
||||
}
|
||||
};
|
||||
let num =
|
||||
if suffix_time == 0 {
|
||||
0.0
|
||||
} else {
|
||||
match from_str::<f64>(arg.as_slice()) {
|
||||
Some(m) => m,
|
||||
None => {
|
||||
crash!(1, "Invalid time interval '{}'", arg.to_string())
|
||||
}
|
||||
}
|
||||
};
|
||||
result + num * suffix_time as f64
|
||||
result + num
|
||||
});
|
||||
timer::sleep(if sleep_time == f64::INFINITY { u64::MAX } else { (sleep_time * 1000.0) as u64 });
|
||||
}
|
||||
|
||||
fn match_suffix(arg: &str) -> Result<(String, int), String> {
|
||||
let result = match (arg).char_at_reverse(0) {
|
||||
's' | 'S' => 1,
|
||||
'm' | 'M' => 60,
|
||||
'h' | 'H' => 60 * 60,
|
||||
'd' | 'D' => 60 * 60 * 24,
|
||||
val => {
|
||||
if !val.is_alphabetic() {
|
||||
return Ok((arg.to_string(), 1))
|
||||
} else if arg == "inf" || arg == "infinity" {
|
||||
return Ok(("inf".to_string(), 1))
|
||||
} else {
|
||||
return Err(format!("Invalid time interval '{}'", arg))
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(((arg).slice_to((arg).len() - 1).to_string(), result))
|
||||
}
|
||||
|
|
158
src/timeout/timeout.rs
Normal file
158
src/timeout/timeout.rs
Normal file
|
@ -0,0 +1,158 @@
|
|||
#![crate_name = "timeout"]
|
||||
|
||||
/*
|
||||
* This file is part of the uutils coreutils package.
|
||||
*
|
||||
* (c) Arcterus <arcterus@mail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#![feature(macro_rules)]
|
||||
|
||||
extern crate getopts;
|
||||
extern crate libc;
|
||||
|
||||
use std::io::{PathDoesntExist, FileNotFound};
|
||||
use std::io::process::{Command, ExitStatus, ExitSignal, InheritFd};
|
||||
|
||||
#[path = "../common/util.rs"]
|
||||
mod util;
|
||||
|
||||
#[path = "../common/time.rs"]
|
||||
mod time;
|
||||
|
||||
#[path = "../common/signals.rs"]
|
||||
mod signals;
|
||||
|
||||
extern {
|
||||
pub fn setpgid(_: libc::pid_t, _: libc::pid_t) -> libc::c_int;
|
||||
}
|
||||
|
||||
static NAME: &'static str = "timeout";
|
||||
static VERSION: &'static str = "1.0.0";
|
||||
|
||||
static ERR_EXIT_STATUS: int = 125;
|
||||
|
||||
pub fn uumain(args: Vec<String>) -> int {
|
||||
let program = args[0].clone();
|
||||
|
||||
let opts = [
|
||||
getopts::optflag("", "preserve-status", "exit with the same status as COMMAND, even when the command times out"),
|
||||
getopts::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"),
|
||||
getopts::optopt("k", "kill-after", "also send a KILL signal if COMMAND is still running this long after the initial signal was sent", "DURATION"),
|
||||
getopts::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"),
|
||||
getopts::optflag("h", "help", "display this help and exit"),
|
||||
getopts::optflag("V", "version", "output version information and exit")
|
||||
];
|
||||
let matches = match getopts::getopts(args.tail(), opts) {
|
||||
Ok(m) => m,
|
||||
Err(f) => {
|
||||
crash!(ERR_EXIT_STATUS, "{}", f)
|
||||
}
|
||||
};
|
||||
if matches.opt_present("help") {
|
||||
print!("{} v{}
|
||||
|
||||
Usage:
|
||||
{} [OPTION] DURATION COMMAND [ARG]...
|
||||
|
||||
{}", NAME, VERSION, program, getopts::usage("Start COMMAND, and kill it if still running after DURATION.", opts));
|
||||
} else if matches.opt_present("version") {
|
||||
println!("{} v{}", NAME, VERSION);
|
||||
} else if matches.free.len() < 2 {
|
||||
show_error!("missing an argument");
|
||||
show_error!("for help, try '{0:s} --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 time::from_str(tstr.as_slice()) {
|
||||
Ok(time) => time,
|
||||
Err(f) => {
|
||||
show_error!("{}", f);
|
||||
return ERR_EXIT_STATUS;
|
||||
}
|
||||
},
|
||||
None => 0f64
|
||||
};
|
||||
let signal = match matches.opt_str("signal") {
|
||||
Some(sigstr) => match signals::signal_by_name_or_value(sigstr.as_slice()) {
|
||||
Some(sig) => sig,
|
||||
None => {
|
||||
show_error!("invalid signal '{}'", sigstr);
|
||||
return ERR_EXIT_STATUS;
|
||||
}
|
||||
},
|
||||
None => signals::signal_by_name_or_value("TERM").unwrap()
|
||||
};
|
||||
let duration = match time::from_str(matches.free[0].as_slice()) {
|
||||
Ok(time) => time,
|
||||
Err(f) => {
|
||||
show_error!("{}", f);
|
||||
return ERR_EXIT_STATUS;
|
||||
}
|
||||
};
|
||||
return timeout(matches.free[1].as_slice(), matches.free.slice_from(2), duration, signal, kill_after, foreground, status);
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
fn timeout(cmdname: &str, args: &[String], duration: f64, signal: uint, kill_after: f64, foreground: bool, preserve_status: bool) -> int {
|
||||
if !foreground {
|
||||
unsafe { setpgid(0, 0) };
|
||||
}
|
||||
let mut process = match Command::new(cmdname).args(args)
|
||||
.stdin(InheritFd(0))
|
||||
.stdout(InheritFd(1))
|
||||
.stderr(InheritFd(2))
|
||||
.spawn() {
|
||||
Ok(p) => p,
|
||||
Err(err) => {
|
||||
show_error!("failed to execute process: {}", err);
|
||||
if err.kind == FileNotFound || err.kind == PathDoesntExist {
|
||||
// XXX: not sure which to use
|
||||
return 127;
|
||||
} else {
|
||||
// XXX: this may not be 100% correct...
|
||||
return 126;
|
||||
}
|
||||
}
|
||||
};
|
||||
process.set_timeout(Some((duration * 1000f64) as u64)); // FIXME: this ignores the f64...
|
||||
match process.wait() {
|
||||
Ok(status) => match status {
|
||||
ExitStatus(stat) => stat,
|
||||
ExitSignal(stat) => stat
|
||||
},
|
||||
Err(_) => {
|
||||
return_if_err!(ERR_EXIT_STATUS, process.signal(signal as int));
|
||||
process.set_timeout(Some((kill_after * 1000f64) as u64));
|
||||
match process.wait() {
|
||||
Ok(status) => {
|
||||
if preserve_status {
|
||||
match status {
|
||||
ExitStatus(stat) => stat,
|
||||
ExitSignal(stat) => stat
|
||||
}
|
||||
} else {
|
||||
124
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
if kill_after == 0f64 {
|
||||
// XXX: this may not be right
|
||||
return 124;
|
||||
}
|
||||
return_if_err!(ERR_EXIT_STATUS, process.signal(signals::signal_by_name_or_value("KILL").unwrap() as int));
|
||||
process.set_timeout(None);
|
||||
return_if_err!(ERR_EXIT_STATUS, process.wait());
|
||||
137
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue