/*++ Copyright (c) 1998-1999 Microsoft Corporation Module Name: groveler.cpp Abstract: SIS Groveler file groveling functions Authors: Cedric Krumbein, 1998 Environment: User Mode Revision History: --*/ #include "all.hxx" #define CLEAR_FILE(FILE) ( \ (FILE).entry.fileID = 0, \ (FILE).entry.fileSize = 0, \ (FILE).entry.signature = 0, \ (FILE).entry.attributes = 0, \ (FILE).entry.csIndex = nullCSIndex, \ (FILE).entry.createTime = 0, \ (FILE).entry.writeTime = 0, \ (FILE).parentID = 0, \ (FILE).retryTime = 0, \ (FILE).startTime = 0, \ (FILE).stopTime = 0, \ (FILE).readSynch.Internal = 0, \ (FILE).readSynch.InternalHigh = 0, \ (FILE).readSynch.Offset = 0, \ (FILE).readSynch.OffsetHigh = 0, \ (FILE).fileName[0] = _T('\0') ) #define CLEAR_OVERLAPPED(OVERLAPPED) ( \ (OVERLAPPED).Internal = 0, \ (OVERLAPPED).InternalHigh = 0, \ (OVERLAPPED).Offset = 0, \ (OVERLAPPED).OffsetHigh = 0 ) // Is CS index set? static const CSID nullCSIndex = { 0, 0, 0, _T('\0'), _T('\0'), _T('\0'), _T('\0'), _T('\0'), _T('\0'), _T('\0'), _T('\0') }; #define HasCSIndex(CSID) \ (memcmp(&(CSID), &nullCSIndex, sizeof(CSID)) != 0) #define SameCSIndex(CSID1, CSID2) \ (memcmp(&(CSID1), &(CSID2), sizeof(CSID)) == 0) // Exceptions enum TerminalException { INITIALIZE_ERROR, DATABASE_ERROR, MEMORY_ERROR, TERMINATE }; enum TargetException { TARGET_INVALID, TARGET_ERROR }; enum MatchException { MATCH_INVALID, MATCH_ERROR, MATCH_STALE }; /*****************************************************************************/ /************************** Miscellaneous functions **************************/ /*****************************************************************************/ // NewHandler() is installed by _set_new_handler() to throw an // exception when the system can't allocate any more memory. static INT __cdecl NewHandler(size_t size) { throw MEMORY_ERROR; return 0; // Dummy return } /*****************************************************************************/ // FileIDCompare() is used by qsort() and bsearch() // to sort or look up a matching file ID. static INT __cdecl FileIDCompare( const VOID *id1, const VOID *id2) { DWORDLONG fileID1 = *(DWORDLONG *)id1, fileID2 = *(DWORDLONG *)id2; return fileID1 < fileID2 ? -1 : fileID1 > fileID2 ? +1 : 0; } /*****************************************************************************/ // qsStringCompare() is used by qsort() to sort an array of character strings. static INT __cdecl qsStringCompare( const VOID *str1, const VOID *str2) { return _tcsicmp(*(TCHAR **)str1, *(TCHAR **)str2); } /*****************************************************************************/ // bsStringCompare() is used by bsearch() look up a matching character string. // It is assumed that str1 is the path name string we are searching for and // str2 is the excluded path name string in the excluded paths list. Note // that if the excluded path is \a\b, then we return a match on anything that // is in this directory or subdirectory, as well as an exact match. // E.g.: \a\b\c\d.foo & \a\b\foo will match, but \a\b.foo will not. static INT __cdecl bsStringCompare( const VOID *str1, const VOID *str2) { TCHAR *s1 = *(TCHAR **) str1; TCHAR *s2 = *(TCHAR **) str2; // str2 is the excluded name. Make sure we catch subdirectories under it, // but make sure we don't confuse \a\bx with \a\b size_t l = _tcslen(s2); INT r = _tcsnicmp(s1, s2, l); if (0 == r) if (_tcslen(s1) > l && _T('\\') != s1[l]) r = 1; return r; } /*****************************************************************************/ /********************** Groveler class private methods ***********************/ /*****************************************************************************/ // IsAllowedID() returns FALSE if the directory or file ID // is on the list of disallowed IDs, and TRUE otherwise. BOOL Groveler::IsAllowedID(DWORDLONG fileID) const { BOOL result; ASSERT(fileID != 0); if (numDisallowedIDs == 0) { ASSERT(disallowedIDs == NULL); result = TRUE; } else { ASSERT(disallowedIDs != NULL); result = bsearch( &fileID, disallowedIDs, numDisallowedIDs, sizeof(DWORDLONG), FileIDCompare) == NULL; } DPRINTF((L"IsAllowedID: %04I64x.%012I64x, (%s)\n", ((fileID >> 48) & 0xffff), (fileID & 0xffffffffffff), result ? L"yes" : L"no")); return result; } /*****************************************************************************/ // IsAllowedName() returns FALSE if the directory or file name // is on the list of disallowed names, and TRUE otherwise. BOOL Groveler::IsAllowedName(TCHAR *fileName) const { BOOL result; ASSERT(fileName != NULL); if (numDisallowedNames == 0) { ASSERT(disallowedNames == NULL); result = TRUE; } else { ASSERT(disallowedNames != NULL); result = (bsearch( &fileName, disallowedNames, numDisallowedNames, sizeof(TCHAR *), bsStringCompare) == NULL); } // // The name wasn't on the disallowed list, see if the GrovelAllPaths option // is set. If not then it must be in the RIS valid path, check for it. // if (result && !GrovelAllPaths && (RISPath != NULL)) { // // Do not do this cheeck if this is the root directory of the volume // if (wcscmp(fileName,L"\\") != 0) { result = (bsStringCompare(&fileName,&RISPath) == 0); } } DPRINTF((L"IsAllowedName: \"%s\", (%s)\n", fileName, result ? L"yes" : L"no")); return result; } /*****************************************************************************/ // WaitForEvent suspends the thread until the specified event is set. VOID Groveler::WaitForEvent(HANDLE event) { DWORD eventNum; BOOL success; ASSERT(event != NULL); eventNum = WaitForSingleObject(event, INFINITE); ASSERT_ERROR(eventNum == WAIT_OBJECT_0); success = ResetEvent(event); ASSERT_ERROR(success); } /*****************************************************************************/ // OpenFileByID() opens the file with the given volumeHandle and fileID. BOOL Groveler::OpenFileByID( FileData *file, BOOL writeEnable) { UNICODE_STRING fileIDString; OBJECT_ATTRIBUTES objectAttributes; IO_STATUS_BLOCK ioStatusBlock; NTSTATUS ntStatus; ASSERT(volumeHandle != NULL); ASSERT(file != NULL); ASSERT(file->entry.fileID != 0); ASSERT(file->handle == NULL); fileIDString.Length = sizeof(DWORDLONG); fileIDString.MaximumLength = sizeof(DWORDLONG); fileIDString.Buffer = (WCHAR *)&file->entry.fileID; objectAttributes.Length = sizeof(OBJECT_ATTRIBUTES); objectAttributes.RootDirectory = volumeHandle; objectAttributes.ObjectName = &fileIDString; objectAttributes.Attributes = OBJ_CASE_INSENSITIVE; objectAttributes.SecurityDescriptor = NULL; objectAttributes.SecurityQualityOfService = NULL; ntStatus = NtCreateFile( &file->handle, GENERIC_READ | (writeEnable ? GENERIC_WRITE : 0), &objectAttributes, &ioStatusBlock, NULL, 0, FILE_SHARE_READ | FILE_SHARE_DELETE | (writeEnable ? FILE_SHARE_WRITE : 0), FILE_OPEN, FILE_OPEN_BY_FILE_ID | FILE_OPEN_REPARSE_POINT | FILE_NO_INTERMEDIATE_BUFFERING, NULL, 0); if (ntStatus == STATUS_SUCCESS) { DWORD bytesReturned; MARK_HANDLE_INFO markHandleInfo = {USN_SOURCE_DATA_MANAGEMENT, volumeHandle, 0}; // Mark the handle so the usn entry for the merge operation (if completed) // can be detected and skipped. BOOL rc = DeviceIoControl( file->handle, FSCTL_MARK_HANDLE, &markHandleInfo, sizeof markHandleInfo, NULL, 0, &bytesReturned, NULL); if (!rc) { DPRINTF((_T("%s: FSCTL_MARK_HANDLE failed, %lu\n"), driveLetterName, GetLastError())); } #if DBG // Get the file name { HRESULT r; ASSERT(file->fileName[0] == _T('\0')); struct TFileName2 { ULONG nameLen; TCHAR name[MAX_PATH+1]; } tFileName[1]; ntStatus = NtQueryInformationFile( file->handle, &ioStatusBlock, tFileName, sizeof tFileName, FileNameInformation); if (ntStatus == STATUS_SUCCESS) { r = StringCbCopyN(file->fileName, sizeof(file->fileName), tFileName->name, tFileName->nameLen); ASSERT(r == S_OK); } else { r = StringCbCopy(file->fileName, sizeof(file->fileName), L""); ASSERT(r == S_OK); } } #endif return TRUE; } ASSERT(file->handle == NULL); SetLastError(RtlNtStatusToDosError(ntStatus)); return FALSE; } /*****************************************************************************/ // OpenFileByName() opens the file with the given fileName. BOOL Groveler::OpenFileByName( FileData *file, BOOL writeEnable, TCHAR *fileName) { UNICODE_STRING dosPathName, ntPathName; OBJECT_ATTRIBUTES objectAttributes; IO_STATUS_BLOCK ioStatusBlock; NTSTATUS ntStatus; ASSERT(file != NULL); ASSERT(file->handle == NULL); if (fileName == NULL) fileName = file->fileName; ASSERT(fileName[0] != _T('\0')); #ifdef _UNICODE dosPathName.Buffer = fileName; #else if (!RtlCreateUnicodeStringFromAsciiz(&dosPathName, fileName)) { ntStatus = STATUS_NO_MEMORY; goto Error; } #endif if (RtlDosPathNameToNtPathName_U(dosPathName.Buffer, &ntPathName, NULL, NULL)) { objectAttributes.Length = sizeof(OBJECT_ATTRIBUTES); objectAttributes.RootDirectory = NULL; objectAttributes.ObjectName = &ntPathName; objectAttributes.Attributes = OBJ_CASE_INSENSITIVE; objectAttributes.SecurityDescriptor = NULL; objectAttributes.SecurityQualityOfService = NULL; ntStatus = NtCreateFile( &file->handle, GENERIC_READ | (writeEnable ? GENERIC_WRITE : 0), &objectAttributes, &ioStatusBlock, NULL, 0, FILE_SHARE_READ | FILE_SHARE_DELETE | (writeEnable ? FILE_SHARE_WRITE : 0), FILE_OPEN, FILE_OPEN_REPARSE_POINT | FILE_NO_INTERMEDIATE_BUFFERING, NULL, 0); RtlFreeUnicodeString(&ntPathName); } else { ntStatus = STATUS_NO_MEMORY; } #ifndef _UNICODE RtlFreeUnicodeString(&dosPathName); #endif if (ntStatus == STATUS_SUCCESS) { DWORD bytesReturned; MARK_HANDLE_INFO markHandleInfo = {USN_SOURCE_DATA_MANAGEMENT, volumeHandle, 0}; // Mark the handle so the usn entry for the merge operation (if completed) // can be detected and skipped. BOOL rc = DeviceIoControl( file->handle, FSCTL_MARK_HANDLE, &markHandleInfo, sizeof markHandleInfo, NULL, 0, &bytesReturned, NULL); if (!rc) { DPRINTF((_T("%s: FSCTL_MARK_HANDLE failed, %lu\n"), driveLetterName, GetLastError())); } return TRUE; } ASSERT(file->handle == NULL); SetLastError(RtlNtStatusToDosError(ntStatus)); return FALSE; } /*****************************************************************************/ // IsFileMapped() checks if the file is mapped by another user. BOOL Groveler::IsFileMapped(FileData *file) { _SIS_LINK_FILES sisLinkFiles; DWORD transferCount; BOOL success; ASSERT(grovHandle != NULL); ASSERT(file->handle != NULL); sisLinkFiles.operation = SIS_LINK_FILES_OP_VERIFY_NO_MAP; sisLinkFiles.u.VerifyNoMap.file = file->handle; success = DeviceIoControl( grovHandle, FSCTL_SIS_LINK_FILES, (VOID *)&sisLinkFiles, sizeof(_SIS_LINK_FILES), NULL, 0, &transferCount, NULL); if (success) return FALSE; ASSERT(GetLastError() == ERROR_SHARING_VIOLATION); return TRUE; } /*****************************************************************************/ // SetOplock() sets an oplock on the open file. BOOL Groveler::SetOplock(FileData *file) { BOOL success; ASSERT(file != NULL); ASSERT(file->handle != NULL); ASSERT(file->oplock.Internal == 0); ASSERT(file->oplock.InternalHigh == 0); ASSERT(file->oplock.Offset == 0); ASSERT(file->oplock.OffsetHigh == 0); ASSERT(file->oplock.hEvent != NULL); ASSERT(IsReset(file->oplock.hEvent)); success = DeviceIoControl( file->handle, FSCTL_REQUEST_BATCH_OPLOCK, NULL, 0, NULL, 0, NULL, &file->oplock); if (success) { ASSERT(IsSet(file->oplock.hEvent)); success = ResetEvent(file->oplock.hEvent); ASSERT_ERROR(success); CLEAR_OVERLAPPED(file->oplock); SetLastError(0); return FALSE; } if (GetLastError() != ERROR_IO_PENDING) { ASSERT(IsReset(file->oplock.hEvent)); CLEAR_OVERLAPPED(file->oplock); return FALSE; } return TRUE; } /*****************************************************************************/ // CloseFile() closes the file if it is still open. If an oplock was // set on the file, it then waits for and resets the oplock break // event triggered by the closing of the file or by an outside access. VOID Groveler::CloseFile(FileData *file) { BOOL success; ASSERT(file != NULL); ASSERT(file->oplock.hEvent != NULL); if (file->handle == NULL) { ASSERT(file->oplock.Internal == 0); ASSERT(file->oplock.InternalHigh == 0); ASSERT(file->oplock.Offset == 0); ASSERT(file->oplock.OffsetHigh == 0); ASSERT(IsReset(file->oplock.hEvent)); } else { success = CloseHandle(file->handle); ASSERT_ERROR(success); file->handle = NULL; if (file->oplock.Internal != 0 || file->oplock.InternalHigh != 0 || file->oplock.Offset != 0 || file->oplock.OffsetHigh != 0) { WaitForEvent(file->oplock.hEvent); CLEAR_OVERLAPPED(file->oplock); } } } /*****************************************************************************/ // CreateDatabase() creates the database. Initialize it such that if // extract_log is called before scan_volume, it will return Grovel_overrun // without attempting any USN extraction. Also, the first time scan_volume // is called (with or without start_over), it will know to initialize // lastUSN and do a full volume scan. BOOL Groveler::CreateDatabase(void) { USN_JOURNAL_DATA usnJournalData; TFileName tempName; TCHAR listValue[17]; DWORDLONG rootID; SGNativeListEntry listEntry; LONG num; tempName.assign(driveName); tempName.append(_T("\\")); rootID = GetFileID(NULL,tempName.name); if (rootID == 0) { DPRINTF((_T("%s: CreateDatabase: can't get root directory ID\n"), driveLetterName)); goto Error; } if (get_usn_log_info(&usnJournalData) != Grovel_ok) { DWORD lastError = GetLastError(); if (lastError == ERROR_JOURNAL_NOT_ACTIVE) { DPRINTF((_T("%s: CreateDatabase: journal not active\n"), driveLetterName)); if ((set_usn_log_size(65536) != ERROR_SUCCESS) || get_usn_log_info(&usnJournalData) != ERROR_SUCCESS) { DPRINTF((_T("%s: CreateDatabase: can't initialize USN journal\n"), driveLetterName)); goto Error; } } else { DPRINTF((_T("%s: CreateDatabase: can't initialize last USN\n"), driveLetterName)); goto Error; } } lastUSN = usnJournalData.NextUsn; usnID = usnJournalData.UsnJournalID; sgDatabase->Close(); if (!sgDatabase->Create(databaseName)) { DPRINTF((_T("%s: CreateDatabase: can't create database \"%s\"\n"), driveLetterName, databaseName)); goto Error; } num = sgDatabase->StackPut(rootID, FALSE); if (num < 0) goto Error; ASSERT(num == 1); // Write UNINITIALIZED_USN into the database now, to be replaced when scan_volume // is complete. This will be a flag to indicate if the database contents are valid. (void)StringCbPrintf(listValue, sizeof(listValue), _T("%016I64x"), UNINITIALIZED_USN); listEntry.name = LAST_USN_NAME; listEntry.value = listValue; num = sgDatabase->ListWrite(&listEntry); if (num < 0) goto Error; ASSERT(num == 1); (void)StringCbPrintf(listValue, sizeof(listValue), _T("%016I64x"), usnID); listEntry.name = USN_ID_NAME; listEntry.value = listValue; num = sgDatabase->ListWrite(&listEntry); if (num < 0) goto Error; ASSERT(num == 1); return TRUE; Error: lastUSN = usnID = UNINITIALIZED_USN; return FALSE; } /*****************************************************************************/ #define MAX_ACTIONS 5 // DoTransaction() performs the specified operations // on the database within a single transaction. VOID Groveler::DoTransaction( DWORD numActions, DatabaseActionList *actionList) { DatabaseActionList *action; DWORD i; LONG num; ASSERT(sgDatabase != NULL); ASSERT(actionList != NULL); if (sgDatabase->BeginTransaction() < 0) throw DATABASE_ERROR; for (i = 0; i < numActions; i++) { action = &actionList[i]; switch(action->type) { case TABLE_PUT: ASSERT(action->u.tableEntry != NULL); num = sgDatabase->TablePut(action->u.tableEntry); ASSERT(num < 0 || num == 1); break; case TABLE_DELETE_BY_FILE_ID: ASSERT(action->u.fileID != 0); num = sgDatabase->TableDeleteByFileID(action->u.fileID); break; case QUEUE_PUT: ASSERT(action->u.queueEntry != NULL); num = sgDatabase->QueuePut(action->u.queueEntry); ASSERT(num < 0 || num == 1); if (num == 1) numFilesEnqueued++; break; case QUEUE_DELETE: ASSERT(action->u.queueIndex != 0); num = sgDatabase->QueueDelete(action->u.queueIndex); ASSERT(num <= 1); if (num == 1) numFilesDequeued++; #if DBG else DPRINTF((_T("DoTransaction: QUEUE_DELETE unsuccessful (%d)"), num)); #endif break; default: ASSERT_PRINTF(FALSE, (_T("type=%lu\n"), action->type)); } if (num < 0) { sgDatabase->AbortTransaction(); throw DATABASE_ERROR; } } if (!sgDatabase->CommitTransaction()) { sgDatabase->AbortTransaction(); throw DATABASE_ERROR; } } /*****************************************************************************/ // EnqueueCSIndex() deletes all entries with the specified CS index from the // table and enqueues them to be re-groveled, all within a single transaction. VOID Groveler::EnqueueCSIndex(CSID *csIndex) { SGNativeTableEntry tableEntry; SGNativeQueueEntry oldQueueEntry, newQueueEntry; DWORD count; LONG num; ASSERT(csIndex != NULL); ASSERT(HasCSIndex(*csIndex)); newQueueEntry.parentID = 0; newQueueEntry.reason = 0; newQueueEntry.readyTime = GetTime() + grovelInterval; newQueueEntry.retryTime = 0; newQueueEntry.fileName = NULL; oldQueueEntry.fileName = NULL; count = 0; if (sgDatabase->BeginTransaction() < 0) throw DATABASE_ERROR; tableEntry.csIndex = *csIndex; num = sgDatabase->TableGetFirstByCSIndex(&tableEntry); while (num > 0) { ASSERT(num == 1); count++; oldQueueEntry.fileID = tableEntry.fileID; num = sgDatabase->QueueGetFirstByFileID(&oldQueueEntry); if (num < 0) break; ASSERT(num == 1); if (num == 0) { newQueueEntry.fileID = tableEntry.fileID; num = sgDatabase->QueuePut(&newQueueEntry); if (num < 0) break; ASSERT(num == 1); numFilesEnqueued++; } num = sgDatabase->TableGetNext(&tableEntry); } if (num < 0) { sgDatabase->AbortTransaction(); throw DATABASE_ERROR; } num = sgDatabase->TableDeleteByCSIndex(csIndex); if (num < 0) { sgDatabase->AbortTransaction(); throw DATABASE_ERROR; } ASSERT(count == (DWORD)num); if (!sgDatabase->CommitTransaction()) { sgDatabase->AbortTransaction(); throw DATABASE_ERROR; } } /*****************************************************************************/ #define TARGET_OPLOCK_BREAK 0 #define TARGET_READ_DONE 1 #define GROVEL_START 2 #define NUM_EVENTS 3 // SigCheckPoint suspends the thread until the target file completes its read // operation. If the time allotment expires before the operation completes, // the grovelStart event is set to signal grovel() to awaken, and this method // won't return until grovel() sets the grovelStart event. If the file's // oplock breaks before this method returns, the file will be closed. VOID Groveler::SigCheckPoint( FileData *target, BOOL targetRead) { HANDLE events[NUM_EVENTS]; DWORD elapsedTime, timeOut, eventNum, eventTime; BOOL targetOplockBroke = FALSE, waitingForGrovelStart = FALSE, success; ASSERT(target != NULL); ASSERT(target->handle != NULL); ASSERT(target->oplock .hEvent != NULL); ASSERT(target->readSynch.hEvent != NULL); ASSERT(grovelStartEvent != NULL); ASSERT(grovelStopEvent != NULL); events[TARGET_OPLOCK_BREAK] = target->oplock .hEvent; events[TARGET_READ_DONE] = target->readSynch.hEvent; events[GROVEL_START] = grovelStartEvent; while (TRUE) { if (waitingForGrovelStart) timeOut = INFINITE; else if (timeAllotted == INFINITE) timeOut = targetRead ? INFINITE : 0; else { elapsedTime = GetTickCount() - startAllottedTime; if (timeAllotted > elapsedTime) timeOut = targetRead ? timeAllotted - elapsedTime : 0; else { waitingForGrovelStart = TRUE; timeOut = INFINITE; grovelStatus = Grovel_pending; ASSERT(IsReset(grovelStopEvent)); success = SetEvent(grovelStopEvent); ASSERT_ERROR(success); } } eventNum = WaitForMultipleObjects(NUM_EVENTS, events, FALSE, timeOut); eventTime = GetTickCount(); switch (eventNum) { case WAIT_OBJECT_0 + TARGET_OPLOCK_BREAK: ASSERT(!targetOplockBroke); targetOplockBroke = TRUE; success = ResetEvent(target->oplock.hEvent); ASSERT_ERROR(success); if (!targetRead) { CLEAR_OVERLAPPED(target->oplock); CloseFile(target); } DPRINTF((_T("%s: target file %s oplock broke during hash\n"), driveLetterName, target->fileName)); break; case WAIT_OBJECT_0 + TARGET_READ_DONE: ASSERT(targetRead); targetRead = FALSE; success = ResetEvent(target->readSynch.hEvent); ASSERT_ERROR(success); target->stopTime = eventTime; if (targetOplockBroke) { CLEAR_OVERLAPPED(target->oplock); CloseFile(target); } break; case WAIT_OBJECT_0 + GROVEL_START: ASSERT(waitingForGrovelStart); waitingForGrovelStart = FALSE; success = ResetEvent(grovelStartEvent); ASSERT_ERROR(success); break; case WAIT_TIMEOUT: ASSERT(!waitingForGrovelStart); if (!targetRead) { if (terminate) throw TERMINATE; if (targetOplockBroke) throw TARGET_ERROR; return; } waitingForGrovelStart = TRUE; grovelStatus = Grovel_pending; ASSERT(IsReset(grovelStopEvent)); success = SetEvent(grovelStopEvent); ASSERT_ERROR(success); break; default: ASSERT_PRINTF(FALSE, (_T("eventNum=%lu\n"), eventNum)); } } } #undef TARGET_OPLOCK_BREAK #undef TARGET_READ_DONE #undef GROVEL_START #undef NUM_EVENTS /*****************************************************************************/ #define TARGET_OPLOCK_BREAK 0 #define MATCH_OPLOCK_BREAK 1 #define TARGET_READ_DONE 2 #define MATCH_READ_DONE 3 #define GROVEL_START 4 #define NUM_EVENTS 5 // CmpCheckPoint suspends the thread until the target file, the // match file, or both complete their read operations. If the time // allotment expires before the operations complete, the grovelStart // event is set to signal grovel() to awaken, and this method won't // return until grovel() sets the grovelStart event. If either file's // oplock breaks before this method returns, the file will be closed. VOID Groveler::CmpCheckPoint( FileData *target, FileData *match, BOOL targetRead, BOOL matchRead) { HANDLE events[NUM_EVENTS]; DWORD elapsedTime, timeOut, eventNum, eventTime; BOOL targetOplockBroke = FALSE, matchOplockBroke = FALSE, waitingForGrovelStart = FALSE, success; ASSERT(target != NULL); ASSERT(match != NULL); ASSERT(target->handle != NULL); ASSERT(match ->handle != NULL); ASSERT(target->oplock .hEvent != NULL); ASSERT(match ->oplock .hEvent != NULL); ASSERT(target->readSynch.hEvent != NULL); ASSERT(match ->readSynch.hEvent != NULL); ASSERT(grovelStartEvent != NULL); ASSERT(grovelStopEvent != NULL); events[TARGET_OPLOCK_BREAK] = target->oplock .hEvent; events[MATCH_OPLOCK_BREAK] = match ->oplock .hEvent; events[TARGET_READ_DONE] = target->readSynch.hEvent; events[MATCH_READ_DONE] = match ->readSynch.hEvent; events[GROVEL_START] = grovelStartEvent; while (TRUE) { if (waitingForGrovelStart) timeOut = INFINITE; else if (timeAllotted == INFINITE) timeOut = targetRead || matchRead ? INFINITE : 0; else { elapsedTime = GetTickCount() - startAllottedTime; if (timeAllotted > elapsedTime) timeOut = targetRead || matchRead ? timeAllotted - elapsedTime : 0; else { waitingForGrovelStart = TRUE; timeOut = INFINITE; grovelStatus = Grovel_pending; ASSERT(IsReset(grovelStopEvent)); success = SetEvent(grovelStopEvent); ASSERT_ERROR(success); } } eventNum = WaitForMultipleObjects(NUM_EVENTS, events, FALSE, timeOut); eventTime = GetTickCount(); switch (eventNum) { case WAIT_OBJECT_0 + TARGET_OPLOCK_BREAK: ASSERT(!targetOplockBroke); targetOplockBroke = TRUE; success = ResetEvent(target->oplock.hEvent); ASSERT_ERROR(success); if (!targetRead) { CLEAR_OVERLAPPED(target->oplock); CloseFile(target); } DPRINTF((_T("%s: target file %s oplock broke during compare\n"), driveLetterName, target->fileName)); break; case WAIT_OBJECT_0 + MATCH_OPLOCK_BREAK: ASSERT(!matchOplockBroke); matchOplockBroke = TRUE; success = ResetEvent(match->oplock.hEvent); ASSERT_ERROR(success); if (!matchRead) { CLEAR_OVERLAPPED(match->oplock); CloseFile(match); } DPRINTF((_T("%s: match file %s oplock broke during compare\n"), driveLetterName, match->fileName)); break; case WAIT_OBJECT_0 + TARGET_READ_DONE: ASSERT(targetRead); targetRead = FALSE; success = ResetEvent(target->readSynch.hEvent); ASSERT_ERROR(success); target->stopTime = eventTime; if (targetOplockBroke) { CLEAR_OVERLAPPED(target->oplock); CloseFile(target); } break; case WAIT_OBJECT_0 + MATCH_READ_DONE: ASSERT(matchRead); matchRead = FALSE; success = ResetEvent(match->readSynch.hEvent); ASSERT_ERROR(success); match->stopTime = eventTime; if (matchOplockBroke) { CLEAR_OVERLAPPED(match->oplock); CloseFile(match); } break; case WAIT_OBJECT_0 + GROVEL_START: ASSERT(waitingForGrovelStart); waitingForGrovelStart = FALSE; success = ResetEvent(grovelStartEvent); ASSERT_ERROR(success); break; case WAIT_TIMEOUT: ASSERT(!waitingForGrovelStart); if (!targetRead && !matchRead) { if (terminate) throw TERMINATE; if (targetOplockBroke) throw TARGET_ERROR; if (matchOplockBroke) throw MATCH_ERROR; return; } waitingForGrovelStart = TRUE; grovelStatus = Grovel_pending; ASSERT(IsReset(grovelStopEvent)); success = SetEvent(grovelStopEvent); ASSERT_ERROR(success); break; default: ASSERT_PRINTF(FALSE, (_T("eventNum=%lu\n"), eventNum)); } } } #undef TARGET_OPLOCK_BREAK #undef MATCH_OPLOCK_BREAK #undef TARGET_READ_DONE #undef MATCH_READ_DONE #undef GROVEL_START #undef NUM_EVENTS /*****************************************************************************/ #define TARGET_OPLOCK_BREAK 0 #define MATCH_OPLOCK_BREAK 1 #define MERGE_DONE 2 #define GROVEL_START 3 #define NUM_EVENTS 4 // MergeCheckPoint suspends the thread until the merge operation is completed. // If the time allotment expires before the merge is completed, the // grovelStart event is set to signal grovel() to awaken, and this method // won't return until grovel() sets the grovelStart event. If either file's // oplock breaks before the merge is completed, the abortMerge event is set. BOOL Groveler::MergeCheckPoint( FileData *target, FileData *match, OVERLAPPED *mergeSynch, HANDLE abortMergeEvent, BOOL merge) { HANDLE events[NUM_EVENTS]; DWORD elapsedTime, timeOut, eventNum, eventTime, lastError = STATUS_TIMEOUT; BOOL targetOplockBroke = FALSE, matchOplockBroke = FALSE, waitingForGrovelStart = FALSE, mergeSuccess = FALSE, success; ASSERT(target != NULL); ASSERT(target->handle != NULL); ASSERT(target->oplock.hEvent != NULL); ASSERT(match != NULL); ASSERT(match->handle != NULL); ASSERT(match->oplock.hEvent != NULL); ASSERT(mergeSynch != NULL); ASSERT(mergeSynch->hEvent != NULL); ASSERT(abortMergeEvent != NULL); ASSERT(grovelStartEvent != NULL); ASSERT(grovelStopEvent != NULL); ASSERT(grovHandle != NULL); events[TARGET_OPLOCK_BREAK] = target->oplock.hEvent; events[MATCH_OPLOCK_BREAK] = match ->oplock.hEvent; events[MERGE_DONE] = mergeSynch-> hEvent; events[GROVEL_START] = grovelStartEvent; while (TRUE) { if (waitingForGrovelStart) timeOut = INFINITE; else if (timeAllotted == INFINITE) timeOut = merge ? INFINITE : 0; else { elapsedTime = GetTickCount() - startAllottedTime; if (timeAllotted > elapsedTime) timeOut = merge ? timeAllotted - elapsedTime : 0; else { waitingForGrovelStart = TRUE; timeOut = INFINITE; grovelStatus = Grovel_pending; ASSERT(IsReset(grovelStopEvent)); success = SetEvent(grovelStopEvent); ASSERT_ERROR(success); } } eventNum = WaitForMultipleObjects(NUM_EVENTS, events, FALSE, timeOut); eventTime = GetTickCount(); switch (eventNum) { case WAIT_OBJECT_0 + TARGET_OPLOCK_BREAK: ASSERT(!targetOplockBroke); targetOplockBroke = TRUE; success = ResetEvent(target->oplock.hEvent); ASSERT_ERROR(success); CLEAR_OVERLAPPED(target->oplock); if (merge) { success = SetEvent(abortMergeEvent); ASSERT_ERROR(success); } DPRINTF((_T("%s: target file %s oplock broke during merge\n"), driveLetterName, target->fileName)); break; case WAIT_OBJECT_0 + MATCH_OPLOCK_BREAK: ASSERT(!matchOplockBroke); matchOplockBroke = TRUE; success = ResetEvent(match->oplock.hEvent); ASSERT_ERROR(success); CLEAR_OVERLAPPED(match->oplock); if (merge) { success = SetEvent(abortMergeEvent); ASSERT_ERROR(success); } DPRINTF((_T("%s: match file %s oplock broke during merge\n"), driveLetterName, match->fileName)); break; case WAIT_OBJECT_0 + MERGE_DONE: ASSERT(merge); merge = FALSE; success = ResetEvent(mergeSynch->hEvent); ASSERT_ERROR(success); target->stopTime = eventTime; mergeSuccess = GetOverlappedResult( grovHandle, mergeSynch, &lastError, FALSE); if (!mergeSuccess) lastError = GetLastError(); else if (lastError != ERROR_SUCCESS) mergeSuccess = FALSE; else { GetCSIndex(target->handle, &target->entry.csIndex); if (!HasCSIndex(match->entry.csIndex)) GetCSIndex(match->handle, &match->entry.csIndex); } CloseFile(target); CloseFile(match); break; case WAIT_OBJECT_0 + GROVEL_START: ASSERT(waitingForGrovelStart); waitingForGrovelStart = FALSE; success = ResetEvent(grovelStartEvent); ASSERT_ERROR(success); break; case WAIT_TIMEOUT: ASSERT(!waitingForGrovelStart); if (!merge) { success = ResetEvent(abortMergeEvent); ASSERT_ERROR(success); if (terminate) throw TERMINATE; if (!mergeSuccess) SetLastError(lastError); return mergeSuccess; } waitingForGrovelStart = TRUE; grovelStatus = Grovel_pending; ASSERT(IsReset(grovelStopEvent)); success = SetEvent(grovelStopEvent); ASSERT_ERROR(success); break; default: ASSERT_PRINTF(FALSE, (_T("eventNum=%lu\n"), eventNum)); } } } #undef TARGET_OPLOCK_BREAK #undef MATCH_OPLOCK_BREAK #undef GROVEL_START #undef MERGE_DONE #undef NUM_EVENTS /*****************************************************************************/ // The following seven methods (GetTarget(), CalculateSignature(), // GetMatchList(), GetCSFile(), GetMatch(), Compare(), and Merge()) // implement the phases of the groveling process. // Structures used by the methods. struct MatchListEntry { DWORDLONG fileID, createTime, writeTime; }; struct CSIndexEntry { CSID csIndex; TCHAR name[1]; }; /*****************************************************************************/ // GetTarget() is the first phase of groveling a file. It dequeues // a file to be groveled (the "target" file), opens it, checks that // it meets all criteria, then passes it on to the next phases. BOOL Groveler::GetTarget( FileData *target, DWORD *queueIndex) { SGNativeTableEntry tableEntry; SGNativeQueueEntry queueEntry, otherQueueEntry; TFileName targetName, parentName; BY_HANDLE_FILE_INFORMATION fileInfo; DWORD lastError; DWORDLONG currentTime, readyTime; #if DBG DWORD earliestTime; #endif ULARGE_INTEGER word; LONG num; BOOL byName, success; TPRINTF((_T("GETTarget: entered\n"))); ASSERT(target != NULL); ASSERT(target->handle == NULL); ASSERT(target->entry.fileID == 0); ASSERT(target->fileName[0] == _T('\0')); ASSERT(!HasCSIndex(target->entry.csIndex)); ASSERT(queueIndex != NULL); ASSERT(sgDatabase != NULL); // Dequeue a file to be groveled. If the queue is empty or if no // entry's ready time has been reached, return Grovel_ok to grovel(). queueEntry.fileName = target->fileName; num = sgDatabase->QueueGetFirst(&queueEntry); if (num < 0) throw DATABASE_ERROR; if (num == 0) { DPRINTF((_T("%s: queue is empty\n"), driveLetterName)); return FALSE; } ASSERT(num == 1); currentTime = GetTime(); if (queueEntry.readyTime > currentTime) { #if DBG earliestTime = (DWORD)((queueEntry.readyTime - currentTime) / 10000); DPRINTF((_T("%s: earliest queue entry ready to be groveled in %lu.%03lu sec\n"), driveLetterName, earliestTime / 1000, earliestTime % 1000)); #endif return FALSE; } *queueIndex = queueEntry.order; target->entry.fileID = queueEntry.fileID; target->parentID = queueEntry.parentID; target->retryTime = queueEntry.retryTime; // Open the file by ID or name, and check by name // that the file and its parent directory are allowed. byName = target->entry.fileID == 0; if (byName) { ASSERT(target->parentID != 0); ASSERT(target->fileName[0] != _T('\0')); #ifdef DEBUG_USN_REASON DPRINTF((_T("--> 0x%08lx 0x%016I64x:\"%s\"\n"), queueEntry.reason, target->parentID, target->fileName)); #endif if (!GetFileName(volumeHandle, target->parentID, &parentName)) { DPRINTF((_T("%s: can't get name for directory 0x%016I64x\n"), driveLetterName, target->parentID)); throw TARGET_INVALID; } targetName.assign(parentName.name); targetName.append(_T("\\")); targetName.append(target->fileName); if (!IsAllowedName(targetName.name)) { DPRINTF((_T("%s: target file \"%s\" is disallowed\n"), driveLetterName, targetName.name)); throw TARGET_INVALID; } targetName.assign(driveName); targetName.append(parentName.name); targetName.append(_T("\\")); targetName.append(target->fileName); if (!OpenFileByName(target, FALSE, targetName.name)) { lastError = GetLastError(); if (lastError == ERROR_FILE_NOT_FOUND || lastError == ERROR_PATH_NOT_FOUND) { DPRINTF((_T("%s: target file \"%s\" doesn\'t exist\n"), driveLetterName, targetName.name)); throw TARGET_INVALID; } DPRINTF((_T("%s: can't open target file \"%s\": %lu\n"), driveLetterName, targetName.name, lastError)); throw TARGET_ERROR; } // Set an oplock on the target file. if (!SetOplock(target)) { DPRINTF((_T("%s: can't set oplock on target file \"%s\": %lu\n"), driveLetterName, targetName.name, GetLastError())); throw TARGET_ERROR; } } else { ASSERT(target->parentID == 0); ASSERT(target->fileName[0] == _T('\0')); target->parentID = 0; #ifdef DEBUG_USN_REASON DPRINTF((_T("--> 0x%08lx 0x%016I64x 0x%016I64x\n"), queueEntry.reason, target->entry.fileID, target->parentID)); #endif TPRINTF((_T("GETTarget: Opening %s:0x%016I64x by ID\n"), driveName,target->entry.fileID)); if (!OpenFileByID(target, FALSE)) { lastError = GetLastError(); if (lastError == ERROR_FILE_NOT_FOUND || lastError == ERROR_PATH_NOT_FOUND || lastError == ERROR_INVALID_PARAMETER) { DPRINTF((_T("%s: target file 0x%016I64x doesn\'t exist: %lu\n"), driveLetterName, target->entry.fileID, lastError)); throw TARGET_INVALID; } DPRINTF((_T("%s: can't open target file 0x%016I64x: %lu\n"), driveLetterName, target->entry.fileID, lastError)); throw TARGET_ERROR; } // Set an oplock on the target file. TPRINTF((_T("GETTarget: Successfully opened %s:0x%016I64x by ID\n"), driveName,target->entry.fileID)); if (!SetOplock(target)) { DPRINTF((_T("%s: can't set oplock on target file %s: %lu\n"), driveLetterName, target->fileName, GetLastError())); throw TARGET_ERROR; } if (!GetFileName(target->handle, &targetName)) { DPRINTF((_T("%s: can't get name for target file %s\n"), driveLetterName, target->fileName)); throw TARGET_ERROR; } if (!IsAllowedName(targetName.name)) { DPRINTF((_T("%s: target file \"%s\" is disallowed\n"), driveLetterName, targetName.name)); throw TARGET_INVALID; } } // Get the information on the target file. if (!GetFileInformationByHandle(target->handle, &fileInfo)) { #if DBG if (byName) { DPRINTF((_T("%s: can't get information on target file \"%s\": %lu\n"), driveLetterName, targetName.name, GetLastError())); } else { DPRINTF((_T("%s: can't get information on target file %s: %lu\n"), driveLetterName, target->fileName, GetLastError())); } #endif throw TARGET_ERROR; } word.HighPart = fileInfo.nFileIndexHigh; word.LowPart = fileInfo.nFileIndexLow; if (byName) target->entry.fileID = word.QuadPart; else { ASSERT(target->entry.fileID == word.QuadPart); } target->parentID = 0; // We don't need the parent ID any more. // If the target file was opened by name, check // if it currently has an entry in the queue by ID. if (byName) { otherQueueEntry.fileID = target->entry.fileID; otherQueueEntry.fileName = NULL; num = sgDatabase->QueueGetFirstByFileID(&otherQueueEntry); if (num < 0) throw DATABASE_ERROR; if (num > 0) { ASSERT(num == 1); DPRINTF((_T("%s: target file \"%s\" is already in queue as 0x%016I64x\n"), driveLetterName, targetName.name, target->entry.fileID)); target->entry.fileID = 0; // Prevent the table entry from being deleted. throw TARGET_INVALID; } } // Fill in the target file's remaining information values. word.HighPart = fileInfo.nFileSizeHigh; word.LowPart = fileInfo.nFileSizeLow; target->entry.fileSize = word.QuadPart; target->entry.attributes = fileInfo.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED; word.HighPart = fileInfo.ftCreationTime.dwHighDateTime; word.LowPart = fileInfo.ftCreationTime.dwLowDateTime; target->entry.createTime = word.QuadPart; word.HighPart = fileInfo.ftLastWriteTime.dwHighDateTime; word.LowPart = fileInfo.ftLastWriteTime.dwLowDateTime; target->entry.writeTime = word.QuadPart; // If the target file is a reparse point, check if it // is a SIS reparse point. If it is, get the CS index. if ((fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) target->entry.csIndex = nullCSIndex; else if (!GetCSIndex(target->handle, &target->entry.csIndex)) { DPRINTF((_T("%s: target file %s is a non-SIS reparse point\n"), driveLetterName, target->fileName)); throw TARGET_INVALID; } // Check if the target file is too small or has any disallowed attributes. if ((fileInfo.dwFileAttributes & disallowedAttributes) != 0 || fileInfo.nNumberOfLinks != 1 || target->entry.fileSize < minFileSize) { DPRINTF((_T("%s: target file \"%s\" is disallowed\n"), driveLetterName, target->fileName)); throw TARGET_INVALID; } // If a table entry exists for the target file, check if it is // consistent with the information we have on the file. If it is, and // the file was opened by name, or if the queue entry was the result // of a SIS merge, close the file and go on to grovel the next target. tableEntry.fileID = target->entry.fileID; num = sgDatabase->TableGetFirstByFileID(&tableEntry); if (num < 0) throw DATABASE_ERROR; if (num > 0) { ASSERT(num == 1); ASSERT(tableEntry.fileID == target->entry.fileID); if (target->entry.fileSize == tableEntry.fileSize && target->entry.attributes == tableEntry.attributes && SameCSIndex(target->entry.csIndex, tableEntry.csIndex) && target->entry.createTime == tableEntry.createTime && target->entry.writeTime == tableEntry.writeTime) { if (byName) { DPRINTF((_T("%s: target file \"%s\" has already been groveled\n"), driveLetterName, targetName.name)); target->entry.fileID = 0; // Prevent the table entry from being deleted. throw TARGET_INVALID; } if (queueEntry.reason == USN_REASON_BASIC_INFO_CHANGE) { DPRINTF((_T("%s: queue entry for file %s is the result of a SIS merge\n"), driveLetterName, target->fileName)); target->entry.fileID = 0; // Prevent the table entry from being deleted. throw TARGET_INVALID; } } } // Check if the time since the target file was last modified is too short. // If it is, close the file and go on to grovel the next target file. readyTime = (target->entry.createTime > target->entry.writeTime ? target->entry.createTime : target->entry.writeTime) + minFileAge; currentTime = GetTime(); if (currentTime < readyTime) throw TARGET_ERROR; // Check if the target file is mapped by another user. if (IsFileMapped(target)) { DPRINTF((_T("%s: target file %s is already mapped\n"), driveLetterName, target->fileName)); throw TARGET_ERROR; } TPRINTF((_T("GETTarget: returning\n"))); return TRUE; } /*****************************************************************************/ // CalculateSignature() calculates the target file's signature. It reads two // pages, 1/3 and 2/3 through the file, and calculates the signature on each // page. VOID Groveler::CalculateSignature(FileData *target) { DWORD lastPageSize, bytesToRead, prevBytesToRead, bytesToRequest, prevBytesToRequest = 0, bytesRead, toggle, lastError; DWORDLONG numPages, pageNum, prevPageNum, lastPageNum, firstPageToRead, lastPageToRead; ULARGE_INTEGER offset; BOOL targetReadDone = FALSE, success; INT i, nPagesToRead; ASSERT(target != NULL); ASSERT(target->entry.fileID != 0); ASSERT(target->handle != NULL); target->entry.signature = 0; if (0 == target->entry.fileSize) return; numPages = (target->entry.fileSize - 1) / SIG_PAGE_SIZE + 1; lastPageSize = (DWORD)((target->entry.fileSize - 1) % SIG_PAGE_SIZE) + 1; lastPageNum = numPages - 1; ASSERT(numPages > 0); firstPageToRead = (numPages + 2) / 3 - 1; lastPageToRead = lastPageNum - firstPageToRead; if (lastPageToRead > firstPageToRead) nPagesToRead = 2; else nPagesToRead = 1; toggle = 0; pageNum = firstPageToRead; // We'll read at most two pages, but make at most three passes through the loop // since we're doing asynchronous reads. for (i = 0; i <= nPagesToRead; ++i) { // Unless this is the first pass through the loop, // wait for the previous read of the target file to complete. if (i > 0) { SigCheckPoint(target, !targetReadDone); success = GetOverlappedResult( target->handle, &target->readSynch, &bytesRead, FALSE); if (!success) { DPRINTF((_T("%s: error getting target file %s read results: %lu\n"), driveLetterName, target->fileName, GetLastError())); throw TARGET_ERROR; } if (bytesRead != prevBytesToRequest && bytesRead != prevBytesToRead) { DPRINTF((_T("%s: sig read only %lu of %lu bytes from target file %s\n"), driveLetterName, bytesRead, prevBytesToRequest, target->fileName)); throw TARGET_ERROR; } if (bytesRead >= sigReportThreshold) { hashReadCount++; hashReadTime += target->stopTime - target->startTime; } } // Unless we've read all of the pages, begin reading the next page. if (i < nPagesToRead) { offset.QuadPart = pageNum * SIG_PAGE_SIZE; target->readSynch.Offset = offset.LowPart; target->readSynch.OffsetHigh = offset.HighPart; bytesToRead = pageNum == lastPageNum ? lastPageSize : SIG_PAGE_SIZE; bytesToRequest = bytesToRead + sectorSize - 1; bytesToRequest -= bytesToRequest % sectorSize; target->startTime = GetTickCount(); targetReadDone = ReadFile(target->handle, target->buffer[toggle], bytesToRequest, NULL, &target->readSynch); target->stopTime = GetTickCount(); lastError = GetLastError(); if (targetReadDone) { success = ResetEvent(target->readSynch.hEvent); ASSERT_ERROR(success); } else if (lastError != ERROR_IO_PENDING) { DPRINTF((_T("%s: error reading target file %s: %lu\n"), driveLetterName, target->fileName, lastError)); throw TARGET_ERROR; } } // Unless this is the first pass through the loop, // calculate the signature of the target file page just read. if (i > 0) target->entry.signature = Checksum((VOID *)target->buffer[1-toggle], prevBytesToRead, prevPageNum * SIG_PAGE_SIZE, target->entry.signature); prevPageNum = pageNum; prevBytesToRead = bytesToRead; prevBytesToRequest = bytesToRequest; toggle = 1-toggle; pageNum = lastPageToRead; } } /*****************************************************************************/ // GetMatchList() looks for file entries in the database ("match" files) // with the same size, signature, and attributes as the target file. VOID Groveler::GetMatchList( FileData *target, FIFO *matchList, Table *csIndexTable) { SGNativeTableEntry tableEntry; MatchListEntry *matchListEntry; CSIndexEntry *csIndexEntry; LONG num; BOOL success; ASSERT(target != NULL); ASSERT(target->entry.fileID != 0); ASSERT(target->entry.fileSize > 0); ASSERT(target->handle != NULL); ASSERT(matchList != NULL); ASSERT(matchList->Number() == 0); ASSERT(csIndexTable != NULL); ASSERT(csIndexTable->Number() == 0); ASSERT(sgDatabase != NULL); tableEntry.fileSize = target->entry.fileSize; tableEntry.signature = target->entry.signature; tableEntry.attributes = target->entry.attributes; #ifdef DEBUG_GET_BY_ATTR DPRINTF((_T("--> {%I64u, 0x%016I64x, 0x%lx}\n"), tableEntry.fileSize, tableEntry.signature, tableEntry.attributes)); #endif num = sgDatabase->TableGetFirstByAttr(&tableEntry); while (num > 0) { ASSERT(num == 1); ASSERT(tableEntry.fileID != 0); ASSERT(tableEntry.fileSize == target->entry.fileSize); ASSERT(tableEntry.signature == target->entry.signature); ASSERT(tableEntry.attributes == target->entry.attributes); if (!HasCSIndex(tableEntry.csIndex)) { matchListEntry = new MatchListEntry; ASSERT(matchListEntry != NULL); matchListEntry->fileID = tableEntry.fileID; matchListEntry->createTime = tableEntry.createTime; matchListEntry->writeTime = tableEntry.writeTime; matchList->Put((VOID *)matchListEntry); #ifdef DEBUG_GET_BY_ATTR DPRINTF((_T(" 0x%016I64x\n"), tableEntry.fileID)); #endif } else { csIndexEntry = (CSIndexEntry *)csIndexTable->Get ((const VOID *)&tableEntry.csIndex, sizeof(CSID)); if (csIndexEntry == NULL) { TCHAR *csName = GetCSName(&tableEntry.csIndex); if (csName != NULL) { // // Calculate how big the name buffer is and how big the // overall structure is. Note that the CSIndexEntry has // space in it for one character, which we account for. // int nameBufLen = ((wcslen(csName) + 1) * sizeof(TCHAR)); //account for NULL int bufLen = ((sizeof(CSIndexEntry)-sizeof(TCHAR)) + nameBufLen); csIndexEntry = (CSIndexEntry *)(new BYTE[bufLen]); ASSERT(csIndexEntry != NULL); csIndexEntry->csIndex = tableEntry.csIndex; (void)StringCbCopy( csIndexEntry->name, nameBufLen, csName); FreeCSName(csName); csName = NULL; } success = csIndexTable->Put((VOID *)csIndexEntry, sizeof(CSID)); ASSERT_ERROR(success); } #ifdef DEBUG_GET_BY_ATTR DPRINTF((_T(" 0x%016I64x %s\n"), match->entry.fileID, csIndexEntry->name)); #endif } num = sgDatabase->TableGetNext(&tableEntry); } if (num < 0) throw DATABASE_ERROR; } /*****************************************************************************/ // GetCSFile() pops the first entry from the CS index table and opens it. BOOL Groveler::GetCSFile( FileData *target, FileData *match, Table *csIndexTable) { CSIndexEntry *csIndexEntry; TFileName csFileName; DWORD lastError; BY_HANDLE_FILE_INFORMATION fileInfo; ULARGE_INTEGER fileSize; LONG num; ASSERT(target != NULL); ASSERT(target->entry.fileID != 0); ASSERT(target->entry.fileSize > 0); ASSERT(target->handle != NULL); ASSERT(match != NULL); ASSERT(match->entry.fileID == 0); ASSERT(match->entry.fileSize == 0); ASSERT(match->entry.signature == 0); ASSERT(match->entry.attributes == 0); ASSERT(!HasCSIndex(match->entry.csIndex)); ASSERT(match->entry.createTime == 0); ASSERT(match->entry.writeTime == 0); ASSERT(match->handle == NULL); ASSERT(match->parentID == 0); ASSERT(match->retryTime == 0); ASSERT(match->fileName[0] == _T('\0')); ASSERT(csIndexTable != NULL); ASSERT(sgDatabase != NULL); // Pop the first entry from the CS index table. If the entry's CS // index is the same as the target file's, skip to the next entry. do { csIndexEntry = (CSIndexEntry *)csIndexTable->GetFirst(); if (csIndexEntry == NULL) { match->entry.csIndex = nullCSIndex; match->fileName[0] = _T('\0'); return FALSE; } ASSERT(HasCSIndex(csIndexEntry->csIndex)); match->entry.csIndex = csIndexEntry->csIndex; _tcscpy(match->fileName, csIndexEntry->name); delete csIndexEntry; csIndexEntry = NULL; } while (SameCSIndex(target->entry.csIndex, match->entry.csIndex)); match->entry.fileSize = target->entry.fileSize; match->entry.signature = target->entry.signature; match->entry.attributes = target->entry.attributes; csFileName.assign(driveName); csFileName.append(CS_DIR_PATH); csFileName.append(_T("\\")); csFileName.append(match->fileName); csFileName.append(_T(".sis")); // Open the CS file. If the file doesn't exist, remove all entries // from the table that point to this file. If the file can't be // opened for any other reason, mark that the target file may // need to be groveled again, then go on to the next match file. #ifdef DEBUG_GET_BY_ATTR DPRINTF((_T("--> %s\n"), match->fileName)); #endif if (!OpenFileByName(match, FALSE, csFileName.name)) { lastError = GetLastError(); if (lastError == ERROR_FILE_NOT_FOUND || lastError == ERROR_PATH_NOT_FOUND) { DPRINTF((_T("%s: CS file %s doesn't exist\n"), driveLetterName, match->fileName)); throw MATCH_INVALID; } DPRINTF((_T("%s: can't open CS file %s: %lu\n"), driveLetterName, match->fileName, lastError)); throw MATCH_ERROR; } // Get the information on the CS file. If this fails, // close the file, mark that the target file may need to // be groveled again, then go on to the next match file. if (!GetFileInformationByHandle(match->handle, &fileInfo)) { DPRINTF((_T("%s: can't get information on CS file %s: %lu\n"), driveLetterName, match->fileName, GetLastError())); throw MATCH_ERROR; } // If the CS file's information doesn't match its expected values, close the // CS file, delete the match file entry from the table, and go on to the // next match file. Otherwise, go on to compare the target and CS files. fileSize.HighPart = fileInfo.nFileSizeHigh; fileSize.LowPart = fileInfo.nFileSizeLow; if (match->entry.fileSize != fileSize.QuadPart) { DPRINTF((_T("%s: CS file %s doesn't have expected information\n"), driveLetterName, match->fileName)); throw MATCH_STALE; } return TRUE; } /*****************************************************************************/ // GetMatch() pops the first entry from the match list and opens it. BOOL Groveler::GetMatch( FileData *target, FileData *match, FIFO *matchList) { SGNativeQueueEntry queueEntry; MatchListEntry *matchListEntry; DWORD attributes, lastError; BY_HANDLE_FILE_INFORMATION fileInfo; ULARGE_INTEGER fileID, fileSize, createTime, writeTime; LONG num; ASSERT(target != NULL); ASSERT(target->entry.fileID != 0); ASSERT(target->entry.fileSize > 0); ASSERT(target->handle != NULL); ASSERT(match != NULL); ASSERT(match->entry.fileID == 0); ASSERT(match->entry.fileSize == 0); ASSERT(match->entry.signature == 0); ASSERT(match->entry.attributes == 0); ASSERT(!HasCSIndex(match->entry.csIndex)); ASSERT(match->entry.createTime == 0); ASSERT(match->entry.writeTime == 0); ASSERT(match->handle == NULL); ASSERT(match->parentID == 0); ASSERT(match->retryTime == 0); ASSERT(match->fileName[0] == _T('\0')); ASSERT(matchList != NULL); ASSERT(sgDatabase != NULL); // Pop the first entry from the match list. If the entry's file ID is // the same as the target file's, or if the entry is on the queue after // having been enqueued by extract_log(), skip to the next entry. while (TRUE) { matchListEntry = (MatchListEntry *)matchList->Get(); if (matchListEntry == NULL) { match->entry.fileID = 0; match->entry.createTime = 0; match->entry.writeTime = 0; return FALSE; } match->entry.fileID = matchListEntry->fileID; match->entry.createTime = matchListEntry->createTime; match->entry.writeTime = matchListEntry->writeTime; delete matchListEntry; matchListEntry = NULL; ASSERT(match->entry.fileID != 0); if (target->entry.fileID == match->entry.fileID) continue; queueEntry.fileID = match->entry.fileID; queueEntry.fileName = NULL; num = sgDatabase->QueueGetFirstByFileID(&queueEntry); if (num < 0) throw DATABASE_ERROR; if (num > 0) { ASSERT(num == 1); if (queueEntry.reason != 0) { DPRINTF((_T("%s: match file 0x%016I64x is in the queue from USN\n"), driveLetterName, match->entry.fileID)); continue; } } break; } match->entry.fileSize = target->entry.fileSize; match->entry.signature = target->entry.signature; match->entry.attributes = target->entry.attributes; // Open the match file. If it doesn't exist, remove its entry from the table. // If the file can't be opened for any other reason, mark that the target // file may need to be groveled again, then go on to the next match file. #ifdef DEBUG_GET_BY_ATTR DPRINTF((_T("--> 0x%016I64x\n"), match->entry.fileID)); #endif if (!OpenFileByID(match, FALSE)) { lastError = GetLastError(); if (lastError == ERROR_FILE_NOT_FOUND || lastError == ERROR_PATH_NOT_FOUND || lastError == ERROR_INVALID_PARAMETER) { DPRINTF((_T("%s: match file 0x%016I64x doesn\'t exist: %lu\n"), driveLetterName, match->entry.fileID, lastError)); throw MATCH_INVALID; } DPRINTF((_T("%s: can't open match file 0x%016I64x: %lu\n"), driveLetterName, match->entry.fileID, lastError)); throw MATCH_ERROR; } // Set an oplock on the match file. if (!SetOplock(match)) { DPRINTF((_T("%s: can't set oplock on match file %s: %lu\n"), driveLetterName, match->fileName, GetLastError())); throw MATCH_ERROR; } // Get the information on the match file. If this fails, // close the file, mark that the target file may need to // be groveled again, then go on to the next match file. if (!GetFileInformationByHandle(match->handle, &fileInfo)) { DPRINTF((_T("%s: can't get information on match file %s: %lu\n"), driveLetterName, match->fileName, GetLastError())); throw MATCH_ERROR; } fileID.HighPart = fileInfo.nFileIndexHigh; fileID.LowPart = fileInfo.nFileIndexLow; ASSERT(match->entry.fileID == fileID.QuadPart); fileSize.HighPart = fileInfo.nFileSizeHigh; fileSize.LowPart = fileInfo.nFileSizeLow; attributes = fileInfo.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED; createTime.HighPart = fileInfo.ftCreationTime.dwHighDateTime; createTime.LowPart = fileInfo.ftCreationTime.dwLowDateTime; writeTime.HighPart = fileInfo.ftLastWriteTime.dwHighDateTime; writeTime.LowPart = fileInfo.ftLastWriteTime.dwLowDateTime; // If the match file's information isn't consistent with its table entry, close // the file, enqueue it to be re-groveled, and go on to the next match file. if (match->entry.fileSize != fileSize .QuadPart || match->entry.attributes != attributes || match->entry.createTime != createTime.QuadPart || match->entry.writeTime != writeTime .QuadPart) { DPRINTF((_T("%s: match file %s doesn't match its information\n"), driveLetterName, match->fileName)); throw MATCH_STALE; } if ((fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { if (GetCSIndex(match->handle, &match->entry.csIndex)) { DPRINTF((_T("%s: match file %s is a SIS reparse point\n"), driveLetterName, match->fileName)); throw MATCH_STALE; } DPRINTF((_T("%s: match file %s is a non-SIS reparse point\n"), driveLetterName, match->fileName)); throw MATCH_INVALID; } // Check if the match file is mapped by another user. if (IsFileMapped(match)) { DPRINTF((_T("%s: match file %s is already mapped\n"), driveLetterName, match->fileName)); throw MATCH_ERROR; } return TRUE; } /*****************************************************************************/ // Compare() compares the target and match files. It reads each file // one page (64 kB) at a time and compares each pair of pages. BOOL Groveler::Compare( FileData *target, FileData *match) { DWORD lastPageSize, bytesToRead = 0, prevBytesToRead, bytesToRequest = 0, prevBytesToRequest = 0, bytesRead, toggle, targetTime, matchTime, lastError; DWORDLONG numPages, pageNum, prevPageNum; ULARGE_INTEGER offset; BOOL targetReadDone = FALSE, matchReadDone = FALSE, filesMatch, success; ASSERT(target != NULL); ASSERT(target->handle != NULL); ASSERT(target->entry.fileID != 0); ASSERT(match != NULL); ASSERT(match->handle != NULL); ASSERT( match->entry.fileID != 0 && !HasCSIndex(match->entry.csIndex) || match->entry.fileID == 0 && match->fileName[0] != _T('\0') && HasCSIndex(match->entry.csIndex)); ASSERT(target->entry.fileSize == match->entry.fileSize); ASSERT(target->entry.signature == match->entry.signature); ASSERT(target->entry.attributes == match->entry.attributes); numPages = (target->entry.fileSize - 1) / CMP_PAGE_SIZE + 1; lastPageSize = (DWORD)((target->entry.fileSize - 1) % CMP_PAGE_SIZE) + 1; toggle = 0; filesMatch = TRUE; for (pageNum = 0; pageNum <= numPages; pageNum++) { // Unless this is the first pass through the loop, // wait for the previous read of both files to complete. if (pageNum > 0) { CmpCheckPoint(target, match, !targetReadDone, !matchReadDone); success = GetOverlappedResult( target->handle, &target->readSynch, &bytesRead, FALSE); if (!success) { DPRINTF((_T("%s: error getting target file %s read results: %lu\n"), driveLetterName, target->fileName, GetLastError())); throw TARGET_ERROR; } if (bytesRead != prevBytesToRequest && bytesRead != prevBytesToRead) { DPRINTF((_T("%s: cmp read only %lu of %lu bytes from target file %s\n"), driveLetterName, bytesRead, prevBytesToRequest, target->fileName)); throw TARGET_ERROR; } success = GetOverlappedResult( match->handle, &match->readSynch, &bytesRead, FALSE); if (!success) { #if DBG if (match->entry.fileID != 0) { DPRINTF((_T("%s: error getting match file %s read results: %lu\n"), driveLetterName, match->fileName, GetLastError())); } else { DPRINTF((_T("%s: error getting CS file %s read results: %lu\n"), driveLetterName, match->fileName, GetLastError())); } #endif throw MATCH_ERROR; } if (bytesRead != prevBytesToRequest && bytesRead != prevBytesToRead) { #if DBG if (match->entry.fileID != 0) { DPRINTF((_T("%s: read only %lu of %lu bytes from match file %s\n"), driveLetterName, bytesRead, prevBytesToRequest, match->fileName)); } else { DPRINTF((_T("%s: read only %lu of %lu bytes from CS file %s\n"), driveLetterName, bytesRead, prevBytesToRequest, match->fileName)); } #endif throw MATCH_ERROR; } if (bytesRead >= cmpReportThreshold) { compareReadCount += 2; if (targetReadDone) { // Non-overlapped targetTime = target->stopTime - target->startTime; matchTime = match ->stopTime - match ->startTime; compareReadTime += targetTime + matchTime; } else { // Overlapped targetTime = target->stopTime - target->startTime; matchTime = match ->stopTime - target->startTime; compareReadTime += targetTime > matchTime ? targetTime : matchTime; } } if (!filesMatch) break; } // Unless all pages of the target file have already been read, // begin reading the next page of the file. if (pageNum < numPages) { offset.QuadPart = pageNum * CMP_PAGE_SIZE; target->readSynch.Offset = match ->readSynch.Offset = offset.LowPart; target->readSynch.OffsetHigh = match ->readSynch.OffsetHigh = offset.HighPart; bytesToRead = pageNum < numPages-1 ? CMP_PAGE_SIZE : lastPageSize; bytesToRequest = bytesToRead + sectorSize - 1; bytesToRequest -= bytesToRequest % sectorSize; target->startTime = GetTickCount(); targetReadDone = ReadFile(target->handle, target->buffer[toggle], bytesToRequest, NULL, &target->readSynch); target->stopTime = GetTickCount(); lastError = GetLastError(); if (targetReadDone) { success = ResetEvent(target->readSynch.hEvent); ASSERT_ERROR(success); } else if (lastError != ERROR_IO_PENDING) { DPRINTF((_T("%s: error reading target file %s: %lu\n"), driveLetterName, target->fileName, lastError)); throw TARGET_ERROR; } match->startTime = GetTickCount(); matchReadDone = ReadFile(match->handle, match->buffer[toggle], bytesToRequest, NULL, &match->readSynch); match->stopTime = GetTickCount(); lastError = GetLastError(); if (matchReadDone) { success = ResetEvent(match->readSynch.hEvent); ASSERT_ERROR(success); } else if (lastError != ERROR_IO_PENDING) { #if DBG if (match->entry.fileID != 0) { DPRINTF((_T("%s: error reading match file %s: %lu\n"), driveLetterName, match->fileName, lastError)); } else { DPRINTF((_T("%s: error reading CS file %s: %lu\n"), driveLetterName, match->fileName, lastError)); } #endif throw MATCH_ERROR; } } // Unless this is the first pass through the loop, // compare the target and match file pages just read. if (pageNum > 0) filesMatch = memcmp(target->buffer[1-toggle], match ->buffer[1-toggle], prevBytesToRead) == 0; prevPageNum = pageNum; prevBytesToRead = bytesToRead; prevBytesToRequest = bytesToRequest; toggle = 1-toggle; } if (!filesMatch) { #if DBG if (match->entry.fileID != 0) { DPRINTF((_T("%s:1 files %s and %s failed compare (sz: 0x%x)\n"), driveLetterName, target->fileName, match->fileName, target->entry.fileSize)); } else { DPRINTF((_T("%s:2 files %s and %s failed compare (sz: 0x%x)\n"), driveLetterName, target->fileName, match->fileName, target->entry.fileSize)); } #endif return FALSE; } return TRUE; } /*****************************************************************************/ // Merge() calls the SIS filter to merge the target and match files. BOOL Groveler::Merge( FileData *target, FileData *match, OVERLAPPED *mergeSynch, HANDLE abortMergeEvent) { _SIS_LINK_FILES sisLinkFiles; #if DBG TCHAR *csName; #endif DWORD transferCount, lastError; BOOL mergeDone, merged, success; ASSERT(target != NULL); ASSERT(target->handle != NULL); ASSERT(target->entry.fileID != 0); ASSERT(match != NULL); ASSERT(match->handle != NULL); ASSERT( match->entry.fileID != 0 && !HasCSIndex(match->entry.csIndex) || match->entry.fileID == 0 && match->fileName[0] != _T('\0') && HasCSIndex(match->entry.csIndex)); ASSERT(mergeSynch != NULL); ASSERT(mergeSynch->Internal == 0); ASSERT(mergeSynch->InternalHigh == 0); ASSERT(mergeSynch->Offset == 0); ASSERT(mergeSynch->OffsetHigh == 0); ASSERT(mergeSynch->hEvent != NULL); ASSERT(IsReset(mergeSynch->hEvent)); ASSERT(abortMergeEvent != NULL); ASSERT(IsReset(abortMergeEvent)); ASSERT(target->entry.fileSize == match->entry.fileSize); ASSERT(target->entry.signature == match->entry.signature); ASSERT(target->entry.attributes == match->entry.attributes); ASSERT(grovHandle != NULL); // Set up to merge the files. if (match->entry.fileID != 0) { sisLinkFiles.operation = SIS_LINK_FILES_OP_MERGE; sisLinkFiles.u.Merge.file1 = target->handle; sisLinkFiles.u.Merge.file2 = match ->handle; sisLinkFiles.u.Merge.abortEvent = NULL; // Should be abortMergeEvent } else { sisLinkFiles.operation = SIS_LINK_FILES_OP_MERGE_CS; sisLinkFiles.u.MergeWithCS.file1 = target->handle; sisLinkFiles.u.MergeWithCS.abortEvent = NULL; // Should be abortMergeEvent sisLinkFiles.u.MergeWithCS.CSid = match->entry.csIndex; } // Call the SIS filter to merge the files. target->startTime = GetTickCount(); mergeDone = DeviceIoControl( grovHandle, FSCTL_SIS_LINK_FILES, (VOID *)&sisLinkFiles, sizeof(_SIS_LINK_FILES), NULL, 0, NULL, mergeSynch); target->stopTime = GetTickCount(); // If the merge completed successfully before the call returned, reset // the merge done event, get the new CS indices, and close the files. if (mergeDone) { success = ResetEvent(mergeSynch->hEvent); ASSERT_ERROR(success); mergeTime += target->stopTime - target->startTime; GetCSIndex(target->handle, &target->entry.csIndex); if (!HasCSIndex(match->entry.csIndex)) GetCSIndex(match->handle, &match->entry.csIndex); CloseFile(target); CloseFile(match); } // If the merge failed, close the files and return an error status. else { lastError = GetLastError(); if (lastError != ERROR_IO_PENDING) { CloseFile(target); CloseFile(match); #if DBG if (match->entry.fileID != 0) { DPRINTF((_T("%s:3 files %s and %s failed merge: %lu\n"), driveLetterName, target->fileName, match->fileName, lastError)); } else { DPRINTF((_T("%s:4 files %s and %s failed merge: %lu\n"), driveLetterName, target->fileName, match->fileName, lastError)); } #endif return FALSE; } // If the merge is in progress, wait for it to complete. // (MergeCheckPoint() will get the new CS indices and close the files. else { merged = MergeCheckPoint(target, match, mergeSynch, abortMergeEvent, !mergeDone); if (!merged) { #if DBG lastError = GetLastError(); if (match->entry.fileID != 0) { DPRINTF((_T("%s: error getting merge results of files %s and %s: %lu\n"), driveLetterName, target->fileName, match->fileName, lastError)); } else { DPRINTF((_T("%s: error getting merge results of files %s and %s: %lu\n"), driveLetterName, target->fileName, match->fileName, lastError)); } #endif return FALSE; } } } // If the merge succeeded, analyze and report the results. mergeTime += target->stopTime - target->startTime; merged = HasCSIndex (target->entry.csIndex) && SameCSIndex(target->entry.csIndex, match->entry.csIndex); #if DBG csName = GetCSName(&target->entry.csIndex); if (merged) { if (match->entry.fileID != 0) { DPRINTF((_T("%s: files %s and %s merged: CS index is %s\n"), driveLetterName, target->fileName, match->fileName, csName != NULL ? csName : _T("..."))); } else { DPRINTF((_T("%s: files %s and %s merged\n"), driveLetterName, target->fileName, match->fileName)); } } else { if (match->entry.fileID != 0) { DPRINTF((_T("%s:5 files %s and %s merged, but CS indices don't match\n"), driveLetterName, target->fileName, match->fileName)); } else { DPRINTF((_T("%s:6 files %s and %s merged, but CS indices don't match\n"), driveLetterName, target->fileName, match->fileName)); } } if (csName != NULL) { FreeCSName(csName); csName = NULL; } #endif return merged; } /*****************************************************************************/ // Worker() performs the groveling processing. VOID Groveler::Worker() { FileData target, match; SGNativeQueueEntry queueEntry; FIFO *matchList = NULL; Table *csIndexTable = NULL; OVERLAPPED mergeSynch = { 0, 0, 0, 0, NULL }; HANDLE abortMergeEvent = NULL; TCHAR *csName; DatabaseActionList actionList[MAX_ACTIONS]; BYTE *buffer1 = NULL, *buffer2 = NULL, *buffer3 = NULL, *buffer4 = NULL; DWORD queueIndex, bufferSize, numCompares, numMatches, numActions; #if DBG DWORD enqueueTime; #endif LONG num; BOOL needToRetry, hashed, gotMatch, filesMatch, merged, success; CLEAR_FILE(target); CLEAR_OVERLAPPED(target.oplock); target.handle = NULL; CLEAR_FILE(match); CLEAR_OVERLAPPED(match.oplock); match.handle = NULL; _set_new_handler(NewHandler); // Create the events. try { if ((target.oplock .hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL || (match .oplock .hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL || (target.readSynch.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL || (match .readSynch.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL || (mergeSynch .hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL || (abortMergeEvent = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL) { DPRINTF((_T("%s: unable to create events: %lu\n"), driveLetterName, GetLastError())); throw INITIALIZE_ERROR; } // Allocate and align the file buffers. bufferSize = SIG_PAGE_SIZE > CMP_PAGE_SIZE ? SIG_PAGE_SIZE : CMP_PAGE_SIZE + sectorSize; buffer1 = new BYTE[bufferSize]; ASSERT(buffer1 != NULL); buffer2 = new BYTE[bufferSize]; ASSERT(buffer2 != NULL); buffer3 = new BYTE[bufferSize]; ASSERT(buffer3 != NULL); buffer4 = new BYTE[bufferSize]; ASSERT(buffer4 != NULL); ASSERT(inUseFileID1 == NULL); ASSERT(inUseFileID2 == NULL); inUseFileID1 = &target.entry.fileID; inUseFileID2 = &match .entry.fileID; target.buffer[0] = buffer1 + sectorSize - (PtrToUlong(buffer1) % sectorSize); target.buffer[1] = buffer2 + sectorSize - (PtrToUlong(buffer2) % sectorSize); match .buffer[0] = buffer3 + sectorSize - (PtrToUlong(buffer3) % sectorSize); match .buffer[1] = buffer4 + sectorSize - (PtrToUlong(buffer4) % sectorSize); // Signal to grovel() that this thread is alive, // then wait for it to signal to start. grovelStatus = Grovel_ok; ASSERT(IsReset(grovelStopEvent)); success = SetEvent(grovelStopEvent); ASSERT_ERROR(success); WaitForEvent(grovelStartEvent); if (terminate) throw TERMINATE; #ifdef _CRTDBG _CrtMemState s[2], sdiff; int stateIndex = 0; _CrtMemCheckpoint(&s[stateIndex]); stateIndex = 1; #endif // The main loop. while (TRUE) { try { #ifdef _CRTDBG _CrtMemCheckpoint(&s[stateIndex]); if (_CrtMemDifference(&sdiff, &s[stateIndex^1], &s[stateIndex])) _CrtMemDumpStatistics(&sdiff); stateIndex ^= 1; #endif hashed = FALSE; numCompares = 0; numMatches = 0; merged = FALSE; needToRetry = FALSE; // Get a target file. abortGroveling is set when scan_volume is attempting to // sync up with this thread. We stop here, a safe place to let scan_volume // replace the database. if (abortGroveling || !GetTarget(&target, &queueIndex)) { CLEAR_FILE(target); grovelStatus = Grovel_ok; ASSERT(IsReset(grovelStopEvent)); success = SetEvent(grovelStopEvent); ASSERT_ERROR(success); WaitForEvent(grovelStartEvent); if (terminate) throw TERMINATE; continue; } // Calculate the target file's signature. hashed = TRUE; CalculateSignature(&target); // Get a list of match files. ASSERT(matchList == NULL); ASSERT(csIndexTable == NULL); matchList = new FIFO(); ASSERT(matchList != NULL); csIndexTable = new Table(); ASSERT(csIndexTable != NULL); GetMatchList(&target, matchList, csIndexTable); // Compare the target file to each match file until a matching file is found // or all comparisons fail. Try the SIS files first, then the regular files. while (TRUE) { try { gotMatch = FALSE; if (!gotMatch && csIndexTable != NULL) { gotMatch = GetCSFile(&target, &match, csIndexTable); if (!gotMatch) { delete csIndexTable; csIndexTable = NULL; } } if (!gotMatch && matchList != NULL) { gotMatch = GetMatch(&target, &match, matchList); if (!gotMatch) { delete matchList; matchList = NULL; } } // After comparing the target file to every file on both // lists, close the target file and update the database, // then go on to process the next target file. if (!gotMatch) { CloseFile(&target); numActions = 3; actionList[0].type = TABLE_DELETE_BY_FILE_ID; actionList[0].u.fileID = target.entry.fileID; actionList[1].type = TABLE_PUT; actionList[1].u.tableEntry = &target.entry; actionList[2].type = QUEUE_DELETE; actionList[2].u.queueIndex = queueIndex; if (needToRetry) { queueEntry.fileID = target.entry.fileID; queueEntry.parentID = target.parentID; queueEntry.reason = 0; queueEntry.fileName = NULL; queueEntry.retryTime = target.retryTime * 2; // Exponential back-off if (queueEntry.retryTime < grovelInterval) queueEntry.retryTime = grovelInterval; queueEntry.readyTime = GetTime() + queueEntry.retryTime; numActions = 4; actionList[3].type = QUEUE_PUT; actionList[3].u.queueEntry = &queueEntry; } #if DBG if (!HasCSIndex(target.entry.csIndex)) { TRACE_PRINTF(TC_groveler, 4, (_T("%s: adding file {%s, %I64u, 0x%016I64x} to table\n"), driveLetterName, target.fileName, target.entry.fileSize, target.entry.signature)); } else { csName = GetCSName(&target.entry.csIndex); TRACE_PRINTF(TC_groveler, 4, (_T("%s: adding file {%s, %I64u, 0x%016I64x, %s} to table\n"), driveLetterName, target.fileName, target.entry.fileSize, target.entry.signature, csName != NULL ? csName : _T("..."))); if (csName != NULL) { FreeCSName(csName); csName = NULL; } } if (needToRetry) { enqueueTime = (DWORD)(queueEntry.retryTime / 10000); DPRINTF((_T(" Re-enqueuing target file %s to be groveled in %lu.%03lu sec\n"), target.fileName, enqueueTime / 1000, enqueueTime % 1000)); } #endif DoTransaction(numActions, actionList); break; } // Compare the target file with this match file. numCompares++; ASSERT(!inCompare); inCompare = TRUE; filesMatch = Compare(&target, &match); inCompare = FALSE; if (!filesMatch) { CloseFile(&match); CLEAR_FILE(match); continue; } // If the target and match files are identical, go on to merge them. numMatches++; merged = Merge(&target, &match, &mergeSynch, abortMergeEvent); // Update the database as follows: // // - Update the target file's table entry. // // - If the merge succeeded and the match file was a regular file, // update the match file's table entry. // // - If the merge failed, re-enqueue the target file to be groveled again. numActions = 3; actionList[0].type = TABLE_DELETE_BY_FILE_ID; actionList[0].u.fileID = target.entry.fileID; actionList[1].type = TABLE_PUT; actionList[1].u.tableEntry = &target.entry; actionList[2].type = QUEUE_DELETE; actionList[2].u.queueIndex = queueIndex; if (merged) { if (match.entry.fileID != 0) { actionList[numActions ].type = TABLE_DELETE_BY_FILE_ID; actionList[numActions++].u.fileID = match.entry.fileID; actionList[numActions ].type = TABLE_PUT; actionList[numActions++].u.tableEntry = &match.entry; } } else { queueEntry.fileID = target.entry.fileID; queueEntry.parentID = target.parentID; queueEntry.reason = 0; queueEntry.fileName = NULL; queueEntry.retryTime = target.retryTime * 2; // Exponential back-off if (queueEntry.retryTime < grovelInterval) queueEntry.retryTime = grovelInterval; queueEntry.readyTime = GetTime() + queueEntry.retryTime; actionList[numActions ].type = QUEUE_PUT; actionList[numActions++].u.queueEntry = &queueEntry; } #if DBG if (!HasCSIndex(target.entry.csIndex)) { TPRINTF((_T("%s: adding file {%s, %I64u, 0x%016I64x} to table\n"), driveLetterName, target.fileName, target.entry.fileSize, target.entry.signature)); } else { csName = GetCSName(&target.entry.csIndex); TPRINTF((_T("%s: adding file {%s, %I64u, 0x%016I64x, %s} to table\n"), driveLetterName, target.fileName, target.entry.fileSize, target.entry.signature, csName != NULL ? csName : _T("..."))); if (csName != NULL) { FreeCSName(csName); csName = NULL; } } if (!merged) { enqueueTime = (DWORD)(queueEntry.retryTime / 10000); DPRINTF((_T(" Re-enqueuing target file %s to be groveled in %lu.%03lu sec\n"), target.fileName, enqueueTime / 1000, enqueueTime % 1000)); } #endif DoTransaction(numActions, actionList); break; } // Match exceptions catch (MatchException matchException) { inCompare = FALSE; switch (matchException) { // MATCH_INVALID: the match file doesn't exist or is disallowed. Close the file // and remove its entry from the table, then go on to try the next match file. case MATCH_INVALID: CloseFile(&match); if (match.entry.fileID != 0) { ASSERT(!HasCSIndex(match.entry.csIndex)); num = sgDatabase->TableDeleteByFileID(match.entry.fileID); if (num < 0) throw DATABASE_ERROR; ASSERT(num == 1); } else { ASSERT(HasCSIndex(match.entry.csIndex)); num = sgDatabase->TableDeleteByCSIndex(&match.entry.csIndex); if (num < 0) throw DATABASE_ERROR; ASSERT(num > 0); } CLEAR_FILE(match); break; // MATCH_ERROR: an error occured while opening or reading the match // file. Close the file and mark that the target file may need to be // groveled again, then go on to try the next match file. case MATCH_ERROR: CloseFile(&match); CLEAR_FILE(match); needToRetry = TRUE; break; // MATCH_STALE: the match file table entry is invalid for some reason. // Close the file, remove its entry from the table, enqueue // it to be re-groveled, then go on to the next match file. case MATCH_STALE: CloseFile(&match); if (match.entry.fileID != 0) { queueEntry.fileID = match.entry.fileID; queueEntry.parentID = match.parentID; queueEntry.reason = 0; queueEntry.readyTime = GetTime() + grovelInterval; queueEntry.retryTime = 0; queueEntry.fileName = NULL; numActions = 2; actionList[0].type = TABLE_DELETE_BY_FILE_ID; actionList[0].u.fileID = match.entry.fileID; actionList[1].type = QUEUE_PUT; actionList[1].u.queueEntry = &queueEntry; #if DBG enqueueTime = (DWORD)(grovelInterval / 10000); DPRINTF((_T(" Enqueuing match file %s to be groveled in %lu.%03lu sec\n"), match.fileName, enqueueTime / 1000, enqueueTime % 1000)); #endif DoTransaction(numActions, actionList); } else { ASSERT(HasCSIndex(match.entry.csIndex)); EnqueueCSIndex(&match.entry.csIndex); } CLEAR_FILE(match); break; default: ASSERT_PRINTF(FALSE, (_T("matchException=%lu\n"), matchException)); } } } } // Target exceptions catch (TargetException targetException) { inCompare = FALSE; DPRINTF((_T("WORKER: Handling TargetException %d, status=%d\n"), targetException,GetLastError())); switch (targetException) { // TARGET_INVALID: the target file is invalid for some reason: it doesn't // exist, it is disallowed properties, it is in the queue by both file // name and file ID, or it was in the queue by file name and has already // been groveled. Close the files, remove the target file's entry from // the table, then go on to grovel the next target file. case TARGET_INVALID: CloseFile(&target); CloseFile(&match); if (matchList != NULL) { delete matchList; matchList = NULL; } if (csIndexTable != NULL) { delete csIndexTable; csIndexTable = NULL; } numActions = 1; actionList[0].type = QUEUE_DELETE; actionList[0].u.queueIndex = queueIndex; if (target.entry.fileID != 0) { numActions = 2; actionList[1].type = TABLE_DELETE_BY_FILE_ID; actionList[1].u.fileID = target.entry.fileID; } DoTransaction(numActions, actionList); break; // An error occured while opening or reading the target file. Close // the files and re-enqueue the target file to be groveled again. case TARGET_ERROR: ASSERT(target.entry.fileID != 0 || target.fileName[0] != _T('\0')); CloseFile(&target); CloseFile(&match); queueEntry.fileID = target.entry.fileID; queueEntry.parentID = target.parentID; queueEntry.reason = 0; queueEntry.fileName = target.entry.fileID == 0 ? target.fileName : NULL; queueEntry.retryTime = target.retryTime * 2; // Exponential back-off if (queueEntry.retryTime < grovelInterval) queueEntry.retryTime = grovelInterval; queueEntry.readyTime = GetTime() + queueEntry.retryTime; actionList[0].type = QUEUE_DELETE; actionList[0].u.queueIndex = queueIndex; actionList[1].type = QUEUE_PUT; actionList[1].u.queueEntry = &queueEntry; #if DBG enqueueTime = (DWORD)(queueEntry.retryTime / 10000); if (target.entry.fileID != 0) { DPRINTF((_T(" Re-enqueuing target file %s to be groveled in %lu.%03lu sec\n"), target.fileName, enqueueTime / 1000, enqueueTime % 1000)); } else { DPRINTF((_T(" Re-enqueuing target file %s to be groveled in %lu.%03lu sec\n"), target.fileName, enqueueTime / 1000, enqueueTime % 1000)); } #endif DoTransaction(2, actionList); break; default: ASSERT_PRINTF(FALSE, (_T("targetException=%lu\n"), targetException)); } } // Do some clean-up. ASSERT(target.handle == NULL); ASSERT(match .handle == NULL); if (matchList != NULL) { delete matchList; matchList = NULL; } if (csIndexTable != NULL) { delete csIndexTable; csIndexTable = NULL; } // Update the activity counters for this target file, // then go on to process the next file. if (hashed) { hashCount++; hashBytes += target.entry.fileSize; } compareCount += numCompares; compareBytes += numCompares * target.entry.fileSize; matchCount += numMatches; matchBytes += numMatches * target.entry.fileSize; if (merged) { mergeCount++; mergeBytes += target.entry.fileSize; } CLEAR_FILE(target); CLEAR_FILE(match); CLEAR_OVERLAPPED(mergeSynch); } } // Terminal exceptions catch (TerminalException terminalException) { switch (terminalException) { case INITIALIZE_ERROR: break; // DATABASE_ERROR: an error occured in the database. Return an error status. case DATABASE_ERROR: break; // MEMORY_ERROR: unable to allocate memory. Return an error status. case MEMORY_ERROR: DPRINTF((_T("%s: Unable to allocate memory\n"), driveLetterName)); break; // TERMINATE: grovel() signaled for this thread to terminate. case TERMINATE: break; default: ASSERT_PRINTF(FALSE, (_T("terminalException=%lu\n"), terminalException)); } } // Close the files and clean up. CloseFile(&target); CloseFile(&target); CLEAR_FILE(target); CLEAR_FILE(match); if (matchList != NULL) { delete matchList; matchList = NULL; } if (csIndexTable != NULL) { delete csIndexTable; csIndexTable = NULL; } if (target.oplock.hEvent != NULL) { success = CloseHandle(target.oplock.hEvent); ASSERT_ERROR(success); target.oplock.hEvent = NULL; } if (match.oplock.hEvent != NULL) { success = CloseHandle(match.oplock.hEvent); ASSERT_ERROR(success); match.oplock.hEvent = NULL; } if (target.readSynch.hEvent != NULL) { success = CloseHandle(target.readSynch.hEvent); ASSERT_ERROR(success); target.readSynch.hEvent = NULL; } if (match.readSynch.hEvent != NULL) { success = CloseHandle(match.readSynch.hEvent); ASSERT_ERROR(success); match.readSynch.hEvent = NULL; } if (mergeSynch.hEvent != NULL) { success = CloseHandle(mergeSynch.hEvent); ASSERT_ERROR(success); mergeSynch.hEvent = NULL; } if (abortMergeEvent != NULL) { success = CloseHandle(abortMergeEvent); ASSERT_ERROR(success); abortMergeEvent = NULL; } if (buffer1 != NULL) { delete [] buffer1; buffer1 = NULL; } if (buffer2 != NULL) { delete [] buffer2; buffer2 = NULL; } if (buffer3 != NULL) { delete [] buffer3; buffer3 = NULL; } if (buffer4 != NULL) { delete [] buffer4; buffer4 = NULL; } inUseFileID1 = NULL; inUseFileID2 = NULL; // Signal grovel() that this thread is terminating by // setting the grovelStop event with an error status. grovelThread = NULL; grovelStatus = Grovel_error; ASSERT(IsReset(grovelStopEvent)); success = SetEvent(grovelStopEvent); ASSERT_ERROR(success); } /*****************************************************************************/ /******************* Groveler class static private methods *******************/ /*****************************************************************************/ // WorkerThread() runs in its own thread. // It calls Worker() to perform the groveling processing. DWORD Groveler::WorkerThread(VOID *groveler) { ((Groveler *)groveler)->Worker(); return 0; // Dummy return value } /*****************************************************************************/ /*********************** Groveler class public methods ***********************/ /*****************************************************************************/ BOOL Groveler::set_log_drive(const _TCHAR *drive_name) { return SGDatabase::set_log_drive(drive_name); } // is_sis_installed tests whether the SIS filter is // installed on a volume by calling SIS copyfile. BOOL Groveler::is_sis_installed(const _TCHAR *drive_name) { HANDLE volHandle; SI_COPYFILE copyFile; DWORD transferCount, lastError; BOOL success; volHandle = CreateFile( drive_name, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (volHandle == INVALID_HANDLE_VALUE) return FALSE; copyFile.SourceFileNameLength = 0; copyFile.DestinationFileNameLength = 0; copyFile.Flags = COPYFILE_SIS_REPLACE; success = DeviceIoControl( volHandle, FSCTL_SIS_COPYFILE, (VOID *)©File, sizeof(SI_COPYFILE), NULL, 0, &transferCount, NULL); lastError = GetLastError(); ASSERT(!success); success = CloseHandle(volHandle); ASSERT_ERROR(success); switch (lastError) { case ERROR_INVALID_FUNCTION: return FALSE; case ERROR_INVALID_PARAMETER: return TRUE; //sis is installed on this volume default: ASSERT_PRINTF(FALSE, (_T("lastError=%lu\n"), lastError)); } return FALSE; // Dummy return value } /*****************************************************************************/ // The groveler constructor creates and initializes all class variables. Groveler::Groveler() { volumeHandle = NULL; grovHandle = NULL; sgDatabase = NULL; driveName = NULL; driveLetterName = NULL; databaseName = NULL; numDisallowedIDs = 0; numDisallowedNames = 0; disallowedIDs = NULL; disallowedNames = NULL; grovelStartEvent = NULL; grovelStopEvent = NULL; grovelThread = NULL; inUseFileID1 = NULL; inUseFileID2 = NULL; abortGroveling = FALSE; inCompare = FALSE; inScan = FALSE; terminate = TRUE; usnID = lastUSN = UNINITIALIZED_USN; } /*****************************************************************************/ // The groveler destructor destroys all class variables. Groveler::~Groveler() { // If the volume is open, call close() to close it. close(); ASSERT(volumeHandle == NULL); ASSERT(grovHandle == NULL); ASSERT(sgDatabase == NULL); ASSERT(driveName == NULL); ASSERT(driveLetterName == NULL); ASSERT(databaseName == NULL); ASSERT(numDisallowedIDs == 0); ASSERT(numDisallowedNames == 0); ASSERT(disallowedIDs == NULL); ASSERT(disallowedNames == NULL); ASSERT(grovelStartEvent == NULL); ASSERT(grovelStopEvent == NULL); ASSERT(grovelThread == NULL); ASSERT(inUseFileID1 == NULL); ASSERT(inUseFileID2 == NULL); ASSERT(terminate); ASSERT(!inCompare); ASSERT(!inScan); ASSERT(usnID == UNINITIALIZED_USN); } /*****************************************************************************/ // Open() opens the specified volume. GrovelStatus Groveler::open( IN const TCHAR *drive_name, IN const TCHAR *drive_letterName, IN BOOL is_log_drive, IN DOUBLE read_report_discard_threshold, IN DWORD min_file_size, IN DWORD min_file_age, IN BOOL allow_compressed_files, IN BOOL allow_encrypted_files, IN BOOL allow_hidden_files, IN BOOL allow_offline_files, IN BOOL allow_temporary_files, IN int previousGrovelAllPathsState, IN DWORD num_excluded_paths, IN const TCHAR **excluded_paths, IN DWORD base_regrovel_interval, IN DWORD max_regrovel_interval) { DWORD threadID; TCHAR fileStr[MAX_PATH+1]; TCHAR listValue[MAX_PATH+1], *strPtr; USN_JOURNAL_DATA usnJournalData; SGNativeListEntry listEntry; DWORDLONG fileID; DWORD sectorsPerCluster, numberOfFreeClusters, totalNumberOfClusters, bufferSize, strLen, i; GrovelStatus openStatus; LONG num; BOOL success; ASSERT(volumeHandle == NULL); ASSERT(grovHandle == NULL); ASSERT(sgDatabase == NULL); ASSERT(databaseName == NULL); ASSERT(numDisallowedIDs == 0); ASSERT(numDisallowedNames == 0); ASSERT(disallowedIDs == NULL); ASSERT(disallowedNames == NULL); ASSERT(grovelStartEvent == NULL); ASSERT(grovelStopEvent == NULL); ASSERT(grovelThread == NULL); ASSERT(inUseFileID1 == NULL); ASSERT(inUseFileID2 == NULL); ASSERT(terminate); ASSERT(!inCompare); ASSERT(!inScan); ASSERT(usnID == UNINITIALIZED_USN); #if 0 while (!IsDebuggerPresent()) Sleep(2000); DebugBreak(); #endif // // Make sure that the filter has run phase 2 initialization if this is // a SIS enabled volume. // is_sis_installed(drive_name); // // Get drive name without trailing slash // int nBufSize = wcslen(drive_name) + 1; //in chars driveName = new TCHAR[nBufSize]; (void)StringCchCopy(driveName, nBufSize, drive_name); TrimTrailingChar(driveName,L'\\'); // // Get drive Letter name without trailing "\" or ":" // nBufSize = wcslen(drive_letterName) + 1; //in chars driveLetterName = new TCHAR[nBufSize]; (void)StringCchCopy(driveLetterName, nBufSize, drive_letterName); TrimTrailingChar(driveLetterName,L'\\'); TrimTrailingChar(driveLetterName,L':'); #ifdef _CRTDBG // Send all reports to STDOUT _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE ); _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR ); _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE ); _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR ); _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE ); _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR ); #endif // Open the volume and the GrovelerFile. The SIS fsctl // functions require that we pass in a handle to GrovelerFile as a means // of proving our "privilege". An access violation is returned if we don't. volumeHandle = CreateFile( driveName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED|FILE_FLAG_BACKUP_SEMANTICS, NULL); if (volumeHandle == INVALID_HANDLE_VALUE) { volumeHandle = NULL; DPRINTF((_T("%s: Can't open volume \"%s\" %lu\n"), driveLetterName, driveName, GetLastError())); close(); return Grovel_error; } (void)StringCbCopy(fileStr,sizeof(fileStr),driveName); (void)StringCbCat(fileStr,sizeof(fileStr),CS_DIR_PATH); (void)StringCbCat(fileStr,sizeof(fileStr),_T("\\")); (void)StringCbCat(fileStr,sizeof(fileStr),GROVELER_FILE_NAME); grovHandle = CreateFile( fileStr, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if (grovHandle == INVALID_HANDLE_VALUE) { grovHandle = NULL; DPRINTF((_T("%s: can't open groveler file \"%s\": %lu\n"), driveLetterName, fileStr, GetLastError())); close(); return Grovel_error; } (void)StringCbCopy(fileStr,sizeof(fileStr),driveName); (void)StringCbCat(fileStr,sizeof(fileStr),_T("\\")); success = GetDiskFreeSpace(fileStr, §orsPerCluster, §orSize, &numberOfFreeClusters, &totalNumberOfClusters); ASSERT(success); ASSERT(SIG_PAGE_SIZE % sectorSize == 0); ASSERT(CMP_PAGE_SIZE % sectorSize == 0); sigReportThreshold = (DWORD)((DOUBLE)SIG_PAGE_SIZE * read_report_discard_threshold); cmpReportThreshold = (DWORD)((DOUBLE)CMP_PAGE_SIZE * read_report_discard_threshold); // // Open this volume's database. If this fails, create a // new database. If that fails, return an error status. // ASSERT(databaseName == NULL); strLen = _tcslen(driveName) + _tcslen(CS_DIR_PATH) + _tcslen(DATABASE_FILE_NAME) + 1; // +1 for '\' databaseName = new TCHAR[strLen+1]; ASSERT(databaseName != NULL); (void)StringCchPrintf(databaseName, (strLen+1), _T("%s%s\\%s"), driveName, CS_DIR_PATH, DATABASE_FILE_NAME); sgDatabase = new SGDatabase(); if (sgDatabase == NULL) { DPRINTF((_T("%s: can't create database object\n"), driveLetterName)); close(); return Grovel_error; } openStatus = Grovel_ok; if (get_usn_log_info(&usnJournalData) != Grovel_ok) { DPRINTF((_T("%s: can't initialize usnID\n"), driveLetterName)); } else { usnID = usnJournalData.UsnJournalID; if (!sgDatabase->Open(driveLetterName,databaseName, is_log_drive)) { DPRINTF((_T("%s: can't open database \"%s\"\n"), driveLetterName, databaseName)); } else { listValue[0] = _T('\0'); listEntry.name = LAST_USN_NAME; listEntry.value = listValue; if (sgDatabase->ListRead(&listEntry) <= 0 || _stscanf(listValue, _T("%I64x"), &lastUSN) != 1 || lastUSN == UNINITIALIZED_USN) { DPRINTF((_T("%s: can't get last USN value\n"), driveLetterName)); } else { DWORDLONG storedUsnID; listValue[0] = _T('\0'); listEntry.name = USN_ID_NAME; listEntry.value = listValue; if (sgDatabase->ListRead(&listEntry) <= 0 || _stscanf(listValue, _T("%I64x"), &storedUsnID) != 1 || storedUsnID != usnID) { DPRINTF((_T("%s: can't get USN ID value from database\n"), driveLetterName)); } else { // // See if any uncommited operations // num = sgDatabase->StackCount(); if (0 == num) { // // See if the RIS state changed. If not, we can // continue. If so, reset grovel state so we will // rescan the volume. // if (GrovelAllPaths == previousGrovelAllPathsState) { goto OpenedDatabase; } else { DPRINTF((L"GrovelAllPaths state changed, rescanning the volume\n")); } } } } } } // Set abortGroveling to block the worker thread, and set lastUSN to block extract_log // until scan_volume starts. abortGroveling = TRUE; lastUSN = usnID = UNINITIALIZED_USN; openStatus = Grovel_new; OpenedDatabase: // Create the disallowed directories list. if (num_excluded_paths == 0) { disallowedIDs = NULL; disallowedNames = NULL; } else { disallowedIDs = new DWORDLONG[num_excluded_paths]; disallowedNames = new TCHAR * [num_excluded_paths]; ASSERT(disallowedIDs != NULL); ASSERT(disallowedNames != NULL); for (i = 0; i < num_excluded_paths; i++) { ASSERT(excluded_paths[i] != NULL); if (excluded_paths[i][0] == _T('\\')) { strLen = _tcslen(excluded_paths[i]); while (strLen > 1 && excluded_paths[i][strLen-1] == _T('\\')) strLen--; strPtr = new TCHAR[strLen+1]; ASSERT(strPtr != NULL); disallowedNames[numDisallowedNames++] = strPtr; _tcsncpy(strPtr, excluded_paths[i], strLen); strPtr[strLen] = _T('\0'); fileID = GetFileID(drive_name,strPtr); if (fileID != 0) { disallowedIDs[numDisallowedIDs++] = fileID; } TPRINTF((L"%s: Exclude path=\"%s\", ID=%04I64x.%012I64x\n", driveLetterName, strPtr, ((fileID >> 48) & 0xffff), (fileID & 0xffffffffffff))); } } if (numDisallowedNames == 0) { delete disallowedNames; disallowedNames = NULL; } else if (numDisallowedNames > 1) qsort( disallowedNames, numDisallowedNames, sizeof(TCHAR *), qsStringCompare); if (numDisallowedIDs == 0) { delete disallowedIDs; disallowedIDs = NULL; } else if (numDisallowedIDs > 1) qsort( disallowedIDs, numDisallowedIDs, sizeof(DWORDLONG), FileIDCompare); } // // Set the remaining class values. // // minFileAge is expressed in 10^-7 seconds, min_file_age in milliseconds. // minFileSize = min_file_size > MIN_FILE_SIZE ? min_file_size : MIN_FILE_SIZE; minFileAge = min_file_age * 10000; grovelInterval = minFileAge > MIN_GROVEL_INTERVAL ? minFileAge : MIN_GROVEL_INTERVAL; disallowedAttributes = FILE_ATTRIBUTE_DIRECTORY | (allow_compressed_files ? 0 : FILE_ATTRIBUTE_COMPRESSED) | (allow_encrypted_files ? 0 : FILE_ATTRIBUTE_ENCRYPTED) | (allow_hidden_files ? 0 : FILE_ATTRIBUTE_HIDDEN) | (allow_offline_files ? 0 : FILE_ATTRIBUTE_OFFLINE) | (allow_temporary_files ? 0 : FILE_ATTRIBUTE_TEMPORARY); // // Create the events used to handshake with the worker thread. // if ((grovelStartEvent = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL || (grovelStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL) { DPRINTF((_T("%s: unable to create events: %lu\n"), driveLetterName, GetLastError())); close(); return Grovel_error; } // // Create the worker thread, then wait for it to set // the grovelStop event to announce its existence. // terminate = FALSE; grovelThread = CreateThread( NULL, 0, WorkerThread, (VOID *)this, 0, &threadID); if (grovelThread == NULL) { DPRINTF((_T("%s: can't create the worker thread: %lu\n"), driveLetterName, GetLastError())); close(); return Grovel_error; } WaitForEvent(grovelStopEvent); if (grovelStatus == Grovel_error) { grovelThread = NULL; close(); return Grovel_error; } ASSERT(grovelStatus == Grovel_ok); return openStatus; } /*****************************************************************************/ GrovelStatus Groveler::close() { DWORD i; LONG num; BOOL success; // If active, signal the worker thread to stop, // then wait for it to acknowledge. terminate = TRUE; if (grovelThread != NULL) { ASSERT(grovelStartEvent != NULL); ASSERT(grovelStopEvent != NULL); timeAllotted = INFINITE; do { ASSERT(IsReset(grovelStartEvent)); success = SetEvent(grovelStartEvent); ASSERT_ERROR(success); WaitForEvent(grovelStopEvent); } while (grovelStatus != Grovel_error); grovelThread = NULL; } inCompare = FALSE; inScan = FALSE; usnID = UNINITIALIZED_USN; ASSERT(inUseFileID1 == NULL); ASSERT(inUseFileID2 == NULL); // Close the events. if (grovelStartEvent != NULL) { success = CloseHandle(grovelStartEvent); ASSERT_ERROR(success); grovelStartEvent = NULL; } if (grovelStopEvent != NULL) { success = CloseHandle(grovelStopEvent); ASSERT_ERROR(success); grovelStopEvent = NULL; } // If the volume or GrovelerFile are open, close them. if (volumeHandle != NULL) { success = CloseHandle(volumeHandle); ASSERT_ERROR(success); volumeHandle = NULL; } if (grovHandle != NULL) { success = CloseHandle(grovHandle); ASSERT_ERROR(success); grovHandle = NULL; } // Close this volume's database. if (sgDatabase != NULL) { delete sgDatabase; sgDatabase = NULL; } if (databaseName != NULL) { delete[] databaseName; databaseName = NULL; } // Deallocate the disallowed directory lists. if (numDisallowedNames == 0) { ASSERT(disallowedNames == NULL); } else { for (i = 0; i < numDisallowedNames; i++) delete (disallowedNames[i]); delete disallowedNames; disallowedNames = NULL; numDisallowedNames = 0; } if (numDisallowedIDs == 0) { ASSERT(disallowedIDs == NULL); } else { delete disallowedIDs; disallowedIDs = NULL; numDisallowedIDs = 0; } if (driveName != NULL) { delete[] driveName; driveName = NULL; } if (driveLetterName != NULL) { delete[] driveLetterName; driveLetterName = NULL; } return Grovel_ok; } /*****************************************************************************/ // grovel() is the front-end method for controlling the groveling // process on each NTFS volume. The groveling process itself is // implemented in the Worker() method. grovel() starts the groveling // process by setting the grovelStart event. Worker() signals back to // grovel() that it is finished or has used up its time allocation by // setting the grovelStop event, which causes grovel() to return. GrovelStatus Groveler::grovel( IN DWORD time_allotted, OUT DWORD *hash_read_ops, OUT DWORD *hash_read_time, OUT DWORD *count_of_files_hashed, OUT DWORDLONG *bytes_of_files_hashed, OUT DWORD *compare_read_ops, OUT DWORD *compare_read_time, OUT DWORD *count_of_files_compared, OUT DWORDLONG *bytes_of_files_compared, OUT DWORD *count_of_files_matching, OUT DWORDLONG *bytes_of_files_matching, OUT DWORD *merge_time, OUT DWORD *count_of_files_merged, OUT DWORDLONG *bytes_of_files_merged, OUT DWORD *count_of_files_enqueued, OUT DWORD *count_of_files_dequeued) { DWORD timeConsumed; BOOL success; ASSERT(volumeHandle != NULL); hashCount = 0; hashReadCount = 0; hashReadTime = 0; hashBytes = 0; compareCount = 0; compareReadCount = 0; compareReadTime = 0; compareBytes = 0; matchCount = 0; matchBytes = 0; mergeCount = 0; mergeTime = 0; mergeBytes = 0; numFilesEnqueued = 0; numFilesDequeued = 0; #ifdef DEBUG_UNTHROTTLED timeAllotted = INFINITE; #else timeAllotted = time_allotted; #endif startAllottedTime = GetTickCount(); ASSERT(IsReset(grovelStartEvent)); success = SetEvent(grovelStartEvent); ASSERT_ERROR(success); WaitForEvent(grovelStopEvent); timeConsumed = GetTickCount() - startAllottedTime; // Return the performance statistics. if (count_of_files_hashed != NULL) *count_of_files_hashed = hashCount; if (hash_read_ops != NULL) *hash_read_ops = hashReadCount; if (hash_read_time != NULL) *hash_read_time = hashReadTime; if (bytes_of_files_hashed != NULL) *bytes_of_files_hashed = hashBytes; if (count_of_files_compared != NULL) *count_of_files_compared = compareCount; if (compare_read_ops != NULL) *compare_read_ops = compareReadCount; if (compare_read_time != NULL) *compare_read_time = compareReadTime; if (bytes_of_files_compared != NULL) *bytes_of_files_compared = compareBytes; if (count_of_files_matching != NULL) *count_of_files_matching = matchCount; if (bytes_of_files_matching != NULL) *bytes_of_files_matching = matchBytes; if (count_of_files_merged != NULL) *count_of_files_merged = mergeCount; if (merge_time != NULL) *merge_time = mergeTime; if (bytes_of_files_merged != NULL) *bytes_of_files_merged = mergeBytes; if (count_of_files_enqueued != NULL) *count_of_files_enqueued = numFilesEnqueued; if (count_of_files_dequeued != NULL) *count_of_files_dequeued = numFilesDequeued; TRACE_PRINTF(TC_groveler, 2, (_T("%s Count Reads Bytes Time (sec)\n"), driveLetterName)); TRACE_PRINTF(TC_groveler, 2, (_T(" Hashings: %7lu %7lu %7I64u %4lu.%03lu Time: %5lu.%03lu sec\n"), hashCount, hashReadCount, hashBytes, hashReadTime / 1000, hashReadTime % 1000, timeConsumed / 1000, timeConsumed % 1000)); TRACE_PRINTF(TC_groveler, 2, (_T(" Compares: %7lu %7lu %7I64u %4lu.%03lu Enqueues: %lu\n"), compareCount, compareReadCount, compareBytes, compareReadTime / 1000, compareReadTime % 1000, numFilesEnqueued)); TRACE_PRINTF(TC_groveler, 2, (_T(" Matches: %7lu %7I64u Dequeues: %lu\n"), matchCount, matchBytes, numFilesDequeued)); TRACE_PRINTF(TC_groveler, 2, (_T(" Merges: %7lu %7I64u %4lu.%03lu\n"), mergeCount, mergeBytes, mergeTime / 1000, mergeTime % 1000)); return grovelStatus; } /*****************************************************************************/ // count_of_files_in_queue() returns a count of the number // of files in this volume's queue waiting to be groveled. DWORD Groveler::count_of_files_in_queue() const { LONG numEntries; ASSERT(volumeHandle != NULL); ASSERT(sgDatabase != NULL); numEntries = sgDatabase->QueueCount(); if (numEntries < 0) return 0; TPRINTF((_T("%s: count_of_files_in_queue=%ld\n"), driveLetterName, numEntries)); return (DWORD)numEntries; } /*****************************************************************************/ // count_of_files_to_compare() returns 1 if two files are ready to be // compared or are in the process of being compared, and 0 otherwise. DWORD Groveler::count_of_files_to_compare() const { DWORD numCompareFiles; ASSERT(volumeHandle != NULL); ASSERT(sgDatabase != NULL); numCompareFiles = inCompare ? 1 : 0; TPRINTF((_T("%s: count_of_files_to_compare=%lu\n"), driveLetterName, numCompareFiles)); return numCompareFiles; } /*****************************************************************************/ // time_to_first_file_ready() returns the time in milliseconds until // the first entry in the queue is ready to be groveled. If the queue // is empty, it returns INFINITE. If an error occurs, it returns 0. DWORD Groveler::time_to_first_file_ready() const { SGNativeQueueEntry queueEntry; DWORDLONG currentTime; DWORD earliestTime; LONG num; ASSERT(volumeHandle != NULL); ASSERT(sgDatabase != NULL); queueEntry.fileName = NULL; num = sgDatabase->QueueGetFirst(&queueEntry); if (num < 0) return 0; if (num == 0) earliestTime = INFINITE; else { ASSERT(num == 1); currentTime = GetTime(); earliestTime = queueEntry.readyTime > currentTime ? (DWORD)((queueEntry.readyTime - currentTime) / 10000) : 0; } TPRINTF((_T("%s: time_to_first_file_ready=%lu.%03lu\n"), driveLetterName, earliestTime / 1000, earliestTime % 1000)); return earliestTime; }