Re: [PATCH v3] fs: let backing files inherit FMODE_NONOTIFY from the user file
From: Amir Goldstein
Date: Tue Nov 03 2026 - 10:49:10 EST
On Tue, Nov 3, 2026 at 2:01 PM Edvard Conrad <edvard@xxxxxxx> wrote:
>
> On Mon, Nov 2, 2026 at 1:34 PM Amir Goldstein <amir73il@xxxxxxxxx> wrote:
> > Good catch!
> > Please test the attached v2 patch and post the new test cases.
>
> v2 fixes the merged directory. An impure directory still deadlocks the
> same way: ovl_iterate_real() reads it through ovl_cache_get_impure() and
> ovl_dir_read_impure(), which v2 leaves without the user file, so
> ovl_dir_read() opens the upper directory with plain ovl_path_open():
>
> fanotify_handle_event
> fsnotify
> fsnotify_open_perm_and_set_mode
> do_dentry_open
> vfs_open
> dentry_open
> ovl_iterate_real
> ovl_iterate
> iterate_dir
> __x64_sys_getdents64
>
> That takes an upper-only directory that a lower file was renamed into,
> on layers that share a filesystem, so ovl_need_adjust_d_ino() sends it
> to ovl_iterate_real(). Passing the file down the same way v2 does for
> the merged case fixes it for me, on top of v2:
>
> diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
> --- a/fs/overlayfs/readdir.c
> +++ b/fs/overlayfs/readdir.c
> @@ -670,7 +670,7 @@ static bool ovl_fill_plain(struct dir_context *ctx, const char *name,
> }
>
> static int ovl_dir_read_impure(const struct path *path, struct list_head *list,
> - struct rb_root *root)
> + struct rb_root *root, const struct file *user_file)
> {
> int err;
> struct path realpath;
> @@ -678,6 +678,7 @@ static int ovl_dir_read_impure(const struct path *path, struct list_head *list,
> struct ovl_readdir_data rdd = {
> .ctx.actor = ovl_fill_plain,
> .ctx.count = INT_MAX,
> + .user_file = user_file,
> .list = list,
> .root = root,
> };
> @@ -714,9 +715,10 @@ static int ovl_dir_read_impure(const struct path *path, struct list_head *list,
> return 0;
> }
>
> -static struct ovl_dir_cache *ovl_cache_get_impure(const struct path *path)
> +static struct ovl_dir_cache *ovl_cache_get_impure(struct file *file)
> {
> int res;
> + const struct path *path = &file->f_path;
> struct dentry *dentry = path->dentry;
> struct inode *inode = d_inode(dentry);
> struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
> @@ -735,7 +737,7 @@ static struct ovl_dir_cache *ovl_cache_get_impure(const struct path *path)
> return ERR_PTR(-ENOMEM);
>
> cache->is_refcounted = false;
> - res = ovl_dir_read_impure(path, &cache->entries, &cache->root);
> + res = ovl_dir_read_impure(path, &cache->entries, &cache->root, file);
> if (res) {
> ovl_cache_free(&cache->entries);
> kfree(cache);
> @@ -856,7 +858,7 @@ static int ovl_iterate_real(struct file *file, struct dir_context *ctx)
> dput(parent);
>
> if (ovl_is_impure_dir(file)) {
> - rdt.cache = ovl_cache_get_impure(&file->f_path);
> + rdt.cache = ovl_cache_get_impure(file);
> if (IS_ERR(rdt.cache))
> return PTR_ERR(rdt.cache);
> }
>
Right, I was being sloppy.
Thanks for the fixes.
I added Co-developed-by to attribute your contributions to this fix.
> Results in a VM on 6b4b543fa18, each kernel run once and with -i 2,
> with the backing filesystem marked:
>
> file dir merged dir impure dir
> listed listed
> + c3d355f427ce PASS FAIL FAIL FAIL
> + your v1 PASS PASS FAIL FAIL
> + your v2 PASS PASS PASS FAIL
> + v2 and the diff above PASS PASS PASS PASS
>
> The new cases are posted on top of the directory cases:
> https://lore.kernel.org/ltp/P5WKBbq--F-9@xxxxxxx/
Thanks again!
Amir.