bug#10686: mv: moving hardlink of a softlink to the softlink does nothing

2012-02-11 Thread Jim Meyering
Jim Meyering wrote:
> The above wasn't quite right in that it failed to honor mv's --backup
> option.  mv --backup s f would not have created the required backup file.
> I've adjusted it to fix that, and added tests to cover both cases.
> This is still not quite ready (i.e., the FIXME comment, where I'll add
> some explanation), but otherwise should be fine.
>
> Date: Wed, 1 Feb 2012 21:42:45 +0100
> Subject: [PATCH] mv: "mv A B" would sometimes succeed, yet A would remain,
>  ...

I address the FIXME and tweaked the comment: (A,B) seemed a little
clearer than (s1,s2).  I've also tightened up the test, regarding
--backup and existence of backup files.


>From de817cfa6e780323b3eac1f92ac81e8c7871d088 Mon Sep 17 00:00:00 2001
From: Jim Meyering 
Date: Wed, 1 Feb 2012 21:42:45 +0100
Subject: [PATCH] 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
---
 NEWS   |5 +++
 src/copy.c |   29 +++--
 tests/Makefile.am  |1 +
 tests/mv/symlink-onto-hardlink-to-self |   56 
 4 files changed, 88 insertions(+), 3 deletions(-)
 create mode 100755 tests/mv/symlink-onto-hardlink-to-self

diff --git a/NEWS b/NEWS
index 9eebbf6..ca8c0b5 100644
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,11 @@ 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") would do nothing and return 0.  Now, in this
+  unusual case, mv does not call rename, and instead simply unlinks A.
+

 * Noteworthy changes in release 8.15 (2012-01-06) [stable]

diff --git a/src/copy.c b/src/copy.c
index 4810de8..541ca20 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -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;
diff --git a/tests/Makefile.am b/tests/Makefile.am
index a94aaa2..c951f69 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -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\
diff --git a/tests/mv/symlink-onto-hardlink-to-self 
b/tests/mv/symlink-onto-hardlink-to-self
new file mode 100755
index 000..efaff66
--- /dev/null
+++ b/tests/mv/symlink-onto-hardlink-to-self
@@ -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., with the linux
+# kernel), prior to core

bug#10686: mv: moving hardlink of a softlink to the softlink does nothing

2012-02-11 Thread Eric Blake
On 02/11/2012 04:23 AM, Jim Meyering wrote:
> +++ b/NEWS
> @@ -11,6 +11,11 @@ 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") would do nothing and return 0.  Now, in this
> +  unusual case, mv does not call rename, and instead simply unlinks A.

You make it sound like a kernel where rename("A","B") returns 0 is
unusual; on the contrary, that is normal, since it is the POSIX-mandated
behavior for kernels.  What is unusual is having two hardlinks to the
same symlink.  Maybe we should reword this slightly, to attach the
"unusual" modifier to the correct phrase, or even take "kernel" out of
the description:

"mv A B" could succeed, yet A would remain.  This would only happen
in the unusual case when both A and B were hard links to the same
symlink, due to the standard behavior of rename.  Now, mv recognizes
the case and simply unlinks A.

> +++ b/tests/mv/symlink-onto-hardlink-to-self
> @@ -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., with the linux
> +# kernel), prior to coreutils-8.16, the mv would successfully perform a 
> no-op.

Again, this is the POSIX-required behavior of ALL kernels, and not
something special to Linux.

-- 
Eric Blake   ebl...@redhat.com+1-919-301-3266
Libvirt virtualization library http://libvirt.org



signature.asc
Description: OpenPGP digital signature