From b40e2e9c83fa48e450686ba7fc201417e39df326 Mon Sep 17 00:00:00 2001 From: Varun Varma Date: Mon, 31 Aug 2026 15:12:36 -0400 Subject: [PATCH] cp: avoid resolving cwd for absolute recursive copies Recursive copies unconditionally resolve cwd. This causes errors when the working directory has been removed, even if the source and destination paths were absolute. This fix is to stop resolving the cwd if the source path is absolute. Fixes https://github.com/uutils/coreutils/issues/9105. --- src/uu/cp/src/copydir.rs | 6 +++++- tests/by-util/test_cp.rs | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/uu/cp/src/copydir.rs b/src/uu/cp/src/copydir.rs index 25b326d3ad9..ba40d3bc623 100644 --- a/src/uu/cp/src/copydir.rs +++ b/src/uu/cp/src/copydir.rs @@ -119,7 +119,11 @@ struct Context<'a> { impl<'a> Context<'a> { fn new(root: &'a Path, target: &'a Path) -> io::Result { - let current_dir = env::current_dir()?; + let current_dir = if root.is_absolute() { + PathBuf::new() + } else { + env::current_dir()? + }; let root_path = current_dir.join(root); let target_is_file = target.is_file(); let root_parent = diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index f216f52a3be..24674a724de 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -7834,6 +7834,52 @@ fn test_cp_preserve_context_root() { } } +// Regression test for https://github.com/uutils/coreutils/issues/9105. +// Absolute operands must not require resolving the current working directory. +#[cfg(unix)] +#[rstest] +#[case::existing_target(true)] +#[case::new_target(false)] +fn test_cp_absolute_paths_from_deleted_cwd(#[case] target_exists: bool) { + use std::process::Command; + + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.mkdir_all("src/sub"); + at.write("src/sub/file", "contents"); + at.mkdir("deleted-cwd"); + if target_exists { + at.mkdir("dst"); + } + + let source = at.plus("src"); + let target = at.plus("dst"); + let deleted_cwd = at.plus("deleted-cwd"); + let output = Command::new("sh") + .arg("-c") + .arg("cd \"$1\" && rmdir \"$1\" && exec \"$2\" \"$3\" -Ra --no-preserve=ownership \"$4\" \"$5\"") + .arg("sh") + .arg(&deleted_cwd) + .arg(&ts.bin_path) + .arg(&ts.util_name) + .arg(&source) + .arg(&target) + .output() + .unwrap(); + + assert!( + output.status.success(), + "cp failed from a deleted cwd: {}", + String::from_utf8_lossy(&output.stderr) + ); + let copied_file = if target_exists { + target.join("src/sub/file") + } else { + target.join("sub/file") + }; + assert_eq!(std::fs::read_to_string(copied_file).unwrap(), "contents"); +} + // Test copying current directory (.) to an existing directory. // This tests the special case where we copy the current directory // to an existing directory, ensuring the directory name is properly