1use crate::{
4 io::{open_recovery_source, FileIo},
5 page::{PageKind, PageRef, PAGE_SIZE},
6 Error, Result,
7};
8use std::path::Path;
9
10#[derive(Clone, Copy, Debug)]
11pub struct LeafCandidate<'a> {
12 pub page_no: u32,
13 pub generation: u64,
14 pub slot: usize,
15 pub key: &'a [u8],
16 pub stored_value: &'a [u8],
17 pub overflow: bool,
18}
19pub enum LeafEvent<'a> {
20 Record(LeafCandidate<'a>),
21 DamagedPage { page_no: u32 },
22 MalformedCell { page_no: u32, slot: usize },
23}
24#[derive(Debug, Default)]
25pub struct LeafScanReport {
26 pub pages: u64,
27 pub damaged_pages: u64,
28 pub malformed_cells: u64,
29 pub truncated_tail_bytes: u64,
30}
31pub struct CandidateReader {
32 file: Box<dyn FileIo>,
33}
34impl CandidateReader {
35 pub fn from_file(file: Box<dyn FileIo>) -> Self { Self { file } }
37 pub fn open(source: &Path) -> Result<Self> {
38 Ok(Self {
39 file: open_recovery_source(&source.join("data"))?,
40 })
41 }
42 pub fn scan<E: From<Error>>(
45 &self,
46 tree_id: u16,
47 mut visitor: impl FnMut(LeafEvent<'_>) -> std::result::Result<(), E>,
48 ) -> std::result::Result<LeafScanReport, E> {
49 let len = self.file.len()?;
50 let pages = len / PAGE_SIZE as u64;
51 if pages > u32::MAX as u64 {
52 return Err(Error::TooLarge.into());
53 }
54 let mut result = LeafScanReport {
55 truncated_tail_bytes: len % PAGE_SIZE as u64,
56 ..Default::default()
57 };
58 let mut bytes = [0; PAGE_SIZE];
59 for no in 0..pages as u32 {
60 self.file
61 .read_at(&mut bytes, no as u64 * PAGE_SIZE as u64)?;
62 result.pages += 1;
63 let page = match PageRef::open(&bytes, no) {
64 Ok(p) => p,
65 Err(_) => {
66 result.damaged_pages += 1;
67 visitor(LeafEvent::DamagedPage { page_no: no })?;
68 continue;
69 }
70 };
71 if page.kind() != PageKind::Leaf || page.tree_id() != tree_id {
72 continue;
73 }
74 for slot in 0..page.nentries() {
75 match super::decode_leaf_record(page.slot(slot), no) {
76 Ok((key, stored_value, overflow)) => {
77 visitor(LeafEvent::Record(LeafCandidate {
78 page_no: no,
79 generation: page.lsn(),
80 slot,
81 key,
82 stored_value,
83 overflow,
84 }))?
85 }
86 Err(_) => {
87 result.malformed_cells += 1;
88 visitor(LeafEvent::MalformedCell { page_no: no, slot })?;
89 }
90 }
91 }
92 }
93 Ok(result)
94 }
95 pub fn read_value(&self, record: LeafCandidate<'_>, max_bytes: usize) -> Result<Vec<u8>> {
98 if !record.overflow {
99 if record.stored_value.len() > max_bytes {
100 return Err(Error::TooLarge);
101 }
102 return Ok(record.stored_value.to_vec());
103 }
104 if record.stored_value.len() != 12 {
105 return Err(bad(record.page_no, "overflow marker size"));
106 }
107 let total = u32::from_le_bytes(record.stored_value[..4].try_into().unwrap()) as usize;
108 if total > max_bytes {
109 return Err(Error::TooLarge);
110 }
111 let mut value = Vec::with_capacity(total);
112 visit_overflow(&*self.file, record.stored_value, |chunk| {
113 value.extend_from_slice(chunk)
114 })?;
115 Ok(value)
116 }
117}
118fn bad(page_no: u32, why: &'static str) -> Error {
119 Error::Corrupt { page_no, why }
120}
121
122pub(super) fn visit_overflow(
123 file: &dyn FileIo,
124 marker: &[u8],
125 mut visit: impl FnMut(&[u8]),
126) -> Result<()> {
127 use crate::btree::{OV_CAP, OV_DATA, OV_NEXT, OV_USED};
128 if marker.len() != 12 {
129 return Err(bad(0, "overflow marker size"));
130 }
131 let total = u32::from_le_bytes(marker[..4].try_into().unwrap()) as usize;
132 let mut no = u32::from_le_bytes(marker[4..8].try_into().unwrap());
133 let want = u32::from_le_bytes(marker[8..].try_into().unwrap());
134 let bound = total.div_ceil(OV_CAP).max(1);
135 let pages = file.len()? / PAGE_SIZE as u64;
136 let (mut seen, mut bytes, mut crc) = (0usize, 0usize, 0u32);
137 let mut buf = [0u8; PAGE_SIZE];
138 while no != 0 {
139 seen += 1;
140 if seen > bound || no < 2 || no as u64 >= pages {
141 return Err(bad(no, "overflow chain bounds"));
142 }
143 file.read_at(&mut buf, no as u64 * PAGE_SIZE as u64)?;
144 let p = PageRef::open(&buf, no)?;
145 if p.kind() != PageKind::Overflow || p.tree_id() != 0 {
146 return Err(bad(no, "overflow page kind"));
147 }
148 let used = u16::from_le_bytes(buf[OV_USED..OV_USED + 2].try_into().unwrap()) as usize;
149 if used > OV_CAP || bytes.checked_add(used).is_none_or(|n| n > total) {
150 return Err(bad(no, "overflow length bounds"));
151 }
152 bytes += used;
153 let chunk = &buf[OV_DATA..OV_DATA + used];
154 crc = crc32c::crc32c_append(crc, chunk);
155 visit(chunk);
156 no = u32::from_le_bytes(buf[OV_NEXT..OV_NEXT + 4].try_into().unwrap());
157 }
158 if seen != bound || bytes != total || crc != want {
159 return Err(bad(0, "overflow whole-value checksum or length"));
160 }
161 Ok(())
162}