Files
surface-camera-build da0a19786e 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.
2026-09-22 02:20:31 -04:00

212 lines
11 KiB
C

/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
* CUBELinux: the coordinate interface.
*
* The kernel's native interface to the store. Operations are the verbs the command language
* already defines — one language, and this is its kernel form (DESIGN-cube-interface.md).
*
* A coordinate is not a name and not a path: it is *where* a record is, and knowing it is the
* authorisation to use it. Nothing here resolves a name, and nothing enumerates.
*/
#ifndef _UAPI_LINUX_CUBE_H
#define _UAPI_LINUX_CUBE_H
#include <linux/types.h>
/* Which cube, and where in it. 32-byte space: unguessable, deliberately. */
struct cube_coord {
__u8 space[32];
__u64 x;
__u64 y;
__u64 z;
};
/*
* The argument block. `size` first, and checked: an interface that cannot grow is an
* interface that has to be replaced, and syscall numbers are permanent.
*/
struct cube_args {
__u32 size; /* sizeof(struct cube_args) as the caller built it */
__u32 op; /* CUBE_OP_* */
struct cube_coord coord; /* unused by CUBE_OP_SYNC */
__u64 value; /* user pointer: bytes to write, or where to put them */
__u64 len; /* in: bytes offered, or the buffer's capacity.
* out: on -ERANGE, the bytes that would be needed;
* on success for CUBE_OP_GET, the bytes read.
*/
__u16 flags; /* in: CUBE_OP_PUT — the class mask to stamp on the record;
* out: CUBE_OP_GET — the mask the record carries.
*
* One field for both directions because it is one thing: the
* class of this record. A write states it, a read learns it,
* and neither is a special case of the other. 0 is "no class",
* which is what every record written before the field existed
* reads as — so not classifying is not writing a special value,
* and a read that found nothing leaves 0 rather than a stale
* class for the caller to believe.
*
* A store whose image is the legacy packed layout has no field
* to put a mask in, and drops it: that layout cannot carry a
* class, and saying otherwise would be a lie about the bytes on
* disk. A read from such a store answers 0 for the same reason.
*/
__u16 reserved; /* must be 0 */
};
#define CUBE_OP_PUT 1 /* store bytes at a coordinate */
#define CUBE_OP_GET 2 /* read them back */
#define CUBE_OP_DEL 3 /* remove the record */
#define CUBE_OP_SYNC 4 /* fold the log into the image */
#define CUBE_OP_ENUM 5 /* walk the records of a space, in batches */
#define CUBE_OP_SPACES 6 /* walk the spaces that hold records */
#define CUBE_OP_RANGE 7 /* walk the records of a space that lie in a box */
#define CUBE_OP_FLAG_SCAN 8 /* walk the records of a space whose class mask matches */
/*
* 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) | 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
* end truncates a listing to its first batch. CUBE_OP_ENUM answers a finished walk with no
* records and the cursor unchanged; CUBE_OP_SPACES answers it with -ENOENT, because asking for
* the space after the last one is asking for a space that is not there. -ERANGE keeps its usual
* meaning: not one whole record fits, and `len` says how much one needs.
*/
struct cube_enum_args {
__u32 size; /* sizeof(struct cube_enum_args) as the caller built it */
__u32 op; /* CUBE_OP_ENUM or CUBE_OP_SPACES */
__u8 space[32]; /* in: the space to walk; out: the space found (CUBE_OP_SPACES) */
__u64 cursor; /* in: 0 to start, or what the last call returned;
* out: what to pass next — see the end-of-walk rule above
*/
__u64 value; /* user pointer: where to put the records */
__u64 len; /* in: the buffer's capacity;
* out: bytes written, or on -ERANGE what would be needed
*/
};
/*
* The region walk's argument block: its own block, for the walk's reason — it needs a box, a cursor
* and a buffer, and the coordinate would otherwise be both an input and an output. It is versioned
* by `size` like the other two, so this interface grows by gaining a block rather than by being
* replaced.
*
* A region is a box, inclusive on both corners. Records come back packed exactly as CUBE_OP_ENUM
* 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
* short, and reading a short batch as the end truncates the answer. The one difference is what it
* 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
* the set — records *outside* the box also have keys inside it (Z-order amplification) — so each
* candidate is decoded and tested against the box before it is returned. More records may therefore
* be examined than are returned, and that over-coverage is the honest cost of the span.
*/
struct cube_range_args {
__u32 size; /* sizeof(struct cube_range_args) as the caller built it */
__u32 op; /* CUBE_OP_RANGE */
__u8 space[32]; /* in: the space to search */
__u64 lo[3]; /* in: the region's near corner, inclusive */
__u64 hi[3]; /* in: the region's far corner, inclusive */
__u64 cursor; /* in: 0 to start, or what the last call returned;
* out: what to pass next — see the end-of-walk rule above
*/
__u64 value; /* user pointer: where to put the records */
__u64 len; /* in: the buffer's capacity;
* out: bytes written, or on -ERANGE what would be needed
*/
};
/*
* The flag scan's argument block: `CUBE_OP_FLAG_SCAN`, the class-mask half of the store's
* classification substrate (DESIGN-flag-vocabularies.md). Its own block for the walks' reason — it
* needs a cursor and a buffer — and versioned by `size` like the other three.
*
* `mask` is a raw 16-bit class mask and this interface does not interpret a bit of it: the
* vocabulary that owns the bits (events today, sealing / lineage / lifecycle later) is the only
* thing that knows what they mean. `mode` says how to read the mask:
*
* CUBE_FLAG_ANY the record shares at least one bit with `mask` — "every error"
* CUBE_FLAG_ALL the record carries every bit of `mask` — "every Wi-Fi error"
*
* A `mask` of zero matches *nothing*, not everything: naming no class is asking no question, and a
* scan that answered a walk's worth of records to an empty question would be a walk wearing a
* scan's hat.
*
* The cursor counts **matches already returned**, as the region walk's counts records in its box,
* and for the same reason: the records examined before a match are not matches, so the index
* position of the cursor-th match is not arithmetic. A batch holds as many whole records as fit, so
* most batches come back short; reading a short batch as the end truncates the answer. A finished
* scan answers with no records and the cursor unchanged.
*
* Records come back as `key(24) | flags(2, little-endian) | value_len(u32, little-endian) | value`
* — the walk's frame with the class mask in it. The mask travels because a scan's answer has to say
* what class each record answered with: a record can carry bits beyond the one asked for, and no
* other operation returns a mask.
*
* Under CUBE_SPACE_EVERY the frame gains a leading `space(32)`, because it has to: a walk's frame
* leaves the space out on the grounds that the caller named it, and a caller who named no space
* cannot be told which one a record came from any other way. A coordinate is meaningless without
* its space, so an every-space answer that omitted it would be unusable rather than merely terse.
* The space leads because the (space, key) pair it forms is the order records are stored in and
* returned in.
*/
struct cube_flag_scan_args {
__u32 size; /* sizeof(struct cube_flag_scan_args) as the caller built it */
__u32 op; /* CUBE_OP_FLAG_SCAN */
__u8 space[32]; /* in: the space to scan, or ignored with CUBE_SPACE_EVERY */
__u16 mask; /* in: the class mask to match; 0 matches nothing */
__u16 mode; /* in: CUBE_FLAG_ANY or CUBE_FLAG_ALL */
__u32 every_space; /* in: CUBE_SPACE_ONE or CUBE_SPACE_EVERY — see below */
__u64 cursor; /* in: 0 to start, or what the last call returned;
* out: what to pass next — see the end-of-scan rule above
*/
__u64 value; /* user pointer: where to put the records */
__u64 len; /* in: the buffer's capacity;
* out: bytes written, or on -ERANGE what would be needed
*/
};
#define CUBE_FLAG_ANY 0 /* the record shares at least one bit with the mask */
#define CUBE_FLAG_ALL 1 /* the record carries every bit of the mask */
/*
* The scan's scope. A space is a hard partition, so this is a choice between two different
* questions and never a filter that can be widened by accident:
*
* CUBE_SPACE_ONE the records of `space` — "this class here"
* CUBE_SPACE_EVERY the records of every space — "this class anywhere"
*
* It is a field and not a reserved space id because there is no such id to reserve: every 32-byte
* value is a legitimate space (root `0x00` and edge `0xFF…FF` are both in use), so a sentinel would
* be a space somebody could name. The field occupies what was padding, so `sizeof` is unchanged —
* which matters, because `size` is what says which argument block arrived and `cube_args` is
* exactly eight bytes wider. A caller that zeroes its block (and every caller does) gets
* CUBE_SPACE_ONE, which is the narrower question.
*/
#define CUBE_SPACE_ONE 0 /* scan only `space` */
#define CUBE_SPACE_EVERY 1 /* scan every space */
#endif /* _UAPI_LINUX_CUBE_H */