Subject: bug#8419: cp -au : New hard links in source becomes new files at destination when using cp -au

From: Pádraig Brady <[email-masked]>
Date: Tue, 26 Jul 2011 01:33:32 +0100

On 25/07/11 17:26, Pádraig Brady wrote:
> Actually I'm wondering now whether the new code
> should be unconditionally replacing the dest?
> What if the dest is a separate newer file?
> I.E. I think the following amended test should pass?
> Also what about backups if the separate file is older?

Well backups take a different path, as do
older or non existing destination paths.
So how about this change to just remove
the new create_hard_link: call and beef up the test?

cheers,
Pádraig.
diff --git a/src/copy.c b/src/copy.c
index df8b1db..d6a0d1a 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -1633,11 +1633,11 @@ copy_internal (char const *src_name, char const *dst_name,
                      this src/dest pair, in case this source file is
                      hard-linked to another one.  In that case, we'll use
                      the mapping information to link the corresponding
-                     destination names.  */
-                  earlier_file = remember_copied (dst_name, src_sb.st_ino,
-                                                  src_sb.st_dev);
-                  if (earlier_file)
-                    goto create_hard_link;
+                     destination names.  Note we don't hard link DST_NAME
+                     here, because it may be a separate file with newer
+                     or same timestamp. If it's older than SRC_NAME,
+                     then this path is not taken.  */
+                  remember_copied (dst_name, src_sb.st_ino, src_sb.st_dev);
 
                   return true;
                 }
@@ -1959,7 +1959,6 @@ copy_internal (char const *src_name, char const *dst_name,
         }
       else
         {
-        create_hard_link:;
           /* We want to guarantee that symlinks are not followed.  */
           bool link_failed = (linkat (AT_FDCWD, earlier_file, AT_FDCWD,
                                       dst_name, 0) != 0);
diff --git a/tests/cp/preserve-link b/tests/cp/preserve-link
index d0da873..6beae02 100755
--- a/tests/cp/preserve-link
+++ b/tests/cp/preserve-link
@@ -28,13 +28,31 @@ same_inode()
 
 mkdir -p s t/s || framework_failure_
 touch s/f t/s/f || framework_failure_
+
+# a non existing link must be linked in the dest tree
 ln s/f s/link || framework_failure_
 
+# an existing link must be updated
+ln s/f s/linke || framework_failure_
+ln t/s/f t/s/linke || framework_failure_
+
+# an updated older file must be overwritten
+ln s/f s/fileo || framework_failure_
+touch -d "-1 hour" t/s/fileo || framework_failure_
+
+# an updated non linked file must not be overwritten
+ln s/f s/fileu || framework_failure_
+touch -d "+1 hour" t/s/fileu || framework_failure_
+
 # This must create a hard link, t/s/link, to the existing file, t/s/f.
 # With cp from coreutils-8.12 and prior, it would mistakenly copy
 # the file rather than creating the link.
+touch -d "+1 hour" t/s/f || framework_failure_
 cp -au s t || fail=1
 
 same_inode t/s/f t/s/link || fail=1
+same_inode t/s/f t/s/linke || fail=1
+same_inode t/s/f t/s/fileo || fail=1
+same_inode t/s/f t/s/fileu && fail=1
 
 Exit $fail