Skip to content

Commit

Permalink
feat: add the ability to override the default temporary directory (#286)
Browse files Browse the repository at this point in the history
This adds a new `env` module with `override_temp_dir` and `temp_dir`
functions.

- `temp_dir` will defer to `std::env::temp_dir` by default unless the
temporary directory has been overridden.
- `override_temp_dir` allows the user to override the default temporary
directory ONCE. Once this value has been set, it cannot be changed Care
should be taken to ensure that the chosen directory is actually writable.

This API is designed for use by the application author when the
application may run in an environment without a reliable global
temporary directory (e.g., Android).

fixes #285
  • Loading branch information
Stebalien authored Jul 29, 2024
1 parent e5418bd commit ce8b147
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 36 deletions.
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

0 comments on commit ce8b147

Please sign in to comment.