1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

mktemp: respect path given in template argument

Fix a bug in `mktemp` where it was not respecting the path given by
the positional argument. Previously, it would place the temporary file
whose name is induced by a given template in the `/tmp` directory,
like this:

    $ mktemp XXX
    /tmp/LJr

    $ mktemp d/XXX
    /tmp/d/IhS

After this commit, it respects the directory given in the template
argument:

    $ mktemp XXX
    LJr

    $ mktemp d/XXX
    d/IhS

Fixes #3440.
This commit is contained in:
Jeffrey Finkelstein 2022-05-01 12:03:02 -04:00
parent f869fafd03
commit aa6aefbd64
2 changed files with 105 additions and 7 deletions

View file

@ -79,7 +79,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let template = matches.value_of(ARG_TEMPLATE).unwrap();
let tmpdir = matches.value_of(OPT_TMPDIR).unwrap_or_default();
let (template, mut tmpdir) = if matches.is_present(OPT_TMPDIR)
// Treat the template string as a path to get the directory
// containing the last component.
let path = PathBuf::from(template);
let (template, tmpdir) = if matches.is_present(OPT_TMPDIR)
&& !PathBuf::from(tmpdir).is_dir() // if a temp dir is provided, it must be an actual path
&& tmpdir.contains("XXX")
// If this is a template, it has to contain at least 3 X
@ -97,8 +101,24 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let tmp = env::temp_dir();
(tmpdir, tmp)
} else if !matches.is_present(OPT_TMPDIR) {
// In this case, the command line was `mktemp -t XXX`, so we
// treat the argument `XXX` as though it were a filename
// regardless of whether it has path separators in it.
if matches.is_present(OPT_T) {
let tmp = env::temp_dir();
(template, tmp)
// In this case, the command line was `mktemp XXX`, so we need
// 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),
};
let filename = path.file_name();
let template = filename.unwrap().to_str().unwrap();
(template, tmp)
}
} else {
(template, PathBuf::from(tmpdir))
};
@ -113,10 +133,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
return Err(MkTempError::InvalidTemplate(template.into()).into());
}
if matches.is_present(OPT_T) {
tmpdir = env::temp_dir();
}
let res = if dry_run {
dry_exec(tmpdir, prefix, rand, suffix)
} else {
@ -272,5 +288,19 @@ fn exec(dir: &Path, prefix: &str, rand: usize, suffix: &str, make_dir: bool) ->
.map_err(|e| MkTempError::PersistError(e.file.path().to_path_buf()))?
.1
};
// Get just the last component of the path to the created
// temporary file or directory.
let filename = path.file_name();
let filename = filename.unwrap().to_str().unwrap();
// Join the directory to the path to get the path to print. We
// cannot use the path returned by the `Builder` because it gives
// the absolute path and we need to return a filename that matches
// the template given on the command-line which might be a
// relative path.
let mut path = dir.to_path_buf();
path.push(filename);
println_verbatim(path).map_err_context(|| "failed to print directory name".to_owned())
}

View file

@ -411,3 +411,71 @@ fn test_mktemp_directory_tmpdir() {
result.no_stderr().stdout_contains("apt-key-gpghome.");
assert!(PathBuf::from(result.stdout_str().trim()).is_dir());
}
/// Decide whether a string matches a given template.
///
/// In the template, the character `'X'` is treated as a wildcard,
/// that is, it matches anything. All other characters in `template`
/// and `s` must match exactly.
///
/// # Examples
///
/// ```rust,ignore
/// # These all match.
/// assert!(matches_template("abc", "abc"));
/// assert!(matches_template("aXc", "abc"));
/// assert!(matches_template("XXX", "abc"));
///
/// # None of these match
/// assert!(matches_template("abc", "abcd"));
/// assert!(matches_template("abc", "ab"));
/// assert!(matches_template("aXc", "abd"));
/// assert!(matches_template("XXX", "abcd"));
/// ```
///
fn matches_template(template: &str, s: &str) -> bool {
if template.len() != s.len() {
return false;
}
for (a, b) in template.chars().zip(s.chars()) {
if !(a == 'X' || a == b) {
return false;
}
}
true
}
/// An assertion that uses [`matches_template`] and adds a helpful error message.
macro_rules! assert_matches_template {
($template:expr, $s:expr) => {{
assert!(
matches_template($template, $s),
"\"{}\" != \"{}\"",
$template,
$s
);
}};
}
/// Test that the file is created in the directory given by the template.
#[test]
fn test_respect_template() {
let (at, mut ucmd) = at_and_ucmd!();
let template = "XXX";
let result = ucmd.arg(template).succeeds();
let filename = result.no_stderr().stdout_str().trim_end();
assert_matches_template!(template, filename);
assert!(at.file_exists(filename));
}
/// Test that the file is created in the directory given by the template.
#[test]
fn test_respect_template_directory() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("d");
let template = "d/XXX";
let result = ucmd.arg(template).succeeds();
let filename = result.no_stderr().stdout_str().trim_end();
assert_matches_template!(template, filename);
assert!(at.file_exists(filename));
}