summaryrefslogtreecommitdiff
path: root/src/copy.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/copy.c')
-rw-r--r--src/copy.c29
1 files changed, 26 insertions, 3 deletions
diff --git a/src/copy.c b/src/copy.c
index 4810de872..541ca20cb 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -1226,10 +1226,33 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
same_link = same;
/* If both the source and destination files are symlinks (and we'll
- know this here IFF preserving symlinks), then it's ok -- as long
- as they are distinct. */
+ know this here IFF preserving symlinks), then it's usually ok
+ when they are distinct. */
if (S_ISLNK (src_sb->st_mode) && S_ISLNK (dst_sb->st_mode))
- return ! same_name (src_name, dst_name);
+ {
+ bool sn = same_name (src_name, dst_name);
+ if ( ! sn)
+ {
+ /* It's fine when we're making any type of backup. */
+ if (x->backup_type != no_backups)
+ return true;
+
+ /* Here we have two symlinks that are hard-linked together,
+ and we're not making backups. In this unusual case, simply
+ returning true would lead to mv calling "rename(A,B)",
+ which would do nothing and return 0. I.e., A would
+ not be removed. Hence, the solution is to tell the
+ caller that all it must do is unlink A and return. */
+ if (same_link)
+ {
+ *unlink_src = true;
+ *return_now = true;
+ return true;
+ }
+ }
+
+ return ! sn;
+ }
src_sb_link = src_sb;
dst_sb_link = dst_sb;