* [PATCH v1 01/27] Documentation: Add the RPC language description of NFSv2
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 1/2] NFSD: map fh_verify() status codes for NFS_ACLv2 replies Chuck Lever
` (28 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
In order to generate source code to encode and decode NFSv2 protocol
elements, include a copy of the RPC language description of NFSv2
for xdrgen to process. The language description is an amalgam of
RFC 1094 and the Open Group's XNFS specification:
https://pubs.opengroup.org/onlinepubs/9629799/chap7.htm
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/nfs2.x | 237 ++++++++++++++++++++++++++++++++
1 file changed, 237 insertions(+)
create mode 100644 Documentation/sunrpc/xdr/nfs2.x
diff --git a/Documentation/sunrpc/xdr/nfs2.x b/Documentation/sunrpc/xdr/nfs2.x
new file mode 100644
index 000000000000..8a05975ad65a
--- /dev/null
+++ b/Documentation/sunrpc/xdr/nfs2.x
@@ -0,0 +1,237 @@
+/*
+ * This document was extracted by hand from RFC 1094. Several errors
+ * and omissions were corrected. Additional sources for this protocol
+ * specification include:
+ *
+ * https://pubs.opengroup.org/onlinepubs/9629799/chap7.htm
+ *
+ * Note that RFC 1094's official date of publication (March 1989) is
+ * before the IETF required its RFCs to carry an explicit copyright
+ * or other IP ownership notice.
+ */
+
+const NFS_MAXDATA = 8192;
+const NFS_MAXPATHLEN = 1024;
+const NFS_MAXNAMLEN = 255;
+const NFS_COOKIESIZE = 4;
+const NFS_FHSIZE = 32;
+
+/*
+ * RFC 1094 calls the following enum "stat". That name conflicts with
+ * the Linux kernel's "struct stat" because in C, struct, union, and
+ * enum tags all share a single tag namespace. Details are specified
+ * in the C11 standard, Section 6.2.3.
+ */
+enum nfsstat {
+ NFS_OK = 0,
+ NFSERR_PERM = 1,
+ NFSERR_NOENT = 2,
+ NFSERR_IO = 5,
+ NFSERR_NXIO = 6,
+ NFSERR_ACCES = 13,
+ NFSERR_EXIST = 17,
+ NFSERR_NODEV = 19,
+ NFSERR_NOTDIR = 20,
+ NFSERR_ISDIR = 21,
+ NFSERR_FBIG = 27,
+ NFSERR_NOSPC = 28,
+ NFSERR_ROFS = 30,
+ NFSERR_NAMETOOLONG = 63,
+ NFSERR_NOTEMPTY = 66,
+ NFSERR_DQUOT = 69,
+ NFSERR_STALE = 70,
+ NFSERR_WFLUSH = 99
+};
+
+enum ftype {
+ NFNON = 0,
+ NFREG = 1,
+ NFDIR = 2,
+ NFBLK = 3,
+ NFCHR = 4,
+ NFLNK = 5
+};
+
+typedef opaque fhandle[NFS_FHSIZE];
+typedef opaque nfscookie[NFS_COOKIESIZE];
+
+struct timeval {
+ unsigned int seconds;
+ unsigned int useconds;
+};
+
+struct fattr {
+ ftype type;
+ unsigned int mode;
+ unsigned int nlink;
+ unsigned int uid;
+ unsigned int gid;
+ unsigned int size;
+ unsigned int blocksize;
+ unsigned int rdev;
+ unsigned int blocks;
+ unsigned int fsid;
+ unsigned int fileid;
+ timeval atime;
+ timeval mtime;
+ timeval ctime;
+};
+
+struct sattr {
+ unsigned int mode;
+ unsigned int uid;
+ unsigned int gid;
+ unsigned int size;
+ timeval atime;
+ timeval mtime;
+};
+
+typedef string filename<NFS_MAXNAMLEN>;
+typedef string path<NFS_MAXPATHLEN>;
+
+union attrstat switch (nfsstat status) {
+ case NFS_OK:
+ fattr attributes;
+ default:
+ void;
+};
+
+struct diropargs {
+ fhandle dir;
+ filename name;
+};
+
+struct diropok {
+ fhandle file;
+ fattr attributes;
+};
+
+union diropres switch (nfsstat status) {
+ case NFS_OK:
+ diropok diropok;
+ default:
+ void;
+};
+
+struct sattrargs {
+ fhandle file;
+ sattr attributes;
+};
+
+union readlinkres switch (nfsstat status) {
+ case NFS_OK:
+ path data;
+ default:
+ void;
+};
+
+struct readargs {
+ fhandle file;
+ unsigned int offset;
+ unsigned int count;
+ unsigned int totalcount;
+};
+
+struct readresok {
+ fattr attributes;
+ opaque data<NFS_MAXDATA>;
+};
+
+union readres switch (nfsstat status) {
+ case NFS_OK:
+ readresok readresok;
+ default:
+ void;
+};
+
+struct writeargs {
+ fhandle file;
+ unsigned int beginoffset;
+ unsigned int offset;
+ unsigned int totalcount;
+ opaque data<NFS_MAXDATA>;
+};
+
+struct createargs {
+ diropargs where;
+ sattr attributes;
+};
+
+struct renameargs {
+ diropargs from;
+ diropargs to;
+};
+
+struct linkargs {
+ fhandle from;
+ diropargs to;
+};
+
+struct symlinkargs {
+ diropargs from;
+ path to;
+ sattr attributes;
+};
+
+struct readdirargs {
+ fhandle dir;
+ nfscookie cookie;
+ unsigned int count;
+};
+
+struct entry {
+ unsigned int fileid;
+ filename name;
+ nfscookie cookie;
+ entry *nextentry;
+};
+
+struct readdirok {
+ entry *entries;
+ bool eof;
+};
+
+union readdirres switch (nfsstat status) {
+ case NFS_OK:
+ readdirok readdirok;
+ default:
+ void;
+};
+
+struct info {
+ unsigned int tsize;
+ unsigned int bsize;
+ unsigned int blocks;
+ unsigned int bfree;
+ unsigned int bavail;
+};
+
+union statfsres switch (nfsstat status) {
+ case NFS_OK:
+ info info;
+ default:
+ void;
+};
+
+program NFS_PROGRAM {
+ version NFS_VERSION {
+ void NFSPROC_NULL(void) = 0;
+ attrstat NFSPROC_GETATTR(fhandle) = 1;
+ attrstat NFSPROC_SETATTR(sattrargs) = 2;
+ void NFSPROC_ROOT(void) = 3;
+ diropres NFSPROC_LOOKUP(diropargs) = 4;
+ readlinkres NFSPROC_READLINK(fhandle) = 5;
+ readres NFSPROC_READ(readargs) = 6;
+ void NFSPROC_WRITECACHE(void) = 7;
+ attrstat NFSPROC_WRITE(writeargs) = 8;
+ diropres NFSPROC_CREATE(createargs) = 9;
+ nfsstat NFSPROC_REMOVE(diropargs) = 10;
+ nfsstat NFSPROC_RENAME(renameargs) = 11;
+ nfsstat NFSPROC_LINK(linkargs) = 12;
+ nfsstat NFSPROC_SYMLINK(symlinkargs) = 13;
+ diropres NFSPROC_MKDIR(createargs) = 14;
+ nfsstat NFSPROC_RMDIR(diropargs) = 15;
+ readdirres NFSPROC_READDIR(readdirargs) = 16;
+ statfsres NFSPROC_STATFS(fhandle) = 17;
+ } = 2;
+} = 100003;
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 1/2] NFSD: map fh_verify() status codes for NFS_ACLv2 replies
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
2026-09-16 16:28 ` [PATCH v1 01/27] Documentation: Add the RPC language description of NFSv2 Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-17 11:57 ` Jeff Layton
2026-09-16 16:28 ` [PATCH v1 02/27] NFSD: Add infrastructure for generating NFSv2 XDR encoders and decoders Chuck Lever
` (27 subsequent siblings)
29 siblings, 1 reply; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
The NFS_ACL v2 protocol shares its status code space with NFSv2,
which has no NFSERR_BADHANDLE and no NFSERR_NOFILEHANDLE. An
NFS_ACLv2 GETACL, SETACL, GETATTR, or ACCESS request that carries a
malformed or empty file handle gets a reply with status 10001 or
10020, values the client cannot interpret.
Commit 1459ad57673b ("nfsd: Move error code mapping to per-version
proc code.") removed the version check from fh_verify() that turned
those codes into NFSERR_STALE for any version 2 program. The NFSv2
procedures gained nfsd_map_status() in exchange, but the NFS_ACL v2
procedures did not, so the unmapped status reaches the encoder.
Add the same mapping to the NFS_ACL v2 procedures.
Fixes: 1459ad57673b ("nfsd: Move error code mapping to per-version proc code.")
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfs2acl.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
index 0a5c444fef99..0673e83b6666 100644
--- a/fs/nfsd/nfs2acl.c
+++ b/fs/nfsd/nfs2acl.c
@@ -59,6 +59,20 @@ static const struct nfsd_access_maps nfsd2_access_maps = {
.other = nfsd2_otheraccess,
};
+static __be32 nfsacld_map_status(__be32 status)
+{
+ switch (status) {
+ case nfserr_nofilehandle:
+ case nfserr_badhandle:
+ status = nfserr_stale;
+ break;
+ case nfserr_wrongsec:
+ status = nfserr_acces;
+ break;
+ }
+ return status;
+}
+
/*
* NULL call.
*/
@@ -123,6 +137,7 @@ static __be32 nfsacld_proc_getacl(struct svc_rqst *rqstp)
/* resp->acl_{access,default} are released in nfssvc_release_getacl. */
out:
+ resp->status = nfsacld_map_status(resp->status);
return rpc_success;
fail:
@@ -181,6 +196,7 @@ static __be32 nfsacld_proc_setacl(struct svc_rqst *rqstp)
out:
/* argp->acl_{access,default} are released in nfsaclsvc_release_setacl. */
+ resp->status = nfsacld_map_status(resp->status);
return rpc_success;
out_drop_lock:
@@ -207,6 +223,7 @@ static __be32 nfsacld_proc_getattr(struct svc_rqst *rqstp)
goto out;
resp->status = fh_getattr(&resp->fh, &resp->stat);
out:
+ resp->status = nfsacld_map_status(resp->status);
return rpc_success;
}
@@ -231,6 +248,7 @@ static __be32 nfsacld_proc_access(struct svc_rqst *rqstp)
goto out;
resp->status = fh_getattr(&resp->fh, &resp->stat);
out:
+ resp->status = nfsacld_map_status(resp->status);
return rpc_success;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [PATCH v1 1/2] NFSD: map fh_verify() status codes for NFS_ACLv2 replies
2026-09-16 16:28 ` [PATCH v1 1/2] NFSD: map fh_verify() status codes for NFS_ACLv2 replies Chuck Lever
@ 2026-09-17 11:57 ` Jeff Layton
0 siblings, 0 replies; 34+ messages in thread
From: Jeff Layton @ 2026-09-17 11:57 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
On Wed, 2026-09-16 at 12:28 -0400, Chuck Lever wrote:
> The NFS_ACL v2 protocol shares its status code space with NFSv2,
> which has no NFSERR_BADHANDLE and no NFSERR_NOFILEHANDLE. An
> NFS_ACLv2 GETACL, SETACL, GETATTR, or ACCESS request that carries a
> malformed or empty file handle gets a reply with status 10001 or
> 10020, values the client cannot interpret.
>
> Commit 1459ad57673b ("nfsd: Move error code mapping to per-version
> proc code.") removed the version check from fh_verify() that turned
> those codes into NFSERR_STALE for any version 2 program. The NFSv2
> procedures gained nfsd_map_status() in exchange, but the NFS_ACL v2
> procedures did not, so the unmapped status reaches the encoder.
>
> Add the same mapping to the NFS_ACL v2 procedures.
>
> Fixes: 1459ad57673b ("nfsd: Move error code mapping to per-version proc code.")
> Signed-off-by: Chuck Lever <cel@kernel.org>
> ---
> fs/nfsd/nfs2acl.c | 18 ++++++++++++++++++
> 1 file changed, 18 insertions(+)
>
> diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
> index 0a5c444fef99..0673e83b6666 100644
> --- a/fs/nfsd/nfs2acl.c
> +++ b/fs/nfsd/nfs2acl.c
> @@ -59,6 +59,20 @@ static const struct nfsd_access_maps nfsd2_access_maps = {
> .other = nfsd2_otheraccess,
> };
>
> +static __be32 nfsacld_map_status(__be32 status)
> +{
> + switch (status) {
> + case nfserr_nofilehandle:
> + case nfserr_badhandle:
> + status = nfserr_stale;
> + break;
> + case nfserr_wrongsec:
> + status = nfserr_acces;
> + break;
> + }
> + return status;
> +}
> +
> /*
> * NULL call.
> */
> @@ -123,6 +137,7 @@ static __be32 nfsacld_proc_getacl(struct svc_rqst *rqstp)
>
> /* resp->acl_{access,default} are released in nfssvc_release_getacl. */
> out:
> + resp->status = nfsacld_map_status(resp->status);
> return rpc_success;
>
> fail:
> @@ -181,6 +196,7 @@ static __be32 nfsacld_proc_setacl(struct svc_rqst *rqstp)
>
> out:
> /* argp->acl_{access,default} are released in nfsaclsvc_release_setacl. */
> + resp->status = nfsacld_map_status(resp->status);
> return rpc_success;
>
> out_drop_lock:
> @@ -207,6 +223,7 @@ static __be32 nfsacld_proc_getattr(struct svc_rqst *rqstp)
> goto out;
> resp->status = fh_getattr(&resp->fh, &resp->stat);
> out:
> + resp->status = nfsacld_map_status(resp->status);
> return rpc_success;
> }
>
> @@ -231,6 +248,7 @@ static __be32 nfsacld_proc_access(struct svc_rqst *rqstp)
> goto out;
> resp->status = fh_getattr(&resp->fh, &resp->stat);
> out:
> + resp->status = nfsacld_map_status(resp->status);
> return rpc_success;
> }
>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v1 02/27] NFSD: Add infrastructure for generating NFSv2 XDR encoders and decoders
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
2026-09-16 16:28 ` [PATCH v1 01/27] Documentation: Add the RPC language description of NFSv2 Chuck Lever
2026-09-16 16:28 ` [PATCH v1 1/2] NFSD: map fh_verify() status codes for NFS_ACLv2 replies Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 2/2] NFSD: map fh_verify() status codes for NFS_ACLv3 replies Chuck Lever
` (26 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
NFSD's NFSv2 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 nfs2.x. The
generated code can be refreshed any time nfs2.x is modified by
running "(cd fs/nfsd && make xdrgen)".
Signed-off-by: Chuck Lever <cel@kernel.org>
---
Documentation/sunrpc/xdr/nfs2.x | 2 +
fs/nfsd/Makefile | 15 +-
fs/nfsd/nfs2xdr_gen.c | 1054 ++++++++++++++++++++++++++++
fs/nfsd/nfs2xdr_gen.h | 35 +
include/linux/sunrpc/xdrgen/nfs2.h | 298 ++++++++
5 files changed, 1402 insertions(+), 2 deletions(-)
create mode 100644 fs/nfsd/nfs2xdr_gen.c
create mode 100644 fs/nfsd/nfs2xdr_gen.h
create mode 100644 include/linux/sunrpc/xdrgen/nfs2.h
diff --git a/Documentation/sunrpc/xdr/nfs2.x b/Documentation/sunrpc/xdr/nfs2.x
index 8a05975ad65a..5e6208d857f3 100644
--- a/Documentation/sunrpc/xdr/nfs2.x
+++ b/Documentation/sunrpc/xdr/nfs2.x
@@ -10,6 +10,8 @@
* or other IP ownership notice.
*/
+pragma header nfs2;
+
const NFS_MAXDATA = 8192;
const NFS_MAXPATHLEN = 1024;
const NFS_MAXNAMLEN = 255;
diff --git a/fs/nfsd/Makefile b/fs/nfsd/Makefile
index f0da4d69dc74..05fb814f760b 100644
--- a/fs/nfsd/Makefile
+++ b/fs/nfsd/Makefile
@@ -14,7 +14,7 @@ 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
-nfsd-$(CONFIG_NFSD_V2) += nfsproc.o nfsxdr.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
nfsd-$(CONFIG_NFSD_V4) += nfs4proc.o nfs4xdr.o nfs4state.o nfs4idmap.o \
@@ -37,13 +37,24 @@ nfsd-$(CONFIG_DEBUG_FS) += debugfs.o
#
.PHONY: xdrgen
-xdrgen: ../../include/linux/sunrpc/xdrgen/nfs4_1.h nfs4xdr_gen.h nfs4xdr_gen.c
+XDRGEN_DEFINITIONS = ../../include/linux/sunrpc/xdrgen/nfs2.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: $(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/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 $< > $@
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 $< > $@
nfs4xdr_gen.c: ../../Documentation/sunrpc/xdr/nfs4_1.x
../../tools/net/sunrpc/xdrgen/xdrgen source $< > $@
diff --git a/fs/nfsd/nfs2xdr_gen.c b/fs/nfsd/nfs2xdr_gen.c
new file mode 100644
index 000000000000..ae81d1731ac6
--- /dev/null
+++ b/fs/nfsd/nfs2xdr_gen.c
@@ -0,0 +1,1054 @@
+// SPDX-License-Identifier: GPL-2.0
+// Generated by xdrgen. Manual edits will be lost.
+// XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x
+// XDR specification modification time: Tue Sep 8 11:23:43 2026
+
+#include <linux/sunrpc/svc.h>
+
+#include "nfs2xdr_gen.h"
+
+static bool __maybe_unused
+xdrgen_decode_nfsstat(struct xdr_stream *xdr, nfsstat *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 NFS_OK:
+ case NFSERR_PERM:
+ case NFSERR_NOENT:
+ case NFSERR_IO:
+ case NFSERR_NXIO:
+ case NFSERR_ACCES:
+ case NFSERR_EXIST:
+ case NFSERR_NODEV:
+ case NFSERR_NOTDIR:
+ case NFSERR_ISDIR:
+ case NFSERR_FBIG:
+ case NFSERR_NOSPC:
+ case NFSERR_ROFS:
+ case NFSERR_NAMETOOLONG:
+ case NFSERR_NOTEMPTY:
+ case NFSERR_DQUOT:
+ case NFSERR_STALE:
+ case NFSERR_WFLUSH:
+ break;
+ default:
+ return false;
+ }
+ *ptr = val;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_ftype(struct xdr_stream *xdr, ftype *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 NFNON:
+ case NFREG:
+ case NFDIR:
+ case NFBLK:
+ case NFCHR:
+ case NFLNK:
+ break;
+ default:
+ return false;
+ }
+ *ptr = val;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_fhandle(struct xdr_stream *xdr, fhandle *ptr)
+{
+ return xdr_stream_decode_opaque_fixed(xdr, ptr, NFS_FHSIZE) == 0;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nfscookie(struct xdr_stream *xdr, nfscookie *ptr)
+{
+ return xdr_stream_decode_opaque_fixed(xdr, ptr, NFS_COOKIESIZE) == 0;
+}
+
+static bool __maybe_unused
+xdrgen_decode_timeval(struct xdr_stream *xdr, struct timeval *ptr)
+{
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->seconds))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->useconds))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_fattr(struct xdr_stream *xdr, struct fattr *ptr)
+{
+ if (!xdrgen_decode_ftype(xdr, &ptr->type))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->mode))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->nlink))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->uid))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->gid))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->size))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->blocksize))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->rdev))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->blocks))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->fsid))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->fileid))
+ return false;
+ if (!xdrgen_decode_timeval(xdr, &ptr->atime))
+ return false;
+ if (!xdrgen_decode_timeval(xdr, &ptr->mtime))
+ return false;
+ if (!xdrgen_decode_timeval(xdr, &ptr->ctime))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_sattr(struct xdr_stream *xdr, struct sattr *ptr)
+{
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->mode))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->uid))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->gid))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->size))
+ return false;
+ if (!xdrgen_decode_timeval(xdr, &ptr->atime))
+ return false;
+ if (!xdrgen_decode_timeval(xdr, &ptr->mtime))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_filename(struct xdr_stream *xdr, filename *ptr)
+{
+ return xdrgen_decode_string(xdr, ptr, NFS_MAXNAMLEN);
+}
+
+static bool __maybe_unused
+xdrgen_decode_path(struct xdr_stream *xdr, path *ptr)
+{
+ return xdrgen_decode_string(xdr, ptr, NFS_MAXPATHLEN);
+}
+
+static bool __maybe_unused
+xdrgen_decode_attrstat(struct xdr_stream *xdr, struct attrstat *ptr)
+{
+ if (!xdrgen_decode_nfsstat(xdr, &ptr->status))
+ return false;
+ switch (ptr->status) {
+ case NFS_OK:
+ if (!xdrgen_decode_fattr(xdr, &ptr->u.attributes))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_diropargs(struct xdr_stream *xdr, struct diropargs *ptr)
+{
+ if (!xdrgen_decode_fhandle(xdr, &ptr->dir))
+ return false;
+ if (!xdrgen_decode_filename(xdr, &ptr->name))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_diropok(struct xdr_stream *xdr, struct diropok *ptr)
+{
+ if (!xdrgen_decode_fhandle(xdr, &ptr->file))
+ return false;
+ if (!xdrgen_decode_fattr(xdr, &ptr->attributes))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_diropres(struct xdr_stream *xdr, struct diropres *ptr)
+{
+ if (!xdrgen_decode_nfsstat(xdr, &ptr->status))
+ return false;
+ switch (ptr->status) {
+ case NFS_OK:
+ if (!xdrgen_decode_diropok(xdr, &ptr->u.diropok))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_sattrargs(struct xdr_stream *xdr, struct sattrargs *ptr)
+{
+ if (!xdrgen_decode_fhandle(xdr, &ptr->file))
+ return false;
+ if (!xdrgen_decode_sattr(xdr, &ptr->attributes))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_readlinkres(struct xdr_stream *xdr, struct readlinkres *ptr)
+{
+ if (!xdrgen_decode_nfsstat(xdr, &ptr->status))
+ return false;
+ switch (ptr->status) {
+ case NFS_OK:
+ if (!xdrgen_decode_path(xdr, &ptr->u.data))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_readargs(struct xdr_stream *xdr, struct readargs *ptr)
+{
+ if (!xdrgen_decode_fhandle(xdr, &ptr->file))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->offset))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->count))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->totalcount))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_readresok(struct xdr_stream *xdr, struct readresok *ptr)
+{
+ if (!xdrgen_decode_fattr(xdr, &ptr->attributes))
+ return false;
+ if (!xdrgen_decode_opaque(xdr, &ptr->data, NFS_MAXDATA))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_readres(struct xdr_stream *xdr, struct readres *ptr)
+{
+ if (!xdrgen_decode_nfsstat(xdr, &ptr->status))
+ return false;
+ switch (ptr->status) {
+ case NFS_OK:
+ if (!xdrgen_decode_readresok(xdr, &ptr->u.readresok))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_writeargs(struct xdr_stream *xdr, struct writeargs *ptr)
+{
+ if (!xdrgen_decode_fhandle(xdr, &ptr->file))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->beginoffset))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->offset))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->totalcount))
+ return false;
+ if (!xdrgen_decode_opaque(xdr, &ptr->data, NFS_MAXDATA))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_createargs(struct xdr_stream *xdr, struct createargs *ptr)
+{
+ if (!xdrgen_decode_diropargs(xdr, &ptr->where))
+ return false;
+ if (!xdrgen_decode_sattr(xdr, &ptr->attributes))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_renameargs(struct xdr_stream *xdr, struct renameargs *ptr)
+{
+ if (!xdrgen_decode_diropargs(xdr, &ptr->from))
+ return false;
+ if (!xdrgen_decode_diropargs(xdr, &ptr->to))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_linkargs(struct xdr_stream *xdr, struct linkargs *ptr)
+{
+ if (!xdrgen_decode_fhandle(xdr, &ptr->from))
+ return false;
+ if (!xdrgen_decode_diropargs(xdr, &ptr->to))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_symlinkargs(struct xdr_stream *xdr, struct symlinkargs *ptr)
+{
+ if (!xdrgen_decode_diropargs(xdr, &ptr->from))
+ return false;
+ if (!xdrgen_decode_path(xdr, &ptr->to))
+ return false;
+ if (!xdrgen_decode_sattr(xdr, &ptr->attributes))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_readdirargs(struct xdr_stream *xdr, struct readdirargs *ptr)
+{
+ if (!xdrgen_decode_fhandle(xdr, &ptr->dir))
+ return false;
+ if (!xdrgen_decode_nfscookie(xdr, &ptr->cookie))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->count))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_entry(struct xdr_stream *xdr, struct entry *ptr)
+{
+ bool opted;
+
+ if (!xdrgen_decode_bool(xdr, &opted))
+ return false;
+ if (!opted)
+ return true;
+
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->fileid))
+ return false;
+ if (!xdrgen_decode_filename(xdr, &ptr->name))
+ return false;
+ if (!xdrgen_decode_nfscookie(xdr, &ptr->cookie))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_readdirok(struct xdr_stream *xdr, struct readdirok *ptr)
+{
+ if (!xdrgen_decode_entry(xdr, ptr->entries))
+ return false;
+ if (!xdrgen_decode_bool(xdr, &ptr->eof))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_readdirres(struct xdr_stream *xdr, struct readdirres *ptr)
+{
+ if (!xdrgen_decode_nfsstat(xdr, &ptr->status))
+ return false;
+ switch (ptr->status) {
+ case NFS_OK:
+ if (!xdrgen_decode_readdirok(xdr, &ptr->u.readdirok))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_info(struct xdr_stream *xdr, struct info *ptr)
+{
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->tsize))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->bsize))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->blocks))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->bfree))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->bavail))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_statfsres(struct xdr_stream *xdr, struct statfsres *ptr)
+{
+ if (!xdrgen_decode_nfsstat(xdr, &ptr->status))
+ return false;
+ switch (ptr->status) {
+ case NFS_OK:
+ if (!xdrgen_decode_info(xdr, &ptr->u.info))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+/**
+ * nfs_svc_decode_fhandle - Decode a fhandle argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nfs_svc_decode_fhandle(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ fhandle *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_fhandle(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_sattrargs - Decode a sattrargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nfs_svc_decode_sattrargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct sattrargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_sattrargs(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_diropargs - Decode a diropargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nfs_svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct diropargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_diropargs(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_readargs - Decode a readargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nfs_svc_decode_readargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct readargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_readargs(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_writeargs - Decode a writeargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nfs_svc_decode_writeargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct writeargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_writeargs(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_createargs - Decode a createargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nfs_svc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct createargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_createargs(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_renameargs - Decode a renameargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nfs_svc_decode_renameargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct renameargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_renameargs(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_linkargs - Decode a linkargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nfs_svc_decode_linkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct linkargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_linkargs(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_symlinkargs - Decode a symlinkargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nfs_svc_decode_symlinkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct symlinkargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_symlinkargs(xdr, argp);
+}
+
+/**
+ * nfs_svc_decode_readdirargs - Decode a readdirargs argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nfs_svc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct readdirargs *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_readdirargs(xdr, argp);
+}
+
+static bool __maybe_unused
+xdrgen_encode_nfsstat(struct xdr_stream *xdr, nfsstat value)
+{
+ return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
+}
+
+static bool __maybe_unused
+xdrgen_encode_ftype(struct xdr_stream *xdr, ftype value)
+{
+ return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
+}
+
+static bool __maybe_unused
+xdrgen_encode_fhandle(struct xdr_stream *xdr, const fhandle value)
+{
+ return xdr_stream_encode_opaque_fixed(xdr, value, NFS_FHSIZE) >= 0;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nfscookie(struct xdr_stream *xdr, const nfscookie value)
+{
+ return xdr_stream_encode_opaque_fixed(xdr, value, NFS_COOKIESIZE) >= 0;
+}
+
+static bool __maybe_unused
+xdrgen_encode_timeval(struct xdr_stream *xdr, const struct timeval *value)
+{
+ if (!xdrgen_encode_unsigned_int(xdr, value->seconds))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->useconds))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_fattr(struct xdr_stream *xdr, const struct fattr *value)
+{
+ if (!xdrgen_encode_ftype(xdr, value->type))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->mode))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->nlink))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->uid))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->gid))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->size))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->blocksize))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->rdev))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->blocks))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->fsid))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->fileid))
+ return false;
+ if (!xdrgen_encode_timeval(xdr, &value->atime))
+ return false;
+ if (!xdrgen_encode_timeval(xdr, &value->mtime))
+ return false;
+ if (!xdrgen_encode_timeval(xdr, &value->ctime))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_sattr(struct xdr_stream *xdr, const struct sattr *value)
+{
+ if (!xdrgen_encode_unsigned_int(xdr, value->mode))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->uid))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->gid))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->size))
+ return false;
+ if (!xdrgen_encode_timeval(xdr, &value->atime))
+ return false;
+ if (!xdrgen_encode_timeval(xdr, &value->mtime))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_filename(struct xdr_stream *xdr, const filename value)
+{
+ return xdr_stream_encode_opaque(xdr, value.data, value.len) >= 0;
+}
+
+static bool __maybe_unused
+xdrgen_encode_path(struct xdr_stream *xdr, const path value)
+{
+ return xdr_stream_encode_opaque(xdr, value.data, value.len) >= 0;
+}
+
+static bool __maybe_unused
+xdrgen_encode_attrstat(struct xdr_stream *xdr, const struct attrstat *ptr)
+{
+ if (!xdrgen_encode_nfsstat(xdr, ptr->status))
+ return false;
+ switch (ptr->status) {
+ case NFS_OK:
+ if (!xdrgen_encode_fattr(xdr, &ptr->u.attributes))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_diropargs(struct xdr_stream *xdr, const struct diropargs *value)
+{
+ if (!xdrgen_encode_fhandle(xdr, value->dir))
+ return false;
+ if (!xdrgen_encode_filename(xdr, value->name))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_diropok(struct xdr_stream *xdr, const struct diropok *value)
+{
+ if (!xdrgen_encode_fhandle(xdr, value->file))
+ return false;
+ if (!xdrgen_encode_fattr(xdr, &value->attributes))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_diropres(struct xdr_stream *xdr, const struct diropres *ptr)
+{
+ if (!xdrgen_encode_nfsstat(xdr, ptr->status))
+ return false;
+ switch (ptr->status) {
+ case NFS_OK:
+ if (!xdrgen_encode_diropok(xdr, &ptr->u.diropok))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_sattrargs(struct xdr_stream *xdr, const struct sattrargs *value)
+{
+ if (!xdrgen_encode_fhandle(xdr, value->file))
+ return false;
+ if (!xdrgen_encode_sattr(xdr, &value->attributes))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_readlinkres(struct xdr_stream *xdr, const struct readlinkres *ptr)
+{
+ if (!xdrgen_encode_nfsstat(xdr, ptr->status))
+ return false;
+ switch (ptr->status) {
+ case NFS_OK:
+ if (!xdrgen_encode_path(xdr, ptr->u.data))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_readargs(struct xdr_stream *xdr, const struct readargs *value)
+{
+ if (!xdrgen_encode_fhandle(xdr, value->file))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->offset))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->count))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->totalcount))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_readresok(struct xdr_stream *xdr, const struct readresok *value)
+{
+ if (!xdrgen_encode_fattr(xdr, &value->attributes))
+ return false;
+ if (value->data.len > NFS_MAXDATA)
+ 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_readres(struct xdr_stream *xdr, const struct readres *ptr)
+{
+ if (!xdrgen_encode_nfsstat(xdr, ptr->status))
+ return false;
+ switch (ptr->status) {
+ case NFS_OK:
+ if (!xdrgen_encode_readresok(xdr, &ptr->u.readresok))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_writeargs(struct xdr_stream *xdr, const struct writeargs *value)
+{
+ if (!xdrgen_encode_fhandle(xdr, value->file))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->beginoffset))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->offset))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->totalcount))
+ return false;
+ if (value->data.len > NFS_MAXDATA)
+ 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_createargs(struct xdr_stream *xdr, const struct createargs *value)
+{
+ if (!xdrgen_encode_diropargs(xdr, &value->where))
+ return false;
+ if (!xdrgen_encode_sattr(xdr, &value->attributes))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_renameargs(struct xdr_stream *xdr, const struct renameargs *value)
+{
+ if (!xdrgen_encode_diropargs(xdr, &value->from))
+ return false;
+ if (!xdrgen_encode_diropargs(xdr, &value->to))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_linkargs(struct xdr_stream *xdr, const struct linkargs *value)
+{
+ if (!xdrgen_encode_fhandle(xdr, value->from))
+ return false;
+ if (!xdrgen_encode_diropargs(xdr, &value->to))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_symlinkargs(struct xdr_stream *xdr, const struct symlinkargs *value)
+{
+ if (!xdrgen_encode_diropargs(xdr, &value->from))
+ return false;
+ if (!xdrgen_encode_path(xdr, value->to))
+ return false;
+ if (!xdrgen_encode_sattr(xdr, &value->attributes))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_readdirargs(struct xdr_stream *xdr, const struct readdirargs *value)
+{
+ if (!xdrgen_encode_fhandle(xdr, value->dir))
+ return false;
+ if (!xdrgen_encode_nfscookie(xdr, value->cookie))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->count))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_entry(struct xdr_stream *xdr, const struct entry *value)
+{
+ if (!xdrgen_encode_bool(xdr, value != NULL))
+ return false;
+ if (!value)
+ return true;
+
+ if (!xdrgen_encode_unsigned_int(xdr, value->fileid))
+ return false;
+ if (!xdrgen_encode_filename(xdr, value->name))
+ return false;
+ if (!xdrgen_encode_nfscookie(xdr, value->cookie))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_readdirok(struct xdr_stream *xdr, const struct readdirok *value)
+{
+ if (!xdrgen_encode_entry(xdr, value->entries))
+ return false;
+ if (!xdrgen_encode_bool(xdr, value->eof))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_readdirres(struct xdr_stream *xdr, const struct readdirres *ptr)
+{
+ if (!xdrgen_encode_nfsstat(xdr, ptr->status))
+ return false;
+ switch (ptr->status) {
+ case NFS_OK:
+ if (!xdrgen_encode_readdirok(xdr, &ptr->u.readdirok))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_info(struct xdr_stream *xdr, const struct info *value)
+{
+ if (!xdrgen_encode_unsigned_int(xdr, value->tsize))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->bsize))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->blocks))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->bfree))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->bavail))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_statfsres(struct xdr_stream *xdr, const struct statfsres *ptr)
+{
+ if (!xdrgen_encode_nfsstat(xdr, ptr->status))
+ return false;
+ switch (ptr->status) {
+ case NFS_OK:
+ if (!xdrgen_encode_info(xdr, &ptr->u.info))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+/**
+ * nfs_svc_encode_attrstat - Encode a attrstat result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ * %true: procedure results encoded successfully
+ * %false: encode failed
+ */
+bool nfs_svc_encode_attrstat(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct attrstat *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_attrstat(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_diropres - Encode a diropres result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ * %true: procedure results encoded successfully
+ * %false: encode failed
+ */
+bool nfs_svc_encode_diropres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct diropres *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_diropres(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_readlinkres - Encode a readlinkres result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ * %true: procedure results encoded successfully
+ * %false: encode failed
+ */
+bool nfs_svc_encode_readlinkres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct readlinkres *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_readlinkres(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_readres - Encode a readres result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ * %true: procedure results encoded successfully
+ * %false: encode failed
+ */
+bool nfs_svc_encode_readres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct readres *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_readres(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_nfsstat - Encode a nfsstat result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ * %true: procedure results encoded successfully
+ * %false: encode failed
+ */
+bool nfs_svc_encode_nfsstat(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ nfsstat *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_nfsstat(xdr, *resp);
+}
+
+/**
+ * nfs_svc_encode_readdirres - Encode a readdirres result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ * %true: procedure results encoded successfully
+ * %false: encode failed
+ */
+bool nfs_svc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct readdirres *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_readdirres(xdr, resp);
+}
+
+/**
+ * nfs_svc_encode_statfsres - Encode a statfsres result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ * %true: procedure results encoded successfully
+ * %false: encode failed
+ */
+bool nfs_svc_encode_statfsres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct statfsres *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_statfsres(xdr, resp);
+}
diff --git a/fs/nfsd/nfs2xdr_gen.h b/fs/nfsd/nfs2xdr_gen.h
new file mode 100644
index 000000000000..df800e42b80e
--- /dev/null
+++ b/fs/nfsd/nfs2xdr_gen.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Generated by xdrgen. Manual edits will be lost. */
+/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x */
+/* XDR specification modification time: Tue Sep 8 11:23:43 2026 */
+
+#ifndef _LINUX_XDRGEN_NFS2_DECL_H
+#define _LINUX_XDRGEN_NFS2_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/nfs2.h>
+
+bool nfs_svc_decode_fhandle(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_sattrargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_readargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_writeargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_renameargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_linkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_symlinkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+
+bool nfs_svc_encode_attrstat(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_diropres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_readlinkres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_readres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_nfsstat(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_svc_encode_statfsres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+
+#endif /* _LINUX_XDRGEN_NFS2_DECL_H */
diff --git a/include/linux/sunrpc/xdrgen/nfs2.h b/include/linux/sunrpc/xdrgen/nfs2.h
new file mode 100644
index 000000000000..4a022b670b0c
--- /dev/null
+++ b/include/linux/sunrpc/xdrgen/nfs2.h
@@ -0,0 +1,298 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Generated by xdrgen. Manual edits will be lost. */
+/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x */
+/* XDR specification modification time: Tue Sep 8 11:23:43 2026 */
+
+#ifndef _LINUX_XDRGEN_NFS2_DEF_H
+#define _LINUX_XDRGEN_NFS2_DEF_H
+
+#include <linux/types.h>
+#include <linux/sunrpc/xdrgen/_defs.h>
+
+enum { NFS_MAXDATA = 8192 };
+
+enum { NFS_MAXPATHLEN = 1024 };
+
+enum { NFS_MAXNAMLEN = 255 };
+
+enum { NFS_COOKIESIZE = 4 };
+
+enum { NFS_FHSIZE = 32 };
+
+enum nfsstat {
+ NFS_OK = 0,
+ NFSERR_PERM = 1,
+ NFSERR_NOENT = 2,
+ NFSERR_IO = 5,
+ NFSERR_NXIO = 6,
+ NFSERR_ACCES = 13,
+ NFSERR_EXIST = 17,
+ NFSERR_NODEV = 19,
+ NFSERR_NOTDIR = 20,
+ NFSERR_ISDIR = 21,
+ NFSERR_FBIG = 27,
+ NFSERR_NOSPC = 28,
+ NFSERR_ROFS = 30,
+ NFSERR_NAMETOOLONG = 63,
+ NFSERR_NOTEMPTY = 66,
+ NFSERR_DQUOT = 69,
+ NFSERR_STALE = 70,
+ NFSERR_WFLUSH = 99,
+};
+
+typedef enum nfsstat nfsstat;
+
+enum ftype {
+ NFNON = 0,
+ NFREG = 1,
+ NFDIR = 2,
+ NFBLK = 3,
+ NFCHR = 4,
+ NFLNK = 5,
+};
+
+typedef enum ftype ftype;
+
+typedef u8 fhandle[NFS_FHSIZE];
+
+typedef u8 nfscookie[NFS_COOKIESIZE];
+
+struct timeval {
+ u32 seconds;
+ u32 useconds;
+};
+
+struct fattr {
+ ftype type;
+ u32 mode;
+ u32 nlink;
+ u32 uid;
+ u32 gid;
+ u32 size;
+ u32 blocksize;
+ u32 rdev;
+ u32 blocks;
+ u32 fsid;
+ u32 fileid;
+ struct timeval atime;
+ struct timeval mtime;
+ struct timeval ctime;
+};
+
+struct sattr {
+ u32 mode;
+ u32 uid;
+ u32 gid;
+ u32 size;
+ struct timeval atime;
+ struct timeval mtime;
+};
+
+typedef string filename;
+
+typedef string path;
+
+struct attrstat {
+ nfsstat status;
+ union {
+ struct fattr attributes;
+ } u;
+};
+
+struct diropargs {
+ fhandle dir;
+ filename name;
+};
+
+struct diropok {
+ fhandle file;
+ struct fattr attributes;
+};
+
+struct diropres {
+ nfsstat status;
+ union {
+ struct diropok diropok;
+ } u;
+};
+
+struct sattrargs {
+ fhandle file;
+ struct sattr attributes;
+};
+
+struct readlinkres {
+ nfsstat status;
+ union {
+ path data;
+ } u;
+};
+
+struct readargs {
+ fhandle file;
+ u32 offset;
+ u32 count;
+ u32 totalcount;
+};
+
+struct readresok {
+ struct fattr attributes;
+ opaque data;
+};
+
+struct readres {
+ nfsstat status;
+ union {
+ struct readresok readresok;
+ } u;
+};
+
+struct writeargs {
+ fhandle file;
+ u32 beginoffset;
+ u32 offset;
+ u32 totalcount;
+ opaque data;
+};
+
+struct createargs {
+ struct diropargs where;
+ struct sattr attributes;
+};
+
+struct renameargs {
+ struct diropargs from;
+ struct diropargs to;
+};
+
+struct linkargs {
+ fhandle from;
+ struct diropargs to;
+};
+
+struct symlinkargs {
+ struct diropargs from;
+ path to;
+ struct sattr attributes;
+};
+
+struct readdirargs {
+ fhandle dir;
+ nfscookie cookie;
+ u32 count;
+};
+
+struct entry {
+ u32 fileid;
+ filename name;
+ nfscookie cookie;
+};
+
+struct readdirok {
+ struct entry *entries;
+ bool eof;
+};
+
+struct readdirres {
+ nfsstat status;
+ union {
+ struct readdirok readdirok;
+ } u;
+};
+
+struct info {
+ u32 tsize;
+ u32 bsize;
+ u32 blocks;
+ u32 bfree;
+ u32 bavail;
+};
+
+struct statfsres {
+ nfsstat status;
+ union {
+ struct info info;
+ } u;
+};
+
+enum {
+ NFSPROC_NULL = 0,
+ NFSPROC_GETATTR = 1,
+ NFSPROC_SETATTR = 2,
+ NFSPROC_ROOT = 3,
+ NFSPROC_LOOKUP = 4,
+ NFSPROC_READLINK = 5,
+ NFSPROC_READ = 6,
+ NFSPROC_WRITECACHE = 7,
+ NFSPROC_WRITE = 8,
+ NFSPROC_CREATE = 9,
+ NFSPROC_REMOVE = 10,
+ NFSPROC_RENAME = 11,
+ NFSPROC_LINK = 12,
+ NFSPROC_SYMLINK = 13,
+ NFSPROC_MKDIR = 14,
+ NFSPROC_RMDIR = 15,
+ NFSPROC_READDIR = 16,
+ NFSPROC_STATFS = 17,
+};
+
+#ifndef NFS_PROGRAM
+#define NFS_PROGRAM (100003)
+#endif
+
+#define NFS2_nfsstat_sz (XDR_int)
+#define NFS2_ftype_sz (XDR_int)
+#define NFS2_fhandle_sz (XDR_QUADLEN(NFS_FHSIZE))
+#define NFS2_nfscookie_sz (XDR_QUADLEN(NFS_COOKIESIZE))
+#define NFS2_timeval_sz \
+ (XDR_unsigned_int + XDR_unsigned_int)
+#define NFS2_fattr_sz \
+ (NFS2_ftype_sz + XDR_unsigned_int + XDR_unsigned_int + XDR_unsigned_int + XDR_unsigned_int + XDR_unsigned_int + XDR_unsigned_int + XDR_unsigned_int + XDR_unsigned_int + XDR_unsigned_int + XDR_unsigned_int + NFS2_timeval_sz + NFS2_timeval_sz + NFS2_timeval_sz)
+#define NFS2_sattr_sz \
+ (XDR_unsigned_int + XDR_unsigned_int + XDR_unsigned_int + XDR_unsigned_int + NFS2_timeval_sz + NFS2_timeval_sz)
+#define NFS2_filename_sz (XDR_unsigned_int + XDR_QUADLEN(NFS_MAXNAMLEN))
+#define NFS2_path_sz (XDR_unsigned_int + XDR_QUADLEN(NFS_MAXPATHLEN))
+#define NFS2_attrstat_sz \
+ (NFS2_nfsstat_sz + NFS2_fattr_sz)
+#define NFS2_diropargs_sz \
+ (NFS2_fhandle_sz + NFS2_filename_sz)
+#define NFS2_diropok_sz \
+ (NFS2_fhandle_sz + NFS2_fattr_sz)
+#define NFS2_diropres_sz \
+ (NFS2_nfsstat_sz + NFS2_diropok_sz)
+#define NFS2_sattrargs_sz \
+ (NFS2_fhandle_sz + NFS2_sattr_sz)
+#define NFS2_readlinkres_sz \
+ (NFS2_nfsstat_sz + NFS2_path_sz)
+#define NFS2_readargs_sz \
+ (NFS2_fhandle_sz + XDR_unsigned_int + XDR_unsigned_int + XDR_unsigned_int)
+#define NFS2_readresok_sz \
+ (NFS2_fattr_sz + XDR_unsigned_int + XDR_QUADLEN(NFS_MAXDATA))
+#define NFS2_readres_sz \
+ (NFS2_nfsstat_sz + NFS2_readresok_sz)
+#define NFS2_writeargs_sz \
+ (NFS2_fhandle_sz + XDR_unsigned_int + XDR_unsigned_int + XDR_unsigned_int + XDR_unsigned_int + XDR_QUADLEN(NFS_MAXDATA))
+#define NFS2_createargs_sz \
+ (NFS2_diropargs_sz + NFS2_sattr_sz)
+#define NFS2_renameargs_sz \
+ (NFS2_diropargs_sz + NFS2_diropargs_sz)
+#define NFS2_linkargs_sz \
+ (NFS2_fhandle_sz + NFS2_diropargs_sz)
+#define NFS2_symlinkargs_sz \
+ (NFS2_diropargs_sz + NFS2_path_sz + NFS2_sattr_sz)
+#define NFS2_readdirargs_sz \
+ (NFS2_fhandle_sz + NFS2_nfscookie_sz + XDR_unsigned_int)
+#define NFS2_entry_sz \
+ (XDR_bool + XDR_unsigned_int + NFS2_filename_sz + NFS2_nfscookie_sz)
+#define NFS2_readdirok_sz \
+ (XDR_bool + XDR_bool)
+#define NFS2_readdirres_sz \
+ (NFS2_nfsstat_sz + NFS2_readdirok_sz)
+#define NFS2_info_sz \
+ (XDR_unsigned_int + XDR_unsigned_int + XDR_unsigned_int + XDR_unsigned_int + XDR_unsigned_int)
+#define NFS2_statfsres_sz \
+ (NFS2_nfsstat_sz + NFS2_info_sz)
+#define NFS2_MAX_ARGS_SZ \
+ (NFS2_writeargs_sz)
+
+#endif /* _LINUX_XDRGEN_NFS2_DEF_H */
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 2/2] NFSD: map fh_verify() status codes for NFS_ACLv3 replies
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (2 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 02/27] NFSD: Add infrastructure for generating NFSv2 XDR encoders and decoders Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-17 11:57 ` Jeff Layton
2026-09-16 16:28 ` [PATCH v1 03/27] NFSD: Use xdrgen-generated NFSv2 protocol definitions Chuck Lever
` (25 subsequent siblings)
29 siblings, 1 reply; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
The NFS_ACLv3 protocol shares its status code space with NFSv3,
which has NFS3ERR_BADHANDLE but no NFSERR_NOFILEHANDLE. An
NFS_ACLv3 GETACL or SETACL request that carries an empty file
handle gets a reply with status 10020, a value the client cannot
interpret.
Commit 1459ad57673b ("nfsd: Move error code mapping to per-version
proc code.") made fh_verify() return nfserr_nofilehandle for an
empty handle regardless of protocol version. The NFSv3 procedures
gained nfsd3_map_status() in exchange, but the NFS_ACLv3 procedures
did not, so the unmapped status reaches the encoder.
Add the same mapping to the NFS_ACLv3 procedures.
Fixes: 1459ad57673b ("nfsd: Move error code mapping to per-version proc code.")
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfs3acl.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/fs/nfsd/nfs3acl.c b/fs/nfsd/nfs3acl.c
index 7183995182ab..5219ec634587 100644
--- a/fs/nfsd/nfs3acl.c
+++ b/fs/nfsd/nfs3acl.c
@@ -14,6 +14,19 @@
#include "xdr3.h"
#include "vfs.h"
+static __be32 nfsd3_map_status(__be32 status)
+{
+ switch (status) {
+ case nfserr_nofilehandle:
+ status = nfserr_badhandle;
+ break;
+ case nfserr_wrongsec:
+ status = nfserr_acces;
+ break;
+ }
+ return status;
+}
+
/*
* NULL call.
*/
@@ -72,6 +85,7 @@ static __be32 nfsd3_proc_getacl(struct svc_rqst *rqstp)
/* resp->acl_{access,default} are released in nfs3svc_release_getacl. */
out:
+ resp->status = nfsd3_map_status(resp->status);
return rpc_success;
fail:
@@ -125,6 +139,7 @@ static __be32 nfsd3_proc_setacl(struct svc_rqst *rqstp)
resp->status = nfserrno(error);
out:
/* argp->acl_{access,default} are released in nfs3svc_release_setacl. */
+ resp->status = nfsd3_map_status(resp->status);
return rpc_success;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [PATCH v1 2/2] NFSD: map fh_verify() status codes for NFS_ACLv3 replies
2026-09-16 16:28 ` [PATCH v1 2/2] NFSD: map fh_verify() status codes for NFS_ACLv3 replies Chuck Lever
@ 2026-09-17 11:57 ` Jeff Layton
0 siblings, 0 replies; 34+ messages in thread
From: Jeff Layton @ 2026-09-17 11:57 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
On Wed, 2026-09-16 at 12:28 -0400, Chuck Lever wrote:
> The NFS_ACLv3 protocol shares its status code space with NFSv3,
> which has NFS3ERR_BADHANDLE but no NFSERR_NOFILEHANDLE. An
> NFS_ACLv3 GETACL or SETACL request that carries an empty file
> handle gets a reply with status 10020, a value the client cannot
> interpret.
>
> Commit 1459ad57673b ("nfsd: Move error code mapping to per-version
> proc code.") made fh_verify() return nfserr_nofilehandle for an
> empty handle regardless of protocol version. The NFSv3 procedures
> gained nfsd3_map_status() in exchange, but the NFS_ACLv3 procedures
> did not, so the unmapped status reaches the encoder.
>
> Add the same mapping to the NFS_ACLv3 procedures.
>
> Fixes: 1459ad57673b ("nfsd: Move error code mapping to per-version proc code.")
> Signed-off-by: Chuck Lever <cel@kernel.org>
> ---
> fs/nfsd/nfs3acl.c | 15 +++++++++++++++
> 1 file changed, 15 insertions(+)
>
> diff --git a/fs/nfsd/nfs3acl.c b/fs/nfsd/nfs3acl.c
> index 7183995182ab..5219ec634587 100644
> --- a/fs/nfsd/nfs3acl.c
> +++ b/fs/nfsd/nfs3acl.c
> @@ -14,6 +14,19 @@
> #include "xdr3.h"
> #include "vfs.h"
>
> +static __be32 nfsd3_map_status(__be32 status)
> +{
> + switch (status) {
> + case nfserr_nofilehandle:
> + status = nfserr_badhandle;
> + break;
> + case nfserr_wrongsec:
> + status = nfserr_acces;
> + break;
> + }
> + return status;
> +}
> +
> /*
> * NULL call.
> */
> @@ -72,6 +85,7 @@ static __be32 nfsd3_proc_getacl(struct svc_rqst *rqstp)
>
> /* resp->acl_{access,default} are released in nfs3svc_release_getacl. */
> out:
> + resp->status = nfsd3_map_status(resp->status);
> return rpc_success;
>
> fail:
> @@ -125,6 +139,7 @@ static __be32 nfsd3_proc_setacl(struct svc_rqst *rqstp)
> resp->status = nfserrno(error);
> out:
> /* argp->acl_{access,default} are released in nfs3svc_release_setacl. */
> + resp->status = nfsd3_map_status(resp->status);
> return rpc_success;
> }
>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v1 03/27] NFSD: Use xdrgen-generated NFSv2 protocol definitions
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (3 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 2/2] NFSD: map fh_verify() status codes for NFS_ACLv3 replies Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 04/27] NFSD: Remove '#include "xdr.h"' from fs/nfsd/xdr3.h Chuck Lever
` (24 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Switch the server's NFSv2 implementation to use the machine-
generated protocol definitions.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfs_common/common.c | 5 +++--
fs/nfsd/nfsd.h | 2 --
fs/nfsd/nfserr.h | 2 +-
fs/nfsd/nfsproc.c | 2 ++
fs/nfsd/nfsxdr.c | 9 ++++++++-
include/linux/nfs_common.h | 4 ++--
6 files changed, 16 insertions(+), 8 deletions(-)
diff --git a/fs/nfs_common/common.c b/fs/nfs_common/common.c
index 0778743ae2c2..a05f2ace31ec 100644
--- a/fs/nfs_common/common.c
+++ b/fs/nfs_common/common.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/module.h>
+#include <uapi/linux/nfs.h>
#include <linux/nfs_common.h>
#include <linux/nfs4.h>
@@ -53,12 +54,12 @@ static const struct {
* Returns a local errno value, or -EIO if the NFS status code is
* not recognized. This function is used jointly by NFSv2 and NFSv3.
*/
-int nfs_stat_to_errno(enum nfs_stat status)
+int nfs_stat_to_errno(int status)
{
int i;
for (i = 0; i < ARRAY_SIZE(nfs_errtbl); i++) {
- if (nfs_errtbl[i].stat == (int)status)
+ if (nfs_errtbl[i].stat == status)
return nfs_errtbl[i].errno;
}
return -EIO;
diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h
index 69e3e92b3ec1..d1e413d21e76 100644
--- a/fs/nfsd/nfsd.h
+++ b/fs/nfsd/nfsd.h
@@ -12,8 +12,6 @@
#include <linux/types.h>
#include <linux/mount.h>
-#include <linux/nfs.h>
-#include <linux/nfs2.h>
#include <linux/sunrpc/svc.h>
#include <linux/sunrpc/svc_xprt.h>
diff --git a/fs/nfsd/nfserr.h b/fs/nfsd/nfserr.h
index d1c434b834b2..35debf1141a3 100644
--- a/fs/nfsd/nfserr.h
+++ b/fs/nfsd/nfserr.h
@@ -10,7 +10,7 @@
#ifndef LINUX_NFSD_NFSERR_H
#define LINUX_NFSD_NFSERR_H
-#include <linux/nfs.h>
+#include <linux/sunrpc/xdrgen/nfs2.h>
#include <linux/nfs3.h>
#include <linux/nfs4.h>
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index 09d360839825..1ce75779312b 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -6,7 +6,9 @@
*/
#include <linux/namei.h>
+#include <linux/sunrpc/svc.h>
+#include "nfs2xdr_gen.h"
#include "cache.h"
#include "xdr.h"
#include "vfs.h"
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
index 0961c13d6ab1..d55e7d6414a0 100644
--- a/fs/nfsd/nfsxdr.c
+++ b/fs/nfsd/nfsxdr.c
@@ -22,6 +22,13 @@
*/
#define NFS2_SATTR_SET_TO_SERVER_TIME (1000000)
+/*
+ * Linux-internal ftype values for socket and unknown inodes, not
+ * in RFC 1094's wire enum; values match enum nfs_ftype.
+ */
+#define NFBAD (7)
+#define NFSOCK (6)
+
/*
* Mapping of S_IF* types to NFS file types
*/
@@ -599,7 +606,7 @@ svcxdr_encode_entry_common(struct nfsd_readdirres *resp, const char *name,
if (xdr_stream_encode_u32(xdr, (u32)ino) < 0)
return false;
/* name */
- if (xdr_stream_encode_opaque(xdr, name, min(namlen, NFS2_MAXNAMLEN)) < 0)
+ if (xdr_stream_encode_opaque(xdr, name, min(namlen, NFS_MAXNAMLEN)) < 0)
return false;
/* cookie */
resp->cookie_offset = dirlist->len;
diff --git a/include/linux/nfs_common.h b/include/linux/nfs_common.h
index a541c3a02887..88dccd8564cf 100644
--- a/include/linux/nfs_common.h
+++ b/include/linux/nfs_common.h
@@ -6,11 +6,11 @@
#define _LINUX_NFS_COMMON_H
#include <linux/errno.h>
-#include <uapi/linux/nfs.h>
+#include <linux/types.h>
/* Mapping from NFS error code to "errno" error code. */
-int nfs_stat_to_errno(enum nfs_stat status);
+int nfs_stat_to_errno(int status);
int nfs4_stat_to_errno(int stat);
__u32 nfs_localio_errno_to_nfs4_stat(int errno);
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 04/27] NFSD: Remove '#include "xdr.h"' from fs/nfsd/xdr3.h
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (4 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 03/27] NFSD: Use xdrgen-generated NFSv2 protocol definitions Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 05/27] NFSD: Relocate the NFSv2 XDR storage union into nfsproc.c Chuck Lever
` (23 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Separate the dependencies between fs/nfsd/xdr3.h and fs/nfsd/xdr.h
so that NFSD's NFSv2 and NFSv3 implementations can be converted
independently to use xdrgen.
The only reason xdr3.h includes xdr.h is to pick up the definition
of struct nfsd_fhandle. xdr3.h does not name the structure itself;
its consumers nfs3proc.c and nfs3xdr.c do, reaching the definition
through xdr3.h. That structure wraps a single struct svc_fh, which
is defined in nfsfh.h, a header both xdr.h and xdr3.h already include
via vfs.h. Move struct nfsd_fhandle next to svc_fh in nfsfh.h so the
two XDR headers share one definition and xdr3.h no longer depends on
xdr.h. nfs2acl.c, which includes both headers, then sees that single
definition instead of a clashing pair.
Stripping xdr.h to its own dependencies also removes the
transitive route by which nfsxdr.c and nfs3xdr.c reached nfsd.h.
Both expand the nfserr_* and nfs_ok status-code macros that
nfsd.h defines, so each gains a direct #include of nfsd.h in
place of the one xdr.h used to supply.
As a clean-up, the #includes at the top of both header files are
made the same.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfs2acl.c | 2 +-
fs/nfsd/nfs3xdr.c | 2 ++
fs/nfsd/nfsfh.h | 5 +++++
fs/nfsd/xdr.h | 7 +------
fs/nfsd/xdr3.h | 3 ++-
5 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
index 0673e83b6666..eada1ab51415 100644
--- a/fs/nfsd/nfs2acl.c
+++ b/fs/nfsd/nfs2acl.c
@@ -10,8 +10,8 @@
/* FIXME: nfsacl.h is a broken header */
#include <linux/nfsacl.h>
#include <linux/gfp.h>
-#include <linux/nfs3.h>
#include "cache.h"
+#include "xdr.h"
#include "xdr3.h"
#include "vfs.h"
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index a14e829e1c41..f0895f5e32f6 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -10,6 +10,8 @@
#include <linux/namei.h>
#include <linux/nfs3.h>
#include <linux/sunrpc/svc_xprt.h>
+
+#include "nfsd.h"
#include "xdr3.h"
#include "auth.h"
#include "netns.h"
diff --git a/fs/nfsd/nfsfh.h b/fs/nfsd/nfsfh.h
index 7d8e3f015307..aa3ff3086406 100644
--- a/fs/nfsd/nfsfh.h
+++ b/fs/nfsd/nfsfh.h
@@ -111,6 +111,11 @@ typedef struct svc_fh {
struct kstat fh_post_attr; /* full attrs after operation */
u64 fh_post_change; /* nfsv4 change; see above */
} svc_fh;
+
+struct nfsd_fhandle {
+ struct svc_fh fh;
+};
+
#define NFSD4_FH_FOREIGN (1<<0)
#define SET_FH_FLAG(c, f) ((c)->fh_flags |= (f))
#define HAS_FH_FLAG(c, f) ((c)->fh_flags & (f))
diff --git a/fs/nfsd/xdr.h b/fs/nfsd/xdr.h
index 52660b9f8dde..a2a3b2f58a1d 100644
--- a/fs/nfsd/xdr.h
+++ b/fs/nfsd/xdr.h
@@ -5,13 +5,8 @@
#define _LINUX_NFSD_XDR_H
#include <linux/vfs.h>
-#include "nfsd.h"
-#include "nfsfh.h"
-#include "vfs.h"
-struct nfsd_fhandle {
- struct svc_fh fh;
-};
+#include "vfs.h"
struct nfsd_sattrargs {
struct svc_fh fh;
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index cad875d14231..22272695e451 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -8,7 +8,8 @@
#ifndef _LINUX_NFSD_XDR3_H
#define _LINUX_NFSD_XDR3_H
-#include "xdr.h"
+#include <linux/vfs.h>
+
#include "vfs.h"
struct nfsd3_sattrargs {
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 05/27] NFSD: Relocate the NFSv2 XDR storage union into nfsproc.c
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (5 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 04/27] NFSD: Remove '#include "xdr.h"' from fs/nfsd/xdr3.h Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 06/27] NFSD: Use xdrgen XDR functions for the NFSv2 NULL procedure Chuck Lever
` (22 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
union nfsd_xdrstore and the NFS2_SVC_XDRSIZE macro it feeds exist
only to compute nfsd_version2.vs_xdrsize, yet xdr.h exposes them to
every file that includes the header. This couples the NFSv2 XDR
type layout to translation units that have no stake in the
service's buffer size.
Move the union next to nfsd_version2 in nfsproc.c, its sole
consumer, and compute vs_xdrsize directly from sizeof(union
nfsd_xdrstore). NFS2_SVC_XDRSIZE then has no users left and is
removed from xdr.h. This mirrors the recent lockd conversion, which
confined each NLM version's storage union to the file that defines
the version structure.
The union also gains the NFSv2 result structures. 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 vs_xdrsize must bound the largest result
the version encodes, not only its largest argument. The NFSv3
storage union already lists both.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfsproc.c | 23 ++++++++++++++++++++++-
fs/nfsd/xdr.h | 18 ------------------
2 files changed, 22 insertions(+), 19 deletions(-)
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index 1ce75779312b..fd303d7dbad3 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -878,10 +878,31 @@ static const struct svc_procedure nfsd_procedures2[18] = {
},
};
+/*
+ * Storage requirements for XDR arguments and results.
+ */
+union nfsd_xdrstore {
+ struct nfsd_sattrargs sattr;
+ struct nfsd_diropargs dirop;
+ struct nfsd_readargs read;
+ struct nfsd_writeargs write;
+ struct nfsd_createargs create;
+ struct nfsd_renameargs rename;
+ struct nfsd_linkargs link;
+ struct nfsd_symlinkargs symlink;
+ struct nfsd_readdirargs readdir;
+ struct nfsd_attrstat attrstat;
+ struct nfsd_diropres diropres;
+ struct nfsd_readlinkres readlinkres;
+ struct nfsd_readres readres;
+ struct nfsd_readdirres readdirres;
+ struct nfsd_statfsres statfsres;
+};
+
const struct svc_version nfsd_version2 = {
.vs_vers = 2,
.vs_nproc = ARRAY_SIZE(nfsd_procedures2),
.vs_proc = nfsd_procedures2,
.vs_dispatch = nfsd_dispatch,
- .vs_xdrsize = NFS2_SVC_XDRSIZE,
+ .vs_xdrsize = sizeof(union nfsd_xdrstore),
};
diff --git a/fs/nfsd/xdr.h b/fs/nfsd/xdr.h
index a2a3b2f58a1d..05b3a18c879a 100644
--- a/fs/nfsd/xdr.h
+++ b/fs/nfsd/xdr.h
@@ -119,24 +119,6 @@ struct nfsd_statfsres {
struct kstatfs stats;
};
-/*
- * Storage requirements for XDR arguments and results.
- */
-union nfsd_xdrstore {
- struct nfsd_sattrargs sattr;
- struct nfsd_diropargs dirop;
- struct nfsd_readargs read;
- struct nfsd_writeargs write;
- struct nfsd_createargs create;
- struct nfsd_renameargs rename;
- struct nfsd_linkargs link;
- struct nfsd_symlinkargs symlink;
- struct nfsd_readdirargs readdir;
-};
-
-#define NFS2_SVC_XDRSIZE sizeof(union nfsd_xdrstore)
-
-
bool nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_sattrargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_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 06/27] NFSD: Use xdrgen XDR functions for the NFSv2 NULL procedure
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (6 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 05/27] NFSD: Relocate the NFSv2 XDR storage union into nfsproc.c Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 07/27] NFSD: Use xdrgen XDR functions for NFSv2 GETATTR procedure Chuck Lever
` (21 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Hand-written XDR encoders and decoders are difficult to maintain
and can diverge from protocol specifications. Migrating to
xdrgen-generated code improves type safety and ensures the
implementation matches the NFS version 2 protocol specification
exactly.
Convert the NULL procedure to use xdrgen_svc_decode_void and
xdrgen_svc_encode_void, the built-ins declared in
include/linux/sunrpc/xdrgen/_builtins.h. NULL has no arguments or
results, so it is the first procedure converted.
NULL returns no XDR-encoded data, so pc_xdrressize is set to
XDR_void. The argzero field is also set to zero since xdrgen
decoders initialize all decoded values.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfsproc.c | 36 +++++++++++++++++++++++++-----------
1 file changed, 25 insertions(+), 11 deletions(-)
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index fd303d7dbad3..0a1c184217e0 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -57,8 +57,22 @@ static __be32 nfsd_map_io_status(__be32 status)
return nfsd_map_status(status);
}
-static __be32
-nfsd_proc_null(struct svc_rqst *rqstp)
+/*
+ * A full specification of each of the following NFSv2 procedures is
+ * available in RFC 1094 Section 2.2.
+ */
+
+/**
+ * nfsd_proc_null - NULL: Do nothing
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * void NFSPROC_NULL(void) = 0;
+ */
+static __be32 nfsd_proc_null(struct svc_rqst *rqstp)
{
return rpc_success;
}
@@ -673,15 +687,15 @@ nfsd_proc_statfs(struct svc_rqst *rqstp)
static const struct svc_procedure nfsd_procedures2[18] = {
[NFSPROC_NULL] = {
- .pc_func = nfsd_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 = 0,
- .pc_name = "NULL",
+ .pc_func = nfsd_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",
},
[NFSPROC_GETATTR] = {
.pc_func = nfsd_proc_getattr,
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 07/27] NFSD: Use xdrgen XDR functions for NFSv2 GETATTR procedure
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (7 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 06/27] NFSD: Use xdrgen XDR functions for the NFSv2 NULL procedure Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 08/27] NFSD: Use xdrgen XDR functions for NFSv2 SETATTR procedure Chuck Lever
` (20 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Replace the NFSPROC_GETATTR entry in the nfsd_procedures2 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_fhandle and nfs_svc_encode_attrstat. 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 NFSv2 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_fattr(), so the
reference to the file handle can be released directly by
nfsd_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.
The new nfsd_mode_to_ftype() utility no longer maps socket and
FIFO devices to non-standard values: NFSOCK=6 which is outside
the ftype enum, and NFCHR for FIFOs which is semantically wrong.
The Linux NFS client implementation does not seem to use or
depend on these non-standard values. The new utility function
returns NFNON for both, which is the RFC-compliant behavior.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
Documentation/sunrpc/xdr/nfs2.x | 1 +
fs/nfsd/nfs2xdr_gen.c | 34 +++---
fs/nfsd/nfs2xdr_gen.h | 2 +-
fs/nfsd/nfsproc.c | 170 ++++++++++++++++++++++++-----
include/linux/sunrpc/xdrgen/nfs2.h | 4 +-
5 files changed, 166 insertions(+), 45 deletions(-)
diff --git a/Documentation/sunrpc/xdr/nfs2.x b/Documentation/sunrpc/xdr/nfs2.x
index 5e6208d857f3..abec81cecbae 100644
--- a/Documentation/sunrpc/xdr/nfs2.x
+++ b/Documentation/sunrpc/xdr/nfs2.x
@@ -44,6 +44,7 @@ enum nfsstat {
NFSERR_STALE = 70,
NFSERR_WFLUSH = 99
};
+pragma big_endian nfsstat;
enum ftype {
NFNON = 0,
diff --git a/fs/nfsd/nfs2xdr_gen.c b/fs/nfsd/nfs2xdr_gen.c
index ae81d1731ac6..59bee2746650 100644
--- a/fs/nfsd/nfs2xdr_gen.c
+++ b/fs/nfsd/nfs2xdr_gen.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
// Generated by xdrgen. Manual edits will be lost.
// XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x
-// XDR specification modification time: Tue Sep 8 11:23:43 2026
+// XDR specification modification time: Tue Sep 8 11:24:48 2026
#include <linux/sunrpc/svc.h>
@@ -10,10 +10,12 @@
static bool __maybe_unused
xdrgen_decode_nfsstat(struct xdr_stream *xdr, nfsstat *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 NFS_OK:
@@ -38,7 +40,7 @@ xdrgen_decode_nfsstat(struct xdr_stream *xdr, nfsstat *ptr)
default:
return false;
}
- *ptr = val;
+ *ptr = raw;
return true;
}
@@ -157,7 +159,7 @@ xdrgen_decode_attrstat(struct xdr_stream *xdr, struct attrstat *ptr)
if (!xdrgen_decode_nfsstat(xdr, &ptr->status))
return false;
switch (ptr->status) {
- case NFS_OK:
+ case __constant_cpu_to_be32(NFS_OK):
if (!xdrgen_decode_fattr(xdr, &ptr->u.attributes))
return false;
break;
@@ -193,7 +195,7 @@ xdrgen_decode_diropres(struct xdr_stream *xdr, struct diropres *ptr)
if (!xdrgen_decode_nfsstat(xdr, &ptr->status))
return false;
switch (ptr->status) {
- case NFS_OK:
+ case __constant_cpu_to_be32(NFS_OK):
if (!xdrgen_decode_diropok(xdr, &ptr->u.diropok))
return false;
break;
@@ -219,7 +221,7 @@ xdrgen_decode_readlinkres(struct xdr_stream *xdr, struct readlinkres *ptr)
if (!xdrgen_decode_nfsstat(xdr, &ptr->status))
return false;
switch (ptr->status) {
- case NFS_OK:
+ case __constant_cpu_to_be32(NFS_OK):
if (!xdrgen_decode_path(xdr, &ptr->u.data))
return false;
break;
@@ -259,7 +261,7 @@ xdrgen_decode_readres(struct xdr_stream *xdr, struct readres *ptr)
if (!xdrgen_decode_nfsstat(xdr, &ptr->status))
return false;
switch (ptr->status) {
- case NFS_OK:
+ case __constant_cpu_to_be32(NFS_OK):
if (!xdrgen_decode_readresok(xdr, &ptr->u.readresok))
return false;
break;
@@ -374,7 +376,7 @@ xdrgen_decode_readdirres(struct xdr_stream *xdr, struct readdirres *ptr)
if (!xdrgen_decode_nfsstat(xdr, &ptr->status))
return false;
switch (ptr->status) {
- case NFS_OK:
+ case __constant_cpu_to_be32(NFS_OK):
if (!xdrgen_decode_readdirok(xdr, &ptr->u.readdirok))
return false;
break;
@@ -406,7 +408,7 @@ xdrgen_decode_statfsres(struct xdr_stream *xdr, struct statfsres *ptr)
if (!xdrgen_decode_nfsstat(xdr, &ptr->status))
return false;
switch (ptr->status) {
- case NFS_OK:
+ case __constant_cpu_to_be32(NFS_OK):
if (!xdrgen_decode_info(xdr, &ptr->u.info))
return false;
break;
@@ -579,7 +581,7 @@ bool nfs_svc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
static bool __maybe_unused
xdrgen_encode_nfsstat(struct xdr_stream *xdr, nfsstat value)
{
- return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
+ return xdr_stream_encode_be32(xdr, value) == XDR_UNIT;
}
static bool __maybe_unused
@@ -680,7 +682,7 @@ xdrgen_encode_attrstat(struct xdr_stream *xdr, const struct attrstat *ptr)
if (!xdrgen_encode_nfsstat(xdr, ptr->status))
return false;
switch (ptr->status) {
- case NFS_OK:
+ case __constant_cpu_to_be32(NFS_OK):
if (!xdrgen_encode_fattr(xdr, &ptr->u.attributes))
return false;
break;
@@ -716,7 +718,7 @@ xdrgen_encode_diropres(struct xdr_stream *xdr, const struct diropres *ptr)
if (!xdrgen_encode_nfsstat(xdr, ptr->status))
return false;
switch (ptr->status) {
- case NFS_OK:
+ case __constant_cpu_to_be32(NFS_OK):
if (!xdrgen_encode_diropok(xdr, &ptr->u.diropok))
return false;
break;
@@ -742,7 +744,7 @@ xdrgen_encode_readlinkres(struct xdr_stream *xdr, const struct readlinkres *ptr)
if (!xdrgen_encode_nfsstat(xdr, ptr->status))
return false;
switch (ptr->status) {
- case NFS_OK:
+ case __constant_cpu_to_be32(NFS_OK):
if (!xdrgen_encode_path(xdr, ptr->u.data))
return false;
break;
@@ -784,7 +786,7 @@ xdrgen_encode_readres(struct xdr_stream *xdr, const struct readres *ptr)
if (!xdrgen_encode_nfsstat(xdr, ptr->status))
return false;
switch (ptr->status) {
- case NFS_OK:
+ case __constant_cpu_to_be32(NFS_OK):
if (!xdrgen_encode_readresok(xdr, &ptr->u.readresok))
return false;
break;
@@ -899,7 +901,7 @@ xdrgen_encode_readdirres(struct xdr_stream *xdr, const struct readdirres *ptr)
if (!xdrgen_encode_nfsstat(xdr, ptr->status))
return false;
switch (ptr->status) {
- case NFS_OK:
+ case __constant_cpu_to_be32(NFS_OK):
if (!xdrgen_encode_readdirok(xdr, &ptr->u.readdirok))
return false;
break;
@@ -931,7 +933,7 @@ xdrgen_encode_statfsres(struct xdr_stream *xdr, const struct statfsres *ptr)
if (!xdrgen_encode_nfsstat(xdr, ptr->status))
return false;
switch (ptr->status) {
- case NFS_OK:
+ case __constant_cpu_to_be32(NFS_OK):
if (!xdrgen_encode_info(xdr, &ptr->u.info))
return false;
break;
diff --git a/fs/nfsd/nfs2xdr_gen.h b/fs/nfsd/nfs2xdr_gen.h
index df800e42b80e..150f4215dbe3 100644
--- a/fs/nfsd/nfs2xdr_gen.h
+++ b/fs/nfsd/nfs2xdr_gen.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Generated by xdrgen. Manual edits will be lost. */
/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x */
-/* XDR specification modification time: Tue Sep 8 11:23:43 2026 */
+/* XDR specification modification time: Tue Sep 8 11:24:48 2026 */
#ifndef _LINUX_XDRGEN_NFS2_DECL_H
#define _LINUX_XDRGEN_NFS2_DECL_H
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index 0a1c184217e0..0196a554de5e 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -9,6 +9,9 @@
#include <linux/sunrpc/svc.h>
#include "nfs2xdr_gen.h"
+
+#include "auth.h"
+#include "nfsd.h"
#include "cache.h"
#include "xdr.h"
#include "vfs.h"
@@ -17,6 +20,26 @@
#define NFSDDBG_FACILITY NFSDDBG_PROC
+/*
+ * 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 fhandle_wrapper {
+ fhandle xdrgen;
+ struct svc_fh fh;
+};
+
+static_assert(offsetof(struct fhandle_wrapper, xdrgen) == 0);
+
+struct attrstat_wrapper {
+ struct attrstat xdrgen;
+ struct kstat stat;
+};
+
+static_assert(offsetof(struct attrstat_wrapper, xdrgen) == 0);
+
static __be32 nfsd_map_status(__be32 status)
{
switch (status) {
@@ -42,6 +65,88 @@ static __be32 nfsd_map_status(__be32 status)
return status;
}
+static __always_inline void
+nfsd_fhandle_to_svc_fh(struct svc_fh *fhp, const fhandle *fhandle)
+{
+ fh_init(fhp, NFS_FHSIZE);
+ fhp->fh_handle.fh_size = NFS_FHSIZE;
+ memcpy(&fhp->fh_handle.fh_raw, fhandle, NFS_FHSIZE);
+}
+
+static __always_inline void
+nfsd_timespec64_to_timeval(struct timeval *dst,
+ const struct timespec64 *src)
+{
+ dst->seconds = src->tv_sec;
+ dst->useconds = src->tv_nsec / NSEC_PER_USEC;
+}
+
+static u32
+nfsd_mode_to_ftype(umode_t mode)
+{
+ switch (mode & S_IFMT) {
+ case S_IFREG: return NFREG;
+ case S_IFDIR: return NFDIR;
+ case S_IFBLK: return NFBLK;
+ case S_IFCHR: return NFCHR;
+ case S_IFLNK: return NFLNK;
+ /*
+ * The NFSv2 protocol's ftype enum does not provide specific values
+ * for sockets and FIFOs.
+ */
+ case S_IFSOCK: return NFNON;
+ case S_IFIFO: return NFNON;
+ }
+ return NFNON;
+}
+
+static void
+nfsd_stat_to_fattr(struct svc_rqst *rqstp, struct fattr *fattr,
+ const struct kstat *stat, const struct svc_fh *fhp)
+{
+ struct user_namespace *userns = nfsd_user_namespace(rqstp);
+ struct timespec64 time;
+ u32 fsid;
+
+ fattr->type = nfsd_mode_to_ftype(stat->mode);
+ fattr->mode = stat->mode;
+ 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 > NFS_MAXPATHLEN)
+ fattr->size = NFS_MAXPATHLEN;
+ else
+ fattr->size = stat->size;
+ fattr->blocksize = stat->blksize;
+ if (S_ISCHR(stat->mode) || S_ISBLK(stat->mode))
+ fattr->rdev = new_encode_dev(stat->rdev);
+ else
+ fattr->rdev = 0xffffffff;
+ fattr->blocks = stat->blocks;
+
+ switch (fsid_source(fhp)) {
+ case FSIDSOURCE_FSID:
+ fsid = (u32)fhp->fh_export->ex_fsid;
+ break;
+ case FSIDSOURCE_UUID:
+ fsid = ((u32 *)fhp->fh_export->ex_uuid)[0];
+ fsid ^= ((u32 *)fhp->fh_export->ex_uuid)[1];
+ fsid ^= ((u32 *)fhp->fh_export->ex_uuid)[2];
+ fsid ^= ((u32 *)fhp->fh_export->ex_uuid)[3];
+ break;
+ default:
+ fsid = new_encode_dev(stat->dev);
+ }
+ fattr->fsid = fsid;
+ fattr->fileid = stat->ino;
+
+ nfsd_timespec64_to_timeval(&fattr->atime, &stat->atime);
+ time = stat->mtime;
+ lease_get_mtime(d_inode(fhp->fh_dentry), &time);
+ nfsd_timespec64_to_timeval(&fattr->mtime, &time);
+ nfsd_timespec64_to_timeval(&fattr->ctime, &stat->ctime);
+}
+
/*
* Because NFSv2 does not have an NFSERR_SYMLINK, Solaris returns
* NFSERR_ISDIR when the target of a READ or WRITE is any object
@@ -77,26 +182,39 @@ static __be32 nfsd_proc_null(struct svc_rqst *rqstp)
return rpc_success;
}
-/*
- * Get a file's attributes
- * N.B. After this call resp->fh needs an fh_put
+/**
+ * nfsd_proc_getattr - GETATTR: Get file attributes
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * attrstat NFSPROC_GETATTR(fhandle) = 1;
*/
-static __be32
-nfsd_proc_getattr(struct svc_rqst *rqstp)
+static __be32 nfsd_proc_getattr(struct svc_rqst *rqstp)
{
- struct nfsd_fhandle *argp = rqstp->rq_argp;
- struct nfsd_attrstat *resp = rqstp->rq_resp;
+ struct fhandle_wrapper *argp = rqstp->rq_argp;
+ struct attrstat_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)
+ nfsd_fhandle_to_svc_fh(fhp, &argp->xdrgen);
+ 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 = nfsd_map_status(resp->status);
+ if (resp->xdrgen.status == nfs_ok)
+ nfsd_stat_to_fattr(rqstp, &resp->xdrgen.u.attributes, statp, fhp);
+ else
+ resp->xdrgen.status = nfsd_map_status(resp->xdrgen.status);
+
+ fh_put(fhp);
return rpc_success;
}
@@ -698,16 +816,15 @@ static const struct svc_procedure nfsd_procedures2[18] = {
.pc_name = "NULL",
},
[NFSPROC_GETATTR] = {
- .pc_func = nfsd_proc_getattr,
- .pc_decode = nfssvc_decode_fhandleargs,
- .pc_encode = nfssvc_encode_attrstatres,
- .pc_release = nfssvc_release_attrstat,
- .pc_argsize = sizeof(struct nfsd_fhandle),
- .pc_argzero = sizeof(struct nfsd_fhandle),
- .pc_ressize = sizeof(struct nfsd_attrstat),
- .pc_cachetype = RC_NOCACHE,
- .pc_xdrressize = ST+AT,
- .pc_name = "GETATTR",
+ .pc_func = nfsd_proc_getattr,
+ .pc_decode = nfs_svc_decode_fhandle,
+ .pc_encode = nfs_svc_encode_attrstat,
+ .pc_argsize = sizeof(struct fhandle_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct attrstat_wrapper),
+ .pc_cachetype = RC_NOCACHE,
+ .pc_xdrressize = NFS2_attrstat_sz,
+ .pc_name = "GETATTR",
},
[NFSPROC_SETATTR] = {
.pc_func = nfsd_proc_setattr,
@@ -896,6 +1013,7 @@ static const struct svc_procedure nfsd_procedures2[18] = {
* Storage requirements for XDR arguments and results.
*/
union nfsd_xdrstore {
+ struct fhandle_wrapper fhandle;
struct nfsd_sattrargs sattr;
struct nfsd_diropargs dirop;
struct nfsd_readargs read;
@@ -905,7 +1023,7 @@ union nfsd_xdrstore {
struct nfsd_linkargs link;
struct nfsd_symlinkargs symlink;
struct nfsd_readdirargs readdir;
- struct nfsd_attrstat attrstat;
+ struct attrstat_wrapper attrstat;
struct nfsd_diropres diropres;
struct nfsd_readlinkres readlinkres;
struct nfsd_readres readres;
diff --git a/include/linux/sunrpc/xdrgen/nfs2.h b/include/linux/sunrpc/xdrgen/nfs2.h
index 4a022b670b0c..7e3af2ad90a7 100644
--- a/include/linux/sunrpc/xdrgen/nfs2.h
+++ b/include/linux/sunrpc/xdrgen/nfs2.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Generated by xdrgen. Manual edits will be lost. */
/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x */
-/* XDR specification modification time: Tue Sep 8 11:23:43 2026 */
+/* XDR specification modification time: Tue Sep 8 11:24:48 2026 */
#ifndef _LINUX_XDRGEN_NFS2_DEF_H
#define _LINUX_XDRGEN_NFS2_DEF_H
@@ -40,7 +40,7 @@ enum nfsstat {
NFSERR_WFLUSH = 99,
};
-typedef enum nfsstat nfsstat;
+typedef __be32 nfsstat;
enum ftype {
NFNON = 0,
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 08/27] NFSD: Use xdrgen XDR functions for NFSv2 SETATTR procedure
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (8 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 07/27] NFSD: Use xdrgen XDR functions for NFSv2 GETATTR procedure Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 09/27] NFSD: Use xdrgen XDR functions for the NFSv2 ROOT procedure Chuck Lever
` (19 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Replace the NFSPROC_SETATTR entry in the nfsd_procedures2 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_sattrargs and nfs_svc_encode_attrstat. Wrapper
structures bridge the generated xdrgen types and the legacy
svc_fh, iattr, and kstat representations the NFSD VFS layer still
uses. The result reuses the attrstat wrapper from the GETATTR
conversion; the new nfsd_sattr_to_iattr() and
nfsd_timeval_to_timespec64() helpers translate the decoded sattr
into the iattr that nfsd_setattr() consumes.
The hand-coded svcxdr_decode_sattr() rejects a time-useconds value
greater than one second before converting it, preventing the
microsecond-to-nanosecond multiplication from wrapping to a valid
but incorrect value on 32-bit platforms. nfsd_sattr_to_iattr()
preserves that range check. Because it runs after the generated
decoder rather than inside it, an out-of-range value fails the
procedure with nfserr_io rather than the RPC GARBAGE_ARGS the
hand-coded decoder returned.
The pc_argzero field is now set to zero for the NFSv2 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. The wrapper's iattr is one of them: nfsd_sattr_to_iattr()
zeroes the whole structure rather than only ia_valid, because the
nfsd_vfs_setattr tracepoint records ia_size, ia_uid, and ia_gid
whether or not their ATTR bits are set.
This refactor replaces the use of svcxdr_encode_fattr(), so the
reference to the file handle can be released directly by
nfsd_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.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfsproc.c | 165 ++++++++++++++++++++++++++++++++++++----------
fs/nfsd/nfsxdr.c | 9 ---
fs/nfsd/xdr.h | 6 --
3 files changed, 131 insertions(+), 49 deletions(-)
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index 0196a554de5e..7fb5895f24d5 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -40,6 +40,14 @@ struct attrstat_wrapper {
static_assert(offsetof(struct attrstat_wrapper, xdrgen) == 0);
+struct sattrargs_wrapper {
+ struct sattrargs xdrgen;
+ struct svc_fh fh;
+ struct iattr iattrs;
+};
+
+static_assert(offsetof(struct sattrargs_wrapper, xdrgen) == 0);
+
static __be32 nfsd_map_status(__be32 status)
{
switch (status) {
@@ -81,6 +89,13 @@ nfsd_timespec64_to_timeval(struct timeval *dst,
dst->useconds = src->tv_nsec / NSEC_PER_USEC;
}
+static __always_inline void
+nfsd_timeval_to_timespec64(struct timespec64 *dst, const struct timeval *src)
+{
+ dst->tv_sec = src->seconds;
+ dst->tv_nsec = src->useconds * NSEC_PER_USEC;
+}
+
static u32
nfsd_mode_to_ftype(umode_t mode)
{
@@ -162,6 +177,76 @@ static __be32 nfsd_map_io_status(__be32 status)
return nfsd_map_status(status);
}
+/*
+ * Sun convention: a sattr time-useconds field of one full second (an
+ * otherwise out-of-range value) means "set this time to the current
+ * server time." It's needed to make permissions checks for the "touch"
+ * program across NFSv2 mounts work correctly. See description of
+ * sattr in section 6.1 of "NFS Illustrated" by Brent Callaghan,
+ * Addison-Wesley, ISBN 0-201-32750-5
+ */
+#define NFS2_SATTR_SET_TO_SERVER_TIME (1000000)
+
+static bool
+nfsd_sattr_to_iattr(struct svc_rqst *rqstp, struct iattr *iap,
+ const struct sattr *sattr)
+{
+ static const unsigned int dont_set_it = (unsigned int)-1;
+
+ /* Consumers read ia_size, ia_uid, and ia_gid unguarded by ia_valid */
+ memset(iap, 0, sizeof(*iap));
+
+ /*
+ * Some Sun NFS clients put 0xffff in the mode field when they
+ * mean 0xffffffff.
+ */
+ if (sattr->mode != dont_set_it && sattr->mode != 0xffff) {
+ iap->ia_valid |= ATTR_MODE;
+ iap->ia_mode = sattr->mode;
+ }
+ if (sattr->uid != dont_set_it) {
+ iap->ia_uid = make_kuid(nfsd_user_namespace(rqstp), sattr->uid);
+ if (uid_valid(iap->ia_uid))
+ iap->ia_valid |= ATTR_UID;
+ }
+ if (sattr->gid != dont_set_it) {
+ iap->ia_gid = make_kgid(nfsd_user_namespace(rqstp), sattr->gid);
+ if (gid_valid(iap->ia_gid))
+ iap->ia_valid |= ATTR_GID;
+ }
+ if (sattr->size != dont_set_it) {
+ iap->ia_valid |= ATTR_SIZE;
+ iap->ia_size = sattr->size;
+ }
+ if (sattr->atime.seconds != dont_set_it &&
+ sattr->atime.useconds != dont_set_it) {
+ /*
+ * Reject out-of-range useconds so the conversion to
+ * nanoseconds cannot wrap to a valid but incorrect
+ * value on 32-bit platforms.
+ */
+ if (sattr->atime.useconds > NFS2_SATTR_SET_TO_SERVER_TIME)
+ return false;
+ iap->ia_valid |= ATTR_ATIME | ATTR_ATIME_SET;
+ nfsd_timeval_to_timespec64(&iap->ia_atime, &sattr->atime);
+
+ if (sattr->atime.useconds == NFS2_SATTR_SET_TO_SERVER_TIME)
+ iap->ia_valid &= ~ATTR_ATIME_SET;
+ }
+ if (sattr->mtime.seconds != dont_set_it &&
+ sattr->mtime.useconds != dont_set_it) {
+ if (sattr->mtime.useconds > NFS2_SATTR_SET_TO_SERVER_TIME)
+ return false;
+ iap->ia_valid |= ATTR_MTIME | ATTR_MTIME_SET;
+ nfsd_timeval_to_timespec64(&iap->ia_mtime, &sattr->mtime);
+
+ if (sattr->mtime.useconds == NFS2_SATTR_SET_TO_SERVER_TIME)
+ iap->ia_valid &= ~(ATTR_ATIME_SET | ATTR_MTIME_SET);
+ }
+
+ return true;
+}
+
/*
* A full specification of each of the following NFSv2 procedures is
* available in RFC 1094 Section 2.2.
@@ -218,27 +303,32 @@ static __be32 nfsd_proc_getattr(struct svc_rqst *rqstp)
return rpc_success;
}
-/*
- * Set a file's attributes
- * N.B. After this call resp->fh needs an fh_put
+/**
+ * nfsd_proc_setattr - SETATTR: Set file attributes
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * attrstat NFSPROC_SETATTR(sattrargs) = 2;
*/
-static __be32
-nfsd_proc_setattr(struct svc_rqst *rqstp)
+static __be32 nfsd_proc_setattr(struct svc_rqst *rqstp)
{
- struct nfsd_sattrargs *argp = rqstp->rq_argp;
- struct nfsd_attrstat *resp = rqstp->rq_resp;
- struct iattr *iap = &argp->attrs;
- struct nfsd_attrs attrs = {
+ struct sattrargs_wrapper *argp = rqstp->rq_argp;
+ struct attrstat_wrapper *resp = rqstp->rq_resp;
+ struct kstat *statp = &resp->stat;
+ struct iattr *iap = &argp->iattrs;
+ struct nfsd_attrs nattrs = {
.na_iattr = iap,
};
- struct svc_fh *fhp;
- int hosterr;
+ struct svc_fh *fhp = &argp->fh;
- dprintk("nfsd: SETATTR %s, valid=%x, size=%ld\n",
- SVCFH_fmt(&argp->fh),
- argp->attrs.ia_valid, (long) argp->attrs.ia_size);
-
- fhp = fh_copy(&resp->fh, &argp->fh);
+ nfsd_fhandle_to_svc_fh(fhp, &argp->xdrgen.file);
+ if (!nfsd_sattr_to_iattr(rqstp, iap, &argp->xdrgen.attributes)) {
+ resp->xdrgen.status = nfserr_io;
+ goto out;
+ }
/*
* NFSv2 does not differentiate between "set-[ac]time-to-now"
@@ -255,6 +345,8 @@ nfsd_proc_setattr(struct svc_rqst *rqstp)
#define MAX_TOUCH_TIME_ERROR (30*60)
if ((iap->ia_valid & BOTH_TIME_SET) == BOTH_TIME_SET &&
iap->ia_mtime.tv_sec == iap->ia_atime.tv_sec) {
+ int hosterr;
+
/*
* Looks probable.
*
@@ -264,13 +356,13 @@ nfsd_proc_setattr(struct svc_rqst *rqstp)
*/
time64_t delta = iap->ia_atime.tv_sec - ktime_get_real_seconds();
- resp->status = fh_verify(rqstp, fhp, 0, NFSD_MAY_NOP);
- if (resp->status != nfs_ok)
+ resp->xdrgen.status = fh_verify(rqstp, fhp, 0, NFSD_MAY_NOP);
+ if (resp->xdrgen.status != nfs_ok)
goto out;
hosterr = fh_want_write(fhp);
if (hosterr) {
- resp->status = nfserrno(hosterr);
+ resp->xdrgen.status = nfserrno(hosterr);
goto out;
}
@@ -287,13 +379,19 @@ nfsd_proc_setattr(struct svc_rqst *rqstp)
}
}
- resp->status = nfsd_setattr(rqstp, fhp, &attrs, NULL);
- if (resp->status != nfs_ok)
+ resp->xdrgen.status = nfsd_setattr(rqstp, fhp, &nattrs, NULL);
+ 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 = nfsd_map_status(resp->status);
+ if (resp->xdrgen.status == nfs_ok)
+ nfsd_stat_to_fattr(rqstp, &resp->xdrgen.u.attributes, statp, fhp);
+ else
+ resp->xdrgen.status = nfsd_map_status(resp->xdrgen.status);
+
+ fh_put(fhp);
return rpc_success;
}
@@ -827,16 +925,15 @@ static const struct svc_procedure nfsd_procedures2[18] = {
.pc_name = "GETATTR",
},
[NFSPROC_SETATTR] = {
- .pc_func = nfsd_proc_setattr,
- .pc_decode = nfssvc_decode_sattrargs,
- .pc_encode = nfssvc_encode_attrstatres,
- .pc_release = nfssvc_release_attrstat,
- .pc_argsize = sizeof(struct nfsd_sattrargs),
- .pc_argzero = sizeof(struct nfsd_sattrargs),
- .pc_ressize = sizeof(struct nfsd_attrstat),
- .pc_cachetype = RC_REPLBUFF,
- .pc_xdrressize = ST+AT,
- .pc_name = "SETATTR",
+ .pc_func = nfsd_proc_setattr,
+ .pc_decode = nfs_svc_decode_sattrargs,
+ .pc_encode = nfs_svc_encode_attrstat,
+ .pc_argsize = sizeof(struct sattrargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct attrstat_wrapper),
+ .pc_cachetype = RC_REPLBUFF,
+ .pc_xdrressize = NFS2_attrstat_sz,
+ .pc_name = "SETATTR",
},
[NFSPROC_ROOT] = {
.pc_func = nfsd_proc_root,
@@ -1014,7 +1111,7 @@ static const struct svc_procedure nfsd_procedures2[18] = {
*/
union nfsd_xdrstore {
struct fhandle_wrapper fhandle;
- struct nfsd_sattrargs sattr;
+ struct sattrargs_wrapper sattrargs;
struct nfsd_diropargs dirop;
struct nfsd_readargs read;
struct nfsd_writeargs write;
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
index d55e7d6414a0..174e6f5622f9 100644
--- a/fs/nfsd/nfsxdr.c
+++ b/fs/nfsd/nfsxdr.c
@@ -302,15 +302,6 @@ nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
return svcxdr_decode_fhandle(xdr, &args->fh);
}
-bool
-nfssvc_decode_sattrargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd_sattrargs *args = rqstp->rq_argp;
-
- return svcxdr_decode_fhandle(xdr, &args->fh) &&
- svcxdr_decode_sattr(rqstp, xdr, &args->attrs);
-}
-
bool
nfssvc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
diff --git a/fs/nfsd/xdr.h b/fs/nfsd/xdr.h
index 05b3a18c879a..9983fe212f3a 100644
--- a/fs/nfsd/xdr.h
+++ b/fs/nfsd/xdr.h
@@ -8,11 +8,6 @@
#include "vfs.h"
-struct nfsd_sattrargs {
- struct svc_fh fh;
- struct iattr attrs;
-};
-
struct nfsd_diropargs {
struct svc_fh fh;
char * name;
@@ -120,7 +115,6 @@ struct nfsd_statfsres {
};
bool nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfssvc_decode_sattrargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_readargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_writeargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 09/27] NFSD: Use xdrgen XDR functions for the NFSv2 ROOT procedure
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (9 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 08/27] NFSD: Use xdrgen XDR functions for NFSv2 SETATTR procedure Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 10/27] NFSD: Use xdrgen XDR functions for the NFSv2 LOOKUP procedure Chuck Lever
` (18 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Hand-written XDR encoders and decoders are difficult to
maintain and can diverge from protocol specifications.
Migrating to xdrgen-generated code improves type safety and
ensures the implementation matches the NFS version 2 protocol
specification exactly.
Convert the ROOT procedure to use xdrgen_svc_decode_void and
xdrgen_svc_encode_void, the built-ins declared in
include/linux/sunrpc/xdrgen/_builtins.h. ROOT is obsolete and has
neither arguments nor results, so its entry mirrors the NULL
procedure.
ROOT returns no XDR-encoded data, so pc_xdrressize is set to
XDR_void. The argzero field is also set to zero since xdrgen
decoders initialize all decoded values.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfsproc.c | 34 ++++++++++++++++++++++------------
1 file changed, 22 insertions(+), 12 deletions(-)
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index 7fb5895f24d5..c0f8c6988925 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -395,9 +395,19 @@ static __be32 nfsd_proc_setattr(struct svc_rqst *rqstp)
return rpc_success;
}
-/* Obsolete, replaced by MNTPROC_MNT. */
-static __be32
-nfsd_proc_root(struct svc_rqst *rqstp)
+/**
+ * nfsd_proc_root - ROOT: Get filesystem root
+ * @rqstp: RPC transaction context
+ *
+ * Obsolete, replaced by MNTPROC_MNT.
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * void NFSPROC_ROOT(void) = 3;
+ */
+static __be32 nfsd_proc_root(struct svc_rqst *rqstp)
{
return rpc_success;
}
@@ -936,15 +946,15 @@ static const struct svc_procedure nfsd_procedures2[18] = {
.pc_name = "SETATTR",
},
[NFSPROC_ROOT] = {
- .pc_func = nfsd_proc_root,
- .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 = 0,
- .pc_name = "ROOT",
+ .pc_func = nfsd_proc_root,
+ .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 = "ROOT",
},
[NFSPROC_LOOKUP] = {
.pc_func = nfsd_proc_lookup,
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 10/27] NFSD: Use xdrgen XDR functions for the NFSv2 LOOKUP procedure
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (10 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 09/27] NFSD: Use xdrgen XDR functions for the NFSv2 ROOT procedure Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 11/27] NFSD: Use xdrgen XDR functions for NFSv2 READLINK procedure Chuck Lever
` (17 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Replace the NFSPROC_LOOKUP entry in the nfsd_procedures2 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_diropargs and nfs_svc_encode_diropres. Wrapper
structures bridge the generated xdrgen types and the legacy
svc_fh and kstat representations the NFSD VFS layer still uses.
The pc_argzero field is now set to zero for the NFSv2 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.
This refactor replaces the use of svcxdr_encode_fattr(), so the
references to the directory and object file handles can be
released directly by nfsd_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.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfsproc.c | 95 ++++++++++++++++++++++++++++++++---------------
1 file changed, 65 insertions(+), 30 deletions(-)
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index c0f8c6988925..ce52939b3ae7 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -48,6 +48,21 @@ struct sattrargs_wrapper {
static_assert(offsetof(struct sattrargs_wrapper, xdrgen) == 0);
+struct diropargs_wrapper {
+ struct diropargs xdrgen;
+ struct svc_fh fh;
+};
+
+static_assert(offsetof(struct diropargs_wrapper, xdrgen) == 0);
+
+struct diropres_wrapper {
+ struct diropres xdrgen;
+ struct svc_fh fh;
+ struct kstat stat;
+};
+
+static_assert(offsetof(struct diropres_wrapper, xdrgen) == 0);
+
static __be32 nfsd_map_status(__be32 status)
{
switch (status) {
@@ -81,6 +96,12 @@ nfsd_fhandle_to_svc_fh(struct svc_fh *fhp, const fhandle *fhandle)
memcpy(&fhp->fh_handle.fh_raw, fhandle, NFS_FHSIZE);
}
+static __always_inline void
+nfsd_svc_fh_to_fhandle(fhandle *fhandle, const struct svc_fh *fhp)
+{
+ memcpy(fhandle, fhp->fh_handle.fh_raw, NFS_FHSIZE);
+}
+
static __always_inline void
nfsd_timespec64_to_timeval(struct timeval *dst,
const struct timespec64 *src)
@@ -412,31 +433,46 @@ static __be32 nfsd_proc_root(struct svc_rqst *rqstp)
return rpc_success;
}
-/*
- * Look up a path name component
- * Note: the dentry in the resp->fh may be negative if the file
- * doesn't exist yet.
- * N.B. After this call resp->fh needs an fh_put
+/**
+ * nfsd_proc_lookup - LOOKUP: Look up file name
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * diropres NFSPROC_LOOKUP(diropargs) = 4;
*/
-static __be32
-nfsd_proc_lookup(struct svc_rqst *rqstp)
+static __be32 nfsd_proc_lookup(struct svc_rqst *rqstp)
{
- struct nfsd_diropargs *argp = rqstp->rq_argp;
- struct nfsd_diropres *resp = rqstp->rq_resp;
+ struct diropargs_wrapper *argp = rqstp->rq_argp;
+ struct diropres_wrapper *resp = rqstp->rq_resp;
+ struct diropargs *what = &argp->xdrgen;
+ struct kstat *statp = &resp->stat;
+ struct svc_fh *dirfhp = &argp->fh;
+ struct svc_fh *fhp = &resp->fh;
- dprintk("nfsd: LOOKUP %s %.*s\n",
- SVCFH_fmt(&argp->fh), argp->len, argp->name);
+ nfsd_fhandle_to_svc_fh(dirfhp, &what->dir);
- fh_init(&resp->fh, NFS_FHSIZE);
- resp->status = nfsd_lookup(rqstp, &argp->fh, argp->name, argp->len,
- &resp->fh);
- fh_put(&argp->fh);
- if (resp->status != nfs_ok)
+ fh_init(fhp, NFS_FHSIZE);
+ resp->xdrgen.status = nfsd_lookup(rqstp, dirfhp,
+ (char *)what->name.data,
+ what->name.len, fhp);
+ if (resp->xdrgen.status != nfs_ok)
goto out;
+ resp->xdrgen.status = fh_getattr(fhp, statp);
- resp->status = fh_getattr(&resp->fh, &resp->stat);
out:
- resp->status = nfsd_map_status(resp->status);
+ if (resp->xdrgen.status == nfs_ok) {
+ nfsd_svc_fh_to_fhandle(&resp->xdrgen.u.diropok.file, fhp);
+ nfsd_stat_to_fattr(rqstp, &resp->xdrgen.u.diropok.attributes,
+ statp, fhp);
+ } else {
+ resp->xdrgen.status = nfsd_map_status(resp->xdrgen.status);
+ }
+
+ fh_put(fhp);
+ fh_put(dirfhp);
return rpc_success;
}
@@ -957,16 +993,15 @@ static const struct svc_procedure nfsd_procedures2[18] = {
.pc_name = "ROOT",
},
[NFSPROC_LOOKUP] = {
- .pc_func = nfsd_proc_lookup,
- .pc_decode = nfssvc_decode_diropargs,
- .pc_encode = nfssvc_encode_diropres,
- .pc_release = nfssvc_release_diropres,
- .pc_argsize = sizeof(struct nfsd_diropargs),
- .pc_argzero = sizeof(struct nfsd_diropargs),
- .pc_ressize = sizeof(struct nfsd_diropres),
- .pc_cachetype = RC_NOCACHE,
- .pc_xdrressize = ST+FH+AT,
- .pc_name = "LOOKUP",
+ .pc_func = nfsd_proc_lookup,
+ .pc_decode = nfs_svc_decode_diropargs,
+ .pc_encode = nfs_svc_encode_diropres,
+ .pc_argsize = sizeof(struct diropargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct diropres_wrapper),
+ .pc_cachetype = RC_NOCACHE,
+ .pc_xdrressize = NFS2_diropres_sz,
+ .pc_name = "LOOKUP",
},
[NFSPROC_READLINK] = {
.pc_func = nfsd_proc_readlink,
@@ -1122,7 +1157,7 @@ static const struct svc_procedure nfsd_procedures2[18] = {
union nfsd_xdrstore {
struct fhandle_wrapper fhandle;
struct sattrargs_wrapper sattrargs;
- struct nfsd_diropargs dirop;
+ struct diropargs_wrapper diropargs;
struct nfsd_readargs read;
struct nfsd_writeargs write;
struct nfsd_createargs create;
@@ -1131,7 +1166,7 @@ union nfsd_xdrstore {
struct nfsd_symlinkargs symlink;
struct nfsd_readdirargs readdir;
struct attrstat_wrapper attrstat;
- struct nfsd_diropres diropres;
+ struct diropres_wrapper diropres;
struct nfsd_readlinkres readlinkres;
struct nfsd_readres readres;
struct nfsd_readdirres readdirres;
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 11/27] NFSD: Use xdrgen XDR functions for NFSv2 READLINK procedure
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (11 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 10/27] NFSD: Use xdrgen XDR functions for the NFSv2 LOOKUP procedure Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 12/27] NFSD: Use xdrgen XDR functions for NFSv2 READ procedure Chuck Lever
` (16 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Replace the hand-written NFSv2 READLINK codec pair with the
xdrgen-generated functions. The NFSPROC_READLINK entry in the
nfsd_procedures2 array now dispatches nfs_svc_decode_fhandle and
nfs_svc_encode_readlinkres. 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
readlinkres union arm: 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
arm 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 fhandle argument decoder
consumes no pages -- so the helper locates the payload through the
Reply buffer itself, and the wrapper collapses to the generated
readlinkres type.
The pc_argzero field is now set to zero for the NFSv2 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.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
Documentation/sunrpc/xdr/nfs2.x | 1 +
fs/nfsd/nfs2xdr_gen.c | 6 ++--
fs/nfsd/nfs2xdr_gen.h | 2 +-
fs/nfsd/nfsproc.c | 58 +++++++++++++++++-------------
fs/nfsd/nfsxdr.c | 22 ------------
fs/nfsd/xdr.h | 7 ----
include/linux/sunrpc/xdrgen/nfs2.h | 2 +-
7 files changed, 41 insertions(+), 57 deletions(-)
diff --git a/Documentation/sunrpc/xdr/nfs2.x b/Documentation/sunrpc/xdr/nfs2.x
index abec81cecbae..771acc4baa17 100644
--- a/Documentation/sunrpc/xdr/nfs2.x
+++ b/Documentation/sunrpc/xdr/nfs2.x
@@ -127,6 +127,7 @@ union readlinkres switch (nfsstat status) {
default:
void;
};
+pragma pages readlinkres data;
struct readargs {
fhandle file;
diff --git a/fs/nfsd/nfs2xdr_gen.c b/fs/nfsd/nfs2xdr_gen.c
index 59bee2746650..e97df0eda0e1 100644
--- a/fs/nfsd/nfs2xdr_gen.c
+++ b/fs/nfsd/nfs2xdr_gen.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
// Generated by xdrgen. Manual edits will be lost.
// XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x
-// XDR specification modification time: Tue Sep 8 11:24:48 2026
+// XDR specification modification time: Tue Sep 8 11:25:27 2026
#include <linux/sunrpc/svc.h>
@@ -745,7 +745,9 @@ xdrgen_encode_readlinkres(struct xdr_stream *xdr, const struct readlinkres *ptr)
return false;
switch (ptr->status) {
case __constant_cpu_to_be32(NFS_OK):
- if (!xdrgen_encode_path(xdr, ptr->u.data))
+ if (ptr->u.data.len > NFS_MAXPATHLEN)
+ return false;
+ if (!svcxdr_encode_opaque_payload(xdr, ptr->u.data.len))
return false;
break;
default:
diff --git a/fs/nfsd/nfs2xdr_gen.h b/fs/nfsd/nfs2xdr_gen.h
index 150f4215dbe3..92d5f9a8f5dd 100644
--- a/fs/nfsd/nfs2xdr_gen.h
+++ b/fs/nfsd/nfs2xdr_gen.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Generated by xdrgen. Manual edits will be lost. */
/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x */
-/* XDR specification modification time: Tue Sep 8 11:24:48 2026 */
+/* XDR specification modification time: Tue Sep 8 11:25:27 2026 */
#ifndef _LINUX_XDRGEN_NFS2_DECL_H
#define _LINUX_XDRGEN_NFS2_DECL_H
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index ce52939b3ae7..7bc89629934b 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -476,25 +476,35 @@ static __be32 nfsd_proc_lookup(struct svc_rqst *rqstp)
return rpc_success;
}
-/*
- * Read a symlink.
+/**
+ * nfsd_proc_readlink - READLINK: Read symbolic link
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * readlinkres NFSPROC_READLINK(fhandle) = 5;
*/
-static __be32
-nfsd_proc_readlink(struct svc_rqst *rqstp)
+static __be32 nfsd_proc_readlink(struct svc_rqst *rqstp)
{
- struct nfsd_fhandle *argp = rqstp->rq_argp;
- struct nfsd_readlinkres *resp = rqstp->rq_resp;
+ struct fhandle_wrapper *argp = rqstp->rq_argp;
+ struct readlinkres *resp = rqstp->rq_resp;
+ struct svc_fh *fhp = &argp->fh;
+ struct page *page;
+ u32 len;
- dprintk("nfsd: READLINK %s\n", SVCFH_fmt(&argp->fh));
+ nfsd_fhandle_to_svc_fh(fhp, &argp->xdrgen);
- /* Read the symlink. */
- resp->len = NFS_MAXPATHLEN;
- resp->page = *(rqstp->rq_next_page++);
- resp->status = nfsd_readlink(rqstp, &argp->fh,
- page_address(resp->page), &resp->len);
+ len = NFS_MAXPATHLEN;
+ page = *(rqstp->rq_next_page++);
+ resp->status = nfsd_readlink(rqstp, fhp, page_address(page), &len);
+ if (resp->status == nfs_ok)
+ resp->u.data.len = len;
+ else
+ resp->status = nfsd_map_status(resp->status);
- fh_put(&argp->fh);
- resp->status = nfsd_map_status(resp->status);
+ fh_put(fhp);
return rpc_success;
}
@@ -1004,15 +1014,15 @@ static const struct svc_procedure nfsd_procedures2[18] = {
.pc_name = "LOOKUP",
},
[NFSPROC_READLINK] = {
- .pc_func = nfsd_proc_readlink,
- .pc_decode = nfssvc_decode_fhandleargs,
- .pc_encode = nfssvc_encode_readlinkres,
- .pc_argsize = sizeof(struct nfsd_fhandle),
- .pc_argzero = sizeof(struct nfsd_fhandle),
- .pc_ressize = sizeof(struct nfsd_readlinkres),
- .pc_cachetype = RC_NOCACHE,
- .pc_xdrressize = ST+1+NFS_MAXPATHLEN/4,
- .pc_name = "READLINK",
+ .pc_func = nfsd_proc_readlink,
+ .pc_decode = nfs_svc_decode_fhandle,
+ .pc_encode = nfs_svc_encode_readlinkres,
+ .pc_argsize = sizeof(struct fhandle_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct readlinkres),
+ .pc_cachetype = RC_NOCACHE,
+ .pc_xdrressize = NFS2_readlinkres_sz,
+ .pc_name = "READLINK",
},
[NFSPROC_READ] = {
.pc_func = nfsd_proc_read,
@@ -1167,7 +1177,7 @@ union nfsd_xdrstore {
struct nfsd_readdirargs readdir;
struct attrstat_wrapper attrstat;
struct diropres_wrapper diropres;
- struct nfsd_readlinkres readlinkres;
+ struct readlinkres readlinkres;
struct nfsd_readres readres;
struct nfsd_readdirres readdirres;
struct nfsd_statfsres statfsres;
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
index 174e6f5622f9..ed5ec00e8557 100644
--- a/fs/nfsd/nfsxdr.c
+++ b/fs/nfsd/nfsxdr.c
@@ -469,28 +469,6 @@ nfssvc_encode_diropres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
return true;
}
-bool
-nfssvc_encode_readlinkres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd_readlinkres *resp = rqstp->rq_resp;
- struct kvec *head = rqstp->rq_res.head;
-
- if (!svcxdr_encode_stat(xdr, resp->status))
- return false;
- switch (resp->status) {
- case nfs_ok:
- if (xdr_stream_encode_u32(xdr, resp->len) < 0)
- return false;
- svcxdr_encode_opaque_pages(rqstp, xdr, &resp->page, 0,
- resp->len);
- if (svc_encode_result_payload(rqstp, head->iov_len, resp->len) < 0)
- return false;
- break;
- }
-
- return true;
-}
-
bool
nfssvc_encode_readres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
diff --git a/fs/nfsd/xdr.h b/fs/nfsd/xdr.h
index 9983fe212f3a..4d06c415a867 100644
--- a/fs/nfsd/xdr.h
+++ b/fs/nfsd/xdr.h
@@ -82,12 +82,6 @@ struct nfsd_diropres {
struct kstat stat;
};
-struct nfsd_readlinkres {
- __be32 status;
- int len;
- struct page *page;
-};
-
struct nfsd_readres {
__be32 status;
struct svc_fh fh;
@@ -127,7 +121,6 @@ bool nfssvc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_statres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_attrstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_diropres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfssvc_encode_readlinkres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_readres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_statfsres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
diff --git a/include/linux/sunrpc/xdrgen/nfs2.h b/include/linux/sunrpc/xdrgen/nfs2.h
index 7e3af2ad90a7..fbda12101f87 100644
--- a/include/linux/sunrpc/xdrgen/nfs2.h
+++ b/include/linux/sunrpc/xdrgen/nfs2.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Generated by xdrgen. Manual edits will be lost. */
/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x */
-/* XDR specification modification time: Tue Sep 8 11:24:48 2026 */
+/* XDR specification modification time: Tue Sep 8 11:25:27 2026 */
#ifndef _LINUX_XDRGEN_NFS2_DEF_H
#define _LINUX_XDRGEN_NFS2_DEF_H
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 12/27] NFSD: Use xdrgen XDR functions for NFSv2 READ procedure
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (12 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 11/27] NFSD: Use xdrgen XDR functions for NFSv2 READLINK procedure Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 13/27] NFSD: Use xdrgen XDR functions for the NFSv2 WRITECACHE procedure Chuck Lever
` (15 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Replace the hand-written NFSv2 READ codec pair with the
xdrgen-generated functions. The NFSPROC_READ entry in the
nfsd_procedures2 array now dispatches nfs_svc_decode_readargs and
nfs_svc_encode_readres. A wrapper structure bridges the generated
xdrgen readargs 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
readresok 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 readargs decoder consumes no
pages -- so the helper locates the payload through the Reply buffer
itself, and the wrapper collapses to the generated readres type.
The pc_argzero field is now set to zero for the NFSv2 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 the generated encoder no longer needs the svc_fh at encode
time, nfsd_proc_read() releases the file handle directly and no
separate ->pc_release callback is 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/nfs2.x | 1 +
fs/nfsd/nfs2xdr_gen.c | 4 +-
fs/nfsd/nfs2xdr_gen.h | 2 +-
fs/nfsd/nfsproc.c | 94 ++++++++++++++++++------------
fs/nfsd/nfsxdr.c | 52 +----------------
fs/nfsd/xdr.h | 17 ------
include/linux/sunrpc/xdrgen/nfs2.h | 2 +-
7 files changed, 62 insertions(+), 110 deletions(-)
diff --git a/Documentation/sunrpc/xdr/nfs2.x b/Documentation/sunrpc/xdr/nfs2.x
index 771acc4baa17..b64b939f11f8 100644
--- a/Documentation/sunrpc/xdr/nfs2.x
+++ b/Documentation/sunrpc/xdr/nfs2.x
@@ -140,6 +140,7 @@ struct readresok {
fattr attributes;
opaque data<NFS_MAXDATA>;
};
+pragma pages readresok data;
union readres switch (nfsstat status) {
case NFS_OK:
diff --git a/fs/nfsd/nfs2xdr_gen.c b/fs/nfsd/nfs2xdr_gen.c
index e97df0eda0e1..abde6fa8e510 100644
--- a/fs/nfsd/nfs2xdr_gen.c
+++ b/fs/nfsd/nfs2xdr_gen.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
// Generated by xdrgen. Manual edits will be lost.
// XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x
-// XDR specification modification time: Tue Sep 8 11:25:27 2026
+// XDR specification modification time: Tue Sep 8 11:25:45 2026
#include <linux/sunrpc/svc.h>
@@ -777,7 +777,7 @@ xdrgen_encode_readresok(struct xdr_stream *xdr, const struct readresok *value)
return false;
if (value->data.len > NFS_MAXDATA)
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/nfs2xdr_gen.h b/fs/nfsd/nfs2xdr_gen.h
index 92d5f9a8f5dd..e8ff072e32e5 100644
--- a/fs/nfsd/nfs2xdr_gen.h
+++ b/fs/nfsd/nfs2xdr_gen.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Generated by xdrgen. Manual edits will be lost. */
/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x */
-/* XDR specification modification time: Tue Sep 8 11:25:27 2026 */
+/* XDR specification modification time: Tue Sep 8 11:25:45 2026 */
#ifndef _LINUX_XDRGEN_NFS2_DECL_H
#define _LINUX_XDRGEN_NFS2_DECL_H
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index 7bc89629934b..c7e273e74c54 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -63,6 +63,13 @@ struct diropres_wrapper {
static_assert(offsetof(struct diropres_wrapper, xdrgen) == 0);
+struct readargs_wrapper {
+ struct readargs xdrgen;
+ struct svc_fh fh;
+};
+
+static_assert(offsetof(struct readargs_wrapper, xdrgen) == 0);
+
static __be32 nfsd_map_status(__be32 status)
{
switch (status) {
@@ -508,40 +515,52 @@ static __be32 nfsd_proc_readlink(struct svc_rqst *rqstp)
return rpc_success;
}
-/*
- * Read a portion of a file.
- * N.B. After this call resp->fh needs an fh_put
+/**
+ * nfsd_proc_read - READ: Read from file
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * readres NFSPROC_READ(readargs) = 6;
*/
-static __be32
-nfsd_proc_read(struct svc_rqst *rqstp)
+static __be32 nfsd_proc_read(struct svc_rqst *rqstp)
{
- struct nfsd_readargs *argp = rqstp->rq_argp;
- struct nfsd_readres *resp = rqstp->rq_resp;
+ struct readargs_wrapper *argp = rqstp->rq_argp;
+ struct readres *resp = rqstp->rq_resp;
+ unsigned long count = argp->xdrgen.count;
+ u32 offset = argp->xdrgen.offset;
+ struct svc_fh *fhp = &argp->fh;
+ struct kstat stat;
u32 eof;
- dprintk("nfsd: READ %s %d bytes at %d\n",
- SVCFH_fmt(&argp->fh),
- argp->count, argp->offset);
+ nfsd_fhandle_to_svc_fh(fhp, &argp->xdrgen.file);
+ count = min_t(u32, count, NFS_MAXDATA);
+ count = min_t(u32, count, rqstp->rq_res.buflen);
- argp->count = min_t(u32, argp->count, NFS_MAXDATA);
- argp->count = min_t(u32, argp->count, rqstp->rq_res.buflen);
-
- resp->pages = rqstp->rq_next_page;
-
- /* Obtain buffer pointer for payload. 19 is 1 word for
- * status, 17 words for fattr, and 1 word for the byte count.
+ /*
+ * 19 is 1 word for status, 17 words for fattr, and 1 word
+ * for the byte count.
*/
- svc_reserve_auth(rqstp, (19<<2) + argp->count + 4);
+ svc_reserve_auth(rqstp, (19 << 2) + count + 4);
+ resp->status = nfsd_read(rqstp, fhp, offset, &count, &eof);
- resp->count = argp->count;
- fh_copy(&resp->fh, &argp->fh);
- resp->status = nfsd_read(rqstp, &resp->fh, argp->offset,
- &resp->count, &eof);
- if (resp->status == nfs_ok)
- resp->status = fh_getattr(&resp->fh, &resp->stat);
- else if (resp->status == nfserr_jukebox)
+ if (resp->status == nfs_ok) {
+ struct readresok *resok = &resp->u.readresok;
+
+ resok->data.len = count;
+ resp->status = fh_getattr(fhp, &stat);
+ if (resp->status == nfs_ok)
+ nfsd_stat_to_fattr(rqstp, &resok->attributes, &stat,
+ fhp);
+ }
+ if (resp->status == nfserr_jukebox)
set_bit(RQ_DROPME, &rqstp->rq_flags);
- resp->status = nfsd_map_io_status(resp->status);
+ if (resp->status != nfs_ok)
+ resp->status = nfsd_map_io_status(resp->status);
+
+ fh_put(fhp);
return rpc_success;
}
@@ -1025,16 +1044,15 @@ static const struct svc_procedure nfsd_procedures2[18] = {
.pc_name = "READLINK",
},
[NFSPROC_READ] = {
- .pc_func = nfsd_proc_read,
- .pc_decode = nfssvc_decode_readargs,
- .pc_encode = nfssvc_encode_readres,
- .pc_release = nfssvc_release_readres,
- .pc_argsize = sizeof(struct nfsd_readargs),
- .pc_argzero = sizeof(struct nfsd_readargs),
- .pc_ressize = sizeof(struct nfsd_readres),
- .pc_cachetype = RC_NOCACHE,
- .pc_xdrressize = ST+AT+1+NFS_MAXDATA/4,
- .pc_name = "READ",
+ .pc_func = nfsd_proc_read,
+ .pc_decode = nfs_svc_decode_readargs,
+ .pc_encode = nfs_svc_encode_readres,
+ .pc_argsize = sizeof(struct readargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct readres),
+ .pc_cachetype = RC_NOCACHE,
+ .pc_xdrressize = NFS2_readres_sz,
+ .pc_name = "READ",
},
[NFSPROC_WRITECACHE] = {
.pc_func = nfsd_proc_writecache,
@@ -1168,7 +1186,7 @@ union nfsd_xdrstore {
struct fhandle_wrapper fhandle;
struct sattrargs_wrapper sattrargs;
struct diropargs_wrapper diropargs;
- struct nfsd_readargs read;
+ struct readargs_wrapper readargs;
struct nfsd_writeargs write;
struct nfsd_createargs create;
struct nfsd_renameargs rename;
@@ -1178,7 +1196,7 @@ union nfsd_xdrstore {
struct attrstat_wrapper attrstat;
struct diropres_wrapper diropres;
struct readlinkres readlinkres;
- struct nfsd_readres readres;
+ struct readres readres;
struct nfsd_readdirres readdirres;
struct nfsd_statfsres statfsres;
};
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
index ed5ec00e8557..1b79f9534af6 100644
--- a/fs/nfsd/nfsxdr.c
+++ b/fs/nfsd/nfsxdr.c
@@ -10,6 +10,7 @@
#include "vfs.h"
#include "nfserr.h"
#include "xdr.h"
+#include "nfs2xdr_gen.h"
#include "auth.h"
/*
@@ -310,25 +311,6 @@ nfssvc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
return svcxdr_decode_diropargs(xdr, &args->fh, &args->name, &args->len);
}
-bool
-nfssvc_decode_readargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd_readargs *args = rqstp->rq_argp;
- u32 totalcount;
-
- if (!svcxdr_decode_fhandle(xdr, &args->fh))
- return false;
- if (xdr_stream_decode_u32(xdr, &args->offset) < 0)
- return false;
- if (xdr_stream_decode_u32(xdr, &args->count) < 0)
- return false;
- /* totalcount is ignored */
- if (xdr_stream_decode_u32(xdr, &totalcount) < 0)
- return false;
-
- return true;
-}
-
bool
nfssvc_decode_writeargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
@@ -469,31 +451,6 @@ nfssvc_encode_diropres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
return true;
}
-bool
-nfssvc_encode_readres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd_readres *resp = rqstp->rq_resp;
- struct kvec *head = rqstp->rq_res.head;
-
- if (!svcxdr_encode_stat(xdr, resp->status))
- return false;
- switch (resp->status) {
- case nfs_ok:
- if (!svcxdr_encode_fattr(rqstp, xdr, &resp->fh, &resp->stat))
- 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;
- }
-
- return true;
-}
-
bool
nfssvc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
@@ -645,10 +602,3 @@ void nfssvc_release_diropres(struct svc_rqst *rqstp)
fh_put(&resp->fh);
}
-
-void nfssvc_release_readres(struct svc_rqst *rqstp)
-{
- struct nfsd_readres *resp = rqstp->rq_resp;
-
- fh_put(&resp->fh);
-}
diff --git a/fs/nfsd/xdr.h b/fs/nfsd/xdr.h
index 4d06c415a867..9d9150760a0b 100644
--- a/fs/nfsd/xdr.h
+++ b/fs/nfsd/xdr.h
@@ -14,12 +14,6 @@ struct nfsd_diropargs {
unsigned int len;
};
-struct nfsd_readargs {
- struct svc_fh fh;
- __u32 offset;
- __u32 count;
-};
-
struct nfsd_writeargs {
svc_fh fh;
__u32 offset;
@@ -82,14 +76,6 @@ struct nfsd_diropres {
struct kstat stat;
};
-struct nfsd_readres {
- __be32 status;
- struct svc_fh fh;
- unsigned long count;
- struct kstat stat;
- struct page **pages;
-};
-
struct nfsd_readdirres {
/* Components of the reply */
__be32 status;
@@ -110,7 +96,6 @@ struct nfsd_statfsres {
bool nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfssvc_decode_readargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_writeargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_renameargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
@@ -121,7 +106,6 @@ bool nfssvc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_statres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_attrstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_diropres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfssvc_encode_readres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_statfsres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
@@ -131,7 +115,6 @@ int nfssvc_encode_entry(void *data, const char *name, int namlen,
void nfssvc_release_attrstat(struct svc_rqst *rqstp);
void nfssvc_release_diropres(struct svc_rqst *rqstp);
-void nfssvc_release_readres(struct svc_rqst *rqstp);
/* Helper functions for NFSv2 ACL code */
bool svcxdr_decode_fhandle(struct xdr_stream *xdr, struct svc_fh *fhp);
diff --git a/include/linux/sunrpc/xdrgen/nfs2.h b/include/linux/sunrpc/xdrgen/nfs2.h
index fbda12101f87..eeb8b9ded65f 100644
--- a/include/linux/sunrpc/xdrgen/nfs2.h
+++ b/include/linux/sunrpc/xdrgen/nfs2.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Generated by xdrgen. Manual edits will be lost. */
/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x */
-/* XDR specification modification time: Tue Sep 8 11:25:27 2026 */
+/* XDR specification modification time: Tue Sep 8 11:25:45 2026 */
#ifndef _LINUX_XDRGEN_NFS2_DEF_H
#define _LINUX_XDRGEN_NFS2_DEF_H
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 13/27] NFSD: Use xdrgen XDR functions for the NFSv2 WRITECACHE procedure
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (13 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 12/27] NFSD: Use xdrgen XDR functions for NFSv2 READ procedure Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 14/27] NFSD: Use xdrgen XDR functions for NFSv2 WRITE procedure Chuck Lever
` (14 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Hand-written XDR encoders and decoders are difficult to
maintain and can diverge from protocol specifications.
Migrating to xdrgen-generated code improves type safety and
ensures the implementation matches the NFS version 2 protocol
specification exactly.
Convert the WRITECACHE procedure to use xdrgen_svc_decode_void
and xdrgen_svc_encode_void, the built-ins declared in
include/linux/sunrpc/xdrgen/_builtins.h. WRITECACHE is reserved and
has neither arguments nor results, so its entry mirrors the
NULL procedure.
WRITECACHE returns no XDR-encoded data, so pc_xdrressize is
set to XDR_void. The argzero field is also set to zero since
xdrgen decoders initialize all decoded values.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfsproc.c | 34 ++++++++++++++++++++++------------
1 file changed, 22 insertions(+), 12 deletions(-)
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index c7e273e74c54..71b792c8d78f 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -564,9 +564,19 @@ static __be32 nfsd_proc_read(struct svc_rqst *rqstp)
return rpc_success;
}
-/* Reserved */
-static __be32
-nfsd_proc_writecache(struct svc_rqst *rqstp)
+/**
+ * nfsd_proc_writecache - WRITECACHE: To-client write cache
+ * @rqstp: RPC transaction context
+ *
+ * Reserved.
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * void NFSPROC_WRITECACHE(void) = 7;
+ */
+static __be32 nfsd_proc_writecache(struct svc_rqst *rqstp)
{
return rpc_success;
}
@@ -1055,15 +1065,15 @@ static const struct svc_procedure nfsd_procedures2[18] = {
.pc_name = "READ",
},
[NFSPROC_WRITECACHE] = {
- .pc_func = nfsd_proc_writecache,
- .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 = 0,
- .pc_name = "WRITECACHE",
+ .pc_func = nfsd_proc_writecache,
+ .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 = "WRITECACHE",
},
[NFSPROC_WRITE] = {
.pc_func = nfsd_proc_write,
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 14/27] NFSD: Use xdrgen XDR functions for NFSv2 WRITE procedure
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (14 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 13/27] NFSD: Use xdrgen XDR functions for the NFSv2 WRITECACHE procedure Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 15/27] NFSD: Refactor nfsd_proc_create() Chuck Lever
` (13 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 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>
The NFSv2 WRITE argument decoder could not be generated 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 writeargs with "pragma pages" and
replace the NFSPROC_WRITE entry in nfsd_procedures2 with the
generated nfs_svc_decode_writeargs and nfs_svc_encode_attrstat. A
wrapper structure bridges the generated writeargs type and the
svc_fh the NFSD VFS layer still uses; the result reuses the
attrstat wrapper from the GETATTR conversion.
The pc_argzero field is now set to zero for the NFSv2 WRITE
procedure. The generated decoder initializes every argument in
the argp->xdrgen field, making the early defensive memset
unnecessary.
This refactor replaces the use of svcxdr_encode_fattr(), so the
reference to the file handle can be released directly by
nfsd_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.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
Documentation/sunrpc/xdr/nfs2.x | 1 +
fs/nfsd/nfs2xdr_gen.c | 6 +--
fs/nfsd/nfs2xdr_gen.h | 2 +-
fs/nfsd/nfsproc.c | 76 ++++++++++++++++++------------
fs/nfsd/nfsxdr.c | 26 ----------
fs/nfsd/xdr.h | 8 ----
include/linux/sunrpc/xdrgen/nfs2.h | 4 +-
7 files changed, 54 insertions(+), 69 deletions(-)
diff --git a/Documentation/sunrpc/xdr/nfs2.x b/Documentation/sunrpc/xdr/nfs2.x
index b64b939f11f8..b2ed52bd1631 100644
--- a/Documentation/sunrpc/xdr/nfs2.x
+++ b/Documentation/sunrpc/xdr/nfs2.x
@@ -156,6 +156,7 @@ struct writeargs {
unsigned int totalcount;
opaque data<NFS_MAXDATA>;
};
+pragma pages writeargs data;
struct createargs {
diropargs where;
diff --git a/fs/nfsd/nfs2xdr_gen.c b/fs/nfsd/nfs2xdr_gen.c
index abde6fa8e510..3c2f78c802a6 100644
--- a/fs/nfsd/nfs2xdr_gen.c
+++ b/fs/nfsd/nfs2xdr_gen.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
// Generated by xdrgen. Manual edits will be lost.
// XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x
-// XDR specification modification time: Tue Sep 8 11:25:45 2026
+// XDR specification modification time: Tue Sep 8 11:26:00 2026
#include <linux/sunrpc/svc.h>
@@ -282,7 +282,7 @@ xdrgen_decode_writeargs(struct xdr_stream *xdr, struct writeargs *ptr)
return false;
if (!xdrgen_decode_unsigned_int(xdr, &ptr->totalcount))
return false;
- if (!xdrgen_decode_opaque(xdr, &ptr->data, NFS_MAXDATA))
+ if (!svcxdr_decode_opaque_payload(xdr, &ptr->data, NFS_MAXDATA))
return false;
return true;
}
@@ -811,7 +811,7 @@ xdrgen_encode_writeargs(struct xdr_stream *xdr, const struct writeargs *value)
return false;
if (value->data.len > NFS_MAXDATA)
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/nfs2xdr_gen.h b/fs/nfsd/nfs2xdr_gen.h
index e8ff072e32e5..8f7d17e8e483 100644
--- a/fs/nfsd/nfs2xdr_gen.h
+++ b/fs/nfsd/nfs2xdr_gen.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Generated by xdrgen. Manual edits will be lost. */
/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x */
-/* XDR specification modification time: Tue Sep 8 11:25:45 2026 */
+/* XDR specification modification time: Tue Sep 8 11:26:00 2026 */
#ifndef _LINUX_XDRGEN_NFS2_DECL_H
#define _LINUX_XDRGEN_NFS2_DECL_H
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index 71b792c8d78f..4d09da326070 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -70,6 +70,13 @@ struct readargs_wrapper {
static_assert(offsetof(struct readargs_wrapper, xdrgen) == 0);
+struct writeargs_wrapper {
+ struct writeargs xdrgen;
+ struct svc_fh fh;
+};
+
+static_assert(offsetof(struct writeargs_wrapper, xdrgen) == 0);
+
static __be32 nfsd_map_status(__be32 status)
{
switch (status) {
@@ -581,29 +588,41 @@ static __be32 nfsd_proc_writecache(struct svc_rqst *rqstp)
return rpc_success;
}
-/*
- * Write data to a file
- * N.B. After this call resp->fh needs an fh_put
+/**
+ * nfsd_proc_write - WRITE: Write to file
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * attrstat NFSPROC_WRITE(writeargs) = 8;
*/
-static __be32
-nfsd_proc_write(struct svc_rqst *rqstp)
+static __be32 nfsd_proc_write(struct svc_rqst *rqstp)
{
- struct nfsd_writeargs *argp = rqstp->rq_argp;
- struct nfsd_attrstat *resp = rqstp->rq_resp;
- unsigned long cnt = argp->len;
+ struct writeargs_wrapper *argp = rqstp->rq_argp;
+ struct attrstat_wrapper *resp = rqstp->rq_resp;
+ struct kstat *statp = &resp->stat;
+ unsigned long count = argp->xdrgen.data.len;
+ struct svc_fh *fhp = &argp->fh;
- dprintk("nfsd: WRITE %s %u bytes at %d\n",
- SVCFH_fmt(&argp->fh),
- argp->len, argp->offset);
+ nfsd_fhandle_to_svc_fh(fhp, &argp->xdrgen.file);
- fh_copy(&resp->fh, &argp->fh);
- resp->status = nfsd_write(rqstp, &resp->fh, argp->offset,
- &argp->payload, &cnt, IOCB_DSYNC, NULL);
- if (resp->status == nfs_ok)
- resp->status = fh_getattr(&resp->fh, &resp->stat);
- else if (resp->status == nfserr_jukebox)
+ resp->xdrgen.status = nfsd_write(rqstp, fhp, argp->xdrgen.offset,
+ &argp->xdrgen.data, &count,
+ IOCB_DSYNC, NULL);
+ if (resp->xdrgen.status == nfs_ok) {
+ resp->xdrgen.status = fh_getattr(fhp, statp);
+ if (resp->xdrgen.status == nfs_ok)
+ nfsd_stat_to_fattr(rqstp, &resp->xdrgen.u.attributes,
+ statp, fhp);
+ }
+ if (resp->xdrgen.status == nfserr_jukebox)
set_bit(RQ_DROPME, &rqstp->rq_flags);
- resp->status = nfsd_map_io_status(resp->status);
+ if (resp->xdrgen.status != nfs_ok)
+ resp->xdrgen.status = nfsd_map_io_status(resp->xdrgen.status);
+
+ fh_put(fhp);
return rpc_success;
}
@@ -1076,16 +1095,15 @@ static const struct svc_procedure nfsd_procedures2[18] = {
.pc_name = "WRITECACHE",
},
[NFSPROC_WRITE] = {
- .pc_func = nfsd_proc_write,
- .pc_decode = nfssvc_decode_writeargs,
- .pc_encode = nfssvc_encode_attrstatres,
- .pc_release = nfssvc_release_attrstat,
- .pc_argsize = sizeof(struct nfsd_writeargs),
- .pc_argzero = sizeof(struct nfsd_writeargs),
- .pc_ressize = sizeof(struct nfsd_attrstat),
- .pc_cachetype = RC_REPLBUFF,
- .pc_xdrressize = ST+AT,
- .pc_name = "WRITE",
+ .pc_func = nfsd_proc_write,
+ .pc_decode = nfs_svc_decode_writeargs,
+ .pc_encode = nfs_svc_encode_attrstat,
+ .pc_argsize = sizeof(struct writeargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct attrstat_wrapper),
+ .pc_cachetype = RC_REPLBUFF,
+ .pc_xdrressize = NFS2_attrstat_sz,
+ .pc_name = "WRITE",
},
[NFSPROC_CREATE] = {
.pc_func = nfsd_proc_create,
@@ -1197,7 +1215,7 @@ union nfsd_xdrstore {
struct sattrargs_wrapper sattrargs;
struct diropargs_wrapper diropargs;
struct readargs_wrapper readargs;
- struct nfsd_writeargs write;
+ struct writeargs_wrapper writeargs;
struct nfsd_createargs create;
struct nfsd_renameargs rename;
struct nfsd_linkargs link;
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
index 1b79f9534af6..33b3fb73053e 100644
--- a/fs/nfsd/nfsxdr.c
+++ b/fs/nfsd/nfsxdr.c
@@ -311,32 +311,6 @@ nfssvc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
return svcxdr_decode_diropargs(xdr, &args->fh, &args->name, &args->len);
}
-bool
-nfssvc_decode_writeargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd_writeargs *args = rqstp->rq_argp;
- u32 beginoffset, totalcount;
-
- if (!svcxdr_decode_fhandle(xdr, &args->fh))
- return false;
- /* beginoffset is ignored */
- if (xdr_stream_decode_u32(xdr, &beginoffset) < 0)
- return false;
- if (xdr_stream_decode_u32(xdr, &args->offset) < 0)
- return false;
- /* totalcount is ignored */
- if (xdr_stream_decode_u32(xdr, &totalcount) < 0)
- return false;
-
- /* opaque data */
- if (xdr_stream_decode_u32(xdr, &args->len) < 0)
- return false;
- if (args->len > NFS_MAXDATA)
- return false;
-
- return xdr_stream_subsegment(xdr, &args->payload, args->len);
-}
-
bool
nfssvc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
diff --git a/fs/nfsd/xdr.h b/fs/nfsd/xdr.h
index 9d9150760a0b..75f40bef6614 100644
--- a/fs/nfsd/xdr.h
+++ b/fs/nfsd/xdr.h
@@ -14,13 +14,6 @@ struct nfsd_diropargs {
unsigned int len;
};
-struct nfsd_writeargs {
- svc_fh fh;
- __u32 offset;
- __u32 len;
- struct xdr_buf payload;
-};
-
struct nfsd_createargs {
struct svc_fh fh;
char * name;
@@ -96,7 +89,6 @@ struct nfsd_statfsres {
bool nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfssvc_decode_writeargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_renameargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_linkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
diff --git a/include/linux/sunrpc/xdrgen/nfs2.h b/include/linux/sunrpc/xdrgen/nfs2.h
index eeb8b9ded65f..7a469ac4b772 100644
--- a/include/linux/sunrpc/xdrgen/nfs2.h
+++ b/include/linux/sunrpc/xdrgen/nfs2.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Generated by xdrgen. Manual edits will be lost. */
/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x */
-/* XDR specification modification time: Tue Sep 8 11:25:45 2026 */
+/* XDR specification modification time: Tue Sep 8 11:26:00 2026 */
#ifndef _LINUX_XDRGEN_NFS2_DEF_H
#define _LINUX_XDRGEN_NFS2_DEF_H
@@ -152,7 +152,7 @@ struct writeargs {
u32 beginoffset;
u32 offset;
u32 totalcount;
- opaque data;
+ struct xdr_buf data;
};
struct createargs {
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 15/27] NFSD: Refactor nfsd_proc_create()
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (15 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 14/27] NFSD: Use xdrgen XDR functions for NFSv2 WRITE procedure Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 16/27] NFSD: Use xdrgen XDR functions for NFSv2 CREATE procedure Chuck Lever
` (12 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 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 a spirit consistent with commit df9606abddfb ("NFSD: Refactor
NFSv3 CREATE"), extract NFSv2-specific create behaviors into a
helper function. This clearly delineates the NFSv2-specific
semantics from the generic nfsd_create() utility.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfsproc.c | 138 ++++++++++++++++++++++++----------------------
1 file changed, 71 insertions(+), 67 deletions(-)
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index 4d09da326070..e041598ce68e 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -627,85 +627,77 @@ static __be32 nfsd_proc_write(struct svc_rqst *rqstp)
}
/*
- * CREATE processing is complicated. The keyword here is `overloaded.'
- * The parent directory is kept locked between the check for existence
- * and the actual create() call in compliance with VFS protocols.
- * N.B. After this call _both_ argp->fh and resp->fh need an fh_put
+ * Implement NFSv2's unchecked CREATE semantics. Based on type bits in the
+ * mode field, NFSv2 CREATE can create regular files, devices, and FIFOs.
+ *
+ * Caller must release @fhp and @resfhp.
*/
static __be32
-nfsd_proc_create(struct svc_rqst *rqstp)
+nfsd_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
+ struct svc_fh *resfhp, struct nfsd_createargs *argp)
{
- struct nfsd_createargs *argp = rqstp->rq_argp;
- struct nfsd_diropres *resp = rqstp->rq_resp;
- svc_fh *dirfhp = &argp->fh;
- svc_fh *newfhp = &resp->fh;
- struct iattr *attr = &argp->attrs;
+ struct iattr *attr = &argp->attrs;
+ dev_t rdev = 0, wanted = new_decode_dev(attr->ia_size);
struct nfsd_attrs attrs = {
.na_iattr = attr,
};
struct svc_export *exp;
- struct inode *inode;
- struct dentry *dchild;
- int type, mode;
- int hosterr;
- dev_t rdev = 0, wanted = new_decode_dev(attr->ia_size);
+ struct dentry *dchild;
+ struct inode *inode;
+ int type, mode;
+ __be32 status;
+ int host_err;
- /* First verify the parent file handle */
- resp->status = fh_verify(rqstp, dirfhp, S_IFDIR, NFSD_MAY_EXEC);
- if (resp->status != nfs_ok)
- goto done; /* must fh_put dirfhp even on error */
-
- /* Check for NFSD_MAY_WRITE in nfsd_create if necessary */
-
- resp->status = nfserr_exist;
if (name_is_dot_dotdot(argp->name, argp->len))
- goto done;
- hosterr = fh_want_write(dirfhp);
- if (hosterr) {
- resp->status = nfserrno(hosterr);
- goto done;
- }
+ return nfserr_exist;
- dchild = start_creating(&nop_mnt_idmap, dirfhp->fh_dentry,
+ status = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_EXEC);
+ if (status != nfs_ok)
+ return status;
+
+ host_err = fh_want_write(fhp);
+ if (host_err)
+ return nfserrno(host_err);
+
+ dchild = start_creating(&nop_mnt_idmap, fhp->fh_dentry,
&QSTR_LEN(argp->name, argp->len));
if (IS_ERR(dchild)) {
- resp->status = nfserrno(PTR_ERR(dchild));
- goto out_write;
+ status = nfserrno(PTR_ERR(dchild));
+ goto out_drop_write;
}
/*
* If name exists we need to check for mountpoints
*/
- exp = exp_get(dirfhp->fh_export);
+ exp = exp_get(fhp->fh_export);
if (d_is_reg(dchild) &&
unlikely(nfsd_mountpoint(dchild, exp))) {
- resp->status = nfsd_cross_mnt(rqstp, &dchild, &exp);
- if (resp->status != nfs_ok) {
+ status = nfsd_cross_mnt(rqstp, &dchild, &exp);
+ if (status != nfs_ok) {
exp_put(exp);
- goto out_unlock;
+ goto out;
}
}
- fh_init(newfhp, NFS_FHSIZE);
- resp->status = fh_compose(newfhp, exp, dchild, dirfhp);
+ status = fh_compose(resfhp, exp, dchild, fhp);
exp_put(exp);
- if (!resp->status && d_really_is_negative(dchild))
- resp->status = nfserr_noent;
- if (resp->status) {
- if (resp->status != nfserr_noent)
- goto out_unlock;
+ if (!status && d_really_is_negative(dchild))
+ status = nfserr_noent;
+ if (status) {
+ if (status != nfserr_noent)
+ goto out;
/*
* If the new file handle wasn't verified, we can't tell
* whether the file exists or not. Time to bail ...
*/
- resp->status = nfserr_acces;
- if (!newfhp->fh_dentry) {
+ status = nfserr_acces;
+ if (!resfhp->fh_dentry) {
printk(KERN_WARNING
"nfsd_proc_create: file handle not verified\n");
- goto out_unlock;
+ goto out;
}
}
- inode = d_inode(newfhp->fh_dentry);
+ inode = d_inode(resfhp->fh_dentry);
/* Unfudge the mode bits */
if (attr->ia_valid & ATTR_MODE) {
@@ -716,13 +708,12 @@ nfsd_proc_create(struct svc_rqst *rqstp)
* else assume a file */
if (inode) {
type = inode->i_mode & S_IFMT;
- switch(type) {
+ switch (type) {
case S_IFCHR:
case S_IFBLK:
/* reserve rdev for later checking */
rdev = inode->i_rdev;
attr->ia_valid |= ATTR_SIZE;
-
fallthrough;
case S_IFIFO:
/* this is probably a permission check..
@@ -730,13 +721,13 @@ nfsd_proc_create(struct svc_rqst *rqstp)
* echo thing > device-special-file-or-pipe
* by doing a CREATE with type==0
*/
- resp->status = nfsd_permission(
+ status = nfsd_permission(
&rqstp->rq_cred,
- newfhp->fh_export,
- newfhp->fh_dentry,
+ resfhp->fh_export,
+ resfhp->fh_dentry,
NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS);
- if (resp->status && resp->status != nfserr_rofs)
- goto out_unlock;
+ if (status && status != nfserr_rofs)
+ goto out;
}
} else
type = S_IFREG;
@@ -771,37 +762,50 @@ nfsd_proc_create(struct svc_rqst *rqstp)
attr->ia_valid &= ~ATTR_SIZE;
/* Make sure the type and device matches */
- resp->status = nfserr_exist;
+ status = nfserr_exist;
if (inode && inode_wrong_type(inode, type))
- goto out_unlock;
+ goto out;
}
- resp->status = nfs_ok;
+ status = nfs_ok;
if (!inode) {
/* File doesn't exist. Create it and set attrs */
- resp->status = nfsd_create_locked(rqstp, dirfhp, &attrs, type,
- rdev, newfhp);
+ status = nfsd_create_locked(rqstp, fhp, &attrs, type,
+ rdev, resfhp);
/* nfsd_create_locked() unlocked the parent */
dput(dchild);
- goto out_write;
+ goto out_drop_write;
} else if (type == S_IFREG) {
dprintk("nfsd: existing %s, valid=%x, size=%ld\n",
- argp->name, attr->ia_valid, (long) attr->ia_size);
+ argp->name, attr->ia_valid, (long)attr->ia_size);
/* File already exists. We ignore all attributes except
* size, so that creat() behaves exactly like
* open(..., O_CREAT|O_TRUNC|O_WRONLY).
*/
attr->ia_valid &= ATTR_SIZE;
if (attr->ia_valid)
- resp->status = nfsd_setattr(rqstp, newfhp, &attrs,
- NULL);
+ status = nfsd_setattr(rqstp, resfhp, &attrs, NULL);
}
-out_unlock:
+out:
end_creating(dchild);
-out_write:
- fh_drop_write(dirfhp);
-done:
+out_drop_write:
+ fh_drop_write(fhp);
+ return status;
+}
+
+/*
+ * N.B. After this call _both_ argp->fh and resp->fh need an fh_put
+ */
+static __be32
+nfsd_proc_create(struct svc_rqst *rqstp)
+{
+ struct nfsd_createargs *argp = rqstp->rq_argp;
+ struct nfsd_diropres *resp = rqstp->rq_resp;
+ svc_fh *dirfhp = &argp->fh;
+ svc_fh *newfhp = fh_init(&resp->fh, NFS_FHSIZE);
+
+ resp->status = nfsd_create_file(rqstp, dirfhp, newfhp, argp);
fh_put(dirfhp);
if (resp->status != nfs_ok)
goto out;
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 16/27] NFSD: Use xdrgen XDR functions for NFSv2 CREATE procedure
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (16 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 15/27] NFSD: Refactor nfsd_proc_create() Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 17/27] NFSD: Use xdrgen XDR functions for the NFSv2 REMOVE procedure Chuck Lever
` (11 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Replace the NFSPROC_CREATE entry in the nfsd_procedures2 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_createargs and nfs_svc_encode_diropres. Wrapper
structures bridge the generated xdrgen types and the legacy
svc_fh, iattr, and kstat representations the NFSD VFS layer still
uses.
The pc_argzero field is now set to zero for the NFSv2 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.
This refactor replaces the use of svcxdr_encode_fattr(), so the
references to the parent directory and the new object file
handles can be released directly by nfsd_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.
nfssvc_decode_createargs remains for the MKDIR procedure which
uses the same argument structure.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfsproc.c | 89 ++++++++++++++++++++++++++++++++---------------
1 file changed, 60 insertions(+), 29 deletions(-)
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index e041598ce68e..69dcd482b77f 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -63,6 +63,14 @@ struct diropres_wrapper {
static_assert(offsetof(struct diropres_wrapper, xdrgen) == 0);
+struct createargs_wrapper {
+ struct createargs xdrgen;
+ struct svc_fh fh;
+ struct iattr iattrs;
+};
+
+static_assert(offsetof(struct createargs_wrapper, xdrgen) == 0);
+
struct readargs_wrapper {
struct readargs xdrgen;
struct svc_fh fh;
@@ -634,9 +642,10 @@ static __be32 nfsd_proc_write(struct svc_rqst *rqstp)
*/
static __be32
nfsd_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
- struct svc_fh *resfhp, struct nfsd_createargs *argp)
+ struct svc_fh *resfhp, struct createargs_wrapper *argp)
{
- struct iattr *attr = &argp->attrs;
+ struct diropargs *where = &argp->xdrgen.where;
+ struct iattr *attr = &argp->iattrs;
dev_t rdev = 0, wanted = new_decode_dev(attr->ia_size);
struct nfsd_attrs attrs = {
.na_iattr = attr,
@@ -648,7 +657,7 @@ nfsd_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
__be32 status;
int host_err;
- if (name_is_dot_dotdot(argp->name, argp->len))
+ if (name_is_dot_dotdot(where->name.data, where->name.len))
return nfserr_exist;
status = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_EXEC);
@@ -660,7 +669,7 @@ nfsd_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
return nfserrno(host_err);
dchild = start_creating(&nop_mnt_idmap, fhp->fh_dentry,
- &QSTR_LEN(argp->name, argp->len));
+ &QSTR_LEN(where->name.data, where->name.len));
if (IS_ERR(dchild)) {
status = nfserrno(PTR_ERR(dchild));
goto out_drop_write;
@@ -777,7 +786,7 @@ nfsd_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
goto out_drop_write;
} else if (type == S_IFREG) {
dprintk("nfsd: existing %s, valid=%x, size=%ld\n",
- argp->name, attr->ia_valid, (long)attr->ia_size);
+ where->name.data, attr->ia_valid, (long)attr->ia_size);
/* File already exists. We ignore all attributes except
* size, so that creat() behaves exactly like
* open(..., O_CREAT|O_TRUNC|O_WRONLY).
@@ -794,24 +803,47 @@ nfsd_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
return status;
}
-/*
- * N.B. After this call _both_ argp->fh and resp->fh need an fh_put
+/**
+ * nfsd_proc_create - CREATE: Create a file
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * diropres NFSPROC_CREATE(createargs) = 9;
*/
-static __be32
-nfsd_proc_create(struct svc_rqst *rqstp)
+static __be32 nfsd_proc_create(struct svc_rqst *rqstp)
{
- struct nfsd_createargs *argp = rqstp->rq_argp;
- struct nfsd_diropres *resp = rqstp->rq_resp;
- svc_fh *dirfhp = &argp->fh;
- svc_fh *newfhp = fh_init(&resp->fh, NFS_FHSIZE);
+ struct createargs_wrapper *argp = rqstp->rq_argp;
+ struct diropres_wrapper *resp = rqstp->rq_resp;
+ struct kstat *statp = &resp->stat;
+ struct svc_fh *dirfhp = &argp->fh;
+ struct svc_fh *fhp = &resp->fh;
- resp->status = nfsd_create_file(rqstp, dirfhp, newfhp, argp);
- fh_put(dirfhp);
- if (resp->status != nfs_ok)
+ nfsd_fhandle_to_svc_fh(dirfhp, &argp->xdrgen.where.dir);
+ fh_init(fhp, NFS_FHSIZE);
+ if (!nfsd_sattr_to_iattr(rqstp, &argp->iattrs, &argp->xdrgen.attributes)) {
+ resp->xdrgen.status = nfserr_io;
goto out;
- resp->status = fh_getattr(&resp->fh, &resp->stat);
+ }
+
+ resp->xdrgen.status = nfsd_create_file(rqstp, dirfhp, fhp, argp);
+ if (resp->xdrgen.status != nfs_ok)
+ goto out;
+ resp->xdrgen.status = fh_getattr(fhp, statp);
+
out:
- resp->status = nfsd_map_status(resp->status);
+ if (resp->xdrgen.status == nfs_ok) {
+ nfsd_svc_fh_to_fhandle(&resp->xdrgen.u.diropok.file, fhp);
+ nfsd_stat_to_fattr(rqstp, &resp->xdrgen.u.diropok.attributes,
+ statp, fhp);
+ } else {
+ resp->xdrgen.status = nfsd_map_status(resp->xdrgen.status);
+ }
+
+ fh_put(fhp);
+ fh_put(dirfhp);
return rpc_success;
}
@@ -1110,16 +1142,15 @@ static const struct svc_procedure nfsd_procedures2[18] = {
.pc_name = "WRITE",
},
[NFSPROC_CREATE] = {
- .pc_func = nfsd_proc_create,
- .pc_decode = nfssvc_decode_createargs,
- .pc_encode = nfssvc_encode_diropres,
- .pc_release = nfssvc_release_diropres,
- .pc_argsize = sizeof(struct nfsd_createargs),
- .pc_argzero = sizeof(struct nfsd_createargs),
- .pc_ressize = sizeof(struct nfsd_diropres),
- .pc_cachetype = RC_REPLBUFF,
- .pc_xdrressize = ST+FH+AT,
- .pc_name = "CREATE",
+ .pc_func = nfsd_proc_create,
+ .pc_decode = nfs_svc_decode_createargs,
+ .pc_encode = nfs_svc_encode_diropres,
+ .pc_argsize = sizeof(struct createargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct diropres_wrapper),
+ .pc_cachetype = RC_REPLBUFF,
+ .pc_xdrressize = NFS2_diropres_sz,
+ .pc_name = "CREATE",
},
[NFSPROC_REMOVE] = {
.pc_func = nfsd_proc_remove,
@@ -1220,7 +1251,7 @@ union nfsd_xdrstore {
struct diropargs_wrapper diropargs;
struct readargs_wrapper readargs;
struct writeargs_wrapper writeargs;
- struct nfsd_createargs create;
+ struct createargs_wrapper createargs;
struct nfsd_renameargs rename;
struct nfsd_linkargs link;
struct nfsd_symlinkargs symlink;
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 17/27] NFSD: Use xdrgen XDR functions for the NFSv2 REMOVE procedure
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (17 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 16/27] NFSD: Use xdrgen XDR functions for NFSv2 CREATE procedure Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 18/27] NFSD: Use xdrgen XDR functions for the NFSv2 RENAME procedure Chuck Lever
` (10 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Replace the NFSPROC_REMOVE entry in the nfsd_procedures2 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_diropargs and nfs_svc_encode_nfsstat. A wrapper
structure bridges the generated xdrgen diropargs type and the
legacy svc_fh representation the NFSD VFS layer still uses.
The pc_argzero field is now set to zero for the NFSv2 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.
The hand-coded nfssvc_decode_diropargs and nfssvc_encode_statres
remain for the procedures that still use them.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfsproc.c | 50 ++++++++++++++++++++++++++++++-----------------
1 file changed, 32 insertions(+), 18 deletions(-)
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index 69dcd482b77f..ae74247a892f 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -847,17 +847,31 @@ static __be32 nfsd_proc_create(struct svc_rqst *rqstp)
return rpc_success;
}
-static __be32
-nfsd_proc_remove(struct svc_rqst *rqstp)
+/**
+ * nfsd_proc_remove - REMOVE: Remove a file
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * nfsstat NFSPROC_REMOVE(diropargs) = 10;
+ */
+static __be32 nfsd_proc_remove(struct svc_rqst *rqstp)
{
- struct nfsd_diropargs *argp = rqstp->rq_argp;
- struct nfsd_stat *resp = rqstp->rq_resp;
+ struct diropargs_wrapper *argp = rqstp->rq_argp;
+ struct diropargs *object = &argp->xdrgen;
+ nfsstat *resp = rqstp->rq_resp;
+ struct svc_fh *fhp = &argp->fh;
- /* Unlink. -SIFDIR means file must not be a directory */
- resp->status = nfsd_unlink(rqstp, &argp->fh, -S_IFDIR,
- argp->name, argp->len);
- fh_put(&argp->fh);
- resp->status = nfsd_map_status(resp->status);
+ nfsd_fhandle_to_svc_fh(fhp, &object->dir);
+
+ /* Unlink. -S_IFDIR means file must not be a directory */
+ *resp = nfsd_unlink(rqstp, fhp, -S_IFDIR,
+ (char *)object->name.data, object->name.len);
+ *resp = nfsd_map_status(*resp);
+
+ fh_put(fhp);
return rpc_success;
}
@@ -1153,15 +1167,15 @@ static const struct svc_procedure nfsd_procedures2[18] = {
.pc_name = "CREATE",
},
[NFSPROC_REMOVE] = {
- .pc_func = nfsd_proc_remove,
- .pc_decode = nfssvc_decode_diropargs,
- .pc_encode = nfssvc_encode_statres,
- .pc_argsize = sizeof(struct nfsd_diropargs),
- .pc_argzero = sizeof(struct nfsd_diropargs),
- .pc_ressize = sizeof(struct nfsd_stat),
- .pc_cachetype = RC_REPLSTAT,
- .pc_xdrressize = ST,
- .pc_name = "REMOVE",
+ .pc_func = nfsd_proc_remove,
+ .pc_decode = nfs_svc_decode_diropargs,
+ .pc_encode = nfs_svc_encode_nfsstat,
+ .pc_argsize = sizeof(struct diropargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(nfsstat),
+ .pc_cachetype = RC_REPLSTAT,
+ .pc_xdrressize = NFS2_nfsstat_sz,
+ .pc_name = "REMOVE",
},
[NFSPROC_RENAME] = {
.pc_func = nfsd_proc_rename,
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 18/27] NFSD: Use xdrgen XDR functions for the NFSv2 RENAME procedure
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (18 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 17/27] NFSD: Use xdrgen XDR functions for the NFSv2 REMOVE procedure Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 19/27] NFSD: Use xdrgen XDR functions for the NFSv2 LINK procedure Chuck Lever
` (9 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Replace the NFSPROC_RENAME entry in the nfsd_procedures2 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_renameargs and nfs_svc_encode_nfsstat. A wrapper
structure bridges the generated xdrgen renameargs type and the
legacy svc_fh representations the NFSD VFS layer still uses.
The pc_argzero field is now set to zero for the NFSv2 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.
nfssvc_decode_renameargs() no longer has any callers, so it is
removed.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfsproc.c | 64 +++++++++++++++++++++++++++++++++--------------
fs/nfsd/nfsxdr.c | 11 --------
fs/nfsd/xdr.h | 10 --------
3 files changed, 45 insertions(+), 40 deletions(-)
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index ae74247a892f..755518f08490 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -85,6 +85,14 @@ struct writeargs_wrapper {
static_assert(offsetof(struct writeargs_wrapper, xdrgen) == 0);
+struct renameargs_wrapper {
+ struct renameargs xdrgen;
+ struct svc_fh ffh;
+ struct svc_fh tfh;
+};
+
+static_assert(offsetof(struct renameargs_wrapper, xdrgen) == 0);
+
static __be32 nfsd_map_status(__be32 status)
{
switch (status) {
@@ -875,17 +883,35 @@ static __be32 nfsd_proc_remove(struct svc_rqst *rqstp)
return rpc_success;
}
-static __be32
-nfsd_proc_rename(struct svc_rqst *rqstp)
+/**
+ * nfsd_proc_rename - RENAME: Rename a file or directory
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * nfsstat NFSPROC_RENAME(renameargs) = 11;
+ */
+static __be32 nfsd_proc_rename(struct svc_rqst *rqstp)
{
- struct nfsd_renameargs *argp = rqstp->rq_argp;
- struct nfsd_stat *resp = rqstp->rq_resp;
+ struct renameargs_wrapper *argp = rqstp->rq_argp;
+ struct diropargs *from = &argp->xdrgen.from;
+ struct diropargs *to = &argp->xdrgen.to;
+ nfsstat *resp = rqstp->rq_resp;
+ struct svc_fh *ffhp = &argp->ffh;
+ struct svc_fh *tfhp = &argp->tfh;
- resp->status = nfsd_rename(rqstp, &argp->ffh, argp->fname, argp->flen,
- &argp->tfh, argp->tname, argp->tlen);
- fh_put(&argp->ffh);
- fh_put(&argp->tfh);
- resp->status = nfsd_map_status(resp->status);
+ nfsd_fhandle_to_svc_fh(ffhp, &from->dir);
+ nfsd_fhandle_to_svc_fh(tfhp, &to->dir);
+
+ *resp = nfsd_rename(rqstp, ffhp, (char *)from->name.data,
+ from->name.len, tfhp, (char *)to->name.data,
+ to->name.len);
+ *resp = nfsd_map_status(*resp);
+
+ fh_put(ffhp);
+ fh_put(tfhp);
return rpc_success;
}
@@ -1178,15 +1204,15 @@ static const struct svc_procedure nfsd_procedures2[18] = {
.pc_name = "REMOVE",
},
[NFSPROC_RENAME] = {
- .pc_func = nfsd_proc_rename,
- .pc_decode = nfssvc_decode_renameargs,
- .pc_encode = nfssvc_encode_statres,
- .pc_argsize = sizeof(struct nfsd_renameargs),
- .pc_argzero = sizeof(struct nfsd_renameargs),
- .pc_ressize = sizeof(struct nfsd_stat),
- .pc_cachetype = RC_REPLSTAT,
- .pc_xdrressize = ST,
- .pc_name = "RENAME",
+ .pc_func = nfsd_proc_rename,
+ .pc_decode = nfs_svc_decode_renameargs,
+ .pc_encode = nfs_svc_encode_nfsstat,
+ .pc_argsize = sizeof(struct renameargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(nfsstat),
+ .pc_cachetype = RC_REPLSTAT,
+ .pc_xdrressize = NFS2_nfsstat_sz,
+ .pc_name = "RENAME",
},
[NFSPROC_LINK] = {
.pc_func = nfsd_proc_link,
@@ -1266,7 +1292,7 @@ union nfsd_xdrstore {
struct readargs_wrapper readargs;
struct writeargs_wrapper writeargs;
struct createargs_wrapper createargs;
- struct nfsd_renameargs rename;
+ struct renameargs_wrapper renameargs;
struct nfsd_linkargs link;
struct nfsd_symlinkargs symlink;
struct nfsd_readdirargs readdir;
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
index 33b3fb73053e..63496ad5e2ac 100644
--- a/fs/nfsd/nfsxdr.c
+++ b/fs/nfsd/nfsxdr.c
@@ -321,17 +321,6 @@ nfssvc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
svcxdr_decode_sattr(rqstp, xdr, &args->attrs);
}
-bool
-nfssvc_decode_renameargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd_renameargs *args = rqstp->rq_argp;
-
- return svcxdr_decode_diropargs(xdr, &args->ffh,
- &args->fname, &args->flen) &&
- svcxdr_decode_diropargs(xdr, &args->tfh,
- &args->tname, &args->tlen);
-}
-
bool
nfssvc_decode_linkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
diff --git a/fs/nfsd/xdr.h b/fs/nfsd/xdr.h
index 75f40bef6614..45582d21624c 100644
--- a/fs/nfsd/xdr.h
+++ b/fs/nfsd/xdr.h
@@ -21,15 +21,6 @@ struct nfsd_createargs {
struct iattr attrs;
};
-struct nfsd_renameargs {
- struct svc_fh ffh;
- char * fname;
- unsigned int flen;
- struct svc_fh tfh;
- char * tname;
- unsigned int tlen;
-};
-
struct nfsd_linkargs {
struct svc_fh ffh;
struct svc_fh tfh;
@@ -90,7 +81,6 @@ struct nfsd_statfsres {
bool nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfssvc_decode_renameargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_linkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_symlinkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 19/27] NFSD: Use xdrgen XDR functions for the NFSv2 LINK procedure
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (19 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 18/27] NFSD: Use xdrgen XDR functions for the NFSv2 RENAME procedure Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 20/27] NFSD: Use xdrgen XDR functions for NFSv2 SYMLINK procedure Chuck Lever
` (8 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Replace the NFSPROC_LINK entry in the nfsd_procedures2 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_linkargs and nfs_svc_encode_nfsstat. A wrapper
structure bridges the generated xdrgen linkargs type and the
legacy svc_fh representations the NFSD VFS layer still uses.
The pc_argzero field is now set to zero for the NFSv2 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.
nfssvc_decode_linkargs() no longer has any callers, so it is
removed.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfsproc.c | 62 ++++++++++++++++++++++++++++++++---------------
fs/nfsd/nfsxdr.c | 10 --------
fs/nfsd/xdr.h | 8 ------
3 files changed, 43 insertions(+), 37 deletions(-)
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index 755518f08490..c74893060857 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -93,6 +93,14 @@ struct renameargs_wrapper {
static_assert(offsetof(struct renameargs_wrapper, xdrgen) == 0);
+struct linkargs_wrapper {
+ struct linkargs xdrgen;
+ struct svc_fh ffh;
+ struct svc_fh tfh;
+};
+
+static_assert(offsetof(struct linkargs_wrapper, xdrgen) == 0);
+
static __be32 nfsd_map_status(__be32 status)
{
switch (status) {
@@ -915,17 +923,33 @@ static __be32 nfsd_proc_rename(struct svc_rqst *rqstp)
return rpc_success;
}
-static __be32
-nfsd_proc_link(struct svc_rqst *rqstp)
+/**
+ * nfsd_proc_link - LINK: Create Link to an object
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * nfsstat NFSPROC_LINK(linkargs) = 12;
+ */
+static __be32 nfsd_proc_link(struct svc_rqst *rqstp)
{
- struct nfsd_linkargs *argp = rqstp->rq_argp;
- struct nfsd_stat *resp = rqstp->rq_resp;
+ struct linkargs_wrapper *argp = rqstp->rq_argp;
+ struct diropargs *to = &argp->xdrgen.to;
+ nfsstat *resp = rqstp->rq_resp;
+ struct svc_fh *ffhp = &argp->ffh;
+ struct svc_fh *tfhp = &argp->tfh;
- resp->status = nfsd_link(rqstp, &argp->tfh, argp->tname, argp->tlen,
- &argp->ffh);
- fh_put(&argp->ffh);
- fh_put(&argp->tfh);
- resp->status = nfsd_map_status(resp->status);
+ nfsd_fhandle_to_svc_fh(ffhp, &argp->xdrgen.from);
+ nfsd_fhandle_to_svc_fh(tfhp, &to->dir);
+
+ *resp = nfsd_link(rqstp, tfhp, (char *)to->name.data, to->name.len,
+ ffhp);
+ *resp = nfsd_map_status(*resp);
+
+ fh_put(ffhp);
+ fh_put(tfhp);
return rpc_success;
}
@@ -1215,15 +1239,15 @@ static const struct svc_procedure nfsd_procedures2[18] = {
.pc_name = "RENAME",
},
[NFSPROC_LINK] = {
- .pc_func = nfsd_proc_link,
- .pc_decode = nfssvc_decode_linkargs,
- .pc_encode = nfssvc_encode_statres,
- .pc_argsize = sizeof(struct nfsd_linkargs),
- .pc_argzero = sizeof(struct nfsd_linkargs),
- .pc_ressize = sizeof(struct nfsd_stat),
- .pc_cachetype = RC_REPLSTAT,
- .pc_xdrressize = ST,
- .pc_name = "LINK",
+ .pc_func = nfsd_proc_link,
+ .pc_decode = nfs_svc_decode_linkargs,
+ .pc_encode = nfs_svc_encode_nfsstat,
+ .pc_argsize = sizeof(struct linkargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(nfsstat),
+ .pc_cachetype = RC_REPLSTAT,
+ .pc_xdrressize = NFS2_nfsstat_sz,
+ .pc_name = "LINK",
},
[NFSPROC_SYMLINK] = {
.pc_func = nfsd_proc_symlink,
@@ -1293,7 +1317,7 @@ union nfsd_xdrstore {
struct writeargs_wrapper writeargs;
struct createargs_wrapper createargs;
struct renameargs_wrapper renameargs;
- struct nfsd_linkargs link;
+ struct linkargs_wrapper linkargs;
struct nfsd_symlinkargs symlink;
struct nfsd_readdirargs readdir;
struct attrstat_wrapper attrstat;
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
index 63496ad5e2ac..ddbdf10b9982 100644
--- a/fs/nfsd/nfsxdr.c
+++ b/fs/nfsd/nfsxdr.c
@@ -321,16 +321,6 @@ nfssvc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
svcxdr_decode_sattr(rqstp, xdr, &args->attrs);
}
-bool
-nfssvc_decode_linkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd_linkargs *args = rqstp->rq_argp;
-
- return svcxdr_decode_fhandle(xdr, &args->ffh) &&
- svcxdr_decode_diropargs(xdr, &args->tfh,
- &args->tname, &args->tlen);
-}
-
bool
nfssvc_decode_symlinkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
diff --git a/fs/nfsd/xdr.h b/fs/nfsd/xdr.h
index 45582d21624c..2cc1581788d8 100644
--- a/fs/nfsd/xdr.h
+++ b/fs/nfsd/xdr.h
@@ -21,13 +21,6 @@ struct nfsd_createargs {
struct iattr attrs;
};
-struct nfsd_linkargs {
- struct svc_fh ffh;
- struct svc_fh tfh;
- char * tname;
- unsigned int tlen;
-};
-
struct nfsd_symlinkargs {
struct svc_fh ffh;
char * fname;
@@ -81,7 +74,6 @@ struct nfsd_statfsres {
bool nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfssvc_decode_linkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_symlinkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 20/27] NFSD: Use xdrgen XDR functions for NFSv2 SYMLINK procedure
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (20 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 19/27] NFSD: Use xdrgen XDR functions for the NFSv2 LINK procedure Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 21/27] NFSD: Use xdrgen XDR functions for NFSv2 MKDIR procedure Chuck Lever
` (7 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
A SYMLINK request's target pathname can extend from the head of the
receive buffer into its page data; the decoder reassembles it from a
head segment and a page via svc_fill_symlink_pathname(). Represent
the pathname in place rather than materializing it inline: the
"pages" directive expresses it as a struct xdr_buf and decodes it
with svcxdr_decode_opaque_payload(), which subsegments the source
stream instead of pulling the string inline through the bounded xdr
scratch buffer.
Annotate the to member of symlinkargs with "pragma pages" and
replace the NFSPROC_SYMLINK entry in nfsd_procedures2 with the
generated nfs_svc_decode_symlinkargs and nfs_svc_encode_nfsstat. A
wrapper structure bridges the generated symlinkargs type and the
svc_fh and iattr representations the NFSD VFS layer still uses.
nfsd_proc_symlink() copies the pathname out of the xdr_buf with
read_bytes_from_xdr_buf(), so a target that lands in the receive
buffer's tail, or straddles its pages and tail, is handled the same
way as one in the head.
The pc_argzero field is now set to zero for the NFSv2 SYMLINK
procedure. The generated decoder initializes every argument in the
argp->xdrgen field, making the early defensive memset unnecessary.
The hand-coded nfsd_proc_symlink() accepts a target of any length
from the decoder and answers one longer than NFS_MAXPATHLEN with
NFSERR_NAMETOOLONG. The generated decoder enforces the path typedef's
bound itself, so such a request now fails decoding and nfsd_dispatch()
returns RPC GARBAGE_ARGS instead. The Linux NFS client sends targets
up to PAGE_SIZE on an NFSv2 mount, and in that case it reports EIO to
the application rather than ENAMETOOLONG.
nfssvc_decode_symlinkargs() no longer has any callers, so it is
removed.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
Documentation/sunrpc/xdr/nfs2.x | 1 +
fs/nfsd/nfs2xdr_gen.c | 8 ++-
fs/nfsd/nfs2xdr_gen.h | 2 +-
fs/nfsd/nfsproc.c | 102 ++++++++++++++++++++---------
fs/nfsd/nfsxdr.c | 20 ------
fs/nfsd/xdr.h | 11 ----
include/linux/sunrpc/xdrgen/nfs2.h | 4 +-
7 files changed, 81 insertions(+), 67 deletions(-)
diff --git a/Documentation/sunrpc/xdr/nfs2.x b/Documentation/sunrpc/xdr/nfs2.x
index b2ed52bd1631..932968c24d99 100644
--- a/Documentation/sunrpc/xdr/nfs2.x
+++ b/Documentation/sunrpc/xdr/nfs2.x
@@ -178,6 +178,7 @@ struct symlinkargs {
path to;
sattr attributes;
};
+pragma pages symlinkargs to;
struct readdirargs {
fhandle dir;
diff --git a/fs/nfsd/nfs2xdr_gen.c b/fs/nfsd/nfs2xdr_gen.c
index 3c2f78c802a6..0ddfd9bc4a99 100644
--- a/fs/nfsd/nfs2xdr_gen.c
+++ b/fs/nfsd/nfs2xdr_gen.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
// Generated by xdrgen. Manual edits will be lost.
// XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x
-// XDR specification modification time: Tue Sep 8 11:26:00 2026
+// XDR specification modification time: Tue Sep 8 11:26:14 2026
#include <linux/sunrpc/svc.h>
@@ -322,7 +322,7 @@ xdrgen_decode_symlinkargs(struct xdr_stream *xdr, struct symlinkargs *ptr)
{
if (!xdrgen_decode_diropargs(xdr, &ptr->from))
return false;
- if (!xdrgen_decode_path(xdr, &ptr->to))
+ if (!svcxdr_decode_opaque_payload(xdr, &ptr->to, NFS_MAXPATHLEN))
return false;
if (!xdrgen_decode_sattr(xdr, &ptr->attributes))
return false;
@@ -851,7 +851,9 @@ xdrgen_encode_symlinkargs(struct xdr_stream *xdr, const struct symlinkargs *valu
{
if (!xdrgen_encode_diropargs(xdr, &value->from))
return false;
- if (!xdrgen_encode_path(xdr, value->to))
+ if (value->to.len > NFS_MAXPATHLEN)
+ return false;
+ if (!svcxdr_encode_opaque_payload(xdr, value->to.len))
return false;
if (!xdrgen_encode_sattr(xdr, &value->attributes))
return false;
diff --git a/fs/nfsd/nfs2xdr_gen.h b/fs/nfsd/nfs2xdr_gen.h
index 8f7d17e8e483..c3641ba63bb3 100644
--- a/fs/nfsd/nfs2xdr_gen.h
+++ b/fs/nfsd/nfs2xdr_gen.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Generated by xdrgen. Manual edits will be lost. */
/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x */
-/* XDR specification modification time: Tue Sep 8 11:26:00 2026 */
+/* XDR specification modification time: Tue Sep 8 11:26:14 2026 */
#ifndef _LINUX_XDRGEN_NFS2_DECL_H
#define _LINUX_XDRGEN_NFS2_DECL_H
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index c74893060857..830d90d0ef9e 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -101,6 +101,14 @@ struct linkargs_wrapper {
static_assert(offsetof(struct linkargs_wrapper, xdrgen) == 0);
+struct symlinkargs_wrapper {
+ struct symlinkargs xdrgen;
+ struct svc_fh ffh;
+ struct iattr iattrs;
+};
+
+static_assert(offsetof(struct symlinkargs_wrapper, xdrgen) == 0);
+
static __be32 nfsd_map_status(__be32 status)
{
switch (status) {
@@ -953,38 +961,72 @@ static __be32 nfsd_proc_link(struct svc_rqst *rqstp)
return rpc_success;
}
-static __be32
-nfsd_proc_symlink(struct svc_rqst *rqstp)
+static char *nfsd_symlink_target(const struct xdr_buf *to)
{
- struct nfsd_symlinkargs *argp = rqstp->rq_argp;
- struct nfsd_stat *resp = rqstp->rq_resp;
- struct nfsd_attrs attrs = {
- .na_iattr = &argp->attrs,
- };
- struct svc_fh newfh;
+ char *result;
- if (argp->tlen > NFS_MAXPATHLEN) {
- resp->status = nfserr_nametoolong;
+ result = kmalloc(to->len + 1, GFP_KERNEL);
+ if (!result)
+ return ERR_PTR(-ESERVERFAULT);
+ /* The target can span the receive buffer's head, pages, and tail */
+ if (read_bytes_from_xdr_buf(to, 0, result, to->len)) {
+ kfree(result);
+ return ERR_PTR(-ESERVERFAULT);
+ }
+ result[to->len] = '\0';
+
+ /* The VFS rejects a pathname that contains a NUL byte */
+ if (strlen(result) != to->len) {
+ kfree(result);
+ return ERR_PTR(-EINVAL);
+ }
+ return result;
+}
+
+/**
+ * nfsd_proc_symlink - SYMLINK: Create a symbolic link
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * nfsstat NFSPROC_SYMLINK(symlinkargs) = 13;
+ */
+static __be32 nfsd_proc_symlink(struct svc_rqst *rqstp)
+{
+ struct symlinkargs_wrapper *argp = rqstp->rq_argp;
+ struct diropargs *from = &argp->xdrgen.from;
+ struct xdr_buf *to = &argp->xdrgen.to;
+ nfsstat *resp = rqstp->rq_resp;
+ struct svc_fh *fhp = &argp->ffh;
+ struct nfsd_attrs nattrs = {
+ .na_iattr = &argp->iattrs,
+ };
+ struct svc_fh newfh;
+ char *tname;
+
+ nfsd_fhandle_to_svc_fh(fhp, &from->dir);
+ if (!nfsd_sattr_to_iattr(rqstp, &argp->iattrs, &argp->xdrgen.attributes)) {
+ *resp = nfserr_io;
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));
+ tname = nfsd_symlink_target(to);
+ if (IS_ERR(tname)) {
+ *resp = nfserrno(PTR_ERR(tname));
goto out;
}
fh_init(&newfh, NFS_FHSIZE);
- resp->status = nfsd_symlink(rqstp, &argp->ffh, argp->fname, argp->flen,
- argp->tname, &attrs, &newfh);
+ *resp = nfsd_symlink(rqstp, fhp, (char *)from->name.data, from->name.len,
+ tname, &nattrs, &newfh);
+ kfree(tname);
+ *resp = nfsd_map_status(*resp);
- kfree(argp->tname);
- fh_put(&argp->ffh);
fh_put(&newfh);
out:
- resp->status = nfsd_map_status(resp->status);
+ fh_put(fhp);
return rpc_success;
}
@@ -1250,15 +1292,15 @@ static const struct svc_procedure nfsd_procedures2[18] = {
.pc_name = "LINK",
},
[NFSPROC_SYMLINK] = {
- .pc_func = nfsd_proc_symlink,
- .pc_decode = nfssvc_decode_symlinkargs,
- .pc_encode = nfssvc_encode_statres,
- .pc_argsize = sizeof(struct nfsd_symlinkargs),
- .pc_argzero = sizeof(struct nfsd_symlinkargs),
- .pc_ressize = sizeof(struct nfsd_stat),
- .pc_cachetype = RC_REPLSTAT,
- .pc_xdrressize = ST,
- .pc_name = "SYMLINK",
+ .pc_func = nfsd_proc_symlink,
+ .pc_decode = nfs_svc_decode_symlinkargs,
+ .pc_encode = nfs_svc_encode_nfsstat,
+ .pc_argsize = sizeof(struct symlinkargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(nfsstat),
+ .pc_cachetype = RC_REPLSTAT,
+ .pc_xdrressize = NFS2_nfsstat_sz,
+ .pc_name = "SYMLINK",
},
[NFSPROC_MKDIR] = {
.pc_func = nfsd_proc_mkdir,
@@ -1318,7 +1360,7 @@ union nfsd_xdrstore {
struct createargs_wrapper createargs;
struct renameargs_wrapper renameargs;
struct linkargs_wrapper linkargs;
- struct nfsd_symlinkargs symlink;
+ struct symlinkargs_wrapper symlinkargs;
struct nfsd_readdirargs readdir;
struct attrstat_wrapper attrstat;
struct diropres_wrapper diropres;
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
index ddbdf10b9982..eb87ee218f12 100644
--- a/fs/nfsd/nfsxdr.c
+++ b/fs/nfsd/nfsxdr.c
@@ -321,26 +321,6 @@ nfssvc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
svcxdr_decode_sattr(rqstp, xdr, &args->attrs);
}
-bool
-nfssvc_decode_symlinkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd_symlinkargs *args = rqstp->rq_argp;
- struct kvec *head = rqstp->rq_arg.head;
-
- if (!svcxdr_decode_diropargs(xdr, &args->ffh, &args->fname, &args->flen))
- return false;
- if (xdr_stream_decode_u32(xdr, &args->tlen) < 0)
- return false;
- if (args->tlen == 0)
- return false;
-
- args->first.iov_len = head->iov_len - xdr_stream_pos(xdr);
- args->first.iov_base = xdr_inline_decode(xdr, args->tlen);
- if (!args->first.iov_base)
- return false;
- return svcxdr_decode_sattr(rqstp, xdr, &args->attrs);
-}
-
bool
nfssvc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
diff --git a/fs/nfsd/xdr.h b/fs/nfsd/xdr.h
index 2cc1581788d8..eeea28caf19d 100644
--- a/fs/nfsd/xdr.h
+++ b/fs/nfsd/xdr.h
@@ -21,16 +21,6 @@ struct nfsd_createargs {
struct iattr attrs;
};
-struct nfsd_symlinkargs {
- struct svc_fh ffh;
- char * fname;
- unsigned int flen;
- char * tname;
- unsigned int tlen;
- struct iattr attrs;
- struct kvec first;
-};
-
struct nfsd_readdirargs {
struct svc_fh fh;
__u32 cookie;
@@ -74,7 +64,6 @@ struct nfsd_statfsres {
bool nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfssvc_decode_symlinkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_statres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
diff --git a/include/linux/sunrpc/xdrgen/nfs2.h b/include/linux/sunrpc/xdrgen/nfs2.h
index 7a469ac4b772..8f7b79983e3e 100644
--- a/include/linux/sunrpc/xdrgen/nfs2.h
+++ b/include/linux/sunrpc/xdrgen/nfs2.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Generated by xdrgen. Manual edits will be lost. */
/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x */
-/* XDR specification modification time: Tue Sep 8 11:26:00 2026 */
+/* XDR specification modification time: Tue Sep 8 11:26:14 2026 */
#ifndef _LINUX_XDRGEN_NFS2_DEF_H
#define _LINUX_XDRGEN_NFS2_DEF_H
@@ -172,7 +172,7 @@ struct linkargs {
struct symlinkargs {
struct diropargs from;
- path to;
+ struct xdr_buf to;
struct sattr attributes;
};
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 21/27] NFSD: Use xdrgen XDR functions for NFSv2 MKDIR procedure
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (21 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 20/27] NFSD: Use xdrgen XDR functions for NFSv2 SYMLINK procedure Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 22/27] NFSD: Use xdrgen XDR functions for NFSv2 RMDIR procedure Chuck Lever
` (6 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Replace the NFSPROC_MKDIR entry in the nfsd_procedures2 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_createargs and nfs_svc_encode_diropres. MKDIR
reuses the createargs and diropres wrapper structures introduced
for the CREATE conversion, which bridge the generated xdrgen
types and the legacy svc_fh, iattr, and kstat representations the
NFSD VFS layer still uses.
The pc_argzero field is now set to zero for the NFSv2 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.
This refactor replaces the use of svcxdr_encode_fattr(), so the
references to the parent directory and the new object file
handles can be released directly by nfsd_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.
With CREATE and MKDIR both converted, nfssvc_decode_createargs(),
nfssvc_encode_diropres(), and nfssvc_release_diropres() no longer
have any callers, so they are removed, along with the
svcxdr_decode_sattr() and svcxdr_encode_fhandle() helpers they
relied on. The now-unused nfsd_createargs and nfsd_diropres
structures are removed as well.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfsproc.c | 80 ++++++++++++++++-----------
fs/nfsd/nfsxdr.c | 134 ----------------------------------------------
fs/nfsd/xdr.h | 16 ------
3 files changed, 49 insertions(+), 181 deletions(-)
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index 830d90d0ef9e..8317029850c9 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -1030,35 +1030,55 @@ static __be32 nfsd_proc_symlink(struct svc_rqst *rqstp)
return rpc_success;
}
-/*
- * Make directory. This operation is not idempotent.
- * N.B. After this call resp->fh needs an fh_put
+/**
+ * nfsd_proc_mkdir - MKDIR: Create a directory
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * diropres NFSPROC_MKDIR(createargs) = 14;
*/
-static __be32
-nfsd_proc_mkdir(struct svc_rqst *rqstp)
+static __be32 nfsd_proc_mkdir(struct svc_rqst *rqstp)
{
- struct nfsd_createargs *argp = rqstp->rq_argp;
- struct nfsd_diropres *resp = rqstp->rq_resp;
- struct nfsd_attrs attrs = {
- .na_iattr = &argp->attrs,
+ struct createargs_wrapper *argp = rqstp->rq_argp;
+ struct diropargs *where = &argp->xdrgen.where;
+ struct diropres_wrapper *resp = rqstp->rq_resp;
+ struct kstat *statp = &resp->stat;
+ struct svc_fh *dirfhp = &argp->fh;
+ struct svc_fh *fhp = &resp->fh;
+ struct nfsd_attrs nattrs = {
+ .na_iattr = &argp->iattrs,
};
- if (resp->fh.fh_dentry) {
- printk(KERN_WARNING
- "nfsd_proc_mkdir: response already verified??\n");
+ nfsd_fhandle_to_svc_fh(dirfhp, &where->dir);
+ fh_init(fhp, NFS_FHSIZE);
+ if (!nfsd_sattr_to_iattr(rqstp, &argp->iattrs, &argp->xdrgen.attributes)) {
+ resp->xdrgen.status = nfserr_io;
+ goto out;
}
- argp->attrs.ia_valid &= ~ATTR_SIZE;
- fh_init(&resp->fh, NFS_FHSIZE);
- resp->status = nfsd_create(rqstp, &argp->fh, argp->name, argp->len,
- &attrs, S_IFDIR, 0, &resp->fh);
- fh_put(&argp->fh);
- if (resp->status != nfs_ok)
+ argp->iattrs.ia_valid &= ~ATTR_SIZE;
+ resp->xdrgen.status = nfsd_create(rqstp, dirfhp,
+ (char *)where->name.data,
+ where->name.len, &nattrs,
+ S_IFDIR, 0, fhp);
+ if (resp->xdrgen.status != nfs_ok)
goto out;
+ resp->xdrgen.status = fh_getattr(fhp, statp);
- resp->status = fh_getattr(&resp->fh, &resp->stat);
out:
- resp->status = nfsd_map_status(resp->status);
+ if (resp->xdrgen.status == nfs_ok) {
+ nfsd_svc_fh_to_fhandle(&resp->xdrgen.u.diropok.file, fhp);
+ nfsd_stat_to_fattr(rqstp, &resp->xdrgen.u.diropok.attributes,
+ statp, fhp);
+ } else {
+ resp->xdrgen.status = nfsd_map_status(resp->xdrgen.status);
+ }
+
+ fh_put(fhp);
+ fh_put(dirfhp);
return rpc_success;
}
@@ -1303,16 +1323,15 @@ static const struct svc_procedure nfsd_procedures2[18] = {
.pc_name = "SYMLINK",
},
[NFSPROC_MKDIR] = {
- .pc_func = nfsd_proc_mkdir,
- .pc_decode = nfssvc_decode_createargs,
- .pc_encode = nfssvc_encode_diropres,
- .pc_release = nfssvc_release_diropres,
- .pc_argsize = sizeof(struct nfsd_createargs),
- .pc_argzero = sizeof(struct nfsd_createargs),
- .pc_ressize = sizeof(struct nfsd_diropres),
- .pc_cachetype = RC_REPLBUFF,
- .pc_xdrressize = ST+FH+AT,
- .pc_name = "MKDIR",
+ .pc_func = nfsd_proc_mkdir,
+ .pc_decode = nfs_svc_decode_createargs,
+ .pc_encode = nfs_svc_encode_diropres,
+ .pc_argsize = sizeof(struct createargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct diropres_wrapper),
+ .pc_cachetype = RC_REPLBUFF,
+ .pc_xdrressize = NFS2_diropres_sz,
+ .pc_name = "MKDIR",
},
[NFSPROC_RMDIR] = {
.pc_func = nfsd_proc_rmdir,
@@ -1361,7 +1380,6 @@ union nfsd_xdrstore {
struct renameargs_wrapper renameargs;
struct linkargs_wrapper linkargs;
struct symlinkargs_wrapper symlinkargs;
- struct nfsd_readdirargs readdir;
struct attrstat_wrapper attrstat;
struct diropres_wrapper diropres;
struct readlinkres readlinkres;
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
index eb87ee218f12..8989957a94e0 100644
--- a/fs/nfsd/nfsxdr.c
+++ b/fs/nfsd/nfsxdr.c
@@ -13,16 +13,6 @@
#include "nfs2xdr_gen.h"
#include "auth.h"
-/*
- * Sun convention: a sattr time-useconds field of one full second (an
- * otherwise out-of-range value) means "set this time to the current
- * server time." It's needed to make permissions checks for the "touch"
- * program across NFSv2 mounts work correctly. See description of
- * sattr in section 6.1 of "NFS Illustrated" by Brent Callaghan,
- * Addison-Wesley, ISBN 0-201-32750-5
- */
-#define NFS2_SATTR_SET_TO_SERVER_TIME (1000000)
-
/*
* Linux-internal ftype values for socket and unknown inodes, not
* in RFC 1094's wire enum; values match enum nfs_ftype.
@@ -91,19 +81,6 @@ svcxdr_decode_fhandle(struct xdr_stream *xdr, struct svc_fh *fhp)
return true;
}
-static bool
-svcxdr_encode_fhandle(struct xdr_stream *xdr, const struct svc_fh *fhp)
-{
- __be32 *p;
-
- p = xdr_reserve_space(xdr, NFS_FHSIZE);
- if (!p)
- return false;
- memcpy(p, &fhp->fh_handle.fh_raw, NFS_FHSIZE);
-
- return true;
-}
-
static __be32 *
encode_timeval(__be32 *p, const struct timespec64 *time)
{
@@ -147,81 +124,6 @@ svcxdr_decode_diropargs(struct xdr_stream *xdr, struct svc_fh *fhp,
svcxdr_decode_filename(xdr, name, len);
}
-static bool
-svcxdr_decode_sattr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
- struct iattr *iap)
-{
- u32 tmp1, tmp2;
- __be32 *p;
-
- p = xdr_inline_decode(xdr, XDR_UNIT * 8);
- if (!p)
- return false;
-
- iap->ia_valid = 0;
-
- /*
- * Some Sun clients put 0xffff in the mode field when they
- * mean 0xffffffff.
- */
- tmp1 = be32_to_cpup(p++);
- if (tmp1 != (u32)-1 && tmp1 != 0xffff) {
- iap->ia_valid |= ATTR_MODE;
- iap->ia_mode = tmp1;
- }
-
- tmp1 = be32_to_cpup(p++);
- if (tmp1 != (u32)-1) {
- iap->ia_uid = make_kuid(nfsd_user_namespace(rqstp), tmp1);
- if (uid_valid(iap->ia_uid))
- iap->ia_valid |= ATTR_UID;
- }
-
- tmp1 = be32_to_cpup(p++);
- if (tmp1 != (u32)-1) {
- iap->ia_gid = make_kgid(nfsd_user_namespace(rqstp), tmp1);
- if (gid_valid(iap->ia_gid))
- iap->ia_valid |= ATTR_GID;
- }
-
- tmp1 = be32_to_cpup(p++);
- if (tmp1 != (u32)-1) {
- iap->ia_valid |= ATTR_SIZE;
- iap->ia_size = tmp1;
- }
-
- tmp1 = be32_to_cpup(p++);
- tmp2 = be32_to_cpup(p++);
- if (tmp1 != (u32)-1 && tmp2 != (u32)-1) {
- /*
- * Range test here to prevent the multiplication from
- * wrapping to a valid (but incorrect) value on 32-bit
- * platforms.
- */
- if (tmp2 > NFS2_SATTR_SET_TO_SERVER_TIME)
- return false;
- iap->ia_valid |= ATTR_ATIME | ATTR_ATIME_SET;
- iap->ia_atime.tv_sec = tmp1;
- iap->ia_atime.tv_nsec = tmp2 * NSEC_PER_USEC;
- if (tmp2 == NFS2_SATTR_SET_TO_SERVER_TIME)
- iap->ia_valid &= ~ATTR_ATIME_SET;
- }
-
- tmp1 = be32_to_cpup(p++);
- tmp2 = be32_to_cpup(p++);
- if (tmp1 != (u32)-1 && tmp2 != (u32)-1) {
- if (tmp2 > NFS2_SATTR_SET_TO_SERVER_TIME)
- return false;
- iap->ia_valid |= ATTR_MTIME | ATTR_MTIME_SET;
- iap->ia_mtime.tv_sec = tmp1;
- iap->ia_mtime.tv_nsec = tmp2 * NSEC_PER_USEC;
- if (tmp2 == NFS2_SATTR_SET_TO_SERVER_TIME)
- iap->ia_valid &= ~(ATTR_ATIME_SET|ATTR_MTIME_SET);
- }
-
- return true;
-}
-
/**
* svcxdr_encode_fattr - Encode NFSv2 file attributes
* @rqstp: Context of a completed RPC transaction
@@ -311,16 +213,6 @@ nfssvc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
return svcxdr_decode_diropargs(xdr, &args->fh, &args->name, &args->len);
}
-bool
-nfssvc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd_createargs *args = rqstp->rq_argp;
-
- return svcxdr_decode_diropargs(xdr, &args->fh,
- &args->name, &args->len) &&
- svcxdr_decode_sattr(rqstp, xdr, &args->attrs);
-}
-
bool
nfssvc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
@@ -365,25 +257,6 @@ nfssvc_encode_attrstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
return true;
}
-bool
-nfssvc_encode_diropres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd_diropres *resp = rqstp->rq_resp;
-
- if (!svcxdr_encode_stat(xdr, resp->status))
- return false;
- switch (resp->status) {
- case nfs_ok:
- if (!svcxdr_encode_fhandle(xdr, &resp->fh))
- return false;
- if (!svcxdr_encode_fattr(rqstp, xdr, &resp->fh, &resp->stat))
- return false;
- break;
- }
-
- return true;
-}
-
bool
nfssvc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
@@ -528,10 +401,3 @@ void nfssvc_release_attrstat(struct svc_rqst *rqstp)
fh_put(&resp->fh);
}
-
-void nfssvc_release_diropres(struct svc_rqst *rqstp)
-{
- struct nfsd_diropres *resp = rqstp->rq_resp;
-
- fh_put(&resp->fh);
-}
diff --git a/fs/nfsd/xdr.h b/fs/nfsd/xdr.h
index eeea28caf19d..bd3123179f61 100644
--- a/fs/nfsd/xdr.h
+++ b/fs/nfsd/xdr.h
@@ -14,13 +14,6 @@ struct nfsd_diropargs {
unsigned int len;
};
-struct nfsd_createargs {
- struct svc_fh fh;
- char * name;
- unsigned int len;
- struct iattr attrs;
-};
-
struct nfsd_readdirargs {
struct svc_fh fh;
__u32 cookie;
@@ -37,12 +30,6 @@ struct nfsd_attrstat {
struct kstat stat;
};
-struct nfsd_diropres {
- __be32 status;
- struct svc_fh fh;
- struct kstat stat;
-};
-
struct nfsd_readdirres {
/* Components of the reply */
__be32 status;
@@ -63,12 +50,10 @@ struct nfsd_statfsres {
bool nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfssvc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_statres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_attrstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfssvc_encode_diropres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_statfsres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
@@ -77,7 +62,6 @@ int nfssvc_encode_entry(void *data, const char *name, int namlen,
loff_t offset, u64 ino, unsigned int d_type);
void nfssvc_release_attrstat(struct svc_rqst *rqstp);
-void nfssvc_release_diropres(struct svc_rqst *rqstp);
/* Helper functions for NFSv2 ACL code */
bool svcxdr_decode_fhandle(struct xdr_stream *xdr, struct svc_fh *fhp);
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 22/27] NFSD: Use xdrgen XDR functions for NFSv2 RMDIR procedure
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (22 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 21/27] NFSD: Use xdrgen XDR functions for NFSv2 MKDIR procedure Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 23/27] NFSD: Use xdrgen XDR functions for the NFSv2 STATFS procedure Chuck Lever
` (5 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Replace the NFSPROC_RMDIR entry in the nfsd_procedures2 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_diropargs and nfs_svc_encode_nfsstat. A wrapper
structure bridges the generated xdrgen diropargs type and the
legacy svc_fh representation the NFSD VFS layer still uses.
The pc_argzero field is now set to zero for the NFSv2 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.
RMDIR is the last procedure to use the hand-coded
nfssvc_decode_diropargs() and nfssvc_encode_statres(), so they no
longer have any callers and are removed, along with the
svcxdr_decode_diropargs() and svcxdr_decode_filename() helpers
they relied on. The now-unused nfsd_diropargs and nfsd_stat
structures are removed as well.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfsproc.c | 50 +++++++++++++++++++++++++++++------------------
fs/nfsd/nfsxdr.c | 48 ---------------------------------------------
fs/nfsd/xdr.h | 12 ------------
3 files changed, 31 insertions(+), 79 deletions(-)
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index 8317029850c9..ddf743aea89c 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -1082,19 +1082,31 @@ static __be32 nfsd_proc_mkdir(struct svc_rqst *rqstp)
return rpc_success;
}
-/*
- * Remove a directory
+/**
+ * nfsd_proc_rmdir - RMDIR: Remove a directory
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * nfsstat NFSPROC_RMDIR(diropargs) = 15;
*/
-static __be32
-nfsd_proc_rmdir(struct svc_rqst *rqstp)
+static __be32 nfsd_proc_rmdir(struct svc_rqst *rqstp)
{
- struct nfsd_diropargs *argp = rqstp->rq_argp;
- struct nfsd_stat *resp = rqstp->rq_resp;
+ struct diropargs_wrapper *argp = rqstp->rq_argp;
+ struct diropargs *object = &argp->xdrgen;
+ nfsstat *resp = rqstp->rq_resp;
+ struct svc_fh *fhp = &argp->fh;
- resp->status = nfsd_unlink(rqstp, &argp->fh, S_IFDIR,
- argp->name, argp->len);
- fh_put(&argp->fh);
- resp->status = nfsd_map_status(resp->status);
+ nfsd_fhandle_to_svc_fh(fhp, &object->dir);
+
+ /* Unlink. S_IFDIR means file must be a directory */
+ *resp = nfsd_unlink(rqstp, fhp, S_IFDIR,
+ (char *)object->name.data, object->name.len);
+ *resp = nfsd_map_status(*resp);
+
+ fh_put(fhp);
return rpc_success;
}
@@ -1334,15 +1346,15 @@ static const struct svc_procedure nfsd_procedures2[18] = {
.pc_name = "MKDIR",
},
[NFSPROC_RMDIR] = {
- .pc_func = nfsd_proc_rmdir,
- .pc_decode = nfssvc_decode_diropargs,
- .pc_encode = nfssvc_encode_statres,
- .pc_argsize = sizeof(struct nfsd_diropargs),
- .pc_argzero = sizeof(struct nfsd_diropargs),
- .pc_ressize = sizeof(struct nfsd_stat),
- .pc_cachetype = RC_REPLSTAT,
- .pc_xdrressize = ST,
- .pc_name = "RMDIR",
+ .pc_func = nfsd_proc_rmdir,
+ .pc_decode = nfs_svc_decode_diropargs,
+ .pc_encode = nfs_svc_encode_nfsstat,
+ .pc_argsize = sizeof(struct diropargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(nfsstat),
+ .pc_cachetype = RC_REPLSTAT,
+ .pc_xdrressize = NFS2_nfsstat_sz,
+ .pc_name = "RMDIR",
},
[NFSPROC_READDIR] = {
.pc_func = nfsd_proc_readdir,
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
index 8989957a94e0..e839de8c9815 100644
--- a/fs/nfsd/nfsxdr.c
+++ b/fs/nfsd/nfsxdr.c
@@ -92,38 +92,6 @@ encode_timeval(__be32 *p, const struct timespec64 *time)
return p;
}
-static bool
-svcxdr_decode_filename(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 > NFS_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_diropargs(struct xdr_stream *xdr, struct svc_fh *fhp,
- char **name, unsigned int *len)
-{
- return svcxdr_decode_fhandle(xdr, fhp) &&
- svcxdr_decode_filename(xdr, name, len);
-}
-
/**
* svcxdr_encode_fattr - Encode NFSv2 file attributes
* @rqstp: Context of a completed RPC transaction
@@ -205,14 +173,6 @@ nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
return svcxdr_decode_fhandle(xdr, &args->fh);
}
-bool
-nfssvc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd_diropargs *args = rqstp->rq_argp;
-
- return svcxdr_decode_diropargs(xdr, &args->fh, &args->name, &args->len);
-}
-
bool
nfssvc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
@@ -232,14 +192,6 @@ nfssvc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
* XDR encode functions
*/
-bool
-nfssvc_encode_statres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd_stat *resp = rqstp->rq_resp;
-
- return svcxdr_encode_stat(xdr, resp->status);
-}
-
bool
nfssvc_encode_attrstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
diff --git a/fs/nfsd/xdr.h b/fs/nfsd/xdr.h
index bd3123179f61..64e0febe32da 100644
--- a/fs/nfsd/xdr.h
+++ b/fs/nfsd/xdr.h
@@ -8,22 +8,12 @@
#include "vfs.h"
-struct nfsd_diropargs {
- struct svc_fh fh;
- char * name;
- unsigned int len;
-};
-
struct nfsd_readdirargs {
struct svc_fh fh;
__u32 cookie;
__u32 count;
};
-struct nfsd_stat {
- __be32 status;
-};
-
struct nfsd_attrstat {
__be32 status;
struct svc_fh fh;
@@ -49,10 +39,8 @@ struct nfsd_statfsres {
};
bool nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfssvc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfssvc_encode_statres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_attrstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_statfsres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_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 23/27] NFSD: Use xdrgen XDR functions for the NFSv2 STATFS procedure
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (23 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 22/27] NFSD: Use xdrgen XDR functions for NFSv2 RMDIR procedure Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 24/27] NFSD: Use xdrgen XDR functions for NFSv2 READDIR arguments Chuck Lever
` (4 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Replace the NFSPROC_STATFS entry in the nfsd_procedures2 array
with an entry that dispatches the xdrgen-generated
nfs_svc_decode_fhandle and nfs_svc_encode_statfsres. Wrapper
structures bridge the generated xdrgen types and the legacy
svc_fh and kstatfs representations the NFSD VFS layer still uses.
The pc_argzero field is now set to zero for the NFSv2 STATFS
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.
nfsd_proc_statfs() now fills in the statfsres info fields
directly. This makes it straightforward to confirm that the file
handle is released during every error flow.
nfssvc_encode_statfsres() no longer has any callers, so it is
removed.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfsproc.c | 68 +++++++++++++++++++++++++++++++++--------------
fs/nfsd/nfsxdr.c | 25 -----------------
fs/nfsd/xdr.h | 6 -----
3 files changed, 48 insertions(+), 51 deletions(-)
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index ddf743aea89c..327088a1012c 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -109,6 +109,13 @@ struct symlinkargs_wrapper {
static_assert(offsetof(struct symlinkargs_wrapper, xdrgen) == 0);
+struct statfsres_wrapper {
+ struct statfsres xdrgen;
+ struct kstatfs stats;
+};
+
+static_assert(offsetof(struct statfsres_wrapper, xdrgen) == 0);
+
static __be32 nfsd_map_status(__be32 status)
{
switch (status) {
@@ -1154,19 +1161,40 @@ nfsd_proc_readdir(struct svc_rqst *rqstp)
return rpc_success;
}
-/*
- * Get file system info
+/**
+ * nfsd_proc_statfs - STATFS: Get filesystem attributes
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * statfsres NFSPROC_STATFS(fhandle) = 17;
*/
-static __be32
-nfsd_proc_statfs(struct svc_rqst *rqstp)
+static __be32 nfsd_proc_statfs(struct svc_rqst *rqstp)
{
- struct nfsd_fhandle *argp = rqstp->rq_argp;
- struct nfsd_statfsres *resp = rqstp->rq_resp;
+ struct fhandle_wrapper *argp = rqstp->rq_argp;
+ struct statfsres_wrapper *resp = rqstp->rq_resp;
+ struct kstatfs *stat = &resp->stats;
+ struct svc_fh *fhp = &argp->fh;
- resp->status = nfsd_statfs(rqstp, &argp->fh, &resp->stats,
- NFSD_MAY_BYPASS_GSS_ON_ROOT);
- fh_put(&argp->fh);
- resp->status = nfsd_map_status(resp->status);
+ nfsd_fhandle_to_svc_fh(fhp, &argp->xdrgen);
+
+ resp->xdrgen.status = nfsd_statfs(rqstp, fhp, stat,
+ NFSD_MAY_BYPASS_GSS_ON_ROOT);
+ if (resp->xdrgen.status == nfs_ok) {
+ struct info *resinfo = &resp->xdrgen.u.info;
+
+ resinfo->tsize = NFS_MAXDATA;
+ resinfo->bsize = stat->f_bsize;
+ resinfo->blocks = stat->f_blocks;
+ resinfo->bfree = stat->f_bfree;
+ resinfo->bavail = stat->f_bavail;
+ } else {
+ resp->xdrgen.status = nfsd_map_status(resp->xdrgen.status);
+ }
+
+ fh_put(fhp);
return rpc_success;
}
@@ -1367,15 +1395,15 @@ static const struct svc_procedure nfsd_procedures2[18] = {
.pc_name = "READDIR",
},
[NFSPROC_STATFS] = {
- .pc_func = nfsd_proc_statfs,
- .pc_decode = nfssvc_decode_fhandleargs,
- .pc_encode = nfssvc_encode_statfsres,
- .pc_argsize = sizeof(struct nfsd_fhandle),
- .pc_argzero = sizeof(struct nfsd_fhandle),
- .pc_ressize = sizeof(struct nfsd_statfsres),
- .pc_cachetype = RC_NOCACHE,
- .pc_xdrressize = ST+5,
- .pc_name = "STATFS",
+ .pc_func = nfsd_proc_statfs,
+ .pc_decode = nfs_svc_decode_fhandle,
+ .pc_encode = nfs_svc_encode_statfsres,
+ .pc_argsize = sizeof(struct fhandle_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct statfsres_wrapper),
+ .pc_cachetype = RC_NOCACHE,
+ .pc_xdrressize = NFS2_statfsres_sz,
+ .pc_name = "STATFS",
},
};
@@ -1397,7 +1425,7 @@ union nfsd_xdrstore {
struct readlinkres readlinkres;
struct readres readres;
struct nfsd_readdirres readdirres;
- struct nfsd_statfsres statfsres;
+ struct statfsres_wrapper statfsres;
};
const struct svc_version nfsd_version2 = {
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
index e839de8c9815..5da47ba5b465 100644
--- a/fs/nfsd/nfsxdr.c
+++ b/fs/nfsd/nfsxdr.c
@@ -232,31 +232,6 @@ nfssvc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
return true;
}
-bool
-nfssvc_encode_statfsres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd_statfsres *resp = rqstp->rq_resp;
- struct kstatfs *stat = &resp->stats;
- __be32 *p;
-
- if (!svcxdr_encode_stat(xdr, resp->status))
- return false;
- switch (resp->status) {
- case nfs_ok:
- p = xdr_reserve_space(xdr, XDR_UNIT * 5);
- if (!p)
- return false;
- *p++ = cpu_to_be32(NFS_MAXDATA);
- *p++ = cpu_to_be32(stat->f_bsize);
- *p++ = cpu_to_be32(stat->f_blocks);
- *p++ = cpu_to_be32(stat->f_bfree);
- *p = cpu_to_be32(stat->f_bavail);
- break;
- }
-
- return true;
-}
-
/**
* nfssvc_encode_nfscookie - Encode a directory offset cookie
* @resp: readdir result context
diff --git a/fs/nfsd/xdr.h b/fs/nfsd/xdr.h
index 64e0febe32da..6537ea17c63a 100644
--- a/fs/nfsd/xdr.h
+++ b/fs/nfsd/xdr.h
@@ -33,16 +33,10 @@ struct nfsd_readdirres {
unsigned int cookie_offset;
};
-struct nfsd_statfsres {
- __be32 status;
- struct kstatfs stats;
-};
-
bool nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_attrstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfssvc_encode_statfsres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
void nfssvc_encode_nfscookie(struct nfsd_readdirres *resp, u32 offset);
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 24/27] NFSD: Use xdrgen XDR functions for NFSv2 READDIR arguments
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (24 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 23/27] NFSD: Use xdrgen XDR functions for the NFSv2 STATFS procedure Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 25/27] NFSD: Add a streaming directory reader Chuck Lever
` (3 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Replace the NFSPROC_READDIR entry's argument decoder in the
nfsd_procedures2 array with the xdrgen-generated
nfs_svc_decode_readdirargs. A wrapper structure bridges the
generated xdrgen readdirargs type and the legacy svc_fh
representation the NFSD VFS layer still uses.
The pc_argzero field is now set to zero for the NFSv2 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.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfsproc.c | 53 ++++++++++++++++++++++++++++++-----------------
fs/nfsd/nfsxdr.c | 15 --------------
fs/nfsd/xdr.h | 7 -------
3 files changed, 34 insertions(+), 41 deletions(-)
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index 327088a1012c..759bd3fc1254 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -116,6 +116,13 @@ struct statfsres_wrapper {
static_assert(offsetof(struct statfsres_wrapper, xdrgen) == 0);
+struct readdirargs_wrapper {
+ struct readdirargs xdrgen;
+ struct svc_fh fh;
+};
+
+static_assert(offsetof(struct readdirargs_wrapper, xdrgen) == 0);
+
static __be32 nfsd_map_status(__be32 status)
{
switch (status) {
@@ -1135,28 +1142,35 @@ static void nfsd_init_dirlist_pages(struct svc_rqst *rqstp,
xdr_init_encode_pages(xdr, buf);
}
-/*
- * Read a portion of a directory.
+/**
+ * nfsd_proc_readdir - READDIR: Read from directory
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * readdirres NFSPROC_READDIR(readdirargs) = 16;
*/
-static __be32
-nfsd_proc_readdir(struct svc_rqst *rqstp)
+static __be32 nfsd_proc_readdir(struct svc_rqst *rqstp)
{
- struct nfsd_readdirargs *argp = rqstp->rq_argp;
+ struct readdirargs_wrapper *argp = rqstp->rq_argp;
struct nfsd_readdirres *resp = rqstp->rq_resp;
- loff_t offset;
+ loff_t offset = be32_to_cpup((__be32 *)argp->xdrgen.cookie);
+ struct svc_fh *fhp = &argp->fh;
- trace_nfsd_vfs_readdir(rqstp, &argp->fh, argp->count, argp->cookie);
+ nfsd_fhandle_to_svc_fh(fhp, &argp->xdrgen.dir);
+ trace_nfsd_vfs_readdir(rqstp, fhp, argp->xdrgen.count, offset);
- nfsd_init_dirlist_pages(rqstp, resp, argp->count);
+ nfsd_init_dirlist_pages(rqstp, resp, argp->xdrgen.count);
resp->common.err = nfs_ok;
resp->cookie_offset = 0;
- offset = argp->cookie;
- resp->status = nfsd_readdir(rqstp, &argp->fh, &offset,
+ resp->status = nfsd_readdir(rqstp, fhp, &offset,
&resp->common, nfssvc_encode_entry);
nfssvc_encode_nfscookie(resp, offset);
- fh_put(&argp->fh);
+ fh_put(fhp);
resp->status = nfsd_map_status(resp->status);
return rpc_success;
}
@@ -1385,14 +1399,14 @@ static const struct svc_procedure nfsd_procedures2[18] = {
.pc_name = "RMDIR",
},
[NFSPROC_READDIR] = {
- .pc_func = nfsd_proc_readdir,
- .pc_decode = nfssvc_decode_readdirargs,
- .pc_encode = nfssvc_encode_readdirres,
- .pc_argsize = sizeof(struct nfsd_readdirargs),
- .pc_argzero = sizeof(struct nfsd_readdirargs),
- .pc_ressize = sizeof(struct nfsd_readdirres),
- .pc_cachetype = RC_NOCACHE,
- .pc_name = "READDIR",
+ .pc_func = nfsd_proc_readdir,
+ .pc_decode = nfs_svc_decode_readdirargs,
+ .pc_encode = nfssvc_encode_readdirres,
+ .pc_argsize = sizeof(struct readdirargs_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct nfsd_readdirres),
+ .pc_cachetype = RC_NOCACHE,
+ .pc_name = "READDIR",
},
[NFSPROC_STATFS] = {
.pc_func = nfsd_proc_statfs,
@@ -1420,6 +1434,7 @@ union nfsd_xdrstore {
struct renameargs_wrapper renameargs;
struct linkargs_wrapper linkargs;
struct symlinkargs_wrapper symlinkargs;
+ struct readdirargs_wrapper readdirargs;
struct attrstat_wrapper attrstat;
struct diropres_wrapper diropres;
struct readlinkres readlinkres;
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
index 5da47ba5b465..bc35c2fced79 100644
--- a/fs/nfsd/nfsxdr.c
+++ b/fs/nfsd/nfsxdr.c
@@ -173,21 +173,6 @@ nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
return svcxdr_decode_fhandle(xdr, &args->fh);
}
-bool
-nfssvc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd_readdirargs *args = rqstp->rq_argp;
-
- if (!svcxdr_decode_fhandle(xdr, &args->fh))
- return false;
- if (xdr_stream_decode_u32(xdr, &args->cookie) < 0)
- return false;
- if (xdr_stream_decode_u32(xdr, &args->count) < 0)
- return false;
-
- return true;
-}
-
/*
* XDR encode functions
*/
diff --git a/fs/nfsd/xdr.h b/fs/nfsd/xdr.h
index 6537ea17c63a..27976b372cf1 100644
--- a/fs/nfsd/xdr.h
+++ b/fs/nfsd/xdr.h
@@ -8,12 +8,6 @@
#include "vfs.h"
-struct nfsd_readdirargs {
- struct svc_fh fh;
- __u32 cookie;
- __u32 count;
-};
-
struct nfsd_attrstat {
__be32 status;
struct svc_fh fh;
@@ -34,7 +28,6 @@ struct nfsd_readdirres {
};
bool nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfssvc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_attrstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 25/27] NFSD: Add a streaming directory reader
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (25 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 24/27] NFSD: Use xdrgen XDR functions for NFSv2 READDIR arguments Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 26/27] NFSD: Refactor NFSv2 directory cookie encoding Chuck Lever
` (2 subsequent siblings)
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
nfsd_readdir() reads a whole directory in one call, invoking a filldir
actor for every entry. A consumer that wants to read entries while it
encodes the reply -- as NFSv4 READDIR does, and as NFSv2 READDIR wants
to -- cannot pause the loop between entries; it can only stop by
returning nonzero from the actor and reporting eof or a full reply
back through readdir_cd.
Factor the directory read into a pull iterator. nfsd_readdir_open()
opens the directory and seeks to the start cookie, nfsd_readdir_next()
yields one buffered entry at a time and refills from the filesystem as
needed, and nfsd_readdir_close() releases the open file. The reader
holds the file across calls, so a consumer can pull entries during
reply encoding and stop whenever its reply buffer fills.
The page-buffer-per-batch design is retained: iterate_dir() fills a
page through the buffering actor, and entries are handed to the
consumer outside that call, so a consumer that calls ->lookup() (v3
readdirplus) still never reenters iterate_dir().
Reimplement nfsd_readdir() as a thin wrapper over the iterator, so
NFSv3 and NFSv4 keep their existing behavior; a new caller uses the
open/next/close interface directly.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/vfs.c | 239 ++++++++++++++++++++++++++++----------------------
fs/nfsd/vfs.h | 40 +++++++++
2 files changed, 176 insertions(+), 103 deletions(-)
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index f9131827d391..4584d5b94fee 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -2351,33 +2351,18 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
*
* This is based heavily on the implementation of same in XFS.
*/
-struct buffered_dirent {
- u64 ino;
- loff_t offset;
- int namlen;
- unsigned int d_type;
- char name[];
-};
-
-struct readdir_data {
- struct dir_context ctx;
- char *dirent;
- size_t used;
- int full;
-};
-
static bool nfsd_buffered_filldir(struct dir_context *ctx, const char *name,
int namlen, loff_t offset, u64 ino,
unsigned int d_type)
{
- struct readdir_data *buf =
- container_of(ctx, struct readdir_data, ctx);
- struct buffered_dirent *de = (void *)(buf->dirent + buf->used);
+ struct nfsd_readdir_iter *iter =
+ container_of(ctx, struct nfsd_readdir_iter, ctx);
+ struct buffered_dirent *de = (void *)(iter->page + iter->used);
unsigned int reclen;
reclen = ALIGN(sizeof(struct buffered_dirent) + namlen, sizeof(u64));
- if (buf->used + reclen > PAGE_SIZE) {
- buf->full = 1;
+ if (iter->used + reclen > PAGE_SIZE) {
+ iter->full = 1;
return false;
}
@@ -2386,79 +2371,128 @@ static bool nfsd_buffered_filldir(struct dir_context *ctx, const char *name,
de->ino = ino;
de->d_type = d_type;
memcpy(de->name, name, namlen);
- buf->used += reclen;
+ iter->used += reclen;
return true;
}
-static __be32 nfsd_buffered_readdir(struct file *file, struct svc_fh *fhp,
- nfsd_filldir_t func, struct readdir_cd *cdp,
- loff_t *offsetp)
+/**
+ * nfsd_readdir_open - open a directory for streaming readdir
+ * @rqstp: RPC transaction context
+ * @fhp: NFS file handle of directory to be read
+ * @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.
+ *
+ * Return: nfs_ok on success, otherwise an nfsstat code. 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)
{
- struct buffered_dirent *de;
- int host_err;
- int size;
- loff_t offset;
- struct readdir_data buf = {
- .ctx.actor = nfsd_buffered_filldir,
- .dirent = kmalloc(PAGE_SIZE, GFP_KERNEL)
- };
+ struct file *file;
+ loff_t offset = *offsetp;
+ __be32 err;
- if (!buf.dirent)
- return nfserrno(-ENOMEM);
+ memset(iter, 0, sizeof(*iter));
+ iter->ctx.actor = nfsd_buffered_filldir;
- offset = *offsetp;
+ err = nfsd_open(rqstp, fhp, S_IFDIR, NFSD_MAY_READ, &file);
+ if (err)
+ return err;
- while (1) {
- unsigned int reclen;
+ if (fhp->fh_64bit_cookies)
+ file->f_mode |= FMODE_64BITHASH;
+ else
+ file->f_mode |= FMODE_32BITHASH;
- cdp->err = nfserr_eof; /* will be cleared on successful read */
- buf.used = 0;
- buf.full = 0;
-
- host_err = iterate_dir(file, &buf.ctx);
- if (buf.full)
- host_err = 0;
-
- if (host_err < 0)
- break;
-
- size = buf.used;
-
- if (!size)
- break;
-
- de = (struct buffered_dirent *)buf.dirent;
- while (size > 0) {
- offset = de->offset;
-
- if (func(cdp, de->name, de->namlen, de->offset,
- de->ino, de->d_type))
- break;
-
- if (cdp->err != nfs_ok)
- break;
-
- trace_nfsd_dirent(fhp, de->ino, de->name, de->namlen);
-
- reclen = ALIGN(sizeof(*de) + de->namlen,
- sizeof(u64));
- size -= reclen;
- de = (struct buffered_dirent *)((char *)de + reclen);
- }
- if (size > 0) /* We bailed out early */
- break;
-
- offset = vfs_llseek(file, 0, SEEK_CUR);
+ offset = vfs_llseek(file, offset, SEEK_SET);
+ if (offset < 0) {
+ err = nfserrno((int)offset);
+ goto out_close;
}
- kfree((buf.dirent));
+ iter->page = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!iter->page) {
+ err = nfserrno(-ENOMEM);
+ goto out_close;
+ }
- if (host_err)
- return nfserrno(host_err);
+ iter->file = file;
+ iter->fhp = fhp;
+ iter->offset = offset;
+ return nfs_ok;
- *offsetp = offset;
- return cdp->err;
+out_close:
+ nfsd_filp_close(file);
+ return err;
+}
+
+/**
+ * nfsd_readdir_next - yield the next buffered directory entry
+ * @iter: directory reader initialized by nfsd_readdir_open()
+ *
+ * Refill from the filesystem as needed and return the next entry. The
+ * returned pointer is valid until the following nfsd_readdir_next() or
+ * nfsd_readdir_close() call.
+ *
+ * Return: the next entry, or NULL at end of directory or on a host
+ * error. On NULL, @iter->eof marks a clean end and @iter->host_err a
+ * filesystem error; @iter->offset holds the resume cookie either way.
+ */
+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;
+ }
+
+ de = (struct buffered_dirent *)iter->pos;
+ iter->offset = de->offset;
+ reclen = ALIGN(sizeof(*de) + de->namlen, sizeof(u64));
+ iter->pos += reclen;
+ iter->remaining -= reclen;
+ return de;
+}
+
+/**
+ * nfsd_readdir_close - release a directory reader
+ * @iter: directory reader to release
+ *
+ * Safe to call on an @iter that nfsd_readdir_open() left initialized,
+ * whether the open succeeded or failed, and safe to call more than once.
+ */
+void nfsd_readdir_close(struct nfsd_readdir_iter *iter)
+{
+ if (iter->file) {
+ nfsd_filp_close(iter->file);
+ iter->file = NULL;
+ }
+ kfree(iter->page);
+ iter->page = NULL;
}
/**
@@ -2480,37 +2514,36 @@ static __be32 nfsd_buffered_readdir(struct file *file, struct svc_fh *fhp,
* returned.
*/
__be32
-nfsd_readdir(struct svc_rqst *rqstp, struct svc_fh *fhp, loff_t *offsetp,
+nfsd_readdir(struct svc_rqst *rqstp, struct svc_fh *fhp, loff_t *offsetp,
struct readdir_cd *cdp, nfsd_filldir_t func)
{
- __be32 err;
- struct file *file;
- loff_t offset = *offsetp;
- int may_flags = NFSD_MAY_READ;
+ struct nfsd_readdir_iter iter;
+ struct buffered_dirent *de;
+ __be32 err;
- err = nfsd_open(rqstp, fhp, S_IFDIR, may_flags, &file);
+ err = nfsd_readdir_open(rqstp, fhp, offsetp, &iter);
if (err)
- goto out;
+ return err;
- if (fhp->fh_64bit_cookies)
- file->f_mode |= FMODE_64BITHASH;
- else
- file->f_mode |= FMODE_32BITHASH;
-
- offset = vfs_llseek(file, offset, SEEK_SET);
- if (offset < 0) {
- err = nfserrno((int)offset);
- goto out_close;
+ while ((de = nfsd_readdir_next(&iter)) != NULL) {
+ if (func(cdp, de->name, de->namlen, de->offset, de->ino,
+ de->d_type))
+ break;
+ if (cdp->err != nfs_ok)
+ break;
+ trace_nfsd_dirent(fhp, de->ino, de->name, de->namlen);
}
+ if (!de)
+ cdp->err = nfserr_eof; /* clean end, or reported in host_err */
+ *offsetp = iter.offset;
- err = nfsd_buffered_readdir(file, fhp, func, cdp, offsetp);
+ nfsd_readdir_close(&iter);
- if (err == nfserr_eof || err == nfserr_toosmall)
- err = nfs_ok; /* can still be found in ->err */
-out_close:
- nfsd_filp_close(file);
-out:
- return err;
+ if (iter.host_err)
+ return nfserrno(iter.host_err);
+ if (cdp->err == nfserr_eof || cdp->err == nfserr_toosmall)
+ return nfs_ok; /* can still be found in ->err */
+ return cdp->err;
}
/**
diff --git a/fs/nfsd/vfs.h b/fs/nfsd/vfs.h
index f0cb184643f2..38f7d36bd4da 100644
--- a/fs/nfsd/vfs.h
+++ b/fs/nfsd/vfs.h
@@ -59,6 +59,41 @@ struct readdir_cd {
__be32 err; /* nfs_ok, nfserr, or nfserr_eof */
};
+/*
+ * One directory entry buffered out of the underlying filesystem.
+ * Buffering decouples the ->iterate_shared() call from the consumer,
+ * which may call ->lookup() and so must not run inside iterate_dir().
+ */
+struct buffered_dirent {
+ u64 ino;
+ loff_t offset;
+ int namlen;
+ unsigned int d_type;
+ char name[];
+};
+
+/*
+ * Streaming directory reader. nfsd_readdir_open() opens the directory
+ * and seeks to the start cookie, nfsd_readdir_next() yields one entry
+ * at a time (refilling from the filesystem as needed), and
+ * nfsd_readdir_close() releases it. The reader holds the open file, so
+ * a consumer can pull entries while a reply is being encoded.
+ */
+struct nfsd_readdir_iter {
+ struct dir_context ctx; /* drives iterate_dir() */
+ struct file *file; /* open directory */
+ struct svc_fh *fhp; /* directory file handle */
+ char *page; /* one batch of raw entries */
+ size_t used; /* bytes filled this batch */
+ int full; /* batch buffer filled up */
+ char *pos; /* next entry to yield */
+ int remaining; /* bytes left from @pos */
+ loff_t offset; /* resume cookie */
+ int host_err; /* set once iterate_dir() fails */
+ bool batched; /* a batch has been read */
+ bool eof; /* directory exhausted */
+};
+
/* nfsd/vfs.c */
struct nfsd_attrs {
struct iattr *na_iattr; /* input */
@@ -178,6 +213,11 @@ __be32 nfsd_unlink(struct svc_rqst *, struct svc_fh *, int type,
char *name, int len);
__be32 nfsd_readdir(struct svc_rqst *, struct svc_fh *,
loff_t *, struct readdir_cd *, nfsd_filldir_t);
+__be32 nfsd_readdir_open(struct svc_rqst *rqstp, struct svc_fh *fhp,
+ loff_t *offsetp,
+ struct nfsd_readdir_iter *iter);
+struct buffered_dirent *nfsd_readdir_next(struct nfsd_readdir_iter *iter);
+void nfsd_readdir_close(struct nfsd_readdir_iter *iter);
__be32 nfsd_statfs(struct svc_rqst *, struct svc_fh *,
struct kstatfs *, int access);
int nfsd_get_case_info(struct dentry *dentry,
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 26/27] NFSD: Refactor NFSv2 directory cookie encoding
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (26 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 25/27] NFSD: Add a streaming directory reader Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-16 16:28 ` [PATCH v1 27/27] NFSD: Use xdrgen XDR functions for NFSv2 READDIR results Chuck Lever
2026-09-17 11:56 ` [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Jeff Layton
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
The current synopsis of nfssvc_encode_nfscookie() takes a
"struct nfsd_readdirres" argument, which is used by the NFSv2
READDIR procedure.
xdrgen defines a distinct argument structure for this procedure.
With a "struct READDIRargs," the nfscookie encoder will need
generic parameter types in order to be usable.
Note that NFSD can't use the xdrgen-generated nfscookie 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 nfssvc_encode_entry(), immediately after calling
nfssvc_encode_nfscookie(), the code calls
svcxdr_encode_entry_common() which sets a new value for
resp->cookie_offset. The cookie_offset value was immediately
overwritten.
- In nfsd_proc_readdir(), nfssvc_encode_nfscookie() is called as
the last cookie encoding, and no further use of cookie_offset
follows.
- The out_toosmall error path in nfssvc_encode_entry() continues
to set resp->cookie_offset = 0
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfsproc.c | 2 +-
fs/nfsd/nfsxdr.c | 21 ++++++++++-----------
fs/nfsd/xdr.h | 3 ++-
3 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index 759bd3fc1254..df610fe3977d 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -1168,7 +1168,7 @@ static __be32 nfsd_proc_readdir(struct svc_rqst *rqstp)
resp->cookie_offset = 0;
resp->status = nfsd_readdir(rqstp, fhp, &offset,
&resp->common, nfssvc_encode_entry);
- nfssvc_encode_nfscookie(resp, offset);
+ nfssvc_encode_nfscookie(&resp->xdr, resp->cookie_offset, offset);
fh_put(fhp);
resp->status = nfsd_map_status(resp->status);
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
index bc35c2fced79..3476ebe14c45 100644
--- a/fs/nfsd/nfsxdr.c
+++ b/fs/nfsd/nfsxdr.c
@@ -218,23 +218,22 @@ nfssvc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
}
/**
- * nfssvc_encode_nfscookie - Encode a directory offset cookie
- * @resp: readdir result context
- * @offset: offset cookie to encode
+ * nfssvc_encode_nfscookie - Encode a directory cookie
+ * @xdr: stream into which to encode the cookie
+ * @pos: byte position in the stream
+ * @cookie: cookie to be encoded
*
* The buffer space for the offset cookie has already been reserved
* by svcxdr_encode_entry_common().
*/
-void nfssvc_encode_nfscookie(struct nfsd_readdirres *resp, u32 offset)
+void nfssvc_encode_nfscookie(struct xdr_stream *xdr, unsigned int pos,
+ u32 cookie)
{
- __be32 cookie = cpu_to_be32(offset);
+ __be32 wire_cookie = cpu_to_be32(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);
}
static bool
@@ -288,7 +287,7 @@ int nfssvc_encode_entry(void *data, const char *name, int namlen,
unsigned int starting_length = resp->dirlist.len;
/* The offset cookie for the previous entry */
- nfssvc_encode_nfscookie(resp, offset);
+ nfssvc_encode_nfscookie(&resp->xdr, resp->cookie_offset, offset);
if (!svcxdr_encode_entry_common(resp, name, namlen, offset, ino))
goto out_toosmall;
diff --git a/fs/nfsd/xdr.h b/fs/nfsd/xdr.h
index 27976b372cf1..57dbf5166599 100644
--- a/fs/nfsd/xdr.h
+++ b/fs/nfsd/xdr.h
@@ -32,7 +32,8 @@ bool nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_attrstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-void nfssvc_encode_nfscookie(struct nfsd_readdirres *resp, u32 offset);
+void nfssvc_encode_nfscookie(struct xdr_stream *xdr, unsigned int pos,
+ u32 cookie);
int nfssvc_encode_entry(void *data, const char *name, int namlen,
loff_t offset, u64 ino, unsigned int d_type);
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH v1 27/27] NFSD: Use xdrgen XDR functions for NFSv2 READDIR results
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (27 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 26/27] NFSD: Refactor NFSv2 directory cookie encoding Chuck Lever
@ 2026-09-16 16:28 ` Chuck Lever
2026-09-17 11:56 ` [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Jeff Layton
29 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-16 16:28 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
READDIR is the last NFSv2 procedure whose reply is still produced by
a hand-written encoder. Its entry list is the value-follows form of
RFC 1094's "entry *entries" -- each entry prefixed by TRUE, the
sequence closed by FALSE -- streamed straight from the directory as
it is read rather than from a materialized array. The generated
encoder could not drive that: the union encoder writes the reply
status word before the entry list, yet a streamed read does not learn
the status until the directory has been read, and the aggregate loop
was bounded by a count the begin hook had to supply up front.
The preceding patches remove both obstacles. The optional-data
aggregate encoder now pulls entries until the hook signals end, so no
count is needed, and the streaming directory reader yields one entry
at a time during encode. Mark readdirok's entry list as an aggregate
and regenerate; the struct definitions stay the verbatim RFC 1094
types, and the lone "pragma aggregate readdirok entries" selects the
hook-driven codec.
Split the read across the two phases so the generated encoder owns
the framing. nfsd_proc_readdir() opens the directory, where an open
error becomes the reply status the union encoder then writes, and the
encode hooks stream the entries: each pull reserves a placeholder
cookie and back-patches the preceding entry's cookie once the
following entry's resume offset is known; the end hook back-patches
the final cookie and reports eof. nfssvc_release_readdirres() closes
the reader and releases the file handle. The decode hooks are stubs
-- a server never decodes a READDIR result -- present only to satisfy
the generated decoder's linkage.
Retire struct nfsd_readdirres for an embedded struct readdirres so
the RPC dispatch layer can cast the response to its xdrgen type.
The directory's svc_fh moves into that wrapper and struct
readdirargs_wrapper goes away. svc_process() zeroes the result buffer
before decode but leaves the argument buffer as the previous request
left it, and ->pc_release runs even when decoding fails. A file
handle kept in the argument wrapper would reach fh_put() on that path
uninitialized.
The generated encoder reserves reply space through the xdr_stream,
which does not advance rq_next_page as it moves onto the next page.
svcrdma retains only the pages below rq_next_page across Send
completion, so the end hook sets rq_next_page past the last page the
reply uses, as nfsd4_encode_operation() does.
A host error during the streamed read cannot retract entries already
on the wire, so it ends the list as a truncated, non-eof reply rather
than an error status; an error opening the directory is still
reported as the reply status. The bytes on the wire are otherwise
unchanged.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
Documentation/sunrpc/xdr/nfs2.x | 10 ++
fs/nfsd/nfs2xdr_gen.c | 35 ++++-
fs/nfsd/nfs2xdr_gen.h | 13 +-
fs/nfsd/nfsproc.c | 67 +++------
fs/nfsd/nfsxdr.c | 231 +++++++++++++++++++----------
fs/nfsd/xdr.h | 35 +++--
include/linux/sunrpc/xdrgen/nfs2.h | 2 +-
7 files changed, 246 insertions(+), 147 deletions(-)
diff --git a/Documentation/sunrpc/xdr/nfs2.x b/Documentation/sunrpc/xdr/nfs2.x
index 932968c24d99..30e9782c17ce 100644
--- a/Documentation/sunrpc/xdr/nfs2.x
+++ b/Documentation/sunrpc/xdr/nfs2.x
@@ -198,6 +198,16 @@ struct readdirok {
bool eof;
};
+/*
+ * A spike encoding the entry list through the aggregate codec. The
+ * list encodes as the value-follows form -- each entry prefixed by
+ * TRUE, the sequence closed by FALSE -- and the encode hooks stream it
+ * straight from the directory during reply encoding rather than from a
+ * materialized array. The struct definitions above are the verbatim
+ * RFC 1094 types; only this pragma selects the hook-driven codec.
+ */
+pragma aggregate readdirok entries;
+
union readdirres switch (nfsstat status) {
case NFS_OK:
readdirok readdirok;
diff --git a/fs/nfsd/nfs2xdr_gen.c b/fs/nfsd/nfs2xdr_gen.c
index 0ddfd9bc4a99..8a3e8ee8db0e 100644
--- a/fs/nfsd/nfs2xdr_gen.c
+++ b/fs/nfsd/nfs2xdr_gen.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
// Generated by xdrgen. Manual edits will be lost.
// XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x
-// XDR specification modification time: Tue Sep 8 11:26:14 2026
+// XDR specification modification time: Tue Sep 8 11:26:28 2026
#include <linux/sunrpc/svc.h>
@@ -363,11 +363,7 @@ xdrgen_decode_entry(struct xdr_stream *xdr, struct entry *ptr)
static bool __maybe_unused
xdrgen_decode_readdirok(struct xdr_stream *xdr, struct readdirok *ptr)
{
- if (!xdrgen_decode_entry(xdr, ptr->entries))
- return false;
- if (!xdrgen_decode_bool(xdr, &ptr->eof))
- return false;
- return true;
+ return false;
}
static bool __maybe_unused
@@ -892,8 +888,31 @@ xdrgen_encode_entry(struct xdr_stream *xdr, const struct entry *value)
static bool __maybe_unused
xdrgen_encode_readdirok(struct xdr_stream *xdr, const struct readdirok *value)
{
- if (!xdrgen_encode_entry(xdr, value->entries))
- return false;
+ {
+ struct xdrgen_aggregate_cursor cursor = {
+ .xdr = xdr,
+ .member_id = NFS2_READDIROK_ENTRIES,
+ .ctx = xdr->xdrgen_ctx,
+ };
+ bool ok = true;
+
+ if (!nfs2_readdirok_encode_begin(&cursor))
+ return false;
+ for (cursor.index = 0; ; cursor.index++) {
+ struct entry element = {};
+
+ if (!nfs2_readdirok_encode(&cursor, &element))
+ break;
+ if (!xdrgen_encode_entry(xdr, &element)) {
+ ok = false;
+ break;
+ }
+ }
+ if (ok && !xdrgen_encode_entry(xdr, NULL))
+ ok = false;
+ if (!nfs2_readdirok_encode_end(&cursor, ok) || !ok)
+ return false;
+ }
if (!xdrgen_encode_bool(xdr, value->eof))
return false;
return true;
diff --git a/fs/nfsd/nfs2xdr_gen.h b/fs/nfsd/nfs2xdr_gen.h
index c3641ba63bb3..7d259aa31191 100644
--- a/fs/nfsd/nfs2xdr_gen.h
+++ b/fs/nfsd/nfs2xdr_gen.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Generated by xdrgen. Manual edits will be lost. */
/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x */
-/* XDR specification modification time: Tue Sep 8 11:26:14 2026 */
+/* XDR specification modification time: Tue Sep 8 11:26:28 2026 */
#ifndef _LINUX_XDRGEN_NFS2_DECL_H
#define _LINUX_XDRGEN_NFS2_DECL_H
@@ -13,6 +13,17 @@
#include <linux/sunrpc/xdrgen/_builtins.h>
#include <linux/sunrpc/xdrgen/nfs2.h>
+enum {
+ NFS2_READDIROK_ENTRIES,
+};
+
+bool nfs2_readdirok_encode_begin(struct xdrgen_aggregate_cursor *c);
+bool nfs2_readdirok_encode(struct xdrgen_aggregate_cursor *c, struct entry *out);
+bool nfs2_readdirok_encode_end(struct xdrgen_aggregate_cursor *c, bool ok);
+bool nfs2_readdirok_decode_begin(struct xdrgen_aggregate_cursor *c);
+bool nfs2_readdirok_decode(struct xdrgen_aggregate_cursor *c, const struct entry *in);
+bool nfs2_readdirok_decode_end(struct xdrgen_aggregate_cursor *c, bool ok);
+
bool nfs_svc_decode_fhandle(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs_svc_decode_sattrargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs_svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index df610fe3977d..1f74008bc06a 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -116,13 +116,6 @@ struct statfsres_wrapper {
static_assert(offsetof(struct statfsres_wrapper, xdrgen) == 0);
-struct readdirargs_wrapper {
- struct readdirargs xdrgen;
- struct svc_fh fh;
-};
-
-static_assert(offsetof(struct readdirargs_wrapper, xdrgen) == 0);
-
static __be32 nfsd_map_status(__be32 status)
{
switch (status) {
@@ -1124,28 +1117,16 @@ static __be32 nfsd_proc_rmdir(struct svc_rqst *rqstp)
return rpc_success;
}
-static void nfsd_init_dirlist_pages(struct svc_rqst *rqstp,
- struct nfsd_readdirres *resp,
- u32 count)
-{
- struct xdr_buf *buf = &resp->dirlist;
- struct xdr_stream *xdr = &resp->xdr;
-
- memset(buf, 0, sizeof(*buf));
-
- /* Reserve room for the NULL ptr & eof flag (-2 words) */
- buf->buflen = clamp(count, (u32)(XDR_UNIT * 2), (u32)PAGE_SIZE);
- buf->buflen -= XDR_UNIT * 2;
- buf->pages = rqstp->rq_next_page;
- rqstp->rq_next_page++;
-
- xdr_init_encode_pages(xdr, buf);
-}
-
/**
* nfsd_proc_readdir - READDIR: Read from directory
* @rqstp: RPC transaction context
*
+ * The directory is opened here so the reply status reflects any open
+ * error before the generated encoder writes the status word. The
+ * entry list is streamed from the reply wrapper's directory reader
+ * during encode; nfssvc_release_readdirres() closes the reader and
+ * releases the file handle.
+ *
* Return:
* %rpc_success: RPC executed successfully
*
@@ -1154,24 +1135,19 @@ static void nfsd_init_dirlist_pages(struct svc_rqst *rqstp,
*/
static __be32 nfsd_proc_readdir(struct svc_rqst *rqstp)
{
- struct readdirargs_wrapper *argp = rqstp->rq_argp;
- struct nfsd_readdirres *resp = rqstp->rq_resp;
- loff_t offset = be32_to_cpup((__be32 *)argp->xdrgen.cookie);
- struct svc_fh *fhp = &argp->fh;
+ struct readdirargs *argp = rqstp->rq_argp;
+ struct readdirres_wrapper *resp = rqstp->rq_resp;
+ loff_t offset = be32_to_cpup((__be32 *)argp->cookie);
+ struct svc_fh *fhp = &resp->fh;
+ __be32 status;
- nfsd_fhandle_to_svc_fh(fhp, &argp->xdrgen.dir);
- trace_nfsd_vfs_readdir(rqstp, fhp, argp->xdrgen.count, offset);
+ nfsd_fhandle_to_svc_fh(fhp, &argp->dir);
+ trace_nfsd_vfs_readdir(rqstp, fhp, argp->count, offset);
- nfsd_init_dirlist_pages(rqstp, resp, argp->xdrgen.count);
+ resp->count = argp->count;
+ status = nfsd_readdir_open(rqstp, fhp, &offset, &resp->iter);
- resp->common.err = nfs_ok;
- resp->cookie_offset = 0;
- resp->status = nfsd_readdir(rqstp, fhp, &offset,
- &resp->common, nfssvc_encode_entry);
- nfssvc_encode_nfscookie(&resp->xdr, resp->cookie_offset, offset);
-
- fh_put(fhp);
- resp->status = nfsd_map_status(resp->status);
+ resp->xdrgen.status = nfsd_map_status(status);
return rpc_success;
}
@@ -1401,10 +1377,11 @@ static const struct svc_procedure nfsd_procedures2[18] = {
[NFSPROC_READDIR] = {
.pc_func = nfsd_proc_readdir,
.pc_decode = nfs_svc_decode_readdirargs,
- .pc_encode = nfssvc_encode_readdirres,
- .pc_argsize = sizeof(struct readdirargs_wrapper),
+ .pc_encode = nfs_svc_encode_readdirres,
+ .pc_release = nfssvc_release_readdirres,
+ .pc_argsize = sizeof(struct readdirargs),
.pc_argzero = 0,
- .pc_ressize = sizeof(struct nfsd_readdirres),
+ .pc_ressize = sizeof(struct readdirres_wrapper),
.pc_cachetype = RC_NOCACHE,
.pc_name = "READDIR",
},
@@ -1434,12 +1411,12 @@ union nfsd_xdrstore {
struct renameargs_wrapper renameargs;
struct linkargs_wrapper linkargs;
struct symlinkargs_wrapper symlinkargs;
- struct readdirargs_wrapper readdirargs;
+ struct readdirargs readdirargs;
struct attrstat_wrapper attrstat;
struct diropres_wrapper diropres;
struct readlinkres readlinkres;
struct readres readres;
- struct nfsd_readdirres readdirres;
+ struct readdirres_wrapper readdirres;
struct statfsres_wrapper statfsres;
};
diff --git a/fs/nfsd/nfsxdr.c b/fs/nfsd/nfsxdr.c
index 3476ebe14c45..d3c0c84c0e98 100644
--- a/fs/nfsd/nfsxdr.c
+++ b/fs/nfsd/nfsxdr.c
@@ -12,6 +12,7 @@
#include "xdr.h"
#include "nfs2xdr_gen.h"
#include "auth.h"
+#include "trace.h"
/*
* Linux-internal ftype values for socket and unknown inodes, not
@@ -194,37 +195,26 @@ nfssvc_encode_attrstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
return true;
}
-bool
-nfssvc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd_readdirres *resp = rqstp->rq_resp;
- struct xdr_buf *dirlist = &resp->dirlist;
-
- if (!svcxdr_encode_stat(xdr, resp->status))
- return false;
- switch (resp->status) {
- case nfs_ok:
- svcxdr_encode_opaque_pages(rqstp, xdr, dirlist->pages, 0,
- dirlist->len);
- /* no more entries */
- if (xdr_stream_encode_item_absent(xdr) < 0)
- return false;
- if (xdr_stream_encode_bool(xdr, resp->common.err == nfserr_eof) < 0)
- return false;
- break;
- }
-
- return true;
-}
+/*
+ * READDIR reply entry list (RFC 1094). The entry list is the
+ * value-follows form of "entry *entries": each entry prefixed by TRUE,
+ * the sequence closed by FALSE. xdrgen's aggregate codec owns that
+ * framing; the hooks below stream one entry at a time straight from the
+ * directory into the live reply, mirroring nfsd4_encode_dirlist4. The
+ * directory was opened in nfsd_proc_readdir(), so reading and encoding
+ * happen together here during reply encoding.
+ */
/**
- * nfssvc_encode_nfscookie - Encode a directory cookie
- * @xdr: stream into which to encode the cookie
- * @pos: byte position in the stream
- * @cookie: cookie to be encoded
+ * nfssvc_encode_nfscookie - Back-patch a directory entry cookie
+ * @xdr: stream holding the reserved cookie slot
+ * @pos: byte position of the cookie slot, or 0 when there is none
+ * @cookie: cookie value to write
*
- * The buffer space for the offset cookie has already been reserved
- * by svcxdr_encode_entry_common().
+ * An NFSv2 entry's cookie is the offset at which the following entry is
+ * read. That offset is not known until the following entry is pulled,
+ * so each entry is encoded with a placeholder cookie that a later call
+ * overwrites once the resume offset is known.
*/
void nfssvc_encode_nfscookie(struct xdr_stream *xdr, unsigned int pos,
u32 cookie)
@@ -236,71 +226,146 @@ void nfssvc_encode_nfscookie(struct xdr_stream *xdr, unsigned int pos,
write_bytes_to_xdr_buf(xdr->buf, pos, &wire_cookie, XDR_UNIT);
}
-static bool
-svcxdr_encode_entry_common(struct nfsd_readdirres *resp, const char *name,
- int namlen, loff_t offset, u64 ino)
+/**
+ * nfs2_readdirok_encode_begin - size the streaming entry budget
+ * @c: aggregate cursor for the entry list
+ *
+ * Derive the entry byte budget from the space left in the live reply
+ * and the client's count hint, reserving room for the list terminator
+ * and the eof flag that follow the entries.
+ *
+ * Return: true.
+ */
+bool nfs2_readdirok_encode_begin(struct xdrgen_aggregate_cursor *c)
{
- struct xdr_buf *dirlist = &resp->dirlist;
- struct xdr_stream *xdr = &resp->xdr;
-
- if (xdr_stream_encode_item_present(xdr) < 0)
- return false;
- /* fileid */
- if (xdr_stream_encode_u32(xdr, (u32)ino) < 0)
- return false;
- /* name */
- if (xdr_stream_encode_opaque(xdr, name, min(namlen, NFS_MAXNAMLEN)) < 0)
- return false;
- /* cookie */
- resp->cookie_offset = dirlist->len;
- if (xdr_stream_encode_u32(xdr, ~0U) < 0)
- return false;
+ struct svc_rqst *rqstp = c->ctx;
+ struct readdirres_wrapper *resp = rqstp->rq_resp;
+ struct xdr_stream *xdr = c->xdr;
+ int bytes_left;
+ /* Reserve the terminator FALSE and the eof bool (two words). */
+ bytes_left = xdr->buf->buflen - xdr->buf->len - XDR_UNIT * 2;
+ if (bytes_left < 0)
+ bytes_left = 0;
+ resp->space_left = min_t(u32, bytes_left,
+ clamp(resp->count, (u32)(XDR_UNIT * 2),
+ (u32)PAGE_SIZE) - XDR_UNIT * 2);
+ resp->cookie_offset = 0;
return true;
}
/**
- * nfssvc_encode_entry - encode one NFSv2 READDIR entry
- * @data: directory context
- * @name: name of the object to be encoded
- * @namlen: length of that name, in bytes
- * @offset: the offset of the previous entry
- * @ino: the fileid of this entry
- * @d_type: unused
+ * nfs2_readdirok_encode - stream the next READDIR entry
+ * @c: aggregate cursor for the entry list
+ * @out: OUT: entry the framing encodes when one is produced
*
- * Return values:
- * %0: Entry was successfully encoded.
- * %-EINVAL: An encoding problem occurred, secondary status code in resp->common.err
+ * Back-patch the previous entry's placeholder cookie with this entry's
+ * resume offset, then, if the reply budget allows, project the next
+ * directory entry into @out for the generated per-entry encoder.
*
- * On exit, the following fields are updated:
- * - resp->xdr
- * - resp->common.err
- * - resp->cookie_offset
+ * Return: true when @out holds an entry to encode; false to end the
+ * list -- because the directory is exhausted, a host error struck, or
+ * the reply budget filled. On budget exhaustion the previous entry's
+ * cookie already points at the rejected entry, the client's resume
+ * point.
*/
-int nfssvc_encode_entry(void *data, const char *name, int namlen,
- loff_t offset, u64 ino, unsigned int d_type)
+bool nfs2_readdirok_encode(struct xdrgen_aggregate_cursor *c,
+ struct entry *out)
{
- struct readdir_cd *ccd = data;
- struct nfsd_readdirres *resp = container_of(ccd,
- struct nfsd_readdirres,
- common);
- unsigned int starting_length = resp->dirlist.len;
+ struct svc_rqst *rqstp = c->ctx;
+ struct readdirres_wrapper *resp = rqstp->rq_resp;
+ struct xdr_stream *xdr = c->xdr;
+ struct buffered_dirent *de;
+ int namlen;
+ u32 need;
- /* The offset cookie for the previous entry */
- nfssvc_encode_nfscookie(&resp->xdr, resp->cookie_offset, offset);
+ /*
+ * The previous entry was just encoded; commit the stream and note
+ * its cookie slot -- the entry's last XDR word -- so it can be
+ * back-patched once this entry's resume offset is known.
+ */
+ if (c->index) {
+ xdr_commit_encode(xdr);
+ resp->cookie_offset = xdr->buf->len - XDR_UNIT;
+ }
- if (!svcxdr_encode_entry_common(resp, name, namlen, offset, ino))
- goto out_toosmall;
+ de = nfsd_readdir_next(&resp->iter);
+ if (!de)
+ return false;
- xdr_commit_encode(&resp->xdr);
- resp->common.err = nfs_ok;
- return 0;
+ /* The previous entry's cookie is this entry's resume offset. */
+ nfssvc_encode_nfscookie(xdr, resp->cookie_offset, (u32)de->offset);
-out_toosmall:
- resp->cookie_offset = 0;
- resp->common.err = nfserr_toosmall;
- resp->dirlist.len = starting_length;
- return -EINVAL;
+ namlen = min_t(int, de->namlen, NFS_MAXNAMLEN);
+
+ /* value-follows + fileid + name (length + data) + cookie */
+ need = XDR_UNIT * (4 + XDR_QUADLEN(namlen));
+ if (need > resp->space_left)
+ return false;
+ resp->space_left -= need;
+
+ out->fileid = (u32)de->ino;
+ out->name.len = namlen;
+ out->name.data = (unsigned char *)de->name;
+ memset(out->cookie, 0, sizeof(out->cookie)); /* back-patched later */
+
+ trace_nfsd_dirent(resp->iter.fhp, de->ino, de->name, namlen);
+ return true;
+}
+
+/**
+ * nfs2_readdirok_encode_end - finish the streamed entry list
+ * @c: aggregate cursor for the entry list
+ * @ok: false if the framing hit a wire error while encoding the list
+ *
+ * Back-patch the final entry's cookie with the directory's resume
+ * offset and report eof. A list cut short by the reply budget or a
+ * host error reports eof false, so the client reads the rest with a
+ * follow-up request.
+ *
+ * Return: true.
+ */
+bool nfs2_readdirok_encode_end(struct xdrgen_aggregate_cursor *c, bool ok)
+{
+ struct svc_rqst *rqstp = c->ctx;
+ struct readdirres_wrapper *resp = rqstp->rq_resp;
+ struct xdr_stream *xdr = c->xdr;
+
+ if (ok)
+ nfssvc_encode_nfscookie(xdr, resp->cookie_offset,
+ (u32)resp->iter.offset);
+ resp->xdrgen.u.readdirok.eof = resp->iter.eof;
+
+ /*
+ * The xdr_stream primitives don't manage rq_next_page, and
+ * svcrdma retains only the pages below it for Send completion.
+ * The eof word that follows starts a new page when this one is
+ * full.
+ */
+ rqstp->rq_next_page = xdr->page_ptr + 1;
+ if (xdr->p == xdr->end)
+ rqstp->rq_next_page++;
+ return true;
+}
+
+/*
+ * A server never decodes a READDIR result; these satisfy the linkage
+ * of the generated (unused) decoder.
+ */
+bool nfs2_readdirok_decode_begin(struct xdrgen_aggregate_cursor *c)
+{
+ return false;
+}
+
+bool nfs2_readdirok_decode(struct xdrgen_aggregate_cursor *c,
+ const struct entry *in)
+{
+ return false;
+}
+
+bool nfs2_readdirok_decode_end(struct xdrgen_aggregate_cursor *c, bool ok)
+{
+ return false;
}
/*
@@ -312,3 +377,11 @@ void nfssvc_release_attrstat(struct svc_rqst *rqstp)
fh_put(&resp->fh);
}
+
+void nfssvc_release_readdirres(struct svc_rqst *rqstp)
+{
+ struct readdirres_wrapper *resp = rqstp->rq_resp;
+
+ nfsd_readdir_close(&resp->iter);
+ fh_put(&resp->fh);
+}
diff --git a/fs/nfsd/xdr.h b/fs/nfsd/xdr.h
index 57dbf5166599..9da15a9ff242 100644
--- a/fs/nfsd/xdr.h
+++ b/fs/nfsd/xdr.h
@@ -14,28 +14,37 @@ struct nfsd_attrstat {
struct kstat stat;
};
-struct nfsd_readdirres {
- /* Components of the reply */
- __be32 status;
+/*
+ * Declared here rather than in nfsproc.c with the other proc wrappers
+ * because READDIR's encode hooks in nfsxdr.c reference it. The xdrgen
+ * field must be first so the struct can be cast to its XDR type for the
+ * RPC dispatch layer.
+ *
+ * The file handle lives here rather than in an argument wrapper: the
+ * RPC layer zeroes the result before decode, so ->pc_release can run on
+ * a request that failed to decode.
+ */
+struct readdirres_wrapper {
+ struct readdirres xdrgen;
+ struct svc_fh fh;
- int count;
-
- /* Used to encode the reply's entry list */
- struct xdr_stream xdr;
- struct xdr_buf dirlist;
- struct readdir_cd common;
- unsigned int cookie_offset;
+ /* Streaming state for encoding the reply's entry list */
+ struct nfsd_readdir_iter iter; /* directory reader */
+ u32 count; /* client's reply size hint */
+ u32 space_left; /* remaining entry budget */
+ unsigned int cookie_offset; /* prev entry's cookie slot */
};
+static_assert(offsetof(struct readdirres_wrapper, xdrgen) == 0);
+
bool nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_attrstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfssvc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
void nfssvc_encode_nfscookie(struct xdr_stream *xdr, unsigned int pos,
u32 cookie);
-int nfssvc_encode_entry(void *data, const char *name, int namlen,
- loff_t offset, u64 ino, unsigned int d_type);
+
+void nfssvc_release_readdirres(struct svc_rqst *rqstp);
void nfssvc_release_attrstat(struct svc_rqst *rqstp);
diff --git a/include/linux/sunrpc/xdrgen/nfs2.h b/include/linux/sunrpc/xdrgen/nfs2.h
index 8f7b79983e3e..1d26de39ac69 100644
--- a/include/linux/sunrpc/xdrgen/nfs2.h
+++ b/include/linux/sunrpc/xdrgen/nfs2.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Generated by xdrgen. Manual edits will be lost. */
/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs2.x */
-/* XDR specification modification time: Tue Sep 8 11:26:14 2026 */
+/* XDR specification modification time: Tue Sep 8 11:26:28 2026 */
#ifndef _LINUX_XDRGEN_NFS2_DEF_H
#define _LINUX_XDRGEN_NFS2_DEF_H
--
2.55.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen
2026-09-16 16:28 [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Chuck Lever
` (28 preceding siblings ...)
2026-09-16 16:28 ` [PATCH v1 27/27] NFSD: Use xdrgen XDR functions for NFSv2 READDIR results Chuck Lever
@ 2026-09-17 11:56 ` Jeff Layton
2026-09-17 14:57 ` Chuck Lever
29 siblings, 1 reply; 34+ messages in thread
From: Jeff Layton @ 2026-09-17 11:56 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
On Wed, 2026-09-16 at 12:28 -0400, Chuck Lever wrote:
> Replace NFSD's NFSv2 XDR encoders and decoders with code that is
> mechanically generated directly from the NFSv2 RPC language
> specification.
>
> The benefits for NFSv2 are marginal. When we eventually 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.
>
> These patches have been lightly tested with fstests run against an
> xfs export.
>
> Chuck Lever (27):
> Documentation: Add the RPC language description of NFSv2
> NFSD: Add infrastructure for generating NFSv2 XDR encoders and
> decoders
> NFSD: Use xdrgen-generated NFSv2 protocol definitions
> NFSD: Remove '#include "xdr.h"' from fs/nfsd/xdr3.h
> NFSD: Relocate the NFSv2 XDR storage union into nfsproc.c
> NFSD: Use xdrgen XDR functions for the NFSv2 NULL procedure
> NFSD: Use xdrgen XDR functions for NFSv2 GETATTR procedure
> NFSD: Use xdrgen XDR functions for NFSv2 SETATTR procedure
> NFSD: Use xdrgen XDR functions for the NFSv2 ROOT procedure
> NFSD: Use xdrgen XDR functions for the NFSv2 LOOKUP procedure
> NFSD: Use xdrgen XDR functions for NFSv2 READLINK procedure
> NFSD: Use xdrgen XDR functions for NFSv2 READ procedure
> NFSD: Use xdrgen XDR functions for the NFSv2 WRITECACHE procedure
> NFSD: Use xdrgen XDR functions for NFSv2 WRITE procedure
> NFSD: Refactor nfsd_proc_create()
> NFSD: Use xdrgen XDR functions for NFSv2 CREATE procedure
> NFSD: Use xdrgen XDR functions for the NFSv2 REMOVE procedure
> NFSD: Use xdrgen XDR functions for the NFSv2 RENAME procedure
> NFSD: Use xdrgen XDR functions for the NFSv2 LINK procedure
> NFSD: Use xdrgen XDR functions for NFSv2 SYMLINK procedure
> NFSD: Use xdrgen XDR functions for NFSv2 MKDIR procedure
> NFSD: Use xdrgen XDR functions for NFSv2 RMDIR procedure
> NFSD: Use xdrgen XDR functions for the NFSv2 STATFS procedure
> NFSD: Use xdrgen XDR functions for NFSv2 READDIR arguments
> NFSD: Add a streaming directory reader
> NFSD: Refactor NFSv2 directory cookie encoding
> NFSD: Use xdrgen XDR functions for NFSv2 READDIR results
>
> Documentation/sunrpc/xdr/nfs2.x | 254 +++++
> fs/nfs_common/common.c | 5 +-
> fs/nfsd/Makefile | 15 +-
> fs/nfsd/nfs2acl.c | 2 +-
> fs/nfsd/nfs2xdr_gen.c | 1079 +++++++++++++++++++
> fs/nfsd/nfs2xdr_gen.h | 46 +
> fs/nfsd/nfs3xdr.c | 2 +
> fs/nfsd/nfsd.h | 2 -
> fs/nfsd/nfserr.h | 2 +-
> fs/nfsd/nfsfh.h | 5 +
> fs/nfsd/nfsproc.c | 1574 +++++++++++++++++++---------
> fs/nfsd/nfsxdr.c | 605 +++--------
> fs/nfsd/vfs.c | 239 +++--
> fs/nfsd/vfs.h | 40 +
> fs/nfsd/xdr.h | 167 +--
> fs/nfsd/xdr3.h | 3 +-
> include/linux/nfs_common.h | 4 +-
> include/linux/sunrpc/xdrgen/nfs2.h | 298 ++++++
> 18 files changed, 3121 insertions(+), 1221 deletions(-)
> create mode 100644 Documentation/sunrpc/xdr/nfs2.x
> create mode 100644 fs/nfsd/nfs2xdr_gen.c
> create mode 100644 fs/nfsd/nfs2xdr_gen.h
> create mode 100644 include/linux/sunrpc/xdrgen/nfs2.h
This all looks like a fairly sane and straightforward conversion, but
it's too much to review in detail. It's almost a shame to do this at a
time when we're talking about deprecating v2, but if it makes ongoing
maintenance easier, then I'm on board.
Also, there are 2 patches that were sent as part of this series, but
seem to be a separate series:
[PATCH v1 1/2] NFSD: map fh_verify() status codes for NFS_ACLv2 replies
[PATCH v1 2/2] NFSD: map fh_verify() status codes for NFS_ACLv3 replies
Did you mean to send those separately? In any case, for the 27-patch
series:
Acked-by: Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 34+ messages in thread* Re: [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen
2026-09-17 11:56 ` [PATCH v1 00/27] Convert server-side NFSv2 XDR to use xdrgen Jeff Layton
@ 2026-09-17 14:57 ` Chuck Lever
0 siblings, 0 replies; 34+ messages in thread
From: Chuck Lever @ 2026-09-17 14:57 UTC (permalink / raw)
To: Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
On Thu, Sep 17, 2026, at 7:56 AM, Jeff Layton wrote:
> On Wed, 2026-09-16 at 12:28 -0400, Chuck Lever wrote:
>> Replace NFSD's NFSv2 XDR encoders and decoders with code that is
>> mechanically generated directly from the NFSv2 RPC language
>> specification.
>>
>> The benefits for NFSv2 are marginal. When we eventually 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.
>>
>> These patches have been lightly tested with fstests run against an
>> xfs export.
> This all looks like a fairly sane and straightforward conversion, but
> it's too much to review in detail. It's almost a shame to do this at a
> time when we're talking about deprecating v2, but if it makes ongoing
> maintenance easier, then I'm on board.
NFSv3 and NFS_ACL v3 are staying around, and they are currently
fairly intertwined with the NFSv2 XDR code. So part of this
conversion *will* live on even after NFSv2 support is removed,
since it de-tangles NFSv2 and NFSv3 to some extent.
> Also, there are 2 patches that were sent as part of this series, but
> seem to be a separate series:
>
> [PATCH v1 1/2] NFSD: map fh_verify() status codes for NFS_ACLv2 replies
> [PATCH v1 2/2] NFSD: map fh_verify() status codes for NFS_ACLv3 replies
>
> Did you mean to send those separately?
I didn't. Those were already sent and the patch email files
were loitering in the same directory as the 27 NFSv2 conversion
patches.
--
Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)
^ permalink raw reply [flat|nested] 34+ messages in thread