Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 26 Apr 2026 01:56:23 +0000
From:      Mark Johnston <markj@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: a02d794f5acd - main - nullfs: Clear inotify flags during reclaim
Message-ID:  <69ed70c7.27bc2.18c1de59@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=a02d794f5acd12ba3cf1de5c204a8dd56af47edd

commit a02d794f5acd12ba3cf1de5c204a8dd56af47edd
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2026-04-26 01:35:37 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-04-26 01:56:14 +0000

    nullfs: Clear inotify flags during reclaim
    
    The inotify flags are copied from the lower vnode into the nullfs vnode
    so that the INOTIFY() macro will invoke VOP_INOTIFY on the nullfs vnode;
    this is then bypassed to the lower vnode.  However, when a nullfs vnode
    is reclaimed we should clear these flags, as the vnode is now doomed and
    no longer forwards VOPs to the lower vnode.
    
    Add regression tests.  Remove a test in vn_inotify_revoke() which is no
    longer needed after this change.
    
    PR:             292495
    Reviewed by:    kib
    Reported by:    Jed Laundry <jlaundry@jlaundry.com>
    Fixes:          f1f230439fa4 ("vfs: Initial revision of inotify")
    MFC after:      1 week
    Differential Revision:  https://reviews.freebsd.org/D56639
---
 sys/fs/nullfs/null_vnops.c    |  12 +++++
 sys/kern/vfs_inotify.c        |   4 --
 tests/sys/kern/inotify_test.c | 112 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 124 insertions(+), 4 deletions(-)

diff --git a/sys/fs/nullfs/null_vnops.c b/sys/fs/nullfs/null_vnops.c
index a372a46bc6c6..50d359c9b909 100644
--- a/sys/fs/nullfs/null_vnops.c
+++ b/sys/fs/nullfs/null_vnops.c
@@ -968,6 +968,7 @@ null_reclaim(struct vop_reclaim_args *ap)
 	struct vnode *vp;
 	struct null_node *xp;
 	struct vnode *lowervp;
+	short flags;
 
 	vp = ap->a_vp;
 	xp = VTONULL(vp);
@@ -997,6 +998,17 @@ null_reclaim(struct vop_reclaim_args *ap)
 	else if (vp->v_writecount < 0)
 		vp->v_writecount = 0;
 
+	/*
+	 * Undo the effects of null_copy_inotify(): setting VIRF_INOTIFY* causes
+	 * the VFS to invoke VOP_INOTIFY on the marked vnode, and for nullfs
+	 * vnodes this is bypassed to the lower vnode.  The inotify watch holds
+	 * a ref on the lower vnode, but not the upper vnode, so VOP_INOTIFY
+	 * must not be called on the upper vnode after this point.
+	 */
+	flags = vn_irflag_read(vp) & (VIRF_INOTIFY | VIRF_INOTIFY_PARENT);
+	if (flags != 0)
+		vn_irflag_unset_locked(vp, flags);
+
 	VI_UNLOCK(vp);
 
 	if ((xp->null_flags & NULLV_NOUNLOCK) != 0)
diff --git a/sys/kern/vfs_inotify.c b/sys/kern/vfs_inotify.c
index 716fdc96e5fb..94e65973a36b 100644
--- a/sys/kern/vfs_inotify.c
+++ b/sys/kern/vfs_inotify.c
@@ -889,10 +889,6 @@ vn_inotify_add_watch(struct vnode *vp, struct inotify_softc *sc, uint32_t mask,
 void
 vn_inotify_revoke(struct vnode *vp)
 {
-	if (vp->v_pollinfo == NULL) {
-		/* This is a nullfs vnode which shadows a watched vnode. */
-		return;
-	}
 	inotify_log(vp, NULL, 0, IN_UNMOUNT, 0);
 }
 
diff --git a/tests/sys/kern/inotify_test.c b/tests/sys/kern/inotify_test.c
index 0a4df4e5fcaa..d3799b12ce20 100644
--- a/tests/sys/kern/inotify_test.c
+++ b/tests/sys/kern/inotify_test.c
@@ -392,6 +392,116 @@ ATF_TC_CLEANUP(inotify_nullfs, tc)
 	}
 }
 
+/*
+ * Watch a file in a nullfs mount, and remove it from the lower mount.  Make
+ * sure that we get an IN_DELETE_SELF event and that the watch is removed.
+ */
+ATF_TC_WITH_CLEANUP(inotify_nullfs_remove);
+ATF_TC_HEAD(inotify_nullfs_remove, tc)
+{
+	atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(inotify_nullfs_remove, tc)
+{
+	char dir[PATH_MAX], path[PATH_MAX], *p;
+	int error, fd, ifd, wd;
+
+	strlcpy(dir, "./test.XXXXXX", sizeof(dir));
+	p = mkdtemp(dir);
+	ATF_REQUIRE(p == dir);
+
+	error = mkdir("./mnt", 0755);
+	ATF_REQUIRE(error == 0);
+
+	/* Mount the testdir onto ./mnt. */
+	mount_nullfs("./mnt", dir);
+
+	snprintf(path, sizeof(path), "%s/file", dir);
+	fd = open(path, O_RDWR | O_CREAT, 0644);
+	ATF_REQUIRE(fd != -1);
+	close_checked(fd);
+
+	ifd = inotify(IN_NONBLOCK);
+	wd = inotify_add_watch(ifd, "./mnt/file", IN_DELETE_SELF);
+	ATF_REQUIRE(wd != -1);
+
+	error = unlink(path);
+	ATF_REQUIRE(error == 0);
+
+	consume_event(ifd, wd, IN_DELETE_SELF, 0, NULL);
+	consume_event(ifd, wd, 0, IN_IGNORED, NULL);
+
+	close_inotify(ifd);
+}
+ATF_TC_CLEANUP(inotify_nullfs_remove, tc)
+{
+	int error;
+
+	error = unmount("./mnt", 0);
+	if (error != 0) {
+		perror("unmount");
+		exit(1);
+	}
+}
+
+/*
+ * Exercise a scenario where a watched lower vnode is deleted by a rename.  The
+ * deletion causes the upper vnode to be reclaimed, and after that point it
+ * should stop trying to forward events back to the (now detached) lower vnode.
+ */
+ATF_TC_WITH_CLEANUP(inotify_nullfs_rename);
+ATF_TC_HEAD(inotify_nullfs_rename, tc)
+{
+	atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(inotify_nullfs_rename, tc)
+{
+	char dir[PATH_MAX], path1[PATH_MAX], path2[PATH_MAX], *p;
+	int error, fd, ifd, wd;
+
+	strlcpy(dir, "./test.XXXXXX", sizeof(dir));
+	p = mkdtemp(dir);
+	ATF_REQUIRE(p == dir);
+
+	error = mkdir("./mnt", 0755);
+	ATF_REQUIRE(error == 0);
+
+	/* Mount the testdir onto ./mnt. */
+	mount_nullfs("./mnt", dir);
+
+	ifd = inotify(IN_NONBLOCK);
+
+	/* Create two files, they will be renamed in the upper layer. */
+	snprintf(path1, sizeof(path1), "%s/file1", dir);
+	fd = open(path1, O_RDWR | O_CREAT, 0644);
+	ATF_REQUIRE(fd != -1);
+	close_checked(fd);
+	snprintf(path2, sizeof(path2), "%s/file2", dir);
+	fd = open(path2, O_RDWR | O_CREAT, 0644);
+	ATF_REQUIRE(fd != -1);
+	close_checked(fd);
+
+	wd = inotify_add_watch(ifd, "./mnt/file1", IN_DELETE_SELF);
+	ATF_REQUIRE(wd != -1);
+	error = rename("./mnt/file2", "./mnt/file1");
+	ATF_REQUIRE(error == 0);
+
+	consume_event(ifd, wd, IN_DELETE_SELF, 0, NULL);
+	consume_event(ifd, wd, 0, IN_IGNORED, NULL);
+
+	close_inotify(ifd);
+}
+ATF_TC_CLEANUP(inotify_nullfs_rename, tc)
+{
+	int error;
+
+	error = unmount("./mnt", 0);
+	if (error != 0) {
+		perror("unmount");
+		exit(1);
+	}
+}
+
 /*
  * Make sure that exceeding max_events pending events results in an overflow
  * event.
@@ -878,6 +988,8 @@ ATF_TP_ADD_TCS(tp)
 	ATF_TP_ADD_TC(tp, inotify_coalesce);
 	ATF_TP_ADD_TC(tp, inotify_mask_create);
 	ATF_TP_ADD_TC(tp, inotify_nullfs);
+	ATF_TP_ADD_TC(tp, inotify_nullfs_remove);
+	ATF_TP_ADD_TC(tp, inotify_nullfs_rename);
 	ATF_TP_ADD_TC(tp, inotify_queue_overflow);
 	/* Tests for the various inotify event types. */
 	ATF_TP_ADD_TC(tp, inotify_event_access_file);


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?69ed70c7.27bc2.18c1de59>