首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >估计NTFS卷上的USN记录数

估计NTFS卷上的USN记录数
EN

Stack Overflow用户
提问于 2012-07-05 07:39:14
回答 2查看 1.7K关注 0票数 4

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

有没有一种方法可以在运行卷之前估计卷上的记录数,以便显示进度?

我猜整个卷的USN数据是从MFT生成的,每个文件(大约)有一条记录。因此,估计MFT中活动文件数量的一种方法可能会起作用。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2012-07-05 12:36:25

您可以使用FSCTL_GET_NTFS_VOLUME_DATA来获取MFT的字节长度。如果将其与选定的代表性卷上的记录数量进行比较,则可以估计单个MFT记录的平均长度,并使用该长度计算特定卷上的记录数量的估计值。

因为MFT包含(例如)每个文件的安全信息,所以卷的平均长度将因卷而异,所以我认为您只能获得数量级的精度,但在大多数情况下可能已经足够好了。

另一种方法是假设文件引用编号线性增加,这大致是正确的。您可以使用FSCTL_ENUM_USN_DATA来确定是否存在引用编号大于特定猜测的文件;您不需要超过128次猜测就可以确定实际的最大引用编号。这至少会给你一个在0到100之间的完成百分比,它不会完全一致,但进度条永远不会是一致的。:-)

附加:

仔细观察,在Windows7 x64上,由FSCTL_ENUM_USN_DATA (在第一个USN_RECORD结构之前返回的四字)返回的"next id“字段毕竟不是文件引用编号,而是文件记录段编号。因此,正如您所观察到的,最后返回的id号乘以BytesPerFileRecordSegment (1024)等于MftValidDataLength。

文件参考编号似乎由两部分组成。低六个字节包含文件记录段编号。每次请求返回的第一条记录总是有一个FRN,它的段号与输入到StartFileReferenceNumber的"next id“相同,当StartFileReferenceNumber为零时,第一次调用除外。上面的两个字节包含未指定的附加信息,它永远不会为零。

FSCTL_ENUM_USN_DATA似乎接受文件记录段编号(在这种情况下,最高的两个字节为零)或文件引用编号(在这种情况下,最高的两个字节为非零)。

奇怪的是,我找不到具有相同记录段编号的两个记录。这表明每个文件记录在MFT中至少使用1K,这似乎是不合理的。

无论如何,结果是将"next id“乘以BytesPerFileRecordSegment,再除以MftValidDataLength得到一个完成百分比,只要在返回无意义的结果时优雅地处理即可。

票数 4
EN

Stack Overflow用户

发布于 2019-09-01 12:00:59

事实上,NTFS_VOLUME_DATA_BUFFER / NTFS_EXTENDED_VOLUME_DATA结构的MftValidDataLength字段对FSCTL_ENUM_USN_DATA将要/将要返回的USN记录的数量设置了上限(即,假设在测量估计和枚举之间没有向日志中添加额外的记录……)

在下面的C#示例中,我将vd.MftValidDataLength值除以vd.BytesPerFileRecordSegment,确保通过在除法之前先添加来向上舍入。至于除数,我相信它的值在任何平台或系统上都是通用的1,024,如果你喜欢硬编码的话。

代码语言:javascript
复制
[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;
};

典型常量,为清晰起见进行了删节:

代码语言:javascript
复制
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方式...

代码语言:javascript
复制
// 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调用本身选择缓冲区大小没有帮助,因为每次迭代中返回的USN_RECORD结构的大小随报告的文件名的长度而变化。

因此,如果您碰巧为所有 of USN_RECORD结构提供了足够大的缓冲区,那么DeviceIoControl确实会尽职尽责地在一次调用中为您提供所有这些缓冲区(从而避免了迭代调用循环的复杂性,从而大大简化了代码),但上面的少量计算并不能给出对缓冲区大小的任何原则性估计,除非您愿意使用它来进行某种粗略的高估。

相反,该值用于在FSCTL_ENUM_USN_DATA枚举操作之前预先分配您自己的固定大小的数据结构,这是您肯定需要的。因此,如果您有自己的值类型,您将为每个USN条目创建该值类型(例如,虚拟结构...)

代码语言:javascript
复制
[StructLayout(LayoutKind.Sequential)]
public struct MFT_IX_REC
{
    public ushort seq;
    public ushort parent_ix_hi;
    public uint parent_ix;
};

然后,使用上面的估计值,您可以在DeviceIoControl之前预先分配这些元素的数组,而不必担心在迭代期间调整大小。

代码语言:javascript
复制
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..
}

这种动态调整数组大小的操作(通常需要在事先不知道条目数量的情况下进行)是一个很大的性能优势,因为它避免了在每次调整大小时将现有数据从旧数组复制到新的、更大的数组所需的昂贵的巨型memcpy操作。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/11336390

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档