//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1991 - 2000.
//
//  File:       RESMAN.CXX
//
//  Contents:   Resource Manager
//
//  Classes:    CResManager
//
//  History:    08-Apr-91   BartoszM    Created
//              4-Jan-95    BartoszM    Separated Filter Manager
//              Jan-08-97   mohamedn    CFwEventItem and CDmFwEventItem
//              24-Feb-97   SitaramR    Push filtering
//
//----------------------------------------------------------------------------

#include <pch.cxx>
#pragma hdrstop

#include <cci.hxx>
#include <xact.hxx>
#include <pstore.hxx>
#include <cifailte.hxx>
#include <ciole.hxx>
#include <regevent.hxx>
#include <ciregkey.hxx>
#include <eventlog.hxx>
#include <cievtmsg.h>
#include <cifrmcom.hxx>
#include <fwevent.hxx>
#include <pidmap.hxx>
#include <identran.hxx>
#include <psavtrak.hxx>

#include "resman.hxx"
#include "ci.hxx"
#include "partn.hxx"
#include "pindex.hxx"
#include "mindex.hxx"
#include "idxids.hxx"
#include "indxact.hxx"
#include "merge.hxx"
#include "mmerglog.hxx"
#include "pendcur.hxx"
#include "fltstate.hxx"
#include "idle.hxx"
#include "notxact.hxx"
#include "lowres.hxx"

const ULONG lowDiskWaterMark = 3 * 512 * 1024; // 1.5 MB
const ULONG highDiskWaterMark = lowDiskWaterMark + 512 * 1024; // 2.0 MB
const ULONG minDiskFreeForMerge = lowDiskWaterMark;

class CRevertBoolValue {
public:
    CRevertBoolValue( BOOL & rfValue, BOOL fValue ) :
        _rfVal( rfValue ),
        _fRevert( TRUE )
    {
        Win4Assert( rfValue != fValue );
        _fPrevVal = _rfVal;
        _rfVal = fValue;
    }

    ~CRevertBoolValue()
    {
        if (_fRevert)
            _rfVal = _fPrevVal;
    }

    void Revert()
    {
        _rfVal = _fPrevVal;
        _fRevert = FALSE;
    }

private:
    BOOL &  _rfVal;
    BOOL    _fPrevVal;
    BOOL    _fRevert;
};


CMergeThreadKiller::CMergeThreadKiller( CResManager & resman )
    : _resman(resman), _fKill(TRUE)
{
}

CMergeThreadKiller::~CMergeThreadKiller()
{
    if ( _fKill )
    {
        _resman._fStopMerge = TRUE;
        _resman._thrMerge.Resume();
        _resman.StopMerges();
    }
}

//+---------------------------------------------------------------------------
//
//  Class:      CPendQueueTrans
//
//  Purpose:    Transaction for flushing the pending queue if there is a
//              failure while extracting entries out of the pending queue
//              and adding them to the changelog.
//
//  History:    9-11-95   srikants   Created
//
//----------------------------------------------------------------------------

class CPendQueueTrans : public CTransaction
{
public:

    CPendQueueTrans( CPendingQueue & pendQueue ) : _pendQueue( pendQueue )
    {
    }

    ~CPendQueueTrans()
    {
        if ( CTransaction::XActAbort == _status )
        {
            CLock lock( _pendQueue.GetMutex() );

            _pendQueue.LokFlushCompletedEntries();
        }
    }

private:

    CPendingQueue &     _pendQueue;

};

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::CResManager, public
//
//  Arguments:  [cat] -- catalog
//              [xact] -- transaction
//
//  History:    08-Apr-91   BartoszM    Created
//              Jan-07-96   mohamedn    CFwEventItem
//
//----------------------------------------------------------------------------

CResManager::CResManager(
        PStorage &storage,
        CCiFrameworkParams & params,
        ICiCDocStore * pDocStore,
        CI_STARTUP_INFO const & startupInfo,
        IPropertyMapper * pPropMapper,
        CTransaction& xact,
        XInterface<CIndexNotificationTable> & xIndexNotifTable )
:   _sigResman(eSigResman),
    _frmwrkParams( params ),
    _mergeTime(0),
    _storage ( storage ),
    _sKeyList(0),
    _idxTab( _storage.QueryIndexTable( xact ) ),
    _partList ( storage, *_idxTab, _sKeyList, xact, params ),
    _fresh ( storage, xact, _partList ),
    _partidToMerge ( partidInvalid ),
    _fStopMerge(FALSE),
    _pMerge(0),
#pragma warning( disable : 4355 )           // this used in base initialization
    _thrMerge ( MergeThread, this, TRUE ),  // create suspended
    _mergeKiller( *this ),
#pragma warning( default : 4355 )
    _activeDeletedIndex( _idxTab->GetDeletedIndex() ),
    _cQuery(0),
    _cFilteredDocuments(0),
    _pBackupWorkItem(0),
    _isBeingEmptied(FALSE),
    _isLowOnDiskSpace(FALSE),
    _isDismounted(FALSE),
    _isOutOfDate(FALSE),
    _isCorrupt(FALSE),
    _fFirstTimeUpdatesAreEnabled( TRUE ),
    _fPushFiltering( FALSE ),
    _fFlushWorkerActive( FALSE ),
    _configFlags( startupInfo.startupFlags ),
    _xIndexNotifTable( xIndexNotifTable.Acquire() ),
    _dwFilteringState( 0 ),
    _pFilterAgent( 0 )
{
    Win4Assert( 0 != pDocStore );

    //
    // Look for a resource monitor.  If none exists, use the default.
    //

    _xLowResDefault.Set( new CLowRes(_frmwrkParams) );

    SCODE sc = pDocStore->QueryInterface( IID_ICiCResourceMonitor,
                                          _xLowRes.GetQIPointer() );

    if ( FAILED(sc) )
    {
        _xLowResDefault->AddRef();
        _xLowRes.Set( _xLowResDefault.GetPointer() );
    }

    //


    //
    // The presence or absence of an actual pointer in _xIndexNotifTable,
    // indicates whether the client has specified pull filtering or push
    // filtering.
    //

    _fPushFiltering = !_xIndexNotifTable.IsNull();

    //
    // Initialize the resman in all the partitions.
    //
    CPartIter iter;
    for ( iter.LokInit(_partList); !iter.LokAtEnd(); iter.LokAdvance(_partList))
    {
        CPartition * pPart = iter.LokGet();
        pPart->SetResMan( this, FPushFiltering() );
    }

    if ( _fPushFiltering )
    {
        //
        // In push filtering, an identity translator is used
        //

        CIdentityNameTranslator *pIdentityTrans = new CIdentityNameTranslator();

        sc = pIdentityTrans->QueryInterface( IID_ICiCDocNameToWorkidTranslator,
                                             _translator.GetQIPointer() );
        Win4Assert( SUCCEEDED( sc ) );

        _translator->Release();              // QI does an AddRef
    }
    else
    {
        //
        // In pull filtering, the client provides the translator, which may
        // be the Ex version.
        //

        sc = pDocStore->QueryInterface( IID_ICiCDocNameToWorkidTranslatorEx,
                                        _translator2.GetQIPointer() );

        if ( SUCCEEDED(sc) )
            sc = _translator2->QueryInterface( IID_ICiCDocNameToWorkidTranslator,
                                               _translator.GetQIPointer() );
        else
            sc = pDocStore->QueryInterface( IID_ICiCDocNameToWorkidTranslator,
                                            _translator.GetQIPointer() );
    }

    if ( S_OK != sc )
        THROW( CException(sc) );

    //
    // Create a workIdToDocName converter object.
    //
    _xWorkidToDocName.Set( new CWorkIdToDocName( _translator.GetPointer(), _translator2.GetPointer() ) );

    ICiCAdviseStatus * pAdviseStatus;
    sc = pDocStore->QueryInterface( IID_ICiCAdviseStatus,
                                    (void **) & pAdviseStatus );
    if ( S_OK != sc )
        THROW( CException(sc) );

    _adviseStatus.Set( pAdviseStatus );
    _prfCounter.SetAdviseStatus( pAdviseStatus );

    ICiCPropertyStorage * pPropStore;
    sc = pDocStore->QueryInterface( IID_ICiCPropertyStorage,
                                    (void **) & pPropStore );
    if ( S_OK != sc )
        THROW( CException(sc) );

    _propStore.Set( pPropStore );

    pDocStore->AddRef();
    _docStore.Set( pDocStore );

    pPropMapper->AddRef();
    _mapper.Set( pPropMapper );

    ULONG mergeTime = _frmwrkParams.GetMasterMergeTime();
    _mergeTime = CMasterMergePolicy::ComputeMidNightMergeTime(mergeTime);

    if ( -1 == _mergeTime )
    {
       CFwEventItem   item(EVENTLOG_ERROR_TYPE,
                           MSG_CI_BAD_SYSTEM_TIME,
                           0);

       item.ReportEvent( _adviseStatus.GetReference() );

       THROW( CException( STATUS_INVALID_PARAMETER ) );
    }

    //
    // Restore the information associated with any master merge that
    // was stopped when the drive was dismounted.
    //
    _partList.RestoreMMergeState(*this, _storage);

    _partIter.LokInit( _partList );

    _isLowOnDiskSpace = LokCheckIfDiskLow( *this,
                                           _storage,
                                           _isLowOnDiskSpace,
                                           _adviseStatus.GetReference() );

    _thrMerge.SetPriority ( _frmwrkParams.GetThreadPriorityMerge() );

    _mergeKiller.Defuse();
    _thrMerge.Resume();

    //
    //  Set the merge progress indicator to 0, and refresh other counters
    //

    _prfCounter.Update( CI_PERF_MERGE_PROGRESS, 0 );

    LokUpdateCounters();

    //
    // Enable or disables updates/notifications based on disk space available
    // and whether the catalog is readonly
    //
    if ( _isLowOnDiskSpace || _storage.IsReadOnly() )
    {
        DisableUpdates( partidDefault );
        _state.LokSetState( eUpdatesDisabled );
    }
    else
    {
        EnableUpdates( partidDefault );
        SCODE sc = _docStore->EnableUpdates();
        if ( SUCCEEDED( sc ) )
           _state.LokSetState( eSteady );
        else
           _state.LokSetState( eUpdatesToBeEnabled );
    }
}

//+-------------------------------------------------------------------------
//
//  Member:     CResManager::~CResManager, public
//
//  Synopsis:   Shuts down merge(s)
//
//  History:    13-Aug-93 KyleP     Created
//
//--------------------------------------------------------------------------

CResManager::~CResManager()
{
    _workMan.AbortWorkItems();
    LokDeleteFlushWorkItems();
    _workMan.WaitForDeath();

    StopMerges();

    #if CIDBG==1
    // ======================================
    {
        CPriLock    lock(_mutex);
        Win4Assert( 0 == _pBackupWorkItem );
    }
    // ======================================
    #endif  // CIDBG==1
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::MergeThread, public
//
//  Synopsis:   Entry point for the thread responsible
//              for asynchronous merges
//
//  History:    05-Mar-92   BartoszM    Created
//
//----------------------------------------------------------------------------
DWORD WINAPI CResManager::MergeThread( void* self )
{
    if ( !((CResManager*)self)->_fStopMerge )
        ((CResManager*)self)->DoMerges();

    return 0;
}

//==============
// STATE CHANGES
//==============

//+---------------------------------------------------------------------------
//
//  Function:   Dismount
//
//  Synopsis:   Dismounts the catalog by stopping any in-progress merge
//              and also finishing off any pending writes ( eg. ChangeLog).
//
//  History:    6-20-94   srikants   Created
//
//----------------------------------------------------------------------------

NTSTATUS CResManager::Dismount()
{
    ciDebugOut(( DEB_ITRACE, "*** CI: Initiating DisMount ***\n" ));

    NTSTATUS status = STATUS_SUCCESS;

    TRY
    {
        //
        // Inform all the partitions to cleanup. We have to abort any
        // in-progress merges without having to take a lock. There could
        // be another thread (like the filter thread) with the lock doing
        // a long operation. In one case we had it trying to create a new
        // fresh test and the memory writer could not make any progress
        // because the merge was continuing to dirty the pages.
        //

        // There is no need to take a lock because the partitions are not
        // going away and the operation we are about to do is just turning
        // on a one-way flag.
        //
        {
            PARTITIONID partid = 1;
            CPartition* pPart = _partList.LokGetPartition ( partid );
            pPart->PrepareForCleanup();
        }

        //
        // Stop in progress merges.
        //

        StopMerges();

        //
        // Dismount each of the partitions.
        //
        // ================================================
        {
            CPriLock lock( _mutex );
            Win4Assert( !_isDismounted );

            _isDismounted = TRUE;

            CKeyList * pKeyList = _sKeyList.Acquire();
            delete pKeyList;

            CPartIter iter;
            for ( iter.LokInit(_partList); !iter.LokAtEnd(); iter.LokAdvance(_partList))
            {
                CPartition * pPart = iter.LokGet();

                // =======================================================
                CChangeTrans  xact( *this, pPart );

                if ( STATUS_SUCCESS == pPart->LokDismount( xact ) )
                {
                    xact.LokCommit();
                }
                //=========================================================
            }

            if ( _pBackupWorkItem )
                _pBackupWorkItem->GetSaveProgressTracker().SetAbort();
        }
        // ================================================

        _workMan.WaitForDeath();

    }
    CATCH( CException, e )
    {
        status = e.GetErrorCode();
        ciDebugOut(( DEB_ERROR,
            "ContentIndex Dismount Failed. Error Code 0x%X\n", status ));
    }
    END_CATCH

    ciDebugOut(( DEB_ITRACE, "*** CI: Completed DisMount ***\n" ));

    return status;
}

//+---------------------------------------------------------------------------
//
//  Function:   CResManager::Empty, public
//
//  Synopsis:   Releases resources associated with the resman object.  This
//              includes all indexes, the persistent freshlog, the persistant
//              changelog, the freshtest, and the master merge log.
//
//  History:    15-Aug-94   DwightKr    Created
//
//----------------------------------------------------------------------------
void CResManager::Empty()
{
    CPriLock lock( _mutex );

    _isBeingEmptied = TRUE;

    //
    //  Cancel any connections to the CI Filter service.
    //
    _pFilterAgent->LokCancel();

    //
    //  Delete everything from the index table here first.
    //
    _idxTab->LokEmpty();

    //
    //  If anything fails after this point, chkdsk /f or autochk will
    //  release the disk storage associated with the leaked objects
    //  since they are no longer part of the persistent index list.
    //

    //
    //  For each partition, zombify all indexes.
    //
    CPartIter iter;
    for ( iter.LokInit(_partList); !iter.LokAtEnd(); iter.LokAdvance(_partList))
    {
        CPartition* pPart = iter.LokGet();

        //
        //  Zombify all indexes in this partition, and delete them if their
        //  ref-count is 0.
        //
        CIndex ** aIndex = 0;
        TRY
        {
            unsigned cIndex;
            aIndex = pPart->LokZombify( cIndex );

            ReleaseIndexes( cIndex, aIndex, NULL );
        }
        CATCH ( CException, e )
        {
        }
        END_CATCH

        delete [] aIndex;

        //
        //  Delete the change log associated with this partition.  These
        //  routines do not throw exceptions.
        //
        WORKID widChangeLog = _partList.GetChangeLogObjectId( pPart->GetId() );
        if ( widChangeLog != widInvalid)
            _storage.RemoveObject( widChangeLog );

        WORKID widMMergeLog;
        WORKID widDummy;
        pPart->GetMMergeObjectIds( widMMergeLog, widDummy, widDummy );
        if ( widMMergeLog != widInvalid)
            _storage.RemoveObject( widMMergeLog );
    }

    _fresh.LokEmpty();          // Release storage associated with fresh test

    WORKID widFreshLog  = _storage.GetSpecialItObjectId( itFreshLog );

#ifdef KEYLIST_ENABLED
    WORKID widKeyList   = _storage.GetSpecialItObjectId( itMMKeyList );
#else
    WORKID widKeyList   = widInvalid;
#endif  //

    WORKID widPhrLat    = _storage.GetSpecialItObjectId( itPhraseLat );

    if ( widFreshLog != widInvalid)
        _storage.RemoveObject( widFreshLog );
    if ( widKeyList != widInvalid)
        _storage.RemoveObject( widKeyList );
    if ( widPhrLat != widInvalid)
        _storage.RemoveObject( widPhrLat );
}

//+---------------------------------------------------------------------------
//
//  Function:   StopCurrentMerge
//
//  Synopsis:   Aborts any in-progress merge.
//
//  History:    5-04-94   srikants   Created
//
//----------------------------------------------------------------------------

NTSTATUS CResManager::StopCurrentMerge()
{
    {
        CPriLock lock( _mutex );

        //
        // If we're doing a merge right now then kill it off.
        //

        if ( 0 != _pMerge )
            _pMerge->LokAbort();

    }

    return STATUS_SUCCESS;
}

//+---------------------------------------------------------------------------
//
//  Member:      CResManager::DisableUpdates, public
//
//  Arguments:   [partid] -- partition id
//
//  History:     24-Dec-94  Anonymous   Created
//
//----------------------------------------------------------------------------
void CResManager::DisableUpdates( PARTITIONID partid )
{
    CPriLock lock( _mutex );
    CPartition * pPart = _partList.LokGetPartition( partid );

    Win4Assert( 0 != pPart );
    pPart->LokDisableUpdates();
}


//+---------------------------------------------------------------------------
//
//  Member:      CResManager::EnableUpdates, public
//
//  Arguments:   [partid]    -- partition id
//
//  History:     24-Dec-94  Anonymous   Created
//
//----------------------------------------------------------------------------
void CResManager::EnableUpdates( PARTITIONID partid )
{
    CPriLock lock( _mutex );
    CPartition * pPart = _partList.LokGetPartition( partid );

    Win4Assert( 0 != pPart );
    pPart->LokEnableUpdates( _fFirstTimeUpdatesAreEnabled );

    _fFirstTimeUpdatesAreEnabled = FALSE;
}


//+---------------------------------------------------------------------------
//
//  Function:   LokCheckIfDiskLow
//
//  Synopsis:   Checks if we are running low on free disk space. It has
//              a "LowWaterMark" and a "HighWaterMark".
//
//              If current state is "not full", then it will check to
//              see if the free disk space is < the LowWaterMark.
//
//              if current state is "full", then it will check to see if
//              the free disk space is < the HighWaterMark.
//
//              The HighWaterMark is > the LowWaterMark to prevent hysterisis.
//
//  Arguments:  [resman]         -- Resource manager
//              [storage]        -- Storage object.
//              [fCurrent]       -- Current state.
//              [adviseStatus]   -- reference to ICiCAdviseStatus
//
//  Returns:    TRUE if we are running low on disk space.
//              FALSE otherwise.
//
//  History:    10-11-94   srikants   Created
//              Jan-07-96  mohamedn   CFwEventItem
//
//----------------------------------------------------------------------------

BOOL LokCheckIfDiskLow( CResManager & resman,
                        PStorage & storage,
                        BOOL fIsCurrentFull,
                        ICiCAdviseStatus & adviseStatus )
{
    BOOL fLowOnDisk = fIsCurrentFull;

    TRY
    {
        __int64 diskTotal, diskRemaining;
        storage.GetDiskSpace( diskTotal, diskRemaining );

        if ( !fIsCurrentFull )
            fLowOnDisk = diskRemaining < lowDiskWaterMark;
        else
            fLowOnDisk = diskRemaining < highDiskWaterMark;

        //
        // It is okay to read it without mutex as it is only a heuristic.
        //
        if ( fLowOnDisk && !fIsCurrentFull && !storage.IsReadOnly() )
        {
            ciDebugOut(( DEB_WARN, "****YOU ARE RUNNING LOW ON DISK SPACE****\n"));
            ciDebugOut(( DEB_WARN, "****PLEASE FREE UP SOME SPACE        ****\n"));

            ULONG mbToFree = highDiskWaterMark/(1024*1024);

            ULONG mbIndex;
            resman.IndexSize( mbIndex );
            mbToFree = max( mbToFree, mbIndex );
            mbToFree = min( mbToFree, 50 ); // don't ask for more than 50 MB

            CFwEventItem item(  EVENTLOG_AUDIT_FAILURE,
                                MSG_CI_LOW_DISK_SPACE,
                                2);

            item.AddArg(storage.GetVolumeName() );
                        item.AddArg(mbToFree);

            item.ReportEvent(adviseStatus);
        }
        else if ( fIsCurrentFull && !fLowOnDisk )
        {
            ciDebugOut(( DEB_WARN, "****DISK SPACE FREED UP              ****\n"));
        }
    }
    CATCH( CException,e )
    {
        ciDebugOut(( DEB_ERROR,
                     "Error 0x%X while getting disk space info\n",
                     e.GetErrorCode() ));
    }
    END_CATCH

    return ( fLowOnDisk );
}


//+---------------------------------------------------------------------------
//
//  Function:   NoFailFreeResources
//
//  Synopsis:   deletes a big wordlist and schedules refiltering
//
//  History:    6-Jan-95    BartoszM    Created
//
//----------------------------------------------------------------------------

void CResManager::NoFailFreeResources()
{
    ciDebugOut(( DEB_WARN, "Running out of resources.  " ));
    TRY
    {

        PARTITIONID partId = 1;

        CPriLock   lock(_mutex);
        CPartition * pPart = LokGetPartition( partId );


        //
        // Before calling remove word list, we must append any refiled
        // documents to the doc queue. This is because, the docqueue
        // can handle only one set of refiled documents.
        //
        {
            // =========================================
            CChangeTrans    xact( *this, pPart );
            pPart->LokAppendRefiledDocs( xact );
            xact.LokCommit();
            // =========================================
        }

        LokNoFailRemoveWordlist( pPart );
    }
    CATCH( CException, e )
    {
    }
    END_CATCH
}


//+-------------------------------------------------------------------------
//
//  Member:     CResManager::IsMemoryLow, private
//
//  Returns:    TRUE if we're in a low memory situation
//
//  History:    10-May-93 AmyA      Created from old DoUpdates
//               3-May-96 dlee      #if 0'ed it out because the memory
//                                  load # isn't reliable, and we've
//                                  got LOTS of other allocations anyway.
//              05-Nov-97 KyleP     New approach
//
//--------------------------------------------------------------------------

BOOL CResManager::IsMemoryLow()
{
    SCODE sc = _xLowRes->IsMemoryLow();

    if ( E_NOTIMPL == sc )
        sc = _xLowResDefault->IsMemoryLow();

    return ( S_OK == sc );
}

//+-------------------------------------------------------------------------
//
//  Member:     CResManager::IsIoHigh, private
//
//  Returns:    TRUE if the system is performing a 'lot' of I/O
//
//  History:    10-Dec-97  KyleP    Created
//
//  Notes:      This call takes time (~ 5 sec) to complete.
//
//--------------------------------------------------------------------------

BOOL CResManager::IsIoHigh()
{
    SCODE sc = _xLowRes->IsIoHigh( &_fStopMerge );

    if ( E_NOTIMPL == sc )
        sc = _xLowResDefault->IsIoHigh( &_fStopMerge );

    return ( S_OK == sc );
}

//+-------------------------------------------------------------------------
//
//  Member:     CResManager::IsBatteryLow, private
//
//  Returns:    TRUE if the system is running low on battery power.
//
//  History:    16-Jul-98  KyleP    Created
//
//  Notes:      By default, even 100% battery (as opposed to A/C) may be
//              considered low.
//
//--------------------------------------------------------------------------

BOOL CResManager::IsBatteryLow()
{
    SCODE sc = _xLowRes->IsBatteryLow();

    if ( E_NOTIMPL == sc )
        sc = _xLowResDefault->IsBatteryLow();

    return ( S_OK == sc );
}

//+-------------------------------------------------------------------------
//
//  Member:     CResManager::IsOnBatteryPower, private
//
//  Returns:    TRUE if the system is running on battery power.
//
//  History:    01-Oct-00  dlee    Created
//
//--------------------------------------------------------------------------

BOOL CResManager::IsOnBatteryPower()
{
    SCODE sc = _xLowRes->IsOnBatteryPower();

    if ( E_NOTIMPL == sc )
        sc = _xLowResDefault->IsOnBatteryPower();

    return ( S_OK == sc );
} //IsOnBatteryPower

//+-------------------------------------------------------------------------
//
//  Member:     CResManager::SampleUserActivity, private
//
//  Returns:    - nothing -
//
//  History:    31 Jul 98  AlanW    Created
//
//--------------------------------------------------------------------------

void CResManager::SampleUserActivity()
{
    SCODE sc = _xLowRes->SampleUserActivity();

    if ( E_NOTIMPL == sc )
        _xLowResDefault->SampleUserActivity();

    return;
}

//+-------------------------------------------------------------------------
//
//  Member:     CResManager::IsUserActive, private
//
//  Arguments:  [fCheckLongTerm] - TRUE if long-term activity should be checked.
//
//  Returns:    TRUE if the interactive user is using the keyboard or mouse
//
//  History:    28 Jul 98  AlanW    Created
//
//--------------------------------------------------------------------------

BOOL CResManager::IsUserActive(BOOL fCheckLongTerm)
{
    SCODE sc = _xLowRes->IsUserActive(fCheckLongTerm);

    if ( E_NOTIMPL == sc )
        sc = _xLowResDefault->IsUserActive(fCheckLongTerm);

    return ( S_OK == sc );
}

//======================
//  STATE AND STATISTICS
//======================

//+-------------------------------------------------------------------------
//
//  Member:     CResManager::CountPendingUpdates, public
//
//  Arguments:  [secCount (output)] -- Count of secondary Q Updates
//
//  Returns:    Count of pending updates.
//
//  History:    14-Dec-92 KyleP     Created
//
//  Notes:      If wordlists are being constructed the count may be low.
//
//--------------------------------------------------------------------------

unsigned CResManager::CountPendingUpdates(unsigned& secCount)
{
    CPriLock lock( _mutex );
    unsigned count = 0;
    CPartIter iter;
    secCount = 0;
    for ( iter.LokInit(_partList); !iter.LokAtEnd(); iter.LokAdvance(_partList))
    {
        count += iter.LokGet()->LokCountUpdates();

        secCount += iter.LokGet()->LokCountSecUpdates();
    }
    return(count + secCount);
}

//+-------------------------------------------------------------------------
//
//  Member:     CResManager::UpdateCounters, public
//
//  Returns:    Updates performance counters.
//
//  History:    05-Jan-95 BartoszM     Created
//
//--------------------------------------------------------------------------

void CResManager::LokUpdateCounters()
{
    if ( !_isDismounted )
    {
        _prfCounter.Update( CI_PERF_NUM_WORDLIST, _partList.LokWlCount() );

        unsigned cSecQDocuments;
        unsigned cUnfilteredDocs = CountPendingUpdates( cSecQDocuments );
        _prfCounter.Update( CI_PERF_FILES_TO_BE_FILTERED, cUnfilteredDocs );
        _prfCounter.Update( CI_PERF_DEFERRED_FILTER_FILES, cSecQDocuments );

        _prfCounter.Update( CI_PERF_NUM_UNIQUE_KEY, _sKeyList->MaxKeyIdInUse() );
        _prfCounter.Update( CI_PERF_DOCUMENTS_FILTERED, _cFilteredDocuments );

        ULONG cDocuments;
        GetClientState( cDocuments );
        _prfCounter.Update( CI_PERF_NUM_DOCUMENTS, cDocuments );
    }
}

//====================
// UPDATES AND QUERIES
//====================

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::ReserveUpdate, public
//
//  Arguments:  [wid] -- Used to confirm hint.
//
//  Returns:    Hint to position of reserved slot.
//
//  History:    30-Aug-95   KyleP       Created
//
//----------------------------------------------------------------------------

unsigned CResManager::ReserveUpdate( WORKID wid )
{
    CPriLock lock ( _pendQueue.GetMutex() );

    return _pendQueue.LokPrepare( wid );
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::UpdateDocument, public
//
//  Arguments:  [iHint]    -- Positional hint
//              [wid]      -- wid to add/delete
//              [partid]   -- partition containing this wid
//              [usn]      -- USN associated with change
//              [volumeId] -- Volume Id
//              [action]   -- addition / deletion
//
//  History:    08-Apr-91   BartoszM    Created
//              08-Oct-93   BartoszM    Rewrote to accept single document
//              30-Aug-95   KyleP       Use reserved slots
//
//----------------------------------------------------------------------------

SCODE CResManager::UpdateDocument( unsigned iHint,
                                   WORKID wid,
                                   PARTITIONID partid,
                                   USN usn,
                                   VOLUMEID volumeId,
                                   ULONG action )
{
    if ( !IsIndexingEnabled() )
        return CI_E_FILTERING_DISABLED;

    Win4Assert ( partid == 1 );

    //
    // Try to avoid lock contention
    // with filter agent
    //
    _pFilterAgent->SlowDown();


    CPriLock lock ( _mutex );

    BOOL fComplete;

    {
        CPriLock lock2( _pendQueue.GetMutex() );

        if ( _isBeingEmptied )          // The content index is empty
        {
            //
            // Just get rid of any pending entries in the queue.
            //
            if ( !_pendQueue.LokComplete( iHint, wid, usn, volumeId, partid, action ) )
            {
                while ( _pendQueue.LokRemove( wid, usn, volumeId, partid, action ) )
                    ;
            }
            return CI_E_SHUTDOWN;
        }

        //
        // The if clause triggers when this document is the only one
        // on the queue.
        //

        fComplete = _pendQueue.LokComplete( iHint, wid, usn, volumeId, partid, action );
    }

    SCODE sc = S_OK;

    if ( fComplete )
    {
        CPartition* pPart = _partList.LokGetPartition ( partid );

        CChangeTrans xact( *this, pPart );
        sc = pPart->LokUpdateDocument ( xact, wid, usn, volumeId, action, 1, 0 );
        xact.LokCommit();

        _pFilterAgent->LokWakeUp();
    }
    else
    {
        BOOL fGotOne = FALSE;

        CPendQueueTrans  pendQueueTrans( _pendQueue );

        while ( TRUE )
        {
            BOOL fGotAnother;

            {
                CLock lock2( _pendQueue.GetMutex() );
                fGotAnother = _pendQueue.LokRemove( wid, usn, volumeId, partid, action );
            }

            if ( fGotAnother )
            {
                fGotOne = TRUE;

                CPartition* pPart = _partList.LokGetPartition ( partid );

                CChangeTrans xact( *this, pPart );
                sc = pPart->LokUpdateDocument ( xact, wid, usn, volumeId, action, 1, 0 );
                xact.LokCommit();
            }
            else
                break;
        }

        pendQueueTrans.Commit();

        if ( fGotOne )
            _pFilterAgent->LokWakeUp();
    }

    return sc;
}


//+---------------------------------------------------------------------------
//
//  Member:     CResManager::FlushUpdates
//
//  Synopsis:   Flushes all update notifications to disk
//
//  History:    27-Jun-97   SitaramR   Created
//
//----------------------------------------------------------------------------

void CResManager::FlushUpdates()
{
    CPriLock lock( _mutex );
    CPartition * pPart = _partList.LokGetPartition( partidDefault );

    Win4Assert( 0 != pPart );

    pPart->LokFlushUpdates();
}


//+---------------------------------------------------------------------------
//
//  Member:     CResManager::QueryIndexes, public
//
//  Arguments:  [cPartitions]     -- count of partitions
//              [aPartID]         -- array of partition id's
//              [freshTest]       -- return arg.: fresh test
//              [cInd]            -- return arg: count of indexes
//              [cPendingUpdates] -- Pending update 'threshold'.  If fewer
//                                   pending updates, the pending wids are
//                                   returned in *pcurPending.
//              [pcurPending]     -- Pending cursors stored here.
//              [pFlags]          -- return arg: status of indexes
//
//  Returns:    Array of pointers to indexes
//
//  History:    08-Oct-91   BartoszM    Created
//
//  Notes:      Called by Query
//              Indexes and fresh test have ref count increased
//              Flags may change to indicate status of indexes
//
//----------------------------------------------------------------------------

CIndex** CResManager::QueryIndexes (
    unsigned cPartitions,
    PARTITIONID aPartID[],
    CFreshTest** freshTest,
    unsigned& cInd,
    ULONG cPendingUpdates,
    CCurStack * pcurPending,
    ULONG* pFlags )
{

    unsigned count = 0;
    CPartition* pPart;

    CPriLock lock ( _mutex );

    if ( _isCorrupt )
        THROW( CException( CI_CORRUPT_DATABASE ) );

    if ( _isBeingEmptied )   // Content index is empty or corrupt
    {
        cInd = 0;
        cPartitions = 0;

        return 0;
    }

    for ( unsigned i = 0; i < cPartitions; i++ )
    {
        pPart = _partList.LokGetPartition( aPartID [i] );
        count += pPart->LokIndexCount();
    }

    XArray<CIndex *> apIndex( count );

#if CIDBG || DBG
    unsigned j = 0;
#endif

    for ( i = 0; i < cPartitions; i++ )
    {
        pPart = _partList.LokGetPartition(aPartID [i]);

#if CIDBG || DBG
        j +=
#endif // CIDBG || DBG
        pPart->LokGetIndexes ( &apIndex[i] );

        if ( LokIsScanNeeded() && !_storage.IsReadOnly() )
            *pFlags |=  CI_NOT_UP_TO_DATE;

        //
        // If we're looking for pending updates, get them.  Otherwise just
        // set the out-of-date flag.
        //

        unsigned cPending = pPart->LokCountUpdates();

        cPending += pPart->LokCountSecUpdates();

        if ( cPending != 0 )
        {
            ULONG fFlags = CI_NOT_UP_TO_DATE;   // assume non-success

            if ( cPendingUpdates > 0 )
            {
                Win4Assert( 0 != pcurPending );

                if ( cPending <= cPendingUpdates )
                {
                    XArray<WORKID> pWid( cPending );

                    BOOL fSucceeded = pPart->LokGetPendingUpdates(
                                                    pWid.GetPointer(),
                                                    cPending );
                    if ( fSucceeded )
                    {
                        Win4Assert( cPending > 0 );

                        XPtr<CPendingCursor> xCursor( new CPendingCursor( pWid, cPending ) );
                        pcurPending->Push( xCursor.GetPointer() );
                        xCursor.Acquire();
                        fFlags = 0;
                    }
                }
            }
            *pFlags |= fFlags;
        }
    }

    //
    // Also check to see if we are actually in the process of filtering some
    // documents.
    //
    if ( _docList.Count() > 0 )
    {
        if ( _docList.Count() <= cPendingUpdates )
        {
            Win4Assert( 0 != pcurPending );

            XArray<WORKID> pWid( _docList.Count() );

            ciDebugOut((DEB_FILTERWIDS,
                       "CResManager::QueryIndexes - pending documents: %d %x\n",
                        _docList.Count(), pWid.GetPointer()));

            _docList.LokGetWids( pWid );
            XPtr<CPendingCursor> xCursor( new CPendingCursor( pWid, _docList.Count() ) );
            pcurPending->Push( xCursor.GetPointer() );
            xCursor.Acquire();
        }
        else
            *pFlags |= CI_NOT_UP_TO_DATE;
    }

    //
    // Also check documents pending notifcation that may be complete but
    // out-of-order.
    //

    {
        CPriLock lock( _pendQueue.GetMutex() );

        unsigned cwidPending = _pendQueue.LokCountCompleted();

        if ( cwidPending > 0 )
        {
            if ( cwidPending <= cPendingUpdates )
            {
                Win4Assert( 0 != pcurPending );

                XArray<WORKID> pWid( cwidPending );

                _pendQueue.LokGetCompleted( pWid.GetPointer() );

                XPtr<CPendingCursor> xCursor( new CPendingCursor( pWid, cwidPending ) );
                pcurPending->Push( xCursor.GetPointer() );
                xCursor.Acquire();
            }
            else
                *pFlags |= CI_NOT_UP_TO_DATE;
        }
    }

    //
    // Finally, check for pending scans.
    //

    if ( _isOutOfDate )
        *pFlags |= CI_NOT_UP_TO_DATE;

    Win4Assert ( j == count );

    *freshTest = _fresh.LokGetFreshTest();

    cInd = count;

    return apIndex.Acquire();
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::ReleaseIndexes, public
//
//  Synopsis:   Decrements ref counts, deletes if indexes
//              marked to be deleted.
//
//  Arguments:  [cInd] -- count of indexes
//              [apIndex] -- array of indexes
//              [freshTest] -- fresh test
//
//  History:    08-Oct-91   BartoszM    Created
//
//  Notes:      Takes ResMan lock
//
//----------------------------------------------------------------------------

void CResManager::ReleaseIndexes ( unsigned cInd, CIndex** apIndex,
    CFreshTest* pFreshTest )
{
    ciDebugOut (( DEB_ITRACE, "Release Indexes\n" ));

    CPriLock lock ( _mutex );

    for ( unsigned i = 0; i < cInd; i++ )
    {
        if ( apIndex[i] != 0 )
            LokReleaseIndex ( apIndex[i] );
    }

    if ( pFreshTest)
        _fresh.LokReleaseFreshTest (pFreshTest);
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::BackupContentIndexData
//
//  Synopsis:   Private method called by the merge thread to backup the
//              content index data. If successful, it sets the status of
//              the backup work item to indicate success. Otherwise, it
//              must be considered to have failed.
//
//  History:    3-18-97   srikants   Created
//
//----------------------------------------------------------------------------

void CResManager::BackupContentIndexData()
{
    Win4Assert( 0 != _pBackupWorkItem );

    ciDebugOut(( DEB_WARN, "Starting Backup of Ci Data\n" ));

    //
    // Create the backup object.
    //
    CPartition *pPartition =_partList.LokGetPartition ( partidDefault );

    CBackupCiPersData   backup( *_pBackupWorkItem,
                                *this,
                                *pPartition );

    // =========================================
    {
        CPriLock    lock(_mutex);
        backup.LokGrabResources();
    }
    // =========================================

    backup.BackupIndexes();

    // =========================================
    {
        CPriLock    lock(_mutex);
        backup.LokBackupMetaInfo();
        ciDebugOut(( DEB_WARN, "Completed Backup of Ci Data\n" ));
        _pBackupWorkItem->LokSetStatus( STATUS_SUCCESS );
    }
    // =========================================
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::LokUpdateBackupMergeProgress
//
//  Synopsis:   Updates the backup progress during the merge.
//
//  History:    3-20-97   srikants   Created
//
//----------------------------------------------------------------------------

void CResManager::LokUpdateBackupMergeProgress()
{
    Win4Assert( 0 != _pBackupWorkItem );

    CCiFrmPerfCounter counter( _adviseStatus.GetPointer(),
                               CI_PERF_MERGE_PROGRESS );
    DWORD dwMergeProgress = counter.GetCurrValue();

    //
    // Assuming Merge is half of the work, we will assume half
    // progress. So make the denominator 200 instead of 100.
    //
    if ( _pMerge )
    {
        const cShadowMergePart = 5;

        if ( _pBackupWorkItem->IsFullSave() )
        {
            if ( _pMerge->GetMergeType() != mtMaster )
            {
                //
                // The shadow merge preceding a master is approx. 5%
                // say.
                //
                dwMergeProgress = cShadowMergePart;
            }
            else
            {
                dwMergeProgress = min(dwMergeProgress + cShadowMergePart, 100);
            }
        }

        _pBackupWorkItem->LokUpdateMergeProgress( (ULONG) dwMergeProgress, 100 );
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::BackupCIData
//
//  Synopsis:   External method to backup content index data. It creates
//              a backup work item and waits for the backup to complete.
//
//  Arguments:  [storage]         - Destination storage
//              [fFull]           - [in/out] Set to TRUE if a full merge is
//              needed on input; On output will be set to TRUE if a full
//              merge was done.
//              [progressTracker] - Progress tracker.
//
//  History:    3-18-97   srikants   Created
//
//----------------------------------------------------------------------------

SCODE CResManager::BackupCIData( PStorage & storage,
                                 BOOL & fFull,
                                 XInterface<ICiEnumWorkids> & xEnumWorkids,
                                 PSaveProgressTracker & progressTracker )
{
    if ( !IsIndexMigrationEnabled() || !IsIndexingEnabled() )
        return CI_E_INVALID_STATE;

    SCODE sc = S_OK;

    Win4Assert( fFull );

    TRY
    {

        // ===================================================
        {
            CLock   lock(_mutex);
            if ( _isDismounted )
                THROW( CException( CI_E_SHUTDOWN ) );

            // There can be only one operation going on at a time.

            if ( 0 != _pBackupWorkItem )
                THROW( CException( CI_E_INVALID_STATE ) );

            _pBackupWorkItem = new CBackupCiWorkItem( storage,
                                                      fFull,
                                                      progressTracker );

            _eventMerge.Set();
        }
        // ===================================================

        ciDebugOut(( DEB_WARN, "Waiting for backup to complete\n" )) ;
        //
        // Wait for the work-item to be completed.
        //
        while ( TRUE )
        {
            DWORD const dwWaitTime = 1 * 60 * 1000; // 1 minute in millisecs
            _pBackupWorkItem->WaitForCompletion( dwWaitTime );

            //============================================================
            {
                CPriLock    lock(_mutex);
                if ( _pBackupWorkItem->LokIsDone() )
                    break;

                //
                // If there is a merge going on, we must update the progress.
                //
                if ( _pBackupWorkItem->LokIsDoingMerge() )
                    LokUpdateBackupMergeProgress();

                _pBackupWorkItem->LokReset();
            }
            //============================================================

        } // of while


        sc= _pBackupWorkItem->GetStatus();
        ciDebugOut(( DEB_WARN, "Backup completed with code 0x%X\n", sc ));
        if ( S_OK == sc )
        {
            fFull = _pBackupWorkItem->IsFullSave();
            xEnumWorkids.Set( _pBackupWorkItem->AcquireWorkIdEnum() );
        }

        // ====================================
        {
            CLock   lock(_mutex);
            delete _pBackupWorkItem;
            _pBackupWorkItem = 0;
        }
        // ====================================
    }
    CATCH( CException,e )
    {
        sc = HRESULT_FROM_WIN32( e.GetErrorCode() );
        Win4Assert( !"How Did we Come Here" );
    }
    END_CATCH

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::CiState, public
//
//  Arguments:  [state] -- internal state of the CI
//
//  History:    01-Nov-95   DwightKr    Created
//
//
//----------------------------------------------------------------------------

#define setState( field, value ) \
    if ( state.cbStruct >= ( offsetof( CIF_STATE, field) +   \
                             sizeof( state.field ) ) )       \
    {                                                        \
        state.field = ( value );                             \
    }

#define roundup(a, b) ((a%b) ? (a/b + 1) : (a/b))

NTSTATUS CResManager::CiState(CIF_STATE & state)
{
    CPriLock lock( _mutex );

    if ( _isDismounted )
        return CI_E_SHUTDOWN;

    if ( _isCorrupt )
        return CI_CORRUPT_DATABASE;

    setState( cWordList, _partList.LokWlCount() );
    setState( cPersistentIndex, _partList.LokIndexCount() - state.cWordList );

    // Get the perf counter for the # of queries executed

    Win4Assert( !_adviseStatus.IsNull() );
    long cQ;
    _adviseStatus->GetPerfCounterValue( CI_PERF_TOTAL_QUERIES, &cQ );
    setState( cQueries, cQ );

    unsigned cSecQDocuments;
    ULONG cUnfilteredDocs = CountPendingUpdates(cSecQDocuments) + _docList.Count();
    setState( cDocuments, cUnfilteredDocs );
    setState( cSecQDocuments, cSecQDocuments );
    setState( cFreshTest, LokGetFreshCount() );

    CCiFrmPerfCounter counter( _adviseStatus.GetPointer(),
                               CI_PERF_MERGE_PROGRESS );
    setState( dwMergeProgress, (ULONG) counter.GetCurrValue() );

    setState( cFilteredDocuments, _cFilteredDocuments );
    unsigned size = roundup(_partList.LokIndexSize(), ((1024*1024)/CI_PAGE_SIZE));
    setState( dwIndexSize,  size);
    setState( cUniqueKeys, _sKeyList->MaxKeyIdInUse() );

    state.cbStruct = min( state.cbStruct, sizeof(state) );

    DWORD eCiState = _dwFilteringState;

    //
    //  Set each of the state bits independently.
    //

    //
    //  Are we in a shadow or master merge?
    //
    if ( 0 != _pMerge )
    {
        switch (_pMerge->GetMergeType() )
        {
        case mtShadow:
            eCiState |= CIF_STATE_SHADOW_MERGE;
            break;

        case mtAnnealing:
            eCiState |= CIF_STATE_ANNEALING_MERGE;
            break;

        case mtIncrBackup:
            eCiState |= CIF_STATE_INDEX_MIGRATION_MERGE;
            break;

        default:
            eCiState |= CIF_STATE_MASTER_MERGE;
            break;
        }
    }
    else
    {
        CPartition *pPartition = _partList.LokGetPartition ( partidDefault );

        if ( pPartition->InMasterMerge() )
            eCiState |= CIF_STATE_MASTER_MERGE_PAUSED;
    }

    if ( LokIsScanNeeded() )
        eCiState |= CIF_STATE_CONTENT_SCAN_REQUIRED;

    setState( eState, (CIF_STATE_FLAGS) eCiState );

    return STATUS_SUCCESS;
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::IndexSize
//
//  Synopsis:   Computes the size of the indexes in all partitions
//
//  Arguments:  [mbIndex] - On output, will have the size of all indexes
//              in MBs.
//
//  History:    4-15-96   srikants   Created
//
//----------------------------------------------------------------------------

void CResManager::IndexSize( ULONG & mbIndex )
{
    CLock   lock(_mutex);
    mbIndex = _partList.LokIndexSize() / ((1024*1024)/CI_PAGE_SIZE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::LokCheckWordlistQuotas, public
//
//  Synopsis:   Determine if we've reached wordlist capacity.
//
//  Returns:    TRUE if we have as much wordlist data in memory as
//              parameters allow.
//
//  History:    14-Jan-1999   KyleP      Created
//
//----------------------------------------------------------------------------

BOOL CResManager::LokCheckWordlistQuotas()
{
    CPartIter iter;

    for ( iter.LokInit(_partList); !iter.LokAtEnd(); iter.LokAdvance(_partList) )
    {
        CPartition* pPart = iter.LokGet();

        if ( pPart->LokCheckWordlistMerge() )
            return TRUE;
    }

    return FALSE;
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::MarkCorruptIndex
//
//  Synopsis:   Marks the index as corrupt, disables updates in all partitions
//              and wakes up the merge thread to notify the framework client
//              about the corruption.
//
//  History:    1-30-97   srikants   Created
//
//----------------------------------------------------------------------------

NTSTATUS CResManager::MarkCorruptIndex()
{
    CPriLock    lock(_mutex);

    //
    // Disable updates in all partitions
    //
    CPartIter iter;
    for ( iter.LokInit(_partList); !iter.LokAtEnd(); iter.LokAdvance(_partList))
    {
        CPartition * pPart = iter.LokGet();
        pPart->LokDisableUpdates();
    }

    if ( _state.FLokCorruptionNotified() )
        return S_OK;

    _isCorrupt = TRUE;
    StopCurrentMerge();
    _eventMerge.Set();

    return S_OK;
}

//=====================
//             KEYINDEX
//=====================

#ifdef KEYLIST_ENABLED
//+---------------------------------------------------------------------------
//
//  Member:      CResManager::_AddRefKeyList, private
//
//  Synopsis:    Adds to refcount on the keylist
//
//  History:     07-Jul-94   dlee        Created
//
//----------------------------------------------------------------------------

CKeyList * CResManager::_AddRefKeyList()
{
    CPriLock lock( _mutex );
    _sKeyList->Reference();
    return _sKeyList.GetPointer();
}

//+---------------------------------------------------------------------------
//
//  Member:      CResManager::_ReleaseKeyList, private
//
//  Synopsis:    Release a refcount on the keylist
//
//  History:     07-Jul-94   dlee        Created
//
//----------------------------------------------------------------------------

void CResManager::_ReleaseKeyList()
{
    CPriLock lock( _mutex );

    _sKeyList->Release();

    if ( !_sKeyList->InUse() && _sKeyList->IsZombie() )
    {
        ciDebugOut(( DEB_ITRACE,
                     "Keylist %x unreferenced zombie.  Deleting\n",
                     _sKeyList->GetId() ));

        CKeyList * pKeyList = _sKeyList.Acquire();
        pKeyList->Remove();
        delete pKeyList;
    }
}

#endif  // KEYLIST_ENABLED

//+---------------------------------------------------------------------------
//
//  Member:      CResManager::KeyToId, public
//
//  Synopsis:    Maps from a key to an id.
//
//  Arguments:   [pkey] -- pointer to the key to be mapped to ULONG
//
//  Returns:     key id - a ULONG
//
//  History:     03-Nov-93   w-Patg      Created.
//               17-Feb-94   KyleP       Initial version
//
//----------------------------------------------------------------------------

ULONG CResManager::KeyToId( CKey const * pkey )
{

#ifdef KEYLIST_ENABLED
    CKeyList * pKeyList = _AddRefKeyList();

    ULONG kid = pKeyList->KeyToId( pkey );

    _ReleaseKeyList();

    return( kid );
#else
    return widInvalid;
#endif  // KEYLIST_ENABLED

}

//+---------------------------------------------------------------------------
//
//  Member:      CResManager::IdToKey, public
//
//  Synopsis:    Maps from an id to a key.
//
//  Arguments:   [ulKid] -- key id to be mapped to a key
//               [rkey] -- reference to the returned key
//
//  Returns:     void
//
//  History:     03-Nov-93   w-Patg      Created.
//               17-Feb-94   KyleP       Initial version
//
//----------------------------------------------------------------------------
void CResManager::IdToKey( ULONG ulKid, CKey & rkey )
{

#ifdef KEYLIST_ENABLED
    CKeyList * pKeyList = _AddRefKeyList();

    pKeyList->IdToKey( ulKid, rkey );

    _ReleaseKeyList();
#else
    rkey.FillMin();
#endif  // KEYLIST_ENABLED

}

//+-------------------------------------------------------------------------
//
//  Member:     CResManager::ComputeRelevantWords, public
//
//  Synopsis:   Computes and returns relevant words for a set of wids
//
//  Arguments:  [cRows]    -- # of rows to compute and in pwid array
//              [cRW]      -- # of rw per wid
//              [pwid]     -- array of wids to work over
//              [partid]   -- partition
//
//  History:    10-May-94 v-dlee  Created
//
//--------------------------------------------------------------------------

CRWStore * CResManager::ComputeRelevantWords(ULONG cRows,ULONG cRW,
                                             WORKID *pwids,
                                             PARTITIONID partid)
{
    CRWStore *prws = 0;

#ifdef KEYLIST_ENABLED

    CPriLock lock( _mutex );

    CPartition *pPart = LokGetPartition(partid);

    if (pPart != 0)
    {
        CPersIndex *pIndex= pPart->GetCurrentMasterIndex();

        if (pIndex != 0)
        {
            prws = pIndex->ComputeRelevantWords(cRows,cRW,pwids,
                                                _sKeyList.GetPointer() );
        }
    }
#endif  // KEYLIST_ENABLED

    return prws;
} //ComputeRelevantWords

//+-------------------------------------------------------------------------
//
//  Member:     CRWStore::RetrieveRelevantWords, public
//
//  Synopsis:   Retrieves relevant words already computed
//
//  Arguments:  [fAcquire] -- TRUE if ownership is transferred.
//              [partid]   -- partition
//
//  History:    10-May-94 v-dlee  Created
//
//--------------------------------------------------------------------------

CRWStore * CResManager::RetrieveRelevantWords(BOOL fAcquire,
                                              PARTITIONID partid)
{
    CRWStore *prws = 0;

#ifdef KEYLIST_ENABLED

    CPriLock lock( _mutex );

    CPartition *pPart = LokGetPartition(partid);

    if (pPart != 0)
    {
        prws = pPart->RetrieveRelevantWords(fAcquire);
    }
#endif  // KEYLIST_ENABLED

    return prws;
} //RetrieveRelevantWords



//================
// PRIVATE METHODS
//================



//+-------------------------------------------------------------------------
//
//  Member:     CResManager::CompactResources
//
//  History:    05-Jan-95 BartoszM     Created
//
//--------------------------------------------------------------------------

void CResManager::CompactResources()
{
    CPriLock lock( _mutex );

    CPartIter iter;
    for ( iter.LokInit(_partList); !iter.LokAtEnd(); iter.LokAdvance(_partList))
    {
        iter.LokGet()->LokCompact();
    }
}


 //=================================//
 //              MERGES             //
 //=================================//

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::DoMerges, private
//
//  History:    08-Apr-91   BartoszM    Created
//              25-Feb-92   BartoszM    Rewrote to use thread
//              30-Jun-97   KrishnaN    Calling docstore.FlushPropertyStore
//                                      before a shadow merge.
//              03-Mar-98   KitmanH     Don't merge if _storage is read-only
//
//  Notes:      Entry point for captive threads
//
//----------------------------------------------------------------------------

void CResManager::DoMerges()
{
    if ( _storage.IsReadOnly() )
       return;

    CCiFrmPerfCounter pPerIndexCounter( _adviseStatus.GetPointer(), CI_PERF_NUM_PERSISTENT_INDEXES );
    CCiFrmPerfCounter pIndexSizeCounter( _adviseStatus.GetPointer(), CI_PERF_INDEX_SIZE );
    CCiFrmPerfCounter pPendingCounter( _adviseStatus.GetPointer(), CI_PERF_FILES_TO_BE_FILTERED );
    CCiFrmPerfCounter pNumKeysCounter( _adviseStatus.GetPointer(), CI_PERF_NUM_UNIQUE_KEY);
    CCiFrmPerfCounter pWordListCounter(_adviseStatus.GetPointer(), CI_PERF_NUM_WORDLIST);
    CCiFrmPerfCounter pDeferredFilesCounter( _adviseStatus.GetPointer(), CI_PERF_DEFERRED_FILTER_FILES );

    XPtr<CIdleTime> xIdle( new CIdleTime() );

    //
    // Allow mount to complete before checking for merges. O/W we will
    // start a merge before the mount/startup is complete and it might
    // prevent a system from coming up.
    //

    const lowDiskPollTime = 1;  // 1 minute

    DWORD dwWaitTime = _isLowOnDiskSpace ? lowDiskPollTime : _frmwrkParams.GetMaxMergeInterval();

    _eventMerge.Wait(  dwWaitTime * 1000 * 60 );

    BOOL fAnnealing = FALSE;

    while ( !_fStopMerge )
    {
        BOOL fCorrupt = FALSE;

        ciDebugOut (( DEB_ITRACE, "\t|Merge Wakeup!\n" ));

        //
        // Book keeping chores.
        //
        BOOL    fBackupCiData = FALSE;
        BOOL    fBackupStarted = FALSE;

        TRY
        {
            // ===============================================
            CPriLock   lock(_mutex);

            if ( _isCorrupt  )
            {
               if ( !_state.FLokCorruptionNotified() )
                  LokNotifyCorruptionToClient();
            }
            else
            {
               //
               // Use low disk condition plus the current state to decide if
               // updates should be enabled or not
               //

               _isLowOnDiskSpace = LokCheckIfDiskLow( *this,
                                                      _storage,
                                                      _isLowOnDiskSpace,
                                                      _adviseStatus.GetReference() );
               if ( _isLowOnDiskSpace )
               {
                  if ( _state.LokGetState() != eUpdatesToBeDisabled
                       && _state.LokGetState() != eUpdatesDisabled )
                  {
                     _state.LokSetState( eUpdatesToBeDisabled );
                     _state.LokSetUpdateType( eIncremental );
                  }
               }
               else
               {
                  if ( _state.LokGetState() == eUpdatesDisabled )
                     _state.LokSetState( eUpdatesToBeEnabled );
               }

               if ( _state.LokGetState() == eUpdatesToBeDisabled )
                  LokNotifyDisableUpdatesToClient();
               else if ( _state.LokGetState() == eUpdatesToBeEnabled )
                  LokNotifyEnableUpdatesToClient();
            }

            fBackupCiData = 0 != _pBackupWorkItem;
            // ===============================================
        }
        CATCH( CException, e )
        {
            // ignore and try again the next time through the loop.
        }
        END_CATCH

        TRY
        {
            if ( !_isCorrupt && _partidToMerge != partidInvalid )
            {
                ciDebugOut (( DEB_ITRACE, "Forced merge in %ld\n",
                              _partidToMerge ));

                switch ( _mtForceMerge )
                {
                case CI_MASTER_MERGE:
                    MasterMerge ( _partidToMerge );
                    break;

                case CI_SHADOW_MERGE:
                    Merge( mtShadow, _partidToMerge );
                    break;

                case CI_ANNEALING_MERGE:
                    Merge( mtAnnealing, _partidToMerge );
                    break;

                case CI_ANY_MERGE:
                    break;  // Do nothing.  Let normal mechanism work it out.

                default:
                    Win4Assert( !"Invalid ForceMerge type" );
                }
            }

            if ( !_isLowOnDiskSpace && !_isCorrupt )
            {
                //
                // Refile the documents from the secondary queue to the primary
                // queue in pull filtering.
                //
                // ======================= lock ======================
                if ( !_fPushFiltering )
                {
                    CLock   lock(_mutex);
                    LokRefileSecQueueDocs();
                }
                // ======================= unlock ======================

                if ( fBackupCiData )
                {
                    BOOL fIsMasterPresent = FALSE;
                    BOOL fIsMMergeInProgress = FALSE;

                    //================================
                    {
                        CPriLock    lock(_mutex);
                        _pBackupWorkItem->LokSetDoingMerge();
                        fIsMasterPresent = LokIsMasterIndexPresent( partidDefault );
                        fIsMMergeInProgress = LokIsMasterMergeInProgress( partidDefault );
                    }
                    //================================

                    if ( _pBackupWorkItem->IsFullSave() ||
                         fIsMMergeInProgress ||
                         !fIsMasterPresent )
                    {
                        _pBackupWorkItem->SetDoingFullSave();
                        MasterMerge( partidDefault );
                    }
                    else
                    {
                        Merge( mtIncrBackup, partidDefault );
                    }

                    fBackupStarted = TRUE;
                    BackupContentIndexData();
                }
                else
                {
                    if ( fAnnealing )
                    {
                        fAnnealing = FALSE;

                        CPartIdStack partitionIds;
                        CheckAndDoMerge( mtAnnealing, partitionIds );

                        while ( partitionIds.Count() > 0 )
                           Merge( mtAnnealing, partitionIds.Pop() );
                    }

                    {
                        CPartIdStack partitionIds;
                        CheckAndDoMerge( mtShadow, partitionIds );

                        while ( partitionIds.Count() > 0 )
                        {
                            // Call FlushPropertyStore on DocStore to give it
                            // a chance to persist changes before a shadow merge.
                            _docStore->FlushPropertyStore();
                            Merge(  mtShadow, partitionIds.Pop() );
                        }
                    }

                    {
                        if ( CheckDeleteMerge( _partidToMerge != partidInvalid ) )
                        {
                            // Call FlushPropertyStore on DocStore to give it
                            // a chance to persist changes before a shadow merge.
                            _docStore->FlushPropertyStore();
                            Merge( mtDeletes, 1 );
                        }
                    }

                    BOOL fIsMasterPresent = FALSE;
                    BOOL fIsMMergeInProgress = FALSE;

                    //================================
                    {
                        CPriLock    lock(_mutex);
                        fIsMasterPresent = LokIsMasterIndexPresent( partidDefault );
                        fIsMMergeInProgress = LokIsMasterMergeInProgress( partidDefault );
                    }
                    //================================

                    {
                        CPartIdStack partitionIds;
                        CheckAndDoMerge( mtMaster, partitionIds );

                        while ( partitionIds.Count() > 0 )
                            MasterMerge( partitionIds.Pop() );
                    }
                }

            }
        }
        CATCH ( CException, e )
        {
            ciDebugOut (( DEB_ERROR,
                          "ResMan::DoMerges -- merge failed with 0x%x\n",
                          e.GetErrorCode() ));

            if ( IsDiskFull(e.GetErrorCode()) )
            {
                ciDebugOut(( DEB_ERROR, "***** DISK IS FULL *****\n" ));
                CPriLock   lock(_mutex);
                _isLowOnDiskSpace = LokCheckIfDiskLow( *this,
                                           _storage,
                                           _isLowOnDiskSpace,
                                           _adviseStatus.GetReference() );
            }
            else if ( IsCorrupt( e.GetErrorCode() ) )
            {
                Win4Assert( "!Merge failed, but not for low disk.  Why?" );
                fCorrupt = TRUE;
            }

            //
            // We have to prevent a master merge from being restarted
            // immediately. The reason for failure could be something like
            // out of disk space (typical reason) or log full. In either
            // case we will only be exacerbating the situation if we
            // restart immediately.
            //
            _eventMerge.Reset();

        }
        END_CATCH

        // ==========================================================
        {
            CPriLock lock( _mutex );

            Win4Assert( _partList.LokIndexCount() >= _partList.LokWlCount() );
            pPerIndexCounter.Update(_partList.LokIndexCount()-_partList.LokWlCount());
            pIndexSizeCounter.Update(_partList.LokIndexSize() / ((1024*1024)/CI_PAGE_SIZE) );

            pNumKeysCounter.Update(_sKeyList->MaxKeyIdInUse());

            pWordListCounter.Update(_partList.LokWlCount());

            _partidToMerge = partidInvalid;
            if ( _fStopMerge )
            {
                ciDebugOut(( DEB_ITRACE, "Stopping Merge\n" ));
                break;
            }

            if ( !_isCorrupt && ( _state.LokGetState() == eSteady ) && ( 0 != _pFilterAgent ) )
                _pFilterAgent->LokWakeUp();

            _eventMerge.Reset();   // sleep

            if ( fBackupStarted || _isCorrupt )
            {
                //
                // Set that backup to be complete even if it failed. Once
                // Backup starts, it either succeeds or fails but in either
                // case the operation is considered done.
                //

                if ( 0 != _pBackupWorkItem )
                {
                    if ( _isCorrupt )
                        _pBackupWorkItem->LokSetStatus( CI_CORRUPT_DATABASE );

                    _pBackupWorkItem->LokSetDone();
                }
            }
        }
        // ==========================================================

        BOOL fSteadyState = TRUE;

        if ( fCorrupt )
        {
            CPriLock lock(_mutex);
            _isCorrupt = TRUE;
            fSteadyState = _state.LokGetState() == eSteady;
        }

        unsigned cSecQCount;
        unsigned cPending = CountPendingUpdates( cSecQCount );

        //
        //  Wait for either the merge event to be signaled or the deadman timeout.
        //

        dwWaitTime = ( ( _isLowOnDiskSpace ) ||
                       ( !fSteadyState ) ||
                       ( 0 != cPending ) ) ?
                     lowDiskPollTime : _frmwrkParams.GetMaxMergeInterval();

        NTSTATUS Status = _eventMerge.Wait( dwWaitTime * 60 * 1000 );

        if ( !_isCorrupt )
        {
            unsigned PercentIdle = xIdle->PercentIdle();

            //
            // Are we idle enough to consider an annealing merge?
            //

            if ( STATUS_TIMEOUT == Status && PercentIdle >= _frmwrkParams.GetMinMergeIdleTime() )
            {
                ciDebugOut(( DEB_ITRACE, "Idle time for period: %u percent\n", PercentIdle ));
                fAnnealing = TRUE;
            }
        }

        if ( _fStopMerge )
        {
            ciDebugOut(( DEB_ITRACE, "Stopping Merge\n" ));
            break;
        }

        cPending = CountPendingUpdates( cSecQCount );

        pPendingCounter.Update(cPending);
        pDeferredFilesCounter.Update(cSecQCount);

        ciDebugOut (( DEB_ITRACE | DEB_PENDING,
                      "%d updates pending\n", cPending ));
    }

    // ===========================================
    {
        CPriLock    lock2(_mutex);
        if ( _pBackupWorkItem )
        {
            ciDebugOut(( DEB_WARN, "Forcing the backup to be done\n" ));
            _pBackupWorkItem->LokSetDone();
        }
    }
    // ===========================================
}

//+-------------------------------------------------------------------------
//
//  Member:     CResManager::StopMerges, private
//
//  Synopsis:   Aborts merge-in-progress (if any) and sets merge flag.
//
//  History:    13-Aug-93 KyleP     Created
//
//--------------------------------------------------------------------------

void CResManager::StopMerges()
{
    {
        CPriLock lock( _mutex );

        _fStopMerge = TRUE;
        _eventMerge.Set(); // wake up

        //
        // If we're doing a merge right now then kill it off.
        //

        if ( 0 != _pMerge )
        {
            _pMerge->LokAbort();
        }
    }

    //
    // Wait for merge thread to die.
    //

    _thrMerge.WaitForDeath();
}


//+---------------------------------------------------------------------------
//
//  Member:     CResManager::CheckAndDoMerge, private
//
//  History:    08-Apr-91   BartoszM    Created
//
//  Notes:      Called by captive thread.
//
//----------------------------------------------------------------------------

void CResManager::CheckAndDoMerge( MergeType mt, CPartIdStack & partitionIds)
{
    if ( !IsBatteryLow() )
    {
        CPriLock lock( _mutex );
        ciDebugOut (( DEB_ITRACE, "check merge\n" ));
        CPartIter iter;

        for ( iter.LokInit(_partList); !iter.LokAtEnd(); iter.LokAdvance(_partList))
        {
            CPartition* pPart = iter.LokGet();

            if ( mt == mtMaster )
            {
                __int64 shadowIndexSize = pPart->LokIndexSize();
                CPersIndex * pMasterIndex = pPart->GetCurrentMasterIndex();
                if ( pMasterIndex )
                    shadowIndexSize -= pMasterIndex->Size();
    
                shadowIndexSize *= CI_PAGE_SIZE;
    
                CMasterMergePolicy mergePolicy( _storage, shadowIndexSize,
                                                LokGetFreshCount(),
                                                _mergeTime,
                                                _frmwrkParams,
                                                _adviseStatus.GetReference() );

                if ( mergePolicy.IsTimeToMasterMerge() ||
                     ( mergePolicy.IsMidNightMergeTime() &&
                       !IsOnBatteryPower() ) )
                    partitionIds.Push( pPart->GetId() );
            }
            else
            {
                if ( pPart->LokCheckMerge(mt) ||
                     ( IsMemoryLow() && pPart->LokCheckLowMemoryMerge() ) )
                {
                    #if CIDBG == 1
                    if ( !pPart->LokCheckMerge(mt) )
                    {
                        ciDebugOut(( DEB_WARN, "Merge due to low memory. %u wordlists, %u shadow.\n",
                                     pPart->WordListCount(), pPart->LokIndexCount() ));
                    }
                    #endif
                    partitionIds.Push( pPart->GetId() );
                }
            }
        }
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::CheckDeleteMerge, private
//
//  Synopsis:   Merge check for delete merge
//
//  Arguments:  [fForce] -- If TRUE, even 1 pending delete will force merge.
//
//  Returns:    TRUE if merge should be performed.
//
//  History:    12-Jun-97   KyleP       Created
//
//  Notes:      Called by captive thread.
//
//----------------------------------------------------------------------------

BOOL CResManager::CheckDeleteMerge( BOOL fForce )
{
    CPriLock lock( _mutex );
    ciDebugOut (( DEB_ITRACE, "check delete merge\n" ));

    //
    // A 'delete merge' occurs when enough documents have been deleted, and no
    // documents have been added or modified (which would cause a shadow merge).
    //

    return ( ( _fresh.LokDeleteCount() > _frmwrkParams.GetMaxFreshDeletes() ) ||
             ( fForce && _fresh.LokDeleteCount() > 0 ) );
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::Merge, private
//
//  Synopsis:   Perform a merge in partition
//
//  Arguments:  [partid] -- partition id
//              [mt]     -- Merge type
//
//  Signals:    CExcetpion
//
//  History:    08-Apr-91   BartoszM    Created
//              13-Nov-91   BartoszM    Rewrote using CMerge
//
//----------------------------------------------------------------------------

void CResManager::Merge( MergeType mt, PARTITIONID partid )
{
    ciDebugOut (( DEB_ITRACE, "%s", (mtDeletes == mt) ? "Delete merging: Have a quick stretch" :
                                                        "Shadow merging: Get a cup of coffee\n" ));

    CMerge Merge( *this, partid, mt );

    //
    // Perform resource acquisition under storage transaction.  If
    // aborted all persistent storage will be deleted by the storage
    // transaction.
    //
    {
        CPriLock lock ( _mutex );

        Win4Assert( _pMerge == 0 );

        if (_fStopMerge)
        {
            ciDebugOut(( DEB_ITRACE, "Stopping Merge\n" ));
            return;
        }

        Merge.LokGrabResources();  // may throw an exception

        //
        // Optimization: Don't merge zero indexes.
        //

        if ( 0 != Merge.LokCountOld() )
        {
            _pMerge = &Merge;
        }
        else if ( mtDeletes != mt )
        {
            ciDebugOut(( DEB_ITRACE, "Shadow merge of zero indexes.  Cancelled.\n" ));
            return;
        }

    }

    XPtr<CNotificationTransaction> xNotifyTrans;

    // Perform the merge under simple transaction
    // No lock held.
    {
        TRY
        {
            //
            // Perform merge, if necessary.
            //

            if ( 0 != Merge.LokCountOld() )
            {
                CCiFrmPerfCounter counter( _adviseStatus.GetPointer(), CI_PERF_MERGE_PROGRESS );
                Merge.Do( counter );

            }

            //
            // NotifTrans should be outside resman lock and the resman lock
            // is acquired in notifTran's destructor
            //
            xNotifyTrans.Set( new CNotificationTransaction( this,
                                  _xIndexNotifTable.GetPointer() ) );
        }
        CATCH ( CException, e )
        {
            CPriLock lock ( _mutex );
            Merge.LokRollBack();
            _pMerge = 0;

            // Really bad errors indicate the index is corrupt.

            SCODE scE = e.GetErrorCode();

            if ( STATUS_INTEGER_DIVIDE_BY_ZERO == scE ||
                 STATUS_ACCESS_VIOLATION       == scE ||
                 STATUS_IN_PAGE_ERROR          == scE )
            {
                ciDebugOut(( DEB_ERROR,
                             "Corrupt index, caught 0x%x\n", scE ));
                _storage.ReportCorruptComponent( L"ShadowMerge" );
                THROW( CException( CI_CORRUPT_DATABASE ) );
            }

            RETHROW();
        }
        END_CATCH
    }

    // nb: no failures allowed until CMergeTrans is constructed!

    CDiskIndex* pIndexNew = 0;
    { //=================== begin notification transaction =========================
        {
            CLock lock( _mutex );
            XPtr<CFreshTest> xFreshTestAtMerge;

            {
                _pMerge = 0;

                CMergeTrans Xact( *this, _storage, Merge );

                unsigned cIndOld  = Merge.LokCountOld();
                INDEXID* aIidOld  = Merge.LokGetIidList();
                pIndexNew = Merge.LokGetNewIndex();

                //
                // Write the new fresh test persistently to disk.
                //
                CPersFresh newFreshLog( _storage, _partList );
                CShadowMergeSwapInfo swapInfo;

                swapInfo._widNewFreshLog = _fresh.LokUpdate( Merge,
                                                             Xact,
                                                             newFreshLog,
                                                             pIndexNew ? pIndexNew->GetId() : iidInvalid,
                                                             cIndOld,
                                                             aIidOld,
                                                             xFreshTestAtMerge );

                swapInfo._widOldFreshLog =
                                    _storage.GetSpecialItObjectId( itFreshLog );
                swapInfo._cIndexOld = cIndOld;
                swapInfo._aIidOld = aIidOld;

                _partList.LokSwapIndexes( Xact,
                                          partid,
                                          pIndexNew,
                                          swapInfo );

                //
                // No failures allowed after this point on until the transaction
                // is committed.
                //
                Merge.ReleaseNewIndex();

                ciDebugOut (( DEB_ITRACE, "done merging\n" ));

                Xact.LokCommit();

#if CIDBG == 1
                if ( pIndexNew )
                    pIndexNew->Reference();
#endif
                //==========================================
            }

            ciFAILTEST( STATUS_NO_MEMORY );

            CPartition *pPartition =_partList.LokGetPartition ( partid );

            //
            // Delete the wids in the change log that have made it to
            // the new shadow index created in this merge. The algorithm
            // needs the latest fresh test, fresh test snapshoted at the
            // start of shadow merge and the doc list. If the latest fresh
            // test is the same as the fresh test at merge, then as an
            // optimization, the fresh test at merge is null. So, in the
            // optimized case, we use the latest fresh test as the fresh
            // test at merge.
            //

            CFreshTest *pFreshTestLatest = _fresh.LokGetFreshTest();
            CFreshTestLock freshTestLock( pFreshTestLatest );

            CFreshTest *pFreshTestAtMerge;
            if ( xFreshTestAtMerge.IsNull() )
                pFreshTestAtMerge = pFreshTestLatest;
            else
                pFreshTestAtMerge = xFreshTestAtMerge.GetPointer();

            //==========================================

            CChangeTrans xact( *this, pPartition );
            pPartition->LokDeleteWIDsInPersistentIndexes( xact,
                                                          *pFreshTestLatest,
                                                          *pFreshTestAtMerge,
                                                          _docList,
                                                          xNotifyTrans.GetReference() );
            xact.Commit();
        }

        xNotifyTrans.Free();
    } //=================== end notification transaction =========================

    #if CIDBG == 1
        if ( pIndexNew )
        {
            pIndexNew->VerifyContents();

            {
                CPriLock lock ( _mutex );
                LokReleaseIndex( pIndexNew );
            }
        }
    #endif

    _storage.CheckPoint();
} //Merge

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::LogMMergeStartFailure
//
//  Synopsis:   Writes an eventlog message to indicate a MasterMerge could
//              not be started.
//
//  Arguments:  [fRestart]       - Indicates if this is a restarted master merge.
//              [storage]        - Storage reference
//              [error]          - Error code.
//              [adviseStatus]   - reference to ICiCAdviseStatus
//
//  History:    5-27-96   srikants   Created
//              Jan-07-96 mohamedn   CDmFwEventItem
//
//----------------------------------------------------------------------------

void CResManager::LogMMergeStartFailure( BOOL fRestart,
                                         PStorage & storage,
                                         ULONG error,
                                         ICiCAdviseStatus & adviseStatus)
{

    TRY
    {
        DWORD dwMsgCode = fRestart ?
                          MSG_CI_MASTER_MERGE_CANT_RESTART :
                          MSG_CI_MASTER_MERGE_CANT_START;


        CDmFwEventItem Item( EVENTLOG_AUDIT_FAILURE,
                             dwMsgCode,
                             2,
                             sizeof(error),
                             (void *)&error );
        Item.AddArg( storage.GetVolumeName() );
        Item.AddArg( error );

        Item.ReportEvent( adviseStatus );

    }
    CATCH( CException, e )
    {
        ciDebugOut(( DEB_ERROR, "Error 0x%X in LogMMergeStartFailure\n",
                     e.GetErrorCode() ));
    }
    END_CATCH
}


//+---------------------------------------------------------------------------
//
//  Member:     CResManager::LogMMergePaused
//
//  Synopsis:   Writes an eventlog message that master merge was paused.
//
//  Arguments:  [storage]      - Storage
//              [error]        - Error which caused the merge to be paused.
//              [adviseStatus] - Advise status to use for notifying about the
//              event.
//
//  History:    1-24-97   srikants   Added header
//
//----------------------------------------------------------------------------

void CResManager::LogMMergePaused( PStorage & storage,
                                   ULONG error,
                                   ICiCAdviseStatus &adviseStatus)
{

    if ( IsCorrupt( error ) )
    {
        // don't log "paused" when there is corruption
        return;
    }

    TRY
    {

        CDmFwEventItem Item( EVENTLOG_INFORMATION_TYPE,
                             MSG_CI_MASTER_MERGE_ABORTED,
                             2,
                             sizeof(error),
                             (void *)&error );
        Item.AddArg( storage.GetVolumeName() );
        Item.AddArg( error );

        Item.ReportEvent( adviseStatus );

    }
    CATCH( CException, e )
    {
        ciDebugOut(( DEB_ERROR, "Error 0x%X in LogMMergePaused\n",
                     e.GetErrorCode() ));
    }
    END_CATCH
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::MasterMerge, private
//
//  Synopsis:   Perform a MasterMerge in partition
//
//  Arguments:  [partid] -- partition id
//
//  Signals:    CException
//
//  History:    08-Apr-91   BartoszM    Created
//              13-Nov-91   BartoszM    Rewrote using CMerge
//              17-Feb-94   KyleP       Added keylist merge
//              Jan-07-96   mohamedn    CDmFwEventItem
//
//----------------------------------------------------------------------------

void CResManager::MasterMerge ( PARTITIONID partid )
{
    ciDebugOut (( DEB_ITRACE, "\n===MASTER MERGE===\n" ));

    //
    //  Force a shadow merge if we are not restarting a master merge.  This
    //  will force all word lists into persistent indexes.
    //
    BOOL    fMasterMergePaused = FALSE;

    //
    // Before starting the master merge, we have to swap the deleted index
    // iid. However, if there is a failure before we commit the beginning
    // of the master merge, we have to roll back this change. The
    // CDeletedIIDTrans object does this.
    //
    CDeletedIIDTrans    delIIDTrans( *this );
    INDEXID iidDelPreMerge = iidInvalid;

    {
        CPriLock lock ( _mutex );
        CPartition *pPartition = _partList.LokGetPartition ( partid );
        fMasterMergePaused = pPartition->InMasterMerge();
        if ( !fMasterMergePaused )
        {
            if ( 0 == LokGetFreshCount() )
            {
                //
                // There is no benefit in doing a master merge. Just return
                //
                return;
            }

            iidDelPreMerge = _activeDeletedIndex.GetIndex();
            delIIDTrans.LokLogNewDeletedIid( iidDelPreMerge,
                                             _activeDeletedIndex.GetNewIndex() );
            _activeDeletedIndex.SwapIndex();
        }
    }


    if ( !fMasterMergePaused )
    {
        //
        // First do a delete merge to finish merging all outstanding
        // word-lists into a persistent index.
        //
        // A delete merge will force an optimized null merge if there are
        // outstanding deletes in the fresh test which need to be logged.
        // This is an important case for Push filtering.  Since it doesn't
        // hurt in the other model, just keep the code identical.
        //

        Merge( mtDeletes, partid );
    }
    else
    {
        //
        // This is a restarted master merge. The _activeDeletedIndex contains
        // the "Post" master merge deleted index. The "New" one will be same
        // as the "Prev" one.
        //
        iidDelPreMerge = _activeDeletedIndex.GetNewIndex();

        CDmFwEventItem Item( EVENTLOG_INFORMATION_TYPE,
                             MSG_CI_MASTER_MERGE_RESTARTED,
                             1 );

        Item.AddArg( _storage.GetVolumeName() );
        Item.ReportEvent( _adviseStatus.GetReference() );

    }


    CMasterMerge Merge( *this, partid );

    TRY
    {
        CPriLock lock ( _mutex );

        Win4Assert( _pMerge == 0 );

        if ( _fStopMerge )
        {
            ciDebugOut(( DEB_ITRACE, "Stopping Merge\n" ));
            return;
        }

        ciFAILTEST( STATUS_NO_MEMORY );

        CPartition *pPartition =_partList.LokGetPartition ( partid );

        if ( !pPartition->InMasterMerge() )
        {
            Win4Assert( delIIDTrans.IsTransLogged() );
            Merge.LokGrabResources( delIIDTrans );     // may throw an exception
            if ( 0 == Merge.LokCountOld() )
            {
                ciDebugOut(( DEB_ITRACE, "Master merge of 0 indexes cancelled\n"));
                return;
            }
        }
        else
        {
            Merge.LokLoadRestartResources();
        }

        //
        // The deletionIIDTrans must be either committed or not logged at
        // all.
        //
        Win4Assert( !delIIDTrans.IsRollBackTrans() );

        _pMerge = &Merge;

    }
    CATCH( CException, e )
    {
        ciDebugOut(( DEB_ERROR, "Error 0x%X while starting a MasterMerge\n",
                     e.GetErrorCode() ));

        LogMMergeStartFailure( fMasterMergePaused,
                               _storage,
                               e.GetErrorCode(),
                               _adviseStatus.GetReference() );

        RETHROW();
    }
    END_CATCH


    // Perform the merge under simple transaction
    // No lock held.
    {
        TRY
        {
            // Perform merge
            CCiFrmPerfCounter counter( _adviseStatus.GetPointer(), CI_PERF_MERGE_PROGRESS );
            Merge.Do( counter );
        }
        CATCH ( CException, e )
        {
            CPriLock lock ( _mutex );
            Merge.LokRollBack();
            _pMerge = 0;

            LogMMergePaused( _storage,
                             e.GetErrorCode(),
                             _adviseStatus.GetReference() );
            RETHROW();
        }
        END_CATCH
    }

    CMasterMergeIndex* pMMergeIndex = 0;
    {
        CPriLock lock ( _mutex );
        {
            _pMerge = 0;

            unsigned cIndOld  = Merge.LokCountOld();
            INDEXID* aIidOld  = Merge.LokGetIidList();
            pMMergeIndex = Merge.LokGetMasterMergeIndex();

            CMergeTrans MergeTrans( *this, _storage, Merge );

            ciFAILTEST( STATUS_NO_MEMORY );

            //==========================================
            CMasterMergeTrans xact( *this,
                                    _storage,
                                    MergeTrans,
                                    pMMergeIndex,
                                    Merge );

            CPartition *pPart = LokGetPartition(partid);

            #ifdef KEYLIST_ENABLED
            pPart->SetRelevantWords(pMMergeIndex->AcquireRelevantWords());
            #endif  // KEYLIST_ENABLED

            //
            // Remove old indexes from fresh list after master merge
            //
            CPersFresh newFreshLog( _storage, _partList );

            CMasterMergeSwapInfo SwapInfo;

            SwapInfo._widNewFreshLog = _fresh.LokRemoveIndexes (
                                          MergeTrans,
                                          newFreshLog,
                                          cIndOld, aIidOld, iidDelPreMerge );

            SwapInfo._widOldFreshLog = _storage.GetSpecialItObjectId( itFreshLog );

            SwapInfo._cIndexOld = cIndOld;
            SwapInfo._aIidOld = aIidOld;
            SwapInfo._partid = partid;

            CKeyList const * pOldKeyList = _sKeyList.GetPointer();
            CKeyList const * pNewKeyList = Merge.LokGetNewKeyList();

            _partList.LokSwapIndexes( MergeTrans,
                                      partid,
                                      pMMergeIndex,
                                      SwapInfo,
                                      pOldKeyList,
                                      pNewKeyList );
            //
            // After this point, we must not resume the master merge even
            // if there is a soft failure.
            //
            Merge.ReleaseNewIndex();

#ifdef KEYLIST_ENABLED
            //
            // Get rid of the current KeyList and replace it with the
            // new KeyList
            //
            _storage.SetSpecialItObjectId( itMMKeyList, widInvalid );
            CKeyList * pKeyList = _sKeyList.Acquire();
            Win4Assert( 0 != pKeyList );
            _sKeyList.Set( Merge.LokGetNewKeyList() );
            Merge.LokReleaseNewKeyList();
            pKeyList->Zombify();

            if ( !pKeyList->InUse() )
            {
                pKeyList->Remove();
                delete pKeyList;
            }

#else
            CKeyList * pKeyList = _sKeyList.Acquire();
            Win4Assert( 0 != pKeyList );
            _sKeyList.Set( Merge.LokGetNewKeyList() );
            Merge.LokReleaseNewKeyList();
            delete pKeyList;
#endif  // KEYLIST_ENABLED

#if CIDBG == 1
            pMMergeIndex->Reference();
#endif
            xact.LokCommit();
            //==========================================

            ciDebugOut (( DEB_ITRACE, "done merging\n" ));
        }

        //
        // There is no need to compact the change log here because the
        // participants in the master merge are persistent indexes and
        // there will be no wids to be deleted from the change log when
        // persistent indexes are merged into another persistent index.
        //
    }

#if CIDBG == 1
    pMMergeIndex->VerifyContents();
    {
        CPriLock lock ( _mutex );
        LokReleaseIndex( pMMergeIndex );
    }
#endif

    _storage.CheckPoint();

    CDmFwEventItem Item( EVENTLOG_INFORMATION_TYPE,
                         MSG_CI_MASTER_MERGE_COMPLETED,
                         1 );
    Item.AddArg( _storage.GetVolumeName() );
    Item.ReportEvent( _adviseStatus.GetReference() );
}

//+---------------------------------------------------------------------------
//
//  Class:      CReleaseMMergeIndex
//
//  Purpose:    Class to acquire the ownernship of the target and current
//              master indexes during the destruction of a mastermerge index
//              and release the target and current indexes.
//
//  History:    9-29-94   srikants   Created
//
//----------------------------------------------------------------------------

class CReleaseMMergeIndex
{

public:

    CReleaseMMergeIndex( CResManager & resman, CMasterMergeIndex * mmIndex );

    ~CReleaseMMergeIndex();

private:

    CResManager &       _resman;
    CMasterMergeIndex * _pmmIndex;
    CPersIndex *        _pTargetMaster;
    CPersIndex *        _pCurrentMaster;
};

CReleaseMMergeIndex::CReleaseMMergeIndex( CResManager & resman,
                     CMasterMergeIndex * mmIndex ) :
                            _resman(resman),
                            _pmmIndex(mmIndex),
                            _pTargetMaster(0),
                            _pCurrentMaster(0)
{
    Win4Assert( _pmmIndex );
    Win4Assert( _pmmIndex->IsZombie() && !_pmmIndex->InUse() );
    _pmmIndex->AcquireCurrentAndTarget( &_pCurrentMaster, &_pTargetMaster );
    Win4Assert( 0 == _pCurrentMaster || _pCurrentMaster->IsZombie() );

    if ( 0 != _pCurrentMaster )
    {
        _pCurrentMaster->Reference();
    }

    Win4Assert( 0 != _pTargetMaster );
    _pTargetMaster->Reference();

}

CReleaseMMergeIndex::~CReleaseMMergeIndex()
{
    delete _pmmIndex;

    if ( 0 != _pCurrentMaster )
        _resman.LokReleaseIndex( _pCurrentMaster );

    _resman.LokReleaseIndex( _pTargetMaster );
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::LokReleaseIndex, private
//
//  Synopsis:   Decrements ref counts, deletes if index
//              marked to be deleted.
//
//  Arguments:  [pIndex] -- index to be released
//
//  History:    08-Oct-91   BartoszM    Created
//
//  Notes:      ResMan LOCKED
//
//----------------------------------------------------------------------------

void CResManager::LokReleaseIndex ( CIndex* pIndex )
{
    pIndex->Release();

    if ( pIndex->IsZombie() && !pIndex->InUse() )
    {
        CIndexId iid = pIndex->GetId();

        //
        //  If it is not a master index, it has to be removed from the
        //  Partition object. A master index would have been already removed
        //  when merge completed.
        //
        //  If we are in the Empty path, then we want to force all indexes
        //  to be deleted if their ref-count is 0.
        //

        if ( !pIndex->IsMaster() || _isBeingEmptied )
        {
            if ( !_isDismounted )
            {
                TRY
                {
                    _partList.LokRemoveIndex ( iid );
                }
                CATCH( CException, e )
                {
                    ciDebugOut(( DEB_ERROR,
                        "Index 0x%08x could not be deleted from index table\n",
                        iid ));
                }
                END_CATCH

                TRY
                {
                    // Must impersonate as system since query threads
                    // impersonating as a query user can't delete the
                    // files sometimes deleted when an index is released
                    // after a merge.  Impersonation can fail, which will
                    // leak the file on disk.

                    CImpersonateSystem impersonate;

                    if ( iid.IsPersistent() )
                        pIndex->Remove();
                }
                CATCH( CException, e )
                {
                    ciDebugOut(( DEB_ERROR,
                        "Index 0x%08x could not be deleted from disk, %#x\n",
                        iid, e.GetErrorCode() ));
                }
                END_CATCH
            }
            else
            {
                //
                // Zombie indexes are cleaned up after reboot.
                //
                //
                // After a dismount, we should not have any dirty streams
                // and releasing a persistent zombie index will update the
                // index table on disk. That is not allowed. This situation
                // happens only when queries have not been released and
                // shutdown is happening.
                //
                //
                ciDebugOut(( DEB_WARN,
                    "Index with iid 0x%X being released AFTER CI dismount\n",
                             iid ));
            }

            delete pIndex;
        }
        else
        {
            //
            // This must be a master merge index as no other master index
            // can be a zombie.
            //
            Win4Assert( pIndex->IsMasterMergeIndex() );
            CReleaseMMergeIndex mmIndexRel( *this,
                                            (CMasterMergeIndex *)pIndex );
        }
    }
}

//+---------------------------------------------------------------------------
//
//  Function:   LokReplaceMasterIndex
//
//  Synopsis:   Replaces the CMasterMergeIndex with the CPersIndex incarnation
//              of the target master index in the index list. Also prepares
//              the CMasterMergeIndex for removal from the in-memory index
//              list by zombifying it.
//
//  Arguments:  [pMMergeIndex] --  The target index in the CMasterMergeIndex
//              form.
//              [pNewMaster]   --  The CPersIndex form of the target index.
//
//  History:    6-30-94   srikants   Created
//
//----------------------------------------------------------------------------

void CResManager::LokReplaceMasterIndex ( CMasterMergeIndex* pMMergeIndex )
{
    Win4Assert( pMMergeIndex->IsMaster() && pMMergeIndex->IsZombie() );

    CPersIndex * pNewMaster = pMMergeIndex->GetTargetMaster();
    Win4Assert( pNewMaster->IsMaster() && !pNewMaster->IsZombie() );

    //
    // Delete the master merge index from the partition.
    //
    CIndexId iid = pMMergeIndex->GetId();
    CPartition * pPart = _partList.LokGetPartition( iid.PartId() );
    pPart->LokRemoveIndex ( iid );

    //
    // Check if there is a query in progress. If not, we can
    // go ahead and delete it. If a query is in progress, the
    // indexsnapshot of the query will delete it.
    //
    if ( !pMMergeIndex->InUse() )
    {
        Win4Assert( !pNewMaster->InUse() );
        pMMergeIndex->ReleaseTargetAndCurrent();
        delete pMMergeIndex;
    }

    //
    // Add the new master index to the partition object. This will
    // now be the only master accessible to new queries.
    //
    pPart->AddIndex( pNewMaster );

}


//
//+---------------------------------------------------------------------------
//
//  Function:   RollBackDeletedIIDChange
//
//  Synopsis:   Rolls back the changed made to the deleted iid during the
//              beginning of a master merge.
//
//  Arguments:  [xact] -  The transaction object which is rolling back the
//              change to the deleted iid.
//
//  History:    7-17-95   srikants   Created
//
//  Notes:      Consider the following sequence of events:
//
//  1. Pre-MasterMerge preparations : Change Deleleted IID to iidDeleted2 from
//     iidDeleted1.
//
//  2. Start a shadow merge.
//
//  3. Commit the beginning of a new MasterMerge
//
//  4. Complete the master merge .....
//
//  If there is a failure between steps 2 and 3, we just revert the deleted
//  IID back to iidDeleted2. Even if some entries were created in the fresh
//  list between steps 2 & 3 of the form, WID->iidDeleted2, it is okay. They
//  will get deleted from the fresh list on the second MasterMerge.
//
//----------------------------------------------------------------------------

void CResManager::RollBackDeletedIIDChange( CDeletedIIDTrans & xact )
{
    CPriLock   lock(_mutex);

    Win4Assert( _activeDeletedIndex.GetIndex() == xact.GetNewDelIID() );
    Win4Assert( _activeDeletedIndex.GetNewIndex() == xact.GetOldDelIID() );

    _activeDeletedIndex.SwapIndex();
}

//========================
//  FILTER RELATED METHODS
//========================



//+---------------------------------------------------------------------------
//
//  Function:   LokGetFilterDocs
//
//  Synopsis:   Gets documents from the change log for filtering. _docList
//              will be filled with the documents.
//
//  Arguments:  [cMaxDocs]        -- [in] Maximum number of docs to retrieve
//              [cDocs]           -- [out] Number of documents retrieved
//              [state]           -- [in/out] State of the document retrieval
//                                   progress.
//
//  Returns:    Number of documents in the docBuffer.
//
//  History:    6-16-94   srikants   Separated for making kernel mode call
//                                   Asynchronous.
//
//  Notes:      This function has two goals:
//
//              1. Select documents from different partitions in a round
//                 robin fashion.
//
//----------------------------------------------------------------------------

BOOL CResManager::LokGetFilterDocs ( ULONG cMaxDocs,
                                     ULONG & cDocs,
                                     CGetFilterDocsState & state )
{
    ciDebugOut(( DEB_ITRACE, "# CResManager::LokGetFilterDocs.\n" ));
    Win4Assert( state.GetTriesLeft() > 0 && state.GetTriesLeft() <=2 );

    cDocs = 0;
    Win4Assert ( !_isBeingEmptied );

    unsigned maxDocs = min( cMaxDocs, CI_MAX_DOCS_IN_WORDLIST);

    if ( _partIter.LokAtEnd() )
        return FALSE;      // There are no valid partitions

    // we are sure that the partition is valid
    CPartition* pPart = _partIter.LokGet();
    Win4Assert ( pPart != 0 );

    //
    // Don't bother looking for more documents to filter if there
    // are too many wordlists in memory -- wait until they are
    // merged.
    //
    if ( pPart->WordListCount() < _frmwrkParams.GetMaxWordlists() )
    {
        CChangeTrans  xact( *this, pPart );
        pPart->LokQueryPendingUpdates ( xact, maxDocs, _docList );
        xact.LokCommit();
    }
    else
    {
        ciDebugOut(( DEB_ITRACE,
                     "Too many wordlists.  Waking merge\n" ));
        _eventMerge.Set();
    }

    _docList.SetPartId ( pPart->GetId() );
    //==================================
    //
    // If there is a failure after this point, we have to re-add
    // the documents to the change log.
    //
    cDocs = _docList.Count();

    for (unsigned i = 0; i < cDocs; i++)
    {
        if (_docList.Status(i) != DELETED && _docList.Status(i) != WL_NULL)
            break;
    }

    BOOL fRetry = FALSE;

    if ( i == cDocs && i != 0 )
    {
        CIndexTrans  xact( *this );
        _fresh.LokDeleteDocuments ( xact, _docList, _activeDeletedIndex.GetIndex() );
        xact.LokCommit();
        cDocs = 0;
        _docList.LokClear();
        state.Reset();

        //
        // If we are doing a bunch of deletions in a "delnode" case,
        // we should see if the number of changed documents has exceeded
        // the max fresh test count and wake up the merge thread. Note
        // that we are not starting a merge here - the merge thread will
        // deal with the policy issues.
        //
        if ( LokGetFreshCount() > _frmwrkParams.GetMaxFreshCount() ||
             CheckDeleteMerge( FALSE ) )
        {
            _eventMerge.Set();
        }

        LokUpdateCounters();
    }

    _partIter.LokAdvance( _partList );
    if ( _partIter.LokAtEnd() )
    {
        _partIter.LokInit( _partList );
        state.DecrementTries();
        fRetry = (0 == cDocs) && (state.GetTriesLeft() > 0);
    }
    else
    {
        fRetry = ( 0 != cDocs );
    }

    Win4Assert( cDocs == _docList.Count() );

    return fRetry;
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::FillDocBuffer, public
//
//  Synopsis:   Fills buffer with strings for paths and properties of docs
//              to be filtered
//
//  Arguments:  [docBuffer] -- (in, out) buffer to be filled with document
//                             names.
//              [cb]        -- (in, out) count of bytes in docBuffer
//              [cDocs]     -- (out) count of # documents to filter
//              [state]     -- [in/out] State of the document retrieval
//                             progress.
//
//  History:    08-Apr-1991   BartoszM    Created
//              24-Nov-1992   KyleP       Retry pending documents
//              22-Mar-1993   AmyA        Split into several functions
//              22-Jan-1997   SrikantS    Changed doc. names to bytes for frame
//                                        work.
//              18-May-2000   KitmanH     Changed to push retried docs to filter
//                                        one by one
//
//----------------------------------------------------------------------------

void CResManager::FillDocBuffer( BYTE * docBuffer,
                                 ULONG & cb, ULONG & cDocs,
                                 CGetFilterDocsState & state )
{
    Win4Assert( docBuffer != 0 );

    // =========================================
    {
        CPriLock    lock(_mutex);
        _docList.LokSortOnWid();
    }
    // ==========================================

    unsigned cDocuments = _docList.Count();

    //
    // Format of the data
    // 4 Bytes remaining files count, 2 Bytes number of files. Following
    // that is (2 Bytes of Name Length in bytes, Name)*
    //

    if ( _fPushFiltering )
    {
        //
        // We can't take an exception while growing the aborted wids
        // array while doing exception processing in the filter manager.
        // So, pre-allocate the array now to insure we won't run out of
        // room later.  Now is a good time to die; later isn't.
        //

        CPriLock lock( _mutex );

        _aAbortedWids.LokReserve( cDocuments );

        //
        // In push filtering there are no refiles, and hence the buffer
        // cannot overflow. Check that the buffer can fit 16 documents.
        // A document name is the serialized form of the wid, i.e. it's
        // 4 bytes long. The buffer size is 4K.
        //

        Win4Assert( cb > 4 + 2 + 16 * (2 + 4) );
    }

    const cbLenField = sizeof(ULONG);

    Win4Assert( cb >= cbLenField );
    Win4Assert( sizeof(ULONG) == 4 );

    if ( cb < cbLenField )
        THROW( CException( STATUS_INVALID_PARAMETER ) );

    //
    // Fill the number of documents left in the changelog in the first
    // 4 bytes of the buffer.
    //
    // =========================================================
    {
        CPriLock lock( _mutex );
        ULONG partid = 1;  
        CPartition * pPart = _partList.LokGetPartition( partid );
        ULONG cDocsLeft = pPart->LokCountUpdates();
        RtlCopyMemory( docBuffer, &cDocsLeft, sizeof(ULONG) );
    }
    // =========================================================

    RtlZeroMemory( docBuffer + cbLenField, sizeof USHORT );

    const cbUSHORT = sizeof USHORT;

    BYTE * pCount = docBuffer + cbLenField;
    *pCount = 0;

    BYTE * pBuf = pCount + cbUSHORT;
    BYTE * pEnd = docBuffer + cb;

    cDocs = 0;

    // copy path/property pairs into buffer until we run out of either
    // buffer space or pairs. We stop filling the buffer if we encounter a
    // document that has a retry count > 1, we will fill the docBuffer with
    // these docs one at a time 

    ciDebugOut((DEB_FILTERWIDS | DEB_ITRACE,
               "CResManager::FillDocBuffer..."));

    unsigned cbExtraSpaceNeeded = 0;

    for (unsigned cSrcDocs = 0; cSrcDocs < cDocuments; cSrcDocs++)
    {
        //
        // Leave the first 2 bytes for the length field
        //
        unsigned cbPath = (unsigned)(pEnd - pBuf);

        if ( cbPath <= cbUSHORT )
        {
            // We would definitely need more space than a USHORT, but
            // we don't know how much right now. But since the filter
            // increases space by a PAGE, I guess we should do fine.
            cbExtraSpaceNeeded = cbUSHORT - cbPath + 1;
            break;
        }

        if (_docList.Status(cSrcDocs) == DELETED)
        {
            cbPath = 0;
            ciDebugOut(( DEB_FILTERWIDS | DEB_NOCOMPNAME, " %dX",
                         _docList.Wid(cSrcDocs) ));
        }
        else
        {          
            if ( _docList.Retries(cSrcDocs) > 1 )
            {
                ciDebugOut(( DEB_FILTERWIDS | DEB_NOCOMPNAME, 
                             "************FillDocBuffer: Retry count is %d. Wid is %d\n", 
                             _docList.Retries(cSrcDocs), 
                             _docList.Wid(cSrcDocs) ));
                ciDebugOut(( DEB_FILTERWIDS | DEB_NOCOMPNAME, "cDocs is %d\n", cDocs ));

                if ( cDocs >  0 )
                {
                    //
                    // There is at least one document in the docBuffer. Stop
                    // now so we can file the doc(s) being retried one by one.
                    //
                    break;
                }
                else
                {
                    //
                    // Add the currenct doc. Stop if the current add is successful
                    //

                    //
                    // Make room for the length field at the beginning.
                    //
                    cbPath -= cbUSHORT;

                    ciDebugOut(( DEB_FILTERWIDS | DEB_NOCOMPNAME, " %d",
                                 _docList.Wid(cSrcDocs) ));

                    unsigned cbPathIn = cbPath;

                    //
                    // If this is not the first try, then attempt to get a more
                    // accurate path.  We may be suffering from NTBUG: 270566
                    //

                    // changes cbPath.
                    if ( !WorkidToDocName( _docList.Wid(cSrcDocs),
                                           pBuf+cbUSHORT,
                                           cbPath,
                                           TRUE ) )
                    {
                        if ( cbPathIn < cbPath)
                        {
                            cbExtraSpaceNeeded = cbPath - cbPathIn;
                        }
                        break;
                    }

                    // cbPath = 0 if object has been deleted (not added to docBuffer)
                    if ( cbPath == 0 )
                    {
                        _docList.SetStatus( cSrcDocs, DELETED );
                        ciDebugOut(( DEB_FILTERWIDS | DEB_NOCOMPNAME, "X" ));
                    }
                    else
                    {
                        ClearNonStoragePropertiesForWid( _docList.Wid(cSrcDocs) );
                        cSrcDocs++;
                        cDocs++;
                        USHORT uscb = (USHORT) cbPath;
                        RtlCopyMemory( pBuf, &uscb, cbUSHORT );
                        pBuf += (cbPath+cbUSHORT);
                        break;
                    }    
                }
            }
            else  // not retries
            {
                ciDebugOut(( DEB_FILTERWIDS | DEB_NOCOMPNAME, 
                             "FillDocBuffer: Not retry for wid %d\n", 
                             _docList.Wid(cSrcDocs) ));

                //
                // Make room for the length field at the beginning.
                //
                cbPath -= cbUSHORT;

                ciDebugOut(( DEB_FILTERWIDS | DEB_NOCOMPNAME, " %d",
                             _docList.Wid(cSrcDocs) ));

                unsigned cbPathIn = cbPath;

                // changes cbPath.
                if ( !WorkidToDocName( _docList.Wid(cSrcDocs),
                                       pBuf+cbUSHORT,
                                       cbPath,
                                       FALSE ) )
                {
                    if ( cbPathIn < cbPath)
                    {
                        cbExtraSpaceNeeded = cbPath - cbPathIn;
                    }
                    break;
                }

                // cbPath = 0 if object has been deleted
                if ( cbPath == 0 )
                {
                    _docList.SetStatus( cSrcDocs, DELETED );
                    ciDebugOut(( DEB_FILTERWIDS | DEB_NOCOMPNAME, "X" ));
                }
                else
                {
                    cDocs++;
                    ClearNonStoragePropertiesForWid( _docList.Wid(cSrcDocs) );
                }
            }
        }

        USHORT uscb = (USHORT) cbPath;
        RtlCopyMemory( pBuf, &uscb, cbUSHORT );

        pBuf += (cbPath+cbUSHORT);
    }
    ciDebugOut (( DEB_FILTERWIDS | DEB_NOCOMPNAME, "\n" ));

    //
    // Fill in the count of the number of documents.
    //
    USHORT usFileCount = (USHORT) cSrcDocs;
    RtlCopyMemory( pCount, &usFileCount, cbUSHORT );


    if ( cSrcDocs < cDocuments )
    {
        if ( 0 == cSrcDocs && cbExtraSpaceNeeded > 0 )
        {
            // We need a larger buffer
            cb += cbExtraSpaceNeeded;
        }

        //
        // No refiling in push filtering
        //
        Win4Assert( !_fPushFiltering );

        ciDebugOut (( DEB_ITRACE, "Putting back documents\n" ));
        CDocList docListExtra;

        docListExtra.SetPartId( _docList.PartId() );

        for ( unsigned i = 0, j = cSrcDocs; j < cDocuments; i++, j++ )
        {
            ciDebugOut(( DEB_ITRACE | DEB_NOCOMPNAME, " %d", _docList.Wid(j) ));
            ciDebugOut(( DEB_FILTERWIDS | DEB_NOCOMPNAME, "Refiling Wid %d\n", _docList.Wid(j) ));
            docListExtra.Set( i,
                              _docList.Wid(j),
                              _docList.Usn(j),
                              _docList.VolumeId(j) );
        }

        ciDebugOut (( DEB_ITRACE | DEB_NOCOMPNAME, "\n" ));

        CPriLock   lock2(_mutex);

        docListExtra.LokSetCount(i);
        _docList.LokSetCount( cSrcDocs );

        ReFileCommit ( docListExtra );
    }

    //
    //  If we were unable to convert any of the wids to paths, then all of
    //  them have been deleted.
    //
    if ( 0 == cDocs )
    {
        ciDebugOut (( DEB_ITRACE, "All docs were deleted\n" ));

        CPriLock    lock3(_mutex);

        _docList.LokClear();
        state.Reset();
    }
    else
    {
        cb = (ULONG)(pBuf - docBuffer);
    }
}

//+---------------------------------------------------------------------------
//
//  Function:   NoFailReFile
//
//  Synopsis:   re-adds the documents to the change log witout committing
//
//  History:    6-16-94   srikants   Separated from FilterReady for making
//                                   kernel mode call Asynchronous.
//
//----------------------------------------------------------------------------

BOOL CResManager::NoFailReFile ( CDocList& docList )
{
    //
    // No refiling in push filtering
    //
    Win4Assert( !_fPushFiltering );

    CPriLock lock ( _mutex );

    if ( _isBeingEmptied )
        return FALSE;

    LokNoFailReFileChanges( docList );
    docList.LokClear();

    return TRUE;
}

//+---------------------------------------------------------------------------
//
//  Function:   ReFileCommit
//
//  Synopsis:   re-adds the documents to the change log and commits
//              them to the change list.
//
//  History:    6-16-94   srikants   Separated from FilterReady for making
//                                   kernel mode call Asynchronous.
//
//----------------------------------------------------------------------------

BOOL CResManager::ReFileCommit ( CDocList& docList )
{
    //
    // No refiling in push filtering
    //
    Win4Assert( !_fPushFiltering );

    CPriLock lock ( _mutex );

    if ( _isBeingEmptied )
        return FALSE;

    LokNoFailReFileChanges( docList );
    docList.LokClear();

    //
    // The docqueue can handle only one set of refiled documents.
    // we MUST complete processing of the
    // refiled documents.
    //
    PARTITIONID partId = 1;    

    CPartition * pPart = LokGetPartition( partId );

    {
        // =========================================
        CChangeTrans    xact( *this, pPart );
        pPart->LokAppendRefiledDocs( xact );
        xact.LokCommit();
        // =========================================
    }
    return TRUE;
}

//+---------------------------------------------------------------------------
//
//  Function:   LokNoFailReFileChanges
//
//  Synopsis:   Refiles the updates with the change log.
//
//  Arguments:  [docList] --  contains the documents to be added back to the
//              change log.
//
//  History:    5-16-94   srikants   Modified to deal with failures during
//                                   append of docs to change log.
//
//  Notes:      This CANNOT throw
//
//----------------------------------------------------------------------------

void CResManager::LokNoFailReFileChanges( CDocList& docList )
{
    //
    // No refiling in push filtering
    //
    Win4Assert( !_fPushFiltering );

    Win4Assert ( docList.PartId() == 1 );

    CPartition* pPart = _partList.LokGetPartition ( docList.PartId() );

    pPart->LokRefileDocs( docList );
    _pFilterAgent->LokWakeUp ();
}

//+-------------------------------------------------------------------------
//
//  Member:     CResManager::LokReFileDocument, public
//
//  Returns:    Put a document back on the quueue to be refiltered
//
//  History:    05-Jan-95 BartoszM     Created
//
//--------------------------------------------------------------------------

void CResManager::LokReFileDocument ( PARTITIONID partid,
                                      WORKID wid,
                                      USN usn,
                                      VOLUMEID volumeId,
                                      ULONG retries,
                                      ULONG secQRetries )
{
    //
    // No refiling in push filtering
    //
    Win4Assert( !_fPushFiltering );

    CPartition* pPart = _partList.LokGetPartition(partid);

    //==========================================
    CChangeTrans xact( *this, pPart );

    //
    // A usn of 0 is used for all refiled documents
    //
    pPart->LokUpdateDocument (xact, wid, 0, volumeId, CI_UPDATE_OBJ, retries, secQRetries);
    xact.LokCommit();
    //==========================================
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::LokAddToSecQueue
//
//  Synopsis:   Adds the given document to the secondary change log of the
//              partition.
//
//  Arguments:  [partid]       - PartitionId
//              [wid]          - WorkId of the document
//              [volumeId]     - Volume id
//              [cSecQRetries] - Sec Q Retry Count
//
//  History:    5-09-96   srikants   Created
//
//----------------------------------------------------------------------------

void CResManager::LokAddToSecQueue( PARTITIONID partid,
                                    WORKID wid,
                                    VOLUMEID volumeId,
                                    ULONG cSecQRetries )
{
    //
    // No refiling in push filtering
    //
    Win4Assert( !_fPushFiltering );

    CPartition* pPart = _partList.LokGetPartition(partid);

    //==========================================
    CChangeTrans xact( *this, pPart );
    pPart->LokAddToSecQueue (xact, wid, volumeId, cSecQRetries);
    xact.LokCommit();
    //==========================================
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::LokRefileSecQueueDocs
//
//  Synopsis:   Transfers the documents from the secondary queue to the primary
//              change queue.
//
//  History:    5-09-96   srikants   Created
//
//----------------------------------------------------------------------------

void CResManager::LokRefileSecQueueDocs()
{
    //
    // No refiling in push filtering
    //
    Win4Assert( !_fPushFiltering );

    CPartIter iter;
    for ( iter.LokInit(_partList); !iter.LokAtEnd(); iter.LokAdvance(_partList))
    {
        CPartition * pPart = iter.LokGet();

        //==========================================
        CChangeTrans    xact( *this, pPart );
        pPart->LokRefileSecQueue( xact );
        xact.LokCommit();
        //==========================================
    }
}


//+-------------------------------------------------------------------------
//
//  Member:     CResManager::LokTransferWordlist, public
//
//  Returns:    Transfer a wordlist from the filter manager
//              to the content index
//
//  History:    05-Jan-95 BartoszM     Created
//
//--------------------------------------------------------------------------

BOOL CResManager::LokTransferWordlist ( PWordList& pWordList )
{
    BOOL fSuccess = TRUE;
    PARTITIONID partid = _docList.PartId();
    CPartition* pPart = _partList.LokGetPartition(partid);
    INDEXID iid = pWordList->GetId();


    TRY
    {
        //==========================================
        CIndexTrans  xact( *this );

        //
        // LokDone can fail while trying to add unfinished docs to the
        // change log. In that case we must disable further USN updates.
        // We should commit the change transaction immediately after the
        // LokDone succeeds.
        //
        CChangeTrans changeXact( *this, pPart );
        pPart->LokDone ( changeXact, iid, _docList );
        ciFAILTEST( STATUS_NO_MEMORY );
        changeXact.LokCommit();

        _fresh.LokAddIndex ( xact, iid, _activeDeletedIndex.GetIndex(),
                             _docList, *pWordList );
        ciDebugOut (( DEB_ITRACE, "CFilterManager::FilterDone "
                    "Success: transfering wordlist\n" ));

        pWordList->Done();

        //
        // This may be an empty wordlist: all documents were deleted or in error.
        // If so, just get rid of the wordlist.
        //

        if ( pWordList->IsEmpty() )
        {
#if CIDBG == 1
            unsigned cShouldHaveData = 0;
            unsigned cDocuments = _docList.Count();

            for ( unsigned iDoc = 0; iDoc < cDocuments; iDoc++ )
            {
                switch ( _docList.Status(iDoc) )
                {
                case TOO_MANY_RETRIES:
                case SUCCESS:
                case TOO_MANY_BLOCKS_TO_FILTER:
                    cShouldHaveData++;
                    break;

                default:
                    break;
                }
            }

            if ( cShouldHaveData != 0 )
            {
                for ( unsigned iDoc = 0; iDoc < cDocuments; iDoc++ )
                {
                    ciDebugOut(( DEB_ERROR, "WID: 0x%x   STATUS: %d\n",
                                 _docList.Wid(iDoc), _docList.Status(iDoc) ));
                }
            }

            Win4Assert( cShouldHaveData == 0 );
#endif
            pWordList.Delete();
        }
        else
            pWordList.Transfer ( pPart );

        xact.LokCommit();

    }
    CATCH ( CException, e )
    {
        ciDebugOut(( DEB_ERROR,
                     "Exception 0x%x caught commiting wordlist.\n"
                     "delete wordlist and clear doclist\n",
                     e.GetErrorCode() ));
        fSuccess = FALSE;
    }
    END_CATCH

    _eventMerge.Set();  // wake up
    return fSuccess;
}


//+---------------------------------------------------------------------------
//
//  Function:   LokNoFailRemoveWordlist
//
//  Synopsis:   Removes the biggest wordlist from memory and adds the
//              documents back to the change log.
//
//  Requires:   _docList.Count() == 0  when this method is called. This
//              relies on the fact that there are no other outstanding
//              wordlists being constructed.
//
//  History:    10-20-94   srikants   Added the header section and modified
//                                    to deal with the change in refiling
//                                    documents.
//
//----------------------------------------------------------------------------

void CResManager::LokNoFailRemoveWordlist( CPartition * pPart )
{
    //
    // In push filtering, we don't delete completed wordlist to simplify
    // the logic of aborting the notification transaction on the client
    //
    if ( _isBeingEmptied || _fPushFiltering )
        return;

    //
    // Look for biggest wordlist
    //

    unsigned cInd;
    CIndex ** apIndex = pPart->LokQueryMergeIndexes( cInd, mtWordlist );
    XPtrST<CIndex *> xIndex( apIndex );

    unsigned size = 0;
    CWordList * pWordList = 0;

    for ( unsigned i = 0; i < cInd; i++ )
    {
        apIndex[i]->Release();

        if ( apIndex[i]->Size() > size )
        {
            Win4Assert( apIndex[i]->IsWordlist() );

            pWordList = (CWordList *)apIndex[i];
            size = pWordList->Size();
        }
    }


    if ( pWordList )
    {
        //
        // Reschedule contents for later filtering.
        //

        INDEXID iid = pWordList->GetId();
        ciDebugOut(( DEB_ITRACE,
                 "Removing wordlist %x to reduce resource consumption.\n",
                  iid ));

        CDocList _docList;
        _docList.SetPartId ( pPart->GetId() );

        pWordList->GetDocuments( _docList );

#if CIDBG == 1
        for ( unsigned cWid = 0; cWid < _docList.Count(); cWid++ )
        {
            ciDebugOut(( DEB_ITRACE, "Reschedule filtering of wid %d\n",
                         _docList.Wid( cWid ) ));
        }
#endif // CIDBG == 1

        LokNoFailReFileChanges ( _docList );

        //
        // Remove it
        //

        ciDebugOut(( DEB_WARN, "Deleting wordlist with USN (%x, %x).\n",
                 lltoHighPart(pWordList->Usn()), (ULONG) pWordList->Usn() ));

        pPart->LokRemoveIndex( iid );

        if ( !pWordList->InUse() )
        {
            _partList.LokRemoveIndex ( iid );
            delete pWordList;
        }
        else
        {
            pWordList->Zombify();
        }
    }
}


//+-------------------------------------------------------------------------
//
//  Member:     CResManager::LokMakeWordlistId, public
//
//  Returns:    Makes unique wordlist id
//
//  History:    05-Jan-95 BartoszM     Created
//
//--------------------------------------------------------------------------

INDEXID CResManager::LokMakeWordlistId (PARTITIONID partid)
{
    CPartition* pPart = _partList.LokGetPartition(partid);

    if ( pPart == 0 )
        return iidInvalid;

    // Get unique index id
    INDEXID iid = pPart->LokMakeWlstId ();
    ciDebugOut((DEB_FILTERWIDS,
                "CResManager::LokMakeWorkListId - new wordlist iid: %x\n", iid));

    return iid;
}


//+-------------------------------------------------------------------------
//
//  Member:      CResManager::ReportFilterFailure, public
//
//  Synopsis:    Write to event log about unsuccessful filtering
//
//  Arguments:   [wid] -- workid of the file that couldn't be filtered
//
//  History:    05-Jan-95 BartoszM     Created
//
//--------------------------------------------------------------------------

void CResManager::ReportFilterFailure (WORKID wid)
{
    PROPVARIANT var;
    var.vt = VT_UI4;
    var.ulVal = wid;

    _adviseStatus->NotifyStatus( CI_NOTIFY_FILTERING_FAILURE,
                                 1,
                                 &var );
}


//=========
// SCANNING
//=========


//+---------------------------------------------------------------------------
//
//  Member:     CResManager::SetUpdateLost
//
//  Synopsis:   Sets that an update got lost and wakes up the merge thread
//              to notify the client about it.
//
//  Arguments:  [partId] - Partition id
//
//  History:    1-24-97   srikants   Created
//
//----------------------------------------------------------------------------

void CResManager::SetUpdatesLost( PARTITIONID partId )
{
    CPriLock    lock(_mutex);
    if ( _state.LokGetState() != eUpdatesToBeDisabled )
    {
       _state.LokSetState( eUpdatesToBeDisabled );
       _state.LokSetUpdateType( eIncremental );
       StopCurrentMerge();
       _eventMerge.Set();
    }

}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::LokCleanupForFullCiScan, public
//
//  History:    10-Nov-94   DwightKr    Created
//
//  Notes:      This routine performs the disk I/O and other operations
//              which does the cleanup after a corruption is detected.
//
//              This routine is similar to the Empty() method, except
//              that we do not delete the change log object or the
//              freshlog object, and the index list must contain
//              the change log and fresh log after the cleanup is complete.
//
//----------------------------------------------------------------------------
void CResManager::LokCleanupForFullCiScan()
{
    if ( 0 != _pMerge )                     // Stop in progress merges.
        _pMerge->LokAbort();

    //
    //  Delete everything from the index table here first.
    //
    _idxTab->LokEmpty();

    //
    //  If anything fails after this point, chkdsk /f or autochk will
    //  release the disk storage associated with the leaked objects
    //  since they are no longer part of the persistent index list.
    //

    //
    //  For each partition, zombify all indexes, and re-add the change
    //  log to the index table on disk.
    //
    CPartIter iter;
    for ( iter.LokInit(_partList);
         !iter.LokAtEnd();
          iter.LokAdvance(_partList))
    {
        CPartition* pPart = iter.LokGet();
        Win4Assert( pPart != NULL );

        pPart->LokDisableUpdates();

        //
        //  Zombify all indexes in this partition, and delete them if
        //  their ref-count is 0.
        //
        unsigned cIndex;
        CIndex ** aIndex = pPart->LokZombify( cIndex );
        ReleaseIndexes( cIndex, aIndex, NULL );

        delete [] aIndex;

        WORKID widMMergeLog;
        WORKID widDummy;
        pPart->GetMMergeObjectIds( widMMergeLog, widDummy, widDummy );
        if ( widMMergeLog != widInvalid)
            _storage.RemoveObject( widMMergeLog );

        //
        //  Empty the fresh test and the fresh log, and add it to the
        //  index table on disk.
        //
        pPart->LokEmpty();          // Release change list/log storage

        WORKID oidChangeLog = pPart->GetChangeLogObjectId();
        Win4Assert( oidChangeLog != widInvalid );
       _partList.LokAddIt( oidChangeLog, itChangeLog, pPart->GetId() );
    }

    //
    //  Empty the fresh test, fresh log, and add the fresh log to
    //  the index table on disk.
    //
    _fresh.LokInit();               // Release fresh test/log storage
    WORKID oidFreshLog = _storage.GetSpecialItObjectId(itFreshLog);
    Win4Assert( oidFreshLog != widInvalid );
    _partList.LokAddIt( oidFreshLog, itFreshLog, partidFresh1 );

#ifdef KEYLIST_ENABLED
    WORKID widKeyList = _storage.GetSpecialItObjectId( itMMKeyList );
#else
    WORKID widKeyList = widInvalid;
#endif  // KEYLIST_ENABLED

    WORKID widPhrLat  = _storage.GetSpecialItObjectId( itPhraseLat );

    if ( widKeyList != widInvalid)
    {
        _storage.RemoveObject( widKeyList );
        _storage.SetSpecialItObjectId( itMMKeyList, widInvalid );
    }

    if ( widPhrLat != widInvalid)
    {
        _storage.RemoveObject( widPhrLat );
        _storage.SetSpecialItObjectId( itPhraseLat, widInvalid );
    }

    _isBeingEmptied = FALSE;                // Allow new queries
}

// ===========================================================
// Content Index Frame Work Interfacing.
// ===========================================================

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::WorkidToDocName
//
//  Synopsis:   Converts the given workid to path and fill it in the buffer
//              given.
//
//  Arguments:  [wid]       -- Workid to convert.
//              [pbBuf]     --  Destination buffer;
//              [cb]        -- On input the total number of bytes that can be
//                             put. On output, the actual number of bytes copied.
//                             For a deleted file, the cb will be 0.
//              [fAccurate] -- TRUE --> Attempt to get a more accurate
//                             workid-to-path translation.
//
//  Returns:    TRUE if successfully converted; FALSE means the buffer was
//              not big enough.
//
//  History:    12-04-96   srikants   Created
//
//  Notes:      WorkidToDocName is called outside the resman lock. It is a
//              potentially long operation and taking resman lock is not
//              advisable. Also, creation of a CWorkidToDocName object once
//              for each wid->docName conversion is expensive. So, we create
//              one in resman constructor and protect it with a separate
//              lock.
//
//----------------------------------------------------------------------------

BOOL CResManager::WorkidToDocName( WORKID wid,
                                   BYTE * pbBuf,
                                   unsigned & cb,
                                   BOOL fAccurate )
{

    CLock   lock(_workidToDocNameMutex);

    ULONG cbDocName;

    BYTE const * pbDocName = _xWorkidToDocName->GetDocName( wid, cbDocName, fAccurate );

    BOOL fStatus = TRUE;

    if ( pbDocName )
    {
        ciDebugOut(( DEB_FILTERWIDS | DEB_NOCOMPNAME, 
                     "WorkidToDocName: wid is %d and DocName is %ws\n",
                     wid, 
                     pbDocName ));

        if ( cbDocName > cb )
            fStatus = FALSE;
        else
            RtlCopyMemory( pbBuf, pbDocName, cbDocName );

        cb = cbDocName;
    }
    else
    {
        cb = 0;
    }

    return fStatus;
} // WorkidToDocName

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::GetClientState
//
//  Synopsis:   Gets the client specific status information like total
//              number of documents.
//
//  Arguments:  [cDocuments]    - [out] Total # of documents
//
//  History:    12-04-96   srikants   Created
//
//----------------------------------------------------------------------------

void CResManager::GetClientState( ULONG & cDocuments )
{
    CI_CLIENT_STATUS    status;
    SCODE sc = _docStore->GetClientStatus( &status );
    if ( S_OK == sc )
    {
        cDocuments = status.cDocuments;
    }
    else
    {
        cDocuments = 0;
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::LokNotifyCorruptionToClient
//
//  Synopsis:   Informs the client that the catalog is corrupt
//
//  History:    12-04-96   srikants   Created
//
//----------------------------------------------------------------------------

void CResManager::LokNotifyCorruptionToClient()
{
    Win4Assert( _isCorrupt );

    DisableUpdates( partidDefault );

    SCODE sc = _docStore->DisableUpdates( FALSE, CI_CORRUPT_INDEX );
    if ( SUCCEEDED( sc ) )
        _state.LokSetCorruptionNotified();

    //
    // Disabled following assert so that the general NT 5 user is not
    // bothered by this assert.
    //

    //Win4Assert( ! "We must empty the Content Index" );
    // Empty();
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::LokNotifyDisableUpdatesToClient
//
//  Synopsis:   Informs the client that updates should be disabled
//
//  History:    07-05-97   SitaramR   Created
//
//----------------------------------------------------------------------------

void CResManager::LokNotifyDisableUpdatesToClient()
{
    Win4Assert( _state.LokGetState() == eUpdatesToBeDisabled );

    DisableUpdates( partidDefault );
    BOOL fIncremental = _state.LokGetUpdateType() == eIncremental;

    SCODE sc = _docStore->DisableUpdates( fIncremental, CI_LOST_UPDATE );
    if ( SUCCEEDED(sc) )
    {
        ciDebugOut(( DEB_WARN, "Notified disabled updates to DocStore\n" ));
        _state.LokSetState( eUpdatesDisabled );
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::LokNotifyEnableUpdatesToClient
//
//  Synopsis:   Informs the client that updates should be enabled
//
//  History:    07-05-97   SitaramR   Created
//
//----------------------------------------------------------------------------

void CResManager::LokNotifyEnableUpdatesToClient()
{
   Win4Assert( _state.LokGetState() == eUpdatesToBeEnabled );
   EnableUpdates( partidDefault );

   SCODE sc = _docStore->EnableUpdates();
   if ( SUCCEEDED(sc) )
   {
      ciDebugOut(( DEB_WARN, "Notified enable updates to DocStore\n" ));
      _state.LokSetState( eSteady );
   }
}

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::StoreValue
//
//  Synopsis:   Stores the given value in the document store
//
//  Arguments:  [wid] - WorkId of the document
//              [ps]  - The FULLPROPSPEC of the property
//              [var] - Value of the property
//              [fInSchema] - Returns TRUE if the property is in the schema
//
//  Returns:    SCODE result
//
//  History:    12-18-96   srikants   Created
//
//----------------------------------------------------------------------------

SCODE CResManager::StoreValue( WORKID wid,
                               CFullPropSpec const & ps,
                               CStorageVariant const & var,
                               BOOL & fInSchema )
{
    FULLPROPSPEC const * fps = ps.CastToStruct();
    PROPVARIANT const * pVar = ConvertToPropVariant( &var );

    // Default to TRUE since the return code isn't checked!

    fInSchema = TRUE;

    SCODE sc = _propStore->StoreProperty( wid, fps, pVar );

    if ( CI_E_PROPERTY_NOT_CACHED == sc )
        fInSchema = FALSE;
    else if ( FAILED( sc ) )
        return sc;

    return S_OK;
}

BOOL CResManager::StoreSecurity( WORKID wid,
                                 PSECURITY_DESCRIPTOR pSD,
                                 ULONG cbSD )
{
    return S_OK == _docStore->StoreSecurity( wid, (BYTE *) pSD, cbSD );
}


//+---------------------------------------------------------------------------
//
//  Member:     CResManager::FPSToPROPID, public
//
//  Synopsis:   Converts FULLPROPSPEC property to PROPID
//
//  Arguments:  [fps] -- FULLPROPSPEC representation of property
//              [pid] -- PROPID written here on success
//
//  Returns:    S_OK on success
//
//  History:    29-Dec-1997  KyleP      Created.
//
//----------------------------------------------------------------------------

SCODE CResManager::FPSToPROPID( CFullPropSpec const & fps, PROPID & pid )
{
    return _mapper->PropertyToPropid( fps.CastToStruct(), TRUE, &pid );
}

//
// Flush notify support
//

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::NotifyFlush
//
//  Synopsis:   Notifies the client that changes got flushed and gives the
//              flush information.
//
//  Arguments:  [ft]       - FileTime of the last successful flush.
//              [cEntries] - Number of entries in the aInfo
//              [aInfo]    - Array of USN information for the flush.
//
//  History:    1-27-97   srikants   Created
//
//  Note:       We should not call directly into framework client because
//              it violates resman->client lock hierarchy. We should not call
//              into client with resman lock held.
//
//----------------------------------------------------------------------------

void CResManager::NotifyFlush( FILETIME const & ft,
                               ULONG cEntries,
                               USN_FLUSH_INFO ** ppInfo )
{
    if ( !_fPushFiltering )
    {
       //
       // Notification of flushes are sent in pull filtering only.
       // For push filtering, the ICiCIndexNotificationStatus
       // interface is used to notify clients.
       //

       CPriLock   lock(_mutex);

       CChangesFlushNotifyItem * pItem =
               new CChangesFlushNotifyItem( ft, cEntries, ppInfo );
       _flushList.Queue( pItem );

       if (! _fFlushWorkerActive)
       {
           CFwFlushNotify * pWorkItem = new CFwFlushNotify( _workMan, *this );
           _workMan.AddToWorkList( pWorkItem );

           pWorkItem->AddToWorkQueue();
           pWorkItem->Release();
        }
    }
} //NotifyFlush

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::ProcessFlushNotifies
//
//  Synopsis:   Processes the queued flush notifies in a FIFO order. This
//              method is called only by the asynchronous worker thread
//              responsible for notifying the client about flushes.
//
//  History:    1-27-97   srikants   Created
//
//----------------------------------------------------------------------------

void CResManager::ProcessFlushNotifies()
{

   //
   // Only one worker thread allowed to operate on the list at a time.
   //
   CLock lock (_mtxChangeFlush );
   CRevertBoolValue RevertFlushActive( _fFlushWorkerActive, TRUE );

   while ( TRUE )
   {
      CChangesFlushNotifyItem * pItem = 0;

      //
      // Remove the first flush item from the list under resman lock.
      //

      //===========================================
      {
         CPriLock   lock(_mutex);
         pItem = _flushList.GetFirst();
         if ( pItem )
            _flushList.Pop();

         if (_flushList.IsEmpty())
             RevertFlushActive.Revert();
      }
      //===========================================

      if ( pItem )
      {

         SCODE sc = _docStore->CheckPointChangesFlushed(
                         pItem->GetFlushTime(),
                         pItem->GetCount(),
                         pItem->GetFlushInfo() );

         if ( FAILED(sc) )
         {
             //
             // We don't have to requeue this item. The client's restart
             // work will just increase.
             //
             ciDebugOut(( DEB_ERROR,
                          "CheckPointChangesFlushed failed. Error (0x%X)\n",
                          sc ));
         }

         pItem->Close();
         delete pItem;
      }
      else
      {
         break;
      }
   } // While (TRUE)
} //ProcessFlushNotifies

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::_LokDeleteFlushWorkItems
//
//  Synopsis:   Deletes the flush notifies in the queue. This is called
//              during shutdown (dismount) to clean up any asynchronous
//              worker threads referring to resman.
//
//  History:    1-27-97   srikants   Created
//
//----------------------------------------------------------------------------

void CResManager::LokDeleteFlushWorkItems()
{
    for ( CChangesFlushNotifyItem * pItem = _flushList.Pop();
          0 != pItem;
          pItem = _flushList.Pop() )
    {
        pItem->Close();
        delete pItem;
    }
} //LokDeleteFlushWorkItems

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::NoFailAbortWidsInDocList
//
//  Synopsis:   Used in push filtering to abort all wids in the current
//              doclist
//
//  History:    24-Feb-97   SitaramR   Created
//
//----------------------------------------------------------------------------

void CResManager::NoFailAbortWidsInDocList()
{
   CPriLock lock ( _mutex );

   if ( _isBeingEmptied )
       return ;

   for ( unsigned i=0; i<_docList.Count(); i++ )
   {
       WORKID wid = _docList.Wid(i);
       USN usn = _docList.Usn(i);

       //
       // Abort the client transaction
       //
       _xIndexNotifTable->AbortWid( wid, usn );

       //
       // Keep track of aborted wids for use during
       // changlog shrink from front.
       //
       _aAbortedWids.NoFailLokAddWid( wid, usn );
   }

   _docList.LokClear();
} //NoFailAbortWidsInDocList

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::NoFailAbortWid
//
//  Synopsis:   Used in push filtering to abort a wid
//
//  Arguments:  [wid]  -- Workid
//              [usn]  -- Usn
//
//  History:    24-Feb-97   SitaramR   Created
//
//----------------------------------------------------------------------------

void CResManager::NoFailAbortWid( WORKID wid, USN usn )
{
    CPriLock lock ( _mutex );

    if ( _isBeingEmptied )
        return ;

    //
    // Abort the client transaction
    //
    _xIndexNotifTable->AbortWid( wid, usn );

    //
    // Keep track of aborted wid for use during
    // changlog shrink from front.
    //
    _aAbortedWids.NoFailLokAddWid( wid, usn );
} //NoFailAbortWid

//+---------------------------------------------------------------------------
//
//  Member:     CResManager::ClearNonStoragePropertiesForWid
//
//  Synopsis:   Clear non-storage properties from the property storage for wid
//
//  Arguments:  [wid]  -- Workid
//
//  History:    10-Oct-2000   KitmanH   Created
//
//----------------------------------------------------------------------------

SCODE CResManager::ClearNonStoragePropertiesForWid( WORKID wid )
{
    return _propStore->ClearNonStoragePropertiesForWid( wid );
}

//====================
// MASTER MERGE POLICY
//====================

//+---------------------------------------------------------------------------
//
//  Member:     CMasterMergePolicy::IsTimeToMasterMerge, public
//
//  Synopsis:   Determines if the system should start a master merge
//              immediately, and not wait until the master merge time.
//
//  History:    4-Aug-94    DwightKr        Created
//
//  Notes:      If new tests are added simply add them here.
//
//----------------------------------------------------------------------------
BOOL CMasterMergePolicy::IsTimeToMasterMerge() const
{
    return IsMaxShadowIndexSize()    ||
           IsMaxFreshListCount()     ||
           IsMinDiskFreeForceMerge();
}

//+---------------------------------------------------------------------------
//
//  Member:     CMasterMergePolicy::IsMinDiskFreeForceMerge, private
//
//  Synopsis:   If the amount of free disk space is lower than a threshold,
//              then we should master merge, since it is likely that disk
//              space will be released.
//
//  History:    4-Aug-94    DwightKr        Created
//              Jan-07-96   mohamedn        CDmFwEventItem
//
//----------------------------------------------------------------------------
BOOL CMasterMergePolicy::IsMinDiskFreeForceMerge() const
{
    //
    //  If we've exceeded the MIN_DISKFREE_FORCE_MERGE space used on
    //  the system, and a master merge has the potential to make some
    //  useful progress, then it's time to force a master merge.  The
    //  values of MIN_DISKFREE_FORCE_MERGE and MAX_SHADOW_FREESPACE
    //  are expressed as a percentage.
    //
    __int64 diskTotal, diskRemaining;
    _storage.GetDiskSpace( diskTotal, diskRemaining );

    if ( diskRemaining < minDiskFreeForMerge )
        return FALSE;

    if (diskRemaining*100/diskTotal < _frmwrkParams.GetMinDiskFreeForceMerge() &&
        _shadowIndexSize*100/diskRemaining > _frmwrkParams.GetMaxShadowFreeForceMerge() )
    {
       CDmFwEventItem item(  EVENTLOG_INFORMATION_TYPE,
                             MSG_CI_MASTER_MERGE_STARTED_TOTAL_DISK_SPACE_USED,
                             2);

       item.AddArg( _storage.GetVolumeName() );
       item.AddArg( _frmwrkParams.GetMinDiskFreeForceMerge() );

       item.ReportEvent(_adviseStatus);

       ciDebugOut(( DEB_ITRACE | DEB_PENDING,
                    "Master merge, reason: Low on disk space\n" ));

       return TRUE;
    }

    return FALSE;
}

//+---------------------------------------------------------------------------
//
//  Member:     CMasterMergePolicy::IsMaxFreshListCount, private
//
//  Synopsis:   If the number of entries in the fresh list is great than
//              a threshold, we should master merge, and free up memory
//              occupied by the fresh hash.
//
//  History:    4-Aug-94    DwightKr        Created
//              Jan-07-96   mohamedn        CDmFwEventItem
//
//----------------------------------------------------------------------------
BOOL CMasterMergePolicy::IsMaxFreshListCount() const
{
    if ( _freshCount > _frmwrkParams.GetMaxFreshCount() )
    {

       CDmFwEventItem item( EVENTLOG_INFORMATION_TYPE,
                            MSG_CI_MASTER_MERGE_STARTED_FRESH_LIST_COUNT,
                            2);

       item.AddArg( _storage.GetVolumeName() );
       item.AddArg( _frmwrkParams.GetMaxFreshCount() );

       item.ReportEvent(_adviseStatus);

       ciDebugOut(( DEB_ITRACE | DEB_PENDING,
                    "Master merge, reason: More than %d entries in freshhash\n",
                     _frmwrkParams.GetMaxFreshCount() ));

       return TRUE;
    }

    return FALSE;
}

//+---------------------------------------------------------------------------
//
//  Member:     CMasterMergePolicy::IsMaxShadowIndexSize, private
//
//  Synopsis:   If the combined size of all shadow indexes is greater than
//              a threahold, we should master merge, and free up disk space.
//              The content index should not take up more than a given
//              percentage of disk space.
//
//  History:    4-Aug-94    DwightKr        Created
//              Jan-07-96   mohamedn        CDmFwEventItem
//
//----------------------------------------------------------------------------
BOOL CMasterMergePolicy::IsMaxShadowIndexSize() const
{
    __int64 diskTotal, diskRemaining;
    _storage.GetDiskSpace( diskTotal, diskRemaining );

    if ( diskRemaining < minDiskFreeForMerge )
        return FALSE;

    if ( _shadowIndexSize*100/diskTotal > _frmwrkParams.GetMaxShadowIndexSize() )
    {

       CDmFwEventItem item( EVENTLOG_INFORMATION_TYPE,
                            MSG_CI_MASTER_MERGE_STARTED_SHADOW_INDEX_SIZE,
                            2);

       item.AddArg( _storage.GetVolumeName() );
       item.AddArg( _frmwrkParams.GetMaxShadowIndexSize() );

       item.ReportEvent(_adviseStatus);

       ciDebugOut(( DEB_ITRACE | DEB_PENDING,
                    "Master merge, reason: Combined shadow indexes > %d%% of disk\n",
                     _frmwrkParams.GetMaxShadowIndexSize() ));

       return TRUE;
    }

    return FALSE;
}

//+---------------------------------------------------------------------------
//
//  Method:     CMasterMergePolicy::ComputeMidNightMergeTime
//
//  Synopsis:   Function to compute the time (in hundreds of nanoseconds
//              from midnight Dec 31, 1969) for the next master merge based on
//              the value given from registry.
//
//  Arguments:  [mergeTime] -  The time read from the registry control params.
//                             It's an offset in minutes after midnight.
//
//  Returns:    LONG - time for next merge
//
//  History:    19-Feb-1996  srikants   Moved the code from svcfilt.cxx
//              17-Nov-1999  KyleP      Stop using CRT
//
//  Notes:      If the time is before January 1, 1970 it will return -1.
//
//----------------------------------------------------------------------------

ULONGLONG CMasterMergePolicy::ComputeMidNightMergeTime( LONG mergeTime )
{
    Win4Assert( mergeTime < 24 * 60 );
    mergeTime *= 60;

    //
    // What time is it here?
    //

    SYSTEMTIME stLocal;
    GetLocalTime( &stLocal );

    LONG lNow = stLocal.wHour*60*60 + stLocal.wMinute*60 + stLocal.wSecond;

    //
    // Have we passed the daily merge time?
    //

    LONGLONG llHNSTilMerge;   // Hundred-Nano-Second-Til-Merge
    LONGLONG const llHNSOneDay = 24*60*60*10000000i64;

    if ( lNow < mergeTime )
        llHNSTilMerge = mergeTime * 10000000i64;
    else
        llHNSTilMerge = mergeTime * 10000000i64 + llHNSOneDay;

    //
    // Create the *local* filetime for the next merge
    //

    stLocal.wHour = 0;
    stLocal.wMinute = 0;
    stLocal.wSecond = 0;
    stLocal.wMilliseconds = 1;

    FILETIME ftLocal;
    if ( !SystemTimeToFileTime( &stLocal, &ftLocal ) )
        return -1;

    ((ULARGE_INTEGER *)&ftLocal)->QuadPart += llHNSTilMerge;

    //
    // Now, adjust to UTC and return
    //

    FILETIME ft;

    if ( !LocalFileTimeToFileTime( &ftLocal, &ft ) )
        return -1;

#if CIDBG == 1
    FILETIME ft2;

    GetSystemTimeAsFileTime( &ft2 );

    Win4Assert( ((ULARGE_INTEGER *)&ft2)->QuadPart < ((ULARGE_INTEGER *)&ft)->QuadPart );
#endif // CIDBG

    return ((ULARGE_INTEGER *)&ft)->QuadPart;
} //ComputeMidNightMergeTime

//+---------------------------------------------------------------------------
//
//  Member:     CMasterMergePolicy::IsMidNightMergeTime, public
//
//  Synopsis:   Determines if it's been 24 hours since the last merge time
//
//  History:     4-Aug-1994  DwightKr   Created
//              17-Nov-1999  KyleP      Stop using CRT
//
//----------------------------------------------------------------------------

BOOL CMasterMergePolicy::IsMidNightMergeTime()
{
    FILETIME ft;
    GetSystemTimeAsFileTime( &ft );

    if ( ((ULARGE_INTEGER *)&ft)->QuadPart > _mergeTime )
    {
        ULONG mergeTime = _frmwrkParams.GetMasterMergeTime();

        ULONGLONG oldMergeTime = _mergeTime;

        _mergeTime = ComputeMidNightMergeTime( mergeTime );

        //
        // If it's been more than 30 minutes since the master merge time, don't
        // do it.  This can happen if we were doing a master merge as part of
        // indexing documents or if you turn on a machine in the morning and it
        // was off at the master merge time.
        //

        LONGLONG const llHNSHalfHour = 30*60*10000000i64;

        if ( ((ULARGE_INTEGER *)&ft)->QuadPart > ( oldMergeTime + llHNSHalfHour ) )
            return FALSE;

        return TRUE;
    }

    return FALSE;
} //IsMidNightMergeTime