sandbox.rs
1 pub mod isolation; 2 3 use crate::{root::Root, sandbox::isolation::IsolationPathAction}; 4 use isolation::{IsolatationConfig, IsolationPath}; 5 use std::{ 6 collections::{BTreeMap, VecDeque}, 7 ffi::OsString, 8 fs::File, 9 io, 10 path::Path, 11 process::{ExitStatus, Stdio}, 12 }; 13 14 #[derive(Debug, Hash)] 15 pub struct SandboxCommand { 16 config: IsolatationConfig, 17 program: OsString, 18 args: VecDeque<OsString>, 19 envs: BTreeMap<OsString, OsString>, 20 stdout: Option<Box<Path>>, 21 stderr: Option<Box<Path>>, 22 } 23 24 impl SandboxCommand { 25 pub fn new(program: impl Into<OsString>) -> Self { 26 Self { 27 config: IsolatationConfig::new(), 28 program: program.into(), 29 args: VecDeque::new(), 30 envs: BTreeMap::new(), 31 stdout: None, 32 stderr: None, 33 } 34 } 35 36 pub fn allow_read_path(mut self, path: IsolationPath) -> Self { 37 self.config 38 .path_rules 39 .push((path, IsolationPathAction::AllowRead)); 40 self 41 } 42 43 pub fn allow_path(mut self, path: IsolationPath) -> Self { 44 self.config 45 .path_rules 46 .push((path, IsolationPathAction::Allow)); 47 self 48 } 49 50 pub fn deny_path(mut self, path: IsolationPath) -> Self { 51 self.config 52 .path_rules 53 .push((path, IsolationPathAction::Deny)); 54 self 55 } 56 57 pub fn arg(mut self, arg: impl Into<OsString>) -> Self { 58 self.args.push_back(arg.into()); 59 self 60 } 61 62 pub fn args<As, A>(mut self, args: As) -> Self 63 where 64 As: IntoIterator<Item = A>, 65 A: Into<OsString>, 66 { 67 for arg in args { 68 self.args.push_back(arg.into()); 69 } 70 self 71 } 72 73 pub fn env<K, V>(mut self, name: K, value: V) -> Self 74 where 75 K: Into<OsString>, 76 V: Into<OsString>, 77 { 78 self.envs.insert(name.into(), value.into()); 79 self 80 } 81 82 pub fn pipe_stdout(mut self, path: impl AsRef<Path>) -> Self { 83 self.stdout = Some(path.as_ref().to_path_buf().into_boxed_path()); 84 self 85 } 86 87 pub fn pipe_stderr(mut self, path: impl AsRef<Path>) -> Self { 88 self.stderr = Some(path.as_ref().to_path_buf().into_boxed_path()); 89 self 90 } 91 92 pub fn run(self, root: &Root) -> io::Result<SandboxCommandStatus> { 93 let temp_dir = root.tmp_dir("sandbox-command", &(&self, root)); 94 95 let stdout_path = self 96 .stdout 97 .unwrap_or_else(|| temp_dir.join("stdout").into_boxed_path()); 98 let stdout = { 99 let file = File::create(&stdout_path) 100 .expect("Failed to create file for capturing standard output"); 101 Stdio::from(file) 102 }; 103 104 let stderr_path = self 105 .stderr 106 .unwrap_or_else(|| temp_dir.join("stderr").into_boxed_path()); 107 let stderr = { 108 let file = File::create(&stderr_path) 109 .expect("Failed to create file for capturing standard error"); 110 Stdio::from(file) 111 }; 112 113 let mut command = isolation::cmd(root, &self.config, &self.program); 114 command 115 .args(self.args) 116 .envs(self.envs) 117 .stdout(stdout) 118 .stderr(stderr) 119 .current_dir(root.path()); 120 121 let status = command.status().unwrap(); 122 123 if !status.success() { 124 temp_dir.keep(); 125 } 126 127 Ok(SandboxCommandStatus { 128 status, 129 stdout: stdout_path, 130 stderr: stderr_path, 131 }) 132 } 133 } 134 135 #[derive(Debug)] 136 pub struct SandboxCommandStatus { 137 pub status: ExitStatus, 138 pub stdout: Box<Path>, 139 pub stderr: Box<Path>, 140 }