mv: "mv A B" would sometimes succeed, yet A would remain, ...

But only when both A and B were hard links to the same symlink.
* src/copy.c (same_file_ok): Handle another special case: the one
in which we are moving a symlink onto a hard link to itself.
In this case, we must explicitly tell the caller to unlink the
source file.  Otherwise, at least the linux-3.x kernel rename
function would do nothing, as mandated by POSIX 2008.
* tests/mv/symlink-onto-hardlink-to-self: New test.
* tests/Makefile.am (TESTS): Add it.
* NEWS (Bug fixes): Mention it.
Reported by Bernhard Voelker in http://bugs.gnu.org/10686
This commit is contained in:
Jim Meyering 2012-02-01 21:42:45 +01:00
parent 31eeead938
commit b01355a1dc
4 changed files with 89 additions and 3 deletions

6
NEWS
View File

@ -11,6 +11,12 @@ GNU coreutils NEWS -*- outline -*-
referent, there is no risk of data loss, since the symlink will
typically still point to one of the hard links.
"mv A B" could succeed, yet A would remain. This would happen only when
both A and B were hard links to the same symlink, and with a kernel for
which rename("A","B") does nothing and returns 0 (POSIX mandates this
surprising rename no-op behavior). Now, mv handles this case by skipping
the usually-useless rename and simply unlinking A.
* Noteworthy changes in release 8.15 (2012-01-06) [stable]

View File

@ -1226,10 +1226,33 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
same_link = same;
/* If both the source and destination files are symlinks (and we'll
know this here IFF preserving symlinks), then it's ok -- as long
as they are distinct. */
know this here IFF preserving symlinks), then it's usually ok
when they are distinct. */
if (S_ISLNK (src_sb->st_mode) && S_ISLNK (dst_sb->st_mode))
return ! same_name (src_name, dst_name);
{
bool sn = same_name (src_name, dst_name);
if ( ! sn)
{
/* It's fine when we're making any type of backup. */
if (x->backup_type != no_backups)
return true;
/* Here we have two symlinks that are hard-linked together,
and we're not making backups. In this unusual case, simply
returning true would lead to mv calling "rename(A,B)",
which would do nothing and return 0. I.e., A would
not be removed. Hence, the solution is to tell the
caller that all it must do is unlink A and return. */
if (same_link)
{
*unlink_src = true;
*return_now = true;
return true;
}
}
return ! sn;
}
src_sb_link = src_sb;
dst_sb_link = dst_sb;

View File

@ -492,6 +492,7 @@ TESTS = \
mv/partition-perm \
mv/perm-1 \
mv/symlink-onto-hardlink \
mv/symlink-onto-hardlink-to-self \
mv/to-symlink \
mv/trailing-slash \
mv/update \

View File

@ -0,0 +1,56 @@
#!/bin/sh
# Demonstrate that when moving a symlink onto a hardlink-to-that-symlink, the
# source symlink is removed. Depending on your kernel (e.g., Linux, Solaris,
# but not NetBSD), prior to coreutils-8.16, the mv would successfully perform
# a no-op. I.e., surprisingly, mv s1 s2 would succeed, yet fail to remove s1.
# Copyright (C) 2012 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=.}/init.sh"; path_prepend_ ../src
print_ver_ mv
# Create a file f, and a symlink s1 to that file.
touch f || framework_failure_
ln -s f s2 || framework_failure_
for opt in '' --backup; do
# Attempt to create a hard link to that symlink.
# On some systems, it's not possible: they create a hard link to the referent.
ln s2 s1 || framework_failure_
# If s1 is not a symlink, skip this test.
test -h s1 \
|| skip_ your kernel or file system cannot create a hard link to a symlink
mv $opt s1 s2 > out 2>&1 || fail=1
compare /dev/null out || fail=1
# Ensure that s1 is gone.
test -e s1 && fail=1
if test "$opt" = --backup; then
# With --backup, ensure that the backup file was created.
ref=$(readlink s2~) || fail=1
test "$ref" = f || fail=1
else
# Without --backup, ensure there is no backup file.
test -e s2~ && fail=1
fi
done
Exit $fail