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.
This commit is contained in:
surface-camera-build
2026-09-22 02:20:31 -04:00
parent 1081b5f1e2
commit da0a19786e
2 changed files with 70 additions and 72 deletions
+55 -66
View File
@@ -2330,7 +2330,13 @@ impl<'a> SpaceWalker<'a> {
}
/// The next live record, or nothing when the space is walked out.
fn next(&mut self) -> Option<(&'a [u8; RAW_KEY_LEN], &'a [u8])> {
/// The next live record: `(key, its class, its value)`.
///
/// The class comes back because a listing has to be able to say what a record *is*. A packed
/// image's records carry no mask field, so they read as "no class" — but a record the log
/// supplied carries the mask its writer stamped, and dropping it here is what left a
/// kernel-backed store unable to answer `entries_flagged`.
fn next(&mut self) -> Option<(&'a [u8; RAW_KEY_LEN], u16, &'a [u8])> {
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.
@@ -2346,7 +2352,7 @@ impl<'a> SpaceWalker<'a> {
self.edit_at += 1;
self.image_next = next_in_space(&mut self.records, &self.wanted);
if !e.deleted {
return Some((e.key, e.value));
return Some((e.key, e.flags, e.value));
}
}
// A record only the log holds, in its key's place.
@@ -2354,19 +2360,20 @@ impl<'a> SpaceWalker<'a> {
self.edit_at += 1;
self.image_next = Some((key, value));
if !e.deleted {
return Some((e.key, e.value));
return Some((e.key, e.flags, e.value));
}
}
// The image's own record, which the log says nothing about.
// The image's own record, which the log says nothing about. A packed image has no
// mask field, so it answers none.
(_, Some((key, value))) => {
self.image_next = next_in_space(&mut self.records, &self.wanted);
return Some((key, value));
return Some((key, 0, value));
}
// The image is walked out; the rest of the log still has records to report.
(Some(e), None) => {
self.edit_at += 1;
if !e.deleted {
return Some((e.key, e.value));
return Some((e.key, e.flags, e.value));
}
}
(None, None) => return None,
@@ -2493,43 +2500,22 @@ struct Batch<'a> {
with_space: bool,
}
/// The fixed part of a flag-scan frame, before the value.
/// The fixed part of a walk's frame, before the value.
fn flagged_frame(with_space: bool) -> usize {
RAW_KEY_LEN + FLAGS_LEN + 4 + if with_space { SPACE_ID_LEN } else { 0 }
}
impl Batch<'_> {
/// Offer one record. `false` means nothing more will fit in this batch.
fn offer(&mut self, key: &[u8], value: &[u8]) -> bool {
self.seen += 1;
if self.seen <= self.cursor {
return true;
}
match pack_record(key, value, self.out, self.written) {
Some(n) => {
self.written += n;
self.returned += 1;
true
}
None => {
self.too_big = RAW_KEY_LEN + 4 + value.len();
false
}
}
}
/// Offer one *flagged* record. The counting rule is `offer`'s, and it is what makes the cursor
/// mean "matches already returned" rather than an index position: only a record that passed the
/// mask is counted, so a filtered walk resumes where it left off.
/// Offer one record, class and all. `false` means nothing more will fit in this batch.
///
/// One frame for every walk. It used to be two — `key | len | value` for the plain walks and
/// `key | flags | len | value` for the scan — which meant a caller could not ask a listing what
/// class its records were, and a *store* could not answer `entries_flagged` from a kernel store
/// at all. A walk that cannot say what a record is forces a second reader of the format to find
/// out, and that is the thing this whole arrangement exists to avoid.
///
/// `space` is carried only when `with_space` is set — see [`Batch::with_space`].
fn offer_flagged(
&mut self,
space: &[u8; SPACE_ID_LEN],
key: &[u8],
flags: u16,
value: &[u8],
) -> bool {
fn offer(&mut self, space: &[u8; SPACE_ID_LEN], key: &[u8], flags: u16, value: &[u8]) -> bool {
self.seen += 1;
if self.seen <= self.cursor {
return true;
@@ -3020,8 +3006,8 @@ unsafe fn v3_enum(
let mut entry = KVVec::<u8>::new();
let mut at = first + cursor;
while at < first + records {
let (value_off, value_len, _) = match image.index_entry(at, &mut entry) {
Ok(pair) => pair,
let (value_off, value_len, flags) = match image.index_entry(at, &mut entry) {
Ok(triple) => triple,
Err(e) => return -(e.to_errno() as i32),
};
let value = match image.value_at(value_off, value_len) {
@@ -3032,7 +3018,7 @@ unsafe fn v3_enum(
Ok(k) => k,
Err(_) => return -22,
};
if !batch.offer(key, value.as_slice()) {
if !batch.offer(&wanted, key, flags, value.as_slice()) {
break;
}
at += 1;
@@ -3061,38 +3047,32 @@ unsafe fn v3_enum(
None => None,
};
match (edit, image_entry, image_key) {
(Some(e), Some((_, value_len, _)), Some(key)) if e.key == key => {
let _ = value_len;
(Some(e), Some((_, _, _)), Some(key)) if e.key == key => {
edit_at += 1;
at += 1;
if !e.deleted && !batch.offer(e.key, e.value) {
if !e.deleted && !batch.offer(&wanted, e.key, e.flags, e.value) {
break;
}
}
(Some(e), Some((value_off, value_len, _)), Some(key)) if e.key < key => {
(Some(e), Some((_, _, _)), Some(key)) if e.key < key => {
edit_at += 1;
if !e.deleted && !batch.offer(e.key, e.value) {
if !e.deleted && !batch.offer(&wanted, e.key, e.flags, e.value) {
break;
}
let _ = (value_off, value_len);
}
(_, Some((value_off, value_len, _)), Some(_)) => {
(_, Some((value_off, value_len, flags)), Some(key)) => {
let value = match image.value_at(value_off, value_len) {
Ok(v) => v,
Err(e) => return -(e.to_errno() as i32),
};
let key = match entry.as_slice()[..RAW_KEY_LEN].try_into() {
Ok(k) => k,
Err(_) => return -22,
};
at += 1;
if !batch.offer(key, value.as_slice()) {
if !batch.offer(&wanted, key, flags, value.as_slice()) {
break;
}
}
(Some(e), None, _) => {
edit_at += 1;
if !e.deleted && !batch.offer(e.key, e.value) {
if !e.deleted && !batch.offer(&wanted, e.key, e.flags, e.value) {
break;
}
}
@@ -3235,31 +3215,40 @@ unsafe fn v3_range(
(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) {
if !e.deleted
&& region.holds(e.key)
&& !batch.offer(&wanted, e.key, e.flags, 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) {
if !e.deleted
&& region.holds(e.key)
&& !batch.offer(&wanted, e.key, e.flags, e.value)
{
break;
}
}
// The image's own record, which the log says nothing about.
(_, Some((value_off, value_len, _)), Some(key)) => {
(_, Some((value_off, value_len, flags)), 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()) {
if region.holds(key) && !batch.offer(&wanted, key, flags, value.as_slice()) {
break;
}
}
(Some(e), None, _) => {
edit_at += 1;
if !e.deleted && region.holds(e.key) && !batch.offer(e.key, e.value) {
if !e.deleted
&& region.holds(e.key)
&& !batch.offer(&wanted, e.key, e.flags, e.value)
{
break;
}
}
@@ -3390,7 +3379,7 @@ fn flag_scan_space(
at += 1;
if !e.deleted
&& flag_matches(e.flags, mask, mode)
&& !batch.offer_flagged(wanted, e.key, e.flags, e.value)
&& !batch.offer(wanted, e.key, e.flags, e.value)
{
return Ok(false);
}
@@ -3400,7 +3389,7 @@ fn flag_scan_space(
edit_at += 1;
if !e.deleted
&& flag_matches(e.flags, mask, mode)
&& !batch.offer_flagged(wanted, e.key, e.flags, e.value)
&& !batch.offer(wanted, e.key, e.flags, e.value)
{
return Ok(false);
}
@@ -3410,7 +3399,7 @@ fn flag_scan_space(
let value = image.value_at(value_off, value_len)?;
at += 1;
if flag_matches(flags, mask, mode)
&& !batch.offer_flagged(wanted, key, flags, value.as_slice())
&& !batch.offer(wanted, key, flags, value.as_slice())
{
return Ok(false);
}
@@ -3419,7 +3408,7 @@ fn flag_scan_space(
edit_at += 1;
if !e.deleted
&& flag_matches(e.flags, mask, mode)
&& !batch.offer_flagged(wanted, e.key, e.flags, e.value)
&& !batch.offer(wanted, e.key, e.flags, e.value)
{
return Ok(false);
}
@@ -3459,7 +3448,7 @@ fn flag_scan_log_space(
edit_at += 1;
if !e.deleted
&& flag_matches(e.flags, mask, mode)
&& !batch.offer_flagged(space, e.key, e.flags, e.value)
&& !batch.offer(space, e.key, e.flags, e.value)
{
return Ok(false);
}
@@ -3805,8 +3794,8 @@ pub unsafe extern "C" fn cubelinux_kernel_enum(
too_big: 0,
with_space: false,
};
while let Some((key, value)) = walker.next() {
if !batch.offer(key, value) {
while let Some((key, flags, value)) = walker.next() {
if !batch.offer(&wanted, key, flags, value) {
break;
}
}
@@ -3908,7 +3897,7 @@ pub unsafe extern "C" fn cubelinux_kernel_range(
too_big: 0,
with_space: false,
};
while let Some((key, value)) = walker.next() {
while let Some((key, flags, 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.
@@ -3917,7 +3906,7 @@ pub unsafe extern "C" fn cubelinux_kernel_range(
Span::Above => break,
Span::Inside => {}
}
if region.holds(key) && !batch.offer(key, value) {
if region.holds(key) && !batch.offer(&wanted, key, flags, value) {
break;
}
}