// 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 )