1
0
mirror of https://github.com/uutils/coreutils synced 2024-07-09 04:06:02 +00:00

Implement timeout (resolves #364)

This commit is contained in:
Arcterus 2014-07-21 18:50:53 -07:00
parent 439a8cadd1
commit a38ee8a007
10 changed files with 235 additions and 52 deletions

View File

@ -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"

View File

@ -93,6 +93,7 @@ UNIX_PROGS := \
logname \
mkfifo \
nohup \
timeout \
tty \
uname \
uptime \

View File

@ -165,7 +165,6 @@ To do
- stty
- tail (not all features implemented)
- test (not all features implemented)
- timeout
- unexpand
- uniq (in progress)
- who

View File

@ -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(),

View File

@ -23,7 +23,7 @@ Linux Programmer's Manual
15 TERM 16 STKFLT 17 CHLD 18 CONT 19 STOP 20 TSTP 21 TTIN
22 TTOU 23 URG 24 XCPU 25 XFSZ 26 VTALRM 27 PROF 28 WINCH
29 POLL 30 PWR 31 SYS
*/
@ -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
View 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))
}
}

View File

@ -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),+) => (

View File

@ -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)

View File

@ -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
View 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
}
}
}
}
}