Skip to content

Commit

Permalink
#152 Handle Ctrl+C for _run and user commands
Browse files Browse the repository at this point in the history
  • Loading branch information
anti-social committed Feb 3, 2016
1 parent eb4654b commit 95e4a08
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 23 deletions.
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ mod container;
mod file_util;
mod path_util;
mod process_util;
mod tty_util;
mod options;

// Commands
Expand Down
48 changes: 37 additions & 11 deletions src/process_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ use std::env;
use std::io::{Read};
use std::path::{Path, PathBuf};

use libc::getuid;
use libc::{getuid, getpgrp};
use nix::sys::signal::{SIGINT, SIGTERM, SIGCHLD};
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::{TtyGuard};


pub static DEFAULT_PATH: &'static str =
Expand All @@ -19,7 +20,6 @@ pub static PROXY_ENV_VARS: [&'static str; 5] =
[ "http_proxy", "https_proxy", "ftp_proxy", "all_proxy", "no_proxy" ];



pub fn capture_stdout(mut cmd: Command) -> Result<Vec<u8>, String> {
cmd.stdout(Stdio::piped());
info!("Running {:?}", cmd);
Expand Down Expand Up @@ -64,31 +64,57 @@ pub fn capture_fd3_status(mut cmd: Command)
Ok((status, buf))
}

pub fn run_and_wait(cmd: &mut Command)
pub fn run_and_wait(cmd: &mut Command, tty_fd: Option<i32>)
-> Result<i32, String>
{
let mut trap = Trap::trap(&[SIGINT, SIGTERM, SIGCHLD]);
info!("Running {:?}", cmd);
let child = try!(cmd.spawn().map_err(|e| format!("{}", e)));
let child = try!(cmd.spawn()
.map_err(|e| format!("Error running {:?}: {}", cmd, e)));
let pgrp = unsafe { getpgrp() };
let tty_guard = match tty_fd {
Some(tty_fd) => Some(try!(TtyGuard::new(tty_fd, pgrp))),
None => None,
};

for signal in trap.by_ref() {
match signal {
SIGINT => {
// SIGINT is usually a Ctrl+C so it's sent to whole process
// group, so we don't need to do anything special
// SIGINT is usually a Ctrl+C, if we trap it here
// child process hasn't controlling terminal,
// so we send the signal to the child process
debug!("Received SIGINT signal. Waiting process to stop..");
try!(child.signal(SIGINT)
.map_err(|e| format!(
"Error sending SIGINT to {:?}: {}", cmd, e)));
}
SIGTERM => {
// SIGTERM is usually sent to a specific process so we
// forward it to children
debug!("Received SIGTERM signal, propagating");
child.signal(SIGTERM).ok();
try!(child.signal(SIGTERM)
.map_err(|e| format!(
"Error sending SIGTERM to {:?}: {}", cmd, e)));
}
SIGCHLD => {
for (pid, status) in reap_zombies() {
if pid == child.pid() {
return Ok(convert_status(status));
}
match tty_guard {
Some(ref tty_guard) => {
match try!(tty_guard.wait_child(&cmd, &child)) {
Some(st) => {
return Ok(convert_status(st));
},
None => {
continue;
},
}
},
None => {
for (pid, status) in reap_zombies() {
if pid == child.pid() {
return Ok(convert_status(status));
}
}
},
}
}
_ => unreachable!(),
Expand Down
127 changes: 127 additions & 0 deletions src/tty_util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use libc::{c_int, pid_t};
use libc::{signal};
use libc::STDIN_FILENO;
use nix;
use nix::errno::EINTR;
use nix::sys::ioctl::ioctl;
use nix::sys::signal::{SIGCONT, SIGTTIN, SIGTTOU};
use nix::sys::wait::{waitpid, WaitStatus, WNOHANG, WUNTRACED};
use nix::unistd::{isatty, getpid, setpgid};

use unshare::{Child, Command, ExitStatus};


mod ffi {
use libc::{c_ulong, pid_t, sighandler_t};

pub static WAIT_ANY: pid_t = -1;

pub static SIG_IGN: sighandler_t = 1;
pub static SIG_ERR: sighandler_t = !0;

pub static TIOCSPGRP: c_ulong = 21520;
}


pub struct TtyGuard {
tty_fd: c_int,
pgrp: pid_t,
}

impl TtyGuard {
pub fn new(tty_fd: c_int, pgrp: pid_t) -> Result<TtyGuard, String> {
try!(give_tty_to(tty_fd, pgrp));
Ok(TtyGuard {
tty_fd: tty_fd,
pgrp: pgrp,
})
}

pub fn wait_child(&self, cmd: &Command, child: &Child)
-> Result<Option<ExitStatus>, String>
{
loop {
match waitpid(ffi::WAIT_ANY, Some(WNOHANG | WUNTRACED)) {
Ok(WaitStatus::Stopped(child_pid, signum)) => {
if child_pid == child.pid() && (signum == SIGTTOU || signum == SIGTTIN) {
try!(give_tty_to(self.tty_fd, child.pid()));
try!(child.signal(SIGCONT)
.map_err(|e| format!(
"Error sending SIGCONT to {:?}: {}", cmd, e)));
}
continue;
},
Ok(WaitStatus::Continued(_)) => {
continue;
},
Ok(WaitStatus::Exited(child_pid, st)) => {
if child_pid == child.pid() {
return Ok(Some(ExitStatus::Exited(st)));
}
continue;
},
Ok(WaitStatus::Signaled(child_pid, signum, core)) => {
if child_pid == child.pid() {
return Ok(Some(ExitStatus::Signaled(signum, core)));
}
continue;
},
Ok(WaitStatus::StillAlive) => {
return Ok(None);
},
Err(nix::Error::Sys(EINTR)) => {
continue;
},
Err(e) => {
return Err(
format!("Error when waiting for {:?}: {}", cmd, e));
},
}
}
}
}

impl Drop for TtyGuard {
fn drop(&mut self) {
let _ = give_tty_to(self.tty_fd, self.pgrp);
}
}

pub fn prepare_tty() -> Result<Option<c_int>, String> {
let tty_fd = STDIN_FILENO;
let is_interactive = isatty(tty_fd).unwrap_or(false);
if is_interactive {
try!(ignore_tty_signals());
let pid = getpid();
try!(setpgid(pid, pid).map_err(|e| format!("{}", e)));
try!(give_tty_to(tty_fd, pid));
Ok(Some(tty_fd))
} else {
Ok(None)
}
}

pub fn give_tty_to(fd: c_int, pgrp: pid_t) -> Result<(), String> {
let res = unsafe { ioctl(fd, ffi::TIOCSPGRP, &pgrp) };
match res {
res if res < 0 => Err(
format!("Error when giving tty with fd {} to process group {}",
fd, pgrp)),
_ => Ok(()),
}
}

pub fn ignore_tty_signals() -> Result<(), String> {
try!(ignore_signal(SIGTTIN));
try!(ignore_signal(SIGTTOU));
Ok(())
}

fn ignore_signal(signum: i32) -> Result<(), String> {
let res = unsafe { signal(signum, ffi::SIG_IGN) };
if res == ffi::SIG_ERR {
return Err(
format!("Error when setting signal handler for signum: {}", signum));
}
Ok(())
}
9 changes: 6 additions & 3 deletions src/wrapper/commandline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use process_util::{run_and_wait, set_uidmap};


pub fn commandline_cmd(command: &CommandInfo,
wrapper: &Wrapper, mut cmdline: Vec<String>)
wrapper: &Wrapper, mut cmdline: Vec<String>, tty_fd: Option<i32>)
-> Result<i32, String>
{
if command.run.len() == 0 {
Expand Down Expand Up @@ -99,6 +99,9 @@ pub fn commandline_cmd(command: &CommandInfo,
cmd.env(k, v);
}

run_and_wait(&mut cmd)
.map_err(|e| format!("Error running {:?}: {}", cmd, e))
if let Some(_) = tty_fd {
cmd.make_group_leader(true);
}

run_and_wait(&mut cmd, tty_fd)
}
15 changes: 12 additions & 3 deletions src/wrapper/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use argparse::{ArgumentParser, Store, StoreOption, List};
use super::config::{find_config, Config, Settings};
use super::config::command::MainCommand::{Command, Supervise};
use config::read_settings::{read_settings, MergedSettings};
use tty_util::{prepare_tty};


mod debug;
Expand Down Expand Up @@ -80,6 +81,14 @@ pub fn run() -> i32 {
}
};

let tty_fd = match prepare_tty() {
Ok(tty_fd) => tty_fd,
Err(e) => {
writeln!(&mut err, "{}", e).ok();
return 121;
},
};

let wrapper = Wrapper {
root: root,
config: &config,
Expand All @@ -94,14 +103,14 @@ 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)
Expand Down
12 changes: 7 additions & 5 deletions src/wrapper/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ 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::{set_uidmap, copy_env_vars, run_and_wait};


pub fn run_command_cmd(wrapper: &Wrapper, cmdline: Vec<String>, user_ns: bool)
pub fn run_command_cmd(wrapper: &Wrapper,
cmdline: Vec<String>, user_ns: bool, tty_fd: Option<i32>)
-> Result<i32, String>
{
let mut container: String = "".to_string();
Expand Down Expand Up @@ -110,8 +111,9 @@ pub fn run_command_cmd(wrapper: &Wrapper, cmdline: Vec<String>, 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)),
if let Some(_) = tty_fd {
cmd.make_group_leader(true);
}

run_and_wait(&mut cmd, tty_fd)
}
2 changes: 1 addition & 1 deletion src/wrapper/supervise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,6 @@ fn supervise_child_command(cmdname: &String, name: &String, bridge: bool,
cmd.env(k, v);
}

run_and_wait(&mut cmd)
run_and_wait(&mut cmd, None)
.map_err(|e| format!("Error running {:?}: {}", cmd, e))
}

0 comments on commit 95e4a08

Please sign in to comment.