1
0
mirror of git://git.sv.gnu.org/coreutils.git synced 2026-02-15 03:41:57 +02:00

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>
This commit is contained in:
Gian Piero Carrubba
2013-11-07 23:35:52 +01:00
committed by Bernhard Voelker
parent 2c6736f92f
commit bf6bf52dce
7 changed files with 157 additions and 20 deletions

5
NEWS
View File

@@ -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.

View 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

View 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)

View File

@@ -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

110
tests/cp/link-deref.sh Executable file
View File

@@ -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

View File

@@ -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)

View 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 \