Skip to main content

fmf_core\scan/
volume_io.rs

1//! Raw volume access: `\\.\C:`-style handles, the NTFS update-sequence
2//! fixup, and the logical→physical run map of the $MFT data stream.
3
4use ntfs_reader::api::NtfsAttributeType;
5use ntfs_reader::errors::NtfsReaderError;
6use ntfs_reader::file::NtfsFile;
7use ntfs_reader::mft::Mft;
8use ntfs_reader::volume::Volume;
9
10const SECTOR: usize = 512;
11
12/// Logical-byte → physical-byte mapping of the $MFT data stream.
13pub(super) struct RunMap {
14    /// (logical start, physical start, length) — all bytes.
15    pub(super) runs: Vec<(u64, u64, u64)>,
16}
17
18impl RunMap {
19    fn from_data_runs(runs: &[ntfs_reader::attribute::DataRun]) -> Self {
20        use ntfs_reader::attribute::DataRun;
21        let mut v = Vec::with_capacity(runs.len());
22        let mut logical = 0u64;
23        for r in runs {
24            match r {
25                DataRun::Data { lcn, length } => {
26                    v.push((logical, *lcn, *length));
27                    logical += length;
28                }
29                DataRun::Sparse { length } => logical += length,
30            }
31        }
32        Self { runs: v }
33    }
34
35    /// Physical offset and remaining contiguous bytes at `logical`.
36    pub(super) fn physical(&self, logical: u64) -> Option<(u64, u64)> {
37        // Runs are few (usually < 100); linear is fine.
38        self.runs
39            .iter()
40            .find(|(ls, _, len)| logical >= *ls && logical < ls + len)
41            .map(|(ls, ph, len)| (ph + (logical - ls), ls + len - logical))
42    }
43}
44
45pub(super) fn open_raw_volume(volume_path: &str) -> std::io::Result<std::fs::File> {
46    use std::os::windows::fs::OpenOptionsExt;
47    const FILE_SHARE_READ: u32 = 0x1;
48    const FILE_SHARE_WRITE: u32 = 0x2;
49    const FILE_SHARE_DELETE: u32 = 0x4;
50    std::fs::OpenOptions::new()
51        .read(true)
52        .share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
53        .open(volume_path)
54}
55
56/// Apply the NTFS update sequence array in place. Returns false when the
57/// sector check bytes don't match (torn/corrupt record).
58pub(super) fn apply_fixup(data: &mut [u8]) -> bool {
59    if data.len() < 48 {
60        return false;
61    }
62    let uso = u16::from_le_bytes([data[4], data[5]]) as usize;
63    let usl = u16::from_le_bytes([data[6], data[7]]) as usize;
64    if usl < 2 || uso + usl * 2 > data.len() {
65        return false;
66    }
67    let usn = [data[uso], data[uso + 1]];
68    let mut sector_off = SECTOR - 2;
69    for i in 1..usl {
70        let usa_off = uso + i * 2;
71        if sector_off + 2 > data.len() {
72            break;
73        }
74        if data[sector_off] != usn[0] || data[sector_off + 1] != usn[1] {
75            return false;
76        }
77        data[sector_off] = data[usa_off];
78        data[sector_off + 1] = data[usa_off + 1];
79        sector_off += SECTOR;
80    }
81    true
82}
83
84/// Volume geometry + the $MFT data-run map — the bootstrap shared by the
85/// full scan and the I/O probe (record 0 → the $MFT's own data runs).
86pub(super) fn mft_layout(volume_path: &str) -> Result<(usize, u64, RunMap), NtfsReaderError> {
87    let volume = Volume::new(volume_path)?;
88    let record_size = volume.file_record_size as usize;
89    let mut reader = ntfs_reader::aligned_reader::open_volume(std::path::Path::new(volume_path))
90        .map_err(NtfsReaderError::from)?;
91    let rec0 = Mft::get_record_fs(&mut reader, volume.file_record_size, volume.mft_position)?;
92    let f0 = NtfsFile::new(0, &rec0);
93    let data_attr = f0
94        .get_attribute(NtfsAttributeType::Data)
95        .ok_or_else(|| NtfsReaderError::MissingMftAttribute("Data".to_string()))?;
96    let (size, runs) = data_attr.get_nonresident_data_runs(&volume)?;
97    Ok((record_size, size, RunMap::from_data_runs(&runs)))
98}