【问题标题】:How to get the full path for USN journal query?如何获取 USN 期刊查询的完整路径?
【发布时间】:2015-10-24 03:18:22
【问题描述】:

我正在尝试通过 MSDN (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365736%28v=vs.85%29.aspx) 上的示例了解如何查询 USN 日志以跟踪 NTFS 驱动器上的文件更改。示例代码运行良好。

然而,在这个示例代码中,USN_RECORD 结构只返回一个文件参考号和文件名。它不会返回文件的完整路径。有谁知道如何查询 USN 日志以返回完整路径?或者有办法从文件参考号中获取完整路径?

谢谢。

【问题讨论】:

    标签: c++ visual-studio winapi msdn ntfs-mft


    【解决方案1】:

    USN_RECORD 结构的ParentFileReferenceNumber 成员是包含该文件的目录的引用号。

    您可以使用FSCTL_ENUM_USN_DATA 按参考号查找文件(或目录!)。您将需要迭代树以构建完整路径。 this answer 中有一些代码作为示例可能会有所帮助。

    此代码查找根目录的参考号,因此您可以知道何时完成:

    HANDLE rootdir_handle;
    USN_RECORD * rootdir_usn;
    
    printf("Opening root directory.\n");
    
    rootdir_handle = CreateFile(L"\\\\?\\C:\\", GENERIC_READ, 
                                FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 
                                FILE_FLAG_BACKUP_SEMANTICS, NULL);
    
    if (rootdir_handle == INVALID_HANDLE_VALUE)
    {
        printf("CreateFile: %u\n", GetLastError());
        return 0;
    }
    
    if (!DeviceIoControl(rootdir_handle, FSCTL_READ_FILE_USN_DATA, NULL, 0, 
                         buffer, BUFFER_SIZE, &bytecount, NULL))
    {
        printf("FSCTL_READ_FILE_USN_DATA: %u\n", GetLastError());
    }
    else
    {
        rootdir_usn = (USN_RECORD *)buffer;
        show_record(rootdir_usn, FALSE);
        rootdir = rootdir_usn->FileReferenceNumber;
    }
    

    【讨论】:

    • @moonbear,您建议的编辑不正确。 FSCTL_ENUM_USN_DATA 的输出以 USN 开头,但 FSCTL_READ_FILE_USN_DATA 的输出不是。
    【解决方案2】:

    我一直在尝试避免递归父目录搜索以获得完整路径,因为我的初始测试增加了解析路径所需的总时间。

    在使用了几个小时的 windbg 和一些 OSR Onlinefourm 的帮助之后,我终于明白了。

    发布答案以帮助遇到同样问题的其他人。

    我目前的解决方案如下。

    USN_RECORD-> FileReferenceNumber 完全依赖于 USN_RECORD 的版本,一旦从 FileReferenceNumber 中提取 FILE_ID_DESCRIPTOR,就可以调用 OpenFileById() 并传递 FILE_ID_DESCRIPTOR 来获取父文件夹的句柄。

    然后可以调用 GetFinalPathNameByHandle() 来获取 ParentDirectory 路径。

    下面是我提取 FILE_ID_DESCRIPTOR 的代码

    如果是 USN_RECORD_V2 中的 FileId,则 FileReferenceNu DWORDLONG。

    FILE_ID_DESCRIPTOR getFileIdDescriptor(const DWORDLONG fileId)
    {
        FILE_ID_DESCRIPTOR fileDescriptor;
        fileDescriptor.Type = FileIdType;
        fileDescriptor.FileId.QuadPart = fileId;
        fileDescriptor.dwSize = sizeof(fileDescriptor);
    
        return fileDescriptor;
    }
    

    如果您最终得到 UNS_RECORD_V3,则 fileId 的类型为 FILE_ID_128,这里是提取 FileId 的代码。

    FILE_ID_DESCRIPTOR getFileIdDescriptor(const FILE_ID_128& fileId)
    {
        FILE_ID_DESCRIPTOR fileDescriptor;
        fileDescriptor.Type = ExtendedFileIdType;
        fileDescriptor.ExtendedFileId = fileId;
        fileDescriptor.dwSize = sizeof(fileDescriptor);
        return fileDescriptor;
    }
    

    提取 FileId 后,您可以通过以下方式获取父路径。

    TCHAR filePath[MAX_PATH];
    HANDLE hh= OpenFileById(volume_, &(getFileIdDescriptor(UsnRecord->FileReferenceNumber)), 0, 0, 0, 0);
    GetFinalPathNameByHandle(hh,filePath, MAX_PATH, 0);
    

    可以找到参考实现@https://github.com/kirankumarcelestial/NTFSChangeJournalUserMode

    但是我发现GetFilePathNameByHandle()其实很慢,这个API最终会调用GetFileInformationByHandleEx(),而GetFileInformationByHandleEx()是对KernelMode的一次调用,这将是获取Parent Information的有效方法。

    【讨论】:

    • TCHAR filePath[MAX_PATH] 会将您的字符串限制为 260 个字符吗?
    【解决方案3】:

    为了成为一个 Wiki,并帮助下一个搜索这个问题的人,以及我希望他们为后代存档的事实,这里是:MSDN 上解释更新序列号的页面(USN ) 日记,它是什么,它是如何工作的,以及如何查询它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-27
      • 1970-01-01
      • 1970-01-01
      • 2012-05-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多