cromwell 0.1.3

Rust Rootless Container Runtime
use std::ffi::CString;
use std::fs;

use nix::sched::{unshare, CloneFlags};
use nix::sys::wait::{waitpid, WaitStatus};
use nix::unistd::{chdir, chroot, fork, ForkResult};
use nix::unistd::{execve, sethostname};

use super::image::Image;
use super::mounts;

pub struct Container {
    pub name: String,
    pub command: String,
    pub image: Image,
}

impl Container {
    pub fn new(name: &str, command: String) -> Container {
        Container {
            name: name.to_string(),
            command,
            image: Image::new(name),
        }
    }

    pub fn prepare(&mut self) {
        self.image.pull().expect("Failed to cromwell pull");

        println!("Started initialize Container!");
        let c_hosts = format!("{}/etc/hosts", self.image.get_full_path());
        let c_resolv = format!("{}/etc/resolv.conf", self.image.get_full_path());

        println!("[INFO] Copying /etc/hosts to {}", c_hosts);
        println!("[INFO] Copying /etc/resolv.conf {}", c_resolv);

        fs::copy("/etc/hosts", c_hosts).expect("Failed copy /etc/hosts: ");
        fs::copy("/etc/resolv.conf", c_resolv).expect("Failed copy /etc/resolv.conf: ");

        unshare(
            CloneFlags::CLONE_NEWPID
                | CloneFlags::CLONE_NEWUTS
                | CloneFlags::CLONE_NEWNS
                | CloneFlags::CLONE_NEWUSER,
        )
        .expect("Can not unshare(2).");

        chroot(self.image.get_full_path().as_str()).expect("chroot failed.");
        chdir("/").expect("cd / failed.");
    }

    pub fn run(&self) {
        match fork() {
            Ok(ForkResult::Parent { child, .. }) => {
                println!("[INFO] container pid: {}", child);

                match waitpid(child, None).expect("waitpid faild") {
                    WaitStatus::Exited(_, _) => {}
                    WaitStatus::Signaled(_, _, _) => {}
                    _ => eprintln!("Unexpected exit."),
                }
            }
            Ok(ForkResult::Child) => {
                sethostname(&self.name).expect("Could not set hostname");

                fs::create_dir_all("proc").unwrap_or_else(|why| {
                    eprintln!("{:?}", why.kind());
                });

                println!("[INFO] 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").unwrap();

                execve(
                    &default_shell,
                    &[default_shell.clone(), shell_opt, cmd],
                    &[lang, path],
                )
                .expect("execution faild.");
            }
            Err(_) => eprintln!("[ERROR] Fork failed"),
        }
    }

    pub fn delete(&self) -> std::io::Result<()> {
        fs::remove_dir_all(&self.image.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".to_string();
        let container = Container::new(image_name, command.clone());
        assert_eq!(container.command, command);
    }
}