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: Wed, 27 Jul 2011 10:35:14 +0100

On 26/07/11 14:23, Jim Meyering wrote:
> Pádraig Brady wrote:
> ...
> 
>> Note also your original test didn't fail for me on ext4 on F15.
> 

> I suspect you'll see that it's processing those two files in the reverse
> order on your system.  In case it's kernel-related, I'm using this:
>   2.6.38.8-32.fc15.x86_64
> and the disk is an SSD.

My guess here is there is inode sorting going on,
which is unstable and returning matching inodes in
a non deterministic order?

Anyway I've updated the test (attached) to try both ways.

cheers,
Pádraig.
>From 2aea1828a1aab158f68cccf3eac408203889021e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <[email-masked]>
Date: Wed, 27 Jul 2011 09:32:39 +0100
Subject: [PATCH] tests: cp/preserve-link: test all relevant paths

* tests/cp/preserve-link: Add test cases for when a missing
link in the destination tree is encountered first and second.
Also add cases for old and new separate files in the destination
tree, both to make the clobbering behavior explicit, and to
test any changes in this area in future.
---
 tests/cp/preserve-link |   68 ++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 60 insertions(+), 8 deletions(-)

diff --git a/tests/cp/preserve-link b/tests/cp/preserve-link
index d0da873..e3c31f9 100755
--- a/tests/cp/preserve-link
+++ b/tests/cp/preserve-link
@@ -26,15 +26,67 @@ same_inode()
     v=$(stat --format %i "$2") && test "$u" = "$v"
 }
 
-mkdir -p s t/s || framework_failure_
-touch s/f t/s/f || framework_failure_
-ln s/f s/link || framework_failure_
+create_source_tree()
+{
+  rm -Rf s
+  mkdir s || framework_failure_
+
+  # a missing link in dest will be created
+  touch s/f || framework_failure_
+  ln s/f s/linkm || framework_failure_
+
+  # an existing link in dest will be maintained
+  ln s/f s/linke || framework_failure_
+
+  # a separate older file in dest will be overwritten
+  ln s/f s/fileo || framework_failure_
+
+  # a separate newer file in dest will be overwritten!
+  ln s/f s/fileu || framework_failure_
+}
+
+create_target_tree()
+{
+  f=$1 # which of f or linkm to create in t/
+
+  rm -Rf t
+  mkdir -p t/s/ || framework_failure_
+
+  # a missing link in dest must be created
+  touch t/s/$f || framework_failure_
+
+  # an existing link must be maintained
+  ln t/s/$f t/s/linke || framework_failure_
+
+  # a separate older file in dest will be overwritten
+  touch -d '-1 hour' t/s/fileo || framework_failure_
+
+  # a separate newer file in dest will be overwritten!
+  touch -d '+1 hour' t/s/fileu || framework_failure_
+}
+
+
+# Note we repeat this, creating either one of
+# two hard linked files from source in the dest, so as to
+# test both paths in `cp` for creating the hard links.
+# The path taken by cp is dependent on which cp encounters
+# first in the source, which is non deterministic currently
+# (I'm guessing that results are sorted by inode and
+# beauses they're the same here, and due to the sort
+# being unstable, either can be processed first).
+create_source_tree
+
+for f in f linkm; do
+  create_target_tree $f
 
-# 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.
-cp -au s t || fail=1
+  # Copy all the hard links across.  With cp from coreutils-8.12
+  # and prior, it would sometimes mistakenly copy rather than link.
+  cp -au s t || fail=1
 
-same_inode t/s/f t/s/link || fail=1
+  same_inode t/s/f t/s/linkm || 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
+done
 
 Exit $fail
-- 
1.7.5.2