* [PATCH v1 00/17] Convert server-side NFS_ACLv2 XDR to use xdrgen
@ 2026-10-22 13:53 Chuck Lever
2026-10-22 13:53 ` [PATCH v1 01/17] xdrgen: Reject out-of-range unsigned short values on decode Chuck Lever
` (16 more replies)
0 siblings, 17 replies; 18+ messages in thread
From: Chuck Lever @ 2026-10-22 13:53 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Replace NFSD's NFS_ACLv2 XDR encoders and decoders with code that is
mechanically generated from the protocol's RPC language description.
No RFC defines NFS_ACL. The description in nfs_acl2.x is copied from
draft-ietf-nfsv4-nfs-acl.
NFS_ACLv2 is the first consumer of the aggregate codec on a counted
array (already queued for v7.4) and the first on the decode side. The
NFSv2 and NFSv3 READDIR conversions exercise only encode of an
optional-data list.
After this series is applied, NFS_ACLv2 reply statuses follow the
draft specification, which has some changes from the previous ad hoc
code. In addition to replacing the status codes defined by the NFSv2
spec, a well-formed but invalid SETACL argument gets ACL2ERR_INVAL,
where the previous decoder returned GARBAGE_ARGS or accepted it. An
undefined GETACL mask bit gets ACL2ERR_INVAL rather than NFSERR_IO.
Statuses that fh_verify() and fh_getattr() produce with no NFSv2
equivalent no longer reach the wire unmapped.
Chuck Lever (17):
xdrgen: Reject out-of-range unsigned short values on decode
xdrgen: Reject out-of-range short values on decode
nfs_common: Export posix_acl_from_nfsacl for server ACL codecs
NFSD: Remove the unused NFSACL_MAXWORDS macro
NFSD: Move the definition of NFS_ACL_MAX_ENTRIES
Documentation: Add the RPC language description of NFS_ACLv2
NFSD: Add infrastructure for generating NFS_ACLv2 XDR encoders and
decoders
NFSD: Remove '#include "xdr3.h"' from nfs2acl.c
NFSD: Remove '#include "xdr.h"' from nfs2acl.c
NFSD: Switch to using the NFS_ACLv2 status codes
NFSD: Use xdrgen XDR functions for the NFS-ACL v2 NULL procedure
NFSD: Implement NFS-ACL v2 ACL encoding and decoding with xdrgen
NFSD: Use xdrgen XDR functions for the NFS-ACL v2 GETACL procedure
NFSD: Use xdrgen XDR functions for the NFS-ACL v2 SETACL procedure
NFSD: Use xdrgen XDR functions for the NFS-ACL v2 GETATTR procedure
NFSD: Use xdrgen XDR functions for the NFS-ACL v2 ACCESS procedure
NFSD: Map every NFS_ACLv2 reply status into aclstat2
Documentation/sunrpc/xdr/nfs_acl2.x | 283 ++++++++
fs/nfs/nfs3acl.c | 1 +
fs/nfs/nfs3client.c | 1 +
fs/nfs/nfs3xdr.c | 1 +
fs/nfs_common/nfsacl.c | 15 +-
fs/nfsd/Makefile | 15 +-
fs/nfsd/nfs2acl.c | 892 +++++++++++++++++-------
fs/nfsd/nfs3acl.c | 4 +-
fs/nfsd/nfs4xdr.c | 1 +
fs/nfsd/nfs_acl2xdr_gen.c | 869 +++++++++++++++++++++++
fs/nfsd/nfs_acl2xdr_gen.h | 40 ++
fs/nfsd/nfssvc.c | 1 +
include/linux/nfsacl.h | 8 +-
include/linux/sunrpc/xdrgen/_builtins.h | 16 +-
include/linux/sunrpc/xdrgen/nfs_acl2.h | 278 ++++++++
include/uapi/linux/nfsacl.h | 2 +
16 files changed, 2155 insertions(+), 272 deletions(-)
create mode 100644 Documentation/sunrpc/xdr/nfs_acl2.x
create mode 100644 fs/nfsd/nfs_acl2xdr_gen.c
create mode 100644 fs/nfsd/nfs_acl2xdr_gen.h
create mode 100644 include/linux/sunrpc/xdrgen/nfs_acl2.h
--
2.56.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v1 01/17] xdrgen: Reject out-of-range unsigned short values on decode
2026-10-22 13:53 [PATCH v1 00/17] Convert server-side NFS_ACLv2 XDR to use xdrgen Chuck Lever
@ 2026-10-22 13:53 ` Chuck Lever
2026-10-22 13:53 ` [PATCH v1 02/17] xdrgen: Reject out-of-range " Chuck Lever
` (15 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Chuck Lever @ 2026-10-22 13:53 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
RFC 4506 does not define a 16-bit integer type. xdrgen's "unsigned
short" rather follows rpcgen, whose runtime decodes the type in
libtirpc's xdr_u_short() by reading one XDR_UNIT as a u_long and
assigning it to a u_short with a plain cast.
xdrgen_decode_unsigned_short() mirrors that cast, so an XDR_UNIT
carrying a value above U16_MAX decodes as its low sixteen bits. A
decoder's semantic checks on the narrowed value then cannot see the
high bits. NFS_ACLv2 is the first consumer: a SETACL entry whose
perm word is 0x00010007 decodes as perm 7, passes the check for bits
outside rwx, and installs read, write and execute.
Nothing in XDR or rpcgen requires a decoder to accept such a word.
Fail the decode when the wire word exceeds U16_MAX. The server-side
generated code reports the failure as GARBAGE_ARGS, which is the
right verdict for a malformed encoding of a 16-bit type, as opposed
to the ACL2ERR_INVAL that a well-formed but invalid value earns.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
include/linux/sunrpc/xdrgen/_builtins.h | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/include/linux/sunrpc/xdrgen/_builtins.h b/include/linux/sunrpc/xdrgen/_builtins.h
index c9033eb1c829..6b389181dc7b 100644
--- a/include/linux/sunrpc/xdrgen/_builtins.h
+++ b/include/linux/sunrpc/xdrgen/_builtins.h
@@ -88,10 +88,14 @@ static inline bool
xdrgen_decode_unsigned_short(struct xdr_stream *xdr, u16 *ptr)
{
__be32 *p = xdr_inline_decode(xdr, XDR_UNIT);
+ u32 val;
if (unlikely(!p))
return false;
- *ptr = (u16)be32_to_cpup(p);
+ val = be32_to_cpup(p);
+ if (unlikely(val > U16_MAX))
+ return false;
+ *ptr = val;
return true;
}
--
2.56.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v1 02/17] xdrgen: Reject out-of-range short values on decode
2026-10-22 13:53 [PATCH v1 00/17] Convert server-side NFS_ACLv2 XDR to use xdrgen Chuck Lever
2026-10-22 13:53 ` [PATCH v1 01/17] xdrgen: Reject out-of-range unsigned short values on decode Chuck Lever
@ 2026-10-22 13:53 ` Chuck Lever
2026-10-22 13:53 ` [PATCH v1 03/17] nfs_common: Export posix_acl_from_nfsacl for server ACL codecs Chuck Lever
` (14 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Chuck Lever @ 2026-10-22 13:53 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
RFC 4506 does not define a 16-bit integer type. xdrgen's "short"
follows rpcgen, whose runtime decodes the type in libtirpc's
xdr_short() by reading one XDR_UNIT as a long and assigning it to a
short with a plain cast. xdrgen_decode_short() mirrors that cast,
so an XDR_UNIT carrying a value outside the sign-extended 16-bit
range decodes as the low sixteen bits of that word. A decoder's
semantic checks on the narrowed value cannot see the discarded high
bits, the same defect xdrgen_decode_unsigned_short() had for the
unsigned type.
Nothing in XDR or rpcgen requires a decoder to accept such a word.
Fail the decode when the wire word, read as a signed 32-bit value,
lies outside S16_MIN to S16_MAX. xdrgen_encode_short() and
libtirpc's xdr_short() sign-extend an s16 to fill the XDR_UNIT, so
a conforming peer never sends such a word.
No NFS specification uses a signed short, so no generated decoder
calls xdrgen_decode_short() today. The check keeps the signed and
unsigned builtins consistent.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
include/linux/sunrpc/xdrgen/_builtins.h | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/include/linux/sunrpc/xdrgen/_builtins.h b/include/linux/sunrpc/xdrgen/_builtins.h
index 6b389181dc7b..55c514bae5d1 100644
--- a/include/linux/sunrpc/xdrgen/_builtins.h
+++ b/include/linux/sunrpc/xdrgen/_builtins.h
@@ -48,18 +48,20 @@ xdrgen_encode_bool(struct xdr_stream *xdr, bool val)
/*
* De facto (non-standard but commonly implemented) signed short type:
- * - Wire sends sign-extended 32-bit value (e.g., 0xFFFFFFFF)
- * - be32_to_cpup() returns u32 (0xFFFFFFFF)
- * - Explicit (s16) cast truncates to 16 bits (0xFFFF = -1)
+ * 16-bit integer sign-extended to fill one XDR_UNIT.
*/
static inline bool
xdrgen_decode_short(struct xdr_stream *xdr, s16 *ptr)
{
__be32 *p = xdr_inline_decode(xdr, XDR_UNIT);
+ s32 val;
if (unlikely(!p))
return false;
- *ptr = (s16)be32_to_cpup(p);
+ val = be32_to_cpup(p);
+ if (unlikely(val < S16_MIN || val > S16_MAX))
+ return false;
+ *ptr = val;
return true;
}
--
2.56.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v1 03/17] nfs_common: Export posix_acl_from_nfsacl for server ACL codecs
2026-10-22 13:53 [PATCH v1 00/17] Convert server-side NFS_ACLv2 XDR to use xdrgen Chuck Lever
2026-10-22 13:53 ` [PATCH v1 01/17] xdrgen: Reject out-of-range unsigned short values on decode Chuck Lever
2026-10-22 13:53 ` [PATCH v1 02/17] xdrgen: Reject out-of-range " Chuck Lever
@ 2026-10-22 13:53 ` Chuck Lever
2026-10-22 13:53 ` [PATCH v1 04/17] NFSD: Remove the unused NFSACL_MAXWORDS macro Chuck Lever
` (13 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Chuck Lever @ 2026-10-22 13:53 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
The NFS_ACLv2 secattr decode is moving off the hand-rolled
nfs_stream_decode_acl onto generated code driven by application hooks in
nfsd. Those hooks finalize a decoded ACL with the same
Solaris-to-POSIX normalization nfs_stream_decode_acl performs
internally: the entry sort and the removal of the bogus ACL_MASK
entry Solaris adds to a minimal ACL.
Export that normalization and describe its contract so the hook
layer shares one implementation instead of duplicating it. The
still-hand-rolled callers keep using it internally, unchanged.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfs_common/nfsacl.c | 13 ++++++++++---
include/linux/nfsacl.h | 2 ++
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/fs/nfs_common/nfsacl.c b/fs/nfs_common/nfsacl.c
index 24ee7f622b18..09d941303d16 100644
--- a/fs/nfs_common/nfsacl.c
+++ b/fs/nfs_common/nfsacl.c
@@ -288,10 +288,16 @@ cmp_acl_entry(const void *x, const void *y)
return 0;
}
-/*
- * Convert from a Solaris ACL to a POSIX 1003.1e draft 17 ACL.
+/**
+ * posix_acl_from_nfsacl - Normalize a decoded Solaris ACL in place
+ * @acl: a posix_acl decoded from the NFSACL wire form, or NULL
+ *
+ * Sorts the entries into POSIX 1003.1e draft 17 order and removes the
+ * bogus ACL_MASK entry Solaris adds to a minimal ACL.
+ *
+ * Return: 0. Always succeeds; a NULL @acl is a no-op.
*/
-static int
+int
posix_acl_from_nfsacl(struct posix_acl *acl)
{
struct posix_acl_entry *pa, *pe,
@@ -327,6 +333,7 @@ posix_acl_from_nfsacl(struct posix_acl *acl)
}
return 0;
}
+EXPORT_SYMBOL_GPL(posix_acl_from_nfsacl);
/**
* nfsacl_decode - Decode an NFSv3 ACL
diff --git a/include/linux/nfsacl.h b/include/linux/nfsacl.h
index e4155e69c8dd..7d7709a0a56e 100644
--- a/include/linux/nfsacl.h
+++ b/include/linux/nfsacl.h
@@ -45,5 +45,7 @@ nfs_stream_decode_acl(struct xdr_stream *xdr, unsigned int *aclcnt,
extern bool
nfs_stream_encode_acl(struct xdr_stream *xdr, struct inode *inode,
struct posix_acl *acl, int encode_entries, int typeflag);
+extern int
+posix_acl_from_nfsacl(struct posix_acl *acl);
#endif /* __LINUX_NFSACL_H */
--
2.56.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v1 04/17] NFSD: Remove the unused NFSACL_MAXWORDS macro
2026-10-22 13:53 [PATCH v1 00/17] Convert server-side NFS_ACLv2 XDR to use xdrgen Chuck Lever
` (2 preceding siblings ...)
2026-10-22 13:53 ` [PATCH v1 03/17] nfs_common: Export posix_acl_from_nfsacl for server ACL codecs Chuck Lever
@ 2026-10-22 13:53 ` Chuck Lever
2026-10-22 13:53 ` [PATCH v1 05/17] NFSD: Move the definition of NFS_ACL_MAX_ENTRIES Chuck Lever
` (12 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Chuck Lever @ 2026-10-22 13:53 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Commit a257cdd0e217 ("[PATCH] NFSD: Add server support for NFSv3
ACLs.") introduced NFSACL_MAXWORDS, but no code has ever referenced
the macro. Remove it.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
include/linux/nfsacl.h | 1 -
1 file changed, 1 deletion(-)
diff --git a/include/linux/nfsacl.h b/include/linux/nfsacl.h
index 7d7709a0a56e..fc00280e67b1 100644
--- a/include/linux/nfsacl.h
+++ b/include/linux/nfsacl.h
@@ -15,7 +15,6 @@
/* Maximum number of ACL entries over NFS */
#define NFS_ACL_MAX_ENTRIES 1024
-#define NFSACL_MAXWORDS (2*(2+3*NFS_ACL_MAX_ENTRIES))
#define NFSACL_MAXPAGES ((2*(8+12*NFS_ACL_MAX_ENTRIES) + PAGE_SIZE-1) \
>> PAGE_SHIFT)
--
2.56.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v1 05/17] NFSD: Move the definition of NFS_ACL_MAX_ENTRIES
2026-10-22 13:53 [PATCH v1 00/17] Convert server-side NFS_ACLv2 XDR to use xdrgen Chuck Lever
` (3 preceding siblings ...)
2026-10-22 13:53 ` [PATCH v1 04/17] NFSD: Remove the unused NFSACL_MAXWORDS macro Chuck Lever
@ 2026-10-22 13:53 ` Chuck Lever
2026-10-22 13:53 ` [PATCH v1 06/17] Documentation: Add the RPC language description of NFS_ACLv2 Chuck Lever
` (11 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Chuck Lever @ 2026-10-22 13:53 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Separate the inclusion of protocol-defined items from forward
declarations for internal functions. This makes it easier to
convert single source files to use the xdrgen-built headers.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfs/nfs3acl.c | 1 +
fs/nfs/nfs3client.c | 1 +
fs/nfs/nfs3xdr.c | 1 +
fs/nfs_common/nfsacl.c | 2 ++
fs/nfsd/nfs2acl.c | 3 ++-
fs/nfsd/nfs3acl.c | 4 +++-
fs/nfsd/nfs4xdr.c | 1 +
fs/nfsd/nfssvc.c | 1 +
include/linux/nfsacl.h | 5 -----
include/uapi/linux/nfsacl.h | 2 ++
10 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/fs/nfs/nfs3acl.c b/fs/nfs/nfs3acl.c
index a126eb31f62f..e9b9ee7cc354 100644
--- a/fs/nfs/nfs3acl.c
+++ b/fs/nfs/nfs3acl.c
@@ -6,6 +6,7 @@
#include <linux/nfs_fs.h>
#include <linux/posix_acl_xattr.h>
#include <linux/nfsacl.h>
+#include <uapi/linux/nfsacl.h>
#include "internal.h"
#include "nfs3_fs.h"
diff --git a/fs/nfs/nfs3client.c b/fs/nfs/nfs3client.c
index 5d97c1d38bb6..eeb86c8a4a04 100644
--- a/fs/nfs/nfs3client.c
+++ b/fs/nfs/nfs3client.c
@@ -3,6 +3,7 @@
#include <linux/nfs_mount.h>
#include <linux/sunrpc/addr.h>
#include <net/handshake.h>
+#include <uapi/linux/nfsacl.h>
#include "internal.h"
#include "nfs3_fs.h"
#include "netns.h"
diff --git a/fs/nfs/nfs3xdr.c b/fs/nfs/nfs3xdr.c
index e745e78faab0..3251c1434e71 100644
--- a/fs/nfs/nfs3xdr.c
+++ b/fs/nfs/nfs3xdr.c
@@ -21,6 +21,7 @@
#include <linux/nfs3.h>
#include <linux/nfs_fs.h>
#include <linux/nfsacl.h>
+#include <uapi/linux/nfsacl.h>
#include <linux/nfs_common.h>
#include "internal.h"
diff --git a/fs/nfs_common/nfsacl.c b/fs/nfs_common/nfsacl.c
index 09d941303d16..a66294fcfb6e 100644
--- a/fs/nfs_common/nfsacl.c
+++ b/fs/nfs_common/nfsacl.c
@@ -29,6 +29,8 @@
#include <linux/nfs3.h>
#include <linux/sort.h>
+#include <uapi/linux/nfsacl.h>
+
MODULE_DESCRIPTION("NFS ACL support");
MODULE_LICENSE("GPL");
diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
index 14f637d87685..93224144105e 100644
--- a/fs/nfsd/nfs2acl.c
+++ b/fs/nfsd/nfs2acl.c
@@ -7,8 +7,9 @@
#include "nfsd.h"
#include "nfserr.h"
-/* FIXME: nfsacl.h is a broken header */
+
#include <linux/nfsacl.h>
+#include <uapi/linux/nfsacl.h>
#include <linux/gfp.h>
#include "cache.h"
#include "xdr.h"
diff --git a/fs/nfsd/nfs3acl.c b/fs/nfsd/nfs3acl.c
index 324cfdd7b262..58f1297c88f9 100644
--- a/fs/nfsd/nfs3acl.c
+++ b/fs/nfsd/nfs3acl.c
@@ -7,9 +7,11 @@
#include "nfsd.h"
#include "nfserr.h"
-/* FIXME: nfsacl.h is a broken header */
+
#include <linux/nfsacl.h>
+#include <uapi/linux/nfsacl.h>
#include <linux/gfp.h>
+
#include "cache.h"
#include "xdr3.h"
#include "vfs.h"
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index c4fd840da4d9..f08b87d3f469 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -44,6 +44,7 @@
#include <linux/xattr.h>
#include <linux/vmalloc.h>
#include <linux/nfsacl.h>
+#include <uapi/linux/nfsacl.h>
#include <uapi/linux/xattr.h>
diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c
index 067543da3ca0..2d719e9ab152 100644
--- a/fs/nfsd/nfssvc.c
+++ b/fs/nfsd/nfssvc.c
@@ -19,6 +19,7 @@
#include <linux/sunrpc/svc_xprt.h>
#include <linux/lockd/bind.h>
#include <linux/nfsacl.h>
+#include <uapi/linux/nfsacl.h>
#include <linux/nfslocalio.h>
#include <linux/seq_file.h>
#include <linux/inetdevice.h>
diff --git a/include/linux/nfsacl.h b/include/linux/nfsacl.h
index fc00280e67b1..403f5f5144e3 100644
--- a/include/linux/nfsacl.h
+++ b/include/linux/nfsacl.h
@@ -7,13 +7,8 @@
#ifndef __LINUX_NFSACL_H
#define __LINUX_NFSACL_H
-
#include <linux/posix_acl.h>
#include <linux/sunrpc/xdr.h>
-#include <uapi/linux/nfsacl.h>
-
-/* Maximum number of ACL entries over NFS */
-#define NFS_ACL_MAX_ENTRIES 1024
#define NFSACL_MAXPAGES ((2*(8+12*NFS_ACL_MAX_ENTRIES) + PAGE_SIZE-1) \
>> PAGE_SHIFT)
diff --git a/include/uapi/linux/nfsacl.h b/include/uapi/linux/nfsacl.h
index 2c2ad204d3b0..8db7ad6d1dfe 100644
--- a/include/uapi/linux/nfsacl.h
+++ b/include/uapi/linux/nfsacl.h
@@ -19,6 +19,8 @@
#define ACLPROC3_GETACL 1
#define ACLPROC3_SETACL 2
+/* Maximum number of ACL entries over NFS */
+#define NFS_ACL_MAX_ENTRIES 1024
/* Flags for the getacl/setacl mode */
#define NFS_ACL 0x0001
--
2.56.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v1 06/17] Documentation: Add the RPC language description of NFS_ACLv2
2026-10-22 13:53 [PATCH v1 00/17] Convert server-side NFS_ACLv2 XDR to use xdrgen Chuck Lever
` (4 preceding siblings ...)
2026-10-22 13:53 ` [PATCH v1 05/17] NFSD: Move the definition of NFS_ACL_MAX_ENTRIES Chuck Lever
@ 2026-10-22 13:53 ` Chuck Lever
2026-10-22 13:53 ` [PATCH v1 07/17] NFSD: Add infrastructure for generating NFS_ACLv2 XDR encoders and decoders Chuck Lever
` (10 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Chuck Lever @ 2026-10-22 13:53 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: linux-nfs, Chuck Lever
From: Chuck Lever <chuck.lever@oracle.com>
In order to generate source code to encode and decode NFS_ACLv2
protocol elements, include a copy of the RPC language description
of NFS_ACLv2 for xdrgen to process. The language description is
copied from draft-ietf-nfsv4-nfs-acl.
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 <chuck.lever@oracle.com>
---
Documentation/sunrpc/xdr/nfs_acl2.x | 278 ++++++++++++++++++++++++++++
1 file changed, 278 insertions(+)
create mode 100644 Documentation/sunrpc/xdr/nfs_acl2.x
diff --git a/Documentation/sunrpc/xdr/nfs_acl2.x b/Documentation/sunrpc/xdr/nfs_acl2.x
new file mode 100644
index 000000000000..7ed89679e395
--- /dev/null
+++ b/Documentation/sunrpc/xdr/nfs_acl2.x
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2024 IETF Trust and the persons
+ * identified as authors of the code. All rights reserved.
+ *
+ * The authors of the code are:
+ * Oracle
+ *
+ * Redistribution and use in source and binary forms, with
+ * or without modification, are permitted provided that the
+ * following conditions are met:
+ *
+ * - Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * - Neither the name of Internet Society, IETF or IETF
+ * Trust, nor the names of specific contributors, may be
+ * used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
+ * AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+const NFS_ACL_MAX_ENTRIES = 1024;
+
+typedef unsigned int uid;
+typedef unsigned short o_mode;
+
+/*
+ * This is the format of an ACL which is passed over the network.
+ */
+struct aclent {
+ int type;
+ uid id;
+ o_mode perm;
+};
+
+/*
+ * The values for the type element of the aclent structure.
+ */
+const NA_USER_OBJ = 0x1; /* object owner */
+const NA_USER = 0x2; /* additional users */
+const NA_GROUP_OBJ = 0x4; /* owning group of the object */
+const NA_GROUP = 0x8; /* additional groups */
+const NA_CLASS_OBJ = 0x10; /* file group class and mask entry */
+const NA_OTHER_OBJ = 0x20; /* other entry for the object */
+const NA_ACL_DEFAULT = 0x1000; /* default flag */
+
+/*
+ * The bit field values for the perm element of the aclent
+ * structure. The three values can be combined to form any
+ * of the 8 combinations.
+ */
+const NA_READ = 0x4; /* read permission */
+const NA_WRITE = 0x2; /* write permission */
+const NA_EXEC = 0x1; /* exec permission */
+
+/*
+ * This is the structure which contains the ACL entries for a
+ * particular entity. It contains the ACL entries which apply
+ * to this object plus any default ACL entries which are
+ * inherited by its children.
+ *
+ * The values for the mask field are defined below.
+ */
+struct secattr {
+ unsigned int mask;
+ int aclcnt;
+ aclent aclent<NFS_ACL_MAX_ENTRIES>;
+ int dfaclcnt;
+ aclent dfaclent<NFS_ACL_MAX_ENTRIES>;
+};
+
+/*
+ * The values for the mask element of the secattr struct as well
+ * as for the mask element in the arguments in the GETACL2 and
+ * GETACL3 procedures.
+ */
+const NA_ACL = 0x1; /* aclent contains a valid list */
+const NA_ACLCNT = 0x2; /* number of entries in the aclent list */
+const NA_DFACL = 0x4; /* dfaclent contains a valid list */
+const NA_DFACLCNT = 0x8; /* number of entries in the dfaclent list */
+
+/*
+ * Share the ports with the NFS service.
+ */
+const NFS_ACL_PORT = 2049;
+const NFS_ACL_RDMA_PORT = 20049;
+
+/*
+ * XDR data types inherited from the NFS version 2 protocol
+ */
+
+enum ftype {
+ NFNON = 0,
+ NFREG = 1,
+ NFDIR = 2,
+ NFBLK = 3,
+ NFCHR = 4,
+ NFLNK = 5
+};
+
+const FHSIZE = 32;
+typedef opaque fhandle[FHSIZE];
+
+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;
+};
+
+/*
+ * ACL error codes; the numeric values match codes with the same
+ * name used in NFS version 2.
+ */
+enum aclstat2 {
+ ACL2_OK = 0,
+ ACL2ERR_PERM = 1,
+ ACL2ERR_NOENT = 2,
+ ACL2ERR_IO = 5,
+ ACL2ERR_ACCES = 13,
+ ACL2ERR_INVAL = 22,
+ ACL2ERR_NOSPC = 28,
+ ACL2ERR_ROFS = 30,
+ ACL2ERR_NOTSUPP = 45,
+ ACL2ERR_DQUOT = 69,
+ ACL2ERR_STALE = 70
+};
+
+/*
+ * NFS_ACL version 2 procedure arguments and results
+ */
+
+struct GETACL2args {
+ fhandle fh;
+ unsigned int mask;
+};
+
+struct GETACL2resok {
+ fattr attr;
+ secattr acl;
+};
+
+union GETACL2res switch (aclstat2 status) {
+case ACL2_OK:
+ GETACL2resok resok;
+default:
+ void;
+};
+
+struct SETACL2args {
+ fhandle fh;
+ secattr acl;
+};
+
+struct SETACL2resok {
+ fattr attr;
+};
+
+union SETACL2res switch (aclstat2 status) {
+case ACL2_OK:
+ SETACL2resok resok;
+default:
+ void;
+};
+
+struct GETATTR2args {
+ fhandle fh;
+};
+
+struct GETATTR2resok {
+ fattr attr;
+};
+
+union GETATTR2res switch (aclstat2 status) {
+case ACL2_OK:
+ GETATTR2resok resok;
+default:
+ void;
+};
+
+struct ACCESS2args {
+ fhandle fh;
+ unsigned int access;
+};
+
+const ACCESS2_READ = 0x1; /* read data or readdir a directory */
+const ACCESS2_LOOKUP = 0x2; /* lookup a name in a directory */
+const ACCESS2_MODIFY = 0x4; /* rewrite existing file data or */
+ /* modify existing directory entries */
+const ACCESS2_EXTEND = 0x8; /* write new data or add directory entries */
+const ACCESS2_DELETE = 0x10; /* delete existing directory entry */
+const ACCESS2_EXECUTE = 0x20; /* execute file (no meaning for a directory) */
+
+struct ACCESS2resok {
+ fattr attr;
+ unsigned int access;
+};
+
+union ACCESS2res switch (aclstat2 status) {
+case ACL2_OK:
+ ACCESS2resok resok;
+default:
+ void;
+};
+
+/*
+ * This is the definition for the GETXATTRDIR procedure which applies
+ * to NFS Version 2 files.
+ */
+struct GETXATTRDIR2args {
+ fhandle fh;
+ bool create;
+};
+
+struct GETXATTRDIR2resok {
+ fhandle fh;
+ fattr attr;
+};
+
+union GETXATTRDIR2res switch (aclstat2 status) {
+case ACL2_OK:
+ GETXATTRDIR2resok resok;
+default:
+ void;
+};
+
+program NFS_ACL_PROGRAM {
+ version NFS_ACL_V2 {
+ void
+ ACLPROC2_NULL(void) = 0;
+ GETACL2res
+ ACLPROC2_GETACL(GETACL2args) = 1;
+ SETACL2res
+ ACLPROC2_SETACL(SETACL2args) = 2;
+ GETATTR2res
+ ACLPROC2_GETATTR(GETATTR2args) = 3;
+ ACCESS2res
+ ACLPROC2_ACCESS(ACCESS2args) = 4;
+ GETXATTRDIR2res
+ ACLPROC2_GETXATTRDIR(GETXATTRDIR2args) = 5;
+ } = 2;
+} = 100227;
--
2.56.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v1 07/17] NFSD: Add infrastructure for generating NFS_ACLv2 XDR encoders and decoders
2026-10-22 13:53 [PATCH v1 00/17] Convert server-side NFS_ACLv2 XDR to use xdrgen Chuck Lever
` (5 preceding siblings ...)
2026-10-22 13:53 ` [PATCH v1 06/17] Documentation: Add the RPC language description of NFS_ACLv2 Chuck Lever
@ 2026-10-22 13:53 ` Chuck Lever
2026-10-22 13:53 ` [PATCH v1 08/17] NFSD: Remove '#include "xdr3.h"' from nfs2acl.c Chuck Lever
` (9 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Chuck Lever @ 2026-10-22 13:53 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
NFSD's NFS_ACLv2 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
nfs_acl2.x. The generated code can be refreshed any time nfs_acl2.x
is modified by running "(cd fs/nfsd && make xdrgen)"
Signed-off-by: Chuck Lever <cel@kernel.org>
---
Documentation/sunrpc/xdr/nfs_acl2.x | 2 +
fs/nfsd/Makefile | 15 +-
fs/nfsd/nfs_acl2xdr_gen.c | 779 +++++++++++++++++++++++++
fs/nfsd/nfs_acl2xdr_gen.h | 28 +
include/linux/sunrpc/xdrgen/nfs_acl2.h | 278 +++++++++
5 files changed, 1099 insertions(+), 3 deletions(-)
create mode 100644 fs/nfsd/nfs_acl2xdr_gen.c
create mode 100644 fs/nfsd/nfs_acl2xdr_gen.h
create mode 100644 include/linux/sunrpc/xdrgen/nfs_acl2.h
diff --git a/Documentation/sunrpc/xdr/nfs_acl2.x b/Documentation/sunrpc/xdr/nfs_acl2.x
index 7ed89679e395..165f50a898c0 100644
--- a/Documentation/sunrpc/xdr/nfs_acl2.x
+++ b/Documentation/sunrpc/xdr/nfs_acl2.x
@@ -40,6 +40,8 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+pragma header nfs_acl2;
+
const NFS_ACL_MAX_ENTRIES = 1024;
typedef unsigned int uid;
diff --git a/fs/nfsd/Makefile b/fs/nfsd/Makefile
index 1dc449af28f4..7db3b248e15a 100644
--- a/fs/nfsd/Makefile
+++ b/fs/nfsd/Makefile
@@ -15,7 +15,7 @@ nfsd-y += nfssvc.o nfsctl.o nfsfh.o vfs.o \
stats.o filecache.o netlink.o \
nfs3proc.o nfs3xdr.o nfs3xdr_gen.o
nfsd-$(CONFIG_NFSD_V2) += nfsproc.o nfsxdr.o nfs2xdr_gen.o
-nfsd-$(CONFIG_NFSD_V2_ACL) += nfs2acl.o
+nfsd-$(CONFIG_NFSD_V2_ACL) += nfs2acl.o nfs_acl2xdr_gen.o
nfsd-$(CONFIG_NFSD_V3_ACL) += nfs3acl.o
nfsd-$(CONFIG_NFSD_V4) += nfs4proc.o nfs4xdr.o nfs4state.o nfs4idmap.o \
nfs4acl.o nfs4callback.o nfs4recover.o nfs4xdr_gen.o
@@ -38,15 +38,20 @@ nfsd-$(CONFIG_DEBUG_FS) += debugfs.o
.PHONY: xdrgen
XDRGEN_DEFINITIONS = ../../include/linux/sunrpc/xdrgen/nfs2.h \
+ ../../include/linux/sunrpc/xdrgen/nfs_acl2.h \
../../include/linux/sunrpc/xdrgen/nfs3.h \
../../include/linux/sunrpc/xdrgen/nfs4_1.h
-XDRGEN_DECLARATIONS = nfs2xdr_gen.h nfs3xdr_gen.h nfs4xdr_gen.h
-XDRGEN_SOURCE = nfs2xdr_gen.c nfs3xdr_gen.c nfs4xdr_gen.c
+XDRGEN_DECLARATIONS = nfs2xdr_gen.h nfs_acl2xdr_gen.h \
+ nfs3xdr_gen.h nfs4xdr_gen.h
+XDRGEN_SOURCE = nfs2xdr_gen.c nfs_acl2xdr_gen.c \
+ nfs3xdr_gen.c nfs4xdr_gen.c
xdrgen: $(XDRGEN_DEFINITIONS) $(XDRGEN_DECLARATIONS) $(XDRGEN_SOURCE)
../../include/linux/sunrpc/xdrgen/nfs2.h: ../../Documentation/sunrpc/xdr/nfs2.x
../../tools/net/sunrpc/xdrgen/xdrgen definitions $< > $@
+../../include/linux/sunrpc/xdrgen/nfs_acl2.h: ../../Documentation/sunrpc/xdr/nfs_acl2.x
+ ../../tools/net/sunrpc/xdrgen/xdrgen definitions $< > $@
../../include/linux/sunrpc/xdrgen/nfs3.h: ../../Documentation/sunrpc/xdr/nfs3.x
../../tools/net/sunrpc/xdrgen/xdrgen definitions $< > $@
../../include/linux/sunrpc/xdrgen/nfs4_1.h: ../../Documentation/sunrpc/xdr/nfs4_1.x
@@ -54,6 +59,8 @@ xdrgen: $(XDRGEN_DEFINITIONS) $(XDRGEN_DECLARATIONS) $(XDRGEN_SOURCE)
nfs2xdr_gen.h: ../../Documentation/sunrpc/xdr/nfs2.x
../../tools/net/sunrpc/xdrgen/xdrgen declarations $< > $@
+nfs_acl2xdr_gen.h: ../../Documentation/sunrpc/xdr/nfs_acl2.x
+ ../../tools/net/sunrpc/xdrgen/xdrgen declarations $< > $@
nfs3xdr_gen.h: ../../Documentation/sunrpc/xdr/nfs3.x
../../tools/net/sunrpc/xdrgen/xdrgen declarations $< > $@
nfs4xdr_gen.h: ../../Documentation/sunrpc/xdr/nfs4_1.x
@@ -61,6 +68,8 @@ nfs4xdr_gen.h: ../../Documentation/sunrpc/xdr/nfs4_1.x
nfs2xdr_gen.c: ../../Documentation/sunrpc/xdr/nfs2.x
../../tools/net/sunrpc/xdrgen/xdrgen source $< > $@
+nfs_acl2xdr_gen.c: ../../Documentation/sunrpc/xdr/nfs_acl2.x
+ ../../tools/net/sunrpc/xdrgen/xdrgen source $< > $@
nfs3xdr_gen.c: ../../Documentation/sunrpc/xdr/nfs3.x
../../tools/net/sunrpc/xdrgen/xdrgen source $< > $@
nfs4xdr_gen.c: ../../Documentation/sunrpc/xdr/nfs4_1.x
diff --git a/fs/nfsd/nfs_acl2xdr_gen.c b/fs/nfsd/nfs_acl2xdr_gen.c
new file mode 100644
index 000000000000..2c1b6a4e2ebc
--- /dev/null
+++ b/fs/nfsd/nfs_acl2xdr_gen.c
@@ -0,0 +1,779 @@
+// SPDX-License-Identifier: GPL-2.0
+// Generated by xdrgen. Manual edits will be lost.
+// XDR specification file: ../../Documentation/sunrpc/xdr/nfs_acl2.x
+// XDR specification modification time: Wed Jul 15 19:22:39 2026
+
+#include <linux/sunrpc/svc.h>
+
+#include "nfs_acl2xdr_gen.h"
+
+static bool __maybe_unused
+xdrgen_decode_uid(struct xdr_stream *xdr, uid *ptr)
+{
+ return xdrgen_decode_unsigned_int(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_o_mode(struct xdr_stream *xdr, o_mode *ptr)
+{
+ return xdrgen_decode_unsigned_short(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_aclent(struct xdr_stream *xdr, struct aclent *ptr)
+{
+ if (!xdrgen_decode_int(xdr, &ptr->type))
+ return false;
+ if (!xdrgen_decode_uid(xdr, &ptr->id))
+ return false;
+ if (!xdrgen_decode_o_mode(xdr, &ptr->perm))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_secattr(struct xdr_stream *xdr, struct secattr *ptr)
+{
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->mask))
+ return false;
+ if (!xdrgen_decode_int(xdr, &ptr->aclcnt))
+ return false;
+ if (xdr_stream_decode_u32(xdr, &ptr->aclent.count) < 0)
+ return false;
+ if (ptr->aclent.count > NFS_ACL_MAX_ENTRIES)
+ return false;
+ for (u32 i = 0; i < ptr->aclent.count; i++)
+ if (!xdrgen_decode_aclent(xdr, &ptr->aclent.element[i]))
+ return false;
+ if (!xdrgen_decode_int(xdr, &ptr->dfaclcnt))
+ return false;
+ if (xdr_stream_decode_u32(xdr, &ptr->dfaclent.count) < 0)
+ return false;
+ if (ptr->dfaclent.count > NFS_ACL_MAX_ENTRIES)
+ return false;
+ for (u32 i = 0; i < ptr->dfaclent.count; i++)
+ if (!xdrgen_decode_aclent(xdr, &ptr->dfaclent.element[i]))
+ return false;
+ 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, FHSIZE) == 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_aclstat2(struct xdr_stream *xdr, aclstat2 *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 ACL2_OK:
+ case ACL2ERR_PERM:
+ case ACL2ERR_NOENT:
+ case ACL2ERR_IO:
+ case ACL2ERR_ACCES:
+ case ACL2ERR_INVAL:
+ case ACL2ERR_NOSPC:
+ case ACL2ERR_ROFS:
+ case ACL2ERR_NOTSUPP:
+ case ACL2ERR_DQUOT:
+ case ACL2ERR_STALE:
+ break;
+ default:
+ return false;
+ }
+ *ptr = val;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_GETACL2args(struct xdr_stream *xdr, struct GETACL2args *ptr)
+{
+ if (!xdrgen_decode_fhandle(xdr, &ptr->fh))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->mask))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_GETACL2resok(struct xdr_stream *xdr, struct GETACL2resok *ptr)
+{
+ if (!xdrgen_decode_fattr(xdr, &ptr->attr))
+ return false;
+ if (!xdrgen_decode_secattr(xdr, &ptr->acl))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_GETACL2res(struct xdr_stream *xdr, struct GETACL2res *ptr)
+{
+ if (!xdrgen_decode_aclstat2(xdr, &ptr->status))
+ return false;
+ switch (ptr->status) {
+ case ACL2_OK:
+ if (!xdrgen_decode_GETACL2resok(xdr, &ptr->u.resok))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_SETACL2args(struct xdr_stream *xdr, struct SETACL2args *ptr)
+{
+ if (!xdrgen_decode_fhandle(xdr, &ptr->fh))
+ return false;
+ if (!xdrgen_decode_secattr(xdr, &ptr->acl))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_SETACL2resok(struct xdr_stream *xdr, struct SETACL2resok *ptr)
+{
+ if (!xdrgen_decode_fattr(xdr, &ptr->attr))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_SETACL2res(struct xdr_stream *xdr, struct SETACL2res *ptr)
+{
+ if (!xdrgen_decode_aclstat2(xdr, &ptr->status))
+ return false;
+ switch (ptr->status) {
+ case ACL2_OK:
+ if (!xdrgen_decode_SETACL2resok(xdr, &ptr->u.resok))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_GETATTR2args(struct xdr_stream *xdr, struct GETATTR2args *ptr)
+{
+ if (!xdrgen_decode_fhandle(xdr, &ptr->fh))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_GETATTR2resok(struct xdr_stream *xdr, struct GETATTR2resok *ptr)
+{
+ if (!xdrgen_decode_fattr(xdr, &ptr->attr))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_GETATTR2res(struct xdr_stream *xdr, struct GETATTR2res *ptr)
+{
+ if (!xdrgen_decode_aclstat2(xdr, &ptr->status))
+ return false;
+ switch (ptr->status) {
+ case ACL2_OK:
+ if (!xdrgen_decode_GETATTR2resok(xdr, &ptr->u.resok))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_ACCESS2args(struct xdr_stream *xdr, struct ACCESS2args *ptr)
+{
+ if (!xdrgen_decode_fhandle(xdr, &ptr->fh))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->access))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_ACCESS2resok(struct xdr_stream *xdr, struct ACCESS2resok *ptr)
+{
+ if (!xdrgen_decode_fattr(xdr, &ptr->attr))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->access))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_ACCESS2res(struct xdr_stream *xdr, struct ACCESS2res *ptr)
+{
+ if (!xdrgen_decode_aclstat2(xdr, &ptr->status))
+ return false;
+ switch (ptr->status) {
+ case ACL2_OK:
+ if (!xdrgen_decode_ACCESS2resok(xdr, &ptr->u.resok))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_GETXATTRDIR2args(struct xdr_stream *xdr, struct GETXATTRDIR2args *ptr)
+{
+ if (!xdrgen_decode_fhandle(xdr, &ptr->fh))
+ return false;
+ if (!xdrgen_decode_bool(xdr, &ptr->create))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_GETXATTRDIR2resok(struct xdr_stream *xdr, struct GETXATTRDIR2resok *ptr)
+{
+ if (!xdrgen_decode_fhandle(xdr, &ptr->fh))
+ return false;
+ if (!xdrgen_decode_fattr(xdr, &ptr->attr))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_GETXATTRDIR2res(struct xdr_stream *xdr, struct GETXATTRDIR2res *ptr)
+{
+ if (!xdrgen_decode_aclstat2(xdr, &ptr->status))
+ return false;
+ switch (ptr->status) {
+ case ACL2_OK:
+ if (!xdrgen_decode_GETXATTRDIR2resok(xdr, &ptr->u.resok))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+/**
+ * nfs_acl_svc_decode_GETACL2args - Decode a GETACL2args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nfs_acl_svc_decode_GETACL2args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct GETACL2args *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_GETACL2args(xdr, argp);
+}
+
+/**
+ * nfs_acl_svc_decode_SETACL2args - Decode a SETACL2args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nfs_acl_svc_decode_SETACL2args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct SETACL2args *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_SETACL2args(xdr, argp);
+}
+
+/**
+ * nfs_acl_svc_decode_GETATTR2args - Decode a GETATTR2args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nfs_acl_svc_decode_GETATTR2args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct GETATTR2args *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_GETATTR2args(xdr, argp);
+}
+
+/**
+ * nfs_acl_svc_decode_ACCESS2args - Decode a ACCESS2args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nfs_acl_svc_decode_ACCESS2args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct ACCESS2args *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_ACCESS2args(xdr, argp);
+}
+
+/**
+ * nfs_acl_svc_decode_GETXATTRDIR2args - Decode a GETXATTRDIR2args argument
+ * @rqstp: RPC transaction context
+ * @xdr: source XDR data stream
+ *
+ * Return values:
+ * %true: procedure arguments decoded successfully
+ * %false: decode failed
+ */
+bool nfs_acl_svc_decode_GETXATTRDIR2args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct GETXATTRDIR2args *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_GETXATTRDIR2args(xdr, argp);
+}
+
+static bool __maybe_unused
+xdrgen_encode_uid(struct xdr_stream *xdr, const uid value)
+{
+ return xdrgen_encode_unsigned_int(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_o_mode(struct xdr_stream *xdr, const o_mode value)
+{
+ return xdrgen_encode_unsigned_short(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_aclent(struct xdr_stream *xdr, const struct aclent *value)
+{
+ if (!xdrgen_encode_int(xdr, value->type))
+ return false;
+ if (!xdrgen_encode_uid(xdr, value->id))
+ return false;
+ if (!xdrgen_encode_o_mode(xdr, value->perm))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_secattr(struct xdr_stream *xdr, const struct secattr *value)
+{
+ if (!xdrgen_encode_unsigned_int(xdr, value->mask))
+ return false;
+ if (!xdrgen_encode_int(xdr, value->aclcnt))
+ return false;
+ if (value->aclent.count > NFS_ACL_MAX_ENTRIES)
+ return false;
+ if (xdr_stream_encode_u32(xdr, value->aclent.count) != XDR_UNIT)
+ return false;
+ for (u32 i = 0; i < value->aclent.count; i++)
+ if (!xdrgen_encode_aclent(xdr, &value->aclent.element[i]))
+ return false;
+ if (!xdrgen_encode_int(xdr, value->dfaclcnt))
+ return false;
+ if (value->dfaclent.count > NFS_ACL_MAX_ENTRIES)
+ return false;
+ if (xdr_stream_encode_u32(xdr, value->dfaclent.count) != XDR_UNIT)
+ return false;
+ for (u32 i = 0; i < value->dfaclent.count; i++)
+ if (!xdrgen_encode_aclent(xdr, &value->dfaclent.element[i]))
+ return false;
+ return true;
+}
+
+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, FHSIZE) >= 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_aclstat2(struct xdr_stream *xdr, aclstat2 value)
+{
+ return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
+}
+
+static bool __maybe_unused
+xdrgen_encode_GETACL2args(struct xdr_stream *xdr, const struct GETACL2args *value)
+{
+ if (!xdrgen_encode_fhandle(xdr, value->fh))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->mask))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_GETACL2resok(struct xdr_stream *xdr, const struct GETACL2resok *value)
+{
+ if (!xdrgen_encode_fattr(xdr, &value->attr))
+ return false;
+ if (!xdrgen_encode_secattr(xdr, &value->acl))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_GETACL2res(struct xdr_stream *xdr, const struct GETACL2res *ptr)
+{
+ if (!xdrgen_encode_aclstat2(xdr, ptr->status))
+ return false;
+ switch (ptr->status) {
+ case ACL2_OK:
+ if (!xdrgen_encode_GETACL2resok(xdr, &ptr->u.resok))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_SETACL2args(struct xdr_stream *xdr, const struct SETACL2args *value)
+{
+ if (!xdrgen_encode_fhandle(xdr, value->fh))
+ return false;
+ if (!xdrgen_encode_secattr(xdr, &value->acl))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_SETACL2resok(struct xdr_stream *xdr, const struct SETACL2resok *value)
+{
+ if (!xdrgen_encode_fattr(xdr, &value->attr))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_SETACL2res(struct xdr_stream *xdr, const struct SETACL2res *ptr)
+{
+ if (!xdrgen_encode_aclstat2(xdr, ptr->status))
+ return false;
+ switch (ptr->status) {
+ case ACL2_OK:
+ if (!xdrgen_encode_SETACL2resok(xdr, &ptr->u.resok))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_GETATTR2args(struct xdr_stream *xdr, const struct GETATTR2args *value)
+{
+ if (!xdrgen_encode_fhandle(xdr, value->fh))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_GETATTR2resok(struct xdr_stream *xdr, const struct GETATTR2resok *value)
+{
+ if (!xdrgen_encode_fattr(xdr, &value->attr))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_GETATTR2res(struct xdr_stream *xdr, const struct GETATTR2res *ptr)
+{
+ if (!xdrgen_encode_aclstat2(xdr, ptr->status))
+ return false;
+ switch (ptr->status) {
+ case ACL2_OK:
+ if (!xdrgen_encode_GETATTR2resok(xdr, &ptr->u.resok))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_ACCESS2args(struct xdr_stream *xdr, const struct ACCESS2args *value)
+{
+ if (!xdrgen_encode_fhandle(xdr, value->fh))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->access))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_ACCESS2resok(struct xdr_stream *xdr, const struct ACCESS2resok *value)
+{
+ if (!xdrgen_encode_fattr(xdr, &value->attr))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->access))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_ACCESS2res(struct xdr_stream *xdr, const struct ACCESS2res *ptr)
+{
+ if (!xdrgen_encode_aclstat2(xdr, ptr->status))
+ return false;
+ switch (ptr->status) {
+ case ACL2_OK:
+ if (!xdrgen_encode_ACCESS2resok(xdr, &ptr->u.resok))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_GETXATTRDIR2args(struct xdr_stream *xdr, const struct GETXATTRDIR2args *value)
+{
+ if (!xdrgen_encode_fhandle(xdr, value->fh))
+ return false;
+ if (!xdrgen_encode_bool(xdr, value->create))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_GETXATTRDIR2resok(struct xdr_stream *xdr, const struct GETXATTRDIR2resok *value)
+{
+ if (!xdrgen_encode_fhandle(xdr, value->fh))
+ return false;
+ if (!xdrgen_encode_fattr(xdr, &value->attr))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_GETXATTRDIR2res(struct xdr_stream *xdr, const struct GETXATTRDIR2res *ptr)
+{
+ if (!xdrgen_encode_aclstat2(xdr, ptr->status))
+ return false;
+ switch (ptr->status) {
+ case ACL2_OK:
+ if (!xdrgen_encode_GETXATTRDIR2resok(xdr, &ptr->u.resok))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+/**
+ * nfs_acl_svc_encode_GETACL2res - Encode a GETACL2res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ * %true: procedure results encoded successfully
+ * %false: encode failed
+ */
+bool nfs_acl_svc_encode_GETACL2res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct GETACL2res *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_GETACL2res(xdr, resp);
+}
+
+/**
+ * nfs_acl_svc_encode_SETACL2res - Encode a SETACL2res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ * %true: procedure results encoded successfully
+ * %false: encode failed
+ */
+bool nfs_acl_svc_encode_SETACL2res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct SETACL2res *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_SETACL2res(xdr, resp);
+}
+
+/**
+ * nfs_acl_svc_encode_GETATTR2res - Encode a GETATTR2res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ * %true: procedure results encoded successfully
+ * %false: encode failed
+ */
+bool nfs_acl_svc_encode_GETATTR2res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct GETATTR2res *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_GETATTR2res(xdr, resp);
+}
+
+/**
+ * nfs_acl_svc_encode_ACCESS2res - Encode a ACCESS2res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ * %true: procedure results encoded successfully
+ * %false: encode failed
+ */
+bool nfs_acl_svc_encode_ACCESS2res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct ACCESS2res *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_ACCESS2res(xdr, resp);
+}
+
+/**
+ * nfs_acl_svc_encode_GETXATTRDIR2res - Encode a GETXATTRDIR2res result
+ * @rqstp: RPC transaction context
+ * @xdr: target XDR data stream
+ *
+ * Return values:
+ * %true: procedure results encoded successfully
+ * %false: encode failed
+ */
+bool nfs_acl_svc_encode_GETXATTRDIR2res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct GETXATTRDIR2res *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_GETXATTRDIR2res(xdr, resp);
+}
diff --git a/fs/nfsd/nfs_acl2xdr_gen.h b/fs/nfsd/nfs_acl2xdr_gen.h
new file mode 100644
index 000000000000..0152aa0b6695
--- /dev/null
+++ b/fs/nfsd/nfs_acl2xdr_gen.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Generated by xdrgen. Manual edits will be lost. */
+/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs_acl2.x */
+/* XDR specification modification time: Wed Jul 15 19:22:39 2026 */
+
+#ifndef _LINUX_XDRGEN_NFS_ACL2_DECL_H
+#define _LINUX_XDRGEN_NFS_ACL2_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/nfs_acl2.h>
+
+bool nfs_acl_svc_decode_GETACL2args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_acl_svc_decode_SETACL2args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_acl_svc_decode_GETATTR2args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_acl_svc_decode_ACCESS2args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_acl_svc_decode_GETXATTRDIR2args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+
+bool nfs_acl_svc_encode_GETACL2res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_acl_svc_encode_SETACL2res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_acl_svc_encode_GETATTR2res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_acl_svc_encode_ACCESS2res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_acl_svc_encode_GETXATTRDIR2res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+
+#endif /* _LINUX_XDRGEN_NFS_ACL2_DECL_H */
diff --git a/include/linux/sunrpc/xdrgen/nfs_acl2.h b/include/linux/sunrpc/xdrgen/nfs_acl2.h
new file mode 100644
index 000000000000..2069015d0c14
--- /dev/null
+++ b/include/linux/sunrpc/xdrgen/nfs_acl2.h
@@ -0,0 +1,278 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Generated by xdrgen. Manual edits will be lost. */
+/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs_acl2.x */
+/* XDR specification modification time: Wed Jul 15 19:22:39 2026 */
+
+#ifndef _LINUX_XDRGEN_NFS_ACL2_DEF_H
+#define _LINUX_XDRGEN_NFS_ACL2_DEF_H
+
+#include <linux/types.h>
+#include <linux/sunrpc/xdrgen/_defs.h>
+
+enum { NFS_ACL_MAX_ENTRIES = 1024 };
+
+typedef u32 uid;
+
+typedef u16 o_mode;
+
+struct aclent {
+ s32 type;
+ uid id;
+ o_mode perm;
+};
+
+enum { NA_USER_OBJ = 0x1 };
+
+enum { NA_USER = 0x2 };
+
+enum { NA_GROUP_OBJ = 0x4 };
+
+enum { NA_GROUP = 0x8 };
+
+enum { NA_CLASS_OBJ = 0x10 };
+
+enum { NA_OTHER_OBJ = 0x20 };
+
+enum { NA_ACL_DEFAULT = 0x1000 };
+
+enum { NA_READ = 0x4 };
+
+enum { NA_WRITE = 0x2 };
+
+enum { NA_EXEC = 0x1 };
+
+struct secattr {
+ u32 mask;
+ s32 aclcnt;
+ struct {
+ u32 count;
+ struct aclent *element;
+ } aclent;
+ s32 dfaclcnt;
+ struct {
+ u32 count;
+ struct aclent *element;
+ } dfaclent;
+};
+
+enum { NA_ACL = 0x1 };
+
+enum { NA_ACLCNT = 0x2 };
+
+enum { NA_DFACL = 0x4 };
+
+enum { NA_DFACLCNT = 0x8 };
+
+enum { NFS_ACL_PORT = 2049 };
+
+enum { NFS_ACL_RDMA_PORT = 20049 };
+
+enum ftype {
+ NFNON = 0,
+ NFREG = 1,
+ NFDIR = 2,
+ NFBLK = 3,
+ NFCHR = 4,
+ NFLNK = 5,
+};
+
+typedef enum ftype ftype;
+
+enum { FHSIZE = 32 };
+
+typedef u8 fhandle[FHSIZE];
+
+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;
+};
+
+enum aclstat2 {
+ ACL2_OK = 0,
+ ACL2ERR_PERM = 1,
+ ACL2ERR_NOENT = 2,
+ ACL2ERR_IO = 5,
+ ACL2ERR_ACCES = 13,
+ ACL2ERR_INVAL = 22,
+ ACL2ERR_NOSPC = 28,
+ ACL2ERR_ROFS = 30,
+ ACL2ERR_NOTSUPP = 45,
+ ACL2ERR_DQUOT = 69,
+ ACL2ERR_STALE = 70,
+};
+
+typedef enum aclstat2 aclstat2;
+
+struct GETACL2args {
+ fhandle fh;
+ u32 mask;
+};
+
+struct GETACL2resok {
+ struct fattr attr;
+ struct secattr acl;
+};
+
+struct GETACL2res {
+ aclstat2 status;
+ union {
+ struct GETACL2resok resok;
+ } u;
+};
+
+struct SETACL2args {
+ fhandle fh;
+ struct secattr acl;
+};
+
+struct SETACL2resok {
+ struct fattr attr;
+};
+
+struct SETACL2res {
+ aclstat2 status;
+ union {
+ struct SETACL2resok resok;
+ } u;
+};
+
+struct GETATTR2args {
+ fhandle fh;
+};
+
+struct GETATTR2resok {
+ struct fattr attr;
+};
+
+struct GETATTR2res {
+ aclstat2 status;
+ union {
+ struct GETATTR2resok resok;
+ } u;
+};
+
+struct ACCESS2args {
+ fhandle fh;
+ u32 access;
+};
+
+enum { ACCESS2_READ = 0x1 };
+
+enum { ACCESS2_LOOKUP = 0x2 };
+
+enum { ACCESS2_MODIFY = 0x4 };
+
+enum { ACCESS2_EXTEND = 0x8 };
+
+enum { ACCESS2_DELETE = 0x10 };
+
+enum { ACCESS2_EXECUTE = 0x20 };
+
+struct ACCESS2resok {
+ struct fattr attr;
+ u32 access;
+};
+
+struct ACCESS2res {
+ aclstat2 status;
+ union {
+ struct ACCESS2resok resok;
+ } u;
+};
+
+struct GETXATTRDIR2args {
+ fhandle fh;
+ bool create;
+};
+
+struct GETXATTRDIR2resok {
+ fhandle fh;
+ struct fattr attr;
+};
+
+struct GETXATTRDIR2res {
+ aclstat2 status;
+ union {
+ struct GETXATTRDIR2resok resok;
+ } u;
+};
+
+enum {
+ ACLPROC2_NULL = 0,
+ ACLPROC2_GETACL = 1,
+ ACLPROC2_SETACL = 2,
+ ACLPROC2_GETATTR = 3,
+ ACLPROC2_ACCESS = 4,
+ ACLPROC2_GETXATTRDIR = 5,
+};
+
+#ifndef NFS_ACL_PROGRAM
+#define NFS_ACL_PROGRAM (100227)
+#endif
+
+#define NFS_ACL2_uid_sz \
+ (XDR_unsigned_int)
+#define NFS_ACL2_o_mode_sz \
+ (XDR_unsigned_short)
+#define NFS_ACL2_aclent_sz \
+ (XDR_int + XDR_unsigned_int + NFS_ACL2_o_mode_sz)
+#define NFS_ACL2_secattr_sz \
+ (XDR_unsigned_int + XDR_int + XDR_unsigned_int + (NFS_ACL_MAX_ENTRIES * (NFS_ACL2_aclent_sz)) + XDR_int + XDR_unsigned_int + (NFS_ACL_MAX_ENTRIES * (NFS_ACL2_aclent_sz)))
+#define NFS_ACL2_ftype_sz (XDR_int)
+#define NFS_ACL2_fhandle_sz (XDR_QUADLEN(FHSIZE))
+#define NFS_ACL2_timeval_sz \
+ (XDR_unsigned_int + XDR_unsigned_int)
+#define NFS_ACL2_fattr_sz \
+ (NFS_ACL2_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 + NFS_ACL2_timeval_sz + NFS_ACL2_timeval_sz + NFS_ACL2_timeval_sz)
+#define NFS_ACL2_aclstat2_sz (XDR_int)
+#define NFS_ACL2_GETACL2args_sz \
+ (NFS_ACL2_fhandle_sz + XDR_unsigned_int)
+#define NFS_ACL2_GETACL2resok_sz \
+ (NFS_ACL2_fattr_sz + NFS_ACL2_secattr_sz)
+#define NFS_ACL2_GETACL2res_sz \
+ (NFS_ACL2_aclstat2_sz + NFS_ACL2_GETACL2resok_sz)
+#define NFS_ACL2_SETACL2args_sz \
+ (NFS_ACL2_fhandle_sz + NFS_ACL2_secattr_sz)
+#define NFS_ACL2_SETACL2resok_sz \
+ (NFS_ACL2_fattr_sz)
+#define NFS_ACL2_SETACL2res_sz \
+ (NFS_ACL2_aclstat2_sz + NFS_ACL2_SETACL2resok_sz)
+#define NFS_ACL2_GETATTR2args_sz \
+ (NFS_ACL2_fhandle_sz)
+#define NFS_ACL2_GETATTR2resok_sz \
+ (NFS_ACL2_fattr_sz)
+#define NFS_ACL2_GETATTR2res_sz \
+ (NFS_ACL2_aclstat2_sz + NFS_ACL2_GETATTR2resok_sz)
+#define NFS_ACL2_ACCESS2args_sz \
+ (NFS_ACL2_fhandle_sz + XDR_unsigned_int)
+#define NFS_ACL2_ACCESS2resok_sz \
+ (NFS_ACL2_fattr_sz + XDR_unsigned_int)
+#define NFS_ACL2_ACCESS2res_sz \
+ (NFS_ACL2_aclstat2_sz + NFS_ACL2_ACCESS2resok_sz)
+#define NFS_ACL2_GETXATTRDIR2args_sz \
+ (NFS_ACL2_fhandle_sz + XDR_bool)
+#define NFS_ACL2_GETXATTRDIR2resok_sz \
+ (NFS_ACL2_fhandle_sz + NFS_ACL2_fattr_sz)
+#define NFS_ACL2_GETXATTRDIR2res_sz \
+ (NFS_ACL2_aclstat2_sz + NFS_ACL2_GETXATTRDIR2resok_sz)
+#define NFS_ACL2_MAX_ARGS_SZ \
+ (NFS_ACL2_SETACL2args_sz)
+
+#endif /* _LINUX_XDRGEN_NFS_ACL2_DEF_H */
--
2.56.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v1 08/17] NFSD: Remove '#include "xdr3.h"' from nfs2acl.c
2026-10-22 13:53 [PATCH v1 00/17] Convert server-side NFS_ACLv2 XDR to use xdrgen Chuck Lever
` (6 preceding siblings ...)
2026-10-22 13:53 ` [PATCH v1 07/17] NFSD: Add infrastructure for generating NFS_ACLv2 XDR encoders and decoders Chuck Lever
@ 2026-10-22 13:53 ` Chuck Lever
2026-10-22 13:53 ` [PATCH v1 09/17] NFSD: Remove '#include "xdr.h"' " Chuck Lever
` (8 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Chuck Lever @ 2026-10-22 13:53 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Begin the process of converting nfs2acl.c to use xdrgen by removing
the xdr3.h header from nfs2acl.c. Eventually nfs2acl.c will use
only NFS_ACLv2 headers.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfs2acl.c | 36 +++++++++++++++++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
index 93224144105e..b079a3ded78f 100644
--- a/fs/nfsd/nfs2acl.c
+++ b/fs/nfsd/nfs2acl.c
@@ -13,11 +13,45 @@
#include <linux/gfp.h>
#include "cache.h"
#include "xdr.h"
-#include "xdr3.h"
#include "vfs.h"
#define NFSDDBG_FACILITY NFSDDBG_PROC
+struct posix_acl;
+
+struct nfsd3_getaclargs {
+ struct svc_fh fh;
+ __u32 mask;
+};
+
+struct nfsd3_getaclres {
+ __be32 status;
+ struct svc_fh fh;
+ int mask;
+ struct posix_acl *acl_access;
+ struct posix_acl *acl_default;
+ struct kstat stat;
+};
+
+struct nfsd3_setaclargs {
+ struct svc_fh fh;
+ __u32 mask;
+ struct posix_acl *acl_access;
+ struct posix_acl *acl_default;
+};
+
+struct nfsd3_accessargs {
+ struct svc_fh fh;
+ __u32 access;
+};
+
+struct nfsd3_accessres {
+ __be32 status;
+ struct svc_fh fh;
+ __u32 access;
+ struct kstat stat;
+};
+
/*
* These maps are identical to the NFSv3 maps (nfs3proc.c). This enables
* the behavior of the two versions to diverge if needed.
--
2.56.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v1 09/17] NFSD: Remove '#include "xdr.h"' from nfs2acl.c
2026-10-22 13:53 [PATCH v1 00/17] Convert server-side NFS_ACLv2 XDR to use xdrgen Chuck Lever
` (7 preceding siblings ...)
2026-10-22 13:53 ` [PATCH v1 08/17] NFSD: Remove '#include "xdr3.h"' from nfs2acl.c Chuck Lever
@ 2026-10-22 13:53 ` Chuck Lever
2026-10-22 13:53 ` [PATCH v1 10/17] NFSD: Switch to using the NFS_ACLv2 status codes Chuck Lever
` (7 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Chuck Lever @ 2026-10-22 13:53 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
To avoid collisions of definitions when nfs2xdr_gen.h is included
in nfs2acl.c, remove the xdr.h head from nfs2acl.c, copying in only
the definitions from xdr.h that are needed.
The duplication is temporary.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfs2acl.c | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
index b079a3ded78f..777d754f016a 100644
--- a/fs/nfsd/nfs2acl.c
+++ b/fs/nfsd/nfs2acl.c
@@ -12,13 +12,18 @@
#include <uapi/linux/nfsacl.h>
#include <linux/gfp.h>
#include "cache.h"
-#include "xdr.h"
#include "vfs.h"
#define NFSDDBG_FACILITY NFSDDBG_PROC
struct posix_acl;
+struct nfsd_attrstat {
+ __be32 status;
+ struct svc_fh fh;
+ struct kstat stat;
+};
+
struct nfsd3_getaclargs {
struct svc_fh fh;
__u32 mask;
@@ -52,6 +57,16 @@ struct nfsd3_accessres {
struct kstat stat;
};
+bool nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfssvc_encode_attrstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+
+void nfssvc_release_attrstat(struct svc_rqst *rqstp);
+
+bool svcxdr_decode_fhandle(struct xdr_stream *xdr, struct svc_fh *fhp);
+bool svcxdr_encode_stat(struct xdr_stream *xdr, __be32 status);
+bool svcxdr_encode_fattr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
+ const struct svc_fh *fhp, const struct kstat *stat);
+
/*
* These maps are identical to the NFSv3 maps (nfs3proc.c). This enables
* the behavior of the two versions to diverge if needed.
--
2.56.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v1 10/17] NFSD: Switch to using the NFS_ACLv2 status codes
2026-10-22 13:53 [PATCH v1 00/17] Convert server-side NFS_ACLv2 XDR to use xdrgen Chuck Lever
` (8 preceding siblings ...)
2026-10-22 13:53 ` [PATCH v1 09/17] NFSD: Remove '#include "xdr.h"' " Chuck Lever
@ 2026-10-22 13:53 ` Chuck Lever
2026-10-22 13:53 ` [PATCH v1 11/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 NULL procedure Chuck Lever
` (6 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Chuck Lever @ 2026-10-22 13:53 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
In preparation for converting the NFS_ACLv2 procedures to use
generated XDR decoding and encoding, swap in the generated headers,
replace the usage of the conventional nfserr codes with the
RPC protocol-specified ACL2ERR constants.
This conversion still depends on the numeric equivalence of the
NFSv2 and NFS_ACLv2 status codes. This is because, for now, the
fh_getattr(), fh_verify(), and nfsd_access() functions return an
nfserr value instead of an errno.
The ACCESS3 and NFS_ACL bit mask symbolic constants are replaced as
well so that this TU continues to compile with the header generated
from RPC spec.
Include ACL2ERR_INVAL in the be32 status-code set and map -EINVAL
to it. The NFS_ACL protocol reports an invalid SETACL argument with
ACL2ERR_INVAL rather than ACL2ERR_IO (draft-ietf-nfsv4-nfs-acl
Section 3.5.4.2); the decode and SETACL conversion patches that
follow use this code to put that status on the wire.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
Documentation/sunrpc/xdr/nfs_acl2.x | 1 +
fs/nfsd/nfs2acl.c | 126 ++++++++++++++++---------
fs/nfsd/nfs_acl2xdr_gen.c | 30 +++---
fs/nfsd/nfs_acl2xdr_gen.h | 2 +-
include/linux/sunrpc/xdrgen/nfs_acl2.h | 4 +-
5 files changed, 103 insertions(+), 60 deletions(-)
diff --git a/Documentation/sunrpc/xdr/nfs_acl2.x b/Documentation/sunrpc/xdr/nfs_acl2.x
index 165f50a898c0..8ec8bffbe77b 100644
--- a/Documentation/sunrpc/xdr/nfs_acl2.x
+++ b/Documentation/sunrpc/xdr/nfs_acl2.x
@@ -163,6 +163,7 @@ enum aclstat2 {
ACL2ERR_DQUOT = 69,
ACL2ERR_STALE = 70
};
+pragma big_endian aclstat2;
/*
* NFS_ACL version 2 procedure arguments and results
diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
index 777d754f016a..c0bc77467e79 100644
--- a/fs/nfsd/nfs2acl.c
+++ b/fs/nfsd/nfs2acl.c
@@ -5,16 +5,23 @@
* Copyright (C) 2002-2003 Andreas Gruenbacher <agruen@suse.de>
*/
-#include "nfsd.h"
-#include "nfserr.h"
-
-#include <linux/nfsacl.h>
-#include <uapi/linux/nfsacl.h>
#include <linux/gfp.h>
+#include <linux/fs.h>
+#include <linux/nfsacl.h>
+#include <linux/nfs3.h>
+#include <linux/nfs4.h>
+
+#include <uapi/linux/nfsd/debug.h>
+
+#include "nfs_acl2xdr_gen.h"
+
#include "cache.h"
+#include "nfsd.h"
#include "vfs.h"
-#define NFSDDBG_FACILITY NFSDDBG_PROC
+#define NFSDDBG_FACILITY NFSDDBG_PROC
+
+#define NFS_ACL_MASK (NA_ACL | NA_ACLCNT | NA_DFACL | NA_DFACLCNT)
struct posix_acl;
@@ -72,19 +79,19 @@ bool svcxdr_encode_fattr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
* the behavior of the two versions to diverge if needed.
*/
static const struct nfsd_access_map nfsd2_regaccess[] = {
- { ACCESS3_READ, NFSD_MAY_READ },
- { ACCESS3_EXECUTE, NFSD_MAY_EXEC },
- { ACCESS3_MODIFY, NFSD_MAY_WRITE | NFSD_MAY_TRUNC },
- { ACCESS3_EXTEND, NFSD_MAY_WRITE },
+ { ACCESS2_READ, NFSD_MAY_READ },
+ { ACCESS2_EXECUTE, NFSD_MAY_EXEC },
+ { ACCESS2_MODIFY, NFSD_MAY_WRITE | NFSD_MAY_TRUNC },
+ { ACCESS2_EXTEND, NFSD_MAY_WRITE },
{ 0, 0 }
};
static const struct nfsd_access_map nfsd2_diraccess[] = {
- { ACCESS3_READ, NFSD_MAY_READ },
- { ACCESS3_LOOKUP, NFSD_MAY_EXEC },
- { ACCESS3_MODIFY, NFSD_MAY_EXEC | NFSD_MAY_WRITE | NFSD_MAY_TRUNC },
- { ACCESS3_EXTEND, NFSD_MAY_EXEC | NFSD_MAY_WRITE },
- { ACCESS3_DELETE, NFSD_MAY_REMOVE },
+ { ACCESS2_READ, NFSD_MAY_READ },
+ { ACCESS2_LOOKUP, NFSD_MAY_EXEC },
+ { ACCESS2_MODIFY, NFSD_MAY_EXEC | NFSD_MAY_WRITE | NFSD_MAY_TRUNC },
+ { ACCESS2_EXTEND, NFSD_MAY_EXEC | NFSD_MAY_WRITE },
+ { ACCESS2_DELETE, NFSD_MAY_REMOVE },
{ 0, 0 }
};
@@ -96,10 +103,10 @@ static const struct nfsd_access_map nfsd2_diraccess[] = {
* filesystem checks.
*/
static const struct nfsd_access_map nfsd2_otheraccess[] = {
- { ACCESS3_READ, NFSD_MAY_READ },
- { ACCESS3_EXECUTE, NFSD_MAY_EXEC },
- { ACCESS3_MODIFY, NFSD_MAY_WRITE | NFSD_MAY_LOCAL_ACCESS },
- { ACCESS3_EXTEND, NFSD_MAY_WRITE | NFSD_MAY_LOCAL_ACCESS },
+ { ACCESS2_READ, NFSD_MAY_READ },
+ { ACCESS2_EXECUTE, NFSD_MAY_EXEC },
+ { ACCESS2_MODIFY, NFSD_MAY_WRITE | NFSD_MAY_LOCAL_ACCESS },
+ { ACCESS2_EXTEND, NFSD_MAY_WRITE | NFSD_MAY_LOCAL_ACCESS },
{ 0, 0 }
};
@@ -109,15 +116,48 @@ static const struct nfsd_access_maps nfsd2_access_maps = {
.other = nfsd2_otheraccess,
};
+enum {
+ acl2_ok = __constant_cpu_to_be32(ACL2_OK),
+ acl2err_perm = __constant_cpu_to_be32(ACL2ERR_PERM),
+ acl2err_noent = __constant_cpu_to_be32(ACL2ERR_NOENT),
+ acl2err_io = __constant_cpu_to_be32(ACL2ERR_IO),
+ acl2err_acces = __constant_cpu_to_be32(ACL2ERR_ACCES),
+ acl2err_inval = __constant_cpu_to_be32(ACL2ERR_INVAL),
+ acl2err_nospc = __constant_cpu_to_be32(ACL2ERR_NOSPC),
+ acl2err_rofs = __constant_cpu_to_be32(ACL2ERR_ROFS),
+ acl2err_notsupp = __constant_cpu_to_be32(ACL2ERR_NOTSUPP),
+ acl2err_dquot = __constant_cpu_to_be32(ACL2ERR_DQUOT),
+ acl2err_stale = __constant_cpu_to_be32(ACL2ERR_STALE),
+};
+
+/* Ensure errno values do not leak onto the wire. */
+static __be32 nfsacld_map_errno(int errno)
+{
+ switch (errno) {
+ case 0: return acl2_ok;
+ case -EPERM: return acl2err_perm;
+ case -ENOENT: return acl2err_noent;
+ case -EACCES: return acl2err_acces;
+ case -EINVAL: return acl2err_inval;
+ case -ENOSPC: return acl2err_nospc;
+ case -EROFS: return acl2err_rofs;
+ case -EOPNOTSUPP: return acl2err_notsupp;
+ case -EDQUOT: return acl2err_dquot;
+ case -EBADF:
+ case -ESTALE: return acl2err_stale;
+ default: return acl2err_io;
+ }
+}
+
static __be32 nfsacld_map_status(__be32 status)
{
switch (status) {
- case nfserr_nofilehandle:
- case nfserr_badhandle:
- status = nfserr_stale;
+ case cpu_to_be32(NFS4ERR_NOFILEHANDLE):
+ case cpu_to_be32(NFS3ERR_BADHANDLE):
+ status = acl2err_stale;
break;
- case nfserr_wrongsec:
- status = nfserr_acces;
+ case cpu_to_be32(NFS4ERR_WRONGSEC):
+ status = acl2err_acces;
break;
}
return status;
@@ -147,39 +187,39 @@ static __be32 nfsacld_proc_getacl(struct svc_rqst *rqstp)
fh = fh_copy(&resp->fh, &argp->fh);
resp->status = fh_verify(rqstp, &resp->fh, 0, NFSD_MAY_NOP);
- if (resp->status != nfs_ok)
+ if (resp->status != acl2_ok)
goto out;
inode = d_inode(fh->fh_dentry);
if (argp->mask & ~NFS_ACL_MASK) {
- resp->status = nfserr_io;
+ resp->status = acl2err_io;
goto out;
}
resp->mask = argp->mask;
resp->status = fh_getattr(fh, &resp->stat);
- if (resp->status != nfs_ok)
+ if (resp->status != acl2_ok)
goto out;
- if (resp->mask & (NFS_ACL|NFS_ACLCNT)) {
+ if (resp->mask & (NA_ACL | NA_ACLCNT)) {
acl = get_inode_acl(inode, ACL_TYPE_ACCESS);
if (acl == NULL) {
/* Solaris returns the inode's minimum ACL. */
acl = posix_acl_from_mode(inode->i_mode, GFP_KERNEL);
}
if (IS_ERR(acl)) {
- resp->status = nfserrno(PTR_ERR(acl));
+ resp->status = nfsacld_map_errno(PTR_ERR(acl));
goto fail;
}
resp->acl_access = acl;
}
- if (resp->mask & (NFS_DFACL|NFS_DFACLCNT)) {
+ if (resp->mask & (NA_DFACL | NA_DFACLCNT)) {
/* Check how Solaris handles requests for the Default ACL
of a non-directory! */
acl = get_inode_acl(inode, ACL_TYPE_DEFAULT);
if (IS_ERR(acl)) {
- resp->status = nfserrno(PTR_ERR(acl));
+ resp->status = nfsacld_map_errno(PTR_ERR(acl));
goto fail;
}
resp->acl_default = acl;
@@ -213,7 +253,7 @@ static __be32 nfsacld_proc_setacl(struct svc_rqst *rqstp)
fh = fh_copy(&resp->fh, &argp->fh);
resp->status = fh_verify(rqstp, &resp->fh, 0, NFSD_MAY_SATTR);
- if (resp->status != nfs_ok)
+ if (resp->status != acl2_ok)
goto out;
inode = d_inode(fh->fh_dentry);
@@ -225,13 +265,13 @@ static __be32 nfsacld_proc_setacl(struct svc_rqst *rqstp)
inode_lock(inode);
error = 0;
- if (argp->mask & NFS_ACL) {
+ if (argp->mask & NA_ACL) {
error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry,
ACL_TYPE_ACCESS, argp->acl_access);
if (error)
goto out_drop_lock;
}
- if (argp->mask & NFS_DFACL) {
+ if (argp->mask & NA_DFACL) {
error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry,
ACL_TYPE_DEFAULT, argp->acl_default);
if (error)
@@ -253,7 +293,7 @@ static __be32 nfsacld_proc_setacl(struct svc_rqst *rqstp)
inode_unlock(inode);
fh_drop_write(fh);
out_errno:
- resp->status = nfserrno(error);
+ resp->status = nfsacld_map_errno(error);
goto out;
}
@@ -269,7 +309,7 @@ static __be32 nfsacld_proc_getattr(struct svc_rqst *rqstp)
fh_copy(&resp->fh, &argp->fh);
resp->status = fh_verify(rqstp, &resp->fh, 0, NFSD_MAY_NOP);
- if (resp->status != nfs_ok)
+ if (resp->status != acl2_ok)
goto out;
resp->status = fh_getattr(&resp->fh, &resp->stat);
out:
@@ -294,7 +334,7 @@ static __be32 nfsacld_proc_access(struct svc_rqst *rqstp)
resp->status = nfsd_access(rqstp, &resp->fh, &nfsd2_access_maps,
&resp->access, NULL);
- if (resp->status != nfs_ok)
+ if (resp->status != acl2_ok)
goto out;
resp->status = fh_getattr(&resp->fh, &resp->stat);
out:
@@ -330,10 +370,10 @@ nfsaclsvc_decode_setaclargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
return false;
if (argp->mask & ~NFS_ACL_MASK)
return false;
- if (!nfs_stream_decode_acl(xdr, NULL, (argp->mask & NFS_ACL) ?
+ if (!nfs_stream_decode_acl(xdr, NULL, (argp->mask & NA_ACL) ?
&argp->acl_access : NULL))
return false;
- if (!nfs_stream_decode_acl(xdr, NULL, (argp->mask & NFS_DFACL) ?
+ if (!nfs_stream_decode_acl(xdr, NULL, (argp->mask & NA_DFACL) ?
&argp->acl_default : NULL))
return false;
@@ -368,17 +408,17 @@ nfsaclsvc_encode_getaclres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
if (!svcxdr_encode_stat(xdr, resp->status))
return false;
switch (resp->status) {
- case nfs_ok:
+ case acl2_ok:
inode = d_inode(dentry);
if (!svcxdr_encode_fattr(rqstp, xdr, &resp->fh, &resp->stat))
return false;
if (xdr_stream_encode_u32(xdr, resp->mask) < 0)
return false;
if (!nfs_stream_encode_acl(xdr, inode, resp->acl_access,
- resp->mask & NFS_ACL, 0))
+ resp->mask & NA_ACL, 0))
return false;
if (!nfs_stream_encode_acl(xdr, inode, resp->acl_default,
- resp->mask & NFS_DFACL, NFS_ACL_DEFAULT))
+ resp->mask & NA_DFACL, NA_ACL_DEFAULT))
return false;
break;
}
@@ -395,7 +435,7 @@ nfsaclsvc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
if (!svcxdr_encode_stat(xdr, resp->status))
return false;
switch (resp->status) {
- case nfs_ok:
+ case acl2_ok:
if (!svcxdr_encode_fattr(rqstp, xdr, &resp->fh, &resp->stat))
return false;
if (xdr_stream_encode_u32(xdr, resp->access) < 0)
diff --git a/fs/nfsd/nfs_acl2xdr_gen.c b/fs/nfsd/nfs_acl2xdr_gen.c
index 2c1b6a4e2ebc..59765733bd49 100644
--- a/fs/nfsd/nfs_acl2xdr_gen.c
+++ b/fs/nfsd/nfs_acl2xdr_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/nfs_acl2.x
-// XDR specification modification time: Wed Jul 15 19:22:39 2026
+// XDR specification modification time: Wed Jul 15 19:23:04 2026
#include <linux/sunrpc/svc.h>
@@ -133,10 +133,12 @@ xdrgen_decode_fattr(struct xdr_stream *xdr, struct fattr *ptr)
static bool __maybe_unused
xdrgen_decode_aclstat2(struct xdr_stream *xdr, aclstat2 *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 ACL2_OK:
@@ -154,7 +156,7 @@ xdrgen_decode_aclstat2(struct xdr_stream *xdr, aclstat2 *ptr)
default:
return false;
}
- *ptr = val;
+ *ptr = raw;
return true;
}
@@ -184,7 +186,7 @@ xdrgen_decode_GETACL2res(struct xdr_stream *xdr, struct GETACL2res *ptr)
if (!xdrgen_decode_aclstat2(xdr, &ptr->status))
return false;
switch (ptr->status) {
- case ACL2_OK:
+ case __constant_cpu_to_be32(ACL2_OK):
if (!xdrgen_decode_GETACL2resok(xdr, &ptr->u.resok))
return false;
break;
@@ -218,7 +220,7 @@ xdrgen_decode_SETACL2res(struct xdr_stream *xdr, struct SETACL2res *ptr)
if (!xdrgen_decode_aclstat2(xdr, &ptr->status))
return false;
switch (ptr->status) {
- case ACL2_OK:
+ case __constant_cpu_to_be32(ACL2_OK):
if (!xdrgen_decode_SETACL2resok(xdr, &ptr->u.resok))
return false;
break;
@@ -250,7 +252,7 @@ xdrgen_decode_GETATTR2res(struct xdr_stream *xdr, struct GETATTR2res *ptr)
if (!xdrgen_decode_aclstat2(xdr, &ptr->status))
return false;
switch (ptr->status) {
- case ACL2_OK:
+ case __constant_cpu_to_be32(ACL2_OK):
if (!xdrgen_decode_GETATTR2resok(xdr, &ptr->u.resok))
return false;
break;
@@ -286,7 +288,7 @@ xdrgen_decode_ACCESS2res(struct xdr_stream *xdr, struct ACCESS2res *ptr)
if (!xdrgen_decode_aclstat2(xdr, &ptr->status))
return false;
switch (ptr->status) {
- case ACL2_OK:
+ case __constant_cpu_to_be32(ACL2_OK):
if (!xdrgen_decode_ACCESS2resok(xdr, &ptr->u.resok))
return false;
break;
@@ -322,7 +324,7 @@ xdrgen_decode_GETXATTRDIR2res(struct xdr_stream *xdr, struct GETXATTRDIR2res *pt
if (!xdrgen_decode_aclstat2(xdr, &ptr->status))
return false;
switch (ptr->status) {
- case ACL2_OK:
+ case __constant_cpu_to_be32(ACL2_OK):
if (!xdrgen_decode_GETXATTRDIR2resok(xdr, &ptr->u.resok))
return false;
break;
@@ -521,7 +523,7 @@ xdrgen_encode_fattr(struct xdr_stream *xdr, const struct fattr *value)
static bool __maybe_unused
xdrgen_encode_aclstat2(struct xdr_stream *xdr, aclstat2 value)
{
- return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
+ return xdr_stream_encode_be32(xdr, value) == XDR_UNIT;
}
static bool __maybe_unused
@@ -550,7 +552,7 @@ xdrgen_encode_GETACL2res(struct xdr_stream *xdr, const struct GETACL2res *ptr)
if (!xdrgen_encode_aclstat2(xdr, ptr->status))
return false;
switch (ptr->status) {
- case ACL2_OK:
+ case __constant_cpu_to_be32(ACL2_OK):
if (!xdrgen_encode_GETACL2resok(xdr, &ptr->u.resok))
return false;
break;
@@ -584,7 +586,7 @@ xdrgen_encode_SETACL2res(struct xdr_stream *xdr, const struct SETACL2res *ptr)
if (!xdrgen_encode_aclstat2(xdr, ptr->status))
return false;
switch (ptr->status) {
- case ACL2_OK:
+ case __constant_cpu_to_be32(ACL2_OK):
if (!xdrgen_encode_SETACL2resok(xdr, &ptr->u.resok))
return false;
break;
@@ -616,7 +618,7 @@ xdrgen_encode_GETATTR2res(struct xdr_stream *xdr, const struct GETATTR2res *ptr)
if (!xdrgen_encode_aclstat2(xdr, ptr->status))
return false;
switch (ptr->status) {
- case ACL2_OK:
+ case __constant_cpu_to_be32(ACL2_OK):
if (!xdrgen_encode_GETATTR2resok(xdr, &ptr->u.resok))
return false;
break;
@@ -652,7 +654,7 @@ xdrgen_encode_ACCESS2res(struct xdr_stream *xdr, const struct ACCESS2res *ptr)
if (!xdrgen_encode_aclstat2(xdr, ptr->status))
return false;
switch (ptr->status) {
- case ACL2_OK:
+ case __constant_cpu_to_be32(ACL2_OK):
if (!xdrgen_encode_ACCESS2resok(xdr, &ptr->u.resok))
return false;
break;
@@ -688,7 +690,7 @@ xdrgen_encode_GETXATTRDIR2res(struct xdr_stream *xdr, const struct GETXATTRDIR2r
if (!xdrgen_encode_aclstat2(xdr, ptr->status))
return false;
switch (ptr->status) {
- case ACL2_OK:
+ case __constant_cpu_to_be32(ACL2_OK):
if (!xdrgen_encode_GETXATTRDIR2resok(xdr, &ptr->u.resok))
return false;
break;
diff --git a/fs/nfsd/nfs_acl2xdr_gen.h b/fs/nfsd/nfs_acl2xdr_gen.h
index 0152aa0b6695..a650fb7a3c83 100644
--- a/fs/nfsd/nfs_acl2xdr_gen.h
+++ b/fs/nfsd/nfs_acl2xdr_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/nfs_acl2.x */
-/* XDR specification modification time: Wed Jul 15 19:22:39 2026 */
+/* XDR specification modification time: Wed Jul 15 19:23:04 2026 */
#ifndef _LINUX_XDRGEN_NFS_ACL2_DECL_H
#define _LINUX_XDRGEN_NFS_ACL2_DECL_H
diff --git a/include/linux/sunrpc/xdrgen/nfs_acl2.h b/include/linux/sunrpc/xdrgen/nfs_acl2.h
index 2069015d0c14..cc0b0db4c738 100644
--- a/include/linux/sunrpc/xdrgen/nfs_acl2.h
+++ b/include/linux/sunrpc/xdrgen/nfs_acl2.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/nfs_acl2.x */
-/* XDR specification modification time: Wed Jul 15 19:22:39 2026 */
+/* XDR specification modification time: Wed Jul 15 19:23:04 2026 */
#ifndef _LINUX_XDRGEN_NFS_ACL2_DEF_H
#define _LINUX_XDRGEN_NFS_ACL2_DEF_H
@@ -118,7 +118,7 @@ enum aclstat2 {
ACL2ERR_STALE = 70,
};
-typedef enum aclstat2 aclstat2;
+typedef __be32 aclstat2;
struct GETACL2args {
fhandle fh;
--
2.56.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v1 11/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 NULL procedure
2026-10-22 13:53 [PATCH v1 00/17] Convert server-side NFS_ACLv2 XDR to use xdrgen Chuck Lever
` (9 preceding siblings ...)
2026-10-22 13:53 ` [PATCH v1 10/17] NFSD: Switch to using the NFS_ACLv2 status codes Chuck Lever
@ 2026-10-22 13:53 ` Chuck Lever
2026-10-22 13:53 ` [PATCH v1 12/17] NFSD: Implement NFS-ACL v2 ACL encoding and decoding with xdrgen Chuck Lever
` (5 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Chuck Lever @ 2026-10-22 13:53 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_ACL version 2 protocol description
exactly.
Convert the NULL procedure to use xdrgen_svc_decode_void and
xdrgen_svc_encode_void, generated from
Documentation/sunrpc/xdr/nfs_acl.x. 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/nfs2acl.c | 32 +++++++++++++++++++-------------
1 file changed, 19 insertions(+), 13 deletions(-)
diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
index c0bc77467e79..0698c9dc85dd 100644
--- a/fs/nfsd/nfs2acl.c
+++ b/fs/nfsd/nfs2acl.c
@@ -163,11 +163,17 @@ static __be32 nfsacld_map_status(__be32 status)
return status;
}
-/*
- * NULL call.
+/**
+ * nfsacld_proc_null - NULL: Do nothing
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * void ACLPROC2_NULL(void) = 0;
*/
-static __be32
-nfsacld_proc_null(struct svc_rqst *rqstp)
+static __be32 nfsacld_proc_null(struct svc_rqst *rqstp)
{
return rpc_success;
}
@@ -482,15 +488,15 @@ static void nfsaclsvc_release_setacl(struct svc_rqst *rqstp)
static const struct svc_procedure nfsd_acl_procedures2[5] = {
[ACLPROC2_NULL] = {
- .pc_func = nfsacld_proc_null,
- .pc_decode = nfssvc_decode_voidarg,
- .pc_encode = nfssvc_encode_voidres,
- .pc_argsize = sizeof(struct nfsd_voidargs),
- .pc_argzero = sizeof(struct nfsd_voidargs),
- .pc_ressize = sizeof(struct nfsd_voidres),
- .pc_cachetype = RC_NOCACHE,
- .pc_xdrressize = ST,
- .pc_name = "NULL",
+ .pc_func = nfsacld_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",
},
[ACLPROC2_GETACL] = {
.pc_func = nfsacld_proc_getacl,
--
2.56.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v1 12/17] NFSD: Implement NFS-ACL v2 ACL encoding and decoding with xdrgen
2026-10-22 13:53 [PATCH v1 00/17] Convert server-side NFS_ACLv2 XDR to use xdrgen Chuck Lever
` (10 preceding siblings ...)
2026-10-22 13:53 ` [PATCH v1 11/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 NULL procedure Chuck Lever
@ 2026-10-22 13:53 ` Chuck Lever
2026-10-22 13:53 ` [PATCH v1 13/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 GETACL procedure Chuck Lever
` (4 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Chuck Lever @ 2026-10-22 13:53 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
The NFS-ACL v2 GETACL result encoder and SETACL argument decoder
cannot be generated the way the other procedures' codecs are: the
wire secattr is a Solaris projection of the kernel's struct
posix_acl, and the per-entry transformation between them -- tag and
id mapping, the canonical four-entry expansion described in
draft-ietf-nfsv4-nfs-acl Section 3.4.4.2, and counts gated by the
request mask -- is application logic that xdrgen cannot synthesize.
Keeping those two codecs hand-rolled would force the generated
NFSv2 types they call, fattr and fhandle, to keep external linkage,
and those symbols would collide at link time with the public
definitions that nfs2xdr_gen.c already emits for NFSv2 proper.
Supply the nfsd side of xdrgen's hook-driven aggregate codec
mechanism. "pragma aggregate" markers on secattr's two aclent<>
members select the streaming codec, whose generated framing owns
the wire representation and drives per-element application hooks. The
hooks hold the posix_acl logic factored from
nfs_stream_encode_acl() and nfs_stream_decode_acl(); both arrays
share one hook set, with the cursor's member_id distinguishing the
access list from the default list. Wrapper structures pair each
generated argument or result type with the legacy application state
(svc_fh, posix_acl pointers, kstat), placing the generated type at
offset zero so rq_argp or rq_resp addresses both the generated type
the codec fills and the wrapper the hooks read. The hooks reach
that wrapper by way of the svc_rqst, which svcxdr_init_decode() and
svcxdr_init_encode() leave in the xdr_stream's application-private
pointer.
On decode, the begin hook validates the declared count and the
request mask, allocates the posix_acl at its known size, and
publishes it into the argument wrapper before any fallible
per-entry decode runs, so a mid-list failure leaves the ACL
reachable by pc_release rather than leaked. A zero-length published
default list stays NULL, which set_posix_acl() treats as ACL
removal.
A well-formed request that is nonetheless invalid -- an undefined
request mask bit, an empty access ACL, a perm bit outside rwx, an
undefined entry type, or more entries than NFS_ACL_MAX_ENTRIES --
instead marks the argument wrapper and lets the decode succeed, so
nfsacld_proc_setacl() can report ACL2ERR_INVAL rather than
GARBAGE_ARGS (draft-ietf-nfsv4-nfs-acl Section 3.4.4.2). The
pre-conversion decoder rejected most of these cases with
GARBAGE_ARGS. It accepted an empty access ACL as a request to
remove the object's access ACL, an operation the protocol does not
define. It also cleared undefined perm bits from the mask entry, an
accommodation for Solaris clients that the draft does not carry,
and it did not check the perm field of a user or group entry at
all. Reporting ACL2ERR_INVAL is a deliberate, minor change to the
SETACL wire behavior.
Because the containing codecs are fully generated, the NFSv2 types
the ACL specification re-declares stay static in nfs_acl2xdr_gen.c,
and no public pragma is needed.
Subsequent patches convert the GETACL and SETACL procedures to the
generated codecs.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
Documentation/sunrpc/xdr/nfs_acl2.x | 2 +
fs/nfsd/nfs2acl.c | 246 ++++++++++++++++++++++++++++
fs/nfsd/nfs_acl2xdr_gen.c | 136 ++++++++++++---
fs/nfsd/nfs_acl2xdr_gen.h | 12 ++
4 files changed, 372 insertions(+), 24 deletions(-)
diff --git a/Documentation/sunrpc/xdr/nfs_acl2.x b/Documentation/sunrpc/xdr/nfs_acl2.x
index 8ec8bffbe77b..25c0b54ece1a 100644
--- a/Documentation/sunrpc/xdr/nfs_acl2.x
+++ b/Documentation/sunrpc/xdr/nfs_acl2.x
@@ -91,6 +91,8 @@ struct secattr {
int dfaclcnt;
aclent dfaclent<NFS_ACL_MAX_ENTRIES>;
};
+pragma aggregate secattr aclent;
+pragma aggregate secattr dfaclent;
/*
* The values for the mask element of the secattr struct as well
diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
index 0698c9dc85dd..13e7415aa97b 100644
--- a/fs/nfsd/nfs2acl.c
+++ b/fs/nfsd/nfs2acl.c
@@ -31,6 +31,45 @@ struct nfsd_attrstat {
struct kstat stat;
};
+/*
+ * 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 GETACL2args_wrapper {
+ struct GETACL2args xdrgen;
+ struct svc_fh fh;
+};
+
+static_assert(offsetof(struct GETACL2args_wrapper, xdrgen) == 0);
+
+struct GETACL2res_wrapper {
+ struct GETACL2res xdrgen;
+ int mask;
+ struct posix_acl *acl_access;
+ struct posix_acl *acl_default;
+ struct kstat stat;
+};
+
+static_assert(offsetof(struct GETACL2res_wrapper, xdrgen) == 0);
+
+struct SETACL2args_wrapper {
+ struct SETACL2args xdrgen;
+ struct svc_fh fh;
+ struct posix_acl *acl_access;
+ struct posix_acl *acl_default;
+ /*
+ * Set when the wire ACL is well-formed XDR but semantically
+ * invalid. The decoder accepts the request so that
+ * nfsacld_proc_setacl can return ACL2ERR_INVAL rather than RPC
+ * GARBAGE_ARGS. See draft-ietf-nfsv4-nfs-acl Section 3.4.4.2.
+ */
+ bool invalid;
+};
+
+static_assert(offsetof(struct SETACL2args_wrapper, xdrgen) == 0);
+
struct nfsd3_getaclargs {
struct svc_fh fh;
__u32 mask;
@@ -163,6 +202,19 @@ static __be32 nfsacld_map_status(__be32 status)
return status;
}
+/*
+ * A minimal (three-entry) ACL travels as four wire entries: the
+ * POSIX entries plus a synthesized NA_CLASS_OBJ mask. The wire
+ * element count is thus max(a_count, 4) when the ACL has at least
+ * three entries, and zero otherwise. An ACL with fewer than three
+ * entries has no minimal set to expand, so it travels as empty.
+ * See draft-ietf-nfsv4-nfs-acl Section 3.4.4.2.
+ */
+static u32 nfsacld_acl_entries(const struct posix_acl *acl)
+{
+ return (acl && acl->a_count >= 3) ? max_t(int, acl->a_count, 4) : 0;
+}
+
/**
* nfsacld_proc_null - NULL: Do nothing
* @rqstp: RPC transaction context
@@ -348,6 +400,200 @@ static __be32 nfsacld_proc_access(struct svc_rqst *rqstp)
return rpc_success;
}
+/*
+ * Aggregate codec hooks for the generated secattr codec.
+ *
+ * The generated codec owns the wire framing and drives these hooks
+ * once per element; the hooks hold all posix_acl application logic. The
+ * application context (@c->ctx) is the svc_rqst, bound to the stream by
+ * svcxdr_init_{decode,encode}(); each hook reaches the proc's arg or
+ * result wrapper through rq_{argp,resp}.
+ * The cursor's member_id selects the member: it is
+ * NFS_ACL2_SECATTR_ACLENT or NFS_ACL2_SECATTR_DFACLENT.
+ */
+
+/* GETACL reply: stream the wire entries out of the source posix_acl. */
+
+bool nfs_acl2_secattr_encode_begin(struct xdrgen_aggregate_cursor *c)
+{
+ struct svc_rqst *rqstp = c->ctx;
+ struct GETACL2res_wrapper *resp = rqstp->rq_resp;
+ bool dfacl = c->member_id == NFS_ACL2_SECATTR_DFACLENT;
+ struct posix_acl *acl = dfacl ? resp->acl_default : resp->acl_access;
+ int gate = dfacl ? (resp->mask & NA_DFACL) : (resp->mask & NA_ACL);
+
+ c->count = gate ? nfsacld_acl_entries(acl) : 0;
+ return true;
+}
+
+bool nfs_acl2_secattr_encode(struct xdrgen_aggregate_cursor *c,
+ struct aclent *out)
+{
+ struct svc_rqst *rqstp = c->ctx;
+ struct GETACL2res_wrapper *resp = rqstp->rq_resp;
+ bool dfacl = c->member_id == NFS_ACL2_SECATTR_DFACLENT;
+ struct posix_acl *acl = dfacl ? resp->acl_default : resp->acl_access;
+ int typeflag = dfacl ? NA_ACL_DEFAULT : 0;
+ const struct posix_acl_entry *entry;
+ int tag;
+
+ /*
+ * A three-entry ACL expands to four wire entries in ascending
+ * type order (USER_OBJ, GROUP_OBJ, a NA_CLASS_OBJ mask cloned
+ * from GROUP_OBJ, OTHER). Some receivers depend on this ordering.
+ * See draft-ietf-nfsv4-nfs-acl Section 3.4.4.2.
+ */
+ if (acl->a_count == 3) {
+ static const u8 map[4] = { 0, 1, 1, 2 };
+
+ entry = &acl->a_entries[map[c->index]];
+ tag = (c->index == 2) ? ACL_MASK : entry->e_tag;
+ } else {
+ entry = &acl->a_entries[c->index];
+ tag = entry->e_tag;
+ }
+
+ out->type = tag | typeflag;
+ switch (tag) {
+ case ACL_USER_OBJ:
+ out->id = from_kuid(&init_user_ns, resp->stat.uid);
+ break;
+ case ACL_GROUP_OBJ:
+ out->id = from_kgid(&init_user_ns, resp->stat.gid);
+ break;
+ case ACL_USER:
+ out->id = from_kuid(&init_user_ns, entry->e_uid);
+ break;
+ case ACL_GROUP:
+ out->id = from_kgid(&init_user_ns, entry->e_gid);
+ break;
+ default:
+ /*
+ * The id field is undefined for these entry types;
+ * emit zero per draft-ietf-nfsv4-nfs-acl Section 3.4.4.2.
+ */
+ out->id = 0;
+ break;
+ }
+ out->perm = entry->e_perm & (ACL_READ | ACL_WRITE | ACL_EXECUTE);
+ return true;
+}
+
+bool nfs_acl2_secattr_encode_end(struct xdrgen_aggregate_cursor *c, bool ok)
+{
+ return true;
+}
+
+/* SETACL args: consume the decoded wire entries into the target posix_acl. */
+
+bool nfs_acl2_secattr_decode_begin(struct xdrgen_aggregate_cursor *c)
+{
+ struct svc_rqst *rqstp = c->ctx;
+ struct SETACL2args_wrapper *argp = rqstp->rq_argp;
+ bool dfacl = c->member_id == NFS_ACL2_SECATTR_DFACLENT;
+ struct posix_acl **pacl = dfacl ? &argp->acl_default : &argp->acl_access;
+ u32 mask = argp->xdrgen.acl.mask;
+ u32 declared = dfacl ? (u32)argp->xdrgen.acl.dfaclcnt :
+ (u32)argp->xdrgen.acl.aclcnt;
+ int gate = dfacl ? (mask & NA_DFACL) : (mask & NA_ACL);
+
+ /* Already rejected: consume the list. */
+ if (argp->invalid)
+ return true;
+ /* The access list is decoded first, so the mask is checked once. */
+ if (!dfacl && (mask & ~NFS_ACL_MASK)) {
+ argp->invalid = true;
+ return true;
+ }
+ /* Too many ACEs is an invalid ACL, not a malformed request. */
+ if (declared > NFS_ACL_MAX_ENTRIES) {
+ argp->invalid = true;
+ return true;
+ }
+
+ if (gate) {
+ /* A published list's declared count must equal its length. */
+ if (c->count != declared)
+ return false;
+ if (!dfacl && !c->count) {
+ argp->invalid = true;
+ return true;
+ }
+ /* An empty default list publishes NULL: default ACL removal. */
+ if (c->count) {
+ *pacl = posix_acl_alloc(c->count, GFP_KERNEL);
+ if (!*pacl)
+ return false;
+ }
+ } else if (c->count > declared) {
+ return false;
+ }
+ return true;
+}
+
+bool nfs_acl2_secattr_decode(struct xdrgen_aggregate_cursor *c,
+ const struct aclent *in)
+{
+ struct svc_rqst *rqstp = c->ctx;
+ struct SETACL2args_wrapper *argp = rqstp->rq_argp;
+ bool dfacl = c->member_id == NFS_ACL2_SECATTR_DFACLENT;
+ struct posix_acl *acl = dfacl ? argp->acl_default : argp->acl_access;
+ struct posix_acl_entry *entry;
+
+ /* Already rejected, or list gated out by mask: consume it. */
+ if (argp->invalid || !acl)
+ return true;
+
+ /* A perm bit outside rwx makes the ACL invalid. */
+ if (in->perm & ~(ACL_READ | ACL_WRITE | ACL_EXECUTE)) {
+ argp->invalid = true;
+ return true;
+ }
+
+ entry = &acl->a_entries[c->index];
+ switch (in->type & ~NA_ACL_DEFAULT) {
+ case ACL_USER:
+ entry->e_uid = make_kuid(&init_user_ns, in->id);
+ if (!uid_valid(entry->e_uid))
+ return false;
+ break;
+ case ACL_GROUP:
+ entry->e_gid = make_kgid(&init_user_ns, in->id);
+ if (!gid_valid(entry->e_gid))
+ return false;
+ break;
+ case ACL_USER_OBJ:
+ case ACL_GROUP_OBJ:
+ case ACL_OTHER:
+ case ACL_MASK:
+ break;
+ default:
+ /* An undefined entry type makes the ACL invalid. */
+ argp->invalid = true;
+ return true;
+ }
+ entry->e_tag = in->type & ~NA_ACL_DEFAULT;
+ entry->e_perm = in->perm;
+
+ return true;
+}
+
+bool nfs_acl2_secattr_decode_end(struct xdrgen_aggregate_cursor *c, bool ok)
+{
+ struct svc_rqst *rqstp = c->ctx;
+ struct SETACL2args_wrapper *argp = rqstp->rq_argp;
+ bool dfacl = c->member_id == NFS_ACL2_SECATTR_DFACLENT;
+ struct posix_acl *acl = dfacl ? argp->acl_default : argp->acl_access;
+
+ /*
+ * On failure the published ACL stays reachable through the arg
+ * wrapper and is freed by pc_release; nothing to do here.
+ */
+ if (!ok || argp->invalid)
+ return true;
+ return posix_acl_from_nfsacl(acl) == 0;
+}
+
/*
* XDR decode functions
*/
diff --git a/fs/nfsd/nfs_acl2xdr_gen.c b/fs/nfsd/nfs_acl2xdr_gen.c
index 59765733bd49..f6e506970d73 100644
--- a/fs/nfsd/nfs_acl2xdr_gen.c
+++ b/fs/nfsd/nfs_acl2xdr_gen.c
@@ -38,22 +38,66 @@ xdrgen_decode_secattr(struct xdr_stream *xdr, struct secattr *ptr)
return false;
if (!xdrgen_decode_int(xdr, &ptr->aclcnt))
return false;
- if (xdr_stream_decode_u32(xdr, &ptr->aclent.count) < 0)
- return false;
- if (ptr->aclent.count > NFS_ACL_MAX_ENTRIES)
- return false;
- for (u32 i = 0; i < ptr->aclent.count; i++)
- if (!xdrgen_decode_aclent(xdr, &ptr->aclent.element[i]))
+ {
+ struct xdrgen_aggregate_cursor cursor = {
+ .xdr = xdr,
+ .member_id = NFS_ACL2_SECATTR_ACLENT,
+ .ctx = xdr->xdrgen_ctx,
+ };
+ bool ok = true;
+
+ if (xdr_stream_decode_u32(xdr, &cursor.count) < 0)
return false;
+ if (cursor.count > NFS_ACL_MAX_ENTRIES)
+ return false;
+ if (!nfs_acl2_secattr_decode_begin(&cursor))
+ return false;
+ for (cursor.index = 0; cursor.index < cursor.count; cursor.index++) {
+ struct aclent element = {};
+
+ if (!xdrgen_decode_aclent(xdr, &element)) {
+ ok = false;
+ break;
+ }
+ if (!nfs_acl2_secattr_decode(&cursor, &element)) {
+ ok = false;
+ break;
+ }
+ }
+ if (!nfs_acl2_secattr_decode_end(&cursor, ok) || !ok)
+ return false;
+ }
if (!xdrgen_decode_int(xdr, &ptr->dfaclcnt))
return false;
- if (xdr_stream_decode_u32(xdr, &ptr->dfaclent.count) < 0)
- return false;
- if (ptr->dfaclent.count > NFS_ACL_MAX_ENTRIES)
- return false;
- for (u32 i = 0; i < ptr->dfaclent.count; i++)
- if (!xdrgen_decode_aclent(xdr, &ptr->dfaclent.element[i]))
+ {
+ struct xdrgen_aggregate_cursor cursor = {
+ .xdr = xdr,
+ .member_id = NFS_ACL2_SECATTR_DFACLENT,
+ .ctx = xdr->xdrgen_ctx,
+ };
+ bool ok = true;
+
+ if (xdr_stream_decode_u32(xdr, &cursor.count) < 0)
return false;
+ if (cursor.count > NFS_ACL_MAX_ENTRIES)
+ return false;
+ if (!nfs_acl2_secattr_decode_begin(&cursor))
+ return false;
+ for (cursor.index = 0; cursor.index < cursor.count; cursor.index++) {
+ struct aclent element = {};
+
+ if (!xdrgen_decode_aclent(xdr, &element)) {
+ ok = false;
+ break;
+ }
+ if (!nfs_acl2_secattr_decode(&cursor, &element)) {
+ ok = false;
+ break;
+ }
+ }
+ if (!nfs_acl2_secattr_decode_end(&cursor, ok) || !ok)
+ return false;
+ }
return true;
}
@@ -445,22 +489,66 @@ xdrgen_encode_secattr(struct xdr_stream *xdr, const struct secattr *value)
return false;
if (!xdrgen_encode_int(xdr, value->aclcnt))
return false;
- if (value->aclent.count > NFS_ACL_MAX_ENTRIES)
- return false;
- if (xdr_stream_encode_u32(xdr, value->aclent.count) != XDR_UNIT)
- return false;
- for (u32 i = 0; i < value->aclent.count; i++)
- if (!xdrgen_encode_aclent(xdr, &value->aclent.element[i]))
+ {
+ struct xdrgen_aggregate_cursor cursor = {
+ .xdr = xdr,
+ .member_id = NFS_ACL2_SECATTR_ACLENT,
+ .ctx = xdr->xdrgen_ctx,
+ };
+ bool ok = true;
+
+ if (!nfs_acl2_secattr_encode_begin(&cursor))
return false;
+ if (cursor.count > NFS_ACL_MAX_ENTRIES)
+ ok = false;
+ if (ok && xdr_stream_encode_u32(xdr, cursor.count) != XDR_UNIT)
+ ok = false;
+ for (cursor.index = 0; ok && cursor.index < cursor.count; cursor.index++) {
+ struct aclent element = {};
+
+ if (!nfs_acl2_secattr_encode(&cursor, &element)) {
+ ok = false;
+ break;
+ }
+ if (!xdrgen_encode_aclent(xdr, &element)) {
+ ok = false;
+ break;
+ }
+ }
+ if (!nfs_acl2_secattr_encode_end(&cursor, ok) || !ok)
+ return false;
+ }
if (!xdrgen_encode_int(xdr, value->dfaclcnt))
return false;
- if (value->dfaclent.count > NFS_ACL_MAX_ENTRIES)
- return false;
- if (xdr_stream_encode_u32(xdr, value->dfaclent.count) != XDR_UNIT)
- return false;
- for (u32 i = 0; i < value->dfaclent.count; i++)
- if (!xdrgen_encode_aclent(xdr, &value->dfaclent.element[i]))
+ {
+ struct xdrgen_aggregate_cursor cursor = {
+ .xdr = xdr,
+ .member_id = NFS_ACL2_SECATTR_DFACLENT,
+ .ctx = xdr->xdrgen_ctx,
+ };
+ bool ok = true;
+
+ if (!nfs_acl2_secattr_encode_begin(&cursor))
return false;
+ if (cursor.count > NFS_ACL_MAX_ENTRIES)
+ ok = false;
+ if (ok && xdr_stream_encode_u32(xdr, cursor.count) != XDR_UNIT)
+ ok = false;
+ for (cursor.index = 0; ok && cursor.index < cursor.count; cursor.index++) {
+ struct aclent element = {};
+
+ if (!nfs_acl2_secattr_encode(&cursor, &element)) {
+ ok = false;
+ break;
+ }
+ if (!xdrgen_encode_aclent(xdr, &element)) {
+ ok = false;
+ break;
+ }
+ }
+ if (!nfs_acl2_secattr_encode_end(&cursor, ok) || !ok)
+ return false;
+ }
return true;
}
diff --git a/fs/nfsd/nfs_acl2xdr_gen.h b/fs/nfsd/nfs_acl2xdr_gen.h
index a650fb7a3c83..b7fc4f23ffc5 100644
--- a/fs/nfsd/nfs_acl2xdr_gen.h
+++ b/fs/nfsd/nfs_acl2xdr_gen.h
@@ -13,6 +13,18 @@
#include <linux/sunrpc/xdrgen/_builtins.h>
#include <linux/sunrpc/xdrgen/nfs_acl2.h>
+enum {
+ NFS_ACL2_SECATTR_ACLENT,
+ NFS_ACL2_SECATTR_DFACLENT,
+};
+
+bool nfs_acl2_secattr_encode_begin(struct xdrgen_aggregate_cursor *c);
+bool nfs_acl2_secattr_encode(struct xdrgen_aggregate_cursor *c, struct aclent *out);
+bool nfs_acl2_secattr_encode_end(struct xdrgen_aggregate_cursor *c, bool ok);
+bool nfs_acl2_secattr_decode_begin(struct xdrgen_aggregate_cursor *c);
+bool nfs_acl2_secattr_decode(struct xdrgen_aggregate_cursor *c, const struct aclent *in);
+bool nfs_acl2_secattr_decode_end(struct xdrgen_aggregate_cursor *c, bool ok);
+
bool nfs_acl_svc_decode_GETACL2args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs_acl_svc_decode_SETACL2args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs_acl_svc_decode_GETATTR2args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
--
2.56.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v1 13/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 GETACL procedure
2026-10-22 13:53 [PATCH v1 00/17] Convert server-side NFS_ACLv2 XDR to use xdrgen Chuck Lever
` (11 preceding siblings ...)
2026-10-22 13:53 ` [PATCH v1 12/17] NFSD: Implement NFS-ACL v2 ACL encoding and decoding with xdrgen Chuck Lever
@ 2026-10-22 13:53 ` Chuck Lever
2026-10-22 13:53 ` [PATCH v1 14/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 SETACL procedure Chuck Lever
` (3 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Chuck Lever @ 2026-10-22 13:53 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Convert the NFS-ACL version 2 GETACL procedure to xdrgen. The
argument decoder is now the generated nfs_acl_svc_decode_GETACL2args,
and the reply's status word and file attributes are emitted by the
generated xdrgen_encode_aclstat2() and xdrgen_encode_fattr(). Wrapper
structures bridge the generated GETACL2args and GETACL2res types and
the legacy svc_fh and posix_acl representations the NFSD VFS layer
still uses. The procedure's pc_argzero is zero because the generated
decoder initializes argp->xdrgen, making the earlier defensive memset
of the argument buffer unnecessary.
An undefined bit in the request mask now yields ACL2ERR_INVAL rather
than ACL2ERR_IO. draft-ietf-nfsv4-nfs-acl Section 4.6.2 permits a
server to respond with INVAL or to ignore the bit, and IO is neither;
the NFS_ACLv3 procedure already reports INVAL.
Only the two ACLs remain hand-written. They are built from the
object's struct posix_acl by nfs_stream_encode_acl(), a form the
generated encoder cannot express, so the generated secattr member of
GETACL2resok is left unused. As with the NFSv3 READ and READLINK
procedures, generated fixed-layout fields are followed by a
hand-written tail.
The procedure now fills the fattr and fetches the ACLs while the file
handle is verified, then releases the file handle itself. The encoder
reads the ACL owner ids from the captured attributes rather than from
a live inode, so nfsaclsvc_release_getacl() shrinks to the two
posix_acl_release() calls. Those releases must stay a release-time job
because the encoder still consumes the posix_acls after the reply is
built.
Filling the fattr reuses the NFSv2 GETATTR conversion's logic: the
mode is masked with S_IALLUGO, a socket reports the Linux-internal
NFSOCK value, and a FIFO reports NFCHR with an rdev of 0xffffffff,
the pairing the NFS client decodes as S_IFIFO. The wire result
matches what the NFSv2 GETATTR encoder already produces.
The generated <linux/sunrpc/xdrgen/nfs_acl.h> defines enum ftype,
which collides with the enum nfs_ftype in <uapi/linux/nfs.h> and with
the generated NFSv2 header, so neither can be included here. The
inherited NFSv2 constants this procedure needs, NFS_MAXPATHLEN and
NFSOCK, are defined locally instead.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfs2acl.c | 246 ++++++++++++++++++++++++++++------------------
1 file changed, 151 insertions(+), 95 deletions(-)
diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
index 13e7415aa97b..8f0be7208e89 100644
--- a/fs/nfsd/nfs2acl.c
+++ b/fs/nfsd/nfs2acl.c
@@ -7,6 +7,7 @@
#include <linux/gfp.h>
#include <linux/fs.h>
+#include <linux/filelock.h>
#include <linux/nfsacl.h>
#include <linux/nfs3.h>
#include <linux/nfs4.h>
@@ -17,12 +18,21 @@
#include "cache.h"
#include "nfsd.h"
+#include "auth.h"
#include "vfs.h"
#define NFSDDBG_FACILITY NFSDDBG_PROC
#define NFS_ACL_MASK (NA_ACL | NA_ACLCNT | NA_DFACL | NA_DFACLCNT)
+/*
+ * The generated <linux/sunrpc/xdrgen/nfs_acl2.h> defines enum ftype, so
+ * <uapi/linux/nfs.h> (which also defines enum nfs_ftype with the same
+ * NFNON..NFLNK enumerators) and the generated nfs2 header cannot be
+ * included here. Define the inherited NFSv2 constant we need.
+ */
+#define NFS_MAXPATHLEN 1024
+
struct posix_acl;
struct nfsd_attrstat {
@@ -70,20 +80,6 @@ struct SETACL2args_wrapper {
static_assert(offsetof(struct SETACL2args_wrapper, xdrgen) == 0);
-struct nfsd3_getaclargs {
- struct svc_fh fh;
- __u32 mask;
-};
-
-struct nfsd3_getaclres {
- __be32 status;
- struct svc_fh fh;
- int mask;
- struct posix_acl *acl_access;
- struct posix_acl *acl_default;
- struct kstat stat;
-};
-
struct nfsd3_setaclargs {
struct svc_fh fh;
__u32 mask;
@@ -215,6 +211,89 @@ static u32 nfsacld_acl_entries(const struct posix_acl *acl)
return (acl && acl->a_count >= 3) ? max_t(int, acl->a_count, 4) : 0;
}
+static __always_inline void
+nfsacld_fhandle_to_svc_fh(struct svc_fh *fhp, const fhandle *fhandle)
+{
+ fh_init(fhp, FHSIZE);
+ fhp->fh_handle.fh_size = FHSIZE;
+ memcpy(&fhp->fh_handle.fh_raw, fhandle, FHSIZE);
+}
+
+static __always_inline void
+nfsacld_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
+nfsacld_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
+nfsacld_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 = nfsacld_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);
+ break;
+ }
+ fattr->fsid = fsid;
+ fattr->fileid = stat->ino;
+
+ nfsacld_timespec64_to_timeval(&fattr->atime, &stat->atime);
+ time = stat->mtime;
+ lease_get_mtime(d_inode(fhp->fh_dentry), &time);
+ nfsacld_timespec64_to_timeval(&fattr->mtime, &time);
+ nfsacld_timespec64_to_timeval(&fattr->ctime, &stat->ctime);
+}
+
/**
* nfsacld_proc_null - NULL: Do nothing
* @rqstp: RPC transaction context
@@ -230,62 +309,84 @@ static __be32 nfsacld_proc_null(struct svc_rqst *rqstp)
return rpc_success;
}
-/*
- * Get the Access and/or Default ACL of a file.
+/**
+ * nfsacld_proc_getacl - GETACL: Get the Access and/or Default ACL of a file
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * GETACL2res ACLPROC2_GETACL(GETACL2args) = 1;
*/
static __be32 nfsacld_proc_getacl(struct svc_rqst *rqstp)
{
- struct nfsd3_getaclargs *argp = rqstp->rq_argp;
- struct nfsd3_getaclres *resp = rqstp->rq_resp;
+ struct GETACL2args_wrapper *argp = rqstp->rq_argp;
+ struct GETACL2res_wrapper *resp = rqstp->rq_resp;
+ struct svc_fh *fhp = &argp->fh;
struct posix_acl *acl;
struct inode *inode;
- svc_fh *fh;
- dprintk("nfsd: GETACL(2acl) %s\n", SVCFH_fmt(&argp->fh));
+ nfsacld_fhandle_to_svc_fh(fhp, &argp->xdrgen.fh);
+ dprintk("nfsd: GETACL(2acl) %s\n", SVCFH_fmt(fhp));
- fh = fh_copy(&resp->fh, &argp->fh);
- resp->status = fh_verify(rqstp, &resp->fh, 0, NFSD_MAY_NOP);
- if (resp->status != acl2_ok)
+ resp->xdrgen.status = fh_verify(rqstp, fhp, 0, NFSD_MAY_NOP);
+ if (resp->xdrgen.status != acl2_ok)
goto out;
+ inode = d_inode(fhp->fh_dentry);
- inode = d_inode(fh->fh_dentry);
-
- if (argp->mask & ~NFS_ACL_MASK) {
- resp->status = acl2err_io;
+ if (argp->xdrgen.mask & ~NFS_ACL_MASK) {
+ resp->xdrgen.status = acl2err_inval;
goto out;
}
- resp->mask = argp->mask;
+ resp->mask = argp->xdrgen.mask;
- resp->status = fh_getattr(fh, &resp->stat);
- if (resp->status != acl2_ok)
+ resp->xdrgen.status = fh_getattr(fhp, &resp->stat);
+ if (resp->xdrgen.status != acl2_ok)
goto out;
+ nfsacld_stat_to_fattr(rqstp, &resp->xdrgen.u.resok.attr, &resp->stat,
+ fhp);
+
if (resp->mask & (NA_ACL | NA_ACLCNT)) {
acl = get_inode_acl(inode, ACL_TYPE_ACCESS);
if (acl == NULL) {
- /* Solaris returns the inode's minimum ACL. */
+ /*
+ * When no ACL is present, emit a minimal ACL from
+ * the mode bits, per draft-ietf-nfsv4-nfs-acl
+ * Section 5.2.2.4.
+ */
acl = posix_acl_from_mode(inode->i_mode, GFP_KERNEL);
}
if (IS_ERR(acl)) {
- resp->status = nfsacld_map_errno(PTR_ERR(acl));
+ resp->xdrgen.status = nfsacld_map_errno(PTR_ERR(acl));
goto fail;
}
resp->acl_access = acl;
}
if (resp->mask & (NA_DFACL | NA_DFACLCNT)) {
- /* Check how Solaris handles requests for the Default ACL
- of a non-directory! */
acl = get_inode_acl(inode, ACL_TYPE_DEFAULT);
if (IS_ERR(acl)) {
- resp->status = nfsacld_map_errno(PTR_ERR(acl));
+ resp->xdrgen.status = nfsacld_map_errno(PTR_ERR(acl));
goto fail;
}
resp->acl_default = acl;
}
- /* resp->acl_{access,default} are released in nfssvc_release_getacl. */
+ /*
+ * The generated secattr encoder streams the ACL entries from
+ * the posix_acls above via the encode hooks; the proc supplies
+ * the scalar fields. aclcnt/dfaclcnt carry the entry count
+ * independent of whether the matching list is requested.
+ */
+ resp->xdrgen.u.resok.acl.mask = resp->mask;
+ resp->xdrgen.u.resok.acl.aclcnt = nfsacld_acl_entries(resp->acl_access);
+ resp->xdrgen.u.resok.acl.dfaclcnt = nfsacld_acl_entries(resp->acl_default);
+
out:
- resp->status = nfsacld_map_status(resp->status);
+ /* resp->acl_{access,default} are released in nfsaclsvc_release_getacl. */
+ fh_put(fhp);
+ resp->xdrgen.status = nfsacld_map_status(resp->xdrgen.status);
return rpc_success;
fail:
@@ -598,19 +699,6 @@ bool nfs_acl2_secattr_decode_end(struct xdrgen_aggregate_cursor *c, bool ok)
* XDR decode functions
*/
-static bool
-nfsaclsvc_decode_getaclargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd3_getaclargs *argp = rqstp->rq_argp;
-
- if (!svcxdr_decode_fhandle(xdr, &argp->fh))
- return false;
- if (xdr_stream_decode_u32(xdr, &argp->mask) < 0)
- return false;
-
- return true;
-}
-
static bool
nfsaclsvc_decode_setaclargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
@@ -649,35 +737,6 @@ nfsaclsvc_decode_accessargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
* XDR encode functions
*/
-/* GETACL */
-static bool
-nfsaclsvc_encode_getaclres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd3_getaclres *resp = rqstp->rq_resp;
- struct dentry *dentry = resp->fh.fh_dentry;
- struct inode *inode;
-
- if (!svcxdr_encode_stat(xdr, resp->status))
- return false;
- switch (resp->status) {
- case acl2_ok:
- inode = d_inode(dentry);
- if (!svcxdr_encode_fattr(rqstp, xdr, &resp->fh, &resp->stat))
- return false;
- if (xdr_stream_encode_u32(xdr, resp->mask) < 0)
- return false;
- if (!nfs_stream_encode_acl(xdr, inode, resp->acl_access,
- resp->mask & NA_ACL, 0))
- return false;
- if (!nfs_stream_encode_acl(xdr, inode, resp->acl_default,
- resp->mask & NA_DFACL, NA_ACL_DEFAULT))
- return false;
- break;
- }
-
- return true;
-}
-
/* ACCESS */
static bool
nfsaclsvc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
@@ -703,9 +762,8 @@ nfsaclsvc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
*/
static void nfsaclsvc_release_getacl(struct svc_rqst *rqstp)
{
- struct nfsd3_getaclres *resp = rqstp->rq_resp;
+ struct GETACL2res_wrapper *resp = rqstp->rq_resp;
- fh_put(&resp->fh);
posix_acl_release(resp->acl_access);
posix_acl_release(resp->acl_default);
}
@@ -729,8 +787,6 @@ static void nfsaclsvc_release_setacl(struct svc_rqst *rqstp)
#define ST 1 /* status*/
#define AT 21 /* attributes */
-#define pAT (1+AT) /* post attributes - conditional */
-#define ACL (1+NFS_ACL_MAX_ENTRIES*3) /* Access Control List */
static const struct svc_procedure nfsd_acl_procedures2[5] = {
[ACLPROC2_NULL] = {
@@ -745,16 +801,16 @@ static const struct svc_procedure nfsd_acl_procedures2[5] = {
.pc_name = "NULL",
},
[ACLPROC2_GETACL] = {
- .pc_func = nfsacld_proc_getacl,
- .pc_decode = nfsaclsvc_decode_getaclargs,
- .pc_encode = nfsaclsvc_encode_getaclres,
- .pc_release = nfsaclsvc_release_getacl,
- .pc_argsize = sizeof(struct nfsd3_getaclargs),
- .pc_argzero = sizeof(struct nfsd3_getaclargs),
- .pc_ressize = sizeof(struct nfsd3_getaclres),
- .pc_cachetype = RC_NOCACHE,
- .pc_xdrressize = ST+1+2*(1+ACL),
- .pc_name = "GETACL",
+ .pc_func = nfsacld_proc_getacl,
+ .pc_decode = nfs_acl_svc_decode_GETACL2args,
+ .pc_encode = nfs_acl_svc_encode_GETACL2res,
+ .pc_release = nfsaclsvc_release_getacl,
+ .pc_argsize = sizeof(struct GETACL2args_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct GETACL2res_wrapper),
+ .pc_cachetype = RC_NOCACHE,
+ .pc_xdrressize = NFS_ACL2_GETACL2res_sz,
+ .pc_name = "GETACL",
},
[ACLPROC2_SETACL] = {
.pc_func = nfsacld_proc_setacl,
@@ -798,11 +854,11 @@ static const struct svc_procedure nfsd_acl_procedures2[5] = {
* Storage requirements for XDR arguments and results.
*/
union nfsd_acl2_xdrstore {
- struct nfsd3_getaclargs getaclargs;
+ struct GETACL2args_wrapper getaclargs;
struct nfsd3_setaclargs setaclargs;
struct nfsd3_accessargs accessargs;
struct nfsd_fhandle fhandle;
- struct nfsd3_getaclres getaclres;
+ struct GETACL2res_wrapper getaclres;
struct nfsd_attrstat attrstat;
struct nfsd3_accessres accessres;
};
--
2.56.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v1 14/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 SETACL procedure
2026-10-22 13:53 [PATCH v1 00/17] Convert server-side NFS_ACLv2 XDR to use xdrgen Chuck Lever
` (12 preceding siblings ...)
2026-10-22 13:53 ` [PATCH v1 13/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 GETACL procedure Chuck Lever
@ 2026-10-22 13:53 ` Chuck Lever
2026-10-22 13:53 ` [PATCH v1 15/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 GETATTR procedure Chuck Lever
` (2 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Chuck Lever @ 2026-10-22 13:53 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Convert the NFS-ACL version 2 SETACL procedure to xdrgen. Because
the reply carries only a status word and, on success, the file
attributes, it is emitted entirely by the generated
nfs_acl_svc_encode_SETACL2res(); unlike the GETACL conversion, no
hand-written reply tail is needed. A result wrapper bridges the
generated SETACL2res type and the scratch kstat that the NFSD VFS
layer fills.
The argument decoder is generated in full. SETACL2args carries a
secattr whose aclent arrays must become a struct posix_acl for the
VFS layer; the hook-driven aggregate codec added earlier performs
that per-entry transformation, so nfs_acl_svc_decode_SETACL2args()
owns the wire framing and drives the application hooks. An argument
wrapper pairs the generated SETACL2args with the decoded posix_acl
pointers and an "invalid" flag.
pc_argzero covers the whole argument wrapper, so the buffer is
zeroed before decode: the two posix_acl pointers start NULL, as the
unconditional release callback requires, and the "invalid" flag
starts clear.
The reply no longer carries a file handle, so the procedure
verifies the handle, sets the ACLs, captures the resulting
attributes, and releases the handle itself; the release callback
shrinks to the two posix_acl_release() calls for the decoded
argument ACLs. When the decoder has marked the argument ACL invalid
(see the codec patch), the procedure reports ACL2ERR_INVAL before
authorization and does nothing else. A default ACL presented for an
object that is not a directory is reported as ACL2ERR_INVAL once
the handle is verified, where set_posix_acl() would have reported
EACCES; draft-ietf-nfsv4-nfs-acl Section 3.4.4.2 lists that case
among the invalid ACLs. Filling the fattr reuses
nfsacld_stat_to_fattr() from the GETACL conversion, so the wire
result matches what the NFSv2 GETATTR encoder already produces.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfs2acl.c | 120 +++++++++++++++++++++++-----------------------
1 file changed, 60 insertions(+), 60 deletions(-)
diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
index 8f0be7208e89..24ec141796b4 100644
--- a/fs/nfsd/nfs2acl.c
+++ b/fs/nfsd/nfs2acl.c
@@ -80,13 +80,13 @@ struct SETACL2args_wrapper {
static_assert(offsetof(struct SETACL2args_wrapper, xdrgen) == 0);
-struct nfsd3_setaclargs {
- struct svc_fh fh;
- __u32 mask;
- struct posix_acl *acl_access;
- struct posix_acl *acl_default;
+struct SETACL2res_wrapper {
+ struct SETACL2res xdrgen;
+ struct kstat stat;
};
+static_assert(offsetof(struct SETACL2res_wrapper, xdrgen) == 0);
+
struct nfsd3_accessargs {
struct svc_fh fh;
__u32 access;
@@ -397,41 +397,59 @@ static __be32 nfsacld_proc_getacl(struct svc_rqst *rqstp)
goto out;
}
-/*
- * Set the Access and/or Default ACL of a file.
+/**
+ * nfsacld_proc_setacl - SETACL: Set the Access and/or Default ACL of a file
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * SETACL2res ACLPROC2_SETACL(SETACL2args) = 2;
*/
static __be32 nfsacld_proc_setacl(struct svc_rqst *rqstp)
{
- struct nfsd3_setaclargs *argp = rqstp->rq_argp;
- struct nfsd_attrstat *resp = rqstp->rq_resp;
+ struct SETACL2args_wrapper *argp = rqstp->rq_argp;
+ struct SETACL2res_wrapper *resp = rqstp->rq_resp;
+ struct svc_fh *fhp = &argp->fh;
struct inode *inode;
- svc_fh *fh;
int error;
- dprintk("nfsd: SETACL(2acl) %s\n", SVCFH_fmt(&argp->fh));
+ nfsacld_fhandle_to_svc_fh(fhp, &argp->xdrgen.fh);
+ dprintk("nfsd: SETACL(2acl) %s\n", SVCFH_fmt(fhp));
- fh = fh_copy(&resp->fh, &argp->fh);
- resp->status = fh_verify(rqstp, &resp->fh, 0, NFSD_MAY_SATTR);
- if (resp->status != acl2_ok)
+ /* A well-formed but invalid ACL is rejected before authorization. */
+ if (argp->invalid) {
+ resp->xdrgen.status = acl2err_inval;
goto out;
+ }
- inode = d_inode(fh->fh_dentry);
+ resp->xdrgen.status = fh_verify(rqstp, fhp, 0, NFSD_MAY_SATTR);
+ if (resp->xdrgen.status != acl2_ok)
+ goto out;
+ inode = d_inode(fhp->fh_dentry);
- error = fh_want_write(fh);
+ /* The VFS reports a default ACL on a non-directory as EACCES. */
+ if (argp->acl_default && !S_ISDIR(inode->i_mode)) {
+ resp->xdrgen.status = acl2err_inval;
+ goto out;
+ }
+
+ error = fh_want_write(fhp);
if (error)
goto out_errno;
inode_lock(inode);
error = 0;
- if (argp->mask & NA_ACL) {
- error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry,
+ if (argp->xdrgen.acl.mask & NA_ACL) {
+ error = set_posix_acl(&nop_mnt_idmap, fhp->fh_dentry,
ACL_TYPE_ACCESS, argp->acl_access);
if (error)
goto out_drop_lock;
}
- if (argp->mask & NA_DFACL) {
- error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry,
+ if (argp->xdrgen.acl.mask & NA_DFACL) {
+ error = set_posix_acl(&nop_mnt_idmap, fhp->fh_dentry,
ACL_TYPE_DEFAULT, argp->acl_default);
if (error)
goto out_drop_lock;
@@ -439,20 +457,24 @@ static __be32 nfsacld_proc_setacl(struct svc_rqst *rqstp)
inode_unlock(inode);
- fh_drop_write(fh);
+ fh_drop_write(fhp);
- resp->status = fh_getattr(fh, &resp->stat);
+ resp->xdrgen.status = fh_getattr(fhp, &resp->stat);
out:
+ if (resp->xdrgen.status == acl2_ok)
+ nfsacld_stat_to_fattr(rqstp, &resp->xdrgen.u.resok.attr,
+ &resp->stat, fhp);
/* argp->acl_{access,default} are released in nfsaclsvc_release_setacl. */
- resp->status = nfsacld_map_status(resp->status);
+ fh_put(fhp);
+ resp->xdrgen.status = nfsacld_map_status(resp->xdrgen.status);
return rpc_success;
out_drop_lock:
inode_unlock(inode);
- fh_drop_write(fh);
+ fh_drop_write(fhp);
out_errno:
- resp->status = nfsacld_map_errno(error);
+ resp->xdrgen.status = nfsacld_map_errno(error);
goto out;
}
@@ -699,27 +721,6 @@ bool nfs_acl2_secattr_decode_end(struct xdrgen_aggregate_cursor *c, bool ok)
* XDR decode functions
*/
-static bool
-nfsaclsvc_decode_setaclargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd3_setaclargs *argp = rqstp->rq_argp;
-
- if (!svcxdr_decode_fhandle(xdr, &argp->fh))
- return false;
- if (xdr_stream_decode_u32(xdr, &argp->mask) < 0)
- return false;
- if (argp->mask & ~NFS_ACL_MASK)
- return false;
- if (!nfs_stream_decode_acl(xdr, NULL, (argp->mask & NA_ACL) ?
- &argp->acl_access : NULL))
- return false;
- if (!nfs_stream_decode_acl(xdr, NULL, (argp->mask & NA_DFACL) ?
- &argp->acl_default : NULL))
- return false;
-
- return true;
-}
-
static bool
nfsaclsvc_decode_accessargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
@@ -777,10 +778,8 @@ static void nfsaclsvc_release_access(struct svc_rqst *rqstp)
static void nfsaclsvc_release_setacl(struct svc_rqst *rqstp)
{
- struct nfsd3_setaclargs *argp = rqstp->rq_argp;
- struct nfsd_attrstat *resp = rqstp->rq_resp;
+ struct SETACL2args_wrapper *argp = rqstp->rq_argp;
- fh_put(&resp->fh);
posix_acl_release(argp->acl_access);
posix_acl_release(argp->acl_default);
}
@@ -813,16 +812,16 @@ static const struct svc_procedure nfsd_acl_procedures2[5] = {
.pc_name = "GETACL",
},
[ACLPROC2_SETACL] = {
- .pc_func = nfsacld_proc_setacl,
- .pc_decode = nfsaclsvc_decode_setaclargs,
- .pc_encode = nfssvc_encode_attrstatres,
- .pc_release = nfsaclsvc_release_setacl,
- .pc_argsize = sizeof(struct nfsd3_setaclargs),
- .pc_argzero = sizeof(struct nfsd3_setaclargs),
- .pc_ressize = sizeof(struct nfsd_attrstat),
- .pc_cachetype = RC_NOCACHE,
- .pc_xdrressize = ST+AT,
- .pc_name = "SETACL",
+ .pc_func = nfsacld_proc_setacl,
+ .pc_decode = nfs_acl_svc_decode_SETACL2args,
+ .pc_encode = nfs_acl_svc_encode_SETACL2res,
+ .pc_release = nfsaclsvc_release_setacl,
+ .pc_argsize = sizeof(struct SETACL2args_wrapper),
+ .pc_argzero = sizeof(struct SETACL2args_wrapper),
+ .pc_ressize = sizeof(struct SETACL2res_wrapper),
+ .pc_cachetype = RC_NOCACHE,
+ .pc_xdrressize = NFS_ACL2_SETACL2res_sz,
+ .pc_name = "SETACL",
},
[ACLPROC2_GETATTR] = {
.pc_func = nfsacld_proc_getattr,
@@ -855,10 +854,11 @@ static const struct svc_procedure nfsd_acl_procedures2[5] = {
*/
union nfsd_acl2_xdrstore {
struct GETACL2args_wrapper getaclargs;
- struct nfsd3_setaclargs setaclargs;
+ struct SETACL2args_wrapper setaclargs;
struct nfsd3_accessargs accessargs;
struct nfsd_fhandle fhandle;
struct GETACL2res_wrapper getaclres;
+ struct SETACL2res_wrapper setaclres;
struct nfsd_attrstat attrstat;
struct nfsd3_accessres accessres;
};
--
2.56.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v1 15/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 GETATTR procedure
2026-10-22 13:53 [PATCH v1 00/17] Convert server-side NFS_ACLv2 XDR to use xdrgen Chuck Lever
` (13 preceding siblings ...)
2026-10-22 13:53 ` [PATCH v1 14/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 SETACL procedure Chuck Lever
@ 2026-10-22 13:53 ` Chuck Lever
2026-10-22 13:53 ` [PATCH v1 16/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 ACCESS procedure Chuck Lever
2026-10-22 13:53 ` [PATCH v1 17/17] NFSD: Map every NFS_ACLv2 reply status into aclstat2 Chuck Lever
16 siblings, 0 replies; 18+ messages in thread
From: Chuck Lever @ 2026-10-22 13:53 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Convert the NFS-ACL version 2 GETATTR procedure to xdrgen. Unlike
the GETACL and SETACL conversions, neither direction needs a
hand-written XDR tail: GETATTR2args carries only a file handle, so
the generated nfs_acl_svc_decode_GETATTR2args decodes the argument
in full, and GETATTR2res carries only a status word and, on
success, the file attributes, so the generated
nfs_acl_svc_encode_GETATTR2res emits the entire reply. Wrapper
structures bridge the generated GETATTR2args and GETATTR2res types
and the legacy svc_fh and scratch kstat that the NFSD VFS layer
still uses.
The procedure's pc_argzero is zero because the generated decoder
writes the whole GETATTR2args, making the earlier defensive memset
of the argument buffer unnecessary.
The reply no longer carries a file handle, so the procedure
verifies the handle, captures the resulting attributes, and
releases the handle itself. GETATTR has no ACLs to release, so its
release callback is dropped entirely. Filling the fattr reuses
nfsacld_stat_to_fattr() from the GETACL conversion, so the wire
result matches what the NFSv2 GETATTR encoder already produces.
GETATTR was the last NFS-ACL v2 procedure to use the file-local
struct nfsd_attrstat and the generic struct nfsd_fhandle, so this
removes that structure definition and the two now-unused members
of the XDR storage union.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfs2acl.c | 81 ++++++++++++++++++++++++++++-------------------
1 file changed, 48 insertions(+), 33 deletions(-)
diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
index 24ec141796b4..d4261ef076bd 100644
--- a/fs/nfsd/nfs2acl.c
+++ b/fs/nfsd/nfs2acl.c
@@ -35,12 +35,6 @@
struct posix_acl;
-struct nfsd_attrstat {
- __be32 status;
- struct svc_fh fh;
- struct kstat stat;
-};
-
/*
* Wrapper structures combine xdrgen types with legacy structures.
* The xdrgen field must be first so the structure can be cast
@@ -87,6 +81,20 @@ struct SETACL2res_wrapper {
static_assert(offsetof(struct SETACL2res_wrapper, xdrgen) == 0);
+struct GETATTR2args_wrapper {
+ struct GETATTR2args xdrgen;
+ struct svc_fh fh;
+};
+
+static_assert(offsetof(struct GETATTR2args_wrapper, xdrgen) == 0);
+
+struct GETATTR2res_wrapper {
+ struct GETATTR2res xdrgen;
+ struct kstat stat;
+};
+
+static_assert(offsetof(struct GETATTR2res_wrapper, xdrgen) == 0);
+
struct nfsd3_accessargs {
struct svc_fh fh;
__u32 access;
@@ -99,11 +107,6 @@ struct nfsd3_accessres {
struct kstat stat;
};
-bool nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-bool nfssvc_encode_attrstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
-
-void nfssvc_release_attrstat(struct svc_rqst *rqstp);
-
bool svcxdr_decode_fhandle(struct xdr_stream *xdr, struct svc_fh *fhp);
bool svcxdr_encode_stat(struct xdr_stream *xdr, __be32 status);
bool svcxdr_encode_fattr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
@@ -478,23 +481,36 @@ static __be32 nfsacld_proc_setacl(struct svc_rqst *rqstp)
goto out;
}
-/*
- * Check file attributes
+/**
+ * nfsacld_proc_getattr - GETATTR: Get the attributes of a file
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * GETATTR2res ACLPROC2_GETATTR(GETATTR2args) = 3;
*/
static __be32 nfsacld_proc_getattr(struct svc_rqst *rqstp)
{
- struct nfsd_fhandle *argp = rqstp->rq_argp;
- struct nfsd_attrstat *resp = rqstp->rq_resp;
+ struct GETATTR2args_wrapper *argp = rqstp->rq_argp;
+ struct GETATTR2res_wrapper *resp = rqstp->rq_resp;
+ struct svc_fh *fhp = &argp->fh;
- dprintk("nfsd: GETATTR %s\n", SVCFH_fmt(&argp->fh));
+ nfsacld_fhandle_to_svc_fh(fhp, &argp->xdrgen.fh);
+ dprintk("nfsd: GETATTR %s\n", SVCFH_fmt(fhp));
- fh_copy(&resp->fh, &argp->fh);
- resp->status = fh_verify(rqstp, &resp->fh, 0, NFSD_MAY_NOP);
- if (resp->status != acl2_ok)
+ resp->xdrgen.status = fh_verify(rqstp, fhp, 0, NFSD_MAY_NOP);
+ if (resp->xdrgen.status != acl2_ok)
goto out;
- resp->status = fh_getattr(&resp->fh, &resp->stat);
+ resp->xdrgen.status = fh_getattr(fhp, &resp->stat);
+
out:
- resp->status = nfsacld_map_status(resp->status);
+ if (resp->xdrgen.status == acl2_ok)
+ nfsacld_stat_to_fattr(rqstp, &resp->xdrgen.u.resok.attr,
+ &resp->stat, fhp);
+ fh_put(fhp);
+ resp->xdrgen.status = nfsacld_map_status(resp->xdrgen.status);
return rpc_success;
}
@@ -824,16 +840,15 @@ static const struct svc_procedure nfsd_acl_procedures2[5] = {
.pc_name = "SETACL",
},
[ACLPROC2_GETATTR] = {
- .pc_func = nfsacld_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 = nfsacld_proc_getattr,
+ .pc_decode = nfs_acl_svc_decode_GETATTR2args,
+ .pc_encode = nfs_acl_svc_encode_GETATTR2res,
+ .pc_argsize = sizeof(struct GETATTR2args_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct GETATTR2res_wrapper),
+ .pc_cachetype = RC_NOCACHE,
+ .pc_xdrressize = NFS_ACL2_GETATTR2res_sz,
+ .pc_name = "GETATTR",
},
[ACLPROC2_ACCESS] = {
.pc_func = nfsacld_proc_access,
@@ -855,11 +870,11 @@ static const struct svc_procedure nfsd_acl_procedures2[5] = {
union nfsd_acl2_xdrstore {
struct GETACL2args_wrapper getaclargs;
struct SETACL2args_wrapper setaclargs;
+ struct GETATTR2args_wrapper getattrargs;
struct nfsd3_accessargs accessargs;
- struct nfsd_fhandle fhandle;
struct GETACL2res_wrapper getaclres;
struct SETACL2res_wrapper setaclres;
- struct nfsd_attrstat attrstat;
+ struct GETATTR2res_wrapper getattrres;
struct nfsd3_accessres accessres;
};
--
2.56.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v1 16/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 ACCESS procedure
2026-10-22 13:53 [PATCH v1 00/17] Convert server-side NFS_ACLv2 XDR to use xdrgen Chuck Lever
` (14 preceding siblings ...)
2026-10-22 13:53 ` [PATCH v1 15/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 GETATTR procedure Chuck Lever
@ 2026-10-22 13:53 ` Chuck Lever
2026-10-22 13:53 ` [PATCH v1 17/17] NFSD: Map every NFS_ACLv2 reply status into aclstat2 Chuck Lever
16 siblings, 0 replies; 18+ messages in thread
From: Chuck Lever @ 2026-10-22 13:53 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Convert the NFS-ACL version 2 ACCESS procedure to xdrgen. As with
the GETATTR conversion, neither direction needs a hand-written XDR
tail: ACCESS2args carries a file handle and an access bitmask, so
the generated nfs_acl_svc_decode_ACCESS2args decodes the argument
in full, and ACCESS2res carries a status word and, on success, the
file attributes and the granted access bits, so the generated
nfs_acl_svc_encode_ACCESS2res emits the entire reply. On an error
status the encoder emits only the status word, matching what the
hand-written encoder produced. Wrapper structures bridge the
generated ACCESS2args and ACCESS2res types and the legacy svc_fh
and scratch kstat that the NFSD VFS layer still uses.
The procedure's pc_argzero is zero because the generated decoder
writes the whole ACCESS2args, making the earlier defensive memset
of the argument buffer unnecessary.
Because nfsacld_proc_access() now fills in the reply attributes and
releases the file handle itself, the fh_getattr() call moves into
the proc function and the separate ->pc_release callback is dropped.
This makes it straightforward to confirm the handle is released on
every flow.
ACCESS was the only remaining user of the file-local struct
nfsd3_accessargs and struct nfsd3_accessres, so this removes those
definitions, the hand-written accessargs decoder and accessres
encoder, and the ST and AT size macros that only the ACCESS entry
still referenced. The svcxdr_decode_fhandle, svcxdr_encode_stat,
and svcxdr_encode_fattr declarations lose their last callers here
and are removed as well.
While rewriting the procedure entry, correct its pc_name from the
stale "SETATTR" to "ACCESS".
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfs2acl.c | 132 ++++++++++++++++------------------------------
1 file changed, 45 insertions(+), 87 deletions(-)
diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
index d4261ef076bd..a805d2ac76db 100644
--- a/fs/nfsd/nfs2acl.c
+++ b/fs/nfsd/nfs2acl.c
@@ -95,22 +95,19 @@ struct GETATTR2res_wrapper {
static_assert(offsetof(struct GETATTR2res_wrapper, xdrgen) == 0);
-struct nfsd3_accessargs {
+struct ACCESS2args_wrapper {
+ struct ACCESS2args xdrgen;
struct svc_fh fh;
- __u32 access;
};
-struct nfsd3_accessres {
- __be32 status;
- struct svc_fh fh;
- __u32 access;
+static_assert(offsetof(struct ACCESS2args_wrapper, xdrgen) == 0);
+
+struct ACCESS2res_wrapper {
+ struct ACCESS2res xdrgen;
struct kstat stat;
};
-bool svcxdr_decode_fhandle(struct xdr_stream *xdr, struct svc_fh *fhp);
-bool svcxdr_encode_stat(struct xdr_stream *xdr, __be32 status);
-bool svcxdr_encode_fattr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
- const struct svc_fh *fhp, const struct kstat *stat);
+static_assert(offsetof(struct ACCESS2res_wrapper, xdrgen) == 0);
/*
* These maps are identical to the NFSv3 maps (nfs3proc.c). This enables
@@ -514,28 +511,41 @@ static __be32 nfsacld_proc_getattr(struct svc_rqst *rqstp)
return rpc_success;
}
-/*
- * Check file access
+/**
+ * nfsacld_proc_access - ACCESS: Check access permission
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * ACCESS2res ACLPROC2_ACCESS(ACCESS2args) = 4;
*/
static __be32 nfsacld_proc_access(struct svc_rqst *rqstp)
{
- struct nfsd3_accessargs *argp = rqstp->rq_argp;
- struct nfsd3_accessres *resp = rqstp->rq_resp;
+ struct ACCESS2args_wrapper *argp = rqstp->rq_argp;
+ struct ACCESS2res_wrapper *resp = rqstp->rq_resp;
+ struct svc_fh *fhp = &argp->fh;
+ nfsacld_fhandle_to_svc_fh(fhp, &argp->xdrgen.fh);
dprintk("nfsd: ACCESS(2acl) %s 0x%x\n",
- SVCFH_fmt(&argp->fh),
- argp->access);
+ SVCFH_fmt(fhp), argp->xdrgen.access);
- fh_copy(&resp->fh, &argp->fh);
- resp->access = argp->access;
-
- resp->status = nfsd_access(rqstp, &resp->fh, &nfsd2_access_maps,
- &resp->access, NULL);
- if (resp->status != acl2_ok)
+ resp->xdrgen.status = nfsd_access(rqstp, fhp, &nfsd2_access_maps,
+ &argp->xdrgen.access, NULL);
+ if (resp->xdrgen.status != acl2_ok)
goto out;
- resp->status = fh_getattr(&resp->fh, &resp->stat);
+ resp->xdrgen.status = fh_getattr(fhp, &resp->stat);
+
out:
- resp->status = nfsacld_map_status(resp->status);
+ if (resp->xdrgen.status == acl2_ok) {
+ struct ACCESS2resok *resok = &resp->xdrgen.u.resok;
+
+ resok->access = argp->xdrgen.access;
+ nfsacld_stat_to_fattr(rqstp, &resok->attr, &resp->stat, fhp);
+ }
+ fh_put(fhp);
+ resp->xdrgen.status = nfsacld_map_status(resp->xdrgen.status);
return rpc_success;
}
@@ -733,47 +743,6 @@ bool nfs_acl2_secattr_decode_end(struct xdrgen_aggregate_cursor *c, bool ok)
return posix_acl_from_nfsacl(acl) == 0;
}
-/*
- * XDR decode functions
- */
-
-static bool
-nfsaclsvc_decode_accessargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd3_accessargs *args = rqstp->rq_argp;
-
- if (!svcxdr_decode_fhandle(xdr, &args->fh))
- return false;
- if (xdr_stream_decode_u32(xdr, &args->access) < 0)
- return false;
-
- return true;
-}
-
-/*
- * XDR encode functions
- */
-
-/* ACCESS */
-static bool
-nfsaclsvc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd3_accessres *resp = rqstp->rq_resp;
-
- if (!svcxdr_encode_stat(xdr, resp->status))
- return false;
- switch (resp->status) {
- case acl2_ok:
- if (!svcxdr_encode_fattr(rqstp, xdr, &resp->fh, &resp->stat))
- return false;
- if (xdr_stream_encode_u32(xdr, resp->access) < 0)
- return false;
- break;
- }
-
- return true;
-}
-
/*
* XDR release functions
*/
@@ -785,13 +754,6 @@ static void nfsaclsvc_release_getacl(struct svc_rqst *rqstp)
posix_acl_release(resp->acl_default);
}
-static void nfsaclsvc_release_access(struct svc_rqst *rqstp)
-{
- struct nfsd3_accessres *resp = rqstp->rq_resp;
-
- fh_put(&resp->fh);
-}
-
static void nfsaclsvc_release_setacl(struct svc_rqst *rqstp)
{
struct SETACL2args_wrapper *argp = rqstp->rq_argp;
@@ -800,9 +762,6 @@ static void nfsaclsvc_release_setacl(struct svc_rqst *rqstp)
posix_acl_release(argp->acl_default);
}
-#define ST 1 /* status*/
-#define AT 21 /* attributes */
-
static const struct svc_procedure nfsd_acl_procedures2[5] = {
[ACLPROC2_NULL] = {
.pc_func = nfsacld_proc_null,
@@ -851,16 +810,15 @@ static const struct svc_procedure nfsd_acl_procedures2[5] = {
.pc_name = "GETATTR",
},
[ACLPROC2_ACCESS] = {
- .pc_func = nfsacld_proc_access,
- .pc_decode = nfsaclsvc_decode_accessargs,
- .pc_encode = nfsaclsvc_encode_accessres,
- .pc_release = nfsaclsvc_release_access,
- .pc_argsize = sizeof(struct nfsd3_accessargs),
- .pc_argzero = sizeof(struct nfsd3_accessargs),
- .pc_ressize = sizeof(struct nfsd3_accessres),
- .pc_cachetype = RC_NOCACHE,
- .pc_xdrressize = ST+AT+1,
- .pc_name = "SETATTR",
+ .pc_func = nfsacld_proc_access,
+ .pc_decode = nfs_acl_svc_decode_ACCESS2args,
+ .pc_encode = nfs_acl_svc_encode_ACCESS2res,
+ .pc_argsize = sizeof(struct ACCESS2args_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct ACCESS2res_wrapper),
+ .pc_cachetype = RC_NOCACHE,
+ .pc_xdrressize = NFS_ACL2_ACCESS2res_sz,
+ .pc_name = "ACCESS",
},
};
@@ -871,11 +829,11 @@ union nfsd_acl2_xdrstore {
struct GETACL2args_wrapper getaclargs;
struct SETACL2args_wrapper setaclargs;
struct GETATTR2args_wrapper getattrargs;
- struct nfsd3_accessargs accessargs;
+ struct ACCESS2args_wrapper accessargs;
struct GETACL2res_wrapper getaclres;
struct SETACL2res_wrapper setaclres;
struct GETATTR2res_wrapper getattrres;
- struct nfsd3_accessres accessres;
+ struct ACCESS2res_wrapper accessres;
};
const struct svc_version nfsd_acl_version2 = {
--
2.56.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH v1 17/17] NFSD: Map every NFS_ACLv2 reply status into aclstat2
2026-10-22 13:53 [PATCH v1 00/17] Convert server-side NFS_ACLv2 XDR to use xdrgen Chuck Lever
` (15 preceding siblings ...)
2026-10-22 13:53 ` [PATCH v1 16/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 ACCESS procedure Chuck Lever
@ 2026-10-22 13:53 ` Chuck Lever
16 siblings, 0 replies; 18+ messages in thread
From: Chuck Lever @ 2026-10-22 13:53 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
nfsacld_map_status() translates only the three fh_verify() statuses
that have no NFSv2 equivalent, so every other status reaches the
encoder unchanged. fh_verify() returns nfserr_perm for a request from
an insecure port, and both it and fh_getattr() pass errno values
through nfserrno(), which yields nfserr_jukebox for ENOMEM, EAGAIN,
and ETIMEDOUT and nfserr_serverfault for an errno its table does not
list. NFS_ACL version 2 shares its status space with NFSv2, so 10008
and 10006 are values an NFS_ACLv2 client cannot interpret.
draft-ietf-nfsv4-nfs-acl Section 5.1.5 lists the status values an
NFS_ACLv2 server returns, and these are not among them.
Pass through the statuses that are members of aclstat2 and map the
rest to ACL2ERR_IO, the status nfsacld_map_errno() already uses for
an unlisted errno. Translate nfserr_notsupp to ACL2ERR_NOTSUPP, the
status nfsacld_map_errno() returns for EOPNOTSUPP. The existing
BADHANDLE, NOFILEHANDLE, and WRONGSEC translations are unchanged.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfs2acl.c | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
index a805d2ac76db..b4ab0bb1808d 100644
--- a/fs/nfsd/nfs2acl.c
+++ b/fs/nfsd/nfs2acl.c
@@ -187,15 +187,28 @@ static __be32 nfsacld_map_errno(int errno)
static __be32 nfsacld_map_status(__be32 status)
{
switch (status) {
+ case acl2_ok:
+ case acl2err_perm:
+ case acl2err_noent:
+ case acl2err_io:
+ case acl2err_acces:
+ case acl2err_inval:
+ case acl2err_nospc:
+ case acl2err_rofs:
+ case acl2err_notsupp:
+ case acl2err_dquot:
+ case acl2err_stale:
+ return status;
case cpu_to_be32(NFS4ERR_NOFILEHANDLE):
case cpu_to_be32(NFS3ERR_BADHANDLE):
- status = acl2err_stale;
- break;
+ return acl2err_stale;
case cpu_to_be32(NFS4ERR_WRONGSEC):
- status = acl2err_acces;
- break;
+ return acl2err_acces;
+ case cpu_to_be32(NFS3ERR_NOTSUPP):
+ return acl2err_notsupp;
+ default:
+ return acl2err_io;
}
- return status;
}
/*
--
2.56.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-10-22 13:53 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-22 13:53 [PATCH v1 00/17] Convert server-side NFS_ACLv2 XDR to use xdrgen Chuck Lever
2026-10-22 13:53 ` [PATCH v1 01/17] xdrgen: Reject out-of-range unsigned short values on decode Chuck Lever
2026-10-22 13:53 ` [PATCH v1 02/17] xdrgen: Reject out-of-range " Chuck Lever
2026-10-22 13:53 ` [PATCH v1 03/17] nfs_common: Export posix_acl_from_nfsacl for server ACL codecs Chuck Lever
2026-10-22 13:53 ` [PATCH v1 04/17] NFSD: Remove the unused NFSACL_MAXWORDS macro Chuck Lever
2026-10-22 13:53 ` [PATCH v1 05/17] NFSD: Move the definition of NFS_ACL_MAX_ENTRIES Chuck Lever
2026-10-22 13:53 ` [PATCH v1 06/17] Documentation: Add the RPC language description of NFS_ACLv2 Chuck Lever
2026-10-22 13:53 ` [PATCH v1 07/17] NFSD: Add infrastructure for generating NFS_ACLv2 XDR encoders and decoders Chuck Lever
2026-10-22 13:53 ` [PATCH v1 08/17] NFSD: Remove '#include "xdr3.h"' from nfs2acl.c Chuck Lever
2026-10-22 13:53 ` [PATCH v1 09/17] NFSD: Remove '#include "xdr.h"' " Chuck Lever
2026-10-22 13:53 ` [PATCH v1 10/17] NFSD: Switch to using the NFS_ACLv2 status codes Chuck Lever
2026-10-22 13:53 ` [PATCH v1 11/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 NULL procedure Chuck Lever
2026-10-22 13:53 ` [PATCH v1 12/17] NFSD: Implement NFS-ACL v2 ACL encoding and decoding with xdrgen Chuck Lever
2026-10-22 13:53 ` [PATCH v1 13/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 GETACL procedure Chuck Lever
2026-10-22 13:53 ` [PATCH v1 14/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 SETACL procedure Chuck Lever
2026-10-22 13:53 ` [PATCH v1 15/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 GETATTR procedure Chuck Lever
2026-10-22 13:53 ` [PATCH v1 16/17] NFSD: Use xdrgen XDR functions for the NFS-ACL v2 ACCESS procedure Chuck Lever
2026-10-22 13:53 ` [PATCH v1 17/17] NFSD: Map every NFS_ACLv2 reply status into aclstat2 Chuck Lever
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox