[RFC PATCH v3 3/8] fuse: store index of the variable length argument

From: Luis Henriques

Date: Wed Feb 25 2026 - 06:30:06 EST


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 <luis@xxxxxxxxxx>
---
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 {