diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 102eadbe4a3..92b5c66bdef 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -17,6 +17,7 @@ use std::env; use std::error::Error; use std::fmt::Display; use std::iter; +use std::ops::Deref; use std::path::{is_separator, Path, PathBuf, MAIN_SEPARATOR}; use rand::Rng; @@ -84,6 +85,34 @@ impl Display for MkTempError { } } +/// Decide whether a [`PathBuf`] represents the empty path, analagous to the empty string. +fn path_buf_is_empty

(p: P) -> bool +where + P: Deref, +{ + p.as_os_str().is_empty() +} + +fn tmpdir_from_template

(path: &P) -> PathBuf +where + P: Deref, +{ + // It is possible that the prefix of the template includes a path + // separator, like `mktemp a/bXXX`. In this case, the `tmpdir` + // should be set to `a/` and the template should be set to + // `bXXX`. If there is no path separator, then the `tmpdir` should + // be the value of the `TMPDIR` environment variable if set, + // otherwise the current directory. + match path.parent() { + None => PathBuf::from("."), + Some(d) if path_buf_is_empty(d) => match env::var("TMPDIR") { + Ok(dir) => PathBuf::from(dir), + Err(_) => PathBuf::from(d), + }, + Some(d) => PathBuf::from(d), + } +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().get_matches_from(args); @@ -123,10 +152,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // to parse out the parent directory and the filename from the // argument `XXX`, since it may be include path separators. } else { - let tmp = match path.parent() { - None => PathBuf::from("."), - Some(d) => PathBuf::from(d), - }; + // Get the parent directory that will contain the file. + // + // This could be + // + // * the prefix of the template, as in `mktemp a/bXXX`, + // * the value of the `TMPDIR` environment variable, or + // * the current directory. + // + // See `tmpdir_from_template()` for more information. + let tmp = tmpdir_from_template(&path); let filename = path.file_name(); let template = filename.unwrap().to_str().unwrap(); // If the command line was `mktemp aXXX/b`, then we will diff --git a/tests/by-util/test_mktemp.rs b/tests/by-util/test_mktemp.rs index 15a6b932ff7..63858c7b5e2 100644 --- a/tests/by-util/test_mktemp.rs +++ b/tests/by-util/test_mktemp.rs @@ -527,3 +527,24 @@ fn test_suffix_path_separator() { .fails() .stderr_only("mktemp: invalid suffix '\\b', contains directory separator\n"); } + +/// Test that files are created relative to `TMPDIR` environment variable. +#[test] +fn test_tmpdir_env_var() { + let (at, mut ucmd) = at_and_ucmd!(); + + // Create a subdirectory within the test fixture directory; we'll + // assign `TMPDIR` to be this subdirectory. + at.mkdir("d"); + let dir = at.plus_as_string("d"); + + // Create a temporary file under `dir`. + let result = ucmd.env(TMPDIR, &dir).succeeds(); + let filename = result.no_stderr().stdout_str().trim_end(); + + // The default template is 10 Xs following "tmp.", and we expect + // that to be under the `dir` that we created above. + let template = format!("{}/tmp.XXXXXXXXXX", dir); + assert_matches_template!(&template, filename); + assert!(at.file_exists(filename)); +}