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 <mreitz@redhat.com> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
This commit is contained in:
Родитель
1866d779d5
Коммит
bf109c6404
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
#include <linux/pagemap.h>
|
#include <linux/pagemap.h>
|
||||||
#include <linux/file.h>
|
#include <linux/file.h>
|
||||||
|
#include <linux/fs_context.h>
|
||||||
#include <linux/sched.h>
|
#include <linux/sched.h>
|
||||||
#include <linux/namei.h>
|
#include <linux/namei.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
|
@ -237,7 +238,8 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
|
||||||
ret = -ENOENT;
|
ret = -ENOENT;
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
fi = get_fuse_inode(inode);
|
fi = get_fuse_inode(inode);
|
||||||
if (outarg.nodeid != get_node_id(inode)) {
|
if (outarg.nodeid != get_node_id(inode) ||
|
||||||
|
(bool) IS_AUTOMOUNT(inode) != (bool) (outarg.attr.flags & FUSE_ATTR_SUBMOUNT)) {
|
||||||
fuse_queue_forget(fm->fc, forget,
|
fuse_queue_forget(fm->fc, forget,
|
||||||
outarg.nodeid, 1);
|
outarg.nodeid, 1);
|
||||||
goto invalid;
|
goto invalid;
|
||||||
|
@ -299,6 +301,79 @@ static int fuse_dentry_delete(const struct dentry *dentry)
|
||||||
return time_before64(fuse_dentry_time(dentry), get_jiffies_64());
|
return time_before64(fuse_dentry_time(dentry), get_jiffies_64());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a fuse_mount object with a new superblock (with path->dentry
|
||||||
|
* as the root), and return that mount so it can be auto-mounted on
|
||||||
|
* @path.
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
if (IS_ERR(fsc)) {
|
||||||
|
err = PTR_ERR(fsc);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = -ENOMEM;
|
||||||
|
fm = kzalloc(sizeof(struct fuse_mount), GFP_KERNEL);
|
||||||
|
if (!fm)
|
||||||
|
goto out_put_fsc;
|
||||||
|
|
||||||
|
refcount_set(&fm->count, 1);
|
||||||
|
fsc->s_fs_info = fm;
|
||||||
|
sb = sget_fc(fsc, NULL, set_anon_super_fc);
|
||||||
|
if (IS_ERR(sb)) {
|
||||||
|
err = PTR_ERR(sb);
|
||||||
|
fuse_mount_put(fm);
|
||||||
|
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)
|
||||||
|
goto out_put_sb;
|
||||||
|
|
||||||
|
sb->s_flags |= SB_ACTIVE;
|
||||||
|
fsc->root = dget(sb->s_root);
|
||||||
|
/* We are done configuring the superblock, so unlock it */
|
||||||
|
up_write(&sb->s_umount);
|
||||||
|
|
||||||
|
down_write(&fc->killsb);
|
||||||
|
list_add_tail(&fm->fc_entry, &fc->mounts);
|
||||||
|
up_write(&fc->killsb);
|
||||||
|
|
||||||
|
/* Create the submount */
|
||||||
|
mnt = vfs_create_mount(fsc);
|
||||||
|
if (IS_ERR(mnt)) {
|
||||||
|
err = PTR_ERR(mnt);
|
||||||
|
goto out_put_fsc;
|
||||||
|
}
|
||||||
|
mntget(mnt);
|
||||||
|
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:
|
||||||
|
return ERR_PTR(err);
|
||||||
|
}
|
||||||
|
|
||||||
const struct dentry_operations fuse_dentry_operations = {
|
const struct dentry_operations fuse_dentry_operations = {
|
||||||
.d_revalidate = fuse_dentry_revalidate,
|
.d_revalidate = fuse_dentry_revalidate,
|
||||||
.d_delete = fuse_dentry_delete,
|
.d_delete = fuse_dentry_delete,
|
||||||
|
@ -306,6 +381,7 @@ const struct dentry_operations fuse_dentry_operations = {
|
||||||
.d_init = fuse_dentry_init,
|
.d_init = fuse_dentry_init,
|
||||||
.d_release = fuse_dentry_release,
|
.d_release = fuse_dentry_release,
|
||||||
#endif
|
#endif
|
||||||
|
.d_automount = fuse_dentry_automount,
|
||||||
};
|
};
|
||||||
|
|
||||||
const struct dentry_operations fuse_root_dentry_operations = {
|
const struct dentry_operations fuse_root_dentry_operations = {
|
||||||
|
|
|
@ -742,6 +742,9 @@ struct fuse_conn {
|
||||||
/** Do not allow MNT_FORCE umount */
|
/** Do not allow MNT_FORCE umount */
|
||||||
unsigned int no_force_umount:1;
|
unsigned int no_force_umount:1;
|
||||||
|
|
||||||
|
/* Auto-mount submounts announced by the server */
|
||||||
|
unsigned int auto_submounts:1;
|
||||||
|
|
||||||
/** The number of requests waiting for completion */
|
/** The number of requests waiting for completion */
|
||||||
atomic_t num_waiting;
|
atomic_t num_waiting;
|
||||||
|
|
||||||
|
|
|
@ -309,7 +309,26 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
|
||||||
struct fuse_inode *fi;
|
struct fuse_inode *fi;
|
||||||
struct fuse_conn *fc = get_fuse_conn_super(sb);
|
struct fuse_conn *fc = get_fuse_conn_super(sb);
|
||||||
|
|
||||||
retry:
|
/*
|
||||||
|
* Auto mount points get their node id from the submount root, which is
|
||||||
|
* not a unique identifier within this filesystem.
|
||||||
|
*
|
||||||
|
* To avoid conflicts, do not place submount points into the inode hash
|
||||||
|
* table.
|
||||||
|
*/
|
||||||
|
if (fc->auto_submounts && (attr->flags & FUSE_ATTR_SUBMOUNT) &&
|
||||||
|
S_ISDIR(attr->mode)) {
|
||||||
|
inode = new_inode(sb);
|
||||||
|
if (!inode)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
fuse_init_inode(inode, attr);
|
||||||
|
get_fuse_inode(inode)->nodeid = nodeid;
|
||||||
|
inode->i_flags |= S_AUTOMOUNT;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
retry:
|
||||||
inode = iget5_locked(sb, nodeid, fuse_inode_eq, fuse_inode_set, &nodeid);
|
inode = iget5_locked(sb, nodeid, fuse_inode_eq, fuse_inode_set, &nodeid);
|
||||||
if (!inode)
|
if (!inode)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -327,7 +346,7 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
|
||||||
iput(inode);
|
iput(inode);
|
||||||
goto retry;
|
goto retry;
|
||||||
}
|
}
|
||||||
|
done:
|
||||||
fi = get_fuse_inode(inode);
|
fi = get_fuse_inode(inode);
|
||||||
spin_lock(&fi->lock);
|
spin_lock(&fi->lock);
|
||||||
fi->nlookup++;
|
fi->nlookup++;
|
||||||
|
@ -1083,6 +1102,9 @@ void fuse_send_init(struct fuse_mount *fm)
|
||||||
if (fm->fc->dax)
|
if (fm->fc->dax)
|
||||||
ia->in.flags |= FUSE_MAP_ALIGNMENT;
|
ia->in.flags |= FUSE_MAP_ALIGNMENT;
|
||||||
#endif
|
#endif
|
||||||
|
if (fm->fc->auto_submounts)
|
||||||
|
ia->in.flags |= FUSE_SUBMOUNTS;
|
||||||
|
|
||||||
ia->args.opcode = FUSE_INIT;
|
ia->args.opcode = FUSE_INIT;
|
||||||
ia->args.in_numargs = 1;
|
ia->args.in_numargs = 1;
|
||||||
ia->args.in_args[0].size = sizeof(ia->in);
|
ia->args.in_args[0].size = sizeof(ia->in);
|
||||||
|
|
|
@ -1431,6 +1431,7 @@ static int virtio_fs_get_tree(struct fs_context *fsc)
|
||||||
&virtio_fs_fiq_ops, fs);
|
&virtio_fs_fiq_ops, fs);
|
||||||
fc->release = fuse_free_conn;
|
fc->release = fuse_free_conn;
|
||||||
fc->delete_stale = true;
|
fc->delete_stale = true;
|
||||||
|
fc->auto_submounts = true;
|
||||||
|
|
||||||
fsc->s_fs_info = fm;
|
fsc->s_fs_info = fm;
|
||||||
sb = sget_fc(fsc, virtio_fs_test_super, virtio_fs_set_super);
|
sb = sget_fc(fsc, virtio_fs_test_super, virtio_fs_set_super);
|
||||||
|
|
Загрузка…
Ссылка в новой задаче