[RFC v1] man/man2/close.2: CAVEATS: Document divergence from POSIX.1-2024

Zack Weinberg zack@owlfolio.org
Sun Jan 25 15:37:01 GMT 2026


On Sat, Jan 24, 2026, at 4:57 PM, The 8472 wrote:
> On 24/01/2026 22:39, Rich Felker wrote:
>> On Sat, Jan 24, 2026 at 08:34:01PM +0100, The 8472 wrote:
>>> On 23/01/2026 01:33, Zack Weinberg wrote:
>>>
>>> [...]
>>>
>>>> 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/

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.

>>     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:

>>      [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


More information about the Libc-alpha mailing list