[LTP] [PATCH] inotify13: check rename cookie pairing after adjacent moves

Hungyu Lin dennylin0707@gmail.com
Tue Oct 27 20:19:55 CET 2026


Queue an unpaired IN_MOVED_FROM event, followed by an IN_MOVED_FROM and
IN_MOVED_TO pair with the same source watch descriptor and name as the
first event. Watch only IN_MOVE so creation of the replacement entry
does not separate the two IN_MOVED_FROM events in the queue.

Affected kernels coalesce the second IN_MOVED_FROM into the first,
leaving the IN_MOVED_TO cookie without a matching IN_MOVED_FROM cookie.
Check that all three events are retained, the two moves have distinct
cookies, and the second IN_MOVED_FROM matches the IN_MOVED_TO cookie.
Cover both regular files and directories.

Suggested-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Hungyu Lin <dennylin0707@gmail.com>
---
 runtest/syscalls                              |   1 +
 testcases/kernel/syscalls/inotify/.gitignore  |   1 +
 testcases/kernel/syscalls/inotify/inotify13.c | 147 ++++++++++++++++++
 3 files changed, 149 insertions(+)
 create mode 100644 testcases/kernel/syscalls/inotify/inotify13.c

diff --git a/runtest/syscalls b/runtest/syscalls
index 2bdb4a9..e578bd9 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -677,6 +677,7 @@ inotify09 inotify09
 inotify10 inotify10
 inotify11 inotify11
 inotify12 inotify12
+inotify13 inotify13
 
 fanotify01 fanotify01
 fanotify02 fanotify02
diff --git a/testcases/kernel/syscalls/inotify/.gitignore b/testcases/kernel/syscalls/inotify/.gitignore
index f6e5c54..b597ea6 100644
--- a/testcases/kernel/syscalls/inotify/.gitignore
+++ b/testcases/kernel/syscalls/inotify/.gitignore
@@ -10,3 +10,4 @@
 /inotify10
 /inotify11
 /inotify12
+/inotify13
diff --git a/testcases/kernel/syscalls/inotify/inotify13.c b/testcases/kernel/syscalls/inotify/inotify13.c
new file mode 100644
index 0000000..4b172ba
--- /dev/null
+++ b/testcases/kernel/syscalls/inotify/inotify13.c
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Hungyu Lin <dennylin0707@gmail.com>
+ */
+
+/*\
+ * Verify that coalescing does not lose an IN_MOVED_FROM event needed to
+ * match a later IN_MOVED_TO event by its rename cookie.
+ *
+ * Move an entry from a watched directory to an unwatched directory, recreate
+ * it under the same name, then move it to another watched directory. Watch
+ * only move events so the two IN_MOVED_FROM events are adjacent in the queue.
+ * Their cookies must differ, and the second must match the IN_MOVED_TO cookie.
+ * Test both regular files and directories without reading between renames.
+ */
+
+#include "config.h"
+#include <fcntl.h>
+#include <string.h>
+#include "tst_test.h"
+#include "inotify.h"
+
+#ifdef HAVE_SYS_INOTIFY_H
+#include <sys/inotify.h>
+
+#define SOURCE "source/entry"
+#define SAVED "unwatched/entry"
+#define TARGET "target/entry"
+
+static int fd_notify = -1;
+
+static void create_entry(int directory)
+{
+	if (directory)
+		SAFE_MKDIR(SOURCE, 0700);
+	else
+		SAFE_TOUCH(SOURCE, 0600, NULL);
+}
+
+static void verify_inotify(unsigned int directory)
+{
+	union {
+		struct inotify_event align;
+		char data[4096];
+	} buf;
+	struct inotify_event *event, *events[3];
+	unsigned int count = 0;
+	size_t pos = 0, len;
+	unsigned int from = IN_MOVED_FROM, to = IN_MOVED_TO;
+	int wd_source, wd_target;
+	const char *type = directory ? "directory" : "file";
+
+	if (directory) {
+		from |= IN_ISDIR;
+		to |= IN_ISDIR;
+	}
+
+	fd_notify = SAFE_MYINOTIFY_INIT1(O_NONBLOCK);
+	wd_source = SAFE_MYINOTIFY_ADD_WATCH(fd_notify, "source", IN_MOVE);
+	wd_target = SAFE_MYINOTIFY_ADD_WATCH(fd_notify, "target", IN_MOVE);
+
+	create_entry(directory);
+	SAFE_RENAME(SOURCE, SAVED);
+	create_entry(directory);
+	SAFE_RENAME(SOURCE, TARGET);
+
+	len = SAFE_READ(SAFE_READ_ANY, fd_notify, buf.data, sizeof(buf.data));
+	SAFE_CLOSE(fd_notify);
+	fd_notify = -1;
+
+	if (directory) {
+		SAFE_RMDIR(SAVED);
+		SAFE_RMDIR(TARGET);
+	} else {
+		SAFE_UNLINK(SAVED);
+		SAFE_UNLINK(TARGET);
+	}
+
+	while (pos < len) {
+		if (len - pos < sizeof(*event))
+			tst_brk(TBROK, "Incomplete inotify event");
+
+		event = (struct inotify_event *)(buf.data + pos);
+		if (event->len > len - pos - sizeof(*event))
+			tst_brk(TBROK, "Incomplete inotify event name");
+
+		tst_res(TINFO, "%s: wd=%d mask=%#x cookie=%u name=%.*s",
+			type, event->wd, event->mask, event->cookie,
+			(int)event->len, event->name);
+		if (event->len < sizeof("entry") ||
+		    memcmp(event->name, "entry", sizeof("entry"))) {
+			tst_res(TFAIL, "%s: unexpected rename event name", type);
+			return;
+		}
+		if (count < ARRAY_SIZE(events))
+			events[count] = event;
+		count++;
+		pos += sizeof(*event) + event->len;
+	}
+
+	if (count != ARRAY_SIZE(events)) {
+		tst_res(TFAIL, "%s: expected two FROM events and one TO, got %u",
+			type, count);
+		return;
+	}
+
+	if (events[0]->wd != wd_source || events[0]->mask != from ||
+	    events[1]->wd != wd_source || events[1]->mask != from ||
+	    events[2]->wd != wd_target || events[2]->mask != to) {
+		tst_res(TFAIL, "%s: unexpected rename event sequence", type);
+		return;
+	}
+
+	if (events[0]->cookie == events[1]->cookie ||
+	    events[1]->cookie != events[2]->cookie) {
+		tst_res(TFAIL, "%s: rename cookies do not identify the later pair",
+			type);
+		return;
+	}
+
+	tst_res(TPASS, "%s: second FROM cookie matches the TO cookie", type);
+}
+
+static void setup(void)
+{
+	SAFE_MKDIR("source", 0700);
+	SAFE_MKDIR("unwatched", 0700);
+	SAFE_MKDIR("target", 0700);
+}
+
+static void cleanup(void)
+{
+	if (fd_notify >= 0)
+		SAFE_CLOSE(fd_notify);
+}
+
+static struct tst_test test = {
+	.test = verify_inotify,
+	.tcnt = 2,
+	.setup = setup,
+	.cleanup = cleanup,
+	.needs_tmpdir = 1,
+};
+
+#else
+TST_TEST_TCONF("system doesn't have required inotify support");
+#endif
-- 
2.43.0



More information about the ltp mailing list