evalbox_sandbox/
workspace.rs1use std::fs;
23use std::io;
24use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
25use std::path::{Path, PathBuf};
26
27use tempfile::TempDir;
28
29#[derive(Debug)]
31pub struct Pipe {
32 pub read: OwnedFd,
33 pub write: OwnedFd,
34}
35
36impl Pipe {
37 pub fn new() -> io::Result<Self> {
38 let mut fds = [0i32; 2];
39 if unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC) } != 0 {
41 return Err(io::Error::last_os_error());
42 }
43 Ok(Self {
45 read: unsafe { OwnedFd::from_raw_fd(fds[0]) },
46 write: unsafe { OwnedFd::from_raw_fd(fds[1]) },
47 })
48 }
49
50 #[inline]
51 pub fn read_fd(&self) -> RawFd {
52 self.read.as_raw_fd()
53 }
54
55 #[inline]
56 pub fn write_fd(&self) -> RawFd {
57 self.write.as_raw_fd()
58 }
59}
60
61#[derive(Debug)]
66pub struct SyncPair {
67 pub child_ready: OwnedFd,
68 pub parent_done: OwnedFd,
69}
70
71impl SyncPair {
72 pub fn new() -> io::Result<Self> {
73 let child_ready = unsafe { libc::eventfd(0, 0) };
74 if child_ready < 0 {
75 return Err(io::Error::last_os_error());
76 }
77 let parent_done = unsafe { libc::eventfd(0, 0) };
78 if parent_done < 0 {
79 unsafe { libc::close(child_ready) };
80 return Err(io::Error::last_os_error());
81 }
82 Ok(Self {
83 child_ready: unsafe { OwnedFd::from_raw_fd(child_ready) },
84 parent_done: unsafe { OwnedFd::from_raw_fd(parent_done) },
85 })
86 }
87
88 #[inline]
89 pub fn child_ready_fd(&self) -> RawFd {
90 self.child_ready.as_raw_fd()
91 }
92
93 #[inline]
94 pub fn parent_done_fd(&self) -> RawFd {
95 self.parent_done.as_raw_fd()
96 }
97}
98
99#[derive(Debug)]
101pub struct Pipes {
102 pub stdin: Pipe,
103 pub stdout: Pipe,
104 pub stderr: Pipe,
105 pub sync: SyncPair,
106}
107
108impl Pipes {
109 pub fn new() -> io::Result<Self> {
110 Ok(Self {
111 stdin: Pipe::new()?,
112 stdout: Pipe::new()?,
113 stderr: Pipe::new()?,
114 sync: SyncPair::new()?,
115 })
116 }
117}
118
119#[derive(Debug)]
121pub struct Workspace {
122 root: PathBuf,
123 pub pipes: Pipes,
124 _tempdir: TempDir,
125}
126
127impl Workspace {
128 pub fn new() -> io::Result<Self> {
129 Self::with_prefix("evalbox-")
130 }
131
132 pub fn with_prefix(prefix: &str) -> io::Result<Self> {
133 let tempdir = TempDir::with_prefix(prefix)?;
134 Ok(Self {
135 root: tempdir.path().to_path_buf(),
136 pipes: Pipes::new()?,
137 _tempdir: tempdir,
138 })
139 }
140
141 #[inline]
142 pub fn root(&self) -> &Path {
143 &self.root
144 }
145
146 pub fn write_file(&self, path: &str, content: &[u8], executable: bool) -> io::Result<PathBuf> {
147 use std::os::unix::fs::PermissionsExt;
148
149 let full = self.root.join(path);
150 if let Some(parent) = full.parent() {
151 fs::create_dir_all(parent)?;
152 }
153 fs::write(&full, content)?;
154
155 if executable {
156 fs::set_permissions(&full, fs::Permissions::from_mode(0o755))?;
157 }
158
159 Ok(full)
160 }
161
162 pub fn create_dir(&self, path: &str) -> io::Result<PathBuf> {
163 let full = self.root.join(path);
164 fs::create_dir_all(&full)?;
165 Ok(full)
166 }
167
168 pub fn setup_sandbox_dirs(&self) -> io::Result<()> {
173 for dir in ["work", "tmp", "home"] {
174 self.create_dir(dir)?;
175 }
176 Ok(())
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn pipe_creation() {
186 let pipe = Pipe::new().unwrap();
187 assert!(pipe.read_fd() >= 0);
188 assert_ne!(pipe.read_fd(), pipe.write_fd());
189 }
190
191 #[test]
192 fn workspace_creation() {
193 let ws = Workspace::new().unwrap();
194 assert!(ws.root().exists());
195 }
196
197 #[test]
198 fn workspace_write_file() {
199 let ws = Workspace::new().unwrap();
200 let path = ws.write_file("test.txt", b"hello", false).unwrap();
201 assert!(path.exists());
202 }
203
204 #[test]
205 fn workspace_write_executable() {
206 use std::os::unix::fs::PermissionsExt;
207
208 let ws = Workspace::new().unwrap();
209 let path = ws.write_file("binary", b"\x7fELF", true).unwrap();
210 assert!(path.exists());
211 let perms = std::fs::metadata(&path).unwrap().permissions();
212 assert_eq!(perms.mode() & 0o777, 0o755);
213 }
214
215 #[test]
216 fn workspace_sandbox_dirs() {
217 let ws = Workspace::new().unwrap();
218 ws.setup_sandbox_dirs().unwrap();
219 assert!(ws.root().join("work").exists());
220 assert!(ws.root().join("tmp").exists());
221 assert!(ws.root().join("home").exists());
222 }
223}