From 24630db45e15b7033dc20555cb60cedbae73c858 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Fri, 23 Sep 2022 15:56:44 -0400 Subject: [PATCH] cp: allow removing symbolic link loop destination Allow `cp --remove-destination` to remove a symbolic link loop (or a symbolic link that initiates a chain of too many symbolic links). Before this commit, if `loop` were a symbolic link to itself, then cp --remove-destination file loop would fail with an error message. After this commit, it succeeds. This matches the behaviotr of GNU cp. --- src/uu/cp/src/cp.rs | 11 ++++++++++- tests/by-util/test_cp.rs | 13 +++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index ef8fb93ec3b..18d851c825c 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1383,6 +1383,15 @@ fn handle_existing_dest( Ok(()) } +/// Decide whether the given path exists. +fn file_or_link_exists(path: &Path) -> bool { + // Using `Path.exists()` or `Path.try_exists()` is not sufficient, + // because if `path` is a symbolic link and there are too many + // levels of symbolic link, then those methods will return false + // or an OS error. + path.symlink_metadata().is_ok() +} + /// Copy the a file from `source` to `dest`. `source` will be dereferenced if /// `options.dereference` is set to true. `dest` will be dereferenced only if /// the source was not a symlink. @@ -1400,7 +1409,7 @@ fn copy_file( symlinked_files: &mut HashSet, source_in_command_line: bool, ) -> CopyResult<()> { - if dest.exists() { + if file_or_link_exists(dest) { handle_existing_dest(source, dest, options, source_in_command_line)?; } diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 45b33157663..c6cdc2fed85 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -2095,3 +2095,16 @@ fn test_cp_mode_hardlink_no_dereference() { assert!(at.symlink_exists("z")); assert_eq!(at.read_symlink("z"), "slink"); } + +#[test] +fn test_remove_destination_symbolic_link_loop() { + let (at, mut ucmd) = at_and_ucmd!(); + at.symlink_file("loop", "loop"); + at.plus("loop"); + at.touch("f"); + ucmd.args(&["--remove-destination", "f", "loop"]) + .succeeds() + .no_stdout() + .no_stderr(); + assert!(at.file_exists("loop")); +}