Subject: bug#6960: mv refuses to move a symlink over a hard link to the same file

From: Jim Meyering <[email-masked]>
Date: Mon, 30 Jan 2012 20:42:31 +0100

Paul Eggert wrote:

> On 01/30/2012 03:41 AM, Jim Meyering wrote:
>>   /* At this point, it is normally an error (data loss) to move a symlink
>>      onto its referent, but in at least one narrow case, it is not:
>>      In move mode, when
>>      1) src is a symlink,
>>      2) dest is not a symlink,
>>      3) dest has a link count of 2 or more and
>>      4) dest and the referent of src are not the same entry,
>>      then it's ok, since while we'll lose one of those hard links,
>>      src will still point to a remaining link.
>>      Note that technically, condition #4 obviates condition #2, but we
>>      retain the 1 < st_nlink condition because that means fewer invocations
>>      of the more expensive #4.
>
> The last 3 lines are confusing.
> Don't you mean "condition #3" and not "condition #2"?

Good catch.  You're right.

> The last 2 lines talk about condition 3, not condition 2.
>
> Come to think of it, why is condition 2 needed at all?

Another good catch.  It's not needed.

> Can't we eliminate it, both in the commentary and in the code?
> If a symlink has a link count greater than 1, then overwriting
> it won't lose the link; in that sense it's just like a regular
> file with a link count greater than 1.  So why are symlinks
> special there?

Thanks for the review!

Adjusted via this:

diff --git a/src/copy.c b/src/copy.c
index 2c7582f..d66b0fc 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -1354,14 +1354,13 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
      onto its referent, but in at least one narrow case, it is not:
      In move mode, when
      1) src is a symlink,
-     2) dest is not a symlink,
-     3) dest has a link count of 2 or more and
-     4) dest and the referent of src are not the same entry,
+     2) dest has a link count of 2 or more and
+     3) dest and the referent of src are not the same directory entry,
      then it's ok, since while we'll lose one of those hard links,
      src will still point to a remaining link.
-     Note that technically, condition #4 obviates condition #2, but we
+     Note that technically, condition #3 obviates condition #2, but we
      retain the 1 < st_nlink condition because that means fewer invocations
-     of the more expensive #4.
+     of the more expensive #3.

      Given this,
        $ touch f && ln f l && ln -s f s
@@ -1373,7 +1372,6 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
      this must succeed: mv s l */
   if (x->move_mode
       && S_ISLNK (src_sb->st_mode)
-      && ! S_ISLNK (dst_sb->st_mode)
       && 1 < dst_sb_link->st_nlink)
     {
       char *abs_src = canonicalize_file_name (src_name);