| //! | 
 | #![allow(clippy::empty_docs)] | 
 | use std::{io, path::Path}; | 
 |  | 
 | use tempfile::{NamedTempFile, TempPath}; | 
 |  | 
 | use crate::{AutoRemove, ContainingDirectory, ForksafeTempfile, Handle, NEXT_MAP_INDEX, REGISTRY}; | 
 |  | 
 | /// Marker to signal the Registration is an open file able to be written to. | 
 | #[derive(Debug)] | 
 | pub struct Writable; | 
 |  | 
 | /// Marker to signal the Registration is a closed file that consumes no additional process resources. | 
 | /// | 
 | /// It can't ever be written to unless reopened after persisting it. | 
 | #[derive(Debug)] | 
 | pub struct Closed; | 
 |  | 
 | pub(crate) enum Mode { | 
 |     Writable, | 
 |     Closed, | 
 | } | 
 |  | 
 | /// Utilities | 
 | impl Handle<()> { | 
 |     fn at_path( | 
 |         path: &Path, | 
 |         directory: ContainingDirectory, | 
 |         cleanup: AutoRemove, | 
 |         mode: Mode, | 
 |         permissions: Option<std::fs::Permissions>, | 
 |     ) -> io::Result<usize> { | 
 |         let tempfile = { | 
 |             let mut builder = tempfile::Builder::new(); | 
 |             let dot_ext_storage; | 
 |             match path.file_stem() { | 
 |                 Some(stem) => builder.prefix(stem), | 
 |                 None => builder.prefix(""), | 
 |             }; | 
 |             if let Some(ext) = path.extension() { | 
 |                 dot_ext_storage = format!(".{}", ext.to_string_lossy()); | 
 |                 builder.suffix(&dot_ext_storage); | 
 |             } | 
 |             if let Some(permissions) = permissions { | 
 |                 builder.permissions(permissions); | 
 |             } | 
 |             let parent_dir = path.parent().expect("parent directory is present"); | 
 |             let parent_dir = directory.resolve(parent_dir)?; | 
 |             ForksafeTempfile::new(builder.rand_bytes(0).tempfile_in(parent_dir)?, cleanup, mode) | 
 |         }; | 
 |         let id = NEXT_MAP_INDEX.fetch_add(1, std::sync::atomic::Ordering::SeqCst); | 
 |         expect_none(REGISTRY.insert(id, Some(tempfile))); | 
 |         Ok(id) | 
 |     } | 
 |  | 
 |     fn new_writable_inner( | 
 |         containing_directory: &Path, | 
 |         directory: ContainingDirectory, | 
 |         cleanup: AutoRemove, | 
 |         mode: Mode, | 
 |     ) -> io::Result<usize> { | 
 |         let containing_directory = directory.resolve(containing_directory)?; | 
 |         let id = NEXT_MAP_INDEX.fetch_add(1, std::sync::atomic::Ordering::SeqCst); | 
 |         expect_none(REGISTRY.insert( | 
 |             id, | 
 |             Some(ForksafeTempfile::new( | 
 |                 NamedTempFile::new_in(containing_directory)?, | 
 |                 cleanup, | 
 |                 mode, | 
 |             )), | 
 |         )); | 
 |         Ok(id) | 
 |     } | 
 | } | 
 |  | 
 | /// Creation and ownership transfer | 
 | impl Handle<Closed> { | 
 |     /// Create a registered tempfile at the given `path`, where `path` includes the desired filename and close it immediately. | 
 |     /// | 
 |     /// Depending on the `directory` configuration, intermediate directories will be created, and depending on `cleanup` empty | 
 |     /// intermediate directories will be removed. | 
 |     /// | 
 |     /// ### Warning of potential leaks | 
 |     /// | 
 |     /// Without [signal handlers](crate::signal) installed, tempfiles will remain once a termination | 
 |     /// signal is encountered as destructors won't run. See [the top-level documentation](crate) for more. | 
 |     pub fn at(path: impl AsRef<Path>, directory: ContainingDirectory, cleanup: AutoRemove) -> io::Result<Self> { | 
 |         Ok(Handle { | 
 |             id: Handle::<()>::at_path(path.as_ref(), directory, cleanup, Mode::Closed, None)?, | 
 |             _marker: Default::default(), | 
 |         }) | 
 |     } | 
 |  | 
 |     /// Like [`at`](Self::at()), but with support for filesystem `permissions`. | 
 |     pub fn at_with_permissions( | 
 |         path: impl AsRef<Path>, | 
 |         directory: ContainingDirectory, | 
 |         cleanup: AutoRemove, | 
 |         permissions: std::fs::Permissions, | 
 |     ) -> io::Result<Self> { | 
 |         Ok(Handle { | 
 |             id: Handle::<()>::at_path(path.as_ref(), directory, cleanup, Mode::Closed, Some(permissions))?, | 
 |             _marker: Default::default(), | 
 |         }) | 
 |     } | 
 |  | 
 |     /// Take ownership of the temporary file path, which deletes it when dropped without persisting it beforehand. | 
 |     /// | 
 |     /// It's a theoretical possibility that the file isn't present anymore if signals interfere, hence the `Option` | 
 |     pub fn take(self) -> Option<TempPath> { | 
 |         let res = REGISTRY.remove(&self.id); | 
 |         std::mem::forget(self); | 
 |         res.and_then(|(_k, v)| v.map(ForksafeTempfile::into_temppath)) | 
 |     } | 
 | } | 
 |  | 
 | /// Creation and ownership transfer | 
 | impl Handle<Writable> { | 
 |     /// Create a registered tempfile at the given `path`, where `path` includes the desired filename. | 
 |     /// | 
 |     /// Depending on the `directory` configuration, intermediate directories will be created, and depending on `cleanup` empty | 
 |     /// intermediate directories will be removed. | 
 |     /// | 
 |     /// ### Warning of potential leaks | 
 |     /// | 
 |     /// Without [signal handlers](crate::signal) installed, tempfiles will remain once a termination | 
 |     /// signal is encountered as destructors won't run. See [the top-level documentation](crate) for more. | 
 |     pub fn at(path: impl AsRef<Path>, directory: ContainingDirectory, cleanup: AutoRemove) -> io::Result<Self> { | 
 |         Ok(Handle { | 
 |             id: Handle::<()>::at_path(path.as_ref(), directory, cleanup, Mode::Writable, None)?, | 
 |             _marker: Default::default(), | 
 |         }) | 
 |     } | 
 |  | 
 |     /// Like [`at`](Self::at()), but with support for filesystem `permissions`. | 
 |     pub fn at_with_permissions( | 
 |         path: impl AsRef<Path>, | 
 |         directory: ContainingDirectory, | 
 |         cleanup: AutoRemove, | 
 |         permissions: std::fs::Permissions, | 
 |     ) -> io::Result<Self> { | 
 |         Ok(Handle { | 
 |             id: Handle::<()>::at_path(path.as_ref(), directory, cleanup, Mode::Writable, Some(permissions))?, | 
 |             _marker: Default::default(), | 
 |         }) | 
 |     } | 
 |  | 
 |     /// Create a registered tempfile within `containing_directory` with a name that won't clash, and clean it up as specified with `cleanup`. | 
 |     /// Control how to deal with intermediate directories with `directory`. | 
 |     /// The temporary file is opened and can be written to using the [`with_mut()`][Handle::with_mut()] method. | 
 |     /// | 
 |     /// ### Warning of potential leaks | 
 |     /// | 
 |     /// Without [signal handlers](crate::signal) installed, tempfiles will remain once a termination | 
 |     /// signal is encountered as destructors won't run. See [the top-level documentation](crate) for more. | 
 |     pub fn new( | 
 |         containing_directory: impl AsRef<Path>, | 
 |         directory: ContainingDirectory, | 
 |         cleanup: AutoRemove, | 
 |     ) -> io::Result<Self> { | 
 |         Ok(Handle { | 
 |             id: Handle::<()>::new_writable_inner(containing_directory.as_ref(), directory, cleanup, Mode::Writable)?, | 
 |             _marker: Default::default(), | 
 |         }) | 
 |     } | 
 |  | 
 |     /// Take ownership of the temporary file. | 
 |     /// | 
 |     /// It's a theoretical possibility that the file isn't present anymore if signals interfere, hence the `Option` | 
 |     pub fn take(self) -> Option<NamedTempFile> { | 
 |         let res = REGISTRY.remove(&self.id); | 
 |         std::mem::forget(self); | 
 |         res.and_then(|(_k, v)| v.map(|v| v.into_tempfile().expect("correct runtime typing"))) | 
 |     } | 
 |  | 
 |     /// Close the underlying file handle but keep track of the temporary file as before for automatic cleanup. | 
 |     /// | 
 |     /// This saves system resources in situations where one opens a tempfile file at a time, writes a new value, and closes | 
 |     /// it right after to perform more updates of this kind in other tempfiles. When all succeed, they can be renamed one after | 
 |     /// another. | 
 |     pub fn close(self) -> std::io::Result<Handle<Closed>> { | 
 |         match REGISTRY.remove(&self.id) { | 
 |             Some((id, Some(t))) => { | 
 |                 std::mem::forget(self); | 
 |                 expect_none(REGISTRY.insert(id, Some(t.close()))); | 
 |                 Ok(Handle::<Closed> { | 
 |                     id, | 
 |                     _marker: Default::default(), | 
 |                 }) | 
 |             } | 
 |             None | Some((_, None)) => Err(std::io::Error::new( | 
 |                 std::io::ErrorKind::NotFound, | 
 |                 format!("The tempfile with id {} wasn't available anymore", self.id), | 
 |             )), | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | /// Mutation | 
 | impl Handle<Writable> { | 
 |     /// Obtain a mutable handler to the underlying named tempfile and call `f(&mut named_tempfile)` on it. | 
 |     /// | 
 |     /// Note that for the duration of the call, a signal interrupting the operation will cause the tempfile not to be cleaned up | 
 |     /// as it is not visible anymore to the signal handler. | 
 |     /// | 
 |     /// # Assumptions | 
 |     /// The caller must assure that the signal handler for cleanup will be followed by an abort call so that | 
 |     /// this code won't run again on a removed instance. An error will occur otherwise. | 
 |     pub fn with_mut<T>(&mut self, once: impl FnOnce(&mut NamedTempFile) -> T) -> std::io::Result<T> { | 
 |         match REGISTRY.remove(&self.id) { | 
 |             Some((id, Some(mut t))) => { | 
 |                 let res = once(t.as_mut_tempfile().expect("correct runtime typing")); | 
 |                 expect_none(REGISTRY.insert(id, Some(t))); | 
 |                 Ok(res) | 
 |             } | 
 |             None | Some((_, None)) => Err(std::io::Error::new( | 
 |                 std::io::ErrorKind::NotFound, | 
 |                 format!("The tempfile with id {} wasn't available anymore", self.id), | 
 |             )), | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | mod io_impls { | 
 |     use std::{io, io::SeekFrom}; | 
 |  | 
 |     use super::{Handle, Writable}; | 
 |  | 
 |     impl io::Write for Handle<Writable> { | 
 |         fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | 
 |             self.with_mut(|f| f.write(buf))? | 
 |         } | 
 |  | 
 |         fn flush(&mut self) -> io::Result<()> { | 
 |             self.with_mut(io::Write::flush)? | 
 |         } | 
 |     } | 
 |  | 
 |     impl io::Seek for Handle<Writable> { | 
 |         fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> { | 
 |             self.with_mut(|f| f.seek(pos))? | 
 |         } | 
 |     } | 
 |  | 
 |     impl io::Read for Handle<Writable> { | 
 |         fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | 
 |             self.with_mut(|f| f.read(buf))? | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | /// | 
 | #[allow(clippy::empty_docs)] | 
 | pub mod persist { | 
 |     use std::path::Path; | 
 |  | 
 |     use crate::{ | 
 |         handle::{expect_none, Closed, Writable}, | 
 |         Handle, REGISTRY, | 
 |     }; | 
 |  | 
 |     mod error { | 
 |         use std::fmt::{self, Debug, Display}; | 
 |  | 
 |         use crate::Handle; | 
 |  | 
 |         /// The error returned by various [`persist(…)`][Handle<crate::handle::Writable>::persist()] methods | 
 |         #[derive(Debug)] | 
 |         pub struct Error<T: Debug> { | 
 |             /// The io error that prevented the attempt to succeed | 
 |             pub error: std::io::Error, | 
 |             /// The registered handle to the tempfile which couldn't be persisted. | 
 |             pub handle: Handle<T>, | 
 |         } | 
 |  | 
 |         impl<T: Debug> Display for Error<T> { | 
 |             fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | 
 |                 Display::fmt(&self.error, f) | 
 |             } | 
 |         } | 
 |  | 
 |         impl<T: Debug> std::error::Error for Error<T> { | 
 |             fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { | 
 |                 self.error.source() | 
 |             } | 
 |         } | 
 |     } | 
 |     pub use error::Error; | 
 |  | 
 |     impl Handle<Writable> { | 
 |         /// Persist this tempfile to replace the file at the given `path` if necessary, in a way that recovers the original instance | 
 |         /// on error or returns the open now persisted former tempfile. | 
 |         /// Note that it might not exist anymore if an interrupt handler managed to steal it and allowed the program to return to | 
 |         /// its normal flow. | 
 |         pub fn persist(self, path: impl AsRef<Path>) -> Result<Option<std::fs::File>, Error<Writable>> { | 
 |             let res = REGISTRY.remove(&self.id); | 
 |  | 
 |             match res.and_then(|(_k, v)| v.map(|v| v.persist(path))) { | 
 |                 Some(Ok(Some(file))) => { | 
 |                     std::mem::forget(self); | 
 |                     Ok(Some(file)) | 
 |                 } | 
 |                 None => { | 
 |                     std::mem::forget(self); | 
 |                     Ok(None) | 
 |                 } | 
 |                 Some(Err((err, tempfile))) => { | 
 |                     expect_none(REGISTRY.insert(self.id, Some(tempfile))); | 
 |                     Err(Error::<Writable> { | 
 |                         error: err, | 
 |                         handle: self, | 
 |                     }) | 
 |                 } | 
 |                 Some(Ok(None)) => unreachable!("no open files in an open handle"), | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     impl Handle<Closed> { | 
 |         /// Persist this tempfile to replace the file at the given `path` if necessary, in a way that recovers the original instance | 
 |         /// on error. | 
 |         pub fn persist(self, path: impl AsRef<Path>) -> Result<(), Error<Closed>> { | 
 |             let res = REGISTRY.remove(&self.id); | 
 |  | 
 |             match res.and_then(|(_k, v)| v.map(|v| v.persist(path))) { | 
 |                 None | Some(Ok(None)) => { | 
 |                     std::mem::forget(self); | 
 |                     Ok(()) | 
 |                 } | 
 |                 Some(Err((err, tempfile))) => { | 
 |                     expect_none(REGISTRY.insert(self.id, Some(tempfile))); | 
 |                     Err(Error::<Closed> { | 
 |                         error: err, | 
 |                         handle: self, | 
 |                     }) | 
 |                 } | 
 |                 Some(Ok(Some(_file))) => unreachable!("no open files in a closed handle"), | 
 |             } | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | impl ContainingDirectory { | 
 |     fn resolve(self, dir: &Path) -> std::io::Result<&Path> { | 
 |         match self { | 
 |             ContainingDirectory::Exists => Ok(dir), | 
 |             ContainingDirectory::CreateAllRaceProof(retries) => crate::create_dir::all(dir, retries), | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | fn expect_none<T>(v: Option<T>) { | 
 |     assert!( | 
 |         v.is_none(), | 
 |         "there should never be conflicts or old values as ids are never reused." | 
 |     ); | 
 | } | 
 |  | 
 | impl<T: std::fmt::Debug> Drop for Handle<T> { | 
 |     fn drop(&mut self) { | 
 |         if let Some((_id, Some(tempfile))) = REGISTRY.remove(&self.id) { | 
 |             tempfile.drop_impl(); | 
 |         } | 
 |     } | 
 | } |