anshdadhich commited on
Commit
ffce3d2
·
verified ·
1 Parent(s): 761f4fd

Upload src/mft/reader.rs

Browse files
Files changed (1) hide show
  1. src/mft/reader.rs +411 -0
src/mft/reader.rs ADDED
@@ -0,0 +1,411 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ #![allow(dead_code)]
3
+
4
+ use std::mem;
5
+ use windows::{
6
+ core::PCWSTR,
7
+ Win32::Foundation::HANDLE,
8
+ Win32::Storage::FileSystem::{
9
+ CreateFileW, ReadFile, SetFilePointerEx,
10
+ FILE_BEGIN, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_SEQUENTIAL_SCAN,
11
+ FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
12
+ },
13
+ Win32::System::Ioctl::{
14
+ FSCTL_ENUM_USN_DATA, MFT_ENUM_DATA_V0, USN_RECORD_V2,
15
+ },
16
+ Win32::System::IO::DeviceIoControl,
17
+ };
18
+
19
+ use crate::mft::types::NtfsDrive;
20
+
21
+ const FALLBACK_BUF: usize = 4 * 1024 * 1024;
22
+ const DIRECT_BUF: usize = 4 * 1024 * 1024;
23
+
24
+ /// Compact MFT record — no heap allocations per file.
25
+ pub struct CompactRecord {
26
+ pub file_ref: u64,
27
+ pub parent_ref: u64,
28
+ pub name_off: u32,
29
+ pub name_len: u16,
30
+ pub is_dir: bool,
31
+ }
32
+
33
+ /// Result of a full MFT scan.
34
+ pub struct ScanResult {
35
+ pub records: Vec<CompactRecord>,
36
+ pub name_data: Vec<u16>,
37
+ }
38
+
39
+ pub struct MftReader {
40
+ handle: HANDLE,
41
+ pub drive: NtfsDrive,
42
+ }
43
+
44
+ impl MftReader {
45
+ pub fn open(drive: &NtfsDrive) -> windows::core::Result<Self> {
46
+ let path: Vec<u16> = drive
47
+ .device_path
48
+ .encode_utf16()
49
+ .chain(Some(0))
50
+ .collect();
51
+
52
+ let handle = unsafe {
53
+ CreateFileW(
54
+ PCWSTR(path.as_ptr()),
55
+ 0x80000000u32,
56
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
57
+ None,
58
+ OPEN_EXISTING,
59
+ FILE_FLAG_BACKUP_SEMANTICS,
60
+ None,
61
+ )?
62
+ };
63
+
64
+ Ok(Self {
65
+ handle,
66
+ drive: drive.clone(),
67
+ })
68
+ }
69
+
70
+ // ---------------------------------------------------------------
71
+ // Primary: direct $MFT file read (falls back to FSCTL if fails)
72
+ // ---------------------------------------------------------------
73
+
74
+ /// Try direct sequential read of $MFT for maximum speed.
75
+ /// Returns None if direct access is unavailable.
76
+ pub fn scan_direct(&self) -> Option<ScanResult> {
77
+ let record_size = self.read_mft_record_size()?;
78
+
79
+ let mft_path = format!("{}$MFT", self.drive.root);
80
+ let mft_wide: Vec<u16> = mft_path.encode_utf16().chain(Some(0)).collect();
81
+
82
+ let mft_handle = unsafe {
83
+ CreateFileW(
84
+ PCWSTR(mft_wide.as_ptr()),
85
+ 0x80000000u32,
86
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
87
+ None,
88
+ OPEN_EXISTING,
89
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN,
90
+ None,
91
+ )
92
+ .ok()?
93
+ };
94
+
95
+ let mut records: Vec<CompactRecord> = Vec::with_capacity(3_000_000);
96
+ let mut name_data: Vec<u16> = Vec::with_capacity(40_000_000);
97
+ let mut buffer = vec![0u8; DIRECT_BUF];
98
+ let mut mft_index: u64 = 0;
99
+ let mut leftover = 0usize;
100
+
101
+ loop {
102
+ let mut bytes_read = 0u32;
103
+ let ok = unsafe {
104
+ ReadFile(
105
+ mft_handle,
106
+ Some(&mut buffer[leftover..]),
107
+ Some(&mut bytes_read),
108
+ None,
109
+ )
110
+ };
111
+ if ok.is_err() || bytes_read == 0 {
112
+ break;
113
+ }
114
+
115
+ let total = leftover + bytes_read as usize;
116
+ let mut offset = 0usize;
117
+
118
+ while offset + record_size <= total {
119
+ let applied =
120
+ Self::apply_fixup(&mut buffer[offset..offset + record_size], record_size);
121
+
122
+ if applied {
123
+ Self::parse_file_record(
124
+ &buffer[offset..offset + record_size],
125
+ mft_index,
126
+ &mut records,
127
+ &mut name_data,
128
+ );
129
+ }
130
+
131
+ mft_index += 1;
132
+ offset += record_size;
133
+ }
134
+
135
+ offset = total - (total % record_size);
136
+
137
+ leftover = total - offset;
138
+ if leftover > 0 {
139
+ unsafe {
140
+ std::ptr::copy(
141
+ buffer.as_ptr().add(offset),
142
+ buffer.as_mut_ptr(),
143
+ leftover,
144
+ );
145
+ }
146
+ }
147
+
148
+ // for (mut recs, mut names) in results {
149
+ // records.append(&mut recs);
150
+ // name_data.append(&mut names);
151
+ // }
152
+ }
153
+
154
+ unsafe {
155
+ windows::Win32::Foundation::CloseHandle(mft_handle).ok();
156
+ }
157
+
158
+ Some(ScanResult { records, name_data })
159
+ }
160
+
161
+ // ---------------------------------------------------------------
162
+ // Fallback: FSCTL_ENUM_USN_DATA (4 MB buffer)
163
+ // ---------------------------------------------------------------
164
+
165
+ pub fn scan(&self) -> ScanResult {
166
+ let mut records: Vec<CompactRecord> = Vec::with_capacity(3_000_000);
167
+ let mut name_data: Vec<u16> = Vec::with_capacity(40_000_000);
168
+
169
+ let mut enum_data = MFT_ENUM_DATA_V0 {
170
+ StartFileReferenceNumber: 0,
171
+ LowUsn: 0,
172
+ HighUsn: i64::MAX,
173
+ };
174
+
175
+ let mut buffer = vec![0u8; FALLBACK_BUF];
176
+
177
+ loop {
178
+ let mut bytes_returned: u32 = 0;
179
+
180
+ let ok = unsafe {
181
+ DeviceIoControl(
182
+ self.handle,
183
+ FSCTL_ENUM_USN_DATA,
184
+ Some(&enum_data as *const _ as *const _),
185
+ mem::size_of::<MFT_ENUM_DATA_V0>() as u32,
186
+ Some(buffer.as_mut_ptr() as *mut _),
187
+ FALLBACK_BUF as u32,
188
+ Some(&mut bytes_returned),
189
+ None,
190
+ )
191
+ };
192
+
193
+ if let Err(e) = ok {
194
+ let code = e.code().0 as u32;
195
+ if code == 0x80070026 {
196
+ break;
197
+ }
198
+ eprintln!("MFT error on {}: {:?}", self.drive.letter, e);
199
+ break;
200
+ }
201
+
202
+ if bytes_returned <= 8 {
203
+ break;
204
+ }
205
+
206
+ let next_ref = u64::from_ne_bytes(buffer[0..8].try_into().unwrap());
207
+ enum_data.StartFileReferenceNumber = next_ref;
208
+
209
+ let mut offset = 8usize;
210
+ while offset + mem::size_of::<USN_RECORD_V2>() <= bytes_returned as usize {
211
+ let record = unsafe {
212
+ &*(buffer.as_ptr().add(offset) as *const USN_RECORD_V2)
213
+ };
214
+
215
+ let rec_len = record.RecordLength as usize;
216
+ if rec_len == 0 || offset + rec_len > bytes_returned as usize {
217
+ break;
218
+ }
219
+
220
+ let name_offset = record.FileNameOffset as usize;
221
+ let name_len = record.FileNameLength as usize / 2;
222
+ let name_ptr = unsafe {
223
+ buffer.as_ptr().add(offset + name_offset) as *const u16
224
+ };
225
+ let name_slice = unsafe { std::slice::from_raw_parts(name_ptr, name_len) };
226
+
227
+ let arena_off = name_data.len() as u32;
228
+ name_data.extend_from_slice(name_slice);
229
+
230
+ records.push(CompactRecord {
231
+ file_ref: record.FileReferenceNumber as u64,
232
+ parent_ref: record.ParentFileReferenceNumber as u64,
233
+ name_off: arena_off,
234
+ name_len: name_len as u16,
235
+ is_dir: (record.FileAttributes & 0x10) != 0,
236
+ });
237
+
238
+ offset += rec_len;
239
+ }
240
+ }
241
+
242
+ ScanResult { records, name_data }
243
+ }
244
+
245
+ // ---------------------------------------------------------------
246
+ // NTFS helpers
247
+ // ---------------------------------------------------------------
248
+
249
+ /// Read MFT record size from the NTFS boot sector.
250
+ fn read_mft_record_size(&self) -> Option<usize> {
251
+ unsafe {
252
+ SetFilePointerEx(self.handle, 0, None, FILE_BEGIN).ok()?;
253
+ }
254
+ let mut boot = [0u8; 512];
255
+ let mut br = 0u32;
256
+ unsafe {
257
+ ReadFile(self.handle, Some(&mut boot), Some(&mut br), None).ok()?;
258
+ }
259
+ if br < 512 || &boot[3..7] != b"NTFS" {
260
+ return None;
261
+ }
262
+
263
+ let bytes_per_sector = u16::from_le_bytes([boot[0x0B], boot[0x0C]]) as usize;
264
+ let sectors_per_cluster = boot[0x0D] as usize;
265
+ let cluster_size = bytes_per_sector * sectors_per_cluster;
266
+
267
+ let raw = boot[0x40] as i8;
268
+ let record_size = if raw > 0 {
269
+ raw as usize * cluster_size
270
+ } else {
271
+ 1usize << (-(raw as i32) as usize)
272
+ };
273
+
274
+ Some(record_size)
275
+ }
276
+
277
+ /// Apply NTFS multi-sector fixup. Returns false if the record is invalid.
278
+ fn apply_fixup(record: &mut [u8], record_size: usize) -> bool {
279
+ if record.len() < 48 || &record[0..4] != b"FILE" {
280
+ return false;
281
+ }
282
+
283
+ let fixup_off = u16::from_le_bytes([record[4], record[5]]) as usize;
284
+ let fixup_cnt = u16::from_le_bytes([record[6], record[7]]) as usize;
285
+
286
+ if fixup_cnt < 2 || fixup_off + fixup_cnt * 2 > record_size {
287
+ return false;
288
+ }
289
+
290
+ let check = [record[fixup_off], record[fixup_off + 1]];
291
+
292
+ for i in 1..fixup_cnt {
293
+ let end = i * 512 - 2;
294
+ if end + 1 >= record_size {
295
+ break;
296
+ }
297
+ if record[end] != check[0] || record[end + 1] != check[1] {
298
+ return false;
299
+ }
300
+ record[end] = record[fixup_off + i * 2];
301
+ record[end + 1] = record[fixup_off + i * 2 + 1];
302
+ }
303
+
304
+ true
305
+ }
306
+
307
+ /// Parse one MFT FILE record, extracting name + parent into the arena.
308
+ fn parse_file_record(
309
+ record: &[u8],
310
+ mft_index: u64,
311
+ records: &mut Vec<CompactRecord>,
312
+ name_data: &mut Vec<u16>,
313
+ ) {
314
+ let flags = u16::from_le_bytes([record[0x16], record[0x17]]);
315
+ if flags & 0x01 == 0 {
316
+ return;
317
+ }
318
+
319
+ let is_dir = flags & 0x02 != 0;
320
+ let seq = u16::from_le_bytes([record[0x10], record[0x11]]) as u64;
321
+ let file_ref = mft_index | (seq << 48);
322
+
323
+ let first_attr = u16::from_le_bytes([record[0x14], record[0x15]]) as usize;
324
+ let mut aoff = first_attr;
325
+
326
+ let mut best_ns: u8 = 255;
327
+ let mut best_name: Option<(usize, usize, u64)> = None;
328
+
329
+ while aoff + 8 <= record.len() {
330
+ let atype = u32::from_le_bytes(record[aoff..aoff + 4].try_into().unwrap());
331
+
332
+ if atype == 0xFFFF_FFFF {
333
+ break;
334
+ }
335
+
336
+ let alen =
337
+ u32::from_le_bytes(record[aoff + 4..aoff + 8].try_into().unwrap()) as usize;
338
+
339
+ if alen == 0 || aoff + alen > record.len() {
340
+ break;
341
+ }
342
+
343
+ if atype == 0x30 && record[aoff + 8] == 0 {
344
+ let vlen =
345
+ u32::from_le_bytes(record[aoff + 16..aoff + 20].try_into().unwrap()) as usize;
346
+
347
+ let voff =
348
+ u16::from_le_bytes([record[aoff + 20], record[aoff + 21]]) as usize;
349
+
350
+ let vs = aoff + voff;
351
+
352
+ if vs + 66 <= record.len() && vlen >= 66 {
353
+ let parent =
354
+ u64::from_le_bytes(record[vs..vs + 8].try_into().unwrap());
355
+
356
+ let nlen = record[vs + 64] as usize;
357
+ let ns = record[vs + 65];
358
+
359
+ if vs + 66 + nlen * 2 <= record.len() {
360
+ if ns == 2 {
361
+ continue;
362
+ }
363
+
364
+ let priority = match ns {
365
+ 1 => 0, // Win32
366
+ 3 => 1, // Win32 + DOS
367
+ 0 => 2, // POSIX
368
+ _ => 3,
369
+ };
370
+
371
+ if priority < best_ns {
372
+ best_ns = priority;
373
+ best_name = Some((vs + 66, nlen, parent));
374
+
375
+ if priority == 0 {
376
+ break; // Win32 name → best possible
377
+ }
378
+ }
379
+ }
380
+ }
381
+ }
382
+
383
+ aoff += alen;
384
+ }
385
+
386
+ if let Some((name_pos, nlen, parent)) = best_name {
387
+ let arena_off = name_data.len() as u32;
388
+
389
+ for i in 0..nlen {
390
+ let p = name_pos + i * 2;
391
+ name_data.push(u16::from_le_bytes([record[p], record[p + 1]]));
392
+ }
393
+
394
+ records.push(CompactRecord {
395
+ file_ref,
396
+ parent_ref: parent,
397
+ name_off: arena_off,
398
+ name_len: nlen as u16,
399
+ is_dir,
400
+ });
401
+ }
402
+ }
403
+
404
+ }
405
+
406
+
407
+ impl Drop for MftReader {
408
+ fn drop(&mut self) {
409
+ unsafe { windows::Win32::Foundation::CloseHandle(self.handle).ok() };
410
+ }
411
+ }