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 2024 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 so we
22 * can verify that things with only FD_CLOFORK survive exec.
23 */
24
25 #include <err.h>
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29 #include <stdbool.h>
30 #include <errno.h>
31 #include <string.h>
32
33 static int
verify_fdwalk_cb(void * arg,int fd)34 verify_fdwalk_cb(void *arg, int fd)
35 {
36 int *max = arg;
37 *max = fd;
38 return (0);
39 }
40
41 static bool
verify_flags(int fd,int exp_flags)42 verify_flags(int fd, int exp_flags)
43 {
44 bool fail = (exp_flags & FD_CLOEXEC) != 0;
45 int flags = fcntl(fd, F_GETFD, NULL);
46
47 if (flags < 0) {
48 int e = errno;
49
50 if (fail) {
51 if (e == EBADF) {
52 (void) printf("TEST PASSED: post-exec fd %d: "
53 "flags 0x%x: correctly closed\n", fd,
54 exp_flags);
55 return (true);
56 }
57
58
59 warn("TEST FAILED: post-fork fd %d: expected fcntl to "
60 "fail with EBADF, but found %s", fd,
61 strerrorname_np(e));
62 return (false);
63 }
64
65 warnx("TEST FAILED: post-fork fd %d: fcntl(F_GETFD) "
66 "unexpectedly failed with %s, expected flags %d", fd,
67 strerrorname_np(e), exp_flags);
68 return (false);
69 }
70
71 if (fail) {
72 warnx("TEST FAILED: post-fork fd %d: received flags %d, but "
73 "expected to fail based on flags %d", fd, flags, exp_flags);
74 return (false);
75 }
76
77 if (flags != exp_flags) {
78 warnx("TEST FAILED: post-exec fd %d: discovered flags 0x%x do "
79 "not match expected flags 0x%x", fd, flags, exp_flags);
80 return (false);
81 }
82
83 (void) printf("TEST PASSED: post-exec fd %d: flags 0x%x: successfully "
84 "matched\n", fd, exp_flags);
85 return (true);
86 }
87
88 int
main(int argc,char * argv[])89 main(int argc, char *argv[])
90 {
91 int maxfd = STDIN_FILENO;
92 int ret = EXIT_SUCCESS;
93
94 /*
95 * We should have one argument for each fd we found, ignoring stdin,
96 * stdout, and stderr. argc will also have an additional entry for our
97 * program name, which we want to skip. Note, the last fd may not exist
98 * because it was marked for close, hence the use of '>' below.
99 */
100 (void) fdwalk(verify_fdwalk_cb, &maxfd);
101 if (maxfd - 3 > argc - 1) {
102 errx(EXIT_FAILURE, "TEST FAILED: found more fds %d than "
103 "arguments %d", maxfd - 3, argc - 1);
104 }
105
106 for (int i = 1; i < argc; i++) {
107 const char *errstr;
108 int targ_fd = i + STDERR_FILENO;
109 long long targ_flags = strtonumx(argv[i], 0,
110 FD_CLOEXEC | FD_CLOFORK, &errstr, 0);
111
112 if (errstr != NULL) {
113 errx(EXIT_FAILURE, "TEST FAILED: failed to parse "
114 "argument %d: %s is %s", i, argv[i], errstr);
115 }
116
117 if (!verify_flags(targ_fd, (int)targ_flags))
118 ret = EXIT_FAILURE;
119 }
120
121 return (ret);
122 }
123