// Copyright (c) 1996-1999 Microsoft Corporation //+------------------------------------------------------------------------- // // Microsoft Windows // // File: log.cxx // // Contents: Implementation of Tracking (Workstation) Service log of moves. // // Classes: CLog // // Functions: // // Notes: The log is composed of a header and a linked-list of move // notification entries. This structure is provided by the // CLogFile class. // //-------------------------------------------------------------------------- #include "pch.cxx" #pragma hdrstop #include "trkwks.hxx" //+---------------------------------------------------------------------------- // // Method: Initialize // // Synopsis: Initialize a CLog object. // // Arguments: [pLogCallback] (in) // A PLogCallback object, which we'll call when we have new // data. // [pcTrkWksConfiguration] (in) // Configuration parameters for the log. // [pcLogFile] (in) // The object representing the log file. // // Returns: None // //+---------------------------------------------------------------------------- void CLog::Initialize( PLogCallback *pLogCallback, const CTrkWksConfiguration *pcTrkWksConfiguration, CLogFile *pcLogFile ) { LogInfo loginfo; // Save the inputs TrkAssert( NULL != pcLogFile || NULL != _pcLogFile ); if( NULL != pcLogFile ) _pcLogFile = pcLogFile; TrkAssert( NULL != pcTrkWksConfiguration || NULL != _pcTrkWksConfiguration ); if( NULL != pcTrkWksConfiguration ) _pcTrkWksConfiguration = pcTrkWksConfiguration; TrkAssert( NULL != pLogCallback || NULL != _pLogCallback ); if( NULL != pLogCallback ) _pLogCallback = pLogCallback; // Read the log info from the log header. _pcLogFile->ReadExtendedHeader( CLOG_LOGINFO_START, &loginfo, CLOG_LOGINFO_LENGTH ); // If the log hadn't been shut down properly, it's been fixed by now, but // we can't trust the loginfo we just read from the header. We also // can't trust it if it doesn't make sense. So if for some reason we // can't trust it, we'll recalculate it (this can be slow, though). if( !_pcLogFile->IsShutdown() || loginfo.ilogStart == loginfo.ilogEnd ) { _fDirty = TRUE; loginfo = QueryLogInfo(); } // Save the now-good information. _loginfo = loginfo; } // CLog::Initialize() //+---------------------------------------------------------------------------- // // Method: QueryLogInfo // // Synopsis: Read the log entries and determine the indices and sequence // numbers. // // Arguments: None // // Returns: A LogInfo structure // //+---------------------------------------------------------------------------- LogInfo CLog::QueryLogInfo() { SequenceNumber seqMin, seqMax; ULONG cEntries; LogIndex ilogMin, ilogMax, ilogEntry; LogInfo loginfo; LogMoveNotification lmn; BOOL fLogEmpty = TRUE; LogEntryHeader entryheader; TrkLog(( TRKDBG_LOG, TEXT("Reading log to determine correct indices") )); // ------------ // Scan the log // ------------ seqMin = 0; seqMax = 0; cEntries = _pcLogFile->NumEntriesInFile(); ilogMin = 0; ilogMax = cEntries - 1; // Scan the log and look at the sequence numbers to find // the start and end indices. for( ilogEntry = 0; ilogEntry < cEntries; ilogEntry++ ) { _pcLogFile->ReadMoveNotification( ilogEntry, &lmn ); if( LE_TYPE_MOVE == lmn.type ) { SequenceNumber seq = lmn.seq; // If this is the first move notification that we've // found, then it is currently both the min and the max. if( fLogEmpty ) { fLogEmpty = FALSE; seqMin = seqMax = seq; ilogMin = ilogMax = seq; } // If this isn't the first entry we've found, then see // if it is a new min or max. else { if( seq <= seqMin ) { seqMin = seq; ilogMin = ilogEntry; } else if( seq >= seqMax ) { seqMax = seq; ilogMax = ilogEntry; } } } // if( LE_TYPE_MOVE == _pcLogFile->ReadMoveNotification( ilogEntry )->type ) } // for( ilogEntry = 0; ilogEntry < cEntries; ilogEntry++ ) // ------------------------------- // Determine the log indices, etc. // ------------------------------- // Were there any entries in the log? if( fLogEmpty ) { // No, the log is empty. loginfo.ilogStart = loginfo.ilogWrite = 0; loginfo.ilogLast = loginfo.ilogEnd = cEntries - 1; } else { // Yes, the log is non-empty. // Point the start index to the oldest move in the log. loginfo.ilogStart = ilogMin; // Point the last index to the oldest move in the log, // and point the write index to the entry after that // (which is the first available entry). loginfo.ilogLast = loginfo.ilogWrite = ilogMax; _pcLogFile->AdjustLogIndex( &loginfo.ilogWrite, 1 ); // The write & start indices should only be the same // in an empty log. We know we're not empty at this point, // so if they're the same, then the start index must have // actually advanced (otherwise, the write index wouldn't be // allowed to be here). So we advance the start index. if( loginfo.ilogWrite == loginfo.ilogStart ) { _pcLogFile->AdjustLogIndex( &loginfo.ilogStart, 1 ); } } // if( fLogEmpty ) ... else // The end if the log is just before the start in the circular list. loginfo.ilogEnd = loginfo.ilogStart; _pcLogFile->AdjustLogIndex( &loginfo.ilogEnd, -1 ); // The read index and next available // sequence number are stored in the last entry header. entryheader = _pcLogFile->ReadEntryHeader( loginfo.ilogLast ); loginfo.ilogRead = entryheader.ilogRead; loginfo.seqNext = entryheader.seq; // The sequence number of the entry last read is one below the sequence number // of the entry currently at the read pointer. If everything is read // (the read pointer is beyond the last entry) or if the entry at the // read pointer is invalid, then we'll assume that the last read seq // number is seqNext-1. _pcLogFile->ReadMoveNotification( loginfo.ilogRead, &lmn ); loginfo.seqLastRead = ( loginfo.ilogWrite != loginfo.ilogRead && LE_TYPE_MOVE == lmn.type ) ? lmn.seq - 1 : loginfo.seqNext-1; TrkAssert( seqMax + 1 == loginfo.seqNext || 0 == loginfo.seqNext ); TrkAssert( loginfo.seqLastRead < loginfo.seqNext ); return( loginfo ); } // CLog::QueryLogInfo() //+---------------------------------------------------------------------------- // // Method: GenerateDefaultLogInfo // // Synopsis: Calculates the default _loginfo structure, based only on // the last index. This requires no calls to CLogFile. // // Arguments: [ilogEnd] (in) // The index of the last entry in the logfile. // // Returns: None // //+---------------------------------------------------------------------------- void CLog::GenerateDefaultLogInfo( LogIndex ilogEnd ) { SetDirty( TRUE ); // Must be called before changing _loginfo _loginfo.ilogStart = _loginfo.ilogWrite = _loginfo.ilogRead = 0; _loginfo.ilogLast = _loginfo.ilogEnd = ilogEnd; _loginfo.seqNext = 0; _loginfo.seqLastRead = _loginfo.seqNext - 1; } //+---------------------------------------------------------------------------- // // Method: Flush // // Synopsis: Write the _loginfo structure to the CLogFile. // // Arguments: None // // Returns: None // //+---------------------------------------------------------------------------- void CLog::Flush( ) { if( _fDirty ) _pcLogFile->WriteExtendedHeader( CLOG_LOGINFO_START, &_loginfo, CLOG_LOGINFO_LENGTH ); SetDirty( FALSE ); } //+---------------------------------------------------------------------------- // // Method: ExpandLog // // Synopsis: Grow the log file, initialize the new entries, and update // our indices. We determine how much to grow based on // configuration parameters in CTrkWksConfiguration. // // Arguments: None. // // Returns: None. // //+---------------------------------------------------------------------------- void CLog::ExpandLog() { TrkAssert( !_pcLogFile->IsMaxSize() ); TrkAssert( IsFull() ); SetDirty( TRUE ); // Must be called before changing _loginfo // Grow the file, initialize the new log entries, and link the new // entries into the existing linked list. We only need to tell // the CLogFile where the start of the circular linked-list is. _pcLogFile->Expand( _loginfo.ilogStart ); // Update the end pointer. _loginfo.ilogEnd = _loginfo.ilogStart; _pcLogFile->AdjustLogIndex( &_loginfo.ilogEnd, -1 ); } // CLog::Expand //+---------------------------------------------------------------------------- // // Method: Read // // Synopsis: Read zero or more entries from the log, starting at the // Read index. Read until we reach the end of the data in // the log, or until we've read as many as the caller // requested. // // Note that we don't update the read index after this read, // the caller must call Seek to accomplish this. This was done // so that if the caller encountered an error after the Read, // the log would still be unchanged for a retry. // // Arguments: [pNotifications] (in/out) // Receives the move notification records. // [pseqFirst] (out) // The sequence number of the first notification returned. // [pcRead] (in/out) // (in) the number of notifications desired // (out) the number of notifications actually read // If the number read is less than the number requested, // the caller may assume that there are no more entries // to read. // // Returns: None // //+---------------------------------------------------------------------------- void CLog::Read(CObjId rgobjidCurrent[], CDomainRelativeObjId rgdroidBirth[], CDomainRelativeObjId rgdroidNew[], SequenceNumber *pseqFirst, IN OUT ULONG *pcRead) { // -------------- // Initialization // -------------- LogIndex ilogEntry; ULONG cRead = 0; ULONG iRead = 0; SequenceNumber seqExpected = 0; ilogEntry = _loginfo.ilogRead; // ---------------- // Read the entries // ---------------- // We can NOOP if the call request no entries, or if there // are no entries in the log, or if all the entries have // been read already. if( *pcRead != 0 && !IsEmpty() && !IsRead() ) { LogMoveNotification lmn; // There are entries which we can read. // Save the sequence number of the first entry that // we'll return to the caller. _pcLogFile->ReadMoveNotification( ilogEntry, &lmn ); *pseqFirst = lmn.seq; seqExpected = lmn.seq; // Read the entries from the log in order, validating // the sequence numbers as we go. do { // Copy the move information into the caller's buffer. // ReadMoveNotification doesn't make the CLogFile dirty. _pcLogFile->ReadMoveNotification( ilogEntry, &lmn ); TrkAssert( seqExpected == lmn.seq ); if( seqExpected != lmn.seq ) { TrkLog(( TRKDBG_ERROR, TEXT("Invalid sequence numbers reading log (%d, %d)"), seqExpected, lmn.seq )); TrkRaiseException( TRK_E_CORRUPT_LOG ); } seqExpected++; rgobjidCurrent[iRead] = lmn.objidCurrent; rgdroidNew[iRead] = lmn.droidNew; rgdroidBirth[iRead] = lmn.droidBirth; cRead++; iRead++; // Move on to the next entry. _pcLogFile->AdjustLogIndex( &ilogEntry, 1 ); // Continue as long as there's still room in the caller's buffer // and we haven't reached the last entry. } while ( cRead < *pcRead && ilogEntry != _loginfo.ilogWrite ); } *pcRead = cRead; } // CLog:Read() //+---------------------------------------------------------------------------- // // CLog::DoSearch // // This is a private worker method that searches the log, either for a // sequence number, or an object ID (which to use is determined by the // fSearchUsingSeq parameter). // // The log entry data and index are returned. // //+---------------------------------------------------------------------------- // NOTE! *piFound is not modified if Search returns FALSE BOOL CLog::DoSearch( BOOL fSearchUsingSeq, SequenceNumber seqSearch, // Use this if fSearchUsingSeq const CObjId &objidCurrent, // Use this if !fSearchUsingSeq ULONG *piFound, CDomainRelativeObjId *pdroidNew, CMachineId *pmcidNew, CDomainRelativeObjId *pdroidBirth ) { BOOL fFound = FALSE; BOOL fFirstPass = TRUE; SequenceNumber seqPrevious = 0; #if DBG LONG l = GetTickCount(); #endif // Only bother to look if there's entries in the log. if (!IsEmpty()) { // Determine the max entries in the log so that we can // detect if we're in an infinite loop. ULONG cEntriesMax = _pcLogFile->NumEntriesInFile(); ULONG cEntryCurrent = 0; LogIndex ilogSearch = _loginfo.ilogWrite; // Search from the end, until we find what we're looking for, // or we reach the beginning of the log. // It's important that we search backwards because of tunneling. // Here's the scenario ... An object is moved from machine A to // B, quickly back to A, and then to C. Since it reappeared on A // quickly after it first disappeared, tunneling will give it the // same Object ID that it had before. So when it moves to C, // we end up with two entries in the log for the object. We want // to search backwards so that we see the move to C, not the move // to B. while( !fFound && ilogSearch != _loginfo.ilogStart ) { // Check to see if we're in an infinite loop. if( ++cEntryCurrent > cEntriesMax ) { TrkLog((TRKDBG_ERROR, TEXT("Corrupt log file: cycle found during search"))); TrkRaiseException( TRK_E_CORRUPT_LOG ); } // Read the previous entry. _pcLogFile->AdjustLogIndex( &ilogSearch, -1 ); LogMoveNotification lmnSearch; _pcLogFile->ReadMoveNotification( ilogSearch, &lmnSearch ); // If this isn't a move entry, then there's nothing left to search. if( LE_TYPE_MOVE != lmnSearch.type ) goto Exit; // Or, if this isn't the first pass, ensure that the sequence numbers // are consequtive. else if( !fFirstPass ) { fFirstPass = FALSE; TrkAssert( seqPrevious - 1 == lmnSearch.seq ); if( seqPrevious - 1 != lmnSearch.seq ) { TrkLog(( TRKDBG_ERROR, TEXT("Corrupt log file: non-consequtive sequence numbers (%d %d)"), seqPrevious, lmnSearch.seq )); TrkRaiseException( TRK_E_CORRUPT_LOG ); } } // Is this the entry we're looking for? if( fSearchUsingSeq && seqSearch == lmnSearch.seq || !fSearchUsingSeq && objidCurrent == lmnSearch.objidCurrent ) { if( NULL != piFound ) *piFound = ilogSearch; if( NULL != pdroidNew ) { *pdroidNew = lmnSearch.droidNew; *pmcidNew = lmnSearch.mcidNew; *pdroidBirth = lmnSearch.droidBirth; } fFound = TRUE; } } // while( !fFound && ilogSearch != _loginfo.ilogStart ) } // if (!IsEmpty()) Exit: return( fFound ); } // CLog::DoSearch //+---------------------------------------------------------------------------- // // Method: Search // // Synopsis: Search the log for an Object ID. Once found, return that // entry's LinkData and BirthID. // // Arguments: [droidCurrent] (in) // The ObjectID for which to search. // [pdroidNew] (out) // The entry's LinkData // [pdroidBirth] (out) // The entry's Birth ID. // // Returns: None // //+---------------------------------------------------------------------------- BOOL CLog::Search( const CObjId &objidCurrent, CDomainRelativeObjId *pdroidNew, CMachineId *pmcidNew, CDomainRelativeObjId *pdroidBirth ) { ULONG iFound; return DoSearch( FALSE, // => Use objidCurrent 0, // Therefore, we don't need a seq number objidCurrent, &iFound, pdroidNew, pmcidNew, pdroidBirth ); } // CLog::Search( CDomainRelativeObjId& ... //+---------------------------------------------------------------------------- // // Method: CLog::Search // // Synopsis: Search the log for the entry with a particular sequence // number. // // Arguments: [seqSearch] (in) // The sequence number for which to search. // [piFound] (out) // The index with this sequence number (if found). // // Returns: [BOOL] // TRUE if found, FALSE otherwise. // //+---------------------------------------------------------------------------- BOOL CLog::Search( SequenceNumber seqSearch, ULONG *piFound ) { CObjId oidNull; return DoSearch( TRUE, // => Use seqSearch seqSearch, oidNull, // We don't need to pass an objid piFound, // And we don't need out-droids & mcid. NULL, NULL, NULL ); } // CLog::Search( SequenceNumber ... //+---------------------------------------------------------------------------- // // Method: Append // // Synopsis: Add a move notification to the log. If the log is full, // either overwrite an old entry, or grow the log. // // Arguments: [droidCurrent] (in) // The link information of the file which was moved. // [droidNew] (in) // The link information of the new file. // [droidBirth] (in) // The Birth ID of the file. // // Returns: None // //+---------------------------------------------------------------------------- // Perf optimization: Tell the caller if the log is now full. The service can then lazily // expand it, hopefully before the next move occurs. void CLog::Append(const CVolumeId &volidCurrent, const CObjId &objidCurrent, const CDomainRelativeObjId &droidNew, const CMachineId &mcidNew, const CDomainRelativeObjId &droidBirth) { LogMoveNotification lmnWrite; CFILETIME cftNow; // Defaults to current UTC BOOL fAdvanceStart = FALSE; LogEntryHeader entryheader; LogInfo loginfoZero; // ----------------- // Handle a Full Log // ----------------- if( IsFull() ) { // Is the log already maxed? If so, we wrap. if( _pcLogFile->IsMaxSize() ) { fAdvanceStart = TRUE; TrkLog(( TRKDBG_VOLUME, TEXT("Wrapping log") )); } // Otherwise, we'll handle it by growing the log file. else ExpandLog(); } // ------------------------- // Write the data to the log // ------------------------- // Before anything else, we must mark ourselves dirty. If the logfile is // currently in the ProperShutdown state, this SetDirty call will take it // out of that state and do a flush. SetDirty( TRUE ); // Mark our loginfo cache in the header as invalid, // in case we get pre-empted. memset( &loginfoZero, 0, sizeof(loginfoZero) ); _pcLogFile->WriteExtendedHeader( CLOG_LOGINFO_START, &loginfoZero, CLOG_LOGINFO_LENGTH ); // Collect the move-notification information memset( &lmnWrite, 0, sizeof(lmnWrite) ); lmnWrite.seq = _loginfo.seqNext; lmnWrite.type = LE_TYPE_MOVE; lmnWrite.objidCurrent = objidCurrent; lmnWrite.droidNew = droidNew; lmnWrite.mcidNew = mcidNew; lmnWrite.droidBirth = droidBirth; lmnWrite.DateWritten = TrkTimeUnits( cftNow ); // Collect the entry header information memset( &entryheader, 0, sizeof(entryheader) ); entryheader.ilogRead = _loginfo.ilogRead; entryheader.seq = _loginfo.seqNext + 1; // Reflect that we'll increment after the write // Write everything to the log (this will do a flush). If this fails, it will raise. _pcLogFile->WriteMoveNotification( _loginfo.ilogWrite, lmnWrite, entryheader ); // Update the sequence number and last & write indices now that we // know the write was successful (all the way to the disk). _loginfo.seqNext++; _loginfo.ilogLast = _loginfo.ilogWrite; _pcLogFile->AdjustLogIndex( &_loginfo.ilogWrite, 1 ); // Do we need to advance the start pointer? // We save this for the end, because it may cause us to access // the disk. if( fAdvanceStart ) { // We're about to advance the start index, and thus effectively // lose an entry. If the read index points to the same place, // then we should advance it as well. if( _loginfo.ilogStart == _loginfo.ilogRead ) _pcLogFile->AdjustLogIndex( &_loginfo.ilogRead, 1 ); // Advance the start/end indices. _pcLogFile->AdjustLogIndex( &_loginfo.ilogEnd, 1 ); _pcLogFile->AdjustLogIndex( &_loginfo.ilogStart, 1 ); } TrkLog(( TRKDBG_VOLUME, TEXT("Appended %s to log (seq=%d)"), (const TCHAR*)CDebugString(objidCurrent), lmnWrite.seq )); // Notify the callback object that there is data available. // Note: This must be the last operation of this method. _pLogCallback->OnEntriesAvailable(); } //+---------------------------------------------------------------------------- // // Method: Seek( SequenceNumber ... // // Synopsis: Moves the Read index to a the log entry with the specified // sequence number. If the seq number doesn't exist, we back // up to the start of the log. // // If this seek causes us to back up the Read index, we notify // the PLogCallback object, since we now have data available // to read. // // Arguments: [seqSeek] (in) // The sequence number to which to seek. // // Returns: [BOOL] // TRUE if the sequence number was found, FALSE otherwise. // //+---------------------------------------------------------------------------- BOOL CLog::Seek( const SequenceNumber &seqSeek ) { BOOL fFound = FALSE; SequenceNumber seqReadOriginal, seqReadNew; LogMoveNotification lmn; LogIndex ilogSearch = 0; // Are we seeking to the end of the log? if( seqSeek == _loginfo.seqNext ) { // We found what we're looking for. fFound = TRUE; // Are we already at seqNext? if( !IsRead() ) { // No, update the read index. SetDirty( TRUE ); _loginfo.ilogRead = _loginfo.ilogWrite; } goto Exit; } // If the log is empty, then there's nothing we need do. if( IsEmpty() ) goto Exit; // Or, if the caller wishes to seek beyond the end of our log, then // again there's nothing to do. This could be the case, for example, // if the log has been restored. if( seqSeek >= _loginfo.seqNext ) goto Exit; // Keep track of the current seq number at the read pointer, so that // we can later tell if it's necessary to notify the client that // there is "new" data. if( IsRead() ) { seqReadOriginal = _loginfo.seqNext; } else { _pcLogFile->ReadMoveNotification( _loginfo.ilogRead, &lmn ); seqReadOriginal = lmn.seq; } // If this seq number is in the log, set the Read index // to it. Otherwise set it to the oldest entry in the // log (that's the best we can do). SetDirty( TRUE ); // Must be called before changing _loginfo if( fFound = Search( seqSeek, &ilogSearch )) _loginfo.ilogRead = ilogSearch; else _loginfo.ilogRead = _loginfo.ilogStart; // Calculate the sequence number of the entry now at the read pointer, and // use it to cache the seq number of the last-read entry (recall that // the read pointer points to the next entry to be read, not the // last entry read). if( IsRead() ) { seqReadNew = _loginfo.seqNext; } else { _pcLogFile->ReadMoveNotification( _loginfo.ilogRead, &lmn ); seqReadNew = lmn.seq; } _loginfo.seqLastRead = seqReadNew - 1; // We already set _fDirty // Update the entry headers with the new read pointer. WriteEntryHeader(); // If we've backed up the read pointer, notify the registered callback. if ( seqReadOriginal > seqReadNew ) { // Note: This must be the last operation of this method. _pLogCallback->OnEntriesAvailable(); } // ---- // Exit // ---- Exit: return( fFound ); } // CLog::Seek( SequenceNumber ... //+---------------------------------------------------------------------------- // // Method: Seek( origin ... // // Synopsis: Move the read pointer relative to an origin (begin, current, end). // // There are two differences between a CLog seek and a file seek: // - If you seek from the beginning (SEEK_SET), and seek beyond the // end of the log, the pointer is wrapped, rather than growing // the log. // - If you seek from the current location (SEEK_CUR), and seek // beyond the end of the log, the log is not grown, and there // is no wrap, the index simply stops there (either at _loginfo.ilogWrite // or _loginfo.ilogStart). // // Arguments: [origin] // Must be either SEEK_SET or SEEK_CUR (there is currently no // support for SEEK_END). // [iSeek] // The amount to move relative to the origin. // // Returns: None // //+---------------------------------------------------------------------------- void CLog::Seek( int origin, int iSeek ) { SequenceNumber seqReadOriginal = 0, seqReadNew = 0; LogMoveNotification lmn; // Early exit if there's nothing to do if( IsEmpty() ) goto Exit; // Keep track of where we are now, so that we can determine if // we've gone overall backwards or forwards. if( IsRead() ) { seqReadOriginal = _loginfo.seqNext; } else { _pcLogFile->ReadMoveNotification( _loginfo.ilogRead, &lmn ); seqReadOriginal = lmn.seq; } // Seek based on the origin. switch( origin ) { case SEEK_SET: { // Advance from the start index. LogIndex ilogRead = _loginfo.ilogStart; _pcLogFile->AdjustLogIndex( &ilogRead, iSeek ); SetDirty( TRUE ); // Must be called before changing _loginfo _loginfo.ilogRead = ilogRead; } break; case SEEK_CUR: { // Advance or retreat from the current read index. LogIndex ilogRead = _loginfo.ilogRead; _pcLogFile->AdjustLogIndex( &ilogRead, iSeek, CLogFile::ADJUST_WITHIN_LIMIT, iSeek >= 0 ? _loginfo.ilogWrite : _loginfo.ilogStart ); SetDirty( TRUE ); // Must be called before changing _loginfo _loginfo.ilogRead = ilogRead; } break; default: TrkAssert( FALSE && TEXT("Unexpected origin in CLog::Seek") ); break; } // Calculate the sequence number of the entry at the read pointer, and // use it to store the seq number of the last-read entry (recall that // the read pointer points to the next entry to be read, not the // last entry read). if( IsRead() ) { seqReadNew = _loginfo.seqNext; } else { _pcLogFile->ReadMoveNotification( _loginfo.ilogRead, &lmn ); seqReadNew = lmn.seq; } SetDirty( TRUE ); // Must be called before changing _loginfo _loginfo.seqLastRead = seqReadNew - 1; // Update the entry headers with the new read pointer. WriteEntryHeader(); // If we've backed up the read pointer, notify the registered callback. if ( seqReadOriginal > seqReadNew ) { // Note: This must be the last operation of this method. _pLogCallback->OnEntriesAvailable(); } // ---- // Exit // ---- Exit: return; } // CLog::Seek( origin ... //+---------------------------------------------------------------------------- // // Method: CLog::IsRead( LogIndex ) (private) // // Synopsis: Determine if the specified entry has been read. See also // the IsRead(void) overload, which checks to see if the whole // log has been read. // // Inputs: [ilog] (in) // The index in the log to be checked. It is assumed // that this index points to a valid move notification // entry. // // Outputs: [BOOL] // True if and only if the entry has been marked as read. // //+---------------------------------------------------------------------------- BOOL CLog::IsRead( LogIndex ilog ) { LogMoveNotification lmn; // Has the whole log been read? if( IsRead() ) return( TRUE ); // Or, has this entry been read? _pcLogFile->ReadMoveNotification( ilog, &lmn ); if( _loginfo.seqLastRead >= lmn.seq ) return( TRUE ); // Otherwise, we know the entry hasn't been read. else return( FALSE ); } // CLog::IsRead( LogIndex )