diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 1f872714f09..040e18faa42 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -18,7 +18,6 @@ use std::io::ErrorKind; use std::iter; use std::path::{MAIN_SEPARATOR, Path, PathBuf}; -#[cfg(unix)] use std::fs; #[cfg(unix)] use std::os::unix::prelude::PermissionsExt; @@ -435,7 +434,27 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } else { res }; - println_verbatim(res?).map_err_context(|| translate!("mktemp-error-failed-print")) + let path = res?; + match println_verbatim(&path) { + Ok(()) => Ok(()), + Err(e) => { + // We created the temporary file or directory but failed to print + // its name to stdout. Since the caller will never learn the name, + // the entry is unreachable, so remove it to match GNU mktemp. + // The tempfile builder's automatic cleanup was disabled via + // `keep()`, so removal must be done manually here. This is + // best-effort: if removal fails, still report the original + // print error. + if !dry_run { + let _ = if make_dir { + fs::remove_dir(&path) + } else { + fs::remove_file(&path) + }; + } + Err(e).map_err_context(|| translate!("mktemp-error-failed-print")) + } + } } pub fn uu_app() -> Command { diff --git a/tests/by-util/test_mktemp.rs b/tests/by-util/test_mktemp.rs index 0fa2896bb6f..a49926dfc92 100644 --- a/tests/by-util/test_mktemp.rs +++ b/tests/by-util/test_mktemp.rs @@ -1209,3 +1209,42 @@ fn test_mktemp_hidden_file_single_dot() { template_name.len() ); } + +#[test] +#[cfg(target_os = "linux")] +fn test_mktemp_cleanup_on_write_error() { + use std::fs::{File, read_dir}; + + // Simulate a write failure by directing stdout to /dev/full (Linux only). + // Skip if /dev/full is unavailable, mirroring the GNU test's guard. + let Ok(dev_full) = File::create("/dev/full") else { + return; + }; + + // Case 1: a temporary file is created, but printing its name fails. + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("d"); + ucmd.arg("-p") + .arg("d") + .set_stdout(dev_full.try_clone().unwrap()) + .fails() + .code_is(1); + assert!( + read_dir(at.plus("d")).unwrap().next().is_none(), + "mktemp left an orphaned temp file after a write error" + ); + + // Case 2: a temporary directory is created, but printing its name fails. + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("d"); + ucmd.arg("-p") + .arg("d") + .arg("-d") + .set_stdout(dev_full) + .fails() + .code_is(1); + assert!( + read_dir(at.plus("d")).unwrap().next().is_none(), + "mktemp left an orphaned temp directory after a write error" + ); +}