| 1 | use crate::{RevisionIndex, Store}; |
| 2 | use std::io; |
| 3 | |
| 4 | pub(crate) fn read_exact( |
| 5 | read: &mut impl FnMut(u64, &mut [u8]) -> io::Result<usize>, |
| 6 | mut offset: u64, |
| 7 | mut output: &mut [u8], |
| 8 | ) -> io::Result<()> { |
| 9 | while !output.is_empty() { |
| 10 | match read(offset, output) { |
| 11 | Ok(0) => return Err(io::ErrorKind::UnexpectedEof.into()), |
| 12 | Ok(count) if count <= output.len() => { |
| 13 | offset += count as u64; |
| 14 | output = &mut output[count..]; |
| 15 | } |
| 16 | Ok(_) => return Err(io::ErrorKind::InvalidData.into()), |
| 17 | Err(error) if error.kind() == io::ErrorKind::Interrupted => {} |
| 18 | Err(error) => return Err(error), |
| 19 | } |
| 20 | } |
| 21 | Ok(()) |
| 22 | } |
| 23 | |
| 24 | /// Reads a bounded snapshot; the caller must provide fresh I/O and exclude in-place maintenance. |
| 25 | /// Includes unpublished trailing bytes so subsequent commits can validate the physical file. |
| 26 | /// `None` is a torn read or storage that does not validate. |
| 27 | pub fn read_snapshot( |
| 28 | read: impl FnMut(u64, &mut [u8]) -> io::Result<usize>, |
| 29 | limit: usize, |
| 30 | ) -> io::Result<Option<Vec<u8>>> { |
| 31 | let Some(bytes) = image(read, limit)? else { |
| 32 | return Ok(None); |
| 33 | }; |
| 34 | let valid = Store::parse(&bytes).is_ok_and(|store| { |
| 35 | store.checksum_mismatches.is_empty() |
| 36 | && RevisionIndex::parse(&store).is_ok_and(|index| index.validate_current().is_ok()) |
| 37 | }); |
| 38 | Ok(valid.then_some(bytes)) |
| 39 | } |
| 40 | |
| 41 | /// Reads stable storage and checksums without requiring traversable property references. |
| 42 | /// Used to inspect encrypted or incomplete documents; this does not establish edit readiness. |
| 43 | /// The caller must provide fresh I/O and exclude in-place maintenance, as for `read_snapshot`. |
| 44 | /// `None` is a torn read; stable storage that fails to parse or checksum is `InvalidData`. |
| 45 | pub fn read_storage_snapshot( |
| 46 | read: impl FnMut(u64, &mut [u8]) -> io::Result<usize>, |
| 47 | limit: usize, |
| 48 | ) -> io::Result<Option<Vec<u8>>> { |
| 49 | let Some(bytes) = image(read, limit)? else { |
| 50 | return Ok(None); |
| 51 | }; |
| 52 | let store = |
| 53 | Store::parse(&bytes).map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?; |
| 54 | if !store.checksum_mismatches.is_empty() { |
| 55 | return Err(io::Error::new( |
| 56 | io::ErrorKind::InvalidData, |
| 57 | "Checksum mismatch", |
| 58 | )); |
| 59 | } |
| 60 | Ok(Some(bytes)) |
| 61 | } |
| 62 | |
| 63 | /// The file's bytes, or `None` where its header changed or its storage ended early meanwhile. |
| 64 | fn image( |
| 65 | mut read: impl FnMut(u64, &mut [u8]) -> io::Result<usize>, |
| 66 | limit: usize, |
| 67 | ) -> io::Result<Option<Vec<u8>>> { |
| 68 | let mut header = [0; 1024]; |
| 69 | read_exact(&mut read, 0, &mut header)?; |
| 70 | let length = u64::from_le_bytes(header[196..204].try_into().unwrap()); |
| 71 | let length = usize::try_from(length) |
| 72 | .ok() |
| 73 | .filter(|length| (1024..=limit).contains(length)) |
| 74 | .ok_or(io::ErrorKind::InvalidData)?; |
| 75 | let mut bytes = Vec::new(); |
| 76 | bytes.try_reserve_exact(length).map_err(io::Error::other)?; |
| 77 | bytes.extend_from_slice(&header); |
| 78 | bytes.resize(length, 0); |
| 79 | if let Err(error) = read_exact(&mut read, 1024, &mut bytes[1024..]) { |
| 80 | // Native writers can shorten unused storage before publishing the new expected length. |
| 81 | return if error.kind() == io::ErrorKind::UnexpectedEof { |
| 82 | Ok(None) |
| 83 | } else { |
| 84 | Err(error) |
| 85 | }; |
| 86 | } |
| 87 | let mut tail = [0; 65536]; |
| 88 | loop { |
| 89 | let size = (limit - bytes.len()).clamp(1, tail.len()); |
| 90 | match read(bytes.len() as u64, &mut tail[..size]) { |
| 91 | Ok(0) => break, |
| 92 | Ok(count) if count <= size && count <= limit - bytes.len() => { |
| 93 | bytes.try_reserve_exact(count).map_err(io::Error::other)?; |
| 94 | bytes.extend_from_slice(&tail[..count]); |
| 95 | } |
| 96 | Ok(_) => return Err(io::ErrorKind::InvalidData.into()), |
| 97 | Err(error) if error.kind() == io::ErrorKind::Interrupted => {} |
| 98 | Err(error) => return Err(error), |
| 99 | } |
| 100 | } |
| 101 | let mut after = [0; 1024]; |
| 102 | read_exact(&mut read, 0, &mut after)?; |
| 103 | Ok((header == after).then_some(bytes)) |
| 104 | } |