BOOL EnumerateMft(HANDLE hcj, USN usnLow, USN usnHigh)
DWORD cb;
BYTE Buffer[sizeof(DWORDLONG) + 16384]; // read in 16KB chunks
// Enumerate MFT for files with 'Last USN'
// between usnLow and usnHigh
MFT_ENUM_DATA med;
med.StartFileReferenceNumber = 0;
med.LowUsn = usnLow;
med.HighUsn = usnHigh;
while(DeviceIoControl(hcj, FSCTL_ENUM_USN_DATA, &med, sizeof(med),
&Buffer, sizeof(Buffer), &cb, NULL)) {
USN_RECORD *pRecord = (USN_RECORD *) &Buffer[sizeof(USN)];
while ((PBYTE) pRecord (pData + cb)) {
// Examine record - this is not actually a journal record
// pRecord->FileReferenceNumber will tell us what FRN we've
// retrieved, and pRecord->Usn is its 'Last USN'
// Valid members are
// pRecord->RecordLength
// pRecord->FileReferenceNumber
// pRecord->ParentFileReferenceNumber
// pRecord->Usn
// pRecord->FileAttributes
// pRecord->FileNameOffset
// pRecord->FileNameLength
// Move to next record
pRecord = (PUSN_RECORD) (((PBYTE) pRecord) + pRecord->RecordLength);
}
// The next call uses the FRN in the first 8 bytes
// of the output buffer
med.StartFileReferenceNumber = * ((DWORDLONG *) Buffer);
}
return(GetLastError() == ERROR_HANDLE_EOF);
}
Figure 4 Journal Events
Your service runs
|
||
USN
|
File
|
Reason
|
128
|
File1
|
created
|
256
|
File2
|
created
|
384
|
File3
|
created
|
512
|
File4
|
created
|
Your application shuts down (NextUSNis 640)
|
||
USN
|
File
|
Reason
|
640
|
File1
|
deleted
|
768
|
File2
|
changed
|
896
|
File3
|
changed
|
1024
|
File5
|
created
|
System purges records
|
||
USN
|
File |
Reason
|
1152
|
File3
|
changed
|
1280
|
File4
|
changed
|
Your application runs again (FirstUSNis 1152)
|
Figure 5 Directory to FRN
BOOL FRNFromPath(LPCTSTR pszPath, DWORDLONG *pFRN) {
HANDLE hdir = CreateFile(pszPath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (hdir == INVALID_HANDLE_VALUE)
return(FALSE);
BY_HANDLE_FILE_INFORMATION fi;
GetFileInformationByHandle(hdir, &fi);
CloseHandle(hdir);
// Fill in the FRN
*pFRN = (((DWORDLONG) fi.nFileIndexHigh) < 32) | fi.nFileIndexLow;
return(TRUE);
}
Figure 6 FRN to Full Path
// Assume that you have a CString class for string manipulation
// Your implementation of the 'directory database' must expose
// a function that returns the stored information for a
// directory's FRN.
// For this sample, assume the prototype below represents your
// implementation of this function.
// Parameters:
// frn - the FRN of a directory you wish to query
// pParentFRN - will be filled in with the directory's parent FRN
// name - will be filled in with the directory's name (such as 'system32')
BOOL InfoFromFRN(DWORDLONG frn, DWORDLONG* pParentFRN, CString &name);
// PathFromFRN - Convert FRN of a path to a full path
// frn - the FRN of the path
// frnRoot - the FRN on the root directory
// pathname - full path returned
BOOL PathFromFRN(DWORDLONG frn, DWORDLONG frnRoot, CString &pathname) {
DWORDLONG frnTemp;
// Get the directory's name
InfoFromFRN(frn, &frnTemp, path name);
// If the caller passed us the FRN of the root
// directory, we are already finished - The call to
// InfoFromFRN should have filled in the path name using
// the format - D:\
if (frn == frnRoot)
return(TRUE);
// Loop through all its parents
while (TRUE) {
CString nameTemp;
DWORDLONG frnTemp;
InfoFromFRN(frn, &frnTemp, nameTemp);
// Prepend its parent's name
pathname = nameTemp + __T("\\") + pathname;
// If this was the root, we're done
if (frn == frnRoot)
break;
// Move to its parent
frn = frnTemp;
}
return(TRUE);
}
Figure 7 Maintaining a Directory Database
// Your directory database must support the following functions
// AddRecord(DWORDLONG frn, DWORDLONG parentfrn, CString name);
// ChangeRecord(DWORDLONG frn, DWORDLONG newparentfrn, CString newname);
// DeleteRecord(DWORDLONG frn);
// This code should be inside your function that processes
// all the journal's 'close' records.
// This code assumes you are using the following variables:
// PUSN_RECORD pRecord - a pointer to the current record
// LPCTSTR name - a zero-terminated pointer to the name derived from
// pRecord->FileNameOffset and pRecord->FileNameLength
// When we see the 'close' reason, we can update our database
// with any new, deleted, or renamed files or directories.
if(pRecord->Reason & USN_REASON_CLOSE) {
DWORDLONG frn = pRecord->FileReferenceNumber;
DWORDLONG parentfrn = pRecord->ParentFileReferenceNumber;
if ((pRecord->Reason & USN_REASON_FILE_CREATE) != 0)
AddRecord(frn, parentfrn, name);
if ((pRecord->Reason & USN_REASON_RENAME_NEW_NAME) != 0)
ChangeRecord(frn, parentfrn, name);
if ((pRecord->Reason & USN_REASON_FILE_DELETE) != 0)
DeleteRecord(frn, parentfrn, name);
}
Figure 8 Adding to a Named Stream
// Get a handle to the 'C' volume
HANDLE hcj = CreateFile("\\\\.\\C:", GENERIC_READ | GENERIC_WRITE
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
// Create private stream on some file
HANDLE hstream = CreateFile("C:\\somefile.txt:SecretInfo",
GENERIC_READ | GENERIC_WRITE
0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTES_NORMAL, NULL);
// Tell the journal that we're creating a private stream that
// no one else should care about
DWORD cbRead;
MARK_HANDLE_INFO mhi;
mhi.UsnSourceInfo = USN_SOURCE_AUXILIARY_DATA; // adding private stream
mhi.VolumeHandle = hcj;
mhi.HandleInfo = 0;
DeviceIoControl(hstream, FSCTL_MARK_HANDLE, &mhi, sizeof(mhi),
NULL, 0, &cbRead, NULL);
// At this point, your application can write to the stream.
// Journal entries will be created, but the SourceInfo member
// of the records will include USN_SOURCE_AUXILIARY_DATA.
// Any application that is monitoring the journal can use this
// information to decide if it cares that the file has changed.
CloseHandle(hcj);
CloseHandle(hstream);
Figure 10 CJTest
CJTest.cpp
/******************************************************************************
Module name: CJTest.cpp
Written by: Jeffrey Cooperstein & Jeffrey Richter
******************************************************************************/
#include "stdafx.h"
#include "Resource.H"
#include "ChangeJrnl.h"
#include "PathDB.h"
///////////////////////////////////////////////////////////////////////////////
// This is the file where we will store our directory database. It will be
// in the root directory of the volume we are working with
#define DBSTORAGEFILE TEXT("\\CJTestPathDBFile.dat")
#define HIGHPART(x) ((DWORD) ((x) >> 32))
#define LOPART(x) ((DWORD) (x))
// Global variables
struct APPGLOBALS {
APPGLOBALS() {
m_LastFileReferenceNumber = 0;
m_LastSourceInfo = 0;
}
CChangeJrnl m_cj; // Change Journal
CPathDB m_db; // Directory Database
// Members that help format display output
DWORDLONG m_LastFileReferenceNumber;
DWORD m_LastSourceInfo;
// Flag that indicates that we've processed all records
BOOL m_fUpToDate;
};
APPGLOBALS g_Globals;
// Prototypes
void ProcessAvailableRecords(HWND hwnd, BOOL fShowRecords);
void InitialzeForMonitoring(HWND hwnd);
///////////////////////////////////////////////////////////////////////////////
// Adds a string to an edit control
void AddText(HWND hwnd, LPCTSTR pszFormat, ...) {
va_list argList;
va_start(argList, pszFormat);
TCHAR sz[20 * 1024];
_vstprintf(sz, pszFormat, argList);
va_end(argList);
// If the edit control is longer than 20000 characters,
// chop off the first fifty lines.
while (GetWindowTextLength(hwnd) > 20000) {
Edit_SetSel(hwnd, 0, Edit_LineIndex(hwnd, 50));
Edit_ReplaceSel(hwnd, TEXT("));
}
int nLen = GetWindowTextLength(hwnd);
Edit_SetSel(hwnd, nLen, nLen);
Edit_ReplaceSel(hwnd, sz);
int nLenNew = GetWindowTextLength(hwnd);
Edit_SetSel(hwnd, nLenNew, nLenNew);
Edit_Scroll(hwnd, 1, 0);
}
///////////////////////////////////////////////////////////////////////////////
BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) {
// Have the results window use a fixed-pitch font
SetWindowFont(GetDlgItem(hwnd, IDC_RESULTS),
GetStockFont(ANSI_FIXED_FONT), FALSE);
// The app will monitor the journal that is on the current drive
TCHAR szCurrentDirectory[MAX_PATH];
GetCurrentDirectory(MAX_PATH, szCurrentDirectory);
// Initialize the CChangeJrnl class with the current drive letter and tell
// it to allocate a buffer of 10000 bytes to read journal records
if (!g_Globals.m_cj.Init(szCurrentDirectory[0], 10000)) {
MessageBox(hwnd, TEXT("CChangeJrnl Initialization Error"),
NULL, MB_OK);
EndDialog(hwnd, IDCANCEL);
return(FALSE);
}
SetFocus(GetDlgItem(hwnd, IDC_RESULTS));
AddText(GetDlgItem(hwnd, IDC_RESULTS), TEXT("Monitoring %c:\\\r\n"),
szCurrentDirectory[0]);
TCHAR cDriveLetter;
DWORDLONG CurUsnJournalID;
USN CurUsn;
// Try to load cached directory database from file
BOOL fOk = g_Globals.m_db.StreamFromFile(DBSTORAGEFILE, &cDriveLetter,
&CurUsnJournalID, &CurUsn);
// If we were able to read cached data, and the data came from the correct
// drive, we can pick up processing the journal where we left off.
if (fOk && (cDriveLetter == szCurrentDirectory[0])) {
g_Globals.m_fUpToDate = TRUE;
// Start processing records where we left off
g_Globals.m_cj.SeekToUsn(CurUsn, 0xFFFFFFFF, FALSE, CurUsnJournalID);
// Process all available records (but don't output them to screen since
// this could be very time-consuming).
// The ProcessAvailableRecords function will handle scenarios where the
// journal is not active or the journal ID has changed since we cached
// the data to disk.
ProcessAvailableRecords(hwnd, FALSE);
} else {
// Make sure the dialog is visible because the next function will bring
// up a modal dialog
ShowWindow(hwnd, SW_SHOW);
// There's no cached data for the directory database. We need to make
// sure the journal is in the correct state, and populate the directory
// database.
InitialzeForMonitoring(hwnd);
}
return(FALSE);
}
///////////////////////////////////////////////////////////////////////////////
BOOL Dlg_OnSize(HWND hwnd, UINT state, int cx, int cy) {
// Size the output edit control to fill the entire dialog
int n = LOWORD(GetDialogBaseUnits());
HWND hwndCtl = GetDlgItem(hwnd, IDC_RESULTS);
SetWindowPos(hwndCtl, NULL, n, n, cx - n - n, cy - n - n, SWP_NOZORDER);
return(FALSE);
}
///////////////////////////////////////////////////////////////////////////////
void DumpJrnlStats(HWND hwnd) {
// Dump the information about the volume's journal
HWND h = GetDlgItem(hwnd, IDC_RESULTS);
USN_JOURNAL_DATA ujd;
if (!g_Globals.m_cj.Query(&ujd)) {
AddText(h,
TEXT("*** Error while querying volume for journal information ***"));
return;
}
// Convert journal ID to time string
TCHAR szDateTime[1024];
if (!CChangeJrnl::GetDateTimeFormatFromFileTime(ujd.UsnJournalID,
szDateTime, sizeof(szDateTime) / sizeof(szDateTime[0]))) {
lstrcpy(szDateTime, TEXT("ERROR"));
}
AddText(h, TEXT("**************************************\r\n"));
AddText(h, TEXT("UsnJournalID time = %s\r\n"), szDateTime);
AddText(h, TEXT("UsnJournalID = 0x%016I64X\r\n"), ujd.UsnJournalID);
AddText(h, TEXT("FirstUsn = 0x%016I64X\r\n"), ujd.FirstUsn);
AddText(h, TEXT("NextUsn = 0x%016I64X\r\n"), ujd.NextUsn);
AddText(h, TEXT("LowestValidUsn = 0x%016I64X\r\n"), ujd.LowestValidUsn);
AddText(h, TEXT("MaxUsn = 0x%016I64X\r\n"), ujd.MaxUsn);
AddText(h, TEXT("MaximumSize = 0x%016I64X\r\n"), ujd.MaximumSize);
AddText(h, TEXT("AllocationDelta = 0x%016I64X\r\n"), ujd.AllocationDelta);
AddText(h, TEXT("**************************************\r\n"));
}
///////////////////////////////////////////////////////////////////////////////
void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) {
switch (id) {
case IDCANCEL:
// If we've kept up to date with the journal, save the directory
// database out to disk.
if (g_Globals.m_fUpToDate) {
g_Globals.m_db.StreamToFile(DBSTORAGEFILE, g_Globals.m_cj);
}
EndDialog(hwnd, id);
break;
case ID_JRNLINFO:
DumpJrnlStats(hwnd);
break;
case ID_DELETEJRNL:
if (IDYES == MessageBox(hwnd,
TEXT("Are you sure you want to delete the current journal?\r\n")
TEXT("(It will be automatically recreated by the application)"),
TEXT("Delete Change Journal"), MB_YESNO)) {
// Delete the journal - Don't wait for it to return. When we try to
// read more records, we'll see that the journal is gone. At that
// point, the InitializeForMonitoring function will recreate it
USN_JOURNAL_DATA ujd;
g_Globals.m_cj.Query(&ujd);
g_Globals.m_cj.Delete(ujd.UsnJournalID, USN_DELETE_FLAG_DELETE);
}
break;
}
}
///////////////////////////////////////////////////////////////////////////////
LRESULT Dlg_OnChangeJournalChanged(HWND hwnd, WPARAM wParam, LPARAM lParam) {
// We get this notification if there are new records to process, or possibly
// if the journal has been deleted. The ProcessAvailableRecords function
// will handle both cases.
ProcessAvailableRecords(hwnd, TRUE);
return(0);
}
///////////////////////////////////////////////////////////////////////////////
INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);
chHANDLE_DLGMSG(hwnd, WM_SIZE, Dlg_OnSize);
chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand);
case WM_CHANGEJOURNALCHANGED:
Dlg_OnChangeJournalChanged(hwnd, wParam, lParam);
break;
}
return(FALSE);
}
///////////////////////////////////////////////////////////////////////////////
int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, LPTSTR pszCmdLine, int) {
DialogBox(hinstExe, MAKEINTRESOURCE(IDD_CJTEST), NULL, Dlg_Proc);
return(0);
}
///////////////////////////////////////////////////////////////////////////////
void ProcessAvailableRecords(HWND hwnd, BOOL fShowRecords /* = TRUE*/) {
// Process all available journal records from the current location to the
// end of the journal. Before this function is called, at least one call has
// to be made to the SeekToUsn function. If this function completes without
// error, it sets up a notification that waits for more data to become
// available.
HWND hwndOut = GetDlgItem(hwnd, IDC_RESULTS);
SetWindowRedraw(hwndOut, FALSE);
PUSN_RECORD pRecord;
// Use EnumNext to loop through available records
while (pRecord = g_Globals.m_cj.EnumNext()) {
// Create a zero-terminated copy of the file name
WCHAR szFile[MAX_PATH];
LPWSTR pszFileName = (LPWSTR)
((PBYTE) pRecord + pRecord->FileNameOffset);
int cFileName = pRecord->FileNameLength / sizeof(WCHAR);
wcsncpy(szFile, pszFileName, cFileName);
szFile[cFileName] = 0;
// If this is a close record for a directory, we may need to adjust
// our directory database
if ( 0 != (pRecord->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
&& 0 != (pRecord->Reason & USN_REASON_CLOSE)) {
// Process newly created directories
if (0 != (pRecord->Reason & USN_REASON_FILE_CREATE)) {
g_Globals.m_db.Add(pRecord->FileReferenceNumber, szFile,
pRecord->ParentFileReferenceNumber);
}
// Process renamed directories
if (0 != (pRecord->Reason & USN_REASON_RENAME_NEW_NAME)) {
g_Globals.m_db.Change(pRecord->FileReferenceNumber, szFile,
pRecord->ParentFileReferenceNumber);
}
// Process deleted directories
if (0 != (pRecord->Reason & USN_REASON_FILE_DELETE)) {
g_Globals.m_db.Delete(pRecord->FileReferenceNumber);
}
}
// Dump the record to screen if requested
if (fShowRecords) {
CString szPath;
TCHAR szFullName[MAX_PATH];
// Get the full path for this record from the directory database
if (g_Globals.m_db.Get(pRecord->ParentFileReferenceNumber, szPath)) {
_stprintf(szFullName, TEXT("%s\\%s"), (LPCTSTR) szPath,
(LPCTSTR) szFile);
} else {
_stprintf(szFullName,
TEXT("%s can not find path for objects parent ID 0x%016I64X"),
szFile, pRecord->ParentFileReferenceNumber);
}
// If this record is dealing with a different file than the last
// record we displayed, print its full path.
if (g_Globals.m_LastFileReferenceNumber !=
pRecord->FileReferenceNumber) {
g_Globals.m_LastSourceInfo = -1;
AddText(hwndOut, TEXT("%s\r\n"), (LPCTSTR) szFullName);
}
// Store the FRN of the current record so we know if we need to
// reprint its full path on the next record
g_Globals.m_LastFileReferenceNumber = pRecord->FileReferenceNumber;
// If the source information has changed since the last record we
// displayed,
// print the new source information
if (g_Globals.m_LastSourceInfo != pRecord->SourceInfo) {
AddText(hwndOut, TEXT(" SourceInfo = %i\r\n"),
pRecord->SourceInfo);
g_Globals.m_LastSourceInfo = pRecord->SourceInfo;
}
// Show the reason codes
TCHAR szReason[1024];
if (!CChangeJrnl::GetReasonString(pRecord->Reason, szReason,
sizeof(szReason) / sizeof(szReason[0]))) {
lstrcpy(szReason, TEXT("ERROR"));
}
AddText(hwndOut, TEXT(" %s"), szReason);
// If the record has a 'rename' flag set, append the file name
if (0 != (pRecord->Reason &
(USN_REASON_RENAME_OLD_NAME | USN_REASON_RENAME_NEW_NAME))) {
AddText(hwndOut, TEXT(" (%s)"), (LPCTSTR) szFullName);
}
AddText(hwndOut, TEXT("\r\n"));
}
}
// EnumNext will return NULL with the 'last error' set to NO_ERROR if
// we reached the end of available journal records without problems
BOOL fOk = (S_OK == GetLastError());
SetWindowRedraw(hwndOut, TRUE);
InvalidateRect(hwndOut, NULL, FALSE);
if (fOk) {
// We successfully processed all new records
// Wait for more data. Specify a delay of 500 milliseconds after new data
// is available so that we don't pound the journal with read requests
// every time a new record is added
g_Globals.m_cj.NotifyMoreData(hwnd, 500);
} else {
// There was a problem. The journal could have been deleted, the id could
// have changed, or records may have been purged before we got to them.
// No matter what it was, we have to invalidate cached data and start
// from scratch
InitialzeForMonitoring(hwnd);
}
}
///////////////////////////////////////////////////////////////////////////////
void InitialzeForMonitoring(HWND hwnd) {
// This function ensures that the journal on the volume is active, and it
// will also populate the directory database.
HWND hwndOut = GetDlgItem(hwnd, IDC_RESULTS);
BOOL fOk = TRUE;
USN_JOURNAL_DATA ujd;
// Try to query for current journal information
while (fOk && !g_Globals.m_cj.Query(&ujd)) {
switch(GetLastError()) {
case ERROR_JOURNAL_DELETE_IN_PROGRESS:
// The system is deleting a journal. We need to wait for it to finish
// before trying to query it again.
AddText(hwndOut, TEXT("Waiting for Delete to finish\r\n"));
InvalidateRect(hwndOut, NULL, FALSE);
UpdateWindow(hwndOut);
g_Globals.m_cj.Delete(0, USN_DELETE_FLAG_NOTIFY);
AddText(hwndOut, TEXT("Delete finished\r\n"));
break;
case ERROR_JOURNAL_NOT_ACTIVE:
// The journal is not active on the volume. We need to create it and
// then query for its information again
AddText(hwndOut, TEXT("Journal not active - creating new one\r\n"));
g_Globals.m_cj.Create(0x800000, 0x100000);
break;
default:
// Some other error happened while querying the journal information.
// There is nothing we can do from here
AddText(hwndOut, TEXT("Unable to query journal\r\n"));
AddText(hwndOut, TEXT("MONITORING STOPPED!\r\n"));
g_Globals.m_fUpToDate = FALSE;
fOk = FALSE;
break;
}
}
if (!fOk) {
// We were not able to query the volume for journal information
return;
}
// Start processing records at the start of the journal
g_Globals.m_cj.SeekToUsn(ujd.FirstUsn, 0xFFFFFFFF, FALSE, ujd.UsnJournalID);
// Make sure the user is willing to perform a rescan of all the directories
// to populate the directory database
int id = MessageBox(hwnd,
TEXT("Full rescan needed to build directory database\r\n")
TEXT("Possible Reasons:\r\n1) No cached data\r\n")
TEXT("2) Journal ID has changed\r\n")
TEXT("3) Some records were purged before we processed them\r\n")
TEXT("\r\nContinue?"), TEXT("Perform Full Rescan"), MB_YESNO);
if (id == IDYES) {
// Use 'method 2' to populate the database
// g_Globals.m_db.PopulateMethod1(g_Globals.m_cj);
g_Globals.m_db.PopulateMethod2(g_Globals.m_cj);
g_Globals.m_fUpToDate = TRUE;
// Process all available records (but don't output them to screen since
// this could be very time consuming).
ProcessAvailableRecords(hwnd, FALSE);
} else {
// The user did not want to spend the time to populate the directory
// database. There's nothing else we can do.
AddText(hwndOut, TEXT("No Longer monitoring the journal\r\n"));
g_Globals.m_fUpToDate = FALSE;
}
}
//////////////////////////////// End of File //////////////////////////////////
PathDB.cpp
/******************************************************************************
Module name: PathDB.cpp
Written by: Jeffrey Cooperstein & Jeffrey Richter
******************************************************************************/
#include "stdafx.h"
#include "PathDB.h"
///////////////////////////////////////////////////////////////////////////////
BOOL CPathDB::Add(DWORDLONG Index, LPCTSTR szName, DWORDLONG ParentIndex) {
DWORD dwOffset;
if (mapIndexToOffset.Lookup(Index, dwOffset)) {
return(FALSE); // Index already in database
}
rgszNames.Add(szName);
rgParentIndex.Add(ParentIndex);
mapIndexToOffset[Index] = rgszNames.GetSize() - 1;
return(TRUE);
}
///////////////////////////////////////////////////////////////////////////////
BOOL CPathDB::Empty() {
rgszNames.RemoveAll();
rgParentIndex.RemoveAll();
mapIndexToOffset.RemoveAll();
return(TRUE);
}
///////////////////////////////////////////////////////////////////////////////
BOOL CPathDB::Get(DWORDLONG Index, CString &sz) {
sz = TEXT(");
do {
DWORD dwOffset;
if (!mapIndexToOffset.Lookup(Index, dwOffset)) {
return(FALSE); // did not find all the parents
}
sz = rgszNames[dwOffset] +
((sz.GetLength() != 0) ? TEXT("\\") : TEXT(")) + sz;
Index = rgParentIndex[dwOffset];
} while (Index != 0);
return(TRUE);
}
///////////////////////////////////////////////////////////////////////////////
BOOL CPathDB::Change(DWORDLONG Index, LPCTSTR szName, DWORDLONG ParentIndex) {
DWORD dwOffset;
if (!mapIndexToOffset.Lookup(Index, dwOffset)) {
return(FALSE); // not found
}
rgszNames[dwOffset] = szName;
rgParentIndex[dwOffset] = ParentIndex;
return(TRUE);
}
///////////////////////////////////////////////////////////////////////////////
BOOL CPathDB::Delete(DWORDLONG Index) {
DWORD dwOffset;
if (!mapIndexToOffset.Lookup(Index, dwOffset)) {
return(FALSE); // not found
}
// NOTE: We lose memory in the rgszNames and rgParentIndex array
// since we cannot shift the rest of the offsets down
// We could be more efficient here.
// At the least, let's free the memory used by the string.
rgszNames[dwOffset].Empty();
mapIndexToOffset.RemoveKey(Index);
return(TRUE);
}
///////////////////////////////////////////////////////////////////////////////
BOOL CPathDB::StreamToFile(LPCTSTR pszFile, CChangeJrnl &cj) {
// Save current state information to a file
TCHAR cDriveLetter = cj.GetDriveLetter();
DWORDLONG UsnJournalID = cj.GetCurUsnJrnlID();
USN usn = cj.GetCurUsn();
HANDLE hFile = CreateFile(pszFile, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hFile) {
return(FALSE);
}
BOOL fOk = TRUE;
POSITION pos = mapIndexToOffset.GetStartPosition();
DWORDLONG Index;
DWORD dwOffset;
DWORD dwWritten;
fOk = fOk && WriteFile(hFile, &cDriveLetter, sizeof(cDriveLetter),
&dwWritten, NULL);
fOk = fOk && WriteFile(hFile, &UsnJournalID, sizeof(UsnJournalID),
&dwWritten, NULL);
fOk = fOk && WriteFile(hFile, &usn, sizeof(usn), &dwWritten, NULL);
while ((pos != 0) && fOk) {
mapIndexToOffset.GetNextAssoc(pos, Index, dwOffset);
fOk = fOk && WriteFile(hFile, &Index, sizeof(Index),
&dwWritten, NULL);
fOk = fOk && WriteFile(hFile, &rgParentIndex[dwOffset],
sizeof(rgParentIndex[dwOffset]), &dwWritten, NULL);
DWORD dwLen = rgszNames[dwOffset].GetLength();
fOk = fOk && WriteFile(hFile, &dwLen, sizeof(dwLen), &dwWritten, NULL);
fOk = fOk && WriteFile(hFile, (LPCTSTR)rgszNames[dwOffset],
dwLen * sizeof(rgszNames[dwOffset][0]), &dwWritten, NULL);
}
Index = -1;
fOk = fOk && WriteFile(hFile, &Index, sizeof(Index), &dwWritten, NULL);
CloseHandle(hFile);
return(fOk);
}
///////////////////////////////////////////////////////////////////////////////
BOOL CPathDB::StreamFromFile(LPCTSTR pszFile, TCHAR *pcDriveLetter,
DWORDLONG *pUsnJournalID, USN *pUsn) {
// Load state information from a file
HANDLE hFile = CreateFile(pszFile, GENERIC_READ, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (INVALID_HANDLE_VALUE == hFile) {
return(FALSE);
}
BOOL fOk = TRUE;
POSITION pos = mapIndexToOffset.GetStartPosition();
DWORD dwWritten;
fOk = fOk && ReadFile(hFile, pcDriveLetter, sizeof(*pcDriveLetter),
&dwWritten, NULL);
fOk = fOk && ReadFile(hFile, pUsnJournalID, sizeof(*pUsnJournalID),
&dwWritten, NULL);
fOk = fOk && ReadFile(hFile, pUsn, sizeof(*pUsn), &dwWritten, NULL);
while (fOk) {
DWORDLONG Index;
DWORDLONG ParentIndex;
DWORD dwLen;
fOk = fOk && ReadFile(hFile, &Index, sizeof(Index), &dwWritten, NULL);
if (-1 == Index) {
break;
}
fOk = fOk && ReadFile(hFile, &ParentIndex, sizeof(ParentIndex),
&dwWritten, NULL);
fOk = fOk && ReadFile(hFile, &dwLen, sizeof(dwLen),
&dwWritten, NULL);
LPTSTR psz = new TCHAR[dwLen+1];
psz[dwLen] = 0;
fOk = fOk && ReadFile(hFile, psz, dwLen * sizeof(TCHAR),
&dwWritten, NULL);
Add(Index, psz, ParentIndex);
delete[] psz;
}
CloseHandle(hFile);
return(fOk);
}
///////////////////////////////////////////////////////////////////////////////
void CPathDB::PopulateMethod1(CChangeJrnl &cj) {
Empty();
// Fill the database by walking all directories on the volume. The function
// RecursePath uses FindFirstFile/FindNextFile to walk the volume, and
// GetFileInformationByHandle to get the file reference numbers of every
// directory
TCHAR szCurrentPath[_MAX_PATH];
wsprintf(szCurrentPath, TEXT("%c:"), cj.GetDriveLetter());
RecursePath(szCurrentPath, szCurrentPath, 0, 0);
}
///////////////////////////////////////////////////////////////////////////////
void CPathDB::PopulateMethod2(CChangeJrnl &cj) {
Empty();
// Enumerate the MFT for all entries. Store the file reference numbers of
// any directories in the database.
USN_JOURNAL_DATA ujd;
cj.Query(&ujd);
// Get the FRN of the root directory
// This had BETTER work, or we can't do anything
TCHAR szRoot[_MAX_PATH];
wsprintf(szRoot, TEXT("%c:\\"), cj.GetDriveLetter());
HANDLE hDir = CreateFile(szRoot, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
BY_HANDLE_FILE_INFORMATION fi;
GetFileInformationByHandle(hDir, &fi);
CloseHandle(hDir);
DWORDLONG IndexRoot = (((DWORDLONG) fi.nFileIndexHigh) < 32)
| fi.nFileIndexLow;
wsprintf(szRoot, TEXT("%c:"), cj.GetDriveLetter());
Add(IndexRoot, szRoot, 0);
MFT_ENUM_DATA med;
med.StartFileReferenceNumber = 0;
med.LowUsn = 0;
med.HighUsn = ujd.NextUsn;
// Process MFT in 64K chunks
BYTE pData[sizeof(DWORDLONG) + 0x10000];
DWORDLONG fnLast = 0;
DWORD cb;
while (DeviceIoControl(cj, FSCTL_ENUM_USN_DATA, &med, sizeof(med),
pData, sizeof(pData), &cb, NULL) != FALSE) {
PUSN_RECORD pRecord = (PUSN_RECORD) &pData[sizeof(USN)];
while ((PBYTE) pRecord < (pData + cb)) {
if (0 != (pRecord->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
CString sz((LPCWSTR)
((PBYTE) pRecord + pRecord->FileNameOffset),
pRecord->FileNameLength / sizeof(WCHAR));
Add(pRecord->FileReferenceNumber, sz,
pRecord->ParentFileReferenceNumber);
}
pRecord = (PUSN_RECORD) ((PBYTE) pRecord + pRecord->RecordLength);
}
med.StartFileReferenceNumber = * (DWORDLONG *) pData;
}
}
///////////////////////////////////////////////////////////////////////////////
void CPathDB::RecursePath(LPCTSTR pszPath, LPCTSTR pszFile,
DWORDLONG ParentIndex, DWORD dwVolumeSerialNumber) {
// Enumerate the directores in the current path and store the file reference
// numbers in the database. Call this function recursively to enumerate into
// the subdirectores.
CString szPath2(pszPath);
szPath2 += TEXT("\\");
HANDLE hDir = CreateFile(szPath2, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
// NOTE: If we cannot get a handle to this, there is no need to recurse into
// it (even though it might be possible). Example - we couldn't open it, but
// FindFirst(...) succeeds for some reason. We don't care about any extra
// information. We can get by FindFirst inside this directory since we won't
// have the FileIndex of this directory. Therefore, any objects under this
// directory will not be able to have their names fully resolved (ie: when
// we walk up the parent IDs for an object below this one, we won't be able
// to find this ID)
if (INVALID_HANDLE_VALUE == hDir) {
return;
}
BY_HANDLE_FILE_INFORMATION fi;
GetFileInformationByHandle(hDir, &fi);
CloseHandle(hDir);
if (0 == dwVolumeSerialNumber) {
dwVolumeSerialNumber = fi.dwVolumeSerialNumber;
}
if (fi.nNumberOfLinks != 1) {
ASSERT(FALSE);
}
if (dwVolumeSerialNumber != fi.dwVolumeSerialNumber) {
// We've wandered onto another volume. This should only
// happen if we recursed into a reparse point, but we check
// for that below!
ASSERT(FALSE);
return;
}
LARGE_INTEGER index;
index.LowPart = fi.nFileIndexLow;
index.HighPart = fi.nFileIndexHigh;
if (!Add(index.QuadPart, pszFile, ParentIndex)) {
// Duplicate - The FRN for this directory was already
// in our database. This shouldn't happen, but if it
// does, there's no need to recurse into subdirectories
return;
}
TCHAR szTempPath[_MAX_PATH];
lstrcpy(szTempPath, pszPath);
lstrcat(szTempPath, TEXT("\\*.*"));
WIN32_FIND_DATA fd;
HANDLE hSearch = FindFirstFile(szTempPath, &fd);
if (INVALID_HANDLE_VALUE == hSearch) {
// Something went wrong trying to enumerate the files and directories
// in the current directory
return;
}
do {
TCHAR szTempRecursePath[_MAX_PATH];
lstrcpy(szTempRecursePath, pszPath);
lstrcat(szTempRecursePath, TEXT("\\"));
lstrcat(szTempRecursePath, fd.cFileName);
if ( (0 != (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
&& (0 != lstrcmp(fd.cFileName, TEXT(".")))
&& (0 != lstrcmp(fd.cFileName, TEXT("..")))
&& (0 == (fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) ) {
RecursePath(szTempRecursePath, fd.cFileName, index.QuadPart,
dwVolumeSerialNumber);
}
} while (FindNextFile(hSearch, &fd) != FALSE);
FindClose(hSearch);
}
///////////////////////////////// End Of File /////////////////////////////////