GNU bug report logs - #6960
mv refuses to move a symlink over a hard link to the same file

Previous Next

Package: coreutils;

Reported by: Matt McCutchen <matt <at> mattmccutchen.net>

Date: Tue, 31 Aug 2010 21:27:01 UTC

Severity: normal

Done: Jim Meyering <jim <at> meyering.net>

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 6960 in the body.
You can then email your comments to 6960 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 owner <at> debbugs.gnu.org, bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Tue, 31 Aug 2010 21:27:01 GMT) Full text and rfc822 format available.

Acknowledgement sent to Matt McCutchen <matt <at> mattmccutchen.net>:
New bug report received and forwarded. Copy sent to bug-coreutils <at> gnu.org. (Tue, 31 Aug 2010 21:27:02 GMT) Full text and rfc822 format available.

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

From: Matt McCutchen <matt <at> mattmccutchen.net>
To: bug-coreutils <at> gnu.org
Subject: mv refuses to move a symlink over a hard link to the same file
Date: Tue, 31 Aug 2010 17:21:08 -0400
If mv is asked to move a symlink over a hard link to the same file, it
fails with the message, "A and B are the same file".  There is no reason
why it should complain rather than perform the move.  Example:

$ ~/coreutils/coreutils.usr/bin/mv --version
mv (GNU coreutils) 8.5.143-77702
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Mike Parker, David MacKenzie, and Jim Meyering.
$ touch New_York
$ ln New_York localtime
$ ln -s New_York localtime.new
$ ls -l
total 0
-rw------- 2 matt matt 0 2010-08-31 17:10 New_York
-rw------- 2 matt matt 0 2010-08-31 17:10 localtime
lrwxrwxrwx 1 matt matt 8 2010-08-31 17:11 localtime.new -> New_York
$ ~/coreutils/coreutils.usr/bin/mv localtime.new localtime
/home/matt/coreutils/coreutils.usr/bin/mv: `localtime.new' and `localtime' are the same file

-- 
Matt





Information forwarded to owner <at> debbugs.gnu.org, bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Tue, 31 Aug 2010 23:02:01 GMT) Full text and rfc822 format available.

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

From: Davide Brini <dave_br <at> gmx.com>
To: bug-coreutils <at> gnu.org
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same file
Date: Tue, 31 Aug 2010 23:48:56 +0100
On Tue, 31 Aug 2010 17:21:08 -0400
Matt McCutchen <matt <at> mattmccutchen.net> wrote:

> If mv is asked to move a symlink over a hard link to the same file, it
> fails with the message, "A and B are the same file".  There is no reason
> why it should complain rather than perform the move.  Example:
> 
> $ ~/coreutils/coreutils.usr/bin/mv --version
> mv (GNU coreutils) 8.5.143-77702
> Copyright (C) 2010 Free Software Foundation, Inc.
> License GPLv3+: GNU GPL version 3 or later
> <http://gnu.org/licenses/gpl.html>. This is free software: you are free
> to change and redistribute it. There is NO WARRANTY, to the extent
> permitted by law.
> 
> Written by Mike Parker, David MacKenzie, and Jim Meyering.
> $ touch New_York
> $ ln New_York localtime
> $ ln -s New_York localtime.new
> $ ls -l
> total 0
> -rw------- 2 matt matt 0 2010-08-31 17:10 New_York
> -rw------- 2 matt matt 0 2010-08-31 17:10 localtime
> lrwxrwxrwx 1 matt matt 8 2010-08-31 17:11 localtime.new -> New_York
> $ ~/coreutils/coreutils.usr/bin/mv localtime.new localtime
> /home/matt/coreutils/coreutils.usr/bin/mv: `localtime.new' and
> `localtime' are the same file

A simpler example is something like

$ touch New_York
$ ln -s New_York New_York.sym
$ mv New_York.sym New_York 
mv: `New_York.sym' and `New_York' are the same file

I think the reason is that when the source is a symbolic link, mv operates
on the symlink itself, not the file it points to. So, it can't rename the
symlink, because if successful that would imply having two entries named
"New_York" in the same directory. If this is really the reason for the
error, the error message could probably be improved.

-- 
D.




Information forwarded to owner <at> debbugs.gnu.org, bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Tue, 31 Aug 2010 23:29:03 GMT) Full text and rfc822 format available.

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

From: Eric Blake <eblake <at> redhat.com>
To: Davide Brini <dave_br <at> gmx.com>
Cc: bug-coreutils <at> gnu.org
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same	file
Date: Tue, 31 Aug 2010 17:30:19 -0600
On 08/31/2010 04:48 PM, Davide Brini wrote:
> A simpler example is something like
>
> $ touch New_York
> $ ln -s New_York New_York.sym
> $ mv New_York.sym New_York
> mv: `New_York.sym' and `New_York' are the same file
>
> I think the reason is that when the source is a symbolic link, mv operates
> on the symlink itself, not the file it points to. So, it can't rename the
> symlink, because if successful that would imply having two entries named
> "New_York" in the same directory.

Not quite.  POSIX specifies that:
http://www.opengroup.org/onlinepubs/9699919799/utilities/mv.html

If the source_file operand and destination path name the same existing 
file, then the destination path shall not be removed, and one of the 
following shall occur:

   1. No change is made to source_file, no error occurs, and no 
diagnostic is issued.
   2. No change is made to source_file, a diagnostic is issued to 
standard error identifying the two names, and the exit status is affected.
   3. 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 question boils down to whether "New_York" and "New_York.sym" name 
the same file (stat semantics), or whether, since mv generally operates 
on symlinks rather than dereferencing them, they are distinct files 
(lstat semantics).

Solaris /usr/xpg4/bin/mv (which is supposedly POSIX-compliant, although 
we could debate that) goes ahead with the move:
$ touch a
$ ln -s a b
$ /usr/xpg4/bin/mv b a
$ echo $? ?
0 a
$ readlink a
a

Certainly, the underlying rename() call is allowed to replace a file 
with a symlink, even if the symlink was to the file that it was 
replacing (and thus will create an ELOOP situation on the symlink while 
losing the contents that were pointed to).  So, in that regards, you 
could argue that Solaris' behavior is dangerous (it lost data) while 
coreutils is playing it safe, when the destination has a link count of 1 
in this simplified example.  But when the destination has a link count 
of 2, there is no data loss, so the original example seems like it is 
pointing out a case where coreutils is over-strict.

And I would argue that the strict POSIX wording says that we should only 
be refusing to move if the two files have the same inode; in both the 
original and simplified examples, the symlink has a different inode than 
the file it would be overwriting, so I think POSIX mandates that the 
data destruction happen rather than the coreutils behavior.

Maybe this would argue for a POSIXLY_CORRECT behavior choice?  :(

Maybe we need to raise this as a question to the Austin Group to argue 
that coreutils behavior should be permitted?

> If this is really the reason for the
> error, the error message could probably be improved.

This part is true - the error message could definitely be more precise, 
if we decide that POSIX even lets us issue an error in the first place.

-- 
Eric Blake   eblake <at> redhat.com    +1-801-349-2682
Libvirt virtualization library http://libvirt.org




Information forwarded to owner <at> debbugs.gnu.org, bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Wed, 01 Sep 2010 00:04:01 GMT) Full text and rfc822 format available.

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

From: Davide Brini <dave_br <at> gmx.com>
To: Eric Blake <eblake <at> redhat.com>
Cc: bug-coreutils <at> gnu.org
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same	file
Date: Wed, 1 Sep 2010 00:50:38 +0100
On Tue, 31 Aug 2010 17:30:19 -0600 Eric Blake <eblake <at> redhat.com> wrote:

> Not quite.  POSIX specifies that:
> http://www.opengroup.org/onlinepubs/9699919799/utilities/mv.html
> 
> If the source_file operand and destination path name the same existing 
> file, then the destination path shall not be removed, and one of the 
> following shall occur:
> 
>     1. No change is made to source_file, no error occurs, and no 
> diagnostic is issued.
>     2. No change is made to source_file, a diagnostic is issued to 
> standard error identifying the two names, and the exit status is affected.
>     3. 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 question boils down to whether "New_York" and "New_York.sym" name 
> the same file (stat semantics), or whether, since mv generally operates 
> on symlinks rather than dereferencing them, they are distinct files 
> (lstat semantics).
> 
> Solaris /usr/xpg4/bin/mv (which is supposedly POSIX-compliant, although 
> we could debate that) goes ahead with the move:
> $ touch a
> $ ln -s a b
> $ /usr/xpg4/bin/mv b a
> $ echo $? ?
> 0 a
> $ readlink a
> a

So they are saying that, since "a" and "b" do not "name the same existing
file", this has to be a perfectly normal rename, although this causes loss
of data. (but then, if one does "mv a b" and "a" and "b" are two different
regular files, there also is a loss of data because the contents of "b" are
overwritten, so from that point of view our example isn't special)

> Certainly, the underlying rename() call is allowed to replace a file 
> with a symlink

Thanks, this was actually my doubt (although now I recognize it wasn't
at all apparent from my previous message).

> even if the symlink was to the file that it was 
> replacing (and thus will create an ELOOP situation on the symlink while 
> losing the contents that were pointed to).

Creating ELOOP is already possible (eg "ln -s a a", although this does not
cause any loss of data).

> So, in that regards, you 
> could argue that Solaris' behavior is dangerous (it lost data) while 
> coreutils is playing it safe, when the destination has a link count of 1 
> in this simplified example.  But when the destination has a link count 
> of 2, there is no data loss, so the original example seems like it is 
> pointing out a case where coreutils is over-strict.
> 
> And I would argue that the strict POSIX wording says that we should only 
> be refusing to move if the two files have the same inode; in both the 
> original and simplified examples, the symlink has a different inode than 
> the file it would be overwriting, so I think POSIX mandates that the 
> data destruction happen rather than the coreutils behavior.

Given that rename() can replace a file with a symlink, I would say so,
but I have no authority on the subject :) 

> Maybe this would argue for a POSIXLY_CORRECT behavior choice?  :(
> 
> Maybe we need to raise this as a question to the Austin Group to argue 
> that coreutils behavior should be permitted?

This sounds like a good idea. Perhaps first get the interpretation of the
standard clarified, and if it turns out to be the destructive one as it
would seem, either always behave that way, or only when POSIXLY_CORRECT.
If instead the current coreutils behavior is already allowed by POSIX, all
the better.

-- 
D.




Information forwarded to owner <at> debbugs.gnu.org, bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Thu, 02 Sep 2010 08:07:02 GMT) Full text and rfc822 format available.

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

From: Jim Meyering <jim <at> meyering.net>
To: Matt McCutchen <matt <at> mattmccutchen.net>
Cc: 6960 <at> debbugs.gnu.org
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same file
Date: Thu, 02 Sep 2010 10:07:44 +0200
Matt McCutchen wrote:

> If mv is asked to move a symlink over a hard link to the same file, it
> fails with the message, "A and B are the same file".  There is no reason
> why it should complain rather than perform the move.

Actually, there is a very good reason.  See below.

> Example:
...
> $ touch New_York
> $ ln New_York localtime
> $ ln -s New_York localtime.new
> $ ls -l
> total 0
> -rw------- 2 matt matt 0 2010-08-31 17:10 New_York
> -rw------- 2 matt matt 0 2010-08-31 17:10 localtime
> lrwxrwxrwx 1 matt matt 8 2010-08-31 17:11 localtime.new -> New_York
> $ ~/coreutils/coreutils.usr/bin/mv localtime.new localtime
> /home/matt/coreutils/coreutils.usr/bin/mv: `localtime.new' and `localtime' are the same file

Here, your regular file, New_York, happens to be empty.
That is a special, degenerate case.
If you lose this file, via use of mv, you lose nothing at all.
Well, in general, you might lose convenient access to the destination inode,
if it had two or more links.
But what if it contained a copy of some important document?


It is a problem of perception.
The user sees two files, A and B.
The naive user sees that they have the same contents,
but does not notice they are symlinked.  May not even
know what a symlink is...
Our user decides to get rid of the duplicate.

The lucky naive user moves the real file onto the symlink (say "mv A B")
and that succeeds.  If however, s/he uses the other argument ordering
("mv B A") and moves the symlink onto the real file, some versions
of "mv" would leave our poor user with no copy of the original file.
This is called "data loss" ;-)  The user did nothing wrong, yet ended
up destroying what may have been important data.  That is why GNU mv
deliberately refuses to perform the offending operation.

One may argue that there is no data loss when the destination link count
is 2 or more, but once the destination has been unlinked, it may be very
challenging to locate another copy.

This is not a bug in GNU mv.
It is a deliberate feature.

Personally, I prefer the semantics of 'mv -f --backup=numbered'
so use a shell alias.




Information forwarded to owner <at> debbugs.gnu.org, bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Thu, 02 Sep 2010 10:09:01 GMT) Full text and rfc822 format available.

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

From: "Voelker, Bernhard" <bernhard.voelker <at> siemens-enterprise.com>
To: Jim Meyering <jim <at> meyering.net>, Matt McCutchen <matt <at> mattmccutchen.net>
Cc: "6960 <at> debbugs.gnu.org" <6960 <at> debbugs.gnu.org>
Subject: RE: bug#6960: mv refuses to move a symlink over a hard link to the
	same	file
Date: Thu, 2 Sep 2010 12:10:01 +0200
Jim Meyering wrote:

> It is a deliberate feature.
>
> Personally, I prefer the semantics of 'mv -f --backup=numbered'
> so use a shell alias.

just for fun I tried to get no backup created and tried '--backup=never',
but a backup is still created (version 8.5 on Cygwin, and 5.93 on SLES-10.3):

$ uname -a > a
$ ln -s a b
$ mv -v --backup=never b a
`b' -> `a' (backup: `a~')
$ ls -l a a~
lrwxrwxrwx  1 vb027591 ugrp  1 2010-09-02 11:50 a -> a
-rw-r--r--+ 1 vb027591 ugrp 69 2010-09-02 11:50 a~

I expected mv either to fail as if --backup=... is not given,
or that it moves the file without creating a backup.

Maybe I'm a bit confused that the combination of the words "backup"
and "never" contrasts to what it does: it _creates_ a backup
- though mentioned in manual:

$ man mv
...
       simple, never
              always make simple backups

Am I just misunderstanding the backup CONTROL "never"?

Have a nice day,
Berny



Information forwarded to owner <at> debbugs.gnu.org, bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Thu, 02 Sep 2010 10:27:02 GMT) Full text and rfc822 format available.

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

From: Jim Meyering <jim <at> meyering.net>
To: "Voelker\, Bernhard" <bernhard.voelker <at> siemens-enterprise.com>
Cc: Matt McCutchen <matt <at> mattmccutchen.net>, 6960 <at> debbugs.gnu.org
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same	file
Date: Thu, 02 Sep 2010 12:28:18 +0200
Voelker, Bernhard wrote:
> Jim Meyering wrote:
>
>> It is a deliberate feature.
>>
>> Personally, I prefer the semantics of 'mv -f --backup=numbered'
>> so use a shell alias.
>
> just for fun I tried to get no backup created and tried '--backup=never',
> but a backup is still created (version 8.5 on Cygwin, and 5.93 on SLES-10.3):
>
> $ uname -a > a
> $ ln -s a b
> $ mv -v --backup=never b a
> `b' -> `a' (backup: `a~')
> $ ls -l a a~
> lrwxrwxrwx  1 vb027591 ugrp  1 2010-09-02 11:50 a -> a
> -rw-r--r--+ 1 vb027591 ugrp 69 2010-09-02 11:50 a~
>
> I expected mv either to fail as if --backup=... is not given,
> or that it moves the file without creating a backup.
>
> Maybe I'm a bit confused that the combination of the words "backup"
> and "never" contrasts to what it does: it _creates_ a backup
> - though mentioned in manual:
>
> $ man mv
> ...
>        simple, never
>               always make simple backups
>
> Am I just misunderstanding the backup CONTROL "never"?

--backup=never means "never make numbered backups; always make single
backups", quoting emacs documentation (see 'Single or Numbered Backups' in
info emacs).  That's where that ambiguous name originated.

Or perhaps more relevant, see "info coreutils mv":

    `simple'
    `never'
          Always make simple backups.  Please note `never' is not to be
          confused with `none'.

When in doubt, refer to the "info" documentation,
not the man page.




Information forwarded to owner <at> debbugs.gnu.org, bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Sat, 27 Aug 2011 21:19:02 GMT) Full text and rfc822 format available.

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

From: Jim Meyering <jim <at> meyering.net>
To: Matt McCutchen <matt <at> mattmccutchen.net>
Cc: 6960 <at> debbugs.gnu.org
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same file
Date: Sat, 27 Aug 2011 23:15:04 +0200
tags 6950 + notabug
close 6950
thanks

Jim Meyering wrote:
> Matt McCutchen wrote:
>
>> If mv is asked to move a symlink over a hard link to the same file, it
>> fails with the message, "A and B are the same file".  There is no reason
>> why it should complain rather than perform the move.
>
> Actually, there is a very good reason.  See below.
>
>> Example:
> ...
>> $ touch New_York
>> $ ln New_York localtime
>> $ ln -s New_York localtime.new
>> $ ls -l
>> total 0
>> -rw------- 2 matt matt 0 2010-08-31 17:10 New_York
>> -rw------- 2 matt matt 0 2010-08-31 17:10 localtime
>> lrwxrwxrwx 1 matt matt 8 2010-08-31 17:11 localtime.new -> New_York
>> $ ~/coreutils/coreutils.usr/bin/mv localtime.new localtime
>> /home/matt/coreutils/coreutils.usr/bin/mv: `localtime.new' and
>> localtime' are the same file
>
> Here, your regular file, New_York, happens to be empty.
> That is a special, degenerate case.
> If you lose this file, via use of mv, you lose nothing at all.
> Well, in general, you might lose convenient access to the destination inode,
> if it had two or more links.
> But what if it contained a copy of some important document?
>
>
> It is a problem of perception.
> The user sees two files, A and B.
> The naive user sees that they have the same contents,
> but does not notice they are symlinked.  May not even
> know what a symlink is...
> Our user decides to get rid of the duplicate.
>
> The lucky naive user moves the real file onto the symlink (say "mv A B")
> and that succeeds.  If however, s/he uses the other argument ordering
> ("mv B A") and moves the symlink onto the real file, some versions
> of "mv" would leave our poor user with no copy of the original file.
> This is called "data loss" ;-)  The user did nothing wrong, yet ended
> up destroying what may have been important data.  That is why GNU mv
> deliberately refuses to perform the offending operation.
>
> One may argue that there is no data loss when the destination link count
> is 2 or more, but once the destination has been unlinked, it may be very
> challenging to locate another copy.
>
> This is not a bug in GNU mv.
> It is a deliberate feature.
>
> Personally, I prefer the semantics of 'mv -f --backup=numbered'
> so use a shell alias.

There was no further discussion, so I've closed this.




Added tag(s) notabug. Request was from Jim Meyering <jim <at> meyering.net> to control <at> debbugs.gnu.org. (Sat, 27 Aug 2011 21:27:02 GMT) Full text and rfc822 format available.

bug closed, send any further explanations to 6960 <at> debbugs.gnu.org and Matt McCutchen <matt <at> mattmccutchen.net> Request was from Jim Meyering <jim <at> meyering.net> to control <at> debbugs.gnu.org. (Sat, 27 Aug 2011 21:27: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. (Sun, 25 Sep 2011 11:24:06 GMT) Full text and rfc822 format available.

bug unarchived. Request was from Anders Kaseorg <andersk <at> MIT.EDU> to control <at> debbugs.gnu.org. (Wed, 04 Jan 2012 20:15:02 GMT) Full text and rfc822 format available.

Information forwarded to bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Wed, 04 Jan 2012 20:36:02 GMT) Full text and rfc822 format available.

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

From: Anders Kaseorg <andersk <at> MIT.EDU>
To: 6960 <at> debbugs.gnu.org
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same file
Date: Wed, 4 Jan 2012 15:32:02 -0500 (EST)
This refusal makes it impossible to overwrite a hard link with a symlink 
_atomically_.

See for example http://bugs.debian.org/654596 .

In reply to message #17:
> One may argue that there is no data loss when the destination link count 
> is 2 or more, but once the destination has been unlinked, it may be very 
> challenging to locate another copy.

I would instead argue that there is no data loss when replacing a hard 
link foo with a symlink to bar, as long as foo and bar are _different_ 
hard links to the same inode.  In that case, locating the other copy is 
not a problem because the symlink will still be valid.

For example, in the example from the original report:
  -rw------- 2 matt matt 0 2010-08-31 17:10 New_York
  -rw------- 2 matt matt 0 2010-08-31 17:10 localtime
  lrwxrwxrwx 1 matt matt 8 2010-08-31 17:11 localtime.new -> New_York
mv may reasonably refuse to overwrite New_York with localtime.new, but it 
should not refuse to overwrite localtime with localtime.new.

> Personally, I prefer the semantics of 'mv -f --backup=numbered' so use a 
> shell alias.

mv --backup=numbered is not atomic; it expands to two rename() syscalls, 
between which the target doesn’t exist at all.

Anders




Information forwarded to bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Wed, 04 Jan 2012 20:50:02 GMT) Full text and rfc822 format available.

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

From: Eric Blake <eblake <at> redhat.com>
To: Anders Kaseorg <andersk <at> MIT.EDU>
Cc: 6960 <at> debbugs.gnu.org
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same file
Date: Wed, 04 Jan 2012 13:46:27 -0700
[Message part 1 (text/plain, inline)]
On 01/04/2012 01:32 PM, Anders Kaseorg wrote:
> This refusal makes it impossible to overwrite a hard link with a symlink 
> _atomically_.

It is already impossible to overwrite a directory with a symlink
atomically; then again, the only time rename() allows overwriting a
directory is if it is empty, and removing an empty directory before
putting a symlink in its place is not a form of data loss.  But whether
the inability to atomically overwrite a directory with a symlink should
carry over to a refusal to atomically overwrite a regular file with a
symlink is a different matter.

>> Personally, I prefer the semantics of 'mv -f --backup=numbered' so use a 
>> shell alias.
> 
> mv --backup=numbered is not atomic; it expands to two rename() syscalls, 
> between which the target doesn’t exist at all.

Maybe we should fix that, to make mv --backup use link()/rename() rather
than rename()/rename(), so that there is no window where the target
doesn't exist.

-- 
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#6960; Package coreutils. (Wed, 04 Jan 2012 20:58:02 GMT) Full text and rfc822 format available.

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

From: Jim Meyering <jim <at> meyering.net>
To: Eric Blake <eblake <at> redhat.com>
Cc: 6960 <at> debbugs.gnu.org, Anders Kaseorg <andersk <at> MIT.EDU>
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same file
Date: Wed, 04 Jan 2012 21:53:53 +0100
Eric Blake wrote:
> On 01/04/2012 01:32 PM, Anders Kaseorg wrote:
>> This refusal makes it impossible to overwrite a hard link with a symlink
>> _atomically_.
>
> It is already impossible to overwrite a directory with a symlink
> atomically; then again, the only time rename() allows overwriting a
> directory is if it is empty, and removing an empty directory before
> putting a symlink in its place is not a form of data loss.  But whether
> the inability to atomically overwrite a directory with a symlink should
> carry over to a refusal to atomically overwrite a regular file with a
> symlink is a different matter.
>
>>> Personally, I prefer the semantics of 'mv -f --backup=numbered' so use a
>>> shell alias.
>>
>> mv --backup=numbered is not atomic; it expands to two rename() syscalls,
>> between which the target doesn’t exist at all.

Oh!  Amazing that suboptimal behavior has persisted in coreutils for so long.

> Maybe we should fix that, to make mv --backup use link()/rename() rather
> than rename()/rename(), so that there is no window where the target
> doesn't exist.

No "maybe" about it ;-)
I was already looking at that.
It will be rather hairy, though.




Information forwarded to bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Wed, 04 Jan 2012 21:05:02 GMT) Full text and rfc822 format available.

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

From: Jim Meyering <jim <at> meyering.net>
To: Eric Blake <eblake <at> redhat.com>
Cc: 6960 <at> debbugs.gnu.org, Anders Kaseorg <andersk <at> MIT.EDU>
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same file
Date: Wed, 04 Jan 2012 22:00:55 +0100
Eric Blake wrote:

> On 01/04/2012 01:32 PM, Anders Kaseorg wrote:
>> This refusal makes it impossible to overwrite a hard link with a symlink
>> _atomically_.
>
> It is already impossible to overwrite a directory with a symlink
> atomically; then again, the only time rename() allows overwriting a
> directory is if it is empty, and removing an empty directory before
> putting a symlink in its place is not a form of data loss.  But whether
> the inability to atomically overwrite a directory with a symlink should
> carry over to a refusal to atomically overwrite a regular file with a
> symlink is a different matter.
>
>>> Personally, I prefer the semantics of 'mv -f --backup=numbered' so use a
>>> shell alias.
>>
>> mv --backup=numbered is not atomic; it expands to two rename() syscalls,
>> between which the target doesn’t exist at all.
>
> Maybe we should fix that, to make mv --backup use link()/rename() rather
> than rename()/rename(), so that there is no window where the target
> doesn't exist.

Note that we can use link/rename only some of the time.
E.g., the rename will always fail when they're on different partitions.




Information forwarded to bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Wed, 04 Jan 2012 21:10:01 GMT) Full text and rfc822 format available.

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

From: Anders Kaseorg <andersk <at> MIT.EDU>
To: Eric Blake <eblake <at> redhat.com>
Cc: 6960 <at> debbugs.gnu.org
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same file
Date: Wed, 4 Jan 2012 16:06:03 -0500 (EST)
On Wed, 4 Jan 2012, Eric Blake wrote:
> But whether the inability to atomically overwrite a directory with a 
> symlink should carry over to a refusal to atomically overwrite a regular 
> file with a symlink is a different matter.

To be clear, mv is already perfectly happy to atomically overwrite a 
regular file with a symlink (even if that causes data loss), as long as it 
doesn’t detect that the symlink points to the same inode.

The only purpose given for this same-inode check is preventing a 
particular kind of accident, and I claim that this purpose would be better 
served by a same-path check, because the same-inode-but-different-path 
case is useful and can’t allow that kind of accident (and indeed, won’t 
lose data at all).

> Maybe we should fix that, to make mv --backup use link()/rename() rather
> than rename()/rename(), so that there is no window where the target
> doesn't exist.

Perhaps, but that’s not a good solution to _this_ problem, because a 
script that wants to do this kind of atomic replacement would then need to 
go delete the backups (potentially resulting in more data loss).

Anders




Information forwarded to bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Wed, 04 Jan 2012 21:17:02 GMT) Full text and rfc822 format available.

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

From: Eric Blake <eblake <at> redhat.com>
To: Jim Meyering <jim <at> meyering.net>
Cc: 6960 <at> debbugs.gnu.org, Anders Kaseorg <andersk <at> MIT.EDU>
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same file
Date: Wed, 04 Jan 2012 14:12:45 -0700
[Message part 1 (text/plain, inline)]
On 01/04/2012 02:00 PM, Jim Meyering wrote:
>>> mv --backup=numbered is not atomic; it expands to two rename() syscalls,
>>> between which the target doesn’t exist at all.
>>
>> Maybe we should fix that, to make mv --backup use link()/rename() rather
>> than rename()/rename(), so that there is no window where the target
>> doesn't exist.
> 
> Note that we can use link/rename only some of the time.
> E.g., the rename will always fail when they're on different partitions.

It's a two-edged sword - POSIX _requires_ mv(1) to be atomic (no period
where target does not exist) if no EXDEV would occur from rename(2), but
does _not_ require atomicity otherwise.  If rename(2) fails because you
cross partitions, then you already _expect_ a window where the target is
bogus, per the POSIX requirements on mv(1); so whether we
rename()/creat() or link()/unlink()/creat(), the behavior is the same.

Is it worth shaving off the extra syscall by using
rename(target,target-backup) instead of link()/unlink() when we can
detect a-priori that rename(source,target) would fail with EXDEV?  Or
should we _always_ favor link(target,target-backup), and only if the
rename(source,target) fails do we fall back to unlink(target) followed
by creating the file with the moved contents?

Or do we go one step further, and guarantee atomicity even where POSIX
does not require it in the EXDEV situation, by doing:
link(target,target-backup)
creat(temp) in dirname(target)
copy source to temp
rename(temp,target)
Then again, that takes up more disk space when there is no target-backup
created, so it seems like it might be worth providing that extra level
of atomicity guarantees only via a command line option.

-- 
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#6960; Package coreutils. (Wed, 04 Jan 2012 21:57:02 GMT) Full text and rfc822 format available.

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

From: Eric Blake <eblake <at> redhat.com>
To: Jim Meyering <jim <at> meyering.net>
Cc: 6960 <at> debbugs.gnu.org, Anders Kaseorg <andersk <at> MIT.EDU>
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same file
Date: Wed, 04 Jan 2012 14:01:52 -0700
[Message part 1 (text/plain, inline)]
tag 6960 - notabug
reopen 6960
thanks

On 01/04/2012 01:53 PM, Jim Meyering wrote:
>>> mv --backup=numbered is not atomic; it expands to two rename() syscalls,
>>> between which the target doesn’t exist at all.
> 
> Oh!  Amazing that suboptimal behavior has persisted in coreutils for so long.
> 
>> Maybe we should fix that, to make mv --backup use link()/rename() rather
>> than rename()/rename(), so that there is no window where the target
>> doesn't exist.
> 
> No "maybe" about it ;-)

In which case we'd better reopen this bug.

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

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

Removed tag(s) notabug. Request was from Eric Blake <eblake <at> redhat.com> to control <at> debbugs.gnu.org. (Wed, 04 Jan 2012 21:57:02 GMT) Full text and rfc822 format available.

Did not alter fixed versions and reopened. Request was from Debbugs Internal Request <help-debbugs <at> gnu.org> to internal_control <at> debbugs.gnu.org. (Wed, 04 Jan 2012 21:57:02 GMT) Full text and rfc822 format available.

Information forwarded to bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Wed, 04 Jan 2012 22:34:01 GMT) Full text and rfc822 format available.

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

From: Jim Meyering <jim <at> meyering.net>
To: Anders Kaseorg <andersk <at> MIT.EDU>
Cc: 6960 <at> debbugs.gnu.org
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same file
Date: Wed, 04 Jan 2012 23:30:16 +0100
Anders Kaseorg wrote:

> This refusal makes it impossible to overwrite a hard link with a symlink
> _atomically_.
>
> See for example http://bugs.debian.org/654596 .
>
> In reply to message #17:
>> One may argue that there is no data loss when the destination link count
>> is 2 or more, but once the destination has been unlinked, it may be very
>> challenging to locate another copy.
>
> I would instead argue that there is no data loss when replacing a hard
> link foo with a symlink to bar, as long as foo and bar are _different_
> hard links to the same inode.  In that case, locating the other copy is
> not a problem because the symlink will still be valid.
>
> For example, in the example from the original report:
>   -rw------- 2 matt matt 0 2010-08-31 17:10 New_York
>   -rw------- 2 matt matt 0 2010-08-31 17:10 localtime
>   lrwxrwxrwx 1 matt matt 8 2010-08-31 17:11 localtime.new -> New_York
> mv may reasonably refuse to overwrite New_York with localtime.new, but it
> should not refuse to overwrite localtime with localtime.new.

This sounds reasonable.
Implementing it may even be easy -- for some inputs.
However, in general, we'll have to find a way to accept this:

    mv localtime.new localtime

without also accepting this:

    mv localtime.new New_York

That latter command would leave New_York as a symlink to itself,
with the sole remaining link being "localtime".

Any solution must ensure that the destination and the referent of
source symlink are not the same entry.  We even have a function for
that, once you find the referent's name: same_name (in lib/same.c).
Sounds trivial, but the catch is how to find the referent's name *in
general*.  What if localtime.new points to a symlink, foo->bar->New_York ?
Then, in order to answer that question, we have to be able to traverse
an arbitrarily-long chain of symlinks.  What if there's a loop by the
time we start traversing?  None of these are insurmountable, but they
give you an idea of the amount of complexity that seems (at least to me)
to be required.

You could form the symlink-free full name of the referent, abs_src
and then test same_name (abs_src, dst_name).

I've just done it:
With this patch, cp still passes all of coreutils tests
as well as the one described in the comments below:
(take this with a grain of salt -- it's seen only minimal testing so far)

diff --git a/src/copy.c b/src/copy.c
index 4255d74..41ee3a6 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -34,6 +34,7 @@
 #include "acl.h"
 #include "backupfile.h"
 #include "buffer-lcm.h"
+#include "canonicalize.h"
 #include "copy.h"
 #include "cp-hash.h"
 #include "extent-scan.h"
@@ -1349,6 +1350,37 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
         }
     }

+  /* In move mode, when
+     src is a symlink,
+     dest is not a symlink,
+     dest has a link count of 2 or more and
+     dest and the referent of src are not the same entry (Hard part),
+     then it's ok, since while we'll lose one of those hard links,
+     src will still point to a remaining link.
+
+     Given this,
+       $ touch f && ln f l && ln -s f s
+       $ ls -og f l s
+       -rw-------. 2  0 Jan  4 22:46 f
+       -rw-------. 2  0 Jan  4 22:46 l
+       lrwxrwxrwx. 1  1 Jan  4 22:46 s -> f
+     this must fail: mv s f
+     this must succeed: mv s l */
+  if (x->dereference == DEREF_NEVER /* FIXME, reconsider this part */
+      && 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);
+      if (abs_src)
+        {
+          bool result = ! same_name (abs_src, dst_name);
+          free (abs_src);
+          return result;
+        }
+    }
+
   /* It's ok to remove a destination symlink.  But that works only when we
      unlink before opening the destination and when the source and destination
      files are on the same partition.  */




Information forwarded to bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Wed, 04 Jan 2012 22:57:01 GMT) Full text and rfc822 format available.

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

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Jim Meyering <jim <at> meyering.net>
Cc: 6960 <at> debbugs.gnu.org, Anders Kaseorg <andersk <at> MIT.EDU>
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same file
Date: Wed, 04 Jan 2012 14:52:28 -0800
On 01/04/12 14:30, Jim Meyering wrote:
> You could form the symlink-free full name of the referent, abs_src
> and then test same_name (abs_src, dst_name).

That doesn't sound right, since the symlink may resolve differently
after it's moved.  For example:

$ ls -ldt lt ny d d/lt.new
drwxr-xr-x. 2 eggert eggert 4096 Jan  4 14:44 d
lrwxrwxrwx. 1 eggert eggert    2 Jan  4 14:26 d/lt.new -> ny
-rw-r--r--. 2 eggert eggert    2 Jan  4 14:26 lt
-rw-r--r--. 2 eggert eggert    2 Jan  4 14:26 ny
$ mv d/lt.new ny
$ ls -ltd lt ny
lrwxrwxrwx. 1 eggert eggert 2 Jan  4 14:26 ny -> ny
-rw-r--r--. 1 eggert eggert 2 Jan  4 14:26 lt

This scenario is almost equivalent to the problematic one in
the original bug report, the one where 'mv' refuses to move,
and yet here 'mv' charges right ahead.

I'm becoming more inclined to think that Anders's argument:

> mv is already perfectly happy to atomically overwrite a 
> regular file with a symlink (even if that causes data loss)

is a valid one.  Currently 'mv' is pretty complicated in this
area, so complicated that I can't easily suggest a fix, but
I'm starting to think that mv shouldn't reject either
"mv localtime.new localtime" or "mv localtime.new New_York"
in Anders's scenario.  That is, it should simply compare inode
numbers without dereferencing them.




Information forwarded to bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Sun, 29 Jan 2012 21:34:02 GMT) Full text and rfc822 format available.

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

From: Jim Meyering <jim <at> meyering.net>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: 6960 <at> debbugs.gnu.org, Anders Kaseorg <andersk <at> MIT.EDU>
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same file
Date: Sun, 29 Jan 2012 22:33:05 +0100
Paul Eggert wrote:
> On 01/04/12 14:30, Jim Meyering wrote:
>> You could form the symlink-free full name of the referent, abs_src
>> and then test same_name (abs_src, dst_name).
>
> That doesn't sound right, since the symlink may resolve differently
> after it's moved.  For example:

Hi Paul,

Interesting point.  Thanks.

> $ ls -ldt lt ny d d/lt.new
> drwxr-xr-x. 2 eggert eggert 4096 Jan  4 14:44 d
> lrwxrwxrwx. 1 eggert eggert    2 Jan  4 14:26 d/lt.new -> ny
> -rw-r--r--. 2 eggert eggert    2 Jan  4 14:26 lt
> -rw-r--r--. 2 eggert eggert    2 Jan  4 14:26 ny
> $ mv d/lt.new ny

However, your example is not relevant, because the source is
a mere dangling symlink.  Thus, it doesn't meet the requirements
for that similarity check to be invoked.

Perhaps you meant something like this:

    $ rm -f [dfgs]; mkdir d; touch f; ln f g; ln -s ../f d/k; env mv d/k g
    mv: 'd/k' and 'g' are the same file

    $ ls -ogi f g d/k
    75574240 lrwxrwxrwx. 1 4 Jan 29 19:43 d/k -> ../f
    75574239 -rw-------. 2 0 Jan 29 19:43 f
    75574239 -rw-------. 2 0 Jan 29 19:43 g

And in that case, I find it hard to imagine a legitimate
use in which one would want to move such a relative symlink
onto its referent at a different level of the hierarchy.
So maybe we don't need to add a conjunct for that case after all.

...
> I'm becoming more inclined to think that Anders's argument:
>
>> mv is already perfectly happy to atomically overwrite a
>> regular file with a symlink (even if that causes data loss)
>
> is a valid one.

If your/Anders' point is that moving an arbitrary symlink onto
an unrelated regular file can unlink the final copy of the target,
well, yes, that's true, but there's nothing subtle or unusual about
that case, while there is in the cases that mv does reject.

mv calls rename, so of course it must induce data loss.
The point of this exception is to reject the very few
cases that indicate user error with very high probability.

Here's a more complete patch:

From 799b7f7c6f27a5356e76861066dfa554d3951f0d Mon Sep 17 00:00:00 2001
From: Jim Meyering <meyering <at> redhat.com>
Date: Thu, 5 Jan 2012 11:45:50 +0100
Subject: [PATCH] mv: allow moving symlink onto same-inode dest with >= 2 hard
 links

Normally, mv detects a few subtle cases in which proceeding with a
same-file rename would, with very high probability, cause data loss.
Here, we have found a corner case in which one of these same-inode
tests makes mv refuse to perform a useful operation.  Permit that
corner case.
* src/copy.c (same_file_ok): Detect/exempt this case.
* tests/mv/symlink-onto-hardlink: New test.
* tests/Makefile.am (TESTS): Add it.
* NEWS (Bug fixes): Mention it.
Initially reported by: Matt McCutchen in http://bugs.gnu.org/6960.
Raised again by Anders Kaseorg due to http://bugs.debian.org/654596.
---
 NEWS                           |    9 ++++++++
 THANKS.in                      |    2 +
 src/copy.c                     |   37 ++++++++++++++++++++++++++++++++++++
 tests/Makefile.am              |    1 +
 tests/mv/symlink-onto-hardlink |   41 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 90 insertions(+), 0 deletions(-)
 create mode 100755 tests/mv/symlink-onto-hardlink

diff --git a/NEWS b/NEWS
index 2b0926f..9eebbf6 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,15 @@ GNU coreutils NEWS                                    -*- outline -*-

 * Noteworthy changes in release ?.? (????-??-??) [?]

+** Bug fixes
+
+  mv now lets you move a symlink onto a same-inode destination file that
+  has two or more hard links.  Before, it would reject that, saying that
+  they are the same, implicitly warning you that the move would result in
+  data loss.  In this unusual case, when not moving the symlink onto its
+  referent, there is no risk of data loss, since the symlink will
+  typically still point to one of the hard links.
+

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

diff --git a/THANKS.in b/THANKS.in
index 13c8817..904bb3e 100644
--- a/THANKS.in
+++ b/THANKS.in
@@ -39,6 +39,7 @@ Alexey Vyskubov                     alexey <at> pippuri.mawhrin.net
 Alfred M. Szmidt                    ams <at> kemisten.nu
 Ambrose Feinstein                   ambrose <at> google.com
 Amr Ali                             amr.ali.cc <at> gmail.com
+Anders Kaseorg                      andersk <at> mit.edu
 Andi Kleen                          freitag <at> alancoxonachip.com
 Andre Novaes Cunha                  Andre.Cunha <at> br.global-one.net
 Andreas Frische                     andreasfrische <at> gmail.com
@@ -384,6 +385,7 @@ Mate Wierdl                         mw <at> moni.msci.memphis.edu
 Matej Vela                          mvela <at> public.srce.hr
 Matias A. Fonzo                     selk <at> dragora.org
 Matt Kraai                          kraai <at> ftbfs.org
+Matt McCutchen                      matt <at> mattmccutchen.net
 Matt Perry                          matt <at> primefactor.com
 Matt Pham                           mattvpham <at> gmail.com
 Matt Schalit                        mschalit <at> pacbell.net
diff --git a/src/copy.c b/src/copy.c
index 51f51be..af79ed3 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -34,6 +34,7 @@
 #include "acl.h"
 #include "backupfile.h"
 #include "buffer-lcm.h"
+#include "canonicalize.h"
 #include "copy.h"
 #include "cp-hash.h"
 #include "extent-scan.h"
@@ -1349,6 +1350,38 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
         }
     }

+  /* 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
+     src is a symlink,
+     dest is not a symlink,
+     dest has a link count of 2 or more and
+     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.
+
+     Given this,
+       $ touch f && ln f l && ln -s f s
+       $ ls -og f l s
+       -rw-------. 2  0 Jan  4 22:46 f
+       -rw-------. 2  0 Jan  4 22:46 l
+       lrwxrwxrwx. 1  1 Jan  4 22:46 s -> f
+     this must fail: mv s f
+     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);
+      if (abs_src)
+        {
+          bool result = ! same_name (abs_src, dst_name);
+          free (abs_src);
+          return result;
+        }
+    }
+
   /* It's ok to remove a destination symlink.  But that works only when we
      unlink before opening the destination and when the source and destination
      files are on the same partition.  */
@@ -1837,6 +1870,10 @@ copy_internal (char const *src_name, char const *dst_name,
                  to use fts, so using alloca here will be less of a problem.  */
               ASSIGN_STRDUPA (dst_backup, tmp_backup);
               free (tmp_backup);
+              /* In move mode, when src_name and dst_name are on the
+                 same partition (FIXME, and when they are non-directories),
+                 make the operation atomic: link dest
+                 to backup, then rename src to dest.  */
               if (rename (dst_name, dst_backup) != 0)
                 {
                   if (errno != ENOENT)
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 8b670fc..a94aaa2 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -491,6 +491,7 @@ TESTS =						\
   mv/part-symlink				\
   mv/partition-perm				\
   mv/perm-1					\
+  mv/symlink-onto-hardlink			\
   mv/to-symlink					\
   mv/trailing-slash				\
   mv/update					\
diff --git a/tests/mv/symlink-onto-hardlink b/tests/mv/symlink-onto-hardlink
new file mode 100755
index 0000000..2dac484
--- /dev/null
+++ b/tests/mv/symlink-onto-hardlink
@@ -0,0 +1,41 @@
+#!/bin/sh
+# Ensure that mv works with a few symlink-onto-hard-link cases.
+
+# 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
+
+touch f || framework_failure_
+ln f h || framework_failure_
+ln -s f s || framework_failure_
+
+# Given two links f and h to some important content, and a symlink s to f,
+# "mv s f" must fail because it might then be hard to find the link, h.
+# "mv s l" may succeed because then, s (now "l") still points to f.
+# Of course, if the symlink were being moved into a different destination
+# directory, things would be very different, and, I suspect, implausible.
+
+echo "mv: 's' and 'f' are the same file" > exp || framework_failure_
+mv s f > out 2> err && fail=1
+compare /dev/null out || fail=1
+compare exp err || fail=1
+
+mv s l > out 2> err || fail=1
+compare /dev/null out || fail=1
+compare /dev/null err || fail=1
+
+Exit $fail
--
1.7.9.1.g63eb




Information forwarded to bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Sun, 29 Jan 2012 22:06:02 GMT) Full text and rfc822 format available.

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

From: Pádraig Brady <P <at> draigBrady.com>
To: Jim Meyering <jim <at> meyering.net>
Cc: Paul Eggert <eggert <at> cs.ucla.edu>, 6960 <at> debbugs.gnu.org,
	Anders Kaseorg <andersk <at> MIT.EDU>
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same file
Date: Sun, 29 Jan 2012 22:05:44 +0000
On 01/29/2012 09:33 PM, Jim Meyering wrote:

> diff --git a/src/copy.c b/src/copy.c
> index 51f51be..af79ed3 100644
> --- a/src/copy.c
> +++ b/src/copy.c
> @@ -34,6 +34,7 @@
>  #include "acl.h"
>  #include "backupfile.h"
>  #include "buffer-lcm.h"
> +#include "canonicalize.h"
>  #include "copy.h"
>  #include "cp-hash.h"
>  #include "extent-scan.h"
> @@ -1349,6 +1350,38 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
>          }
>      }
> 
> +  /* 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
> +     src is a symlink,
> +     dest is not a symlink,
> +     dest has a link count of 2 or more and
> +     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.
> +
> +     Given this,
> +       $ touch f && ln f l && ln -s f s
> +       $ ls -og f l s
> +       -rw-------. 2  0 Jan  4 22:46 f
> +       -rw-------. 2  0 Jan  4 22:46 l
> +       lrwxrwxrwx. 1  1 Jan  4 22:46 s -> f
> +     this must fail: mv s f
> +     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);
> +      if (abs_src)
> +        {
> +          bool result = ! same_name (abs_src, dst_name);
> +          free (abs_src);
> +          return result;
> +        }
> +    }

Well the logic follows the description at least.
I was going to say the nlink test was redundant,
but it's a bit clearer with that left, and also
it's a performance improvement in the nlink=1 case.

cheers,
Pádraig.




Information forwarded to bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Mon, 30 Jan 2012 09:05:01 GMT) Full text and rfc822 format available.

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

From: Jim Meyering <jim <at> meyering.net>
To: Pádraig Brady <P <at> draigBrady.com>
Cc: Paul Eggert <eggert <at> cs.ucla.edu>, 6960 <at> debbugs.gnu.org,
	Anders Kaseorg <andersk <at> MIT.EDU>
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same file
Date: Mon, 30 Jan 2012 10:04:36 +0100
Pádraig Brady wrote:
> On 01/29/2012 09:33 PM, 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
>> +     src is a symlink,
>> +     dest is not a symlink,
>> +     dest has a link count of 2 or more and
>> +     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.
>> +
>> +     Given this,
>> +       $ touch f && ln f l && ln -s f s
>> +       $ ls -og f l s
>> +       -rw-------. 2  0 Jan  4 22:46 f
>> +       -rw-------. 2  0 Jan  4 22:46 l
>> +       lrwxrwxrwx. 1  1 Jan  4 22:46 s -> f
>> +     this must fail: mv s f
>> +     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);
>> +      if (abs_src)
>> +        {
>> +          bool result = ! same_name (abs_src, dst_name);
>> +          free (abs_src);
>> +          return result;
>> +        }
>> +    }
>
> Well the logic follows the description at least.
> I was going to say the nlink test was redundant,
> but it's a bit clearer with that left, and also
> it's a performance improvement in the nlink=1 case.

Thanks for the review.
I'll wait a day or so more, in case Paul or anyone else has feedback.
BTW, I removed the

    x->dereference == DEREF_NEVER

test that I'd used in the first version, since
in move mode, that condition is always true.




Information forwarded to bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Mon, 30 Jan 2012 11:42:02 GMT) Full text and rfc822 format available.

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

From: Jim Meyering <jim <at> meyering.net>
To: Pádraig Brady <P <at> draigBrady.com>
Cc: Paul Eggert <eggert <at> cs.ucla.edu>, 6960 <at> debbugs.gnu.org,
	Anders Kaseorg <andersk <at> MIT.EDU>
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same file
Date: Mon, 30 Jan 2012 12:41:16 +0100
Pádraig Brady wrote:

> On 01/29/2012 09:33 PM, Jim Meyering wrote:
>
>> diff --git a/src/copy.c b/src/copy.c
>> index 51f51be..af79ed3 100644
>> --- a/src/copy.c
>> +++ b/src/copy.c
>> @@ -34,6 +34,7 @@
>>  #include "acl.h"
>>  #include "backupfile.h"
>>  #include "buffer-lcm.h"
>> +#include "canonicalize.h"
>>  #include "copy.h"
>>  #include "cp-hash.h"
>>  #include "extent-scan.h"
>> @@ -1349,6 +1350,38 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
>>          }
>>      }
>>
>> +  /* 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
>> +     src is a symlink,
>> +     dest is not a symlink,
>> +     dest has a link count of 2 or more and
>> +     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.
>> +
>> +     Given this,
>> +       $ touch f && ln f l && ln -s f s
>> +       $ ls -og f l s
>> +       -rw-------. 2  0 Jan  4 22:46 f
>> +       -rw-------. 2  0 Jan  4 22:46 l
>> +       lrwxrwxrwx. 1  1 Jan  4 22:46 s -> f
>> +     this must fail: mv s f
>> +     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);
>> +      if (abs_src)
>> +        {
>> +          bool result = ! same_name (abs_src, dst_name);
>> +          free (abs_src);
>> +          return result;
>> +        }
>> +    }
>
> Well the logic follows the description at least.
> I was going to say the nlink test was redundant,
> but it's a bit clearer with that left, and also
> it's a performance improvement in the nlink=1 case.

That's a good point.
I've added it:

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




Information forwarded to bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Mon, 30 Jan 2012 17:47:02 GMT) Full text and rfc822 format available.

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

From: Paul Eggert <eggert <at> cs.ucla.edu>
To: Jim Meyering <jim <at> meyering.net>
Cc: 6960 <at> debbugs.gnu.org, Pádraig Brady <P <at> draigBrady.com>,
	Anders Kaseorg <andersk <at> MIT.EDU>
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same file
Date: Mon, 30 Jan 2012 09:46:24 -0800
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"?
The last 2 lines talk about condition 3, not condition 2.

Come to think of it, why is condition 2 needed at all?
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?




Information forwarded to bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Mon, 30 Jan 2012 19:43:02 GMT) Full text and rfc822 format available.

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

From: Jim Meyering <jim <at> meyering.net>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: 6960 <at> debbugs.gnu.org, Pádraig Brady <P <at> draigBrady.com>,
	Anders Kaseorg <andersk <at> MIT.EDU>
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same file
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);




Information forwarded to bug-coreutils <at> gnu.org:
bug#6960; Package coreutils. (Tue, 31 Jan 2012 12:36:01 GMT) Full text and rfc822 format available.

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

From: Jim Meyering <jim <at> meyering.net>
To: Paul Eggert <eggert <at> cs.ucla.edu>
Cc: 6960 <at> debbugs.gnu.org, Pádraig Brady <P <at> draigBrady.com>,
	Anders Kaseorg <andersk <at> MIT.EDU>
Subject: Re: bug#6960: mv refuses to move a symlink over a hard link to the
	same file
Date: Tue, 31 Jan 2012 13:35:25 +0100
Jim Meyering wrote:
...
> Thanks for the review!

I've pushed that fix:

    http://git.sv.gnu.org/cgit/coreutils.git/commit/?id=d1b0155d805ce

and am marking this "done".

I've created a new ticket for the task of making
'mv --backup=numbered ...' atomic:

   http://bugs.gnu.org/10679




bug closed, send any further explanations to 6960 <at> debbugs.gnu.org and Matt McCutchen <matt <at> mattmccutchen.net> Request was from Jim Meyering <jim <at> meyering.net> to control <at> debbugs.gnu.org. (Sat, 18 Feb 2012 18:58: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. (Sun, 18 Mar 2012 11:24:03 GMT) Full text and rfc822 format available.

This bug report was last modified 12 years and 13 days ago.

Previous Next


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