diff --git a/src/main.rs b/src/main.rs index 861e5bdb..9446adf0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,6 +24,7 @@ mod container; mod file_util; mod path_util; mod process_util; +mod tty_util; mod options; // Commands diff --git a/src/process_util.rs b/src/process_util.rs index 6ff19911..342e47f4 100644 --- a/src/process_util.rs +++ b/src/process_util.rs @@ -2,14 +2,18 @@ use std::env; use std::io::{Read}; use std::path::{Path, PathBuf}; -use libc::getuid; -use nix::sys::signal::{SIGINT, SIGTERM, SIGCHLD}; +use libc::{getuid, getpgrp}; +use nix; +use nix::errno::EINTR; +use nix::sys::signal::{SIGINT, SIGTERM, SIGCHLD, SIGTTIN, SIGTTOU, SIGCONT}; +use nix::sys::wait::{waitpid, WaitStatus, WNOHANG, WUNTRACED}; use unshare::{Command, Stdio, Fd, ExitStatus, UidMap, GidMap, reap_zombies}; use signal::trap::Trap; use config::Settings; use container::uidmap::{Uidmap}; use path_util::PathExt; +use tty_util::give_tty_to; pub static DEFAULT_PATH: &'static str = @@ -97,6 +101,66 @@ pub fn run_and_wait(cmd: &mut Command) unreachable!(); } +pub fn run_with_tty_and_wait(cmd: &mut Command, tty_fd: i32) + -> Result +{ + let pgid = unsafe { getpgrp() }; + let child = match cmd.spawn() { + Ok(child) => child, + Err(e) => return Err(format!("Error running {:?}: {}", cmd, e)), + }; + let mut trap = Trap::trap(&[SIGCHLD, SIGTERM]); + 'signal_loop: for signal in trap.by_ref() { + match signal { + SIGCHLD => { + debug!("Received SIGCHLD signal, waiting child"); + loop { + match waitpid(child.pid(), Some(WNOHANG | WUNTRACED)) { + Ok(WaitStatus::Stopped(_, signum)) => { + if signum == SIGTTOU || signum == SIGTTIN { + try!(give_tty_to(tty_fd, child.pid())); + try!(child.signal(SIGCONT) + .map_err(|e| format!( + "Error when sending SIGCONT + signal to {:?}: {}", cmd, e))); + } + continue 'signal_loop; + }, + Ok(WaitStatus::Exited(_, st)) => { + try!(give_tty_to(tty_fd, pgid)); + return Ok(st as i32); + }, + Ok(WaitStatus::Signaled(_, signum, _)) => { + try!(give_tty_to(tty_fd, pgid)); + return Ok(128 + signum); + }, + Ok(_) => { + continue 'signal_loop; + }, + Err(nix::Error::Sys(EINTR)) => { + continue; + }, + Err(e) => { + try!(give_tty_to(tty_fd, pgid)); + return Err( + format!("Error when waiting {:?}: {}", cmd, e)); + }, + } + } + }, + SIGTERM => { + debug!("Received SIGTERM signal, propagating"); + try!(child.signal(SIGTERM) + .map_err(|e| format!( + "Error when sending SIGTERM + signal to {:?}: {}", cmd, e))); + } + _ => unreachable!("Unhandled signal"), + } + } + unreachable!(); +} + pub fn convert_status(st: ExitStatus) -> i32 { match st { ExitStatus::Exited(c) => c as i32, diff --git a/src/tty_util.rs b/src/tty_util.rs new file mode 100644 index 00000000..51a3d537 --- /dev/null +++ b/src/tty_util.rs @@ -0,0 +1,30 @@ +use libc::{c_int, c_ulong, pid_t, sighandler_t}; +use libc::signal; +use nix::sys::ioctl::ioctl; +use nix::sys::signal::{SIGTTIN, SIGTTOU}; + + +static SIG_IGN: sighandler_t = 1; +static SIG_ERR: sighandler_t = !0; + +static TIOCSPGRP: c_ulong = 21520; + + +pub fn give_tty_to(fd: c_int, pid: pid_t) -> Result<(), String> { + let res = unsafe { ioctl(fd, TIOCSPGRP, &pid) }; + match res { + res if res < 0 => Err(format!("Error when giving tty with fd {} to process {}", fd, pid)), + _ => Ok(()), + } +} + +pub fn ignore_tty_signals() -> Result<(), String> { + let tty_signals = vec![SIGTTIN, SIGTTOU]; + for signum in tty_signals { + let res = unsafe { signal(signum, SIG_IGN) }; + if res == SIG_ERR { + return Err(format!("Error when setting signal handler for signum: {}", signum)); + } + } + Ok(()) +} diff --git a/src/wrapper/commandline.rs b/src/wrapper/commandline.rs index 7f195aa9..c5b41a38 100644 --- a/src/wrapper/commandline.rs +++ b/src/wrapper/commandline.rs @@ -15,11 +15,11 @@ use container::uidmap::{map_users}; use super::setup; use super::Wrapper; use super::util::find_cmd; -use process_util::{run_and_wait, set_uidmap}; +use process_util::{run_and_wait, run_with_tty_and_wait, set_uidmap}; pub fn commandline_cmd(command: &CommandInfo, - wrapper: &Wrapper, mut cmdline: Vec) + wrapper: &Wrapper, mut cmdline: Vec, tty_fd: Option) -> Result { if command.run.len() == 0 { @@ -99,6 +99,11 @@ pub fn commandline_cmd(command: &CommandInfo, cmd.env(k, v); } - run_and_wait(&mut cmd) - .map_err(|e| format!("Error running {:?}: {}", cmd, e)) + match tty_fd { + Some(tty_fd) => { + cmd.make_group_leader(true); + run_with_tty_and_wait(&mut cmd, tty_fd) + }, + None => run_and_wait(&mut cmd) + } } diff --git a/src/wrapper/mod.rs b/src/wrapper/mod.rs index 50d6aa31..29588d87 100644 --- a/src/wrapper/mod.rs +++ b/src/wrapper/mod.rs @@ -4,9 +4,12 @@ use std::path::Path; use std::process::exit; use argparse::{ArgumentParser, Store, StoreOption, List}; +use libc::STDIN_FILENO; +use nix::unistd::{isatty, getpid, setpgid}; use super::config::{find_config, Config, Settings}; use super::config::command::MainCommand::{Command, Supervise}; +use super::tty_util::{ignore_tty_signals, give_tty_to}; use config::read_settings::{read_settings, MergedSettings}; @@ -80,6 +83,34 @@ pub fn run() -> i32 { } }; + let tty_fd = STDIN_FILENO; + let is_interactive = isatty(tty_fd).unwrap_or(false); + if is_interactive { + match ignore_tty_signals() { + Ok(_) => {}, + Err(e) => { + writeln!(&mut err, "{}", e).ok(); + return 121; + }, + } + let pid = getpid(); + match setpgid(pid, pid) { + Ok(_) => {}, + Err(e) => { + writeln!(&mut err, "{}", e).ok(); + return 121; + }, + } + match give_tty_to(tty_fd, pid) { + Ok(_) => {}, + Err(e) => { + writeln!(&mut err, "{}", e).ok(); + return 121; + }, + } + } + let tty_fd = if is_interactive { Some(tty_fd) } else { None }; + let wrapper = Wrapper { root: root, config: &config, @@ -94,14 +125,15 @@ pub fn run() -> i32 { "_build_shell" => Ok(debug::run_interactive_build_shell(&wrapper)), "_build" => build::build_container_cmd(&wrapper, args), "_version_hash" => build::print_version_hash_cmd(&wrapper, args), - "_run" => run::run_command_cmd(&wrapper, args, true), - "_run_in_netns" => run::run_command_cmd(&wrapper, args, false), + "_run" => run::run_command_cmd(&wrapper, args, true, tty_fd), + "_run_in_netns" => run::run_command_cmd(&wrapper, args, false, tty_fd), "_clean" => clean::clean_cmd(&wrapper, args), "_pack_image" => pack::pack_image_cmd(&wrapper, args), _ => { match config.commands.get(&cmd) { Some(&Command(ref cmd_info)) => { - commandline::commandline_cmd(cmd_info, &wrapper, args) + commandline::commandline_cmd( + cmd_info, &wrapper, args, tty_fd) } Some(&Supervise(ref svc_info)) => { supervise::supervise_cmd(&cmd, svc_info, &wrapper, args) diff --git a/src/wrapper/run.rs b/src/wrapper/run.rs index d21f2aa6..0a542a58 100644 --- a/src/wrapper/run.rs +++ b/src/wrapper/run.rs @@ -12,10 +12,12 @@ use container::uidmap::{map_users}; use super::setup; use super::Wrapper; use path_util::PathExt; -use process_util::{convert_status, set_uidmap, copy_env_vars}; +use process_util::{run_and_wait, run_with_tty_and_wait}; +use process_util::{set_uidmap, copy_env_vars}; -pub fn run_command_cmd(wrapper: &Wrapper, cmdline: Vec, user_ns: bool) +pub fn run_command_cmd(wrapper: &Wrapper, + cmdline: Vec, user_ns: bool, tty_fd: Option) -> Result { let mut container: String = "".to_string(); @@ -110,8 +112,11 @@ pub fn run_command_cmd(wrapper: &Wrapper, cmdline: Vec, user_ns: bool) cmd.env(k.to_string(), v.to_string()); } - match cmd.status() { - Ok(s) => Ok(convert_status(s)), - Err(e) => Err(format!("Error running {:?}: {}", cmd, e)), + match tty_fd { + Some(tty_fd) => { + cmd.make_group_leader(true); + run_with_tty_and_wait(&mut cmd, tty_fd) + }, + None => run_and_wait(&mut cmd), } }