Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Max Reitz and committed by
Miklos Szeredi
bf109c64 1866d779

+105 -3
+77 -1
fs/fuse/dir.c
··· 10 10 11 11 #include <linux/pagemap.h> 12 12 #include <linux/file.h> 13 + #include <linux/fs_context.h> 13 14 #include <linux/sched.h> 14 15 #include <linux/namei.h> 15 16 #include <linux/slab.h> ··· 238 237 ret = -ENOENT; 239 238 if (!ret) { 240 239 fi = get_fuse_inode(inode); 241 - if (outarg.nodeid != get_node_id(inode)) { 240 + if (outarg.nodeid != get_node_id(inode) || 241 + (bool) IS_AUTOMOUNT(inode) != (bool) (outarg.attr.flags & FUSE_ATTR_SUBMOUNT)) { 242 242 fuse_queue_forget(fm->fc, forget, 243 243 outarg.nodeid, 1); 244 244 goto invalid; ··· 301 299 return time_before64(fuse_dentry_time(dentry), get_jiffies_64()); 302 300 } 303 301 302 + /* 303 + * Create a fuse_mount object with a new superblock (with path->dentry 304 + * as the root), and return that mount so it can be auto-mounted on 305 + * @path. 306 + */ 307 + static struct vfsmount *fuse_dentry_automount(struct path *path) 308 + { 309 + struct fs_context *fsc; 310 + struct fuse_mount *parent_fm = get_fuse_mount_super(path->mnt->mnt_sb); 311 + struct fuse_conn *fc = parent_fm->fc; 312 + struct fuse_mount *fm; 313 + struct vfsmount *mnt; 314 + struct fuse_inode *mp_fi = get_fuse_inode(d_inode(path->dentry)); 315 + struct super_block *sb; 316 + int err; 317 + 318 + fsc = fs_context_for_submount(path->mnt->mnt_sb->s_type, path->dentry); 319 + if (IS_ERR(fsc)) { 320 + err = PTR_ERR(fsc); 321 + goto out; 322 + } 323 + 324 + err = -ENOMEM; 325 + fm = kzalloc(sizeof(struct fuse_mount), GFP_KERNEL); 326 + if (!fm) 327 + goto out_put_fsc; 328 + 329 + refcount_set(&fm->count, 1); 330 + fsc->s_fs_info = fm; 331 + sb = sget_fc(fsc, NULL, set_anon_super_fc); 332 + if (IS_ERR(sb)) { 333 + err = PTR_ERR(sb); 334 + fuse_mount_put(fm); 335 + goto out_put_fsc; 336 + } 337 + fm->fc = fuse_conn_get(fc); 338 + 339 + /* Initialize superblock, making @mp_fi its root */ 340 + err = fuse_fill_super_submount(sb, mp_fi); 341 + if (err) 342 + goto out_put_sb; 343 + 344 + sb->s_flags |= SB_ACTIVE; 345 + fsc->root = dget(sb->s_root); 346 + /* We are done configuring the superblock, so unlock it */ 347 + up_write(&sb->s_umount); 348 + 349 + down_write(&fc->killsb); 350 + list_add_tail(&fm->fc_entry, &fc->mounts); 351 + up_write(&fc->killsb); 352 + 353 + /* Create the submount */ 354 + mnt = vfs_create_mount(fsc); 355 + if (IS_ERR(mnt)) { 356 + err = PTR_ERR(mnt); 357 + goto out_put_fsc; 358 + } 359 + mntget(mnt); 360 + put_fs_context(fsc); 361 + return mnt; 362 + 363 + out_put_sb: 364 + /* 365 + * Only jump here when fsc->root is NULL and sb is still locked 366 + * (otherwise put_fs_context() will put the superblock) 367 + */ 368 + deactivate_locked_super(sb); 369 + out_put_fsc: 370 + put_fs_context(fsc); 371 + out: 372 + return ERR_PTR(err); 373 + } 374 + 304 375 const struct dentry_operations fuse_dentry_operations = { 305 376 .d_revalidate = fuse_dentry_revalidate, 306 377 .d_delete = fuse_dentry_delete, ··· 381 306 .d_init = fuse_dentry_init, 382 307 .d_release = fuse_dentry_release, 383 308 #endif 309 + .d_automount = fuse_dentry_automount, 384 310 }; 385 311 386 312 const struct dentry_operations fuse_root_dentry_operations = {
+3
fs/fuse/fuse_i.h
··· 742 742 /** Do not allow MNT_FORCE umount */ 743 743 unsigned int no_force_umount:1; 744 744 745 + /* Auto-mount submounts announced by the server */ 746 + unsigned int auto_submounts:1; 747 + 745 748 /** The number of requests waiting for completion */ 746 749 atomic_t num_waiting; 747 750
+24 -2
fs/fuse/inode.c
··· 309 309 struct fuse_inode *fi; 310 310 struct fuse_conn *fc = get_fuse_conn_super(sb); 311 311 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: 313 332 inode = iget5_locked(sb, nodeid, fuse_inode_eq, fuse_inode_set, &nodeid); 314 333 if (!inode) 315 334 return NULL; ··· 346 327 iput(inode); 347 328 goto retry; 348 329 } 349 - 330 + done: 350 331 fi = get_fuse_inode(inode); 351 332 spin_lock(&fi->lock); 352 333 fi->nlookup++; ··· 1102 1083 if (fm->fc->dax) 1103 1084 ia->in.flags |= FUSE_MAP_ALIGNMENT; 1104 1085 #endif 1086 + if (fm->fc->auto_submounts) 1087 + ia->in.flags |= FUSE_SUBMOUNTS; 1088 + 1105 1089 ia->args.opcode = FUSE_INIT; 1106 1090 ia->args.in_numargs = 1; 1107 1091 ia->args.in_args[0].size = sizeof(ia->in);
+1
fs/fuse/virtio_fs.c
··· 1431 1431 &virtio_fs_fiq_ops, fs); 1432 1432 fc->release = fuse_free_conn; 1433 1433 fc->delete_stale = true; 1434 + fc->auto_submounts = true; 1434 1435 1435 1436 fsc->s_fs_info = fm; 1436 1437 sb = sget_fc(fsc, virtio_fs_test_super, virtio_fs_set_super);