了解更改日记记录的缓冲区

返回更新序列号 (USN) 更改日志记录的控制代码 FSCTL_READ_USN_JOURNALFSCTL_ENUM_USN_DATA 会在输出缓冲区中返回类似数据。 两者都会返回一个 USN,后面跟着零个或多个更改日志记录,每个记录都是 USN_RECORD_V2USN_RECORD_V3 结构。

USN 操作的目标卷必须是 ReFS 或 NTFS 3.0 或更高版本。 要获取卷的 NTFS 版本,请打开具有管理员访问权限的命令提示符并执行以下命令:

FSUtil.exe FSInfo NTFSInfo X**:**

其中, X 是卷的驱动器号。

下面列出了获取更改日志记录的方法:

  • 使用 FSCTL_ENUM_USN_DATA 获取两个 USN 之间所有更改日志记录的列表(枚举)。
  • 使用 FSCTL_READ_USN_JOURNAL 可以更有选择性,例如选择特定的更改原因或在文件关闭时返回。

注意

这两种操作都只会返回符合指定条件的更改日志记录子集。

 

作为输出缓冲区第一项返回的 USN 是要检索的下一个记录编号的 USN。 使用此值可继续读取从结束边界向前的记录。

USN_RECORD_V2USN_RECORD_V3FileName 成员包含相关记录适用的文件名。 文件名的长度各不相同,因此 USN_RECORD_V2USN_RECORD_V3 是长度可变的结构。 其第一个成员 RecordLength 是结构的长度(包括文件名),以字节为单位。

在处理 USN_RECORD_V2USN_RECORD_V3 结构中的 FileName 成员时,不要假定文件名包含尾部的 '\0' 分隔符。 要确定文件名的长度,请使用 FileNameLength 成员。

以下示例调用了 FSCTL_READ_USN_JOURNAL,并读取了操作返回的更改日志记录缓冲区。

#include <Windows.h>
#include <WinIoCtl.h>
#include <stdio.h>

#define BUF_LEN 4096

void main()
{
   HANDLE hVol;
   CHAR Buffer[BUF_LEN];

   USN_JOURNAL_DATA JournalData;
   READ_USN_JOURNAL_DATA ReadData = {0, 0xFFFFFFFF, FALSE, 0, 0};
   PUSN_RECORD UsnRecord;  

   DWORD dwBytes;
   DWORD dwRetBytes;
   int I;

   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;
   }

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

   ReadData.UsnJournalID = JournalData.UsnJournalID;

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

   for(I=0; I<=10; I++)
   {
      memset( Buffer, 0, 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("File name: %.*S\n", 
                  UsnRecord->FileNameLength/2, 
                  UsnRecord->FileName );
         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_RECORD_V2USN_RECORD_V3 结构指定的任何记录的大小(以字节为单位)最多为 ((MaxComponentLength - 1) * Width) + Size,其中 MaxComponentLength 是记录文件名的最大长度(以字符为单位)。 宽度是宽字符的大小,而 Size 是结构的大小。

要获取最大长度,请调用 GetVolumeInformation 函数,并检查 lpMaximumComponentLength 参数指向的值。 从 MaxComponentLength 中减去 1,以考虑 USN_RECORDUSN_RECORD_V3 的定义包含一个文件名字符这一事实。

在 C 编程语言中,可能的最大记录大小如下:

(MaxComponentLength - 1) * sizeof(WCHAR) + sizeof(USN_RECORD)