The branch main has been updated by markj:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=0083a4d6224f1290af31528b55ba27c51e27ba46

commit 0083a4d6224f1290af31528b55ba27c51e27ba46
Author:     Mark Johnston <[email protected]>
AuthorDate: 2026-07-07 14:14:39 +0000
Commit:     Mark Johnston <[email protected]>
CommitDate: 2026-07-07 14:14:39 +0000

    tests/procdesc: Add some test cases for pdopenpid()
    
    Reviewed by:    kib
    MFC with:       5c32aa785184 ("kern: add pdopenpid(2)")
    Differential Revision:  https://reviews.freebsd.org/D58023
---
 tests/sys/kern/procdesc.c | 467 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 467 insertions(+)

diff --git a/tests/sys/kern/procdesc.c b/tests/sys/kern/procdesc.c
index 2fe50be45bf7..388104054b82 100644
--- a/tests/sys/kern/procdesc.c
+++ b/tests/sys/kern/procdesc.c
@@ -27,16 +27,21 @@
  */
 
 #include <sys/types.h>
+#include <sys/capsicum.h>
+#include <sys/resource.h>
 #include <sys/user.h>
 #include <sys/proc.h>
 #include <sys/procdesc.h>
 #include <sys/sysctl.h>
 #include <sys/wait.h>
 
+#include <errno.h>
 #include <fcntl.h>
 #include <poll.h>
 #include <pthread.h>
+#include <signal.h>
 #include <stdio.h>
+#include <string.h>
 #include <unistd.h>
 
 #include <atf-c.h>
@@ -205,11 +210,473 @@ ATF_TC_BODY(poll_exit_wakeup, tc)
        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 should fail in capability mode.
+ */
+ATF_TC_WITHOUT_HEAD(pdopenpid_capmode);
+ATF_TC_BODY(pdopenpid_capmode, tc)
+{
+       pid_t child, parent;
+
+       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_ERRNO(ECAPMODE, pdopenpid(child, 0) < 0);
+}
+
+/*
+ * 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, status;
+
+       child = pdfork(&fd1, PD_DAEMON);
+       ATF_REQUIRE_MSG(child >= 0, "pdfork: %s", strerror(errno));
+       if (child == 0)
+               _exit(42);
+
+       fd2 = pdopenpid(child, 0);
+       ATF_REQUIRE_MSG(fd2 >= 0, "pdopenpid: %s", strerror(errno));
+
+       /* 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 no longer be able to collect. */
+       ATF_REQUIRE_ERRNO(ESRCH, pdwait(fd2, &status, WEXITED, NULL, NULL) < 0);
+
+       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);
+}
+
 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_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);
+
        return (atf_no_error());
 }

Reply via email to