NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

kern/58929: POSIX.1-2024 compliance: posix_close, POSIX_CLOSE_RESTART



>Number:         58929
>Category:       kern
>Synopsis:       POSIX.1-2024 compliance: posix_close, POSIX_CLOSE_RESTART
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sat Dec 21 21:05:00 +0000 2024
>Originator:     Taylor R Campbell
>Release:        
>Organization:
The CloseBSD Fdation
>Environment:
>Description:
Through POSIX.1-2008, the state of the descriptor after the close() function _fails_ has been specified to be indeterminate:

> If close() is interrupted by a signal that is to be caught, it shall return -1 with errno set to [EINTR] and the state of fildes is unspecified. If an I/O error occurred while reading from or writing to the file system during close(), it may return -1 with errno set to [EIO]; if this error is returned, the state of fildes is unspecified.
> 
> https://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html

On every OS I checked (NetBSD, FreeBSD, OpenBSD, Linux, illumos), the semantics is that after close(), the descriptor is closed, unconditionally, whether close() succeeded or failed -- even if this involves doing some I/O that may be interrupted by a signal which causes EINTR to come flying out of close().

Rather than simply endorse the reasonable semantics that probably every reasonable OS on the planet already implements, POSIX.1-2024 changed that language to:

> If close() is interrupted by a signal that is to be caught, then it is unspecified whether it returns -1 with errno set to [EINTR] and fildes remaining open, or returns -1 with errno set to [EINPROGRESS] and fildes being closed, or returns 0 to indicate successful completion; except that if POSIX_CLOSE_RESTART is defined as 0, then the option of returning -1 with errno set to [EINTR] and fildes remaining open shall not occur.
>
> [...]
>
> The posix_close() function shall be equivalent to the close() function, except with the modifications based on the flag argument as described below. If flag is 0, then posix_close() shall not return -1 with errno set to [EINTR], which implies that fildes will always be closed (except for [EBADF], where fildes was invalid). If flag includes POSIX_CLOSE_RESTART and POSIX_CLOSE_RESTART is defined as a non-zero value, and posix_close() is interrupted by a signal that is to be caught, then posix_close() may return -1 with errno set to [EINTR], in which case fildes shall be left open; however, it is unspecified whether fildes can subsequently be passed to any function except close() or posix_close() without error. If flag is invalid, posix_close() may fail with errno set to [EINVAL], but shall otherwise behave as if flag had been 0 and close fildes.
>
> [...]
>
> If POSIX_CLOSE_RESTART is zero, the close() function shall not return an [EINTR] error.
>
> https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/posix_close.html

This probably caters only to such obscure and likely indefensible niche proprietary operating systems that I doubt whether many applications in the real world will ever call posix_close(), especially given the mental gymnastics needed to understand the language describing it.  But it's there in POSIX so it would be reasonable for us to implement it, or if we don't, then or at least to document why we don't.
>How-To-Repeat:
read POSIX.1-2024 and take some aspirin for the headache it will cause
>Fix:
Since close() (a) may return EINTR, and (b) is unconditionally final, i.e., cannot be restarted even if it returns EINTR, we cannot simply #define POSIX_CLOSE_RESTART 0.

So, perhaps we can do this:

#define POSIX_CLOSE_RESTART 1

int
posix_close(int fildes, int flag)
{

	if (close(fildes) == -1 && errno != EINTR)
		return -1;

	case (flag) {
	case 0:
	case POSIX_CLOSE_RESTART:
		break;
	default:
		errno = EINVAL;
		return -1;
	}

	return 0;
}

This meets the following criteria:

> If flag is 0, then posix_close() shall not return -1 with errno set to [EINTR], which implies that fildes will always be closed (except for [EBADF], where fildes was invalid).

This posix_close() definition does not fail with EINTR.  fildes is always closed.

> If flag includes POSIX_CLOSE_RESTART and POSIX_CLOSE_RESTART is defined as a non-zero value, and posix_close() is interrupted by a signal that is to be caught, then posix_close() may return -1 with errno set to [EINTR], in which case fildes shall be left open; [...]

POSIX_CLOSE_RESTART is defined as a non-zero value, so this clause applies.  And while this clause says posix_close() MAY fail with EINTR, it does not say posix_close() MUST fail with EINTR if interrupted by a signal.  This posix_close() definition succeeds (returns 0) if interrupted by a signal, so it meets the requirement by never failing with EINTR and never leaving fildes open.

> If flag is invalid, posix_close() may fail with errno set to [EINVAL], but shall otherwise behave as if flag had been 0 and close fildes.

This checks flag for validity _after_ close(), so fildes is closed even if flag is invalid.  (EBADF and other possible close() failures like EIO take precedence over EINVAL.)
This posix_close() definition closes fildes even if the flag is invalid.



Home | Main Index | Thread Index | Old Index