diff options
| author | Kyle Evans <kevans@FreeBSD.org> | 2026-04-09 02:37:00 +0000 |
|---|---|---|
| committer | Kyle Evans <kevans@FreeBSD.org> | 2026-04-09 02:37:11 +0000 |
| commit | 0c9cec8b66e7033f50059329704515d5222b9ff4 (patch) | |
| tree | b8f86715c820210cfdaac214df0256e44590797d /tests | |
| parent | 0bf4d22c37083170961c31694b90551538901a1c (diff) | |
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/sys/kqueue/kqueue_fork.c | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/tests/sys/kqueue/kqueue_fork.c b/tests/sys/kqueue/kqueue_fork.c index e4c0412c1980..6f517a2e0e29 100644 --- a/tests/sys/kqueue/kqueue_fork.c +++ b/tests/sys/kqueue/kqueue_fork.c @@ -27,9 +27,13 @@ */ #include <sys/event.h> +#include <sys/procdesc.h> +#include <sys/stat.h> +#include <sys/user.h> #include <sys/wait.h> #include <err.h> +#include <fcntl.h> #include <signal.h> #include <unistd.h> @@ -81,9 +85,145 @@ ATF_TC_BODY(shared_table_filt_sig, tc) ATF_REQUIRE_EQ(WEXITSTATUS(status), 0); } +#define TIMER_FORKED 0 +#define TIMER_TIMEOUT 1 + +#define RECV_TIMER 0x01 +#define RECV_VNODE 0x02 +#define RECV_CLOREAD 0x04 +#define RECV_ERROR 0x80 +#define RECV_ALL (RECV_TIMER | RECV_VNODE) + +static int +cponfork_notes_check(int kq, int clofd) +{ + struct kevent ev; + int error, received = 0; + + EV_SET(&ev, TIMER_TIMEOUT, EVFILT_TIMER, + EV_ADD | EV_ENABLE | EV_ONESHOT, NOTE_SECONDS, 4, NULL); + error = kevent(kq, &ev, 1, NULL, 0, NULL); + if (error == -1) + return (RECV_ERROR); + + while ((received & RECV_ALL) != RECV_ALL) { + error = kevent(kq, NULL, 0, &ev, 1, NULL); + if (error < 0) + return (RECV_ERROR); + else if (error == 0) + break; + + switch (ev.filter) { + case EVFILT_TIMER: + if (ev.ident == TIMER_TIMEOUT) + return (received | RECV_ERROR); + + received |= RECV_TIMER; + break; + case EVFILT_VNODE: + received |= RECV_VNODE; + break; + case EVFILT_READ: + if ((int)ev.ident != clofd) + return (received | RECV_ERROR); + received |= RECV_CLOREAD; + break; + } + } + + return (received); +} + +ATF_TC_WITHOUT_HEAD(cponfork_notes); +ATF_TC_BODY(cponfork_notes, tc) +{ + struct kevent ev[3]; + int clofd, dfd, error, kq, pdfd, pmask, status; + pid_t pid; + + kq = kqueuex(KQUEUE_CPONFORK); + ATF_REQUIRE(kq >= 0); + + dfd = open(".", O_DIRECTORY); + ATF_REQUIRE(dfd >= 0); + + clofd = kqueue(); + ATF_REQUIRE(clofd >= 0); + + /* + * Setup an event on clofd that we can trigger to make it readable, + * as we'll want this ready to go when we fork to be sure that if we + * *were* going to receive an event from it, it would have occurred + * before the three-second timer that would normally close out the child + * fires. + */ + EV_SET(&ev[0], 0, EVFILT_USER, EV_ADD | EV_ENABLE, 0, 0, NULL); + error = kevent(clofd, &ev[0], 1, NULL, 0, NULL); + ATF_REQUIRE(error != -1); + + /* + * Every event we setup here we should expect to observe in both the + * child and the parent, with exception to the EVFILT_READ of clofd. We + * except that one to be dropped in the child when the kqueue it's + * attached to goes away, thus its exclusion from the RECV_ALL mask. + */ + EV_SET(&ev[0], dfd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_ONESHOT, + NOTE_WRITE, 0, NULL); + EV_SET(&ev[1], TIMER_FORKED, EVFILT_TIMER, EV_ADD | EV_ENABLE | EV_ONESHOT, + NOTE_SECONDS, 3, NULL); + EV_SET(&ev[2], clofd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_ONESHOT, 0, + 0, NULL); + error = kevent(kq, &ev[0], 3, NULL, 0, NULL); + ATF_REQUIRE(error != -1); + + /* Fire off an event to make clofd readable. */ + EV_SET(&ev[0], 0, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL); + error = kevent(clofd, &ev[0], 1, NULL, 0, NULL); + + /* + * We're only using pdfork here for the kill-on-exit semantics, in case + * the parent fails to setup some context needed for one of our events + * to fire. + */ + pid = pdfork(&pdfd, 0); + ATF_REQUIRE(pid != -1); + if (pid == 0) { + struct kinfo_file kf = { .kf_structsize = sizeof(kf) }; + + if (fcntl(kq, F_KINFO, &kf) != 0) + _exit(RECV_ERROR); + else if (kf.kf_type != KF_TYPE_KQUEUE) + _exit(RECV_ERROR); + + _exit(cponfork_notes_check(kq, clofd)); + } + + /* Setup anything we need to fire off any of our events above. */ + error = mkdir("canary", 0755); + ATF_REQUIRE(error == 0); + + /* + * We'll simultaneously do the same exercise of polling the kqueue in + * the parent, to demonstrate that forking doesn't "steal" any of the + * knotes from us -- all of the events we've added are one-shot and + * still fire twice (once in parent, once in child). + */ + pmask = cponfork_notes_check(kq, clofd); + ATF_REQUIRE_EQ(pmask, RECV_ALL | RECV_CLOREAD); + + /* Wait for the child to timeout or observe the timer. */ + _Static_assert(RECV_ALL <= UCHAR_MAX, + "Too many events to observe -- switch from waitpid -> waitid"); + error = waitpid(pid, &status, 0); + ATF_REQUIRE(error != -1); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE_EQ(WEXITSTATUS(status), RECV_ALL); +} + ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, shared_table_filt_sig); + ATF_TP_ADD_TC(tp, cponfork_notes); return (atf_no_error()); } |
