Skip to main content

kernel/recover/
reader.rs

1//! Read-only forensic primitives. These establish byte integrity, never current
2//! membership. The owner above the kernel decides keyspaces and row encodings.
3use 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    /// A read-only logical file supplied by a format owner (for committed WAL overlays).
36    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    /// Stream independently verified leaf cells. A single page buffer is reused;
43    /// callback ownership prevents retaining borrowed data across the next read.
44    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    /// Materialize at most the caller's explicit encoded-value allowance.
96    /// Large values are checked before allocating. No partial overflow is returned.
97    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}