use std::ffi::CString;
use std::fs::{self, File};
use std::io::prelude::*;
use std::iter;
use std::path::Path;
use nix::sched::{unshare, CloneFlags};
use nix::sys::wait::{waitpid, WaitStatus};
use nix::unistd::{chdir, chroot, daemon, fork, getgid, getpid, getuid, ForkResult, Gid, Uid};
use nix::unistd::{execve, sethostname};
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use log::info;
use super::config::Config;
use super::image::Image;
use super::mounts;
use super::pids::Pidfile;
pub struct Container {
id: String,
command: String,
image: Option<Image>,
host_uid: Uid,
host_gid: Gid,
become_daemon: bool,
config: Config,
}
impl Container {
pub fn new(
image_name: Option<&str>,
command: &str,
path: Option<&str>,
become_daemon: bool,
) -> Container {
let mut rng = thread_rng();
if let Some(id) = path {
return Container {
id: id.to_string(),
command: command.to_string(),
image: None,
host_uid: getuid(),
host_gid: getgid(),
become_daemon,
config: Config::new(None),
};
}
let id: String = iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.take(16)
.collect();
Container {
id,
command: command.to_string(),
image: Some(Image::new(image_name.unwrap())),
host_uid: getuid(),
host_gid: getgid(),
become_daemon,
config: Config::new(None),
}
}
fn uid_map(&self) -> std::io::Result<()> {
let mut uid_map_file = File::create("/proc/self/uid_map")?;
let uid_map = format!("0 {} 1", self.host_uid);
uid_map_file.write_all(uid_map.as_bytes())?;
info!("[Host] wrote {} /proc/self/uid_map", uid_map);
Ok(())
}
fn gid_map(&self) -> std::io::Result<()> {
let mut setgroups_file = File::create("/proc/self/setgroups")?;
setgroups_file.write_all(b"deny")?;
let mut gid_map_file = File::create("/proc/self/gid_map")?;
info!("[Host] open(2) /proc/self/gid_map done.");
let gid_map = format!("0 {} 1", self.host_gid);
gid_map_file.write_all(gid_map.as_bytes())?;
info!("[Host] wrote {} /proc/self/gid_map", gid_map);
Ok(())
}
fn guid_map(&self) -> std::io::Result<()> {
self.uid_map().expect("Failed to write uid_map");
self.gid_map().expect("Failed to write gid_map");
Ok(())
}
fn get_full_path(&self) -> String {
format!("{}/{}", self.config.container_path, self.id)
}
pub fn prepare(&mut self) {
if let Some(image) = &mut self.image {
image.pull(&self.id).expect("Failed to cromwell pull");
let c_hosts = format!("{}/etc/hosts", image.get_full_path(&self.id));
let c_resolv = format!("{}/etc/resolv.conf", image.get_full_path(&self.id));
fs::copy("/etc/hosts", &c_hosts).expect("Failed copy /etc/hosts");
info!("[Host] Copied /etc/hosts to {}", c_hosts);
fs::copy("/etc/resolv.conf", &c_resolv).expect("Failed copy /etc/resolv.conf");
info!("[Host] Copied /etc/resolv.conf {}", c_resolv);
}
if self.become_daemon {
daemon(true, false).expect("cannot become daemon");
}
unshare(
CloneFlags::CLONE_NEWPID
| CloneFlags::CLONE_NEWUTS
| CloneFlags::CLONE_NEWNS
| CloneFlags::CLONE_NEWUSER,
)
.expect("Can not unshare(2).");
self.guid_map()
.expect("Failed to write /proc/self/gid_map|uid_map");
}
pub fn run(&self) {
match fork() {
Ok(ForkResult::Parent { child, .. }) => {
info!("[Host] PID: {}", getpid());
info!("[Container] PID: {}", child);
let pids_path = format!("{}/pids", self.config.base_dir);
fs::create_dir_all(&pids_path).expect("failed mkdir pids");
let pidfile_path = format!("{}/{}.pid", pids_path, self.id);
let pidfile_path = Path::new(&pidfile_path);
Pidfile::create(&pidfile_path, child).expect("Failed to create pidfile");
match waitpid(child, None).expect("waitpid faild") {
WaitStatus::Exited(_, _) => {
Pidfile::delete(&pidfile_path).expect("Failed to remove pidfile");
}
WaitStatus::Signaled(_, _, _) => {}
_ => eprintln!("Unexpected exit."),
}
}
Ok(ForkResult::Child) => {
chroot(self.get_full_path().as_str()).expect("chroot failed.");
chdir("/").expect("cd / failed.");
sethostname(&self.id).expect("Could not set hostname");
fs::create_dir_all("proc").unwrap_or_else(|why| {
eprintln!("{:?}", why.kind());
});
info!("[Container] Mount procfs ... ");
mounts::mount_proc().expect("mount procfs failed");
let cmd = CString::new(self.command.clone()).unwrap();
let default_shell = CString::new("/bin/sh").unwrap();
let shell_opt = CString::new("-c").unwrap();
let lang = CString::new("LC_ALL=C").unwrap();
let path =
CString::new("PATH=/bin/:/usr/bin/:/usr/local/bin:/sbin:/usr/sbin").unwrap();
execve(
&default_shell,
&[default_shell.clone(), shell_opt, cmd],
&[lang, path],
)
.expect("execution failed.");
}
Err(e) => panic!("Fork failed: {}", e),
}
}
pub fn delete(&self) -> std::io::Result<()> {
fs::remove_dir_all(&self.get_full_path())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_init_container() {
let image_name = "library/alpine:3.8";
let command = "/bin/bash";
let container = Container::new(Some(image_name), &command, None, false);
assert_eq!(container.command, command);
}
}