1

我正在试验FSCTL_MOVE_FILE. 大多数情况下,一切都按预期工作。但是,有时如果我尝试重新读取(通过FSCTL_GET_NTFS_FILE_RECORD)我刚刚移动的 Mft 记录,我会得到一些错误的数据。

具体来说,如果文件记录说 $ATTRIBUTE_LIST 属性是非常驻的,我使用我的卷句柄从磁盘读取数据,我发现那里的数据内部不一致(记录长度大于数据的实际长度) .

我一看到这种情况,原因就很清楚了:我在 Ntfs 驱动程序完成写入之前读取记录。调试支持这一理论。但是知道这并不能帮助我解决它。我正在使用同步方法进行FSCTL_MOVE_FILE调用,但显然文件系统仍然可以在后台更新内容。唔。

在普通文件中,我会考虑LockFileEx使用共享锁(因为我只是在阅读)。但我不确定这对音量句柄有什么意义?而且我更不确定 Ntfs 在内部使用这种机制来确保一致性。

不过,这似乎是一个开始的地方。但是我LockFileEx对音量句柄的调用正在返回ERROR_INVALID_PARAMETER。我没有看到哪个参数可能出错,除非它是音量句柄本身。也许他们只是不支持锁?CreateFile或者也许在打开音量手柄时我应该设置一些特殊的标志?我尝试启用SE_BACKUP_NAMEand FILE_FLAG_BACKUP_SEMANTICS,但错误保持不变。

展望未来,我可以在这里看到一些替代方案:

  1. 弄清楚如何使用卷句柄锁定部分(并希望 Ntfs 驱动程序也这样做)。在这一点上似乎很可疑。
  2. 弄清楚如何刷新我刚刚移动的文件的元数据(nb:MOVE_FILE_DATA.FileHandle 的 FlushFileBuffers 没有帮助。也许刷新卷句柄?)。
  3. 是否有一些“官方”方法可以读取不涉及ReadFile卷句柄的非常驻数据?我没有找到,但也许我错过了。
  4. 移动数据后稍等片刻,让驱动程序完成更新所有内容。呸。

FWIW,这是一些针对卷句柄执行 LockFileEx 的测试代码。请注意,您必须以管理员身份运行才能锁定卷句柄。我正在使用J:,因为那是我的闪存驱动器。50000 是随机挑选的,但应该小于闪存驱动器的大小。

void Lock()
{
    WCHAR path[] = L"\\\\.\\j:";

    HANDLE hRootHandle = CreateFile(path,
                             GENERIC_READ, 
                             FILE_SHARE_READ | FILE_SHARE_WRITE, 
                             NULL, 
                             OPEN_EXISTING, 
                             0, 
                             NULL);

    OVERLAPPED olap;
    memset(&olap, 0, sizeof(olap));
    olap.Offset = 50000;

    // Lock 1k of data at offset 50000
    BOOL b = LockFileEx(hRootHandle, 1, 0, 1024, 0, &olap);
    DWORD j = GetLastError();

    CloseHandle(hRootHandle);
}

查看坏数据的代码……相当复杂。然而,它很容易重现。当它失败时,我最终尝试读取长度为“0”的可变长度 $ATTRIBUTE_LIST 条目,这会导致无限循环,因为看起来我从未完成读取整个缓冲区。如果长度为零,我正在通过退出来解决它,但我担心缓冲区中的“剩余垃圾”而不是干净的零。检测到这是不可能的,所以我希望有更好的解决方案。

毫不奇怪,关于这方面的信息并不多。因此,如果有人在这里有一些经验,我可以使用一些见解。


编辑1:

更多不太有效的事情:

  • LockFileEx 仍然没有运气。
  • 我尝试冲洗音量手柄(如保罗建议的那样)。虽然这有效,但它使我的执行时间增加了一倍以上。而且,严格来说,它仍然不能解决问题。仍然不能保证 Ntfs 不会在 FlushFileBuffers 和 FSCTL_GET_NTFS_FILE_RECORD / ReadFile 之间做出更多改变。
  • 我想知道 $STANDARD_INFORMATION 属性的“RecordChanged”时间戳。但是,由于对 ATTRIBUTE_LIST 的这些更改,它没有被更改。
  • 对文件进行分段最终会导致添加一个 ATTRIBUTE_LIST,并且随着分段的不断增加,更多的 DATA 记录将添加到该列表中。添加 DATA 记录后,UpdateSequenceNumber(不是 MFT_SEGMENT_REFERENCE 的一部分,另一个)会更新。不幸的是,有一系列事件可以执行此更新。显然,ATTRIBUTE_LIST 缓冲区“长度”在“UpdateSequenceNumber”之前更新。因此,查看“UpdateSequenceNumber”是否已更改无助于避免读取(可能)错误信息。

我的下一个最佳想法是看看 Ntfs 是否总是在更新记录长度之前将新字节归零(或者可能每当记录长度缩小时?)。如果我可以依赖记录长度为零(而不是任何剩余数据可能占用这些字节),我可以假装称之为固定。

4

2 回答 2

2

您的问题的解决方案似乎确实是FlushFileBuffers()使用音量句柄进行调用。在MSDN页面底部附近有这样的说法:

要刷新卷上所有打开的文件,请使用卷的句柄调用 FlushFileBuffers。调用者必须具有管理权限...

该页面上的其他信息使我相信这也会刷新元数据,尽管在这种特定情况下并没有直接说明。也许你可以更新我。

为了从细节退一步看大局,出于各种原因,必须在某个地方为此提供一个 API,尽管我认为它可能不公开

于 2018-06-10T04:18:41.163 回答
1

我想我明白了。

重申目标:

在使用FSCTL_GET_NTFS_FILE_RECORD从 Mft 读取记录后,我一直发现该ATTRIBUTE_LIST记录处于“不一致状态”,​​以至于报告的记录长度大于记录中的实际数据量。读取超出已写入内容的数据似乎有风险,因为我无法确定我所读取的内容是否有效,还是剩余的垃圾。

为此,我提出了 4 个替代方案,希望它们能让我解决这个问题。

  1. 在卷上使用 LockFileEx (当我开始时这似乎是最好的答案)结果证明是一个完整的非首发。RbMm 和 eryksun(以​​及我自己的实验)提供了一些非常有说服力的证据,这只是行不通。正如 LockFileEx 中的“文件”所暗示的那样,此函数仅适用于文件。
  2. 冲洗音量手柄可使症状消失。但性能损失巨大(> 100%)。也不清楚这个问题是否真的得到了解决,或者仅仅是隐藏在这导致的放缓背后。
  3. 读取非常驻数据的“其他”api 的想法似乎是神话般的。
  4. 在执行 a 之后等待一些(未指定)时间FSCTL_MOVE_FILE不是计划,而是希望。

短时间内,看起来检查 NtfsRecord 中的 UpdateSequenceNumber 可能会提供解决方案。但是,Ntfs 在更新记录时使用的事件顺序意味着 ATTRIBUTE_LIST 的记录长度在 UpdateSequenceNumber 之前得到更新(很好)。

但后来我开始思考这可能是一个问题的确切时间。如果我忽略它,它会在哪里失败?

目前,随着 ATTRIBUTE_LIST 的增长(因为我故意大量分割文件),我遇到了这个问题。那时,由于记录长度为零,因此很容易检测到。我已经多次运行该程序,虽然这只是轶事,但随着记录的增长,额外的空间总是归零。这是有道理的,因为您在第一次分配缓冲区时会将整个缓冲区归零。标准编程实践和观察都支持这一结论。

但是当记录开始缩小时呢?还是先缩小再增长?你最终能得到剩余的数据而不是(容易解释的)零吗?

然后它击中了我: ATTRIBUTE_LIST永远不会缩小。几周前我只是在抱怨这个。即使您完全对文件进行碎片整理并且不再需要所有这些额外的 DATA 记录,Ntfs 也不会压缩它们。现在我第一次看到了为什么会这样。这可能会在 W10 中发生变化,但这可能只是对未记录函数的过于乐观的解释。

所以,我不需要担心读取垃圾数据(可能包括导致我溢出缓冲区的无意义的记录长度)。ATTRIBUTE_LIST 中的记录长度是可信的。最后一条记录的记录长度可能为零。

我可以忽略零长度记录(本质上返回增长前信息)或重新读取记录,直到 UpdateSequenceNumber 更改(表示更新完成)。

多田。

于 2018-06-10T22:03:40.290 回答