#[cfg(not(unix))]
fn main() {
eprintln!("nohup: only available on Unix");
std::process::exit(1);
}
#[cfg(unix)]
use std::fs::{File, OpenOptions};
#[cfg(unix)]
use std::os::unix::io::AsRawFd;
#[cfg(unix)]
use std::os::unix::process::CommandExt;
#[cfg(unix)]
use std::process;
#[cfg(unix)]
const TOOL_NAME: &str = "nohup";
#[cfg(unix)]
const VERSION: &str = env!("CARGO_PKG_VERSION");
#[cfg(unix)]
fn main() {
coreutils_rs::common::reset_sigpipe();
let args: Vec<String> = std::env::args().skip(1).collect();
if args.is_empty() {
eprintln!("{}: missing operand", TOOL_NAME);
eprintln!("Try '{} --help' for more information.", TOOL_NAME);
if std::env::var_os("POSIXLY_CORRECT").is_some() {
process::exit(127);
} else {
process::exit(125);
}
}
match args[0].as_str() {
"--help" => {
println!("Usage: {} COMMAND [ARG]...", TOOL_NAME);
println!(" or: {} OPTION", TOOL_NAME);
println!("Run COMMAND, ignoring hangup signals.");
println!();
println!("If standard output is a terminal, append output to 'nohup.out' if possible,");
println!("'$HOME/nohup.out' otherwise.");
println!("If standard error is a terminal, redirect it to standard output.");
println!(
"To save output to FILE, use '{} COMMAND > FILE'.",
TOOL_NAME
);
println!();
println!(" --help display this help and exit");
println!(" --version output version information and exit");
return;
}
"--version" => {
println!("{} (fcoreutils) {}", TOOL_NAME, VERSION);
return;
}
_ => {}
}
unsafe {
libc::signal(libc::SIGHUP, libc::SIG_IGN);
}
let command = &args[0];
let command_args: Vec<&str> = args[1..].iter().map(|s| s.as_str()).collect();
let _stdout_file: Option<File> = if unsafe { libc::isatty(1) } == 1 {
let file = open_nohup_out();
match file {
Some(f) => {
unsafe {
libc::dup2(f.as_raw_fd(), 1);
}
eprintln!(
"{}: ignoring input and appending output to 'nohup.out'",
TOOL_NAME
);
Some(f)
}
None => {
eprintln!(
"{}: failed to open 'nohup.out': Permission denied or no suitable path",
TOOL_NAME
);
process::exit(127);
}
}
} else {
None
};
if unsafe { libc::isatty(2) } == 1 {
unsafe {
libc::dup2(1, 2);
}
}
let err = std::process::Command::new(command)
.args(&command_args)
.exec();
let code = if err.kind() == std::io::ErrorKind::NotFound {
127
} else {
126
};
eprintln!(
"{}: failed to run command '{}': {}",
TOOL_NAME,
command,
coreutils_rs::common::io_error_msg(&err)
);
process::exit(code);
}
#[cfg(unix)]
fn open_nohup_out() -> Option<File> {
if let Ok(f) = OpenOptions::new()
.create(true)
.append(true)
.open("nohup.out")
{
return Some(f);
}
if let Ok(home) = std::env::var("HOME") {
let path = std::path::Path::new(&home).join("nohup.out");
if let Ok(f) = OpenOptions::new().create(true).append(true).open(path) {
return Some(f);
}
}
None
}
#[cfg(all(test, unix))]
mod tests {
use std::process::Command;
fn cmd() -> Command {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("fnohup");
Command::new(path)
}
#[test]
fn test_nohup_runs_command() {
let output = cmd().args(["echo", "hello"]).output().unwrap();
assert_eq!(output.status.code(), Some(0));
let stdout = String::from_utf8_lossy(&output.stdout);
assert_eq!(stdout.trim(), "hello");
}
#[test]
fn test_nohup_missing_command() {
let output = cmd().output().unwrap();
assert_eq!(output.status.code(), Some(125));
}
#[test]
fn test_nohup_nonexistent_command() {
let output = cmd().arg("nonexistent_cmd_12345").output().unwrap();
assert_eq!(output.status.code(), Some(127));
}
#[test]
fn test_nohup_matches_gnu() {
let gnu = Command::new("nohup").args(["echo", "test"]).output();
if let Ok(gnu) = gnu {
let ours = cmd().args(["echo", "test"]).output().unwrap();
assert_eq!(ours.status.code(), gnu.status.code(), "Exit code mismatch");
assert_eq!(ours.stdout, gnu.stdout, "STDOUT mismatch");
}
}
#[test]
fn test_nohup_runs_command_exit_success() {
let output = cmd().args(["echo", "hello"]).output().unwrap();
assert!(output.status.success());
}
#[test]
fn test_nohup_passes_exit_code() {
let output = cmd().args(["sh", "-c", "exit 42"]).output().unwrap();
assert_eq!(output.status.code(), Some(42));
}
#[test]
fn test_nohup_with_args() {
let output = cmd()
.args(["sh", "-c", "echo hello world"])
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert_eq!(stdout.trim(), "hello world");
}
#[test]
fn test_nohup_nohup_out() {
let dir = tempfile::tempdir().unwrap();
let output = cmd()
.args(["echo", "test_output"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(output.status.success());
}
}