Re: [RFC v1] man/man2/close.2: CAVEATS: Document divergence from POSIX.1-2024
From: Zack Weinberg <hidden>
Date: 2026-01-25 15:37:43
Also in:
linux-fsdevel
On Sat, Jan 24, 2026, at 4:57 PM, The 8472 wrote:
On 24/01/2026 22:39, Rich Felker wrote:quoted
On Sat, Jan 24, 2026 at 08:34:01PM +0100, The 8472 wrote:quoted
On 23/01/2026 01:33, Zack Weinberg wrote: [...]quoted
ERRORS EBADF The fd argument was not a valid, open file descriptor.Unfortunately EBADF from FUSE is passed through unfiltered by the kernel on close[0], that makes it more difficult to reliably detect bugs relating to double-closes of file descriptors.Wow, that's a nasty bug. Are the kernel folks not amenable to fixing it?Not when I brought it up last time, no[0] [0] https://lore.kernel.org/linux-fsdevel/1b946a20-5e8a-497e-96ef-f7b1e037edcb@infinite-source.de/ (local)
It seems to me that Antonio Muscemi’s point is valid for *most* errno codes. Like, a whole lot of them exist just to give more information *to a human user* about the cause of an unrecoverable error. Take the list of “error codes that indicate a delayed error from a previous write(2) operation,” from a little later in the draft, for instance: there’s no plausible way for a *program* to react differently to EFBIG, EDQUOT, and ENOSPC, but we expect that the *user* will want to react differently, so we want different error messages for each, so they’re different error codes. It’s not a problem if the kernel produces an error code of this type that wasn’t in the official documented list, because the program doesn’t need to treat it specially. But EBADF is different; it has the very specific meaning “user space passed an invalid file descriptor to a system call,” which almost always indicates a *bug in the program*, and allowing that meaning to be diluted is not OK. It’s getting off topic for this conversation, but there’s a short list of other errno codes that indicate a specific situation that the *program* should respond to in a specific way (EAGAIN, EINTR, EINPROGRESS, EFAULT, and EPIPE are the only ones I can think of) and maybe it would spark a more constructive conversation on the kernel side if we presented a *comprehensive* list of errno codes that FUSE servers shouldn’t be allowed to produce with a specific rationale for each.
quoted
Delayed errors reported by close() In a variety of situations, most notably when writing to a file that is hosted on a network file server, write(2) operations may “optimistically” return successfully as soon as the write has been queued for processing. close(2) waits for confirmation that *most* of the processing for previous writes to a file has been completed, and reports any errors that the earlier write() calls *would have* reported, if they hadn’t returned optimistically. Especially, close() will report “disk full” (ENOSPC) and “disk quota exceeded” (EDQUOT) errors that write() didn’t wait for.The Rust standard library team is also interested in this topic, there is lively discussion[1] whether it makes sense to surface errors from close at all. Our current default is to ignore them. It is my understanding that errors may not have happened yet at the time of close due to delayed writeback or additional descriptors pointing to the description, e.g. in a forked child, and thus close() is not a reliable mechanism for error detection and fsync() is the only available option. [1] https://github.com/rust-lang/libs-team/issues/705
This is something I care about a lot as well, but I currently don’t have an *opinion*. To form an informed opinion, I need the answers to these questions:
quoted
[QUERY: Do delayed errors ever happen in any of these situations? - The fd is not the last reference to the open file description - The OFD was opened with O_RDONLY - The OFD was opened with O_RDWR but has never actually been written to - No data has been written to the OFD since the last call to fsync() for that OFD - No data has been written to the OFD since the last call to fdatasync() for that OFD If we can give some guidance about when people don’t need to worry about delayed errors, it would be helpful.]
In particular, I really hope delayed errors *aren’t* ever reported
when you close a file descriptor that *isn’t* the last reference
to its open file description, because the thread-safe way to close
stdout without losing write errors[2] depends on that not happening.
And whether the Rust stdlib can legitimately say “leaving aside the
additional cost of calling fsync(), you do not *need* the error return
from close() because you can call fsync() first,” depends on whether
it’s actually true that you *won’t* ever get a delayed error from
close() if you called fsync() first and didn’t do any more output in
between (assume the fd has no duplicates here). I would not be
surprised at all if those FUSE guys insisted on their right to make
char msg[] = "soon I will be invincible\n";
int fd = open("/test-fuse-fs/test.txt", O_WRONLY, 0666);
write(fd, msg, sizeof(msg) - 1);
fsync(fd);
close(fd);
return an error *only* from the close, not the write or the fsync.
And I also wouldn’t be surprised at all to find production NFS or
SMB servers that did that.
[2] https://stackoverflow.com/a/50865617 (third code block)
zw