All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 30/34] NFSD: Refactor NFSv3 directory entry encoding
@ 2026-10-13 12:49 Chuck Lever
  2026-10-13 12:49 ` [PATCH v3 31/34] NFSD: Split struct nfsd3_readdirres Chuck Lever
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Chuck Lever @ 2026-10-13 12:49 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

The current synopsis of svcxdr_encode_entry3_common() takes a
"struct nfsd3_readdirres" argument, which is common to the NFSv3
READDIR and READDIRPLUS procedures.

However, xdrgen defines two distinct argument structures for these
procedures, since they are actually two distinct RPC procedures and
have different arguments.

With both a "struct READDIR3args" and a "struct READDIRPLUS3args,"
the directory entry encoders will need to incorporate the common
entry encoding.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
 fs/nfsd/nfs3xdr.c | 70 +++++++++++++++++++++++------------------------
 1 file changed, 34 insertions(+), 36 deletions(-)

diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index 279b19d5e3ac..a28c3e636284 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -306,7 +306,7 @@ compose_entry_fh(struct nfsd3_readdirres *cd, struct svc_fh *fhp,
  * @cookie: cookie to be encoded
  *
  * The buffer space for the offset cookie has already been reserved
- * by svcxdr_encode_entry3_common().
+ * by nfs3svc_encode_entry3() or nfs3svc_encode_entryplus3().
  */
 void nfs3svc_encode_cookie3(struct xdr_stream *xdr, unsigned int pos,
 			    u64 cookie)
@@ -318,29 +318,6 @@ void nfs3svc_encode_cookie3(struct xdr_stream *xdr, unsigned int pos,
 	write_bytes_to_xdr_buf(xdr->buf, pos, &wire_cookie, XDR_UNIT * 2);
 }
 
-static bool
-svcxdr_encode_entry3_common(struct nfsd3_readdirres *resp, const char *name,
-			    int namlen, loff_t offset, u64 ino)
-{
-	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_u64(xdr, ino) < 0)
-		return false;
-	/* name */
-	if (xdr_stream_encode_opaque(xdr, name, min(namlen, NFS3_MAXNAMLEN)) < 0)
-		return false;
-	/* cookie */
-	resp->cookie_offset = dirlist->len;
-	if (xdr_stream_encode_u64(xdr, OFFSET_MAX) < 0)
-		return false;
-
-	return true;
-}
-
 /**
  * nfs3svc_encode_entry3 - encode one NFSv3 READDIR entry
  * @data: directory context
@@ -363,18 +340,28 @@ int nfs3svc_encode_entry3(void *data, const char *name, int namlen,
 			  loff_t offset, u64 ino, unsigned int d_type)
 {
 	struct readdir_cd *ccd = data;
-	struct nfsd3_readdirres *resp = container_of(ccd,
-						     struct nfsd3_readdirres,
-						     common);
+	struct nfsd3_readdirres *resp =
+		container_of(ccd, struct nfsd3_readdirres, common);
 	unsigned int starting_length = resp->dirlist.len;
+	struct xdr_stream *xdr = &resp->xdr;
 
 	/* The offset cookie for the previous entry */
-	nfs3svc_encode_cookie3(&resp->xdr, resp->cookie_offset, offset);
+	nfs3svc_encode_cookie3(xdr, resp->cookie_offset, offset);
 
-	if (!svcxdr_encode_entry3_common(resp, name, namlen, offset, ino))
+	if (xdr_stream_encode_item_present(xdr) < 0)
+		goto out_toosmall;
+	/* fileid */
+	if (xdr_stream_encode_u64(xdr, ino) < 0)
+		goto out_toosmall;
+	/* name */
+	if (xdr_stream_encode_opaque(xdr, name, min(namlen, NFS3_MAXNAMLEN)) < 0)
+		goto out_toosmall;
+	/* cookie */
+	resp->cookie_offset = resp->dirlist.len;
+	if (xdr_stream_encode_u64(xdr, OFFSET_MAX) < 0)
 		goto out_toosmall;
 
-	xdr_commit_encode(&resp->xdr);
+	xdr_commit_encode(xdr);
 	resp->common.err = nfs_ok;
 	return 0;
 
@@ -438,20 +425,31 @@ int nfs3svc_encode_entryplus3(void *data, const char *name, int namlen,
 			      loff_t offset, u64 ino, unsigned int d_type)
 {
 	struct readdir_cd *ccd = data;
-	struct nfsd3_readdirres *resp = container_of(ccd,
-						     struct nfsd3_readdirres,
-						     common);
+	struct nfsd3_readdirres *resp =
+		container_of(ccd, struct nfsd3_readdirres, common);
 	unsigned int starting_length = resp->dirlist.len;
+	struct xdr_stream *xdr = &resp->xdr;
 
 	/* The offset cookie for the previous entry */
-	nfs3svc_encode_cookie3(&resp->xdr, resp->cookie_offset, offset);
+	nfs3svc_encode_cookie3(xdr, resp->cookie_offset, offset);
 
-	if (!svcxdr_encode_entry3_common(resp, name, namlen, offset, ino))
+	if (xdr_stream_encode_item_present(xdr) < 0)
 		goto out_toosmall;
+	/* fileid */
+	if (xdr_stream_encode_u64(xdr, ino) < 0)
+		goto out_toosmall;
+	/* name */
+	if (xdr_stream_encode_opaque(xdr, name, min(namlen, NFS3_MAXNAMLEN)) < 0)
+		goto out_toosmall;
+	/* cookie */
+	resp->cookie_offset = resp->dirlist.len;
+	if (xdr_stream_encode_u64(xdr, OFFSET_MAX) < 0)
+		goto out_toosmall;
+
 	if (!svcxdr_encode_entry3_plus(resp, name, namlen, ino))
 		goto out_toosmall;
 
-	xdr_commit_encode(&resp->xdr);
+	xdr_commit_encode(xdr);
 	resp->common.err = nfs_ok;
 	return 0;
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v3 31/34] NFSD: Split struct nfsd3_readdirres
  2026-10-13 12:49 [PATCH v3 30/34] NFSD: Refactor NFSv3 directory entry encoding Chuck Lever
@ 2026-10-13 12:49 ` Chuck Lever
  2026-10-13 12:49 ` [PATCH v3 32/34] NFSD: Use xdrgen XDR functions for NFSv3 READDIR results Chuck Lever
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Chuck Lever @ 2026-10-13 12:49 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Currently NFSv3 READDIR and READDIRPLUS share the same C structure
for encoding their results.

xdrgen defines two distinct result structures for the NFSv3 READDIR
and READDIRPLUS procedures since they are actually two distinct
procedures and have different result types.

Define a duplicate of struct nfsd3_readdirres to be used with
READDIRPLUS. Then duplicate nfsd3_init_dirlist_pages() and
nfs3svc_encode_readdirplusres() . These duplicates will use the new
result type for NFSv3 READDIRPLUS.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs3proc.c | 30 ++++++++++++++++++++++++++----
 fs/nfsd/nfs3xdr.c  | 39 ++++++++++++++++++++++++++++++++++-----
 fs/nfsd/xdr3.h     | 17 +++++++++++++++++
 3 files changed, 77 insertions(+), 9 deletions(-)

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 9d2fcb697e72..7020f044df64 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -1238,6 +1238,27 @@ static __be32 nfsd3_proc_symlink(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
+static void
+nfsd3_init_dirlistplus_pages(struct svc_rqst *rqstp,
+			     struct nfsd3_readdirplusres *resp,
+			     u32 count)
+{
+	struct xdr_buf *buf = &resp->dirlist;
+	struct xdr_stream *xdr = &resp->xdr;
+	unsigned int sendbuf = min_t(unsigned int, rqstp->rq_res.buflen,
+				     svc_max_payload(rqstp));
+
+	memset(buf, 0, sizeof(*buf));
+
+	/* Reserve room for the NULL ptr & eof flag (-2 words) */
+	buf->buflen = clamp(count, (u32)(XDR_UNIT * 2), sendbuf);
+	buf->buflen -= XDR_UNIT * 2;
+	buf->pages = rqstp->rq_next_page;
+	rqstp->rq_next_page += (buf->buflen + PAGE_SIZE - 1) >> PAGE_SHIFT;
+
+	xdr_init_encode_pages(xdr, buf);
+}
+
 /**
  * nfsd3_proc_mknod - MKNOD: Create a special file
  * @rqstp: RPC transaction context
@@ -1571,7 +1592,7 @@ static __be32 nfsd3_proc_readdir(struct svc_rqst *rqstp)
 static __be32 nfsd3_proc_readdirplus(struct svc_rqst *rqstp)
 {
 	struct READDIRPLUS3args_wrapper *argp = rqstp->rq_argp;
-	struct nfsd3_readdirres  *resp = rqstp->rq_resp;
+	struct nfsd3_readdirplusres *resp = rqstp->rq_resp;
 	loff_t offset = argp->xdrgen.cookie;
 	struct svc_fh *fhp = &argp->fh;
 
@@ -1579,7 +1600,7 @@ static __be32 nfsd3_proc_readdirplus(struct svc_rqst *rqstp)
 	trace_nfsd_vfs_readdir(rqstp, fhp, argp->xdrgen.maxcount,
 			       argp->xdrgen.cookie);
 
-	nfsd3_init_dirlist_pages(rqstp, resp, argp->xdrgen.maxcount);
+	nfsd3_init_dirlistplus_pages(rqstp, resp, argp->xdrgen.maxcount);
 	fh_copy(&resp->fh, fhp);
 	resp->common.err = nfs_ok;
 	resp->cookie_offset = 0;
@@ -2030,11 +2051,11 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 	[NFSPROC3_READDIRPLUS] = {
 		.pc_func	= nfsd3_proc_readdirplus,
 		.pc_decode	= nfs_svc_decode_READDIRPLUS3args,
-		.pc_encode	= nfs3svc_encode_readdirres,
+		.pc_encode	= nfs3svc_encode_readdirplusres,
 		.pc_release	= nfs3svc_release_fhandle,
 		.pc_argsize	= sizeof(struct READDIRPLUS3args_wrapper),
 		.pc_argzero	= 0,
-		.pc_ressize	= sizeof(struct nfsd3_readdirres),
+		.pc_ressize	= sizeof(struct nfsd3_readdirplusres),
 		.pc_cachetype	= RC_NOCACHE,
 		.pc_name	= "READDIRPLUS",
 	},
@@ -2121,6 +2142,7 @@ union nfsd3_xdrstore {
 	struct READ3res			readres;
 	struct nfsd3_writeres		writeres;
 	struct nfsd3_readdirres		readdirres;
+	struct nfsd3_readdirplusres	readdirplusres;
 	struct FSSTAT3args_wrapper	fsstatargs;
 	struct nfsd3_fsstatres		fsstatres;
 	struct FSINFO3args_wrapper	fsinfoargs;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index a28c3e636284..2d00343ed202 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -228,7 +228,6 @@ svcxdr_encode_post_op_attr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
  * XDR encode functions
  */
 
-/* READDIR */
 bool
 nfs3svc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 {
@@ -259,8 +258,38 @@ nfs3svc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 	return true;
 }
 
+bool
+nfs3svc_encode_readdirplusres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct nfsd3_readdirplusres *resp = rqstp->rq_resp;
+	struct xdr_buf *dirlist = &resp->dirlist;
+
+	if (!svcxdr_encode_nfsstat3(xdr, resp->status))
+		return false;
+	switch (resp->status) {
+	case nfs_ok:
+		if (!svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh))
+			return false;
+		if (!svcxdr_encode_cookieverf3(xdr, resp->verf))
+			return false;
+		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;
+	default:
+		if (!svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh))
+			return false;
+	}
+
+	return true;
+}
+
 static __be32
-compose_entry_fh(struct nfsd3_readdirres *cd, struct svc_fh *fhp,
+compose_entry_fh(struct nfsd3_readdirplusres *cd, struct svc_fh *fhp,
 		 const char *name, int namlen, u64 ino)
 {
 	struct svc_export	*exp;
@@ -373,7 +402,7 @@ int nfs3svc_encode_entry3(void *data, const char *name, int namlen,
 }
 
 static bool
-svcxdr_encode_entry3_plus(struct nfsd3_readdirres *resp, const char *name,
+svcxdr_encode_entry3_plus(struct nfsd3_readdirplusres *resp, const char *name,
 			  int namlen, u64 ino)
 {
 	struct xdr_stream *xdr = &resp->xdr;
@@ -425,8 +454,8 @@ int nfs3svc_encode_entryplus3(void *data, const char *name, int namlen,
 			      loff_t offset, u64 ino, unsigned int d_type)
 {
 	struct readdir_cd *ccd = data;
-	struct nfsd3_readdirres *resp =
-		container_of(ccd, struct nfsd3_readdirres, common);
+	struct nfsd3_readdirplusres *resp =
+		container_of(ccd, struct nfsd3_readdirplusres, common);
 	unsigned int starting_length = resp->dirlist.len;
 	struct xdr_stream *xdr = &resp->xdr;
 
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index fdb2e68e16d5..0e97f54360a8 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -72,6 +72,22 @@ struct nfsd3_readdirres {
 
 };
 
+struct nfsd3_readdirplusres {
+	/* Components of the reply */
+	__be32			status;
+	struct svc_fh		fh;
+	__be32			verf[2];
+
+	/* Used to encode the reply's entry list */
+	struct xdr_stream	xdr;
+	struct xdr_buf		dirlist;
+	struct svc_fh		scratch;
+	struct readdir_cd	common;
+	unsigned int		cookie_offset;
+	struct svc_rqst *	rqstp;
+
+};
+
 struct nfsd3_fsstatres {
 	struct FSSTAT3res	xdrgen;
 	struct kstatfs		stats;
@@ -108,6 +124,7 @@ struct nfsd3_getaclres {
 
 bool nfs3svc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs3svc_encode_readdirplusres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 void nfs3svc_release_fhandle(struct svc_rqst *);
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v3 32/34] NFSD: Use xdrgen XDR functions for NFSv3 READDIR results
  2026-10-13 12:49 [PATCH v3 30/34] NFSD: Refactor NFSv3 directory entry encoding Chuck Lever
  2026-10-13 12:49 ` [PATCH v3 31/34] NFSD: Split struct nfsd3_readdirres Chuck Lever
@ 2026-10-13 12:49 ` Chuck Lever
  2026-10-13 12:49 ` [PATCH v3 33/34] NFSD: Use xdrgen XDR functions for NFSv3 READDIRPLUS results Chuck Lever
  2026-10-13 12:49 ` [PATCH v3 34/34] SUNRPC: Remove svc_fill_symlink_pathname() Chuck Lever
  3 siblings, 0 replies; 5+ messages in thread
From: Chuck Lever @ 2026-10-13 12:49 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

NFSv3 READDIR's reply is still produced by a hand-written encoder.
Its entry list is the value-follows form of RFC 1813's "entry3
*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 opened, and the aggregate loop was bounded by a
count the begin hook had to supply up front.

The preceding NFSv2 work removed both obstacles in shared code. The
optional-data aggregate encoder 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 dirlist3's entry list as an
aggregate and regenerate; the struct definitions stay the verbatim
RFC 1813 types, and the lone "pragma aggregate dirlist3 entries"
selects the hook-driven codec.

Split the read across the two phases so the generated encoder owns
the framing. nfsd3_proc_readdir() opens the directory, where an open
error becomes the reply status the union encoder then writes, and
fills the directory attributes and cookie verifier that precede the
entry list. 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. The end hook also advances
rq_next_page past the pages the entry list filled, because svcrdma
and svc_rqst_release_pages() handle only the reply pages below
it. nfs3svc_release_readdirres() closes the reader and releases the
file handle. Set pc_argzero to clear the argument wrapper, because
svc_release_rqst() calls that hook even when argument decoding fails
and nfsd3_proc_readdir() never initializes the file handle.

struct nfsd3_readdirres now embeds struct READDIR3res so the RPC
dispatch layer can cast the response to its xdrgen type, and holds the
streaming reader in place of the old page-buffer state.
READDIR3args_wrapper moves to xdr3.h so the encode and release paths
can reach the directory file handle.

A host error during the streamed read ends the list as a truncated,
non-eof reply rather than an error status. By then the generated
union encoder has committed NFS3_OK and the resok fields ahead of
the entry list, and the aggregate hooks cannot rewind the stream to
re-encode the reply as resfail. The client resumes from the last
cookie, so its follow-up READDIR starts at the failing batch, and
nfsd_readdir_open() reports the error as the reply status there. An
error opening the directory is still reported as the reply status.
The bytes on the wire are otherwise unchanged.

nfs3svc_encode_readdirres() and nfs3svc_encode_entry3() no longer have
any callers, so they are removed.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 Documentation/sunrpc/xdr/nfs3.x    |  10 ++
 fs/nfsd/nfs3proc.c                 |  68 ++++-----
 fs/nfsd/nfs3xdr.c                  | 226 +++++++++++++++++++----------
 fs/nfsd/nfs3xdr_gen.c              |  35 ++++-
 fs/nfsd/nfs3xdr_gen.h              |  13 +-
 fs/nfsd/xdr3.h                     |  39 +++--
 include/linux/sunrpc/xdrgen/nfs3.h |   2 +-
 7 files changed, 245 insertions(+), 148 deletions(-)

diff --git a/Documentation/sunrpc/xdr/nfs3.x b/Documentation/sunrpc/xdr/nfs3.x
index c987788bf34c..cf743a8a5f3d 100644
--- a/Documentation/sunrpc/xdr/nfs3.x
+++ b/Documentation/sunrpc/xdr/nfs3.x
@@ -627,6 +627,16 @@ struct dirlist3 {
 	bool		eof;
 };
 
+/*
+ * The entry list encodes as the value-follows form of "entry3
+ * *entries" -- 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 1813 types; only this
+ * pragma selects the hook-driven codec.
+ */
+pragma aggregate dirlist3 entries;
+
 struct READDIR3resok {
 	post_op_attr	dir_attributes;
 	cookieverf3	cookieverf;
diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 7020f044df64..daf7be8e961b 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -218,13 +218,6 @@ struct COMMIT3args_wrapper {
 
 static_assert(offsetof(struct COMMIT3args_wrapper, xdrgen) == 0);
 
-struct READDIR3args_wrapper {
-	struct READDIR3args	xdrgen;
-	struct svc_fh		fh;
-};
-
-static_assert(offsetof(struct READDIR3args_wrapper, xdrgen) == 0);
-
 struct READDIRPLUS3args_wrapper {
 	struct READDIRPLUS3args	xdrgen;
 	struct svc_fh		fh;
@@ -1521,30 +1514,16 @@ static __be32 nfsd3_proc_link(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-static void nfsd3_init_dirlist_pages(struct svc_rqst *rqstp,
-				     struct nfsd3_readdirres *resp,
-				     u32 count)
-{
-	struct xdr_buf *buf = &resp->dirlist;
-	struct xdr_stream *xdr = &resp->xdr;
-	unsigned int sendbuf = min_t(unsigned int, rqstp->rq_res.buflen,
-				     svc_max_payload(rqstp));
-
-	memset(buf, 0, sizeof(*buf));
-
-	/* Reserve room for the NULL ptr & eof flag (-2 words) */
-	buf->buflen = clamp(count, (u32)(XDR_UNIT * 2), sendbuf);
-	buf->buflen -= XDR_UNIT * 2;
-	buf->pages = rqstp->rq_next_page;
-	rqstp->rq_next_page += (buf->buflen + PAGE_SIZE - 1) >> PAGE_SHIFT;
-
-	xdr_init_encode_pages(xdr, buf);
-}
-
 /**
  * nfsd3_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's directory reader during
+ * encode; nfs3svc_release_readdirres() closes the reader and releases
+ * the file handle.
+ *
  * Return:
  *   %rpc_success:		RPC executed successfully
  *
@@ -1554,28 +1533,31 @@ static void nfsd3_init_dirlist_pages(struct svc_rqst *rqstp,
 static __be32 nfsd3_proc_readdir(struct svc_rqst *rqstp)
 {
 	struct READDIR3args_wrapper *argp = rqstp->rq_argp;
-	struct nfsd3_readdirres  *resp = rqstp->rq_resp;
+	struct nfsd3_readdirres *resp = rqstp->rq_resp;
 	loff_t offset = argp->xdrgen.cookie;
 	struct svc_fh *fhp = &argp->fh;
+	__be32 status;
 
 	nfsd3_fh3_to_svc_fh(fhp, &argp->xdrgen.dir);
 	trace_nfsd_vfs_readdir(rqstp, fhp, argp->xdrgen.count,
 			       argp->xdrgen.cookie);
 
-	nfsd3_init_dirlist_pages(rqstp, resp, argp->xdrgen.count);
-	fh_copy(&resp->fh, fhp);
-	resp->common.err = nfs_ok;
-	resp->cookie_offset = 0;
-	resp->rqstp = rqstp;
-	resp->status = nfsd_readdir(rqstp, &resp->fh, &offset,
-				    &resp->common, nfs3svc_encode_entry3);
-	memcpy(resp->verf, argp->xdrgen.cookieverf, NFS3_COOKIEVERFSIZE);
-	nfs3svc_encode_cookie3(&resp->xdr, resp->cookie_offset, offset);
+	resp->count = argp->xdrgen.count;
+	status = nfsd_readdir_open(rqstp, fhp, &offset, &resp->iter);
 
-	/* Recycle only pages that were part of the reply */
-	rqstp->rq_next_page = resp->xdr.page_ptr + 1;
+	if (status == nfs_ok) {
+		struct READDIR3resok *resok = &resp->xdrgen.u.resok;
 
-	resp->status = nfsd3_map_status(resp->status);
+		memcpy(resok->cookieverf, argp->xdrgen.cookieverf,
+		       NFS3_COOKIEVERFSIZE);
+		nfsd3_fill_post_op_attr(rqstp, &resok->dir_attributes, fhp);
+	} else {
+		struct READDIR3resfail *resfail = &resp->xdrgen.u.resfail;
+
+		nfsd3_fill_post_op_attr(rqstp, &resfail->dir_attributes, fhp);
+	}
+
+	resp->xdrgen.status = nfsd3_map_status(status);
 	return rpc_success;
 }
 
@@ -2040,10 +2022,10 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 	[NFSPROC3_READDIR] = {
 		.pc_func	= nfsd3_proc_readdir,
 		.pc_decode	= nfs_svc_decode_READDIR3args,
-		.pc_encode	= nfs3svc_encode_readdirres,
-		.pc_release	= nfs3svc_release_fhandle,
+		.pc_encode	= nfs_svc_encode_READDIR3res,
+		.pc_release	= nfs3svc_release_readdirres,
 		.pc_argsize	= sizeof(struct READDIR3args_wrapper),
-		.pc_argzero	= 0,
+		.pc_argzero	= sizeof(struct READDIR3args_wrapper),
 		.pc_ressize	= sizeof(struct nfsd3_readdirres),
 		.pc_cachetype	= RC_NOCACHE,
 		.pc_name	= "READDIR",
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index 2d00343ed202..52a178fe69bb 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -16,6 +16,9 @@
 #include "netns.h"
 #include "vfs.h"
 #include "nfserr.h"
+#include "trace.h"
+
+#include "nfs3xdr_gen.h"
 
 /*
  * Mapping of S_IF* types to NFS file types
@@ -228,33 +231,141 @@ svcxdr_encode_post_op_attr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
  * XDR encode functions
  */
 
-bool
-nfs3svc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_readdirres *resp = rqstp->rq_resp;
-	struct xdr_buf *dirlist = &resp->dirlist;
+/*
+ * READDIR reply entry list (RFC 1813).  The entry list is the
+ * value-follows form of "entry3 *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
+ * nfsd3_proc_readdir(), so reading and encoding happen together here
+ * during reply encoding.
+ */
 
-	if (!svcxdr_encode_nfsstat3(xdr, resp->status))
-		return false;
-	switch (resp->status) {
-	case nfs_ok:
-		if (!svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh))
-			return false;
-		if (!svcxdr_encode_cookieverf3(xdr, resp->verf))
-			return false;
-		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;
-	default:
-		if (!svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh))
-			return false;
+/**
+ * nfs3_dirlist3_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 nfs3_dirlist3_encode_begin(struct xdrgen_aggregate_cursor *c)
+{
+	struct svc_rqst *rqstp = c->ctx;
+	struct nfsd3_readdirres *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)svc_max_payload(rqstp)) -
+				 XDR_UNIT * 2);
+	resp->cookie_offset = 0;
+	return true;
+}
+
+/**
+ * nfs3_dirlist3_encode - stream the next READDIR entry
+ * @c: aggregate cursor for the entry list
+ * @out: OUT: entry the framing encodes when one is produced
+ *
+ * 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.
+ *
+ * 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.
+ */
+bool nfs3_dirlist3_encode(struct xdrgen_aggregate_cursor *c,
+			  struct entry3 *out)
+{
+	struct svc_rqst *rqstp = c->ctx;
+	struct nfsd3_readdirres *resp = rqstp->rq_resp;
+	struct xdr_stream *xdr = c->xdr;
+	struct buffered_dirent *de;
+	int namlen;
+	u32 need;
+
+	/*
+	 * The previous entry was just encoded; commit the stream and note
+	 * its cookie slot -- the entry's last two XDR words -- 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 * 2;
 	}
 
+	de = nfsd_readdir_next(&resp->iter);
+	if (!de)
+		return false;
+
+	/* The previous entry's cookie is this entry's resume offset. */
+	nfs3svc_encode_cookie3(xdr, resp->cookie_offset, de->offset);
+
+	namlen = min_t(int, de->namlen, NFS3_MAXNAMLEN);
+
+	/* value-follows + fileid + name (length + data) + cookie */
+	need = XDR_UNIT * (6 + XDR_QUADLEN(namlen));
+	if (need > resp->space_left)
+		return false;
+	resp->space_left -= need;
+
+	out->fileid = de->ino;
+	out->name.len = namlen;
+	out->name.data = (unsigned char *)de->name;
+	out->cookie = 0;	/* back-patched later */
+
+	trace_nfsd_dirent(resp->iter.fhp, de->ino, de->name, namlen);
+	return true;
+}
+
+/*
+ * The xdr_stream primitives do not advance rq_next_page, and svcrdma
+ * retains only the pages below it for Send completion. The eof word that
+ * follows the entry list starts a new page when this one is full.
+ */
+static void nfs3svc_update_next_page(struct svc_rqst *rqstp,
+				     struct xdr_stream *xdr)
+{
+	rqstp->rq_next_page = xdr->page_ptr + 1;
+	if (xdr->p == xdr->end)
+		rqstp->rq_next_page++;
+}
+
+/**
+ * nfs3_dirlist3_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 nfs3_dirlist3_encode_end(struct xdrgen_aggregate_cursor *c, bool ok)
+{
+	struct svc_rqst *rqstp = c->ctx;
+	struct nfsd3_readdirres *resp = rqstp->rq_resp;
+	struct xdr_stream *xdr = c->xdr;
+
+	if (ok)
+		nfs3svc_encode_cookie3(xdr, resp->cookie_offset,
+				       resp->iter.offset);
+	resp->xdrgen.u.resok.reply.eof = resp->iter.eof;
+	nfs3svc_update_next_page(rqstp, xdr);
 	return true;
 }
 
@@ -335,7 +446,7 @@ compose_entry_fh(struct nfsd3_readdirplusres *cd, struct svc_fh *fhp,
  * @cookie: cookie to be encoded
  *
  * The buffer space for the offset cookie has already been reserved
- * by nfs3svc_encode_entry3() or nfs3svc_encode_entryplus3().
+ * by nfs3_dirlist3_encode() or nfs3svc_encode_entryplus3().
  */
 void nfs3svc_encode_cookie3(struct xdr_stream *xdr, unsigned int pos,
 			    u64 cookie)
@@ -347,60 +458,6 @@ void nfs3svc_encode_cookie3(struct xdr_stream *xdr, unsigned int pos,
 	write_bytes_to_xdr_buf(xdr->buf, pos, &wire_cookie, XDR_UNIT * 2);
 }
 
-/**
- * nfs3svc_encode_entry3 - encode one NFSv3 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
- *
- * Return values:
- *   %0: Entry was successfully encoded.
- *   %-EINVAL: An encoding problem occurred, secondary status code in resp->common.err
- *
- * On exit, the following fields are updated:
- *   - resp->xdr
- *   - resp->common.err
- *   - resp->cookie_offset
- */
-int nfs3svc_encode_entry3(void *data, const char *name, int namlen,
-			  loff_t offset, u64 ino, unsigned int d_type)
-{
-	struct readdir_cd *ccd = data;
-	struct nfsd3_readdirres *resp =
-		container_of(ccd, struct nfsd3_readdirres, common);
-	unsigned int starting_length = resp->dirlist.len;
-	struct xdr_stream *xdr = &resp->xdr;
-
-	/* The offset cookie for the previous entry */
-	nfs3svc_encode_cookie3(xdr, resp->cookie_offset, offset);
-
-	if (xdr_stream_encode_item_present(xdr) < 0)
-		goto out_toosmall;
-	/* fileid */
-	if (xdr_stream_encode_u64(xdr, ino) < 0)
-		goto out_toosmall;
-	/* name */
-	if (xdr_stream_encode_opaque(xdr, name, min(namlen, NFS3_MAXNAMLEN)) < 0)
-		goto out_toosmall;
-	/* cookie */
-	resp->cookie_offset = resp->dirlist.len;
-	if (xdr_stream_encode_u64(xdr, OFFSET_MAX) < 0)
-		goto out_toosmall;
-
-	xdr_commit_encode(xdr);
-	resp->common.err = nfs_ok;
-	return 0;
-
-out_toosmall:
-	resp->cookie_offset = 0;
-	resp->common.err = nfserr_toosmall;
-	resp->dirlist.len = starting_length;
-	return -EINVAL;
-}
-
 static bool
 svcxdr_encode_entry3_plus(struct nfsd3_readdirplusres *resp, const char *name,
 			  int namlen, u64 ino)
@@ -499,3 +556,14 @@ nfs3svc_release_fhandle(struct svc_rqst *rqstp)
 
 	fh_put(&resp->fh);
 }
+
+void
+nfs3svc_release_readdirres(struct svc_rqst *rqstp)
+{
+	struct nfsd3_readdirres *resp = rqstp->rq_resp;
+	struct READDIR3args_wrapper *argp = rqstp->rq_argp;
+
+	/* argp->fh is zeroed by pc_argzero when argument decoding fails */
+	nfsd_readdir_close(&resp->iter);
+	fh_put(&argp->fh);
+}
diff --git a/fs/nfsd/nfs3xdr_gen.c b/fs/nfsd/nfs3xdr_gen.c
index 3667c160e789..452e456dc8da 100644
--- a/fs/nfsd/nfs3xdr_gen.c
+++ b/fs/nfsd/nfs3xdr_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/nfs3.x
-// XDR specification modification time: Tue Jul 14 12:13:57 2026
+// XDR specification modification time: Tue Jul 14 20:36:22 2026
 
 #include <linux/sunrpc/svc.h>
 
@@ -1258,11 +1258,7 @@ xdrgen_decode_entry3(struct xdr_stream *xdr, struct entry3 *ptr)
 static bool __maybe_unused
 xdrgen_decode_dirlist3(struct xdr_stream *xdr, struct dirlist3 *ptr)
 {
-	if (!xdrgen_decode_entry3(xdr, ptr->entries))
-		return false;
-	if (!xdrgen_decode_bool(xdr, &ptr->eof))
-		return false;
-	return true;
+	return false;
 }
 
 static bool __maybe_unused
@@ -3089,8 +3085,31 @@ xdrgen_encode_entry3(struct xdr_stream *xdr, const struct entry3 *value)
 static bool __maybe_unused
 xdrgen_encode_dirlist3(struct xdr_stream *xdr, const struct dirlist3 *value)
 {
-	if (!xdrgen_encode_entry3(xdr, value->entries))
-		return false;
+	{
+		struct xdrgen_aggregate_cursor cursor = {
+			.xdr = xdr,
+			.member_id = NFS3_DIRLIST3_ENTRIES,
+			.ctx = xdr->xdrgen_ctx,
+		};
+		bool ok = true;
+
+		if (!nfs3_dirlist3_encode_begin(&cursor))
+			return false;
+		for (cursor.index = 0; ; cursor.index++) {
+			struct entry3 element = {};
+
+			if (!nfs3_dirlist3_encode(&cursor, &element))
+				break;
+			if (!xdrgen_encode_entry3(xdr, &element)) {
+				ok = false;
+				break;
+			}
+		}
+		if (ok && !xdrgen_encode_entry3(xdr, NULL))
+			ok = false;
+		if (!nfs3_dirlist3_encode_end(&cursor, ok) || !ok)
+			return false;
+	}
 	if (!xdrgen_encode_bool(xdr, value->eof))
 		return false;
 	return true;
diff --git a/fs/nfsd/nfs3xdr_gen.h b/fs/nfsd/nfs3xdr_gen.h
index f336ffa92a7a..5c302645bfbe 100644
--- a/fs/nfsd/nfs3xdr_gen.h
+++ b/fs/nfsd/nfs3xdr_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/nfs3.x */
-/* XDR specification modification time: Tue Jul 14 12:13:57 2026 */
+/* XDR specification modification time: Tue Jul 14 20:36:22 2026 */
 
 #ifndef _LINUX_XDRGEN_NFS3_DECL_H
 #define _LINUX_XDRGEN_NFS3_DECL_H
@@ -13,6 +13,17 @@
 #include <linux/sunrpc/xdrgen/_builtins.h>
 #include <linux/sunrpc/xdrgen/nfs3.h>
 
+enum {
+	NFS3_DIRLIST3_ENTRIES,
+};
+
+bool nfs3_dirlist3_encode_begin(struct xdrgen_aggregate_cursor *c);
+bool nfs3_dirlist3_encode(struct xdrgen_aggregate_cursor *c, struct entry3 *out);
+bool nfs3_dirlist3_encode_end(struct xdrgen_aggregate_cursor *c, bool ok);
+bool nfs3_dirlist3_decode_begin(struct xdrgen_aggregate_cursor *c);
+bool nfs3_dirlist3_decode(struct xdrgen_aggregate_cursor *c, const struct entry3 *in);
+bool nfs3_dirlist3_decode_end(struct xdrgen_aggregate_cursor *c, bool ok);
+
 bool nfs_svc_decode_GETATTR3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs_svc_decode_SETATTR3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs_svc_decode_LOOKUP3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 0e97f54360a8..75de77aa8e13 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -56,22 +56,31 @@ struct nfsd3_writeres {
 
 static_assert(offsetof(struct nfsd3_writeres, xdrgen) == 0);
 
-struct nfsd3_readdirres {
-	/* Components of the reply */
-	__be32			status;
+/*
+ * Declared here rather than in nfs3proc.c with the other proc wrappers
+ * because READDIR's encode path in nfs3xdr.c reads the file handle from
+ * it during encode and release. The xdrgen field must be first so the
+ * struct can be cast to its XDR type for the RPC dispatch layer.
+ */
+struct READDIR3args_wrapper {
+	struct READDIR3args	xdrgen;
 	struct svc_fh		fh;
-	__be32			verf[2];
-
-	/* Used to encode the reply's entry list */
-	struct xdr_stream	xdr;
-	struct xdr_buf		dirlist;
-	struct svc_fh		scratch;
-	struct readdir_cd	common;
-	unsigned int		cookie_offset;
-	struct svc_rqst *	rqstp;
-
 };
 
+static_assert(offsetof(struct READDIR3args_wrapper, xdrgen) == 0);
+
+struct nfsd3_readdirres {
+	struct READDIR3res	xdrgen;
+
+	/* 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 nfsd3_readdirres, xdrgen) == 0);
+
 struct nfsd3_readdirplusres {
 	/* Components of the reply */
 	__be32			status;
@@ -123,15 +132,13 @@ struct nfsd3_getaclres {
 };
 
 bool nfs3svc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_readdirplusres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 void nfs3svc_release_fhandle(struct svc_rqst *);
+void nfs3svc_release_readdirres(struct svc_rqst *rqstp);
 
 void nfs3svc_encode_cookie3(struct xdr_stream *xdr, unsigned int pos,
 			    u64 cookie);
-int nfs3svc_encode_entry3(void *data, const char *name, int namlen,
-			  loff_t offset, u64 ino, unsigned int d_type);
 int nfs3svc_encode_entryplus3(void *data, const char *name, int namlen,
 			      loff_t offset, u64 ino, unsigned int d_type);
 /* Helper functions for NFSv3 ACL code */
diff --git a/include/linux/sunrpc/xdrgen/nfs3.h b/include/linux/sunrpc/xdrgen/nfs3.h
index 3e24abb1d2e5..27750bbab2ee 100644
--- a/include/linux/sunrpc/xdrgen/nfs3.h
+++ b/include/linux/sunrpc/xdrgen/nfs3.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/nfs3.x */
-/* XDR specification modification time: Tue Jul 14 12:13:57 2026 */
+/* XDR specification modification time: Tue Jul 14 20:36:22 2026 */
 
 #ifndef _LINUX_XDRGEN_NFS3_DEF_H
 #define _LINUX_XDRGEN_NFS3_DEF_H
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v3 33/34] NFSD: Use xdrgen XDR functions for NFSv3 READDIRPLUS results
  2026-10-13 12:49 [PATCH v3 30/34] NFSD: Refactor NFSv3 directory entry encoding Chuck Lever
  2026-10-13 12:49 ` [PATCH v3 31/34] NFSD: Split struct nfsd3_readdirres Chuck Lever
  2026-10-13 12:49 ` [PATCH v3 32/34] NFSD: Use xdrgen XDR functions for NFSv3 READDIR results Chuck Lever
@ 2026-10-13 12:49 ` Chuck Lever
  2026-10-13 12:49 ` [PATCH v3 34/34] SUNRPC: Remove svc_fill_symlink_pathname() Chuck Lever
  3 siblings, 0 replies; 5+ messages in thread
From: Chuck Lever @ 2026-10-13 12:49 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

NFSv3 READDIRPLUS's reply is still produced by a hand-written
encoder. Like READDIR, its entry list is the value-follows form of
RFC 1813's "entryplus3 *entries" -- each entry prefixed by TRUE, the
sequence closed by FALSE -- streamed from the directory as it is
read. Each entry additionally carries the object's post-op
attributes and file handle. The hand-written encoder materialized
the whole list into page memory during nfsd_readdir(), then copied
it into the reply as an opaque blob.

The preceding READDIR conversion built the streaming machinery in
shared code: the optional-data aggregate encoder pulls entries until
the hook signals end, and the streaming directory reader yields one
entry at a time during encode. Mark dirlistplus3's entry list as an
aggregate and regenerate; the struct definitions stay the verbatim
RFC 1813 types, and the lone "pragma aggregate dirlistplus3 entries"
selects the hook-driven codec.

Split the read across the two phases so the generated encoder owns
the framing. nfsd3_proc_readdirplus() opens the directory, where an
open error becomes the reply status the union encoder then writes,
and fills the directory attributes and cookie verifier that precede
the entry list. The encode hooks stream the entries: each pull
composes the entry's post-op attributes and file handle with the
same fill helpers the per-procedure encoders use, reserves a
placeholder cookie, and back-patches the preceding entry's cookie
once the following entry's resume offset is known. Because an
entryplus3's cookie is not its final field, the hook records the
cookie's slot from the entry's start position rather than from the
tail of the stream. The end hook advances rq_next_page
past the pages the entry list filled, as the READDIR end hook
does. nfs3svc_release_readdirplusres() closes the reader and releases
the file handle. Set pc_argzero to clear the argument wrapper, because
svc_release_rqst() calls that hook even when argument decoding fails
and nfsd3_proc_readdirplus() never initializes the file handle.

struct nfsd3_readdirplusres now embeds struct READDIRPLUS3res so the
RPC dispatch layer can cast the response to its xdrgen type, and
holds the streaming reader in place of the old page-buffer state.
READDIRPLUS3args_wrapper moves to xdr3.h so the encode and release
paths can reach the directory file handle.

A host error during the streamed read ends the list as a truncated,
non-eof reply rather than an error status. By then the generated
union encoder has committed NFS3_OK and the resok fields ahead of
the entry list, and the aggregate hooks cannot rewind the stream to
re-encode the reply as resfail. The client resumes from the last
cookie, so its follow-up READDIRPLUS starts at the failing batch,
and nfsd_readdir_open() reports the error as the reply status there.
An error opening the directory is still reported as the reply
status. The bytes on the wire are otherwise unchanged.

The hand-written READDIRPLUS result encoder, its per-entry encoder,
and the post-op file-handle encoders they relied on no longer have
any callers, so they are removed. The declaration of
nfs3svc_encode_accessres(), whose definition went away with the ACCESS
conversion, is removed along with them.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 Documentation/sunrpc/xdr/nfs3.x    |  10 +
 fs/nfsd/nfs3proc.c                 |  88 +++-----
 fs/nfsd/nfs3xdr.c                  | 313 +++++++++++++++--------------
 fs/nfsd/nfs3xdr_gen.c              |  35 +++-
 fs/nfsd/nfs3xdr_gen.h              |  13 +-
 fs/nfsd/xdr3.h                     |  48 +++--
 include/linux/sunrpc/xdrgen/nfs3.h |   2 +-
 7 files changed, 268 insertions(+), 241 deletions(-)

diff --git a/Documentation/sunrpc/xdr/nfs3.x b/Documentation/sunrpc/xdr/nfs3.x
index cf743a8a5f3d..2a126b8f7092 100644
--- a/Documentation/sunrpc/xdr/nfs3.x
+++ b/Documentation/sunrpc/xdr/nfs3.x
@@ -676,6 +676,16 @@ struct dirlistplus3 {
 	bool		eof;
 };
 
+/*
+ * The entry list encodes as the value-follows form of "entryplus3
+ * *entries" -- 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 1813 types; only this
+ * pragma selects the hook-driven codec.
+ */
+pragma aggregate dirlistplus3 entries;
+
 struct READDIRPLUS3resok {
 	post_op_attr	dir_attributes;
 	cookieverf3	cookieverf;
diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index daf7be8e961b..013f3b128a45 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -218,13 +218,6 @@ struct COMMIT3args_wrapper {
 
 static_assert(offsetof(struct COMMIT3args_wrapper, xdrgen) == 0);
 
-struct READDIRPLUS3args_wrapper {
-	struct READDIRPLUS3args	xdrgen;
-	struct svc_fh		fh;
-};
-
-static_assert(offsetof(struct READDIRPLUS3args_wrapper, xdrgen) == 0);
-
 static int nfsd3_ftype3_to_mode(ftype3 ftype)
 {
 	switch (ftype) {
@@ -435,10 +428,6 @@ nfsd3_stat_to_fattr3(struct svc_rqst *rqstp, struct fattr3 *fattr,
 	nfsd3_timespec64_to_nfstime3(&fattr->ctime, &stat->ctime);
 }
 
-static void
-nfsd3_fill_post_op_attr(struct svc_rqst *rqstp, struct post_op_attr *attr,
-			struct svc_fh *fhp);
-
 static void
 nfsd3_fill_wcc_data(struct svc_rqst *rqstp, struct wcc_data *wcc,
 		    struct svc_fh *fhp)
@@ -525,7 +514,7 @@ nfsd3_sattr3_to_iattr(struct svc_rqst *rqstp, struct iattr *iap,
  * post_op_attrs invoke nfsd3_fill_wcc_data() before invoking
  * nfsd3_fill_post_op_attr() on the same fhp argument.
  */
-static void
+void
 nfsd3_fill_post_op_attr(struct svc_rqst *rqstp, struct post_op_attr *attr,
 			struct svc_fh *fhp)
 {
@@ -550,7 +539,7 @@ nfsd3_fill_post_op_attr(struct svc_rqst *rqstp, struct post_op_attr *attr,
 	attr->attributes_follow = false;
 }
 
-static void
+void
 nfsd3_fill_post_op_fh3(struct post_op_fh3 *post_op_fh,
 		       const struct svc_fh *fhp, u8 *data)
 {
@@ -1231,27 +1220,6 @@ static __be32 nfsd3_proc_symlink(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-static void
-nfsd3_init_dirlistplus_pages(struct svc_rqst *rqstp,
-			     struct nfsd3_readdirplusres *resp,
-			     u32 count)
-{
-	struct xdr_buf *buf = &resp->dirlist;
-	struct xdr_stream *xdr = &resp->xdr;
-	unsigned int sendbuf = min_t(unsigned int, rqstp->rq_res.buflen,
-				     svc_max_payload(rqstp));
-
-	memset(buf, 0, sizeof(*buf));
-
-	/* Reserve room for the NULL ptr & eof flag (-2 words) */
-	buf->buflen = clamp(count, (u32)(XDR_UNIT * 2), sendbuf);
-	buf->buflen -= XDR_UNIT * 2;
-	buf->pages = rqstp->rq_next_page;
-	rqstp->rq_next_page += (buf->buflen + PAGE_SIZE - 1) >> PAGE_SHIFT;
-
-	xdr_init_encode_pages(xdr, buf);
-}
-
 /**
  * nfsd3_proc_mknod - MKNOD: Create a special file
  * @rqstp: RPC transaction context
@@ -1565,6 +1533,12 @@ static __be32 nfsd3_proc_readdir(struct svc_rqst *rqstp)
  * nfsd3_proc_readdirplus - READDIRPLUS: Extended 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's directory reader during
+ * encode; nfs3svc_release_readdirplusres() closes the reader and
+ * releases the file handle.
+ *
  * Return:
  *   %rpc_success:		RPC executed successfully
  *
@@ -1577,35 +1551,33 @@ static __be32 nfsd3_proc_readdirplus(struct svc_rqst *rqstp)
 	struct nfsd3_readdirplusres *resp = rqstp->rq_resp;
 	loff_t offset = argp->xdrgen.cookie;
 	struct svc_fh *fhp = &argp->fh;
+	__be32 status;
 
 	nfsd3_fh3_to_svc_fh(fhp, &argp->xdrgen.dir);
 	trace_nfsd_vfs_readdir(rqstp, fhp, argp->xdrgen.maxcount,
 			       argp->xdrgen.cookie);
 
-	nfsd3_init_dirlistplus_pages(rqstp, resp, argp->xdrgen.maxcount);
-	fh_copy(&resp->fh, fhp);
-	resp->common.err = nfs_ok;
-	resp->cookie_offset = 0;
-	resp->rqstp = rqstp;
-	resp->status = fh_verify(rqstp, &resp->fh, S_IFDIR, NFSD_MAY_NOP);
-	if (resp->status != nfs_ok)
-		goto out;
+	resp->count = argp->xdrgen.maxcount;
+	status = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_NOP);
+	if (status == nfs_ok &&
+	    fhp->fh_export->ex_flags & NFSEXP_NOREADDIRPLUS)
+		status = nfserr_notsupp;
+	if (status == nfs_ok)
+		status = nfsd_readdir_open(rqstp, fhp, &offset, &resp->iter);
 
-	if (resp->fh.fh_export->ex_flags & NFSEXP_NOREADDIRPLUS) {
-		resp->status = nfserr_notsupp;
-		goto out;
+	if (status == nfs_ok) {
+		struct READDIRPLUS3resok *resok = &resp->xdrgen.u.resok;
+
+		memcpy(resok->cookieverf, argp->xdrgen.cookieverf,
+		       NFS3_COOKIEVERFSIZE);
+		nfsd3_fill_post_op_attr(rqstp, &resok->dir_attributes, fhp);
+	} else {
+		struct READDIRPLUS3resfail *resfail = &resp->xdrgen.u.resfail;
+
+		nfsd3_fill_post_op_attr(rqstp, &resfail->dir_attributes, fhp);
 	}
 
-	resp->status = nfsd_readdir(rqstp, &resp->fh, &offset,
-				    &resp->common, nfs3svc_encode_entryplus3);
-	memcpy(resp->verf, argp->xdrgen.cookieverf, NFS3_COOKIEVERFSIZE);
-	nfs3svc_encode_cookie3(&resp->xdr, resp->cookie_offset, offset);
-
-	/* Recycle only pages that were part of the reply */
-	rqstp->rq_next_page = resp->xdr.page_ptr + 1;
-
-out:
-	resp->status = nfsd3_map_status(resp->status);
+	resp->xdrgen.status = nfsd3_map_status(status);
 	return rpc_success;
 }
 
@@ -2033,10 +2005,10 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 	[NFSPROC3_READDIRPLUS] = {
 		.pc_func	= nfsd3_proc_readdirplus,
 		.pc_decode	= nfs_svc_decode_READDIRPLUS3args,
-		.pc_encode	= nfs3svc_encode_readdirplusres,
-		.pc_release	= nfs3svc_release_fhandle,
+		.pc_encode	= nfs_svc_encode_READDIRPLUS3res,
+		.pc_release	= nfs3svc_release_readdirplusres,
 		.pc_argsize	= sizeof(struct READDIRPLUS3args_wrapper),
-		.pc_argzero	= 0,
+		.pc_argzero	= sizeof(struct READDIRPLUS3args_wrapper),
 		.pc_ressize	= sizeof(struct nfsd3_readdirplusres),
 		.pc_cachetype	= RC_NOCACHE,
 		.pc_name	= "READDIRPLUS",
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index 52a178fe69bb..850647ac98bb 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -95,47 +95,6 @@ svcxdr_encode_nfsstat3(struct xdr_stream *xdr, __be32 status)
 	return true;
 }
 
-static bool
-svcxdr_encode_nfs_fh3(struct xdr_stream *xdr, const struct svc_fh *fhp)
-{
-	u32 size = fhp->fh_handle.fh_size;
-	__be32 *p;
-
-	p = xdr_reserve_space(xdr, XDR_UNIT + size);
-	if (!p)
-		return false;
-	*p++ = cpu_to_be32(size);
-	if (size)
-		p[XDR_QUADLEN(size) - 1] = 0;
-	memcpy(p, &fhp->fh_handle.fh_raw, size);
-
-	return true;
-}
-
-static bool
-svcxdr_encode_post_op_fh3(struct xdr_stream *xdr, const struct svc_fh *fhp)
-{
-	if (xdr_stream_encode_item_present(xdr) < 0)
-		return false;
-	if (!svcxdr_encode_nfs_fh3(xdr, fhp))
-		return false;
-
-	return true;
-}
-
-static bool
-svcxdr_encode_cookieverf3(struct xdr_stream *xdr, const __be32 *verf)
-{
-	__be32 *p;
-
-	p = xdr_reserve_space(xdr, NFS3_COOKIEVERFSIZE);
-	if (!p)
-		return false;
-	memcpy(p, verf, NFS3_COOKIEVERFSIZE);
-
-	return true;
-}
-
 static bool
 svcxdr_encode_fattr3(struct svc_rqst *rqstp, struct xdr_stream *xdr,
 		     const struct svc_fh *fhp, const struct kstat *stat)
@@ -369,36 +328,6 @@ bool nfs3_dirlist3_encode_end(struct xdrgen_aggregate_cursor *c, bool ok)
 	return true;
 }
 
-bool
-nfs3svc_encode_readdirplusres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_readdirplusres *resp = rqstp->rq_resp;
-	struct xdr_buf *dirlist = &resp->dirlist;
-
-	if (!svcxdr_encode_nfsstat3(xdr, resp->status))
-		return false;
-	switch (resp->status) {
-	case nfs_ok:
-		if (!svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh))
-			return false;
-		if (!svcxdr_encode_cookieverf3(xdr, resp->verf))
-			return false;
-		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;
-	default:
-		if (!svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh))
-			return false;
-	}
-
-	return true;
-}
-
 static __be32
 compose_entry_fh(struct nfsd3_readdirplusres *cd, struct svc_fh *fhp,
 		 const char *name, int namlen, u64 ino)
@@ -407,8 +336,8 @@ compose_entry_fh(struct nfsd3_readdirplusres *cd, struct svc_fh *fhp,
 	struct dentry		*dparent, *dchild;
 	__be32 rv = nfserr_noent;
 
-	dparent = cd->fh.fh_dentry;
-	exp  = cd->fh.fh_export;
+	dparent = cd->iter.fhp->fh_dentry;
+	exp  = cd->iter.fhp->fh_export;
 
 	if (name_is_dot_dotdot(name, namlen)) {
 		if (namlen == 2) {
@@ -433,7 +362,7 @@ compose_entry_fh(struct nfsd3_readdirplusres *cd, struct svc_fh *fhp,
 		goto out;
 	if (dchild->d_inode->i_ino != ino)
 		goto out;
-	rv = fh_compose(fhp, exp, dchild, &cd->fh);
+	rv = fh_compose(fhp, exp, dchild, cd->iter.fhp);
 out:
 	dput(dchild);
 	return rv;
@@ -446,7 +375,7 @@ compose_entry_fh(struct nfsd3_readdirplusres *cd, struct svc_fh *fhp,
  * @cookie: cookie to be encoded
  *
  * The buffer space for the offset cookie has already been reserved
- * by nfs3_dirlist3_encode() or nfs3svc_encode_entryplus3().
+ * by nfs3_dirlist3_encode() or nfs3_dirlistplus3_encode().
  */
 void nfs3svc_encode_cookie3(struct xdr_stream *xdr, unsigned int pos,
 			    u64 cookie)
@@ -458,105 +387,171 @@ void nfs3svc_encode_cookie3(struct xdr_stream *xdr, unsigned int pos,
 	write_bytes_to_xdr_buf(xdr->buf, pos, &wire_cookie, XDR_UNIT * 2);
 }
 
-static bool
-svcxdr_encode_entry3_plus(struct nfsd3_readdirplusres *resp, const char *name,
-			  int namlen, u64 ino)
+/*
+ * READDIRPLUS reply entry list (RFC 1813).  Like READDIR's entry list,
+ * the entries are the value-follows form of "entryplus3 *entries" and
+ * xdrgen's aggregate codec owns the framing.  Each entry additionally
+ * carries the object's post-op attributes and file handle, composed
+ * and stat'ed here as the entry is streamed.  The directory was opened
+ * in nfsd3_proc_readdirplus(), so reading and encoding happen together
+ * during reply encoding.
+ */
+
+/**
+ * nfs3_dirlistplus3_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 maxcount hint, reserving room for the list
+ * terminator and the eof flag that follow the entries.
+ *
+ * Return: true.
+ */
+bool nfs3_dirlistplus3_encode_begin(struct xdrgen_aggregate_cursor *c)
 {
-	struct xdr_stream *xdr = &resp->xdr;
-	struct svc_fh *fhp = &resp->scratch;
-	bool result;
+	struct svc_rqst *rqstp = c->ctx;
+	struct nfsd3_readdirplusres *resp = rqstp->rq_resp;
+	struct xdr_stream *xdr = c->xdr;
+	int bytes_left;
 
-	result = false;
-	fh_init(fhp, NFS3_FHSIZE);
-	if (compose_entry_fh(resp, fhp, name, namlen, ino) != nfs_ok)
-		goto out_noattrs;
-
-	if (!svcxdr_encode_post_op_attr(resp->rqstp, xdr, fhp))
-		goto out;
-	if (!svcxdr_encode_post_op_fh3(xdr, fhp))
-		goto out;
-	result = true;
-
-out:
-	fh_put(fhp);
-	return result;
-
-out_noattrs:
-	if (xdr_stream_encode_item_absent(xdr) < 0)
-		return false;
-	if (xdr_stream_encode_item_absent(xdr) < 0)
-		return false;
+	/* 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)svc_max_payload(rqstp)) -
+				 XDR_UNIT * 2);
+	resp->cookie_offset = 0;
 	return true;
 }
 
 /**
- * nfs3svc_encode_entryplus3 - encode one NFSv3 READDIRPLUS 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
+ * nfs3_dirlistplus3_encode - stream the next READDIRPLUS 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, compose the entry's post-op attributes and file
+ * handle, then, if the reply budget allows, project the next directory
+ * entry into @out for the generated per-entry encoder.  @out's file
+ * handle bytes live in a reply-owned scratch buffer, valid until the
+ * generated encoder writes them and the following pull overwrites them.
  *
- * 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 nfs3svc_encode_entryplus3(void *data, const char *name, int namlen,
-			      loff_t offset, u64 ino, unsigned int d_type)
+bool nfs3_dirlistplus3_encode(struct xdrgen_aggregate_cursor *c,
+			      struct entryplus3 *out)
 {
-	struct readdir_cd *ccd = data;
-	struct nfsd3_readdirplusres *resp =
-		container_of(ccd, struct nfsd3_readdirplusres, common);
-	unsigned int starting_length = resp->dirlist.len;
-	struct xdr_stream *xdr = &resp->xdr;
+	struct svc_rqst *rqstp = c->ctx;
+	struct nfsd3_readdirplusres *resp = rqstp->rq_resp;
+	struct svc_fh *scratch = &resp->scratch;
+	struct xdr_stream *xdr = c->xdr;
+	struct buffered_dirent *de;
+	int namlen;
+	u32 need;
 
-	/* The offset cookie for the previous entry */
-	nfs3svc_encode_cookie3(xdr, resp->cookie_offset, offset);
+	/*
+	 * Commit the previous entry so its cookie slot (recorded in
+	 * resp->cookie_offset) is safe to back-patch below.  Unlike
+	 * entry3, an entryplus3's cookie is not its final field, so the
+	 * slot cannot be derived from buf->len after the fact; it is
+	 * computed from the entry's start position instead (see the end
+	 * of this function).
+	 */
+	if (c->index)
+		xdr_commit_encode(xdr);
 
-	if (xdr_stream_encode_item_present(xdr) < 0)
-		goto out_toosmall;
-	/* fileid */
-	if (xdr_stream_encode_u64(xdr, ino) < 0)
-		goto out_toosmall;
-	/* name */
-	if (xdr_stream_encode_opaque(xdr, name, min(namlen, NFS3_MAXNAMLEN)) < 0)
-		goto out_toosmall;
-	/* cookie */
-	resp->cookie_offset = resp->dirlist.len;
-	if (xdr_stream_encode_u64(xdr, OFFSET_MAX) < 0)
-		goto out_toosmall;
+	de = nfsd_readdir_next(&resp->iter);
+	if (!de)
+		return false;
 
-	if (!svcxdr_encode_entry3_plus(resp, name, namlen, ino))
-		goto out_toosmall;
+	/* The previous entry's cookie is this entry's resume offset. */
+	nfs3svc_encode_cookie3(xdr, resp->cookie_offset, de->offset);
 
-	xdr_commit_encode(xdr);
-	resp->common.err = nfs_ok;
-	return 0;
+	namlen = min_t(int, de->namlen, NFS3_MAXNAMLEN);
 
-out_toosmall:
-	resp->cookie_offset = 0;
-	resp->common.err = nfserr_toosmall;
-	resp->dirlist.len = starting_length;
-	return -EINVAL;
+	out->fileid = de->ino;
+	out->name.len = namlen;
+	out->name.data = (unsigned char *)de->name;
+	out->cookie = 0;	/* back-patched later */
+
+	/*
+	 * value-follows + fileid + name (length + data) + cookie +
+	 * attributes_follow + handle_follows. Rejecting on this
+	 * minimum first spares the lookup and getattr below.
+	 */
+	need = XDR_UNIT * (8 + XDR_QUADLEN(namlen));
+	if (need > resp->space_left)
+		return false;
+
+	/* Compose the entry's post-op attributes and file handle. */
+	fh_init(scratch, NFS3_FHSIZE);
+	if (compose_entry_fh(resp, scratch, de->name, namlen, de->ino) == nfs_ok) {
+		nfsd3_fill_post_op_attr(rqstp, &out->name_attributes, scratch);
+		nfsd3_fill_post_op_fh3(&out->name_handle, scratch, resp->fh_data);
+	} else {
+		out->name_attributes.attributes_follow = false;
+		out->name_handle.handle_follows = false;
+	}
+	fh_put(scratch);
+
+	/* optional fattr3 */
+	if (out->name_attributes.attributes_follow)
+		need += XDR_UNIT * 21;
+	/* optional nfs_fh3 */
+	if (out->name_handle.handle_follows)
+		need += XDR_UNIT * (1 + XDR_QUADLEN(out->name_handle.u.handle.data.len));
+	if (need > resp->space_left)
+		return false;
+	resp->space_left -= need;
+
+	/*
+	 * buf->len marks where the generated encoder begins this entry.
+	 * Its cookie follows the value-follows flag, the fileid, and the
+	 * name; record that slot for the next entry (or the end hook) to
+	 * back-patch once the resume offset is known.
+	 */
+	resp->cookie_offset = xdr->buf->len +
+			      XDR_UNIT * (4 + XDR_QUADLEN(namlen));
+
+	trace_nfsd_dirent(resp->iter.fhp, de->ino, de->name, namlen);
+	return true;
+}
+
+/**
+ * nfs3_dirlistplus3_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 nfs3_dirlistplus3_encode_end(struct xdrgen_aggregate_cursor *c, bool ok)
+{
+	struct svc_rqst *rqstp = c->ctx;
+	struct nfsd3_readdirplusres *resp = rqstp->rq_resp;
+	struct xdr_stream *xdr = c->xdr;
+
+	if (ok)
+		nfs3svc_encode_cookie3(xdr, resp->cookie_offset,
+				       resp->iter.offset);
+	resp->xdrgen.u.resok.reply.eof = resp->iter.eof;
+	nfs3svc_update_next_page(rqstp, xdr);
+	return true;
 }
 
 /*
  * XDR release functions
  */
-void
-nfs3svc_release_fhandle(struct svc_rqst *rqstp)
-{
-	struct nfsd3_attrstat *resp = rqstp->rq_resp;
-
-	fh_put(&resp->fh);
-}
-
 void
 nfs3svc_release_readdirres(struct svc_rqst *rqstp)
 {
@@ -567,3 +562,13 @@ nfs3svc_release_readdirres(struct svc_rqst *rqstp)
 	nfsd_readdir_close(&resp->iter);
 	fh_put(&argp->fh);
 }
+
+void
+nfs3svc_release_readdirplusres(struct svc_rqst *rqstp)
+{
+	struct nfsd3_readdirplusres *resp = rqstp->rq_resp;
+	struct READDIRPLUS3args_wrapper *argp = rqstp->rq_argp;
+
+	nfsd_readdir_close(&resp->iter);
+	fh_put(&argp->fh);
+}
diff --git a/fs/nfsd/nfs3xdr_gen.c b/fs/nfsd/nfs3xdr_gen.c
index 452e456dc8da..6ab69e869839 100644
--- a/fs/nfsd/nfs3xdr_gen.c
+++ b/fs/nfsd/nfs3xdr_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/nfs3.x
-// XDR specification modification time: Tue Jul 14 20:36:22 2026
+// XDR specification modification time: Tue Jul 14 21:15:59 2026
 
 #include <linux/sunrpc/svc.h>
 
@@ -1340,11 +1340,7 @@ xdrgen_decode_entryplus3(struct xdr_stream *xdr, struct entryplus3 *ptr)
 static bool __maybe_unused
 xdrgen_decode_dirlistplus3(struct xdr_stream *xdr, struct dirlistplus3 *ptr)
 {
-	if (!xdrgen_decode_entryplus3(xdr, ptr->entries))
-		return false;
-	if (!xdrgen_decode_bool(xdr, &ptr->eof))
-		return false;
-	return true;
+	return false;
 }
 
 static bool __maybe_unused
@@ -3192,8 +3188,31 @@ xdrgen_encode_entryplus3(struct xdr_stream *xdr, const struct entryplus3 *value)
 static bool __maybe_unused
 xdrgen_encode_dirlistplus3(struct xdr_stream *xdr, const struct dirlistplus3 *value)
 {
-	if (!xdrgen_encode_entryplus3(xdr, value->entries))
-		return false;
+	{
+		struct xdrgen_aggregate_cursor cursor = {
+			.xdr = xdr,
+			.member_id = NFS3_DIRLISTPLUS3_ENTRIES,
+			.ctx = xdr->xdrgen_ctx,
+		};
+		bool ok = true;
+
+		if (!nfs3_dirlistplus3_encode_begin(&cursor))
+			return false;
+		for (cursor.index = 0; ; cursor.index++) {
+			struct entryplus3 element = {};
+
+			if (!nfs3_dirlistplus3_encode(&cursor, &element))
+				break;
+			if (!xdrgen_encode_entryplus3(xdr, &element)) {
+				ok = false;
+				break;
+			}
+		}
+		if (ok && !xdrgen_encode_entryplus3(xdr, NULL))
+			ok = false;
+		if (!nfs3_dirlistplus3_encode_end(&cursor, ok) || !ok)
+			return false;
+	}
 	if (!xdrgen_encode_bool(xdr, value->eof))
 		return false;
 	return true;
diff --git a/fs/nfsd/nfs3xdr_gen.h b/fs/nfsd/nfs3xdr_gen.h
index 5c302645bfbe..92f3bf85d1db 100644
--- a/fs/nfsd/nfs3xdr_gen.h
+++ b/fs/nfsd/nfs3xdr_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/nfs3.x */
-/* XDR specification modification time: Tue Jul 14 20:36:22 2026 */
+/* XDR specification modification time: Tue Jul 14 21:15:59 2026 */
 
 #ifndef _LINUX_XDRGEN_NFS3_DECL_H
 #define _LINUX_XDRGEN_NFS3_DECL_H
@@ -24,6 +24,17 @@ bool nfs3_dirlist3_decode_begin(struct xdrgen_aggregate_cursor *c);
 bool nfs3_dirlist3_decode(struct xdrgen_aggregate_cursor *c, const struct entry3 *in);
 bool nfs3_dirlist3_decode_end(struct xdrgen_aggregate_cursor *c, bool ok);
 
+enum {
+	NFS3_DIRLISTPLUS3_ENTRIES,
+};
+
+bool nfs3_dirlistplus3_encode_begin(struct xdrgen_aggregate_cursor *c);
+bool nfs3_dirlistplus3_encode(struct xdrgen_aggregate_cursor *c, struct entryplus3 *out);
+bool nfs3_dirlistplus3_encode_end(struct xdrgen_aggregate_cursor *c, bool ok);
+bool nfs3_dirlistplus3_decode_begin(struct xdrgen_aggregate_cursor *c);
+bool nfs3_dirlistplus3_decode(struct xdrgen_aggregate_cursor *c, const struct entryplus3 *in);
+bool nfs3_dirlistplus3_decode_end(struct xdrgen_aggregate_cursor *c, bool ok);
+
 bool nfs_svc_decode_GETATTR3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs_svc_decode_SETATTR3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs_svc_decode_LOOKUP3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 75de77aa8e13..a6f0e0b7bfe4 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -81,22 +81,33 @@ struct nfsd3_readdirres {
 
 static_assert(offsetof(struct nfsd3_readdirres, xdrgen) == 0);
 
-struct nfsd3_readdirplusres {
-	/* Components of the reply */
-	__be32			status;
+/*
+ * Declared here rather than in nfs3proc.c with the other proc wrappers
+ * because READDIRPLUS's encode path in nfs3xdr.c reads the file handle
+ * from it during encode and release. The xdrgen field must be first so
+ * the struct can be cast to its XDR type for the RPC dispatch layer.
+ */
+struct READDIRPLUS3args_wrapper {
+	struct READDIRPLUS3args	xdrgen;
 	struct svc_fh		fh;
-	__be32			verf[2];
-
-	/* Used to encode the reply's entry list */
-	struct xdr_stream	xdr;
-	struct xdr_buf		dirlist;
-	struct svc_fh		scratch;
-	struct readdir_cd	common;
-	unsigned int		cookie_offset;
-	struct svc_rqst *	rqstp;
-
 };
 
+static_assert(offsetof(struct READDIRPLUS3args_wrapper, xdrgen) == 0);
+
+struct nfsd3_readdirplusres {
+	struct READDIRPLUS3res	xdrgen;
+
+	/* 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 */
+	struct svc_fh		scratch;	/* per-entry file handle */
+	u8			fh_data[NFS3_FHSIZE];	/* per-entry handle bytes */
+};
+
+static_assert(offsetof(struct nfsd3_readdirplusres, xdrgen) == 0);
+
 struct nfsd3_fsstatres {
 	struct FSSTAT3res	xdrgen;
 	struct kstatfs		stats;
@@ -131,16 +142,15 @@ struct nfsd3_getaclres {
 	struct kstat		stat;
 };
 
-bool nfs3svc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_encode_readdirplusres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-
-void nfs3svc_release_fhandle(struct svc_rqst *);
 void nfs3svc_release_readdirres(struct svc_rqst *rqstp);
+void nfs3svc_release_readdirplusres(struct svc_rqst *rqstp);
 
 void nfs3svc_encode_cookie3(struct xdr_stream *xdr, unsigned int pos,
 			    u64 cookie);
-int nfs3svc_encode_entryplus3(void *data, const char *name, int namlen,
-			      loff_t offset, u64 ino, unsigned int d_type);
+void nfsd3_fill_post_op_attr(struct svc_rqst *rqstp, struct post_op_attr *attr,
+			     struct svc_fh *fhp);
+void nfsd3_fill_post_op_fh3(struct post_op_fh3 *post_op_fh,
+			    const struct svc_fh *fhp, u8 *data);
 /* Helper functions for NFSv3 ACL code */
 bool svcxdr_decode_nfs_fh3(struct xdr_stream *xdr, struct svc_fh *fhp);
 bool svcxdr_encode_nfsstat3(struct xdr_stream *xdr, __be32 status);
diff --git a/include/linux/sunrpc/xdrgen/nfs3.h b/include/linux/sunrpc/xdrgen/nfs3.h
index 27750bbab2ee..734f60f502c6 100644
--- a/include/linux/sunrpc/xdrgen/nfs3.h
+++ b/include/linux/sunrpc/xdrgen/nfs3.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/nfs3.x */
-/* XDR specification modification time: Tue Jul 14 20:36:22 2026 */
+/* XDR specification modification time: Tue Jul 14 21:15:59 2026 */
 
 #ifndef _LINUX_XDRGEN_NFS3_DEF_H
 #define _LINUX_XDRGEN_NFS3_DEF_H
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v3 34/34] SUNRPC: Remove svc_fill_symlink_pathname()
  2026-10-13 12:49 [PATCH v3 30/34] NFSD: Refactor NFSv3 directory entry encoding Chuck Lever
                   ` (2 preceding siblings ...)
  2026-10-13 12:49 ` [PATCH v3 33/34] NFSD: Use xdrgen XDR functions for NFSv3 READDIRPLUS results Chuck Lever
@ 2026-10-13 12:49 ` Chuck Lever
  3 siblings, 0 replies; 5+ messages in thread
From: Chuck Lever @ 2026-10-13 12:49 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

The NFSv2 and NFSv3 SYMLINK procedures copy the target pathname out
of a struct xdr_buf with nfsd_symlink_target(), so
svc_fill_symlink_pathname() has no callers left. Remove it.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 include/linux/sunrpc/svc.h |  3 ---
 net/sunrpc/svc.c           | 50 --------------------------------------
 2 files changed, 53 deletions(-)

diff --git a/include/linux/sunrpc/svc.h b/include/linux/sunrpc/svc.h
index a47f05fc6a12..a7039ec035fa 100644
--- a/include/linux/sunrpc/svc.h
+++ b/include/linux/sunrpc/svc.h
@@ -492,9 +492,6 @@ const char *	   svc_proc_name(const struct svc_rqst *rqstp);
 int		   svc_encode_result_payload(struct svc_rqst *rqstp,
 					     unsigned int offset,
 					     unsigned int length);
-char		  *svc_fill_symlink_pathname(struct svc_rqst *rqstp,
-					     struct kvec *first, void *p,
-					     size_t total);
 __be32		   svc_generic_init_request(struct svc_rqst *rqstp,
 					    const struct svc_program *progp,
 					    struct svc_process_info *procinfo);
diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c
index 7e23af94a719..5c21ec585cd0 100644
--- a/net/sunrpc/svc.c
+++ b/net/sunrpc/svc.c
@@ -1883,53 +1883,3 @@ int svc_encode_result_payload(struct svc_rqst *rqstp, unsigned int offset,
 							   length);
 }
 EXPORT_SYMBOL_GPL(svc_encode_result_payload);
-
-/**
- * svc_fill_symlink_pathname - Construct pathname argument for VFS symlink call
- * @rqstp: svc_rqst to operate on
- * @first: buffer containing first section of pathname
- * @p: buffer containing remaining section of pathname
- * @total: total length of the pathname argument
- *
- * The VFS symlink API demands a NUL-terminated pathname in mapped memory.
- * Returns pointer to a NUL-terminated string, or an ERR_PTR. Caller must free
- * the returned string.
- */
-char *svc_fill_symlink_pathname(struct svc_rqst *rqstp, struct kvec *first,
-				void *p, size_t total)
-{
-	size_t len, remaining;
-	char *result, *dst;
-
-	result = kmalloc(total + 1, GFP_KERNEL);
-	if (!result)
-		return ERR_PTR(-ESERVERFAULT);
-
-	dst = result;
-	remaining = total;
-
-	len = min_t(size_t, total, first->iov_len);
-	if (len) {
-		memcpy(dst, first->iov_base, len);
-		dst += len;
-		remaining -= len;
-	}
-
-	if (remaining) {
-		len = min_t(size_t, remaining, PAGE_SIZE);
-		memcpy(dst, p, len);
-		dst += len;
-	}
-
-	*dst = '\0';
-
-	/* Sanity check: Linux doesn't allow the pathname argument to
-	 * contain a NUL byte.
-	 */
-	if (strlen(result) != total) {
-		kfree(result);
-		return ERR_PTR(-EINVAL);
-	}
-	return result;
-}
-EXPORT_SYMBOL_GPL(svc_fill_symlink_pathname);
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-10-13 12:50 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-13 12:49 [PATCH v3 30/34] NFSD: Refactor NFSv3 directory entry encoding Chuck Lever
2026-10-13 12:49 ` [PATCH v3 31/34] NFSD: Split struct nfsd3_readdirres Chuck Lever
2026-10-13 12:49 ` [PATCH v3 32/34] NFSD: Use xdrgen XDR functions for NFSv3 READDIR results Chuck Lever
2026-10-13 12:49 ` [PATCH v3 33/34] NFSD: Use xdrgen XDR functions for NFSv3 READDIRPLUS results Chuck Lever
2026-10-13 12:49 ` [PATCH v3 34/34] SUNRPC: Remove svc_fill_symlink_pathname() Chuck Lever

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.