jailer/lib.rs
1//! Crate for creating sandbox environments to perform actions in isolation.
2//!
3//! This crate provides two main types:
4//! - [`Jailer`]: A simple sandbox that changes the current working directory
5//! into a temporary one. When dropped or closed, it restores the original
6//! directory and cleans up the temporary space.
7//! - [`EnvJailer`]: Extends [`Jailer`] by also managing environment variables,
8//! allowing preservation of selected variables while clearing others on exit.
9//!
10//! Both types are thread-safe and ensure only one instance runs at a time via
11//! a global mutex.
12
13use std::collections::{HashMap, HashSet};
14use std::ffi::{OsStr, OsString};
15use std::path::PathBuf;
16use std::sync::{Arc, OnceLock};
17
18use parking_lot::lock_api::ArcMutexGuard;
19use parking_lot::{Mutex, RawMutex};
20use tempfile::TempDir;
21
22/// Static mutex which is wrapped around once lock to be thread safe
23static MUTEX: OnceLock<Arc<Mutex<()>>> = OnceLock::new();
24
25/// Initialize or get mutex
26fn initialize_or_get_mutex<'a>() -> &'a Arc<Mutex<()>> {
27 MUTEX.get_or_init(|| Arc::new(Mutex::new(())))
28}
29
30/// [`Jailer`] struct which creates a jail environment.
31///
32/// [`Jailer`] creates a temporary directory and changes the current working
33/// directory to it. On drop or manual close, it restores the original working
34/// directory and deletes the temporary directory.
35///
36/// It uses a global mutex to ensure only one `Jailer` is active at a time
37/// across threads.
38pub struct Jailer {
39 temp_directory: Option<TempDir>,
40 original_directory: PathBuf,
41 _lock: ArcMutexGuard<RawMutex, ()>,
42 is_closed: bool,
43}
44
45impl Jailer {
46 /// Create a new [`Jailer`].
47 ///
48 /// This will:
49 /// - Lock globally to prevent concurrent instances.
50 /// - Create a temporary directory.
51 /// - Change the current directory to that temp dir.
52 ///
53 /// # Errors
54 ///
55 /// Returns an error if:
56 /// - The temporary directory cannot be created.
57 /// - Changing the current directory fails.
58 ///
59 /// # Example
60 ///
61 /// ```rust
62 /// use jailer::Jailer;
63 ///
64 /// // Capture the original working directory
65 /// let original_directory = std::env::current_dir().unwrap();
66 ///
67 /// let mut jailer = Jailer::new().unwrap();
68 ///
69 /// // We're now inside the jail
70 /// let inside_jailer_directory = std::env::current_dir().unwrap();
71 /// assert_ne!(inside_jailer_directory, original_directory);
72 ///
73 /// // Do some action in jailer...
74 ///
75 /// // Close the jail explicitly
76 /// jailer.close().unwrap();
77 ///
78 /// // Back to original directory
79 /// let after_jailer_directory = std::env::current_dir().unwrap();
80 /// assert_eq!(after_jailer_directory, original_directory);
81 /// assert_ne!(inside_jailer_directory, after_jailer_directory);
82 /// ```
83 pub fn new() -> Result<Self, std::io::Error> {
84 let lock = initialize_or_get_mutex().lock_arc();
85 let temp_dir = TempDir::new()?;
86 let original_directory = std::env::current_dir()?;
87 std::env::set_current_dir(&temp_dir)?;
88 Ok(Self {
89 temp_directory: Some(temp_dir),
90 original_directory,
91 _lock: lock,
92 is_closed: false,
93 })
94 }
95
96 /// Get a reference to the original directory
97 ///
98 /// This returns the directory that was active when the [`Jailer`] was
99 /// created
100 ///
101 /// # Example
102 ///
103 /// ```rust
104 /// use jailer::Jailer;
105 ///
106 /// let original_directory = std::env::current_dir().unwrap();
107 /// let jailer = Jailer::new().unwrap();
108 ///
109 /// assert_eq!(jailer.original_directory(), &original_directory);
110 /// jailer.close().unwrap();
111 /// ```
112 #[must_use]
113 pub fn original_directory(&self) -> &PathBuf {
114 &self.original_directory
115 }
116
117 /// Closes the [`Jailer`] and performs cleanup.
118 ///
119 /// This method:
120 /// - Changes back to the original working directory.
121 /// - Deletes the temporary directory.
122 /// - Releases the global lock.
123 ///
124 /// It consumes `self`, so the jailer cannot be used afterward.
125 ///
126 /// # Errors
127 ///
128 /// Returns an error if changing the directory or deleting the temp dir
129 /// fails.
130 pub fn close(mut self) -> Result<(), std::io::Error> {
131 std::env::set_current_dir(self.original_directory.as_path())?;
132 if let Some(temp) = self.temp_directory.take() {
133 temp.close()?;
134 }
135 self.is_closed = true;
136 Ok(())
137 }
138}
139
140impl Drop for Jailer {
141 fn drop(&mut self) {
142 if !self.is_closed {
143 std::env::set_current_dir(self.original_directory.as_path()).ok();
144 if let Some(temp) = self.temp_directory.take() {
145 temp.close().ok();
146 }
147 }
148 }
149}
150
151/// [`EnvJailer`] struct which creates a jail environment with environment
152/// variable management.
153///
154/// [`EnvJailer`] wraps [`Jailer`] and adds support for preserving specific
155/// environment variables. On drop or close, it:
156/// - Removes all environment variables not marked as preserved.
157/// - Restores original values for preserved keys.
158/// - Reverts to the original working directory and cleans up the temp dir.
159///
160/// # Safety
161///
162/// Environment variable manipulation via [`std::env::set_var`] and
163/// [`std::env::remove_var`] is considered unsafe due to potential race
164/// conditions in multi-threaded programs. Therefore, methods like
165/// [`EnvJailer::close`] are marked as `unsafe`.
166pub struct EnvJailer {
167 jailer: Option<Jailer>,
168 original_directory: PathBuf,
169 original_env_vars_os: HashMap<OsString, OsString>,
170 preserved_env_vars_os: HashSet<OsString>,
171}
172
173impl EnvJailer {
174 /// Create a new [`EnvJailer`].
175 ///
176 /// This captures the current environment variables and working directory,
177 /// then initializes a new [`Jailer`].
178 ///
179 /// # Errors
180 ///
181 /// Returns an error if the underlying [`Jailer`] cannot be created.
182 ///
183 /// # Example
184 ///
185 /// ```rust
186 /// use jailer::EnvJailer;
187 ///
188 /// // Capture the original working directory
189 /// let original_directory = std::env::current_dir().unwrap();
190 ///
191 /// let mut env_jailer = EnvJailer::new().unwrap();
192 ///
193 /// // Do some action in jailer...
194 ///
195 /// // Close the jail explicitly (unsafe due to env var changes)
196 /// unsafe {
197 /// env_jailer.close().unwrap();
198 /// }
199 ///
200 /// // Back to original directory
201 /// assert_eq!(std::env::current_dir().unwrap(), original_directory);
202 /// ```
203 pub fn new() -> Result<Self, std::io::Error> {
204 let original_env_vars_os = std::env::vars_os().collect();
205 let jailer = Jailer::new()?;
206 let original_dir = jailer.original_directory().clone();
207
208 Ok(Self {
209 jailer: Some(jailer),
210 original_directory: original_dir,
211 original_env_vars_os,
212 preserved_env_vars_os: HashSet::new(),
213 })
214 }
215
216 /// Get a reference to the original directory
217 ///
218 /// This returns the directory that was active when the [`EnvJailer`] was
219 /// created
220 ///
221 /// # Example
222 ///
223 /// ```rust
224 /// use jailer::EnvJailer;
225 ///
226 /// let original_directory = std::env::current_dir().unwrap();
227 /// let env_jailer = EnvJailer::new().unwrap();
228 /// assert_eq!(env_jailer.original_directory(), &original_directory);
229 /// unsafe {
230 /// env_jailer.close().unwrap();
231 /// }
232 /// ```
233 #[must_use]
234 pub fn original_directory(&self) -> &PathBuf {
235 &self.original_directory
236 }
237
238 /// Mark an environment variable as preserved.
239 ///
240 /// When the jailer is closed or dropped, this key will retain its current
241 /// value instead of being removed or reset to the original value.
242 ///
243 /// # Example
244 ///
245 /// ```rust
246 /// use jailer::EnvJailer;
247 ///
248 /// unsafe {
249 /// std::env::set_var("KEY", "VALUE");
250 /// std::env::set_var("ANOTHER_KEY", "VALUE");
251 /// }
252 ///
253 /// let mut env_jailer = EnvJailer::new().unwrap();
254 ///
255 /// assert_eq!(std::env::var("KEY"), Ok("VALUE".to_string()));
256 ///
257 /// unsafe {
258 /// std::env::set_var("KEY2", "VALUE2");
259 /// }
260 /// env_jailer.set_preserved_env("KEY");
261 ///
262 /// unsafe {
263 /// std::env::set_var("KEY", "VALUE2");
264 /// std::env::set_var("ANOTHER_KEY", "ANOTHER_VAL");
265 /// }
266 ///
267 /// assert_eq!(std::env::var("KEY"), Ok("VALUE2".to_string()));
268 /// assert_eq!(std::env::var("ANOTHER_KEY"), Ok("ANOTHER_VAL".to_string()));
269 ///
270 /// unsafe {
271 /// env_jailer.close().unwrap();
272 /// }
273 ///
274 /// assert_eq!(std::env::var("KEY"), Ok("VALUE2".to_string()));
275 /// assert_eq!(std::env::var("ANOTHER_KEY"), Ok("VALUE".to_string()));
276 /// assert!(std::env::var("KEY2").is_err());
277 /// ```
278 pub fn set_preserved_env<K>(&mut self, key: K)
279 where
280 K: AsRef<OsStr>,
281 {
282 self.preserved_env_vars_os
283 .insert(key.as_ref().to_os_string());
284 }
285
286 /// Remove an environment variable from the preserved list.
287 ///
288 /// Note: This does *not* remove the current environment variable.
289 /// To remove it, call [`std::env::remove_var`] manually.
290 ///
291 /// # Example
292 ///
293 /// ```rust
294 /// use jailer::EnvJailer;
295 ///
296 /// let mut env_jailer = EnvJailer::new().unwrap();
297 ///
298 /// unsafe {
299 /// std::env::set_var("KEY", "VALUE");
300 /// }
301 ///
302 /// env_jailer.set_preserved_env("KEY");
303 /// assert_eq!(std::env::var("KEY"), Ok("VALUE".to_string()));
304 /// env_jailer.remove_preserved_env("KEY");
305 ///
306 /// unsafe {
307 /// env_jailer.close().unwrap();
308 /// }
309 ///
310 /// assert!(std::env::var("KEY").is_err());
311 /// ```
312 pub fn remove_preserved_env<K>(&mut self, key: K)
313 where
314 K: AsRef<OsStr>,
315 {
316 self.preserved_env_vars_os.remove(key.as_ref());
317 }
318
319 /// Returns a reference to the map of original environment variables.
320 ///
321 /// These are the environment variables present when the [`EnvJailer`] was
322 /// created. Any variables added during the session are not included
323 /// here.
324 #[must_use]
325 pub fn original_env_vars_os(&self) -> &HashMap<OsString, OsString> {
326 &self.original_env_vars_os
327 }
328
329 /// Returns a reference to the set of preserved environment variable names.
330 #[must_use]
331 pub fn preserved_env_vars_os(&self) -> &HashSet<OsString> {
332 &self.preserved_env_vars_os
333 }
334
335 unsafe fn revert_env_vars(&self) {
336 for key in std::env::vars_os().collect::<HashMap<_, _>>().keys() {
337 if !self.preserved_env_vars_os.contains(key) {
338 unsafe {
339 std::env::remove_var(key);
340 }
341 }
342 }
343 for (key, value) in &self.original_env_vars_os {
344 if !self.preserved_env_vars_os.contains(key) {
345 unsafe {
346 std::env::set_var(key, value);
347 }
348 }
349 }
350 }
351
352 /// Closes the [`EnvJailer`] and performs cleanup.
353 ///
354 /// This method:
355 /// - Reverts environment variables to their original state (except
356 /// preserved ones).
357 /// - Closes the underlying [`Jailer`] (restoring directory and removing
358 /// temp dir).
359 ///
360 /// It consumes `self`, so the jailer cannot be used afterward.
361 ///
362 /// # Errors
363 ///
364 /// Returns an error if the underlying [`Jailer::close`] fails.
365 ///
366 /// # Safety
367 ///
368 /// This function calls [`std::env::remove_var`] and [`std::env::set_var`],
369 /// which are unsafe due to possible data races in concurrent contexts.
370 pub unsafe fn close(mut self) -> Result<(), std::io::Error> {
371 unsafe {
372 self.revert_env_vars();
373 }
374 if let Some(jailer) = self.jailer.take() {
375 jailer.close()?;
376 }
377 Ok(())
378 }
379}
380
381impl Drop for EnvJailer {
382 fn drop(&mut self) {
383 if self.jailer.is_some() {
384 unsafe {
385 self.revert_env_vars();
386 }
387 }
388 }
389}