cubelinux: CUBE_OP_RANGE — a region walk that seeks on the box's key span

The seventh operation: `cube(2)` gains CUBE_OP_RANGE, a bounded walk of one
space's records that lie in a box. Its argument block is its own (`cube_range_args`,
versioned by `size` like the walk's), and the box travels as its six numbers for
the reason a coordinate does — the key it has to become is the driver's business.

The operation rests on the span that the shared file already owns. `key_span(lo, hi)`
bounds every key in the box because the interleave is monotone on each axis, so a
v3 image is **sought**: the fixed-stride index is binary-searched for the span's foot
(`Addressed::lower_bound`) and read forward to its head, merging the log's edits
exactly as a walk does. A packed v1/v2 image has no index to search, so its space is
walked with the same span used only to stop early — and the contract is the same
either way, so a caller is not told which path it got.

The trap that shaped the code, and the reason it is written the way it is: **the span
is a bound, not the set.** Keys of points outside the box fall inside it (Z-order
amplification), so every candidate is decoded and tested against the box before it
is returned — which is what `morton_decode`, the interleave's inverse, is for, now in
the shared file with the same kind of hand-pinned tests the interleave has. And the
cursor counts the records *in the box*, not the records of the space, because those
are the records the walk returns.

The over-coverage — records examined versus records returned — is the number this
operation is meant to publish, and it is not wired to the caller yet: the cost gate
measures it by comparing against the userspace store, which counts the same thing.
This commit is contained in:
surface-camera-build
2026-09-21 19:44:48 -04:00
parent 72a6bbe173
commit 88ce1bf2bf
4 changed files with 515 additions and 5 deletions
+22 -2
View File
@@ -468,9 +468,29 @@ pub fn morton_key(x: u64, y: u64, z: u64) -> [u8; RAW_KEY_LEN] {
k
}
/// The point a key encodes — the inverse of [`morton_key`].
///
/// Bit `n` of the key is bit `n / 3` of axis `n % 3`, which is the interleave read backwards.
///
/// Why the kernel needs it: a region walk is handed a **key span**, not a set of keys. A span is a
/// bound, not the set — keys of points *outside* the box also fall inside it (Z-order
/// amplification) — so every candidate found in the span has to be turned back into a point and
/// tested for membership. Without this, the walk would return records that are outside the region
/// the caller asked for, which is exactly the mistake the span's own doc warns about.
pub fn morton_decode(k: &[u8; RAW_KEY_LEN]) -> [u64; 3] {
let mut out = [0u64; 3];
let mut n = 0;
while n < RAW_KEY_LEN * 8 {
if k[RAW_KEY_LEN - 1 - (n / 8)] & (1 << (n % 8)) != 0 {
out[n % 3] |= 1u64 << (n / 3);
}
n += 1;
}
out
}
/// Compare two keys as the 192-bit numbers they are. `a < b` means `a` sorts first.
pub fn key_cmp(a: &[u8; RAW_KEY_LEN], b: &[u8; RAW_KEY_LEN]) -> core::cmp::Ordering {
let mut i = 0;
pub fn key_cmp(a: &[u8; RAW_KEY_LEN], b: &[u8; RAW_KEY_LEN]) -> core::cmp::Ordering { let mut i = 0;
while i < RAW_KEY_LEN {
if a[i] != b[i] {
return if a[i] < b[i] {
+81 -1
View File
@@ -45,6 +45,17 @@ int cubelinux_kernel_enum(const __u8 *space, __u64 cursor, void *buf, size_t cap
__u64 *out_len, __u64 *out_cursor);
int cubelinux_kernel_spaces(__u64 cursor, __u8 *space_out);
/*
* The region walk (CUBE_OP_RANGE). The box travels as its six numbers for the same reason the
* coordinate does: the format knowledge stays on the Rust side, which owns the key the box has to
* become.
*/
int cubelinux_kernel_range(const __u8 *space,
__u64 lo_x, __u64 lo_y, __u64 lo_z,
__u64 hi_x, __u64 hi_y, __u64 hi_z,
__u64 cursor, void *buf, size_t cap,
__u64 *out_len, __u64 *out_cursor);
/* The store device path, resolved from the `cube_store=` boot parameter at boot. */
const char *cubelinux_store_device(void);
@@ -283,7 +294,74 @@ static long cube_enum_op(unsigned int op, void __user *uargs)
}
/*
* One syscall, two argument blocks. They share a prefix `size`, then `op` so the size the
* The region walk CUBE_OP_RANGE which travels in `struct cube_range_args`.
*
* Deliberately the same shape as the space walk above, because it is the same contract with one
* more input: a batch fills the caller's buffer and returns how much was used plus the cursor to
* pass next; a record that does not fit ends the batch; a record that cannot fit in any buffer the
* caller offered comes back as -ERANGE with `len` saying what it would need. Nobody guesses a size
* and nobody gets half a record.
*
* The two things a caller must know beyond the walk's rules: the cursor counts the records **in the
* box** rather than the records of the space (those are the records being returned), and the kernel
* may *examine* more records than it returns, because it seeks on the box's key span and the span
* is a bound rather than the set. That over-coverage is the honest cost of the seek, not a defect.
*/
static long cube_range_op(void __user *uargs)
{
struct cube_range_args r;
void *buf = NULL;
long ret = 0;
u64 out_len = 0, out_cursor = 0;
if (copy_from_user(&r, uargs, sizeof(r)))
return -EFAULT;
if (r.size != sizeof(struct cube_range_args) || r.op != CUBE_OP_RANGE)
return -EINVAL;
/*
* An inverted box is empty, not an error: there is nothing in it, and saying so is the honest
* answer. Answering it here also keeps the empty case away from the seek, where an inverted
* span would be a range whose start is above its end.
*/
if (r.lo[0] > r.hi[0] || r.lo[1] > r.hi[1] || r.lo[2] > r.hi[2]) {
r.len = 0;
if (copy_to_user(uargs, &r, sizeof(r)))
return -EFAULT;
return 0;
}
if (r.len > CUBE_MAX_WALK)
return -E2BIG;
if (r.len > 0) {
buf = kvmalloc(r.len, GFP_KERNEL);
if (!buf)
return -ENOMEM;
}
ret = cubelinux_kernel_range(r.space, r.lo[0], r.lo[1], r.lo[2],
r.hi[0], r.hi[1], r.hi[2],
r.cursor, buf, r.len, &out_len, &out_cursor);
if (ret == 0) {
if (out_len > 0 && copy_to_user((void __user *)r.value, buf, out_len))
ret = -EFAULT;
r.len = out_len;
r.cursor = out_cursor;
if (copy_to_user(uargs, &r, sizeof(r)))
ret = -EFAULT;
} else if (ret == -ERANGE) {
/* Nothing was written; `len` now says how much one record needs. */
r.len = out_len;
if (copy_to_user(uargs, &r, sizeof(r)))
ret = -EFAULT;
}
kvfree(buf);
return ret;
}
/*
* One syscall, three argument blocks. They share a prefix `size`, then `op` so the size the
* caller declares is what says which one arrived. That is the whole point of putting `size`
* first: an interface that cannot grow has to be replaced, and this one grows by being given a
* new block with a new size.
@@ -298,5 +376,7 @@ SYSCALL_DEFINE2(cube, unsigned int, op, void __user *, uargs)
return cube_args_op(op, uargs);
if (size == sizeof(struct cube_enum_args))
return cube_enum_op(op, uargs);
if (size == sizeof(struct cube_range_args))
return cube_range_op(uargs);
return -EINVAL;
}
+373 -2
View File
@@ -2298,6 +2298,81 @@ impl Batch<'_> {
}
}
// ── The region walk: a box, its key span, and what decides membership ────────────────────
/// The inclusive box a region walk answers, and the key span that bounds it.
///
/// The span is the whole mechanism of `CUBE_OP_RANGE`: [`cube_format::key_span`] gives the two
/// corner keys, and **every** key in the box lies between them because the interleave is monotone on
/// each axis. So a sorted index can be binary-searched for the foot and read forward to the head.
///
/// It is a **bound and not the set**: a point *outside* the box can have a key inside the span (the
/// classic Z-order amplification), so [`holds`](Self::holds) is what decides and a caller must ask
/// it about every candidate. Confusing the bound for the set is the mistake that would make this
/// return records from outside the region the caller named — and there is no decomposition here to
/// blame it on, which is why the pair is checked in `cube-format`'s tests.
struct Region {
lo: [u64; 3],
hi: [u64; 3],
foot: [u8; RAW_KEY_LEN],
head: [u8; RAW_KEY_LEN],
}
impl Region {
/// `None` for an empty box — inverted on some axis. An empty box has no span worth building,
/// and building one would produce a range whose start is above its end.
fn new(lo: [u64; 3], hi: [u64; 3]) -> Option<Self> {
let mut i = 0;
while i < 3 {
if lo[i] > hi[i] {
return None;
}
i += 1;
}
let (foot, head) = cube_format::key_span(lo, hi);
Some(Region { lo, hi, foot, head })
}
/// Whether a key's point lies in the box — the test that keeps a bound from becoming a wrong
/// answer. This is why `morton_decode` exists.
fn holds(&self, key: &[u8; RAW_KEY_LEN]) -> bool {
let p = cube_format::morton_decode(key);
p[0] >= self.lo[0]
&& p[0] <= self.hi[0]
&& p[1] >= self.lo[1]
&& p[1] <= self.hi[1]
&& p[2] >= self.lo[2]
&& p[2] <= self.hi[2]
}
/// Whether a key sorts below the span, inside it, or above it.
///
/// A dedicated three-way rather than an `Ordering`, because `Ordering` has no room for it: a key
/// between the foot and the head is `Less` *than the head* and that is exactly the case a walk
/// must keep. Returning `key_cmp(key, head)` and matching on it conflates "inside the span" with
/// "below the foot", which silently skips every record in the region the caller asked for —
/// a mistake this code made for one edit, caught before it built.
fn place(&self, key: &[u8; RAW_KEY_LEN]) -> Span {
if cube_format::key_cmp(key, &self.foot) == core::cmp::Ordering::Less {
Span::Below
} else if cube_format::key_cmp(key, &self.head) == core::cmp::Ordering::Greater {
Span::Above
} else {
Span::Inside
}
}
}
/// Where a key sits relative to a region's key span. See [`Region::place`].
enum Span {
/// Sorts before the span's foot: not in the box, and skippable on a layout with no index.
Below,
/// Sorts within the span: a candidate, which still has to pass [`Region::holds`].
Inside,
/// Sorts past the span's head: the walk is finished, because the records are key-ordered.
Above,
}
// ── v3: the addressed reader ────────────────────────────────────────────────────────────
//
// A v3 image is read at the offsets the header describes, so an operation reads the bytes it names
@@ -2437,9 +2512,37 @@ impl Addressed {
Ok(None)
}
/// The first index position at or **after** `key`, within a space's index range.
///
/// This is `find` answering the other question. `find` asks "where is this exact key" and is only
/// useful for a coordinate read; a region walk asks "where does this span begin", which is the
/// same binary search over the same arithmetic addresses with `>=` in place of `==`. The walk
/// then reads forward from here and stops when it passes the span's head, so the cost is the
/// span rather than the space — and it is the reason the span needed no decomposition.
fn lower_bound(
&mut self,
key: &[u8; RAW_KEY_LEN],
first: u64,
records: u64,
) -> Result<u64> {
let mut buf = KVVec::<u8>::new();
let mut lo = 0u64;
let mut hi = records;
while lo < hi {
let mid = lo + (hi - lo) / 2;
let off = self.header.index_off + (first + mid) * INDEX_ENTRY as u64;
self.read_image_at(off, INDEX_ENTRY, &mut buf)?;
if &buf.as_slice()[..RAW_KEY_LEN] < &key[..] {
lo = mid + 1;
} else {
hi = mid;
}
}
Ok(first + lo)
}
/// The value of an index entry, read from where the index says it is.
fn value_at(&mut self, value_off: u64, value_len: u64) -> Result<KVVec<u8>> {
let mut out = KVVec::<u8>::new();
fn value_at(&mut self, value_off: u64, value_len: u64) -> Result<KVVec<u8>> { let mut out = KVVec::<u8>::new();
self.read_image_at(value_off, value_len as usize, &mut out)?;
Ok(out)
}
@@ -2711,6 +2814,166 @@ unsafe fn v3_enum(
}
0
}
/// `CUBE_OP_RANGE` against a v3 image: **seek** the index to the span's foot, then read forward.
///
/// This is `v3_enum` with two differences, and both are the operation's whole point:
///
/// * it starts at the **span's foot** rather than at the space's first record, found by a binary
/// search over the fixed stride, so the cost is the span and not the space;
/// * it decides with [`Region::holds`] rather than returning everything, and stops the moment the
/// index walks past the span's head, because the index is sorted by key.
///
/// The log's edits interleave exactly as they do in a walk, so this is the same merge — with one
/// added step, which is the log's half of the seek: an edit sorting below the foot cannot be in the
/// box, because the span bounds every key in it.
///
/// Records **examined** can exceed records **returned**, and that excess is the honest cost of a
/// bound: the span contains keys of points outside the box, and `holds` rejects them.
///
/// # Safety
/// `buf` must hold `cap` writable bytes; `out_len` and `out_cursor` must point to writable `u64`s.
unsafe fn v3_range(
mut image: Addressed,
wanted: [u8; SPACE_ID_LEN],
region: Region,
cursor: u64,
buf: *mut u8,
cap: usize,
out_len: *mut u64,
out_cursor: *mut u64,
) -> i32 {
let (first, records) = match image.space_entry(&wanted) {
Ok(Some(entry)) => entry,
Ok(None) => (0, 0),
Err(e) => return -(e.to_errno() as i32),
};
// Own the log: the edits borrow it, and walking the image needs `&mut image`.
let mut log = KVVec::<u8>::new();
if log.extend_from_slice(image.log(), GFP_KERNEL).is_err() {
return -12; // -ENOMEM
}
let mut edits = KVVec::<LogEdit<'_>>::new();
if log_edits(log.as_slice(), &wanted, &mut edits).is_err() {
return -12;
}
// The log's half of the seek.
let mut edit_at = 0usize;
while edit_at < edits.len()
&& cube_format::key_cmp(edits.as_slice()[edit_at].key, &region.foot)
== core::cmp::Ordering::Less
{
edit_at += 1;
}
// The index's half.
let mut at = match image.lower_bound(&region.foot, first, records) {
Ok(p) => p,
Err(e) => return -(e.to_errno() as i32),
};
// SAFETY: the shim guarantees `cap` writable bytes at `buf`.
let out = unsafe { core::slice::from_raw_parts_mut(buf, cap) };
let mut batch = Batch {
out,
written: 0,
returned: 0,
seen: cursor,
cursor,
too_big: 0,
};
let mut entry = KVVec::<u8>::new();
loop {
// The entries of one key arrive in log order, so the last of a key's group is the newest
// word on it and the earlier ones are history.
while edit_at + 1 < edits.len()
&& edits.as_slice()[edit_at + 1].key == edits.as_slice()[edit_at].key
{
edit_at += 1;
}
let edit = edits.as_slice().get(edit_at).copied();
let image_entry = if at < first + records {
match image.index_entry(at, &mut entry) {
Ok(pair) => Some(pair),
Err(e) => return -(e.to_errno() as i32),
}
} else {
None
};
let image_key: Option<&[u8; RAW_KEY_LEN]> = match image_entry {
Some(_) => entry.as_slice()[..RAW_KEY_LEN].try_into().ok(),
None => None,
};
// The smallest key still ahead of either stream. If it is past the head, the walk is done:
// the index is sorted by key, so nothing further can be inside the span. This is what makes
// the seek stop at the span instead of reading to the end of the space.
let next_key = match (edit.map(|e| e.key), image_key) {
(Some(e), Some(k)) => {
if e < k {
e
} else {
k
}
}
(Some(e), None) => e,
(None, Some(k)) => k,
(None, None) => break,
};
if cube_format::key_cmp(next_key, &region.head) == core::cmp::Ordering::Greater {
break;
}
match (edit, image_entry, image_key) {
// The log's word on a coordinate the image holds: it wins.
(Some(e), Some(_), Some(key)) if e.key == key => {
edit_at += 1;
at += 1;
if !e.deleted && region.holds(e.key) && !batch.offer(e.key, e.value) {
break;
}
}
// A record only the log holds, in its key's place.
(Some(e), Some(_), Some(key)) if e.key < key => {
edit_at += 1;
if !e.deleted && region.holds(e.key) && !batch.offer(e.key, e.value) {
break;
}
}
// The image's own record, which the log says nothing about.
(_, Some((value_off, value_len)), Some(key)) => {
let value = match image.value_at(value_off, value_len) {
Ok(v) => v,
Err(e) => return -(e.to_errno() as i32),
};
at += 1;
if region.holds(key) && !batch.offer(key, value.as_slice()) {
break;
}
}
(Some(e), None, _) => {
edit_at += 1;
if !e.deleted && region.holds(e.key) && !batch.offer(e.key, e.value) {
break;
}
}
(None, None, _) => break,
_ => break,
}
}
// SAFETY: both out-pointers are writable under this function's contract.
unsafe {
if batch.too_big > 0 && batch.written == 0 {
*out_len = batch.too_big as u64;
*out_cursor = cursor;
return -34; // -ERANGE
}
*out_len = batch.written as u64;
*out_cursor = cursor + batch.returned;
}
0
}
///
/// Returns the record's length, or a negative errno. When the value does not fit in `len` the
/// bytes are *not* copied and the length is returned anyway, so the caller can size its buffer
@@ -2867,6 +3130,114 @@ pub unsafe extern "C" fn cubelinux_kernel_enum(
0
}
/// `CUBE_OP_RANGE`: the live records of one space whose point lies in the caller's box.
///
/// The cursor means what it means for `CUBE_OP_ENUM` — a **count of records already returned**, so it
/// is opaque, survives an append, and is the only end-of-walk signal — with one difference a caller
/// must know: it counts the records **in the box**, because those are the records this returns.
///
/// The box travels as its six numbers rather than as a struct, for the reason a coordinate does: the
/// key the box has to become is this side's business.
///
/// Two paths, and the difference between them is the honest difference between the layouts. A **v3**
/// image is addressed, so this **seeks**: the box's corner keys bound every key in it, the index is
/// sorted, and a binary search for the span's foot followed by a forward read to its head costs the
/// span rather than the space. A **v1/v2** image is a packed list with no index, so there is nothing
/// to search and the space is walked — the span still stops the walk early, but the cost is the walk.
/// Neither path tells the caller which it got, because the contract is the same either way.
///
/// # Safety
/// `space` must point to 32 readable bytes; `buf` must hold `cap` writable bytes; `out_len` and
/// `out_cursor` must each point to a writable `u64`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn cubelinux_kernel_range(
space: *const u8,
lo_x: u64,
lo_y: u64,
lo_z: u64,
hi_x: u64,
hi_y: u64,
hi_z: u64,
cursor: u64,
buf: *mut u8,
cap: usize,
out_len: *mut u64,
out_cursor: *mut u64,
) -> i32 {
let mut wanted = [0u8; SPACE_ID_LEN];
// SAFETY: the caller guarantees 32 readable bytes at `space`.
unsafe { core::ptr::copy_nonoverlapping(space, wanted.as_mut_ptr(), SPACE_ID_LEN) };
let region = match Region::new([lo_x, lo_y, lo_z], [hi_x, hi_y, hi_z]) {
Some(region) => region,
// An empty box. Nothing is in it, so the answer is nothing — and answering here keeps an
// inverted span away from the seek, where it would be a range that starts above its end.
// The shim answers this too; a second caller must not be able to reach the seek without it.
None => {
// SAFETY: both out-pointers are writable under this function's contract.
unsafe {
*out_len = 0;
*out_cursor = cursor;
}
return 0;
}
};
match Addressed::open() {
Ok(Some(image)) => {
return unsafe { v3_range(image, wanted, region, cursor, buf, cap, out_len, out_cursor) }
}
Ok(None) => {}
Err(e) => return -(e.to_errno() as i32),
}
let view = match read_view() {
Ok(v) => v,
Err(e) => return -(e.to_errno() as i32),
};
let mut walker = match SpaceWalker::new(&view, &wanted) {
Ok(w) => w,
Err(_) => return -12, // -ENOMEM
};
// SAFETY: the shim guarantees `cap` writable bytes at `buf`.
let out = unsafe { core::slice::from_raw_parts_mut(buf, cap) };
let mut batch = Batch {
out,
written: 0,
returned: 0,
seen: cursor,
cursor,
too_big: 0,
};
while let Some((key, value)) = walker.next() {
match region.place(key) {
// Below the span: not in the box, and skipping it is the seek's substitute on a layout
// that has no index to search.
Span::Below => continue,
// Above the span: the walk is sorted by key, so nothing further can be in the box.
Span::Above => break,
Span::Inside => {}
}
if region.holds(key) && !batch.offer(key, value) {
break;
}
}
// SAFETY: both out-pointers are writable under this function's contract.
unsafe {
if batch.too_big > 0 && batch.written == 0 {
// Not one whole record fits. Say how much it needs, the way a read does.
*out_len = batch.too_big as u64;
*out_cursor = cursor;
return -34; // -ERANGE
}
*out_len = batch.written as u64;
*out_cursor = cursor + batch.returned;
}
0
}
/// `CUBE_OP_SPACES`: the `cursor`-th distinct space that holds a record, or -ENOENT at the end.
///
/// An index rather than a count of records: a caller that wants a space's records walks it with
+39
View File
@@ -42,6 +42,7 @@ struct cube_args {
#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 */
/*
* The walk's argument block: its own block rather than a wider `cube_args`, because it needs a
@@ -72,4 +73,42 @@ struct cube_enum_args {
*/
};
/*
* 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) | 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.
*
* 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
*/
};
#endif /* _UAPI_LINUX_CUBE_H */