Durchlaufen eines Puffers mit Änderungsjournal-Datensätzen

Die Steuercodes, die Änderungsjournal-Datensätze mit Sequenznummern (USN) zurückgeben, FSCTL_READ_USN_JOURNAL und FSCTL_ENUM_USN_DATA, geben ähnliche Daten im Ausgabepuffer zurück. Beide geben eine USN zurück, gefolgt von null oder mehr Datensätzen des Änderungsjournals, jeweils in einer USN_RECORD_V2- oder USN_RECORD_V3-Struktur.

Das Zielvolume für USN-Operationen muss ReFS oder NTFS 3.0 oder höher sein. Um die NTFS-Version eines Volumes abzurufen, öffnen Sie eine Eingabeaufforderung mit Administratorzugriffsrechten und führen Sie den folgenden Befehl aus

FSUtil.exe FSInfo NTFSInfo X**:**

Wobei X der Laufwerksbuchstabe des Volumes ist.

Die folgende Liste zeigt, wie Sie Datensätze des Änderungsjournals abrufen können:

  • Verwenden Sie FSCTL_ENUM_USN_DATA, um eine Auflistung (Enumeration) aller Änderungsjournal-Datensätze zwischen zwei USNs zu erhalten.
  • Verwenden Sie FSCTL_READ_USN_JOURNAL, um selektiver vorzugehen, z. B. um bestimmte Gründe für Änderungen auszuwählen oder um zurückzukehren, wenn eine Datei geschlossen wird.

Hinweis

Diese beiden Vorgänge geben nur die Untermenge der Datensätze des Änderungsjournals zurück, die den angegebenen Kriterien entsprechen.

 

Die USN, die als erstes Element im Ausgabepuffer zurückgegeben wird, ist die USN des nächsten abzurufenden Datensatzes. Verwenden Sie diesen Wert, um Datensätze vom Ende der Begrenzung an weiter zu lesen.

Das FileName-Mitglied von USN_RECORD_V2 oder USN_RECORD_V3 enthält den Namen der Datei, auf die sich der betreffende Datensatz bezieht. Der Dateiname variiert in seiner Länge, daher sind USN_RECORD_V2 und USN_RECORD_V3 Strukturen mit variabler Länge. Ihr erstes Mitglied, RecordLength, ist die Länge der Struktur (einschließlich des Dateinamens) in Bytes.

Wenn Sie mit dem Mitglied FileName der Strukturen USN_RECORD_V2 und USN_RECORD_V3 arbeiten, gehen Sie nicht davon aus, dass der Dateiname ein nachgestelltes „\0“-Trennzeichen enthält. Um die Länge des Dateinamens zu bestimmen, verwenden Sie das Mitglied FileNameLength.

Das folgende Beispiel ruft FSCTL_READ_USN_JOURNAL auf und durchläuft den Puffer der Datensätze des Änderungsjournals, den der Vorgang zurückgibt.

#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);

}

Die Größe eines Datensatzes, der durch eine USN_RECORD_V2- oder USN_RECORD_V3-Struktur angegeben ist, beträgt maximal ((MaxComponentLength - 1) * Width) + Size in Bytes, wobei MaxComponentLength die maximale Länge des Dateinamens des Datensatzes in Zeichen ist. Die Breite ist die Größe eines breiten Zeichens, und die Size ist die Größe der Struktur.

Um die maximale Länge abzurufen, rufen Sie die Funktion GetVolumeInformation auf und untersuchen den Wert, auf den der Parameter lpMaximumComponentLength zeigt. Ziehen Sie eins von MaxComponentLength ab, um der Tatsache Rechnung zu tragen, dass die Definition von USN_RECORD und USN_RECORD_V3 ein Zeichen des Dateinamens enthält.

In der Programmiersprache C ist die größtmögliche Größe eines Datensatzes die folgende:

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