mirror of
https://github.com/uutils/coreutils
synced 2024-11-16 09:48:03 +00:00
nohup: move from getopts to clap (#1871)
- changed some error return codes to match GNU implementation - changed warning/error messages to match GNU nohup - replaced getopts dependency with clap - added a test
This commit is contained in:
parent
20dec4cbba
commit
a1b50ae0f4
3 changed files with 90 additions and 66 deletions
|
@ -15,7 +15,7 @@ edition = "2018"
|
|||
path = "src/nohup.rs"
|
||||
|
||||
[dependencies]
|
||||
getopts = "0.2.18"
|
||||
clap = "2.33"
|
||||
libc = "0.2.42"
|
||||
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["fs"] }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::{App, AppSettings, Arg};
|
||||
use libc::{c_char, dup2, execvp, signal};
|
||||
use libc::{SIGHUP, SIG_IGN};
|
||||
use std::env;
|
||||
|
@ -20,50 +21,42 @@ use std::os::unix::prelude::*;
|
|||
use std::path::{Path, PathBuf};
|
||||
use uucore::fs::{is_stderr_interactive, is_stdin_interactive, is_stdout_interactive};
|
||||
|
||||
static NAME: &str = "nohup";
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static ABOUT: &str = "Run COMMAND ignoring hangup signals.";
|
||||
static LONG_HELP: &str = "
|
||||
If standard input is terminal, it'll be replaced with /dev/null.
|
||||
If standard output is terminal, it'll be appended to nohup.out instead,
|
||||
or $HOME/nohup.out, if nohup.out open failed.
|
||||
If standard error is terminal, it'll be redirected to stdout.
|
||||
";
|
||||
static NOHUP_OUT: &str = "nohup.out";
|
||||
// exit codes that match the GNU implementation
|
||||
static EXIT_CANCELED: i32 = 125;
|
||||
static EXIT_CANNOT_INVOKE: i32 = 126;
|
||||
static EXIT_ENOENT: i32 = 127;
|
||||
static POSIX_NOHUP_FAILURE: i32 = 127;
|
||||
|
||||
#[cfg(target_vendor = "apple")]
|
||||
extern "C" {
|
||||
fn _vprocmgr_detach_from_console(flags: u32) -> *const libc::c_int;
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
unsafe fn _vprocmgr_detach_from_console(_: u32) -> *const libc::c_int {
|
||||
std::ptr::null()
|
||||
mod options {
|
||||
pub const CMD: &str = "cmd";
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let args = args.collect_str();
|
||||
let usage = get_usage();
|
||||
|
||||
let mut opts = getopts::Options::new();
|
||||
let matches = App::new(executable!())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.usage(&usage[..])
|
||||
.after_help(LONG_HELP)
|
||||
.arg(
|
||||
Arg::with_name(options::CMD)
|
||||
.hidden(true)
|
||||
.required(true)
|
||||
.multiple(true),
|
||||
)
|
||||
.setting(AppSettings::TrailingVarArg)
|
||||
.get_matches_from(args);
|
||||
|
||||
opts.optflag("h", "help", "Show help and exit");
|
||||
opts.optflag("V", "version", "Show version and exit");
|
||||
|
||||
let matches = match opts.parse(&args[1..]) {
|
||||
Ok(m) => m,
|
||||
Err(f) => {
|
||||
show_error!("{}", f);
|
||||
show_usage(&opts);
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
if matches.opt_present("V") {
|
||||
println!("{} {}", NAME, VERSION);
|
||||
return 0;
|
||||
}
|
||||
if matches.opt_present("h") {
|
||||
show_usage(&opts);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if matches.free.is_empty() {
|
||||
show_error!("Missing operand: COMMAND");
|
||||
println!("Try `{} --help` for more information.", NAME);
|
||||
return 1;
|
||||
}
|
||||
replace_fds();
|
||||
|
||||
unsafe { signal(SIGHUP, SIG_IGN) };
|
||||
|
@ -73,13 +66,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
};
|
||||
|
||||
let cstrs: Vec<CString> = matches
|
||||
.free
|
||||
.iter()
|
||||
.values_of(options::CMD)
|
||||
.unwrap()
|
||||
.map(|x| CString::new(x.as_bytes()).unwrap())
|
||||
.collect();
|
||||
let mut args: Vec<*const c_char> = cstrs.iter().map(|s| s.as_ptr()).collect();
|
||||
args.push(std::ptr::null());
|
||||
unsafe { execvp(args[0], args.as_mut_ptr()) }
|
||||
|
||||
let ret = unsafe { execvp(args[0], args.as_mut_ptr()) };
|
||||
match ret {
|
||||
libc::ENOENT => EXIT_ENOENT,
|
||||
_ => EXIT_CANNOT_INVOKE,
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_fds() {
|
||||
|
@ -108,23 +106,32 @@ fn replace_fds() {
|
|||
}
|
||||
|
||||
fn find_stdout() -> File {
|
||||
let internal_failure_code = match std::env::var("POSIXLY_CORRECT") {
|
||||
Ok(_) => POSIX_NOHUP_FAILURE,
|
||||
Err(_) => EXIT_CANCELED,
|
||||
};
|
||||
|
||||
match OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(Path::new("nohup.out"))
|
||||
.open(Path::new(NOHUP_OUT))
|
||||
{
|
||||
Ok(t) => {
|
||||
show_warning!("Output is redirected to: nohup.out");
|
||||
show_info!("ignoring input and appending output to '{}'", NOHUP_OUT);
|
||||
t
|
||||
}
|
||||
Err(e) => {
|
||||
Err(e1) => {
|
||||
let home = match env::var("HOME") {
|
||||
Err(_) => crash!(2, "Cannot replace STDOUT: {}", e),
|
||||
Err(_) => {
|
||||
show_info!("failed to open '{}': {}", NOHUP_OUT, e1);
|
||||
exit!(internal_failure_code)
|
||||
}
|
||||
Ok(h) => h,
|
||||
};
|
||||
let mut homeout = PathBuf::from(home);
|
||||
homeout.push("nohup.out");
|
||||
homeout.push(NOHUP_OUT);
|
||||
let homeout_str = homeout.to_str().unwrap();
|
||||
match OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
|
@ -132,30 +139,29 @@ fn find_stdout() -> File {
|
|||
.open(&homeout)
|
||||
{
|
||||
Ok(t) => {
|
||||
show_warning!("Output is redirected to: {:?}", homeout);
|
||||
show_info!("ignoring input and appending output to '{}'", homeout_str);
|
||||
t
|
||||
}
|
||||
Err(e) => crash!(2, "Cannot replace STDOUT: {}", e),
|
||||
Err(e2) => {
|
||||
show_info!("failed to open '{}': {}", NOHUP_OUT, e1);
|
||||
show_info!("failed to open '{}': {}", homeout_str, e2);
|
||||
exit!(internal_failure_code)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn show_usage(opts: &getopts::Options) {
|
||||
let msg = format!(
|
||||
"{0} {1}
|
||||
|
||||
Usage:
|
||||
{0} COMMAND [ARG]...
|
||||
{0} OPTION
|
||||
|
||||
Run COMMAND ignoring hangup signals.
|
||||
If standard input is terminal, it'll be replaced with /dev/null.
|
||||
If standard output is terminal, it'll be appended to nohup.out instead,
|
||||
or $HOME/nohup.out, if nohup.out open failed.
|
||||
If standard error is terminal, it'll be redirected to stdout.",
|
||||
NAME, VERSION
|
||||
);
|
||||
|
||||
print!("{}", opts.usage(&msg));
|
||||
fn get_usage() -> String {
|
||||
format!("{0} COMMAND [ARG]...\n {0} FLAG", executable!())
|
||||
}
|
||||
|
||||
#[cfg(target_vendor = "apple")]
|
||||
extern "C" {
|
||||
fn _vprocmgr_detach_from_console(flags: u32) -> *const libc::c_int;
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
unsafe fn _vprocmgr_detach_from_console(_: u32) -> *const libc::c_int {
|
||||
std::ptr::null()
|
||||
}
|
||||
|
|
|
@ -1 +1,19 @@
|
|||
// ToDO: add tests
|
||||
use crate::common::util::*;
|
||||
use std::thread::sleep;
|
||||
|
||||
// General observation: nohup.out will not be created in tests run by cargo test
|
||||
// because stdin/stdout is not attached to a TTY.
|
||||
// All that can be tested is the side-effects.
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd", target_vendor = "apple"))]
|
||||
fn test_nohup_multiple_args_and_flags() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
ucmd.args(&["touch", "-t", "1006161200", "file1", "file2"])
|
||||
.succeeds();
|
||||
sleep(std::time::Duration::from_millis(10));
|
||||
|
||||
assert!(at.file_exists("file1"));
|
||||
assert!(at.file_exists("file2"));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue