From: Chuck Lever <cel@kernel.org>
To: NeilBrown <neil@brown.name>, Jeff Layton <jlayton@kernel.org>,
Olga Kornievskaia <okorniev@redhat.com>,
Dai Ngo <dai.ngo@oracle.com>, Tom Talpey <tom@talpey.com>
Cc: <linux-nfs@vger.kernel.org>
Subject: [PATCH v1 09/10] xdrgen: Extend the aggregate codec to optional-data list members
Date: Thu, 3 Sep 2026 11:03:50 -0400 [thread overview]
Message-ID: <20260903150351.9572-10-cel@kernel.org> (raw)
In-Reply-To: <20260903150351.9572-1-cel@kernel.org>
The hook-driven aggregate codec emits only the counted-array wire
form. XDR list types built on the optional-data idiom ("type *name")
carry no count and cannot be expressed that way. NFSv2 READDIR's
entry list is one such type.
Accept an optional-data member under "pragma aggregate" and emit
value-follows framing in place of the counted array. The element
type's existing single-node encoder already writes its own
value-follows boolean, so the loop calls it once per element and
once more with a NULL element to write the terminator. The struct C
definitions are untouched; only the marked member's framing changes.
The generated decoder is present for linkage only. Decoding a
value-follows list needs a presence-reporting element decoder, which
this change does not add. NFSv2 READDIR only encodes its entry list,
so the decode path is never exercised.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
tools/net/sunrpc/xdrgen/README | 23 +++++++---
tools/net/sunrpc/xdrgen/generators/struct.py | 42 +++++++++++++++++++
.../C/struct/decoder/aggregate_optional.j2 | 31 ++++++++++++++
.../C/struct/encoder/aggregate_optional.j2 | 31 ++++++++++++++
tools/net/sunrpc/xdrgen/xdr_ast.py | 12 +++---
5 files changed, 128 insertions(+), 11 deletions(-)
create mode 100644 tools/net/sunrpc/xdrgen/templates/C/struct/decoder/aggregate_optional.j2
create mode 100644 tools/net/sunrpc/xdrgen/templates/C/struct/encoder/aggregate_optional.j2
diff --git a/tools/net/sunrpc/xdrgen/README b/tools/net/sunrpc/xdrgen/README
index 92578671b5e9..ae8a8050698b 100644
--- a/tools/net/sunrpc/xdrgen/README
+++ b/tools/net/sunrpc/xdrgen/README
@@ -163,9 +163,18 @@ wasted work. This directive marks such a member so that xdrgen emits a
codec that owns only the wire framing (the length prefix, the bound
check, and the per-element codec) and drives the application
through begin/item/end hooks, processing one element at a time with
-no staged array. The marked member must be a variable-length
-array member of a struct; xdrgen rejects a directive naming
-anything else, and generates the codec for the server side only.
+no staged array. The marked member must be a struct member
+declared either as a variable-length array or as an optional-data
+list ("type *name"); xdrgen rejects a directive naming anything
+else, and generates the codec for the server side only.
+
+The two forms differ only in framing. A variable-length array
+carries a u32 element count, so the framing writes that count and
+bound-checks it. An optional-data list carries no count: each
+element is prefixed by a value-follows TRUE and the sequence is
+closed by a FALSE, which the framing writes after the last
+element. For the optional-data form xdrgen generates a working
+encoder only; the decoder it emits does not decode the list.
For example:
@@ -185,9 +194,11 @@ member:
nfs_acl2_secattr_decode()
nfs_acl2_secattr_decode_end()
-A decoder fills the cursor's count from the wire before calling the
-begin hook; an encoder's begin hook sets that count, and the framing
-then bound-checks it and emits the length prefix. Once a begin hook
+For a variable-length array, a decoder fills the cursor's count from
+the wire before calling the begin hook, and an encoder's begin hook
+sets that count for the framing to bound-check and emit. An
+optional-data list's encoder begin hook sets the count as well; its
+decoder never fills it. Once a begin hook
has succeeded its end hook runs, so that it can release what begin
took; the end hook receives the running success flag.
diff --git a/tools/net/sunrpc/xdrgen/generators/struct.py b/tools/net/sunrpc/xdrgen/generators/struct.py
index 41030403b759..945b9c36a701 100644
--- a/tools/net/sunrpc/xdrgen/generators/struct.py
+++ b/tools/net/sunrpc/xdrgen/generators/struct.py
@@ -178,6 +178,27 @@ def emit_struct_member_decoder(
)
)
return
+ if isinstance(field, _XdrOptionalData) and (
+ (struct_name, field.name) in aggregate_members
+ ):
+ if peer != "server":
+ raise NotImplementedError(
+ "pragma aggregate is server-side only; "
+ + peer
+ + " generation is not yet supported"
+ )
+ template = get_jinja2_template(environment, "decoder", "aggregate_optional")
+ print(
+ template.render(
+ name=field.name,
+ type=field.spec.type_name,
+ c_type=kernel_c_type(field.spec),
+ classifier=field.spec.c_classifier,
+ hook=aggregate_hook_base(struct_name),
+ member_sym=aggregate_member_symbol(struct_name, field.name),
+ )
+ )
+ return
if isinstance(field, _XdrBasic):
template = get_jinja2_template(environment, "decoder", field.template)
print(
@@ -285,6 +306,27 @@ def emit_struct_member_encoder(
)
)
return
+ if isinstance(field, _XdrOptionalData) and (
+ (struct_name, field.name) in aggregate_members
+ ):
+ if peer != "server":
+ raise NotImplementedError(
+ "pragma aggregate is server-side only; "
+ + peer
+ + " generation is not yet supported"
+ )
+ template = get_jinja2_template(environment, "encoder", "aggregate_optional")
+ print(
+ template.render(
+ name=field.name,
+ type=field.spec.type_name,
+ c_type=kernel_c_type(field.spec),
+ classifier=field.spec.c_classifier,
+ hook=aggregate_hook_base(struct_name),
+ member_sym=aggregate_member_symbol(struct_name, field.name),
+ )
+ )
+ return
if (struct_name, field.name) in pages_members:
if peer != "server":
raise NotImplementedError(
diff --git a/tools/net/sunrpc/xdrgen/templates/C/struct/decoder/aggregate_optional.j2 b/tools/net/sunrpc/xdrgen/templates/C/struct/decoder/aggregate_optional.j2
new file mode 100644
index 000000000000..add4813aa803
--- /dev/null
+++ b/tools/net/sunrpc/xdrgen/templates/C/struct/decoder/aggregate_optional.j2
@@ -0,0 +1,31 @@
+{# SPDX-License-Identifier: GPL-2.0 #}
+{# Placeholder: cannot decode value-follows framing, because the
+ element decoder does not report presence. #}
+{% if annotate %}
+ /* member {{ name }} (aggregate list) */
+{% endif %}
+ {
+ struct xdrgen_aggregate_cursor cursor = {
+ .xdr = xdr,
+ .member_id = {{ member_sym }},
+ .ctx = xdr->xdrgen_ctx,
+ };
+ bool ok = true;
+
+ if (!{{ hook }}_decode_begin(&cursor))
+ return false;
+ for (cursor.index = 0; cursor.index < cursor.count; cursor.index++) {
+ {{ classifier }}{{ c_type }} element = {};
+
+ if (!xdrgen_decode_{{ type }}(xdr, &element)) {
+ ok = false;
+ break;
+ }
+ if (!{{ hook }}_decode(&cursor, &element)) {
+ ok = false;
+ break;
+ }
+ }
+ if (!{{ hook }}_decode_end(&cursor, ok) || !ok)
+ return false;
+ }
diff --git a/tools/net/sunrpc/xdrgen/templates/C/struct/encoder/aggregate_optional.j2 b/tools/net/sunrpc/xdrgen/templates/C/struct/encoder/aggregate_optional.j2
new file mode 100644
index 000000000000..d45b823225f7
--- /dev/null
+++ b/tools/net/sunrpc/xdrgen/templates/C/struct/encoder/aggregate_optional.j2
@@ -0,0 +1,31 @@
+{# SPDX-License-Identifier: GPL-2.0 #}
+{% if annotate %}
+ /* member {{ name }} (aggregate list) */
+{% endif %}
+ {
+ struct xdrgen_aggregate_cursor cursor = {
+ .xdr = xdr,
+ .member_id = {{ member_sym }},
+ .ctx = xdr->xdrgen_ctx,
+ };
+ bool ok = true;
+
+ if (!{{ hook }}_encode_begin(&cursor))
+ return false;
+ for (cursor.index = 0; cursor.index < cursor.count; cursor.index++) {
+ {{ classifier }}{{ c_type }} element = {};
+
+ if (!{{ hook }}_encode(&cursor, &element)) {
+ ok = false;
+ break;
+ }
+ if (!xdrgen_encode_{{ type }}(xdr, &element)) {
+ ok = false;
+ break;
+ }
+ }
+ if (ok && !xdrgen_encode_{{ type }}(xdr, NULL))
+ ok = false;
+ if (!{{ hook }}_encode_end(&cursor, ok) || !ok)
+ return false;
+ }
diff --git a/tools/net/sunrpc/xdrgen/xdr_ast.py b/tools/net/sunrpc/xdrgen/xdr_ast.py
index 680a87e3bb39..1fbfae49e0cd 100644
--- a/tools/net/sunrpc/xdrgen/xdr_ast.py
+++ b/tools/net/sunrpc/xdrgen/xdr_ast.py
@@ -1207,12 +1207,14 @@ def check_aggregate_directives(root: "Specification") -> None:
meta,
)
field = fields[marked[1]]
- # Only the counted-array framing is generated, so any other
- # member form would emit hook prototypes that nothing calls.
- if not isinstance(field, _XdrVariableLengthArray):
+ # Any other member form would emit hook prototypes that
+ # nothing calls.
+ if not isinstance(
+ field, (_XdrVariableLengthArray, _XdrOptionalData)
+ ):
raise XdrSemanticError(
- f"'{value.name}.{marked[1]}' is not a variable-length"
- " array",
+ f"'{value.name}.{marked[1]}' is neither a"
+ " variable-length array nor an optional-data list",
meta,
)
if element_type is None:
--
2.55.0
next prev parent reply other threads:[~2026-09-03 15:04 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-03 15:03 [PATCH v1 00/10] New pragmas for the xdrgen tool Chuck Lever
2026-09-03 15:03 ` [PATCH v1 01/10] SUNRPC: Carry a generated-codec context pointer in struct xdr_stream Chuck Lever
2026-09-03 15:03 ` [PATCH v1 02/10] SUNRPC: Bind the svc_rqst to its XDR streams Chuck Lever
2026-09-03 15:03 ` [PATCH v1 03/10] SUNRPC: Add svcxdr_encode_opaque_payload() Chuck Lever
2026-09-03 15:03 ` [PATCH v1 04/10] xdrgen: Pass the containing struct name to member codec emitters Chuck Lever
2026-09-03 15:03 ` [PATCH v1 05/10] xdrgen: Add a "pragma pages" directive Chuck Lever
2026-09-03 15:03 ` [PATCH v1 06/10] SUNRPC: Add svcxdr_decode_opaque_payload() Chuck Lever
2026-09-03 15:03 ` [PATCH v1 07/10] xdrgen: Extend the pages directive to page-resident arguments Chuck Lever
2026-09-03 15:03 ` [PATCH v1 08/10] xdrgen: Add hook-driven aggregate codec for variable-length arrays Chuck Lever
2026-09-03 15:03 ` Chuck Lever [this message]
2026-09-03 15:03 ` [PATCH v1 10/10] xdrgen: Stream optional-data aggregate lists during encode Chuck Lever
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260903150351.9572-10-cel@kernel.org \
--to=cel@kernel.org \
--cc=dai.ngo@oracle.com \
--cc=jlayton@kernel.org \
--cc=linux-nfs@vger.kernel.org \
--cc=neil@brown.name \
--cc=okorniev@redhat.com \
--cc=tom@talpey.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox