use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use windows::Win32::Foundation::{CloseHandle, ERROR_HANDLE_EOF, HANDLE};
use windows::Win32::Storage::FileSystem::{
BY_HANDLE_FILE_INFORMATION, CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_SHARE_READ,
FILE_SHARE_WRITE, GetFileInformationByHandle, OPEN_EXISTING,
};
use windows::Win32::System::IO::DeviceIoControl;
use windows::Win32::System::Ioctl::{FSCTL_ENUM_USN_DATA, MFT_ENUM_DATA_V0};
use windows::core::PCWSTR;
use crate::cursor::VolumeKey;
use crate::frn_table::{FrnEntry, FrnTable};
use crate::usn_translate::parse_usn_record_v2_bytes;
pub(crate) struct MftEnumerationStats {
pub scanned: usize,
pub matched: usize,
}
const ENUM_BUFFER_SIZE: usize = 64 * 1024;
struct VolumeHandleGuard(HANDLE);
impl Drop for VolumeHandleGuard {
fn drop(&mut self) {
unsafe {
let _ = CloseHandle(self.0);
}
}
}
pub(crate) fn enumerate(
volume: &VolumeKey,
roots: &[PathBuf],
) -> Result<(FrnTable, Vec<PathBuf>, MftEnumerationStats)> {
let handle = crate::volume::open_volume_handle(volume)
.map_err(|e| anyhow::anyhow!("打开卷句柄失败: {e}"))?;
let _guard = VolumeHandleGuard(handle);
let mut table = FrnTable::new();
for root in roots {
let frn = resolve_frn(root)
.with_context(|| format!("解析监听根的 FRN 失败: {}", root.display()))?;
table.register_root(frn, root.clone());
}
let mut scanned = 0usize;
let mut start_frn: u64 = 0;
let mut buf = vec![0u8; ENUM_BUFFER_SIZE];
loop {
let input = MFT_ENUM_DATA_V0 {
StartFileReferenceNumber: start_frn,
LowUsn: 0,
HighUsn: i64::MAX,
};
let mut bytes_returned: u32 = 0;
let result = unsafe {
DeviceIoControl(
handle,
FSCTL_ENUM_USN_DATA,
Some(std::ptr::from_ref(&input).cast::<core::ffi::c_void>()),
std::mem::size_of::<MFT_ENUM_DATA_V0>() as u32,
Some(buf.as_mut_ptr().cast::<core::ffi::c_void>()),
buf.len() as u32,
Some(&mut bytes_returned),
None,
)
};
match result {
Ok(()) => {}
Err(err) if err.code() == ERROR_HANDLE_EOF.to_hresult() => break,
Err(err) => return Err(anyhow::anyhow!("FSCTL_ENUM_USN_DATA 失败: {err}")),
}
if (bytes_returned as usize) < 8 {
break;
}
start_frn = u64::from_ne_bytes(buf[0..8].try_into().expect("8 字节切片"));
let mut offset = 8usize;
let end = bytes_returned as usize;
while offset + 4 <= end {
let record_length =
u32::from_ne_bytes(buf[offset..offset + 4].try_into().expect("4 字节切片"))
as usize;
if record_length == 0 || offset + record_length > end {
break;
}
if let Some(record) = parse_usn_record_v2_bytes(&buf[offset..offset + record_length]) {
table.upsert(
record.frn,
FrnEntry {
parent_frn: record.parent_frn,
name: record.name,
is_dir: record.is_dir,
},
);
scanned += 1;
}
offset += record_length;
}
}
table.retain_in_scope();
let matched = table.entry_count();
let mut files = Vec::with_capacity(matched);
for (frn, entry) in table.iter() {
if entry.is_dir {
continue;
}
if let Some(path) = table.reconstruct_path(*frn) {
files.push(path);
}
}
Ok((table, files, MftEnumerationStats { scanned, matched }))
}
fn resolve_frn(path: &Path) -> Result<u64> {
use std::os::windows::ffi::OsStrExt;
let wide: Vec<u16> = path
.as_os_str()
.encode_wide()
.chain(std::iter::once(0))
.collect();
let handle = unsafe {
CreateFileW(
PCWSTR(wide.as_ptr()),
windows::Win32::Storage::FileSystem::FILE_GENERIC_READ.0,
FILE_SHARE_READ | FILE_SHARE_WRITE,
None,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
None,
)
}
.map_err(|e| anyhow::anyhow!("打开句柄失败: {e}"))?;
let _guard = VolumeHandleGuard(handle);
let mut info = BY_HANDLE_FILE_INFORMATION::default();
unsafe { GetFileInformationByHandle(handle, &mut info) }
.map_err(|e| anyhow::anyhow!("查询文件信息失败: {e}"))?;
Ok(((info.nFileIndexHigh as u64) << 32) | info.nFileIndexLow as u64)
}