[LTP] [PATCH v2] inotify13: check rename cookie pairing after adjacent moves
Hungyu Lin
dennylin0707@gmail.com
Wed Oct 28 06:15:18 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 | 176 ++++++++++++++++++
3 files changed, 178 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..5b28e8d
--- /dev/null
+++ b/testcases/kernel/syscalls/inotify/inotify13.c
@@ -0,0 +1,176 @@
+// 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"
+
+struct event_t {
+ const char *name;
+ unsigned int mask;
+ int wd;
+};
+
+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)
+{
+ const char *type = directory ? "directory" : "file";
+ unsigned int isdir = directory ? IN_ISDIR : 0;
+ struct event_t event_set[] = {
+ { "entry", IN_MOVED_FROM | isdir, 0 },
+ { "entry", IN_MOVED_FROM | isdir, 0 },
+ { "entry", IN_MOVED_TO | isdir, 0 },
+ };
+ union {
+ struct inotify_event align;
+ char data[4096];
+ } buf;
+ struct inotify_event *event;
+ const struct event_t *expected;
+ unsigned int cookies[ARRAY_SIZE(event_set)] = { 0 };
+ unsigned int count = 0, i;
+ size_t pos = 0, len;
+ int wd_source, wd_target, failed = 0;
+
+ 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);
+
+ event_set[0].wd = wd_source;
+ event_set[1].wd = wd_source;
+ event_set[2].wd = wd_target;
+
+ 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;
+
+ /* Reuse these paths for the next case and for -i iterations. */
+ 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 at offset %zu", pos);
+
+ event = (struct inotify_event *)(buf.data + pos);
+ if (event->len > len - pos - sizeof(*event))
+ tst_brk(TBROK, "Incomplete inotify event name at offset %zu", pos);
+
+ if (count >= ARRAY_SIZE(event_set)) {
+ tst_res(TINFO, "%s: unexpected event %u: wd=%d mask=%#x cookie=%u len=%u name=\"%.*s\"",
+ type, count, event->wd, event->mask, event->cookie,
+ event->len, (int)event->len, event->name);
+ failed = 1;
+ } else {
+ expected = &event_set[count];
+ cookies[count] = event->cookie;
+ tst_res(TINFO, "%s: event %u: wd=%d (expected %d) mask=%#x (expected %#x) cookie=%u len=%u name=\"%.*s\" (expected \"%s\")",
+ type, count, event->wd, expected->wd,
+ event->mask, expected->mask, event->cookie,
+ event->len, (int)event->len, event->name,
+ expected->name);
+ if (event->wd != expected->wd ||
+ event->mask != expected->mask ||
+ event->len < strlen(expected->name) + 1 ||
+ memcmp(event->name, expected->name,
+ strlen(expected->name) + 1))
+ failed = 1;
+ }
+ count++;
+ pos += sizeof(*event) + event->len;
+ }
+
+ for (i = count; i < ARRAY_SIZE(event_set); i++) {
+ expected = &event_set[i];
+ tst_res(TINFO, "%s: missing event %u: expected wd=%d mask=%#x name=\"%s\"", type, i, expected->wd,
+ expected->mask, expected->name);
+ failed = 1;
+ }
+
+ if (failed) {
+ tst_res(TFAIL, "%s: unexpected rename event sequence (%u events, expected %zu)",
+ type, count, ARRAY_SIZE(event_set));
+ return;
+ }
+
+ if (cookies[0] == cookies[1]) {
+ tst_res(TFAIL, "%s: both FROM cookies are %u (expected different cookies)",
+ type, cookies[0]);
+ return;
+ }
+
+ if (cookies[1] != cookies[2]) {
+ tst_res(TFAIL, "%s: second FROM cookie=%u, TO cookie=%u (expected equal cookies)",
+ type, cookies[1], cookies[2]);
+ return;
+ }
+
+ tst_res(TPASS, "%s: second FROM cookie=%u matches TO cookie=%u",
+ type, cookies[1], cookies[2]);
+}
+
+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