Skip to content

Commit bd2b267

Browse files
authored
feat: make it possible to disable cleanup in-place after creation (#355)
This makes it possible to disable automatic cleanup of temporary files & directories _after_ they've been created. 1. Rename `Builder::keep` into `Builder::disable_cleanup` for consistency. `::keep` on `NamedTempDir`, `TempDir`, `TempPath`, etc. provide a way to _convert_ (one-way) a temporary directory/file into a persistent temporary directory/file but aren't a way to simply disable automatic cleanup in-place. 2. Add `disable_cleanup` methods to all named temporary file types, making it possible to replicate the behavior of `Builder::disable_cleanup` _after_ creating a temporary file. Given that this operation is in-place, it can be undone. fixes #347
1 parent 3b30099 commit bd2b267

File tree

7 files changed

+143
-62
lines changed

7 files changed

+143
-62
lines changed

src/dir/imp/any.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ fn not_supported<T>(msg: &str) -> io::Result<T> {
1010
pub fn create(
1111
path: PathBuf,
1212
permissions: Option<&std::fs::Permissions>,
13-
keep: bool,
13+
disable_cleanup: bool,
1414
) -> io::Result<TempDir> {
1515
if permissions.map_or(false, |p| p.readonly()) {
1616
return not_supported("changing permissions is not supported on this platform");
@@ -19,6 +19,6 @@ pub fn create(
1919
.with_err_path(|| &path)
2020
.map(|_| TempDir {
2121
path: path.into_boxed_path(),
22-
keep,
22+
disable_cleanup,
2323
})
2424
}

src/dir/imp/unix.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::path::PathBuf;
66
pub fn create(
77
path: PathBuf,
88
permissions: Option<&std::fs::Permissions>,
9-
keep: bool,
9+
disable_cleanup: bool,
1010
) -> io::Result<TempDir> {
1111
let mut dir_options = std::fs::DirBuilder::new();
1212
#[cfg(not(target_os = "wasi"))]
@@ -21,6 +21,6 @@ pub fn create(
2121
.with_err_path(|| &path)
2222
.map(|_| TempDir {
2323
path: path.into_boxed_path(),
24-
keep,
24+
disable_cleanup,
2525
})
2626
}

src/dir/mod.rs

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ pub fn tempdir_in<P: AsRef<Path>>(dir: P) -> io::Result<TempDir> {
182182
/// [`std::process::exit()`]: http://doc.rust-lang.org/std/process/fn.exit.html
183183
pub struct TempDir {
184184
path: Box<Path>,
185-
keep: bool,
185+
disable_cleanup: bool,
186186
}
187187

188188
impl TempDir {
@@ -394,6 +394,9 @@ impl TempDir {
394394
/// This consumes the [`TempDir`] without deleting directory on the filesystem, meaning that
395395
/// the directory will no longer be automatically deleted.
396396
///
397+
/// If you want to disable automatic cleanup of the temporary directory in-place, keeping the
398+
/// `TempDir` as-is, use [`TempDir::disable_cleanup`] instead.
399+
///
397400
/// [`TempDir`]: struct.TempDir.html
398401
/// [`PathBuf`]: http://doc.rust-lang.org/std/path/struct.PathBuf.html
399402
///
@@ -414,13 +417,19 @@ impl TempDir {
414417
/// # Ok::<(), std::io::Error>(())
415418
/// ```
416419
#[must_use]
417-
pub fn keep(self) -> PathBuf {
418-
// Prevent the Drop impl from being called.
419-
let mut this = mem::ManuallyDrop::new(self);
420+
pub fn keep(mut self) -> PathBuf {
421+
self.disable_cleanup(true);
422+
mem::replace(&mut self.path, PathBuf::new().into_boxed_path()).into()
423+
}
420424

421-
// replace this.path with an empty Box, since an empty Box does not
422-
// allocate any heap memory.
423-
mem::replace(&mut this.path, PathBuf::new().into_boxed_path()).into()
425+
/// Disable cleanup of the temporary directory. If `disable_cleanup` is `true`, the temporary
426+
/// directory will not be deleted when this `TempDir` is dropped. This method is equivalent to
427+
/// calling [`Builder::disable_cleanup`] when creating the `TempDir`.
428+
///
429+
/// **NOTE:** this method is primarily useful for testing/debugging. If you want to simply turn
430+
/// a temporary directory into a non-temporary directory, prefer [`TempDir::keep`].
431+
pub fn disable_cleanup(&mut self, disable_cleanup: bool) {
432+
self.disable_cleanup = disable_cleanup
424433
}
425434

426435
/// Closes and removes the temporary directory, returning a `Result`.
@@ -490,7 +499,7 @@ impl fmt::Debug for TempDir {
490499

491500
impl Drop for TempDir {
492501
fn drop(&mut self) {
493-
if !self.keep {
502+
if !self.disable_cleanup {
494503
let _ = remove_dir_all(self.path());
495504
}
496505
}
@@ -499,9 +508,9 @@ impl Drop for TempDir {
499508
pub(crate) fn create(
500509
path: PathBuf,
501510
permissions: Option<&std::fs::Permissions>,
502-
keep: bool,
511+
disable_cleanup: bool,
503512
) -> io::Result<TempDir> {
504-
imp::create(path, permissions, keep)
513+
imp::create(path, permissions, disable_cleanup)
505514
}
506515

507516
mod imp;

src/file/mod.rs

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,12 @@ impl error::Error for PathPersistError {
124124
/// This is useful when the temporary file needs to be used by a child process,
125125
/// for example.
126126
///
127-
/// When dropped, the temporary file is deleted unless `keep(true)` was called
128-
/// on the builder that constructed this value.
127+
/// When dropped, the temporary file is deleted unless `disable_cleanup(true)` was called on the
128+
/// builder that constructed this temporary file and/or was called on either this `TempPath` or the
129+
/// `NamedTempFile` from which this `TempPath` was constructed.
129130
pub struct TempPath {
130131
path: Box<Path>,
131-
keep: bool,
132+
disable_cleanup: bool,
132133
}
133134

134135
impl TempPath {
@@ -298,12 +299,13 @@ impl TempPath {
298299
pub fn keep(mut self) -> Result<PathBuf, PathPersistError> {
299300
match imp::keep(&self.path) {
300301
Ok(_) => {
301-
// Don't drop `self`. We don't want to try deleting the old
302-
// temporary file path. (It'll fail, but the failure is never
303-
// seen.)
304-
let path = mem::replace(&mut self.path, PathBuf::new().into_boxed_path());
305-
mem::forget(self);
306-
Ok(path.into())
302+
self.disable_cleanup(true);
303+
Ok(mem::replace(
304+
&mut self.path,
305+
// Replace with an empty boxed path buf, this doesn't allocate.
306+
PathBuf::new().into_boxed_path(),
307+
)
308+
.into_path_buf())
307309
}
308310
Err(e) => Err(PathPersistError {
309311
error: e,
@@ -312,6 +314,17 @@ impl TempPath {
312314
}
313315
}
314316

317+
/// Disable cleanup of the temporary file. If `disable_cleanup` is `true`, the temporary file
318+
/// will not be deleted when this `TempPath` is dropped. This method is equivalent to calling
319+
/// [`Builder::disable_cleanup`] when creating the original `NamedTempFile`, which see for
320+
/// relevant warnings.
321+
///
322+
/// **NOTE:** this method is primarily useful for testing/debugging. If you want to simply turn
323+
/// a temporary file-path into a non-temporary file-path, prefer [`TempPath::keep`].
324+
pub fn disable_cleanup(&mut self, disable_cleanup: bool) {
325+
self.disable_cleanup = disable_cleanup
326+
}
327+
315328
/// Create a new TempPath from an existing path. This can be done even if no
316329
/// file exists at the given path.
317330
///
@@ -321,14 +334,14 @@ impl TempPath {
321334
pub fn from_path(path: impl Into<PathBuf>) -> Self {
322335
Self {
323336
path: path.into().into_boxed_path(),
324-
keep: false,
337+
disable_cleanup: false,
325338
}
326339
}
327340

328-
pub(crate) fn new(path: PathBuf, keep: bool) -> Self {
341+
pub(crate) fn new(path: PathBuf, disable_cleanup: bool) -> Self {
329342
Self {
330343
path: path.into_boxed_path(),
331-
keep,
344+
disable_cleanup,
332345
}
333346
}
334347
}
@@ -341,7 +354,7 @@ impl fmt::Debug for TempPath {
341354

342355
impl Drop for TempPath {
343356
fn drop(&mut self) {
344-
if !self.keep {
357+
if !self.disable_cleanup {
345358
let _ = fs::remove_file(&self.path);
346359
}
347360
}
@@ -775,7 +788,6 @@ impl<F> NamedTempFile<F> {
775788
/// Keep the temporary file from being deleted. This function will turn the
776789
/// temporary file into a non-temporary file without moving it.
777790
///
778-
///
779791
/// # Errors
780792
///
781793
/// On some platforms (e.g., Windows), we need to mark the file as
@@ -806,6 +818,17 @@ impl<F> NamedTempFile<F> {
806818
}
807819
}
808820

821+
/// Disable cleanup of the temporary file. If `disable_cleanup` is `true`, the temporary file
822+
/// will not be deleted when this `TempPath` is dropped. This method is equivalent to calling
823+
/// [`Builder::disable_cleanup`] when creating the original `NamedTempFile`, which see for
824+
/// relevant warnings.
825+
///
826+
/// **NOTE:** this method is primarily useful for testing/debugging. If you want to simply turn
827+
/// a temporary file into a non-temporary file, prefer [`NamedTempFile::keep`].
828+
pub fn disable_cleanup(&mut self, disable_cleanup: bool) {
829+
self.path.disable_cleanup(disable_cleanup)
830+
}
831+
809832
/// Get a reference to the underlying file.
810833
pub fn as_file(&self) -> &F {
811834
&self.file
@@ -1048,7 +1071,7 @@ pub(crate) fn create_named(
10481071
.map(|file| NamedTempFile {
10491072
path: TempPath {
10501073
path: path.into_boxed_path(),
1051-
keep,
1074+
disable_cleanup: keep,
10521075
},
10531076
file,
10541077
})

src/lib.rs

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ pub struct Builder<'a, 'b> {
228228
suffix: &'b OsStr,
229229
append: bool,
230230
permissions: Option<std::fs::Permissions>,
231-
keep: bool,
231+
disable_cleanup: bool,
232232
}
233233

234234
impl Default for Builder<'_, '_> {
@@ -239,7 +239,7 @@ impl Default for Builder<'_, '_> {
239239
suffix: OsStr::new(""),
240240
append: false,
241241
permissions: None,
242-
keep: false,
242+
disable_cleanup: false,
243243
}
244244
}
245245
}
@@ -465,31 +465,42 @@ impl<'a, 'b> Builder<'a, 'b> {
465465
self
466466
}
467467

468-
/// Set the file/folder to be kept even when the [`NamedTempFile`]/[`TempDir`] goes out of
469-
/// scope.
468+
/// Disable cleanup of the file/folder to even when the [`NamedTempFile`]/[`TempDir`] goes out
469+
/// of scope. Prefer [`NamedTempFile::keep`] and `[`TempDir::keep`] where possible,
470+
/// `disable_cleanup` is provided for testing & debugging.
470471
///
471472
/// By default, the file/folder is automatically cleaned up in the destructor of
472-
/// [`NamedTempFile`]/[`TempDir`]. When `keep` is set to `true`, this behavior is suppressed.
473+
/// [`NamedTempFile`]/[`TempDir`]. When `disable_cleanup` is set to `true`, this behavior is
474+
/// suppressed. If you wish to disable cleanup after creating a temporary file/directory, call
475+
/// [`NamedTempFile::disable_cleanup`] or [`TempDir::disable_cleanup`].
473476
///
474-
/// If you wish to keep a temporary file or directory after creating it, call
475-
/// [`NamedTempFile::keep`] or [`TempDir::keep`] respectively to turn it into a regular
476-
/// file/path (respectively) that won't be automatically deleted.
477+
/// # Warnings
478+
///
479+
/// On some platforms (for now, only Windows), temporary files are marked with a special
480+
/// "temporary file" (`FILE_ATTRIBUTE_TEMPORARY`) attribute. Disabling cleanup _will not_ unset
481+
/// this attribute while calling [`NamedTempFile::keep`] will.
477482
///
478483
/// # Examples
479484
///
480485
/// ```
481486
/// use tempfile::Builder;
482487
///
483488
/// let named_tempfile = Builder::new()
484-
/// .keep(true)
489+
/// .disable_cleanup(true)
485490
/// .tempfile()?;
486491
/// # Ok::<(), std::io::Error>(())
487492
/// ```
488-
pub fn keep(&mut self, keep: bool) -> &mut Self {
489-
self.keep = keep;
493+
pub fn disable_cleanup(&mut self, disable_cleanup: bool) -> &mut Self {
494+
self.disable_cleanup = disable_cleanup;
490495
self
491496
}
492497

498+
/// Deprecated alias for [`Builder::disable_cleanup`].
499+
#[deprecated = "Use Builder::disable_cleanup"]
500+
pub fn keep(&mut self, keep: bool) -> &mut Self {
501+
self.disable_cleanup(keep)
502+
}
503+
493504
/// Create the named temporary file.
494505
///
495506
/// # Security
@@ -555,7 +566,7 @@ impl<'a, 'b> Builder<'a, 'b> {
555566
path,
556567
OpenOptions::new().append(self.append),
557568
self.permissions.as_ref(),
558-
self.keep,
569+
self.disable_cleanup,
559570
)
560571
},
561572
)
@@ -616,7 +627,7 @@ impl<'a, 'b> Builder<'a, 'b> {
616627
self.prefix,
617628
self.suffix,
618629
self.random_len,
619-
|path| dir::create(path, self.permissions.as_ref(), self.keep),
630+
|path| dir::create(path, self.permissions.as_ref(), self.disable_cleanup),
620631
)
621632
}
622633

@@ -741,7 +752,7 @@ impl<'a, 'b> Builder<'a, 'b> {
741752
move |path| {
742753
Ok(NamedTempFile::from_parts(
743754
f(&path)?,
744-
TempPath::new(path, self.keep),
755+
TempPath::new(path, self.disable_cleanup),
745756
))
746757
},
747758
)

tests/namedtempfile.rs

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -411,22 +411,40 @@ fn test_keep() {
411411
}
412412

413413
#[test]
414-
fn test_builder_keep() {
414+
fn test_disable_cleanup() {
415415
configure_wasi_temp_dir();
416416

417-
let mut tmpfile = Builder::new().keep(true).tempfile().unwrap();
418-
write!(tmpfile, "abcde").unwrap();
419-
let path = tmpfile.path().to_owned();
420-
drop(tmpfile);
417+
// Case 0: never mark as "disable cleanup"
418+
// Case 1: enable "disable cleanup" in the builder, don't touch it after.
419+
// Case 2: enable "disable cleanup" in the builder, turn it off after.
420+
// Case 3: don't enable disable cleanup in the builder, turn it on after.
421+
422+
for case in 0..4 {
423+
let in_builder = case & 1 > 0;
424+
let toggle = case & 2 > 0;
425+
let mut tmpfile = Builder::new()
426+
.disable_cleanup(in_builder)
427+
.tempfile()
428+
.unwrap();
429+
write!(tmpfile, "abcde").unwrap();
430+
if toggle {
431+
tmpfile.disable_cleanup(!in_builder);
432+
}
421433

422-
{
423-
// Try opening it again.
424-
let mut f = File::open(&path).unwrap();
425-
let mut buf = String::new();
426-
f.read_to_string(&mut buf).unwrap();
427-
assert_eq!("abcde", buf);
434+
let path = tmpfile.path().to_owned();
435+
drop(tmpfile);
436+
437+
if in_builder ^ toggle {
438+
// Try opening it again.
439+
let mut f = File::open(&path).unwrap();
440+
let mut buf = String::new();
441+
f.read_to_string(&mut buf).unwrap();
442+
assert_eq!("abcde", buf);
443+
std::fs::remove_file(&path).unwrap();
444+
} else {
445+
assert!(!path.exists(), "tempfile wasn't deleted");
446+
}
428447
}
429-
std::fs::remove_file(&path).unwrap();
430448
}
431449

432450
#[test]

0 commit comments

Comments
 (0)