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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user