fuse: Call vfs_get_tree() for submounts
authorGreg Kurz <groug@kaod.org>
Fri, 4 Jun 2021 16:11:54 +0000 (18:11 +0200)
committerMiklos Szeredi <mszeredi@redhat.com>
Tue, 22 Jun 2021 07:15:35 +0000 (09:15 +0200)
We recently fixed an infinite loop by setting the SB_BORN flag on
submounts along with the write barrier needed by super_cache_count().
This is the job of vfs_get_tree() and FUSE shouldn't have to care
about the barrier at all.

Split out some code from fuse_dentry_automount() to the dedicated
fuse_get_tree_submount() handler for submounts and call vfs_get_tree().

Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Max Reitz <mreitz@redhat.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
fs/fuse/dir.c
fs/fuse/inode.c

index 3fa8604..b88e578 100644 (file)
@@ -309,12 +309,8 @@ static int fuse_dentry_delete(const struct dentry *dentry)
 static struct vfsmount *fuse_dentry_automount(struct path *path)
 {
        struct fs_context *fsc;
-       struct fuse_mount *parent_fm = get_fuse_mount_super(path->mnt->mnt_sb);
-       struct fuse_conn *fc = parent_fm->fc;
-       struct fuse_mount *fm;
        struct vfsmount *mnt;
        struct fuse_inode *mp_fi = get_fuse_inode(d_inode(path->dentry));
-       struct super_block *sb;
        int err;
 
        fsc = fs_context_for_submount(path->mnt->mnt_sb->s_type, path->dentry);
@@ -323,48 +319,15 @@ static struct vfsmount *fuse_dentry_automount(struct path *path)
                goto out;
        }
 
-       err = -ENOMEM;
-       fm = kzalloc(sizeof(struct fuse_mount), GFP_KERNEL);
-       if (!fm)
-               goto out_put_fsc;
+       /* Pass the FUSE inode of the mount for fuse_get_tree_submount() */
+       fsc->fs_private = mp_fi;
 
-       fsc->s_fs_info = fm;
-       sb = sget_fc(fsc, NULL, set_anon_super_fc);
-       if (IS_ERR(sb)) {
-               err = PTR_ERR(sb);
-               kfree(fm);
+       err = vfs_get_tree(fsc);
+       if (err)
                goto out_put_fsc;
-       }
-       fm->fc = fuse_conn_get(fc);
-
-       /* Initialize superblock, making @mp_fi its root */
-       err = fuse_fill_super_submount(sb, mp_fi);
-       if (err) {
-               fuse_conn_put(fc);
-               kfree(fm);
-               sb->s_fs_info = NULL;
-               goto out_put_sb;
-       }
-
-       down_write(&fc->killsb);
-       list_add_tail(&fm->fc_entry, &fc->mounts);
-       up_write(&fc->killsb);
-
-       sb->s_flags |= SB_ACTIVE;
-       fsc->root = dget(sb->s_root);
-
-       /*
-        * FIXME: setting SB_BORN requires a write barrier for
-        *        super_cache_count(). We should actually come
-        *        up with a proper ->get_tree() implementation
-        *        for submounts and call vfs_get_tree() to take
-        *        care of the write barrier.
-        */
-       smp_wmb();
-       sb->s_flags |= SB_BORN;
 
        /* We are done configuring the superblock, so unlock it */
-       up_write(&sb->s_umount);
+       up_write(&fsc->root->d_sb->s_umount);
 
        /* Create the submount */
        mnt = vfs_create_mount(fsc);
@@ -376,12 +339,6 @@ static struct vfsmount *fuse_dentry_automount(struct path *path)
        put_fs_context(fsc);
        return mnt;
 
-out_put_sb:
-       /*
-        * Only jump here when fsc->root is NULL and sb is still locked
-        * (otherwise put_fs_context() will put the superblock)
-        */
-       deactivate_locked_super(sb);
 out_put_fsc:
        put_fs_context(fsc);
 out:
index a0a8228..c7aee88 100644 (file)
@@ -1353,8 +1353,44 @@ int fuse_fill_super_submount(struct super_block *sb,
        return 0;
 }
 
+/* Filesystem context private data holds the FUSE inode of the mount point */
 static int fuse_get_tree_submount(struct fs_context *fsc)
 {
+       struct fuse_mount *fm;
+       struct fuse_inode *mp_fi = fsc->fs_private;
+       struct fuse_conn *fc = get_fuse_conn(&mp_fi->inode);
+       struct super_block *sb;
+       int err;
+
+       fm = kzalloc(sizeof(struct fuse_mount), GFP_KERNEL);
+       if (!fm)
+               return -ENOMEM;
+
+       fsc->s_fs_info = fm;
+       sb = sget_fc(fsc, NULL, set_anon_super_fc);
+       if (IS_ERR(sb)) {
+               kfree(fm);
+               return PTR_ERR(sb);
+       }
+       fm->fc = fuse_conn_get(fc);
+
+       /* Initialize superblock, making @mp_fi its root */
+       err = fuse_fill_super_submount(sb, mp_fi);
+       if (err) {
+               fuse_conn_put(fc);
+               kfree(fm);
+               sb->s_fs_info = NULL;
+               deactivate_locked_super(sb);
+               return err;
+       }
+
+       down_write(&fc->killsb);
+       list_add_tail(&fm->fc_entry, &fc->mounts);
+       up_write(&fc->killsb);
+
+       sb->s_flags |= SB_ACTIVE;
+       fsc->root = dget(sb->s_root);
+
        return 0;
 }