]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
cp: fix --link regarding the dereferencing of symbolic links
authorGian Piero Carrubba <gpiero@rm-rf.it>
Thu, 7 Nov 2013 22:35:52 +0000 (23:35 +0100)
committerBernhard Voelker <mail@bernhard-voelker.de>
Thu, 7 Nov 2013 22:45:22 +0000 (23:45 +0100)
* src/copy.c (create_hard_link): Add a bool 'dereference' parameter,
and pass AT_SYMLINK_FOLLOW as 'flags' to linkat() when dereference
is true.
(should_dereference): Add new 'bool' function to determine if a
file should be dereferenced or not.
(copy_internal): Use the above new should_dereference() and remember
its return value in a new local bool 'dereference' variable.  Use that
in all three calls to create_hard_link().
* src/cp.c (main): after parsing the options, if x.dereference is
still DEFEF_UNDEFINED and the x.recursive is true, then only set
x.dereference to DEREF_NEVER iff --link was not specified.
* doc/coreutils.texi (cp invocation): Mention that cp(1) does not
follow symbolic links in the source when --link is specified.
Likewise in the description of the -R option when used together with
that option.
* tests/cp/same-file.sh: Adapt the expected results for the -fl,
the -bl and the -bfl tests.
* tests/cp/link-deref.sh: Add a new test.
* tests/local.mk (all_tests): Reference the above new test.
* NEWS (Changes in behavior): Mention the change.

This fixes http://bugs.gnu.org/15173

Co-authored-by: Bernhard Voelker <mail@bernhard-voelker.de>
NEWS
doc/coreutils.texi
src/copy.c
src/cp.c
tests/cp/link-deref.sh [new file with mode: 0755]
tests/cp/same-file.sh
tests/local.mk

diff --git a/NEWS b/NEWS
index bb35583b6d5390f1b4f028a94f63b77a7c6d8c1c..6f685b4786d3c3b5e068661e60f77a0c2067dfcb 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -70,6 +70,11 @@ GNU coreutils NEWS                                    -*- outline -*-
 
 ** Changes in behavior
 
+  cp --link now dereferences a symbolic link as source before creating the
+  hard link in the destination unless the -P,--no-deref option is specified.
+  Previously, it would create a hard link of the symbolic link, even when
+  the dereferencing options -L or -H were specified.
+
   dd status=none now suppresses all non fatal diagnostic messages,
   not just the transfer counts.
 
index cc806d52cc76a61bc4c7c499a6299cdd354e8713..3ae39d18cbd4a5b6eb1d24949fdb125f9bbacbbc 100644 (file)
@@ -8052,8 +8052,8 @@ copy recursively by descending into source directories and copying files
 to corresponding destination directories.
 
 When copying from a symbolic link, @command{cp} normally follows the
-link only when not copying
-recursively.  This default can be overridden with the
+link only when not copying recursively or when @option{--link}
+(@option{-l}) is used.  This default can be overridden with the
 @option{--archive} (@option{-a}), @option{-d}, @option{--dereference}
 (@option{-L}), @option{--no-dereference} (@option{-P}), and
 @option{-H} options.  If more than one of these options is specified,
@@ -8327,7 +8327,8 @@ any missing intermediate directories.
 @cindex recursively copying directories
 @cindex non-directories, copying as special files
 Copy directories recursively.  By default, do not follow symbolic
-links in the source; see the @option{--archive} (@option{-a}), @option{-d},
+links in the source unless used together with the @option{--link}
+(@option{-l}) option; see the @option{--archive} (@option{-a}), @option{-d},
 @option{--dereference} (@option{-L}), @option{--no-dereference}
 (@option{-P}), and @option{-H} options.  Special files are copied by
 creating a destination file of the same type as the source; see the
index f66ab464658c47e150cb04a823f7a4de77a82574..bcae12368d0c662df3ae49b889959c56f9aa7e7a 100644 (file)
@@ -1558,18 +1558,23 @@ restore_default_fscreatecon_or_die (void)
            _("failed to restore the default file creation context"));
 }
 
-/* Create a hard link DST_NAME to SRC_NAME, honoring the REPLACE and
-   VERBOSE settings.  Return true upon success.  Otherwise, diagnose
-   the failure and return false.
-   If SRC_NAME is a symbolic link it will not be followed.  If the system
-   doesn't support hard links to symbolic links, then DST_NAME will
-   be created as a symbolic link to SRC_NAME.  */
+/* Create a hard link DST_NAME to SRC_NAME, honoring the REPLACE, VERBOSE and
+   DEREFERENCE settings.  Return true upon success.  Otherwise, diagnose the
+   failure and return false.  If SRC_NAME is a symbolic link, then it will not
+   be followed unless DEREFERENCE is true.
+   If the system doesn't support hard links to symbolic links, then DST_NAME
+   will be created as a symbolic link to SRC_NAME.  */
 static bool
 create_hard_link (char const *src_name, char const *dst_name,
-                  bool replace, bool verbose)
+                  bool replace, bool verbose, bool dereference)
 {
-  /* We want to guarantee that symlinks are not followed.  */
-  bool link_failed = (linkat (AT_FDCWD, src_name, AT_FDCWD, dst_name, 0) != 0);
+  /* We want to guarantee that symlinks are not followed, unless requested.  */
+  int flags = 0;
+  if (dereference)
+    flags = AT_SYMLINK_FOLLOW;
+
+  bool link_failed = (linkat (AT_FDCWD, src_name, AT_FDCWD, dst_name, flags)
+                      != 0);
 
   /* If the link failed because of an existing destination,
      remove that file and then call link again.  */
@@ -1582,7 +1587,8 @@ create_hard_link (char const *src_name, char const *dst_name,
         }
       if (verbose)
         printf (_("removed %s\n"), quote (dst_name));
-      link_failed = (linkat (AT_FDCWD, src_name, AT_FDCWD, dst_name, 0) != 0);
+      link_failed = (linkat (AT_FDCWD, src_name, AT_FDCWD, dst_name, flags)
+                     != 0);
     }
 
   if (link_failed)
@@ -1595,6 +1601,17 @@ create_hard_link (char const *src_name, char const *dst_name,
   return true;
 }
 
+/* Return true if the current file should be (tried to be) dereferenced:
+   either for DEREF_ALWAYS or for DEREF_COMMAND_LINE_ARGUMENTS in the case
+   where the current file is a COMMAND_LINE_ARG; otherwise return false.  */
+static inline bool _GL_ATTRIBUTE_PURE
+should_dereference (const struct cp_options *x, bool command_line_arg)
+{
+  return x->dereference == DEREF_ALWAYS
+         || (x->dereference == DEREF_COMMAND_LINE_ARGUMENTS
+             && command_line_arg);
+}
+
 /* Copy the file SRC_NAME to the file DST_NAME.  The files may be of
    any type.  NEW_DST should be true if the file DST_NAME cannot
    exist because its parent directory was just created; NEW_DST should
@@ -1670,6 +1687,8 @@ copy_internal (char const *src_name, char const *dst_name,
       record_file (x->src_info, src_name, &src_sb);
     }
 
+  bool dereference = should_dereference (x, command_line_arg);
+
   if (!new_dst)
     {
       /* Regular files can be created by writing through symbolic
@@ -1748,7 +1767,7 @@ copy_internal (char const *src_name, char const *dst_name,
                       /* Note we currently replace DST_NAME unconditionally,
                          even if it was a newer separate file.  */
                       if (! create_hard_link (earlier_file, dst_name, true,
-                                              x->verbose))
+                                              x->verbose, dereference))
                         {
                           goto un_backup;
                         }
@@ -2078,7 +2097,8 @@ copy_internal (char const *src_name, char const *dst_name,
         }
       else
         {
-          if (! create_hard_link (earlier_file, dst_name, true, x->verbose))
+          if (! create_hard_link (earlier_file, dst_name, true, x->verbose,
+                                  dereference))
             goto un_backup;
 
           return true;
@@ -2389,7 +2409,7 @@ copy_internal (char const *src_name, char const *dst_name,
            && !(LINK_FOLLOWS_SYMLINKS && S_ISLNK (src_mode)
                 && x->dereference == DEREF_NEVER))
     {
-      if (! create_hard_link (src_name, dst_name, false, false))
+      if (! create_hard_link (src_name, dst_name, false, false, dereference))
         goto un_backup;
     }
   else if (S_ISREG (src_mode)
index 7bc8630fbc76570af54c21e3dafc4232a724722c..78c0a0402fb851ed3c2e37a9bbf55a885ad841cd 100644 (file)
--- a/src/cp.c
+++ b/src/cp.c
@@ -1135,7 +1135,7 @@ main (int argc, char **argv)
 
   if (x.dereference == DEREF_UNDEFINED)
     {
-      if (x.recursive)
+      if (x.recursive && ! x.hard_link)
         /* This is compatible with FreeBSD.  */
         x.dereference = DEREF_NEVER;
       else
diff --git a/tests/cp/link-deref.sh b/tests/cp/link-deref.sh
new file mode 100755 (executable)
index 0000000..91a6c5d
--- /dev/null
@@ -0,0 +1,110 @@
+#!/bin/sh
+# Exercise cp --link's behavior regarding the dereferencing of symbolic links.
+
+# Copyright (C) 2013 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ cp
+
+mkdir dir              || framework_failure_
+: > file               || framework_failure_
+ln -s dir     dirlink  || framework_failure_
+ln -s file    filelink || framework_failure_
+ln -s nowhere danglink || framework_failure_
+
+# printf format of the output line.
+outformat='%s|result=%s|inode=%s|type=%s|error=%s\n'
+
+for src in dirlink filelink danglink; do
+  # Get symlink's target.
+  tgt=$(readlink $src) || framework_failure_
+  # Get inodes and file type of the symlink (src) and its target (tgt).
+  # Note: this will fail for 'danglink'; catch it.
+  ino_src="$(stat -c '%i' $src)" || framework_failure_
+  typ_src="$(stat -c '%F' $src)" || framework_failure_
+  ino_tgt="$(stat -c '%i' $tgt 2>/dev/null)" || ino_tgt=
+  typ_tgt="$(stat -c '%F' $tgt 2>/dev/null)" || typ_tgt=
+
+  for o in '' -L -H -P; do
+    for r in '' -R; do
+
+      command="cp --link $o $r $src dst"
+      $command 2> err
+      result=$?
+
+      # Get inode and file type of the destination (which may fail, too).
+      ino_dst="$(stat -c '%i' dst 2>/dev/null)" || ini_dst=
+      typ_dst="$(stat -c '%F' dst 2>/dev/null)" || typ_dst=
+
+      # Print the actual result in a certain format.
+      printf "$outformat" \
+        "$command"   \
+        "$result"   \
+        "$ino_dst"  \
+        "$typ_dst"  \
+        "$(< err)"  \
+        > out
+
+      # What was expected?
+      if [ "$o" = "-P" ]; then
+        # cp --link should not dereference if -P is given.
+        exp_result=0
+        exp_inode=$ino_src
+        exp_ftype=$typ_src
+        exp_error=
+      elif [ "$src" = 'danglink' ]; then
+        # Dereferencing should fail for the 'danglink'.
+        exp_result=1
+        exp_inode=
+        exp_ftype=
+        exp_error="cp: cannot stat 'danglink': No such file or directory"
+      elif [ "$src" = 'dirlink' ] && [ "$r" != '-R' ]; then
+        # Dereferencing should fail for the 'dirlink' without -R.
+        exp_result=1
+        exp_inode=
+        exp_ftype=
+        exp_error="cp: omitting directory 'dirlink'"
+      elif [ "$src" = 'dirlink' ]; then
+        # cp --link -R 'dirlink' should create a new directory.
+        exp_result=0
+        exp_inode=$ino_dst
+        exp_ftype=$typ_dst
+        exp_error=
+      else
+        # cp --link 'filelink' should create a hard link to the target.
+        exp_result=0
+        exp_inode=$ino_tgt
+        exp_ftype=$typ_tgt
+        exp_error=
+      fi
+
+      # Print the expected result in a certain format.
+      printf "$outformat" \
+        "$command"   \
+        "$exp_result" \
+        "$exp_inode"  \
+        "$exp_ftype"  \
+        "$exp_error"  \
+        > exp
+
+      compare exp out || { ls -lid $src $tgt dst; fail=1; }
+
+      rm -rf dst err exp out || framework_failure_
+    done
+  done
+done
+
+Exit $fail
index 003a62ba7f100a12bb0ad593f67c0295b868916e..326f9c6bd1cdf70b488ac04b170d56b9de1628e2 100755 (executable)
@@ -189,9 +189,9 @@ cat <<\EOF | sed "$remove_these_sed" > expected
 0 -bf (foo sl1 -> foo sl2 sl2.~1~ -> foo)
 0 -bdf (foo sl1 -> foo sl2 -> foo sl2.~1~ -> foo)
 1 -l [cp: cannot create hard link 'sl2' to 'sl1'] (foo sl1 -> foo sl2 -> foo)
-0 -fl (foo sl1 -> foo sl2 -> foo)
-0 -bl (foo sl1 -> foo sl2 -> foo sl2.~1~ -> foo)
-0 -bfl (foo sl1 -> foo sl2 -> foo sl2.~1~ -> foo)
+0 -fl (foo sl1 -> foo sl2)
+0 -bl (foo sl1 -> foo sl2 sl2.~1~ -> foo)
+0 -bfl (foo sl1 -> foo sl2 sl2.~1~ -> foo)
 
 1 [cp: 'foo' and 'hardlink' are the same file] (foo hardlink)
 1 -d [cp: 'foo' and 'hardlink' are the same file] (foo hardlink)
index e18deacc71e12135654cb75b9f070a3dde550627..3c924255fef4db9aaa0465b2658bae9968579fe6 100644 (file)
@@ -427,6 +427,7 @@ all_tests =                                 \
   tests/cp/file-perm-race.sh                   \
   tests/cp/into-self.sh                                \
   tests/cp/link.sh                             \
+  tests/cp/link-deref.sh                       \
   tests/cp/link-no-deref.sh                    \
   tests/cp/link-preserve.sh                    \
   tests/cp/link-symlink.sh                     \