All of lore.kernel.org
 help / color / mirror / Atom feed
From: Chuck Lever <cel@kernel.org>
To: NeilBrown <neil@brown.name>, Jeff Layton <jlayton@kernel.org>,
	Olga Kornievskaia <okorniev@redhat.com>,
	Dai Ngo <dai.ngo@oracle.com>, Tom Talpey <tom@talpey.com>
Cc: <linux-nfs@vger.kernel.org>
Subject: [PATCH v1 27/27] NFSD: Use xdrgen XDR functions for NFSv2 READDIR results
Date: Wed, 16 Sep 2026 12:28:58 -0400	[thread overview]
Message-ID: <20260916162859.2051-30-cel@kernel.org> (raw)
In-Reply-To: <20260916162859.2051-1-cel@kernel.org>

READDIR is the last NFSv2 procedure whose reply is still produced by
a hand-written encoder. Its entry list is the value-follows form of
RFC 1094's "entry *entries" -- each entry prefixed by TRUE, the
sequence closed by FALSE -- streamed straight from the directory as
it is read rather than from a materialized array. The generated
encoder could not drive that: the union encoder writes the reply
status word before the entry list, yet a streamed read does not learn
the status until the directory has been read, and the aggregate loop
was bounded by a count the begin hook had to supply up front.

The preceding patches remove both obstacles. The optional-data
aggregate encoder now pulls entries until the hook signals end, so no
count is needed, and the streaming directory reader yields one entry
at a time during encode. Mark readdirok's entry list as an aggregate
and regenerate; the struct definitions stay the verbatim RFC 1094
types, and the lone "pragma aggregate readdirok entries" selects the
hook-driven codec.

Split the read across the two phases so the generated encoder owns
the framing. nfsd_proc_readdir() opens the directory, where an open
error becomes the reply status the union encoder then writes, and the
encode hooks stream the entries: each pull reserves a placeholder
cookie and back-patches the preceding entry's cookie once the
following entry's resume offset is known; the end hook back-patches
the final cookie and reports eof. nfssvc_release_readdirres() closes
the reader and releases the file handle. The decode hooks are stubs
-- a server never decodes a READDIR result -- present only to satisfy
the generated decoder's linkage.

Retire struct nfsd_readdirres for an embedded struct readdirres so
the RPC dispatch layer can cast the response to its xdrgen type.
The directory's svc_fh moves into that wrapper and struct
readdirargs_wrapper goes away. svc_process() zeroes the result buffer
before decode but leaves the argument buffer as the previous request
left it, and ->pc_release runs even when decoding fails. A file
handle kept in the argument wrapper would reach fh_put() on that path
uninitialized.

The generated encoder reserves reply space through the xdr_stream,
which does not advance rq_next_page as it moves onto the next page.
svcrdma retains only the pages below rq_next_page across Send
completion, so the end hook sets rq_next_page past the last page the
reply uses, as nfsd4_encode_operation() does.

A host error during the streamed read cannot retract entries already
on the wire, so it ends the list as a truncated, non-eof reply rather
than an error status; an error opening the directory is still
reported as the reply status. The bytes on the wire are otherwise
unchanged.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 Documentation/sunrpc/xdr/nfs2.x    |  10 ++
 fs/nfsd/nfs2xdr_gen.c              |  35 ++++-
 fs/nfsd/nfs2xdr_gen.h              |  13 +-
 fs/nfsd/nfsproc.c                  |  67 +++------
 fs/nfsd/nfsxdr.c                   | 231 +++++++++++++++++++----------
 fs/nfsd/xdr.h                      |  35 +++--
 include/linux/sunrpc/xdrgen/nfs2.h |   2 +-
 7 files changed, 246 insertions(+), 147 deletions(-)

diff --git a/Documentation/sunrpc/xdr/nfs2.x b/Documentation/sunrpc/xdr/nfs2.x
index 932968c24d99..30e9782c17ce 100644
--- a/Documentation/sunrpc/xdr/nfs2.x
+++ b/Documentation/sunrpc/xdr/nfs2.x
@@ -198,6 +198,16 @@ struct readdirok {
 	bool		eof;
 };
 
+/*
+ * A spike encoding the entry list through the aggregate codec.  The
+ * list encodes as the value-follows form -- each entry prefixed by
+ * TRUE, the sequence closed by FALSE -- and the encode hooks stream it
+ * straight from the directory during reply encoding rather than from a
+ * materialized array.  The struct definitions above are the verbatim
+ * RFC 1094 types; only this pragma selects the hook-driven codec.
+ */
+pragma aggregate readdirok entries;
+
 union readdirres switch (nfsstat status) {
 	case NFS_OK:
 		readdirok	readdirok;
diff --git a/fs/nfsd/nfs2xdr_gen.c b/fs/nfsd/nfs2xdr_gen.c
index 0ddfd9bc4a99..8a3e8ee8db0e 100644
--- a/fs/nfsd/nfs2xdr_gen.c
+++ b/fs/nfsd/nfs2xdr_gen.c
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 // Generated by xdrgen. Manual edits will be lost.
 // XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x
-// XDR specification modification time: Tue Sep  8 11:26:14 2026
+// XDR specification modification time: Tue Sep  8 11:26:28 2026
 
 #include <linux/sunrpc/svc.h>
 
@@ -363,11 +363,7 @@ xdrgen_decode_entry(struct xdr_stream *xdr, struct entry *ptr)
 static bool __maybe_unused
 xdrgen_decode_readdirok(struct xdr_stream *xdr, struct readdirok *ptr)
 {
-	if (!xdrgen_decode_entry(xdr, ptr->entries))
-		return false;
-	if (!xdrgen_decode_bool(xdr, &ptr->eof))
-		return false;
-	return true;
+	return false;
 }
 
 static bool __maybe_unused
@@ -892,8 +888,31 @@ xdrgen_encode_entry(struct xdr_stream *xdr, const struct entry *value)
 static bool __maybe_unused
 xdrgen_encode_readdirok(struct xdr_stream *xdr, const struct readdirok *value)
 {
-	if (!xdrgen_encode_entry(xdr, value->entries))
-		return false;
+	{
+		struct xdrgen_aggregate_cursor cursor = {
+			.xdr = xdr,
+			.member_id = NFS2_READDIROK_ENTRIES,
+			.ctx = xdr->xdrgen_ctx,
+		};
+		bool ok = true;
+
+		if (!nfs2_readdirok_encode_begin(&cursor))
+			return false;
+		for (cursor.index = 0; ; cursor.index++) {
+			struct entry element = {};
+
+			if (!nfs2_readdirok_encode(&cursor, &element))
+				break;
+			if (!xdrgen_encode_entry(xdr, &element)) {
+				ok = false;
+				break;
+			}
+		}
+		if (ok && !xdrgen_encode_entry(xdr, NULL))
+			ok = false;
+		if (!nfs2_readdirok_encode_end(&cursor, ok) || !ok)
+			return false;
+	}
 	if (!xdrgen_encode_bool(xdr, value->eof))
 		return false;
 	return true;
diff --git a/fs/nfsd/nfs2xdr_gen.h b/fs/nfsd/nfs2xdr_gen.h
index c3641ba63bb3..7d259aa31191 100644
--- a/fs/nfsd/nfs2xdr_gen.h
+++ b/fs/nfsd/nfs2xdr_gen.h
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: GPL-2.0 */
 /* Generated by xdrgen. Manual edits will be lost. */
 /* XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x */
-/* XDR specification modification time: Tue Sep  8 11:26:14 2026 */
+/* XDR specification modification time: Tue Sep  8 11:26:28 2026 */
 
 #ifndef _LINUX_XDRGEN_NFS2_DECL_H
 #define _LINUX_XDRGEN_NFS2_DECL_H
@@ -13,6 +13,17 @@
 #include <linux/sunrpc/xdrgen/_builtins.h>
 #include <linux/sunrpc/xdrgen/nfs2.h>
 
+enum {
+	NFS2_READDIROK_ENTRIES,
+};
+
+bool nfs2_readdirok_encode_begin(struct xdrgen_aggregate_cursor *c);
+bool nfs2_readdirok_encode(struct xdrgen_aggregate_cursor *c, struct entry *out);
+bool nfs2_readdirok_encode_end(struct xdrgen_aggregate_cursor *c, bool ok);
+bool nfs2_readdirok_decode_begin(struct xdrgen_aggregate_cursor *c);
+bool nfs2_readdirok_decode(struct xdrgen_aggregate_cursor *c, const struct entry *in);
+bool nfs2_readdirok_decode_end(struct xdrgen_aggregate_cursor *c, bool ok);
+
 bool nfs_svc_decode_fhandle(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs_svc_decode_sattrargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs_svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index df610fe3977d..1f74008bc06a 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -116,13 +116,6 @@ struct statfsres_wrapper {
 
 static_assert(offsetof(struct statfsres_wrapper, xdrgen) == 0);
 
-struct readdirargs_wrapper {
-	struct readdirargs	xdrgen;
-	struct svc_fh		fh;
-};
-
-static_assert(offsetof(struct readdirargs_wrapper, xdrgen) == 0);
-
 static __be32 nfsd_map_status(__be32 status)
 {
 	switch (status) {
@@ -1124,28 +1117,16 @@ static __be32 nfsd_proc_rmdir(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-static void nfsd_init_dirlist_pages(struct svc_rqst *rqstp,
-				    struct nfsd_readdirres *resp,
-				    u32 count)
-{
-	struct xdr_buf *buf = &resp->dirlist;
-	struct xdr_stream *xdr = &resp->xdr;
-
-	memset(buf, 0, sizeof(*buf));
-
-	/* Reserve room for the NULL ptr & eof flag (-2 words) */
-	buf->buflen = clamp(count, (u32)(XDR_UNIT * 2), (u32)PAGE_SIZE);
-	buf->buflen -= XDR_UNIT * 2;
-	buf->pages = rqstp->rq_next_page;
-	rqstp->rq_next_page++;
-
-	xdr_init_encode_pages(xdr, buf);
-}
-
 /**
  * nfsd_proc_readdir - READDIR: Read from directory
  * @rqstp: RPC transaction context
  *
+ * The directory is opened here so the reply status reflects any open
+ * error before the generated encoder writes the status word.  The
+ * entry list is streamed from the reply wrapper's directory reader
+ * during encode; nfssvc_release_readdirres() closes the reader and
+ * releases the file handle.
+ *
  * Return:
  *   %rpc_success:		RPC executed successfully
  *
@@ -1154,24 +1135,19 @@ static void nfsd_init_dirlist_pages(struct svc_rqst *rqstp,
  */
 static __be32 nfsd_proc_readdir(struct svc_rqst *rqstp)
 {
-	struct readdirargs_wrapper *argp = rqstp->rq_argp;
-	struct nfsd_readdirres *resp = rqstp->rq_resp;
-	loff_t offset = be32_to_cpup((__be32 *)argp->xdrgen.cookie);
-	struct svc_fh *fhp = &argp->fh;
+	struct readdirargs *argp = rqstp->rq_argp;
+	struct readdirres_wrapper *resp = rqstp->rq_resp;
+	loff_t offset = be32_to_cpup((__be32 *)argp->cookie);
+	struct svc_fh *fhp = &resp->fh;
+	__be32 status;
 
-	nfsd_fhandle_to_svc_fh(fhp, &argp->xdrgen.dir);
-	trace_nfsd_vfs_readdir(rqstp, fhp, argp->xdrgen.count, offset);
+	nfsd_fhandle_to_svc_fh(fhp, &argp->dir);
+	trace_nfsd_vfs_readdir(rqstp, fhp, argp->count, offset);
 
-	nfsd_init_dirlist_pages(rqstp, resp, argp->xdrgen.count);
+	resp->count = argp->count;
+	status = nfsd_readdir_open(rqstp, fhp, &offset, &resp->iter);
 
-	resp->common.err = nfs_ok;
-	resp->cookie_offset = 0;
-	resp->status = nfsd_readdir(rqstp, fhp, &offset,
-				    &resp->common, nfssvc_encode_entry);
-	nfssvc_encode_nfscookie(&resp->xdr, resp->cookie_offset, offset);
-
-	fh_put(fhp);
-	resp->status = nfsd_map_status(resp->status);
+	resp->xdrgen.status = nfsd_map_status(status);
 	return rpc_success;
 }
 
@@ -1401,10 +1377,11 @@ static const struct svc_procedure nfsd_procedures2[18] = {
 	[NFSPROC_READDIR] = {
 		.pc_func	= nfsd_proc_readdir,
 		.pc_decode	= nfs_svc_decode_readdirargs,
-		.pc_encode	= nfssvc_encode_readdirres,
-		.pc_argsize	= sizeof(struct readdirargs_wrapper),
+		.pc_encode	= nfs_svc_encode_readdirres,
+		.pc_release	= nfssvc_release_readdirres,
+		.pc_argsize	= sizeof(struct readdirargs),
 		.pc_argzero	= 0,
-		.pc_ressize	= sizeof(struct nfsd_readdirres),
+		.pc_ressize	= sizeof(struct readdirres_wrapper),
 		.pc_cachetype	= RC_NOCACHE,
 		.pc_name	= "READDIR",
 	},
@@ -1434,12 +1411,12 @@ union nfsd_xdrstore {
 	struct renameargs_wrapper	renameargs;
 	struct linkargs_wrapper		linkargs;
 	struct symlinkargs_wrapper	symlinkargs;
-	struct readdirargs_wrapper	readdirargs;
+	struct readdirargs	readdirargs;
 	struct attrstat_wrapper		attrstat;
 	struct diropres_wrapper		diropres;
 	struct readlinkres	readlinkres;
 	struct readres		readres;
-	struct nfsd_readdirres	readdirres;
+	struct readdirres_wrapper	readdirres;
 	struct statfsres_wrapper	statfsres;
 };
 
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
index 3476ebe14c45..d3c0c84c0e98 100644
--- a/fs/nfsd/nfsxdr.c
+++ b/fs/nfsd/nfsxdr.c
@@ -12,6 +12,7 @@
 #include "xdr.h"
 #include "nfs2xdr_gen.h"
 #include "auth.h"
+#include "trace.h"
 
 /*
  * Linux-internal ftype values for socket and unknown inodes, not
@@ -194,37 +195,26 @@ nfssvc_encode_attrstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 	return true;
 }
 
-bool
-nfssvc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd_readdirres *resp = rqstp->rq_resp;
-	struct xdr_buf *dirlist = &resp->dirlist;
-
-	if (!svcxdr_encode_stat(xdr, resp->status))
-		return false;
-	switch (resp->status) {
-	case nfs_ok:
-		svcxdr_encode_opaque_pages(rqstp, xdr, dirlist->pages, 0,
-					   dirlist->len);
-		/* no more entries */
-		if (xdr_stream_encode_item_absent(xdr) < 0)
-			return false;
-		if (xdr_stream_encode_bool(xdr, resp->common.err == nfserr_eof) < 0)
-			return false;
-		break;
-	}
-
-	return true;
-}
+/*
+ * READDIR reply entry list (RFC 1094).  The entry list is the
+ * value-follows form of "entry *entries": each entry prefixed by TRUE,
+ * the sequence closed by FALSE.  xdrgen's aggregate codec owns that
+ * framing; the hooks below stream one entry at a time straight from the
+ * directory into the live reply, mirroring nfsd4_encode_dirlist4.  The
+ * directory was opened in nfsd_proc_readdir(), so reading and encoding
+ * happen together here during reply encoding.
+ */
 
 /**
- * nfssvc_encode_nfscookie - Encode a directory cookie
- * @xdr: stream into which to encode the cookie
- * @pos: byte position in the stream
- * @cookie: cookie to be encoded
+ * nfssvc_encode_nfscookie - Back-patch a directory entry cookie
+ * @xdr: stream holding the reserved cookie slot
+ * @pos: byte position of the cookie slot, or 0 when there is none
+ * @cookie: cookie value to write
  *
- * The buffer space for the offset cookie has already been reserved
- * by svcxdr_encode_entry_common().
+ * An NFSv2 entry's cookie is the offset at which the following entry is
+ * read.  That offset is not known until the following entry is pulled,
+ * so each entry is encoded with a placeholder cookie that a later call
+ * overwrites once the resume offset is known.
  */
 void nfssvc_encode_nfscookie(struct xdr_stream *xdr, unsigned int pos,
 			     u32 cookie)
@@ -236,71 +226,146 @@ void nfssvc_encode_nfscookie(struct xdr_stream *xdr, unsigned int pos,
 	write_bytes_to_xdr_buf(xdr->buf, pos, &wire_cookie, XDR_UNIT);
 }
 
-static bool
-svcxdr_encode_entry_common(struct nfsd_readdirres *resp, const char *name,
-			   int namlen, loff_t offset, u64 ino)
+/**
+ * nfs2_readdirok_encode_begin - size the streaming entry budget
+ * @c: aggregate cursor for the entry list
+ *
+ * Derive the entry byte budget from the space left in the live reply
+ * and the client's count hint, reserving room for the list terminator
+ * and the eof flag that follow the entries.
+ *
+ * Return: true.
+ */
+bool nfs2_readdirok_encode_begin(struct xdrgen_aggregate_cursor *c)
 {
-	struct xdr_buf *dirlist = &resp->dirlist;
-	struct xdr_stream *xdr = &resp->xdr;
-
-	if (xdr_stream_encode_item_present(xdr) < 0)
-		return false;
-	/* fileid */
-	if (xdr_stream_encode_u32(xdr, (u32)ino) < 0)
-		return false;
-	/* name */
-	if (xdr_stream_encode_opaque(xdr, name, min(namlen, NFS_MAXNAMLEN)) < 0)
-		return false;
-	/* cookie */
-	resp->cookie_offset = dirlist->len;
-	if (xdr_stream_encode_u32(xdr, ~0U) < 0)
-		return false;
+	struct svc_rqst *rqstp = c->ctx;
+	struct readdirres_wrapper *resp = rqstp->rq_resp;
+	struct xdr_stream *xdr = c->xdr;
+	int bytes_left;
 
+	/* Reserve the terminator FALSE and the eof bool (two words). */
+	bytes_left = xdr->buf->buflen - xdr->buf->len - XDR_UNIT * 2;
+	if (bytes_left < 0)
+		bytes_left = 0;
+	resp->space_left = min_t(u32, bytes_left,
+				 clamp(resp->count, (u32)(XDR_UNIT * 2),
+				       (u32)PAGE_SIZE) - XDR_UNIT * 2);
+	resp->cookie_offset = 0;
 	return true;
 }
 
 /**
- * nfssvc_encode_entry - encode one NFSv2 READDIR entry
- * @data: directory context
- * @name: name of the object to be encoded
- * @namlen: length of that name, in bytes
- * @offset: the offset of the previous entry
- * @ino: the fileid of this entry
- * @d_type: unused
+ * nfs2_readdirok_encode - stream the next READDIR entry
+ * @c: aggregate cursor for the entry list
+ * @out: OUT: entry the framing encodes when one is produced
  *
- * Return values:
- *   %0: Entry was successfully encoded.
- *   %-EINVAL: An encoding problem occurred, secondary status code in resp->common.err
+ * Back-patch the previous entry's placeholder cookie with this entry's
+ * resume offset, then, if the reply budget allows, project the next
+ * directory entry into @out for the generated per-entry encoder.
  *
- * On exit, the following fields are updated:
- *   - resp->xdr
- *   - resp->common.err
- *   - resp->cookie_offset
+ * Return: true when @out holds an entry to encode; false to end the
+ * list -- because the directory is exhausted, a host error struck, or
+ * the reply budget filled.  On budget exhaustion the previous entry's
+ * cookie already points at the rejected entry, the client's resume
+ * point.
  */
-int nfssvc_encode_entry(void *data, const char *name, int namlen,
-			loff_t offset, u64 ino, unsigned int d_type)
+bool nfs2_readdirok_encode(struct xdrgen_aggregate_cursor *c,
+			   struct entry *out)
 {
-	struct readdir_cd *ccd = data;
-	struct nfsd_readdirres *resp = container_of(ccd,
-						    struct nfsd_readdirres,
-						    common);
-	unsigned int starting_length = resp->dirlist.len;
+	struct svc_rqst *rqstp = c->ctx;
+	struct readdirres_wrapper *resp = rqstp->rq_resp;
+	struct xdr_stream *xdr = c->xdr;
+	struct buffered_dirent *de;
+	int namlen;
+	u32 need;
 
-	/* The offset cookie for the previous entry */
-	nfssvc_encode_nfscookie(&resp->xdr, resp->cookie_offset, offset);
+	/*
+	 * The previous entry was just encoded; commit the stream and note
+	 * its cookie slot -- the entry's last XDR word -- so it can be
+	 * back-patched once this entry's resume offset is known.
+	 */
+	if (c->index) {
+		xdr_commit_encode(xdr);
+		resp->cookie_offset = xdr->buf->len - XDR_UNIT;
+	}
 
-	if (!svcxdr_encode_entry_common(resp, name, namlen, offset, ino))
-		goto out_toosmall;
+	de = nfsd_readdir_next(&resp->iter);
+	if (!de)
+		return false;
 
-	xdr_commit_encode(&resp->xdr);
-	resp->common.err = nfs_ok;
-	return 0;
+	/* The previous entry's cookie is this entry's resume offset. */
+	nfssvc_encode_nfscookie(xdr, resp->cookie_offset, (u32)de->offset);
 
-out_toosmall:
-	resp->cookie_offset = 0;
-	resp->common.err = nfserr_toosmall;
-	resp->dirlist.len = starting_length;
-	return -EINVAL;
+	namlen = min_t(int, de->namlen, NFS_MAXNAMLEN);
+
+	/* value-follows + fileid + name (length + data) + cookie */
+	need = XDR_UNIT * (4 + XDR_QUADLEN(namlen));
+	if (need > resp->space_left)
+		return false;
+	resp->space_left -= need;
+
+	out->fileid = (u32)de->ino;
+	out->name.len = namlen;
+	out->name.data = (unsigned char *)de->name;
+	memset(out->cookie, 0, sizeof(out->cookie));	/* back-patched later */
+
+	trace_nfsd_dirent(resp->iter.fhp, de->ino, de->name, namlen);
+	return true;
+}
+
+/**
+ * nfs2_readdirok_encode_end - finish the streamed entry list
+ * @c: aggregate cursor for the entry list
+ * @ok: false if the framing hit a wire error while encoding the list
+ *
+ * Back-patch the final entry's cookie with the directory's resume
+ * offset and report eof.  A list cut short by the reply budget or a
+ * host error reports eof false, so the client reads the rest with a
+ * follow-up request.
+ *
+ * Return: true.
+ */
+bool nfs2_readdirok_encode_end(struct xdrgen_aggregate_cursor *c, bool ok)
+{
+	struct svc_rqst *rqstp = c->ctx;
+	struct readdirres_wrapper *resp = rqstp->rq_resp;
+	struct xdr_stream *xdr = c->xdr;
+
+	if (ok)
+		nfssvc_encode_nfscookie(xdr, resp->cookie_offset,
+					(u32)resp->iter.offset);
+	resp->xdrgen.u.readdirok.eof = resp->iter.eof;
+
+	/*
+	 * The xdr_stream primitives don't manage rq_next_page, and
+	 * svcrdma retains only the pages below it for Send completion.
+	 * The eof word that follows starts a new page when this one is
+	 * full.
+	 */
+	rqstp->rq_next_page = xdr->page_ptr + 1;
+	if (xdr->p == xdr->end)
+		rqstp->rq_next_page++;
+	return true;
+}
+
+/*
+ * A server never decodes a READDIR result; these satisfy the linkage
+ * of the generated (unused) decoder.
+ */
+bool nfs2_readdirok_decode_begin(struct xdrgen_aggregate_cursor *c)
+{
+	return false;
+}
+
+bool nfs2_readdirok_decode(struct xdrgen_aggregate_cursor *c,
+			   const struct entry *in)
+{
+	return false;
+}
+
+bool nfs2_readdirok_decode_end(struct xdrgen_aggregate_cursor *c, bool ok)
+{
+	return false;
 }
 
 /*
@@ -312,3 +377,11 @@ void nfssvc_release_attrstat(struct svc_rqst *rqstp)
 
 	fh_put(&resp->fh);
 }
+
+void nfssvc_release_readdirres(struct svc_rqst *rqstp)
+{
+	struct readdirres_wrapper *resp = rqstp->rq_resp;
+
+	nfsd_readdir_close(&resp->iter);
+	fh_put(&resp->fh);
+}
diff --git a/fs/nfsd/xdr.h b/fs/nfsd/xdr.h
index 57dbf5166599..9da15a9ff242 100644
--- a/fs/nfsd/xdr.h
+++ b/fs/nfsd/xdr.h
@@ -14,28 +14,37 @@ struct nfsd_attrstat {
 	struct kstat		stat;
 };
 
-struct nfsd_readdirres {
-	/* Components of the reply */
-	__be32			status;
+/*
+ * Declared here rather than in nfsproc.c with the other proc wrappers
+ * because READDIR's encode hooks in nfsxdr.c reference it. The xdrgen
+ * field must be first so the struct can be cast to its XDR type for the
+ * RPC dispatch layer.
+ *
+ * The file handle lives here rather than in an argument wrapper: the
+ * RPC layer zeroes the result before decode, so ->pc_release can run on
+ * a request that failed to decode.
+ */
+struct readdirres_wrapper {
+	struct readdirres	xdrgen;
+	struct svc_fh		fh;
 
-	int			count;
-
-	/* Used to encode the reply's entry list */
-	struct xdr_stream	xdr;
-	struct xdr_buf		dirlist;
-	struct readdir_cd	common;
-	unsigned int		cookie_offset;
+	/* Streaming state for encoding the reply's entry list */
+	struct nfsd_readdir_iter iter;		/* directory reader */
+	u32			count;		/* client's reply size hint */
+	u32			space_left;	/* remaining entry budget */
+	unsigned int		cookie_offset;	/* prev entry's cookie slot */
 };
 
+static_assert(offsetof(struct readdirres_wrapper, xdrgen) == 0);
+
 bool nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 bool nfssvc_encode_attrstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfssvc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 void nfssvc_encode_nfscookie(struct xdr_stream *xdr, unsigned int pos,
 			     u32 cookie);
-int nfssvc_encode_entry(void *data, const char *name, int namlen,
-			loff_t offset, u64 ino, unsigned int d_type);
+
+void nfssvc_release_readdirres(struct svc_rqst *rqstp);
 
 void nfssvc_release_attrstat(struct svc_rqst *rqstp);
 
diff --git a/include/linux/sunrpc/xdrgen/nfs2.h b/include/linux/sunrpc/xdrgen/nfs2.h
index 8f7b79983e3e..1d26de39ac69 100644
--- a/include/linux/sunrpc/xdrgen/nfs2.h
+++ b/include/linux/sunrpc/xdrgen/nfs2.h
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: GPL-2.0 */
 /* Generated by xdrgen. Manual edits will be lost. */
 /* XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x */
-/* XDR specification modification time: Tue Sep  8 11:26:14 2026 */
+/* XDR specification modification time: Tue Sep  8 11:26:28 2026 */
 
 #ifndef _LINUX_XDRGEN_NFS2_DEF_H
 #define _LINUX_XDRGEN_NFS2_DEF_H
-- 
2.55.0


  parent reply	other threads:[~2026-09-16 16:29 UTC|newest]

Thread overview: 34+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
2026-09-16 16:28 ` [PATCH v1 01/27] Documentation: Add the RPC language description of NFSv2 Chuck Lever
2026-09-16 16:28 ` [PATCH v1 1/2] NFSD: map fh_verify() status codes for NFS_ACLv2 replies Chuck Lever
2026-09-17 11:57   ` Jeff Layton
2026-09-16 16:28 ` [PATCH v1 02/27] NFSD: Add infrastructure for generating NFSv2 XDR encoders and decoders Chuck Lever
2026-09-16 16:28 ` [PATCH v1 2/2] NFSD: map fh_verify() status codes for NFS_ACLv3 replies Chuck Lever
2026-09-17 11:57   ` Jeff Layton
2026-09-16 16:28 ` [PATCH v1 03/27] NFSD: Use xdrgen-generated NFSv2 protocol definitions Chuck Lever
2026-09-16 16:28 ` [PATCH v1 04/27] NFSD: Remove '#include "xdr.h"' from fs/nfsd/xdr3.h Chuck Lever
2026-09-16 16:28 ` [PATCH v1 05/27] NFSD: Relocate the NFSv2 XDR storage union into nfsproc.c Chuck Lever
2026-09-16 16:28 ` [PATCH v1 06/27] NFSD: Use xdrgen XDR functions for the NFSv2 NULL procedure Chuck Lever
2026-09-16 16:28 ` [PATCH v1 07/27] NFSD: Use xdrgen XDR functions for NFSv2 GETATTR procedure Chuck Lever
2026-09-16 16:28 ` [PATCH v1 08/27] NFSD: Use xdrgen XDR functions for NFSv2 SETATTR procedure Chuck Lever
2026-09-16 16:28 ` [PATCH v1 09/27] NFSD: Use xdrgen XDR functions for the NFSv2 ROOT procedure Chuck Lever
2026-09-16 16:28 ` [PATCH v1 10/27] NFSD: Use xdrgen XDR functions for the NFSv2 LOOKUP procedure Chuck Lever
2026-09-16 16:28 ` [PATCH v1 11/27] NFSD: Use xdrgen XDR functions for NFSv2 READLINK procedure Chuck Lever
2026-09-16 16:28 ` [PATCH v1 12/27] NFSD: Use xdrgen XDR functions for NFSv2 READ procedure Chuck Lever
2026-09-16 16:28 ` [PATCH v1 13/27] NFSD: Use xdrgen XDR functions for the NFSv2 WRITECACHE procedure Chuck Lever
2026-09-16 16:28 ` [PATCH v1 14/27] NFSD: Use xdrgen XDR functions for NFSv2 WRITE procedure Chuck Lever
2026-09-16 16:28 ` [PATCH v1 15/27] NFSD: Refactor nfsd_proc_create() Chuck Lever
2026-09-16 16:28 ` [PATCH v1 16/27] NFSD: Use xdrgen XDR functions for NFSv2 CREATE procedure Chuck Lever
2026-09-16 16:28 ` [PATCH v1 17/27] NFSD: Use xdrgen XDR functions for the NFSv2 REMOVE procedure Chuck Lever
2026-09-16 16:28 ` [PATCH v1 18/27] NFSD: Use xdrgen XDR functions for the NFSv2 RENAME procedure Chuck Lever
2026-09-16 16:28 ` [PATCH v1 19/27] NFSD: Use xdrgen XDR functions for the NFSv2 LINK procedure Chuck Lever
2026-09-16 16:28 ` [PATCH v1 20/27] NFSD: Use xdrgen XDR functions for NFSv2 SYMLINK procedure Chuck Lever
2026-09-16 16:28 ` [PATCH v1 21/27] NFSD: Use xdrgen XDR functions for NFSv2 MKDIR procedure Chuck Lever
2026-09-16 16:28 ` [PATCH v1 22/27] NFSD: Use xdrgen XDR functions for NFSv2 RMDIR procedure Chuck Lever
2026-09-16 16:28 ` [PATCH v1 23/27] NFSD: Use xdrgen XDR functions for the NFSv2 STATFS procedure Chuck Lever
2026-09-16 16:28 ` [PATCH v1 24/27] NFSD: Use xdrgen XDR functions for NFSv2 READDIR arguments Chuck Lever
2026-09-16 16:28 ` [PATCH v1 25/27] NFSD: Add a streaming directory reader Chuck Lever
2026-09-16 16:28 ` [PATCH v1 26/27] NFSD: Refactor NFSv2 directory cookie encoding Chuck Lever
2026-09-16 16:28 ` Chuck Lever [this message]
2026-09-17 11:56 ` [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Jeff Layton
2026-09-17 14:57   ` Chuck Lever

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260916162859.2051-30-cel@kernel.org \
    --to=cel@kernel.org \
    --cc=dai.ngo@oracle.com \
    --cc=jlayton@kernel.org \
    --cc=linux-nfs@vger.kernel.org \
    --cc=neil@brown.name \
    --cc=okorniev@redhat.com \
    --cc=tom@talpey.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.