diff --git a/drivers/cube/cube_format.rs b/drivers/cube/cube_format.rs index b0231de4f..08ab23ca9 100644 --- a/drivers/cube/cube_format.rs +++ b/drivers/cube/cube_format.rs @@ -22,63 +22,44 @@ // gates stay readable. /// Every image starts with these four bytes. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub const MAGIC: &[u8; 4] = b"CUBE"; /// The original format: magic, version, curve, then records until zero padding. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub const VERSION_V1: u8 = 1; /// The packed format: the same, plus the image's byte extent and record count. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub const VERSION_V2: u8 = 2; /// The addressed format: the same, plus a space table, a fixed-size index, and packed values. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub const VERSION_V3: u8 = 3; -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub const HEADER_LEN_V1: usize = 6; -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub const HEADER_LEN_V2: usize = 6 + 8 + 8; /// magic(4) version(1) curve(1) image_bytes(8) record_count(8) space_count(8) index_off(8) /// values_off(8). -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub const HEADER_LEN_V3: usize = 4 + 1 + 1 + 8 + 8 + 8 + 8 + 8; -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub const SPACE_ID_LEN: usize = 32; -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub const RAW_KEY_LEN: usize = 24; /// A packed record's fixed part: `space | key | value_len(u64)`, with the value behind it. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub const RECORD_FIXED: usize = SPACE_ID_LEN + RAW_KEY_LEN + 8; /// A v3 index entry: `key | value_off(u64) | value_len(u64)`. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub const INDEX_ENTRY: usize = RAW_KEY_LEN + 8 + 8; /// A v3 space-table row: `space | first index(u64) | records(u64)`. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub const SPACE_ENTRY: usize = SPACE_ID_LEN + 8 + 8; /// The log's framing, which the fold and the readers both parse. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub const WAL_MAGIC: &[u8; 4] = b"CUBW"; -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub const WAL_VERSION: u8 = 1; -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub const WAL_HEADER_LEN: usize = 6; /// `op(1) | crc(4) | space(32) | key(24) | len(4)`, with the value behind it. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub const ENTRY_FIXED: usize = 1 + 4 + SPACE_ID_LEN + RAW_KEY_LEN + 4; /// `CUBE_OP_PUT`: an entry that stores a value. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub const WAL_OP_WRITE: u8 = 1; /// `CUBE_OP_DEL`: an entry that removes one. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub const WAL_OP_DELETE: u8 = 2; /// What an image's header says, in any version. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub struct Header { pub version: u8, pub curve: u8, @@ -112,7 +93,6 @@ impl Header { /// Why an image was refused. A reader that guesses at an extent can read somebody else's bytes, so /// these are all refusals rather than defaults. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub enum Bad { /// Not a CUBE image at all. Magic, @@ -125,7 +105,6 @@ pub enum Bad { } /// Read and validate an image header. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub fn parse_header(bytes: &[u8]) -> Result { if bytes.len() < 5 || &bytes[0..4] != MAGIC { return Err(Bad::Magic); @@ -173,7 +152,6 @@ pub fn parse_header(bytes: &[u8]) -> Result { /// The v3 tables' geometry: where the index is, where the values start, and how many of each. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub struct V3 { pub record_count: u64, pub space_count: u64, @@ -282,7 +260,6 @@ impl V3 { /// A v3 index entry: the key, and where its value lies. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub struct IndexEntry<'a> { pub key: &'a [u8; RAW_KEY_LEN], pub value_off: u64, @@ -291,7 +268,6 @@ pub struct IndexEntry<'a> { /// One packed record, and where the next one starts. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub struct PackedRecord<'a> { pub space: &'a [u8; SPACE_ID_LEN], pub key: &'a [u8; RAW_KEY_LEN], @@ -304,7 +280,6 @@ pub struct PackedRecord<'a> { /// /// This is the v1/v2 layout's only parsing rule, and it is shared for the same reason the constants /// are: a reader that disagrees about a frame's length reads every following record wrong. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub fn packed_record(bytes: &[u8], off: usize, end: usize) -> Option> { if off + RECORD_FIXED > end || end > bytes.len() { return None; @@ -325,7 +300,6 @@ pub fn packed_record(bytes: &[u8], off: usize, end: usize) -> Option { pub space: &'a [u8; SPACE_ID_LEN], pub key: &'a [u8; RAW_KEY_LEN], @@ -338,7 +312,6 @@ pub struct WalEntry<'a> { /// /// The checksum covers space, key, length and value, so a torn tail is stopped at rather than /// applied — the rule the fold and every reader share. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub fn wal_entry(log: &[u8], off: usize) -> Option> { if off + ENTRY_FIXED > log.len() { return None; @@ -367,7 +340,6 @@ pub fn wal_entry(log: &[u8], off: usize) -> Option> { /// A little-endian `u64` at `at`, or zero if it does not fit — callers bound-check first, and the /// alternative is a panic in a reader that exists to refuse rather than guess. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub fn le_u64(bytes: &[u8], at: usize) -> u64 { let mut w = [0u8; 8]; if at + 8 <= bytes.len() { @@ -377,7 +349,6 @@ pub fn le_u64(bytes: &[u8], at: usize) -> u64 { } /// A little-endian `u32` at `at`, with the same rule. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub fn le_u32(bytes: &[u8], at: usize) -> u32 { let mut w = [0u8; 4]; if at + 4 <= bytes.len() { @@ -391,7 +362,6 @@ pub fn le_u32(bytes: &[u8], at: usize) -> u32 { /// A corruption check, not a security check: it catches a torn write or a flipped bit, and says /// nothing about whether anyone tampered with the bytes — that is `cube-crypt`'s job, and it belongs /// on the record rather than on the framing. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub fn crc32(bytes: &[u8]) -> u32 { let mut crc = 0xFFFF_FFFFu32; for byte in bytes { @@ -405,11 +375,9 @@ pub fn crc32(bytes: &[u8]) -> u32 { } /// The store's digest hash: FNV-1a, 64-bit. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325; /// Fold more bytes into an FNV-1a hash. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub fn fnv1a64(bytes: &[u8], mut h: u64) -> u64 { for byte in bytes { h ^= *byte as u64; @@ -423,7 +391,6 @@ pub fn fnv1a64(bytes: &[u8], mut h: u64) -> u64 { /// `bytes` is the records' logical size — the same number whatever layout holds them — because this /// line is what a kernel-written store and a userspace-written one are compared by, and a figure /// that changed with the layout would make one store look like two. -#[allow(dead_code)] // one build uses it and the other does not: neither subset is dead pub struct Digest { pub version: u8, pub curve: u8, diff --git a/drivers/cube/cubelinux_store.rs b/drivers/cube/cubelinux_store.rs index a751435f3..966e92147 100644 --- a/drivers/cube/cubelinux_store.rs +++ b/drivers/cube/cubelinux_store.rs @@ -50,6 +50,7 @@ use core::fmt::{self, Write}; // image. The constants below are aliases of the shared ones, and the two functions that *validate* // a header delegate to it — validation is where a second description would be read as truth. #[path = "cube_format.rs"] +#[allow(dead_code)] // the two builds use different subsets of one API mod cube_format; @@ -827,6 +828,39 @@ fn log_window<'a>(device: &'a [u8], layout: &Layout) -> &'a [u8] { fn build_merged(image: &[u8], log: &[u8], header: &Header) -> Result<(Merged, u64), &'static str> { let mut merged = Merged::new().map_err(|_| "out-of-memory")?; + // A v3 image holds its records behind a space table and an index rather than packed one after + // another, so the store it describes is read through that arithmetic. This is the path a fold + // takes once a store HAS been folded — the first fold reads a packed image and writes an + // addressed one, and every fold after that reads an addressed one — so a reader that only knew + // the packed layout could fold a store exactly once. Found on the box, where that is not a + // hypothesis: the live store's first fold succeeded and its second answered -EINVAL. + if header.version == VERSION_V3 { + let geometry = cube_format::V3::decode(image).map_err(|_| "bad-tables")?; + let mut row = 0u64; + while row < geometry.space_count { + let (space, first, records) = geometry.space_row(image, row).ok_or("truncated-table")?; + let mut i = 0u64; + while i < records { + let entry = geometry + .index_entry(image, first + i) + .ok_or("truncated-index")?; + let value = geometry + .value(image, &entry) + .ok_or("truncated-image")?; + merged + .add(space, entry.key, value, false) + .map_err(|_| "out-of-memory")?; + i += 1; + } + row += 1; + } + let applied = match apply_log(log, &mut merged)? { + Some(n) => n, + None => 0, + }; + return Ok((merged, applied)); + } + let extent = header.image_bytes.ok_or("v1-has-no-extent")? as usize; let extent = core::cmp::min(extent, image.len()); let mut off = if header.version == VERSION_V1 {