cube(2): one frame for every walk — the class mask is in it

CUBE_OP_ENUM and CUBE_OP_RANGE returned `key | value_len | value`, so a caller
could walk a store and not learn what any record it walked past *was*. A store
could not answer `entries_flagged` from a kernel store at all, and the only way
to find out was to open the device and read the format directly — which is the
second reader of the format this interface exists to make unnecessary.

Both walks now return the flag scan's frame: `key(24) | flags(2) | value_len |
value`, with the space ahead of it when the scope is every space. One frame,
one packer, one `Batch::offer`; the separate `offer_flagged` and `pack_record`
are gone, and so is the reason for them to disagree.

The class comes from wherever the value did: an addressed image's index entry
(the binary search already read it and used to throw it away), a log entry's
mask, or zero for a packed v1/v2 record, which has no field to carry one. The
merge in `SpaceWalker` hands it back alongside the key and the value for the
same reason — a record the log supplied carries the class its writer stamped,
and dropping it there is what left a listing unable to say what it was listing.
This commit is contained in:
surface-camera-build
2026-09-22 02:20:31 -04:00
parent 1081b5f1e2
commit da0a19786e
2 changed files with 70 additions and 72 deletions
+15 -6
View File
@@ -66,10 +66,15 @@ struct cube_args {
* The walk's argument block: its own block rather than a wider `cube_args`, because it needs a
* cursor and a buffer, and the coordinate would otherwise be both an input and an output.
*
* Records are packed as `key(24) | value_len(u32, little-endian) | value`, in the store's own
* order space first, then key which is the order a checkpoint writes them and the order the
* userspace store returns them, so a kernel listing and a userspace listing can be compared
* directly. The space is not repeated per record: the caller named it.
* Records are packed as `key(24) | flags(2, little-endian) | value_len(u32, little-endian) |
* value`, in the store's own order space first, then key which is the order a checkpoint writes
* them and the order the userspace store returns them, so a kernel listing and a userspace listing
* can be compared directly. The space is not repeated per record: the caller named it.
*
* The class mask is in the frame because a listing has to be able to say what a record *is*, and a
* caller that cannot see it has to open the store itself to find out which is the second reader
* of the format this interface exists to make unnecessary. A record whose layout carries no mask (a
* packed v1/v2 image) reads as zero: "no class", the same answer every other reader gives.
*
* A walk ends when the cursor stops moving, and that is the only end signal: a batch holds as
* many whole records as fit, so most batches come back short, and reading a short batch as the
@@ -98,8 +103,9 @@ struct cube_enum_args {
* replaced.
*
* A region is a box, inclusive on both corners. Records come back packed exactly as CUBE_OP_ENUM
* packs them `key(24) | value_len(u32, little-endian) | value`, in the store's own order so a
* kernel region answer and a userspace one can be compared byte for byte.
* packs them `key(24) | flags(2, little-endian) | value_len(u32, little-endian) | value`, in the
* store's own order so a kernel region answer and a userspace one can be compared byte for
* byte.
*
* The cursor is a COUNT OF RECORDS ALREADY RETURNED, and it is the end-of-walk signal for the same
* reason as the walk's: a batch holds as many whole records as fit, so most batches come back
@@ -107,6 +113,9 @@ struct cube_enum_args {
* counts a walk counts the records of the space, a region walk counts the records *in the box*,
* because those are the records it returns.
*
* Records come back in the same frame the walk uses, class mask included:
* `key(24) | flags(2) | value_len(u32) | value`.
*
* Implementation, because it is what makes this cheap: **the kernel seeks.** The box's two corner
* keys bound every key inside it (`cube_format::key_span`), so a v3 image's sorted index is
* binary-searched for the foot of that span and read forward to its head. The span is a BOUND, not