> From f10165cb187a0e1da252abed644d63d8b8355cac Mon Sep 17 00:00:00 2001 > From: Jiri Pirko > Date: Thu, 15 Oct 2026 16:50:41 +0200 > Subject: [PATCH 08/20] ctlv: Prepare message output handling > > Add schema-aware reply construction, generated output accessors, > operation errors and query fragment completion. > > Keep CONFIG_CTLV unwired until dispatch completes the module. > > Assisted-by: Cursor: GPT-5.6 Sol > Assisted-by: Cursor: Claude Opus 5.5 > Signed-off-by: Jiri Pirko > --- > drivers/ctlv/Makefile | 2 +- > drivers/ctlv/builder.c | 374 ++++++++++++++++++++++++++++++++++++ > drivers/ctlv/builder.h | 14 ++ > include/linux/ctlv.h | 71 +++++++ > include/linux/ctlv_family.h | 41 ++++ > 5 files changed, 501 insertions(+), 1 deletion(-) > create mode 100644 drivers/ctlv/builder.c > create mode 100644 drivers/ctlv/builder.h > > diff --git a/drivers/ctlv/Makefile b/drivers/ctlv/Makefile > index 3fd656835b8a..e1a22a199ef1 100644 > --- a/drivers/ctlv/Makefile > +++ b/drivers/ctlv/Makefile > @@ -1,4 +1,4 @@ > # SPDX-License-Identifier: GPL-2.0 > obj-$(CONFIG_CTLV) += ctlv.o > > -ctlv-y += blob_desc.o message.o schema.o > +ctlv-y += blob_desc.o builder.o message.o schema.o > diff --git a/drivers/ctlv/builder.c b/drivers/ctlv/builder.c > new file mode 100644 > index 000000000000..41748ceae1a3 > --- /dev/null > +++ b/drivers/ctlv/builder.c > @@ -0,0 +1,374 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES > + * > + * Schema-aware reply construction and operation completion. > + */ > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include > +#include > + > +#include "builder.h" > +#include "internal.h" > +#include "message.h" > +#include "schema.h" > + > +static void ctlv_message_output_frag_mark(struct ctlv_op_ctx *ctx); Forward declaration of a static function that is defined at the very end of the file. Move the definition above `ctlv_op_query_more()` and drop the prototype. This is the serious design problem of the patch. The loop dereferences every cursor from `ctx->deepest` up through ->up. Those are `struct ctlv_cursor` objects on the stack frames of the family's (helper) functions. The header says "`ctlv_op_query_more()` marks still-open nests, whose cursors must remain live", but the normal way to reach this path is exactly the one where they are not: a put or `nest_start` fails because the reply buffer is full (not poisoned for queries, see `ctlv_message_output_full()`), the helper returns false, its frame is popped (taking its nest cursor with it), and the top-level query callback then calls `ctlv_op_query_more()`. `ctx->deepest` still points into the dead frame. `ctlv_op_query_done()` carefully avoids following such a pointer ("A cursor of a frame that has returned is not followed"), but this function follows it unconditionally, reading ->start and ->up from reused stack and OR-ing `FRAG_MORE` into root + a garbage offset: a kernel memory write at a stack-derived offset. Please make this robust by construction: e.g. keep the open-nest offsets in the ctx itself (a depth-indexed array of `u32`, depth <= `CTLV_MAX_NEST_DEPTH`) so `frag_mark` needs no cursor pointers, and ->deepest becomes a depth. That also removes the "deepest != nest" WARNs and the pointer-identity tricks in `ctlv_op_query_done()` and `core.c`'s `ctlv_reply_finish()`. Also confirm the validator and the ABI doc agree that `FRAG_MORE` is set on the root attr as well. > + > +void ctlv_message_output_init(struct ctlv_msg *msg, struct ctlv_op_ctx *ctx, void *buf, size_t len, > + const struct ctlv_schema_tree *tree) > +{ > + *msg = (struct ctlv_msg){ > + .root = buf, > + .len = len, > + .tree = tree, > + .family = ctx->family, > + .ctx = ctx, > + }; > + *msg->root = (struct ctlv_attr){ This writes a `struct ctlv_attr` into buf with no check that len >= `sizeof(*msg->root)` (or that buf is `CTLV_ALIGNTO`-aligned). `core.c` guarantees it via `ctlv_reply_needed()`, but `event.c` computes `rec->room - sizeof(rec->hdr)` itself. A `WARN_ON_ONCE` would be cheap insurance against a heap write. The function also has no comment at all, and no check that len fits in the `u32` `attr->len` (see `write()` below). > + .len = sizeof(*msg->root), > + .flags = CTLV_ATTR_F_NESTED, > + .attr_id = CTLV_ATTR_ROOT, > + }; > +} > + > +/* The first thing that went wrong is what the op ends up failing with. */ > +static bool ctlv_message_output_poison(struct ctlv_cursor *nest, int error) > +{ > + if (!nest->msg->poison) > + nest->msg->poison = error; > + return false; > +} > + > +/* A poisoned message is refused silently: whatever poisoned it has warned. */ Also the comment "Also what refuses a cursor that reads a request." in this function is hard to parse. Say what it means: a request msg has ->ctx == `NULL`, so this doubles as a direction check. > +static bool ctlv_message_output_ready(struct ctlv_cursor *nest) > +{ > + if (WARN_ON_ONCE(!nest->msg || !nest->attr)) > + return false; > + /* Also what refuses a cursor that reads a request. */ > + if (WARN_ON_ONCE(!nest->msg->ctx)) > + return false; > + if (nest->msg->poison) > + return false; > + if (WARN_ON_ONCE(nest->msg->ctx->deepest != nest)) > + return ctlv_message_output_poison(nest, -EINVAL); > + return true; > +} > + > +/* What the schema places in this nest under one ID, as one kind. */ > +static const struct ctlv_node_schema *ctlv_message_output_member(struct ctlv_cursor *nest, > + u64 attr_id, u8 kind, > + struct ctlv_schema_pos *pos) > +{ > + const struct ctlv_node_schema *node; > + > + if (!ctlv_message_output_ready(nest)) > + return NULL; > + if (WARN_ON_ONCE(!ctlv_schema_member(nest->msg->family, nest->tree, > + nest->node, attr_id, pos))) { > + ctlv_message_output_poison(nest, -EINVAL); > + return NULL; > + } > + node = &pos->tree->node_schemas[pos->node]; > + if (WARN_ON_ONCE(node->kind != kind)) { > + ctlv_message_output_poison(nest, -EINVAL); > + return NULL; > + } > + return node; > +} > + > +/* > + * A query is offered whatever room its caller had, so running out ends a > + * fragment. Anything else wrote a message its own schema does not describe. > + */ > +static bool ctlv_message_output_full(struct ctlv_cursor *nest) > +{ > + const struct ctlv_op_schema *op = nest->msg->ctx->op->schema; > + > + if (op->type == CTLV_OP_TYPE_QUERY) > + return false; > + WARN_ONCE(1, "ctlv: op %#018llx wrote past the reply length its schema declares\n", > + op->op_id); > + return ctlv_message_output_poison(nest, -EMSGSIZE); > +} > + > +/* What one cursor stands on, as the buffer this direction may change. */ > +static struct ctlv_attr *ctlv_message_output_attr(const struct ctlv_cursor *nest) The comment ("as the buffer this direction may change") is cryptic; this is a const-stripping cast because cursors store ->attr as const. Document that. > +{ > + return (struct ctlv_attr *)((u8 *)nest->msg->root + nest->start); > +} > + > +/* The pad is written rather than left as it was: it belongs to the nest. */ > +static struct ctlv_attr *ctlv_message_output_write(struct ctlv_cursor *nest, u64 attr_id, u32 flags, > + const void *payload, u32 payload_len) > +{ > + u32 len = sizeof(struct ctlv_attr) + payload_len; > + size_t step = ALIGN((size_t)len, CTLV_ALIGNTO); > + struct ctlv_msg *msg = nest->msg; > + struct ctlv_cursor *open; > + struct ctlv_attr *attr; > + size_t offset; > + > + offset = nest->start + ctlv_message_output_attr(nest)->len; > + if (offset + step > msg->len) { > + ctlv_message_output_full(nest); > + return NULL; > + } > + > + attr = (struct ctlv_attr *)((u8 *)msg->root + offset); > + *attr = (struct ctlv_attr){ > + .len = len, > + .flags = flags, > + .attr_id = attr_id, > + }; > + if (payload_len) The function returns a `struct ctlv_attr` * and every `ctlv_put_*()` does `return ctlv_message_output_write(...)` into a bool. That is an implicit pointer-to-bool conversion; use an explicit `!!` or split the pointer-returning variant (needed only by `nest_start`) from the bool one. > + memcpy(attr + 1, payload, payload_len); > + memset((u8 *)attr + len, 0, step - len); > + if (!(flags & CTLV_ATTR_F_NESTED)) > + msg->ctx->frag.progress = true; > + > + /* Every nest the attr landed in is that much longer, the root too. */ > + for (open = nest; open; open = open->up) `attr->len` is a `u32` while `msg->len` is a `size_t`; the `offset + step > msg->len` check bounds only against `msg->len`. It is correct only because `core.c` caps the capacity at `CTLV_MAX_INLINE_MESSAGE_LEN`. `builder.c` is the only place that could enforce "root length fits in `u32`"; add a check or comment in `ctlv_message_output_init()`. > + ctlv_message_output_attr(open)->len += step; > + return attr; > +} > + > +/* The 64-bit payload that every integer, enum and flags attr carries. */ > +static bool ctlv_message_put_scalar(struct ctlv_cursor *nest, u64 attr_id, u8 kind, u64 value) `ctlv_put_u64()` and `ctlv_put_s64()` are one-line wrappers around this; `ctlv_put_s64()` silently converts `s64` to `u64` for the range check. Two exports plus a static helper for one operation is more than needed; `ctlv_put_s64()` could just cast and call `ctlv_put_u64()`. > +{ > + const struct ctlv_node_schema *node; > + struct ctlv_schema_pos pos; > + > + node = ctlv_message_output_member(nest, attr_id, kind, &pos); > + if (!node) > + return false; > + /* A value outside what the attr declares is one nothing may be told. */ > + if (WARN_ON_ONCE(!ctlv_schema_value_ok(node, value))) > + return ctlv_message_output_poison(nest, -EINVAL); > + return ctlv_message_output_write(nest, attr_id, 0, &value, sizeof(value)); > +} > + > +bool ctlv_put_u64(struct ctlv_cursor *nest, u64 attr_id, u8 kind, u64 value) > +{ > + return ctlv_message_put_scalar(nest, attr_id, kind, value); > +} > +EXPORT_SYMBOL_NS_GPL(ctlv_put_u64, "CTLV"); > + > +bool ctlv_put_s64(struct ctlv_cursor *nest, u64 attr_id, u8 kind, s64 value) > +{ > + return ctlv_message_put_scalar(nest, attr_id, kind, value); > +} > +EXPORT_SYMBOL_NS_GPL(ctlv_put_s64, "CTLV"); > + > +bool ctlv_put_bool(struct ctlv_cursor *nest, u64 attr_id, u8 kind, bool value) > +{ > + u8 payload = value; > + struct ctlv_schema_pos pos; > + > + if (!ctlv_message_output_member(nest, attr_id, kind, &pos)) > + return false; > + return ctlv_message_output_write(nest, attr_id, 0, &payload, sizeof(payload)); > +} > +EXPORT_SYMBOL_NS_GPL(ctlv_put_bool, "CTLV"); > + > +bool ctlv_put_string(struct ctlv_cursor *nest, u64 attr_id, u8 kind, const char *value) > +{ > + const struct ctlv_node_schema *node; > + struct ctlv_schema_pos pos; > + size_t len; > + > + node = ctlv_message_output_member(nest, attr_id, kind, &pos); > + if (!node) > + return false; > + len = strnlen(value, (size_t)node->length.max_len + 1); A `NULL` @value oopses here. `strnlen()` is already bounded to `max_len` + 1, so the `len > node->length.max_len ||` clause below is redundant with `ctlv_schema_length_ok()`, which tests len <= `max_len` (`schema.c`:685). Drop it. > + if (WARN_ON_ONCE(len > node->length.max_len || !ctlv_schema_length_ok(node, len))) Redundant clause, see above. Also, a too-long dynamic string (e.g. a device name) is a runtime-reachable WARN; document that the family must truncate before calling. > + return ctlv_message_output_poison(nest, -EINVAL); > + return ctlv_message_output_write(nest, attr_id, 0, value, len + 1); > +} > +EXPORT_SYMBOL_NS_GPL(ctlv_put_string, "CTLV"); > + > +bool ctlv_put_binary(struct ctlv_cursor *nest, u64 attr_id, u8 kind, const void *value, u32 len) > +{ > + const struct ctlv_node_schema *node; > + struct ctlv_schema_pos pos; > + > + node = ctlv_message_output_member(nest, attr_id, kind, &pos); > + if (!node) > + return false; > + if (WARN_ON_ONCE(!ctlv_schema_length_ok(node, len))) > + return ctlv_message_output_poison(nest, -EINVAL); > + return ctlv_message_output_write(nest, attr_id, 0, value, len); > +} > +EXPORT_SYMBOL_NS_GPL(ctlv_put_binary, "CTLV"); > + > +bool ctlv_nest_start(struct ctlv_cursor *nest, struct ctlv_cursor *child, u64 attr_id, u8 kind) > +{ > + struct ctlv_schema_pos pos; > + struct ctlv_attr *attr; > + > + *child = (struct ctlv_cursor){}; > + if (!ctlv_message_output_member(nest, attr_id, kind, &pos)) > + return false; > + /* Unreachable through any schema the generator would have accepted. */ A check commented as unreachable is dead code. The generator already enforces `CTLV_MAX_DECLARED_NEST_DEPTH`; drop it or say it defends against a hand-written tree. Also: `*child = (struct ctlv_cursor){};` runs before any failure path, so every failing `ctlv_nest_start()` (including the legitimate query "reply full" case, which does not poison) leaves a zeroed child. If the family/generated code then calls `ctlv_nest_end(child)`, it trips `WARN_ON_ONCE(!child->msg ...)`. The contract that `nest_end` must not follow a failed start is not documented anywhere (no kernel-doc for `ctlv_nest_start()` or `CTLV_NEST_START`). > + if (WARN_ON_ONCE(nest->depth + 1 > CTLV_MAX_NEST_DEPTH)) > + return ctlv_message_output_poison(nest, -EINVAL); > + > + attr = ctlv_message_output_write(nest, attr_id, CTLV_ATTR_F_NESTED, NULL, 0); > + if (!attr) > + return false; > + > + *child = (struct ctlv_cursor){ > + .msg = nest->msg, > + .attr = attr, > + .up = nest, > + .start = ctlv_message_offset(nest->msg, attr), > + .tree = pos.tree, > + .node = pos.node, > + .depth = nest->depth + 1, > + }; > + nest->msg->ctx->deepest = child; `ctx->deepest` now points at a cursor on the caller's stack. See the comment on `ctlv_message_output_frag_mark()` below for why that is dangerous. > + return true; > +} > +EXPORT_SYMBOL_NS_GPL(ctlv_nest_start, "CTLV"); > + > +void ctlv_nest_end(struct ctlv_cursor *child) > +{ > + struct ctlv_op_ctx *ctx; > + > + if (WARN_ON_ONCE(!child->msg || !child->msg->ctx || !child->up)) > + return; > + ctx = child->msg->ctx; > + if (child->msg->poison) > + return; > + if (WARN_ON_ONCE(ctx->deepest != child)) { > + ctlv_message_output_poison(child, -EINVAL); > + return; > + } > + if (ctlv_message_output_attr(child)->len == sizeof(struct ctlv_attr)) Why does an empty nest count as progress? This needs a comment: it is presumably the rule that lets `query_more()` accept a fragment consisting only of an empty nest. > + ctx->frag.progress = true; > + ctx->deepest = child->up; > +} > +EXPORT_SYMBOL_NS_GPL(ctlv_nest_end, "CTLV"); > + > +static bool ctlv_op_error_valid(int error) > +{ > + return !WARN_ON_ONCE(error >= 0 || error < -MAX_ERRNO); `MAX_ERRNO` is in ``, which is not included (it builds only via transitive includes). Same for `vscnprintf()` (``) and `WARN_ON_ONCE` (`` is included, fine). > +} > + > +int ctlv_op_error(struct ctlv_op_ctx *ctx, int error, const char *message) > +{ > + if (!ctlv_op_error_valid(error)) > + return error < 0 && error >= -MAX_ERRNO ? error : -EINVAL; Dead expression. `ctlv_op_error_valid()` is false only if error >= 0 or error < -`MAX_ERRNO`; in both cases "error < 0 && error >= -`MAX_ERRNO`" is false, so this always evaluates to `-EINVAL`. Just "return `-EINVAL`;" (which is what the kernel-doc says). Same in `ctlv_op_errorf()` below. The two functions also duplicate the whole prologue; share a helper or make `ctlv_op_error()` call a `va_list` variant. The kernel-doc should also state the "first message wins" semantics and that @message must not be `NULL`. > + if (!ctx->error.message[0]) > + strscpy(ctx->error.message, message, sizeof(ctx->error.message)); > + return error; > +} > +EXPORT_SYMBOL_NS_GPL(ctlv_op_error, "CTLV"); > + > +int ctlv_op_errorf(struct ctlv_op_ctx *ctx, int error, const char *fmt, ...) > +{ > + va_list args; > + > + if (!ctlv_op_error_valid(error)) > + return error < 0 && error >= -MAX_ERRNO ? error : -EINVAL; > + if (ctx->error.message[0]) > + return error; > + va_start(args, fmt); > + vscnprintf(ctx->error.message, sizeof(ctx->error.message), fmt, args); The message is delivered to userspace. Worth a sentence in the kernel-doc that families must not format kernel addresses or other internal state here. > + va_end(args); > + return error; > +} > +EXPORT_SYMBOL_NS_GPL(ctlv_op_errorf, "CTLV"); > + > +/* Continuation is a query's, so asking about one anywhere else is a bug. */ > +static bool ctlv_op_is_query(const struct ctlv_op_ctx *ctx) > +{ > + return !WARN_ON_ONCE(ctx->op->schema->type != CTLV_OP_TYPE_QUERY); > +} > + > +u64 ctlv_op_query_generation(const struct ctlv_op_ctx *ctx) > +{ > + if (!ctlv_op_is_query(ctx)) > + return 0; > + return ctx->frag.generation; > +} > +EXPORT_SYMBOL_NS_GPL(ctlv_op_query_generation, "CTLV"); > + > +u64 ctlv_op_query_cursor(const struct ctlv_op_ctx *ctx) > +{ > + if (!ctlv_op_is_query(ctx)) > + return 0; > + return ctx->frag.cursor; > +} > +EXPORT_SYMBOL_NS_GPL(ctlv_op_query_cursor, "CTLV"); > + > +/* > + * What both endings check: a query, one ending, and a generation that has not > + * moved under a continuation, which no fragment can be delivered across. > + */ > +static int ctlv_query_fragment_end(struct ctlv_op_ctx *ctx, u64 generation) `ctx->frag`.answered is set before the poison/stale checks, so after any error a second call WARNs instead of reporting the first error. Acceptable as a bug detector, but say so. Also `ctx->frag`.reported is committed here while `ctlv_op_query_more()` can still fail afterwards; harmless only because `core.c` ignores it on error. Validate everything first, then commit state. > +{ > + if (!ctlv_op_is_query(ctx)) > + return -EINVAL; > + if (WARN_ON_ONCE(ctx->frag.answered)) > + return -EINVAL; > + ctx->frag.answered = true; > + /* Whatever the builder gave up on has warned about it already. */ > + if (ctx->output.poison) > + return ctx->output.poison; > + if (ctx->frag.cursor && generation != ctx->frag.generation) { > + ctx->error.reason = CTLV_VALIDATION_REASON_STALE_GENERATION; > + ctx->error.expected_generation = ctx->frag.generation; > + ctx->error.current_generation = generation; > + return -ESTALE; > + } > + ctx->frag.reported = generation; > + return 0; > +} > + > +int ctlv_op_query_more(struct ctlv_op_ctx *ctx, u64 generation, u64 next_cursor) > +{ > + int ret = ctlv_query_fragment_end(ctx, generation); > + > + if (!ret && `WARN_ON_ONCE(!ctx->frag.progress ...)` assumes the buffer fits at least one leaf. That is guaranteed by `query_reply_min_len` in `core.c` (a cursor-bearing exec with `reply_len` < that returns `-EMSGSIZE`), but only if the generator computes it correctly; document the cross-file dependency here. > + WARN_ON_ONCE(!ctx->frag.progress || !next_cursor || next_cursor == ctx->frag.cursor)) > + ret = -EINVAL; > + if (!ret) { > + ctlv_message_output_frag_mark(ctx); > + ctx->frag.next_cursor = next_cursor; > + } > + ctx->frag.result = ret; > + return ret; > +} > +EXPORT_SYMBOL_NS_GPL(ctlv_op_query_more, "CTLV"); > + > +int ctlv_op_query_done(struct ctlv_op_ctx *ctx, u64 generation) > +{ > + int ret = ctlv_query_fragment_end(ctx, generation); > + > + if (!ret && WARN_ON_ONCE(ctx->deepest != &ctx->output_root)) { > + /* A cursor of a frame that has returned is not followed. */ > + ctx->deepest = &ctx->output_root; > + ret = -EINVAL; > + } > + ctx->frag.result = ret; > + return ret; > +} > +EXPORT_SYMBOL_NS_GPL(ctlv_op_query_done, "CTLV"); > + > +static void ctlv_message_output_frag_mark(struct ctlv_op_ctx *ctx) > +{ > + struct ctlv_cursor *open; > + > + for (open = ctx->deepest; open; open = open->up) > + ctlv_message_output_attr(open)->flags |= CTLV_ATTR_F_FRAG_MORE; > + ctx->deepest = &ctx->output_root; > +} > diff --git a/drivers/ctlv/builder.h b/drivers/ctlv/builder.h > new file mode 100644 > index 000000000000..744595ebd88e > --- /dev/null > +++ b/drivers/ctlv/builder.h > @@ -0,0 +1,14 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +#ifndef _CTLV_BUILDER_H > +#define _CTLV_BUILDER_H > + > +#include > + > +struct ctlv_msg; > +struct ctlv_op_ctx; > +struct ctlv_schema_tree; > + > +void ctlv_message_output_init(struct ctlv_msg *msg, struct ctlv_op_ctx *ctx, void *buf, size_t len, > + const struct ctlv_schema_tree *tree); > + > +#endif /* _CTLV_BUILDER_H */ > diff --git a/include/linux/ctlv.h b/include/linux/ctlv.h > index addb893f4b8d..45f0c53b56ea 100644 > --- a/include/linux/ctlv.h > +++ b/include/linux/ctlv.h > @@ -52,4 +52,75 @@ struct ctlv_cursor { > u32 index_scope; > }; > > +/** > + * ctlv_nest_end - Finish an output nest. > + * @child: nest cursor returned by a generated start helper > + */ > +void ctlv_nest_end(struct ctlv_cursor *child); > + > +/** > + * ctlv_op_error - Record an operation error message. > + * @ctx: operation context > + * @error: negative error to return > + * @message: message copied into the operation result > + * > + * Return: @error, or -EINVAL if @error is invalid. > + */ > +int ctlv_op_error(struct ctlv_op_ctx *ctx, int error, const char *message); > + > +/** > + * ctlv_op_errorf - Format an operation error message. > + * @ctx: operation context > + * @error: negative error to return > + * @fmt: printf format and arguments > + * > + * Return: @error, or -EINVAL if @error is invalid. > + */ > +__printf(3, 4) > +int ctlv_op_errorf(struct ctlv_op_ctx *ctx, int error, const char *fmt, ...); > + > +/* > + * The core retains no query state. A continuation repeats the request and This is a plain comment, not kernel-doc, and it sits on top of an unrelated /** `ctlv_op_query_generation` */ block. Fold it into a DOC: section or the kernel-doc of the functions. Likewise none of `ctlv_put_*()` / `ctlv_nest_start()` in `ctlv_family.h` have kernel-doc, though the return contract (false == nothing written; for non-queries it also poisons, for queries it may just mean "full") is subtle and should be written down once. > + * passes back the family's generation and nonzero cursor. It fails with > + * -ESTALE if the generation changed. Each fragment must complete a logical > + * leaf and return a cursor different from its input. > + * > + * ctlv_op_query_more() marks still-open nests, whose cursors must remain live. > + */ > +/** > + * ctlv_op_query_generation - Get a query continuation generation. > + * @ctx: query operation context > + * > + * Return: generation supplied by the previous fragment, or zero initially. > + */ > +u64 ctlv_op_query_generation(const struct ctlv_op_ctx *ctx); > + > +/** > + * ctlv_op_query_cursor - Get a query continuation cursor. > + * @ctx: query operation context > + * > + * Return: cursor supplied by the previous fragment, or zero initially. > + */ > +u64 ctlv_op_query_cursor(const struct ctlv_op_ctx *ctx); > + > +/** > + * ctlv_op_query_more - Complete a query fragment with more data pending. > + * @ctx: query operation context > + * @generation: current data generation > + * @next_cursor: nonzero cursor for the next fragment > + * > + * Return: 0, or a negative error. > + */ > +int ctlv_op_query_more(struct ctlv_op_ctx *ctx, u64 generation, > + u64 next_cursor); > + > +/** > + * ctlv_op_query_done - Complete the last query fragment. > + * @ctx: query operation context > + * @generation: current data generation > + * > + * Return: 0, or a negative error. > + */ > +int ctlv_op_query_done(struct ctlv_op_ctx *ctx, u64 generation); > + > #endif /* _LINUX_CTLV_H */ > diff --git a/include/linux/ctlv_family.h b/include/linux/ctlv_family.h > index eae690b9c1d5..d2598ec686f1 100644 > --- a/include/linux/ctlv_family.h > +++ b/include/linux/ctlv_family.h > @@ -326,6 +326,47 @@ bool ctlv_attr_bool(const struct ctlv_cursor *cursor); > const char *ctlv_attr_string(const struct ctlv_cursor *cursor); > const void *ctlv_attr_binary(const struct ctlv_cursor *cursor, u32 *len); > > +bool ctlv_put_u64(struct ctlv_cursor *nest, u64 attr_id, u8 kind, u64 value); > +bool ctlv_put_s64(struct ctlv_cursor *nest, u64 attr_id, u8 kind, s64 value); > +bool ctlv_put_bool(struct ctlv_cursor *nest, u64 attr_id, u8 kind, bool value); > +bool ctlv_put_string(struct ctlv_cursor *nest, u64 attr_id, u8 kind, > + const char *value); > +bool ctlv_put_binary(struct ctlv_cursor *nest, u64 attr_id, u8 kind, > + const void *value, u32 len); > +bool ctlv_nest_start(struct ctlv_cursor *nest, struct ctlv_cursor *child, > + u64 attr_id, u8 kind); > + > +#define __CTLV_DEFINE_PUT(_name, _type, _put_fn, _attr_id, _kind) \ Minor style: the macro blocks mix alignments of the trailing backslashes (the `ctlv_put_binary()` line overruns the column), and `ctlv_blob_resolve()` (added by 9/20) is wedged right before this macro block with no blank line. > +static inline bool _name##_put(struct ctlv_cursor *nest, \ > + _type value) \ > +{ \ > + return _put_fn(nest, (_attr_id), (_kind), value); \ > +} > + > +#define CTLV_PUT_U64(_name, _attr_id, _kind) \ > + __CTLV_DEFINE_PUT(_name, u64, ctlv_put_u64, _attr_id, _kind) > +#define CTLV_PUT_S64(_name, _attr_id, _kind) \ > + __CTLV_DEFINE_PUT(_name, s64, ctlv_put_s64, _attr_id, _kind) > +#define CTLV_PUT_BOOL(_name, _attr_id, _kind) \ > + __CTLV_DEFINE_PUT(_name, bool, ctlv_put_bool, _attr_id, _kind) > +#define CTLV_PUT_STRING(_name, _attr_id, _kind) \ > + __CTLV_DEFINE_PUT(_name, const char *, ctlv_put_string, \ > + _attr_id, _kind) > + > +#define CTLV_PUT_BINARY(_name, _attr_id, _kind) \ > +static inline bool _name##_put(struct ctlv_cursor *nest, \ > + const void *value, u32 len) \ > +{ \ > + return ctlv_put_binary(nest, (_attr_id), (_kind), value, len); \ > +} > + > +#define CTLV_NEST_START(_name, _attr_id, _kind) \ > +static inline bool _name##_start(struct ctlv_cursor *nest, \ > + struct ctlv_cursor *nested) \ > +{ \ > + return ctlv_nest_start(nest, nested, (_attr_id), (_kind)); \ > +} > + > #define CTLV_IN_REF(_name, _tree, _attr_id, _node, _slot, _kind) \ > static inline struct ctlv_input_ref _name##_ref(void) \ > { \ > -- > 2.55.0 > Summary of the most serious issues in 8/20: - `ctlv_op_query_more()` (`frag_mark`) walks `ctx->deepest->up` through cursors that live on family stack frames. A normal "reply full" path abandons open nests without `ctlv_nest_end()`; the walk then dereferences dead stack and writes through a garbage ->start. Store open-nest offsets in the ctx instead. - The ternary in `ctlv_op_error()`/`ctlv_op_errorf()` is dead; the result is always `-EINVAL`. - A failed `nest_start()` leaves a zeroed child that makes a following `ctlv_nest_end()` WARN; the `MAX_NEST_DEPTH` check is documented as unreachable. - Redundant `max_len` check in `ctlv_put_string()`, forward declaration of a static function, missing includes, detached non-kernel-doc comment block in `ctlv.h`.