New rename(2) function

Eric Blake ebb9@byu.net
Fri Aug 10 21:02:00 GMT 2007


[adding coreutils, since this may be an upstream problem; this stems from
an attempt to make cygwin's rename(2) more POSIX-compliant]

According to Christian Franke on 8/10/2007 2:37 PM:
>>
>> Btw., this looks weird in mv:
>>
>>   $ mv -T Bar bar
>>   mv: cannot remove `Bar': Operation not permitted

For some background, this occurred on a case-insensitive system, where Bar
was an existing directory; the intent was to change the case of the file
on disk.

> 
> The root of the problem is IMO the copy.c:same_file_ok() function, see
> attached code snippets (coreutils 6.9).
> 
> On -T, it treats the dest dir as a file and checks (1 < #links) which is
> always true for a directory. The same_name() function is case sensitive.

[Aside: 1 < #links is not always true for directories on all file systems,
but happens to be true for POSIX-compliant file systems as well as on NTFS]

> 
> Therefore, same_file_ok() returns true and sets unlink_src=true.
> 
> Later, copy_internal() handles the source path as a hard link to
> destination and tries to unlink() the source. Fortunately, unlink() is
> unable to rmdir().
> 
> No problem if both names match:
> 
> $ mv -T bar bar
> mv: `bar' and `bar' are the same file
> 
> $ mv -T ./BAR BAR
> mv: `./BAR' and `BAR' are the same file

Yes, the 'operation not permitted' is a weird fallout of the code path,
but I am not sure how much effort it is worth to try to improve the
message in this corner case.

> 
> In the file case, the problem does not occur because #links usually are 1:
> 
> $ touch Foo
> 
> $ mv Foo foo
> mv: `Foo' and `foo' are the same file
> 
> 
> But be aware: If there are hard links, things get really worse:
> 
> $ touch Foo
> 
> $ ln Foo Fool
> 
> $ mv -v Foo foo
> removed `Foo'

Ouch.  Coreutils decided that since Foo and foo represent the same file
(and on a case-insensitive system, they do), and since Foo has more than
one link, that it was safe to unlink Foo because the data would be left in
foo.  But how is coreutils supposed to know when a file system is
case-insensitive, such that unlinking Foo is the wrong action?  In other
words, how do you distinguish between two spellings of the same directory
entry, which is a different matter than two directory entries to the same
inode?

> 
> $ ls foo* Foo*
> ls: cannot access foo*: No such file or directory
> Fool

At least the data was not lost; you still have it in Fool.  Calling unlink
on a file with 2 or more links does not irretrievably lose the data in the
inode, even if, as in this case, it lost a directory entry when it wasn't
supposed to.

> 
> 
> This is probably not POSIX compliant ;-)

Ah, but POSIX requires a case-sensitive file system.  So you already left
the realm of POSIX in the first place long before the rename(2) or mv(1),
since in POSIX stat("foo") would have failed rather than coming back
identical with stat("Foo").

-- 
Don't work too hard, make some time for fun as well!

Eric Blake             ebb9@byu.net



More information about the Cygwin-developers mailing list