diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 5ca3ebb36591e6..03096dbda777de 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -431,7 +431,8 @@ bool fuse_invalid_attr(struct fuse_attr *attr) return !fuse_valid_type(attr->mode) || !fuse_valid_size(attr->size); } -int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name, +int fuse_lookup_name(struct super_block *sb, dev_t sub_dev, + u64 nodeid, const struct qstr *name, struct fuse_entry_out *outarg, struct inode **inode) { struct fuse_mount *fm = get_fuse_mount_super(sb); @@ -468,7 +469,8 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name outarg->generation = 0; } - *inode = fuse_iget(sb, outarg->nodeid, outarg->generation, + *inode = fuse_iget(sb, sub_dev, + outarg->nodeid, outarg->generation, &outarg->attr, ATTR_TIMEOUT(outarg), attr_version, evict_ctr); err = -ENOMEM; @@ -498,7 +500,8 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, return ERR_PTR(-EIO); locked = fuse_lock_inode(dir); - err = fuse_lookup_name(dir->i_sb, get_node_id(dir), &entry->d_name, + err = fuse_lookup_name(dir->i_sb, get_sub_dev(dir), + get_node_id(dir), &entry->d_name, &outarg, &inode); fuse_unlock_inode(dir, locked); if (err == -ENOENT) { @@ -756,7 +759,8 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry, ff->fh = outopen.fh; ff->nodeid = outentry.nodeid; ff->open_flags = outopen.open_flags; - inode = fuse_iget(dir->i_sb, outentry.nodeid, outentry.generation, + inode = fuse_iget(dir->i_sb, get_sub_dev(dir), + outentry.nodeid, outentry.generation, &outentry.attr, ATTR_TIMEOUT(&outentry), 0, 0); if (!inode) { flags &= ~(O_CREAT | O_EXCL | O_TRUNC); @@ -886,8 +890,9 @@ static int create_new_entry(struct fuse_mount *fm, struct fuse_args *args, if ((outarg.attr.mode ^ mode) & S_IFMT) goto out_put_forget_req; - inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation, - &outarg.attr, ATTR_TIMEOUT(&outarg), 0, 0); + inode = fuse_iget(dir->i_sb, get_sub_dev(dir), outarg.nodeid, + outarg.generation, &outarg.attr, + ATTR_TIMEOUT(&outarg), 0, 0); if (!inode) { fuse_queue_forget(fm->fc, forget, outarg.nodeid, 1); return -ENOMEM; @@ -1207,8 +1212,9 @@ static void fuse_fillattr(struct inode *inode, struct fuse_attr *attr, { unsigned int blkbits; struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_inode *fi = get_fuse_inode(inode); - stat->dev = inode->i_sb->s_dev; + stat->dev = fi->sub_dev ? fi->sub_dev : inode->i_sb->s_dev; stat->ino = attr->ino; stat->mode = (inode->i_mode & S_IFMT) | (attr->mode & 07777); stat->nlink = attr->nlink; @@ -1446,6 +1452,8 @@ static int fuse_update_get_attr(struct inode *inode, struct file *file, generic_fillattr(&nop_mnt_idmap, sx_mask, inode, stat); stat->mode = fi->orig_i_mode; stat->ino = fi->orig_ino; + if (fi->sub_dev) + stat->dev = fi->sub_dev; if (test_bit(FUSE_I_BTIME, &fi->state)) { stat->btime = fi->i_btime; stat->result_mask |= STATX_BTIME; @@ -2247,8 +2255,9 @@ static int fuse_getattr(struct mnt_idmap *idmap, * If user explicitly requested *nothing* then don't * error out, but return st_dev only. */ + struct fuse_inode *fi = get_fuse_inode(inode); stat->result_mask = 0; - stat->dev = inode->i_sb->s_dev; + stat->dev = fi->sub_dev ? fi->sub_dev : inode->i_sb->s_dev; return 0; } return -EACCES; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 72aabf8dd5ff31..ce5ace9fe1ffde 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -136,6 +136,9 @@ struct fuse_inode { /** 64 bit inode number */ u64 orig_ino; + /** Device ID for sub-root (orig_ino == fc->subvol_root_ino) */ + dev_t sub_dev; + /** Version of last attribute change */ u64 attr_version; @@ -583,6 +586,9 @@ struct fuse_fs_context { unsigned int blksize; const char *subtype; + /* Inode number for subvolume-root */ + u64 subvol_root_ino; + /* DAX device, may be NULL */ struct dax_device *dax_dev; @@ -897,6 +903,9 @@ struct fuse_conn { /** Device ID from the root super block */ dev_t dev; + /** Inode number for subvolume (0 = disabled) */ + u64 subvol_root_ino; + /** Dentries in the control filesystem */ struct dentry *ctl_dentry[FUSE_CTL_NUM_DENTRIES]; @@ -1019,6 +1028,11 @@ static inline u64 get_node_id(struct inode *inode) return get_fuse_inode(inode)->nodeid; } +static inline dev_t get_sub_dev(struct inode *inode) +{ + return get_fuse_inode(inode)->sub_dev; +} + static inline int invalid_nodeid(u64 nodeid) { return !nodeid || nodeid == FUSE_ROOT_ID; @@ -1099,12 +1113,13 @@ extern const struct dentry_operations fuse_root_dentry_operations; /** * Get a filled in inode */ -struct inode *fuse_iget(struct super_block *sb, u64 nodeid, - int generation, struct fuse_attr *attr, - u64 attr_valid, u64 attr_version, - u64 evict_ctr); +struct inode *fuse_iget(struct super_block *sb, dev_t sub_dev, + u64 nodeid, int generation, + struct fuse_attr *attr, u64 attr_valid, + u64 attr_version, u64 evict_ctr); -int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name, +int fuse_lookup_name(struct super_block *sb, dev_t sub_dev, + u64 nodeid, const struct qstr *name, struct fuse_entry_out *outarg, struct inode **inode); /** diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index a2d496160da4c5..a17aebc3f6c2d7 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -153,6 +153,7 @@ static void fuse_cleanup_submount_lookup(struct fuse_conn *fc, static void fuse_evict_inode(struct inode *inode) { struct fuse_inode *fi = get_fuse_inode(inode); + struct fuse_conn *fc = get_fuse_conn_super(inode->i_sb); /* Will write inode on close/munmap and in all other dirtiers */ WARN_ON(inode->i_state & I_DIRTY_INODE); @@ -160,8 +161,6 @@ static void fuse_evict_inode(struct inode *inode) truncate_inode_pages_final(&inode->i_data); clear_inode(inode); if (inode->i_sb->s_flags & SB_ACTIVE) { - struct fuse_conn *fc = get_fuse_conn(inode); - if (FUSE_IS_DAX(inode)) fuse_dax_inode_cleanup(inode); if (fi->nlookup) { @@ -191,6 +190,13 @@ static void fuse_evict_inode(struct inode *inode) WARN_ON(!list_empty(&fi->queued_writes)); fuse_dlm_cache_release_locks(fi); } + /* + * Must be done here rather than in ->free_inode(), which is not + * ordered against superblock destruction and thus must not touch + * the fuse_conn. + */ + if (fi->sub_dev && fi->orig_ino == fc->subvol_root_ino) + free_anon_bdev(fi->sub_dev); } static int fuse_reconfigure(struct fs_context *fsc) @@ -340,8 +346,6 @@ static void fuse_change_attributes_common_sx(struct inode *inode, if (!fc->default_permissions) inode->i_mode &= ~S_ISVTX; - fi->orig_ino = attr->ino; - /* * We are refreshing inode data and it is possible that another * client set suid/sgid or security.capability xattr. So clear @@ -442,8 +446,6 @@ void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr, if (!fc->default_permissions) inode->i_mode &= ~S_ISVTX; - fi->orig_ino = attr->ino; - /* * We are refreshing inode data and it is possible that another * client set suid/sgid or security.capability xattr. So clear @@ -611,10 +613,10 @@ static int fuse_inode_set(struct inode *inode, void *_nodeidp) return 0; } -struct inode *fuse_iget(struct super_block *sb, u64 nodeid, - int generation, struct fuse_attr *attr, - u64 attr_valid, u64 attr_version, - u64 evict_ctr) +struct inode *fuse_iget(struct super_block *sb, dev_t sub_dev, + u64 nodeid, int generation, + struct fuse_attr *attr, u64 attr_valid, + u64 attr_version, u64 evict_ctr) { struct inode *inode; struct fuse_inode *fi; @@ -654,12 +656,21 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid, if (!inode) return NULL; + fi = get_fuse_inode(inode); if ((inode->i_state & I_NEW)) { inode->i_flags |= S_NOATIME; if (!fc->writeback_cache || !S_ISREG(attr->mode)) inode->i_flags |= S_NOCMTIME; inode->i_generation = generation; fuse_init_inode(inode, attr, fc); + fi->orig_ino = attr->ino; + if (fc->subvol_root_ino && fi->orig_ino == fc->subvol_root_ino) { + int err = get_anon_bdev(&fi->sub_dev); + if (err) + pr_warn("failed to alloc anon bdev\n"); + } else { + fi->sub_dev = sub_dev; + } unlock_new_inode(inode); } else if (fuse_stale_inode(inode, generation, attr)) { /* nodeid was reused, any I/O on the old inode should fail */ @@ -670,7 +681,6 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid, goto retry; } } - fi = get_fuse_inode(inode); spin_lock(&fi->lock); fi->nlookup++; spin_unlock(&fi->lock); @@ -984,6 +994,7 @@ enum { OPT_ALLOW_OTHER, OPT_MAX_READ, OPT_BLKSIZE, + OPT_SUBVOL_ROOT_INO, OPT_ERR }; @@ -998,6 +1009,7 @@ static const struct fs_parameter_spec fuse_fs_parameters[] = { fsparam_u32 ("max_read", OPT_MAX_READ), fsparam_u32 ("blksize", OPT_BLKSIZE), fsparam_string ("subtype", OPT_SUBTYPE), + fsparam_u64 ("subvol_root_ino", OPT_SUBVOL_ROOT_INO), {} }; @@ -1097,6 +1109,10 @@ static int fuse_parse_param(struct fs_context *fsc, struct fs_parameter *param) ctx->blksize = result.uint_32; break; + case OPT_SUBVOL_ROOT_INO: + ctx->subvol_root_ino = result.uint_64; + break; + default: return -EINVAL; } @@ -1262,7 +1278,7 @@ static struct inode *fuse_get_root_inode(struct super_block *sb, unsigned mode) attr.mode = mode; attr.ino = FUSE_ROOT_ID; attr.nlink = 1; - return fuse_iget(sb, FUSE_ROOT_ID, 0, &attr, 0, 0, 0); + return fuse_iget(sb, 0, FUSE_ROOT_ID, 0, &attr, 0, 0, 0); } struct fuse_inode_handle { @@ -1289,8 +1305,8 @@ static struct dentry *fuse_get_dentry(struct super_block *sb, if (!fc->export_support) goto out_err; - err = fuse_lookup_name(sb, handle->nodeid, &name, &outarg, - &inode); + err = fuse_lookup_name(sb, 0, handle->nodeid, &name, + &outarg, &inode); if (err && err != -ENOENT) goto out_err; if (err || !inode) { @@ -1390,7 +1406,7 @@ static struct dentry *fuse_get_parent(struct dentry *child) if (!fc->export_support) return ERR_PTR(-ESTALE); - err = fuse_lookup_name(child_inode->i_sb, get_node_id(child_inode), + err = fuse_lookup_name(child_inode->i_sb, 0, get_node_id(child_inode), &dotdot_name, &outarg, &inode); if (err) { if (err == -ENOENT) @@ -1524,8 +1540,8 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args, if (flags & FUSE_ATOMIC_O_TRUNC) fc->atomic_o_trunc = 1; if (arg->minor >= 9) { - /* LOOKUP has dependency on proto version */ - if (flags & FUSE_EXPORT_SUPPORT) + if ((flags & FUSE_EXPORT_SUPPORT) && + !fc->subvol_root_ino) fc->export_support = 1; } if (flags & FUSE_BIG_WRITES) @@ -1674,6 +1690,12 @@ static struct fuse_init_args *fuse_new_init(struct fuse_mount *fm) if (fuse_uring_enabled()) flags |= FUSE_OVER_IO_URING; + if (fm->fc->subvol_root_ino) { + pr_warn("subvol_root_ino=%llu, disabling export_support\n", + fm->fc->subvol_root_ino); + flags &= ~FUSE_EXPORT_SUPPORT; + } + ia->in.flags = flags; ia->in.flags2 = flags >> 32; @@ -1890,7 +1912,7 @@ static int fuse_fill_super_submount(struct super_block *sb, return -ENOMEM; fuse_fill_attr_from_inode(&root_attr, parent_fi); - root = fuse_iget(sb, parent_fi->nodeid, 0, &root_attr, 0, 0, + root = fuse_iget(sb, 0, parent_fi->nodeid, 0, &root_attr, 0, 0, fuse_get_evict_ctr(fm->fc)); /* * This inode is just a duplicate, so it is not looked up and @@ -2032,6 +2054,7 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx) fc->destroy = ctx->destroy; fc->no_control = ctx->no_control; fc->no_force_umount = ctx->no_force_umount; + fc->subvol_root_ino = ctx->subvol_root_ino; err = -ENOMEM; root = fuse_get_root_inode(sb, ctx->rootmode); diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c index ef231156599099..892453614889d3 100644 --- a/fs/fuse/readdir.c +++ b/fs/fuse/readdir.c @@ -231,7 +231,8 @@ static int fuse_direntplus_link(struct file *file, * which bumps nlookup inside */ } else { - inode = fuse_iget(dir->i_sb, o->nodeid, o->generation, + inode = fuse_iget(dir->i_sb, get_sub_dev(dir), + o->nodeid, o->generation, &o->attr, ATTR_TIMEOUT(o), attr_version, evict_ctr); if (!inode)