diff --git a/ofborg/src/nix.rs b/ofborg/src/nix.rs index dbb21c2..5692a28 100644 --- a/ofborg/src/nix.rs +++ b/ofborg/src/nix.rs @@ -31,6 +31,12 @@ impl Nix { } pub fn safely_build_attrs(&self, nixpkgs: &Path, file: &str, attrs: Vec) -> Result { + let cmd = self.safely_build_attrs_cmd(nixpkgs, file, attrs); + + return self.run(cmd, true); + } + + pub fn safely_build_attrs_cmd(&self, nixpkgs: &Path, file: &str, attrs: Vec) -> Command { let mut attrargs: Vec = Vec::with_capacity(3 + (attrs.len() * 2)); attrargs.push(file.to_owned()); attrargs.push(String::from("--no-out-link")); @@ -40,14 +46,14 @@ impl Nix { attrargs.push(attr); } - return self.safely("nix-build", nixpkgs, attrargs, true); + return self.safe_command("nix-build", nixpkgs, attrargs); } pub fn safely(&self, cmd: &str, nixpkgs: &Path, args: Vec, keep_stdout: bool) -> Result { - let mut nixpath = OsString::new(); - nixpath.push("nixpkgs="); - nixpath.push(nixpkgs.as_os_str()); + return self.run(self.safe_command(cmd, nixpkgs, args), keep_stdout); + } + pub fn run(&self, mut cmd: Command, keep_stdout: bool) -> Result { let stderr = tempfile().expect("Fetching a stderr tempfile"); let mut reader = stderr.try_clone().expect("Cloning stderr to the reader"); @@ -60,22 +66,11 @@ impl Nix { stdout = Stdio::null(); } - - let status = Command::new(cmd) - .env_clear() - .current_dir(nixpkgs) + let status = cmd .stdout(Stdio::from(stdout)) .stderr(Stdio::from(stderr)) - .env("HOME", "/homeless-shelter") - .env("NIX_PATH", nixpath) - .env("NIX_REMOTE", &self.remote) - .args(&["--show-trace"]) - .args(&["--option", "restrict-eval", "true"]) - .args(&["--option", "build-timeout", &format!("{}", self.build_timeout)]) - .args(&["--argstr", "system", &self.system]) - .args(args) .status() - .expect(format!("Running {:?}", cmd).as_ref()); + .expect(format!("Running a program ...").as_ref()); reader.seek(SeekFrom::Start(0)).expect("Seeking to Start(0)"); @@ -85,4 +80,217 @@ impl Nix { return Err(reader) } } + + pub fn safe_command(&self, cmd: &str, nixpkgs: &Path, args: Vec) -> Command { + let mut nixpath = OsString::new(); + nixpath.push("nixpkgs="); + nixpath.push(nixpkgs.as_os_str()); + + let mut command = Command::new(cmd); + command.env_clear(); + command.current_dir(nixpkgs); + command.env("HOME", "/homeless-shelter"); + command.env("NIX_PATH", nixpath); + command.env("NIX_REMOTE", &self.remote); + command.args(&["--show-trace"]); + command.args(&["--option", "restrict-eval", "true"]); + command.args(&["--option", "build-timeout", &format!("{}", self.build_timeout)]); + command.args(&["--argstr", "system", &self.system]); + command.args(args); + + return command; + } + +} + +#[cfg(test)] +mod tests { + fn nix() -> Nix { + Nix::new("x86_64-linux".to_owned(), "daemon".to_owned(), 1800) + } + + fn build_path() -> PathBuf { + let mut cwd = env::current_dir().unwrap(); + cwd.push(Path::new("./test-srcs/build")); + return cwd; + } + + fn passing_eval_path() -> PathBuf { + let mut cwd = env::current_dir().unwrap(); + cwd.push(Path::new("./test-srcs/eval")); + return cwd; + } + + #[derive(Debug)] + enum Expect { + Pass, + Fail, + } + + fn assert_run(res: Result, expected: Expect, require: Vec<&str>) { + let expectation_held: bool = match expected { + Expect::Pass => res.is_ok(), + Expect::Fail => res.is_err(), + }; + + let file: File = match res { + Ok(file) => file, + Err(file) => file, + }; + + let lines: Vec = BufReader::new(file) + .lines() + .into_iter() + .filter(|line| line.is_ok()) + .map(|line| line.unwrap()) + .collect(); + + let buildlog = lines + .into_iter() + .map(|line| format!(" | {}", line)) + .collect::>() + .join("\n"); + + let total_requirements = require.len(); + let mut missed_requirements: usize = 0; + let requirements_held: Vec> = + require.into_iter() + .map(|line| line.to_owned()) + .map(|line| + if buildlog.contains(&line) { + Ok(line) + } else { + missed_requirements += 1; + Err(line) + } + ) + .collect(); + + let mut prefixes: Vec = vec![ + "".to_owned(), + "".to_owned(), + ]; + + if !expectation_held { + prefixes.push(format!( + "The run was expected to {:?}, but did not.", + expected + )); + prefixes.push("".to_owned()); + } else { + prefixes.push(format!( + "The run was expected to {:?}, and did.", + expected + )); + prefixes.push("".to_owned()); + } + + let mut suffixes = vec![ + "".to_owned(), + format!("{} out of {} required lines matched.", + (total_requirements - missed_requirements), + total_requirements + ), + "".to_owned(), + ]; + + for expected_line in requirements_held { + suffixes.push(format!(" - {:?}", expected_line)); + } + suffixes.push("".to_owned()); + + let output_blocks: Vec> = vec![ + prefixes, + vec![buildlog, "".to_owned()], + suffixes, + ]; + + let output_blocks_strings: Vec = + output_blocks + .into_iter() + .map(|lines| lines.join("\n")) + .collect(); + + let output: String = output_blocks_strings + .join("\n"); + + if expectation_held && missed_requirements == 0 { + } else { + panic!(output); + } + } + + use super::*; + use std::io::BufReader; + use std::io::BufRead; + use std::path::PathBuf; + use std::env; + + #[test] + fn safely_build_attrs_success() { + let nix = nix(); + + let ret: Result = nix.safely_build_attrs( + build_path().as_path(), + "default.nix", + vec![String::from("success")] + ); + + assert_run(ret, Expect::Pass, vec![ + "-success.drv", + "building path(s)", + "hi", + "-success" + ]); + } + + #[test] + fn safely_build_attrs_failure() { + let nix = nix(); + + let ret: Result = nix.safely_build_attrs( + build_path().as_path(), + "default.nix", + vec![String::from("failed")] + ); + + assert_run(ret, Expect::Fail, vec![ + "-failed.drv", + "building path(s)", + "hi", + "failed to produce output path" + ]); + } + + #[test] + fn strict_sandboxing() { + let ret: Result = nix().safely_build_attrs( + build_path().as_path(), + "default.nix", + vec![String::from("sandbox-violation")] + ); + + assert_run(ret, Expect::Fail, vec![ + "error: while evaluating the attribute", + "access to path", + "is forbidden in restricted mode" + ]); + } + + + #[test] + fn instantiation() { + let ret: Result = nix().safely( + "nix-instantiate", + passing_eval_path().as_path(), + vec![], + true + ); + + assert_run(ret, Expect::Pass, vec![ + "the result might be removed by the garbage collector", + "-failed.drv", + "-success.drv" + ]); + } } diff --git a/ofborg/test-srcs/build/default.nix b/ofborg/test-srcs/build/default.nix new file mode 100644 index 0000000..840aa12 --- /dev/null +++ b/ofborg/test-srcs/build/default.nix @@ -0,0 +1,27 @@ +let + nix = import ; +in { + success = derivation { + name = "success"; + system = builtins.currentSystem; + builder = nix.shell; + args = [ + "-c" + "echo hi; echo ${toString builtins.currentTime} > $out" ]; + }; + + failed = derivation { + name = "failed"; + system = builtins.currentSystem; + builder = nix.shell; + args = [ + "-c" + "echo hi; echo ${toString builtins.currentTime}" ]; + }; + + sandbox-violation = derivation { + name = "sandbox-violation"; + system = builtins.currentSystem; + builder = ./../../src; + }; +} diff --git a/ofborg/test-srcs/build/succeed.sh b/ofborg/test-srcs/build/succeed.sh new file mode 100644 index 0000000..7ec52d0 --- /dev/null +++ b/ofborg/test-srcs/build/succeed.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +echo "$@" +echo hi diff --git a/ofborg/test-srcs/eval/default.nix b/ofborg/test-srcs/eval/default.nix new file mode 100644 index 0000000..35981f7 --- /dev/null +++ b/ofborg/test-srcs/eval/default.nix @@ -0,0 +1,21 @@ +let + nix = import ; +in rec { + success = derivation { + name = "success"; + system = builtins.currentSystem; + builder = nix.shell; + args = [ + "-c" + "echo hi; echo ${toString builtins.currentTime} > $out" ]; + }; + + failed = derivation { + name = "failed"; + system = builtins.currentSystem; + builder = nix.shell; + args = [ + "-c" + "echo hi; echo ${toString builtins.currentTime}; echo ${success}" ]; + }; +}