Skip to content

Commit

Permalink
tailhook#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 Jan 26, 2016
1 parent c852be0 commit 82018de
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 14 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
68 changes: 66 additions & 2 deletions src/process_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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<i32, String>
{
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,
Expand Down
30 changes: 30 additions & 0 deletions src/tty_util.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
13 changes: 9 additions & 4 deletions src/wrapper/commandline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<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,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)
}
}
38 changes: 35 additions & 3 deletions src/wrapper/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};


Expand Down Expand Up @@ -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,
Expand All @@ -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)
Expand Down
15 changes: 10 additions & 5 deletions src/wrapper/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<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 +112,11 @@ 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)),
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),
}
}

0 comments on commit 82018de

Please sign in to comment.