fmf_core\scan/
deferred.rs1use ntfs_reader::api::{
7 NtfsAttributeListEntry, NtfsAttributeType, NtfsFileName, NtfsFileNamespace,
8};
9use ntfs_reader::file::NtfsFile;
10use rustc_hash::FxHashMap;
11
12use crate::mft::pick_name;
13
14use super::parse::{ParsedBatch, RecordArena};
15use super::volume_io::{RunMap, apply_fixup, open_raw_volume};
16
17pub(super) const EXT_NAME_CACHE_CAP: usize = 128 << 10;
21
22struct RecordReader<'a> {
24 file: std::fs::File,
25 map: &'a RunMap,
26 record_size: usize,
27 buf: Vec<u8>,
28}
29
30impl RecordReader<'_> {
31 fn read_record(&mut self, number: u64) -> Option<&[u8]> {
32 use std::io::{Read, Seek, SeekFrom};
33 let logical = number * self.record_size as u64;
34 let (phys, contig) = self.map.physical(logical)?;
35 if (contig as usize) < self.record_size {
36 return None;
37 }
38 self.buf.resize(self.record_size, 0);
39 self.file.seek(SeekFrom::Start(phys)).ok()?;
40 self.file.read_exact(&mut self.buf).ok()?;
41 if !NtfsFile::is_valid(&self.buf) || !apply_fixup(&mut self.buf) {
42 return None;
43 }
44 Some(&self.buf)
45 }
46}
47
48struct LazyRecordReader<'a> {
51 volume_path: &'a str,
52 map: &'a RunMap,
53 record_size: usize,
54 inner: Option<RecordReader<'a>>,
55 failed: bool,
56 failures: u64,
60}
61
62impl<'a> LazyRecordReader<'a> {
63 const fn new(volume_path: &'a str, map: &'a RunMap, record_size: usize) -> Self {
64 LazyRecordReader {
65 volume_path,
66 map,
67 record_size,
68 inner: None,
69 failed: false,
70 failures: 0,
71 }
72 }
73
74 fn read_record(&mut self, number: u64) -> Option<&[u8]> {
75 if self.inner.is_none() && !self.failed {
76 match open_raw_volume(self.volume_path) {
77 Ok(file) => {
78 self.inner = Some(RecordReader {
79 file,
80 map: self.map,
81 record_size: self.record_size,
82 buf: Vec::new(),
83 });
84 }
85 Err(e) => {
86 self.failed = true;
87 tracing::warn!(error = %e, "deferred-pass fallback volume handle unavailable");
88 }
89 }
90 }
91 let Some(inner) = self.inner.as_mut() else {
92 self.failures += 1;
93 return None;
94 };
95 let got = inner.read_record(number);
96 if got.is_none() {
97 self.failures += 1;
98 }
99 got
100 }
101}
102
103fn resolve_attr_list_name(
109 base: &NtfsFile,
110 ext: &FxHashMap<u64, u32>,
111 arena: &RecordArena,
112 rr: &mut LazyRecordReader,
113) -> Option<NtfsFileName> {
114 let attr = base.get_attribute(NtfsAttributeType::AttributeList)?;
115 if attr.header.is_non_resident != 0 {
116 return None; }
118 let header = attr.resident_header()?;
119 let data = attr.data();
120 let start = header.value_offset as usize;
121 let end = start.checked_add(header.value_length as usize)?;
122 if end > data.len() {
123 return None;
124 }
125 let list = &data[start..end];
126
127 let mut best: Option<NtfsFileName> = None;
128 let mut off = 0usize;
129 while off + size_of::<NtfsAttributeListEntry>() <= list.len() {
130 let entry = unsafe {
135 std::ptr::read_unaligned(list.as_ptr().add(off).cast::<NtfsAttributeListEntry>())
136 };
137 let len = entry.length as usize;
138 if len < size_of::<NtfsAttributeListEntry>() || off + len > list.len() {
139 break;
140 }
141 if entry.type_id == NtfsAttributeType::FileName as u32 {
142 let target = entry.reference();
143 if target != base.number {
144 let picked = match ext.get(&target) {
145 Some(&slot) => pick_name(&NtfsFile::new(target, arena.get(slot))),
146 None => rr
147 .read_record(target)
148 .and_then(|bytes| pick_name(&NtfsFile::new(target, bytes))),
149 };
150 if let Some(name) = picked {
151 let ns = name.header.namespace;
152 if ns == NtfsFileNamespace::Win32 as u8
153 || ns == NtfsFileNamespace::Win32AndDos as u8
154 {
155 return Some(name);
156 }
157 if best.is_none() {
158 best = Some(name);
159 }
160 }
161 }
162 }
163 off += len.next_multiple_of(8);
164 }
165 best
166}
167
168pub(super) fn resolve_deferred(
174 volume_path: &str,
175 runmap: &RunMap,
176 record_size: usize,
177 ext: &FxHashMap<u64, u32>,
178 arena: &RecordArena,
179 deferred: &[(u64, u32)],
180) -> Vec<ParsedBatch> {
181 use rayon::prelude::*;
182 const DEFER_CHUNK: usize = 256;
183
184 deferred
185 .par_chunks(DEFER_CHUNK)
186 .map(|chunk| {
187 let mut out = ParsedBatch::default();
188 let mut rr = LazyRecordReader::new(volume_path, runmap, record_size);
189 for &(number, slot) in chunk {
190 let f = NtfsFile::new(number, arena.get(slot));
191 match resolve_attr_list_name(&f, ext, arena, &mut rr) {
192 Some(name) => out.push_named(&f, &name),
193 None => out.deferred_unresolved += 1,
194 }
195 }
196 out.deferred_name_read_failures = rr.failures;
197 out
198 })
199 .collect()
200}