Subject: bug#15173: [PATCH] cp: with --link always use linkat() if available

From: Pádraig Brady <[email-masked]>
Date: Mon, 10 Feb 2014 02:18:10 +0000

On 12/12/2013 06:44 PM, Pádraig Brady wrote:
> On 12/09/2013 02:24 AM, Pádraig Brady wrote:
>> Sorry if you get multiple copies of this.
>>
>> The test for this is failing on solaris 10 (NFS)
>> It does seem that hardlinks to symlinks are supported:
>>
>> $ touch tfile
>> $ ln -s tfile tlink
>> $ src/ln -L tlink tlink-ln-L
>> $ src/ln -P tlink tlink-ln-P
>> $ src/ln tlink tlink-ln
>> $ ls -li tfile tlink*
>>       8550 -rw-r--r--   3 padraig  csw            0 Dec  9 01:18 tfile
>>       8551 lrwxrwxrwx   2 padraig  csw            5 Dec  9 01:19 tlink -> tfile
>>       8550 -rw-r--r--   3 padraig  csw            0 Dec  9 01:18 tlink-ln
>>       8550 -rw-r--r--   3 padraig  csw            0 Dec  9 01:18 tlink-ln-L
>>       8551 lrwxrwxrwx   2 padraig  csw            5 Dec  9 01:19 tlink-ln-P -> tfile
>>
>> But we have linkat() emulation in place I think:
>>
>> $ grep LINK lib/config.h
> ...
>> /* #undef HAVE_LINKAT */
>> #define LINK_FOLLOWS_SYMLINKS -1
> 
>> FAIL: tests/cp/link-deref
>> =========================
> 
>> --- exp	Mon Dec  9 01:00:08 2013
>> +++ out	Mon Dec  9 01:00:08 2013
>> @@ -1,1 +1,1 @@
>> -cp --link -P  dirlink dst|result=0|inode=8436|type=symbolic link|error=
>> +cp --link -P  dirlink dst|result=0|inode=8467|type=symbolic link|error=
>> + rm -f diff.out
>> + false
>> + ls -lid dirlink dir dst
>> 8434 drwxr-xr-x 2 padraig csw 2 Dec  9 01:00 dir
>> 8436 lrwxrwxrwx 1 padraig csw 3 Dec  9 01:00 dirlink -> dir
>> 8467 lrwxrwxrwx 1 padraig csw 3 Dec  9 01:00 dst -> dir
>> + fail=1
> 
> So the attached should address this on FreeBSD ast least
> where we HAVE_LINKAT so don't need to fallback to
> the symlink -> symlink emulation in copy.c

I still think this is correct, and while I didn't think it
worth the small risk right before the 8.22 release,
I hope to push this rebased patch soon.

thanks,
Pádraig.
>From 1893578fb0cdbb1875a42bc86cad452f6c62e5dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <[email-masked]>
Date: Thu, 12 Dec 2013 18:31:48 +0000
Subject: [PATCH] cp: with --link always use linkat() if available

* src/copy.c (copy_reg): If linkat() is available it doesn't
matter about the gnulib emulation provided, and thus the
LINK_FOLLOWS_SYMLINKS should not have significance here.
This was noticed on FreeBSD and the consequence is that
cp --link will create hardlinks to symlinks there, rather
than emulating with symlinks to symlinks.
* tests/cp/link-deref.sh: Adjust to remove the no longer
significant LINK_FOLLOWS_SYMLINKS check.
---
 src/copy.c             |   10 ++++++++--
 tests/cp/link-deref.sh |    3 +--
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/src/copy.c b/src/copy.c
index 3e4cbff..d560dfd 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -2492,8 +2492,11 @@ copy_internal (char const *src_name, char const *dst_name,
      timestamps or ownership so we only call it when we know the
      emulation will not be needed.  */
   else if (x->hard_link
+#if ! defined HAVE_LINKAT
            && !(LINK_FOLLOWS_SYMLINKS && S_ISLNK (src_mode)
-                && x->dereference == DEREF_NEVER))
+                && x->dereference == DEREF_NEVER)
+#endif
+          )
     {
       if (! create_hard_link (src_name, dst_name, false, false, dereference))
         goto un_backup;
@@ -2632,8 +2635,11 @@ copy_internal (char const *src_name, char const *dst_name,
   /* If we've just created a hard-link due to cp's --link option,
      we're done.  */
   if (x->hard_link && ! S_ISDIR (src_mode)
+#if ! defined HAVE_LINKAT
       && !(LINK_FOLLOWS_SYMLINKS && S_ISLNK (src_mode)
-           && x->dereference == DEREF_NEVER))
+           && x->dereference == DEREF_NEVER)
+#endif
+     )
     return delayed_ok;
 
   if (copied_as_regular)
diff --git a/tests/cp/link-deref.sh b/tests/cp/link-deref.sh
index e56d592..b249fce 100755
--- a/tests/cp/link-deref.sh
+++ b/tests/cp/link-deref.sh
@@ -19,8 +19,7 @@
 . "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
 print_ver_ cp
 
-if grep '^#define HAVE_LINKAT 1' "$CONFIG_HEADER" > /dev/null \
-   && grep '^#define LINK_FOLLOWS_SYMLINKS 0' "$CONFIG_HEADER" > /dev/null; then
+if grep '^#define HAVE_LINKAT 1' "$CONFIG_HEADER" > /dev/null; then
   # With this config (which is the case on GNU/Linux) cp will attempt to
   # linkat() to hardlink a symlink.  So now see if the current file system
   # supports this operation.
-- 
1.7.7.6