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 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 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 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