Range reads M0/M1 (indexed lookups, Storage trait), ZFP, in-place editing #17

Merged
osobh merged 52 commits from feat/p3-range-zfp-edit into main 2026-09-26 20:42:22 +00:00
Showing only changes of commit 0d908facd3 - Show all commits
+92 -12
View File
@@ -21,13 +21,14 @@
//! file is never copied whole.
#[cfg(not(feature = "std"))]
use alloc::{collections::BTreeSet, vec::Vec};
use alloc::{borrow::Cow, collections::BTreeSet, vec::Vec};
#[cfg(feature = "std")]
use std::collections::BTreeSet;
use std::{borrow::Cow, collections::BTreeSet};
use crate::error::FormatError;
use crate::message_type::MessageType;
use crate::object_header::ObjectHeader;
use crate::storage::{Storage, read_exact_at};
use crate::superblock::Superblock;
/// Message type of the File Space Info message.
@@ -169,6 +170,15 @@ impl<'a> Cursor<'a> {
pub fn read_superblock_extension(
data: &[u8],
sb: &Superblock,
) -> Result<Option<SuperblockExtension>, FormatError> {
read_superblock_extension_in(&data, sb)
}
/// [`read_superblock_extension`] over any [`Storage`]; its length is the
/// end of file.
pub fn read_superblock_extension_in(
file: &dyn Storage,
sb: &Superblock,
) -> Result<Option<SuperblockExtension>, FormatError> {
let os = sb.offset_size;
let ls = sb.length_size;
@@ -181,8 +191,8 @@ pub fn read_superblock_extension(
return Ok(None);
};
let addr = usize::try_from(addr).map_err(|_| ext_err("address out of range"))?;
let header = ObjectHeader::parse(data, addr, os, ls)?;
let eoa = data.len() as u64;
let header = ObjectHeader::parse_in(file, addr as u64, os, ls)?;
let eoa = file.len();
let mut ext = SuperblockExtension::default();
for msg in &header.messages {
@@ -340,12 +350,21 @@ impl CacheImage {
data: &[u8],
location: CacheImageLocation,
sb: &Superblock,
) -> Result<Self, FormatError> {
Self::decode_in(&data, location, sb)
}
/// [`Self::decode`] over any [`Storage`]: one read of the image block.
pub fn decode_in(
file: &dyn Storage,
location: CacheImageLocation,
sb: &Superblock,
) -> Result<Self, FormatError> {
let (offset_size, length_size) = (sb.offset_size, sb.length_size);
let bad = FormatError::InvalidCacheImage;
let block = image_block(data, location)?;
let eoa = data.len() as u64;
let mut c = Cursor::new(block, bad(RAN_OFF));
let block = image_block_in(file, location)?;
let eoa = file.len();
let mut c = Cursor::new(&block, bad(RAN_OFF));
// Header: signature, version, flags, image data length, entry count.
if c.take(4)? != MDCI_SIGNATURE {
@@ -463,6 +482,11 @@ impl CacheImage {
image_block(data, self.location)
}
/// [`Self::block`] over any [`Storage`].
pub fn block_in<'a>(&self, file: &'a dyn Storage) -> Result<Cow<'a, [u8]>, FormatError> {
image_block_in(file, self.location)
}
/// Write every entry over `dst`, the file's bytes from the superblock
/// on (as long as the `data` the image was decoded from), taking the
/// entries from `block` (the image block, see [`Self::block`]). `block`
@@ -483,13 +507,28 @@ impl CacheImage {
}
fn image_block(data: &[u8], location: CacheImageLocation) -> Result<&[u8], FormatError> {
let (start, len) = image_block_range(data.len() as u64, location)?;
Ok(&data[start as usize..start as usize + len])
}
fn image_block_in(
file: &dyn Storage,
location: CacheImageLocation,
) -> Result<Cow<'_, [u8]>, FormatError> {
let (start, len) = image_block_range(file.len(), location)?;
read_exact_at(file, start, len)
}
/// Where the image block is, checked against a file of `file_len` bytes.
fn image_block_range(file_len: u64, location: CacheImageLocation) -> Result<(u64, usize), FormatError> {
let bad = FormatError::InvalidCacheImage;
let start = usize::try_from(location.address).map_err(|_| bad("address out of range"))?;
let len = usize::try_from(location.length).map_err(|_| bad("length out of range"))?;
start
.checked_add(len)
.and_then(|end| data.get(start..end))
.ok_or(bad("image block extends past the end of the file"))
.filter(|&end| end as u64 <= file_len)
.ok_or(bad("image block extends past the end of the file"))?;
Ok((start as u64, len))
}
/// What an opener must do before reading a file's metadata: check the
@@ -498,11 +537,19 @@ fn image_block(data: &[u8], location: CacheImageLocation) -> Result<&[u8], Forma
/// ([`CacheImage::decode`]). `data` is the file from the superblock on, up
/// to its recorded end of file.
pub fn cache_image_state(data: &[u8], sb: &Superblock) -> Result<CacheImageState, FormatError> {
match read_superblock_extension(data, sb)? {
cache_image_state_in(&data, sb)
}
/// [`cache_image_state`] over any [`Storage`].
pub fn cache_image_state_in(
file: &dyn Storage,
sb: &Superblock,
) -> Result<CacheImageState, FormatError> {
match read_superblock_extension_in(file, sb)? {
Some(SuperblockExtension {
cache_image: Some(location),
..
}) => Ok(match CacheImage::decode(data, location, sb) {
}) => Ok(match CacheImage::decode_in(file, location, sb) {
Ok(image) => CacheImageState::Loaded(image),
Err(e) => CacheImageState::Unloadable(e),
}),
@@ -566,7 +613,7 @@ mod tests {
/// holds the given messages, padded to `len` bytes.
fn file_with_ext(messages: &[(u16, &[u8])], len: usize) -> Vec<u8> {
let mut body = Vec::new();
for (t, d) in messages {
for &(t, d) in messages {
let padded = d.len().div_ceil(8) * 8;
body.extend_from_slice(&t.to_le_bytes());
body.extend_from_slice(&(padded as u16).to_le_bytes());
@@ -809,4 +856,37 @@ mod tests {
// An entry cannot be its own parent.
assert!(load(image_with_deps(&[(40, b"C", 1, Some(40))])).is_err());
}
/// The extension and cache image decode identically through a
/// `read_at`-only storage, errors included.
#[test]
fn storage_parse_matches_slice_parse() {
use crate::storage::CountingStorage;
let img = image(&[(16, b"HEADER"), (40, b"NODE")]);
let mut with_image = file_with_ext(&[(MSG_MDCI, &mdci(256, img.len() as u64))], 256);
with_image.extend_from_slice(&img);
let mut bad_image = with_image.clone();
bad_image[256] = b'X';
let files = [
file_with_ext(&[(MSG_FSINFO, &fsinfo_v1(4096, false, 0))], 256),
file_with_ext(&[(MSG_FSINFO, &fsinfo_v1(256, false, 0))], 256),
file_with_ext(&[(MSG_MDCI, &mdci(128, 64))], 192),
file_with_ext(&[(MSG_MDCI, &mdci(0x10100, 0x1000_0000))], 2565),
file_with_ext(&[(MSG_FSINFO, &fsinfo_v1(4096, true, 12))], 60),
with_image,
bad_image,
];
for f in files {
let storage = CountingStorage::new(f.clone());
let sb = sb_v2(48);
assert_eq!(
read_superblock_extension_in(&storage, &sb),
read_superblock_extension(&f, &sb)
);
assert_eq!(cache_image_state_in(&storage, &sb), cache_image_state(&f, &sb));
if let Ok(CacheImageState::Loaded(image)) = cache_image_state(&f, &sb) {
assert_eq!(&*image.block_in(&storage).unwrap(), image.block(&f).unwrap());
}
}
}
}