xref: /illumos-gate/usr/src/test/os-tests/tests/oclo/ocloexec_verify.c (revision b3ff81dc6673bee7f291d9d66a832cb3e1004f49)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2025 Oxide Computer Company
14  */
15 
16 /*
17  * Verify that our file descriptors starting after stderr are correct based upon
18  * the series of passed in arguments from the 'oclo' program. Arguments are
19  * passed as a string that represents the flags that were originally verified
20  * pre-fork/exec via fcntl(F_GETFD). In addition, anything that was originally
21  * closed because it had FD_CLOFORK set was reopened with the same flags. This
22  * allows us to verify that the combinations worked and that FD_CLOFORK was
23  * properly cleared.
24  */
25 
26 #include <err.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <fcntl.h>
30 #include <stdbool.h>
31 #include <errno.h>
32 #include <string.h>
33 
34 static int
verify_fdwalk_cb(void * arg,int fd)35 verify_fdwalk_cb(void *arg, int fd)
36 {
37 	int *max = arg;
38 	*max = fd;
39 	return (0);
40 }
41 
42 /*
43  * Our flags may have FD_CLOFORK set in them (anything with FD_CLOEXEC Should
44  * not exist by definition). FD_CLOFORK is supposed to be cleared on exec. We
45  * still indicate which file descriptors FD_CLOFORK so we can check where it
46  * wasn't cleared.
47  */
48 static bool
verify_flags(int fd,int exp_flags)49 verify_flags(int fd, int exp_flags)
50 {
51 	bool fail = (exp_flags & FD_CLOEXEC) != 0;
52 	int flags = fcntl(fd, F_GETFD, NULL);
53 	bool clofork = (exp_flags & FD_CLOFORK) != 0;
54 	exp_flags &= ~FD_CLOFORK;
55 
56 	if (flags < 0) {
57 		int e = errno;
58 
59 		if (fail) {
60 			if (e == EBADF) {
61 				(void) printf("TEST PASSED: post-exec fd %d: "
62 				    "flags 0x%x: correctly closed\n", fd,
63 				    exp_flags);
64 				return (true);
65 			}
66 
67 
68 			warn("TEST FAILED: post-fork fd %d: expected fcntl to "
69 			    "fail with EBADF, but found %s", fd,
70 			    strerrorname_np(e));
71 			return (false);
72 		}
73 
74 		warnx("TEST FAILED: post-fork fd %d: fcntl(F_GETFD) "
75 		    "unexpectedly failed with %s, expected flags %d", fd,
76 		    strerrorname_np(e), exp_flags);
77 		return (false);
78 	}
79 
80 	if (fail) {
81 		warnx("TEST FAILED: post-fork fd %d: received flags %d, but "
82 		    "expected to fail based on flags %d", fd, flags, exp_flags);
83 		return (false);
84 	}
85 
86 	if (clofork && (flags & FD_CLOFORK) != 0) {
87 		warnx("TEST FAILED: post-fork fd %d (flags %d) retained "
88 		    "FD_CLOFORK, but it should have been cleared", fd, flags);
89 		return (false);
90 	}
91 
92 	if (flags != exp_flags) {
93 		warnx("TEST FAILED: post-exec fd %d: discovered flags 0x%x do "
94 		    "not match expected flags 0x%x", fd, flags, exp_flags);
95 		return (false);
96 	}
97 
98 	(void) printf("TEST PASSED: post-exec fd %d: flags 0x%x: successfully "
99 	    "matched\n", fd, exp_flags);
100 	return (true);
101 }
102 
103 int
main(int argc,char * argv[])104 main(int argc, char *argv[])
105 {
106 	int maxfd = STDIN_FILENO;
107 	int ret = EXIT_SUCCESS;
108 
109 	/*
110 	 * We should have one argument for each fd we found, ignoring stdin,
111 	 * stdout, and stderr. argc will also have an additional entry for our
112 	 * program name, which we want to skip. Note, the last fd may not exist
113 	 * because it was marked for close, hence the use of '>' below.
114 	 */
115 	(void) fdwalk(verify_fdwalk_cb, &maxfd);
116 	if (maxfd - 3 > argc - 1) {
117 		errx(EXIT_FAILURE, "TEST FAILED: found more fds %d than "
118 		    "arguments %d", maxfd - 3, argc - 1);
119 	}
120 
121 	for (int i = 1; i < argc; i++) {
122 		const char *errstr;
123 		int targ_fd = i + STDERR_FILENO;
124 		long long targ_flags = strtonumx(argv[i], 0,
125 		    FD_CLOEXEC | FD_CLOFORK, &errstr, 0);
126 
127 		if (errstr != NULL) {
128 			errx(EXIT_FAILURE, "TEST FAILED: failed to parse "
129 			    "argument %d: %s is %s", i, argv[i], errstr);
130 		}
131 
132 		if (!verify_flags(targ_fd, (int)targ_flags))
133 			ret = EXIT_FAILURE;
134 	}
135 
136 	return (ret);
137 }
138