Walking a Buffer of Change Journal Records
The control codes that return change journal records, FSCTL_READ_USN_JOURNAL and FSCTL_ENUM_USN_DATA, return similar data in the output buffer. Both return a USN followed by 0 (zero) or more change journal records, each in a USN_RECORD structure. The following list identifies ways to get change journal records:
Both of these operations return only the subset of change journal records that meet the specified criteria. The USN returned as the first item in the output buffer is the USN of the next record number to be retrieved. Use this value to continue reading records from the end boundary forward. The FileName member of USN_RECORD contains the name of the file to which the record in question applies. The file name varies in length, so USN_RECORD is a variable length structure. Its first member, RecordLength, is the length of the structure (including the file name), in bytes. When you work with the FileName member of USN_RECORD, do not assume that the file name contains a trailing '\0' delimiter. To determine the length of the file name, use the FileNameLength member.
Walking a Buffer of Change Journal Records Program Example
The following example calls FSCTL_READ_USN_JOURNAL and walks the buffer of change journal records that the operation returns. To compile an application that uses this function, you may need to define the _WIN32_WINNT macro as 0x0500 (Refer to Using the Windows Headers).
Create a new Win32 console application project and give a suitable project name.

Add the source file and give a suitable name.

Add the following source code.
#include <Windows.h>
#include <WinIoCtl.h>
#include <stdio.h>
#define BUF_LEN 4096
// Format the Win32 system error code to string
void ErrorMessage(DWORD dwCode);
int wmain(int argc, WCHAR **argv)
{
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( L\\\\.\\C:,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
if(hVol == INVALID_HANDLE_VALUE)
{
wprintf(LCreateFile() failed\n);
ErrorMessage(GetLastError());
return 1;
}
wprintf(LCreateFile() is OK!\n);
if(!DeviceIoControl(hVol,
FSCTL_QUERY_USN_JOURNAL,
NULL,
0,
&JournalData,
sizeof(JournalData),
&dwBytes,
NULL))
{
wprintf(LDeviceIoControl() - Query journal failed\n);
ErrorMessage(GetLastError());
return 1;
}
ReadData.UsnJournalID = JournalData.UsnJournalID;
wprintf(LJournal ID: %I64x\n, JournalData.UsnJournalID );
wprintf(LFirstUsn: %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))
{
wprintf(LDeviceIoControl()- Read journal failed\n);
ErrorMessage(GetLastError());
return 1;
}
wprintf(LDeviceIoControl() is OK!\n);
dwRetBytes = dwBytes - sizeof(USN);
// Find the first record
UsnRecord = (PUSN_RECORD)(((PUCHAR)Buffer) + sizeof(USN));
wprintf(L****************************************\n);
// This loop could go on for a long time, given the current buffer size.
while(dwRetBytes > 0)
{
wprintf(LUSN: %I64x\n, UsnRecord->Usn );
wprintf(LFile name: %.*S\n, UsnRecord->FileNameLength/2,UsnRecord->FileName );
wprintf(LReason: %x\n, UsnRecord->Reason );
wprintf(L\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;
}
if(CloseHandle(hVol) != 0)
wprintf(LCloseHandle() is OK!\n);
else
{
wprintf(LCloseHandle() failed\n);
ErrorMessage(GetLastError());
}
return 0;
}
void ErrorMessage(DWORD dwCode)
{
// get the error code...
DWORD dwErrCode = dwCode;
DWORD dwNumChar;
LPWSTR szErrString = NULL; // will be allocated and filled by FormatMessage
dwNumChar = FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM, // use windows internal message table
0, // 0 since source is internal message table
dwErrCode, // this is the error code number
0, // auto-determine language to use
(LPWSTR)&szErrString, // the message
0, // min size for buffer
0 ); // since getting message from system tables
if(dwNumChar == 0)
wprintf(LFormatMessage() failed, error %u\n, GetLastError());
//else
// wprintf(LFormatMessage() should be fine!\n);
wprintf(LError code %u:\n %s\n, dwErrCode, szErrString) ;
// This buffer used by FormatMessage()
if(LocalFree(szErrString) != NULL)
wprintf(LFailed to free up the buffer, error %u\n, GetLastError());
//else
// wprintf(LBuffer has been freed\n);
}
Build and run the project. The following screenshot is an output sample and there is no active journal in the system where the program was run.

The size in bytes of any record specified by a USN_RECORD structure is at most ((MaxComponentLength - 1) * Width) + Size where MaxComponentLength is the maximum length in characters of the record file name. The width is the size of a wide character, and the Size is the size of the structure. To obtain the maximum length, call the GetVolumeInformation() function and examine the value pointed to by the lpMaximumComponentLength parameter. Subtract one from MaxComponentLength to account for the fact that the definition of USN_RECORD includes one character of the file name. In the C programming language, the largest possible record size is the following:
MaxComponentLength*sizeof(WCHAR) + sizeof(USN_RECORD) - sizeof(WCHAR)