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
+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;
}