//+---------------------------------------------------------------------------
//
//  Copyright (C) Microsoft Corporation, 1991 - 1998
//
//  File:       dellog.cxx
//
//  Contents:   Deletion log for usns
//
//  History:    28-Jul-97   SitaramR    Created
//
//----------------------------------------------------------------------------

#include <pch.cxx>
#pragma hdrstop

#include <mmstrm.hxx>

#include "cicat.hxx"
#include "dellog.hxx"

const LONGLONG eSigDelLog = 0x64656c746e6c6f67i64;  // Signature


//+---------------------------------------------------------------------------
//
//  Member:     CFakeVolIdMap::CFakeVolIdMap
//
//  Synopsis:   Constructor
//
//  History:    28-Jul-97     SitaramR       Created
//
//----------------------------------------------------------------------------

CFakeVolIdMap::CFakeVolIdMap()
{
    for ( ULONG i=0; i<COUNT_SPECIAL_CHARS; i++)
        _aVolIdSpecial[i] = 0;
}


//+---------------------------------------------------------------------------
//
//  Member:     CFakeVolIdMap::VolIdToFakeVolId
//
//  Synopsis:   Converts a volume id to a fake volume id in the range
//              0..RTL_MAX_DRIVE_LETTERS-1
//
//  History:    28-Jul-97     SitaramR       Created
//
//  Notes:      Drive letters in the range 'a' to 'z' are mapped by subtracting
//              'a', which is the base volume id, i.e. they are in the range
//              0..25 The volume id's for the remaining drive letters are
//              maintained in _aVolIdSpecial, and the fake volume ids for these
//              special drives are in the range 26..RTL_MAX_DRIVE_LETTERS-1.
//
//----------------------------------------------------------------------------

ULONG CFakeVolIdMap::VolIdToFakeVolId( VOLUMEID volumeId )
{
    //
    // Volume ids are obtained by the ascii value of the drive letter
    //
    Win4Assert( volumeId < 0xff );

    if ( volumeId >= VolumeIdBase
         && volumeId-VolumeIdBase < COUNT_ALPHABETS )
    {
        return volumeId-VolumeIdBase;
    }

    //
    // Lookup in _aVolIdSpecial
    //
    for ( ULONG i=0; i<COUNT_SPECIAL_CHARS; i++ )
    {
        if ( _aVolIdSpecial[i] == volumeId )
            return COUNT_ALPHABETS + i;
    }

    for ( i=0; i<COUNT_SPECIAL_CHARS; i++ )
    {
        if ( _aVolIdSpecial[i] == 0 )
        {
            _aVolIdSpecial[i] = volumeId;
            return COUNT_ALPHABETS + i;
        }
    }

    Win4Assert( !"Volume id map overflow" );

    return 0;
}

//+---------------------------------------------------------------------------
//
//  Member:     CFakeVolIdMap::FakeVolIdToVolId
//
//  Synopsis:   Converts a fake volume id in the range 0..RTL_MAX_DRIVE_LETTERS-1
//              to a real volume id
//
//  History:    28-Jul-97     SitaramR       Created
//
//  Notes:      See VolIdToFakeVolId for mapping info.
//
//----------------------------------------------------------------------------

VOLUMEID CFakeVolIdMap::FakeVolIdToVolId( ULONG fakeVolId )
{
    Win4Assert( fakeVolId < RTL_MAX_DRIVE_LETTERS );

    if ( fakeVolId < COUNT_ALPHABETS )
        return VolumeIdBase + fakeVolId;
    else
        return _aVolIdSpecial[fakeVolId-COUNT_ALPHABETS];
}


//+---------------------------------------------------------------------------
//
//  Member:     CDeletionLog::CDeletionLog
//
//  Synopsis:   Constructor
//
//  Arguments:  [fileIdMap] -- File id map
//              [cicat]     -- Ci catalog
//
//  History:    28-Jul-97     SitaramR       Created
//
//----------------------------------------------------------------------------

CDeletionLog::CDeletionLog( CFileIdMap & fileIdMap, CiCat& cicat )
   : _fileIdMap(fileIdMap),
     _cicat(cicat)
{
}


//+---------------------------------------------------------------------------
//
//  Member:     CDeletionLog::FastInit
//
//  Synopsis:   Initialization
//
//  Arguments:  [pStorage]   -- Ci storage
//              [pwcsCatDir] -- Catalog dir
//              [version]    -- Version #
//
//  History:    28-Jul-97     SitaramR       Created
//              02-Mar-98     KitmanH        Used QueryDeletionlog to obtain a new 
//                                           CMmStream
//
//  Notes:      The persistent format is <vol id><cEntries><entry 1>..<entry cEntries>
//              for each volume id, and each entry has <fileid><wid><usn>.
//
//----------------------------------------------------------------------------

void CDeletionLog::FastInit( CiStorage * pStorage,
                             ULONG version )
{

    XPtr<PMmStream> sStrm( pStorage->QueryDeletionLog() );

    _xPersStream.Set( new CDynStream( sStrm.GetPointer() ) );

    sStrm.Acquire();

    _xPersStream->CheckVersion( *pStorage, version );

    ULONG cVolumes = _xPersStream->Count();
    _xPersStream->InitializeForRead();

    for ( ULONG i=0; i<cVolumes; i++ )
    {
        ULONG cbRead;
        LONGLONG llSig;

        cbRead = _xPersStream->Read( &llSig, sizeof(llSig) );
        if ( cbRead != sizeof(llSig) )
            FatalCorruption( cbRead, sizeof(llSig) );

        if ( eSigDelLog != llSig )
        {
            ciDebugOut(( DEB_ERROR,
                         "CDeletionLog: Signature mismatch 0x%x:0x%x\n",
                         lltoHighPart(llSig),
                         lltoLowPart(llSig) ));
            FatalCorruption( 0, 0 );
        }

        VOLUMEID volumeId;
        cbRead = _xPersStream->Read( &volumeId, sizeof(VOLUMEID) );
        if ( cbRead != sizeof(VOLUMEID) )
            FatalCorruption( cbRead, sizeof(VOLUMEID) );

        ULONG cEntries;
        cbRead = _xPersStream->Read( &cEntries, sizeof(ULONG) );
        if ( cbRead != sizeof(ULONG) )
            FatalCorruption( cbRead, sizeof(ULONG) );

        for ( ULONG j=0; j<cEntries;j++ )
        {
            FILEID fileId;
            cbRead = _xPersStream->Read( &fileId, sizeof(FILEID) );
            if ( cbRead != sizeof(FILEID) )
                FatalCorruption( cbRead, sizeof(FILEID) );

            WORKID wid;
            cbRead = _xPersStream->Read( &wid, sizeof(WORKID) );
            if ( cbRead != sizeof(WORKID) )
                FatalCorruption( cbRead, sizeof(WORKID) );

            USN usn;
            cbRead = _xPersStream->Read( &usn, sizeof(USN) );
            if ( cbRead != sizeof(USN) )
                FatalCorruption( cbRead, sizeof(USN) );

            MarkForDeletion( volumeId,
                             fileId,
                             wid,
                             usn );
        }
    }
}


//+---------------------------------------------------------------------------
//
//  Member:     CDeletionLog::ReInit
//
//  Synopsis:   Empties the deletion log
//
//  History:    28-Jul-97     SitaramR       Created
//
//----------------------------------------------------------------------------

void CDeletionLog::ReInit( ULONG version )
{
    CLock lock(_mutex);

    _xPersStream->SetVersion( version );

    for ( ULONG i=0; i<RTL_MAX_DRIVE_LETTERS; i++ )
        _aDelLogEntryList[i].Clear();

    Flush();
}


//+---------------------------------------------------------------------------
//
//  Member:     CDeletionLog::Flush
//
//  Synopsis:   Serializes the deletion log to disk
//
//  History:    28-Jul-97     SitaramR       Created
//
//----------------------------------------------------------------------------

void CDeletionLog::Flush()
{
    Win4Assert( !_xPersStream.IsNull() );

    CLock lock(_mutex);

    _xPersStream->InitializeForWrite( GetSize() );
    LONGLONG llSig = eSigDelLog;

    ULONG cVolumes = 0;
    for ( ULONG i=0; i<RTL_MAX_DRIVE_LETTERS; i++ )
    {
        if ( _aDelLogEntryList[i].Count() > 0 )
        {
            cVolumes++;

            _xPersStream->Write( &llSig, sizeof(llSig) );

            VOLUMEID volumeId = _fakeVolIdMap.FakeVolIdToVolId( i );
            _xPersStream->Write( &volumeId, sizeof(VOLUMEID) );

            ULONG cEntries = _aDelLogEntryList[i].Count();
            _xPersStream->Write( &cEntries, sizeof(ULONG) );

#if CIDBG==1
            ULONG cEntriesInList = 0;
            USN usnPrev = 0;
#endif

            for ( CDelLogEntryListIter entryListIter( _aDelLogEntryList[i] );
                  !_aDelLogEntryList[i].AtEnd(entryListIter);
                  _aDelLogEntryList[i].Advance( entryListIter) )
            {
                 FILEID fileId = entryListIter->FileId();
                _xPersStream->Write( &fileId, sizeof(FILEID) );

                WORKID wid = entryListIter->WorkId();
                _xPersStream->Write( &wid, sizeof(WORKID) );

                USN usn = entryListIter->Usn();
                _xPersStream->Write( &usn, sizeof(USN) );

#if CIDBG==1
                //
                // Check usn's are monotonically increasing
                //
                cEntriesInList++;
                Win4Assert( entryListIter->Usn() >= usnPrev );
                usnPrev = entryListIter->Usn();
#endif
            }

#if CIDBG==1
            Win4Assert( cEntriesInList == _aDelLogEntryList[i].Count() );
#endif
        }
    }

    _xPersStream->SetCount( cVolumes );
    _xPersStream->Flush();
}



//+---------------------------------------------------------------------------
//
//  Member:     CDeletionLog::MarkForDeletion
//
//  Synopsis:   Adds a deletion entry
//
//  History:    28-Jul-97     SitaramR       Created
//
//----------------------------------------------------------------------------

void CDeletionLog::MarkForDeletion( VOLUMEID volumeId,
                                    FILEID fileId,
                                    WORKID wid,
                                    USN usn )
{
    CLock lock(_mutex);

    XPtr<CDelLogEntry> xEntry( new CDelLogEntry( fileId, wid, usn ) );
    ULONG fakeVolId = _fakeVolIdMap.VolIdToFakeVolId( volumeId );

    Win4Assert( fakeVolId < RTL_MAX_DRIVE_LETTERS );

    if ( _aDelLogEntryList[fakeVolId].Count() == 0 )
    {
        //
        // Empty list case
        //
        _aDelLogEntryList[fakeVolId].Queue( xEntry.Acquire() );
    }
    else
    {
        CDelLogEntry *pEntryLast = _aDelLogEntryList[fakeVolId].GetLast();
        if ( xEntry->Usn() > pEntryLast->Usn() )
        {
            //
            // If the usn is less than the last entry's usn, then it means that it
            // is a usn that is being replayed, and there is no need to add it
            // to the deletion log again.
            //
            _aDelLogEntryList[fakeVolId].Queue( xEntry.Acquire() );
        }
    }
}


//+---------------------------------------------------------------------------
//
//  Member:     CDeletionLog::ProcessChangesFlush
//
//  Synopsis:   Process the list of changes that has been flushed by
//              framework/changelog and do the actual deletes from
//              the file id map.
//
//  Arguments:  [usnFlushInfoList] -- List of changes flushed
//
//  History:    28-Jul-97     SitaramR       Created
//
//----------------------------------------------------------------------------

void CDeletionLog::ProcessChangesFlush( CUsnFlushInfoList & usnFlushInfoList )
{
    CLock lock( _mutex );

    for ( ULONG i=0; i<usnFlushInfoList.Count(); i++ )
    {
        CUsnFlushInfo *pFlushInfo = usnFlushInfoList.Get(i);

        ULONG fakeVolId = _fakeVolIdMap.VolIdToFakeVolId( pFlushInfo->VolumeId() );

        Win4Assert( fakeVolId < RTL_MAX_DRIVE_LETTERS );

        CDelLogEntryList& entryList = _aDelLogEntryList[fakeVolId];
#if CIDBG==1
        USN usnPrev = 0;
#endif

        CDelLogEntryListIter entryListIter( entryList );
        while ( !entryList.AtEnd( entryListIter ) )
        {
            CDelLogEntry *pEntry = entryListIter.GetEntry();
            entryList.Advance( entryListIter );

#if CIDBG==1
            //
            // Check that usn's are monotonically increasing
            //
            Win4Assert( pEntry->Usn() >= usnPrev );
            usnPrev = pEntry->Usn();
#endif

            if ( pEntry->Usn() <= pFlushInfo->UsnHighest() )
            {
                entryList.RemoveFromList( pEntry );
                _fileIdMap.Delete( pEntry->FileId(), pEntry->WorkId() );
                delete pEntry;
            }
            else
            {
                //
                // Since the list is in increasing usn order, we are done
                // with this volume id.
                //
                break;
            }
        }
    }
}


//+---------------------------------------------------------------------------
//
//  Member:     CDeletionLog::GetSize
//
//  Synopsis:   Returns the size of the serialized stream in bytes
//
//  History:    28-Jul-97     SitaramR       Created
//
//----------------------------------------------------------------------------

ULONG CDeletionLog::GetSize()
{
    //
    // Start with a slop factor
    //
    ULONG ulSize = 1024;

    for ( ULONG i=0; i<RTL_MAX_DRIVE_LETTERS; i++ )
    {
        ulSize += sizeof(VOLUMEID)   // volume id field
                  + sizeof(ULONG)    // cEntries field
                  + _aDelLogEntryList[i].Count() * sizeof(CDelLogEntry);
    }

    return ulSize;
}


//+---------------------------------------------------------------------------
//
//  Member:     CDeletionLog::FatalCorruption
//
//  Synopsis:   Handles deletion log corruption
//
//  Arguments:  [cbRead]   -- Count of bytes read
//              [cbToRead] -- Count of bytes to read
//
//  History:    28-Jul-97     SitaramR       Created
//
//----------------------------------------------------------------------------

void CDeletionLog::FatalCorruption( ULONG cbRead, ULONG cbToRead )
{
    Win4Assert( !"Corrupt deletion log" );

    ciDebugOut(( DEB_ERROR,
                 "CDeletionLog: read %d bytes instead of %d\n",
                 cbRead,
                 cbToRead ));

    PStorage & storage = _cicat.GetStorage();
    storage.ReportCorruptComponent( L"Deletion log" );

    THROW( CException( CI_CORRUPT_CATALOG ) );
}