Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tempfile V4 #327

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
rust-version:
- nightly
- stable
- "1.63"
- "1.84"
os:
- ubuntu-latest
- windows-latest
Expand Down
4 changes: 1 addition & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ authors = [
]
documentation = "https://docs.rs/tempfile"
edition = "2021"
rust-version = "1.63"
rust-version = "1.84"
homepage = "https://stebalien.com/projects/tempfile-rs/"
keywords = ["tempfile", "tmpfile", "filesystem"]
license = "MIT OR Apache-2.0"
Expand All @@ -18,8 +18,6 @@ description = "A library for managing temporary files and directories."

[dependencies]
fastrand = "2.1.1"
# Not available in stdlib until 1.70, but we support 1.63 to support Debian stable.
once_cell = { version = "1.19.0", default-features = false, features = ["std"] }

[target.'cfg(any(unix, windows, target_os = "wasi"))'.dependencies]
getrandom = { version = "0.3.0", default-features = false, optional = true }
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ patterns and surprisingly difficult to implement securely).
Usage
-----

Minimum required Rust version: 1.63.0
Minimum required Rust version: 1.84.0

Add this to your `Cargo.toml`:

Expand Down
4 changes: 1 addition & 3 deletions src/dir/imp/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ pub fn create(
#[cfg(not(target_os = "wasi"))]
{
use std::os::unix::fs::{DirBuilderExt, PermissionsExt};
if let Some(p) = permissions {
dir_options.mode(p.mode());
}
dir_options.mode(permissions.map(|p| p.mode()).unwrap_or(0o700));
}
dir_options
.create(&path)
Expand Down
28 changes: 12 additions & 16 deletions src/dir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::ffi::OsStr;
use std::fs::remove_dir_all;
use std::mem;
use std::path::{self, Path, PathBuf};
Expand Down Expand Up @@ -268,8 +267,8 @@ impl TempDir {
/// assert!(tmp_name.starts_with("foo-"));
/// # Ok::<(), std::io::Error>(())
/// ```
pub fn with_prefix<S: AsRef<OsStr>>(prefix: S) -> io::Result<TempDir> {
Builder::new().prefix(&prefix).tempdir()
pub fn with_prefix(prefix: &str) -> io::Result<TempDir> {
Builder::new().prefix(prefix).tempdir()
}

/// Attempts to make a temporary directory with the specified suffix inside of
Expand All @@ -293,8 +292,8 @@ impl TempDir {
/// assert!(tmp_name.ends_with("-foo"));
/// # Ok::<(), std::io::Error>(())
/// ```
pub fn with_suffix<S: AsRef<OsStr>>(suffix: S) -> io::Result<TempDir> {
Builder::new().suffix(&suffix).tempdir()
pub fn with_suffix(suffix: &str) -> io::Result<TempDir> {
Builder::new().suffix(suffix).tempdir()
}
/// Attempts to make a temporary directory with the specified prefix inside
/// the specified directory. The directory and everything inside it will be
Expand All @@ -317,11 +316,8 @@ impl TempDir {
/// assert!(tmp_name.ends_with("-foo"));
/// # Ok::<(), std::io::Error>(())
/// ```
pub fn with_suffix_in<S: AsRef<OsStr>, P: AsRef<Path>>(
suffix: S,
dir: P,
) -> io::Result<TempDir> {
Builder::new().suffix(&suffix).tempdir_in(dir)
pub fn with_suffix_in<P: AsRef<Path>>(suffix: &str, dir: P) -> io::Result<TempDir> {
Builder::new().suffix(suffix).tempdir_in(dir)
}

/// Attempts to make a temporary directory with the specified prefix inside
Expand All @@ -345,11 +341,8 @@ impl TempDir {
/// assert!(tmp_name.starts_with("foo-"));
/// # Ok::<(), std::io::Error>(())
/// ```
pub fn with_prefix_in<S: AsRef<OsStr>, P: AsRef<Path>>(
prefix: S,
dir: P,
) -> io::Result<TempDir> {
Builder::new().prefix(&prefix).tempdir_in(dir)
pub fn with_prefix_in<P: AsRef<Path>>(prefix: &str, dir: P) -> io::Result<TempDir> {
Builder::new().prefix(prefix).tempdir_in(dir)
}

/// Accesses the [`Path`] to the temporary directory.
Expand Down Expand Up @@ -467,7 +460,10 @@ impl TempDir {
}
}

impl AsRef<Path> for TempDir {
// NOTE: This is implemented on &TempDir, not TempDir, to prevent accidentally moving the TempDir
// into a function that calls `as_ref()` before immediately dropping it (deleting the underlying
// temporary directory).
impl AsRef<Path> for &TempDir {
fn as_ref(&self) -> &Path {
self.path()
}
Expand Down
66 changes: 45 additions & 21 deletions src/env.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
use std::env;
use std::path::{Path, PathBuf};
use std::sync::{LazyLock, OnceLock};

// Once rust 1.70 is wide-spread (Debian stable), we can use OnceLock from stdlib.
use once_cell::sync::OnceCell as OnceLock;
#[cfg(doc)]
use crate::Builder;

static ENV_TEMPDIR: LazyLock<PathBuf> = LazyLock::new(env::temp_dir);
static DEFAULT_TEMPDIR: OnceLock<PathBuf> = OnceLock::new();
static DEFAULT_PREFIX: OnceLock<String> = OnceLock::new();

/// Override the default temporary directory (defaults to [`std::env::temp_dir`]). This function
/// changes the _global_ default temporary directory for the entire program and should not be called
/// except in exceptional cases where it's not configured correctly by the platform. Applications
/// should first check if the path returned by [`env::temp_dir`] is acceptable.
///
/// Only the first call to this function will succeed. All further calls will fail with `Err(path)`
/// where `path` is previously set default temporary directory override.
/// Only the **first** call to this function will succeed and return `Ok(path)` where `path` is a
/// static reference to the temporary directory. All further calls will fail with `Err(path)` where
/// `path` is the previously set default temporary directory override.
///
/// **NOTE:** This function does not check if the specified directory exists and/or is writable.
pub fn override_temp_dir(path: &Path) -> Result<(), PathBuf> {
let mut we_set = false;
let val = DEFAULT_TEMPDIR.get_or_init(|| {
we_set = true;
path.to_path_buf()
});
if we_set {
Ok(())
} else {
Err(val.to_owned())
pub fn override_temp_dir(path: impl Into<PathBuf>) -> Result<&'static Path, &'static Path> {
let mut path = Some(path.into());
let val = DEFAULT_TEMPDIR.get_or_init(|| path.take().unwrap());
match path {
Some(_) => Err(val),
None => Ok(val),
}
}

Expand All @@ -34,11 +34,35 @@ pub fn override_temp_dir(path: &Path) -> Result<(), PathBuf> {
/// This function simply delegates to [`std::env::temp_dir`] unless the default temporary directory
/// has been override by a call to [`override_temp_dir`].
///
/// **NOTE:** This function does check if the returned directory exists and/or is writable.
pub fn temp_dir() -> PathBuf {
DEFAULT_TEMPDIR
.get()
.map(|p| p.to_owned())
// Don't cache this in case the user uses std::env::set to change the temporary directory.
.unwrap_or_else(env::temp_dir)
/// **NOTE:**
///
/// 1. This function does check if the returned directory exists and/or is writable.
/// 2. This function caches the result of [`std::env::temp_dir`]. Any future changes to, e.g., the
/// `TMPDIR` environment variable won't have any effect.
pub fn temp_dir() -> &'static Path {
DEFAULT_TEMPDIR.get().unwrap_or_else(|| &ENV_TEMPDIR)
}

/// Override the default prefix for new temporary files (defaults to "tmp"). This function changes
/// the _global_ default prefix used by the entire program and should only be used by the top-level
/// application. It's recommended that the top-level application call this function to specify an
/// application-specific prefix to make it easier to identify temporary files belonging to the
/// application.
///
/// Only the **first** call to this function will succeed and return `Ok(prefix)` where `prefix` is
/// a static reference to the default temporary file prefix. All further calls will fail with
/// `Err(prefix)` where `prefix` is the previously set default temporary file prefix.
pub fn override_default_prefix(prefix: impl Into<String>) -> Result<&'static str, &'static str> {
let mut prefix = Some(prefix.into());
let val = DEFAULT_PREFIX.get_or_init(|| prefix.take().unwrap());
match prefix {
Some(_) => Err(val),
None => Ok(val),
}
}

/// Returns the default prefix used for new temporary files if no prefix is explicitly specified via
/// [`Builder::prefix`].
pub fn default_prefix() -> &'static str {
DEFAULT_PREFIX.get().map(|p| &**p).unwrap_or("tmp".as_ref())
}
5 changes: 2 additions & 3 deletions src/file/imp/unix.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::ffi::OsStr;
use std::fs::{self, File, OpenOptions};
use std::io;

Expand Down Expand Up @@ -72,8 +71,8 @@ pub fn create(dir: &Path) -> io::Result<File> {
fn create_unix(dir: &Path) -> io::Result<File> {
util::create_helper(
dir,
OsStr::new(".tmp"),
OsStr::new(""),
crate::env::default_prefix(),
"",
crate::NUM_RAND_CHARS,
|path| create_unlinked(&path),
)
Expand Down
5 changes: 2 additions & 3 deletions src/file/imp/windows.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::ffi::OsStr;
use std::fs::{File, OpenOptions};
use std::os::windows::ffi::OsStrExt;
use std::os::windows::fs::OpenOptionsExt;
Expand Down Expand Up @@ -63,8 +62,8 @@ pub fn create_named(
pub fn create(dir: &Path) -> io::Result<File> {
util::create_helper(
dir,
OsStr::new(".tmp"),
OsStr::new(""),
crate::env::default_prefix(),
"",
crate::NUM_RAND_CHARS,
|path| {
let f = OpenOptions::new()
Expand Down
35 changes: 18 additions & 17 deletions src/file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,13 +355,13 @@ impl Deref for TempPath {
}
}

impl AsRef<Path> for TempPath {
impl AsRef<Path> for &TempPath {
fn as_ref(&self) -> &Path {
&self.path
}
}

impl AsRef<OsStr> for TempPath {
impl AsRef<OsStr> for &TempPath {
fn as_ref(&self) -> &OsStr {
self.path.as_os_str()
}
Expand Down Expand Up @@ -445,7 +445,7 @@ impl<F> fmt::Debug for NamedTempFile<F> {
}
}

impl<F> AsRef<Path> for NamedTempFile<F> {
impl<F> AsRef<Path> for &NamedTempFile<F> {
#[inline]
fn as_ref(&self) -> &Path {
self.path()
Expand Down Expand Up @@ -567,8 +567,8 @@ impl NamedTempFile<File> {
/// See [`NamedTempFile::new()`] for details.
///
/// [`NamedTempFile::new()`]: #method.new
pub fn with_suffix<S: AsRef<OsStr>>(suffix: S) -> io::Result<NamedTempFile> {
Builder::new().suffix(&suffix).tempfile()
pub fn with_suffix(suffix: &str) -> io::Result<NamedTempFile> {
Builder::new().suffix(suffix).tempfile()
}
/// Create a new named temporary file with the specified filename suffix,
/// in the specified directory.
Expand All @@ -582,20 +582,17 @@ impl NamedTempFile<File> {
/// See [`NamedTempFile::new()`] for details.
///
/// [`NamedTempFile::new()`]: #method.new
pub fn with_suffix_in<S: AsRef<OsStr>, P: AsRef<Path>>(
suffix: S,
dir: P,
) -> io::Result<NamedTempFile> {
Builder::new().suffix(&suffix).tempfile_in(dir)
pub fn with_suffix_in<P: AsRef<Path>>(suffix: &str, dir: P) -> io::Result<NamedTempFile> {
Builder::new().suffix(suffix).tempfile_in(dir)
}

/// Create a new named temporary file with the specified filename prefix.
///
/// See [`NamedTempFile::new()`] for details.
///
/// [`NamedTempFile::new()`]: #method.new
pub fn with_prefix<S: AsRef<OsStr>>(prefix: S) -> io::Result<NamedTempFile> {
Builder::new().prefix(&prefix).tempfile()
pub fn with_prefix(prefix: &str) -> io::Result<NamedTempFile> {
Builder::new().prefix(prefix).tempfile()
}
/// Create a new named temporary file with the specified filename prefix,
/// in the specified directory.
Expand All @@ -609,11 +606,8 @@ impl NamedTempFile<File> {
/// See [`NamedTempFile::new()`] for details.
///
/// [`NamedTempFile::new()`]: #method.new
pub fn with_prefix_in<S: AsRef<OsStr>, P: AsRef<Path>>(
prefix: S,
dir: P,
) -> io::Result<NamedTempFile> {
Builder::new().prefix(&prefix).tempfile_in(dir)
pub fn with_prefix_in<P: AsRef<Path>>(prefix: &str, dir: P) -> io::Result<NamedTempFile> {
Builder::new().prefix(prefix).tempfile_in(dir)
}
}

Expand Down Expand Up @@ -998,12 +992,19 @@ impl<F: Seek> Seek for NamedTempFile<F> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
self.as_file_mut().seek(pos).with_err_path(|| self.path())
}
fn rewind(&mut self) -> io::Result<()> {
self.as_file_mut().rewind().with_err_path(|| self.path())
}
}

impl Seek for &NamedTempFile<File> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
self.as_file().seek(pos).with_err_path(|| self.path())
}

fn rewind(&mut self) -> io::Result<()> {
self.as_file().rewind().with_err_path(|| self.path())
}
}

#[cfg(any(unix, target_os = "wasi"))]
Expand Down
Loading