Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions fs/fuse/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
25 changes: 20 additions & 5 deletions fs/fuse/fuse_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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];

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);

/**
Expand Down
56 changes: 40 additions & 16 deletions fs/fuse/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -191,6 +192,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)
Expand Down Expand Up @@ -340,8 +348,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
Expand Down Expand Up @@ -442,8 +448,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
Expand Down Expand Up @@ -611,10 +615,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;
Expand Down Expand Up @@ -654,12 +658,20 @@ 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(&get_fuse_inode(inode)->sub_dev);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since fi moved further up, I think we could use it to simplify this line to
int err = get_anon_bdev(fi->sub_dev);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, it looks like we only set sub_dev when the state is new. but if a process were to hold a file open while another process evicts the parent, i /think/ the child stays cached with a stale sub_dev until it closes

if (err)
pr_warn("failed to alloc anon bdev\n");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we get to this error condition, i think fi->sub_dev is still unallocated, and err should be the errno. Should we return err here rather than continue with a null fi->sub_dev ?

} else
fi->sub_dev = sub_dev;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkpatch nits: this "else" should have curly braces since the "if" arm has them

unlock_new_inode(inode);
} else if (fuse_stale_inode(inode, generation, attr)) {
/* nodeid was reused, any I/O on the old inode should fail */
Expand All @@ -670,7 +682,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);
Expand Down Expand Up @@ -984,6 +995,7 @@ enum {
OPT_ALLOW_OTHER,
OPT_MAX_READ,
OPT_BLKSIZE,
OPT_SUBVOL_ROOT_INO,
OPT_ERR
};

Expand All @@ -998,6 +1010,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),
{}
};

Expand Down Expand Up @@ -1097,6 +1110,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;
}
Expand Down Expand Up @@ -1262,7 +1279,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 {
Expand All @@ -1289,8 +1306,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) {
Expand Down Expand Up @@ -1390,7 +1407,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)
Expand Down Expand Up @@ -1524,8 +1541,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)
Expand Down Expand Up @@ -1674,6 +1691,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;

Expand Down Expand Up @@ -1890,7 +1913,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
Expand Down Expand Up @@ -2032,6 +2055,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);
Expand Down
3 changes: 2 additions & 1 deletion fs/fuse/readdir.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down