LockFileEx 可以与卷句柄一起使用吗?

Can LockFileEx be used with Volume Handles?

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

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

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

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

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

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

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

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

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 条目,这导致无限循环,因为看起来我从未读完整个缓冲区。如果长度为零,我正在通过退出来解决它,但我担心缓冲区中的 "leftover garbage" 而不是干净的零。检测到那是不可能的,所以我希望有更好的解决方案。

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


编辑 1:

更多不太有效的东西:

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

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

To flush all open files on a volume, call FlushFileBuffers with a handle to the volume. The caller must have administrative privileges...

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

从细节上退一步,看一下大局,出于各种原因, 必须在某处成为 API , 虽然我想它可能不是 public.

我想我明白了。

重申目标:

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

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

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

对于 ,看起来检查 NtfsRecord 中的 UpdateSequenceNumber 可能会提供解决方案。然而,Ntfs 在更新记录时使用的事件顺序意味着 ATTRIBUTE_LIST 的记录长度在 UpdateSequenceNumber.

之前得到更新(很好)

但后来我开始思考究竟什么时候这可能是个问题。如果我忽略它,它会在哪里失败?

目前我遇到的问题是 ATTRIBUTE_LIST 越来越大(因为我故意大量地对文件进行碎片化)。那时,由于记录长度为零,它很容易被检测到。我已经 运行 这个程序好几次了,虽然这只是轶事,但随着记录的增长,额外的 space 总是归零。这是有道理的,因为当你第一次分配它时,你会将整个缓冲区清零。标准的编程实践和观察都支持这一结论。

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

然后它击中了我:ATTRIBUTE_LIST从不缩小。我是 just complaining about this a few weeks ago. Even when you completely defragment the file and all these extra DATA records are no longer required, Ntfs doesn't compact them. And now for the first time I have a glimpse of why that might be. There's a possibility 这可能会在 W10 中改变,但这可能只是对未记录函数的过于乐观的解释。

因此,我不必担心读取垃圾数据(可能包括无意义的记录长度导致我超过 运行 缓冲区)。 ATTRIBUTE_LIST中的记录长度是可以信任的。最后一条记录的记录长度可能为零。

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

多田.