cube(2): CUBE_OP_FLAG_SCAN — classify at write, retrieve by class
The store's flag field is a substrate, and this is its read half: a v4 index entry carries a 16-bit class mask, and a scan by class is a walk that reads the mask and tests it. Nothing about the merge changes — the same log overlay, the same order — so a caller pays for its own class rather than for the store. The op takes a space, a mask, a mode (any/all), a cursor and a buffer, in its own size-versioned block. A mask of zero matches nothing, because naming no class is asking no question. Records come back as key | flags(2) | value_len | value: the mask travels, since a record can carry bits the scan did not name and no other op returns a mask. Both layouts the mask can be in are read. With the record still in the log it comes from the v2 log entry; after a fold it comes from the v4 index entry. The non-indexed path is not a corner — it is the state of every store between the write that classified something and the fold, so answering it with 'nothing' would make the substrate work only after a checkpoint. Also settles what an append writes into which log, since the entry's frame has to match the header a reader frames it by: a log that already holds v1 entries keeps taking v1 entries (a device folds first — that is the v4 migration — and a bare image keeps the log it has), an empty log is framed v2 on a device and left alone on a bare image, and a log with no header is framed v2 on a device and v1 on a bare image. The bare layout is the legacy one: it has no index for a mask to be folded into, so a mask written there could only be scanned and never checkpointed — half a feature, bought by making every existing reader of that layout grow a version it cannot use.
This commit is contained in:
@@ -56,6 +56,15 @@ int cubelinux_kernel_range(const __u8 *space,
|
||||
__u64 cursor, void *buf, size_t cap,
|
||||
__u64 *out_len, __u64 *out_cursor);
|
||||
|
||||
/*
|
||||
* The flag scan (CUBE_OP_FLAG_SCAN). The mask and its mode travel as plain numbers: which bits mean
|
||||
* what is a vocabulary's business, and the kernel never interprets one — it compares masks, which is
|
||||
* what lets a new vocabulary attach without a format change.
|
||||
*/
|
||||
int cubelinux_kernel_flag_scan(const __u8 *space, __u16 mask, __u16 mode,
|
||||
__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);
|
||||
|
||||
@@ -361,7 +370,60 @@ static long cube_range_op(void __user *uargs)
|
||||
}
|
||||
|
||||
/*
|
||||
* One syscall, three argument blocks. They share a prefix — `size`, then `op` — so the size the
|
||||
* The flag scan — CUBE_OP_FLAG_SCAN — which travels in `struct cube_flag_scan_args`.
|
||||
*
|
||||
* The walks' shape again, because it is the walks' contract: 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.
|
||||
*
|
||||
* The one thing a caller must know beyond the walk's rules: the cursor counts the records that
|
||||
* **matched**, not the records examined, because the records the mask rejected are not answers.
|
||||
*/
|
||||
static long cube_flag_scan_op(void __user *uargs)
|
||||
{
|
||||
struct cube_flag_scan_args f;
|
||||
void *buf = NULL;
|
||||
long ret = 0;
|
||||
u64 out_len = 0, out_cursor = 0;
|
||||
|
||||
if (copy_from_user(&f, uargs, sizeof(f)))
|
||||
return -EFAULT;
|
||||
if (f.size != sizeof(struct cube_flag_scan_args) || f.op != CUBE_OP_FLAG_SCAN)
|
||||
return -EINVAL;
|
||||
if (f.mode != CUBE_FLAG_ANY && f.mode != CUBE_FLAG_ALL)
|
||||
return -EINVAL;
|
||||
|
||||
if (f.len > CUBE_MAX_WALK)
|
||||
return -E2BIG;
|
||||
if (f.len > 0) {
|
||||
buf = kvmalloc(f.len, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = cubelinux_kernel_flag_scan(f.space, f.mask, f.mode, f.cursor,
|
||||
buf, f.len, &out_len, &out_cursor);
|
||||
if (ret == 0) {
|
||||
if (out_len > 0 && copy_to_user((void __user *)f.value, buf, out_len))
|
||||
ret = -EFAULT;
|
||||
f.len = out_len;
|
||||
f.cursor = out_cursor;
|
||||
if (copy_to_user(uargs, &f, sizeof(f)))
|
||||
ret = -EFAULT;
|
||||
} else if (ret == -ERANGE) {
|
||||
/* Nothing was written; `len` now says how much one record needs. */
|
||||
f.len = out_len;
|
||||
if (copy_to_user(uargs, &f, sizeof(f)))
|
||||
ret = -EFAULT;
|
||||
}
|
||||
|
||||
kvfree(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* One syscall, four 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.
|
||||
@@ -378,5 +440,7 @@ SYSCALL_DEFINE2(cube, unsigned int, op, void __user *, uargs)
|
||||
return cube_enum_op(op, uargs);
|
||||
if (size == sizeof(struct cube_range_args))
|
||||
return cube_range_op(uargs);
|
||||
if (size == sizeof(struct cube_flag_scan_args))
|
||||
return cube_flag_scan_op(uargs);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user