Skip to content

Fix file descriptor cleanup on OpenBSD - #571

Open
fabienr wants to merge 1 commit into
boostorg:developfrom
fabienr:openbsd-closefrom
Open

fabienr wants to merge 1 commit into
boostorg:developfrom
fabienr:openbsd-closefrom

Conversation

@fabienr

@fabienr fabienr commented Oct 21, 2026

Copy link
Copy Markdown

OpenBSD's /dev/fd contains static device nodes rather than a list of open descriptors. Enumerating it can encounter closed descriptors and miss open descriptors beyond the available nodes.

In Process v2, use close() between retained descriptors and closefrom() above the last one, avoiding /dev/fd enumeration.

For Process v1, deliberately keep the fix minimal: ignore EBADF and retry close() on EINTR, rather than rewriting the cleanup algorithm to follow the v2 approach. This prevents limit_handles failures but leaves descriptors missing from /dev/fd unhandled.

continue;

if (::close(conv) != 0)
while (::close(conv) == -1 && errno != EBADF)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

::close(conv) != 0 maybe ? It seems more portable and preserves the original code.

The same remark applies to V2, but this one is specific to openbsd atm.

Any opinion ?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think just ignoring BADF is the right call, I don't like the while loop there, just for readability. Maybe we should do it like this?

if (::close(conv) != 0 && errno != EBADF)
{
   while (errno == EINTR && close(conv) == -1);
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just notice the change I made isn't correct in cases of EINTR.
Also, the loop call close() instead of ::close().
Actually, you mean we should drop exec.set_error() ?

Then we don't need if { while() } construct, just this ?
while (::close(conv) == -1 && errno == EINTR);

OpenBSD's /dev/fd contains static device nodes rather than a list of
open descriptors. Enumerating it can encounter closed descriptors and
miss open descriptors beyond the available nodes.

In Process v2, use close() between retained descriptors and closefrom()
above the last one, avoiding /dev/fd enumeration.

For Process v1, deliberately keep the fix minimal: ignore EBADF and
retry close() on EINTR, rather than rewriting the cleanup algorithm
to follow the v2 approach. This prevents limit_handles failures but
leaves descriptors missing from /dev/fd unhandled.
// gives us
//
// int closefrom(int fd);
// void closefrom(int lowfd);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just notice that FreeBSD closefrom differ from OpenBSD; see the comment below

{
errno = 0;
::closefrom(whitelist.back() + 1);
} while (errno == EINTR);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is trade-off to handle both FreeBSD and OpenBSD implementation.

Initially this was meant to be small; should we implement yet another close_all() ?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should be #if-defed, because resetting errno like that is very uncommon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants