1#![cfg_attr(
31 all(doc, feature = "document-features"),
32 doc = ::document_features::document_features!()
33)]
34#![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg, doc_auto_cfg))]
35#![deny(missing_docs, rust_2018_idioms, unsafe_code)]
36
37use std::{
38 io,
39 marker::PhantomData,
40 path::{Path, PathBuf},
41 sync::atomic::AtomicUsize,
42};
43
44use once_cell::sync::Lazy;
45
46#[cfg(feature = "hp-hashmap")]
47type HashMap<K, V> = dashmap::DashMap<K, V>;
48
49#[cfg(not(feature = "hp-hashmap"))]
50mod hashmap {
51 use std::collections::HashMap;
52
53 use parking_lot::Mutex;
54
55 pub struct Concurrent<K, V> {
57 inner: Mutex<HashMap<K, V>>,
58 }
59
60 impl<K, V> Default for Concurrent<K, V>
61 where
62 K: Eq + std::hash::Hash,
63 {
64 fn default() -> Self {
65 Concurrent {
66 inner: Default::default(),
67 }
68 }
69 }
70
71 impl<K, V> Concurrent<K, V>
72 where
73 K: Eq + std::hash::Hash + Clone,
74 {
75 pub fn insert(&self, key: K, value: V) -> Option<V> {
76 self.inner.lock().insert(key, value)
77 }
78
79 pub fn remove(&self, key: &K) -> Option<(K, V)> {
80 self.inner.lock().remove(key).map(|v| (key.clone(), v))
81 }
82
83 pub fn for_each<F>(&self, cb: F)
84 where
85 Self: Sized,
86 F: FnMut(&mut V),
87 {
88 if let Some(mut guard) = self.inner.try_lock() {
89 guard.values_mut().for_each(cb);
90 }
91 }
92 }
93}
94
95#[cfg(not(feature = "hp-hashmap"))]
96type HashMap<K, V> = hashmap::Concurrent<K, V>;
97
98pub use gix_fs::dir::{create as create_dir, remove as remove_dir};
99
100#[cfg(feature = "signals")]
102pub mod signal;
103
104mod forksafe;
105use forksafe::ForksafeTempfile;
106
107pub mod handle;
108use crate::handle::{Closed, Writable};
109
110pub mod registry;
112
113static NEXT_MAP_INDEX: AtomicUsize = AtomicUsize::new(0);
114static REGISTRY: Lazy<HashMap<usize, Option<ForksafeTempfile>>> = Lazy::new(|| {
115 #[cfg(feature = "signals")]
116 if signal::handler::MODE.load(std::sync::atomic::Ordering::SeqCst) != signal::handler::Mode::None as usize {
117 for sig in signal_hook::consts::TERM_SIGNALS {
118 #[allow(unsafe_code)]
120 unsafe {
121 #[cfg(not(windows))]
122 {
123 signal_hook_registry::register_sigaction(*sig, signal::handler::cleanup_tempfiles_nix)
124 }
125 #[cfg(windows)]
126 {
127 signal_hook::low_level::register(*sig, signal::handler::cleanup_tempfiles_windows)
128 }
129 }
130 .expect("signals can always be installed");
131 }
132 }
133 HashMap::default()
134});
135
136#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
138pub enum ContainingDirectory {
139 Exists,
141 CreateAllRaceProof(create_dir::Retries),
144}
145
146#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
149pub enum AutoRemove {
150 Tempfile,
152 TempfileAndEmptyParentDirectoriesUntil {
154 boundary_directory: PathBuf,
156 },
157}
158
159impl AutoRemove {
160 fn execute_best_effort(self, directory_to_potentially_delete: &Path) -> Option<PathBuf> {
161 match self {
162 AutoRemove::Tempfile => None,
163 AutoRemove::TempfileAndEmptyParentDirectoriesUntil { boundary_directory } => {
164 remove_dir::empty_upward_until_boundary(directory_to_potentially_delete, &boundary_directory).ok();
165 Some(boundary_directory)
166 }
167 }
168 }
169}
170
171#[derive(Debug)]
186#[must_use = "A handle that is immediately dropped doesn't lock a resource meaningfully"]
187pub struct Handle<Marker: std::fmt::Debug> {
188 id: usize,
189 _marker: PhantomData<Marker>,
190}
191
192pub fn new(
194 containing_directory: impl AsRef<Path>,
195 directory: ContainingDirectory,
196 cleanup: AutoRemove,
197) -> io::Result<Handle<Writable>> {
198 Handle::<Writable>::new(containing_directory, directory, cleanup)
199}
200
201pub fn writable_at(
203 path: impl AsRef<Path>,
204 directory: ContainingDirectory,
205 cleanup: AutoRemove,
206) -> io::Result<Handle<Writable>> {
207 Handle::<Writable>::at(path, directory, cleanup)
208}
209
210pub fn writable_at_with_permissions(
212 path: impl AsRef<Path>,
213 directory: ContainingDirectory,
214 cleanup: AutoRemove,
215 permissions: std::fs::Permissions,
216) -> io::Result<Handle<Writable>> {
217 Handle::<Writable>::at_with_permissions(path, directory, cleanup, permissions)
218}
219
220pub fn mark_at(
222 path: impl AsRef<Path>,
223 directory: ContainingDirectory,
224 cleanup: AutoRemove,
225) -> io::Result<Handle<Closed>> {
226 Handle::<Closed>::at(path, directory, cleanup)
227}
228
229pub fn mark_at_with_permissions(
231 path: impl AsRef<Path>,
232 directory: ContainingDirectory,
233 cleanup: AutoRemove,
234 permissions: std::fs::Permissions,
235) -> io::Result<Handle<Closed>> {
236 Handle::<Closed>::at_with_permissions(path, directory, cleanup, permissions)
237}