git: b70997c8c75a - main - inotify: Unconditionally generate IN_IGNORED events for files/dirs

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Mon, 06 Jul 2026 12:55:19 UTC
The branch main has been updated by markj:

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

commit b70997c8c75adc3ab343c47d5ba7d01c9c774d9e
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2026-07-06 12:50:37 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-07-06 12:50:37 +0000

    inotify: Unconditionally generate IN_IGNORED events for files/dirs
    
    The implementation previously only generated an IN_IGNORED event for a
    deleted watched file if the watch explicitly requested IN_DELETE_SELF.
    This is not correct, IN_IGNORED should always be raised when the watched
    subject is deleted.  Adjust the implementation of inotify_log_one()
    accordingly.
    
    This also fixes a problem where a deleted watched file's watch
    would not be removed if IN_DELETE_SELF was not in the watch's event
    mask, in which case the unlinked vnode would linger until the inotify
    descriptor itself is closed.
    
    Add a regression test.
    
    Reported by:    jrtc27
    Reviewed by:    jrtc27
    MFC after:      1 week
    Differential Revision:  https://reviews.freebsd.org/D58050
---
 sys/kern/vfs_inotify.c        | 35 +++++++++++++++++++++++------------
 tests/sys/kern/inotify_test.c | 24 ++++++++++++++++++++++++
 2 files changed, 47 insertions(+), 12 deletions(-)

diff --git a/sys/kern/vfs_inotify.c b/sys/kern/vfs_inotify.c
index 94e65973a36b..9269dc0723af 100644
--- a/sys/kern/vfs_inotify.c
+++ b/sys/kern/vfs_inotify.c
@@ -614,25 +614,37 @@ inotify_log_one(struct inotify_watch *watch, const char *name, size_t namelen,
 	struct inotify_watch key;
 	struct inotify_softc *sc;
 	struct inotify_record *rec;
-	bool allocfail;
+	bool allocfail, delete, notify, oneshot, unmount;
 
 	mtx_assert(&watch->vp->v_pollinfo->vpi_lock, MA_OWNED);
 
 	sc = watch->sc;
-	rec = inotify_alloc_record(watch->wd, name, namelen, event, cookie,
-	    M_NOWAIT);
-	if (rec == NULL) {
-		rec = &sc->overflow;
-		allocfail = true;
+
+	delete = (event & IN_DELETE_SELF) != 0;
+	notify = (watch->mask & event) != 0;
+	oneshot = (watch->mask & IN_ONESHOT) != 0;
+	unmount = (event & IN_UNMOUNT) != 0;
+	if (!notify && !delete && !unmount)
+		return;
+
+	if (notify || unmount) {
+		rec = inotify_alloc_record(watch->wd, name, namelen, event,
+		    cookie, M_NOWAIT);
+		if (rec == NULL) {
+			rec = &sc->overflow;
+			allocfail = true;
+		} else {
+			allocfail = false;
+		}
 	} else {
-		allocfail = false;
+		rec = NULL;
 	}
 
 	mtx_lock(&sc->lock);
-	if (!inotify_queue_record(sc, rec) && rec != &sc->overflow)
+	if (rec != NULL && !inotify_queue_record(sc, rec) &&
+	    rec != &sc->overflow)
 		free(rec, M_INOTIFY);
-	if ((watch->mask & IN_ONESHOT) != 0 ||
-	    (event & (IN_DELETE_SELF | IN_UNMOUNT)) != 0) {
+	if (oneshot || delete || unmount) {
 		if (!allocfail) {
 			rec = inotify_alloc_record(watch->wd, NULL, 0,
 			    IN_IGNORED, 0, M_NOWAIT);
@@ -676,8 +688,7 @@ inotify_log(struct vnode *vp, const char *name, size_t namelen, int event,
 	TAILQ_FOREACH_SAFE(watch, &vp->v_pollinfo->vpi_inotify, vlink, tmp) {
 		KASSERT(watch->vp == vp,
 		    ("inotify_log: watch %p vp != vp", watch));
-		if ((watch->mask & event) != 0 || event == IN_UNMOUNT)
-			inotify_log_one(watch, name, namelen, event, cookie);
+		inotify_log_one(watch, name, namelen, event, cookie);
 	}
 	mtx_unlock(&vp->v_pollinfo->vpi_lock);
 }
diff --git a/tests/sys/kern/inotify_test.c b/tests/sys/kern/inotify_test.c
index d3799b12ce20..9d5265e71f95 100644
--- a/tests/sys/kern/inotify_test.c
+++ b/tests/sys/kern/inotify_test.c
@@ -54,6 +54,8 @@ ev2name(int event)
 		return ("IN_MOVED_TO");
 	case IN_OPEN:
 		return ("IN_OPEN");
+	case IN_IGNORED:
+		return ("IN_IGNORED");
 	default:
 		return (NULL);
 	}
@@ -832,6 +834,27 @@ ATF_TC_BODY(inotify_event_delete, tc)
 	close_inotify(ifd);
 }
 
+ATF_TC_WITHOUT_HEAD(inotify_event_delete_ignored);
+ATF_TC_BODY(inotify_event_delete_ignored, tc)
+{
+	char path[PATH_MAX];
+	int error, ifd, wd;
+
+	ifd = inotify(IN_NONBLOCK);
+
+	wd = watch_dir(ifd, IN_DELETE, path);
+
+	/*
+	 * IN_IGNORED should be generated when a watched directory is removed,
+	 * even if the watch was not configured to raise IN_DELETE_SELF.
+	 */
+	error = rmdir(path);
+	ATF_REQUIRE(error == 0);
+	consume_event(ifd, wd, 0, IN_IGNORED, NULL);
+
+	close_inotify(ifd);
+}
+
 ATF_TC_WITHOUT_HEAD(inotify_event_move);
 ATF_TC_BODY(inotify_event_move, tc)
 {
@@ -999,6 +1022,7 @@ ATF_TP_ADD_TCS(tp)
 	ATF_TP_ADD_TC(tp, inotify_event_close_write);
 	ATF_TP_ADD_TC(tp, inotify_event_create);
 	ATF_TP_ADD_TC(tp, inotify_event_delete);
+	ATF_TP_ADD_TC(tp, inotify_event_delete_ignored);
 	ATF_TP_ADD_TC(tp, inotify_event_move);
 	ATF_TP_ADD_TC(tp, inotify_event_move_dir);
 	ATF_TP_ADD_TC(tp, inotify_event_open);