renameat2() RENAME_NOREPLACE errors trying to change case of filename

From: Joshua Hudson

Date: Thu May 14 2026 - 09:53:24 EST


Consider a file on a FAT filesystem (USB stick, EFI partition, etc.)

If it becomes desirable or necessary to change the case of the file,
the old way of calling rename() works, however calling renameat2(-1,
"NAME", -1, "name", RENAME_NOREPLACE); fails with EEXIST. I have
traced this within the kernel, the noreplace check is very simplistic.

Logically the bug is as follows:
1. Get source dnode
2. Get directory of destination (and lock)
3. Check if destination exists
4. If it does, return -EEXIST

A fix would look like this:
1. Get source dnode
2. Get directory of destination (and lock)
3. Check if destination exists and not the same dnode as #1
4. If it does, return -EEXIST

I deliberately wrote dnode and not inode; we don't want to clobber a
different link to the same file.

I encountered this the first time in running a battery of unit tests
for library code, and basically said "who cares"; even I really didn't
at the time, just documented faulting kernel call and moved on.

I encountered this a second time when correcting something on my EFI
partition and got slightly annoyed.

Related: Last I checked, mv -i has a TOCCOU bug in how it avoids
clobbering existing files without prompting. Someday they may fix this
bug (which would be done by calling renameat2(); when they do,
observing this on FAT filesystems becomes trivial.

This came up again trying to write some other documentation and I
thought time to report this.