diff --git a/CHANGELOG.md b/CHANGELOG.md index 1955a7ed9..82f650079 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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³²). diff --git a/Cargo.toml b/Cargo.toml index 0bfa28e55..503f15296 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } diff --git a/src/dir/mod.rs b/src/dir/mod.rs index ba1b0db16..ea9017d4c 100644 --- a/src/dir/mod.rs +++ b/src/dir/mod.rs @@ -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 @@ -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"); @@ -109,7 +112,7 @@ pub fn tempdir_in>(dir: P) -> io::Result { /// `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`]. /// @@ -144,7 +147,7 @@ pub fn tempdir_in>(dir: P) -> io::Result { /// 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>(()) /// ``` @@ -156,7 +159,7 @@ pub fn tempdir_in>(dir: P) -> io::Result { /// 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>(()) @@ -170,7 +173,6 @@ pub fn tempdir_in>(dir: P) -> io::Result { /// [`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 { @@ -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"); @@ -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)?; diff --git a/src/env.rs b/src/env.rs new file mode 100644 index 000000000..86d51992a --- /dev/null +++ b/src/env.rs @@ -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 = 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) +} diff --git a/src/file/imp/unix.rs b/src/file/imp/unix.rs index 5f2cb5dcb..2a3cd9ab1 100644 --- a/src/file/imp/unix.rs +++ b/src/file/imp/unix.rs @@ -1,4 +1,3 @@ -use std::env; use std::ffi::OsStr; use std::fs::{self, File, OpenOptions}; use std::io; @@ -40,7 +39,7 @@ fn create_unlinked(path: &Path) -> io::Result { // 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; } diff --git a/src/file/mod.rs b/src/file/mod.rs index 2aad4cd9b..2780ae14e 100644 --- a/src/file/mod.rs +++ b/src/file/mod.rs @@ -1,4 +1,3 @@ -use std::env; use std::error; use std::ffi::OsStr; use std::fmt; @@ -14,6 +13,7 @@ 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; @@ -21,7 +21,7 @@ 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 /// @@ -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 { tempfile_in(env::temp_dir()) } @@ -59,7 +57,7 @@ pub fn tempfile() -> io::Result { /// # 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 /// @@ -82,8 +80,6 @@ pub fn tempfile() -> io::Result { /// 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>(dir: P) -> io::Result { imp::create(dir.as_ref()) } @@ -363,7 +359,7 @@ impl AsRef 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()`]. /// @@ -440,7 +436,6 @@ impl AsRef 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 { @@ -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()) diff --git a/src/lib.rs b/src/lib.rs index 4e78037bd..057b82b31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 @@ -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.")?; @@ -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. @@ -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"); @@ -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( @@ -147,8 +146,8 @@ 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; @@ -156,6 +155,8 @@ 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, @@ -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. @@ -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; } @@ -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`] diff --git a/tests/namedtempfile.rs b/tests/namedtempfile.rs index 4b940b6b8..34ddc44bc 100644 --- a/tests/namedtempfile.rs +++ b/tests/namedtempfile.rs @@ -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>(path: P) -> bool { std::fs::metadata(path.as_ref()).is_ok() @@ -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)) } diff --git a/tests/tempdir.rs b/tests/tempdir.rs index 8ca7984ac..2bc4001df 100644 --- a/tests/tempdir.rs +++ b/tests/tempdir.rs @@ -10,7 +10,6 @@ #![deny(rust_2018_idioms)] -use std::env; use std::fs; use std::path::Path; use std::sync::mpsc::channel; @@ -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(); }