The system revokes open files for a tty device when the device is being
reused. The revoking has applied to the old poll/select implementations
sort of automagically because the scan code has always (re)walked the
path from file to vnode to device. With kqueue, the rewalking does not
happen in particular when event registrations are preserved between
system calls.

Ideally, the revoking of a tty device should invalidate any associated
knotes. The invalidation could be done in the device close routine.
However, the revoking takes full effect only after vclean() has changed
the vnode v_op pointer to dead_vops. The kevent() system call is already
unlocked, so programs can in principle register new knotes until the
pointer update becomes visible to other threads.

To avoid going down the rabbit hole of vnode revoking right now, the diff
below adds just-in-time fixing of knotes that should be cleared. It stops
programs from keeping their knotes on revoked ttys. It is ugly but does
not litter the already complex tty code with kqueue-specific stopgaps.

OK?

Index: kern/kern_event.c
===================================================================
RCS file: src/sys/kern/kern_event.c,v
retrieving revision 1.177
diff -u -p -r1.177 kern_event.c
--- kern/kern_event.c   20 Dec 2021 16:24:32 -0000      1.177
+++ kern/kern_event.c   23 Dec 2021 14:20:54 -0000
@@ -55,6 +55,7 @@
 #include <sys/syscallargs.h>
 #include <sys/time.h>
 #include <sys/timeout.h>
+#include <sys/vnode.h>
 #include <sys/wait.h>
 
 #ifdef DIAGNOSTIC
@@ -1381,6 +1396,31 @@ retry:
                        continue;
                }
 
+               /* Fix knotes whose vnodes have been revoked. */
+               if ((kn->kn_fop->f_flags & FILTEROP_ISFD) &&
+                   kn->kn_fp != NULL &&
+                   kn->kn_fp->f_type == DTYPE_VNODE) {
+                       struct vnode *vp = kn->kn_fp->f_data;
+
+                       if (__predict_false(vp->v_op == &dead_vops &&
+                           kn->kn_fop != &dead_filtops)) {
+                               filter_detach(kn);
+                               kn->kn_fop = &dead_filtops;
+
+                               /*
+                                * Check if the event should be delivered.
+                                * Use f_event directly because this is
+                                * a special situation.
+                                */
+                               if (kn->kn_fop->f_event(kn, 0) == 0) {
+                                       filter_detach(kn);
+                                       knote_drop(kn, p);
+                                       mtx_enter(&kq->kq_lock);
+                                       continue;
+                               }
+                       }
+               }
+
                memset(kevp, 0, sizeof(*kevp));
                if (filter_process(kn, kevp) == 0) {
                        mtx_enter(&kq->kq_lock);

Reply via email to