Skip to content

Commit

Permalink
mktemp: respect TMPDIR environment variable
Browse files Browse the repository at this point in the history
Change `mktemp` so that it respects the value of the `TMPDIR`
environment variable, if no directory is otherwise specified in its
arguments. For example, before this commit

    $ TMPDIR=. mktemp
    /tmp/tmp.WDJ66MaS1T

After this commit,

    $ TMPDIR=. mktemp
    ./tmp.h96VZBhv8P

This matches the behavior of GNU `mktemp`.
  • Loading branch information
jfinkels committed May 22, 2022
1 parent d921073 commit ff3cae3
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 4 deletions.
43 changes: 39 additions & 4 deletions src/uu/mktemp/src/mktemp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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: P) -> bool
where
P: Deref<Target = Path>,
{
p.as_os_str().is_empty()
}

fn tmpdir_from_template<P>(path: &P) -> PathBuf
where
P: Deref<Target = Path>,
{
// 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);
Expand Down Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions tests/by-util/test_mktemp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

0 comments on commit ff3cae3

Please sign in to comment.