summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGian Piero Carrubba <gpiero@rm-rf.it>2013-11-07 23:35:52 +0100
committerBernhard Voelker <mail@bernhard-voelker.de>2013-11-07 23:45:22 +0100
commitbf6bf52dce37832b03ecfbe1b3a3b104f532df42 (patch)
treec62463b576c33400b029667a0991367c9da47b7c
parent2c6736f92fc7d2f310714473ea84ceff57e01da2 (diff)
downloadcoreutils-bf6bf52dce37832b03ecfbe1b3a3b104f532df42.tar.xz
cp: fix --link regarding the dereferencing of symbolic links
* 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>
-rw-r--r--NEWS5
-rw-r--r--doc/coreutils.texi7
-rw-r--r--src/copy.c46
-rw-r--r--src/cp.c2
-rwxr-xr-xtests/cp/link-deref.sh110
-rwxr-xr-xtests/cp/same-file.sh6
-rw-r--r--tests/local.mk1
7 files changed, 157 insertions, 20 deletions
diff --git a/NEWS b/NEWS
index bb35583b6..6f685b478 100644
--- 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.
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index cc806d52c..3ae39d18c 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -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
diff --git a/src/copy.c b/src/copy.c
index f66ab4646..bcae12368 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -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)
diff --git a/src/cp.c b/src/cp.c
index 7bc8630fb..78c0a0402 100644
--- 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
index 000000000..91a6c5de3
--- /dev/null
+++ b/tests/cp/link-deref.sh
@@ -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
diff --git a/tests/cp/same-file.sh b/tests/cp/same-file.sh
index 003a62ba7..326f9c6bd 100755
--- a/tests/cp/same-file.sh
+++ b/tests/cp/same-file.sh
@@ -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)
diff --git a/tests/local.mk b/tests/local.mk
index e18deacc7..3c924255f 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -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 \