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;