Skip to content

Add support for POSIX O_CLOFORK #1698

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,369 changes: 1,369 additions & 0 deletions cddl/contrib/opensolaris/tests/os-tests/tests/oclo/oclo.c

Large diffs are not rendered by default.

202 changes: 202 additions & 0 deletions cddl/contrib/opensolaris/tests/os-tests/tests/oclo/oclo_errors.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/*
* This file and its contents are supplied under the terms of the
* Common Development and Distribution License ("CDDL"), version 1.0.
* You may only use this file in accordance with the terms of version
* 1.0 of the CDDL.
*
* A full copy of the text of the CDDL should have accompanied this
* source. A copy of the CDDL is also available via the Internet at
* http://www.illumos.org/license/CDDL.
*/

/*
* Copyright 2024 Oxide Computer Company
*/

/*
* Verify that unsupported flags will properly generate errors across the
* functions that we know perform strict error checking. This includes:
*
* o fcntl(..., F_DUP3FD, ...)
* o dup3()
* o pipe2()
* o socket()
* o accept4()
*/

#include <netinet/in.h>
#include <sys/socket.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define strerrorname_np(e) (sys_errlist[e])

static bool
oclo_check(const char *desc, const char *act, int ret, int e)
{
if (ret >= 0) {
warnx("TEST FAILED: %s: fd was %s!", desc, act);
return (false);
} else if (errno != EINVAL) {
e = errno;
warnx("TEST FAILED: %s: failed with %s, expected "
"EINVAL", desc, strerrorname_np(e));
return (false);
}

(void) printf("TEST PASSED: %s: correctly failed with EINVAL\n",
desc);
return (true);
}

static bool
oclo_dup3(const char *desc, int flags)
{
int fd = dup3(STDERR_FILENO, 23, flags);
return (oclo_check(desc, "duplicated", fd, errno));
}

static bool
oclo_dup3fd(const char *desc, int flags)
{
int fd = fcntl(STDERR_FILENO, F_DUP3FD | (flags << F_DUP3FD_SHIFT), 23);
return (oclo_check(desc, "duplicated", fd, errno));
}


static bool
oclo_pipe2(const char *desc, int flags)
{
int fds[2], ret;

ret = pipe2(fds, flags);
return (oclo_check(desc, "piped", ret, errno));
}

#if 0
static bool
oclo_socket(const char *desc, int type)
{
int fd = socket(PF_UNIX, SOCK_STREAM | type, 0);
return (oclo_check(desc, "created", fd, errno));
}
#endif

static bool
oclo_accept(const char *desc, int flags)
{
int sock, fd, e;
struct sockaddr_in in;

sock = socket(PF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (sock < 0) {
warn("TEST FAILED: %s: failed to create listen socket", desc);
return (false);
}

(void) memset(&in, 0, sizeof (in));
in.sin_family = AF_INET;
in.sin_port = 0;
in.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

if (bind(sock, (struct sockaddr *)&in, sizeof (in)) != 0) {
warn("TEST FAILED: %s: failed to bind socket", desc);
(void) close(sock);
return (false);
}

if (listen(sock, 5) < 0) {
warn("TEST FAILED: %s: failed to listen on socket", desc);
(void) close(sock);
return (false);
}


fd = accept4(sock, NULL, NULL, flags);
e = errno;
(void) close(sock);
return (oclo_check(desc, "accepted", fd, e));
}

int
main(void)
{
int ret = EXIT_SUCCESS;

closefrom(STDERR_FILENO + 1);

if (!oclo_dup3("dup3(): O_RDWR", O_RDWR)) {
ret = EXIT_FAILURE;
}

if (!oclo_dup3("dup3(): O_NONBLOCK|O_CLOXEC", O_NONBLOCK | O_CLOEXEC)) {
ret = EXIT_FAILURE;
}

if (!oclo_dup3("dup3(): O_CLOFORK|O_WRONLY", O_CLOFORK | O_WRONLY)) {
ret = EXIT_FAILURE;
}

if (!oclo_dup3fd("fcntl(FDUP3FD): 0x7777", 0x7777)) {
ret = EXIT_FAILURE;
}

if (!oclo_dup3fd("fcntl(FDUP3FD): FD_CLOEXEC|FD_CLOFORK + 1",
(FD_CLOEXEC | FD_CLOFORK) + 1)) {
ret = EXIT_FAILURE;
}

if (!oclo_dup3fd("fcntl(FDUP3FD): INT_MAX", INT_MAX)) {
ret = EXIT_FAILURE;
}


if (!oclo_pipe2("pipe2(): O_RDWR", O_RDWR)) {
ret = EXIT_FAILURE;
}

if (!oclo_pipe2("pipe2(): O_SYNC|O_CLOXEC", O_SYNC | O_CLOEXEC)) {
ret = EXIT_FAILURE;
}

if (!oclo_pipe2("pipe2(): O_CLOFORK|O_WRONLY", O_CLOFORK | O_WRONLY)) {
ret = EXIT_FAILURE;
}

if (!oclo_pipe2("pipe2(): INT32_MAX", INT32_MAX)) {
ret = EXIT_FAILURE;
}

#if 0 /* These tests are known to fail on FreeBSD */
if (!oclo_socket("socket(): INT32_MAX", INT32_MAX)) {
ret = EXIT_FAILURE;
}

if (!oclo_socket("socket(): 3 << 25", 3 << 25)) {
ret = EXIT_FAILURE;
}
#endif

if (!oclo_accept("accept4(): INT32_MAX", INT32_MAX)) {
ret = EXIT_FAILURE;
}

if (!oclo_accept("accept4(): 3 << 25", 3 << 25)) {
ret = EXIT_FAILURE;
}

if (ret == EXIT_SUCCESS) {
(void) printf("All tests completed successfully\n");
}

return (ret);
}
139 changes: 139 additions & 0 deletions cddl/contrib/opensolaris/tests/os-tests/tests/oclo/ocloexec_verify.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* This file and its contents are supplied under the terms of the
* Common Development and Distribution License ("CDDL"), version 1.0.
* You may only use this file in accordance with the terms of version
* 1.0 of the CDDL.
*
* A full copy of the text of the CDDL should have accompanied this
* source. A copy of the CDDL is also available via the Internet at
* http://www.illumos.org/license/CDDL.
*/

/*
* Copyright 2024 Oxide Computer Company
*/

/*
* Verify that our file descriptors starting after stderr are correct based upon
* the series of passed in arguments from the 'oclo' program. Arguments are
* passed as a string that represents the flags that were originally verified
* pre-fork/exec via fcntl(F_GETFD). In addition, anything that was originally
* closed because it had FD_CLOFORK set was reopened with the same flags so we
* can verify that things with only FD_CLOFORK survive exec.
*/

#include <sys/types.h>
#include <sys/user.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libutil.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define strerrorname_np(e) (sys_errlist[e])

static int
getmaxfd(void)
{
struct kinfo_file *files;
int i, cnt, max;

if ((files = kinfo_getfile(getpid(), &cnt)) == NULL)
err(1, "kinfo_getfile");

max = -1;
for (i = 0; i < cnt; i++)
if (files[i].kf_fd > max)
max = files[i].kf_fd;

free(files);
return (max);
}

static bool
verify_flags(int fd, int exp_flags)
{
bool fail = (exp_flags & FD_CLOEXEC) != 0;
int flags = fcntl(fd, F_GETFD, NULL);

if (flags < 0) {
int e = errno;

if (fail) {
if (e == EBADF) {
(void) printf("TEST PASSED: post-exec fd %d: "
"flags 0x%x: correctly closed\n", fd,
exp_flags);
return (true);
}


warn("TEST FAILED: post-fork fd %d: expected fcntl to "
"fail with EBADF, but found %s", fd,
strerrorname_np(e));
return (false);
}

warnx("TEST FAILED: post-fork fd %d: fcntl(F_GETFD) "
"unexpectedly failed with %s, expected flags %d", fd,
strerrorname_np(e), exp_flags);
return (false);
}

if (fail) {
warnx("TEST FAILED: post-fork fd %d: received flags %d, but "
"expected to fail based on flags %d", fd, flags, exp_flags);
return (false);
}

if (flags != exp_flags) {
warnx("TEST FAILED: post-exec fd %d: discovered flags 0x%x do "
"not match expected flags 0x%x", fd, flags, exp_flags);
return (false);
}

(void) printf("TEST PASSED: post-exec fd %d: flags 0x%x: successfully "
"matched\n", fd, exp_flags);
return (true);
}

int
main(int argc, char *argv[])
{
int maxfd;
int ret = EXIT_SUCCESS;

/*
* We should have one argument for each fd we found, ignoring stdin,
* stdout, and stderr. argc will also have an additional entry for our
* program name, which we want to skip. Note, the last fd may not exist
* because it was marked for close, hence the use of '>' below.
*/
maxfd = getmaxfd();
if (maxfd - 3 > argc - 1) {
errx(EXIT_FAILURE, "TEST FAILED: found more fds %d than "
"arguments %d", maxfd - 3, argc - 1);
}

for (int i = 1; i < argc; i++) {
char *endptr;
int targ_fd = i + STDERR_FILENO;
errno = 0;
long long val = strtoll(argv[i], &endptr, 0);

if (errno != 0 || *endptr != '\0' ||
(val < 0 || val > (FD_CLOEXEC | FD_CLOFORK))) {
errx(EXIT_FAILURE, "TEST FAILED: failed to parse "
"argument %d: %s", i, argv[i]);
}

if (!verify_flags(targ_fd, (int)val))
ret = EXIT_FAILURE;
}

return (ret);
}
15 changes: 13 additions & 2 deletions lib/libc/gen/dup3.3
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd August 16, 2013
.Dd May 17, 2025
.Dt DUP3 3
.Os
.Sh NAME
Expand All @@ -47,6 +47,11 @@ The close-on-exec flag on the new file descriptor is determined by the
bit in
.Fa flags .
.Pp
The close-on-fork flag on the new file descriptor is determined by the
.Dv O_CLOFORK
bit in
.Fa flags .
.Pp
If
.Fa oldd
\*(Ne
Expand Down Expand Up @@ -91,7 +96,9 @@ argument.
The
.Fa flags
argument has bits set other than
.Dv O_CLOEXEC .
.Dv O_CLOEXEC
or
.Dv O_CLOFORK .
.El
.Sh SEE ALSO
.Xr accept 2 ,
Expand All @@ -112,3 +119,7 @@ The
.Fn dup3
function appeared in
.Fx 10.0 .
The
.Dv O_CLOFORK
flag appeared in
.Fx 15.0 .
Loading