diff --git a/lib/libc/sys/kqueue.2 b/lib/libc/sys/kqueue.2 --- a/lib/libc/sys/kqueue.2 +++ b/lib/libc/sys/kqueue.2 @@ -145,6 +145,15 @@ and .Fa eventlist . .Pp +The special value +.Va KQUEUE_FD_ANON +may be given as the +.Va fd +argument to +.Fn kevent . +In this case, an anonymous temporary queue is used, and no longer exists +after the call returns. +.Pp The .Fn EV_SET macro is provided for ease of initializing a @@ -262,6 +271,9 @@ See .Sx RETURN VALUES below. +.It Dv EV_ANON +This is used for anonymous operations that are not associated with a particular +kqueue descriptor. .It Dv EV_KEEPUDATA Causes .Fn kevent @@ -662,6 +674,46 @@ On return, .Va fflags contains the users defined flags in the lower 24 bits. +.It Dv EVFILT_USERMEM +Waits for a value to change at an address in user memory. The address +is provided in +.Va ident . +The value it points to is compared with an expected value provided in +.Va data , +and the event is triggered immediately if they are not equal. Otherwise, it +must be triggered explicitly using +.Va NOTE_TRIGGER +by any thread that wants to advertise that it has changed the value. The event +can be triggered by any process that has the same memory object mapped, even at +a different address, unless +.Va NOTE_USERMEM_PRIVATE +is added to +.Va fflags +to prevent inter-process events. +When +.Va NOTE_TRIGGER +is used in +.Va fflags , +.Va flags +should be set to +.Va EV_ANON , +because this operation wakes waiters not only in the given kqueue object. +.Bl -tag -width "Dv NOTE_UMTX_WAIT_PRIVATE" +.It Dv NOTE_USERMEM_INT +.Va ident +points to an object the size of an integer. +.It Dv NOTE_USERMEM_LONG +.Va ident +points to an object the size of a long. +.It Dv NOTE_TRIGGER +Cause the event to be triggered. The number of waiters to wake up should +be provided in +.Va data . +.It Dv NOTE_USERMEM_PRIVATE +.Va ident +is in a private namespace that is not shared with other processes that map +the containing memory object. Can be ORed with the preceding values. +.El .El .Sh CANCELLATION BEHAVIOUR If diff --git a/share/man/man9/kqueue.9 b/share/man/man9/kqueue.9 --- a/share/man/man9/kqueue.9 +++ b/share/man/man9/kqueue.9 @@ -212,6 +212,14 @@ that the .Va knote was added to. +.It Va f_anon +The +.Va f_anon +function will be called for +.Vt kevent +objects with the +.Dv EV_ANON +flag, to perform actions that don't relate to a specific kqueue or knote. .El .Pp The function diff --git a/sys/kern/kern_event.c b/sys/kern/kern_event.c --- a/sys/kern/kern_event.c +++ b/sys/kern/kern_event.c @@ -70,6 +70,7 @@ #include #include #include +#include #include #ifdef KTRACE #include @@ -203,6 +204,13 @@ .f_event = filt_user, .f_touch = filt_usertouch, }; +static struct filterops usermem_filtops = { + .f_attach = filt_usermemattach, + .f_detach = filt_usermemdetach, + .f_event = filt_usermem, + .f_touch = filt_usermemtouch, + .f_anon = filt_usermemanon, +}; static uma_zone_t knote_zone; static unsigned int __exclusive_cache_line kq_ncallouts; @@ -361,6 +369,7 @@ { &user_filtops, 1 }, /* EVFILT_USER */ { &null_filtops }, /* EVFILT_SENDFILE */ { &file_filtops, 1 }, /* EVFILT_EMPTY */ + { &usermem_filtops, 1 }, /* EVFILT_USERMEM */ }; /* @@ -1293,6 +1302,11 @@ struct file *fp; int error; + if (fd == KQUEUE_FD_ANON) { + error = kern_kevent_anonymous(td, nchanges, nevents, k_ops, timeout); + return (error); + } + cap_rights_init_zero(&rights); if (nchanges > 0) cap_rights_set_one(&rights, CAP_KQUEUE_CHANGE); @@ -1372,15 +1386,15 @@ * used to perform one-shot polling, similar to poll() and select(). */ int -kern_kevent_anonymous(struct thread *td, int nevents, - struct kevent_copyops *k_ops) +kern_kevent_anonymous(struct thread *td, int nchanges, int nevents, + struct kevent_copyops *k_ops, const struct timespec *timeout) { struct kqueue kq = {}; int error; kqueue_init(&kq); kq.kq_refcnt = 1; - error = kqueue_kevent(&kq, td, nevents, nevents, k_ops, NULL); + error = kqueue_kevent(&kq, td, nchanges, nevents, k_ops, timeout); kqueue_drain(&kq, td); kqueue_destroy(&kq); return (error); @@ -1500,6 +1514,17 @@ if (fops == NULL) return EINVAL; + /* Anonymous actions have no kqueue or knote */ + if (kev->flags & EV_ANON) { + tkn = NULL; + if (fops->f_anon == NULL) { + error = EINVAL; + goto done; + } + error = fops->f_anon(kev); + goto done; + } + if (kev->flags & EV_ADD) { /* Reject an invalid flag pair early */ if (kev->flags & EV_KEEPUDATA) { diff --git a/sys/kern/kern_umtx.c b/sys/kern/kern_umtx.c --- a/sys/kern/kern_umtx.c +++ b/sys/kern/kern_umtx.c @@ -60,6 +60,7 @@ #include #include #include +#include #include #include #include @@ -598,16 +599,22 @@ umtxq_signal_queue(struct umtx_key *key, int n_wake, int q) { struct umtxq_queue *uh; - struct umtx_q *uq; + struct umtx_q *uq, *uq_temp; int ret; ret = 0; UMTXQ_LOCKED_ASSERT(umtxq_getchain(key)); uh = umtxq_queue_lookup(key, q); if (uh != NULL) { - while ((uq = TAILQ_FIRST(&uh->head)) != NULL) { - umtxq_remove_queue(uq, q); - wakeup(uq); + TAILQ_FOREACH_SAFE(uq, &uh->head, uq_link, uq_temp) { + if (uq->uq_flags & UQF_KQUEUE) { + if (uq->uq_flags & UQF_KQUEUE_ONCE) + umtxq_remove_queue(uq, q); + KNOTE_LOCKED(&uq->uq_klist, 1); + } else { + umtxq_remove_queue(uq, q); + wakeup(uq); + } if (++ret >= n_wake) return (ret); } @@ -5124,3 +5131,166 @@ if (rb_inact != 0) (void)umtx_handle_rb(td, rb_inact, NULL, true, compat32); } + +int +filt_usermemattach(struct knote *kn) +{ + struct umtx_q *uq; + void *uaddr; + u_long tmp; + u_int tmp32; + int error; + bool is_private; + + is_private = (kn->kn_sfflags & NOTE_USERMEM_PRIVATE) != 0; + + uaddr = (void *)kn->kn_kevent.ident; + + /* XXX stricter checking of acceptable fflags */ + if ((kn->kn_sfflags & NOTE_USERMEM_INT) == 0 && + (kn->kn_sfflags & NOTE_USERMEM_LONG) == 0) + return (EINVAL); + + /* Make a new umtx queue object and join the wait list. */ + uq = umtxq_alloc(); + if ((error = umtx_key_get(uaddr, TYPE_SIMPLE_WAIT, + is_private ? THREAD_SHARE : AUTO_SHARE, &uq->uq_key)) != 0) { + goto exit_free; + } + + knlist_init_mtx(&uq->uq_klist, &umtxq_getchain(&uq->uq_key)->uc_lock); + uq->uq_flags |= UQF_KQUEUE; + if (kn->kn_flags & EV_ONESHOT) + uq->uq_flags |= UQF_KQUEUE_ONCE; + kn->kn_ptr.p_v = uq; + + umtxq_lock(&uq->uq_key); + knlist_add(&uq->uq_klist, kn, 1); + umtxq_insert(uq); + umtxq_unlock(&uq->uq_key); + + /* Fetch the user's value. */ + if (kn->kn_sfflags & NOTE_USERMEM_INT) { + /* XXX also need to use this for _LONG for 32 bit process? */ + error = fueword32(uaddr, &tmp32); + tmp = tmp32; + } else { + error = fueword(uaddr, &tmp); + } + if (error != 0) { + error = EFAULT; + goto exit_dequeue; + } + + /* If value doesn't match, trigger immediately. */ + if (tmp != (u_long) kn->kn_sdata) { + kn->kn_hookid = 1; + if (uq->uq_flags & UQF_KQUEUE_ONCE) { + umtxq_lock(&uq->uq_key); + umtxq_remove(uq); + umtxq_unlock(&uq->uq_key); + } + } + + /* Otherwise we're now in the queue, waiting. */ + return (0); + +exit_dequeue: + umtxq_lock(&uq->uq_key); + knlist_remove(&uq->uq_klist, kn, 1); + kn->kn_ptr.p_v = NULL; + umtxq_remove(uq); + umtxq_unlock(&uq->uq_key); + + umtx_key_release(&uq->uq_key); + +exit_free: + umtxq_free(uq); + + return (error); +} + +void +filt_usermemdetach(struct knote *kn) +{ + struct umtx_q *uq; + + uq = kn->kn_ptr.p_v; + + umtxq_lock(&uq->uq_key); + knlist_remove(&uq->uq_klist, kn, 1); + kn->kn_ptr.p_v = NULL; + umtxq_remove(uq); + umtxq_unlock(&uq->uq_key); + + umtx_key_release(&uq->uq_key); + + umtxq_free(uq); +} + +int +filt_usermem(struct knote *kn, long hint) +{ + if (hint) + kn->kn_hookid = 1; + + return (kn->kn_hookid); +} + +void +filt_usermemtouch(struct knote *kn, struct kevent *kev, u_long type) +{ + switch (type) { + case EVENT_REGISTER: + /* + * Re-adding the same ident. + * + * XXX If data and fflags haven't changed, there's nothing to + * do as we're already in the umtx queue and anyone who changed + * the value should have woken us. Otherwise, we'd need to + * compare the value, but we can't here: we hold a + * non-sleepable lock, but reading userspace memory acquires a + * sleepable lock. So in that case, trigger immediately and + * let the user deal with it. + */ + if (kev->data != kn->kn_sdata || kev->fflags != kn->kn_sfflags) { + struct umtx_q *uq; + + kn->kn_hookid = 1; + kn->kn_sdata = kev->data; + kn->kn_fflags = kn->kn_sfflags; + + uq = kn->kn_ptr.p_v; + if (uq->uq_flags & UQF_KQUEUE_ONCE) + umtxq_remove(uq); + } + break; + case EVENT_PROCESS: + *kev = kn->kn_kevent; + kn->kn_hookid = 0; + break; + default: + panic("filt_usermemtouch() - invalid type (%ld)", type); + break; + } +} + +int +filt_usermemanon(struct kevent *kev) +{ + int is_private; + void *uaddr; + int n_wake; + int error; + + if (kev->fflags != NOTE_TRIGGER) + return (EINVAL); + + is_private = (kev->fflags & NOTE_USERMEM_PRIVATE) != 0; + uaddr = (void *)kev->ident; + n_wake = kev->data; + + error = kern_umtx_wake(curthread, uaddr, n_wake, is_private); + + return (error); +} diff --git a/sys/sys/event.h b/sys/sys/event.h --- a/sys/sys/event.h +++ b/sys/sys/event.h @@ -34,6 +34,8 @@ #include #include +#define KQUEUE_FD_ANON (-2) /* anonymous temporary kqueue */ + #define EVFILT_READ (-1) #define EVFILT_WRITE (-2) #define EVFILT_AIO (-3) /* attached to aio requests */ @@ -47,7 +49,8 @@ #define EVFILT_USER (-11) /* User events */ #define EVFILT_SENDFILE (-12) /* attached to sendfile requests */ #define EVFILT_EMPTY (-13) /* empty send socket buf */ -#define EVFILT_SYSCOUNT 13 +#define EVFILT_USERMEM (-14) /* attached to value in user memory */ +#define EVFILT_SYSCOUNT 14 #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L #define EV_SET(kevp_, a, b, c, d, e, f) do { \ @@ -145,6 +148,7 @@ #define EV_CLEAR 0x0020 /* clear event state after reporting */ #define EV_RECEIPT 0x0040 /* force EV_ERROR on success, data=0 */ #define EV_DISPATCH 0x0080 /* disable event after reporting */ +#define EV_ANON 0x0400 /* anonymous action (not specific kq) */ #define EV_SYSFLAGS 0xF000 /* reserved by system */ #define EV_DROP 0x1000 /* note should be dropped */ @@ -218,6 +222,11 @@ #define NOTE_NSECONDS 0x00000008 /* data is nanoseconds */ #define NOTE_ABSTIME 0x00000010 /* timeout is absolute */ +/* addition flags for EVFILT_USERMEM */ +#define NOTE_USERMEM_INT 0x00000001 /* ident is pointer to int */ +#define NOTE_USERMEM_LONG 0x00000002 /* ident is pointer to long */ +#define NOTE_USERMEM_PRIVATE 0x00000004 /* no inter-process events */ + struct knote; SLIST_HEAD(klist, knote); struct kqueue; @@ -267,6 +276,7 @@ void (*f_detach)(struct knote *kn); int (*f_event)(struct knote *kn, long hint); void (*f_touch)(struct knote *kn, struct kevent *kev, u_long type); + int (*f_anon)(struct kevent *kev); }; /* diff --git a/sys/sys/syscallsubr.h b/sys/sys/syscallsubr.h --- a/sys/sys/syscallsubr.h +++ b/sys/sys/syscallsubr.h @@ -185,8 +185,8 @@ int kern_jail_set(struct thread *td, struct uio *options, int flags); int kern_kevent(struct thread *td, int fd, int nchanges, int nevents, struct kevent_copyops *k_ops, const struct timespec *timeout); -int kern_kevent_anonymous(struct thread *td, int nevents, - struct kevent_copyops *k_ops); +int kern_kevent_anonymous(struct thread *td, int nchanges, int nevents, + struct kevent_copyops *k_ops, const struct timespec *timeout); int kern_kevent_fp(struct thread *td, struct file *fp, int nchanges, int nevents, struct kevent_copyops *k_ops, const struct timespec *timeout); diff --git a/sys/sys/umtx.h b/sys/sys/umtx.h --- a/sys/sys/umtx.h +++ b/sys/sys/umtx.h @@ -138,4 +138,13 @@ __END_DECLS +#ifdef _KERNEL +struct knote; +int filt_usermemattach(struct knote *kn); +void filt_usermemdetach(struct knote *kn); +int filt_usermem(struct knote *kn, long hint); +void filt_usermemtouch(struct knote *kn, struct kevent *touch, u_long type); +int filt_usermemanon(struct kevent *kev); +#endif + #endif /* !_SYS_UMTX_H_ */ diff --git a/sys/sys/umtxvar.h b/sys/sys/umtxvar.h --- a/sys/sys/umtxvar.h +++ b/sys/sys/umtxvar.h @@ -120,6 +120,8 @@ /* Umtx flags. */ int uq_flags; #define UQF_UMTXQ 0x0001 +#define UQF_KQUEUE 0x0002 +#define UQF_KQUEUE_ONCE 0x0004 /* Futex bitset mask */ u_int uq_bitset; @@ -148,6 +150,9 @@ /* The queue we on */ struct umtxq_queue *uq_cur_queue; + + /* List to hold knote, if waiting with kevent. */ + struct knlist uq_klist; }; TAILQ_HEAD(umtxq_head, umtx_q); diff --git a/tests/sys/kqueue/Makefile b/tests/sys/kqueue/Makefile --- a/tests/sys/kqueue/Makefile +++ b/tests/sys/kqueue/Makefile @@ -6,6 +6,7 @@ BINDIR= ${TESTSDIR} ATF_TESTS_C+= kqueue_peek_signal +ATF_TESTS_C+= kqueue_usermem NETBSD_ATF_TESTS_C= proc1_test # XXX: fails `ke.fflags & NOTE_TRACKERR` invariant diff --git a/tests/sys/kqueue/kqueue_usermem.c b/tests/sys/kqueue/kqueue_usermem.c new file mode 100644 --- /dev/null +++ b/tests/sys/kqueue/kqueue_usermem.c @@ -0,0 +1,320 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * 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 /* only needed to test _umtx_op iterop */ +#include + +#include +#include +#include +#include +#include + +ATF_TC_WITHOUT_HEAD(main); + +ATF_TC_BODY(main, tc) +{ + struct timespec zero_timeout = {0, 0}; + u_long *shmem; + int shmem_fd; + int rv; + + shmem_fd = shm_open(SHM_ANON, O_CREAT | O_RDWR, 0); + ATF_REQUIRE(shmem_fd >= 0); + + rv = ftruncate(shmem_fd, sizeof(*shmem)); + ATF_REQUIRE(rv == 0); + + shmem = mmap(NULL, sizeof(*shmem), PROT_READ | PROT_WRITE, + MAP_SHARED, shmem_fd, 0); + ATF_REQUIRE(shmem != MAP_FAILED); + + int kq = kqueue(); + ATF_REQUIRE(kq >= 0); + + struct kevent kev; + struct kevent out_kev; + + /* Fires immediately because value is not as expected. */ + *shmem = 0; + EV_SET(&kev, (uintptr_t) shmem, EVFILT_USERMEM, EV_ADD | EV_ONESHOT, + NOTE_USERMEM_LONG, 42, 0); + rv = kevent(kq, &kev, 1, &out_kev, 1, &zero_timeout); + ATF_CHECK_EQ(1, rv); + ATF_CHECK_EQ(out_kev.ident, (uintptr_t) shmem); + + /* Doesn't fire immediately because value is as expected. */ + *shmem = 42; + EV_SET(&kev, (uintptr_t) shmem, EVFILT_USERMEM, EV_ADD | EV_ONESHOT, + NOTE_USERMEM_LONG, 42, 0); + rv = kevent(kq, &kev, 1, &out_kev, 1, &zero_timeout); + ATF_REQUIRE_EQ(0, rv); + + /* + * Re-adding, with a different expected value. This causes it to fire + * (not because the expected value doesn't match *shmem, but because it + * doesn't match what we originally added). + */ + *shmem = 42; + EV_SET(&kev, (uintptr_t) shmem, EVFILT_USERMEM, EV_ADD | EV_ONESHOT, + NOTE_USERMEM_LONG, 43, 0); + rv = kevent(kq, &kev, 1, &out_kev, 1, &zero_timeout); + ATF_REQUIRE_EQ(1, rv); + ATF_CHECK_EQ(out_kev.ident, (uintptr_t) shmem); + + /* Try to wake it using _umtx_op. */ + rv = _umtx_op(shmem, UMTX_OP_WAKE, 1, NULL, NULL); + ATF_REQUIRE_EQ(0, rv); + + /* No event should be received (it was one-shot). */ + rv = kevent(kq, NULL, 0, &out_kev, 1, &zero_timeout); + ATF_REQUIRE_EQ(0, rv); + + /* Add it again, with the right value, this time not one-shot. */ + *shmem = 42; + EV_SET(&kev, (uintptr_t) shmem, EVFILT_USERMEM, EV_ADD, + NOTE_USERMEM_LONG, 42, 0); + rv = kevent(kq, &kev, 1, &out_kev, 1, &zero_timeout); + ATF_REQUIRE_EQ(0, rv); + + /* Try to wake it with the right address. */ + rv = _umtx_op(shmem, UMTX_OP_WAKE, 1, NULL, NULL); + ATF_REQUIRE_EQ(0, rv); + + /* Should be woken. */ + rv = kevent(kq, NULL, 0, &out_kev, 1, &zero_timeout); + ATF_CHECK_EQ(1, rv); + ATF_CHECK_EQ(out_kev.ident, (uintptr_t) shmem); + + /* And again. */ + rv = _umtx_op(shmem, UMTX_OP_WAKE, 1, NULL, NULL); + ATF_REQUIRE_EQ(0, rv); + + /* Should be woken again, it's still there... */ + rv = kevent(kq, NULL, 0, &out_kev, 1, &zero_timeout); + ATF_CHECK_EQ(1, rv); + ATF_CHECK_EQ(out_kev.ident, (uintptr_t) shmem); + + /* Delete it. */ + EV_SET(&kev, (uintptr_t) shmem, EVFILT_USERMEM, EV_DELETE, + NOTE_USERMEM_LONG, 0, 0); + rv = kevent(kq, &kev, 1, &out_kev, 1, &zero_timeout); + ATF_REQUIRE_EQ(0, rv); + + /* Shouldn't be woken, now deleted. */ + rv = _umtx_op(shmem, UMTX_OP_WAKE, 1, NULL, NULL); + ATF_REQUIRE_EQ(0, rv); + rv = kevent(kq, NULL, 0, &out_kev, 1, &zero_timeout); + ATF_CHECK_EQ(0, rv); + + /* Add it again, with the right value. */ + *shmem = 42; + EV_SET(&kev, (uintptr_t) shmem, EVFILT_USERMEM, EV_ADD | EV_ONESHOT, + NOTE_USERMEM_LONG, 42, 0); + rv = kevent(kq, &kev, 1, &out_kev, 1, &zero_timeout); + ATF_REQUIRE_EQ(0, rv); + + /* Try to wake some other umtx address. */ + rv = _umtx_op(shmem + 1, UMTX_OP_WAKE, 1, NULL, NULL); + ATF_REQUIRE_EQ(0, rv); + + /* Shouldn't be woken, because that was the wrong address. */ + rv = kevent(kq, NULL, 0, &out_kev, 1, &zero_timeout); + ATF_CHECK_EQ(0, rv); + + /* Try to wake it with the right address. */ + rv = _umtx_op(shmem, UMTX_OP_WAKE, 1, NULL, NULL); + ATF_REQUIRE_EQ(0, rv); + + /* Should be woken. */ + rv = kevent(kq, NULL, 0, &out_kev, 1, &zero_timeout); + ATF_CHECK_EQ(1, rv); + ATF_CHECK_EQ(out_kev.ident, (uintptr_t) shmem); + + /* Try to wake it again. */ + rv = _umtx_op(shmem, UMTX_OP_WAKE, 1, NULL, NULL); + ATF_REQUIRE_EQ(0, rv); + + /* Shouldn't be woken, because it was one-shot. */ + rv = kevent(kq, NULL, 0, &out_kev, 1, &zero_timeout); + ATF_CHECK_EQ(0, rv); + + /* Add. Not woken yet. */ + *shmem = 42; + EV_SET(&kev, (uintptr_t) shmem, EVFILT_USERMEM, EV_ADD | EV_ONESHOT, + NOTE_USERMEM_LONG, 42, 0); + rv = kevent(kq, &kev, 1, &out_kev, 1, &zero_timeout); + ATF_REQUIRE_EQ(0, rv); + + /* Wake it with NOTE_TRIGGER sent to the same kqueue. */ + EV_SET(&kev, (uintptr_t) shmem, EVFILT_USERMEM, EV_ANON, + NOTE_TRIGGER, 1, 0); + rv = kevent(kq, &kev, 1, NULL, 0, &zero_timeout); + ATF_REQUIRE_EQ(0, rv); + + /* Should be woken. */ + rv = kevent(kq, NULL, 0, &out_kev, 1, &zero_timeout); + ATF_CHECK_EQ(1, rv); + ATF_CHECK_EQ(out_kev.ident, (uintptr_t) shmem); + + /* Add. Not woken yet. */ + *shmem = 42; + EV_SET(&kev, (uintptr_t) shmem, EVFILT_USERMEM, EV_ADD | EV_ONESHOT, + NOTE_USERMEM_LONG, 42, 0); + rv = kevent(kq, &kev, 1, &out_kev, 1, &zero_timeout); + ATF_REQUIRE_EQ(0, rv); + + /* Wake it with NOTE_TRIGGER sent with another kqueue. */ + int kq2 = kqueue(); + ATF_REQUIRE(kq2 >= 0); + EV_SET(&kev, (uintptr_t) shmem, EVFILT_USERMEM, EV_ANON, + NOTE_TRIGGER, 1, 0); + rv = kevent(kq2, &kev, 1, &out_kev, 1, &zero_timeout); + ATF_REQUIRE_EQ(0, rv); + + /* Should be woken. */ + rv = kevent(kq, NULL, 0, &out_kev, 1, &zero_timeout); + ATF_CHECK_EQ(1, rv); + ATF_CHECK_EQ(out_kev.ident, (uintptr_t) shmem); + + /* Add to both kqueues, not woken yet. */ + *shmem = 42; + EV_SET(&kev, (uintptr_t) shmem, EVFILT_USERMEM, EV_ADD | EV_ONESHOT, + NOTE_USERMEM_LONG, 42, 0); + rv = kevent(kq, &kev, 1, &out_kev, 1, &zero_timeout); + ATF_REQUIRE_EQ(0, rv); + *shmem = 42; + EV_SET(&kev, (uintptr_t) shmem, EVFILT_USERMEM, EV_ADD | EV_ONESHOT, + NOTE_USERMEM_LONG, 42, 0); + rv = kevent(kq2, &kev, 1, &out_kev, 1, &zero_timeout); + ATF_REQUIRE_EQ(0, rv); + + /* Wake it with NOTE_TRIGGER sent using one of them. */ + EV_SET(&kev, (uintptr_t) shmem, EVFILT_USERMEM, EV_ANON, + NOTE_TRIGGER, 1000, 0); + rv = kevent(kq, &kev, 1, NULL, 0, &zero_timeout); + ATF_REQUIRE_EQ(0, rv); + + /* Should be woken in both kqueues. */ + rv = kevent(kq, NULL, 0, &out_kev, 1, &zero_timeout); + ATF_CHECK_EQ(1, rv); + ATF_CHECK_EQ(out_kev.ident, (uintptr_t) shmem); + rv = kevent(kq2, NULL, 0, &out_kev, 1, &zero_timeout); + ATF_CHECK_EQ(1, rv); + ATF_CHECK_EQ(out_kev.ident, (uintptr_t) shmem); + + /* Add. Not woken yet. */ + *shmem = 42; + EV_SET(&kev, (uintptr_t) shmem, EVFILT_USERMEM, EV_ADD | EV_ONESHOT, + NOTE_USERMEM_LONG, 42, 0); + rv = kevent(kq, &kev, 1, &out_kev, 1, &zero_timeout); + ATF_REQUIRE_EQ(0, rv); + + /* Wake it with NOTE_TRIGGER sent with KQUEUE_FD_ANON */ + EV_SET(&kev, (uintptr_t) shmem, EVFILT_USERMEM, EV_ANON, + NOTE_TRIGGER, 1000, 0); + rv = kevent(KQUEUE_FD_ANON, &kev, 1, NULL, 0, &zero_timeout); + ATF_REQUIRE_EQ(0, rv); + + /* Should be woken. */ + rv = kevent(kq, NULL, 0, &out_kev, 1, &zero_timeout); + ATF_CHECK_EQ(1, rv); + ATF_CHECK_EQ(out_kev.ident, (uintptr_t) shmem); + + /* Add. Not woken yet. */ + *shmem = 42; + EV_SET(&kev, (uintptr_t) shmem, EVFILT_USERMEM, EV_ADD | EV_ONESHOT, + NOTE_USERMEM_LONG, 42, 0); + rv = kevent(kq, &kev, 1, &out_kev, 1, &zero_timeout); + ATF_REQUIRE_EQ(0, rv); + + /* Wake it from another process. */ + pid_t pid = fork(); + if (pid == 0) { + /* Child wakes. */ + _umtx_op(shmem, UMTX_OP_WAKE, 1, NULL, NULL); + _Exit(0); + } else { + /* Parent waits for child. */ + ATF_REQUIRE(pid > 0); + int status; + wait(&status); + } + + /* Should be woken. */ + rv = kevent(kq, NULL, 0, &out_kev, 1, &zero_timeout); + ATF_CHECK_EQ(1, rv); + ATF_CHECK_EQ(out_kev.ident, (uintptr_t) shmem); + + /* Try to wake it again. */ + rv = _umtx_op(shmem, UMTX_OP_WAKE, 1, NULL, NULL); + ATF_REQUIRE_EQ(0, rv); + + /* Shouldn't be woken, because it was one-shot. */ + rv = kevent(kq, NULL, 0, &out_kev, 1, &zero_timeout); + ATF_CHECK_EQ(0, rv); + + /* Add, in process-private namespace. Not woken yet. */ + *shmem = 42; + EV_SET(&kev, (uintptr_t) shmem, EVFILT_USERMEM, EV_ADD | EV_ONESHOT, + NOTE_USERMEM_LONG | NOTE_USERMEM_PRIVATE, 42, 0); + rv = kevent(kq, &kev, 1, &out_kev, 1, &zero_timeout); + ATF_REQUIRE_EQ(0, rv); + + /* Try to wake it from another process. */ + pid = fork(); + if (pid == 0) { + /* Child wakes. */ + _umtx_op(shmem, UMTX_OP_WAKE, 1, NULL, NULL); + _Exit(0); + } else { + /* Parent waits for child. */ + ATF_REQUIRE(pid > 0); + int status; + wait(&status); + } + + /* Shouldn't be woken, because other process couldn't see it. */ + rv = kevent(kq, NULL, 0, &out_kev, 1, &zero_timeout); + ATF_CHECK_EQ(0, rv); + + /* This process can see it. */ + _umtx_op(shmem, UMTX_OP_WAKE_PRIVATE, 1, NULL, NULL); + + /* Should be woken. */ + rv = kevent(kq, NULL, 0, &out_kev, 1, &zero_timeout); + ATF_CHECK_EQ(1, rv); + ATF_CHECK_EQ(out_kev.ident, (uintptr_t) shmem); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, main); + + return (atf_no_error()); +}