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

feat: add the ability to override the default temporary directory #286

Merged
merged 3 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 3.11.0

- Add the ability to override the default temporary directory. This API shouldn't be used in general, but there are some cases where it's unavoidable.

## 3.10.1

- Handle potential integer overflows in 32-bit systems when seeking/truncating "spooled" temporary files past 4GiB (2³²).
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ description = "A library for managing temporary files and directories."
[dependencies]
cfg-if = "1"
fastrand = "2.0.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, target_os = "wasi"))'.dependencies]
rustix = { version = "0.38.31", features = ["fs"] }
Expand Down
16 changes: 9 additions & 7 deletions src/dir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ use std::{fmt, io};
use crate::error::IoResultExt;
use crate::Builder;

#[cfg(doc)]
use crate::env;

/// Create a new temporary directory.
///
/// The `tempdir` function creates a directory in the file system
Expand All @@ -39,7 +42,7 @@ use crate::Builder;
/// use std::fs::File;
/// use std::io::Write;
///
/// // Create a directory inside of `std::env::temp_dir()`
/// // Create a directory inside of `env::temp_dir()`
/// let tmp_dir = tempdir()?;
///
/// let file_path = tmp_dir.path().join("my-temporary-note.txt");
Expand Down Expand Up @@ -109,7 +112,7 @@ pub fn tempdir_in<P: AsRef<Path>>(dir: P) -> io::Result<TempDir> {
/// `TempDir` creates a new directory with a randomly generated name.
///
/// The default constructor, [`TempDir::new()`], creates directories in
/// the location returned by [`std::env::temp_dir()`], but `TempDir`
/// the location returned by [`env::temp_dir()`], but `TempDir`
/// can be configured to manage a temporary directory in any location
/// by constructing with a [`Builder`].
///
Expand Down Expand Up @@ -144,7 +147,7 @@ pub fn tempdir_in<P: AsRef<Path>>(dir: P) -> io::Result<TempDir> {
/// use std::io::Write;
/// use tempfile::TempDir;
///
/// // Create a directory inside of `std::env::temp_dir()`
/// // Create a directory inside of `env::temp_dir()`
/// let tmp_dir = TempDir::new()?;
/// # Ok::<(), std::io::Error>(())
/// ```
Expand All @@ -156,7 +159,7 @@ pub fn tempdir_in<P: AsRef<Path>>(dir: P) -> io::Result<TempDir> {
/// use std::io::Write;
/// use tempfile::Builder;
///
/// // Create a directory inside of `std::env::temp_dir()`,
/// // Create a directory inside of `env::temp_dir()`,
/// // whose name will begin with 'example'.
/// let tmp_dir = Builder::new().prefix("example").tempdir()?;
/// # Ok::<(), std::io::Error>(())
Expand All @@ -170,7 +173,6 @@ pub fn tempdir_in<P: AsRef<Path>>(dir: P) -> io::Result<TempDir> {
/// [`TempDir::new()`]: struct.TempDir.html#method.new
/// [`TempDir::path()`]: struct.TempDir.html#method.path
/// [`TempDir`]: struct.TempDir.html
/// [`std::env::temp_dir()`]: https://doc.rust-lang.org/std/env/fn.temp_dir.html
/// [`std::fs`]: http://doc.rust-lang.org/std/fs/index.html
/// [`std::process::exit()`]: http://doc.rust-lang.org/std/process/fn.exit.html
pub struct TempDir {
Expand All @@ -196,7 +198,7 @@ impl TempDir {
/// use std::io::Write;
/// use tempfile::TempDir;
///
/// // Create a directory inside of `std::env::temp_dir()`
/// // Create a directory inside of `env::temp_dir()`
/// let tmp_dir = TempDir::new()?;
///
/// let file_path = tmp_dir.path().join("my-temporary-note.txt");
Expand Down Expand Up @@ -378,7 +380,7 @@ impl TempDir {
/// use std::io::Write;
/// use tempfile::TempDir;
///
/// // Create a directory inside of `std::env::temp_dir()`.
/// // Create a directory inside of `env::temp_dir()`.
/// let tmp_dir = TempDir::new()?;
/// let file_path = tmp_dir.path().join("my-temporary-note.txt");
/// let mut tmp_file = File::create(file_path)?;
Expand Down
40 changes: 40 additions & 0 deletions src/env.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use std::env;
use std::path::{Path, PathBuf};

// Once rust 1.70 is wide-spread (Debian stable), we can use OnceLock from stdlib.
use once_cell::sync::OnceCell as OnceLock;

static DEFAULT_TEMPDIR: OnceLock<PathBuf> = 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.
///
/// Only the first call to this function will succeed, returning `Ok(path)`. All further calls will
/// fail with `Err(path)`. In both cases, `path` is the default temporary directory (on success, the
/// new one; on failure, the existing one).
pub fn override_temp_dir(path: &Path) -> Result<&'static Path, &'static Path> {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered treating this as a fallback (TMPDIR -> the override -> "/tmp"), but handling all the platform-specific cases got hairy.

I also considered testing if the various temporary directories were writable.... but honestly, I don't want to be in the position of "guessing" where temporary files should be written (although I'll revisit that later if this keeps coming up).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest Result<(), TempDirAlreadyOverridden> as the return type.

In the success case there is not point in returning the value just passed in.
In the error case it is problematic that Path does not implement Display.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs: Is there a requirement that the directory already exists (I think so) or will it be created (recursively) if non-existent?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the success case there is not point in returning the value just passed in.

It returns a static reference, which could be convenient if you need to know where the temporary directory is. But... it's inconsistent with temp_dir, so it's probably not all that useful.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. Ok, I'm going to change it to return Result<(), PathBuf>, but I actually don't want the user to unwrap() this error, they need to handle it.

let mut we_set = false;
let val = DEFAULT_TEMPDIR.get_or_init(|| {
we_set = true;
path.to_path_buf()
});
if we_set {
Ok(val)
} else {
Err(val)
}
}

/// Returns the default temporary directory, used for both temporary directories and files if no
/// directory is explicitly specified.
///
/// This function simply delegates to [`std::env::temp_dir`] unless the default temporary directory
/// has been override by a call to [`override_temp_dir`].
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)
}
3 changes: 1 addition & 2 deletions src/file/imp/unix.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::env;
use std::ffi::OsStr;
use std::fs::{self, File, OpenOptions};
use std::io;
Expand Down Expand Up @@ -40,7 +39,7 @@ fn create_unlinked(path: &Path) -> io::Result<File> {
// shadow this to decrease the lifetime. It can't live longer than `tmp`.
let mut path = path;
if !path.is_absolute() {
let cur_dir = env::current_dir()?;
let cur_dir = std::env::current_dir()?;
tmp = cur_dir.join(path);
path = &tmp;
}
Expand Down
17 changes: 6 additions & 11 deletions src/file/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::env;
use std::error;
use std::ffi::OsStr;
use std::fmt;
Expand All @@ -14,14 +13,15 @@ use std::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, RawHandle};
use std::path::{Path, PathBuf};

use crate::env;
use crate::error::IoResultExt;
use crate::Builder;

mod imp;

/// Create a new temporary file.
///
/// The file will be created in the location returned by [`std::env::temp_dir()`].
/// The file will be created in the location returned by [`env::temp_dir()`].
///
/// # Security
///
Expand All @@ -42,14 +42,12 @@ mod imp;
/// use tempfile::tempfile;
/// use std::io::Write;
///
/// // Create a file inside of `std::env::temp_dir()`.
/// // Create a file inside of `env::temp_dir()`.
/// let mut file = tempfile()?;
///
/// writeln!(file, "Brian was here. Briefly.")?;
/// # Ok::<(), std::io::Error>(())
/// ```
///
/// [`std::env::temp_dir()`]: https://doc.rust-lang.org/std/env/fn.temp_dir.html
pub fn tempfile() -> io::Result<File> {
tempfile_in(env::temp_dir())
}
Expand All @@ -59,7 +57,7 @@ pub fn tempfile() -> io::Result<File> {
/// # Security
///
/// This variant is secure/reliable in the presence of a pathological temporary file cleaner.
/// If the temporary file isn't created in [`std::env::temp_dir()`] then temporary file cleaners aren't an issue.
/// If the temporary file isn't created in [`env::temp_dir()`] then temporary file cleaners aren't an issue.
///
/// # Resource Leaking
///
Expand All @@ -82,8 +80,6 @@ pub fn tempfile() -> io::Result<File> {
/// writeln!(file, "Brian was here. Briefly.")?;
/// # Ok::<(), std::io::Error>(())
/// ```
///
/// [`std::env::temp_dir()`]: https://doc.rust-lang.org/std/env/fn.temp_dir.html
pub fn tempfile_in<P: AsRef<Path>>(dir: P) -> io::Result<File> {
imp::create(dir.as_ref())
}
Expand Down Expand Up @@ -363,7 +359,7 @@ impl AsRef<OsStr> for TempPath {
/// A named temporary file.
///
/// The default constructor, [`NamedTempFile::new()`], creates files in
/// the location returned by [`std::env::temp_dir()`], but `NamedTempFile`
/// the location returned by [`env::temp_dir()`], but `NamedTempFile`
/// can be configured to manage a temporary file in any location
/// by constructing with [`NamedTempFile::new_in()`].
///
Expand Down Expand Up @@ -440,7 +436,6 @@ impl AsRef<OsStr> for TempPath {
/// [`tempfile()`]: fn.tempfile.html
/// [`NamedTempFile::new()`]: #method.new
/// [`NamedTempFile::new_in()`]: #method.new_in
/// [`std::env::temp_dir()`]: https://doc.rust-lang.org/std/env/fn.temp_dir.html
/// [`std::process::exit()`]: http://doc.rust-lang.org/std/process/fn.exit.html
/// [`lazy_static`]: https://github.com/rust-lang-nursery/lazy-static.rs/issues/62
pub struct NamedTempFile<F = File> {
Expand Down Expand Up @@ -1017,7 +1012,7 @@ pub(crate) fn create_named(
// Make the path absolute. Otherwise, changing directories could cause us to
// delete the wrong file.
if !path.is_absolute() {
path = env::current_dir()?.join(path)
path = std::env::current_dir()?.join(path)
}
imp::create_named(&path, open_options, permissions)
.with_err_path(|| path.clone())
Expand Down
19 changes: 10 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
//! use tempfile::tempdir;
//! use std::process::Command;
//!
//! // Create a directory inside of `std::env::temp_dir()`.
//! // Create a directory inside of `env::temp_dir()`.
//! let temp_dir = tempdir()?;
//!
//! // Spawn the `touch` command inside the temporary directory and collect the exit status
Expand All @@ -67,7 +67,7 @@
//! use tempfile::tempfile;
//! use std::io::Write;
//!
//! // Create a file inside of `std::env::temp_dir()`.
//! // Create a file inside of `env::temp_dir()`.
//! let mut file = tempfile()?;
//!
//! writeln!(file, "Brian was here. Briefly.")?;
Expand All @@ -82,7 +82,7 @@
//!
//! let text = "Brian was here. Briefly.";
//!
//! // Create a file inside of `std::env::temp_dir()`.
//! // Create a file inside of `env::temp_dir()`.
//! let mut file1 = NamedTempFile::new()?;
//!
//! // Re-open it.
Expand All @@ -105,7 +105,7 @@
//! use std::fs::File;
//! use std::io::Write;
//!
//! // Create a directory inside of `std::env::temp_dir()`.
//! // Create a directory inside of `env::temp_dir()`.
//! let dir = tempdir()?;
//!
//! let file_path = dir.path().join("my-temporary-note.txt");
Expand All @@ -126,7 +126,6 @@
//! [`tempdir()`]: fn.tempdir.html
//! [`TempDir`]: struct.TempDir.html
//! [`NamedTempFile`]: struct.NamedTempFile.html
//! [`std::env::temp_dir()`]: https://doc.rust-lang.org/std/env/fn.temp_dir.html
//! [`lazy_static`]: https://github.com/rust-lang-nursery/lazy-static.rs/issues/62

#![doc(
Expand All @@ -147,15 +146,17 @@ const NUM_RAND_CHARS: usize = 6;

use std::ffi::OsStr;
use std::fs::OpenOptions;
use std::io;
use std::path::Path;
use std::{env, io};

mod dir;
mod error;
mod file;
mod spooled;
mod util;

pub mod env;

pub use crate::dir::{tempdir, tempdir_in, TempDir};
pub use crate::file::{
tempfile, tempfile_in, NamedTempFile, PathPersistError, PersistError, TempPath,
Expand Down Expand Up @@ -469,7 +470,7 @@ impl<'a, 'b> Builder<'a, 'b> {
)
}

/// Attempts to make a temporary directory inside of `env::temp_dir()` whose
/// Attempts to make a temporary directory inside of [`env::temp_dir()`] whose
/// name will have the prefix, `prefix`. The directory and
/// everything inside it will be automatically deleted once the
/// returned `TempDir` is destroyed.
Expand Down Expand Up @@ -522,7 +523,7 @@ impl<'a, 'b> Builder<'a, 'b> {
let storage;
let mut dir = dir.as_ref();
if !dir.is_absolute() {
let cur_dir = env::current_dir()?;
let cur_dir = std::env::current_dir()?;
storage = cur_dir.join(dir);
dir = &storage;
}
Expand All @@ -540,7 +541,7 @@ impl<'a, 'b> Builder<'a, 'b> {
/// Attempts to create a temporary file (or file-like object) using the
/// provided closure. The closure is passed a temporary file path and
/// returns an [`std::io::Result`]. The path provided to the closure will be
/// inside of [`std::env::temp_dir()`]. Use [`Builder::make_in`] to provide
/// inside of [`env::temp_dir()`]. Use [`Builder::make_in`] to provide
/// a custom temporary directory. If the closure returns one of the
/// following errors, then another randomized file path is tried:
/// - [`std::io::ErrorKind::AlreadyExists`]
Expand Down
9 changes: 4 additions & 5 deletions tests/namedtempfile.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
#![deny(rust_2018_idioms)]

use std::env;
use std::ffi::{OsStr, OsString};
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
use tempfile::{tempdir, Builder, NamedTempFile, TempPath};
use tempfile::{env, tempdir, Builder, NamedTempFile, TempPath};

fn exists<P: AsRef<Path>>(path: P) -> bool {
std::fs::metadata(path.as_ref()).is_ok()
Expand Down Expand Up @@ -276,10 +275,10 @@ fn test_write_after_close() {

#[test]
fn test_change_dir() {
env::set_current_dir(env::temp_dir()).unwrap();
std::env::set_current_dir(env::temp_dir()).unwrap();
let tmpfile = NamedTempFile::new_in(".").unwrap();
let path = env::current_dir().unwrap().join(tmpfile.path());
env::set_current_dir("/").unwrap();
let path = std::env::current_dir().unwrap().join(tmpfile.path());
std::env::set_current_dir("/").unwrap();
drop(tmpfile);
assert!(!exists(path))
}
Expand Down
3 changes: 1 addition & 2 deletions tests/tempdir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

#![deny(rust_2018_idioms)]

use std::env;
use std::fs;
use std::path::Path;
use std::sync::mpsc::channel;
Expand Down Expand Up @@ -149,7 +148,7 @@ where
F: FnOnce(),
{
let tmpdir = TempDir::new().unwrap();
assert!(env::set_current_dir(tmpdir.path()).is_ok());
assert!(std::env::set_current_dir(tmpdir.path()).is_ok());

f();
}
Expand Down