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 all commits
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
43 changes: 43 additions & 0 deletions src/env.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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. All further calls will fail with `Err(path)`
/// where `path` is 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())
}
}

/// 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`].
///
/// **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)
}
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