Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 23 Mar 2026 14:47:40 +0000
From:      Mariusz Zaborski <oshogbo@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: fce6921464b5 - main - Revert "capsicum-test: remove stale file"
Message-ID:  <69c1528c.1bbe1.69ce59f7@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by oshogbo:

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

commit fce6921464b5b21836a304d0460c69db4a6f3942
Author:     Mariusz Zaborski <oshogbo@FreeBSD.org>
AuthorDate: 2026-03-23 14:44:34 +0000
Commit:     Mariusz Zaborski <oshogbo@FreeBSD.org>
CommitDate: 2026-03-23 14:45:13 +0000

    Revert "capsicum-test: remove stale file"
    
    This was unintended, sorry.
    
    This reverts commit 20b99e3a147963c6ef715112bd38e349c7a5a459.
---
 tests/sys/capsicum/linux.cc | 1500 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1500 insertions(+)

diff --git a/tests/sys/capsicum/linux.cc b/tests/sys/capsicum/linux.cc
new file mode 100644
index 000000000000..091bf395237a
--- /dev/null
+++ b/tests/sys/capsicum/linux.cc
@@ -0,0 +1,1500 @@
+// Tests of Linux-specific functionality
+#ifdef __linux__
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/timerfd.h>
+#include <sys/signalfd.h>
+#include <sys/eventfd.h>
+#include <sys/epoll.h>
+#include <sys/inotify.h>
+#include <sys/fanotify.h>
+#include <sys/mman.h>
+#include <sys/capability.h>  // Requires e.g. libcap-dev package for POSIX.1e capabilities headers
+#include <linux/aio_abi.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+#include <linux/version.h>
+#include <poll.h>
+#include <sched.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <string>
+
+#include "capsicum.h"
+#include "syscalls.h"
+#include "capsicum-test.h"
+
+TEST(Linux, TimerFD) {
+  int fd = timerfd_create(CLOCK_MONOTONIC, 0);
+
+  cap_rights_t r_ro;
+  cap_rights_init(&r_ro, CAP_READ);
+  cap_rights_t r_wo;
+  cap_rights_init(&r_wo, CAP_WRITE);
+  cap_rights_t r_rw;
+  cap_rights_init(&r_rw, CAP_READ, CAP_WRITE);
+  cap_rights_t r_rwpoll;
+  cap_rights_init(&r_rwpoll, CAP_READ, CAP_WRITE, CAP_EVENT);
+
+  int cap_fd_ro = dup(fd);
+  EXPECT_OK(cap_fd_ro);
+  EXPECT_OK(cap_rights_limit(cap_fd_ro, &r_ro));
+  int cap_fd_wo = dup(fd);
+  EXPECT_OK(cap_fd_wo);
+  EXPECT_OK(cap_rights_limit(cap_fd_wo, &r_wo));
+  int cap_fd_rw = dup(fd);
+  EXPECT_OK(cap_fd_rw);
+  EXPECT_OK(cap_rights_limit(cap_fd_rw, &r_rw));
+  int cap_fd_all = dup(fd);
+  EXPECT_OK(cap_fd_all);
+  EXPECT_OK(cap_rights_limit(cap_fd_all, &r_rwpoll));
+
+  struct itimerspec old_ispec;
+  struct itimerspec ispec;
+  ispec.it_interval.tv_sec = 0;
+  ispec.it_interval.tv_nsec = 0;
+  ispec.it_value.tv_sec = 0;
+  ispec.it_value.tv_nsec = 100000000;  // 100ms
+  EXPECT_NOTCAPABLE(timerfd_settime(cap_fd_ro, 0, &ispec, NULL));
+  EXPECT_NOTCAPABLE(timerfd_settime(cap_fd_wo, 0, &ispec, &old_ispec));
+  EXPECT_OK(timerfd_settime(cap_fd_wo, 0, &ispec, NULL));
+  EXPECT_OK(timerfd_settime(cap_fd_rw, 0, &ispec, NULL));
+  EXPECT_OK(timerfd_settime(cap_fd_all, 0, &ispec, NULL));
+
+  EXPECT_NOTCAPABLE(timerfd_gettime(cap_fd_wo, &old_ispec));
+  EXPECT_OK(timerfd_gettime(cap_fd_ro, &old_ispec));
+  EXPECT_OK(timerfd_gettime(cap_fd_rw, &old_ispec));
+  EXPECT_OK(timerfd_gettime(cap_fd_all, &old_ispec));
+
+  // To be able to poll() for the timer pop, still need CAP_EVENT.
+  struct pollfd poll_fd;
+  for (int ii = 0; ii < 3; ii++) {
+    poll_fd.revents = 0;
+    poll_fd.events = POLLIN;
+    switch (ii) {
+    case 0: poll_fd.fd = cap_fd_ro; break;
+    case 1: poll_fd.fd = cap_fd_wo; break;
+    case 2: poll_fd.fd = cap_fd_rw; break;
+    }
+    // Poll immediately returns with POLLNVAL
+    EXPECT_OK(poll(&poll_fd, 1, 400));
+    EXPECT_EQ(0, (poll_fd.revents & POLLIN));
+    EXPECT_NE(0, (poll_fd.revents & POLLNVAL));
+  }
+
+  poll_fd.fd = cap_fd_all;
+  EXPECT_OK(poll(&poll_fd, 1, 400));
+  EXPECT_NE(0, (poll_fd.revents & POLLIN));
+  EXPECT_EQ(0, (poll_fd.revents & POLLNVAL));
+
+  EXPECT_OK(timerfd_gettime(cap_fd_all, &old_ispec));
+  EXPECT_EQ(0, old_ispec.it_value.tv_sec);
+  EXPECT_EQ(0, old_ispec.it_value.tv_nsec);
+  EXPECT_EQ(0, old_ispec.it_interval.tv_sec);
+  EXPECT_EQ(0, old_ispec.it_interval.tv_nsec);
+
+  close(cap_fd_all);
+  close(cap_fd_rw);
+  close(cap_fd_wo);
+  close(cap_fd_ro);
+  close(fd);
+}
+
+FORK_TEST(Linux, SignalFDIfSingleThreaded) {
+  if (force_mt) {
+    GTEST_SKIP() << "multi-threaded run clashes with signals";
+  }
+  pid_t me = getpid();
+  sigset_t mask;
+  sigemptyset(&mask);
+  sigaddset(&mask, SIGUSR1);
+
+  // Block signals before registering against a new signal FD.
+  EXPECT_OK(sigprocmask(SIG_BLOCK, &mask, NULL));
+  int fd = signalfd(-1, &mask, 0);
+  EXPECT_OK(fd);
+
+  cap_rights_t r_rs;
+  cap_rights_init(&r_rs, CAP_READ, CAP_SEEK);
+  cap_rights_t r_ws;
+  cap_rights_init(&r_ws, CAP_WRITE, CAP_SEEK);
+  cap_rights_t r_sig;
+  cap_rights_init(&r_sig, CAP_FSIGNAL);
+  cap_rights_t r_rssig;
+  cap_rights_init(&r_rssig, CAP_FSIGNAL, CAP_READ, CAP_SEEK);
+  cap_rights_t r_rssig_poll;
+  cap_rights_init(&r_rssig_poll, CAP_FSIGNAL, CAP_READ, CAP_SEEK, CAP_EVENT);
+
+  // Various capability variants.
+  int cap_fd_none = dup(fd);
+  EXPECT_OK(cap_fd_none);
+  EXPECT_OK(cap_rights_limit(cap_fd_none, &r_ws));
+  int cap_fd_read = dup(fd);
+  EXPECT_OK(cap_fd_read);
+  EXPECT_OK(cap_rights_limit(cap_fd_read, &r_rs));
+  int cap_fd_sig = dup(fd);
+  EXPECT_OK(cap_fd_sig);
+  EXPECT_OK(cap_rights_limit(cap_fd_sig, &r_sig));
+  int cap_fd_sig_read = dup(fd);
+  EXPECT_OK(cap_fd_sig_read);
+  EXPECT_OK(cap_rights_limit(cap_fd_sig_read, &r_rssig));
+  int cap_fd_all = dup(fd);
+  EXPECT_OK(cap_fd_all);
+  EXPECT_OK(cap_rights_limit(cap_fd_all, &r_rssig_poll));
+
+  struct signalfd_siginfo fdsi;
+
+  // Need CAP_READ to read the signal information
+  kill(me, SIGUSR1);
+  EXPECT_NOTCAPABLE(read(cap_fd_none, &fdsi, sizeof(struct signalfd_siginfo)));
+  EXPECT_NOTCAPABLE(read(cap_fd_sig, &fdsi, sizeof(struct signalfd_siginfo)));
+  int len = read(cap_fd_read, &fdsi, sizeof(struct signalfd_siginfo));
+  EXPECT_OK(len);
+  EXPECT_EQ(sizeof(struct signalfd_siginfo), (size_t)len);
+  EXPECT_EQ(SIGUSR1, (int)fdsi.ssi_signo);
+
+  // Need CAP_FSIGNAL to modify the signal mask.
+  sigemptyset(&mask);
+  sigaddset(&mask, SIGUSR1);
+  sigaddset(&mask, SIGUSR2);
+  EXPECT_OK(sigprocmask(SIG_BLOCK, &mask, NULL));
+  EXPECT_NOTCAPABLE(signalfd(cap_fd_none, &mask, 0));
+  EXPECT_NOTCAPABLE(signalfd(cap_fd_read, &mask, 0));
+  EXPECT_EQ(cap_fd_sig, signalfd(cap_fd_sig, &mask, 0));
+
+  // Need CAP_EVENT to get notification of a signal in poll(2).
+  kill(me, SIGUSR2);
+
+  struct pollfd poll_fd;
+  poll_fd.revents = 0;
+  poll_fd.events = POLLIN;
+  poll_fd.fd = cap_fd_sig_read;
+  EXPECT_OK(poll(&poll_fd, 1, 400));
+  EXPECT_EQ(0, (poll_fd.revents & POLLIN));
+  EXPECT_NE(0, (poll_fd.revents & POLLNVAL));
+
+  poll_fd.fd = cap_fd_all;
+  EXPECT_OK(poll(&poll_fd, 1, 400));
+  EXPECT_NE(0, (poll_fd.revents & POLLIN));
+  EXPECT_EQ(0, (poll_fd.revents & POLLNVAL));
+}
+
+TEST(Linux, EventFD) {
+  int fd = eventfd(0, 0);
+  EXPECT_OK(fd);
+
+  cap_rights_t r_rs;
+  cap_rights_init(&r_rs, CAP_READ, CAP_SEEK);
+  cap_rights_t r_ws;
+  cap_rights_init(&r_ws, CAP_WRITE, CAP_SEEK);
+  cap_rights_t r_rws;
+  cap_rights_init(&r_rws, CAP_READ, CAP_WRITE, CAP_SEEK);
+  cap_rights_t r_rwspoll;
+  cap_rights_init(&r_rwspoll, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_EVENT);
+
+  int cap_ro = dup(fd);
+  EXPECT_OK(cap_ro);
+  EXPECT_OK(cap_rights_limit(cap_ro, &r_rs));
+  int cap_wo = dup(fd);
+  EXPECT_OK(cap_wo);
+  EXPECT_OK(cap_rights_limit(cap_wo, &r_ws));
+  int cap_rw = dup(fd);
+  EXPECT_OK(cap_rw);
+  EXPECT_OK(cap_rights_limit(cap_rw, &r_rws));
+  int cap_all = dup(fd);
+  EXPECT_OK(cap_all);
+  EXPECT_OK(cap_rights_limit(cap_all, &r_rwspoll));
+
+  pid_t child = fork();
+  if (child == 0) {
+    // Child: write counter to eventfd
+    uint64_t u = 42;
+    EXPECT_NOTCAPABLE(write(cap_ro, &u, sizeof(u)));
+    EXPECT_OK(write(cap_wo, &u, sizeof(u)));
+    exit(HasFailure());
+  }
+
+  sleep(1);  // Allow child to write
+
+  struct pollfd poll_fd;
+  poll_fd.revents = 0;
+  poll_fd.events = POLLIN;
+  poll_fd.fd = cap_rw;
+  EXPECT_OK(poll(&poll_fd, 1, 400));
+  EXPECT_EQ(0, (poll_fd.revents & POLLIN));
+  EXPECT_NE(0, (poll_fd.revents & POLLNVAL));
+
+  poll_fd.fd = cap_all;
+  EXPECT_OK(poll(&poll_fd, 1, 400));
+  EXPECT_NE(0, (poll_fd.revents & POLLIN));
+  EXPECT_EQ(0, (poll_fd.revents & POLLNVAL));
+
+  uint64_t u;
+  EXPECT_NOTCAPABLE(read(cap_wo, &u, sizeof(u)));
+  EXPECT_OK(read(cap_ro, &u, sizeof(u)));
+  EXPECT_EQ(42, (int)u);
+
+  // Wait for the child.
+  int status;
+  EXPECT_EQ(child, waitpid(child, &status, 0));
+  int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+  EXPECT_EQ(0, rc);
+
+  close(cap_all);
+  close(cap_rw);
+  close(cap_wo);
+  close(cap_ro);
+  close(fd);
+}
+
+FORK_TEST(Linux, epoll) {
+  int sock_fds[2];
+  EXPECT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fds));
+  // Queue some data.
+  char buffer[4] = {1, 2, 3, 4};
+  EXPECT_OK(write(sock_fds[1], buffer, sizeof(buffer)));
+
+  EXPECT_OK(cap_enter());  // Enter capability mode.
+
+  int epoll_fd = epoll_create(1);
+  EXPECT_OK(epoll_fd);
+
+  cap_rights_t r_rs;
+  cap_rights_init(&r_rs, CAP_READ, CAP_SEEK);
+  cap_rights_t r_ws;
+  cap_rights_init(&r_ws, CAP_WRITE, CAP_SEEK);
+  cap_rights_t r_rws;
+  cap_rights_init(&r_rws, CAP_READ, CAP_WRITE, CAP_SEEK);
+  cap_rights_t r_rwspoll;
+  cap_rights_init(&r_rwspoll, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_EVENT);
+  cap_rights_t r_epoll;
+  cap_rights_init(&r_epoll, CAP_EPOLL_CTL);
+
+  int cap_epoll_wo = dup(epoll_fd);
+  EXPECT_OK(cap_epoll_wo);
+  EXPECT_OK(cap_rights_limit(cap_epoll_wo, &r_ws));
+  int cap_epoll_ro = dup(epoll_fd);
+  EXPECT_OK(cap_epoll_ro);
+  EXPECT_OK(cap_rights_limit(cap_epoll_ro, &r_rs));
+  int cap_epoll_rw = dup(epoll_fd);
+  EXPECT_OK(cap_epoll_rw);
+  EXPECT_OK(cap_rights_limit(cap_epoll_rw, &r_rws));
+  int cap_epoll_poll = dup(epoll_fd);
+  EXPECT_OK(cap_epoll_poll);
+  EXPECT_OK(cap_rights_limit(cap_epoll_poll, &r_rwspoll));
+  int cap_epoll_ctl = dup(epoll_fd);
+  EXPECT_OK(cap_epoll_ctl);
+  EXPECT_OK(cap_rights_limit(cap_epoll_ctl, &r_epoll));
+
+  // Can only modify the FDs being monitored if the CAP_EPOLL_CTL right is present.
+  struct epoll_event eev;
+  memset(&eev, 0, sizeof(eev));
+  eev.events = EPOLLIN|EPOLLOUT|EPOLLPRI;
+  EXPECT_NOTCAPABLE(epoll_ctl(cap_epoll_ro, EPOLL_CTL_ADD, sock_fds[0], &eev));
+  EXPECT_NOTCAPABLE(epoll_ctl(cap_epoll_wo, EPOLL_CTL_ADD, sock_fds[0], &eev));
+  EXPECT_NOTCAPABLE(epoll_ctl(cap_epoll_rw, EPOLL_CTL_ADD, sock_fds[0], &eev));
+  EXPECT_OK(epoll_ctl(cap_epoll_ctl, EPOLL_CTL_ADD, sock_fds[0], &eev));
+  eev.events = EPOLLIN|EPOLLOUT;
+  EXPECT_NOTCAPABLE(epoll_ctl(cap_epoll_ro, EPOLL_CTL_MOD, sock_fds[0], &eev));
+  EXPECT_NOTCAPABLE(epoll_ctl(cap_epoll_wo, EPOLL_CTL_MOD, sock_fds[0], &eev));
+  EXPECT_NOTCAPABLE(epoll_ctl(cap_epoll_rw, EPOLL_CTL_MOD, sock_fds[0], &eev));
+  EXPECT_OK(epoll_ctl(cap_epoll_ctl, EPOLL_CTL_MOD, sock_fds[0], &eev));
+
+  // Running epoll_pwait(2) requires CAP_EVENT.
+  eev.events = 0;
+  EXPECT_NOTCAPABLE(epoll_pwait(cap_epoll_ro, &eev, 1, 100, NULL));
+  EXPECT_NOTCAPABLE(epoll_pwait(cap_epoll_wo, &eev, 1, 100, NULL));
+  EXPECT_NOTCAPABLE(epoll_pwait(cap_epoll_rw, &eev, 1, 100, NULL));
+  EXPECT_OK(epoll_pwait(cap_epoll_poll, &eev, 1, 100, NULL));
+  EXPECT_EQ(EPOLLIN, eev.events & EPOLLIN);
+
+  EXPECT_NOTCAPABLE(epoll_ctl(cap_epoll_ro, EPOLL_CTL_DEL, sock_fds[0], &eev));
+  EXPECT_NOTCAPABLE(epoll_ctl(cap_epoll_wo, EPOLL_CTL_DEL, sock_fds[0], &eev));
+  EXPECT_NOTCAPABLE(epoll_ctl(cap_epoll_rw, EPOLL_CTL_DEL, sock_fds[0], &eev));
+  EXPECT_OK(epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock_fds[0], &eev));
+
+  close(cap_epoll_ctl);
+  close(cap_epoll_poll);
+  close(cap_epoll_rw);
+  close(cap_epoll_ro);
+  close(cap_epoll_wo);
+  close(epoll_fd);
+  close(sock_fds[1]);
+  close(sock_fds[0]);
+}
+
+TEST(Linux, fstatat) {
+  int fd = open(TmpFile("cap_fstatat"), O_CREAT|O_RDWR, 0644);
+  EXPECT_OK(fd);
+  unsigned char buffer[] = {1, 2, 3, 4};
+  EXPECT_OK(write(fd, buffer, sizeof(buffer)));
+  cap_rights_t rights;
+  int cap_rf = dup(fd);
+  EXPECT_OK(cap_rf);
+  EXPECT_OK(cap_rights_limit(cap_rf, cap_rights_init(&rights, CAP_READ, CAP_FSTAT)));
+  int cap_ro = dup(fd);
+  EXPECT_OK(cap_ro);
+  EXPECT_OK(cap_rights_limit(cap_ro, cap_rights_init(&rights, CAP_READ)));
+
+  struct stat info;
+  EXPECT_OK(fstatat(fd, "", &info, AT_EMPTY_PATH));
+  EXPECT_NOTCAPABLE(fstatat(cap_ro, "", &info, AT_EMPTY_PATH));
+  EXPECT_OK(fstatat(cap_rf, "", &info, AT_EMPTY_PATH));
+
+  close(cap_ro);
+  close(cap_rf);
+  close(fd);
+
+  int dir = open(tmpdir.c_str(), O_RDONLY);
+  EXPECT_OK(dir);
+  int dir_rf = dup(dir);
+  EXPECT_OK(dir_rf);
+  EXPECT_OK(cap_rights_limit(dir_rf, cap_rights_init(&rights, CAP_READ, CAP_FSTAT)));
+  int dir_ro = dup(fd);
+  EXPECT_OK(dir_ro);
+  EXPECT_OK(cap_rights_limit(dir_ro, cap_rights_init(&rights, CAP_READ)));
+
+  EXPECT_OK(fstatat(dir, "cap_fstatat", &info, AT_EMPTY_PATH));
+  EXPECT_NOTCAPABLE(fstatat(dir_ro, "cap_fstatat", &info, AT_EMPTY_PATH));
+  EXPECT_OK(fstatat(dir_rf, "cap_fstatat", &info, AT_EMPTY_PATH));
+
+  close(dir_ro);
+  close(dir_rf);
+  close(dir);
+
+  unlink(TmpFile("cap_fstatat"));
+}
+
+// fanotify support may not be available at compile-time
+#ifdef __NR_fanotify_init
+TEST(Linux, FanotifyIfRoot) {
+  GTEST_SKIP_IF_NOT_ROOT();
+  int fa_fd = fanotify_init(FAN_CLASS_NOTIF, O_RDWR);
+  EXPECT_OK(fa_fd);
+  if (fa_fd < 0) return;  // May not be enabled
+
+  cap_rights_t r_rs;
+  cap_rights_init(&r_rs, CAP_READ, CAP_SEEK);
+  cap_rights_t r_ws;
+  cap_rights_init(&r_ws, CAP_WRITE, CAP_SEEK);
+  cap_rights_t r_rws;
+  cap_rights_init(&r_rws, CAP_READ, CAP_WRITE, CAP_SEEK);
+  cap_rights_t r_rwspoll;
+  cap_rights_init(&r_rwspoll, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_EVENT);
+  cap_rights_t r_rwsnotify;
+  cap_rights_init(&r_rwsnotify, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_NOTIFY);
+  cap_rights_t r_rsl;
+  cap_rights_init(&r_rsl, CAP_READ, CAP_SEEK, CAP_LOOKUP);
+  cap_rights_t r_rslstat;
+  cap_rights_init(&r_rslstat, CAP_READ, CAP_SEEK, CAP_LOOKUP, CAP_FSTAT);
+  cap_rights_t r_rsstat;
+  cap_rights_init(&r_rsstat, CAP_READ, CAP_SEEK, CAP_FSTAT);
+
+  int cap_fd_ro = dup(fa_fd);
+  EXPECT_OK(cap_fd_ro);
+  EXPECT_OK(cap_rights_limit(cap_fd_ro, &r_rs));
+  int cap_fd_wo = dup(fa_fd);
+  EXPECT_OK(cap_fd_wo);
+  EXPECT_OK(cap_rights_limit(cap_fd_wo, &r_ws));
+  int cap_fd_rw = dup(fa_fd);
+  EXPECT_OK(cap_fd_rw);
+  EXPECT_OK(cap_rights_limit(cap_fd_rw, &r_rws));
+  int cap_fd_poll = dup(fa_fd);
+  EXPECT_OK(cap_fd_poll);
+  EXPECT_OK(cap_rights_limit(cap_fd_poll, &r_rwspoll));
+  int cap_fd_not = dup(fa_fd);
+  EXPECT_OK(cap_fd_not);
+  EXPECT_OK(cap_rights_limit(cap_fd_not, &r_rwsnotify));
+
+  int rc = mkdir(TmpFile("cap_notify"), 0755);
+  EXPECT_TRUE(rc == 0 || errno == EEXIST);
+  int dfd = open(TmpFile("cap_notify"), O_RDONLY);
+  EXPECT_OK(dfd);
+  int fd = open(TmpFile("cap_notify/file"), O_CREAT|O_RDWR, 0644);
+  close(fd);
+  int cap_dfd = dup(dfd);
+  EXPECT_OK(cap_dfd);
+  EXPECT_OK(cap_rights_limit(cap_dfd, &r_rslstat));
+  EXPECT_OK(cap_dfd);
+  int cap_dfd_rs = dup(dfd);
+  EXPECT_OK(cap_dfd_rs);
+  EXPECT_OK(cap_rights_limit(cap_dfd_rs, &r_rs));
+  EXPECT_OK(cap_dfd_rs);
+  int cap_dfd_rsstat = dup(dfd);
+  EXPECT_OK(cap_dfd_rsstat);
+  EXPECT_OK(cap_rights_limit(cap_dfd_rsstat, &r_rsstat));
+  EXPECT_OK(cap_dfd_rsstat);
+  int cap_dfd_rsl = dup(dfd);
+  EXPECT_OK(cap_dfd_rsl);
+  EXPECT_OK(cap_rights_limit(cap_dfd_rsl, &r_rsl));
+  EXPECT_OK(cap_dfd_rsl);
+
+  // Need CAP_NOTIFY to change what's monitored.
+  EXPECT_NOTCAPABLE(fanotify_mark(cap_fd_ro, FAN_MARK_ADD, FAN_OPEN|FAN_MODIFY|FAN_EVENT_ON_CHILD, cap_dfd, NULL));
+  EXPECT_NOTCAPABLE(fanotify_mark(cap_fd_wo, FAN_MARK_ADD, FAN_OPEN|FAN_MODIFY|FAN_EVENT_ON_CHILD, cap_dfd, NULL));
+  EXPECT_NOTCAPABLE(fanotify_mark(cap_fd_rw, FAN_MARK_ADD, FAN_OPEN|FAN_MODIFY|FAN_EVENT_ON_CHILD, cap_dfd, NULL));
+  EXPECT_OK(fanotify_mark(cap_fd_not, FAN_MARK_ADD, FAN_OPEN|FAN_MODIFY|FAN_EVENT_ON_CHILD, cap_dfd, NULL));
+
+  // Need CAP_FSTAT on the thing monitored.
+  EXPECT_NOTCAPABLE(fanotify_mark(cap_fd_not, FAN_MARK_ADD, FAN_OPEN|FAN_MODIFY|FAN_EVENT_ON_CHILD, cap_dfd_rs, NULL));
+  EXPECT_OK(fanotify_mark(cap_fd_not, FAN_MARK_ADD, FAN_OPEN|FAN_MODIFY|FAN_EVENT_ON_CHILD, cap_dfd_rsstat, NULL));
+
+  // Too add monitoring of a file under a dfd, need CAP_LOOKUP|CAP_FSTAT on the dfd.
+  EXPECT_NOTCAPABLE(fanotify_mark(cap_fd_not, FAN_MARK_ADD, FAN_OPEN|FAN_MODIFY, cap_dfd_rsstat, "file"));
+  EXPECT_NOTCAPABLE(fanotify_mark(cap_fd_not, FAN_MARK_ADD, FAN_OPEN|FAN_MODIFY, cap_dfd_rsl, "file"));
+  EXPECT_OK(fanotify_mark(cap_fd_not, FAN_MARK_ADD, FAN_OPEN|FAN_MODIFY, cap_dfd, "file"));
+
+  pid_t child = fork();
+  if (child == 0) {
+    // Child: Perform activity in the directory under notify.
+    sleep(1);
+    unlink(TmpFile("cap_notify/temp"));
+    int fd = open(TmpFile("cap_notify/temp"), O_CREAT|O_RDWR, 0644);
+    close(fd);
+    exit(0);
+  }
+
+  // Need CAP_EVENT to poll.
+  struct pollfd poll_fd;
+  poll_fd.revents = 0;
+  poll_fd.events = POLLIN;
+  poll_fd.fd = cap_fd_rw;
+  EXPECT_OK(poll(&poll_fd, 1, 1400));
+  EXPECT_EQ(0, (poll_fd.revents & POLLIN));
+  EXPECT_NE(0, (poll_fd.revents & POLLNVAL));
+
+  poll_fd.fd = cap_fd_not;
+  EXPECT_OK(poll(&poll_fd, 1, 1400));
+  EXPECT_EQ(0, (poll_fd.revents & POLLIN));
+  EXPECT_NE(0, (poll_fd.revents & POLLNVAL));
+
+  poll_fd.fd = cap_fd_poll;
+  EXPECT_OK(poll(&poll_fd, 1, 1400));
+  EXPECT_NE(0, (poll_fd.revents & POLLIN));
+  EXPECT_EQ(0, (poll_fd.revents & POLLNVAL));
+
+  // Need CAP_READ to read.
+  struct fanotify_event_metadata ev;
+  memset(&ev, 0, sizeof(ev));
+  EXPECT_NOTCAPABLE(read(cap_fd_wo, &ev, sizeof(ev)));
+  rc = read(fa_fd, &ev, sizeof(ev));
+  EXPECT_OK(rc);
+  EXPECT_EQ((int)sizeof(struct fanotify_event_metadata), rc);
+  EXPECT_EQ(child, ev.pid);
+  EXPECT_NE(0, ev.fd);
+
+  // TODO(drysdale): reinstate if/when capsicum-linux propagates rights
+  // to fanotify-generated FDs.
+#ifdef OMIT
+  // fanotify(7) gives us a FD for the changed file.  This should
+  // only have rights that are a subset of those for the original
+  // monitored directory file descriptor.
+  cap_rights_t rights;
+  CAL_ALL(&rights);
+  EXPECT_OK(cap_rights_get(ev.fd, &rights));
+  EXPECT_RIGHTS_IN(&rights, &r_rslstat);
+#endif
+
+  // Wait for the child.
+  int status;
+  EXPECT_EQ(child, waitpid(child, &status, 0));
+  rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+  EXPECT_EQ(0, rc);
+
+  close(cap_dfd_rsstat);
+  close(cap_dfd_rsl);
+  close(cap_dfd_rs);
+  close(cap_dfd);
+  close(dfd);
+  unlink(TmpFile("cap_notify/file"));
+  unlink(TmpFile("cap_notify/temp"));
+  rmdir(TmpFile("cap_notify"));
+  close(cap_fd_not);
+  close(cap_fd_poll);
+  close(cap_fd_rw);
+  close(cap_fd_wo);
+  close(cap_fd_ro);
+  close(fa_fd);
+}
+#endif
+
+TEST(Linux, inotify) {
+  int i_fd = inotify_init();
+  EXPECT_OK(i_fd);
+
+  cap_rights_t r_rs;
+  cap_rights_init(&r_rs, CAP_READ, CAP_SEEK);
+  cap_rights_t r_ws;
+  cap_rights_init(&r_ws, CAP_WRITE, CAP_SEEK);
+  cap_rights_t r_rws;
+  cap_rights_init(&r_rws, CAP_READ, CAP_WRITE, CAP_SEEK);
+  cap_rights_t r_rwsnotify;
+  cap_rights_init(&r_rwsnotify, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_NOTIFY);
+
+  int cap_fd_ro = dup(i_fd);
+  EXPECT_OK(cap_fd_ro);
+  EXPECT_OK(cap_rights_limit(cap_fd_ro, &r_rs));
+  int cap_fd_wo = dup(i_fd);
+  EXPECT_OK(cap_fd_wo);
+  EXPECT_OK(cap_rights_limit(cap_fd_wo, &r_ws));
+  int cap_fd_rw = dup(i_fd);
+  EXPECT_OK(cap_fd_rw);
+  EXPECT_OK(cap_rights_limit(cap_fd_rw, &r_rws));
+  int cap_fd_all = dup(i_fd);
+  EXPECT_OK(cap_fd_all);
+  EXPECT_OK(cap_rights_limit(cap_fd_all, &r_rwsnotify));
+
+  int fd = open(TmpFile("cap_inotify"), O_CREAT|O_RDWR, 0644);
+  EXPECT_NOTCAPABLE(inotify_add_watch(cap_fd_rw, TmpFile("cap_inotify"), IN_ACCESS|IN_MODIFY));
+  int wd = inotify_add_watch(i_fd, TmpFile("cap_inotify"), IN_ACCESS|IN_MODIFY);
+  EXPECT_OK(wd);
+
+  unsigned char buffer[] = {1, 2, 3, 4};
+  EXPECT_OK(write(fd, buffer, sizeof(buffer)));
+
+  struct inotify_event iev;
+  memset(&iev, 0, sizeof(iev));
+  EXPECT_NOTCAPABLE(read(cap_fd_wo, &iev, sizeof(iev)));
+  int rc = read(cap_fd_ro, &iev, sizeof(iev));
+  EXPECT_OK(rc);
+  EXPECT_EQ((int)sizeof(iev), rc);
+  EXPECT_EQ(wd, iev.wd);
+
+  EXPECT_NOTCAPABLE(inotify_rm_watch(cap_fd_wo, wd));
+  EXPECT_OK(inotify_rm_watch(cap_fd_all, wd));
+
+  close(fd);
+  close(cap_fd_all);
+  close(cap_fd_rw);
+  close(cap_fd_wo);
+  close(cap_fd_ro);
+  close(i_fd);
+  unlink(TmpFile("cap_inotify"));
+}
+
+TEST(Linux, ArchChangeIfAvailable) {
+  const char* prog_candidates[] = {"./mini-me.32", "./mini-me.x32", "./mini-me.64"};
+  const char* progs[] = {NULL, NULL, NULL};
+  char* argv_pass[] = {(char*)"to-come", (char*)"--capmode", NULL};
+  char* null_envp[] = {NULL};
+  int fds[3];
+  int count = 0;
+
+  for (int ii = 0; ii < 3; ii++) {
+    fds[count] = open(prog_candidates[ii], O_RDONLY);
+    if (fds[count] >= 0) {
+      progs[count] = prog_candidates[ii];
+      count++;
+    }
+  }
+  if (count == 0) {
+    GTEST_SKIP() << "no different-architecture programs available";
+  }
+
+  for (int ii = 0; ii < count; ii++) {
+    // Fork-and-exec a binary of this architecture.
+    pid_t child = fork();
+    if (child == 0) {
+      EXPECT_OK(cap_enter());  // Enter capability mode
+      if (verbose) fprintf(stderr, "[%d] call fexecve(%s, %s)\n",
+                           getpid_(), progs[ii], argv_pass[1]);
+      argv_pass[0] = (char *)progs[ii];
+      int rc = fexecve_(fds[ii], argv_pass, null_envp);
+      fprintf(stderr, "fexecve(%s) returned %d errno %d\n", progs[ii], rc, errno);
+      exit(99);  // Should not reach here.
+    }
+    int status;
+    EXPECT_EQ(child, waitpid(child, &status, 0));
+    int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+    EXPECT_EQ(0, rc);
+    close(fds[ii]);
+  }
+}
+
+FORK_TEST(Linux, NamespaceIfRoot) {
+  GTEST_SKIP_IF_NOT_ROOT();
+  pid_t me = getpid_();
+
+  // Create a new UTS namespace.
+  EXPECT_OK(unshare(CLONE_NEWUTS));
+  // Open an FD to its symlink.
+  char buffer[256];
+  sprintf(buffer, "/proc/%d/ns/uts", me);
+  int ns_fd = open(buffer, O_RDONLY);
+
+  cap_rights_t r_rwlstat;
+  cap_rights_init(&r_rwlstat, CAP_READ, CAP_WRITE, CAP_LOOKUP, CAP_FSTAT);
+  cap_rights_t r_rwlstatns;
+  cap_rights_init(&r_rwlstatns, CAP_READ, CAP_WRITE, CAP_LOOKUP, CAP_FSTAT, CAP_SETNS);
+
+  int cap_fd = dup(ns_fd);
+  EXPECT_OK(cap_fd);
+  EXPECT_OK(cap_rights_limit(cap_fd, &r_rwlstat));
+  int cap_fd_setns = dup(ns_fd);
+  EXPECT_OK(cap_fd_setns);
+  EXPECT_OK(cap_rights_limit(cap_fd_setns, &r_rwlstatns));
+  EXPECT_NOTCAPABLE(setns(cap_fd, CLONE_NEWUTS));
+  EXPECT_OK(setns(cap_fd_setns, CLONE_NEWUTS));
+
+  EXPECT_OK(cap_enter());  // Enter capability mode.
+
+  // No setns(2) but unshare(2) is allowed.
+  EXPECT_CAPMODE(setns(ns_fd, CLONE_NEWUTS));
+  EXPECT_OK(unshare(CLONE_NEWUTS));
+}
+
+static void SendFD(int fd, int over) {
+  struct msghdr mh;
+  mh.msg_name = NULL;  // No address needed
+  mh.msg_namelen = 0;
+  char buffer1[1024];
+  struct iovec iov[1];
+  iov[0].iov_base = buffer1;
+  iov[0].iov_len = sizeof(buffer1);
+  mh.msg_iov = iov;
+  mh.msg_iovlen = 1;
+  char buffer2[1024];
+  mh.msg_control = buffer2;
+  mh.msg_controllen = CMSG_LEN(sizeof(int));
+  struct cmsghdr *cmptr = CMSG_FIRSTHDR(&mh);
+  cmptr->cmsg_level = SOL_SOCKET;
+  cmptr->cmsg_type = SCM_RIGHTS;
+  cmptr->cmsg_len = CMSG_LEN(sizeof(int));
+  *(int *)CMSG_DATA(cmptr) = fd;
+  buffer1[0] = 0;
+  iov[0].iov_len = 1;
+  int rc = sendmsg(over, &mh, 0);
+  EXPECT_OK(rc);
+}
+
+static int ReceiveFD(int over) {
+  struct msghdr mh;
+  mh.msg_name = NULL;  // No address needed
+  mh.msg_namelen = 0;
+  char buffer1[1024];
+  struct iovec iov[1];
+  iov[0].iov_base = buffer1;
+  iov[0].iov_len = sizeof(buffer1);
+  mh.msg_iov = iov;
+  mh.msg_iovlen = 1;
+  char buffer2[1024];
+  mh.msg_control = buffer2;
+  mh.msg_controllen = sizeof(buffer2);
+  int rc = recvmsg(over, &mh, 0);
+  EXPECT_OK(rc);
+  EXPECT_LE(CMSG_LEN(sizeof(int)), mh.msg_controllen);
+  struct cmsghdr *cmptr = CMSG_FIRSTHDR(&mh);
+  int fd = *(int*)CMSG_DATA(cmptr);
+  EXPECT_EQ(CMSG_LEN(sizeof(int)), cmptr->cmsg_len);
+  cmptr = CMSG_NXTHDR(&mh, cmptr);
+  EXPECT_TRUE(cmptr == NULL);
+  return fd;
+}
+
+static int shared_pd = -1;
+static int shared_sock_fds[2];
+
+static int ChildFunc(void *arg) {
+  // This function is running in a new PID namespace, and so is pid 1.
+  if (verbose) fprintf(stderr, "    ChildFunc: pid=%d, ppid=%d\n", getpid_(), getppid());
+  EXPECT_EQ(1, getpid_());
+  EXPECT_EQ(0, getppid());
+
+  // The shared process descriptor is outside our namespace, so we cannot
+  // get its pid.
+  if (verbose) fprintf(stderr, "    ChildFunc: shared_pd=%d\n", shared_pd);
+  pid_t shared_child = -1;
+  EXPECT_OK(pdgetpid(shared_pd, &shared_child));
+  if (verbose) fprintf(stderr, "    ChildFunc: corresponding pid=%d\n", shared_child);
+  EXPECT_EQ(0, shared_child);
+
+  // But we can pdkill() it even so.
+  if (verbose) fprintf(stderr, "    ChildFunc: call pdkill(pd=%d)\n", shared_pd);
+  EXPECT_OK(pdkill(shared_pd, SIGINT));
+
+  int pd;
+  pid_t child = pdfork(&pd, 0);
+  EXPECT_OK(child);
+  if (child == 0) {
+    // Child: expect pid 2.
+    if (verbose) fprintf(stderr, "      child of ChildFunc: pid=%d, ppid=%d\n", getpid_(), getppid());
+    EXPECT_EQ(2, getpid_());
+    EXPECT_EQ(1, getppid());
+    while (true) {
+      if (verbose) fprintf(stderr, "      child of ChildFunc: \"I aten't dead\"\n");
+      sleep(1);
+    }
+    exit(0);
+  }
+  EXPECT_EQ(2, child);
+  EXPECT_PID_ALIVE(child);
+  if (verbose) fprintf(stderr, "    ChildFunc: pdfork() -> pd=%d, corresponding pid=%d state='%c'\n",
+                       pd, child, ProcessState(child));
+
+  pid_t pid;
+  EXPECT_OK(pdgetpid(pd, &pid));
+  EXPECT_EQ(child, pid);
+
+  sleep(2);
+
+  // Send the process descriptor over UNIX domain socket back to parent.
+  SendFD(pd, shared_sock_fds[1]);
+
+  // Wait for death of (grand)child, killed by our parent.
+  if (verbose) fprintf(stderr, "    ChildFunc: wait on pid=%d\n", child);
+  int status;
+  EXPECT_EQ(child, wait4(child, &status, __WALL, NULL));
+
+  if (verbose) fprintf(stderr, "    ChildFunc: return 0\n");
+  return 0;
+}
+
+#define STACK_SIZE (1024 * 1024)
+static char child_stack[STACK_SIZE];
+
+// TODO(drysdale): fork into a user namespace first so GTEST_SKIP_IF_NOT_ROOT can be removed.
+TEST(Linux, PidNamespacePdForkIfRoot) {
+  GTEST_SKIP_IF_NOT_ROOT();
+  // Pass process descriptors in both directions across a PID namespace boundary.
+  // pdfork() off a child before we start, holding its process descriptor in a global
+  // variable that's accessible to children.
+  pid_t firstborn = pdfork(&shared_pd, 0);
+  EXPECT_OK(firstborn);
+  if (firstborn == 0) {
+    while (true) {
+      if (verbose) fprintf(stderr, "  Firstborn: \"I aten't dead\"\n");
+      sleep(1);
+    }
+    exit(0);
+  }
+  EXPECT_PID_ALIVE(firstborn);
+  if (verbose) fprintf(stderr, "Parent: pre-pdfork()ed pd=%d, pid=%d state='%c'\n",
+                       shared_pd, firstborn, ProcessState(firstborn));
+  sleep(2);
+
+  // Prepare sockets to communicate with child process.
+  EXPECT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, shared_sock_fds));
+
+  // Clone into a child process with a new pid namespace.
+  pid_t child = clone(ChildFunc, child_stack + STACK_SIZE,
+                      CLONE_FILES|CLONE_NEWPID|SIGCHLD, NULL);
+  EXPECT_OK(child);
+  EXPECT_PID_ALIVE(child);
+  if (verbose) fprintf(stderr, "Parent: child is %d state='%c'\n", child, ProcessState(child));
+
+  // Ensure the child runs.  First thing it does is to kill our firstborn, using shared_pd.
+  sleep(1);
+  EXPECT_PID_DEAD(firstborn);
+
+  // But we can still retrieve firstborn's PID, as it's not been reaped yet.
+  pid_t child0;
+  EXPECT_OK(pdgetpid(shared_pd, &child0));
+  EXPECT_EQ(firstborn, child0);
+  if (verbose) fprintf(stderr, "Parent: check on firstborn: pdgetpid(pd=%d) -> child=%d state='%c'\n",
+                       shared_pd, child0, ProcessState(child0));
+
+  // Now reap it.
+  int status;
+  EXPECT_EQ(firstborn, waitpid(firstborn, &status, __WALL));
+
+  // Get the process descriptor of the child-of-child via socket transfer.
+  int grandchild_pd = ReceiveFD(shared_sock_fds[0]);
+
+  // Our notion of the pid associated with the grandchild is in the main PID namespace.
+  pid_t grandchild;
+  EXPECT_OK(pdgetpid(grandchild_pd, &grandchild));
+  EXPECT_NE(2, grandchild);
+  if (verbose) fprintf(stderr, "Parent: pre-pdkill:  pdgetpid(grandchild_pd=%d) -> grandchild=%d state='%c'\n",
+                       grandchild_pd, grandchild, ProcessState(grandchild));
+  EXPECT_PID_ALIVE(grandchild);
+
+  // Kill the grandchild via the process descriptor.
+  EXPECT_OK(pdkill(grandchild_pd, SIGINT));
+  usleep(10000);
+  if (verbose) fprintf(stderr, "Parent: post-pdkill: pdgetpid(grandchild_pd=%d) -> grandchild=%d state='%c'\n",
+                       grandchild_pd, grandchild, ProcessState(grandchild));
+  EXPECT_PID_DEAD(grandchild);
+
+  sleep(2);
+
+  // Wait for the child.
+  EXPECT_EQ(child, waitpid(child, &status, WNOHANG));
+  int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+  EXPECT_EQ(0, rc);
+
+  close(shared_sock_fds[0]);
+  close(shared_sock_fds[1]);
+  close(shared_pd);
+  close(grandchild_pd);
+}
+
+int NSInit(void *data) {
+  // This function is running in a new PID namespace, and so is pid 1.
+  if (verbose) fprintf(stderr, "  NSInit: pid=%d, ppid=%d\n", getpid_(), getppid());
+  EXPECT_EQ(1, getpid_());
+  EXPECT_EQ(0, getppid());
+
+  int pd;
+  pid_t child = pdfork(&pd, 0);
+  EXPECT_OK(child);
+  if (child == 0) {
+    // Child: loop forever until terminated.
+    if (verbose) fprintf(stderr, "    child of NSInit: pid=%d, ppid=%d\n", getpid_(), getppid());
+    while (true) {
+      if (verbose) fprintf(stderr, "    child of NSInit: \"I aten't dead\"\n");
+      usleep(100000);
+    }
+    exit(0);
+  }
+  EXPECT_EQ(2, child);
+  EXPECT_PID_ALIVE(child);
+  if (verbose) fprintf(stderr, "  NSInit: pdfork() -> pd=%d, corresponding pid=%d state='%c'\n",
+                       pd, child, ProcessState(child));
+  sleep(1);
+
+  // Send the process descriptor over UNIX domain socket back to parent.
+  SendFD(pd, shared_sock_fds[1]);
+  close(pd);
+
+  // Wait for a byte back in the other direction.
+  int value;
+  if (verbose) fprintf(stderr, "  NSInit: block waiting for value\n");
+  read(shared_sock_fds[1], &value, sizeof(value));
+
+  if (verbose) fprintf(stderr, "  NSInit: return 0\n");
+  return 0;
+}
+
+TEST(Linux, DeadNSInitIfRoot) {
+  GTEST_SKIP_IF_NOT_ROOT();
+
+  // Prepare sockets to communicate with child process.
+  EXPECT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, shared_sock_fds));
+
+  // Clone into a child process with a new pid namespace.
+  pid_t child = clone(NSInit, child_stack + STACK_SIZE,
+                      CLONE_FILES|CLONE_NEWPID|SIGCHLD, NULL);
+  usleep(10000);
+  EXPECT_OK(child);
+  EXPECT_PID_ALIVE(child);
+  if (verbose) fprintf(stderr, "Parent: child is %d state='%c'\n", child, ProcessState(child));
+
+  // Get the process descriptor of the child-of-child via socket transfer.
+  int grandchild_pd = ReceiveFD(shared_sock_fds[0]);
+  pid_t grandchild;
+  EXPECT_OK(pdgetpid(grandchild_pd, &grandchild));
+  if (verbose) fprintf(stderr, "Parent: grandchild is %d state='%c'\n", grandchild, ProcessState(grandchild));
+
+  // Send an int to the child to trigger its termination.  Grandchild should also
+  // go, as its init process is gone.
+  int zero = 0;
+  if (verbose) fprintf(stderr, "Parent: write 0 to pipe\n");
+  write(shared_sock_fds[0], &zero, sizeof(zero));
+  EXPECT_PID_ZOMBIE(child);
+  EXPECT_PID_GONE(grandchild);
+
+  // Wait for the child.
+  int status;
+  EXPECT_EQ(child, waitpid(child, &status, WNOHANG));
+  int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+  EXPECT_EQ(0, rc);
+  EXPECT_PID_GONE(child);
+
+  close(shared_sock_fds[0]);
+  close(shared_sock_fds[1]);
+  close(grandchild_pd);
+
+  if (verbose) {
+    fprintf(stderr, "Parent: child %d in state='%c'\n", child, ProcessState(child));
+    fprintf(stderr, "Parent: grandchild %d in state='%c'\n", grandchild, ProcessState(grandchild));
+  }
+}
+
+TEST(Linux, DeadNSInit2IfRoot) {
+  GTEST_SKIP_IF_NOT_ROOT();
+
+  // Prepare sockets to communicate with child process.
+  EXPECT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, shared_sock_fds));
+
+  // Clone into a child process with a new pid namespace.
+  pid_t child = clone(NSInit, child_stack + STACK_SIZE,
+                      CLONE_FILES|CLONE_NEWPID|SIGCHLD, NULL);
+  usleep(10000);
+  EXPECT_OK(child);
+  EXPECT_PID_ALIVE(child);
+  if (verbose) fprintf(stderr, "Parent: child is %d state='%c'\n", child, ProcessState(child));
+
+  // Get the process descriptor of the child-of-child via socket transfer.
+  int grandchild_pd = ReceiveFD(shared_sock_fds[0]);
+  pid_t grandchild;
+  EXPECT_OK(pdgetpid(grandchild_pd, &grandchild));
+  if (verbose) fprintf(stderr, "Parent: grandchild is %d state='%c'\n", grandchild, ProcessState(grandchild));
+
+  // Kill the grandchild
+  EXPECT_OK(pdkill(grandchild_pd, SIGINT));
+  usleep(10000);
+  EXPECT_PID_ZOMBIE(grandchild);
+  // Close the process descriptor, so there are now no procdesc references to grandchild.
+  close(grandchild_pd);
+
+  // Send an int to the child to trigger its termination.  Grandchild should also
+  // go, as its init process is gone.
+  int zero = 0;
+  if (verbose) fprintf(stderr, "Parent: write 0 to pipe\n");
+  write(shared_sock_fds[0], &zero, sizeof(zero));
+  EXPECT_PID_ZOMBIE(child);
+  EXPECT_PID_GONE(grandchild);
+
+  // Wait for the child.
+  int status;
+  EXPECT_EQ(child, waitpid(child, &status, WNOHANG));
+  int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+  EXPECT_EQ(0, rc);
+
+  close(shared_sock_fds[0]);
+  close(shared_sock_fds[1]);
+
+  if (verbose) {
+    fprintf(stderr, "Parent: child %d in state='%c'\n", child, ProcessState(child));
*** 538 LINES SKIPPED ***


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?69c1528c.1bbe1.69ce59f7>