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:
committed by
Bernhard Voelker
parent
2c6736f92f
commit
bf6bf52dce
5
NEWS
5
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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
46
src/copy.c
46
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)
|
||||
|
||||
2
src/cp.c
2
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
|
||||
|
||||
110
tests/cp/link-deref.sh
Executable file
110
tests/cp/link-deref.sh
Executable 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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 \
|
||||
|
||||
Reference in New Issue
Block a user