summaryrefslogtreecommitdiffstats
path: root/vendor/gix-tempfile/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix-tempfile/src/lib.rs')
-rw-r--r--vendor/gix-tempfile/src/lib.rs216
1 files changed, 216 insertions, 0 deletions
diff --git a/vendor/gix-tempfile/src/lib.rs b/vendor/gix-tempfile/src/lib.rs
new file mode 100644
index 000000000..8585c60b7
--- /dev/null
+++ b/vendor/gix-tempfile/src/lib.rs
@@ -0,0 +1,216 @@
+//! git-style registered tempfiles that are removed upon typical termination signals.
+//!
+//! To register signal handlers in a typical application that doesn't have its own, call
+//! [`gix_tempfile::signal::setup(Default::default())`][signal::setup()] before creating the first tempfile.
+//!
+//! Signal handlers are powered by [`signal-hook`] to get notified when the application is told to shut down
+//! to assure tempfiles are deleted. The deletion is filtered by process id to allow forks to have their own
+//! set of tempfiles that won't get deleted when the parent process exits.
+//!
+//! ### Initial Setup
+//!
+//! As no handlers for `TERMination` are installed, it is required to call [`signal::setup()`] before creating the first tempfile.
+//! This also allows to control how `git-tempfiles` integrates with other handlers under application control.
+//!
+//! As a general rule of thumb, use `Default::default()` as argument to emulate the default behaviour and
+//! abort the process after cleaning temporary files. Read more about options in [signal::handler::Mode].
+//!
+//! # Limitations
+//!
+//! ## Tempfiles might remain on disk
+//!
+//! * Uninterruptible signals are received like `SIGKILL`
+//! * The application is performing a write operation on the tempfile when a signal arrives, preventing this tempfile to be removed,
+//! but not others. Any other operation dealing with the tempfile suffers from the same issue.
+//!
+//! [signal-hook]: https://docs.rs/signal-hook
+//!
+//! ## Feature Flags
+#![cfg_attr(
+ feature = "document-features",
+ cfg_attr(doc, doc = ::document_features::document_features!())
+)]
+#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
+#![deny(missing_docs, rust_2018_idioms, unsafe_code)]
+
+use std::{
+ io,
+ marker::PhantomData,
+ path::{Path, PathBuf},
+ sync::atomic::AtomicUsize,
+};
+
+use once_cell::sync::Lazy;
+
+#[cfg(feature = "hp-hashmap")]
+type HashMap<K, V> = dashmap::DashMap<K, V>;
+
+#[cfg(not(feature = "hp-hashmap"))]
+mod hashmap {
+ use parking_lot::Mutex;
+ use std::collections::HashMap;
+
+ // TODO(performance): use the `gix-hashtable` slot-map once available. It seems quite fast already though, so experiment.
+ pub struct Concurrent<K, V> {
+ inner: Mutex<HashMap<K, V>>,
+ }
+
+ impl<K, V> Default for Concurrent<K, V>
+ where
+ K: Eq + std::hash::Hash,
+ {
+ fn default() -> Self {
+ Concurrent {
+ inner: Default::default(),
+ }
+ }
+ }
+
+ impl<K, V> Concurrent<K, V>
+ where
+ K: Eq + std::hash::Hash + Clone,
+ {
+ pub fn insert(&self, key: K, value: V) -> Option<V> {
+ self.inner.lock().insert(key, value)
+ }
+
+ pub fn remove(&self, key: &K) -> Option<(K, V)> {
+ self.inner.lock().remove(key).map(|v| (key.clone(), v))
+ }
+
+ pub fn for_each<F>(&self, cb: F)
+ where
+ Self: Sized,
+ F: FnMut(&mut V),
+ {
+ if let Some(mut guard) = self.inner.try_lock() {
+ guard.values_mut().for_each(cb);
+ }
+ }
+ }
+}
+
+#[cfg(not(feature = "hp-hashmap"))]
+type HashMap<K, V> = hashmap::Concurrent<K, V>;
+
+mod fs;
+pub use fs::{create_dir, remove_dir};
+
+#[cfg(feature = "signals")]
+/// signal setup and reusable handlers.
+pub mod signal;
+
+mod forksafe;
+use forksafe::ForksafeTempfile;
+
+pub mod handle;
+use crate::handle::{Closed, Writable};
+
+///
+pub mod registry;
+
+static NEXT_MAP_INDEX: AtomicUsize = AtomicUsize::new(0);
+static REGISTRY: Lazy<HashMap<usize, Option<ForksafeTempfile>>> = Lazy::new(|| {
+ #[cfg(feature = "signals")]
+ if signal::handler::MODE.load(std::sync::atomic::Ordering::SeqCst) != signal::handler::Mode::None as usize {
+ for sig in signal_hook::consts::TERM_SIGNALS {
+ // SAFETY: handlers are considered unsafe because a lot can go wrong. See `cleanup_tempfiles()` for details on safety.
+ #[allow(unsafe_code)]
+ unsafe {
+ #[cfg(not(windows))]
+ {
+ signal_hook_registry::register_sigaction(*sig, signal::handler::cleanup_tempfiles_nix)
+ }
+ #[cfg(windows)]
+ {
+ signal_hook::low_level::register(*sig, signal::handler::cleanup_tempfiles_windows)
+ }
+ }
+ .expect("signals can always be installed");
+ }
+ }
+ HashMap::default()
+});
+
+/// A type expressing the ways we can deal with directories containing a tempfile.
+#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
+pub enum ContainingDirectory {
+ /// Assume the directory for the tempfile exists and cause failure if it doesn't
+ Exists,
+ /// Create the directory recursively with the given amount of retries in a way that is somewhat race resistant
+ /// depending on the amount of retries.
+ CreateAllRaceProof(create_dir::Retries),
+}
+
+/// A type expressing the ways we cleanup after ourselves to remove resources we created.
+/// Note that cleanup has no effect if the tempfile is persisted.
+#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
+pub enum AutoRemove {
+ /// Remove the temporary file after usage if it wasn't persisted.
+ Tempfile,
+ /// Remove the temporary file as well the containing directories if they are empty until the given `directory`.
+ TempfileAndEmptyParentDirectoriesUntil {
+ /// The directory which shall not be removed even if it is empty.
+ 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 } => {
+ remove_dir::empty_upward_until_boundary(directory_to_potentially_delete, &boundary_directory).ok();
+ Some(boundary_directory)
+ }
+ }
+ }
+}
+
+/// A registered temporary file which will delete itself on drop or if the program is receiving signals that
+/// should cause it to terminate.
+///
+/// # Note
+///
+/// Signals interrupting the calling thread right after taking ownership of the registered tempfile
+/// will cause all but this tempfile to be removed automatically. In the common case it will persist on disk as destructors
+/// were not called or didn't get to remove the file.
+///
+/// In the best case the file is a true temporary with a non-clashing name that 'only' fills up the disk,
+/// in the worst case the temporary file is used as a lock file which may leave the repository in a locked
+/// state forever.
+///
+/// This kind of raciness exists whenever [`take()`][Handle::take()] is used and can't be circumvented.
+#[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>,
+}
+
+/// A shortcut to [`Handle::<Writable>::new()`], creating a writable temporary file with non-clashing name in a directory.
+pub fn new(
+ containing_directory: impl AsRef<Path>,
+ directory: ContainingDirectory,
+ cleanup: AutoRemove,
+) -> io::Result<Handle<Writable>> {
+ Handle::<Writable>::new(containing_directory, directory, cleanup)
+}
+
+/// A shortcut to [`Handle::<Writable>::at()`] providing a writable temporary file at the given path.
+pub fn writable_at(
+ path: impl AsRef<Path>,
+ directory: ContainingDirectory,
+ cleanup: AutoRemove,
+) -> io::Result<Handle<Writable>> {
+ Handle::<Writable>::at(path, directory, cleanup)
+}
+
+/// A shortcut to [`Handle::<Closed>::at()`] providing a closed temporary file to mark the presence of something.
+pub fn mark_at(
+ path: impl AsRef<Path>,
+ directory: ContainingDirectory,
+ cleanup: AutoRemove,
+) -> io::Result<Handle<Closed>> {
+ Handle::<Closed>::at(path, directory, cleanup)
+}