|
|
/*++
Copyright (c) 1998 Microsoft Corporation
Module Name:
extract.cpp
Abstract:
SIS Groveler USN journal reading functions
Authors:
Cedric Krumbein, 1998
Environment:
User Mode
Revision History:
--*/
#include "all.hxx"
// NT Update Sequence Number (USN) journal definitions
#define USN_ADD_REASONS ( 0U \
| USN_REASON_DATA_OVERWRITE \ | USN_REASON_DATA_EXTEND \ | USN_REASON_DATA_TRUNCATION \ | USN_REASON_NAMED_DATA_OVERWRITE \ | USN_REASON_NAMED_DATA_EXTEND \ | USN_REASON_NAMED_DATA_TRUNCATION \ | USN_REASON_FILE_CREATE \ | USN_REASON_FILE_DELETE \ /* | USN_REASON_PROPERTY_CHANGE */ \ /* | USN_REASON_SECURITY_CHANGE */ \ /* | USN_REASON_RENAME_OLD_NAME */ \ /* | USN_REASON_RENAME_NEW_NAME */ \ | USN_REASON_INDEXABLE_CHANGE \ | USN_REASON_BASIC_INFO_CHANGE \ /* | USN_REASON_HARD_LINK_CHANGE */ \ | USN_REASON_COMPRESSION_CHANGE \ | USN_REASON_ENCRYPTION_CHANGE \ | USN_REASON_OBJECT_ID_CHANGE \ /* | USN_REASON_REPARSE_POINT_CHANGE */ \ | USN_REASON_CLOSE \ )
/*****************************************************************************/
// set_usn_log_size() sets the maximum size of this volume's USN journal.
GrovelStatus Groveler::set_usn_log_size( IN DWORDLONG usn_log_size) { CREATE_USN_JOURNAL_DATA createUSN;
DWORD transferCount;
ASSERT(volumeHandle != NULL);
createUSN.MaximumSize = usn_log_size; createUSN.AllocationDelta = USN_PAGE_SIZE;
// Set the maximum size of the USN journal.
if (!DeviceIoControl( volumeHandle, FSCTL_CREATE_USN_JOURNAL, &createUSN, sizeof(CREATE_USN_JOURNAL_DATA), NULL, 0, &transferCount, NULL)) { DPRINTF((_T("%s: error setting USN journal size: %lu\n"), driveName, GetLastError())); return Grovel_error; }
TPRINTF((_T("%s: set USN journal size to %I64u\n"), driveName, usn_log_size));
return Grovel_ok; }
/*****************************************************************************/
// get_usn_log_size() returns the current size of this volume's USN journal.
GrovelStatus Groveler::get_usn_log_info( OUT USN_JOURNAL_DATA *usnJournalData) { DWORD transferCount, lastError;
BOOL success;
ASSERT(volumeHandle != NULL);
// Query the USN journal settings.
success = DeviceIoControl( volumeHandle, FSCTL_QUERY_USN_JOURNAL, NULL, 0, usnJournalData, sizeof(USN_JOURNAL_DATA), &transferCount, NULL);
if (!success) lastError = GetLastError(); else if (transferCount != sizeof(USN_JOURNAL_DATA)) { lastError = 0; success = FALSE; }
if (!success) { DPRINTF((_T("%s: error querying USN journal settings: %lu\n"), driveName, lastError)); return Grovel_error; }
TPRINTF((_T("%s: USN journal: ID=0x%I64x size=0x%I64x\n"), driveName, usnJournalData->UsnJournalID, usnJournalData->MaximumSize));
return Grovel_ok; }
/*****************************************************************************/
// extract_log() reads this volume's USN journal.
// If the lastUSN parameter equals zero or doesn't exist, the USN journal
// is read from the beginning. Otherwise, the lastUSN paramerer indicates
// the most recent USN entry read during the last call of extract_log().
// If the lastUSN entry is still available in the USN journal, read the
// journal beginning at the entry following the lastUSN entry. If the
// lastUSN entry is no longer available, it indicates that the USN
// journal has wrapped: read all entries from the journal.
enum USNException { USN_ERROR };
enum DatabaseException { DATABASE_ERROR };
GrovelStatus Groveler::extract_log2( OUT DWORD *num_entries_extracted, OUT DWORDLONG *num_bytes_extracted, OUT DWORDLONG *num_bytes_skipped, OUT DWORD *num_files_enqueued, OUT DWORD *num_files_dequeued) { struct FileEntry { DWORDLONG fileID, parentID, timeStamp; DWORD attributes, reason; } *fileEntry = NULL;
struct DirEntry { DWORDLONG dirID; } *dirEntry = NULL;
Table *fileTable = NULL, *dirTable = NULL;
BYTE usnBuffer[USN_PAGE_SIZE + sizeof(DWORDLONG)];
READ_USN_JOURNAL_DATA readUSN;
USN_RECORD *usnRecord;
SGNativeTableEntry tableEntry;
SGNativeQueueEntry queueEntry;
SGNativeStackEntry stackEntry;
SGNativeListEntry listEntry;
TCHAR listValue[17];
DWORDLONG usn_log_size, numBytesExtracted = 0, numBytesSkipped = 0, startUSN, firstUSN, nextUSN, thisUSN;
DWORD numEntriesExtracted = 0, numTableDeletions = 0, numQueueDeletions = 0 , numQueueAdditions = 0, numActions = 0, offset, bytesRead, lastError;
LONG num;
BOOL firstEntry = TRUE, deleteEntry, addEntry, success;
GrovelStatus status;
ASSERT(volumeHandle != NULL); ASSERT(sgDatabase != NULL);
// If we don't know the previous USN, we can't extract.
if (lastUSN == UNINITIALIZED_USN) { status = Grovel_overrun; goto Abort; } ASSERT(usnID != UNINITIALIZED_USN);
fileTable = new Table; ASSERT(fileTable != NULL);
if (inScan) { dirTable = new Table; ASSERT(dirTable != NULL); }
// Set up to read the volume's USN journal.
startUSN = lastUSN == UNINITIALIZED_USN ? 0 : lastUSN;
readUSN.ReturnOnlyOnClose = 1; readUSN.Timeout = 0; readUSN.BytesToWaitFor = 0; readUSN.ReasonMask = ~0U; readUSN.UsnJournalID = usnID;
// Read the USN journal one page at a time.
try { while (TRUE) { readUSN.StartUsn = startUSN;
if (!DeviceIoControl( volumeHandle, FSCTL_READ_USN_JOURNAL, &readUSN, sizeof(READ_USN_JOURNAL_DATA), usnBuffer, USN_PAGE_SIZE + sizeof(DWORDLONG), &bytesRead, NULL)) {
lastError = GetLastError();
// NTRAID#65198-2000/03/10-nealch Handle USN id change (treat as overwrite w/ unknown no. of bytes skipped)
// If the journal overflowed, report by how much.
if (lastError == ERROR_KEY_DELETED || lastError == ERROR_JOURNAL_ENTRY_DELETED) { USN_JOURNAL_DATA usnJournalData;
if (get_usn_log_info(&usnJournalData) != Grovel_ok) return Grovel_error;
// The USN journal will not wrap in our lifetimes so we don't really need
// to handle USN Journal wrapping.
ASSERT((DWORDLONG) usnJournalData.FirstUsn > lastUSN);
numBytesSkipped = (DWORDLONG) usnJournalData.FirstUsn - lastUSN; goto Overrun; }
throw USN_ERROR; }
lastError = 0;
if (bytesRead < sizeof(DWORDLONG)) throw USN_ERROR;
nextUSN = *(DWORDLONG *)usnBuffer; if (nextUSN < startUSN) throw USN_ERROR;
if (nextUSN == startUSN) { if (bytesRead != sizeof(DWORDLONG)) throw USN_ERROR; break; }
bytesRead -= sizeof(DWORDLONG); offset = 0; numBytesExtracted += bytesRead;
// Process each USN journal entry.
while (bytesRead > 0) { if (bytesRead < sizeof(USN_RECORD)) throw USN_ERROR;
usnRecord = (USN_RECORD *)&usnBuffer[offset + sizeof(DWORDLONG)];
if (usnRecord->RecordLength < offsetof(USN_RECORD, FileName) + usnRecord->FileNameLength || usnRecord->RecordLength > bytesRead) throw USN_ERROR;
thisUSN = (DWORDLONG)usnRecord->Usn; if (thisUSN < startUSN + offset) throw USN_ERROR;
// If this is the first entry, check if it is the expected
// USN. If it isn't, the USN journal has wrapped.
if (firstEntry) if (startUSN == 0) numBytesSkipped = thisUSN; else if (thisUSN <= startUSN + usnRecord->RecordLength) numBytesSkipped = 0; else numBytesSkipped = thisUSN - startUSN - usnRecord->RecordLength;
// Skip the first entry if the starting address is greater than zero.
// After skipping the first entry, examine each USN entry as follows:
//
// - If the entry is a directory, and a volume scan is underway,
// add the directory's ID to the directory table.
//
// - If the entry is a file, add it to the file table. Include
// its ID and its parent directory's ID, its most recent time
// stamp and attributes, and its accumulated reason bits.
if (firstEntry && startUSN > 0) numBytesExtracted -= usnRecord->RecordLength;
else { if (usnRecord-> FileReferenceNumber == 0 || usnRecord->ParentFileReferenceNumber == 0) throw USN_ERROR;
// The entry is a directory.
if ((usnRecord->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
if (dirTable != NULL) { dirEntry = (DirEntry *)dirTable->Get( (const VOID *)&usnRecord->FileReferenceNumber, sizeof(DWORDLONG));
if (dirEntry != NULL) { ASSERT(dirEntry->dirID == usnRecord->FileReferenceNumber); } else { dirEntry = new DirEntry; ASSERT(dirEntry != NULL); dirEntry->dirID = usnRecord->FileReferenceNumber; success = dirTable->Put((VOID *)dirEntry, sizeof(DWORDLONG)); ASSERT(success); } } }
// The entry is a file. If USN_SOURCE_DATA_MANAGEMENT is set, assume this entry was created by
// the groveler during a merge operation.
else if ((usnRecord->SourceInfo & USN_SOURCE_DATA_MANAGEMENT) == 0) {
fileEntry = (FileEntry *)fileTable->Get( (const VOID *)&usnRecord->FileReferenceNumber, sizeof(DWORDLONG));
if (fileEntry != NULL) { ASSERT(fileEntry->fileID == usnRecord->FileReferenceNumber); } else { fileEntry = new FileEntry; ASSERT(fileEntry != NULL); fileEntry->fileID = usnRecord->FileReferenceNumber; fileEntry->reason = 0; success = fileTable->Put((VOID *)fileEntry, sizeof(DWORDLONG)); ASSERT(success); }
fileEntry->parentID = usnRecord->ParentFileReferenceNumber; fileEntry->timeStamp = (DWORDLONG)usnRecord->TimeStamp.QuadPart; fileEntry->attributes = usnRecord->FileAttributes;
if ((usnRecord->Reason & USN_REASON_FILE_DELETE) != 0) fileEntry->reason = USN_REASON_FILE_DELETE; else fileEntry->reason |= usnRecord->Reason; } else { TPRINTF((_T("%s: USN_SOURCE_DATA_MANAGEMENT set on file 0x%016I64x\n"), driveName, usnRecord->FileReferenceNumber)); }
if (numEntriesExtracted++ == 0) firstUSN = thisUSN; }
lastUSN = thisUSN; offset += usnRecord->RecordLength; bytesRead -= usnRecord->RecordLength; firstEntry = FALSE; }
startUSN = nextUSN; } }
// If an error occured while reading the USN journal, return an error status.
catch (USNException usnException) { ASSERT(usnException == USN_ERROR);
if (fileTable != NULL) { delete fileTable; fileTable = NULL; }
if (dirTable != NULL) { delete dirTable; dirTable = NULL; }
lastUSN = UNINITIALIZED_USN;
DPRINTF((_T("%s: error reading USN journal: %lu\n"), driveName, lastError)); return Grovel_error; }
// We've finished reading the USN journal, so update the database. Process
// each entry in the file table, and group the updates into transactions.
try { while ((fileEntry = (FileEntry *)fileTable->GetFirst()) != NULL) { ASSERT(fileEntry->fileID != 0);
// If the file is currently open in the grovel process, skip this entry.
if (inUseFileID1 != NULL && fileEntry->fileID == *inUseFileID1 || inUseFileID2 != NULL && fileEntry->fileID == *inUseFileID2) {
DPRINTF((_T("%s: extract_log/grovel collision on file 0x%016I64x\n"), driveName, fileEntry->fileID));
} else {
// Delete the file from the queue and the table...
//
// - if the file's most recent reason bits in the USN journal
// indicate it was deleted,
//
// - if the file or the file's most recent parent directory is disallowed,
//
// - or if the file has disallowed attributes.
//
// Otherwise, update or add the file to the queue...
//
// - if the file's reason bits indicate it was changed,
//
// - or if the file isn't present in the table.
if (fileEntry->reason == USN_REASON_FILE_DELETE || !IsAllowedID(fileEntry->fileID) || !IsAllowedID(fileEntry->parentID) || (fileEntry->attributes & disallowedAttributes) != 0) { deleteEntry = TRUE; addEntry = FALSE; } else { deleteEntry = FALSE; if ((fileEntry->reason & USN_ADD_REASONS) != 0) addEntry = TRUE; else { tableEntry.fileID = fileEntry->fileID; num = sgDatabase->TableGetFirstByFileID(&tableEntry); if (num < 0) throw DATABASE_ERROR; ASSERT(num == 0 || num == 1); addEntry = num == 0; } }
if (deleteEntry || addEntry) { if (numActions == 0) { if (sgDatabase->BeginTransaction() < 0) throw DATABASE_ERROR; numActions = 1; }
queueEntry.reason = 0;
num = sgDatabase->TableDeleteByFileID(fileEntry->fileID); if (num < 0) throw DATABASE_ERROR; if (num > 0) { ASSERT(num == 1); numTableDeletions++; numActions++; }
queueEntry.fileID = fileEntry->fileID; queueEntry.fileName = NULL; num = sgDatabase->QueueGetFirstByFileID(&queueEntry); if (num < 0) throw DATABASE_ERROR;
if (num > 0) { ASSERT(num == 1); num = sgDatabase->QueueDeleteByFileID(fileEntry->fileID); if (num < 0) throw DATABASE_ERROR; ASSERT(num == 1); numQueueDeletions++; numActions++; }
if (addEntry) { queueEntry.fileID = fileEntry->fileID; queueEntry.parentID = 0; queueEntry.reason |= fileEntry->reason; queueEntry.readyTime = fileEntry->timeStamp + minFileAge; queueEntry.retryTime = 0; queueEntry.fileName = NULL;
num = sgDatabase->QueuePut(&queueEntry); if (num < 0) throw DATABASE_ERROR; ASSERT(num == 1);
#ifdef DEBUG_USN_REASON
if (numQueueAdditions == 0) { DPRINTF((_T("--> __REASON__ _____FILE_ID______\n"))); } DPRINTF((_T(" 0x%08lx 0x%016I64x\n"), fileEntry->reason, fileEntry->fileID)); #endif
numQueueAdditions++; numActions++; }
if (numActions >= MAX_ACTIONS_PER_TRANSACTION) { if (!sgDatabase->CommitTransaction()) throw DATABASE_ERROR; TPRINTF((_T("%s: committing %lu actions to \"%s\"\n"), driveName, numActions, databaseName)); numActions = 0; } } }
delete fileEntry; fileEntry = NULL; }
delete fileTable; fileTable = NULL;
// Process each entry in the directory table. If the directory hasn't already
// been scanned or isn't on the list to be scanned, add it to the list.
if (dirTable != NULL) { ASSERT(inScan);
while ((dirEntry = (DirEntry *)dirTable->GetFirst()) != NULL) { ASSERT(dirEntry->dirID != 0);
stackEntry.fileID = dirEntry->dirID; num = sgDatabase->StackGetFirstByFileID(&stackEntry); if (num < 0) throw DATABASE_ERROR;
if (num == 0) { if (numActions == 0) { if (sgDatabase->BeginTransaction() < 0) throw DATABASE_ERROR; numActions = 1; }
num = sgDatabase->StackPut(dirEntry->dirID, FALSE); if (num < 0) throw DATABASE_ERROR; ASSERT(num == 1); numActions++;
if (numActions >= MAX_ACTIONS_PER_TRANSACTION) { if (!sgDatabase->CommitTransaction()) throw DATABASE_ERROR; TPRINTF((_T("%s: committing %lu actions to \"%s\"\n"), driveName, numActions, databaseName)); numActions = 0; } }
delete dirEntry; dirEntry = NULL; }
delete dirTable; dirTable = NULL; }
// Update the last USN number in the database, then commit the changes. If we're
// doing a volume scan, don't update the lastUSN until the scan is complete.
if (!inScan) { _stprintf(listValue, _T("%016I64x"), lastUSN); listEntry.name = LAST_USN_NAME; listEntry.value = listValue; num = sgDatabase->ListWrite(&listEntry); if (num <= 0) throw DATABASE_ERROR; }
if (numActions > 0) { if (!sgDatabase->CommitTransaction()) throw DATABASE_ERROR; TPRINTF((_T("%s: committing %lu actions to \"%s\"\n"), driveName, numActions, databaseName)); numActions = 0; } }
// If a database error occured, return an error status.
catch (DatabaseException databaseException) {
ASSERT(databaseException == DATABASE_ERROR);
if (numActions > 0) { sgDatabase->AbortTransaction(); numActions = 0; }
if (fileTable != NULL) { delete fileTable; fileTable = NULL; }
if (dirTable != NULL) { delete dirTable; dirTable = NULL; }
return Grovel_error; }
Overrun:
status = numBytesSkipped == 0 ? Grovel_ok : Grovel_overrun;
Abort:
// Return the performance statistics.
if (num_entries_extracted != NULL) *num_entries_extracted = numEntriesExtracted; if (num_bytes_extracted != NULL) *num_bytes_extracted = numBytesExtracted; if (num_bytes_skipped != NULL) *num_bytes_skipped = numBytesSkipped; if (num_files_enqueued != NULL) *num_files_enqueued = numQueueAdditions; if (num_files_dequeued != NULL) *num_files_dequeued = numQueueDeletions;
#if DBG
if (numEntriesExtracted > 0 && firstUSN < lastUSN) { TRACE_PRINTF(TC_extract, 2, (_T("%s: USN 0x%I64x-%I64x\n"), driveName, firstUSN, lastUSN)); } else { TRACE_PRINTF(TC_extract, 2, (_T("%s: USN 0x%I64x\n"), driveName, lastUSN)); } TRACE_PRINTF(TC_extract, 2, (_T(" NumEntriesExtracted=%lu NumBytesExtracted=%I64u NumBytesSkipped=%I64u\n"), numEntriesExtracted, numBytesExtracted, numBytesSkipped)); TRACE_PRINTF(TC_extract, 2, (_T(" NumTableDeletions=%lu NumQueueDeletions=%lu NumQueueAdditions=%lu\n"), numTableDeletions, numQueueDeletions, numQueueAdditions)); #endif
return status;
}
GrovelStatus Groveler::extract_log( OUT DWORD *num_entries_extracted, OUT DWORDLONG *num_bytes_extracted, OUT DWORDLONG *num_bytes_skipped, OUT DWORD *num_files_enqueued, OUT DWORD *num_files_dequeued) { GrovelStatus status;
#ifdef _CRTDBG
_CrtMemState s1, s2, sdiff;
_CrtMemCheckpoint(&s1); #endif
status = extract_log2( num_entries_extracted, num_bytes_extracted, num_bytes_skipped, num_files_enqueued, num_files_dequeued);
#ifdef _CRTDBG
_CrtMemCheckpoint(&s2); if (_CrtMemDifference(&sdiff, &s1, &s2)) _CrtMemDumpStatistics(&sdiff); #endif
return status; }
|