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)