GNU bug report logs - #10686
mv: moving hardlink of a softlink to the softlink does nothing

Previous Next

Package: coreutils;

Reported by: Bernhard Voelker <mail <at> bernhard-voelker.de>

Date: Wed, 1 Feb 2012 08:23:02 UTC

Severity: normal

Tags: fixed

Done: Assaf Gordon <assafgordon <at> gmail.com>

Bug is archived. No further changes may be made.

To add a comment to this bug, you must first unarchive it, by sending
a message to control AT debbugs.gnu.org, with unarchive 10686 in the body.
You can then email your comments to 10686 AT debbugs.gnu.org in the normal way.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to bug-coreutils <at> gnu.org:
bug#10686; Package coreutils. (Wed, 01 Feb 2012 08:23:02 GMT) Full text and rfc822 format available.

Acknowledgement sent to Bernhard Voelker <mail <at> bernhard-voelker.de>:
New bug report received and forwarded. Copy sent to bug-coreutils <at> gnu.org. (Wed, 01 Feb 2012 08:23:02 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Bernhard Voelker <mail <at> bernhard-voelker.de>
To: bug-coreutils <at> gnu.org
Subject: mv: moving hardlink of a softlink to the softlink does nothing
Date: Wed, 01 Feb 2012 09:21:09 +0100
Playing around with the latest mv checkin,
I noticed another corner case:

Create a file 'f', a symlink 'l' to it, and
then a hardlink 's' to that symlink:

  $ touch f && ln -s f l && ln l s && ls -ogi
  total 0
  6444 -rw-r--r-- 1 0 Feb  1 08:52 f
  6462 lrwxrwxrwx 2 1 Feb  1 08:52 l -> f
  6462 lrwxrwxrwx 2 1 Feb  1 08:52 s -> f

Trying to mv the hardlink over the symlink seems to succeed:

  $ ~/git/coreutils/src/mv s l

... but the name 's' was not unlinked:

  $ ls -ogi
  total 0
  6444 -rw-r--r-- 1 0 Feb  1 08:52 f
  6462 lrwxrwxrwx 2 1 Feb  1 08:52 l -> f
  6462 lrwxrwxrwx 2 1 Feb  1 08:52 s -> f

Using the -v option looks also normal, but is a nop:

  $ ~/git/coreutils/src/mv -v s l
  ‘s’ -> ‘l’
  $ ls -ogi
  total 0
  6444 -rw-r--r-- 1 0 Feb  1 08:52 f
  6462 lrwxrwxrwx 2 1 Feb  1 08:52 l -> f
  6462 lrwxrwxrwx 2 1 Feb  1 08:52 s -> f

The strace only shows the rename():

  stat("l", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
  lstat("s", {st_mode=S_IFLNK|0777, st_size=1, ...}) = 0
  lstat("l", {st_mode=S_IFLNK|0777, st_size=1, ...}) = 0
  rename("s", "l")                        = 0


I'd have expected either
a) the name 's' to be unlinked, or
b) an error message that something was wrong (well, whatever).
I'd prefer a) of course.

That behaviour didn't change at least since version 7.1
(on openSuSE-11.3), but back in the earlier days in 5.93
(SLES-10.3), 's' disappeared as expected:

  stat("l", {st_mode=S_IFREG|0640, st_size=0, ...}) = 0
  lstat("s", {st_mode=S_IFLNK|0777, st_size=1, ...}) = 0
  lstat("l", {st_mode=S_IFLNK|0777, st_size=1, ...}) = 0
  unlink("l")                             = 0
  rename("s", "l")                        = 0

Does mv now work as specified? Is this a kernel or
a coreutils (or a user) issue?

Have a nice day,
Berny





Information forwarded to bug-coreutils <at> gnu.org:
bug#10686; Package coreutils. (Wed, 01 Feb 2012 11:44:02 GMT) Full text and rfc822 format available.

Message #8 received at 10686 <at> debbugs.gnu.org (full text, mbox):

From: Bernhard Voelker <mail <at> bernhard-voelker.de>
To: 10686 <at> debbugs.gnu.org
Subject: Re: mv: moving hardlink of a softlink to the softlink does nothing
Date: Wed, 01 Feb 2012 12:43:25 +0100
On 02/01/2012 09:21 AM, Bernhard Voelker wrote:

>   $ touch f && ln -s f l && ln l s && ls -ogi
>   total 0
>   6444 -rw-r--r-- 1 0 Feb  1 08:52 f
>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 l -> f
>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 s -> f
> 
> Trying to mv the hardlink over the symlink seems to succeed:
> 
>   $ ~/git/coreutils/src/mv s l
> 
> ... but the name 's' was not unlinked:

...

> That behaviour didn't change at least since version 7.1
> (on openSuSE-11.3), but back in the earlier days in 5.93
> (SLES-10.3), 's' disappeared as expected:

Hmm, looks like commit
   f1d1ee91217d08527c6bf7682987339e38116268
introduced this in 2006.

Have a nice day,
Berny




Information forwarded to bug-coreutils <at> gnu.org:
bug#10686; Package coreutils. (Wed, 01 Feb 2012 12:49:02 GMT) Full text and rfc822 format available.

Message #11 received at 10686 <at> debbugs.gnu.org (full text, mbox):

From: Jim Meyering <jim <at> meyering.net>
To: Bernhard Voelker <mail <at> bernhard-voelker.de>
Cc: 10686 <at> debbugs.gnu.org
Subject: Re: bug#10686: mv: moving hardlink of a softlink to the softlink does
	nothing
Date: Wed, 01 Feb 2012 13:47:44 +0100
Bernhard Voelker wrote:
> Playing around with the latest mv checkin,
> I noticed another corner case:
>
> Create a file 'f', a symlink 'l' to it, and
> then a hardlink 's' to that symlink:
>
>   $ touch f && ln -s f l && ln l s && ls -ogi
>   total 0
>   6444 -rw-r--r-- 1 0 Feb  1 08:52 f
>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 l -> f
>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 s -> f

Hi Bernhard,
Thanks for the report.

Here, you've already gotten into questionable territory, since the
idea of a hard link to a symbolic link is not standardized.
For example, on FreeBSD 9.0, you cannot even create one:
(instead, it hard-links the referent of the symlink)

    freebsd$ touch f && ln -s f l && ln l s && ls -ogi f l s
    1587047 -rw-r--r-- 2 0 Feb  1 04:58 f
    1587100 lrwxr-xr-x 1 1 Feb  1 04:58 l -> f
    1587047 -rw-r--r-- 2 0 Feb  1 04:58 s

> Trying to mv the hardlink over the symlink seems to succeed:
>
>   $ ~/git/coreutils/src/mv s l
>
> ... but the name 's' was not unlinked:

That is definitely undesirable behavior.
For now, one possibility is to make "mv" reject
that corner case with this diagnostic:

    mv: 's' and 'l' are the same file

Proposed patch below.

>   $ ls -ogi
>   total 0
>   6444 -rw-r--r-- 1 0 Feb  1 08:52 f
>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 l -> f
>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 s -> f
>
> Using the -v option looks also normal, but is a nop:
>
>   $ ~/git/coreutils/src/mv -v s l
>   ‘s’ -> ‘l’
>   $ ls -ogi
>   total 0
>   6444 -rw-r--r-- 1 0 Feb  1 08:52 f
>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 l -> f
>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 s -> f
>
> The strace only shows the rename():
>
>   stat("l", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
>   lstat("s", {st_mode=S_IFLNK|0777, st_size=1, ...}) = 0
>   lstat("l", {st_mode=S_IFLNK|0777, st_size=1, ...}) = 0
>   rename("s", "l")                        = 0
>
>
> I'd have expected either
> a) the name 's' to be unlinked, or
> b) an error message that something was wrong (well, whatever).
> I'd prefer a) of course.

So would I.

> That behaviour didn't change at least since version 7.1
> (on openSuSE-11.3), but back in the earlier days in 5.93
> (SLES-10.3), 's' disappeared as expected:
>
>   stat("l", {st_mode=S_IFREG|0640, st_size=0, ...}) = 0
>   lstat("s", {st_mode=S_IFLNK|0777, st_size=1, ...}) = 0
>   lstat("l", {st_mode=S_IFLNK|0777, st_size=1, ...}) = 0
>   unlink("l")                             = 0
>   rename("s", "l")                        = 0
>
> Does mv now work as specified? Is this a kernel or
> a coreutils (or a user) issue?

It's a standards and kernel issue.
POSIX explicitly says (of rename)

    If the old argument and the new argument resolve to the same existing
    file, rename( ) shall return successfully and perform no other action.

though that particular wording might be slated to change with POSIX 2008,
to allow different (more desirable) behavior.  Search the austin-group
archives if you need to be sure.

Also, I don't know whether the above wording applies to this particular
case of two hard links to the same symlink.  Again, I think we're in
unspecified territory.

Here's a proposed patch.
This is such an improbable corner case that I think it's not worth trying
to fix any other way (either by unlinking the destination or by
replacing rename).

(of course, if I go with this, I'll insert a big comment justifying
this tiny change)

diff --git a/src/copy.c b/src/copy.c
index 4810de8..78cd8c8 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -1229,7 +1229,7 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
          know this here IFF preserving symlinks), then it's ok -- as long
          as they are distinct.  */
       if (S_ISLNK (src_sb->st_mode) && S_ISLNK (dst_sb->st_mode))
-        return ! same_name (src_name, dst_name);
+        return ! same_name (src_name, dst_name) && ! same_link;

       src_sb_link = src_sb;
       dst_sb_link = dst_sb;




Information forwarded to bug-coreutils <at> gnu.org:
bug#10686; Package coreutils. (Wed, 01 Feb 2012 13:57:05 GMT) Full text and rfc822 format available.

Message #14 received at 10686 <at> debbugs.gnu.org (full text, mbox):

From: Bernhard Voelker <mail <at> bernhard-voelker.de>
To: Jim Meyering <jim <at> meyering.net>
Cc: 10686 <at> debbugs.gnu.org
Subject: Re: bug#10686: mv: moving hardlink of a softlink to the softlink
	does nothing
Date: Wed, 01 Feb 2012 14:55:36 +0100
On 02/01/2012 01:47 PM, Jim Meyering wrote:
> Bernhard Voelker wrote:
>> Playing around with the latest mv checkin,
>> I noticed another corner case:
>>
>> Create a file 'f', a symlink 'l' to it, and
>> then a hardlink 's' to that symlink:
>>
>>   $ touch f && ln -s f l && ln l s && ls -ogi
>>   total 0
>>   6444 -rw-r--r-- 1 0 Feb  1 08:52 f
>>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 l -> f
>>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 s -> f
> 
> Hi Bernhard,
> Thanks for the report.
> 
> Here, you've already gotten into questionable territory, since the
> idea of a hard link to a symbolic link is not standardized.
> For example, on FreeBSD 9.0, you cannot even create one:
> (instead, it hard-links the referent of the symlink)
> 
>     freebsd$ touch f && ln -s f l && ln l s && ls -ogi f l s
>     1587047 -rw-r--r-- 2 0 Feb  1 04:58 f
>     1587100 lrwxr-xr-x 1 1 Feb  1 04:58 l -> f
>     1587047 -rw-r--r-- 2 0 Feb  1 04:58 s
> 
>> Trying to mv the hardlink over the symlink seems to succeed:
>>
>>   $ ~/git/coreutils/src/mv s l
>>
>> ... but the name 's' was not unlinked:
> 
> That is definitely undesirable behavior.
> For now, one possibility is to make "mv" reject
> that corner case with this diagnostic:
> 
>     mv: 's' and 'l' are the same file
> 
> Proposed patch below.
> 
>>   $ ls -ogi
>>   total 0
>>   6444 -rw-r--r-- 1 0 Feb  1 08:52 f
>>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 l -> f
>>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 s -> f
>>
>> Using the -v option looks also normal, but is a nop:
>>
>>   $ ~/git/coreutils/src/mv -v s l
>>   ‘s’ -> ‘l’
>>   $ ls -ogi
>>   total 0
>>   6444 -rw-r--r-- 1 0 Feb  1 08:52 f
>>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 l -> f
>>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 s -> f
>>
>> The strace only shows the rename():
>>
>>   stat("l", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
>>   lstat("s", {st_mode=S_IFLNK|0777, st_size=1, ...}) = 0
>>   lstat("l", {st_mode=S_IFLNK|0777, st_size=1, ...}) = 0
>>   rename("s", "l")                        = 0
>>
>>
>> I'd have expected either
>> a) the name 's' to be unlinked, or
>> b) an error message that something was wrong (well, whatever).
>> I'd prefer a) of course.
> 
> So would I.
> 
>> That behaviour didn't change at least since version 7.1
>> (on openSuSE-11.3), but back in the earlier days in 5.93
>> (SLES-10.3), 's' disappeared as expected:
>>
>>   stat("l", {st_mode=S_IFREG|0640, st_size=0, ...}) = 0
>>   lstat("s", {st_mode=S_IFLNK|0777, st_size=1, ...}) = 0
>>   lstat("l", {st_mode=S_IFLNK|0777, st_size=1, ...}) = 0
>>   unlink("l")                             = 0
>>   rename("s", "l")                        = 0
>>
>> Does mv now work as specified? Is this a kernel or
>> a coreutils (or a user) issue?
> 
> It's a standards and kernel issue.
> POSIX explicitly says (of rename)
> 
>     If the old argument and the new argument resolve to the same existing
>     file, rename( ) shall return successfully and perform no other action.
> 
> though that particular wording might be slated to change with POSIX 2008,
> to allow different (more desirable) behavior.  Search the austin-group
> archives if you need to be sure.
> 
> Also, I don't know whether the above wording applies to this particular
> case of two hard links to the same symlink.  Again, I think we're in
> unspecified territory.
> 
> Here's a proposed patch.
> This is such an improbable corner case that I think it's not worth trying
> to fix any other way (either by unlinking the destination or by
> replacing rename).
> 
> (of course, if I go with this, I'll insert a big comment justifying
> this tiny change)
> 
> diff --git a/src/copy.c b/src/copy.c
> index 4810de8..78cd8c8 100644
> --- a/src/copy.c
> +++ b/src/copy.c
> @@ -1229,7 +1229,7 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
>           know this here IFF preserving symlinks), then it's ok -- as long
>           as they are distinct.  */
>        if (S_ISLNK (src_sb->st_mode) && S_ISLNK (dst_sb->st_mode))
> -        return ! same_name (src_name, dst_name);
> +        return ! same_name (src_name, dst_name) && ! same_link;
> 
>        src_sb_link = src_sb;
>        dst_sb_link = dst_sb;

Hi Jim,

well, if it's not standardized (yet), then I agree with you that we
should choose the simpler b) alternative.

Do you think it's work thinking about the -f case?

  $ ~/git/coreutils/src/mv -f s l
  /home/berny/git/coreutils/src/mv: ‘s’ and ‘l’ are the same file

The info pages are not quite clear in which cases mv tries to
overwrite the destination. Usually, I am tempted to think that
a "--force" will "just do it" - regardless of data loss.
But in this case, mv "just does not" and the only thing left
is to rm that hardlink.

That makes me think that the implementation of the a) alternative
just reduces to calling unlink() ... ;-)

Have a nice day,
Berny




Information forwarded to bug-coreutils <at> gnu.org:
bug#10686; Package coreutils. (Wed, 01 Feb 2012 14:03:02 GMT) Full text and rfc822 format available.

Message #17 received at 10686 <at> debbugs.gnu.org (full text, mbox):

From: Jim Meyering <jim <at> meyering.net>
To: Bernhard Voelker <mail <at> bernhard-voelker.de>
Cc: 10686 <at> debbugs.gnu.org
Subject: Re: bug#10686: mv: moving hardlink of a softlink to the softlink does
	nothing
Date: Wed, 01 Feb 2012 15:02:27 +0100
Bernhard Voelker wrote:
...
> well, if it's not standardized (yet), then I agree with you that we
> should choose the simpler b) alternative.
>
> Do you think it's work thinking about the -f case?
>
>   $ ~/git/coreutils/src/mv -f s l
>   /home/berny/git/coreutils/src/mv: ‘s’ and ‘l’ are the same file

mv's -f option is unrelated to this case.
It relates solely to controlling whether one is prompted
due to permissions or -i.

From 'info mv's description of the common case (no options):

       If a destination file exists but is normally unwritable, standard
    input is a terminal, and the `-f' or `--force' option is not given,
    `mv' prompts the user for whether to replace the file.  (You might own
    the file, or have write permission on its directory.)  If the response
    is not affirmative, the file is skipped.


Yes, it is unfortunate that the standards-imposed semantics
of -f is different from what we'd expect.

> The info pages are not quite clear in which cases mv tries to
> overwrite the destination. Usually, I am tempted to think that
> a "--force" will "just do it" - regardless of data loss.
> But in this case, mv "just does not" and the only thing left
> is to rm that hardlink.

If the above is still not clear, please suggest how to improve it.

> That makes me think that the implementation of the a) alternative
> just reduces to calling unlink() ... ;-)




Information forwarded to bug-coreutils <at> gnu.org:
bug#10686; Package coreutils. (Wed, 01 Feb 2012 14:28:01 GMT) Full text and rfc822 format available.

Message #20 received at 10686 <at> debbugs.gnu.org (full text, mbox):

From: Bernhard Voelker <mail <at> bernhard-voelker.de>
To: Jim Meyering <jim <at> meyering.net>
Cc: 10686 <at> debbugs.gnu.org
Subject: Re: bug#10686: mv: moving hardlink of a softlink to the softlink
	does nothing
Date: Wed, 01 Feb 2012 15:27:09 +0100

On 02/01/2012 03:02 PM, Jim Meyering wrote:
> Bernhard Voelker wrote:
> ...
>> well, if it's not standardized (yet), then I agree with you that we
>> should choose the simpler b) alternative.
>>
>> Do you think it's work thinking about the -f case?
>>
>>   $ ~/git/coreutils/src/mv -f s l
>>   /home/berny/git/coreutils/src/mv: ‘s’ and ‘l’ are the same file
> 
> mv's -f option is unrelated to this case.
> It relates solely to controlling whether one is prompted
> due to permissions or -i.
> 
> From 'info mv's description of the common case (no options):
> 
>        If a destination file exists but is normally unwritable, standard
>     input is a terminal, and the `-f' or `--force' option is not given,
>     `mv' prompts the user for whether to replace the file.  (You might own
>     the file, or have write permission on its directory.)  If the response
>     is not affirmative, the file is skipped.
> 
> 
> Yes, it is unfortunate that the standards-imposed semantics
> of -f is different from what we'd expect.
> 

> If the above is still not clear, please suggest how to improve it.

Well, if permissions are the only case when mv would require
confirmation or -f, then it should be added to mv's description.

Additionally, the info page might also have note in which cases
mv will refuse to work, e.g. when "‘s’ and ‘l’ are the same file".

As I'm not sure (anymore) about both, I cannot propose a wording.


>> That makes me think that the implementation of the a) alternative
>> just reduces to calling unlink() ... ;-)

This hardlink-to-softlink case is IMHO identical to the normal
hardlinks case (in which mv just unlinks the source):

  $ touch f1 && ln f1 f2 && ls -ogi f1 f2
  6590 -rw-r--r-- 2 0 Feb  1 15:17 f1
  6590 -rw-r--r-- 2 0 Feb  1 15:17 f2
  $ ~/git/coreutils/src/mv f1 f2
  $ ls -ogi f1 f2
  ls: cannot access f1: No such file or directory
  6590 -rw-r--r-- 1 0 Feb  1 15:17 f2

Therefore, I'd tend to a) again. It doesn't matter if src is
a regular file or a symlink. What do you think?

Have a nice day,
Berny




Information forwarded to bug-coreutils <at> gnu.org:
bug#10686; Package coreutils. (Wed, 01 Feb 2012 15:02:02 GMT) Full text and rfc822 format available.

Message #23 received at 10686 <at> debbugs.gnu.org (full text, mbox):

From: Eric Blake <eblake <at> redhat.com>
To: Jim Meyering <jim <at> meyering.net>
Cc: Bernhard Voelker <mail <at> bernhard-voelker.de>, 10686 <at> debbugs.gnu.org
Subject: Re: bug#10686: mv: moving hardlink of a softlink to the softlink
	does nothing
Date: Wed, 01 Feb 2012 08:01:05 -0700
[Message part 1 (text/plain, inline)]
On 02/01/2012 05:47 AM, Jim Meyering wrote:
> Bernhard Voelker wrote:
>> Playing around with the latest mv checkin,
>> I noticed another corner case:
>>
>> Create a file 'f', a symlink 'l' to it, and
>> then a hardlink 's' to that symlink:
>>
>>   $ touch f && ln -s f l && ln l s && ls -ogi
>>   total 0
>>   6444 -rw-r--r-- 1 0 Feb  1 08:52 f
>>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 l -> f
>>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 s -> f
> 
> Hi Bernhard,
> Thanks for the report.
> 
> Here, you've already gotten into questionable territory, since the
> idea of a hard link to a symbolic link is not standardized.

Actually, POSIX 2008 _did_ standardize the notion of a hard link to a
symlink, thanks to linkat().

> For example, on FreeBSD 9.0, you cannot even create one:

That's a POSIX-compliance bug in FreeBSD.  In that case, the best we can
do is emulate it by creating a new symlink with identical contents and
owner and as much of the timestamp as lutimens will allow.

> 
> It's a standards and kernel issue.
> POSIX explicitly says (of rename)
> 
>     If the old argument and the new argument resolve to the same existing
>     file, rename( ) shall return successfully and perform no other action.
> 
> though that particular wording might be slated to change with POSIX 2008,
> to allow different (more desirable) behavior.  Search the austin-group
> archives if you need to be sure.

The wording for rename(2) did not change, but the wording for mv(1)
_did_ change to allow slightly more desirable behavior.  Here's the
latest wording, as modified by the latest bug report:

http://austingroupbugs.net/view.php?id=534

If the source_file operand and destination path resolve to either the
same existing directory entry or different directory entries for the
same existing file, then the destination path shall not be removed, and
one of the following shall occur:
 a. No change is made to source_file, no error occurs, and no diagnostic
is issued.
 b. No change is made to source_file, a diagnostic is issued to standard
error identifying the two names, and the exit status is affected.
 c. If the source_file operand and destination path name distinct
directory entries, then the source_file operand is removed, no error
occurs, and no diagnostic is issued.

The mv utility shall do nothing more with the current source_file, and
go on to any remaining source_files.

> Also, I don't know whether the above wording applies to this particular
> case of two hard links to the same symlink.  Again, I think we're in
> unspecified territory.

POSIX actually specifies this quite well - two hardlinks to the same
symlink qualify as an instance of two file names that resolve to the
same inode after path resolution as checked with lstat().

-- 
Eric Blake   eblake <at> redhat.com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org

[signature.asc (application/pgp-signature, attachment)]

Information forwarded to bug-coreutils <at> gnu.org:
bug#10686; Package coreutils. (Wed, 01 Feb 2012 15:46:02 GMT) Full text and rfc822 format available.

Message #26 received at 10686 <at> debbugs.gnu.org (full text, mbox):

From: Jim Meyering <jim <at> meyering.net>
To: Eric Blake <eblake <at> redhat.com>
Cc: Bernhard Voelker <mail <at> bernhard-voelker.de>, 10686 <at> debbugs.gnu.org
Subject: Re: bug#10686: mv: moving hardlink of a softlink to the softlink does
	nothing
Date: Wed, 01 Feb 2012 16:45:06 +0100
Eric Blake wrote:

> On 02/01/2012 05:47 AM, Jim Meyering wrote:
>> Bernhard Voelker wrote:
>>> Playing around with the latest mv checkin,
>>> I noticed another corner case:
>>>
>>> Create a file 'f', a symlink 'l' to it, and
>>> then a hardlink 's' to that symlink:
>>>
>>>   $ touch f && ln -s f l && ln l s && ls -ogi
>>>   total 0
>>>   6444 -rw-r--r-- 1 0 Feb  1 08:52 f
>>>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 l -> f
>>>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 s -> f
>>
>> Hi Bernhard,
>> Thanks for the report.
>>
>> Here, you've already gotten into questionable territory, since the
>> idea of a hard link to a symbolic link is not standardized.
>
> Actually, POSIX 2008 _did_ standardize the notion of a hard link to a
> symlink, thanks to linkat().
>
>> For example, on FreeBSD 9.0, you cannot even create one:
>
> That's a POSIX-compliance bug in FreeBSD.  In that case, the best we can
> do is emulate it by creating a new symlink with identical contents and
> owner and as much of the timestamp as lutimens will allow.
>
>>
>> It's a standards and kernel issue.
>> POSIX explicitly says (of rename)
>>
>>     If the old argument and the new argument resolve to the same existing
>>     file, rename( ) shall return successfully and perform no other action.
>>
>> though that particular wording might be slated to change with POSIX 2008,
>> to allow different (more desirable) behavior.  Search the austin-group
>> archives if you need to be sure.
>
> The wording for rename(2) did not change, but the wording for mv(1)
> _did_ change to allow slightly more desirable behavior.  Here's the
> latest wording, as modified by the latest bug report:
>
> http://austingroupbugs.net/view.php?id=534
>
> If the source_file operand and destination path resolve to either the
> same existing directory entry or different directory entries for the
> same existing file, then the destination path shall not be removed, and
> one of the following shall occur:
>  a. No change is made to source_file, no error occurs, and no diagnostic
> is issued.
>  b. No change is made to source_file, a diagnostic is issued to standard
> error identifying the two names, and the exit status is affected.
>  c. If the source_file operand and destination path name distinct
> directory entries, then the source_file operand is removed, no error
> occurs, and no diagnostic is issued.
>
> The mv utility shall do nothing more with the current source_file, and
> go on to any remaining source_files.
>
>> Also, I don't know whether the above wording applies to this particular
>> case of two hard links to the same symlink.  Again, I think we're in
>> unspecified territory.
>
> POSIX actually specifies this quite well - two hardlinks to the same
> symlink qualify as an instance of two file names that resolve to the
> same inode after path resolution as checked with lstat().

Thanks for the clarification and quotes, Eric.

Bernhard, here's a better patch.
With it, mv simply unlinks the "s" in your example:

diff --git a/src/copy.c b/src/copy.c
index 4810de8..73f5cc5 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -1229,7 +1229,17 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
          know this here IFF preserving symlinks), then it's ok -- as long
          as 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 && same_link)
+            {
+              *unlink_src = true;
+              *return_now = true;
+              return true;
+            }
+
+          return ! sn;
+        }

       src_sb_link = src_sb;
       dst_sb_link = dst_sb;




Information forwarded to bug-coreutils <at> gnu.org:
bug#10686; Package coreutils. (Wed, 01 Feb 2012 16:12:01 GMT) Full text and rfc822 format available.

Message #29 received at 10686 <at> debbugs.gnu.org (full text, mbox):

From: Bernhard Voelker <mail <at> bernhard-voelker.de>
To: Jim Meyering <jim <at> meyering.net>
Cc: Eric Blake <eblake <at> redhat.com>, 10686 <at> debbugs.gnu.org
Subject: Re: bug#10686: mv: moving hardlink of a softlink to the softlink
	does nothing
Date: Wed, 01 Feb 2012 17:10:41 +0100
On 02/01/2012 04:45 PM, Jim Meyering wrote:
> Thanks for the clarification and quotes, Eric.
> 
> Bernhard, here's a better patch.
> With it, mv simply unlinks the "s" in your example:
> 
> diff --git a/src/copy.c b/src/copy.c
> index 4810de8..73f5cc5 100644
> --- a/src/copy.c
> +++ b/src/copy.c
> @@ -1229,7 +1229,17 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
>           know this here IFF preserving symlinks), then it's ok -- as long
>           as 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 && same_link)
> +            {
> +              *unlink_src = true;
> +              *return_now = true;
> +              return true;
> +            }
> +
> +          return ! sn;
> +        }
> 
>        src_sb_link = src_sb;
>        dst_sb_link = dst_sb;

Thank you both.
The patch works.

I wonder if just removing the x->hard_link constraint at the beginning
of same_file_ok() would alsoo do (although the comment sounds like this
is no good idea):

@@ -1213,11 +1213,11 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
   /* FIXME: this should (at the very least) be moved into the following
      if-block.  More likely, it should be removed, because it inhibits
      making backups.  But removing it will result in a change in behavior
      that will probably have to be documented -- and tests will have to
      be updated.  */
-  if (same && x->hard_link)
+  if (same)
     {
       *return_now = true;
       return true;
     }

Please don't think I just want to give you more work,
but I think this deserves a new test, doesn't it?

Have a nice day,
Berny






Information forwarded to bug-coreutils <at> gnu.org:
bug#10686; Package coreutils. (Wed, 01 Feb 2012 16:20:02 GMT) Full text and rfc822 format available.

Message #32 received at 10686 <at> debbugs.gnu.org (full text, mbox):

From: Jim Meyering <jim <at> meyering.net>
To: Bernhard Voelker <mail <at> bernhard-voelker.de>
Cc: Eric Blake <eblake <at> redhat.com>, 10686 <at> debbugs.gnu.org
Subject: Re: bug#10686: mv: moving hardlink of a softlink to the softlink does
	nothing
Date: Wed, 01 Feb 2012 17:19:26 +0100
Bernhard Voelker wrote:

> On 02/01/2012 04:45 PM, Jim Meyering wrote:
>> Thanks for the clarification and quotes, Eric.
>>
>> Bernhard, here's a better patch.
>> With it, mv simply unlinks the "s" in your example:
>>
>> diff --git a/src/copy.c b/src/copy.c
>> index 4810de8..73f5cc5 100644
>> --- a/src/copy.c
>> +++ b/src/copy.c
>> @@ -1229,7 +1229,17 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
>>           know this here IFF preserving symlinks), then it's ok -- as long
>>           as 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 && same_link)
>> +            {
>> +              *unlink_src = true;
>> +              *return_now = true;
>> +              return true;
>> +            }
>> +
>> +          return ! sn;
>> +        }
>>
>>        src_sb_link = src_sb;
>>        dst_sb_link = dst_sb;
>
> Thank you both.
> The patch works.
>
> I wonder if just removing the x->hard_link constraint at the beginning
> of same_file_ok() would alsoo do (although the comment sounds like this
> is no good idea):
>
> @@ -1213,11 +1213,11 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
>    /* FIXME: this should (at the very least) be moved into the following
>       if-block.  More likely, it should be removed, because it inhibits
>       making backups.  But removing it will result in a change in behavior
>       that will probably have to be documented -- and tests will have to
>       be updated.  */
> -  if (same && x->hard_link)
> +  if (same)

I doubt that would work (it doesn't set *unlink_src) -- though you're
welcome to try it.  Even if it did, I'd prefer not to put a change like
this in code that might be considered for removal.

>        *return_now = true;
>        return true;
>      }
>
> Please don't think I just want to give you more work,
> but I think this deserves a new test, doesn't it?

Of course ;-)  That was not a complete patch by a long shot.
It didn't even have a commit log.  You obviously know by now that
any change like this absolutely requires an addition to the tests suite.
And since (thanks to Eric's clarification) it does officially count
as a bug fix, I'll write a NEWS entry, too.

Thanks again for spotting the problem.




Information forwarded to bug-coreutils <at> gnu.org:
bug#10686; Package coreutils. (Sat, 04 Feb 2012 15:50:02 GMT) Full text and rfc822 format available.

Message #35 received at 10686 <at> debbugs.gnu.org (full text, mbox):

From: Jim Meyering <jim <at> meyering.net>
To: Eric Blake <eblake <at> redhat.com>
Cc: Bernhard Voelker <mail <at> bernhard-voelker.de>, 10686 <at> debbugs.gnu.org
Subject: Re: bug#10686: mv: moving hardlink of a softlink to the softlink does
	nothing
Date: Sat, 04 Feb 2012 16:49:02 +0100
Jim Meyering wrote:

> Eric Blake wrote:
>
>> On 02/01/2012 05:47 AM, Jim Meyering wrote:
>>> Bernhard Voelker wrote:
>>>> Playing around with the latest mv checkin,
>>>> I noticed another corner case:
>>>>
>>>> Create a file 'f', a symlink 'l' to it, and
>>>> then a hardlink 's' to that symlink:
>>>>
>>>>   $ touch f && ln -s f l && ln l s && ls -ogi
>>>>   total 0
>>>>   6444 -rw-r--r-- 1 0 Feb  1 08:52 f
>>>>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 l -> f
>>>>   6462 lrwxrwxrwx 2 1 Feb  1 08:52 s -> f
>>>
>>> Hi Bernhard,
>>> Thanks for the report.
>>>
>>> Here, you've already gotten into questionable territory, since the
>>> idea of a hard link to a symbolic link is not standardized.
>>
>> Actually, POSIX 2008 _did_ standardize the notion of a hard link to a
>> symlink, thanks to linkat().
>>
>>> For example, on FreeBSD 9.0, you cannot even create one:
>>
>> That's a POSIX-compliance bug in FreeBSD.  In that case, the best we can
>> do is emulate it by creating a new symlink with identical contents and
>> owner and as much of the timestamp as lutimens will allow.
>>
>>>
>>> It's a standards and kernel issue.
>>> POSIX explicitly says (of rename)
>>>
>>>     If the old argument and the new argument resolve to the same existing
>>>     file, rename( ) shall return successfully and perform no other action.
>>>
>>> though that particular wording might be slated to change with POSIX 2008,
>>> to allow different (more desirable) behavior.  Search the austin-group
>>> archives if you need to be sure.
>>
>> The wording for rename(2) did not change, but the wording for mv(1)
>> _did_ change to allow slightly more desirable behavior.  Here's the
>> latest wording, as modified by the latest bug report:
>>
>> http://austingroupbugs.net/view.php?id=534
>>
>> If the source_file operand and destination path resolve to either the
>> same existing directory entry or different directory entries for the
>> same existing file, then the destination path shall not be removed, and
>> one of the following shall occur:
>>  a. No change is made to source_file, no error occurs, and no diagnostic
>> is issued.
>>  b. No change is made to source_file, a diagnostic is issued to standard
>> error identifying the two names, and the exit status is affected.
>>  c. If the source_file operand and destination path name distinct
>> directory entries, then the source_file operand is removed, no error
>> occurs, and no diagnostic is issued.
>>
>> The mv utility shall do nothing more with the current source_file, and
>> go on to any remaining source_files.
>>
>>> Also, I don't know whether the above wording applies to this particular
>>> case of two hard links to the same symlink.  Again, I think we're in
>>> unspecified territory.
>>
>> POSIX actually specifies this quite well - two hardlinks to the same
>> symlink qualify as an instance of two file names that resolve to the
>> same inode after path resolution as checked with lstat().
>
> Thanks for the clarification and quotes, Eric.
>
> Bernhard, here's a better patch.
> With it, mv simply unlinks the "s" in your example:
>
> diff --git a/src/copy.c b/src/copy.c
> index 4810de8..73f5cc5 100644
> --- a/src/copy.c
> +++ b/src/copy.c
> @@ -1229,7 +1229,17 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
>           know this here IFF preserving symlinks), then it's ok -- as long
>           as 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 && same_link)
> +            {
> +              *unlink_src = true;
> +              *return_now = true;
> +              return true;
> +            }
> +
> +          return ! sn;
> +        }

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.


From 646456b8ce053b85d30174462620492df648e0e2 Mon Sep 17 00:00:00 2001
From: Jim Meyering <meyering <at> redhat.com>
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                             |   26 ++++++++++++++--
 tests/Makefile.am                      |    1 +
 tests/mv/symlink-onto-hardlink-to-self |   53 ++++++++++++++++++++++++++++++++
 4 files changed, 82 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..0764b8e 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -1226,10 +1226,30 @@ 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;
+
+              /* This is an unusual case.
+                 FIXME: explain
+               */
+              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 0000000..c8adc0b
--- /dev/null
+++ b/tests/mv/symlink-onto-hardlink-to-self
@@ -0,0 +1,53 @@
+#!/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.
+# 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
+
+  # With --backup, ensure that the backup file was created.
+  if test "$opt" = --backup; then
+    ref=$(readlink s2~) || fail=1
+    test "$ref" = f || fail=1
+  fi
+
+done
+
+Exit $fail
--
1.7.9.112.gb85f2




Information forwarded to bug-coreutils <at> gnu.org:
bug#10686; Package coreutils. (Mon, 06 Feb 2012 14:40:01 GMT) Full text and rfc822 format available.

Message #38 received at 10686 <at> debbugs.gnu.org (full text, mbox):

From: Bernhard Voelker <mail <at> bernhard-voelker.de>
To: Jim Meyering <jim <at> meyering.net>
Cc: Eric Blake <eblake <at> redhat.com>, 10686 <at> debbugs.gnu.org
Subject: Re: bug#10686: mv: moving hardlink of a softlink to the softlink
	does nothing
Date: Mon, 06 Feb 2012 15:38:46 +0100
On 02/04/2012 04:49 PM, 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.

Thanks, looks fine.

Have a nice day,
Berny




Information forwarded to bug-coreutils <at> gnu.org:
bug#10686; Package coreutils. (Tue, 07 Feb 2012 12:28:01 GMT) Full text and rfc822 format available.

Message #41 received at 10686 <at> debbugs.gnu.org (full text, mbox):

From: Bernhard Voelker <mail <at> bernhard-voelker.de>
To: Michael Felt <mamfelt <at> gmail.com>
Cc: 10686 <at> debbugs.gnu.org
Subject: Re: bug#10686: mv: moving hardlink of a softlink to the softlink
	does nothing
Date: Tue, 07 Feb 2012 13:26:09 +0100
On 02/07/2012 11:45 AM, Michael Felt wrote:
> Just reading - for reference only - AIX 5.3, and I expect new versions behave as follows
> (second link becomes a hardlink to original file, mv "removes" one hard link, i.e. original file (as inode) remains.
> 
> root <at> x105:[/tmp/test]touch f
> root <at> x105:[/tmp/test]ln -s f l
> root <at> x105:[/tmp/test]ls -l
> total 0
> -rw-r--r--  1 root system 0 2012-02-07 11:41 f
> lrwxrwxrwx  1 root system 1 2012-02-07 11:41 l -> f
> root <at> x105:[/tmp/test]ln l s
> root <at> x105:[/tmp/test]ls -ogi
> total 0
> 239 -rw-r--r--  2 0 2012-02-07 11:41 f
> 240 lrwxrwxrwx  1 1 2012-02-07 11:41 l -> f
> 239 -rw-r--r--  2 0 2012-02-07 11:41 s
> root <at> x105:[/tmp/test]mv s f
> root <at> x105:[/tmp/test]ls -ogi
> total 0
> 239 -rw-r--r--  1 0 2012-02-07 11:41 f
> 240 lrwxrwxrwx  1 1 2012-02-07 11:41 l -> f

Hi Michael,

the result is the same, but there's a little difference here:
your ln does not create a hardlink to the symlink, but
rather a hardlink to the symlink's *target*.

On Linux, the situation before mv looks like this:

  $ touch f && ln -s f l && ln l s && ls -ogi
  total 0
  11279 -rw-r--r-- 1 0 Feb  7 13:17 f
  11280 lrwxrwxrwx 2 1 Feb  7 13:17 l -> f
  11280 lrwxrwxrwx 2 1 Feb  7 13:17 s -> f

Before Jim's patch, mv didn't remove the source, i.e. 's':

  $ /bin/mv s l ; ls -ogi
  total 0
  11279 -rw-r--r-- 1 0 Feb  7 13:17 f
  11280 lrwxrwxrwx 2 1 Feb  7 13:17 l -> f
  11280 lrwxrwxrwx 2 1 Feb  7 13:17 s -> f

Now, mv correctly unlinks 's':

  $ ~/git/coreutils/src/mv s l ; ls -ogi
  total 0
  11279 -rw-r--r-- 1 0 Feb  7 13:17 f
  11280 lrwxrwxrwx 1 1 Feb  7 13:17 l -> f

Regarding your case, there's no change.

Have a nice day,
Berny




Information forwarded to bug-coreutils <at> gnu.org:
bug#10686; Package coreutils. (Sat, 11 Feb 2012 11:26:01 GMT) Full text and rfc822 format available.

Message #44 received at 10686 <at> debbugs.gnu.org (full text, mbox):

From: Jim Meyering <jim <at> meyering.net>
To: Eric Blake <eblake <at> redhat.com>
Cc: Bernhard Voelker <mail <at> bernhard-voelker.de>, 10686 <at> debbugs.gnu.org
Subject: Re: bug#10686: mv: moving hardlink of a softlink to the softlink does
	nothing
Date: Sat, 11 Feb 2012 12:23:38 +0100
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 <meyering <at> redhat.com>
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 0000000..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 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
--
1.7.9.190.gf1d33




Information forwarded to bug-coreutils <at> gnu.org:
bug#10686; Package coreutils. (Sat, 11 Feb 2012 12:45:01 GMT) Full text and rfc822 format available.

Message #47 received at 10686 <at> debbugs.gnu.org (full text, mbox):

From: Eric Blake <eblake <at> redhat.com>
To: Jim Meyering <jim <at> meyering.net>
Cc: Bernhard Voelker <mail <at> bernhard-voelker.de>, 10686 <at> debbugs.gnu.org
Subject: Re: bug#10686: mv: moving hardlink of a softlink to the softlink
	does nothing
Date: Sat, 11 Feb 2012 05:43:15 -0700
[Message part 1 (text/plain, inline)]
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   eblake <at> redhat.com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org

[signature.asc (application/pgp-signature, attachment)]

Information forwarded to bug-coreutils <at> gnu.org:
bug#10686; Package coreutils. (Sun, 12 Feb 2012 13:17:02 GMT) Full text and rfc822 format available.

Message #50 received at 10686 <at> debbugs.gnu.org (full text, mbox):

From: Jim Meyering <jim <at> meyering.net>
To: Eric Blake <eblake <at> redhat.com>
Cc: Bernhard Voelker <mail <at> bernhard-voelker.de>, 10686 <at> debbugs.gnu.org
Subject: Re: bug#10686: mv: moving hardlink of a softlink to the softlink does
	nothing
Date: Sun, 12 Feb 2012 14:14:40 +0100
Eric Blake wrote:
> On 02/11/2012 04:23 AM, Jim Meyering wrote:
>> +++ b/NEWS
...
>> +  "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;

Thank you for the review and suggestions.

Such kernels *should* be unusual.  This rename-is-sometimes-a-no-op
exception makes it hard to use rename in an application that must
reliably produce results that make sense even to people who don't
care what inodes and invariants are.

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

This is the NEWS file, where we prefer to stick to the facts, but I
feel I have to make a small statement, so have adjusted it like this:

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


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

NetBSD 5.1 has the sensible kernel rename behavior, i.e.,
what one would expect in the absence of standardized legacy:

    netbsd$ : > f; ln f g; perl -e 'rename qw(f g) or die "$!"'; ls f g
    ls: cannot access f: No such file or directory
    g
    [Exit 2]

Programs like mv should not have to jump through hoops like copy.c's
same_file_ok function just to avoid the surprising (nonsensical, to most)
behavior of the standardized rename syscall.

I adjusted that comment, too, and pushed the result:

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




Added tag(s) fixed. Request was from Assaf Gordon <assafgordon <at> gmail.com> to control <at> debbugs.gnu.org. (Tue, 16 Oct 2018 16:21:02 GMT) Full text and rfc822 format available.

bug closed, send any further explanations to 10686 <at> debbugs.gnu.org and Bernhard Voelker <mail <at> bernhard-voelker.de> Request was from Assaf Gordon <assafgordon <at> gmail.com> to control <at> debbugs.gnu.org. (Tue, 16 Oct 2018 16:21:02 GMT) Full text and rfc822 format available.

bug archived. Request was from Debbugs Internal Request <help-debbugs <at> gnu.org> to internal_control <at> debbugs.gnu.org. (Wed, 14 Nov 2018 12:24:05 GMT) Full text and rfc822 format available.

This bug report was last modified 5 years and 161 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.