Winapi 如何从NTFS更改日志中获取已删除文件的完整路径?

Winapi 如何从NTFS更改日志中获取已删除文件的完整路径?,winapi,visual-c++,ntfs,deviceiocontrol,Winapi,Visual C++,Ntfs,Deviceiocontrol,我正在读取NTFS更改日志以监视驱动器上的任何文件更改,但返回的USN_记录只包括关联文件的文件名,而不包括完整路径 为了获取完整路径,我使用FileReferenceNumber(也在USN_记录中)和OpenFileById API来获取关联文件的句柄,然后使用GetFinalPathNameByHandleA来获取完整路径 只要该文件仍然存在,这就非常有效,但是如果该文件已被删除(例如,如果我正在处理USN_记录以进行文件删除),那么OpenFileById显然会失败,因此我无法获得完整路

我正在读取NTFS更改日志以监视驱动器上的任何文件更改,但返回的USN_记录只包括关联文件的文件名,而不包括完整路径

为了获取完整路径,我使用FileReferenceNumber(也在USN_记录中)和OpenFileById API来获取关联文件的句柄,然后使用GetFinalPathNameByHandleA来获取完整路径

只要该文件仍然存在,这就非常有效,但是如果该文件已被删除(例如,如果我正在处理USN_记录以进行文件删除),那么OpenFileById显然会失败,因此我无法获得完整路径

虽然USN_记录确实包含一个ParentFileReferenceNumber成员(我可以使用OpenFileById打开该成员),该成员应允许我获取父目录的名称,但同样的问题也存在,即如果父目录已被删除怎么办

此外,即使父目录尚未删除,我如何向上走到下一个目录(即父目录的父目录),依此类推,直到到达卷根目录

下面是我的示例代码:

#include "stdafx.h"
#include <Windows.h>
#include <WinIoCtl.h>
#include <stdio.h>
#include <string>

using std::string;
#define BUF_LEN 4096

std::string GetFullPath(HANDLE hVol, DWORDLONG fileRefNum)
{
    FILE_ID_DESCRIPTOR fid;
    ZeroMemory(&fid, sizeof(fid));
    fid.dwSize = sizeof(fid);
    fid.Type = FileIdType;
    fid.FileId.QuadPart = fileRefNum;   

    HANDLE handle = OpenFileById(hVol, &fid, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, FILE_FLAG_BACKUP_SEMANTICS);
    if (handle == INVALID_HANDLE_VALUE)
        return "Error: " + std::to_string(GetLastError());

    char buffer[1024];
    GetFinalPathNameByHandleA(handle, &buffer[0], 1024, 0);

    return string(&buffer[0]);
}

void main()
{
    // Open the volume
    DWORD dwBytes;
    DWORD dwRetBytes;
    HANDLE hVol = CreateFile(TEXT("\\\\.\\c:"), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
    if (hVol == INVALID_HANDLE_VALUE)
    {
        printf("CreateFile failed (%d)\n", GetLastError());
        return;
    }

    // Query the Journal
    USN_JOURNAL_DATA_V0 JournalData;
    if (!DeviceIoControl(hVol, FSCTL_QUERY_USN_JOURNAL, NULL, 0, &JournalData, sizeof(JournalData), &dwBytes, NULL))
    {
        printf("Query journal failed (%d)\n", GetLastError());
        return;
    }


    printf("Journal ID: %I64x\n", JournalData.UsnJournalID);
    printf("FirstUsn: %I64x\n\n", JournalData.FirstUsn);

    READ_USN_JOURNAL_DATA_V0 ReadData;
    ZeroMemory(&ReadData, sizeof(ReadData));
    ReadData.ReasonMask = 0xFFFFFFFF;
    ReadData.UsnJournalID = JournalData.UsnJournalID;
    PUSN_RECORD UsnRecord;
    CHAR Buffer[BUF_LEN];
    for (int loop = 0; loop <= 100; loop++)
    {
        // Read the journal
        ZeroMemory(Buffer, BUF_LEN);
        if (!DeviceIoControl(hVol, FSCTL_READ_USN_JOURNAL, &ReadData, sizeof(ReadData), &Buffer, BUF_LEN, &dwBytes, NULL))
        {
            printf("Read journal failed (%d)\n", GetLastError());
            return;
        }
        dwRetBytes = dwBytes - sizeof(USN);

        // Find the first record
        UsnRecord = (PUSN_RECORD)(((PUCHAR)Buffer) + sizeof(USN));
        printf("****************************************\n");

        // This loop could go on for a long time, given the current buffer size.
        while (dwRetBytes > 0)
        {
            printf("USN:            %I64x\n", UsnRecord->Usn);
            printf("Filename:       %.*S\n", UsnRecord->FileNameLength / 2, UsnRecord->FileName);
            printf("Full filename:  %s\n", GetFullPath(hVol, UsnRecord->FileReferenceNumber).c_str());
            printf("Reason:         %x\n", UsnRecord->Reason);
            printf("\n");

            dwRetBytes -= UsnRecord->RecordLength;

            // Find the next record
            UsnRecord = (PUSN_RECORD)(((PCHAR)UsnRecord) + UsnRecord->RecordLength);
        }
        // Update starting USN for next call
        ReadData.StartUsn = *(USN *)&Buffer;
    }

    CloseHandle(hVol);
}

实际上,即使对于未删除的文件,您也可以在usn记录中获得无效的
FileReferenceNumber
。通过它打开文件并返回
错误\u无效\u参数
(87)。一般来说,获取完整路径的方法不是非常有效的。需要将
GetFileInformationByHandleEx
FileNameInfo
一起使用。但是,并不是每个文件都可以打开。我认为这里唯一的选择是通过
FSCTL_ENUM_USN_DATA
列出完整的文件列表,而不是排序并自己搜索每个文件的父文件。比如说,在我的测试中,我在一些磁盘上找到了166774个文件的父母,而没有找到47个。谢谢你的回复@RbMm。是什么让您认为FileReferenceNumber可能对未删除的文件无效?我以前在任何地方都没见过这一点。同样,为什么GetFinalPathNameByHandle不是获取完整路径的有效方法,为什么使用GetFileInformationByHandleEx被认为更好?GetFinalPathNameByHandle是否存在已知问题?1-仅通过测试,当我枚举usn记录时-我查看记录时没有
usn\u原因\u文件\u删除
,但它的id已经无效或属于另一个文件。但是,此文件可能存在另一条usn记录,且已带有
usn\u原因\u文件\u删除
。关于
GetFileInformationByHandleEx
-它对文件系统执行单个调用,并返回文件系统内部的完整路径。
GetFinalPathNameByHandle
执行许多调用—它在内部也调用了
GetFileInformationByHandleEx
,也称为
ZwQueryObject
(这也是对fs的调用),打开、调用和关闭装载管理器。。所以简单地说,permofance-巨大的差异再次感谢@RbMm。是的,这也是我的想法,即使你处理一个记录的原因不是USN_原因_文件_删除,也可能是日志中稍后还有另一个USN_原因_文件_删除记录。有趣的是,我刚刚尝试使用GetFinalPathNameByHandle和GetFileInformationByHandleEx,我发现了一个可以成功打开的文件(使用OpenFileById)调用GetFinalPathNameByHandle时出现错误2(找不到文件),但调用GetFinalPathNameByHandle时失败(使用相同的句柄)GetFileInformationByHandleEx!我可以在资源管理器中看到该文件,因此我不确定GetFinalPathNameByHandle失败的原因。奇怪。即使对于未删除的文件,您也可以在usn记录中获得无效的
FileReferenceNumber
。通过它打开文件并返回
错误\u无效\u参数
(87)。一般来说,
GetFinalPathNameByHandleA
不是获取完整路径的有效方法。需要将
GetFileInformationByHandleEx
FileNameInfo
一起使用。但是,并不是每个文件都可以打开。我认为这里的唯一选项是通过
FSCTL\u ENUM\u USN\u DATA
列出完整文件,而不是对其进行排序并自己搜索父文件每个文件。比如说,在我的测试中,我在一些磁盘上找到了166774个文件的父文件,而没有找到47个。谢谢你的回复@RbMm。是什么让你认为FileReferenceNumber可能对未删除的文件无效?我以前从未在任何地方看到过这一点。同样,为什么GetFinalPathName ByHandle不是获取完整路径的有效方法,为什么使用GetFileInformationByHandleEx是否被认为更好?GetFinalPathNameByHandle是否存在已知问题?1-通过测试,当我枚举usn记录时-我查看记录时没有
usn\u原因\u文件\u删除
,但它的id已经无效或属于另一个文件。但是,对于这个文件,可能存在另一个usn记录
USN\u REASON\u FILE\u DELETE
。about
GetFileInformationByHandleEx
-它对文件系统执行单个调用,并返回文件系统内部的完整路径。
GetFinalPathName ByHandle
执行许多调用-它在内部也调用了
GetFileInformationByHandleEx
,也称为
ZwQueryObject
(这也是对fs的调用),打开,调用并关闭挂载管理器..所以只需permofance-巨大的差异再次感谢@RbMm。是的,这也是我的想法,即使你处理一个记录的原因不是USN_原因_文件_删除,也可能是日志中稍后还有另一个USN_原因_文件_删除记录。有趣的是,我刚刚尝试使用了GetFinalPathNameByHandle和GetFileInformationByHandleEx,我发现一个文件可以成功打开(使用OpenFileById),但在调用GetFinalPathNameByHandle时出现错误2(找不到文件),但在调用(使用相同的句柄)时成功GetFileInformationByHandleEx!我可以在资源管理器中看到该文件,因此我不确定GetFinalPathNameByHandle失败的原因。奇怪。
USN:            8ebc423a8
Filename:       999000000167237.xml
Full filename:  Error: 87
Reason:         80000200

USN:            8ebc42410
Filename:       999000000167238.xml
Full filename:  Error: 87
Reason:         80000200

USN:            8ebc42478
Filename:       MobilityDB.db
Full filename:  \\?\C:\ProgramData\NGC\Open Mobile\Profiles\12316\MobilityDB.db
Reason:         1