Re: [RFC PATCH v3 8/8] fuse: implementation of mkobj_handle+statx+open compound operation

From: Horst Birthelmer

Date: Wed Feb 25 2026 - 10:30:29 EST


Hi Luis,

On Wed, Feb 25, 2026 at 11:24:39AM +0000, Luis Henriques wrote:
> 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 <luis@xxxxxxxxxx>
> ---
> 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;
> +

Just to clarify for myself and maybe others.
You want this to be processed atomic on the fuse server and never
be separated by the upcoming 'decode and send separate' code in the
kernel?
Is that really necessarry? What would the consequences be,
if this is not really atomic?

> + 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);

Your compound looks good so far ;-)

> + 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;
> + }

this is probably why you opted for that 'give me any occurred error'
functionality?

> + 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,
>