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

Amir Goldstein amir73il@gmail.com
Wed Oct 28 15:32:50 CET 2026


On Wed, Oct 28, 2026 at 7:15 AM 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>
> ---
>  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);
> +}
> +

Seems nicer to have delete_entry() rather than open coding it

> +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\")",

I suppose you are not writing the test manually so fast?
so please ask your agent to look at the other tests and follow their
pattern, which is to TINFO the correct event without (expected) values
and to TFAIL with specific reason and the expected value.


> +                               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;

I mean it's not super important, but as long as you have the agent working
for you at least give it accurate instructions, because this code I find
a lot less readable that the usual pattern and if anyone looks at the output
it's even less clear when something goes wrong, without learning the test code.

> +               }
> +               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;
> +       }

These are examples of good explicit failure messages.

Thanks,
Amir.

> +
> +       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