Compare commits
5
Commits
e0218ec96b
...
8c24a7ff0d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c24a7ff0d | ||
|
|
8db279a4d8 | ||
|
|
fc057820fa | ||
|
|
9fb4169e52 | ||
|
|
4cfdbfe63b |
@@ -264,8 +264,24 @@ static long cube_enum_op(unsigned int op, void __user *uargs)
|
||||
__u8 found[32];
|
||||
|
||||
ret = cubelinux_kernel_spaces(e.cursor, found);
|
||||
/*
|
||||
* The driver answers with one space written and 0, or with a negative errno. A
|
||||
* positive value is neither, and it must not be read as either: the buffer was not
|
||||
* written, so a caller handed this answer copies a space nobody found.
|
||||
*
|
||||
* This arm is the only one that advances the cursor by itself. Every other walk
|
||||
* leaves the cursor where the caller put it, so a bogus return there ends the walk
|
||||
* on the client's no-progress rule. Here it would hand the walk a cursor that keeps
|
||||
* moving over a space that is not there — which is not a hypothetical: a kernel error
|
||||
* code that had lost its sign did exactly that, and the walk served ~200,000 invented
|
||||
* records a minute until the machine stopped making progress. The cause is fixed
|
||||
* where it was (the driver's errno conversions, cubelinux_store.rs); this is the
|
||||
* bound that keeps a defect of that shape from becoming a loop again.
|
||||
*/
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret != 0)
|
||||
return -EINVAL;
|
||||
memcpy(e.space, found, sizeof(found));
|
||||
/* An index here, not a count of records: hand back the one after this space. */
|
||||
e.cursor = e.cursor + 1;
|
||||
|
||||
+311
-47
@@ -220,6 +220,11 @@ const LOG_ALIGN: usize = 4096;
|
||||
/// Control block magic, distinct from the image's `CUBE` and the log's `CUBW`.
|
||||
const CTL_MAGIC: [u8; 4] = *b"CUBS";
|
||||
const CTL_VERSION: u8 = 1;
|
||||
/// The image header's own magic, which a bare packed image begins with.
|
||||
///
|
||||
/// It is read for one decision: a device whose control block does not decode is a packed image only
|
||||
/// if it says so. Without that question, anything at all was walked as though it were a store.
|
||||
const IMAGE_MAGIC: &[u8; 4] = b"CUBE";
|
||||
/// One control copy, and where the two live.
|
||||
const CTL_COPY_LEN: usize = 2048;
|
||||
const CTL_COPY_A: usize = 0;
|
||||
@@ -461,7 +466,15 @@ fn store_file() -> Result<*mut bindings::file> {
|
||||
// O_RDWR is 2. SAFETY: store_device() is a NUL-terminated C string the module parameter filled
|
||||
// at boot; filp_open returns a valid `struct file *` or an error pointer, checked below.
|
||||
let filp = unsafe { bindings::filp_open(store_device(), 2, 0) };
|
||||
let filp = kernel::error::from_err_ptr(filp)?;
|
||||
// A store that is not there is not an empty store, and this interface has to be able to say so.
|
||||
// Every walk here ends on -ENOENT — "no such space; the walk is finished" — so a store that
|
||||
// cannot even be opened would answer its caller with an empty listing and a success, and a
|
||||
// caller has no way to tell the two apart. That is not hypothetical either: the benchmark boot
|
||||
// that started this work had its store on a disk whose driver had not loaded, and the walk had
|
||||
// no way to say what was wrong. "No such device" is what the store being absent is called here;
|
||||
// the interface's own words for it are `cube_store=` and `/dev/cubelinux`.
|
||||
let filp =
|
||||
kernel::error::from_err_ptr(filp).map_err(|e| if e == ENOENT { ENODEV } else { e })?;
|
||||
if filp.is_null() {
|
||||
return Err(EINVAL);
|
||||
}
|
||||
@@ -1328,6 +1341,13 @@ fn encode_entry(
|
||||
/// control write's `fsync` flushes what precedes it in the same file. So the entry's own fsync is a
|
||||
/// second flush of data the next one covers.
|
||||
///
|
||||
/// **Only where that next write exists.** A store with no control block has no count to write, so
|
||||
/// nothing after the entry is flushed and this call would be the last word on a mutation that is
|
||||
/// only in the page cache — an acknowledgement of a write the machine may never get back. `append`
|
||||
/// therefore calls this only when `layout.control` is `Some`, and takes the entry's own `fsync`
|
||||
/// otherwise; the two cases are one boolean apart and both are gated
|
||||
/// (`kernel/verify-kernel-append.sh`, over an unformatted device and a formatted one).
|
||||
///
|
||||
/// What this gives up, stated rather than implied: the two writes are no longer independently
|
||||
/// durable, so a crash *inside* the control write's commit can in principle leave the control block
|
||||
/// counting bytes whose entry did not fully reach the media. That is a torn tail, and
|
||||
@@ -1678,6 +1698,18 @@ fn append(
|
||||
// valid file or an error pointer, which is checked. O_RDWR is 2.
|
||||
let file = store_file()?;
|
||||
|
||||
// Whether this append gets its durability from its own flush or from the control block's.
|
||||
//
|
||||
// The collapse to one `fsync` per append rests on a sentence: the entry goes down unsynced and
|
||||
// the control write that counts it — written next, to the same file — carries the flush for
|
||||
// both. That is true where there is a control block to write, and is measured to be true
|
||||
// (`kernel/verify-kernel-append.sh` over a formatted device: the entry is on the device after a
|
||||
// SIGKILL). It is *not* true of a store with no control block: the count write never happens,
|
||||
// so nothing on this path is flushed at all and the caller is told a write it may never get
|
||||
// back. That shape is not exotic — it is what a store the CLI builds looks like, and it is the
|
||||
// shape that gate's own device has, where four acknowledged writes did not survive a kill.
|
||||
// So the entry carries its own flush exactly when no later write in this operation will.
|
||||
let synced_by_the_count = layout.control.is_some();
|
||||
let mut result: Result<(), Error> = Ok(());
|
||||
// A log region that has never been written is zeros, not a log: give it a header, the same
|
||||
// thing the userspace log does when its file does not exist. A header that does not already
|
||||
@@ -1692,14 +1724,16 @@ fn append(
|
||||
result = write_and_sync(file, layout.log_off as u64, &hdr);
|
||||
}
|
||||
if result.is_ok() {
|
||||
// The entry, without its own flush: the control block below is written next and synced, and
|
||||
// that sync flushes this. One durability boundary per append instead of two — measured, one
|
||||
// `pwrite`+`fsync` on this filesystem is 4.3 ms, so this is about half of every write.
|
||||
result = write_at(
|
||||
file,
|
||||
(region_at + WAL_HEADER_LEN + used) as u64,
|
||||
entry.as_slice(),
|
||||
);
|
||||
// One durability boundary per append where the control block can carry it — measured, one
|
||||
// `pwrite`+`fsync` on this filesystem is 4.3 ms, so this is about half of every write. Where
|
||||
// it cannot, this write is the boundary, and a write that is acknowledged without having
|
||||
// been flushed is not a write.
|
||||
let at = (region_at + WAL_HEADER_LEN + used) as u64;
|
||||
result = if synced_by_the_count {
|
||||
write_at(file, at, entry.as_slice())
|
||||
} else {
|
||||
write_and_sync(file, at, entry.as_slice())
|
||||
};
|
||||
}
|
||||
|
||||
// The count second, so replay can never be told about an entry that is not there.
|
||||
@@ -2083,7 +2117,7 @@ pub unsafe extern "C" fn cubelinux_kernel_put(
|
||||
};
|
||||
match append_mutation(&mutation, 1) {
|
||||
Ok(_) => 0,
|
||||
Err(e) => -(e.to_errno() as i32),
|
||||
Err(e) => e.to_errno() as i32,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3014,9 +3048,26 @@ impl Addressed {
|
||||
(Some(x), Some(y)) => core::cmp::max(x, y),
|
||||
(Some(x), None) => x,
|
||||
(None, Some(y)) => y,
|
||||
// Neither copy decodes: there is no control block, so this is a bare image and the
|
||||
// walking reader handles it.
|
||||
(None, None) => return Ok(None),
|
||||
// Neither copy decodes. That is *not* by itself evidence of a packed image: it is
|
||||
// evidence that this device does not begin with a control block, and on a device that is
|
||||
// not a store at all there is nothing to walk. The userspace raw reader has always asked
|
||||
// that question — its message for this is `not a CUBE store (bad magic)` — and the kernel
|
||||
// did not, which is the whole of this defect: with nothing behind the read, the walking
|
||||
// reader served *invented* records, a garbage space with a zero-length value and a cursor
|
||||
// that kept moving, for as long as the machine was left on. A boot pointed at a store it
|
||||
// could not read hung instead of saying so.
|
||||
//
|
||||
// So the fallback is taken only for something that says it is a packed image: a bare
|
||||
// packed image begins with the image header's magic. A device with anything else at
|
||||
// offset 0 is refused here, loudly, which is what a missing or wrong store deserves.
|
||||
(None, None) => {
|
||||
let mut head = KVVec::<u8>::with_capacity(8, GFP_KERNEL)?;
|
||||
read_exact_at(file, 0, IMAGE_MAGIC.len(), &mut head, &mut cache.scratch)?;
|
||||
if head.as_slice() != IMAGE_MAGIC {
|
||||
return Err(EINVAL);
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
} as u64;
|
||||
|
||||
let mut opened = Addressed {
|
||||
@@ -3279,16 +3330,16 @@ unsafe fn v3_get(
|
||||
let (first, records) = match image.space_entry(&sp) {
|
||||
Ok(Some(entry)) => entry,
|
||||
Ok(None) => return -2,
|
||||
Err(e) => return -(e.to_errno() as isize),
|
||||
Err(e) => return e.to_errno() as isize,
|
||||
};
|
||||
let (value_off, value_len, flags) = match image.find(&key, first, records) {
|
||||
Ok(Some(entry)) => entry,
|
||||
Ok(None) => return -2,
|
||||
Err(e) => return -(e.to_errno() as isize),
|
||||
Err(e) => return e.to_errno() as isize,
|
||||
};
|
||||
let value = match image.value_at(value_off, value_len) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return -(e.to_errno() as isize),
|
||||
Err(e) => return e.to_errno() as isize,
|
||||
};
|
||||
// The record's own class, from the index entry the address came from. A v3 entry has no mask
|
||||
// and answers zero, which is what "no class" means everywhere here.
|
||||
@@ -3297,6 +3348,157 @@ unsafe fn v3_get(
|
||||
copy_out(value.as_slice(), buf, len)
|
||||
}
|
||||
|
||||
// ── the arrangement vocabulary: one value, several records ─────────────────────────────────────
|
||||
//
|
||||
// Three bits say where a record sits in a chain of records that together hold one value:
|
||||
// `START_RECORD` on the first slice, `CONTINUATION` on every slice that is followed by another,
|
||||
// `END_RECORD` on the last — so a single-slice value carries `START|END`, which is what a
|
||||
// self-contained record already is. `cube_core::WordFlags` allocates them,
|
||||
// `DESIGN-flag-vocabularies.md` §7 is the design, and `crates/cube-store/src/chain.rs` is the
|
||||
// producer and the reference reader.
|
||||
//
|
||||
// **The bits were allocated and unread, so "is anything already using them?" decided whether
|
||||
// reading them here is safe — and it was measured rather than assumed.** Over this machine's live
|
||||
// store, `cube-image flag-scan` finds 1 record in class 0x0080 and 137 in 0x0800, and nothing at all
|
||||
// in 0x0001, 0x0002, 0x0003, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0100 or 0x0200. So no read
|
||||
// that works today can change meaning because of the code below.
|
||||
const START_RECORD: u16 = 1 << 2;
|
||||
const END_RECORD: u16 = 1 << 3;
|
||||
const CONTINUATION: u16 = 1 << 5;
|
||||
|
||||
/// `cube-store-seal`'s flag: this record's value is an *envelope*, not plaintext.
|
||||
///
|
||||
/// It is what stops the kernel joining a chain, and the reason is in the format rather than in
|
||||
/// policy. A sealed slice carries its own header and its own nonce, so N of them concatenated are
|
||||
/// not one value anybody can open; joining those belongs to whoever holds the key, one envelope at
|
||||
/// a time, which is what `chain.rs` does.
|
||||
const SEALED_FLAG: u16 = 0x0800;
|
||||
|
||||
/// The most slices one chain may be joined from — the userspace reader's own ceiling, because two
|
||||
/// readers of one format that disagreed about the bound would disagree about what is valid.
|
||||
const MAX_CHAIN_SLICES: usize = 1 << 16;
|
||||
|
||||
/// The most bytes a joined chain may be: `CUBE_MAX_VALUE`, the largest value a caller can ask for in
|
||||
/// one call. A longer chain could not be handed over in one piece, so reading on to find its end
|
||||
/// would be work whose answer nobody can receive.
|
||||
const MAX_CHAIN_BYTES: usize = 16 * 1024 * 1024;
|
||||
|
||||
/// Whether these flags say the record *begins* a chain that continues past it.
|
||||
fn chain_starts(flags: u16) -> bool {
|
||||
flags & START_RECORD != 0 && flags & END_RECORD == 0
|
||||
}
|
||||
|
||||
/// Read the chain starting at `(x, y, z)` and hand the caller the joined value.
|
||||
///
|
||||
/// The first slice has already been read — this is only called once a read found a chain — so it is
|
||||
/// not read again to find out, and its length and flags come in as arguments.
|
||||
///
|
||||
/// The buffer rule is a single record's rule applied to the whole value: if the joined bytes do not
|
||||
/// fit, nothing is copied and the length is returned, so the caller sizes up and asks again. The log
|
||||
/// is consulted per slice, so a chain that is written but not yet folded joins exactly like one that
|
||||
/// has been folded.
|
||||
///
|
||||
/// # Safety
|
||||
/// `space` must point to 32 readable bytes; `buf` to `len` writable bytes; `out_flags` must be
|
||||
/// writable.
|
||||
unsafe fn v3_get_chain(
|
||||
space: *const u8,
|
||||
x: u64,
|
||||
y: u64,
|
||||
z: u64,
|
||||
first_len: usize,
|
||||
first_flags: u16,
|
||||
buf: *mut u8,
|
||||
len: usize,
|
||||
out_flags: *mut u16,
|
||||
) -> isize {
|
||||
// Pass one: how long is the value, and does the chain hold together at all? The sizes come from
|
||||
// the index — and the log — so this pass reads no value bytes, which is what keeps the
|
||||
// size-then-read pattern as cheap for a chain as it is for a record.
|
||||
let mut total = first_len;
|
||||
let mut flags = first_flags;
|
||||
let mut zz = z;
|
||||
let mut slices = 1usize;
|
||||
while flags & END_RECORD == 0 {
|
||||
if flags & CONTINUATION == 0 {
|
||||
// It neither continues nor ends. Where the value stops is not knowable, and inventing an
|
||||
// end would hand the caller bytes nobody wrote.
|
||||
return -5; // -EIO
|
||||
}
|
||||
if slices >= MAX_CHAIN_SLICES {
|
||||
// A chain with no end is a read with no end: the ceiling is the answer.
|
||||
return -5; // -EIO
|
||||
}
|
||||
zz += 1;
|
||||
slices += 1;
|
||||
let image = match Addressed::open() {
|
||||
Ok(Some(image)) => image,
|
||||
Ok(None) => return -95, // -EOPNOTSUPP
|
||||
Err(e) => return e.to_errno() as isize,
|
||||
};
|
||||
// The next slice is arithmetic, not a pointer: the same space at the same x and y, one `z`
|
||||
// further along. Nothing is stored to find it, so nothing can point wrongly.
|
||||
let (sp, key) = unsafe { coord_key(space, x, y, zz) };
|
||||
let mut slice_flags = 0u16;
|
||||
let n = unsafe { v3_get(image, sp, key, core::ptr::null_mut(), 0, &mut slice_flags) };
|
||||
if n == -2 {
|
||||
// A hole. A chain with a missing slice is not a shorter value, it is a corrupt one, and
|
||||
// returning the prefix would hand the caller something that looks smaller rather than
|
||||
// broken.
|
||||
return -5; // -EIO
|
||||
}
|
||||
if n < 0 {
|
||||
return n;
|
||||
}
|
||||
if slice_flags & (CONTINUATION | END_RECORD) == 0 || slice_flags & START_RECORD != 0 {
|
||||
// A record that belongs to no chain, or a second beginning: either way the chain this
|
||||
// read started from is not the chain it claimed to be.
|
||||
return -5; // -EIO
|
||||
}
|
||||
total = match total.checked_add(n as usize) {
|
||||
Some(t) if t <= MAX_CHAIN_BYTES => t,
|
||||
_ => return -7, // -E2BIG
|
||||
};
|
||||
flags = slice_flags;
|
||||
}
|
||||
// The flags describe the bytes the caller is handed, not the slice they came from: this is now a
|
||||
// whole self-contained value, so it answers `START|END`. That is also what lets the userspace
|
||||
// reader work unchanged against a joining store — it stops at `END_RECORD`, which is what it was
|
||||
// already written to do.
|
||||
unsafe { *out_flags = (first_flags & !CONTINUATION) | END_RECORD };
|
||||
if total > len {
|
||||
return total as isize;
|
||||
}
|
||||
// Pass two: copy, in z order. Both passes walk the same coordinates, so each slice is copied into
|
||||
// the place the first pass measured for it.
|
||||
let mut off = 0usize;
|
||||
let mut zz = z;
|
||||
for _ in 0..slices {
|
||||
let image = match Addressed::open() {
|
||||
Ok(Some(image)) => image,
|
||||
Ok(None) => return -95, // -EOPNOTSUPP
|
||||
Err(e) => return e.to_errno() as isize,
|
||||
};
|
||||
let (sp, key) = unsafe { coord_key(space, x, y, zz) };
|
||||
let mut slice_flags = 0u16;
|
||||
let n = unsafe {
|
||||
v3_get(image, sp, key, unsafe { buf.add(off) }, len - off, &mut slice_flags)
|
||||
};
|
||||
if n < 0 {
|
||||
return n;
|
||||
}
|
||||
off += n as usize;
|
||||
zz += 1;
|
||||
}
|
||||
// `off` and `total` come from the same coordinates read twice, so they must agree. A
|
||||
// disagreement means the store moved under the read, and a length nobody measured is the wrong
|
||||
// thing to return quietly.
|
||||
if off != total {
|
||||
return -5; // -EIO
|
||||
}
|
||||
total as isize
|
||||
}
|
||||
|
||||
/// `CUBE_OP_SPACES` against a v3 image: the space table, and whatever only the log writes.
|
||||
///
|
||||
/// # Safety
|
||||
@@ -3393,7 +3595,7 @@ unsafe fn v3_enum(
|
||||
let (first, records) = match image.space_entry(&wanted) {
|
||||
Ok(Some(entry)) => entry,
|
||||
Ok(None) => (0, 0),
|
||||
Err(e) => return -(e.to_errno() as i32),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
};
|
||||
// Own the log: the edits borrow it, and walking the image needs `&mut image`.
|
||||
let mut log = KVVec::<u8>::new();
|
||||
@@ -3448,11 +3650,11 @@ unsafe fn v3_enum(
|
||||
while at < first + records {
|
||||
let (value_off, value_len, flags) = match image.index_entry(at, &mut entry) {
|
||||
Ok(triple) => triple,
|
||||
Err(e) => return -(e.to_errno() as i32),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
};
|
||||
let value = match image.value_at(value_off, value_len) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return -(e.to_errno() as i32),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
};
|
||||
let key: &[u8; RAW_KEY_LEN] = match entry.as_slice()[..RAW_KEY_LEN].try_into() {
|
||||
Ok(k) => k,
|
||||
@@ -3487,7 +3689,7 @@ unsafe fn v3_enum(
|
||||
let image_entry = if at < first + records {
|
||||
match image.index_entry(at, &mut entry) {
|
||||
Ok(pair) => Some(pair),
|
||||
Err(e) => return -(e.to_errno() as i32),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
@@ -3513,7 +3715,7 @@ unsafe fn v3_enum(
|
||||
(_, 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),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
};
|
||||
at += 1;
|
||||
if !batch.offer(&wanted, key, flags, value.as_slice()) {
|
||||
@@ -3576,7 +3778,7 @@ unsafe fn v3_range(
|
||||
let (first, records) = match image.space_entry(&wanted) {
|
||||
Ok(Some(entry)) => entry,
|
||||
Ok(None) => (0, 0),
|
||||
Err(e) => return -(e.to_errno() as i32),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
};
|
||||
// Own the log: the edits borrow it, and walking the image needs `&mut image`.
|
||||
let mut log = KVVec::<u8>::new();
|
||||
@@ -3599,7 +3801,7 @@ unsafe fn v3_range(
|
||||
// The index's half.
|
||||
let mut at = match image.lower_bound(®ion.foot, first, records) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return -(e.to_errno() as i32),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
};
|
||||
|
||||
// SAFETY: the shim guarantees `cap` writable bytes at `buf`.
|
||||
@@ -3632,7 +3834,7 @@ unsafe fn v3_range(
|
||||
let image_entry = if at < first + records {
|
||||
match image.index_entry(at, &mut entry) {
|
||||
Ok(pair) => Some(pair),
|
||||
Err(e) => return -(e.to_errno() as i32),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
@@ -3687,7 +3889,7 @@ unsafe fn v3_range(
|
||||
(_, 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),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
};
|
||||
at += 1;
|
||||
if region.holds(key) && !batch.offer(&wanted, key, flags, value.as_slice()) {
|
||||
@@ -3945,7 +4147,7 @@ unsafe fn v3_flag_scan(
|
||||
let (first, records) = match image.space_entry(&wanted) {
|
||||
Ok(Some(entry)) => entry,
|
||||
Ok(None) => (0, 0),
|
||||
Err(e) => return -(e.to_errno() as i32),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
};
|
||||
// Own the log: the edits borrow it, and walking the image needs `&mut image`.
|
||||
let mut log = KVVec::<u8>::new();
|
||||
@@ -3967,7 +4169,7 @@ unsafe fn v3_flag_scan(
|
||||
mode,
|
||||
&mut batch,
|
||||
) {
|
||||
return -(e.to_errno() as i32);
|
||||
return e.to_errno() as i32;
|
||||
}
|
||||
|
||||
// SAFETY: both out-pointers are writable under this function's contract.
|
||||
@@ -4019,7 +4221,7 @@ unsafe fn v3_flag_scan_every(
|
||||
let (space, first, records) = match image.space_row(row) {
|
||||
Ok(Some(triple)) => triple,
|
||||
Ok(None) => break,
|
||||
Err(e) => return -(e.to_errno() as i32),
|
||||
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()
|
||||
@@ -4030,7 +4232,7 @@ unsafe fn v3_flag_scan_every(
|
||||
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),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
}
|
||||
}
|
||||
// A space the log also writes into is the same space, not a second one.
|
||||
@@ -4040,7 +4242,7 @@ unsafe fn v3_flag_scan_every(
|
||||
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),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
}
|
||||
}
|
||||
row += 1;
|
||||
@@ -4051,7 +4253,7 @@ unsafe fn v3_flag_scan_every(
|
||||
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),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4068,6 +4270,11 @@ unsafe fn v3_flag_scan_every(
|
||||
/// only if it says nothing about the coordinate does the image answer. Nothing is copied out of
|
||||
/// either: the value the caller gets is a slice of bytes already in memory.
|
||||
///
|
||||
/// A value written as a *chain* — several records at consecutive `z`, marked with the arrangement
|
||||
/// bits — comes back **joined**: a `get` on the first slice returns every slice's bytes in `z` order,
|
||||
/// because the kernel is the half that can walk a chain without the caller reading each slice itself.
|
||||
/// What joins and what deliberately does not is stated at the arrangement vocabulary below.
|
||||
///
|
||||
/// # Safety
|
||||
/// `space` must point to 32 readable bytes; `buf` to `len` writable bytes.
|
||||
#[unsafe(no_mangle)]
|
||||
@@ -4088,13 +4295,34 @@ pub unsafe extern "C" fn cubelinux_kernel_get(
|
||||
// A v3 image is addressed, so a read is a binary search and a few bytes. v1 and v2 images are
|
||||
// packed lists, which the walking reader below handles.
|
||||
match Addressed::open() {
|
||||
Ok(Some(image)) => return unsafe { v3_get(image, sp, key, buf, len, out_flags) },
|
||||
Ok(Some(image)) => {
|
||||
let mut flags = 0u16;
|
||||
let got = unsafe { v3_get(image, sp, key, buf, len, &mut flags) };
|
||||
// A record that begins a chain and does not end it holds one slice of a larger value, and
|
||||
// the kernel is the half that can join it: a caller would otherwise read every slice
|
||||
// itself just to learn how long the value is.
|
||||
//
|
||||
// Two cases do not join here, and both are said in the answer rather than approximated. A
|
||||
// *sealed* slice is an envelope with its own header and nonce, so joining those is not
|
||||
// joining a value — that belongs to whoever holds the key. A packed image has no index to
|
||||
// address slices by. In both, the caller gets the slice with the record's own flags, and
|
||||
// `CONTINUATION` without `END_RECORD` says plainly "this is a slice, not the whole
|
||||
// value": told, not misled.
|
||||
if got >= 0 && chain_starts(flags) && (flags & SEALED_FLAG) == 0 {
|
||||
return unsafe {
|
||||
v3_get_chain(space, x, y, z, got as usize, flags, buf, len, out_flags)
|
||||
};
|
||||
}
|
||||
// SAFETY: the caller guarantees a writable u16.
|
||||
unsafe { *out_flags = flags };
|
||||
return got;
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => return -(e.to_errno() as isize),
|
||||
Err(e) => return e.to_errno() as isize,
|
||||
}
|
||||
let view = match read_view() {
|
||||
Ok(v) => v,
|
||||
Err(e) => return -(e.to_errno() as isize),
|
||||
Err(e) => return e.to_errno() as isize,
|
||||
};
|
||||
|
||||
match log_effect(view.log(), &sp, &key) {
|
||||
@@ -4209,6 +4437,9 @@ pub unsafe extern "C" fn cubelinux_kernel_enum(
|
||||
out_len: *mut u64,
|
||||
out_cursor: *mut u64,
|
||||
) -> i32 {
|
||||
if let Err(e) = store_readable() {
|
||||
return e.to_errno() as i32;
|
||||
}
|
||||
let mut wanted = [0u8; SPACE_ID_LEN];
|
||||
// SAFETY: the caller guarantees 32 readable bytes at `space`.
|
||||
unsafe { core::ptr::copy_nonoverlapping(space, wanted.as_mut_ptr(), SPACE_ID_LEN) };
|
||||
@@ -4218,12 +4449,12 @@ pub unsafe extern "C" fn cubelinux_kernel_enum(
|
||||
return unsafe { v3_enum(image, wanted, cursor, buf, cap, out_len, out_cursor) }
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => return -(e.to_errno() as i32),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
}
|
||||
|
||||
let view = match read_view() {
|
||||
Ok(v) => v,
|
||||
Err(e) => return -(e.to_errno() as i32),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
};
|
||||
|
||||
// The live records of this space: the image's, with the log's edits applied as an overlay.
|
||||
@@ -4323,12 +4554,12 @@ pub unsafe extern "C" fn cubelinux_kernel_range(
|
||||
return unsafe { v3_range(image, wanted, region, cursor, buf, cap, out_len, out_cursor) }
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => return -(e.to_errno() as i32),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
}
|
||||
|
||||
let view = match read_view() {
|
||||
Ok(v) => v,
|
||||
Err(e) => return -(e.to_errno() as i32),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
};
|
||||
let mut walker = match SpaceWalker::new(&view, &wanted) {
|
||||
Ok(w) => w,
|
||||
@@ -4434,12 +4665,12 @@ pub unsafe extern "C" fn cubelinux_kernel_flag_scan(
|
||||
}
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => return -(e.to_errno() as i32),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
}
|
||||
|
||||
let view = match read_view() {
|
||||
Ok(v) => v,
|
||||
Err(e) => return -(e.to_errno() as i32),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
};
|
||||
|
||||
// A v1/v2 image holds packed records, which have no mask field at all — they read as "no class"
|
||||
@@ -4472,17 +4703,47 @@ pub unsafe extern "C" fn cubelinux_kernel_flag_scan(
|
||||
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),
|
||||
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);
|
||||
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) }
|
||||
}
|
||||
|
||||
/// Whether the store can be read at all, asked before an operation answers anything.
|
||||
///
|
||||
/// A walk pointed at something that is not a store served invented records — a garbage space, a
|
||||
/// zero-length value, a cursor that advanced on its own, forever — and a boot that hung instead of
|
||||
/// saying what was wrong. Probing every branch of `Addressed::open()` showed why the earlier guesses
|
||||
/// were wrong: it is entered, the file opens, and execution never gets past the control-block read, so
|
||||
/// neither the packed fallback nor the guard written for it was ever on that path. Whatever answered
|
||||
/// downstream answered over a device that cannot be read.
|
||||
///
|
||||
/// So the question is asked here, where the answer leaves no room: four bytes at offset zero.
|
||||
///
|
||||
/// **A refusal is only a refusal if it is negative.** This guard fired on every call and the walk
|
||||
/// still received "here is a space": the callers below converted the error with `-(e.to_errno())`,
|
||||
/// and [`Error::to_errno`] already returns the kernel's error code, which is negative. A doubled
|
||||
/// minus sign made every refusal in this file a *positive* number, and a positive return is what
|
||||
/// every layer above reads as success — the syscall's `ret < 0`, and the client's own wrapper. The
|
||||
/// conversion is now `e.to_errno()`, as `kernel::from_result` does it, and the walk arm that advances
|
||||
/// its own cursor refuses a positive answer as well, so this cannot be read as an answer again.
|
||||
fn store_readable() -> Result<()> {
|
||||
let file = store_file()?;
|
||||
let mut head = KVVec::<u8>::with_capacity(8, GFP_KERNEL)?;
|
||||
// The scratch is what `read_exact_at` reads *into*, and it asks for `min(left, scratch.len())`
|
||||
// bytes — so a scratch with capacity but no length asks for zero bytes, gets zero, and the
|
||||
// helper correctly reports a short read for every store, good or bad. Length, not capacity.
|
||||
let mut scratch = KVVec::<u8>::with_capacity(4096, GFP_KERNEL)?;
|
||||
scratch.resize(4096, 0, GFP_KERNEL)?;
|
||||
read_exact_at(file, 0, IMAGE_MAGIC.len(), &mut head, &mut scratch)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `CUBE_OP_SPACES`: the `cursor`-th distinct space that holds a record, or -ENOENT at the end.
|
||||
///
|
||||
/// An index rather than a count of records: a caller that wants a space's records walks it with
|
||||
@@ -4493,14 +4754,17 @@ pub unsafe extern "C" fn cubelinux_kernel_flag_scan(
|
||||
/// `space_out` must point to 32 writable bytes.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn cubelinux_kernel_spaces(cursor: u64, space_out: *mut u8) -> i32 {
|
||||
if let Err(e) = store_readable() {
|
||||
return e.to_errno() as i32;
|
||||
}
|
||||
match Addressed::open() {
|
||||
Ok(Some(image)) => return unsafe { v3_spaces(image, cursor, space_out) },
|
||||
Ok(None) => {}
|
||||
Err(e) => return -(e.to_errno() as i32),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
}
|
||||
let view = match read_view() {
|
||||
Ok(v) => v,
|
||||
Err(e) => return -(e.to_errno() as i32),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
};
|
||||
|
||||
// Candidates, in ascending order: the spaces the image's records run through (a checkpoint
|
||||
@@ -4587,7 +4851,7 @@ pub unsafe extern "C" fn cubelinux_kernel_del(space: *const u8, x: u64, y: u64,
|
||||
// bounded read: a delete does not touch the records either.
|
||||
match append_mutation(&mutation, 2) {
|
||||
Ok(_) => 0,
|
||||
Err(e) => -(e.to_errno() as i32),
|
||||
Err(e) => e.to_errno() as i32,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4598,11 +4862,11 @@ pub extern "C" fn cubelinux_kernel_sync() -> i32 {
|
||||
ensure_boot_record();
|
||||
let (device, layout) = match device_and_layout() {
|
||||
Ok(pair) => pair,
|
||||
Err(e) => return -(e.to_errno() as i32),
|
||||
Err(e) => return e.to_errno() as i32,
|
||||
};
|
||||
match fold_now(&device, &layout) {
|
||||
Ok(()) => 0,
|
||||
Err(e) => -(e.to_errno() as i32),
|
||||
Err(e) => e.to_errno() as i32,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user