Initial commit: FluidExplorer:从零实现的 WinUI 3 文件资源管理器替代品(Mica、命令栏、面包屑)
This commit is contained in:
@@ -0,0 +1,458 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace FluidExplorer.Services.Search.Usn;
|
||||
|
||||
/// <summary>
|
||||
/// USN 日志 / NTFS 卷所需的最小 P/Invoke 与结构体集合。
|
||||
///
|
||||
/// 布局约定:所有 struct 均为 <see cref="LayoutKind.Sequential"/>,默认采用 x64 自然对齐;
|
||||
/// 字段顺序与偏移和 Windows SDK 的 winioctl.h / ntifs.h 完全一致。
|
||||
/// 每个结构体后面都标注了实测字节大小,便于对照 <see cref="Marshal.SizeOf{T}()"/> 校验。
|
||||
/// </summary>
|
||||
internal static class UsnNative
|
||||
{
|
||||
// ---------------------------------------------------------------- 访问权限 / 打开方式
|
||||
|
||||
internal const uint GENERIC_READ = 0x80000000;
|
||||
internal const uint GENERIC_WRITE = 0x40000000;
|
||||
internal const uint FILE_READ_DATA = 0x0001;
|
||||
internal const uint FILE_READ_ATTRIBUTES = 0x0080;
|
||||
internal const uint FILE_LIST_DIRECTORY = 0x0001;
|
||||
|
||||
internal const uint FILE_SHARE_READ = 0x00000001;
|
||||
internal const uint FILE_SHARE_WRITE = 0x00000002;
|
||||
internal const uint FILE_SHARE_DELETE = 0x00000004;
|
||||
|
||||
internal const uint OPEN_EXISTING = 3;
|
||||
|
||||
/// <summary>
|
||||
/// 打开 <c>\\.\C:</c> 时用的 dwFlagsAndAttributes。
|
||||
/// 参考实现(Everything)用 FILE_ATTRIBUTE_READONLY:传 FILE_ATTRIBUTE_NORMAL 在部分环境下会开不了卷句柄。
|
||||
/// </summary>
|
||||
internal const uint FILE_ATTRIBUTE_READONLY = 0x00000001;
|
||||
|
||||
/// <summary>开目录句柄必须带 FILE_FLAG_BACKUP_SEMANTICS,否则 CreateFile 会失败。</summary>
|
||||
internal const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
|
||||
|
||||
/// <summary>顺序扫描提示:读 MFT 时对缓存友好(读一次不再复用)。</summary>
|
||||
internal const uint FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000;
|
||||
|
||||
// ---------------------------------------------------------------- 控制码 CTL_CODE(FILE_DEVICE_FILE_SYSTEM=0x09, ...)
|
||||
|
||||
internal const uint FSCTL_ENUM_USN_DATA = 0x000900B3;
|
||||
internal const uint FSCTL_READ_USN_JOURNAL = 0x000900BB;
|
||||
internal const uint FSCTL_QUERY_USN_JOURNAL = 0x000900F4;
|
||||
internal const uint FSCTL_CREATE_USN_JOURNAL = 0x000900E7;
|
||||
internal const uint FSCTL_DELETE_USN_JOURNAL = 0x000900F8;
|
||||
internal const uint FSCTL_GET_NTFS_VOLUME_DATA = 0x00090064;
|
||||
internal const uint FSCTL_GET_NTFS_FILE_RECORD = 0x00090068;
|
||||
|
||||
/// <summary>FSCTL_DELETE_USN_JOURNAL 的 DeleteFlags:真正删除日志。</summary>
|
||||
internal const uint USN_DELETE_FLAG_DELETE = 0x00000001;
|
||||
internal const uint USN_DELETE_FLAG_DO_NOT_DELETE = 0x00000000;
|
||||
|
||||
// ---------------------------------------------------------------- ReadDirectoryChangesW(USN 日志不可用时的回退监听)
|
||||
|
||||
internal const uint FILE_NOTIFY_CHANGE_FILE_NAME = 0x00000001;
|
||||
internal const uint FILE_NOTIFY_CHANGE_DIR_NAME = 0x00000002;
|
||||
internal const uint FILE_NOTIFY_CHANGE_ATTRIBUTES = 0x00000004;
|
||||
internal const uint FILE_NOTIFY_CHANGE_SIZE = 0x00000008;
|
||||
internal const uint FILE_NOTIFY_CHANGE_LAST_WRITE = 0x00000010;
|
||||
|
||||
internal const uint FILE_ACTION_ADDED = 0x00000001;
|
||||
internal const uint FILE_ACTION_REMOVED = 0x00000002;
|
||||
internal const uint FILE_ACTION_MODIFIED = 0x00000003;
|
||||
internal const uint FILE_ACTION_RENAMED_OLD_NAME = 0x00000004;
|
||||
internal const uint FILE_ACTION_RENAMED_NEW_NAME = 0x00000005;
|
||||
|
||||
// ---------------------------------------------------------------- Win32 错误码
|
||||
|
||||
internal const int ERROR_INVALID_FUNCTION = 1;
|
||||
internal const int ERROR_ACCESS_DENIED = 5;
|
||||
internal const int ERROR_INVALID_HANDLE = 6;
|
||||
internal const int ERROR_NOT_READY = 21;
|
||||
internal const int ERROR_HANDLE_EOF = 38;
|
||||
internal const int ERROR_NOT_SUPPORTED = 50;
|
||||
internal const int ERROR_INVALID_PARAMETER = 87;
|
||||
internal const int ERROR_MORE_DATA = 234;
|
||||
internal const int ERROR_OPERATION_ABORTED = 995;
|
||||
internal const int ERROR_NOTIFY_ENUM_DIR = 1022;
|
||||
internal const int ERROR_JOURNAL_DELETE_IN_PROGRESS = 1178;
|
||||
internal const int ERROR_JOURNAL_NOT_ACTIVE = 1179;
|
||||
internal const int ERROR_JOURNAL_ENTRY_DELETED = 1181;
|
||||
internal const int ERROR_CANCELLED = 1223;
|
||||
|
||||
// ---------------------------------------------------------------- 文件属性
|
||||
|
||||
internal const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
|
||||
|
||||
// ---------------------------------------------------------------- USN 变更原因
|
||||
|
||||
internal const uint USN_REASON_DATA_OVERWRITE = 0x00000001;
|
||||
internal const uint USN_REASON_DATA_EXTEND = 0x00000002;
|
||||
internal const uint USN_REASON_DATA_TRUNCATION = 0x00000004;
|
||||
internal const uint USN_REASON_NAMED_DATA_OVERWRITE = 0x00000010;
|
||||
internal const uint USN_REASON_NAMED_DATA_EXTEND = 0x00000020;
|
||||
internal const uint USN_REASON_NAMED_DATA_TRUNCATION = 0x00000040;
|
||||
internal const uint USN_REASON_FILE_CREATE = 0x00000100;
|
||||
internal const uint USN_REASON_FILE_DELETE = 0x00000200;
|
||||
internal const uint USN_REASON_EA_CHANGE = 0x00000400;
|
||||
internal const uint USN_REASON_SECURITY_CHANGE = 0x00000800;
|
||||
internal const uint USN_REASON_RENAME_OLD_NAME = 0x00001000;
|
||||
internal const uint USN_REASON_RENAME_NEW_NAME = 0x00002000;
|
||||
internal const uint USN_REASON_INDEXABLE_CHANGE = 0x00004000;
|
||||
internal const uint USN_REASON_BASIC_INFO_CHANGE = 0x00008000;
|
||||
internal const uint USN_REASON_HARD_LINK_CHANGE = 0x00010000;
|
||||
internal const uint USN_REASON_COMPRESSION_CHANGE = 0x00020000;
|
||||
internal const uint USN_REASON_ENCRYPTION_CHANGE = 0x00040000;
|
||||
internal const uint USN_REASON_OBJECT_ID_CHANGE = 0x00080000;
|
||||
internal const uint USN_REASON_REPARSE_POINT_CHANGE = 0x00100000;
|
||||
internal const uint USN_REASON_STREAM_CHANGE = 0x00200000;
|
||||
internal const uint USN_REASON_CLOSE = 0x80000000;
|
||||
|
||||
internal const uint USN_REASON_ANY = 0xFFFFFFFF;
|
||||
|
||||
// ---------------------------------------------------------------- 线程访问权限(CancelSynchronousIo 需要 THREAD_TERMINATE)
|
||||
|
||||
internal const uint THREAD_TERMINATE = 0x0001;
|
||||
internal static readonly IntPtr INVALID_HANDLE_VALUE = new(-1);
|
||||
|
||||
// ================================================================ 结构体
|
||||
|
||||
/// <summary>MFT_ENUM_DATA_V0 —— FSCTL_ENUM_USN_DATA 的输入。x64 大小 24。</summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct MftEnumDataV0
|
||||
{
|
||||
internal ulong StartFileReferenceNumber; // 0
|
||||
internal long LowUsn; // 8 枚举时用 0
|
||||
internal long HighUsn; // 16 枚举时用 long.MaxValue
|
||||
}
|
||||
|
||||
/// <summary>USN_JOURNAL_DATA_V0 —— FSCTL_QUERY_USN_JOURNAL 的输出。x64 大小 56。</summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct UsnJournalDataV0
|
||||
{
|
||||
internal ulong UsnJournalID; // 0
|
||||
internal long FirstUsn; // 8
|
||||
internal long NextUsn; // 16
|
||||
internal long LowestValidUsn; // 24
|
||||
internal long MaxUsn; // 32
|
||||
internal ulong MaximumSize; // 40
|
||||
internal ulong AllocationDelta; // 48
|
||||
}
|
||||
|
||||
/// <summary>READ_USN_JOURNAL_DATA_V0 —— FSCTL_READ_USN_JOURNAL 的输入。x64 大小 40。</summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct ReadUsnJournalDataV0
|
||||
{
|
||||
internal long StartUsn; // 0
|
||||
internal uint ReasonMask; // 8
|
||||
internal uint ReturnOnlyOnClose; // 12
|
||||
internal ulong Timeout; // 16 100ns 单位;0 = 无限等待
|
||||
internal ulong BytesToWaitFor; // 24 攒够这么多字节再返回(减少唤醒次数)
|
||||
internal ulong UsnJournalID; // 32
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// USN_RECORD_V2 的固定头部(不含变长文件名)。
|
||||
///
|
||||
/// 字段偏移与 Windows SDK 完全一致(x64):RecordLength@0、MajorVersion@4、FRN@8、
|
||||
/// ParentFrn@16、Usn@24、TimeStamp@32、Reason@40、SourceInfo@44、SecurityId@48、
|
||||
/// FileAttributes@52、FileNameLength@56、FileNameOffset@58;变长文件名紧跟在第 60 字节之后。
|
||||
///
|
||||
/// 显式 Pack=4 的原因:默认 8 字节对齐会把 sizeof 从 60 凑成 64(尾部补 4 字节),
|
||||
/// 字段偏移虽然不变,但用 sizeof(T) 做缓冲边界判断会凭空多要求 4 字节;
|
||||
/// Pack=4 下偏移完全不变、sizeof 恰好 60,与磁盘上的紧凑布局严格一致。
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
internal struct UsnRecordV2
|
||||
{
|
||||
internal uint RecordLength; // 0
|
||||
internal ushort MajorVersion; // 4 必须 == 2
|
||||
internal ushort MinorVersion; // 6
|
||||
internal ulong FileReferenceNumber; // 8 高 16 位是序列号,低 48 位是 MFT 记录号
|
||||
internal ulong ParentFileReferenceNumber; // 16 同上
|
||||
internal long Usn; // 24
|
||||
internal long TimeStamp; // 32 FILETIME(100ns since 1601)
|
||||
internal uint Reason; // 40
|
||||
internal uint SourceInfo; // 44
|
||||
internal uint SecurityId; // 48
|
||||
internal uint FileAttributes; // 52
|
||||
internal ushort FileNameLength; // 56 字节数,非字符数
|
||||
internal ushort FileNameOffset; // 58 相对记录起始的字节偏移
|
||||
|
||||
internal const int Size = 60;
|
||||
}
|
||||
|
||||
/// <summary>NTFS_VOLUME_DATA_BUFFER —— FSCTL_GET_NTFS_VOLUME_DATA 的输出。x64 大小 96。</summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct NtfsVolumeDataBuffer
|
||||
{
|
||||
internal long VolumeSerialNumber; // 0
|
||||
internal long NumberSectors; // 8
|
||||
internal long TotalClusters; // 16
|
||||
internal long FreeClusters; // 24
|
||||
internal long TotalReserved; // 32
|
||||
internal uint BytesPerSector; // 40
|
||||
internal uint BytesPerCluster; // 44
|
||||
internal uint BytesPerFileRecordSegment; // 48
|
||||
internal uint ClustersPerFileRecordSegment; // 52
|
||||
internal long MftValidDataLength; // 56
|
||||
internal long MftStartLcn; // 64
|
||||
internal long Mft2StartLcn; // 72
|
||||
internal long MftZoneStart; // 80
|
||||
internal long MftZoneEnd; // 88
|
||||
}
|
||||
|
||||
/// <summary>NTFS_FILE_RECORD_INPUT_BUFFER —— FSCTL_GET_NTFS_FILE_RECORD 的输入。x64 大小 8。</summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct NtfsFileRecordInputBuffer
|
||||
{
|
||||
internal ulong FileReferenceNumber; // 只取低 48 位记录号
|
||||
}
|
||||
|
||||
/// <summary>NTFS_FILE_RECORD_OUTPUT_BUFFER 的固定头。x64 大小 12(后面紧跟变长记录)。</summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct NtfsFileRecordOutputBuffer
|
||||
{
|
||||
internal ulong FileReferenceNumber; // 0
|
||||
internal uint FileRecordLength; // 8
|
||||
}
|
||||
|
||||
/// <summary>FSCTL_CREATE_USN_JOURNAL 的输入。x64 大小 16;两个字段都为 0 = 使用系统默认值。</summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct CreateUsnJournalData
|
||||
{
|
||||
internal ulong MaximumSize; // 0 = 系统默认
|
||||
internal ulong AllocationDelta; // 0 = 系统默认
|
||||
}
|
||||
|
||||
/// <summary>FSCTL_DELETE_USN_JOURNAL 的输入。x64 大小 16(ulong + DWORD + 对齐填充)。</summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct DeleteUsnJournalData
|
||||
{
|
||||
internal ulong UsnJournalID;
|
||||
internal uint DeleteFlags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FILETIME 的精确布局:两个 DWORD。
|
||||
/// 刻意不用 long —— 在 LayoutKind.Sequential 下 long 会带来 8 字节对齐,
|
||||
/// 从而把 BY_HANDLE_FILE_INFORMATION 的后续字段全部顶偏。
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct FileTimeValue
|
||||
{
|
||||
internal uint LowDateTime;
|
||||
internal uint HighDateTime;
|
||||
|
||||
internal readonly long ToInt64() => ((long)HighDateTime << 32) | LowDateTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BY_HANDLE_FILE_INFORMATION(GetFileInformationByHandle 的输出)。x64 大小 52。
|
||||
/// 其中 FileIndexHigh/Low 合起来就是文件的 64 位 FRN(低 48 位记录号 + 高 16 位序列号),
|
||||
/// 与 USN_RECORD_V2 里的 FileReferenceNumber 口径一致 —— 这是 RDCW 回退路径能定位索引条目的关键。
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct ByHandleFileInformation
|
||||
{
|
||||
internal uint FileAttributes; // 0
|
||||
internal FileTimeValue CreationTime; // 4
|
||||
internal FileTimeValue LastAccessTime; // 12
|
||||
internal FileTimeValue LastWriteTime; // 20
|
||||
internal uint VolumeSerialNumber; // 28
|
||||
internal uint FileSizeHigh; // 32
|
||||
internal uint FileSizeLow; // 36
|
||||
internal uint NumberOfLinks; // 40
|
||||
internal uint FileIndexHigh; // 44
|
||||
internal uint FileIndexLow; // 48
|
||||
|
||||
internal readonly ulong FileIndex => ((ulong)FileIndexHigh << 32) | FileIndexLow;
|
||||
|
||||
internal readonly long FileSize => ((long)FileSizeHigh << 32) | FileSizeLow;
|
||||
}
|
||||
|
||||
/// <summary>FILE_NOTIFY_INFORMATION 的固定头(变长文件名紧跟其后,UTF-16,不以 NUL 结尾)。</summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct FileNotifyInformation
|
||||
{
|
||||
internal uint NextEntryOffset; // 0
|
||||
internal uint Action; // 4
|
||||
internal uint FileNameLength; // 8,字节数
|
||||
// WCHAR FileName[1]; // 12
|
||||
internal const int HeaderSize = 12;
|
||||
}
|
||||
|
||||
// ================================================================ P/Invoke
|
||||
|
||||
[DllImport("kernel32.dll", EntryPoint = "CreateFileW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
internal static extern SafeFileHandle CreateFileW(
|
||||
string lpFileName,
|
||||
uint dwDesiredAccess,
|
||||
uint dwShareMode,
|
||||
IntPtr lpSecurityAttributes,
|
||||
uint dwCreationDisposition,
|
||||
uint dwFlagsAndAttributes,
|
||||
IntPtr hTemplateFile);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
internal static extern unsafe bool DeviceIoControl(
|
||||
SafeFileHandle hDevice,
|
||||
uint dwIoControlCode,
|
||||
void* lpInBuffer,
|
||||
uint nInBufferSize,
|
||||
void* lpOutBuffer,
|
||||
uint nOutBufferSize,
|
||||
out uint lpBytesReturned,
|
||||
IntPtr lpOverlapped);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
internal static extern unsafe bool ReadFile(
|
||||
SafeFileHandle hFile,
|
||||
void* lpBuffer,
|
||||
uint nNumberOfBytesToRead,
|
||||
out uint lpNumberOfBytesRead,
|
||||
IntPtr lpOverlapped);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
internal static extern unsafe bool SetFilePointerEx(
|
||||
SafeFileHandle hFile,
|
||||
long liDistanceToMove,
|
||||
out long lpNewFilePointer,
|
||||
uint dwMoveMethod);
|
||||
|
||||
internal const uint FILE_BEGIN = 0;
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
internal static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
internal static extern IntPtr OpenThread(uint dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
internal static extern uint GetCurrentThreadId();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
internal static extern bool CancelSynchronousIo(IntPtr hThread);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
internal static extern bool GetFileInformationByHandle(SafeFileHandle hFile, out ByHandleFileInformation lpFileInformation);
|
||||
|
||||
/// <summary>
|
||||
/// 递归监听目录变化。lpOverlapped == NULL 时是同步阻塞调用(靠 CancelSynchronousIo 打断)。
|
||||
/// 返回 TRUE 且 bytesReturned == 0 表示变更缓冲区溢出(ERROR_NOTIFY_ENUM_DIR),期间的事件已丢失。
|
||||
/// </summary>
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
internal static extern unsafe bool ReadDirectoryChangesW(
|
||||
SafeFileHandle hDirectory,
|
||||
void* lpBuffer,
|
||||
uint nBufferLength,
|
||||
bool bWatchSubtree,
|
||||
uint dwNotifyFilter,
|
||||
out uint lpBytesReturned,
|
||||
IntPtr lpOverlapped,
|
||||
IntPtr lpCompletionRoutine);
|
||||
|
||||
// ================================================================ 托管包装
|
||||
|
||||
/// <summary>把读/写缓冲固定后调用 DeviceIoControl;返回 false 时用 <see cref="Marshal.GetLastWin32Error"/> 取错误码。</summary>
|
||||
internal static unsafe bool Ioctl(SafeFileHandle handle, uint code, ReadOnlySpan<byte> input, Span<byte> output, out int bytesReturned)
|
||||
{
|
||||
fixed (byte* pIn = input)
|
||||
fixed (byte* pOut = output)
|
||||
{
|
||||
var ok = DeviceIoControl(
|
||||
handle, code,
|
||||
input.Length == 0 ? null : pIn, (uint)input.Length,
|
||||
output.Length == 0 ? null : pOut, (uint)output.Length,
|
||||
out var ret, IntPtr.Zero);
|
||||
bytesReturned = (int)ret;
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>在卷句柄上做一次带偏移的同步读取(卷句柄偏移 = 卷内绝对字节偏移)。</summary>
|
||||
internal static unsafe int ReadAt(SafeFileHandle handle, Span<byte> buffer, long offset)
|
||||
{
|
||||
if (!SetFilePointerEx(handle, offset, out _, FILE_BEGIN)) return -1;
|
||||
fixed (byte* p = buffer)
|
||||
{
|
||||
if (!ReadFile(handle, p, (uint)buffer.Length, out var read, IntPtr.Zero)) return -1;
|
||||
return (int)read;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按路径取文件的 64 位 FRN 与基本元数据(大小/属性/最后写入时间)。
|
||||
/// 这是 RDCW 回退监听能定位索引条目的基础:句柄上的 FileIndex 与 USN 的 FRN 同口径。
|
||||
/// 只要求 FILE_READ_ATTRIBUTES,普通用户也能用(目录需要 FILE_FLAG_BACKUP_SEMANTICS)。
|
||||
/// </summary>
|
||||
internal static bool TryStatPath(string path, out ByHandleFileInformation info)
|
||||
{
|
||||
info = default;
|
||||
var handle = CreateFileW(
|
||||
path,
|
||||
FILE_READ_ATTRIBUTES,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
IntPtr.Zero,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS,
|
||||
IntPtr.Zero);
|
||||
if (handle.IsInvalid)
|
||||
{
|
||||
handle.Dispose();
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
return GetFileInformationByHandle(handle, out info);
|
||||
}
|
||||
finally
|
||||
{
|
||||
handle.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>把 Win32 错误码翻译成中文可读信息,供 UI 直接展示。</summary>
|
||||
internal static string DescribeError(int error, string? context = null)
|
||||
{
|
||||
var text = error switch
|
||||
{
|
||||
ERROR_ACCESS_DENIED => "访问被拒绝(需要管理员权限)",
|
||||
ERROR_INVALID_FUNCTION => "函数不正确(非 NTFS 卷、或无管理员权限调用 NTFS 专属 FSCTL 都会返回它)",
|
||||
ERROR_NOT_SUPPORTED => "该卷不支持此操作",
|
||||
ERROR_JOURNAL_NOT_ACTIVE => "该卷未启用 USN 变更日志",
|
||||
ERROR_JOURNAL_DELETE_IN_PROGRESS => "USN 变更日志正在被删除",
|
||||
ERROR_JOURNAL_ENTRY_DELETED => "请求的 USN 记录已被删除",
|
||||
ERROR_HANDLE_EOF => "已到数据末尾",
|
||||
ERROR_OPERATION_ABORTED => "操作已取消",
|
||||
ERROR_CANCELLED => "操作已取消",
|
||||
ERROR_NOT_READY => "卷未就绪",
|
||||
_ => SafeSystemMessage(error)
|
||||
};
|
||||
return context is null ? text : $"{context}:{text}(Win32 错误 {error})";
|
||||
}
|
||||
|
||||
private static string SafeSystemMessage(int error)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new Win32Exception(error).Message;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "未知错误";
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================ 记录号归一化
|
||||
|
||||
/// <summary>MFT 记录号掩码:FRN 的低 48 位。</summary>
|
||||
internal const ulong RecordNumberMask = 0x0000_FFFF_FFFF_FFFFUL;
|
||||
|
||||
/// <summary>剥掉 FRN 高 16 位的序列号,只保留 MFT 记录号(全索引统一用这个做键)。</summary>
|
||||
internal static ulong NormalizeFrn(ulong frn) => frn & RecordNumberMask;
|
||||
}
|
||||
Reference in New Issue
Block a user