diff --git a/drivers/cube/cube_syscall.c b/drivers/cube/cube_syscall.c index b2d2444ed..335cfe413 100644 --- a/drivers/cube/cube_syscall.c +++ b/drivers/cube/cube_syscall.c @@ -62,7 +62,7 @@ int cubelinux_kernel_range(const __u8 *space, * 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, + __u32 every_space, __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. */ @@ -393,6 +393,8 @@ static long cube_flag_scan_op(void __user *uargs) return -EINVAL; if (f.mode != CUBE_FLAG_ANY && f.mode != CUBE_FLAG_ALL) return -EINVAL; + if (f.every_space != CUBE_SPACE_ONE && f.every_space != CUBE_SPACE_EVERY) + return -EINVAL; if (f.len > CUBE_MAX_WALK) return -E2BIG; @@ -402,8 +404,8 @@ static long cube_flag_scan_op(void __user *uargs) return -ENOMEM; } - ret = cubelinux_kernel_flag_scan(f.space, f.mask, f.mode, f.cursor, - buf, f.len, &out_len, &out_cursor); + ret = cubelinux_kernel_flag_scan(f.space, f.mask, f.mode, f.every_space, + 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; diff --git a/drivers/cube/cubelinux_store.rs b/drivers/cube/cubelinux_store.rs index 5966ece5f..491146c46 100644 --- a/drivers/cube/cubelinux_store.rs +++ b/drivers/cube/cubelinux_store.rs @@ -2466,6 +2466,19 @@ struct Batch<'a> { cursor: u64, /// Set when a record did not fit: the size it needs, for the `-ERANGE` answer. too_big: usize, + /// Whether each frame carries its own space. + /// + /// A walk's frame leaves the space out because the caller named it — repeating 32 bytes per + /// record to say what the caller already said is a tax on every listing. A scan that names *no* + /// space cannot do that: a coordinate is meaningless without its space, so an every-space + /// answer has to say which space each record came from or the answer is unusable. Hence a frame + /// that depends on the scope, which is the scope's business and not the record's. + with_space: bool, +} + +/// The fixed part of a flag-scan 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<'_> { @@ -2491,19 +2504,27 @@ impl Batch<'_> { /// 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. - fn offer_flagged(&mut self, key: &[u8], flags: u16, value: &[u8]) -> bool { + /// + /// `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 { self.seen += 1; if self.seen <= self.cursor { return true; } - match pack_flagged(key, flags, value, self.out, self.written) { + match pack_flagged(space, key, flags, value, self.out, self.written, self.with_space) { Some(n) => { self.written += n; self.returned += 1; true } None => { - self.too_big = RAW_KEY_LEN + FLAGS_LEN + 4 + value.len(); + self.too_big = flagged_frame(self.with_space) + value.len(); false } } @@ -2686,6 +2707,30 @@ impl Addressed { Ok(None) } + /// One space-table row by its position: the space, where its records start, and how many. + /// + /// [`space_entry`](Self::space_entry) answers "where is *this* space" by binary search, which is + /// what a caller who named a space wants. A caller who named none has to visit every space in + /// the table's own order — which is sorted, so the rows come back in the order a checkpoint + /// wrote them and an every-space walk answers in (space, key) order for free. + fn space_row(&mut self, row: u64) -> Result> { + if row >= self.header.space_count { + return Ok(None); + } + let mut buf = KVVec::::new(); + self.read_image_at(HEADER_LEN_V3 as u64 + row * SPACE_ENTRY as u64, SPACE_ENTRY, &mut buf)?; + let entry = buf.as_slice(); + let space: [u8; SPACE_ID_LEN] = match entry[..SPACE_ID_LEN].try_into() { + Ok(s) => s, + Err(_) => return Err(EINVAL), + }; + let mut w = [0u8; 8]; + w.copy_from_slice(&entry[SPACE_ID_LEN..SPACE_ID_LEN + 8]); + let first = u64::from_le_bytes(w); + w.copy_from_slice(&entry[SPACE_ID_LEN + 8..SPACE_ENTRY]); + Ok(Some((space, first, u64::from_le_bytes(w)))) + } + /// One index entry, by its position in the index: `(value_off, value_len, flags)`. /// /// The class mask comes back with the address because it is in the same stride and costs @@ -2927,6 +2972,7 @@ unsafe fn v3_enum( seen: cursor, cursor, too_big: 0, + with_space: false, }; if edits.is_empty() { @@ -3100,6 +3146,7 @@ unsafe fn v3_range( seen: 0, cursor, too_big: 0, + with_space: false, }; let mut entry = KVVec::::new(); @@ -3207,6 +3254,10 @@ const FLAG_MODE_ANY: u16 = 0; /// A record matches when it carries *every* bit of the scan mask: "every Wi-Fi error". const FLAG_MODE_ALL: u16 = 1; +/// The scan's scope: one named space, or every space. Two questions, not one widened. +const FLAG_SCOPE_ONE: u32 = 0; +const FLAG_SCOPE_EVERY: u32 = 1; + /// Whether a record's class mask answers a scan for `mask` under `mode`. /// /// A mask of zero matches nothing. Asking "what is this" with no class named is asking no question, @@ -3224,15 +3275,177 @@ fn flag_matches(flags: u16, mask: u16, mode: u16) -> bool { } } -/// `CUBE_OP_FLAG_SCAN` against a v3/v4 image: the live records of one space whose mask matches. +/// The answer a finished batch gives: how much was written and what cursor to pass next, or +/// `-ERANGE` with the size the record that did not fit would need. +/// +/// # Safety +/// `out_len` and `out_cursor` must point to writable `u64`s. +unsafe fn finish_batch( + batch: &Batch<'_>, + cursor: u64, + out_len: *mut u64, + out_cursor: *mut u64, +) -> i32 { + // 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 +} + +/// Scan one space's live records into `batch`, in key order, offering only those whose mask matches. /// /// This is [`v3_enum`]'s merge with one difference, and it is the operation's whole point: a record /// is offered only when its class mask answers the scan, so the cursor counts **matches** rather -/// than records walked. That is why this re-walks from the space's first record on every batch (as +/// than records walked. That is why a scan re-walks from the space's first record on every batch (as /// the region walk does) instead of starting at `first + cursor` the way the plain walk can: the /// index position of the cursor-th match is not arithmetic, because the records before it are not /// all matches. /// +/// Returns `false` when `batch` is full, so a caller walking several spaces can stop — which is what +/// the every-space scope needs: one batch, several spaces, and one cursor counting matches across +/// all of them. +fn flag_scan_space( + image: &mut Addressed, + log: &[u8], + wanted: &[u8; SPACE_ID_LEN], + first: u64, + records: u64, + mask: u16, + mode: u16, + batch: &mut Batch<'_>, +) -> Result { + let mut edits = KVVec::>::new(); + log_edits(log, wanted, &mut edits)?; + + let mut entry = KVVec::::new(); + let mut at = first; + let mut edit_at = 0usize; + 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 newest word carries the mask that counts. + while edit_at + 1 < edits.len() && edits[edit_at + 1].key == edits[edit_at].key { + edit_at += 1; + } + let edit = edits.as_slice().get(edit_at).copied(); + let image_entry = if at < first + records { + Some(image.index_entry(at, &mut entry)?) + } 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, + }; + + match (edit, image_entry, image_key) { + // The log's word on a coordinate the image holds: it wins, mask and all. + (Some(e), Some(_), Some(key)) if e.key == key => { + edit_at += 1; + at += 1; + if !e.deleted + && flag_matches(e.flags, mask, mode) + && !batch.offer_flagged(wanted, e.key, e.flags, e.value) + { + return Ok(false); + } + } + // 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 + && flag_matches(e.flags, mask, mode) + && !batch.offer_flagged(wanted, e.key, e.flags, e.value) + { + return Ok(false); + } + } + // The image's own record, which the log says nothing about. + (_, Some((value_off, value_len, flags)), Some(key)) => { + 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()) + { + return Ok(false); + } + } + (Some(e), None, _) => { + edit_at += 1; + if !e.deleted + && flag_matches(e.flags, mask, mode) + && !batch.offer_flagged(wanted, e.key, e.flags, e.value) + { + return Ok(false); + } + } + // One side ran out and the other has nothing left that could interleave: this space is + // done, not the batch. + _ => return Ok(true), + } + } +} + +/// Offer the log's live word on each coordinate it holds for one space. +/// +/// This is the whole answer for a store whose image has no index: a packed record carries no mask +/// field, so none of the image's records can answer a scan and only the log's can. That is not a +/// corner — it is the state of every store between the write that classified something and the fold +/// that moves the mask into the index. +/// +/// Returns `false` when `batch` is full. +fn flag_scan_log_space( + log: &[u8], + space: &[u8; SPACE_ID_LEN], + mask: u16, + mode: u16, + batch: &mut Batch<'_>, +) -> Result { + let mut edits = KVVec::>::new(); + log_edits(log, space, &mut edits)?; + let mut edit_at = 0usize; + while edit_at < edits.len() { + // The entries of one key arrive in log order, so the last of a key's group is the newest + // word on it — and only the newest can match, because it is what a read would return. + while edit_at + 1 < edits.len() && edits[edit_at + 1].key == edits[edit_at].key { + edit_at += 1; + } + let e = edits.as_slice()[edit_at]; + edit_at += 1; + if !e.deleted + && flag_matches(e.flags, mask, mode) + && !batch.offer_flagged(space, e.key, e.flags, e.value) + { + return Ok(false); + } + } + Ok(true) +} + +/// A batch ready to receive a flag scan's matches. `seen` starts at 0 for the reason the region +/// walk's does: this path re-walks from the beginning on every batch, so `offer_flagged` is the only +/// thing that counts. Starting it at `cursor` would skip the first `cursor` matches of a fresh walk +/// *and* still count from there, re-serving the first batch forever. +fn flag_batch<'a>(out: &'a mut [u8], cursor: u64, with_space: bool) -> Batch<'a> { + Batch { + out, + written: 0, + returned: 0, + seen: 0, + cursor, + too_big: 0, + with_space, + } +} + +/// `CUBE_OP_FLAG_SCAN` against a v3/v4 image, scoped to one space. +/// /// A v3 image is a store whose records were written before the mask existed. Its entries read as /// "no class", so a scan over it matches nothing and says so by returning an empty batch — which is /// the honest answer, not an error. @@ -3260,108 +3473,111 @@ unsafe fn v3_flag_scan( if log.extend_from_slice(image.log(), GFP_KERNEL).is_err() { return -12; // -ENOMEM } - let mut edits = KVVec::>::new(); - if log_edits(log.as_slice(), &wanted, &mut edits).is_err() { + + // SAFETY: the shim guarantees `cap` writable bytes at `buf`. + let out = unsafe { core::slice::from_raw_parts_mut(buf, cap) }; + // The caller named the space, so the frame need not repeat it per record. + let mut batch = flag_batch(out, cursor, false); + if let Err(e) = flag_scan_space( + &mut image, + log.as_slice(), + &wanted, + first, + records, + mask, + mode, + &mut batch, + ) { + return -(e.to_errno() as i32); + } + + // SAFETY: both out-pointers are writable under this function's contract. + unsafe { finish_batch(&batch, cursor, out_len, out_cursor) } +} + +/// `CUBE_OP_FLAG_SCAN` with `CUBE_SPACE_EVERY`: the same question asked of every space. +/// +/// One batch and one cursor across all of them, so a caller pages through the whole store's matches +/// as one list — which is what "every event anywhere" has to mean if it is to be usable in batches +/// at all. The spaces are visited in the space table's order, and a space the log writes into that +/// the image has never seen is visited in its place in that same order, so the answer comes back in +/// (space, key) order exactly as a checkpoint writes records. +/// +/// # Safety +/// `buf` must hold `cap` writable bytes; `out_len` and `out_cursor` must point to writable `u64`s. +unsafe fn v3_flag_scan_every( + mut image: Addressed, + mask: u16, + mode: u16, + cursor: u64, + buf: *mut u8, + cap: usize, + out_len: *mut u64, + out_cursor: *mut u64, +) -> i32 { + let mut log = KVVec::::new(); + if log.extend_from_slice(image.log(), GFP_KERNEL).is_err() { + return -12; // -ENOMEM + } + // The spaces the log writes into, sorted — including any the image has never seen, which are the + // ones a plain walk of the space table would miss entirely. + let mut from_log = KVVec::<[u8; SPACE_ID_LEN]>::new(); + if log_spaces(log.as_slice(), &mut from_log).is_err() { return -12; } // 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` counts matches, and the cursor skips the matches already returned; starting it at - // `cursor` would skip the first `cursor` matches of a fresh walk *and* still count from - // there, re-serving the first batch forever. It starts at 0 and `offer_flagged` is the only - // thing that counts. - seen: 0, - cursor, - too_big: 0, - }; + // The caller named no space, so every frame has to say which one its record came from. + let mut batch = flag_batch(out, cursor, true); - let mut entry = KVVec::::new(); - let mut at = first; - let mut edit_at = 0usize; - 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 newest word carries the mask that counts. - while edit_at + 1 < edits.len() && edits[edit_at + 1].key == edits[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(triple) => Some(triple), + // One closure would have to borrow `image` and `batch` mutably at once, so the walk steps by + // hand; `full` is the batch saying it has no room for another record. + let mut full = false; + let mut row = 0u64; + let mut log_at = 0usize; + while row < image.header.space_count && !full { + let (space, first, records) = match image.space_row(row) { + Ok(Some(triple)) => triple, + Ok(None) => break, + Err(e) => return -(e.to_errno() as i32), + }; + // A space only the log writes sorts before this one: visit it here, in its place. + while log_at < from_log.len() + && from_log.as_slice()[log_at] < space + && !full + { + let candidate = from_log.as_slice()[log_at]; + log_at += 1; + match flag_scan_space(&mut image, log.as_slice(), &candidate, 0, 0, mask, mode, &mut batch) { + Ok(keep_going) => full = !keep_going, 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, - }; - - match (edit, image_entry, image_key) { - // The log's word on a coordinate the image holds: it wins, mask and all. - (Some(e), Some(_), Some(key)) if e.key == key => { - edit_at += 1; - at += 1; - if !e.deleted - && flag_matches(e.flags, mask, mode) - && !batch.offer_flagged(e.key, e.flags, e.value) - { - break; - } + } + // A space the log also writes into is the same space, not a second one. + if log_at < from_log.len() && from_log.as_slice()[log_at] == space { + log_at += 1; + } + if !full { + match flag_scan_space(&mut image, log.as_slice(), &space, first, records, mask, mode, &mut batch) { + Ok(keep_going) => full = !keep_going, + Err(e) => return -(e.to_errno() as i32), } - // 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 - && flag_matches(e.flags, mask, mode) - && !batch.offer_flagged(e.key, e.flags, e.value) - { - break; - } - } - // The image's own record, which the log says nothing about. - (_, 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 flag_matches(flags, mask, mode) && !batch.offer_flagged(key, flags, value.as_slice()) - { - break; - } - } - (Some(e), None, _) => { - edit_at += 1; - if !e.deleted - && flag_matches(e.flags, mask, mode) - && !batch.offer_flagged(e.key, e.flags, e.value) - { - break; - } - } - (None, None, _) => break, - _ => break, + } + row += 1; + } + // Whatever the log writes into that sorts after every space the image has. + while log_at < from_log.len() && !full { + let candidate = from_log.as_slice()[log_at]; + log_at += 1; + match flag_scan_space(&mut image, log.as_slice(), &candidate, 0, 0, mask, mode, &mut batch) { + Ok(keep_going) => full = !keep_going, + Err(e) => return -(e.to_errno() as i32), } } // 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 + unsafe { finish_batch(&batch, cursor, out_len, out_cursor) } } /// @@ -3437,23 +3653,43 @@ fn pack_record(key: &[u8], value: &[u8], out: &mut [u8], at: usize) -> Option Option { - let need = RAW_KEY_LEN + FLAGS_LEN + 4 + value.len(); +/// +/// The space leads, and only when `with_space` says so, because it is the one field the frame +/// cannot imply when the caller named no space — and it leads rather than trails because the +/// (space, key) order it forms is the order records are stored in and returned in. +fn pack_flagged( + space: &[u8; SPACE_ID_LEN], + key: &[u8], + flags: u16, + value: &[u8], + out: &mut [u8], + at: usize, + with_space: bool, +) -> Option { + let need = flagged_frame(with_space) + value.len(); if at + need > out.len() { return None; } + let mut at = at; + if with_space { + out[at..at + SPACE_ID_LEN].copy_from_slice(&space[..SPACE_ID_LEN]); + at += SPACE_ID_LEN; + } out[at..at + RAW_KEY_LEN].copy_from_slice(&key[..RAW_KEY_LEN]); - out[at + RAW_KEY_LEN..at + RAW_KEY_LEN + FLAGS_LEN].copy_from_slice(&flags.to_le_bytes()); - out[at + RAW_KEY_LEN + FLAGS_LEN..at + RAW_KEY_LEN + FLAGS_LEN + 4] - .copy_from_slice(&(value.len() as u32).to_le_bytes()); - out[at + RAW_KEY_LEN + FLAGS_LEN + 4..at + need].copy_from_slice(value); + at += RAW_KEY_LEN; + out[at..at + FLAGS_LEN].copy_from_slice(&flags.to_le_bytes()); + at += FLAGS_LEN; + out[at..at + 4].copy_from_slice(&(value.len() as u32).to_le_bytes()); + at += 4; + out[at..at + value.len()].copy_from_slice(value); Some(need) } @@ -3519,6 +3755,7 @@ pub unsafe extern "C" fn cubelinux_kernel_enum( seen: 0, cursor, too_big: 0, + with_space: false, }; while let Some((key, value)) = walker.next() { if !batch.offer(key, value) { @@ -3621,6 +3858,7 @@ pub unsafe extern "C" fn cubelinux_kernel_range( seen: 0, cursor, too_big: 0, + with_space: false, }; while let Some((key, value)) = walker.next() { match region.place(key) { @@ -3650,7 +3888,7 @@ pub unsafe extern "C" fn cubelinux_kernel_range( 0 } -/// `CUBE_OP_FLAG_SCAN`: the live records of one space whose class mask matches, in key order. +/// `CUBE_OP_FLAG_SCAN`: the live records whose class mask matches, in key order. /// /// Its cursor counts **matches already returned**, exactly as the region walk's counts records in /// the box — for the same reason, and with the same consequence: a batch holds as many whole @@ -3661,6 +3899,10 @@ pub unsafe extern "C" fn cubelinux_kernel_range( /// with the class mask in it, because a scan's answer has to say what class each record answered /// with and no other op returns a mask. /// +/// `every_space` chooses between two questions rather than widening one: a space is a hard +/// partition, so "this class here" and "this class anywhere" are different answers, and the narrower +/// one is what a caller gets by leaving the field zero. +/// /// # 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`. @@ -3669,6 +3911,7 @@ pub unsafe extern "C" fn cubelinux_kernel_flag_scan( space: *const u8, mask: u16, mode: u16, + every_space: u32, cursor: u64, buf: *mut u8, cap: usize, @@ -3681,15 +3924,25 @@ pub unsafe extern "C" fn cubelinux_kernel_flag_scan( // A mode this build does not know is refused rather than defaulted: a caller that asked for // "all" and silently got "any" would get a superset, and a superset of a security question is - // the worst way to be wrong. + // the worst way to be wrong. The same for a scope it does not know — a caller that asked for + // "every space" and silently got one space would get a subset, which is the other way to be + // wrong and just as quiet. if mode != FLAG_MODE_ANY && mode != FLAG_MODE_ALL { return -22; // -EINVAL } + if every_space != FLAG_SCOPE_ONE && every_space != FLAG_SCOPE_EVERY { + return -22; + } + let every = every_space == FLAG_SCOPE_EVERY; match Addressed::open() { Ok(Some(image)) => { return unsafe { - v3_flag_scan(image, wanted, mask, mode, cursor, buf, cap, out_len, out_cursor) + if every { + v3_flag_scan_every(image, mask, mode, cursor, buf, cap, out_len, out_cursor) + } else { + v3_flag_scan(image, wanted, mask, mode, cursor, buf, cap, out_len, out_cursor) + } } } Ok(None) => {} @@ -3704,8 +3957,8 @@ pub unsafe extern "C" fn cubelinux_kernel_flag_scan( // A v1/v2 image holds packed records, which have no mask field at all — they read as "no class" // and so can never match a scan. What *can* match is the log: its entries are the flagged ones, // and they carry the mask the writer stamped. So the answer here is the log's live word on each - // coordinate it holds for the space, in key order — a walk's merge with the image's half of it - // known in advance to be empty of matches. + // coordinate it holds, in key order — a walk's merge with the image's half of it known in + // advance to be empty of matches. // // This is not a corner: it is the state of every store between the write that classified // something and the fold that moves the mask into the index — which is the ordinary state of a @@ -3715,50 +3968,31 @@ pub unsafe extern "C" fn cubelinux_kernel_flag_scan( if log.extend_from_slice(view.log(), GFP_KERNEL).is_err() { return -12; // -ENOMEM } - let mut edits = KVVec::>::new(); - if log_edits(log.as_slice(), &wanted, &mut edits).is_err() { - return -12; - } // 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: 0, - cursor, - too_big: 0, - }; + let mut batch = flag_batch(out, cursor, every); - let mut edit_at = 0usize; - while edit_at < edits.len() { - // The entries of one key arrive in log order, so the last of a key's group is the newest - // word on it — and only the newest can match, because it is what a read would return. - while edit_at + 1 < edits.len() && edits[edit_at + 1].key == edits[edit_at].key { - edit_at += 1; + if every { + // Every space the log writes into, sorted — which is every space that can hold a match, + // since none of the image's records can. + let mut from_log = KVVec::<[u8; SPACE_ID_LEN]>::new(); + if log_spaces(log.as_slice(), &mut from_log).is_err() { + return -12; } - let e = edits.as_slice()[edit_at]; - edit_at += 1; - if !e.deleted - && flag_matches(e.flags, mask, mode) - && !batch.offer_flagged(e.key, e.flags, e.value) - { - break; + for space in from_log.as_slice() { + match flag_scan_log_space(log.as_slice(), space, mask, mode, &mut batch) { + Ok(true) => {} + Ok(false) => break, + Err(e) => return -(e.to_errno() as i32), + } } + } else if let Err(e) = flag_scan_log_space(log.as_slice(), &wanted, mask, mode, &mut batch) { + return -(e.to_errno() as i32); } // 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 + unsafe { finish_batch(&batch, cursor, out_len, out_cursor) } } /// `CUBE_OP_SPACES`: the `cursor`-th distinct space that holds a record, or -ENOENT at the end. diff --git a/include/uapi/linux/cube.h b/include/uapi/linux/cube.h index 48f17e7db..58fa9698a 100644 --- a/include/uapi/linux/cube.h +++ b/include/uapi/linux/cube.h @@ -138,13 +138,21 @@ struct cube_range_args { * — the walk's frame with the class mask in it. The mask travels because a scan's answer has to say * what class each record answered with: a record can carry bits beyond the one asked for, and no * other operation returns a mask. + * + * Under CUBE_SPACE_EVERY the frame gains a leading `space(32)`, because it has to: a walk's frame + * leaves the space out on the grounds that the caller named it, and a caller who named no space + * cannot be told which one a record came from any other way. A coordinate is meaningless without + * its space, so an every-space answer that omitted it would be unusable rather than merely terse. + * The space leads because the (space, key) pair it forms is the order records are stored in and + * returned in. */ struct cube_flag_scan_args { __u32 size; /* sizeof(struct cube_flag_scan_args) as the caller built it */ __u32 op; /* CUBE_OP_FLAG_SCAN */ - __u8 space[32]; /* in: the space to scan */ + __u8 space[32]; /* in: the space to scan, or ignored with CUBE_SPACE_EVERY */ __u16 mask; /* in: the class mask to match; 0 matches nothing */ __u16 mode; /* in: CUBE_FLAG_ANY or CUBE_FLAG_ALL */ + __u32 every_space; /* in: CUBE_SPACE_ONE or CUBE_SPACE_EVERY — see below */ __u64 cursor; /* in: 0 to start, or what the last call returned; * out: what to pass next — see the end-of-scan rule above */ @@ -157,4 +165,21 @@ struct cube_flag_scan_args { #define CUBE_FLAG_ANY 0 /* the record shares at least one bit with the mask */ #define CUBE_FLAG_ALL 1 /* the record carries every bit of the mask */ +/* + * The scan's scope. A space is a hard partition, so this is a choice between two different + * questions and never a filter that can be widened by accident: + * + * CUBE_SPACE_ONE the records of `space` — "this class here" + * CUBE_SPACE_EVERY the records of every space — "this class anywhere" + * + * It is a field and not a reserved space id because there is no such id to reserve: every 32-byte + * value is a legitimate space (root `0x00` and edge `0xFF…FF` are both in use), so a sentinel would + * be a space somebody could name. The field occupies what was padding, so `sizeof` is unchanged — + * which matters, because `size` is what says which argument block arrived and `cube_args` is + * exactly eight bytes wider. A caller that zeroes its block (and every caller does) gets + * CUBE_SPACE_ONE, which is the narrower question. + */ +#define CUBE_SPACE_ONE 0 /* scan only `space` */ +#define CUBE_SPACE_EVERY 1 /* scan every space */ + #endif /* _UAPI_LINUX_CUBE_H */