Skip to content

Commit

Permalink
add keep() function to builder that suppresses delete-on-drop behavior (
Browse files Browse the repository at this point in the history
#293)

fixes #194
  • Loading branch information
RalfJung authored Aug 6, 2024
1 parent 96f2e7e commit 6e99572
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 28 deletions.
7 changes: 6 additions & 1 deletion src/dir/imp/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ fn not_supported<T>(msg: &str) -> io::Result<T> {
Err(io::Error::new(io::ErrorKind::Other, msg))
}

pub fn create(path: PathBuf, permissions: Option<&std::fs::Permissions>) -> io::Result<TempDir> {
pub fn create(
path: PathBuf,
permissions: Option<&std::fs::Permissions>,
keep: bool,
) -> io::Result<TempDir> {
if permissions.map_or(false, |p| p.readonly()) {
return not_supported("changing permissions is not supported on this platform");
}
fs::create_dir(&path)
.with_err_path(|| &path)
.map(|_| TempDir {
path: path.into_boxed_path(),
keep,
})
}
7 changes: 6 additions & 1 deletion src/dir/imp/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ use crate::TempDir;
use std::io;
use std::path::PathBuf;

pub fn create(path: PathBuf, permissions: Option<&std::fs::Permissions>) -> io::Result<TempDir> {
pub fn create(
path: PathBuf,
permissions: Option<&std::fs::Permissions>,
keep: bool,
) -> io::Result<TempDir> {
let mut dir_options = std::fs::DirBuilder::new();
#[cfg(not(target_os = "wasi"))]
{
Expand All @@ -17,5 +21,6 @@ pub fn create(path: PathBuf, permissions: Option<&std::fs::Permissions>) -> io::
.with_err_path(|| &path)
.map(|_| TempDir {
path: path.into_boxed_path(),
keep,
})
}
8 changes: 6 additions & 2 deletions src/dir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ pub fn tempdir_in<P: AsRef<Path>>(dir: P) -> io::Result<TempDir> {
/// [`std::process::exit()`]: http://doc.rust-lang.org/std/process/fn.exit.html
pub struct TempDir {
path: Box<Path>,
keep: bool,
}

impl TempDir {
Expand Down Expand Up @@ -425,15 +426,18 @@ impl fmt::Debug for TempDir {

impl Drop for TempDir {
fn drop(&mut self) {
let _ = remove_dir_all(self.path());
if !self.keep {
let _ = remove_dir_all(self.path());
}
}
}

pub(crate) fn create(
path: PathBuf,
permissions: Option<&std::fs::Permissions>,
keep: bool,
) -> io::Result<TempDir> {
imp::create(path, permissions)
imp::create(path, permissions, keep)
}

mod imp;
3 changes: 1 addition & 2 deletions src/file/imp/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,7 @@ fn create_unix(dir: &Path) -> io::Result<File> {
OsStr::new(".tmp"),
OsStr::new(""),
crate::NUM_RAND_CHARS,
None,
|path, _| create_unlinked(&path),
|path| create_unlinked(&path),
)
}

Expand Down
3 changes: 1 addition & 2 deletions src/file/imp/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ pub fn create(dir: &Path) -> io::Result<File> {
OsStr::new(".tmp"),
OsStr::new(""),
crate::NUM_RAND_CHARS,
None,
|path, _permissions| {
|path| {
OpenOptions::new()
.create_new(true)
.read(true)
Expand Down
19 changes: 16 additions & 3 deletions src/file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,11 @@ impl error::Error for PathPersistError {
/// This is useful when the temporary file needs to be used by a child process,
/// for example.
///
/// When dropped, the temporary file is deleted.
/// When dropped, the temporary file is deleted unless `keep(true)` was called
/// on the builder that constructed this value.
pub struct TempPath {
path: Box<Path>,
keep: bool,
}

impl TempPath {
Expand Down Expand Up @@ -273,7 +275,6 @@ impl TempPath {
/// Keep the temporary file from being deleted. This function will turn the
/// temporary file into a non-temporary file without moving it.
///
///
/// # Errors
///
/// On some platforms (e.g., Windows), we need to mark the file as
Expand Down Expand Up @@ -320,6 +321,14 @@ impl TempPath {
pub fn from_path(path: impl Into<PathBuf>) -> Self {
Self {
path: path.into().into_boxed_path(),
keep: false,
}
}

pub(crate) fn new(path: PathBuf, keep: bool) -> Self {
Self {
path: path.into_boxed_path(),
keep,
}
}
}
Expand All @@ -332,7 +341,9 @@ impl fmt::Debug for TempPath {

impl Drop for TempPath {
fn drop(&mut self) {
let _ = fs::remove_file(&self.path);
if !self.keep {
let _ = fs::remove_file(&self.path);
}
}
}

Expand Down Expand Up @@ -1008,6 +1019,7 @@ pub(crate) fn create_named(
mut path: PathBuf,
open_options: &mut OpenOptions,
permissions: Option<&std::fs::Permissions>,
keep: bool,
) -> io::Result<NamedTempFile> {
// Make the path absolute. Otherwise, changing directories could cause us to
// delete the wrong file.
Expand All @@ -1019,6 +1031,7 @@ pub(crate) fn create_named(
.map(|file| NamedTempFile {
path: TempPath {
path: path.into_boxed_path(),
keep,
},
file,
})
Expand Down
49 changes: 35 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ pub struct Builder<'a, 'b> {
suffix: &'b OsStr,
append: bool,
permissions: Option<std::fs::Permissions>,
keep: bool,
}

impl<'a, 'b> Default for Builder<'a, 'b> {
Expand All @@ -181,6 +182,7 @@ impl<'a, 'b> Default for Builder<'a, 'b> {
suffix: OsStr::new(""),
append: false,
permissions: None,
keep: false,
}
}
}
Expand Down Expand Up @@ -403,6 +405,27 @@ impl<'a, 'b> Builder<'a, 'b> {
self
}

/// Set the file/folder to be kept even when the [`NamedTempFile`]/[`TempDir`] goes out of
/// scope.
///
/// By default, the file/folder is automatically cleaned up in the destructor of
/// [`NamedTempFile`]/[`TempDir`]. When `keep` is set to `true`, this behavior is supressed.
///
/// # Examples
///
/// ```
/// use tempfile::Builder;
///
/// let named_tempfile = Builder::new()
/// .keep(true)
/// .tempfile()?;
/// # Ok::<(), std::io::Error>(())
/// ```
pub fn keep(&mut self, keep: bool) -> &mut Self {
self.keep = keep;
self
}

/// Create the named temporary file.
///
/// # Security
Expand Down Expand Up @@ -463,9 +486,13 @@ impl<'a, 'b> Builder<'a, 'b> {
self.prefix,
self.suffix,
self.random_len,
self.permissions.as_ref(),
|path, permissions| {
file::create_named(path, OpenOptions::new().append(self.append), permissions)
|path| {
file::create_named(
path,
OpenOptions::new().append(self.append),
self.permissions.as_ref(),
self.keep,
)
},
)
}
Expand Down Expand Up @@ -528,14 +555,9 @@ impl<'a, 'b> Builder<'a, 'b> {
dir = &storage;
}

util::create_helper(
dir,
self.prefix,
self.suffix,
self.random_len,
self.permissions.as_ref(),
dir::create,
)
util::create_helper(dir, self.prefix, self.suffix, self.random_len, |path| {
dir::create(path, self.permissions.as_ref(), self.keep)
})
}

/// Attempts to create a temporary file (or file-like object) using the
Expand Down Expand Up @@ -656,11 +678,10 @@ impl<'a, 'b> Builder<'a, 'b> {
self.prefix,
self.suffix,
self.random_len,
None,
move |path, _permissions| {
move |path| {
Ok(NamedTempFile::from_parts(
f(&path)?,
TempPath::from_path(path),
TempPath::new(path, self.keep),
))
},
)
Expand Down
5 changes: 2 additions & 3 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ pub fn create_helper<R>(
prefix: &OsStr,
suffix: &OsStr,
random_len: usize,
permissions: Option<&std::fs::Permissions>,
mut f: impl FnMut(PathBuf, Option<&std::fs::Permissions>) -> io::Result<R>,
mut f: impl FnMut(PathBuf) -> io::Result<R>,
) -> io::Result<R> {
let num_retries = if random_len != 0 {
crate::NUM_RETRIES
Expand All @@ -35,7 +34,7 @@ pub fn create_helper<R>(

for _ in 0..num_retries {
let path = base.join(tmpname(prefix, suffix, random_len));
return match f(path, permissions) {
return match f(path) {
Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists && num_retries > 1 => continue,
// AddrInUse can happen if we're creating a UNIX domain socket and
// the path already exists.
Expand Down
17 changes: 17 additions & 0 deletions tests/namedtempfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,23 @@ fn test_keep() {
std::fs::remove_file(&path).unwrap();
}

#[test]
fn test_builder_keep() {
let mut tmpfile = Builder::new().keep(true).tempfile().unwrap();
write!(tmpfile, "abcde").unwrap();
let path = tmpfile.path().to_owned();
drop(tmpfile);

{
// Try opening it again.
let mut f = File::open(&path).unwrap();
let mut buf = String::new();
f.read_to_string(&mut buf).unwrap();
assert_eq!("abcde", buf);
}
std::fs::remove_file(&path).unwrap();
}

#[test]
fn test_make() {
let tmpfile = Builder::new().make(|path| File::create(path)).unwrap();
Expand Down
9 changes: 9 additions & 0 deletions tests/tempdir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ fn pass_as_asref_path() {
}
}

fn test_keep() {
let tmpdir = Builder::new().keep(true).tempdir().unwrap();
let path = tmpdir.path().to_owned();
drop(tmpdir);
assert!(path.exists());
fs::remove_dir(path).unwrap();
}

#[test]
fn main() {
in_tmpdir(test_tempdir);
Expand All @@ -172,4 +180,5 @@ fn main() {
in_tmpdir(test_rm_tempdir_close);
in_tmpdir(dont_double_panic);
in_tmpdir(pass_as_asref_path);
in_tmpdir(test_keep);
}

0 comments on commit 6e99572

Please sign in to comment.