#![cfg_attr(feature="cargo-clippy", allow(needless_return))]
use std::fs::File;
use std::io::{Error, ErrorKind};
use std::process::Command;
use std::os::unix::process::CommandExt;
use std::os::unix::io::AsRawFd;
extern crate libc;
use libc::pid_t;
extern crate nix;
use nix::{Errno, c_int};
use nix::unistd;
use nix::sys::wait;
use nix::sys::signal;
use nix::sys::signalfd;
#[macro_use]
extern crate log;
extern crate env_logger;
#[macro_use]
extern crate clap;
fn reap_zombies() -> Result<Vec<pid_t>, Error> {
let mut pids = Vec::new();
loop {
match wait::waitpid(-1, Some(wait::WNOHANG)) {
Ok(wait::WaitStatus::Exited(cpid, _)) |
Ok(wait::WaitStatus::Signaled(cpid, _, _)) => {
debug!("child process exited: {}", cpid);
pids.push(cpid);
}
Ok(wait::WaitStatus::StillAlive) |
Err(nix::Error::Sys(Errno::ECHILD)) => break,
status @ Ok(_) => info!("saw unknown status: {:?}", status),
Err(err) => return Err(Error::from(err)),
};
}
return Ok(pids);
}
fn forward_signal(pid: pid_t, sig: signal::Signal) -> Result<(), Error> {
signal::kill(pid, sig)?;
debug!("forwarded {:?} to {}", sig, pid);
return Ok(());
}
fn process_signals(pid1: pid_t, sfd: &mut signalfd::SignalFd) -> Result<Vec<pid_t>, Error> {
let siginfo = sfd.read_signal()?.ok_or(Error::new(
ErrorKind::Other,
"no signals read",
))?;
let signum = signal::Signal::from_c_int(siginfo.ssi_signo as c_int)?;
match signum {
signal::Signal::SIGCHLD => reap_zombies(),
_ => forward_signal(pid1, signum).map(|_| Vec::new()),
}
}
fn make_foreground() -> Result<(), Error> {
unistd::setpgid(0, 0)?;
let pgrp = unistd::getpgrp();
let tty = match File::open("/dev/tty") {
Ok(tty) => tty,
Err(err) => {
info!("failed to open /dev/tty: {}", err);
return Ok(());
},
};
let mut sigmask = signal::SigSet::empty();
sigmask.add(signal::Signal::SIGTTOU);
sigmask.thread_block()?;
return match unistd::tcsetpgrp(tty.as_raw_fd(), pgrp) {
Ok(_) => Ok(()),
err @ Err(nix::Error::Sys(Errno::ENOTTY)) |
err @ Err(nix::Error::Sys(Errno::ENXIO)) => {
info!("failed to set process in foreground: {:?}", err);
Ok(())
}
Err(err) => Err(Error::from(err)),
};
}
fn main() {
let env = env_logger::Env::new().filter("INITRS_LOG")
.write_style("INITRS_LOG_STYLE");
env_logger::init_from_env(env);
let init_sigmask =
signal::SigSet::thread_get_mask().expect("could not get main thread sigmask");
let sigmask = signal::SigSet::all();
sigmask.thread_block().expect("could not block all signals");
let mut sfd =
signalfd::SignalFd::new(&sigmask).expect("could not create signalfd for all signals");
let options = clap::App::new("initrs")
.setting(clap::AppSettings::TrailingVarArg)
.author("Aleksa Sarai <asarai@suse.de>")
.version(crate_version!())
.about("Simple init for containers.")
.arg(clap::Arg::with_name("command")
.required(true)
.multiple(true))
.get_matches();
let args = options.values_of("command").unwrap().collect::<Vec<_>>();
let (cmd, args) = args.as_slice().split_first().unwrap();
let child = Command::new(cmd)
.args(args)
.before_exec(move || {
make_foreground()?;
init_sigmask.thread_set_mask()?;
return Ok(());
})
.spawn()
.expect("failed to start child process");
let pid1 = child.id() as pid_t;
debug!("spawned '{}' as pid1 with pid {}", cmd, pid1);
loop {
match process_signals(pid1, &mut sfd) {
Err(err) => info!("unexpected error during signal handling: {}", err),
Ok(pids) => {
if pids.contains(&pid1) {
break;
}
}
};
}
debug!("bailing: pid1 {} has exited", pid1);
}