估计NTFS卷上的USN记录数

时间:2012-07-04 23:39:14

标签: windows ntfs journal usn

首次使用USN日志时,必须使用FSCTL_ENUM_USN_DATA控制代码枚举卷的整个USN记录集。这通常是一个漫长的操作。

有没有办法在运行之前估计卷上的记录数量,因此可以显示进度?

我猜测整个卷的USN数据是从MFT生成的,每个文件有一条记录(大约)。因此,估算MFT中活动文件数量的方法也许可行。

2 个答案:

答案 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.. } 操作每次调整大小时,都可以将现有数据从旧阵列复制到新的更大的阵列中。