Module Name:    src
Committed By:   thorpej
Date:           Wed Oct 20 03:08:19 UTC 2021

Modified Files:
        src/external/cddl/osnet/dist/uts/common/fs/zfs: zfs_vnops.c
        src/lib/libc/sys: kqueue.2
        src/sys/coda: coda_vnops.c
        src/sys/fs/msdosfs: msdosfs_vnops.c
        src/sys/fs/nilfs: nilfs_vnops.c
        src/sys/fs/ptyfs: ptyfs_vnops.c
        src/sys/fs/puffs: puffs_vnops.c
        src/sys/fs/sysvbfs: sysvbfs_vnops.c
        src/sys/fs/tmpfs: tmpfs_rename.c tmpfs_subr.c tmpfs_vnops.c
        src/sys/fs/udf: udf_rename.c udf_vnops.c
        src/sys/fs/union: union_vnops.c
        src/sys/fs/unionfs: unionfs_vnops.c
        src/sys/fs/v7fs: v7fs_vnops.c
        src/sys/kern: vfs_vnode.c vfs_vnops.c vnode_if.sh vnode_if.src
        src/sys/miscfs/deadfs: dead_vnops.c
        src/sys/miscfs/genfs: genfs.h genfs_rename.c genfs_vnops.c
            layer_vnops.c
        src/sys/miscfs/umapfs: umap_vnops.c
        src/sys/nfs: nfs_bio.c nfs_kq.c nfs_vnops.c
        src/sys/rump/librump/rumpvfs: rumpfs.c
        src/sys/sys: event.h vnode.h
        src/sys/ufs/chfs: chfs_vnops.c
        src/sys/ufs/ext2fs: ext2fs_readwrite.c ext2fs_rename.c ext2fs_vnops.c
        src/sys/ufs/lfs: lfs_rename.c lfs_vnops.c ulfs_readwrite.c ulfs_vnops.c
        src/sys/ufs/ufs: ufs_acl.c ufs_extern.h ufs_readwrite.c ufs_rename.c
            ufs_vnops.c
        src/tests/kernel/kqueue: t_vnode.c

Log Message:
Overhaul of the EVFILT_VNODE kevent(2) filter:

- Centralize vnode kevent handling in the VOP_*() wrappers, rather than
  forcing each individual file system to deal with it (except VOP_RENAME(),
  because VOP_RENAME() is a mess and we currently have 2 different ways
  of handling it; at least it's reasonably well-centralized in the "new"
  way).
- Add support for NOTE_OPEN, NOTE_CLOSE, NOTE_CLOSE_WRITE, and NOTE_READ,
  compatible with the same events in FreeBSD.
- Track which kevent notifications clients are interested in receiving
  to avoid doing work for events no one cares about (avoiding, e.g.
  taking locks and traversing the klist to send a NOTE_WRITE when
  someone is merely watching for a file to be deleted, for example).

In support of the above:

- Add support in vnode_if.sh for specifying PRE- and POST-op handlers,
  to be invoked before and after vop_pre() and vop_post(), respectively.
  Basic idea from FreeBSD, but implemented differently.
- Add support in vnode_if.sh for specifying CONTEXT fields in the
  vop_*_args structures.  These context fields are used to convey information
  between the file system VOP function and the VOP wrapper, but do not
  occupy an argument slot in the VOP_*() call itself.  These context fields
  are initialized and subsequently interpreted by PRE- and POST-op handlers.
- Version VOP_REMOVE(), uses the a context field for the file system to report
  back the resulting link count of the target vnode.  Return this in tmpfs,
  udf, nfs, chfs, ext2fs, lfs, and ufs.

NetBSD 9.99.92.


To generate a diff of this commit:
cvs rdiff -u -r1.75 -r1.76 \
    src/external/cddl/osnet/dist/uts/common/fs/zfs/zfs_vnops.c
cvs rdiff -u -r1.54 -r1.55 src/lib/libc/sys/kqueue.2
cvs rdiff -u -r1.115 -r1.116 src/sys/coda/coda_vnops.c
cvs rdiff -u -r1.106 -r1.107 src/sys/fs/msdosfs/msdosfs_vnops.c
cvs rdiff -u -r1.44 -r1.45 src/sys/fs/nilfs/nilfs_vnops.c
cvs rdiff -u -r1.66 -r1.67 src/sys/fs/ptyfs/ptyfs_vnops.c
cvs rdiff -u -r1.222 -r1.223 src/sys/fs/puffs/puffs_vnops.c
cvs rdiff -u -r1.67 -r1.68 src/sys/fs/sysvbfs/sysvbfs_vnops.c
cvs rdiff -u -r1.10 -r1.11 src/sys/fs/tmpfs/tmpfs_rename.c
cvs rdiff -u -r1.113 -r1.114 src/sys/fs/tmpfs/tmpfs_subr.c
cvs rdiff -u -r1.147 -r1.148 src/sys/fs/tmpfs/tmpfs_vnops.c
cvs rdiff -u -r1.13 -r1.14 src/sys/fs/udf/udf_rename.c
cvs rdiff -u -r1.116 -r1.117 src/sys/fs/udf/udf_vnops.c
cvs rdiff -u -r1.78 -r1.79 src/sys/fs/union/union_vnops.c
cvs rdiff -u -r1.16 -r1.17 src/sys/fs/unionfs/unionfs_vnops.c
cvs rdiff -u -r1.31 -r1.32 src/sys/fs/v7fs/v7fs_vnops.c
cvs rdiff -u -r1.127 -r1.128 src/sys/kern/vfs_vnode.c
cvs rdiff -u -r1.223 -r1.224 src/sys/kern/vfs_vnops.c
cvs rdiff -u -r1.71 -r1.72 src/sys/kern/vnode_if.sh
cvs rdiff -u -r1.82 -r1.83 src/sys/kern/vnode_if.src
cvs rdiff -u -r1.65 -r1.66 src/sys/miscfs/deadfs/dead_vnops.c
cvs rdiff -u -r1.37 -r1.38 src/sys/miscfs/genfs/genfs.h
cvs rdiff -u -r1.5 -r1.6 src/sys/miscfs/genfs/genfs_rename.c
cvs rdiff -u -r1.215 -r1.216 src/sys/miscfs/genfs/genfs_vnops.c
cvs rdiff -u -r1.71 -r1.72 src/sys/miscfs/genfs/layer_vnops.c
cvs rdiff -u -r1.61 -r1.62 src/sys/miscfs/umapfs/umap_vnops.c
cvs rdiff -u -r1.199 -r1.200 src/sys/nfs/nfs_bio.c
cvs rdiff -u -r1.31 -r1.32 src/sys/nfs/nfs_kq.c
cvs rdiff -u -r1.320 -r1.321 src/sys/nfs/nfs_vnops.c
cvs rdiff -u -r1.165 -r1.166 src/sys/rump/librump/rumpvfs/rumpfs.c
cvs rdiff -u -r1.48 -r1.49 src/sys/sys/event.h
cvs rdiff -u -r1.297 -r1.298 src/sys/sys/vnode.h
cvs rdiff -u -r1.45 -r1.46 src/sys/ufs/chfs/chfs_vnops.c
cvs rdiff -u -r1.77 -r1.78 src/sys/ufs/ext2fs/ext2fs_readwrite.c
cvs rdiff -u -r1.11 -r1.12 src/sys/ufs/ext2fs/ext2fs_rename.c
cvs rdiff -u -r1.135 -r1.136 src/sys/ufs/ext2fs/ext2fs_vnops.c
cvs rdiff -u -r1.24 -r1.25 src/sys/ufs/lfs/lfs_rename.c
cvs rdiff -u -r1.339 -r1.340 src/sys/ufs/lfs/lfs_vnops.c
cvs rdiff -u -r1.27 -r1.28 src/sys/ufs/lfs/ulfs_readwrite.c
cvs rdiff -u -r1.54 -r1.55 src/sys/ufs/lfs/ulfs_vnops.c
cvs rdiff -u -r1.2 -r1.3 src/sys/ufs/ufs/ufs_acl.c
cvs rdiff -u -r1.87 -r1.88 src/sys/ufs/ufs/ufs_extern.h
cvs rdiff -u -r1.126 -r1.127 src/sys/ufs/ufs/ufs_readwrite.c
cvs rdiff -u -r1.13 -r1.14 src/sys/ufs/ufs/ufs_rename.c
cvs rdiff -u -r1.259 -r1.260 src/sys/ufs/ufs/ufs_vnops.c
cvs rdiff -u -r1.1 -r1.2 src/tests/kernel/kqueue/t_vnode.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/external/cddl/osnet/dist/uts/common/fs/zfs/zfs_vnops.c
diff -u src/external/cddl/osnet/dist/uts/common/fs/zfs/zfs_vnops.c:1.75 src/external/cddl/osnet/dist/uts/common/fs/zfs/zfs_vnops.c:1.76
--- src/external/cddl/osnet/dist/uts/common/fs/zfs/zfs_vnops.c:1.75	Mon Sep  6 08:37:43 2021
+++ src/external/cddl/osnet/dist/uts/common/fs/zfs/zfs_vnops.c	Wed Oct 20 03:08:19 2021
@@ -4477,7 +4477,7 @@ zfs_rename(vnode_t *sdvp, vnode_t **svpp
 				    NOTE_DELETE : NOTE_LINK));
 			} else {
 				genfs_rename_knote(sdvp, *svpp, tdvp, *tvpp,
-				    ((tzp != NULL) && (tzp->z_links == 0)));
+				    tzp != NULL ? tzp->z_links : 0);
 			}
 #endif
 		}
@@ -5147,9 +5147,6 @@ zfs_netbsd_write(void *v)
 
 	resid = uio->uio_resid;
 	error = zfs_write(vp, uio, ioflags(ap->a_ioflag), ap->a_cred, NULL);
-	if (resid > uio->uio_resid)
-		VN_KNOTE(vp, NOTE_WRITE |
-		    (zp->z_size > osize ? NOTE_EXTEND : 0));
 
 	return error;
 }
@@ -5342,8 +5339,6 @@ zfs_netbsd_create(void *v)
 
 	KASSERT((error == 0) == (*vpp != NULL));
 	KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE);
-	if (error == 0)
-		VN_KNOTE(dvp, NOTE_WRITE);
 	if (*vpp != NULL)
 		VOP_UNLOCK(*vpp, 0);
 
@@ -5383,8 +5378,6 @@ zfs_netbsd_mknod(void *v)
 
 	KASSERT((error == 0) == (*vpp != NULL));
 	KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE);
-	if (error == 0)
-		VN_KNOTE(dvp, NOTE_WRITE);
 	if (*vpp != NULL)
 		VOP_UNLOCK(*vpp, 0);
 
@@ -5394,10 +5387,11 @@ zfs_netbsd_mknod(void *v)
 static int
 zfs_netbsd_remove(void *v)
 {
-	struct vop_remove_v2_args /* {
+	struct vop_remove_v3_args /* {
 		struct vnode *a_dvp;
 		struct vnode *a_vp;
 		struct componentname *a_cnp;
+		nlink_t ctx_vp_new_nlink;
 	} */ *ap = v;
 	struct vnode *dvp = ap->a_dvp;
 	struct vnode *vp = ap->a_vp;
@@ -5414,11 +5408,12 @@ zfs_netbsd_remove(void *v)
 
 	error = zfs_remove(dvp, vp, nm, cnp->cn_cred);
 
+	/*
+	 * XXX Should update ctx_vp_new_nlink, but for now the
+	 * XXX the kevent sent on "vp"  matches historical behavior.
+	 */
+
 	PNBUF_PUT(nm);
-	if (error == 0) {
-		VN_KNOTE(vp, NOTE_DELETE);
-		VN_KNOTE(dvp, NOTE_WRITE);
-	}
 	vput(vp);
 	KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE);
 	return (error);
@@ -5454,8 +5449,6 @@ zfs_netbsd_mkdir(void *v)
 
 	KASSERT((error == 0) == (*vpp != NULL));
 	KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE);
-	if (error == 0)
-		VN_KNOTE(dvp, NOTE_WRITE | NOTE_LINK);
 	if (*vpp != NULL)
 		VOP_UNLOCK(*vpp, 0);
 
@@ -5486,10 +5479,6 @@ zfs_netbsd_rmdir(void *v)
 	error = zfs_rmdir(dvp, vp, nm, cnp->cn_cred);
 
 	PNBUF_PUT(nm);
-	if (error == 0) {
-		VN_KNOTE(dvp, NOTE_WRITE | NOTE_LINK);
-		VN_KNOTE(vp, NOTE_DELETE);
-	}
 	vput(vp);
 	KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE);
 	return error;
@@ -5653,7 +5642,6 @@ zfs_netbsd_setattr(void *v)
 	if (error)
 		return error;
 
-	VN_KNOTE(vp, NOTE_ATTRIB);
 	cache_enter_id(vp, zp->z_mode, zp->z_uid, zp->z_gid, true);
 
 	return error;
@@ -5662,7 +5650,7 @@ zfs_netbsd_setattr(void *v)
 static int
 zfs_netbsd_rename(void *v)
 {
-	struct vop_rename_args  /* {
+	struct vop_rename_args /* {
 		struct vnode *a_fdvp;
 		struct vnode *a_fvp;
 		struct componentname *a_fcnp;
@@ -5760,8 +5748,6 @@ zfs_netbsd_symlink(void *v)
 	error = zfs_symlink(dvp, vpp, nm, vap, target, cnp->cn_cred, 0);
 
 	PNBUF_PUT(nm);
-	if (error == 0)
-		VN_KNOTE(ap->a_dvp, NOTE_WRITE);
 	KASSERT((error == 0) == (*vpp != NULL));
 	KASSERT(VOP_ISLOCKED(dvp) == LK_EXCLUSIVE);
 	if (*vpp != NULL)
@@ -5803,10 +5789,6 @@ zfs_netbsd_link(void *v)
 	    NULL, 0);
 
 	PNBUF_PUT(nm);
-	if (error == 0) {
-		VN_KNOTE(vp, NOTE_LINK);
-		VN_KNOTE(dvp, NOTE_WRITE);
-	}
 	VOP_UNLOCK(vp, 0);
 	return error;
 }

Index: src/lib/libc/sys/kqueue.2
diff -u src/lib/libc/sys/kqueue.2:1.54 src/lib/libc/sys/kqueue.2:1.55
--- src/lib/libc/sys/kqueue.2:1.54	Wed Oct 13 04:57:19 2021
+++ src/lib/libc/sys/kqueue.2	Wed Oct 20 03:08:19 2021
@@ -1,4 +1,4 @@
-.\"	$NetBSD: kqueue.2,v 1.54 2021/10/13 04:57:19 thorpej Exp $
+.\"	$NetBSD: kqueue.2,v 1.55 2021/10/20 03:08:19 thorpej Exp $
 .\"
 .\" Copyright (c) 2000 Jonathan Lemon
 .\" All rights reserved.
@@ -32,7 +32,7 @@
 .\"
 .\" $FreeBSD: src/lib/libc/sys/kqueue.2,v 1.22 2001/06/27 19:55:57 dd Exp $
 .\"
-.Dd October 11, 2021
+.Dd October 15, 2021
 .Dt KQUEUE 2
 .Os
 .Sh NAME
@@ -440,24 +440,32 @@ Takes a file descriptor as the identifie
 .Va fflags ,
 and returns when one or more of the requested events occurs on the descriptor.
 The events to monitor are:
-.Bl -tag -width XXNOTE_RENAME
+.Bl -tag -width NOTE_CLOSE_WRITE
+.It Dv NOTE_ATTRIB
+The file referenced by the descriptor had its attributes changed.
+.It Dv NOTE_CLOSE
+A file descriptor without write access referencing the file was closed.
+.It Dv NOTE_CLOSE_WRITE
+A file descriptor with write access referencing the file was closed.
 .It Dv NOTE_DELETE
 .Xr unlink 2
 was called on the file referenced by the descriptor.
-.It Dv NOTE_WRITE
-A write occurred on the file referenced by the descriptor.
 .It Dv NOTE_EXTEND
 The file referenced by the descriptor was extended.
-.It Dv NOTE_ATTRIB
-The file referenced by the descriptor had its attributes changed.
 .It Dv NOTE_LINK
 The link count on the file changed.
+.It Dv NOTE_OPEN
+The file refrenced by the descriptor was opened.
+.It Dv NOTE_READ
+A read occurred on the file referenced by the descriptor.
 .It Dv NOTE_RENAME
 The file referenced by the descriptor was renamed.
 .It Dv NOTE_REVOKE
 Access to the file was revoked via
 .Xr revoke 2
 or the underlying file system was unmounted.
+.It Dv NOTE_WRITE
+A write occurred on the file referenced by the descriptor.
 .El
 .Pp
 On return,

Index: src/sys/coda/coda_vnops.c
diff -u src/sys/coda/coda_vnops.c:1.115 src/sys/coda/coda_vnops.c:1.116
--- src/sys/coda/coda_vnops.c:1.115	Tue Jun 29 22:34:05 2021
+++ src/sys/coda/coda_vnops.c	Wed Oct 20 03:08:16 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: coda_vnops.c,v 1.115 2021/06/29 22:34:05 dholland Exp $	*/
+/*	$NetBSD: coda_vnops.c,v 1.116 2021/10/20 03:08:16 thorpej Exp $	*/
 
 /*
  *
@@ -46,7 +46,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: coda_vnops.c,v 1.115 2021/06/29 22:34:05 dholland Exp $");
+__KERNEL_RCSID(0, "$NetBSD: coda_vnops.c,v 1.116 2021/10/20 03:08:16 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -1075,7 +1075,7 @@ int
 coda_remove(void *v)
 {
 /* true args */
-    struct vop_remove_v2_args *ap = v;
+    struct vop_remove_v3_args *ap = v;
     vnode_t *dvp = ap->a_dvp;
     struct cnode *cp = VTOC(dvp);
     vnode_t *vp = ap->a_vp;

Index: src/sys/fs/msdosfs/msdosfs_vnops.c
diff -u src/sys/fs/msdosfs/msdosfs_vnops.c:1.106 src/sys/fs/msdosfs/msdosfs_vnops.c:1.107
--- src/sys/fs/msdosfs/msdosfs_vnops.c:1.106	Sun Jul 18 23:57:14 2021
+++ src/sys/fs/msdosfs/msdosfs_vnops.c	Wed Oct 20 03:08:17 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: msdosfs_vnops.c,v 1.106 2021/07/18 23:57:14 dholland Exp $	*/
+/*	$NetBSD: msdosfs_vnops.c,v 1.107 2021/10/20 03:08:17 thorpej Exp $	*/
 
 /*-
  * Copyright (C) 1994, 1995, 1997 Wolfgang Solfrank.
@@ -48,7 +48,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: msdosfs_vnops.c,v 1.106 2021/07/18 23:57:14 dholland Exp $");
+__KERNEL_RCSID(0, "$NetBSD: msdosfs_vnops.c,v 1.107 2021/10/20 03:08:17 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -151,7 +151,6 @@ msdosfs_create(void *v)
 	DETIMES(&ndirent, NULL, NULL, NULL, pdep->de_pmp->pm_gmtoff);
 	if ((error = createde(&ndirent, pdep, &dep, cnp)) != 0)
 		goto bad;
-	VN_KNOTE(ap->a_dvp, NOTE_WRITE);
 	*ap->a_vpp = DETOV(dep);
 	cache_enter(ap->a_dvp, *ap->a_vpp, cnp->cn_nameptr, cnp->cn_namelen,
 	    cnp->cn_flags);
@@ -427,7 +426,6 @@ msdosfs_setattr(void *v)
 	}
 
 	if (de_changed) {
-		VN_KNOTE(vp, NOTE_ATTRIB);
 		error = deupdat(dep, 1);
 		if (error)
 			goto bad;
@@ -545,7 +543,7 @@ msdosfs_write(void *v)
 		int a_ioflag;
 		kauth_cred_t a_cred;
 	} */ *ap = v;
-	int resid, extended = 0;
+	int resid;
 	int error = 0;
 	int ioflag = ap->a_ioflag;
 	u_long osize;
@@ -625,7 +623,6 @@ msdosfs_write(void *v)
 		if (rem > 0)
 			ubc_zerorange(&vp->v_uobj, (off_t)dep->de_FileSize,
 			    rem, UBC_VNODE_FLAGS(vp));
-		extended = 1;
 	}
 
 	do {
@@ -664,8 +661,6 @@ msdosfs_write(void *v)
 	 * to the size it was before the write was attempted.
 	 */
 errexit:
-	if (resid > uio->uio_resid)
-		VN_KNOTE(vp, NOTE_WRITE | (extended ? NOTE_EXTEND : 0));
 	if (error) {
 		detrunc(dep, osize, ioflag & IO_SYNC, NOCRED);
 		uio->uio_offset -= resid - uio->uio_resid;
@@ -717,10 +712,11 @@ msdosfs_update(struct vnode *vp, const s
 int
 msdosfs_remove(void *v)
 {
-	struct vop_remove_v2_args /* {
+	struct vop_remove_v3_args /* {
 		struct vnode *a_dvp;
 		struct vnode *a_vp;
 		struct componentname *a_cnp;
+		nlink_t ctx_vp_new_nlink;
 	} */ *ap = v;
 	struct denode *dep = VTODE(ap->a_vp);
 	struct denode *ddep = VTODE(ap->a_dvp);
@@ -734,8 +730,6 @@ msdosfs_remove(void *v)
 	printf("msdosfs_remove(), dep %p, usecount %d\n",
 		dep, vrefcnt(ap->a_vp));
 #endif
-	VN_KNOTE(ap->a_vp, NOTE_DELETE);
-	VN_KNOTE(ap->a_dvp, NOTE_WRITE);
 	if (ddep == dep)
 		vrele(ap->a_vp);
 	else
@@ -1255,7 +1249,6 @@ msdosfs_mkdir(void *v)
 	ndirent.de_devvp = pdep->de_devvp;
 	if ((error = createde(&ndirent, pdep, &dep, cnp)) != 0)
 		goto bad;
-	VN_KNOTE(ap->a_dvp, NOTE_WRITE | NOTE_LINK);
 	*ap->a_vpp = DETOV(dep);
 	return (0);
 
@@ -1315,7 +1308,6 @@ msdosfs_rmdir(void *v)
 	 * directory.  Since dos filesystems don't do this we just purge
 	 * the name cache and let go of the parent directory denode.
 	 */
-	VN_KNOTE(dvp, NOTE_WRITE | NOTE_LINK);
 	cache_purge(dvp);
 	/*
 	 * Truncate the directory that is being deleted.
@@ -1323,7 +1315,6 @@ msdosfs_rmdir(void *v)
 	error = detrunc(ip, (u_long)0, IO_SYNC, cnp->cn_cred);
 	cache_purge(vp);
 out:
-	VN_KNOTE(vp, NOTE_DELETE);
 	vput(vp);
 	return (error);
 }

Index: src/sys/fs/nilfs/nilfs_vnops.c
diff -u src/sys/fs/nilfs/nilfs_vnops.c:1.44 src/sys/fs/nilfs/nilfs_vnops.c:1.45
--- src/sys/fs/nilfs/nilfs_vnops.c:1.44	Sat Jul 24 21:31:38 2021
+++ src/sys/fs/nilfs/nilfs_vnops.c	Wed Oct 20 03:08:17 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: nilfs_vnops.c,v 1.44 2021/07/24 21:31:38 andvar Exp $	*/
+/*	$NetBSD: nilfs_vnops.c,v 1.45 2021/10/20 03:08:17 thorpej Exp $	*/
 
 /*
  * Copyright (c) 2008, 2009 Reinoud Zandijk
@@ -28,7 +28,7 @@
 
 #include <sys/cdefs.h>
 #ifndef lint
-__KERNEL_RCSID(0, "$NetBSD: nilfs_vnops.c,v 1.44 2021/07/24 21:31:38 andvar Exp $");
+__KERNEL_RCSID(0, "$NetBSD: nilfs_vnops.c,v 1.45 2021/10/20 03:08:17 thorpej Exp $");
 #endif /* not lint */
 
 
@@ -213,7 +213,7 @@ nilfs_write(void *v)
 	struct nilfs_node      *nilfs_node = VTOI(vp);
 	uint64_t file_size;
 	vsize_t len;
-	int error, resid, extended;
+	int error, resid;
 
 	DPRINTF(WRITE, ("nilfs_write called\n"));
 
@@ -285,10 +285,6 @@ nilfs_write(void *v)
 	 * the superuser as a precaution against tampering.
 	 */
 
-	/* if we wrote a thing, note write action on vnode */
-	if (resid > uio->uio_resid)
-		VN_KNOTE(vp, NOTE_WRITE | (extended ? NOTE_EXTEND : 0));
-
 	if (error) {
 		/* bring back file size to its former size */
 		/* take notice of its errors? */
@@ -1196,9 +1192,6 @@ nilfs_link(void *v)
 	if (error)
 		VOP_ABORTOP(dvp, cnp);
 
-	VN_KNOTE(vp, NOTE_LINK);
-	VN_KNOTE(dvp, NOTE_WRITE);
-
 	return error;
 }
 
@@ -1362,7 +1355,7 @@ nilfs_rename(void *v)
 	}
 
 	/* remove existing entry if present */
-	if (tvp) 
+	if (tvp)
 		nilfs_dir_detach(tdnode->ump, tdnode, tnode, tcnp);
 
 	/* create new directory entry for the node */
@@ -1401,10 +1394,11 @@ out_unlocked:
 int
 nilfs_remove(void *v)
 {
-	struct vop_remove_v2_args /* {
+	struct vop_remove_v3_args /* {
 		struct vnode *a_dvp;
 		struct vnode *a_vp;
 		struct componentname *a_cnp;
+		nlink_t ctx_vp_new_nlink;
 	} */ *ap = v;
 	struct vnode *dvp = ap->a_dvp;
 	struct vnode *vp  = ap->a_vp;
@@ -1423,11 +1417,6 @@ nilfs_remove(void *v)
 		error = EPERM;
 	}
 
-	if (error == 0) {
-		VN_KNOTE(vp, NOTE_DELETE);
-		VN_KNOTE(dvp, NOTE_WRITE);
-	}
-
 	if (dvp == vp)
 		vrele(vp);
 	else
@@ -1476,7 +1465,6 @@ nilfs_rmdir(void *v)
 	if (error == 0) {
 		cache_purge(vp);
 //		cache_purge(dvp);	/* XXX from msdosfs, why? */
-		VN_KNOTE(vp, NOTE_DELETE);
 	}
 	DPRINTFIF(NODE, error, ("\tgot error removing file\n"));
 

Index: src/sys/fs/ptyfs/ptyfs_vnops.c
diff -u src/sys/fs/ptyfs/ptyfs_vnops.c:1.66 src/sys/fs/ptyfs/ptyfs_vnops.c:1.67
--- src/sys/fs/ptyfs/ptyfs_vnops.c:1.66	Sun Jul 18 23:57:34 2021
+++ src/sys/fs/ptyfs/ptyfs_vnops.c	Wed Oct 20 03:08:17 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: ptyfs_vnops.c,v 1.66 2021/07/18 23:57:34 dholland Exp $	*/
+/*	$NetBSD: ptyfs_vnops.c,v 1.67 2021/10/20 03:08:17 thorpej Exp $	*/
 
 /*
  * Copyright (c) 1993, 1995
@@ -76,7 +76,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ptyfs_vnops.c,v 1.66 2021/07/18 23:57:34 dholland Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ptyfs_vnops.c,v 1.67 2021/10/20 03:08:17 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -477,7 +477,6 @@ ptyfs_setattr(void *v)
 		if (error)
 			return error;
 	}
-	VN_KNOTE(vp, NOTE_ATTRIB);
 	return 0;
 }
 

Index: src/sys/fs/puffs/puffs_vnops.c
diff -u src/sys/fs/puffs/puffs_vnops.c:1.222 src/sys/fs/puffs/puffs_vnops.c:1.223
--- src/sys/fs/puffs/puffs_vnops.c:1.222	Sat Jul 24 21:31:38 2021
+++ src/sys/fs/puffs/puffs_vnops.c	Wed Oct 20 03:08:17 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: puffs_vnops.c,v 1.222 2021/07/24 21:31:38 andvar Exp $	*/
+/*	$NetBSD: puffs_vnops.c,v 1.223 2021/10/20 03:08:17 thorpej Exp $	*/
 
 /*
  * Copyright (c) 2005, 2006, 2007  Antti Kantee.  All Rights Reserved.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: puffs_vnops.c,v 1.222 2021/07/24 21:31:38 andvar Exp $");
+__KERNEL_RCSID(0, "$NetBSD: puffs_vnops.c,v 1.223 2021/10/20 03:08:17 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/buf.h>
@@ -1796,11 +1796,12 @@ callremove(struct puffs_mount *pmp, puff
 int
 puffs_vnop_remove(void *v)
 {
-	struct vop_remove_v2_args /* {
+	struct vop_remove_v3_args /* {
 		const struct vnodeop_desc *a_desc;
 		struct vnode *a_dvp;
 		struct vnode *a_vp;
 		struct componentname *a_cnp;
+		nlink_t ctx_vp_new_nlink;
 	} */ *ap = v;
 	PUFFS_MSG_VARS(vn, remove);
 	struct vnode *dvp = ap->a_dvp;
@@ -2172,6 +2173,8 @@ puffs_vnop_rename(void *v)
 		if (PUFFS_USE_DOTDOTCACHE(pmp) &&
 		    (VPTOPP(fvp)->pn_parent != tdvp))
 			update_parent(fvp, tdvp);
+
+		/* XXX Update ap->ctx_vp_new_nlink */
 	}
 
 

Index: src/sys/fs/sysvbfs/sysvbfs_vnops.c
diff -u src/sys/fs/sysvbfs/sysvbfs_vnops.c:1.67 src/sys/fs/sysvbfs/sysvbfs_vnops.c:1.68
--- src/sys/fs/sysvbfs/sysvbfs_vnops.c:1.67	Sat Jun 27 17:29:18 2020
+++ src/sys/fs/sysvbfs/sysvbfs_vnops.c	Wed Oct 20 03:08:17 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: sysvbfs_vnops.c,v 1.67 2020/06/27 17:29:18 christos Exp $	*/
+/*	$NetBSD: sysvbfs_vnops.c,v 1.68 2021/10/20 03:08:17 thorpej Exp $	*/
 
 /*-
  * Copyright (c) 2004 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: sysvbfs_vnops.c,v 1.67 2020/06/27 17:29:18 christos Exp $");
+__KERNEL_RCSID(0, "$NetBSD: sysvbfs_vnops.c,v 1.68 2021/10/20 03:08:17 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/kernel.h>
@@ -474,7 +474,6 @@ sysvbfs_write(void *arg)
 	struct uio *uio = a->a_uio;
 	int advice = IO_ADV_DECODE(a->a_ioflag);
 	struct sysvbfs_node *bnode = v->v_data;
-	bool extended = false;
 	vsize_t sz;
 	int err = 0;
 
@@ -489,7 +488,6 @@ sysvbfs_write(void *arg)
 
 	if (bnode->size < uio->uio_offset + uio->uio_resid) {
 		sysvbfs_file_setsize(v, uio->uio_offset + uio->uio_resid);
-		extended = true;
 	}
 
 	while (uio->uio_resid > 0) {
@@ -503,19 +501,18 @@ sysvbfs_write(void *arg)
 	if (err)
 		sysvbfs_file_setsize(v, bnode->size - uio->uio_resid);
 
-	VN_KNOTE(v, NOTE_WRITE | (extended ? NOTE_EXTEND : 0));
-
 	return err;
 }
 
 int
 sysvbfs_remove(void *arg)
 {
-	struct vop_remove_v2_args /* {
+	struct vop_remove_v3_args /* {
 		struct vnodeop_desc *a_desc;
 		struct vnode * a_dvp;
 		struct vnode * a_vp;
 		struct componentname * a_cnp;
+		nlink_t ctx_vp_new_nlink;
 	} */ *ap = arg;
 	struct vnode *vp = ap->a_vp;
 	struct vnode *dvp = ap->a_dvp;
@@ -534,8 +531,6 @@ sysvbfs_remove(void *arg)
 	if ((err = bfs_file_delete(bfs, ap->a_cnp->cn_nameptr, true)) != 0)
 		DPRINTF("%s: bfs_file_delete failed.\n", __func__);
 
-	VN_KNOTE(ap->a_vp, NOTE_DELETE);
-	VN_KNOTE(ap->a_dvp, NOTE_WRITE);
 	if (dvp == vp)
 		vrele(vp);
 	else

Index: src/sys/fs/tmpfs/tmpfs_rename.c
diff -u src/sys/fs/tmpfs/tmpfs_rename.c:1.10 src/sys/fs/tmpfs/tmpfs_rename.c:1.11
--- src/sys/fs/tmpfs/tmpfs_rename.c:1.10	Tue Dec  3 04:59:05 2019
+++ src/sys/fs/tmpfs/tmpfs_rename.c	Wed Oct 20 03:08:17 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: tmpfs_rename.c,v 1.10 2019/12/03 04:59:05 riastradh Exp $	*/
+/*	$NetBSD: tmpfs_rename.c,v 1.11 2021/10/20 03:08:17 thorpej Exp $	*/
 
 /*-
  * Copyright (c) 2012 The NetBSD Foundation, Inc.
@@ -34,7 +34,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: tmpfs_rename.c,v 1.10 2019/12/03 04:59:05 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: tmpfs_rename.c,v 1.11 2021/10/20 03:08:17 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/errno.h>
@@ -264,7 +264,7 @@ tmpfs_gro_rename(struct mount *mp, kauth
     struct vnode *fdvp, struct componentname *fcnp,
     void *fde, struct vnode *fvp,
     struct vnode *tdvp, struct componentname *tcnp,
-    void *tde, struct vnode *tvp)
+    void *tde, struct vnode *tvp, nlink_t *tvp_nlinkp)
 {
 	tmpfs_node_t *fdnode = VP_TO_TMPFS_DIR(fdvp);
 	tmpfs_node_t *tdnode = VP_TO_TMPFS_DIR(tdvp);
@@ -317,14 +317,6 @@ tmpfs_gro_rename(struct mount *mp, kauth
 	if (fdvp != tdvp) {
 		tmpfs_dir_detach(fdnode, *fdep);
 		tmpfs_dir_attach(tdnode, *fdep, VP_TO_TMPFS_NODE(fvp));
-	} else if (tvp == NULL) {
-		/*
-		 * We are changing the directory.  tmpfs_dir_attach and
-		 * tmpfs_dir_detach note the events for us, but for
-		 * this case we don't call them, so we must note the
-		 * event explicitly.
-		 */
-		VN_KNOTE(fdvp, NOTE_WRITE);
 	}
 
 	/*
@@ -350,6 +342,8 @@ tmpfs_gro_rename(struct mount *mp, kauth
 		}
 		tmpfs_dir_detach(tdnode, *tdep);
 		tmpfs_free_dirent(VFS_TO_TMPFS(mp), *tdep);
+
+		*tvp_nlinkp = VP_TO_TMPFS_NODE(tvp)->tn_links;
 	}
 
 	/*
@@ -377,8 +371,6 @@ tmpfs_gro_rename(struct mount *mp, kauth
 	tmpfs_update(tdvp, TMPFS_UPDATE_MTIME | TMPFS_UPDATE_CTIME);
 	tmpfs_update(fvp, TMPFS_UPDATE_CTIME);
 
-	VN_KNOTE(fvp, NOTE_RENAME);
-
 	genfs_rename_cache_purge(fdvp, fvp, tdvp, tvp);
 
 	return 0;
@@ -390,7 +382,8 @@ tmpfs_gro_rename(struct mount *mp, kauth
  */
 static int
 tmpfs_gro_remove(struct mount *mp, kauth_cred_t cred,
-    struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp)
+    struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp,
+    nlink_t *tvp_nlinkp)
 {
 	tmpfs_node_t *dnode = VP_TO_TMPFS_DIR(dvp);
 	struct tmpfs_dirent **dep = de;
@@ -413,6 +406,9 @@ tmpfs_gro_remove(struct mount *mp, kauth
 	tmpfs_free_dirent(VFS_TO_TMPFS(mp), *dep);
 	tmpfs_update(dvp, TMPFS_UPDATE_MTIME | TMPFS_UPDATE_CTIME);
 
+	KASSERT((*dep)->td_node == VP_TO_TMPFS_NODE(vp));
+	*tvp_nlinkp = VP_TO_TMPFS_NODE(vp)->tn_links;
+
 	return 0;
 }
 

Index: src/sys/fs/tmpfs/tmpfs_subr.c
diff -u src/sys/fs/tmpfs/tmpfs_subr.c:1.113 src/sys/fs/tmpfs/tmpfs_subr.c:1.114
--- src/sys/fs/tmpfs/tmpfs_subr.c:1.113	Sat Sep  5 16:30:12 2020
+++ src/sys/fs/tmpfs/tmpfs_subr.c	Wed Oct 20 03:08:17 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: tmpfs_subr.c,v 1.113 2020/09/05 16:30:12 riastradh Exp $	*/
+/*	$NetBSD: tmpfs_subr.c,v 1.114 2021/10/20 03:08:17 thorpej Exp $	*/
 
 /*
  * Copyright (c) 2005-2020 The NetBSD Foundation, Inc.
@@ -73,7 +73,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: tmpfs_subr.c,v 1.113 2020/09/05 16:30:12 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: tmpfs_subr.c,v 1.114 2021/10/20 03:08:17 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/cprng.h>
@@ -537,7 +537,6 @@ tmpfs_dir_attach(tmpfs_node_t *dnode, tm
 
 		TMPFS_VALIDATE_DIR(node);
 	}
-	VN_KNOTE(dvp, events);
 }
 
 /*
@@ -554,8 +553,7 @@ void
 tmpfs_dir_detach(tmpfs_node_t *dnode, tmpfs_dirent_t *de)
 {
 	tmpfs_node_t *node = de->td_node;
-	vnode_t *vp, *dvp = dnode->tn_vnode;
-	int events = NOTE_WRITE;
+	vnode_t *dvp = dnode->tn_vnode;
 
 	KASSERT(dvp == NULL || VOP_ISLOCKED(dvp));
 
@@ -566,11 +564,6 @@ tmpfs_dir_detach(tmpfs_node_t *dnode, tm
 		KASSERT(node->tn_links > 0);
 		node->tn_links--;
 
-		if ((vp = node->tn_vnode) != NULL) {
-			KASSERT(VOP_ISLOCKED(vp));
-			VN_KNOTE(vp, node->tn_links ? NOTE_LINK : NOTE_DELETE);
-		}
-
 		/* If directory - decrease the link count of parent. */
 		if (node->tn_type == VDIR) {
 			KASSERT(node->tn_spec.tn_dir.tn_parent == dnode);
@@ -578,7 +571,6 @@ tmpfs_dir_detach(tmpfs_node_t *dnode, tm
 
 			KASSERT(dnode->tn_links > 0);
 			dnode->tn_links--;
-			events |= NOTE_LINK;
 		}
 	}
 	de->td_node = NULL;
@@ -593,7 +585,6 @@ tmpfs_dir_detach(tmpfs_node_t *dnode, tm
 
 	if (dvp) {
 		uvm_vnp_setsize(dvp, dnode->tn_size);
-		VN_KNOTE(dvp, events);
 	}
 }
 
@@ -952,9 +943,6 @@ tmpfs_reg_resize(struct vnode *vp, off_t
 		/* Decrease the used-memory counter. */
 		tmpfs_mem_decr(tmp, (oldpages - newpages) << PAGE_SHIFT);
 	}
-	if (newsize > oldsize) {
-		VN_KNOTE(vp, NOTE_EXTEND);
-	}
 	return 0;
 }
 
@@ -1014,7 +1002,6 @@ tmpfs_chflags(vnode_t *vp, int flags, ka
 		node->tn_flags = flags;
 	}
 	tmpfs_update(vp, TMPFS_UPDATE_CTIME);
-	VN_KNOTE(vp, NOTE_ATTRIB);
 	return 0;
 }
 
@@ -1044,7 +1031,6 @@ tmpfs_chmod(vnode_t *vp, mode_t mode, ka
 	}
 	node->tn_mode = (mode & ALLPERMS);
 	tmpfs_update(vp, TMPFS_UPDATE_CTIME);
-	VN_KNOTE(vp, NOTE_ATTRIB);
 	cache_enter_id(vp, node->tn_mode, node->tn_uid, node->tn_gid, true);
 	return 0;
 }
@@ -1089,7 +1075,6 @@ tmpfs_chown(vnode_t *vp, uid_t uid, gid_
 	node->tn_uid = uid;
 	node->tn_gid = gid;
 	tmpfs_update(vp, TMPFS_UPDATE_CTIME);
-	VN_KNOTE(vp, NOTE_ATTRIB);
 	cache_enter_id(vp, node->tn_mode, node->tn_uid, node->tn_gid, true);
 	return 0;
 }
@@ -1185,7 +1170,6 @@ tmpfs_chtimes(vnode_t *vp, const struct 
 		node->tn_birthtime = *btime;
 	}
 	mutex_exit(&node->tn_timelock);
-	VN_KNOTE(vp, NOTE_ATTRIB);
 	return 0;
 }
 

Index: src/sys/fs/tmpfs/tmpfs_vnops.c
diff -u src/sys/fs/tmpfs/tmpfs_vnops.c:1.147 src/sys/fs/tmpfs/tmpfs_vnops.c:1.148
--- src/sys/fs/tmpfs/tmpfs_vnops.c:1.147	Sun Jul 18 23:57:14 2021
+++ src/sys/fs/tmpfs/tmpfs_vnops.c	Wed Oct 20 03:08:17 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: tmpfs_vnops.c,v 1.147 2021/07/18 23:57:14 dholland Exp $	*/
+/*	$NetBSD: tmpfs_vnops.c,v 1.148 2021/10/20 03:08:17 thorpej Exp $	*/
 
 /*
  * Copyright (c) 2005, 2006, 2007, 2020 The NetBSD Foundation, Inc.
@@ -35,7 +35,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: tmpfs_vnops.c,v 1.147 2021/07/18 23:57:14 dholland Exp $");
+__KERNEL_RCSID(0, "$NetBSD: tmpfs_vnops.c,v 1.148 2021/10/20 03:08:17 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/dirent.h>
@@ -648,7 +648,6 @@ tmpfs_write(void *v)
 	}
 
 	tmpfs_update(vp, TMPFS_UPDATE_MTIME | TMPFS_UPDATE_CTIME);
-	VN_KNOTE(vp, NOTE_WRITE);
 out:
 	if (error) {
 		KASSERT(oldsize == node->tn_size);
@@ -685,10 +684,11 @@ tmpfs_fsync(void *v)
 int
 tmpfs_remove(void *v)
 {
-	struct vop_remove_v2_args /* {
+	struct vop_remove_v3_args /* {
 		struct vnode *a_dvp;
 		struct vnode *a_vp;
 		struct componentname *a_cnp;
+		nlink_t ctx_vp_new_nlink;
 	} */ *ap = v;
 	vnode_t *dvp = ap->a_dvp, *vp = ap->a_vp;
 	tmpfs_node_t *dnode, *node;
@@ -747,6 +747,7 @@ tmpfs_remove(void *v)
 		/* We removed a hard link. */
 		tflags |= TMPFS_UPDATE_CTIME;
 	}
+	ap->ctx_vp_new_nlink = node->tn_links;
 	tmpfs_update(dvp, tflags);
 	error = 0;
 out:
@@ -814,10 +815,7 @@ tmpfs_link(void *v)
 	tmpfs_dir_attach(dnode, de, node);
 	tmpfs_update(dvp, TMPFS_UPDATE_MTIME | TMPFS_UPDATE_CTIME);
 
-	/* Update the timestamps and trigger the event. */
-	if (node->tn_vnode) {
-		VN_KNOTE(node->tn_vnode, NOTE_LINK);
-	}
+	/* Update the timestamps. */
 	tmpfs_update(vp, TMPFS_UPDATE_CTIME);
 	error = 0;
 out:

Index: src/sys/fs/udf/udf_rename.c
diff -u src/sys/fs/udf/udf_rename.c:1.13 src/sys/fs/udf/udf_rename.c:1.14
--- src/sys/fs/udf/udf_rename.c:1.13	Fri Jan 17 20:08:08 2020
+++ src/sys/fs/udf/udf_rename.c	Wed Oct 20 03:08:17 2021
@@ -1,4 +1,4 @@
-/* $NetBSD: udf_rename.c,v 1.13 2020/01/17 20:08:08 ad Exp $ */
+/* $NetBSD: udf_rename.c,v 1.14 2021/10/20 03:08:17 thorpej Exp $ */
 
 /*
  * Copyright (c) 2013 Reinoud Zandijk
@@ -28,7 +28,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: udf_rename.c,v 1.13 2020/01/17 20:08:08 ad Exp $");
+__KERNEL_RCSID(0, "$NetBSD: udf_rename.c,v 1.14 2021/10/20 03:08:17 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/errno.h>
@@ -322,7 +322,7 @@ udf_gro_rename(struct mount *mp, kauth_c
     struct vnode *fdvp, struct componentname *fcnp,
     void *fde, struct vnode *fvp,
     struct vnode *tdvp, struct componentname *tcnp,
-    void *tde, struct vnode *tvp)
+    void *tde, struct vnode *tvp, nlink_t *tvp_nlinkp)
 {
 	struct udf_node *fnode, *fdnode, *tnode, *tdnode;
 	struct vattr fvap;
@@ -364,8 +364,15 @@ udf_gro_rename(struct mount *mp, kauth_c
 		return error;
 
 	/* remove existing entry if present */
-	if (tvp) 
+	if (tvp) {
 		udf_dir_detach(tdnode->ump, tdnode, tnode, tcnp);
+		if (tnode->fe) {
+			*tvp_nlinkp = udf_rw16(tnode->fe->link_cnt);
+		} else {
+			KASSERT(tnode->efe != NULL);
+			*tvp_nlinkp = udf_rw16(tnode->efe->link_cnt);
+		}
+	}
 
 	/* create new directory entry for the node */
 	error = udf_dir_attach(tdnode->ump, tdnode, fnode, &fvap, tcnp);
@@ -384,7 +391,6 @@ udf_gro_rename(struct mount *mp, kauth_c
 			goto rollback;
 	}
 
-	VN_KNOTE(fvp, NOTE_RENAME);
 	genfs_rename_cache_purge(fdvp, fvp, tdvp, tvp);
 	return 0;
 
@@ -404,7 +410,8 @@ rollback_attach:
  */
 static int
 udf_gro_remove(struct mount *mp, kauth_cred_t cred,
-    struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp)
+    struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp,
+    nlink_t *tvp_nlinkp)
 {
 	struct udf_node *dir_node, *udf_node;
 
@@ -426,6 +433,13 @@ udf_gro_remove(struct mount *mp, kauth_c
 	udf_node = VTOI(vp);
 	udf_dir_detach(dir_node->ump, dir_node, udf_node, cnp);
 
+	if (udf_node->fe) {
+		*tvp_nlinkp = udf_rw16(udf_node->fe->link_cnt);
+	} else {
+		KASSERT(udf_node->efe != NULL);
+		*tvp_nlinkp = udf_rw16(udf_node->efe->link_cnt);
+	}
+
 	return 0;
 }
 

Index: src/sys/fs/udf/udf_vnops.c
diff -u src/sys/fs/udf/udf_vnops.c:1.116 src/sys/fs/udf/udf_vnops.c:1.117
--- src/sys/fs/udf/udf_vnops.c:1.116	Sat Jul 24 21:31:38 2021
+++ src/sys/fs/udf/udf_vnops.c	Wed Oct 20 03:08:17 2021
@@ -1,4 +1,4 @@
-/* $NetBSD: udf_vnops.c,v 1.116 2021/07/24 21:31:38 andvar Exp $ */
+/* $NetBSD: udf_vnops.c,v 1.117 2021/10/20 03:08:17 thorpej Exp $ */
 
 /*
  * Copyright (c) 2006, 2008 Reinoud Zandijk
@@ -32,7 +32,7 @@
 
 #include <sys/cdefs.h>
 #ifndef lint
-__KERNEL_RCSID(0, "$NetBSD: udf_vnops.c,v 1.116 2021/07/24 21:31:38 andvar Exp $");
+__KERNEL_RCSID(0, "$NetBSD: udf_vnops.c,v 1.117 2021/10/20 03:08:17 thorpej Exp $");
 #endif /* not lint */
 
 
@@ -398,10 +398,6 @@ udf_write(void *v)
 	 * the superuser as a precaution against tampering.
 	 */
 
-	/* if we wrote a thing, note write action on vnode */
-	if (resid > uio->uio_resid)
-		VN_KNOTE(vp, NOTE_WRITE | (extended ? NOTE_EXTEND : 0));
-
 	if (error) {
 		/* bring back file size to its former size */
 		/* take notice of its errors? */
@@ -1139,7 +1135,6 @@ udf_chsize(struct vnode *vp, u_quad_t ne
 		udf_node->i_flags |= IN_CHANGE | IN_MODIFY;
 		if (vp->v_mount->mnt_flag & MNT_RELATIME)
 			udf_node->i_flags |= IN_ACCESS;
-		VN_KNOTE(vp, NOTE_ATTRIB | (extended ? NOTE_EXTEND : 0));
 		udf_update(vp, NULL, NULL, NULL, 0);
 	}
 
@@ -1273,7 +1268,6 @@ udf_setattr(void *v)
 		error = udf_chtimes(vp, &vap->va_atime, &vap->va_mtime,
 		    &vap->va_birthtime, vap->va_vaflags, cred);
 	}
-	VN_KNOTE(vp, NOTE_ATTRIB);
 
 	return error;
 }
@@ -1599,9 +1593,6 @@ udf_link(void *v)
 	if (error)
 		VOP_ABORTOP(dvp, cnp);
 
-	VN_KNOTE(vp, NOTE_LINK);
-	VN_KNOTE(dvp, NOTE_WRITE);
-
 	return error;
 }
 
@@ -1939,10 +1930,11 @@ udf_readlink(void *v)
 int
 udf_remove(void *v)
 {
-	struct vop_remove_v2_args /* {
+	struct vop_remove_v3_args /* {
 		struct vnode *a_dvp;
 		struct vnode *a_vp;
 		struct componentname *a_cnp;
+		nlink_t ctx_vp_new_nlink;
 	} */ *ap = v;
 	struct vnode *dvp = ap->a_dvp;
 	struct vnode *vp  = ap->a_vp;
@@ -1956,16 +1948,21 @@ udf_remove(void *v)
 	if (vp->v_type != VDIR) {
 		error = udf_dir_detach(ump, dir_node, udf_node, cnp);
 		DPRINTFIF(NODE, error, ("\tgot error removing file\n"));
+		if (error == 0) {
+			if (udf_node->fe) {
+				ap->ctx_vp_new_nlink =
+				    udf_rw16(udf_node->fe->link_cnt);
+			} else {
+				KASSERT(udf_node->efe != NULL);
+				ap->ctx_vp_new_nlink =
+				    udf_rw16(udf_node->efe->link_cnt);
+			}
+		}
 	} else {
 		DPRINTF(NODE, ("\tis a directory: perm. denied\n"));
 		error = EPERM;
 	}
 
-	if (error == 0) {
-		VN_KNOTE(vp, NOTE_DELETE);
-		VN_KNOTE(dvp, NOTE_WRITE);
-	}
-
 	if (dvp == vp)
 		vrele(vp);
 	else
@@ -2031,7 +2028,6 @@ udf_rmdir(void *v)
 		 */
 		dirhash_purge(&udf_node->dir_hash);
 		udf_shrink_node(udf_node, 0);
-		VN_KNOTE(vp, NOTE_DELETE);
 	}
 	DPRINTFIF(NODE, error, ("\tgot error removing dir\n"));
 

Index: src/sys/fs/union/union_vnops.c
diff -u src/sys/fs/union/union_vnops.c:1.78 src/sys/fs/union/union_vnops.c:1.79
--- src/sys/fs/union/union_vnops.c:1.78	Sun Jul  4 11:24:09 2021
+++ src/sys/fs/union/union_vnops.c	Wed Oct 20 03:08:17 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: union_vnops.c,v 1.78 2021/07/04 11:24:09 hannken Exp $	*/
+/*	$NetBSD: union_vnops.c,v 1.79 2021/10/20 03:08:17 thorpej Exp $	*/
 
 /*
  * Copyright (c) 1992, 1993, 1994, 1995
@@ -72,7 +72,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: union_vnops.c,v 1.78 2021/07/04 11:24:09 hannken Exp $");
+__KERNEL_RCSID(0, "$NetBSD: union_vnops.c,v 1.79 2021/10/20 03:08:17 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -1179,10 +1179,11 @@ union_seek(void *v)
 int
 union_remove(void *v)
 {
-	struct vop_remove_v2_args /* {
+	struct vop_remove_v3_args /* {
 		struct vnode *a_dvp;
 		struct vnode *a_vp;
 		struct componentname *a_cnp;
+		nlink_t ctx_vp_new_nlink;
 	} */ *ap = v;
 	int error;
 	struct union_node *dun = VTOUNION(ap->a_dvp);
@@ -1296,7 +1297,7 @@ union_link(void *v)
 int
 union_rename(void *v)
 {
-	struct vop_rename_args  /* {
+	struct vop_rename_args /* {
 		struct vnode *a_fdvp;
 		struct vnode *a_fvp;
 		struct componentname *a_fcnp;

Index: src/sys/fs/unionfs/unionfs_vnops.c
diff -u src/sys/fs/unionfs/unionfs_vnops.c:1.16 src/sys/fs/unionfs/unionfs_vnops.c:1.17
--- src/sys/fs/unionfs/unionfs_vnops.c:1.16	Tue Jun 29 22:38:46 2021
+++ src/sys/fs/unionfs/unionfs_vnops.c	Wed Oct 20 03:08:17 2021
@@ -893,7 +893,7 @@ unionfs_fsync(void *v)
 static int
 unionfs_remove(void *v)
 {
-	struct vop_remove_v2_args *ap = v;
+	struct vop_remove_v3_args *ap = v;
 	int		error;
 	struct unionfs_node *dunp;
 	struct unionfs_node *unp;

Index: src/sys/fs/v7fs/v7fs_vnops.c
diff -u src/sys/fs/v7fs/v7fs_vnops.c:1.31 src/sys/fs/v7fs/v7fs_vnops.c:1.32
--- src/sys/fs/v7fs/v7fs_vnops.c:1.31	Sat Jun 27 17:29:18 2020
+++ src/sys/fs/v7fs/v7fs_vnops.c	Wed Oct 20 03:08:18 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: v7fs_vnops.c,v 1.31 2020/06/27 17:29:18 christos Exp $	*/
+/*	$NetBSD: v7fs_vnops.c,v 1.32 2021/10/20 03:08:18 thorpej Exp $	*/
 
 /*-
  * Copyright (c) 2004, 2011 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: v7fs_vnops.c,v 1.31 2020/06/27 17:29:18 christos Exp $");
+__KERNEL_RCSID(0, "$NetBSD: v7fs_vnops.c,v 1.32 2021/10/20 03:08:18 thorpej Exp $");
 #if defined _KERNEL_OPT
 #include "opt_v7fs.h"
 #endif
@@ -683,11 +683,12 @@ v7fs_fsync(void *v)
 int
 v7fs_remove(void *v)
 {
-	struct vop_remove_v2_args /* {
+	struct vop_remove_v3_args /* {
 				  struct vnodeop_desc *a_desc;
 				  struct vnode * a_dvp;
 				  struct vnode * a_vp;
 				  struct componentname * a_cnp;
+				  nlink_t ctx_vp_new_nlink;
 				  } */ *a = v;
 	struct v7fs_node *parent_node = a->a_dvp->v_data;
 	struct v7fs_mount *v7fsmount = parent_node->v7fsmount;

Index: src/sys/kern/vfs_vnode.c
diff -u src/sys/kern/vfs_vnode.c:1.127 src/sys/kern/vfs_vnode.c:1.128
--- src/sys/kern/vfs_vnode.c:1.127	Thu Apr  1 06:26:14 2021
+++ src/sys/kern/vfs_vnode.c	Wed Oct 20 03:08:18 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: vfs_vnode.c,v 1.127 2021/04/01 06:26:14 simonb Exp $	*/
+/*	$NetBSD: vfs_vnode.c,v 1.128 2021/10/20 03:08:18 thorpej Exp $	*/
 
 /*-
  * Copyright (c) 1997-2011, 2019, 2020 The NetBSD Foundation, Inc.
@@ -148,7 +148,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: vfs_vnode.c,v 1.127 2021/04/01 06:26:14 simonb Exp $");
+__KERNEL_RCSID(0, "$NetBSD: vfs_vnode.c,v 1.128 2021/10/20 03:08:18 thorpej Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_pax.h"
@@ -1826,6 +1826,10 @@ vcache_reclaim(vnode_t *vp)
 	vp->v_vflag |= VV_LOCKSWORK;
 	VSTATE_CHANGE(vp, VS_RECLAIMING, VS_RECLAIMED);
 	vp->v_tag = VT_NON;
+	/*
+	 * Don't check for interest in NOTE_REVOKE; it's always posted
+	 * because it sets EV_EOF.
+	 */
 	KNOTE(&vp->v_klist, NOTE_REVOKE);
 	mutex_exit(vp->v_interlock);
 

Index: src/sys/kern/vfs_vnops.c
diff -u src/sys/kern/vfs_vnops.c:1.223 src/sys/kern/vfs_vnops.c:1.224
--- src/sys/kern/vfs_vnops.c:1.223	Sat Sep 11 10:09:13 2021
+++ src/sys/kern/vfs_vnops.c	Wed Oct 20 03:08:18 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: vfs_vnops.c,v 1.223 2021/09/11 10:09:13 riastradh Exp $	*/
+/*	$NetBSD: vfs_vnops.c,v 1.224 2021/10/20 03:08:18 thorpej Exp $	*/
 
 /*-
  * Copyright (c) 2009 The NetBSD Foundation, Inc.
@@ -66,7 +66,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: vfs_vnops.c,v 1.223 2021/09/11 10:09:13 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: vfs_vnops.c,v 1.224 2021/10/20 03:08:18 thorpej Exp $");
 
 #include "veriexec.h"
 
@@ -1365,3 +1365,91 @@ vn_bdev_openpath(struct pathbuf *pb, str
 
 	return vn_bdev_open(dev, vpp, l);
 }
+
+static long
+vn_knote_to_interest(const struct knote *kn)
+{
+	switch (kn->kn_filter) {
+	case EVFILT_READ:
+		/*
+		 * Writing to the file or changing its attributes can
+		 * set the file size, which impacts the readability
+		 * filter.
+		 *
+		 * (No need to set NOTE_EXTEND here; it's only ever
+		 * send with other hints; see vnode_if.c.)
+		 */
+		return NOTE_WRITE | NOTE_ATTRIB;
+
+	case EVFILT_VNODE:
+		return kn->kn_sfflags;
+
+	case EVFILT_WRITE:
+	default:
+		return 0;
+	}
+}
+
+void
+vn_knote_attach(struct vnode *vp, struct knote *kn)
+{
+	long interest = 0;
+
+	/*
+	 * We maintain a bitmask of the kevents that there is interest in,
+	 * to minimize the impact of having watchers.  It's silly to have
+	 * to traverse vn_klist every time a read or write happens simply
+	 * because there is someone interested in knowing when the file
+	 * is deleted, for example.
+	 */
+
+	mutex_enter(vp->v_interlock);
+	SLIST_INSERT_HEAD(&vp->v_klist, kn, kn_selnext);
+	SLIST_FOREACH(kn, &vp->v_klist, kn_selnext) {
+		interest |= vn_knote_to_interest(kn);
+	}
+	vp->v_klist_interest = interest;
+	mutex_exit(vp->v_interlock);
+}
+
+void
+vn_knote_detach(struct vnode *vp, struct knote *kn)
+{
+	int interest = 0;
+
+	/*
+	 * We special case removing the head of the list, beacuse:
+	 *
+	 * 1. It's extremely likely that we're detaching the only
+	 *    knote.
+	 *
+	 * 2. We're already traversing the whole list, so we don't
+	 *    want to use the generic SLIST_REMOVE() which would
+	 *    traverse it *again*.
+	 */
+
+	mutex_enter(vp->v_interlock);
+	if (__predict_true(kn == SLIST_FIRST(&vp->v_klist))) {
+		SLIST_REMOVE_HEAD(&vp->v_klist, kn_selnext);
+		SLIST_FOREACH(kn, &vp->v_klist, kn_selnext) {
+			interest |= vn_knote_to_interest(kn);
+		}
+		vp->v_klist_interest = interest;
+	} else {
+		struct knote *thiskn, *nextkn, *prevkn = NULL;
+
+		SLIST_FOREACH_SAFE(thiskn, &vp->v_klist, kn_selnext, nextkn) {
+			if (thiskn == kn) {
+				KASSERT(kn != NULL);
+				KASSERT(prevkn != NULL);
+				SLIST_REMOVE_AFTER(prevkn, kn_selnext);
+				kn = NULL;
+			} else {
+				interest |= vn_knote_to_interest(thiskn);
+				prevkn = thiskn;
+			}
+		}
+		vp->v_klist_interest = interest;
+	}
+	mutex_exit(vp->v_interlock);
+}

Index: src/sys/kern/vnode_if.sh
diff -u src/sys/kern/vnode_if.sh:1.71 src/sys/kern/vnode_if.sh:1.72
--- src/sys/kern/vnode_if.sh:1.71	Thu Aug 12 19:15:15 2021
+++ src/sys/kern/vnode_if.sh	Wed Oct 20 03:08:18 2021
@@ -29,7 +29,7 @@ copyright="\
  * SUCH DAMAGE.
  */
 "
-SCRIPT_ID='$NetBSD: vnode_if.sh,v 1.71 2021/08/12 19:15:15 andvar Exp $'
+SCRIPT_ID='$NetBSD: vnode_if.sh,v 1.72 2021/10/20 03:08:18 thorpej Exp $'
 
 # Script to produce VFS front-end sugar.
 #
@@ -99,8 +99,13 @@ awk_parser='
 	name=$1;
 	args_name=$1;
 	argc=0;
+	have_context=0;
+	is_context=0;
+	ncontext=0;
 	willmake=-1;
 	fstrans="";
+	do_pre="";
+	do_post="";
 	next;
 }
 # Last line of description
@@ -117,39 +122,64 @@ awk_parser='
 		fstrans = $1;
 		sub("FSTRANS=", "", fstrans);
 		next;
+	} else if ($1 ~ "^PRE=") {
+		do_pre = $1;
+		sub("PRE=", "", do_pre);
+		next;
+	} else if ($1 ~ "^POST=") {
+		do_post = $1;
+		sub("POST=", "", do_post);
+		next;
 	}
 
-	argdir[argc] = $1; i=2;
-
-	if ($2 == "LOCKED=YES") {
-		lockstate[argc] = 1;
-		i++;
-	} else if ($2 == "LOCKED=NO") {
-		lockstate[argc] = 0;
-		i++;
-	} else
-		lockstate[argc] = -1;
-
-	if ($2 == "WILLRELE" ||
-	    $3 == "WILLRELE") {
-		willrele[argc] = 1;
-		i++;
-	} else if ($2 == "WILLPUT" ||
-		   $3 == "WILLPUT") {
-		willrele[argc] = 3;
-		i++;
-	} else
-		willrele[argc] = 0;
-
-	if ($2 == "WILLMAKE") {
-		willmake=argc;
-		i++;
+	if ($1 == "CONTEXT") {
+		# CONTEXT require PRE and POST handlers.
+		if (do_pre == "" || do_post == "")
+			next;
+		is_context=1;
+		have_context=1;
+	} else {
+		if (have_context) {
+			# CONTEXT members must come at the end of
+			# the args structure, so everything else
+			# is ignored.
+			next;
+		}
+		argdir[argc] = $1;
 	}
-	if (argc == 0 && fstrans == "") {
-		if (lockstate[0] == 1)
-			fstrans = "NO";
-		else
-			fstrans = "YES";
+	i=2;
+
+	if (is_context == 0) {
+		if ($2 == "LOCKED=YES") {
+			lockstate[argc] = 1;
+			i++;
+		} else if ($2 == "LOCKED=NO") {
+			lockstate[argc] = 0;
+			i++;
+		} else
+			lockstate[argc] = -1;
+
+		if ($2 == "WILLRELE" ||
+		    $3 == "WILLRELE") {
+			willrele[argc] = 1;
+			i++;
+		} else if ($2 == "WILLPUT" ||
+			   $3 == "WILLPUT") {
+			willrele[argc] = 3;
+			i++;
+		} else
+			willrele[argc] = 0;
+
+		if ($2 == "WILLMAKE") {
+			willmake=argc;
+			i++;
+		}
+		if (argc == 0 && fstrans == "") {
+			if (lockstate[0] == 1)
+				fstrans = "NO";
+			else
+				fstrans = "YES";
+		}
 	}
 
 	# XXX: replace non-portable types for rump.  We should really
@@ -166,14 +196,17 @@ awk_parser='
 		if (at == "daddr_t")
 			at = "int64_t"
 	}
-	argtype[argc] = at;
+	argtype[argc + ncontext] = at;
 	i++;
 	while (i < NF) {
-		argtype[argc] = argtype[argc]" "$i;
+		argtype[argc + ncontext] = argtype[argc + ncontext]" "$i;
 		i++;
 	}
-	argname[argc] = $i;
-	argc++;
+	argname[argc + ncontext] = $i;
+	if (is_context)
+		ncontext++;
+	else
+		argc++;
 	next;
 }
 '
@@ -240,6 +273,10 @@ function doit() {
 		for (i=0; i<argc; i++) {
 			printf("\t%s a_%s;\n", argtype[i], argname[i]);
 		}
+		for (i=0; i<ncontext; i++) {
+			printf("\t%s ctx_%s;\n", argtype[argc+i], \
+			    argname[argc+i]);
+		}
 		printf("};\n");
 		printf("extern const struct vnodeop_desc %s_desc;\n", name);
 	}
@@ -312,6 +349,7 @@ echo '
 #include <sys/param.h>
 #include <sys/mount.h>
 #include <sys/buf.h>
+#include <sys/fcntl.h>
 #include <sys/vnode.h>
 #include <sys/lock.h>'
 [ -z "${rump}" ] && echo '#include <sys/fstrans.h>'
@@ -360,6 +398,199 @@ vop_pre(vnode_t *vp, struct mount **mp, 
 	return 0;
 }
 
+static inline u_quad_t
+vop_pre_get_size(struct vnode *vp)
+{
+	mutex_enter(vp->v_interlock);
+	KASSERT(vp->v_size != VSIZENOTSET);
+	u_quad_t rv = (u_quad_t)vp->v_size;
+	mutex_exit(vp->v_interlock);
+
+	return rv;
+}
+
+/*
+ * VOP_RMDIR(), VOP_REMOVE(), and VOP_RENAME() need special handling
+ * because they each drop the caller's references on one or more of
+ * their arguments.  While there must be an open file descriptor in
+ * associated with a vnode in order for knotes to be attached to it,
+ * that status could change during the course of the operation.  So,
+ * for the vnode arguments that are WILLRELE or WILLPUT, we check
+ * pre-op if there are registered knotes, take a hold count if so,
+ * and post-op release the hold after activating any knotes still
+ * associated with the vnode.
+ */
+
+#define	VOP_POST_KNOTE(thisvp, e, n)					\\
+do {									\\
+	if (__predict_true((e) == 0)) {					\\
+		/*							\\
+		 * VN_KNOTE() does the VN_KEVENT_INTEREST()		\\
+		 * check for us.					\\
+		 */							\\
+		VN_KNOTE((thisvp), (n));				\\
+	}								\\
+} while (/*CONSTCOND*/0)
+
+#define	VOP_POST_KNOTE_HELD(thisvp, e, n)				\\
+do {									\\
+	/*								\\
+	 * We don't perform a VN_KEVENT_INTEREST() check here; it	\\
+	 * was already performed when we did the pre-op work that	\\
+	 * caused the vnode to be held in the first place.		\\
+	 */								\\
+	mutex_enter((thisvp)->v_interlock);				\\
+	if (__predict_true((e) == 0)) {					\\
+		knote(&(thisvp)->v_klist, (n));				\\
+	}								\\
+	holdrelel((thisvp));						\\
+	mutex_exit((thisvp)->v_interlock);				\\
+	/*								\\
+	 * thisvp might be gone now!  Don't touch!			\\
+	 */								\\
+} while (/*CONSTCOND*/0)
+
+#define	vop_create_post(ap, e)						\\
+	VOP_POST_KNOTE((ap)->a_dvp, (e), NOTE_WRITE)
+
+#define	vop_mknod_post(ap, e)						\\
+	VOP_POST_KNOTE((ap)->a_dvp, (e), NOTE_WRITE)
+
+#define	vop_setattr_pre(ap)						\\
+	u_quad_t osize = 0;						\\
+	long vp_events =						\\
+	    VN_KEVENT_INTEREST((ap)->a_vp, NOTE_ATTRIB | NOTE_EXTEND)	\\
+	    ? NOTE_ATTRIB : 0;						\\
+	bool check_extend = false;					\\
+	if (__predict_false(vp_events != 0 &&				\\
+	    (ap)->a_vap->va_size != VNOVALSIZE)) {			\\
+		check_extend = true;					\\
+		osize = vop_pre_get_size((ap)->a_vp);			\\
+	}
+
+#define	vop_setattr_post(ap, e)						\\
+do {									\\
+	if (__predict_false(vp_events != 0)) {				\\
+		if (__predict_false(check_extend &&			\\
+		    (ap)->a_vap->va_size > osize)) {			\\
+			vp_events |= NOTE_EXTEND;			\\
+		}							\\
+		VOP_POST_KNOTE((ap)->a_vp, (e), vp_events);		\\
+	}								\\
+} while (/*CONSTCOND*/0)
+
+#define	vop_setacl_post(ap, e)						\\
+	VOP_POST_KNOTE((ap)->a_vp, (e), NOTE_ATTRIB)
+
+#define	vop_link_post(ap, e)						\\
+do {									\\
+	VOP_POST_KNOTE((ap)->a_dvp, (e), NOTE_WRITE);			\\
+	VOP_POST_KNOTE((ap)->a_vp, (e), NOTE_LINK);			\\
+} while (/*CONSTCOND*/0)
+
+#define	vop_mkdir_post(ap, e)						\\
+	VOP_POST_KNOTE((ap)->a_dvp, (e), NOTE_WRITE | NOTE_LINK)
+
+#define	vop_remove_pre_common(ap)					\\
+	bool post_event_vp =						\\
+	    VN_KEVENT_INTEREST((ap)->a_vp, NOTE_DELETE | NOTE_LINK);	\\
+	if (__predict_false(post_event_vp)) {				\\
+		vhold((ap)->a_vp);					\\
+	}
+
+#define	vop_remove_post_common(ap, e, dn, lc)				\\
+do {									\\
+	VOP_POST_KNOTE((ap)->a_dvp, (e), (dn));				\\
+	if (__predict_false(post_event_vp)) {				\\
+		VOP_POST_KNOTE_HELD((ap)->a_vp, (e),			\\
+		    (lc) ? NOTE_LINK : NOTE_DELETE);			\\
+	}								\\
+} while (/*CONSTCOND*/0)
+
+/*
+ * One could make the argument that VOP_REMOVE() should send NOTE_LINK
+ * on vp if the resulting link count is not zero, but that's not what
+ * the documentation says.
+ *
+ * We could change this easily by passing ap->ctx_vp_new_nlink to
+ * vop_remove_post_common().
+ */
+#define	vop_remove_pre(ap)						\\
+	vop_remove_pre_common((ap));					\\
+	/*								\\
+	 * We will assume that the file being removed is deleted unless	\\
+	 * the file system tells us otherwise by updating vp_new_nlink.	\\
+	 */								\\
+	(ap)->ctx_vp_new_nlink = 0;
+
+#define	vop_remove_post(ap, e)						\\
+	vop_remove_post_common((ap), (e), NOTE_WRITE, 0)
+
+#define	vop_rmdir_pre(ap)						\\
+	vop_remove_pre_common(ap)
+
+#define	vop_rmdir_post(ap, e)						\\
+	vop_remove_post_common((ap), (e), NOTE_WRITE | NOTE_LINK, 0)
+
+#define	vop_symlink_post(ap, e)						\\
+	VOP_POST_KNOTE((ap)->a_dvp, (e), NOTE_WRITE)
+
+#define	vop_open_post(ap, e)						\\
+	VOP_POST_KNOTE((ap)->a_vp, (e), NOTE_OPEN)
+
+#define	vop_close_post(ap, e)						\\
+do {									\\
+	extern int (**dead_vnodeop_p)(void *);				\\
+									\\
+	/* See the definition of VN_KNOTE() in <sys/vnode.h>. */	\\
+	if (__predict_false(VN_KEVENT_INTEREST((ap)->a_vp,		\\
+	    NOTE_CLOSE_WRITE | NOTE_CLOSE) && (e) == 0)) {		\\
+		struct vnode *thisvp = (ap)->a_vp;			\\
+		mutex_enter(thisvp->v_interlock);			\\
+		/*							\\
+		 * Don't send NOTE_CLOSE when closing a vnode that's	\\
+		 * been reclaimed or otherwise revoked; a NOTE_REVOKE	\\
+		 * has already been sent, and this close is effectively	\\
+		 * meaningless from the watcher's perspective.		\\
+		 */							\\
+		if (__predict_true(thisvp->v_op != dead_vnodeop_p)) {	\\
+			knote(&thisvp->v_klist,				\\
+			    ((ap)->a_fflag & FWRITE)			\\
+			    ? NOTE_CLOSE_WRITE : NOTE_CLOSE);		\\
+		}							\\
+		mutex_exit(thisvp->v_interlock);			\\
+	}								\\
+} while (/*CONSTCOND*/0)
+
+#define	vop_read_post(ap, e)						\\
+	VOP_POST_KNOTE((ap)->a_vp, (e), NOTE_READ)
+
+#define	vop_write_pre(ap)						\\
+	off_t ooffset = 0, noffset = 0;					\\
+	u_quad_t osize = 0;						\\
+	long vp_events =						\\
+	    VN_KEVENT_INTEREST((ap)->a_vp, NOTE_WRITE | NOTE_EXTEND)	\\
+	    ? NOTE_WRITE : 0;						\\
+	if (__predict_false(vp_events != 0)) {				\\
+		ooffset = (ap)->a_uio->uio_offset;			\\
+		osize = vop_pre_get_size((ap)->a_vp);			\\
+	}
+
+#define	vop_write_post(ap, e)						\\
+do {									\\
+	/*								\\
+	 * If any data was written, we'll post an event, even if	\\
+	 * there was an error.						\\
+	 */								\\
+	noffset = (ap)->a_uio->uio_offset;				\\
+	if (__predict_false(vp_events != 0 && noffset > ooffset)) {	\\
+		if (noffset > osize) {					\\
+			vp_events |= NOTE_EXTEND;			\\
+		}							\\
+		VN_KNOTE((ap)->a_vp, vp_events);			\\
+	}								\\
+} while (/*CONSTCOND*/0)
+
 static inline void
 vop_post(vnode_t *vp, struct mount *mp, bool mpsafe, enum fst_op op)
 {
@@ -481,6 +712,10 @@ function bodynorm() {
 			printf("#endif\n");
 		}
 	}
+	# This is done before generic vop_pre() because we want
+	# to do any setup before beginning an fstrans.
+	if (do_pre != "")
+		printf("\t%s(&a);\n", do_pre);
 	if (fstrans == "LOCK")
 		printf("\terror = vop_pre(%s, &mp, &mpsafe, %s);\n",
 			argname[0], "(!(flags & (LK_SHARED|LK_EXCLUSIVE)) ? FST_NO : (flags & LK_NOWAIT ? FST_TRY : FST_YES))");
@@ -502,6 +737,11 @@ function bodynorm() {
 	else
 		printf("\tvop_post(%s, mp, mpsafe, FST_%s);\n",
 			argname[0], fstrans);
+	# This is done after generic vop_post() in order to minimize
+	# time spent with the KERNEL_LOCK held for file systems that
+	# still require it.
+	if (do_post != "")
+		printf("\t%s(&a, error);\n", do_post);
 	if (willmake != -1) {
 		printf("#ifdef DIAGNOSTIC\n");
 		printf("\tif (error == 0)\n"				\

Index: src/sys/kern/vnode_if.src
diff -u src/sys/kern/vnode_if.src:1.82 src/sys/kern/vnode_if.src:1.83
--- src/sys/kern/vnode_if.src:1.82	Fri Jul  2 16:56:22 2021
+++ src/sys/kern/vnode_if.src	Wed Oct 20 03:08:18 2021
@@ -1,4 +1,4 @@
-#	$NetBSD: vnode_if.src,v 1.82 2021/07/02 16:56:22 dholland Exp $
+#	$NetBSD: vnode_if.src,v 1.83 2021/10/20 03:08:18 thorpej Exp $
 #
 # Copyright (c) 1992, 1993
 #	The Regents of the University of California.  All rights reserved.
@@ -95,6 +95,7 @@ vop_lookup {
 #
 vop_create {
 	VERSION 3
+	POST=vop_create_post
 	IN LOCKED=YES struct vnode *dvp;
 	OUT WILLMAKE struct vnode **vpp;
 	IN struct componentname *cnp;
@@ -109,6 +110,7 @@ vop_create {
 #
 vop_mknod {
 	VERSION 3
+	POST=vop_mknod_post
 	IN LOCKED=YES struct vnode *dvp;
 	OUT WILLMAKE struct vnode **vpp;
 	IN struct componentname *cnp;
@@ -119,6 +121,7 @@ vop_mknod {
 #% open               vp      L L L
 #
 vop_open {
+	POST=vop_open_post
 	IN LOCKED=YES struct vnode *vp;
 	IN int mode;
 	IN kauth_cred_t cred;
@@ -128,6 +131,7 @@ vop_open {
 #% close      vp      L L L
 #
 vop_close {
+	POST=vop_close_post
 	IN LOCKED=YES struct vnode *vp;
 	IN int fflag;
 	IN kauth_cred_t cred;
@@ -164,6 +168,8 @@ vop_getattr {
 #% setattr    vp      L L L
 #
 vop_setattr {
+	PRE=vop_setattr_pre
+	POST=vop_setattr_post
 	IN LOCKED=YES struct vnode *vp;
 	IN struct vattr *vap;
 	IN kauth_cred_t cred;
@@ -173,6 +179,7 @@ vop_setattr {
 #% read               vp      L L L
 #
 vop_read {
+	POST=vop_read_post
 	IN LOCKED=YES struct vnode *vp;
 	INOUT struct uio *uio;
 	IN int ioflag;
@@ -183,6 +190,8 @@ vop_read {
 #% write      vp      L L L
 #
 vop_write {
+	PRE=vop_write_pre
+	POST=vop_write_post
 	IN LOCKED=YES struct vnode *vp;
 	INOUT struct uio *uio;
 	IN int ioflag;
@@ -294,10 +303,13 @@ vop_seek {
 #! remove cnp	DELETE, LOCKPARENT | LOCKLEAF
 #
 vop_remove {
-	VERSION 2
+	VERSION 3
+	PRE=vop_remove_pre
+	POST=vop_remove_post
 	IN LOCKED=YES struct vnode *dvp;
 	IN LOCKED=YES WILLPUT struct vnode *vp;
 	IN struct componentname *cnp;
+	CONTEXT nlink_t vp_new_nlink;
 };
 
 #
@@ -308,6 +320,7 @@ vop_remove {
 #
 vop_link {
 	VERSION 2
+	POST=vop_link_post
 	IN LOCKED=YES struct vnode *dvp;
 	IN LOCKED=NO struct vnode *vp;
 	IN struct componentname *cnp;
@@ -339,6 +352,7 @@ vop_rename {
 #
 vop_mkdir {
 	VERSION 3
+	POST=vop_mkdir_post
 	IN LOCKED=YES struct vnode *dvp;
 	OUT WILLMAKE struct vnode **vpp;
 	IN struct componentname *cnp;
@@ -353,6 +367,8 @@ vop_mkdir {
 #
 vop_rmdir {
 	VERSION 2
+	PRE=vop_rmdir_pre
+	POST=vop_rmdir_post
 	IN LOCKED=YES struct vnode *dvp;
 	IN LOCKED=YES WILLPUT struct vnode *vp;
 	IN struct componentname *cnp;
@@ -366,6 +382,7 @@ vop_rmdir {
 #
 vop_symlink {
 	VERSION 3
+	POST=vop_symlink_post
 	IN LOCKED=YES struct vnode *dvp;
 	OUT WILLMAKE struct vnode **vpp;
 	IN struct componentname *cnp;
@@ -549,6 +566,7 @@ vop_getacl {
 #% setacl	vp L L L
 #
 vop_setacl {
+	POST=vop_setacl_post
 	IN struct vnode *vp;
 	IN acl_type_t type;
 	IN struct acl *aclp;

Index: src/sys/miscfs/deadfs/dead_vnops.c
diff -u src/sys/miscfs/deadfs/dead_vnops.c:1.65 src/sys/miscfs/deadfs/dead_vnops.c:1.66
--- src/sys/miscfs/deadfs/dead_vnops.c:1.65	Sun Jul 18 23:57:14 2021
+++ src/sys/miscfs/deadfs/dead_vnops.c	Wed Oct 20 03:08:18 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: dead_vnops.c,v 1.65 2021/07/18 23:57:14 dholland Exp $	*/
+/*	$NetBSD: dead_vnops.c,v 1.66 2021/10/20 03:08:18 thorpej Exp $	*/
 
 /*
  * Copyright (c) 1989, 1993
@@ -32,7 +32,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: dead_vnops.c,v 1.65 2021/07/18 23:57:14 dholland Exp $");
+__KERNEL_RCSID(0, "$NetBSD: dead_vnops.c,v 1.66 2021/10/20 03:08:18 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -225,10 +225,11 @@ dead_poll(void *v)
 int
 dead_remove(void *v)
 {
-	struct vop_remove_v2_args /* {
+	struct vop_remove_v3_args /* {
 		struct vnode *a_dvp;
 		struct vnode *a_vp;
 		struct componentname *a_cnp;
+		nlink_t ctx_vp_new_nlink;
 	} */ *ap = v;
 
 	vput(ap->a_vp);

Index: src/sys/miscfs/genfs/genfs.h
diff -u src/sys/miscfs/genfs/genfs.h:1.37 src/sys/miscfs/genfs/genfs.h:1.38
--- src/sys/miscfs/genfs/genfs.h:1.37	Tue Jun 29 22:34:08 2021
+++ src/sys/miscfs/genfs/genfs.h	Wed Oct 20 03:08:18 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: genfs.h,v 1.37 2021/06/29 22:34:08 dholland Exp $	*/
+/*	$NetBSD: genfs.h,v 1.38 2021/10/20 03:08:18 thorpej Exp $	*/
 
 #ifndef	_MISCFS_GENFS_GENFS_H_
 #define	_MISCFS_GENFS_GENFS_H_
@@ -84,7 +84,7 @@ int	genfs_sane_rename(const struct genfs
 	    kauth_cred_t, bool);
 
 void	genfs_rename_knote(struct vnode *, struct vnode *, struct vnode *,
-	    struct vnode *, bool);
+	    struct vnode *, nlink_t);
 void	genfs_rename_cache_purge(struct vnode *, struct vnode *, struct vnode *,
 	    struct vnode *);
 
@@ -119,10 +119,10 @@ struct genfs_rename_ops {
 	    struct vnode *fdvp, struct componentname *fcnp,
 	    void *fde, struct vnode *fvp,
 	    struct vnode *tdvp, struct componentname *tcnp,
-	    void *tde, struct vnode *tvp);
+	    void *tde, struct vnode *tvp, nlink_t *tvp_nlinkp);
 	int (*gro_remove)(struct mount *mp, kauth_cred_t cred,
 	    struct vnode *dvp, struct componentname *cnp, void *de,
-	    struct vnode *vp);
+	    struct vnode *vp, nlink_t *tvp_nlinkp);
 	int (*gro_lookup)(struct mount *mp, struct vnode *dvp,
 	    struct componentname *cnp, void *de_ret, struct vnode **vp_ret);
 	int (*gro_genealogy)(struct mount *mp, kauth_cred_t cred,

Index: src/sys/miscfs/genfs/genfs_rename.c
diff -u src/sys/miscfs/genfs/genfs_rename.c:1.5 src/sys/miscfs/genfs/genfs_rename.c:1.6
--- src/sys/miscfs/genfs/genfs_rename.c:1.5	Sat Sep  5 02:47:03 2020
+++ src/sys/miscfs/genfs/genfs_rename.c	Wed Oct 20 03:08:18 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: genfs_rename.c,v 1.5 2020/09/05 02:47:03 riastradh Exp $	*/
+/*	$NetBSD: genfs_rename.c,v 1.6 2021/10/20 03:08:18 thorpej Exp $	*/
 
 /*-
  * Copyright (c) 2012 The NetBSD Foundation, Inc.
@@ -37,7 +37,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: genfs_rename.c,v 1.5 2020/09/05 02:47:03 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: genfs_rename.c,v 1.6 2021/10/20 03:08:18 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/kauth.h>
@@ -129,7 +129,7 @@ static void genfs_rename_exit(const stru
     struct vnode *, struct vnode *);
 static int genfs_rename_remove(const struct genfs_rename_ops *, struct mount *,
     kauth_cred_t,
-    struct vnode *, struct componentname *, void *, struct vnode *);
+    struct vnode *, struct componentname *, void *, struct vnode *, nlink_t *);
 
 /*
  * genfs_insane_rename: Generic implementation of the insane API for
@@ -163,7 +163,7 @@ genfs_insane_rename(void *v,
 	struct vnode *tdvp, struct componentname *tcnp,
 	kauth_cred_t cred, bool posixly_correct))
 {
-	struct vop_rename_args	/* {
+	struct vop_rename_args /* {
 		struct vnode *a_fdvp;
 		struct vnode *a_fvp;
 		struct componentname *a_fcnp;
@@ -247,6 +247,7 @@ genfs_sane_rename(const struct genfs_ren
 {
 	struct mount *mp;
 	struct vnode *fvp = NULL, *tvp = NULL;
+	nlink_t tvp_new_nlink = 0;
 	int error;
 
 	KASSERT(ops != NULL);
@@ -314,7 +315,7 @@ genfs_sane_rename(const struct genfs_ren
 		else
 			/* XXX Can't use VOP_REMOVE because of locking.  */
 			error = genfs_rename_remove(ops, mp, cred,
-			    fdvp, fcnp, fde, fvp);
+			    fdvp, fcnp, fde, fvp, &tvp_new_nlink);
 		goto out;
 	}
 	KASSERT(fvp != tvp);
@@ -363,25 +364,29 @@ genfs_sane_rename(const struct genfs_ren
 	 */
 	error = ops->gro_rename(mp, cred,
 	    fdvp, fcnp, fde, fvp,
-	    tdvp, tcnp, tde, tvp);
+	    tdvp, tcnp, tde, tvp,
+	    &tvp_new_nlink);
 	if (error)
 		goto out;
 
 	/* Success!  */
 
-out:	genfs_rename_exit(ops, mp, fdvp, fvp, tdvp, tvp);
+out:	if (error == 0) {
+		genfs_rename_knote(fdvp, fvp, tdvp, tvp, tvp_new_nlink);
+	}
+	genfs_rename_exit(ops, mp, fdvp, fvp, tdvp, tvp);
 	return error;
 }
 
 /*
  * genfs_rename_knote: Note events about the various vnodes in a
  * rename.  To be called by gro_rename on success.  The only pair of
- * vnodes that may be identical is {fdvp, tdvp}.  deleted_p is true iff
- * the rename overwrote the last link to tvp.
+ * vnodes that may be identical is {fdvp, tdvp}.  tvp_new_nlink is
+ * the resulting link count of tvp.
  */
 void
 genfs_rename_knote(struct vnode *fdvp, struct vnode *fvp,
-    struct vnode *tdvp, struct vnode *tvp, bool deleted_p)
+    struct vnode *tdvp, struct vnode *tvp, nlink_t tvp_new_nlink)
 {
 	long fdvp_events, tdvp_events;
 	bool directory_p, reparent_p, replaced_p;
@@ -404,7 +409,6 @@ genfs_rename_knote(struct vnode *fdvp, s
 	replaced_p = (tvp != NULL);
 
 	KASSERT((tvp == NULL) || (directory_p == (tvp->v_type == VDIR)));
-	KASSERT(!deleted_p || replaced_p);
 
 	fdvp_events = NOTE_WRITE;
 	if (directory_p && reparent_p)
@@ -424,7 +428,7 @@ genfs_rename_knote(struct vnode *fdvp, s
 	}
 
 	if (replaced_p)
-		VN_KNOTE(tvp, (deleted_p? NOTE_DELETE : NOTE_LINK));
+		VN_KNOTE(tvp, (tvp_new_nlink == 0 ? NOTE_DELETE : NOTE_LINK));
 }
 
 /*
@@ -994,15 +998,15 @@ genfs_rename_exit(const struct genfs_ren
 /*
  * genfs_rename_remove: Remove the entry for the non-directory vp with
  * componentname cnp from the directory dvp, using the lookup results
- * de.  It is the responsibility of gro_remove to purge the name cache
- * and note kevents.
+ * de.  It is the responsibility of gro_remove to purge the name cache.
  *
  * Everything must be locked and referenced.
  */
 static int
 genfs_rename_remove(const struct genfs_rename_ops *ops,
     struct mount *mp, kauth_cred_t cred,
-    struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp)
+    struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp,
+    nlink_t *tvp_nlinkp)
 {
 	int error;
 
@@ -1029,7 +1033,7 @@ genfs_rename_remove(const struct genfs_r
 	if (error)
 		return error;
 
-	error = ops->gro_remove(mp, cred, dvp, cnp, de, vp);
+	error = ops->gro_remove(mp, cred, dvp, cnp, de, vp, tvp_nlinkp);
 	if (error)
 		return error;
 

Index: src/sys/miscfs/genfs/genfs_vnops.c
diff -u src/sys/miscfs/genfs/genfs_vnops.c:1.215 src/sys/miscfs/genfs/genfs_vnops.c:1.216
--- src/sys/miscfs/genfs/genfs_vnops.c:1.215	Mon Oct 11 01:49:08 2021
+++ src/sys/miscfs/genfs/genfs_vnops.c	Wed Oct 20 03:08:18 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: genfs_vnops.c,v 1.215 2021/10/11 01:49:08 thorpej Exp $	*/
+/*	$NetBSD: genfs_vnops.c,v 1.216 2021/10/20 03:08:18 thorpej Exp $	*/
 
 /*-
  * Copyright (c) 2008 The NetBSD Foundation, Inc.
@@ -57,7 +57,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: genfs_vnops.c,v 1.215 2021/10/11 01:49:08 thorpej Exp $");
+__KERNEL_RCSID(0, "$NetBSD: genfs_vnops.c,v 1.216 2021/10/20 03:08:18 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -507,9 +507,7 @@ filt_genfsdetach(struct knote *kn)
 {
 	struct vnode *vp = (struct vnode *)kn->kn_hook;
 
-	mutex_enter(vp->v_interlock);
-	SLIST_REMOVE(&vp->v_klist, kn, knote, kn_selnext);
-	mutex_exit(vp->v_interlock);
+	vn_knote_detach(vp, kn);
 }
 
 static int
@@ -644,9 +642,7 @@ genfs_kqfilter(void *v)
 
 	kn->kn_hook = vp;
 
-	mutex_enter(vp->v_interlock);
-	SLIST_INSERT_HEAD(&vp->v_klist, kn, kn_selnext);
-	mutex_exit(vp->v_interlock);
+	vn_knote_attach(vp, kn);
 
 	return (0);
 }

Index: src/sys/miscfs/genfs/layer_vnops.c
diff -u src/sys/miscfs/genfs/layer_vnops.c:1.71 src/sys/miscfs/genfs/layer_vnops.c:1.72
--- src/sys/miscfs/genfs/layer_vnops.c:1.71	Sat May 16 18:31:51 2020
+++ src/sys/miscfs/genfs/layer_vnops.c	Wed Oct 20 03:08:18 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: layer_vnops.c,v 1.71 2020/05/16 18:31:51 christos Exp $	*/
+/*	$NetBSD: layer_vnops.c,v 1.72 2021/10/20 03:08:18 thorpej Exp $	*/
 
 /*
  * Copyright (c) 1999 National Aeronautics & Space Administration
@@ -170,7 +170,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: layer_vnops.c,v 1.71 2020/05/16 18:31:51 christos Exp $");
+__KERNEL_RCSID(0, "$NetBSD: layer_vnops.c,v 1.72 2021/10/20 03:08:18 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -611,10 +611,11 @@ layer_inactive(void *v)
 int
 layer_remove(void *v)
 {
-	struct vop_remove_v2_args /* {
+	struct vop_remove_v3_args /* {
 		struct vnode		*a_dvp;
 		struct vnode		*a_vp;
 		struct componentname	*a_cnp;
+		nlink_t			 ctx_vp_new_nlink;
 	} */ *ap = v;
 	struct vnode *vp = ap->a_vp;
 	int error;
@@ -632,7 +633,7 @@ layer_remove(void *v)
 int
 layer_rename(void *v)
 {
-	struct vop_rename_args  /* {
+	struct vop_rename_args /* {
 		struct vnode		*a_fdvp;
 		struct vnode		*a_fvp;
 		struct componentname	*a_fcnp;

Index: src/sys/miscfs/umapfs/umap_vnops.c
diff -u src/sys/miscfs/umapfs/umap_vnops.c:1.61 src/sys/miscfs/umapfs/umap_vnops.c:1.62
--- src/sys/miscfs/umapfs/umap_vnops.c:1.61	Sat May 16 18:31:51 2020
+++ src/sys/miscfs/umapfs/umap_vnops.c	Wed Oct 20 03:08:18 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: umap_vnops.c,v 1.61 2020/05/16 18:31:51 christos Exp $	*/
+/*	$NetBSD: umap_vnops.c,v 1.62 2021/10/20 03:08:18 thorpej Exp $	*/
 
 /*
  * Copyright (c) 1992, 1993
@@ -39,7 +39,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: umap_vnops.c,v 1.61 2020/05/16 18:31:51 christos Exp $");
+__KERNEL_RCSID(0, "$NetBSD: umap_vnops.c,v 1.62 2021/10/20 03:08:18 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -517,7 +517,7 @@ umap_print(void *v)
 int
 umap_rename(void *v)
 {
-	struct vop_rename_args  /* {
+	struct vop_rename_args /* {
 		struct vnode *a_fdvp;
 		struct vnode *a_fvp;
 		struct componentname *a_fcnp;

Index: src/sys/nfs/nfs_bio.c
diff -u src/sys/nfs/nfs_bio.c:1.199 src/sys/nfs/nfs_bio.c:1.200
--- src/sys/nfs/nfs_bio.c:1.199	Sat Sep  5 16:30:12 2020
+++ src/sys/nfs/nfs_bio.c	Wed Oct 20 03:08:18 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: nfs_bio.c,v 1.199 2020/09/05 16:30:12 riastradh Exp $	*/
+/*	$NetBSD: nfs_bio.c,v 1.200 2021/10/20 03:08:18 thorpej Exp $	*/
 
 /*
  * Copyright (c) 1989, 1993
@@ -35,7 +35,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: nfs_bio.c,v 1.199 2020/09/05 16:30:12 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: nfs_bio.c,v 1.200 2021/10/20 03:08:18 thorpej Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_nfs.h"
@@ -455,7 +455,6 @@ nfs_write(void *v)
 	vsize_t bytelen;
 	int error = 0;
 	int ioflag = ap->a_ioflag;
-	int extended = 0, wrotedata = 0;
 
 #ifdef DIAGNOSTIC
 	if (uio->uio_rw != UIO_WRITE)
@@ -545,7 +544,6 @@ nfs_write(void *v)
 			}
 			break;
 		}
-		wrotedata = 1;
 
 		/*
 		 * update UVM's notion of the size now that we've
@@ -554,7 +552,6 @@ nfs_write(void *v)
 
 		if (vp->v_size < uio->uio_offset) {
 			uvm_vnp_setsize(vp, uio->uio_offset);
-			extended = 1;
 		}
 
 		if ((oldoff & ~(nmp->nm_wsize - 1)) !=
@@ -566,8 +563,6 @@ nfs_write(void *v)
 				       ~(nmp->nm_wsize - 1)), PGO_CLEANIT);
 		}
 	} while (uio->uio_resid > 0);
-	if (wrotedata)
-		VN_KNOTE(vp, NOTE_WRITE | (extended ? NOTE_EXTEND : 0));
 	if (error == 0 && (ioflag & IO_SYNC) != 0) {
 		rw_enter(vp->v_uobj.vmobjlock, RW_WRITER);
 		error = VOP_PUTPAGES(vp,

Index: src/sys/nfs/nfs_kq.c
diff -u src/sys/nfs/nfs_kq.c:1.31 src/sys/nfs/nfs_kq.c:1.32
--- src/sys/nfs/nfs_kq.c:1.31	Mon Oct 11 01:49:08 2021
+++ src/sys/nfs/nfs_kq.c	Wed Oct 20 03:08:18 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: nfs_kq.c,v 1.31 2021/10/11 01:49:08 thorpej Exp $	*/
+/*	$NetBSD: nfs_kq.c,v 1.32 2021/10/20 03:08:18 thorpej Exp $	*/
 
 /*-
  * Copyright (c) 2002, 2008 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: nfs_kq.c,v 1.31 2021/10/11 01:49:08 thorpej Exp $");
+__KERNEL_RCSID(0, "$NetBSD: nfs_kq.c,v 1.32 2021/10/20 03:08:18 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -191,9 +191,7 @@ filt_nfsdetach(struct knote *kn)
 	struct vnode *vp = (struct vnode *)kn->kn_hook;
 	struct kevq *ke;
 
-	mutex_enter(vp->v_interlock);
-	SLIST_REMOVE(&vp->v_klist, kn, knote, kn_selnext);
-	mutex_exit(vp->v_interlock);
+	vn_knote_detach(vp, kn);
 
 	/* Remove the vnode from watch list */
 	mutex_enter(&nfskq_lock);
@@ -364,10 +362,9 @@ nfs_kqfilter(void *v)
 		SLIST_INSERT_HEAD(&kevlist, ke, kev_link);
 	}
 
-	mutex_enter(vp->v_interlock);
-	SLIST_INSERT_HEAD(&vp->v_klist, kn, kn_selnext);
 	kn->kn_hook = vp;
-	mutex_exit(vp->v_interlock);
+
+	vn_knote_attach(vp, kn);
 
 	/* kick the poller */
 	cv_signal(&nfskq_cv);

Index: src/sys/nfs/nfs_vnops.c
diff -u src/sys/nfs/nfs_vnops.c:1.320 src/sys/nfs/nfs_vnops.c:1.321
--- src/sys/nfs/nfs_vnops.c:1.320	Sun Jul 18 23:57:15 2021
+++ src/sys/nfs/nfs_vnops.c	Wed Oct 20 03:08:18 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: nfs_vnops.c,v 1.320 2021/07/18 23:57:15 dholland Exp $	*/
+/*	$NetBSD: nfs_vnops.c,v 1.321 2021/10/20 03:08:18 thorpej Exp $	*/
 
 /*
  * Copyright (c) 1989, 1993
@@ -39,7 +39,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: nfs_vnops.c,v 1.320 2021/07/18 23:57:15 dholland Exp $");
+__KERNEL_RCSID(0, "$NetBSD: nfs_vnops.c,v 1.321 2021/10/20 03:08:18 thorpej Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_nfs.h"
@@ -652,7 +652,6 @@ nfs_setattr(void *v)
 		}
 		genfs_node_unlock(vp);
 	}
-	VN_KNOTE(vp, NOTE_ATTRIB);
 	return (error);
 }
 
@@ -1536,7 +1535,6 @@ nfs_mknod(void *v)
 	int error;
 
 	error = nfs_mknodrpc(dvp, ap->a_vpp, cnp, ap->a_vap);
-	VN_KNOTE(dvp, NOTE_WRITE);
 	if (error == 0 || error == EEXIST)
 		cache_purge1(dvp, cnp->cn_nameptr, cnp->cn_namelen, 0);
 	return (error);
@@ -1677,7 +1675,6 @@ again:
 	VTONFS(dvp)->n_flag |= NMODIFIED;
 	if (!wccflag)
 		NFS_INVALIDATE_ATTRCACHE(VTONFS(dvp));
-	VN_KNOTE(ap->a_dvp, NOTE_WRITE);
 	return (error);
 }
 
@@ -1695,11 +1692,12 @@ again:
 int
 nfs_remove(void *v)
 {
-	struct vop_remove_v2_args /* {
+	struct vop_remove_v3_args /* {
 		struct vnodeop_desc *a_desc;
 		struct vnode * a_dvp;
 		struct vnode * a_vp;
 		struct componentname * a_cnp;
+		nlink_t ctx_vp_new_nlink;
 	} */ *ap = v;
 	struct vnode *vp = ap->a_vp;
 	struct vnode *dvp = ap->a_dvp;
@@ -1736,13 +1734,12 @@ nfs_remove(void *v)
 				cnp->cn_namelen, cnp->cn_cred, curlwp);
 	} else if (!np->n_sillyrename)
 		error = nfs_sillyrename(dvp, vp, cnp, false);
-	if (!error && nfs_getattrcache(vp, &vattr) == 0 &&
-	    vattr.va_nlink == 1) {
-		np->n_flag |= NREMOVED;
+	if (error == 0 && nfs_getattrcache(vp, &vattr) == 0) {
+		ap->ctx_vp_new_nlink = vattr.va_nlink - 1;
+		if (vattr.va_nlink == 1)
+			np->n_flag |= NREMOVED;
 	}
 	NFS_INVALIDATE_ATTRCACHE(np);
-	VN_KNOTE(vp, NOTE_DELETE);
-	VN_KNOTE(dvp, NOTE_WRITE);
 	if (dvp == vp)
 		vrele(vp);
 	else
@@ -1812,7 +1809,7 @@ nfs_removerpc(struct vnode *dvp, const c
 int
 nfs_rename(void *v)
 {
-	struct vop_rename_args  /* {
+	struct vop_rename_args /* {
 		struct vnode *a_fdvp;
 		struct vnode *a_fvp;
 		struct componentname *a_fcnp;
@@ -2029,8 +2026,6 @@ nfs_link(void *v)
 		cache_purge1(dvp, cnp->cn_nameptr, cnp->cn_namelen, 0);
 	}
 	VOP_UNLOCK(vp);
-	VN_KNOTE(vp, NOTE_LINK);
-	VN_KNOTE(dvp, NOTE_WRITE);
 	return (error);
 }
 
@@ -2120,7 +2115,6 @@ nfs_symlink(void *v)
 	VTONFS(dvp)->n_flag |= NMODIFIED;
 	if (!wccflag)
 		NFS_INVALIDATE_ATTRCACHE(VTONFS(dvp));
-	VN_KNOTE(dvp, NOTE_WRITE);
 	return (error);
 }
 
@@ -2207,7 +2201,6 @@ nfs_mkdir(void *v)
 				vrele(newvp);
 		}
 	} else {
-		VN_KNOTE(dvp, NOTE_WRITE | NOTE_LINK);
 		nfs_cache_enter(dvp, newvp, cnp);
 		*ap->a_vpp = newvp;
 		VOP_UNLOCK(newvp);
@@ -2262,8 +2255,6 @@ nfs_rmdir(void *v)
 	VTONFS(dvp)->n_flag |= NMODIFIED;
 	if (!wccflag)
 		NFS_INVALIDATE_ATTRCACHE(VTONFS(dvp));
-	VN_KNOTE(dvp, NOTE_WRITE | NOTE_LINK);
-	VN_KNOTE(vp, NOTE_DELETE);
 	cache_purge(vp);
 	vput(vp);
 	/*

Index: src/sys/rump/librump/rumpvfs/rumpfs.c
diff -u src/sys/rump/librump/rumpvfs/rumpfs.c:1.165 src/sys/rump/librump/rumpvfs/rumpfs.c:1.166
--- src/sys/rump/librump/rumpvfs/rumpfs.c:1.165	Sun Jul 18 23:56:14 2021
+++ src/sys/rump/librump/rumpvfs/rumpfs.c	Wed Oct 20 03:08:18 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: rumpfs.c,v 1.165 2021/07/18 23:56:14 dholland Exp $	*/
+/*	$NetBSD: rumpfs.c,v 1.166 2021/10/20 03:08:18 thorpej Exp $	*/
 
 /*
  * Copyright (c) 2009, 2010, 2011 Antti Kantee.  All Rights Reserved.
@@ -26,7 +26,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: rumpfs.c,v 1.165 2021/07/18 23:56:14 dholland Exp $");
+__KERNEL_RCSID(0, "$NetBSD: rumpfs.c,v 1.166 2021/10/20 03:08:18 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/atomic.h>
@@ -1084,10 +1084,11 @@ out:
 static int
 rump_vop_remove(void *v)
 {
-        struct vop_remove_v2_args /* {
+        struct vop_remove_v3_args /* {
                 struct vnode *a_dvp;
                 struct vnode *a_vp;
                 struct componentname *a_cnp;
+		nlink_t ctx_vp_new_nlink;
         }; */ *ap = v;
 	struct vnode *dvp = ap->a_dvp;
 	struct vnode *vp = ap->a_vp;

Index: src/sys/sys/event.h
diff -u src/sys/sys/event.h:1.48 src/sys/sys/event.h:1.49
--- src/sys/sys/event.h:1.48	Wed Oct 13 04:57:19 2021
+++ src/sys/sys/event.h	Wed Oct 20 03:08:18 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: event.h,v 1.48 2021/10/13 04:57:19 thorpej Exp $	*/
+/*	$NetBSD: event.h,v 1.49 2021/10/20 03:08:18 thorpej Exp $	*/
 
 /*-
  * Copyright (c) 1999,2000,2001 Jonathan Lemon <jle...@freebsd.org>
@@ -145,6 +145,10 @@ _EV_SET(struct kevent *_kevp, uintptr_t 
 #define	NOTE_LINK	0x0010U			/* link count changed */
 #define	NOTE_RENAME	0x0020U			/* vnode was renamed */
 #define	NOTE_REVOKE	0x0040U			/* vnode access was revoked */
+#define	NOTE_OPEN	0x0080U			/* vnode was opened */
+#define	NOTE_CLOSE	0x0100U			/* file closed (no FWRITE) */
+#define	NOTE_CLOSE_WRITE 0x0200U		/* file closed (FWRITE) */
+#define	NOTE_READ	0x0400U			/* file was read */
 
 /*
  * data/hint flags for EVFILT_PROC, shared with userspace

Index: src/sys/sys/vnode.h
diff -u src/sys/sys/vnode.h:1.297 src/sys/sys/vnode.h:1.298
--- src/sys/sys/vnode.h:1.297	Tue Jun 29 22:40:53 2021
+++ src/sys/sys/vnode.h	Wed Oct 20 03:08:18 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: vnode.h,v 1.297 2021/06/29 22:40:53 dholland Exp $	*/
+/*	$NetBSD: vnode.h,v 1.298 2021/10/20 03:08:18 thorpej Exp $	*/
 
 /*-
  * Copyright (c) 2008, 2020 The NetBSD Foundation, Inc.
@@ -180,6 +180,7 @@ struct vnode {
 	enum vtagtype	v_tag;			/* -   type of underlying data */
 	void 		*v_data;		/* -   private data for fs */
 	struct klist	v_klist;		/* i   notes attached to vnode */
+	long		v_klist_interest;	/* i   what the noes are interested in */
 	void		*v_segvguard;		/* e   for PAX_SEGVGUARD */
 };
 #define	v_mountedhere	v_un.vu_mountedhere
@@ -411,15 +412,32 @@ void vref(struct vnode *);
 
 #define	NULLVP	((struct vnode *)NULL)
 
-static __inline void
+/*
+ * Macro to determine kevent interest on a vnode.
+ */
+#define	VN_KEVENT_INTEREST(vp, n)					\
+	((vp)->v_klist_interest != 0)
+
+static inline void
 VN_KNOTE(struct vnode *vp, long hint)
 {
-
-	mutex_enter(vp->v_interlock);
-	KNOTE(&vp->v_klist, hint);
-	mutex_exit(vp->v_interlock);
+	/*
+	 * Testing for klist interest unlocked is fine here, as registering
+	 * interest in vnode events is inherently racy with respect to other
+	 * system activity anyway.  Having knotes attached to vnodes is
+	 * actually incredibly rare, so we want to void having to take locks,
+	 * etc. in what is the overwhelmingly common case.
+	 */
+	if (__predict_false(VN_KEVENT_INTEREST(vp, hint))) {
+		mutex_enter(vp->v_interlock);
+		knote(&vp->v_klist, hint);
+		mutex_exit(vp->v_interlock);
+	}
 }
 
+void	vn_knote_attach(struct vnode *, struct knote *);
+void	vn_knote_detach(struct vnode *, struct knote *);
+
 /*
  * Global vnode data.
  */

Index: src/sys/ufs/chfs/chfs_vnops.c
diff -u src/sys/ufs/chfs/chfs_vnops.c:1.45 src/sys/ufs/chfs/chfs_vnops.c:1.46
--- src/sys/ufs/chfs/chfs_vnops.c:1.45	Sun Jul 18 23:56:14 2021
+++ src/sys/ufs/chfs/chfs_vnops.c	Wed Oct 20 03:08:19 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: chfs_vnops.c,v 1.45 2021/07/18 23:56:14 dholland Exp $	*/
+/*	$NetBSD: chfs_vnops.c,v 1.46 2021/10/20 03:08:19 thorpej Exp $	*/
 
 /*-
  * Copyright (c) 2010 Department of Software Engineering,
@@ -206,7 +206,6 @@ chfs_create(void *v)
 		return error;
 	}
 
-	VN_KNOTE(ap->a_dvp, NOTE_WRITE);
 	return 0;
 }
 /* --------------------------------------------------------------------- */
@@ -773,7 +772,6 @@ chfs_write(void *v)
 	off_t osize, origoff, oldoff, preallocoff, endallocoff, nsize;
 	int blkoffset, error, flags, ioflag, resid;
 	int aflag;
-	int extended=0;
 	vsize_t bytelen;
 	bool async;
 	struct ufsmount *ump;
@@ -946,7 +944,6 @@ chfs_write(void *v)
 
 		if (vp->v_size < newoff) {
 			uvm_vnp_setsize(vp, newoff);
-			extended = 1;
 		}
 
 		if (error)
@@ -988,8 +985,6 @@ out:
 				ip->mode &= ~ISGID;
 		}
 	}
-	if (resid > uio->uio_resid)
-		VN_KNOTE(vp, NOTE_WRITE | (extended ? NOTE_EXTEND : 0));
 	if (error) {
 		(void) UFS_TRUNCATE(vp, osize, ioflag & IO_SYNC, ap->a_cred);
 		uio->uio_offset -= resid - uio->uio_resid;
@@ -1038,9 +1033,15 @@ chfs_fsync(void *v)
 int
 chfs_remove(void *v)
 {
-	struct vnode *dvp = ((struct vop_remove_v2_args *) v)->a_dvp;
-	struct vnode *vp = ((struct vop_remove_v2_args *) v)->a_vp;
-	struct componentname *cnp = (((struct vop_remove_v2_args *) v)->a_cnp);
+	struct vop_remove_v3_args /* {
+		struct vnode *a_dvp;
+		struct vnode *a_vp;
+		struct componentname *a_cnp;
+		nlink_t ctx_vp_new_nlink;
+	} */ *ap = v;
+	struct vnode *dvp = ap->a_dvp;
+	struct vnode *vp = ap->a_vp;
+	struct componentname *cnp = ap->a_cnp;
 	dbg("remove\n");
 
 	KASSERT(VOP_ISLOCKED(dvp));
@@ -1060,6 +1061,9 @@ chfs_remove(void *v)
 
 	error = chfs_do_unlink(ip,
 	    parent, cnp->cn_nameptr, cnp->cn_namelen);
+	if (error == 0) {
+		ap->ctx_vp_new_nlink = ip->chvc->nlink;
+	}
 
 out:
 	vput(vp);
@@ -1111,12 +1115,21 @@ out:
 int
 chfs_rename(void *v)
 {
-	struct vnode *fdvp = ((struct vop_rename_args *) v)->a_fdvp;
-	struct vnode *fvp = ((struct vop_rename_args *) v)->a_fvp;
-	struct componentname *fcnp = ((struct vop_rename_args *) v)->a_fcnp;
-	struct vnode *tdvp = ((struct vop_rename_args *) v)->a_tdvp;
-	struct vnode *tvp = ((struct vop_rename_args *) v)->a_tvp;
-	struct componentname *tcnp = ((struct vop_rename_args *) v)->a_tcnp;
+	struct vop_rename_args /* {
+		const struct vnodeop_desc *a_desc;
+		struct vnode *a_fdvp;
+		struct vnode *a_fvp;
+		struct componentname *a_fcnp;
+		struct vnode *a_tdvp;
+		struct vnode *a_tvp;
+		struct componentname *a_tcnp;
+	} */ *ap = v;
+	struct vnode *fdvp = ap->a_fdvp;
+	struct vnode *fvp = ap->a_fvp;
+	struct componentname *fcnp = ap->a_fcnp;
+	struct vnode *tdvp = ap->a_tdvp;
+	struct vnode *tvp = ap->a_tvp;
+	struct componentname *tcnp = ap->a_tcnp;
 
 	struct chfs_inode *oldparent, *old;
 	struct chfs_inode *newparent;
@@ -1262,7 +1275,6 @@ chfs_symlink(void *v)
 	err = chfs_makeinode(IFLNK | vap->va_mode, dvp, vpp, cnp, VLNK);
 	if (err)
 		return (err);
-	VN_KNOTE(dvp, NOTE_WRITE);
 	vp = *vpp;
 	len = strlen(target);
 	ip = VTOI(vp);

Index: src/sys/ufs/ext2fs/ext2fs_readwrite.c
diff -u src/sys/ufs/ext2fs/ext2fs_readwrite.c:1.77 src/sys/ufs/ext2fs/ext2fs_readwrite.c:1.78
--- src/sys/ufs/ext2fs/ext2fs_readwrite.c:1.77	Thu Apr 23 21:47:08 2020
+++ src/sys/ufs/ext2fs/ext2fs_readwrite.c	Wed Oct 20 03:08:19 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: ext2fs_readwrite.c,v 1.77 2020/04/23 21:47:08 ad Exp $	*/
+/*	$NetBSD: ext2fs_readwrite.c,v 1.78 2021/10/20 03:08:19 thorpej Exp $	*/
 
 /*-
  * Copyright (c) 1993
@@ -60,7 +60,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ext2fs_readwrite.c,v 1.77 2020/04/23 21:47:08 ad Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ext2fs_readwrite.c,v 1.78 2021/10/20 03:08:19 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -83,7 +83,7 @@ __KERNEL_RCSID(0, "$NetBSD: ext2fs_readw
 
 static int	ext2fs_post_read_update(struct vnode *, int, int);
 static int	ext2fs_post_write_update(struct vnode *, struct uio *, int,
-		    kauth_cred_t, off_t, int, int, int);
+		    kauth_cred_t, off_t, int, int);
 
 /*
  * Vnode op for reading.
@@ -271,7 +271,6 @@ ext2fs_write(void *v)
 	vsize_t bytelen;
 	off_t oldoff = 0;					/* XXX */
 	bool async;
-	int extended = 0;
 	int advice;
 
 	ioflag = ap->a_ioflag;
@@ -327,7 +326,6 @@ ext2fs_write(void *v)
 
 		if (vp->v_size < uio->uio_offset) {
 			uvm_vnp_setsize(vp, uio->uio_offset);
-			extended = 1;
 		}
 
 		/*
@@ -350,7 +348,7 @@ ext2fs_write(void *v)
 	}
 
 	error = ext2fs_post_write_update(vp, uio, ioflag, ap->a_cred, osize,
-	    resid, extended, error);
+	    resid, error);
 	return error;
 }
 
@@ -368,7 +366,6 @@ ext2fs_bufwr(struct vnode *vp, struct ui
 	off_t osize;
 	daddr_t lbn;
 	int resid, blkoffset, xfersize;
-	int extended = 0;
 	int error;
 
 	KASSERT(VOP_ISLOCKED(vp) == LK_EXCLUSIVE);
@@ -418,7 +415,6 @@ ext2fs_bufwr(struct vnode *vp, struct ui
 
 		if (vp->v_size < uio->uio_offset) {
 			uvm_vnp_setsize(vp, uio->uio_offset);
-			extended = 1;
 		}
 
 		if (ioflag & IO_SYNC)
@@ -432,13 +428,13 @@ ext2fs_bufwr(struct vnode *vp, struct ui
 	}
 
 	error = ext2fs_post_write_update(vp, uio, ioflag, cred, osize, resid,
-	    extended, error);
+	    error);
 	return error;
 }
 
 static int
 ext2fs_post_write_update(struct vnode *vp, struct uio *uio, int ioflag,
-    kauth_cred_t cred, off_t osize, int resid, int extended, int oerror)
+    kauth_cred_t cred, off_t osize, int resid, int oerror)
 {
 	struct inode *ip = VTOI(vp);
 	int error = oerror;
@@ -467,10 +463,6 @@ ext2fs_post_write_update(struct vnode *v
 		}
 	}
 
-	/* If we successfully wrote anything, notify kevent listeners.  */
-	if (resid > uio->uio_resid)
-		VN_KNOTE(vp, NOTE_WRITE | (extended ? NOTE_EXTEND : 0));
-
 	/*
 	 * Update the size on disk: truncate back to original size on
 	 * error, or reflect the new size on success.

Index: src/sys/ufs/ext2fs/ext2fs_rename.c
diff -u src/sys/ufs/ext2fs/ext2fs_rename.c:1.11 src/sys/ufs/ext2fs/ext2fs_rename.c:1.12
--- src/sys/ufs/ext2fs/ext2fs_rename.c:1.11	Mon Aug 15 18:38:10 2016
+++ src/sys/ufs/ext2fs/ext2fs_rename.c	Wed Oct 20 03:08:19 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: ext2fs_rename.c,v 1.11 2016/08/15 18:38:10 jdolecek Exp $	*/
+/*	$NetBSD: ext2fs_rename.c,v 1.12 2021/10/20 03:08:19 thorpej Exp $	*/
 
 /*-
  * Copyright (c) 2012 The NetBSD Foundation, Inc.
@@ -34,7 +34,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ext2fs_rename.c,v 1.11 2016/08/15 18:38:10 jdolecek Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ext2fs_rename.c,v 1.12 2021/10/20 03:08:19 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/buf.h>
@@ -272,7 +272,7 @@ ext2fs_gro_rename(struct mount *mp, kaut
     struct vnode *fdvp, struct componentname *fcnp,
     void *fde, struct vnode *fvp,
     struct vnode *tdvp, struct componentname *tcnp,
-    void *tde, struct vnode *tvp)
+    void *tde, struct vnode *tvp, nlink_t *tvp_nlinkp)
 {
 	struct ufs_lookup_results *fulr = fde;
 	struct ufs_lookup_results *tulr = tde;
@@ -445,6 +445,7 @@ ext2fs_gro_rename(struct mount *mp, kaut
 				goto whymustithurtsomuch;
 #endif
 		}
+		*tvp_nlinkp = VTOI(tvp)->i_e2fs_nlink;
 		/*
 		 * XXX Why is this here, and not above the preceding
 		 * conditional?
@@ -489,13 +490,6 @@ ext2fs_gro_rename(struct mount *mp, kaut
 	if (error)
 		goto whymustithurtsomuch;
 
-	/*
-	 * XXX Perhaps this should go at the top, in case the file
-	 * system is modified but incompletely so because of an
-	 * intermediate error.
-	 */
-	genfs_rename_knote(fdvp, fvp, tdvp, tvp,
-	    ((tvp != NULL) && (VTOI(tvp)->i_e2fs_nlink == 0)));
 #if 0				/* XXX */
 	genfs_rename_cache_purge(fdvp, fvp, tdvp, tvp);
 #endif
@@ -691,7 +685,8 @@ next:
  */
 static int
 ext2fs_gro_remove(struct mount *mp, kauth_cred_t cred,
-    struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp)
+    struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp,
+    nlink_t *tvp_nlinkp)
 {
 	struct ufs_lookup_results *ulr = de;
 	int error;
@@ -718,8 +713,7 @@ ext2fs_gro_remove(struct mount *mp, kaut
 	VTOI(vp)->i_e2fs_nlink--;
 	VTOI(vp)->i_flag |= IN_CHANGE;
 
-	VN_KNOTE(dvp, NOTE_WRITE);
-	VN_KNOTE(vp, (VTOI(vp)->i_e2fs_nlink? NOTE_LINK : NOTE_DELETE));
+	*tvp_nlinkp = VTOI(vp)->i_e2fs_nlink;
 
 	return 0;
 }

Index: src/sys/ufs/ext2fs/ext2fs_vnops.c
diff -u src/sys/ufs/ext2fs/ext2fs_vnops.c:1.135 src/sys/ufs/ext2fs/ext2fs_vnops.c:1.136
--- src/sys/ufs/ext2fs/ext2fs_vnops.c:1.135	Sun Jul 18 23:57:15 2021
+++ src/sys/ufs/ext2fs/ext2fs_vnops.c	Wed Oct 20 03:08:19 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: ext2fs_vnops.c,v 1.135 2021/07/18 23:57:15 dholland Exp $	*/
+/*	$NetBSD: ext2fs_vnops.c,v 1.136 2021/10/20 03:08:19 thorpej Exp $	*/
 
 /*
  * Copyright (c) 1982, 1986, 1989, 1993
@@ -65,7 +65,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ext2fs_vnops.c,v 1.135 2021/07/18 23:57:15 dholland Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ext2fs_vnops.c,v 1.136 2021/10/20 03:08:19 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -140,7 +140,6 @@ ext2fs_create(void *v)
 
 	if (error)
 		return error;
-	VN_KNOTE(ap->a_dvp, NOTE_WRITE);
 	VOP_UNLOCK(*ap->a_vpp);
 	return 0;
 }
@@ -165,7 +164,6 @@ ext2fs_mknod(void *v)
 
 	if ((error = ext2fs_makeinode(vap, ap->a_dvp, vpp, ap->a_cnp, 1)) != 0)
 		return error;
-	VN_KNOTE(ap->a_dvp, NOTE_WRITE);
 	ip = VTOI(*vpp);
 	ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE;
 	VOP_UNLOCK(*vpp);
@@ -453,7 +451,6 @@ ext2fs_setattr(void *v)
 			return EROFS;
 		error = ext2fs_chmod(vp, (int)vap->va_mode, cred, l);
 	}
-	VN_KNOTE(vp, NOTE_ATTRIB);
 	return error;
 }
 
@@ -531,10 +528,11 @@ ext2fs_chown(struct vnode *vp, uid_t uid
 int
 ext2fs_remove(void *v)
 {
-	struct vop_remove_v2_args /* {
+	struct vop_remove_v3_args /* {
 		struct vnode *a_dvp;
 		struct vnode *a_vp;
 		struct componentname *a_cnp;
+		nlink_t ctx_vp_new_nlink;
 	} */ *ap = v;
 	struct inode *ip;
 	struct vnode *vp = ap->a_vp;
@@ -556,11 +554,10 @@ ext2fs_remove(void *v)
 		if (error == 0) {
 			ip->i_e2fs_nlink--;
 			ip->i_flag |= IN_CHANGE;
+			ap->ctx_vp_new_nlink = ip->i_e2fs_nlink;
 		}
 	}
 
-	VN_KNOTE(vp, NOTE_DELETE);
-	VN_KNOTE(dvp, NOTE_WRITE);
 	if (dvp == vp)
 		vrele(vp);
 	else
@@ -622,8 +619,6 @@ ext2fs_link(void *v)
 out1:
 	VOP_UNLOCK(vp);
 out2:
-	VN_KNOTE(vp, NOTE_LINK);
-	VN_KNOTE(dvp, NOTE_WRITE);
 	return error;
 }
 
@@ -748,7 +743,6 @@ bad:
 		ip->i_flag |= IN_CHANGE;
 		vput(tvp);
 	} else {
-		VN_KNOTE(dvp, NOTE_WRITE | NOTE_LINK);
 		VOP_UNLOCK(tvp);
 		*ap->a_vpp = tvp;
 	}
@@ -817,7 +811,6 @@ ext2fs_rmdir(void *v)
 	if (dp->i_e2fs_nlink != EXT2FS_LINK_INF)
 		dp->i_e2fs_nlink--;
 	dp->i_flag |= IN_CHANGE;
-	VN_KNOTE(dvp, NOTE_WRITE | NOTE_LINK);
 	cache_purge(dvp);
 	/*
 	 * Truncate inode.  The only stuff left
@@ -834,7 +827,6 @@ ext2fs_rmdir(void *v)
 	error = ext2fs_truncate(vp, (off_t)0, IO_SYNC, cnp->cn_cred);
 	cache_purge(ITOV(ip));
 out:
-	VN_KNOTE(vp, NOTE_DELETE);
 	vput(vp);
 	return error;
 }
@@ -861,7 +853,6 @@ ext2fs_symlink(void *v)
 	error = ext2fs_makeinode(ap->a_vap, ap->a_dvp, vpp, ap->a_cnp, 1);
 	if (error)
 		return error;
-	VN_KNOTE(ap->a_dvp, NOTE_WRITE);
 	vp = *vpp;
 	len = strlen(ap->a_target);
 	ip = VTOI(vp);

Index: src/sys/ufs/lfs/lfs_rename.c
diff -u src/sys/ufs/lfs/lfs_rename.c:1.24 src/sys/ufs/lfs/lfs_rename.c:1.25
--- src/sys/ufs/lfs/lfs_rename.c:1.24	Sat Sep  5 16:30:13 2020
+++ src/sys/ufs/lfs/lfs_rename.c	Wed Oct 20 03:08:19 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: lfs_rename.c,v 1.24 2020/09/05 16:30:13 riastradh Exp $	*/
+/*	$NetBSD: lfs_rename.c,v 1.25 2021/10/20 03:08:19 thorpej Exp $	*/
 /*  from NetBSD: ufs_rename.c,v 1.12 2015/03/27 17:27:56 riastradh Exp  */
 
 /*-
@@ -89,7 +89,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: lfs_rename.c,v 1.24 2020/09/05 16:30:13 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: lfs_rename.c,v 1.25 2021/10/20 03:08:19 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -477,7 +477,8 @@ next:
  */
 static int
 ulfs_gro_remove(struct mount *mp, kauth_cred_t cred,
-    struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp)
+    struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp,
+    nlink_t *tvp_nlinkp)
 {
 	struct ulfs_lookup_results *ulr = de;
 	int error;
@@ -498,13 +499,9 @@ ulfs_gro_remove(struct mount *mp, kauth_
 
 	/* XXX ulfs_dirremove decrements vp's link count for us.  */
 	error = ulfs_dirremove(dvp, ulr, VTOI(vp), cnp->cn_flags, 0);
-	if (error)
-		goto out1;
 
-	VN_KNOTE(dvp, NOTE_WRITE);
-	VN_KNOTE(vp, (VTOI(vp)->i_nlink? NOTE_LINK : NOTE_DELETE));
+	*tvp_nlinkp = VTOI(vp)->i_nlink;
 
-out1:
 	return error;
 }
 
@@ -732,7 +729,7 @@ ulfs_gro_rename(struct mount *mp, kauth_
     struct vnode *fdvp, struct componentname *fcnp,
     void *fde, struct vnode *fvp,
     struct vnode *tdvp, struct componentname *tcnp,
-    void *tde, struct vnode *tvp)
+    void *tde, struct vnode *tvp, nlink_t *tvp_nlinkp)
 {
 	struct lfs *fs;
 	struct ulfs_lookup_results *fulr = fde;
@@ -987,13 +984,9 @@ ulfs_gro_rename(struct mount *mp, kauth_
 #endif
 		goto arghmybrainhurts;
 
-	/*
-	 * XXX Perhaps this should go at the top, in case the file
-	 * system is modified but incompletely so because of an
-	 * intermediate error.
-	 */
-	genfs_rename_knote(fdvp, fvp, tdvp, tvp,
-	    ((tvp != NULL) && (VTOI(tvp)->i_nlink == 0)));
+	if (tvp != NULL) {
+		*tvp_nlinkp = VTOI(tvp)->i_nlink;
+	}
 #if 0				/* XXX */
 	genfs_rename_cache_purge(fdvp, fvp, tdvp, tvp);
 #endif
@@ -1019,7 +1012,7 @@ lfs_gro_rename(struct mount *mp, kauth_c
     struct vnode *fdvp, struct componentname *fcnp,
     void *fde, struct vnode *fvp,
     struct vnode *tdvp, struct componentname *tcnp,
-    void *tde, struct vnode *tvp)
+    void *tde, struct vnode *tvp, nlink_t *tvp_nlinkp)
 {
 	int error;
 
@@ -1054,7 +1047,8 @@ lfs_gro_rename(struct mount *mp, kauth_c
 
 	error = ulfs_gro_rename(mp, cred,
 	    fdvp, fcnp, fde, fvp,
-	    tdvp, tcnp, tde, tvp);
+	    tdvp, tcnp, tde, tvp,
+	    tvp_nlinkp);
 
 	if (tvp && VTOI(tvp)->i_nlink == 0)
 		lfs_orphan(VTOI(tvp)->i_lfs, VTOI(tvp)->i_number);

Index: src/sys/ufs/lfs/lfs_vnops.c
diff -u src/sys/ufs/lfs/lfs_vnops.c:1.339 src/sys/ufs/lfs/lfs_vnops.c:1.340
--- src/sys/ufs/lfs/lfs_vnops.c:1.339	Sun Jul 18 23:57:15 2021
+++ src/sys/ufs/lfs/lfs_vnops.c	Wed Oct 20 03:08:19 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: lfs_vnops.c,v 1.339 2021/07/18 23:57:15 dholland Exp $	*/
+/*	$NetBSD: lfs_vnops.c,v 1.340 2021/10/20 03:08:19 thorpej Exp $	*/
 
 /*-
  * Copyright (c) 1999, 2000, 2001, 2002, 2003 The NetBSD Foundation, Inc.
@@ -125,7 +125,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: lfs_vnops.c,v 1.339 2021/07/18 23:57:15 dholland Exp $");
+__KERNEL_RCSID(0, "$NetBSD: lfs_vnops.c,v 1.340 2021/10/20 03:08:19 thorpej Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_compat_netbsd.h"
@@ -685,7 +685,6 @@ lfs_symlink(void *v)
 	}
 	KASSERT(VOP_ISLOCKED(*vpp) == LK_EXCLUSIVE);
 
-	VN_KNOTE(ap->a_dvp, NOTE_WRITE);
 	ip = VTOI(*vpp);
 
 	/*
@@ -780,7 +779,6 @@ lfs_mknod(void *v)
 	}
 	KASSERT(VOP_ISLOCKED(*vpp) == LK_EXCLUSIVE);
 
-	VN_KNOTE(dvp, NOTE_WRITE);
 	ip = VTOI(*vpp);
 	ino = ip->i_number;
 	ip->i_state |= IN_ACCESS | IN_CHANGE | IN_UPDATE;
@@ -850,7 +848,6 @@ lfs_create(void *v)
 		goto out;
 	}
 	KASSERT(VOP_ISLOCKED(*vpp) == LK_EXCLUSIVE);
-	VN_KNOTE(dvp, NOTE_WRITE);
 	VOP_UNLOCK(*vpp);
 
 out:
@@ -1002,7 +999,6 @@ lfs_mkdir(void *v)
 			      cnp, ip->i_number, LFS_IFTODT(ip->i_mode), bp);
  bad:
 	if (error == 0) {
-		VN_KNOTE(dvp, NOTE_WRITE | NOTE_LINK);
 		VOP_UNLOCK(tvp);
 	} else {
 		dp->i_nlink--;
@@ -1035,10 +1031,11 @@ out:
 int
 lfs_remove(void *v)
 {
-	struct vop_remove_v2_args /* {
+	struct vop_remove_v3_args /* {
 		struct vnode *a_dvp;
 		struct vnode *a_vp;
 		struct componentname *a_cnp;
+		nlink_t ctx_vp_new_nlink;
 	} */ *ap = v;
 	struct vnode *dvp, *vp;
 	struct inode *ip;

Index: src/sys/ufs/lfs/ulfs_readwrite.c
diff -u src/sys/ufs/lfs/ulfs_readwrite.c:1.27 src/sys/ufs/lfs/ulfs_readwrite.c:1.28
--- src/sys/ufs/lfs/ulfs_readwrite.c:1.27	Thu Apr 23 21:47:09 2020
+++ src/sys/ufs/lfs/ulfs_readwrite.c	Wed Oct 20 03:08:19 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: ulfs_readwrite.c,v 1.27 2020/04/23 21:47:09 ad Exp $	*/
+/*	$NetBSD: ulfs_readwrite.c,v 1.28 2021/10/20 03:08:19 thorpej Exp $	*/
 /*  from NetBSD: ufs_readwrite.c,v 1.120 2015/04/12 22:48:38 riastradh Exp  */
 
 /*-
@@ -33,7 +33,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(1, "$NetBSD: ulfs_readwrite.c,v 1.27 2020/04/23 21:47:09 ad Exp $");
+__KERNEL_RCSID(1, "$NetBSD: ulfs_readwrite.c,v 1.28 2021/10/20 03:08:19 thorpej Exp $");
 
 #define	FS			struct lfs
 #define	I_FS			i_lfs
@@ -48,7 +48,7 @@ __KERNEL_RCSID(1, "$NetBSD: ulfs_readwri
 
 static int	ulfs_post_read_update(struct vnode *, int, int);
 static int	ulfs_post_write_update(struct vnode *, struct uio *, int,
-		    kauth_cred_t, off_t, int, int, int);
+		    kauth_cred_t, off_t, int, int);
 
 /*
  * Vnode op for reading.
@@ -239,7 +239,6 @@ WRITE(void *v)
 	off_t osize, origoff, oldoff, preallocoff, endallocoff, nsize;
 	int blkoffset, error, flags, ioflag, resid;
 	int aflag;
-	int extended=0;
 	vsize_t bytelen;
 	bool async;
 
@@ -384,7 +383,6 @@ WRITE(void *v)
 
 		if (vp->v_size < newoff) {
 			uvm_vnp_setsize(vp, newoff);
-			extended = 1;
 		}
 
 		if (error)
@@ -406,7 +404,7 @@ WRITE(void *v)
 
 out:
 	error = ulfs_post_write_update(vp, uio, ioflag, cred, osize, resid,
-	    extended, error);
+	    error);
 
 	return (error);
 }
@@ -424,7 +422,6 @@ BUFWR(struct vnode *vp, struct uio *uio,
 	off_t osize;
 	int resid, xfersize, size, blkoffset;
 	daddr_t lbn;
-	int extended=0;
 	int error;
 	bool need_unreserve = false;
 
@@ -482,7 +479,6 @@ BUFWR(struct vnode *vp, struct uio *uio,
 			ip->i_size = uio->uio_offset + xfersize;
 			DIP_ASSIGN(ip, size, ip->i_size);
 			uvm_vnp_setsize(vp, ip->i_size);
-			extended = 1;
 		}
 		size = lfs_blksize(fs, ip, lbn) - bp->b_resid;
 		if (xfersize > size)
@@ -512,14 +508,14 @@ BUFWR(struct vnode *vp, struct uio *uio,
 	}
 
 	error = ulfs_post_write_update(vp, uio, ioflag, cred, osize, resid,
-	    extended, error);
+	    error);
 
 	return (error);
 }
 
 static int
 ulfs_post_write_update(struct vnode *vp, struct uio *uio, int ioflag,
-    kauth_cred_t cred, off_t osize, int resid, int extended, int oerror)
+    kauth_cred_t cred, off_t osize, int resid, int oerror)
 {
 	struct inode *ip = VTOI(vp);
 	int error = oerror;
@@ -552,10 +548,6 @@ ulfs_post_write_update(struct vnode *vp,
 		}
 	}
 
-	/* If we successfully wrote anything, notify kevent listeners.  */
-	if (resid > uio->uio_resid)
-		VN_KNOTE(vp, NOTE_WRITE | (extended ? NOTE_EXTEND : 0));
-
 	/*
 	 * Update the size on disk: truncate back to original size on
 	 * error, or reflect the new size on success.

Index: src/sys/ufs/lfs/ulfs_vnops.c
diff -u src/sys/ufs/lfs/ulfs_vnops.c:1.54 src/sys/ufs/lfs/ulfs_vnops.c:1.55
--- src/sys/ufs/lfs/ulfs_vnops.c:1.54	Sat Sep  5 16:30:13 2020
+++ src/sys/ufs/lfs/ulfs_vnops.c	Wed Oct 20 03:08:19 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: ulfs_vnops.c,v 1.54 2020/09/05 16:30:13 riastradh Exp $	*/
+/*	$NetBSD: ulfs_vnops.c,v 1.55 2021/10/20 03:08:19 thorpej Exp $	*/
 /*  from NetBSD: ufs_vnops.c,v 1.232 2016/05/19 18:32:03 riastradh Exp  */
 
 /*-
@@ -67,7 +67,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ulfs_vnops.c,v 1.54 2020/09/05 16:30:13 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ulfs_vnops.c,v 1.55 2021/10/20 03:08:19 thorpej Exp $");
 
 #if defined(_KERNEL_OPT)
 #include "opt_lfs.h"
@@ -410,7 +410,6 @@ ulfs_setattr(void *v)
 		}
 		error = ulfs_chmod(vp, (int)vap->va_mode, cred, l);
 	}
-	VN_KNOTE(vp, NOTE_ATTRIB);
 out:
 	return (error);
 }
@@ -506,10 +505,11 @@ ulfs_chown(struct vnode *vp, uid_t uid, 
 int
 ulfs_remove(void *v)
 {
-	struct vop_remove_v2_args /* {
+	struct vop_remove_v3_args /* {
 		struct vnode		*a_dvp;
 		struct vnode		*a_vp;
 		struct componentname	*a_cnp;
+		nlink_t			 ctx_vp_new_nlink;
 	} */ *ap = v;
 	struct vnode	*vp, *dvp;
 	struct inode	*ip;
@@ -535,9 +535,10 @@ ulfs_remove(void *v)
 	else {
 		error = ulfs_dirremove(dvp, ulr,
 				      ip, ap->a_cnp->cn_flags, 0);
+		if (error == 0) {
+			ap->ctx_vp_new_nlink = ip->i_nlink;
+		}
 	}
-	VN_KNOTE(vp, NOTE_DELETE);
-	VN_KNOTE(dvp, NOTE_WRITE);
 	if (dvp == vp)
 		vrele(vp);
 	else
@@ -608,8 +609,6 @@ ulfs_link(void *v)
  out1:
 	VOP_UNLOCK(vp);
  out2:
-	VN_KNOTE(vp, NOTE_LINK);
-	VN_KNOTE(dvp, NOTE_WRITE);
 	return (error);
 }
 
@@ -734,7 +733,6 @@ ulfs_rmdir(void *v)
 	if (error) {
 		goto out;
 	}
-	VN_KNOTE(dvp, NOTE_WRITE | NOTE_LINK);
 	cache_purge(dvp);
 	/*
 	 * Truncate inode.  The only stuff left in the directory is "." and
@@ -754,7 +752,6 @@ ulfs_rmdir(void *v)
 		ulfsdirhash_free(ip);
 #endif
  out:
-	VN_KNOTE(vp, NOTE_DELETE);
 	vput(vp);
 	return (error);
 }

Index: src/sys/ufs/ufs/ufs_acl.c
diff -u src/sys/ufs/ufs/ufs_acl.c:1.2 src/sys/ufs/ufs/ufs_acl.c:1.3
--- src/sys/ufs/ufs/ufs_acl.c:1.2	Sun Oct 10 23:02:10 2021
+++ src/sys/ufs/ufs/ufs_acl.c	Wed Oct 20 03:08:19 2021
@@ -36,7 +36,7 @@
 #if 0
 __FBSDID("$FreeBSD: head/sys/ufs/ufs/ufs_acl.c 356669 2020-01-13 02:31:51Z mjg $");
 #endif
-__KERNEL_RCSID(0, "$NetBSD: ufs_acl.c,v 1.2 2021/10/10 23:02:10 thorpej Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ufs_acl.c,v 1.3 2021/10/20 03:08:19 thorpej Exp $");
 
 #if defined(_KERNEL_OPT) 
 #include "opt_ffs.h"
@@ -425,8 +425,6 @@ ufs_setacl_nfs4_internal(struct vnode *v
 	DIP_ASSIGN(ip, mode, ip->i_mode);
 	ip->i_flag |= IN_CHANGE;
 
-	VN_KNOTE(vp, NOTE_REVOKE);
-
 	error = UFS_UPDATE(vp, NULL, NULL, 0);
 	if (lock)
 		UFS_WAPBL_END(vp->v_mount);
@@ -607,7 +605,6 @@ ufs_setacl_posix1e(struct vnode *vp, int
 		UFS_WAPBL_END(vp->v_mount);
 	}
 
-	VN_KNOTE(vp, NOTE_ATTRIB);
 	return (error);
 }
 

Index: src/sys/ufs/ufs/ufs_extern.h
diff -u src/sys/ufs/ufs/ufs_extern.h:1.87 src/sys/ufs/ufs/ufs_extern.h:1.88
--- src/sys/ufs/ufs/ufs_extern.h:1.87	Sun Jul 18 23:57:15 2021
+++ src/sys/ufs/ufs/ufs_extern.h	Wed Oct 20 03:08:19 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: ufs_extern.h,v 1.87 2021/07/18 23:57:15 dholland Exp $	*/
+/*	$NetBSD: ufs_extern.h,v 1.88 2021/10/20 03:08:19 thorpej Exp $	*/
 
 /*-
  * Copyright (c) 1991, 1993, 1994
@@ -133,9 +133,11 @@ int	ufs_gro_remove_check_permitted(struc
 	    struct vnode *, struct vnode *);
 int	ufs_gro_rename(struct mount *, kauth_cred_t,
 	    struct vnode *, struct componentname *, void *, struct vnode *,
-	    struct vnode *, struct componentname *, void *, struct vnode *);
+	    struct vnode *, struct componentname *, void *, struct vnode *,
+	    nlink_t *);
 int	ufs_gro_remove(struct mount *, kauth_cred_t,
-	    struct vnode *, struct componentname *, void *, struct vnode *);
+	    struct vnode *, struct componentname *, void *, struct vnode *,
+	    nlink_t *);
 int	ufs_gro_lookup(struct mount *, struct vnode *,
 	    struct componentname *, void *, struct vnode **);
 int	ufs_gro_genealogy(struct mount *, kauth_cred_t,

Index: src/sys/ufs/ufs/ufs_readwrite.c
diff -u src/sys/ufs/ufs/ufs_readwrite.c:1.126 src/sys/ufs/ufs/ufs_readwrite.c:1.127
--- src/sys/ufs/ufs/ufs_readwrite.c:1.126	Thu Apr 23 21:47:09 2020
+++ src/sys/ufs/ufs/ufs_readwrite.c	Wed Oct 20 03:08:19 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: ufs_readwrite.c,v 1.126 2020/04/23 21:47:09 ad Exp $	*/
+/*	$NetBSD: ufs_readwrite.c,v 1.127 2021/10/20 03:08:19 thorpej Exp $	*/
 
 /*-
  * Copyright (c) 1993
@@ -32,7 +32,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(1, "$NetBSD: ufs_readwrite.c,v 1.126 2020/04/23 21:47:09 ad Exp $");
+__KERNEL_RCSID(1, "$NetBSD: ufs_readwrite.c,v 1.127 2021/10/20 03:08:19 thorpej Exp $");
 
 #define	FS			struct fs
 #define	I_FS			i_fs
@@ -50,7 +50,7 @@ __KERNEL_RCSID(1, "$NetBSD: ufs_readwrit
 
 static int	ufs_post_read_update(struct vnode *, int, int);
 static int	ufs_post_write_update(struct vnode *, struct uio *, int,
-		    kauth_cred_t, off_t, int, int, int);
+		    kauth_cred_t, off_t, int, int);
 
 /*
  * Vnode op for reading.
@@ -245,7 +245,6 @@ WRITE(void *v)
 	off_t osize, origoff, oldoff, preallocoff, endallocoff, nsize;
 	int blkoffset, error, flags, ioflag, resid;
 	int aflag;
-	int extended=0;
 	vsize_t bytelen;
 	bool async;
 	struct ufsmount *ump;
@@ -419,7 +418,6 @@ WRITE(void *v)
 
 		if (vp->v_size < newoff) {
 			uvm_vnp_setsize(vp, newoff);
-			extended = 1;
 		}
 
 		if (error)
@@ -448,7 +446,7 @@ WRITE(void *v)
 
 out:
 	error = ufs_post_write_update(vp, uio, ioflag, cred, osize, resid,
-	    extended, error);
+	    error);
 	UFS_WAPBL_END(vp->v_mount);
 
 	return (error);
@@ -468,7 +466,6 @@ BUFWR(struct vnode *vp, struct uio *uio,
 	off_t osize;
 	int resid, xfersize, size, blkoffset;
 	daddr_t lbn;
-	int extended=0;
 	int error;
 
 	KASSERT(ISSET(ioflag, IO_NODELOCKED));
@@ -520,7 +517,6 @@ BUFWR(struct vnode *vp, struct uio *uio,
 			ip->i_size = uio->uio_offset + xfersize;
 			DIP_ASSIGN(ip, size, ip->i_size);
 			uvm_vnp_setsize(vp, ip->i_size);
-			extended = 1;
 		}
 		size = ufs_blksize(fs, ip, lbn) - bp->b_resid;
 		if (xfersize > size)
@@ -548,14 +544,14 @@ BUFWR(struct vnode *vp, struct uio *uio,
 	}
 
 	error = ufs_post_write_update(vp, uio, ioflag, cred, osize, resid,
-	    extended, error);
+	    error);
 
 	return (error);
 }
 
 static int
 ufs_post_write_update(struct vnode *vp, struct uio *uio, int ioflag,
-    kauth_cred_t cred, off_t osize, int resid, int extended, int oerror)
+    kauth_cred_t cred, off_t osize, int resid, int oerror)
 {
 	struct inode *ip = VTOI(vp);
 	int error = oerror;
@@ -588,10 +584,6 @@ ufs_post_write_update(struct vnode *vp, 
 		}
 	}
 
-	/* If we successfully wrote anything, notify kevent listeners.  */
-	if (resid > uio->uio_resid)
-		VN_KNOTE(vp, NOTE_WRITE | (extended ? NOTE_EXTEND : 0));
-
 	/*
 	 * Update the size on disk: truncate back to original size on
 	 * error, or reflect the new size on success.

Index: src/sys/ufs/ufs/ufs_rename.c
diff -u src/sys/ufs/ufs/ufs_rename.c:1.13 src/sys/ufs/ufs/ufs_rename.c:1.14
--- src/sys/ufs/ufs/ufs_rename.c:1.13	Fri Oct 28 20:38:12 2016
+++ src/sys/ufs/ufs/ufs_rename.c	Wed Oct 20 03:08:19 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: ufs_rename.c,v 1.13 2016/10/28 20:38:12 jdolecek Exp $	*/
+/*	$NetBSD: ufs_rename.c,v 1.14 2021/10/20 03:08:19 thorpej Exp $	*/
 
 /*-
  * Copyright (c) 2012 The NetBSD Foundation, Inc.
@@ -34,7 +34,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ufs_rename.c,v 1.13 2016/10/28 20:38:12 jdolecek Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ufs_rename.c,v 1.14 2021/10/20 03:08:19 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/buf.h>
@@ -282,7 +282,7 @@ ufs_gro_rename(struct mount *mp, kauth_c
     struct vnode *fdvp, struct componentname *fcnp,
     void *fde, struct vnode *fvp,
     struct vnode *tdvp, struct componentname *tcnp,
-    void *tde, struct vnode *tvp)
+    void *tde, struct vnode *tvp, nlink_t *tvp_nlinkp)
 {
 	struct ufs_lookup_results *fulr = fde;
 	struct ufs_lookup_results *tulr = tde;
@@ -528,13 +528,9 @@ ufs_gro_rename(struct mount *mp, kauth_c
 #endif
 		goto arghmybrainhurts;
 
-	/*
-	 * XXX Perhaps this should go at the top, in case the file
-	 * system is modified but incompletely so because of an
-	 * intermediate error.
-	 */
-	genfs_rename_knote(fdvp, fvp, tdvp, tvp,
-	    ((tvp != NULL) && (VTOI(tvp)->i_nlink == 0)));
+	if (tvp != NULL) {
+		*tvp_nlinkp = VTOI(tvp)->i_nlink;
+	}
 #if 0				/* XXX */
 	genfs_rename_cache_purge(fdvp, fvp, tdvp, tvp);
 #endif
@@ -762,7 +758,8 @@ ufs_direct_namlen(const struct direct *e
  */
 int
 ufs_gro_remove(struct mount *mp, kauth_cred_t cred,
-    struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp)
+    struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp,
+    nlink_t *tvp_nlinkp)
 {
 	struct ufs_lookup_results *ulr = de;
 	int error;
@@ -783,18 +780,14 @@ ufs_gro_remove(struct mount *mp, kauth_c
 
 	error = UFS_WAPBL_BEGIN(mp);
 	if (error)
-		goto out0;
+		goto out;
 
 	/* XXX ufs_dirremove decrements vp's link count for us.  */
 	error = ufs_dirremove(dvp, ulr, VTOI(vp), cnp->cn_flags, 0);
-	if (error)
-		goto out1;
-
-	VN_KNOTE(dvp, NOTE_WRITE);
-	VN_KNOTE(vp, (VTOI(vp)->i_nlink? NOTE_LINK : NOTE_DELETE));
+	UFS_WAPBL_END(mp);
 
-out1:	UFS_WAPBL_END(mp);
-out0:
+	*tvp_nlinkp = VTOI(vp)->i_nlink;
+out:
 	return error;
 }
 

Index: src/sys/ufs/ufs/ufs_vnops.c
diff -u src/sys/ufs/ufs/ufs_vnops.c:1.259 src/sys/ufs/ufs/ufs_vnops.c:1.260
--- src/sys/ufs/ufs/ufs_vnops.c:1.259	Sat Sep  5 16:30:13 2020
+++ src/sys/ufs/ufs/ufs_vnops.c	Wed Oct 20 03:08:19 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: ufs_vnops.c,v 1.259 2020/09/05 16:30:13 riastradh Exp $	*/
+/*	$NetBSD: ufs_vnops.c,v 1.260 2021/10/20 03:08:19 thorpej Exp $	*/
 
 /*-
  * Copyright (c) 2008, 2020 The NetBSD Foundation, Inc.
@@ -66,7 +66,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ufs_vnops.c,v 1.259 2020/09/05 16:30:13 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ufs_vnops.c,v 1.260 2021/10/20 03:08:19 thorpej Exp $");
 
 #if defined(_KERNEL_OPT)
 #include "opt_ffs.h"
@@ -165,7 +165,6 @@ ufs_create(void *v)
 		return (error);
 	}
 	UFS_WAPBL_END(dvp->v_mount);
-	VN_KNOTE(dvp, NOTE_WRITE);
 	VOP_UNLOCK(*ap->a_vpp);
 	return (0);
 }
@@ -202,7 +201,6 @@ ufs_mknod(void *v)
 	 */
 	if ((error = ufs_makeinode(vap, ap->a_dvp, ulr, vpp, ap->a_cnp)) != 0)
 		goto out;
-	VN_KNOTE(ap->a_dvp, NOTE_WRITE);
 	ip = VTOI(*vpp);
 	ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE;
 	UFS_WAPBL_UPDATE(*vpp, NULL, NULL, 0);
@@ -672,7 +670,6 @@ ufs_setattr(void *v)
 		error = ufs_chmod(vp, (int)vap->va_mode, cred, l);
 		UFS_WAPBL_END(vp->v_mount);
 	}
-	VN_KNOTE(vp, NOTE_ATTRIB);
 out:
 	cache_enter_id(vp, ip->i_mode, ip->i_uid, ip->i_gid, !HAS_ACLS(ip));
 	return (error);
@@ -822,10 +819,11 @@ ufs_chown(struct vnode *vp, uid_t uid, g
 int
 ufs_remove(void *v)
 {
-	struct vop_remove_v2_args /* {
+	struct vop_remove_v3_args /* {
 		struct vnode		*a_dvp;
 		struct vnode		*a_vp;
 		struct componentname	*a_cnp;
+		nlink_t 		 ctx_vp_new_nlink;
 	} */ *ap = v;
 	struct vnode	*vp, *dvp;
 	struct inode	*ip;
@@ -863,10 +861,11 @@ ufs_remove(void *v)
 			error = ufs_dirremove(dvp, ulr,
 					      ip, ap->a_cnp->cn_flags, 0);
 			UFS_WAPBL_END(mp);
+			if (error == 0) {
+				ap->ctx_vp_new_nlink = ip->i_nlink;
+			}
 		}
 	}
-	VN_KNOTE(vp, NOTE_DELETE);
-	VN_KNOTE(dvp, NOTE_WRITE);
 #ifdef notyet
 err:
 #endif
@@ -946,8 +945,6 @@ ufs_link(void *v)
  out1:
 	VOP_UNLOCK(vp);
  out2:
-	VN_KNOTE(vp, NOTE_LINK);
-	VN_KNOTE(dvp, NOTE_WRITE);
 	return (error);
 }
 
@@ -1351,7 +1348,6 @@ ufs_mkdir(void *v)
 	pool_cache_put(ufs_direct_cache, newdir);
  bad:
 	if (error == 0) {
-		VN_KNOTE(dvp, NOTE_WRITE | NOTE_LINK);
 		VOP_UNLOCK(tvp);
 		UFS_WAPBL_END(dvp->v_mount);
 	} else {
@@ -1447,7 +1443,6 @@ ufs_rmdir(void *v)
 		UFS_WAPBL_END(dvp->v_mount);
 		goto out;
 	}
-	VN_KNOTE(dvp, NOTE_WRITE | NOTE_LINK);
 	cache_purge(dvp);
 	/*
 	 * Truncate inode.  The only stuff left in the directory is "." and
@@ -1473,7 +1468,6 @@ ufs_rmdir(void *v)
 		ufsdirhash_free(ip);
 #endif
  out:
-	VN_KNOTE(vp, NOTE_DELETE);
 	vput(vp);
 	return error;
  err:
@@ -1516,7 +1510,6 @@ ufs_symlink(void *v)
 	error = ufs_makeinode(ap->a_vap, ap->a_dvp, ulr, vpp, ap->a_cnp);
 	if (error)
 		goto out;
-	VN_KNOTE(ap->a_dvp, NOTE_WRITE);
 	vp = *vpp;
 	len = strlen(ap->a_target);
 	ip = VTOI(vp);

Index: src/tests/kernel/kqueue/t_vnode.c
diff -u src/tests/kernel/kqueue/t_vnode.c:1.1 src/tests/kernel/kqueue/t_vnode.c:1.2
--- src/tests/kernel/kqueue/t_vnode.c:1.1	Wed Jan 14 22:22:14 2015
+++ src/tests/kernel/kqueue/t_vnode.c	Wed Oct 20 03:08:19 2021
@@ -509,25 +509,198 @@ ATF_TC_CLEANUP(dir_note_write_mv_file_wi
 	cleanup();
 }
 
+static const char testfile[] = "testfile";
+
+ATF_TC_WITH_CLEANUP(open_write_read_close);
+ATF_TC_HEAD(open_write_read_close, tc)
+{
+	atf_tc_set_md_var(tc, "descr", "This test case exercises "
+		"that kevent(2) returns NOTE_OPEN, NOTE_READ, NOTE_WRITE, "
+		"NOTE_CLOSE, and NOTE_CLOSE_WRITE.");
+}
+ATF_TC_BODY(open_write_read_close, tc)
+{
+	struct kevent event[1];
+	char buf[sizeof(testfile)];
+	int fd;
+
+	ATF_REQUIRE((kq = kqueue()) != -1);
+
+	/*
+	 * Create the test file and register an event on it.  We need
+	 * to keep the fd open to keep receiving events, so we'll just
+	 * leak it and re-use the fd variable.
+	 */
+	ATF_REQUIRE((fd = open(testfile,
+			       O_RDWR | O_CREAT | O_TRUNC, 0600)) != -1);
+	EV_SET(&event[0], fd, EVFILT_VNODE, EV_ADD | EV_CLEAR,
+	       NOTE_OPEN | NOTE_READ | NOTE_WRITE |
+	       NOTE_CLOSE | NOTE_CLOSE_WRITE, 0, NULL);
+	ATF_REQUIRE(kevent(kq, event, 1, NULL, 0, NULL) == 0);
+
+	/*
+	 * Open the file for writing, check for NOTE_OPEN.
+	 * Write to the file, check for NOTE_WRITE | NOTE_EXTEND.
+	 * Re-write the file, check for NOTE_WRITE and !NOTE_EXTEND.
+	 * Write one additional byte, check for NOTE_WRITE | NOTE_EXTEND.
+	 * Close the file, check for NOTE_CLOSE_WRITE.
+	 */
+	ATF_REQUIRE((fd = open(testfile, O_RDWR)) != -1);
+	ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1);
+	ATF_REQUIRE(event[0].fflags & NOTE_OPEN);
+
+	ATF_REQUIRE((pwrite(fd, testfile,
+			    sizeof(testfile), 0)) == sizeof(testfile));
+	ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1);
+	ATF_REQUIRE(event[0].fflags & NOTE_WRITE);
+	ATF_REQUIRE(event[0].fflags & NOTE_EXTEND);
+
+	ATF_REQUIRE((pwrite(fd, testfile,
+			    sizeof(testfile), 0)) == sizeof(testfile));
+	ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1);
+	ATF_REQUIRE(event[0].fflags & NOTE_WRITE);
+	ATF_REQUIRE((event[0].fflags & NOTE_EXTEND) == 0);
+
+	ATF_REQUIRE((pwrite(fd, testfile,
+			    1, sizeof(testfile))) == 1);
+	ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1);
+	ATF_REQUIRE(event[0].fflags & NOTE_WRITE);
+	ATF_REQUIRE(event[0].fflags & NOTE_EXTEND);
+
+	(void)close(fd);
+	ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1);
+	ATF_REQUIRE(event[0].fflags & NOTE_CLOSE_WRITE);
+	ATF_REQUIRE((event[0].fflags & NOTE_CLOSE) == 0);
+
+	/*
+	 * Open the file for reading, check for NOTE_OPEN.
+	 * Read from the file, check for NOTE_READ.
+	 * Close the file., check for NOTE_CLOSE.
+	 */
+	ATF_REQUIRE((fd = open(testfile, O_RDONLY)) != -1);
+	ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1);
+	ATF_REQUIRE(event[0].fflags & NOTE_OPEN);
+
+	ATF_REQUIRE((read(fd, buf, sizeof(buf))) == sizeof(buf));
+	ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1);
+	ATF_REQUIRE(event[0].fflags & NOTE_READ);
+
+	(void)close(fd);
+	ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1);
+	ATF_REQUIRE(event[0].fflags & NOTE_CLOSE);
+	ATF_REQUIRE((event[0].fflags & NOTE_CLOSE_WRITE) == 0);
+}
+ATF_TC_CLEANUP(open_write_read_close, tc)
+{
+	(void)unlink(testfile);
+}
+
+ATF_TC_WITH_CLEANUP(interest);
+ATF_TC_HEAD(interest, tc)
+{
+	atf_tc_set_md_var(tc, "descr", "This test case exercises "
+		"the kernel code that computes vnode kevent interest");
+}
+ATF_TC_BODY(interest, tc)
+{
+	struct kevent event[3];
+	int open_ev_fd, write_ev_fd, close_ev_fd;
+	int fd;
+
+	/*
+	 * This test cases exercises some implementation details
+	 * regarding how "kevent interest" is computed for a vnode.
+	 *
+	 * We are going to add events, one at a time, in a specific
+	 * order, and then remove one of them, with the knowledge that
+	 * a specific code path in vfs_vnops.c:vn_knote_detach() will
+	 * be taken.  There are several KASSERT()s in this code path
+	 * that will be validated.
+	 *
+	 * In order to ensure distinct knotes are attached to the vnodes,
+	 * we must use a different file descriptor to register interest
+	 * in each kind of event.
+	 */
+
+	ATF_REQUIRE((kq = kqueue()) != -1);
+
+	/*
+	 * Create the test file and register an event on it.  We need
+	 * to keep the fd open to keep receiving events, so we'll just
+	 * leak it and re-use the fd variable.
+	 */
+	ATF_REQUIRE((open_ev_fd = open(testfile,
+	    O_RDWR | O_CREAT | O_TRUNC, 0600)) != -1);
+	ATF_REQUIRE((write_ev_fd = dup(open_ev_fd)) != -1);
+	ATF_REQUIRE((close_ev_fd = dup(open_ev_fd)) != -1);
+
+	EV_SET(&event[0], open_ev_fd, EVFILT_VNODE, EV_ADD | EV_CLEAR,
+	       NOTE_OPEN, 0, NULL);
+	EV_SET(&event[1], write_ev_fd, EVFILT_VNODE, EV_ADD | EV_CLEAR,
+	       NOTE_WRITE, 0, NULL);
+	EV_SET(&event[2], close_ev_fd, EVFILT_VNODE, EV_ADD | EV_CLEAR,
+	       NOTE_CLOSE | NOTE_CLOSE_WRITE, 0, NULL);
+	ATF_REQUIRE(kevent(kq, event, 3, NULL, 0, NULL) == 0);
+
+	/*
+	 * The testfile vnode now has 3 knotes attached, in "LIFO"
+	 * order:
+	 *
+	 *	NOTE_CLOSE -> NOTE_WRITE -> NOTE_OPEN
+	 *
+	 * We will now remove the NOTE_WRITE knote.
+	 */
+	(void)close(write_ev_fd);
+
+	ATF_REQUIRE((fd = open(testfile, O_RDWR)) != -1);
+	ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1);
+	ATF_REQUIRE(event[0].fflags & NOTE_OPEN);
+
+	ATF_REQUIRE((pwrite(fd, testfile,
+			    sizeof(testfile), 0)) == sizeof(testfile));
+	ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 0);
+
+	(void)close(fd);
+	ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1);
+	ATF_REQUIRE(event[0].fflags & NOTE_CLOSE_WRITE);
+	ATF_REQUIRE((event[0].fflags & NOTE_CLOSE) == 0);
+
+}
+ATF_TC_CLEANUP(interest, tc)
+{
+	(void)unlink(testfile);
+}
+
+
 ATF_TP_ADD_TCS(tp)
 {
 	ATF_TP_ADD_TC(tp, dir_no_note_link_create_file_in);
 	ATF_TP_ADD_TC(tp, dir_no_note_link_delete_file_in);
+
 	ATF_TP_ADD_TC(tp, dir_no_note_link_mv_dir_within);
 	ATF_TP_ADD_TC(tp, dir_no_note_link_mv_file_within);
+
 	ATF_TP_ADD_TC(tp, dir_note_link_create_dir_in);
 	ATF_TP_ADD_TC(tp, dir_note_link_delete_dir_in);
+
 	ATF_TP_ADD_TC(tp, dir_note_link_mv_dir_in);
 	ATF_TP_ADD_TC(tp, dir_note_link_mv_dir_out);
+
 	ATF_TP_ADD_TC(tp, dir_note_write_create_dir_in);
 	ATF_TP_ADD_TC(tp, dir_note_write_create_file_in);
+
 	ATF_TP_ADD_TC(tp, dir_note_write_delete_dir_in);
 	ATF_TP_ADD_TC(tp, dir_note_write_delete_file_in);
+
 	ATF_TP_ADD_TC(tp, dir_note_write_mv_dir_in);
 	ATF_TP_ADD_TC(tp, dir_note_write_mv_dir_out);
 	ATF_TP_ADD_TC(tp, dir_note_write_mv_dir_within);
 	ATF_TP_ADD_TC(tp, dir_note_write_mv_file_in);
 	ATF_TP_ADD_TC(tp, dir_note_write_mv_file_out);
 	ATF_TP_ADD_TC(tp, dir_note_write_mv_file_within);
+
+	ATF_TP_ADD_TC(tp, open_write_read_close);
+	ATF_TP_ADD_TC(tp, interest);
+
 	return atf_no_error();
 }

Reply via email to