I'm reading the NTFS change Journal to monitor any file changes on a drive but the USN_RECORD's returned only include the filename of the associated file and not the full path.
To get the full path I'm using the FileReferenceNumber (Also in the USN_RECORD) with OpenFileById API to get a handle to the associated file, followed by GetFinalPathNameByHandleA to get the full path.
This works great as long that file still exists, but if the file has been deleted (For example if I'm processing a USN_RECORD for a file deletion) then obviously OpenFileById fails and so I'm unable to get the full path.
Whilst the USN_RECORD does include a ParentFileReferenceNumber member (Which I could presumably open using OpenFileById) that should allow me to get the name of the parent directory, the same problem then exists, i.e. what if the parent directory had been deleted?
Also even if the parent directory hasn't been deleted how do I then walk up to the next directory up (i.e. the parent of the parent) and so on until I reach to volume root?
Here is my example code:
#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);
}
And example output:
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