From 0321a7990b277702fa0b4f8366121bf53d03cb64 Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Thu, 23 Sep 2021 17:31:39 -0700 Subject: [PATCH] kqueue: Add EV_KEEPUDATA flag When this flag is set, operations that update an existing kevent will not change the udata field. This can be used to NOTE_TRIGGER or EV_{EN,DIS}ABLE events without overwriting the stashed pointer. Reviewed by: Domagoj Stolfa Obtained from: CheriBSD Sponsored by: Microsoft Differential Revision: https://reviews.freebsd.org/D30286 --- lib/libc/sys/kqueue.2 | 17 ++++++++- sys/kern/kern_event.c | 10 +++++- sys/sys/event.h | 1 + tests/sys/kqueue/libkqueue/user.c | 60 +++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 2 deletions(-) diff --git a/lib/libc/sys/kqueue.2 b/lib/libc/sys/kqueue.2 index ed737c626ef8..3ded4ae3d8f7 100644 --- a/lib/libc/sys/kqueue.2 +++ b/lib/libc/sys/kqueue.2 @@ -24,7 +24,7 @@ .\" .\" $FreeBSD$ .\" -.Dd September 7, 2021 +.Dd September 23, 2021 .Dt KQUEUE 2 .Os .Sh NAME @@ -262,6 +262,21 @@ Filters may set this flag to indicate filter-specific EOF condition. See .Sx RETURN VALUES below. +.It Dv EV_KEEPUDATA +Causes +.Fn kevent +to leave unchanged any +.Fa udata +associated with an existing event. This allows other aspects of the +event to be modified without requiring the caller to know the +.Fa udata +value presently associated. +This is especially useful with +.Dv NOTE_TRIGGER +or flags like +.Dv EV_ENABLE. +This flag may not be used with +.Dv EV_ADD. .El .Pp The predefined system filters are listed below. diff --git a/sys/kern/kern_event.c b/sys/kern/kern_event.c index db505b234268..5fa5bf9cad06 100644 --- a/sys/kern/kern_event.c +++ b/sys/kern/kern_event.c @@ -1496,6 +1496,13 @@ kqueue_register(struct kqueue *kq, struct kevent *kev, struct thread *td, return EINVAL; if (kev->flags & EV_ADD) { + /* Reject an invalid flag pair early */ + if (kev->flags & EV_KEEPUDATA) { + tkn = NULL; + error = EINVAL; + goto done; + } + /* * Prevent waiting with locks. Non-sleepable * allocation failures are handled in the loop, only @@ -1684,7 +1691,8 @@ kqueue_register(struct kqueue *kq, struct kevent *kev, struct thread *td, kn_enter_flux(kn); KQ_UNLOCK(kq); knl = kn_list_lock(kn); - kn->kn_kevent.udata = kev->udata; + if ((kev->flags & EV_KEEPUDATA) == 0) + kn->kn_kevent.udata = kev->udata; if (!fops->f_isfd && fops->f_touch != NULL) { fops->f_touch(kn, kev, EVENT_REGISTER); } else { diff --git a/sys/sys/event.h b/sys/sys/event.h index 80ed1268c8a1..f1bdc7e2a80e 100644 --- a/sys/sys/event.h +++ b/sys/sys/event.h @@ -138,6 +138,7 @@ struct kevent32_freebsd11 { #define EV_ENABLE 0x0004 /* enable event */ #define EV_DISABLE 0x0008 /* disable event (not reported) */ #define EV_FORCEONESHOT 0x0100 /* enable _ONESHOT and force trigger */ +#define EV_KEEPUDATA 0x0200 /* do not update the udata field */ /* flags */ #define EV_ONESHOT 0x0010 /* only report one occurrence */ diff --git a/tests/sys/kqueue/libkqueue/user.c b/tests/sys/kqueue/libkqueue/user.c index 3844251ff4ba..1f66234c4cda 100644 --- a/tests/sys/kqueue/libkqueue/user.c +++ b/tests/sys/kqueue/libkqueue/user.c @@ -60,6 +60,32 @@ event_wait(void) success(); } +static void +event_wait_keepudata(void) +{ + const char *test_id = "kevent(EVFILT_USER, wait w/ EV_KEEPUDATA)"; + struct kevent kev; + + test_begin(test_id); + + test_no_kevents(); + + kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, &kev); + kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_KEEPUDATA, NOTE_TRIGGER, 0, + NULL); + + kev.fflags &= ~NOTE_FFCTRLMASK; + kev.fflags &= ~NOTE_TRIGGER; + kev.flags = EV_CLEAR; + kev.udata = &kev; + kevent_cmp(&kev, kevent_get(kqfd)); + + test_no_kevents(); + + success(); +} + + static void disable_and_enable(void) { @@ -88,6 +114,38 @@ disable_and_enable(void) success(); } +static void +disable_and_enable_keepudata(void) +{ + const char *test_id = + "kevent(EVFILT_USER, EV_DISABLE and EV_ENABLE w/ EV_KEEPUDATA)"; + struct kevent kev; + + test_begin(test_id); + + test_no_kevents(); + + kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_ADD, 0, 0, &kev); + kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_DISABLE | EV_KEEPUDATA, 0, 0, + NULL); + + /* Trigger the event, but since it is disabled, nothing will happen. */ + kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_KEEPUDATA, NOTE_TRIGGER, 0, NULL); + test_no_kevents(); + + kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_ENABLE | EV_KEEPUDATA, 0, 0, + NULL); + kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_KEEPUDATA, NOTE_TRIGGER, 0, NULL); + + kev.flags = EV_CLEAR; + kev.fflags &= ~NOTE_FFCTRLMASK; + kev.fflags &= ~NOTE_TRIGGER; + kev.udata = &kev; + kevent_cmp(&kev, kevent_get(kqfd)); + + success(); +} + static void oneshot(void) { @@ -120,7 +178,9 @@ test_evfilt_user(void) add_and_delete(); event_wait(); + event_wait_keepudata(); disable_and_enable(); + disable_and_enable_keepudata(); oneshot(); /* TODO: try different fflags operations */