* [PATCH] inotify13: check rename cookie pairing after adjacent moves
@ 2026-10-27 19:19 ` Hungyu Lin
0 siblings, 0 replies; 8+ messages in thread
From: Hungyu Lin @ 2026-10-27 19:19 UTC (permalink / raw)
To: ltp; +Cc: amir73il, jack, linux-fsdevel, dennylin0707
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
^ permalink raw reply related [flat|nested] 8+ messages in thread* [LTP] [PATCH] inotify13: check rename cookie pairing after adjacent moves
@ 2026-10-27 19:19 ` Hungyu Lin
0 siblings, 0 replies; 8+ messages in thread
From: Hungyu Lin @ 2026-10-27 19:19 UTC (permalink / raw)
To: ltp; +Cc: linux-fsdevel, dennylin0707, amir73il, jack
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
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH] inotify13: check rename cookie pairing after adjacent moves
2026-10-27 19:19 ` [LTP] " Hungyu Lin
@ 2026-10-28 3:19 ` Amir Goldstein
-1 siblings, 0 replies; 8+ messages in thread
From: Amir Goldstein @ 2026-10-28 3:19 UTC (permalink / raw)
To: Hungyu Lin; +Cc: ltp, jack, linux-fsdevel
On Tue, Oct 27, 2026 at 9:20 PM Hungyu Lin <dennylin0707@gmail.com> wrote:
>
> 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>
> ---
Thanks for following up on this
> 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);
> + }
not strictly needed for tmpdir setup
> +
> + 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;
> + }
Most tests use the pattern of preparing an expected event_set[] array
sometimes interleaved with generating the events.
I find this existing pattern more readable, but if there is a reason not to
follow existing practice, I can live with the above pattern.
Existing tests are also more verbose in the specific unexpected detail.
Thanks,
Amir.
> +
> + 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
>
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [LTP] [PATCH] inotify13: check rename cookie pairing after adjacent moves
@ 2026-10-28 3:19 ` Amir Goldstein
0 siblings, 0 replies; 8+ messages in thread
From: Amir Goldstein @ 2026-10-28 3:19 UTC (permalink / raw)
To: Hungyu Lin; +Cc: linux-fsdevel, jack, ltp
On Tue, Oct 27, 2026 at 9:20 PM Hungyu Lin <dennylin0707@gmail.com> wrote:
>
> 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>
> ---
Thanks for following up on this
> 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);
> + }
not strictly needed for tmpdir setup
> +
> + 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;
> + }
Most tests use the pattern of preparing an expected event_set[] array
sometimes interleaved with generating the events.
I find this existing pattern more readable, but if there is a reason not to
follow existing practice, I can live with the above pattern.
Existing tests are also more verbose in the specific unexpected detail.
Thanks,
Amir.
> +
> + 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
>
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [LTP] inotify13: check rename cookie pairing after adjacent moves
2026-10-27 19:19 ` [LTP] " Hungyu Lin
(?)
(?)
@ 2026-10-29 19:57 ` linuxtestproject.agent
-1 siblings, 0 replies; 8+ messages in thread
From: linuxtestproject.agent @ 2026-10-29 19:57 UTC (permalink / raw)
To: Hungyu Lin; +Cc: ltp
Hi Hungyu,
On Tue Oct 27 19:19:55 2026 +0000, Hungyu Lin <dennylin0707@gmail.com> wrote:
> inotify13: check rename cookie pairing after adjacent moves
> + len = SAFE_READ(SAFE_READ_ANY, fd_notify, buf.data, sizeof(buf.data));
> + SAFE_CLOSE(fd_notify);
> + fd_notify = -1;
SAFE_CLOSE() already sets fd_notify to -1, so the manual reset should be
removed.
> +static void cleanup(void)
> +{
> + if (fd_notify >= 0)
> + SAFE_CLOSE(fd_notify);
> +}
Use if (fd_notify != -1) to check file descriptor validity in cleanup().
Verdict - Needs revision
---
Note:
The agent can sometimes produce false positives although often its
findings are genuine. If you find issues with the review, please
comment this email or ignore the suggestions.
Regards,
LTP AI Reviewer
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2] inotify13: check rename cookie pairing after adjacent moves
@ 2026-10-28 5:15 Hungyu Lin
2026-10-29 19:56 ` [LTP] " linuxtestproject.agent
0 siblings, 1 reply; 8+ messages in thread
From: Hungyu Lin @ 2026-10-28 5:15 UTC (permalink / raw)
To: ltp; +Cc: amir73il, jack, linux-fsdevel, dennylin0707
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
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [LTP] inotify13: check rename cookie pairing after adjacent moves
2026-10-28 5:15 [PATCH v2] " Hungyu Lin
@ 2026-10-29 19:56 ` linuxtestproject.agent
0 siblings, 0 replies; 8+ messages in thread
From: linuxtestproject.agent @ 2026-10-29 19:56 UTC (permalink / raw)
To: Hungyu Lin; +Cc: ltp
Hi Hungyu,
On Wed Oct 28 05:15:18 2026 +0000, Hungyu Lin <dennylin0707@gmail.com> wrote:
> inotify13: check rename cookie pairing after adjacent moves
> + len = SAFE_READ(SAFE_READ_ANY, fd_notify, buf.data, sizeof(buf.data));
> + SAFE_CLOSE(fd_notify);
> + fd_notify = -1;
SAFE_CLOSE() already sets fd_notify to -1, so the manual reset to -1 should
be removed.
> +static void cleanup(void)
> +{
> + if (fd_notify >= 0)
> + SAFE_CLOSE(fd_notify);
> +}
Use fd_notify != -1 instead of fd_notify >= 0 to check whether the file
descriptor is valid.
Verdict - Needs revision
---
Note:
The agent can sometimes produce false positives although often its
findings are genuine. If you find issues with the review, please
comment this email or ignore the suggestions.
Regards,
LTP AI Reviewer
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v3] inotify13: check rename cookie pairing after adjacent moves
@ 2026-10-29 17:49 Hungyu Lin
2026-10-29 19:58 ` [LTP] " linuxtestproject.agent
0 siblings, 1 reply; 8+ messages in thread
From: Hungyu Lin @ 2026-10-29 17:49 UTC (permalink / raw)
To: ltp; +Cc: amir73il, jack, linux-fsdevel, dennylin0707
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>
---
Changes in v3:
- Check event fields separately, starting with the mask, and report specific
TFAIL diagnostics with actual and expected values. Log correct events
with TINFO only.
- Report missing and extra events directly instead of using a generic
failure flag.
- Use delete_entry() for both cleanup paths, as suggested by Amir.
- Use a file/directory test-case table and derive the test count from it.
- Clarify the operation sequence and affected-kernel history, the truncated
payload diagnostic, and both cookie relations in the success message.
- Handle an empty nonblocking queue as missing events rather than TBROK.
Built with GCC 13.3.0 and checked with LTP checkpatch and sparse. On ARM64
QEMU, the test reports 0 pass / 2 fail on the baseline kernel and 2 pass /
0 fail with either kernel fix. With -i 20, the results are 0 pass / 40
fail before the fix and 40 pass / 0 fail afterwards.
runtest/syscalls | 1 +
testcases/kernel/syscalls/inotify/.gitignore | 1 +
testcases/kernel/syscalls/inotify/inotify13.c | 217 ++++++++++++++++++++++++++
3 files changed, 219 insertions(+)
create mode 100644 testcases/kernel/syscalls/inotify/inotify13.c
diff --git a/runtest/syscalls b/runtest/syscalls
index 2bdb4a9a2b4801d03d93efea686124530f5ce2d5..e578bd92d7e7ded5c0ca2f95dc87fce814d733cc 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 f6e5c546ac4032766cee9f16b3689be5d0302d37..b597ea63f493b2da2a58017d3eb7b16a7b033fc5 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 0000000000000000000000000000000000000000..2ea5b5d4aeff9b368e5fa79ec18207035567a34b
--- /dev/null
+++ b/testcases/kernel/syscalls/inotify/inotify13.c
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Hungyu Lin <dennylin0707@gmail.com>
+ */
+
+/*\
+ * Verify that two IN_MOVED_FROM events that differ only in their rename
+ * cookie are not coalesced.
+ *
+ * inotify(7) allows coalescing only of events with identical wd, mask,
+ * cookie and name. A regression introduced in v2.6.31 dropped the cookie
+ * comparison. On affected kernels, the second IN_MOVED_FROM is coalesced
+ * into the first, leaving the following IN_MOVED_TO without a matching
+ * IN_MOVED_FROM cookie.
+ *
+ * With source/ and target/ watched for IN_MOVE, and without reading the queue:
+ *
+ * 1. Move source/entry to unwatched/ (IN_MOVED_FROM, cookie A).
+ * 2. Recreate source/entry.
+ * 3. Move source/entry to target/ (IN_MOVED_FROM and IN_MOVED_TO, cookie B).
+ *
+ * All three events must be queued, with A != B. IN_CREATE is not watched, so
+ * the two IN_MOVED_FROM events are adjacent; the queue is not read in between,
+ * because only the last unread event can be merged with a new one. Both a
+ * regular file and a directory are tested.
+ */
+
+#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 UNWATCHED "unwatched/entry"
+#define TARGET "target/entry"
+
+struct event_t {
+ const char *name;
+ unsigned int mask;
+ int wd;
+};
+
+static struct tcase {
+ const char *type;
+ unsigned int isdir;
+} tcases[] = {
+ { "file", 0 },
+ { "directory", IN_ISDIR },
+};
+
+static int fd_notify = -1;
+
+static void create_entry(unsigned int isdir, const char *path)
+{
+ if (isdir)
+ SAFE_MKDIR(path, 0700);
+ else
+ SAFE_TOUCH(path, 0600, NULL);
+}
+
+static void delete_entry(unsigned int isdir, const char *path)
+{
+ if (isdir)
+ SAFE_RMDIR(path);
+ else
+ SAFE_UNLINK(path);
+}
+
+static void verify_inotify(unsigned int n)
+{
+ const char *type = tcases[n].type;
+ unsigned int isdir = tcases[n].isdir;
+ 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;
+
+ 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(isdir, SOURCE);
+ SAFE_RENAME(SOURCE, UNWATCHED);
+ create_entry(isdir, SOURCE);
+ SAFE_RENAME(SOURCE, TARGET);
+
+ len = SAFE_READ(SAFE_READ_ANY_EAGAIN, fd_notify, buf.data, sizeof(buf.data));
+ SAFE_CLOSE(fd_notify);
+
+ /* Reuse these paths for the next case and for -i iterations. */
+ delete_entry(isdir, UNWATCHED);
+ delete_entry(isdir, TARGET);
+
+ while (pos < len) {
+ if (len - pos < sizeof(*event))
+ tst_brk(TBROK, "%s: event %u has %zu header bytes (expected at least %zu)",
+ type, count, len - pos, sizeof(*event));
+
+ event = (struct inotify_event *)(buf.data + pos);
+ if (event->len > len - pos - sizeof(*event))
+ tst_brk(TBROK, "%s: event %u claims %u name bytes but only %zu are left in the buffer, wd=%d mask=%#x cookie=%u",
+ type, count, event->len, len - pos - sizeof(*event),
+ event->wd, event->mask, event->cookie);
+
+ if (count >= ARRAY_SIZE(event_set)) {
+ tst_res(TFAIL, "%s: unexpected extra event %u: got wd=%d mask=%#x cookie=%u len=%u name=\"%.*s\", expected no event after %zu events",
+ type, count, event->wd,
+ event->mask, event->cookie, event->len,
+ (int)event->len, event->name, ARRAY_SIZE(event_set));
+ return;
+ }
+
+ expected = &event_set[count];
+ if (event->mask != expected->mask) {
+ tst_res(TFAIL, "%s: event %u has wrong mask: got wd=%d mask=%#x cookie=%u name=\"%.*s\", expected wd=%d mask=%#x name=\"%s\"",
+ type, count, event->wd, event->mask, event->cookie,
+ (int)event->len, event->name, expected->wd,
+ expected->mask, expected->name);
+ return;
+ }
+
+ if (event->wd != expected->wd) {
+ tst_res(TFAIL, "%s: event %u has wrong wd: got wd=%d mask=%#x cookie=%u name=\"%.*s\", expected wd=%d mask=%#x name=\"%s\"",
+ type, count, event->wd, event->mask, event->cookie,
+ (int)event->len, event->name, expected->wd,
+ expected->mask, expected->name);
+ return;
+ }
+
+ if (event->len < strlen(expected->name) + 1 ||
+ strncmp(expected->name, event->name, event->len)) {
+ tst_res(TFAIL, "%s: event %u has wrong name: got wd=%d mask=%#x cookie=%u len=%u name=\"%.*s\", expected wd=%d mask=%#x name=\"%s\"",
+ type, count, event->wd, event->mask, event->cookie,
+ event->len, (int)event->len, event->name,
+ expected->wd, expected->mask, expected->name);
+ return;
+ }
+
+ cookies[count] = event->cookie;
+ tst_res(TINFO, "%s: 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);
+ count++;
+ pos += sizeof(*event) + event->len;
+ }
+
+ for (i = count; i < ARRAY_SIZE(event_set); i++) {
+ expected = &event_set[i];
+ tst_res(TFAIL, "%s: missing event %u: got no event, expected wd=%d mask=%#x name=\"%s\"",
+ type, i, expected->wd, expected->mask, expected->name);
+ }
+
+ if (count < ARRAY_SIZE(event_set))
+ return;
+
+ if (cookies[0] == cookies[1]) {
+ tst_res(TFAIL, "%s: expected different FROM cookies for wd=%d mask=%#x name=\"%s\", got first=%u second=%u",
+ type, event_set[0].wd, event_set[0].mask,
+ event_set[0].name, cookies[0], cookies[1]);
+ return;
+ }
+
+ if (cookies[1] != cookies[2]) {
+ tst_res(TFAIL, "%s: expected matching rename cookies: FROM wd=%d mask=%#x name=\"%s\" cookie=%u, TO wd=%d mask=%#x name=\"%s\" cookie=%u",
+ type, event_set[1].wd, event_set[1].mask,
+ event_set[1].name, cookies[1], event_set[2].wd,
+ event_set[2].mask, event_set[2].name, cookies[2]);
+ return;
+ }
+
+ tst_res(TPASS, "%s: FROM cookies %u and %u differ, TO cookie %u matches the second",
+ type, cookies[0], 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 = ARRAY_SIZE(tcases),
+ .setup = setup,
+ .cleanup = cleanup,
+ .needs_tmpdir = 1,
+};
+
+#else
+TST_TEST_TCONF("system doesn't have required inotify support");
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [LTP] inotify13: check rename cookie pairing after adjacent moves
2026-10-29 17:49 [PATCH v3] " Hungyu Lin
@ 2026-10-29 19:58 ` linuxtestproject.agent
0 siblings, 0 replies; 8+ messages in thread
From: linuxtestproject.agent @ 2026-10-29 19:58 UTC (permalink / raw)
To: Hungyu Lin; +Cc: ltp
Hi Hungyu,
On Thu Oct 29 17:49:07 2026 +0000, Hungyu Lin <dennylin0707@gmail.com> wrote:
> inotify13: check rename cookie pairing after adjacent moves
Verdict - Reviewed
---
Note:
The agent can sometimes produce false positives although often its
findings are genuine. If you find issues with the review, please
comment this email or ignore the suggestions.
Regards,
LTP AI Reviewer
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 8+ messages in thread
* [LTP] [PATCH v4] inotify13: check rename cookie pairing after adjacent moves
@ 2026-10-29 21:07 Hungyu Lin
2026-10-30 0:31 ` [LTP] " linuxtestproject.agent
0 siblings, 1 reply; 8+ messages in thread
From: Hungyu Lin @ 2026-10-29 21:07 UTC (permalink / raw)
To: ltp; +Cc: linux-fsdevel, dennylin0707, amir73il, jack
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>
---
Changes in v4:
- Put expected values immediately after the mismatched fields, following
the existing per-field diagnostic format.
- Use the existing "got unnecessary event" and "didn't get event" wording.
- Keep event context for extra events and mask, watch descriptor and
filename mismatches. Omit it from the two cookie-failure messages,
since it is already present in TINFO, as confirmed by Amir.
Built with GCC 13.3.0 and checked with LTP checkpatch and sparse. On ARM64
QEMU, the test reports 0 pass / 2 fail on the baseline kernel and 2 pass /
0 fail with the kernel fix. Local diagnostic controls also exercise both
cookie failures and verify the preceding TINFO and concise TFAIL output.
The controls are kept outside this patch.
runtest/syscalls | 1 +
testcases/kernel/syscalls/inotify/.gitignore | 1 +
testcases/kernel/syscalls/inotify/inotify13.c | 211 ++++++++++++++++++++++++++
3 files changed, 213 insertions(+)
create mode 100644 testcases/kernel/syscalls/inotify/inotify13.c
diff --git a/runtest/syscalls b/runtest/syscalls
index 2bdb4a9a2b4801d03d93efea686124530f5ce2d5..e578bd92d7e7ded5c0ca2f95dc87fce814d733cc 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 f6e5c546ac4032766cee9f16b3689be5d0302d37..b597ea63f493b2da2a58017d3eb7b16a7b033fc5 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 0000000000000000000000000000000000000000..9ee660e041435d16b63097ba8e2f86515d6bac57
--- /dev/null
+++ b/testcases/kernel/syscalls/inotify/inotify13.c
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Hungyu Lin <dennylin0707@gmail.com>
+ */
+
+/*\
+ * Verify that two IN_MOVED_FROM events that differ only in their rename
+ * cookie are not coalesced.
+ *
+ * inotify(7) allows coalescing only of events with identical wd, mask,
+ * cookie and name. A regression introduced in v2.6.31 dropped the cookie
+ * comparison. On affected kernels, the second IN_MOVED_FROM is coalesced
+ * into the first, leaving the following IN_MOVED_TO without a matching
+ * IN_MOVED_FROM cookie.
+ *
+ * With source/ and target/ watched for IN_MOVE, and without reading the queue:
+ *
+ * 1. Move source/entry to unwatched/ (IN_MOVED_FROM, cookie A).
+ * 2. Recreate source/entry.
+ * 3. Move source/entry to target/ (IN_MOVED_FROM and IN_MOVED_TO, cookie B).
+ *
+ * All three events must be queued, with A != B. IN_CREATE is not watched, so
+ * the two IN_MOVED_FROM events are adjacent; the queue is not read in between,
+ * because only the last unread event can be merged with a new one. Both a
+ * regular file and a directory are tested.
+ */
+
+#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 UNWATCHED "unwatched/entry"
+#define TARGET "target/entry"
+
+struct event_t {
+ const char *name;
+ unsigned int mask;
+ int wd;
+};
+
+static struct tcase {
+ const char *type;
+ unsigned int isdir;
+} tcases[] = {
+ { "file", 0 },
+ { "directory", IN_ISDIR },
+};
+
+static int fd_notify = -1;
+
+static void create_entry(unsigned int isdir, const char *path)
+{
+ if (isdir)
+ SAFE_MKDIR(path, 0700);
+ else
+ SAFE_TOUCH(path, 0600, NULL);
+}
+
+static void delete_entry(unsigned int isdir, const char *path)
+{
+ if (isdir)
+ SAFE_RMDIR(path);
+ else
+ SAFE_UNLINK(path);
+}
+
+static void verify_inotify(unsigned int n)
+{
+ const char *type = tcases[n].type;
+ unsigned int isdir = tcases[n].isdir;
+ 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;
+
+ 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(isdir, SOURCE);
+ SAFE_RENAME(SOURCE, UNWATCHED);
+ create_entry(isdir, SOURCE);
+ SAFE_RENAME(SOURCE, TARGET);
+
+ len = SAFE_READ(SAFE_READ_ANY_EAGAIN, fd_notify, buf.data, sizeof(buf.data));
+ SAFE_CLOSE(fd_notify);
+
+ /* Reuse these paths for the next case and for -i iterations. */
+ delete_entry(isdir, UNWATCHED);
+ delete_entry(isdir, TARGET);
+
+ while (pos < len) {
+ if (len - pos < sizeof(*event))
+ tst_brk(TBROK, "%s: event %u has %zu header bytes (expected at least %zu)",
+ type, count, len - pos, sizeof(*event));
+
+ event = (struct inotify_event *)(buf.data + pos);
+ if (event->len > len - pos - sizeof(*event))
+ tst_brk(TBROK, "%s: event %u claims %u name bytes but only %zu are left in the buffer, wd=%d mask=%#x cookie=%u",
+ type, count, event->len, len - pos - sizeof(*event),
+ event->wd, event->mask, event->cookie);
+
+ if (count >= ARRAY_SIZE(event_set)) {
+ tst_res(TFAIL, "%s: got unnecessary event %u: mask=%#x wd=%d cookie=%u len=%u name=\"%.*s\"",
+ type, count, event->mask, event->wd, event->cookie,
+ event->len, (int)event->len, event->name);
+ return;
+ }
+
+ expected = &event_set[count];
+ if (event->mask != expected->mask) {
+ tst_res(TFAIL, "%s: got event %u: mask=%#x (expected %#x) wd=%d cookie=%u name=\"%.*s\"",
+ type, count, event->mask, expected->mask, event->wd,
+ event->cookie, (int)event->len, event->name);
+ return;
+ }
+
+ if (event->wd != expected->wd) {
+ tst_res(TFAIL, "%s: got event %u: mask=%#x wd=%d (expected %d) cookie=%u name=\"%.*s\"",
+ type, count, event->mask, event->wd, expected->wd,
+ event->cookie, (int)event->len, event->name);
+ return;
+ }
+
+ if (event->len < strlen(expected->name) + 1 ||
+ strncmp(expected->name, event->name, event->len)) {
+ tst_res(TFAIL, "%s: got event %u: mask=%#x wd=%d cookie=%u len=%u name=\"%.*s\" (expected \"%s\")",
+ type, count, event->mask, event->wd, event->cookie,
+ event->len, (int)event->len, event->name,
+ expected->name);
+ return;
+ }
+
+ cookies[count] = event->cookie;
+ tst_res(TINFO, "%s: 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);
+ count++;
+ pos += sizeof(*event) + event->len;
+ }
+
+ for (i = count; i < ARRAY_SIZE(event_set); i++) {
+ expected = &event_set[i];
+ tst_res(TFAIL, "%s: didn't get event %u: mask=%#x wd=%d name=\"%s\"",
+ type, i, expected->mask, expected->wd, expected->name);
+ }
+
+ if (count < ARRAY_SIZE(event_set))
+ return;
+
+ if (cookies[0] == cookies[1]) {
+ tst_res(TFAIL, "%s: got second FROM cookie=%u (expected != %u)",
+ type, cookies[1], cookies[0]);
+ return;
+ }
+
+ if (cookies[1] != cookies[2]) {
+ tst_res(TFAIL, "%s: got TO cookie=%u (expected %u from second FROM)",
+ type, cookies[2], cookies[1]);
+ return;
+ }
+
+ tst_res(TPASS, "%s: FROM cookies %u and %u differ, TO cookie %u matches the second",
+ type, cookies[0], 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 = ARRAY_SIZE(tcases),
+ .setup = setup,
+ .cleanup = cleanup,
+ .needs_tmpdir = 1,
+};
+
+#else
+TST_TEST_TCONF("system doesn't have required inotify support");
+#endif
--
2.43.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [LTP] inotify13: check rename cookie pairing after adjacent moves
2026-10-29 21:07 [LTP] [PATCH v4] " Hungyu Lin
@ 2026-10-30 0:31 ` linuxtestproject.agent
0 siblings, 0 replies; 8+ messages in thread
From: linuxtestproject.agent @ 2026-10-30 0:31 UTC (permalink / raw)
To: Hungyu Lin; +Cc: ltp
Hi Hungyu,
On Thu Oct 29, 2026, Hungyu Lin <dennylin0707@gmail.com> wrote:
> inotify13: check rename cookie pairing after adjacent moves
> +/*\
> + * Verify that two IN_MOVED_FROM events that differ only in their rename
> + * cookie are not coalesced.
> + *
> + * inotify(7) allows coalescing only of events with identical wd, mask,
Use :manpage:`inotify(7)` in the RST doc comment so that the test catalog
renders a link to the man page.
> +static void cleanup(void)
> +{
> + if (fd_notify >= 0)
> + SAFE_CLOSE(fd_notify);
> +}
Use "if (fd_notify != -1)" to check file descriptor validity in cleanup(),
matching the -1 initialization convention.
> +static struct tst_test test = {
> + .test = verify_inotify,
> + .tcnt = ARRAY_SIZE(tcases),
> + .setup = setup,
> + .cleanup = cleanup,
> + .needs_tmpdir = 1,
> +};
Since this is a kernel regression test, add .tags with the "linux-git" fix
commit hash in struct tst_test once the kernel fix is merged.
Verdict - Needs revision
---
Note:
The agent can sometimes produce false positives although often its
findings are genuine. If you find issues with the review, please
comment this email or ignore the suggestions.
Regards,
LTP AI Reviewer
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-10-30 0:31 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-27 19:19 [PATCH] inotify13: check rename cookie pairing after adjacent moves Hungyu Lin
2026-10-27 19:19 ` [LTP] " Hungyu Lin
2026-10-28 3:19 ` Amir Goldstein
2026-10-28 3:19 ` [LTP] " Amir Goldstein
2026-10-29 19:57 ` [LTP] " linuxtestproject.agent
-- strict thread matches above, loose matches on Subject: below --
2026-10-28 5:15 [PATCH v2] " Hungyu Lin
2026-10-29 19:56 ` [LTP] " linuxtestproject.agent
2026-10-29 17:49 [PATCH v3] " Hungyu Lin
2026-10-29 19:58 ` [LTP] " linuxtestproject.agent
2026-10-29 21:07 [LTP] [PATCH v4] " Hungyu Lin
2026-10-30 0:31 ` [LTP] " linuxtestproject.agent
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.