//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1991 - 2000.
//
//  File:       RCSTXACT.CXX
//
//  Contents:   RecoverableStream Transactions
//
//  Classes:    CRcovStrmTrans,
//              CRcovStrmReadTrans,
//              CRcovStrmWriteTrans,
//              CRcovStrmAppendTrans,
//              CRcovStrmMDTrans
//
//
//  History:    28-Jan-1994     SrikantS    Created
//
//----------------------------------------------------------------------------

#include <pch.cxx>
#pragma hdrstop

#include <rcstxact.hxx>
#include <cifailte.hxx>
#include <eventlog.hxx>

#ifndef FACB_MAPPING_GRANULARITY
#define VACB_MAPPING_GRANULARITY 262144
#endif

#define  CI_PAGES_IN_CACHE_PAGE (VACB_MAPPING_GRANULARITY/CI_PAGE_SIZE)
#define  CACHE_PAGE_TO_CI_PAGE_SHIFT 6  // 64

//+---------------------------------------------------------------------------
//
//  Function:   CiPagesFromCachePages
//
//  Synopsis:   Given a number in "cache" page units, it converts into
//              "ci" pages. A cache page is 256K in size and a ci page is
//              4K in size.
//
//  Arguments:  [cachePage] - A number in cache page units
//
//  History:    10-17-95   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

inline LONGLONG CiPagesFromCachePages( const LONGLONG & cachePage )
{
    Win4Assert( (1 << CACHE_PAGE_TO_CI_PAGE_SHIFT) ==  CI_PAGES_IN_CACHE_PAGE );
    return cachePage << CACHE_PAGE_TO_CI_PAGE_SHIFT;
}

inline
ULONG PgCachePgTrunc(ULONG x)
{
    return x & ~(VACB_MAPPING_GRANULARITY - 1);
}

inline BOOL IsHighBitSet( ULONG ul )
{
    return ul & 0x80000000;
}

//+---------------------------------------------------------------------------
//
//  Member:     CRcovStrmTrans::CRcovStrmTrans
//
//  Synopsis:
//
//  Arguments:  [obj] -
//              [op]  -
//
//  Returns:
//
//  Modifies:
//
//  History:    1-28-94   srikants   Created
//              3-13-98   kitmanh    Don't Open backup mmstreams if catalog
//                                   is read-only
//
//  Notes:
//
//----------------------------------------------------------------------------

CRcovStrmTrans::CRcovStrmTrans( PRcovStorageObj &obj,
                                RcovOpType op
                              ) : _obj(obj),
                                  _hdr(obj.GetHeader()),
                                  _sObj(_obj)
{
    Win4Assert( opNone != op );

    //
    // Read the header from the disk.
    //
    _obj.ReadHeader();

    _iPrim = _hdr.GetPrimary();
    _iBack = _hdr.GetBackup();
    _iCurr = _iBack;

    //
    // Check if the backup is clean or not.
    //
    if ( !_hdr.IsBackupClean() )
    {
        ciDebugOut(( DEB_WARN, "CRcovStrmTrans::Cleaning up a Failed Previous Trans\n" ));

        //
        // Open the streams and create memory mapped streams for
        // accessing the backup in write mode and the primary in
        // read mode.
        //

        _obj.Open( _hdr.GetPrimary(), FALSE );

        // We can't cleanup if the catalog is read only, so just fail

        if ( obj.IsReadOnly() )
        {
            PStorage & storage = _obj.GetStorage();
            storage.ReportCorruptComponent( L"RcovStorageTrans2" );
            THROW (CException( CI_CORRUPT_DATABASE ) );
        }

        _obj.Open( _hdr.GetBackup(), TRUE );

        //
        // There is an assumption here that the primary stream
        // is as big as the header says but it was not true in a corrupt
        // shutdown.
        //
        PMmStream & primStrm = _obj.GetMmStream( _iPrim );
        LONGLONG llcbMinimum = _hdr.GetCbToSkip(_iPrim) +
                               _hdr.GetUserDataSize(_iPrim);

        if ( llcbMinimum > primStrm.Size() )
        {
            ciDebugOut((DEB_ERROR, "**** CI MAY HAVE LOST IMPORTANT INFO ***\n" ));
            ciDebugOut((DEB_ERROR, "**** EMPTYING CI RCOV OBJECT ***** \n" ));

            PStorage & storage = _obj.GetStorage();
            Win4Assert( !"Corrupt catalog" );

            storage.ReportCorruptComponent( L"RcovStorageTrans1" );

            Win4Assert( !"Recoverable object corrupt" );
            THROW (CException( CI_CORRUPT_DATABASE ) );
        }

        CleanupAndSynchronize();
    }

    Win4Assert( _hdr.IsBackupClean() );
    Win4Assert( _hdr.GetCbToSkip(_iPrim) == _hdr.GetCbToSkip(_iBack) );
    Win4Assert( _hdr.IsBackupClean() );

    _hdr.SetRcovOp( op );

    //
    // Open the stream(s) for read/write access as needed.
    //

    if ( opRead == op )
    {
        _obj.Open( _hdr.GetPrimary(), FALSE );
        _iCurr = _iPrim;
    }
    else
    {
        // Win4Assert( !obj.IsReadOnly() );
        // We can get here if adding a property to the property map when
        // the catalog is read-only by issuing a query on a property we
        // haven't seen before.

        if ( obj.IsReadOnly() )
            THROW( CException( E_ACCESSDENIED ) );

        _obj.Open( _hdr.GetBackup(), TRUE );
        _obj.WriteHeader();
    }

    Win4Assert( _obj.IsOpen(_iCurr) );
    Seek( 0 );

    END_CONSTRUCTION( CRcovStrmTrans );
}

//+---------------------------------------------------------------------------
//
//  Member:     CRcovStrmTrans::CleanupAndSynchronize
//
//  Synopsis:
//
//  Returns:
//
//  Modifies:
//
//  History:    9-12-95   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

void CRcovStrmTrans::CleanupAndSynchronize()
{
    Win4Assert( !_hdr.IsBackupClean() );

    if ( 0 != _hdr.GetCbToSkip(_iBack) )
    {
        //
        // Get rid of the bytes to be skipped in the front.
        //
        EmptyBackupStream();
    }

    Win4Assert( 0 == _hdr.GetCbToSkip(_iBack) );

    SetStrmSize( _iBack, _hdr.GetUserDataSize(_iPrim) );
    _hdr.SetUserDataSize( _iBack, _hdr.GetUserDataSize(_iPrim) );
    _hdr.SetCount( _iBack, _hdr.GetCount(_iPrim) );
    CopyToBack( 0, 0, _hdr.GetUserDataSize(_iPrim) );

    if ( _hdr.GetCbToSkip(_iPrim) != _hdr.GetCbToSkip(_iBack) )
    {
        Unmap( _iPrim );
        _obj.Close( _iPrim );

        CommitPh1();
        Win4Assert( 0 != _hdr.GetCbToSkip(_iBack) );
        EmptyBackupStream();

        SetStrmSize( _iBack, _hdr.GetUserDataSize(_iPrim) );
        _hdr.SetUserDataSize( _iBack, _hdr.GetUserDataSize(_iPrim) );
        CopyToBack( 0, 0, _hdr.GetUserDataSize(_iPrim) );
    }

    Win4Assert( _hdr.GetCbToSkip(_iBack) == _hdr.GetCbToSkip(_iBack) );
    Win4Assert( 0 == _hdr.GetCbToSkip(_iBack) );

    CommitPh2();
}

inline LONGLONG llCommonPageTrunc ( LONGLONG cb )
{
    return (cb & ~(COMMON_PAGE_SIZE-1));
}

inline LONGLONG llCommonPageRound ( LONGLONG cb )
{
    return ( cb + (COMMON_PAGE_SIZE-1) ) & ~(COMMON_PAGE_SIZE-1);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRcovStrmTrans::Seek
//
//  Synopsis:   Seeks to the specified offset. This is an exported function
//              the offset is the offset visible to the user, ie, offset
//              after the "bytes to skip" in the front of the stream.
//
//  Arguments:  [offset] - offset into the stream. If offset is ENDOFSTRM,
//              it will be positioned after the last "user" byte.
//
//  Returns:    BOOL - FALSE if seek is to beyond end of data, TRUE otherwise
//
//  History:    9-19-95   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

BOOL CRcovStrmTrans::Seek( ULONG offset )
{
    PMmStream & mmStrm = _obj.GetMmStream( _iCurr );

    //
    // Compute the length of the committed part of the stream.
    // (Excluding the hole in the front).
    //
    Win4Assert( mmStrm.Size() >= _hdr.GetCbToSkip(_iCurr) );

    ULONG cbStrmMax = _GetCommittedStrmSize(_iCurr);

    if ( ENDOFSTRM == offset )
    {
        offset = _hdr.GetUserDataSize(_iCurr);
    }
    else if ( offset >= _hdr.GetUserDataSize(_iCurr) )
    {
        Win4Assert( 0 == offset ||
                    _hdr.GetUserDataSize(_iCurr) == offset );
        return FALSE;
    }

    //
    // Add the offset of the starting of user data from the beginning
    // of the "present but invisible to user" range of bytes.
    //
    offset += _hdr.GetUserDataStart(_iCurr);

    _Seek( offset );
    return TRUE;
}

//+---------------------------------------------------------------------------
//
//  Function:   _Seek
//
//  Synopsis:   Seeks the current stream to the specified offset.
//              If the offset specified is ENDOFSTRM, it will  be
//              positioned after the last valid byte.
//
//  Effects:    As long as the current offset is < the full size
//              of the stream ( not the valid size in the header but
//              the actual size of the strm ), it will be mapped.
//
//  Arguments:  [offset] --  byte offset from the beginning of the
//              "hole".
//
//  History:    2-02-94   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

void CRcovStrmTrans::_Seek( ULONG offset )
{

    Win4Assert( ENDOFSTRM != offset );
    Win4Assert( offset >= _hdr.GetUserDataStart(_iCurr) );
    Win4Assert( offset <= _hdr.GetFullSize(_iCurr) );

    CStrmInfo & si = _aStrmInfo[_iCurr];
    PMmStream & mmStrm = _obj.GetMmStream( _iCurr );

    //
    // Compute the length of the committed part of the stream.
    // (Excluding the hole in the front).
    //
    Win4Assert( mmStrm.Size() >= _hdr.GetCbToSkip(_iCurr) );

    si._oCurrent = offset;

    if ( IsMapped(si._oCurrent) ||
        _GetCommittedStrmSize( mmStrm, _iCurr) <= si._oCurrent )
    {
        return;
    }

    Win4Assert( _GetCommittedStrmSize( mmStrm, _iCurr) > si._oCurrent );

    //
    // Since the current offset is within the valid range of bytes
    // and it is not mapped, we have to unmap the currently mapped
    // range (if any) and map the new range.
    //
    CMmStreamBuf & sbuf = _obj.GetMmStreamBuf( _iCurr );
    if ( sbuf.Get() )
    {
        mmStrm.Flush( sbuf, sbuf.Size() );
        mmStrm.Unmap( sbuf );
    }

    //
    // We always map on page boundaries.
    //
    si._oMapLow = CommonPageTrunc( si._oCurrent );
    si._oMapHigh = si._oMapLow + (COMMON_PAGE_SIZE-1);


    LONGLONG llOffset = _hdr.GetHoleLength(_iCurr) + si._oMapLow;

    mmStrm.Map( sbuf, COMMON_PAGE_SIZE,
                lltoLowPart(llOffset),
                (ULONG) lltoHighPart(llOffset),
                mmStrm.isWritable()     // Map for writing only if the stream
                                        // is writable.
              );

#if CIDBG==1
    LONGLONG llHighMap = llOffset+COMMON_PAGE_SIZE-1;
    Win4Assert( llHighMap < mmStrm.Size() );
#endif  // CIDBG==1

    Win4Assert( IsMapped( si._oCurrent ) );
}

//+---------------------------------------------------------------------------
//
//  Function:   Advance
//
//  Synopsis:   Advances the current offset by the specified number of
//              bytes.
//
//  Arguments:  [cb] --  Number of bytes by which to increment the
//              current position.
//
//  History:    2-02-94   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

void CRcovStrmTrans::Advance( ULONG cb )
{

    CStrmInfo & si = _aStrmInfo[_iCurr];

    Win4Assert( !IsHighBitSet(si._oCurrent) );

    ULONG  offset = si._oCurrent + cb;
    _Seek( offset  );
}

//+---------------------------------------------------------------------------
//
//  Function:   Read
//
//  Synopsis:   Reads the specified number of bytes from the current
//              stream into the given buffer.
//
//  Effects:    The current pointer will be advanced by the number of
//              bytes read.
//
//  Arguments:  [pvBuf]    --  Buffer to read the data into.
//              [cbToRead] --  Number of bytes to read.
//
//  Returns:    The number of bytes read. It will not read beyond the
//              end of valid bytes (end of stream).
//
//  History:    2-02-94   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

ULONG CRcovStrmTrans::Read( void *pvBuf, ULONG cbToRead )
{

    CStrmInfo & si = _aStrmInfo[_iCurr];
    ULONG   cbRead = 0;
    BYTE *  pbDst = (BYTE *) pvBuf;

    while ( (cbToRead > 0) && (si._oCurrent < _hdr.GetFullSize(_iCurr)) )
    {

        _Seek( si._oCurrent );

        ULONG   cbMapped = si._oMapHigh - si._oCurrent + 1;
        ULONG   cbCopy = min ( cbToRead, cbMapped );
        Win4Assert( cbMapped && cbCopy );

        ULONG   oStart = si._oCurrent - si._oMapLow;
        CMmStreamBuf & sbuf = _obj.GetMmStreamBuf( _iCurr );
        BYTE *  pbSrc = (BYTE *)sbuf.Get() + oStart;

        memcpy( pbDst, pbSrc, cbCopy );

        pbDst += cbCopy;
        cbToRead -= cbCopy;
        cbRead += cbCopy;
        si._oCurrent += cbCopy;
    }

    return cbRead;
}


//+---------------------------------------------------------------------------
//
//  Function:   Write
//
//  Synopsis:   Writes the given bytes to the current backup stream
//
//  Effects:    The current pointer will be advanced by the number
//              of bytes written. If necessary, the stream will be
//              grown to accommodate the given bytes.
//
//  Arguments:  [pvBuf]     --   Buffer containing the data.
//              [cbToWrite] --   Number of bytes to write.
//
//  History:    2-02-94   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

void CRcovStrmTrans::Write( const void *pvBuf, ULONG cbToWrite )
{
    Win4Assert( cbToWrite && pvBuf || !cbToWrite );
    Win4Assert( _iCurr == _iBack );

    CStrmInfo & si = _aStrmInfo[_iCurr];
    if ( si._oCurrent+ cbToWrite > _hdr.GetFullSize(_iCurr) )
    {
        //
        // Grow the stream for writing the given bytes.
        //
        ULONG cbDelta = (si._oCurrent + cbToWrite) -
                                                _hdr.GetFullSize(_iCurr);
        Grow( _iCurr, cbDelta );
    }

    BYTE *  pbSrc = (BYTE *) pvBuf;
    while (cbToWrite > 0)
    {

        Win4Assert( si._oCurrent < _hdr.GetFullSize( _iCurr ) );
        _Seek( si._oCurrent );

        ULONG cbMapped = si._oMapHigh - si._oCurrent + 1;

        ULONG   cbCopy = min ( cbToWrite, cbMapped );
        Win4Assert( cbCopy && cbMapped );

        ULONG   oStart = si._oCurrent - si._oMapLow;
        CMmStreamBuf & sbuf = _obj.GetMmStreamBuf( _iCurr );
        BYTE *  pbDst = (BYTE *)sbuf.Get() + oStart;

        memcpy( pbDst, pbSrc, cbCopy );

        pbSrc += cbCopy;
        si._oCurrent += cbCopy;
        cbToWrite -= cbCopy;
    }
}

//+---------------------------------------------------------------------------
//
//  Function:   Unmap
//
//  Synopsis:   Unmaps the currently mapped region of the specified
//              stream.
//
//  Effects:    _aStrmInfo[nCopy] will be updated to indicate  that
//              nothing is mapped.
//
//  Arguments:  [nCopy] -- Stream to unmap.
//
//  History:    2-02-94   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

void CRcovStrmTrans::Unmap( CRcovStorageHdr::DataCopyNum nCopy )
{
    CStrmInfo & si = _aStrmInfo[nCopy];

    if ( ENDOFSTRM == si._oMapLow )
    {
        Win4Assert( ENDOFSTRM == si._oMapHigh );
        return;
    }

    PMmStream & mmStrm = _obj.GetMmStream( nCopy );
    CMmStreamBuf & sbuf = _obj.GetMmStreamBuf( nCopy );

    if ( sbuf.Get() )
    {
        mmStrm.Flush( sbuf, sbuf.Size() );
        mmStrm.Unmap( sbuf );
    }

    //
    // Reset the high and low end points of the mapping info.
    //
    si.Reset();

    return;
}

//+---------------------------------------------------------------------------
//
//  Function:   Grow
//
//  Synopsis:   Grows the current stream to accommodate "cbDelta" more
//              bytes in the stream.
//
//  Effects:    The valid size entry in the header for the current
//              stream will be updated to indicate the new size.
//
//  Arguments:  [nCopy]   -- The stream which needs to be grown.
//              [cbDelta] --  Number of bytes to grow by.
//
//  History:    2-02-94   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

inline void CRcovStrmTrans::Grow(
        CRcovStorageHdr::DataCopyNum nCopy, ULONG cbDelta )
{


    //
    // We don't want to do overflow checking or use 64bit arithmetic.
    // 2GB for a changelog is huge enough!
    //
    Win4Assert( !IsHighBitSet( _hdr.GetUserDataSize(nCopy) ) );
    ULONG   cbNew = _hdr.GetUserDataSize(nCopy) + cbDelta;

    PMmStream & mmStrm = _obj.GetMmStream(nCopy);

    Win4Assert( mmStrm.Size() >= _hdr.GetCbToSkip(nCopy) );

    ULONG ulCommittedSize = lltoul( mmStrm.Size() - _hdr.GetCbToSkip(nCopy) );

    //
    // Check if the stream is big enough to accommodate the additional
    // bytes.
    //
    if ( ulCommittedSize >= cbNew )
    {
        _hdr.SetUserDataSize(nCopy, cbNew);
        return;
    }

    //
    // We have to grow the stream also.
    //
    SetStrmSize( nCopy, cbNew );
   _hdr.SetUserDataSize(nCopy, cbNew );

}

//+---------------------------------------------------------------------------
//
//  Function:   SetStrmSize
//
//  Synopsis:   Sets the size of the given stream to be the specified
//              number of bytes. The size will be rounded up to the
//              nearest COMMON_PAGE_SIZE. However, the stream will
//              never be reduced below COMMON_PAGE_SIZE, ie, the minimum
//              size of the stream will be adjusted to be COMMON_PAGE_SIZE.
//
//  Effects:    The stream will be unmapped after this operation.
//
//  Arguments:  [nCopy] --  The stream whose size must be set.
//              [cbNew] --  New size of the stream.
//
//  History:    2-03-94   srikants   Created
//
//  Notes:      This is an internal function and it is assumed that the
//              necessary transaction protocol is being followed. Also
//              note that the size in the header is NOT set by this
//              method.
//
//----------------------------------------------------------------------------

void CRcovStrmTrans::SetStrmSize(
            CRcovStorageHdr::DataCopyNum nCopy, ULONG cbNew )
{
    PMmStream & mmStrm = _obj.GetMmStream( nCopy );

    //
    // Size must be atleast one OFS page in size.
    //
    if ( 0 == cbNew )
        cbNew = COMMON_PAGE_SIZE;

    Unmap( nCopy );

    LONGLONG llTotalLen = cbNew + _hdr.GetCbToSkip(nCopy);
    llTotalLen = llCommonPageRound( llTotalLen );

    if ( mmStrm.Size() != llTotalLen )     // optimization.
    {
        mmStrm.SetSize( _obj.GetStorage(),
                        lltoLowPart(llTotalLen),
                        (ULONG) lltoHighPart(llTotalLen) );
    }
}


//+---------------------------------------------------------------------------
//
//  Function:   EmptyBackupStream
//
//  Synopsis:   Empties the contents of the backup stream by setting its
//              size to "0" (in user space to COMMON_PAGE_SIZE).
//
//  History:    10-18-95   srikants   Created
//
//  Notes:      When there is a hole in a sparse stream, we have to first
//              set its size to 0 to remove the sparseness completely.
//              Otherwise, we will try writing/reading into decommitted
//              space.
//
//              For example, if there is a 256K hole in a 1M stream, setting
//              the size of the stream to 64K will leave a 64K hole in the
//              front. To avoid this, we first set the size to 0 and then
//              set size to 64K to get committed 64K stream.
//
//----------------------------------------------------------------------------

void CRcovStrmTrans::EmptyBackupStream()
{

    ciDebugOut(( DEB_ITRACE, "Emptying contents of backup stream\n" ));

    PMmStream & mmStrm = _obj.GetMmStream( _iBack );
    Unmap( _iBack );

    //
    // In use space we cannot have a 0 length stream because mapping
    // of a zero length stream fails. Also, there are no "holes" in
    // user space.
    //

    ULONG cbLow = COMMON_PAGE_SIZE;

    mmStrm.SetSize( _obj.GetStorage(),
                    cbLow,
                    0 );

    _hdr.SetUserDataSize(_iBack,0);
    _hdr.SetCbToSkip(_iBack,0);
    _obj.WriteHeader();
}

//+---------------------------------------------------------------------------
//
//  Function:   CopyToBack
//
//  Synopsis:   Copies the specified number of bytes from the current
//              primary stream to the backup stream.
//
//  Effects:
//
//  Arguments:  [oSrc]     --  Starting byte offset in the primary where
//              to start the copying from (Offset from the beginning of the
//              user data).
//              [oDst]     --  Starting byte offset in the backup where
//              to copy the data to. (Offset from the beginning of the user
//              data.)
//              [cbToCopy] --  Number of bytes to copy.
//
//  History:    2-03-94   srikants   Created
//
//  Notes:      This does not modify the "valid size" of the backup
//              stream - it is upto the caller to change it. Also, it
//              is assumed that the backup stream is big enough to
//              hold all the data and the primary data is big enough
//              to give the data.
//
//----------------------------------------------------------------------------

void CRcovStrmTrans::CopyToBack( ULONG oSrc, ULONG oDst, ULONG cbToCopy )
{

    //
    // Transform the offsets to the offset from the beginning of the
    // hole.
    //
    Win4Assert( oSrc != ENDOFSTRM );
    Win4Assert( oDst != ENDOFSTRM );

    oSrc += _hdr.GetUserDataStart(_iPrim);
    oDst += _hdr.GetUserDataStart(_iBack);

    ULONG   cbLeft = cbToCopy;
    const CRcovStorageHdr::DataCopyNum iStartCurr = _iCurr;

    CStrmInfo & siPrim = _aStrmInfo[_iPrim];
    CStrmInfo & siBack = _aStrmInfo[_iBack];

    while ( cbLeft > 0 )
    {

        SetCurrentStrm( _iPrim );
        _Seek(  oSrc );
        Win4Assert( IsMapped( oSrc ) );

        SetCurrentStrm( _iBack );
        _Seek( oDst );
        Win4Assert( IsMapped( oDst ) );

        PMmStream & mmPrimStrm = _obj.GetMmStream( _iPrim );
        PMmStream & mmBackStrm = _obj.GetMmStream( _iBack );

        CMmStreamBuf & sPrimBuf = _obj.GetMmStreamBuf( _iPrim );
        CMmStreamBuf & sBackBuf = _obj.GetMmStreamBuf( _iBack );

        const BYTE *pbSrc = (const BYTE *) sPrimBuf.Get();
        pbSrc += ( oSrc - siPrim._oMapLow);

        BYTE * pbDst = (BYTE *) sBackBuf.Get();
        pbDst += ( oDst - siBack._oMapLow);

        ULONG cbMapRead  = siPrim._oMapHigh -  oSrc + 1;
        ULONG cbMapWrite = siBack._oMapHigh -  oDst + 1;

        Win4Assert( cbMapRead && cbMapWrite );

        ULONG cbCopy = min( cbLeft, min(cbMapRead, cbMapWrite) );
        Win4Assert( cbCopy );

        memcpy( pbDst, pbSrc, cbCopy );

        oSrc += cbCopy;
        oDst += cbCopy;
        cbLeft -= cbCopy;
    }

    //
    // Restore the current index stream to be the same as the
    // original.
    //
    _iCurr = iStartCurr;

}

//+---------------------------------------------------------------------------
//
//  Function:   CommitPh1
//
//  Synopsis:   Commits the changes to the current backup stream and
//              makes it the primary. It also opens the new backup
//              stream in a write mode for making changes to the new backup
//              stream in the phase 2 of the commit process.
//
//  Effects:    The backup stream gets flushed to the disk.
//
//  Arguments:  (none)
//
//  History:    2-05-94   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

void CRcovStrmTrans::CommitPh1()
{

    //
    // Flush the backup stream.
    //
    PMmStream & mmStrm = _obj.GetMmStream( _iBack );
    CMmStreamBuf & sbuf = _obj.GetMmStreamBuf( _iBack );
    Unmap( _iBack );    // Should unmap before flushing.

    //
    // Swap the primary and backup in the header and write
    // out the header.
    //
    _hdr.SwitchPrimary();
    _obj.WriteHeader();

    Win4Assert( _iPrim == _hdr.GetBackup() );

    //
    // Swap the primary and backup streams and make
    // _iCurr the new backup. This is to prepare for
    // the phase 2 of the commit process.
    //
    _iCurr = _iPrim;
    _iPrim = _iBack;
    _iBack = _iCurr;

    //
    // Now open the current backup stream for writing.
    //
    _obj.Open( _iBack, TRUE );

}


//+---------------------------------------------------------------------------
//
//  Function:   CommitPh2
//
//  Synopsis:   Commits the phase 2 of the transaction. It writes out
//              the changes to the backup stream. Upon successful
//              completion, it marks that both streams are clean in
//              the header and closes the streams.
//
//  Effects:    After this returns, the streams are no longer accessible
//              as they are closed. They should be re-opened for another
//              transaction.
//
//  Arguments:  (none)
//
//  History:    2-10-94   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

void CRcovStrmTrans::CommitPh2()
{
    //
    // Close the primary stream and mark that it has been unmapped.
    //
    _obj.Close( _iPrim );       // close automatically unmaps if it is
                                // mapped.
    _aStrmInfo[_iPrim].Reset();

     //
     // Now Flush the backup and close it.
     //
    PMmStream & mmStrm = _obj.GetMmStream( _iBack );
    CMmStreamBuf & sbuf = _obj.GetMmStreamBuf( _iBack );
    Unmap( _iBack );        // Must unmap before flushing.

    _obj.Close( _iBack );

    //
    // Update the header that the object is now clean.
    //
    _hdr.SyncBackup();
    _obj.WriteHeader();
}

#if 0
void CRcovStrmTrans::Compare()
{
    #if CIDBG == 1

    Win4Assert( !_obj.IsOpen( _hdr.GetPrimary() ) );
    Win4Assert( !_obj.IsOpen( _hdr.GetBackup() ) );

    if( _hdr.GetUserDataSize( _hdr.GetPrimary() ) != _hdr.GetUserDataSize( _hdr.GetBackup() ) )
    {
        ciDebugOut(( DEB_ERROR, "Primary (%u) = 0x%x, Secondary (%u) = 0x%x\n",
                     _hdr.GetPrimary(), _hdr.GetUserDataSize( _hdr.GetPrimary() ),
                     _hdr.GetBackup(), _hdr.GetUserDataSize( _hdr.GetBackup() ) ));
        Win4Assert( _hdr.GetUserDataSize( _hdr.GetPrimary() ) == _hdr.GetUserDataSize( _hdr.GetBackup() ) );
    }

    _obj.Open( _hdr.GetPrimary(), FALSE );
    _obj.Open( _hdr.GetBackup(), FALSE );

    ULONG oSrc = _hdr.GetUserDataStart( _hdr.GetPrimary() );
    ULONG oDst = _hdr.GetUserDataStart( _hdr.GetBackup() );

    if ( oSrc != oDst )
    {
        ciDebugOut(( DEB_ERROR, "Primary (%u) = 0x%x, Secondary (%u) = 0x%x\n",
                     _hdr.GetPrimary(), oSrc,
                     _hdr.GetBackup(), oDst ));

        Win4Assert( oSrc == oDst );
    }

    if ( _hdr.GetUserDataSize( _hdr.GetPrimary() ) != _hdr.GetUserDataSize( _hdr.GetBackup() ) )
    {
        ciDebugOut(( DEB_ERROR, "Primary (%u) = 0x%x, Secondary (%u) = 0x%x\n",
                     _hdr.GetPrimary(), _hdr.GetUserDataSize( _hdr.GetPrimary() ),
                     _hdr.GetBackup(), _hdr.GetUserDataSize( _hdr.GetBackup() ) ));

        Win4Assert( _hdr.GetUserDataSize( _hdr.GetPrimary() ) == _hdr.GetUserDataSize( _hdr.GetBackup() ) );
    }

    ULONG cbLeft = _hdr.GetUserDataSize( _hdr.GetPrimary() );

    CStrmInfo & siPrim = _aStrmInfo[_hdr.GetPrimary()];
    CStrmInfo & siBack = _aStrmInfo[_hdr.GetBackup()];

    while ( cbLeft > 0 )
    {

        SetCurrentStrm( _hdr.GetPrimary() );
        _Seek(  oSrc );
        Win4Assert( IsMapped( oSrc ) );

        SetCurrentStrm( _hdr.GetBackup() );
        _Seek( oDst );
        Win4Assert( IsMapped( oDst ) );

        PMmStream & mmPrimStrm = _obj.GetMmStream( _hdr.GetPrimary() );
        PMmStream & mmBackStrm = _obj.GetMmStream( _hdr.GetBackup() );

        CMmStreamBuf & sPrimBuf = _obj.GetMmStreamBuf( _hdr.GetPrimary() );
        CMmStreamBuf & sBackBuf = _obj.GetMmStreamBuf( _hdr.GetBackup() );

        const BYTE *pbSrc = (const BYTE *) sPrimBuf.Get();
        pbSrc += ( oSrc - siPrim._oMapLow);

        BYTE * pbDst = (BYTE *) sBackBuf.Get();
        pbDst += ( oDst - siBack._oMapLow);

        ULONG cbMapRead  = siPrim._oMapHigh -  oSrc + 1;
        ULONG cbMapWrite = siBack._oMapHigh -  oDst + 1;

        Win4Assert( cbMapRead && cbMapWrite );

        ULONG cbCopy = min( cbLeft, min(cbMapRead, cbMapWrite) );
        Win4Assert( cbCopy );

        if ( 0 != memcmp( pbDst, pbSrc, cbCopy ) )
        {
            ciDebugOut(( DEB_ERROR, "CRcovStrmTrans::Compare -- mismatch\n" ));
            ciDebugOut(( DEB_ERROR | DEB_NOCOMPNAME, "  Primary (%u) offset 0x%x, pb = 0x%x\n",
                         _hdr.GetPrimary(), oSrc, pbSrc ));
            ciDebugOut(( DEB_ERROR | DEB_NOCOMPNAME, "  Backup (%u) offset 0x%x, pb = 0x%x\n",
                         _hdr.GetBackup(), oDst, pbDst ));
            Win4Assert( !"CRcovStrmTrans::Compare -- mismatch" );
        }

        oSrc += cbCopy;
        oDst += cbCopy;
        cbLeft -= cbCopy;
    }

    _obj.Close( _hdr.GetPrimary() );
    _obj.Close( _hdr.GetBackup() );

    #endif // CIDBG
}
#endif

void CRcovStrmWriteTrans::Commit()
{
    Win4Assert( XActAbort == CTransaction::GetStatus() );

    CommitPh1();

    //
    // After phase1 is successfully committed, the transaction will be
    // completed by making "forward progress". So, we should not throw
    // now even if the phase2 fails. Even if phase2 fails now, it will
    // be eventually completed later when a new transaction is created
    // for this object.
    //
    TRY
    {
        //
        // Copy the entire contents from the new primary to the
        // new backup.
        //
        SetStrmSize( _iBack, GetStorageHdr().GetUserDataSize(_iPrim) );
        GetStorageHdr().SetUserDataSize( _iBack,
                                GetStorageHdr().GetUserDataSize(_iPrim) );

        CopyToBack( 0, 0, GetStorageHdr().GetUserDataSize(_iPrim));

        CommitPh2();
        Win4Assert( GetStorageHdr().IsBackupClean() );
    }
    CATCH( CException, e )
    {
        GetRcovObj().Close( _iPrim );
        GetRcovObj().Close( _iBack );

        ciDebugOut(( DEB_ERROR, "Phase2 of CRcomStrmWriteTrans failed 0x%X\n",
                     e.GetErrorCode() ));
    }
    END_CATCH

    CRcovStrmTrans::Commit();

}

void CRcovStrmWriteTrans::Empty()
{
    Win4Assert( _iCurr == _iBack );
    SetStrmSize( _iBack, 0 );
    GetStorageHdr().SetUserDataSize( _iBack, 0 );
    Seek(0);
}

CRcovStrmAppendTrans::CRcovStrmAppendTrans( PRcovStorageObj & obj )
    : CRcovStrmTrans( obj, opAppend )
{
    //
    // Seek to end of file.
    //
    Seek( ENDOFSTRM );

    END_CONSTRUCTION( CRcovStrmAppendTrans );
}

void CRcovStrmAppendTrans::Commit()
{
    Win4Assert( XActAbort == CTransaction::GetStatus() );

    Win4Assert( GetStorageHdr().GetUserDataSize(_iPrim) <=
                GetStorageHdr().GetUserDataSize(_iBack) );

    CTransaction::_status = XActAbort;
    CommitPh1();
    //
    // After phase1 is successfully committed, the transaction will be
    // completed by making "forward progress". So, we should not throw
    // now even if the phase2 fails. Even if phase2 fails now, it will
    // be eventually completed later when a new transaction is created
    // for this object.
    //
    TRY
    {
        //
        // Now copy the contents of the primary to the backup
        // from the point where append started.
        //
        ULONG cbToCopy = GetStorageHdr().GetUserDataSize(_iPrim) -
                                    GetStorageHdr().GetUserDataSize(_iBack);
        ULONG offset = GetStorageHdr().GetUserDataSize(_iBack);

        ciFAILTEST( STATUS_DISK_FULL );

        Grow(_iBack, cbToCopy);

        ciFAILTEST( STATUS_DISK_FULL );

        CopyToBack( offset, offset, cbToCopy );

        ciFAILTEST( STATUS_DISK_FULL );

        CommitPh2();

        ciFAILTEST( STATUS_DISK_FULL );

    }
    CATCH ( CException, e )
    {
        GetRcovObj().Close( _iPrim );
        GetRcovObj().Close( _iBack );

        ciDebugOut(( DEB_ERROR, "Phase2 of CRcomStrmAppendTrans failed 0x%X\n",
                     e.GetErrorCode() ));
    }
    END_CATCH

    CRcovStrmTrans::Commit();
}

CRcovStrmMDTrans::CRcovStrmMDTrans( PRcovStorageObj & obj,
                                    MDOp op, ULONG cb )
    : CRcovStrmTrans( obj, opMetaData ),
      _op(op), _cbOp(cb)
{

    END_CONSTRUCTION( CRcovStrmMDTrans );
}

void CRcovStrmMDTrans::Commit()
{
    Win4Assert( XActAbort == CTransaction::GetStatus() );

    switch ( _op )
    {

        case mdopSetSize:
            SetSize( _cbOp );
            break;

        case mdopGrow:
            Grow( _cbOp );
            break;

        case mdopFrontShrink:
            ShrinkFromFront( _cbOp );
            break;

        case mdopBackCompact:
            CompactFromEnd( _cbOp );
            break;

        default:
            Win4Assert( ! "Control Should Not Come Here" );
            break;
    }

    CRcovStrmTrans::Commit();
}

void CRcovStrmMDTrans::SetSize( ULONG cbNew )
{

    //
    // set the size on the backup first.
    //
    Win4Assert( _iCurr == _iBack );
    CRcovStrmTrans::SetStrmSize( _iBack, cbNew );
    GetStorageHdr().SetUserDataSize( _iBack, cbNew );

    //
    // Do the phase1 commit now.
    //
    CommitPh1();

    //
    // After phase1 is successfully committed, the transaction will be
    // completed by making "forward progress". So, we should not throw
    // now even if the phase2 fails. Even if phase2 fails now, it will
    // be eventually completed later when a new transaction is created
    // for this object.
    //
    TRY
    {
        //
        // Apply the same operation on the new backup.
        //
        Win4Assert( _iCurr == _iBack );
        CRcovStrmTrans::SetStrmSize( _iBack, cbNew );
        CommitPh2();

        Win4Assert( GetStorageHdr().IsBackupClean() );
    }
    CATCH( CException,e )
    {
        GetRcovObj().Close( _iPrim );
        GetRcovObj().Close( _iBack );

        ciDebugOut(( DEB_ERROR, "Phase2 of CRcovStrmMDTrans failed 0x%X\n",
                     e.GetErrorCode() ));
    }
    END_CATCH
}

void CRcovStrmMDTrans::Grow( ULONG cbDelta )
{
    const ULONG cbNew = GetStorageHdr().GetUserDataSize(_iCurr)+cbDelta;
    CRcovStrmMDTrans::SetSize( cbNew );
}

void CRcovStrmMDTrans::CompactFromEnd( ULONG cbDelta )
{
    Win4Assert( cbDelta <= GetStorageHdr().GetUserDataSize( _iCurr ) );
    Win4Assert( _iCurr == _iBack );

    cbDelta = min (cbDelta, GetStorageHdr().GetUserDataSize( _iCurr ));
    const ULONG cbNew = GetStorageHdr().GetUserDataSize(_iCurr) - cbDelta;

    CRcovStrmMDTrans::SetSize( cbNew );

}

//+---------------------------------------------------------------------------
//
//  Member:     CRcovStrmMDTrans::IncreaseBytesToSkip
//
//  Synopsis:   Increases the number of bytes to be "skipped" in the front
//              by the specified amount.
//
//  Arguments:  [cbDelta] - Number of additional bytes to skip.
//
//  History:    9-19-95   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

void CRcovStrmMDTrans::IncreaseBytesToSkip( ULONG cbDelta )
{
    CRcovStorageHdr & hdr = GetStorageHdr();

    //
    // Update the hole length and the valid data length.
    //
    Win4Assert( hdr.GetUserDataSize(_iBack) >= cbDelta );
    const ULONG cbNewValid = hdr.GetUserDataSize(_iPrim) - cbDelta;
    hdr.SetUserDataSize( _iBack, cbNewValid );

    Win4Assert( hdr.GetCbToSkip(_iPrim) == hdr.GetCbToSkip(_iBack) );
    const LONGLONG llcbNewSkip = hdr.GetCbToSkip(_iBack)+cbDelta;
    hdr.SetCbToSkip( _iBack, llcbNewSkip );

    // Phase1 complete - just set the values for the backup.
    hdr.SwitchPrimary();
    Win4Assert( _iPrim == hdr.GetBackup() );

    _iCurr = _iPrim;
    _iPrim = _iBack;
    _iBack = _iCurr;

    // phase 2 complete. Commit the changes
    hdr.SyncBackup();

    GetRcovObj().WriteHeader();
}

//+---------------------------------------------------------------------------
//
//  Member:     CRcovStrmMDTrans::CopyShrinkFromFront
//
//  Synopsis:   Implements a shrink from front by copying bytes.
//
//  Arguments:  [cbNew]   -  Number of bytes that will be in the stream after
//              doing a shrink from front.
//              [cbDelta] -  Number of bytes to shrink in the front.
//
//  History:    10-02-95   srikants   Created ( Moved from ShrinkFromFront()
//                                    methoed )
//
//  Notes:
//
//----------------------------------------------------------------------------

void CRcovStrmMDTrans::CopyShrinkFromFront( ULONG cbNew,
                                            ULONG cbDelta )
{

    CRcovStorageHdr & hdr = GetStorageHdr();

    ULONG oDst = 0;
    ULONG oSrc = cbDelta;

    // this check is an optimization
    if ( hdr.GetHoleLength(_iBack) > SHRINK_FROM_FRONT_PAGE_SIZE )
    {
        //
        // If the hole in front is <= SHRINK_FROM_FRONT_PAGE_SIZE, then
        // this there is no "sparseness" in the front.
        //
        EmptyBackupStream();
    }

    //
    // Open the Primary in a read-only mode and close it before
    // calling CommitPh1().
    // Copy the contents from oSrc in the primary to oDst in the
    // backup stream.
    //
    hdr.SetCbToSkip(_iBack,0);
    CRcovStrmTrans::SetStrmSize( _iBack, cbNew );
    hdr.SetUserDataSize( _iBack, cbNew );

    GetRcovObj().Open( _iPrim, FALSE );
    CopyToBack( oSrc, oDst, cbNew );
    Unmap( _iPrim );
    GetRcovObj().Close( _iPrim );

    //
    // Commit the phase1 changes now.
    //
    CommitPh1();

    //
    // After phase1 is successfully committed, the transaction will be
    // completed by making "forward progress". So, we should not throw
    // now even if the phase2 fails. Even if phase2 fails now, it will
    // be eventually completed later when a new transaction is created
    // for this object.
    //
    TRY
    {
        //
        // We have to synchronize the new backup with the new primary.
        //
        Win4Assert( _iCurr == _iBack );

        // this check is an optimization
        if ( hdr.GetHoleLength(_iBack) > SHRINK_FROM_FRONT_PAGE_SIZE )
        {
            //
            // If the hole in front is <= SHRINK_FROM_FRONT_PAGE_SIZE, then
            // this there is no "sparseness" in the front.
            //
            EmptyBackupStream();
        }

        hdr.SetCbToSkip(_iBack,0);
        CRcovStrmTrans::SetStrmSize( _iBack, cbNew );

        CopyToBack( 0, 0, cbNew );

        hdr.SetUserDataSize( _iBack, cbNew );
        CommitPh2();

        Win4Assert( hdr.IsBackupClean() );
    }
    CATCH( CException, e )
    {
        GetRcovObj().Close( _iPrim );
        GetRcovObj().Close( _iBack );

        ciDebugOut(( DEB_ERROR, "Phase2 of ShrinkFromFront() failed 0x%X\n",
                     e.GetErrorCode() ));
    }
    END_CATCH
}

void CRcovStrmMDTrans::ShrinkFromFront( ULONG cbDelta )
{

    CRcovStorageHdr & hdr = GetStorageHdr();

    Win4Assert( cbDelta <= hdr.GetUserDataSize( _iCurr ) );
    Win4Assert( _iCurr == _iBack );

    cbDelta = min (cbDelta, hdr.GetUserDataSize( _iCurr ));

    Win4Assert( hdr.GetCbToSkip(_iCurr) >= hdr.GetHoleLength(_iCurr) );

    const ULONG cbCommittedSkip = lltoul( hdr.GetCbToSkip(_iCurr) - hdr.GetHoleLength(_iCurr) );

    //
    // If the total number of bytes "present but invisible" to user is
    // < the threshold, don't do any copying or shrinking. Just increment
    // the bytest to skip and return.
    //
    if ( cbCommittedSkip + cbDelta < SHRINK_FROM_FRONT_PAGE_SIZE )
    {
        IncreaseBytesToSkip( cbDelta );
        return;
    }

    const ULONG cbNew  = hdr.GetUserDataSize(_iCurr) - cbDelta;

    CopyShrinkFromFront( cbNew, cbDelta );
}



//+---------------------------------------------------------------------------
//
//  Member:     CCopyRcovObject::_SetDstSize
//
//  Synopsis:   Sets the size of the destination object to be same as
//              the source object.
//
//  History:    3-17-97   srikants   Created
//
//----------------------------------------------------------------------------

void CCopyRcovObject::_SetDstSize()
{
    CRcovStrmMDTrans trans( _dst, CRcovStrmMDTrans::mdopSetSize, _cbSrc );
    trans.Commit();
}

//+---------------------------------------------------------------------------
//
//  Member:     CCopyRcovObject::_CopyData
//
//  Synopsis:   Copies the data from the source object to the destination
//              object.
//
//  History:    3-17-97   srikants   Created
//
//----------------------------------------------------------------------------

void CCopyRcovObject::_CopyData()
{

    const cbPageSize = 4096;    // Read and write 4k at a time.
    XArray<BYTE>  xByte( cbPageSize );

    //
    // Start a read transaction on the source object
    //
    CRcovStrmReadTrans  srcTrans( _src );

    //
    // Start a write transaction on the destination.
    //
    CRcovStrmWriteTrans dstTrans( _dst );

    dstTrans.Empty();
    dstTrans.Seek(0);

    ULONG cbRemaining = _cbSrc;

    while ( cbRemaining > 0 )
    {
        ULONG cbToCopy = min( cbRemaining, cbPageSize );
        srcTrans.Read( xByte.GetPointer(), cbToCopy );
        dstTrans.Write( xByte.GetPointer(), cbToCopy );

        cbRemaining -= cbToCopy;
    }

    //
    // Set the user header.
    //
    CRcovUserHdr  usrHdr;
    _srcHdr.GetUserHdr( _srcHdr.GetPrimary(), usrHdr );
    _dstHdr.SetUserHdr( _dstHdr.GetBackup(), usrHdr );

    //
    // Set the number of records.
    //
    ULONG nRec = _srcHdr.GetCount( _srcHdr.GetPrimary() );
    _dstHdr.SetCount( _dstHdr.GetBackup(), nRec );

    //
    // Commit the transaction.
    //
    dstTrans.Commit();
}

//+---------------------------------------------------------------------------
//
//  Member:     CCopyRcovObject::DoIt
//
//  Synopsis:   Copies the contents of one object to another object.
//
//  Returns:    STATUS_SUCCESS if successful;
//              Other error code if there is an error.
//
//  History:    3-17-97   srikants   Created
//
//----------------------------------------------------------------------------

NTSTATUS CCopyRcovObject::DoIt()
{

    NTSTATUS  status = STATUS_SUCCESS;

    TRY
    {
        _SetDstSize();
        _CopyData();
    }
    CATCH( CException, e )
    {
        status = e.GetErrorCode();
        ciDebugOut(( DEB_ERROR, "CCopyRcovObject::DoIt. Error 0x%X\n", status ));
    }
    END_CATCH

    return status;
}