首次使用USN日志时,必须使用FSCTL_ENUM_USN_DATA控制代码枚举卷的整个USN记录集。这通常是一个漫长的操作。
有没有办法在运行之前估计卷上的记录数量,因此可以显示进度?
我猜测整个卷的USN数据是从MFT生成的,每个文件有一条记录(大约)。因此,估算MFT中活动文件数量的方法也许可行。
答案 0 :(得分:4)
您可以使用FSCTL_GET_NTFS_VOLUME_DATA来获取MFT的字节长度。如果将此值与选定代表卷上的记录数进行比较,则可以估计单个MFT记录的平均长度,并使用此值来计算特定卷上记录数的估计值。
由于MFT包含(例如)每个文件的安全信息,因此平均长度会因音量而异,因此我认为您只能获得数量级的准确性,但它可能是在大多数情况下都足够好。
另一种方法是假设文件引用数字线性增加,这大致是正确的。您可以使用FSCTL_ENUM_USN_DATA来查明是否有任何文件的参考编号高于特定的猜测;您需要不超过128个猜测来确定实际的最大参考编号。这至少会给你一个百分比在任何给定点完成0到100之间,它不会完全统一,但进度条永远不会。 : - )
其他强> 的
仔细观察,在Windows 7 x64上," next id" FSCTL_ENUM_USN_DATA(在第一个USN_RECORD结构之前返回的四字)返回的字段毕竟不是文件引用号,而是文件记录段号。因此,正如您所观察到的,返回的最后一个id号乘以BytesPerFileRecordSegment(1024),等于MftValidDataLength。
文件参考号似乎由两部分组成。低六个字节包含文件记录段号。从每个请求返回的第一条记录总是有一个FRN,其段号与" next id"馈入StartFileReferenceNumber,但StartFileReferenceNumber为零时的第一次调用除外。上面两个字节包含未指定的附加信息,它们永远不会为零。
似乎FSCTL_ENUM_USN_DATA接受文件记录段号(在这种情况下前两个字节为零)或一个文件引用号(在这种情况下,前两个字节是非零的)。 / p>
一个奇怪的是,我无法找到具有相同记录段号的两条记录。这表明每个文件记录在MFT中使用至少1K,这看起来并不合理。
无论如何,结果是,将#34;下一个id"增加可能是明智的。 by BytesPerFileRecordSegment并将其除以MftValidDataLength以获得完成的百分比,只要你优雅地处理它,如果这会返回一个荒谬的结果。
答案 1 :(得分:2)
实际上,MftValidDataLength
/ NTFS_VOLUME_DATA_BUFFER
结构的NTFS_EXTENDED_VOLUME_DATA
字段对将要/将要使用的 USN 记录的数量设置了上限由FSCTL_ENUM_USN_DATA
返回(也就是说,假设在测量估算值和枚举的时间之间没有在 之间添加其他记录)
在下面的 C#示例中,我将vd.MftValidDataLength
值除以vd.BytesPerFileRecordSegment
,确保 向上取整 在除法之前先添加 dividend - 1
,然后再添加“ strong>”。至于除数,我相信在任何平台或系统上,它的价值始终普遍1,024
,以防您希望对其进行硬编码。
[Serializable, StructLayout(LayoutKind.Sequential)]
public struct NTFS_EXTENDED_VOLUME_DATA
{
public VOLUME_ID /**/ VolumeSerialNumber;
public long /**/ NumberSectors;
public long /**/ TotalClusters;
public long /**/ FreeClusters;
public long /**/ TotalReserved;
public uint /**/ BytesPerSector;
public uint /**/ BytesPerCluster;
public int /**/ BytesPerFileRecordSegment; // <--
public uint /**/ ClustersPerFileRecordSegment;
public long /**/ MftValidDataLength; // <--
public long /**/ MftStartLcn;
public long /**/ Mft2StartLcn;
public long /**/ MftZoneStart;
public long /**/ MftZoneEnd;
public uint /**/ ByteCount;
public ushort /**/ MajorVersion;
public ushort /**/ MinorVersion;
public uint /**/ BytesPerPhysicalSector;
public ushort /**/ LfsMajorVersion;
public ushort /**/ LfsMinorVersion;
public uint /**/ MaxDeviceTrimExtentCount;
public uint /**/ MaxDeviceTrimByteCount;
public uint /**/ MaxVolumeTrimExtentCount;
public uint /**/ MaxVolumeTrimByteCount;
};
典型常量,为清楚起见被删节:
public enum FSCTL : uint
{
// etc... etc...
FILESYSTEM_GET_STATISTICS /**/ = (9 << 16) | 0x0060,
GET_NTFS_VOLUME_DATA /**/ = (9 << 16) | 0x0064, // <--
GET_NTFS_FILE_RECORD /**/ = (9 << 16) | 0x0068,
GET_VOLUME_BITMAP /**/ = (9 << 16) | 0x006f,
GET_RETRIEVAL_POINTERS /**/ = (9 << 16) | 0x0073,
// etc... etc...
ENUM_USN_DATA /**/ = (9 << 16) | 0x00b3,
READ_USN_JOURNAL /**/ = (9 << 16) | 0x00bb,
// etc... etc...
CREATE_USN_JOURNAL /**/ = (9 << 16) | 0x00e7,
// etc... etc...
};
伪代码如下,因为每个人都有自己喜欢的P / Invoke方式...
// etc..
if (!GetDeviceIoControl(h_vol, FSCTL.GET_NTFS_VOLUME_DATA, out NTFS_EXTENDED_VOLUME_DATA vd))
throw new Win32Exception(Marshal.GetLastWin32Error());
var c_mft_estimate = (vd.MftValidDataLength + (vd.BytesPerFileRecordSegment - 1))
/ vd.BytesPerFileRecordSegment;
太好了,那么您可以用这个值做什么?不幸的是,知道FSCTL_ENUM_USN_DATA
将返回的 USN 记录的最大上限无助于为DeviceIoControl/FSCTL_ENUM_USN_DATA
自己选择缓冲区大小,因为{{ 1}}每次迭代返回的结构的大小会根据所报告文件名的长度而变化。
因此,确实如此,如果您碰巧为USN_RECORD
结构的 all 提供了足够大的缓冲区,则USN_RECORD
确实会在一次调用中尽职地全部提供给您(从而避免了迭代调用循环的复杂性,从而大大简化了代码),上面的少量计算并没有给出任何原则上的估计那个缓冲区的大小,除非您愿意为某种总的高估使用它。
值 的用途是用于预分配 您自己的固定大小 数据结构,您将使用它在DeviceIoControl
枚举操作之前肯定需要。因此,如果您有自己的值类型,您将为每个USN条目创建该值类型(虚拟结构,例如...)
FSCTL_ENUM_USN_DATA
然后,使用上面的估计,您可以在[StructLayout(LayoutKind.Sequential)]
public struct MFT_IX_REC
{
public ushort seq;
public ushort parent_ix_hi;
public uint parent_ix;
};
之前预先分配一个这样的数组,而不必担心在迭代过程中调整大小。
DeviceIoControl
消除动态数组大小调整(通常在您不预先知道条目数时需要)是不平凡的性能优势,因为它避免了所需的昂贵的大型var med = new MFT_ENUM_DATA { ... };
// ...
var rg_mftix = new MFT_IX_REC[c_mft_estimate];
// ... ready to go, without having to check whether the array needs resizing within the loop
for (int i=0; DeviceIoControl(h_vol, FSCTL.ENUM_USN_DATA, in med, out USN_RECORD usn, ...); i++)
{
// etc..
rg_mftix[i].parent_ix = (uint)usn.ParentId;
// etc..
}
操作每次调整大小时,都可以将现有数据从旧阵列复制到新的更大的阵列中。