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));
+}