Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/uu/mktemp/src/mktemp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,17 @@ impl Params {
let prefix_str = prefix_path.to_string_lossy();
if prefix_str.ends_with(MAIN_SEPARATOR) {
(prefix_path, String::new())
} else if prefix_from_template.ends_with("/..") || prefix_from_template == ".." {
// `Path::file_name()` returns `None` for a path ending in `..`,
// and `Path::parent()` pops the preceding component, so both the
// directory and the prefix get mis-computed (e.g. `mktemp /tmp/..XXXXXX`
// would return `/tmp/<random>` instead of `/tmp/..<random>`). Strip
// the trailing `..` from `prefix_from_template` and use it as the
// literal prefix, matching GNU mktemp behavior.
let directory = Path::new(&prefix_from_option)
.join(&prefix_from_template[..prefix_from_template.len() - 2]);
let prefix = "..".to_string();
(directory, prefix)
} else {
let directory = match prefix_path.parent() {
None => PathBuf::new(),
Expand Down
68 changes: 68 additions & 0 deletions tests/by-util/test_mktemp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1172,3 +1172,71 @@
// but we can verify the command succeeds
ucmd.arg("-d").arg("-p").arg(at.plus(dir_name)).succeeds();
}

#[test]
#[cfg(unix)]
fn test_mktemp_dotdot_prefix() {
// Regression test for https://github.com/uutils/coreutils/issues/12312
//
// `mktemp <dir>/..XXXXXX` should produce a file named `..<random>`
// inside <dir>, matching GNU mktemp. Previously, `Path::file_name()`
// returned `None` for a path ending in `..`, causing the literal `..`
// prefix to be dropped from the output filename.
let scene = TestScenario::new(util_name!());
let dir = tempdir().unwrap();
let template = dir.path().join("..XXXXXX");

let result = scene.ucmd().arg(template.to_str().unwrap()).succeeds();

let stdout_path = result.stdout_str().trim();
let file_name = std::path::Path::new(stdout_path)
.file_name()
.expect("output path should have a file name")
.to_str()
.expect("output path should be UTF-8");
assert!(
file_name.starts_with("..") && file_name.len() == "..XXXXXX".len(),
"expected file name to start with '..' and match template length, got {stdout_path}"
);
// The created file should live in <dir>, not <dir>/.. (the parent).
let parent = std::path::Path::new(stdout_path)
.parent()
.expect("output path should have a parent");
assert_eq!(

Check failure on line 1205 in tests/by-util/test_mktemp.rs

View workflow job for this annotation

GitHub Actions / Style and Lint (ubuntu-24.04, unix)

ERROR: `cargo clippy`: variables can be used directly in the `format!` string (file:'tests/by-util/test_mktemp.rs', line:1205)

Check failure on line 1205 in tests/by-util/test_mktemp.rs

View workflow job for this annotation

GitHub Actions / Style and Lint (unix)

ERROR: `cargo clippy`: variables can be used directly in the `format!` string (file:'tests/by-util/test_mktemp.rs', line:1205)
parent,
dir.path(),
"expected file to be created in {:?}, got {:?}",
dir.path(),
parent
);
assert!(
std::path::Path::new(stdout_path).exists(),
"expected mktemp to actually create {stdout_path}"
);
}

#[test]
#[cfg(unix)]
fn test_mktemp_dotdot_prefix_with_tmpdir_flag() {
// Same fix should apply when the template is passed via --tmpdir / -p.
let scene = TestScenario::new(util_name!());
let dir = tempdir().unwrap();

let result = scene
.ucmd()
.arg("-p")
.arg(dir.path())
.arg("..XXXXXX")
.succeeds();

let stdout_path = result.stdout_str().trim();
let file_name = std::path::Path::new(stdout_path)
.file_name()
.expect("output path should have a file name")
.to_str()
.expect("output path should be UTF-8");
assert!(
file_name.starts_with(".."),
"expected file name to start with '..', got {stdout_path}"
);
}
Loading