windows-nt/Source/XPSP1/NT/base/fs/sis/groveler/extract.cpp
2020-09-26 16:20:57 +08:00

730 lines
22 KiB
C++

/*++
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;
}