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}