linux-nfs.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen
@ 2026-09-23 13:27 Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 01/33] NFSD: Report a first-batch readdir error in the reply status Chuck Lever
                   ` (32 more replies)
  0 siblings, 33 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace NFSD's NFSv3 XDR encoders and decoders with code that is
mechanically generated directly from the NFSv3 RPC language
specification. Unlike NFSv2, where there are at least two
incomplete language specifications and much lore, nfs3.x here is
based closely on RFC 1813.

As we convert newer NFS versions to use xdrgen, I expect a reduction
in human coding errors, improved spec compliance, and better memory
safety for those more complex NFS programs.

Chuck Lever (33):
  NFSD: Report a first-batch readdir error in the reply status
  Documentation: Add the RPC language description of NFSv3
  NFSD: Add infrastructure for generating NFSv3 XDR encoders and
    decoders
  NFSD: Replace nfs3.h with nfs3xdr_gen.h
  NFSD: Replace the nfsd3_createres macro
  NFSD: Relocate the NFSv3 XDR storage union into nfs3proc.c
  NFSD: Use xdrgen XDR functions for the NFSv3 NULL procedure
  NFSD: Use xdrgen XDR functions for NFSv3 GETATTR procedure
  NFSD: Use xdrgen XDR functions for NFSv3 SETATTR procedure
  NFSD: Use xdrgen XDR functions for the NFSv3 LOOKUP procedure
  NFSD: Use xdrgen XDR functions for NFSv3 ACCESS procedure
  NFSD: Use xdrgen XDR functions for NFSv3 READLINK procedure
  NFSD: Use xdrgen XDR functions for NFSv3 READ procedure
  NFSD: Use xdrgen XDR functions for NFSv3 WRITE procedure
  NFSD: Use xdrgen XDR functions for NFSv3 CREATE procedure
  NFSD: Use xdrgen XDR functions for NFSv3 MKDIR procedure
  NFSD: Use xdrgen XDR functions for NFSv3 SYMLINK procedure
  NFSD: Use xdrgen XDR functions for NFSv3 MKNOD procedure
  NFSD: Use xdrgen XDR functions for the NFSv3 REMOVE procedure
  NFSD: Use xdrgen XDR functions for the NFSv3 RMDIR procedure
  NFSD: Use xdrgen XDR functions for the NFSv3 RENAME procedure
  NFSD: Use xdrgen XDR functions for the NFSv3 LINK procedure
  NFSD: Use xdrgen XDR functions for the NFSv3 FSSTAT procedure
  NFSD: Use xdrgen XDR functions for the NFSv3 FSINFO procedure
  NFSD: Use xdrgen XDR functions for the NFSv3 PATHCONF procedure
  NFSD: Use xdrgen XDR functions for the NFSv3 COMMIT procedure
  NFSD: Use xdrgen XDR functions for NFSv3 READDIR arguments
  NFSD: Use xdrgen XDR functions for NFSv3 READDIRPLUS arguments
  NFSD: Refactor NFSv3 directory cookie encoding
  NFSD: Refactor NFSv3 directory entry encoding
  NFSD: Split struct nfsd3_readdirres
  NFSD: Use xdrgen XDR functions for NFSv3 READDIR results
  NFSD: Use xdrgen XDR functions for NFSv3 READDIRPLUS results

 Documentation/sunrpc/xdr/nfs3.x    |  839 ++++++
 fs/nfsd/Makefile                   |   15 +-
 fs/nfsd/nfs2acl.c                  |   41 +-
 fs/nfsd/nfs3acl.c                  |   12 +-
 fs/nfsd/nfs3proc.c                 | 2545 ++++++++++++------
 fs/nfsd/nfs3xdr.c                  | 1391 +++-------
 fs/nfsd/nfs3xdr_gen.c              | 3862 ++++++++++++++++++++++++++++
 fs/nfsd/nfs3xdr_gen.h              |   82 +
 fs/nfsd/nfsctl.c                   |    1 -
 fs/nfsd/nfserr.h                   |    2 +-
 fs/nfsd/nfsfh.c                    |    1 -
 fs/nfsd/vfs.c                      |   76 +-
 fs/nfsd/xdr3.h                     |  293 +--
 include/linux/nfs3.h               |    3 -
 include/linux/sunrpc/xdrgen/nfs3.h | 1092 ++++++++
 15 files changed, 8104 insertions(+), 2151 deletions(-)
 create mode 100644 Documentation/sunrpc/xdr/nfs3.x
 create mode 100644 fs/nfsd/nfs3xdr_gen.c
 create mode 100644 fs/nfsd/nfs3xdr_gen.h
 create mode 100644 include/linux/sunrpc/xdrgen/nfs3.h

-- 
2.55.0


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

* [PATCH v1 01/33] NFSD: Report a first-batch readdir error in the reply status
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 02/33] Documentation: Add the RPC language description of NFSv3 Chuck Lever
                   ` (31 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

The NFSv2 READDIR encoder pulls directory entries while the reply
is encoded, after the status word is on the wire. nfsd_readdir_open()
does not read from the directory, so an iterate_dir() failure on the
first batch surfaces only in nfsd_readdir_next(), where nothing can
turn it into a status. The reply carries NFS_OK, no entries, and eof
false. The client resends the same cookie and receives the same reply,
so a directory that fails with -EIO or -ENOENT traps it in a loop.

Read the first batch in nfsd_readdir_open() so the error selects the
reply status before any of it is committed. An error on a later batch
still cuts the list short with eof false. The client's follow-up
request then starts from that cookie and gets the error in its status.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/vfs.c | 76 +++++++++++++++++++++++++++++++++------------------
 1 file changed, 50 insertions(+), 26 deletions(-)

diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index 4584d5b94fee..d46257dd8c61 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -2376,6 +2376,33 @@ static bool nfsd_buffered_filldir(struct dir_context *ctx, const char *name,
 	return true;
 }
 
+/*
+ * Read the next batch of entries.  Returns false at end of directory
+ * (@iter->eof set) or on a filesystem error (@iter->host_err set).
+ */
+static bool nfsd_readdir_fill(struct nfsd_readdir_iter *iter)
+{
+	/* Between batches the resume cookie advances past the last. */
+	if (iter->batched)
+		iter->offset = vfs_llseek(iter->file, 0, SEEK_CUR);
+
+	iter->used = 0;
+	iter->full = 0;
+	iter->host_err = iterate_dir(iter->file, &iter->ctx);
+	if (iter->full)
+		iter->host_err = 0;
+	if (iter->host_err < 0)
+		return false;
+	if (!iter->used) {
+		iter->eof = true;
+		return false;
+	}
+	iter->batched = true;
+	iter->pos = iter->page;
+	iter->remaining = iter->used;
+	return true;
+}
+
 /**
  * nfsd_readdir_open - open a directory for streaming readdir
  * @rqstp: RPC transaction context
@@ -2383,13 +2410,15 @@ static bool nfsd_buffered_filldir(struct dir_context *ctx, const char *name,
  * @offsetp: seek offset at which to resume reading
  * @iter: OUT: directory reader to initialize
  *
- * On success the directory is open and positioned at @offsetp, and
- * @iter is ready for nfsd_readdir_next().  The caller must release
- * @iter with nfsd_readdir_close() once done, including on error paths
- * that follow a successful open.
+ * On success the directory is open and positioned at @offsetp, the
+ * first batch of entries has been read, and @iter is ready for
+ * nfsd_readdir_next().  The caller must release @iter with
+ * nfsd_readdir_close() once done, including on error paths that
+ * follow a successful open.
  *
- * Return: nfs_ok on success, otherwise an nfsstat code.  On error @iter
- * is left safe to pass to nfsd_readdir_close().
+ * Return: nfs_ok on success, otherwise an nfsstat code, including the
+ * error from reading the first batch.  On error @iter is left safe to
+ * pass to nfsd_readdir_close().
  */
 __be32 nfsd_readdir_open(struct svc_rqst *rqstp, struct svc_fh *fhp,
 			 loff_t *offsetp, struct nfsd_readdir_iter *iter)
@@ -2425,6 +2454,17 @@ __be32 nfsd_readdir_open(struct svc_rqst *rqstp, struct svc_fh *fhp,
 	iter->file = file;
 	iter->fhp = fhp;
 	iter->offset = offset;
+
+	/*
+	 * The first batch is read here so a filesystem error surfaces
+	 * in the reply status.  Once the encoder has committed nfs_ok,
+	 * an error can only be reported as a short list.
+	 */
+	if (!nfsd_readdir_fill(iter) && iter->host_err) {
+		err = nfserrno(iter->host_err);
+		nfsd_readdir_close(iter);
+		return err;
+	}
 	return nfs_ok;
 
 out_close:
@@ -2449,26 +2489,10 @@ struct buffered_dirent *nfsd_readdir_next(struct nfsd_readdir_iter *iter)
 	struct buffered_dirent *de;
 	unsigned int reclen;
 
-	while (iter->remaining <= 0) {
-		/* Between batches the resume cookie advances past the last. */
-		if (iter->batched)
-			iter->offset = vfs_llseek(iter->file, 0, SEEK_CUR);
-
-		iter->used = 0;
-		iter->full = 0;
-		iter->host_err = iterate_dir(iter->file, &iter->ctx);
-		if (iter->full)
-			iter->host_err = 0;
-		if (iter->host_err < 0)
-			return NULL;
-		if (!iter->used) {
-			iter->eof = true;
-			return NULL;
-		}
-		iter->batched = true;
-		iter->pos = iter->page;
-		iter->remaining = iter->used;
-	}
+	if (iter->eof || iter->host_err)
+		return NULL;
+	if (iter->remaining <= 0 && !nfsd_readdir_fill(iter))
+		return NULL;
 
 	de = (struct buffered_dirent *)iter->pos;
 	iter->offset = de->offset;
-- 
2.55.0


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

* [PATCH v1 02/33] Documentation: Add the RPC language description of NFSv3
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 01/33] NFSD: Report a first-batch readdir error in the reply status Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 03/33] NFSD: Add infrastructure for generating NFSv3 XDR encoders and decoders Chuck Lever
                   ` (30 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, Chuck Lever

From: Chuck Lever <chuck.lever@oracle.com>

In order to generate source code to encode and decode NFSv3 protocol
elements, include a copy of the RPC language description of NFSv3
for xdrgen to process. The language description is copied from
RFC 1813.

The goals of replacing hand-written XDR functions with ones that
are tool-generated are to improve memory safety and make XDR
encoding and decoding less brittle to maintain. Parts of the
NFSv4 protocol are still being extended actively. Tool-generated
XDR code reduces the time it takes to get a working implementation
of new protocol elements.

The xdrgen utility derives both the type definitions and the
encode/decode functions directly from protocol specifications,
using names and symbols familiar to anyone who knows those specs.
Unlike hand-written code that can inadvertently diverge from the
specification, xdrgen guarantees that the generated code matches
the specification exactly.

We would eventually like xdrgen to generate Rust code as well,
making the conversion of the kernel's NFS stacks to use Rust just
a little easier for us.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 Documentation/sunrpc/xdr/nfs3.x | 812 ++++++++++++++++++++++++++++++++
 1 file changed, 812 insertions(+)
 create mode 100644 Documentation/sunrpc/xdr/nfs3.x

diff --git a/Documentation/sunrpc/xdr/nfs3.x b/Documentation/sunrpc/xdr/nfs3.x
new file mode 100644
index 000000000000..7996775fb4ae
--- /dev/null
+++ b/Documentation/sunrpc/xdr/nfs3.x
@@ -0,0 +1,812 @@
+/*
+ * Copyright (c) 1995 IETF Trust and the persons identified
+ * as the document authors.  All rights reserved.
+ *
+ * The document authors are identified in RFC 1813.
+ *
+ * Redistribution and use in source and binary forms, with
+ * or without modification, are permitted provided that the
+ * following conditions are met:
+ *
+ * - Redistributions of source code must retain the above
+ *   copyright notice, this list of conditions and the
+ *   following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the
+ *   following disclaimer in the documentation and/or other
+ *   materials provided with the distribution.
+ *
+ * - Neither the name of Internet Society, IETF or IETF
+ *   Trust, nor the names of specific contributors, may be
+ *   used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
+ *   AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ *   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ *   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ *   FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
+ *   EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ *   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ *   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ *   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ *   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ *   IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ *   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+const NFS3_FHSIZE		= 64;
+const NFS3_COOKIEVERFSIZE	= 8;
+const NFS3_CREATEVERFSIZE	= 8;
+const NFS3_WRITEVERFSIZE	= 8;
+
+typedef unsigned hyper uint64;
+typedef hyper int64;
+typedef unsigned long uint32;
+typedef long int32;
+typedef string filename3<>;
+typedef string nfspath3<>;
+typedef uint64 fileid3;
+typedef uint64 cookie3;
+typedef opaque cookieverf3[NFS3_COOKIEVERFSIZE];
+typedef opaque createverf3[NFS3_CREATEVERFSIZE];
+typedef opaque writeverf3[NFS3_WRITEVERFSIZE];
+typedef uint32 uid3;
+typedef uint32 gid3;
+typedef uint64 size3;
+typedef uint64 offset3;
+typedef uint32 mode3;
+typedef uint32 count3;
+
+enum nfsstat3 {
+	NFS3_OK			= 0,
+	NFS3ERR_PERM		= 1,
+	NFS3ERR_NOENT		= 2,
+	NFS3ERR_IO		= 5,
+	NFS3ERR_NXIO		= 6,
+	NFS3ERR_ACCES		= 13,
+	NFS3ERR_EXIST		= 17,
+	NFS3ERR_XDEV		= 18,
+	NFS3ERR_NODEV		= 19,
+	NFS3ERR_NOTDIR		= 20,
+	NFS3ERR_ISDIR		= 21,
+	NFS3ERR_INVAL		= 22,
+	NFS3ERR_FBIG		= 27,
+	NFS3ERR_NOSPC		= 28,
+	NFS3ERR_ROFS		= 30,
+	NFS3ERR_MLINK		= 31,
+	NFS3ERR_NAMETOOLONG	= 63,
+	NFS3ERR_NOTEMPTY	= 66,
+	NFS3ERR_DQUOT		= 69,
+	NFS3ERR_STALE		= 70,
+	NFS3ERR_REMOTE		= 71,
+	NFS3ERR_BADHANDLE	= 10001,
+	NFS3ERR_NOT_SYNC	= 10002,
+	NFS3ERR_BAD_COOKIE	= 10003,
+	NFS3ERR_NOTSUPP		= 10004,
+	NFS3ERR_TOOSMALL	= 10005,
+	NFS3ERR_SERVERFAULT	= 10006,
+	NFS3ERR_BADTYPE		= 10007,
+	NFS3ERR_JUKEBOX		= 10008
+};
+
+enum ftype3 {
+	NF3REG		= 1,
+	NF3DIR		= 2,
+	NF3BLK		= 3,
+	NF3CHR		= 4,
+	NF3LNK		= 5,
+	NF3SOCK		= 6,
+	NF3FIFO		= 7
+};
+
+struct specdata3 {
+	uint32		specdata1;
+	uint32		specdata2;
+};
+
+struct nfs_fh3 {
+	opaque		data<NFS3_FHSIZE>;
+};
+
+struct nfstime3 {
+	uint32		seconds;
+	uint32		nseconds;
+};
+
+struct fattr3 {
+	ftype3		type;
+	mode3		mode;
+	uint32		nlink;
+	uid3		uid;
+	gid3		gid;
+	size3		size;
+	size3		used;
+	specdata3	rdev;
+	uint64		fsid;
+	fileid3		fileid;
+	nfstime3	atime;
+	nfstime3	mtime;
+	nfstime3	ctime;
+};
+
+union post_op_attr switch (bool attributes_follow) {
+	case TRUE:
+		fattr3		attributes;
+	case FALSE:
+		void;
+};
+
+struct wcc_attr {
+	size3		size;
+	nfstime3	mtime;
+	nfstime3	ctime;
+};
+
+union pre_op_attr switch (bool attributes_follow) {
+	case TRUE:
+		wcc_attr	attributes;
+	case FALSE:
+		void;
+};
+
+struct wcc_data {
+	pre_op_attr	before;
+	post_op_attr	after;
+};
+
+union post_op_fh3 switch (bool handle_follows) {
+	case TRUE:
+		nfs_fh3		handle;
+	case FALSE:
+		void;
+};
+
+enum time_how {
+	DONT_CHANGE		= 0,
+	SET_TO_SERVER_TIME	= 1,
+	SET_TO_CLIENT_TIME	= 2
+};
+
+union set_mode3 switch (bool set_it) {
+	case TRUE:
+		mode3		mode;
+	default:
+		void;
+};
+
+union set_uid3 switch (bool set_it) {
+	case TRUE:
+		uid3		uid;
+	default:
+		void;
+};
+
+union set_gid3 switch (bool set_it) {
+	case TRUE:
+		gid3		gid;
+	default:
+		void;
+};
+
+union set_size3 switch (bool set_it) {
+	case TRUE:
+		size3		size;
+	default:
+		void;
+};
+
+union set_atime switch (time_how set_it) {
+	case SET_TO_CLIENT_TIME:
+		nfstime3	atime;
+	default:
+		void;
+};
+
+union set_mtime switch (time_how set_it) {
+	case SET_TO_CLIENT_TIME:
+		nfstime3	mtime;
+	default:
+		void;
+};
+
+struct sattr3 {
+	set_mode3	mode;
+	set_uid3	uid;
+	set_gid3	gid;
+	set_size3	size;
+	set_atime	atime;
+	set_mtime	mtime;
+};
+
+struct diropargs3 {
+	nfs_fh3		dir;
+	filename3	name;
+};
+
+struct GETATTR3args {
+	nfs_fh3		object;
+};
+
+struct GETATTR3resok {
+	fattr3		obj_attributes;
+};
+
+union GETATTR3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		GETATTR3resok	resok;
+	default:
+		void;
+};
+
+union sattrguard3 switch (bool check) {
+	case TRUE:
+		nfstime3	obj_ctime;
+	case FALSE:
+		void;
+};
+
+struct SETATTR3args {
+	nfs_fh3		object;
+	sattr3		new_attributes;
+	sattrguard3	guard;
+};
+
+struct SETATTR3resok {
+	wcc_data	obj_wcc;
+};
+
+struct SETATTR3resfail {
+	wcc_data	obj_wcc;
+};
+
+union SETATTR3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		SETATTR3resok		resok;
+	default:
+		SETATTR3resfail		resfail;
+};
+
+struct LOOKUP3args {
+	diropargs3	what;
+};
+
+struct LOOKUP3resok {
+	nfs_fh3		object;
+	post_op_attr	obj_attributes;
+	post_op_attr	dir_attributes;
+};
+
+struct LOOKUP3resfail {
+	post_op_attr	dir_attributes;
+};
+
+union LOOKUP3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		LOOKUP3resok		resok;
+	default:
+		LOOKUP3resfail		resfail;
+};
+
+const ACCESS3_READ	= 0x0001;
+const ACCESS3_LOOKUP	= 0x0002;
+const ACCESS3_MODIFY	= 0x0004;
+const ACCESS3_EXTEND	= 0x0008;
+const ACCESS3_DELETE	= 0x0010;
+const ACCESS3_EXECUTE	= 0x0020;
+
+struct ACCESS3args {
+	nfs_fh3		object;
+	uint32		access;
+};
+
+struct ACCESS3resok {
+	post_op_attr	obj_attributes;
+	uint32		access;
+};
+
+struct ACCESS3resfail {
+	post_op_attr	obj_attributes;
+};
+
+union ACCESS3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		ACCESS3resok	resok;
+	default:
+		ACCESS3resfail	resfail;
+};
+
+struct READLINK3args {
+	nfs_fh3		symlink;
+};
+
+struct READLINK3resok {
+	post_op_attr	symlink_attributes;
+	nfspath3	data;
+};
+
+struct READLINK3resfail {
+	post_op_attr	symlink_attributes;
+};
+
+union READLINK3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		READLINK3resok	resok;
+	default:
+		READLINK3resfail	resfail;
+};
+
+struct READ3args {
+	nfs_fh3		file;
+	offset3		offset;
+	count3		count;
+};
+
+struct READ3resok {
+	post_op_attr	file_attributes;
+	count3		count;
+	bool		eof;
+	opaque		data<>;
+};
+
+struct READ3resfail {
+	post_op_attr	file_attributes;
+};
+
+union READ3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		READ3resok	resok;
+	default:
+		READ3resfail	resfail;
+};
+
+enum stable_how {
+	UNSTABLE	= 0,
+	DATA_SYNC	= 1,
+	FILE_SYNC	= 2
+};
+
+struct WRITE3args {
+	nfs_fh3		file;
+	offset3		offset;
+	count3		count;
+	stable_how	stable;
+	opaque		data<>;
+};
+
+struct WRITE3resok {
+	wcc_data	file_wcc;
+	count3		count;
+	stable_how	committed;
+	writeverf3	verf;
+};
+
+struct WRITE3resfail {
+	wcc_data	file_wcc;
+};
+
+union WRITE3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		WRITE3resok		resok;
+	default:
+		WRITE3resfail		resfail;
+};
+
+enum createmode3 {
+	UNCHECKED	= 0,
+	GUARDED		= 1,
+	EXCLUSIVE	= 2
+};
+
+union createhow3 switch (createmode3 mode) {
+	case UNCHECKED:
+	case GUARDED:
+		sattr3			obj_attributes;
+	case EXCLUSIVE:
+		createverf3		verf;
+};
+
+struct CREATE3args {
+	diropargs3	where;
+	createhow3	how;
+};
+
+struct CREATE3resok {
+	post_op_fh3	obj;
+	post_op_attr	obj_attributes;
+	wcc_data	dir_wcc;
+};
+
+struct CREATE3resfail {
+	wcc_data	dir_wcc;
+};
+
+union CREATE3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		CREATE3resok		resok;
+	default:
+		CREATE3resfail		resfail;
+};
+
+struct MKDIR3args {
+	diropargs3	where;
+	sattr3		attributes;
+};
+
+struct MKDIR3resok {
+	post_op_fh3	obj;
+	post_op_attr	obj_attributes;
+	wcc_data	dir_wcc;
+};
+
+struct MKDIR3resfail {
+	wcc_data	dir_wcc;
+};
+
+union MKDIR3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		MKDIR3resok		resok;
+	default:
+		MKDIR3resfail		resfail;
+};
+
+struct symlinkdata3 {
+	sattr3		symlink_attributes;
+	nfspath3	symlink_data;
+};
+
+struct SYMLINK3args {
+	diropargs3	where;
+	symlinkdata3	symlink;
+};
+
+struct SYMLINK3resok {
+	post_op_fh3	obj;
+	post_op_attr	obj_attributes;
+	wcc_data	dir_wcc;
+};
+
+struct SYMLINK3resfail {
+	wcc_data	dir_wcc;
+};
+
+union SYMLINK3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		SYMLINK3resok		resok;
+	default:
+		SYMLINK3resfail		resfail;
+};
+
+struct devicedata3 {
+	sattr3		dev_attributes;
+	specdata3	spec;
+};
+
+union mknoddata3 switch (ftype3 type) {
+	case NF3CHR:
+	case NF3BLK:
+		devicedata3	device;
+	case NF3SOCK:
+	case NF3FIFO:
+		sattr3		pipe_attributes;
+	default:
+		void;
+};
+
+struct MKNOD3args {
+	diropargs3	where;
+	mknoddata3	what;
+};
+
+struct MKNOD3resok {
+	post_op_fh3	obj;
+	post_op_attr	obj_attributes;
+	wcc_data	dir_wcc;
+};
+
+struct MKNOD3resfail {
+	wcc_data	dir_wcc;
+};
+
+union MKNOD3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		MKNOD3resok		resok;
+	default:
+		MKNOD3resfail		resfail;
+};
+
+struct REMOVE3args {
+	diropargs3	object;
+};
+
+struct REMOVE3resok {
+	wcc_data	dir_wcc;
+};
+
+struct REMOVE3resfail {
+	wcc_data	dir_wcc;
+};
+
+union REMOVE3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		REMOVE3resok		resok;
+	default:
+		REMOVE3resfail		resfail;
+};
+
+struct RMDIR3args {
+	diropargs3	object;
+};
+
+struct RMDIR3resok {
+	wcc_data	dir_wcc;
+};
+
+struct RMDIR3resfail {
+	wcc_data	dir_wcc;
+};
+
+union RMDIR3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		RMDIR3resok		resok;
+	default:
+		RMDIR3resfail		resfail;
+};
+
+struct RENAME3args {
+	diropargs3	from;
+	diropargs3	to;
+};
+
+struct RENAME3resok {
+	wcc_data	fromdir_wcc;
+	wcc_data	todir_wcc;
+};
+
+struct RENAME3resfail {
+	wcc_data	fromdir_wcc;
+	wcc_data	todir_wcc;
+};
+
+union RENAME3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		RENAME3resok		resok;
+	default:
+		RENAME3resfail		resfail;
+};
+
+struct LINK3args {
+	nfs_fh3		file;
+	diropargs3	link;
+};
+
+struct LINK3resok {
+	post_op_attr	file_attributes;
+	wcc_data	linkdir_wcc;
+};
+
+struct LINK3resfail {
+	post_op_attr	file_attributes;
+	wcc_data	linkdir_wcc;
+};
+
+union LINK3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		LINK3resok		resok;
+	default:
+		LINK3resfail		resfail;
+};
+
+struct READDIR3args {
+	nfs_fh3		dir;
+	cookie3		cookie;
+	cookieverf3	cookieverf;
+	count3		count;
+};
+
+struct entry3 {
+	fileid3		fileid;
+	filename3	name;
+	cookie3		cookie;
+	entry3		*nextentry;
+};
+
+struct dirlist3 {
+	entry3		*entries;
+	bool		eof;
+};
+
+struct READDIR3resok {
+	post_op_attr	dir_attributes;
+	cookieverf3	cookieverf;
+	dirlist3	reply;
+};
+
+struct READDIR3resfail {
+	post_op_attr	dir_attributes;
+};
+
+union READDIR3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		READDIR3resok		resok;
+	default:
+		READDIR3resfail		resfail;
+};
+
+struct READDIRPLUS3args {
+	nfs_fh3		dir;
+	cookie3		cookie;
+	cookieverf3	cookieverf;
+	count3		dircount;
+	count3		maxcount;
+};
+
+struct entryplus3 {
+	fileid3		fileid;
+	filename3	name;
+	cookie3		cookie;
+	post_op_attr	name_attributes;
+	post_op_fh3	name_handle;
+	entryplus3	*nextentry;
+};
+
+struct dirlistplus3 {
+	entryplus3	*entries;
+	bool		eof;
+};
+
+struct READDIRPLUS3resok {
+	post_op_attr	dir_attributes;
+	cookieverf3	cookieverf;
+	dirlistplus3	reply;
+};
+
+struct READDIRPLUS3resfail {
+	post_op_attr	dir_attributes;
+};
+
+union READDIRPLUS3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		READDIRPLUS3resok	resok;
+	default:
+		READDIRPLUS3resfail	resfail;
+};
+
+
+struct FSSTAT3args {
+	nfs_fh3		fsroot;
+};
+
+struct FSSTAT3resok {
+	post_op_attr	obj_attributes;
+	size3		tbytes;
+	size3		fbytes;
+	size3		abytes;
+	size3		tfiles;
+	size3		ffiles;
+	size3		afiles;
+	uint32		invarsec;
+};
+
+struct FSSTAT3resfail {
+	post_op_attr	obj_attributes;
+};
+
+union FSSTAT3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		FSSTAT3resok		resok;
+	default:
+		FSSTAT3resfail		resfail;
+};
+
+const FSF3_LINK		= 0x0001;
+const FSF3_SYMLINK	= 0x0002;
+const FSF3_HOMOGENEOUS	= 0x0008;
+const FSF3_CANSETTIME	= 0x0010;
+
+struct FSINFO3args {
+	nfs_fh3		fsroot;
+};
+
+struct FSINFO3resok {
+	post_op_attr	obj_attributes;
+	uint32		rtmax;
+	uint32		rtpref;
+	uint32		rtmult;
+	uint32		wtmax;
+	uint32		wtpref;
+	uint32		wtmult;
+	uint32		dtpref;
+	size3		maxfilesize;
+	nfstime3	time_delta;
+	uint32		properties;
+};
+
+struct FSINFO3resfail {
+	post_op_attr	obj_attributes;
+};
+
+union FSINFO3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		FSINFO3resok		resok;
+	default:
+		FSINFO3resfail		resfail;
+};
+
+struct PATHCONF3args {
+	nfs_fh3		object;
+};
+
+struct PATHCONF3resok {
+	post_op_attr	obj_attributes;
+	uint32		linkmax;
+	uint32		name_max;
+	bool		no_trunc;
+	bool		chown_restricted;
+	bool		case_insensitive;
+	bool		case_preserving;
+};
+
+struct PATHCONF3resfail {
+	post_op_attr	obj_attributes;
+};
+
+union PATHCONF3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		PATHCONF3resok		resok;
+	default:
+		PATHCONF3resfail	resfail;
+};
+
+struct COMMIT3args {
+	nfs_fh3		file;
+	offset3		offset;
+	count3		count;
+};
+
+struct COMMIT3resok {
+	wcc_data	file_wcc;
+	writeverf3	verf;
+};
+
+struct COMMIT3resfail {
+	wcc_data	file_wcc;
+};
+
+union COMMIT3res switch (nfsstat3 status) {
+	case NFS3_OK:
+		COMMIT3resok		resok;
+	default:
+		COMMIT3resfail		resfail;
+};
+
+program NFS_PROGRAM {
+	version NFS_V3 {
+		void		NFSPROC3_NULL(void) = 0;
+		GETATTR3res	NFSPROC3_GETATTR(GETATTR3args) = 1;
+		SETATTR3res	NFSPROC3_SETATTR(SETATTR3args) = 2;
+		LOOKUP3res	NFSPROC3_LOOKUP(LOOKUP3args) = 3;
+		ACCESS3res	NFSPROC3_ACCESS(ACCESS3args) = 4;
+		READLINK3res	NFSPROC3_READLINK(READLINK3args) = 5;
+		READ3res	NFSPROC3_READ(READ3args) = 6;
+		WRITE3res	NFSPROC3_WRITE(WRITE3args) = 7;
+		CREATE3res	NFSPROC3_CREATE(CREATE3args) = 8;
+		MKDIR3res	NFSPROC3_MKDIR(MKDIR3args) = 9;
+		SYMLINK3res	NFSPROC3_SYMLINK(SYMLINK3args) = 10;
+		MKNOD3res	NFSPROC3_MKNOD(MKNOD3args) = 11;
+		REMOVE3res	NFSPROC3_REMOVE(REMOVE3args) = 12;
+		RMDIR3res	NFSPROC3_RMDIR(RMDIR3args) = 13;
+		RENAME3res	NFSPROC3_RENAME(RENAME3args) = 14;
+		LINK3res	NFSPROC3_LINK(LINK3args) = 15;
+		READDIR3res	NFSPROC3_READDIR(READDIR3args) = 16;
+		READDIRPLUS3res	NFSPROC3_READDIRPLUS(READDIRPLUS3args) = 17;
+		FSSTAT3res	NFSPROC3_FSSTAT(FSSTAT3args) = 18;
+		FSINFO3res	NFSPROC3_FSINFO(FSINFO3args) = 19;
+		PATHCONF3res	NFSPROC3_PATHCONF(PATHCONF3args) = 20;
+		COMMIT3res	NFSPROC3_COMMIT(COMMIT3args) = 21;
+	} = 3;
+} = 100003;
-- 
2.55.0


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

* [PATCH v1 03/33] NFSD: Add infrastructure for generating NFSv3 XDR encoders and decoders
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 01/33] NFSD: Report a first-batch readdir error in the reply status Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 02/33] Documentation: Add the RPC language description of NFSv3 Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 04/33] NFSD: Replace nfs3.h with nfs3xdr_gen.h Chuck Lever
                   ` (29 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

NFSD's NFSv3 XDR codecs are hand-maintained; they are being converted
to xdrgen-generated code one RPC procedure at a time. The first step
is to generate and commit the xdrgen C code based on nfs3.x. The
generated code can be refreshed any time nfs3.x is modified by
running "(cd fs/nfsd && make xdrgen)".

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 Documentation/sunrpc/xdr/nfs3.x    |    2 +
 fs/nfsd/Makefile                   |   15 +-
 fs/nfsd/nfs3xdr_gen.c              | 3768 ++++++++++++++++++++++++++++
 fs/nfsd/nfs3xdr_gen.h              |   60 +
 include/linux/sunrpc/xdrgen/nfs3.h | 1092 ++++++++
 5 files changed, 4933 insertions(+), 4 deletions(-)
 create mode 100644 fs/nfsd/nfs3xdr_gen.c
 create mode 100644 fs/nfsd/nfs3xdr_gen.h
 create mode 100644 include/linux/sunrpc/xdrgen/nfs3.h

diff --git a/Documentation/sunrpc/xdr/nfs3.x b/Documentation/sunrpc/xdr/nfs3.x
index 7996775fb4ae..9feabfda94e7 100644
--- a/Documentation/sunrpc/xdr/nfs3.x
+++ b/Documentation/sunrpc/xdr/nfs3.x
@@ -39,6 +39,8 @@
  *   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+pragma header nfs3;
+
 const NFS3_FHSIZE		= 64;
 const NFS3_COOKIEVERFSIZE	= 8;
 const NFS3_CREATEVERFSIZE	= 8;
diff --git a/fs/nfsd/Makefile b/fs/nfsd/Makefile
index 05fb814f760b..1dc449af28f4 100644
--- a/fs/nfsd/Makefile
+++ b/fs/nfsd/Makefile
@@ -12,8 +12,8 @@ nfsd-y			+= trace.o
 
 nfsd-y 			+= nfssvc.o nfsctl.o nfsfh.o vfs.o \
 			   export.o auth.o lockd.o nfscache.o \
-			   stats.o filecache.o nfs3proc.o nfs3xdr.o \
-			   netlink.o
+			   stats.o filecache.o netlink.o \
+			   nfs3proc.o nfs3xdr.o nfs3xdr_gen.o
 nfsd-$(CONFIG_NFSD_V2) += nfsproc.o nfsxdr.o nfs2xdr_gen.o
 nfsd-$(CONFIG_NFSD_V2_ACL) += nfs2acl.o
 nfsd-$(CONFIG_NFSD_V3_ACL) += nfs3acl.o
@@ -38,23 +38,30 @@ nfsd-$(CONFIG_DEBUG_FS) += debugfs.o
 .PHONY: xdrgen
 
 XDRGEN_DEFINITIONS	= ../../include/linux/sunrpc/xdrgen/nfs2.h \
+			  ../../include/linux/sunrpc/xdrgen/nfs3.h \
 			  ../../include/linux/sunrpc/xdrgen/nfs4_1.h
-XDRGEN_DECLARATIONS	= nfs2xdr_gen.h nfs4xdr_gen.h
-XDRGEN_SOURCE		= nfs2xdr_gen.c nfs4xdr_gen.c
+XDRGEN_DECLARATIONS	= nfs2xdr_gen.h nfs3xdr_gen.h nfs4xdr_gen.h
+XDRGEN_SOURCE		= nfs2xdr_gen.c nfs3xdr_gen.c nfs4xdr_gen.c
 
 xdrgen: $(XDRGEN_DEFINITIONS) $(XDRGEN_DECLARATIONS) $(XDRGEN_SOURCE)
 
 ../../include/linux/sunrpc/xdrgen/nfs2.h: ../../Documentation/sunrpc/xdr/nfs2.x
 	../../tools/net/sunrpc/xdrgen/xdrgen definitions $< > $@
+../../include/linux/sunrpc/xdrgen/nfs3.h: ../../Documentation/sunrpc/xdr/nfs3.x
+	../../tools/net/sunrpc/xdrgen/xdrgen definitions $< > $@
 ../../include/linux/sunrpc/xdrgen/nfs4_1.h: ../../Documentation/sunrpc/xdr/nfs4_1.x
 	../../tools/net/sunrpc/xdrgen/xdrgen definitions $< > $@
 
 nfs2xdr_gen.h: ../../Documentation/sunrpc/xdr/nfs2.x
 	../../tools/net/sunrpc/xdrgen/xdrgen declarations $< > $@
+nfs3xdr_gen.h: ../../Documentation/sunrpc/xdr/nfs3.x
+	../../tools/net/sunrpc/xdrgen/xdrgen declarations $< > $@
 nfs4xdr_gen.h: ../../Documentation/sunrpc/xdr/nfs4_1.x
 	../../tools/net/sunrpc/xdrgen/xdrgen declarations $< > $@
 
 nfs2xdr_gen.c: ../../Documentation/sunrpc/xdr/nfs2.x
 	../../tools/net/sunrpc/xdrgen/xdrgen source $< > $@
+nfs3xdr_gen.c: ../../Documentation/sunrpc/xdr/nfs3.x
+	../../tools/net/sunrpc/xdrgen/xdrgen source $< > $@
 nfs4xdr_gen.c: ../../Documentation/sunrpc/xdr/nfs4_1.x
 	../../tools/net/sunrpc/xdrgen/xdrgen source $< > $@
diff --git a/fs/nfsd/nfs3xdr_gen.c b/fs/nfsd/nfs3xdr_gen.c
new file mode 100644
index 000000000000..48b382ccfc81
--- /dev/null
+++ b/fs/nfsd/nfs3xdr_gen.c
@@ -0,0 +1,3768 @@
+// 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: Fri Jul 10 23:20:55 2026
+
+#include <linux/sunrpc/svc.h>
+
+#include "nfs3xdr_gen.h"
+
+static bool __maybe_unused
+xdrgen_decode_uint64(struct xdr_stream *xdr, uint64 *ptr)
+{
+	return xdrgen_decode_unsigned_hyper(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_int64(struct xdr_stream *xdr, int64 *ptr)
+{
+	return xdrgen_decode_hyper(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_uint32(struct xdr_stream *xdr, uint32 *ptr)
+{
+	return xdrgen_decode_unsigned_long(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_int32(struct xdr_stream *xdr, int32 *ptr)
+{
+	return xdrgen_decode_long(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_filename3(struct xdr_stream *xdr, filename3 *ptr)
+{
+	return xdrgen_decode_string(xdr, ptr, 0);
+}
+
+static bool __maybe_unused
+xdrgen_decode_nfspath3(struct xdr_stream *xdr, nfspath3 *ptr)
+{
+	return xdrgen_decode_string(xdr, ptr, 0);
+}
+
+static bool __maybe_unused
+xdrgen_decode_fileid3(struct xdr_stream *xdr, fileid3 *ptr)
+{
+	return xdrgen_decode_uint64(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_cookie3(struct xdr_stream *xdr, cookie3 *ptr)
+{
+	return xdrgen_decode_uint64(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_cookieverf3(struct xdr_stream *xdr, cookieverf3 *ptr)
+{
+	return xdr_stream_decode_opaque_fixed(xdr, ptr, NFS3_COOKIEVERFSIZE) == 0;
+}
+
+static bool __maybe_unused
+xdrgen_decode_createverf3(struct xdr_stream *xdr, createverf3 *ptr)
+{
+	return xdr_stream_decode_opaque_fixed(xdr, ptr, NFS3_CREATEVERFSIZE) == 0;
+}
+
+static bool __maybe_unused
+xdrgen_decode_writeverf3(struct xdr_stream *xdr, writeverf3 *ptr)
+{
+	return xdr_stream_decode_opaque_fixed(xdr, ptr, NFS3_WRITEVERFSIZE) == 0;
+}
+
+static bool __maybe_unused
+xdrgen_decode_uid3(struct xdr_stream *xdr, uid3 *ptr)
+{
+	return xdrgen_decode_uint32(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_gid3(struct xdr_stream *xdr, gid3 *ptr)
+{
+	return xdrgen_decode_uint32(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_size3(struct xdr_stream *xdr, size3 *ptr)
+{
+	return xdrgen_decode_uint64(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_offset3(struct xdr_stream *xdr, offset3 *ptr)
+{
+	return xdrgen_decode_uint64(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_mode3(struct xdr_stream *xdr, mode3 *ptr)
+{
+	return xdrgen_decode_uint32(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_count3(struct xdr_stream *xdr, count3 *ptr)
+{
+	return xdrgen_decode_uint32(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_nfsstat3(struct xdr_stream *xdr, nfsstat3 *ptr)
+{
+	u32 val;
+
+	if (xdr_stream_decode_u32(xdr, &val) < 0)
+		return false;
+	/* Compiler may optimize to a range check for dense enums */
+	switch (val) {
+	case NFS3_OK:
+	case NFS3ERR_PERM:
+	case NFS3ERR_NOENT:
+	case NFS3ERR_IO:
+	case NFS3ERR_NXIO:
+	case NFS3ERR_ACCES:
+	case NFS3ERR_EXIST:
+	case NFS3ERR_XDEV:
+	case NFS3ERR_NODEV:
+	case NFS3ERR_NOTDIR:
+	case NFS3ERR_ISDIR:
+	case NFS3ERR_INVAL:
+	case NFS3ERR_FBIG:
+	case NFS3ERR_NOSPC:
+	case NFS3ERR_ROFS:
+	case NFS3ERR_MLINK:
+	case NFS3ERR_NAMETOOLONG:
+	case NFS3ERR_NOTEMPTY:
+	case NFS3ERR_DQUOT:
+	case NFS3ERR_STALE:
+	case NFS3ERR_REMOTE:
+	case NFS3ERR_BADHANDLE:
+	case NFS3ERR_NOT_SYNC:
+	case NFS3ERR_BAD_COOKIE:
+	case NFS3ERR_NOTSUPP:
+	case NFS3ERR_TOOSMALL:
+	case NFS3ERR_SERVERFAULT:
+	case NFS3ERR_BADTYPE:
+	case NFS3ERR_JUKEBOX:
+		break;
+	default:
+		return false;
+	}
+	*ptr = val;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_ftype3(struct xdr_stream *xdr, ftype3 *ptr)
+{
+	u32 val;
+
+	if (xdr_stream_decode_u32(xdr, &val) < 0)
+		return false;
+	/* Compiler may optimize to a range check for dense enums */
+	switch (val) {
+	case NF3REG:
+	case NF3DIR:
+	case NF3BLK:
+	case NF3CHR:
+	case NF3LNK:
+	case NF3SOCK:
+	case NF3FIFO:
+		break;
+	default:
+		return false;
+	}
+	*ptr = val;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_specdata3(struct xdr_stream *xdr, struct specdata3 *ptr)
+{
+	if (!xdrgen_decode_uint32(xdr, &ptr->specdata1))
+		return false;
+	if (!xdrgen_decode_uint32(xdr, &ptr->specdata2))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nfs_fh3(struct xdr_stream *xdr, struct nfs_fh3 *ptr)
+{
+	if (!xdrgen_decode_opaque(xdr, &ptr->data, NFS3_FHSIZE))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nfstime3(struct xdr_stream *xdr, struct nfstime3 *ptr)
+{
+	if (!xdrgen_decode_uint32(xdr, &ptr->seconds))
+		return false;
+	if (!xdrgen_decode_uint32(xdr, &ptr->nseconds))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_fattr3(struct xdr_stream *xdr, struct fattr3 *ptr)
+{
+	if (!xdrgen_decode_ftype3(xdr, &ptr->type))
+		return false;
+	if (!xdrgen_decode_mode3(xdr, &ptr->mode))
+		return false;
+	if (!xdrgen_decode_uint32(xdr, &ptr->nlink))
+		return false;
+	if (!xdrgen_decode_uid3(xdr, &ptr->uid))
+		return false;
+	if (!xdrgen_decode_gid3(xdr, &ptr->gid))
+		return false;
+	if (!xdrgen_decode_size3(xdr, &ptr->size))
+		return false;
+	if (!xdrgen_decode_size3(xdr, &ptr->used))
+		return false;
+	if (!xdrgen_decode_specdata3(xdr, &ptr->rdev))
+		return false;
+	if (!xdrgen_decode_uint64(xdr, &ptr->fsid))
+		return false;
+	if (!xdrgen_decode_fileid3(xdr, &ptr->fileid))
+		return false;
+	if (!xdrgen_decode_nfstime3(xdr, &ptr->atime))
+		return false;
+	if (!xdrgen_decode_nfstime3(xdr, &ptr->mtime))
+		return false;
+	if (!xdrgen_decode_nfstime3(xdr, &ptr->ctime))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_post_op_attr(struct xdr_stream *xdr, struct post_op_attr *ptr)
+{
+	if (!xdrgen_decode_bool(xdr, &ptr->attributes_follow))
+		return false;
+	if (ptr->attributes_follow) {
+		if (!xdrgen_decode_fattr3(xdr, &ptr->u.attributes))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_wcc_attr(struct xdr_stream *xdr, struct wcc_attr *ptr)
+{
+	if (!xdrgen_decode_size3(xdr, &ptr->size))
+		return false;
+	if (!xdrgen_decode_nfstime3(xdr, &ptr->mtime))
+		return false;
+	if (!xdrgen_decode_nfstime3(xdr, &ptr->ctime))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_pre_op_attr(struct xdr_stream *xdr, struct pre_op_attr *ptr)
+{
+	if (!xdrgen_decode_bool(xdr, &ptr->attributes_follow))
+		return false;
+	if (ptr->attributes_follow) {
+		if (!xdrgen_decode_wcc_attr(xdr, &ptr->u.attributes))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_wcc_data(struct xdr_stream *xdr, struct wcc_data *ptr)
+{
+	if (!xdrgen_decode_pre_op_attr(xdr, &ptr->before))
+		return false;
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->after))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_post_op_fh3(struct xdr_stream *xdr, struct post_op_fh3 *ptr)
+{
+	if (!xdrgen_decode_bool(xdr, &ptr->handle_follows))
+		return false;
+	if (ptr->handle_follows) {
+		if (!xdrgen_decode_nfs_fh3(xdr, &ptr->u.handle))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_time_how(struct xdr_stream *xdr, time_how *ptr)
+{
+	u32 val;
+
+	if (xdr_stream_decode_u32(xdr, &val) < 0)
+		return false;
+	/* Compiler may optimize to a range check for dense enums */
+	switch (val) {
+	case DONT_CHANGE:
+	case SET_TO_SERVER_TIME:
+	case SET_TO_CLIENT_TIME:
+		break;
+	default:
+		return false;
+	}
+	*ptr = val;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_set_mode3(struct xdr_stream *xdr, struct set_mode3 *ptr)
+{
+	if (!xdrgen_decode_bool(xdr, &ptr->set_it))
+		return false;
+	if (ptr->set_it) {
+		if (!xdrgen_decode_mode3(xdr, &ptr->u.mode))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_set_uid3(struct xdr_stream *xdr, struct set_uid3 *ptr)
+{
+	if (!xdrgen_decode_bool(xdr, &ptr->set_it))
+		return false;
+	if (ptr->set_it) {
+		if (!xdrgen_decode_uid3(xdr, &ptr->u.uid))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_set_gid3(struct xdr_stream *xdr, struct set_gid3 *ptr)
+{
+	if (!xdrgen_decode_bool(xdr, &ptr->set_it))
+		return false;
+	if (ptr->set_it) {
+		if (!xdrgen_decode_gid3(xdr, &ptr->u.gid))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_set_size3(struct xdr_stream *xdr, struct set_size3 *ptr)
+{
+	if (!xdrgen_decode_bool(xdr, &ptr->set_it))
+		return false;
+	if (ptr->set_it) {
+		if (!xdrgen_decode_size3(xdr, &ptr->u.size))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_set_atime(struct xdr_stream *xdr, struct set_atime *ptr)
+{
+	if (!xdrgen_decode_time_how(xdr, &ptr->set_it))
+		return false;
+	switch (ptr->set_it) {
+	case SET_TO_CLIENT_TIME:
+		if (!xdrgen_decode_nfstime3(xdr, &ptr->u.atime))
+			return false;
+		break;
+	default:
+		break;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_set_mtime(struct xdr_stream *xdr, struct set_mtime *ptr)
+{
+	if (!xdrgen_decode_time_how(xdr, &ptr->set_it))
+		return false;
+	switch (ptr->set_it) {
+	case SET_TO_CLIENT_TIME:
+		if (!xdrgen_decode_nfstime3(xdr, &ptr->u.mtime))
+			return false;
+		break;
+	default:
+		break;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_sattr3(struct xdr_stream *xdr, struct sattr3 *ptr)
+{
+	if (!xdrgen_decode_set_mode3(xdr, &ptr->mode))
+		return false;
+	if (!xdrgen_decode_set_uid3(xdr, &ptr->uid))
+		return false;
+	if (!xdrgen_decode_set_gid3(xdr, &ptr->gid))
+		return false;
+	if (!xdrgen_decode_set_size3(xdr, &ptr->size))
+		return false;
+	if (!xdrgen_decode_set_atime(xdr, &ptr->atime))
+		return false;
+	if (!xdrgen_decode_set_mtime(xdr, &ptr->mtime))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_diropargs3(struct xdr_stream *xdr, struct diropargs3 *ptr)
+{
+	if (!xdrgen_decode_nfs_fh3(xdr, &ptr->dir))
+		return false;
+	if (!xdrgen_decode_filename3(xdr, &ptr->name))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_GETATTR3args(struct xdr_stream *xdr, struct GETATTR3args *ptr)
+{
+	if (!xdrgen_decode_nfs_fh3(xdr, &ptr->object))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_GETATTR3resok(struct xdr_stream *xdr, struct GETATTR3resok *ptr)
+{
+	if (!xdrgen_decode_fattr3(xdr, &ptr->obj_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_GETATTR3res(struct xdr_stream *xdr, struct GETATTR3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_GETATTR3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		break;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_sattrguard3(struct xdr_stream *xdr, struct sattrguard3 *ptr)
+{
+	if (!xdrgen_decode_bool(xdr, &ptr->check))
+		return false;
+	if (ptr->check) {
+		if (!xdrgen_decode_nfstime3(xdr, &ptr->u.obj_ctime))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_SETATTR3args(struct xdr_stream *xdr, struct SETATTR3args *ptr)
+{
+	if (!xdrgen_decode_nfs_fh3(xdr, &ptr->object))
+		return false;
+	if (!xdrgen_decode_sattr3(xdr, &ptr->new_attributes))
+		return false;
+	if (!xdrgen_decode_sattrguard3(xdr, &ptr->guard))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_SETATTR3resok(struct xdr_stream *xdr, struct SETATTR3resok *ptr)
+{
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->obj_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_SETATTR3resfail(struct xdr_stream *xdr, struct SETATTR3resfail *ptr)
+{
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->obj_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_SETATTR3res(struct xdr_stream *xdr, struct SETATTR3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_SETATTR3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_SETATTR3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_LOOKUP3args(struct xdr_stream *xdr, struct LOOKUP3args *ptr)
+{
+	if (!xdrgen_decode_diropargs3(xdr, &ptr->what))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_LOOKUP3resok(struct xdr_stream *xdr, struct LOOKUP3resok *ptr)
+{
+	if (!xdrgen_decode_nfs_fh3(xdr, &ptr->object))
+		return false;
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->obj_attributes))
+		return false;
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->dir_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_LOOKUP3resfail(struct xdr_stream *xdr, struct LOOKUP3resfail *ptr)
+{
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->dir_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_LOOKUP3res(struct xdr_stream *xdr, struct LOOKUP3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_LOOKUP3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_LOOKUP3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_ACCESS3args(struct xdr_stream *xdr, struct ACCESS3args *ptr)
+{
+	if (!xdrgen_decode_nfs_fh3(xdr, &ptr->object))
+		return false;
+	if (!xdrgen_decode_uint32(xdr, &ptr->access))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_ACCESS3resok(struct xdr_stream *xdr, struct ACCESS3resok *ptr)
+{
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->obj_attributes))
+		return false;
+	if (!xdrgen_decode_uint32(xdr, &ptr->access))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_ACCESS3resfail(struct xdr_stream *xdr, struct ACCESS3resfail *ptr)
+{
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->obj_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_ACCESS3res(struct xdr_stream *xdr, struct ACCESS3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_ACCESS3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_ACCESS3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_READLINK3args(struct xdr_stream *xdr, struct READLINK3args *ptr)
+{
+	if (!xdrgen_decode_nfs_fh3(xdr, &ptr->symlink))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_READLINK3resok(struct xdr_stream *xdr, struct READLINK3resok *ptr)
+{
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->symlink_attributes))
+		return false;
+	if (!xdrgen_decode_nfspath3(xdr, &ptr->data))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_READLINK3resfail(struct xdr_stream *xdr, struct READLINK3resfail *ptr)
+{
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->symlink_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_READLINK3res(struct xdr_stream *xdr, struct READLINK3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_READLINK3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_READLINK3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_READ3args(struct xdr_stream *xdr, struct READ3args *ptr)
+{
+	if (!xdrgen_decode_nfs_fh3(xdr, &ptr->file))
+		return false;
+	if (!xdrgen_decode_offset3(xdr, &ptr->offset))
+		return false;
+	if (!xdrgen_decode_count3(xdr, &ptr->count))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_READ3resok(struct xdr_stream *xdr, struct READ3resok *ptr)
+{
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->file_attributes))
+		return false;
+	if (!xdrgen_decode_count3(xdr, &ptr->count))
+		return false;
+	if (!xdrgen_decode_bool(xdr, &ptr->eof))
+		return false;
+	if (!xdrgen_decode_opaque(xdr, &ptr->data, 0))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_READ3resfail(struct xdr_stream *xdr, struct READ3resfail *ptr)
+{
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->file_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_READ3res(struct xdr_stream *xdr, struct READ3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_READ3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_READ3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_stable_how(struct xdr_stream *xdr, stable_how *ptr)
+{
+	u32 val;
+
+	if (xdr_stream_decode_u32(xdr, &val) < 0)
+		return false;
+	/* Compiler may optimize to a range check for dense enums */
+	switch (val) {
+	case UNSTABLE:
+	case DATA_SYNC:
+	case FILE_SYNC:
+		break;
+	default:
+		return false;
+	}
+	*ptr = val;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_WRITE3args(struct xdr_stream *xdr, struct WRITE3args *ptr)
+{
+	if (!xdrgen_decode_nfs_fh3(xdr, &ptr->file))
+		return false;
+	if (!xdrgen_decode_offset3(xdr, &ptr->offset))
+		return false;
+	if (!xdrgen_decode_count3(xdr, &ptr->count))
+		return false;
+	if (!xdrgen_decode_stable_how(xdr, &ptr->stable))
+		return false;
+	if (!xdrgen_decode_opaque(xdr, &ptr->data, 0))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_WRITE3resok(struct xdr_stream *xdr, struct WRITE3resok *ptr)
+{
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->file_wcc))
+		return false;
+	if (!xdrgen_decode_count3(xdr, &ptr->count))
+		return false;
+	if (!xdrgen_decode_stable_how(xdr, &ptr->committed))
+		return false;
+	if (!xdrgen_decode_writeverf3(xdr, &ptr->verf))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_WRITE3resfail(struct xdr_stream *xdr, struct WRITE3resfail *ptr)
+{
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->file_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_WRITE3res(struct xdr_stream *xdr, struct WRITE3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_WRITE3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_WRITE3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_createmode3(struct xdr_stream *xdr, createmode3 *ptr)
+{
+	u32 val;
+
+	if (xdr_stream_decode_u32(xdr, &val) < 0)
+		return false;
+	/* Compiler may optimize to a range check for dense enums */
+	switch (val) {
+	case UNCHECKED:
+	case GUARDED:
+	case EXCLUSIVE:
+		break;
+	default:
+		return false;
+	}
+	*ptr = val;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_createhow3(struct xdr_stream *xdr, struct createhow3 *ptr)
+{
+	if (!xdrgen_decode_createmode3(xdr, &ptr->mode))
+		return false;
+	switch (ptr->mode) {
+	case UNCHECKED:
+	case GUARDED:
+		if (!xdrgen_decode_sattr3(xdr, &ptr->u.obj_attributes))
+			return false;
+		break;
+	case EXCLUSIVE:
+		if (!xdrgen_decode_createverf3(xdr, &ptr->u.verf))
+			return false;
+		break;
+	default:
+		break;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_CREATE3args(struct xdr_stream *xdr, struct CREATE3args *ptr)
+{
+	if (!xdrgen_decode_diropargs3(xdr, &ptr->where))
+		return false;
+	if (!xdrgen_decode_createhow3(xdr, &ptr->how))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_CREATE3resok(struct xdr_stream *xdr, struct CREATE3resok *ptr)
+{
+	if (!xdrgen_decode_post_op_fh3(xdr, &ptr->obj))
+		return false;
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->obj_attributes))
+		return false;
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_CREATE3resfail(struct xdr_stream *xdr, struct CREATE3resfail *ptr)
+{
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_CREATE3res(struct xdr_stream *xdr, struct CREATE3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_CREATE3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_CREATE3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_MKDIR3args(struct xdr_stream *xdr, struct MKDIR3args *ptr)
+{
+	if (!xdrgen_decode_diropargs3(xdr, &ptr->where))
+		return false;
+	if (!xdrgen_decode_sattr3(xdr, &ptr->attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_MKDIR3resok(struct xdr_stream *xdr, struct MKDIR3resok *ptr)
+{
+	if (!xdrgen_decode_post_op_fh3(xdr, &ptr->obj))
+		return false;
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->obj_attributes))
+		return false;
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_MKDIR3resfail(struct xdr_stream *xdr, struct MKDIR3resfail *ptr)
+{
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_MKDIR3res(struct xdr_stream *xdr, struct MKDIR3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_MKDIR3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_MKDIR3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_symlinkdata3(struct xdr_stream *xdr, struct symlinkdata3 *ptr)
+{
+	if (!xdrgen_decode_sattr3(xdr, &ptr->symlink_attributes))
+		return false;
+	if (!xdrgen_decode_nfspath3(xdr, &ptr->symlink_data))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_SYMLINK3args(struct xdr_stream *xdr, struct SYMLINK3args *ptr)
+{
+	if (!xdrgen_decode_diropargs3(xdr, &ptr->where))
+		return false;
+	if (!xdrgen_decode_symlinkdata3(xdr, &ptr->symlink))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_SYMLINK3resok(struct xdr_stream *xdr, struct SYMLINK3resok *ptr)
+{
+	if (!xdrgen_decode_post_op_fh3(xdr, &ptr->obj))
+		return false;
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->obj_attributes))
+		return false;
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_SYMLINK3resfail(struct xdr_stream *xdr, struct SYMLINK3resfail *ptr)
+{
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_SYMLINK3res(struct xdr_stream *xdr, struct SYMLINK3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_SYMLINK3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_SYMLINK3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_devicedata3(struct xdr_stream *xdr, struct devicedata3 *ptr)
+{
+	if (!xdrgen_decode_sattr3(xdr, &ptr->dev_attributes))
+		return false;
+	if (!xdrgen_decode_specdata3(xdr, &ptr->spec))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_mknoddata3(struct xdr_stream *xdr, struct mknoddata3 *ptr)
+{
+	if (!xdrgen_decode_ftype3(xdr, &ptr->type))
+		return false;
+	switch (ptr->type) {
+	case NF3CHR:
+	case NF3BLK:
+		if (!xdrgen_decode_devicedata3(xdr, &ptr->u.device))
+			return false;
+		break;
+	case NF3SOCK:
+	case NF3FIFO:
+		if (!xdrgen_decode_sattr3(xdr, &ptr->u.pipe_attributes))
+			return false;
+		break;
+	default:
+		break;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_MKNOD3args(struct xdr_stream *xdr, struct MKNOD3args *ptr)
+{
+	if (!xdrgen_decode_diropargs3(xdr, &ptr->where))
+		return false;
+	if (!xdrgen_decode_mknoddata3(xdr, &ptr->what))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_MKNOD3resok(struct xdr_stream *xdr, struct MKNOD3resok *ptr)
+{
+	if (!xdrgen_decode_post_op_fh3(xdr, &ptr->obj))
+		return false;
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->obj_attributes))
+		return false;
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_MKNOD3resfail(struct xdr_stream *xdr, struct MKNOD3resfail *ptr)
+{
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_MKNOD3res(struct xdr_stream *xdr, struct MKNOD3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_MKNOD3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_MKNOD3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_REMOVE3args(struct xdr_stream *xdr, struct REMOVE3args *ptr)
+{
+	if (!xdrgen_decode_diropargs3(xdr, &ptr->object))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_REMOVE3resok(struct xdr_stream *xdr, struct REMOVE3resok *ptr)
+{
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_REMOVE3resfail(struct xdr_stream *xdr, struct REMOVE3resfail *ptr)
+{
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_REMOVE3res(struct xdr_stream *xdr, struct REMOVE3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_REMOVE3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_REMOVE3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_RMDIR3args(struct xdr_stream *xdr, struct RMDIR3args *ptr)
+{
+	if (!xdrgen_decode_diropargs3(xdr, &ptr->object))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_RMDIR3resok(struct xdr_stream *xdr, struct RMDIR3resok *ptr)
+{
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_RMDIR3resfail(struct xdr_stream *xdr, struct RMDIR3resfail *ptr)
+{
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_RMDIR3res(struct xdr_stream *xdr, struct RMDIR3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_RMDIR3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_RMDIR3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_RENAME3args(struct xdr_stream *xdr, struct RENAME3args *ptr)
+{
+	if (!xdrgen_decode_diropargs3(xdr, &ptr->from))
+		return false;
+	if (!xdrgen_decode_diropargs3(xdr, &ptr->to))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_RENAME3resok(struct xdr_stream *xdr, struct RENAME3resok *ptr)
+{
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->fromdir_wcc))
+		return false;
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->todir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_RENAME3resfail(struct xdr_stream *xdr, struct RENAME3resfail *ptr)
+{
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->fromdir_wcc))
+		return false;
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->todir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_RENAME3res(struct xdr_stream *xdr, struct RENAME3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_RENAME3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_RENAME3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_LINK3args(struct xdr_stream *xdr, struct LINK3args *ptr)
+{
+	if (!xdrgen_decode_nfs_fh3(xdr, &ptr->file))
+		return false;
+	if (!xdrgen_decode_diropargs3(xdr, &ptr->link))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_LINK3resok(struct xdr_stream *xdr, struct LINK3resok *ptr)
+{
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->file_attributes))
+		return false;
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->linkdir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_LINK3resfail(struct xdr_stream *xdr, struct LINK3resfail *ptr)
+{
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->file_attributes))
+		return false;
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->linkdir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_LINK3res(struct xdr_stream *xdr, struct LINK3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_LINK3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_LINK3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_READDIR3args(struct xdr_stream *xdr, struct READDIR3args *ptr)
+{
+	if (!xdrgen_decode_nfs_fh3(xdr, &ptr->dir))
+		return false;
+	if (!xdrgen_decode_cookie3(xdr, &ptr->cookie))
+		return false;
+	if (!xdrgen_decode_cookieverf3(xdr, &ptr->cookieverf))
+		return false;
+	if (!xdrgen_decode_count3(xdr, &ptr->count))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_entry3(struct xdr_stream *xdr, struct entry3 *ptr)
+{
+	bool opted;
+
+	if (!xdrgen_decode_bool(xdr, &opted))
+		return false;
+	if (!opted)
+		return true;
+
+	if (!xdrgen_decode_fileid3(xdr, &ptr->fileid))
+		return false;
+	if (!xdrgen_decode_filename3(xdr, &ptr->name))
+		return false;
+	if (!xdrgen_decode_cookie3(xdr, &ptr->cookie))
+		return false;
+	return true;
+}
+
+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;
+}
+
+static bool __maybe_unused
+xdrgen_decode_READDIR3resok(struct xdr_stream *xdr, struct READDIR3resok *ptr)
+{
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->dir_attributes))
+		return false;
+	if (!xdrgen_decode_cookieverf3(xdr, &ptr->cookieverf))
+		return false;
+	if (!xdrgen_decode_dirlist3(xdr, &ptr->reply))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_READDIR3resfail(struct xdr_stream *xdr, struct READDIR3resfail *ptr)
+{
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->dir_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_READDIR3res(struct xdr_stream *xdr, struct READDIR3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_READDIR3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_READDIR3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_READDIRPLUS3args(struct xdr_stream *xdr, struct READDIRPLUS3args *ptr)
+{
+	if (!xdrgen_decode_nfs_fh3(xdr, &ptr->dir))
+		return false;
+	if (!xdrgen_decode_cookie3(xdr, &ptr->cookie))
+		return false;
+	if (!xdrgen_decode_cookieverf3(xdr, &ptr->cookieverf))
+		return false;
+	if (!xdrgen_decode_count3(xdr, &ptr->dircount))
+		return false;
+	if (!xdrgen_decode_count3(xdr, &ptr->maxcount))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_entryplus3(struct xdr_stream *xdr, struct entryplus3 *ptr)
+{
+	bool opted;
+
+	if (!xdrgen_decode_bool(xdr, &opted))
+		return false;
+	if (!opted)
+		return true;
+
+	if (!xdrgen_decode_fileid3(xdr, &ptr->fileid))
+		return false;
+	if (!xdrgen_decode_filename3(xdr, &ptr->name))
+		return false;
+	if (!xdrgen_decode_cookie3(xdr, &ptr->cookie))
+		return false;
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->name_attributes))
+		return false;
+	if (!xdrgen_decode_post_op_fh3(xdr, &ptr->name_handle))
+		return false;
+	return true;
+}
+
+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;
+}
+
+static bool __maybe_unused
+xdrgen_decode_READDIRPLUS3resok(struct xdr_stream *xdr, struct READDIRPLUS3resok *ptr)
+{
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->dir_attributes))
+		return false;
+	if (!xdrgen_decode_cookieverf3(xdr, &ptr->cookieverf))
+		return false;
+	if (!xdrgen_decode_dirlistplus3(xdr, &ptr->reply))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_READDIRPLUS3resfail(struct xdr_stream *xdr, struct READDIRPLUS3resfail *ptr)
+{
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->dir_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_READDIRPLUS3res(struct xdr_stream *xdr, struct READDIRPLUS3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_READDIRPLUS3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_READDIRPLUS3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_FSSTAT3args(struct xdr_stream *xdr, struct FSSTAT3args *ptr)
+{
+	if (!xdrgen_decode_nfs_fh3(xdr, &ptr->fsroot))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_FSSTAT3resok(struct xdr_stream *xdr, struct FSSTAT3resok *ptr)
+{
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->obj_attributes))
+		return false;
+	if (!xdrgen_decode_size3(xdr, &ptr->tbytes))
+		return false;
+	if (!xdrgen_decode_size3(xdr, &ptr->fbytes))
+		return false;
+	if (!xdrgen_decode_size3(xdr, &ptr->abytes))
+		return false;
+	if (!xdrgen_decode_size3(xdr, &ptr->tfiles))
+		return false;
+	if (!xdrgen_decode_size3(xdr, &ptr->ffiles))
+		return false;
+	if (!xdrgen_decode_size3(xdr, &ptr->afiles))
+		return false;
+	if (!xdrgen_decode_uint32(xdr, &ptr->invarsec))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_FSSTAT3resfail(struct xdr_stream *xdr, struct FSSTAT3resfail *ptr)
+{
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->obj_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_FSSTAT3res(struct xdr_stream *xdr, struct FSSTAT3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_FSSTAT3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_FSSTAT3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_FSINFO3args(struct xdr_stream *xdr, struct FSINFO3args *ptr)
+{
+	if (!xdrgen_decode_nfs_fh3(xdr, &ptr->fsroot))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_FSINFO3resok(struct xdr_stream *xdr, struct FSINFO3resok *ptr)
+{
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->obj_attributes))
+		return false;
+	if (!xdrgen_decode_uint32(xdr, &ptr->rtmax))
+		return false;
+	if (!xdrgen_decode_uint32(xdr, &ptr->rtpref))
+		return false;
+	if (!xdrgen_decode_uint32(xdr, &ptr->rtmult))
+		return false;
+	if (!xdrgen_decode_uint32(xdr, &ptr->wtmax))
+		return false;
+	if (!xdrgen_decode_uint32(xdr, &ptr->wtpref))
+		return false;
+	if (!xdrgen_decode_uint32(xdr, &ptr->wtmult))
+		return false;
+	if (!xdrgen_decode_uint32(xdr, &ptr->dtpref))
+		return false;
+	if (!xdrgen_decode_size3(xdr, &ptr->maxfilesize))
+		return false;
+	if (!xdrgen_decode_nfstime3(xdr, &ptr->time_delta))
+		return false;
+	if (!xdrgen_decode_uint32(xdr, &ptr->properties))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_FSINFO3resfail(struct xdr_stream *xdr, struct FSINFO3resfail *ptr)
+{
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->obj_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_FSINFO3res(struct xdr_stream *xdr, struct FSINFO3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_FSINFO3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_FSINFO3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_PATHCONF3args(struct xdr_stream *xdr, struct PATHCONF3args *ptr)
+{
+	if (!xdrgen_decode_nfs_fh3(xdr, &ptr->object))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_PATHCONF3resok(struct xdr_stream *xdr, struct PATHCONF3resok *ptr)
+{
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->obj_attributes))
+		return false;
+	if (!xdrgen_decode_uint32(xdr, &ptr->linkmax))
+		return false;
+	if (!xdrgen_decode_uint32(xdr, &ptr->name_max))
+		return false;
+	if (!xdrgen_decode_bool(xdr, &ptr->no_trunc))
+		return false;
+	if (!xdrgen_decode_bool(xdr, &ptr->chown_restricted))
+		return false;
+	if (!xdrgen_decode_bool(xdr, &ptr->case_insensitive))
+		return false;
+	if (!xdrgen_decode_bool(xdr, &ptr->case_preserving))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_PATHCONF3resfail(struct xdr_stream *xdr, struct PATHCONF3resfail *ptr)
+{
+	if (!xdrgen_decode_post_op_attr(xdr, &ptr->obj_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_PATHCONF3res(struct xdr_stream *xdr, struct PATHCONF3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_PATHCONF3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_PATHCONF3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_COMMIT3args(struct xdr_stream *xdr, struct COMMIT3args *ptr)
+{
+	if (!xdrgen_decode_nfs_fh3(xdr, &ptr->file))
+		return false;
+	if (!xdrgen_decode_offset3(xdr, &ptr->offset))
+		return false;
+	if (!xdrgen_decode_count3(xdr, &ptr->count))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_COMMIT3resok(struct xdr_stream *xdr, struct COMMIT3resok *ptr)
+{
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->file_wcc))
+		return false;
+	if (!xdrgen_decode_writeverf3(xdr, &ptr->verf))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_COMMIT3resfail(struct xdr_stream *xdr, struct COMMIT3resfail *ptr)
+{
+	if (!xdrgen_decode_wcc_data(xdr, &ptr->file_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_COMMIT3res(struct xdr_stream *xdr, struct COMMIT3res *ptr)
+{
+	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_decode_COMMIT3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_decode_COMMIT3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+/**
+ * nfs_svc_decode_GETATTR3args - Decode a GETATTR3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_GETATTR3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct GETATTR3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_GETATTR3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_SETATTR3args - Decode a SETATTR3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_SETATTR3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct SETATTR3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_SETATTR3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_LOOKUP3args - Decode a LOOKUP3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_LOOKUP3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct LOOKUP3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_LOOKUP3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_ACCESS3args - Decode a ACCESS3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_ACCESS3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct ACCESS3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_ACCESS3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_READLINK3args - Decode a READLINK3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_READLINK3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct READLINK3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_READLINK3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_READ3args - Decode a READ3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_READ3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct READ3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_READ3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_WRITE3args - Decode a WRITE3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_WRITE3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct WRITE3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_WRITE3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_CREATE3args - Decode a CREATE3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_CREATE3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct CREATE3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_CREATE3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_MKDIR3args - Decode a MKDIR3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_MKDIR3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct MKDIR3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_MKDIR3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_SYMLINK3args - Decode a SYMLINK3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_SYMLINK3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct SYMLINK3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_SYMLINK3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_MKNOD3args - Decode a MKNOD3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_MKNOD3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct MKNOD3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_MKNOD3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_REMOVE3args - Decode a REMOVE3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_REMOVE3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct REMOVE3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_REMOVE3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_RMDIR3args - Decode a RMDIR3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_RMDIR3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct RMDIR3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_RMDIR3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_RENAME3args - Decode a RENAME3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_RENAME3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct RENAME3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_RENAME3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_LINK3args - Decode a LINK3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_LINK3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct LINK3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_LINK3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_READDIR3args - Decode a READDIR3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_READDIR3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct READDIR3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_READDIR3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_READDIRPLUS3args - Decode a READDIRPLUS3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_READDIRPLUS3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct READDIRPLUS3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_READDIRPLUS3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_FSSTAT3args - Decode a FSSTAT3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_FSSTAT3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct FSSTAT3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_FSSTAT3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_FSINFO3args - Decode a FSINFO3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_FSINFO3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct FSINFO3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_FSINFO3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_PATHCONF3args - Decode a PATHCONF3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_PATHCONF3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct PATHCONF3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_PATHCONF3args(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_COMMIT3args - Decode a COMMIT3args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ *   %true: procedure arguments decoded successfully
+ *   %false: decode failed
+ */
+bool nfs_svc_decode_COMMIT3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct COMMIT3args *argp = rqstp->rq_argp;
+
+	return xdrgen_decode_COMMIT3args(xdr, argp);
+}
+
+static bool __maybe_unused
+xdrgen_encode_uint64(struct xdr_stream *xdr, const uint64 value)
+{
+	return xdrgen_encode_unsigned_hyper(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_int64(struct xdr_stream *xdr, const int64 value)
+{
+	return xdrgen_encode_hyper(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_uint32(struct xdr_stream *xdr, const uint32 value)
+{
+	return xdrgen_encode_unsigned_long(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_int32(struct xdr_stream *xdr, const int32 value)
+{
+	return xdrgen_encode_long(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_filename3(struct xdr_stream *xdr, const filename3 value)
+{
+	return xdr_stream_encode_opaque(xdr, value.data, value.len) >= 0;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nfspath3(struct xdr_stream *xdr, const nfspath3 value)
+{
+	return xdr_stream_encode_opaque(xdr, value.data, value.len) >= 0;
+}
+
+static bool __maybe_unused
+xdrgen_encode_fileid3(struct xdr_stream *xdr, const fileid3 value)
+{
+	return xdrgen_encode_uint64(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_cookie3(struct xdr_stream *xdr, const cookie3 value)
+{
+	return xdrgen_encode_uint64(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_cookieverf3(struct xdr_stream *xdr, const cookieverf3 value)
+{
+	return xdr_stream_encode_opaque_fixed(xdr, value, NFS3_COOKIEVERFSIZE) >= 0;
+}
+
+static bool __maybe_unused
+xdrgen_encode_createverf3(struct xdr_stream *xdr, const createverf3 value)
+{
+	return xdr_stream_encode_opaque_fixed(xdr, value, NFS3_CREATEVERFSIZE) >= 0;
+}
+
+static bool __maybe_unused
+xdrgen_encode_writeverf3(struct xdr_stream *xdr, const writeverf3 value)
+{
+	return xdr_stream_encode_opaque_fixed(xdr, value, NFS3_WRITEVERFSIZE) >= 0;
+}
+
+static bool __maybe_unused
+xdrgen_encode_uid3(struct xdr_stream *xdr, const uid3 value)
+{
+	return xdrgen_encode_uint32(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_gid3(struct xdr_stream *xdr, const gid3 value)
+{
+	return xdrgen_encode_uint32(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_size3(struct xdr_stream *xdr, const size3 value)
+{
+	return xdrgen_encode_uint64(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_offset3(struct xdr_stream *xdr, const offset3 value)
+{
+	return xdrgen_encode_uint64(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_mode3(struct xdr_stream *xdr, const mode3 value)
+{
+	return xdrgen_encode_uint32(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_count3(struct xdr_stream *xdr, const count3 value)
+{
+	return xdrgen_encode_uint32(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_nfsstat3(struct xdr_stream *xdr, nfsstat3 value)
+{
+	return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
+}
+
+static bool __maybe_unused
+xdrgen_encode_ftype3(struct xdr_stream *xdr, ftype3 value)
+{
+	return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
+}
+
+static bool __maybe_unused
+xdrgen_encode_specdata3(struct xdr_stream *xdr, const struct specdata3 *value)
+{
+	if (!xdrgen_encode_uint32(xdr, value->specdata1))
+		return false;
+	if (!xdrgen_encode_uint32(xdr, value->specdata2))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nfs_fh3(struct xdr_stream *xdr, const struct nfs_fh3 *value)
+{
+	if (value->data.len > NFS3_FHSIZE)
+		return false;
+	if (xdr_stream_encode_opaque(xdr, value->data.data, value->data.len) < 0)
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nfstime3(struct xdr_stream *xdr, const struct nfstime3 *value)
+{
+	if (!xdrgen_encode_uint32(xdr, value->seconds))
+		return false;
+	if (!xdrgen_encode_uint32(xdr, value->nseconds))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_fattr3(struct xdr_stream *xdr, const struct fattr3 *value)
+{
+	if (!xdrgen_encode_ftype3(xdr, value->type))
+		return false;
+	if (!xdrgen_encode_mode3(xdr, value->mode))
+		return false;
+	if (!xdrgen_encode_uint32(xdr, value->nlink))
+		return false;
+	if (!xdrgen_encode_uid3(xdr, value->uid))
+		return false;
+	if (!xdrgen_encode_gid3(xdr, value->gid))
+		return false;
+	if (!xdrgen_encode_size3(xdr, value->size))
+		return false;
+	if (!xdrgen_encode_size3(xdr, value->used))
+		return false;
+	if (!xdrgen_encode_specdata3(xdr, &value->rdev))
+		return false;
+	if (!xdrgen_encode_uint64(xdr, value->fsid))
+		return false;
+	if (!xdrgen_encode_fileid3(xdr, value->fileid))
+		return false;
+	if (!xdrgen_encode_nfstime3(xdr, &value->atime))
+		return false;
+	if (!xdrgen_encode_nfstime3(xdr, &value->mtime))
+		return false;
+	if (!xdrgen_encode_nfstime3(xdr, &value->ctime))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_post_op_attr(struct xdr_stream *xdr, const struct post_op_attr *ptr)
+{
+	if (!xdrgen_encode_bool(xdr, ptr->attributes_follow))
+		return false;
+	if (ptr->attributes_follow) {
+		if (!xdrgen_encode_fattr3(xdr, &ptr->u.attributes))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_wcc_attr(struct xdr_stream *xdr, const struct wcc_attr *value)
+{
+	if (!xdrgen_encode_size3(xdr, value->size))
+		return false;
+	if (!xdrgen_encode_nfstime3(xdr, &value->mtime))
+		return false;
+	if (!xdrgen_encode_nfstime3(xdr, &value->ctime))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_pre_op_attr(struct xdr_stream *xdr, const struct pre_op_attr *ptr)
+{
+	if (!xdrgen_encode_bool(xdr, ptr->attributes_follow))
+		return false;
+	if (ptr->attributes_follow) {
+		if (!xdrgen_encode_wcc_attr(xdr, &ptr->u.attributes))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_wcc_data(struct xdr_stream *xdr, const struct wcc_data *value)
+{
+	if (!xdrgen_encode_pre_op_attr(xdr, &value->before))
+		return false;
+	if (!xdrgen_encode_post_op_attr(xdr, &value->after))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_post_op_fh3(struct xdr_stream *xdr, const struct post_op_fh3 *ptr)
+{
+	if (!xdrgen_encode_bool(xdr, ptr->handle_follows))
+		return false;
+	if (ptr->handle_follows) {
+		if (!xdrgen_encode_nfs_fh3(xdr, &ptr->u.handle))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_time_how(struct xdr_stream *xdr, time_how value)
+{
+	return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
+}
+
+static bool __maybe_unused
+xdrgen_encode_set_mode3(struct xdr_stream *xdr, const struct set_mode3 *ptr)
+{
+	if (!xdrgen_encode_bool(xdr, ptr->set_it))
+		return false;
+	if (ptr->set_it) {
+		if (!xdrgen_encode_mode3(xdr, ptr->u.mode))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_set_uid3(struct xdr_stream *xdr, const struct set_uid3 *ptr)
+{
+	if (!xdrgen_encode_bool(xdr, ptr->set_it))
+		return false;
+	if (ptr->set_it) {
+		if (!xdrgen_encode_uid3(xdr, ptr->u.uid))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_set_gid3(struct xdr_stream *xdr, const struct set_gid3 *ptr)
+{
+	if (!xdrgen_encode_bool(xdr, ptr->set_it))
+		return false;
+	if (ptr->set_it) {
+		if (!xdrgen_encode_gid3(xdr, ptr->u.gid))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_set_size3(struct xdr_stream *xdr, const struct set_size3 *ptr)
+{
+	if (!xdrgen_encode_bool(xdr, ptr->set_it))
+		return false;
+	if (ptr->set_it) {
+		if (!xdrgen_encode_size3(xdr, ptr->u.size))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_set_atime(struct xdr_stream *xdr, const struct set_atime *ptr)
+{
+	if (!xdrgen_encode_time_how(xdr, ptr->set_it))
+		return false;
+	switch (ptr->set_it) {
+	case SET_TO_CLIENT_TIME:
+		if (!xdrgen_encode_nfstime3(xdr, &ptr->u.atime))
+			return false;
+		break;
+	default:
+		break;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_set_mtime(struct xdr_stream *xdr, const struct set_mtime *ptr)
+{
+	if (!xdrgen_encode_time_how(xdr, ptr->set_it))
+		return false;
+	switch (ptr->set_it) {
+	case SET_TO_CLIENT_TIME:
+		if (!xdrgen_encode_nfstime3(xdr, &ptr->u.mtime))
+			return false;
+		break;
+	default:
+		break;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_sattr3(struct xdr_stream *xdr, const struct sattr3 *value)
+{
+	if (!xdrgen_encode_set_mode3(xdr, &value->mode))
+		return false;
+	if (!xdrgen_encode_set_uid3(xdr, &value->uid))
+		return false;
+	if (!xdrgen_encode_set_gid3(xdr, &value->gid))
+		return false;
+	if (!xdrgen_encode_set_size3(xdr, &value->size))
+		return false;
+	if (!xdrgen_encode_set_atime(xdr, &value->atime))
+		return false;
+	if (!xdrgen_encode_set_mtime(xdr, &value->mtime))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_diropargs3(struct xdr_stream *xdr, const struct diropargs3 *value)
+{
+	if (!xdrgen_encode_nfs_fh3(xdr, &value->dir))
+		return false;
+	if (!xdrgen_encode_filename3(xdr, value->name))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_GETATTR3args(struct xdr_stream *xdr, const struct GETATTR3args *value)
+{
+	if (!xdrgen_encode_nfs_fh3(xdr, &value->object))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_GETATTR3resok(struct xdr_stream *xdr, const struct GETATTR3resok *value)
+{
+	if (!xdrgen_encode_fattr3(xdr, &value->obj_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_GETATTR3res(struct xdr_stream *xdr, const struct GETATTR3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_GETATTR3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		break;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_sattrguard3(struct xdr_stream *xdr, const struct sattrguard3 *ptr)
+{
+	if (!xdrgen_encode_bool(xdr, ptr->check))
+		return false;
+	if (ptr->check) {
+		if (!xdrgen_encode_nfstime3(xdr, &ptr->u.obj_ctime))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_SETATTR3args(struct xdr_stream *xdr, const struct SETATTR3args *value)
+{
+	if (!xdrgen_encode_nfs_fh3(xdr, &value->object))
+		return false;
+	if (!xdrgen_encode_sattr3(xdr, &value->new_attributes))
+		return false;
+	if (!xdrgen_encode_sattrguard3(xdr, &value->guard))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_SETATTR3resok(struct xdr_stream *xdr, const struct SETATTR3resok *value)
+{
+	if (!xdrgen_encode_wcc_data(xdr, &value->obj_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_SETATTR3resfail(struct xdr_stream *xdr, const struct SETATTR3resfail *value)
+{
+	if (!xdrgen_encode_wcc_data(xdr, &value->obj_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_SETATTR3res(struct xdr_stream *xdr, const struct SETATTR3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_SETATTR3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_SETATTR3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_LOOKUP3args(struct xdr_stream *xdr, const struct LOOKUP3args *value)
+{
+	if (!xdrgen_encode_diropargs3(xdr, &value->what))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_LOOKUP3resok(struct xdr_stream *xdr, const struct LOOKUP3resok *value)
+{
+	if (!xdrgen_encode_nfs_fh3(xdr, &value->object))
+		return false;
+	if (!xdrgen_encode_post_op_attr(xdr, &value->obj_attributes))
+		return false;
+	if (!xdrgen_encode_post_op_attr(xdr, &value->dir_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_LOOKUP3resfail(struct xdr_stream *xdr, const struct LOOKUP3resfail *value)
+{
+	if (!xdrgen_encode_post_op_attr(xdr, &value->dir_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_LOOKUP3res(struct xdr_stream *xdr, const struct LOOKUP3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_LOOKUP3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_LOOKUP3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_ACCESS3args(struct xdr_stream *xdr, const struct ACCESS3args *value)
+{
+	if (!xdrgen_encode_nfs_fh3(xdr, &value->object))
+		return false;
+	if (!xdrgen_encode_uint32(xdr, value->access))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_ACCESS3resok(struct xdr_stream *xdr, const struct ACCESS3resok *value)
+{
+	if (!xdrgen_encode_post_op_attr(xdr, &value->obj_attributes))
+		return false;
+	if (!xdrgen_encode_uint32(xdr, value->access))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_ACCESS3resfail(struct xdr_stream *xdr, const struct ACCESS3resfail *value)
+{
+	if (!xdrgen_encode_post_op_attr(xdr, &value->obj_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_ACCESS3res(struct xdr_stream *xdr, const struct ACCESS3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_ACCESS3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_ACCESS3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_READLINK3args(struct xdr_stream *xdr, const struct READLINK3args *value)
+{
+	if (!xdrgen_encode_nfs_fh3(xdr, &value->symlink))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_READLINK3resok(struct xdr_stream *xdr, const struct READLINK3resok *value)
+{
+	if (!xdrgen_encode_post_op_attr(xdr, &value->symlink_attributes))
+		return false;
+	if (!xdrgen_encode_nfspath3(xdr, value->data))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_READLINK3resfail(struct xdr_stream *xdr, const struct READLINK3resfail *value)
+{
+	if (!xdrgen_encode_post_op_attr(xdr, &value->symlink_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_READLINK3res(struct xdr_stream *xdr, const struct READLINK3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_READLINK3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_READLINK3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_READ3args(struct xdr_stream *xdr, const struct READ3args *value)
+{
+	if (!xdrgen_encode_nfs_fh3(xdr, &value->file))
+		return false;
+	if (!xdrgen_encode_offset3(xdr, value->offset))
+		return false;
+	if (!xdrgen_encode_count3(xdr, value->count))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_READ3resok(struct xdr_stream *xdr, const struct READ3resok *value)
+{
+	if (!xdrgen_encode_post_op_attr(xdr, &value->file_attributes))
+		return false;
+	if (!xdrgen_encode_count3(xdr, value->count))
+		return false;
+	if (!xdrgen_encode_bool(xdr, value->eof))
+		return false;
+	if (xdr_stream_encode_opaque(xdr, value->data.data, value->data.len) < 0)
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_READ3resfail(struct xdr_stream *xdr, const struct READ3resfail *value)
+{
+	if (!xdrgen_encode_post_op_attr(xdr, &value->file_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_READ3res(struct xdr_stream *xdr, const struct READ3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_READ3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_READ3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_stable_how(struct xdr_stream *xdr, stable_how value)
+{
+	return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
+}
+
+static bool __maybe_unused
+xdrgen_encode_WRITE3args(struct xdr_stream *xdr, const struct WRITE3args *value)
+{
+	if (!xdrgen_encode_nfs_fh3(xdr, &value->file))
+		return false;
+	if (!xdrgen_encode_offset3(xdr, value->offset))
+		return false;
+	if (!xdrgen_encode_count3(xdr, value->count))
+		return false;
+	if (!xdrgen_encode_stable_how(xdr, value->stable))
+		return false;
+	if (xdr_stream_encode_opaque(xdr, value->data.data, value->data.len) < 0)
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_WRITE3resok(struct xdr_stream *xdr, const struct WRITE3resok *value)
+{
+	if (!xdrgen_encode_wcc_data(xdr, &value->file_wcc))
+		return false;
+	if (!xdrgen_encode_count3(xdr, value->count))
+		return false;
+	if (!xdrgen_encode_stable_how(xdr, value->committed))
+		return false;
+	if (!xdrgen_encode_writeverf3(xdr, value->verf))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_WRITE3resfail(struct xdr_stream *xdr, const struct WRITE3resfail *value)
+{
+	if (!xdrgen_encode_wcc_data(xdr, &value->file_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_WRITE3res(struct xdr_stream *xdr, const struct WRITE3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_WRITE3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_WRITE3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_createmode3(struct xdr_stream *xdr, createmode3 value)
+{
+	return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
+}
+
+static bool __maybe_unused
+xdrgen_encode_createhow3(struct xdr_stream *xdr, const struct createhow3 *ptr)
+{
+	if (!xdrgen_encode_createmode3(xdr, ptr->mode))
+		return false;
+	switch (ptr->mode) {
+	case UNCHECKED:
+	case GUARDED:
+		if (!xdrgen_encode_sattr3(xdr, &ptr->u.obj_attributes))
+			return false;
+		break;
+	case EXCLUSIVE:
+		if (!xdrgen_encode_createverf3(xdr, ptr->u.verf))
+			return false;
+		break;
+	default:
+		break;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_CREATE3args(struct xdr_stream *xdr, const struct CREATE3args *value)
+{
+	if (!xdrgen_encode_diropargs3(xdr, &value->where))
+		return false;
+	if (!xdrgen_encode_createhow3(xdr, &value->how))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_CREATE3resok(struct xdr_stream *xdr, const struct CREATE3resok *value)
+{
+	if (!xdrgen_encode_post_op_fh3(xdr, &value->obj))
+		return false;
+	if (!xdrgen_encode_post_op_attr(xdr, &value->obj_attributes))
+		return false;
+	if (!xdrgen_encode_wcc_data(xdr, &value->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_CREATE3resfail(struct xdr_stream *xdr, const struct CREATE3resfail *value)
+{
+	if (!xdrgen_encode_wcc_data(xdr, &value->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_CREATE3res(struct xdr_stream *xdr, const struct CREATE3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_CREATE3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_CREATE3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_MKDIR3args(struct xdr_stream *xdr, const struct MKDIR3args *value)
+{
+	if (!xdrgen_encode_diropargs3(xdr, &value->where))
+		return false;
+	if (!xdrgen_encode_sattr3(xdr, &value->attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_MKDIR3resok(struct xdr_stream *xdr, const struct MKDIR3resok *value)
+{
+	if (!xdrgen_encode_post_op_fh3(xdr, &value->obj))
+		return false;
+	if (!xdrgen_encode_post_op_attr(xdr, &value->obj_attributes))
+		return false;
+	if (!xdrgen_encode_wcc_data(xdr, &value->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_MKDIR3resfail(struct xdr_stream *xdr, const struct MKDIR3resfail *value)
+{
+	if (!xdrgen_encode_wcc_data(xdr, &value->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_MKDIR3res(struct xdr_stream *xdr, const struct MKDIR3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_MKDIR3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_MKDIR3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_symlinkdata3(struct xdr_stream *xdr, const struct symlinkdata3 *value)
+{
+	if (!xdrgen_encode_sattr3(xdr, &value->symlink_attributes))
+		return false;
+	if (!xdrgen_encode_nfspath3(xdr, value->symlink_data))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_SYMLINK3args(struct xdr_stream *xdr, const struct SYMLINK3args *value)
+{
+	if (!xdrgen_encode_diropargs3(xdr, &value->where))
+		return false;
+	if (!xdrgen_encode_symlinkdata3(xdr, &value->symlink))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_SYMLINK3resok(struct xdr_stream *xdr, const struct SYMLINK3resok *value)
+{
+	if (!xdrgen_encode_post_op_fh3(xdr, &value->obj))
+		return false;
+	if (!xdrgen_encode_post_op_attr(xdr, &value->obj_attributes))
+		return false;
+	if (!xdrgen_encode_wcc_data(xdr, &value->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_SYMLINK3resfail(struct xdr_stream *xdr, const struct SYMLINK3resfail *value)
+{
+	if (!xdrgen_encode_wcc_data(xdr, &value->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_SYMLINK3res(struct xdr_stream *xdr, const struct SYMLINK3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_SYMLINK3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_SYMLINK3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_devicedata3(struct xdr_stream *xdr, const struct devicedata3 *value)
+{
+	if (!xdrgen_encode_sattr3(xdr, &value->dev_attributes))
+		return false;
+	if (!xdrgen_encode_specdata3(xdr, &value->spec))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_mknoddata3(struct xdr_stream *xdr, const struct mknoddata3 *ptr)
+{
+	if (!xdrgen_encode_ftype3(xdr, ptr->type))
+		return false;
+	switch (ptr->type) {
+	case NF3CHR:
+	case NF3BLK:
+		if (!xdrgen_encode_devicedata3(xdr, &ptr->u.device))
+			return false;
+		break;
+	case NF3SOCK:
+	case NF3FIFO:
+		if (!xdrgen_encode_sattr3(xdr, &ptr->u.pipe_attributes))
+			return false;
+		break;
+	default:
+		break;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_MKNOD3args(struct xdr_stream *xdr, const struct MKNOD3args *value)
+{
+	if (!xdrgen_encode_diropargs3(xdr, &value->where))
+		return false;
+	if (!xdrgen_encode_mknoddata3(xdr, &value->what))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_MKNOD3resok(struct xdr_stream *xdr, const struct MKNOD3resok *value)
+{
+	if (!xdrgen_encode_post_op_fh3(xdr, &value->obj))
+		return false;
+	if (!xdrgen_encode_post_op_attr(xdr, &value->obj_attributes))
+		return false;
+	if (!xdrgen_encode_wcc_data(xdr, &value->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_MKNOD3resfail(struct xdr_stream *xdr, const struct MKNOD3resfail *value)
+{
+	if (!xdrgen_encode_wcc_data(xdr, &value->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_MKNOD3res(struct xdr_stream *xdr, const struct MKNOD3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_MKNOD3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_MKNOD3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_REMOVE3args(struct xdr_stream *xdr, const struct REMOVE3args *value)
+{
+	if (!xdrgen_encode_diropargs3(xdr, &value->object))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_REMOVE3resok(struct xdr_stream *xdr, const struct REMOVE3resok *value)
+{
+	if (!xdrgen_encode_wcc_data(xdr, &value->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_REMOVE3resfail(struct xdr_stream *xdr, const struct REMOVE3resfail *value)
+{
+	if (!xdrgen_encode_wcc_data(xdr, &value->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_REMOVE3res(struct xdr_stream *xdr, const struct REMOVE3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_REMOVE3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_REMOVE3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_RMDIR3args(struct xdr_stream *xdr, const struct RMDIR3args *value)
+{
+	if (!xdrgen_encode_diropargs3(xdr, &value->object))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_RMDIR3resok(struct xdr_stream *xdr, const struct RMDIR3resok *value)
+{
+	if (!xdrgen_encode_wcc_data(xdr, &value->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_RMDIR3resfail(struct xdr_stream *xdr, const struct RMDIR3resfail *value)
+{
+	if (!xdrgen_encode_wcc_data(xdr, &value->dir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_RMDIR3res(struct xdr_stream *xdr, const struct RMDIR3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_RMDIR3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_RMDIR3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_RENAME3args(struct xdr_stream *xdr, const struct RENAME3args *value)
+{
+	if (!xdrgen_encode_diropargs3(xdr, &value->from))
+		return false;
+	if (!xdrgen_encode_diropargs3(xdr, &value->to))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_RENAME3resok(struct xdr_stream *xdr, const struct RENAME3resok *value)
+{
+	if (!xdrgen_encode_wcc_data(xdr, &value->fromdir_wcc))
+		return false;
+	if (!xdrgen_encode_wcc_data(xdr, &value->todir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_RENAME3resfail(struct xdr_stream *xdr, const struct RENAME3resfail *value)
+{
+	if (!xdrgen_encode_wcc_data(xdr, &value->fromdir_wcc))
+		return false;
+	if (!xdrgen_encode_wcc_data(xdr, &value->todir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_RENAME3res(struct xdr_stream *xdr, const struct RENAME3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_RENAME3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_RENAME3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_LINK3args(struct xdr_stream *xdr, const struct LINK3args *value)
+{
+	if (!xdrgen_encode_nfs_fh3(xdr, &value->file))
+		return false;
+	if (!xdrgen_encode_diropargs3(xdr, &value->link))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_LINK3resok(struct xdr_stream *xdr, const struct LINK3resok *value)
+{
+	if (!xdrgen_encode_post_op_attr(xdr, &value->file_attributes))
+		return false;
+	if (!xdrgen_encode_wcc_data(xdr, &value->linkdir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_LINK3resfail(struct xdr_stream *xdr, const struct LINK3resfail *value)
+{
+	if (!xdrgen_encode_post_op_attr(xdr, &value->file_attributes))
+		return false;
+	if (!xdrgen_encode_wcc_data(xdr, &value->linkdir_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_LINK3res(struct xdr_stream *xdr, const struct LINK3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_LINK3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_LINK3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_READDIR3args(struct xdr_stream *xdr, const struct READDIR3args *value)
+{
+	if (!xdrgen_encode_nfs_fh3(xdr, &value->dir))
+		return false;
+	if (!xdrgen_encode_cookie3(xdr, value->cookie))
+		return false;
+	if (!xdrgen_encode_cookieverf3(xdr, value->cookieverf))
+		return false;
+	if (!xdrgen_encode_count3(xdr, value->count))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_entry3(struct xdr_stream *xdr, const struct entry3 *value)
+{
+	if (!xdrgen_encode_bool(xdr, value != NULL))
+		return false;
+	if (!value)
+		return true;
+
+	if (!xdrgen_encode_fileid3(xdr, value->fileid))
+		return false;
+	if (!xdrgen_encode_filename3(xdr, value->name))
+		return false;
+	if (!xdrgen_encode_cookie3(xdr, value->cookie))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_dirlist3(struct xdr_stream *xdr, const struct dirlist3 *value)
+{
+	if (!xdrgen_encode_entry3(xdr, value->entries))
+		return false;
+	if (!xdrgen_encode_bool(xdr, value->eof))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_READDIR3resok(struct xdr_stream *xdr, const struct READDIR3resok *value)
+{
+	if (!xdrgen_encode_post_op_attr(xdr, &value->dir_attributes))
+		return false;
+	if (!xdrgen_encode_cookieverf3(xdr, value->cookieverf))
+		return false;
+	if (!xdrgen_encode_dirlist3(xdr, &value->reply))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_READDIR3resfail(struct xdr_stream *xdr, const struct READDIR3resfail *value)
+{
+	if (!xdrgen_encode_post_op_attr(xdr, &value->dir_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_READDIR3res(struct xdr_stream *xdr, const struct READDIR3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_READDIR3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_READDIR3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_READDIRPLUS3args(struct xdr_stream *xdr, const struct READDIRPLUS3args *value)
+{
+	if (!xdrgen_encode_nfs_fh3(xdr, &value->dir))
+		return false;
+	if (!xdrgen_encode_cookie3(xdr, value->cookie))
+		return false;
+	if (!xdrgen_encode_cookieverf3(xdr, value->cookieverf))
+		return false;
+	if (!xdrgen_encode_count3(xdr, value->dircount))
+		return false;
+	if (!xdrgen_encode_count3(xdr, value->maxcount))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_entryplus3(struct xdr_stream *xdr, const struct entryplus3 *value)
+{
+	if (!xdrgen_encode_bool(xdr, value != NULL))
+		return false;
+	if (!value)
+		return true;
+
+	if (!xdrgen_encode_fileid3(xdr, value->fileid))
+		return false;
+	if (!xdrgen_encode_filename3(xdr, value->name))
+		return false;
+	if (!xdrgen_encode_cookie3(xdr, value->cookie))
+		return false;
+	if (!xdrgen_encode_post_op_attr(xdr, &value->name_attributes))
+		return false;
+	if (!xdrgen_encode_post_op_fh3(xdr, &value->name_handle))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_dirlistplus3(struct xdr_stream *xdr, const struct dirlistplus3 *value)
+{
+	if (!xdrgen_encode_entryplus3(xdr, value->entries))
+		return false;
+	if (!xdrgen_encode_bool(xdr, value->eof))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_READDIRPLUS3resok(struct xdr_stream *xdr, const struct READDIRPLUS3resok *value)
+{
+	if (!xdrgen_encode_post_op_attr(xdr, &value->dir_attributes))
+		return false;
+	if (!xdrgen_encode_cookieverf3(xdr, value->cookieverf))
+		return false;
+	if (!xdrgen_encode_dirlistplus3(xdr, &value->reply))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_READDIRPLUS3resfail(struct xdr_stream *xdr, const struct READDIRPLUS3resfail *value)
+{
+	if (!xdrgen_encode_post_op_attr(xdr, &value->dir_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_READDIRPLUS3res(struct xdr_stream *xdr, const struct READDIRPLUS3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_READDIRPLUS3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_READDIRPLUS3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_FSSTAT3args(struct xdr_stream *xdr, const struct FSSTAT3args *value)
+{
+	if (!xdrgen_encode_nfs_fh3(xdr, &value->fsroot))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_FSSTAT3resok(struct xdr_stream *xdr, const struct FSSTAT3resok *value)
+{
+	if (!xdrgen_encode_post_op_attr(xdr, &value->obj_attributes))
+		return false;
+	if (!xdrgen_encode_size3(xdr, value->tbytes))
+		return false;
+	if (!xdrgen_encode_size3(xdr, value->fbytes))
+		return false;
+	if (!xdrgen_encode_size3(xdr, value->abytes))
+		return false;
+	if (!xdrgen_encode_size3(xdr, value->tfiles))
+		return false;
+	if (!xdrgen_encode_size3(xdr, value->ffiles))
+		return false;
+	if (!xdrgen_encode_size3(xdr, value->afiles))
+		return false;
+	if (!xdrgen_encode_uint32(xdr, value->invarsec))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_FSSTAT3resfail(struct xdr_stream *xdr, const struct FSSTAT3resfail *value)
+{
+	if (!xdrgen_encode_post_op_attr(xdr, &value->obj_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_FSSTAT3res(struct xdr_stream *xdr, const struct FSSTAT3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_FSSTAT3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_FSSTAT3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_FSINFO3args(struct xdr_stream *xdr, const struct FSINFO3args *value)
+{
+	if (!xdrgen_encode_nfs_fh3(xdr, &value->fsroot))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_FSINFO3resok(struct xdr_stream *xdr, const struct FSINFO3resok *value)
+{
+	if (!xdrgen_encode_post_op_attr(xdr, &value->obj_attributes))
+		return false;
+	if (!xdrgen_encode_uint32(xdr, value->rtmax))
+		return false;
+	if (!xdrgen_encode_uint32(xdr, value->rtpref))
+		return false;
+	if (!xdrgen_encode_uint32(xdr, value->rtmult))
+		return false;
+	if (!xdrgen_encode_uint32(xdr, value->wtmax))
+		return false;
+	if (!xdrgen_encode_uint32(xdr, value->wtpref))
+		return false;
+	if (!xdrgen_encode_uint32(xdr, value->wtmult))
+		return false;
+	if (!xdrgen_encode_uint32(xdr, value->dtpref))
+		return false;
+	if (!xdrgen_encode_size3(xdr, value->maxfilesize))
+		return false;
+	if (!xdrgen_encode_nfstime3(xdr, &value->time_delta))
+		return false;
+	if (!xdrgen_encode_uint32(xdr, value->properties))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_FSINFO3resfail(struct xdr_stream *xdr, const struct FSINFO3resfail *value)
+{
+	if (!xdrgen_encode_post_op_attr(xdr, &value->obj_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_FSINFO3res(struct xdr_stream *xdr, const struct FSINFO3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_FSINFO3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_FSINFO3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_PATHCONF3args(struct xdr_stream *xdr, const struct PATHCONF3args *value)
+{
+	if (!xdrgen_encode_nfs_fh3(xdr, &value->object))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_PATHCONF3resok(struct xdr_stream *xdr, const struct PATHCONF3resok *value)
+{
+	if (!xdrgen_encode_post_op_attr(xdr, &value->obj_attributes))
+		return false;
+	if (!xdrgen_encode_uint32(xdr, value->linkmax))
+		return false;
+	if (!xdrgen_encode_uint32(xdr, value->name_max))
+		return false;
+	if (!xdrgen_encode_bool(xdr, value->no_trunc))
+		return false;
+	if (!xdrgen_encode_bool(xdr, value->chown_restricted))
+		return false;
+	if (!xdrgen_encode_bool(xdr, value->case_insensitive))
+		return false;
+	if (!xdrgen_encode_bool(xdr, value->case_preserving))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_PATHCONF3resfail(struct xdr_stream *xdr, const struct PATHCONF3resfail *value)
+{
+	if (!xdrgen_encode_post_op_attr(xdr, &value->obj_attributes))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_PATHCONF3res(struct xdr_stream *xdr, const struct PATHCONF3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_PATHCONF3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_PATHCONF3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_COMMIT3args(struct xdr_stream *xdr, const struct COMMIT3args *value)
+{
+	if (!xdrgen_encode_nfs_fh3(xdr, &value->file))
+		return false;
+	if (!xdrgen_encode_offset3(xdr, value->offset))
+		return false;
+	if (!xdrgen_encode_count3(xdr, value->count))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_COMMIT3resok(struct xdr_stream *xdr, const struct COMMIT3resok *value)
+{
+	if (!xdrgen_encode_wcc_data(xdr, &value->file_wcc))
+		return false;
+	if (!xdrgen_encode_writeverf3(xdr, value->verf))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_COMMIT3resfail(struct xdr_stream *xdr, const struct COMMIT3resfail *value)
+{
+	if (!xdrgen_encode_wcc_data(xdr, &value->file_wcc))
+		return false;
+	return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_COMMIT3res(struct xdr_stream *xdr, const struct COMMIT3res *ptr)
+{
+	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
+		return false;
+	switch (ptr->status) {
+	case NFS3_OK:
+		if (!xdrgen_encode_COMMIT3resok(xdr, &ptr->u.resok))
+			return false;
+		break;
+	default:
+		if (!xdrgen_encode_COMMIT3resfail(xdr, &ptr->u.resfail))
+			return false;
+	}
+	return true;
+}
+
+/**
+ * nfs_svc_encode_GETATTR3res - Encode a GETATTR3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_GETATTR3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct GETATTR3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_GETATTR3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_SETATTR3res - Encode a SETATTR3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_SETATTR3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct SETATTR3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_SETATTR3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_LOOKUP3res - Encode a LOOKUP3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_LOOKUP3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct LOOKUP3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_LOOKUP3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_ACCESS3res - Encode a ACCESS3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_ACCESS3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct ACCESS3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_ACCESS3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_READLINK3res - Encode a READLINK3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_READLINK3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct READLINK3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_READLINK3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_READ3res - Encode a READ3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_READ3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct READ3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_READ3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_WRITE3res - Encode a WRITE3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_WRITE3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct WRITE3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_WRITE3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_CREATE3res - Encode a CREATE3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_CREATE3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct CREATE3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_CREATE3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_MKDIR3res - Encode a MKDIR3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_MKDIR3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct MKDIR3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_MKDIR3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_SYMLINK3res - Encode a SYMLINK3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_SYMLINK3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct SYMLINK3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_SYMLINK3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_MKNOD3res - Encode a MKNOD3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_MKNOD3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct MKNOD3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_MKNOD3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_REMOVE3res - Encode a REMOVE3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_REMOVE3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct REMOVE3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_REMOVE3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_RMDIR3res - Encode a RMDIR3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_RMDIR3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct RMDIR3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_RMDIR3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_RENAME3res - Encode a RENAME3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_RENAME3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct RENAME3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_RENAME3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_LINK3res - Encode a LINK3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_LINK3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct LINK3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_LINK3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_READDIR3res - Encode a READDIR3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_READDIR3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct READDIR3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_READDIR3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_READDIRPLUS3res - Encode a READDIRPLUS3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_READDIRPLUS3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct READDIRPLUS3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_READDIRPLUS3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_FSSTAT3res - Encode a FSSTAT3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_FSSTAT3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct FSSTAT3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_FSSTAT3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_FSINFO3res - Encode a FSINFO3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_FSINFO3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct FSINFO3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_FSINFO3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_PATHCONF3res - Encode a PATHCONF3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_PATHCONF3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct PATHCONF3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_PATHCONF3res(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_COMMIT3res - Encode a COMMIT3res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ *   %true: procedure results encoded successfully
+ *   %false: encode failed
+ */
+bool nfs_svc_encode_COMMIT3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+	struct COMMIT3res *resp = rqstp->rq_resp;
+
+	return xdrgen_encode_COMMIT3res(xdr, resp);
+}
diff --git a/fs/nfsd/nfs3xdr_gen.h b/fs/nfsd/nfs3xdr_gen.h
new file mode 100644
index 000000000000..e2fbb583da92
--- /dev/null
+++ b/fs/nfsd/nfs3xdr_gen.h
@@ -0,0 +1,60 @@
+/* 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: Fri Jul 10 23:20:55 2026 */
+
+#ifndef _LINUX_XDRGEN_NFS3_DECL_H
+#define _LINUX_XDRGEN_NFS3_DECL_H
+
+#include <linux/types.h>
+
+#include <linux/sunrpc/xdr.h>
+#include <linux/sunrpc/xdrgen/_defs.h>
+#include <linux/sunrpc/xdrgen/_builtins.h>
+#include <linux/sunrpc/xdrgen/nfs3.h>
+
+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);
+bool nfs_svc_decode_ACCESS3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_READLINK3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_READ3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_WRITE3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_CREATE3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_MKDIR3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_SYMLINK3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_MKNOD3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_REMOVE3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_RMDIR3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_RENAME3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_LINK3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_READDIR3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_READDIRPLUS3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_FSSTAT3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_FSINFO3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_PATHCONF3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_COMMIT3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+
+bool nfs_svc_encode_GETATTR3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_SETATTR3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_LOOKUP3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_ACCESS3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_READLINK3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_READ3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_WRITE3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_CREATE3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_MKDIR3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_SYMLINK3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_MKNOD3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_REMOVE3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_RMDIR3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_RENAME3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_LINK3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_READDIR3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_READDIRPLUS3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_FSSTAT3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_FSINFO3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_PATHCONF3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_COMMIT3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+
+#endif /* _LINUX_XDRGEN_NFS3_DECL_H */
diff --git a/include/linux/sunrpc/xdrgen/nfs3.h b/include/linux/sunrpc/xdrgen/nfs3.h
new file mode 100644
index 000000000000..3afc4ffb6daa
--- /dev/null
+++ b/include/linux/sunrpc/xdrgen/nfs3.h
@@ -0,0 +1,1092 @@
+/* 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: Fri Jul 10 23:20:55 2026 */
+
+#ifndef _LINUX_XDRGEN_NFS3_DEF_H
+#define _LINUX_XDRGEN_NFS3_DEF_H
+
+#include <linux/types.h>
+#include <linux/sunrpc/xdrgen/_defs.h>
+
+enum { NFS3_FHSIZE = 64 };
+
+enum { NFS3_COOKIEVERFSIZE = 8 };
+
+enum { NFS3_CREATEVERFSIZE = 8 };
+
+enum { NFS3_WRITEVERFSIZE = 8 };
+
+typedef u64 uint64;
+
+typedef s64 int64;
+
+typedef u32 uint32;
+
+typedef s32 int32;
+
+typedef string filename3;
+
+typedef string nfspath3;
+
+typedef uint64 fileid3;
+
+typedef uint64 cookie3;
+
+typedef u8 cookieverf3[NFS3_COOKIEVERFSIZE];
+
+typedef u8 createverf3[NFS3_CREATEVERFSIZE];
+
+typedef u8 writeverf3[NFS3_WRITEVERFSIZE];
+
+typedef uint32 uid3;
+
+typedef uint32 gid3;
+
+typedef uint64 size3;
+
+typedef uint64 offset3;
+
+typedef uint32 mode3;
+
+typedef uint32 count3;
+
+enum nfsstat3 {
+	NFS3_OK = 0,
+	NFS3ERR_PERM = 1,
+	NFS3ERR_NOENT = 2,
+	NFS3ERR_IO = 5,
+	NFS3ERR_NXIO = 6,
+	NFS3ERR_ACCES = 13,
+	NFS3ERR_EXIST = 17,
+	NFS3ERR_XDEV = 18,
+	NFS3ERR_NODEV = 19,
+	NFS3ERR_NOTDIR = 20,
+	NFS3ERR_ISDIR = 21,
+	NFS3ERR_INVAL = 22,
+	NFS3ERR_FBIG = 27,
+	NFS3ERR_NOSPC = 28,
+	NFS3ERR_ROFS = 30,
+	NFS3ERR_MLINK = 31,
+	NFS3ERR_NAMETOOLONG = 63,
+	NFS3ERR_NOTEMPTY = 66,
+	NFS3ERR_DQUOT = 69,
+	NFS3ERR_STALE = 70,
+	NFS3ERR_REMOTE = 71,
+	NFS3ERR_BADHANDLE = 10001,
+	NFS3ERR_NOT_SYNC = 10002,
+	NFS3ERR_BAD_COOKIE = 10003,
+	NFS3ERR_NOTSUPP = 10004,
+	NFS3ERR_TOOSMALL = 10005,
+	NFS3ERR_SERVERFAULT = 10006,
+	NFS3ERR_BADTYPE = 10007,
+	NFS3ERR_JUKEBOX = 10008,
+};
+
+typedef enum nfsstat3 nfsstat3;
+
+enum ftype3 {
+	NF3REG = 1,
+	NF3DIR = 2,
+	NF3BLK = 3,
+	NF3CHR = 4,
+	NF3LNK = 5,
+	NF3SOCK = 6,
+	NF3FIFO = 7,
+};
+
+typedef enum ftype3 ftype3;
+
+struct specdata3 {
+	uint32 specdata1;
+	uint32 specdata2;
+};
+
+struct nfs_fh3 {
+	opaque data;
+};
+
+struct nfstime3 {
+	uint32 seconds;
+	uint32 nseconds;
+};
+
+struct fattr3 {
+	ftype3 type;
+	mode3 mode;
+	uint32 nlink;
+	uid3 uid;
+	gid3 gid;
+	size3 size;
+	size3 used;
+	struct specdata3 rdev;
+	uint64 fsid;
+	fileid3 fileid;
+	struct nfstime3 atime;
+	struct nfstime3 mtime;
+	struct nfstime3 ctime;
+};
+
+struct post_op_attr {
+	bool attributes_follow;
+	union {
+		struct fattr3 attributes;
+	} u;
+};
+
+struct wcc_attr {
+	size3 size;
+	struct nfstime3 mtime;
+	struct nfstime3 ctime;
+};
+
+struct pre_op_attr {
+	bool attributes_follow;
+	union {
+		struct wcc_attr attributes;
+	} u;
+};
+
+struct wcc_data {
+	struct pre_op_attr before;
+	struct post_op_attr after;
+};
+
+struct post_op_fh3 {
+	bool handle_follows;
+	union {
+		struct nfs_fh3 handle;
+	} u;
+};
+
+enum time_how {
+	DONT_CHANGE = 0,
+	SET_TO_SERVER_TIME = 1,
+	SET_TO_CLIENT_TIME = 2,
+};
+
+typedef enum time_how time_how;
+
+struct set_mode3 {
+	bool set_it;
+	union {
+		mode3 mode;
+	} u;
+};
+
+struct set_uid3 {
+	bool set_it;
+	union {
+		uid3 uid;
+	} u;
+};
+
+struct set_gid3 {
+	bool set_it;
+	union {
+		gid3 gid;
+	} u;
+};
+
+struct set_size3 {
+	bool set_it;
+	union {
+		size3 size;
+	} u;
+};
+
+struct set_atime {
+	time_how set_it;
+	union {
+		struct nfstime3 atime;
+	} u;
+};
+
+struct set_mtime {
+	time_how set_it;
+	union {
+		struct nfstime3 mtime;
+	} u;
+};
+
+struct sattr3 {
+	struct set_mode3 mode;
+	struct set_uid3 uid;
+	struct set_gid3 gid;
+	struct set_size3 size;
+	struct set_atime atime;
+	struct set_mtime mtime;
+};
+
+struct diropargs3 {
+	struct nfs_fh3 dir;
+	filename3 name;
+};
+
+struct GETATTR3args {
+	struct nfs_fh3 object;
+};
+
+struct GETATTR3resok {
+	struct fattr3 obj_attributes;
+};
+
+struct GETATTR3res {
+	nfsstat3 status;
+	union {
+		struct GETATTR3resok resok;
+	} u;
+};
+
+struct sattrguard3 {
+	bool check;
+	union {
+		struct nfstime3 obj_ctime;
+	} u;
+};
+
+struct SETATTR3args {
+	struct nfs_fh3 object;
+	struct sattr3 new_attributes;
+	struct sattrguard3 guard;
+};
+
+struct SETATTR3resok {
+	struct wcc_data obj_wcc;
+};
+
+struct SETATTR3resfail {
+	struct wcc_data obj_wcc;
+};
+
+struct SETATTR3res {
+	nfsstat3 status;
+	union {
+		struct SETATTR3resok resok;
+		struct SETATTR3resfail resfail;
+	} u;
+};
+
+struct LOOKUP3args {
+	struct diropargs3 what;
+};
+
+struct LOOKUP3resok {
+	struct nfs_fh3 object;
+	struct post_op_attr obj_attributes;
+	struct post_op_attr dir_attributes;
+};
+
+struct LOOKUP3resfail {
+	struct post_op_attr dir_attributes;
+};
+
+struct LOOKUP3res {
+	nfsstat3 status;
+	union {
+		struct LOOKUP3resok resok;
+		struct LOOKUP3resfail resfail;
+	} u;
+};
+
+enum { ACCESS3_READ = 0x0001 };
+
+enum { ACCESS3_LOOKUP = 0x0002 };
+
+enum { ACCESS3_MODIFY = 0x0004 };
+
+enum { ACCESS3_EXTEND = 0x0008 };
+
+enum { ACCESS3_DELETE = 0x0010 };
+
+enum { ACCESS3_EXECUTE = 0x0020 };
+
+struct ACCESS3args {
+	struct nfs_fh3 object;
+	uint32 access;
+};
+
+struct ACCESS3resok {
+	struct post_op_attr obj_attributes;
+	uint32 access;
+};
+
+struct ACCESS3resfail {
+	struct post_op_attr obj_attributes;
+};
+
+struct ACCESS3res {
+	nfsstat3 status;
+	union {
+		struct ACCESS3resok resok;
+		struct ACCESS3resfail resfail;
+	} u;
+};
+
+struct READLINK3args {
+	struct nfs_fh3 symlink;
+};
+
+struct READLINK3resok {
+	struct post_op_attr symlink_attributes;
+	nfspath3 data;
+};
+
+struct READLINK3resfail {
+	struct post_op_attr symlink_attributes;
+};
+
+struct READLINK3res {
+	nfsstat3 status;
+	union {
+		struct READLINK3resok resok;
+		struct READLINK3resfail resfail;
+	} u;
+};
+
+struct READ3args {
+	struct nfs_fh3 file;
+	offset3 offset;
+	count3 count;
+};
+
+struct READ3resok {
+	struct post_op_attr file_attributes;
+	count3 count;
+	bool eof;
+	opaque data;
+};
+
+struct READ3resfail {
+	struct post_op_attr file_attributes;
+};
+
+struct READ3res {
+	nfsstat3 status;
+	union {
+		struct READ3resok resok;
+		struct READ3resfail resfail;
+	} u;
+};
+
+enum stable_how {
+	UNSTABLE = 0,
+	DATA_SYNC = 1,
+	FILE_SYNC = 2,
+};
+
+typedef enum stable_how stable_how;
+
+struct WRITE3args {
+	struct nfs_fh3 file;
+	offset3 offset;
+	count3 count;
+	stable_how stable;
+	opaque data;
+};
+
+struct WRITE3resok {
+	struct wcc_data file_wcc;
+	count3 count;
+	stable_how committed;
+	writeverf3 verf;
+};
+
+struct WRITE3resfail {
+	struct wcc_data file_wcc;
+};
+
+struct WRITE3res {
+	nfsstat3 status;
+	union {
+		struct WRITE3resok resok;
+		struct WRITE3resfail resfail;
+	} u;
+};
+
+enum createmode3 {
+	UNCHECKED = 0,
+	GUARDED = 1,
+	EXCLUSIVE = 2,
+};
+
+typedef enum createmode3 createmode3;
+
+struct createhow3 {
+	createmode3 mode;
+	union {
+		struct sattr3 obj_attributes;
+		createverf3 verf;
+	} u;
+};
+
+struct CREATE3args {
+	struct diropargs3 where;
+	struct createhow3 how;
+};
+
+struct CREATE3resok {
+	struct post_op_fh3 obj;
+	struct post_op_attr obj_attributes;
+	struct wcc_data dir_wcc;
+};
+
+struct CREATE3resfail {
+	struct wcc_data dir_wcc;
+};
+
+struct CREATE3res {
+	nfsstat3 status;
+	union {
+		struct CREATE3resok resok;
+		struct CREATE3resfail resfail;
+	} u;
+};
+
+struct MKDIR3args {
+	struct diropargs3 where;
+	struct sattr3 attributes;
+};
+
+struct MKDIR3resok {
+	struct post_op_fh3 obj;
+	struct post_op_attr obj_attributes;
+	struct wcc_data dir_wcc;
+};
+
+struct MKDIR3resfail {
+	struct wcc_data dir_wcc;
+};
+
+struct MKDIR3res {
+	nfsstat3 status;
+	union {
+		struct MKDIR3resok resok;
+		struct MKDIR3resfail resfail;
+	} u;
+};
+
+struct symlinkdata3 {
+	struct sattr3 symlink_attributes;
+	nfspath3 symlink_data;
+};
+
+struct SYMLINK3args {
+	struct diropargs3 where;
+	struct symlinkdata3 symlink;
+};
+
+struct SYMLINK3resok {
+	struct post_op_fh3 obj;
+	struct post_op_attr obj_attributes;
+	struct wcc_data dir_wcc;
+};
+
+struct SYMLINK3resfail {
+	struct wcc_data dir_wcc;
+};
+
+struct SYMLINK3res {
+	nfsstat3 status;
+	union {
+		struct SYMLINK3resok resok;
+		struct SYMLINK3resfail resfail;
+	} u;
+};
+
+struct devicedata3 {
+	struct sattr3 dev_attributes;
+	struct specdata3 spec;
+};
+
+struct mknoddata3 {
+	ftype3 type;
+	union {
+		struct devicedata3 device;
+		struct sattr3 pipe_attributes;
+	} u;
+};
+
+struct MKNOD3args {
+	struct diropargs3 where;
+	struct mknoddata3 what;
+};
+
+struct MKNOD3resok {
+	struct post_op_fh3 obj;
+	struct post_op_attr obj_attributes;
+	struct wcc_data dir_wcc;
+};
+
+struct MKNOD3resfail {
+	struct wcc_data dir_wcc;
+};
+
+struct MKNOD3res {
+	nfsstat3 status;
+	union {
+		struct MKNOD3resok resok;
+		struct MKNOD3resfail resfail;
+	} u;
+};
+
+struct REMOVE3args {
+	struct diropargs3 object;
+};
+
+struct REMOVE3resok {
+	struct wcc_data dir_wcc;
+};
+
+struct REMOVE3resfail {
+	struct wcc_data dir_wcc;
+};
+
+struct REMOVE3res {
+	nfsstat3 status;
+	union {
+		struct REMOVE3resok resok;
+		struct REMOVE3resfail resfail;
+	} u;
+};
+
+struct RMDIR3args {
+	struct diropargs3 object;
+};
+
+struct RMDIR3resok {
+	struct wcc_data dir_wcc;
+};
+
+struct RMDIR3resfail {
+	struct wcc_data dir_wcc;
+};
+
+struct RMDIR3res {
+	nfsstat3 status;
+	union {
+		struct RMDIR3resok resok;
+		struct RMDIR3resfail resfail;
+	} u;
+};
+
+struct RENAME3args {
+	struct diropargs3 from;
+	struct diropargs3 to;
+};
+
+struct RENAME3resok {
+	struct wcc_data fromdir_wcc;
+	struct wcc_data todir_wcc;
+};
+
+struct RENAME3resfail {
+	struct wcc_data fromdir_wcc;
+	struct wcc_data todir_wcc;
+};
+
+struct RENAME3res {
+	nfsstat3 status;
+	union {
+		struct RENAME3resok resok;
+		struct RENAME3resfail resfail;
+	} u;
+};
+
+struct LINK3args {
+	struct nfs_fh3 file;
+	struct diropargs3 link;
+};
+
+struct LINK3resok {
+	struct post_op_attr file_attributes;
+	struct wcc_data linkdir_wcc;
+};
+
+struct LINK3resfail {
+	struct post_op_attr file_attributes;
+	struct wcc_data linkdir_wcc;
+};
+
+struct LINK3res {
+	nfsstat3 status;
+	union {
+		struct LINK3resok resok;
+		struct LINK3resfail resfail;
+	} u;
+};
+
+struct READDIR3args {
+	struct nfs_fh3 dir;
+	cookie3 cookie;
+	cookieverf3 cookieverf;
+	count3 count;
+};
+
+struct entry3 {
+	fileid3 fileid;
+	filename3 name;
+	cookie3 cookie;
+};
+
+struct dirlist3 {
+	struct entry3 *entries;
+	bool eof;
+};
+
+struct READDIR3resok {
+	struct post_op_attr dir_attributes;
+	cookieverf3 cookieverf;
+	struct dirlist3 reply;
+};
+
+struct READDIR3resfail {
+	struct post_op_attr dir_attributes;
+};
+
+struct READDIR3res {
+	nfsstat3 status;
+	union {
+		struct READDIR3resok resok;
+		struct READDIR3resfail resfail;
+	} u;
+};
+
+struct READDIRPLUS3args {
+	struct nfs_fh3 dir;
+	cookie3 cookie;
+	cookieverf3 cookieverf;
+	count3 dircount;
+	count3 maxcount;
+};
+
+struct entryplus3 {
+	fileid3 fileid;
+	filename3 name;
+	cookie3 cookie;
+	struct post_op_attr name_attributes;
+	struct post_op_fh3 name_handle;
+};
+
+struct dirlistplus3 {
+	struct entryplus3 *entries;
+	bool eof;
+};
+
+struct READDIRPLUS3resok {
+	struct post_op_attr dir_attributes;
+	cookieverf3 cookieverf;
+	struct dirlistplus3 reply;
+};
+
+struct READDIRPLUS3resfail {
+	struct post_op_attr dir_attributes;
+};
+
+struct READDIRPLUS3res {
+	nfsstat3 status;
+	union {
+		struct READDIRPLUS3resok resok;
+		struct READDIRPLUS3resfail resfail;
+	} u;
+};
+
+struct FSSTAT3args {
+	struct nfs_fh3 fsroot;
+};
+
+struct FSSTAT3resok {
+	struct post_op_attr obj_attributes;
+	size3 tbytes;
+	size3 fbytes;
+	size3 abytes;
+	size3 tfiles;
+	size3 ffiles;
+	size3 afiles;
+	uint32 invarsec;
+};
+
+struct FSSTAT3resfail {
+	struct post_op_attr obj_attributes;
+};
+
+struct FSSTAT3res {
+	nfsstat3 status;
+	union {
+		struct FSSTAT3resok resok;
+		struct FSSTAT3resfail resfail;
+	} u;
+};
+
+enum { FSF3_LINK = 0x0001 };
+
+enum { FSF3_SYMLINK = 0x0002 };
+
+enum { FSF3_HOMOGENEOUS = 0x0008 };
+
+enum { FSF3_CANSETTIME = 0x0010 };
+
+struct FSINFO3args {
+	struct nfs_fh3 fsroot;
+};
+
+struct FSINFO3resok {
+	struct post_op_attr obj_attributes;
+	uint32 rtmax;
+	uint32 rtpref;
+	uint32 rtmult;
+	uint32 wtmax;
+	uint32 wtpref;
+	uint32 wtmult;
+	uint32 dtpref;
+	size3 maxfilesize;
+	struct nfstime3 time_delta;
+	uint32 properties;
+};
+
+struct FSINFO3resfail {
+	struct post_op_attr obj_attributes;
+};
+
+struct FSINFO3res {
+	nfsstat3 status;
+	union {
+		struct FSINFO3resok resok;
+		struct FSINFO3resfail resfail;
+	} u;
+};
+
+struct PATHCONF3args {
+	struct nfs_fh3 object;
+};
+
+struct PATHCONF3resok {
+	struct post_op_attr obj_attributes;
+	uint32 linkmax;
+	uint32 name_max;
+	bool no_trunc;
+	bool chown_restricted;
+	bool case_insensitive;
+	bool case_preserving;
+};
+
+struct PATHCONF3resfail {
+	struct post_op_attr obj_attributes;
+};
+
+struct PATHCONF3res {
+	nfsstat3 status;
+	union {
+		struct PATHCONF3resok resok;
+		struct PATHCONF3resfail resfail;
+	} u;
+};
+
+struct COMMIT3args {
+	struct nfs_fh3 file;
+	offset3 offset;
+	count3 count;
+};
+
+struct COMMIT3resok {
+	struct wcc_data file_wcc;
+	writeverf3 verf;
+};
+
+struct COMMIT3resfail {
+	struct wcc_data file_wcc;
+};
+
+struct COMMIT3res {
+	nfsstat3 status;
+	union {
+		struct COMMIT3resok resok;
+		struct COMMIT3resfail resfail;
+	} u;
+};
+
+enum {
+	NFSPROC3_NULL = 0,
+	NFSPROC3_GETATTR = 1,
+	NFSPROC3_SETATTR = 2,
+	NFSPROC3_LOOKUP = 3,
+	NFSPROC3_ACCESS = 4,
+	NFSPROC3_READLINK = 5,
+	NFSPROC3_READ = 6,
+	NFSPROC3_WRITE = 7,
+	NFSPROC3_CREATE = 8,
+	NFSPROC3_MKDIR = 9,
+	NFSPROC3_SYMLINK = 10,
+	NFSPROC3_MKNOD = 11,
+	NFSPROC3_REMOVE = 12,
+	NFSPROC3_RMDIR = 13,
+	NFSPROC3_RENAME = 14,
+	NFSPROC3_LINK = 15,
+	NFSPROC3_READDIR = 16,
+	NFSPROC3_READDIRPLUS = 17,
+	NFSPROC3_FSSTAT = 18,
+	NFSPROC3_FSINFO = 19,
+	NFSPROC3_PATHCONF = 20,
+	NFSPROC3_COMMIT = 21,
+};
+
+#ifndef NFS_PROGRAM
+#define NFS_PROGRAM (100003)
+#endif
+
+#define NFS3_uint64_sz                  \
+	(XDR_unsigned_hyper)
+#define NFS3_int64_sz                   \
+	(XDR_hyper)
+#define NFS3_uint32_sz                  \
+	(XDR_unsigned_long)
+#define NFS3_int32_sz                   \
+	(XDR_long)
+#define NFS3_filename3_sz               (XDR_unsigned_int)
+#define NFS3_nfspath3_sz                (XDR_unsigned_int)
+#define NFS3_fileid3_sz                 \
+	(NFS3_uint64_sz)
+#define NFS3_cookie3_sz                 \
+	(NFS3_uint64_sz)
+#define NFS3_cookieverf3_sz             (XDR_QUADLEN(NFS3_COOKIEVERFSIZE))
+#define NFS3_createverf3_sz             (XDR_QUADLEN(NFS3_CREATEVERFSIZE))
+#define NFS3_writeverf3_sz              (XDR_QUADLEN(NFS3_WRITEVERFSIZE))
+#define NFS3_uid3_sz                    \
+	(NFS3_uint32_sz)
+#define NFS3_gid3_sz                    \
+	(NFS3_uint32_sz)
+#define NFS3_size3_sz                   \
+	(NFS3_uint64_sz)
+#define NFS3_offset3_sz                 \
+	(NFS3_uint64_sz)
+#define NFS3_mode3_sz                   \
+	(NFS3_uint32_sz)
+#define NFS3_count3_sz                  \
+	(NFS3_uint32_sz)
+#define NFS3_nfsstat3_sz                (XDR_int)
+#define NFS3_ftype3_sz                  (XDR_int)
+#define NFS3_specdata3_sz               \
+	(NFS3_uint32_sz + NFS3_uint32_sz)
+#define NFS3_nfs_fh3_sz                 \
+	(XDR_unsigned_int + XDR_QUADLEN(NFS3_FHSIZE))
+#define NFS3_nfstime3_sz                \
+	(NFS3_uint32_sz + NFS3_uint32_sz)
+#define NFS3_fattr3_sz                  \
+	(NFS3_ftype3_sz + NFS3_mode3_sz + NFS3_uint32_sz + NFS3_uid3_sz + NFS3_gid3_sz + NFS3_size3_sz + NFS3_size3_sz + NFS3_specdata3_sz + NFS3_uint64_sz + NFS3_fileid3_sz + NFS3_nfstime3_sz + NFS3_nfstime3_sz + NFS3_nfstime3_sz)
+#define NFS3_post_op_attr_sz            \
+	(XDR_bool + NFS3_fattr3_sz)
+#define NFS3_wcc_attr_sz                \
+	(NFS3_size3_sz + NFS3_nfstime3_sz + NFS3_nfstime3_sz)
+#define NFS3_pre_op_attr_sz             \
+	(XDR_bool + NFS3_wcc_attr_sz)
+#define NFS3_wcc_data_sz                \
+	(NFS3_pre_op_attr_sz + NFS3_post_op_attr_sz)
+#define NFS3_post_op_fh3_sz             \
+	(XDR_bool + NFS3_nfs_fh3_sz)
+#define NFS3_time_how_sz                (XDR_int)
+#define NFS3_set_mode3_sz               \
+	(NFS3_time_how_sz + NFS3_mode3_sz)
+#define NFS3_set_uid3_sz                \
+	(NFS3_time_how_sz + NFS3_uid3_sz)
+#define NFS3_set_gid3_sz                \
+	(NFS3_time_how_sz + NFS3_gid3_sz)
+#define NFS3_set_size3_sz               \
+	(NFS3_time_how_sz + NFS3_size3_sz)
+#define NFS3_set_atime_sz               \
+	(NFS3_time_how_sz + NFS3_nfstime3_sz)
+#define NFS3_set_mtime_sz               \
+	(NFS3_time_how_sz + NFS3_nfstime3_sz)
+#define NFS3_sattr3_sz                  \
+	(NFS3_set_mode3_sz + NFS3_set_uid3_sz + NFS3_set_gid3_sz + NFS3_set_size3_sz + NFS3_set_atime_sz + NFS3_set_mtime_sz)
+#define NFS3_diropargs3_sz              \
+	(NFS3_nfs_fh3_sz + NFS3_filename3_sz)
+#define NFS3_GETATTR3args_sz            \
+	(NFS3_nfs_fh3_sz)
+#define NFS3_GETATTR3resok_sz           \
+	(NFS3_fattr3_sz)
+#define NFS3_GETATTR3res_sz             \
+	(NFS3_nfsstat3_sz + NFS3_GETATTR3resok_sz)
+#define NFS3_sattrguard3_sz             \
+	(XDR_bool + NFS3_nfstime3_sz)
+#define NFS3_SETATTR3args_sz            \
+	(NFS3_nfs_fh3_sz + NFS3_sattr3_sz + NFS3_sattrguard3_sz)
+#define NFS3_SETATTR3resok_sz           \
+	(NFS3_wcc_data_sz)
+#define NFS3_SETATTR3resfail_sz         \
+	(NFS3_wcc_data_sz)
+#define NFS3_SETATTR3res_sz             \
+	(NFS3_nfsstat3_sz + NFS3_SETATTR3resok_sz)
+#define NFS3_LOOKUP3args_sz             \
+	(NFS3_diropargs3_sz)
+#define NFS3_LOOKUP3resok_sz            \
+	(NFS3_nfs_fh3_sz + NFS3_post_op_attr_sz + NFS3_post_op_attr_sz)
+#define NFS3_LOOKUP3resfail_sz          \
+	(NFS3_post_op_attr_sz)
+#define NFS3_LOOKUP3res_sz              \
+	(NFS3_nfsstat3_sz + NFS3_LOOKUP3resok_sz)
+#define NFS3_ACCESS3args_sz             \
+	(NFS3_nfs_fh3_sz + NFS3_uint32_sz)
+#define NFS3_ACCESS3resok_sz            \
+	(NFS3_post_op_attr_sz + NFS3_uint32_sz)
+#define NFS3_ACCESS3resfail_sz          \
+	(NFS3_post_op_attr_sz)
+#define NFS3_ACCESS3res_sz              \
+	(NFS3_nfsstat3_sz + NFS3_ACCESS3resok_sz)
+#define NFS3_READLINK3args_sz           \
+	(NFS3_nfs_fh3_sz)
+#define NFS3_READLINK3resok_sz          \
+	(NFS3_post_op_attr_sz + NFS3_nfspath3_sz)
+#define NFS3_READLINK3resfail_sz        \
+	(NFS3_post_op_attr_sz)
+#define NFS3_READLINK3res_sz            \
+	(NFS3_nfsstat3_sz + NFS3_READLINK3resok_sz)
+#define NFS3_READ3args_sz               \
+	(NFS3_nfs_fh3_sz + NFS3_offset3_sz + NFS3_count3_sz)
+#define NFS3_READ3resok_sz              \
+	(NFS3_post_op_attr_sz + NFS3_count3_sz + XDR_bool + XDR_unsigned_int)
+#define NFS3_READ3resfail_sz            \
+	(NFS3_post_op_attr_sz)
+#define NFS3_READ3res_sz                \
+	(NFS3_nfsstat3_sz + NFS3_READ3resok_sz)
+#define NFS3_stable_how_sz              (XDR_int)
+#define NFS3_WRITE3args_sz              \
+	(NFS3_nfs_fh3_sz + NFS3_offset3_sz + NFS3_count3_sz + NFS3_stable_how_sz + XDR_unsigned_int)
+#define NFS3_WRITE3resok_sz             \
+	(NFS3_wcc_data_sz + NFS3_count3_sz + NFS3_stable_how_sz + NFS3_writeverf3_sz)
+#define NFS3_WRITE3resfail_sz           \
+	(NFS3_wcc_data_sz)
+#define NFS3_WRITE3res_sz               \
+	(NFS3_nfsstat3_sz + NFS3_WRITE3resok_sz)
+#define NFS3_createmode3_sz             (XDR_int)
+#define NFS3_createhow3_sz              \
+	(NFS3_createmode3_sz + NFS3_sattr3_sz)
+#define NFS3_CREATE3args_sz             \
+	(NFS3_diropargs3_sz + NFS3_createhow3_sz)
+#define NFS3_CREATE3resok_sz            \
+	(NFS3_post_op_fh3_sz + NFS3_post_op_attr_sz + NFS3_wcc_data_sz)
+#define NFS3_CREATE3resfail_sz          \
+	(NFS3_wcc_data_sz)
+#define NFS3_CREATE3res_sz              \
+	(NFS3_nfsstat3_sz + NFS3_CREATE3resok_sz)
+#define NFS3_MKDIR3args_sz              \
+	(NFS3_diropargs3_sz + NFS3_sattr3_sz)
+#define NFS3_MKDIR3resok_sz             \
+	(NFS3_post_op_fh3_sz + NFS3_post_op_attr_sz + NFS3_wcc_data_sz)
+#define NFS3_MKDIR3resfail_sz           \
+	(NFS3_wcc_data_sz)
+#define NFS3_MKDIR3res_sz               \
+	(NFS3_nfsstat3_sz + NFS3_MKDIR3resok_sz)
+#define NFS3_symlinkdata3_sz            \
+	(NFS3_sattr3_sz + NFS3_nfspath3_sz)
+#define NFS3_SYMLINK3args_sz            \
+	(NFS3_diropargs3_sz + NFS3_symlinkdata3_sz)
+#define NFS3_SYMLINK3resok_sz           \
+	(NFS3_post_op_fh3_sz + NFS3_post_op_attr_sz + NFS3_wcc_data_sz)
+#define NFS3_SYMLINK3resfail_sz         \
+	(NFS3_wcc_data_sz)
+#define NFS3_SYMLINK3res_sz             \
+	(NFS3_nfsstat3_sz + NFS3_SYMLINK3resok_sz)
+#define NFS3_devicedata3_sz             \
+	(NFS3_sattr3_sz + NFS3_specdata3_sz)
+#define NFS3_mknoddata3_sz              \
+	(NFS3_ftype3_sz + NFS3_devicedata3_sz)
+#define NFS3_MKNOD3args_sz              \
+	(NFS3_diropargs3_sz + NFS3_mknoddata3_sz)
+#define NFS3_MKNOD3resok_sz             \
+	(NFS3_post_op_fh3_sz + NFS3_post_op_attr_sz + NFS3_wcc_data_sz)
+#define NFS3_MKNOD3resfail_sz           \
+	(NFS3_wcc_data_sz)
+#define NFS3_MKNOD3res_sz               \
+	(NFS3_nfsstat3_sz + NFS3_MKNOD3resok_sz)
+#define NFS3_REMOVE3args_sz             \
+	(NFS3_diropargs3_sz)
+#define NFS3_REMOVE3resok_sz            \
+	(NFS3_wcc_data_sz)
+#define NFS3_REMOVE3resfail_sz          \
+	(NFS3_wcc_data_sz)
+#define NFS3_REMOVE3res_sz              \
+	(NFS3_nfsstat3_sz + NFS3_REMOVE3resok_sz)
+#define NFS3_RMDIR3args_sz              \
+	(NFS3_diropargs3_sz)
+#define NFS3_RMDIR3resok_sz             \
+	(NFS3_wcc_data_sz)
+#define NFS3_RMDIR3resfail_sz           \
+	(NFS3_wcc_data_sz)
+#define NFS3_RMDIR3res_sz               \
+	(NFS3_nfsstat3_sz + NFS3_RMDIR3resok_sz)
+#define NFS3_RENAME3args_sz             \
+	(NFS3_diropargs3_sz + NFS3_diropargs3_sz)
+#define NFS3_RENAME3resok_sz            \
+	(NFS3_wcc_data_sz + NFS3_wcc_data_sz)
+#define NFS3_RENAME3resfail_sz          \
+	(NFS3_wcc_data_sz + NFS3_wcc_data_sz)
+#define NFS3_RENAME3res_sz              \
+	(NFS3_nfsstat3_sz + NFS3_RENAME3resok_sz)
+#define NFS3_LINK3args_sz               \
+	(NFS3_nfs_fh3_sz + NFS3_diropargs3_sz)
+#define NFS3_LINK3resok_sz              \
+	(NFS3_post_op_attr_sz + NFS3_wcc_data_sz)
+#define NFS3_LINK3resfail_sz            \
+	(NFS3_post_op_attr_sz + NFS3_wcc_data_sz)
+#define NFS3_LINK3res_sz                \
+	(NFS3_nfsstat3_sz + NFS3_LINK3resok_sz)
+#define NFS3_READDIR3args_sz            \
+	(NFS3_nfs_fh3_sz + NFS3_cookie3_sz + NFS3_cookieverf3_sz + NFS3_count3_sz)
+#define NFS3_entry3_sz                  \
+	(XDR_bool + NFS3_fileid3_sz + NFS3_filename3_sz + NFS3_cookie3_sz)
+#define NFS3_dirlist3_sz                \
+	(XDR_bool + XDR_bool)
+#define NFS3_READDIR3resok_sz           \
+	(NFS3_post_op_attr_sz + NFS3_cookieverf3_sz + NFS3_dirlist3_sz)
+#define NFS3_READDIR3resfail_sz         \
+	(NFS3_post_op_attr_sz)
+#define NFS3_READDIR3res_sz             \
+	(NFS3_nfsstat3_sz + NFS3_READDIR3resok_sz)
+#define NFS3_READDIRPLUS3args_sz        \
+	(NFS3_nfs_fh3_sz + NFS3_cookie3_sz + NFS3_cookieverf3_sz + NFS3_count3_sz + NFS3_count3_sz)
+#define NFS3_entryplus3_sz              \
+	(XDR_bool + NFS3_fileid3_sz + NFS3_filename3_sz + NFS3_cookie3_sz + NFS3_post_op_attr_sz + NFS3_post_op_fh3_sz)
+#define NFS3_dirlistplus3_sz            \
+	(XDR_bool + XDR_bool)
+#define NFS3_READDIRPLUS3resok_sz       \
+	(NFS3_post_op_attr_sz + NFS3_cookieverf3_sz + NFS3_dirlistplus3_sz)
+#define NFS3_READDIRPLUS3resfail_sz     \
+	(NFS3_post_op_attr_sz)
+#define NFS3_READDIRPLUS3res_sz         \
+	(NFS3_nfsstat3_sz + NFS3_READDIRPLUS3resok_sz)
+#define NFS3_FSSTAT3args_sz             \
+	(NFS3_nfs_fh3_sz)
+#define NFS3_FSSTAT3resok_sz            \
+	(NFS3_post_op_attr_sz + NFS3_size3_sz + NFS3_size3_sz + NFS3_size3_sz + NFS3_size3_sz + NFS3_size3_sz + NFS3_size3_sz + NFS3_uint32_sz)
+#define NFS3_FSSTAT3resfail_sz          \
+	(NFS3_post_op_attr_sz)
+#define NFS3_FSSTAT3res_sz              \
+	(NFS3_nfsstat3_sz + NFS3_FSSTAT3resok_sz)
+#define NFS3_FSINFO3args_sz             \
+	(NFS3_nfs_fh3_sz)
+#define NFS3_FSINFO3resok_sz            \
+	(NFS3_post_op_attr_sz + NFS3_uint32_sz + NFS3_uint32_sz + NFS3_uint32_sz + NFS3_uint32_sz + NFS3_uint32_sz + NFS3_uint32_sz + NFS3_uint32_sz + NFS3_size3_sz + NFS3_nfstime3_sz + NFS3_uint32_sz)
+#define NFS3_FSINFO3resfail_sz          \
+	(NFS3_post_op_attr_sz)
+#define NFS3_FSINFO3res_sz              \
+	(NFS3_nfsstat3_sz + NFS3_FSINFO3resok_sz)
+#define NFS3_PATHCONF3args_sz           \
+	(NFS3_nfs_fh3_sz)
+#define NFS3_PATHCONF3resok_sz          \
+	(NFS3_post_op_attr_sz + NFS3_uint32_sz + NFS3_uint32_sz + XDR_bool + XDR_bool + XDR_bool + XDR_bool)
+#define NFS3_PATHCONF3resfail_sz        \
+	(NFS3_post_op_attr_sz)
+#define NFS3_PATHCONF3res_sz            \
+	(NFS3_nfsstat3_sz + NFS3_PATHCONF3resok_sz)
+#define NFS3_COMMIT3args_sz             \
+	(NFS3_nfs_fh3_sz + NFS3_offset3_sz + NFS3_count3_sz)
+#define NFS3_COMMIT3resok_sz            \
+	(NFS3_wcc_data_sz + NFS3_writeverf3_sz)
+#define NFS3_COMMIT3resfail_sz          \
+	(NFS3_wcc_data_sz)
+#define NFS3_COMMIT3res_sz              \
+	(NFS3_nfsstat3_sz + NFS3_COMMIT3resok_sz)
+#define NFS3_MAX_ARGS_SZ                \
+	(NFS3_MKNOD3args_sz)
+
+#endif /* _LINUX_XDRGEN_NFS3_DEF_H */
-- 
2.55.0


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

* [PATCH v1 04/33] NFSD: Replace nfs3.h with nfs3xdr_gen.h
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (2 preceding siblings ...)
  2026-09-23 13:27 ` [PATCH v1 03/33] NFSD: Add infrastructure for generating NFSv3 XDR encoders and decoders Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 05/33] NFSD: Replace the nfsd3_createres macro Chuck Lever
                   ` (28 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

nfs3xdr_gen.h includes a set of header files that are tool-generated
from the nfs3.x specification. The goal is to replace NFSD's XDR
infrastructure with tool-generated files which are to have better
memory safety and are less brittle to maintain.

The generated header names the NFSv3 wire constants as the
specification spells them, so point NFSD's references at the new
names: NFS3_ACCESS_* becomes ACCESS3_*, NFS3PROC_* becomes
NFSPROC3_*, and the createmode and write-stability constants shed
their prefixes entirely. Move to xdr3.h the constants the generator
does not emit: the NF3NON and NF3BAD ftype3 sentinels and the name
and path length limits, none of which appear on the wire.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs2acl.c    | 26 ++++++------
 fs/nfsd/nfs3proc.c   | 96 +++++++++++++++++++++++---------------------
 fs/nfsd/nfs3xdr.c    |  9 ++---
 fs/nfsd/nfsctl.c     |  1 -
 fs/nfsd/nfserr.h     |  2 +-
 fs/nfsd/nfsfh.c      |  1 -
 fs/nfsd/xdr3.h       |  6 +++
 include/linux/nfs3.h |  3 --
 8 files changed, 74 insertions(+), 70 deletions(-)

diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
index eada1ab51415..13904e2740bf 100644
--- a/fs/nfsd/nfs2acl.c
+++ b/fs/nfsd/nfs2acl.c
@@ -22,19 +22,19 @@
  * the behavior of the two versions to diverge if needed.
  */
 static const struct nfsd_access_map nfsd2_regaccess[] = {
-	{ NFS3_ACCESS_READ,	NFSD_MAY_READ				},
-	{ NFS3_ACCESS_EXECUTE,	NFSD_MAY_EXEC				},
-	{ NFS3_ACCESS_MODIFY,	NFSD_MAY_WRITE|NFSD_MAY_TRUNC		},
-	{ NFS3_ACCESS_EXTEND,	NFSD_MAY_WRITE				},
+	{ ACCESS3_READ,		NFSD_MAY_READ				},
+	{ ACCESS3_EXECUTE,	NFSD_MAY_EXEC				},
+	{ ACCESS3_MODIFY,	NFSD_MAY_WRITE|NFSD_MAY_TRUNC		},
+	{ ACCESS3_EXTEND,	NFSD_MAY_WRITE				},
 	{ 0,			0					}
 };
 
 static const struct nfsd_access_map nfsd2_diraccess[] = {
-	{ NFS3_ACCESS_READ,	NFSD_MAY_READ				},
-	{ NFS3_ACCESS_LOOKUP,	NFSD_MAY_EXEC				},
-	{ NFS3_ACCESS_MODIFY,	NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC },
-	{ NFS3_ACCESS_EXTEND,	NFSD_MAY_EXEC|NFSD_MAY_WRITE		},
-	{ NFS3_ACCESS_DELETE,	NFSD_MAY_REMOVE				},
+	{ ACCESS3_READ,		NFSD_MAY_READ				},
+	{ ACCESS3_LOOKUP,	NFSD_MAY_EXEC				},
+	{ ACCESS3_MODIFY,	NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC },
+	{ ACCESS3_EXTEND,	NFSD_MAY_EXEC|NFSD_MAY_WRITE		},
+	{ ACCESS3_DELETE,	NFSD_MAY_REMOVE				},
 	{ 0,			0					}
 };
 
@@ -46,10 +46,10 @@ static const struct nfsd_access_map nfsd2_diraccess[] = {
  * filesystem checks.
  */
 static const struct nfsd_access_map nfsd2_otheraccess[] = {
-	{ NFS3_ACCESS_READ,	NFSD_MAY_READ				},
-	{ NFS3_ACCESS_EXECUTE,	NFSD_MAY_EXEC				},
-	{ NFS3_ACCESS_MODIFY,	NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS	},
-	{ NFS3_ACCESS_EXTEND,	NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS	},
+	{ ACCESS3_READ,		NFSD_MAY_READ				},
+	{ ACCESS3_EXECUTE,	NFSD_MAY_EXEC				},
+	{ ACCESS3_MODIFY,	NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS	},
+	{ ACCESS3_EXTEND,	NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS	},
 	{ 0,			0					}
 };
 
diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 60cd01b6a37d..223398ada3e3 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -9,7 +9,6 @@
 #include <linux/ext2_fs.h>
 #include <linux/magic.h>
 #include <linux/namei.h>
-#include <linux/nfs3.h>
 
 #include "cache.h"
 #include "xdr3.h"
@@ -18,8 +17,13 @@
 #include "filecache.h"
 #include "trace.h"
 
+#include "nfs3xdr_gen.h"
+
 #define NFSDDBG_FACILITY		NFSDDBG_PROC
 
+#define NFS3_FSF_DEFAULT		0x001b
+#define NFS3_FSF_BILLYBOY		0x0018
+
 static int	nfs3_ftypes[] = {
 	0,			/* NF3NON */
 	S_IFREG,		/* NF3REG */
@@ -51,19 +55,19 @@ static bool nfsd3_time_in_range(const struct iattr *iap)
 }
 
 static const struct nfsd_access_map nfsd3_regaccess[] = {
-	{ NFS3_ACCESS_READ,	NFSD_MAY_READ				},
-	{ NFS3_ACCESS_EXECUTE,	NFSD_MAY_EXEC				},
-	{ NFS3_ACCESS_MODIFY,	NFSD_MAY_WRITE|NFSD_MAY_TRUNC		},
-	{ NFS3_ACCESS_EXTEND,	NFSD_MAY_WRITE				},
+	{ ACCESS3_READ,		NFSD_MAY_READ				},
+	{ ACCESS3_EXECUTE,	NFSD_MAY_EXEC				},
+	{ ACCESS3_MODIFY,	NFSD_MAY_WRITE|NFSD_MAY_TRUNC		},
+	{ ACCESS3_EXTEND,	NFSD_MAY_WRITE				},
 	{ 0,			0					}
 };
 
 static const struct nfsd_access_map nfsd3_diraccess[] = {
-	{ NFS3_ACCESS_READ,	NFSD_MAY_READ				},
-	{ NFS3_ACCESS_LOOKUP,	NFSD_MAY_EXEC				},
-	{ NFS3_ACCESS_MODIFY,	NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC },
-	{ NFS3_ACCESS_EXTEND,	NFSD_MAY_EXEC|NFSD_MAY_WRITE		},
-	{ NFS3_ACCESS_DELETE,	NFSD_MAY_REMOVE				},
+	{ ACCESS3_READ,		NFSD_MAY_READ				},
+	{ ACCESS3_LOOKUP,	NFSD_MAY_EXEC				},
+	{ ACCESS3_MODIFY,	NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC },
+	{ ACCESS3_EXTEND,	NFSD_MAY_EXEC|NFSD_MAY_WRITE		},
+	{ ACCESS3_DELETE,	NFSD_MAY_REMOVE				},
 	{ 0,			0					}
 };
 
@@ -75,10 +79,10 @@ static const struct nfsd_access_map nfsd3_diraccess[] = {
  * filesystem checks.
  */
 static const struct nfsd_access_map nfsd3_otheraccess[] = {
-	{ NFS3_ACCESS_READ,	NFSD_MAY_READ				},
-	{ NFS3_ACCESS_EXECUTE,	NFSD_MAY_EXEC				},
-	{ NFS3_ACCESS_MODIFY,	NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS	},
-	{ NFS3_ACCESS_EXTEND,	NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS	},
+	{ ACCESS3_READ,		NFSD_MAY_READ				},
+	{ ACCESS3_EXECUTE,	NFSD_MAY_EXEC				},
+	{ ACCESS3_MODIFY,	NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS	},
+	{ ACCESS3_EXTEND,	NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS	},
 	{ 0,			0					}
 };
 
@@ -88,13 +92,13 @@ static const struct nfsd_access_maps nfsd3_access_maps = {
 	.other		= nfsd3_otheraccess,
 };
 
-static int nfsd3_iocb_flags(enum nfs3_stable_how how)
+static int nfsd3_iocb_flags(enum stable_how how)
 {
 	switch (how) {
-	case NFS_FILE_SYNC:
+	case FILE_SYNC:
 		/* persist data and timestamps */
 		return IOCB_DSYNC | IOCB_SYNC;
-	case NFS_DATA_SYNC:
+	case DATA_SYNC:
 		/* persist data only */
 		return IOCB_DSYNC;
 	default:
@@ -280,7 +284,7 @@ nfsd3_proc_read(struct svc_rqst *rqstp)
 	 * + 1 (xdr opaque byte count) = 26
 	 */
 	resp->count = argp->count;
-	svc_reserve_auth(rqstp, ((1 + NFS3_POST_OP_ATTR_WORDS + 3) << 2) +
+	svc_reserve_auth(rqstp, ((1 + NFS3_post_op_attr_sz + 3) << 2) +
 			 resp->count + 4);
 
 	fh_copy(&resp->fh, &argp->fh);
@@ -378,7 +382,7 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
 	}
 
 	exp = exp_get(fhp->fh_export);
-	if (argp->createmode == NFS3_CREATE_UNCHECKED) {
+	if (argp->createmode == UNCHECKED) {
 		/*
 		 * If name is already in dcache we need to check for mountpoints
 		 */
@@ -399,7 +403,7 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
 
 	v_mtime = 0;
 	v_atime = 0;
-	if (argp->createmode == NFS3_CREATE_EXCLUSIVE) {
+	if (argp->createmode == EXCLUSIVE) {
 		u32 *verifier = (u32 *)argp->verf;
 
 		/*
@@ -415,15 +419,15 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
 		status = nfs_ok;
 
 		switch (argp->createmode) {
-		case NFS3_CREATE_UNCHECKED:
+		case UNCHECKED:
 			if (!d_is_reg(child))
 				break;
 			iap->ia_valid &= ATTR_SIZE;
 			goto set_attr;
-		case NFS3_CREATE_GUARDED:
+		case GUARDED:
 			status = nfserr_exist;
 			break;
-		case NFS3_CREATE_EXCLUSIVE:
+		case EXCLUSIVE:
 			if (inode_get_mtime_sec(d_inode(child)) == v_mtime &&
 			    inode_get_atime_sec(d_inode(child)) == v_atime &&
 			    d_inode(child)->i_size == 0) {
@@ -450,7 +454,7 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
 	/* A newly created file already has a file size of zero. */
 	if ((iap->ia_valid & ATTR_SIZE) && (iap->ia_size == 0))
 		iap->ia_valid &= ~ATTR_SIZE;
-	if (argp->createmode == NFS3_CREATE_EXCLUSIVE) {
+	if (argp->createmode == EXCLUSIVE) {
 		iap->ia_valid = ATTR_MTIME | ATTR_ATIME |
 				ATTR_MTIME_SET | ATTR_ATIME_SET;
 		iap->ia_mtime.tv_sec = v_mtime;
@@ -921,7 +925,7 @@ nfsd3_proc_commit(struct svc_rqst *rqstp)
 #define WC (7+pAT)	/* WCC attributes */
 
 static const struct svc_procedure nfsd_procedures3[22] = {
-	[NFS3PROC_NULL] = {
+	[NFSPROC3_NULL] = {
 		.pc_func = nfsd3_proc_null,
 		.pc_decode = nfssvc_decode_voidarg,
 		.pc_encode = nfssvc_encode_voidres,
@@ -932,7 +936,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_xdrressize = ST,
 		.pc_name = "NULL",
 	},
-	[NFS3PROC_GETATTR] = {
+	[NFSPROC3_GETATTR] = {
 		.pc_func = nfsd3_proc_getattr,
 		.pc_decode = nfs3svc_decode_fhandleargs,
 		.pc_encode = nfs3svc_encode_getattrres,
@@ -944,7 +948,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_xdrressize = ST+AT,
 		.pc_name = "GETATTR",
 	},
-	[NFS3PROC_SETATTR] = {
+	[NFSPROC3_SETATTR] = {
 		.pc_func = nfsd3_proc_setattr,
 		.pc_decode = nfs3svc_decode_sattrargs,
 		.pc_encode = nfs3svc_encode_wccstatres,
@@ -956,7 +960,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_xdrressize = ST+WC,
 		.pc_name = "SETATTR",
 	},
-	[NFS3PROC_LOOKUP] = {
+	[NFSPROC3_LOOKUP] = {
 		.pc_func = nfsd3_proc_lookup,
 		.pc_decode = nfs3svc_decode_diropargs,
 		.pc_encode = nfs3svc_encode_lookupres,
@@ -968,7 +972,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_xdrressize = ST+FH+pAT+pAT,
 		.pc_name = "LOOKUP",
 	},
-	[NFS3PROC_ACCESS] = {
+	[NFSPROC3_ACCESS] = {
 		.pc_func = nfsd3_proc_access,
 		.pc_decode = nfs3svc_decode_accessargs,
 		.pc_encode = nfs3svc_encode_accessres,
@@ -980,7 +984,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_xdrressize = ST+pAT+1,
 		.pc_name = "ACCESS",
 	},
-	[NFS3PROC_READLINK] = {
+	[NFSPROC3_READLINK] = {
 		.pc_func = nfsd3_proc_readlink,
 		.pc_decode = nfs3svc_decode_fhandleargs,
 		.pc_encode = nfs3svc_encode_readlinkres,
@@ -992,7 +996,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_xdrressize = ST+pAT+1+NFS3_MAXPATHLEN/4,
 		.pc_name = "READLINK",
 	},
-	[NFS3PROC_READ] = {
+	[NFSPROC3_READ] = {
 		.pc_func = nfsd3_proc_read,
 		.pc_decode = nfs3svc_decode_readargs,
 		.pc_encode = nfs3svc_encode_readres,
@@ -1004,7 +1008,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_xdrressize = ST+pAT+4+NFSSVC_MAXBLKSIZE/4,
 		.pc_name = "READ",
 	},
-	[NFS3PROC_WRITE] = {
+	[NFSPROC3_WRITE] = {
 		.pc_func = nfsd3_proc_write,
 		.pc_decode = nfs3svc_decode_writeargs,
 		.pc_encode = nfs3svc_encode_writeres,
@@ -1016,7 +1020,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_xdrressize = ST+WC+4,
 		.pc_name = "WRITE",
 	},
-	[NFS3PROC_CREATE] = {
+	[NFSPROC3_CREATE] = {
 		.pc_func = nfsd3_proc_create,
 		.pc_decode = nfs3svc_decode_createargs,
 		.pc_encode = nfs3svc_encode_createres,
@@ -1028,7 +1032,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_xdrressize = ST+(1+FH+pAT)+WC,
 		.pc_name = "CREATE",
 	},
-	[NFS3PROC_MKDIR] = {
+	[NFSPROC3_MKDIR] = {
 		.pc_func = nfsd3_proc_mkdir,
 		.pc_decode = nfs3svc_decode_mkdirargs,
 		.pc_encode = nfs3svc_encode_createres,
@@ -1040,7 +1044,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_xdrressize = ST+(1+FH+pAT)+WC,
 		.pc_name = "MKDIR",
 	},
-	[NFS3PROC_SYMLINK] = {
+	[NFSPROC3_SYMLINK] = {
 		.pc_func = nfsd3_proc_symlink,
 		.pc_decode = nfs3svc_decode_symlinkargs,
 		.pc_encode = nfs3svc_encode_createres,
@@ -1052,7 +1056,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_xdrressize = ST+(1+FH+pAT)+WC,
 		.pc_name = "SYMLINK",
 	},
-	[NFS3PROC_MKNOD] = {
+	[NFSPROC3_MKNOD] = {
 		.pc_func = nfsd3_proc_mknod,
 		.pc_decode = nfs3svc_decode_mknodargs,
 		.pc_encode = nfs3svc_encode_createres,
@@ -1064,7 +1068,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_xdrressize = ST+(1+FH+pAT)+WC,
 		.pc_name = "MKNOD",
 	},
-	[NFS3PROC_REMOVE] = {
+	[NFSPROC3_REMOVE] = {
 		.pc_func = nfsd3_proc_remove,
 		.pc_decode = nfs3svc_decode_diropargs,
 		.pc_encode = nfs3svc_encode_wccstatres,
@@ -1076,7 +1080,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_xdrressize = ST+WC,
 		.pc_name = "REMOVE",
 	},
-	[NFS3PROC_RMDIR] = {
+	[NFSPROC3_RMDIR] = {
 		.pc_func = nfsd3_proc_rmdir,
 		.pc_decode = nfs3svc_decode_diropargs,
 		.pc_encode = nfs3svc_encode_wccstatres,
@@ -1088,7 +1092,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_xdrressize = ST+WC,
 		.pc_name = "RMDIR",
 	},
-	[NFS3PROC_RENAME] = {
+	[NFSPROC3_RENAME] = {
 		.pc_func = nfsd3_proc_rename,
 		.pc_decode = nfs3svc_decode_renameargs,
 		.pc_encode = nfs3svc_encode_renameres,
@@ -1100,7 +1104,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_xdrressize = ST+WC+WC,
 		.pc_name = "RENAME",
 	},
-	[NFS3PROC_LINK] = {
+	[NFSPROC3_LINK] = {
 		.pc_func = nfsd3_proc_link,
 		.pc_decode = nfs3svc_decode_linkargs,
 		.pc_encode = nfs3svc_encode_linkres,
@@ -1112,7 +1116,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_xdrressize = ST+pAT+WC,
 		.pc_name = "LINK",
 	},
-	[NFS3PROC_READDIR] = {
+	[NFSPROC3_READDIR] = {
 		.pc_func = nfsd3_proc_readdir,
 		.pc_decode = nfs3svc_decode_readdirargs,
 		.pc_encode = nfs3svc_encode_readdirres,
@@ -1123,7 +1127,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_cachetype = RC_NOCACHE,
 		.pc_name = "READDIR",
 	},
-	[NFS3PROC_READDIRPLUS] = {
+	[NFSPROC3_READDIRPLUS] = {
 		.pc_func = nfsd3_proc_readdirplus,
 		.pc_decode = nfs3svc_decode_readdirplusargs,
 		.pc_encode = nfs3svc_encode_readdirres,
@@ -1134,7 +1138,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_cachetype = RC_NOCACHE,
 		.pc_name = "READDIRPLUS",
 	},
-	[NFS3PROC_FSSTAT] = {
+	[NFSPROC3_FSSTAT] = {
 		.pc_func = nfsd3_proc_fsstat,
 		.pc_decode = nfs3svc_decode_fhandleargs,
 		.pc_encode = nfs3svc_encode_fsstatres,
@@ -1145,7 +1149,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_xdrressize = ST+pAT+2*6+1,
 		.pc_name = "FSSTAT",
 	},
-	[NFS3PROC_FSINFO] = {
+	[NFSPROC3_FSINFO] = {
 		.pc_func = nfsd3_proc_fsinfo,
 		.pc_decode = nfs3svc_decode_fhandleargs,
 		.pc_encode = nfs3svc_encode_fsinfores,
@@ -1156,7 +1160,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_xdrressize = ST+pAT+12,
 		.pc_name = "FSINFO",
 	},
-	[NFS3PROC_PATHCONF] = {
+	[NFSPROC3_PATHCONF] = {
 		.pc_func = nfsd3_proc_pathconf,
 		.pc_decode = nfs3svc_decode_fhandleargs,
 		.pc_encode = nfs3svc_encode_pathconfres,
@@ -1167,7 +1171,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_xdrressize = ST+pAT+6,
 		.pc_name = "PATHCONF",
 	},
-	[NFS3PROC_COMMIT] = {
+	[NFSPROC3_COMMIT] = {
 		.pc_func = nfsd3_proc_commit,
 		.pc_decode = nfs3svc_decode_commitargs,
 		.pc_encode = nfs3svc_encode_commitres,
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index f0895f5e32f6..d163b51684d5 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -8,7 +8,6 @@
  */
 
 #include <linux/namei.h>
-#include <linux/nfs3.h>
 #include <linux/sunrpc/svc_xprt.h>
 
 #include "nfsd.h"
@@ -560,7 +559,7 @@ nfs3svc_decode_writeargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 		return false;
 	if (xdr_stream_decode_u32(xdr, &args->stable) < 0)
 		return false;
-	if (args->stable > NFS_FILE_SYNC)
+	if (args->stable > FILE_SYNC)
 		return false;
 
 	/* opaque data */
@@ -588,10 +587,10 @@ nfs3svc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 	if (xdr_stream_decode_u32(xdr, &args->createmode) < 0)
 		return false;
 	switch (args->createmode) {
-	case NFS3_CREATE_UNCHECKED:
-	case NFS3_CREATE_GUARDED:
+	case UNCHECKED:
+	case GUARDED:
 		return svcxdr_decode_sattr3(rqstp, xdr, &args->attrs);
-	case NFS3_CREATE_EXCLUSIVE:
+	case EXCLUSIVE:
 		args->verf = xdr_inline_decode(xdr, NFS3_CREATEVERFSIZE);
 		if (!args->verf)
 			return false;
diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
index e2455a549e25..fc014a7f9b8d 100644
--- a/fs/nfsd/nfsctl.c
+++ b/fs/nfsd/nfsctl.c
@@ -20,7 +20,6 @@
 #include <linux/module.h>
 #include <linux/fsnotify.h>
 #include <linux/nfslocalio.h>
-#include <linux/nfs3.h>
 
 #include "idmap.h"
 #include "nfsd.h"
diff --git a/fs/nfsd/nfserr.h b/fs/nfsd/nfserr.h
index 35debf1141a3..155a7fa88d52 100644
--- a/fs/nfsd/nfserr.h
+++ b/fs/nfsd/nfserr.h
@@ -11,7 +11,7 @@
 #define LINUX_NFSD_NFSERR_H
 
 #include <linux/sunrpc/xdrgen/nfs2.h>
-#include <linux/nfs3.h>
+#include <linux/sunrpc/xdrgen/nfs3.h>
 #include <linux/nfs4.h>
 
 /*
diff --git a/fs/nfsd/nfsfh.c b/fs/nfsd/nfsfh.c
index 2bd6907f443f..b1f3c22af525 100644
--- a/fs/nfsd/nfsfh.c
+++ b/fs/nfsd/nfsfh.c
@@ -9,7 +9,6 @@
  */
 
 #include <linux/exportfs.h>
-#include <linux/nfs3.h>
 
 #include <linux/sunrpc/svcauth_gss.h>
 #include <crypto/utils.h>
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 22272695e451..60c7fe131e2e 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -12,6 +12,12 @@
 
 #include "vfs.h"
 
+#define NF3NON		(0)
+#define NF3BAD		(8)
+
+#define NFS3_MAXNAMLEN			NAME_MAX
+#define NFS3_MAXPATHLEN			PATH_MAX
+
 struct nfsd3_sattrargs {
 	struct svc_fh		fh;
 	struct iattr		attrs;
diff --git a/include/linux/nfs3.h b/include/linux/nfs3.h
index b6539a75edea..1eb8e99390be 100644
--- a/include/linux/nfs3.h
+++ b/include/linux/nfs3.h
@@ -51,7 +51,4 @@ enum nfs3_stable_how {
 	NFS_INVALID_STABLE_HOW = -1
 };
 
-/* Number of 32bit words in post_op_attr */
-#define NFS3_POST_OP_ATTR_WORDS		22
-
 #endif /* _LINUX_NFS3_H */
-- 
2.55.0


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

* [PATCH v1 05/33] NFSD: Replace the nfsd3_createres macro
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (3 preceding siblings ...)
  2026-09-23 13:27 ` [PATCH v1 04/33] NFSD: Replace nfs3.h with nfs3xdr_gen.h Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 06/33] NFSD: Relocate the NFSv3 XDR storage union into nfs3proc.c Chuck Lever
                   ` (27 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey
  Cc: linux-nfs, Chuck Lever

From: Chuck Lever <chuck.lever@oracle.com>

I'm about to use the name "nfsd3_createres" for a C struct,
so let's get rid of the macro to reduce confusion.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
 fs/nfsd/nfs3proc.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 223398ada3e3..200e084d749b 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -916,7 +916,6 @@ nfsd3_proc_commit(struct svc_rqst *rqstp)
 #define nfsd3_fhandleargs		nfsd_fhandle
 #define nfsd3_attrstatres		nfsd3_attrstat
 #define nfsd3_wccstatres		nfsd3_attrstat
-#define nfsd3_createres			nfsd3_diropres
 
 #define ST 1		/* status*/
 #define FH 17		/* filehandle with length */
@@ -1027,7 +1026,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_release = nfs3svc_release_fhandle2,
 		.pc_argsize = sizeof(struct nfsd3_createargs),
 		.pc_argzero = sizeof(struct nfsd3_createargs),
-		.pc_ressize = sizeof(struct nfsd3_createres),
+		.pc_ressize = sizeof(struct nfsd3_diropres),
 		.pc_cachetype = RC_REPLBUFF,
 		.pc_xdrressize = ST+(1+FH+pAT)+WC,
 		.pc_name = "CREATE",
@@ -1039,7 +1038,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_release = nfs3svc_release_fhandle2,
 		.pc_argsize = sizeof(struct nfsd3_mkdirargs),
 		.pc_argzero = sizeof(struct nfsd3_mkdirargs),
-		.pc_ressize = sizeof(struct nfsd3_createres),
+		.pc_ressize = sizeof(struct nfsd3_diropres),
 		.pc_cachetype = RC_REPLBUFF,
 		.pc_xdrressize = ST+(1+FH+pAT)+WC,
 		.pc_name = "MKDIR",
@@ -1051,7 +1050,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_release = nfs3svc_release_fhandle2,
 		.pc_argsize = sizeof(struct nfsd3_symlinkargs),
 		.pc_argzero = sizeof(struct nfsd3_symlinkargs),
-		.pc_ressize = sizeof(struct nfsd3_createres),
+		.pc_ressize = sizeof(struct nfsd3_diropres),
 		.pc_cachetype = RC_REPLBUFF,
 		.pc_xdrressize = ST+(1+FH+pAT)+WC,
 		.pc_name = "SYMLINK",
@@ -1063,7 +1062,7 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_release = nfs3svc_release_fhandle2,
 		.pc_argsize = sizeof(struct nfsd3_mknodargs),
 		.pc_argzero = sizeof(struct nfsd3_mknodargs),
-		.pc_ressize = sizeof(struct nfsd3_createres),
+		.pc_ressize = sizeof(struct nfsd3_diropres),
 		.pc_cachetype = RC_REPLBUFF,
 		.pc_xdrressize = ST+(1+FH+pAT)+WC,
 		.pc_name = "MKNOD",
-- 
2.55.0


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

* [PATCH v1 06/33] NFSD: Relocate the NFSv3 XDR storage union into nfs3proc.c
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (4 preceding siblings ...)
  2026-09-23 13:27 ` [PATCH v1 05/33] NFSD: Replace the nfsd3_createres macro Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 07/33] NFSD: Use xdrgen XDR functions for the NFSv3 NULL procedure Chuck Lever
                   ` (26 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

union nfsd3_xdrstore and the NFS3_SVC_XDRSIZE macro it feeds exist
only to size service result buffers, yet xdr3.h exposes them to
every file that includes the header. This couples the NFSv3 XDR type
layout to translation units that have no stake in a service's buffer
size.

Three svc_version structures drew their vs_xdrsize from the shared
union: nfsd_version3 in nfs3proc.c and the two NFSACL programs,
nfsd_acl_version2 and nfsd_acl_version3. Give each its own storage
union next to the version structure it sizes, listing only that
program's own argument and result types, and compute vs_xdrsize from
sizeof of the local union. NFS3_SVC_XDRSIZE then has no users left
and is removed from xdr3.h.

The svc layer allocates rqstp->rq_resp at sv_xdrsize -- the largest
vs_xdrsize among a program's versions -- and clears it to the
dispatched procedure's pc_ressize, so each union must bound the
largest result its version encodes. The NFSACL programs previously
borrowed the full NFSv3 union; sizing them from their own result
types is both tighter and self-contained. This mirrors the recent
lockd conversion, which confined each NLM version's storage union to
the file that defines the version structure.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs2acl.c  | 15 ++++++++++++++-
 fs/nfsd/nfs3acl.c  | 12 +++++++++++-
 fs/nfsd/nfs3proc.c | 30 +++++++++++++++++++++++++++++-
 fs/nfsd/xdr3.h     | 30 ------------------------------
 4 files changed, 54 insertions(+), 33 deletions(-)

diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
index 13904e2740bf..db0e260f447d 100644
--- a/fs/nfsd/nfs2acl.c
+++ b/fs/nfsd/nfs2acl.c
@@ -452,10 +452,23 @@ static const struct svc_procedure nfsd_acl_procedures2[5] = {
 	},
 };
 
+/*
+ * Storage requirements for XDR arguments and results.
+ */
+union nfsd_acl2_xdrstore {
+	struct nfsd3_getaclargs		getaclargs;
+	struct nfsd3_setaclargs		setaclargs;
+	struct nfsd3_accessargs		accessargs;
+	struct nfsd_fhandle		fhandle;
+	struct nfsd3_getaclres		getaclres;
+	struct nfsd_attrstat		attrstat;
+	struct nfsd3_accessres		accessres;
+};
+
 const struct svc_version nfsd_acl_version2 = {
 	.vs_vers	= 2,
 	.vs_nproc	= ARRAY_SIZE(nfsd_acl_procedures2),
 	.vs_proc	= nfsd_acl_procedures2,
 	.vs_dispatch	= nfsd_dispatch,
-	.vs_xdrsize	= NFS3_SVC_XDRSIZE,
+	.vs_xdrsize	= sizeof(union nfsd_acl2_xdrstore),
 };
diff --git a/fs/nfsd/nfs3acl.c b/fs/nfsd/nfs3acl.c
index 5219ec634587..324cfdd7b262 100644
--- a/fs/nfsd/nfs3acl.c
+++ b/fs/nfsd/nfs3acl.c
@@ -294,11 +294,21 @@ static const struct svc_procedure nfsd_acl_procedures3[3] = {
 	},
 };
 
+/*
+ * Storage requirements for XDR arguments and results.
+ */
+union nfsd_acl3_xdrstore {
+	struct nfsd3_getaclargs		getaclargs;
+	struct nfsd3_setaclargs		setaclargs;
+	struct nfsd3_getaclres		getaclres;
+	struct nfsd3_attrstat		attrstat;
+};
+
 const struct svc_version nfsd_acl_version3 = {
 	.vs_vers	= 3,
 	.vs_nproc	= ARRAY_SIZE(nfsd_acl_procedures3),
 	.vs_proc	= nfsd_acl_procedures3,
 	.vs_dispatch	= nfsd_dispatch,
-	.vs_xdrsize	= NFS3_SVC_XDRSIZE,
+	.vs_xdrsize	= sizeof(union nfsd_acl3_xdrstore),
 };
 
diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 200e084d749b..180601d2d139 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -1184,10 +1184,38 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 	},
 };
 
+/*
+ * Storage requirements for XDR arguments and results.
+ */
+union nfsd3_xdrstore {
+	struct nfsd3_sattrargs		sattrargs;
+	struct nfsd3_diropargs		diropargs;
+	struct nfsd3_readargs		readargs;
+	struct nfsd3_writeargs		writeargs;
+	struct nfsd3_createargs		createargs;
+	struct nfsd3_renameargs		renameargs;
+	struct nfsd3_linkargs		linkargs;
+	struct nfsd3_symlinkargs	symlinkargs;
+	struct nfsd3_readdirargs	readdirargs;
+	struct nfsd3_diropres 		diropres;
+	struct nfsd3_accessres		accessres;
+	struct nfsd3_readlinkres	readlinkres;
+	struct nfsd3_readres		readres;
+	struct nfsd3_writeres		writeres;
+	struct nfsd3_renameres		renameres;
+	struct nfsd3_linkres		linkres;
+	struct nfsd3_readdirres		readdirres;
+	struct nfsd3_fsstatres		fsstatres;
+	struct nfsd3_fsinfores		fsinfores;
+	struct nfsd3_pathconfres	pathconfres;
+	struct nfsd3_commitres		commitres;
+	struct nfsd3_getaclres		getaclres;
+};
+
 const struct svc_version nfsd_version3 = {
 	.vs_vers	= 3,
 	.vs_nproc	= ARRAY_SIZE(nfsd_procedures3),
 	.vs_proc	= nfsd_procedures3,
 	.vs_dispatch	= nfsd_dispatch,
-	.vs_xdrsize	= NFS3_SVC_XDRSIZE,
+	.vs_xdrsize	= sizeof(union nfsd3_xdrstore),
 };
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 60c7fe131e2e..35fb715e7c3f 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -243,36 +243,6 @@ struct nfsd3_fhandle_pair {
 	struct svc_fh		fh2;
 };
 
-/*
- * Storage requirements for XDR arguments and results.
- */
-union nfsd3_xdrstore {
-	struct nfsd3_sattrargs		sattrargs;
-	struct nfsd3_diropargs		diropargs;
-	struct nfsd3_readargs		readargs;
-	struct nfsd3_writeargs		writeargs;
-	struct nfsd3_createargs		createargs;
-	struct nfsd3_renameargs		renameargs;
-	struct nfsd3_linkargs		linkargs;
-	struct nfsd3_symlinkargs	symlinkargs;
-	struct nfsd3_readdirargs	readdirargs;
-	struct nfsd3_diropres 		diropres;
-	struct nfsd3_accessres		accessres;
-	struct nfsd3_readlinkres	readlinkres;
-	struct nfsd3_readres		readres;
-	struct nfsd3_writeres		writeres;
-	struct nfsd3_renameres		renameres;
-	struct nfsd3_linkres		linkres;
-	struct nfsd3_readdirres		readdirres;
-	struct nfsd3_fsstatres		fsstatres;
-	struct nfsd3_fsinfores		fsinfores;
-	struct nfsd3_pathconfres	pathconfres;
-	struct nfsd3_commitres		commitres;
-	struct nfsd3_getaclres		getaclres;
-};
-
-#define NFS3_SVC_XDRSIZE		sizeof(union nfsd3_xdrstore)
-
 bool nfs3svc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_sattrargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-- 
2.55.0


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

* [PATCH v1 07/33] NFSD: Use xdrgen XDR functions for the NFSv3 NULL procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (5 preceding siblings ...)
  2026-09-23 13:27 ` [PATCH v1 06/33] NFSD: Relocate the NFSv3 XDR storage union into nfs3proc.c Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 08/33] NFSD: Use xdrgen XDR functions for NFSv3 GETATTR procedure Chuck Lever
                   ` (25 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Update the NFSPROC3_NULL entry in the nfsd_procedures3 array
to use the generated XDR encoder and decoder functions
(xdrgen_svc_decode_void and xdrgen_svc_encode_void) instead of the
hand-written nfssvc_decode_voidarg and nfssvc_encode_voidres.

Also update pc_xdrressize to 0 since the NULL procedure returns
a void result with no XDR-encoded data.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs3proc.c | 35 +++++++++++++++++++++++------------
 1 file changed, 23 insertions(+), 12 deletions(-)

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 180601d2d139..0c4109bde2bb 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -130,10 +130,21 @@ static __be32 nfsd3_map_status(__be32 status)
 }
 
 /*
- * NULL call.
+ * A full specification of each of the following NFSv3 procedures is
+ * available in RFC 1813 Section 3.3.
  */
-static __be32
-nfsd3_proc_null(struct svc_rqst *rqstp)
+
+/**
+ * nfsd3_proc_null - NULL: Do nothing
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   void NFSPROC3_NULL(void) = 0;
+ */
+static __be32 nfsd3_proc_null(struct svc_rqst *rqstp)
 {
 	return rpc_success;
 }
@@ -925,15 +936,15 @@ nfsd3_proc_commit(struct svc_rqst *rqstp)
 
 static const struct svc_procedure nfsd_procedures3[22] = {
 	[NFSPROC3_NULL] = {
-		.pc_func = nfsd3_proc_null,
-		.pc_decode = nfssvc_decode_voidarg,
-		.pc_encode = nfssvc_encode_voidres,
-		.pc_argsize = sizeof(struct nfsd_voidargs),
-		.pc_argzero = sizeof(struct nfsd_voidargs),
-		.pc_ressize = sizeof(struct nfsd_voidres),
-		.pc_cachetype = RC_NOCACHE,
-		.pc_xdrressize = ST,
-		.pc_name = "NULL",
+		.pc_func	= nfsd3_proc_null,
+		.pc_decode	= xdrgen_svc_decode_void,
+		.pc_encode	= xdrgen_svc_encode_void,
+		.pc_argsize	= XDR_void,
+		.pc_argzero	= 0,
+		.pc_ressize	= 0,
+		.pc_cachetype	= RC_NOCACHE,
+		.pc_xdrressize	= XDR_void,
+		.pc_name	= "NULL",
 	},
 	[NFSPROC3_GETATTR] = {
 		.pc_func = nfsd3_proc_getattr,
-- 
2.55.0


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

* [PATCH v1 08/33] NFSD: Use xdrgen XDR functions for NFSv3 GETATTR procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (6 preceding siblings ...)
  2026-09-23 13:27 ` [PATCH v1 07/33] NFSD: Use xdrgen XDR functions for the NFSv3 NULL procedure Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 09/33] NFSD: Use xdrgen XDR functions for NFSv3 SETATTR procedure Chuck Lever
                   ` (24 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_GETATTR entry in the nfsd_procedures3
array with an entry that dispatches the xdrgen-generated
nfs_svc_decode_GETATTR3args and nfs_svc_encode_GETATTR3res. Wrapper
structures bridge the generated xdrgen types and the legacy svc_fh
and kstat representations the NFSD VFS layer still uses. A few helper
functions are introduced which will be used here and in subsequent
patches.

The pc_argzero field is now set to zero for the NFSv3 GETATTR
procedure. The xdrgen decoders are trusted to initialize all
arguments in the argp->xdrgen field, making the early defensive memset
unnecessary. The remaining argp fields are cleared as needed.

This refactor replaces the use of svcxdr_encode_fattr3(). Because
nfsd3_proc_getattr() now fills in the response attributes, the
fh_getattr() call is made in the proc function rather than in the XDR
result encoder, and the reference to the file handle can be released
directly by nfsd3_proc_getattr(). A separate ->pc_release callback is
thus no longer needed. This makes it straightforward to confirm that
the file handle is properly released during every error flow.

Following the precedent of commit 40ee5dc6af35 ("knfsd: 64 bit ino
support for NFS server"), lease_get_mtime() determines the file's
mtime for GETATTR (and later when filling in post-op attributes),
but is not used for WCC data. This seems odd to me; NFSv4 does not use
lease_get_mtime() at all, and not using it for WCC data seems like an
NFSv3 file's reported mtime values might vary depending on how the file
metadata is queried.

nfs3svc_encode_getattrres() no longer has any callers, so it is
removed.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 Documentation/sunrpc/xdr/nfs3.x    |   1 +
 fs/nfsd/nfs3proc.c                 | 161 ++++++++++++++++++++++++-----
 fs/nfsd/nfs3xdr.c                  |  19 ----
 fs/nfsd/nfs3xdr_gen.c              |  94 ++++++++---------
 fs/nfsd/nfs3xdr_gen.h              |   2 +-
 fs/nfsd/xdr3.h                     |   1 -
 include/linux/sunrpc/xdrgen/nfs3.h |   4 +-
 7 files changed, 187 insertions(+), 95 deletions(-)

diff --git a/Documentation/sunrpc/xdr/nfs3.x b/Documentation/sunrpc/xdr/nfs3.x
index 9feabfda94e7..1ef99ec54103 100644
--- a/Documentation/sunrpc/xdr/nfs3.x
+++ b/Documentation/sunrpc/xdr/nfs3.x
@@ -95,6 +95,7 @@ enum nfsstat3 {
 	NFS3ERR_BADTYPE		= 10007,
 	NFS3ERR_JUKEBOX		= 10008
 };
+pragma big_endian nfsstat3;
 
 enum ftype3 {
 	NF3REG		= 1,
diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 0c4109bde2bb..1facaccf408b 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -9,7 +9,10 @@
 #include <linux/ext2_fs.h>
 #include <linux/magic.h>
 #include <linux/namei.h>
+#include <linux/sunrpc/svc.h>
 
+#include "auth.h"
+#include "nfsd.h"
 #include "cache.h"
 #include "xdr3.h"
 #include "vfs.h"
@@ -24,6 +27,26 @@
 #define NFS3_FSF_DEFAULT		0x001b
 #define NFS3_FSF_BILLYBOY		0x0018
 
+/*
+ * Wrapper structures combine xdrgen types with legacy structures.
+ * The xdrgen field must be first so the structure can be cast
+ * to its XDR type for the RPC dispatch layer.
+ */
+
+struct GETATTR3args_wrapper {
+	struct GETATTR3args	xdrgen;
+	struct svc_fh		fh;
+};
+
+static_assert(offsetof(struct GETATTR3args_wrapper, xdrgen) == 0);
+
+struct GETATTR3res_wrapper {
+	struct GETATTR3res	xdrgen;
+	struct kstat		stat;
+};
+
+static_assert(offsetof(struct GETATTR3res_wrapper, xdrgen) == 0);
+
 static int	nfs3_ftypes[] = {
 	0,			/* NF3NON */
 	S_IFREG,		/* NF3REG */
@@ -129,6 +152,77 @@ static __be32 nfsd3_map_status(__be32 status)
 	return status;
 }
 
+/* XDR decoding has already checked that the FH length is valid */
+static __always_inline void
+nfsd3_fh3_to_svc_fh(struct svc_fh *fhp, const struct nfs_fh3 *fh3)
+{
+	fh_init(fhp, NFS3_FHSIZE);
+	fhp->fh_handle.fh_size = fh3->data.len;
+	memcpy(&fhp->fh_handle.fh_raw, fh3->data.data, fh3->data.len);
+}
+
+static __always_inline void
+nfsd3_timespec64_to_nfstime3(struct nfstime3 *dst,
+			     const struct timespec64 *src)
+{
+	dst->seconds = src->tv_sec;
+	dst->nseconds = src->tv_nsec;
+}
+
+static u32
+nfsd3_mode_to_ftype3(umode_t mode)
+{
+	switch (mode & S_IFMT) {
+	case S_IFREG:  return NF3REG;
+	case S_IFDIR:  return NF3DIR;
+	case S_IFBLK:  return NF3BLK;
+	case S_IFCHR:  return NF3CHR;
+	case S_IFLNK:  return NF3LNK;
+	case S_IFSOCK: return NF3SOCK;
+	case S_IFIFO:  return NF3FIFO;
+	}
+
+	pr_warn_once("NFSD: unexpected file type: mode=0%o\n", mode);
+	return NF3REG;
+}
+
+static void
+nfsd3_stat_to_fattr3(struct svc_rqst *rqstp, struct fattr3 *fattr,
+		     const struct kstat *stat, const struct svc_fh *fhp)
+{
+	struct user_namespace *userns = nfsd_user_namespace(rqstp);
+
+	fattr->type = nfsd3_mode_to_ftype3(stat->mode);
+	fattr->mode = stat->mode & S_IALLUGO;
+	fattr->nlink = stat->nlink;
+	fattr->uid = from_kuid_munged(userns, stat->uid);
+	fattr->gid = from_kgid_munged(userns, stat->gid);
+	if (S_ISLNK(stat->mode) && stat->size > NFS3_MAXPATHLEN)
+		fattr->size = NFS3_MAXPATHLEN;
+	else
+		fattr->size = stat->size;
+	fattr->used = stat->blocks << 9;
+	fattr->rdev.specdata1 = MAJOR(stat->rdev);
+	fattr->rdev.specdata2 = MINOR(stat->rdev);
+
+	switch (fsid_source(fhp)) {
+	case FSIDSOURCE_FSID:
+		fattr->fsid = (u64)fhp->fh_export->ex_fsid;
+		break;
+	case FSIDSOURCE_UUID:
+		fattr->fsid = ((u64 *)fhp->fh_export->ex_uuid)[0];
+		fattr->fsid ^= ((u64 *)fhp->fh_export->ex_uuid)[1];
+		break;
+	default:
+		fattr->fsid = (u64)huge_encode_dev(fhp->fh_dentry->d_sb->s_dev);
+	}
+	fattr->fileid = stat->ino;
+
+	nfsd3_timespec64_to_nfstime3(&fattr->atime, &stat->atime);
+	nfsd3_timespec64_to_nfstime3(&fattr->mtime, &stat->mtime);
+	nfsd3_timespec64_to_nfstime3(&fattr->ctime, &stat->ctime);
+}
+
 /*
  * A full specification of each of the following NFSv3 procedures is
  * available in RFC 1813 Section 3.3.
@@ -149,26 +243,42 @@ static __be32 nfsd3_proc_null(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-/*
- * Get a file's attributes
+/**
+ * nfsd3_proc_getattr - GETATTR: Get file attributes
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   GETATTR3res NFSPROC3_GETATTR(GETATTR3args) = 1;
  */
-static __be32
-nfsd3_proc_getattr(struct svc_rqst *rqstp)
+static __be32 nfsd3_proc_getattr(struct svc_rqst *rqstp)
 {
-	struct nfsd_fhandle *argp = rqstp->rq_argp;
-	struct nfsd3_attrstat *resp = rqstp->rq_resp;
+	struct GETATTR3args_wrapper *argp = rqstp->rq_argp;
+	struct GETATTR3res_wrapper *resp = rqstp->rq_resp;
+	struct kstat *statp = &resp->stat;
+	struct svc_fh *fhp = &argp->fh;
 
-	trace_nfsd_vfs_getattr(rqstp, &argp->fh);
-
-	fh_copy(&resp->fh, &argp->fh);
-	resp->status = fh_verify(rqstp, &resp->fh, 0,
-				 NFSD_MAY_NOP | NFSD_MAY_BYPASS_GSS_ON_ROOT);
-	if (resp->status != nfs_ok)
+	nfsd3_fh3_to_svc_fh(fhp, &argp->xdrgen.object);
+	trace_nfsd_vfs_getattr(rqstp, fhp);
+	resp->xdrgen.status = fh_verify(rqstp, fhp, 0, NFSD_MAY_NOP |
+					NFSD_MAY_BYPASS_GSS_ON_ROOT);
+	if (resp->xdrgen.status != nfs_ok)
 		goto out;
 
-	resp->status = fh_getattr(&resp->fh, &resp->stat);
+	resp->xdrgen.status = fh_getattr(fhp, statp);
+
 out:
-	resp->status = nfsd3_map_status(resp->status);
+	if (resp->xdrgen.status == nfs_ok) {
+		lease_get_mtime(d_inode(fhp->fh_dentry), &statp->mtime);
+		nfsd3_stat_to_fattr3(rqstp, &resp->xdrgen.u.resok.obj_attributes,
+				     statp, fhp);
+	} else {
+		resp->xdrgen.status = nfsd3_map_status(resp->xdrgen.status);
+	}
+
+	fh_put(fhp);
 	return rpc_success;
 }
 
@@ -920,12 +1030,10 @@ nfsd3_proc_commit(struct svc_rqst *rqstp)
  * NFSv3 Server procedures.
  * Only the results of non-idempotent operations are cached.
  */
-#define nfs3svc_encode_attrstatres	nfs3svc_encode_attrstat
 #define nfs3svc_encode_wccstatres	nfs3svc_encode_wccstat
 #define nfsd3_mkdirargs			nfsd3_createargs
 #define nfsd3_readdirplusargs		nfsd3_readdirargs
 #define nfsd3_fhandleargs		nfsd_fhandle
-#define nfsd3_attrstatres		nfsd3_attrstat
 #define nfsd3_wccstatres		nfsd3_attrstat
 
 #define ST 1		/* status*/
@@ -947,16 +1055,15 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "NULL",
 	},
 	[NFSPROC3_GETATTR] = {
-		.pc_func = nfsd3_proc_getattr,
-		.pc_decode = nfs3svc_decode_fhandleargs,
-		.pc_encode = nfs3svc_encode_getattrres,
-		.pc_release = nfs3svc_release_fhandle,
-		.pc_argsize = sizeof(struct nfsd_fhandle),
-		.pc_argzero = sizeof(struct nfsd_fhandle),
-		.pc_ressize = sizeof(struct nfsd3_attrstatres),
-		.pc_cachetype = RC_NOCACHE,
-		.pc_xdrressize = ST+AT,
-		.pc_name = "GETATTR",
+		.pc_func	= nfsd3_proc_getattr,
+		.pc_decode	= nfs_svc_decode_GETATTR3args,
+		.pc_encode	= nfs_svc_encode_GETATTR3res,
+		.pc_argsize	= sizeof(struct GETATTR3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct GETATTR3res_wrapper),
+		.pc_cachetype	= RC_NOCACHE,
+		.pc_xdrressize	= NFS3_GETATTR3res_sz,
+		.pc_name	= "GETATTR",
 	},
 	[NFSPROC3_SETATTR] = {
 		.pc_func = nfsd3_proc_setattr,
@@ -1199,6 +1306,8 @@ static const struct svc_procedure nfsd_procedures3[22] = {
  * Storage requirements for XDR arguments and results.
  */
 union nfsd3_xdrstore {
+	struct GETATTR3args_wrapper	getattrargs;
+	struct GETATTR3res_wrapper	getattrres;
 	struct nfsd3_sattrargs		sattrargs;
 	struct nfsd3_diropargs		diropargs;
 	struct nfsd3_readargs		readargs;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index d163b51684d5..5802208f96b7 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -738,25 +738,6 @@ nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
  * XDR encode functions
  */
 
-/* GETATTR */
-bool
-nfs3svc_encode_getattrres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_attrstat *resp = rqstp->rq_resp;
-
-	if (!svcxdr_encode_nfsstat3(xdr, resp->status))
-		return false;
-	switch (resp->status) {
-	case nfs_ok:
-		lease_get_mtime(d_inode(resp->fh.fh_dentry), &resp->stat.mtime);
-		if (!svcxdr_encode_fattr3(rqstp, xdr, &resp->fh, &resp->stat))
-			return false;
-		break;
-	}
-
-	return true;
-}
-
 /* SETATTR, REMOVE, RMDIR */
 bool
 nfs3svc_encode_wccstat(struct svc_rqst *rqstp, struct xdr_stream *xdr)
diff --git a/fs/nfsd/nfs3xdr_gen.c b/fs/nfsd/nfs3xdr_gen.c
index 48b382ccfc81..e21ee0053129 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: Fri Jul 10 23:20:55 2026
+// XDR specification modification time: Fri Jul 10 23:21:45 2026
 
 #include <linux/sunrpc/svc.h>
 
@@ -112,10 +112,12 @@ xdrgen_decode_count3(struct xdr_stream *xdr, count3 *ptr)
 static bool __maybe_unused
 xdrgen_decode_nfsstat3(struct xdr_stream *xdr, nfsstat3 *ptr)
 {
+	__be32 raw;
 	u32 val;
 
-	if (xdr_stream_decode_u32(xdr, &val) < 0)
+	if (xdr_stream_decode_be32(xdr, &raw) < 0)
 		return false;
+	val = be32_to_cpu(raw);
 	/* Compiler may optimize to a range check for dense enums */
 	switch (val) {
 	case NFS3_OK:
@@ -151,7 +153,7 @@ xdrgen_decode_nfsstat3(struct xdr_stream *xdr, nfsstat3 *ptr)
 	default:
 		return false;
 	}
-	*ptr = val;
+	*ptr = raw;
 	return true;
 }
 
@@ -447,7 +449,7 @@ xdrgen_decode_GETATTR3res(struct xdr_stream *xdr, struct GETATTR3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_GETATTR3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -503,7 +505,7 @@ xdrgen_decode_SETATTR3res(struct xdr_stream *xdr, struct SETATTR3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_SETATTR3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -548,7 +550,7 @@ xdrgen_decode_LOOKUP3res(struct xdr_stream *xdr, struct LOOKUP3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_LOOKUP3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -593,7 +595,7 @@ xdrgen_decode_ACCESS3res(struct xdr_stream *xdr, struct ACCESS3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_ACCESS3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -636,7 +638,7 @@ xdrgen_decode_READLINK3res(struct xdr_stream *xdr, struct READLINK3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_READLINK3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -687,7 +689,7 @@ xdrgen_decode_READ3res(struct xdr_stream *xdr, struct READ3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_READ3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -762,7 +764,7 @@ xdrgen_decode_WRITE3res(struct xdr_stream *xdr, struct WRITE3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_WRITE3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -850,7 +852,7 @@ xdrgen_decode_CREATE3res(struct xdr_stream *xdr, struct CREATE3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_CREATE3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -897,7 +899,7 @@ xdrgen_decode_MKDIR3res(struct xdr_stream *xdr, struct MKDIR3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_MKDIR3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -954,7 +956,7 @@ xdrgen_decode_SYMLINK3res(struct xdr_stream *xdr, struct SYMLINK3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_SYMLINK3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -1033,7 +1035,7 @@ xdrgen_decode_MKNOD3res(struct xdr_stream *xdr, struct MKNOD3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_MKNOD3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -1074,7 +1076,7 @@ xdrgen_decode_REMOVE3res(struct xdr_stream *xdr, struct REMOVE3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_REMOVE3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -1115,7 +1117,7 @@ xdrgen_decode_RMDIR3res(struct xdr_stream *xdr, struct RMDIR3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_RMDIR3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -1162,7 +1164,7 @@ xdrgen_decode_RENAME3res(struct xdr_stream *xdr, struct RENAME3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_RENAME3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -1209,7 +1211,7 @@ xdrgen_decode_LINK3res(struct xdr_stream *xdr, struct LINK3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_LINK3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -1289,7 +1291,7 @@ xdrgen_decode_READDIR3res(struct xdr_stream *xdr, struct READDIR3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_READDIR3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -1375,7 +1377,7 @@ xdrgen_decode_READDIRPLUS3res(struct xdr_stream *xdr, struct READDIRPLUS3res *pt
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_READDIRPLUS3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -1430,7 +1432,7 @@ xdrgen_decode_FSSTAT3res(struct xdr_stream *xdr, struct FSSTAT3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_FSSTAT3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -1491,7 +1493,7 @@ xdrgen_decode_FSINFO3res(struct xdr_stream *xdr, struct FSINFO3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_FSINFO3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -1544,7 +1546,7 @@ xdrgen_decode_PATHCONF3res(struct xdr_stream *xdr, struct PATHCONF3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_PATHCONF3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -1591,7 +1593,7 @@ xdrgen_decode_COMMIT3res(struct xdr_stream *xdr, struct COMMIT3res *ptr)
 	if (!xdrgen_decode_nfsstat3(xdr, &ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_decode_COMMIT3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -2043,7 +2045,7 @@ xdrgen_encode_count3(struct xdr_stream *xdr, const count3 value)
 static bool __maybe_unused
 xdrgen_encode_nfsstat3(struct xdr_stream *xdr, nfsstat3 value)
 {
-	return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
+	return xdr_stream_encode_be32(xdr, value) == XDR_UNIT;
 }
 
 static bool __maybe_unused
@@ -2308,7 +2310,7 @@ xdrgen_encode_GETATTR3res(struct xdr_stream *xdr, const struct GETATTR3res *ptr)
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_GETATTR3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -2364,7 +2366,7 @@ xdrgen_encode_SETATTR3res(struct xdr_stream *xdr, const struct SETATTR3res *ptr)
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_SETATTR3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -2409,7 +2411,7 @@ xdrgen_encode_LOOKUP3res(struct xdr_stream *xdr, const struct LOOKUP3res *ptr)
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_LOOKUP3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -2454,7 +2456,7 @@ xdrgen_encode_ACCESS3res(struct xdr_stream *xdr, const struct ACCESS3res *ptr)
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_ACCESS3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -2497,7 +2499,7 @@ xdrgen_encode_READLINK3res(struct xdr_stream *xdr, const struct READLINK3res *pt
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_READLINK3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -2548,7 +2550,7 @@ xdrgen_encode_READ3res(struct xdr_stream *xdr, const struct READ3res *ptr)
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_READ3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -2609,7 +2611,7 @@ xdrgen_encode_WRITE3res(struct xdr_stream *xdr, const struct WRITE3res *ptr)
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_WRITE3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -2683,7 +2685,7 @@ xdrgen_encode_CREATE3res(struct xdr_stream *xdr, const struct CREATE3res *ptr)
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_CREATE3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -2730,7 +2732,7 @@ xdrgen_encode_MKDIR3res(struct xdr_stream *xdr, const struct MKDIR3res *ptr)
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_MKDIR3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -2787,7 +2789,7 @@ xdrgen_encode_SYMLINK3res(struct xdr_stream *xdr, const struct SYMLINK3res *ptr)
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_SYMLINK3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -2866,7 +2868,7 @@ xdrgen_encode_MKNOD3res(struct xdr_stream *xdr, const struct MKNOD3res *ptr)
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_MKNOD3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -2907,7 +2909,7 @@ xdrgen_encode_REMOVE3res(struct xdr_stream *xdr, const struct REMOVE3res *ptr)
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_REMOVE3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -2948,7 +2950,7 @@ xdrgen_encode_RMDIR3res(struct xdr_stream *xdr, const struct RMDIR3res *ptr)
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_RMDIR3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -2995,7 +2997,7 @@ xdrgen_encode_RENAME3res(struct xdr_stream *xdr, const struct RENAME3res *ptr)
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_RENAME3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -3042,7 +3044,7 @@ xdrgen_encode_LINK3res(struct xdr_stream *xdr, const struct LINK3res *ptr)
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_LINK3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -3120,7 +3122,7 @@ xdrgen_encode_READDIR3res(struct xdr_stream *xdr, const struct READDIR3res *ptr)
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_READDIR3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -3204,7 +3206,7 @@ xdrgen_encode_READDIRPLUS3res(struct xdr_stream *xdr, const struct READDIRPLUS3r
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_READDIRPLUS3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -3259,7 +3261,7 @@ xdrgen_encode_FSSTAT3res(struct xdr_stream *xdr, const struct FSSTAT3res *ptr)
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_FSSTAT3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -3320,7 +3322,7 @@ xdrgen_encode_FSINFO3res(struct xdr_stream *xdr, const struct FSINFO3res *ptr)
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_FSINFO3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -3373,7 +3375,7 @@ xdrgen_encode_PATHCONF3res(struct xdr_stream *xdr, const struct PATHCONF3res *pt
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_PATHCONF3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
@@ -3420,7 +3422,7 @@ xdrgen_encode_COMMIT3res(struct xdr_stream *xdr, const struct COMMIT3res *ptr)
 	if (!xdrgen_encode_nfsstat3(xdr, ptr->status))
 		return false;
 	switch (ptr->status) {
-	case NFS3_OK:
+	case __constant_cpu_to_be32(NFS3_OK):
 		if (!xdrgen_encode_COMMIT3resok(xdr, &ptr->u.resok))
 			return false;
 		break;
diff --git a/fs/nfsd/nfs3xdr_gen.h b/fs/nfsd/nfs3xdr_gen.h
index e2fbb583da92..58499ca28c43 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: Fri Jul 10 23:20:55 2026 */
+/* XDR specification modification time: Fri Jul 10 23:21:45 2026 */
 
 #ifndef _LINUX_XDRGEN_NFS3_DECL_H
 #define _LINUX_XDRGEN_NFS3_DECL_H
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 35fb715e7c3f..2dcd36160ced 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -259,7 +259,6 @@ bool nfs3svc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_readdirplusargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
-bool nfs3svc_encode_getattrres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_wccstat(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_lookupres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
diff --git a/include/linux/sunrpc/xdrgen/nfs3.h b/include/linux/sunrpc/xdrgen/nfs3.h
index 3afc4ffb6daa..785ebff351ce 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: Fri Jul 10 23:20:55 2026 */
+/* XDR specification modification time: Fri Jul 10 23:21:45 2026 */
 
 #ifndef _LINUX_XDRGEN_NFS3_DEF_H
 #define _LINUX_XDRGEN_NFS3_DEF_H
@@ -83,7 +83,7 @@ enum nfsstat3 {
 	NFS3ERR_JUKEBOX = 10008,
 };
 
-typedef enum nfsstat3 nfsstat3;
+typedef __be32 nfsstat3;
 
 enum ftype3 {
 	NF3REG = 1,
-- 
2.55.0


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

* [PATCH v1 09/33] NFSD: Use xdrgen XDR functions for NFSv3 SETATTR procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (7 preceding siblings ...)
  2026-09-23 13:27 ` [PATCH v1 08/33] NFSD: Use xdrgen XDR functions for NFSv3 GETATTR procedure Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 10/33] NFSD: Use xdrgen XDR functions for the NFSv3 LOOKUP procedure Chuck Lever
                   ` (23 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_SETATTR entry in the nfsd_procedures3 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_SETATTR3args and nfs_svc_encode_SETATTR3res. A
wrapper structure bridges the generated xdrgen argument type and
the legacy svc_fh and iattr representations the NFSD VFS layer
still uses; the result uses the generated SETATTR3res type
directly. The new nfsd3_sattr3_to_iattr() and
nfsd3_nfstime3_to_timespec64() helpers translate the decoded
sattr3 into the iattr that nfsd_setattr() consumes, and
nfsd3_fill_wcc_data() assembles the weak cache consistency data
that both the success and failure results carry.

When an operation leaves no post-op attributes saved,
svcxdr_encode_wcc_data() omits the pre-op attributes and fetches fresh
post-op attributes instead. nfsd3_fill_wcc_data() keeps that behavior
through a new nfsd3_fill_post_op_attr() helper, which later procedures
also use to fill in post-op attributes.

The pc_argzero field is now set to zero for the NFSv3 SETATTR
procedure. The xdrgen decoders are trusted to initialize all
arguments in the argp->xdrgen field, making the early defensive
memset unnecessary. The remaining argp fields are cleared as
needed.

Because nfsd3_proc_setattr() now assembles the WCC data, that
work moves out of the XDR result encoder, and the reference to
the file handle can be released directly by nfsd3_proc_setattr().
A separate ->pc_release callback is thus no longer needed. This
makes it straightforward to confirm that the file handle is
properly released during every error flow.

nfs3svc_decode_sattrargs() no longer has any callers, so it and
its svcxdr_decode_sattrguard3() helper are removed.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs3proc.c | 211 ++++++++++++++++++++++++++++++++++++++-------
 fs/nfsd/nfs3xdr.c  |  27 ------
 fs/nfsd/xdr3.h     |   8 --
 3 files changed, 179 insertions(+), 67 deletions(-)

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 1facaccf408b..815722afde9a 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -47,6 +47,15 @@ struct GETATTR3res_wrapper {
 
 static_assert(offsetof(struct GETATTR3res_wrapper, xdrgen) == 0);
 
+struct SETATTR3args_wrapper {
+	struct SETATTR3args	xdrgen;
+	struct svc_fh		fh;
+	struct iattr		iattrs;
+	struct timespec64	guard;
+};
+
+static_assert(offsetof(struct SETATTR3args_wrapper, xdrgen) == 0);
+
 static int	nfs3_ftypes[] = {
 	0,			/* NF3NON */
 	S_IFREG,		/* NF3REG */
@@ -186,6 +195,14 @@ nfsd3_mode_to_ftype3(umode_t mode)
 	return NF3REG;
 }
 
+static __always_inline void
+nfsd3_nfstime3_to_timespec64(struct timespec64 *dst,
+			     const struct nfstime3 *src)
+{
+	dst->tv_sec = src->seconds;
+	dst->tv_nsec = src->nseconds;
+}
+
 static void
 nfsd3_stat_to_fattr3(struct svc_rqst *rqstp, struct fattr3 *fattr,
 		     const struct kstat *stat, const struct svc_fh *fhp)
@@ -223,6 +240,121 @@ 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)
+{
+	/* Pre-op attributes are of no use to the client without post-op ones */
+	if (!fhp->fh_post_saved) {
+		wcc->before.attributes_follow = false;
+		nfsd3_fill_post_op_attr(rqstp, &wcc->after, fhp);
+		return;
+	}
+
+	wcc->before.attributes_follow = fhp->fh_pre_saved;
+	if (fhp->fh_pre_saved) {
+		struct wcc_attr *wattr = &wcc->before.u.attributes;
+
+		wattr->size = fhp->fh_pre_size;
+		nfsd3_timespec64_to_nfstime3(&wattr->mtime, &fhp->fh_pre_mtime);
+		nfsd3_timespec64_to_nfstime3(&wattr->ctime, &fhp->fh_pre_ctime);
+	}
+
+	wcc->after.attributes_follow = true;
+	nfsd3_stat_to_fattr3(rqstp, &wcc->after.u.attributes,
+			     &fhp->fh_post_attr, fhp);
+}
+
+static void
+nfsd3_sattr3_to_iattr(struct svc_rqst *rqstp, struct iattr *iap,
+		      const struct sattr3 *sattr)
+{
+	/* trace_nfsd_vfs_setattr() records fields ia_valid leaves unset */
+	memset(iap, 0, sizeof(*iap));
+
+	if (sattr->mode.set_it) {
+		iap->ia_valid |= ATTR_MODE;
+		iap->ia_mode = sattr->mode.u.mode;
+	}
+	if (sattr->uid.set_it) {
+		iap->ia_uid = make_kuid(nfsd_user_namespace(rqstp),
+					sattr->uid.u.uid);
+		if (uid_valid(iap->ia_uid))
+			iap->ia_valid |= ATTR_UID;
+	}
+	if (sattr->gid.set_it) {
+		iap->ia_gid = make_kgid(nfsd_user_namespace(rqstp),
+					sattr->gid.u.gid);
+		if (gid_valid(iap->ia_gid))
+			iap->ia_valid |= ATTR_GID;
+	}
+	if (sattr->size.set_it) {
+		iap->ia_valid |= ATTR_SIZE;
+		iap->ia_size = sattr->size.u.size;
+	}
+	switch (sattr->atime.set_it) {
+	case DONT_CHANGE:
+		break;
+	case SET_TO_SERVER_TIME:
+		iap->ia_valid |= ATTR_ATIME;
+		break;
+	case SET_TO_CLIENT_TIME:
+		nfsd3_nfstime3_to_timespec64(&iap->ia_atime,
+					     &sattr->atime.u.atime);
+		iap->ia_valid |= ATTR_ATIME | ATTR_ATIME_SET;
+		break;
+	}
+	switch (sattr->mtime.set_it) {
+	case DONT_CHANGE:
+		break;
+	case SET_TO_SERVER_TIME:
+		iap->ia_valid |= ATTR_MTIME;
+		break;
+	case SET_TO_CLIENT_TIME:
+		nfsd3_nfstime3_to_timespec64(&iap->ia_mtime,
+					     &sattr->mtime.u.mtime);
+		iap->ia_valid |= ATTR_MTIME | ATTR_MTIME_SET;
+		break;
+	}
+}
+
+/*
+ * struct kstat is pretty huge. To reduce stack utilization, reuse @fhp's
+ * fh_post_attr field as the stat buffer passed to fh_getattr.
+ *
+ * This is safe to do as long as proc functions that need both WCC and
+ * post_op_attrs invoke nfsd3_fill_wcc_data() before invoking
+ * nfsd3_fill_post_op_attr() on the same fhp argument.
+ */
+static void
+nfsd3_fill_post_op_attr(struct svc_rqst *rqstp, struct post_op_attr *attr,
+			struct svc_fh *fhp)
+{
+	struct kstat *statp = &fhp->fh_post_attr;
+	struct dentry *dentry = fhp->fh_dentry;
+
+	/*
+	 * The inode may be NULL if the call failed because of a stale
+	 * file handle. In this case, no attributes are returned.
+	 */
+	if (fhp->fh_no_wcc || !dentry || !d_really_is_positive(dentry))
+		goto no_post_op_attrs;
+	if (fh_getattr(fhp, statp) != nfs_ok)
+		goto no_post_op_attrs;
+
+	attr->attributes_follow = true;
+	lease_get_mtime(d_inode(dentry), &statp->mtime);
+	nfsd3_stat_to_fattr3(rqstp, &attr->u.attributes, statp, fhp);
+	return;
+
+no_post_op_attrs:
+	attr->attributes_follow = false;
+}
+
 /*
  * A full specification of each of the following NFSv3 procedures is
  * available in RFC 1813 Section 3.3.
@@ -282,32 +414,47 @@ static __be32 nfsd3_proc_getattr(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-/*
- * Set a file's attributes
+/**
+ * nfsd3_proc_setattr - SETATTR: Set file attributes
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   SETATTR3res NFSPROC3_SETATTR(SETATTR3args) = 2;
  */
-static __be32
-nfsd3_proc_setattr(struct svc_rqst *rqstp)
+static __be32 nfsd3_proc_setattr(struct svc_rqst *rqstp)
 {
-	struct nfsd3_sattrargs *argp = rqstp->rq_argp;
-	struct nfsd3_attrstat *resp = rqstp->rq_resp;
-	struct nfsd_attrs attrs = {
-		.na_iattr	= &argp->attrs,
-	};
+	struct SETATTR3args_wrapper *argp = rqstp->rq_argp;
+	struct SETATTR3res *resp = rqstp->rq_resp;
 	const struct timespec64 *guardtime = NULL;
+	struct svc_fh *fhp = &argp->fh;
+	struct nfsd_attrs nattrs = {
+		.na_iattr	= &argp->iattrs,
+	};
 
-	dprintk("nfsd: SETATTR(3)  %s\n",
-				SVCFH_fmt(&argp->fh));
-
-	fh_copy(&resp->fh, &argp->fh);
-	if (!nfsd3_time_in_range(&argp->attrs)) {
-		resp->status = nfserr_inval;
-		goto out;
+	nfsd3_fh3_to_svc_fh(fhp, &argp->xdrgen.object);
+	nfsd3_sattr3_to_iattr(rqstp, &argp->iattrs, &argp->xdrgen.new_attributes);
+	if (argp->xdrgen.guard.check) {
+		nfsd3_nfstime3_to_timespec64(&argp->guard,
+					     &argp->xdrgen.guard.u.obj_ctime);
+		guardtime = &argp->guard;
 	}
-	if (argp->check_guard)
-		guardtime = &argp->guardtime;
-	resp->status = nfsd_setattr(rqstp, &resp->fh, &attrs, guardtime);
-out:
-	resp->status = nfsd3_map_status(resp->status);
+
+	if (nfsd3_time_in_range(&argp->iattrs))
+		resp->status = nfsd_setattr(rqstp, fhp, &nattrs, guardtime);
+	else
+		resp->status = nfserr_inval;
+
+	if (resp->status == nfs_ok) {
+		nfsd3_fill_wcc_data(rqstp, &resp->u.resok.obj_wcc, fhp);
+	} else {
+		resp->status = nfsd3_map_status(resp->status);
+		nfsd3_fill_wcc_data(rqstp, &resp->u.resfail.obj_wcc, fhp);
+	}
+
+	fh_put(fhp);
 	return rpc_success;
 }
 
@@ -1066,16 +1213,15 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "GETATTR",
 	},
 	[NFSPROC3_SETATTR] = {
-		.pc_func = nfsd3_proc_setattr,
-		.pc_decode = nfs3svc_decode_sattrargs,
-		.pc_encode = nfs3svc_encode_wccstatres,
-		.pc_release = nfs3svc_release_fhandle,
-		.pc_argsize = sizeof(struct nfsd3_sattrargs),
-		.pc_argzero = sizeof(struct nfsd3_sattrargs),
-		.pc_ressize = sizeof(struct nfsd3_wccstatres),
-		.pc_cachetype = RC_REPLBUFF,
-		.pc_xdrressize = ST+WC,
-		.pc_name = "SETATTR",
+		.pc_func	= nfsd3_proc_setattr,
+		.pc_decode	= nfs_svc_decode_SETATTR3args,
+		.pc_encode	= nfs_svc_encode_SETATTR3res,
+		.pc_argsize	= sizeof(struct SETATTR3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct SETATTR3res),
+		.pc_cachetype	= RC_REPLBUFF,
+		.pc_xdrressize	= NFS3_SETATTR3res_sz,
+		.pc_name	= "SETATTR",
 	},
 	[NFSPROC3_LOOKUP] = {
 		.pc_func = nfsd3_proc_lookup,
@@ -1308,7 +1454,8 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 union nfsd3_xdrstore {
 	struct GETATTR3args_wrapper	getattrargs;
 	struct GETATTR3res_wrapper	getattrres;
-	struct nfsd3_sattrargs		sattrargs;
+	struct SETATTR3args_wrapper	setattrargs;
+	struct SETATTR3res		setattrres;
 	struct nfsd3_diropargs		diropargs;
 	struct nfsd3_readargs		readargs;
 	struct nfsd3_writeargs		writeargs;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index 5802208f96b7..eb5f3148a0ab 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -295,23 +295,6 @@ svcxdr_decode_sattr3(struct svc_rqst *rqstp, struct xdr_stream *xdr,
 	return true;
 }
 
-static bool
-svcxdr_decode_sattrguard3(struct xdr_stream *xdr, struct nfsd3_sattrargs *args)
-{
-	u32 check;
-
-	if (xdr_stream_decode_bool(xdr, &check) < 0)
-		return false;
-	if (check) {
-		if (!svcxdr_decode_nfstime3(xdr, &args->guardtime))
-			return false;
-		args->check_guard = 1;
-	} else
-		args->check_guard = 0;
-
-	return true;
-}
-
 static bool
 svcxdr_decode_specdata3(struct xdr_stream *xdr, struct nfsd3_mknodargs *args)
 {
@@ -499,16 +482,6 @@ nfs3svc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 	return svcxdr_decode_nfs_fh3(xdr, &args->fh);
 }
 
-bool
-nfs3svc_decode_sattrargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_sattrargs *args = rqstp->rq_argp;
-
-	return svcxdr_decode_nfs_fh3(xdr, &args->fh) &&
-		svcxdr_decode_sattr3(rqstp, xdr, &args->attrs) &&
-		svcxdr_decode_sattrguard3(xdr, args);
-}
-
 bool
 nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 {
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 2dcd36160ced..354cff178b75 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -18,13 +18,6 @@
 #define NFS3_MAXNAMLEN			NAME_MAX
 #define NFS3_MAXPATHLEN			PATH_MAX
 
-struct nfsd3_sattrargs {
-	struct svc_fh		fh;
-	struct iattr		attrs;
-	int			check_guard;
-	struct timespec64	guardtime;
-};
-
 struct nfsd3_diropargs {
 	struct svc_fh		fh;
 	char *			name;
@@ -244,7 +237,6 @@ struct nfsd3_fhandle_pair {
 };
 
 bool nfs3svc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_decode_sattrargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_accessargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_readargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-- 
2.55.0


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

* [PATCH v1 10/33] NFSD: Use xdrgen XDR functions for the NFSv3 LOOKUP procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (8 preceding siblings ...)
  2026-09-23 13:27 ` [PATCH v1 09/33] NFSD: Use xdrgen XDR functions for NFSv3 SETATTR procedure Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 11/33] NFSD: Use xdrgen XDR functions for NFSv3 ACCESS procedure Chuck Lever
                   ` (22 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_LOOKUP entry in the nfsd_procedures3 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_LOOKUP3args and nfs_svc_encode_LOOKUP3res. Wrapper
structures bridge the generated xdrgen types and the legacy svc_fh
representation the NFSD VFS layer still uses.

The pc_argzero field is now set to zero for the NFSv3 LOOKUP
procedure. The xdrgen decoders are trusted to initialize all
arguments in the argp->xdrgen field, making the early defensive
memset unnecessary. The remaining argp fields are cleared as
needed.

nfs3svc_decode_diropargs() rejects an empty name, a name longer
than NFS3_MAXNAMLEN, and a name containing '/' or NUL. The
generated filename3 decoder checks none of these. Introduce
nfsd3_check_filename() so that nfsd3_proc_lookup() applies the same
checks before it calls nfsd_lookup(). After the change, a name that
fails them draws NFS3ERR_INVAL or NFS3ERR_NAMETOOLONG rather than
GARBAGE_ARGS. Later procedures that take a filename share the helper.

Because nfsd3_proc_lookup() now fills in the post-op attributes,
the fh_getattr() calls are made in the proc function rather than in
the XDR result encoder, and the references to the file handles can
be released directly by nfsd3_proc_lookup(). A separate ->pc_release
callback is thus no longer needed. This makes it straightforward to
confirm that the file handles are properly released during every
error flow.

Following the precedent of commit 40ee5dc6af35 ("knfsd: 64 bit ino
support for NFS server"), lease_get_mtime() determines the file's
mtime when filling in post-op attributes, but is not used for WCC
data. This seems odd to me; NFSv4 does not use lease_get_mtime() at
all, and not using it for WCC data seems like an NFSv3 file's
reported mtime values might vary depending on how the file metadata
is queried.

nfs3svc_encode_lookupres() no longer has any callers, so it is
removed.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs3proc.c | 116 +++++++++++++++++++++++++++++++++++----------
 fs/nfsd/nfs3xdr.c  |  25 ----------
 fs/nfsd/xdr3.h     |   1 -
 3 files changed, 90 insertions(+), 52 deletions(-)

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 815722afde9a..735b4fc21336 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -56,6 +56,21 @@ struct SETATTR3args_wrapper {
 
 static_assert(offsetof(struct SETATTR3args_wrapper, xdrgen) == 0);
 
+struct LOOKUP3args_wrapper {
+	struct LOOKUP3args	xdrgen;
+	struct svc_fh		fh;
+};
+
+static_assert(offsetof(struct LOOKUP3args_wrapper, xdrgen) == 0);
+
+struct LOOKUP3res_wrapper {
+	struct LOOKUP3res	xdrgen;
+	struct svc_fh		fh;
+	u8			fh_data[NFS3_FHSIZE];
+};
+
+static_assert(offsetof(struct LOOKUP3res_wrapper, xdrgen) == 0);
+
 static int	nfs3_ftypes[] = {
 	0,			/* NF3NON */
 	S_IFREG,		/* NF3REG */
@@ -170,6 +185,14 @@ nfsd3_fh3_to_svc_fh(struct svc_fh *fhp, const struct nfs_fh3 *fh3)
 	memcpy(&fhp->fh_handle.fh_raw, fh3->data.data, fh3->data.len);
 }
 
+static __always_inline void
+nfsd3_svc_fh_to_fh3(struct nfs_fh3 *fh3, const struct svc_fh *fhp, u8 *scratch)
+{
+	memcpy(scratch, fhp->fh_handle.fh_raw, fhp->fh_handle.fh_size);
+	fh3->data.data = scratch;
+	fh3->data.len = fhp->fh_handle.fh_size;
+}
+
 static __always_inline void
 nfsd3_timespec64_to_nfstime3(struct nfstime3 *dst,
 			     const struct timespec64 *src)
@@ -178,6 +201,22 @@ nfsd3_timespec64_to_nfstime3(struct nfstime3 *dst,
 	dst->nseconds = src->tv_nsec;
 }
 
+static __be32
+nfsd3_check_filename(const unsigned char *name, u32 len)
+{
+	u32 i;
+
+	if (len == 0)
+		return nfserr_inval;
+	if (len > NFS3_MAXNAMLEN)
+		return nfserr_nametoolong;
+	for (i = 0; i < len; i++) {
+		if (name[i] == '\0' || name[i] == '/')
+			return nfserr_inval;
+	}
+	return nfs_ok;
+}
+
 static u32
 nfsd3_mode_to_ftype3(umode_t mode)
 {
@@ -458,27 +497,51 @@ static __be32 nfsd3_proc_setattr(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-/*
- * Look up a path name component
+/**
+ * nfsd3_proc_lookup - LOOKUP: Look up file name
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   LOOKUP3res NFSPROC3_LOOKUP(LOOKUP3args) = 3;
  */
-static __be32
-nfsd3_proc_lookup(struct svc_rqst *rqstp)
+static __be32 nfsd3_proc_lookup(struct svc_rqst *rqstp)
 {
-	struct nfsd3_diropargs *argp = rqstp->rq_argp;
-	struct nfsd3_diropres  *resp = rqstp->rq_resp;
+	struct LOOKUP3args_wrapper *argp = rqstp->rq_argp;
+	struct LOOKUP3res_wrapper *resp = rqstp->rq_resp;
+	struct diropargs3 *what = &argp->xdrgen.what;
+	struct svc_fh *dirfhp = &argp->fh;
+	struct svc_fh *fhp = &resp->fh;
 
-	dprintk("nfsd: LOOKUP(3)   %s %.*s\n",
-				SVCFH_fmt(&argp->fh),
-				argp->len,
-				argp->name);
+	nfsd3_fh3_to_svc_fh(dirfhp, &what->dir);
 
-	fh_copy(&resp->dirfh, &argp->fh);
-	fh_init(&resp->fh, NFS3_FHSIZE);
+	fh_init(fhp, NFS3_FHSIZE);
+	resp->xdrgen.status = nfsd3_check_filename(what->name.data,
+						   what->name.len);
+	if (resp->xdrgen.status != nfs_ok)
+		goto out;
+	resp->xdrgen.status = nfsd_lookup(rqstp, dirfhp,
+					  (char *)what->name.data,
+					  what->name.len, fhp);
 
-	resp->status = nfsd_lookup(rqstp, &resp->dirfh,
-				   argp->name, argp->len,
-				   &resp->fh);
-	resp->status = nfsd3_map_status(resp->status);
+out:
+	if (resp->xdrgen.status == nfs_ok) {
+		struct LOOKUP3resok *resok = &resp->xdrgen.u.resok;
+
+		nfsd3_svc_fh_to_fh3(&resok->object, fhp, resp->fh_data);
+		nfsd3_fill_post_op_attr(rqstp, &resok->obj_attributes, fhp);
+		nfsd3_fill_post_op_attr(rqstp, &resok->dir_attributes, dirfhp);
+	} else {
+		struct LOOKUP3resfail *resfail = &resp->xdrgen.u.resfail;
+
+		resp->xdrgen.status = nfsd3_map_status(resp->xdrgen.status);
+		nfsd3_fill_post_op_attr(rqstp, &resfail->dir_attributes, dirfhp);
+	}
+
+	fh_put(fhp);
+	fh_put(dirfhp);
 	return rpc_success;
 }
 
@@ -1224,16 +1287,15 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "SETATTR",
 	},
 	[NFSPROC3_LOOKUP] = {
-		.pc_func = nfsd3_proc_lookup,
-		.pc_decode = nfs3svc_decode_diropargs,
-		.pc_encode = nfs3svc_encode_lookupres,
-		.pc_release = nfs3svc_release_fhandle2,
-		.pc_argsize = sizeof(struct nfsd3_diropargs),
-		.pc_argzero = sizeof(struct nfsd3_diropargs),
-		.pc_ressize = sizeof(struct nfsd3_diropres),
-		.pc_cachetype = RC_NOCACHE,
-		.pc_xdrressize = ST+FH+pAT+pAT,
-		.pc_name = "LOOKUP",
+		.pc_func	= nfsd3_proc_lookup,
+		.pc_decode	= nfs_svc_decode_LOOKUP3args,
+		.pc_encode	= nfs_svc_encode_LOOKUP3res,
+		.pc_argsize	= sizeof(struct LOOKUP3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct LOOKUP3res_wrapper),
+		.pc_cachetype	= RC_NOCACHE,
+		.pc_xdrressize	= NFS3_LOOKUP3res_sz,
+		.pc_name	= "LOOKUP",
 	},
 	[NFSPROC3_ACCESS] = {
 		.pc_func = nfsd3_proc_access,
@@ -1456,6 +1518,8 @@ union nfsd3_xdrstore {
 	struct GETATTR3res_wrapper	getattrres;
 	struct SETATTR3args_wrapper	setattrargs;
 	struct SETATTR3res		setattrres;
+	struct LOOKUP3args_wrapper	lookupargs;
+	struct LOOKUP3res_wrapper	lookupres;
 	struct nfsd3_diropargs		diropargs;
 	struct nfsd3_readargs		readargs;
 	struct nfsd3_writeargs		writeargs;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index eb5f3148a0ab..36c22611370d 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -721,31 +721,6 @@ nfs3svc_encode_wccstat(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 		svcxdr_encode_wcc_data(rqstp, xdr, &resp->fh);
 }
 
-/* LOOKUP */
-bool
-nfs3svc_encode_lookupres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_diropres *resp = rqstp->rq_resp;
-
-	if (!svcxdr_encode_nfsstat3(xdr, resp->status))
-		return false;
-	switch (resp->status) {
-	case nfs_ok:
-		if (!svcxdr_encode_nfs_fh3(xdr, &resp->fh))
-			return false;
-		if (!svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh))
-			return false;
-		if (!svcxdr_encode_post_op_attr(rqstp, xdr, &resp->dirfh))
-			return false;
-		break;
-	default:
-		if (!svcxdr_encode_post_op_attr(rqstp, xdr, &resp->dirfh))
-			return false;
-	}
-
-	return true;
-}
-
 /* ACCESS */
 bool
 nfs3svc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 354cff178b75..d17192f3c983 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -252,7 +252,6 @@ bool nfs3svc_decode_readdirplusargs(struct svc_rqst *rqstp, struct xdr_stream *x
 bool nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 bool nfs3svc_encode_wccstat(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_encode_lookupres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_readlinkres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_readres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-- 
2.55.0


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

* [PATCH v1 11/33] NFSD: Use xdrgen XDR functions for NFSv3 ACCESS procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (9 preceding siblings ...)
  2026-09-23 13:27 ` [PATCH v1 10/33] NFSD: Use xdrgen XDR functions for the NFSv3 LOOKUP procedure Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 12/33] NFSD: Use xdrgen XDR functions for NFSv3 READLINK procedure Chuck Lever
                   ` (21 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_ACCESS entry in the nfsd_procedures3 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_ACCESS3args and nfs_svc_encode_ACCESS3res. Wrapper
structures bridge the generated xdrgen types and the legacy svc_fh
representation the NFSD VFS layer still uses.

The pc_argzero field is now set to zero for the NFSv3 ACCESS
procedure. The xdrgen decoders are trusted to initialize all
arguments in the argp->xdrgen field, making the early defensive
memset unnecessary. The remaining argp fields are cleared as
needed.

Because nfsd3_proc_access() now fills in the post-op attributes,
the fh_getattr() calls are made in the proc function rather than in
the XDR result encoder, and the reference to the file handle can be
released directly by nfsd3_proc_access(). A separate ->pc_release
callback is thus no longer needed. This makes it straightforward to
confirm that the file handle is properly released during every
error flow.

nfs3svc_decode_accessargs() and nfs3svc_encode_accessres() no
longer have any callers, so they are removed.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs3proc.c | 73 ++++++++++++++++++++++++++++++----------------
 fs/nfsd/nfs3xdr.c  | 36 -----------------------
 fs/nfsd/xdr3.h     |  1 -
 3 files changed, 48 insertions(+), 62 deletions(-)

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 735b4fc21336..5182108999b5 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -71,6 +71,13 @@ struct LOOKUP3res_wrapper {
 
 static_assert(offsetof(struct LOOKUP3res_wrapper, xdrgen) == 0);
 
+struct ACCESS3args_wrapper {
+	struct ACCESS3args	xdrgen;
+	struct svc_fh		fh;
+};
+
+static_assert(offsetof(struct ACCESS3args_wrapper, xdrgen) == 0);
+
 static int	nfs3_ftypes[] = {
 	0,			/* NF3NON */
 	S_IFREG,		/* NF3REG */
@@ -545,24 +552,40 @@ static __be32 nfsd3_proc_lookup(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-/*
- * Check file access
+/**
+ * nfsd3_proc_access - ACCESS: Check access permission
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   ACCESS3res NFSPROC3_ACCESS(ACCESS3args) = 4;
  */
-static __be32
-nfsd3_proc_access(struct svc_rqst *rqstp)
+static __be32 nfsd3_proc_access(struct svc_rqst *rqstp)
 {
-	struct nfsd3_accessargs *argp = rqstp->rq_argp;
-	struct nfsd3_accessres *resp = rqstp->rq_resp;
+	struct ACCESS3args_wrapper *argp = rqstp->rq_argp;
+	struct ACCESS3res *resp = rqstp->rq_resp;
+	struct svc_fh *fhp = &argp->fh;
 
-	dprintk("nfsd: ACCESS(3)   %s 0x%x\n",
-				SVCFH_fmt(&argp->fh),
-				argp->access);
+	nfsd3_fh3_to_svc_fh(fhp, &argp->xdrgen.object);
 
-	fh_copy(&resp->fh, &argp->fh);
-	resp->access = argp->access;
-	resp->status = nfsd_access(rqstp, &resp->fh, &nfsd3_access_maps,
-				   &resp->access, NULL);
-	resp->status = nfsd3_map_status(resp->status);
+	resp->status = nfsd_access(rqstp, fhp, &nfsd3_access_maps,
+				   &argp->xdrgen.access, NULL);
+
+	if (resp->status == nfs_ok) {
+		struct ACCESS3resok *resok = &resp->u.resok;
+
+		resok->access = argp->xdrgen.access;
+		nfsd3_fill_post_op_attr(rqstp, &resok->obj_attributes, fhp);
+	} else {
+		struct ACCESS3resfail *resfail = &resp->u.resfail;
+
+		resp->status = nfsd3_map_status(resp->status);
+		nfsd3_fill_post_op_attr(rqstp, &resfail->obj_attributes, fhp);
+	}
+
+	fh_put(fhp);
 	return rpc_success;
 }
 
@@ -1298,16 +1321,15 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "LOOKUP",
 	},
 	[NFSPROC3_ACCESS] = {
-		.pc_func = nfsd3_proc_access,
-		.pc_decode = nfs3svc_decode_accessargs,
-		.pc_encode = nfs3svc_encode_accessres,
-		.pc_release = nfs3svc_release_fhandle,
-		.pc_argsize = sizeof(struct nfsd3_accessargs),
-		.pc_argzero = sizeof(struct nfsd3_accessargs),
-		.pc_ressize = sizeof(struct nfsd3_accessres),
-		.pc_cachetype = RC_NOCACHE,
-		.pc_xdrressize = ST+pAT+1,
-		.pc_name = "ACCESS",
+		.pc_func	= nfsd3_proc_access,
+		.pc_decode	= nfs_svc_decode_ACCESS3args,
+		.pc_encode	= nfs_svc_encode_ACCESS3res,
+		.pc_argsize	= sizeof(struct ACCESS3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct ACCESS3res),
+		.pc_cachetype	= RC_NOCACHE,
+		.pc_xdrressize	= NFS3_ACCESS3res_sz,
+		.pc_name	= "ACCESS",
 	},
 	[NFSPROC3_READLINK] = {
 		.pc_func = nfsd3_proc_readlink,
@@ -1520,6 +1542,8 @@ union nfsd3_xdrstore {
 	struct SETATTR3res		setattrres;
 	struct LOOKUP3args_wrapper	lookupargs;
 	struct LOOKUP3res_wrapper	lookupres;
+	struct ACCESS3args_wrapper	accessargs;
+	struct ACCESS3res		accessres;
 	struct nfsd3_diropargs		diropargs;
 	struct nfsd3_readargs		readargs;
 	struct nfsd3_writeargs		writeargs;
@@ -1529,7 +1553,6 @@ union nfsd3_xdrstore {
 	struct nfsd3_symlinkargs	symlinkargs;
 	struct nfsd3_readdirargs	readdirargs;
 	struct nfsd3_diropres 		diropres;
-	struct nfsd3_accessres		accessres;
 	struct nfsd3_readlinkres	readlinkres;
 	struct nfsd3_readres		readres;
 	struct nfsd3_writeres		writeres;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index 36c22611370d..f3f723215114 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -490,19 +490,6 @@ nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 	return svcxdr_decode_diropargs3(xdr, &args->fh, &args->name, &args->len);
 }
 
-bool
-nfs3svc_decode_accessargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_accessargs *args = rqstp->rq_argp;
-
-	if (!svcxdr_decode_nfs_fh3(xdr, &args->fh))
-		return false;
-	if (xdr_stream_decode_u32(xdr, &args->access) < 0)
-		return false;
-
-	return true;
-}
-
 bool
 nfs3svc_decode_readargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 {
@@ -721,29 +708,6 @@ nfs3svc_encode_wccstat(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 		svcxdr_encode_wcc_data(rqstp, xdr, &resp->fh);
 }
 
-/* ACCESS */
-bool
-nfs3svc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_accessres *resp = rqstp->rq_resp;
-
-	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 (xdr_stream_encode_u32(xdr, resp->access) < 0)
-			return false;
-		break;
-	default:
-		if (!svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh))
-			return false;
-	}
-
-	return true;
-}
-
 /* READLINK */
 bool
 nfs3svc_encode_readlinkres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index d17192f3c983..bb23c444c23d 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -238,7 +238,6 @@ struct nfsd3_fhandle_pair {
 
 bool nfs3svc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_decode_accessargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_readargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_writeargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-- 
2.55.0


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

* [PATCH v1 12/33] NFSD: Use xdrgen XDR functions for NFSv3 READLINK procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (10 preceding siblings ...)
  2026-09-23 13:27 ` [PATCH v1 11/33] NFSD: Use xdrgen XDR functions for NFSv3 ACCESS procedure Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 13/33] NFSD: Use xdrgen XDR functions for NFSv3 READ procedure Chuck Lever
                   ` (20 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the hand-written NFSv3 READLINK codec pair with the
xdrgen-generated functions. The NFSPROC3_READLINK entry in the
nfsd_procedures3 array now dispatches nfs_svc_decode_READLINK3args
and nfs_svc_encode_READLINK3res. A wrapper structure bridges the
generated xdrgen fhandle type and the legacy svc_fh representation
the NFSD VFS layer still uses.

The link target does not reside at the data pointer of the
READLINK3resok data member: nfsd_readlink() deposits it directly
in a page of the Reply buffer, so the generic encoder that copies
an opaque from the member's data pointer cannot encode it. Mark
the data member with the "pragma pages" directive so the generated
encoder calls svcxdr_encode_opaque_payload(), which encodes the
length prefix and inserts the payload page into the stream by
reference. The page anchor that the result structure carried
duplicated rq_res.pages -- svc_process() points rq_res.pages at
the first Reply page before dispatch, and the READLINK3args
argument decoder consumes no pages -- so the helper locates the
payload through the Reply buffer itself, and the wrapper collapses
to the generated READLINK3res type.

The pc_argzero field is now set to zero for the NFSv3 READLINK
procedure. The xdrgen decoders are trusted to initialize all
arguments in the argp->xdrgen field, making the early defensive
memset unnecessary. The remaining argp fields are cleared as
needed.

Because nfsd3_proc_readlink() now fills in the post-op attributes,
the fh_getattr() calls are made in the proc function rather than
in the XDR result encoder, and the reference to the file handle
can be released directly by nfsd3_proc_readlink(). A separate
->pc_release callback is thus no longer needed. This makes it
straightforward to confirm that the file handle is properly
released during every error flow.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 Documentation/sunrpc/xdr/nfs3.x    |  1 +
 fs/nfsd/nfs3proc.c                 | 79 ++++++++++++++++++++----------
 fs/nfsd/nfs3xdr.c                  | 28 -----------
 fs/nfsd/nfs3xdr_gen.c              |  4 +-
 fs/nfsd/nfs3xdr_gen.h              |  2 +-
 fs/nfsd/xdr3.h                     |  9 +---
 include/linux/sunrpc/xdrgen/nfs3.h |  2 +-
 7 files changed, 60 insertions(+), 65 deletions(-)

diff --git a/Documentation/sunrpc/xdr/nfs3.x b/Documentation/sunrpc/xdr/nfs3.x
index 1ef99ec54103..b50ffb77af54 100644
--- a/Documentation/sunrpc/xdr/nfs3.x
+++ b/Documentation/sunrpc/xdr/nfs3.x
@@ -331,6 +331,7 @@ struct READLINK3resok {
 	post_op_attr	symlink_attributes;
 	nfspath3	data;
 };
+pragma pages READLINK3resok data;
 
 struct READLINK3resfail {
 	post_op_attr	symlink_attributes;
diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 5182108999b5..0a465c68234e 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -78,6 +78,13 @@ struct ACCESS3args_wrapper {
 
 static_assert(offsetof(struct ACCESS3args_wrapper, xdrgen) == 0);
 
+struct READLINK3args_wrapper {
+	struct READLINK3args	xdrgen;
+	struct svc_fh		fh;
+};
+
+static_assert(offsetof(struct READLINK3args_wrapper, xdrgen) == 0);
+
 static int	nfs3_ftypes[] = {
 	0,			/* NF3NON */
 	S_IFREG,		/* NF3REG */
@@ -589,24 +596,44 @@ static __be32 nfsd3_proc_access(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-/*
- * Read a symlink.
+/**
+ * nfsd3_proc_readlink - READLINK: Read from symbolic link
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   READLINK3res NFSPROC3_READLINK(READLINK3args) = 5;
  */
-static __be32
-nfsd3_proc_readlink(struct svc_rqst *rqstp)
+static __be32 nfsd3_proc_readlink(struct svc_rqst *rqstp)
 {
-	struct nfsd_fhandle *argp = rqstp->rq_argp;
-	struct nfsd3_readlinkres *resp = rqstp->rq_resp;
+	struct READLINK3args_wrapper *argp = rqstp->rq_argp;
+	struct READLINK3res *resp = rqstp->rq_resp;
+	struct svc_fh *fhp = &argp->fh;
+	struct page *page;
+	u32 len;
 
-	dprintk("nfsd: READLINK(3) %s\n", SVCFH_fmt(&argp->fh));
+	nfsd3_fh3_to_svc_fh(fhp, &argp->xdrgen.symlink);
 
-	/* Read the symlink. */
-	fh_copy(&resp->fh, &argp->fh);
-	resp->len = NFS3_MAXPATHLEN;
-	resp->pages = rqstp->rq_next_page++;
-	resp->status = nfsd_readlink(rqstp, &resp->fh,
-				     page_address(*resp->pages), &resp->len);
-	resp->status = nfsd3_map_status(resp->status);
+	len = NFS3_MAXPATHLEN;
+	page = *(rqstp->rq_next_page++);
+	resp->status = nfsd_readlink(rqstp, fhp, page_address(page), &len);
+
+	if (resp->status == nfs_ok) {
+		struct READLINK3resok *resok = &resp->u.resok;
+
+		resok->data.len = len;
+		nfsd3_fill_post_op_attr(rqstp, &resok->symlink_attributes, fhp);
+	} else {
+		struct READLINK3resfail *resfail = &resp->u.resfail;
+
+		resp->status = nfsd3_map_status(resp->status);
+		nfsd3_fill_post_op_attr(rqstp, &resfail->symlink_attributes,
+					fhp);
+	}
+
+	fh_put(fhp);
 	return rpc_success;
 }
 
@@ -1332,16 +1359,17 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "ACCESS",
 	},
 	[NFSPROC3_READLINK] = {
-		.pc_func = nfsd3_proc_readlink,
-		.pc_decode = nfs3svc_decode_fhandleargs,
-		.pc_encode = nfs3svc_encode_readlinkres,
-		.pc_release = nfs3svc_release_fhandle,
-		.pc_argsize = sizeof(struct nfsd_fhandle),
-		.pc_argzero = sizeof(struct nfsd_fhandle),
-		.pc_ressize = sizeof(struct nfsd3_readlinkres),
-		.pc_cachetype = RC_NOCACHE,
-		.pc_xdrressize = ST+pAT+1+NFS3_MAXPATHLEN/4,
-		.pc_name = "READLINK",
+		.pc_func	= nfsd3_proc_readlink,
+		.pc_decode	= nfs_svc_decode_READLINK3args,
+		.pc_encode	= nfs_svc_encode_READLINK3res,
+		.pc_argsize	= sizeof(struct READLINK3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct READLINK3res),
+		.pc_cachetype	= RC_NOCACHE,
+		/* _sz omits the payload, which nfs3.x leaves unbounded */
+		.pc_xdrressize	= NFS3_READLINK3res_sz +
+				  XDR_QUADLEN(NFS3_MAXPATHLEN),
+		.pc_name	= "READLINK",
 	},
 	[NFSPROC3_READ] = {
 		.pc_func = nfsd3_proc_read,
@@ -1544,6 +1572,7 @@ union nfsd3_xdrstore {
 	struct LOOKUP3res_wrapper	lookupres;
 	struct ACCESS3args_wrapper	accessargs;
 	struct ACCESS3res		accessres;
+	struct READLINK3args_wrapper	readlinkargs;
 	struct nfsd3_diropargs		diropargs;
 	struct nfsd3_readargs		readargs;
 	struct nfsd3_writeargs		writeargs;
@@ -1553,7 +1582,7 @@ union nfsd3_xdrstore {
 	struct nfsd3_symlinkargs	symlinkargs;
 	struct nfsd3_readdirargs	readdirargs;
 	struct nfsd3_diropres 		diropres;
-	struct nfsd3_readlinkres	readlinkres;
+	struct READLINK3res		readlinkres;
 	struct nfsd3_readres		readres;
 	struct nfsd3_writeres		writeres;
 	struct nfsd3_renameres		renameres;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index f3f723215114..a77b5f7a7e27 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -708,34 +708,6 @@ nfs3svc_encode_wccstat(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 		svcxdr_encode_wcc_data(rqstp, xdr, &resp->fh);
 }
 
-/* READLINK */
-bool
-nfs3svc_encode_readlinkres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_readlinkres *resp = rqstp->rq_resp;
-	struct kvec *head = rqstp->rq_res.head;
-
-	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 (xdr_stream_encode_u32(xdr, resp->len) < 0)
-			return false;
-		svcxdr_encode_opaque_pages(rqstp, xdr, resp->pages, 0,
-					   resp->len);
-		if (svc_encode_result_payload(rqstp, head->iov_len, resp->len) < 0)
-			return false;
-		break;
-	default:
-		if (!svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh))
-			return false;
-	}
-
-	return true;
-}
-
 /* READ */
 bool
 nfs3svc_encode_readres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
diff --git a/fs/nfsd/nfs3xdr_gen.c b/fs/nfsd/nfs3xdr_gen.c
index e21ee0053129..bc6fb9829397 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: Fri Jul 10 23:21:45 2026
+// XDR specification modification time: Tue Jul 14 09:53:56 2026
 
 #include <linux/sunrpc/svc.h>
 
@@ -2480,7 +2480,7 @@ xdrgen_encode_READLINK3resok(struct xdr_stream *xdr, const struct READLINK3resok
 {
 	if (!xdrgen_encode_post_op_attr(xdr, &value->symlink_attributes))
 		return false;
-	if (!xdrgen_encode_nfspath3(xdr, value->data))
+	if (!svcxdr_encode_opaque_payload(xdr, value->data.len))
 		return false;
 	return true;
 }
diff --git a/fs/nfsd/nfs3xdr_gen.h b/fs/nfsd/nfs3xdr_gen.h
index 58499ca28c43..3fefbb797b69 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: Fri Jul 10 23:21:45 2026 */
+/* XDR specification modification time: Tue Jul 14 09:53:56 2026 */
 
 #ifndef _LINUX_XDRGEN_NFS3_DECL_H
 #define _LINUX_XDRGEN_NFS3_DECL_H
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index bb23c444c23d..39a2109a2c66 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -9,6 +9,7 @@
 #define _LINUX_NFSD_XDR3_H
 
 #include <linux/vfs.h>
+#include <linux/sunrpc/xdrgen/nfs3.h>
 
 #include "vfs.h"
 
@@ -134,13 +135,6 @@ struct nfsd3_accessres {
 	struct kstat		stat;
 };
 
-struct nfsd3_readlinkres {
-	__be32			status;
-	struct svc_fh		fh;
-	__u32			len;
-	struct page		**pages;
-};
-
 struct nfsd3_readres {
 	__be32			status;
 	struct svc_fh		fh;
@@ -252,7 +246,6 @@ bool nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 bool nfs3svc_encode_wccstat(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_encode_readlinkres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_readres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_writeres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_createres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
diff --git a/include/linux/sunrpc/xdrgen/nfs3.h b/include/linux/sunrpc/xdrgen/nfs3.h
index 785ebff351ce..3f8d1ecda7f2 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: Fri Jul 10 23:21:45 2026 */
+/* XDR specification modification time: Tue Jul 14 09:53:56 2026 */
 
 #ifndef _LINUX_XDRGEN_NFS3_DEF_H
 #define _LINUX_XDRGEN_NFS3_DEF_H
-- 
2.55.0


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

* [PATCH v1 13/33] NFSD: Use xdrgen XDR functions for NFSv3 READ procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (11 preceding siblings ...)
  2026-09-23 13:27 ` [PATCH v1 12/33] NFSD: Use xdrgen XDR functions for NFSv3 READLINK procedure Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 14/33] NFSD: Use xdrgen XDR functions for NFSv3 WRITE procedure Chuck Lever
                   ` (19 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_READ entry in the nfsd_procedures3 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_READ3args and nfs_svc_encode_READ3res. A wrapper
structure bridges the generated xdrgen READ3args type and the
legacy svc_fh representation the NFSD VFS layer still uses.

The file's data does not reside at the data pointer of the
READ3resok data member: nfsd_read() deposits it directly in the
pages of the Reply buffer, so the generic encoder that copies an
opaque from the member's data pointer cannot encode it. Mark the
member with the "pragma pages" directive so the generated encoder
calls svcxdr_encode_opaque_payload(), which encodes the length
prefix and inserts the payload pages into the stream by reference.
The page anchor that the result structure carried duplicated
rq_res.pages -- svc_process() points rq_res.pages at the first
Reply page before dispatch, and the READ3args decoder consumes no
pages -- so the helper locates the payload through the Reply
buffer itself, and the wrapper collapses to the generated READ3res
type.

The pc_argzero field is now set to zero for the NFSv3 READ
procedure. The xdrgen decoders are trusted to initialize all
arguments in the argp->xdrgen field, making the early defensive
memset unnecessary. The remaining argp fields are cleared as
needed.

Because nfsd3_proc_read() now fills in the post-op attributes,
the fh_getattr() calls are made in the proc function rather than
in the XDR result encoder, and the reference to the file handle
can be released directly by nfsd3_proc_read(). A separate
->pc_release callback is thus no longer needed. This makes it
straightforward to confirm that the file handle is properly
released during every error flow.

nfs3svc_decode_readargs() no longer has any callers, so it is
removed.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 Documentation/sunrpc/xdr/nfs3.x    |  1 +
 fs/nfsd/nfs3proc.c                 | 99 ++++++++++++++++++------------
 fs/nfsd/nfs3xdr.c                  | 48 ---------------
 fs/nfsd/nfs3xdr_gen.c              |  4 +-
 fs/nfsd/nfs3xdr_gen.h              |  2 +-
 fs/nfsd/xdr3.h                     | 16 -----
 include/linux/sunrpc/xdrgen/nfs3.h |  2 +-
 7 files changed, 66 insertions(+), 106 deletions(-)

diff --git a/Documentation/sunrpc/xdr/nfs3.x b/Documentation/sunrpc/xdr/nfs3.x
index b50ffb77af54..b7571bc47f47 100644
--- a/Documentation/sunrpc/xdr/nfs3.x
+++ b/Documentation/sunrpc/xdr/nfs3.x
@@ -356,6 +356,7 @@ struct READ3resok {
 	bool		eof;
 	opaque		data<>;
 };
+pragma pages READ3resok data;
 
 struct READ3resfail {
 	post_op_attr	file_attributes;
diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 0a465c68234e..ba140c2008f2 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -85,6 +85,13 @@ struct READLINK3args_wrapper {
 
 static_assert(offsetof(struct READLINK3args_wrapper, xdrgen) == 0);
 
+struct READ3args_wrapper {
+	struct READ3args	xdrgen;
+	struct svc_fh		fh;
+};
+
+static_assert(offsetof(struct READ3args_wrapper, xdrgen) == 0);
+
 static int	nfs3_ftypes[] = {
 	0,			/* NF3NON */
 	S_IFREG,		/* NF3REG */
@@ -637,41 +644,57 @@ static __be32 nfsd3_proc_readlink(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-/*
- * Read a portion of a file.
+/**
+ * nfsd3_proc_read - READ: Read from file
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   READ3res NFSPROC3_READ(READ3args) = 6;
  */
-static __be32
-nfsd3_proc_read(struct svc_rqst *rqstp)
+static __be32 nfsd3_proc_read(struct svc_rqst *rqstp)
 {
-	struct nfsd3_readargs *argp = rqstp->rq_argp;
-	struct nfsd3_readres *resp = rqstp->rq_resp;
+	struct READ3args_wrapper *argp = rqstp->rq_argp;
+	struct READ3res *resp = rqstp->rq_resp;
+	unsigned long count = argp->xdrgen.count;
+	u64 offset = argp->xdrgen.offset;
+	struct svc_fh *fhp = &argp->fh;
+	u32 eof;
 
-	dprintk("nfsd: READ(3) %s %lu bytes at %Lu\n",
-				SVCFH_fmt(&argp->fh),
-				(unsigned long) argp->count,
-				(unsigned long long) argp->offset);
+	nfsd3_fh3_to_svc_fh(fhp, &argp->xdrgen.file);
+	count = min_t(u32, count, svc_max_payload(rqstp));
+	count = min_t(u32, count, rqstp->rq_res.buflen);
+	if (offset > (u64)OFFSET_MAX)
+		offset = (u64)OFFSET_MAX;
+	if (offset + count > (u64)OFFSET_MAX)
+		count = (u64)OFFSET_MAX - offset;
 
-	argp->count = min_t(u32, argp->count, svc_max_payload(rqstp));
-	argp->count = min_t(u32, argp->count, rqstp->rq_res.buflen);
-	if (argp->offset > (u64)OFFSET_MAX)
-		argp->offset = (u64)OFFSET_MAX;
-	if (argp->offset + argp->count > (u64)OFFSET_MAX)
-		argp->count = (u64)OFFSET_MAX - argp->offset;
-
-	resp->pages = rqstp->rq_next_page;
-
-	/* Obtain buffer pointer for payload.
+	/*
+	 * Obtain buffer pointer for payload.
 	 * 1 (status) + 22 (post_op_attr) + 1 (count) + 1 (eof)
 	 * + 1 (xdr opaque byte count) = 26
 	 */
-	resp->count = argp->count;
 	svc_reserve_auth(rqstp, ((1 + NFS3_post_op_attr_sz + 3) << 2) +
-			 resp->count + 4);
+			 count + 4);
+	resp->status = nfsd_read(rqstp, fhp, offset, &count, &eof);
 
-	fh_copy(&resp->fh, &argp->fh);
-	resp->status = nfsd_read(rqstp, &resp->fh, argp->offset,
-				 &resp->count, &resp->eof);
-	resp->status = nfsd3_map_status(resp->status);
+	if (resp->status == nfs_ok) {
+		struct READ3resok *resok = &resp->u.resok;
+
+		resok->count = count;
+		resok->eof = !!eof;
+		resok->data.len = count;
+		nfsd3_fill_post_op_attr(rqstp, &resok->file_attributes, fhp);
+	} else {
+		struct READ3resfail *resfail = &resp->u.resfail;
+
+		resp->status = nfsd3_map_status(resp->status);
+		nfsd3_fill_post_op_attr(rqstp, &resfail->file_attributes, fhp);
+	}
+
+	fh_put(fhp);
 	return rpc_success;
 }
 
@@ -1372,16 +1395,16 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "READLINK",
 	},
 	[NFSPROC3_READ] = {
-		.pc_func = nfsd3_proc_read,
-		.pc_decode = nfs3svc_decode_readargs,
-		.pc_encode = nfs3svc_encode_readres,
-		.pc_release = nfs3svc_release_fhandle,
-		.pc_argsize = sizeof(struct nfsd3_readargs),
-		.pc_argzero = sizeof(struct nfsd3_readargs),
-		.pc_ressize = sizeof(struct nfsd3_readres),
-		.pc_cachetype = RC_NOCACHE,
-		.pc_xdrressize = ST+pAT+4+NFSSVC_MAXBLKSIZE/4,
-		.pc_name = "READ",
+		.pc_func	= nfsd3_proc_read,
+		.pc_decode	= nfs_svc_decode_READ3args,
+		.pc_encode	= nfs_svc_encode_READ3res,
+		.pc_argsize	= sizeof(struct READ3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct READ3res),
+		.pc_cachetype	= RC_NOCACHE,
+		.pc_xdrressize	= NFS3_READ3res_sz +
+				  XDR_QUADLEN(NFSSVC_MAXBLKSIZE),
+		.pc_name	= "READ",
 	},
 	[NFSPROC3_WRITE] = {
 		.pc_func = nfsd3_proc_write,
@@ -1573,8 +1596,8 @@ union nfsd3_xdrstore {
 	struct ACCESS3args_wrapper	accessargs;
 	struct ACCESS3res		accessres;
 	struct READLINK3args_wrapper	readlinkargs;
+	struct READ3args_wrapper	readargs;
 	struct nfsd3_diropargs		diropargs;
-	struct nfsd3_readargs		readargs;
 	struct nfsd3_writeargs		writeargs;
 	struct nfsd3_createargs		createargs;
 	struct nfsd3_renameargs		renameargs;
@@ -1583,7 +1606,7 @@ union nfsd3_xdrstore {
 	struct nfsd3_readdirargs	readdirargs;
 	struct nfsd3_diropres 		diropres;
 	struct READLINK3res		readlinkres;
-	struct nfsd3_readres		readres;
+	struct READ3res			readres;
 	struct nfsd3_writeres		writeres;
 	struct nfsd3_renameres		renameres;
 	struct nfsd3_linkres		linkres;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index a77b5f7a7e27..9cee503407f7 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -490,21 +490,6 @@ nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 	return svcxdr_decode_diropargs3(xdr, &args->fh, &args->name, &args->len);
 }
 
-bool
-nfs3svc_decode_readargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_readargs *args = rqstp->rq_argp;
-
-	if (!svcxdr_decode_nfs_fh3(xdr, &args->fh))
-		return false;
-	if (xdr_stream_decode_u64(xdr, &args->offset) < 0)
-		return false;
-	if (xdr_stream_decode_u32(xdr, &args->count) < 0)
-		return false;
-
-	return true;
-}
-
 bool
 nfs3svc_decode_writeargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 {
@@ -708,39 +693,6 @@ nfs3svc_encode_wccstat(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 		svcxdr_encode_wcc_data(rqstp, xdr, &resp->fh);
 }
 
-/* READ */
-bool
-nfs3svc_encode_readres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_readres *resp = rqstp->rq_resp;
-	struct kvec *head = rqstp->rq_res.head;
-
-	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 (xdr_stream_encode_u32(xdr, resp->count) < 0)
-			return false;
-		if (xdr_stream_encode_bool(xdr, resp->eof) < 0)
-			return false;
-		if (xdr_stream_encode_u32(xdr, resp->count) < 0)
-			return false;
-		svcxdr_encode_opaque_pages(rqstp, xdr, resp->pages,
-					   rqstp->rq_res.page_base,
-					   resp->count);
-		if (svc_encode_result_payload(rqstp, head->iov_len, resp->count) < 0)
-			return false;
-		break;
-	default:
-		if (!svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh))
-			return false;
-	}
-
-	return true;
-}
-
 /* WRITE */
 bool
 nfs3svc_encode_writeres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
diff --git a/fs/nfsd/nfs3xdr_gen.c b/fs/nfsd/nfs3xdr_gen.c
index bc6fb9829397..0961f6c92ee6 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 09:53:56 2026
+// XDR specification modification time: Tue Jul 14 11:24:16 2026
 
 #include <linux/sunrpc/svc.h>
 
@@ -2531,7 +2531,7 @@ xdrgen_encode_READ3resok(struct xdr_stream *xdr, const struct READ3resok *value)
 		return false;
 	if (!xdrgen_encode_bool(xdr, value->eof))
 		return false;
-	if (xdr_stream_encode_opaque(xdr, value->data.data, value->data.len) < 0)
+	if (!svcxdr_encode_opaque_payload(xdr, value->data.len))
 		return false;
 	return true;
 }
diff --git a/fs/nfsd/nfs3xdr_gen.h b/fs/nfsd/nfs3xdr_gen.h
index 3fefbb797b69..d54b087b7748 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 09:53:56 2026 */
+/* XDR specification modification time: Tue Jul 14 11:24:16 2026 */
 
 #ifndef _LINUX_XDRGEN_NFS3_DECL_H
 #define _LINUX_XDRGEN_NFS3_DECL_H
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 39a2109a2c66..6ca1a2c84b42 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -30,12 +30,6 @@ struct nfsd3_accessargs {
 	__u32			access;
 };
 
-struct nfsd3_readargs {
-	struct svc_fh		fh;
-	__u64			offset;
-	__u32			count;
-};
-
 struct nfsd3_writeargs {
 	svc_fh			fh;
 	__u64			offset;
@@ -135,14 +129,6 @@ struct nfsd3_accessres {
 	struct kstat		stat;
 };
 
-struct nfsd3_readres {
-	__be32			status;
-	struct svc_fh		fh;
-	unsigned long		count;
-	__u32			eof;
-	struct page		**pages;
-};
-
 struct nfsd3_writeres {
 	__be32			status;
 	struct svc_fh		fh;
@@ -232,7 +218,6 @@ struct nfsd3_fhandle_pair {
 
 bool nfs3svc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_decode_readargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_writeargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_mkdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
@@ -246,7 +231,6 @@ bool nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 bool nfs3svc_encode_wccstat(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_encode_readres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_writeres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_createres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_renameres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
diff --git a/include/linux/sunrpc/xdrgen/nfs3.h b/include/linux/sunrpc/xdrgen/nfs3.h
index 3f8d1ecda7f2..75ff4a924cf4 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 09:53:56 2026 */
+/* XDR specification modification time: Tue Jul 14 11:24:16 2026 */
 
 #ifndef _LINUX_XDRGEN_NFS3_DEF_H
 #define _LINUX_XDRGEN_NFS3_DEF_H
-- 
2.55.0


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

* [PATCH v1 14/33] NFSD: Use xdrgen XDR functions for NFSv3 WRITE procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (12 preceding siblings ...)
  2026-09-23 13:27 ` [PATCH v1 13/33] NFSD: Use xdrgen XDR functions for NFSv3 READ procedure Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 15/33] NFSD: Use xdrgen XDR functions for NFSv3 CREATE procedure Chuck Lever
                   ` (18 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_WRITE entry in the nfsd_procedures3 array with
an entry that dispatches the xdrgen-generated nfs_svc_decode_WRITE3args
and nfs_svc_encode_WRITE3res. Wrapper structures bridge the generated
xdrgen types and the legacy svc_fh representation the NFSD VFS layer
still uses.

The NFSv3 WRITE argument decoder could not be generated before because
the write payload must be directed into the server's page cache and
decoded in place; a materializing decoder would copy every write. The
"pages" directive now expresses this: it represents the payload as a
struct xdr_buf and decodes it with svcxdr_decode_opaque_payload(), which
subsegments the source stream in place rather than copying it. Annotate
the data member of WRITE3args with "pragma pages".

Clamping the payload size when decoding serves no functional purpose and
is removed entirely. The transport layer has already guaranteed that the
incoming message, including the WRITE payload, does not exceed
svc_max_payload.

The WRITE count versus data length sanity check moves to the
proc function: if those values are ever not equal, that does not
count as GARBAGE_ARGS, since the server is able to decode these
arguments. Rather, the arguments are invalid.

The pc_argzero field is now set to zero for the NFSv3 WRITE
procedure. The xdrgen decoders are trusted to initialize all
arguments in the argp->xdrgen field, making the early defensive memset
unnecessary.

Because nfsd3_proc_write() now fills in the WCC data, including
both the pre-op and post-op attributes, the fh_getattr() calls are
made in the proc function rather than in the XDR result encoder,
and the reference to the file handle can be released directly by
nfsd3_proc_write(). A separate ->pc_release callback is thus no longer
needed. This makes it straightforward to confirm that the file handle
is properly released during every error flow.

nfs3svc_decode_writeargs() and nfs3svc_encode_writeres() no longer have
any callers, so they are removed.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 Documentation/sunrpc/xdr/nfs3.x    |  1 +
 fs/nfsd/nfs3proc.c                 | 94 +++++++++++++++++++-----------
 fs/nfsd/nfs3xdr.c                  | 59 -------------------
 fs/nfsd/nfs3xdr_gen.c              |  8 +--
 fs/nfsd/nfs3xdr_gen.h              |  4 +-
 fs/nfsd/xdr3.h                     | 19 +-----
 include/linux/sunrpc/xdrgen/nfs3.h |  6 +-
 7 files changed, 73 insertions(+), 118 deletions(-)

diff --git a/Documentation/sunrpc/xdr/nfs3.x b/Documentation/sunrpc/xdr/nfs3.x
index b7571bc47f47..61b87245be1e 100644
--- a/Documentation/sunrpc/xdr/nfs3.x
+++ b/Documentation/sunrpc/xdr/nfs3.x
@@ -382,6 +382,7 @@ struct WRITE3args {
 	stable_how	stable;
 	opaque		data<>;
 };
+pragma pages WRITE3args data;
 
 struct WRITE3resok {
 	wcc_data	file_wcc;
diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index ba140c2008f2..6adb00a99052 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -92,6 +92,13 @@ struct READ3args_wrapper {
 
 static_assert(offsetof(struct READ3args_wrapper, xdrgen) == 0);
 
+struct WRITE3args_wrapper {
+	struct WRITE3args	xdrgen;
+	struct svc_fh		fh;
+};
+
+static_assert(offsetof(struct WRITE3args_wrapper, xdrgen) == 0);
+
 static int	nfs3_ftypes[] = {
 	0,			/* NF3NON */
 	S_IFREG,		/* NF3REG */
@@ -698,35 +705,55 @@ static __be32 nfsd3_proc_read(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-/*
- * Write data to a file
+/**
+ * nfsd3_proc_write - WRITE: Write to file
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   WRITE3res NFSPROC3_WRITE(WRITE3args) = 7;
  */
-static __be32
-nfsd3_proc_write(struct svc_rqst *rqstp)
+static __be32 nfsd3_proc_write(struct svc_rqst *rqstp)
 {
-	struct nfsd3_writeargs *argp = rqstp->rq_argp;
+	struct WRITE3args_wrapper *argp = rqstp->rq_argp;
 	struct nfsd3_writeres *resp = rqstp->rq_resp;
-	unsigned long cnt = argp->len;
+	struct svc_fh *fhp = &argp->fh;
+	unsigned long count;
 
-	dprintk("nfsd: WRITE(3)    %s %d bytes at %Lu%s\n",
-				SVCFH_fmt(&argp->fh),
-				argp->len,
-				(unsigned long long) argp->offset,
-				argp->stable ? " stable" : "");
+	nfsd3_fh3_to_svc_fh(fhp, &argp->xdrgen.file);
+	if (argp->xdrgen.count != argp->xdrgen.data.len) {
+		resp->xdrgen.status = nfserr_inval;
+		goto out;
+	}
+	count = min_t(u32, argp->xdrgen.count, svc_max_payload(rqstp));
+	if (argp->xdrgen.offset > (u64)OFFSET_MAX ||
+	    argp->xdrgen.offset + count > (u64)OFFSET_MAX) {
+		resp->xdrgen.status = nfserr_fbig;
+		goto out;
+	}
 
-	resp->status = nfserr_fbig;
-	if (argp->offset > (u64)OFFSET_MAX ||
-	    argp->offset + argp->len > (u64)OFFSET_MAX)
-		return rpc_success;
+	resp->xdrgen.status = nfsd_write(rqstp, fhp, argp->xdrgen.offset,
+					 &argp->xdrgen.data, &count,
+					 nfsd3_iocb_flags(argp->xdrgen.stable),
+					 (__be32 *)resp->xdrgen.u.resok.verf);
 
-	fh_copy(&resp->fh, &argp->fh);
-	resp->committed = argp->stable;
-	resp->status = nfsd_write(rqstp, &resp->fh, argp->offset,
-				  &argp->payload, &cnt,
-				  nfsd3_iocb_flags(resp->committed),
-				  resp->verf);
-	resp->count = cnt;
-	resp->status = nfsd3_map_status(resp->status);
+out:
+	if (resp->xdrgen.status == nfs_ok) {
+		struct WRITE3resok *resok = &resp->xdrgen.u.resok;
+
+		resok->count = count;
+		resok->committed = argp->xdrgen.stable;
+		nfsd3_fill_wcc_data(rqstp, &resok->file_wcc, fhp);
+	} else {
+		struct WRITE3resfail *resfail = &resp->xdrgen.u.resfail;
+
+		resp->xdrgen.status = nfsd3_map_status(resp->xdrgen.status);
+		nfsd3_fill_wcc_data(rqstp, &resfail->file_wcc, fhp);
+	}
+
+	fh_put(fhp);
 	return rpc_success;
 }
 
@@ -1407,16 +1434,15 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "READ",
 	},
 	[NFSPROC3_WRITE] = {
-		.pc_func = nfsd3_proc_write,
-		.pc_decode = nfs3svc_decode_writeargs,
-		.pc_encode = nfs3svc_encode_writeres,
-		.pc_release = nfs3svc_release_fhandle,
-		.pc_argsize = sizeof(struct nfsd3_writeargs),
-		.pc_argzero = sizeof(struct nfsd3_writeargs),
-		.pc_ressize = sizeof(struct nfsd3_writeres),
-		.pc_cachetype = RC_REPLBUFF,
-		.pc_xdrressize = ST+WC+4,
-		.pc_name = "WRITE",
+		.pc_func	= nfsd3_proc_write,
+		.pc_decode	= nfs_svc_decode_WRITE3args,
+		.pc_encode	= nfs_svc_encode_WRITE3res,
+		.pc_argsize	= sizeof(struct WRITE3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct nfsd3_writeres),
+		.pc_cachetype	= RC_REPLBUFF,
+		.pc_xdrressize	= NFS3_WRITE3res_sz,
+		.pc_name	= "WRITE",
 	},
 	[NFSPROC3_CREATE] = {
 		.pc_func = nfsd3_proc_create,
@@ -1598,7 +1624,7 @@ union nfsd3_xdrstore {
 	struct READLINK3args_wrapper	readlinkargs;
 	struct READ3args_wrapper	readargs;
 	struct nfsd3_diropargs		diropargs;
-	struct nfsd3_writeargs		writeargs;
+	struct WRITE3args_wrapper	writeargs;
 	struct nfsd3_createargs		createargs;
 	struct nfsd3_renameargs		renameargs;
 	struct nfsd3_linkargs		linkargs;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index 9cee503407f7..cc3aebd97959 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -490,38 +490,6 @@ nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 	return svcxdr_decode_diropargs3(xdr, &args->fh, &args->name, &args->len);
 }
 
-bool
-nfs3svc_decode_writeargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_writeargs *args = rqstp->rq_argp;
-	u32 max_blocksize = svc_max_payload(rqstp);
-
-	if (!svcxdr_decode_nfs_fh3(xdr, &args->fh))
-		return false;
-	if (xdr_stream_decode_u64(xdr, &args->offset) < 0)
-		return false;
-	if (xdr_stream_decode_u32(xdr, &args->count) < 0)
-		return false;
-	if (xdr_stream_decode_u32(xdr, &args->stable) < 0)
-		return false;
-	if (args->stable > FILE_SYNC)
-		return false;
-
-	/* opaque data */
-	if (xdr_stream_decode_u32(xdr, &args->len) < 0)
-		return false;
-
-	/* request sanity */
-	if (args->count != args->len)
-		return false;
-	if (args->count > max_blocksize) {
-		args->count = max_blocksize;
-		args->len = max_blocksize;
-	}
-
-	return xdr_stream_subsegment(xdr, &args->payload, args->count);
-}
-
 bool
 nfs3svc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 {
@@ -693,33 +661,6 @@ nfs3svc_encode_wccstat(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 		svcxdr_encode_wcc_data(rqstp, xdr, &resp->fh);
 }
 
-/* WRITE */
-bool
-nfs3svc_encode_writeres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_writeres *resp = rqstp->rq_resp;
-
-	if (!svcxdr_encode_nfsstat3(xdr, resp->status))
-		return false;
-	switch (resp->status) {
-	case nfs_ok:
-		if (!svcxdr_encode_wcc_data(rqstp, xdr, &resp->fh))
-			return false;
-		if (xdr_stream_encode_u32(xdr, resp->count) < 0)
-			return false;
-		if (xdr_stream_encode_u32(xdr, resp->committed) < 0)
-			return false;
-		if (!svcxdr_encode_writeverf3(xdr, resp->verf))
-			return false;
-		break;
-	default:
-		if (!svcxdr_encode_wcc_data(rqstp, xdr, &resp->fh))
-			return false;
-	}
-
-	return true;
-}
-
 /* CREATE, MKDIR, SYMLINK, MKNOD */
 bool
 nfs3svc_encode_createres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
diff --git a/fs/nfsd/nfs3xdr_gen.c b/fs/nfsd/nfs3xdr_gen.c
index 0961f6c92ee6..159aff1a8567 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 11:24:16 2026
+// XDR specification file: Documentation/sunrpc/xdr/nfs3.x
+// XDR specification modification time: Tue Jul 14 11:43:26 2026
 
 #include <linux/sunrpc/svc.h>
 
@@ -731,7 +731,7 @@ xdrgen_decode_WRITE3args(struct xdr_stream *xdr, struct WRITE3args *ptr)
 		return false;
 	if (!xdrgen_decode_stable_how(xdr, &ptr->stable))
 		return false;
-	if (!xdrgen_decode_opaque(xdr, &ptr->data, 0))
+	if (!svcxdr_decode_opaque_payload(xdr, &ptr->data, 0))
 		return false;
 	return true;
 }
@@ -2578,7 +2578,7 @@ xdrgen_encode_WRITE3args(struct xdr_stream *xdr, const struct WRITE3args *value)
 		return false;
 	if (!xdrgen_encode_stable_how(xdr, value->stable))
 		return false;
-	if (xdr_stream_encode_opaque(xdr, value->data.data, value->data.len) < 0)
+	if (!svcxdr_encode_opaque_payload(xdr, value->data.len))
 		return false;
 	return true;
 }
diff --git a/fs/nfsd/nfs3xdr_gen.h b/fs/nfsd/nfs3xdr_gen.h
index d54b087b7748..98e29c8efc5e 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 11:24:16 2026 */
+/* XDR specification file: Documentation/sunrpc/xdr/nfs3.x */
+/* XDR specification modification time: Tue Jul 14 11:43:26 2026 */
 
 #ifndef _LINUX_XDRGEN_NFS3_DECL_H
 #define _LINUX_XDRGEN_NFS3_DECL_H
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 6ca1a2c84b42..9c41244d27bb 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -30,15 +30,6 @@ struct nfsd3_accessargs {
 	__u32			access;
 };
 
-struct nfsd3_writeargs {
-	svc_fh			fh;
-	__u64			offset;
-	__u32			count;
-	__u32			stable;
-	__u32			len;
-	struct xdr_buf		payload;
-};
-
 struct nfsd3_createargs {
 	struct svc_fh		fh;
 	char *			name;
@@ -130,13 +121,11 @@ struct nfsd3_accessres {
 };
 
 struct nfsd3_writeres {
-	__be32			status;
-	struct svc_fh		fh;
-	unsigned long		count;
-	int			committed;
-	__be32			verf[2];
+	struct WRITE3res	xdrgen;
 };
 
+static_assert(offsetof(struct nfsd3_writeres, xdrgen) == 0);
+
 struct nfsd3_renameres {
 	__be32			status;
 	struct svc_fh		ffh;
@@ -218,7 +207,6 @@ struct nfsd3_fhandle_pair {
 
 bool nfs3svc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_decode_writeargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_mkdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_mknodargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
@@ -231,7 +219,6 @@ bool nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 bool nfs3svc_encode_wccstat(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_encode_writeres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_createres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_renameres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_linkres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
diff --git a/include/linux/sunrpc/xdrgen/nfs3.h b/include/linux/sunrpc/xdrgen/nfs3.h
index 75ff4a924cf4..3b6738c0074b 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 11:24:16 2026 */
+/* XDR specification file: Documentation/sunrpc/xdr/nfs3.x */
+/* XDR specification modification time: Tue Jul 14 11:43:26 2026 */
 
 #ifndef _LINUX_XDRGEN_NFS3_DEF_H
 #define _LINUX_XDRGEN_NFS3_DEF_H
@@ -382,7 +382,7 @@ struct WRITE3args {
 	offset3 offset;
 	count3 count;
 	stable_how stable;
-	opaque data;
+	struct xdr_buf data;
 };
 
 struct WRITE3resok {
-- 
2.55.0


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

* [PATCH v1 15/33] NFSD: Use xdrgen XDR functions for NFSv3 CREATE procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (13 preceding siblings ...)
  2026-09-23 13:27 ` [PATCH v1 14/33] NFSD: Use xdrgen XDR functions for NFSv3 WRITE procedure Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 16/33] NFSD: Use xdrgen XDR functions for NFSv3 MKDIR procedure Chuck Lever
                   ` (17 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_CREATE entry in the nfsd_procedures3 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_CREATE3args and nfs_svc_encode_CREATE3res. Wrapper
structures bridge the generated xdrgen types and the legacy
svc_fh and iattr representations the NFSD VFS layer still uses.

Introduce nfsd3_fill_post_op_fh3() to fill a post_op_fh3. It is
useful here and in subsequent patches.

Filename validity is now checked by nfsd3_create_file() so that a
proper NFS3ERR status code can be returned to the client rather
than GARBAGE_ARGS.

The pc_argzero field is now set to zero for the NFSv3 CREATE
procedure. The xdrgen decoders are trusted to initialize all
arguments in the argp->xdrgen field, making the early defensive
memset unnecessary. The remaining argp fields are cleared as
needed.

Because nfsd3_proc_create() now fills in the post-op file handle,
the post-op attributes, and the WCC data, the fh_getattr() calls
are made in the proc function rather than in the XDR result
encoder, and the references to the file handles can be released
directly by nfsd3_proc_create(). A separate ->pc_release callback
is thus no longer needed. This makes it straightforward to
confirm that the file handles are properly released during every
error flow.

nfs3svc_decode_createargs() no longer has any callers, so it is
removed.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs3proc.c | 194 ++++++++++++++++++++++++++++++---------------
 fs/nfsd/nfs3xdr.c  |  24 ------
 fs/nfsd/xdr3.h     |   1 -
 3 files changed, 130 insertions(+), 89 deletions(-)

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 6adb00a99052..eb8773e6ae17 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -99,6 +99,22 @@ struct WRITE3args_wrapper {
 
 static_assert(offsetof(struct WRITE3args_wrapper, xdrgen) == 0);
 
+struct CREATE3args_wrapper {
+	struct CREATE3args	xdrgen;
+	struct svc_fh		fh;
+	struct iattr		iattrs;
+};
+
+static_assert(offsetof(struct CREATE3args_wrapper, xdrgen) == 0);
+
+struct CREATE3res_wrapper {
+	struct CREATE3res	xdrgen;
+	struct svc_fh		fh;
+	u8			fh_data[NFS3_FHSIZE];
+};
+
+static_assert(offsetof(struct CREATE3res_wrapper, xdrgen) == 0);
+
 static int	nfs3_ftypes[] = {
 	0,			/* NF3NON */
 	S_IFREG,		/* NF3REG */
@@ -422,6 +438,18 @@ nfsd3_fill_post_op_attr(struct svc_rqst *rqstp, struct post_op_attr *attr,
 	attr->attributes_follow = false;
 }
 
+static void
+nfsd3_fill_post_op_fh3(struct post_op_fh3 *post_op_fh,
+		       const struct svc_fh *fhp, u8 *data)
+{
+	if (fhp->fh_handle.fh_size != 0) {
+		post_op_fh->handle_follows = true;
+		nfsd3_svc_fh_to_fh3(&post_op_fh->u.handle, fhp, data);
+	} else {
+		post_op_fh->handle_follows = false;
+	}
+}
+
 /*
  * A full specification of each of the following NFSv3 procedures is
  * available in RFC 1813 Section 3.3.
@@ -761,59 +789,72 @@ static __be32 nfsd3_proc_write(struct svc_rqst *rqstp)
  * Implement NFSv3's unchecked, guarded, and exclusive CREATE
  * semantics for regular files. Except for the created file,
  * this operation is stateless on the server.
- *
- * Upon return, caller must release @fhp and @resfhp.
  */
 static __be32
-nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
-		  struct svc_fh *resfhp, struct nfsd3_createargs *argp)
+nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *dirfhp,
+		  struct svc_fh *resfhp, struct CREATE3args_wrapper *argp)
 {
-	struct iattr *iap = &argp->attrs;
+	struct diropargs3 *where = &argp->xdrgen.where;
+	struct createhow3 *how = &argp->xdrgen.how;
+	struct iattr *iattrs = &argp->iattrs;
 	struct dentry *parent, *child;
-	struct nfsd_attrs attrs = {
-		.na_iattr	= iap,
-	};
 	struct svc_export *exp;
 	__u32 v_mtime, v_atime;
 	struct inode *inode;
+	struct nfsd_attrs nattrs = {
+		.na_iattr	= iattrs,
+	};
 	__be32 status;
 	int host_err;
 
-	trace_nfsd_vfs_create(rqstp, fhp, S_IFREG, argp->name, argp->len);
-
-	if (!nfsd3_time_in_range(iap))
-		return nfserr_inval;
-	if (name_is_dot_dotdot(argp->name, argp->len))
-		return nfserr_exist;
-	if (!(iap->ia_valid & ATTR_MODE))
-		iap->ia_mode = 0;
-
-	status = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_EXEC);
+	status = nfsd3_check_filename(where->name.data, where->name.len);
 	if (status != nfs_ok)
 		return status;
 
-	parent = fhp->fh_dentry;
+	trace_nfsd_vfs_create(rqstp, dirfhp, S_IFREG, (char *)where->name.data,
+			      where->name.len);
+
+	if (name_is_dot_dotdot(where->name.data, where->name.len))
+		return nfserr_exist;
+
+	if (how->mode != EXCLUSIVE) {
+		nfsd3_sattr3_to_iattr(rqstp, iattrs,
+				      &how->u.obj_attributes);
+		if (!(iattrs->ia_valid & ATTR_MODE))
+			iattrs->ia_mode = 0;
+	} else {
+		memset(iattrs, 0, sizeof(*iattrs));
+	}
+
+	if (!nfsd3_time_in_range(iattrs))
+		return nfserr_inval;
+
+	status = fh_verify(rqstp, dirfhp, S_IFDIR, NFSD_MAY_EXEC);
+	if (status != nfs_ok)
+		return status;
+
+	parent = dirfhp->fh_dentry;
 	inode = d_inode(parent);
 
-	host_err = fh_want_write(fhp);
+	host_err = fh_want_write(dirfhp);
 	if (host_err)
 		return nfserrno(host_err);
 
 	child = start_creating(&nop_mnt_idmap, parent,
-			       &QSTR_LEN(argp->name, argp->len));
+			       &QSTR_LEN(where->name.data, where->name.len));
 	if (IS_ERR(child)) {
 		status = nfserrno(PTR_ERR(child));
 		goto out_write;
 	}
 
 	if (d_really_is_negative(child)) {
-		status = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE);
+		status = fh_verify(rqstp, dirfhp, S_IFDIR, NFSD_MAY_CREATE);
 		if (status != nfs_ok)
 			goto out;
 	}
 
-	exp = exp_get(fhp->fh_export);
-	if (argp->createmode == UNCHECKED) {
+	exp = exp_get(dirfhp->fh_export);
+	if (how->mode == UNCHECKED) {
 		/*
 		 * If name is already in dcache we need to check for mountpoints
 		 */
@@ -827,15 +868,15 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
 		}
 	}
 
-	status = fh_compose(resfhp, exp, child, fhp);
+	status = fh_compose(resfhp, exp, child, dirfhp);
 	exp_put(exp);
 	if (status != nfs_ok)
 		goto out;
 
 	v_mtime = 0;
 	v_atime = 0;
-	if (argp->createmode == EXCLUSIVE) {
-		u32 *verifier = (u32 *)argp->verf;
+	if (how->mode == EXCLUSIVE) {
+		u32 *verifier = (u32 *)how->u.verf;
 
 		/*
 		 * Solaris 7 gets confused (bugid 4218508) if these have
@@ -849,11 +890,11 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
 	if (d_really_is_positive(child)) {
 		status = nfs_ok;
 
-		switch (argp->createmode) {
+		switch (how->mode) {
 		case UNCHECKED:
 			if (!d_is_reg(child))
 				break;
-			iap->ia_valid &= ATTR_SIZE;
+			iattrs->ia_valid &= ATTR_SIZE;
 			goto set_attr;
 		case GUARDED:
 			status = nfserr_exist;
@@ -870,52 +911,77 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
 	}
 
 	if (!IS_POSIXACL(inode))
-		iap->ia_mode &= ~current_umask();
+		iattrs->ia_mode &= ~current_umask();
 
-	status = fh_fill_pre_attrs(fhp);
+	status = fh_fill_pre_attrs(dirfhp);
 	if (status != nfs_ok)
 		goto out;
-	host_err = vfs_create(&nop_mnt_idmap, child, iap->ia_mode, NULL);
+	host_err = vfs_create(&nop_mnt_idmap, child, iattrs->ia_mode, NULL);
 	if (host_err < 0) {
 		status = nfserrno(host_err);
 		goto out;
 	}
-	fh_fill_post_attrs(fhp);
+	fh_fill_post_attrs(dirfhp);
 
 	/* A newly created file already has a file size of zero. */
-	if ((iap->ia_valid & ATTR_SIZE) && (iap->ia_size == 0))
-		iap->ia_valid &= ~ATTR_SIZE;
-	if (argp->createmode == EXCLUSIVE) {
-		iap->ia_valid = ATTR_MTIME | ATTR_ATIME |
-				ATTR_MTIME_SET | ATTR_ATIME_SET;
-		iap->ia_mtime.tv_sec = v_mtime;
-		iap->ia_atime.tv_sec = v_atime;
-		iap->ia_mtime.tv_nsec = 0;
-		iap->ia_atime.tv_nsec = 0;
+	if ((iattrs->ia_valid & ATTR_SIZE) && iattrs->ia_size == 0)
+		iattrs->ia_valid &= ~ATTR_SIZE;
+	if (how->mode == EXCLUSIVE) {
+		iattrs->ia_valid = ATTR_MTIME | ATTR_ATIME |
+				   ATTR_MTIME_SET | ATTR_ATIME_SET;
+		iattrs->ia_mtime.tv_sec = v_mtime;
+		iattrs->ia_atime.tv_sec = v_atime;
+		iattrs->ia_mtime.tv_nsec = 0;
+		iattrs->ia_atime.tv_nsec = 0;
 	}
 
 set_attr:
-	status = nfsd_create_setattr(rqstp, fhp, resfhp, &attrs);
+	status = nfsd_create_setattr(rqstp, dirfhp, resfhp, &nattrs);
 
 out:
 	end_creating(child);
 out_write:
-	fh_drop_write(fhp);
+	fh_drop_write(dirfhp);
 	return status;
 }
 
-static __be32
-nfsd3_proc_create(struct svc_rqst *rqstp)
+/**
+ * nfsd3_proc_create - CREATE: Create a file
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   CREATE3res NFSPROC3_CREATE(CREATE3args) = 8;
+ */
+static __be32 nfsd3_proc_create(struct svc_rqst *rqstp)
 {
-	struct nfsd3_createargs *argp = rqstp->rq_argp;
-	struct nfsd3_diropres *resp = rqstp->rq_resp;
-	svc_fh *dirfhp, *newfhp;
+	struct CREATE3args_wrapper *argp = rqstp->rq_argp;
+	struct CREATE3res_wrapper *resp = rqstp->rq_resp;
+	struct svc_fh *dirfhp = &argp->fh;
+	struct svc_fh *fhp = &resp->fh;
 
-	dirfhp = fh_copy(&resp->dirfh, &argp->fh);
-	newfhp = fh_init(&resp->fh, NFS3_FHSIZE);
+	nfsd3_fh3_to_svc_fh(dirfhp, &argp->xdrgen.where.dir);
 
-	resp->status = nfsd3_create_file(rqstp, dirfhp, newfhp, argp);
-	resp->status = nfsd3_map_status(resp->status);
+	fh_init(fhp, NFS3_FHSIZE);
+	resp->xdrgen.status = nfsd3_create_file(rqstp, dirfhp, fhp, argp);
+
+	if (resp->xdrgen.status == nfs_ok) {
+		struct CREATE3resok *resok = &resp->xdrgen.u.resok;
+
+		nfsd3_fill_post_op_fh3(&resok->obj, fhp, resp->fh_data);
+		nfsd3_fill_post_op_attr(rqstp, &resok->obj_attributes, fhp);
+		nfsd3_fill_wcc_data(rqstp, &resok->dir_wcc, dirfhp);
+	} else {
+		struct CREATE3resfail *resfail = &resp->xdrgen.u.resfail;
+
+		resp->xdrgen.status = nfsd3_map_status(resp->xdrgen.status);
+		nfsd3_fill_wcc_data(rqstp, &resfail->dir_wcc, dirfhp);
+	}
+
+	fh_put(fhp);
+	fh_put(dirfhp);
 	return rpc_success;
 }
 
@@ -1445,16 +1511,15 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "WRITE",
 	},
 	[NFSPROC3_CREATE] = {
-		.pc_func = nfsd3_proc_create,
-		.pc_decode = nfs3svc_decode_createargs,
-		.pc_encode = nfs3svc_encode_createres,
-		.pc_release = nfs3svc_release_fhandle2,
-		.pc_argsize = sizeof(struct nfsd3_createargs),
-		.pc_argzero = sizeof(struct nfsd3_createargs),
-		.pc_ressize = sizeof(struct nfsd3_diropres),
-		.pc_cachetype = RC_REPLBUFF,
-		.pc_xdrressize = ST+(1+FH+pAT)+WC,
-		.pc_name = "CREATE",
+		.pc_func	= nfsd3_proc_create,
+		.pc_decode	= nfs_svc_decode_CREATE3args,
+		.pc_encode	= nfs_svc_encode_CREATE3res,
+		.pc_argsize	= sizeof(struct CREATE3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct CREATE3res_wrapper),
+		.pc_cachetype	= RC_REPLBUFF,
+		.pc_xdrressize	= NFS3_CREATE3res_sz,
+		.pc_name	= "CREATE",
 	},
 	[NFSPROC3_MKDIR] = {
 		.pc_func = nfsd3_proc_mkdir,
@@ -1625,7 +1690,8 @@ union nfsd3_xdrstore {
 	struct READ3args_wrapper	readargs;
 	struct nfsd3_diropargs		diropargs;
 	struct WRITE3args_wrapper	writeargs;
-	struct nfsd3_createargs		createargs;
+	struct CREATE3args_wrapper	createargs;
+	struct CREATE3res_wrapper	createres;
 	struct nfsd3_renameargs		renameargs;
 	struct nfsd3_linkargs		linkargs;
 	struct nfsd3_symlinkargs	symlinkargs;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index cc3aebd97959..1bdfe5398031 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -490,30 +490,6 @@ nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 	return svcxdr_decode_diropargs3(xdr, &args->fh, &args->name, &args->len);
 }
 
-bool
-nfs3svc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_createargs *args = rqstp->rq_argp;
-
-	if (!svcxdr_decode_diropargs3(xdr, &args->fh, &args->name, &args->len))
-		return false;
-	if (xdr_stream_decode_u32(xdr, &args->createmode) < 0)
-		return false;
-	switch (args->createmode) {
-	case UNCHECKED:
-	case GUARDED:
-		return svcxdr_decode_sattr3(rqstp, xdr, &args->attrs);
-	case EXCLUSIVE:
-		args->verf = xdr_inline_decode(xdr, NFS3_CREATEVERFSIZE);
-		if (!args->verf)
-			return false;
-		break;
-	default:
-		return false;
-	}
-	return true;
-}
-
 bool
 nfs3svc_decode_mkdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 {
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 9c41244d27bb..30347cce5517 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -207,7 +207,6 @@ struct nfsd3_fhandle_pair {
 
 bool nfs3svc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_mkdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_mknodargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_renameargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-- 
2.55.0


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

* [PATCH v1 16/33] NFSD: Use xdrgen XDR functions for NFSv3 MKDIR procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (14 preceding siblings ...)
  2026-09-23 13:27 ` [PATCH v1 15/33] NFSD: Use xdrgen XDR functions for NFSv3 CREATE procedure Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 17/33] NFSD: Use xdrgen XDR functions for NFSv3 SYMLINK procedure Chuck Lever
                   ` (16 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_MKDIR entry in the nfsd_procedures3 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_MKDIR3args and nfs_svc_encode_MKDIR3res. Wrapper
structures bridge the generated xdrgen types and the legacy
svc_fh and iattr representations the NFSD VFS layer still uses.

Filename validity is now checked by nfsd3_proc_mkdir() so that a
proper NFS3ERR status code can be returned to the client rather
than GARBAGE_ARGS.

The pc_argzero field is now set to zero for the NFSv3 MKDIR
procedure. The xdrgen decoders are trusted to initialize all
arguments in the argp->xdrgen field, making the early defensive
memset unnecessary. The remaining argp fields are cleared as
needed.

Because nfsd3_proc_mkdir() now fills in the post-op file handle,
the post-op attributes, and the WCC data, the fh_getattr() calls
are made in the proc function rather than in the XDR result
encoder, and the references to the file handles can be released
directly by nfsd3_proc_mkdir(). A separate ->pc_release callback
is thus no longer needed. This makes it straightforward to
confirm that the file handles are properly released during every
error flow.

nfs3svc_decode_mkdirargs() no longer has any callers, so it is
removed.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs3proc.c | 102 +++++++++++++++++++++++++++++++++------------
 fs/nfsd/nfs3xdr.c  |  10 -----
 fs/nfsd/xdr3.h     |   3 --
 3 files changed, 75 insertions(+), 40 deletions(-)

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index eb8773e6ae17..64e5be5fecee 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -115,6 +115,22 @@ struct CREATE3res_wrapper {
 
 static_assert(offsetof(struct CREATE3res_wrapper, xdrgen) == 0);
 
+struct MKDIR3args_wrapper {
+	struct MKDIR3args	xdrgen;
+	struct svc_fh		fh;
+	struct iattr		iattrs;
+};
+
+static_assert(offsetof(struct MKDIR3args_wrapper, xdrgen) == 0);
+
+struct MKDIR3res_wrapper {
+	struct MKDIR3res	xdrgen;
+	struct svc_fh		fh;
+	u8			fh_data[NFS3_FHSIZE];
+};
+
+static_assert(offsetof(struct MKDIR3res_wrapper, xdrgen) == 0);
+
 static int	nfs3_ftypes[] = {
 	0,			/* NF3NON */
 	S_IFREG,		/* NF3REG */
@@ -985,29 +1001,61 @@ static __be32 nfsd3_proc_create(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-/*
- * Make directory. This operation is not idempotent.
+/**
+ * nfsd3_proc_mkdir - MKDIR: Create a directory
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   MKDIR3res NFSPROC3_MKDIR(MKDIR3args) = 9;
  */
-static __be32
-nfsd3_proc_mkdir(struct svc_rqst *rqstp)
+static __be32 nfsd3_proc_mkdir(struct svc_rqst *rqstp)
 {
-	struct nfsd3_createargs *argp = rqstp->rq_argp;
-	struct nfsd3_diropres *resp = rqstp->rq_resp;
-	struct nfsd_attrs attrs = {
-		.na_iattr	= &argp->attrs,
+	struct MKDIR3args_wrapper *argp = rqstp->rq_argp;
+	struct diropargs3 *where = &argp->xdrgen.where;
+	struct MKDIR3res_wrapper *resp = rqstp->rq_resp;
+	struct iattr *iattrs = &argp->iattrs;
+	struct svc_fh *dirfhp = &argp->fh;
+	struct svc_fh *fhp = &resp->fh;
+	struct nfsd_attrs nattrs = {
+		.na_iattr	= iattrs,
 	};
 
-	argp->attrs.ia_valid &= ~ATTR_SIZE;
-	fh_copy(&resp->dirfh, &argp->fh);
-	fh_init(&resp->fh, NFS3_FHSIZE);
-	if (!nfsd3_time_in_range(&argp->attrs)) {
-		resp->status = nfserr_inval;
+	nfsd3_fh3_to_svc_fh(dirfhp, &where->dir);
+	resp->xdrgen.status = nfsd3_check_filename(where->name.data,
+						   where->name.len);
+	if (resp->xdrgen.status != nfs_ok)
+		goto out;
+	nfsd3_sattr3_to_iattr(rqstp, iattrs, &argp->xdrgen.attributes);
+	if (!nfsd3_time_in_range(iattrs)) {
+		resp->xdrgen.status = nfserr_inval;
 		goto out;
 	}
-	resp->status = nfsd_create(rqstp, &resp->dirfh, argp->name, argp->len,
-				   &attrs, S_IFDIR, 0, &resp->fh);
+
+	fh_init(fhp, NFS3_FHSIZE);
+	iattrs->ia_valid &= ~ATTR_SIZE;
+	resp->xdrgen.status = nfsd_create(rqstp, dirfhp, (char *)where->name.data,
+					  where->name.len, &nattrs, S_IFDIR,
+					  0, fhp);
+
 out:
-	resp->status = nfsd3_map_status(resp->status);
+	if (resp->xdrgen.status == nfs_ok) {
+		struct MKDIR3resok *resok = &resp->xdrgen.u.resok;
+
+		nfsd3_fill_post_op_fh3(&resok->obj, fhp, resp->fh_data);
+		nfsd3_fill_post_op_attr(rqstp, &resok->obj_attributes, fhp);
+		nfsd3_fill_wcc_data(rqstp, &resok->dir_wcc, dirfhp);
+	} else {
+		struct MKDIR3resfail *resfail = &resp->xdrgen.u.resfail;
+
+		resp->xdrgen.status = nfsd3_map_status(resp->xdrgen.status);
+		nfsd3_fill_wcc_data(rqstp, &resfail->dir_wcc, dirfhp);
+	}
+
+	fh_put(fhp);
+	fh_put(dirfhp);
 	return rpc_success;
 }
 
@@ -1407,7 +1455,6 @@ nfsd3_proc_commit(struct svc_rqst *rqstp)
  * Only the results of non-idempotent operations are cached.
  */
 #define nfs3svc_encode_wccstatres	nfs3svc_encode_wccstat
-#define nfsd3_mkdirargs			nfsd3_createargs
 #define nfsd3_readdirplusargs		nfsd3_readdirargs
 #define nfsd3_fhandleargs		nfsd_fhandle
 #define nfsd3_wccstatres		nfsd3_attrstat
@@ -1522,16 +1569,15 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "CREATE",
 	},
 	[NFSPROC3_MKDIR] = {
-		.pc_func = nfsd3_proc_mkdir,
-		.pc_decode = nfs3svc_decode_mkdirargs,
-		.pc_encode = nfs3svc_encode_createres,
-		.pc_release = nfs3svc_release_fhandle2,
-		.pc_argsize = sizeof(struct nfsd3_mkdirargs),
-		.pc_argzero = sizeof(struct nfsd3_mkdirargs),
-		.pc_ressize = sizeof(struct nfsd3_diropres),
-		.pc_cachetype = RC_REPLBUFF,
-		.pc_xdrressize = ST+(1+FH+pAT)+WC,
-		.pc_name = "MKDIR",
+		.pc_func	= nfsd3_proc_mkdir,
+		.pc_decode	= nfs_svc_decode_MKDIR3args,
+		.pc_encode	= nfs_svc_encode_MKDIR3res,
+		.pc_argsize	= sizeof(struct MKDIR3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct MKDIR3res_wrapper),
+		.pc_cachetype	= RC_REPLBUFF,
+		.pc_xdrressize	= NFS3_MKDIR3res_sz,
+		.pc_name	= "MKDIR",
 	},
 	[NFSPROC3_SYMLINK] = {
 		.pc_func = nfsd3_proc_symlink,
@@ -1692,6 +1738,8 @@ union nfsd3_xdrstore {
 	struct WRITE3args_wrapper	writeargs;
 	struct CREATE3args_wrapper	createargs;
 	struct CREATE3res_wrapper	createres;
+	struct MKDIR3args_wrapper	mkdirargs;
+	struct MKDIR3res_wrapper	mkdirres;
 	struct nfsd3_renameargs		renameargs;
 	struct nfsd3_linkargs		linkargs;
 	struct nfsd3_symlinkargs	symlinkargs;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index 1bdfe5398031..67cf76c13636 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -490,16 +490,6 @@ nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 	return svcxdr_decode_diropargs3(xdr, &args->fh, &args->name, &args->len);
 }
 
-bool
-nfs3svc_decode_mkdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_createargs *args = rqstp->rq_argp;
-
-	return svcxdr_decode_diropargs3(xdr, &args->fh,
-					&args->name, &args->len) &&
-		svcxdr_decode_sattr3(rqstp, xdr, &args->attrs);
-}
-
 bool
 nfs3svc_decode_symlinkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 {
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 30347cce5517..04eda3c9c0fb 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -33,8 +33,6 @@ struct nfsd3_accessargs {
 struct nfsd3_createargs {
 	struct svc_fh		fh;
 	char *			name;
-	unsigned int		len;
-	int			createmode;
 	struct iattr		attrs;
 	__be32 *		verf;
 };
@@ -207,7 +205,6 @@ struct nfsd3_fhandle_pair {
 
 bool nfs3svc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_decode_mkdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_mknodargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_renameargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_linkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-- 
2.55.0


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

* [PATCH v1 17/33] NFSD: Use xdrgen XDR functions for NFSv3 SYMLINK procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (15 preceding siblings ...)
  2026-09-23 13:27 ` [PATCH v1 16/33] NFSD: Use xdrgen XDR functions for NFSv3 MKDIR procedure Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 18/33] NFSD: Use xdrgen XDR functions for NFSv3 MKNOD procedure Chuck Lever
                   ` (15 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_SYMLINK entry in the nfsd_procedures3 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_SYMLINK3args and nfs_svc_encode_SYMLINK3res. Wrapper
structures bridge the generated xdrgen types and the legacy
svc_fh and iattr representations the NFSD VFS layer still uses.

A SYMLINK request's target pathname can extend from the head of
the receive buffer into its page data; nfsd3_proc_symlink()
reassembles it from a head segment and a page via
svc_fill_symlink_pathname(). Annotate the symlink_data member of
symlinkdata3 with "pragma pages" so the generated decoder
represents the pathname as a struct xdr_buf and decodes it with
svcxdr_decode_opaque_payload(), which subsegments the receive
stream instead of pulling the string inline through the bounded
xdr scratch buffer.

Filename validity is now checked by nfsd3_proc_symlink() so that a
proper NFS3ERR status code can be returned to the client rather
than GARBAGE_ARGS.

The pc_argzero field is now set to zero for the NFSv3 SYMLINK
procedure. The xdrgen decoders are trusted to initialize all
arguments in the argp->xdrgen field, making the early defensive
memset unnecessary. The remaining argp fields are cleared as
needed.

Because nfsd3_proc_symlink() now fills in the post-op file handle,
the post-op attributes, and the WCC data, the fh_getattr() calls
are made in the proc function rather than in the XDR result
encoder, and the references to the file handles can be released
directly by nfsd3_proc_symlink(). A separate ->pc_release callback
is thus no longer needed. This makes it straightforward to
confirm that the file handles are properly released during every
error flow.

nfs3svc_decode_symlinkargs() no longer has any callers, so it is
removed.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 Documentation/sunrpc/xdr/nfs3.x    |   1 +
 fs/nfsd/nfs3proc.c                 | 122 +++++++++++++++++++++--------
 fs/nfsd/nfs3xdr.c                  |  19 -----
 fs/nfsd/nfs3xdr_gen.c              |   6 +-
 fs/nfsd/nfs3xdr_gen.h              |   2 +-
 fs/nfsd/xdr3.h                     |  11 ---
 include/linux/sunrpc/xdrgen/nfs3.h |   4 +-
 7 files changed, 95 insertions(+), 70 deletions(-)

diff --git a/Documentation/sunrpc/xdr/nfs3.x b/Documentation/sunrpc/xdr/nfs3.x
index 61b87245be1e..c987788bf34c 100644
--- a/Documentation/sunrpc/xdr/nfs3.x
+++ b/Documentation/sunrpc/xdr/nfs3.x
@@ -464,6 +464,7 @@ struct symlinkdata3 {
 	sattr3		symlink_attributes;
 	nfspath3	symlink_data;
 };
+pragma pages symlinkdata3 symlink_data;
 
 struct SYMLINK3args {
 	diropargs3	where;
diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 64e5be5fecee..97c68c51f9a5 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -131,6 +131,22 @@ struct MKDIR3res_wrapper {
 
 static_assert(offsetof(struct MKDIR3res_wrapper, xdrgen) == 0);
 
+struct SYMLINK3args_wrapper {
+	struct SYMLINK3args	xdrgen;
+	struct svc_fh		fh;
+	struct iattr		iattrs;
+};
+
+static_assert(offsetof(struct SYMLINK3args_wrapper, xdrgen) == 0);
+
+struct SYMLINK3res_wrapper {
+	struct SYMLINK3res	xdrgen;
+	struct svc_fh		fh;
+	u8			fh_data[NFS3_FHSIZE];
+};
+
+static_assert(offsetof(struct SYMLINK3res_wrapper, xdrgen) == 0);
+
 static int	nfs3_ftypes[] = {
 	0,			/* NF3NON */
 	S_IFREG,		/* NF3REG */
@@ -1059,43 +1075,81 @@ static __be32 nfsd3_proc_mkdir(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-static __be32
-nfsd3_proc_symlink(struct svc_rqst *rqstp)
+/**
+ * nfsd3_proc_symlink - SYMLINK: Create a symbolic link
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   SYMLINK3res NFSPROC3_SYMLINK(SYMLINK3args) = 10;
+ */
+static __be32 nfsd3_proc_symlink(struct svc_rqst *rqstp)
 {
-	struct nfsd3_symlinkargs *argp = rqstp->rq_argp;
-	struct nfsd3_diropres *resp = rqstp->rq_resp;
-	struct nfsd_attrs attrs = {
-		.na_iattr	= &argp->attrs,
+	struct SYMLINK3args_wrapper *argp = rqstp->rq_argp;
+	struct symlinkdata3 *symlink = &argp->xdrgen.symlink;
+	struct xdr_buf *to = &symlink->symlink_data;
+	struct diropargs3 *where = &argp->xdrgen.where;
+	struct SYMLINK3res_wrapper *resp = rqstp->rq_resp;
+	struct iattr *iattrs = &argp->iattrs;
+	struct svc_fh *dirfhp = &argp->fh;
+	struct svc_fh *fhp = &resp->fh;
+	struct nfsd_attrs nattrs = {
+		.na_iattr	= iattrs,
 	};
+	char *tname;
+	void *page;
 
-	if (!nfsd3_time_in_range(&argp->attrs)) {
-		resp->status = nfserr_inval;
+	nfsd3_fh3_to_svc_fh(dirfhp, &where->dir);
+	resp->xdrgen.status = nfsd3_check_filename(where->name.data,
+						   where->name.len);
+	if (resp->xdrgen.status != nfs_ok)
+		goto out;
+	nfsd3_sattr3_to_iattr(rqstp, iattrs, &symlink->symlink_attributes);
+	if (!nfsd3_time_in_range(iattrs)) {
+		resp->xdrgen.status = nfserr_inval;
 		goto out;
 	}
-	if (argp->tlen == 0) {
-		resp->status = nfserr_inval;
+	if (to->len == 0) {
+		resp->xdrgen.status = nfserr_inval;
 		goto out;
 	}
-	if (argp->tlen > NFS3_MAXPATHLEN) {
-		resp->status = nfserr_nametoolong;
+	if (to->len > NFS3_MAXPATHLEN) {
+		resp->xdrgen.status = nfserr_nametoolong;
 		goto out;
 	}
 
-	argp->tname = svc_fill_symlink_pathname(rqstp, &argp->first,
-						page_address(rqstp->rq_arg.pages[0]),
-						argp->tlen);
-	if (IS_ERR(argp->tname)) {
-		resp->status = nfserrno(PTR_ERR(argp->tname));
+	page = to->page_len ? page_address(to->pages[0]) + to->page_base : NULL;
+	tname = svc_fill_symlink_pathname(rqstp, &to->head[0], page, to->len);
+	if (IS_ERR(tname)) {
+		resp->xdrgen.status = nfserrno(PTR_ERR(tname));
 		goto out;
 	}
 
-	fh_copy(&resp->dirfh, &argp->ffh);
-	fh_init(&resp->fh, NFS3_FHSIZE);
-	resp->status = nfsd_symlink(rqstp, &resp->dirfh, argp->fname,
-				    argp->flen, argp->tname, &attrs, &resp->fh);
-	kfree(argp->tname);
+	fh_init(fhp, NFS3_FHSIZE);
+	resp->xdrgen.status = nfsd_symlink(rqstp, dirfhp,
+					   (char *)where->name.data,
+					   where->name.len, tname,
+					   &nattrs, fhp);
+	kfree(tname);
+
 out:
-	resp->status = nfsd3_map_status(resp->status);
+	if (resp->xdrgen.status == nfs_ok) {
+		struct SYMLINK3resok *resok = &resp->xdrgen.u.resok;
+
+		nfsd3_fill_post_op_fh3(&resok->obj, fhp, resp->fh_data);
+		nfsd3_fill_post_op_attr(rqstp, &resok->obj_attributes, fhp);
+		nfsd3_fill_wcc_data(rqstp, &resok->dir_wcc, dirfhp);
+	} else {
+		struct SYMLINK3resfail *resfail = &resp->xdrgen.u.resfail;
+
+		resp->xdrgen.status = nfsd3_map_status(resp->xdrgen.status);
+		nfsd3_fill_wcc_data(rqstp, &resfail->dir_wcc, dirfhp);
+	}
+
+	fh_put(fhp);
+	fh_put(dirfhp);
 	return rpc_success;
 }
 
@@ -1580,16 +1634,15 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "MKDIR",
 	},
 	[NFSPROC3_SYMLINK] = {
-		.pc_func = nfsd3_proc_symlink,
-		.pc_decode = nfs3svc_decode_symlinkargs,
-		.pc_encode = nfs3svc_encode_createres,
-		.pc_release = nfs3svc_release_fhandle2,
-		.pc_argsize = sizeof(struct nfsd3_symlinkargs),
-		.pc_argzero = sizeof(struct nfsd3_symlinkargs),
-		.pc_ressize = sizeof(struct nfsd3_diropres),
-		.pc_cachetype = RC_REPLBUFF,
-		.pc_xdrressize = ST+(1+FH+pAT)+WC,
-		.pc_name = "SYMLINK",
+		.pc_func	= nfsd3_proc_symlink,
+		.pc_decode	= nfs_svc_decode_SYMLINK3args,
+		.pc_encode	= nfs_svc_encode_SYMLINK3res,
+		.pc_argsize	= sizeof(struct SYMLINK3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct SYMLINK3res_wrapper),
+		.pc_cachetype	= RC_REPLBUFF,
+		.pc_xdrressize	= NFS3_SYMLINK3res_sz,
+		.pc_name	= "SYMLINK",
 	},
 	[NFSPROC3_MKNOD] = {
 		.pc_func = nfsd3_proc_mknod,
@@ -1740,9 +1793,10 @@ union nfsd3_xdrstore {
 	struct CREATE3res_wrapper	createres;
 	struct MKDIR3args_wrapper	mkdirargs;
 	struct MKDIR3res_wrapper	mkdirres;
+	struct SYMLINK3args_wrapper	symlinkargs;
+	struct SYMLINK3res_wrapper	symlinkres;
 	struct nfsd3_renameargs		renameargs;
 	struct nfsd3_linkargs		linkargs;
-	struct nfsd3_symlinkargs	symlinkargs;
 	struct nfsd3_readdirargs	readdirargs;
 	struct nfsd3_diropres 		diropres;
 	struct READLINK3res		readlinkres;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index 67cf76c13636..68f00ff604be 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -490,25 +490,6 @@ nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 	return svcxdr_decode_diropargs3(xdr, &args->fh, &args->name, &args->len);
 }
 
-bool
-nfs3svc_decode_symlinkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_symlinkargs *args = rqstp->rq_argp;
-	struct kvec *head = rqstp->rq_arg.head;
-
-	if (!svcxdr_decode_diropargs3(xdr, &args->ffh, &args->fname, &args->flen))
-		return false;
-	if (!svcxdr_decode_sattr3(rqstp, xdr, &args->attrs))
-		return false;
-	if (xdr_stream_decode_u32(xdr, &args->tlen) < 0)
-		return false;
-
-	/* symlink_data */
-	args->first.iov_len = head->iov_len - xdr_stream_pos(xdr);
-	args->first.iov_base = xdr_inline_decode(xdr, args->tlen);
-	return args->first.iov_base != NULL;
-}
-
 bool
 nfs3svc_decode_mknodargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 {
diff --git a/fs/nfsd/nfs3xdr_gen.c b/fs/nfsd/nfs3xdr_gen.c
index 159aff1a8567..3667c160e789 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 11:43:26 2026
+// XDR specification modification time: Tue Jul 14 12:13:57 2026
 
 #include <linux/sunrpc/svc.h>
 
@@ -915,7 +915,7 @@ xdrgen_decode_symlinkdata3(struct xdr_stream *xdr, struct symlinkdata3 *ptr)
 {
 	if (!xdrgen_decode_sattr3(xdr, &ptr->symlink_attributes))
 		return false;
-	if (!xdrgen_decode_nfspath3(xdr, &ptr->symlink_data))
+	if (!svcxdr_decode_opaque_payload(xdr, &ptr->symlink_data, 0))
 		return false;
 	return true;
 }
@@ -2748,7 +2748,7 @@ xdrgen_encode_symlinkdata3(struct xdr_stream *xdr, const struct symlinkdata3 *va
 {
 	if (!xdrgen_encode_sattr3(xdr, &value->symlink_attributes))
 		return false;
-	if (!xdrgen_encode_nfspath3(xdr, value->symlink_data))
+	if (!svcxdr_encode_opaque_payload(xdr, value->symlink_data.len))
 		return false;
 	return true;
 }
diff --git a/fs/nfsd/nfs3xdr_gen.h b/fs/nfsd/nfs3xdr_gen.h
index 98e29c8efc5e..f336ffa92a7a 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 11:43:26 2026 */
+/* XDR specification modification time: Tue Jul 14 12:13:57 2026 */
 
 #ifndef _LINUX_XDRGEN_NFS3_DECL_H
 #define _LINUX_XDRGEN_NFS3_DECL_H
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 04eda3c9c0fb..11a4e7cb65f6 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -62,16 +62,6 @@ struct nfsd3_linkargs {
 	unsigned int		tlen;
 };
 
-struct nfsd3_symlinkargs {
-	struct svc_fh		ffh;
-	char *			fname;
-	unsigned int		flen;
-	char *			tname;
-	unsigned int		tlen;
-	struct iattr		attrs;
-	struct kvec		first;
-};
-
 struct nfsd3_readdirargs {
 	struct svc_fh		fh;
 	__u64			cookie;
@@ -208,7 +198,6 @@ bool nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_mknodargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_renameargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_linkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_decode_symlinkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_readdirplusargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
diff --git a/include/linux/sunrpc/xdrgen/nfs3.h b/include/linux/sunrpc/xdrgen/nfs3.h
index 3b6738c0074b..3e24abb1d2e5 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 11:43:26 2026 */
+/* XDR specification modification time: Tue Jul 14 12:13:57 2026 */
 
 #ifndef _LINUX_XDRGEN_NFS3_DEF_H
 #define _LINUX_XDRGEN_NFS3_DEF_H
@@ -468,7 +468,7 @@ struct MKDIR3res {
 
 struct symlinkdata3 {
 	struct sattr3 symlink_attributes;
-	nfspath3 symlink_data;
+	struct xdr_buf symlink_data;
 };
 
 struct SYMLINK3args {
-- 
2.55.0


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

* [PATCH v1 18/33] NFSD: Use xdrgen XDR functions for NFSv3 MKNOD procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (16 preceding siblings ...)
  2026-09-23 13:27 ` [PATCH v1 17/33] NFSD: Use xdrgen XDR functions for NFSv3 SYMLINK procedure Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 19/33] NFSD: Use xdrgen XDR functions for the NFSv3 REMOVE procedure Chuck Lever
                   ` (14 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_MKNOD entry in the nfsd_procedures3 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_MKNOD3args and nfs_svc_encode_MKNOD3res. Wrapper
structures bridge the generated xdrgen types and the legacy
svc_fh and iattr representations the NFSD VFS layer still uses.

Filename validity is now checked by nfsd3_proc_mknod() so that a
proper NFS3ERR status code can be returned to the client rather
than GARBAGE_ARGS.

The pc_argzero field is now set to zero for the NFSv3 MKNOD
procedure. The xdrgen decoders are trusted to initialize all
arguments in the argp->xdrgen field, making the early defensive
memset unnecessary. The remaining argp fields are cleared as
needed.

Because nfsd3_proc_mknod() now fills in the post-op file handle,
the post-op attributes, and the WCC data, the fh_getattr() calls
are made in the proc function rather than in the XDR result
encoder, and the references to the file handles can be released
directly by nfsd3_proc_mknod(). A separate ->pc_release callback
is thus no longer needed. This makes it straightforward to
confirm that the file handles are properly released during every
error flow.

nfs3svc_decode_mknodargs() and nfs3svc_encode_createres() no
longer have any callers, so they are removed. MKNOD was the last
consumer of the hand-coded sattr3 decoder, so svcxdr_decode_sattr3()
and its helpers svcxdr_decode_nfstime3(), svcxdr_decode_specdata3(),
and svcxdr_decode_devicedata3() are removed as well.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs3proc.c | 154 +++++++++++++++++++++++++++------------
 fs/nfsd/nfs3xdr.c  | 175 ---------------------------------------------
 fs/nfsd/xdr3.h     |  18 -----
 3 files changed, 107 insertions(+), 240 deletions(-)

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 97c68c51f9a5..e178cb7fa206 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -147,17 +147,37 @@ struct SYMLINK3res_wrapper {
 
 static_assert(offsetof(struct SYMLINK3res_wrapper, xdrgen) == 0);
 
-static int	nfs3_ftypes[] = {
-	0,			/* NF3NON */
-	S_IFREG,		/* NF3REG */
-	S_IFDIR,		/* NF3DIR */
-	S_IFBLK,		/* NF3BLK */
-	S_IFCHR,		/* NF3CHR */
-	S_IFLNK,		/* NF3LNK */
-	S_IFSOCK,		/* NF3SOCK */
-	S_IFIFO,		/* NF3FIFO */
+struct MKNOD3args_wrapper {
+	struct MKNOD3args	xdrgen;
+	struct svc_fh		fh;
+	struct iattr		iattrs;
 };
 
+static_assert(offsetof(struct MKNOD3args_wrapper, xdrgen) == 0);
+
+struct MKNOD3res_wrapper {
+	struct MKNOD3res	xdrgen;
+	struct svc_fh		fh;
+	u8			fh_data[NFS3_FHSIZE];
+};
+
+static_assert(offsetof(struct MKNOD3res_wrapper, xdrgen) == 0);
+
+static int nfsd3_ftype3_to_mode(ftype3 ftype)
+{
+	switch (ftype) {
+	case NF3REG:  return S_IFREG;
+	case NF3DIR:  return S_IFDIR;
+	case NF3BLK:  return S_IFBLK;
+	case NF3CHR:  return S_IFCHR;
+	case NF3LNK:  return S_IFLNK;
+	case NF3SOCK: return S_IFSOCK;
+	case NF3FIFO: return S_IFIFO;
+	default:
+		return 0;
+	}
+}
+
 /*
  * Reject a client-supplied atime or mtime whose nanoseconds field is out
  * of range. Such a value is well-formed on the wire but is not a valid
@@ -1153,45 +1173,86 @@ static __be32 nfsd3_proc_symlink(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-/*
- * Make socket/fifo/device.
+/**
+ * nfsd3_proc_mknod - MKNOD: Create a special file
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   MKNOD3res NFSPROC3_MKNOD(MKNOD3args) = 11;
  */
-static __be32
-nfsd3_proc_mknod(struct svc_rqst *rqstp)
+static __be32 nfsd3_proc_mknod(struct svc_rqst *rqstp)
 {
-	struct nfsd3_mknodargs *argp = rqstp->rq_argp;
-	struct nfsd3_diropres  *resp = rqstp->rq_resp;
-	struct nfsd_attrs attrs = {
-		.na_iattr	= &argp->attrs,
+	struct MKNOD3args_wrapper *argp = rqstp->rq_argp;
+	struct diropargs3 *where = &argp->xdrgen.where;
+	struct mknoddata3 *what = &argp->xdrgen.what;
+	struct MKNOD3res_wrapper *resp = rqstp->rq_resp;
+	struct iattr *iattrs = &argp->iattrs;
+	struct svc_fh *dirfhp = &argp->fh;
+	struct svc_fh *fhp = &resp->fh;
+	struct nfsd_attrs nattrs = {
+		.na_iattr	= iattrs,
 	};
-	int type;
-	dev_t	rdev = 0;
+	dev_t rdev = 0;
 
-	fh_copy(&resp->dirfh, &argp->fh);
-	fh_init(&resp->fh, NFS3_FHSIZE);
-
-	if (argp->ftype == NF3CHR || argp->ftype == NF3BLK) {
-		rdev = MKDEV(argp->major, argp->minor);
-		if (MAJOR(rdev) != argp->major ||
-		    MINOR(rdev) != argp->minor) {
-			resp->status = nfserr_inval;
+	nfsd3_fh3_to_svc_fh(dirfhp, &where->dir);
+	resp->xdrgen.status = nfsd3_check_filename(where->name.data,
+						   where->name.len);
+	if (resp->xdrgen.status != nfs_ok)
+		goto out;
+	switch (what->type) {
+	case NF3CHR:
+	case NF3BLK:
+		rdev = MKDEV(what->u.device.spec.specdata1,
+			     what->u.device.spec.specdata2);
+		if (MAJOR(rdev) != what->u.device.spec.specdata1 ||
+		    MINOR(rdev) != what->u.device.spec.specdata2) {
+			resp->xdrgen.status = nfserr_inval;
 			goto out;
 		}
-	} else if (argp->ftype != NF3SOCK && argp->ftype != NF3FIFO) {
-		resp->status = nfserr_badtype;
+		nfsd3_sattr3_to_iattr(rqstp, iattrs,
+				      &what->u.device.dev_attributes);
+		break;
+	case NF3SOCK:
+	case NF3FIFO:
+		nfsd3_sattr3_to_iattr(rqstp, iattrs,
+				      &what->u.pipe_attributes);
+		break;
+	default:
+		resp->xdrgen.status = nfserr_badtype;
 		goto out;
 	}
 
-	if (!nfsd3_time_in_range(&argp->attrs)) {
-		resp->status = nfserr_inval;
+	if (!nfsd3_time_in_range(iattrs)) {
+		resp->xdrgen.status = nfserr_inval;
 		goto out;
 	}
 
-	type = nfs3_ftypes[argp->ftype];
-	resp->status = nfsd_create(rqstp, &resp->dirfh, argp->name, argp->len,
-				   &attrs, type, rdev, &resp->fh);
+	fh_init(fhp, NFS3_FHSIZE);
+	resp->xdrgen.status = nfsd_create(rqstp, dirfhp,
+					  (char *)where->name.data,
+					  where->name.len, &nattrs,
+					  nfsd3_ftype3_to_mode(what->type),
+					  rdev, fhp);
+
 out:
-	resp->status = nfsd3_map_status(resp->status);
+	if (resp->xdrgen.status == nfs_ok) {
+		struct MKNOD3resok *resok = &resp->xdrgen.u.resok;
+
+		nfsd3_fill_post_op_fh3(&resok->obj, fhp, resp->fh_data);
+		nfsd3_fill_post_op_attr(rqstp, &resok->obj_attributes, fhp);
+		nfsd3_fill_wcc_data(rqstp, &resok->dir_wcc, dirfhp);
+	} else {
+		struct MKNOD3resfail *resfail = &resp->xdrgen.u.resfail;
+
+		resp->xdrgen.status = nfsd3_map_status(resp->xdrgen.status);
+		nfsd3_fill_wcc_data(rqstp, &resfail->dir_wcc, dirfhp);
+	}
+
+	fh_put(fhp);
+	fh_put(dirfhp);
 	return rpc_success;
 }
 
@@ -1514,7 +1575,6 @@ nfsd3_proc_commit(struct svc_rqst *rqstp)
 #define nfsd3_wccstatres		nfsd3_attrstat
 
 #define ST 1		/* status*/
-#define FH 17		/* filehandle with length */
 #define AT 21		/* attributes */
 #define pAT (1+AT)	/* post attributes - conditional */
 #define WC (7+pAT)	/* WCC attributes */
@@ -1645,16 +1705,15 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "SYMLINK",
 	},
 	[NFSPROC3_MKNOD] = {
-		.pc_func = nfsd3_proc_mknod,
-		.pc_decode = nfs3svc_decode_mknodargs,
-		.pc_encode = nfs3svc_encode_createres,
-		.pc_release = nfs3svc_release_fhandle2,
-		.pc_argsize = sizeof(struct nfsd3_mknodargs),
-		.pc_argzero = sizeof(struct nfsd3_mknodargs),
-		.pc_ressize = sizeof(struct nfsd3_diropres),
-		.pc_cachetype = RC_REPLBUFF,
-		.pc_xdrressize = ST+(1+FH+pAT)+WC,
-		.pc_name = "MKNOD",
+		.pc_func	= nfsd3_proc_mknod,
+		.pc_decode	= nfs_svc_decode_MKNOD3args,
+		.pc_encode	= nfs_svc_encode_MKNOD3res,
+		.pc_argsize	= sizeof(struct MKNOD3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct MKNOD3res_wrapper),
+		.pc_cachetype	= RC_REPLBUFF,
+		.pc_xdrressize	= NFS3_MKNOD3res_sz,
+		.pc_name	= "MKNOD",
 	},
 	[NFSPROC3_REMOVE] = {
 		.pc_func = nfsd3_proc_remove,
@@ -1795,10 +1854,11 @@ union nfsd3_xdrstore {
 	struct MKDIR3res_wrapper	mkdirres;
 	struct SYMLINK3args_wrapper	symlinkargs;
 	struct SYMLINK3res_wrapper	symlinkres;
+	struct MKNOD3args_wrapper	mknodargs;
+	struct MKNOD3res_wrapper	mknodres;
 	struct nfsd3_renameargs		renameargs;
 	struct nfsd3_linkargs		linkargs;
 	struct nfsd3_readdirargs	readdirargs;
-	struct nfsd3_diropres 		diropres;
 	struct READLINK3res		readlinkres;
 	struct READ3res			readres;
 	struct nfsd3_writeres		writeres;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index 68f00ff604be..53a82af80ad5 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -57,20 +57,6 @@ encode_nfstime3(__be32 *p, const struct timespec64 *time)
 	return p;
 }
 
-static bool
-svcxdr_decode_nfstime3(struct xdr_stream *xdr, struct timespec64 *timep)
-{
-	__be32 *p;
-
-	p = xdr_inline_decode(xdr, XDR_UNIT * 2);
-	if (!p)
-		return false;
-	timep->tv_sec = be32_to_cpup(p++);
-	timep->tv_nsec = be32_to_cpup(p);
-
-	return true;
-}
-
 /**
  * svcxdr_decode_nfs_fh3 - Decode an NFSv3 file handle
  * @xdr: XDR stream positioned at an undecoded NFSv3 FH
@@ -209,114 +195,6 @@ svcxdr_decode_diropargs3(struct xdr_stream *xdr, struct svc_fh *fhp,
 		svcxdr_decode_filename3(xdr, name, len);
 }
 
-static bool
-svcxdr_decode_sattr3(struct svc_rqst *rqstp, struct xdr_stream *xdr,
-		     struct iattr *iap)
-{
-	u32 set_it;
-
-	iap->ia_valid = 0;
-
-	if (xdr_stream_decode_bool(xdr, &set_it) < 0)
-		return false;
-	if (set_it) {
-		u32 mode;
-
-		if (xdr_stream_decode_u32(xdr, &mode) < 0)
-			return false;
-		iap->ia_valid |= ATTR_MODE;
-		iap->ia_mode = mode;
-	}
-	if (xdr_stream_decode_bool(xdr, &set_it) < 0)
-		return false;
-	if (set_it) {
-		u32 uid;
-
-		if (xdr_stream_decode_u32(xdr, &uid) < 0)
-			return false;
-		iap->ia_uid = make_kuid(nfsd_user_namespace(rqstp), uid);
-		if (uid_valid(iap->ia_uid))
-			iap->ia_valid |= ATTR_UID;
-	}
-	if (xdr_stream_decode_bool(xdr, &set_it) < 0)
-		return false;
-	if (set_it) {
-		u32 gid;
-
-		if (xdr_stream_decode_u32(xdr, &gid) < 0)
-			return false;
-		iap->ia_gid = make_kgid(nfsd_user_namespace(rqstp), gid);
-		if (gid_valid(iap->ia_gid))
-			iap->ia_valid |= ATTR_GID;
-	}
-	if (xdr_stream_decode_bool(xdr, &set_it) < 0)
-		return false;
-	if (set_it) {
-		u64 newsize;
-
-		if (xdr_stream_decode_u64(xdr, &newsize) < 0)
-			return false;
-		iap->ia_valid |= ATTR_SIZE;
-		iap->ia_size = newsize;
-	}
-	if (xdr_stream_decode_u32(xdr, &set_it) < 0)
-		return false;
-	switch (set_it) {
-	case DONT_CHANGE:
-		break;
-	case SET_TO_SERVER_TIME:
-		iap->ia_valid |= ATTR_ATIME;
-		break;
-	case SET_TO_CLIENT_TIME:
-		if (!svcxdr_decode_nfstime3(xdr, &iap->ia_atime))
-			return false;
-		iap->ia_valid |= ATTR_ATIME | ATTR_ATIME_SET;
-		break;
-	default:
-		return false;
-	}
-	if (xdr_stream_decode_u32(xdr, &set_it) < 0)
-		return false;
-	switch (set_it) {
-	case DONT_CHANGE:
-		break;
-	case SET_TO_SERVER_TIME:
-		iap->ia_valid |= ATTR_MTIME;
-		break;
-	case SET_TO_CLIENT_TIME:
-		if (!svcxdr_decode_nfstime3(xdr, &iap->ia_mtime))
-			return false;
-		iap->ia_valid |= ATTR_MTIME | ATTR_MTIME_SET;
-		break;
-	default:
-		return false;
-	}
-
-	return true;
-}
-
-static bool
-svcxdr_decode_specdata3(struct xdr_stream *xdr, struct nfsd3_mknodargs *args)
-{
-	__be32 *p;
-
-	p = xdr_inline_decode(xdr, XDR_UNIT * 2);
-	if (!p)
-		return false;
-	args->major = be32_to_cpup(p++);
-	args->minor = be32_to_cpup(p);
-
-	return true;
-}
-
-static bool
-svcxdr_decode_devicedata3(struct svc_rqst *rqstp, struct xdr_stream *xdr,
-			  struct nfsd3_mknodargs *args)
-{
-	return svcxdr_decode_sattr3(rqstp, xdr, &args->attrs) &&
-		svcxdr_decode_specdata3(xdr, args);
-}
-
 static bool
 svcxdr_encode_fattr3(struct svc_rqst *rqstp, struct xdr_stream *xdr,
 		     const struct svc_fh *fhp, const struct kstat *stat)
@@ -490,34 +368,6 @@ nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 	return svcxdr_decode_diropargs3(xdr, &args->fh, &args->name, &args->len);
 }
 
-bool
-nfs3svc_decode_mknodargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_mknodargs *args = rqstp->rq_argp;
-
-	if (!svcxdr_decode_diropargs3(xdr, &args->fh, &args->name, &args->len))
-		return false;
-	if (xdr_stream_decode_u32(xdr, &args->ftype) < 0)
-		return false;
-	switch (args->ftype) {
-	case NF3CHR:
-	case NF3BLK:
-		return svcxdr_decode_devicedata3(rqstp, xdr, args);
-	case NF3SOCK:
-	case NF3FIFO:
-		return svcxdr_decode_sattr3(rqstp, xdr, &args->attrs);
-	case NF3REG:
-	case NF3DIR:
-	case NF3LNK:
-		/* Valid XDR but illegal file types */
-		break;
-	default:
-		return false;
-	}
-
-	return true;
-}
-
 bool
 nfs3svc_decode_renameargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 {
@@ -608,31 +458,6 @@ nfs3svc_encode_wccstat(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 		svcxdr_encode_wcc_data(rqstp, xdr, &resp->fh);
 }
 
-/* CREATE, MKDIR, SYMLINK, MKNOD */
-bool
-nfs3svc_encode_createres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_diropres *resp = rqstp->rq_resp;
-
-	if (!svcxdr_encode_nfsstat3(xdr, resp->status))
-		return false;
-	switch (resp->status) {
-	case nfs_ok:
-		if (!svcxdr_encode_post_op_fh3(xdr, &resp->fh))
-			return false;
-		if (!svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh))
-			return false;
-		if (!svcxdr_encode_wcc_data(rqstp, xdr, &resp->dirfh))
-			return false;
-		break;
-	default:
-		if (!svcxdr_encode_wcc_data(rqstp, xdr, &resp->dirfh))
-			return false;
-	}
-
-	return true;
-}
-
 /* RENAME */
 bool
 nfs3svc_encode_renameres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 11a4e7cb65f6..184d7852a94a 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -37,15 +37,6 @@ struct nfsd3_createargs {
 	__be32 *		verf;
 };
 
-struct nfsd3_mknodargs {
-	struct svc_fh		fh;
-	char *			name;
-	unsigned int		len;
-	__u32			ftype;
-	__u32			major, minor;
-	struct iattr		attrs;
-};
-
 struct nfsd3_renameargs {
 	struct svc_fh		ffh;
 	char *			fname;
@@ -94,13 +85,6 @@ struct nfsd3_attrstat {
 	struct kstat            stat;
 };
 
-/* LOOKUP, CREATE, MKDIR, SYMLINK, MKNOD */
-struct nfsd3_diropres  {
-	__be32			status;
-	struct svc_fh		dirfh;
-	struct svc_fh		fh;
-};
-
 struct nfsd3_accessres {
 	__be32			status;
 	struct svc_fh		fh;
@@ -195,7 +179,6 @@ struct nfsd3_fhandle_pair {
 
 bool nfs3svc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_decode_mknodargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_renameargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_linkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
@@ -204,7 +187,6 @@ bool nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 bool nfs3svc_encode_wccstat(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_encode_createres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_renameres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_linkres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-- 
2.55.0


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

* [PATCH v1 19/33] NFSD: Use xdrgen XDR functions for the NFSv3 REMOVE procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (17 preceding siblings ...)
  2026-09-23 13:27 ` [PATCH v1 18/33] NFSD: Use xdrgen XDR functions for NFSv3 MKNOD procedure Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 20/33] NFSD: Use xdrgen XDR functions for the NFSv3 RMDIR procedure Chuck Lever
                   ` (13 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_REMOVE entry in the nfsd_procedures3 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_REMOVE3args and nfs_svc_encode_REMOVE3res. A
wrapper structure bridges the generated xdrgen argument type and
the legacy svc_fh representation the NFSD VFS layer still uses;
the result uses the generated REMOVE3res type directly.

Filename validity is now checked by nfsd3_proc_remove() so that a
proper NFS3ERR status code can be returned to the client rather
than GARBAGE_ARGS.

The pc_argzero field is now set to zero for the NFSv3 REMOVE
procedure. The xdrgen decoders are trusted to initialize all
arguments in the argp->xdrgen field, making the early defensive
memset unnecessary. The remaining argp fields are cleared as
needed.

Because nfsd3_proc_remove() now assembles the WCC data, that work
moves out of the XDR result encoder, and the reference to the
file handle can be released directly by nfsd3_proc_remove(). A
separate ->pc_release callback is thus no longer needed. This
makes it straightforward to confirm that the file handle is
properly released during every error flow.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs3proc.c | 72 ++++++++++++++++++++++++++++++++--------------
 1 file changed, 51 insertions(+), 21 deletions(-)

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index e178cb7fa206..02c7501c8663 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -163,6 +163,13 @@ struct MKNOD3res_wrapper {
 
 static_assert(offsetof(struct MKNOD3res_wrapper, xdrgen) == 0);
 
+struct REMOVE3args_wrapper {
+	struct REMOVE3args	xdrgen;
+	struct svc_fh		fh;
+};
+
+static_assert(offsetof(struct REMOVE3args_wrapper, xdrgen) == 0);
+
 static int nfsd3_ftype3_to_mode(ftype3 ftype)
 {
 	switch (ftype) {
@@ -1256,20 +1263,42 @@ static __be32 nfsd3_proc_mknod(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-/*
- * Remove file/fifo/socket etc.
+/**
+ * nfsd3_proc_remove - REMOVE: Remove a file
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   REMOVE3res NFSPROC3_REMOVE(REMOVE3args) = 12;
  */
-static __be32
-nfsd3_proc_remove(struct svc_rqst *rqstp)
+static __be32 nfsd3_proc_remove(struct svc_rqst *rqstp)
 {
-	struct nfsd3_diropargs *argp = rqstp->rq_argp;
-	struct nfsd3_attrstat *resp = rqstp->rq_resp;
+	struct REMOVE3args_wrapper *argp = rqstp->rq_argp;
+	struct diropargs3 *object = &argp->xdrgen.object;
+	struct REMOVE3res *resp = rqstp->rq_resp;
+	struct svc_fh *fhp = &argp->fh;
 
-	/* Unlink. -S_IFDIR means file must not be a directory */
-	fh_copy(&resp->fh, &argp->fh);
-	resp->status = nfsd_unlink(rqstp, &resp->fh, -S_IFDIR,
-				   argp->name, argp->len);
-	resp->status = nfsd3_map_status(resp->status);
+	nfsd3_fh3_to_svc_fh(fhp, &object->dir);
+	resp->status = nfsd3_check_filename(object->name.data,
+					    object->name.len);
+	if (resp->status != nfs_ok)
+		goto out;
+
+	resp->status = nfsd_unlink(rqstp, fhp, -S_IFDIR,
+				   (char *)object->name.data,
+				   object->name.len);
+
+out:
+	if (resp->status == nfs_ok) {
+		nfsd3_fill_wcc_data(rqstp, &resp->u.resok.dir_wcc, fhp);
+	} else {
+		resp->status = nfsd3_map_status(resp->status);
+		nfsd3_fill_wcc_data(rqstp, &resp->u.resfail.dir_wcc, fhp);
+	}
+
+	fh_put(fhp);
 	return rpc_success;
 }
 
@@ -1716,16 +1745,15 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "MKNOD",
 	},
 	[NFSPROC3_REMOVE] = {
-		.pc_func = nfsd3_proc_remove,
-		.pc_decode = nfs3svc_decode_diropargs,
-		.pc_encode = nfs3svc_encode_wccstatres,
-		.pc_release = nfs3svc_release_fhandle,
-		.pc_argsize = sizeof(struct nfsd3_diropargs),
-		.pc_argzero = sizeof(struct nfsd3_diropargs),
-		.pc_ressize = sizeof(struct nfsd3_wccstatres),
-		.pc_cachetype = RC_REPLBUFF,
-		.pc_xdrressize = ST+WC,
-		.pc_name = "REMOVE",
+		.pc_func	= nfsd3_proc_remove,
+		.pc_decode	= nfs_svc_decode_REMOVE3args,
+		.pc_encode	= nfs_svc_encode_REMOVE3res,
+		.pc_argsize	= sizeof(struct REMOVE3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct REMOVE3res),
+		.pc_cachetype	= RC_REPLBUFF,
+		.pc_xdrressize	= NFS3_REMOVE3res_sz,
+		.pc_name	= "REMOVE",
 	},
 	[NFSPROC3_RMDIR] = {
 		.pc_func = nfsd3_proc_rmdir,
@@ -1856,6 +1884,8 @@ union nfsd3_xdrstore {
 	struct SYMLINK3res_wrapper	symlinkres;
 	struct MKNOD3args_wrapper	mknodargs;
 	struct MKNOD3res_wrapper	mknodres;
+	struct REMOVE3args_wrapper	removeargs;
+	struct REMOVE3res		removeres;
 	struct nfsd3_renameargs		renameargs;
 	struct nfsd3_linkargs		linkargs;
 	struct nfsd3_readdirargs	readdirargs;
-- 
2.55.0


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

* [PATCH v1 20/33] NFSD: Use xdrgen XDR functions for the NFSv3 RMDIR procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (18 preceding siblings ...)
  2026-09-23 13:27 ` [PATCH v1 19/33] NFSD: Use xdrgen XDR functions for the NFSv3 REMOVE procedure Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:27 ` [PATCH v1 21/33] NFSD: Use xdrgen XDR functions for the NFSv3 RENAME procedure Chuck Lever
                   ` (12 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_RMDIR entry in the nfsd_procedures3 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_RMDIR3args and nfs_svc_encode_RMDIR3res. A
wrapper structure bridges the generated xdrgen argument type and
the legacy svc_fh representation the NFSD VFS layer still uses;
the result uses the generated RMDIR3res type directly.

Filename validity is now checked by nfsd3_proc_rmdir() so that a
proper NFS3ERR status code can be returned to the client rather
than GARBAGE_ARGS.

The pc_argzero field is now set to zero for the NFSv3 RMDIR
procedure. The xdrgen decoders are trusted to initialize all
arguments in the argp->xdrgen field, making the early defensive
memset unnecessary. The remaining argp fields are cleared as
needed.

Because nfsd3_proc_rmdir() now assembles the WCC data, that work
moves out of the XDR result encoder, and the reference to the
file handle can be released directly by nfsd3_proc_rmdir(). A
separate ->pc_release callback is thus no longer needed. This
makes it straightforward to confirm that the file handle is
properly released during every error flow.

nfs3svc_decode_diropargs() and nfs3svc_encode_wccstat() no longer
have any callers, so they are removed.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs3proc.c | 74 ++++++++++++++++++++++++++++++++--------------
 fs/nfsd/nfs3xdr.c  | 18 -----------
 fs/nfsd/xdr3.h     |  8 -----
 3 files changed, 51 insertions(+), 49 deletions(-)

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 02c7501c8663..e31b96bf4a80 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -170,6 +170,13 @@ struct REMOVE3args_wrapper {
 
 static_assert(offsetof(struct REMOVE3args_wrapper, xdrgen) == 0);
 
+struct RMDIR3args_wrapper {
+	struct RMDIR3args	xdrgen;
+	struct svc_fh		fh;
+};
+
+static_assert(offsetof(struct RMDIR3args_wrapper, xdrgen) == 0);
+
 static int nfsd3_ftype3_to_mode(ftype3 ftype)
 {
 	switch (ftype) {
@@ -1302,19 +1309,42 @@ static __be32 nfsd3_proc_remove(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-/*
- * Remove a directory
+/**
+ * nfsd3_proc_rmdir - RMDIR: Remove a directory
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   RMDIR3res NFSPROC3_RMDIR(RMDIR3args) = 13;
  */
-static __be32
-nfsd3_proc_rmdir(struct svc_rqst *rqstp)
+static __be32 nfsd3_proc_rmdir(struct svc_rqst *rqstp)
 {
-	struct nfsd3_diropargs *argp = rqstp->rq_argp;
-	struct nfsd3_attrstat *resp = rqstp->rq_resp;
+	struct RMDIR3args_wrapper *argp = rqstp->rq_argp;
+	struct diropargs3 *object = &argp->xdrgen.object;
+	struct RMDIR3res *resp = rqstp->rq_resp;
+	struct svc_fh *fhp = &argp->fh;
 
-	fh_copy(&resp->fh, &argp->fh);
-	resp->status = nfsd_unlink(rqstp, &resp->fh, S_IFDIR,
-				   argp->name, argp->len);
-	resp->status = nfsd3_map_status(resp->status);
+	nfsd3_fh3_to_svc_fh(fhp, &object->dir);
+	resp->status = nfsd3_check_filename(object->name.data,
+					    object->name.len);
+	if (resp->status != nfs_ok)
+		goto out;
+
+	resp->status = nfsd_unlink(rqstp, fhp, S_IFDIR,
+				   (char *)object->name.data,
+				   object->name.len);
+
+out:
+	if (resp->status == nfs_ok) {
+		nfsd3_fill_wcc_data(rqstp, &resp->u.resok.dir_wcc, fhp);
+	} else {
+		resp->status = nfsd3_map_status(resp->status);
+		nfsd3_fill_wcc_data(rqstp, &resp->u.resfail.dir_wcc, fhp);
+	}
+
+	fh_put(fhp);
 	return rpc_success;
 }
 
@@ -1598,10 +1628,8 @@ nfsd3_proc_commit(struct svc_rqst *rqstp)
  * NFSv3 Server procedures.
  * Only the results of non-idempotent operations are cached.
  */
-#define nfs3svc_encode_wccstatres	nfs3svc_encode_wccstat
 #define nfsd3_readdirplusargs		nfsd3_readdirargs
 #define nfsd3_fhandleargs		nfsd_fhandle
-#define nfsd3_wccstatres		nfsd3_attrstat
 
 #define ST 1		/* status*/
 #define AT 21		/* attributes */
@@ -1756,16 +1784,15 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "REMOVE",
 	},
 	[NFSPROC3_RMDIR] = {
-		.pc_func = nfsd3_proc_rmdir,
-		.pc_decode = nfs3svc_decode_diropargs,
-		.pc_encode = nfs3svc_encode_wccstatres,
-		.pc_release = nfs3svc_release_fhandle,
-		.pc_argsize = sizeof(struct nfsd3_diropargs),
-		.pc_argzero = sizeof(struct nfsd3_diropargs),
-		.pc_ressize = sizeof(struct nfsd3_wccstatres),
-		.pc_cachetype = RC_REPLBUFF,
-		.pc_xdrressize = ST+WC,
-		.pc_name = "RMDIR",
+		.pc_func	= nfsd3_proc_rmdir,
+		.pc_decode	= nfs_svc_decode_RMDIR3args,
+		.pc_encode	= nfs_svc_encode_RMDIR3res,
+		.pc_argsize	= sizeof(struct RMDIR3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct RMDIR3res),
+		.pc_cachetype	= RC_REPLBUFF,
+		.pc_xdrressize	= NFS3_RMDIR3res_sz,
+		.pc_name	= "RMDIR",
 	},
 	[NFSPROC3_RENAME] = {
 		.pc_func = nfsd3_proc_rename,
@@ -1874,7 +1901,6 @@ union nfsd3_xdrstore {
 	struct ACCESS3res		accessres;
 	struct READLINK3args_wrapper	readlinkargs;
 	struct READ3args_wrapper	readargs;
-	struct nfsd3_diropargs		diropargs;
 	struct WRITE3args_wrapper	writeargs;
 	struct CREATE3args_wrapper	createargs;
 	struct CREATE3res_wrapper	createres;
@@ -1886,6 +1912,8 @@ union nfsd3_xdrstore {
 	struct MKNOD3res_wrapper	mknodres;
 	struct REMOVE3args_wrapper	removeargs;
 	struct REMOVE3res		removeres;
+	struct RMDIR3args_wrapper	rmdirargs;
+	struct RMDIR3res		rmdirres;
 	struct nfsd3_renameargs		renameargs;
 	struct nfsd3_linkargs		linkargs;
 	struct nfsd3_readdirargs	readdirargs;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index 53a82af80ad5..1cfc79724450 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -360,14 +360,6 @@ nfs3svc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 	return svcxdr_decode_nfs_fh3(xdr, &args->fh);
 }
 
-bool
-nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_diropargs *args = rqstp->rq_argp;
-
-	return svcxdr_decode_diropargs3(xdr, &args->fh, &args->name, &args->len);
-}
-
 bool
 nfs3svc_decode_renameargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 {
@@ -448,16 +440,6 @@ nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
  * XDR encode functions
  */
 
-/* SETATTR, REMOVE, RMDIR */
-bool
-nfs3svc_encode_wccstat(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_attrstat *resp = rqstp->rq_resp;
-
-	return svcxdr_encode_nfsstat3(xdr, resp->status) &&
-		svcxdr_encode_wcc_data(rqstp, xdr, &resp->fh);
-}
-
 /* RENAME */
 bool
 nfs3svc_encode_renameres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 184d7852a94a..26ce94e49eda 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -19,12 +19,6 @@
 #define NFS3_MAXNAMLEN			NAME_MAX
 #define NFS3_MAXPATHLEN			PATH_MAX
 
-struct nfsd3_diropargs {
-	struct svc_fh		fh;
-	char *			name;
-	unsigned int		len;
-};
-
 struct nfsd3_accessargs {
 	struct svc_fh		fh;
 	__u32			access;
@@ -178,14 +172,12 @@ struct nfsd3_fhandle_pair {
 };
 
 bool nfs3svc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_renameargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_linkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_readdirplusargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
-bool nfs3svc_encode_wccstat(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_renameres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_linkres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-- 
2.55.0


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

* [PATCH v1 21/33] NFSD: Use xdrgen XDR functions for the NFSv3 RENAME procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (19 preceding siblings ...)
  2026-09-23 13:27 ` [PATCH v1 20/33] NFSD: Use xdrgen XDR functions for the NFSv3 RMDIR procedure Chuck Lever
@ 2026-09-23 13:27 ` Chuck Lever
  2026-09-23 13:28 ` [PATCH v1 22/33] NFSD: Use xdrgen XDR functions for the NFSv3 LINK procedure Chuck Lever
                   ` (11 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:27 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_RENAME entry in the nfsd_procedures3 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_RENAME3args and nfs_svc_encode_RENAME3res. A
wrapper structure bridges the generated xdrgen argument type and
the legacy svc_fh representation the NFSD VFS layer still uses;
the result uses the generated RENAME3res type directly.

Filename validity is now checked by nfsd3_proc_rename() so that a
proper NFS3ERR status code can be returned to the client rather
than GARBAGE_ARGS.

The pc_argzero field is now set to zero for the NFSv3 RENAME
procedure. The xdrgen decoders are trusted to initialize all
arguments in the argp->xdrgen field, making the early defensive
memset unnecessary. The remaining argp fields are cleared as
needed.

Because nfsd3_proc_rename() now assembles the WCC data, that work
moves out of the XDR result encoder, and the references to the
file handles can be released directly by nfsd3_proc_rename(). A
separate ->pc_release callback is thus no longer needed. This
makes it straightforward to confirm that the file handles are
properly released during every error flow.

nfs3svc_decode_renameargs() and nfs3svc_encode_renameres() no
longer have any callers, so they are removed.

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

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index e31b96bf4a80..b9f7f9d74f3f 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -177,6 +177,14 @@ struct RMDIR3args_wrapper {
 
 static_assert(offsetof(struct RMDIR3args_wrapper, xdrgen) == 0);
 
+struct RENAME3args_wrapper {
+	struct RENAME3args	xdrgen;
+	struct svc_fh		ffh;
+	struct svc_fh		tfh;
+};
+
+static_assert(offsetof(struct RENAME3args_wrapper, xdrgen) == 0);
+
 static int nfsd3_ftype3_to_mode(ftype3 ftype)
 {
 	switch (ftype) {
@@ -1348,17 +1356,57 @@ static __be32 nfsd3_proc_rmdir(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-static __be32
-nfsd3_proc_rename(struct svc_rqst *rqstp)
+/**
+ * nfsd3_proc_rename - RENAME: Rename a file or directory
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   RENAME3res NFSPROC3_RENAME(RENAME3args) = 14;
+ */
+static __be32 nfsd3_proc_rename(struct svc_rqst *rqstp)
 {
-	struct nfsd3_renameargs *argp = rqstp->rq_argp;
-	struct nfsd3_renameres *resp = rqstp->rq_resp;
+	struct RENAME3args_wrapper *argp = rqstp->rq_argp;
+	struct diropargs3 *from = &argp->xdrgen.from;
+	struct diropargs3 *to = &argp->xdrgen.to;
+	struct RENAME3res *resp = rqstp->rq_resp;
+	struct svc_fh *ffhp = &argp->ffh;
+	struct svc_fh *tfhp = &argp->tfh;
 
-	fh_copy(&resp->ffh, &argp->ffh);
-	fh_copy(&resp->tfh, &argp->tfh);
-	resp->status = nfsd_rename(rqstp, &resp->ffh, argp->fname, argp->flen,
-				   &resp->tfh, argp->tname, argp->tlen);
-	resp->status = nfsd3_map_status(resp->status);
+	nfsd3_fh3_to_svc_fh(ffhp, &from->dir);
+	nfsd3_fh3_to_svc_fh(tfhp, &to->dir);
+	resp->status = nfsd3_check_filename(from->name.data,
+					    from->name.len);
+	if (resp->status != nfs_ok)
+		goto out;
+	resp->status = nfsd3_check_filename(to->name.data,
+					    to->name.len);
+	if (resp->status != nfs_ok)
+		goto out;
+
+	resp->status = nfsd_rename(rqstp, ffhp, (char *)from->name.data,
+				   from->name.len, tfhp,
+				   (char *)to->name.data,
+				   to->name.len);
+
+out:
+	if (resp->status == nfs_ok) {
+		struct RENAME3resok *resok = &resp->u.resok;
+
+		nfsd3_fill_wcc_data(rqstp, &resok->fromdir_wcc, ffhp);
+		nfsd3_fill_wcc_data(rqstp, &resok->todir_wcc, tfhp);
+	} else {
+		struct RENAME3resfail *resfail = &resp->u.resfail;
+
+		resp->status = nfsd3_map_status(resp->status);
+		nfsd3_fill_wcc_data(rqstp, &resfail->fromdir_wcc, ffhp);
+		nfsd3_fill_wcc_data(rqstp, &resfail->todir_wcc, tfhp);
+	}
+
+	fh_put(ffhp);
+	fh_put(tfhp);
 	return rpc_success;
 }
 
@@ -1795,16 +1843,15 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "RMDIR",
 	},
 	[NFSPROC3_RENAME] = {
-		.pc_func = nfsd3_proc_rename,
-		.pc_decode = nfs3svc_decode_renameargs,
-		.pc_encode = nfs3svc_encode_renameres,
-		.pc_release = nfs3svc_release_fhandle2,
-		.pc_argsize = sizeof(struct nfsd3_renameargs),
-		.pc_argzero = sizeof(struct nfsd3_renameargs),
-		.pc_ressize = sizeof(struct nfsd3_renameres),
-		.pc_cachetype = RC_REPLBUFF,
-		.pc_xdrressize = ST+WC+WC,
-		.pc_name = "RENAME",
+		.pc_func	= nfsd3_proc_rename,
+		.pc_decode	= nfs_svc_decode_RENAME3args,
+		.pc_encode	= nfs_svc_encode_RENAME3res,
+		.pc_argsize	= sizeof(struct RENAME3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct RENAME3res),
+		.pc_cachetype	= RC_REPLBUFF,
+		.pc_xdrressize	= NFS3_RENAME3res_sz,
+		.pc_name	= "RENAME",
 	},
 	[NFSPROC3_LINK] = {
 		.pc_func = nfsd3_proc_link,
@@ -1914,13 +1961,13 @@ union nfsd3_xdrstore {
 	struct REMOVE3res		removeres;
 	struct RMDIR3args_wrapper	rmdirargs;
 	struct RMDIR3res		rmdirres;
-	struct nfsd3_renameargs		renameargs;
+	struct RENAME3args_wrapper	renameargs;
+	struct RENAME3res		renameres;
 	struct nfsd3_linkargs		linkargs;
 	struct nfsd3_readdirargs	readdirargs;
 	struct READLINK3res		readlinkres;
 	struct READ3res			readres;
 	struct nfsd3_writeres		writeres;
-	struct nfsd3_renameres		renameres;
 	struct nfsd3_linkres		linkres;
 	struct nfsd3_readdirres		readdirres;
 	struct nfsd3_fsstatres		fsstatres;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index 1cfc79724450..3ec835e0f364 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -360,17 +360,6 @@ nfs3svc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 	return svcxdr_decode_nfs_fh3(xdr, &args->fh);
 }
 
-bool
-nfs3svc_decode_renameargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_renameargs *args = rqstp->rq_argp;
-
-	return svcxdr_decode_diropargs3(xdr, &args->ffh,
-					&args->fname, &args->flen) &&
-		svcxdr_decode_diropargs3(xdr, &args->tfh,
-					 &args->tname, &args->tlen);
-}
-
 bool
 nfs3svc_decode_linkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 {
@@ -440,17 +429,6 @@ nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
  * XDR encode functions
  */
 
-/* RENAME */
-bool
-nfs3svc_encode_renameres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_renameres *resp = rqstp->rq_resp;
-
-	return svcxdr_encode_nfsstat3(xdr, resp->status) &&
-		svcxdr_encode_wcc_data(rqstp, xdr, &resp->ffh) &&
-		svcxdr_encode_wcc_data(rqstp, xdr, &resp->tfh);
-}
-
 /* LINK */
 bool
 nfs3svc_encode_linkres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 26ce94e49eda..320572a192d2 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -31,15 +31,6 @@ struct nfsd3_createargs {
 	__be32 *		verf;
 };
 
-struct nfsd3_renameargs {
-	struct svc_fh		ffh;
-	char *			fname;
-	unsigned int		flen;
-	struct svc_fh		tfh;
-	char *			tname;
-	unsigned int		tlen;
-};
-
 struct nfsd3_linkargs {
 	struct svc_fh		ffh;
 	struct svc_fh		tfh;
@@ -92,12 +83,6 @@ struct nfsd3_writeres {
 
 static_assert(offsetof(struct nfsd3_writeres, xdrgen) == 0);
 
-struct nfsd3_renameres {
-	__be32			status;
-	struct svc_fh		ffh;
-	struct svc_fh		tfh;
-};
-
 struct nfsd3_linkres {
 	__be32			status;
 	struct svc_fh		tfh;
@@ -172,14 +157,12 @@ struct nfsd3_fhandle_pair {
 };
 
 bool nfs3svc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_decode_renameargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_linkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_readdirplusargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 bool nfs3svc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_encode_renameres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_linkres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_fsstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-- 
2.55.0


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

* [PATCH v1 22/33] NFSD: Use xdrgen XDR functions for the NFSv3 LINK procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (20 preceding siblings ...)
  2026-09-23 13:27 ` [PATCH v1 21/33] NFSD: Use xdrgen XDR functions for the NFSv3 RENAME procedure Chuck Lever
@ 2026-09-23 13:28 ` Chuck Lever
  2026-09-23 13:28 ` [PATCH v1 23/33] NFSD: Use xdrgen XDR functions for the NFSv3 FSSTAT procedure Chuck Lever
                   ` (10 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:28 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_LINK entry in the nfsd_procedures3 array with
an entry that dispatches the xdrgen-generated nfs_svc_decode_LINK3args
and nfs_svc_encode_LINK3res. A wrapper structure bridges the
generated xdrgen argument type and the legacy svc_fh representation
the NFSD VFS layer still uses; the result uses the generated LINK3res
type directly.

Filename validity is now checked by nfsd3_proc_link() so that a
proper NFS3ERR status code can be returned to the client rather than
GARBAGE_ARGS.

The pc_argzero field is now set to zero for the NFSv3 LINK procedure.
The xdrgen decoders are trusted to initialize all arguments in the
argp->xdrgen field, making the early defensive memset unnecessary.
The remaining argp fields are cleared as needed.

Because nfsd3_proc_link() now fills in the post-op attributes and
assembles the WCC data, that work moves out of the XDR result
encoder, and the references to the file handles can be released
directly by nfsd3_proc_link(). A separate ->pc_release callback is
thus no longer needed. This makes it straightforward to confirm that
the file handles are properly released during every error flow.

nfs3svc_decode_linkargs(), nfs3svc_encode_linkres(), and
nfs3svc_release_fhandle2() no longer have any callers, so they are
removed, and with them the now-orphaned svcxdr_decode_filename3() and
svcxdr_decode_diropargs3() decode helpers and the struct
nfsd3_fhandle_pair release type.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs3proc.c | 82 ++++++++++++++++++++++++++++++++++------------
 fs/nfsd/nfs3xdr.c  | 63 -----------------------------------
 fs/nfsd/xdr3.h     | 23 -------------
 3 files changed, 61 insertions(+), 107 deletions(-)

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index b9f7f9d74f3f..3706991ecea4 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -185,6 +185,14 @@ struct RENAME3args_wrapper {
 
 static_assert(offsetof(struct RENAME3args_wrapper, xdrgen) == 0);
 
+struct LINK3args_wrapper {
+	struct LINK3args	xdrgen;
+	struct svc_fh		ffh;
+	struct svc_fh		tfh;
+};
+
+static_assert(offsetof(struct LINK3args_wrapper, xdrgen) == 0);
+
 static int nfsd3_ftype3_to_mode(ftype3 ftype)
 {
 	switch (ftype) {
@@ -1410,17 +1418,50 @@ static __be32 nfsd3_proc_rename(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-static __be32
-nfsd3_proc_link(struct svc_rqst *rqstp)
+/**
+ * nfsd3_proc_link - LINK: Create Link to an object
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   LINK3res NFSPROC3_LINK(LINK3args) = 15;
+ */
+static __be32 nfsd3_proc_link(struct svc_rqst *rqstp)
 {
-	struct nfsd3_linkargs *argp = rqstp->rq_argp;
-	struct nfsd3_linkres  *resp = rqstp->rq_resp;
+	struct LINK3args_wrapper *argp = rqstp->rq_argp;
+	struct diropargs3 *link = &argp->xdrgen.link;
+	struct LINK3res *resp = rqstp->rq_resp;
+	struct svc_fh *ffhp = &argp->ffh;
+	struct svc_fh *tfhp = &argp->tfh;
 
-	fh_copy(&resp->fh,  &argp->ffh);
-	fh_copy(&resp->tfh, &argp->tfh);
-	resp->status = nfsd_link(rqstp, &resp->tfh, argp->tname, argp->tlen,
-				 &resp->fh);
-	resp->status = nfsd3_map_status(resp->status);
+	nfsd3_fh3_to_svc_fh(ffhp, &argp->xdrgen.file);
+	nfsd3_fh3_to_svc_fh(tfhp, &link->dir);
+	resp->status = nfsd3_check_filename(link->name.data,
+					    link->name.len);
+	if (resp->status != nfs_ok)
+		goto out;
+
+	resp->status = nfsd_link(rqstp, tfhp, (char *)link->name.data,
+				 link->name.len, ffhp);
+
+out:
+	if (resp->status == nfs_ok) {
+		struct LINK3resok *resok = &resp->u.resok;
+
+		nfsd3_fill_post_op_attr(rqstp, &resok->file_attributes, ffhp);
+		nfsd3_fill_wcc_data(rqstp, &resok->linkdir_wcc, tfhp);
+	} else {
+		struct LINK3resfail *resfail = &resp->u.resfail;
+
+		resp->status = nfsd3_map_status(resp->status);
+		nfsd3_fill_post_op_attr(rqstp, &resfail->file_attributes, ffhp);
+		nfsd3_fill_wcc_data(rqstp, &resfail->linkdir_wcc, tfhp);
+	}
+
+	fh_put(ffhp);
+	fh_put(tfhp);
 	return rpc_success;
 }
 
@@ -1854,16 +1895,15 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "RENAME",
 	},
 	[NFSPROC3_LINK] = {
-		.pc_func = nfsd3_proc_link,
-		.pc_decode = nfs3svc_decode_linkargs,
-		.pc_encode = nfs3svc_encode_linkres,
-		.pc_release = nfs3svc_release_fhandle2,
-		.pc_argsize = sizeof(struct nfsd3_linkargs),
-		.pc_argzero = sizeof(struct nfsd3_linkargs),
-		.pc_ressize = sizeof(struct nfsd3_linkres),
-		.pc_cachetype = RC_REPLBUFF,
-		.pc_xdrressize = ST+pAT+WC,
-		.pc_name = "LINK",
+		.pc_func	= nfsd3_proc_link,
+		.pc_decode	= nfs_svc_decode_LINK3args,
+		.pc_encode	= nfs_svc_encode_LINK3res,
+		.pc_argsize	= sizeof(struct LINK3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct LINK3res),
+		.pc_cachetype	= RC_REPLBUFF,
+		.pc_xdrressize	= NFS3_LINK3res_sz,
+		.pc_name	= "LINK",
 	},
 	[NFSPROC3_READDIR] = {
 		.pc_func = nfsd3_proc_readdir,
@@ -1963,12 +2003,12 @@ union nfsd3_xdrstore {
 	struct RMDIR3res		rmdirres;
 	struct RENAME3args_wrapper	renameargs;
 	struct RENAME3res		renameres;
-	struct nfsd3_linkargs		linkargs;
+	struct LINK3args_wrapper	linkargs;
+	struct LINK3res			linkres;
 	struct nfsd3_readdirargs	readdirargs;
 	struct READLINK3res		readlinkres;
 	struct READ3res			readres;
 	struct nfsd3_writeres		writeres;
-	struct nfsd3_linkres		linkres;
 	struct nfsd3_readdirres		readdirres;
 	struct nfsd3_fsstatres		fsstatres;
 	struct nfsd3_fsinfores		fsinfores;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index 3ec835e0f364..59e89b7f9d19 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -162,39 +162,6 @@ svcxdr_encode_writeverf3(struct xdr_stream *xdr, const __be32 *verf)
 	return true;
 }
 
-static bool
-svcxdr_decode_filename3(struct xdr_stream *xdr, char **name, unsigned int *len)
-{
-	u32 size, i;
-	__be32 *p;
-	char *c;
-
-	if (xdr_stream_decode_u32(xdr, &size) < 0)
-		return false;
-	if (size == 0 || size > NFS3_MAXNAMLEN)
-		return false;
-	p = xdr_inline_decode(xdr, size);
-	if (!p)
-		return false;
-
-	*len = size;
-	*name = (char *)p;
-	for (i = 0, c = *name; i < size; i++, c++) {
-		if (*c == '\0' || *c == '/')
-			return false;
-	}
-
-	return true;
-}
-
-static bool
-svcxdr_decode_diropargs3(struct xdr_stream *xdr, struct svc_fh *fhp,
-			 char **name, unsigned int *len)
-{
-	return svcxdr_decode_nfs_fh3(xdr, fhp) &&
-		svcxdr_decode_filename3(xdr, name, len);
-}
-
 static bool
 svcxdr_encode_fattr3(struct svc_rqst *rqstp, struct xdr_stream *xdr,
 		     const struct svc_fh *fhp, const struct kstat *stat)
@@ -360,16 +327,6 @@ nfs3svc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 	return svcxdr_decode_nfs_fh3(xdr, &args->fh);
 }
 
-bool
-nfs3svc_decode_linkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_linkargs *args = rqstp->rq_argp;
-
-	return svcxdr_decode_nfs_fh3(xdr, &args->ffh) &&
-		svcxdr_decode_diropargs3(xdr, &args->tfh,
-					 &args->tname, &args->tlen);
-}
-
 bool
 nfs3svc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 {
@@ -429,17 +386,6 @@ nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
  * XDR encode functions
  */
 
-/* LINK */
-bool
-nfs3svc_encode_linkres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_linkres *resp = rqstp->rq_resp;
-
-	return svcxdr_encode_nfsstat3(xdr, resp->status) &&
-		svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh) &&
-		svcxdr_encode_wcc_data(rqstp, xdr, &resp->tfh);
-}
-
 /* READDIR */
 bool
 nfs3svc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
@@ -840,12 +786,3 @@ nfs3svc_release_fhandle(struct svc_rqst *rqstp)
 
 	fh_put(&resp->fh);
 }
-
-void
-nfs3svc_release_fhandle2(struct svc_rqst *rqstp)
-{
-	struct nfsd3_fhandle_pair *resp = rqstp->rq_resp;
-
-	fh_put(&resp->fh1);
-	fh_put(&resp->fh2);
-}
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 320572a192d2..9f29f34cca8f 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -31,13 +31,6 @@ struct nfsd3_createargs {
 	__be32 *		verf;
 };
 
-struct nfsd3_linkargs {
-	struct svc_fh		ffh;
-	struct svc_fh		tfh;
-	char *			tname;
-	unsigned int		tlen;
-};
-
 struct nfsd3_readdirargs {
 	struct svc_fh		fh;
 	__u64			cookie;
@@ -83,12 +76,6 @@ struct nfsd3_writeres {
 
 static_assert(offsetof(struct nfsd3_writeres, xdrgen) == 0);
 
-struct nfsd3_linkres {
-	__be32			status;
-	struct svc_fh		tfh;
-	struct svc_fh		fh;
-};
-
 struct nfsd3_readdirres {
 	/* Components of the reply */
 	__be32			status;
@@ -149,21 +136,12 @@ struct nfsd3_getaclres {
 	struct kstat		stat;
 };
 
-/* dummy type for release */
-struct nfsd3_fhandle_pair {
-	__u32			dummy;
-	struct svc_fh		fh1;
-	struct svc_fh		fh2;
-};
-
 bool nfs3svc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_decode_linkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_readdirplusargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 bool nfs3svc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_encode_linkres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_fsstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_fsinfores(struct svc_rqst *rqstp, struct xdr_stream *xdr);
@@ -171,7 +149,6 @@ bool nfs3svc_encode_pathconfres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_commitres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 void nfs3svc_release_fhandle(struct svc_rqst *);
-void nfs3svc_release_fhandle2(struct svc_rqst *);
 
 void nfs3svc_encode_cookie3(struct nfsd3_readdirres *resp, u64 offset);
 int nfs3svc_encode_entry3(void *data, const char *name, int namlen,
-- 
2.55.0


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

* [PATCH v1 23/33] NFSD: Use xdrgen XDR functions for the NFSv3 FSSTAT procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (21 preceding siblings ...)
  2026-09-23 13:28 ` [PATCH v1 22/33] NFSD: Use xdrgen XDR functions for the NFSv3 LINK procedure Chuck Lever
@ 2026-09-23 13:28 ` Chuck Lever
  2026-09-23 13:28 ` [PATCH v1 24/33] NFSD: Use xdrgen XDR functions for the NFSv3 FSINFO procedure Chuck Lever
                   ` (9 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:28 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_FSSTAT entry in the nfsd_procedures3 array with
an entry that dispatches the xdrgen-generated
nfs_svc_decode_FSSTAT3args and nfs_svc_encode_FSSTAT3res. A wrapper
structure bridges the generated xdrgen argument type and the legacy
svc_fh representation the NFSD VFS layer still uses. The result
wrapper carries a scratch kstatfs alongside the generated FSSTAT3res
type, which nfsd_statfs() fills before nfsd3_proc_fsstat() copies the
values into the encoded result.

The pc_argzero field is now set to zero for the NFSv3 FSSTAT
procedure. The xdrgen decoders are trusted to initialize all
arguments in the argp->xdrgen field, making the early defensive
memset unnecessary. The remaining argp fields are cleared as needed.

nfsd3_proc_fsstat() now sets the post-op attributes directly. This
makes it straightforward to confirm that the file handle is released
during every error flow.

nfs3svc_encode_fsstatres() no longer has any callers, so it is
removed, and with it the now-orphaned svcxdr_encode_fsstat3resok()
encode helper.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs3proc.c | 71 +++++++++++++++++++++++++++++++++++-----------
 fs/nfsd/nfs3xdr.c  | 45 -----------------------------
 fs/nfsd/xdr3.h     |  6 ++--
 3 files changed, 57 insertions(+), 65 deletions(-)

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 3706991ecea4..b9bcd05d4152 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -193,6 +193,13 @@ struct LINK3args_wrapper {
 
 static_assert(offsetof(struct LINK3args_wrapper, xdrgen) == 0);
 
+struct FSSTAT3args_wrapper {
+	struct FSSTAT3args	xdrgen;
+	struct svc_fh		fh;
+};
+
+static_assert(offsetof(struct FSSTAT3args_wrapper, xdrgen) == 0);
+
 static int nfsd3_ftype3_to_mode(ftype3 ftype)
 {
 	switch (ftype) {
@@ -1559,18 +1566,47 @@ nfsd3_proc_readdirplus(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-/*
- * Get file system stats
+/**
+ * nfsd3_proc_fsstat - FSSTAT: Get dynamic file system information
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   FSSTAT3res NFSPROC3_FSSTAT(FSSTAT3args) = 18;
  */
-static __be32
-nfsd3_proc_fsstat(struct svc_rqst *rqstp)
+static __be32 nfsd3_proc_fsstat(struct svc_rqst *rqstp)
 {
-	struct nfsd_fhandle *argp = rqstp->rq_argp;
+	struct FSSTAT3args_wrapper *argp = rqstp->rq_argp;
 	struct nfsd3_fsstatres *resp = rqstp->rq_resp;
+	struct kstatfs *statp = &resp->stats;
+	struct svc_fh *fhp = &argp->fh;
 
-	resp->status = nfsd_statfs(rqstp, &argp->fh, &resp->stats, 0);
-	fh_put(&argp->fh);
-	resp->status = nfsd3_map_status(resp->status);
+	nfsd3_fh3_to_svc_fh(fhp, &argp->xdrgen.fsroot);
+
+	resp->xdrgen.status = nfsd_statfs(rqstp, fhp, statp, 0);
+
+	if (resp->xdrgen.status == nfs_ok) {
+		struct FSSTAT3resok *resok = &resp->xdrgen.u.resok;
+		u64 bs = statp->f_bsize;
+
+		resok->obj_attributes.attributes_follow = false;
+		resok->tbytes = bs * statp->f_blocks;
+		resok->fbytes = bs * statp->f_bfree;
+		resok->abytes = bs * statp->f_bavail;
+		resok->tfiles = statp->f_files;
+		resok->ffiles = statp->f_ffree;
+		resok->afiles = statp->f_ffree;
+		resok->invarsec = 0;
+	} else {
+		struct FSSTAT3resfail *resfail = &resp->xdrgen.u.resfail;
+
+		resp->xdrgen.status = nfsd3_map_status(resp->xdrgen.status);
+		resfail->obj_attributes.attributes_follow = false;
+	}
+
+	fh_put(fhp);
 	return rpc_success;
 }
 
@@ -1928,15 +1964,15 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name = "READDIRPLUS",
 	},
 	[NFSPROC3_FSSTAT] = {
-		.pc_func = nfsd3_proc_fsstat,
-		.pc_decode = nfs3svc_decode_fhandleargs,
-		.pc_encode = nfs3svc_encode_fsstatres,
-		.pc_argsize = sizeof(struct nfsd3_fhandleargs),
-		.pc_argzero = sizeof(struct nfsd3_fhandleargs),
-		.pc_ressize = sizeof(struct nfsd3_fsstatres),
-		.pc_cachetype = RC_NOCACHE,
-		.pc_xdrressize = ST+pAT+2*6+1,
-		.pc_name = "FSSTAT",
+		.pc_func	= nfsd3_proc_fsstat,
+		.pc_decode	= nfs_svc_decode_FSSTAT3args,
+		.pc_encode	= nfs_svc_encode_FSSTAT3res,
+		.pc_argsize	= sizeof(struct FSSTAT3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct nfsd3_fsstatres),
+		.pc_cachetype	= RC_NOCACHE,
+		.pc_xdrressize	= NFS3_FSSTAT3res_sz,
+		.pc_name	= "FSSTAT",
 	},
 	[NFSPROC3_FSINFO] = {
 		.pc_func = nfsd3_proc_fsinfo,
@@ -2010,6 +2046,7 @@ union nfsd3_xdrstore {
 	struct READ3res			readres;
 	struct nfsd3_writeres		writeres;
 	struct nfsd3_readdirres		readdirres;
+	struct FSSTAT3args_wrapper	fsstatargs;
 	struct nfsd3_fsstatres		fsstatres;
 	struct nfsd3_fsinfores		fsinfores;
 	struct nfsd3_pathconfres	pathconfres;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index 59e89b7f9d19..0ca671bdd1c8 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -620,51 +620,6 @@ int nfs3svc_encode_entryplus3(void *data, const char *name, int namlen,
 	return -EINVAL;
 }
 
-static bool
-svcxdr_encode_fsstat3resok(struct xdr_stream *xdr,
-			   const struct nfsd3_fsstatres *resp)
-{
-	const struct kstatfs *s = &resp->stats;
-	u64 bs = s->f_bsize;
-	__be32 *p;
-
-	p = xdr_reserve_space(xdr, XDR_UNIT * 13);
-	if (!p)
-		return false;
-	p = xdr_encode_hyper(p, bs * s->f_blocks);	/* total bytes */
-	p = xdr_encode_hyper(p, bs * s->f_bfree);	/* free bytes */
-	p = xdr_encode_hyper(p, bs * s->f_bavail);	/* user available bytes */
-	p = xdr_encode_hyper(p, s->f_files);		/* total inodes */
-	p = xdr_encode_hyper(p, s->f_ffree);		/* free inodes */
-	p = xdr_encode_hyper(p, s->f_ffree);		/* user available inodes */
-	*p = cpu_to_be32(resp->invarsec);		/* mean unchanged time */
-
-	return true;
-}
-
-/* FSSTAT */
-bool
-nfs3svc_encode_fsstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_fsstatres *resp = rqstp->rq_resp;
-
-	if (!svcxdr_encode_nfsstat3(xdr, resp->status))
-		return false;
-	switch (resp->status) {
-	case nfs_ok:
-		if (!svcxdr_encode_post_op_attr(rqstp, xdr, &nfs3svc_null_fh))
-			return false;
-		if (!svcxdr_encode_fsstat3resok(xdr, resp))
-			return false;
-		break;
-	default:
-		if (!svcxdr_encode_post_op_attr(rqstp, xdr, &nfs3svc_null_fh))
-			return false;
-	}
-
-	return true;
-}
-
 static bool
 svcxdr_encode_fsinfo3resok(struct xdr_stream *xdr,
 			   const struct nfsd3_fsinfores *resp)
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 9f29f34cca8f..01c8539e67b8 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -93,11 +93,12 @@ struct nfsd3_readdirres {
 };
 
 struct nfsd3_fsstatres {
-	__be32			status;
+	struct FSSTAT3res	xdrgen;
 	struct kstatfs		stats;
-	__u32			invarsec;
 };
 
+static_assert(offsetof(struct nfsd3_fsstatres, xdrgen) == 0);
+
 struct nfsd3_fsinfores {
 	__be32			status;
 	__u32			f_rtmax;
@@ -143,7 +144,6 @@ bool nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 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_fsstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_fsinfores(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_pathconfres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_commitres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-- 
2.55.0


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

* [PATCH v1 24/33] NFSD: Use xdrgen XDR functions for the NFSv3 FSINFO procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (22 preceding siblings ...)
  2026-09-23 13:28 ` [PATCH v1 23/33] NFSD: Use xdrgen XDR functions for the NFSv3 FSSTAT procedure Chuck Lever
@ 2026-09-23 13:28 ` Chuck Lever
  2026-09-23 13:28 ` [PATCH v1 25/33] NFSD: Use xdrgen XDR functions for the NFSv3 PATHCONF procedure Chuck Lever
                   ` (8 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:28 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_FSINFO entry in the nfsd_procedures3 array with
an entry that dispatches the xdrgen-generated nfs_svc_decode_FSINFO3args
and nfs_svc_encode_FSINFO3res. A wrapper structure bridges the
generated xdrgen argument type and the legacy svc_fh representation the
NFSD VFS layer still uses.

The pc_argzero field is now set to zero for the NFSv3 FSINFO
procedure. The xdrgen decoders are trusted to initialize all arguments
in the argp->xdrgen field, making the early defensive memset
unnecessary. The remaining argp fields are cleared as needed.

nfsd3_proc_fsinfo() now sets the post-op attributes directly. This
makes it straightforward to confirm that the file handle is released
during every error flow.

nfs3svc_encode_fsinfores() no longer has any callers, so it is
removed, and with it the now-orphaned svcxdr_encode_fsinfo3resok()
encode helper and the nfs3svc_time_delta constant it referenced.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs3proc.c | 107 +++++++++++++++++++++++++++------------------
 fs/nfsd/nfs3xdr.c  |  55 -----------------------
 fs/nfsd/xdr3.h     |  14 ++----
 3 files changed, 67 insertions(+), 109 deletions(-)

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index b9bcd05d4152..ea3baab1ed95 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -24,9 +24,6 @@
 
 #define NFSDDBG_FACILITY		NFSDDBG_PROC
 
-#define NFS3_FSF_DEFAULT		0x001b
-#define NFS3_FSF_BILLYBOY		0x0018
-
 /*
  * Wrapper structures combine xdrgen types with legacy structures.
  * The xdrgen field must be first so the structure can be cast
@@ -200,6 +197,13 @@ struct FSSTAT3args_wrapper {
 
 static_assert(offsetof(struct FSSTAT3args_wrapper, xdrgen) == 0);
 
+struct FSINFO3args_wrapper {
+	struct FSINFO3args	xdrgen;
+	struct svc_fh		fh;
+};
+
+static_assert(offsetof(struct FSINFO3args_wrapper, xdrgen) == 0);
+
 static int nfsd3_ftype3_to_mode(ftype3 ftype)
 {
 	switch (ftype) {
@@ -1610,47 +1614,63 @@ static __be32 nfsd3_proc_fsstat(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-/*
- * Get file system info
+/**
+ * nfsd3_proc_fsinfo - FSINFO: Get static file system information
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   FSINFO3res NFSPROC3_FSINFO(FSINFO3args) = 19;
  */
-static __be32
-nfsd3_proc_fsinfo(struct svc_rqst *rqstp)
+static __be32 nfsd3_proc_fsinfo(struct svc_rqst *rqstp)
 {
-	struct nfsd_fhandle *argp = rqstp->rq_argp;
+	struct FSINFO3args_wrapper *argp = rqstp->rq_argp;
 	struct nfsd3_fsinfores *resp = rqstp->rq_resp;
-	u32	max_blocksize = svc_max_payload(rqstp);
+	struct svc_fh *fhp = &argp->fh;
 
-	dprintk("nfsd: FSINFO(3)   %s\n",
-				SVCFH_fmt(&argp->fh));
+	nfsd3_fh3_to_svc_fh(fhp, &argp->xdrgen.fsroot);
 
-	resp->f_rtmax  = max_blocksize;
-	resp->f_rtpref = max_blocksize;
-	resp->f_rtmult = PAGE_SIZE;
-	resp->f_wtmax  = max_blocksize;
-	resp->f_wtpref = max_blocksize;
-	resp->f_wtmult = PAGE_SIZE;
-	resp->f_dtpref = max_blocksize;
-	resp->f_maxfilesize = ~(u32) 0;
-	resp->f_properties = NFS3_FSF_DEFAULT;
+	resp->xdrgen.status = fh_verify(rqstp, fhp, 0, NFSD_MAY_NOP |
+					NFSD_MAY_BYPASS_GSS_ON_ROOT);
 
-	resp->status = fh_verify(rqstp, &argp->fh, 0,
-				 NFSD_MAY_NOP | NFSD_MAY_BYPASS_GSS_ON_ROOT);
+	if (resp->xdrgen.status == nfs_ok) {
+		struct FSINFO3resok *resok = &resp->xdrgen.u.resok;
+		struct super_block *sb = fhp->fh_dentry->d_sb;
+		u32 max_blocksize = svc_max_payload(rqstp);
 
-	/* Check special features of the file system. May request
-	 * different read/write sizes for file systems known to have
-	 * problems with large blocks */
-	if (resp->status == nfs_ok) {
-		struct super_block *sb = argp->fh.fh_dentry->d_sb;
+		resok->obj_attributes.attributes_follow = false;
+		resok->rtmax = max_blocksize;
+		resok->rtpref = max_blocksize;
+		resok->rtmult = PAGE_SIZE;
+		resok->wtmax = max_blocksize;
+		resok->wtpref = max_blocksize;
+		resok->wtmult = PAGE_SIZE;
+		resok->dtpref = max_blocksize;
+		resok->maxfilesize = ~(u32)0;
+		/* NFSD is accurate to the nearest second. */
+		resok->time_delta.seconds = 1;
+		resok->time_delta.nseconds = 0;
+		resok->properties = FSF3_LINK | FSF3_SYMLINK |
+				    FSF3_HOMOGENEOUS | FSF3_CANSETTIME;
 
-		/* Note that we don't care for remote fs's here */
-		if (sb->s_magic == MSDOS_SUPER_MAGIC) {
-			resp->f_properties = NFS3_FSF_BILLYBOY;
-		}
-		resp->f_maxfilesize = sb->s_maxbytes;
+		/*
+		 * Check special features of the file system. May
+		 * request different read/write sizes for file systems
+		 * known to have problems with large blocks.
+		 */
+		if (sb->s_magic == MSDOS_SUPER_MAGIC)
+			resok->properties = FSF3_HOMOGENEOUS | FSF3_CANSETTIME;
+		resok->maxfilesize = sb->s_maxbytes;
+	} else {
+		struct FSINFO3resfail *resfail = &resp->xdrgen.u.resfail;
+
+		resp->xdrgen.status = nfsd3_map_status(resp->xdrgen.status);
+		resfail->obj_attributes.attributes_follow = false;
 	}
 
-	fh_put(&argp->fh);
-	resp->status = nfsd3_map_status(resp->status);
+	fh_put(fhp);
 	return rpc_success;
 }
 
@@ -1975,15 +1995,15 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "FSSTAT",
 	},
 	[NFSPROC3_FSINFO] = {
-		.pc_func = nfsd3_proc_fsinfo,
-		.pc_decode = nfs3svc_decode_fhandleargs,
-		.pc_encode = nfs3svc_encode_fsinfores,
-		.pc_argsize = sizeof(struct nfsd3_fhandleargs),
-		.pc_argzero = sizeof(struct nfsd3_fhandleargs),
-		.pc_ressize = sizeof(struct nfsd3_fsinfores),
-		.pc_cachetype = RC_NOCACHE,
-		.pc_xdrressize = ST+pAT+12,
-		.pc_name = "FSINFO",
+		.pc_func	= nfsd3_proc_fsinfo,
+		.pc_decode	= nfs_svc_decode_FSINFO3args,
+		.pc_encode	= nfs_svc_encode_FSINFO3res,
+		.pc_argsize	= sizeof(struct FSINFO3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct nfsd3_fsinfores),
+		.pc_cachetype	= RC_NOCACHE,
+		.pc_xdrressize	= NFS3_FSINFO3res_sz,
+		.pc_name	= "FSINFO",
 	},
 	[NFSPROC3_PATHCONF] = {
 		.pc_func = nfsd3_proc_pathconf,
@@ -2048,6 +2068,7 @@ union nfsd3_xdrstore {
 	struct nfsd3_readdirres		readdirres;
 	struct FSSTAT3args_wrapper	fsstatargs;
 	struct nfsd3_fsstatres		fsstatres;
+	struct FSINFO3args_wrapper	fsinfoargs;
 	struct nfsd3_fsinfores		fsinfores;
 	struct nfsd3_pathconfres	pathconfres;
 	struct nfsd3_commitres		commitres;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index 0ca671bdd1c8..9e400a97e435 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -24,15 +24,6 @@ static const struct svc_fh nfs3svc_null_fh = {
 	.fh_no_wcc	= true,
 };
 
-/*
- * time_delta. {1, 0} means the server is accurate only
- * to the nearest second.
- */
-static const struct timespec64 nfs3svc_time_delta = {
-	.tv_sec		= 1,
-	.tv_nsec	= 0,
-};
-
 /*
  * Mapping of S_IF* types to NFS file types
  */
@@ -620,52 +611,6 @@ int nfs3svc_encode_entryplus3(void *data, const char *name, int namlen,
 	return -EINVAL;
 }
 
-static bool
-svcxdr_encode_fsinfo3resok(struct xdr_stream *xdr,
-			   const struct nfsd3_fsinfores *resp)
-{
-	__be32 *p;
-
-	p = xdr_reserve_space(xdr, XDR_UNIT * 12);
-	if (!p)
-		return false;
-	*p++ = cpu_to_be32(resp->f_rtmax);
-	*p++ = cpu_to_be32(resp->f_rtpref);
-	*p++ = cpu_to_be32(resp->f_rtmult);
-	*p++ = cpu_to_be32(resp->f_wtmax);
-	*p++ = cpu_to_be32(resp->f_wtpref);
-	*p++ = cpu_to_be32(resp->f_wtmult);
-	*p++ = cpu_to_be32(resp->f_dtpref);
-	p = xdr_encode_hyper(p, resp->f_maxfilesize);
-	p = encode_nfstime3(p, &nfs3svc_time_delta);
-	*p = cpu_to_be32(resp->f_properties);
-
-	return true;
-}
-
-/* FSINFO */
-bool
-nfs3svc_encode_fsinfores(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_fsinfores *resp = rqstp->rq_resp;
-
-	if (!svcxdr_encode_nfsstat3(xdr, resp->status))
-		return false;
-	switch (resp->status) {
-	case nfs_ok:
-		if (!svcxdr_encode_post_op_attr(rqstp, xdr, &nfs3svc_null_fh))
-			return false;
-		if (!svcxdr_encode_fsinfo3resok(xdr, resp))
-			return false;
-		break;
-	default:
-		if (!svcxdr_encode_post_op_attr(rqstp, xdr, &nfs3svc_null_fh))
-			return false;
-	}
-
-	return true;
-}
-
 static bool
 svcxdr_encode_pathconf3resok(struct xdr_stream *xdr,
 			     const struct nfsd3_pathconfres *resp)
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 01c8539e67b8..7c39d7c35df0 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -100,18 +100,11 @@ struct nfsd3_fsstatres {
 static_assert(offsetof(struct nfsd3_fsstatres, xdrgen) == 0);
 
 struct nfsd3_fsinfores {
-	__be32			status;
-	__u32			f_rtmax;
-	__u32			f_rtpref;
-	__u32			f_rtmult;
-	__u32			f_wtmax;
-	__u32			f_wtpref;
-	__u32			f_wtmult;
-	__u32			f_dtpref;
-	__u64			f_maxfilesize;
-	__u32			f_properties;
+	struct FSINFO3res	xdrgen;
 };
 
+static_assert(offsetof(struct nfsd3_fsinfores, xdrgen) == 0);
+
 struct nfsd3_pathconfres {
 	__be32			status;
 	__u32			p_link_max;
@@ -144,7 +137,6 @@ bool nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 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_fsinfores(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_pathconfres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_commitres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
-- 
2.55.0


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

* [PATCH v1 25/33] NFSD: Use xdrgen XDR functions for the NFSv3 PATHCONF procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (23 preceding siblings ...)
  2026-09-23 13:28 ` [PATCH v1 24/33] NFSD: Use xdrgen XDR functions for the NFSv3 FSINFO procedure Chuck Lever
@ 2026-09-23 13:28 ` Chuck Lever
  2026-09-23 13:28 ` [PATCH v1 26/33] NFSD: Use xdrgen XDR functions for the NFSv3 COMMIT procedure Chuck Lever
                   ` (7 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:28 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_PATHCONF entry in the nfsd_procedures3 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_PATHCONF3args and nfs_svc_encode_PATHCONF3res. A
wrapper structure bridges the generated xdrgen argument type and the
legacy svc_fh representation the NFSD VFS layer still uses.

The pc_argzero field is now set to zero for the NFSv3 PATHCONF
procedure. The xdrgen decoders are trusted to initialize all
arguments in the argp->xdrgen field, making the early defensive
memset unnecessary. The remaining argp fields are cleared as needed.

nfsd3_proc_pathconf() now sets the post-op attributes directly. This
makes it straightforward to confirm that the file handle is released
during every error flow.

nfs3svc_encode_pathconfres() no longer has any callers, so it is
removed, and with it the now-orphaned svcxdr_encode_pathconf3resok()
encode helper and the nfs3svc_null_fh constant it referenced. This
was also the last user of the shared nfs3svc_decode_fhandleargs()
decoder, so that too is removed.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs3proc.c | 94 +++++++++++++++++++++++++++-------------------
 fs/nfsd/nfs3xdr.c  | 57 ----------------------------
 fs/nfsd/xdr3.h     | 12 ++----
 3 files changed, 59 insertions(+), 104 deletions(-)

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index ea3baab1ed95..c4b6ccb3a74e 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -204,6 +204,13 @@ struct FSINFO3args_wrapper {
 
 static_assert(offsetof(struct FSINFO3args_wrapper, xdrgen) == 0);
 
+struct PATHCONF3args_wrapper {
+	struct PATHCONF3args	xdrgen;
+	struct svc_fh		fh;
+};
+
+static_assert(offsetof(struct PATHCONF3args_wrapper, xdrgen) == 0);
+
 static int nfsd3_ftype3_to_mode(ftype3 ftype)
 {
 	switch (ftype) {
@@ -1674,40 +1681,47 @@ static __be32 nfsd3_proc_fsinfo(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-/*
- * Get pathconf info for the specified file
+/**
+ * nfsd3_proc_pathconf - PATHCONF: Retrieve POSIX information
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   PATHCONF3res NFSPROC3_PATHCONF(PATHCONF3args) = 20;
  */
-static __be32
-nfsd3_proc_pathconf(struct svc_rqst *rqstp)
+static __be32 nfsd3_proc_pathconf(struct svc_rqst *rqstp)
 {
-	struct nfsd_fhandle *argp = rqstp->rq_argp;
+	struct PATHCONF3args_wrapper *argp = rqstp->rq_argp;
 	struct nfsd3_pathconfres *resp = rqstp->rq_resp;
+	struct svc_fh *fhp = &argp->fh;
 
-	dprintk("nfsd: PATHCONF(3) %s\n",
-				SVCFH_fmt(&argp->fh));
+	nfsd3_fh3_to_svc_fh(fhp, &argp->xdrgen.object);
 
-	/* Set default pathconf */
-	resp->p_link_max = 255;		/* at least */
-	resp->p_name_max = 255;		/* at least */
-	resp->p_no_trunc = 0;
-	resp->p_chown_restricted = 1;
-	resp->p_case_insensitive = false;
-	resp->p_case_preserving = true;
+	resp->xdrgen.status = fh_verify(rqstp, fhp, 0, NFSD_MAY_NOP);
 
-	resp->status = fh_verify(rqstp, &argp->fh, 0, NFSD_MAY_NOP);
-
-	if (resp->status == nfs_ok) {
-		struct super_block *sb = argp->fh.fh_dentry->d_sb;
+	if (resp->xdrgen.status == nfs_ok) {
+		struct PATHCONF3resok *resok = &resp->xdrgen.u.resok;
+		struct super_block *sb = fhp->fh_dentry->d_sb;
 		int err;
 
+		resok->obj_attributes.attributes_follow = false;
+		resok->linkmax = 255;
+		resok->name_max = NAME_MAX;
+		resok->no_trunc = false;
+		resok->chown_restricted = true;
+		resok->case_insensitive = false;
+		resok->case_preserving = true;
+
 		if (sb->s_magic == EXT2_SUPER_MAGIC) {
-			resp->p_link_max = EXT2_LINK_MAX;
-			resp->p_name_max = EXT2_NAME_LEN;
+			resok->linkmax = EXT2_LINK_MAX;
+			resok->name_max = EXT2_NAME_LEN;
 		}
 
-		err = nfsd_get_case_info(argp->fh.fh_dentry,
-					 &resp->p_case_insensitive,
-					 &resp->p_case_preserving);
+		err = nfsd_get_case_info(fhp->fh_dentry,
+					 &resok->case_insensitive,
+					 &resok->case_preserving);
 		/*
 		 * RFC 1813 lists NFS3ERR_STALE, NFS3ERR_BADHANDLE, and
 		 * NFS3ERR_SERVERFAULT as the only PATHCONF errors.
@@ -1724,19 +1738,23 @@ nfsd3_proc_pathconf(struct svc_rqst *rqstp)
 			 * handle is unusable without implying a server
 			 * malfunction.
 			 */
-			resp->status = nfserr_stale;
+			resp->xdrgen.status = nfserr_stale;
 			break;
 		case -ESTALE:
-			resp->status = nfserr_stale;
+			resp->xdrgen.status = nfserr_stale;
 			break;
 		default:
-			resp->status = nfserr_serverfault;
+			resp->xdrgen.status = nfserr_serverfault;
 			break;
 		}
+	} else {
+		struct PATHCONF3resfail *resfail = &resp->xdrgen.u.resfail;
+
+		resp->xdrgen.status = nfsd3_map_status(resp->xdrgen.status);
+		resfail->obj_attributes.attributes_follow = false;
 	}
 
-	fh_put(&argp->fh);
-	resp->status = nfsd3_map_status(resp->status);
+	fh_put(fhp);
 	return rpc_success;
 }
 
@@ -1774,7 +1792,6 @@ nfsd3_proc_commit(struct svc_rqst *rqstp)
  * Only the results of non-idempotent operations are cached.
  */
 #define nfsd3_readdirplusargs		nfsd3_readdirargs
-#define nfsd3_fhandleargs		nfsd_fhandle
 
 #define ST 1		/* status*/
 #define AT 21		/* attributes */
@@ -2006,15 +2023,15 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "FSINFO",
 	},
 	[NFSPROC3_PATHCONF] = {
-		.pc_func = nfsd3_proc_pathconf,
-		.pc_decode = nfs3svc_decode_fhandleargs,
-		.pc_encode = nfs3svc_encode_pathconfres,
-		.pc_argsize = sizeof(struct nfsd3_fhandleargs),
-		.pc_argzero = sizeof(struct nfsd3_fhandleargs),
-		.pc_ressize = sizeof(struct nfsd3_pathconfres),
-		.pc_cachetype = RC_NOCACHE,
-		.pc_xdrressize = ST+pAT+6,
-		.pc_name = "PATHCONF",
+		.pc_func	= nfsd3_proc_pathconf,
+		.pc_decode	= nfs_svc_decode_PATHCONF3args,
+		.pc_encode	= nfs_svc_encode_PATHCONF3res,
+		.pc_argsize	= sizeof(struct PATHCONF3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct nfsd3_pathconfres),
+		.pc_cachetype	= RC_NOCACHE,
+		.pc_xdrressize	= NFS3_PATHCONF3res_sz,
+		.pc_name	= "PATHCONF",
 	},
 	[NFSPROC3_COMMIT] = {
 		.pc_func = nfsd3_proc_commit,
@@ -2070,6 +2087,7 @@ union nfsd3_xdrstore {
 	struct nfsd3_fsstatres		fsstatres;
 	struct FSINFO3args_wrapper	fsinfoargs;
 	struct nfsd3_fsinfores		fsinfores;
+	struct PATHCONF3args_wrapper	pathconfargs;
 	struct nfsd3_pathconfres	pathconfres;
 	struct nfsd3_commitres		commitres;
 	struct nfsd3_getaclres		getaclres;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index 9e400a97e435..3da66b5cac22 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -17,13 +17,6 @@
 #include "vfs.h"
 #include "nfserr.h"
 
-/*
- * Force construction of an empty post-op attr
- */
-static const struct svc_fh nfs3svc_null_fh = {
-	.fh_no_wcc	= true,
-};
-
 /*
  * Mapping of S_IF* types to NFS file types
  */
@@ -310,14 +303,6 @@ svcxdr_encode_wcc_data(struct svc_rqst *rqstp, struct xdr_stream *xdr,
  * XDR decode functions
  */
 
-bool
-nfs3svc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd_fhandle *args = rqstp->rq_argp;
-
-	return svcxdr_decode_nfs_fh3(xdr, &args->fh);
-}
-
 bool
 nfs3svc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 {
@@ -611,48 +596,6 @@ int nfs3svc_encode_entryplus3(void *data, const char *name, int namlen,
 	return -EINVAL;
 }
 
-static bool
-svcxdr_encode_pathconf3resok(struct xdr_stream *xdr,
-			     const struct nfsd3_pathconfres *resp)
-{
-	__be32 *p;
-
-	p = xdr_reserve_space(xdr, XDR_UNIT * 6);
-	if (!p)
-		return false;
-	*p++ = cpu_to_be32(resp->p_link_max);
-	*p++ = cpu_to_be32(resp->p_name_max);
-	p = xdr_encode_bool(p, resp->p_no_trunc);
-	p = xdr_encode_bool(p, resp->p_chown_restricted);
-	p = xdr_encode_bool(p, resp->p_case_insensitive);
-	xdr_encode_bool(p, resp->p_case_preserving);
-
-	return true;
-}
-
-/* PATHCONF */
-bool
-nfs3svc_encode_pathconfres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_pathconfres *resp = rqstp->rq_resp;
-
-	if (!svcxdr_encode_nfsstat3(xdr, resp->status))
-		return false;
-	switch (resp->status) {
-	case nfs_ok:
-		if (!svcxdr_encode_post_op_attr(rqstp, xdr, &nfs3svc_null_fh))
-			return false;
-		if (!svcxdr_encode_pathconf3resok(xdr, resp))
-			return false;
-		break;
-	default:
-		if (!svcxdr_encode_post_op_attr(rqstp, xdr, &nfs3svc_null_fh))
-			return false;
-	}
-
-	return true;
-}
-
 /* COMMIT */
 bool
 nfs3svc_encode_commitres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 7c39d7c35df0..4116038ec80e 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -106,15 +106,11 @@ struct nfsd3_fsinfores {
 static_assert(offsetof(struct nfsd3_fsinfores, xdrgen) == 0);
 
 struct nfsd3_pathconfres {
-	__be32			status;
-	__u32			p_link_max;
-	__u32			p_name_max;
-	__u32			p_no_trunc;
-	__u32			p_chown_restricted;
-	bool			p_case_insensitive;
-	bool			p_case_preserving;
+	struct PATHCONF3res	xdrgen;
 };
 
+static_assert(offsetof(struct nfsd3_pathconfres, xdrgen) == 0);
+
 struct nfsd3_commitres {
 	__be32			status;
 	struct svc_fh		fh;
@@ -130,14 +126,12 @@ struct nfsd3_getaclres {
 	struct kstat		stat;
 };
 
-bool nfs3svc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_readdirplusargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 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_pathconfres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_commitres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 void nfs3svc_release_fhandle(struct svc_rqst *);
-- 
2.55.0


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

* [PATCH v1 26/33] NFSD: Use xdrgen XDR functions for the NFSv3 COMMIT procedure
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (24 preceding siblings ...)
  2026-09-23 13:28 ` [PATCH v1 25/33] NFSD: Use xdrgen XDR functions for the NFSv3 PATHCONF procedure Chuck Lever
@ 2026-09-23 13:28 ` Chuck Lever
  2026-09-23 13:28 ` [PATCH v1 27/33] NFSD: Use xdrgen XDR functions for NFSv3 READDIR arguments Chuck Lever
                   ` (6 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:28 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_COMMIT entry in the nfsd_procedures3 array with
an entry that dispatches the xdrgen-generated
nfs_svc_decode_COMMIT3args and nfs_svc_encode_COMMIT3res. A wrapper
structure bridges the generated xdrgen argument type and the legacy
svc_fh representation the NFSD VFS layer still uses.

The pc_argzero field is now set to zero for the NFSv3 COMMIT
procedure. The xdrgen decoders are trusted to initialize all
arguments in the argp->xdrgen field, making the early defensive
memset unnecessary. The remaining argp fields are cleared as needed.

Because nfsd3_proc_commit() now fills in the WCC data, including
both the pre-op and post-op attributes, the fh_getattr() calls are
made in the proc function rather than in the XDR result encoder, and
the reference to the file handle can be released directly by
nfsd3_proc_commit(). A separate ->pc_release callback is thus no
longer needed. This makes it straightforward to confirm that the
file handle is properly released during every error flow.

nfs3svc_decode_commitargs() and nfs3svc_encode_commitres() no longer
have any callers, so they are removed. COMMIT was the last caller of
the hand-written WCC encoders, so svcxdr_encode_wcc_data() and its
svcxdr_encode_pre_op_attr() and svcxdr_encode_wcc_attr() helpers are
removed as well, along with the now-orphaned svcxdr_encode_writeverf3()
encode helper.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs3proc.c |  80 +++++++++++++++++++-------------
 fs/nfsd/nfs3xdr.c  | 113 ---------------------------------------------
 fs/nfsd/xdr3.h     |  14 ++----
 3 files changed, 52 insertions(+), 155 deletions(-)

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index c4b6ccb3a74e..68bf988476cf 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -211,6 +211,13 @@ struct PATHCONF3args_wrapper {
 
 static_assert(offsetof(struct PATHCONF3args_wrapper, xdrgen) == 0);
 
+struct COMMIT3args_wrapper {
+	struct COMMIT3args	xdrgen;
+	struct svc_fh		fh;
+};
+
+static_assert(offsetof(struct COMMIT3args_wrapper, xdrgen) == 0);
+
 static int nfsd3_ftype3_to_mode(ftype3 ftype)
 {
 	switch (ftype) {
@@ -1758,31 +1765,47 @@ static __be32 nfsd3_proc_pathconf(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-/*
- * Commit a file (range) to stable storage.
+/**
+ * nfsd3_proc_commit - COMMIT: Commit cached data on a server to stable storage
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   COMMIT3res NFSPROC3_COMMIT(COMMIT3args) = 21;
  */
-static __be32
-nfsd3_proc_commit(struct svc_rqst *rqstp)
+static __be32 nfsd3_proc_commit(struct svc_rqst *rqstp)
 {
-	struct nfsd3_commitargs *argp = rqstp->rq_argp;
+	struct COMMIT3args_wrapper *argp = rqstp->rq_argp;
 	struct nfsd3_commitres *resp = rqstp->rq_resp;
+	struct svc_fh *fhp = &argp->fh;
 	struct nfsd_file *nf;
 
-	dprintk("nfsd: COMMIT(3)   %s %u@%Lu\n",
-				SVCFH_fmt(&argp->fh),
-				argp->count,
-				(unsigned long long) argp->offset);
+	nfsd3_fh3_to_svc_fh(fhp, &argp->xdrgen.file);
 
-	fh_copy(&resp->fh, &argp->fh);
-	resp->status = nfsd_file_acquire_gc(rqstp, &resp->fh, NFSD_MAY_WRITE |
-					    NFSD_MAY_NOT_BREAK_LEASE, &nf);
-	if (resp->status)
+	resp->xdrgen.status = nfsd_file_acquire_gc(rqstp, fhp, NFSD_MAY_WRITE |
+						   NFSD_MAY_NOT_BREAK_LEASE, &nf);
+	if (resp->xdrgen.status)
 		goto out;
-	resp->status = nfsd_commit(rqstp, &resp->fh, nf, argp->offset,
-				   argp->count, resp->verf);
+	resp->xdrgen.status = nfsd_commit(rqstp, fhp, nf,
+					  argp->xdrgen.offset, argp->xdrgen.count,
+					  (__be32 *)resp->xdrgen.u.resok.verf);
 	nfsd_file_put(nf);
+
 out:
-	resp->status = nfsd3_map_status(resp->status);
+	if (resp->xdrgen.status == nfs_ok) {
+		struct COMMIT3resok *resok = &resp->xdrgen.u.resok;
+
+		nfsd3_fill_wcc_data(rqstp, &resok->file_wcc, fhp);
+	} else {
+		struct COMMIT3resfail *resfail = &resp->xdrgen.u.resfail;
+
+		resp->xdrgen.status = nfsd3_map_status(resp->xdrgen.status);
+		nfsd3_fill_wcc_data(rqstp, &resfail->file_wcc, fhp);
+	}
+
+	fh_put(fhp);
 	return rpc_success;
 }
 
@@ -1793,11 +1816,6 @@ nfsd3_proc_commit(struct svc_rqst *rqstp)
  */
 #define nfsd3_readdirplusargs		nfsd3_readdirargs
 
-#define ST 1		/* status*/
-#define AT 21		/* attributes */
-#define pAT (1+AT)	/* post attributes - conditional */
-#define WC (7+pAT)	/* WCC attributes */
-
 static const struct svc_procedure nfsd_procedures3[22] = {
 	[NFSPROC3_NULL] = {
 		.pc_func	= nfsd3_proc_null,
@@ -2034,16 +2052,15 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "PATHCONF",
 	},
 	[NFSPROC3_COMMIT] = {
-		.pc_func = nfsd3_proc_commit,
-		.pc_decode = nfs3svc_decode_commitargs,
-		.pc_encode = nfs3svc_encode_commitres,
-		.pc_release = nfs3svc_release_fhandle,
-		.pc_argsize = sizeof(struct nfsd3_commitargs),
-		.pc_argzero = sizeof(struct nfsd3_commitargs),
-		.pc_ressize = sizeof(struct nfsd3_commitres),
-		.pc_cachetype = RC_NOCACHE,
-		.pc_xdrressize = ST+WC+2,
-		.pc_name = "COMMIT",
+		.pc_func	= nfsd3_proc_commit,
+		.pc_decode	= nfs_svc_decode_COMMIT3args,
+		.pc_encode	= nfs_svc_encode_COMMIT3res,
+		.pc_argsize	= sizeof(struct COMMIT3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct nfsd3_commitres),
+		.pc_cachetype	= RC_NOCACHE,
+		.pc_xdrressize	= NFS3_COMMIT3res_sz,
+		.pc_name	= "COMMIT",
 	},
 };
 
@@ -2089,6 +2106,7 @@ union nfsd3_xdrstore {
 	struct nfsd3_fsinfores		fsinfores;
 	struct PATHCONF3args_wrapper	pathconfargs;
 	struct nfsd3_pathconfres	pathconfres;
+	struct COMMIT3args_wrapper	commitargs;
 	struct nfsd3_commitres		commitres;
 	struct nfsd3_getaclres		getaclres;
 };
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index 3da66b5cac22..e93983d08aa5 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -133,19 +133,6 @@ svcxdr_encode_cookieverf3(struct xdr_stream *xdr, const __be32 *verf)
 	return true;
 }
 
-static bool
-svcxdr_encode_writeverf3(struct xdr_stream *xdr, const __be32 *verf)
-{
-	__be32 *p;
-
-	p = xdr_reserve_space(xdr, NFS3_WRITEVERFSIZE);
-	if (!p)
-		return false;
-	memcpy(p, verf, NFS3_WRITEVERFSIZE);
-
-	return true;
-}
-
 static bool
 svcxdr_encode_fattr3(struct svc_rqst *rqstp, struct xdr_stream *xdr,
 		     const struct svc_fh *fhp, const struct kstat *stat)
@@ -198,35 +185,6 @@ svcxdr_encode_fattr3(struct svc_rqst *rqstp, struct xdr_stream *xdr,
 	return true;
 }
 
-static bool
-svcxdr_encode_wcc_attr(struct xdr_stream *xdr, const struct svc_fh *fhp)
-{
-	__be32 *p;
-
-	p = xdr_reserve_space(xdr, XDR_UNIT * 6);
-	if (!p)
-		return false;
-	p = xdr_encode_hyper(p, (u64)fhp->fh_pre_size);
-	p = encode_nfstime3(p, &fhp->fh_pre_mtime);
-	encode_nfstime3(p, &fhp->fh_pre_ctime);
-
-	return true;
-}
-
-static bool
-svcxdr_encode_pre_op_attr(struct xdr_stream *xdr, const struct svc_fh *fhp)
-{
-	if (!fhp->fh_pre_saved) {
-		if (xdr_stream_encode_item_absent(xdr) < 0)
-			return false;
-		return true;
-	}
-
-	if (xdr_stream_encode_item_present(xdr) < 0)
-		return false;
-	return svcxdr_encode_wcc_attr(xdr, fhp);
-}
-
 /**
  * svcxdr_encode_post_op_attr - Encode NFSv3 post-op attributes
  * @rqstp: Context of a completed RPC transaction
@@ -266,39 +224,6 @@ svcxdr_encode_post_op_attr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
 	return xdr_stream_encode_item_absent(xdr) > 0;
 }
 
-/*
- * Encode weak cache consistency data
- */
-static bool
-svcxdr_encode_wcc_data(struct svc_rqst *rqstp, struct xdr_stream *xdr,
-		       const struct svc_fh *fhp)
-{
-	struct dentry *dentry = fhp->fh_dentry;
-
-	if (!dentry || !d_really_is_positive(dentry) || !fhp->fh_post_saved)
-		goto neither;
-
-	/* before */
-	if (!svcxdr_encode_pre_op_attr(xdr, fhp))
-		return false;
-
-	/* after */
-	if (xdr_stream_encode_item_present(xdr) < 0)
-		return false;
-	if (!svcxdr_encode_fattr3(rqstp, xdr, fhp, &fhp->fh_post_attr))
-		return false;
-
-	return true;
-
-neither:
-	if (xdr_stream_encode_item_absent(xdr) < 0)
-		return false;
-	if (!svcxdr_encode_post_op_attr(rqstp, xdr, fhp))
-		return false;
-
-	return true;
-}
-
 /*
  * XDR decode functions
  */
@@ -343,21 +268,6 @@ nfs3svc_decode_readdirplusargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 	return true;
 }
 
-bool
-nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_commitargs *args = rqstp->rq_argp;
-
-	if (!svcxdr_decode_nfs_fh3(xdr, &args->fh))
-		return false;
-	if (xdr_stream_decode_u64(xdr, &args->offset) < 0)
-		return false;
-	if (xdr_stream_decode_u32(xdr, &args->count) < 0)
-		return false;
-
-	return true;
-}
-
 /*
  * XDR encode functions
  */
@@ -596,29 +506,6 @@ int nfs3svc_encode_entryplus3(void *data, const char *name, int namlen,
 	return -EINVAL;
 }
 
-/* COMMIT */
-bool
-nfs3svc_encode_commitres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_commitres *resp = rqstp->rq_resp;
-
-	if (!svcxdr_encode_nfsstat3(xdr, resp->status))
-		return false;
-	switch (resp->status) {
-	case nfs_ok:
-		if (!svcxdr_encode_wcc_data(rqstp, xdr, &resp->fh))
-			return false;
-		if (!svcxdr_encode_writeverf3(xdr, resp->verf))
-			return false;
-		break;
-	default:
-		if (!svcxdr_encode_wcc_data(rqstp, xdr, &resp->fh))
-			return false;
-	}
-
-	return true;
-}
-
 /*
  * XDR release functions
  */
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 4116038ec80e..c434c93462a8 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -38,12 +38,6 @@ struct nfsd3_readdirargs {
 	__be32 *		verf;
 };
 
-struct nfsd3_commitargs {
-	struct svc_fh		fh;
-	__u64			offset;
-	__u32			count;
-};
-
 struct nfsd3_getaclargs {
 	struct svc_fh		fh;
 	__u32			mask;
@@ -112,11 +106,11 @@ struct nfsd3_pathconfres {
 static_assert(offsetof(struct nfsd3_pathconfres, xdrgen) == 0);
 
 struct nfsd3_commitres {
-	__be32			status;
-	struct svc_fh		fh;
-	__be32			verf[2];
+	struct COMMIT3res	xdrgen;
 };
 
+static_assert(offsetof(struct nfsd3_commitres, xdrgen) == 0);
+
 struct nfsd3_getaclres {
 	__be32			status;
 	struct svc_fh		fh;
@@ -128,11 +122,9 @@ struct nfsd3_getaclres {
 
 bool nfs3svc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_readdirplusargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 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_commitres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 void nfs3svc_release_fhandle(struct svc_rqst *);
 
-- 
2.55.0


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

* [PATCH v1 27/33] NFSD: Use xdrgen XDR functions for NFSv3 READDIR arguments
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (25 preceding siblings ...)
  2026-09-23 13:28 ` [PATCH v1 26/33] NFSD: Use xdrgen XDR functions for the NFSv3 COMMIT procedure Chuck Lever
@ 2026-09-23 13:28 ` Chuck Lever
  2026-09-23 13:28 ` [PATCH v1 28/33] NFSD: Use xdrgen XDR functions for NFSv3 READDIRPLUS arguments Chuck Lever
                   ` (5 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:28 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_READDIR entry in the nfsd_procedures3 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_READDIR3args. A wrapper structure bridges the
generated xdrgen READDIR3args type and the legacy svc_fh
representation the NFSD VFS layer still uses.

The pc_argzero field is now set to zero for the NFSv3 READDIR
procedure. The xdrgen decoders are trusted to initialize all
arguments in the argp->xdrgen field, making the early defensive
memset unnecessary. The remaining argp fields are cleared as
needed.

nfs3svc_decode_readdirargs() no longer has any callers, so it is
removed.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs3proc.c | 59 +++++++++++++++++++++++++++++-----------------
 fs/nfsd/nfs3xdr.c  | 18 --------------
 fs/nfsd/xdr3.h     |  1 -
 3 files changed, 37 insertions(+), 41 deletions(-)

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 68bf988476cf..f221bd11482e 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -218,6 +218,13 @@ 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);
+
 static int nfsd3_ftype3_to_mode(ftype3 ftype)
 {
 	switch (ftype) {
@@ -1510,28 +1517,35 @@ static void nfsd3_init_dirlist_pages(struct svc_rqst *rqstp,
 	xdr_init_encode_pages(xdr, buf);
 }
 
-/*
- * Read a portion of a directory.
+/**
+ * nfsd3_proc_readdir - READDIR: Read from directory
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   READDIR3res NFSPROC3_READDIR(READDIR3args) = 16;
  */
-static __be32
-nfsd3_proc_readdir(struct svc_rqst *rqstp)
+static __be32 nfsd3_proc_readdir(struct svc_rqst *rqstp)
 {
-	struct nfsd3_readdirargs *argp = rqstp->rq_argp;
+	struct READDIR3args_wrapper *argp = rqstp->rq_argp;
 	struct nfsd3_readdirres  *resp = rqstp->rq_resp;
-	loff_t		offset;
+	loff_t offset = argp->xdrgen.cookie;
+	struct svc_fh *fhp = &argp->fh;
 
-	trace_nfsd_vfs_readdir(rqstp, &argp->fh, argp->count, argp->cookie);
+	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->count);
-
-	fh_copy(&resp->fh, &argp->fh);
+	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;
-	offset = argp->cookie;
 	resp->status = nfsd_readdir(rqstp, &resp->fh, &offset,
 				    &resp->common, nfs3svc_encode_entry3);
-	memcpy(resp->verf, argp->verf, 8);
+	memcpy(resp->verf, argp->xdrgen.cookieverf, NFS3_COOKIEVERFSIZE);
 	nfs3svc_encode_cookie3(resp, offset);
 
 	/* Recycle only pages that were part of the reply */
@@ -1997,15 +2011,15 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "LINK",
 	},
 	[NFSPROC3_READDIR] = {
-		.pc_func = nfsd3_proc_readdir,
-		.pc_decode = nfs3svc_decode_readdirargs,
-		.pc_encode = nfs3svc_encode_readdirres,
-		.pc_release = nfs3svc_release_fhandle,
-		.pc_argsize = sizeof(struct nfsd3_readdirargs),
-		.pc_argzero = sizeof(struct nfsd3_readdirargs),
-		.pc_ressize = sizeof(struct nfsd3_readdirres),
-		.pc_cachetype = RC_NOCACHE,
-		.pc_name = "READDIR",
+		.pc_func	= nfsd3_proc_readdir,
+		.pc_decode	= nfs_svc_decode_READDIR3args,
+		.pc_encode	= nfs3svc_encode_readdirres,
+		.pc_release	= nfs3svc_release_fhandle,
+		.pc_argsize	= sizeof(struct READDIR3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct nfsd3_readdirres),
+		.pc_cachetype	= RC_NOCACHE,
+		.pc_name	= "READDIR",
 	},
 	[NFSPROC3_READDIRPLUS] = {
 		.pc_func = nfsd3_proc_readdirplus,
@@ -2095,7 +2109,8 @@ union nfsd3_xdrstore {
 	struct RENAME3res		renameres;
 	struct LINK3args_wrapper	linkargs;
 	struct LINK3res			linkres;
-	struct nfsd3_readdirargs	readdirargs;
+	struct READDIR3args_wrapper	readdirargs;
+	struct nfsd3_readdirargs	readdirplusargs;
 	struct READLINK3res		readlinkres;
 	struct READ3res			readres;
 	struct nfsd3_writeres		writeres;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index e93983d08aa5..0e83a2245c45 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -228,24 +228,6 @@ svcxdr_encode_post_op_attr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
  * XDR decode functions
  */
 
-bool
-nfs3svc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_readdirargs *args = rqstp->rq_argp;
-
-	if (!svcxdr_decode_nfs_fh3(xdr, &args->fh))
-		return false;
-	if (xdr_stream_decode_u64(xdr, &args->cookie) < 0)
-		return false;
-	args->verf = xdr_inline_decode(xdr, NFS3_COOKIEVERFSIZE);
-	if (!args->verf)
-		return false;
-	if (xdr_stream_decode_u32(xdr, &args->count) < 0)
-		return false;
-
-	return true;
-}
-
 bool
 nfs3svc_decode_readdirplusargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 {
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index c434c93462a8..c67933ad3cd6 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -120,7 +120,6 @@ struct nfsd3_getaclres {
 	struct kstat		stat;
 };
 
-bool nfs3svc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_decode_readdirplusargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 bool nfs3svc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-- 
2.55.0


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

* [PATCH v1 28/33] NFSD: Use xdrgen XDR functions for NFSv3 READDIRPLUS arguments
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (26 preceding siblings ...)
  2026-09-23 13:28 ` [PATCH v1 27/33] NFSD: Use xdrgen XDR functions for NFSv3 READDIR arguments Chuck Lever
@ 2026-09-23 13:28 ` Chuck Lever
  2026-09-23 13:28 ` [PATCH v1 29/33] NFSD: Refactor NFSv3 directory cookie encoding Chuck Lever
                   ` (4 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:28 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

Replace the NFSPROC3_READDIRPLUS entry in the nfsd_procedures3 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_READDIRPLUS3args. A wrapper structure bridges the
generated xdrgen READDIRPLUS3args type and the legacy svc_fh
representation the NFSD VFS layer still uses.

The pc_argzero field is now set to zero for the NFSv3 READDIRPLUS
procedure. The xdrgen decoders are trusted to initialize all
arguments in the argp->xdrgen field, making the early defensive
memset unnecessary. The remaining argp fields are cleared as
needed.

nfs3svc_decode_readdirplusargs() no longer has any callers, so it
is removed.

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

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index f221bd11482e..e09c2a839a1c 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -225,6 +225,13 @@ struct READDIR3args_wrapper {
 
 static_assert(offsetof(struct READDIR3args_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) {
@@ -1555,27 +1562,32 @@ static __be32 nfsd3_proc_readdir(struct svc_rqst *rqstp)
 	return rpc_success;
 }
 
-/*
- * Read a portion of a directory, including file handles and attrs.
- * For now, we choose to ignore the dircount parameter.
+/**
+ * nfsd3_proc_readdirplus - READDIRPLUS: Extended read from directory
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ *   %rpc_success:		RPC executed successfully
+ *
+ * RPC synopsis:
+ *   READDIRPLUS3res NFSPROC3_READDIRPLUS(READDIRPLUS3args) = 17;
  */
-static __be32
-nfsd3_proc_readdirplus(struct svc_rqst *rqstp)
+static __be32 nfsd3_proc_readdirplus(struct svc_rqst *rqstp)
 {
-	struct nfsd3_readdirargs *argp = rqstp->rq_argp;
+	struct READDIRPLUS3args_wrapper *argp = rqstp->rq_argp;
 	struct nfsd3_readdirres  *resp = rqstp->rq_resp;
-	loff_t	offset;
+	loff_t offset = argp->xdrgen.cookie;
+	struct svc_fh *fhp = &argp->fh;
 
-	trace_nfsd_vfs_readdir(rqstp, &argp->fh, argp->count, argp->cookie);
+	nfsd3_fh3_to_svc_fh(fhp, &argp->xdrgen.dir);
+	trace_nfsd_vfs_readdir(rqstp, fhp, argp->xdrgen.maxcount,
+			       argp->xdrgen.cookie);
 
-	nfsd3_init_dirlist_pages(rqstp, resp, argp->count);
-
-	fh_copy(&resp->fh, &argp->fh);
+	nfsd3_init_dirlist_pages(rqstp, resp, argp->xdrgen.maxcount);
+	fh_copy(&resp->fh, fhp);
 	resp->common.err = nfs_ok;
 	resp->cookie_offset = 0;
 	resp->rqstp = rqstp;
-	offset = argp->cookie;
-
 	resp->status = fh_verify(rqstp, &resp->fh, S_IFDIR, NFSD_MAY_NOP);
 	if (resp->status != nfs_ok)
 		goto out;
@@ -1587,7 +1599,7 @@ nfsd3_proc_readdirplus(struct svc_rqst *rqstp)
 
 	resp->status = nfsd_readdir(rqstp, &resp->fh, &offset,
 				    &resp->common, nfs3svc_encode_entryplus3);
-	memcpy(resp->verf, argp->verf, 8);
+	memcpy(resp->verf, argp->xdrgen.cookieverf, NFS3_COOKIEVERFSIZE);
 	nfs3svc_encode_cookie3(resp, offset);
 
 	/* Recycle only pages that were part of the reply */
@@ -1828,8 +1840,6 @@ static __be32 nfsd3_proc_commit(struct svc_rqst *rqstp)
  * NFSv3 Server procedures.
  * Only the results of non-idempotent operations are cached.
  */
-#define nfsd3_readdirplusargs		nfsd3_readdirargs
-
 static const struct svc_procedure nfsd_procedures3[22] = {
 	[NFSPROC3_NULL] = {
 		.pc_func	= nfsd3_proc_null,
@@ -2022,15 +2032,15 @@ static const struct svc_procedure nfsd_procedures3[22] = {
 		.pc_name	= "READDIR",
 	},
 	[NFSPROC3_READDIRPLUS] = {
-		.pc_func = nfsd3_proc_readdirplus,
-		.pc_decode = nfs3svc_decode_readdirplusargs,
-		.pc_encode = nfs3svc_encode_readdirres,
-		.pc_release = nfs3svc_release_fhandle,
-		.pc_argsize = sizeof(struct nfsd3_readdirplusargs),
-		.pc_argzero = sizeof(struct nfsd3_readdirplusargs),
-		.pc_ressize = sizeof(struct nfsd3_readdirres),
-		.pc_cachetype = RC_NOCACHE,
-		.pc_name = "READDIRPLUS",
+		.pc_func	= nfsd3_proc_readdirplus,
+		.pc_decode	= nfs_svc_decode_READDIRPLUS3args,
+		.pc_encode	= nfs3svc_encode_readdirres,
+		.pc_release	= nfs3svc_release_fhandle,
+		.pc_argsize	= sizeof(struct READDIRPLUS3args_wrapper),
+		.pc_argzero	= 0,
+		.pc_ressize	= sizeof(struct nfsd3_readdirres),
+		.pc_cachetype	= RC_NOCACHE,
+		.pc_name	= "READDIRPLUS",
 	},
 	[NFSPROC3_FSSTAT] = {
 		.pc_func	= nfsd3_proc_fsstat,
@@ -2110,7 +2120,7 @@ union nfsd3_xdrstore {
 	struct LINK3args_wrapper	linkargs;
 	struct LINK3res			linkres;
 	struct READDIR3args_wrapper	readdirargs;
-	struct nfsd3_readdirargs	readdirplusargs;
+	struct READDIRPLUS3args_wrapper	readdirplusargs;
 	struct READLINK3res		readlinkres;
 	struct READ3res			readres;
 	struct nfsd3_writeres		writeres;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index 0e83a2245c45..b1a930b857d7 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -224,32 +224,6 @@ svcxdr_encode_post_op_attr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
 	return xdr_stream_encode_item_absent(xdr) > 0;
 }
 
-/*
- * XDR decode functions
- */
-
-bool
-nfs3svc_decode_readdirplusargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
-	struct nfsd3_readdirargs *args = rqstp->rq_argp;
-	u32 dircount;
-
-	if (!svcxdr_decode_nfs_fh3(xdr, &args->fh))
-		return false;
-	if (xdr_stream_decode_u64(xdr, &args->cookie) < 0)
-		return false;
-	args->verf = xdr_inline_decode(xdr, NFS3_COOKIEVERFSIZE);
-	if (!args->verf)
-		return false;
-	/* dircount is ignored */
-	if (xdr_stream_decode_u32(xdr, &dircount) < 0)
-		return false;
-	if (xdr_stream_decode_u32(xdr, &args->count) < 0)
-		return false;
-
-	return true;
-}
-
 /*
  * XDR encode functions
  */
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index c67933ad3cd6..eb4a4e9d5073 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -31,13 +31,6 @@ struct nfsd3_createargs {
 	__be32 *		verf;
 };
 
-struct nfsd3_readdirargs {
-	struct svc_fh		fh;
-	__u64			cookie;
-	__u32			count;
-	__be32 *		verf;
-};
-
 struct nfsd3_getaclargs {
 	struct svc_fh		fh;
 	__u32			mask;
@@ -120,8 +113,6 @@ struct nfsd3_getaclres {
 	struct kstat		stat;
 };
 
-bool nfs3svc_decode_readdirplusargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-
 bool nfs3svc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 bool nfs3svc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
-- 
2.55.0


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

* [PATCH v1 29/33] NFSD: Refactor NFSv3 directory cookie encoding
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (27 preceding siblings ...)
  2026-09-23 13:28 ` [PATCH v1 28/33] NFSD: Use xdrgen XDR functions for NFSv3 READDIRPLUS arguments Chuck Lever
@ 2026-09-23 13:28 ` Chuck Lever
  2026-09-23 13:28 ` [PATCH v1 30/33] NFSD: Refactor NFSv3 directory entry encoding Chuck Lever
                   ` (3 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:28 UTC (permalink / raw)
  To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs

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

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

With a "struct READDIR3args" and a "struct READDIRPLUS3args," the
cookie3 encoder will need generic parameter types in order to be
callable from both.

Note that NFSD can't use the xdrgen-generated cookie3 encoder. This
is because directory cookies need to point to the previous directory
entry. Space for each cookie in the send buffer is reserved when
each directory entry is encoded; when the /next/ entry is encoded,
the encoder writes its offset into that reserved space.

As a clean-up, remove "resp->cookie_offset = 0;" :

- In nfs3svc_encode_entry3() and nfs3svc_encode_entryplus3(),
  immediately after calling nfs3svc_encode_cookie3(), the code calls
  svcxdr_encode_entry3_common() which sets a new resp->cookie_offset
  value. The cookie_offset value was immediately overwritten.
- In nfsd3_proc_readdir() and nfsd3_proc_readdirplus(),
  nfs3svc_encode_cookie3() is called as the last cookie encoding,
  and no further use of cookie_offset follows.
- The out_toosmall error paths in entry encoders continue to set
  resp->cookie_offset = 0

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 fs/nfsd/nfs3proc.c |  4 ++--
 fs/nfsd/nfs3xdr.c  | 22 +++++++++++-----------
 fs/nfsd/xdr3.h     |  3 ++-
 3 files changed, 15 insertions(+), 14 deletions(-)

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index e09c2a839a1c..0316bb60b50b 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -1553,7 +1553,7 @@ static __be32 nfsd3_proc_readdir(struct svc_rqst *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, offset);
+	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;
@@ -1600,7 +1600,7 @@ static __be32 nfsd3_proc_readdirplus(struct svc_rqst *rqstp)
 	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, offset);
+	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;
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index b1a930b857d7..279b19d5e3ac 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -300,22 +300,22 @@ compose_entry_fh(struct nfsd3_readdirres *cd, struct svc_fh *fhp,
 }
 
 /**
- * nfs3svc_encode_cookie3 - Encode a directory offset cookie
- * @resp: readdir result context
- * @offset: offset cookie to encode
+ * nfs3svc_encode_cookie3 - Encode a directory cookie
+ * @xdr: stream into which to encode the cookie
+ * @pos: byte position in the stream
+ * @cookie: cookie to be encoded
  *
  * The buffer space for the offset cookie has already been reserved
  * by svcxdr_encode_entry3_common().
  */
-void nfs3svc_encode_cookie3(struct nfsd3_readdirres *resp, u64 offset)
+void nfs3svc_encode_cookie3(struct xdr_stream *xdr, unsigned int pos,
+			    u64 cookie)
 {
-	__be64 cookie = cpu_to_be64(offset);
+	__be64 wire_cookie = cpu_to_be64(cookie);
 
-	if (!resp->cookie_offset)
+	if (!pos)
 		return;
-	write_bytes_to_xdr_buf(&resp->dirlist, resp->cookie_offset, &cookie,
-			       sizeof(cookie));
-	resp->cookie_offset = 0;
+	write_bytes_to_xdr_buf(xdr->buf, pos, &wire_cookie, XDR_UNIT * 2);
 }
 
 static bool
@@ -369,7 +369,7 @@ int nfs3svc_encode_entry3(void *data, const char *name, int namlen,
 	unsigned int starting_length = resp->dirlist.len;
 
 	/* The offset cookie for the previous entry */
-	nfs3svc_encode_cookie3(resp, offset);
+	nfs3svc_encode_cookie3(&resp->xdr, resp->cookie_offset, offset);
 
 	if (!svcxdr_encode_entry3_common(resp, name, namlen, offset, ino))
 		goto out_toosmall;
@@ -444,7 +444,7 @@ int nfs3svc_encode_entryplus3(void *data, const char *name, int namlen,
 	unsigned int starting_length = resp->dirlist.len;
 
 	/* The offset cookie for the previous entry */
-	nfs3svc_encode_cookie3(resp, offset);
+	nfs3svc_encode_cookie3(&resp->xdr, resp->cookie_offset, offset);
 
 	if (!svcxdr_encode_entry3_common(resp, name, namlen, offset, ino))
 		goto out_toosmall;
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index eb4a4e9d5073..ad3cfd629494 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -118,7 +118,8 @@ bool nfs3svc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
 
 void nfs3svc_release_fhandle(struct svc_rqst *);
 
-void nfs3svc_encode_cookie3(struct nfsd3_readdirres *resp, u64 offset);
+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,
-- 
2.55.0


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

* [PATCH v1 30/33] NFSD: Refactor NFSv3 directory entry encoding
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (28 preceding siblings ...)
  2026-09-23 13:28 ` [PATCH v1 29/33] NFSD: Refactor NFSv3 directory cookie encoding Chuck Lever
@ 2026-09-23 13:28 ` Chuck Lever
  2026-09-23 13:28 ` [PATCH v1 31/33] NFSD: Split struct nfsd3_readdirres Chuck Lever
                   ` (2 subsequent siblings)
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:28 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] 34+ messages in thread

* [PATCH v1 31/33] NFSD: Split struct nfsd3_readdirres
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (29 preceding siblings ...)
  2026-09-23 13:28 ` [PATCH v1 30/33] NFSD: Refactor NFSv3 directory entry encoding Chuck Lever
@ 2026-09-23 13:28 ` Chuck Lever
  2026-09-23 13:28 ` [PATCH v1 32/33] NFSD: Use xdrgen XDR functions for NFSv3 READDIR results Chuck Lever
  2026-09-23 13:28 ` [PATCH v1 33/33] NFSD: Use xdrgen XDR functions for NFSv3 READDIRPLUS results Chuck Lever
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:28 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 0316bb60b50b..797c91fb7ad2 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -1242,6 +1242,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
@@ -1575,7 +1596,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;
 
@@ -1583,7 +1604,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;
@@ -2034,11 +2055,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",
 	},
@@ -2125,6 +2146,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 ad3cfd629494..a9e3ac100251 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -79,6 +79,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;
@@ -115,6 +131,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] 34+ messages in thread

* [PATCH v1 32/33] NFSD: Use xdrgen XDR functions for NFSv3 READDIR results
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (30 preceding siblings ...)
  2026-09-23 13:28 ` [PATCH v1 31/33] NFSD: Split struct nfsd3_readdirres Chuck Lever
@ 2026-09-23 13:28 ` Chuck Lever
  2026-09-23 13:28 ` [PATCH v1 33/33] NFSD: Use xdrgen XDR functions for NFSv3 READDIRPLUS results Chuck Lever
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:28 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. The decode
hooks are stubs -- a server never decodes a READDIR result -- present
only to satisfy the generated decoder's linkage.

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 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.

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                  | 246 ++++++++++++++++++++---------
 fs/nfsd/nfs3xdr_gen.c              |  56 ++++++-
 fs/nfsd/nfs3xdr_gen.h              |  13 +-
 fs/nfsd/xdr3.h                     |  39 +++--
 include/linux/sunrpc/xdrgen/nfs3.h |   2 +-
 7 files changed, 289 insertions(+), 145 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 797c91fb7ad2..bcf7107ec280 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;
@@ -1525,30 +1518,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
  *
@@ -1558,28 +1537,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;
 }
 
@@ -2044,10 +2026,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..7831d15e4451 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,36 +231,164 @@ 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;
+}
+
+/*
+ * A server never decodes a READDIR result; these satisfy the linkage
+ * of the generated (unused) decoder.
+ */
+bool nfs3_dirlist3_decode_begin(struct xdrgen_aggregate_cursor *c)
+{
+	return false;
+}
+
+bool nfs3_dirlist3_decode(struct xdrgen_aggregate_cursor *c,
+			  const struct entry3 *in)
+{
+	return false;
+}
+
+bool nfs3_dirlist3_decode_end(struct xdrgen_aggregate_cursor *c, bool ok)
+{
+	return false;
+}
+
 bool
 nfs3svc_encode_readdirplusres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
 {
@@ -335,7 +466,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 +478,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 +576,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..5739a6e004ab 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,8 +1258,31 @@ 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;
+	{
+		struct xdrgen_aggregate_cursor cursor = {
+			.xdr = xdr,
+			.member_id = NFS3_DIRLIST3_ENTRIES,
+			.ctx = xdr->xdrgen_ctx,
+		};
+		bool ok = true;
+
+		if (!nfs3_dirlist3_decode_begin(&cursor))
+			return false;
+		for (cursor.index = 0; cursor.index < cursor.count; cursor.index++) {
+			struct entry3 element = {};
+
+			if (!xdrgen_decode_entry3(xdr, &element)) {
+				ok = false;
+				break;
+			}
+			if (!nfs3_dirlist3_decode(&cursor, &element)) {
+				ok = false;
+				break;
+			}
+		}
+		if (!nfs3_dirlist3_decode_end(&cursor, ok) || !ok)
+			return false;
+	}
 	if (!xdrgen_decode_bool(xdr, &ptr->eof))
 		return false;
 	return true;
@@ -3089,8 +3112,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 a9e3ac100251..d0047ecf5723 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -63,22 +63,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;
@@ -130,15 +139,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] 34+ messages in thread

* [PATCH v1 33/33] NFSD: Use xdrgen XDR functions for NFSv3 READDIRPLUS results
  2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
                   ` (31 preceding siblings ...)
  2026-09-23 13:28 ` [PATCH v1 32/33] NFSD: Use xdrgen XDR functions for NFSv3 READDIR results Chuck Lever
@ 2026-09-23 13:28 ` Chuck Lever
  32 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-23 13:28 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. The
decode hooks are stubs -- a server never decodes a READDIRPLUS
result -- present only to satisfy the generated decoder's linkage.

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 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.

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                  | 328 +++++++++++++++--------------
 fs/nfsd/nfs3xdr_gen.c              |  56 ++++-
 fs/nfsd/nfs3xdr_gen.h              |  13 +-
 fs/nfsd/xdr3.h                     |  48 +++--
 include/linux/sunrpc/xdrgen/nfs3.h |   2 +-
 7 files changed, 307 insertions(+), 238 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 bcf7107ec280..68b10b275f48 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) {
@@ -437,10 +430,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)
@@ -527,7 +516,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)
 {
@@ -552,7 +541,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)
 {
@@ -1235,27 +1224,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
@@ -1569,6 +1537,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
  *
@@ -1581,35 +1555,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;
 }
 
@@ -2037,10 +2009,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 7831d15e4451..531595d1f163 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)
@@ -389,36 +348,6 @@ bool nfs3_dirlist3_decode_end(struct xdrgen_aggregate_cursor *c, bool ok)
 	return false;
 }
 
-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)
@@ -427,8 +356,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) {
@@ -453,7 +382,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;
@@ -466,7 +395,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)
@@ -478,105 +407,186 @@ 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 */
+
+	/* 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);
+
+	/* value-follows + fileid + name (length + data) + cookie */
+	need = XDR_UNIT * (6 + XDR_QUADLEN(namlen));
+	/* name_attributes: attributes_follow + optional fattr3 */
+	need += XDR_UNIT;
+	if (out->name_attributes.attributes_follow)
+		need += XDR_UNIT * 21;
+	/* name_handle: handle_follows + optional nfs_fh3 */
+	need += XDR_UNIT;
+	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;
+}
+
+/*
+ * A server never decodes a READDIRPLUS result; these satisfy the
+ * linkage of the generated (unused) decoder.
+ */
+bool nfs3_dirlistplus3_decode_begin(struct xdrgen_aggregate_cursor *c)
+{
+	return false;
+}
+
+bool nfs3_dirlistplus3_decode(struct xdrgen_aggregate_cursor *c,
+			      const struct entryplus3 *in)
+{
+	return false;
+}
+
+bool nfs3_dirlistplus3_decode_end(struct xdrgen_aggregate_cursor *c, bool ok)
+{
+	return false;
 }
 
 /*
  * 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)
 {
@@ -587,3 +597,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 5739a6e004ab..fbafcdd80cea 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>
 
@@ -1367,8 +1367,31 @@ 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;
+	{
+		struct xdrgen_aggregate_cursor cursor = {
+			.xdr = xdr,
+			.member_id = NFS3_DIRLISTPLUS3_ENTRIES,
+			.ctx = xdr->xdrgen_ctx,
+		};
+		bool ok = true;
+
+		if (!nfs3_dirlistplus3_decode_begin(&cursor))
+			return false;
+		for (cursor.index = 0; cursor.index < cursor.count; cursor.index++) {
+			struct entryplus3 element = {};
+
+			if (!xdrgen_decode_entryplus3(xdr, &element)) {
+				ok = false;
+				break;
+			}
+			if (!nfs3_dirlistplus3_decode(&cursor, &element)) {
+				ok = false;
+				break;
+			}
+		}
+		if (!nfs3_dirlistplus3_decode_end(&cursor, ok) || !ok)
+			return false;
+	}
 	if (!xdrgen_decode_bool(xdr, &ptr->eof))
 		return false;
 	return true;
@@ -3219,8 +3242,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 d0047ecf5723..2dc542250522 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -88,22 +88,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;
@@ -138,16 +149,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] 34+ messages in thread

end of thread, other threads:[~2026-09-23 13:28 UTC | newest]

Thread overview: 34+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-23 13:27 [PATCH v1 00/33] Convert server-side NFSv3 XDR to use xdrgen Chuck Lever
2026-09-23 13:27 ` [PATCH v1 01/33] NFSD: Report a first-batch readdir error in the reply status Chuck Lever
2026-09-23 13:27 ` [PATCH v1 02/33] Documentation: Add the RPC language description of NFSv3 Chuck Lever
2026-09-23 13:27 ` [PATCH v1 03/33] NFSD: Add infrastructure for generating NFSv3 XDR encoders and decoders Chuck Lever
2026-09-23 13:27 ` [PATCH v1 04/33] NFSD: Replace nfs3.h with nfs3xdr_gen.h Chuck Lever
2026-09-23 13:27 ` [PATCH v1 05/33] NFSD: Replace the nfsd3_createres macro Chuck Lever
2026-09-23 13:27 ` [PATCH v1 06/33] NFSD: Relocate the NFSv3 XDR storage union into nfs3proc.c Chuck Lever
2026-09-23 13:27 ` [PATCH v1 07/33] NFSD: Use xdrgen XDR functions for the NFSv3 NULL procedure Chuck Lever
2026-09-23 13:27 ` [PATCH v1 08/33] NFSD: Use xdrgen XDR functions for NFSv3 GETATTR procedure Chuck Lever
2026-09-23 13:27 ` [PATCH v1 09/33] NFSD: Use xdrgen XDR functions for NFSv3 SETATTR procedure Chuck Lever
2026-09-23 13:27 ` [PATCH v1 10/33] NFSD: Use xdrgen XDR functions for the NFSv3 LOOKUP procedure Chuck Lever
2026-09-23 13:27 ` [PATCH v1 11/33] NFSD: Use xdrgen XDR functions for NFSv3 ACCESS procedure Chuck Lever
2026-09-23 13:27 ` [PATCH v1 12/33] NFSD: Use xdrgen XDR functions for NFSv3 READLINK procedure Chuck Lever
2026-09-23 13:27 ` [PATCH v1 13/33] NFSD: Use xdrgen XDR functions for NFSv3 READ procedure Chuck Lever
2026-09-23 13:27 ` [PATCH v1 14/33] NFSD: Use xdrgen XDR functions for NFSv3 WRITE procedure Chuck Lever
2026-09-23 13:27 ` [PATCH v1 15/33] NFSD: Use xdrgen XDR functions for NFSv3 CREATE procedure Chuck Lever
2026-09-23 13:27 ` [PATCH v1 16/33] NFSD: Use xdrgen XDR functions for NFSv3 MKDIR procedure Chuck Lever
2026-09-23 13:27 ` [PATCH v1 17/33] NFSD: Use xdrgen XDR functions for NFSv3 SYMLINK procedure Chuck Lever
2026-09-23 13:27 ` [PATCH v1 18/33] NFSD: Use xdrgen XDR functions for NFSv3 MKNOD procedure Chuck Lever
2026-09-23 13:27 ` [PATCH v1 19/33] NFSD: Use xdrgen XDR functions for the NFSv3 REMOVE procedure Chuck Lever
2026-09-23 13:27 ` [PATCH v1 20/33] NFSD: Use xdrgen XDR functions for the NFSv3 RMDIR procedure Chuck Lever
2026-09-23 13:27 ` [PATCH v1 21/33] NFSD: Use xdrgen XDR functions for the NFSv3 RENAME procedure Chuck Lever
2026-09-23 13:28 ` [PATCH v1 22/33] NFSD: Use xdrgen XDR functions for the NFSv3 LINK procedure Chuck Lever
2026-09-23 13:28 ` [PATCH v1 23/33] NFSD: Use xdrgen XDR functions for the NFSv3 FSSTAT procedure Chuck Lever
2026-09-23 13:28 ` [PATCH v1 24/33] NFSD: Use xdrgen XDR functions for the NFSv3 FSINFO procedure Chuck Lever
2026-09-23 13:28 ` [PATCH v1 25/33] NFSD: Use xdrgen XDR functions for the NFSv3 PATHCONF procedure Chuck Lever
2026-09-23 13:28 ` [PATCH v1 26/33] NFSD: Use xdrgen XDR functions for the NFSv3 COMMIT procedure Chuck Lever
2026-09-23 13:28 ` [PATCH v1 27/33] NFSD: Use xdrgen XDR functions for NFSv3 READDIR arguments Chuck Lever
2026-09-23 13:28 ` [PATCH v1 28/33] NFSD: Use xdrgen XDR functions for NFSv3 READDIRPLUS arguments Chuck Lever
2026-09-23 13:28 ` [PATCH v1 29/33] NFSD: Refactor NFSv3 directory cookie encoding Chuck Lever
2026-09-23 13:28 ` [PATCH v1 30/33] NFSD: Refactor NFSv3 directory entry encoding Chuck Lever
2026-09-23 13:28 ` [PATCH v1 31/33] NFSD: Split struct nfsd3_readdirres Chuck Lever
2026-09-23 13:28 ` [PATCH v1 32/33] NFSD: Use xdrgen XDR functions for NFSv3 READDIR results Chuck Lever
2026-09-23 13:28 ` [PATCH v1 33/33] NFSD: Use xdrgen XDR functions for NFSv3 READDIRPLUS results Chuck Lever

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).