Skip to main content

async_tempfile/
lib.rs

1//! # async-tempfile
2//!
3//! Provides the [`TempFile`] struct, an asynchronous wrapper based on `tokio::fs` for temporary
4//! files that will be automatically deleted when the last reference to the struct is dropped.
5//!
6//! ```
7//! use async_tempfile::TempFile;
8//!
9//! #[tokio::main]
10//! async fn main() {
11//!     let parent = TempFile::new().await.unwrap();
12//!
13//!     // The cloned reference will not delete the file when dropped.
14//!     {
15//!         let nested = parent.open_rw().await.unwrap();
16//!         assert_eq!(nested.file_path(), parent.file_path());
17//!         assert!(nested.file_path().is_file());
18//!     }
19//!
20//!     // The file still exists; it will be deleted when `parent` is dropped.
21//!     assert!(parent.file_path().is_file());
22//! }
23//! ```
24//!
25//! ## Features
26//!
27//! * `uuid` - Enables UUID-based random file name generation via the [`uuid`](https://crates.io/crates/uuid) crate
28//!   and the `new_with_uuid*` group of methods. Not enabled by default; `new`/`new_in` work without it.
29
30// Document crate features on docs.rs.
31#![cfg_attr(docsrs, feature(doc_cfg))]
32// This crate deletes files purely from safe code; no `unsafe` is permitted.
33#![forbid(unsafe_code)]
34
35mod builder;
36mod errors;
37mod random_name;
38mod tempdir;
39mod tempfile;
40
41pub use builder::{TempDirBuilder, TempFileBuilder};
42pub use errors::{Error, PersistError};
43#[cfg(not(feature = "uuid"))]
44pub(crate) use random_name::RandomName;
45pub use tempdir::TempDir;
46pub use tempfile::TempFile;
47
48use std::path::Path;
49use std::sync::atomic::{AtomicBool, Ordering};
50
51/// Returns whether `path` is a directory, using async I/O so the calling task
52/// does not block a runtime worker thread on the `stat` syscall.
53pub(crate) async fn path_is_dir(path: &Path) -> bool {
54    tokio::fs::metadata(path)
55        .await
56        .map(|m| m.is_dir())
57        .unwrap_or(false)
58}
59
60/// Returns whether `path` is a regular file, using async I/O (see [`path_is_dir`]).
61pub(crate) async fn path_is_file(path: &Path) -> bool {
62    tokio::fs::metadata(path)
63        .await
64        .map(|m| m.is_file())
65        .unwrap_or(false)
66}
67
68/// Returns `true` if `affix` is safe to splice into a file name.
69///
70/// `prefix`/`suffix` are composed into a single path component
71/// (`{prefix}{random}{suffix}`). A path separator in either would let the
72/// composed name escape the target directory once joined onto it: `Path::join`
73/// replaces the base entirely when the fragment is absolute, and a fragment
74/// containing `..` resolves to a parent directory when the OS interprets the
75/// resulting path. We therefore reject any affix containing a separator
76/// ([`std::path::is_separator`], which is platform-aware: `/` everywhere, plus
77/// `\` on Windows) rather than letting it reach the filesystem.
78pub(crate) fn affix_is_safe(affix: &str) -> bool {
79    !affix.chars().any(std::path::is_separator)
80}
81
82/// Determines the ownership of a temporary file or directory.
83#[derive(Debug, Eq, PartialEq, Copy, Clone)]
84pub enum Ownership {
85    /// The file or directory is owned by [`TempFile`] and will be deleted when
86    /// the last reference to it is dropped.
87    Owned,
88    /// The file or directory is borrowed by [`TempFile`] and will be left untouched
89    /// when the last reference to it is dropped.
90    Borrowed,
91}
92
93/// Lock-free, interior-mutable storage of an [`Ownership`] value, shared across
94/// all clones via the `Arc`-wrapped core.
95///
96/// Lock-free is a hard requirement, not an optimization: the value is read from
97/// `Drop`, which may run on a runtime worker thread and therefore must never
98/// block on a lock. `keep`, `persist`, `drop_async`, and `close` flip it to
99/// `Borrowed` to disable automatic deletion.
100pub(crate) struct AtomicOwnership(AtomicBool);
101
102impl AtomicOwnership {
103    /// Creates a new value from the given ownership (`Owned` == `true`).
104    pub(crate) fn new(ownership: Ownership) -> Self {
105        Self(AtomicBool::new(matches!(ownership, Ownership::Owned)))
106    }
107
108    /// Returns the current ownership.
109    pub(crate) fn get(&self) -> Ownership {
110        if self.is_owned() {
111            Ownership::Owned
112        } else {
113            Ownership::Borrowed
114        }
115    }
116
117    /// Returns `true` if the value is currently `Owned`.
118    pub(crate) fn is_owned(&self) -> bool {
119        self.0.load(Ordering::Acquire)
120    }
121
122    /// Flips the value to `Borrowed`, disabling automatic deletion. Idempotent.
123    pub(crate) fn set_borrowed(&self) {
124        self.0.store(false, Ordering::Release);
125    }
126}