* [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen
@ 2026-10-25 15:16 Chuck Lever
2026-10-25 15:16 ` [PATCH v1 01/13] Documentation: Add the RPC language description of NFS_ACLv3 Chuck Lever
` (13 more replies)
0 siblings, 14 replies; 16+ messages in thread
From: Chuck Lever @ 2026-10-25 15:16 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Replace NFSD's NFS_ACLv3 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_acl3.x is copied from
draft-ietf-nfsv4-nfs-acl.
Chuck Lever (13):
Documentation: Add the RPC language description of NFS_ACLv3
NFSD: Add infrastructure for generating NFS_ACLv3 XDR encoders and
decoders
NFSD: Remove '#include "xdr3.h"' from nfs3acl.c
NFSD: New NFS_ACLv3 status codes
NFSD: Use xdrgen XDR functions for the NFS-ACL v3 NULL procedure
NFSD: Implement NFS-ACL v3 ACL encoding and decoding with xdrgen
NFSD: Use xdrgen XDR functions for the NFS-ACL v3 GETACL procedure
NFSD: Use xdrgen XDR functions for the NFS-ACL v3 SETACL procedure
NFSD: Map every NFS_ACLv3 reply status into aclstat3
NFSD: Remove the hand-coded XDR helpers for the NFS_ACLv3 procedures
NFS: Move NFSACL_MAXPAGES into fs/nfs/nfs3_fs.h
NFSD: Share the attribute conversion helpers with the NFS_ACL procs
nfs_common: Remove the unused NFS ACL stream codecs
Documentation/sunrpc/xdr/nfs_acl3.x | 273 ++++++++
fs/nfs/nfs3_fs.h | 3 +
fs/nfs/nfs3xdr.c | 1 +
fs/nfs_common/nfsacl.c | 125 ----
fs/nfsd/Makefile | 13 +-
fs/nfsd/fattr.h | 20 +
fs/nfsd/nfs2acl.c | 99 +--
fs/nfsd/nfs3acl.c | 623 +++++++++++++-----
fs/nfsd/nfs3xdr.c | 166 -----
fs/nfsd/nfs_acl3xdr_gen.c | 839 +++++++++++++++++++++++++
fs/nfsd/nfs_acl3xdr_gen.h | 36 ++
fs/nfsd/nfsproc.c | 3 +-
fs/nfsd/xdr3.h | 8 +-
include/linux/nfsacl.h | 9 -
include/linux/sunrpc/xdrgen/nfs_acl3.h | 284 +++++++++
15 files changed, 1948 insertions(+), 554 deletions(-)
create mode 100644 Documentation/sunrpc/xdr/nfs_acl3.x
create mode 100644 fs/nfsd/fattr.h
create mode 100644 fs/nfsd/nfs_acl3xdr_gen.c
create mode 100644 fs/nfsd/nfs_acl3xdr_gen.h
create mode 100644 include/linux/sunrpc/xdrgen/nfs_acl3.h
--
2.56.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v1 01/13] Documentation: Add the RPC language description of NFS_ACLv3
2026-10-25 15:16 [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen Chuck Lever
@ 2026-10-25 15:16 ` Chuck Lever
2026-10-25 15:16 ` [PATCH v1 02/13] NFSD: Add infrastructure for generating NFS_ACLv3 XDR encoders and decoders Chuck Lever
` (12 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Chuck Lever @ 2026-10-25 15:16 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Converting nfs3acl.c from hand-written XDR codecs to xdrgen-generated
code first requires a machine-readable description of the protocol for
xdrgen to process. Include a copy of the RPC language description of
NFS_ACLv3, taken from the NFS ACL protocol specification.
The description carries no pragmas. Later patches add the header,
aggregate, and endianness pragmas as the generated code that needs
each one is introduced.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
Documentation/sunrpc/xdr/nfs_acl3.x | 268 ++++++++++++++++++++++++++++
1 file changed, 268 insertions(+)
create mode 100644 Documentation/sunrpc/xdr/nfs_acl3.x
diff --git a/Documentation/sunrpc/xdr/nfs_acl3.x b/Documentation/sunrpc/xdr/nfs_acl3.x
new file mode 100644
index 000000000000..913ecf3a369f
--- /dev/null
+++ b/Documentation/sunrpc/xdr/nfs_acl3.x
@@ -0,0 +1,268 @@
+/*
+ * 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 3 protocol
+ */
+
+typedef unsigned hyper uint64;
+typedef unsigned long uint32;
+typedef uint64 fileid3;
+typedef uint32 uid3;
+typedef uint32 gid3;
+typedef uint64 size3;
+typedef uint32 mode3;
+
+enum ftype3 {
+ NF3REG = 1,
+ NF3DIR = 2,
+ NF3BLK = 3,
+ NF3CHR = 4,
+ NF3LNK = 5,
+ NF3SOCK = 6,
+ NF3FIFO = 7
+};
+
+struct specdata3 {
+ uint32 specdata1;
+ uint32 specdata2;
+};
+
+const NFS3_FHSIZE = 64;
+
+struct nfs_fh3 {
+ opaque data<NFS3_FHSIZE>;
+};
+
+struct nfstime3 {
+ uint32 seconds;
+ uint32 nseconds;
+};
+
+struct fattr3 {
+ ftype3 type;
+ mode3 mode;
+ uint32 nlink;
+ uid3 uid;
+ gid3 gid;
+ size3 size;
+ size3 used;
+ specdata3 rdev;
+ uint64 fsid;
+ fileid3 fileid;
+ nfstime3 atime;
+ nfstime3 mtime;
+ nfstime3 ctime;
+};
+
+union post_op_attr switch (bool attributes_follow) {
+case TRUE:
+ fattr3 attributes;
+case FALSE:
+ void;
+};
+
+/*
+ * ACL error codes; the numeric values match codes with the same
+ * name used in NFS version 3.
+ */
+enum aclstat3 {
+ ACL3_OK = 0,
+ ACL3ERR_PERM = 1,
+ ACL3ERR_NOENT = 2,
+ ACL3ERR_IO = 5,
+ ACL3ERR_ACCES = 13,
+ ACL3ERR_INVAL = 22,
+ ACL3ERR_NOSPC = 28,
+ ACL3ERR_ROFS = 30,
+ ACL3ERR_DQUOT = 69,
+ ACL3ERR_STALE = 70,
+ ACL3ERR_BADHANDLE = 10001,
+ ACL3ERR_NOTSUPP = 10004,
+ ACL3ERR_SERVERFAULT = 10006,
+ ACL3ERR_JUKEBOX = 10008
+};
+
+/*
+ * NFS_ACL version 3 procedure arguments and results
+ */
+
+struct GETACL3args {
+ nfs_fh3 fh;
+ unsigned int mask;
+};
+
+struct GETACL3resok {
+ post_op_attr attr;
+ secattr acl;
+};
+
+struct GETACL3resfail {
+ post_op_attr attr;
+};
+
+union GETACL3res switch (aclstat3 status) {
+case ACL3_OK:
+ GETACL3resok resok;
+default:
+ GETACL3resfail resfail;
+};
+
+struct SETACL3args {
+ nfs_fh3 fh;
+ secattr acl;
+};
+
+struct SETACL3resok {
+ post_op_attr attr;
+};
+
+struct SETACL3resfail {
+ post_op_attr attr;
+};
+
+union SETACL3res switch (aclstat3 status) {
+case ACL3_OK:
+ SETACL3resok resok;
+default:
+ SETACL3resfail resfail;
+};
+
+/*
+ * This is the definition for the GETXATTRDIR procedure which
+ * applies to NFS Version 3 files.
+ */
+struct GETXATTRDIR3args {
+ nfs_fh3 fh;
+ bool create;
+};
+
+struct GETXATTRDIR3resok {
+ nfs_fh3 fh;
+ post_op_attr attr;
+};
+
+union GETXATTRDIR3res switch (aclstat3 status) {
+case ACL3_OK:
+ GETXATTRDIR3resok resok;
+default:
+ void;
+};
+
+program NFS_ACL_PROGRAM {
+ version NFS_ACL_V3 {
+ void
+ ACLPROC3_NULL(void) = 0;
+ GETACL3res
+ ACLPROC3_GETACL(GETACL3args) = 1;
+ SETACL3res
+ ACLPROC3_SETACL(SETACL3args) = 2;
+ GETXATTRDIR3res
+ ACLPROC3_GETXATTRDIR(GETXATTRDIR3args) = 3;
+ } = 3;
+} = 100227;
--
2.56.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v1 02/13] NFSD: Add infrastructure for generating NFS_ACLv3 XDR encoders and decoders
2026-10-25 15:16 [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen Chuck Lever
2026-10-25 15:16 ` [PATCH v1 01/13] Documentation: Add the RPC language description of NFS_ACLv3 Chuck Lever
@ 2026-10-25 15:16 ` Chuck Lever
2026-10-25 15:16 ` [PATCH v1 03/13] NFSD: Remove '#include "xdr3.h"' from nfs3acl.c Chuck Lever
` (11 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Chuck Lever @ 2026-10-25 15:16 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
NFSD's NFS_ACLv3 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_acl3.x. The generated code can be refreshed any time nfs_acl3.x
is modified by running "(cd fs/nfsd && make xdrgen)"
Signed-off-by: Chuck Lever <cel@kernel.org>
---
Documentation/sunrpc/xdr/nfs_acl3.x | 2 +
fs/nfsd/Makefile | 13 +-
fs/nfsd/nfs_acl3xdr_gen.c | 749 +++++++++++++++++++++++++
fs/nfsd/nfs_acl3xdr_gen.h | 24 +
include/linux/sunrpc/xdrgen/nfs_acl3.h | 284 ++++++++++
5 files changed, 1069 insertions(+), 3 deletions(-)
create mode 100644 fs/nfsd/nfs_acl3xdr_gen.c
create mode 100644 fs/nfsd/nfs_acl3xdr_gen.h
create mode 100644 include/linux/sunrpc/xdrgen/nfs_acl3.h
diff --git a/Documentation/sunrpc/xdr/nfs_acl3.x b/Documentation/sunrpc/xdr/nfs_acl3.x
index 913ecf3a369f..b0e1d2e1fd43 100644
--- a/Documentation/sunrpc/xdr/nfs_acl3.x
+++ b/Documentation/sunrpc/xdr/nfs_acl3.x
@@ -40,6 +40,8 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+pragma header nfs_acl3;
+
const NFS_ACL_MAX_ENTRIES = 1024;
typedef unsigned int uid;
diff --git a/fs/nfsd/Makefile b/fs/nfsd/Makefile
index 7db3b248e15a..96af6d479731 100644
--- a/fs/nfsd/Makefile
+++ b/fs/nfsd/Makefile
@@ -16,7 +16,7 @@ nfsd-y += nfssvc.o nfsctl.o nfsfh.o vfs.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 nfs_acl2xdr_gen.o
-nfsd-$(CONFIG_NFSD_V3_ACL) += nfs3acl.o
+nfsd-$(CONFIG_NFSD_V3_ACL) += nfs3acl.o nfs_acl3xdr_gen.o
nfsd-$(CONFIG_NFSD_V4) += nfs4proc.o nfs4xdr.o nfs4state.o nfs4idmap.o \
nfs4acl.o nfs4callback.o nfs4recover.o nfs4xdr_gen.o
nfsd-$(CONFIG_NFSD_PNFS) += nfs4layouts.o
@@ -40,11 +40,12 @@ nfsd-$(CONFIG_DEBUG_FS) += debugfs.o
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/nfs_acl3.h \
../../include/linux/sunrpc/xdrgen/nfs4_1.h
XDRGEN_DECLARATIONS = nfs2xdr_gen.h nfs_acl2xdr_gen.h \
- nfs3xdr_gen.h nfs4xdr_gen.h
+ nfs3xdr_gen.h nfs_acl3xdr_gen.h nfs4xdr_gen.h
XDRGEN_SOURCE = nfs2xdr_gen.c nfs_acl2xdr_gen.c \
- nfs3xdr_gen.c nfs4xdr_gen.c
+ nfs3xdr_gen.c nfs_acl3xdr_gen.c nfs4xdr_gen.c
xdrgen: $(XDRGEN_DEFINITIONS) $(XDRGEN_DECLARATIONS) $(XDRGEN_SOURCE)
@@ -54,6 +55,8 @@ xdrgen: $(XDRGEN_DEFINITIONS) $(XDRGEN_DECLARATIONS) $(XDRGEN_SOURCE)
../../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/nfs_acl3.h: ../../Documentation/sunrpc/xdr/nfs_acl3.x
+ ../../tools/net/sunrpc/xdrgen/xdrgen definitions $< > $@
../../include/linux/sunrpc/xdrgen/nfs4_1.h: ../../Documentation/sunrpc/xdr/nfs4_1.x
../../tools/net/sunrpc/xdrgen/xdrgen definitions $< > $@
@@ -63,6 +66,8 @@ 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 $< > $@
+nfs_acl3xdr_gen.h: ../../Documentation/sunrpc/xdr/nfs_acl3.x
+ ../../tools/net/sunrpc/xdrgen/xdrgen declarations $< > $@
nfs4xdr_gen.h: ../../Documentation/sunrpc/xdr/nfs4_1.x
../../tools/net/sunrpc/xdrgen/xdrgen declarations $< > $@
@@ -72,5 +77,7 @@ 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 $< > $@
+nfs_acl3xdr_gen.c: ../../Documentation/sunrpc/xdr/nfs_acl3.x
+ ../../tools/net/sunrpc/xdrgen/xdrgen source $< > $@
nfs4xdr_gen.c: ../../Documentation/sunrpc/xdr/nfs4_1.x
../../tools/net/sunrpc/xdrgen/xdrgen source $< > $@
diff --git a/fs/nfsd/nfs_acl3xdr_gen.c b/fs/nfsd/nfs_acl3xdr_gen.c
new file mode 100644
index 000000000000..5bdeb29addfe
--- /dev/null
+++ b/fs/nfsd/nfs_acl3xdr_gen.c
@@ -0,0 +1,749 @@
+// SPDX-License-Identifier: GPL-2.0
+// Generated by xdrgen. Manual edits will be lost.
+// XDR specification file: ../../Documentation/sunrpc/xdr/nfs_acl3.x
+// XDR specification modification time: Fri Jul 10 20:10:15 2026
+
+#include <linux/sunrpc/svc.h>
+
+#include "nfs_acl3xdr_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_uint64(struct xdr_stream *xdr, uint64 *ptr)
+{
+ return xdrgen_decode_unsigned_hyper(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_uint32(struct xdr_stream *xdr, uint32 *ptr)
+{
+ return xdrgen_decode_unsigned_long(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_fileid3(struct xdr_stream *xdr, fileid3 *ptr)
+{
+ return xdrgen_decode_uint64(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_uid3(struct xdr_stream *xdr, uid3 *ptr)
+{
+ return xdrgen_decode_uint32(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_gid3(struct xdr_stream *xdr, gid3 *ptr)
+{
+ return xdrgen_decode_uint32(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_size3(struct xdr_stream *xdr, size3 *ptr)
+{
+ return xdrgen_decode_uint64(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_mode3(struct xdr_stream *xdr, mode3 *ptr)
+{
+ return xdrgen_decode_uint32(xdr, ptr);
+}
+
+static bool __maybe_unused
+xdrgen_decode_ftype3(struct xdr_stream *xdr, ftype3 *ptr)
+{
+ u32 val;
+
+ if (xdr_stream_decode_u32(xdr, &val) < 0)
+ return false;
+ /* Compiler may optimize to a range check for dense enums */
+ switch (val) {
+ case NF3REG:
+ case NF3DIR:
+ case NF3BLK:
+ case NF3CHR:
+ case NF3LNK:
+ case NF3SOCK:
+ case NF3FIFO:
+ break;
+ default:
+ return false;
+ }
+ *ptr = val;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_specdata3(struct xdr_stream *xdr, struct specdata3 *ptr)
+{
+ if (!xdrgen_decode_uint32(xdr, &ptr->specdata1))
+ return false;
+ if (!xdrgen_decode_uint32(xdr, &ptr->specdata2))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nfs_fh3(struct xdr_stream *xdr, struct nfs_fh3 *ptr)
+{
+ if (!xdrgen_decode_opaque(xdr, &ptr->data, NFS3_FHSIZE))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_nfstime3(struct xdr_stream *xdr, struct nfstime3 *ptr)
+{
+ if (!xdrgen_decode_uint32(xdr, &ptr->seconds))
+ return false;
+ if (!xdrgen_decode_uint32(xdr, &ptr->nseconds))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_fattr3(struct xdr_stream *xdr, struct fattr3 *ptr)
+{
+ if (!xdrgen_decode_ftype3(xdr, &ptr->type))
+ return false;
+ if (!xdrgen_decode_mode3(xdr, &ptr->mode))
+ return false;
+ if (!xdrgen_decode_uint32(xdr, &ptr->nlink))
+ return false;
+ if (!xdrgen_decode_uid3(xdr, &ptr->uid))
+ return false;
+ if (!xdrgen_decode_gid3(xdr, &ptr->gid))
+ return false;
+ if (!xdrgen_decode_size3(xdr, &ptr->size))
+ return false;
+ if (!xdrgen_decode_size3(xdr, &ptr->used))
+ return false;
+ if (!xdrgen_decode_specdata3(xdr, &ptr->rdev))
+ return false;
+ if (!xdrgen_decode_uint64(xdr, &ptr->fsid))
+ return false;
+ if (!xdrgen_decode_fileid3(xdr, &ptr->fileid))
+ return false;
+ if (!xdrgen_decode_nfstime3(xdr, &ptr->atime))
+ return false;
+ if (!xdrgen_decode_nfstime3(xdr, &ptr->mtime))
+ return false;
+ if (!xdrgen_decode_nfstime3(xdr, &ptr->ctime))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_post_op_attr(struct xdr_stream *xdr, struct post_op_attr *ptr)
+{
+ if (!xdrgen_decode_bool(xdr, &ptr->attributes_follow))
+ return false;
+ if (ptr->attributes_follow) {
+ if (!xdrgen_decode_fattr3(xdr, &ptr->u.attributes))
+ return false;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_aclstat3(struct xdr_stream *xdr, aclstat3 *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 ACL3_OK:
+ case ACL3ERR_PERM:
+ case ACL3ERR_NOENT:
+ case ACL3ERR_IO:
+ case ACL3ERR_ACCES:
+ case ACL3ERR_INVAL:
+ case ACL3ERR_NOSPC:
+ case ACL3ERR_ROFS:
+ case ACL3ERR_DQUOT:
+ case ACL3ERR_STALE:
+ case ACL3ERR_BADHANDLE:
+ case ACL3ERR_NOTSUPP:
+ case ACL3ERR_SERVERFAULT:
+ case ACL3ERR_JUKEBOX:
+ break;
+ default:
+ return false;
+ }
+ *ptr = val;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_GETACL3args(struct xdr_stream *xdr, struct GETACL3args *ptr)
+{
+ if (!xdrgen_decode_nfs_fh3(xdr, &ptr->fh))
+ return false;
+ if (!xdrgen_decode_unsigned_int(xdr, &ptr->mask))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_GETACL3resok(struct xdr_stream *xdr, struct GETACL3resok *ptr)
+{
+ if (!xdrgen_decode_post_op_attr(xdr, &ptr->attr))
+ return false;
+ if (!xdrgen_decode_secattr(xdr, &ptr->acl))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_GETACL3resfail(struct xdr_stream *xdr, struct GETACL3resfail *ptr)
+{
+ if (!xdrgen_decode_post_op_attr(xdr, &ptr->attr))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_GETACL3res(struct xdr_stream *xdr, struct GETACL3res *ptr)
+{
+ if (!xdrgen_decode_aclstat3(xdr, &ptr->status))
+ return false;
+ switch (ptr->status) {
+ case ACL3_OK:
+ if (!xdrgen_decode_GETACL3resok(xdr, &ptr->u.resok))
+ return false;
+ break;
+ default:
+ if (!xdrgen_decode_GETACL3resfail(xdr, &ptr->u.resfail))
+ return false;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_SETACL3args(struct xdr_stream *xdr, struct SETACL3args *ptr)
+{
+ if (!xdrgen_decode_nfs_fh3(xdr, &ptr->fh))
+ return false;
+ if (!xdrgen_decode_secattr(xdr, &ptr->acl))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_SETACL3resok(struct xdr_stream *xdr, struct SETACL3resok *ptr)
+{
+ if (!xdrgen_decode_post_op_attr(xdr, &ptr->attr))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_SETACL3resfail(struct xdr_stream *xdr, struct SETACL3resfail *ptr)
+{
+ if (!xdrgen_decode_post_op_attr(xdr, &ptr->attr))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_SETACL3res(struct xdr_stream *xdr, struct SETACL3res *ptr)
+{
+ if (!xdrgen_decode_aclstat3(xdr, &ptr->status))
+ return false;
+ switch (ptr->status) {
+ case ACL3_OK:
+ if (!xdrgen_decode_SETACL3resok(xdr, &ptr->u.resok))
+ return false;
+ break;
+ default:
+ if (!xdrgen_decode_SETACL3resfail(xdr, &ptr->u.resfail))
+ return false;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_GETXATTRDIR3args(struct xdr_stream *xdr, struct GETXATTRDIR3args *ptr)
+{
+ if (!xdrgen_decode_nfs_fh3(xdr, &ptr->fh))
+ return false;
+ if (!xdrgen_decode_bool(xdr, &ptr->create))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_GETXATTRDIR3resok(struct xdr_stream *xdr, struct GETXATTRDIR3resok *ptr)
+{
+ if (!xdrgen_decode_nfs_fh3(xdr, &ptr->fh))
+ return false;
+ if (!xdrgen_decode_post_op_attr(xdr, &ptr->attr))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_decode_GETXATTRDIR3res(struct xdr_stream *xdr, struct GETXATTRDIR3res *ptr)
+{
+ if (!xdrgen_decode_aclstat3(xdr, &ptr->status))
+ return false;
+ switch (ptr->status) {
+ case ACL3_OK:
+ if (!xdrgen_decode_GETXATTRDIR3resok(xdr, &ptr->u.resok))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+/**
+ * nfs_acl_svc_decode_GETACL3args - Decode a GETACL3args 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_GETACL3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct GETACL3args *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_GETACL3args(xdr, argp);
+}
+
+/**
+ * nfs_acl_svc_decode_SETACL3args - Decode a SETACL3args 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_SETACL3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct SETACL3args *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_SETACL3args(xdr, argp);
+}
+
+/**
+ * nfs_acl_svc_decode_GETXATTRDIR3args - Decode a GETXATTRDIR3args 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_GETXATTRDIR3args(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct GETXATTRDIR3args *argp = rqstp->rq_argp;
+
+ return xdrgen_decode_GETXATTRDIR3args(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_uint64(struct xdr_stream *xdr, const uint64 value)
+{
+ return xdrgen_encode_unsigned_hyper(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_uint32(struct xdr_stream *xdr, const uint32 value)
+{
+ return xdrgen_encode_unsigned_long(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_fileid3(struct xdr_stream *xdr, const fileid3 value)
+{
+ return xdrgen_encode_uint64(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_uid3(struct xdr_stream *xdr, const uid3 value)
+{
+ return xdrgen_encode_uint32(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_gid3(struct xdr_stream *xdr, const gid3 value)
+{
+ return xdrgen_encode_uint32(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_size3(struct xdr_stream *xdr, const size3 value)
+{
+ return xdrgen_encode_uint64(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_mode3(struct xdr_stream *xdr, const mode3 value)
+{
+ return xdrgen_encode_uint32(xdr, value);
+}
+
+static bool __maybe_unused
+xdrgen_encode_ftype3(struct xdr_stream *xdr, ftype3 value)
+{
+ return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
+}
+
+static bool __maybe_unused
+xdrgen_encode_specdata3(struct xdr_stream *xdr, const struct specdata3 *value)
+{
+ if (!xdrgen_encode_uint32(xdr, value->specdata1))
+ return false;
+ if (!xdrgen_encode_uint32(xdr, value->specdata2))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nfs_fh3(struct xdr_stream *xdr, const struct nfs_fh3 *value)
+{
+ if (value->data.len > NFS3_FHSIZE)
+ return false;
+ if (xdr_stream_encode_opaque(xdr, value->data.data, value->data.len) < 0)
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_nfstime3(struct xdr_stream *xdr, const struct nfstime3 *value)
+{
+ if (!xdrgen_encode_uint32(xdr, value->seconds))
+ return false;
+ if (!xdrgen_encode_uint32(xdr, value->nseconds))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_fattr3(struct xdr_stream *xdr, const struct fattr3 *value)
+{
+ if (!xdrgen_encode_ftype3(xdr, value->type))
+ return false;
+ if (!xdrgen_encode_mode3(xdr, value->mode))
+ return false;
+ if (!xdrgen_encode_uint32(xdr, value->nlink))
+ return false;
+ if (!xdrgen_encode_uid3(xdr, value->uid))
+ return false;
+ if (!xdrgen_encode_gid3(xdr, value->gid))
+ return false;
+ if (!xdrgen_encode_size3(xdr, value->size))
+ return false;
+ if (!xdrgen_encode_size3(xdr, value->used))
+ return false;
+ if (!xdrgen_encode_specdata3(xdr, &value->rdev))
+ return false;
+ if (!xdrgen_encode_uint64(xdr, value->fsid))
+ return false;
+ if (!xdrgen_encode_fileid3(xdr, value->fileid))
+ return false;
+ if (!xdrgen_encode_nfstime3(xdr, &value->atime))
+ return false;
+ if (!xdrgen_encode_nfstime3(xdr, &value->mtime))
+ return false;
+ if (!xdrgen_encode_nfstime3(xdr, &value->ctime))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_post_op_attr(struct xdr_stream *xdr, const struct post_op_attr *ptr)
+{
+ if (!xdrgen_encode_bool(xdr, ptr->attributes_follow))
+ return false;
+ if (ptr->attributes_follow) {
+ if (!xdrgen_encode_fattr3(xdr, &ptr->u.attributes))
+ return false;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_aclstat3(struct xdr_stream *xdr, aclstat3 value)
+{
+ return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
+}
+
+static bool __maybe_unused
+xdrgen_encode_GETACL3args(struct xdr_stream *xdr, const struct GETACL3args *value)
+{
+ if (!xdrgen_encode_nfs_fh3(xdr, &value->fh))
+ return false;
+ if (!xdrgen_encode_unsigned_int(xdr, value->mask))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_GETACL3resok(struct xdr_stream *xdr, const struct GETACL3resok *value)
+{
+ if (!xdrgen_encode_post_op_attr(xdr, &value->attr))
+ return false;
+ if (!xdrgen_encode_secattr(xdr, &value->acl))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_GETACL3resfail(struct xdr_stream *xdr, const struct GETACL3resfail *value)
+{
+ if (!xdrgen_encode_post_op_attr(xdr, &value->attr))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_GETACL3res(struct xdr_stream *xdr, const struct GETACL3res *ptr)
+{
+ if (!xdrgen_encode_aclstat3(xdr, ptr->status))
+ return false;
+ switch (ptr->status) {
+ case ACL3_OK:
+ if (!xdrgen_encode_GETACL3resok(xdr, &ptr->u.resok))
+ return false;
+ break;
+ default:
+ if (!xdrgen_encode_GETACL3resfail(xdr, &ptr->u.resfail))
+ return false;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_SETACL3args(struct xdr_stream *xdr, const struct SETACL3args *value)
+{
+ if (!xdrgen_encode_nfs_fh3(xdr, &value->fh))
+ return false;
+ if (!xdrgen_encode_secattr(xdr, &value->acl))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_SETACL3resok(struct xdr_stream *xdr, const struct SETACL3resok *value)
+{
+ if (!xdrgen_encode_post_op_attr(xdr, &value->attr))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_SETACL3resfail(struct xdr_stream *xdr, const struct SETACL3resfail *value)
+{
+ if (!xdrgen_encode_post_op_attr(xdr, &value->attr))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_SETACL3res(struct xdr_stream *xdr, const struct SETACL3res *ptr)
+{
+ if (!xdrgen_encode_aclstat3(xdr, ptr->status))
+ return false;
+ switch (ptr->status) {
+ case ACL3_OK:
+ if (!xdrgen_encode_SETACL3resok(xdr, &ptr->u.resok))
+ return false;
+ break;
+ default:
+ if (!xdrgen_encode_SETACL3resfail(xdr, &ptr->u.resfail))
+ return false;
+ }
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_GETXATTRDIR3args(struct xdr_stream *xdr, const struct GETXATTRDIR3args *value)
+{
+ if (!xdrgen_encode_nfs_fh3(xdr, &value->fh))
+ return false;
+ if (!xdrgen_encode_bool(xdr, value->create))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_GETXATTRDIR3resok(struct xdr_stream *xdr, const struct GETXATTRDIR3resok *value)
+{
+ if (!xdrgen_encode_nfs_fh3(xdr, &value->fh))
+ return false;
+ if (!xdrgen_encode_post_op_attr(xdr, &value->attr))
+ return false;
+ return true;
+}
+
+static bool __maybe_unused
+xdrgen_encode_GETXATTRDIR3res(struct xdr_stream *xdr, const struct GETXATTRDIR3res *ptr)
+{
+ if (!xdrgen_encode_aclstat3(xdr, ptr->status))
+ return false;
+ switch (ptr->status) {
+ case ACL3_OK:
+ if (!xdrgen_encode_GETXATTRDIR3resok(xdr, &ptr->u.resok))
+ return false;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+/**
+ * nfs_acl_svc_encode_GETACL3res - Encode a GETACL3res 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_GETACL3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct GETACL3res *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_GETACL3res(xdr, resp);
+}
+
+/**
+ * nfs_acl_svc_encode_SETACL3res - Encode a SETACL3res 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_SETACL3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct SETACL3res *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_SETACL3res(xdr, resp);
+}
+
+/**
+ * nfs_acl_svc_encode_GETXATTRDIR3res - Encode a GETXATTRDIR3res 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_GETXATTRDIR3res(struct svc_rqst *rqstp, struct xdr_stream *xdr)
+{
+ struct GETXATTRDIR3res *resp = rqstp->rq_resp;
+
+ return xdrgen_encode_GETXATTRDIR3res(xdr, resp);
+}
diff --git a/fs/nfsd/nfs_acl3xdr_gen.h b/fs/nfsd/nfs_acl3xdr_gen.h
new file mode 100644
index 000000000000..d778004ba26a
--- /dev/null
+++ b/fs/nfsd/nfs_acl3xdr_gen.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Generated by xdrgen. Manual edits will be lost. */
+/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs_acl3.x */
+/* XDR specification modification time: Fri Jul 10 20:10:15 2026 */
+
+#ifndef _LINUX_XDRGEN_NFS_ACL3_DECL_H
+#define _LINUX_XDRGEN_NFS_ACL3_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_acl3.h>
+
+bool nfs_acl_svc_decode_GETACL3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_acl_svc_decode_SETACL3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_acl_svc_decode_GETXATTRDIR3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+
+bool nfs_acl_svc_encode_GETACL3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_acl_svc_encode_SETACL3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+bool nfs_acl_svc_encode_GETXATTRDIR3res(struct svc_rqst *rqstp, struct xdr_stream *xdr);
+
+#endif /* _LINUX_XDRGEN_NFS_ACL3_DECL_H */
diff --git a/include/linux/sunrpc/xdrgen/nfs_acl3.h b/include/linux/sunrpc/xdrgen/nfs_acl3.h
new file mode 100644
index 000000000000..8758450dbca0
--- /dev/null
+++ b/include/linux/sunrpc/xdrgen/nfs_acl3.h
@@ -0,0 +1,284 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Generated by xdrgen. Manual edits will be lost. */
+/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs_acl3.x */
+/* XDR specification modification time: Fri Jul 10 20:10:15 2026 */
+
+#ifndef _LINUX_XDRGEN_NFS_ACL3_DEF_H
+#define _LINUX_XDRGEN_NFS_ACL3_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 };
+
+typedef u64 uint64;
+
+typedef u32 uint32;
+
+typedef uint64 fileid3;
+
+typedef uint32 uid3;
+
+typedef uint32 gid3;
+
+typedef uint64 size3;
+
+typedef uint32 mode3;
+
+enum ftype3 {
+ NF3REG = 1,
+ NF3DIR = 2,
+ NF3BLK = 3,
+ NF3CHR = 4,
+ NF3LNK = 5,
+ NF3SOCK = 6,
+ NF3FIFO = 7,
+};
+
+typedef enum ftype3 ftype3;
+
+struct specdata3 {
+ uint32 specdata1;
+ uint32 specdata2;
+};
+
+enum { NFS3_FHSIZE = 64 };
+
+struct nfs_fh3 {
+ opaque data;
+};
+
+struct nfstime3 {
+ uint32 seconds;
+ uint32 nseconds;
+};
+
+struct fattr3 {
+ ftype3 type;
+ mode3 mode;
+ uint32 nlink;
+ uid3 uid;
+ gid3 gid;
+ size3 size;
+ size3 used;
+ struct specdata3 rdev;
+ uint64 fsid;
+ fileid3 fileid;
+ struct nfstime3 atime;
+ struct nfstime3 mtime;
+ struct nfstime3 ctime;
+};
+
+struct post_op_attr {
+ bool attributes_follow;
+ union {
+ struct fattr3 attributes;
+ } u;
+};
+
+enum aclstat3 {
+ ACL3_OK = 0,
+ ACL3ERR_PERM = 1,
+ ACL3ERR_NOENT = 2,
+ ACL3ERR_IO = 5,
+ ACL3ERR_ACCES = 13,
+ ACL3ERR_INVAL = 22,
+ ACL3ERR_NOSPC = 28,
+ ACL3ERR_ROFS = 30,
+ ACL3ERR_DQUOT = 69,
+ ACL3ERR_STALE = 70,
+ ACL3ERR_BADHANDLE = 10001,
+ ACL3ERR_NOTSUPP = 10004,
+ ACL3ERR_SERVERFAULT = 10006,
+ ACL3ERR_JUKEBOX = 10008,
+};
+
+typedef enum aclstat3 aclstat3;
+
+struct GETACL3args {
+ struct nfs_fh3 fh;
+ u32 mask;
+};
+
+struct GETACL3resok {
+ struct post_op_attr attr;
+ struct secattr acl;
+};
+
+struct GETACL3resfail {
+ struct post_op_attr attr;
+};
+
+struct GETACL3res {
+ aclstat3 status;
+ union {
+ struct GETACL3resok resok;
+ struct GETACL3resfail resfail;
+ } u;
+};
+
+struct SETACL3args {
+ struct nfs_fh3 fh;
+ struct secattr acl;
+};
+
+struct SETACL3resok {
+ struct post_op_attr attr;
+};
+
+struct SETACL3resfail {
+ struct post_op_attr attr;
+};
+
+struct SETACL3res {
+ aclstat3 status;
+ union {
+ struct SETACL3resok resok;
+ struct SETACL3resfail resfail;
+ } u;
+};
+
+struct GETXATTRDIR3args {
+ struct nfs_fh3 fh;
+ bool create;
+};
+
+struct GETXATTRDIR3resok {
+ struct nfs_fh3 fh;
+ struct post_op_attr attr;
+};
+
+struct GETXATTRDIR3res {
+ aclstat3 status;
+ union {
+ struct GETXATTRDIR3resok resok;
+ } u;
+};
+
+enum {
+ ACLPROC3_NULL = 0,
+ ACLPROC3_GETACL = 1,
+ ACLPROC3_SETACL = 2,
+ ACLPROC3_GETXATTRDIR = 3,
+};
+
+#ifndef NFS_ACL_PROGRAM
+#define NFS_ACL_PROGRAM (100227)
+#endif
+
+#define NFS_ACL3_uid_sz \
+ (XDR_unsigned_int)
+#define NFS_ACL3_o_mode_sz \
+ (XDR_unsigned_short)
+#define NFS_ACL3_aclent_sz \
+ (XDR_int + NFS_ACL3_uid_sz + NFS_ACL3_o_mode_sz)
+#define NFS_ACL3_secattr_sz \
+ (XDR_unsigned_int + XDR_int + XDR_unsigned_int + (NFS_ACL_MAX_ENTRIES * (NFS_ACL3_aclent_sz)) + XDR_int + XDR_unsigned_int + (NFS_ACL_MAX_ENTRIES * (NFS_ACL3_aclent_sz)))
+#define NFS_ACL3_uint64_sz \
+ (XDR_unsigned_hyper)
+#define NFS_ACL3_uint32_sz \
+ (XDR_unsigned_long)
+#define NFS_ACL3_fileid3_sz \
+ (NFS_ACL3_uint64_sz)
+#define NFS_ACL3_uid3_sz \
+ (NFS_ACL3_uint32_sz)
+#define NFS_ACL3_gid3_sz \
+ (NFS_ACL3_uint32_sz)
+#define NFS_ACL3_size3_sz \
+ (NFS_ACL3_uint64_sz)
+#define NFS_ACL3_mode3_sz \
+ (NFS_ACL3_uint32_sz)
+#define NFS_ACL3_ftype3_sz (XDR_int)
+#define NFS_ACL3_specdata3_sz \
+ (NFS_ACL3_uint32_sz + NFS_ACL3_uint32_sz)
+#define NFS_ACL3_nfs_fh3_sz \
+ (XDR_unsigned_int + XDR_QUADLEN(NFS3_FHSIZE))
+#define NFS_ACL3_nfstime3_sz \
+ (NFS_ACL3_uint32_sz + NFS_ACL3_uint32_sz)
+#define NFS_ACL3_fattr3_sz \
+ (NFS_ACL3_ftype3_sz + NFS_ACL3_mode3_sz + NFS_ACL3_uint32_sz + NFS_ACL3_uid3_sz + NFS_ACL3_gid3_sz + NFS_ACL3_size3_sz + NFS_ACL3_size3_sz + NFS_ACL3_specdata3_sz + NFS_ACL3_uint64_sz + NFS_ACL3_fileid3_sz + NFS_ACL3_nfstime3_sz + NFS_ACL3_nfstime3_sz + NFS_ACL3_nfstime3_sz)
+#define NFS_ACL3_post_op_attr_sz \
+ (XDR_bool + NFS_ACL3_fattr3_sz)
+#define NFS_ACL3_aclstat3_sz (XDR_int)
+#define NFS_ACL3_GETACL3args_sz \
+ (NFS_ACL3_nfs_fh3_sz + XDR_unsigned_int)
+#define NFS_ACL3_GETACL3resok_sz \
+ (NFS_ACL3_post_op_attr_sz + NFS_ACL3_secattr_sz)
+#define NFS_ACL3_GETACL3resfail_sz \
+ (NFS_ACL3_post_op_attr_sz)
+#define NFS_ACL3_GETACL3res_sz \
+ (NFS_ACL3_aclstat3_sz + NFS_ACL3_GETACL3resok_sz)
+#define NFS_ACL3_SETACL3args_sz \
+ (NFS_ACL3_nfs_fh3_sz + NFS_ACL3_secattr_sz)
+#define NFS_ACL3_SETACL3resok_sz \
+ (NFS_ACL3_post_op_attr_sz)
+#define NFS_ACL3_SETACL3resfail_sz \
+ (NFS_ACL3_post_op_attr_sz)
+#define NFS_ACL3_SETACL3res_sz \
+ (NFS_ACL3_aclstat3_sz + NFS_ACL3_SETACL3resok_sz)
+#define NFS_ACL3_GETXATTRDIR3args_sz \
+ (NFS_ACL3_nfs_fh3_sz + XDR_bool)
+#define NFS_ACL3_GETXATTRDIR3resok_sz \
+ (NFS_ACL3_nfs_fh3_sz + NFS_ACL3_post_op_attr_sz)
+#define NFS_ACL3_GETXATTRDIR3res_sz \
+ (NFS_ACL3_aclstat3_sz + NFS_ACL3_GETXATTRDIR3resok_sz)
+#define NFS_ACL3_MAX_ARGS_SZ \
+ (NFS_ACL3_SETACL3args_sz)
+
+#endif /* _LINUX_XDRGEN_NFS_ACL3_DEF_H */
--
2.56.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v1 03/13] NFSD: Remove '#include "xdr3.h"' from nfs3acl.c
2026-10-25 15:16 [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen Chuck Lever
2026-10-25 15:16 ` [PATCH v1 01/13] Documentation: Add the RPC language description of NFS_ACLv3 Chuck Lever
2026-10-25 15:16 ` [PATCH v1 02/13] NFSD: Add infrastructure for generating NFS_ACLv3 XDR encoders and decoders Chuck Lever
@ 2026-10-25 15:16 ` Chuck Lever
2026-10-25 15:16 ` [PATCH v1 04/13] NFSD: New NFS_ACLv3 status codes Chuck Lever
` (10 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Chuck Lever @ 2026-10-25 15:16 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Begin the process of converting nfs3acl.c to use xdrgen by removing
the xdr3.h header from nfs3acl.c. Eventually nfs3acl.c will use
only NFS_ACLv3 headers.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfs3acl.c | 34 +++++++++++++++++++++++++++++++++-
1 file changed, 33 insertions(+), 1 deletion(-)
diff --git a/fs/nfsd/nfs3acl.c b/fs/nfsd/nfs3acl.c
index 58f1297c88f9..98de13b44a80 100644
--- a/fs/nfsd/nfs3acl.c
+++ b/fs/nfsd/nfs3acl.c
@@ -13,9 +13,41 @@
#include <linux/gfp.h>
#include "cache.h"
-#include "xdr3.h"
#include "vfs.h"
+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_attrstat {
+ __be32 status;
+ struct svc_fh fh;
+ struct kstat stat;
+};
+
+/* Helper functions for NFSv3 ACL code */
+bool svcxdr_decode_nfs_fh3(struct xdr_stream *xdr, struct svc_fh *fhp);
+bool svcxdr_encode_nfsstat3(struct xdr_stream *xdr, __be32 status);
+bool svcxdr_encode_post_op_attr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
+ const struct svc_fh *fhp);
+
static __be32 nfsd3_map_status(__be32 status)
{
switch (status) {
--
2.56.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v1 04/13] NFSD: New NFS_ACLv3 status codes
2026-10-25 15:16 [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen Chuck Lever
` (2 preceding siblings ...)
2026-10-25 15:16 ` [PATCH v1 03/13] NFSD: Remove '#include "xdr3.h"' from nfs3acl.c Chuck Lever
@ 2026-10-25 15:16 ` Chuck Lever
2026-10-25 15:16 ` [PATCH v1 05/13] NFSD: Use xdrgen XDR functions for the NFS-ACL v3 NULL procedure Chuck Lever
` (9 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Chuck Lever @ 2026-10-25 15:16 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
In preparation for converting the NFS_ACLv3 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 ACL3ERR constants.
Note that this conversion still depends on the numeric equivalence
of the NFSv3 and NFS_ACLv3 status codes.
nfsd3_map_errno() keeps the errno choices nfserrno() makes for the
errors that aclstat3 can express, including the filesystem
corruption errnos that map to ACL3ERR_IO and the transient ones
that map to ACL3ERR_JUKEBOX. Every other errno becomes
ACL3ERR_SERVERFAULT.
The NFS_ACL bit mask symbolic constants are replaced as well so that
this TU continues to compile with the header generated from the RPC
spec.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
Documentation/sunrpc/xdr/nfs_acl3.x | 1 +
fs/nfsd/nfs3acl.c | 99 +++++++++++++++++++-------
fs/nfsd/nfs_acl3xdr_gen.c | 22 +++---
fs/nfsd/nfs_acl3xdr_gen.h | 2 +-
include/linux/sunrpc/xdrgen/nfs_acl3.h | 4 +-
5 files changed, 90 insertions(+), 38 deletions(-)
diff --git a/Documentation/sunrpc/xdr/nfs_acl3.x b/Documentation/sunrpc/xdr/nfs_acl3.x
index b0e1d2e1fd43..b355705bf970 100644
--- a/Documentation/sunrpc/xdr/nfs_acl3.x
+++ b/Documentation/sunrpc/xdr/nfs_acl3.x
@@ -189,6 +189,7 @@ enum aclstat3 {
ACL3ERR_SERVERFAULT = 10006,
ACL3ERR_JUKEBOX = 10008
};
+pragma big_endian aclstat3;
/*
* NFS_ACL version 3 procedure arguments and results
diff --git a/fs/nfsd/nfs3acl.c b/fs/nfsd/nfs3acl.c
index 98de13b44a80..536593ba59bc 100644
--- a/fs/nfsd/nfs3acl.c
+++ b/fs/nfsd/nfs3acl.c
@@ -5,16 +5,19 @@
* 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/nfs4.h>
+
+#include "nfs_acl3xdr_gen.h"
#include "cache.h"
+#include "nfsd.h"
#include "vfs.h"
+#define NFS_ACL_MASK (NA_ACL | NA_ACLCNT | NA_DFACL | NA_DFACLCNT)
+
struct nfsd3_getaclargs {
struct svc_fh fh;
__u32 mask;
@@ -48,14 +51,60 @@ bool svcxdr_encode_nfsstat3(struct xdr_stream *xdr, __be32 status);
bool svcxdr_encode_post_op_attr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
const struct svc_fh *fhp);
+enum {
+ acl3_ok = __constant_cpu_to_be32(ACL3_OK),
+ acl3err_perm = __constant_cpu_to_be32(ACL3ERR_PERM),
+ acl3err_noent = __constant_cpu_to_be32(ACL3ERR_NOENT),
+ acl3err_io = __constant_cpu_to_be32(ACL3ERR_IO),
+ acl3err_acces = __constant_cpu_to_be32(ACL3ERR_ACCES),
+ acl3err_inval = __constant_cpu_to_be32(ACL3ERR_INVAL),
+ acl3err_nospc = __constant_cpu_to_be32(ACL3ERR_NOSPC),
+ acl3err_rofs = __constant_cpu_to_be32(ACL3ERR_ROFS),
+ acl3err_dquot = __constant_cpu_to_be32(ACL3ERR_DQUOT),
+ acl3err_stale = __constant_cpu_to_be32(ACL3ERR_STALE),
+ acl3err_badhandle = __constant_cpu_to_be32(ACL3ERR_BADHANDLE),
+ acl3err_notsupp = __constant_cpu_to_be32(ACL3ERR_NOTSUPP),
+ acl3err_serverfault = __constant_cpu_to_be32(ACL3ERR_SERVERFAULT),
+ acl3err_jukebox = __constant_cpu_to_be32(ACL3ERR_JUKEBOX),
+};
+
+/* Ensure errno values do not leak onto the wire. */
+static __be32 nfsd3_map_errno(int errno)
+{
+ switch (errno) {
+ case 0: return acl3_ok;
+ case -EPERM:
+ case -ENOKEY: return acl3err_perm;
+ case -ENOENT: return acl3err_noent;
+ case -EIO:
+ case -EREMOTEIO:
+ case -ETXTBSY:
+ case -EUCLEAN:
+ case -EBADMSG: return acl3err_io;
+ case -EACCES: return acl3err_acces;
+ case -EINVAL: return acl3err_inval;
+ case -ENOSPC: return acl3err_nospc;
+ case -EROFS: return acl3err_rofs;
+ case -EDQUOT: return acl3err_dquot;
+ case -EBADF:
+ case -ESTALE: return acl3err_stale;
+ case -EOPNOTSUPP: return acl3err_notsupp;
+ case -ENOMEM:
+ case -EAGAIN:
+ case -ERESTARTSYS:
+ case -ETIMEDOUT: return acl3err_jukebox;
+ default: return acl3err_serverfault;
+ }
+}
+
static __be32 nfsd3_map_status(__be32 status)
{
switch (status) {
- case nfserr_nofilehandle:
- status = nfserr_badhandle;
+ case cpu_to_be32(NFS4ERR_NOFILEHANDLE):
+ status = acl3err_badhandle;
break;
- case nfserr_wrongsec:
- status = nfserr_acces;
+ case cpu_to_be32(NFS4ERR_WRONGSEC):
+ status = acl3err_acces;
break;
}
return status;
@@ -83,35 +132,35 @@ static __be32 nfsd3_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 != acl3_ok)
goto out;
inode = d_inode(fh->fh_dentry);
if (argp->mask & ~NFS_ACL_MASK) {
- resp->status = nfserr_inval;
+ resp->status = acl3err_inval;
goto out;
}
resp->mask = argp->mask;
- 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 = nfsd3_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 = nfsd3_map_errno(PTR_ERR(acl));
goto fail;
}
resp->acl_default = acl;
@@ -143,7 +192,7 @@ static __be32 nfsd3_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 != acl3_ok)
goto out;
inode = d_inode(fh->fh_dentry);
@@ -155,13 +204,13 @@ static __be32 nfsd3_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);
}
@@ -170,7 +219,7 @@ static __be32 nfsd3_proc_setacl(struct svc_rqst *rqstp)
inode_unlock(inode);
fh_drop_write(fh);
out_errno:
- resp->status = nfserrno(error);
+ resp->status = nfsd3_map_errno(error);
out:
/* argp->acl_{access,default} are released in nfs3svc_release_setacl. */
resp->status = nfsd3_map_status(resp->status);
@@ -205,10 +254,10 @@ nfs3svc_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;
@@ -230,7 +279,7 @@ nfs3svc_encode_getaclres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
if (!svcxdr_encode_nfsstat3(xdr, resp->status))
return false;
switch (resp->status) {
- case nfs_ok:
+ case acl3_ok:
inode = d_inode(dentry);
if (!svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh))
return false;
@@ -238,11 +287,11 @@ nfs3svc_encode_getaclres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
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;
default:
diff --git a/fs/nfsd/nfs_acl3xdr_gen.c b/fs/nfsd/nfs_acl3xdr_gen.c
index 5bdeb29addfe..05e35653a0fc 100644
--- a/fs/nfsd/nfs_acl3xdr_gen.c
+++ b/fs/nfsd/nfs_acl3xdr_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_acl3.x
-// XDR specification modification time: Fri Jul 10 20:10:15 2026
+// XDR specification modification time: Fri Jul 10 20:12:08 2026
#include <linux/sunrpc/svc.h>
@@ -198,10 +198,12 @@ xdrgen_decode_post_op_attr(struct xdr_stream *xdr, struct post_op_attr *ptr)
static bool __maybe_unused
xdrgen_decode_aclstat3(struct xdr_stream *xdr, aclstat3 *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 ACL3_OK:
@@ -222,7 +224,7 @@ xdrgen_decode_aclstat3(struct xdr_stream *xdr, aclstat3 *ptr)
default:
return false;
}
- *ptr = val;
+ *ptr = raw;
return true;
}
@@ -260,7 +262,7 @@ xdrgen_decode_GETACL3res(struct xdr_stream *xdr, struct GETACL3res *ptr)
if (!xdrgen_decode_aclstat3(xdr, &ptr->status))
return false;
switch (ptr->status) {
- case ACL3_OK:
+ case __constant_cpu_to_be32(ACL3_OK):
if (!xdrgen_decode_GETACL3resok(xdr, &ptr->u.resok))
return false;
break;
@@ -303,7 +305,7 @@ xdrgen_decode_SETACL3res(struct xdr_stream *xdr, struct SETACL3res *ptr)
if (!xdrgen_decode_aclstat3(xdr, &ptr->status))
return false;
switch (ptr->status) {
- case ACL3_OK:
+ case __constant_cpu_to_be32(ACL3_OK):
if (!xdrgen_decode_SETACL3resok(xdr, &ptr->u.resok))
return false;
break;
@@ -340,7 +342,7 @@ xdrgen_decode_GETXATTRDIR3res(struct xdr_stream *xdr, struct GETXATTRDIR3res *pt
if (!xdrgen_decode_aclstat3(xdr, &ptr->status))
return false;
switch (ptr->status) {
- case ACL3_OK:
+ case __constant_cpu_to_be32(ACL3_OK):
if (!xdrgen_decode_GETXATTRDIR3resok(xdr, &ptr->u.resok))
return false;
break;
@@ -573,7 +575,7 @@ xdrgen_encode_post_op_attr(struct xdr_stream *xdr, const struct post_op_attr *pt
static bool __maybe_unused
xdrgen_encode_aclstat3(struct xdr_stream *xdr, aclstat3 value)
{
- return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
+ return xdr_stream_encode_be32(xdr, value) == XDR_UNIT;
}
static bool __maybe_unused
@@ -610,7 +612,7 @@ xdrgen_encode_GETACL3res(struct xdr_stream *xdr, const struct GETACL3res *ptr)
if (!xdrgen_encode_aclstat3(xdr, ptr->status))
return false;
switch (ptr->status) {
- case ACL3_OK:
+ case __constant_cpu_to_be32(ACL3_OK):
if (!xdrgen_encode_GETACL3resok(xdr, &ptr->u.resok))
return false;
break;
@@ -653,7 +655,7 @@ xdrgen_encode_SETACL3res(struct xdr_stream *xdr, const struct SETACL3res *ptr)
if (!xdrgen_encode_aclstat3(xdr, ptr->status))
return false;
switch (ptr->status) {
- case ACL3_OK:
+ case __constant_cpu_to_be32(ACL3_OK):
if (!xdrgen_encode_SETACL3resok(xdr, &ptr->u.resok))
return false;
break;
@@ -690,7 +692,7 @@ xdrgen_encode_GETXATTRDIR3res(struct xdr_stream *xdr, const struct GETXATTRDIR3r
if (!xdrgen_encode_aclstat3(xdr, ptr->status))
return false;
switch (ptr->status) {
- case ACL3_OK:
+ case __constant_cpu_to_be32(ACL3_OK):
if (!xdrgen_encode_GETXATTRDIR3resok(xdr, &ptr->u.resok))
return false;
break;
diff --git a/fs/nfsd/nfs_acl3xdr_gen.h b/fs/nfsd/nfs_acl3xdr_gen.h
index d778004ba26a..8d960c0506db 100644
--- a/fs/nfsd/nfs_acl3xdr_gen.h
+++ b/fs/nfsd/nfs_acl3xdr_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_acl3.x */
-/* XDR specification modification time: Fri Jul 10 20:10:15 2026 */
+/* XDR specification modification time: Fri Jul 10 20:12:08 2026 */
#ifndef _LINUX_XDRGEN_NFS_ACL3_DECL_H
#define _LINUX_XDRGEN_NFS_ACL3_DECL_H
diff --git a/include/linux/sunrpc/xdrgen/nfs_acl3.h b/include/linux/sunrpc/xdrgen/nfs_acl3.h
index 8758450dbca0..4d71e4907261 100644
--- a/include/linux/sunrpc/xdrgen/nfs_acl3.h
+++ b/include/linux/sunrpc/xdrgen/nfs_acl3.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_acl3.x */
-/* XDR specification modification time: Fri Jul 10 20:10:15 2026 */
+/* XDR specification modification time: Fri Jul 10 20:12:08 2026 */
#ifndef _LINUX_XDRGEN_NFS_ACL3_DEF_H
#define _LINUX_XDRGEN_NFS_ACL3_DEF_H
@@ -149,7 +149,7 @@ enum aclstat3 {
ACL3ERR_JUKEBOX = 10008,
};
-typedef enum aclstat3 aclstat3;
+typedef __be32 aclstat3;
struct GETACL3args {
struct nfs_fh3 fh;
--
2.56.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v1 05/13] NFSD: Use xdrgen XDR functions for the NFS-ACL v3 NULL procedure
2026-10-25 15:16 [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen Chuck Lever
` (3 preceding siblings ...)
2026-10-25 15:16 ` [PATCH v1 04/13] NFSD: New NFS_ACLv3 status codes Chuck Lever
@ 2026-10-25 15:16 ` Chuck Lever
2026-10-25 15:16 ` [PATCH v1 06/13] NFSD: Implement NFS-ACL v3 ACL encoding and decoding with xdrgen Chuck Lever
` (8 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Chuck Lever @ 2026-10-25 15:16 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 3 protocol description exactly.
Convert the NULL procedure to xdrgen_svc_decode_void and
xdrgen_svc_encode_void. NULL has no arguments or results, so it is the
first procedure converted and needs only the generic void codecs, not
the generated NFS_ACLv3 header. GETACL and SETACL remain hand-rolled
until subsequent patches.
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/nfs3acl.c | 26 ++++++++++++++++----------
1 file changed, 16 insertions(+), 10 deletions(-)
diff --git a/fs/nfsd/nfs3acl.c b/fs/nfsd/nfs3acl.c
index 536593ba59bc..048046af15c0 100644
--- a/fs/nfsd/nfs3acl.c
+++ b/fs/nfsd/nfs3acl.c
@@ -110,11 +110,17 @@ static __be32 nfsd3_map_status(__be32 status)
return status;
}
-/*
- * NULL call.
+/**
+ * nfsd3_proc_null - NULL: Do nothing
+ * @rqstp: RPC transaction context
+ *
+ * Return:
+ * %rpc_success: RPC executed successfully
+ *
+ * RPC synopsis:
+ * void ACLPROC3_NULL(void) = 0;
*/
-static __be32
-nfsd3_proc_null(struct svc_rqst *rqstp)
+static __be32 nfsd3_proc_null(struct svc_rqst *rqstp)
{
return rpc_success;
}
@@ -342,13 +348,13 @@ static void nfs3svc_release_setacl(struct svc_rqst *rqstp)
static const struct svc_procedure nfsd_acl_procedures3[3] = {
[ACLPROC3_NULL] = {
.pc_func = nfsd3_proc_null,
- .pc_decode = nfssvc_decode_voidarg,
- .pc_encode = nfssvc_encode_voidres,
- .pc_argsize = sizeof(struct nfsd_voidargs),
- .pc_argzero = sizeof(struct nfsd_voidargs),
- .pc_ressize = sizeof(struct nfsd_voidres),
+ .pc_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 = ST,
+ .pc_xdrressize = XDR_void,
.pc_name = "NULL",
},
[ACLPROC3_GETACL] = {
--
2.56.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v1 06/13] NFSD: Implement NFS-ACL v3 ACL encoding and decoding with xdrgen
2026-10-25 15:16 [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen Chuck Lever
` (4 preceding siblings ...)
2026-10-25 15:16 ` [PATCH v1 05/13] NFSD: Use xdrgen XDR functions for the NFS-ACL v3 NULL procedure Chuck Lever
@ 2026-10-25 15:16 ` Chuck Lever
2026-10-25 15:16 ` [PATCH v1 07/13] NFSD: Use xdrgen XDR functions for the NFS-ACL v3 GETACL procedure Chuck Lever
` (7 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Chuck Lever @ 2026-10-25 15:16 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
The NFS-ACL v3 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 that Solaris VxFS
interoperability requires, and counts gated by the request mask --
is application logic that xdrgen cannot synthesize. Keeping those two
codecs hand-rolled would force the generated NFSv3 types they call,
nfs_fh3, fattr3, and post_op_attr, to keep external linkage, and
those symbols would collide at link time with the public
definitions that nfs3xdr_gen.c already emits for NFSv3 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, a user or group id with no kernel mapping, a
list whose declared count differs from its length, or more entries
than NFS_ACL_MAX_ENTRIES -- instead marks the argument wrapper and
lets the decode succeed, so nfsd3_proc_setacl() can report
ACL3ERR_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, and it cleared perm bits outside rwx in an NA_CLASS_OBJ entry
without complaint because Solaris clients were seen to set them. The
draft makes both invalid, so the NFS_ACLv3 server now reports
ACL3ERR_INVAL for them as well. These are deliberate, minor changes to
the SETACL wire behavior.
Because the containing codecs are fully generated, the NFSv3 types
the ACL specification re-declares stay static in nfs_acl3xdr_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_acl3.x | 2 +
fs/nfsd/nfs3acl.c | 271 +++++++++++++++++++++++++
fs/nfsd/nfs_acl3xdr_gen.c | 138 ++++++++++---
fs/nfsd/nfs_acl3xdr_gen.h | 14 +-
include/linux/sunrpc/xdrgen/nfs_acl3.h | 2 +-
5 files changed, 400 insertions(+), 27 deletions(-)
diff --git a/Documentation/sunrpc/xdr/nfs_acl3.x b/Documentation/sunrpc/xdr/nfs_acl3.x
index b355705bf970..931a7d629603 100644
--- a/Documentation/sunrpc/xdr/nfs_acl3.x
+++ b/Documentation/sunrpc/xdr/nfs_acl3.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/nfs3acl.c b/fs/nfsd/nfs3acl.c
index 048046af15c0..1fb9d265bc03 100644
--- a/fs/nfsd/nfs3acl.c
+++ b/fs/nfsd/nfs3acl.c
@@ -18,6 +18,52 @@
#define NFS_ACL_MASK (NA_ACL | NA_ACLCNT | NA_DFACL | NA_DFACLCNT)
+/*
+ * 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 GETACL3args_wrapper {
+ struct GETACL3args xdrgen;
+ struct svc_fh fh;
+};
+
+static_assert(offsetof(struct GETACL3args_wrapper, xdrgen) == 0);
+
+/*
+ * The ACL is encoded from the posix_acl fields below, so the
+ * generated secattr (xdrgen.u.resok.acl) is left unused.
+ */
+struct GETACL3res_wrapper {
+ struct GETACL3res xdrgen;
+ int mask;
+ struct posix_acl *acl_access;
+ struct posix_acl *acl_default;
+ struct kstat stat;
+};
+
+static_assert(offsetof(struct GETACL3res_wrapper, xdrgen) == 0);
+
+/*
+ * The ACLs are decoded by hand into the posix_acl fields below, so
+ * the generated aclent arrays (xdrgen.acl) are left unused.
+ */
+struct SETACL3args_wrapper {
+ struct SETACL3args 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
+ * nfsd3_proc_setacl can return ACL3ERR_INVAL rather than RPC
+ * GARBAGE_ARGS. See draft-ietf-nfsv4-nfs-acl Section 3.4.4.2.
+ */
+ bool invalid;
+};
+
+static_assert(offsetof(struct SETACL3args_wrapper, xdrgen) == 0);
+
struct nfsd3_getaclargs {
struct svc_fh fh;
__u32 mask;
@@ -110,6 +156,19 @@ static __be32 nfsd3_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 nfs3acl_acl_entries(const struct posix_acl *acl)
+{
+ return (acl && acl->a_count >= 3) ? max_t(int, acl->a_count, 4) : 0;
+}
+
/**
* nfsd3_proc_null - NULL: Do nothing
* @rqstp: RPC transaction context
@@ -318,6 +377,218 @@ nfs3svc_encode_setaclres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh);
}
+/*
+ * 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_ACL3_SECATTR_ACLENT or NFS_ACL3_SECATTR_DFACLENT.
+ */
+
+/* GETACL reply: stream the wire entries out of the source posix_acl. */
+
+bool nfs_acl3_secattr_encode_begin(struct xdrgen_aggregate_cursor *c)
+{
+ struct svc_rqst *rqstp = c->ctx;
+ struct GETACL3res_wrapper *resp = rqstp->rq_resp;
+ bool dfacl = c->member_id == NFS_ACL3_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 ? nfs3acl_acl_entries(acl) : 0;
+ return true;
+}
+
+bool nfs_acl3_secattr_encode(struct xdrgen_aggregate_cursor *c,
+ struct aclent *out)
+{
+ struct svc_rqst *rqstp = c->ctx;
+ struct GETACL3res_wrapper *resp = rqstp->rq_resp;
+ bool dfacl = c->member_id == NFS_ACL3_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_acl3_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_acl3_secattr_decode_begin(struct xdrgen_aggregate_cursor *c)
+{
+ struct svc_rqst *rqstp = c->ctx;
+ struct SETACL3args_wrapper *argp = rqstp->rq_argp;
+ bool dfacl = c->member_id == NFS_ACL3_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 whose declared count differs from its
+ * length is an invalid ACL, not a malformed request.
+ */
+ if (c->count != declared) {
+ argp->invalid = true;
+ return true;
+ }
+ 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) {
+ argp->invalid = true;
+ return true;
+ }
+ return true;
+}
+
+bool nfs_acl3_secattr_decode(struct xdrgen_aggregate_cursor *c,
+ const struct aclent *in)
+{
+ struct svc_rqst *rqstp = c->ctx;
+ struct SETACL3args_wrapper *argp = rqstp->rq_argp;
+ bool dfacl = c->member_id == NFS_ACL3_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;
+ }
+
+ /* A type bit the protocol does not define makes the ACL invalid. */
+ if (in->type & ~(NA_USER_OBJ | NA_USER | NA_GROUP_OBJ | NA_GROUP |
+ NA_CLASS_OBJ | NA_OTHER_OBJ | NA_ACL_DEFAULT)) {
+ argp->invalid = true;
+ return true;
+ }
+
+ entry = &acl->a_entries[c->index];
+ entry->e_tag = in->type & ~NA_ACL_DEFAULT;
+ entry->e_perm = in->perm;
+ switch (entry->e_tag) {
+ case ACL_USER:
+ /* An id with no kernel mapping makes the ACL invalid. */
+ entry->e_uid = make_kuid(&init_user_ns, in->id);
+ if (!uid_valid(entry->e_uid)) {
+ argp->invalid = true;
+ return true;
+ }
+ break;
+ case ACL_GROUP:
+ entry->e_gid = make_kgid(&init_user_ns, in->id);
+ if (!gid_valid(entry->e_gid)) {
+ argp->invalid = true;
+ return true;
+ }
+ 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;
+ }
+
+ return true;
+}
+
+bool nfs_acl3_secattr_decode_end(struct xdrgen_aggregate_cursor *c, bool ok)
+{
+ struct svc_rqst *rqstp = c->ctx;
+ struct SETACL3args_wrapper *argp = rqstp->rq_argp;
+ bool dfacl = c->member_id == NFS_ACL3_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 release functions
*/
diff --git a/fs/nfsd/nfs_acl3xdr_gen.c b/fs/nfsd/nfs_acl3xdr_gen.c
index 05e35653a0fc..bbd8bb7d79d3 100644
--- a/fs/nfsd/nfs_acl3xdr_gen.c
+++ b/fs/nfsd/nfs_acl3xdr_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_acl3.x
-// XDR specification modification time: Fri Jul 10 20:12:08 2026
+// XDR specification modification time: Fri Jul 10 20:12:44 2026
#include <linux/sunrpc/svc.h>
@@ -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_ACL3_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_acl3_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_acl3_secattr_decode(&cursor, &element)) {
+ ok = false;
+ break;
+ }
+ }
+ if (!nfs_acl3_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_ACL3_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_acl3_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_acl3_secattr_decode(&cursor, &element)) {
+ ok = false;
+ break;
+ }
+ }
+ if (!nfs_acl3_secattr_decode_end(&cursor, ok) || !ok)
+ return false;
+ }
return true;
}
@@ -431,22 +475,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_ACL3_SECATTR_ACLENT,
+ .ctx = xdr->xdrgen_ctx,
+ };
+ bool ok = true;
+
+ if (!nfs_acl3_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_acl3_secattr_encode(&cursor, &element)) {
+ ok = false;
+ break;
+ }
+ if (!xdrgen_encode_aclent(xdr, &element)) {
+ ok = false;
+ break;
+ }
+ }
+ if (!nfs_acl3_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_ACL3_SECATTR_DFACLENT,
+ .ctx = xdr->xdrgen_ctx,
+ };
+ bool ok = true;
+
+ if (!nfs_acl3_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_acl3_secattr_encode(&cursor, &element)) {
+ ok = false;
+ break;
+ }
+ if (!xdrgen_encode_aclent(xdr, &element)) {
+ ok = false;
+ break;
+ }
+ }
+ if (!nfs_acl3_secattr_encode_end(&cursor, ok) || !ok)
+ return false;
+ }
return true;
}
diff --git a/fs/nfsd/nfs_acl3xdr_gen.h b/fs/nfsd/nfs_acl3xdr_gen.h
index 8d960c0506db..629a06566ca7 100644
--- a/fs/nfsd/nfs_acl3xdr_gen.h
+++ b/fs/nfsd/nfs_acl3xdr_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_acl3.x */
-/* XDR specification modification time: Fri Jul 10 20:12:08 2026 */
+/* XDR specification modification time: Fri Jul 10 20:12:44 2026 */
#ifndef _LINUX_XDRGEN_NFS_ACL3_DECL_H
#define _LINUX_XDRGEN_NFS_ACL3_DECL_H
@@ -13,6 +13,18 @@
#include <linux/sunrpc/xdrgen/_builtins.h>
#include <linux/sunrpc/xdrgen/nfs_acl3.h>
+enum {
+ NFS_ACL3_SECATTR_ACLENT,
+ NFS_ACL3_SECATTR_DFACLENT,
+};
+
+bool nfs_acl3_secattr_encode_begin(struct xdrgen_aggregate_cursor *c);
+bool nfs_acl3_secattr_encode(struct xdrgen_aggregate_cursor *c, struct aclent *out);
+bool nfs_acl3_secattr_encode_end(struct xdrgen_aggregate_cursor *c, bool ok);
+bool nfs_acl3_secattr_decode_begin(struct xdrgen_aggregate_cursor *c);
+bool nfs_acl3_secattr_decode(struct xdrgen_aggregate_cursor *c, const struct aclent *in);
+bool nfs_acl3_secattr_decode_end(struct xdrgen_aggregate_cursor *c, bool ok);
+
bool nfs_acl_svc_decode_GETACL3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs_acl_svc_decode_SETACL3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs_acl_svc_decode_GETXATTRDIR3args(struct svc_rqst *rqstp, struct xdr_stream *xdr);
diff --git a/include/linux/sunrpc/xdrgen/nfs_acl3.h b/include/linux/sunrpc/xdrgen/nfs_acl3.h
index 4d71e4907261..92c0d1e0b0a8 100644
--- a/include/linux/sunrpc/xdrgen/nfs_acl3.h
+++ b/include/linux/sunrpc/xdrgen/nfs_acl3.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_acl3.x */
-/* XDR specification modification time: Fri Jul 10 20:12:08 2026 */
+/* XDR specification modification time: Fri Jul 10 20:12:44 2026 */
#ifndef _LINUX_XDRGEN_NFS_ACL3_DEF_H
#define _LINUX_XDRGEN_NFS_ACL3_DEF_H
--
2.56.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v1 07/13] NFSD: Use xdrgen XDR functions for the NFS-ACL v3 GETACL procedure
2026-10-25 15:16 [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen Chuck Lever
` (5 preceding siblings ...)
2026-10-25 15:16 ` [PATCH v1 06/13] NFSD: Implement NFS-ACL v3 ACL encoding and decoding with xdrgen Chuck Lever
@ 2026-10-25 15:16 ` Chuck Lever
2026-10-25 15:16 ` [PATCH v1 08/13] NFSD: Use xdrgen XDR functions for the NFS-ACL v3 SETACL procedure Chuck Lever
` (6 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Chuck Lever @ 2026-10-25 15:16 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Continue migrating nfs3acl.c off its hand-written codecs. GETACL3res
encode carries a secattr, a post_op_attr, and an aclstat3 status, none
of which the hand-rolled path expresses in a form xdrgen can check
against the protocol description.
Convert GETACL to the generated codec. The preceding patch installed
the secattr aggregate hooks and the argument and result wrappers, so
the proc fills only the scalar secattr fields -- the request mask and
the two entry counts -- and the generated encoder streams the ACL
entries from the proc's posix_acls through those hooks. The
GETACL3args and GETACL3res wrappers carry the svc_fh and posix_acl
state the NFSD VFS layer still uses alongside the generated types.
The NFSv3 file handle is variable-length, so the proc rejects an
unparseable handle as ACL3ERR_BADHANDLE before acquiring any
resources. The procedure fills the post_op_attr, records the inode's
owner ids, and fetches the ACLs while the file handle is verified,
then releases the file handle itself. When the file handle carries
fh_no_wcc or the attribute fetch fails, the post_op_attr stays absent
and the ACL is still returned, as the hand-rolled encoder left it.
The encoder reads the ACL owner ids from the recorded owner rather
than from a live inode, so nfs3svc_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 fattr3 masks the mode with S_IALLUGO and reuses the fsid
and timestamp handling the native NFSv3 attribute encoder already
applies.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfs3acl.c | 282 +++++++++++++++++++++++++++-------------------
1 file changed, 166 insertions(+), 116 deletions(-)
diff --git a/fs/nfsd/nfs3acl.c b/fs/nfsd/nfs3acl.c
index 1fb9d265bc03..beb51b9dc2f1 100644
--- a/fs/nfsd/nfs3acl.c
+++ b/fs/nfsd/nfs3acl.c
@@ -7,6 +7,7 @@
#include <linux/gfp.h>
#include <linux/fs.h>
+#include <linux/filelock.h>
#include <linux/nfsacl.h>
#include <linux/nfs4.h>
@@ -14,10 +15,19 @@
#include "cache.h"
#include "nfsd.h"
+#include "auth.h"
#include "vfs.h"
#define NFS_ACL_MASK (NA_ACL | NA_ACLCNT | NA_DFACL | NA_DFACLCNT)
+/*
+ * The generated <linux/sunrpc/xdrgen/nfs_acl3.h> defines the NFSv3
+ * inherited types (nfs_fh3, fattr3, post_op_attr, ...), so the native
+ * NFSv3 headers that also define them cannot be included here. Define
+ * the one inherited NFSv3 constant needed for symlink size clamping.
+ */
+#define NFS3_MAXPATHLEN PATH_MAX
+
/*
* Wrapper structures combine xdrgen types with legacy structures.
* The xdrgen field must be first so the structure can be cast
@@ -30,15 +40,13 @@ struct GETACL3args_wrapper {
static_assert(offsetof(struct GETACL3args_wrapper, xdrgen) == 0);
-/*
- * The ACL is encoded from the posix_acl fields below, so the
- * generated secattr (xdrgen.u.resok.acl) is left unused.
- */
struct GETACL3res_wrapper {
struct GETACL3res xdrgen;
int mask;
struct posix_acl *acl_access;
struct posix_acl *acl_default;
+ kuid_t uid;
+ kgid_t gid;
struct kstat stat;
};
@@ -64,39 +72,6 @@ struct SETACL3args_wrapper {
static_assert(offsetof(struct SETACL3args_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;
- struct posix_acl *acl_access;
- struct posix_acl *acl_default;
-};
-
-struct nfsd3_attrstat {
- __be32 status;
- struct svc_fh fh;
- struct kstat stat;
-};
-
-/* Helper functions for NFSv3 ACL code */
-bool svcxdr_decode_nfs_fh3(struct xdr_stream *xdr, struct svc_fh *fhp);
-bool svcxdr_encode_nfsstat3(struct xdr_stream *xdr, __be32 status);
-bool svcxdr_encode_post_op_attr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
- const struct svc_fh *fhp);
-
enum {
acl3_ok = __constant_cpu_to_be32(ACL3_OK),
acl3err_perm = __constant_cpu_to_be32(ACL3ERR_PERM),
@@ -156,6 +131,103 @@ static __be32 nfsd3_map_status(__be32 status)
return status;
}
+/* Legacy structures for the hand-rolled SETACL codec. */
+struct nfsd3_setaclargs {
+ struct svc_fh fh;
+ __u32 mask;
+ struct posix_acl *acl_access;
+ struct posix_acl *acl_default;
+};
+
+struct nfsd3_attrstat {
+ __be32 status;
+ struct svc_fh fh;
+ struct kstat stat;
+};
+
+/* Helper functions for NFSv3 ACL code */
+bool svcxdr_decode_nfs_fh3(struct xdr_stream *xdr, struct svc_fh *fhp);
+bool svcxdr_encode_nfsstat3(struct xdr_stream *xdr, __be32 status);
+bool svcxdr_encode_post_op_attr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
+ const struct svc_fh *fhp);
+
+static bool
+nfs3acl_decode_fh(const struct nfs_fh3 *fh, struct svc_fh *fhp)
+{
+ if (fh->data.len == 0 || fh->data.len > NFS3_FHSIZE)
+ return false;
+ fh_init(fhp, NFS3_FHSIZE);
+ fhp->fh_handle.fh_size = fh->data.len;
+ memcpy(&fhp->fh_handle.fh_raw, fh->data.data, fh->data.len);
+ return true;
+}
+
+static u32
+nfs3acl_mode_to_ftype3(umode_t mode)
+{
+ switch (mode & S_IFMT) {
+ case S_IFREG: return NF3REG;
+ case S_IFDIR: return NF3DIR;
+ case S_IFBLK: return NF3BLK;
+ case S_IFCHR: return NF3CHR;
+ case S_IFLNK: return NF3LNK;
+ case S_IFSOCK: return NF3SOCK;
+ case S_IFIFO: return NF3FIFO;
+ }
+ return NF3REG;
+}
+
+static void
+nfs3acl_stat_to_fattr3(struct svc_rqst *rqstp, struct fattr3 *fattr,
+ const struct kstat *stat, const struct svc_fh *fhp)
+{
+ struct user_namespace *userns = nfsd_user_namespace(rqstp);
+ u64 fsid;
+
+ fattr->type = nfs3acl_mode_to_ftype3(stat->mode);
+ fattr->mode = stat->mode & S_IALLUGO;
+ fattr->nlink = stat->nlink;
+ fattr->uid = from_kuid_munged(userns, stat->uid);
+ fattr->gid = from_kgid_munged(userns, stat->gid);
+ if (S_ISLNK(stat->mode) && stat->size > NFS3_MAXPATHLEN)
+ fattr->size = NFS3_MAXPATHLEN;
+ else
+ fattr->size = stat->size;
+ fattr->used = (u64)stat->blocks << 9;
+ fattr->rdev.specdata1 = MAJOR(stat->rdev);
+ fattr->rdev.specdata2 = MINOR(stat->rdev);
+
+ switch (fsid_source(fhp)) {
+ case FSIDSOURCE_FSID:
+ fsid = (u64)fhp->fh_export->ex_fsid;
+ break;
+ case FSIDSOURCE_UUID:
+ fsid = ((u64 *)fhp->fh_export->ex_uuid)[0];
+ fsid ^= ((u64 *)fhp->fh_export->ex_uuid)[1];
+ break;
+ default:
+ fsid = (u64)huge_encode_dev(fhp->fh_dentry->d_sb->s_dev);
+ }
+ fattr->fsid = fsid;
+ fattr->fileid = stat->ino;
+
+ fattr->atime.seconds = stat->atime.tv_sec;
+ fattr->atime.nseconds = stat->atime.tv_nsec;
+ fattr->mtime.seconds = stat->mtime.tv_sec;
+ fattr->mtime.nseconds = stat->mtime.tv_nsec;
+ fattr->ctime.seconds = stat->ctime.tv_sec;
+ fattr->ctime.nseconds = stat->ctime.tv_nsec;
+}
+
+static void
+nfs3acl_encode_post_op_attr(struct svc_rqst *rqstp, struct post_op_attr *attr,
+ struct kstat *stat, const struct svc_fh *fhp)
+{
+ attr->attributes_follow = true;
+ lease_get_mtime(d_inode(fhp->fh_dentry), &stat->mtime);
+ nfs3acl_stat_to_fattr3(rqstp, &attr->u.attributes, stat, fhp);
+}
+
/*
* A minimal (three-entry) ACL travels as four wire entries: the
* POSIX entries plus a synthesized NA_CLASS_OBJ mask. The wire
@@ -184,29 +256,48 @@ static __be32 nfsd3_proc_null(struct svc_rqst *rqstp)
return rpc_success;
}
-/*
- * Get the Access and/or Default ACL of a file.
+/**
+ * nfsd3_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:
+ * GETACL3res ACLPROC3_GETACL(GETACL3args) = 1;
*/
static __be32 nfsd3_proc_getacl(struct svc_rqst *rqstp)
{
- struct nfsd3_getaclargs *argp = rqstp->rq_argp;
- struct nfsd3_getaclres *resp = rqstp->rq_resp;
+ struct GETACL3args_wrapper *argp = rqstp->rq_argp;
+ struct GETACL3res_wrapper *resp = rqstp->rq_resp;
+ struct svc_fh *fhp = &argp->fh;
struct posix_acl *acl;
struct inode *inode;
- svc_fh *fh;
- fh = fh_copy(&resp->fh, &argp->fh);
- resp->status = fh_verify(rqstp, &resp->fh, 0, NFSD_MAY_NOP);
- if (resp->status != acl3_ok)
+ resp->xdrgen.u.resok.attr.attributes_follow = false;
+
+ /* An unparsable handle leaves @fhp uninitialized, so skip fh_put. */
+ if (!nfs3acl_decode_fh(&argp->xdrgen.fh, fhp)) {
+ resp->xdrgen.status = acl3err_badhandle;
+ return rpc_success;
+ }
+
+ resp->xdrgen.status = fh_verify(rqstp, fhp, 0, NFSD_MAY_NOP);
+ if (resp->xdrgen.status != acl3_ok)
goto out;
+ inode = d_inode(fhp->fh_dentry);
- inode = d_inode(fh->fh_dentry);
+ if (!fhp->fh_no_wcc && fh_getattr(fhp, &resp->stat) == acl3_ok)
+ nfs3acl_encode_post_op_attr(rqstp, &resp->xdrgen.u.resok.attr,
+ &resp->stat, fhp);
- if (argp->mask & ~NFS_ACL_MASK) {
- resp->status = acl3err_inval;
+ if (argp->xdrgen.mask & ~NFS_ACL_MASK) {
+ resp->xdrgen.status = acl3err_inval;
goto out;
}
- resp->mask = argp->mask;
+ resp->mask = argp->xdrgen.mask;
+ resp->uid = inode->i_uid;
+ resp->gid = inode->i_gid;
if (resp->mask & (NA_ACL | NA_ACLCNT)) {
acl = get_inode_acl(inode, ACL_TYPE_ACCESS);
@@ -215,25 +306,34 @@ static __be32 nfsd3_proc_getacl(struct svc_rqst *rqstp)
acl = posix_acl_from_mode(inode->i_mode, GFP_KERNEL);
}
if (IS_ERR(acl)) {
- resp->status = nfsd3_map_errno(PTR_ERR(acl));
+ resp->xdrgen.status = nfsd3_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 = nfsd3_map_errno(PTR_ERR(acl));
+ resp->xdrgen.status = nfsd3_map_errno(PTR_ERR(acl));
goto fail;
}
resp->acl_default = acl;
}
- /* resp->acl_{access,default} are released in nfs3svc_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 = nfs3acl_acl_entries(resp->acl_access);
+ resp->xdrgen.u.resok.acl.dfaclcnt = nfs3acl_acl_entries(resp->acl_default);
+
out:
- resp->status = nfsd3_map_status(resp->status);
+ /* resp->acl_{access,default} are released in nfs3svc_release_getacl. */
+ fh_put(fhp);
+ resp->xdrgen.status = nfsd3_map_status(resp->xdrgen.status);
return rpc_success;
fail:
@@ -295,19 +395,6 @@ static __be32 nfsd3_proc_setacl(struct svc_rqst *rqstp)
* XDR decode functions
*/
-static bool
-nfs3svc_decode_getaclargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd3_getaclargs *args = rqstp->rq_argp;
-
- if (!svcxdr_decode_nfs_fh3(xdr, &args->fh))
- return false;
- if (xdr_stream_decode_u32(xdr, &args->mask) < 0)
- return false;
-
- return true;
-}
-
static bool
nfs3svc_decode_setaclargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
@@ -333,40 +420,6 @@ nfs3svc_decode_setaclargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
* XDR encode functions
*/
-/* GETACL */
-static bool
-nfs3svc_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_nfsstat3(xdr, resp->status))
- return false;
- switch (resp->status) {
- case acl3_ok:
- inode = d_inode(dentry);
- if (!svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh))
- 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;
- default:
- if (!svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh))
- return false;
- }
-
- return true;
-}
-
/* SETACL */
static bool
nfs3svc_encode_setaclres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
@@ -433,10 +486,10 @@ bool nfs_acl3_secattr_encode(struct xdrgen_aggregate_cursor *c,
out->type = tag | typeflag;
switch (tag) {
case ACL_USER_OBJ:
- out->id = from_kuid(&init_user_ns, resp->stat.uid);
+ out->id = from_kuid(&init_user_ns, resp->uid);
break;
case ACL_GROUP_OBJ:
- out->id = from_kgid(&init_user_ns, resp->stat.gid);
+ out->id = from_kgid(&init_user_ns, resp->gid);
break;
case ACL_USER:
out->id = from_kuid(&init_user_ns, entry->e_uid);
@@ -594,9 +647,8 @@ bool nfs_acl3_secattr_decode_end(struct xdrgen_aggregate_cursor *c, bool ok)
*/
static void nfs3svc_release_getacl(struct svc_rqst *rqstp)
{
- struct nfsd3_getaclres *resp = rqstp->rq_resp;
+ struct GETACL3res_wrapper *resp = rqstp->rq_resp;
- fh_put(&resp->fh);
posix_acl_release(resp->acl_access);
posix_acl_release(resp->acl_default);
}
@@ -614,7 +666,6 @@ static void nfs3svc_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_procedures3[3] = {
[ACLPROC3_NULL] = {
@@ -630,14 +681,14 @@ static const struct svc_procedure nfsd_acl_procedures3[3] = {
},
[ACLPROC3_GETACL] = {
.pc_func = nfsd3_proc_getacl,
- .pc_decode = nfs3svc_decode_getaclargs,
- .pc_encode = nfs3svc_encode_getaclres,
+ .pc_decode = nfs_acl_svc_decode_GETACL3args,
+ .pc_encode = nfs_acl_svc_encode_GETACL3res,
.pc_release = nfs3svc_release_getacl,
- .pc_argsize = sizeof(struct nfsd3_getaclargs),
- .pc_argzero = sizeof(struct nfsd3_getaclargs),
- .pc_ressize = sizeof(struct nfsd3_getaclres),
+ .pc_argsize = sizeof(struct GETACL3args_wrapper),
+ .pc_argzero = 0,
+ .pc_ressize = sizeof(struct GETACL3res_wrapper),
.pc_cachetype = RC_NOCACHE,
- .pc_xdrressize = ST+1+2*(1+ACL),
+ .pc_xdrressize = NFS_ACL3_GETACL3res_sz,
.pc_name = "GETACL",
},
[ACLPROC3_SETACL] = {
@@ -658,9 +709,9 @@ static const struct svc_procedure nfsd_acl_procedures3[3] = {
* Storage requirements for XDR arguments and results.
*/
union nfsd_acl3_xdrstore {
- struct nfsd3_getaclargs getaclargs;
+ struct GETACL3args_wrapper getaclargs;
struct nfsd3_setaclargs setaclargs;
- struct nfsd3_getaclres getaclres;
+ struct GETACL3res_wrapper getaclres;
struct nfsd3_attrstat attrstat;
};
@@ -671,4 +722,3 @@ const struct svc_version nfsd_acl_version3 = {
.vs_dispatch = nfsd_dispatch,
.vs_xdrsize = sizeof(union nfsd_acl3_xdrstore),
};
-
--
2.56.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v1 08/13] NFSD: Use xdrgen XDR functions for the NFS-ACL v3 SETACL procedure
2026-10-25 15:16 [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen Chuck Lever
` (6 preceding siblings ...)
2026-10-25 15:16 ` [PATCH v1 07/13] NFSD: Use xdrgen XDR functions for the NFS-ACL v3 GETACL procedure Chuck Lever
@ 2026-10-25 15:16 ` Chuck Lever
2026-10-25 15:16 ` [PATCH v1 09/13] NFSD: Map every NFS_ACLv3 reply status into aclstat3 Chuck Lever
` (5 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Chuck Lever @ 2026-10-25 15:16 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
SETACL3args decode and SETACL3res encode are the last hand-rolled
NFS_ACLv3 codecs. The previous patch already defined the secattr decode
hooks that the generated SETACL3args decoder drives, so the conversion
is now a matter of wiring the procedure to the generated entry points.
Convert SETACL to the generated codec. The decode hooks publish each
posix_acl into the argument wrapper as it is allocated; a zero-length
default list publishes a NULL posix_acl for ACL removal. When the
decoder has marked the argument ACL invalid (see the codec patch), the
procedure reports ACL3ERR_INVAL once the handle is verified and does
nothing else, so a stale handle or a refused export still takes
precedence over a bad ACL. A default ACL presented for an object that
is not a directory is reported as ACL3ERR_INVAL at the same point,
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. SETACL3args
therefore zeroes its whole argument wrapper through pc_argzero so both
ACL pointers and the "invalid" flag start clear and pc_release reaps a
partially decoded request with no leak and no double free.
Both arms of SETACL3res open with a post_op_attr, so the proc fills the
attributes for the reply whether or not the operation succeeded, as
the hand-rolled encoder did. A failed attribute fetch leaves the
post_op_attr absent; it does not turn a SETACL that has already been
applied into a failure. With SETACL generated, the hand-rolled setacl
codecs, the legacy argument and result structures, and the svcxdr
helper declarations they relied on are all removed.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfs3acl.c | 174 ++++++++++++++++++++--------------------------
1 file changed, 76 insertions(+), 98 deletions(-)
diff --git a/fs/nfsd/nfs3acl.c b/fs/nfsd/nfs3acl.c
index beb51b9dc2f1..19f5a6127673 100644
--- a/fs/nfsd/nfs3acl.c
+++ b/fs/nfsd/nfs3acl.c
@@ -52,10 +52,6 @@ struct GETACL3res_wrapper {
static_assert(offsetof(struct GETACL3res_wrapper, xdrgen) == 0);
-/*
- * The ACLs are decoded by hand into the posix_acl fields below, so
- * the generated aclent arrays (xdrgen.acl) are left unused.
- */
struct SETACL3args_wrapper {
struct SETACL3args xdrgen;
struct svc_fh fh;
@@ -72,6 +68,13 @@ struct SETACL3args_wrapper {
static_assert(offsetof(struct SETACL3args_wrapper, xdrgen) == 0);
+struct SETACL3res_wrapper {
+ struct SETACL3res xdrgen;
+ struct kstat stat;
+};
+
+static_assert(offsetof(struct SETACL3res_wrapper, xdrgen) == 0);
+
enum {
acl3_ok = __constant_cpu_to_be32(ACL3_OK),
acl3err_perm = __constant_cpu_to_be32(ACL3ERR_PERM),
@@ -131,26 +134,6 @@ static __be32 nfsd3_map_status(__be32 status)
return status;
}
-/* Legacy structures for the hand-rolled SETACL codec. */
-struct nfsd3_setaclargs {
- struct svc_fh fh;
- __u32 mask;
- struct posix_acl *acl_access;
- struct posix_acl *acl_default;
-};
-
-struct nfsd3_attrstat {
- __be32 status;
- struct svc_fh fh;
- struct kstat stat;
-};
-
-/* Helper functions for NFSv3 ACL code */
-bool svcxdr_decode_nfs_fh3(struct xdr_stream *xdr, struct svc_fh *fhp);
-bool svcxdr_encode_nfsstat3(struct xdr_stream *xdr, __be32 status);
-bool svcxdr_encode_post_op_attr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
- const struct svc_fh *fhp);
-
static bool
nfs3acl_decode_fh(const struct nfs_fh3 *fh, struct svc_fh *fhp)
{
@@ -344,90 +327,91 @@ static __be32 nfsd3_proc_getacl(struct svc_rqst *rqstp)
goto out;
}
-/*
- * Set the Access and/or Default ACL of a file.
+/**
+ * nfsd3_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:
+ * SETACL3res ACLPROC3_SETACL(SETACL3args) = 2;
*/
static __be32 nfsd3_proc_setacl(struct svc_rqst *rqstp)
{
- struct nfsd3_setaclargs *argp = rqstp->rq_argp;
- struct nfsd3_attrstat *resp = rqstp->rq_resp;
+ struct SETACL3args_wrapper *argp = rqstp->rq_argp;
+ struct SETACL3res_wrapper *resp = rqstp->rq_resp;
+ struct svc_fh *fhp = &argp->fh;
struct inode *inode;
- svc_fh *fh;
int error;
- fh = fh_copy(&resp->fh, &argp->fh);
- resp->status = fh_verify(rqstp, &resp->fh, 0, NFSD_MAY_SATTR);
- if (resp->status != acl3_ok)
+ resp->xdrgen.u.resok.attr.attributes_follow = false;
+
+ if (!nfs3acl_decode_fh(&argp->xdrgen.fh, fhp)) {
+ resp->xdrgen.status = acl3err_badhandle;
+ return rpc_success;
+ }
+
+ resp->xdrgen.status = fh_verify(rqstp, fhp, 0, NFSD_MAY_SATTR);
+ if (resp->xdrgen.status != acl3_ok)
goto out;
+ inode = d_inode(fhp->fh_dentry);
- inode = d_inode(fh->fh_dentry);
+ if (argp->invalid) {
+ resp->xdrgen.status = acl3err_inval;
+ goto out;
+ }
- 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 = acl3err_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;
}
+ inode_unlock(inode);
+
+ fh_drop_write(fhp);
+
+out:
+ /*
+ * resok and resfail both open with a post_op_attr, so filling
+ * the resok arm serves either status.
+ */
+ if (!fhp->fh_no_wcc && fhp->fh_dentry &&
+ fh_getattr(fhp, &resp->stat) == acl3_ok)
+ nfs3acl_encode_post_op_attr(rqstp, &resp->xdrgen.u.resok.attr,
+ &resp->stat, fhp);
+ /* argp->acl_{access,default} are released in nfs3svc_release_setacl. */
+ fh_put(fhp);
+ resp->xdrgen.status = nfsd3_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 = nfsd3_map_errno(error);
-out:
- /* argp->acl_{access,default} are released in nfs3svc_release_setacl. */
- resp->status = nfsd3_map_status(resp->status);
- return rpc_success;
-}
-
-/*
- * XDR decode functions
- */
-
-static bool
-nfs3svc_decode_setaclargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd3_setaclargs *argp = rqstp->rq_argp;
-
- if (!svcxdr_decode_nfs_fh3(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;
-}
-
-/*
- * XDR encode functions
- */
-
-/* SETACL */
-static bool
-nfs3svc_encode_setaclres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
-{
- struct nfsd3_attrstat *resp = rqstp->rq_resp;
-
- return svcxdr_encode_nfsstat3(xdr, resp->status) &&
- svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh);
+ resp->xdrgen.status = nfsd3_map_errno(error);
+ goto out;
}
/*
@@ -655,18 +639,12 @@ static void nfs3svc_release_getacl(struct svc_rqst *rqstp)
static void nfs3svc_release_setacl(struct svc_rqst *rqstp)
{
- struct nfsd3_setaclargs *argp = rqstp->rq_argp;
- struct nfsd3_attrstat *resp = rqstp->rq_resp;
+ struct SETACL3args_wrapper *argp = rqstp->rq_argp;
- fh_put(&resp->fh);
posix_acl_release(argp->acl_access);
posix_acl_release(argp->acl_default);
}
-#define ST 1 /* status*/
-#define AT 21 /* attributes */
-#define pAT (1+AT) /* post attributes - conditional */
-
static const struct svc_procedure nfsd_acl_procedures3[3] = {
[ACLPROC3_NULL] = {
.pc_func = nfsd3_proc_null,
@@ -693,14 +671,14 @@ static const struct svc_procedure nfsd_acl_procedures3[3] = {
},
[ACLPROC3_SETACL] = {
.pc_func = nfsd3_proc_setacl,
- .pc_decode = nfs3svc_decode_setaclargs,
- .pc_encode = nfs3svc_encode_setaclres,
+ .pc_decode = nfs_acl_svc_decode_SETACL3args,
+ .pc_encode = nfs_acl_svc_encode_SETACL3res,
.pc_release = nfs3svc_release_setacl,
- .pc_argsize = sizeof(struct nfsd3_setaclargs),
- .pc_argzero = sizeof(struct nfsd3_setaclargs),
- .pc_ressize = sizeof(struct nfsd3_attrstat),
+ .pc_argsize = sizeof(struct SETACL3args_wrapper),
+ .pc_argzero = sizeof(struct SETACL3args_wrapper),
+ .pc_ressize = sizeof(struct SETACL3res_wrapper),
.pc_cachetype = RC_NOCACHE,
- .pc_xdrressize = ST+pAT,
+ .pc_xdrressize = NFS_ACL3_SETACL3res_sz,
.pc_name = "SETACL",
},
};
@@ -710,9 +688,9 @@ static const struct svc_procedure nfsd_acl_procedures3[3] = {
*/
union nfsd_acl3_xdrstore {
struct GETACL3args_wrapper getaclargs;
- struct nfsd3_setaclargs setaclargs;
+ struct SETACL3args_wrapper setaclargs;
struct GETACL3res_wrapper getaclres;
- struct nfsd3_attrstat attrstat;
+ struct SETACL3res_wrapper setaclres;
};
const struct svc_version nfsd_acl_version3 = {
--
2.56.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v1 09/13] NFSD: Map every NFS_ACLv3 reply status into aclstat3
2026-10-25 15:16 [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen Chuck Lever
` (7 preceding siblings ...)
2026-10-25 15:16 ` [PATCH v1 08/13] NFSD: Use xdrgen XDR functions for the NFS-ACL v3 SETACL procedure Chuck Lever
@ 2026-10-25 15:16 ` Chuck Lever
2026-10-25 15:16 ` [PATCH v1 10/13] NFSD: Remove the hand-coded XDR helpers for the NFS_ACLv3 procedures Chuck Lever
` (4 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Chuck Lever @ 2026-10-25 15:16 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
nfsd3_map_status() translates only the two fh_verify() statuses that
have no NFSv3 equivalent, so every other status reaches the encoder
unchanged. fh_verify() and fh_getattr() pass errno values through
nfserrno(), whose table produces NFSv3 status values that aclstat3
does not define. draft-ietf-nfsv4-nfs-acl Section 6.2 lists the
status values an NFS_ACLv3 server returns, and those are not among
them.
Pass through the statuses that are members of aclstat3 and map the
rest to ACL3ERR_SERVERFAULT, the status nfsd3_map_errno() already
uses for an unlisted errno. The existing NOFILEHANDLE and WRONGSEC
translations are unchanged.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfs3acl.c | 24 +++++++++++++++++++-----
1 file changed, 19 insertions(+), 5 deletions(-)
diff --git a/fs/nfsd/nfs3acl.c b/fs/nfsd/nfs3acl.c
index 19f5a6127673..66bb650e99f4 100644
--- a/fs/nfsd/nfs3acl.c
+++ b/fs/nfsd/nfs3acl.c
@@ -124,14 +124,28 @@ static __be32 nfsd3_map_errno(int errno)
static __be32 nfsd3_map_status(__be32 status)
{
switch (status) {
+ case acl3_ok:
+ case acl3err_perm:
+ case acl3err_noent:
+ case acl3err_io:
+ case acl3err_acces:
+ case acl3err_inval:
+ case acl3err_nospc:
+ case acl3err_rofs:
+ case acl3err_dquot:
+ case acl3err_stale:
+ case acl3err_badhandle:
+ case acl3err_notsupp:
+ case acl3err_serverfault:
+ case acl3err_jukebox:
+ return status;
case cpu_to_be32(NFS4ERR_NOFILEHANDLE):
- status = acl3err_badhandle;
- break;
+ return acl3err_badhandle;
case cpu_to_be32(NFS4ERR_WRONGSEC):
- status = acl3err_acces;
- break;
+ return acl3err_acces;
+ default:
+ return acl3err_serverfault;
}
- return status;
}
static bool
--
2.56.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v1 10/13] NFSD: Remove the hand-coded XDR helpers for the NFS_ACLv3 procedures
2026-10-25 15:16 [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen Chuck Lever
` (8 preceding siblings ...)
2026-10-25 15:16 ` [PATCH v1 09/13] NFSD: Map every NFS_ACLv3 reply status into aclstat3 Chuck Lever
@ 2026-10-25 15:16 ` Chuck Lever
2026-10-25 15:16 ` [PATCH v1 11/13] NFS: Move NFSACL_MAXPAGES into fs/nfs/nfs3_fs.h Chuck Lever
` (3 subsequent siblings)
13 siblings, 0 replies; 16+ messages in thread
From: Chuck Lever @ 2026-10-25 15:16 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
The NFS_ACLv3 GETACL and SETACL procedures were the last callers of
svcxdr_decode_nfs_fh3(), svcxdr_encode_nfsstat3(), and
svcxdr_encode_post_op_attr(). With their arguments and replies now
handled by xdrgen, the three helpers have no caller, and
svcxdr_encode_fattr3(), nfs3_ftypes[], and encode_nfstime3() have no
caller other than svcxdr_encode_post_op_attr().
Remove the hand-coded helpers and their declarations in xdr3.h.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/nfs3xdr.c | 166 ----------------------------------------------
fs/nfsd/xdr3.h | 5 --
2 files changed, 171 deletions(-)
diff --git a/fs/nfsd/nfs3xdr.c b/fs/nfsd/nfs3xdr.c
index 850647ac98bb..8e231e0eb0f8 100644
--- a/fs/nfsd/nfs3xdr.c
+++ b/fs/nfsd/nfs3xdr.c
@@ -20,172 +20,6 @@
#include "nfs3xdr_gen.h"
-/*
- * Mapping of S_IF* types to NFS file types
- */
-static const u32 nfs3_ftypes[] = {
- NF3NON, NF3FIFO, NF3CHR, NF3BAD,
- NF3DIR, NF3BAD, NF3BLK, NF3BAD,
- NF3REG, NF3BAD, NF3LNK, NF3BAD,
- NF3SOCK, NF3BAD, NF3LNK, NF3BAD,
-};
-
-
-/*
- * Basic NFSv3 data types (RFC 1813 Sections 2.5 and 2.6)
- */
-
-static __be32 *
-encode_nfstime3(__be32 *p, const struct timespec64 *time)
-{
- *p++ = cpu_to_be32((u32)time->tv_sec);
- *p++ = cpu_to_be32(time->tv_nsec);
-
- return p;
-}
-
-/**
- * svcxdr_decode_nfs_fh3 - Decode an NFSv3 file handle
- * @xdr: XDR stream positioned at an undecoded NFSv3 FH
- * @fhp: OUT: filled-in server file handle
- *
- * Return values:
- * %false: The encoded file handle was not valid
- * %true: @fhp has been initialized
- */
-bool
-svcxdr_decode_nfs_fh3(struct xdr_stream *xdr, struct svc_fh *fhp)
-{
- __be32 *p;
- u32 size;
-
- if (xdr_stream_decode_u32(xdr, &size) < 0)
- return false;
- if (size == 0 || size > NFS3_FHSIZE)
- return false;
- p = xdr_inline_decode(xdr, size);
- if (!p)
- return false;
- fh_init(fhp, NFS3_FHSIZE);
- fhp->fh_handle.fh_size = size;
- memcpy(&fhp->fh_handle.fh_raw, p, size);
-
- return true;
-}
-
-/**
- * svcxdr_encode_nfsstat3 - Encode an NFSv3 status code
- * @xdr: XDR stream
- * @status: status value to encode
- *
- * Return values:
- * %false: Send buffer space was exhausted
- * %true: Success
- */
-bool
-svcxdr_encode_nfsstat3(struct xdr_stream *xdr, __be32 status)
-{
- __be32 *p;
-
- p = xdr_reserve_space(xdr, sizeof(status));
- if (!p)
- return false;
- *p = status;
-
- return true;
-}
-
-static bool
-svcxdr_encode_fattr3(struct svc_rqst *rqstp, struct xdr_stream *xdr,
- const struct svc_fh *fhp, const struct kstat *stat)
-{
- struct user_namespace *userns = nfsd_user_namespace(rqstp);
- __be32 *p;
- u64 fsid;
-
- p = xdr_reserve_space(xdr, XDR_UNIT * 21);
- if (!p)
- return false;
-
- *p++ = cpu_to_be32(nfs3_ftypes[(stat->mode & S_IFMT) >> 12]);
- *p++ = cpu_to_be32((u32)(stat->mode & S_IALLUGO));
- *p++ = cpu_to_be32((u32)stat->nlink);
- *p++ = cpu_to_be32((u32)from_kuid_munged(userns, stat->uid));
- *p++ = cpu_to_be32((u32)from_kgid_munged(userns, stat->gid));
- if (S_ISLNK(stat->mode) && stat->size > NFS3_MAXPATHLEN)
- p = xdr_encode_hyper(p, (u64)NFS3_MAXPATHLEN);
- else
- p = xdr_encode_hyper(p, (u64)stat->size);
-
- /* used */
- p = xdr_encode_hyper(p, ((u64)stat->blocks) << 9);
-
- /* rdev */
- *p++ = cpu_to_be32((u32)MAJOR(stat->rdev));
- *p++ = cpu_to_be32((u32)MINOR(stat->rdev));
-
- switch(fsid_source(fhp)) {
- case FSIDSOURCE_FSID:
- fsid = (u64)fhp->fh_export->ex_fsid;
- break;
- case FSIDSOURCE_UUID:
- fsid = ((u64 *)fhp->fh_export->ex_uuid)[0];
- fsid ^= ((u64 *)fhp->fh_export->ex_uuid)[1];
- break;
- default:
- fsid = (u64)huge_encode_dev(fhp->fh_dentry->d_sb->s_dev);
- }
- p = xdr_encode_hyper(p, fsid);
-
- /* fileid */
- p = xdr_encode_hyper(p, stat->ino);
-
- p = encode_nfstime3(p, &stat->atime);
- p = encode_nfstime3(p, &stat->mtime);
- encode_nfstime3(p, &stat->ctime);
-
- return true;
-}
-
-/**
- * svcxdr_encode_post_op_attr - Encode NFSv3 post-op attributes
- * @rqstp: Context of a completed RPC transaction
- * @xdr: XDR stream
- * @fhp: File handle to encode
- *
- * Return values:
- * %false: Send buffer space was exhausted
- * %true: Success
- */
-bool
-svcxdr_encode_post_op_attr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
- const struct svc_fh *fhp)
-{
- struct dentry *dentry = fhp->fh_dentry;
- struct kstat stat;
-
- /*
- * The inode may be NULL if the call failed because of a
- * stale file handle. In this case, no attributes are
- * returned.
- */
- if (fhp->fh_no_wcc || !dentry || !d_really_is_positive(dentry))
- goto no_post_op_attrs;
- if (fh_getattr(fhp, &stat) != nfs_ok)
- goto no_post_op_attrs;
-
- if (xdr_stream_encode_item_present(xdr) < 0)
- return false;
- lease_get_mtime(d_inode(dentry), &stat.mtime);
- if (!svcxdr_encode_fattr3(rqstp, xdr, fhp, &stat))
- return false;
-
- return true;
-
-no_post_op_attrs:
- return xdr_stream_encode_item_absent(xdr) > 0;
-}
-
/*
* XDR encode functions
*/
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index a6f0e0b7bfe4..90dadcacdf2b 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -151,10 +151,5 @@ void nfsd3_fill_post_op_attr(struct svc_rqst *rqstp, struct post_op_attr *attr,
struct svc_fh *fhp);
void nfsd3_fill_post_op_fh3(struct post_op_fh3 *post_op_fh,
const struct svc_fh *fhp, u8 *data);
-/* Helper functions for NFSv3 ACL code */
-bool svcxdr_decode_nfs_fh3(struct xdr_stream *xdr, struct svc_fh *fhp);
-bool svcxdr_encode_nfsstat3(struct xdr_stream *xdr, __be32 status);
-bool svcxdr_encode_post_op_attr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
- const struct svc_fh *fhp);
#endif /* _LINUX_NFSD_XDR3_H */
--
2.56.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v1 11/13] NFS: Move NFSACL_MAXPAGES into fs/nfs/nfs3_fs.h
2026-10-25 15:16 [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen Chuck Lever
` (9 preceding siblings ...)
2026-10-25 15:16 ` [PATCH v1 10/13] NFSD: Remove the hand-coded XDR helpers for the NFS_ACLv3 procedures Chuck Lever
@ 2026-10-25 15:16 ` Chuck Lever
2026-10-26 22:50 ` Anna Schumaker
2026-10-25 15:16 ` [PATCH v1 12/13] NFSD: Share the attribute conversion helpers with the NFS_ACL procs Chuck Lever
` (2 subsequent siblings)
13 siblings, 1 reply; 16+ messages in thread
From: Chuck Lever @ 2026-10-25 15:16 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
Only the NFSv3 client's ACL code reads NFSACL_MAXPAGES, yet the macro
lives in <linux/nfsacl.h>, where it is the last reference to
NFS_ACL_MAX_ENTRIES. <linux/nfsacl.h> no longer includes
<uapi/linux/nfsacl.h>, so a file that includes only the kernel header
cannot expand NFSACL_MAXPAGES. Move the macro next to its users and
include nfs3_fs.h from nfs3xdr.c.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfs/nfs3_fs.h | 3 +++
fs/nfs/nfs3xdr.c | 1 +
include/linux/nfsacl.h | 3 ---
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/fs/nfs/nfs3_fs.h b/fs/nfs/nfs3_fs.h
index ffcabadb3546..09663adae033 100644
--- a/fs/nfs/nfs3_fs.h
+++ b/fs/nfs/nfs3_fs.h
@@ -11,6 +11,9 @@
* nfs3acl.c
*/
#ifdef CONFIG_NFS_V3_ACL
+#define NFSACL_MAXPAGES ((2*(8+12*NFS_ACL_MAX_ENTRIES) + PAGE_SIZE-1) \
+ >> PAGE_SHIFT)
+
extern struct posix_acl *nfs3_get_acl(struct inode *inode, int type, bool rcu);
extern int nfs3_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry,
struct posix_acl *acl, int type);
diff --git a/fs/nfs/nfs3xdr.c b/fs/nfs/nfs3xdr.c
index 3251c1434e71..19ab2fe84b8c 100644
--- a/fs/nfs/nfs3xdr.c
+++ b/fs/nfs/nfs3xdr.c
@@ -25,6 +25,7 @@
#include <linux/nfs_common.h>
#include "internal.h"
+#include "nfs3_fs.h"
#include "nfstrace.h"
#define NFSDBG_FACILITY NFSDBG_XDR
diff --git a/include/linux/nfsacl.h b/include/linux/nfsacl.h
index 403f5f5144e3..5160f30151e8 100644
--- a/include/linux/nfsacl.h
+++ b/include/linux/nfsacl.h
@@ -10,9 +10,6 @@
#include <linux/posix_acl.h>
#include <linux/sunrpc/xdr.h>
-#define NFSACL_MAXPAGES ((2*(8+12*NFS_ACL_MAX_ENTRIES) + PAGE_SIZE-1) \
- >> PAGE_SHIFT)
-
#define NFS_ACL_MAX_ENTRIES_INLINE (5)
#define NFS_ACL_INLINE_BUFSIZE ((2*(2+3*NFS_ACL_MAX_ENTRIES_INLINE)) << 2)
--
2.56.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v1 12/13] NFSD: Share the attribute conversion helpers with the NFS_ACL procs
2026-10-25 15:16 [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen Chuck Lever
` (10 preceding siblings ...)
2026-10-25 15:16 ` [PATCH v1 11/13] NFS: Move NFSACL_MAXPAGES into fs/nfs/nfs3_fs.h Chuck Lever
@ 2026-10-25 15:16 ` Chuck Lever
2026-10-25 15:16 ` [PATCH v1 13/13] nfs_common: Remove the unused NFS ACL stream codecs Chuck Lever
2026-10-27 10:56 ` [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen Jeff Layton
13 siblings, 0 replies; 16+ messages in thread
From: Chuck Lever @ 2026-10-25 15:16 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
nfs2acl.c and nfs3acl.c each carry a private copy of the kstat to
fattr conversion that nfsproc.c and nfs3proc.c already implement,
down to the fsid selection and the symlink size clamp, along with a
private definition of the path length limit the clamp uses. The
copies exist because the NFS_ACL protocol descriptions define fattr,
fattr3, and post_op_attr themselves, so the NFS_ACL proc files cannot
include the native NFSv2 and NFSv3 generated headers, and xdr.h and
xdr3.h pull those in. A fix to a native helper has to be repeated by
hand in its NFS_ACL copy.
Add fattr.h, which declares nfsd_stat_to_fattr() and
nfsd3_fill_post_op_attr() against incomplete struct types. The
NFS_ACL copies of the attribute types match the native ones member
for member, so each translation unit completes the types from its
own protocol header and the helpers link across them. Export
nfsd_stat_to_fattr() from nfsproc.c, move the nfsd3_fill_post_op_attr()
prototype out of xdr3.h, and drop the NFS_ACL copies. With
nfsd3_fill_post_op_attr() fetching the attributes itself, the
NFS_ACLv3 result wrappers no longer carry a kstat.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfsd/fattr.h | 20 ++++++++++
fs/nfsd/nfs2acl.c | 99 ++++-------------------------------------------
fs/nfsd/nfs3acl.c | 87 ++---------------------------------------
fs/nfsd/nfsproc.c | 3 +-
fs/nfsd/xdr3.h | 3 +-
5 files changed, 34 insertions(+), 178 deletions(-)
create mode 100644 fs/nfsd/fattr.h
diff --git a/fs/nfsd/fattr.h b/fs/nfsd/fattr.h
new file mode 100644
index 000000000000..7ea116c6a63c
--- /dev/null
+++ b/fs/nfsd/fattr.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Attribute conversion helpers shared by the NFS and NFS_ACL procs.
+ */
+
+#ifndef _NFSD_FATTR_H
+#define _NFSD_FATTR_H
+
+struct fattr;
+struct kstat;
+struct post_op_attr;
+struct svc_fh;
+struct svc_rqst;
+
+void nfsd_stat_to_fattr(struct svc_rqst *rqstp, struct fattr *fattr,
+ const struct kstat *stat, const struct svc_fh *fhp);
+void nfsd3_fill_post_op_attr(struct svc_rqst *rqstp, struct post_op_attr *attr,
+ struct svc_fh *fhp);
+
+#endif /* _NFSD_FATTR_H */
diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c
index 504dc57e9b8e..efb18b65217d 100644
--- a/fs/nfsd/nfs2acl.c
+++ b/fs/nfsd/nfs2acl.c
@@ -7,7 +7,6 @@
#include <linux/gfp.h>
#include <linux/fs.h>
-#include <linux/filelock.h>
#include <linux/nfsacl.h>
#include <linux/nfs3.h>
#include <linux/nfs4.h>
@@ -20,19 +19,12 @@
#include "nfsd.h"
#include "auth.h"
#include "vfs.h"
+#include "fattr.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;
/*
@@ -232,81 +224,6 @@ nfsacld_fhandle_to_svc_fh(struct svc_fh *fhp, const fhandle *fhandle)
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
@@ -358,8 +275,8 @@ static __be32 nfsacld_proc_getacl(struct svc_rqst *rqstp)
if (resp->xdrgen.status != acl2_ok)
goto out;
- nfsacld_stat_to_fattr(rqstp, &resp->xdrgen.u.resok.attr, &resp->stat,
- fhp);
+ nfsd_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);
@@ -476,8 +393,8 @@ static __be32 nfsacld_proc_setacl(struct svc_rqst *rqstp)
out:
if (resp->xdrgen.status == acl2_ok)
- nfsacld_stat_to_fattr(rqstp, &resp->xdrgen.u.resok.attr,
- &resp->stat, fhp);
+ nfsd_stat_to_fattr(rqstp, &resp->xdrgen.u.resok.attr,
+ &resp->stat, fhp);
/* argp->acl_{access,default} are released in nfsaclsvc_release_setacl. */
fh_put(fhp);
resp->xdrgen.status = nfsacld_map_status(resp->xdrgen.status);
@@ -517,8 +434,8 @@ static __be32 nfsacld_proc_getattr(struct svc_rqst *rqstp)
out:
if (resp->xdrgen.status == acl2_ok)
- nfsacld_stat_to_fattr(rqstp, &resp->xdrgen.u.resok.attr,
- &resp->stat, fhp);
+ nfsd_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;
@@ -555,7 +472,7 @@ static __be32 nfsacld_proc_access(struct svc_rqst *rqstp)
struct ACCESS2resok *resok = &resp->xdrgen.u.resok;
resok->access = argp->xdrgen.access;
- nfsacld_stat_to_fattr(rqstp, &resok->attr, &resp->stat, fhp);
+ nfsd_stat_to_fattr(rqstp, &resok->attr, &resp->stat, fhp);
}
fh_put(fhp);
resp->xdrgen.status = nfsacld_map_status(resp->xdrgen.status);
diff --git a/fs/nfsd/nfs3acl.c b/fs/nfsd/nfs3acl.c
index 66bb650e99f4..2ba499422869 100644
--- a/fs/nfsd/nfs3acl.c
+++ b/fs/nfsd/nfs3acl.c
@@ -7,7 +7,6 @@
#include <linux/gfp.h>
#include <linux/fs.h>
-#include <linux/filelock.h>
#include <linux/nfsacl.h>
#include <linux/nfs4.h>
@@ -17,17 +16,10 @@
#include "nfsd.h"
#include "auth.h"
#include "vfs.h"
+#include "fattr.h"
#define NFS_ACL_MASK (NA_ACL | NA_ACLCNT | NA_DFACL | NA_DFACLCNT)
-/*
- * The generated <linux/sunrpc/xdrgen/nfs_acl3.h> defines the NFSv3
- * inherited types (nfs_fh3, fattr3, post_op_attr, ...), so the native
- * NFSv3 headers that also define them cannot be included here. Define
- * the one inherited NFSv3 constant needed for symlink size clamping.
- */
-#define NFS3_MAXPATHLEN PATH_MAX
-
/*
* Wrapper structures combine xdrgen types with legacy structures.
* The xdrgen field must be first so the structure can be cast
@@ -47,7 +39,6 @@ struct GETACL3res_wrapper {
struct posix_acl *acl_default;
kuid_t uid;
kgid_t gid;
- struct kstat stat;
};
static_assert(offsetof(struct GETACL3res_wrapper, xdrgen) == 0);
@@ -70,7 +61,6 @@ static_assert(offsetof(struct SETACL3args_wrapper, xdrgen) == 0);
struct SETACL3res_wrapper {
struct SETACL3res xdrgen;
- struct kstat stat;
};
static_assert(offsetof(struct SETACL3res_wrapper, xdrgen) == 0);
@@ -159,72 +149,6 @@ nfs3acl_decode_fh(const struct nfs_fh3 *fh, struct svc_fh *fhp)
return true;
}
-static u32
-nfs3acl_mode_to_ftype3(umode_t mode)
-{
- switch (mode & S_IFMT) {
- case S_IFREG: return NF3REG;
- case S_IFDIR: return NF3DIR;
- case S_IFBLK: return NF3BLK;
- case S_IFCHR: return NF3CHR;
- case S_IFLNK: return NF3LNK;
- case S_IFSOCK: return NF3SOCK;
- case S_IFIFO: return NF3FIFO;
- }
- return NF3REG;
-}
-
-static void
-nfs3acl_stat_to_fattr3(struct svc_rqst *rqstp, struct fattr3 *fattr,
- const struct kstat *stat, const struct svc_fh *fhp)
-{
- struct user_namespace *userns = nfsd_user_namespace(rqstp);
- u64 fsid;
-
- fattr->type = nfs3acl_mode_to_ftype3(stat->mode);
- fattr->mode = stat->mode & S_IALLUGO;
- fattr->nlink = stat->nlink;
- fattr->uid = from_kuid_munged(userns, stat->uid);
- fattr->gid = from_kgid_munged(userns, stat->gid);
- if (S_ISLNK(stat->mode) && stat->size > NFS3_MAXPATHLEN)
- fattr->size = NFS3_MAXPATHLEN;
- else
- fattr->size = stat->size;
- fattr->used = (u64)stat->blocks << 9;
- fattr->rdev.specdata1 = MAJOR(stat->rdev);
- fattr->rdev.specdata2 = MINOR(stat->rdev);
-
- switch (fsid_source(fhp)) {
- case FSIDSOURCE_FSID:
- fsid = (u64)fhp->fh_export->ex_fsid;
- break;
- case FSIDSOURCE_UUID:
- fsid = ((u64 *)fhp->fh_export->ex_uuid)[0];
- fsid ^= ((u64 *)fhp->fh_export->ex_uuid)[1];
- break;
- default:
- fsid = (u64)huge_encode_dev(fhp->fh_dentry->d_sb->s_dev);
- }
- fattr->fsid = fsid;
- fattr->fileid = stat->ino;
-
- fattr->atime.seconds = stat->atime.tv_sec;
- fattr->atime.nseconds = stat->atime.tv_nsec;
- fattr->mtime.seconds = stat->mtime.tv_sec;
- fattr->mtime.nseconds = stat->mtime.tv_nsec;
- fattr->ctime.seconds = stat->ctime.tv_sec;
- fattr->ctime.nseconds = stat->ctime.tv_nsec;
-}
-
-static void
-nfs3acl_encode_post_op_attr(struct svc_rqst *rqstp, struct post_op_attr *attr,
- struct kstat *stat, const struct svc_fh *fhp)
-{
- attr->attributes_follow = true;
- lease_get_mtime(d_inode(fhp->fh_dentry), &stat->mtime);
- nfs3acl_stat_to_fattr3(rqstp, &attr->u.attributes, stat, fhp);
-}
-
/*
* A minimal (three-entry) ACL travels as four wire entries: the
* POSIX entries plus a synthesized NA_CLASS_OBJ mask. The wire
@@ -284,9 +208,7 @@ static __be32 nfsd3_proc_getacl(struct svc_rqst *rqstp)
goto out;
inode = d_inode(fhp->fh_dentry);
- if (!fhp->fh_no_wcc && fh_getattr(fhp, &resp->stat) == acl3_ok)
- nfs3acl_encode_post_op_attr(rqstp, &resp->xdrgen.u.resok.attr,
- &resp->stat, fhp);
+ nfsd3_fill_post_op_attr(rqstp, &resp->xdrgen.u.resok.attr, fhp);
if (argp->xdrgen.mask & ~NFS_ACL_MASK) {
resp->xdrgen.status = acl3err_inval;
@@ -411,10 +333,7 @@ static __be32 nfsd3_proc_setacl(struct svc_rqst *rqstp)
* resok and resfail both open with a post_op_attr, so filling
* the resok arm serves either status.
*/
- if (!fhp->fh_no_wcc && fhp->fh_dentry &&
- fh_getattr(fhp, &resp->stat) == acl3_ok)
- nfs3acl_encode_post_op_attr(rqstp, &resp->xdrgen.u.resok.attr,
- &resp->stat, fhp);
+ nfsd3_fill_post_op_attr(rqstp, &resp->xdrgen.u.resok.attr, fhp);
/* argp->acl_{access,default} are released in nfs3svc_release_setacl. */
fh_put(fhp);
resp->xdrgen.status = nfsd3_map_status(resp->xdrgen.status);
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index 39e5caa8e286..2f8da62ad6b0 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -15,6 +15,7 @@
#include "cache.h"
#include "xdr.h"
#include "vfs.h"
+#include "fattr.h"
#include "nfserr.h"
#include "trace.h"
@@ -189,7 +190,7 @@ nfsd_mode_to_ftype(umode_t mode)
return NFNON;
}
-static void
+void
nfsd_stat_to_fattr(struct svc_rqst *rqstp, struct fattr *fattr,
const struct kstat *stat, const struct svc_fh *fhp)
{
diff --git a/fs/nfsd/xdr3.h b/fs/nfsd/xdr3.h
index 90dadcacdf2b..ae8afec767ef 100644
--- a/fs/nfsd/xdr3.h
+++ b/fs/nfsd/xdr3.h
@@ -12,6 +12,7 @@
#include <linux/sunrpc/xdrgen/nfs3.h>
#include "vfs.h"
+#include "fattr.h"
#define NF3NON (0)
#define NF3BAD (8)
@@ -147,8 +148,6 @@ void nfs3svc_release_readdirplusres(struct svc_rqst *rqstp);
void nfs3svc_encode_cookie3(struct xdr_stream *xdr, unsigned int pos,
u64 cookie);
-void nfsd3_fill_post_op_attr(struct svc_rqst *rqstp, struct post_op_attr *attr,
- struct svc_fh *fhp);
void nfsd3_fill_post_op_fh3(struct post_op_fh3 *post_op_fh,
const struct svc_fh *fhp, u8 *data);
--
2.56.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v1 13/13] nfs_common: Remove the unused NFS ACL stream codecs
2026-10-25 15:16 [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen Chuck Lever
` (11 preceding siblings ...)
2026-10-25 15:16 ` [PATCH v1 12/13] NFSD: Share the attribute conversion helpers with the NFS_ACL procs Chuck Lever
@ 2026-10-25 15:16 ` Chuck Lever
2026-10-27 10:56 ` [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen Jeff Layton
13 siblings, 0 replies; 16+ messages in thread
From: Chuck Lever @ 2026-10-25 15:16 UTC (permalink / raw)
To: NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
nfs_stream_encode_acl() and nfs_stream_decode_acl() served the
hand-rolled NFSD codecs for the NFS_ACLv2 and NFS_ACLv3 GETACL and
SETACL procedures. Those procedures now encode and decode ACL entries
through generated codecs, and the NFS client has always used the
xdr_buf variants, so neither function has a caller.
Remove both functions, their exports, and their declarations.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfs_common/nfsacl.c | 125 -----------------------------------------
include/linux/nfsacl.h | 6 --
2 files changed, 131 deletions(-)
diff --git a/fs/nfs_common/nfsacl.c b/fs/nfs_common/nfsacl.c
index a66294fcfb6e..cb43fa203a2c 100644
--- a/fs/nfs_common/nfsacl.c
+++ b/fs/nfs_common/nfsacl.c
@@ -141,79 +141,6 @@ int nfsacl_encode(struct xdr_buf *buf, unsigned int base, struct inode *inode,
}
EXPORT_SYMBOL_GPL(nfsacl_encode);
-/**
- * nfs_stream_encode_acl - Encode an NFSv3 ACL
- *
- * @xdr: an xdr_stream positioned to receive an encoded ACL
- * @inode: inode of file whose ACL this is
- * @acl: posix_acl to encode
- * @encode_entries: whether to encode ACEs as well
- * @typeflag: ACL type: NFS_ACL_DEFAULT or zero
- *
- * Return values:
- * %false: The ACL could not be encoded
- * %true: @xdr is advanced to the next available position
- */
-bool nfs_stream_encode_acl(struct xdr_stream *xdr, struct inode *inode,
- struct posix_acl *acl, int encode_entries,
- int typeflag)
-{
- const size_t elem_size = XDR_UNIT * 3;
- u32 entries = (acl && acl->a_count >= 3) ?
- max_t(int, acl->a_count, 4) : 0;
- struct nfsacl_encode_desc nfsacl_desc = {
- .desc = {
- .elem_size = elem_size,
- .array_len = encode_entries ? entries : 0,
- .xcode = xdr_nfsace_encode,
- },
- .acl = acl,
- .typeflag = typeflag,
- .uid = inode->i_uid,
- .gid = inode->i_gid,
- };
- struct nfsacl_simple_acl aclbuf;
- unsigned int base;
- int err;
-
- if (entries > NFS_ACL_MAX_ENTRIES)
- return false;
- if (xdr_stream_encode_u32(xdr, entries) < 0)
- return false;
-
- if (encode_entries && acl && acl->a_count == 3) {
- struct posix_acl *acl2 =
- container_of(&aclbuf.acl, struct posix_acl, hdr);
-
- /* Avoid the use of posix_acl_alloc(). nfsacl_encode() is
- * invoked in contexts where a memory allocation failure is
- * fatal. Fortunately this fake ACL is small enough to
- * construct on the stack. */
- posix_acl_init(acl2, 4);
-
- /* Insert entries in canonical order: other orders seem
- to confuse Solaris VxFS. */
- acl2->a_entries[0] = acl->a_entries[0]; /* ACL_USER_OBJ */
- acl2->a_entries[1] = acl->a_entries[1]; /* ACL_GROUP_OBJ */
- acl2->a_entries[2] = acl->a_entries[1]; /* ACL_MASK */
- acl2->a_entries[2].e_tag = ACL_MASK;
- acl2->a_entries[3] = acl->a_entries[2]; /* ACL_OTHER */
- nfsacl_desc.acl = acl2;
- }
-
- base = xdr_stream_pos(xdr);
- if (!xdr_reserve_space(xdr, XDR_UNIT +
- elem_size * nfsacl_desc.desc.array_len))
- return false;
- err = xdr_encode_array2(xdr->buf, base, &nfsacl_desc.desc);
- if (err)
- return false;
-
- return true;
-}
-EXPORT_SYMBOL_GPL(nfs_stream_encode_acl);
-
-
struct nfsacl_decode_desc {
struct xdr_array2_desc desc;
unsigned int count;
@@ -380,55 +307,3 @@ int nfsacl_decode(struct xdr_buf *buf, unsigned int base, unsigned int *aclcnt,
nfsacl_desc.desc.array_len;
}
EXPORT_SYMBOL_GPL(nfsacl_decode);
-
-/**
- * nfs_stream_decode_acl - Decode an NFSv3 ACL
- *
- * @xdr: an xdr_stream positioned at an encoded ACL
- * @aclcnt: OUT: count of ACEs in decoded posix_acl
- * @pacl: OUT: a dynamically-allocated buffer containing the decoded posix_acl
- *
- * Return values:
- * %false: The encoded ACL is not valid
- * %true: @pacl contains a decoded ACL, and @xdr is advanced
- *
- * On a successful return, caller must release *pacl using posix_acl_release().
- */
-bool nfs_stream_decode_acl(struct xdr_stream *xdr, unsigned int *aclcnt,
- struct posix_acl **pacl)
-{
- const size_t elem_size = XDR_UNIT * 3;
- struct nfsacl_decode_desc nfsacl_desc = {
- .desc = {
- .elem_size = elem_size,
- .xcode = pacl ? xdr_nfsace_decode : NULL,
- },
- };
- unsigned int base;
- u32 entries;
-
- if (xdr_stream_decode_u32(xdr, &entries) < 0)
- return false;
- if (entries > NFS_ACL_MAX_ENTRIES)
- return false;
-
- base = xdr_stream_pos(xdr);
- if (!xdr_inline_decode(xdr, XDR_UNIT + elem_size * entries))
- return false;
- nfsacl_desc.desc.array_maxlen = entries;
- if (xdr_decode_array2(xdr->buf, base, &nfsacl_desc.desc))
- return false;
-
- if (pacl) {
- if (entries != nfsacl_desc.desc.array_len ||
- posix_acl_from_nfsacl(nfsacl_desc.acl) != 0) {
- posix_acl_release(nfsacl_desc.acl);
- return false;
- }
- *pacl = nfsacl_desc.acl;
- }
- if (aclcnt)
- *aclcnt = entries;
- return true;
-}
-EXPORT_SYMBOL_GPL(nfs_stream_decode_acl);
diff --git a/include/linux/nfsacl.h b/include/linux/nfsacl.h
index 5160f30151e8..4ad81b2b2623 100644
--- a/include/linux/nfsacl.h
+++ b/include/linux/nfsacl.h
@@ -30,12 +30,6 @@ nfsacl_encode(struct xdr_buf *buf, unsigned int base, struct inode *inode,
extern int
nfsacl_decode(struct xdr_buf *buf, unsigned int base, unsigned int *aclcnt,
struct posix_acl **pacl);
-extern bool
-nfs_stream_decode_acl(struct xdr_stream *xdr, unsigned int *aclcnt,
- struct posix_acl **pacl);
-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);
--
2.56.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH v1 11/13] NFS: Move NFSACL_MAXPAGES into fs/nfs/nfs3_fs.h
2026-10-25 15:16 ` [PATCH v1 11/13] NFS: Move NFSACL_MAXPAGES into fs/nfs/nfs3_fs.h Chuck Lever
@ 2026-10-26 22:50 ` Anna Schumaker
0 siblings, 0 replies; 16+ messages in thread
From: Anna Schumaker @ 2026-10-26 22:50 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Jeff Layton, Olga Kornievskaia, Dai Ngo,
Tom Talpey
Cc: linux-nfs
Hi Chuck,
On Sun, Oct 25, 2026, at 8:16 AM, Chuck Lever wrote:
> Only the NFSv3 client's ACL code reads NFSACL_MAXPAGES, yet the macro
> lives in <linux/nfsacl.h>, where it is the last reference to
> NFS_ACL_MAX_ENTRIES. <linux/nfsacl.h> no longer includes
> <uapi/linux/nfsacl.h>, so a file that includes only the kernel header
> cannot expand NFSACL_MAXPAGES. Move the macro next to its users and
> include nfs3_fs.h from nfs3xdr.c.
I assume this should go through the NFSD tree with the rest of the
patch series? If so:
Acked-by: Anna Schumaker <anna.schumaker@hammerspace.com>
>
> Signed-off-by: Chuck Lever <cel@kernel.org>
> ---
> fs/nfs/nfs3_fs.h | 3 +++
> fs/nfs/nfs3xdr.c | 1 +
> include/linux/nfsacl.h | 3 ---
> 3 files changed, 4 insertions(+), 3 deletions(-)
>
> diff --git a/fs/nfs/nfs3_fs.h b/fs/nfs/nfs3_fs.h
> index ffcabadb3546..09663adae033 100644
> --- a/fs/nfs/nfs3_fs.h
> +++ b/fs/nfs/nfs3_fs.h
> @@ -11,6 +11,9 @@
> * nfs3acl.c
> */
> #ifdef CONFIG_NFS_V3_ACL
> +#define NFSACL_MAXPAGES ((2*(8+12*NFS_ACL_MAX_ENTRIES) + PAGE_SIZE-1) \
> + >> PAGE_SHIFT)
> +
> extern struct posix_acl *nfs3_get_acl(struct inode *inode, int type, bool rcu);
> extern int nfs3_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry,
> struct posix_acl *acl, int type);
> diff --git a/fs/nfs/nfs3xdr.c b/fs/nfs/nfs3xdr.c
> index 3251c1434e71..19ab2fe84b8c 100644
> --- a/fs/nfs/nfs3xdr.c
> +++ b/fs/nfs/nfs3xdr.c
> @@ -25,6 +25,7 @@
> #include <linux/nfs_common.h>
>
> #include "internal.h"
> +#include "nfs3_fs.h"
> #include "nfstrace.h"
>
> #define NFSDBG_FACILITY NFSDBG_XDR
> diff --git a/include/linux/nfsacl.h b/include/linux/nfsacl.h
> index 403f5f5144e3..5160f30151e8 100644
> --- a/include/linux/nfsacl.h
> +++ b/include/linux/nfsacl.h
> @@ -10,9 +10,6 @@
> #include <linux/posix_acl.h>
> #include <linux/sunrpc/xdr.h>
>
> -#define NFSACL_MAXPAGES ((2*(8+12*NFS_ACL_MAX_ENTRIES) + PAGE_SIZE-1) \
> - >> PAGE_SHIFT)
> -
> #define NFS_ACL_MAX_ENTRIES_INLINE (5)
> #define NFS_ACL_INLINE_BUFSIZE ((2*(2+3*NFS_ACL_MAX_ENTRIES_INLINE)) << 2)
>
> --
> 2.56.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen
2026-10-25 15:16 [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen Chuck Lever
` (12 preceding siblings ...)
2026-10-25 15:16 ` [PATCH v1 13/13] nfs_common: Remove the unused NFS ACL stream codecs Chuck Lever
@ 2026-10-27 10:56 ` Jeff Layton
13 siblings, 0 replies; 16+ messages in thread
From: Jeff Layton @ 2026-10-27 10:56 UTC (permalink / raw)
To: Chuck Lever, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey; +Cc: linux-nfs
On Sun, 2026-10-25 at 11:16 -0400, Chuck Lever wrote:
> Replace NFSD's NFS_ACLv3 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_acl3.x is copied from
> draft-ietf-nfsv4-nfs-acl.
>
> Chuck Lever (13):
> Documentation: Add the RPC language description of NFS_ACLv3
> NFSD: Add infrastructure for generating NFS_ACLv3 XDR encoders and
> decoders
> NFSD: Remove '#include "xdr3.h"' from nfs3acl.c
> NFSD: New NFS_ACLv3 status codes
> NFSD: Use xdrgen XDR functions for the NFS-ACL v3 NULL procedure
> NFSD: Implement NFS-ACL v3 ACL encoding and decoding with xdrgen
> NFSD: Use xdrgen XDR functions for the NFS-ACL v3 GETACL procedure
> NFSD: Use xdrgen XDR functions for the NFS-ACL v3 SETACL procedure
> NFSD: Map every NFS_ACLv3 reply status into aclstat3
> NFSD: Remove the hand-coded XDR helpers for the NFS_ACLv3 procedures
> NFS: Move NFSACL_MAXPAGES into fs/nfs/nfs3_fs.h
> NFSD: Share the attribute conversion helpers with the NFS_ACL procs
> nfs_common: Remove the unused NFS ACL stream codecs
>
> Documentation/sunrpc/xdr/nfs_acl3.x | 273 ++++++++
> fs/nfs/nfs3_fs.h | 3 +
> fs/nfs/nfs3xdr.c | 1 +
> fs/nfs_common/nfsacl.c | 125 ----
> fs/nfsd/Makefile | 13 +-
> fs/nfsd/fattr.h | 20 +
> fs/nfsd/nfs2acl.c | 99 +--
> fs/nfsd/nfs3acl.c | 623 +++++++++++++-----
> fs/nfsd/nfs3xdr.c | 166 -----
> fs/nfsd/nfs_acl3xdr_gen.c | 839 +++++++++++++++++++++++++
> fs/nfsd/nfs_acl3xdr_gen.h | 36 ++
> fs/nfsd/nfsproc.c | 3 +-
> fs/nfsd/xdr3.h | 8 +-
> include/linux/nfsacl.h | 9 -
> include/linux/sunrpc/xdrgen/nfs_acl3.h | 284 +++++++++
> 15 files changed, 1948 insertions(+), 554 deletions(-)
> create mode 100644 Documentation/sunrpc/xdr/nfs_acl3.x
> create mode 100644 fs/nfsd/fattr.h
> create mode 100644 fs/nfsd/nfs_acl3xdr_gen.c
> create mode 100644 fs/nfsd/nfs_acl3xdr_gen.h
> create mode 100644 include/linux/sunrpc/xdrgen/nfs_acl3.h
This all looks fine to me.
Acked-by: Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-10-27 10:56 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-25 15:16 [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen Chuck Lever
2026-10-25 15:16 ` [PATCH v1 01/13] Documentation: Add the RPC language description of NFS_ACLv3 Chuck Lever
2026-10-25 15:16 ` [PATCH v1 02/13] NFSD: Add infrastructure for generating NFS_ACLv3 XDR encoders and decoders Chuck Lever
2026-10-25 15:16 ` [PATCH v1 03/13] NFSD: Remove '#include "xdr3.h"' from nfs3acl.c Chuck Lever
2026-10-25 15:16 ` [PATCH v1 04/13] NFSD: New NFS_ACLv3 status codes Chuck Lever
2026-10-25 15:16 ` [PATCH v1 05/13] NFSD: Use xdrgen XDR functions for the NFS-ACL v3 NULL procedure Chuck Lever
2026-10-25 15:16 ` [PATCH v1 06/13] NFSD: Implement NFS-ACL v3 ACL encoding and decoding with xdrgen Chuck Lever
2026-10-25 15:16 ` [PATCH v1 07/13] NFSD: Use xdrgen XDR functions for the NFS-ACL v3 GETACL procedure Chuck Lever
2026-10-25 15:16 ` [PATCH v1 08/13] NFSD: Use xdrgen XDR functions for the NFS-ACL v3 SETACL procedure Chuck Lever
2026-10-25 15:16 ` [PATCH v1 09/13] NFSD: Map every NFS_ACLv3 reply status into aclstat3 Chuck Lever
2026-10-25 15:16 ` [PATCH v1 10/13] NFSD: Remove the hand-coded XDR helpers for the NFS_ACLv3 procedures Chuck Lever
2026-10-25 15:16 ` [PATCH v1 11/13] NFS: Move NFSACL_MAXPAGES into fs/nfs/nfs3_fs.h Chuck Lever
2026-10-26 22:50 ` Anna Schumaker
2026-10-25 15:16 ` [PATCH v1 12/13] NFSD: Share the attribute conversion helpers with the NFS_ACL procs Chuck Lever
2026-10-25 15:16 ` [PATCH v1 13/13] nfs_common: Remove the unused NFS ACL stream codecs Chuck Lever
2026-10-27 10:56 ` [PATCH v1 00/13] Convert server-side NFS_ACLv3 XDR to use xdrgen Jeff Layton
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox