#![allow(dead_code)] use std::mem; use windows::{ core::PCWSTR, Win32::Foundation::HANDLE, Win32::Storage::FileSystem::{ CreateFileW, ReadFile, SetFilePointerEx, FILE_BEGIN, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_SEQUENTIAL_SCAN, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, }, Win32::System::Ioctl::{ FSCTL_ENUM_USN_DATA, MFT_ENUM_DATA_V0, USN_RECORD_V2, }, Win32::System::IO::DeviceIoControl, }; use crate::mft::types::NtfsDrive; const FALLBACK_BUF: usize = 4 * 1024 * 1024; const DIRECT_BUF: usize = 4 * 1024 * 1024; /// Compact MFT record — no heap allocations per file. pub struct CompactRecord { pub file_ref: u64, pub parent_ref: u64, pub name_off: u32, pub name_len: u16, pub is_dir: bool, } /// Result of a full MFT scan. pub struct ScanResult { pub records: Vec, pub name_data: Vec, } pub struct MftReader { handle: HANDLE, pub drive: NtfsDrive, } impl MftReader { pub fn open(drive: &NtfsDrive) -> windows::core::Result { let path: Vec = drive .device_path .encode_utf16() .chain(Some(0)) .collect(); let handle = unsafe { CreateFileW( PCWSTR(path.as_ptr()), 0x80000000u32, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, None, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, None, )? }; Ok(Self { handle, drive: drive.clone(), }) } // --------------------------------------------------------------- // Primary: direct $MFT file read (falls back to FSCTL if fails) // --------------------------------------------------------------- /// Try direct sequential read of $MFT for maximum speed. /// Returns None if direct access is unavailable. pub fn scan_direct(&self) -> Option { let record_size = self.read_mft_record_size()?; let mft_path = format!("{}$MFT", self.drive.root); let mft_wide: Vec = mft_path.encode_utf16().chain(Some(0)).collect(); let mft_handle = unsafe { CreateFileW( PCWSTR(mft_wide.as_ptr()), 0x80000000u32, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, None, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_SEQUENTIAL_SCAN, None, ) .ok()? }; let mut records: Vec = Vec::with_capacity(3_000_000); let mut name_data: Vec = Vec::with_capacity(40_000_000); let mut buffer = vec![0u8; DIRECT_BUF]; let mut mft_index: u64 = 0; let mut leftover = 0usize; loop { let mut bytes_read = 0u32; let ok = unsafe { ReadFile( mft_handle, Some(&mut buffer[leftover..]), Some(&mut bytes_read), None, ) }; if ok.is_err() || bytes_read == 0 { break; } let total = leftover + bytes_read as usize; let mut offset = 0usize; while offset + record_size <= total { let applied = Self::apply_fixup(&mut buffer[offset..offset + record_size], record_size); if applied { Self::parse_file_record( &buffer[offset..offset + record_size], mft_index, &mut records, &mut name_data, ); } mft_index += 1; offset += record_size; } offset = total - (total % record_size); leftover = total - offset; if leftover > 0 { unsafe { std::ptr::copy( buffer.as_ptr().add(offset), buffer.as_mut_ptr(), leftover, ); } } // for (mut recs, mut names) in results { // records.append(&mut recs); // name_data.append(&mut names); // } } unsafe { windows::Win32::Foundation::CloseHandle(mft_handle).ok(); } Some(ScanResult { records, name_data }) } // --------------------------------------------------------------- // Fallback: FSCTL_ENUM_USN_DATA (4 MB buffer) // --------------------------------------------------------------- pub fn scan(&self) -> ScanResult { let mut records: Vec = Vec::with_capacity(3_000_000); let mut name_data: Vec = Vec::with_capacity(40_000_000); let mut enum_data = MFT_ENUM_DATA_V0 { StartFileReferenceNumber: 0, LowUsn: 0, HighUsn: i64::MAX, }; let mut buffer = vec![0u8; FALLBACK_BUF]; loop { let mut bytes_returned: u32 = 0; let ok = unsafe { DeviceIoControl( self.handle, FSCTL_ENUM_USN_DATA, Some(&enum_data as *const _ as *const _), mem::size_of::() as u32, Some(buffer.as_mut_ptr() as *mut _), FALLBACK_BUF as u32, Some(&mut bytes_returned), None, ) }; if let Err(e) = ok { let code = e.code().0 as u32; if code == 0x80070026 { break; } eprintln!("MFT error on {}: {:?}", self.drive.letter, e); break; } if bytes_returned <= 8 { break; } let next_ref = u64::from_ne_bytes(buffer[0..8].try_into().unwrap()); enum_data.StartFileReferenceNumber = next_ref; let mut offset = 8usize; while offset + mem::size_of::() <= bytes_returned as usize { let record = unsafe { &*(buffer.as_ptr().add(offset) as *const USN_RECORD_V2) }; let rec_len = record.RecordLength as usize; if rec_len == 0 || offset + rec_len > bytes_returned as usize { break; } let name_offset = record.FileNameOffset as usize; let name_len = record.FileNameLength as usize / 2; let name_ptr = unsafe { buffer.as_ptr().add(offset + name_offset) as *const u16 }; let name_slice = unsafe { std::slice::from_raw_parts(name_ptr, name_len) }; let arena_off = name_data.len() as u32; name_data.extend_from_slice(name_slice); records.push(CompactRecord { file_ref: record.FileReferenceNumber as u64, parent_ref: record.ParentFileReferenceNumber as u64, name_off: arena_off, name_len: name_len as u16, is_dir: (record.FileAttributes & 0x10) != 0, }); offset += rec_len; } } ScanResult { records, name_data } } // --------------------------------------------------------------- // NTFS helpers // --------------------------------------------------------------- /// Read MFT record size from the NTFS boot sector. fn read_mft_record_size(&self) -> Option { unsafe { SetFilePointerEx(self.handle, 0, None, FILE_BEGIN).ok()?; } let mut boot = [0u8; 512]; let mut br = 0u32; unsafe { ReadFile(self.handle, Some(&mut boot), Some(&mut br), None).ok()?; } if br < 512 || &boot[3..7] != b"NTFS" { return None; } let bytes_per_sector = u16::from_le_bytes([boot[0x0B], boot[0x0C]]) as usize; let sectors_per_cluster = boot[0x0D] as usize; let cluster_size = bytes_per_sector * sectors_per_cluster; let raw = boot[0x40] as i8; let record_size = if raw > 0 { raw as usize * cluster_size } else { 1usize << (-(raw as i32) as usize) }; Some(record_size) } /// Apply NTFS multi-sector fixup. Returns false if the record is invalid. fn apply_fixup(record: &mut [u8], record_size: usize) -> bool { if record.len() < 48 || &record[0..4] != b"FILE" { return false; } let fixup_off = u16::from_le_bytes([record[4], record[5]]) as usize; let fixup_cnt = u16::from_le_bytes([record[6], record[7]]) as usize; if fixup_cnt < 2 || fixup_off + fixup_cnt * 2 > record_size { return false; } let check = [record[fixup_off], record[fixup_off + 1]]; for i in 1..fixup_cnt { let end = i * 512 - 2; if end + 1 >= record_size { break; } if record[end] != check[0] || record[end + 1] != check[1] { return false; } record[end] = record[fixup_off + i * 2]; record[end + 1] = record[fixup_off + i * 2 + 1]; } true } /// Parse one MFT FILE record, extracting name + parent into the arena. fn parse_file_record( record: &[u8], mft_index: u64, records: &mut Vec, name_data: &mut Vec, ) { let flags = u16::from_le_bytes([record[0x16], record[0x17]]); if flags & 0x01 == 0 { return; } let is_dir = flags & 0x02 != 0; let seq = u16::from_le_bytes([record[0x10], record[0x11]]) as u64; let file_ref = mft_index | (seq << 48); let first_attr = u16::from_le_bytes([record[0x14], record[0x15]]) as usize; let mut aoff = first_attr; let mut best_ns: u8 = 255; let mut best_name: Option<(usize, usize, u64)> = None; while aoff + 8 <= record.len() { let atype = u32::from_le_bytes(record[aoff..aoff + 4].try_into().unwrap()); if atype == 0xFFFF_FFFF { break; } let alen = u32::from_le_bytes(record[aoff + 4..aoff + 8].try_into().unwrap()) as usize; if alen == 0 || aoff + alen > record.len() { break; } if atype == 0x30 && record[aoff + 8] == 0 { let vlen = u32::from_le_bytes(record[aoff + 16..aoff + 20].try_into().unwrap()) as usize; let voff = u16::from_le_bytes([record[aoff + 20], record[aoff + 21]]) as usize; let vs = aoff + voff; if vs + 66 <= record.len() && vlen >= 66 { let parent = u64::from_le_bytes(record[vs..vs + 8].try_into().unwrap()); let nlen = record[vs + 64] as usize; let ns = record[vs + 65]; if vs + 66 + nlen * 2 <= record.len() { if ns == 2 { continue; } let priority = match ns { 1 => 0, // Win32 3 => 1, // Win32 + DOS 0 => 2, // POSIX _ => 3, }; if priority < best_ns { best_ns = priority; best_name = Some((vs + 66, nlen, parent)); if priority == 0 { break; // Win32 name → best possible } } } } } aoff += alen; } if let Some((name_pos, nlen, parent)) = best_name { let arena_off = name_data.len() as u32; for i in 0..nlen { let p = name_pos + i * 2; name_data.push(u16::from_le_bytes([record[p], record[p + 1]])); } records.push(CompactRecord { file_ref, parent_ref: parent, name_off: arena_off, name_len: nlen as u16, is_dir, }); } } } impl Drop for MftReader { fn drop(&mut self) { unsafe { windows::Win32::Foundation::CloseHandle(self.handle).ok() }; } }