use std::collections::{HashMap, HashSet};
use std::ffi::{OsStr, OsString};
use std::path::PathBuf;
use std::sync::{Arc, OnceLock};
use parking_lot::lock_api::ArcMutexGuard;
use parking_lot::{Mutex, RawMutex};
use tempfile::TempDir;
static MUTEX: OnceLock<Arc<Mutex<()>>> = OnceLock::new();
fn initialize_or_get_mutex<'a>() -> &'a Arc<Mutex<()>> {
MUTEX.get_or_init(|| Arc::new(Mutex::new(())))
}
pub struct Jailer {
temp_directory: Option<TempDir>,
original_directory: PathBuf,
_lock: ArcMutexGuard<RawMutex, ()>,
is_closed: bool,
}
impl Jailer {
pub fn new() -> Result<Self, std::io::Error> {
let lock = initialize_or_get_mutex().lock_arc();
let temp_dir = TempDir::new()?;
let original_directory = std::env::current_dir()?;
std::env::set_current_dir(&temp_dir)?;
Ok(Self {
temp_directory: Some(temp_dir),
original_directory,
_lock: lock,
is_closed: false,
})
}
#[must_use]
pub fn original_directory(&self) -> &PathBuf {
&self.original_directory
}
pub fn close(mut self) -> Result<(), std::io::Error> {
std::env::set_current_dir(self.original_directory.as_path())?;
if let Some(temp) = self.temp_directory.take() {
temp.close()?;
}
self.is_closed = true;
Ok(())
}
}
impl Drop for Jailer {
fn drop(&mut self) {
if !self.is_closed {
std::env::set_current_dir(self.original_directory.as_path()).ok();
if let Some(temp) = self.temp_directory.take() {
temp.close().ok();
}
}
}
}
pub struct EnvJailer {
jailer: Option<Jailer>,
original_directory: PathBuf,
original_env_vars_os: HashMap<OsString, OsString>,
preserved_env_vars_os: HashSet<OsString>,
}
impl EnvJailer {
pub fn new() -> Result<Self, std::io::Error> {
let original_env_vars_os = std::env::vars_os().collect();
let jailer = Jailer::new()?;
let original_dir = jailer.original_directory().clone();
Ok(Self {
jailer: Some(jailer),
original_directory: original_dir,
original_env_vars_os,
preserved_env_vars_os: HashSet::new(),
})
}
#[must_use]
pub fn original_directory(&self) -> &PathBuf {
&self.original_directory
}
pub fn set_preserved_env<K>(&mut self, key: K)
where
K: AsRef<OsStr>,
{
self.preserved_env_vars_os
.insert(key.as_ref().to_os_string());
}
pub fn remove_preserved_env<K>(&mut self, key: K)
where
K: AsRef<OsStr>,
{
self.preserved_env_vars_os.remove(key.as_ref());
}
#[must_use]
pub fn original_env_vars_os(&self) -> &HashMap<OsString, OsString> {
&self.original_env_vars_os
}
#[must_use]
pub fn preserved_env_vars_os(&self) -> &HashSet<OsString> {
&self.preserved_env_vars_os
}
unsafe fn revert_env_vars(&self) {
for key in std::env::vars_os().collect::<HashMap<_, _>>().keys() {
if !self.preserved_env_vars_os.contains(key) {
unsafe {
std::env::remove_var(key);
}
}
}
for (key, value) in &self.original_env_vars_os {
if !self.preserved_env_vars_os.contains(key) {
unsafe {
std::env::set_var(key, value);
}
}
}
}
pub unsafe fn close(mut self) -> Result<(), std::io::Error> {
unsafe {
self.revert_env_vars();
}
if let Some(jailer) = self.jailer.take() {
jailer.close()?;
}
Ok(())
}
}
impl Drop for EnvJailer {
fn drop(&mut self) {
if self.jailer.is_some() {
unsafe {
self.revert_env_vars();
}
}
}
}