fuse_lookup_name() requires a struct fuse_entry_out to be passed in. However, the only caller that really needs it is fuse_lookup(). And even this function only cares about the dentry time. This patch simplifies fuse_lookup_name() so that it doesn't require a struct as a parameter, replacing it by a local variable. Instead, there'll be an (optional) out argument, that can be used to return the dentry time. Signed-off-by: Luis Henriques --- fs/fuse/dir.c | 36 +++++++++++++++++++----------------- fs/fuse/fuse_i.h | 2 +- fs/fuse/inode.c | 7 ++----- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index ef297b867060..e3000affff88 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -548,10 +548,11 @@ bool fuse_invalid_attr(struct fuse_attr *attr) } int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name, - struct fuse_entry_out *outarg, struct inode **inode) + u64 *time, struct inode **inode) { struct fuse_mount *fm = get_fuse_mount_super(sb); FUSE_ARGS(args); + struct fuse_entry_out outarg; struct fuse_forget_link *forget; u64 attr_version, evict_ctr; int err; @@ -570,30 +571,34 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name attr_version = fuse_get_attr_version(fm->fc); evict_ctr = fuse_get_evict_ctr(fm->fc); - fuse_lookup_init(fm->fc, &args, nodeid, name, outarg); + fuse_lookup_init(fm->fc, &args, nodeid, name, &outarg); err = fuse_simple_request(fm, &args); /* Zero nodeid is same as -ENOENT, but with valid timeout */ - if (err || !outarg->nodeid) + if (err || !outarg.nodeid) goto out_put_forget; err = -EIO; - if (fuse_invalid_attr(&outarg->attr)) + if (fuse_invalid_attr(&outarg.attr)) goto out_put_forget; - if (outarg->nodeid == FUSE_ROOT_ID && outarg->generation != 0) { + if (outarg.nodeid == FUSE_ROOT_ID && outarg.generation != 0) { pr_warn_once("root generation should be zero\n"); - outarg->generation = 0; + outarg.generation = 0; } - *inode = fuse_iget(sb, outarg->nodeid, outarg->generation, - &outarg->attr, ATTR_TIMEOUT(outarg), + *inode = fuse_iget(sb, outarg.nodeid, outarg.generation, + &outarg.attr, ATTR_TIMEOUT(&outarg), attr_version, evict_ctr); err = -ENOMEM; if (!*inode) { - fuse_queue_forget(fm->fc, forget, outarg->nodeid, 1); + fuse_queue_forget(fm->fc, forget, outarg.nodeid, 1); goto out; } err = 0; + if (time) + *time = fuse_time_to_jiffies(outarg.entry_valid, + outarg.entry_valid_nsec); + out_put_forget: kfree(forget); out: @@ -603,12 +608,11 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, unsigned int flags) { - struct fuse_entry_out outarg; struct fuse_conn *fc; struct inode *inode; struct dentry *newent; + u64 time = 0; int err, epoch; - bool outarg_valid = true; bool locked; if (fuse_is_bad(dir)) @@ -619,12 +623,10 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, locked = fuse_lock_inode(dir); err = fuse_lookup_name(dir->i_sb, get_node_id(dir), &entry->d_name, - &outarg, &inode); + &time, &inode); fuse_unlock_inode(dir, locked); - if (err == -ENOENT) { - outarg_valid = false; + if (err == -ENOENT) err = 0; - } if (err) goto out_err; @@ -639,8 +641,8 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, entry = newent ? newent : entry; entry->d_time = epoch; - if (outarg_valid) - fuse_change_entry_timeout(entry, &outarg); + if (time) + fuse_dentry_settime(entry, time); else fuse_invalidate_entry_cache(entry); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 3184ef864cf0..6178a012f36c 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1149,7 +1149,7 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid, u64 evict_ctr); int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name, - struct fuse_entry_out *outarg, struct inode **inode); + u64 *time, struct inode **inode); /** * Send FORGET command diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 38ca362ee2ca..8231c207abea 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1089,14 +1089,12 @@ static struct dentry *fuse_get_dentry(struct super_block *sb, inode = ilookup5(sb, handle->nodeid, fuse_inode_eq, &handle->nodeid); if (!inode) { - struct fuse_entry_out outarg; const struct qstr name = QSTR_INIT(".", 1); if (!fc->export_support) goto out_err; - err = fuse_lookup_name(sb, handle->nodeid, &name, &outarg, - &inode); + err = fuse_lookup_name(sb, handle->nodeid, &name, NULL, &inode); if (err && err != -ENOENT) goto out_err; if (err || !inode) { @@ -1190,14 +1188,13 @@ static struct dentry *fuse_get_parent(struct dentry *child) struct fuse_conn *fc = get_fuse_conn(child_inode); struct inode *inode; struct dentry *parent; - struct fuse_entry_out outarg; int err; if (!fc->export_support) return ERR_PTR(-ESTALE); err = fuse_lookup_name(child_inode->i_sb, get_node_id(child_inode), - &dotdot_name, &outarg, &inode); + &dotdot_name, NULL, &inode); if (err) { if (err == -ENOENT) return ERR_PTR(-ESTALE); Export (and rename) extend_arg() and fuse_ext_size() as these functions are useful for using extension headers in other places. Signed-off-by: Luis Henriques --- fs/fuse/dir.c | 9 ++------- fs/fuse/fuse_i.h | 7 +++++++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index e3000affff88..f5eacea44896 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -713,7 +713,7 @@ static int get_security_context(struct dentry *entry, umode_t mode, return err; } -static void *extend_arg(struct fuse_in_arg *buf, u32 bytes) +void *fuse_extend_arg(struct fuse_in_arg *buf, u32 bytes) { void *p; u32 newlen = buf->size + bytes; @@ -733,11 +733,6 @@ static void *extend_arg(struct fuse_in_arg *buf, u32 bytes) return p + newlen - bytes; } -static u32 fuse_ext_size(size_t size) -{ - return FUSE_REC_ALIGN(sizeof(struct fuse_ext_header) + size); -} - /* * This adds just a single supplementary group that matches the parent's group. */ @@ -758,7 +753,7 @@ static int get_create_supp_group(struct mnt_idmap *idmap, !vfsgid_in_group_p(vfsgid)) return 0; - xh = extend_arg(ext, sg_len); + xh = fuse_extend_arg(ext, sg_len); if (!xh) return -ENOMEM; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 6178a012f36c..135027efec7a 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1410,6 +1410,13 @@ int fuse_valid_type(int m); bool fuse_invalid_attr(struct fuse_attr *attr); +void *fuse_extend_arg(struct fuse_in_arg *buf, u32 bytes); + +static inline u32 fuse_ext_size(size_t size) +{ + return FUSE_REC_ALIGN(sizeof(struct fuse_ext_header) + size); +} + /** * Is current process allowed to perform filesystem operation? */ Operations that have a variable length argument assume that it will always be the last argument on the list. This patch allows this assumption to be removed by keeping track of the index of variable length argument. Signed-off-by: Luis Henriques --- fs/fuse/compound.c | 1 + fs/fuse/cuse.c | 1 + fs/fuse/dev.c | 4 ++-- fs/fuse/dir.c | 1 + fs/fuse/file.c | 1 + fs/fuse/fuse_i.h | 2 ++ fs/fuse/inode.c | 1 + fs/fuse/ioctl.c | 1 + fs/fuse/virtio_fs.c | 6 +++--- fs/fuse/xattr.c | 2 ++ 10 files changed, 15 insertions(+), 5 deletions(-) diff --git a/fs/fuse/compound.c b/fs/fuse/compound.c index 1a85209f4e99..2dc024301aad 100644 --- a/fs/fuse/compound.c +++ b/fs/fuse/compound.c @@ -153,6 +153,7 @@ ssize_t fuse_compound_send(struct fuse_compound_req *compound) .in_numargs = 2, .out_numargs = 2, .out_argvar = true, + .out_argvar_idx = 1, }; unsigned int req_count = compound->compound_header.count; size_t total_expected_out_size = 0; diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c index dfcb98a654d8..3ce8ee9a4275 100644 --- a/fs/fuse/cuse.c +++ b/fs/fuse/cuse.c @@ -460,6 +460,7 @@ static int cuse_send_init(struct cuse_conn *cc) ap->args.out_args[0].value = &ia->out; ap->args.out_args[1].size = CUSE_INIT_INFO_MAX; ap->args.out_argvar = true; + ap->args.out_argvar_idx = 1; ap->args.out_pages = true; ap->num_folios = 1; ap->folios = &ia->folio; diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 0b0241f47170..5b02724f4377 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -694,7 +694,7 @@ ssize_t __fuse_simple_request(struct mnt_idmap *idmap, ret = req->out.h.error; if (!ret && args->out_argvar) { BUG_ON(args->out_numargs == 0); - ret = args->out_args[args->out_numargs - 1].size; + ret = args->out_args[args->out_argvar_idx].size; } fuse_put_request(req); @@ -2157,7 +2157,7 @@ int fuse_copy_out_args(struct fuse_copy_state *cs, struct fuse_args *args, if (reqsize < nbytes || (reqsize > nbytes && !args->out_argvar)) return -EINVAL; else if (reqsize > nbytes) { - struct fuse_arg *lastarg = &args->out_args[args->out_numargs-1]; + struct fuse_arg *lastarg = &args->out_args[args->out_argvar_idx]; unsigned diffsize = reqsize - nbytes; if (diffsize > lastarg->size) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index f5eacea44896..a1121feb63ee 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1835,6 +1835,7 @@ static int fuse_readlink_folio(struct inode *inode, struct folio *folio) ap.args.nodeid = get_node_id(inode); ap.args.out_pages = true; ap.args.out_argvar = true; + ap.args.out_argvar_idx = 0; ap.args.page_zeroing = true; ap.args.out_numargs = 1; ap.args.out_args[0].size = desc.length; diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 49c21498230d..1045d74dd95f 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -682,6 +682,7 @@ void fuse_read_args_fill(struct fuse_io_args *ia, struct file *file, loff_t pos, args->in_args[0].size = sizeof(ia->read.in); args->in_args[0].value = &ia->read.in; args->out_argvar = true; + args->out_argvar_idx = 0; args->out_numargs = 1; args->out_args[0].size = count; } diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 135027efec7a..04f09e2ccfd0 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -331,6 +331,8 @@ struct fuse_args { uint32_t opcode; uint8_t in_numargs; uint8_t out_numargs; + /* The index of the variable length out arg */ + uint8_t out_argvar_idx; uint8_t ext_idx; bool force:1; bool noreply:1; diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 8231c207abea..006436a3ad06 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1540,6 +1540,7 @@ static struct fuse_init_args *fuse_new_init(struct fuse_mount *fm) with interface version < 7.5. Rest of init_out is zeroed by do_get_request(), so a short reply is not a problem */ ia->args.out_argvar = true; + ia->args.out_argvar_idx = 0; ia->args.out_args[0].size = sizeof(ia->out); ia->args.out_args[0].value = &ia->out; ia->args.force = true; diff --git a/fs/fuse/ioctl.c b/fs/fuse/ioctl.c index 07a02e47b2c3..7eb8d7a59edc 100644 --- a/fs/fuse/ioctl.c +++ b/fs/fuse/ioctl.c @@ -337,6 +337,7 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg, ap.args.out_args[1].size = out_size; ap.args.out_pages = true; ap.args.out_argvar = true; + ap.args.out_argvar_idx = 1; transferred = fuse_send_ioctl(fm, &ap.args, &outarg); err = transferred; diff --git a/fs/fuse/virtio_fs.c b/fs/fuse/virtio_fs.c index 057e65b51b99..dd681bc672b8 100644 --- a/fs/fuse/virtio_fs.c +++ b/fs/fuse/virtio_fs.c @@ -738,7 +738,7 @@ static void copy_args_from_argbuf(struct fuse_args *args, struct fuse_req *req) unsigned int argsize = args->out_args[i].size; if (args->out_argvar && - i == args->out_numargs - 1 && + i == args->out_argvar_idx && argsize > remaining) { argsize = remaining; } @@ -746,13 +746,13 @@ static void copy_args_from_argbuf(struct fuse_args *args, struct fuse_req *req) memcpy(args->out_args[i].value, req->argbuf + offset, argsize); offset += argsize; - if (i != args->out_numargs - 1) + if (i != args->out_argvar_idx) remaining -= argsize; } /* Store the actual size of the variable-length arg */ if (args->out_argvar) - args->out_args[args->out_numargs - 1].size = remaining; + args->out_args[args->out_argvar_idx].size = remaining; kfree(req->argbuf); req->argbuf = NULL; diff --git a/fs/fuse/xattr.c b/fs/fuse/xattr.c index 93dfb06b6cea..f123446fe537 100644 --- a/fs/fuse/xattr.c +++ b/fs/fuse/xattr.c @@ -73,6 +73,7 @@ ssize_t fuse_getxattr(struct inode *inode, const char *name, void *value, args.out_numargs = 1; if (size) { args.out_argvar = true; + args.out_argvar_idx = 0; args.out_args[0].size = size; args.out_args[0].value = value; } else { @@ -135,6 +136,7 @@ ssize_t fuse_listxattr(struct dentry *entry, char *list, size_t size) args.out_numargs = 1; if (size) { args.out_argvar = true; + args.out_argvar_idx = 0; args.out_args[0].size = size; args.out_args[0].value = list; } else { Remove the fuse_conn argument from function fuse_lookup_init() as it isn't used since commit 21f621741a77 ("fuse: fix LOOKUP vs INIT compat handling"). Signed-off-by: Luis Henriques --- fs/fuse/dir.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index a1121feb63ee..92c9ebfb4985 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -354,8 +354,8 @@ static void fuse_invalidate_entry(struct dentry *entry) fuse_invalidate_entry_cache(entry); } -static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args, - u64 nodeid, const struct qstr *name, +static void fuse_lookup_init(struct fuse_args *args, u64 nodeid, + const struct qstr *name, struct fuse_entry_out *outarg) { memset(outarg, 0, sizeof(struct fuse_entry_out)); @@ -421,8 +421,7 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name, attr_version = fuse_get_attr_version(fm->fc); - fuse_lookup_init(fm->fc, &args, get_node_id(dir), - name, &outarg); + fuse_lookup_init(&args, get_node_id(dir), name, &outarg); ret = fuse_simple_request(fm, &args); /* Zero nodeid is same as -ENOENT */ if (!ret && !outarg.nodeid) @@ -571,7 +570,7 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name attr_version = fuse_get_attr_version(fm->fc); evict_ctr = fuse_get_evict_ctr(fm->fc); - fuse_lookup_init(fm->fc, &args, nodeid, name, &outarg); + fuse_lookup_init(&args, nodeid, name, &outarg); err = fuse_simple_request(fm, &args); /* Zero nodeid is same as -ENOENT, but with valid timeout */ if (err || !outarg.nodeid) Split function fuse_do_statx(), so that we get two helper functions: one to initialise the arguments and another to update the attributes and statistics. This will allow compound operations to re-use these two helpers. Signed-off-by: Luis Henriques --- fs/fuse/dir.c | 88 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 92c9ebfb4985..5c0f1364c392 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1406,43 +1406,37 @@ static void fuse_statx_to_attr(struct fuse_statx *sx, struct fuse_attr *attr) attr->blksize = sx->blksize; } -static int fuse_do_statx(struct mnt_idmap *idmap, struct inode *inode, - struct file *file, struct kstat *stat) +static void fuse_statx_init(struct fuse_args *args, struct fuse_statx_in *inarg, + u64 nodeid, struct fuse_file *ff, + struct fuse_statx_out *outarg) { - int err; - struct fuse_attr attr; - struct fuse_statx *sx; - struct fuse_statx_in inarg; - struct fuse_statx_out outarg; - struct fuse_mount *fm = get_fuse_mount(inode); - u64 attr_version = fuse_get_attr_version(fm->fc); - FUSE_ARGS(args); - - memset(&inarg, 0, sizeof(inarg)); - memset(&outarg, 0, sizeof(outarg)); - /* Directories have separate file-handle space */ - if (file && S_ISREG(inode->i_mode)) { - struct fuse_file *ff = file->private_data; + memset(inarg, 0, sizeof(*inarg)); + memset(outarg, 0, sizeof(*outarg)); - inarg.getattr_flags |= FUSE_GETATTR_FH; - inarg.fh = ff->fh; + if (ff) { + inarg->getattr_flags |= FUSE_GETATTR_FH; + inarg->fh = ff->fh; } /* For now leave sync hints as the default, request all stats. */ - inarg.sx_flags = 0; - inarg.sx_mask = STATX_BASIC_STATS | STATX_BTIME; - args.opcode = FUSE_STATX; - args.nodeid = get_node_id(inode); - args.in_numargs = 1; - args.in_args[0].size = sizeof(inarg); - args.in_args[0].value = &inarg; - args.out_numargs = 1; - args.out_args[0].size = sizeof(outarg); - args.out_args[0].value = &outarg; - err = fuse_simple_request(fm, &args); - if (err) - return err; + inarg->sx_flags = 0; + inarg->sx_mask = STATX_BASIC_STATS | STATX_BTIME; + args->opcode = FUSE_STATX; + args->nodeid = nodeid; + args->in_numargs = 1; + args->in_args[0].size = sizeof(*inarg); + args->in_args[0].value = inarg; + args->out_numargs = 1; + args->out_args[0].size = sizeof(*outarg); + args->out_args[0].value = outarg; +} + +static int fuse_statx_update(struct mnt_idmap *idmap, + struct fuse_statx_out *outarg, struct inode *inode, + u64 attr_version, struct kstat *stat) +{ + struct fuse_statx *sx = &outarg->stat; + struct fuse_attr attr; - sx = &outarg.stat; if (((sx->mask & STATX_SIZE) && !fuse_valid_size(sx->size)) || ((sx->mask & STATX_TYPE) && (!fuse_valid_type(sx->mode) || inode_wrong_type(inode, sx->mode)))) { @@ -1450,10 +1444,10 @@ static int fuse_do_statx(struct mnt_idmap *idmap, struct inode *inode, return -EIO; } - fuse_statx_to_attr(&outarg.stat, &attr); + fuse_statx_to_attr(sx, &attr); if ((sx->mask & STATX_BASIC_STATS) == STATX_BASIC_STATS) { - fuse_change_attributes(inode, &attr, &outarg.stat, - ATTR_TIMEOUT(&outarg), attr_version); + fuse_change_attributes(inode, &attr, sx, + ATTR_TIMEOUT(outarg), attr_version); } if (stat) { @@ -1467,6 +1461,30 @@ static int fuse_do_statx(struct mnt_idmap *idmap, struct inode *inode, return 0; } +static int fuse_do_statx(struct mnt_idmap *idmap, struct inode *inode, + struct file *file, struct kstat *stat) +{ + int err; + struct fuse_statx_in inarg; + struct fuse_statx_out outarg; + struct fuse_mount *fm = get_fuse_mount(inode); + struct fuse_file *ff = NULL; + u64 attr_version = fuse_get_attr_version(fm->fc); + FUSE_ARGS(args); + + /* Directories have separate file-handle space */ + if (file && S_ISREG(inode->i_mode)) + ff = file->private_data; + + fuse_statx_init(&args, &inarg, get_node_id(inode), ff, &outarg); + + err = fuse_simple_request(fm, &args); + if (err) + return err; + + return fuse_statx_update(idmap, &outarg, inode, attr_version, stat); +} + /* * Helper function to initialize fuse_args for GETATTR operations */ The implementation of lookup_handle+statx compound operation extends the lookup operation so that a file handle is be passed into the kernel. It also needs to include an extra inarg, so that the parent directory file handle can be sent to user-space. This extra inarg is added as an extension header to the request. By having a separate statx including in a compound operation allows the attr to be dropped from the lookup_handle request, simplifying the traditional FUSE lookup operation. Signed-off-by: Luis Henriques --- fs/fuse/dir.c | 294 +++++++++++++++++++++++++++++++++++--- fs/fuse/fuse_i.h | 23 ++- fs/fuse/inode.c | 48 +++++-- fs/fuse/readdir.c | 2 +- include/uapi/linux/fuse.h | 23 ++- 5 files changed, 355 insertions(+), 35 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 5c0f1364c392..7fa8c405f1a3 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -21,6 +21,7 @@ #include #include #include +#include static bool __read_mostly allow_sys_admin_access; module_param(allow_sys_admin_access, bool, 0644); @@ -372,6 +373,47 @@ static void fuse_lookup_init(struct fuse_args *args, u64 nodeid, args->out_args[0].value = outarg; } +static int do_lookup_handle_statx(struct fuse_mount *fm, u64 parent_nodeid, + struct inode *parent_inode, + const struct qstr *name, + struct fuse_entry2_out *lookup_out, + struct fuse_statx_out *statx_out, + struct fuse_file_handle **fh); +static void fuse_statx_to_attr(struct fuse_statx *sx, struct fuse_attr *attr); +static int do_reval_lookup(struct fuse_mount *fm, u64 parent_nodeid, + const struct qstr *name, u64 *nodeid, + u64 *generation, u64 *attr_valid, + struct fuse_attr *attr, struct fuse_file_handle **fh) +{ + struct fuse_entry_out entry_out; + struct fuse_entry2_out lookup_out; + struct fuse_statx_out statx_out; + FUSE_ARGS(lookup_args); + int ret = 0; + + if (fm->fc->lookup_handle) { + ret = do_lookup_handle_statx(fm, parent_nodeid, NULL, name, + &lookup_out, &statx_out, fh); + if (!ret) { + *nodeid = lookup_out.nodeid; + *generation = lookup_out.generation; + *attr_valid = fuse_time_to_jiffies(lookup_out.entry_valid, + lookup_out.entry_valid_nsec); + fuse_statx_to_attr(&statx_out.stat, attr); + } + } else { + fuse_lookup_init(&lookup_args, parent_nodeid, name, &entry_out); + ret = fuse_simple_request(fm, &lookup_args); + if (!ret) { + *nodeid = entry_out.nodeid; + *generation = entry_out.generation; + *attr_valid = ATTR_TIMEOUT(&entry_out); + memcpy(attr, &entry_out.attr, sizeof(*attr)); + } + } + + return ret; +} /* * Check whether the dentry is still valid * @@ -399,10 +441,11 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name, goto invalid; else if (time_before64(fuse_dentry_time(entry), get_jiffies_64()) || (flags & (LOOKUP_EXCL | LOOKUP_REVAL | LOOKUP_RENAME_TARGET))) { - struct fuse_entry_out outarg; - FUSE_ARGS(args); struct fuse_forget_link *forget; + struct fuse_file_handle *fh = NULL; u64 attr_version; + u64 nodeid, generation, attr_valid; + struct fuse_attr attr; /* For negative dentries, always do a fresh lookup */ if (!inode) @@ -421,35 +464,36 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name, attr_version = fuse_get_attr_version(fm->fc); - fuse_lookup_init(&args, get_node_id(dir), name, &outarg); - ret = fuse_simple_request(fm, &args); + ret = do_reval_lookup(fm, get_node_id(dir), name, &nodeid, + &generation, &attr_valid, &attr, &fh); /* Zero nodeid is same as -ENOENT */ - if (!ret && !outarg.nodeid) + if (!ret && !nodeid) ret = -ENOENT; if (!ret) { fi = get_fuse_inode(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, - outarg.nodeid, 1); + if (!fuse_file_handle_is_equal(fm->fc, fi->fh, fh) || + nodeid != get_node_id(inode) || + (bool) IS_AUTOMOUNT(inode) != (bool) (attr.flags & FUSE_ATTR_SUBMOUNT)) { + fuse_queue_forget(fm->fc, forget, nodeid, 1); + kfree(fh); goto invalid; } spin_lock(&fi->lock); fi->nlookup++; spin_unlock(&fi->lock); } + kfree(fh); kfree(forget); if (ret == -ENOMEM || ret == -EINTR) goto out; - if (ret || fuse_invalid_attr(&outarg.attr) || - fuse_stale_inode(inode, outarg.generation, &outarg.attr)) + if (ret || fuse_invalid_attr(&attr) || + fuse_stale_inode(inode, generation, &attr)) goto invalid; forget_all_cached_acls(inode); - fuse_change_attributes(inode, &outarg.attr, NULL, - ATTR_TIMEOUT(&outarg), + fuse_change_attributes(inode, &attr, NULL, attr_valid, attr_version); - fuse_change_entry_timeout(entry, &outarg); + fuse_dentry_settime(entry, attr_valid); } else if (inode) { fi = get_fuse_inode(inode); if (flags & LOOKUP_RCU) { @@ -546,8 +590,215 @@ 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, - u64 *time, struct inode **inode) +static int create_ext_handle(struct fuse_in_arg *ext, struct fuse_inode *fi) +{ + struct fuse_ext_header *xh; + struct fuse_file_handle *fh; + u32 len; + + len = fuse_ext_size(sizeof(*fi->fh) + fi->fh->size); + xh = fuse_extend_arg(ext, len); + if (!xh) + return -ENOMEM; + + xh->size = len; + xh->type = FUSE_EXT_HANDLE; + fh = (struct fuse_file_handle *)&xh[1]; + fh->size = fi->fh->size; + memcpy(fh->handle, fi->fh->handle, fh->size); + + return 0; +} + +static int fuse_lookup_handle_init(struct fuse_args *args, u64 nodeid, + struct fuse_inode *fi, + const struct qstr *name, + struct fuse_entry2_out *outarg) +{ + struct fuse_file_handle *fh; + size_t fh_size = sizeof(*fh) + MAX_HANDLE_SZ; + int ret = -ENOMEM; + + fh = kzalloc(fh_size, GFP_KERNEL); + if (!fh) + return ret; + + memset(outarg, 0, sizeof(struct fuse_entry2_out)); + args->opcode = FUSE_LOOKUP_HANDLE; + args->nodeid = nodeid; + args->in_numargs = 3; + fuse_set_zero_arg0(args); + args->in_args[1].size = name->len; + args->in_args[1].value = name->name; + args->in_args[2].size = 1; + args->in_args[2].value = ""; + if (fi && fi->fh) { + args->is_ext = true; + args->ext_idx = args->in_numargs++; + args->in_args[args->ext_idx].size = 0; + ret = create_ext_handle(&args->in_args[args->ext_idx], fi); + if (ret) { + kfree(fh); + return ret; + } + } + args->out_numargs = 2; + args->out_argvar = true; + args->out_argvar_idx = 1; + args->out_args[0].size = sizeof(struct fuse_entry2_out); + args->out_args[0].value = outarg; + + /* XXX do allocation to the actual size of the handle */ + args->out_args[1].size = fh_size; + args->out_args[1].value = fh; + + return 0; +} + +static void fuse_req_free_argvar_ext(struct fuse_args *args) +{ + if (args->out_argvar) + kfree(args->out_args[args->out_argvar_idx].value); + if (args->is_ext) + kfree(args->in_args[args->ext_idx].value); +} + +static void fuse_statx_init(struct fuse_args *args, struct fuse_statx_in *inarg, + u64 nodeid, struct fuse_file *ff, + struct fuse_statx_out *outarg); +static int fuse_statx_update(struct mnt_idmap *idmap, + struct fuse_statx_out *outarg, struct inode *inode, + u64 attr_version, struct kstat *stat); +static int do_lookup_handle_statx(struct fuse_mount *fm, u64 parent_nodeid, + struct inode *parent_inode, + const struct qstr *name, + struct fuse_entry2_out *lookup_out, + struct fuse_statx_out *statx_out, + struct fuse_file_handle **fh) +{ + struct fuse_compound_req *compound; + struct fuse_inode *fi = NULL; + struct fuse_statx_in statx_in; + FUSE_ARGS(lookup_args); + FUSE_ARGS(statx_args); + int ret; + + if (parent_inode) + fi = get_fuse_inode(parent_inode); + + compound = fuse_compound_alloc(fm, 0); + if (!compound) + return -ENOMEM; + + ret = fuse_lookup_handle_init(&lookup_args, parent_nodeid, fi, + name, lookup_out); + if (ret) + goto out_compound; + + ret = fuse_compound_add(compound, &lookup_args); + if (ret) + goto out_args; + + /* + * XXX nodeid is the parent of the inode we want! At this point + * we still don't have the nodeid. Using FUSE_ROOT_ID for now. + */ + fuse_statx_init(&statx_args, &statx_in, FUSE_ROOT_ID, NULL, statx_out); + ret = fuse_compound_add(compound, &statx_args); + if (ret) + goto out_args; + + ret = fuse_compound_send(compound); + if (ret) + goto out_args; + + ret = fuse_compound_get_error(compound, 0); + if (ret || !lookup_out->nodeid) + goto out_args; + if (lookup_out->nodeid == FUSE_ROOT_ID && + lookup_out->generation != 0) { + pr_warn_once("root generation should be zero\n"); + lookup_out->generation = 0; + } + if ((lookup_args.out_args[1].size > 0) && + (lookup_args.out_args[1].value)) { + struct fuse_file_handle *h = lookup_args.out_args[1].value; + + *fh = kzalloc(sizeof(*fh) + h->size, GFP_KERNEL); + if (!*fh) { + ret = -ENOMEM; + goto out_args; + } + (*fh)->size = h->size; + memcpy((*fh)->handle, h->handle, (*fh)->size); + } + + ret = fuse_compound_get_error(compound, 1); + if (ret) { + kfree(*fh); + *fh = NULL; + } + +out_args: + fuse_req_free_argvar_ext(&lookup_args); +out_compound: + kfree(compound); + + return ret; +} + +static int fuse_lookup_handle_name(struct super_block *sb, u64 nodeid, + struct inode *dir, const struct qstr *name, + u64 *time, struct inode **inode) +{ + struct fuse_mount *fm = get_fuse_mount_super(sb); + struct fuse_file_handle *fh = NULL; + struct fuse_entry2_out lookup_out; + struct fuse_statx_out statx_out; + struct fuse_attr attr; + struct fuse_forget_link *forget; + u64 attr_version, evict_ctr; + int ret; + + forget = fuse_alloc_forget(); + if (!forget) + return -ENOMEM; + + attr_version = fuse_get_attr_version(fm->fc); + evict_ctr = fuse_get_evict_ctr(fm->fc); + + ret = do_lookup_handle_statx(fm, nodeid, dir, name, &lookup_out, + &statx_out, &fh); + if (ret) + goto out_forget; + + fuse_statx_to_attr(&statx_out.stat, &attr); + WARN_ON(fuse_invalid_attr(&attr)); + + *inode = fuse_iget(sb, lookup_out.nodeid, lookup_out.generation, + &attr, ATTR_TIMEOUT(&statx_out), + attr_version, evict_ctr, fh); + ret = -ENOMEM; + if (!*inode) { + fuse_queue_forget(fm->fc, forget, lookup_out.nodeid, 1); + goto out_forget; + } + if (time) + *time = fuse_time_to_jiffies(lookup_out.entry_valid, + lookup_out.entry_valid_nsec); + + /* XXX idmap? */ + ret = fuse_statx_update(&nop_mnt_idmap, &statx_out, *inode, + attr_version, NULL); + +out_forget: + kfree(forget); + + return ret; +} + +int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct inode *dir, + const struct qstr *name, u64 *time, struct inode **inode) { struct fuse_mount *fm = get_fuse_mount_super(sb); FUSE_ARGS(args); @@ -561,6 +812,9 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name if (name->len > fm->fc->name_max) goto out; + if (fm->fc->lookup_handle) + return fuse_lookup_handle_name(sb, nodeid, dir, name, time, + inode); forget = fuse_alloc_forget(); err = -ENOMEM; @@ -586,7 +840,7 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name *inode = fuse_iget(sb, outarg.nodeid, outarg.generation, &outarg.attr, ATTR_TIMEOUT(&outarg), - attr_version, evict_ctr); + attr_version, evict_ctr, NULL); err = -ENOMEM; if (!*inode) { fuse_queue_forget(fm->fc, forget, outarg.nodeid, 1); @@ -621,7 +875,7 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, epoch = atomic_read(&fc->epoch); 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_node_id(dir), dir, &entry->d_name, &time, &inode); fuse_unlock_inode(dir, locked); if (err == -ENOENT) @@ -882,7 +1136,7 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir, ff->nodeid = outentry.nodeid; ff->open_flags = outopenp->open_flags; inode = fuse_iget(dir->i_sb, outentry.nodeid, outentry.generation, - &outentry.attr, ATTR_TIMEOUT(&outentry), 0, 0); + &outentry.attr, ATTR_TIMEOUT(&outentry), 0, 0, NULL); if (!inode) { flags &= ~(O_CREAT | O_EXCL | O_TRUNC); fuse_sync_release(NULL, ff, flags); @@ -1009,7 +1263,7 @@ static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_moun goto out_put_forget_req; inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation, - &outarg.attr, ATTR_TIMEOUT(&outarg), 0, 0); + &outarg.attr, ATTR_TIMEOUT(&outarg), 0, 0, NULL); if (!inode) { fuse_queue_forget(fm->fc, forget, outarg.nodeid, 1); return ERR_PTR(-ENOMEM); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 04f09e2ccfd0..173032887fc2 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -223,6 +223,8 @@ struct fuse_inode { * so preserve the blocksize specified by the server. */ u8 cached_i_blkbits; + + struct fuse_file_handle *fh; }; /** FUSE inode state bits */ @@ -923,6 +925,9 @@ struct fuse_conn { /* Is synchronous FUSE_INIT allowed? */ unsigned int sync_init:1; + /** Is LOOKUP_HANDLE implemented by the fs? */ + unsigned int lookup_handle:1; + /* Use io_uring for communication */ unsigned int io_uring; @@ -1072,6 +1077,18 @@ static inline int invalid_nodeid(u64 nodeid) return !nodeid || nodeid == FUSE_ROOT_ID; } +static inline bool fuse_file_handle_is_equal(struct fuse_conn *fc, + struct fuse_file_handle *fh1, + struct fuse_file_handle *fh2) +{ + if (!fc->lookup_handle || + ((fh1 == fh2) && !fh1) || /* both NULL */ + (fh1 && fh2 && (fh1->size == fh2->size) && + (!memcmp(fh1->handle, fh2->handle, fh1->size)))) + return true; + return false; +} + static inline u64 fuse_get_attr_version(struct fuse_conn *fc) { return atomic64_read(&fc->attr_version); @@ -1148,10 +1165,10 @@ extern const struct dentry_operations fuse_dentry_operations; 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); + u64 evict_ctr, struct fuse_file_handle *fh); -int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name, - u64 *time, struct inode **inode); +int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct inode *dir, + const struct qstr *name, u64 *time, struct inode **inode); /** * Send FORGET command diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 006436a3ad06..9f2c0c9e877c 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -120,6 +120,8 @@ static struct inode *fuse_alloc_inode(struct super_block *sb) if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH)) fuse_inode_backing_set(fi, NULL); + fi->fh = NULL; + return &fi->inode; out_free_forget: @@ -141,6 +143,8 @@ static void fuse_free_inode(struct inode *inode) if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH)) fuse_backing_put(fuse_inode_backing(fi)); + kfree(fi->fh); + kmem_cache_free(fuse_inode_cachep, fi); } @@ -465,7 +469,7 @@ static int fuse_inode_set(struct inode *inode, void *_nodeidp) 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) + u64 evict_ctr, struct fuse_file_handle *fh) { struct inode *inode; struct fuse_inode *fi; @@ -505,6 +509,26 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid, if (!inode) return NULL; + fi = get_fuse_inode(inode); + if (fc->lookup_handle && !fi->fh) { + if (!fh && (nodeid != FUSE_ROOT_ID)) { + pr_err("NULL file handle for nodeid %llu\n", nodeid); + WARN_ON_ONCE(1); + } else { + size_t sz = sizeof(struct fuse_file_handle); + + if (fh) + sz += fh->size; + + fi->fh = kzalloc(sz, GFP_KERNEL); + if (!fi->fh) { + iput(inode); + return NULL; // ENOMEM + } + if (fh) + memcpy(fi->fh, fh, sz); + } + } if ((inode_state_read_once(inode) & I_NEW)) { inode->i_flags |= S_NOATIME; if (!fc->writeback_cache || !S_ISREG(attr->mode)) @@ -512,7 +536,8 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid, inode->i_generation = generation; fuse_init_inode(inode, attr, fc); unlock_new_inode(inode); - } else if (fuse_stale_inode(inode, generation, attr)) { + } else if (fuse_stale_inode(inode, generation, attr) || + (!fuse_file_handle_is_equal(fc, fi->fh, fh))) { /* nodeid was reused, any I/O on the old inode should fail */ fuse_make_bad(inode); if (inode != d_inode(sb->s_root)) { @@ -521,7 +546,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); @@ -1068,7 +1092,7 @@ static struct inode *fuse_get_root_inode(struct super_block *sb, unsigned int mo 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, FUSE_ROOT_ID, 0, &attr, 0, 0, 0, NULL); } struct fuse_inode_handle { @@ -1094,7 +1118,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, NULL, &inode); + err = fuse_lookup_name(sb, handle->nodeid, NULL, &name, NULL, + &inode); if (err && err != -ENOENT) goto out_err; if (err || !inode) { @@ -1115,9 +1140,9 @@ static struct dentry *fuse_get_dentry(struct super_block *sb, return entry; - out_iput: +out_iput: iput(inode); - out_err: +out_err: return ERR_PTR(err); } @@ -1194,7 +1219,7 @@ static struct dentry *fuse_get_parent(struct dentry *child) return ERR_PTR(-ESTALE); err = fuse_lookup_name(child_inode->i_sb, get_node_id(child_inode), - &dotdot_name, NULL, &inode); + child_inode, &dotdot_name, NULL, &inode); if (err) { if (err == -ENOENT) return ERR_PTR(-ESTALE); @@ -1459,6 +1484,9 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args, if (flags & FUSE_REQUEST_TIMEOUT) timeout = arg->request_timeout; + + if (flags & FUSE_HAS_LOOKUP_HANDLE) + fc->lookup_handle = 1; } else { ra_pages = fc->max_read / PAGE_SIZE; fc->no_lock = 1; @@ -1509,7 +1537,7 @@ static struct fuse_init_args *fuse_new_init(struct fuse_mount *fm) FUSE_SECURITY_CTX | FUSE_CREATE_SUPP_GROUP | FUSE_HAS_EXPIRE_ONLY | FUSE_DIRECT_IO_ALLOW_MMAP | FUSE_NO_EXPORT_SUPPORT | FUSE_HAS_RESEND | FUSE_ALLOW_IDMAP | - FUSE_REQUEST_TIMEOUT; + FUSE_REQUEST_TIMEOUT | FUSE_LOOKUP_HANDLE; #ifdef CONFIG_FUSE_DAX if (fm->fc->dax) flags |= FUSE_MAP_ALIGNMENT; @@ -1745,7 +1773,7 @@ static int fuse_fill_super_submount(struct super_block *sb, fuse_fill_attr_from_inode(&root_attr, parent_fi); root = fuse_iget(sb, parent_fi->nodeid, 0, &root_attr, 0, 0, - fuse_get_evict_ctr(fm->fc)); + fuse_get_evict_ctr(fm->fc), NULL); /* * This inode is just a duplicate, so it is not looked up and * its nlookup should not be incremented. fuse_iget() does diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c index c2aae2eef086..2b59a196bcbf 100644 --- a/fs/fuse/readdir.c +++ b/fs/fuse/readdir.c @@ -235,7 +235,7 @@ static int fuse_direntplus_link(struct file *file, } else { inode = fuse_iget(dir->i_sb, o->nodeid, o->generation, &o->attr, ATTR_TIMEOUT(o), - attr_version, evict_ctr); + attr_version, evict_ctr, NULL); if (!inode) inode = ERR_PTR(-ENOMEM); diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index 113583c4efb6..89e6176abe25 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -240,6 +240,9 @@ * - add FUSE_COPY_FILE_RANGE_64 * - add struct fuse_copy_file_range_out * - add FUSE_NOTIFY_PRUNE + * + * 7.46 + * - add FUSE_LOOKUP_HANDLE */ #ifndef _LINUX_FUSE_H @@ -275,7 +278,7 @@ #define FUSE_KERNEL_VERSION 7 /** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 45 +#define FUSE_KERNEL_MINOR_VERSION 46 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 @@ -495,6 +498,7 @@ struct fuse_file_lock { #define FUSE_ALLOW_IDMAP (1ULL << 40) #define FUSE_OVER_IO_URING (1ULL << 41) #define FUSE_REQUEST_TIMEOUT (1ULL << 42) +#define FUSE_HAS_LOOKUP_HANDLE (1ULL << 43) /** * CUSE INIT request/reply flags @@ -609,6 +613,7 @@ enum fuse_ext_type { /* Types 0..31 are reserved for fuse_secctx_header */ FUSE_MAX_NR_SECCTX = 31, FUSE_EXT_GROUPS = 32, + FUSE_EXT_HANDLE = 33, }; enum fuse_opcode { @@ -671,6 +676,8 @@ enum fuse_opcode { */ FUSE_COMPOUND = 54, + FUSE_LOOKUP_HANDLE = 55, + /* CUSE specific operations */ CUSE_INIT = 4096, @@ -707,6 +714,20 @@ struct fuse_entry_out { struct fuse_attr attr; }; +struct fuse_entry2_out { + uint64_t nodeid; + uint64_t generation; + uint64_t entry_valid; + uint32_t entry_valid_nsec; + uint32_t flags; + uint64_t spare; +}; + +struct fuse_file_handle { + uint16_t size; + uint8_t handle[]; +}; + struct fuse_forget_in { uint64_t nlookup; }; Allow fuse_open_args_fill() fuction to be used from different files. Signed-off-by: Luis Henriques --- fs/fuse/file.c | 2 +- fs/fuse/fuse_i.h | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 1045d74dd95f..ea9023150a38 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -26,7 +26,7 @@ /* * Helper function to initialize fuse_args for OPEN/OPENDIR operations */ -static void fuse_open_args_fill(struct fuse_args *args, u64 nodeid, int opcode, +void fuse_open_args_fill(struct fuse_args *args, u64 nodeid, int opcode, struct fuse_open_in *inarg, struct fuse_open_out *outarg) { args->opcode = opcode; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 173032887fc2..f37959c76412 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1588,10 +1588,14 @@ int fuse_file_io_open(struct file *file, struct inode *inode); void fuse_file_io_release(struct fuse_file *ff, struct inode *inode); /* file.c */ +void fuse_open_args_fill(struct fuse_args *args, u64 nodeid, int opcode, + struct fuse_open_in *inarg, + struct fuse_open_out *outarg); + struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid, - struct inode *inode, - unsigned int open_flags, - bool isdir); + struct inode *inode, + unsigned int open_flags, + bool isdir); void fuse_file_release(struct inode *inode, struct fuse_file *ff, unsigned int open_flags, fl_owner_t id, bool isdir); The implementation of this compound operation allows atomic_open() to use file handle. It also introduces a new MKOBJ_HANDLE operation that will handle the file system object creation and will return the file handle. The atomicity of the operation (create + open) needs to be handled in user-space (e.g. the handling of the O_EXCL flag). Signed-off-by: Luis Henriques --- fs/fuse/dir.c | 219 +++++++++++++++++++++++++++++++++++++- include/uapi/linux/fuse.h | 2 + 2 files changed, 220 insertions(+), 1 deletion(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 7fa8c405f1a3..b5beb1d62c3d 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1173,6 +1173,220 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir, return err; } +static int fuse_mkobj_handle_init(struct fuse_mount *fm, struct fuse_args *args, + struct mnt_idmap *idmap, struct inode *dir, + struct dentry *entry, unsigned int flags, + umode_t mode, + struct fuse_create_in *inarg, + struct fuse_entry2_out *outarg, + struct fuse_file_handle **fh) +{ + struct fuse_inode *fi; + size_t fh_size = sizeof(struct fuse_file_handle) + MAX_HANDLE_SZ; + int err = 0; + + *fh = kzalloc(fh_size, GFP_KERNEL); + if (!*fh) + return -ENOMEM; + + memset(inarg, 0, sizeof(*inarg)); + memset(outarg, 0, sizeof(*outarg)); + + inarg->flags = flags; + inarg->mode = mode; + inarg->umask = current_umask(); + + if (fm->fc->handle_killpriv_v2 && (flags & O_TRUNC) && + !(flags & O_EXCL) && !capable(CAP_FSETID)) + inarg->open_flags |= FUSE_OPEN_KILL_SUIDGID; + + args->opcode = FUSE_MKOBJ_HANDLE; + args->nodeid = get_node_id(dir); + args->in_numargs = 2; + args->in_args[0].size = sizeof(*inarg); + args->in_args[0].value = inarg; + args->in_args[1].size = entry->d_name.len + 1; + args->in_args[1].value = entry->d_name.name; + + err = get_create_ext(idmap, args, dir, entry, mode); + if (err) + goto out_err; + fi = get_fuse_inode(dir); + if (fi && fi->fh) { + if (!args->is_ext) { + args->is_ext = true; + args->ext_idx = args->in_numargs++; + } + err = create_ext_handle(&args->in_args[args->ext_idx], fi); + if (err) + goto out_err; + } + + args->out_numargs = 2; + args->out_args[0].size = sizeof(*outarg); + args->out_args[0].value = outarg; + args->out_args[1].size = fh_size; + args->out_args[1].value = *fh; + +out_err: + if (err) { + kfree(*fh); + free_ext_value(args); + } + + return err; +} + +static int fuse_mkobj_statx_open(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *entry, struct file *file, + unsigned int flags, umode_t mode) +{ + struct fuse_compound_req *compound; + struct fuse_mount *fm = get_fuse_mount(dir); + struct fuse_inode *fi = NULL; + struct fuse_create_in mkobj_in; + struct fuse_entry2_out mkobj_out; + struct fuse_statx_in statx_in; + struct fuse_statx_out statx_out; + struct fuse_open_in open_in; + struct fuse_open_out *open_outp; + FUSE_ARGS(mkobj_args); + FUSE_ARGS(statx_args); + FUSE_ARGS(open_args); + struct fuse_forget_link *forget; + struct fuse_file *ff; + struct fuse_attr attr; + struct fuse_file_handle *fh = NULL; + struct inode *inode; + int epoch, ret = -EIO; + int i; + + epoch = atomic_read(&fm->fc->epoch); + + ret = -ENOMEM; + forget = fuse_alloc_forget(); + if (!forget) + return -ENOMEM; + ff = fuse_file_alloc(fm, true); + if (!ff) + goto out_forget; + + if (!fm->fc->dont_mask) + mode &= ~current_umask(); + + flags &= ~O_NOCTTY; + + compound = fuse_compound_alloc(fm, FUSE_COMPOUND_ATOMIC); + if (!compound) + goto out_free_ff; + + fi = get_fuse_inode(dir); + if (!fi) { + ret = -EIO; + goto out_compound; + } + ret = fuse_mkobj_handle_init(fm, &mkobj_args, idmap, dir, entry, flags, + mode, &mkobj_in, &mkobj_out, &fh); + if (ret) + goto out_compound; + + ret = fuse_compound_add(compound, &mkobj_args); + if (ret) + goto out_mkobj_args; + + fuse_statx_init(&statx_args, &statx_in, FUSE_ROOT_ID, NULL, &statx_out); + ret = fuse_compound_add(compound, &statx_args); + if (ret) + goto out_mkobj_args; + + ff->fh = 0; + ff->open_flags = FOPEN_KEEP_CACHE; + memset(&open_in, 0, sizeof(open_in)); + + /* XXX flags handling */ + open_in.flags = ff->open_flags & ~(O_CREAT | O_EXCL | O_NOCTTY); + if (!fm->fc->atomic_o_trunc) + open_in.flags &= ~O_TRUNC; + if (fm->fc->handle_killpriv_v2 && + (open_in.flags & O_TRUNC) && !capable(CAP_FSETID)) + open_in.open_flags |= FUSE_OPEN_KILL_SUIDGID; + + open_outp = &ff->args->open_outarg; + fuse_open_args_fill(&open_args, FUSE_ROOT_ID, FUSE_OPEN, &open_in, + open_outp); + + ret = fuse_compound_add(compound, &open_args); + if (ret) + goto out_mkobj_args; + + ret = fuse_compound_send(compound); + if (ret) + goto out_mkobj_args; + + for (i = 0; i < 3; i++) { + int err; + + err = fuse_compound_get_error(compound, i); + if (err && !ret) + ret = err; + } + if (ret) + goto out_mkobj_args; + + fuse_statx_to_attr(&statx_out.stat, &attr); + WARN_ON(fuse_invalid_attr(&attr)); + ret = -EIO; + if (!S_ISREG(attr.mode) || invalid_nodeid(mkobj_out.nodeid) || + fuse_invalid_attr(&attr)) + goto out_mkobj_args; + + ff->fh = open_outp->fh; + ff->nodeid = mkobj_out.nodeid; + ff->open_flags = open_outp->open_flags; + inode = fuse_iget(dir->i_sb, mkobj_out.nodeid, mkobj_out.generation, + &attr, ATTR_TIMEOUT(&statx_out), 0, 0, fh); + if (!inode) { + flags &= ~(O_CREAT | O_EXCL | O_TRUNC); + fuse_sync_release(NULL, ff, flags); + fuse_queue_forget(fm->fc, forget, mkobj_out.nodeid, 1); + ret = -ENOMEM; + goto out_mkobj_args; + } + d_instantiate(entry, inode); + + entry->d_time = epoch; + fuse_dentry_settime(entry, + fuse_time_to_jiffies(mkobj_out.entry_valid, + mkobj_out.entry_valid_nsec)); + fuse_dir_changed(dir); + ret = generic_file_open(inode, file); + if (!ret) { + file->private_data = ff; + ret = finish_open(file, entry, fuse_finish_open); + } + if (ret) { + fuse_sync_release(get_fuse_inode(inode), ff, flags); + } else { + if (fm->fc->atomic_o_trunc && (flags & O_TRUNC)) + truncate_pagecache(inode, 0); + else if (!(ff->open_flags & FOPEN_KEEP_CACHE)) + invalidate_inode_pages2(inode->i_mapping); + } + +out_mkobj_args: + fuse_req_free_argvar_ext(&mkobj_args); +out_compound: + kfree(compound); +out_free_ff: + if (ret) + fuse_file_free(ff); +out_forget: + kfree(forget); + kfree(fh); + + return ret; +} + static int fuse_mknod(struct mnt_idmap *, struct inode *, struct dentry *, umode_t, dev_t); static int fuse_atomic_open(struct inode *dir, struct dentry *entry, @@ -1201,7 +1415,10 @@ static int fuse_atomic_open(struct inode *dir, struct dentry *entry, if (fc->no_create) goto mknod; - err = fuse_create_open(idmap, dir, entry, file, flags, mode, FUSE_CREATE); + if (fc->lookup_handle) + err = fuse_mkobj_statx_open(idmap, dir, entry, file, flags, mode); + else + err = fuse_create_open(idmap, dir, entry, file, flags, mode, FUSE_CREATE); if (err == -ENOSYS) { fc->no_create = 1; goto mknod; diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index 89e6176abe25..f49eb1b8f2f3 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -243,6 +243,7 @@ * * 7.46 * - add FUSE_LOOKUP_HANDLE + * - add FUSE_MKOBJ_HANDLE */ #ifndef _LINUX_FUSE_H @@ -677,6 +678,7 @@ enum fuse_opcode { FUSE_COMPOUND = 54, FUSE_LOOKUP_HANDLE = 55, + FUSE_MKOBJ_HANDLE = 56, /* CUSE specific operations */ CUSE_INIT = 4096,