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

Amir Goldstein amir73il@gmail.com
Wed Oct 28 04:19:56 CET 2026


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
>


More information about the ltp mailing list