format: read fractal heaps over Storage
FractalHeapHeader::parse_in reads the header as one window (a second, longer one when it holds an I/O filter pipeline); read_managed_object_in reads direct blocks, indirect blocks (one window up to the last child entry) and huge objects with bounded reads. The &[u8] methods are wrappers. A huge object indexed by the huge-object v2 B-tree, which is not converted yet, is a clean ContiguousStorageRequired error on a backend without the whole file in memory (after the "no index" check, so the error order is unchanged). storage::Window (crate-internal) reads a window of a structure and reports bounds failures exactly as the whole-file ensure_len did, and a short read inside the file is now a Storage error rather than an EOF. New tests: headers (with and without a filter pipeline) cut at every length, and managed objects in a direct root and through an indirect root, huge objects with direct IDs and tiny objects, give identical results through a read_at-only CountingStorage. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -206,11 +206,52 @@ pub fn read_exact_at(
|
||||
if bytes.len() < len {
|
||||
// The storage shrank or the backend served a short read inside the
|
||||
// file: never parse a partial structure.
|
||||
return Err(eof());
|
||||
return Err(short_read());
|
||||
}
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
fn short_read() -> FormatError {
|
||||
FormatError::Storage(
|
||||
"short read inside the file (the storage shrank or the backend failed)".into(),
|
||||
)
|
||||
}
|
||||
|
||||
/// A window of the file: up to `max` bytes read at `base`, fewer only at
|
||||
/// the end of the file. Its [`Window::ensure`] reports a bounds failure
|
||||
/// exactly as the whole-file check `ensure_len(file_data, base + rel, n)`
|
||||
/// did — with the absolute position and the file's length — as long as
|
||||
/// every position checked lies within the `max` bytes the window was asked
|
||||
/// for: then a position past the window is past the end of the file.
|
||||
pub(crate) struct Window<'a> {
|
||||
/// The bytes, from `base` on.
|
||||
pub bytes: Cow<'a, [u8]>,
|
||||
base: usize,
|
||||
file_len: usize,
|
||||
}
|
||||
|
||||
impl<'a> Window<'a> {
|
||||
/// Read up to `max` bytes at `base`.
|
||||
pub fn read(file: &'a dyn Storage, base: u64, max: usize) -> Result<Self, FormatError> {
|
||||
Ok(Window {
|
||||
bytes: read_upto(file, base, max)?,
|
||||
base: usize::try_from(base).unwrap_or(usize::MAX),
|
||||
file_len: len_usize(file),
|
||||
})
|
||||
}
|
||||
|
||||
/// Check that `[rel, rel + needed)` (relative to `base`) is in the file.
|
||||
pub fn ensure(&self, rel: usize, needed: usize) -> Result<(), FormatError> {
|
||||
match rel.checked_add(needed) {
|
||||
Some(end) if end <= self.bytes.len() => Ok(()),
|
||||
_ => Err(FormatError::UnexpectedEof {
|
||||
expected: self.base.saturating_add(rel).saturating_add(needed),
|
||||
available: self.file_len,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Up to `max` bytes from `offset` on: fewer only at the end of the
|
||||
/// storage. For structures whose size is only known once their prefix has
|
||||
/// been parsed and whose parsers bound-check what they are given.
|
||||
@@ -224,9 +265,7 @@ pub fn read_upto(
|
||||
let len = usize::try_from(avail).map_or(max, |a| a.min(max));
|
||||
let bytes = file.read_at(offset, len)?;
|
||||
if bytes.len() < len {
|
||||
return Err(FormatError::Storage(
|
||||
"short read inside the file (the storage shrank or the backend failed)".into(),
|
||||
));
|
||||
return Err(short_read());
|
||||
}
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user