summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock58
-rw-r--r--Cargo.toml3
-rw-r--r--src/executor.rs2
-rw-r--r--src/main.rs42
-rw-r--r--src/prepared_command.rs96
-rw-r--r--src/runner.rs2
-rw-r--r--src/runner/buildah.rs62
-rw-r--r--src/runner/runc.rs22
-rw-r--r--src/unshare.rs130
-rw-r--r--src/util.rs45
10 files changed, 394 insertions, 68 deletions
diff --git a/Cargo.lock b/Cargo.lock
index cd97c8e..5c5545a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,18 +1,63 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "cc"
+version = "1.0.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
name = "dtoa"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e"
[[package]]
+name = "libc"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff"
+
+[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "nix"
+version = "0.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2"
+dependencies = [
+ "bitflags",
+ "cc",
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
name = "proc-macro2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -34,9 +79,12 @@ dependencies = [
name = "rebel"
version = "0.1.0"
dependencies = [
+ "libc",
+ "nix",
"scopeguard",
"serde",
"serde_yaml",
+ "users",
"walkdir",
]
@@ -105,6 +153,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
+name = "users"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
+dependencies = [
+ "libc",
+ "log",
+]
+
+[[package]]
name = "walkdir"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 820a5fa..480d584 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,7 +7,10 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+libc = "0.2.84"
+nix = "0.19.1"
scopeguard = "1.1.0"
serde = { version = "1", features = ["derive"] }
serde_yaml = "0.8"
+users = "0.11.0"
walkdir = "2"
diff --git a/src/executor.rs b/src/executor.rs
index 2523162..814aa79 100644
--- a/src/executor.rs
+++ b/src/executor.rs
@@ -70,7 +70,7 @@ impl<'a> Executor<'a> {
}
pub fn run(&mut self) -> runner::Result<()> {
- let runner = runner::buildah::BuildahRunner::new(self.tasks);
+ let runner = runner::runc::RuncRunner::new(self.tasks);
while !self.tasks_runnable.is_empty() {
self.run_one(&runner)?;
}
diff --git a/src/main.rs b/src/main.rs
index 72178be..8d4787d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,12 +1,47 @@
mod executor;
+mod prepared_command;
mod recipe;
mod resolve;
mod runner;
mod types;
+mod unshare;
+mod util;
-use std::path::Path;
+use nix::{
+ mount::{self, MsFlags},
+ unistd,
+};
+use std::{io::Result, path::Path};
use types::*;
+use util::ToIOResult;
+
+fn mount_buildtmp() -> Result<()> {
+ mount::mount::<_, _, _, str>(
+ Some("buildtmp"),
+ "build/tmp",
+ Some("tmpfs"),
+ MsFlags::empty(),
+ None,
+ )
+ .to_io_result()
+}
+
+fn exec_shell() -> Result<std::convert::Infallible> {
+ let bin_sh = std::ffi::CString::new("/bin/sh").unwrap();
+ unistd::execv(&bin_sh, &[&bin_sh]).to_io_result()
+}
+
+fn execute(mut exc: executor::Executor) -> Result<()> {
+ unshare::unshare()?;
+ mount_buildtmp()?;
+
+ exc.run()?;
+
+ exec_shell()?;
+
+ Ok(())
+}
fn main() {
let recipes = recipe::read_recipes(Path::new("examples")).unwrap();
@@ -29,10 +64,9 @@ fn main() {
std::process::exit(1);
}
let taskset = rsv.to_taskset();
- let mut executor = executor::Executor::new(&tasks, taskset);
+ let exc = executor::Executor::new(&tasks, taskset);
- let result = executor.run();
- if let Err(error) = result {
+ if let Err(error) = execute(exc) {
eprintln!("{}", error);
std::process::exit(1);
}
diff --git a/src/prepared_command.rs b/src/prepared_command.rs
new file mode 100644
index 0000000..00d648e
--- /dev/null
+++ b/src/prepared_command.rs
@@ -0,0 +1,96 @@
+use std::{
+ ffi::{CString, OsStr},
+ io::{Error, ErrorKind, Result},
+ iter,
+ os::unix::{ffi::*, io::RawFd},
+ ptr,
+};
+
+use libc::{c_char, c_void};
+use nix::{fcntl::OFlag, sys::wait, unistd};
+
+use crate::util::ToIOResult;
+
+#[derive(Clone, Debug)]
+pub struct PreparedCommandBuilder {
+ program: CString,
+ args: Vec<CString>,
+}
+
+#[derive(Debug)]
+pub struct PreparedCommand {
+ child: unistd::Pid,
+ pipew: RawFd,
+}
+
+impl Drop for PreparedCommand {
+ fn drop(&mut self) {
+ let _ = unistd::close(self.pipew);
+ }
+}
+
+fn os2c<S: AsRef<OsStr>>(s: S) -> CString {
+ CString::new(s.as_ref().as_bytes()).unwrap()
+}
+
+impl PreparedCommandBuilder {
+ pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
+ self.args.push(os2c(arg));
+ self
+ }
+
+ pub fn prepare(&mut self) -> Result<PreparedCommand> {
+ let exe_p = self.program.as_ptr();
+
+ let argv: Vec<*const c_char> = iter::once(exe_p)
+ .chain(self.args.iter().map(|arg| arg.as_ptr()))
+ .chain(iter::once(ptr::null()))
+ .collect();
+
+ let argv_p = argv.as_ptr();
+
+ let (piper, pipew) = unistd::pipe2(OFlag::O_CLOEXEC).to_io_result()?;
+
+ unsafe {
+ match unistd::fork().to_io_result()? {
+ unistd::ForkResult::Parent { child } => {
+ return Ok(PreparedCommand { child, pipew });
+ }
+ unistd::ForkResult::Child => {}
+ }
+
+ // Child process - only async-signal-safe calls allowed
+
+ libc::close(pipew);
+
+ // Wait for run trigger
+ let mut buf = [0u8; 1];
+ if libc::read(piper, buf.as_mut_ptr() as *mut c_void, buf.len()) != 1 {
+ // PreparedCommand was dropped, or controlling process exited
+ libc::_exit(0);
+ }
+
+ libc::execvp(exe_p, argv_p);
+
+ // exec failed
+ libc::_exit(127);
+ }
+ }
+}
+
+impl PreparedCommand {
+ pub fn new<S: AsRef<OsStr>>(program: S) -> PreparedCommandBuilder {
+ PreparedCommandBuilder {
+ program: os2c(program),
+ args: Vec::new(),
+ }
+ }
+
+ pub fn run(self) -> Result<wait::WaitStatus> {
+ if unistd::write(self.pipew, &[0]).to_io_result()? != 1 {
+ return Err(Error::new(ErrorKind::Other, "command trigger write failed"));
+ }
+
+ wait::waitpid(Some(self.child), None).to_io_result()
+ }
+}
diff --git a/src/runner.rs b/src/runner.rs
index 7b94573..a290aeb 100644
--- a/src/runner.rs
+++ b/src/runner.rs
@@ -1,4 +1,4 @@
-pub mod buildah;
+pub mod runc;
use std::io;
diff --git a/src/runner/buildah.rs b/src/runner/buildah.rs
deleted file mode 100644
index 72a2e5f..0000000
--- a/src/runner/buildah.rs
+++ /dev/null
@@ -1,62 +0,0 @@
-use scopeguard::defer;
-use std::{
- io::{Error, ErrorKind},
- process::{Command, Stdio},
-};
-
-use super::*;
-use crate::types::*;
-
-static IMAGE: &str = "rebel:latest";
-
-pub struct BuildahRunner<'a> {
- tasks: &'a TaskMap,
-}
-
-impl<'a> BuildahRunner<'a> {
- pub fn new(tasks: &'a TaskMap) -> Self {
- BuildahRunner { tasks }
- }
-}
-
-fn check_status(output: &std::process::Output, message: &str) -> Result<()> {
- if output.status.success() {
- Ok(())
- } else {
- Err(Error::new(ErrorKind::Other, message))
- }
-}
-
-impl<'a> Runner for BuildahRunner<'a> {
- fn run(&self, task: &TaskRef) -> Result<()> {
- let task_def = self.tasks.get(task).expect("Invalid TaskRef");
-
- let buildah_from = Command::new("buildah")
- .arg("from")
- .arg("--name")
- .arg(task)
- .arg(IMAGE)
- .output()?;
- check_status(&buildah_from, "unable to create container")?;
-
- defer! {
- let _ = Command::new("buildah")
- .arg("rm")
- .arg(task)
- .stdout(Stdio::null())
- .output();
- }
-
- let buildah_run = Command::new("buildah")
- .arg("run")
- .arg(task)
- .arg("sh")
- .arg("-c")
- .arg(&task_def.run)
- .output()?;
-
- println!("{}:\n\t{:?}\n\t{:?}", task, task_def, buildah_run);
-
- Ok(())
- }
-}
diff --git a/src/runner/runc.rs b/src/runner/runc.rs
new file mode 100644
index 0000000..b323d5d
--- /dev/null
+++ b/src/runner/runc.rs
@@ -0,0 +1,22 @@
+use super::*;
+use crate::types::TaskMap;
+
+pub struct RuncRunner<'a> {
+ tasks: &'a TaskMap,
+}
+
+impl<'a> RuncRunner<'a> {
+ pub fn new(tasks: &'a TaskMap) -> Self {
+ RuncRunner { tasks }
+ }
+}
+
+impl<'a> Runner for RuncRunner<'a> {
+ fn run(&self, task: &TaskRef) -> Result<()> {
+ let task_def = self.tasks.get(task).expect("Invalid TaskRef");
+
+ println!("{}:\n\t{:?}", task, task_def);
+
+ Ok(())
+ }
+}
diff --git a/src/unshare.rs b/src/unshare.rs
new file mode 100644
index 0000000..2aa2b09
--- /dev/null
+++ b/src/unshare.rs
@@ -0,0 +1,130 @@
+use std::{
+ ffi::{OsStr, OsString},
+ fs::File,
+ io::{self, BufRead, Result},
+ os::unix::ffi::*,
+ path::Path,
+};
+
+use nix::{
+ sched::{self, CloneFlags},
+ unistd,
+};
+
+use crate::prepared_command::PreparedCommand;
+use crate::util::{Checkable, ToIOResult};
+
+#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
+struct SubIDRange {
+ start: libc::uid_t,
+ count: libc::uid_t,
+}
+
+fn parse_pid(s: &OsStr) -> Option<libc::uid_t> {
+ s.to_str().and_then(|s| s.parse::<libc::uid_t>().ok())
+}
+
+fn parse_id_range(line: Vec<u8>, uid: &OsStr, username: Option<&OsStr>) -> Option<SubIDRange> {
+ let parts: Vec<_> = line.split(|c| *c == b':').map(OsStr::from_bytes).collect();
+ if parts.len() != 3 {
+ return None;
+ }
+ if parts[0] != uid && Some(parts[0]) != username {
+ return None;
+ }
+
+ if let (Some(start), Some(count)) = (parse_pid(parts[1]), parse_pid(parts[2])) {
+ Some(SubIDRange { start, count })
+ } else {
+ None
+ }
+}
+
+fn read_id_ranges(filename: &Path) -> Result<Vec<SubIDRange>> {
+ let uid = users::get_effective_uid();
+ let username = users::get_user_by_uid(uid).map(|user| user.name().to_os_string());
+
+ let uidstr = OsString::from(uid.to_string());
+ let usernamestr = username.as_ref().map(OsString::as_os_str);
+
+ let file = File::open(filename)?;
+ let lines = io::BufReader::new(file).split(b'\n');
+
+ let mut ranges = Vec::new();
+
+ for line in lines {
+ if let Some(range) = parse_id_range(line?, &uidstr, usernamestr) {
+ ranges.push(range);
+ }
+ }
+
+ Ok(ranges)
+}
+
+#[derive(Debug)]
+struct SubIDMap {
+ lower: libc::uid_t,
+ upper: libc::uid_t,
+ count: libc::uid_t,
+}
+
+fn generate_idmap(id: libc::uid_t, mut ranges: Vec<SubIDRange>) -> Vec<SubIDMap> {
+ let mut map = Vec::new();
+
+ map.push(SubIDMap {
+ lower: 0,
+ upper: id,
+ count: 1,
+ });
+
+ let mut lower = 1;
+
+ ranges.sort();
+ for range in &ranges {
+ map.push(SubIDMap {
+ lower,
+ upper: range.start,
+ count: range.count,
+ });
+ lower += range.count;
+ }
+
+ map
+}
+
+fn get_uid_map() -> Result<Vec<SubIDMap>> {
+ let uid = users::get_effective_uid();
+ let uid_ranges = read_id_ranges(Path::new("/etc/subuid"))?;
+ Ok(generate_idmap(uid, uid_ranges))
+}
+
+fn get_gid_map() -> Result<Vec<SubIDMap>> {
+ let gid = users::get_effective_gid();
+ let gid_ranges = read_id_ranges(Path::new("/etc/subgid"))?;
+ Ok(generate_idmap(gid, gid_ranges))
+}
+
+fn prepare_idmap_cmd(cmd: &str, pid: &str, map: &Vec<SubIDMap>) -> Result<PreparedCommand> {
+ let mut builder = PreparedCommand::new(cmd);
+ builder.arg(&pid);
+ for uids in map {
+ builder.arg(uids.lower.to_string());
+ builder.arg(uids.upper.to_string());
+ builder.arg(uids.count.to_string());
+ }
+ builder.prepare()
+}
+
+pub fn unshare() -> Result<()> {
+ let pid = unistd::getpid().to_string();
+
+ let newuidmap = prepare_idmap_cmd("newuidmap", pid.as_str(), &get_uid_map()?)?;
+ let newgidmap = prepare_idmap_cmd("newgidmap", pid.as_str(), &get_gid_map()?)?;
+
+ sched::unshare(CloneFlags::CLONE_NEWUSER | CloneFlags::CLONE_NEWNS).to_io_result()?;
+
+ newuidmap.run()?.check()?;
+ newgidmap.run()?.check()?;
+
+ Ok(())
+}
diff --git a/src/util.rs b/src/util.rs
new file mode 100644
index 0000000..c18a263
--- /dev/null
+++ b/src/util.rs
@@ -0,0 +1,45 @@
+use std::{
+ io::{Error, ErrorKind, Result},
+ process::ExitStatus,
+};
+
+use nix::sys::wait;
+
+pub trait ToIOResult<T> {
+ fn to_io_result(self) -> Result<T>;
+}
+
+impl<T> ToIOResult<T> for nix::Result<T> {
+ fn to_io_result(self) -> Result<T> {
+ self.map_err(|error| Error::new(ErrorKind::Other, error))
+ }
+}
+
+pub trait Checkable {
+ fn check(&self) -> Result<()>;
+}
+
+impl Checkable for ExitStatus {
+ fn check(&self) -> Result<()> {
+ if self.success() {
+ Ok(())
+ } else {
+ Err(Error::new(
+ ErrorKind::Other,
+ format!("process exited with status {}", self),
+ ))
+ }
+ }
+}
+
+impl Checkable for wait::WaitStatus {
+ fn check(&self) -> Result<()> {
+ match self {
+ wait::WaitStatus::Exited(_, 0) => Ok(()),
+ _ => Err(Error::new(
+ ErrorKind::Other,
+ format!("process exited with status {:?}", self),
+ )),
+ }
+ }
+}