//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1991 - 1999.
//
//  File:       VMap.cxx
//
//  Contents:   Virtual <--> physical path map.
//
//  Classes:    CVMap
//
//  History:    05-Feb-96   KyleP       Created
//
//  Notes:      Automatic and manual roots have the following relationship.
//
//              1. If a root is purely manual (e.g. not in Gibraltar), then
//                 addition is only by the user, and deletion really deletes
//                 the root.  A placeholder is not maintained in the vroot
//                 list.
//
//              2. If a root is purely automatic, then it is always used,
//                 and is added and removed in complete sync with Gibraltar.
//
//              3. If a root was manual and was automatically added, the 'in-use'
//                 state from pure-manual mode is maintained.
//
//              4. If a root was automatic and was manually added, it is
//                 always set to 'in-use'.
//
//              5. If a root was both manual and automatic and automaticlly
//                 deleted the deletion occurs.  Period.
//
//              6. If a root was both manual and automatic and manually deleted,
//                 the root is kept as an out-of-use placeholder.
//
//
//----------------------------------------------------------------------------

#include <pch.cxx>
#pragma hdrstop

#include <rcstxact.hxx>
#include <rcstrmit.hxx>

#include <vmap.hxx>
#include <funypath.hxx>

//+-------------------------------------------------------------------------
//
//  Member:     CVMapDesc::Init, public
//
//  Synopsis:   Initialize virtual root descriptor
//
//  Arguments:  [pVRoot]     -- Virtual root
//              [ccVRoot]    -- Size in chars of [pwcVRoot]
//              [pPRoot]     -- Physical root
//              [ccPRoot]    -- Size in chars of [pwcPRoot]
//              [idParent]   -- Link to parent
//              [fAutomatic] -- TRUE for root tied to IIS
//              [eType]      -- VRoot type
//              [fIsIndexed] -- TRUE if the item should be indexed (used)
//              [fNonIndexedVDir] -- TRUE if a non-indexed virtual directory
//
//  History:    11-Feb-96 KyleP     Created
//              24 Nov 99 KLam      Don't pre-reference zero length strings.
//
//--------------------------------------------------------------------------

void CVMapDesc::Init( WCHAR const * pVRoot,
                      ULONG ccVRoot,
                      WCHAR const * pPRoot,
                      ULONG ccPRoot,
                      ULONG idParent,
                      BOOL fAutomatic,
                      CiVRootTypeEnum eType,
                      BOOL fIsIndexed,
                      BOOL fNonIndexedVDir )
{
    Win4Assert( ccVRoot < MAX_PATH );
    Win4Assert( ccPRoot < MAX_PATH );

    RtlCopyMemory(  _wcVScope, pVRoot, ccVRoot * sizeof(WCHAR ) );
    RtlCopyMemory(  _wcPScope, pPRoot, ccPRoot * sizeof(WCHAR ) );

    _ccVScope = ccVRoot;
    _ccPScope = ccPRoot;
    if ( (0 == _ccVScope) || (_wcVScope[_ccVScope - 1] != L'\\') )
        _wcVScope[_ccVScope++] = L'\\';

    if ( (0 == _ccPScope) || (_wcPScope[_ccPScope - 1] != L'\\') )
        _wcPScope[_ccPScope++] = L'\\';

    // null-terminate these for readability

    _wcVScope[ _ccVScope ] = 0;
    _wcPScope[ _ccPScope ] = 0;

    _idParent = idParent;

    if ( fAutomatic )
        _Type = PCatalog::AutomaticRoot;
    else
        _Type = PCatalog::ManualRoot;

    if ( fIsIndexed )
        _Type |= PCatalog::UsedRoot;

    if ( fNonIndexedVDir )
        _Type |= PCatalog::NonIndexedVDir;

    #if CIDBG == 1
        if ( fNonIndexedVDir )
            Win4Assert( !fIsIndexed );
    #endif // CIDBG == 1

    if ( NNTPVRoot == eType )
        _Type |= PCatalog::NNTPRoot;
    else if ( IMAPVRoot == eType )
        _Type |= PCatalog::IMAPRoot;
} //Init

//+-------------------------------------------------------------------------
//
//  Member:     CVMapDesc::IsVirtualMatch, public
//
//  Synopsis:   Virtual scope test
//
//  Arguments:  [pVPath]  -- Virtual path
//              [ccVPath] -- Size in chars of [pVPath]
//
//  Returns:    TRUE if [pVPath] is an exact match (sans slash)
//
//  History:    11-Feb-96 KyleP     Created
//              24 Nov 99 KLam      Don't pre-reference zero length strings.
//
//--------------------------------------------------------------------------

BOOL CVMapDesc::IsVirtualMatch( WCHAR const * pVPath, unsigned ccVPath ) const
{
    //
    // Adjust for possible lack of terminating backslash.
    //

    unsigned ccComp;

    if ( (0 == ccVPath) || (pVPath[ccVPath-1] != L'\\') )
        ccComp = _ccVScope - 1;
    else
        ccComp = _ccVScope;

    //
    // Compare strings.
    //

    if ( ccComp == ccVPath )
        return RtlEqualMemory( _wcVScope, pVPath, ccVPath * sizeof(WCHAR) );
    else
        return FALSE;
}

//+-------------------------------------------------------------------------
//
//  Member:     CVMapDesc::IsPhysicalMatch, public
//
//  Synopsis:   Physical scope test
//
//  Arguments:  [pPPath]  -- Virtual path
//              [ccPPath] -- Size in chars of [pPPath]
//
//  Returns:    TRUE if [pPPath] is an exact match (sans slash)
//
//  History:    11-Feb-96 KyleP     Created
//              24 Nov 99 KLam      Don't pre-reference zero length strings.
//
//--------------------------------------------------------------------------

BOOL CVMapDesc::IsPhysicalMatch( WCHAR const * pPPath, unsigned ccPPath ) const
{
    //
    // Adjust for possible lack of terminating backslash.
    //

    unsigned ccComp;

    if ( (0 == ccPPath) || (pPPath[ccPPath-1] != L'\\') )
        ccComp = _ccPScope - 1;
    else
        ccComp = _ccPScope;

    //
    // Compare strings.
    //

    if ( ccComp == ccPPath )
        return RtlEqualMemory( _wcPScope, pPPath, ccPPath * sizeof(WCHAR) );
    else
        return FALSE;
}

//+-------------------------------------------------------------------------
//
//  Member:     CVMap::CVMap, public
//
//  Synopsis:   Null ctor for 2-phase initialization
//
//  History:    11-Feb-96 KyleP     Created
//
//--------------------------------------------------------------------------

CVMap::CVMap()
{
    END_CONSTRUCTION( CVMap );
}

void CVMap::Empty()
{
    delete _xrsoMap.Acquire();
    _aMap.Clear();
}

//+-------------------------------------------------------------------------
//
//  Member:     CVMap::Init, public
//
//  Synopsis:   2nd half of 2-phase initialization
//
//  Arguments:  [pobj] -- Recoverable storage for descriptors
//
//  Returns:    TRUE if map was shut down cleanly
//
//  History:    11-Feb-96 KyleP     Created
//
//--------------------------------------------------------------------------

BOOL CVMap::Init( PRcovStorageObj * pobj )
{
    _xrsoMap.Set( pobj );

    //
    // Initialize array.
    //

    CRcovStorageHdr & hdr = _xrsoMap->GetHeader();

    struct CRcovUserHdr data;
    hdr.GetUserHdr( hdr.GetPrimary(), data );

    RtlCopyMemory( &_fDirty, &data._abHdr, sizeof(_fDirty) );

    //
    // Load records into memory.
    //

    CRcovStrmReadTrans xact( _xrsoMap.GetReference() );
    CRcovStrmReadIter  iter( xact, sizeof( CVMapDesc ) );

    unsigned i = 0;

    while ( !iter.AtEnd() )
    {
        iter.GetRec( &_aMap[i] );
        i++;
    }

    if (_aMap.Count() != hdr.GetCount( hdr.GetPrimary() ) )
    {
        ciDebugOut(( DEB_ERROR,
                     "_aMap.Count() == %d, hdr.GetCount(...) = %d\n",
                     _aMap.Count(),  hdr.GetCount( hdr.GetPrimary() ) ));
    }

    Win4Assert( _aMap.Count() == hdr.GetCount( hdr.GetPrimary() ) );

    RecomputeNonIndexedInfo();

    DumpVMap();

    return !_fDirty;
} //Init

//+-------------------------------------------------------------------------
//
//  Member:     CVMap::RecomputeNonIndexedInfo, private
//
//  Synopsis:   Rebuilds _aExcludeParent when vroot info changes
//
//  History:    1-Apr-97 dlee     Created
//
//--------------------------------------------------------------------------

void CVMap::RecomputeNonIndexedInfo()
{
    // blow away existing info and create it all again

    _aExcludeParent.Clear();

    for ( unsigned i = 0; i < _aMap.Count(); i++ )
        _aExcludeParent[i] = INVALID_VMAP_INDEX;

    // look for non-indexed vdirs, and note the vroot that matches
    // both virtual and physical paths.

    for ( i = 0; i < _aMap.Count(); i++ )
    {
        if ( !_aMap[i].IsFree() &&
             _aMap[i].IsNonIndexedVDir() )
        {
            unsigned int ccBestMatch = 0;

            for ( unsigned r = 0; r < _aMap.Count(); r++ )
            {
                // if 'i' is in both virtual and physical paths of 'r',
                // make 'r' the exclude parent of 'i'.

                if ( i != r &&
                     !_aMap[r].IsFree() &&
                     !_aMap[r].IsNonIndexedVDir() )
                {
                    // -1 since we don't need to match on the '\' at the end

                    if ( _aMap[r].IsInPhysicalScope( _aMap[i].PhysicalPath(),
                                                     _aMap[i].PhysicalLength() - 1 ) )
                    {
                        if ( ( _aMap[r].VirtualLength() < _aMap[i].VirtualLength() ) &&
                             ( _aMap[r].IsVirtualMatch( _aMap[i].VirtualPath(),
                                                        _aMap[r].VirtualLength() ) ) )
                        {
                            ciDebugOut(( DEB_ITRACE, "nivd %ws found match %ws, old cc %d\n",
                                         _aMap[i].VirtualPath(),
                                         _aMap[r].VirtualPath(),
                                         ccBestMatch ));

                            if ( _aMap[r].VirtualLength() > ccBestMatch )
                            {
                                _aExcludeParent[i] = r;
                                ccBestMatch = _aMap[r].VirtualLength();
                            }
                        }
                    }
                }
            }
        }
    }
} //RecomputeNonIndexedInfo

//+-------------------------------------------------------------------------
//
//  Member:     CVMap::DumpVMap, private
//
//  Synopsis:   Dumps the current state of the vmap table
//
//  History:    1-Apr-97 dlee     Created
//
//--------------------------------------------------------------------------

void CVMap::DumpVMap()
{
#if CIDBG == 1
    for ( unsigned i = 0; i < _aMap.Count(); i++ )
    {
        ciDebugOut(( DEB_ITRACE,
                     "vmap 0x%x, isfree %d parent 0x%x\n",
                     i, _aMap[i].IsFree(), _aMap[i].Parent() ));

        if ( !_aMap[i].IsFree() )
            ciDebugOut(( DEB_ITRACE,
                         "    expar: 0x%x, nonivdir %d, inuse %d, manual %d, auto %d, nntp %d, imap %d, vpath '%ws', ppath '%ws'\n",
                         _aExcludeParent[i],
                         _aMap[i].IsNonIndexedVDir(),
                         _aMap[i].IsInUse(),
                         _aMap[i].IsManual(),
                         _aMap[i].IsAutomatic(),
                         _aMap[i].IsNNTP(),
                         _aMap[i].IsIMAP(),
                         _aMap[i].VirtualPath(),
                         _aMap[i].PhysicalPath() ));
    }
#endif // CIDBG
} //DumpVMap

//+-------------------------------------------------------------------------
//
//  Member:     CVMap::Add, public
//
//  Synopsis:   Add new virtual/physical mapping
//
//  Arguments:  [vroot]      -- New virtual root
//              [root]       -- Place in physical hierarchy
//              [fAutomatic] -- TRUE for root tied to Gibraltar
//              [idNew]      -- Id of new root returned here
//              [eType]      -- vroot type
//              [fVRoot]     -- TRUE if a vroot, not a vdir
//              [fIsIndexed] -- TRUE if it should be indexed, FALSE otherwise
//
//  Returns:    TRUE if scope was added or changed
//
//  History:    11-Feb-96 KyleP     Created
//
//--------------------------------------------------------------------------

BOOL CVMap::Add( WCHAR const * vroot,
                 WCHAR const * root,
                 BOOL fAutomatic,
                 ULONG & idNew,
                 CiVRootTypeEnum eType,
                 BOOL fVRoot,
                 BOOL fIsIndexed )
{
    Win4Assert( eType == W3VRoot ||
                eType == NNTPVRoot ||
                eType == IMAPVRoot );

    Win4Assert( !fIsIndexed || fVRoot );

    CLock   lock(_mutex);

    unsigned ccVRoot = wcslen(vroot);
    unsigned ccPRoot = wcslen(root);

    Win4Assert( ccVRoot < MAX_PATH );
    Win4Assert( ccPRoot < MAX_PATH );

    ULONG idParent = INVALID_VMAP_INDEX;
    ULONG ccParent = 0;

    idNew = INVALID_VMAP_INDEX;
    BOOL fWasInUse = FALSE;
    BOOL fWasAutomatic = FALSE;
    BOOL fWasManual = FALSE;
    BOOL fPRootChanged = TRUE;
    BOOL fWasNonIndexedVDir = FALSE;
    ULONG idOldParent = INVALID_VMAP_INDEX;

    //
    // Find virtual parent.
    //

    for ( unsigned i = 0; i < _aMap.Count(); i++ )
    {
        //
        // Is the new entry already in the list?
        //

        if ( !_aMap[i].IsFree() )
        {
            //
            // Should we even consider this entry?
            //

            if ( (W3VRoot == eType) && !_aMap[i].IsW3() )
                continue;

            if ( (NNTPVRoot == eType) && !_aMap[i].IsNNTP() )
                continue;

            if ( (IMAPVRoot == eType) && !_aMap[i].IsIMAP() )
                continue;

            if ( _aMap[i].IsVirtualMatch( vroot, ccVRoot ) )
            {
                //
                // Collect stats:
                //

                fWasInUse = _aMap[i].IsInUse();
                fWasAutomatic = _aMap[i].IsAutomatic();
                fWasManual = _aMap[i].IsManual();
                fWasNonIndexedVDir = _aMap[i].IsNonIndexedVDir();
                fPRootChanged = !_aMap[i].IsPhysicalMatch( root, ccPRoot );
                idOldParent = _aMap[i].Parent();

                ciDebugOut(( DEB_ITRACE,
                             "vroot '%ws', fWasInUse %d, fIsIndexed %d, fWasNonIndexedVDir %d\n",
                             vroot, fWasInUse, fIsIndexed, fWasNonIndexedVDir ));

                //
                // If there is no change, we can return w/o doing anything.
                //

                if ( ( ( fWasInUse && fIsIndexed ) || ( !fWasInUse && !fIsIndexed ) ) &&
                     ( (fWasNonIndexedVDir && !fVRoot) || (!fWasNonIndexedVDir && fVRoot) ) &&
                     ( (fAutomatic && fWasAutomatic) || (!fAutomatic && fWasManual) ) &&
                     !fPRootChanged )
                {
                    ciDebugOut(( DEB_ITRACE, "no change for '%ws'\n", vroot ));
                    return FALSE;
                }
                else
                {
                    ciDebugOut(( DEB_ITRACE, "modified vroot entry for '%ws'\n", vroot ));
                    idNew = i;
                    break;
                }
            }

            //
            // Is this a longer physical path match?
            //

            unsigned ccMatch = _aMap[i].PhysicalMatchLen( root );

            if ( ccMatch > ccParent && ccMatch <= ccPRoot )
            {
                ccParent = ccMatch;
                idParent = i;
            }
        }
        else
        {
            idNew = i;
            continue;
        }
    }

    //
    // Add new root.
    //

    CRcovStorageHdr & hdr = _xrsoMap->GetHeader();
    CRcovStrmWriteTrans xact( _xrsoMap.GetReference() );
    CRcovStrmWriteIter  iter( xact, sizeof(CVMapDesc) );

    Win4Assert( _aMap.Count() == hdr.GetCount( hdr.GetPrimary() ) );

    //
    // This may be a new record.
    //

    BOOL  fAppend;

    if ( idNew == INVALID_VMAP_INDEX )
    {
        ciDebugOut(( DEB_ITRACE, "new vroot entry for '%ws'\n", vroot ));
        idNew = _aMap.Count();
        fAppend = TRUE;
    }
    else
        fAppend = FALSE;

    _aMap[idNew].Init( vroot,
                       ccVRoot,
                       root,
                       ccPRoot,
                       idParent,
                       fAutomatic,
                       eType,
                       fIsIndexed,
                       !fVRoot );

    //
    // This may have been an upgrade (not a new root), so add back previous state.
    //

    if ( fWasAutomatic )
        _aMap[idNew].SetAutomatic();

    if ( fWasManual )
        _aMap[idNew].SetManual();

    //
    // Write out changes.
    //
    if ( fAppend )
        iter.AppendRec( &_aMap[idNew] );
    else
        iter.SetRec( &_aMap[idNew], idNew );
    //
    // Look for roots that used to point at parent.  They may need to be changed.
    // Only bother if root was not previously in use.
    //

    Win4Assert( !_fDirty );
    _fDirty = (fWasInUse != _aMap[idNew].IsInUse() || fPRootChanged);

    //
    // Put this root in the hierarchy.
    //

    if ( _fDirty && fVRoot )
    {
        for ( i = 0; i < _aMap.Count(); i++ )
        {
            if ( _aMap[i].IsFree() || !_aMap[i].IsInUse() )
                continue;

            //
            // If physical root changed, then we need to adjust a lot of parent pointers.
            // Stage one is equivalent to removing the old root.
            //

            if ( fPRootChanged && _aMap[i].Parent() == idNew )
            {
                Win4Assert( i != idNew );
                ciDebugOut(( DEB_ITRACE, "VRoot %d has new parent %d\n", i, idNew ));

                _aMap[i].SetParent( idOldParent );

                iter.SetRec( &_aMap[i], i );
            }

            if ( _aMap[i].Parent() != idParent )
                continue;

            if ( i == idNew )
                continue;

            //
            // Is this a longer physical path match?
            //

            unsigned ccMatch = _aMap[i].PhysicalMatchLen( root );

            if ( ccMatch >= ccPRoot )
            {
                ciDebugOut(( DEB_ITRACE, "VRoot %d has new parent %d\n", i, idNew ));

                _aMap[i].SetParent( idNew );

                iter.SetRec( &_aMap[i], i );
            }
        }
    }

    //
    // Finish transaction
    //

    struct CRcovUserHdr data;
    RtlCopyMemory( &data._abHdr, &_fDirty, sizeof(_fDirty) );

    hdr.SetUserHdr( hdr.GetBackup(), data );
    hdr.SetCount( hdr.GetBackup(), _aMap.Count() );

    xact.Commit();

    RecomputeNonIndexedInfo();
    DumpVMap();

    return _fDirty;
} //Add

//+-------------------------------------------------------------------------
//
//  Member:     CVMap::Remove, public
//
//  Synopsis:   Remove virtual/physical mapping
//
//  Arguments:  [vroot]            -- New virtual root
//              [fOnlyIfAutomatic] -- If TRUE, then a manual root will not
//                                    be removed
//              [idOld]            -- Id which was removed
//              [idNew]            -- Id which replaces [idOld] (farther up physical path)
//              [eType]            -- VRoot type
//              [fVRoot]           -- TRUE if a vroot, FALSE if a vdir
//
//  Returns:    TRUE if scope was removed
//
//  History:    11-Feb-96 KyleP     Created
//
//--------------------------------------------------------------------------

BOOL CVMap::Remove( WCHAR const * vroot,
                    BOOL fOnlyIfAutomatic,
                    ULONG & idOld,
                    ULONG & idNew,
                    CiVRootTypeEnum eType,
                    BOOL fVRoot )
{
    Win4Assert( eType == W3VRoot ||
                eType == NNTPVRoot ||
                eType == IMAPVRoot );

    CLock   lock(_mutex);

    unsigned ccVRoot = wcslen(vroot);

    //
    // Find id of root.
    //

    idOld = INVALID_VMAP_INDEX;

    for ( unsigned i = 0; i < _aMap.Count(); i++ )
    {
        BOOL fSameType = ( ( ( W3VRoot == eType ) && _aMap[i].IsW3() ) ||
                           ( ( NNTPVRoot == eType ) && _aMap[i].IsNNTP() ) ||
                           ( ( IMAPVRoot == eType ) && _aMap[i].IsIMAP() ) );

        if ( !_aMap[i].IsFree() &&
             fSameType &&
             _aMap[i].IsVirtualMatch( vroot, ccVRoot ) )
        {
            idOld = i;
            break;
        }
    }

    if ( idOld == INVALID_VMAP_INDEX )
        return FALSE;

    BOOL fWasInUse = _aMap[idOld].IsInUse();

    //
    // Turn off either automatic bit or manual bit.
    //

    if ( fOnlyIfAutomatic )
    {
        //
        // When a root is deleted by Gibraltar, it is *really* deleted.
        //

        _aMap[idOld].ClearAutomatic();
        _aMap[idOld].ClearManual();
        _aMap[idOld].ClearInUse();
    }
    else
    {
        //
        // If this is an automatic root, then put the
        // root under manual control (to keep it deleted),
        // otherwise clear the manual flag to free up the entry.
        //

        if ( _aMap[idOld].IsAutomatic() )
            _aMap[idOld].SetManual();
        else
            _aMap[idOld].ClearManual();

        _aMap[idOld].ClearInUse();
    }

    idNew = _aMap[i].Parent();

    //
    // Make persistent changes
    //

    CRcovStorageHdr & hdr = _xrsoMap->GetHeader();
    CRcovStrmWriteTrans xact( _xrsoMap.GetReference() );
    CRcovStrmWriteIter  iter( xact, sizeof(CVMapDesc) );

    if ( !_aMap[idOld].IsAutomatic() && !_aMap[idOld].IsManual() )
        _aMap[idOld].Delete();

    iter.SetRec( &_aMap[idOld], idOld );

    //
    // Look for roots that used to point here.  They will need to be changed.
    // Change *only* if we really decided we're not tracking this root.
    // Only bother if we really stopped using the root.
    //

#   if CIDBG == 1
    if( _aMap[idOld].IsFree() )
        Win4Assert( !_aMap[idOld].IsInUse() );
#   endif

    if ( !_aMap[idOld].IsInUse() && fWasInUse )
    {
        for ( i = 0; i < _aMap.Count(); i++ )
        {
            if ( _aMap[i].IsFree() )
                continue;

            if ( _aMap[i].Parent() == idOld )
            {
                ciDebugOut(( DEB_ITRACE, "VRoot %d has new parent %d\n", i, idNew ));

                _aMap[i].SetParent( idNew );

                iter.SetRec( &_aMap[i], i );
            }
        }
    }

    //
    // Finish transaction
    //

    Win4Assert( !_fDirty );
    ciDebugOut(( DEB_ITRACE, "fVRoot, isinuse %d, wasinuse %d\n",
                 _aMap[idOld].IsInUse(), fWasInUse ));
    _fDirty = (!fVRoot || !_aMap[idOld].IsInUse() && fWasInUse);
    struct CRcovUserHdr data;
    RtlCopyMemory( &data._abHdr, &_fDirty, sizeof(_fDirty) );

    hdr.SetUserHdr( hdr.GetBackup(), data );
    hdr.SetCount( hdr.GetBackup(), _aMap.Count() );

    xact.Commit();

    RecomputeNonIndexedInfo();
    DumpVMap();

    ciDebugOut(( DEB_ITRACE, "vmap::remove fDirty: %d\n", _fDirty ));

    return _fDirty;
} //Remove

//+-------------------------------------------------------------------------
//
//  Member:     CVMap::MarkClean, public
//
//  Synopsis:   Used to indicate all modifications based on path
//              addition/removal are complete.
//
//  History:    14-Feb-96 KyleP     Added header
//
//--------------------------------------------------------------------------

void CVMap::MarkClean()
{
    CLock   lock(_mutex);

    CRcovStorageHdr & hdr = _xrsoMap->GetHeader();
    CRcovStrmAppendTrans xact( _xrsoMap.GetReference() );

    _fDirty = FALSE;
    struct CRcovUserHdr data;
    RtlCopyMemory( &data._abHdr, &_fDirty, sizeof(_fDirty) );

    hdr.SetUserHdr( hdr.GetBackup(), data );
    hdr.SetCount( hdr.GetBackup(), _aMap.Count() );

    xact.Commit();
} //MarkClean

//+-------------------------------------------------------------------------
//
//  Member:     CVMap::PhysicalPathToId, private
//
//  Synopsis:   Given a physical path, find the virtual root that should
//              be associated with it.
//
//  Arguments:  [path]             -- Physical path
//              [fNonIndexedVDirs] -- If TRUE, returns 0xffffffff or a
//                                    non-indexed vdir.  If FALSE, returns
//                                    an indexed vroot or 0xffffffff.
//
//  Returns:    Id of virtual root
//
//  History:    11-Feb-96 KyleP     Added header
//
//--------------------------------------------------------------------------

ULONG CVMap::PhysicalPathToId(
    WCHAR const * path,
    BOOL          fNonIndexedVDirs )
{
    //
    // Find virtual parent.
    //

    unsigned ccPath = wcslen(path);

    ULONG idParent = INVALID_VMAP_INDEX;
    ULONG ccParent = 0;

    for ( unsigned i = 0; i < _aMap.Count(); i++ )
    {
        if ( _aMap[i].IsFree() ||
             ( !fNonIndexedVDirs && !_aMap[i].IsInUse() ) ||
             ( fNonIndexedVDirs && !_aMap[i].IsNonIndexedVDir() ) )
            continue;

        if ( !fNonIndexedVDirs )
        {
            BOOL fIgnore = FALSE;

            for ( unsigned x = 0; x < _aExcludeParent.Count(); x++ )
            {
                // If 'i' is a parent that should be excluded because the
                // file is in a non-indexed vdir of vroot 'i', ignore it.

                if ( i == _aExcludeParent[x] )
                {
                    Win4Assert( _aMap[x].IsNonIndexedVDir() );
                    if ( _aMap[x].IsInPhysicalScope( path, ccPath ) )
                    {
                        fIgnore = TRUE;
                        break;
                    }
                }
            }

            if ( fIgnore )
                continue;
        }

        //
        // Is this a longer physical path match?
        //

        unsigned ccMatch = _aMap[i].PhysicalMatchLen( path );

        if ( ccMatch > ccParent && ccMatch < ccPath )
        {
            ccParent = ccMatch;
            idParent = i;
        }
        else if ( ccMatch > 0 && ccMatch == ccParent )
        {
            //
            // Consider the case of multiple virtual roots pointing to the
            // same place in the physical heirarchy:
            //
            //                v0
            //                  \
            //                   \
            //       v1 -> v2 -> v3
            //
            // Files under v1/v2/v3 must be tagged with id v1, or we will
            // not recognize v1 as a valid virtual root for the file.  So
            // if we happened to find v2 or v3 first, we need to swap
            // roots if we can get to the old best match from the new best
            // match by traversing parent links.
            //

            for ( unsigned id = _aMap[i].Parent();
                  id != INVALID_VMAP_INDEX;
                  id = _aMap[id].Parent() )
            {
                //
                // Have we traversed up the tree?
                //

                if ( _aMap[id].PhysicalLength() < ccParent )
                    break;

                //
                // Did we find the previous parent?
                //

                if ( id == idParent )
                    break;
            }

            if ( id == idParent )
                idParent = i;
        }
    }

    return idParent;
} //PhysicalPathToId

//+-------------------------------------------------------------------------
//
//  Member:     CVMap::PhysicalPathToId, public
//
//  Synopsis:   Given a physical path, find the virtual root that should
//              be associated with it.
//
//  Arguments:  [path] -- Physical path
//
//  Returns:    Id of virtual root
//
//  History:    11-Feb-96 KyleP     Added header
//
//--------------------------------------------------------------------------

ULONG CVMap::PhysicalPathToId( WCHAR const * path )
{
    CLock   lock(_mutex);

    // first try an indexed vroot, but settle for a non-indexed-vdir

    ULONG id = PhysicalPathToId( path, FALSE );

    if ( INVALID_VMAP_INDEX == id )
        id = PhysicalPathToId( path, TRUE );

    return id;
} //PhysicalPathToId

//+-------------------------------------------------------------------------
//
//  Member:     CVMap::VirtualToPhysicalRoot, public
//
//  Synopsis:   Given a virtual path, returns corresponding physical root.
//              Unlike the iterative version of this method, this version
//              requires an exact match on virtual root.
//
//  Arguments:  [pwcVRoot] -- Virtual root
//              [ccVRoot]  -- Size in chars of [pwcVRoot]
//              [lcaseFunnyPRoot] -- Physical root
//              [ccPRoot]         -- returns actual count of chars in  [lcaseFunnyPRoot]
//
//  Returns:    TRUE if match was found.
//
//  History:    11-Feb-96  KyleP    Created
//              15 Mar 96  AlanW    Fixed bugs in enumeration which missed
//                                  some roots and erroneously added others.
//
//--------------------------------------------------------------------------

BOOL CVMap::VirtualToPhysicalRoot( WCHAR const * pwcVRoot,
                                   unsigned ccVRoot,
                                   CLowerFunnyPath & lcaseFunnyPRoot,
                                   unsigned & ccPRoot )
{
    //
    // Find id of root.
    //

    unsigned iBmk = INVALID_VMAP_INDEX;

    for ( unsigned i = 0; i < _aMap.Count(); i++ )
    {
        if ( !_aMap[i].IsFree() &&
             _aMap[i].IsVirtualMatch( pwcVRoot, ccVRoot ) )
        {
            iBmk = i;
            break;
        }
    }

    if ( iBmk == INVALID_VMAP_INDEX )
        return FALSE;

    //
    // Copy out physical root.
    //

    ccPRoot = _aMap[iBmk].PhysicalLength() - 1;

    //
    // Special case root.
    //

    if ( _aMap[iBmk].PhysicalPath()[1] == L':' )
    {
        if ( ccPRoot == 2 )
            ccPRoot++;
    }
    else
    {
        unsigned cSlash = 0;

        for ( unsigned i = 0; i < ccPRoot && cSlash < 4; i++ )
        {
            if ( _aMap[iBmk].PhysicalPath()[i] == L'\\' )
                cSlash++;
        }

        if ( cSlash < 4 )
        {
            Win4Assert( cSlash == 3 );
            ccPRoot++;
        }
    }

    lcaseFunnyPRoot.SetPath( _aMap[iBmk].PhysicalPath(), ccPRoot );

    return TRUE;
} //VirtualToPhysicalRoot

//+-------------------------------------------------------------------------
//
//  Member:     CVMap::VirtualToPhysicalRoot, public
//
//  Synopsis:   Given a virtual path, returns a virtual root under that
//              virtual path, and the corresponding physical root.  Will
//              not return overlapping virtual roots.
//
//              There may be multiple virtual roots that match the virtual
//              path passed in, so this should be called in a loop until
//              FALSE is returned.  The [iBmk] should be zero for the first
//              call in the iteration.
//
//  Arguments:  [pwcVPath]  -- Virtual path
//              [ccVPath]   -- Size in chars of [pwcVPath]
//              [xwcsVRoot] -- Virtual root
//              [ccVRoot]   -- returns count of chars in [xwcsVRoot]
//              [lcaseFunnyPRoot] -- Physical root
//              [ccPRoot]   -- Size in actual chars of [lcaseFunnyPRoot]
//              [iBmk]      -- Bookmark for iteration.
//
//  Returns:    TRUE if match was found.
//
//  History:    11-Feb-96  KyleP    Created
//              15 Mar 96  AlanW    Fixed bugs in enumeration which missed
//                                  some roots and erroneously added others.
//              24 Nov 99  KLam     Don't pre-reference zero length strings.
//
//--------------------------------------------------------------------------

BOOL CVMap::VirtualToPhysicalRoot( WCHAR const * pwcVPath,
                                   unsigned ccVPath,
                                   XGrowable<WCHAR> & xwcsVRoot,
                                   unsigned & ccVRoot,
                                   CLowerFunnyPath & lcaseFunnyPRoot,
                                   unsigned & ccPRoot,
                                   unsigned & iBmk )
{

    CLock   lock(_mutex);


    //
    // Path must be terminated with a backslash, or the path can be of
    // 0 characters.
    //

    XGrowable<WCHAR> xwcTemp;

    if ( (0 != ccVPath) && (pwcVPath[ccVPath-1] != L'\\') )
    {
        xwcTemp.SetSize( ccVPath + 1 );
        RtlCopyMemory( xwcTemp.Get(), pwcVPath, ccVPath * sizeof(WCHAR) );
        xwcTemp[ccVPath] = L'\\';

        pwcVPath = xwcTemp.Get();
        ccVPath++;
    }

    CScopeMatch Match( pwcVPath, ccVPath );

    for ( ; iBmk < _aMap.Count(); iBmk++ )
    {
        if ( _aMap[iBmk].IsFree() || !_aMap[iBmk].IsInUse() )
            continue;

        //
        // Possible match?
        //

        if ( Match.IsInScope( _aMap[iBmk].VirtualPath(),
                              _aMap[iBmk].VirtualLength() ) )
        {
            //
            // The virtual root is a match.  If there is a parent of
            // this virtual root in the physical name space, ignore this
            // one in favor of the other one, farther up the tree (as long
            // as the parent root is also a scope match).
            //

            for ( unsigned idParent = _aMap[ iBmk ].Parent();
                  idParent != INVALID_VMAP_INDEX;
                  idParent = _aMap[idParent].Parent() )
            {
                if ( Match.IsInScope( _aMap[idParent].VirtualPath(),
                                      _aMap[idParent].VirtualLength() ) )
                    break;
            }

            //
            // Did we get all the way to the top of chain without
            // finding another match?
            //

            if ( idParent == INVALID_VMAP_INDEX )
                break;
        }

        if ( Match.IsPrefix( _aMap[iBmk].VirtualPath(),
                             _aMap[iBmk].VirtualLength() ) )
        {
            //
            // The virtual root is a prefix of the path.  Return it only
            // if there is not some other vroot that is a better match.
            //

            BOOL fBetterMatch = FALSE;
            for ( unsigned iRoot = 0;
                  !fBetterMatch && iRoot < _aMap.Count();
                  iRoot++ )
            {
                if ( iRoot == iBmk ||
                     _aMap[iRoot].IsFree() )
                    continue;

                if ( _aMap[iBmk].VirtualLength() < _aMap[iRoot].VirtualLength() &&
                    Match.IsPrefix( _aMap[iRoot].VirtualPath(),
                                    _aMap[iRoot].VirtualLength() ) )
                {
                    fBetterMatch = TRUE;
                }
            }

            //
            //  Was there no better match?  If so, return this root.
            //
            if ( ! fBetterMatch )
                break;
        }
    }

    if ( iBmk < _aMap.Count() )
    {
        if ( ccVPath > _aMap[iBmk].VirtualLength() )
        {
            ccVRoot = ccVPath;
            xwcsVRoot.SetBuf( pwcVPath, ccVPath );

            unsigned ccExtra = ccVPath - _aMap[iBmk].VirtualLength();
            ccPRoot = _aMap[iBmk].PhysicalLength() + ccExtra;

            lcaseFunnyPRoot.SetPath( _aMap[iBmk].PhysicalPath(), _aMap[iBmk].PhysicalLength() );
            lcaseFunnyPRoot.AppendPath( pwcVPath + _aMap[iBmk].VirtualLength(), ccExtra );
        }
        else
        {
            ccVRoot = _aMap[iBmk].VirtualLength();
            xwcsVRoot.SetBuf( _aMap[iBmk].VirtualPath(), ccVRoot * sizeof(WCHAR) );

            lcaseFunnyPRoot.SetPath( _aMap[iBmk].PhysicalPath(), ccPRoot = _aMap[iBmk].PhysicalLength() );
        }
        iBmk++;
        return TRUE;
    }
    else
        return FALSE;
} //VirtualToPhysicalRoot

//+-------------------------------------------------------------------------
//
//  Member:     CVMap::VirtualToAllPhysicalRoots, public
//
//  Synopsis:   Like VirtualToPhysicalRoot, except returns all matches
//              (rather than just the highest parent which matches)
//              and also returns non-indexed matches.  Returns type
//              in ulType so caller can decide what to do with
//              non-indexed matches.
//
//  Arguments:  [pwcVPath]  -- Virtual path
//              [ccVPath]   -- Size in chars of [pwcVPath]
//              [xwcsVRoot] -- Virtual root
//              [ccVRoot]   -- returns count of chars in [pwcVRoot]
//              [lcaseFunnyPRoot] -- Physical root
//              [ccPRoot]   -- returns count of actual chars in [lcaseFunnyPRoot]
//              [ulType]    -- Match type
//              [iBmk]      -- Bookmark for iteration.
//
//  Returns:    TRUE if match was found.
//
//  History:    01-Sep-97     Emilyb   Created
//
//--------------------------------------------------------------------------

BOOL CVMap::VirtualToAllPhysicalRoots( WCHAR const * pwcVPath,
                                       unsigned ccVPath,
                                       XGrowable<WCHAR> & xwcsVRoot,
                                       unsigned & ccVRoot,
                                       CLowerFunnyPath & lcaseFunnyPRoot,
                                       unsigned & ccPRoot,
                                       ULONG & ulType,
                                       unsigned & iBmk )
{

    CLock   lock(_mutex);


    //
    // Path must be terminated with a backslash, or the path can be of
    // 0 characters.
    //

    XGrowable<WCHAR> xwcTemp;

    if ( ( 0 != ccVPath ) && ( pwcVPath[ccVPath-1] != L'\\' ) )
    {
        xwcTemp.SetSize( ccVPath + 1 );
        RtlCopyMemory( xwcTemp.Get(), pwcVPath, ccVPath * sizeof(WCHAR) );
        xwcTemp[ccVPath] = L'\\';

        pwcVPath = xwcTemp.Get();
        ccVPath++;
    }

    CScopeMatch Match( pwcVPath, ccVPath );

    for ( ; iBmk < _aMap.Count(); iBmk++ )
    {
        if ( _aMap[iBmk].IsFree() )
            continue;

        //
        // Possible match?
        //

        if ( Match.IsInScope( _aMap[iBmk].VirtualPath(),
                              _aMap[iBmk].VirtualLength() ) )
        {
            break;
        }

        if ( Match.IsPrefix( _aMap[iBmk].VirtualPath(),
                             _aMap[iBmk].VirtualLength() ) )
        {
            //
            // The virtual root is a prefix of the path.  Return it only
            // if there is not some other vroot that is a better match.
            //

            BOOL fBetterMatch = FALSE;
            for ( unsigned iRoot = 0;
                  !fBetterMatch && iRoot < _aMap.Count();
                  iRoot++ )
            {
                if ( iRoot == iBmk ||
                     _aMap[iRoot].IsFree() )
                    continue;

                if ( _aMap[iBmk].VirtualLength() < _aMap[iRoot].VirtualLength() &&
                    Match.IsPrefix( _aMap[iRoot].VirtualPath(),
                                    _aMap[iRoot].VirtualLength() ) )
                {
                    fBetterMatch = TRUE;
                }
            }

            //
            //  Was there no better match?  If so, return this root.
            //
            if ( ! fBetterMatch )
                break;
        }
    }


    if ( iBmk < _aMap.Count() )
    {
        if ( ccVPath > _aMap[iBmk].VirtualLength() )
        {
            ccVRoot = ccVPath;
            xwcsVRoot.SetBuf( pwcVPath, ccVPath );

            unsigned ccExtra = ccVPath - _aMap[iBmk].VirtualLength();
            ccPRoot = _aMap[iBmk].PhysicalLength() + ccExtra;

            lcaseFunnyPRoot.SetPath( _aMap[iBmk].PhysicalPath(), _aMap[iBmk].PhysicalLength() );
            lcaseFunnyPRoot.AppendPath( pwcVPath + _aMap[iBmk].VirtualLength(), ccExtra );
        }
        else
        {
            ccVRoot = _aMap[iBmk].VirtualLength();
            xwcsVRoot.SetBuf( _aMap[iBmk].VirtualPath(), ccVRoot );

            lcaseFunnyPRoot.SetPath( _aMap[iBmk].PhysicalPath(), ccPRoot = _aMap[iBmk].PhysicalLength() );
        }
        ulType = _aMap[iBmk].RootType();

        iBmk++;
        return TRUE;
    }
    else
        return FALSE;
} //VirtualToAllPhysicalRoots


//+-------------------------------------------------------------------------
//
//  Member:     CVMap::EnumerateRoot, public
//
//  Synopsis:   Enumerate all virtual paths
//
//  Arguments:  [xwcVRoot] -- Virtual root
//              [ccVRoot]  -- returns count of chars in [xwcVRoot]
//              [lcaseFunnyPRoot] -- Physical root as funny path
//              [ccPRoot]  -- returns count of actual chars in [lcaseFunnyPRoot]
//              [iBmk]     -- Bookmark for iteration.
//
//  Returns:    Type of root (Manual or Automatic)
//
//  History:    15-Feb-96 KyleP     Created
//
//--------------------------------------------------------------------------

ULONG CVMap::EnumerateRoot( XGrowable<WCHAR> & xwcVRoot,
                            unsigned & ccVRoot,
                            CLowerFunnyPath & lcaseFunnyPRoot,
                            unsigned & ccPRoot,
                            unsigned & iBmk )
{

    CLock   lock(_mutex);

    for ( ; iBmk < _aMap.Count(); iBmk++ )
    {
        if ( !_aMap[iBmk].IsFree() )
            break;
    }

    if ( iBmk < _aMap.Count() )
    {
        ccVRoot = _aMap[iBmk].VirtualLength() - 1;

        //
        // Special case root.
        //

        if ( 0 == ccVRoot )
            ccVRoot++;

        xwcVRoot.SetSize( ccVRoot + 1 );
        xwcVRoot.SetBuf( _aMap[iBmk].VirtualPath(), ccVRoot );
        xwcVRoot[ccVRoot] = 0;

        ccPRoot = _aMap[iBmk].PhysicalLength() - 1;

        //
        // Special case root.
        //

        if ( _aMap[iBmk].PhysicalPath()[1] == L':' )
        {
            if ( ccPRoot == 2 )
                ccPRoot++;
        }
        else
        {
            unsigned cSlash = 0;

            for ( unsigned i = 0; i < ccPRoot && cSlash < 4; i++ )
            {
                if ( _aMap[iBmk].PhysicalPath()[i] == L'\\' )
                    cSlash++;
            }

            if ( cSlash < 4 )
            {
                Win4Assert( cSlash == 3 );
                ccPRoot++;
            }
        }


        lcaseFunnyPRoot.SetPath( _aMap[iBmk].PhysicalPath(), ccPRoot );

        ULONG eType = _aMap[iBmk].RootType();

        iBmk++;

        return eType;
    }
    else
        return (ULONG) PCatalog::EndRoot;
} //EnumerateRoot

//+-------------------------------------------------------------------------
//
//  Member:     CVMap::DoesPhysicalRootExist, public
//
//  Synopsis:   Determines whether a physical root is in the table
//
//  Arguments:  [pwcPRoot] -- Physical root to find
//
//  Returns:    TRUE if the physical root exists in the table
//
//  History:    16-Oct-96 dlee     Created
//
//--------------------------------------------------------------------------

BOOL CVMap::DoesPhysicalRootExist(
    WCHAR const * pwcPRoot )
{
    unsigned cwcPRoot = wcslen( pwcPRoot );

    for ( unsigned i = 0; i < _aMap.Count(); i++ )
    {
        if ( !_aMap[i].IsFree() &&
             !_aMap[i].IsNonIndexedVDir() &&
             _aMap[i].IsPhysicalMatch( pwcPRoot, cwcPRoot ) )
            return TRUE;
    }

    return FALSE;
} //DoesPhysicalRootExist