Source code

Revision control

Copy as Markdown

Other Tools

//! Temporary files and directories.
//!
//! - Use the [`tempfile()`] function for temporary files
//! - Use the [`tempdir()`] function for temporary directories.
//!
//! # Design
//!
//! This crate provides several approaches to creating temporary files and directories.
//! [`tempfile()`] relies on the OS to remove the temporary file once the last handle is closed.
//! [`TempDir`] and [`NamedTempFile`] both rely on Rust destructors for cleanup.
//!
//! When choosing between the temporary file variants, prefer `tempfile`
//! unless you either need to know the file's path or to be able to persist it.
//!
//! ## Resource Leaking
//!
//! `tempfile` will (almost) never fail to cleanup temporary resources. However `TempDir` and `NamedTempFile` will
//! fail if their destructors don't run. This is because `tempfile` relies on the OS to cleanup the
//! underlying file, while `TempDir` and `NamedTempFile` rely on rust destructors to do so.
//! Destructors may fail to run if the process exits through an unhandled signal interrupt (like `SIGINT`),
//! or if the instance is declared statically (like with [`lazy_static`]), among other possible
//! reasons.
//!
//! ## Security
//!
//! In the presence of pathological temporary file cleaner, relying on file paths is unsafe because
//! a temporary file cleaner could delete the temporary file which an attacker could then replace.
//!
//! `tempfile` doesn't rely on file paths so this isn't an issue. However, `NamedTempFile` does
//! rely on file paths for _some_ operations. See the security documentation on
//! the `NamedTempFile` type for more information.
//!
//! ## Early drop pitfall
//!
//! Because `TempDir` and `NamedTempFile` rely on their destructors for cleanup, this can lead
//! to an unexpected early removal of the directory/file, usually when working with APIs which are
//! generic over `AsRef<Path>`. Consider the following example:
//!
//! ```no_run
//! # use tempfile::tempdir;
//! # use std::io;
//! # use std::process::Command;
//! # fn main() {
//! # if let Err(_) = run() {
//! # ::std::process::exit(1);
//! # }
//! # }
//! # fn run() -> Result<(), io::Error> {
//! // Create a directory inside of `std::env::temp_dir()`.
//! let temp_dir = tempdir()?;
//!
//! // Spawn the `touch` command inside the temporary directory and collect the exit status
//! // Note that `temp_dir` is **not** moved into `current_dir`, but passed as a reference
//! let exit_status = Command::new("touch").arg("tmp").current_dir(&temp_dir).status()?;
//! assert!(exit_status.success());
//!
//! # Ok(())
//! # }
//! ```
//!
//! This works because a reference to `temp_dir` is passed to `current_dir`, resulting in the
//! destructor of `temp_dir` being run after the `Command` has finished execution. Moving the
//! `TempDir` into the `current_dir` call would result in the `TempDir` being converted into
//! an internal representation, with the original value being dropped and the directory thus
//! being deleted, before the command can be executed.
//!
//! The `touch` command would fail with an `No such file or directory` error.
//!
//! ## Examples
//!
//! Create a temporary file and write some data into it:
//!
//! ```
//! use tempfile::tempfile;
//! use std::io::{self, Write};
//!
//! # fn main() {
//! # if let Err(_) = run() {
//! # ::std::process::exit(1);
//! # }
//! # }
//! # fn run() -> Result<(), io::Error> {
//! // Create a file inside of `std::env::temp_dir()`.
//! let mut file = tempfile()?;
//!
//! writeln!(file, "Brian was here. Briefly.")?;
//! # Ok(())
//! # }
//! ```
//!
//! Create a named temporary file and open an independent file handle:
//!
//! ```
//! use tempfile::NamedTempFile;
//! use std::io::{self, Write, Read};
//!
//! # fn main() {
//! # if let Err(_) = run() {
//! # ::std::process::exit(1);
//! # }
//! # }
//! # fn run() -> Result<(), io::Error> {
//! let text = "Brian was here. Briefly.";
//!
//! // Create a file inside of `std::env::temp_dir()`.
//! let mut file1 = NamedTempFile::new()?;
//!
//! // Re-open it.
//! let mut file2 = file1.reopen()?;
//!
//! // Write some test data to the first handle.
//! file1.write_all(text.as_bytes())?;
//!
//! // Read the test data using the second handle.
//! let mut buf = String::new();
//! file2.read_to_string(&mut buf)?;
//! assert_eq!(buf, text);
//! # Ok(())
//! # }
//! ```
//!
//! Create a temporary directory and add a file to it:
//!
//! ```
//! use tempfile::tempdir;
//! use std::fs::File;
//! use std::io::{self, Write};
//!
//! # fn main() {
//! # if let Err(_) = run() {
//! # ::std::process::exit(1);
//! # }
//! # }
//! # fn run() -> Result<(), io::Error> {
//! // Create a directory inside of `std::env::temp_dir()`.
//! let dir = tempdir()?;
//!
//! let file_path = dir.path().join("my-temporary-note.txt");
//! let mut file = File::create(file_path)?;
//! writeln!(file, "Brian was here. Briefly.")?;
//!
//! // By closing the `TempDir` explicitly, we can check that it has
//! // been deleted successfully. If we don't close it explicitly,
//! // the directory will still be deleted when `dir` goes out
//! // of scope, but we won't know whether deleting the directory
//! // succeeded.
//! drop(file);
//! dir.close()?;
//! # Ok(())
//! # }
//! ```
//!
//! [`tempfile()`]: fn.tempfile.html
//! [`tempdir()`]: fn.tempdir.html
//! [`TempDir`]: struct.TempDir.html
//! [`NamedTempFile`]: struct.NamedTempFile.html
#![doc(
html_root_url = "https://docs.rs/tempfile/3.1.0"
)]
#![cfg_attr(test, deny(warnings))]
#![deny(rust_2018_idioms)]
#![allow(clippy::redundant_field_names)]
#![cfg_attr(all(feature = "nightly", target_os = "wasi"), feature(wasi_ext))]
#[cfg(doctest)]
doc_comment::doctest!("../README.md");
const NUM_RETRIES: u32 = 1 << 31;
const NUM_RAND_CHARS: usize = 6;
use std::ffi::OsStr;
use std::fs::OpenOptions;
use std::path::Path;
use std::{env, io};
mod dir;
mod error;
mod file;
mod spooled;
mod util;
pub use crate::dir::{tempdir, tempdir_in, TempDir};
pub use crate::file::{
tempfile, tempfile_in, NamedTempFile, PathPersistError, PersistError, TempPath,
};
pub use crate::spooled::{spooled_tempfile, SpooledTempFile};
/// Create a new temporary file or directory with custom parameters.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Builder<'a, 'b> {
random_len: usize,
prefix: &'a OsStr,
suffix: &'b OsStr,
append: bool,
}
impl<'a, 'b> Default for Builder<'a, 'b> {
fn default() -> Self {
Builder {
random_len: crate::NUM_RAND_CHARS,
prefix: OsStr::new(".tmp"),
suffix: OsStr::new(""),
append: false,
}
}
}
impl<'a, 'b> Builder<'a, 'b> {
/// Create a new `Builder`.
///
/// # Examples
///
/// Create a named temporary file and write some data into it:
///
/// ```
/// # use std::io;
/// # use std::ffi::OsStr;
/// # fn main() {
/// # if let Err(_) = run() {
/// # ::std::process::exit(1);
/// # }
/// # }
/// # fn run() -> Result<(), io::Error> {
/// use tempfile::Builder;
///
/// let named_tempfile = Builder::new()
/// .prefix("my-temporary-note")
/// .suffix(".txt")
/// .rand_bytes(5)
/// .tempfile()?;
///
/// let name = named_tempfile
/// .path()
/// .file_name().and_then(OsStr::to_str);
///
/// if let Some(name) = name {
/// assert!(name.starts_with("my-temporary-note"));
/// assert!(name.ends_with(".txt"));
/// assert_eq!(name.len(), "my-temporary-note.txt".len() + 5);
/// }
/// # Ok(())
/// # }
/// ```
///
/// Create a temporary directory and add a file to it:
///
/// ```
/// # use std::io::{self, Write};
/// # use std::fs::File;
/// # use std::ffi::OsStr;
/// # fn main() {
/// # if let Err(_) = run() {
/// # ::std::process::exit(1);
/// # }
/// # }
/// # fn run() -> Result<(), io::Error> {
/// use tempfile::Builder;
///
/// let dir = Builder::new()
/// .prefix("my-temporary-dir")
/// .rand_bytes(5)
/// .tempdir()?;
///
/// let file_path = dir.path().join("my-temporary-note.txt");
/// let mut file = File::create(file_path)?;
/// writeln!(file, "Brian was here. Briefly.")?;
///
/// // By closing the `TempDir` explicitly, we can check that it has
/// // been deleted successfully. If we don't close it explicitly,
/// // the directory will still be deleted when `dir` goes out
/// // of scope, but we won't know whether deleting the directory
/// // succeeded.
/// drop(file);
/// dir.close()?;
/// # Ok(())
/// # }
/// ```
///
/// Create a temporary directory with a chosen prefix under a chosen folder:
///
/// ```ignore
/// let dir = Builder::new()
/// .prefix("my-temporary-dir")
/// .tempdir_in("folder-with-tempdirs")?;
/// ```
#[must_use]
pub fn new() -> Self {
Self::default()
}
/// Set a custom filename prefix.
///
/// Path separators are legal but not advisable.
/// Default: `.tmp`.
///
/// # Examples
///
/// ```
/// # use std::io;
/// # fn main() {
/// # if let Err(_) = run() {
/// # ::std::process::exit(1);
/// # }
/// # }
/// # fn run() -> Result<(), io::Error> {
/// # use tempfile::Builder;
/// let named_tempfile = Builder::new()
/// .prefix("my-temporary-note")
/// .tempfile()?;
/// # Ok(())
/// # }
/// ```
pub fn prefix<S: AsRef<OsStr> + ?Sized>(&mut self, prefix: &'a S) -> &mut Self {
self.prefix = prefix.as_ref();
self
}
/// Set a custom filename suffix.
///
/// Path separators are legal but not advisable.
/// Default: empty.
///
/// # Examples
///
/// ```
/// # use std::io;
/// # fn main() {
/// # if let Err(_) = run() {
/// # ::std::process::exit(1);
/// # }
/// # }
/// # fn run() -> Result<(), io::Error> {
/// # use tempfile::Builder;
/// let named_tempfile = Builder::new()
/// .suffix(".txt")
/// .tempfile()?;
/// # Ok(())
/// # }
/// ```
pub fn suffix<S: AsRef<OsStr> + ?Sized>(&mut self, suffix: &'b S) -> &mut Self {
self.suffix = suffix.as_ref();
self
}
/// Set the number of random bytes.
///
/// Default: `6`.
///
/// # Examples
///
/// ```
/// # use std::io;
/// # fn main() {
/// # if let Err(_) = run() {
/// # ::std::process::exit(1);
/// # }
/// # }
/// # fn run() -> Result<(), io::Error> {
/// # use tempfile::Builder;
/// let named_tempfile = Builder::new()
/// .rand_bytes(5)
/// .tempfile()?;
/// # Ok(())
/// # }
/// ```
pub fn rand_bytes(&mut self, rand: usize) -> &mut Self {
self.random_len = rand;
self
}
/// Set the file to be opened in append mode.
///
/// Default: `false`.
///
/// # Examples
///
/// ```
/// # use std::io;
/// # fn main() {
/// # if let Err(_) = run() {
/// # ::std::process::exit(1);
/// # }
/// # }
/// # fn run() -> Result<(), io::Error> {
/// # use tempfile::Builder;
/// let named_tempfile = Builder::new()
/// .append(true)
/// .tempfile()?;
/// # Ok(())
/// # }
/// ```
pub fn append(&mut self, append: bool) -> &mut Self {
self.append = append;
self
}
/// Create the named temporary file.
///
/// # Security
///
/// See [the security][security] docs on `NamedTempFile`.
///
/// # Resource leaking
///
/// See [the resource leaking][resource-leaking] docs on `NamedTempFile`.
///
/// # Errors
///
/// If the file cannot be created, `Err` is returned.
///
/// # Examples
///
/// ```
/// # use std::io;
/// # fn main() {
/// # if let Err(_) = run() {
/// # ::std::process::exit(1);
/// # }
/// # }
/// # fn run() -> Result<(), io::Error> {
/// # use tempfile::Builder;
/// let tempfile = Builder::new().tempfile()?;
/// # Ok(())
/// # }
/// ```
///
/// [security]: struct.NamedTempFile.html#security
/// [resource-leaking]: struct.NamedTempFile.html#resource-leaking
pub fn tempfile(&self) -> io::Result<NamedTempFile> {
self.tempfile_in(env::temp_dir())
}
/// Create the named temporary file in the specified directory.
///
/// # Security
///
/// See [the security][security] docs on `NamedTempFile`.
///
/// # Resource leaking
///
/// See [the resource leaking][resource-leaking] docs on `NamedTempFile`.
///
/// # Errors
///
/// If the file cannot be created, `Err` is returned.
///
/// # Examples
///
/// ```
/// # use std::io;
/// # fn main() {
/// # if let Err(_) = run() {
/// # ::std::process::exit(1);
/// # }
/// # }
/// # fn run() -> Result<(), io::Error> {
/// # use tempfile::Builder;
/// let tempfile = Builder::new().tempfile_in("./")?;
/// # Ok(())
/// # }
/// ```
///
/// [security]: struct.NamedTempFile.html#security
/// [resource-leaking]: struct.NamedTempFile.html#resource-leaking
pub fn tempfile_in<P: AsRef<Path>>(&self, dir: P) -> io::Result<NamedTempFile> {
util::create_helper(
dir.as_ref(),
self.prefix,
self.suffix,
self.random_len,
|path| file::create_named(path, OpenOptions::new().append(self.append)),
)
}
/// 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.
///
/// # Resource leaking
///
/// See [the resource leaking][resource-leaking] docs on `TempDir`.
///
/// # Errors
///
/// If the directory can not be created, `Err` is returned.
///
/// # Examples
///
/// ```
/// use std::fs::File;
/// use std::io::Write;
/// use tempfile::Builder;
///
/// # use std::io;
/// # fn run() -> Result<(), io::Error> {
/// let tmp_dir = Builder::new().tempdir()?;
/// # Ok(())
/// # }
/// ```
///
/// [resource-leaking]: struct.TempDir.html#resource-leaking
pub fn tempdir(&self) -> io::Result<TempDir> {
self.tempdir_in(env::temp_dir())
}
/// Attempts to make a temporary directory inside of `dir`.
/// The directory and everything inside it will be automatically
/// deleted once the returned `TempDir` is destroyed.
///
/// # Resource leaking
///
/// See [the resource leaking][resource-leaking] docs on `TempDir`.
///
/// # Errors
///
/// If the directory can not be created, `Err` is returned.
///
/// # Examples
///
/// ```
/// use std::fs::{self, File};
/// use std::io::Write;
/// use tempfile::Builder;
///
/// # use std::io;
/// # fn run() -> Result<(), io::Error> {
/// let tmp_dir = Builder::new().tempdir_in("./")?;
/// # Ok(())
/// # }
/// ```
///
/// [resource-leaking]: struct.TempDir.html#resource-leaking
pub fn tempdir_in<P: AsRef<Path>>(&self, dir: P) -> io::Result<TempDir> {
let storage;
let mut dir = dir.as_ref();
if !dir.is_absolute() {
let cur_dir = env::current_dir()?;
storage = cur_dir.join(dir);
dir = &storage;
}
util::create_helper(dir, self.prefix, self.suffix, self.random_len, dir::create)
}
/// 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
/// a custom temporary directory. If the closure returns one of the
/// following errors, then another randomized file path is tried:
/// - [`std::io::ErrorKind::AlreadyExists`]
/// - [`std::io::ErrorKind::AddrInUse`]
///
/// This can be helpful for taking full control over the file creation, but
/// leaving the temporary file path construction up to the library. This
/// also enables creating a temporary UNIX domain socket, since it is not
/// possible to bind to a socket that already exists.
///
/// Note that [`Builder::append`] is ignored when using [`Builder::make`].
///
/// # Security
///
/// This has the same [security implications][security] as
/// [`NamedTempFile`], but with additional caveats. Specifically, it is up
/// to the closure to ensure that the file does not exist and that such a
/// check is *atomic*. Otherwise, a [time-of-check to time-of-use
/// bug][TOCTOU] could be introduced.
///
/// For example, the following is **not** secure:
///
/// ```
/// # use std::io;
/// # use std::fs::File;
/// # fn main() {
/// # if let Err(_) = run() {
/// # ::std::process::exit(1);
/// # }
/// # }
/// # fn run() -> Result<(), io::Error> {
/// # use tempfile::Builder;
/// // This is NOT secure!
/// let tempfile = Builder::new().make(|path| {
/// if path.is_file() {
/// return Err(io::ErrorKind::AlreadyExists.into());
/// }
///
/// // Between the check above and the usage below, an attacker could
/// // have replaced `path` with another file, which would get truncated
/// // by `File::create`.
///
/// File::create(path)
/// })?;
/// # Ok(())
/// # }
/// ```
/// Note that simply using [`std::fs::File::create`] alone is not correct
/// because it does not fail if the file already exists:
/// ```
/// # use std::io;
/// # use std::fs::File;
/// # fn main() {
/// # if let Err(_) = run() {
/// # ::std::process::exit(1);
/// # }
/// # }
/// # fn run() -> Result<(), io::Error> {
/// # use tempfile::Builder;
/// // This could overwrite an existing file!
/// let tempfile = Builder::new().make(|path| File::create(path))?;
/// # Ok(())
/// # }
/// ```
/// For creating regular temporary files, use [`Builder::tempfile`] instead
/// to avoid these problems. This function is meant to enable more exotic
/// use-cases.
///
/// # Resource leaking
///
/// See [the resource leaking][resource-leaking] docs on `NamedTempFile`.
///
/// # Errors
///
/// If the closure returns any error besides
/// [`std::io::ErrorKind::AlreadyExists`] or
/// [`std::io::ErrorKind::AddrInUse`], then `Err` is returned.
///
/// # Examples
/// ```
/// # use std::io;
/// # fn main() {
/// # if let Err(_) = run() {
/// # ::std::process::exit(1);
/// # }
/// # }
/// # fn run() -> Result<(), io::Error> {
/// # use tempfile::Builder;
/// # #[cfg(unix)]
/// use std::os::unix::net::UnixListener;
/// # #[cfg(unix)]
/// let tempsock = Builder::new().make(|path| UnixListener::bind(path))?;
/// # Ok(())
/// # }
/// ```
///
/// [security]: struct.NamedTempFile.html#security
/// [resource-leaking]: struct.NamedTempFile.html#resource-leaking
pub fn make<F, R>(&self, f: F) -> io::Result<NamedTempFile<R>>
where
F: FnMut(&Path) -> io::Result<R>,
{
self.make_in(env::temp_dir(), f)
}
/// This is the same as [`Builder::make`], except `dir` is used as the base
/// directory for the temporary file path.
///
/// See [`Builder::make`] for more details and security implications.
///
/// # Examples
/// ```
/// # use std::io;
/// # fn main() {
/// # if let Err(_) = run() {
/// # ::std::process::exit(1);
/// # }
/// # }
/// # fn run() -> Result<(), io::Error> {
/// # use tempfile::Builder;
/// # #[cfg(unix)]
/// use std::os::unix::net::UnixListener;
/// # #[cfg(unix)]
/// let tempsock = Builder::new().make_in("./", |path| UnixListener::bind(path))?;
/// # Ok(())
/// # }
/// ```
pub fn make_in<F, R, P>(&self, dir: P, mut f: F) -> io::Result<NamedTempFile<R>>
where
F: FnMut(&Path) -> io::Result<R>,
P: AsRef<Path>,
{
util::create_helper(
dir.as_ref(),
self.prefix,
self.suffix,
self.random_len,
move |path| {
Ok(NamedTempFile::from_parts(
f(&path)?,
TempPath::from_path(path),
))
},
)
}
}