Thread (49 messages) 49 messages, 14 authors, 2026-02-06

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

From: Zack Weinberg <hidden>
Date: 2026-01-23 00:34:57
Also in: linux-fsdevel

Alright, since it actually seems possible we might be having a
reasonable conversation about the close manpage now, I've done
another draft. I *think* this covers all the concerns expressed
so far.  I am feeling somewhat more charitable toward the Austin
Group after close-reading the current POSIX spec for close,
so there is no BUGS section after all.  In their shoes I would
still have disallowed EINTR returns from close altogether, but
I can see why they felt that was a step too far.

This is a full top-to-bottom rewrite of the manpage; please speak
up if you don't like any of my changes to any of it, not just the
new stuff about delayed errors.  It's written in freeform text for
ease of reading; I'll do proper troff markup after the text is
finalized.  (Alejandro, do you have a preference between -man
and -mdoc markup?)

Please note the [QUERY:] sections sprinkled throughout NOTES.
I would like to have answers to those questions for the final draft.

zw

NAME
       close - close a file descriptor

LIBRARY
       Standard C library (libc, -lc)

SYNOPSIS
       #include <unistd.h>

       int close(int fd);

DESCRIPTION
       close() closes a file descriptor, so that it no longer refers
       to any file and may be reused.

       When the last file descriptor referring to an underlying open
       file description (see open(2)) is closed, the resources
       associated with the open file description are freed.  If that
       open file description is the last reference to a file which has
       been removed using unlink(2), the file is deleted.

       When *any* file descriptor is closed, all record locks held by
       the *process*, on the file formerly referred to by that file
       descriptor, are released.  This happens even if the file is
       still open in the process via a different file descriptor.
       See fcntl(2) for discussion of the consequences, and for
       alternatives with less surprising semantics.

       close() may report a *delayed error* from previous I/O
       operations on a file.  When it does this, the file descriptor
       has still been closed, but the error needs to be handled.
       See RETURN VALUE, ERRORS, and NOTES for further discussion of
       what the errors reported by close mean, and how to handle them.

       Despite the possibility of delayed errors, a successful close()
       does *not* guarantee that all data written to the file has been
       successfully saved to persistent storage.  If you need such a
       guarantee, use fsync(2); see that page for details.

       The close-on-exec file descriptor flag can be used to ensure
       that a file descriptor is automatically closed upon a
       successful execve(2); see fcntl(2) for details.

RETURN VALUE
       close() returns zero if the descriptor has been closed and
       there were no delayed errors to report.

       It returns -1 if there was an error that prevented the
       file descriptor from being closed, *or* if the descriptor
       has successfully been closed but there was a delayed error
       to report.  The errno code can be used to distinguish them;
       see ERRORS and NOTES.

ERRORS
       EBADF  The fd argument was not a valid, open file descriptor.

       EINTR  The close() call was interrupted by a signal.
              The file descriptor *may or may not* have been closed,
              depending on the operating system.  See “Signals and
              close(),” below.

       EINPROGRESS
              [POSIX.1-2024 only] The close() call was interrupted by
              a signal, after the file descriptor number was released
              for reuse, but before all clean-up work had been
              completed.  The file descriptor has been closed,
              and a delayed error may have been lost.  See “Signals
              and close(),” below.

       EIO
       ESTALE
       EDQUOT
       EFBIG
       ENOSPC These error codes indicate a delayed error from a
              previous write(2) operation.  The file descriptor has
              been closed, but the error needs to be handled.
              See “Delayed errors reported by close()”, below.

       Depending on the underlying file and/or file system, close()
       may return with other errno codes besides those listed.
       All such codes also indicate delayed errors.

NOTES
   Multithreaded processes and close()

       In a multithreaded program, each thread must take care not to
       accidentally close file descriptors that are in use by other
       threads.  Because system calls that *open* files, sockets,
       etc. always allocate the lowest file descriptor number that’s
       not in use, file descriptor numbers are rapidly reused.
       Closing an fd that another thread is still using is therefore
       likely to cause data to be read or written to the wrong place.

       Sometimes programs *deliberately* close a file descriptor that
       is in use by another thread, intending to cancel any blocking
       I/O operation that the other thread is performing.  Whether
       this works depends on the operating system.  On Linux, it
       doesn’t work; a blocking I/O system call holds a direct
       reference to the underlying open file description that is the
       target of the I/O, and is unaffected by the program closing the
       file descriptor that was used to initiate the I/O operation.
       (See open(2) for a discussion of open file descriptions.)

   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.

       (To wait for *all* processing to complete, it is necessary to
       use fsync(2) as well.)

       Because of these delayed errors, it’s important to check the
       return value of close() and handle any errors it reports.
       Ignoring delayed errors can cause silent loss of data.

       However, when handling delayed errors, keep in mind that the
       close() call should *not* be repeated.  When close() has a
       delayed error to report, it still closes the file before
       returning.  The file descriptor number might already have been
       reused for some other file, especially in multithreaded
       programs.  To make another attempt at the failed writes, it’s
       necessary to reopen the file and start all over again.

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

    Signals and close()

       close() waits for various I/O operations to complete; it is a
       blocking system call, which can be interrupted by signals and
       thread cancellation.  As usual, when close() is interrupted
       by a signal, it returns -1 and sets errno to EINTR.

       Unlike most system calls that can be interrupted by signals,
       it is not safe to repeat an interrupted call to close().
       Prior to POSIX.1-2024, when a close() was interrupted by a
       signal, it was *unspecified* whether the file descriptor was
       still open afterward.  The authors of this manpage are aware
       of both systems where the file descriptor is guaranteed to
       still be open after an interrupted close(), e.g. HP-UX, and
       systems where it is guaranteed to be *closed* after an
       interrupted close(), e.g. Linux and FreeBSD.

       POSIX.1-2024 makes stricter requirements; operating systems
       should now return EINPROGRESS, rather than EINTR, when close()
       is interrupted before it’s completely done, but after the file
       descriptor number is released for reuse.  As usual, though, it
       will be a a long time before portable code can safely assume
       all supported systems are compliant with this new requirement.

       Regardless of the error code, on systems where an interrupted
       close() cannot be retried, an interruption means that delayed
       errors may be lost, and in turn *that* means data might silently
       be lost.  Therefore, we strongly recommend that programmers
       avoid allowing close() to be interrupted by signals in the
       first place.  This can be done in all the usual ways—use only
       signal handlers installed by sigaction(2) with the SA_RESTART
       flag, keep signals blocked at all times except during calls
       to ppoll(2), dedicate a thread to signal handling, etc.

   [QUERY: Do we know if close() is allowed to block or report delayed
       errors when no data has been written to the OFD since the last
       completed fsync() or fdatasync() on that OFD?  If it isn’t
       allowed to block or report delayed errors in that case, another
       good recommendation would be to always use at least fdatasync()
       and let *that* be the thing that gets interrupted by signals.
       The POSIX.1-2024 RATIONALE section makes a very similar
       recommendation, but doesn’t appear to back that up with
       normative requirements on close().]

STANDARDS
       POSIX.1-2024.

HISTORY
       The close() system call was present in Unix V7.

       POSIX.1-2024 clarified the semantics of delayed errors; prior
       to that revision, it was unspecified whether a close() call
       that returned a delayed error would close the file descriptor.
       However, we are not aware of any systems where it didn’t.

SEE ALSO
       close_range(2), fcntl(2), fsync(2), fdatasync(2), shutdown(2),
       unlink(2), open(2), read(2), write(2), fopen(3), fclose(3)
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help