/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2026 ConnectWise * Copyright (c) 2026 Mark Johnston * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "freebsd_test_suite/macros.h" /* Tests for procdesc(4) that aren't specific to any one syscall */ /* * Block until a thread in the specified process is sleeping in the specified * wait message. */ static void wait_for_naptime(pid_t pid, const char *wmesg) { kvm_t *kd; int count; kd = kvm_openfiles(NULL, "/dev/null", NULL, O_RDONLY, NULL); ATF_REQUIRE(kd != NULL); for (;;) { struct kinfo_proc *kip; int i; usleep(1000); kip = kvm_getprocs(kd, KERN_PROC_PID | KERN_PROC_INC_THREAD, pid, &count); ATF_REQUIRE(kip != NULL); for (i = 0; i < count; i++) { ATF_REQUIRE(kip[i].ki_stat != SZOMB); if (kip[i].ki_stat == SSLEEP && strcmp(kip[i].ki_wmesg, wmesg) == 0) break; } if (i < count) break; } kvm_close(kd); } /* * Even after waiting on a process descriptor with waitpid(2), the kernel will * not recycle the pid until after the process descriptor is closed. That is * important to prevent users from trying to wait() twice, the second time * using a dangling pid. * * Whether this same anti-recycling behavior is used with pdwait() is * unimportant, because pdwait _always_ uses a process descriptor. */ ATF_TC_WITHOUT_HEAD(pid_recycle); ATF_TC_BODY(pid_recycle, tc) { size_t len; int i, pd, pid_max; pid_t dangle_pid; len = sizeof(pid_max); ATF_REQUIRE_EQ_MSG(0, sysctlbyname("kern.pid_max", &pid_max, &len, NULL, 0), "sysctlbyname: %s", strerror(errno)); /* Create a process descriptor */ dangle_pid = pdfork(&pd, PD_CLOEXEC | PD_DAEMON); ATF_REQUIRE_MSG(dangle_pid >= 0, "pdfork: %s", strerror(errno)); if (dangle_pid == 0) { // In child _exit(0); } /* * Reap the child, but don't close the pd, creating a dangling pid. * Notably, it isn't a Zombie, because the process is reaped. */ ATF_REQUIRE_EQ(dangle_pid, waitpid(dangle_pid, NULL, WEXITED)); /* * Now create and kill pid_max additional children. Test to see if pid * gets reused. If not, that means the kernel is correctly reserving * the dangling pid from reuse. */ for (i = 0; i < pid_max; i++) { pid_t pid; pid = vfork(); ATF_REQUIRE_MSG(pid >= 0, "vfork: %s", strerror(errno)); if (pid == 0) _exit(0); ATF_REQUIRE_MSG(pid != dangle_pid, "pid got recycled after %d forks", i); ATF_REQUIRE_EQ(pid, waitpid(pid, NULL, WEXITED)); } close(pd); } static void * poll_procdesc(void *arg) { struct pollfd pfd; pfd.fd = *(int *)arg; pfd.events = POLLHUP; (void)poll(&pfd, 1, 5000); return ((void *)(uintptr_t)pfd.revents); } /* * Regression test to exercise the case where a procdesc is closed while a * thread is poll()ing it. */ ATF_TC_WITHOUT_HEAD(poll_close_race); ATF_TC_BODY(poll_close_race, tc) { pthread_t thr; pid_t pid; uintptr_t revents; int error, pd; pid = pdfork(&pd, PD_DAEMON); ATF_REQUIRE_MSG(pid >= 0, "pdfork: %s", strerror(errno)); if (pid == 0) { pause(); _exit(0); } error = pthread_create(&thr, NULL, poll_procdesc, &pd); ATF_REQUIRE_MSG(error == 0, "pthread_create: %s", strerror(error)); wait_for_naptime(getpid(), "select"); ATF_REQUIRE_MSG(close(pd) == 0, "close: %s", strerror(errno)); error = pthread_join(thr, (void *)&revents); ATF_REQUIRE_MSG(error == 0, "pthread_join: %s", strerror(error)); ATF_REQUIRE_EQ(revents, POLLNVAL); } /* * Verify that poll(2) of a procdesc returns POLLHUP when the process exits. */ ATF_TC_WITHOUT_HEAD(poll_exit_wakeup); ATF_TC_BODY(poll_exit_wakeup, tc) { pthread_t thr; uintptr_t revents; pid_t pid; int error, pd; pid = pdfork(&pd, PD_DAEMON); ATF_REQUIRE_MSG(pid >= 0, "pdfork: %s", strerror(errno)); if (pid == 0) { pause(); _exit(0); } error = pthread_create(&thr, NULL, poll_procdesc, &pd); ATF_REQUIRE_MSG(error == 0, "pthread_create: %s", strerror(error)); wait_for_naptime(getpid(), "select"); ATF_REQUIRE_MSG(pdkill(pd, SIGKILL) == 0, "pdkill: %s", strerror(errno)); error = pthread_join(thr, (void *)&revents); ATF_REQUIRE_MSG(error == 0, "pthread_join: %s", strerror(error)); ATF_REQUIRE_EQ(revents, POLLHUP); ATF_REQUIRE_MSG(close(pd) == 0, "close: %s", strerror(errno)); } /* Tests for pdopenpid(2) */ /* * Basic: open a process descriptor for a child, verify pdgetpid() returns the * right pid. */ ATF_TC_WITHOUT_HEAD(pdopenpid_basic); ATF_TC_BODY(pdopenpid_basic, tc) { pid_t child, queried; int fd; child = fork(); ATF_REQUIRE_MSG(child >= 0, "fork: %s", strerror(errno)); if (child == 0) { for (;;) pause(); _exit(0); } fd = pdopenpid(child, 0); ATF_REQUIRE_MSG(fd >= 0, "pdopenpid: %s", strerror(errno)); ATF_REQUIRE_MSG(pdgetpid(fd, &queried) == 0, "pdgetpid: %s", strerror(errno)); ATF_REQUIRE_EQ(child, queried); ATF_REQUIRE_MSG(pdkill(fd, SIGKILL) == 0, "pdkill: %s", strerror(errno)); ATF_REQUIRE_EQ(child, waitpid(child, NULL, 0)); ATF_REQUIRE(close(fd) == 0); } /* * pdopenpid with PD_CLOEXEC should set the close-on-exec flag. */ ATF_TC_WITHOUT_HEAD(pdopenpid_cloexec); ATF_TC_BODY(pdopenpid_cloexec, tc) { pid_t child; int fd, flags; child = fork(); ATF_REQUIRE_MSG(child >= 0, "fork: %s", strerror(errno)); if (child == 0) { for (;;) pause(); _exit(0); } fd = pdopenpid(child, PD_CLOEXEC); ATF_REQUIRE_MSG(fd >= 0, "pdopenpid: %s", strerror(errno)); flags = fcntl(fd, F_GETFD); ATF_REQUIRE(flags >= 0); ATF_REQUIRE(flags & FD_CLOEXEC); ATF_REQUIRE_MSG(pdkill(fd, SIGKILL) == 0, "pdkill: %s", strerror(errno)); ATF_REQUIRE_EQ(child, waitpid(child, NULL, 0)); ATF_REQUIRE(close(fd) == 0); } /* * Invalid flags should return EINVAL. */ ATF_TC_WITHOUT_HEAD(pdopenpid_einval); ATF_TC_BODY(pdopenpid_einval, tc) { ATF_REQUIRE_ERRNO(EINVAL, pdopenpid(getpid(), 0xdeadbeef) < 0); ATF_REQUIRE_ERRNO(EINVAL, pdopenpid(getpid(), ~PD_ALLOWED_AT_FORK) < 0); } /* * Validate handling of EMFILE. */ ATF_TC_WITHOUT_HEAD(pdopenpid_emfile); ATF_TC_BODY(pdopenpid_emfile, tc) { pid_t child; struct rlimit rl; int fd; child = fork(); ATF_REQUIRE_MSG(child >= 0, "fork: %s", strerror(errno)); if (child == 0) { for (;;) pause(); _exit(0); } /* * Determine the lowest unused fd, then set the fd limit to that * value so that no new fds can be allocated. */ fd = dup(STDIN_FILENO); ATF_REQUIRE(fd >= 0); ATF_REQUIRE(close(fd) == 0); ATF_REQUIRE_EQ(getrlimit(RLIMIT_NOFILE, &rl), 0); rl.rlim_cur = fd; ATF_REQUIRE_EQ(setrlimit(RLIMIT_NOFILE, &rl), 0); ATF_REQUIRE_ERRNO(EMFILE, pdopenpid(child, 0) < 0); /* * The child should not have been killed or reparented as a side * effect of the failed syscall. */ ATF_REQUIRE_MSG(kill(child, 0) == 0, "child was killed: %s", strerror(errno)); ATF_REQUIRE_MSG(waitpid(child, NULL, WNOHANG) == 0, "child was reparented"); ATF_REQUIRE_MSG(kill(child, SIGKILL) == 0, "kill: %s", strerror(errno)); ATF_REQUIRE_EQ(child, waitpid(child, NULL, 0)); } /* * Opening a nonexistent pid should return ESRCH. */ ATF_TC_WITHOUT_HEAD(pdopenpid_esrch); ATF_TC_BODY(pdopenpid_esrch, tc) { ATF_REQUIRE_ERRNO(ESRCH, pdopenpid(123456789, 0) < 0); } /* * pdopenpid works for children in capability mode. */ ATF_TC_WITHOUT_HEAD(pdopenpid_child); ATF_TC_BODY(pdopenpid_child, tc) { pid_t child, parent; ATF_REQUIRE_FEATURE("security_capability_mode"); parent = getpid(); child = fork(); ATF_REQUIRE_MSG(child >= 0, "fork: %s", strerror(errno)); if (child == 0) { while (getppid() == parent) sleep(1); _exit(0); } ATF_REQUIRE_MSG(cap_enter() == 0, "cap_enter: %s", strerror(errno)); ATF_REQUIRE_MSG(pdopenpid(child, 0) >= 0, "pdopenpid: %s", strerror(errno)); } /* * pdopenpid should fail in capability mode. */ ATF_TC_WITHOUT_HEAD(pdopenpid_capmode); ATF_TC_BODY(pdopenpid_capmode, tc) { pid_t child; volatile pid_t grandchild; ATF_REQUIRE_FEATURE("security_capability_mode"); child = vfork(); ATF_REQUIRE_MSG(child >= 0, "fork: %s", strerror(errno)); if (child == 0) { grandchild = fork(); ATF_REQUIRE_MSG(grandchild >= 0, "fork: %s", strerror(errno)); if (grandchild == 0) { for (;;) sleep(1); } else { _exit(0); } } ATF_REQUIRE_MSG(cap_enter() == 0, "cap_enter: %s", strerror(errno)); ATF_REQUIRE_ERRNO(ECAPMODE, pdopenpid(grandchild, 0) < 0); kill(grandchild, SIGKILL); } /* * Open a process descriptor for a child that already has one from pdfork(). * Both fds should refer to the same process. */ ATF_TC_WITHOUT_HEAD(pdopenpid_pdfork_then_open); ATF_TC_BODY(pdopenpid_pdfork_then_open, tc) { pid_t child, pid1, pid2; int fd1, fd2; child = pdfork(&fd1, PD_DAEMON); ATF_REQUIRE_MSG(child >= 0, "pdfork: %s", strerror(errno)); if (child == 0) { for (;;) pause(); _exit(0); } fd2 = pdopenpid(child, 0); ATF_REQUIRE_MSG(fd2 >= 0, "pdopenpid: %s", strerror(errno)); ATF_REQUIRE(pdgetpid(fd1, &pid1) == 0); ATF_REQUIRE(pdgetpid(fd2, &pid2) == 0); ATF_REQUIRE_EQ(pid1, pid2); ATF_REQUIRE_EQ(child, pid1); /* Kill via the second fd, wait via the first. */ ATF_REQUIRE_MSG(pdkill(fd2, SIGKILL) == 0, "pdkill: %s", strerror(errno)); ATF_REQUIRE_MSG(pdwait(fd1, NULL, WEXITED, NULL, NULL) == 0, "pdwait: %s", strerror(errno)); ATF_REQUIRE(close(fd1) == 0); ATF_REQUIRE(close(fd2) == 0); } /* * Open a process descriptor for a child created with fork(), which has no * pre-existing procdesc. Then use pdkill() and pdwait() on it. */ ATF_TC_WITHOUT_HEAD(pdopenpid_fork_then_open); ATF_TC_BODY(pdopenpid_fork_then_open, tc) { pid_t child; int fd, status; child = fork(); ATF_REQUIRE_MSG(child >= 0, "fork: %s", strerror(errno)); if (child == 0) { for (;;) pause(); _exit(0); } fd = pdopenpid(child, 0); ATF_REQUIRE_MSG(fd >= 0, "pdopenpid: %s", strerror(errno)); ATF_REQUIRE_MSG(pdkill(fd, SIGKILL) == 0, "pdkill: %s", strerror(errno)); ATF_REQUIRE_MSG(pdwait(fd, &status, WEXITED, NULL, NULL) == 0, "pdwait: %s", strerror(errno)); ATF_REQUIRE(WIFSIGNALED(status)); ATF_REQUIRE_EQ(WTERMSIG(status), SIGKILL); ATF_REQUIRE(close(fd) == 0); } /* * Closing one fd should not kill the process when another fd still references * the same procdesc. */ ATF_TC_WITHOUT_HEAD(pdopenpid_close_one_of_two); ATF_TC_BODY(pdopenpid_close_one_of_two, tc) { pid_t child, queried; int fd1, fd2, status; child = pdfork(&fd1, PD_DAEMON); ATF_REQUIRE_MSG(child >= 0, "pdfork: %s", strerror(errno)); if (child == 0) { for (;;) pause(); _exit(0); } fd2 = pdopenpid(child, 0); ATF_REQUIRE_MSG(fd2 >= 0, "pdopenpid: %s", strerror(errno)); /* Close the first fd; the process should remain alive. */ ATF_REQUIRE(close(fd1) == 0); /* * Verify the process is still reachable via the second fd and still * alive. */ ATF_REQUIRE_EQ(0, pdgetpid(fd2, &queried)); ATF_REQUIRE_EQ(child, queried); ATF_REQUIRE_MSG(pdkill(fd2, 0) == 0, "pdkill(0) after closing first fd: %s", strerror(errno)); /* Now kill and reap via the second fd. */ ATF_REQUIRE_MSG(pdkill(fd2, SIGKILL) == 0, "pdkill: %s", strerror(errno)); ATF_REQUIRE_MSG(pdwait(fd2, &status, WEXITED, NULL, NULL) == 0, "pdwait: %s", strerror(errno)); ATF_REQUIRE(WIFSIGNALED(status)); ATF_REQUIRE_EQ(WTERMSIG(status), SIGKILL); ATF_REQUIRE(close(fd2) == 0); } /* * Two calls to pdopenpid for the same process (no pdfork) should both work. */ ATF_TC_WITHOUT_HEAD(pdopenpid_open_twice); ATF_TC_BODY(pdopenpid_open_twice, tc) { pid_t child, pid1, pid2; int fd1, fd2; child = fork(); ATF_REQUIRE_MSG(child >= 0, "fork: %s", strerror(errno)); if (child == 0) { for (;;) pause(); _exit(0); } fd1 = pdopenpid(child, 0); ATF_REQUIRE_MSG(fd1 >= 0, "pdopenpid(1): %s", strerror(errno)); fd2 = pdopenpid(child, 0); ATF_REQUIRE_MSG(fd2 >= 0, "pdopenpid(2): %s", strerror(errno)); ATF_REQUIRE_EQ(0, pdgetpid(fd1, &pid1)); ATF_REQUIRE_EQ(0, pdgetpid(fd2, &pid2)); ATF_REQUIRE_EQ(pid1, pid2); /* Close the first, process should survive. */ ATF_REQUIRE(close(fd1) == 0); ATF_REQUIRE_MSG(pdkill(fd2, 0) == 0, "pdkill(0) after closing first fd: %s", strerror(errno)); ATF_REQUIRE_MSG(pdkill(fd2, SIGKILL) == 0, "pdkill: %s", strerror(errno)); ATF_REQUIRE_MSG(pdwait(fd2, NULL, WEXITED, NULL, NULL) == 0, "pdwait: %s", strerror(errno)); ATF_REQUIRE(close(fd2) == 0); } /* * When a process with two procdesc fds exits, only one pdwait should succeed * in collecting the exit status. The other should get ESRCH. */ ATF_TC_WITHOUT_HEAD(pdopenpid_pdwait_only_one); ATF_TC_BODY(pdopenpid_pdwait_only_one, tc) { pid_t child; int fd1, fd2, pip[2], status; ATF_REQUIRE_EQ(pipe(pip), 0); child = pdfork(&fd1, PD_DAEMON); ATF_REQUIRE_MSG(child >= 0, "pdfork: %s", strerror(errno)); if (child == 0) { char c; close(pip[1]); /* Block until the parent has opened the second fd. */ (void)read(pip[0], &c, 1); _exit(42); } ATF_REQUIRE(close(pip[0]) == 0); /* Open the second fd while the child is still alive. */ fd2 = pdopenpid(child, 0); ATF_REQUIRE_MSG(fd2 >= 0, "pdopenpid: %s", strerror(errno)); /* Release the child so that it exits. */ ATF_REQUIRE(close(pip[1]) == 0); /* Collect via the first fd. */ ATF_REQUIRE_MSG(pdwait(fd1, &status, WEXITED, NULL, NULL) == 0, "pdwait(fd1): %s", strerror(errno)); ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == 42); /* The second fd should be able to collect as well. */ ATF_REQUIRE_MSG(pdwait(fd2, &status, WEXITED, NULL, NULL) == 0, "pdwait(fd2): %s", strerror(errno)); ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == 42); ATF_REQUIRE(close(fd1) == 0); ATF_REQUIRE(close(fd2) == 0); } /* * Opening a process descriptor for ourselves should work. */ ATF_TC_WITHOUT_HEAD(pdopenpid_self); ATF_TC_BODY(pdopenpid_self, tc) { pid_t child, queried; int childfd, fd, status; /* * Closing self-referencing procdesc would steal the exit * status from the parent. Keep one more procdesc for the * child in the parent so that we can query the state. * * For the same reason we must use fork() and test in the * child, otherwise kyua does not get the SIGCHILD nor the * exit status. */ child = pdfork(&childfd, 0); if (child == 0) { fd = pdopenpid(getpid(), PD_DAEMON); if (fd < 0) _exit(21); if (pdgetpid(fd, &queried) == -1) _exit(22); if (getpid() != queried) _exit(23); if (close(fd) != 0) _exit(24); _exit(0); } ATF_REQUIRE(pdgetpid(childfd, &queried) == 0); ATF_REQUIRE_EQ(queried, child); ATF_REQUIRE(pdwait(childfd, &status, WEXITED, NULL, NULL) == 0); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(WEXITSTATUS(status), 0); ATF_REQUIRE(close(childfd) == 0); } /* * pdopenpid for a process that is exiting (P_WEXIT set) should fail with EBUSY. */ ATF_TC_WITHOUT_HEAD(pdopenpid_exiting); ATF_TC_BODY(pdopenpid_exiting, tc) { pid_t child; int fd, pip[2], status; ATF_REQUIRE_EQ(pipe(pip), 0); child = fork(); ATF_REQUIRE_MSG(child >= 0, "fork: %s", strerror(errno)); if (child == 0) { char c; close(pip[1]); (void)read(pip[0], &c, 1); _exit(0); } ATF_REQUIRE(close(pip[0]) == 0); /* Tell the child to exit. */ ATF_REQUIRE(close(pip[1]) == 0); /* Might still race. */ usleep(1000); fd = pdopenpid(child, 0); if (fd >= 0) { ATF_REQUIRE(close(fd) == 0); } else { ATF_REQUIRE_MSG(errno == EBUSY, "unexpected errno %d (%s)", errno, strerror(errno)); } ATF_REQUIRE(waitpid(child, &status, 0) == child); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(WEXITSTATUS(status), 0); } /* * Make sure that opening a process descriptor doesn't cause a wakeup in the * target process. */ ATF_TC_WITHOUT_HEAD(pdopenpid_no_wakeup); ATF_TC_BODY(pdopenpid_no_wakeup, tc) { pid_t child; int fd, pip[2]; ATF_REQUIRE(pipe(pip) == 0); child = fork(); ATF_REQUIRE_MSG(child >= 0, "fork: %s", strerror(errno)); if (child == 0) { char buf[1]; close(pip[1]); (void)read(pip[0], buf, 1); _exit(0); } close(pip[0]); wait_for_naptime(child, "piperd"); fd = pdopenpid(child, 0); ATF_REQUIRE_MSG(fd >= 0, "pdopenpid: %s", strerror(errno)); /* * If pdopenpid() caused a wakeup, read() returned and the child exited. */ wait_for_naptime(child, "piperd"); ATF_REQUIRE(close(pip[1]) == 0); ATF_REQUIRE(close(fd) == 0); } /* * basic pddupfd functionality */ #define EXPECTED_OFFSET 123 ATF_TC_WITHOUT_HEAD(pddupfd_basic); ATF_TC_BODY(pddupfd_basic, tc) { int *comm; struct __wrusage wu; struct __siginfo si; int fd, pp, rfd, status, workfd, r, r1; off_t off; pid_t child; comm = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); ATF_REQUIRE(comm != MAP_FAILED); atomic_store_int(comm, -1); child = fork(); ATF_REQUIRE(child != -1); if (child == 0) { workfd = open("/etc/passwd", O_RDONLY); ATF_REQUIRE(workfd != -1); atomic_store_int(comm, workfd); while ((r = (int)atomic_load_int(comm)) >= 0) sleep(1); lseek(workfd, EXPECTED_OFFSET, SEEK_SET); r -= 1; atomic_store_int(comm, r); while (r == (int)atomic_load_int(comm)) sleep(1); _exit(0x23); } fd = pdopenpid(child, 0); ATF_REQUIRE(fd != -1); ATF_REQUIRE(pdgetpid(fd, &pp) == 0); while ((rfd = (int)atomic_load_int(comm)) == -1) ; ATF_REQUIRE(rfd >= 0); workfd = pddupfd(fd, rfd, 0); ATF_REQUIRE(workfd != -1); /* * pddupfd-ed file must dup-ed, i.e. the underlying file must * be same between the remote and local filedescriptors. * Check that the initial seek offset is zero. Then observe * the updated offset, which is only possible if the file is * indeed shared. */ off = lseek(workfd, 0, SEEK_CUR); ATF_REQUIRE_EQ(off, 0); r = -1; atomic_store_int(comm, r); while ((r1 = (int)atomic_load_int(comm)) == r) sleep(1); off = lseek(workfd, 0, SEEK_CUR); ATF_REQUIRE_EQ(off, EXPECTED_OFFSET); r1 -= 1; atomic_store_int(comm, r1); ATF_REQUIRE(close(workfd) == 0); ATF_REQUIRE(pdwait(fd, &status, WEXITED, &wu, &si) != -1); ATF_REQUIRE(close(fd) == 0); } ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, pid_recycle); ATF_TP_ADD_TC(tp, poll_close_race); ATF_TP_ADD_TC(tp, poll_exit_wakeup); ATF_TP_ADD_TC(tp, pdopenpid_basic); ATF_TP_ADD_TC(tp, pdopenpid_cloexec); ATF_TP_ADD_TC(tp, pdopenpid_einval); ATF_TP_ADD_TC(tp, pdopenpid_emfile); ATF_TP_ADD_TC(tp, pdopenpid_esrch); ATF_TP_ADD_TC(tp, pdopenpid_child); ATF_TP_ADD_TC(tp, pdopenpid_capmode); ATF_TP_ADD_TC(tp, pdopenpid_pdfork_then_open); ATF_TP_ADD_TC(tp, pdopenpid_fork_then_open); ATF_TP_ADD_TC(tp, pdopenpid_close_one_of_two); ATF_TP_ADD_TC(tp, pdopenpid_open_twice); ATF_TP_ADD_TC(tp, pdopenpid_pdwait_only_one); ATF_TP_ADD_TC(tp, pdopenpid_self); ATF_TP_ADD_TC(tp, pdopenpid_exiting); ATF_TP_ADD_TC(tp, pdopenpid_no_wakeup); ATF_TP_ADD_TC(tp, pddupfd_basic); return (atf_no_error()); }