xdrgen: Stream optional-data aggregate lists during encode

The optional-data aggregate encoder loops cursor.count times, so the
begin hook must report the element count before the first element is
written.  A producer that reads its elements while the reply is
encoded -- NFSv2 READDIR streaming directory entries straight into the
send buffer -- does not know that count in advance, and would have to
materialize the whole list just to satisfy the loop bound.

An optional-data list carries no count on the wire, so nothing forces
one on the encoder.  Drive it from the item hook instead: a false
return ends the list, and the framing writes the terminator.

The counted-array form and its hook contract are unchanged.  No
in-tree spec marks an optional-data member yet, so no generated code
changes here.

Acked-by: Jeff Layton <jlayton@kernel.org>
Link: https://patch.msgid.link/20260908134234.512312-11-cel@kernel.org
Signed-off-by: Chuck Lever <cel@kernel.org>
This commit is contained in:
Chuck Lever 2026-09-08 09:42:34 -04:00
parent 9ebc61dfe9
commit 1e7f9d30d5
3 changed files with 28 additions and 15 deletions

View File

@ -30,15 +30,24 @@ typedef struct {
} opaque;
/*
* Cursor a hook-driven aggregate codec hands to its application hooks,
* one element at a time, in place of a materialized C array. The
* generated framing owns it. @xdr is the RPC layer's stream; @ctx is
* that stream's xdrgen_ctx, the svc_rqst on the server. @index is
* the current element and @count the wire array length: a decoder
* fills @count from the wire before the begin hook runs, an
* encoder's begin hook sets it. @member_id selects among a type's
* marked members. The begin/item/end contract is under "Pragma
* aggregate" in tools/net/sunrpc/xdrgen/README.
* Cursor a hook-driven aggregate codec hands to its begin/item/end
* hooks, one element at a time, in place of a materialized C array.
* The generated framing owns it. @xdr is the RPC layer's stream and
* @ctx its xdrgen_ctx, the svc_rqst on the server. @index is the
* current element; @member_id selects among a type's marked members.
* For a counted array @count is the wire length: a decoder fills it
* before the begin hook runs, an encoder's begin hook sets it. The
* hook contract is under "Pragma aggregate" in
* tools/net/sunrpc/xdrgen/README.
*
* An optional-data list ("type *name") carries no count. Its encoder
* pulls elements until the item hook returns false. False ends the
* list rather than failing the encode: the framing writes the
* value-follows terminator, so a producer that stops early leaves a
* valid truncated list on the wire. A producer that stops for a
* mid-list error records it in the application state @ctx reaches,
* and either returns false from the end hook, which fails the
* encode, or reports it in the procedure status.
*/
struct xdrgen_aggregate_cursor {
struct xdr_stream *xdr;

View File

@ -182,7 +182,13 @@ 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. The element type must therefore be a pointer type,
whose codec owns that framing; xdrgen rejects any other element
type. For the optional-data form xdrgen generates a working
type. That form has no count to loop on, so its encoder pulls
elements until the item hook returns false. False ends the list
rather than failing the encode: a producer stops when its source
is exhausted or when a reply budget fills. A producer that stops
for an error records it in its own state and either fails the
encode from the end hook or reports it in the procedure status.
For the optional-data form xdrgen generates a working
encoder only; the decoder it emits fails without consuming the
list, so xdrgen also rejects the directive when the struct is
reachable from an RPC argument.
@ -208,7 +214,7 @@ member:
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
optional-data list's encoder does not read the count and 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.

View File

@ -12,13 +12,11 @@
if (!{{ hook }}_encode_begin(&cursor))
return false;
for (cursor.index = 0; cursor.index < cursor.count; cursor.index++) {
for (cursor.index = 0; ; cursor.index++) {
{{ classifier }}{{ c_type }} element = {};
if (!{{ hook }}_encode(&cursor, &element)) {
ok = false;
if (!{{ hook }}_encode(&cursor, &element))
break;
}
if (!xdrgen_encode_{{ type }}(xdr, &element)) {
ok = false;
break;