Skip to content

Commit bf109c6

Browse files
XanClicMiklos Szeredi
authored andcommitted
fuse: implement crossmounts
FUSE servers can indicate crossmount points by setting FUSE_ATTR_SUBMOUNT in fuse_attr.flags. The inode will then be marked as S_AUTOMOUNT, and the .d_automount implementation creates a new submount at that location, so that the submount gets a distinct st_dev value. Note that all submounts get a distinct superblock and a distinct st_dev value, so for virtio-fs, even if the same filesystem is mounted more than once on the host, none of its mount points will have the same st_dev. We need distinct superblocks because the superblock points to the root node, but the different host mounts may show different trees (e.g. due to submounts in some of them, but not in others). Right now, this behavior is only enabled when fuse_conn.auto_submounts is set, which is the case only for virtio-fs. Signed-off-by: Max Reitz <[email protected]> Signed-off-by: Miklos Szeredi <[email protected]>
1 parent 1866d77 commit bf109c6

File tree

4 files changed

+105
-3
lines changed

4 files changed

+105
-3
lines changed

fs/fuse/dir.c

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include <linux/pagemap.h>
1212
#include <linux/file.h>
13+
#include <linux/fs_context.h>
1314
#include <linux/sched.h>
1415
#include <linux/namei.h>
1516
#include <linux/slab.h>
@@ -237,7 +238,8 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
237238
ret = -ENOENT;
238239
if (!ret) {
239240
fi = get_fuse_inode(inode);
240-
if (outarg.nodeid != get_node_id(inode)) {
241+
if (outarg.nodeid != get_node_id(inode) ||
242+
(bool) IS_AUTOMOUNT(inode) != (bool) (outarg.attr.flags & FUSE_ATTR_SUBMOUNT)) {
241243
fuse_queue_forget(fm->fc, forget,
242244
outarg.nodeid, 1);
243245
goto invalid;
@@ -299,13 +301,87 @@ static int fuse_dentry_delete(const struct dentry *dentry)
299301
return time_before64(fuse_dentry_time(dentry), get_jiffies_64());
300302
}
301303

304+
/*
305+
* Create a fuse_mount object with a new superblock (with path->dentry
306+
* as the root), and return that mount so it can be auto-mounted on
307+
* @path.
308+
*/
309+
static struct vfsmount *fuse_dentry_automount(struct path *path)
310+
{
311+
struct fs_context *fsc;
312+
struct fuse_mount *parent_fm = get_fuse_mount_super(path->mnt->mnt_sb);
313+
struct fuse_conn *fc = parent_fm->fc;
314+
struct fuse_mount *fm;
315+
struct vfsmount *mnt;
316+
struct fuse_inode *mp_fi = get_fuse_inode(d_inode(path->dentry));
317+
struct super_block *sb;
318+
int err;
319+
320+
fsc = fs_context_for_submount(path->mnt->mnt_sb->s_type, path->dentry);
321+
if (IS_ERR(fsc)) {
322+
err = PTR_ERR(fsc);
323+
goto out;
324+
}
325+
326+
err = -ENOMEM;
327+
fm = kzalloc(sizeof(struct fuse_mount), GFP_KERNEL);
328+
if (!fm)
329+
goto out_put_fsc;
330+
331+
refcount_set(&fm->count, 1);
332+
fsc->s_fs_info = fm;
333+
sb = sget_fc(fsc, NULL, set_anon_super_fc);
334+
if (IS_ERR(sb)) {
335+
err = PTR_ERR(sb);
336+
fuse_mount_put(fm);
337+
goto out_put_fsc;
338+
}
339+
fm->fc = fuse_conn_get(fc);
340+
341+
/* Initialize superblock, making @mp_fi its root */
342+
err = fuse_fill_super_submount(sb, mp_fi);
343+
if (err)
344+
goto out_put_sb;
345+
346+
sb->s_flags |= SB_ACTIVE;
347+
fsc->root = dget(sb->s_root);
348+
/* We are done configuring the superblock, so unlock it */
349+
up_write(&sb->s_umount);
350+
351+
down_write(&fc->killsb);
352+
list_add_tail(&fm->fc_entry, &fc->mounts);
353+
up_write(&fc->killsb);
354+
355+
/* Create the submount */
356+
mnt = vfs_create_mount(fsc);
357+
if (IS_ERR(mnt)) {
358+
err = PTR_ERR(mnt);
359+
goto out_put_fsc;
360+
}
361+
mntget(mnt);
362+
put_fs_context(fsc);
363+
return mnt;
364+
365+
out_put_sb:
366+
/*
367+
* Only jump here when fsc->root is NULL and sb is still locked
368+
* (otherwise put_fs_context() will put the superblock)
369+
*/
370+
deactivate_locked_super(sb);
371+
out_put_fsc:
372+
put_fs_context(fsc);
373+
out:
374+
return ERR_PTR(err);
375+
}
376+
302377
const struct dentry_operations fuse_dentry_operations = {
303378
.d_revalidate = fuse_dentry_revalidate,
304379
.d_delete = fuse_dentry_delete,
305380
#if BITS_PER_LONG < 64
306381
.d_init = fuse_dentry_init,
307382
.d_release = fuse_dentry_release,
308383
#endif
384+
.d_automount = fuse_dentry_automount,
309385
};
310386

311387
const struct dentry_operations fuse_root_dentry_operations = {

fs/fuse/fuse_i.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,9 @@ struct fuse_conn {
742742
/** Do not allow MNT_FORCE umount */
743743
unsigned int no_force_umount:1;
744744

745+
/* Auto-mount submounts announced by the server */
746+
unsigned int auto_submounts:1;
747+
745748
/** The number of requests waiting for completion */
746749
atomic_t num_waiting;
747750

fs/fuse/inode.c

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,26 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
309309
struct fuse_inode *fi;
310310
struct fuse_conn *fc = get_fuse_conn_super(sb);
311311

312-
retry:
312+
/*
313+
* Auto mount points get their node id from the submount root, which is
314+
* not a unique identifier within this filesystem.
315+
*
316+
* To avoid conflicts, do not place submount points into the inode hash
317+
* table.
318+
*/
319+
if (fc->auto_submounts && (attr->flags & FUSE_ATTR_SUBMOUNT) &&
320+
S_ISDIR(attr->mode)) {
321+
inode = new_inode(sb);
322+
if (!inode)
323+
return NULL;
324+
325+
fuse_init_inode(inode, attr);
326+
get_fuse_inode(inode)->nodeid = nodeid;
327+
inode->i_flags |= S_AUTOMOUNT;
328+
goto done;
329+
}
330+
331+
retry:
313332
inode = iget5_locked(sb, nodeid, fuse_inode_eq, fuse_inode_set, &nodeid);
314333
if (!inode)
315334
return NULL;
@@ -327,7 +346,7 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
327346
iput(inode);
328347
goto retry;
329348
}
330-
349+
done:
331350
fi = get_fuse_inode(inode);
332351
spin_lock(&fi->lock);
333352
fi->nlookup++;
@@ -1083,6 +1102,9 @@ void fuse_send_init(struct fuse_mount *fm)
10831102
if (fm->fc->dax)
10841103
ia->in.flags |= FUSE_MAP_ALIGNMENT;
10851104
#endif
1105+
if (fm->fc->auto_submounts)
1106+
ia->in.flags |= FUSE_SUBMOUNTS;
1107+
10861108
ia->args.opcode = FUSE_INIT;
10871109
ia->args.in_numargs = 1;
10881110
ia->args.in_args[0].size = sizeof(ia->in);

fs/fuse/virtio_fs.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1431,6 +1431,7 @@ static int virtio_fs_get_tree(struct fs_context *fsc)
14311431
&virtio_fs_fiq_ops, fs);
14321432
fc->release = fuse_free_conn;
14331433
fc->delete_stale = true;
1434+
fc->auto_submounts = true;
14341435

14351436
fsc->s_fs_info = fm;
14361437
sb = sget_fc(fsc, virtio_fs_test_super, virtio_fs_set_super);

0 commit comments

Comments
 (0)