From f2e4225bc6e9e140efe04c999cad4f101efbef2d Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 15 Sep 2022 22:49:20 -0400 Subject: [PATCH 1/2] mktemp: refactor make_temp_dir(), make_temp_file() Factor code out of `exec()` into two helper functions, `make_temp_dir()` and `make_temp_file()`. --- src/uu/mktemp/src/mktemp.rs | 79 ++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 28 deletions(-) diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index dac2a097b..a4787931a 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -460,37 +460,60 @@ pub fn dry_exec(tmpdir: &str, prefix: &str, rand: usize, suffix: &str) -> UResul println_verbatim(tmpdir).map_err_context(|| "failed to print directory name".to_owned()) } -fn exec(dir: &str, prefix: &str, rand: usize, suffix: &str, make_dir: bool) -> UResult<()> { - let context = || { - format!( - "failed to create file via template '{}{}{}'", - prefix, - "X".repeat(rand), - suffix - ) - }; - +/// Create a temporary directory with the given parameters. +/// +/// This function creates a temporary directory as a subdirectory of +/// `dir`. The name of the directory is the concatenation of `prefix`, +/// a string of `rand` random characters, and `suffix`. The +/// permissions of the directory are set to `u+rwx` +/// +/// # Errors +/// +/// If the temporary directory could not be written to disk. +fn make_temp_dir(dir: &str, prefix: &str, rand: usize, suffix: &str) -> UResult { let mut builder = Builder::new(); builder.prefix(prefix).rand_bytes(rand).suffix(suffix); - - let path = if make_dir { - builder - .tempdir_in(&dir) - .map_err_context(context)? - .into_path() // `into_path` consumes the TempDir without removing it - } else { - builder - .tempfile_in(&dir) - .map_err_context(context)? - .keep() // `keep` ensures that the file is not deleted - .map_err(|e| MkTempError::PersistError(e.file.path().to_path_buf()))? - .1 - }; - - #[cfg(not(windows))] - if make_dir { - fs::set_permissions(&path, fs::Permissions::from_mode(0o700))?; + match builder.tempdir_in(&dir) { + Ok(d) => { + // `into_path` consumes the TempDir without removing it + let path = d.into_path(); + #[cfg(not(windows))] + fs::set_permissions(&path, fs::Permissions::from_mode(0o700))?; + Ok(path) + } + Err(e) => Err(e.into()), } +} + +/// Create a temporary file with the given parameters. +/// +/// This function creates a temporary file in the directory `dir`. The +/// name of the file is the concatenation of `prefix`, a string of +/// `rand` random characters, and `suffix`. The permissions of the +/// file are set to `u+rw`. +/// +/// # Errors +/// +/// If the file could not be written to disk. +fn make_temp_file(dir: &str, prefix: &str, rand: usize, suffix: &str) -> UResult { + let mut builder = Builder::new(); + builder.prefix(prefix).rand_bytes(rand).suffix(suffix); + match builder.tempfile_in(&dir) { + // `keep` ensures that the file is not deleted + Ok(named_tempfile) => match named_tempfile.keep() { + Ok((_, pathbuf)) => Ok(pathbuf), + Err(e) => Err(MkTempError::PersistError(e.file.path().to_path_buf()).into()), + }, + Err(e) => Err(e.into()), + } +} + +fn exec(dir: &str, prefix: &str, rand: usize, suffix: &str, make_dir: bool) -> UResult<()> { + let path = if make_dir { + make_temp_dir(dir, prefix, rand, suffix)? + } else { + make_temp_file(dir, prefix, rand, suffix)? + }; // Get just the last component of the path to the created // temporary file or directory. From 60ca9a03bb8414277429a06a04984b9667fda83d Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 15 Sep 2022 22:49:50 -0400 Subject: [PATCH 2/2] mktemp: add message for directory not found Add special handling in `mktemp` for when the directory that will contain the temporary file is not found. This situation now produces the message mktemp: failed to create file via template 'XXX': No such file or directory to match the behavior of GNU mktemp. --- src/uu/mktemp/src/mktemp.rs | 28 +++++++++++- tests/by-util/test_mktemp.rs | 86 ++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 2 deletions(-) diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index a4787931a..b19061691 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -16,6 +16,7 @@ use uucore::format_usage; use std::env; use std::error::Error; use std::fmt::Display; +use std::io::ErrorKind; use std::iter; use std::path::{Path, PathBuf, MAIN_SEPARATOR}; @@ -54,6 +55,9 @@ enum MkTempError { SuffixContainsDirSeparator(String), InvalidTemplate(String), TooManyTemplates, + + /// When a specified temporary directory could not be found. + NotFound(String, String), } impl UError for MkTempError { @@ -93,6 +97,12 @@ impl Display for MkTempError { TooManyTemplates => { write!(f, "too many templates") } + NotFound(template_type, s) => write!( + f, + "failed to create {} via template {}: No such file or directory", + template_type, + s.quote() + ), } } } @@ -469,7 +479,8 @@ pub fn dry_exec(tmpdir: &str, prefix: &str, rand: usize, suffix: &str) -> UResul /// /// # Errors /// -/// If the temporary directory could not be written to disk. +/// If the temporary directory could not be written to disk or if the +/// given directory `dir` does not exist. fn make_temp_dir(dir: &str, prefix: &str, rand: usize, suffix: &str) -> UResult { let mut builder = Builder::new(); builder.prefix(prefix).rand_bytes(rand).suffix(suffix); @@ -481,6 +492,12 @@ fn make_temp_dir(dir: &str, prefix: &str, rand: usize, suffix: &str) -> UResult< fs::set_permissions(&path, fs::Permissions::from_mode(0o700))?; Ok(path) } + Err(e) if e.kind() == ErrorKind::NotFound => { + let filename = format!("{}{}{}", prefix, "X".repeat(rand), suffix); + let path = Path::new(dir).join(filename); + let s = path.display().to_string(); + Err(MkTempError::NotFound("directory".to_string(), s).into()) + } Err(e) => Err(e.into()), } } @@ -494,7 +511,8 @@ fn make_temp_dir(dir: &str, prefix: &str, rand: usize, suffix: &str) -> UResult< /// /// # Errors /// -/// If the file could not be written to disk. +/// If the file could not be written to disk or if the directory does +/// not exist. fn make_temp_file(dir: &str, prefix: &str, rand: usize, suffix: &str) -> UResult { let mut builder = Builder::new(); builder.prefix(prefix).rand_bytes(rand).suffix(suffix); @@ -504,6 +522,12 @@ fn make_temp_file(dir: &str, prefix: &str, rand: usize, suffix: &str) -> UResult Ok((_, pathbuf)) => Ok(pathbuf), Err(e) => Err(MkTempError::PersistError(e.file.path().to_path_buf()).into()), }, + Err(e) if e.kind() == ErrorKind::NotFound => { + let filename = format!("{}{}{}", prefix, "X".repeat(rand), suffix); + let path = Path::new(dir).join(filename); + let s = path.display().to_string(); + Err(MkTempError::NotFound("file".to_string(), s).into()) + } Err(e) => Err(e.into()), } } diff --git a/tests/by-util/test_mktemp.rs b/tests/by-util/test_mktemp.rs index f0c7a347a..57448474f 100644 --- a/tests/by-util/test_mktemp.rs +++ b/tests/by-util/test_mktemp.rs @@ -739,3 +739,89 @@ fn test_tmpdir_env_var() { assert_matches_template!(template, filename); assert!(at.file_exists(filename)); } + +#[test] +fn test_nonexistent_tmpdir_env_var() { + #[cfg(not(windows))] + new_ucmd!().env(TMPDIR, "no/such/dir").fails().stderr_only("mktemp: failed to create file via template 'no/such/dir/tmp.XXXXXXXXXX': No such file or directory\n"); + #[cfg(windows)] + { + let result = new_ucmd!().env(TMPDIR, r"no\such\dir").fails(); + result.no_stdout(); + let stderr = result.stderr_str(); + assert!( + stderr.starts_with("mktemp: failed to create file via template"), + "{}", + stderr + ); + assert!( + stderr.ends_with("no\\such\\dir\\tmp.XXXXXXXXXX': No such file or directory\n"), + "{}", + stderr + ); + } + + #[cfg(not(windows))] + new_ucmd!().env(TMPDIR, "no/such/dir").arg("-d").fails().stderr_only("mktemp: failed to create directory via template 'no/such/dir/tmp.XXXXXXXXXX': No such file or directory\n"); + #[cfg(windows)] + { + let result = new_ucmd!().env(TMPDIR, r"no\such\dir").arg("-d").fails(); + result.no_stdout(); + let stderr = result.stderr_str(); + assert!( + stderr.starts_with("mktemp: failed to create directory via template"), + "{}", + stderr + ); + assert!( + stderr.ends_with("no\\such\\dir\\tmp.XXXXXXXXXX': No such file or directory\n"), + "{}", + stderr + ); + } +} + +#[test] +fn test_nonexistent_dir_prefix() { + #[cfg(not(windows))] + new_ucmd!().arg("d/XXX").fails().stderr_only( + "mktemp: failed to create file via template 'd/XXX': No such file or directory\n", + ); + #[cfg(windows)] + { + let result = new_ucmd!().arg(r"d\XXX").fails(); + result.no_stdout(); + let stderr = result.stderr_str(); + assert!( + stderr.starts_with("mktemp: failed to create file via template"), + "{}", + stderr + ); + assert!( + stderr.ends_with("d\\XXX': No such file or directory\n"), + "{}", + stderr + ); + } + + #[cfg(not(windows))] + new_ucmd!().arg("-d").arg("d/XXX").fails().stderr_only( + "mktemp: failed to create directory via template 'd/XXX': No such file or directory\n", + ); + #[cfg(windows)] + { + let result = new_ucmd!().arg("-d").arg(r"d\XXX").fails(); + result.no_stdout(); + let stderr = result.stderr_str(); + assert!( + stderr.starts_with("mktemp: failed to create directory via template"), + "{}", + stderr + ); + assert!( + stderr.ends_with("d\\XXX': No such file or directory\n"), + "{}", + stderr + ); + } +}