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) };
75 if child_ready < 0 {
76 return Err(io::Error::last_os_error());
77 }
78 let parent_done = unsafe { libc::eventfd(0, 0) };
79 if parent_done < 0 {
80 unsafe { libc::close(child_ready) };
81 return Err(io::Error::last_os_error());
82 }
83 Ok(Self {
84 child_ready: unsafe { OwnedFd::from_raw_fd(child_ready) },
85 parent_done: unsafe { OwnedFd::from_raw_fd(parent_done) },
86 })
87 }
88
89 #[inline]
90 pub fn child_ready_fd(&self) -> RawFd {
91 self.child_ready.as_raw_fd()
92 }
93
94 #[inline]
95 pub fn parent_done_fd(&self) -> RawFd {
96 self.parent_done.as_raw_fd()
97 }
98}
99
100#[derive(Debug)]
102pub struct Pipes {
103 pub stdin: Pipe,
104 pub stdout: Pipe,
105 pub stderr: Pipe,
106 pub sync: SyncPair,
107}
108
109impl Pipes {
110 pub fn new() -> io::Result<Self> {
111 Ok(Self {
112 stdin: Pipe::new()?,
113 stdout: Pipe::new()?,
114 stderr: Pipe::new()?,
115 sync: SyncPair::new()?,
116 })
117 }
118}
119
120#[derive(Debug)]
122pub struct Workspace {
123 root: PathBuf,
124 pub pipes: Pipes,
125 _tempdir: TempDir,
126}
127
128impl Workspace {
129 pub fn new() -> io::Result<Self> {
130 Self::with_prefix("evalbox-")
131 }
132
133 pub fn with_prefix(prefix: &str) -> io::Result<Self> {
134 let tempdir = TempDir::with_prefix(prefix)?;
135 Ok(Self {
136 root: tempdir.path().to_path_buf(),
137 pipes: Pipes::new()?,
138 _tempdir: tempdir,
139 })
140 }
141
142 #[inline]
143 pub fn root(&self) -> &Path {
144 &self.root
145 }
146
147 pub fn write_file(&self, path: &str, content: &[u8], executable: bool) -> io::Result<PathBuf> {
148 use std::os::unix::fs::PermissionsExt;
149
150 let full = self.root.join(path);
151 if let Some(parent) = full.parent() {
152 fs::create_dir_all(parent)?;
153 }
154 fs::write(&full, content)?;
155
156 if executable {
157 fs::set_permissions(&full, fs::Permissions::from_mode(0o755))?;
158 }
159
160 Ok(full)
161 }
162
163 pub fn create_dir(&self, path: &str) -> io::Result<PathBuf> {
164 let full = self.root.join(path);
165 fs::create_dir_all(&full)?;
166 Ok(full)
167 }
168
169 pub fn setup_sandbox_dirs(&self) -> io::Result<()> {
174 for dir in ["work", "tmp", "home"] {
175 self.create_dir(dir)?;
176 }
177 Ok(())
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn pipe_creation() {
187 let pipe = Pipe::new().unwrap();
188 assert!(pipe.read_fd() >= 0);
189 assert_ne!(pipe.read_fd(), pipe.write_fd());
190 }
191
192 #[test]
193 fn workspace_creation() {
194 let ws = Workspace::new().unwrap();
195 assert!(ws.root().exists());
196 }
197
198 #[test]
199 fn workspace_write_file() {
200 let ws = Workspace::new().unwrap();
201 let path = ws.write_file("test.txt", b"hello", false).unwrap();
202 assert!(path.exists());
203 }
204
205 #[test]
206 fn workspace_write_executable() {
207 use std::os::unix::fs::PermissionsExt;
208
209 let ws = Workspace::new().unwrap();
210 let path = ws.write_file("binary", b"\x7fELF", true).unwrap();
211 assert!(path.exists());
212 let perms = std::fs::metadata(&path).unwrap().permissions();
213 assert_eq!(perms.mode() & 0o777, 0o755);
214 }
215
216 #[test]
217 fn workspace_sandbox_dirs() {
218 let ws = Workspace::new().unwrap();
219 ws.setup_sandbox_dirs().unwrap();
220 assert!(ws.root().join("work").exists());
221 assert!(ws.root().join("tmp").exists());
222 assert!(ws.root().join("home").exists());
223 }
224}