Jinoh Kang (@iamahuman) commented about server/fd.c:
- /* same dentry means a no-op */
- if (!strcmp( srcname, dstname )) goto ret;
- /* This is more complicated now, because in case of a casefold (+F) directory, the destination may very well be the same dentry,
* even if the name doesn't match (if it differs just by case), in which case unlinking it is wrong and can be dangerous. Instead,
* we first rename the source to a temporary filename in the same directory. If this is a casefold dir, this will also remove the
* destination, otherwise the destination still exists. We then create a hardlink from the destination to our temporary name, and
* finally, unlink the temporary. This still works if the directory is case sensitive, so it's not a problem in either case. */
- tmp_value += (current_time >> 16) + current_time;
- for (i = 0; i < 0x8000; i++, tmp_value += 7777)
- {
snprintf( tmpname, sizeof(tmpname), tmpname_fmt, tmp_value );
if (!is_dir)
{
/* to avoid TOCTTOU, probe existence of tmpname and link at the same time */
if (linkat( dirfd, srcname, dirfd, tmpname, 0 ))
I've been told that RenameFile() can be called on filesystems where hardlinks are unsupported (e.g., FAT32 which does casefolding as well).
When unsupported, linkat(2) returns EPERM[^p] or EOPNOTSUPP[^o]. If either are returned, we should fall back to no-hardlink path (which we originally intended for `is_dir` case).
[^p]: On Linux[^l], or on other OSes if forbidden by policy. [^o]: On macOS[^x], FreeBSD[^f], and NetBSD[^n]. [^l]: https://www.man7.org/linux/man-pages/man2/link.2.html#ERRORS [^x]: aka ENOTSUP. https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b... [^f]: https://man.freebsd.org/cgi/man.cgi?query=linkat&apropos=0&sektion=2... [^n]: https://man.netbsd.org/linkat.2#ERRORS