use std::collections::BTreeMap;
use std::fs;
use std::io::{Read, Seek, SeekFrom, Write};
use crate::convert::TryToUsize;
use crate::error::{Error, FormatError};
use crate::source::{
BytesSource, MetadataCacheConfig, MetadataCacheStats, MetadataReadCache, Source,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum WriteBuffering {
Unbuffered,
Operation { page_size: u64, max_bytes: usize },
Session { page_size: u64, max_bytes: usize },
}
impl WriteBuffering {
const fn budget(self) -> Option<(u64, usize)> {
match self {
WriteBuffering::Unbuffered => None,
WriteBuffering::Operation {
page_size,
max_bytes,
}
| WriteBuffering::Session {
page_size,
max_bytes,
} => Some((page_size, max_bytes)),
}
}
}
#[cfg(test)]
pub(crate) mod disk_log {
use std::cell::RefCell;
pub(crate) enum DiskOp {
Write { offset: u64, bytes: Vec<u8> },
SetLen(u64),
}
impl DiskOp {
pub(crate) fn describe(&self) -> String {
match self {
DiskOp::Write { offset, bytes } => {
std::format!("write {offset}..{}", offset + bytes.len() as u64)
}
DiskOp::SetLen(len) => std::format!("set_len {len}"),
}
}
}
thread_local! {
static LOG: RefCell<Option<Vec<DiskOp>>> = const { RefCell::new(None) };
}
pub(crate) fn start() {
LOG.with(|l| *l.borrow_mut() = Some(Vec::new()));
}
pub(crate) fn take() -> Vec<DiskOp> {
LOG.with(|l| l.borrow_mut().take()).unwrap_or_default()
}
pub(crate) fn record_write(offset: u64, bytes: &[u8]) {
LOG.with(|l| {
if let Some(log) = l.borrow_mut().as_mut() {
log.push(DiskOp::Write {
offset,
bytes: bytes.to_vec(),
});
}
});
}
pub(crate) fn record_set_len(len: u64) {
LOG.with(|l| {
if let Some(log) = l.borrow_mut().as_mut() {
log.push(DiskOp::SetLen(len));
}
});
}
}
pub(crate) trait FileImage: Source + Send + Sync {
fn append(&mut self, bytes: &[u8]) -> Result<u64, Error>;
fn write_at(&mut self, offset: u64, bytes: &[u8]) -> Result<(), Error>;
fn truncate(&mut self, len: u64) -> Result<(), Error>;
fn sync_data(&mut self) -> Result<(), Error>;
fn sync_all(&mut self) -> Result<(), Error>;
fn ordering_barrier(&mut self) -> Result<(), Error>;
#[cfg(test)]
fn issued_write_order(&self) -> Vec<(u64, u64)>;
#[cfg(test)]
fn issued_writes(&self) -> u64 {
self.issued_write_order().len() as u64
}
#[cfg(test)]
fn issued_write_bytes(&self) -> u64 {
self.issued_write_order().iter().map(|&(_, n)| n).sum()
}
fn set_write_buffering(&mut self, mode: WriteBuffering) -> Result<(), Error>;
fn as_slice(&self) -> Option<&[u8]> {
None
}
}
pub(crate) struct BufferedWrites {
handle: fs::File,
mode: WriteBuffering,
runs: BTreeMap<u64, Vec<u8>>,
pending_bytes: usize,
on_disk_len: u64,
#[cfg(test)]
issued_order: Vec<(u64, u64)>,
}
impl BufferedWrites {
pub(crate) fn new(handle: fs::File, on_disk_len: u64) -> Self {
Self {
handle,
mode: WriteBuffering::Unbuffered,
runs: BTreeMap::new(),
pending_bytes: 0,
on_disk_len,
#[cfg(test)]
issued_order: Vec::new(),
}
}
#[cfg(test)]
pub(crate) fn issued_order(&self) -> &[(u64, u64)] {
&self.issued_order
}
pub(crate) fn handle(&self) -> &fs::File {
&self.handle
}
pub(crate) fn on_disk_len(&self) -> u64 {
self.on_disk_len
}
pub(crate) fn set_mode(&mut self, mode: WriteBuffering) -> Result<(), Error> {
self.flush()?;
self.mode = mode;
Ok(())
}
pub(crate) fn write_at(&mut self, offset: u64, bytes: &[u8]) -> Result<(), Error> {
if bytes.is_empty() {
return Ok(());
}
let Some((_, max_bytes)) = self.mode.budget() else {
return self.issue(offset, bytes);
};
if bytes.len() >= max_bytes {
self.flush()?;
return self.issue(offset, bytes);
}
self.absorb(offset, bytes);
if self.pending_bytes > max_bytes {
self.flush()?;
}
Ok(())
}
fn invariants_hold(&self) -> bool {
let mut total = 0usize;
let mut prev_end: Option<u64> = None;
for (&k, v) in &self.runs {
if v.is_empty() {
return false;
}
if prev_end.is_some_and(|end| k <= end) {
return false;
}
total += v.len();
prev_end = Some(k + v.len() as u64);
}
total == self.pending_bytes
}
fn absorb(&mut self, offset: u64, bytes: &[u8]) {
let mut lo = offset;
let mut hi = offset + bytes.len() as u64;
if let Some(k) = self.run_containing(offset, hi) {
let run = self.runs.get_mut(&k).expect("just enumerated");
#[expect(
clippy::cast_possible_truncation,
reason = "run_containing proved k <= offset and that the run reaches offset + bytes.len(), so this is an index into a buffer already resident, and a resident buffer's length is a usize"
)]
let at = (offset - k) as usize;
run[at..at + bytes.len()].copy_from_slice(bytes);
return;
}
if let Some(k) = self.run_ending_at(offset, hi) {
let run = self.runs.get_mut(&k).expect("just enumerated");
run.extend_from_slice(bytes);
self.pending_bytes += bytes.len();
debug_assert!(self.invariants_hold(), "absorb: extension path");
return;
}
if let Some((&k, v)) = self.runs.range(..lo).next_back() {
let end = k + v.len() as u64;
if end >= lo {
lo = k;
hi = hi.max(end);
}
}
let mut absorbed: Vec<u64> = Vec::new();
for (&k, v) in self.runs.range(lo..) {
if k > hi {
break;
}
hi = hi.max(k + v.len() as u64);
absorbed.push(k);
}
debug_assert!(
lo <= offset && hi >= offset + bytes.len() as u64,
"the merged span must contain the write that caused it"
);
let span = (hi - lo)
.to_usize()
.expect("a merged run is the union of buffers already in memory");
let mut merged = vec![0u8; span];
for k in absorbed {
let old = self.runs.remove(&k).expect("just enumerated");
self.pending_bytes -= old.len();
#[expect(
clippy::cast_possible_truncation,
reason = "every absorbed run starts within [lo, hi), which `merged` spans, so this indexes `merged`"
)]
let at = (k - lo) as usize;
merged[at..at + old.len()].copy_from_slice(&old);
}
#[expect(
clippy::cast_possible_truncation,
reason = "lo <= offset, asserted above, and `merged` spans [lo, hi) which contains the write"
)]
let at = (offset - lo) as usize;
merged[at..at + bytes.len()].copy_from_slice(bytes);
self.pending_bytes += merged.len();
self.runs.insert(lo, merged);
debug_assert!(self.invariants_hold(), "absorb: general merge");
}
fn run_containing(&self, offset: u64, end: u64) -> Option<u64> {
let (&k, v) = self.runs.range(..=offset).next_back()?;
(k + v.len() as u64 >= end).then_some(k)
}
fn run_ending_at(&self, offset: u64, end: u64) -> Option<u64> {
let (&k, v) = self.runs.range(..offset).next_back()?;
(k + v.len() as u64 == offset && self.runs.range(offset..=end).next().is_none())
.then_some(k)
}
fn walk_from(&self, offset: u64) -> u64 {
self.runs
.range(..=offset)
.next_back()
.map_or(offset, |(&k, _)| k)
}
fn covers(&self, offset: u64, end: u64) -> bool {
let mut at = offset;
for (&k, v) in self.runs.range(self.walk_from(offset)..) {
if at >= end {
return true;
}
if k > at {
return false;
}
at = at.max(k + v.len() as u64);
}
at >= end
}
pub(crate) fn overlay(&self, offset: u64, buf: &mut [u8]) {
if self.runs.is_empty() || buf.is_empty() {
return;
}
let end = offset + buf.len() as u64;
for (&k, v) in self.runs.range(self.walk_from(offset)..) {
if k >= end {
break;
}
let run_end = k + v.len() as u64;
if run_end <= offset {
continue;
}
let from = k.max(offset);
let to = run_end.min(end);
#[expect(
clippy::cast_possible_truncation,
reason = "from and to are clamped to both the read window and the run, so each delta is at most buf.len() or v.len() — lengths of buffers already in memory"
)]
let (buf_from, buf_to, run_from, run_to) = (
(from - offset) as usize,
(to - offset) as usize,
(from - k) as usize,
(to - k) as usize,
);
buf[buf_from..buf_to].copy_from_slice(&v[run_from..run_to]);
}
}
pub(crate) fn ordering_barrier(&mut self) -> Result<(), Error> {
match self.mode {
WriteBuffering::Unbuffered | WriteBuffering::Session { .. } => Ok(()),
WriteBuffering::Operation { .. } => self.flush(),
}
}
pub(crate) fn flush(&mut self) -> Result<(), Error> {
let page_size = self.mode.budget().map_or(1, |(p, _)| p).max(1);
while let Some((start, mut bytes)) = self.runs.pop_first() {
self.pending_bytes -= bytes.len();
while let Some((&next, _)) = self.runs.first_key_value() {
if next > self.on_disk_len
|| !same_page(start + bytes.len() as u64 - 1, next, page_size)
{
break;
}
let gap_at = start + bytes.len() as u64;
if gap_at < next {
let filled = bytes.len();
let gap = match (next - gap_at).to_usize() {
Ok(gap) => gap,
Err(e) => {
self.restore(start, bytes);
return Err(Error::Format(e));
}
};
bytes.resize(filled + gap, 0);
if let Err(e) =
read_at_handle(&self.handle, self.on_disk_len, gap_at, &mut bytes[filled..])
{
bytes.truncate(filled);
self.restore(start, bytes);
return Err(Error::Format(e));
}
}
let (_, tail) = self.runs.pop_first().expect("just peeked");
self.pending_bytes -= tail.len();
bytes.extend_from_slice(&tail);
}
if let Err(e) = self.issue(start, &bytes) {
self.restore(start, bytes);
return Err(e);
}
}
Ok(())
}
fn restore(&mut self, start: u64, bytes: Vec<u8>) {
self.pending_bytes += bytes.len();
self.runs.insert(start, bytes);
}
fn issue(&mut self, offset: u64, bytes: &[u8]) -> Result<(), Error> {
self.handle
.seek(SeekFrom::Start(offset))
.map_err(Error::Io)?;
self.handle.write_all(bytes).map_err(Error::Io)?;
#[cfg(test)]
self.issued_order.push((offset, bytes.len() as u64));
#[cfg(test)]
disk_log::record_write(offset, bytes);
self.on_disk_len = self.on_disk_len.max(offset + bytes.len() as u64);
Ok(())
}
pub(crate) fn set_len(&mut self, len: u64) -> Result<(), Error> {
self.handle.set_len(len).map_err(Error::Io)?;
#[cfg(test)]
disk_log::record_set_len(len);
self.discard_from(len);
self.on_disk_len = len;
self.flush()?;
Ok(())
}
fn discard_from(&mut self, len: u64) {
let doomed: Vec<u64> = self.runs.range(len..).map(|(&k, _)| k).collect();
for k in doomed {
let v = self.runs.remove(&k).expect("just enumerated");
self.pending_bytes -= v.len();
}
if let Some((&k, _)) = self.runs.range(..len).next_back() {
let v = self.runs.get_mut(&k).expect("just enumerated");
let keep = (len - k).to_usize().unwrap_or(usize::MAX);
if keep < v.len() {
self.pending_bytes -= v.len() - keep;
v.truncate(keep);
}
}
debug_assert!(self.invariants_hold(), "discard_from");
}
pub(crate) fn sync_data(&mut self) -> Result<(), Error> {
self.flush()?;
self.handle.flush().map_err(Error::Io)?;
self.handle.sync_data().map_err(Error::Io)?;
Ok(())
}
pub(crate) fn sync_all(&mut self) -> Result<(), Error> {
self.flush()?;
self.handle.flush().map_err(Error::Io)?;
self.handle.sync_all().map_err(Error::Io)?;
Ok(())
}
}
impl Drop for BufferedWrites {
fn drop(&mut self) {
let _ = self.flush();
}
}
const fn same_page(a: u64, b: u64, page_size: u64) -> bool {
a / page_size == b / page_size
}
pub(crate) struct MirrorImage {
writes: BufferedWrites,
data: Vec<u8>,
}
impl MirrorImage {
pub(crate) fn new(handle: fs::File, data: Vec<u8>) -> Self {
let on_disk_len = data.len() as u64;
Self {
writes: BufferedWrites::new(handle, on_disk_len),
data,
}
}
}
impl Source for MirrorImage {
fn len(&self) -> u64 {
self.data.len() as u64
}
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<(), FormatError> {
BytesSource::new(&self.data[..]).read_at(offset, buf)
}
}
impl FileImage for MirrorImage {
fn append(&mut self, bytes: &[u8]) -> Result<u64, Error> {
let addr = self.data.len() as u64;
self.writes.write_at(addr, bytes)?;
self.data.extend_from_slice(bytes);
Ok(addr)
}
fn write_at(&mut self, offset: u64, bytes: &[u8]) -> Result<(), Error> {
debug_assert!(
offset.saturating_add(bytes.len() as u64) <= self.len(),
"write_at past end-of-file: {offset}+{} > {}",
bytes.len(),
self.len()
);
let offset_usize = offset.to_usize()?;
self.writes.write_at(offset, bytes)?;
self.data[offset_usize..offset_usize + bytes.len()].copy_from_slice(bytes);
Ok(())
}
fn truncate(&mut self, len: u64) -> Result<(), Error> {
debug_assert!(
len <= self.len(),
"truncate would grow the image: {len} > {}",
self.len()
);
let len_usize = len.to_usize()?;
self.writes.set_len(len)?;
self.data.truncate(len_usize);
Ok(())
}
fn sync_data(&mut self) -> Result<(), Error> {
self.writes.sync_data()
}
fn sync_all(&mut self) -> Result<(), Error> {
self.writes.sync_all()
}
fn ordering_barrier(&mut self) -> Result<(), Error> {
self.writes.ordering_barrier()
}
#[cfg(test)]
fn issued_write_order(&self) -> Vec<(u64, u64)> {
self.writes.issued_order().to_vec()
}
fn set_write_buffering(&mut self, mode: WriteBuffering) -> Result<(), Error> {
self.writes.set_mode(mode)
}
fn as_slice(&self) -> Option<&[u8]> {
Some(&self.data)
}
}
pub(crate) fn read_at_handle(
handle: &fs::File,
len: u64,
offset: u64,
buf: &mut [u8],
) -> Result<(), FormatError> {
let end = offset
.checked_add(buf.len() as u64)
.ok_or(FormatError::OffsetOverflow {
offset,
length: buf.len() as u64,
})?;
if end > len {
return Err(FormatError::UnexpectedEof {
expected: end.to_usize().unwrap_or(usize::MAX),
available: len.to_usize().unwrap_or(usize::MAX),
});
}
let mut h = handle;
h.seek(SeekFrom::Start(offset))
.map_err(|e| FormatError::Source(std::format!("{e}")))?;
h.read_exact(buf)
.map_err(|e| FormatError::Source(std::format!("{e}")))?;
Ok(())
}
pub(crate) struct BorrowedHandle<'a> {
handle: &'a fs::File,
len: u64,
}
impl<'a> BorrowedHandle<'a> {
pub(crate) fn new(handle: &'a fs::File, len: u64) -> Self {
Self { handle, len }
}
}
impl Source for BorrowedHandle<'_> {
fn len(&self) -> u64 {
self.len
}
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<(), FormatError> {
read_at_handle(self.handle, self.len, offset, buf)
}
}
pub(crate) struct HandleImage {
writes: BufferedWrites,
len: u64,
metadata_cache: Option<(MetadataCacheConfig, std::sync::Mutex<MetadataReadCache>)>,
}
impl HandleImage {
pub(crate) fn new(handle: fs::File, len: u64, cache: MetadataCacheConfig) -> Self {
Self {
writes: BufferedWrites::new(handle, len),
len,
metadata_cache: cache
.is_enabled()
.then(|| (cache, std::sync::Mutex::new(MetadataReadCache::new()))),
}
}
fn invalidate(&self, offset: u64, len: u64) {
let Some((_, cache)) = &self.metadata_cache else {
return;
};
MetadataReadCache::locked(cache)
.invalidate_overlapping(offset, len.to_usize().unwrap_or(usize::MAX));
}
}
impl Source for HandleImage {
fn len(&self) -> u64 {
self.len
}
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<(), FormatError> {
let end = offset
.checked_add(buf.len() as u64)
.ok_or(FormatError::OffsetOverflow {
offset,
length: buf.len() as u64,
})?;
if end > self.len {
return Err(FormatError::UnexpectedEof {
expected: end.to_usize().unwrap_or(usize::MAX),
available: self.len.to_usize().unwrap_or(usize::MAX),
});
}
let on_disk = self.writes.on_disk_len();
let disk_end = on_disk.clamp(offset, end);
#[expect(
clippy::cast_possible_truncation,
reason = "disk_end is clamped to [offset, offset + buf.len()), so this is at \
most buf.len()"
)]
let take = (disk_end - offset) as usize;
if take > 0 {
read_at_handle(self.writes.handle(), on_disk, offset, &mut buf[..take])?;
}
buf[take..].fill(0);
debug_assert!(
disk_end >= end || self.writes.covers(disk_end, end),
"read of [{offset}, {end}) reaches past the file's {on_disk} bytes into a \
range no pending write covers, so it would return zeros"
);
self.writes.overlay(offset, buf);
Ok(())
}
fn read_metadata_at(&self, offset: u64, len: usize) -> Result<Vec<u8>, FormatError> {
let Some((config, cache)) = &self.metadata_cache else {
return self.read_exact_at(offset, len);
};
MetadataReadCache::read_through(cache, *config, offset, len, || {
self.read_exact_at(offset, len)
})
}
fn metadata_cache_stats(&self) -> Option<MetadataCacheStats> {
let (_, cache) = self.metadata_cache.as_ref()?;
Some(MetadataReadCache::locked(cache).stats())
}
fn reset_metadata_cache_stats(&self) {
if let Some((_, cache)) = &self.metadata_cache {
MetadataReadCache::locked(cache).reset_stats();
}
}
}
impl FileImage for HandleImage {
fn append(&mut self, bytes: &[u8]) -> Result<u64, Error> {
let addr = self.len;
self.invalidate(addr, bytes.len() as u64);
self.writes.write_at(addr, bytes)?;
self.len += bytes.len() as u64;
Ok(addr)
}
fn write_at(&mut self, offset: u64, bytes: &[u8]) -> Result<(), Error> {
let end = offset
.checked_add(bytes.len() as u64)
.filter(|&e| e <= self.len)
.ok_or(Error::Format(FormatError::UnexpectedEof {
expected: offset.to_usize().unwrap_or(usize::MAX),
available: self.len.to_usize().unwrap_or(usize::MAX),
}))?;
debug_assert!(end <= self.len);
self.invalidate(offset, bytes.len() as u64);
self.writes.write_at(offset, bytes)
}
fn truncate(&mut self, len: u64) -> Result<(), Error> {
debug_assert!(
len <= self.len,
"truncate would grow the image: {len} > {}",
self.len
);
self.invalidate(len, self.len.saturating_sub(len));
self.writes.set_len(len)?;
self.len = len;
Ok(())
}
fn sync_data(&mut self) -> Result<(), Error> {
self.writes.sync_data()
}
fn sync_all(&mut self) -> Result<(), Error> {
self.writes.sync_all()
}
fn ordering_barrier(&mut self) -> Result<(), Error> {
self.writes.ordering_barrier()
}
#[cfg(test)]
fn issued_write_order(&self) -> Vec<(u64, u64)> {
self.writes.issued_order().to_vec()
}
fn set_write_buffering(&mut self, mode: WriteBuffering) -> Result<(), Error> {
self.writes.set_mode(mode)
}
}
#[cfg(test)]
pub(crate) struct CountingImage {
inner: Box<dyn FileImage>,
read_bytes: std::sync::Arc<std::sync::atomic::AtomicU64>,
syncs: std::sync::Arc<std::sync::atomic::AtomicU64>,
}
#[cfg(test)]
impl CountingImage {
pub(crate) fn new(
inner: Box<dyn FileImage>,
read_bytes: std::sync::Arc<std::sync::atomic::AtomicU64>,
syncs: std::sync::Arc<std::sync::atomic::AtomicU64>,
) -> Self {
Self {
inner,
read_bytes,
syncs,
}
}
fn count_sync(&self) {
self.syncs
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
}
#[cfg(test)]
impl Source for CountingImage {
fn len(&self) -> u64 {
self.inner.len()
}
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<(), FormatError> {
self.read_bytes
.fetch_add(buf.len() as u64, std::sync::atomic::Ordering::Relaxed);
self.inner.read_at(offset, buf)
}
}
#[cfg(test)]
impl FileImage for CountingImage {
fn append(&mut self, bytes: &[u8]) -> Result<u64, Error> {
self.inner.append(bytes)
}
fn write_at(&mut self, offset: u64, bytes: &[u8]) -> Result<(), Error> {
self.inner.write_at(offset, bytes)
}
fn truncate(&mut self, len: u64) -> Result<(), Error> {
self.inner.truncate(len)
}
fn sync_data(&mut self) -> Result<(), Error> {
self.count_sync();
self.inner.sync_data()
}
fn sync_all(&mut self) -> Result<(), Error> {
self.count_sync();
self.inner.sync_all()
}
fn ordering_barrier(&mut self) -> Result<(), Error> {
self.inner.ordering_barrier()
}
fn issued_write_order(&self) -> Vec<(u64, u64)> {
self.inner.issued_write_order()
}
fn set_write_buffering(&mut self, mode: WriteBuffering) -> Result<(), Error> {
self.inner.set_write_buffering(mode)
}
fn as_slice(&self) -> Option<&[u8]> {
self.inner.as_slice()
}
}
#[cfg(test)]
pub(crate) struct TornWriteImage {
inner: MirrorImage,
fails: core::ops::Range<u64>,
struck: bool,
}
#[cfg(test)]
impl TornWriteImage {
pub(crate) fn new(inner: MirrorImage, fails: core::ops::Range<u64>) -> Self {
Self {
inner,
fails,
struck: false,
}
}
fn hits(&self, offset: u64, len: usize) -> bool {
offset < self.fails.end && self.fails.start < offset.saturating_add(len as u64)
}
fn failure() -> Error {
Error::Io(std::io::Error::other("simulated device write failure"))
}
}
#[cfg(test)]
impl Source for TornWriteImage {
fn len(&self) -> u64 {
self.inner.len()
}
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<(), FormatError> {
self.inner.read_at(offset, buf)
}
}
#[cfg(test)]
impl FileImage for TornWriteImage {
fn append(&mut self, bytes: &[u8]) -> Result<u64, Error> {
self.inner.append(bytes)
}
fn write_at(&mut self, offset: u64, bytes: &[u8]) -> Result<(), Error> {
if !self.hits(offset, bytes.len()) {
return self.inner.write_at(offset, bytes);
}
if self.struck {
return Err(Self::failure());
}
self.struck = true;
self.inner.write_at(offset, bytes)?;
Err(Self::failure())
}
fn truncate(&mut self, len: u64) -> Result<(), Error> {
self.inner.truncate(len)
}
fn sync_data(&mut self) -> Result<(), Error> {
self.inner.sync_data()
}
fn sync_all(&mut self) -> Result<(), Error> {
self.inner.sync_all()
}
fn ordering_barrier(&mut self) -> Result<(), Error> {
self.inner.ordering_barrier()
}
fn issued_write_order(&self) -> Vec<(u64, u64)> {
self.inner.issued_write_order()
}
fn set_write_buffering(&mut self, mode: WriteBuffering) -> Result<(), Error> {
self.inner.set_write_buffering(mode)
}
fn as_slice(&self) -> Option<&[u8]> {
self.inner.as_slice()
}
}
#[cfg(test)]
pub(crate) struct SourceOnlyImage(MirrorImage);
#[cfg(test)]
impl SourceOnlyImage {
pub(crate) fn new(inner: MirrorImage) -> Self {
Self(inner)
}
}
#[cfg(test)]
impl Source for SourceOnlyImage {
fn len(&self) -> u64 {
self.0.len()
}
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<(), FormatError> {
self.0.read_at(offset, buf)
}
}
#[cfg(test)]
impl FileImage for SourceOnlyImage {
fn append(&mut self, bytes: &[u8]) -> Result<u64, Error> {
self.0.append(bytes)
}
fn write_at(&mut self, offset: u64, bytes: &[u8]) -> Result<(), Error> {
self.0.write_at(offset, bytes)
}
fn truncate(&mut self, len: u64) -> Result<(), Error> {
self.0.truncate(len)
}
fn sync_data(&mut self) -> Result<(), Error> {
self.0.sync_data()
}
fn sync_all(&mut self) -> Result<(), Error> {
self.0.sync_all()
}
fn ordering_barrier(&mut self) -> Result<(), Error> {
self.0.ordering_barrier()
}
fn issued_write_order(&self) -> Vec<(u64, u64)> {
self.0.issued_write_order()
}
fn set_write_buffering(&mut self, mode: WriteBuffering) -> Result<(), Error> {
self.0.set_write_buffering(mode)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Copy, Debug)]
enum Backing {
Mirror,
Handle,
}
const BACKINGS: [Backing; 2] = [Backing::Mirror, Backing::Handle];
fn image(
dir: &std::path::Path,
initial: &[u8],
backing: Backing,
) -> (std::path::PathBuf, Box<dyn FileImage>) {
let path = dir.join(std::format!("{backing:?}.bin"));
std::fs::write(&path, initial).unwrap();
let handle = fs::OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.unwrap();
let img: Box<dyn FileImage> = match backing {
Backing::Mirror => Box::new(MirrorImage::new(handle, initial.to_vec())),
Backing::Handle => Box::new(HandleImage::new(
handle,
initial.len() as u64,
MetadataCacheConfig::new(64 * 1024),
)),
};
(path, img)
}
fn bytes(img: &dyn FileImage) -> Vec<u8> {
let mut buf = vec![0u8; img.len().to_usize().unwrap()];
img.read_at(0, &mut buf).unwrap();
buf
}
fn assert_in_sync(path: &std::path::Path, img: &dyn FileImage, backing: Backing) {
let on_disk = std::fs::read(path).unwrap();
assert_eq!(
img.len(),
on_disk.len() as u64,
"{backing:?}: end-of-file disagrees with the file"
);
assert_eq!(
bytes(img),
on_disk,
"{backing:?}: reads disagree with the file"
);
if let Some(slice) = img.as_slice() {
assert_eq!(
slice,
&on_disk[..],
"{backing:?}: the slice disagrees with the file"
);
}
}
#[test]
fn append_returns_the_pre_append_end_and_extends_by_exactly_the_length() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
let (path, mut img) = image(dir.path(), b"abcd", backing);
let addr = img.append(b"XYZ").unwrap();
assert_eq!(addr, 4, "{backing:?}: append must report where it wrote");
assert_eq!(
img.len(),
7,
"{backing:?}: append must extend len by exactly bytes.len()"
);
assert_in_sync(&path, img.as_ref(), backing);
}
}
#[test]
fn write_at_overwrites_both_sides_in_place() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
let (path, mut img) = image(dir.path(), b"abcdef", backing);
img.write_at(2, b"ZZ").unwrap();
assert_eq!(bytes(img.as_ref()), b"abZZef");
assert_eq!(
img.len(),
6,
"{backing:?}: an in-place write must not move end-of-file"
);
assert_in_sync(&path, img.as_ref(), backing);
}
}
#[test]
fn truncate_shrinks_both_sides() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
let (path, mut img) = image(dir.path(), b"abcdef", backing);
img.truncate(2).unwrap();
assert_eq!(bytes(img.as_ref()), b"ab");
assert_eq!(img.len(), 2, "{backing:?}");
assert_in_sync(&path, img.as_ref(), backing);
}
}
#[test]
fn append_after_truncate_lands_at_the_new_end() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
let (path, mut img) = image(dir.path(), b"abcdef", backing);
img.truncate(3).unwrap();
let addr = img.append(b"Z").unwrap();
assert_eq!(addr, 3, "{backing:?}");
assert_eq!(bytes(img.as_ref()), b"abcZ");
assert_in_sync(&path, img.as_ref(), backing);
}
}
#[test]
fn reads_observe_writes_immediately() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
let (_path, mut img) = image(dir.path(), b"abcdef", backing);
img.write_at(0, b"ZY").unwrap();
img.append(b"!").unwrap();
let mut buf = [0u8; 3];
img.read_at(0, &mut buf).unwrap();
assert_eq!(&buf, b"ZYc", "{backing:?}");
img.read_at(6, &mut buf[..1]).unwrap();
assert_eq!(buf[0], b'!', "{backing:?}");
}
}
#[test]
fn cached_reads_observe_writes_immediately() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
let (_path, mut img) = image(dir.path(), b"abcdef", backing);
assert_eq!(img.read_metadata_at(0, 4).unwrap(), b"abcd", "{backing:?}");
img.write_at(1, b"ZZ").unwrap();
assert_eq!(
img.read_metadata_at(0, 4).unwrap(),
b"aZZd",
"{backing:?}: a cached read outlived the write that overwrote it"
);
}
}
#[test]
fn a_cached_read_does_not_survive_being_truncated_and_appended_over() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
let (_path, mut img) = image(dir.path(), b"abcdef", backing);
assert_eq!(img.read_metadata_at(4, 2).unwrap(), b"ef", "{backing:?}");
img.truncate(4).unwrap();
img.append(b"ZZ").unwrap();
assert_eq!(
img.read_metadata_at(4, 2).unwrap(),
b"ZZ",
"{backing:?}: a read cached before the truncate survived the append"
);
}
}
#[test]
fn reads_past_end_of_file_are_refused() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
let (_path, img) = image(dir.path(), b"abcd", backing);
let mut buf = [0u8; 2];
assert!(img.read_at(3, &mut buf).is_err(), "{backing:?}");
}
}
const PAGE: u64 = 64;
const GATHERED: WriteBuffering = WriteBuffering::Operation {
page_size: PAGE,
max_bytes: 4096,
};
fn gathering(
dir: &std::path::Path,
initial: &[u8],
backing: Backing,
mode: WriteBuffering,
) -> (std::path::PathBuf, Box<dyn FileImage>) {
gathering_named(dir, "g", initial, backing, mode)
}
fn gathering_named(
dir: &std::path::Path,
name: &str,
initial: &[u8],
backing: Backing,
mode: WriteBuffering,
) -> (std::path::PathBuf, Box<dyn FileImage>) {
let path = dir.join(std::format!("{name}_{backing:?}.bin"));
std::fs::write(&path, initial).unwrap();
let handle = fs::OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.unwrap();
let mut img: Box<dyn FileImage> = match backing {
Backing::Mirror => Box::new(MirrorImage::new(handle, initial.to_vec())),
Backing::Handle => Box::new(HandleImage::new(
handle,
initial.len() as u64,
MetadataCacheConfig::new(64 * 1024),
)),
};
img.set_write_buffering(mode).unwrap();
(path, img)
}
#[test]
fn a_gathered_write_reads_back_before_it_reaches_the_file() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
let (path, mut img) = gathering(dir.path(), b"abcdef", backing, GATHERED);
img.write_at(1, b"ZZ").unwrap();
img.append(b"gh").unwrap();
assert_eq!(bytes(img.as_ref()), b"aZZdefgh", "{backing:?}");
assert_eq!(
img.read_metadata_at(0, 4).unwrap(),
b"aZZd",
"{backing:?}: the cached read path must see it too"
);
assert_eq!(
std::fs::read(&path).unwrap(),
b"abcdef",
"{backing:?}: nothing was to be issued yet"
);
let mut inner = [0u8; 1];
img.read_at(2, &mut inner).unwrap();
assert_eq!(
&inner, b"Z",
"{backing:?}: a read starting inside a pending run missed it"
);
img.ordering_barrier().unwrap();
assert_in_sync(&path, img.as_ref(), backing);
}
}
#[test]
fn writes_sharing_a_page_are_issued_as_one() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
let initial: Vec<u8> = (0..PAGE as u8 * 3).collect();
let (path, mut img) =
gathering_named(dir.path(), "one_page", &initial, backing, GATHERED);
let before = img.issued_writes();
img.write_at(2, b"XX").unwrap();
img.write_at(40, b"YY").unwrap();
img.ordering_barrier().unwrap();
assert_eq!(
img.issued_writes() - before,
1,
"{backing:?}: two writes in one page are one write"
);
let mut want = initial.clone();
want[2..4].copy_from_slice(b"XX");
want[40..42].copy_from_slice(b"YY");
assert_eq!(
std::fs::read(&path).unwrap(),
want,
"{backing:?}: joining two runs must not disturb the bytes between them"
);
let (path2, mut img2) =
gathering_named(dir.path(), "two_pages", &initial, backing, GATHERED);
let before = img2.issued_writes();
img2.write_at(2, b"XX").unwrap();
img2.write_at(2 + PAGE, b"YY").unwrap();
img2.ordering_barrier().unwrap();
assert_eq!(
img2.issued_writes() - before,
2,
"{backing:?}: two writes a page apart stay two"
);
let mut want2 = initial.clone();
want2[2..4].copy_from_slice(b"XX");
want2[PAGE as usize + 2..PAGE as usize + 4].copy_from_slice(b"YY");
assert_eq!(std::fs::read(&path2).unwrap(), want2, "{backing:?}");
}
}
#[test]
fn a_later_write_wins_over_the_gathered_one_it_covers() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
let (path, mut img) = gathering(dir.path(), b"abcdefgh", backing, GATHERED);
img.write_at(2, b"1111").unwrap();
img.write_at(3, b"22").unwrap();
img.ordering_barrier().unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"ab1221gh", "{backing:?}");
assert_in_sync(&path, img.as_ref(), backing);
}
}
#[test]
fn a_barrier_issues_what_was_gathered() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
let (path, mut img) = gathering(dir.path(), b"abcdef", backing, GATHERED);
img.write_at(0, b"Z").unwrap();
img.sync_data().unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"Zbcdef", "{backing:?}");
img.write_at(1, b"Y").unwrap();
img.sync_all().unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"ZYcdef", "{backing:?}");
}
}
#[test]
fn truncate_discards_the_gathered_bytes_past_the_cut_rather_than_writing_them() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
let (path, mut img) = gathering(dir.path(), b"abcdefgh", backing, GATHERED);
img.append(&[9u8; 200]).unwrap();
let before = img.issued_writes();
img.truncate(8).unwrap();
assert_eq!(
img.issued_writes(),
before,
"{backing:?}: bytes about to stop existing were written out first"
);
assert_eq!(img.len(), 8, "{backing:?}");
assert_eq!(std::fs::read(&path).unwrap(), b"abcdefgh", "{backing:?}");
let before = img.issued_write_bytes();
img.write_at(1, b"ZZZZZZ").unwrap();
img.truncate(4).unwrap();
assert_eq!(
img.issued_write_bytes() - before,
3,
"{backing:?}: the part of the run past the cut was written anyway"
);
assert_eq!(std::fs::read(&path).unwrap(), b"aZZZ", "{backing:?}");
assert_in_sync(&path, img.as_ref(), backing);
}
}
#[test]
fn a_truncate_leaves_the_real_length_where_a_later_append_can_read_back() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
let (_path, mut img) = gathering(dir.path(), b"abcdefgh", backing, GATHERED);
img.truncate(4).unwrap();
img.append(b"WXYZ").unwrap();
let mut buf = [0u8; 4];
img.read_at(4, &mut buf)
.unwrap_or_else(|e| panic!("{backing:?}: reading the appended range failed: {e}"));
assert_eq!(&buf, b"WXYZ", "{backing:?}");
assert_eq!(bytes(img.as_ref()), b"abcdWXYZ", "{backing:?}");
}
}
#[test]
fn the_general_merge_also_lets_the_later_write_win() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
let (path, mut img) = gathering(dir.path(), b"abcdefghij", backing, GATHERED);
img.write_at(0, b"111").unwrap();
img.write_at(6, b"222").unwrap();
img.write_at(2, b"XXXXX").unwrap();
img.ordering_barrier().unwrap();
assert_eq!(
std::fs::read(&path).unwrap(),
b"11XXXXX22j",
"{backing:?}: the general merge must let the later write win"
);
assert_in_sync(&path, img.as_ref(), backing);
}
}
#[test]
fn session_retention_survives_an_ordering_barrier_and_operation_retention_does_not() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
let held = WriteBuffering::Session {
page_size: PAGE,
max_bytes: 4096,
};
let (path, mut img) = gathering_named(dir.path(), "held", b"abcdef", backing, held);
img.write_at(0, b"Z").unwrap();
img.ordering_barrier().unwrap();
assert_eq!(
std::fs::read(&path).unwrap(),
b"abcdef",
"{backing:?}: a page buffer must survive an ordering barrier"
);
img.sync_all().unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"Zbcdef", "{backing:?}");
let (released, mut img) =
gathering_named(dir.path(), "released", b"abcdef", backing, GATHERED);
img.write_at(0, b"Z").unwrap();
img.ordering_barrier().unwrap();
assert_eq!(
std::fs::read(&released).unwrap(),
b"Zbcdef",
"{backing:?}: operation retention must release at an ordering barrier"
);
}
}
#[test]
fn the_budget_drains_a_buffer_that_would_outgrow_it() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
let (path, mut img) = gathering(
dir.path(),
&vec![0u8; 4096],
backing,
WriteBuffering::Session {
page_size: PAGE,
max_bytes: 100,
},
);
for page in 0..3u64 {
img.write_at(page * PAGE, &[7u8; 40]).unwrap();
}
assert_ne!(
std::fs::read(&path).unwrap(),
vec![0u8; 4096],
"{backing:?}: the budget must have forced a drain"
);
img.sync_all().unwrap();
assert_in_sync(&path, img.as_ref(), backing);
}
}
#[test]
fn a_failed_truncate_keeps_the_writes_it_would_have_discarded() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("read_only");
std::fs::write(&path, b"abcdefgh").unwrap();
let mut writes = BufferedWrites::new(fs::File::open(&path).unwrap(), 8);
writes.set_mode(GATHERED).unwrap();
writes.write_at(8, &[9u8; 200]).unwrap();
assert_eq!(
writes.pending_bytes, 200,
"the write must be held, not issued"
);
assert!(
writes.set_len(8).is_err(),
"a read-only handle must refuse set_len, or this proves nothing"
);
assert_eq!(
writes.pending_bytes, 200,
"a truncate that failed discarded the writes it never doomed"
);
assert_eq!(
std::fs::read(&path).unwrap(),
b"abcdefgh",
"a failed truncate must not have written anything either"
);
}
#[test]
fn a_failed_flush_keeps_its_writes_pending() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("readonly.bin");
std::fs::write(&path, b"abcdefgh").unwrap();
let handle = fs::OpenOptions::new().read(true).open(&path).unwrap();
let mut img = MirrorImage::new(handle, b"abcdefgh".to_vec());
img.set_write_buffering(GATHERED).unwrap();
img.write_at(0, b"ZZ").unwrap();
assert!(
img.sync_all().is_err(),
"a write to a read-only handle must fail"
);
assert!(
img.sync_all().is_err(),
"the retry must report the failure too, not an empty buffer's success"
);
assert_eq!(
std::fs::read(&path).unwrap(),
b"abcdefgh",
"nothing should have reached the file"
);
let write_only = fs::OpenOptions::new().write(true).open(&path).unwrap();
let mut gapped = MirrorImage::new(write_only, b"abcdefgh".to_vec());
gapped.set_write_buffering(GATHERED).unwrap();
gapped.write_at(0, b"X").unwrap();
gapped.write_at(4, b"Y").unwrap();
assert!(
gapped.sync_all().is_err(),
"the gap read must fail on a handle that cannot read"
);
assert!(
gapped.sync_all().is_err(),
"and the retry must still report it rather than an empty buffer"
);
assert_eq!(
std::fs::read(&path).unwrap(),
b"abcdefgh",
"a failed gap read must not have written a partial join"
);
let write_only = fs::OpenOptions::new().write(true).open(&path).unwrap();
let mut w = BufferedWrites::new(write_only, 8);
w.set_mode(GATHERED).unwrap();
w.write_at(0, b"X").unwrap();
w.write_at(4, b"Y").unwrap();
assert!(w.flush().is_err(), "the gap read must fail");
assert_eq!(
w.pending_bytes, 2,
"the run kept the padding the failed gap read added"
);
assert_eq!(
w.runs.get(&0).map(Vec::len),
Some(1),
"the restored run must end where it did, clear of its neighbour"
);
let writable = fs::OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.unwrap();
let mut recovered = MirrorImage::new(writable, b"abcdefgh".to_vec());
recovered.set_write_buffering(GATHERED).unwrap();
recovered.write_at(0, b"ZZ").unwrap();
recovered.sync_all().unwrap();
assert_eq!(std::fs::read(&path).unwrap(), b"ZZcdefgh");
}
#[test]
fn dropping_the_image_issues_what_it_still_holds() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
let (path, mut img) = gathering(dir.path(), b"abcdef", backing, GATHERED);
img.write_at(0, b"Z").unwrap();
drop(img);
assert_eq!(std::fs::read(&path).unwrap(), b"Zbcdef", "{backing:?}");
}
}
#[test]
fn only_the_mirror_offers_a_whole_file_slice() {
let dir = tempfile::tempdir().unwrap();
let (_p1, mirror) = image(dir.path(), b"abcdef", Backing::Mirror);
let (_p2, handle) = image(dir.path(), b"abcdef", Backing::Handle);
assert_eq!(mirror.as_slice(), Some(&b"abcdef"[..]));
assert!(handle.as_slice().is_none());
}
#[test]
fn source_only_withholds_the_slice_but_reads_the_same() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("source_only.bin");
std::fs::write(&path, b"abcdef").unwrap();
let handle = fs::OpenOptions::new()
.read(true)
.write(true)
.open(&path)
.unwrap();
let mut only = SourceOnlyImage::new(MirrorImage::new(handle, b"abcdef".to_vec()));
assert!(only.as_slice().is_none(), "the slice must be withheld");
only.write_at(1, b"Z").unwrap();
only.append(b"gh").unwrap();
assert_eq!(only.len(), 8);
let mut buf = [0u8; 8];
only.read_at(0, &mut buf).unwrap();
assert_eq!(&buf, b"aZcdefgh");
}
struct Xorshift(u64);
impl Xorshift {
fn next(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
fn upto(&mut self, n: u64) -> u64 {
self.next() % n
}
}
const MIXES: [WriteBuffering; 5] = [
WriteBuffering::Unbuffered,
WriteBuffering::Operation {
page_size: 1,
max_bytes: 4096,
},
WriteBuffering::Operation {
page_size: 16,
max_bytes: 96,
},
WriteBuffering::Operation {
page_size: 64,
max_bytes: 4096,
},
WriteBuffering::Operation {
page_size: 4096,
max_bytes: 1 << 20,
},
];
#[test]
fn a_random_operation_sequence_matches_a_byte_model() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
for (mi, mode) in MIXES.into_iter().enumerate() {
for seed in 0..8u64 {
let initial: Vec<u8> = (0..300u32).map(|i| (i % 251) as u8).collect();
let sub = dir.path().join(std::format!("m{mi}s{seed}"));
std::fs::create_dir_all(&sub).unwrap();
let (path, mut img) = gathering(&sub, &initial, backing, mode);
let mut model = initial.clone();
let mut rng = Xorshift(0x9E37_79B9_7F4A_7C15 ^ (seed << 32) ^ (mi as u64));
let at =
|step: u32| std::format!("{backing:?} mode {mi} seed {seed} step {step}");
let fsync_at = rng.upto(300) as u32;
for step in 0..300u32 {
match rng.upto(10) {
0..=3 if !model.is_empty() => {
let len = 1 + rng.upto(48).min(model.len() as u64 - 1);
let offset = rng.upto(model.len() as u64 - len + 1);
let bytes = vec![(rng.next() & 0xff) as u8; len as usize];
img.write_at(offset, &bytes).unwrap();
model[offset as usize..(offset + len) as usize]
.copy_from_slice(&bytes);
}
4..=6 => {
let bytes =
vec![(rng.next() & 0xff) as u8; 1 + rng.upto(80) as usize];
let placed = img.append(&bytes).unwrap();
assert_eq!(placed, model.len() as u64, "{}: append", at(step));
model.extend_from_slice(&bytes);
}
7 => {
let keep = rng.upto(model.len() as u64 + 1);
img.truncate(keep).unwrap();
model.truncate(keep as usize);
}
_ => {
if step == fsync_at {
img.sync_data().unwrap();
} else {
img.ordering_barrier().unwrap();
}
assert_eq!(
std::fs::read(&path).unwrap(),
model,
"{}: a barrier left the file disagreeing with the model",
at(step)
);
}
}
assert_eq!(img.len(), model.len() as u64, "{}: length", at(step));
assert_eq!(bytes(img.as_ref()), model, "{}: reads", at(step));
if let Some(slice) = img.as_slice() {
assert_eq!(slice, &model[..], "{}: slice", at(step));
}
}
img.sync_all().unwrap();
assert_in_sync(&path, img.as_ref(), backing);
}
}
}
}
#[test]
fn every_window_over_a_buffered_image_matches_the_model() {
let dir = tempfile::tempdir().unwrap();
for backing in BACKINGS {
let initial: Vec<u8> = (0..200u32).map(|i| (i % 251) as u8).collect();
let (_, mut img) = gathering(dir.path(), &initial, backing, GATHERED);
let mut model = initial.clone();
let put = |img: &mut Box<dyn FileImage>, model: &mut Vec<u8>, at: u64, b: &[u8]| {
img.write_at(at, b).unwrap();
model[at as usize..at as usize + b.len()].copy_from_slice(b);
};
put(&mut img, &mut model, 30, &[0xA1; 10]);
put(&mut img, &mut model, 40, &[0xA2; 10]);
put(&mut img, &mut model, 90, &[0xB0; 1]);
put(&mut img, &mut model, 130, &[0xC0; 20]);
let tail = [0xD0u8; 30];
img.append(&tail).unwrap();
model.extend_from_slice(&tail);
let len = model.len() as u64;
assert_eq!(img.len(), len);
for start in 0..=len {
for end in start..=len {
let mut buf = vec![0u8; (end - start) as usize];
img.read_at(start, &mut buf).unwrap();
assert_eq!(
buf,
&model[start as usize..end as usize],
"{backing:?}: window {start}..{end}"
);
}
}
}
}
}