#![deny(missing_docs, unsafe_code, rust_2018_idioms)]
use std::{
io,
marker::PhantomData,
path::{Path, PathBuf},
sync::atomic::AtomicUsize,
};
use dashmap::DashMap;
use once_cell::sync::Lazy;
mod fs;
pub use fs::{create_dir, remove_dir};
pub mod handler;
mod forksafe;
use forksafe::ForksafeTempfile;
pub mod handle;
use crate::handle::{Closed, Writable};
static SIGNAL_HANDLER_MODE: AtomicUsize = AtomicUsize::new(SignalHandlerMode::None as usize);
static NEXT_MAP_INDEX: AtomicUsize = AtomicUsize::new(0);
static REGISTER: Lazy<DashMap<usize, Option<ForksafeTempfile>>> = Lazy::new(|| {
let mode = SIGNAL_HANDLER_MODE.load(std::sync::atomic::Ordering::SeqCst);
if mode != SignalHandlerMode::None as usize {
for sig in signal_hook::consts::TERM_SIGNALS {
#[allow(unsafe_code)]
unsafe {
#[cfg(not(windows))]
{
signal_hook_registry::register_sigaction(*sig, handler::cleanup_tempfiles_nix)
}
#[cfg(windows)]
{
signal_hook::low_level::register(*sig, handler::cleanup_tempfiles_windows)
}
}
.expect("signals can always be installed");
}
}
DashMap::new()
});
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
pub enum SignalHandlerMode {
None = 0,
DeleteTempfilesOnTermination = 1,
DeleteTempfilesOnTerminationAndRestoreDefaultBehaviour = 2,
}
impl Default for SignalHandlerMode {
fn default() -> Self {
if cfg!(test) {
SignalHandlerMode::DeleteTempfilesOnTermination
} else {
SignalHandlerMode::DeleteTempfilesOnTerminationAndRestoreDefaultBehaviour
}
}
}
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
pub enum ContainingDirectory {
Exists,
CreateAllRaceProof(create_dir::Retries),
}
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum AutoRemove {
Tempfile,
TempfileAndEmptyParentDirectoriesUntil {
boundary_directory: PathBuf,
},
}
impl AutoRemove {
fn execute_best_effort(self, directory_to_potentially_delete: &Path) -> Option<PathBuf> {
match self {
AutoRemove::Tempfile => None,
AutoRemove::TempfileAndEmptyParentDirectoriesUntil { boundary_directory } => {
crate::remove_dir::empty_upward_until_boundary(directory_to_potentially_delete, &boundary_directory)
.ok();
Some(boundary_directory)
}
}
}
}
#[derive(Debug)]
#[must_use = "A handle that is immediately dropped doesn't lock a resource meaningfully"]
pub struct Handle<Marker: std::fmt::Debug> {
id: usize,
_marker: PhantomData<Marker>,
}
pub fn new(
containing_directory: impl AsRef<Path>,
directory: ContainingDirectory,
cleanup: AutoRemove,
) -> io::Result<Handle<Writable>> {
Handle::<Writable>::new(containing_directory, directory, cleanup)
}
pub fn writable_at(
path: impl AsRef<Path>,
directory: ContainingDirectory,
cleanup: AutoRemove,
) -> io::Result<Handle<Writable>> {
Handle::<Writable>::at(path, directory, cleanup)
}
pub fn mark_at(
path: impl AsRef<Path>,
directory: ContainingDirectory,
cleanup: AutoRemove,
) -> io::Result<Handle<Closed>> {
Handle::<Closed>::at(path, directory, cleanup)
}
pub fn setup(mode: SignalHandlerMode) {
SIGNAL_HANDLER_MODE.store(mode as usize, std::sync::atomic::Ordering::SeqCst);
Lazy::force(®ISTER);
}
#[deprecated(
since = "2.0.0",
note = "call setup(…) instead, this function will be removed in the next major release"
)]
#[doc(hidden)]
pub fn force_setup(mode: SignalHandlerMode) {
setup(mode)
}