diff --git a/debian/patches/cp-improve-symlink-handling.patch b/debian/patches/cp-improve-symlink-handling.patch new file mode 100644 index 0000000000000000000000000000000000000000..2ce8c96eceb946255b4e0db9708992650c4123a4 --- /dev/null +++ b/debian/patches/cp-improve-symlink-handling.patch @@ -0,0 +1,194 @@ +From: Michael Debertol <michael.debertol@gmail.com> +Date: Thu, 17 Jun 2021 22:26:13 +0200 +Subject: cp: improve symlink handling + +Origin: upstream, https://github.com/uutils/coreutils/commit/12a1c87cb8e72f0df8b03594f425f8c98268ec1c +--- + src/uu/cp/src/cp.rs | 79 +++++++++++++++++++++++++++--------------------- + tests/by-util/test_cp.rs | 26 ++++++++++++++++ + 2 files changed, 70 insertions(+), 35 deletions(-) + +diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs +index 569ee78..d6420fb 100644 +--- a/src/uu/cp/src/cp.rs ++++ b/src/uu/cp/src/cp.rs +@@ -47,7 +47,6 @@ use std::os::windows::ffi::OsStrExt; + use std::path::{Path, PathBuf, StripPrefixError}; + use std::str::FromStr; + use std::string::ToString; +-use uucore::fs::resolve_relative_path; + use uucore::fs::{canonicalize, CanonicalizeMode}; + use walkdir::WalkDir; + +@@ -202,7 +201,6 @@ pub struct Options { + copy_contents: bool, + copy_mode: CopyMode, + dereference: bool, +- no_dereference: bool, + no_target_dir: bool, + one_file_system: bool, + overwrite: OverwriteMode, +@@ -620,11 +618,12 @@ impl Options { + attributes_only: matches.is_present(OPT_ATTRIBUTES_ONLY), + copy_contents: matches.is_present(OPT_COPY_CONTENTS), + copy_mode: CopyMode::from_matches(matches), +- dereference: matches.is_present(OPT_DEREFERENCE), + // No dereference is set with -p, -d and --archive +- no_dereference: matches.is_present(OPT_NO_DEREFERENCE) ++ dereference: !(matches.is_present(OPT_NO_DEREFERENCE) + || matches.is_present(OPT_NO_DEREFERENCE_PRESERVE_LINKS) +- || matches.is_present(OPT_ARCHIVE), ++ || matches.is_present(OPT_ARCHIVE) ++ || recursive) ++ || matches.is_present(OPT_DEREFERENCE), + one_file_system: matches.is_present(OPT_ONE_FILE_SYSTEM), + overwrite: OverwriteMode::from_matches(matches), + parents: matches.is_present(OPT_PARENTS), +@@ -876,7 +875,14 @@ fn copy_source( + options: &Options, + ) -> CopyResult<()> { + let source_path = Path::new(&source); +- if source_path.is_dir() { ++ // if no-dereference is enabled and this is a symlink, don't treat it as a directory ++ if source_path.is_dir() ++ && !(!options.dereference ++ && fs::symlink_metadata(source_path) ++ .unwrap() ++ .file_type() ++ .is_symlink()) ++ { + // Copy as directory + copy_directory(source, target, options) + } else { +@@ -917,7 +923,7 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult + return Err(format!("omitting directory '{}'", root.display()).into()); + } + +- let root_path = Path::new(&root).canonicalize()?; ++ let root_path = env::current_dir().unwrap().join(root); + + let root_parent = if target.exists() { + root_path.parent() +@@ -938,17 +944,15 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult + #[cfg(any(windows, target_os = "redox"))] + let mut hard_links: Vec<(String, u64)> = vec![]; + +- for path in WalkDir::new(root).same_file_system(options.one_file_system) { ++ for path in WalkDir::new(root) ++ .same_file_system(options.one_file_system) ++ .follow_links(options.dereference) ++ { + let p = or_continue!(path); + let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink(); +- let path = if (options.no_dereference || options.dereference) && is_symlink { +- // we are dealing with a symlink. Don't follow it +- match env::current_dir() { +- Ok(cwd) => cwd.join(resolve_relative_path(p.path())), +- Err(e) => crash!(1, "failed to get current directory {}", e), +- } +- } else { +- or_continue!(p.path().canonicalize()) ++ let path = match env::current_dir() { ++ Ok(cwd) => cwd.join(&p.path()), ++ Err(e) => crash!(1, "failed to get current directory {}", e), + }; + + let local_to_root_parent = match root_parent { +@@ -972,9 +976,10 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult + }; + + let local_to_target = target.join(&local_to_root_parent); +- +- if path.is_dir() && !local_to_target.exists() { +- or_continue!(fs::create_dir_all(local_to_target.clone())); ++ if is_symlink && !options.dereference { ++ copy_link(&path, &local_to_target)?; ++ } else if path.is_dir() && !local_to_target.exists() { ++ or_continue!(fs::create_dir_all(local_to_target)); + } else if !path.is_dir() { + if preserve_hard_links { + let mut found_hard_link = false; +@@ -1232,22 +1237,8 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> + ReflinkMode::Never => {} + } + } +- } else if options.no_dereference && fs::symlink_metadata(&source)?.file_type().is_symlink() { +- // Here, we will copy the symlink itself (actually, just recreate it) +- let link = fs::read_link(&source)?; +- let dest: Cow<'_, Path> = if dest.is_dir() { +- match source.file_name() { +- Some(name) => dest.join(name).into(), +- None => crash!( +- EXIT_ERR, +- "cannot stat ‘{}’: No such file or directory", +- source.display() +- ), +- } +- } else { +- dest.into() +- }; +- symlink_file(&link, &dest, &*context_for(&link, &dest))?; ++ } else if !options.dereference && fs::symlink_metadata(&source)?.file_type().is_symlink() { ++ copy_link(source, dest)?; + } else if source.to_string_lossy() == "/dev/null" { + /* workaround a limitation of fs::copy + * https://github.com/rust-lang/rust/issues/79390 +@@ -1264,6 +1255,24 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> + Ok(()) + } + ++fn copy_link(source: &Path, dest: &Path) -> CopyResult<()> { ++ // Here, we will copy the symlink itself (actually, just recreate it) ++ let link = fs::read_link(&source)?; ++ let dest: Cow<'_, Path> = if dest.is_dir() { ++ match source.file_name() { ++ Some(name) => dest.join(name).into(), ++ None => crash!( ++ EXIT_ERR, ++ "cannot stat ‘{}’: No such file or directory", ++ source.display() ++ ), ++ } ++ } else { ++ dest.into() ++ }; ++ symlink_file(&link, &dest, &*context_for(&link, &dest)) ++} ++ + /// Generate an error message if `target` is not the correct `target_type` + pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> { + match (target_type, target.is_dir()) { +diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs +index 1fa8212..bac7e2a 100644 +--- a/tests/by-util/test_cp.rs ++++ b/tests/by-util/test_cp.rs +@@ -1074,3 +1074,29 @@ fn test_cp_one_file_system() { + } + } + } ++ ++#[test] ++fn test_copy_dir_symlink() { ++ let (at, mut ucmd) = at_and_ucmd!(); ++ at.mkdir("dir"); ++ at.symlink_dir("dir", "dir-link"); ++ ucmd.args(&["-r", "dir-link", "copy"]).succeeds(); ++ assert_eq!(at.resolve_link("copy"), "dir"); ++} ++ ++#[test] ++fn test_copy_dir_with_symlinks() { ++ let (at, mut ucmd) = at_and_ucmd!(); ++ at.mkdir("dir"); ++ at.make_file("dir/file"); ++ ++ TestScenario::new("ln") ++ .ucmd() ++ .arg("-sr") ++ .arg(at.subdir.join("dir/file")) ++ .arg(at.subdir.join("dir/file-link")) ++ .succeeds(); ++ ++ ucmd.args(&["-r", "dir", "copy"]).succeeds(); ++ assert_eq!(at.resolve_link("copy/file-link"), "file"); ++} diff --git a/debian/patches/cp-move-symlink-check-to-the-right-place.patch b/debian/patches/cp-move-symlink-check-to-the-right-place.patch new file mode 100644 index 0000000000000000000000000000000000000000..95e411b9e22d8256d49798924838bca011b1270c --- /dev/null +++ b/debian/patches/cp-move-symlink-check-to-the-right-place.patch @@ -0,0 +1,41 @@ +From: Michael Debertol <michael.debertol@gmail.com> +Date: Fri, 18 Jun 2021 11:42:37 +0200 +Subject: cp: move symlink check to the right place + +Origin: upstream, https://github.com/uutils/coreutils/commit/315bfd65a3f07221abdb262ba8731d364441e581 +--- + src/uu/cp/src/cp.rs | 14 ++++++-------- + 1 file changed, 6 insertions(+), 8 deletions(-) + +diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs +index d6420fb..4b93b3a 100644 +--- a/src/uu/cp/src/cp.rs ++++ b/src/uu/cp/src/cp.rs +@@ -875,14 +875,7 @@ fn copy_source( + options: &Options, + ) -> CopyResult<()> { + let source_path = Path::new(&source); +- // if no-dereference is enabled and this is a symlink, don't treat it as a directory +- if source_path.is_dir() +- && !(!options.dereference +- && fs::symlink_metadata(source_path) +- .unwrap() +- .file_type() +- .is_symlink()) +- { ++ if source_path.is_dir() { + // Copy as directory + copy_directory(source, target, options) + } else { +@@ -923,6 +916,11 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult + return Err(format!("omitting directory '{}'", root.display()).into()); + } + ++ // if no-dereference is enabled and this is a symlink, copy it as a file ++ if !options.dereference && fs::symlink_metadata(root).unwrap().file_type().is_symlink() { ++ return copy_file(root, target, options); ++ } ++ + let root_path = env::current_dir().unwrap().join(root); + + let root_parent = if target.exists() { diff --git a/debian/patches/series b/debian/patches/series index cfcf418ddfbdc1646dcc1840f2595d448cbc706d..ca4378e7c6c1c6a1f4b256c2bb8f75286c70a717 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -14,6 +14,8 @@ Sort-Implement-stable-sort-ignore-non-printing-month.patch Ignore-a-test.patch Sort-Various-fixes-and-performance-improvements.patch sort-implement-k-and-t-support.patch +cp-improve-symlink-handling.patch +cp-move-symlink-check-to-the-right-place.patch # Apertis patches sort-fix-dependencies-versions.patch