diff --git a/Makefile b/Makefile index 0b7492e90..c2c4a8b1d 100644 --- a/Makefile +++ b/Makefile @@ -56,6 +56,7 @@ UNIX_PROGS := \ kill \ logname \ mkfifo \ + nohup \ tty \ uname \ uptime \ diff --git a/nohup/nohup.rs b/nohup/nohup.rs new file mode 100644 index 000000000..4c7c493c3 --- /dev/null +++ b/nohup/nohup.rs @@ -0,0 +1,175 @@ +#![crate_id = "nohup#1.0.0"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) 2014 Vsevolod Velichko + * + * 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; +extern crate native; + +use getopts::{optflag, getopts, usage}; +use std::io::stdio::{stdin_raw, stdout_raw, stderr_raw}; +use std::rt::rtio::{Open, Read, Append, Write}; +use libc::funcs::posix88::unistd::{dup2, execvp}; +use libc::consts::os::posix88::SIGHUP; +use libc::funcs::posix01::signal::signal; +use libc::consts::os::posix01::SIG_IGN; + +#[path = "../common/util.rs"] mod util; +#[path = "../common/c_types.rs"] mod c_types; + +static NAME: &'static str = "nohup"; +static VERSION: &'static str = "1.0.0"; + +#[cfg(target_os = "macos")] +extern { + fn _vprocmgr_detach_from_console(flags: u32) -> *libc::c_int; +} + +#[cfg(target_os = "macos")] +fn rewind_stdout(s: &mut T) { + s.seek(0, std::rt::rtio::SeekEnd); +} + +#[cfg(target_os = "linux")] +#[cfg(target_os = "freebsd")] +fn _vprocmgr_detach_from_console(_: u32) -> *libc::c_int { std::ptr::null() } + +#[cfg(target_os = "linux")] +#[cfg(target_os = "freebsd")] +fn rewind_stdout(_: &mut T) {} + +#[allow(dead_code)] +fn main () { std::os::set_exit_status(uumain(std::os::args())); } + +pub fn uumain(args: Vec) -> int { + let program = args.get(0); + + let options = [ + optflag("h", "help", "Show help and exit"), + optflag("V", "version", "Show version and exit"), + ]; + + let opts = match getopts(args.tail(), options) { + Ok(m) => m, + Err(f) => { + show_error!("{}", f); + show_usage(program.as_slice(), options); + return 1 + } + }; + + if opts.opt_present("V") { version(); return 0 } + if opts.opt_present("h") { show_usage(program.as_slice(), options); return 0 } + + if opts.free.len() == 0 { + show_error!("Missing operand: COMMAND"); + println!("Try `{:s} --help` for more information.", program.as_slice()); + return 1 + } + replace_fds(); + + unsafe { signal(SIGHUP, SIG_IGN) }; + + if _vprocmgr_detach_from_console(0) != std::ptr::null() { crash!(2, "Cannot detach from console")}; + + unsafe { + // we ignore the memory leak here because it doesn't matter anymore + let executable = opts.free.get(0).as_slice().to_c_str().unwrap(); + let mut args: Vec<*i8> = opts.free.iter().map(|x| x.to_c_str().unwrap()).collect(); + args.push(std::ptr::null()); + execvp(executable as *libc::c_char, args.as_ptr() as **libc::c_char) as int + } +} + +fn replace_fds() { + let replace_stdin = stdin_raw().isatty(); + let replace_stdout = stdout_raw().isatty(); + let replace_stderr = stderr_raw().isatty(); + + if replace_stdin { + let devnull = "/dev/null".to_c_str(); + let new_stdin = match native::io::file::open(&devnull, Open, Read) { + Ok(t) => t, + Err(_) => { + let e = std::io::IoError::last_error(); + crash!(2, "Cannot replace STDIN: {}", e) + } + }; + if unsafe { dup2(new_stdin.fd(), 0) } != 0 { + crash!(2, "Cannot replace STDIN: {}", std::io::IoError::last_error()) + } + } + + if replace_stdout { + let mut new_stdout = find_stdout(); + + rewind_stdout(&mut new_stdout); + + if unsafe { dup2(new_stdout.fd(), 1) } != 1 { + crash!(2, "Cannot replace STDOUT: {}", std::io::IoError::last_error()) + } + } + + if replace_stderr { + if unsafe { dup2(1, 2) } != 2 { + crash!(2, "Cannot replace STDERR: {}", std::io::IoError::last_error()) + } + } +} + +fn find_stdout() -> native::io::file::FileDesc { + let localout = "nohup.out".to_c_str(); + match native::io::file::open(&localout, Append, Write) { + Ok(t) => { + show_warning!("Output is redirected to: nohup.out"); + t + }, + Err(_) => { + let e = std::io::IoError::last_error(); + let home = match std::os::getenv("HOME") { + None => crash!(2, "Cannot replace STDOUT: {}", e), + Some(h) => h + }; + let mut homeoutpath = Path::new(home); + homeoutpath.push("nohup.out"); + let homeout = homeoutpath.to_c_str(); + match native::io::file::open(&homeout, Append, Write) { + Ok(t) => { + show_warning!("Output is redirected to: {}", homeoutpath.display()); + t + }, + Err(_) => { + let e = std::io::IoError::last_error(); + crash!(2, "Cannot replace STDOUT: {}", e) + } + } + } + } +} + +fn version() { + println!("{} v{}", NAME, VERSION) +} + +fn show_usage(program: &str, options: &[getopts::OptGroup]) { + version(); + println!("Usage:"); + println!(" {:s} COMMAND [ARG]…", program); + println!(" {:s} OPTION", program); + println!(""); + print!("{:s}", usage( + "Run COMMAND ignoring hangup signals.\n\ + If standard input is terminal, it'll be replaced with /dev/null.\n\ + If standard output is terminal, it'll be appended to nohup.out instead, \ + or $HOME/nohup.out, if nohup.out open failed.\n\ + If standard error is terminal, it'll be redirected to stdout.", options) + ); +}